☰
AI-For-Beginners 语言建模实战:无监督训练 CBoW 与 Skip-Gram 词向量
2026/10/4 9:23:07 网站建设 项目流程
  • 教程
  • 人工智能
  • 机器学习
  • 深度学习

【免费下载链接】AI-For-Beginners

12 Weeks, 24 Lessons, AI for All!

项目地址:https://gitcode.com/GitHub_Trending/ai/AI-For-Beginners
点击查看免费下载

本文是 AI-For-Beginners 课程 NLP 专题中「语言建模(Language Modeling / Sprachmodellierung)」一课的完整技术指南。你将理解为什么语义嵌入(Word2Vec、GloVe)本质上就是一种语言建模,掌握 N-Gram、连续词袋(CBoW)与 Skip-Gram 三种嵌入训练方法的核心思想,并基于仓库内的两个 Jupyter Notebook,用 TensorFlow 与 PyTorch 从零训练出属于你自己的 Word2Vec 词向量空间,最后通过近义词查询(close words)实验与 Skip-Gram 改造作业完成实战闭环。

语言建模:从「预测缺失词」到理解语言

在课程前文(14-Embeddings 一课)中我们已经知道,嵌入层(Embedding Layer)能把词映射为稠密低维向量,Word2Vec 与 GloVe 这类语义嵌入则进一步让语义相近的词在向量空间中彼此靠近。而它们本质上是走向**语言建模(Language Modeling)**的第一步——即创建某种能够理解(或表示)语言本质的模型。

语言建模的核心思想是在无标签数据集上进行无监督训练。这一点至关重要,因为:

  • 我们拥有海量未标注文本,几乎取之不尽;
  • 而有标签文本的数量,永远受限于我们愿意投入的标注人力成本。

最常见的做法是构建能够预测文本中缺失词的语言模型:只需要在文本中随机遮掉一个词,把它当作训练样本即可,数据标注成本几乎为零。这种「填空」式的自监督任务,正是词向量得以大规模训练的基础。

三种嵌入训练方法

训练嵌入有若干种思路,它们的共同点都是利用上下文共现关系学习词与词之间的关联:

方法训练目标数学描述
N-Gram 语言建模依据前 N 个词预测下一个 token用前 N 个 token 预测第 N+1 个 token
连续词袋(CBoW)依据上下文预测中间词在 token 序列 $W_{-N}, \dots, W_N$ 中预测中间 token $W_0$
Skip-Gram依据中间词预测一组相邻词从中间 token $W_0$ 预测邻域集合 ${W_{-N},\dots,W_{-1},W_1,\dots,W_N}$

两种 Word2Vec 架构各有取舍:课程文档明确指出CBoW 训练更快,而 Skip-Gram 较慢,但对低频词(infrequent words)的表示效果更好。理解这一权衡,是后续作业中选择方案时的关键依据。

✍️ 实战一:用 TensorFlow 训练 CBoW 模型

仓库中的 CBoW-TF.ipynb 完整展示了在 TensorFlow/Keras 中从零训练 CBoW 模型的流程,数据集选用 AG News(新闻分类数据集)。以下代码片段均取自该 Notebook 的实际实现。

1. 加载数据集与定义词表

from tensorflow import keras import tensorflow as tf import tensorflow_datasets as tfds import numpy as np ds_train, ds_test = tfds.load('ag_news_subset').values()

2. 构建 CBoW 模型

CBoW 的核心任务,是依据 $2N$ 个相邻词预测中间词。例如当 $N=1$ 时,从句子I like to train networks可以得到如下训练对(第一个词是输入,第二个词是要预测的目标):(like,I)、(I,like)、(to,like)、(like,to)、(train,to)、(to,train)、(networks,train)、(train,networks)。

模型架构非常简洁:

  • 输入词经过嵌入层——这个嵌入层本身就是要训练得到的 Word2Vec 词向量,因此单独定义为embedder变量;本示例嵌入维度取30(真实 Word2Vec 常为 300,可自行调大实验);
  • 嵌入向量再传入一个稠密层预测输出词,神经元数量为vocab_size。
vocab_size = 5000 vectorizer = keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size, input_shape=(1,)) embedder = keras.layers.Embedding(vocab_size, 30, input_length=1) model = keras.Sequential([ embedder, keras.layers.Dense(vocab_size, activation='softmax') ]) model.summary()

