NLP 基础概念
AI 学习笔记系列的第一篇,介绍 NLP 的基础概念,包括文本表示、TF-IDF、Word2vec、评估指标等内容。
自然语言处理(Natural Language Processing,NLP)
自然语言处理(Natural Language Processing,NLP)是人工智能的一个重要分支,致力于让计算机能够理解、分析和生成自然语言文本。
文本的表示(Text Representation)
要让计算机理解文本,首先要把文本转换成计算机能够处理的形式(向量,即一组数字),这就是文本的表示(Representation)。
我们把处理文本的基本单位叫做词元(Token),它可以是一个词、一个子词或者一个字符。 **词表(Vocab)**是所有词元的集合。 我们把语料切成一条条样本来处理,每条样本就是一个文档(Document),它可以是一段话、一篇文章或者一个对话。 每个文档都可以表示为一个词元的计数集合,这些词元来自于词表。
我们先来看最基本的三种文本表示方法:
- 向量(One-hot):把文本转换成一个固定长度的向量。每一个 token 对应向量中的一个位置,如果文本中包含这个词元,那么这个位置的值为
1,否则为0。 - 稀疏向量(Sparse Vector):把文本转换成一个高维的稀疏向量。
- 映射(Map):把文本转换成一个键值对的映射。
性能权衡
这三种方法各有利弊,在性能上的权衡取决于它们的实现细节。
下面用 V 表示词表大小(全局的 token 种类数),U 表示某一篇文档里出现过的不同 token 种类数,D 表示典型文档长度(这个文档里 token 的总数,算重复)。
- 空间开销:
- One-hot 最大,为
O(V),实际中几乎没人直接使用这类向量,只在一些特定情况使用,例如生成后传给 GPU 做并行处理。 - Sparse vector 为
O(U)。 - Map 则取决于实现细节,通常也是
O(U),但如果实现得不好(例如使用了一个不必要的大数组来存储键值对)可能会达到O(V)。
- One-hot 最大,为
- 相似度计算时间(比较两个文档):
- One-hot 最慢,为
O(V),但如果能进行大规模并行向量计算,这个问题会减轻。 - Sparse vector 在低效实现(即比较两个向量全部元素)下是
O(D^2),若创建时保持有序(一次性代价)可降到O(D)。 - Map 是
O(U),因为只需要遍历一个 map 的元素时查找另一个 map。
- One-hot 最慢,为
通常来说:
- 对于大量短文档,sparse vector 可能因空间优势比 map 更合适。
- One-Hot 相较于 sparse vector,修改某个值更快,检查某个值更快。
- Map 相较于 sparse vector,计算相似度更快,内存占用更低。
TF-IDF
TF-IDF(Term Frequency-Inverse Document Frequency)是一种常用的统计方法,它结合了词频(TF)和逆向文件频率(IDF)来衡量一个词在文档中的重要性。
- TF(Term Frequency):$D = \text{文档长度}$,$\text{count}(t, d) = \text{token } t \text{ 在文档 } d \text{ 中出现的次数}$,$TF(t, d) = \frac{\text{count}(t, d)}{D}$。
- IDF(Inverse Document Frequency):$N = \text{文档总数}$,$df(t) = \text{token } t \text{ 出现于多少文档}$,$\text{IDF}(t) = \log(\frac{N}{1 + df(t)})$。
- $\text{TF-IDF}(t, d) = \text{TF}(t, d) \times \text{IDF}(t)$。
TF-IDF 试图把原始词频计数转成更适合任务的数值。 这个公式在告诉我们,某篇文档相对整个语料最有区分度的内容是什么。 具体而言:
- 一个词出现很多次通常意味着重要,但额外一次出现的边际意义会下降,从出现 $1$ 次到 $2$ 次比从 $101$ 次到 $102$ 次更有意义。
- 一个词如果只出现在少量文档中,它很可能是关键词。
备注:直观上,TF-IDF 更像是在回答两个问题:这个词在当前文档里是否重要,以及它在整个语料里是否足够少见。
有了 TF-IDF,我们可以通过计数为每个词得到一个在文档中的权重,并据此把文档表示成向量,它简单、可解释、训练快,但高维(词表很大)、稀疏(一篇文档只会用到其中很小一部分词)、泛化能力有限(比如同义词不共享统计信号)。
备注:这里需要补充两个概念。模型是一个基于特定算法和大量历史数据构建的、具有预测或生成能力的计算机程序。训练是指利用特定的算法让模型在海量历史数据中进行反复学习,从而自动调整内部参数,使其达到高精度的过程。
既然维度高,一个自然的想法就是降维,把高维稀疏向量压到低维稠密向量,同时尽量保留有用结构。 降维后,每个维度不再对应一个具体词,而是混合特征。 这里不探讨具体的算法。 另一个想法是:能不能人为构造任务并据此学习向量?
Word2vec
Word2vec 就是基于人为构造的任务来学习词向量的一个经典方法。 它的算法可以简单概括如下:
- 查上下文词向量:把上下文里的每个词当作索引,从词向量表里把对应的向量取出来。
- 计算均值:把这些上下文向量做平均,得到一个代表这段上下文整体意思的向量。
- 矩阵乘法:用一层线性变换把这个上下文向量映射成对词表里每个词的打分。
- 向量归一化:把这些打分用归一公式变成概率分布,让它们能表示下一个词是各候选词的概率。
- 计算损失:把模型给真目标词的概率拿出来算错误程度,真词概率越低损失越大。
- 更新权重:根据这次预测的纠错信号,把参数往更不容易犯同样错误的方向调一小步。
word2vec 中的向量和矩阵是学出来的,参数初始是随机的,因此预测很差,随后通过大量小步更新不断改进预测,最终变好。 矩阵乘法之所以能给出缺失词的合理预测,核心在于参数是学出来的。
在向量归一化(Normalize the vector)部分,归一公式的作用是把分数变成可比较的概率。 这一步之前,向量各位置可以是任意值。我们的目标是猜词,因此需要形成一个概率分布。 归一化公式有以下性质:
- 它可以接收任意实数输入(正/负都可)。
- 输出都在 $[0, 1]$。
- 输出值之和为 $1$。
这意味着我们可以把它的输出当作猜测词的概率分布。 同时不管输入是什么,都能稳定处理,不会出现除以 $0$ 等问题。
一个常见的归一公式是 softmax:$\text{softmax}(x_i) = \frac{e^{x_i}}{\sum_{j} e^{x_j}}$。
评估
有了向量表示,我们就需要评估它们的质量,看看它们是否能捕捉到文本之间的相似性和关系。
比较向量
首先,我们可以通过比较向量来衡量文本之间的相似度。
最直观的想法是:把两个向量做点积(Dot Product),得到一个数值,数值越大说明它们越相似:$\text{similarity} = A \cdot B = \sum_{i=1}^{n} A_i B_i$。
更常见的做法是计算余弦相似度(Cosine Similarity),它衡量两个向量 $A$ 和 $B$ 之间的夹角 $\theta$,数值在 $[-1, 1]$ 之间,越接近 $1$ 说明它们越相似:$\text{similarity} = \cos(\theta) = \frac{A \cdot B}{|A| \times |B|}$。
分布式相似性(Distributional Similarity)
分布式相似性基于“词义可由其上下文来理解”这一假设。 它的计算方式通常是将词转化为高维向量,然后计算向量之间的距离,如余弦相似度等。
如果两个词在相似的上下文中出现,那么它们的分布式相似性就高,通常意味着它们的意义也相似。 反之,若两个词的分布式相似性很低,通常意味着它们出现在不同语境里。
备注:不要与分布式表示(Distributed Representation)混淆,后者指用相对稠密的向量而非 one-hot来表示信息。
词类比任务(Word analogy task)
词类比任务是一种内在评估指标,用来粗略检查词向量空间里是否编码了某些词间关系。 做法是把类比题写成向量运算:先算 $u = v(\text{word1}) - v(\text{word2}) + v(\text{word3})$,再在词表里找与 $u$ 最接近的词作为答案(排除输入词),例如 $v(\text{Paris}) - v(\text{France}) + v(\text{Italy}) \approx v(\text{Rome})$(巴黎 - 法国 + 意大利 = 罗马)。
它之所以可能有效,直觉是某些关系在向量空间里表现为相对稳定的方向 / 位移。 如果从“国家”到“首都”对应一个近似固定的偏移,那么 $v(\text{Paris})-v(\text{France})$ 和 $v(\text{Rome})-v(\text{Italy})$ 会接近,于是把这个偏移加到 $v(\text{Italy})$ 上就会落到 $v(\text{Rome})$ 附近。
但它有明显局限:通常对高频词更有效(向量更稳定),只对部分较规则的关系奏效,而且强依赖空间的局部几何结构。 遇到更复杂语义关系或多义词时,静态词向量把不同语境混在一起,类比结果就会变得不稳定。
基本概念
- True / False:预测相对答案是对或错。
- Positive / Negative:预测类别是正类或负类。
- True Positive (TP):被正确预测为正类的样本数。
- False Positive (FP):被错误预测为正类的样本数。
- True Negative (TN):被正确预测为负类的样本数。
- False Negative (FN):被错误预测为负类的样本数。
评估指标
- 准确率(Accuracy):正确标注样本数 / 全部测试样本数,即 $\frac{TP + TN}{TP + FP + TN + FN}$。
- 精确率(Precision):被猜为正且确为正的样本数 / 被猜为正的样本数,即 $\frac{TP}{TP + FP}$。
- 召回率(Recall):被猜为正且确为正的样本数 / 实际为正的样本数,即 $\frac{TP}{TP + FN}$。
- F-Score:Precision 与 Recall 的调和平均,即 $F_1 = 2 \times \frac{\text{Precision} \times \text{Recall}}{\text{Precision} + \text{Recall}}$。
Precision 关注正类预测有多准,Recall 关注能找全多少正类。
- 垃圾邮件检测用 Precision:FP(错杀正常邮件)比 FN(漏掉垃圾邮件)更糟。
- 文档推送用 Recall:FN(漏交本应提供的文档)比 FP(多交不相关文档)更糟。
- 很多任务里,我们更关心 TP,而不是 TN。因为很多 NLP 任务是在找某种东西(如命名实体),而 TN 往往是我们不关心的非目标项,因此重要性较低。
平衡 precision 与 recall,常见做法有两种:
- 调整损失函数,让某类错误惩罚更大(例如 FP 的损失设为 FN 的 2 倍,从而鼓励更高 precision)。
- 训练后调整模型输出阈值。最直接的做法是看模型分数并改变预测阈值。阈值越高,该预测类别越少(通常 precision 更高、recall 更低)。
备注:要完整理解损失函数,需要梯度更新与反向传播。这里的核心思想是:不同错误类型触发不同大小的更新,有些错误会触发更大更新,使模型以后更不容易犯该类错误。
相比 Accuracy,Precision 的优势在于更聚焦目标类别。 Precision 聚焦我们关心的类别(可能很稀有)。 Accuracy 会把所有标签(包括大量 TN)都计入得分。
下面是一些实际场景的例子:
- 识别与审判相关文档:Recall。
- 判断网站是否用意大利语(研究网络上意大利语使用频率):F-Score。
- 客户不希望漏掉任何真实邮件的垃圾邮件检测:Precision。
- 筛选话剧演员申请(导演希望省时但仍组建最佳阵容):Recall。
多分类评估指标
上面的指标都是针对二分类任务的,对于有多个类别的任务,我们可以分别计算每个类别的 Precision 和 Recall,然后根据不同的加权方式得到整体指标:
- $\text{Precision}_{\text{macro}} = \frac{\text{Precision}_A + \text{Precision}_B + \dots}{N}$
- $\text{Precision}_{\text{micro}} = \frac{TP_A + TP_B + \dots}{TP_A + FP_A + TP_B + FP_B + \dots}$
- $\text{Recall}_{\text{macro}} = \frac{\text{Recall}_A + \text{Recall}_B + \dots}{N}$
- $\text{Recall}_{\text{micro}} = \frac{TP_A + TP_B + \dots}{TP_A + FN_A + TP_B + FN_B + \dots}$
- $\text{F1}_{\text{macro}} = \frac{\text{F1}_A + \text{F1}_B + \dots}{N}$
- $\text{F1}_{\text{micro}} = \frac{2 \times (TP_A + TP_B + \dots)}{2 \times (TP_A + TP_B + \dots) + (FP_A + FP_B + \dots) + (FN_A + FN_B + \dots)}$
当每个样本都必须且只能属于某个真实类别、没有“都不属于”的兜底标签(None / Other)时,把各类统计汇总起来算(Micro)和按类分别算再平均(Macro)会得到相同的值。
Micro 和 macro precision / recall 的选择,取决于评估目标。 若目标是尽可能做对更多样本,micro 更合适。 若担心稀有类别处理差,macro 更合适。
备注:实践里经常会同时报告 micro 和 macro,避免只看一个数字导致误判。
当测试集上 micro F-score 远高于 macro F-score 时,通常说明类别分布不均衡。 可能是某个标签很稀有,系统在该标签上表现差,但在另外两个标签上表现好。
阈值曲线类指标
受试者工作特征曲线(Receiver Operating Characteristic,ROC)曲线:把模型的判定阈值从低到高扫一遍,在每个阈值下计算真阳性率 $TPR=\frac{TP}{TP+FN}$ 和假阳性率 $FPR=\frac{FP}{FP+TN}$,然后画出 $TPR$ 对 $FPR$ 的曲线。 它用来观察模型在“多抓到正例”和“多误报负例”之间如何权衡,适合看整体区分能力。
曲线下面积(Area Under the Curve,AUC):是 ROC 曲线下面积,用一个数字概括模型跨所有阈值的总体区分能力。 AUC 越接近 $1$ 表示模型把正负样本区分得越好,越接近 $0.5$ 表示接近随机猜测,它的价值在于不依赖你选定某一个具体阈值。
精确率-召回率曲线(Precision-Recall Curve,PRC)曲线:同样扫描阈值,但画的是 precision 对 recall 的曲线。 它更直接反映“预测为正的有多准”和“正例找得有多全”的权衡,通常在正例很稀少(类别不平衡)时比 ROC 更有参考价值。
ROC、PRC 曲线与 AUC 的价值,在于展示不同阈值下的整体表现。 比较两个模型时,我们关心 precision 与 recall 的权衡。 ROC / PRC 用于可视化这一权衡,帮助决策。 AUC 通过给出一个单数值,衡量模型在所有可能 precision / recall 平衡下的整体质量,从而回避具体阈值选择。
