NLP 系统
AI 学习笔记系列的第二篇,介绍 NLP 系统的核心组件:数据、模型、推断方法、指标、学习方法,以及常见的模型和推断方法。
NLP 系统核心组件
NLP 系统有五个关键组件:
- 数据(Data):我们希望系统处理的语言现象样例。
- 模型(Model):把
(input, output)对映射为分数的函数。 - 推断方法(Inference Method):在给定模型时,为样本做预测的方法。
- 指标(Metric):对给定模型产生的输出进行打分的函数。
- 学习方法(Learning Method):在给定数据与推断方法时更新模型的方法。
备注:这里的模型是另一个层级的抽象,和之前提到的模型定义不冲突。
我们可以用这些组件来描述不同类型的 NLP 系统:
- 规则方法(Rule-Based):
- Data:非严格必需,但没有样例很难写规则。
- Model:一组规则。
- Inference Method:执行规则的代码。
- Metric:预测与真值一致记 0,否则记 1。
- Learning Method:人工改规则,或用规则发现算法。
- 线性模型(Linear):
- Data:带真标签的样例集。
- Model:特征与权重。
- Inference Method:多种方法。
- Metric:多种方法。
- Learning Method:多种方法。
- 神经网络(Neural Networks):
- Data:带真标签的样例集。
- Model & Inference Method:神经网络的参数与结构。
- Metric:多种方法。
- Learning Method:多种方法。
数据(Data)
数据即输入和输出的配对:(input, output)。
输入可以是不同长度文本,输出可以是标签集合(句子,情感)、结构化输出(转录文本,reply-to 链接)、自由文本(文档,摘要)。
以分类问题为例(垃圾邮件检测;作者归属;法律响应性;情感分析;人口属性预测),输入是文档,输出是标签。
监督学习(Supervised Learning)指使用有标签数据进行训练。 无监督学习(Unsupervised Learning)指使用无标签数据进行训练。
模型(Model)
- 代码视角:
def model(text, label): ... - 数学视角:$\text{score}_1 = a_1x + b_1x + k_1, …$
为了更好地介绍模型,我们先引入 BoW 的概念: 词袋(BoW,Bag of Words)是一种简单的文本表示方法,它把文本看作一个词的集合,而不考虑词序。 BoW 不处理词义,未知词会在词袋中会被忽略。
在 BoW 模型中,我们可以把每个词看作一个特征,模型就是一组权重,告诉我们每个词对某个标签的贡献。 我们将计数结果与权重结合起来,得出最终得分。 我们通常会加入一个偏向权重,以偏向某个标签。
备注:如何让 BoW 考虑词义(Word Senses)?可以先做词义消歧,再把词义信息放进词袋。例如放入 bat (the animal) 而不是 bat。
推断方法(Inference Method)
常见的推断方法有:
- 贪心搜索(Greedy Search)
- A* 搜索(A* Search)
- 动态规划(Dynamic Programming)
- 二分类可看符号(Sign)
- 多类别可用多组权重
- 多类情况下可全部比较
- 类别数小时可穷举测试
也可把上面的写成向量形式,即结合 Model 和 Inference Method,直接把输入向量化后乘以权重向量得到分数。
指标(Metric)
常见的指标有:
- 0-1 损失(0-1 Loss)
- Hinge 损失(Hinge Loss)
- 交叉熵损失(Cross-Entropy Loss)
- 平方误差(Squared Error)
具体的公式这里不展开,但它们的核心思想是衡量模型预测与真值之间的差距,数值越大说明预测越差,指标与学习方法密切相关。
学习方法(Learning Method)
学习方法主要是思考下面两个问题:
- 每次用多少样本(1 个、几个、全部)?
- $a$、$b$ 等参数每次改多少?
在实际中,如何选这些权重? 可以精确定义概率模型并估计,也可以随机初始化并迭代改进。
模型、推断方法、学习方法的区别
先假设给定输入,我们要产出最优输出。
- 模型(Model):告诉你某个输出对该输入有多好。坏选项给低分,好选项给高分。可看作
(input, output) -> score的函数。 - 推断(Inference):如何找到在模型下得分最高的输出。模型只会给候选打分,因此需要一个过程决定要把哪些
(input, output)送给模型评估。 - 学习(Learning):可选模型很多,如何得到好的?学习就是用于改进模型的过程。
- 模型给
(input, output)打分,推断方法利用模型找到高分选项。
常见模型
这一块的细节都属于机器学习(Machine Learning)的范畴,仅做简要介绍。
神经网络(Neural Networks)
神经网络可以理解为在线性模型上叠加非线性变换。 常见非线性函数有 Sigmoid、tanh、ReLU。
最基础的结构是前馈神经网络(FFN,Feed-Forward Neural Network),也叫多层感知机(MLP)。 它通过多层变换把输入映射到输出分数,再结合损失函数训练参数。
训练时,通常配合随机梯度下降(SGD,Stochastic Gradient Descent)及其变体。 核心流程是:前向计算得到预测,反向传播计算梯度,再按梯度方向更新参数。
反向传播(Backpropagation)本质上是一个高效求导算法。 它会计算损失对每个参数的偏导数,从而告诉我们每个参数该怎么改。
为了抑制过拟合,常见正则化(Regularization)方法有:
- 在损失中增加惩罚项(如 L1、L2)。
- Dropout:训练时随机屏蔽一部分神经元。
- Early Stopping:验证集性能不再提升时提前停止训练。
神经网络中**环(Cycle)**指计算图里存在回路。 在一般前馈计算图里我们要求无环,这样前向和反向都能按拓扑顺序稳定计算。反之梯度计算会不清晰,向前预测定义也会很复杂。
循环神经网络(RNN)
处理变长输入时,一个朴素做法是“查词向量后求均值”,但这会弱化词序信息。 另一个做法是拼接后截断 / 填充,但长度上限固定,表达能力受限。
循环神经网络(Recurrent Neural Network,RNN)通过递归状态处理序列。 每读入一个 token,就更新一次隐藏状态,最终形成随上下文变化的表示。
RNN 常见有两类用法:
- 转换器(Transducer):每个输入位置都输出结果(如序列标注)。
- 接收器 / 编码器(Acceptor / Encoder):将整段输入压缩为一个表示(如句向量)。
RNN 训练时常见两个问题:
- 梯度爆炸(Exploding Gradients):梯度过大导致训练不稳定,通常用梯度裁剪缓解。
- 梯度消失(Vanishing Gradients):长距离依赖难学习,模型更偏向局部信息。
梯度消失与反复非线性变换和长链路传播相关。 直观上,早期信息经过多步传递后影响会被不断缩小。
普通 RNN 在长序列上常见的瓶颈是梯度消失:训练时模型需要把后面预测错了的信号传回到很早的位置,但这个信号在一层层时间步的非线性变换中会被不断压缩,传到句首时几乎没剩多少,于是模型很难学会利用远距离信息。 结果就是,如果关键信息出现在句首而模型最终判断错了,句首那个词对最终决策的影响在训练更新里会变得很小,学习被拖慢,模型更倾向只抓住局部上下文。
长短期记忆网络(LSTM)
长短期记忆网络(Long Short-Term Memory,LSTM)通过门控机制和 cell state 缓解这个问题。 直观上可以把它理解为:RNN 的 hidden state 每一步都会被强非线性重新加工,信息容易在反复加工中衰减。 而 LSTM 额外维护了一条更直通的 cell state 通道,让重要信息可以在更少被压缩的情况下跨时间步传递,并且通过门控决定哪些信息该写入、保留或遗忘。 因此相比普通 RNN,LSTM 更容易保留远距离依赖,在长句或长序列任务上通常更稳,这也是它在早期序列任务中长期占优的原因之一。
为了理解这类模型在序列中到底记住了什么,常见做法是做隐藏状态的输入 / 输出可视化:选一个具体样例(例如字符级模型做“给定前缀预测下一个字符”),然后沿时间步观察 hidden state 或 cell state 的某些维度数值如何变化。 有时你会发现某个维度在接近行尾时明显变化,像是在表示“快结束了”;有时某个维度会在模型更有把握时变大,像是“信心信号”。
备注:这些现象能提供直觉,但不要把单个维度过度解释成唯一语义,它更多是帮助我们观察模型是否在用某类信息。
一个模型也可以同时完成多个任务,例如:
- 可以在多个相关任务上训练(如 POS 和 NER,见后文)。
- 可以把一个任务输出作为另一个任务输入。
- 可以使用双向处理输入。
- 可以可视化隐藏状态中的值。
词性标注(POS)与命名实体识别(NER)
词性标注(Part-of-Speech Tagging,POS)关注词在句中的语法角色,如名词、动词、形容词等。 它常用于语法分析、词义区分、文本规范化等任务。
POS 的词类由以下定义:
- 形态学上下文(Morphological Context):可与相似词缀(前后缀)组合,例如
-ing、-ed。 - 句法分布(Syntactic Distribution):可被另一词替换且仍语法正确,例如
dog和cat都可以是主语。 - 词性标注:可帮助区分词义和指示发音,例如
record作为名词和动词的发音不同。
命名实体识别(Named Entity Recognition,NER)识别人名、地名、机构名等实体。 它在信息抽取、问答、检索增强系统中非常重要。
序列标注中常见 BIOES 标注方案:
B(Begin):实体开始。I(Inside):实体内部。E(End):实体结束。S(Single):单 token 实体。O(Outside):非实体。
例如:B-PER 表示“人物实体开始”,I-LOC 表示“地点实体内部”。
回到之前的问题,如何训练多个相关任务? 有几种方式做多预测:
- 构建多个完全独立系统。
- 修改模型使其有多个输出头,例如取 hidden state 后做两套计算。
- 修改输出空间。例如联合 POS 与 NER 时,不分别预测(Noun/Verb…)和(B-PER/I-PER/O/B-LOC…),而是预测组合标签(Noun-B-PER、Noun-I-PER、Noun-O、Noun-B-LOC…)。
训练过程与单一输出的情况类似。对方式 2,用真标签算损失并反向传播。 对方式 3,先根据数据确定正确组合标签,再与预测比较并反向传播。
常见推断方法
模型可以给整段序列打分:把整个输出当作一个整体来评价好坏。 这种时候,推断就可能需要 beam / 动态规划(Viterbi / CKY)/ 搜索来找到全局最高分的序列。
模型也可以给局部打分:模型只在局部做决定,每一步给一个分数,整段的分数通常是这些局部分数的组合(常见是求和)。 这种情况下贪心往往就够用(每步选最高分)。
穷举搜索(Exhaustive Search)
穷举搜索会枚举所有候选并选最高分。 优点是“理论上不漏最优解”,缺点是搜索空间通常指数级增长,几乎不可扩展。
对于序列标注,候选规模约为 |tags|^{|words|},长度稍长就不可用。
贪心搜索(Greedy Search)
贪心搜索每一步只做当前最优选择。 它速度快、实现简单,适合对时延敏感的场景。
常见变体:
- Top-1:每步取最大概率(argmax)。
- 随机采样:按完整分布采样。
- Top-K 采样:仅在最高 $K$ 个候选中采样。
- Top-P(Nucleus)采样:保留累计概率达到阈值 $P$ 的最小候选集并采样。
Top-K 与 Top-P 的共同点是截断低概率候选后再采样。 差异在于截断规则:一个按数量固定($K$),一个按累计概率固定($P$)。
束搜索(Beam Search)
Beam Search 可以理解为保留多个部分解的贪心算法。 每一步不只保留 1 条路径,而是保留分数最高的 $k$ 条候选(Beam Size)。
- $k = 1$ 时退化为贪心。
- $k$ 越大,搜索更充分,但计算和显存成本更高。
最佳 $k$ 依应用和模型而变,需要实验确定。
实现上我们每步维护一个可扩展候选列表。 按评分方法不同,有时可提前停止(若能确定剩余候选不可能更优)。 候选通常会在分数被新候选超过时移出 beam。 当某候选当前位于 beam 底部(分最低),且有新候选分更高时,该候选被移除。
对变长输出,beam 定义不止一种:可按输出长度定义 beam,也可按步数定义。
通常 $k$ 很小,且候选评分本身常是主要开销,选择 $k$ 的常见做法如下:
- 简单法,
O(nk):每个新候选在线性列表中找插入位置,插入并更新。 - 堆法,
O(n \log k):用最小堆,逐项更新。 - 快速选择法,
O(n):先记录全部候选,用 快速选择算法(Quickselect)找第 $k$ 名,再取其余 $k-1$。
备注:什么时候应优先 beam 而不是 greedy,主要取决于模型的打分方式。如果模型每一步的分数基本就决定了后续(某个候选现在分低,后面也很难翻盘),那用 greedy 就够了,因为提前多留候选也没意义。如果模型可能出现先走一步看起来不太好,但继续走下去整体更好的情况(总分需要把多步合在一起看),beam 才可能保留这些早期不占优但最终更优的路线。但通常要实验验证,因为它确实增加计算成本。
图搜索(Graph Search)
当问题可以表示为图结构时,可把解码视作在图上找最优路径 / 结构(利用图论方法)。 可以把核心思想概括为:估计最终分数,并据此引导生成。 A* 等方法都可归入这一类。
如何估计最终分数? 一般由另一个模型给分:输入为当前路径和未处理输入信息,输出为分数。
序列标注(Sequence Tagging)与 Viterbi
序列标注任务(如 POS / NER)通常有固定标签集、已知输入长度。 该类任务的挑战在于:
- 独立预测可能互相不一致。
- 贪心预测可能在过晚时刻才暴露错误。
- 逐个枚举所有标签序列不可行。
Viterbi 算法用动态规划寻找全局最高分标签序列。 其核心思想是:到当前位置、以某标签结尾的最佳路径分数,可以由前一位置各标签的最佳分数递推得到。
对输入序列中的每个位置:
对每个可能的标签:
最佳分数 = 负无穷
最佳前驱标签 = None
对每个可能的前一标签:
分数 = 前一标签的概率 x 在给定输入和前一标签条件下当前标签的概率
如果该分数优于当前最佳分数:
更新最佳分数与最佳前驱标签
为该(位置,标签)组合保存最佳分数与最佳前驱标签
路径 = []
当前标签 = End(结束标签)
从序列末尾位置遍历到起始位置:
当前标签 = 在该位置、以当前标签结尾时记录的最佳前驱标签
将当前标签加入路径
将路径反转
时间复杂度通常是 O(|words| * |labels|^2)。
如果把转移依赖扩展到更多历史标签,复杂度会进一步升高。
备注:Viterbi 不是 RNN 专属算法,只要模型能给出局部转移 / 发射分数即可使用,比如线性模型,前馈神经网络,RNN,Transformer 等。
图解析(Graph Parsing)
句法解析可以用来消解歧义。 一个常见表示是依存句法(Dependency Parsing):句子里的关系用弧 / 边 / 依存关系(Arcs / Edges / Dependencies)表示,弧从中心词(Head)指向依存词(Dependent)。 在这种设定下,我们通常会有一个弧模型来为候选弧或候选结构打分,最终目标是在所有合法结构中找到得分最高的那一个。
以 I eat apples.(我吃苹果。)为例,依存关系可以写成两条弧(从 head 指向 dependent):
eat -> I(I 是动词 eat 的主语)eat -> apples(Apples 是动词 eat 的宾语)
这里 eat 是中心词(Head),I 和 apples 是依存词(Dependent),每一条 head -> dependent 就是一条弧 / 边 / 依存关系。
在依存解析里,推断方法通常要满足一些结构约束:
- 输出必须是一棵树
- 无交叉弧
- 单根
- 在所有可能树中选择得分最高的那棵
这类推断算法通常由两样东西定义:
- 项目(Items):用来表示一个子问题状态,保留关键信息
- 规则(Rules):用来说明如何把更小的 items 组合成更大的 items,或者如何在 items 上添加弧
CKY / CYK 算法用动态规划把整句解析拆成一系列更小的子问题,并在子问题上保存最优解及其来源,最后通过回溯恢复整棵树。
为每个跨度(Span)和项目类型(Item Type)创建存储结构,用来保存当前最优选项
插入初始项目(Initial Items)
对每一种可能的跨度长度:
对每一种可能的跨度起点:
对每一种可能的项目类型:
Best = None(当前最优解)
对每一个可能的切分点(Split Point):
对每一条规则(Rule):
计算该组合的分数,并用它更新 Best
记录该(Span,Item Type)的最优结果 Best
计算向空项目(Empty Items)添加一条弧(Arc)的分数,并把结果记录下来
CKY / CYK 算法的时间复杂度是 O(|rules_{comb}||words|^3 + |rules_{arc}||words|^2)。
备注:无交叉弧在这里不仅是语言学上的常见约束,也和推断算法能否工作直接相关:上面的 CKY 方法无法产生交叉弧。原因是一旦创建某条弧,对应的 item 就不再拆分,因此无法再从该 item 的中部连向外部去产生交叉结构。
CKY 的目标是找到模型下得分最高的句子解析,它之所以能做到这一点,是因为它是一种动态规划方法,依赖两个关键条件:
- 把大问题拆成小问题
- 用小问题的解组合出大问题的解
大问题指的是这句话的最高分解析是什么,小问题指的是对每个句子片段(Span),最高分解析是什么。 求小问题时会继续拆分,直到最小可处理片段(例如相邻两词),此时分数容易计算。 随后再把这些局部最优解按规则组合起来,逐步构造更长 span 的最优解。
这一步里 items 和 rules 的作用很关键:
- items:保留该片段解的关键属性,同时抽象掉尽量多细节(让状态空间可控)
- rules:负责组合 items,也负责确保最终结构满足合法树等约束
即使在 LLM 很强的今天,句法解析依然有实际价值,原因有这些:
- 有时你需要的就是解析结果本身,例如运行解析器并检查某条依存关系。
- 解析也可以作为系统组件使用,例如根据解析结果限制喂给 LLM 的文本片段,避免在一条依存弧中间截断,从而减少语义被拆碎。
- LLM 在某些结构化语义任务上仍相对弱。
推断方法选择
在穷举、贪心、beam、图搜索这几类推断方式里,现实中最常用哪一种强依赖任务与目标。
- 对生成任务(摘要、聊天)来说,带采样的贪心常见,因为它在效率与效果之间平衡得好,而且这些任务往往没有唯一正确答案,适度多样性反而有益。
- 对标注任务(如 NER)来说更看场景。带采样的贪心仍常见,但 beam 和动态规划(例如 Viterbi / CKY 这类思路)也经常使用。
