前两章分别介绍了智能体的定义和发展历史,本章将完全聚焦于大语言模型本身解答一个关键问题:现代智能体是如何工作的?我们将从语言模型的基本定义出发,通过对这些原理的学习,为理解LLM如何获得强大的知识储备与推理能力打下坚实的基础。
3.1.1从N-gram到RNN
语言模型 (Language Model, LM) 是自然语言处理的核心,其根本任务是计算一个词序列(即一个句子)出现的概率。一个好的语言模型能够告诉我们什么样的句子是通顺的、自然的。在多智能体系统中,语言模型是智能体理解人类指令、生成回应的基础。本节将回顾从经典的统计方法到现代深度学习模型的演进历程,为理解后续的 Transformer 架构打下坚实的基础。
(1)统计语言模型与N-gram的思想
在深度学习兴起之前,统计方法是语言模型的主流。其核心思想是,一个句子出现的概率,等于该句子中每个词出现的条件概率的连乘。对于一个由词 w_1,w_2,\cdots,w_mw1,w2,⋯,wm 构成的句子 S,其概率 P(S) 可以表示为:
P(S)=P(w_1,w_2,...,w_m)=P(w_1)⋅P(w_2∣w_1)⋅P(w_3∣w_1,w_2)⋯P(w_m∣w_1,...,w_{m−1})P(S)=P(w1,w2,...,wm)=P(w1)⋅P(w2∣w1)⋅P(w3∣w1,w2)⋯P(wm∣w1,...,wm−1)
这个公式被称为概率的链式法则。然而,直接计算这个公式几乎是不可能的,因为像 P(w_m∣w_1,\cdots,w_{m−1})P(wm∣w1,⋯,wm−1) 这样的条件概率太难从语料库中估计了,词序列 w_1,\cdots,w_{m−1}w1,⋯,wm−1 可能从未在训练数据中出现过。

