01_文本处理与词表示
1 文本处理基本方法
1.1 分词
分词(Tokenization) 是将原始文本切分为若干具有独立语义的最小单元(即 token) 的过程,是所有 NLP 任务的起点。
不同语言有不同的分词策略:
- 英文
- 词级分词:用空格或标点进行分割,容易出现
OOV(Out-Of-Vocabulary,未登录词)问题。通常会将其统一替换为特殊标记(如
<UNK>),从而导致语义信息的丢失。 - 字符级分词:按字母进行分词,几乎不存在 OOV 问题,但由于单个字符语义信息极弱,模型必须依赖更长的上下文来推断词义和结构,增加了建模难度和训练成本。
- 子词级分词:按照词根词缀等进行分词,既缓解了 OOV 问题,又保留了语义信息,是现代主流分词方式,常见算法有 BPE (Byte Pair Encoding)、WordPiece 和 Unigram Language Model。
- 词级分词:用空格或标点进行分割,容易出现
OOV(Out-Of-Vocabulary,未登录词)问题。通常会将其统一替换为特殊标记(如
- 中文:
- 字符级分词:由于中文单个字符的语义信息非常强,因此比英文更适合进行字符级分词。
- 词级分词:由于中文没有空格等天然词边界,词级分词通常依赖词典、规则或模型来识别词语边界。
- 子词级分词:虽然中文没有词根词缀,但是 BPE 算法可以通过学习语料中高频的字组合,如“自然”、“语言”,自动构建子词词表。无需人工词典,具有较强的适应能力,是现在主流方法。
1.2 分词工具
1.2.1 中文分词
主流的中文分词工具有如下两类: - 基于词典或模型的传统方法,主要以词为单位进行切分,如 jieba、HanLP,用于传统 NLP 任务。 - 基于子词建模算法(如 BPE)的方式,从数据中自动学习高频字组合,构建子词词表,如 Hugging Face Tokenizer、SentencePiece、tiktoken,常用于大规模预训练语言模型中。
下面介绍常用的 jieba 分词模块。
jieba.cut和jieba.cut_for_search返回一个可迭代的generatorjieba.lcut和jieba.lcut_for_search直接返回list。
cut(text, cut_all=False, HMM=True),cut_for_search(text, HMM=True):
cut_all控制是否使用全模式分词。HMM控制是否使用 HMM 模型。
精确模式(默认):试图将句子最精确地切开,适合文本分析。
1 | |
/opt/miniconda3/envs/dl/lib/python3.12/site-packages/jieba/_compat.py:18: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
import pkg_resources
Building prefix dict from the default dictionary ...
Loading model from cache /var/folders/7s/cqjc9twd1ngbl_f7129vv6bc0000gn/T/jieba.cache
Loading model cost 0.283 seconds.
Prefix dict has been built successfully.
'坤/坤/爆/表示/:/“/自然语言/处理/是/计算机科学/领域/重要/的/研究/方向/”'
全模式:把句子中所有的可以成词的词语都扫描出来, 速度非常快,但是不能消除歧义。
1 | |
'坤/坤/爆/表示/:“/自然/自然语言/语言/处理/是/计算/计算机/计算机科学/算机/科学/领域/重要/的/研究/方向/”'
搜索引擎模式:在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词。
1 | |
'坤/坤/爆/表示/:/“/自然/语言/自然语言/处理/是/计算/算机/科学/计算机/计算机科学/领域/重要/的/研究/方向/”'
自定义词典:创建词表文件,自行添加新词保证更高的正确率。
1 | |
1 | |
'坤坤爆/表示/:/“/自然语言处理/是/计算机科学/领域/重要/的/研究/方向/”'
使用 add_word(word, freq=None, tag=None) 和
del_word(word) 可在程序中动态修改词典。
使用 suggest_freq(segment, tune=True)
可调节单个词语的词频,使其能(或不能)被分出来。
注意:自动计算的词频在使用 HMM
新词发现功能时可能无效。
词表(Vocabulary) 是由语料库构建出的去重后的 token 集合,词表中每个 token 都分配有唯一的 ID,并支持 token 与 ID 之间的双向映射。
1.2.2 英文分词
英文分词工具常用NLTK(Natural Language
Toolkit)。通过pip install nltk安装,通常还需要下载
punkt 模型。
1 | |
NLTK 包含许多自然语言处理工具,这里仅介绍一些基本功能,更多详细信息,请参考 NLTK 文档。
1 | |
分词结果:['Natural', 'language', 'processing', 'is', 'so', 'fun', '.']
词还原结果:Natural language processing is so fun.
此外 NLTK 还提供了BLEU (Bilingual Evaluation Understudy) 评估指标,用于评估机器翻译和文本生成的质量。
BLEU 的分数范围为0-1,分数越高质量越好。BLEU 的计算有两部分组成:
- N-gram精确度:
- 1-gram : 比较单个单词,主要评估内容准确性。
- 2-gram / 3-gram: 比较连续的词组,主要评估内容流畅性。
- Clipping (截断): 为了防止机器作弊,会限制某个词的匹配次数不能超过参考答案中实际出现的次数。比如参考答案是 “the cat”,机器输出 “the the the the”)。
- 简短惩罚
- 如果机器生成的句子比参考答案短,分数会大打折扣。
- 如果没有简短惩罚,机器会偏向生成短句,因为短句容易全对,正确率高。比如参考答案是 “The cat is on the table”,机器输出 “The cat”。
1 | |
1.0
0.8091067115702212
BLEU 计算简单快速,但是不懂同义词,比如参考为 “He is quick”,预测为 “He is fast”,那么 BLEU 值就会下降。
1.3 命名实体识别
命名实体识别(Named Entity Recognition,简称NER):识别出一段文本中可能存在的命名实体。
命名实体:人名, 地名, 机构名等专有名词,如: 周杰伦, 黑山县, 孔子学院。
1.4 词性标注
词性标注(Part-Of-Speech tagging, 简称POS):标注出一段文本中每个词汇的词性。
1 | |
([pair('Natural', 'eng'),
pair(' ', 'x'),
pair('language', 'eng'),
pair(' ', 'x'),
pair('processing', 'eng'),
pair(' ', 'x'),
pair('is', 'eng'),
pair(' ', 'x'),
pair('so', 'eng'),
pair(' ', 'x'),
pair('fun', 'eng'),
pair('.', 'm')],
'Natural',
'eng')
2 词表示
在分词完成之后,文本被转换为一系列的 token(词、子词或字符),这些符号本身对计算机而言是不可计算的,为了让模型能够理解和处理文本,必须将这些 token 转换为数值形式,这一步就是所谓的词表示(word representation)。
2.1 one-hot 编码
one-hot编码,又称独热编码,将词汇表中的每个词映射为一个稀疏向量,向量的长度等于整个词表的大小。该词在对应的位置为 1,其他位置为 0。
one-hot 操作非常简单,但缺点也十分明显,完全割裂了词与词之间的语义关系;并且词表规模较大时,one-hot 编码的稀疏向量长度会非常大,占用大量内存,计算效率也会下降。
2.2 Word2Vec 模型
2.2.1 概述
Word2Vec 通过对大规模语料的学习,为每个词生成一个具有语义意义的稠密向量表示。该向量在该高维空间中的位置反映了单词的含义,使得意思相近的词在空间中距离更近。
Word2Vec 的设计理念源自“分布假设”——即一个词的含义由它周围的词决定。基于这一理念使用神经网络模型,通过学习词与上下文之间的关系,自动为每个词生成一个能够反映语义特征的向量表示。

