Embedding 到底要不要每次都做从分词到向量空间的完整指南面向读者正在学习 NLP、RAG 和大模型工程的开发者本文回答四个问题有没有像 ImageNet 一样别人已经把词向量做好了文本进入 NLP 模型时是先分词还是先 Embedding使用预训练向量后为什么很多场景仍然需要做 Embedding一个词的向量坐标究竟怎样一步步计算和训练出来先讲一个故事词语城的地图师很久以前有一座叫“词语城”的城市。城里住着许多词语居民“苹果”住在水果市场也可能出现在科技街英文单词 “bank” 既可能指存钱的地方也可能指河岸“喜欢”“检索”“模型”这些词彼此之间也有远近关系。有一天一个新词来到城门口。守门人不能直接把这个词交给计算机因为计算机看不懂“苹果”这两个字。于是城里的第一位工作人员给它发了一张号码牌苹果 → 314这张号码牌只是身份证编号不代表“苹果”比“香蕉”更大也不代表它们之间没有关系。接着地图师把这个词放到城市地图里水果市场里的苹果靠近梨、香蕉和水果科技街里的 Apple靠近手机、电脑和公司同一个词在不同句子里可能被地图师放到不同的位置。图 1先给词语发号码牌再把它放到语义地图中。这张图就是今天要讲的 Embedding 的直觉把文字放进一个可以计算距离的向量空间。不过地图师不是靠手工画地图。它会先读大量文本观察哪些词经常一起出现再通过训练逐渐调整每个词的坐标。读到足够多的例子后“猫”和“狗”通常会比“猫”和“数据库”更接近。故事里的“号码牌”和“地图坐标”对应两个不同步骤文字 → token 编号 → 向量坐标这也是初学者最容易混淆的地方token ID 只是编号Embedding 才是向量表示。先说结论有现成的预训练词向量和 Embedding 模型但它们不能让 Embedding 这一步永久消失。原因很简单“下载好的词向量”通常是一张可以查表的向量字典“Embedding”也可以指把一条新文本送进模型计算出它在向量空间中的位置大模型内部还自带一张Token Embedding 矩阵文本输入时必须先转成 token ID再从矩阵中取出向量RAG 中还要把新文档和用户的新问题编码到同一个向量空间否则无法进行向量检索。可以把它理解成别人已经帮你修好了“地图”和“坐标系”但你拿到一条新地址时仍然要把这条地址定位到地图上。Embedding 到底是什么计算机看到的不是“猫”“汽车”“喜欢”这些词而是数字。最简单的表示方法是给每个词分配一个整数猫 → 17 汽车 → 231 喜欢 → 904整数只是编号并没有表达语义。猫17并不代表猫比汽车更小也不代表猫和汽车没有关系。Embedding 的作用是把编号转换成一串连续数字猫 → [0.21, -0.77, 0.35, ...] 汽车 → [0.18, -0.12, 0.91, ...] 喜欢 → [-0.43, 0.66, 0.08, ...]这串数字就是向量。向量之间的距离或夹角可以用来近似表达语义相似度。图 2真实向量空间维度很高这里用二维投影帮助理解“距离”和“方向”。上图只画了二维真实模型通常是 384、768、1024 甚至更高维。二维图只是为了让人类看懂“相似的东西会靠近”这个直觉。一个关键误区Embedding 不是“把词翻译成一个固定的数学答案”。它更像是一个经过大量语料训练出来的坐标系统语义相近的词方向往往更接近常出现在相似上下文的词往往更接近向量空间里的方向可能对应某些语法或语义变化具体效果取决于训练语料、模型结构、训练目标和使用场景。Word2Vec 的论文提出了从大规模文本中学习连续词表示的方法GloVe 则利用全局词共现统计学习词向量。Word2Vec 论文 、Stanford GloVe 项目文本进入模型时先分词还是先 Embedding答案是先 Tokenize再把 token 转成 ID最后查 Embedding 矩阵。原始文本 ↓ 文本规范化 ↓ Tokenizer 分词 / 切分 token ↓ token 转整数 ID ↓ Embedding 矩阵查表 ↓ Transformer、RNN 或其他 NLP 模型图 3文本先经过 Tokenizer得到 token ID再查 Embedding 矩阵。“分词”不一定等于按词切分中文学习者很容易把 Tokenizer 理解成传统中文分词我喜欢自然语言处理 → 我 / 喜欢 / 自然语言 / 处理但现代大模型更多使用子词subword或字节级 token。常见算法包括BPEByte Pair EncodingWordPieceUnigramByte-level BPEHugging Face 文档明确区分了预分词、子词切分、token 到整数 ID 等步骤BPE、Unigram 和 WordPiece 都是常见的子词算法。Hugging Face Tokenization Algorithms因此一句话可能切成我喜欢RAG → [“我”, “喜欢”, “R”, “AG”]也可能切成我喜欢RAG → [“我”, “喜欢”, “RAG”]具体结果由模型自己的词表和 Tokenizer 决定。为什么不直接按“完整单词”切分因为完整词表会遇到三个问题新词很多词表会不断膨胀拼写变化、专业术语、代码、网址很难全部收录一个生僻词如果完全不在词表中就只能变成未知词。子词方法允许模型把生僻词拆成已知的小片段。例如英文中的unbelievable → un believe able这样即使模型没有见过完整单词也可能利用已经学过的子词来处理它。Embedding 矩阵到底是什么假设词表一共有 50,000 个 token每个 token 用 768 个数字表示那么模型里就有一张矩阵Embedding 矩阵形状 [50000, 768]可以把它想象成一个通讯录token ID对应向量0[0.12, -0.03, ...]1[-0.44, 0.81, ...]2[0.27, 0.19, ...]……49999[0.05, -0.66, ...]当 Tokenizer 输出input_ids[314,9821,441]模型会从矩阵中取出第 314、9821、441 行xembedding_table[input_ids]这一步本质上是查表不等于每次重新训练一个 Embedding 模型。这张矩阵从哪里来有两种情况情况 A训练自己的 NLP 模型一开始随机初始化 Embedding 矩阵然后在训练过程中不断更新预测错误 ↓ 计算损失 ↓ 反向传播 ↓ 更新 Embedding 矩阵你不需要手工为每个词填写向量。情况 B加载预训练模型直接加载别人训练好的 Tokenizer 和模型权重fromtransformersimportAutoTokenizer,AutoModel tokenizerAutoTokenizer.from_pretrained(your-model)modelAutoModel.from_pretrained(your-model)inputstokenizer(我喜欢 RAG,return_tensorspt)outputsmodel(**inputs)这时Tokenizer、Embedding 矩阵和后续模型参数已经一起发布了。你只是把新文本转成 token使用现成参数进行推理。Hugging Face Model Hub 提供了大量可下载的预训练模型权重、Tokenizer 和模型卡片。Hugging Face Model Hub手算一次一个词怎样学到向量坐标回到“词语城”的故事地图师究竟怎样调整“猫”的位置下面把“训练得到向量”拆成一次可以复算的数字计算。我们用经典的Word2Vec Skip-gram输入中心词预测附近出现的词。比如从“猫吃鱼”中在足够大的上下文窗口内取出一个训练样本中心词是“猫”目标邻近词是“鱼”。这里直接给出已经分词并提取好的样本不再展开分词算法。为了手算我们把演示模型的词表设为“猫、鱼、汽车”向量设为 2 维。下面的初始参数由我们选定用来模拟随机初始化后面的概率、梯度和更新结果都按照公式计算。这不是一个已经训练好的真实中文词向量模型。1. 先建词表再准备两张参数表词token ID猫0鱼1汽车2Skip-gram 训练时有两张参数表输入 Embedding 表E以及输出参数表U。每张表都是3 × 23 个词每个词 2 个数字。词输入表 E用作中心词时的向量输出表 U用作候选邻近词时的参数猫[0.20, 0.10][0.10, -0.20]鱼[-0.10, 0.30][0.40, 0.50]汽车[0.40, -0.20][-0.30, 0.20]同一个词在两张表中的数字可以不同。本例把输入表E中的行作为最后保存的词向量输出表U帮助完成预测任务。不同实现也可以对两张表的结果做进一步处理。图 4ID 用来找到输入表中的行输出参数表用来给候选词打分两者都在训练中学习。输入“猫”后先得到ID 0再取出输入表的第 0 行v E[0] [0.20, 0.10]如果用 one-hot 表示等价于[1, 0, 0] × E [0.20, 0.10]所以查表和 one-hot 矩阵乘法在这里得到相同结果。实际实现直接查表即可。2. 点积给每个候选邻近词打分预测时将“猫”的输入向量v与每个候选词的输出参数u做点积对应维度相乘再相加。猫的分数 [0.20, 0.10] · [0.10, -0.20] 0.20 × 0.10 0.10 × (-0.20) 0.00 鱼的分数 [0.20, 0.10] · [0.40, 0.50] 0.20 × 0.40 0.10 × 0.50 0.13 汽车分数 [0.20, 0.10] · [-0.30, 0.20] 0.20 × (-0.30) 0.10 × 0.20 -0.04得到三个预测分数z [0.00, 0.13, -0.04]。这些是预测邻近词的分数不是已经归一化的概率也不是“猫”和这些词的余弦相似度。3. Softmax把分数变成概率Softmax 先对分数取指数再除以所有指数的总和P(候选词 i | 猫) exp(z_i) / Σ exp(z_j) exp(0.00) 1.000000 exp(0.13) ≈ 1.138828 exp(-0.04) ≈ 0.960789 总和 ≈ 3.099618候选词计算预测概率猫1.000000 / 3.0996180.322620鱼1.138828 / 3.0996180.367409汽车0.960789 / 3.0996180.309970“鱼”的概率最高但只有约 36.74%。这道训练题的正确答案就是“鱼”因此我们要提高模型对它的预测概率。图 5沿箭头读一遍就能看到“猫”的初始向量怎样参与预测再根据误差得到一次更新后的向量。4. 交叉熵衡量预测有多差正确答案按“猫、鱼、汽车”的顺序写成 one-hot 标记y [0, 1, 0]对这个单样本交叉熵损失就是正确答案概率的负对数L -ln P(鱼 | 猫) -ln(0.367409) ≈ 1.001279模型给“鱼”的概率越大这个损失越小。下一步就是找到能让损失下降的参数调整方向。5. 反向传播梯度的两个数字究竟怎么算这里用到 Softmax 与交叉熵组合后的一个结果损失对每个分数的导数等于预测概率 - 正确答案标记即p - y。猫0.322620 - 0 0.322620 鱼0.367409 - 1 -0.632591 汽车0.309970 - 0 0.309970这些数字先描述分数应该如何调整。要继续算“猫”的输入向量v就把每个误差乘以对应的输出参数向量然后相加。因为分数是z_i v · u_i它对v的导数是u_ig ∂L/∂v Σ (p_i - y_i) × u_i先分别计算三个候选词贡献的梯度猫的贡献 0.322620 × [0.10, -0.20] ≈ [0.032262, -0.064524] 鱼的贡献 -0.632591 × [0.40, 0.50] ≈ [-0.253036, -0.316295] 汽车贡献 0.309970 × [-0.30, 0.20] ≈ [-0.092991, 0.061994]把三个向量相加第 1 维0.032262 - 0.253036 - 0.092991 ≈ -0.313765 第 2 维-0.064524 - 0.316295 0.061994 ≈ -0.318825 g ≈ [-0.313765, -0.318825]各中间值四舍五入后可能有最后一位的差异代码使用未舍入的值计算。这就是前面流程图中两个梯度数字的来源。梯度表示当前损失对这两个坐标的变化有多敏感。图 6三个候选词各贡献一个二维向量相加得到梯度再逐维执行更新。6. 梯度下降真正移动“猫”的坐标设学习率η 0.1。学习率控制每次沿调整方向走多大一步。本例使用最简单的随机梯度下降SGD更新规则新向量 旧向量 - 学习率 × 梯度代入两个维度第 1 维0.20 - 0.1 × (-0.313765) ≈ 0.231377 第 2 维0.10 - 0.1 × (-0.318825) ≈ 0.131883 更新前的“猫” [0.200000, 0.100000] 更新后的“猫” [0.231377, 0.131883]这一次词的向量坐标发生了可以算出来的变化。它是更新后的中间状态还不是用一个训练样本就得到了最终语义向量。实际训练也更新输出参数表U其梯度为∂L/∂u_i (p_i - y_i) × v。例如“鱼”的输出参数会变成鱼的输出参数梯度 -0.632591 × [0.20, 0.10] ≈ [-0.126518, -0.063259] 鱼的新输出参数 [0.40, 0.50] - 0.1 × [-0.126518, -0.063259] ≈ [0.412652, 0.506326]所有梯度都用本轮更新前的参数计算再一起应用更新。按这个规则同时更新E中“猫”的行和U的三行后重新算同一个样本指标更新前更新后P(鱼猫)0.367409损失L1.0012790.977861这个样本的正确答案概率提高了损失下降了。一次训练更新有效并不说明只训练一次就能理解“猫”的意思。7. 用几十行 Python 复算得到完全对应的结果下面只用 Python 标准库不需要安装深度学习框架。它演示一次训练更新并同时更新输入向量和输出参数importmath words[猫,鱼,汽车]v[0.20,0.10]# 输入表 E 中“猫”的一行U[[0.10,-0.20],[0.40,0.50],[-0.30,0.20]]target1# 正确邻近词“鱼”learning_rate0.1defpredict(v,U):scores[sum(a*bfora,binzip(v,u))foruinU]exp_scores[math.exp(z-max(scores))forzinscores]probabilities[e/sum(exp_scores)foreinexp_scores]returnscores,probabilities scores,ppredict(v,U)loss-math.log(p[target])errors[p[i]-(1ifitargetelse0)foriinrange(3)]grad_v[sum(errors[i]*U[i][j]foriinrange(3))forjinrange(2)]grad_U[[errors[i]*v[j]forjinrange(2)]foriinrange(3)]# 同一训练步的梯度都用更新前的参数计算new_v[v[j]-learning_rate*grad_v[j]forjinrange(2)]new_U[[U[i][j]-learning_rate*grad_U[i][j]forjinrange(2)]foriinrange(3)]_,new_ppredict(new_v,new_U)print(初始概率:,[round(x,6)forxinp])print(输入向量梯度:,[round(x,6)forxingrad_v])print(更新后的猫向量:,[round(x,6)forxinnew_v])print(损失:,round(loss,6),→,round(-math.log(new_p[target]),6))print(P(鱼):,round(p[target],6),→,round(new_p[target],6))输出初始概率: [0.32262, 0.367409, 0.30997] 输入向量梯度: [-0.313765, -0.318825] 更新后的猫向量: [0.231377, 0.131883] 损失: 1.001279 → 0.977861 P(鱼): 0.367409 → 0.376115代码计算 Softmax 时减去最大分数是为了数值稳定得到的概率与上面的公式相同。8. 从“一次更新”到“训练好的词向量”接下来不断提取其他“中心词—邻近词”样本重复预测、计算损失和更新。大量上下文提供约束模型逐渐学出能帮助预测的向量。出现在相似上下文中的词可能因此得到相似的输入表示。训练结束后把输入表E保存下来。本例使用“猫”时只需读取E[0]即可得到训练后的坐标。这个坐标没有固定的人类标准答案也没有预先规定“第 1 维是动物属性、第 2 维是交通属性”它由初始化、语料和训练过程共同决定。这里展示的是Skip-gram 完整 Softmax SGD适合第一次学习。真实词表很大时Word2Vec 常用负采样等方式减少计算量现代 Transformer 的 token Embedding 也作为参数参与训练但后续网络和训练任务更加复杂不能把上面的三词例子当作其完整计算过程。Word2Vec 原论文、Word2Vec 负采样论文、TensorFlow 官方实现教程有没有类似 ImageNet 的“词向量网站”有而且不止一个。但它们对应的东西不同。Word2Vec经典词向量Word2Vec 通过上下文预测学习词向量经典做法包括CBOW根据上下文预测中心词Skip-gram根据中心词预测上下文。它适合词语相似度传统文本分类关键词分析轻量 NLP 实验。局限是同一个词通常只有一个固定向量。例如“苹果”苹果水果 苹果科技公司在传统 Word2Vec 中通常共享同一个向量很难根据句子自动区分含义。GloVe基于共现统计的词向量GloVe 使用词与词在大规模语料中的共现统计来学习向量。Stanford 官方页面提供了多个预训练版本例如 Wikipedia、Gigaword、Common Crawl 和 Twitter 语料版本。Stanford GloVe 预训练向量fastText适合多语言和生僻词fastText 不只把一个完整词当成原子单位还利用字符 n-gram 学习词的组成因此对拼写变化、生僻词和未登录词更友好。fastText 官方提供了多语言预训练词向量包括 157 种语言版本也提供基于 Wikipedia 的 294 语言词向量页面。fastText 157 语言词向量 、fastText Wiki 词向量腾讯 AI Lab 中文词向量腾讯 AI Lab 曾公开中文和英文词、短语的预训练向量提供 100 维和 200 维等版本。Tencent AI Lab Embedding CorporaHugging Face Hub现在更像“模型商店”如果把 ImageNet 理解成“标准数据集 预训练模型生态”那么今天更接近这个角色的是 Hugging Face Hub可以搜索预训练模型可以查看模型用途和许可证可以下载 Tokenizer 和权重可以找到句子向量、跨语言向量、代码向量、图文向量等模型。但它不是一份“人类所有词汇的最终坐标表”。模型通常有自己的词表、训练语料、向量维度和适用范围。为什么不能做一张“全人类词汇总表”这个想法很自然但实际有几个限制。词汇会持续变化新产品、新网络用语、新技术、新人名每天都在出现。一张固定词表很快就会过时。一个词可能有多个意思I went to the bank to deposit money. We sat on the river bank.这里英文单词 “bank” 的含义不同。固定词向量无法只靠一个坐标完全表达这两种语境。不同领域需要不同空间医学、法律、金融和日常聊天中的词语关系不同“支架”在医学领域有一种关系 “支架”在软件工程中可能指另一种结构因此Embedding 模型需要和任务、语料、语言、领域匹配。词向量不等于知识库Embedding 主要用于表达相似性和检索关系。它不等于事实数据库精确的百科知识可以逐条核对的法律条文永远正确的答案。RAG 仍然需要把原文保存下来向量只负责帮助找到相关内容。静态词向量和上下文 Embedding 的区别图 7静态词向量通常一词一个坐标上下文模型会根据句子重新计算表示。在 RAG 中还要区分离线建库和在线查询文档向量可以提前计算用户的新问题通常要在请求到来时编码。图 8文档向量可以预先算好每条新查询仍需进入同一个 Embedding 模型。静态词向量典型代表Word2VecGloVefastText特点同一个词 → 通常同一个向量优点体积小速度快可直接加载到传统 NLP 模型很适合学习 Embedding 原理。缺点不能充分利用上下文难以处理一词多义可能和你的领域不匹配通常不能直接替代现代 Transformer 的输入层。上下文 Embedding典型来源BERT、RoBERTa 等编码器模型Sentence Transformers各种专门的文本向量模型大语言模型中间层或专门的 Embedding API。特点同一个词 不同上下文 → 可能得到不同表示例如句子 A我去银行存钱 句子 B河边的银行很长模型会根据整句话产生不同的上下文表示。Sentence Transformers 文档把 Embedding 用于语义相似度、语义搜索、聚类、分类和释义挖掘并说明常见流程是先用双编码器快速召回再用重排模型精排。Sentence Transformers 使用文档为什么 RAG 里还是要做 EmbeddingRAG 中通常有两个时间点离线建库和在线查询。原始文档切分 ChunkEmbedding 模型向量数据库用户问题同一个 Embedding 模型向量检索召回相关文档大语言模型答案离线阶段文档只需要处理一次除非文档发生变化chunkssplit_documents(documents)chunk_vectorsembedding_model.encode(chunks)vector_db.insert(chunk_vectors,metadatachunks)这一步可以批量处理后台执行缓存结果文档不变时不重复计算。在线阶段用户每提出一个新问题就需要把问题放到同一个向量空间query_vectorembedding_model.encode([公司的报销规则是什么])resultsvector_db.search(query_vector,top_k5)这里的“每次做 Embedding”通常指对新的查询文本做一次前向计算。它不代表每次重新训练模型也不代表每次重新生成整套词向量。为什么查询和文档必须使用同一个空间如果文档用模型 A 编码查询用模型 B 编码两个向量的坐标含义可能不同距离就没有可比性。文档向量模型 A 的坐标系 查询向量模型 B 的坐标系 ↓ 距离结果可能没有意义因此 RAG 通常要求文档和查询使用同一个 Embedding 模型使用同一套预处理方式使用相同的向量维度注意模型要求的 query/document 指令格式更换模型后重新构建索引。一个最小可运行的句子 Embedding 示例下面的示例使用 Sentence Transformers。模型名只是示例生产环境应根据中文、英文、多语言、代码或领域数据选择并评估模型。pipinstallsentence-transformersfromsentence_transformersimportSentenceTransformer# 示例模型适合入门理解生产环境请根据语言和任务选型modelSentenceTransformer(sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2)sentences[RAG 会先检索相关资料再让大模型生成答案。,检索增强生成可以减少模型凭空编造。,卷积神经网络常用于图像识别。,]vectorsmodel.encode(sentences,normalize_embeddingsTrue)print(vectors.shape)# 结果是一个“每句话对应一个向量”的矩阵具体维度以模型配置为准计算相似度fromsentence_transformers.utilimportcos_sim querymodel.encode([什么是检索增强生成],normalize_embeddingsTrue,)scorescos_sim(query,vectors)[0]print(scores)注意模型推理需要计算资源文档向量可以提前算好并缓存查询向量通常在用户请求到来时计算如果请求量大可以批量处理、并发处理或独立部署 Embedding 服务不能只看向量距离还要评估召回率、答案准确率和业务指标。写 NLP 模型时到底应该自己训练 Embedding 吗可以按下面的决策表判断。场景推荐做法学习 Embedding 原理自己训练一个小型 Embedding 层传统文本分类、情感分析先尝试预训练词向量或预训练编码器中文语义检索使用中文或多语言预训练 Embedding 模型RAG 文档库选择一个 Embedding 模型批量编码并入库领域语料和通用模型差异很大先评测必要时微调 Embedding 模型训练一个大语言模型使用模型配套 Tokenizer 和 Embedding 权重只有少量数据不建议从零训练词向量初学阶段最容易犯的错误是一看到 Embedding 就想“我是不是要重新训练一套所有词向量”。通常不需要。更准确的理解是自己训练模型Embedding 矩阵是模型参数会随训练更新 使用预训练模型Embedding 矩阵已经有了推理时查表或前向计算 使用 RAG新文档和新问题仍需用选定的 Embedding 模型编码最后总结问题一有没有现成词向量有Word2VecGloVefastText腾讯 AI Lab 中文词向量Hugging Face 上的各种预训练 Embedding 模型问题二是不是先分词是。现代流程通常是文本 → Tokenizer → token ID → Embedding → Transformer“分词”常常是子词切分不一定是传统意义上的按词切分。问题三为什么还要 Embedding因为每条新文本都要被定位到向量空间中模型内部需要把 token ID 映射成向量RAG 需要把新文档和新问题编码成向量语义搜索需要比较查询向量和文档向量句子和段落的向量不是一份永久不变的“全人类词典”。真正应该记住的一句话是预训练模型让你不用从零训练 Embedding但不能让新文本跳过 Tokenize 和向量化。参考资料Hugging FaceTokenization algorithmsHugging FaceThe Model HubMikolov et al.Word2VecStanfordGloVefastTextPre-trained vectorsTencent AI LabEmbedding CorporaSentence TransformersUsageHugging FaceTokenizer API