2、词嵌入与文本表示:One-hot编码、Word2Vec原理、GloVe与FastText、TensorFlow实现词嵌入层
好,咱们进入第二章。说实话,词嵌入这块内容,是我在NLP实战中觉得最「有意思」的部分之一。为什么?因为你要让计算机理解「苹果」和「香蕉」是相似的,而「苹果」和「汽车」不相似——这本身就是个很神奇的过程。
我个人习惯把文本表示分成两个时代:一个是「原始人时代」,一个是「现代文明时代」。咱们先从原始人时代讲起。
2.1 One-hot编码:最朴素的表示法
One-hot编码,说白了就是给每个词发一个「身份证号」。假设你的词汇表里有10000个词,那每个词就是一个10000维的向量,其中只有一位是1,其余全是0。
举个例子:
词汇表 = ["我", "爱", "自然", "语言", "处理"]
"我" → [1, 0, 0, 0, 0]
"爱" → [0, 1, 0, 0, 0]
"自然" → [0, 0, 1, 0, 0]
嗯,这里要注意一个问题。One-hot有两个致命缺陷:
- 维度灾难:词汇量一大,向量维度就爆炸。我见过有人用50万词汇量的One-hot,那训练起来简直要命。
- 语义孤岛:任意两个词之间的余弦相似度都是0。也就是说,「猫」和「狗」在计算机眼里毫无关系——这显然不合理。
2.2 Word2Vec:让词学会「物以类聚」
Word2Vec的出现,可以说是NLP领域的一次「工业革命」。它的核心思想其实很简单:一个词的含义,由它周围的词决定。你想想看,如果「苹果」经常和「吃」、「水果」、「甜」一起出现,那它大概率是一种水果。如果它和「手机」、「系统」、「应用商店」一起出现,那它大概率是那个科技公司。
Word2Vec有两种训练模式:
| 模式 | 核心思路 | 典型场景 |
|---|---|---|
| CBOW(连续词袋) | 用上下文词预测当前词 | 小数据集、高频词效果好 |
| Skip-gram | 用当前词预测上下文词 | 大数据集、低频词效果好 |
我个人习惯在小数据集上用CBOW,因为训练速度快。但如果数据量够大,Skip-gram的效果往往更好——它能更好地捕捉到低频词的语义。
训练完成后,每个词就被映射到一个稠密向量(比如300维)。这时候,「猫」和「狗」的向量距离就很近了。更神奇的是,Word2Vec还能做类比推理:
vec("国王") - vec("男人") + vec("女人") ≈ vec("女王")
我第一次看到这个结果时,说实话挺震撼的。模型居然学会了「性别」这个抽象概念。
2.3 GloVe与FastText:站在巨人的肩膀上
Word2Vec虽然好,但它只利用了局部上下文信息。GloVe(Global Vectors)则更进一步,它同时考虑了全局的共现统计信息。
GloVe的核心思想是:词与词的共现概率比,能反映它们之间的语义关系。比如「冰」和「水」经常一起出现,但「冰」和「蒸汽」很少一起出现。这个比例关系,GloVe能很好地捕捉到。
而FastText,则是我在项目中经常用的一个工具。它解决了Word2Vec的一个痛点:未登录词问题。你想想看,如果测试集里出现了一个训练集没见过的词,Word2Vec就直接懵了。FastText的做法是:把词拆成字符级别的n-gram。
举个例子,「苹果」会被拆成:
"苹", "果", "苹果", "苹果" (假设n=2和3)
这样,即使遇到一个新词「苹狗」,模型也能通过「苹」和「狗」的子词信息,猜出它大概的意思。
2.4 TensorFlow实现词嵌入层
好了,理论讲完了,咱们来点实际的。在TensorFlow里实现词嵌入,其实就一行代码的事:
import tensorflow as tf
# 定义一个词嵌入层
embedding_layer = tf.keras.layers.Embedding(
input_dim=10000, # 词汇表大小
output_dim=128, # 嵌入维度
embeddings_initializer='uniform', # 初始化方式
input_length=50 # 输入序列长度
)
# 使用示例
input_sequence = tf.constant([[1, 2, 3, 4, 5]]) # 假设是词索引
output = embedding_layer(input_sequence)
print(output.shape) # (1, 5, 128)
嗯,这里要注意几个关键参数:
- input_dim:一定要比实际词汇量略大,留出padding和OOV(未登录词)的位置。
- output_dim:一般取50-300之间。太小了语义信息不够,太大了容易过拟合。
- embeddings_initializer:我习惯用'uniform',但如果你有预训练的词向量,可以用预训练权重初始化。
如果你想用预训练的词向量(比如GloVe或Word2Vec),可以这样加载:
import numpy as np
# 假设你已经有了一个词向量字典 word2vec
embedding_matrix = np.zeros((vocab_size, embedding_dim))
for word, i in word_index.items():
if word in word2vec:
embedding_matrix[i] = word2vec[word]
# 创建嵌入层并加载预训练权重
embedding_layer = tf.keras.layers.Embedding(
input_dim=vocab_size,
output_dim=embedding_dim,
weights=[embedding_matrix],
trainable=True # 是否微调
)
最后,我想分享一个我在项目中的经验。很多人以为词嵌入层只是把词变成向量就完事了,其实不然。词嵌入层的训练过程,本质上是在学习一个「语义空间」。在这个空间里,相似的词会聚在一起,不同的词会分开。所以,词嵌入层的质量,直接决定了你整个NLP模型的天花板。
我曾经在一个情感分析项目里,一开始用随机初始化的词嵌入,准确率只有82%。后来换成了预训练的GloVe词向量,准确率直接跳到89%。你看,这就是词嵌入的力量。
下一章,咱们会讲RNN和LSTM,到时候你会看到,词嵌入层和循环神经网络结合起来,能做出多么强大的文本模型。