词向量的开发也包括分析学习到的向量,并探索如何利用向量分析来操控它们。例如,从“国王”一词中减去“男性特质”,加上“女性特质”,就会得到“女王”一词,这体现了“国王之于女王,正如男人之于女人”的类比。也就是说,词语之间许多语义和句法关系在词向量空间中几乎是线性的。

Word2Vec 提供了两种典型的模型结构,用于实现对词向量的学习:
- CBOW(Continuous Bag of Words):连续词袋模型,利用上下文(当前词的前后几个词)来预测中心词。
- Skip-gram:根据中心词预测上下文词。
两种方法的选择取决于具体任务,Skip-gram 在数据量有限的情况下表现良好,擅长表示不常用词。相比之下,CBOW 能更好地表示常用词。

2.2.2 CBOW 和 Skip-gram
Word2Vec 不依赖人工标注,可以直接利用大规模原始文本作为数据源,由于预测输出都是词语,所有首先对数据源进行分词,并转换为模型可以识别的 one-hot 编码。
详细流程:CBOW、Skip-gram深度解析
CBOW模型的前向传播过程:
- 输入上下文:输入 one-hot 表示的上下文词。
- 查找词向量:与 Win 矩阵相乘,由于 one-hot 只有一个 1,所以每个词的结果就是从 Win 矩阵中取出对应的一行。Win 实际就相当于词向量矩阵,每一行代表一个词向量,而列数就是词向量的维度,由神经元的数量决定。
- 平均词向量:将上下文的多个词向量求和,然后除以词向量的数量,得到平均词向量,得到一个表示整体语义的向量。
- 预测中心词:将平均词向量与 Wout 矩阵相乘,得到对整个词表的预测得分。
- Softmax 输出:对预测得分进行 Softmax 运算,得到每个词的概率。
- 计算损失:将预测概率与真实标签进行交叉熵损失计算。
之后再进行反向传播时,参数矩阵 𝑊𝑖𝑛 中对应的词向量就会被更新,模型通过不断训练,逐步优化这些向量,最终便能得到具有语义的词向量。