几点实现细节值得注意:

  • Keras 嵌入层能自动把数值输入转换为 one-hot 编码,无需手动 one-hot 编码输入词;input_length=1表示输入序列只含一个词(嵌入层通常设计用于更长序列);
  • 输出层配合sparse_categorical_crossentropy损失函数时,标签只需提供词的编号,同样无需 one-hot 编码;
  • 从model.summary()的输出可以看到参数量构成:Embedding 层(None, 1, 30)共 150,000 参数,Dense 层(None, 1, 5000)共 155,000 参数,总计 305,000 个可训练参数;
  • vocab_size设为 5000 是为了限制计算量;随后用vectorizer.adapt()从训练集前 500 条新闻中构建词表,并通过get_vocabulary()取出。

3. 生成 CBoW 训练样本

下面这个to_cbow函数是整条数据流水线的核心:给定一个句子(可以是词列表,也可以是已编码的向量/张量),它会按窗口大小生成「上下文词 → 中心词」的样本对:

def to_cbow(sent, window_size=2): res = [] for i, x in enumerate(sent): for j in range(max(0, i-window_size), min(i+window_size+1, len(sent))): if i != j: res.append([sent[j], x]) return res print(to_cbow(['I','like','to','train','networks'])) print(to_cbow(vectorizer('I like to train networks')))

运行输出既展示了原始词对,也展示了经由vectorizer编码后的整数编号对,证明该函数可以同时作用于「词」与「张量」两种输入形式。

4. 组装训练数据集并训练

先遍历前 10,000 条新闻(去掉该限制可训练出更好的嵌入,但等待时间更长),调用to_cbow生成样本对,再打包为 batch:

X = [] Y = [] for i, x in zip(range(10000), ds_train.map(extract_text).as_numpy_iterator()): for w1, w2 in to_cbow(vectorizer(x), window_size=1): X.append(tf.expand_dims(w1, 0)) Y.append(tf.expand_dims(w2, 0)) ds = tf.data.Dataset.from_tensor_slices((X, Y)).batch(256) model.compile(optimizer=keras.optimizers.SGD(lr=0.1), loss='sparse_categorical_crossentropy') model.fit(ds, epochs=200)
  • 训练使用SGD 优化器、学习率 0.1(也鼓励尝试 Adam 等其他优化器);
  • 每轮 2156 个 batch、batch size 256,共训练 200 个 epoch,可重复执行该单元格以继续降低损失;
  • Notebook 中的实际训练日志显示 loss 从首轮约 5.61 逐步降至第 200 轮约 5.02,损失持续收敛;
  • 注意该 Notebook 是在 Colab 环境(Python 3.8.12)中运行,代码中使用的keras.layers.experimental.preprocessing.TextVectorization与SGD(lr=...)属于较老版本的 Keras API,新版本中分别对应keras.layers.TextVectorization与SGD(learning_rate=...)。

5. 提取词向量并做近义词查询

训练完成后,embedder就是我们的 Word2Vec 嵌入。把词表中所有词的向量提取出来(需要 reshape 去掉多余的维度):

vectors = embedder(vectorizer(vocab)) vectors = tf.reshape(vectors, (-1, 30))

查看单词Paris被编码成的 30 维向量:

paris_vec = embedder(vectorizer('paris'))[0] print(paris_vec)

再实现一个近义词查询函数:计算输入词向量 $v$ 与词表中每个词向量 $w_i$ 的范数 $|w_i - v|$,排序后取前 n 个最接近的词:

def close_words(x, n=5): vec = embedder(vectorizer(x))[0] top5 = np.linalg.norm(vectors-vec, axis=1).argsort()[:n] return [ vocab[x] for x in top5 ]

Notebook 中的实际查询结果:

  • close_words('paris')→['paris', 'philippines', 'seoul', 'jakarta', 'zoo']
  • close_words('china')→['china', 'russia', 'pakistan', 'israel', 'turkey']
  • close_words('official')→['official', 'military', 'office', 'police', 'sources']

可以看到,即便只用 10,000 条新闻和 30 维嵌入训练 200 轮,模型也已经捕获了「国家名聚集」「官方/权威类词聚集」等明显的语义结构。

✍️ 实战二:用 PyTorch 训练 CBoW 模型

仓库中的 CBoW-PyTorch.ipynb 提供了同一实验的 PyTorch 版本,架构与数据处理思路一致,可对照学习两个框架的 API 差异。

1. 加载数据集与构建词表

