3、特征工程入门:TF-IDF原理、词袋模型、N-gram特征提取

各位同学好,我是老李。今天咱们聊聊特征工程里最基础、也最绕不开的三个概念:词袋模型、TF-IDF 和 N-gram。说实话,我当年刚入行时,觉得这玩意儿不就是数数词频嘛,有啥好学的?结果第一次做法律文书分类,就被现实狠狠教育了一顿。

你想想看,法律案例检索里,一份判决书动辄几千字,里面充斥着「本院认为」、「依照规定」、「驳回上诉」这些高频词。如果直接拿原始文本去训练模型,那模型学到的全是噪音,根本抓不住关键信息。所以,特征工程就是帮模型「划重点」的过程。

核心观点:特征工程不是简单的数学变换,而是把人类对文本的理解,翻译成机器能懂的数学语言。做得好,模型事半功倍;做得糙,再牛的算法也白搭。

3.1 词袋模型:最朴素的文本向量化

词袋模型(Bag of Words,简称 BoW)是文本特征提取的起点。它的思路极其简单:把文档看成一堆词的集合,忽略语法和词序,只关心每个词出现了多少次。

举个例子,假设有两份法律文书:

  • 文书A:「原告主张被告违约,被告辩称合同无效」
  • 文书B:「合同无效导致违约,原告主张赔偿损失」

用词袋模型处理后,我们会先构建一个词典,然后统计每份文书中每个词的出现次数。结果大概长这样:

文书A 文书B
原告 1 1
主张 1 1
被告 1 0
违约 1 1
合同 1 1
无效 1 1
辩称 1 0
赔偿 0 1
损失 0 1

你看,每份文书都变成了一个固定长度的向量,向量的维度就是词典的大小。这就是词袋模型的核心——把文本映射到高维稀疏空间。

我的经验:词袋模型虽然简单,但在法律场景下有个致命问题——它完全忽略了词的语义关系。比如「违约」和「违反合同」在词袋模型里是两个完全独立的特征,但实际意思很接近。所以,我一般只在做基线模型时用词袋,或者配合后面的 TF-IDF 一起用。

3.2 TF-IDF:给词加上权重

词袋模型有个明显的缺陷:它认为所有词同等重要。但在法律文书中,「的」、「了」、「是」这些停用词出现频率极高,却几乎不携带任何信息。而像「违约金」、「管辖权」、「再审」这些词,虽然出现次数不多,但恰恰是区分案件类型的关键。

TF-IDF 就是来解决这个问题的。它的全称是 Term Frequency-Inverse Document Frequency,翻译过来就是「词频-逆文档频率」。说白了,它给每个词算两个数:

  • TF(词频):这个词在当前文档中出现了多少次。出现越多,说明它对当前文档越重要。
  • IDF(逆文档频率):这个词在所有文档中出现了多少次。出现越少,说明它越稀有,越能区分不同文档。

最终的 TF-IDF 值就是 TF 乘以 IDF。一个词如果在当前文档中出现很多,但在其他文档中出现很少,那它的 TF-IDF 值就很高。反之,像「的」这种到处都有的词,IDF 值会非常低,最终权重几乎为零。

公式长这样:

TF(t, d) = 词 t 在文档 d 中出现的次数 / 文档 d 的总词数
IDF(t) = log(总文档数 / 包含词 t 的文档数 + 1)
TF-IDF(t, d) = TF(t, d) × IDF(t)

避坑指南:我曾经在一个法律案例检索项目里,直接用原始 TF-IDF 跑模型,结果发现「本院认为」这个词的权重特别高。为什么?因为几乎每份判决书都有这个词,IDF 值很低,但 TF 值也不高啊?后来一查,原来是分词出了问题——「本院认为」被当成了一个整体词。所以,做 TF-IDF 之前,一定要先做好分词和停用词过滤,否则结果会非常离谱。

在实际应用中,我习惯用 scikit-learn 的 TfidfVectorizer,几行代码就能搞定:

from sklearn.feature_extraction.text import TfidfVectorizer

