词向量模型详解:让机器理解词语的奥秘

导读:为什么"国王"减去"男人"加上"女人"等于"女王"?词向量如何让计算机理解词语的语义?本文将带你深入探索词向量模型的世界。


一、从One-Hot到Dense:词表示的革命

1.1 传统方法的困境

在深度学习时代之前,我们如何表示一个词?最常见的方法是One-Hot编码

# 假设词典:["apple", "banana", "orange", "grape"]
"apple"[1, 0, 0, 0]
"banana"[0, 1, 0, 0]
"orange"[0, 0, 1, 0]
"grape"[0, 0, 0, 1]

问题显而易见

  • 维度灾难:词典多大,向量就多长(动辄数万维)
  • 语义鸿沟:任意两个词的内积都是0,无法衡量相似度
  • 数据稀疏:每个词都是孤岛,无法泛化

1.2 词向量的核心思想

分布假说(Distributional Hypothesis)“一个词的语义由其上下文决定” —— J.R. Firth, 1957

词向量(Word Embedding)将每个词映射到一个低维、稠密、连续的向量空间,让语义相似的词在空间中距离更近。

# 词向量示例(维度=300)
"king"[0.21, -0.32, 0.15, ..., 0.42]
"queen"[0.19, -0.30, 0.18, ..., 0.39]
"man"[0.25, -0.28, 0.12, ..., 0.45]
"woman"[0.23, -0.26, 0.14, ..., 0.41]

# 神奇的向量运算:king - man + woman ≈ queen

二、Word2Vec:词向量的开山之作

2013年,Google的Tomas Mikolov团队提出了Word2Vec,引爆了NLP领域。它包含两种训练架构:

2.1 CBOW(Continuous Bag-of-Words)

核心思想:用上下文预测中心词

上下文:[the, cat, on, mat] → 预测中心词:sat

训练样本:
输入:the, cat, on, mat (one-hot编码)
输出:sat (概率分布)

模型结构

输入层 → 投影层(求平均) → 输出层(Softmax)
  ↓           ↓                  ↓
上下文词    隐藏层向量      词汇表概率分布

适用场景:频繁词、训练速度快

2.2 Skip-Gram

核心思想:用中心词预测上下文

中心词:sat → 预测上下文:[the, cat, on, mat]

训练样本:
输入:sat
输出:the, cat, on, mat (多个预测任务)

模型结构

输入层 → 投影层 → 输出层(多个Softmax)
  ↓         ↓           ↓
中心词   词向量    每个上下文词的概率

适用场景:稀有词、小数据集、效果更好

2.3 关键技术:Hierarchical Softmax & Negative Sampling

原始Softmax计算复杂度为O(V),V是词典大小(可能数十万)。Word2Vec引入了两种优化:

Hierarchical Softmax(层次Softmax)

  • 使用Huffman树组织词汇表
  • 复杂度降至O(log V)
  • 对低频词效果更好

Negative Sampling(负采样)

  • 将多分类问题转化为多个二分类问题
  • 每次只更新正样本 + 少量负样本(通常5-20个)
  • 公式:
    J(θ)=log⁡σ(woTwc)+∑i=1kEwi∼Pn[log⁡σ(−wiTwc)]J(\theta) = \log \sigma(w_o^T w_c) + \sum_{i=1}^{k} \mathbb{E}_{w_i \sim P_n} [\log \sigma(-w_i^T w_c)]J(θ)=logσ(woTwc)+i=1kEwiPn[logσ(wiTwc)]

其中:

  • wcw_cwc:中心词向量
  • wow_owo:上下文词向量
  • kkk:负样本数量
  • PnP_nPn:噪声分布(通常是词频的3/4次方)

2.4 Word2Vec代码实战

from gensim.models import Word2Vec
from gensim.models.word2vec import LineSentence
import numpy as np

# 准备语料(分词后的句子列表)
sentences = [
    ["the", "cat", "sat", "on", "the", "mat"],
    ["the", "dog", "played", "in", "the", "garden"],
    ["cats", "and", "dogs", "are", "pets"]
]

