什么是向量(3blue1brown)
💡洞见
向量本身没有意义,只是表示“一组数据”;而向量赋予了它们计算性,有了“计算性”,他们就多出了其他的性质
向量”本身并不存在于数字之中,而存在于我们赋予这些数字的“规则”之中
它是一种对数据附加线性结构的选择;
一旦做了这种选择,线性代数这一整套“计算性”就可以上场,
由此产生了许多新的可谈论的性质与模式。
数学形式: z=x+y
基础性质: 交换律、结合律、存在逆元(构成阿贝尔群)。
在向量微积分中,标量场 f 在某一点的梯度,是一个指向该点函数值增长最快方向的向量。梯度的模长(大小)代表了该方向上的最大变化率。
即对于一个 n 维变量的函数 f(x),其中 x=[x1,x2,…,xn]T,其梯度记作 ∇f(x) 或 grad f(x)。
它是由函数对每一个变量的偏导数(Partial Derivatives) 组成的向量
∇f(x)=∂x1∂f∂x2∂f⋮∂xn∂f
最速上升方向:∇f 指向函数值增长最快的方向。
注:在机器学习中,我们通常需要最小化损失,所以我们沿梯度的负方向(−∇f)移动,这被称为“梯度下降”。
垂直于等高线 (Orthogonality):在任何一点,梯度向量总是与经过该点的等高线(在二维)或等值面(在高维)垂直(正交)。
线性近似 (Linear Approximation):梯度是函数在某一点进行线性逼近的最佳系数。
f(x+Δx)≈f(x)+∇f(x)⋅Δx
梯度下降算法 (Gradient Descent):
这是训练大模型的核心引擎。通过迭代更新参数 θ 来最小化损失函数 J(θ):
θnew=θold−η⋅∇J(θold)
| 符号 | 名称 | 含义与作用 |
|---|
| w | 模型参数 (Weights) | 模型需要学习的权重向量,决定了预测的结果。 |
| η | 学习率 (Learning Rate) | 步长控制参数,决定了每一步更新的幅度大小。 |
| ∇L(w) | 梯度向量 (Gradient) | 损失函数对参数的偏导数,指向函数增长最快的方向。 |
| − | 负号 (Negative Sign) | 代表梯度反方向,确保参数向损失函数减小的方向(下山)移动。 |
🧠洞见
在教科书里,为了让你明白“下山”的道理,老师只能画个山谷。但这给很多人造成了误解。
梯度“整体性”是向量这一数学工具赋予的,数据本身是散沙。是线性代数(向量空间理论)把散沙聚成塔。
你就是说一个高维向量的集合,你要把它看成若干个偏导数的集合,而不是一个整体的概念 (因为在高维视角下,这个意义过于抽象)。
整体意义的核心:资源分配的“最优解”。分散的每一维度告诉你的是“如果只动我,会发生什么”。组合后的梯度告诉你的是“如果大家配合起来一起动,怎么动才能在付出同样代价的情况下,收益最大化”。这就是那个“整体的意义”:它是局部最优的合成方向。
P(A发生的事件B条件)=P(B)P(A∩B)集合表示=P(B)P(A,B)逗号表示=P(B)P(AB)简写表示 表示在已知事件 B 发生的条件下,事件 A 发生的概率 其中 P(B)>0
条件概率链式法则是概率论中的一个基本规则,它表明:任意一组随机变量的联合概率可以分解为一系列条件概率的乘积。
对于 N 个随机变量 X1,X2,...,XN,链式法则表述为:
P(X1,X2,...,XN)=P(X1)⋅P(X2∣X1)⋅P(X3∣X1,X2)⋯P(XN∣X1,X2,...,XN−1)
用连乘符号简洁表示为:
P(X1:N)=∏i=1NP(Xi∣X1:i−1)
其中:
X1:N 表示从 X1 到 XN 的整个序列
X1:i−1 表示从 X1 到 Xi−1 的历史序列
当 i=1 时,X1:0 为空集,所以 P(X1∣X1:0)=P(X1)
例子:
P("我 喜欢 机器 学习")=P("我") ×P("喜欢"∣"我") ×P("机器"∣"我 喜欢") ×P("学习"∣"我 喜欢 机器")
最大似然估计(Maximum Likelihood Estimation, MLE)是一种参数估计方法,旨在寻找使观测样本出现概率最大的参数值。
给定独立同分布(i.i.d.)的样本集D={x1,x2,...,xn}
1.设总体 X 为离散型随机变量,其概率质量函数(PMF)为
P(X=x;θ未知参数)=p(x;θ未知参数) 其中 (θ∈Θ⊆Rk) 是未知参数,(Θ) 为参数空间。
在概率论统计学的域中,最重要的是要记住一个默认条件:总概率质量 ∑P(X=x) = 1
例如:
泊松分布 X∼Poisson(λ未知参数)
💡洞见
参数估计的直观理解:可以将统计学中的参数θ类比于物理学公式M=mg中的重力加速度g。在这一类比中:
- 写出似然函数
L(θ以参数为变量∣D样本域(类似于定义域))
似然函数定义为:
L(θ∣D)=P(D∣θ)=i=1∏np(xi∣θ)
- 取对数得到ℓ(θ∣D)
对数似然函数(便于计算):
ℓ(θ∣D)=logL(θ∣D)=i=1∑nlogp(xi∣θ)
- 对参数θ求导:∂θ∂ℓ(θ∣D)=0
最大似然估计值θ^为:
θ^=θargmaxL(θ∣D)=θargmaxℓ(θ∣D)
- 验证解是否为最大值点(二阶导数检验或边界分析)
⚠️
这里的竖线 "|" 不表示条件概率,而是表示参数依赖关系。更准确的写法应该是 p(x;θ) 或 pθ(x),但统计学中习惯用 p(x∣θ)。
为什么用这种记号:
- 历史惯例:在贝叶斯统计中,参数被视为随机变量,此时 p(x|θ) 确实表示条件概率
- 计算便利:在最大似然估计中,我们可以把 p(x|θ) 看作关于 θ 的函数(即似然函数)
更好的理解方式:
当您看到 p(x|θ) 时,需要根据上下文判断它是表示参数依赖还是条件概率。在最大似然估计的上下文中,它通常表示参数依赖关系,θ 不是随机变量。
定义:
对序列 {w1,w2,w3,…,wN} ,当前状态 wN 出现的概率只与前 n 个状态 {wN−n,…,wN−1} 有关,即:
P(wN∣w1,w2,…,wN−1)≈P(wN∣wN−n,…,wN−1)。(1.5)
马尔可夫假设的核心思想是:
当前状态只依赖于最近的几个状态,与更早的状态无关。
在语言中,这意味着:
一个词的出现只依赖于它前面的几个词,与更早的词无关。
即
基于大量语言数据的统计规律和实验验证,我们发现:只考虑最近的n个词,已经能够以足够高的准确度近似预测下一个词,而考虑更早的词带来的改进微乎其微。
现实问题:
马尔可夫假设的简化:
将"依赖所有前面的词"简化为"依赖最近的n个词"
使计算变得可行,同时保留了语言的主要依赖关系
原始语料:
- "我 喜欢 机器 学习"
- "我 喜欢 深度 学习"
- "他 喜欢 机器 学习"
- "我 讨厌 传统 方法"
添加边界标记(<s>表示句首,</s>表示句尾):
<s> 我 喜欢 机器 学习 </s>
<s> 我 喜欢 深度 学习 </s>
<s> 他 喜欢 机器 学习 </s>
<s> 我 讨厌 传统 方法 </s>
| 词语 | 频率 | 总词数 |
|---|
| <s> | 4 | 24 |
| 我 | 3 |
|
| 喜欢 | 3 |
|
| 机器 | 2 |
|
| 学习 | 3 |
|
| 深度 | 1 |
|
| 他 | 1 |
|
| 讨厌 | 1 |
|
| 传统 | 1 |
|
| 方法 | 1 |
|
| </s> | 4 |
|
| Bigram | 频率 |
|---|
| <s> 我 | 3 |
| <s> 他 | 1 |
| 我 喜欢 | 2 |
| 我 讨厌 | 1 |
| 喜欢 机器 | 2 |
| 喜欢 深度 | 1 |
| 机器 学习 | 2 |
| 深度 学习 | 1 |
| 他 喜欢 | 1 |
| 学习 </s> | 3 |
| Trigram | 频率 |
|---|
| <s> 我 喜欢 | 2 |
| <s> 我 讨厌 | 1 |
| <s> 他 喜欢 | 1 |
| 我 喜欢 机器 | 1 |
| 我 喜欢 深度 | 1 |
| 喜欢 机器 学习 | 2 |
| 喜欢 深度 学习 | 1 |
| 他 喜欢 机器 | 1 |
| 机器 学习 </s> | 2 |
| 深度 学习 </s> | 1 |
🧠洞见
在此板块中:
P(w)=total wordscount(w),或
P(wi∣wi−1)=count(wi−1)count(wi−1,wi) ,等等
这明显是统计学中:用频率代替概率的做法;
教材中也有原文:n-grams语言模型的输出 Pn−grams(w1:N) 是对 P(wi∣w1:i−1) 的近似。即,
$P _ {n - grams} \left(w _ {1: N}\right) \approx P \left(w _ {1: N}\right) $ 。
这包含两重近似:
模型近似:通过n阶马尔可夫假设,将完整历史条件概率 P(wi∣w1:i−1) 简化为局部历史条件概率 P(wi∣wi−n+1:i−1)
参数估计:'用频率代替概率'的做法的正确性,主要由大数定律(保证频率收敛到概率)和离散型随机变量的极大似然估计(证明频率在统计学意义上是最优估计量)严格证明。”
此 严格证明,体现为教材中的1.10-1.17
使用最大似然估计:P(w)=total wordscount(w)
P(我)=243=0.125
P(喜欢)=243=0.125
P(机器)=242=0.083
P(学习)=243=0.125
句子概率:
P(“我 喜欢 机器 学习”)=P(我)×P(喜欢)×P(机器)×P(学习)=0.125×0.125×0.083×0.125=0.000162
P(wi∣wi−1)=count(wi−1)count(wi−1,wi)
P(我∣<s>)=count(<s>)count(<s> 我)=43=0.75
P(喜欢∣我)=count(我)count(我 喜欢)=32=0.667
P(机器∣喜欢)=count(喜欢)count(喜欢 机器)=32=0.667
P(学习∣机器)=count(机器)count(机器 学习)=22=1.0
P(</s>∣学习)=count(学习)count(学习 </s>)=33=1.0
句子概率:
P(&\text{"<s> 我 喜欢 机器 学习 </s>"}) = \\ &P(\text{我}|\text{<s>}) \times P(\text{喜欢}|\text{我}) \times P(\text{机器}|\text{喜欢}) \times P(\text{学习}|\text{机器}) \times P(\text{</s>}|\text{学习}) = \\ &0.75 \times 0.667 \times 0.667 \times 1.0 \times 1.0 = 0.334 \end{align*}
P(wi∣wi−2,wi−1)=count(wi−2,wi−1)count(wi−2,wi−1,wi)
- P(喜欢∣<s> 我)=count(<s> 我)count(<s> 我 喜欢)=32=0.667
- P(机器∣我 喜欢)=count(我 喜欢)count(我 喜欢 机器)=21=0.5
- P(学习∣喜欢 机器)=count(喜欢 机器)count(喜欢 机器 学习)=22=1.0
- P(</s>∣机器 学习)=count(机器 学习)count(机器 学习 </s>)=22=1.0
句子概率:
$\begin{align*} P(&\text{"<s> 我 喜欢 机器 学习 </s>"}) = \\ &P(\text{我}|\text{<s>}) \times P(\text{喜欢}|\text{<s> 我}) \times P(\text{机器}|\text{我 喜欢}) \times P(\text{学习}|\text{喜欢 机器}) \times P(\text{</s>}|\text{机器 学习}) = \\ &0.75 \times 0.667 \times 0.5 \times 1.0 \times 1.0 = 0.250 \end{align*}
| 模型 | 句子概率 | 优势 | 劣势 |
|---|
| Unigram | 0.000162 | 简单,无数据稀疏问题 | 忽略词序,"我 喜欢 机器 学习"和"学习 机器 喜欢 我"概率相同 |
| Bigram | 0.334 | 捕获相邻词关系,"喜欢 机器"比"讨厌 机器"概率高 | 无法理解"我 喜欢 机器"与"他 喜欢 机器"的区别 |
| Trigram | 0.250 | 捕获更多上下文,"我 喜欢 机器"与"他 喜欢 机器"有不同概率 | 数据稀疏问题更严重,需要更多训练数据 |
新句子:"我 喜欢 人工 智能"
在训练语料中:
- "喜欢 人工" 未出现 → count = 0
- "人工 智能" 未出现 → count = 0
未平滑的Bigram概率:
P("我 喜欢 人工 智能")=0.75×0.667×0×0=0
加一平滑后的概率(V=11个词):
Psmoothed(人工∣喜欢)=3+110+1=141=0.071
Psmoothed(智能∣人工)=0+110+1=111=0.091
n-grams核心思想:将长序列概率分解为短序列条件概率
- Unigram: 只考虑单个词
- Bigram: 考虑当前词与前一个词的关系
- Trigram: 考虑当前词与前两个词的关系
权衡关系:
- n↑ → 上下文信息↑,但数据稀疏问题↑
- n↓ → 数据稀疏问题↓,但上下文信息↓
实用技巧:
- 总是添加
和标记处理边界 - 使用平滑技术避免零概率
- 实际应用中,bigram和trigram最常用
关键洞见:n-grams模型通过局部上下文建模语言,虽然简单但有效。现代深度学习模型(如RNN、Transformer)本质上是更复杂的n-grams变体,能够捕获更长距离的依赖关系。
您指正得很对,这些确实是n-grams的核心局限,我来重新优化表格,确保明确包含这些关键概念
| 局限性类型 | 具体表现 | 影响程度 |
|---|
| 零概率问题 | 未在训练数据中出现的n-gram组合概率为零 例:"我 喜欢 人工 智能"中"喜欢 人工"未出现 | ⭐⭐⭐⭐⭐ (基础性问题) |
| 维度灾难 | 参数空间大小为O(Vⁿ),V为词汇表大小 n=3, V=50K时需估计125万亿参数 | ⭐⭐⭐⭐⭐ (计算不可行) |
| 缺乏语义泛化 | 无法将"喜欢 吃 苹果"的知识迁移到"喜欢 吃 香蕉" 表面形式不同即视为完全不同的事件 | ⭐⭐⭐⭐ (理解能力受限) |
| 长距离依赖缺失 | 无法捕获超过n-1词距的关系 例:无法连接"巴黎"与指代它的"这座城市" | ⭐⭐⭐⭐ (上下文受限) |
| 上下文僵化 | 多义词在不同语境下表示相同 例:"银行"(金融机构)与"银行"(河岸)无法区分 | ⭐⭐⭐⭐ (歧义处理差) |
| 结构盲区 | 无法识别语法结构 例:无法理解"虽然...但是..."的逻辑关联 | ⭐⭐⭐ (逻辑理解弱) |
| 单向上下文 | 仅使用左侧历史预测下一词 无法利用右侧信息进行全局优化 | ⭐⭐⭐ (信息利用不全) |
| 资源效率低 | 大型n-gram模型占用GB级存储 参数无法共享,重复存储相似模式 | ⭐⭐ (工程实现难) |
💡 核心洞见:n-grams模型的核心缺陷在于符号级统计与语义理解之间的鸿沟。它记录"发生了什么",却无法理解"为什么发生"。现代神经语言模型通过连续向量空间表示和层次化特征提取,从根本上跨越了这一鸿沟。
这一阶段的核心是神经概率语言模型(NPLM)及其改进版本,它们都是基于前馈神经网络架构的语言模型。以下是这一特定阶段的关键论文:
2003 Bengio的NPLM:原始架构使用前馈网络,输入是前n-1个词的one-hot编码,通过嵌入层映射为稠密向量,连接后送入隐藏层,输出层用softmax预测下一个词
2007-2008的改进:主要解决计算瓶颈问题
- 词表越大,softmax计算越慢(需要计算所有词的概率)
- 层次化softmax将计算复杂度从O(V)降低到O(log V)
- 噪声对比估计(NCE)避免计算完整softmax
模型结构:典型的NPLM前馈架构
One-hot 输入 → 嵌入层(lookup table) → 拼接词向量 → 隐藏层(MLP) → softmax输出
这一阶段的模型虽然仍是前馈网络,但它们解决了n-grams的核心问题:
- 通过连续词向量表示解决"零概率"问题
- 通过语义相似性实现更好的泛化
- 通过参数共享大幅减少所需参数量
这些工作为2010年后RNN语言模型的兴起奠定了理论和实践基础,特别是词嵌入的思想成为后续所有神经网络语言模型的核心组件。
1943年春,神经科学家Warren McCulloch坐在芝加哥一家烟雾缭绕的咖啡馆里,窗外下着细雨。他刚听完一场关于大脑神经传导的讲座,心中充满困惑:生物神经元如何通过简单的"放电/不放电"机制,产生复杂的思维?
这时,一个瘦小的年轻数学家Walter Pitts坐在他对面,默默听着McCulloch的困惑。Pitts曾是流浪少年,靠在图书馆自学成为数学天才。两人已是老友,但今晚不同。
就在McCulloch搅动咖啡,描述神经元像开关一样工作时,Pitts突然放下杯子,眼中闪烁着光芒:"等等!如果我们将神经元视为一个阈值单元,当输入加权和超过阈值才激活..."
McCulloch后来回忆:"那一刻,咖啡馆里的嘈杂声仿佛消失了。我们意识到,这个简单方程不仅描述了神经元,更可能描述了'思想'本身。"
1957年7月,Frank Rosenblatt驱车前往长岛海边度假。作为美国海军实验室的研究员,他一直在尝试构建能"学习"的机器,但进展缓慢。
那天下午,Rosenblatt坐在沙滩上,看着海鸥群在浪尖觅食。一只年幼的海鸥连续几次捕食失败,但逐渐调整策略,最终成功。Rosenblatt突然站起身,沙子从裤腿滑落——他意识到生物学习的核心机制!
海鸥不是靠预编程捕食,而是通过错误调整行为。Rosenblatt冲回旅馆,在便签本上潦草地写下感知机学习规则:
winew=wiold+η⋅(t−y)⋅xi
其中:
- η 为学习率
- t 为目标输出
- y 为实际输出
- xi 为输入特征
"机器应该像海鸥一样,从错误中学习!"Rosenblatt在日记中写道。第二年,他基于这个沙滩灵感,建造了世界上第一台能学习的机器——Mark I Perceptron。
1986年秋,David Rumelhart在斯坦福大学的办公室已连续工作36小时。他和Hinton试图解决神经网络的最大难题:如何训练多层网络?当时普遍认为这不可能实现。
Rumelhart尝试了各种方法,但三层网络连最简单的XOR问题都无法解决。凌晨三点,咖啡杯已空,他准备放弃。就在关闭电脑前,他尝试了一个来自1970年代旧论文的疯狂想法——让误差从输出层反向传播到输入层。
当代码运行完成,屏幕上显示"XOR训练成功,误差<0.01"时,Rumelhart不敢相信自己的眼睛。他颤抖着手拨通Hinton家的电话,声音哽咽:"Geoff...它工作了!误差真的能反向传播!"
反向传播的核心公式在那一刻被验证:
∂wij(l)∂E=δj(l)⋅ai(l−1)
其中误差项通过链式法则反向传播:
δj(l)=f′(zj(l))⋅k∑wjk(l+1)δk(l+1)
Hinton后来回忆:"David凌晨的电话改变了AI历史。那一刻,我们意识到深层网络的学习障碍被打破了。"
2012年9月30日,Alex Krizhevsky独自坐在多伦多大学实验室。他刚刚提交了ImageNet竞赛的最终测试,结果将在几小时后公布。过去半年,他和导师Hinton几乎住在实验室,训练一个被同行嘲笑的8层深度神经网络。
凌晨2:47,Krizhevsky刷新邮件,看到初步结果。他猛地从椅子上跳起,咖啡洒了一地——错误率15.3%,比第二名低了整整10.8个百分点!他颤抖着在团队群里发消息:"我们赢了...不只是赢了,我们改变了游戏规则。"
当Hinton赶到实验室,两人盯着GPU集群闪烁的灯光。Krizhevsky指着屏幕说:"看,ReLU和Dropout的组合让深层网络不再饱和。"Hinton点头:"不,Alex,是数据+计算力+正确架构的完美风暴。"
AlexNet的核心创新在那一刻被证明:
a(l)=ReLU(z(l))=max(0,z(l))
结合Dropout正则化:
a~(l)=r⊙a(l),rj∼Bernoulli(p)
计算机视觉专家Li Fei-Fei后来评价:"那个凌晨,传统计算机视觉死了,深度学习诞生了。"
从故事:1943年咖啡馆里的神经元顿悟为起点。为了实现对生物电信号的“物理模拟”
由 20 世纪初,神经科学领域有一个非常著名的法则叫做 “全或无”法则 (All-or-None Law)。
生物现象:生物神经元在接收电刺激时,并不是刺激多大电流就输出多大电流。如果刺激低于某个阈值,神经元完全保持静默(0);一旦超过阈值,它会瞬间爆发一个强度恒定的脉冲信号(1)。
早期的 AI 先驱不仅仅想模拟大脑,更想证明“神经网络可以进行通用计算”。
- 二进制的必然性:当时的科学背景是香农(Claude Shannon)刚刚统一了布尔逻辑与开关电路。科学家们认为,如果能让每个神经元都表现得像一个“开关”(即阶跃函数的 0 或 1),那么就可以通过组合这些神经元来构建 与(AND)、或(OR)、非(NOT) 等逻辑门。
- 实现目标:一旦神经网络能够实现逻辑门,就意味着它可以处理任何计算机能处理的任务。
为了实现
的映射路径,出现了一系列解决此问题函数的发展
阶跃函数是感知机(Perceptron)的核心,它模拟了神经元的“阈值电位”。只有当加权输入之和达到阈值 θ 时,神经元才会“点火”。
$ y = f\left(\sum\_{i=1}^{n} w\_i x\_i - \theta\right)
其中:
xi 为输入信号
wi 为连接权重
θ 为激活阈值
f 为阶跃函数:
f(x)={1 x≥0 0x<0
硬性二值化: 将复杂的连续输入简化为绝对的“是/否”(1/0)判定,实现了最基础的二元分类。
逻辑门实现: 证明了通过调整权重 w 和阈值 θ,单个神经元就可以完成“与、或、非”等布尔运算,奠定了神经网络作为“计算通用机”的基础。
其中 x 是输入(0或1),w 是权重(重要程度),θ 是阈值(进门槛)。只有当“总分” ≥0 时,输出才为 1。
逻辑: 只有当 x1=1 且 x2=1 时,结果才为 1。
- 设置: 权重 w1=1,w2=1;阈值 θ=1.5。
- 计算过程:
- (0, 0) →1(0)+1(0)−1.5=−1.5(小于0,输出 0)
- (1, 0) →1(1)+1(0)−1.5=−0.5(小于0,输出 0)
- (0, 1) →1(0)+1(1)−1.5=−0.5(小于0,输出 0)
- (1, 1) →1(1)+1(1)−1.5=0.5(大于0,输出 1)✅
- 直观理解: 门槛设得很高(1.5),单靠一个人的力量(权重1)跨不过去,必须两人合力。
逻辑: 只要 x1=1 或 x2=1,结果就为 1。
- 设置: 权重 w1=1,w2=1;阈值 θ=0.5。
- 计算过程:
- (0, 0) →1(0)+1(0)−0.5=−0.5(输出 0)
- (1, 0) →1(1)+1(0)−0.5=0.5(输出 1)✅
- (0, 1) →1(0)+1(1)−0.5=0.5(输出 1)✅
- (1, 1) →1(1)+1(1)−0.5=1.5(输出 1)✅
- 直观理解: 门槛降得很低(0.5),任何一个人(权重1)都能单独跨过去。
逻辑: 输入 1 输出 0,输入 0 输出 1。
- 设置: 只有一个输入 x1。权重 w1=−1(负分);阈值 θ=−0.5。
- 计算过程:
- (0) →−1(0)−(−0.5)=0.5(输出 1)✅
- (1) →−1(1)−(−0.5)=−0.5(输出 0)✅
- 直观理解: 权重是负的,意味着“你越努力,总分越低”。
当年 McCulloch 和 Pitts 证明了这件事后,全世界都震惊了。因为:
通用性: 既然神经元能完成 AND、OR、NOT,而这三种门可以组合成世界上任何复杂的计算机芯片(CPU),那么结论就是:神经网络在理论上可以计算任何东西。
在阶跃函数时代,这些权重 w 和阈值 θ 是科学家手动凑出来的(就像我上面展示的那样)。
阶跃函数的三个致命问题:
- 不可导问题:阶跃函数在 x=0 处不连续,在其他地方导数为 0。这意味着无法使用梯度下降法(Gradient Descent)来训练网络,因为没有梯度(斜率)来告诉模型参数该往哪边调。Sigmoid 处处可导,且导数平滑。
- 二元局限性:阶跃函数非黑即白(只能输出 0 或 1),无法表达“不确定性”。Sigmoid 可以输出中间值,模拟了生物神经元“放电频率”的强弱。
- 维度灾难与连续性:在处理文本、图像等高维数据时,需要将离散的符号映射到连续的空间,Sigmoid 这种平滑函数是构建这种连续表示的基础。
后来人们想,能不能让机器自己去凑这些数字?阶跃函数是“死板”的,稍微调一点点权重,输出可能完全不变(依然是0),导致机器不知道调得对不对。为了能“顺滑”地自动调整并解决上文提到的阶跃函数的局限性,人们才把阶跃函数改成了有坡度的 Sigmoid。
σ(x)=1+e−x1
sigmoid函数.png数学平滑化: 解决了阶跃函数不可导的缺陷。Sigmoid 在全定义域内连续可导,为反向传播算法(Backpropagation)提供了数学前提,让模型能够通过“微调”权重来学习。
Sigmoid 函数把那个生硬的台阶拉成了一个平滑的斜坡。
处处有斜率: 因为它连续可导,所以无论当前的权重 w 在哪里,我们都能算出该点的斜率(梯度)。
感知微小变化: 只要你调整一点点 w,输出 y 就会产生一个非常细微的、连续的变化。
数学前提: 反向传播算法要求计算损失函数对权重的偏导数(∂w∂Loss)。根据链式法则,这个偏导数等于:
∂w∂Loss=∂Out∂Loss×∂In∂Out×∂w∂In
中间那项 ∂In∂Out 就是激活函数的导数。如果这一项是 0 或者不存在,整个链条就断了,权重就无法更新。
激活强度的量化: 输出范围在 (0,1),不仅能代表“开关”,还能代表“激活的程度”。
概率转化: 在输出层,它可以将神经元的输出解读为概率(例如:80% 的可能性为真)。
存在“梯度饱和”问题。当输入过大或过小时,导数趋近于 0,导致深层网络训练时出现“梯度消失”。
双曲正切激活函数(Hyperbolic Tangent Activation Function),通常简写为 tanh,是深度学习中非常经典且重要的非线性激活函数。在早期的神经网络(如多层感知机 MLP、循环神经网络 RNN)中,它是最常用的激活函数之一。
以下是对 tanh 激活函数的深入解析:
tanh 函数将输入值 x 映射到 (−1,1) 的区间内。其数学表达式为:
tanh(x)=ex+e−xex−e−x
它与 Sigmoid 函数 (σ(x)) 有着紧密的联系,实际上是 Sigmoid 函数的一种变体:
tanh(x)=2σ(2x)−1
证明:
tanh(x)=ex+e−xex−e−x(定义)=ex(ex+e−x)ex(ex−e−x)(分子分母同乘 ex)=e2x+1e2x−1=e2x+1e2x+1−2(分子加1减1,拆分分式)=e2x+1e2x+1−e2x+12=1−e2x+12=1−(e2x+1)⋅e−2x2⋅e−2x(分子分母同乘 e−2x,凑出 Sigmoid 结构)=1−1+e−2x2e−2x=1−2(1+e−2xe−2x)=1−2(1+e−2x1+e−2x−1)=1−2(1−1+e−2x1) =1+e−2x2−1 =1−2+1+e−2x2=2σ(2x)−1(结论)
洞见🧠:
“从几何上看,这个等式意味着:将 Sigmoid 函数的图像在水平方向压缩 1/2(2x),在垂直方向拉伸 2 倍(2σ),最后再向下平移 1 个单位(−1),就得到了 tanh 函数。这解释了为什么 tanh 是‘零中心化’的(输出区间从 [0,1] 变到了 [−1,1])。”
在神经网络训练(反向传播)中,激活函数的导数至关重要。tanh(x) 的导数可以用其自身简洁地表示:
dxdtanh(x)=1−tanh2(x)
\text{证明:
}\text{证明过程:}
\begin{aligned} \frac{d}{dx}\tanh(x) &= \frac{d}{dx} \left\[ 2\sigma(2x) - 1 \right] \ &= 2 \cdot \frac{d}{dx} \sigma(2x) \quad \text{(常数的导数为 0)} \ &= 2 \cdot \sigma'(2x) \cdot \frac{d}{dx}(2x) \quad \text{(复合函数求导法则 / 链式法则)} \ &= 2 \cdot \sigma'(2x) \cdot 2 \ &= 4 \cdot \sigma(2x)\big(1 - \sigma(2x)\big) \quad \text{(代入 Sigmoid 导数公式)} \ &= 4\sigma(2x) - 4\sigma^2(2x) \quad \text{(展开括号)} \\ \text{为了化简回} \tanh \text{的形式} \text{我们利用} \sigma(2x) = \frac{\tanh(x) + 1}{2} \text{进行代入}\\ \frac{d}{dx}\tanh(x) &= 4 \left\[ \frac{\tanh(x) + 1}{2} \right] \left\[ 1 - \frac{\tanh(x) + 1}{2} \right] \ &= 2\big(\tanh(x) + 1\big) \cdot \left\[ \frac{2 - (\tanh(x) + 1)}{2} \right] \ &= (\tanh(x) + 1) \cdot (1 - \tanh(x)) \ &= 1 - \tanh^2(x) \quad \text{(平方差公式)} \end{aligned}
这个特性使得在编程实现时,只要记住了前向传播的输出值,计算导数就变得非常高效。
实现“零中心化”(Zero-centered):
- 作用: 这是 tanh 对 Sigmoid 最核心的改进。Sigmoid 的输出区间是 (0,1),这意味着神经元的输出永远是正数。这会导致在反向传播时,权重的梯度更新方向出现“锯齿效应”(要么全部向正方向更新,要么全部向负方向更新),从而减慢收敛速度。
- 目的: tanh 将输出区间拉伸至 (−1,1),其均值为 0。这种分布使得数据在层与层之间传递时更加均衡,能够加快神经网络的收敛速度。
更强的梯度信号:
- 作用: 根据我们之前的推导,tanh 的导数最大值为 1(当 x=0 时),而 Sigmoid 的导数最大值仅为 0.25。
- 目的: 在深层网络的前期训练中,tanh 能够比 Sigmoid 传递更强的梯度信号,缓解了一定程度的梯度消失问题,使模型更容易起步。
非线性特征提取:
- 作用: 与 Sigmoid 一样,tanh 提供了平滑的非线性映射。
- 目的: 它保留了“饱和区”(两侧平坦)和“敏感区”(中间陡峭),让模型能够学习到复杂的非线性关系,同时其平滑可导的特性保证了微调权重的可能性。
尽管 tanh 有很多优点,但在现代超大规模模型(如 Transformer)中,它逐渐被 ReLU、GELU 等函数取代,主要原因如下:
- 梯度消失(Gradient Vanishing):当 ∣x∣ 较大时,函数的输出进入饱和区(Saturated regime),斜率几乎为 0。在反向传播过程中,这意味着梯度会变得极小,导致深层参数无法更新。
- 计算成本:涉及指数运算(ex),相比于 ReLU 这种简单的阈值计算,其计算开销相对较大。
ReLU(Rectified Linear Unit,线性整流单元)是现代深度学习中最流行、最常用的激活函数。别看名字听起来很高深,它的公式是所有激活函数里最简单的:
f(x)=max(0,x)
或者写成更直观的分段形式:
f(x)={xif x>0 0if x≤0
直观理解,ReLU 就像一个严格的门卫。如果是正数,它原样放行;如果是负数,它直接拦截,将其置为 0。
虽然看起来它只是一个简单的折线,但它巧妙地结合了线性与非线性:
- 单侧抑制:它在 x≤0 时关闭神经元,引入了稀疏性。
- 线性响应:它在 x>0 时保持线性(导数为 1),这与 Sigmoid/Tanh 的饱和区形成了鲜明对比。
之所以 ReLU 能取代 Sigmoid 和 Tanh 统治深度学习界,是因为它完美解决了两个阻碍深层网络训练的顽疾:
解决梯度消失问题 (Vanishing Gradient):
Sigmoid 和 Tanh 在两端(很大或很小的 x)导数都趋近于 0,导致深层网络的梯度无法回传。而 ReLU 在正区间的导数恒为 1。这意味着无论网络有多深,只要是激活路径,梯度就能等大地传回去,不会衰减。这是训练深层网络(如 ResNet, Transformer)的基石。
极高的计算效率 (Computational Efficiency):
计算机做指数运算(ex)是很累的,而 ReLU 只需要判断 if x > 0。在处理数亿参数的大模型时,这种“简陋”的操作反而带来了巨大的性能提升。
稀疏激活性 (Sparse Activation):
ReLU 会让负区间的神经元直接输出 0(彻底沉默)。据统计,在训练好的网络中,任何时刻可能只有 50% 的神经元是“醒着”的。这种稀疏性更符合生物神经元的工作方式,也让模型能提炼出更有区分度的特征。
ReLU 唯一的缺点是死区风险。如果一个神经元的权重 w 更新到了一个坑里,使得对于所有输入 x,加权和都小于 0,那么这个神经元就会永远输出 0。
一旦输出 0,导数也是 0,梯度就再也传不回去了。这个神经元就像“死”了一样,永久失去作用。
注:为了解决这个问题,后来又提出了 Leaky ReLU(给负数区一点点斜率)等变体,但在大模型实践中,标准的 ReLU 依然表现极其强劲。
Softmax 函数将一个包含 N 个实数的向量 y 映射为一个概率分布。对于向量中的第 i 个元素 yi,其 Softmax 分值为:
P(y_i)=∑_j=1Ney_jey_i
其中:
- yi:输出层的第 i 个神经元的原始输出值(通常称为 Logit)。
- N:类别的总数。
- P(yi):归一化后的概率,满足 0≤P(yi)≤1 且 ∑j=1NP(yj)=1。
- 归一化(Normalization):无论输入 yi 的值是多少,所有输出值的总和始终为 1。这使得输出可以被直接解释为概率分布。
- 单调性:如果 yi>yk,那么 P(yi)>P(yk)。它保持了原始输出的大小次序。
- 平移不变性:对所有的 yi 同时加上一个常数 C,Softmax 的输出结果不变。
- 注:在工程实现中,常利用此性质减去 max(y) 来防止数值溢出(Numerical Stability)。
Softmax 的导数涉及自身与其他项的关系(雅可比矩阵):
∂y_j∂P_i={P_i(1−P_i)if i=j −P_iP_jif i=j
\text{
这种结构在配合}\text{交叉熵损失函数(}Cross-Entropy Loss)\text{时,梯度简化为极其优雅的形式:}
∂y_i∂Loss=P_i−Label_i
- 多分类决策:
- Sigmoid 只能解决“二选一”的问题,而 Softmax 能够处理任意 N 个互斥类别的分类问题(如:识别手写数字 0-9)。
- 扩大差异(马太效应):
- 通过指数函数 eyi,Softmax 会显著放大数值较大的项,并抑制数值较小的项。这使得模型在预测时能更“果断”地指向得分最高的那一类,增强了区分度。
- 概率解释性:
- 将神经网络的“生硬分数”转化为符合直觉的“置信度”。例如,模型输出 P(猫)=0.9,表示它有 90% 的把握认为输入是猫。
- 可微的“最大值”:
- 它是 max 函数的“平滑”版本(这也是它名字中 Soft 的来源)。直接取最大值(Hardmax)不可导,无法训练;Softmax 既保留了寻找最大值的趋势,又保证了全过程可导,方便反向传播。
- 异常值敏感:由于使用了指数运算,如果某个 Logit 值非常大,会迅速吸走绝大部分概率,导致模型对其他类别完全“视而不见”,产生过拟合。
- 计算代价:在处理超大规模分类(如自然语言处理中的几万个词汇表)时,分母的累加运算极其耗时。
- 优化方案:在大模型中,通常使用层级 Softmax(Hierarchical Softmax)或负采样(Negative Sampling)来替代。
- 非独立性假设:Softmax 假设各个类别之间是互斥的(即一件东西只能属于一个类)。如果一个样本同时属于多个类(多标签分类),则应回到 Sigmoid。
阶跃函数/Sigmoid/tanh/ReLU:通常作为隐藏层的激活函数,负责特征提取和非线性变换。
Softmax:通常作为输出层的激活函数,负责将提取出的特征转化为最终的决策概率。

NPLM 本质上是一个标准的三层前馈神经网络(Feed-Forward Neural Network),虽然名为“三层”,但在具体实现中通常包含以下几个关键组件:
输入层 (Input Layer):
- 接收上下文窗口中的 n−1 个词(的索引)。
- 在数学上,这等价于输入这些词的 One-Hot 向量。
投影层 (Projection / Embedding Layer):
- 这是 NPLM 最具创新性的地方。
- 作用:将离散的单词索引映射为稠密的实数向量(即词向量)。
- 特点:所有输入位置共享同一个词向量矩阵 C。比如,“我”出现在第一个位置和第二个位置,查出来的向量是完全一样的。
- 操作:查表(Look-up)后,将这就 n−1 个词的向量首尾拼接(Concatenate)成一个长向量 x。
隐藏层 (Hidden Layer):
- 作用:进行非线性特征提取。
- 公式:h=tanh(W(h)x+b(h))
- 在这里,模型将学习如何组合不同位置的词义(例如学习“喜欢”前面通常是名词,后面通常是名词)。
输出层 (Output Layer):
- 作用:预测下一个词的概率。
- 公式:y=W(y)h+b(y),最后经过 validity Softmax 归一化。
- 输出向量的维度等于词汇表大小 ∣V∣,每一维对应一个词的概率。
.jpg)
| 参数名称 | 参数值 |
|---|
| 上下文窗口大小 | n=3(预测第4个词) |
| 词向量维度 | m=3 |
| 隐藏层大小 | 4 |
| 激活函数 | tanh |
| 输出层 | softmax |
| 学习率 | η=0.1 |
| 训练批次 | 1样本/批次(在线学习) |
| 项目 | 内容 |
|---|
| 词汇表 | V=(v1,v2,...,v11)=(’<s>’,’我’,’喜欢’,’机器’,’学习’,’深度’,’他’,’讨厌’,’传统’,’方法’,’</s>’) |
| 词索引映射 | index(’<s>’)=1、index(’我’)=2、⋯、index(’</s>’)=11 |
| 训练序列 | S1=[’<s>’,’<s>’,’<s>’,’我’,’喜欢’,’机器’,’学习’,’</s>’] |
💡 关键洞见:为什么要引入起始符 <s>?
1.NPLM 的网络结构要求输入层必须始终接收 3 个词向量(即上下文窗口 n=3)。引入 <s> 主要解决了前馈神经网络(FFNN)对固定输入维度的刚性需求。对于句首的单词(如“我”),由于缺乏真实的历史上文,模型无法直接运行。
2.通过填充 <s>,我们不仅在物理上补齐了输入矩阵的维度,使第一步运算得以进行;还在语义上为模型提供了一个明确的“句首信号”。随着训练的进行,<s> 的词向量将学到“句子开始”这一特殊的语义特征,从而帮助模型更准确地预测哪些词(如代词、名词)更倾向于出现在句子的开头。
3.结束符</s>的作用也是:在语义上为模型提供了一个明确的“句尾信号”
💡 关键洞见:为什么要对词汇表进行索引映射 ?
1.为了方便在数学公式中表示和索引。2.为了在数据集上做标注
如 “我”后面应该出现的正确的词是“喜欢”或者“讨厌”,即在做数据标注时,正确集应该是[2,3],[2,8]
在损失计算的过程中,见 \eqrefeq:grad−y−derivation,以预测“我”后面的词举例;对于i = 3或8,公式就变成了 Pt−1。这意味着模型预测的概率 Pt 本应该接近 1,但如果它只有 0.9,那么梯度就是 0.9−1=−0.1。这个负号告诉模型:“你猜低了,赶紧把分数加上去。”
💡 关键洞见:关于数值精度的说明
这里的矩阵参数展示为两位小数,但这是一种人为简化。
从数学上的均匀分布 U(−0.1,0.1) 采样实际上会产生连续的高精度浮点数(如 0.04821...)。在本例中,我们人为将数值限制在两位小数,主要是为了确保简洁易读,方便验证。
在真实的深度学习工程实现中,这些参数通常使用 32 位或 64 位浮点数存储,以保持数值计算的精度。
使用均匀分布 U(−0.1,0.1) 随机初始化:
Cinit=0.05−0.060.03−0.080.07−0.040.03−0.020.09−0.070.08−0.050.02−0.030.07−0.040.05−0.060.06−0.070.02−0.090.04−0.030.01−0.010.08−0.050.09−0.070.04−0.020.01\labeleq:C−init
💡 关键洞见:均匀分布 U(−0.1,0.1) 的背景与“随机初始化”究竟在做什么?
这里的
Cinit∼U(−0.1,0.1)
含义是:词向量矩阵中的每一个元素,都是在区间 [-0.1, 0.1] 上独立、等概率地随机采样得到的。
换句话说,落在这个区间内的任意实数,被选中的概率是一样的,没有“偏爱”某个数值。
之所以要这样做,有三个关键原因:
打破对称性(symmetry breaking)
如果一开始把所有参数都设为 0,那么网络中许多单元在前向和反向传播时会得到完全相同的梯度,导致它们永远学不到互补的功能;
随机初始化则让不同维度、不同词从一开始就略有差别,从而能在训练中走向不同的方向,各自“专精”不同特征。
数值足够小,避免激活函数过早饱和
区间[-0.1, 0.1] 很小,经过一两层线性变换后,送入 (\tanh) 等非线性时,输入通常仍然比较接近 0。
在 |x| 很小的时候,tanh(x)≈x,梯度接近 1,不会出现激活函数“饱和”(梯度非常接近 0)的问题,有利于训练一开始的稳定收敛。
词向量一开始是“无偏好”的
这些随机的小数本身没有语义,只是提供一个“起点”。
之后通过大量样本的梯度更新,模型会逐步把这些向量调整成有语义结构的表示(相似词向量靠近,不同词被区分开)。
在实现层面,“随机初始化”就是对矩阵的每个元素调用一次随机数生成函数。例如用 Python + NumPy,可以写成:
import numpy as np
# C 的形状是 (词向量维度 m, 词表大小 |V|)
C_init = np.random.uniform(low=-0.1, high=0.1, size=(3, 11))
这段代码会为 (C) 中的 3×11 个元素,各生成一个 [-0.1, 0.1] 区间内的随机数,得到的某一次具体结果,就类似你在上方给出的那个 Cinit。
权重矩阵 W(h)∈R4×9:
W^{(h)} = \begin{bmatrix} 0.01 & -0.02 & 0.03 & -0.04 & 0.05 & -0.06 & 0.07 & -0.08 & 0.09 \\ -0.01 & 0.02 & -0.03 & 0.04 & -0.05 & 0.06 & -0.07 & 0.08 & -0.09 \\ 0.02 & -0.03 & 0.04 & -0.05 & 0.06 & -0.07 & 0.08 & -0.09 & 0.01 \\ -0.02 & 0.03 & -0.04 & 0.05 & -0.06 & 0.07 & -0.08 & 0.09 & -0.01 \end{bmatrix} \label{eq:Wh} $
💡
W(h) 的尺寸是 4×9。这个尺寸是由 “输入数据的总长度” 和 “隐藏层神经元的数量” 共同决定的。
9 列(宽度)来源:输入层 x 的维度即:$ \text{输入维度} = \text{窗口大小} \times \text{词向量维度} = 3 \times 3 = \mathbf{9}$;
4 行(高度)来源:隐藏层 h 的大小,这意味着我们希望把那 9 个输入特征,经过线性变换后,压缩/映射成 4 个新的特征。
🧠
隐藏层大小(Hidden Layer Size,记为 Nh)是一个 超参数(Hyperparameter)。
如果你把 Nh 设得太小(比如输入 100 维,隐藏层只有 2 维,输出 50 类),后果:欠拟合(Underfitting),即数学上:将高维数据强行投影到极低维空间,导致原本可分的数据挤在一起,变得不可分。
如果Nh 设得极其巨大(比如输入 100 维,隐藏层 10000 维)。后果:过拟合(Overfitting) 和 计算浪费。参数量过多,模型具有了捕捉数据中“随机噪声”的能力,而不是只捕捉“通用规律”。
虽然没有一个万能公式能算出最优的 h,但学术界和工业界有几个常用的“初始设定策略”:
🧠
隐藏层大小(记为 Nh)规定多少合适?
策略一:几何金字塔 (Geometric Pyramid)
通常,隐藏层的大小位于输入层和输出层之间。
Nh≈Nin×Nout
9×11≈99≈10。
注:你的笔记中选了 4,这是一个偏向“压缩特征”的选择,对于演示简单例子是完全合理的。
策略二:输入的倍数 (Expansion Ratio)
在现代深度学习(特别是 Transformer 和 CNN)中,经常反其道而行之,将隐藏层设为输入的倍数。
- 常见设定:2×Nin 或 4×Nin。
- 逻辑:升维打击。将数据投射到更高维的空间,使得数据在那个空间里更容易被线性分割(Cover's Theorem)。
- 例如:Transformer 的前馈网络(FFN)通常把维度放大 4 倍,然后再缩回去。
策略三:2 的幂次 (Powers of 2)
你会发现大牛们的代码里,隐藏层往往是 32, 64, 128, 512, 1024...
逻辑:纯粹的工程优化。GPU 和计算机内存对 2 的幂次大小的数据块读写效率最高(Memory Alignment)。选 500 不如选 512 跑得快。
此案例中
x(9维)W(h)h(4维)W(y)y(11维)
这里选择 4 是一个非常典型的“特征压缩”(Bottleneck)设计。
它的假设是:虽然输入的词向量组合有 9 个维度,但真正决定下一个词是什么的“核心语义”,其实只用 4 个数字就能概括。
偏置向量 :
$ b^{(h)} \in \mathbb{R}^4:b^{(h)T} = \begin{bmatrix} 0.0 & 0.0 & 0.0 & 0.0 \end{bmatrix}
权重矩阵 W(y)∈R11×4:
W(y)=0.010.020.030.040.050.060.070.080.09−0.01−0.02−0.02−0.03−0.04−0.05−0.06−0.07−0.08−0.09−0.010.020.030.030.040.050.060.070.080.090.010.02−0.03−0.04−0.04−0.05−0.06−0.07−0.08−0.09−0.01−0.02−0.030.040.05\labeleq:Wy
b(y)∈R11$:b(y)T=[0.00.00.00.00.00.00.00.00.00.00.0]
💡 关键洞见:为什么偏置向量 b 通常初始化为 0?
偏置项的初始化策略与权重矩阵截然不同,通常直接设为全 0,这主要基于以下两个原因:
第一,权重的随机性已经打破了对称性。
神经网络初始化的核心任务是防止所有神经元学习到完全相同的特征(即“对称性困境”)。只要权重矩阵 W 是随机初始化的,每个神经元对输入的响应就已经各不相同了。
此时将偏置 b 初始化为 0 不会重新引入对称性问题,因此无需引入额外的随机噪声。
第二,最大化初始梯度收益。
NPLM 使用的是 tanh 激活函数,其导数(梯度)在输入为 0 附近最大(即函数的线性区域)。将偏置初始化为 0,可以保证在训练伊始,神经元的净输入主要由加权后的输入数据决定,大概率落在 tanh 函数的中心区域。这能有效避免初始状态就落入函数的饱和区(即两端平缓区域),从而保证梯度能够顺畅地反向传播,加速模型收敛。
我们将模拟 NPLM 在处理句子时的第一个时间步。
NPLM 使用一个固定大小的“滑动窗口”来预测下一个词。根据规格定义,我们的上下文窗口大小为 3,即根据前 3 个词预测第 4 个词。
当前任务:模型刚开始读取句子,需要预测第一个实际的单词 "我"。
输入上下文 (Context):由于 "我" 是句首,其前文不足,需要用填充符号 <s> (Start of Sentence) 补齐。
样本提取示意图:
| 时间步 (t) | 上下文输入 (wt−3,wt−2,wt−1) | 预测目标 (wt) |
|---|
| t=4 | ['<s>', '<s>', '<s>'] | '我' |
| (下一时刻) | ['<s>', '<s>', '我'] | '喜欢' |
数值化映射(String → Index):
为了送入神经网络,我们需要将单词转换为词汇表中的索引(Index):
输入序列 (Input Indices):
- wt−3=’<s>’→Index: 1
- wt−2=’<s>’→Index: 1
- wt−1=’<s>’→Index: 1
- Input Vector Indices=[1,1,1]
目标标签 (Target Label):
- wt=’我’→Index: 2
- 这是我们在计算损失函数时需要的“标准答案”(Ground Truth)。
x=[c(w_1) c(w_2) c(w_3)]=[0.05−0.060.030.05−0.060.030.05−0.060.03]T\labeleq:x−concat
其中词向量来自 \eqrefeq:C−init
计算 z(h)=W(h)x+b(h)(其中 W(h) 见 \eqrefeq:Wh,x 见 \eqrefeq:x−concat),z(h)∈R4:
z(h)_1=0.01⋅0.05+(−0.02)⋅(−0.06)+0.03⋅0.03+(−0.04)⋅0.05+0.05⋅(−0.06)+(−0.06)⋅0.03+0.07⋅0.05+(−0.08)⋅(−0.06)+0.09⋅0.03+0.0=0.0068
\text{。。。。
以此类推
因此}:
z(h)=[0.0068 −0.0068 0.0055 −0.0055]\labeleq:zh
应用 tanh 激活函数(使用 tanh(x)≈x 当 x 很小时):
h=tanh(z(h))=tanh(0.0068)tanh(−0.0068)tanh(0.0055)tanh(−0.0055)≈0.0068−0.00680.0055−0.0055\labeleq:h
洞见
计算 y=W(y)h+b(y)(其中 W(y) 见 \eqrefeq:Wy,h 见 \eqrefeq:h),y∈R11:
y_1=W(y)_1,:⋅h=0.01×0.0068+(−0.02)×(−0.0068)+0.03×0.0055+(−0.04)×(−0.0055)+0.0$=0.000068+0.000136+0.000165+0.000220=0.000589
。。。。
y\_{11} = W^{(y)}\_{11,:} \cdot h = (-0.02) \times 0.0068 + 0.03 \times (-0.0068) + (-0.04) \times 0.0055 + 0.05 \times (-0.0055) + 0.0$ = -0.000136 - 0.000204 - 0.000220 - 0.000275 = -0.000835\$
因此:
y=[0.0005890.0008350.0010810.0013270.0015730.0018190.0015700.0013210.000955−0.000589−0.000835]T\labeleq:y−output
在神经网络的前向传播末端,模型输出的是一组未归一化的原始分值(Logits)。为了评估模型表现并启动反向传播,我们需要将这些分值转化为概率,并计算其与真实标签之间的距离。
计算 softmax: P(wi)=∑j=111eyjeyi,其中 y 来自 \eqrefeq:y−output。
首先计算 eyi:
- ey1=e0.000589≈1.000589
- ey2=e0.000835≈1.000835
- 。。。。
- ey11=e−0.000835≈0.999165
分母:
∑_j=111ey_j=1.000589+1.000835+1.001082+1.001328+1.001574+1.001821+1.001571+1.001322+1.000955+0.999411+0.999165=11.009653
概率分布:
P(w1)=P(’<s>’)=11.0096531.000589=0.09088
P(w2)=P(’我’)=11.0096531.000835=0.09090
。。。。
P(w11)=P(’</s>’)=11.0096530.999165=0.09076
因此,预测分布为:
P=[0.090880.090900.090930.090950.090970.090990.090970.090950.090910.090780.09076]T\labeleq:P−dist
这里的 P 向量计算的是:在当前上下文窗口(<s>, <s>, <s>)条件下,词汇表中每一个词成为“下一个词”的条件概率。
数学定义:
对于单标签分类任务(目标类索引为 t),交叉熵损失衡量了预测概率分布 P 与真实分布(One-hot 编码)的差异:
L=−log(P_t)\labeleq:loss−def
我们可以从以下三个视角来理解为什么要取对数:
信息论视角:衡量“意外感”
在信息论中,一个事件发生的概率越低,它包含的信息量(Information Content)就越大。
- 数学定义:信息量 I(x)=−log(P(x))。
- 逻辑:
- 如果你预测一个概率为 0.99 的事件发生,这很正常,信息量几乎为 0。
- 如果你预测一个概率为 0.01 的事件发生(即你之前的预测完全错了),这非常令人“意外”,信息量极大。
- 交叉熵的本质:它是用模型预测的分布去表达真实分布时,所产生的平均意外感。取对数就是为了把概率转换成这种可以相加的“意外分值”。v
统计学视角:最大似然估计(MLE)
在训练模型时,我们的目标是让模型预测出正确标签的概率最大化。
假设正确类别的概率是 Pt,我们希望 Pt 越大越好。
- 乘法难题:如果我们有多个样本,总概率是 Ptotal=P1×P2×⋯×Pn。
- 对数的妙用:由于概率都在 (0,1) 之间,连乘会导致数值迅速变小(趋近于 0),计算机无法处理。
- 转换:对总概率取对数 log(Ptotal)=log(P1)+log(P2)+⋯+log(Pn)。
- 连乘变连加:计算变得稳定。
- 求导变简单:log(x) 的导数是 1/x,在反向传播时非常优雅。
优化视角:消除梯度消失
为什么不直接用“距离” (1−Pt) 而是用 −log(Pt)?
- 如果不取对数:直接使用均方误差(MSE),梯度会包含激活函数(如 Sigmoid/Softmax)的导数。当预测值接近 0 或 1 时,这些激活函数进入“饱和区”,导数极小,导致模型“学不动了”。
- 取对数后:−log(Pt) 的增长速度在 Pt→0 时极快。
- 当模型错得离谱时,对数函数会提供一个巨大的梯度,强制模型快速修正。
- 正如我们之前推导的,Softmax 和对数(交叉熵)结合后,导数简化为了 P−Label,彻底抵消了激活函数带来的梯度变小问题。
“取对数的操作将概率空间的乘法转换成了能量空间的加法。它不仅符合信息论中对‘意外感’的量化,更在工程上提供了一个永不枯竭的梯度源泉,确保模型在预测错误时能得到足够强的修正信号。”
由于目标词是 ’我’(索引2),根据 \eqrefeq:loss−def 使用交叉熵损失,其中 P(w2) 来自 \eqrefeq:P−dist:
L=−logP(w_2)=−log(0.09090)=2.3985\labeleq:loss
物理含义:
交叉熵本质上是在衡量“信息意外感”。如果模型对正确类别的预测概率越低,损失值就越趋向于无穷大,从而给予模型强烈的反馈。
这是神经网络能够高效学习的数学基石。虽然 Softmax 和 交叉熵各自的求导较为复杂,但当它们组合在一起时,损失 L 对原始输出 yi 的偏导数会产生惊人的简化:
∂y_i∂L=∂y_i∂(−lnP_t)=−P_t1⋅∂y_i∂P_t=−P_t1⋅∂y_i∂(∑_jey_jey_t)={−P_t1⋅(∑ey_j)2ey_t∑ey_j−(ey_t)2=−P_t1⋅(P_t−P_t2)=P_t−1,−P_t1⋅(∑ey_j)20−ey_tey_i=−P_t1⋅(−P_tP_i)=P_i,if i=tif i=t=P_i−1(i=t)\labeleq:grad−y−derivation
💡 关键洞见: t 是什么?`?
说明:t 代表 Target(真实标签)的索引
如 “我”(i = 2)后面应该出现的正确的词是“喜欢”(i=3)或者“讨厌”(i=8),即在做数据标注时,正确集应该是[2,3],[2,8]
在损失计算的过程中,,以预测“我”后面的词举例;对于i = 3或8,公式就变成了 Pt−1。这意味着模型预测的概率 Pt 本应该接近 1,但如果它只有 0.9,那么梯度就是 0.9−1=−0.1。这个负号告诉模型:“你猜低了,赶紧把分数加上去。”
- 误差信号源:这个简洁的 P−Label 构成了整个反向传播的起始信号。它直观地告诉网络:预测高了就减小权重,预测低了就增加权重。
- 消除饱和死区:单独的 Sigmoid 在值很大时导数趋于 0(梯度消失),但 Softmax 配合交叉熵后,导数形式中不再含有会导致消失的微小乘积项,只要有偏差,梯度就足够强,保证了深层网络也能快速收敛。
💡 洞见:
权重 W 和偏置 b 是模型的可学习参数 (Learnable Parameters),构成了模型的假设空间;而 Loss 是衡量模型预测分布 P(y∣x) 与真实分布(Label)之间差异的标量度量 (Scalar Metric)。它是参数优化的目标函数,而非模型结构的一部分。
在监督学习框架下,Loss 仅在输出层计算。这是因为训练数据只提供了观测变量 (Observable Variables) y 的真实标签(Ground Truth),而隐藏层属于潜变量 (Latent Variables),缺乏显式的监督信号。因此,必须通过计算输出层的残差,才能构建起优化的起点。
Loss 的核心作用是为反向传播提供全局梯度信号。它定义了优化图谱(Optimization Landscape)的形状。通过链式法则,Loss 将输出空间的误差 (Error) 转化为参数空间的梯度 (Gradient) ∇θL,从而指导随机初始化的参数向损失最小化的方向收敛。
在 4.4 节之前,我们实际上只完成了“构建”工作:我们搭建了神经网络的数学骨架,并用随机数填充了它。此时的模型虽然能运行,但在本质上只是一个“只会输出随机噪声的机器”,它与真实语料库之间存在巨大的偏差(即 Loss)。
从 4.4 节开始,我们将进入“校准(拟合)”阶段。 我们不再关注网络结构本身,而是聚焦于消除偏差。通过反向传播,我们将利用算出的误差去倒推每一个参数的责任,并强制修正它们。 如果说前向传播是模型在“表达”它当前的随机状态,那么反向传播就是语料库在“纠正”模型的错误认知。 这就是神经网络从“随机初始化”收敛到“数据规律”的过程。
计算梯度的唯一目的就是告诉模型如何调整参数,让损失变小。
在训练神经网络时,我们使用梯度下降法来更新参数:
W(y)∗new=W(y)∗old−η⋅∂W(y)∂L
b(y)∗new=b(y)∗old−η⋅∂b(y)∂L
其中:
- η 是学习率(步长)
- ∂W(y)∂L 告诉我们:如果改变 W(y),损失会如何变化
- ∂b(y)∂L 告诉我们:如果改变 b(y),损失会如何变化
如果说前向传播是“预测”,那么反向传播就是“复盘”。它利用链式法则(Chain Rule),将输出层的总损失(Loss)按照路径反向拆解,计算出每一个权重 w 和偏置 b 对最终误差的贡献度(即梯度)。
数学表述:
对于每一层,我们需要计算:
∂w∂Loss=∂激活输出∂Loss×∂线性输入∂激活输出×∂w∂线性输入
中间项正是你之前推导过的激活函数导数(如 σ′(x) 或 1−tanh2(x))。这解释了为什么激活函数必须处处可导。
1.首先计算 ∂y∂L(见 \eqrefeq:grad−y−derivation):
∂y_i∂L=P(w_i)−1!(i=index(target))其中 1(⋅) 是指示函数,当条件为真时为 1,否则为 0。
因此:
∂y1∂L=0.09088−0=0.09088
∂y2∂L=0.09090−1=−0.90910
。。。
∂y11∂L=0.09076−0=0.09076
即:
∂y∂L=[0.09088−0.909100.090930.090950.090970.090990.090970.090950.090910.090780.09076]T\labeleq:grad−y
2.计算 ∂W(y)∂L :
∂W(y)∂L=∂y∂L⋅∂W(y)∂y(链式法则)由于 y=W(y)h+b(y),对于 W(y) 的第 i 行第 j 列元素 W(y)∗i,j:∂W(y)∗i,j∂y_i=h_j,∂W(y)_i,j∂L=∂y_i∂L⋅h_j写成矩阵形式:∂W(y)∂L=∂y∂L⋅hT其中h来自\eqrefeq:h,是被激活函数处理的隐藏层向量
因此:
∂W(y)∂L=[0.09088 −0.90910 0.09093 0.09095 0.09097 0.09099 0.09097 0.09095 0.09091 0.09078 0.09076]×[0.0068−0.00680.0055−0.0055]=0.000618−0.0061820.0006180.0006180.0006190.0006190.0006190.0006180.0006180.0006170.000617−0.0006180.006182−0.000618−0.000618−0.000619−0.000619−0.000619−0.000618−0.000618−0.000617−0.0006170.000500−0.0050000.0005000.0005000.0005000.0005000.0005000.0005000.0005000.0005000.000500−0.0005000.005000−0.000500−0.000500−0.000500−0.000500−0.000500−0.000500−0.000500−0.000500−0.000500
\text{计算}$ \frac{\partial \mathcal{L}}{\partial b^{(y)}}$ :\text{
由于 }y=W(y)h+b(y)\text{,对于偏置向量 }b(y)\text{ 的第 }i\text{ 个元素 }bi(y):
∂b(y)_i∂y_i=1
因此:
∂b(y)_i∂L=∂y_i∂L⋅1=∂y_i∂L
写成向量形式:
∂b(y)∂L=∂y∂L
数值计算:
∂b(y)∂L=[0.09088−0.909100.090930.090950.090970.090990.090970.090950.090910.090780.09076]T
有了输出层的误差梯度后,我们接下来的任务是将这个误差“传回”给隐藏层。这就好比我们知道了最终结果偏离了多少,现在要问责中间的计算单元(隐藏层神经元):“你们当初输出了什么,才导致最终结果错成这样?”
数学上,这通过计算损失函数 $ \mathcal{L} $ 对隐藏层输出 $ h $ 的偏导数来实现。简单来说,就是把误差 $ \frac{\partial \mathcal{L}}{\partial y} $ 通过权重矩阵 $ W^{(y)} $ 反向加权求和。
1. 计算过程
我们需要计算 $ \frac{\partial \mathcal{L}}{\partial h} = (W{(y)})T \cdot \frac{\partial \mathcal{L}}{\partial y} $。
以隐藏层第一个神经元 $ h_1 $ 为例,它的梯度是所有输出层节点梯度的加权和:
∂h_1∂L=∑_j=111W(y)_j,1⋅∂y_j∂L
我们可以展开第一项 h1 的具体计算过程,以看清误差是如何被加权求和的:
∂h_1∂L=0.01×0.09088∗W(y)∗1,1⋅P_1+0.02×(0.09090−1)∗W(y)∗2,1⋅(P_2−1)←目标词Contribution+0.03×0.09093∗W(y)∗3,1⋅P_3+…(其余8项)≈0.0009+(−0.01818)+0.0027+…≈0.0177
\text{注意:}\text{ 这里不能简单地将误差加和后再乘一个权重,而是}\text{每一项误差都要乘以其对应的权重}\text{(如 }0.01, 0.02, 0.03...\text{)。正是因为目标词对应的权重是 }0.02\text{,而其他词的权重各不相同(甚至有负数),才共同作用产生了最终的梯度。
代入精确数值计算,我们得到隐藏层四个神经元的误差梯度:}
∂h∂L≈[0.0177 −0.0063 −0.0054 0.0169]
2.\text{ 通过 }tanh\text{ 激活函数层}\text{
现在的梯度还在 }h\text{(激活后的值)上,我们要穿过 }tanh\text{ 函数,找到 }z(h)\text{(激活前的值)的梯度。因为我们之前计算过 }$ z^{(h)} 的值非常小(接近 0),而 \tanh 函数在 0 附近的导数 1 - \tanh^2(x) $\text{ 几乎等于 }1\text{。
因此,穿过激活函数后,梯度数值几乎保持不变:}
∂z(h)∂L≈∂h∂L=[0.0177 −0.0063 −0.0054 0.0169]
3. 更新隐藏层权重
现在我们知道了隐藏层应该如何调整输出($ \frac{\partial \mathcal{L}}{\partial z^{(h)}} $),就可以结合当时的输入(拼接后的词向量 $ x $),计算出隐藏层权重 W(h) 需要调整的方向。例如对于 W1,1(h),其梯度约为 0.0177×0.05≈0.00088。
最后一步,也是最神奇的一步:误差继续传播,直达输入的词向量。
我们现在的目标是算出 $ \frac{\partial \mathcal{L}}{\partial x} $。这意味着我们要问:“输入的词向量要怎么改,才能让预测更准?” 这正是 Word2Vec 等技术的核心——让词向量在训练中自己学会“移动”到有意义的位置。
1. 计算过程
计算方法是将误差通过隐藏层权重矩阵 $ W^{(h)} $ 再反向投射回去:$ \frac{\partial \mathcal{L}}{\partial x} = (W{(h)})T \cdot \frac{\partial \mathcal{L}}{\partial z^{(h)}} $。
以输入向量的第一个分量 $ x_1 $ 为例(该分量对应第一个 <s> 的第一个维度):
∂x_1∂L=∑_k=14W(h)_k,1⋅∂z(h)_k∂L
代入 $ W^{(h)} $ 的第一列 [0.01,−0.01,0.02,−0.02] 和上面的梯度值:
≈0.01(0.0177)−0.01(−0.0063)+0.02(−0.0054)−0.02(0.0169)≈0.000177+0.000063−0.000108−0.000338≈−0.000206
\text{以此类推,我们可以算出完整的 }9\text{ 维输入梯度 }$ \frac{\partial \mathcal{L}}{\partial x} $。
2.\text{ 词向量的累积更新}\text{
注意,我们的输入 }$ x $\text{ 实际上是由三个相同的词 }<s>\text{ 拼接而成的。因此,}<s>\text{ 这个词向量在本次训练中的总误差,应该是这三个位置梯度的}\text{累加}。
∂c(’<s>’)∂L=∂x∂L∗1:3∗位置1梯度+∂x∂L∗4:6∗位置2梯度+∂x∂L∗7:9∗位置3梯度
最终,我们使用这个累加后的梯度来更新 <s> 的词向量:
c(’<s>’)new=c(’<s>’)old−η⋅∂c(’<s>’)∂L
通过无数次这样的微调,原本随机初始化的 <s> 向量就会慢慢变成一个真正能代表“句首”含义的数学向量。
根据计算出的梯度,我们使用学习率 η=0.1 更新模型参数(SGD 步骤)。
1. 输出层权重更新 (W(y))
公式:W(y)←W(y)−η∂W(y)∂L
以连接 h1 到输出目标“我”(y2) 的权重 W2,1(y) 为例:
梯度计算:∂W2,1(y)∂L=∂y2∂L⋅h1≈(−0.9091)⋅0.0068≈−0.00618
更新步:
W(y)new_2,1=0.02−0.1×(−0.00618)=0.02+0.000618=0.020618
\text{直观意义}\text{:因为 }h1\text{ 是正数 }(0.0068)\text{,且我们需要提高“我”的概率(即提高 }y2\text{),所以权重应该}\text{变大}。
2.\text{ 词向量更新} (C)\text{
公式:}C←C−η∂C∂L\text{
以 }<s>\text{ 的第一个维度为例(累积了三个位置的梯度):
梯度计算:}∂c(’<s>’)1∂L≈−0.000155\text{(数值非常小,因为初始权重还是随机且相互抵消的)
更新步:}
c(’<s>’)new_1=0.05−0.1×(−0.000155)=0.05+0.0000155=0.0500155
\text{直观意义}\text{:虽然变化微小,但经过在海量数据上的无数次迭代,这些微小的推动力汇聚起来,最终会将 }<s>\text{ 推向向量空间中正确的位置。
}3.\text{ 更新后的参数全貌}\text{
经过这一轮反向传播,我们的模型参数发生了如下微小的物理变化(保留}6\text{位小数):
更新后的 }\text{词向量矩阵} Cnew\text{(仅第一行 }<s>\text{ 被修正了):}
Cnew=0.050015−0.0800000.030000−0.0700000.020000−0.0400000.060000−0.0900000.010000−0.0500000.040000−0.0600130.070000−0.0200000.080000−0.0300000.050000−0.0700000.040000−0.0100000.090000−0.0200000.029807−0.0400000.090000−0.0500000.070000−0.0600000.020000−0.0300000.080000−0.0700000.010000
更新后的 隐藏层权重 W(h)new:
W(h)new=0.009909−0.0099680.020027−0.020086−0.0198910.019962−0.0300330.0301030.029945−0.0299810.040016−0.040052−0.0400910.040032−0.0499730.0499140.050109−0.0500380.059967−0.059897−0.0600550.060019−0.0699840.0699480.069909−0.0699680.080027−0.080086−0.0798910.079962−0.0900330.0901030.089945−0.0899810.010016−0.010052
更新后的 输出层权重 W(y)new(注意第二行目标词权重的显著提升):
W(y)new=0.0099380.0206180.0299380.0399380.0499380.0599380.0699380.0799380.089938−0.010062−0.020062−0.019938−0.030618−0.039938−0.049938−0.059938−0.069938−0.079938−0.089938−0.0099380.0200620.0300620.0299500.0405000.0499500.0599500.0699500.0799500.0899500.0099500.019950−0.030050−0.040050−0.039950−0.050500−0.059950−0.069950−0.079950−0.089950−0.009950−0.019950−0.0299500.0400500.050050
💡 总结
可以看到,为了让模型更倾向于预测“我”,与“我”相关的输出层连接权重(第二行)得到了最显著的增强(约 +0.0006),而底层的词向量 <s> 仅发生了极为微小的位移(约 +0.000015)。这符合深度学习的规律:越靠近输出层,梯度信号越强,调整越剧烈;越靠近底层,信号越微弱,需要依靠海量数据的长期浸泡才能发生质变。
至此,我们完整拆解了 Bengio 等人在 2003 年提出的神经网络语言模型。这个模型虽然古老,但它具有划时代的意义,因为它确立了现代 NLP 的两大基石:
- 分布式词向量 (Distributed Representation):首次证明了用低维稠密向量(即矩阵 C)来表示词义是可行的,这直接启发了后来的 Word2Vec。
- “嵌入-编码-输出”的通用架构:Embedding 层 → 神经网络隐层 → Softmax 输出层。这套标准模版(及其变体)至今仍统治着深度学习领域。
它的局限性在哪里?
尽管理念先进,NPLM 在当时并没有立刻引爆 AI 浪潮,主要受限于以下两点,这也正是后续模型进化的方向:
计算量的瓶颈(Softmax 层):
- 模型最慢的部分在于输出层。为了算一个概率 P(y∣x),必须计算词表中所有 ∣V∣ 个词的得分并进行归一化(∑eyi)。当词表达到几十万甚至上百万时,这个计算量是可以忽略不计的。
- 后续进化:Word2Vec (2013) 引入了 层级 Softmax (Hierarchical Softmax) 和 负采样 (Negative Sampling),巧妙地避开了全局 Softmax 的计算,极大地提升了训练速度。
固定窗口的局限 (Fixed Context):
- NPLM 依然像 N-gram 一样,只能看到固定的前 n−1 个词。如果关键线索出现在 100 个词之前(例如:“小明拿起球拍……挥手打出了这一球”),NPLM 因为窗口太小(比如 n=4)而无法捕捉这种长距离依赖。
- 后续进化:RNN/LSTM 引入了“记忆”单元来处理变长序列;而 Transformer (Attention) 则通过注意力机制,实现了对全局上下文的直接建模。
| 时间段 | 状态 | 核心事件 | 为什么没火 / 为什么火了 |
|---|
| 80-90年代 | 诞生 | RNN, LSTM 发明 | 没火:算力太差,梯度消失问题无法解决,不如统计模型好用。 |
| 2003 | 铺垫 | NPLM (FNN) | 铺垫:证明了词向量有效,但算不动深层网络。 |
| 2012 | 启示 | AlexNet (CNN) | 转折:图像领域证明了深度学习可行,GPU 算力到位。 |
| 2013 | 试错 | Deep FNN 尝试 | 失败:发现单纯加深 FNN 搞不定语言的变长特性。 |
| 2014-2015 | 爆发 | RNN 复兴 (Seq2Seq) | 爆发:有了 GPU,有了 Word2Vec,人们发现旧的 RNN 架构正好能解决 Deep FNN 的痛点! |
在AlexNet (CNN)论文中,我们知道了在图片训练上对于多层“隐藏层”,深度神经网络(DNN)可以自动从数据中学习出“层次化”的特征。NLP 研究者看到这一幕非常兴奋:“如果图像可以从像素层层抽象出物体,那么语言是不是也可以?从词向量 → 短语 → 句法结构 → 深层语义?” 👉 这就是为什么 NLP 领域在 2013 年左右疯狂尝试 Deep FNN 的直接动力。
在上一章 NPLM 的学习中,我们遇到了一个致命的瓶颈:“固定窗口”。
无论我们的神经网络多么强大,它永远只能像金鱼一样,只记得前 n−1 个词。如果一句话很长,或者关键线索在文章的开头(例如:“小明出生在法国……所以他会说__语”),NPLM 根本无法跨越这么长的距离关联到“法语”。
为了解决这个问题,我们需要一个能拥有记忆的模型,这就是 RNN (Recurrent Neural Network)。
如果说 NPLM 是在处理一张张静态的快照(固定输入的词向量拼接),那么 RNN 就是在处理连续的电影片段。
- 前馈网络 (Feedforward, 如 NPLM):处理完一个输入 x,输出结果,然后立刻“失忆”。下一个输入 x′ 进来时,网络是一个全新的空白状态。
- 循环网络 (Recurrent, 如 RNN):处理输入 xt 时,不仅看当前的词,还会参考上一步的状态 ht−1。这个 ht−1 就像是接力棒,传递了之前所有历史信息的摘要。
为了精确控制每一个时间步的计算,我们需要拆解 RNN 的内部结构。
不同于 NPLM 将上下文一次性“压扁”输入,RNN 是一个循环(Recurrent)结构。它在每一个时间步 t 重复执行相同的计算逻辑,但输入的数据 xt 和内部状态 ht 却在不断变化。
虽然物理上只有一个 RNN 单元,但在逻辑上,我们可以将其按时间轴展开。对于一个长度为 T 的序列,RNN 的计算流程如下:
- 输入层 (xt):当前时刻的单词(One-hot 或 Embedding)。
- 隐藏层 (ht):当前时刻的“大脑状态”。它不仅取决于当前的输入,还取决于上一时刻的状态 ht−1。
- 输出层 (yt):预测下一个单词的概率分布。
与 NPLM 不同,RNN 的强大之处在于参数共享。无论序列有多长,我们始终只使用一套权重矩阵。这三组矩阵是 RNN 学习的核心内容:
输入到隐层权重 W(x) (Dimension: m×d)
- 作用:将当前的输入词向量 xt 变换到隐层空间。
- 类比:NPLM 中的 W(h),负责处理“新鲜事”。
隐层到隐层权重 W(h) (Dimension: m×m)
- 作用:将上一时刻的状态 ht−1 变换到当前空间。
- 关键:这是 RNN 的记忆核心,负责传递历史信息。
隐层到输出权重 W(y) (Dimension: ∣V∣×m)
- 作用:根据当前状态 ht 预测下一个词。
- 类比:NPLM 中的输出层权重 W(y)。
在每一个时间步 t,我们需要按顺序执行以下计算:
步骤 1:融合信息,更新状态
我们需要计算当前隐层状态 ht。计算公式为:
h_t=σ(W(h)h_t−1+W(x)x_t+b_h)
- W(h)ht−1:对“过去记忆”的提取。
- W(x)xt:对“当前输入”的提取。
- σ:通常使用 tanh 激活函数,将值压缩在 [−1,1] 之间。
步骤 2:基于当前状态,预测未来
有了最新的状态 ht,我们就可以预测下一个词的概率分布 yt:
y\_t = \text{softmax}(W^{(y)} h\_t + b\_y) $
- 注意:yt 是一个维度为 ∣V∣ 的向量,表示词表中每个词是下一个词的概率。

为了彻底理解 RNN 的工作机制,我们再次请出老朋友——那句“我 喜欢 机器 学习”。这一次,我们将用 RNN 的视角来重新审视它是如何被处理的。
假设我们依然使用简化的参数配置,以便于追踪数据流向:
- 词表大小 (∣V∣):11(包含
<s>, 我, 喜欢, 机器, 学习, </s> 等)。 - 词向量维度 (d):3。
- 隐藏层维度 (m):4。
这意味着我们将有以下三个核心矩阵:
- W(x) (4×3):负责将输入的 3 维词向量转换进 4 维隐层。
- W(h) (4×4):负责将上一步的 4 维隐状态转换进当前。
- W(y) (11×4):负责从 4 维隐状态预测 11 个词的概率。
我们的目标是预测序列 <s> → 我 → 喜欢 → 机器 接下来的词。RNN 会一步步“吃”进这个序列。
时刻 t=0:初始化
- 在一切开始之前,我们需要一个初始状态 h0。通常将其设为全 0 向量:
h0=[0,0,0,0]T
时刻 t=1:输入“<s>”
时刻 t=2:输入“我”
时刻 t=3:输入“喜欢”
时刻 t=4:输入“机器”
RNN对上一个“时刻”,即ht−1的“记忆”在公式中有体现。对ht−2−h0 的记忆就是一个 隐藏的迭代关系,即
h3=计算(这是h2计算(h1,x2),x3)
$ h\_i = \tanh(W^{(x)} x\_i + \mathbf{W^{(h)} h\_{i-1}} + b\_h)
h_i=tanh(W(x)x_i+b_h)
y_i=softmax(W(y)h_i+b_y)
为了看清“记忆”是如何传递的,我们假设 W(h) 是一个简单的单位矩阵 I(这意味着过去的状态被原封不动地传给了现在),且激活函数暂且看作是线性的(ht≈…)。
那么,h4 实际上等于:
h\_4 \approx \underbrace{W^{(x)} x\_4}*{\text{机器}} + \underbrace{W^{(x)} x\_3}*{\text{喜欢}} + \underbrace{W^{(x)} x\_2}*{\text{我}} + \underbrace{W^{(x)} x\_1}*{\text{\<s\>}} $
看到其中的奥妙了吗?
在 RNN 中,最后的隐状态 h4 实际上是所有历史输入信息的一个“加权和”。这就是为什么 RNN 能够处理任意长度序列的原因——它把所有历史都压缩进了一个固定大小的向量 ht 中。
为了理解为什么 h1 会被记得(以及为什么容易被遗忘),我们换个比喻:调鸡尾酒。
- t=1 (处理 h1):你往杯子里倒了一杯伏特加(这是 x1 带来的信息,h1 现在全是伏特加味)。
- t=2 (处理 h2):你拿着这杯伏特加,往里面加了橙汁(x2)。现在的混合液(h2)是“伏特加+橙汁”。
- t=3 (处理 h3):你拿着这杯混合液,又往里面加了石榴糖浆(x3)。现在的混合液(h3)是“伏特加+橙汁+石榴糖浆”。
问: 在 h3(最后这杯酒)里,还有没有 h1(伏特加)?
答: 当然有!它是混合液的一部分基底。
但是(关键问题来了):
如果这个过程重复 100 次呢?你往里面不断加水、加茶、加可乐……到了第 100 杯(h100)的时候,你还能尝出第一杯倒进去的伏特加味道吗?
恐怕很难了。
按照“溶液的比喻”,为什么做不到:反向传播(Backpropagation),它的目的不就是去调整权重 W,找到那个神奇的数值,让模型能够学会‘无论隔多远都要记住’吗?
答案是:理论上是的,但实际上做不到。这就是著名的“梯度消失”(Vanishing Gradient)
在 NPLM 中,我们只关注预测一个词的准确性。但在 RNN 语言模型中,我们的目标是预测整个序列的概率。根据教材公式 (1.30),RNN 的训练不再是单点的战役,而是一场持久战。
整体概率的链式分解
对于句子“我 喜欢 机器 学习”,RNN 实际上是在同时做四个预测任务:
- 给定
<s>,预测 “我” - 给定
<s>, 我,预测 “喜欢” - 给定
<s>, 我, 喜欢,预测 “机器” - 给定
<s>, 我, 喜欢, 机器,预测 “学习”
序列损失函数 (Sequence Loss)
为了训练这个“时间机器”,我们需要计算每一个时间步的交叉熵损失,然后将它们平均或求和。
设 t 时刻的真实标签为 yt,模型预测的概率分布为 y^t,则总损失 L 定义为:
L(θ)=T1t=1∑Tℓ(y^t,yt)=−T1t=1∑TlogP(targett∣contextt)
直观理解:
我们不仅要求模型在结尾处答对,还要求它在过程中的每一步都“想对”。如果模型在第 2 步就猜错了(比如把“喜欢”猜成了“讨厌”),即使第 4 步猜对了“学习”,总损失依然会很高。这种机制强制模型必须全程保持清醒,不能只看结果。
上文调色盘(或者盐水)的形象案例,其实触碰到了 RNN 最大的痛点:
虽然 h3 确实包含了 h1 的信息,但是随着步骤(Time Steps)的增加,最早期的信息会被后来的信息不断稀释、覆盖。
在数学上,这导致了著名的梯度消失(Vanishing Gradient)问题。
近的记得清: h3 记得 h2 非常清楚。
远的忘得快: h100 对 h1 的记忆可能已经微乎其微了。
RNN 最迷人也最危险的地方在于它的反向传播。由于参数 W(h) 在所有时间步是共享的,当我们计算 t=4 时刻的误差对 W(h) 的梯度时,我们需要追溯前面所有时刻的影响。这种算法被称为 BPTT (Backpropagation Through Time)。
梯度的链式连乘
损失 L 关于隐层参数 WH 的梯度包含了一个关键的连乘项。对于 t 时刻的损失,其梯度回传到 k 时刻 (k<t) 的路径上,涉及了从 k 到 t 之间所有隐状态导数的乘积:
$ \frac{\partial h\_t}{\partial h\_k} = \prod\_{j=k+1}^{t} \frac{\partial h\_j}{\partial h\_{j-1}} = \prod\_{j=k+1}^{t} W\_H^T \cdot \text{diag}(f'(z\_j)) $
这里 ∂hj−1∂hj 本质上就是权重矩阵 WH 乘以激活函数的导数。
核心危机:连乘的放大与缩小
想象一下,如果序列长度 T 是 100(即我们要处理 100 个词长的句子),那么梯度就需要连乘 100 次矩阵 WH。
梯度消失 (Gradient Vanishing)
如果 WH 的最大特征值小于 1(或者激活函数导数小于 1,如 Sigmoid/Tanh 的导数都在 0 到 1 之间),那么经过多次连乘,梯度值会指数级衰减,趋近于 0。
后果:模型出现了“健忘症”。第 100 步产生的误差信号,传回到第 1 步时已经微乎其微。模型无法学习到长距离的依赖关系(例如无法将句首的“小明”与句尾的“他”联系起来)。
梯度爆炸 (Gradient Exploding)
如果 WH 的最大特征值大于 1,多次连乘后梯度会指数级膨胀,变成 NaN 或无穷大。
后果:模型训练极其不稳定,参数大幅震荡,无法收敛。
💡 洞见:RNN 的两难困境
为了捕捉长距离依赖,我们需要梯度能够传播得很远;但为了梯度不爆炸或消失,我们又需要严格控制权重的数值范围。
这种“长期记忆”与“数值稳定性”之间的内在矛盾,是原始 RNN 结构无法克服的物理缺陷。这也直接催生了后来的 LSTM 和 GRU(通过门控机制,创造了一条可以让梯度“无损通过”的高速公路)。
在 RNN 训练好之后,我们如何用它来生成文本?这里存在一个训练与推理不一致的经典问题。
在推理(Inference)阶段,模型的运作方式是“自产自销”:
- 输入
<s>,模型预测输出 我。 - 将预测出的
我 作为下一时刻的输入,模型预测 喜欢。 - 将预测出的
喜欢 作为下一时刻的输入...
这种模式被称为自回归。但在训练阶段,如果我们也这么做,会遇到严重问题:错误累积。如果模型第一步把“我”预测成了“你”,那么第二步的输入就是错误的“你”,导致后面步步皆错,训练效率极低。
为了解决训练效率问题,教材 1.2.2 节提到了 Teacher Forcing 技术。
定义:在训练阶段,不使用模型上一时刻的预测输出作为当前时刻的输入,而是强制使用数据集中的真实标签(Ground Truth)作为输入。
例子:
即使模型在 t=2 时错误地预测了“讨厌”,在计算 t=3 时,我们依然强行把正确的词“喜欢”喂给模型作为输入。
优势:
并行化可能:虽然 RNN 结构是串行的,但有了 Teacher Forcing,我们不需要等上一时刻预测完才能计算下一时刻。我们可以一次性把所有正确答案准备好,计算所有时刻的损失。
纠错快:就像老师在学生每做一个动作时就立刻纠正手型,而不是等整套广播体操做完再纠正。
Teacher Forcing 虽然好,但带来了一个副作用,即 曝光偏差。
现象:
训练时:模型被“老师”保护得很好,每次输入的都是标准答案(Ground Truth),从未见过错误的输入。
测试时:没有“老师”在场,模型必须依赖自己上一步的预测。一旦生成了训练中没见过的错误词,模型就会因为缺乏纠错经验而彻底迷失(Error Propagation)。
🧠 总结
RNN 是深度学习处理序列数据的第一次伟大尝试。
伟大之处:引入了隐状态 h,实现了参数共享和变长序列处理,理论上具备了图灵完备性。
阿喀琉斯之踵:BPTT 算法带来的梯度消失/爆炸限制了它的记忆长度;Teacher Forcing 带来的曝光偏差限制了它的生成稳健性。
这一切的局限,都在呼唤一个新的架构——Transformer 的诞生。
在 nanoGPT(基于 GPT-2 架构)中,核心的维度定义与你之前的 NPLM 有所不同,我们引入了多头注意力和层数的概念。
| 参数名称 | 参数值 | 含义说明 |
|---|
| 词汇表大小 | V=11 | 包含特殊符号和标点在内的总词数 |
| 上下文窗口大小 | T=3 | 序列最大长度(Context Length/Block Size) |
| 嵌入维度 | C=4 | 词向量与隐藏层的统一维度(即论文中的 dmodel) |
| 注意力头数 | nhead=2 | 将 4 维切分为 2 个头,每个头维度 dk=2 |
| Transformer层数 | nlayer=1 | 仅演示单层 Decoder Block 的完整前向传播 |
| 激活函数 | GELU | GPT 系列默认使用的非线性激活函数 |
| 学习率 | η=0.1 | 用于后续演示反向传播的梯度下降步长 |
与 NPLM 类似,我们需要将文本映射为离散的索引。在这里,我们复用你之前的词汇表,并设定当前时间步的输入状态。
| 项目 | 内容 |
|---|
| 词汇表 | V=(’<s>’,’我’,’喜欢’,’机器’,’学习’,’深度’,’他’,’讨厌’,’传统’,’方法’,’</s>’) |
| 词索引映射 | index(’<s>’)=0、index(’我’)=1、⋯、index(’</s>’)=10 |
| 训练输入序列 | X=[’<s>’,’我’,’喜欢’] (对应索引:[0,1,2]) |
| 训练目标序列 | Y=[’我’,’喜欢’,’机器’] (对应索引:[1,2,3]) |
💡 关键洞见:自回归(Autoregressive)与并行训练的本质区别
在你之前的 NPLM 例子中,输入是前 3 个词,输出仅仅是第 4 个词(单点预测)。
Transformer/nanoGPT 的强大之处在于全局并行。输入序列 X=[0,1,2] 实际上同时在做三个位置的预测。第一个位置用索引 0 预测索引 1;第二个位置用索引 0,1 预测索引 2;第三个位置用索引 0,1,2 预测索引 3。通过后续的下三角掩码(Causal Mask),模型在一次前向传播中就能并行计算出整个序列所有位置的 Loss,这极大地提升了现代大语言模型的训练效率。GPT 系列统一使用 作为文档的起始和结束标识。
在进入复杂的自注意力机制之前,输入数据首先要经过嵌入层(Embedding Layer)。nanoGPT 包含两个独立的嵌入矩阵:词嵌入和位置嵌入。
我们将每个词索引映射为 C=4 维的稠密向量。这里依然使用均匀分布 U(−0.1,0.1) 进行初始化展示。权重矩阵 Wte∈R11×4:
Wte=0.050.02−0.060.010.030.070.08−0.010.04−0.090.06−0.08−0.040.07−0.05−0.04−0.06−0.070.09−0.030.01−0.020.030.06−0.020.040.090.020.01−0.020.07−0.050.08−0.07−0.090.08−0.02−0.05−0.030.050.06−0.080.03−0.01
矩阵尺寸由上下文窗口大小 T=3 和嵌入维度 C=4 决定,即 Wpe∈R3×4。我们同样进行随机初始化:
Wpe=0.01−0.050.09−0.020.06−0.010.03−0.070.02−0.040.08−0.03
💡 关键洞见:原版 Transformer 与 nanoGPT (GPT-2) 在位置编码上的重大分歧
你在阅读《Attention Is All You Need》时,会发现作者使用正弦和余弦函数(Sine/Cosine)来硬编码位置信息,不需要反向传播更新。其初衷是希望模型能直接利用三角函数的周期性推断出相对位置。
然而,在你当前手写的 nanoGPT(遵循 GPT-2 架构)中,位置信息采用的是上方的绝对可学习参数(Learned Positional Embeddings)。这意味着位置矩阵 Wpe 就像词汇表一样,里面的数值(如 0.01,−0.02 等)会随着每一轮的反向传播而不断被更新。工程实践证明,在海量数据喂养下,让模型自己去“领悟”位置特征,往往比人类硬性规定的三角函数效果更好。
在时间步 t=1,2,3(对应我们序列的三个位置),我们将词令牌嵌入与对应的绝对位置嵌入直接按元素相加,生成最终输入到 Transformer 块的隐藏状态 H(0)∈R3×4。
以第一行输入文本 <|endoftext|> 为例,其词索引为 0,提取 Wte 的第一行,并加上 Wpe 的第一行(代表绝对位置 0):
H0,:(0)=[0.05−0.080.03−0.07]+[0.01−0.020.03−0.04]=[0.06−0.100.06−0.11]
以此类推,输入序列 X=[0,1,2] (<|endoftext|>,我,喜欢)最终得到的初始隐藏层矩阵为:
H(0)=0.06−0.030.03−0.100.020.060.06−0.010.00−0.11−0.010.05
至此,数据已经准备完毕,彻底化作了包含语义与位置双重信息的浮点数矩阵,准备好进入庞大的注意力机制中了。
由于后续的 Q、K、V 投影矩阵生成以及掩码自注意力(Masked Self-Attention)的数学推导是整篇文档最硬核、最长的一块内容,我把它们划分在接下来的小节中。
你想我现在继续为你生成第 3 节剩余的参数初始化(Q/K/V 权重、LayerNorm 权重),并直接切入第 4 节的前向传播(计算 Attention 矩阵 A)吗?
$