NLP学习笔记02:文本表示方法------从词袋模型到 BERT
作者:Ye Shun
日期:2026-04-15
一、前言
在自然语言处理(NLP)中,文本本身是非结构化数据,计算机并不能直接理解一句话的含义。无论是文本分类、情感分析、信息检索,还是问答系统、机器翻译和大语言模型,第一步都离不开一个核心问题:怎样把文本变成计算机可以处理的数值形式。
这就是文本表示(Text Representation)要解决的问题。
简单来说,文本表示的目标是把词、句子或文档映射为向量,让模型可以在"数值空间"中进行计算。文本表示方法的发展大致经历了这样一条路线:
- 传统统计方法:词袋模型、TF-IDF、N-gram
- 静态词向量方法:Word2Vec、GloVe、FastText
- 上下文感知表示:ELMo、BERT 及其变体
- 句向量与文档向量:Doc2Vec、Sentence-BERT、主题模型
这篇笔记会按照这条演进路径展开,帮助我们理解为什么 NLP 会从"统计特征工程"逐步走向"预训练表示学习"。
二、传统文本表示
传统方法的特点是实现简单、可解释性较强、训练成本低,但通常难以捕捉深层语义。
1. 词袋模型(Bag of Words)
词袋模型是最基础的文本表示方法之一。它把一段文本看作一个无序的词汇集合,只关心某个词是否出现、出现了几次,而不关心词语顺序和语法结构。
基本思想
- 先构建整个语料的词汇表
- 再统计每篇文档中每个词出现的次数
- 最终把每篇文档表示成一个高维稀疏向量
比如有一个小语料:
python
corpus = [
"This is the first document.",
"This document is the second document.",
"And this is the third one.",
"Is this the first document?"
]
使用 CountVectorizer 构建词袋特征:
python
from sklearn.feature_extraction.text import CountVectorizer
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(corpus)
print(vectorizer.get_feature_names_out())
print(X.toarray())
这里输出的每一行都对应一篇文档,每一列对应词汇表中的一个词,数值则表示词频。
优点
- 实现简单,容易理解
- 训练和计算速度快
- 在小规模数据和基线任务中仍然有效
缺点
- 忽略词序和上下文
- 不能表达语义相似性
- 维度通常很高,而且向量非常稀疏
- 无法处理一词多义和同义表达
换句话说,词袋模型能回答"这个词出现了没有",却很难回答"这个词在这里是什么意思"。
2. TF-IDF
TF-IDF(Term Frequency-Inverse Document Frequency)可以看作是对词袋模型的加权改进。它不仅考虑一个词在当前文档中出现得多不多,还考虑这个词在整个语料里是否过于常见。
核心思想
- 如果一个词在某篇文档中频繁出现,它可能很重要
- 但如果这个词在所有文档中都频繁出现,它的区分能力就会下降
因此,TF-IDF 用下面的思想来衡量词的重要性:
- TF:词在当前文档中出现的频率
- IDF:词在整个语料中的稀有程度
- TF-IDF = TF × IDF
常见公式为:
text
TF(t, d) = 词 t 在文档 d 中出现次数 / 文档 d 总词数
IDF(t) = log(文档总数 / 包含词 t 的文档数)
TF-IDF(t, d) = TF(t, d) × IDF(t)
代码示例如下:
python
from sklearn.feature_extraction.text import TfidfVectorizer
tfidf_vectorizer = TfidfVectorizer()
X_tfidf = tfidf_vectorizer.fit_transform(corpus)
print(tfidf_vectorizer.get_feature_names_out())
print(X_tfidf.toarray())
优点
- 比原始词频更能突出"有区分度"的词
- 在文本分类、检索等任务中常作为强基线
- 不需要复杂训练,使用方便
缺点
- 本质上仍是稀疏统计特征
- 仍然无法建模词序和上下文
- 不能直接表示语义关系
TF-IDF 很适合"关键词重要性"场景,但不擅长深层语义理解。
3. N-gram 模型
词袋模型忽略了顺序信息,而 N-gram 模型试图部分弥补这个问题。它不只看单个词,还考虑连续的 n 个词组合。
常见类型包括:
- Unigram:单个词
- Bigram:两个连续词
- Trigram:三个连续词
例如:
natural languagelanguage processingnatural language processing
代码示例如下:
python
from sklearn.feature_extraction.text import CountVectorizer
bigram_vectorizer = CountVectorizer(ngram_range=(2, 2))
X_bigram = bigram_vectorizer.fit_transform(corpus)
print(bigram_vectorizer.get_feature_names_out())
优点
- 能捕捉局部词序信息
- 能表示短语和固定搭配
- 对某些分类任务和检索任务很有帮助
缺点
- 特征维度膨胀更严重
- 数据稀疏问题更明显
- 仍然无法处理长距离依赖
可以把 N-gram 看成是"比词袋多了一点顺序信息",但还远远称不上真正的语义建模。
三、词向量表示
传统统计方法的局限很明显:它们通常只能表示"词出现了几次",却不能表示"词和词之间有多像"。词向量方法的出现,正是为了让词语拥有可以学习的连续分布式表示。
1. Word2Vec
Word2Vec 是 Google 在 2013 年提出的经典词向量模型。它的核心思想很简单:一个词的含义,可以从它出现的上下文中学习出来。
Word2Vec 主要有两种架构:
- CBOW(Continuous Bag of Words):根据上下文预测中心词
- Skip-gram:根据中心词预测上下文
代码示例如下:
python
from gensim.models import Word2Vec
sentences = [["cat", "say", "meow"], ["dog", "say", "woof"]]
model = Word2Vec(sentences, vector_size=100, window=5, min_count=1, workers=4)
vector = model.wv["cat"]
similar_words = model.wv.most_similar("cat")
特点
- 得到的是低维稠密向量,通常 50 到 300 维
- 可以在一定程度上捕捉语义和语法关系
- 支持向量运算,如
king - man + woman ≈ queen
局限
- 每个词只有一个固定向量
- 无法区分一词多义
- 对上下文变化不敏感
也就是说,在 Word2Vec 中,"bank"无论出现在"river bank"还是"bank account"中,向量通常是一样的。
2. GloVe
GloVe(Global Vectors for Word Representation)是 Stanford 提出的词向量方法。它结合了局部上下文窗口和全局共现统计信息。
核心思想
- 先统计词与词的共现矩阵
- 再通过优化目标学习词向量
- 希望两个词向量的点积可以反映它们共现次数的对数
可以简单理解为:
- Word2Vec 更偏"预测式"
- GloVe 更偏"统计式 + 表示学习"
两者比较如下:
| 特性 | Word2Vec | GloVe |
|---|---|---|
| 训练方式 | 局部窗口预测 | 全局共现统计 |
| 计算效率 | 较高 | 较低 |
| 小数据集表现 | 较好 | 一般 |
| 大数据集表现 | 好 | 更好 |
在工程中,很多时候会直接使用预训练好的 GloVe 向量,而不是自己从头训练。
3. FastText
FastText 是 Facebook 提出的词向量模型,它在 Word2Vec 的基础上进一步考虑了子词(subword)信息。
核心特点
- 把一个词表示为多个字符 n-gram 的组合
- 能较好处理未登录词(OOV)
- 对词形变化丰富的语言更友好
例如单词 apple 可以被拆成多个子串,再共同组成词向量。这意味着即便模型从未见过完整单词,也可能根据子词信息构造出一个近似向量。
代码示例如下:
python
from gensim.models import FastText
sentences = [["cat", "say", "meow"], ["dog", "say", "woof"]]
model = FastText(sentences, vector_size=100, window=5, min_count=1, workers=4)
vector = model.wv["unseenword"]
优点
- 更擅长处理 OOV 问题
- 对拼写变化和词形变化更鲁棒
- 在很多语言任务上表现稳定
局限
- 仍然是静态词向量
- 上下文相关性仍然有限
四、上下文感知的表示
静态词向量的最大问题在于:同一个词,不管放在什么句子里,向量都不变。但现实语言中,很多词的含义高度依赖上下文。
为了解决这个问题,NLP 开始进入"上下文表示"阶段。
1. ELMo
ELMo(Embeddings from Language Models)是较早的重要上下文相关词表示方法之一。
核心特点
- 基于双向 LSTM 语言模型
- 一个词的表示取决于整句上下文
- 可以利用不同层表示不同粒度的信息
这意味着同一个词在不同句子中会得到不同的向量表示。相比 Word2Vec 这类静态词向量,ELMo 明显更接近真实语言理解。
2. BERT 及其变体
BERT(Bidirectional Encoder Representations from Transformers)是 Google 在 2018 年提出的预训练语言模型,也是现代 NLP 的重要里程碑。
关键创新
- 使用 Transformer 架构
- 使用双向上下文建模
- 使用掩码语言模型(MLM)训练目标
- 原始版本中还包含下一句预测(NSP)任务
常见变体包括:
- RoBERTa:优化训练策略
- DistilBERT:更轻量、更快
- ALBERT:通过参数共享压缩模型
代码示例如下:
python
from transformers import BertTokenizer, BertModel
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
model = BertModel.from_pretrained("bert-base-uncased")
inputs = tokenizer("Hello, my dog is cute", return_tensors="pt")
outputs = model(**inputs)
last_hidden_states = outputs.last_hidden_state
last_hidden_state 中保存了每个 token 的上下文表示,它已经不是简单的词频或静态词向量,而是融合了整句信息的深层表示。
3. 预训练 + 微调范式
现代 NLP 很多模型的基本流程都是:
- 在海量通用语料上做预训练
- 在具体任务数据上做微调
这种范式带来的好处是:
- 能利用大规模数据学习通用语言知识
- 能较快迁移到下游任务
- 大幅减少手工特征工程工作
常见模型演进可以简单概括为:
| 模型 | 发布时间 | 主要特点 |
|---|---|---|
| Word2Vec | 2013 | 静态词向量 |
| GloVe | 2014 | 全局统计 + 局部窗口 |
| ELMo | 2018 | 双向 LSTM,上下文相关 |
| BERT | 2018 | Transformer,双向上下文 |
| GPT-3 | 2020 | 大规模生成式 Transformer |
从这里也能看出,文本表示的发展趋势,就是从"静态特征"不断走向"动态语义表示"。
五、文档级表示
前面讨论的大多数方法主要关注词级别表示,但在很多实际任务中,我们更关心的是整句话、整段文本甚至整篇文档的表示方式。
1. Doc2Vec
Doc2Vec 可以看作是 Word2Vec 在文档级表示上的扩展。它不只学习词向量,也学习文档本身的向量。
主要有两种形式:
- PV-DM(Distributed Memory):类似 CBOW,引入文档 ID
- PV-DBOW(Distributed Bag of Words):类似 Skip-gram
代码示例如下:
python
from gensim.models import Doc2Vec
from gensim.models.doc2vec import TaggedDocument
documents = [TaggedDocument(doc.split(), [i]) for i, doc in enumerate(corpus)]
model = Doc2Vec(documents, vector_size=100, window=5, min_count=1, workers=4)
vector = model.infer_vector(["new", "document", "text"])
Doc2Vec 在早期文档分类和聚类任务中较常见,但现在很多场景会更多使用基于预训练模型的句向量或文档向量。
2. 句向量与文档向量
如果我们已经有词向量,最直接的方法就是把一个句子中所有词向量取平均,得到句向量。
常见方法包括:
- 平均法:直接平均所有词向量
- SIF:对高频词降权后再平均
- BERT 句向量:使用
[CLS]或平均 token 向量
在实践中,Sentence-BERT 是非常常见的句向量工具:
python
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("all-MiniLM-L6-v2")
sentences = ["This is an example sentence", "Each sentence is converted"]
embeddings = model.encode(sentences)
句向量常用于:
- 语义相似度计算
- 文本检索
- 问答匹配
- 聚类和推荐
3. 主题模型(LDA)
LDA(Latent Dirichlet Allocation)是一种经典的无监督主题模型。虽然它不属于现代神经网络表示学习方法,但在文档级表示里依然值得了解。
基本思想
- 一篇文档可以看成多个主题的混合
- 每个主题可以看成若干词语的概率分布
- 通过概率推断学习文档和主题之间的关系
代码示例如下:
python
from sklearn.decomposition import LatentDirichletAllocation
from sklearn.feature_extraction.text import CountVectorizer
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(corpus)
lda = LatentDirichletAllocation(n_components=2, random_state=42)
lda.fit(X)
LDA 常见应用包括:
- 文档聚类
- 内容推荐
- 主题发现
- 摘要和分析前的探索性处理
它并不直接给出"语义向量",但可以给出"主题分布表示",这也是一种很有价值的文档表示方式。
六、如何选择合适的文本表示方法
文本表示方法并没有放之四海而皆准的"最优解",选择时通常要结合任务目标、数据规模和算力条件。
可以从下面几个角度来判断:
1. 看任务需求
- 如果只是做简单分类、关键词统计、基线实验,词袋模型和 TF-IDF 往往已经够用
- 如果需要一定语义信息,可以考虑 Word2Vec、FastText
- 如果需要上下文理解、语义匹配、复杂推理,通常更适合 BERT 类模型
2. 看数据规模
- 小数据集下,传统方法可能更稳定
- 数据规模较大时,词向量和预训练模型优势更明显
3. 看计算资源
- 传统方法成本最低
- 词向量训练成本适中
- BERT 类模型通常最吃显存和算力
4. 看语言特点
- 对于中文,分词质量会直接影响传统表示效果
- 对于词形变化丰富的语言,FastText 的子词信息往往更有帮助
因此,方法选择本质上是一个"效果、成本、任务需求"的平衡问题。
七、一个简明的演进总结
如果用一句话概括文本表示的发展,可以理解为:
- 传统方法关注"这个词出现了多少次"
- 词向量方法关注"这个词和哪些词更相似"
- 上下文模型关注"这个词在当前句子里到底是什么意思"
- 文档表示关注"这一整段文本整体表达了什么"
这条演进路线非常重要,因为它几乎贯穿了整个 NLP 技术发展史。
八、总结
文本表示是 NLP 中最核心的基础能力之一。没有合适的文本表示,就谈不上后续的建模和推理。
从方法演进上看:
- 词袋模型、TF-IDF 和 N-gram 属于传统统计表示,简单高效,但语义能力有限
- Word2Vec、GloVe 和 FastText 把文本表示推进到低维稠密向量时代
- ELMo 和 BERT 让表示从"静态词向量"升级为"上下文相关表示"
- Doc2Vec、Sentence-BERT 和 LDA 则进一步把表示扩展到句子和文档层面
在学习路径上,建议不要只盯着 BERT 或大模型,而忽略前面的基础方法。因为很多现代方法的改进方向,本质上都是在弥补传统表示的不足。把这些基础方法理解清楚,后面学检索增强、向量数据库、语义匹配和大语言模型时,思路会清晰很多。