图 3.1 马尔可夫假设示意图
为了解决这个问题,研究者引入了马尔可夫假设 (Markov Assumption) 。其核心思想是:我们不必回溯一个词的全部历史,可以近似地认为,一个词的出现概率只与它前面有限的 n−1n−1 个词有关,如图3.1所示。基于这个假设建立的语言模型,我们称之为 N-gram模型。这里的 "N" 代表我们考虑的上下文窗口大小。让我们来看几个最常见的例子来理解这个概念:
- Bigram (当 N=2 时) :这是最简单的情况,我们假设一个词的出现只与它前面的一个词有关。因此,链式法则中复杂的条件概率 P(w_i∣w_1,\cdots,w_{i−1})P(wi∣w1,⋯,wi−1) 就可以被近似为更容易计算的形式:
P(w_{i}∣w_{1},...,w_{i−1})≈P(w_{i}∣w_{i−1})P(wi∣w1,...,wi−1)≈P(wi∣wi−1)
- Trigram (当 N=3 时) :类似地,我们假设一个词的出现只与它前面的两个词有关:
P(w_i∣w_1,...,w_{i−1})≈P(w_i∣w_{i−2},w_{i−1})P(wi∣w1,...,wi−1)≈P(wi∣wi−2,wi−1)
这些概率可以通过在大型语料库中进行最大似然估计(Maximum Likelihood Estimation,MLE) 来计算。这个术语听起来很复杂,但其思想非常直观:最可能出现的,就是我们在数据中看到次数最多的。例如,对于 Bigram 模型,我们想计算在词 w_{i−1}wi−1 出现后,下一个词是 w_iwi 的概率 P(w_i∣w_{i−1})P(wi∣wi−1)。根据最大似然估计,这个概率可以通过简单的计数来估算:
P(w_i∣w_{i−1})=\frac{Count(w_{i−1},w_i)}{Count(w_{i−1})}P(wi∣wi−1)=Count(wi−1)Count(wi−1,wi)
这里的 Count() 函数就代表"计数":
- Count(w_{i−1},w_i)Count(wi−1,wi):表示词对 (w_{i−1},w_i)(wi−1,wi) 在语料库中连续出现的总次数。
- Count(w_{i−1})Count(wi−1):表示单个词 w_{i−1}wi−1 在语料库中出现的总次数。
公式的含义就是:我们用"词对 Count(w_{i−1},w_i)Count(wi−1,wi) 出现的次数"除以"词 Count(w_{i−1})Count(wi−1) 出现的总次数",来作为 P(w_i∣w_{i−1})P(wi∣wi−1) 的一个近似估计。
为了让这个过程更具体,我们来手动进行一次计算。假设我们拥有一个仅包含以下两句话的迷你语料库:datawhale agent learns, datawhale agent works。我们的目标是:使用 Bigram (N=2) 模型,估算句子 datawhale agent learns 出现的概率。根据 Bigram 的假设,我们每次会考察连续的两个词(即一个词对)。
第一步:计算第一个词的概率 P(datawhale)P(datawhale) 这是 datawhale 出现的次数除以总词数。datawhale 出现了 2 次,总词数是 6。
P(\text{datawhale}) = \frac{\text{总语料中"datawhale"的数量}}{\text{总语料的词数}} = \frac{2}{6} \approx 0.333P(datawhale)=总语料的词数总语料中"datawhale"的数量=62≈0.333
第二步:计算条件概率 P(agent∣datawhale)P(agent∣datawhale) 这是词对 datawhale agent 出现的次数除以 datawhale 出现的总次数。datawhale agent 出现了 2 次,datawhale 出现了 2 次。
P(\text{agent}|\text{datawhale}) = \frac{\text{Count}(\text{datawhale agent})}{\text{Count}(\text{datawhale})} = \frac{2}{2} = 1P(agent∣datawhale)=Count(datawhale)Count(datawhale agent)=22=1
第三步:计算条件概率 P(learns∣agent)P(learns∣agent) 这是词对 agent learns 出现的次数除以 agent 出现的总次数。agent learns 出现了 1 次,agent 出现了 2 次。
P(\text{learns}|\text{agent}) = \frac{\text{Count(agent learns)}}{\text{Count(agent)}} = \frac{1}{2} = 0.5P(learns∣agent)=Count(agent)Count(agent learns)=21=0.5
最后:将概率连乘 所以,整个句子的近似概率为:
P(\text{datawhale agent learns}) \approx P(\text{datawhale}) \cdot P(\text{agent}|\text{datawhale}) \cdot P(\text{learns}|\text{agent}) \approx 0.333 \cdot 1 \cdot 0.5 \approx 0.167P(datawhale agent learns)≈P(datawhale)⋅P(agent∣datawhale)⋅P(learns∣agent)≈0.333⋅1⋅0.5≈0.167
ini
import collections
# 示例语料库,与上方案例讲解中的语料库保持一致
corpus = "datawhale agent learns datawhale agent works"
tokens = corpus.split()
total_tokens = len(tokens)
# --- 第一步:计算 P(datawhale) ---
count_datawhale = tokens.count('datawhale')
p_datawhale = count_datawhale / total_tokens
print(f"第一步: P(datawhale) = {count_datawhale}/{total_tokens} = {p_datawhale:.3f}")
# --- 第二步:计算 P(agent|datawhale) ---
# 先计算 bigrams 用于后续步骤
bigrams = zip(tokens, tokens[1:])
bigram_counts = collections.Counter(bigrams)
count_datawhale_agent = bigram_counts[('datawhale', 'agent')]
# count_datawhale 已在第一步计算
p_agent_given_datawhale = count_datawhale_agent / count_datawhale
print(f"第二步: P(agent|datawhale) = {count_datawhale_agent}/{count_datawhale} = {p_agent_given_datawhale:.3f}")
# --- 第三步:计算 P(learns|agent) ---
count_agent_learns = bigram_counts[('agent', 'learns')]
count_agent = tokens.count('agent')
p_learns_given_agent = count_agent_learns / count_agent
print(f"第三步: P(learns|agent) = {count_agent_learns}/{count_agent} = {p_learns_given_agent:.3f}")
# --- 最后:将概率连乘 ---
p_sentence = p_datawhale * p_agent_given_datawhale * p_learns_given_agent
print(f"最后: P('datawhale agent learns') ≈ {p_datawhale:.3f} * {p_agent_given_datawhale:.3f} * {p_learns_given_agent:.3f} = {p_sentence:.3f}")
>>>
第一步: P(datawhale) = 2/6 = 0.333
第二步: P(agent|datawhale) = 2/2 = 1.000
第三步: P(learns|agent) = 1/2 = 0.500
最后: P('datawhale agent learns') ≈ 0.333 * 1.000 * 0.500 = 0.167Copy to clipboardErrorCopied
N-gram 模型虽然简单有效,但有两个致命缺陷:
- 数据稀疏性 (Sparsity) :如果一个词序列从未在语料库中出现,其概率估计就为 0,这显然是不合理的。虽然可以通过平滑 (Smoothing) 技术缓解,但无法根除。
- 泛化能力差: 模型无法理解词与词之间的语义相似性。例如,即使模型在语料库中见过很多次
agent learns,它也无法将这个知识泛化到语义相似的词上。当我们计算robot learns的概率时,如果robot这个词从未出现过,或者robot learns这个组合从未出现过,模型计算出的概率也会是零。模型无法理解agent和robot在语义上的相似性。
(2)神经网络语言模型与词嵌入
N-gram 模型的根本缺陷在于它将词视为孤立、离散的符号。为了克服这个问题,研究者们转向了神经网络,并提出了一种思想:用连续的向量来表示词。2003年,Bengio 等人提出的前馈神经网络语言模型 (Feedforward Neural Network Language Model) 是这一领域的里程碑1。
其核心思想可以分为两步:
- 构建一个语义空间 :创建一个高维的连续向量空间,然后将词汇表中的每个词都映射为该空间中的一个点。这个点(即向量)就被称为词嵌入 (Word Embedding) 或词向量。在这个空间里,语义上相近的词,它们对应的向量在空间中的位置也相近。例如,
agent和robot的向量会靠得很近,而agent和apple的向量会离得很远。 - 学习从上下文到下一个词的映射:利用神经网络的强大拟合能力,来学习一个函数。这个函数的输入是前 n−1n−1 个词的词向量,输出是词汇表中每个词在当前上下文后出现的概率分布。