Skip-gram 前向传播流程:
- 输入中心词:输入用 one-hot 表示的中心词。
- 查找词向量:与 Win 相乘,取出表示中心词的词向量。
- 预测上下文:与 Wout 相乘,得到整个词表的预测得分。
- Softmax 输出:对预测得分进行 Softmax 运算,得到每个词的概率。
- 计算损失:将预测概率与真实标签进行交叉熵损失计算。
再经过反向传播便会更新 Win 中对应的词向量,不断迭代,最终得到具有语义信息的词向量。

2.2.3 词向量的训练和使用
词向量训练和加载都可以借助 Gensim 或者 FastText来完成。
pip install gensim 或者
pip install fasttext。
Gensim 基本操作:
- 加载模型:中文词向量
1 | |
- 训练模型:
1 | |
- 保存模型:
1 | |
- 查看词向量的维度:
model.vector_size - Gensim 4.0 之后,将模型和词向量分离,需要使用
wv(word vector)模块,下面所有操作的model都要改为model.wv - 获取词向量:
model['中国'] - 计算相似度:
model.similarity('中国', '美国') - 查找最相似的词:
model.most_similar('中国', topn=10) - 获取词到索引映射:
model.index_to_key,返回列表 - 获取索引到词的映射:
model.key_to_index,返回字典
词的相似度使用余弦相似度计算,越接近 1 语义越接近,越接近 0 越不相关,越接近 -1 越相反,极度不相似。
$$\operatorname{similarity}\left(w_{1}, w_{2}\right)=\cos (\theta)=\frac{\overrightarrow{w_{1}} \cdot \overrightarrow{w_{2}}}{\left\|\overrightarrow{w_{1}}\right\| \cdot\left\|\overrightarrow{w_{2}}\right\|}$$
Fasttext 基本操作:
- 加载模型:
model = fasttext.load_model('model.bin') - 训练模型:
1 | |
- 保存模型:
model.save_model('model.bin') - 获取词向量:
model.get_word_vector('中国') - 查找最相似的词:
model.get_nearest_neighbors('中国')
2.2.4 应用 Word2Vec
在后续训练或预测过程中,模型会首先对输入文本进行分词,再通过词表将每个 token 映射为其对应的 ID,这些 ID 会被输入嵌入层(Embedding Layer),转换为低维稠密的词向量表示。