# 训练Word2Vec模型
model = Word2Vec(
    sentences,
    vector_size=100,      # 向量维度
    window=5,             # 上下文窗口大小
    min_count=1,          # 忽略词频低于此值的词
    workers=4,            # 并行线程数
    sg=1,                 # 1=Skip-gram, 0=CBOW
    negative=5,           # 负采样数量
    epochs=10             # 训练轮数
)

# 查看词向量
print("cat的词向量:", model.wv['cat'])
print("向量维度:", len(model.wv['cat']))

# 查找相似词
print("\n与'cat'最相似的词:")
for word, similarity in model.wv.most_similar('cat', topn=5):
    print(f"  {word}: {similarity:.4f}")

# 向量运算
print("\n向量运算示例:")
result = model.wv.most_similar(
    positive=['king', 'woman'], 
    negative=['man'], 
    topn=1
)
print(f"king - man + woman ≈ {result[0][0]} (相似度: {result[0][1]:.4f})")

# 计算相似度
print(f"\ncat和dog的相似度: {model.wv.similarity('cat', 'dog'):.4f}")
print(f"cat和apple的相似度: {model.wv.similarity('cat', 'apple'):.4f}")

# 保存和加载模型
model.save('word2vec.model')
# model = Word2Vec.load('word2vec.model')

三、GloVe:全局统计与局部上下文的完美结合

2014年,斯坦福大学的Jeffrey Pennington等人提出了GloVe(Global Vectors)

3.1 核心思想

Word2Vec只利用局部上下文信息,而GloVe结合了:

  • 全局矩阵分解(如LSA)的统计优势
  • 局部上下文窗口(如Word2Vec)的预测能力

3.2 共现矩阵

GloVe首先构建词-词共现矩阵 XXX,其中 XijX_{ij}Xij 表示词 jjj 出现在词 iii 上下文中的次数。

# 示例共现矩阵(简化版)
#        the  cat  sat  on  mat
# the     0    2    1    1    0
# cat     2    0    1    1    1
# sat     1    1    0    1    1
# on      1    1    1    0    1
# mat     0    1    1    1    0

3.3 损失函数

GloVe的目标是最小化以下损失:

J=∑i,j=1Vf(Xij)(wiTw~j+bi+b~j−log⁡Xij)2J = \sum_{i,j=1}^{V} f(X_{ij}) (w_i^T \tilde{w}_j + b_i + \tilde{b}_j - \log X_{ij})^2J=i,j=1Vf(Xij)(wiTw~j+bi+b~jlogXij)2

其中:

  • wiw_iwi:词 iii 的主向量
  • w~j\tilde{w}_jw~j:词 jjj 的上下文向量
  • bi,b~jb_i, \tilde{b}_jbi,b~j:偏置项
  • f(Xij)f(X_{ij})f(Xij):加权函数,防止过大共现次数主导训练