图 3.2 神经网络语言模型架构示意图
如图3.2所示,在这个架构中,词嵌入是在模型训练过程中自动学习得到的。模型为了完成"预测下一个词"这个任务,会不断调整每个词的向量位置,最终使这些向量能够蕴含丰富的语义信息。一旦我们将词转换成了向量,我们就可以用数学工具来度量它们之间的关系。最常用的方法是余弦相似度 (Cosine Similarity) ,它通过计算两个向量夹角的余弦值来衡量它们的相似性。
\text{similarity}(\vec{a}, \vec{b}) = \cos(\theta) = \frac{\vec{a} \cdot \vec{b}}{|\vec{a}| |\vec{b}|}similarity(a,b)=cos(θ)=∣a∣∣b∣a⋅b
这个公式的含义是:
- 如果两个向量方向完全相同,夹角为0°,余弦值为1,表示完全相关。
- 如果两个向量方向正交,夹角为90°,余弦值为0,表示毫无关系。
- 如果两个向量方向完全相反,夹角为180°,余弦值为-1,表示完全负相关。
通过这种方式,词向量不仅能捕捉到"同义词"这类简单的关系,还能捕捉到更复杂的类比关系。
一个著名的例子展示了词向量捕捉到的语义关系: vector('King') - vector('Man') + vector('Woman') 这个向量运算的结果,在向量空间中与 vector('Queen') 的位置惊人地接近。这好比在进行语义的平移:我们从"国王"这个点出发,减去"男性"的向量,再加上"女性"的向量,最终就抵达了"女王"的位置。这证明了词嵌入能够学习到"性别"、"皇室"这类抽象概念。
python
import numpy as np
# 假设我们已经学习到了简化的二维词向量
embeddings = {
"king": np.array([0.9, 0.8]),
"queen": np.array([0.9, 0.2]),
"man": np.array([0.7, 0.9]),
"woman": np.array([0.7, 0.3])
}
def cosine_similarity(vec1, vec2):
dot_product = np.dot(vec1, vec2)
norm_product = np.linalg.norm(vec1) * np.linalg.norm(vec2)
return dot_product / norm_product
# king - man + woman
result_vec = embeddings["king"] - embeddings["man"] + embeddings["woman"]
# 计算结果向量与 "queen" 的相似度
sim = cosine_similarity(result_vec, embeddings["queen"])
print(f"king - man + woman 的结果向量: {result_vec}")
print(f"该结果与 'queen' 的相似度: {sim:.4f}")
>>>
king - man + woman 的结果向量: [0.9 0.2]
该结果与 'queen' 的相似度: 1.0000Copy to clipboardErrorCopied
神经网络语言模型通过词嵌入,成功解决了 N-gram 模型的泛化能力差的问题。然而,它仍然有一个类似 N-gram 的限制:上下文窗口是固定的。它只能考虑固定数量的前文,这为能处理任意长序列的循环神经网络埋下了伏笔。
(3)循环神经网络(RNN)与长短时记忆网络(LSTM) 前一节的神经网络语言模型虽然引入了词嵌入解决了泛化问题,但它和 N-gram 模型一样,上下文窗口是固定大小的。为了预测下一个词,它只能看到前 n−1 个词,再早的历史信息就被丢弃了。这显然不符合我们人类理解语言的方式。为了打破固定窗口的限制,循环神经网络 (Recurrent Neural Network, RNN) 应运而生,其核心思想非常直观:为网络增加"记忆"能力2。
如图3.3所示,RNN 的设计引入了一个隐藏状态 (hidden state) 向量,我们可以将其理解为网络的短期记忆。在处理序列的每一步,网络都会读取当前的输入词,并结合它上一刻的记忆(即上一个时间步的隐藏状态),然后生成一个新的记忆(即当前时间步的隐藏状态)传递给下一刻。这个循环往复的过程,使得信息可以在序列中不断向后传递。

