资讯详情

从编号到词向量:Embedding层的前向查表与反向训练

📅 2026/10/25 8:57:29 | 华诺云谱 👁 阅读
从编号到词向量:Embedding层的前向查表与反向训练
上一篇我们把一段文本切成了 token并且给每个 token 按词表顺序编了个号。那会儿我就在评论区看到一批人卡在同一步编号嘛整数喂给模型不就完事了但实际写代码的时候你会发现模型接受的根本不是整数本身而是先过一个叫 Embedding 的层出来后每个编号都成了一串浮点数——比如tensor([0.0245, -0.0193, 0.0832, ...])。这就很反直觉一个 3怎么突然就有意思了我第一次在调试大模型中间张量的时候也对着屏幕愣了半天。干过传统编程的人都知道id 就是 id数据库里主键是 3 不代表比 2 大、比 4 小。可到了深度学习里编号查完表之后拿出来的向量竟然能用来算苹果和香蕉的距离甚至能算词和词的类比关系。这篇文章就把这件事彻底讲透Embedding 到底做了一步什么操作编号为什么能变成词向量以及这个词向量在大模型里又是怎么被继续改造的。1. 先打破一个直觉编号进 Embedding 层本质是查表1.1 前向传播一个矩阵行抽取操作先看代码。PyTorch 里最典型的 Embedding 层长得这样import torch import torch.nn as nn # 词表大小 10000向量维度 64 emb nn.Embedding(num_embeddings10000, embedding_dim64) # 假设三个 token id ids torch.tensor([3, 17, 892]) vectors emb(ids) print(vectors.shape) # torch.Size([3, 64]) print(vectors)看到没输入是[3, 17, 892]这三个整数输出的形状是(3, 64)也就是每个编号各自拿到了一条 64 维的浮点向量。关键点来了这个操作的前向计算没有任何语义理解它就是一次查表。你可以把它完全等价成下面这行vectors emb.weight[ids]emb.weight的形状是(10000, 64)也就是一张有 10000 行的表每行是一个 64 维向量。ids里面的编号 3、17、892 就当成行号从表里把对应的行抽出来。所以严格讲编号本身并不是变成了词向量而是编号作为一个索引帮你从 Embedding 矩阵里把对应的那一行向量取了出来。这个类比就像你去学校档案室学生编号本身是 3 号、17 号这些数字没有任何性格、成绩、兴趣信息。但管理员拿着编号去柜子里抽档案拿到的那份档案才真正记录了这个人是什么样。Embedding 矩阵就是那个档案柜每个 token 编号背后挂着一份向量档案。1.2 真正改变向量语义的不是查表是反向传播如果只是查表那你可能马上要问表里存的那些浮点数从哪来一开始是什么答案是一开始是随机数。nn.Embedding(10000, 64)在你创建它的瞬间权重矩阵里的所有值都是从一个随机分布里采出来的噪声。你用随机初始化的权重去查表查出来的向量之间没有任何语义关系——你把苹果和火星的编号拿去查查到的那两行向量跟扔骰子没有区别。那后面为什么有语义了因为训练过程会反复修改这张表里每一行的数值。前向传播只是取反向传播才是改。每训练一步模型根据损失函数算出梯度然后沿着梯度方向微调整个 Embedding 矩阵里的参数。经过海量文本的反复更新苹果那一行的数值会慢慢变得跟香蕉水果等词的向量相近跟火星的向量相去甚远。这里有个初学者常犯的误区以为 Embedding 层本身带有什么 NLP 魔法。其实它就是一个普通的可学习参数矩阵跟全连接层的权重矩阵没有本质区别。它的语义完全是被训练任务逼出来的。2. 为什么不能直接拿编号当数字独热编码差在哪2.1 直接喂编号的致命问题既然编号是整数为什么不直接把3、17、892当成数值特征喂给模型这样连 Embedding 层都省了。问题出在数值的序和大小关系上。编号 3 和编号 4 在词表里相邻但它俩的语义可能毫无关系。比如一个按字母排序的词表里3 号是 about4 号是 above这俩语义接近还说得过去可如果 892 号是 apple、893 号是 abandon 呢中间没有任何几何意义。你要是直接把编号作为数值特征模型会忍不住学到数值越大代表某种信号越强这种关系。这是神经网络的天性它看到数字 100 和 200就会天然假设 200 的强度是 100 的两倍。但 token id 之间根本不存在这种线性关系。喂原始编号等于给模型喂了一堆有误导性的数值信号训练会很难收敛。2.2 分布式表示从工牌号到性格量表那不用编号用**独热编码one-hot encoding**呢这也是很多人第一个想到的办法。独热编码的做法是词表 10000 个词就用 10000 维的向量。每个词对应一个维度这个词出现时那个维度置 1其余全是 0。比如猫是 3 号向量就是[0, 0, 1, 0, ..., 0]。这个方案有两个硬伤。第一个硬伤是维度爆炸。词表越大向量就越长。现实中的大模型词表动辄几万甚至十几万 token每次都要操作这么稀疏的高维向量存储和计算都扛不住。第二个硬伤更致命任意两个不同的独热向量在空间里都是完全正交的余弦相似度永远是 0。猫和狗的向量没有一丝相似之处猫和火星的向量也没有模型完全没有办法从向量本身推断出任何语义亲疏关系。稠密 Embedding 用的则是另一套思路不搞一个词一个位置的开关式表示而是用一个固定长度的稠密实数向量来表示每个词每个维度上都有数值。你可以把每个维度理解为模型自己从数据里发现的某种抽象特征——虽然这个特征具体代表什么人很难说清但多个维度组合起来就能构成一个词的多维画像。打个比方独热编码像工牌号每个员工一个编号编号只回答你是谁分布式表示像一份性格量表每个员工在几个维度上打分比如外向程度、细心程度。有了量表就算你没见过这两个人也能算出来他们在性格空间里靠得近不近。2.3 独热编码和稠密向量的定量对比我把两种表示摆一起对比你感受会更直接对比项独热编码稠密 Embedding维度等于词表大小比如 50000自定常见 128~1024稀疏度极度稀疏99.99% 是 0稠密处处非零词间相似度任意两词相似度恒为 0余弦相似度可比较可学习性不可学习固定编码端到端训练更新存储成本词表 5 万就要 5 万维5 万 x 512 参数远小于前者说到底Embedding 就是用可学习的稠密低维实数向量替代不可学习的稀疏高维独热向量。它保留了每个词的独立身份又给了模型表达词与词之间关系的手段。这个思路在深度学习里叫分布式表示distributed representation——语义不再由单个维度独立承担而是分布在一组维度的组合模式上。3. 语义是训练打磨出来的梯度如何重塑向量空间3.1 语言学假设相似语境等于相似语义现在到了最核心的问题Embedding 矩阵里那一个个浮点数是怎么从随机噪声变成能承载语义的结构的这背后有一个语言学层面的假设圈内叫分布式假设distributional hypothesis一个词的意思由它经常出现的上下文决定。说得再直白点如果两个词经常出现在相同的语境里那它们的语义大概率是相近的。苹果和香蕉都会出现在我吃了一个__这个__很甜水果店里的__这样的上下文里所以它们应该是近义词或同类词。苹果和火星几乎不会共享上下文所以它们的距离应该很远。那怎么把这种直觉变成一个可计算的训练目标最经典的思路就是 word2vec 里的两个任务Skip-gram 和 CBOW。Skip-gram给你一个中心词让你预测它周围会出现哪些词。CBOW给你周围的上下文词让你预测中间那个词是谁。不管哪种本质上都是让模型学会一件事利用词之间的共现关系预测上下文。3.2 训练任务如何把相邻词的向量拉近以 Skip-gram 为例。假设语料里有一句话苹果 是 一种 非常 好吃 的 水果。我们取中心词好吃窗口大小设为 2那么模型的任务就是看到好吃的向量去预测它周围可能出现苹果一种非常的这些词的概率。这个预测过程不是直接拿好吃的向量去比而是把中心词向量跟上下文词的向量分别做点积点积结果经过 softmax 变成概率。模型希望真实上下文词的概率尽可能高于是损失函数会引导梯度更新——本质上是在做一件事把中心词的向量往真实上下文词向量的方向上推同时把那些没有出现的随机词向量往反方向推一点。所以如果一个词经常和另一堆词共同出现它们的向量就会被反复互相推近。训练几十万步之后苹果和香蕉因为上下文高度重叠向量空间里自然靠在一起苹果和火星因为上下文毫无交集被推得越来越远。这里我不想堆公式给你讲个直觉想象你在空旷操场上摆了 5 万张卡片每张卡片是一个词。有一个训练信号反复告诉你每次出现苹果时周围应该站着甜、水果、树、汁、红这些卡片每次看到这堆卡片时中心位置大概率是苹果。你不断根据这类信号挪动卡片位置最终所有卡片会自动聚类成水果类聚一堆、动物类聚一堆、动词类聚一堆的布局。你甚至不需要别人告诉你苹果是水果仅凭共现统计规律这个几何结构自己就冒出来了。这非常神奇但背后的机制并不神秘Embedding 向量是训练信号在参数空间里留下的痕迹。3.3 大模型依然沿用了这套底层逻辑说到大模型很多人会问GPT 这类模型又不专门训练 embedding那它的词向量靠谱吗靠谱。因为大模型的训练任务是预测下一个 token这个任务的底层逻辑跟 Skip-gram 是互通的。每次模型看到前文、要预测下一个 token 时它都在隐式地学习 token 之间的共现规律。所有 token 的 embedding 参数都参与了反向传播一样会被海量语料反复打磨。区别在于大模型的输入 Embedding 只是第一层后面还有几十层 Transformer 会继续改写这些向量。但作为起点的 Embedding 层本身早就在训练中形成了非常丰富的静态语义结构。你可以把它理解为大模型的 Embedding 层是一本静态词典Transformer 层则在这本词典的基础上根据当前上下文再做动态调节。4. 大模型里的 Embedding 远比查表复杂从静态向量到上下文表征4.1 编号对应的是 token 而不是词先把概念捋一下在大模型里你分配编号的对象严格来说不是词而是 token。token 可能是完整的词比如 apple可能是词根比如 un可能是标点甚至可能是单个字符。所以更准确的说法是大模型学到的叫token embedding不是传统意义上的 word embedding。这带来一个很实际的影响一个单词可能被拆成好几个 token每个 token 单独编号、单独有向量。比如 unbelievable 可能被切成 un、believ、able。那它的语义就不存在单一向量里而是由这几个 token 组合表达。这就解释了为什么大模型的词表里经常能看到奇怪的 ##able 这种条目——那是子词 token不是完整词。4.2 位置编码和 Transformer 改造编号过了 Embedding 层拿到初始向量之后接着还要做一件事加位置信息。如果没有位置编码苹果 吃 了 和 吃了 苹果 这两句的输入向量序列是完全一样的模型无法区分苹果在第一个位置还是第三个位置。所以 Transformer 架构会给每个位置的向量注入位置信息常见的有绝对位置编码、相对位置编码或者是旋转位置编码RoPE这类更现代的做法。做完这些之后向量序列才进入 Transformer 层。每一层里每个 token 的向量都会经过注意力机制去看序列里其他 token 的向量然后更新自己的值。注意这里有本质变化Embedding 层查出来的初始向量是固定的、与上下文无关的但经过 Transformer 之后每个 token 的最终向量是随着上下文动态变化的。NLP 界有个经典例子word2vec 学到的 bank 只有一个向量不管是河岸还是银行都用它但在 BERT 里bank 出现在 river bank 和 central bank 两条句子里时最终向量是完全不同的。这就是静态 embedding 和上下文 embeddingcontextualized embedding的区别。4.3 想检查理解程度该看哪一层如果你要排查大模型经常会遇到一个困惑到底该把哪一层的输出叫embedding我的经验是分场景要看基础的词义结构看输入 Embedding 层的输出就够了。它反映的是静态语义。要看模型在具体语境下的理解得看最后一层或倒数几层的输出。那里面包含了对整个句子的上下文建模结果。很多向量检索系统、RAG 应用里说的把文本编码成向量通常指的不是第一层 Embedding而是模型输出的语义向量比如 CLS 位置或最后一层的平均池化。两者差距很大别搞混。还有一个高维向量经常被误解的点768 维甚至 4096 维的向量并不是这个词有 768 种意思每维对应一种。真实情况是模型学出来的每个维度本身很难用人能理解的方式解释语义是散在整体方向上的。这就像你没办法单独指着红酒果香维度的某一个物理量说它代表什么但两个酒的多维轮廓一比较相似度就出来了。5. 自己动手三种方式验证 Embedding 里藏了什么理论讲再多不如自己上手看一眼。你不需要完整训练一个大模型随便拿一个预训练好的 embedding 文件或者训练一个小模型就能做这三件事。5.1 降维可视化看词聚类第一步把高维向量降维到二维平面肉眼观察词的分布。常用工具是 t-SNE 或 UMAP。import numpy as np from sklearn.manifold import TSNE import matplotlib.pyplot as plt # 假设 vectors 是词向量矩阵shape(N, dim) # 先抽一批高频词别全量跑 indices np.arange(0, 1000) embedding_subset vectors[indices] tsne TSNE(n_components2, random_state42, perplexity30) coords tsne.fit_transform(embedding_subset) for i, (x, y) in enumerate(coords): plt.scatter(x, y, s5) plt.annotate(vocab[indices[i]], (x, y), fontsize8) plt.show()跑完你会发现非常直观数字类聚在一边颜色类聚在一边食物类聚在一边。如果训练语料足够大、质量足够好甚至可以观察到男女性别词沿着同一方向分开排列。这个几何结构就是语义相似度的可视化证明。实操细节t-SNE 全量跑几万词会非常慢先随机抽 1000~2000 个词子集perplexity 一般取 15~50太小会显得聚成一团太大结构又会被打散。5.2 余弦相似度查最近邻可视化适合宏观观察最近邻检索则适合具体验证某个词的朋友圈。import numpy as np # vecs: (N, dim) 已归一化 # target_idx: 要查的词 query vecs[target_idx] sims vecs query # 如果向量都是 L2 归一化的点积等价余弦相似度 top_indices np.argsort(sims)[::-1][1:11] # 排除自己 for idx in top_indices: print(vocab[idx], sims[idx])我拿一个小语料训练过的 embedding 做过实验查 dog前几位往往是 cat, pet, puppy, leash查 beautiful出现的是 gorgeous, lovely, pretty, wonderful。这种结果真的会让人有种模型是不是懂点事的错觉。补充一个经验小语料几 MB 文本训练出来的 embedding 一般不太干净最近邻里会混入很多噪声。如果你看到 beautiful 的最近邻是 page 这种完全不搭边的词别怀疑代码就是训练数据太少、共现统计不够。5.3 词类比运算测试这是 word2vec 时期最有名的玩法vector(king) - vector(man) vector(woman) ≈ vector(queen)。原理不复杂因为训练好的向量空间里国王和王后的差异方向跟男人和女人的差异方向基本一致所以做一次向量加减法得到的结果应该落在queen附近。result_vec vectors[king_idx] - vectors[man_idx] vectors[woman_idx] # 然后拿 result_vec 和全词表做余弦相似度检索 # 如果一切正常排名第一的应该是 queen在大模型那种超大词表里这个测试有时候会失效因为 token 切分和上下文建模会改变很多细节。但在中小规模 embedding 上进行这个检验仍然是判断语义质量的好方法。如果连这类简单的类比结构都没学出来你基本可以断定语料量不够或者训练不充分。6. 实操中容易踩的六个 Embedding 坑最后说说我在实际项目里积累的一些教训。这些坑教科书上很少写但几乎每个人都会踩一遍。坑一embedding_dim 宁小勿大。向量维度不是越大越好。维度越高Embedding 层的参数量就越大词表大小 × 维度在小数据集上特别容易过拟合。通用经验中小语料用 128~256大规模预训练模型用 768 起步。你要是拿 20 万条数据去训一个维度 4096 的 embedding大概率训出一堆噪声。坑二不固定随机种子复现不出来。由于 Embedding 矩阵是随机初始化的哪怕你数据、代码完全一样不设随机种子跑两次最终向量也有差异。调参阶段务必设置random.seed、np.random.seed、torch.manual_seed有 GPU 还要额外设置torch.cuda.manual_seed_all。我见过有人两天前跑出一个很好的结果隔天换了台机器复现不出来最后发现是 embedding 初始化不同造成的。坑三微调时冻结还是更新 Embedding要想清楚。如果你用预训练 embedding 做下游任务、数据量又少建议冻结 Embedding 层省显存、防过拟合。如果任务领域跟预训练语料差异极大比如预训练语料是通用文本下游是一堆专业术语可以考虑解冻并微调让模型吸收领域信息。折中方案用较低的学习率微调 Embedding。坑四计算相似度前先归一化。高维向量用欧氏距离容易受向量模长影响而模型学习到的模长往往跟词频有关系——高频词模长偏大低频词偏小。比较语义相似度优先用余弦相似度计算前先对向量做 L2 归一化。做向量检索系统时同样建议统一归一化否则召回质量波动很大。坑五OOV词表外词的处理。模型训练时词表是封闭的推理时碰到完全没见过的新词分词器会切成更小的 token 或子词尽量绕开完全无向量可用的局面。如果你在维护自己的词表遇到真正的新 token常见做法是随机初始化一个向量或者取已有近义词向量的平均值做初始化。直接全零初始化要谨慎零向量在空间里会变成所有词的公共邻居检索时容易出奇怪结果。坑六输入 embedding 和输出层权重共享的问题。不少 Transformer 架构里输入 Embedding 矩阵和最后的输出 softmax 投影是同一个权重矩阵这叫 weight tying。好处是省参数量、训练更稳。但如果你手动微调模型改了输入 Embedding 的权重输出投影会同步变化反过来也一样。调试时别以为它俩是两个独立副本否则会白排查很久。写到这里Embedding 从编号到词向量的最后一层窗户纸也应该捅破了。它的本质就三句话前向是一次查表反向是不断改写表格内容表格里的每一行最终被训练成了语义空间里的一个坐标点。从我个人的实操体验来说做模型调试时间长了你会发现几乎所有跟语义沾边的问题——任务迁移、检索排序、模型泛化——追根溯源都会落到向量空间的结构上。所以这一步基础打牢后面玩大模型会顺很多。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