Word2vec实战:用Python从零实现Skip-Gram模型(附代码)

在自然语言处理领域,词向量技术早已成为基础但至关重要的存在。想象一下,当我们能够将词语转化为具有语义信息的数字向量,计算机就能像理解坐标一样理解词语之间的关系——"国王"减去"男人"加上"女人"约等于"女王",这种直观的向量运算正是词向量魅力的体现。本文将带您从零开始,用Python和NumPy实现经典的Skip-Gram模型,不仅理解其数学原理,更能亲手构建出具备实用价值的词向量系统。

1. 环境准备与数据预处理

1.1 基础工具配置

实现Skip-Gram模型需要以下Python库支持:

import numpy as np
import matplotlib.pyplot as plt
from collections import Counter
from sklearn.manifold import TSNE

建议使用Python 3.8+环境,主要依赖库版本要求:

  • NumPy ≥ 1.20
  • Matplotlib ≥ 3.4

1.2 文本预处理流程

原始文本需要经过标准化处理才能用于模型训练:

  1. 文本清洗:移除标点、特殊字符,统一为小写
  2. 分词处理:将句子拆分为单词序列
  3. 构建词汇表:统计词频,建立词到索引的映射
def preprocess_text(text):
    text = text.lower()
    text = ''.join([c for c in text if c.isalpha() or c == ' '])
    words = text.split()
    return words

1.3 词汇表构建技巧

高效的词汇表管理是模型成功的关键:

策略 说明 实现方法
词频过滤 去除低频噪声词 Counter(words).most_common(vocab_size)
特殊标记 处理未知词 添加<UNK> token
二次采样 平衡高频词影响 $P(w_i) = 1 - \sqrt{t/f(w_i)}$

提示:实际应用中,建议保留词频≥5的词语,词汇表大小通常控制在10,000-50,000之间

2. Skip-Gram模型架构解析

2.1 模型核心组件

Skip-Gram通过中心词预测上下文词,主要包含三个部分:

  1. 输入层:中心词的one-hot编码
  2. 投影层:词嵌入矩阵(核心参数)
  3. 输出层:上下文词概率分布
class SkipGram:
    def __init__(self, vocab_size, embedding_dim):
        self.W1 = np.random.randn(vocab_size, embedding_dim) * 0.01
        self.W2 = np.random.randn(embedding_dim, vocab_size) * 0.01

2.2 负采样优化

传统Softmax计算开销大,负采样通过对比学习提高效率:

  • 正样本:真实上下文词
  • 负样本:随机采样的非上下文词

负采样概率公式: $$ P(w_i) = \frac{f(w_i)^{3/4}}{\sum_j f(w_j)^{3/4}} $$

2.3 损失函数设计

采用负对数似然损失,结合负采样:

def negative_sampling_loss(target, context, negative_samples):
    # 正样本得分
    pos_score = np.dot(self.W1[target], self.W2[:, context])
    # 负样本得分
    neg_scores = np.dot(self.W1[target], self.W2[:, negative_samples])
    # 计算损失
    loss = -np.log(sigmoid(pos_score)) - np.sum(np.log(sigmoid(-neg_scores)))
    return loss

3. 模型训练实战

3.1 参数初始化策略

词向量矩阵初始化直接影响训练效果:

方法 优点 适用场景
随机小值 简单快速 小型词汇表
Xavier初始化 保持方差一致 深度模型
预训练初始化 加速收敛 迁移学习

推荐初始化方案:

embedding_dim = 300
W = np.random.uniform(-1, 1, (vocab_size, embedding_dim)) / embedding_dim

3.2 训练过程优化

实际训练时需要关注的几个关键点:

  1. 学习率调整:初始0.025,线性衰减至0.0001
  2. 批次生成:使用滑动窗口构建(center, context)对
  3. 梯度裁剪:防止梯度爆炸
def train_skipgram(corpus, vocab, embedding_dim=100, epochs=5):
    model = SkipGram(len(vocab), embedding_dim)
    for epoch in range(epochs):
        loss = 0
        for center, context in generate_batches(corpus):
            grad_W1, grad_W2 = compute_gradients(center, context)
            model.W1 -= learning_rate * grad_W1
            model.W2 -= learning_rate * grad_W2
            loss += calculate_loss(center, context)
        print(f"Epoch {epoch}, Loss: {loss/len(corpus)}")

3.3 训练监控技巧

有效监控训练过程的几种方法:

  • 损失曲线可视化:观察收敛趋势
  • 最近邻检查:定期查询词向量的最近邻
  • 类比任务评估:man→woman as king→?
def plot_learning_curve(loss_history):
    plt.plot(loss_history)
    plt.xlabel('Iteration')
    plt.ylabel('Loss')
    plt.title('Training Loss Curve')
    plt.show()

4. 词向量应用与可视化

4.1 相似度计算

词向量最直接的应用是计算词语相似度:

def cosine_similarity(vec1, vec2):
    return np.dot(vec1, vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2))

def most_similar(word, vocab, W, topn=5):
    idx = vocab[word]
    vec = W[idx]
    similarities = []
    for i, other_vec in enumerate(W):
        if i != idx:
            similarities.append((vocab.lookup_token(i), cosine_similarity(vec, other_vec)))
    return sorted(similarities, key=lambda x: -x[1])[:topn]

4.2 t-SNE降维可视化

高维词向量可通过t-SNE投影到2D空间:

def visualize_embeddings(vocab, W, num_words=100):
    tsne = TSNE(n_components=2, random_state=0)
    words = [vocab.lookup_token(i) for i in range(min(num_words, len(vocab)))]
    vectors = W[:num_words]
    Y = tsne.fit_transform(vectors)
    
    plt.figure(figsize=(12,8))
    for i, word in enumerate(words):
        plt.scatter(Y[i,0], Y[i,1])
        plt.annotate(word, xy=(Y[i,0], Y[i,1]), xytext=(5,2),
                     textcoords='offset points', ha='right', va='bottom')
    plt.show()

4.3 实际应用案例

训练好的词向量可用于多种下游任务:

  1. 文本分类:作为特征输入
  2. 推荐系统:物品描述的向量表示
  3. 问答系统:问题与答案的语义匹配
# 示例:基于词向量的简单文本分类
def document_vector(doc, vocab, W):
    vectors = [W[vocab[word]] for word in doc if word in vocab]
    return np.mean(vectors, axis=0) if vectors else np.zeros(W.shape[1])

5. 高级优化与技巧

5.1 动态上下文窗口

传统固定窗口的改进方案:

  • 自适应窗口大小:根据词频调整
  • 位置加权:距离中心词越近权重越高

实现示例:

def dynamic_window(center_idx, sentence, max_window=5):
    word_freq = get_word_frequencies()
    base_window = max_window // (1 + np.log(word_freq[center_idx]))
    return max(1, int(base_window))

5.2 子词信息融合

处理罕见词和形态学关系:

  • 字符n-gram:将单词拆分为子单元
  • FastText风格:用子词向量求和表示整词
def get_subword_vectors(word, subword_dict, W):
    subwords = [word[i:i+n] for n in (3,4,5) for i in range(len(word)-n+1)]
    return [W[subword_dict[sw]] for sw in subwords if sw in subword_dict]

5.3 多任务学习

联合优化Skip-Gram与其他相关任务:

任务类型 损失函数 共享参数
词性标注 交叉熵 词嵌入层
依存分析 边分类损失 投影矩阵
命名实体识别 CRF损失 上下文向量
class MultiTaskSkipGram(SkipGram):
    def __init__(self, vocab_size, embedding_dim, num_pos_tags):
        super().__init__(vocab_size, embedding_dim)
        self.pos_layer = np.random.randn(embedding_dim, num_pos_tags) * 0.01
        
    def pos_tagging_loss(self, word_idx, pos_tag):
        hidden = self.W1[word_idx]
        pos_scores = np.dot(hidden, self.pos_layer)
        return -np.log(softmax(pos_scores)[pos_tag])

6. 性能优化策略

6.1 并行化训练

加速大规模语料训练的几种方法:

  1. 数据并行:将语料分片多线程处理
  2. 模型并行:拆分参数矩阵到不同设备
  3. 异步更新:Hogwild!算法
from multiprocessing import Pool

def parallel_train(args):
    model, batch = args
    gradients = model.compute_gradients(batch)
    return gradients

with Pool(processes=4) as pool:
    results = pool.map(parallel_train, [(model, b) for b in batches])

6.2 内存优化

处理超大规模词汇表的技巧:

技术 节省内存 实现复杂度
哈希技巧
矩阵分块
量化压缩 极高
# 使用内存映射处理大矩阵
W = np.memmap('embeddings.bin', dtype='float32', 
              mode='w+', shape=(vocab_size, dim))

6.3 混合精度训练

利用现代GPU的FP16计算能力:

from torch.cuda.amp import autocast, GradScaler

scaler = GradScaler()

with autocast():
    loss = model(batch)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()

7. 模型评估与调优

7.1 内在评估方法

直接评估词向量质量的指标:

  1. 词语相似度:与人类评分相关性
  2. 类比任务:语义/句法关系准确率
  3. 聚类质量:ARI、NMI等指标
def evaluate_analogies(vocab, W, analogy_file):
    # 加载类比问题集
    analogies = load_analogies(analogy_file)
    correct = 0
    for a, b, c, d in analogies:
        vec = W[vocab[b]] - W[vocab[a]] + W[vocab[c]]
        closest = find_closest_word(vec, vocab, W)
        if closest == d:
            correct += 1
    return correct / len(analogies)

7.2 超参数搜索

关键超参数的影响及调优范围:

参数 典型值 影响
向量维度 100-500 表征能力
窗口大小 2-10 上下文范围
负采样数 5-20 训练效率
学习率 0.001-0.1 收敛速度
from sklearn.model_selection import ParameterGrid

param_grid = {
    'dim': [100, 200, 300],
    'window': [3, 5, 7],
    'negative': [5, 10, 15]
}

for params in ParameterGrid(param_grid):
    model = SkipGram(vocab_size, **params)
    score = evaluate(model)
    print(f"{params}: {score}")

7.3 错误分析与改进

常见问题及解决方案:

  1. 语义混淆:增大训练数据量
  2. 词频偏差:调整二次采样率
  3. 过拟合:增加负采样数量
  4. 欠拟合:提高向量维度
def error_analysis(model, vocab, W):
    # 找出相似度异常的词语对
    anomalies = []
    for word in vocab:
        similars = most_similar(word, vocab, W)
        if any(not is_semantically_related(word, sim) for sim in similars):
            anomalies.append((word, similars))
    return anomalies
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