加权函数设计:
f(x)={(x/xmax)αif x<xmax1otherwisef(x) = \begin{cases} (x/x_{max})^\alpha & \text{if } x < x_{max} \\ 1 & \text{otherwise} \end{cases}f(x)={(x/xmax)α1if x<xmaxotherwise

通常 α=0.75,xmax=100\alpha = 0.75, x_{max} = 100α=0.75,xmax=100

3.4 GloVe代码实战

# 方法1:使用gensim(需要自己构建共现矩阵)
from gensim.scripts.glove2word2vec import glove2word2vec
from gensim.models import KeyedVectors

# 下载预训练的GloVe向量
# 从 https://nlp.stanford.edu/projects/glove/ 下载

# 转换GloVe格式为word2vec格式
glove2word2vec(
    glove_input_file='glove.6B.100d.txt',
    word2vec_output_file='glove.6B.100d.word2vec.txt'
)

# 加载模型
model = KeyedVectors.load_word2vec_format('glove.6B.100d.word2vec.txt')

# 方法2:使用torchvision(自动下载)
import torch
from torchtext.vocab import GloVe

# 加载预训练GloVe
glove = GloVe(name='6B', dim=100)

# 查询词向量
vector = glove['cat']
print(f"cat的向量形状: {vector.shape}")

# 查找最相似词
def find_similar_words(word, vocab, vectors, topk=5):
    idx = vocab.stoi.get(word)
    if idx is None:
        return []
    
    vector = vectors[idx]
    scores = torch.mm(vectors, vector.unsqueeze(1)).squeeze()
    top_indices = scores.argsort(descending=True)[:topk+1]
    
    return [(vocab.itos[i.item()], scores[i].item()) 
            for i in top_indices if vocab.itos[i.item()] != word]

similar = find_similar_words('cat', glove.vocab, glove.vectors)
print(f"\n与'cat'相似的词: {similar}")

四、FastText:处理未登录词的利器

2016年,Facebook AI Research提出了FastText,解决了Word2Vec和GloVe的一个致命缺陷:无法处理未登录词(OOV, Out-of-Vocabulary)

4.1 核心创新:子词(Subword)信息

FastText将每个词表示为字符n-gram的集合,加上特殊边界符号。

# 以"apple"为例,n=3
# 添加边界符号: <apple>
# 提取3-gram:
# [<ap, app, ppl, ple, le>, <ap, app, ppl, ple, le>]
# 实际包括: <ap, app, ppl, ple, le> + 完整词apple

# 词向量 = 所有n-gram向量的和
vector("apple") = Σ vector(n-gram_i)

4.2 优势

  1. 处理OOV词:即使词不在词典中,也能通过字符n-gram生成向量
  2. 捕捉形态学信息:相似拼写的词共享n-gram,向量相近
  3. 适合形态丰富的语言:如德语、俄语、土耳其语

4.3 FastText代码实战

from gensim.models import FastText

# 准备语料
sentences = [
    ["the", "cat", "sat", "on", "the", "mat"],
    ["the", "dog", "played", "in", "the", "garden"],
    ["cats", "and", "dogs", "are", "pets"]
]

# 训练FastText模型
model = FastText(
    sentences,
    vector_size=100,
    window=5,
    min_count=1,
    workers=4,
    sg=1,                 # 1=Skip-gram, 0=CBOW
    negative=5,
    epochs=10,
    min_n=3,              # 最小n-gram长度
    max_n=6               # 最大n-gram长度
)

# 获取词向量(包括OOV词)
print("cat的词向量:", model.wv['cat'])

# 关键:查询未登录词
oov_word = "kittens"  # 假设这个词不在训练集中
if oov_word in model.wv:
    print(f"\n{oov_word}的词向量:", model.wv[oov_word])
else:
    # FastText会为OOV词生成向量
    print(f"\n{oov_word}是未登录词,但FastText仍能生成向量")
    print(f"向量形状: {model.wv[oov_word].shape}")

# 查找相似词
print(f"\n与'dog'相似的词:")
for word, sim in model.wv.most_similar('dog', topn=5):
    print(f"  {word}: {sim:.4f}")

# 保存模型
model.save('fasttext.model')

五、词向量的可视化与评估

5.1 降维可视化

import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
import numpy as np

def visualize_word_embeddings(model, words, method='pca'):
    """可视化词向量"""
    # 获取词向量
    vectors = [model.wv[word] for word in words]
    vectors = np.array(vectors)
    
    # 降维到2D
    if method == 'pca':
        reducer = PCA(n_components=2)
    else:
        from sklearn.manifold import TSNE
        reducer = TSNE(n_components=2, random_state=42)
    
    vectors_2d = reducer.fit_transform(vectors)
    
    # 绘图
    plt.figure(figsize=(10, 8))
    plt.scatter(vectors_2d[:, 0], vectors_2d[:, 1])
    
    for i, word in enumerate(words):
        plt.annotate(word, (vectors_2d[i, 0], vectors_2d[i, 1]))
    
    plt.title(f'Word Embeddings Visualization ({method.upper()})')
    plt.grid(True, alpha=0.3)
    plt.show()

# 使用示例
words = ['king', 'queen', 'man', 'woman', 'prince', 'princess', 'boy', 'girl']
visualize_word_embeddings(model, words, method='pca')

5.2 内在评估(Intrinsic Evaluation)

# 1. 词相似度任务
from scipy.stats import spearmanr

def evaluate_word_similarity(model, test_data):
    """
    评估词相似度
    test_data: [(word1, word2, human_score), ...]
    """
    model_scores = []
    human_scores = []
    
    for word1, word2, human_score in test_data:
        if word1 in model.wv and word2 in model.wv:
            model_scores.append(model.wv.similarity(word1, word2))
            human_scores.append(human_score)
    
    correlation, _ = spearmanr(model_scores, human_scores)
    return correlation

# 2. 词类比任务
def evaluate_word_analogies(model, analogy_data):
    """
    评估词类比
    analogy_data: [(word1, word2, word3, expected_word4), ...]
    """
    correct = 0
    total = len(analogy_data)
    
    for word1, word2, word3, expected in analogy_data:
        result = model.wv.most_similar(
            positive=[word2, word3], 
            negative=[word1], 
            topn=1
        )
        if result[0][0] == expected:
            correct += 1
    
    return correct / total

# 示例测试数据
analogy_test = [
    ('king', 'queen', 'man', 'woman'),
    ('paris', 'france', 'tokyo', 'japan'),
    ('good', 'better', 'bad', 'worse')
]

accuracy = evaluate_word_analogies(model, analogy_test)
print(f"类比任务准确率: {accuracy:.2%}")

5.3 外在评估(Extrinsic Evaluation)

在实际NLP任务中评估词向量效果:

from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

def evaluate_on_downstream_task(model, texts, labels):
    """
    在下游任务(如文本分类)中评估词向量
    """
    # 将文本表示为词向量的平均
    def text_to_vector(text):
        words = text.lower().split()
        vectors = [model.wv[w] for w in words if w in model.wv]
        if not vectors:
            return np.zeros(model.vector_size)
        return np.mean(vectors, axis=0)
    
    X = np.array([text_to_vector(text) for text in texts])
    y = np.array(labels)
    
    # 划分训练测试集
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.2, random_state=42
    )
    
    # 训练分类器
    clf = LogisticRegression(max_iter=1000)
    clf.fit(X_train, y_train)
    
    # 评估
    y_pred = clf.predict(X_test)
    accuracy = accuracy_score(y_test, y_pred)
    
    return accuracy

# 示例
texts = ["I love this movie", "Terrible film", "Amazing story", "Boring plot"]
labels = [1, 0, 1, 0]  # 1=正面, 0=负面

accuracy = evaluate_on_downstream_task(model, texts, labels)
print(f"情感分类准确率: {accuracy:.2%}")

六、预训练词向量资源

6.1 常用预训练模型

模型 语言 维度 下载地址
Word2Vec (Google) 英文 300 https://code.google.com/archive/p/word2vec/
GloVe 英文 50-300 https://nlp.stanford.edu/projects/glove/
FastText 多语言 300 https://fasttext.cc/docs/en/pretrained-vectors.html
中文Word2Vec 中文 300 https://github.com/Embedding/Chinese-Word-Vectors

6.2 加载预训练模型

from gensim.models import KeyedVectors

# 加载Google Word2Vec(二进制格式)
# model = KeyedVectors.load_word2vec_format('GoogleNews-vectors-negative300.bin', binary=True)

# 加载GloVe(文本格式)
# model = KeyedVectors.load_word2vec_format('glove.6B.100d.txt', no_header=True)

# 加载FastText
from gensim.models import FastText
# model = FastText.load_fasttext_format('cc.en.300.bin')

七、词向量的局限性与演进

7.1 主要局限

  1. 一词多义问题

    # "bank"在"river bank"和"bank account"中意思不同
    # 但Word2Vec/GloVe只能给出一个向量
    
  2. 上下文无关

    • 无法根据句子语境调整词义
  3. 静态表示

    • 训练完成后向量固定,无法微调适应新任务

7.2 演进方向

静态词向量 → 动态词向量 → 预训练语言模型
(Word2Vec)   (ELMo)         (BERT, GPT)

ELMo(Embeddings from Language Models)

  • 使用双向LSTM
  • 根据上下文生成动态词向量
  • 同一词在不同语境有不同表示

BERT及Transformer模型

  • 基于Self-Attention机制
  • 深度双向上下文
  • 成为当前NLP的标准工具

八、实战建议与最佳实践

8.1 如何选择词向量模型?

场景 推荐模型 理由
快速原型、小数据集 FastText 训练快,处理OOV
通用NLP任务 GloVe/Word2Vec预训练 效果好,资源丰富
形态丰富语言 FastText 子词信息有用
需要上下文理解 BERT等 动态表示
资源受限环境 自己训练Word2Vec 控制维度,轻量化

8.2 训练技巧

# 1. 数据预处理
def preprocess_text(text):
    # 转小写
    text = text.lower()
    # 去除标点
    import re
    text = re.sub(r'[^\w\s]', '', text)
    # 分词
    return text.split()

# 2. 超参数调优建议
hyperparams = {
    'vector_size': [100, 200, 300],    # 维度:100-300通常足够
    'window': [3, 5, 10],              # 窗口:小窗口捕获语法,大窗口捕获语义
    'min_count': [1, 5, 10],           # 过滤低频词
    'negative': [5, 10, 15],           # 负样本数
    'epochs': [10, 20, 50],            # 训练轮数
    'learning_rate': [0.025, 0.05]     # 学习率
}

# 3. 增量训练
# 模型可以在新数据上继续训练
model.build_vocab(new_sentences, update=True)
model.train(new_sentences, total_examples=len(new_sentences), epochs=5)

8.3 常见问题解决

问题1:内存不足

# 使用迭代器而非加载全部数据到内存
def sentence_generator(file_path):
    with open(file_path, 'r') as f:
        for line in f:
            yield preprocess_text(line)

model = Word2Vec(vector_size=100, min_count=1)
model.build_vocab(sentence_generator('large_corpus.txt'))
model.train(sentence_generator('large_corpus.txt'), 
            total_examples=1000000, epochs=10)

问题2:训练太慢

# 使用多进程
model = Word2Vec(sentences, workers=8)  # 使用8个CPU核心

# 减少负样本数
model = Word2Vec(sentences, negative=5)  # 默认是5,可以减少

# 降低维度
model = Word2Vec(sentences, vector_size=100)  # 而非300

问题3:效果不佳

  • 增加训练数据量
  • 调整窗口大小(语义任务用大窗口,语法任务用小窗口)
  • 使用预训练向量初始化
  • 增加训练轮数

九、总结与展望

核心要点回顾

  1. 词向量的本质:将离散符号映射到连续向量空间,捕捉语义相似性
  2. Word2Vec:CBOW和Skip-gram两种架构,负采样加速训练
  3. GloVe:结合全局统计和局部上下文
  4. FastText:引入子词信息,解决OOV问题
  5. 评估方法:内在评估(相似度、类比)+ 外在评估(下游任务)

未来趋势

  1. 多模态词向量:结合文本、图像、音频
  2. 知识增强词向量:融入知识图谱信息
  3. 低资源语言:跨语言迁移学习
  4. 可解释性:理解词向量学到的语义

学习资源

  • 论文

    • Word2Vec: “Efficient Estimation of Word Representations in Vector Space”
    • GloVe: “GloVe: Global Vectors for Word Representation”
    • FastText: “Enriching Word Vectors with Subword Information”
  • 课程

    • Stanford CS224N: Natural Language Processing with Deep Learning
    • Coursera: Natural Language Processing Specialization
  • 工具

    • Gensim: https://radimrehurek.com/gensim/
    • spaCy: https://spacy.io/
    • Hugging Face: https://huggingface.co/

结语

词向量是NLP领域的基石技术,尽管BERT等预训练模型已经崛起,但理解词向量的原理仍然是每个NLP工程师的必修课。它们轻量、高效、可解释,在许多场景下仍然是首选方案。

希望这篇文章能帮助你深入理解词向量的奥秘!如果有任何问题,欢迎在评论区讨论。

Happy Embedding! 🚀


参考文献

  1. Mikolov, T., et al. (2013). Efficient Estimation of Word Representations in Vector Space. ICLR.
  2. Pennington, J., et al. (2014). GloVe: Global Vectors for Word Representation. EMNLP.
  3. Bojanowski, P., et al. (2017). Enriching Word Vectors with Subword Information. TACL.
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