大多数 NLP 模型中的第一层都是嵌入层,本质上就是一个词向量查找表(lookup table),输入词在词汇表中的索引,输出对应的词向量表示。
嵌入层的参数矩阵有两种常见初始化方式:
- 随机初始化:随机生成一个矩阵,矩阵的行数等于词汇表大小,列数等于词向量的维度。
1 | |
torch.Size([8, 5])
- 预训练初始化:从预训练的词向量文件中加载矩阵。
1 | |
tensor([[-0.0681, -0.0189, 0.1154, -0.1504, -0.0787],
[ 0.1462, 0.1014, 0.1352, 0.0153, 0.1270]])
自然:[-0.06810732 -0.01892803 0.11537147 -0.15043275 -0.07872207]
语言:[0.14623532 0.10140524 0.13515386 0.01525731 0.12701781]
2.3 上下文相关词表示
虽然像 Word2Vec 这样的模型已经能够为词语提供具有语义的向量表示,但是它只为每个词分配一个固定的向量表示,不论它在句中出现的语境如何,这种表示被称为静态词向量(static embeddings)。
然而一个词的语义会受到上下文的语境所影响,比如“吃苹果”和“苹果发布新产品”。
上下文相关词表示(Contextual Word Representations),是指词向量会根据所在句子上下文动态变化,从而更好地捕捉其语义。代表性的模型是——ELMo (Embeddings from Language Models),其基于 LSTM 语言模型,使用上下文动态生成每个词的表示。
3 文本特征处理
3.1 N-gram
在先前的词袋模型中,通过统计词频表示文本,虽然简单,但完全忽略了词之间的顺序。90 年代,统计方法成为主流,为了解决这个问题,当时引入了 N-gram 模型,将相邻的 N 个词组合在一起,就可以保留一部分词序信息。
N-gram 是一种基于统计的方法,用于预测给定文本序列中下一个词出现的概率。它基于马尔可夫假设,核心思想是下一个词的出现的概率只依赖于它前面的 N-1 个词。
- Bigram(2-gram):每个词只与它前面的一个词有关
- Trigram(3-gram):只考虑它前面的两个词。
N-gram 特征是指从文本中提取出的 n-gram 形式的特征,通常用于机器学习模型的输入。
1 | |
2-gram:{('爱', '自然'), ('自然', '语言'), ('我', '爱'), ('语言', '处理')}
3-gram:{('我', '爱', '自然'), ('爱', '自然', '语言'), ('自然', '语言', '处理')}
3.2 文本长度规范
一般模型的输入需要等尺寸大小的矩阵,因此要对每条文本数值映射后的长度进行规范。根据句子长度进行分析,得到覆盖绝大多数文本的合理长度,对超长文本进行截断,对不足文本进行补齐(一般使用数字0)。
由于 DataLoader 的一批次当中,每个样本的长度可能不同,所以需要使用
pad_sequence 函数将句子长度填充到相同长度。
1 | |
input: tensor([ 3, 5, 7, 9, 10, 11]), target: 1
input: tensor([ 4, 6, 8, 10, 12, 13, 15]), target: 1
input: tensor([ 4, 6, 8, 10, 12, 14, 16, 18, 20]), target: 0
input: tensor([ 5, 7, 9, 11]), target: 1
input: tensor([ 6, 8, 10, 12, 14, 16]), target: 0
input: tensor([ 7, 9, 11, 13, 15, 17, 19]), target: 1
DataLoader 有一个 collate_fn
参数,可以自定义数据处理方法,这个函数的输入是一个 batch
的数据,返回值是处理后的 batch 数据。
1 | |
tensor([[ 4, 6, 8, 10, 12, 13, 15, 0, 0],
[ 4, 6, 8, 10, 12, 14, 16, 18, 20],
[ 5, 7, 9, 11, 0, 0, 0, 0, 0]])
(tensor(1), tensor(0), tensor(1))
[7, 9, 4]
tensor([[ 7, 9, 11, 13, 15, 17, 19],
[ 6, 8, 10, 12, 14, 16, 0],
[ 3, 5, 7, 9, 10, 11, 0]])
(tensor(1), tensor(0), tensor(1))
[7, 6, 6]
一般 <PAD> 的索引为 0,设置填充之后,在
embedding 中冻结这个权重,这样 <PAD>
的向量就固定了,不会被训练。
1 | |
Embedding(10, 5, padding_idx=0)
填充PAD之后,由于最后一个时间步可能不是真实数据,在模型的
forward 函数中,需要截取掉多余的填充数据。
1 | |
在计算损失的时候,如果算入<PAD>,会引入没有意义的评估标准,使损失值偏大,可以设置
ignore_index 来忽略掉该索引对应的损失值。
1 | |
为了方便演示,这里都是直接使用 0 作为 pad_token_id
的,实际应该使用 tokenizer.pad_token_id 动态获取。
4 文本数据增强
回译(Back Translation)数据增强是一种常见的自然语言处理技术,主要用于提升模型的训练效果,尤其在训练数据稀缺的情况下。它的基本思想是通过将原始文本翻译成另一种语言,然后再将翻译后的文本翻译回原始语言,生成新的训练样本。这种方式可以有效地提高数据的多样性,同时保持原始信息的语义。