import torch import torchtext import os import collections import builtins import random import numpy as np device = torch.device("cuda" if torch.cuda.is_available() else "cpu") def load_dataset(ngrams=1, min_freq=1, vocab_size=5000, lines_cnt=500): tokenizer = torchtext.data.utils.get_tokenizer('basic_english') test_dataset, train_dataset = torchtext.datasets.AG_NEWS(root='./data') train_dataset = list(train_dataset) test_dataset = list(test_dataset) classes = ['World', 'Sports', 'Business', 'Sci/Tech'] counter = collections.Counter() for i, (_, line) in enumerate(train_dataset): counter.update(torchtext.data.utils.ngrams_iterator(tokenizer(line), ngrams=ngrams)) if i == lines_cnt: break vocab = torchtext.vocab.Vocab(collections.Counter(dict(counter.most_common(vocab_size))), min_freq=min_freq) return train_dataset, test_dataset, classes, vocab, tokenizer

这里同样把vocab_size限制为 5000 以控制计算量,分词器为basic_english,词表基于出现频率最高的 token 构建。

2. 构建 CBoW 模型

PyTorch 版本将嵌入层与输出线性层封装在Sequential中,逻辑与 TensorFlow 版本完全对应:

vocab_size = len(vocab) embedder = torch.nn.Embedding(num_embeddings=vocab_size, embedding_dim=30) model = torch.nn.Sequential( embedder, torch.nn.Linear(in_features=30, out_features=vocab_size), )

print(model)输出Embedding(5002, 30)与Linear(in_features=30, out_features=5002)——这里的 5002 是 PyTorch 词表在 5000 之外自动加入特殊 token 后的实际大小。

3. 编码与训练数据准备

与 TensorFlow 版一样,先实现句子编码函数,再复用同一个to_cbow生成样本对:

def encode(x, vocabulary, tokenizer=tokenizer): return [vocabulary[s] for s in tokenizer(x)] X = [] Y = [] for i, x in zip(range(10000), train_dataset): for w1, w2 in to_cbow(encode(x[1], vocab), window_size=5): X.append(w1) Y.append(w2) X = torch.tensor(X) Y = torch.tensor(Y)

注意此处训练时使用的窗口大小是5(与 TensorFlow 版的window_size=1不同,属于实现上的差异,可以自行实验对比)。随后用自定义的SimpleIterableDataset封装并打乱数据,经DataLoader以 batch size 256 供训练使用。

4. 训练循环

def train_epoch(net, dataloader, lr=0.01, optimizer=None, loss_fn=torch.nn.CrossEntropyLoss(), epochs=None, report_freq=1): optimizer = optimizer or torch.optim.Adam(net.parameters(), lr=lr) loss_fn = loss_fn.to(device) net.train() for i in range(epochs): total_loss, j = 0, 0 for labels, features in dataloader: optimizer.zero_grad() features, labels = features.to(device), labels.to(device) out = net(features) loss = loss_fn(out, labels) loss.backward() optimizer.step() total_loss += loss j += 1 if i % report_freq == 0: print(f"Epoch: {i+1}: loss={total_loss.item()/j}") return total_loss.item()/j train_epoch(net=model, dataloader=dl, optimizer=torch.optim.SGD(model.parameters(), lr=0.1), loss_fn=torch.nn.CrossEntropyLoss(), epochs=10)
  • 损失函数使用CrossEntropyLoss,同样只需要词的编号作为标签;
  • 可选用SGD(lr=0.1)或默认的Adam(lr=0.01)优化器,先训练 10 个 epoch 起步;
  • 实际训练日志显示 loss 从第 1 轮约 5.66 稳步降至第 10 轮约 5.55。

5. 提取向量与近义词查询

vectors = torch.stack([embedder(torch.tensor(vocab[s])) for s in vocab.itos], 0) paris_vec = embedder(torch.tensor(vocab['paris'])) print(paris_vec) def close_words(x, n=5): vec = embedder(torch.tensor(vocab[x])) top5 = np.linalg.norm(vectors.detach().numpy() - vec.detach().numpy(), axis=1).argsort()[:n] return [ vocab.itos[x] for x in top5 ]

实际查询结果示例:

  • close_words('microsoft')→['microsoft', 'quoted', 'lp', 'rate', 'top']
  • close_words('basketball')→['basketball', 'lot', 'sinai', 'states', 'healthdaynews']
  • close_words('funds')→['funds', 'travel', 'sydney', 'japan', 'business']

与 TensorFlow 版本相同,PyTorch 版也观察到词向量在语义上出现了可解释的聚类现象。

