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):可被另一词替换且仍语法正确,例如 dogcat 都可以是主语。
  • 词性标注:可帮助区分词义和指示发音,例如 record 作为名词和动词的发音不同。

命名实体识别(Named Entity Recognition,NER)识别人名、地名、机构名等实体。 它在信息抽取、问答、检索增强系统中非常重要。

序列标注中常见 BIOES 标注方案:

  • B(Begin):实体开始。
  • I(Inside):实体内部。
  • E(End):实体结束。
  • S(Single):单 token 实体。
  • O(Outside):非实体。

例如:B-PER 表示“人物实体开始”,I-LOC 表示“地点实体内部”。

回到之前的问题,如何训练多个相关任务? 有几种方式做多预测:

  1. 构建多个完全独立系统。
  2. 修改模型使其有多个输出头,例如取 hidden state 后做两套计算。
  3. 修改输出空间。例如联合 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)/ 搜索来找到全局最高分的序列。

模型也可以给局部打分:模型只在局部做决定,每一步给一个分数,整段的分数通常是这些局部分数的组合(常见是求和)。 这种情况下贪心往往就够用(每步选最高分)。

穷举搜索会枚举所有候选并选最高分。 优点是“理论上不漏最优解”,缺点是搜索空间通常指数级增长,几乎不可扩展。

对于序列标注,候选规模约为 |tags|^{|words|},长度稍长就不可用。

贪心搜索每一步只做当前最优选择。 它速度快、实现简单,适合对时延敏感的场景。

常见变体:

  • Top-1:每步取最大概率(argmax)。
  • 随机采样:按完整分布采样。
  • Top-K 采样:仅在最高 $K$ 个候选中采样。
  • Top-P(Nucleus)采样:保留累计概率达到阈值 $P$ 的最小候选集并采样。

Top-K 与 Top-P 的共同点是截断低概率候选后再采样。 差异在于截断规则:一个按数量固定($K$),一个按累计概率固定($P$)。

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 才可能保留这些早期不占优但最终更优的路线。但通常要实验验证,因为它确实增加计算成本。

当问题可以表示为图结构时,可把解码视作在图上找最优路径 / 结构(利用图论方法)。 可以把核心思想概括为:估计最终分数,并据此引导生成。 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),Iapples 是依存词(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 的目标是找到模型下得分最高的句子解析,它之所以能做到这一点,是因为它是一种动态规划方法,依赖两个关键条件:

  1. 把大问题拆成小问题
  2. 用小问题的解组合出大问题的解

大问题指的是这句话的最高分解析是什么,小问题指的是对每个句子片段(Span),最高分解析是什么。 求小问题时会继续拆分,直到最小可处理片段(例如相邻两词),此时分数容易计算。 随后再把这些局部最优解按规则组合起来,逐步构造更长 span 的最优解。

这一步里 items 和 rules 的作用很关键:

  • items:保留该片段解的关键属性,同时抽象掉尽量多细节(让状态空间可控)
  • rules:负责组合 items,也负责确保最终结构满足合法树等约束

即使在 LLM 很强的今天,句法解析依然有实际价值,原因有这些:

  • 有时你需要的就是解析结果本身,例如运行解析器并检查某条依存关系。
  • 解析也可以作为系统组件使用,例如根据解析结果限制喂给 LLM 的文本片段,避免在一条依存弧中间截断,从而减少语义被拆碎。
  • LLM 在某些结构化语义任务上仍相对弱。

推断方法选择

在穷举、贪心、beam、图搜索这几类推断方式里,现实中最常用哪一种强依赖任务与目标。

  • 对生成任务(摘要、聊天)来说,带采样的贪心常见,因为它在效率与效果之间平衡得好,而且这些任务往往没有唯一正确答案,适度多样性反而有益。
  • 对标注任务(如 NER)来说更看场景。带采样的贪心仍常见,但 beam 和动态规划(例如 Viterbi / CKY 这类思路)也经常使用。