corpus = [
    "原告主张被告违约",
    "合同无效导致违约",
    "被告辩称合同无效"
]

vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(corpus)

# 查看特征词
print(vectorizer.get_feature_names_out())
# 输出:['合同' '原告' '被告' '辩称' '违约' '无效' '导致' '主张']

# 查看 TF-IDF 矩阵
print(X.toarray())

输出结果是一个稀疏矩阵,每一行代表一份文档,每一列代表一个词,值就是 TF-IDF 权重。你会发现,「导致」这个词只在第三份文档中出现,所以它的权重会相对较高。

3.3 N-gram:捕捉词序信息

词袋模型和 TF-IDF 都有一个共同的问题:它们把词当成独立的个体,完全忽略了词与词之间的顺序。但在法律文本中,词序往往决定了语义。比如「不构成侵权」和「构成不侵权」,词袋模型会认为它们很相似,但实际意思完全相反。

N-gram 就是来解决这个问题的。它的思路很简单:不再只考虑单个词,而是考虑连续的 N 个词组成的词组。比如:

  • Unigram(1-gram):单个词,如「原告」、「被告」、「违约」
  • Bigram(2-gram):连续两个词,如「原告主张」、「被告违约」、「合同无效」
  • Trigram(3-gram):连续三个词,如「原告主张被告」、「被告辩称合同」

用 N-gram 处理后,上面的例子会变成:

特征 文书A 文书B
原告 1 1
原告主张 1 0
被告违约 1 0
合同无效 0 1
无效导致 0 1

你看,通过 Bigram,我们捕捉到了「原告主张」和「被告违约」这些短语信息,模型就能更好地区分不同文书的语义。

我的建议:N 的取值很关键。N 太小,捕捉不到长距离依赖;N 太大,特征空间会爆炸,而且很多 N-gram 只出现一次,变成噪音。我个人的经验是,在法律文本中,Bigram 和 Trigram 的效果最好。比如「不构成侵权」这个 Trigram,比单独的「不」、「构成」、「侵权」要有用得多。

3.4 三者的对比与选择

说了这么多,你可能想问:到底该用哪个?我整理了一个对比表,方便你参考:

方法 优点 缺点 适用场景
词袋模型 简单、快速、易于理解 忽略词序、忽略语义、高维稀疏 基线模型、短文本分类
TF-IDF 降低高频词影响、突出关键信息 仍忽略词序、对长文本效果一般 信息检索、关键词提取、文本分类
N-gram 捕捉局部词序、保留短语信息 特征维度爆炸、数据稀疏 情感分析、法律条款匹配、命名实体识别

在实际项目中,我通常的做法是:先用词袋模型快速搭建一个基线,然后换成 TF-IDF 看看效果有没有提升,最后再尝试加入 N-gram 特征。如果效果不明显,就果断放弃,不要为了用而用。

3.5 知识体系总览

为了让你更直观地理解这三者的关系,我画了一张图:

特征工程核心方法对比 词袋模型 (BoW) TF-IDF N-gram 核心:词频统计 忽略词序和语义 核心:词频 × 逆文档频率 降低高频词干扰 核心:连续N个词 捕捉局部词序 缺点:高维稀疏 缺点:仍忽略词序 缺点:维度爆炸 推荐用法:先 BoW 基线 → 加 TF-IDF 提权 → 加 N-gram 补词序 三者可以组合使用,但要注意特征维度和过拟合问题

嗯,这张图基本把三者的关系说清楚了。你想想看,从词袋模型到 TF-IDF,再到 N-gram,其实是一个逐步「精细化」的过程。词袋模型是基础,TF-IDF 加了权重,N-gram 加了词序。每一步都在解决前一步的某个缺陷。

最后说一句,特征工程没有银弹。我在不同项目里试过各种组合,有时候简单的词袋模型加上好的分类器,效果反而比花里胡哨的特征工程要好。关键是要理解你的数据和业务场景,然后有针对性地选择方法。


公众号:蓝海资料掘金营,微信deep3321