4. 特征工程与向量化:TF-IDF、Word2Vec、GloVe、BERT Embedding 的提取与对比

文本挖掘里,有一句话我特别认同:“垃圾进,垃圾出”。你模型再牛,喂进去的特征不对,结果就是白搭。特征工程与向量化,说白了就是把人类能看懂的文字,转成机器能算的数学向量。这一步做扎实了,后面调优才有的放矢。

今天咱们就聊聊四种最主流的文本向量化方法:TF-IDF、Word2Vec、GloVe、BERT Embedding。我会结合自己踩过的坑,给你讲清楚它们各自怎么用、什么时候用、以及为什么选这个不选那个。

核心观点:没有最好的向量化方法,只有最合适的。选型取决于你的数据量、任务类型、以及对计算资源的容忍度。

文本向量化方法对比 文本向量化 TF-IDF 稀疏向量,词袋模型 Word2Vec 稠密向量,上下文预测 GloVe 全局统计 + 局部窗口 BERT Embedding 动态上下文,预训练 计算成本:低 语义理解:中 计算成本:高

4.1 TF-IDF:经典但不过时

TF-IDF 是我入行时学的第一个向量化方法。它的思路特别朴素:一个词在文档里出现次数越多越重要(TF),但在所有文档里出现越频繁反而越不重要(IDF)。你想想看,像“的”、“了”、“是”这种停用词,几乎每篇都有,IDF 一算,权重直接压到最低。

我在项目中遇到过一件事:给一个法律文书分类系统做特征工程,一开始直接用词频,结果“根据”、“规定”这种词权重特别高,分类效果很差。换成 TF-IDF 后,那些真正有区分度的词(比如“合同”、“违约”、“赔偿”)才被凸显出来。

实战建议:TF-IDF 适合短文本分类、关键词提取。但注意,它完全忽略词序和语义。“苹果好吃”和“好吃苹果”在 TF-IDF 眼里一模一样。

from sklearn.feature_extraction.text import TfidfVectorizer

corpus = [
    "我喜欢吃苹果",
    "苹果手机很好用",
    "这个苹果不太甜"
]

vectorizer = TfidfVectorizer(max_features=100, ngram_range=(1,2))
X = vectorizer.fit_transform(corpus)
print(X.toarray())  # 输出稀疏矩阵
print(vectorizer.get_feature_names_out())

4.2 Word2Vec:词向量的里程碑

Word2Vec 的出现,算是把 NLP 往前推了一大步。它不再是简单的词袋统计,而是通过上下文预测来学习词的稠密向量。说白了,就是“一个词的意思,由它周围的词决定”。

我记得第一次用 Word2Vec 做相似词计算时,发现“国王 - 男人 + 女人 ≈ 女王”,当时觉得这玩意儿太神奇了。但后来在实际项目中,我发现一个问题:Word2Vec 对低频词很不友好。如果一个词在语料里只出现几次,它的向量基本就是随机噪声。

避坑指南:我曾经在一个医疗文本项目里直接用默认参数的 Word2Vec,结果“阑尾炎”和“盲肠炎”的向量距离特别远,因为语料里“盲肠炎”出现次数太少。后来我加大了 min_count 参数,并且用预训练的词向量做初始化,效果才上来。

from gensim.models import Word2Vec

sentences = [["我", "喜欢", "吃", "苹果"],
             ["苹果", "手机", "很", "好用"],
             ["这个", "苹果", "不", "太", "甜"]]

model = Word2Vec(sentences, vector_size=100, window=5, min_count=1, sg=0)
# sg=0 是 CBOW,sg=1 是 Skip-gram
vector = model.wv['苹果']
print(vector[:10])  # 打印前10维

4.3 GloVe:全局统计的智慧

GloVe 是 Word2Vec 的一个有力竞争者。它俩的区别在哪?Word2Vec 只看局部窗口,GloVe 同时看全局共现矩阵。你想想看,如果只靠局部窗口,“苹果”和“手机”在 Word2Vec 里可能学不到太强的关联,除非它们经常出现在同一个窗口里。但 GloVe 会统计整个语料库中“苹果”和“手机”共同出现的次数,这个全局信息很有价值。

我个人习惯是:数据量大的时候优先用 GloVe。因为它的训练过程可以并行化,而且对低频词的表示比 Word2Vec 稳定一些。但要注意,GloVe 需要先构建共现矩阵,内存消耗比较大。

选型建议:如果你的语料在百万级别以上,GloVe 是个好选择。如果只有几万条数据,Word2Vec 的 Skip-gram 模式反而更灵活。

# GloVe 通常使用预训练好的向量
# 下载 glove.6B.100d.txt 后加载
import numpy as np

def load_glove_embeddings(filepath):
    embeddings = {}
    with open(filepath, 'r', encoding='utf-8') as f:
        for line in f:
            values = line.split()
            word = values[0]
            vector = np.asarray(values[1:], dtype='float32')
            embeddings[word] = vector
    return embeddings

# 使用示例
glove = load_glove_embeddings('glove.6B.100d.txt')
print(glove['apple'][:10])

4.4 BERT Embedding:动态上下文的王者

BERT 出来之后,很多传统方法都显得有点“过时”了。为什么?因为 BERT 的向量是动态的。同样一个“苹果”,在“吃苹果”和“苹果手机”里,BERT 会生成两个不同的向量。这在 TF-IDF 和 Word2Vec 里是做不到的。

但 BERT 也有它的代价。我记得有一次给客户做实时文本分类系统,用 BERT 做特征提取,单条文本推理时间接近 100 毫秒。客户说“太慢了”,最后我们换成了 TF-IDF + 轻量级分类器,虽然准确率掉了 3 个点,但延迟降到了 5 毫秒以内。嗯,这里要注意:BERT 不是万能的,它适合离线分析或对延迟不敏感的场景

实用技巧:如果你用 BERT 做特征提取,建议取倒数第二层的输出,而不是最后一层。最后一层太偏向预训练任务,倒数第二层的语义更通用。

from transformers import BertTokenizer, BertModel
import torch

tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
model = BertModel.from_pretrained('bert-base-chinese')

text = "我喜欢吃苹果"
inputs = tokenizer(text, return_tensors='pt')
outputs = model(**inputs)

# 取 [CLS] 向量作为句子表示
sentence_embedding = outputs.last_hidden_state[:, 0, :]
print(sentence_embedding.shape)  # torch.Size([1, 768])

4.5 四种方法对比总结

说了这么多,咱们来张表总结一下。这张表是我在实际项目中反复验证过的,你可以直接拿来当选型参考。

方法 向量类型 语义理解 计算成本 适用场景
TF-IDF 稀疏 低(无语义) 极低 短文本分类、关键词提取
Word2Vec 稠密 中(局部语义) 词相似度、中等规模语料
GloVe 稠密 中高(全局+局部) 大规模语料、词类比任务
BERT 稠密(动态) 高(上下文感知) 语义匹配、问答、离线分析

最后说一句:别盲目追新。我见过太多人一上来就用 BERT,结果数据量不够、硬件跟不上,效果还不如 TF-IDF。选型之前,先问问自己:我的数据有多大?我的任务需要多深的语义理解?我的线上延迟要求是多少?想清楚这些,你自然就知道该选哪个了。

专注资料整理