查看全课大纲(第 27 / 69 节)
- 1.NLP 的研究领域、应用与学习路径
- 2.自然语言的基本术语
- 3.信息论基础:信息熵与交叉熵
- 4.语言的进化与语义变迁
- 5.主观概率、客观概率与条件概率
- 6.贝叶斯原理与推理
- 7.随机变量:二项式、期望方差与联合概率
- 8.常见概率分布
- 9.辛普森悖论与贝叶斯解题实例
- 10.语言模型:语言概率与词袋模型
- 11.n-gram 语言模型(二元 / 三元 / N-Gram)
- 12.语言模型评价:交叉熵、困惑度与插值
- 13.朴素贝叶斯文本分类:垃圾邮件
- 14.拼写检查
- 15.中文分词:构词法、最大匹配、最优路径与 Jieba
- 16.词表示问题与语义相似度
- 17.TF-IDF 权重处理
- 18.One-Hot 表达与词表示的局限
- 19.Word2Vec 核心思想
- 20.Word2Vec 训练过程
- 21.Skip-gram 模型详解
- 22.词性标注任务与标注方法(规则 + N-Gram)
- 23.EM 算法与混合模型
- 24.高斯混合模型 GMM
- 25.从混合模型到隐马尔可夫模型
- 26.隐马尔可夫模型(HMM)推导与维特比解码
- 27.线性链条件随机场 CRF
- 28.贝叶斯网络与图模型
- 29.隐马尔可夫链与 HMM 推导
- 30.PLSA 主题模型
- 31.LSI 与 SVD 向量表达
- 32.LDA 与狄利克雷分布
- 33.主题模型实验复现(困惑度 / 分类 / 相似度 / 延展)
- 34.上下文无关文法(CFG)与句法分析
- 35.概率上下文无关文法 PCFG
- 36.统计机器翻译:词对齐与翻译模型
- 37.统计机器翻译中的对齐模型与参数估计
- 38.从统计机器翻译到神经机器翻译
- 39.神经元与反向传播速览(指向深度学习课)
- 40.CNN 原理及其在 NLP 中的应用
- 41.循环神经网络的基本原理与 Elman / Jordan 网络
- 42.LSTM:长短期记忆网络的核心思想与分步实现
- 43.Encoder-Decoder 框架
- 44.Seq2Seq 与早期注意力机制
- 45.注意力机制的背景与动机
- 46.注意力模型实现原理与匹配函数
- 47.记忆网络:组成与计算
- 48.注意力机制的应用与历史延展
- 49.Transformer 结构:Encoder-Decoder 栈与 Self-Attention
- 50.Multi-Head Attention、FFNN 与位置编码
- 51.层归一化、损失函数与 Universal Transformer 延展
- 52.预训练范式:从 ELMo 到 BERT
- 53.BERT 与掩码语言模型
- 54.BERT 下游任务微调与 GLUE 口径
- 55.GPT 与自回归语言建模
- 56.GPT-1 到 GPT-4 的演进
- 57.指令微调与 RLHF
- 58.分词器:BPE、WordPiece 与中文词表
- 59.上下文窗口与长文本处理
- 60.能力边界、成本与适用场景
- 61.文本分类实战:从朴素贝叶斯到逻辑回归
- 62.命名实体识别实战
- 63.文本生成与摘要实战
- 64.语义检索与向量库实战
- 65.RAG 知识库问答
- 66.NLP 评估指标:BLEU / ROUGE / BERTScore 与人工评估
- 67.幻觉与事实性问题
- 68.文本与知识结合(知识图谱压缩)
- 69.跨媒体信息检索与未来展望
线性链条件随机场 CRF
小象实战讲义 · 自然语言处理
在上一节,我们学习了隐马尔可夫模型(HMM),它通过建模状态序列的联合概率 p(x, y) 来解决序列标注问题。然而,HMM 的生成式模型特性限制了它对丰富输入特征(如词的前后缀、上下文窗口)的利用。本节我们将学习条件随机场(Conditional Random Fields, CRF),特别是其线性链形式。CRF 作为一种判别式模型,直接对条件概率 p(y | x) 建模,能够灵活地集成任意特征,有效克服了 HMM 和最大熵马尔可夫模型(MEMM)的局限,成为序列标注任务中长达十余年的经典强基线。
💡 核心导读
- 从 HMM 到 CRF:理解为何在 HMM 之后需要 CRF,以及 CRF 如何通过指数族形式将 HMM 泛化为一个更强大的判别式模型。
- 线性链 CRF 的定义:掌握其核心公式
p(y|x) = (1/Z(x)) exp(Σθ_k f_k),明确全局归一化与特征函数的概念。 - CRF vs. MEMM:辨析两者的关键区别在于归一化位置(全局 vs. 局部),并理解局部归一化如何导致 标注偏置(Label Bias) 问题。
- CRF 在中文分词中的应用:了解如何将分词转化为字标注问题,以及 CRF 如何通过特征函数集成字特征与转移信息。
- CRF 的现代定位:认识 CRF 作为经典结构化输出层,在现代神经序列标注模型(如 BiLSTM-CRF)中的角色。
从 HMM 到判别式序列模型
HMM 是一个生成式模型,它通过联合概率 p(x, y) = p(y) p(x|y) 来建模。其核心假设是观测独立性,即当前观测 x_t 只依赖于当前状态 y_t。这限制了模型利用更丰富的上下文信息,例如,在词性标注中,一个词的词性可能不仅依赖于该词本身,还依赖于其前后相邻的词。
最大熵马尔可夫模型(MEMM)试图解决这个问题。它是一个判别式模型,直接对 p(y_t | y_{t-1}, x) 建模,允许特征函数 f_k 依赖于整个输入序列 x。然而,MEMM 采用逐位置(局部)归一化:
p_{\text{MEMM}}(y|x) = \prod_{t=1}^{T} p(y_t | y_{t-1}, x)
其中 p(y_t | y_{t-1}, x) = \frac{1}{Z_t(y_{t-1}, x)} \exp\left(\sum_{k} \theta_k f_k(y_t, y_{t-1}, x_t)\right)。
局部归一化会导致 标注偏置(Label Bias) 问题:模型倾向于选择那些后续状态选择少(即局部归一化分母小)的转移路径,而忽略了全局最优路径。线性链 CRF 正是为了改进 MEMM 的这一缺陷而被提出的。
线性链条件随机场的定义
条件随机场(Conditional Random Field, CRF)由 Lafferty、McCallum 与 Pereira 在 ICML 2001 的论文 Conditional Random Fields: Probabilistic Models for Segmenting and Labeling Sequence Data 中提出。下面按其后续综述教程给定义(Sutton & McCallum, Definition 2.1):
设 x、y 为随机向量,\theta = \lbrace \theta_k\rbrace \in \mathbb{R}^K 为参数向量,\lbrace f_k(y, y', x_t)\rbrace_{k=1}^{K} 为一组实值特征函数,则线性链条件随机场是形如
的条件分布。其中 Z(x) 是随输入序列 x 而变的归一化函数(或称配分函数):
关键点解析:
- 特征函数
f_k(y_t, y_{t-1}, x_t):这是模型的核心。它可以是任意实值函数,用于捕捉t时刻的局部依赖关系,即当前标签y_t、前一个标签y_{t-1}和当前(及上下文)观测x_t之间的关联。x_t可以包含x_{t+1}等跨时刻信息。 - 全局归一化:分母
Z(x)是对所有可能的标签序列y求和。这与 MEMM 的逐位置归一化有本质区别,也是 CRF 能够避免标注偏置问题的根源。 - 指数族形式:整个条件概率被表示为指数族分布,权重
\theta_k决定了每个特征f_k的重要性。
CRF 与 HMM、MEMM 的关系
CRF 泛化了 HMM
HMM 的联合分布可以改写成指数族形式(Sutton & McCallum, 式 2.13–2.15): p(y,x) = \frac{1}{Z} \prod_t \exp\left\lbrace \sum_{i,j \in S} \theta_{ij} \mathbf{1}\lbrace y_t=i\rbrace\mathbf{1}\lbrace y_{t-1}=j\rbrace + \sum_{i \in S} \sum_{o \in O} \mu_{oi} \mathbf{1}\lbrace y_t=i\rbrace\mathbf{1}\lbrace x_t=o\rbrace \right\rbrace
其中 \theta_{ij} = \log p(y_t=i | y_{t-1}=j), \mu_{oi} = \log p(x_t=o | y_t=i)。
如果我们定义特征函数为指示函数(例如,f_{ij}(y_t, y_{t-1}, x_t) = \mathbf{1}\lbrace y_t=i\rbrace\mathbf{1}\lbrace y_{t-1}=j\rbrace),那么 HMM 就成了一类特殊的线性链 CRF。关键结论是:将 HMM 的联合分布条件化后得到的 p(y|x),恰好是一类只包含“当前词身份”这类简单特征的 CRF。CRF 的扩展能力在于允许使用更丰富、更灵活的特征,而不限于简单的指示函数。
CRF 改进了 MEMM
两者的核心公式对比如下:
- MEMM:
p_{\text{MEMM}}(y|x) = \prod_{t=1}^{T} p(y_t | y_{t-1}, x),其中p(y_t | y_{t-1}, x) = \frac{1}{Z_t(y_{t-1}, x)} \exp\left(\sum_{k} \theta_k f_k(y_t, y_{t-1}, x_t)\right)。 - CRF:
p_{\text{CRF}}(y|x) = \frac{1}{Z(x)} \prod_{t=1}^{T} \exp\left(\sum_{k} \theta_k f_k(y_t, y_{t-1}, x_t)\right)。
形式差异仅在于归一化的位置:MEMM 在每个位置进行局部归一化(Z_t),而 CRF 在整个序列上进行全局归一化(Z(x))。正是这个差异让 CRF 避开了 MEMM 的标注偏置:整条标签序列在同一个配分函数下竞争,某个位置的局部高分不再能”锁死”后续路径。但要说清楚一点:一阶线性链 CRF 的局部因子仍是 f_k(y_t, y_{t-1}, x_t),它直接建模的标签依赖只到相邻两个位置。“长期依赖”的来源是更丰富的观测特征(特征可以看整句 x)或高阶因子结构,不是全局归一化本身带来的;把两者混为一谈,会让人误以为换成 CRF 就自动获得了长程标签建模能力。教程明确指出:“线性链 CRF 最初正是作为对 MEMM 的改进被提出的”。
CRF 的参数估计与推断
参数估计(训练)
给定训练集 D = \lbrace (x^{(i)}, y^{(i)})\rbrace_{i=1}^{N},CRF 通过最大化条件对数似然来估计参数 \theta(Sutton & McCallum, 式 4.1/4.2):
代入 CRF 定义式展开:
由于参数规模通常很大(几十万量级),为防止过拟合,常加入 L2 正则化项(式 4.4): 其中 1/\sigma^2 控制正则化强度。
最大化 \ell(\theta) 是一个无约束凸优化问题,通常使用梯度上升法(如 L-BFGS)求解。计算梯度时需要计算特征函数关于模型分布 p(y|x^{(i)}; \theta) 的期望,这涉及到推断。
推断:计算 Z(x) 与解码
- 计算配分函数 Z(x):虽然
Z(x)需要对所有可能的y序列求和(项数随序列长度指数增长),但得益于线性链结构,我们可以使用前向-后向算法高效计算,其复杂度为O(T * |S|^2),其中|S|是标签集大小。 - 解码(寻找最优 y):给定输入
x,寻找最可能的标签序列y^* = \arg\max_y p(y|x)。这同样可以使用维特比(Viterbi)算法高效求解,其思想与 HMM 中的 Viterbi 算法一致,只是将 HMM 的转移和发射概率替换为 CRF 的特征分数加权和。
CRF 在中文分词中的应用实例
中文分词是 CRF 成功应用的经典领域。根据黄昌宁《中文分词十年回顾》中的阐述,中文分词被转化为字标注问题:为句子中的每个字分配一个标签,表示它在词语中的位置(如 B、M、E、S,分别表示词首、词中、词尾和单字词)。
句子: 我 们 热 爱 学 习
标签: S S B E B E
分词: 我 | 们 | 热爱 | 学习CRF 在该任务中的优势在于:
- 特征灵活:特征函数可以方便地集成字本身、前后缀、词典匹配等多种信息。例如:
f1(y_t=B, y_{t-1}=S, x_t="学") = 1(当前字是“学”,前一个标签是 S,当前标签是 B)f2(y_t=E, y_{t-1}=B, x_t="习", x_{t-1}="学") = 1(当前字是“习”,前字是“学”,构成“B-E”转移)
- 自动集成转移:与需要单独设计转移特征的 SVM 或最大熵模型不同,一阶线性链 CRF 通过特征
f_k(y_t, y_{t-1}, x_t)自然地将状态转移过程集成到模型中。 - 实战战绩:原文报告,在 2006 年的国际中文分词评测(Bakeoff)中,基于字的 CRF 分词系统在六项评测中取得了四个第一、两个第三的优异成绩。实验表明,以当前字为中心,前后各取两字(即 5 字窗口)的特征窗口设置效果较为理想。
下面我们用一个极简的示例,演示如何为这样一个分词任务手工定义特征函数并计算得分(非完整训练,仅为说明原理)。
# 示例:手工计算一个微型CRF在特定序列上的未归一化得分
import itertools
# 定义标签集和微型"句子"
labels = ['B', 'M', 'E', 'S']
sentence = ['x1', 'x2', 'x3'] # 假设的3个观测
true_tags = ['B', 'E', 'S'] # 假设的真实标签序列
# 手工定义几个特征函数及其权重(假设已学习得到)
def feature1(yt, yt_1, xt):
# 特征1:如果当前标签是B且前一个标签是S,则激活
return 1 if (yt == 'B' and yt_1 == 'S') else 0
weight1 = 0.8
def feature2(yt, yt_1, xt):
# 特征2:如果当前标签是E且前一个标签是B,则激活
return 1 if (yt == 'E' and yt_1 == 'B') else 0
weight2 = 1.2
def feature3(yt, yt_1, xt):
# 特征3:如果当前观测是'x2'且标签是E,则激活(模拟字特征)
return 1 if (xt == 'x2' and yt == 'E') else 0
weight3 = 0.5
features = [(feature1, weight1), (feature2, weight2), (feature3, weight3)]
def compute_score(tag_sequence, obs_sequence):
"""计算给定标签序列和观测序列的未归一化得分 exp(Σθ·f)"""
T = len(tag_sequence)
total_score = 0.0
for t in range(T):
yt = tag_sequence[t]
yt_1 = tag_sequence[t-1] if t > 0 else '<START>' # 序列开始
xt = obs_sequence[t]
for feat_func, weight in features:
total_score += weight * feat_func(yt, yt_1, xt)
return total_score
# 计算真实序列的得分
true_score = compute_score(true_tags, sentence)
print(f"真实标签序列 {true_tags} 的未归一化对数得分: {true_score:.4f}")
# 枚举所有可能的标签序列,观察得分分布(仅用于理解Z(x))
all_possible_sequences = list(itertools.product(labels, repeat=len(sentence)))
print(f"\n所有 {len(all_possible_sequences)} 种可能序列的得分:")
for seq in all_possible_sequences:
s = compute_score(seq, sentence)
print(f" {list(seq)}: {s:.4f}")真实标签序列 ['B', 'E', 'S'] 的未归一化对数得分: 1.7000
所有 64 种可能序列的得分:
['B', 'B', 'B']: 0.0000
['B', 'B', 'M']: 0.0000
['B', 'B', 'E']: 1.2000
['B', 'B', 'S']: 0.0000
['B', 'M', 'B']: 0.0000
['B', 'M', 'M']: 0.0000
['B', 'M', 'E']: 0.0000
['B', 'M', 'S']: 0.0000
['B', 'E', 'B']: 1.7000
['B', 'E', 'M']: 1.7000
['B', 'E', 'E']: 1.7000
['B', 'E', 'S']: 1.7000
['B', 'S', 'B']: 0.8000
['B', 'S', 'M']: 0.0000
['B', 'S', 'E']: 0.0000
['B', 'S', 'S']: 0.0000
['M', 'B', 'B']: 0.0000
['M', 'B', 'M']: 0.0000
['M', 'B', 'E']: 1.2000
['M', 'B', 'S']: 0.0000
['M', 'M', 'B']: 0.0000
['M', 'M', 'M']: 0.0000
['M', 'M', 'E']: 0.0000
['M', 'M', 'S']: 0.0000
['M', 'E', 'B']: 0.5000
['M', 'E', 'M']: 0.5000
['M', 'E', 'E']: 0.5000
['M', 'E', 'S']: 0.5000
['M', 'S', 'B']: 0.8000
['M', 'S', 'M']: 0.0000
['M', 'S', 'E']: 0.0000
['M', 'S', 'S']: 0.0000
['E', 'B', 'B']: 0.0000
['E', 'B', 'M']: 0.0000
['E', 'B', 'E']: 1.2000
['E', 'B', 'S']: 0.0000
['E', 'M', 'B']: 0.0000
['E', 'M', 'M']: 0.0000
['E', 'M', 'E']: 0.0000
['E', 'M', 'S']: 0.0000
['E', 'E', 'B']: 0.5000
['E', 'E', 'M']: 0.5000
['E', 'E', 'E']: 0.5000
['E', 'E', 'S']: 0.5000
['E', 'S', 'B']: 0.8000
['E', 'S', 'M']: 0.0000
['E', 'S', 'E']: 0.0000
['E', 'S', 'S']: 0.0000
['S', 'B', 'B']: 0.8000
['S', 'B', 'M']: 0.8000
['S', 'B', 'E']: 2.0000
['S', 'B', 'S']: 0.8000
['S', 'M', 'B']: 0.0000
['S', 'M', 'M']: 0.0000
['S', 'M', 'E']: 0.0000
['S', 'M', 'S']: 0.0000
['S', 'E', 'B']: 0.5000
['S', 'E', 'M']: 0.5000
['S', 'E', 'E']: 0.5000
['S', 'E', 'S']: 0.5000
['S', 'S', 'B']: 0.8000
['S', 'S', 'M']: 0.0000
['S', 'S', 'E']: 0.0000
['S', 'S', 'S']: 0.0000从输出可以看到,不同的标签序列会得到不同的得分。Z(x) 就是所有这些序列的 exp(score) 之和。p(y|x) 正比于 exp(score(y, x))。在实际的 CRF 实现中,我们使用前向-后向算法高效计算 Z(x) 和所有边缘概率,而不需要枚举。
CRF 的现代定位与演进
尽管深度学习已成为主流,CRF 并未退出历史舞台,而是找到了新的定位:
- 强特征基线:在数据量较小、特征工程可解释性要求高的场景(如某些特定领域的命名实体识别),基于手工特征的 CRF 依然是一个稳健、高效的强基线。
- 神经 + 结构化输出层(可选组合):现代序列标注的主流做法是端到端神经模型——编码器接逐位置分类或指针式解码,把特征工程交给表示学习;CRF 层则是可以挂在编码器之后的可选输出层,用于注入标签间的转移约束。它并非现代架构的标配。以 BiLSTM-CRF 为例:先用双向 LSTM 编码整个句子,得到每个位置的上下文相关向量表示,再把这些向量作为 CRF 的“观测”输入,由 CRF 层来建模标签间的转移约束。这样既利用了神经网络的表示学习能力,又在需要时保留了 CRF 的结构化预测能力。
- 端到端神经模型的补充:纯端到端的神经序列标注(如直接用 Softmax 逐位置分类)虽然简单,但忽略了标签间的依赖关系。CRF 层提供了一种轻量级的方式来注入这种先验知识。
对于需要显式标签转移约束的场景(如实体边界必须合法、标签序列有强约束),工程师仍可在 Transformer 编码器(如 BERT)后接一个 CRF 层;但在没有这类约束需求时,端到端逐位置分类往往是更省事的默认选择。CRF 的核心思想——全局归一化的对数线性模型——作为处理输出结构依赖的有效工具,依然活跃在研究和工程的第一线。
📝 动手练一练
- 概念辨析:MEMM 和线性链 CRF 在公式上非常相似。请指出导致它们本质不同的关键一点,并解释这种不同会带来什么影响。
👉 点击查看参考答案
关键不同在于归一化的位置:
- MEMM 进行的是局部归一化(per-position normalization),即每个时间步
t的条件概率p(y_t | y_{t-1}, x)独立归一化(分母是Z_t(y_{t-1}, x))。 - 线性链 CRF 进行的是全局归一化(global normalization),即整个序列的条件概率
p(y | x)由一个归一化常数Z(x)统一处理。
带来的影响: 局部归一化会导致 标注偏置(Label Bias) 问题。模型在每一步做出的转移决策,其“概率质量”被限制在必须分配给当前步的所有可能后续标签上。这会导致模型倾向于选择那些“出口”少(即后续可能标签少)的状态转移,因为概率更容易集中,而可能忽略了全局更优但每一步概率不那么集中的路径。全局归一化的 CRF 在整体序列层面进行比较和归一化,因此可以避免这个问题,做出更全局一致的预测。
- 手算理解:假设一个极简的 CRF 用于二字词标注,标签集为
{B, E},观测序列为["研", "究"]。只定义两个特征函数及其权重:f1: 如果 (y_t=B, x_t="研") 则返回1,否则0,权重θ1 = 1.0f2: 如果 (y_{t-1}=B, y_t=E) 则返回1,否则0,权重θ2 = 2.0请计算标签序列["B", "E"]和["E", "B"]的未归一化得分(即Σθ·f)。根据得分,你认为哪个序列更可能?
👉 点击查看参考答案
对于序列 y1 = ["B", "E"]:
- t=1:
y1=B,y0=<START>,x1="研"。f1(B, <START>, "研") = 1,贡献1.0 * 1 = 1.0f2(B, <START>, "研") = 0,贡献2.0 * 0 = 0.0
- t=2:
y2=E,y1=B,x2="究"。f1(E, B, "究") = 0f2(E, B, "究") = 1,贡献2.0 * 1 = 2.0总得分score1 = 1.0 + 2.0 = 3.0
对于序列 y2 = ["E", "B"]:
- t=1:
y1=E,y0=<START>,x1="研"。f1(E, <START>, "研") = 0f2(E, <START>, "研") = 0
- t=2:
y2=B,y1=E,x2="究"。f1(B, E, "究") = 0(因为x2是“究”不是“研”)f2(B, E, "究") = 0总得分score2 = 0.0
score1 (3.0) > score2 (0.0),因此序列 ["B", "E"] 的未归一化得分远高于 ["E", "B"]。在 CRF 模型中,p(y|x) 正比于 exp(score),所以 ["B", "E"] 的可能性远大于 ["E", "B"]。这符合我们对“研究”构成一个词的直觉。
本章小结
本节我们深入探讨了线性链条件随机场(CRF)。我们从 HMM 和 MEMM 的局限性出发,理解了 CRF 作为判别式、全局归一化模型的优势。核心在于掌握其定义式 p(y|x) = (1/Z(x)) exp(Σθ_k f_k),以及特征函数和全局归一化两个关键概念。我们辨析了 CRF 与 HMM、MEMM 的关系,并通过中文分词实例了解了其应用。最后,我们看到了 CRF 在现代神经序列标注模型中的延续价值。
📋 行动清单
- 理解并能够复述线性链 CRF 的定义公式,明确
Z(x)的含义。 - 能够清晰解释 CRF 与 MEMM 在归一化方式上的区别,以及由此产生的标注偏置问题。
- 了解如何将中文分词等任务形式化为字标注问题,并知道 CRF 在其中可以使用的特征类型。
- 认识到 CRF 可以作为现代神经网络(如 BiLSTM、Transformer)的一个有效的结构化输出层。
—— 小象教研组