结论:训练词嵌入并不复杂

通过上述两个 Notebook 可以看到:前一课中看似「魔法」般的词嵌入(14-Embeddings 课程),其训练过程实际上只需要「嵌入层 + 一个输出层」加上巧妙的 CBoW 样本构造即可完成。课程给出的结论是:训练词嵌入并不是一项复杂的任务,当面对领域特定文本时,我们完全有能力训练自己的词向量。

同时要记住一个关键局限:传统预训练嵌入(如 Word2Vec)是静态的——一个词的所有含义被编码进同一个向量,存在词义消歧(word sense disambiguation)问题。例如play在「I went to aplayat the theatre.」和「John wants toplaywith his friends.」中含义截然不同,却被表示为同一向量。解决之道是基于语言模型的上下文嵌入(contextual embeddings),课程将在后续(如 Transformer、大语言模型相关章节)中深入探讨。

🚀 实战作业:训练 Skip-Gram 模型

语言建模这一课配套的实验室作业(lab/README.md)要求你将 CBoW 代码改造为Skip-Gram 模型:

任务:训练一个带嵌入层的网络,使其能在 $N$ 个 token 宽的 Skip-Gram 窗口中预测相邻词。可以直接使用本课的 CBoW-TF.ipynb 代码并稍作修改——核心改动就是把样本对的构造逻辑从「上下文词 → 中心词」反转为「中心词 → 上下文词」。

数据集:可以使用任何一本书。Project Gutenberg 提供大量免费文本,例如 Lewis Carroll 的《爱丽丝漫游奇境记》;也可以用如下代码获取莎士比亚戏剧全集:

path_to_file = tf.keras.utils.get_file( 'shakespeare.txt', 'https://storage.googleapis.com/download.tensorflow.org/data/shakespeare.txt') text = open(path_to_file, 'rb').read().decode(encoding='utf-8')

进阶探索方向(作业原文建议):

  • 嵌入大小如何影响结果质量?
  • 不同的文本风格(如新闻 vs 文学作品)如何影响结果?
  • 选取若干差异明显的词及其同义词,取它们的向量表示,用 PCA 降至 2 维后绘制在二维平面上——观察是否存在可识别的模式?

运行环境与准备工作

本课属于 NLP 专题(lessons/5-NLP/README.md)的一部分。若使用本地 Python 环境运行,需按框架安装依赖:

# PyTorch pip install -r lessons/5-NLP/requirements-pytorch.txt # TensorFlow pip install -r lessons/5-NLP/requirements-tf.txt

另外,本节部分示例会训练较大的模型,课程文档给出如下 GPU 注意事项:

  • 建议在GPU 机器上运行 Notebook 以减少等待时间;
  • 训练大模型时可能耗尽 GPU 显存,可通过减小 minibatch size缓解;
  • 旧版 TensorFlow 在同一 Python 内核中训练多个模型时可能无法正确释放 GPU 显存,可配置按需增长的显存分配策略:
physical_devices = tf.config.list_physical_devices('GPU') if len(physical_devices)>0: tf.config.experimental.set_memory_growth(physical_devices[0], True)

复习与自学

课程文档推荐了以下自学习方向(均为业界公认的官方学习资源,可通过搜索引擎检索到):

  • PyTorch 官方的语言建模 / 词嵌入教程;
  • TensorFlow 官方的 Word2Vec 训练教程;
  • 使用gensim框架——仅需几行代码即可训练最常用的词嵌入(如 Word2Vec、FastText 等)。

结合本课源码,建议的完整学习路径是:先运行 CBoW-TF.ipynb 与 CBoW-PyTorch.ipynb 建立感性认识,再完成 Skip-Gram 改造作业(lab/README.md),最后用自己训练的向量做 PCA 可视化,验证「语义相近的词在向量空间中彼此靠近」这一核心性质——至此,你就完整掌握了语言建模与词向量训练的从理论到实战的闭环。

  • 教程
  • 人工智能
  • 机器学习
  • 深度学习

【免费下载链接】AI-For-Beginners

12 Weeks, 24 Lessons, AI for All!

项目地址:https://gitcode.com/GitHub_Trending/ai/AI-For-Beginners
点击查看免费下载

相关推荐

上一篇:SOFAJRaft嵌入式KV存储实现:从RheaKV到分布式锁的完整应用指南
下一篇:Laf 云数据库 database-ql 指南:MongoDB 文档模型、集合与数据类型全解析

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询