- 教程
- 人工智能
- 机器学习
- 深度学习
【免费下载链接】AI-For-Beginners
12 Weeks, 24 Lessons, AI for All!
导读
本文围绕 AI-For-Beginners 课程第 5 模块 NLP 的第 14 课《Embeddings》展开,完整讲解如何用词嵌入(Embedding)层取代 one-hot 词袋表示,在 PyTorch 与 TensorFlow 两种框架下从零搭建基于"嵌入 + 聚合 + 线性分类"结构的新闻文本分类器,并进一步探讨如何将 Word2Vec、GloVe 等语义预训练向量接入分类网络。读完本文,你将掌握变长序列的 padding/offset 两种批处理方案、EmbeddingBag的用法、预训练词向量与自有词表的对齐方法,以及课程作业要求的"用自己的数据集重跑笔记、记录并解释实验结果"的完整实战路径。
本文主体对应课程作业文档 translations/bn/lessons/5-NLP/14-Embeddings/assignment.md(英文原版为 lessons/5-NLP/14-Embeddings/assignment.md),其要求是:使用本课配套笔记(PyTorch 或 TensorFlow 版本),用你自己的数据集重新运行,改写笔记并突出你的发现;可以尝试不同类型的数据集(例如使用 Beatles 歌词语料)并记录观察结果。
为什么需要 Embedding:从稀疏 one-hot 到稠密低维向量
本课 README.md 开篇回顾了前一课基于 BoW / TF-IDF 的分类器:它操作的是长度为vocab_size的高维词袋向量,本质上是把低维的位置表示显式转换成稀疏的 one-hot 表示。这种 one-hot 表示有两个明显问题:
- 内存不高效:词汇量达到数万时,每个样本都要维护一个超长稀疏向量;
- 词与词相互独立:one-hot 向量之间没有任何语义相似度信息,
cat和dog的向量距离与cat和apple完全一样。
Embedding 的核心思想是用低维稠密向量来表示词,并且让这个向量在一定程度上反映词的语义。嵌入层以"词编号"为输入,输出指定embedding_size维的向量。从实现上看,它非常像一个Linear层,只不过输入不再需要 one-hot 编码,直接给词编号即可,从而避免了构造巨大的 one-hot 向量。
把嵌入层作为分类网络的第一层后,模型就从"词袋(bag-of-words)"切换为embedding bag模型:先把文本中每个词转成对应嵌入向量,再对这些向量做某种聚合(sum、average或max),最后接线性分类器。课程自带的插图 embedding-classifier-example.png 直观展示了"五个词的序列 → 嵌入 → 聚合 → 分类"的完整数据流。
数据集与基础工具:AG_NEWS 与课程公用工具模块
两个配套笔记都在AG_NEWS数据集上继续前一课的实验。该数据集用于将新闻标题分类到 4 个类别:World(世界)、Sports(体育)、Business(商业)、Sci/Tech(科技)。
- PyTorch 版通过
torchtext.datasets.AG_NEWS(root='./data')加载; - TensorFlow 版通过
tensorflow_datasets的tfds.load('ag_news_subset')加载。
PyTorch 笔记还依赖课程提供的公共工具模块 torchnlp.py,其中包含四个关键函数,建议替换数据集时优先复用:
| 函数 | 作用 |
|---|---|
load_dataset(ngrams=1, min_freq=1) | 加载 AG_NEWS,用basic_englishtokenizer 统计词频并构建vocab,返回(train, test, classes, vocab);笔记输出显示词表规模约 95812 |
encode(x, voc=None, unk=0) | 把文本 token 映射为词表下标序列;voc传 None 用内置 vocab,传 GloVe 对象则用其stoi,缺失词回退到unk=0 |
train_epoch(...) | 面向"标签 + 特征"二元组的通用训练循环(用于padify数据流) |
padify / offsetify | 两种变长序列批处理 collate 函数(见下文) |
train_epoch_emb(...) | 面向"标签 + 文本 + offset"三元组的训练循环(用于EmbeddingBag数据流) |
设备选择统一为torch.device("cuda" if torch.cuda.is_available() else "cpu")。值得注意的细节是encode通过hasattr(v, 'get_stoi')同时兼容torchtext的vocab对象与 GloVe 对象,这正是替换数据集与词表时最容易被忽略的兼容性问题。
PyTorch 路线:从 Embedding + 聚合到 EmbeddingBag
3.1 最简单的嵌入分类器
课程 EmbeddingsPyTorch.ipynb 首先给出一个极简的嵌入分类器:Embedding → 沿序列维度取均值 → Linear。
class EmbedClassifier(torch.nn.Module): def __init__(self, vocab_size, embed_dim, num_class): super().__init__() self.embedding = torch.nn.Embedding(vocab_size, embed_dim) self.fc = torch.nn.Linear(embed_dim, num_class) def forward(self, x): x = self.embedding(x) x = torch.mean(x, dim=1) return self.fc(x)训练时使用torch.nn.CrossEntropyLoss与 Adam 优化器(lr=1),从笔记记录看仅训练 25k 条样本(不足一个完整 epoch)即可达到约 0.76 的准确率;笔记注释明确提示:继续训练多个 epoch 并调节学习率,有望把准确率提升到约 90%。
3.2 变长序列:padify 与 offset 两种方案
切换到词嵌入后,每个样本的序列长度不再固定。把样本凑成 minibatch 有两种做法:
方案 A:padding(padify)。按 minibatch 内最长序列补零对齐:
def padify(b): v = [encode(x[1]) for x in b] # b 为 (label, text) 元组列表 l = max(map(len, v)) # 当前 batch 内最大序列长度 return ( torch.LongTensor([t[0]-1 for t in b]), torch.stack([torch.nn.functional.pad(torch.tensor(t), (0, l-len(t)), mode='constant', value=0) for t in v]) ) train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=padify, shuffle=True)方案 B:offset 表示(offsetify)。把整个 batch 的所有序列拼成一个长向量,再用 offset 向量记录每条序列的起始位置,配合 PyTorch 的EmbeddingBag层使用。EmbeddingBag与Embedding类似,但输入是"内容向量 + offset 向量",内部自带mean/sum/max聚合层:
class EmbedClassifier(torch.nn.Module): def __init__(self, vocab_size, embed_dim, num_class): super().__init__() self.embedding = torch.nn.EmbeddingBag(vocab_size, embed_dim) self.fc = torch.nn.Linear(embed_dim, num_class) def forward(self, text, off): x = self.embedding(text, off) return self.fc(x)对应 collate 函数:
def offsetify(b): x = [torch.tensor(encode(t[1])) for t in b] o = [0] + [len(t) for t in x] o = torch.tensor(o[:-1]).cumsum(dim=0) return ( torch.LongTensor([t[0]-1 for t in b]), # labels torch.cat(x), # text(拼成一个大向量) o # offsets )由于网络现在接收"文本 + offset"两个不同大小的输入,数据加载器返回 3 个值,因此课程定义了适配的train_epoch_emb(torchnlp.py 中use_pack_sequence参数还兼容某些需要把 offset 放回 CPU 的场景)。笔记输出显示,EmbeddingBag版在同样 25k 样本下可达约 0.755 的准确率。
课程插图 offset-sequence-representation.png 用字符序列示意了 offset 表示的核心思想(本课实际处理的是词序列,但原理一致):所有序列共享一个内容张量,由 offset 边界切分。
TensorFlow 路线:TextVectorization + Embedding + 聚合
4.1 模型结构与参数量
EmbeddingsTF.ipynb 用 Keras 实现同样思路,但输入层换成TextVectorization(字符串 → token 编号张量):
vocab_size = 30000 batch_size = 128 vectorizer = keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size, input_shape=(1,)) model = keras.models.Sequential([ vectorizer, keras.layers.Embedding(vocab_size, 100), keras.layers.Lambda(lambda x: tf.reduce_mean(x, axis=1)), keras.layers.Dense(4, activation='softmax') ]) model.summary()四层职责明确:
TextVectorization:文本 →n个 token 编号(0 到vocab_size之间),低于词频阈值的词被忽略;Embedding(30000, 100):每个编号 → 100 维稠密向量,输出形状(None, None, 100);Lambda聚合:沿第 1 轴取平均,把n×100压成 100 维,代表整句;Dense(4, softmax):线性分类。
model.summary()显示 Embedding 层参数为3,000,000(vocab_size × 100),全模型约 3,000,404 个参数。Output Shape中第一个None是 batch 维,第二个None是 token 序列长度维——它随样本变化,这正是下文要处理的变长问题。
训练时先用子集适配向量器,再编译训练(损失sparse_categorical_crossentropy):
vectorizer.adapt(ds_train.take(500).map(extract_text)) model.compile(loss='sparse_categorical_crossentropy', metrics=['acc']) model.fit(ds_train.map(tupelize).batch(batch_size), validation_data=ds_test.map(tupelize).batch(batch_size))笔记记录的训练结果:训练 acc ≈ 0.8155、验证 acc ≈ 0.8642。需要注意,笔记仅用 500 条样本子集构建词表以加速,未收录的 token 会被忽略,可能轻微拉低准确率——实际生产中"用子集估计词表"通常是可接受的近似。
4.2 Keras 如何隐式处理 padding
与 PyTorch 显式提供padify不同,Keras 的TextVectorization在批量调用时自动把序列补成矩形张量:单条文本vectorizer('Hello, world!')返回shape=(2,),而两条一起调用时输出shape=(2, 6),短序列用PAD token(值为 0)填充。笔记还给出一个实用建议:为尽量减少 padding,可以先按 token 数对数据集排序,让每个 minibatch 内序列长度接近,从而减少无效计算。
Word2Vec 语义嵌入:CBoW、Skip-Gram 与向量算术
5.1 两种预训练架构
前面训练的嵌入层只是"降维查找表",向量没有语义。为了让近义词(如同义词)在欧氏距离等向量度量下彼此靠近,需要在大规模语料上以特定方式预训练。本课重点介绍 Word2Vec,其两种经典架构:
- Continuous Bag-of-Words(CBoW):用上下文预测当前词。给定 ngram $(W_{-2},W_{-1},W_0,W_1,W_2)$,模型用 $(W_{-2},W_{-1},W_1,W_2)$ 预测 $W_0$;
- Continuous Skip-Gram:与 CBoW 相反,用当前词预测窗口内的上下文词。
课程给出的经验结论:CBoW 更快,skip-gram 更慢但对低频词的表征更好。课程插图 example-algorithms-for-converting-words-to-vectors.png 对两者做了并排示意。
5.2 用 gensim 实操:相似词、词向量与类比推理
两个笔记都用gensim加载在 Google News 上预训练的word2vec-google-news-300(首次下载需等待较长时间):
import gensim.downloader as api w2v = api.load('word2vec-google-news-300')查看与neural最相似的词(两个笔记输出一致):
neuronal -> 0.7804799675941467 neurons -> 0.7326500415802002 neural_circuits -> 0.7252851724624634 neuron -> 0.7174385190010071 cortical -> 0.6941086649894714 ...提取单词向量(300 维,示例仅展示前 20 个分量):
w2v.word_vec('play')[:20] # PyTorch 笔记 w2v['play'][:20] # TF 笔记语义向量的经典性质是支持向量算术:king − man + woman的结果向量最接近queen。most_similar一行即可完成类比:
w2v.most_similar(positive=['king', 'woman'], negative=['man'])[0] # ('queen', 0.7118192911148071)TF 笔记进一步拆解了底层逻辑:先计算qvec = w2v['king'] - 1.7*w2v['man'] + 1.7*w2v['woman'],再对词表中所有向量求平方距离、用np.argmin找最近下标。笔记特别提示:给man/woman加的小系数(1.7)是实验调出来的,去掉系数看结果变化,本身就是很好的动手实验。
5.3 局限与替代方法:FastText 与 GloVe
- Word2Vec 的 CBoW / skip-gram 都是predictive(预测式)嵌入,只利用局部上下文,不利用全局共现信息;
- Word2Vec 不考虑词形(morphology),即词根等子词信息。
FastText在 Word2Vec 基础上,同时学习"整词 + 词内字符 n-gram"的向量,训练时聚合为一个向量;虽大幅增加预训练计算量,却让嵌入能编码子词信息。GloVe则走另一条路:先构造词-上下文共现矩阵,再用矩阵分解/降维方式以最小化重构损失的方式得到词向量。gensim 同时支持这几类模型,替换加载代码即可切换实验(下文会展示 GloVe 的具体接入)。
把预训练向量接进分类器:两条工程路径
预训练嵌入(Word2Vec、GloVe)可以替代分类网络中的嵌入层,但预训练词表与自有语料词表通常不一致,这是接入时必须处理的工程问题。课程给出两种解决思路:
- 在自有语料上重新训练 Word2Vec;
- 加载数据集时直接使用预训练词表(词表可在加载时指定)。
6.1 PyTorch:逐词填充权重矩阵
PyTorch 笔记先保持自有词表(约 95812 词),遍历vocab.get_itos(),命中预训练词的用w2v.get_vector(w)初始化,未命中的用正态随机初始化:
embed_size = len(w2v.get_vector('hello')) # 300 net = EmbedClassifier(vocab_size, embed_size, len(classes)) found, not_found = 0, 0 for i, w in enumerate(vocab.get_itos()): try: net.embedding.weight[i].data = torch.tensor(w2v.get_vector(w)) found += 1 except: net.embedding.weight[i].data = torch.normal(0.0, 1.0, (embed_size,)) not_found += 1 print(f"Done, found {found} words, {not_found} words missing") # 笔记输出:41080 词命中,54732 词缺失由于嵌入维度从 32 升至 300,参数量与训练时间显著增加,笔记提示需要更多样本以防过拟合;实测约 0.762 的准确率与随机初始化相比提升有限,原因正是两个词表差异太大(近 5.5 万词缺失)。
6.2 PyTorch:用 torchtext 的 GloVe 词表
更省事的路径是直接用 torchtext 内置的 GloVe 词表,让"编码、嵌入、分类"共用同一套词表:
vocab = torchtext.vocab.GloVe(name='6B', dim=50)该对象提供三类基础操作:
vocab.stoi:词 → 下标;vocab.itos:下标 → 词;vocab.vectors:嵌入矩阵,词s的向量即vocab.vectors[vocab.stoi[s]]。
权重装载因此变成一行拷贝:
net = EmbedClassifier(len(vocab), len(vocab.vectors[0]), len(classes)) net.embedding.weight.data = vocab.vectors用 GloVe 词表重写offsetify(encode(t[1], voc=vocab)),训练后准确率约 0.757。笔记指出提升不明显的原因:数据集中的部分词不在 GloVe 词表中而被忽略,因此更彻底的做法是在自有数据集上训练专属嵌入。用 GloVe 词表还能复现"向量算术类比":
qvec = vocab.vectors[vocab.stoi['king']] - vocab.vectors[vocab.stoi['man']] + 1.3*vocab.vectors[vocab.stoi['woman']] d = torch.sum((vocab.vectors - qvec)**2, dim=1) min_idx = torch.argmin(d) vocab.itos[min_idx] # 'queen'6.3 TensorFlow:tokenizer 词表 vs 嵌入词表
TF 笔记同样给出两条路径。
路径 A:用 tokenizer 词表。遍历vectorizer.get_vocabulary(),命中则写入w2v向量(缺失词可置零或随机,TF 笔记注释了随机初始化备选),然后构建不可训练的预训练嵌入层:
W = np.zeros((vocab_size, embed_size)) for i, w in enumerate(vocab): try: W[i] = w2v.get_vector(w) except: not_found += 1 emb = keras.layers.Embedding(vocab_size, embed_size, weights=[W], trainable=False) model = keras.models.Sequential([vectorizer, emb, keras.layers.Lambda(lambda x: tf.reduce_mean(x, axis=1)), keras.layers.Dense(4, activation='softmax')])笔记输出显示 30000 词表中 4551 词命中、784 词缺失;训练结果 acc ≈ 0.7822、val_acc ≈ 0.8175(低于随机初始化的 0.86,原因是冻结嵌入层 + 词表缺失)。trainable=False的取舍值得记录:冻结嵌入层会略降准确率,但显著加速训练。
路径 B:直接用嵌入词表。将TextVectorization的词表设置为 Word2Vec 的全部词汇,再用 gensim 的get_keras_embedding(train_embeddings=False)自动生成对应嵌入层:
vocab = list(w2v.vocab.keys()) vectorizer = keras.layers.experimental.preprocessing.TextVectorization(input_shape=(1,)) vectorizer.set_vocabulary(vocab) model = keras.models.Sequential([ vectorizer, w2v.get_keras_embedding(train_embeddings=False), keras.layers.Lambda(lambda x: tf.reduce_mean(x, axis=1)), keras.layers.Dense(4, activation='softmax') ])该方案训练 5 个 epoch 后 val_acc 约 0.61,明显偏低——因为 Word2Vec 词表面向通用新闻语料,AG_NEWS 中的领域词大量缺失(OOV 被忽略)。这一结果本身就是宝贵的实验记录:预训练词表与任务语料分布不匹配时,直接迁移的收益有限。
上下文嵌入与词义消歧
Word2Vec 这类传统预训练嵌入的核心局限是**词义消歧(word sense disambiguation)**问题:一个词的所有可能含义被压缩进同一个向量。以play为例:
- I went to aplayat the theatre.(名词:戏剧)
- John wants toplaywith his friends.(动词:玩耍)
预训练嵌入把这两个义项编码成同一个向量,给下游模型带来困扰。克服方法是基于**语言模型(language model)**构建嵌入——语言模型在大规模语料上训练,"知道"词在不同上下文中的组合方式,从而给出上下文相关的表示。本课明确说明上下文嵌入超出本课范围,将在课程后续语言模型章节(第 15 课及以后)深入展开。
课程作业完整实操方案:用自己的数据集重跑
结合本课作业 assignment.md 的要求,给出可直接执行的实验路径:
步骤 1:选择数据集
- 最简单:沿用 AG_NEWS,改动模型超参(
embed_dim、lr、epoch_size、batch_size),观察并记录准确率曲线; - 进阶:换不同数据类型的语料,例如 Beatle 歌词语料(可从 Kaggle 等公开平台获取,需按数据来源要求注明出处与致谢)。歌词属于短文本、词汇重复度高、与新闻标题的分布差异明显,非常适合对比"嵌入层学到什么"。
步骤 2:替换数据加载
- PyTorch:在 EmbeddingsPyTorch.ipynb 中把
load_dataset()换成自己的数据集读取逻辑,但继续复用 torchnlp.py 的encode / padify / offsetify / train_epoch_emb,只需保证每个样本是(label, text)二元组; - TensorFlow:在 EmbeddingsTF.ipynb 中把
tfds.load('ag_news_subset')换成自有数据集的tf.data.Dataset,并让extract_text返回拼接后的文本、tupelize返回(text, label)。
步骤 3:记录并解释发现(作业的评分核心)
建议至少记录以下对照项:
- 随机嵌入 vs 预训练嵌入:在同一数据集上分别跑 3.1 节与 6.x 节模型,比较准确率与训练时间(注意:维度从 32→300 会显著增加训练耗时);
- padify vs offsetify:比较两种变长序列方案的训练速度与准确率;
- 词表匹配度:打印
found / not_found,解释词表差异对结果的影响(课程在两套框架下都观察到"缺失词拖累准确率"); - 语义检查:用
most_similar('play')观察你的语料下play的最近邻,对比"名词义项 vs 动词义项",体会词义消歧问题。
步骤 4:改写笔记、突出个人观察
在保留原笔记结构的基础上,把你的数据集、超参与新增分析以 Markdown 注释的形式写入 notebook 单元格,把"结果为何如此"的分析写清楚——这正是作业要求的"underline your own findings"。
学习路径衔接与自测
本课位于课程第 5 模块 NLP 中段:前一课 13-TextRep 建立了 BoW / TF-IDF 文本表示基础(同用 AG_NEWS 数据集,配套 TextRepresentationPyTorch.ipynb),本课把表示升级为稠密嵌入,而上下文嵌入与语言模型将在后续课程展开。建议完成后通过课程测验(lesson 自带 quiz 入口见 README.md)自检,并延伸阅读 Word2Vec 原始论文《Efficient Estimation of Word Representations in Vector Space》以加深理解。
小结
- 嵌入层是词袋向量的低维稠密替代品,
Embedding / EmbeddingBag / Keras Embedding只是同一思想在不同框架下的落地; - 变长序列有 padding(
padify+ 均值聚合)与 offset(offsetify+EmbeddingBag)两种工程方案,Keras 的TextVectorization则自动隐式补零; - 语义嵌入(Word2Vec CBoW/skip-gram、FastText、GloVe)支持
king − man + woman ≈ queen这类向量算术,但都是静态向量,无法消歧一词多义; - 接入预训练向量的关键是处理词表差异:逐词填充(缺失随机初始化)或直接用预训练词表加载数据,并记录
found / not_found指标; - 作业落地的核心是"换数据集 → 重跑 → 记录 → 解释",把观察写成可复现的 notebook,即完成本课要求。
- 教程
- 人工智能
- 机器学习
- 深度学习
【免费下载链接】AI-For-Beginners
12 Weeks, 24 Lessons, AI for All!
相关推荐
AI for Beginners 第 14 课:词嵌入(Embeddings)——从 BoW 到 Word2Vec 与预训练向量的完整实战指南
AI for Beginners 第 14 课:词嵌入(Embeddings)——从 BoW 到 Word2Vec 与预训练向量的完整实战指南 本课程属于 AI
教程人工智能机器学习深度学习AI-For-Beginners 教程第 14 课实战:词嵌入(Embeddings)与 Word2Vec 语义向量全解析
AI For Beginners 教程第 14 课实战:词嵌入(Embeddings)与 Word2Vec 语义向量全解析 本课是 AI For Beginne
教程人工智能机器学习深度学习AI-For-Beginners 词嵌入(Embeddings)实战指南:从 Embedding 层到 Word2Vec 与预训练向量
AI For Beginners 词嵌入(Embeddings)实战指南:从 Embedding 层到 Word2Vec 与预训练向量 在自然语言处理中,词嵌入
教程人工智能机器学习深度学习
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考