一、HMM 回顾与统计建模框架
1.1 序列标注的两个模型
先对之前内容进行梳理:对于序列标注任务,我们介绍了两个模型——一个是隐马尔可夫模型(Hidden Markov Model, HMM),另一个是条件随机场(Conditional Random Field, CRF)。介绍完这两个模型之后,终于可以对它们的理论进行深入分析。
1.2 生成模型的联合概率建模
HMM 是一个生成模型(generative model)。一般来讲,生成模型是对我们能够观察到的可观测数据(visible data)$V$ 和我们希望推断的内部隐状态(hidden state)$H$ 进行联合概率密度的建模:
\[P(V, H \mid \theta)\]其中 $\theta$ 是模型的参数。
1.3 HMM 的三组参数
在 HMM 中,参数 $\theta$ 已经是非常清楚的了,包括三部分:
- 状态转移概率(transition probability):$P(H_t \mid H_{t-1})$ —— 隐状态之间的转移
- 发射概率(emission probability):$P(V_t \mid H_t)$ —— 在状态转移过程当中,能观察到的那个 observation 的生成概率
- 初始状态分布 $\pi$:$P(H_0)$ —— 初始状态的先验分布
1.4 为什么叫”生成模型”
为什么我们把 HMM 叫做生成模型?其实很简单。如果我们已经给定了参数 $\theta$,得到了联合概率分布 $P(V, H \mid \theta)$,我们实际上可以做两件事:
第一件事:评估(Evaluation)——将隐状态边缘化,得到似然函数
给定参数后,我们想知道观测数据出现的概率有多大。由于 $H$ 是离散状态,我们用求和的形式将隐状态”积掉”(边缘化):
\[P(V \mid \theta) = \sum_{H} P(V, H \mid \theta)\]这就得到了在给定参数情况下,可观测数据(visible data)的发生概率。这个量就是我们的似然函数(likelihood function)。它的作用是评估模型——在当前参数下,这个 observation 出现的概率有多大。
因为隐状态是离散的分布,所以我们用加和的形式把它边缘化掉。如果扩展到更一般的、连续的形式,就需要用积分来完成边缘化。
第二件事:解码(Decoding)——给定观测数据,求隐状态
解码就是在给定可观测数据(visible / observation)的情况下,把隐状态推断出来。从统计角度来说,就是求条件概率:
\[P(H \mid V, \theta)\]如果我们已经知道了联合分布,要得到条件分布,容易不容易?
答:其实挺容易的。联合概率不能直接”积掉”变成条件概率,但可以用贝叶斯公式拆开:
\[P(H \mid V, \theta) = \frac{P(V, H \mid \theta)}{P(V \mid \theta)}\]
总而言之,生成模型是对联合概率进行建模。我们既可以用它来生成数据(采样),也可以从数据中把隐藏状态推断出来。
1.5 参数优化:最大化似然函数
接下来是优化问题。这是一个非常通用的框架,以后遇到的模型也可以参考这个思路。
核心思路:几乎所有的统计模型,一旦你决定了怎么去建模,得到了在给定参数 $\theta$ 的情况下生成数据的概率——也就是似然函数 $P(V \mid \theta)$ 之后——我们要做的无非就是通过调整参数 $\theta$,使得似然函数的值越来越大。
所有的统计模型都是这么干的:确定模型结构 → 得到似然函数 → 最大化似然函数。
方法一:基于梯度的优化
一般来讲,我们会对似然函数关于参数 $\theta$ 求梯度:
\[\nabla_\theta P(V \mid \theta)\]得到参数的调整方向,然后按迭代法更新参数:
\[\theta^{(t+1)} = \theta^{(t)} + \eta \cdot \nabla_\theta P(V \mid \theta^{(t)})\]这是一个万能的方法。具体来说:
- 一阶方法(梯度下降):利用似然函数对参数的一阶导数(梯度方向),计算简单但方向不够精确
- 二阶方法(牛顿法):利用二阶导数信息,方向更准确,但需要计算海塞矩阵(Hessian matrix),计算复杂度高
很多优化算法本质上都是在研究如何对海塞矩阵进行快速的初始化和近似估计——因为直接计算海塞矩阵太贵了。一阶梯度的方向肯定没有二阶的准,但二阶的代价是计算复杂度大幅增加。
方法二:EM 算法(Expectation-Maximization)
HMM 有一个特例——它有一种更高效的迭代方法,叫 EM 算法:
- E 步(Expectation):根据当前参数,估计隐状态的期望,求出数据的发生概率(即似然函数)
- M 步(Maximization):通过参数更新来最大化似然函数
EM 算法的套路其实和梯度法是一样的——都是先得到似然函数,然后最大化它。只不过跟基于梯度的方法不一样的是,EM 利用了模型的特殊结构,迭代效率更高。
EM 算法不限于隐马尔可夫模型,但是 HMM 用 EM 算法的迭代效率特别高。
总结两种优化路线:
| 方法 | 适用范围 | 特点 |
|---|---|---|
| 基于梯度(一阶/二阶) | 万能方法,适用于几乎所有模型 | 一阶简单但慢,二阶准但计算代价高 |
| EM 算法 | 含隐变量的模型(如 HMM) | 利用模型结构,迭代效率更高 |
1.6 概率图模型表示
HMM 和 CRF 都可以用概率图模型(Probabilistic Graphical Model)来表示。概率模型一般来讲都可以用基于图的方式来表示建模过程中不同变量之间的依赖关系。这种依赖关系会告诉我们概率模型的分解形式到底是什么样的。
两种图的含义:
- 有向图(directed graph):每条有向边表示一个条件概率
- 无向图(undirected graph):无向边连接的变量之间表示联合概率(势函数)
以后看到概率图,你一下子就能看出这个概率模型的结构。有向图代表条件概率,无向图代表联合概率——记住这两条就行。
1.7 HMM 的概率分解形式
看 HMM 的有向图结构,我们就能直接写出整个序列的概率分解形式。
- 第一条边:根据初始隐状态 $H_0$,生成第一个观测 $V_0$ 的概率 → $P(H_0) \cdot P(V_0 \mid H_0)$
- 第二条边(状态转移):从 $H_0$ 转移到 $H_1$ → 乘上 $P(H_1 \mid H_0)$
- 第三条边(发射):根据 $H_1$ 生成 $V_1$ → 乘上 $P(V_1 \mid H_1)$
- 以此类推…
最终得到完整的分解式:
\[P(V, H) = P(H_0) \cdot P(V_0 \mid H_0) \cdot \prod_{t=1}^{T} \left[ P(H_t \mid H_{t-1}) \cdot P(V_t \mid H_t) \right]\]这个分解式就是 HMM 模型最主要的基础。
二、HMM 的 Label Bias 问题
2.1 概率连乘形式的固有缺陷
上面的概率分解式是一个连乘形式。可以认为:这种分解式会带来一个根本性的问题。
考虑连乘式中的每一步:状态转移概率 $P(H_t \mid H_{t-1})$ 必须满足概率归一化条件——从某个隐状态出发,转移到所有可能后续状态的概率之和必须等于 1。这就是所谓的局部归一化(local normalization)。
2.2 后续状态数量导致的偏置
局部归一化会带来什么问题呢?举一个直观的例子:
- 如果某个隐状态 $H_0$ 后面只有 1 种可能的后续状态,那么转移概率必定为 1
- 如果后面有 2 种可能的后续状态,从期望来讲,每个状态的转移概率约为 0.5
- 如果后面有 10 种可能的后续状态,从期望来讲,每个状态的转移概率约为 0.1
在概率连乘展开的过程中,同样一个序列,如果以这样的连乘形式来计算,后续状态数量少的隐状态一定会”占便宜”——因为它不需要把概率分散到多个后续状态上去,每一步乘的因子更大。
2.3 Label Bias Problem
这就是 HMM 这类模型天然引入的偏置(bias):模型会系统性地偏好那些后续状态较少的路径,因为这些路径在概率连乘中不会因为归一化而被压低。
总结来看,这就是因为概率分解式造成的模型天然劣势。一个状态如果后续分支特别多,它一定会分散概率。
这个问题的根源就在于 local normalization——每一步的状态转移概率必须加和为 1,否则就不是一个合法的概率分布。这是概率模型的基本要求,但正是这个要求导致了 label bias。
2.4 马尔可夫性质的影响
此外,HMM 还有马尔可夫性质:一旦进入了某个隐状态 $H_t$,后面的发展就完全由当前状态决定,跟之前发生了什么事情都没有关系。这意味着每一步的转移概率完全独立于更远的历史,进一步加剧了 local normalization 的问题。
总结来看,按照这样的概率分解式,模型一定会有 bias。那怎么办呢?——这就引出了从 HMM 走向 CRF 的动机。
三、从HMM/CRF迈向深度学习
3.1 CRF(条件随机场)如何解决Label Bias
上一节我们看到,HMM(隐马尔可夫模型)由于其生成模型的本质,在局部归一化时会产生 label bias 问题。那么如何解决?
| 关键思路:不再使用生成模型,而是改用判别模型——直接建模 $P(H | V)$,即给定观察序列,标签序列的概率。 |
注意:生成模型是条件概率分解($P(V H) \cdot P(H)$),而判别模型反而是直接建模联合概率的一个变换形式。经常搞反——”为什么生成模型是条件概率,判别模型反而像是联合概率?”
| 具体做法:对 $P(H | V)$ 用贝叶斯公式展开: |
- 分母 $P(V)$:对于同一个观察样本,所有候选标签序列共享相同的分母,因此在比较不同标签序列时可以忽略
- 分子:不再像HMM那样分解为概率相乘的形式,而是变成一个打分形式(scoring)
什么是打分形式?定义一系列特征函数(feature function),观察某个 pattern(如当前汉字与某个标签的共现)在数据集中出现的频率/支持度:
- 如果某个 pattern 经常出现,给高分
- 如果不出现,给低分
CRF的得分函数:将所有定义的特征模板演化出来的特征在数据集中出现的频率全部加起来,作为该标签序列的得分。
\[\text{Score}(H, V) = \sum_{k} \lambda_k \cdot f_k(H, V)\]这是一个 global normalization(全局归一化)——分子是对整条序列所有观察到的特征的求和,而不是像HMM那样逐位置局部归一化。
“那么分母说分母要不要计算?分母其实都是一样的——你出现了这个样本,我也出现这个样本,分母都是一样的。所以模型就变成了:我只要把定义的所有特征来支持某种标签序列的得分全部加起来,那个得分最高的就是解码结果。”
3.2 CRF的特征模板设计
CRF理论上是一个上下层全连接的无向图模型——上面是状态(hidden state),下面是观察(observation)——这给了我们非常大的自由度来定义特征模板。
例如可以定义:
- C0S0:当前观察字与当前标签的关系
- C-1C0S0:前一个字、当前字与当前标签的组合
- C-1C0C1 / S-1S0S1:更大窗口的组合
- 甚至全连接
但是全连接会带来组合爆炸问题,而且一定会过拟合——因为大窗口组合在整个语料集中出现的频率极低。
特征的数量与窗口内状态数、观察数呈指数关系:状态数 $\times$ 状态数 $\times \cdots \times$ 观察数。
为什么大模板还要配合小模板?
这是一个常见的问题:”既然我已经定义了三元组合的模板(如 C-1C0S0),它不是已经包含了二元模板(如 C0S0)的信息了吗?”
原因很简单:大窗口模板可能导致特征非常稀疏。在解码(测试)时,某些字符组合可能在训练集中从未出现过——可能是训练集不够大,也可能是测试集与训练集分布差异较大。当大模板匹配不上时,模型可以退化(back off) 到只利用当前字与标签的信息(小模板),仍然能够工作。
“定义模板时需要注意:大的模板和小的模板都要定义,因为大模板可能在解码时没有被看到。”
3.3 CRF特征工程的痛点
以中文分词任务为例:
- 使用CRF模型,窗口大小最多为5
- 历史上学者尝试过的候选特征模板大约有128个(从几百个中筛选)
- 五阶窗口下,每个 character 有约8000个可能取值(从 Wikipedia 等语料中提取的简体中文字符+标点),理论特征空间为 $8000^5$
- 要在所有可能的特征子集中选出一个最优子集,这是一个组合优化问题
“我们在中文分词上做过这个工作。即使用贪心策略(greedy)——先对所有模板跑一遍选最优的,再加入下一个最优组合——根本跑不完,计算复杂度非常高。”
这正是推动深度学习兴起的核心动力之一。
3.4 深度学习的两大驱动力
大约在2011-2012年,深度学习刚刚兴起。从CRF时代转向深度学习,有两个关键驱动力:
驱动力一:避免 task-specific feature engineering
CRF需要针对任务手工设计特征模板,这是典型的 task-specific 工作。深度学习的核心优势在于:特征捕捉与任务学习一体化。
“特征捕捉的多个网络层和任务相关的网络层(如分类的 softmax 层、回归的线性层),梯度是不会断的——一直会反传到最初的表征上。所以这个表征就是为这个任务服务的,中间的特征工程就不需要了。”
这就是端到端(end-to-end) 的反向传播——从输出一直传到输入的表示层。
提到周志华的深度森林(deep forest):用树(tree)搭建深度结构。”但这个东西没有起来。我们神经网络多优雅——特征捕捉跟任务直接打通,一套反向传播从尾巴传到头。”
驱动力二:分布式表示(distributed representation)取代 one-hot 编码
3.5 One-hot编码的两大缺陷
如果有8000多个汉字,最简单的编码是 one-hot:字在词表中排第几位,那个位就是1,其余为0。
缺陷一:浪费空间。 8000维的向量只有一个位置是1。
缺陷二(更关键):语义无关性。 “猫”在第128位,”狗”在第1005位——这两个向量之间没有任何关联,它们是正交的。同义词、功能相似词在特征空间上没有关系。
3.6 Embedding/嵌入的含义
核心思想:用一个低维连续空间(如50维或128维)来表示每一个汉字,并且在这个几何空间中,语义相似的词/字距离较近。
- 猫和狗很像 $\Rightarrow$ embedding 向量在空间中接近
- 蹲和站功能相似 $\Rightarrow$ 在空间中也接近
为什么叫”嵌入(embedding)”?就是把词/字嵌到(embed into) 某个空间里面去,而这个空间要有一定的结构性。
3.7 泛化能力的关键
有了好的 embedding,模型的泛化能力大大提升:
“如果我能够处理’猫蹲在墙角’,我就能够处理’狗站在树下’。因为’站’和’蹲’语义上很接近,’猫’和’狗’语义上也很接近。”
3.8 深度学习的核心:表征学习
“深度学习最主要的是表征。你要得到一个好的表征,这个表征适合这个 domain,你就成功了。”
“以前我们从来没考虑过这个事儿——你的输入编码是怎样的,模型就不动了,你 one-hot 进来就是 one-hot。”
深度学习的另一个关键点是 distributed representation——现在统一称为 embedding。
3.9 NLP与CV的历史关系
深度学习最早可能是从图像(CV)领域兴起的。
“以前有一段阶段,搞 NLP 的学者很聪明——图像发展快,我不用拍脑袋的,只要看图像那边干什么,把方法引到 NLP,又是一篇顶会。后来终于 NLP 又扬眉吐气了——因为 Transformer 架构上取得的成果,后来图像反过来要看 NLP 那边搞啥。”
3.10 Transformer的定位
“Transformer 模型其实是一个工程上的优化,Google 也承认了。它的架构并不是一个完全新的架构,而是吸取了非常多之前研究结果的一个工程上的成功。”
Transformer 融合了多项已有技术:
- 残差连接(residual connection)
- 多头注意力机制(multi-head attention)
- 层归一化(layer normalization)
而其最初的驱动力是并行化——Google 当时不是用 GPU,而是自己搞了一套 TPU,Transformer 就是针对这个硬件做的优化,没想到后来成为了基础架构。
四、早期深度学习序列标注模型
4.1 最简单的架构:窗口前馈神经网络
回到序列标注任务,最早的深度学习方案是一个非常简单的窗口前馈神经网络。
输入层: 以句子”狗蹲在墙角”为例,对每个字先转换为一个 embedding(分布式表示),假设维度为50。
Concatenation(拼接): 取窗口大小为5(当前字及前后各2个字),将5个字的 embedding 拼接:
\[5 \times 50 = 250 \text{ 维}\]注意区分两种操作:
- Concatenation(拼接):将多个向量首尾相连,维度相加。例如5个50维向量拼接成250维。
- Element-wise addition(逐元素加法):将多个同维向量对应元素相加,维度不变。
“看文献时,context 向量的 concatenation 就是拼接的,element-wise 是对应位置相加。”
隐藏层(第二层): 线性变换 + 偏置 + 非线性激活
\[h = \sigma(W_2 \cdot x_{\text{concat}} + b_2)\]其中 $W_2$ 是第二层的权重矩阵(不是 $W$ 的平方!$W_1$ 指的是 embedding 矩阵),$\sigma$ 是 sigmoid 激活函数。假设隐藏层维度为100,则 $W_2$ 将250维降到100维。
这个100维的隐藏表征,就是当前字在受到窗口内上下文影响后的、上下文敏感的表征(context-sensitive representation)。
输出层(第三层): 再经过一个线性变换
\[o = W_3 \cdot h + b_3\]输出维度等于标签集大小——对于 BIES 标注方案,就是4维。
输出的含义: 每一维对应一个标签(B/I/E/S),表示当前字在这个上下文窗口影响下,被赋予每个标签的得分/概率值。
4.2 神经网络的优势:避免组合爆炸
“在离散的情况下,五个 character 的组合会产生组合爆炸问题,但在这里就没有了。而且通过拼接的方式,它其实把窗口内五个字的任意一种组合模式都隐含地建模进去了——如果’蹲’对当前字影响较大,就会体现在隐藏层的表征上。”
这正是从离散特征工程转向连续空间表示的核心优势。
4.3 结合CRF的得分函数
单独的窗口网络只给出了每个位置上各标签的得分,但序列标注还需要考虑标签之间的转移关系。
整个序列的得分函数定义为:
\[S(C, T; \theta) = \sum_{i=1}^{N} \left[ f(t_i \mid C, i) + A(t_{i-1}, t_i) \right]\]其中:
- $f(t_i \mid C, i)$:神经网络在位置 $i$ 计算出的、给定上下文 $C$ 时标签 $t_i$ 的得分
- $A(t_{i-1}, t_i)$:转移矩阵,表示从标签 $t_{i-1}$ 转移到 $t_i$ 的得分
- 例如 B $\to$ B 这种转移是不合法的(一个词的开始之后不可能紧接着另一个词的开始),对应的转移得分应极低
4.4 解码:Viterbi算法
解码目标是找到使总得分最大的标签序列:
\[T^* = \arg\max_{T'} S(C, T'; \theta)\]这个 argmax 的求解方法就是之前介绍过的 Viterbi 解码——一个动态规划算法:
- 每个位置都有每个标签的得分
- 下一步的得分 = 当前得分 + 转移得分,取最大值
- 从左到右递推,最后回溯得到最优路径
“argmax 的求解方法——就是 Viterbi 解码,和之前 HMM 上的一模一样,就是一个动态规划,非常简单。”
4.5 网络方程详解
整个网络可以看成函数的复合(composite function),因此可以用链式法则进行反向传播。
第一层(lookup / token embedding):
\[z_i = [M(c_{i-w/2}); M(c_{i-w/2+1}); \ldots; M(c_i); \ldots; M(c_{i+w/2})]\]其中 $M(\cdot)$ 是从词表中提取 embedding 的函数(lookup function),$w$ 是窗口大小。
“这个 lookup function 就是现在说的 token embedding——从词表中把某个 character 对应的 embedding 取出来的一个函数。以前叫 character,现在叫 token。”
将窗口内所有字的 embedding 进行 concatenation。
第二层(隐藏层):
\[h_i = \sigma(W_2 \cdot z_i + b_2)\]$W_2$ 线性变换 + bias + sigmoid 非线性激活。
第三层(输出层):
\[o_i = W_3 \cdot h_i + b_3\]得到当前位置在窗口上下文限定下,不同 tag 的得分向量。
4.6 模型参数
模型中需要学习的参数 $\theta$ 包括:
| 参数 | 含义 | 规模示例 |
|---|---|---|
| $M$ | embedding 矩阵 | $8000 \times 50$(8000个字,每个50维) |
| $W_2, b_2$ | 第二层权重和偏置 | $100 \times 250$(250维输入降到100维隐藏层) |
| $W_3, b_3$ | 第三层权重和偏置 | $4 \times 100$(100维隐藏层到4个标签) |
| $A$ | 转移矩阵 | $4 \times 4$(标签间转移得分) |
4.7 训练优化
训练目标:最大化正确标签序列的对数概率:
\[\max_\theta \log P(T \mid C; \theta) = S(C, T; \theta) - \log \sum_{T'} \exp S(C, T'; \theta)\]其中:
- 分子(取 log 后变为第一项):正确标签序列 $T$ 的得分
- 分母(取 log 后变为第二项):遍历所有可能的标签序列 $T’$ 的得分之和——即配分函数(partition function)
配分函数的难点: 要使概率成为一个合法的概率分布,分母需要考虑所有可能的标签序列。如果标签集大小为4、序列长度为 $N$,理论上有 $4^N$ 种可能——这是 intractable(不可处理的) 的。
“你在文献里经常会看到这个东西 intractable——它是一个组合爆炸的问题,很难估计。我们一般把它叫做配分函数(partition function),也就是使所有可能性概率加和为一的归一化项。”
简化策略: 这个工作吸取了 CRF 的思想,做了一个非常简单但有效的优化——只关注梯度的方向:
- 对于正确的标签:梯度设为 $+1$(让该标签的得分上升)
- 对于错误的标签:梯度设为 $-1$(让该标签的得分下降)
“有时候梯度的 scale 没有那么重要。你算出来梯度是 -2 还是 -5 其实不是那么关键,而是看正还是负比较重要。为什么?因为不管你这个值是多少,后面还会去乘上一个 learning rate。所以在估计梯度时,方向(正/负)比大小更重要。”
这样就避免了计算配分函数的难题。
4.8 训练示例
以”狗蹲在墙角”为例:
| 位置 | 字 | 正确标签 | 预测标签 | 操作 |
|---|---|---|---|---|
| 1 | 狗 | S | S | 正确,不调整 |
| 2 | 蹲 | S | B | 错误! S 标签 +1,B 标签 -1 |
| 3 | 在 | S | E | 错误! S 标签 +1,E 标签 -1 |
| 4 | 墙 | B | B | 正确,不调整 |
| 5 | 角 | E | E | 正确,不调整 |
“一旦有了梯度以后就很方便了——把错误位置的正确标签得分加1,错误标签得分减1,然后反传。经过多轮训练,网络就收敛了。非常简单。”
4.9 小结:这个网络实现了什么
“虽然这个网络当时还不能算是真正的深度网络,但它实现了我们说的两点:第一,不需要特征工程——它没有手工定义的特征模板;第二,模型和特征提取是一体的——直接通过梯度反传的形式就把这个事儿解决了。”
这个窗口前馈网络是从传统序列标注模型迈向深度学习的过渡性工作,为后续更深层的架构(RNN、LSTM、GRU、Transformer)奠定了基础。
五、词向量预训练
5.1 为什么需要预训练
神经网络有大量的局部最优值(local minima),对初始参数值非常敏感。这是神经网络历史上遭受多次打击的核心原因之一。
神经网络历史上的三次浪潮:
- 第一次浪潮:单个感知机(perceptron)无法解决非线性分类问题(如XOR)。解决方案:从单个神经元扩展到多层神经网络,理论证明中间层神经元足够多时可以逼近任意非线性映射。
- 第二次浪潮:虽然理论上可以逼近任意函数,但找不到好的训练算法——因为神经网络对初始值非常敏感,一旦初始值不好,优化直接陷入糟糕的局部最优。
- 第三次浪潮:通过预训练(pre-training)解决初始化问题,让参数在优化开始时就处于参数空间中较好的basin(盆地)。
Why pre-training?就是因为神经网络优化很难做,它对初始值很敏感。 我们要让网络在开始的时候,就在参数空间的比较好的位置上开始,然后一下子就能调到性能最优点上去。
JMLR论文的实验证据: 一篇发表在 Journal of Machine Learning Research 上的工作,用两层网络做了对比实验:
- 有预训练(with pre-training):不同次训练的轨迹比较可控,全部收敛到误差接近零的位置附近。
- 无预训练(without pre-training):不同次训练发散严重,收敛到误差约20%的位置,而且不同运行收敛到了不同的局部最优。
经过好的预训练,不同初始化都会收敛到差不多的局部最优;没有预训练的话,可能收敛到了不同的局部最优值。
5.2 早期预训练:只训词向量
在NLP的早期深度学习阶段,网络还不是很深,embedding参数在整个网络参数中占比高达70%~80%。因此,最早的预训练工作聚焦于训练好embedding空间。
现在Transformer时代,embedding的参数在整个参数中可能十分之一都不到了,因为后面的Transformer有非常多的block。但当时那个年代,前面的embedding参数空间基本上占百分之七八十。
发展脉络:先解决词向量的预训练,再扩展到整个网络的预训练。
5.3 语言模型驱动词向量训练
训练词向量需要一个代理任务(proxy task),自然而然的选择就是语言模型(Language Model)。
语言的本质是上下文预测:
你说了前面几个字,我基本上能预料到你后面想说什么。我们作为native speaker有一个很强的能力——根据上下文来预测。
注意:那个时代的 language model 跟现在的 LLM 完全不是一个概念。早期的语言模型就是从左往右,给定左边的上下文来预测下一个词。
5.4 Bengio的神经语言模型(2003)
Bengio(图灵奖获得者之一,与Hinton、LeCun并列)在2003年提出了第一个神经语言模型:
架构:
- 输入:下一个词的前 $n$ 个词的词向量 $w_{t-1}, w_{t-2}, \ldots, w_{t-n+1}$
- 将这些词向量 concatenation 拼接起来
- 经过一个线性层 + tanh非线性变换,融合前面几个单词的语义
- 最后加一个 softmax分类头 预测下一个词
这套不需要人工标注样本——语料本身就是标签,可以认为是无监督(self-supervised)训练。
瓶颈:softmax层的参数量巨大。 中间隐层假设1000维,英文词表约20万词,那最后一层softmax的参数就是 $1000 \times 200000 = 2 \times 10^8$。
效果还可以,但是很遗憾,这个东西要训练好几个月。
现在的 token parking(分词技术如BPE)就是为了降低词表规模,但当时还没有这种想法。
5.5 Collobert & Weston的对比学习方法(2008)
Collobert(当时在NEC Lab)早期有一篇影响力非常大的工作,核心改进是用对比损失(contrastive loss)/ Margin Loss 代替softmax,大幅降低计算复杂度。
核心思路:负采样(Negative Sampling)
当分类头维度极大时,一个很好的方法是把多分类任务变成打分排序问题:
- 正确的ground truth词的得分要高
- 随机采出若干个(比如20~50个)负样本
- 要求正样本得分高于所有负样本
这样算法复杂度就下来了。你把20万分类变成跟几十个负样本做对比,速度提升巨大。
Margin Loss 的具体形式:
\[L = \max(0, \; 1 - f(w_{\text{pos}}) + f(w_{\text{neg}}))\]计算示例:
- 正样本得分 $f(w_{\text{pos}}) = 2$,负样本得分 $f(w_{\text{neg}}) = 1$,则 $L = \max(0, 1-2+1) = 0$,不调整(正样本得分已经比负样本高)
- 如果负样本得分反超正样本,loss > 0,才会调整参数
后来对比loss慢慢演变,变成只需要一正一负就够了——你采一个负样本就够了。
对比学习(contrastive learning) 后来成为深度学习中极其重要的一个范式。
5.6 Word2Vec(Google, Mikolov)
前面两个方法仍然太慢。Google提出了更加暴力但极其有效的方法——Word2Vec,包含两个模型:
CBOW(Continuous Bag of Words)
- 取当前词的前后各若干个上下文词(如窗口大小=5,前2后2)
- 直接将上下文词的词向量相加(不用任何线性变换!可以取平均)
- 用这个上下文表征去预测中间那个词
他怎么样?直接就加!而且加的效果很好。以前我们会用一个线性变换来得到上下文表征,他直接加,在词向量空间里面注意了,两个东西的上下文要弄起来,现在就简单的方法就是加。这个到现在都是这么用的。
因为没有线性变换层,只有加法,训练速度比前面的方法快得多。
Skip-gram
与CBOW反过来:用当前这个词去预测上下文。
- 每次一轮进来的时候,不是把四个上下文词都预测,而是随机挑一个来预测
- 最终效果:经常一起出现的词,它们的embedding距离就比较近
令人意外的结果:Skip-gram效果优于CBOW!
这个就是非常令人不可思议了。看起来CBOW很优雅,没想到Skip-gram还是更好。
Hierarchical Softmax:哈夫曼树优化
为了解决CBOW/Skip-gram中仍然存在的softmax大分类头问题,Word2Vec使用了Hierarchical Softmax:
- 将词表构建为一棵哈夫曼树(Huffman Tree)
- 把一个20万级的多分类问题变成一个序列二分类问题
- 树上每个节点都有一个参数,预测时将上下文表示与节点参数做logistic回归,决定往左走还是往右走
- 到达目标词只需路径上的若干次二分类都正确即可
为什么用哈夫曼树: 高频词在树中的路径短(分类次数少),低频词路径长。这样平均训练效率非常高。
如果以后碰到几十万或者几百万分类头的问题,千万不要忘记有一个很好的方法:把大量的多分类变成连续的二分类问题。这个优化效果也很好,到现在都不过时。
5.7 词向量的几何性质
Collobert的词向量:语义聚类
Collobert预训练出的embedding空间中,语义相似的词会聚类在一起:
- 输入”French”,最近邻是:Austrian, Belgian, German, Italian, Greek… (都是欧洲国家相关的词)
- 输入”Xbox”,最近邻是:PlayStation 等游戏设备
Word2Vec的词向量:向量算术
Word2Vec的几何空间更加出色,不仅有聚类效果,还能做向量类比运算:
\[\text{king} - \text{man} + \text{woman} \approx \text{queen}\] \[\text{Madrid} - \text{Spanish} + \text{French} \approx \text{Paris}\]马德里是西班牙的首都,把西班牙这个成分减掉,加上French,它居然等于Paris!
注意:
话说回来,这些例子是从大量样本里面挑出来的。我们去验证了一下,也不尽如意——挑出来那几个例子是不错的,但你要去细究它就不一定了。比如北京减去China加上French到底是不是等于Paris?不一定。
5.8 预训练效果验证(中文分词)
在中文分词任务上,embedding预训练带来了巨大的性能提升:
- OOV(Out of Vocabulary,未见词) 的识别准确率从 64% 直接飙升到 70+%,非常显著(significant)
- 这体现了深度学习/神经网络的强泛化能力——在语料集中没见过的pattern也可以正确识别
预训练embedding所带来的模型泛化,也体现了深度学习或者是神经网络,它的泛化能力非常的强。
5.9 CRF vs 神经网络对比
| 对比维度 | CRF | 神经网络 |
|---|---|---|
| 参数量 | $10^6$ | $10^5$(少一个数量级) |
| 训练速度 | 相对较快 | 慢(需要大量反向传播) |
| 推理速度 | 较慢 | 快(基础运算是矩阵乘法,非线性函数通过查表实现) |
| 部署 | — | 早期神经网络模型可以在手机上运行 |
神经网络的训练很慢,但是推理会很快。为什么推理快?因为它的基础就是矩阵乘法,而且那些非线性函数只要通过查表就可以出来。所以早期以神经网络为基础的模型,基本在手机上就能跑。
六、递归神经网络(RNN)
6.1 设计动机
前面的模型(基于窗口的前馈网络)只能看固定大小的上下文窗口。为了处理变长时序信号,需要一种能够融合历史信息的网络架构——这就是递归神经网络(Recurrent Neural Network, RNN)。
6.2 Vanilla RNN的结构与公式
RNN的核心是维护一个记忆向量(memory / hidden state) $H_t$,每个时间步通过以下公式更新:
\[H_t = \sigma(W \cdot H_{t-1} + U \cdot X_t)\]其中:
- $X_t$:当前时刻的输入(如character embedding)
- $H_{t-1}$:上一时刻的记忆/摘要
- $U$:将输入融入记忆的权重矩阵
- $W$:更新历史记忆的权重矩阵(循环连接)
- $\sigma$:非线性激活函数
输出层:
\[O_t = W_{\text{out}} \cdot H_t\]这个记忆每次都要综合当前的输入和之前那个时刻的记忆,综合成一个总的历史记忆。它建模了你看到这个序列的历史的压缩——一个向量摘要(summary)。
6.3 “Recurrent”的含义
为什么叫 recurrent?主要是因为它有自己对自己的循环连接。这个网络会维持一个 memory(记忆),就是这个 $H$,也叫 summary。
展开来看:第一个token输入进去,得到一个中间的记忆表征;第二个进去后融合前面那个得到新的表征;依此类推,不断向前推进。
6.4 参数共享:处理变长序列的关键
所有时刻共享同一组参数 $W$ 和 $U$,这是RNN能处理变长序列的关键。
我这个网络不是每一个时刻都有特定的 $W$ 和 $U$,而是所有时刻都共享一个 $W$ 和 $U$。如果每个时刻都有一个 $W$ 和 $U$,就只能处理定长问题了。
正因为参数共享,才会产生后面要讨论的梯度问题。
6.5 中文分词应用
将RNN用于中文分词:
- 输入:character embedding 序列
- 每个时间步,RNN根据hidden state输出4个tag(B/M/E/S)的分类结果
- 通过隐层 $H_t$ 融合了历史上下文信息
6.6 梯度问题:理解LSTM的关键前置知识
RNN在反向传播时,需要对 $H_t, H_{t-1}, H_{t-2}, \ldots$ 逐层计算梯度。由链式法则,梯度涉及雅可比矩阵(Jacobian matrix) $\frac{\partial H_t}{\partial H_{t-1}}$ 的连乘。
两种问题:
- 梯度爆炸(Gradient Exploding):如果雅可比矩阵的 norm > 1,连乘后序列越长梯度越大,最终爆炸
- 梯度消失(Gradient Vanishing):如果雅可比矩阵的 norm < 1,连乘后序列越长梯度越小,最终趋近于零
如果它的雅可比矩阵 norm 大于1,你序列一长,它是不是一定会 explode?如果 norm 小于1,一定会 vanish。
这意味着传统的 Vanilla RNN 只能建模大约10步左右的依赖关系,无法捕捉长距离依赖。
了解这个问题以后,我们才会知道为什么后面要提出 LSTM。