图 3.3 RNN 结构示意图
然而,标准的 RNN 在实践中存在一个严重的问题:长期依赖问题 (Long-term Dependency Problem) 。在训练过程中,模型需要通过反向传播算法根据输出端的误差来调整网络深处的权重。对于 RNN 而言,序列的长度就是网络的深度。当序列很长时,梯度在从后向前传播的过程中会经过多次连乘,这会导致梯度值快速趋向于零(梯度消失 )或变得极大(梯度爆炸)。梯度消失使得模型无法有效学习到序列早期信息对后期输出的影响,即难以捕捉长距离的依赖关系。
为了解决长期依赖问题,长短时记忆网络 (Long Short-Term Memory, LSTM) 被设计出来3。LSTM 是一种特殊的 RNN,其核心创新在于引入了细胞状态 (Cell State) 和一套精密的门控机制 (Gating Mechanism) 。细胞状态可以看作是一条独立于隐藏状态的信息通路,允许信息在时间步之间更顺畅地传递。门控机制则是由几个小型神经网络构成,它们可以学习如何有选择地让信息通过,从而控制细胞状态中信息的增加与移除。这些门包括:
- 遗忘门 (Forget Gate) :决定从上一时刻的细胞状态中丢弃哪些信息。
- 输入门 (Input Gate) :决定将当前输入中的哪些新信息存入细胞状态。
- 输出门 (Output Gate) :决定根据当前的细胞状态,输出哪些信息到隐藏状态。