资讯动态

一句话变成一串数字:自然语言处理中的词向量、句向量与BERT实战

发布时间:2026/10/19 12:30:52 来源:尧图企业网站定制
“苹果好吃吗”——这句再普通不过的话落到AI面前不是一串文字而是一串冷冰冰的数字。很多人第一次接触“AI读懂语言”这个概念时会觉得它像魔术凭什么机器能分得清“苹果”指水果还是指某手机品牌凭什么它能理解“好吃”是正面评价说到底是因为AI把人类的语言换了一种载体——数字。这篇文章就围绕“一句话变成一串数字”这个核心拆解AI处理自然语言的全过程讲清楚分词、词嵌入、句子向量这些关键环节到底在干什么也会给出可以直接拿走的实操代码和踩坑经验。无论你是刚接触自然语言处理的开发者还是产品经理、数据运营看完这篇文章你至少能明白AI“读懂”语言本质上是一场转换游戏。## 1. 先从“苹果好吃吗”说起AI处理语言的第一步所有环节里最容易被人忽略、却最能决定效果的第一步其实是“词”——也就是分词。机器不认识连续的汉字串它看到的“苹果好吃吗”只是一个字符序列。“好吃吗”这个词怎么切直接决定后续所有环节的输入质量。1.1 为什么必须先分词中文不像英文那样天然有空格分隔所以分词是个独立问题。拿“苹果好吃吗”来说各种分词器会给出不同的结果按词典最大匹配“苹果 / 好吃 / 吗”按统计模型“苹果 / 好 / 吃吗”按人工干预“苹果 / 好 / 吃 / 吗”切分方式不同向量空间的语义就会变化。“好吃吗”被切开之后原本应该合在一起的情感表达就散了。所以你经常会看到同一句话不同分词器给出的“语义向量”差距巨大而这并不是模型的错是入口收窄了。我这里说的“向量”可以理解成“一串数字”。机器不懂“红”和“脆”这些概念它只关心这些词在大量文本里和什么词一起出现、出现频率如何然后把这种关系压缩成一串数字。这个思路就是自然语言处理里最核心的“分布假说”一个词的语义由它的上下文决定。放到“苹果好吃吗”上就是“苹果”如果频繁和“脆”“甜”“水果”一起出现AI就更倾向于把它编码为水果“苹果”如果频繁和“芯片”“屏幕”“手机”一起出现AI就会把它编码为电子产品。1.2 从词到数字老三样与新思路理想状态下分词之后应该给每个词分配一串数字。最原始的做法是“词袋模型”建立一个全词典每个词对应一个位置句子里出现过的词标1没出现过的标0。于是“苹果好吃吗”就变成了一条包含几万个0和几个1的超长向量。这个做法听起来合理实际用起来问题很大。第一它至少需要几万维计算和存储都很吃力第二它完全丢失了语序——“苹果吃我吗”和“我吃苹果吗”在词袋里一模一样第三它无法表达“好”和“棒”之间的相似性因为它们对应不同的位置。于是就有了“词嵌入”的思路把每个词压缩到一个固定维度比如300维同时让语义相近的词在空间里距离更近。这里有个生活化类比你把全国所有人按照身高、体重、年龄、收入画在一张多维坐标纸里身高差不多、收入也差不多的两个人会靠得很近。词嵌入做的就是类似的事只不过坐标轴没法一一解释它们是模型自动学出来的潜在特征。1.3 定长向量与变长文本的矛盾句子长度是不固定的而神经网络处理数据时通常需要固定长度的输入。这时“池化”就出来了。常见做法是对一句里所有词的向量做平均或最大化把整句压成一个固定向量。以“苹果好吃吗”为例假设“苹果”向量是300维“好吃”向量是300维“吗”向量是300维平均之后得到的还是300维这300个浮点数就是这句话的“句子向量”。它有个特点任何长度的句子最终都被塞进了同一个长度里——300这个定长向量就能拿去和别的句子比较或者作为下一条分类模型的输入。但直接平均有个隐患句子里的“吗”这种虚词会稀释“好吃”带来的情感信息。“吗”在语料里出现次数多位置又偏中性一股脑平均进去可能让整个句子向量变得模糊。要解决这个问题可以引入“词语重要性权重”这就是传统方法里TF-IDF要做的事。## 2. 关键是“分布”词向量和句向量是怎么被训练出来的很多人看到“一串数字”会追问这串数字到底怎么来的训练过程是什么这里需要引入两条路线一条是静态词向量比如word2vec一条是动态词向量比如BERT。两者处理“苹果好吃吗”的方式可以说天差地别。2.1 静态词向量让“邻居词”来投票word2vec的思路非常朴素它不关心词本身是什么只关心词周围出现了什么。形式上可以这样理解给定一个窗口比如前后各两个词模型的任务是“通过‘苹果’来预测‘好吃’和‘吗’”或者反过来“通过‘好吃’和‘吗’来预测‘苹果’”。这两种方向分别叫Skip-gram和CBOW。训练之后模型的权重矩阵就成了词表。每一行都是一个词的向量。这个过程有一个外号叫“让邻居词来投票”其实本质是在做一种非常精巧的压缩一个词的意思等于它所有常见上下文的意思的总和。用“苹果好吃吗”举例语料里大量句子是“苹果很甜”“苹果吃起来脆”“香蕉好吃吗”模型就会学到“苹果”和“甜”、“脆”、“香蕉”在空间里的关系很亲密“苹果”如果大量和“手机壳”“系统更新”共现它的向量就会又跑到另一个角落。静态词向量的核心缺陷在于一个词只有一个向量。“苹果”的两种含义被强行拧成了一个点结果往往是既不靠近水果也不靠近手机而是卡在了中间。这在互联网真实语料里尤其常见因为两个含义的出现频率可能都不低。2.2 从词向量到句向量不只是平均知道词向量之后最省事的句向量生成方式就是平均词向量。但前面已经说过这个做法信息损失严重。一个改进是使用加权平均给“好吃”这种实义词更高的权重压低“吗”这类虚词。权重可以用TF-IDF计算。关于“苹果好吃吗”这样的短句词频都是1其实看不出差异。但如果你处理的是长文本比如一篇文章“苹果”出现5次“好吃”出现2次“吗”出现50次那么TF-IDF会认为“吗”这种遍地都是的词价值很低给它的权重几乎为零。这个操作在实际项目里非常常见尤其是做文本相似度、文本聚类的时候节省了大量计算效果提升也明显。再往下走还有一种做法是池化。平均能保留整体语义最大池化则只保留每个维度上最突出的信号。实践里平均的稳定性通常更好最大池化容易受异常值影响。不少老练的项目会把两者结合拼接起来用虽然维度变成双倍但信息量往往更足。2.3 动态词向量让“苹果”长出不只一张脸BERT这类预训练模型出现之后句向量生成发生了质变。BERT的思路简单说就是让模型同时看一句话里所有词的位置通过一个叫“自注意力”的机制动态地为每个词生成一个依赖上下文的新向量。这意味着“苹果好吃吗”里的“苹果”和“苹果手机多少钱”里的“苹果”在BERT内部会成为两个完全不同的向量。这解决了一词多义的难题。但代价是模型体积和计算量大增。句子“苹果好吃吗”经过BERT中间层输出是13个向量每层每个token都有一份。具体取哪个向量有很多经验讲究cls位置输出通常被当作句子级别的语义汇总但实践里更稳妥的做法是取所有token向量的平均或者倒数第二层的“cls”向量。我做过多次对比对短句相似度任务平均向量一般优于直接用cls。这一部分可以用一张表格来对比方法单词语义表示句子表示方式是否解决多义词资源消耗词袋模型高维0/1向量词频累加否很低word2vec词向量固定词向量平均/加权否较低TF-IDF加权词向量固定词向量加权平均否较低BERT动态向量上下文相关cls/平均池化是较高## 3. 实操心法亲手把“苹果好吃吗”变成一串数字前面讲了很多理论这里给出一套可以实际运行的流程。我用的方案是Python Hugging Face的transformers BERT中文预训练模型这是目前社区里最稳妥的组合之一。跑完这段代码你会真实看到那句话变成一串数字的全过程。3.1 环境准备与依赖安装建议用Python 3.9以上版本先建一个干净的虚拟环境避免依赖冲突。安装所需模块pip install transformers torch如果电脑配置一般也可以使用pip install transformers tensorflow不过下面代码以PyTorch接口为例。实际运行时Hugging Face会自动下载预训练模型中文模型可以用bert-base-chinese。如果网络受限可以提前用huggingface-cli download把模型拉下来。3.2 编码流程拆解把“苹果好吃吗”送入模型要经历三步分词、转id、模型前向计算。from transformers import BertTokenizer, BertModel import torch tokenizer BertTokenizer.from_pretrained(bert-base-chinese) model BertModel.from_pretrained(bert-base-chinese) sentence 苹果好吃吗 inputs tokenizer(sentence, return_tensorspt) print(input_ids:, inputs[input_ids])你会看到一个类似[101, 2678, 1965, 1450, 7993, 102]的输出。101和102是BERT专用的起始和结束标记中间的2678、1965、1450、7993编号就是“苹果好吃吗”四个字在词表里的id。注意BERT用的是单字切分所以没有出现分词步骤这种设计避免了分词错误向模型传导。接下来做前向计算with torch.no_grad(): outputs model(**inputs) last_hidden_state outputs.last_hidden_state # 取所有token向量平均作为句子向量 sentence_vector last_hidden_state.mean(dim1).squeeze() print(sentence vector:, sentence_vector) print(shape:, sentence_vector.shape)输出是一个长度为768的一维张量里面是像1.2345e-01, -2.3456e-01这样的浮点数。这就是标题里说的“一串数字”。这一步本质上是在做“把语义压缩进一个数学空间”的操作。输出768维是因为bert-base这个模型的隐藏层维度是768。3.3 比较句子相似度拿到句子向量之后最常用的任务是判断两句话是否意思相近。计算机判断“意思相近”的方式是计算两个向量之间的距离。最常用的指标是余弦相似度公式不复杂简单说是看两个向量的方向是不是大致一致。from sklearn.metrics.pairwise import cosine_similarity import numpy as np def get_sentence_vector(text): inputs tokenizer(text, return_tensorspt) with torch.no_grad(): vec model(**inputs).last_hidden_state.mean(dim1).squeeze().numpy() return vec v1 get_sentence_vector(苹果好吃吗) v2 get_sentence_vector(香蕉好吃吗) v3 get_sentence_vector(这个手机好用吗) v4 get_sentence_vector(苹果多少钱一斤) print(苹果好吃吗 vs 香蕉好吃吗:, cosine_similarity([v1], [v2])[0][0]) print(苹果好吃吗 vs 这个手机好用吗:, cosine_similarity([v1], [v3])[0][0]) print(苹果好吃吗 vs 苹果多少钱一斤:, cosine_similarity([v1], [v4])[0][0])预期的结果大概是这样的与“香蕉好吃吗”的相似度明显更高跟“这个手机好用吗”的相似度会低一些跟“苹果多少钱一斤”也不算低因为两者有共用的“苹果”这个词。产生这个现象的原因正好印证了概念层面学的知识——“苹果”的向量在模型内部会根据上下文移动“好吃”的语义也会吸引“香蕉”靠近。这里有一个容易搞错的细节相似度高不代表逻辑相关。“苹果好吃吗”和“香蕉好吃吗”的相似度高是因为句法和情感倾向一致并没有推演出“提问者在讨论水果”这是模型读懂了表面语义但还不具备常识推理能力。实际应用时不要对语义向量抱有不切实际的期望。## 4. 实践中的常见问题与避坑指南做自然语言处理的落地项目最难的不是把Demo跑通而是把效果调到能上线。这里把最容易踩的几个坑集中列出来希望你看完能少走弯路。4.1 短句直接平均向量容易被“吗”带偏一句话“苹果好吃吗”一共五个字里面“吗”是一个疑问语气词语义贡献很弱。如果直接把5个字的向量平均语气词的噪声就会影响结果。尤其当句子更长时问题更明显。一个可行办法是使用“加权池化”先算每个词的TF-IDF权重再做加权平均。对于“好吃吗”这类词给它们的权重乘以一个大于1的系数削弱“吗”的影响。如果你用的是BERT可以多用几层中间的隐藏状态再做平均效果往往更好。4.2 维度选择与预训练模型匹配不同模型输出的向量维度差别很大word2vec中文预训练通常是300维BERT输出的句子向量是768维。维度越大计算量和存储量越大但并不是越大越好。做语义检索时300维往往足够做情感分类这种精细任务时768维更常用。如果你用word2vec做短句相似度建议把通用词表里出现频率过高的词过滤掉。“苹果好吃吗”里的“吗”在word2vec词表里也会有一个向量但它几乎不携带情感信息。直接在预处理环节去掉疑问词往往比事后调权重更简单粗暴。4.3 “苹果”的歧义到底能不能解决BERT的动态向量缓解了一词多义的问题但并没有彻底解决。原因在于预训练模型学习到的语义取决于语料分布。如果某个领域语料里“苹果”绝大多数时候指水果那么即使输入“苹果手机怎么样”模型对“苹果”一词的编码仍然会偏向水果。真正要解决领域歧义需要在目标领域数据上做微调。我在做一个设备评价分类项目时遇到过类似情况。刚开始直接用通用模型效果一般后来在几千条手机评价上做微调后模型的准确率有明显提升。这是在业务数据上做迁移学习的效果。4.4 复现波动设备影响不可忽视BERT模型在GPU和CPU上跑出来的结果严格来说会有细微差别。这主要是因为浮点数运算精度和并行方式不同。如果你在本地CPU上实验然后在服务器GPU上部署相似度结果可能出现小数点后四位的差异。对于绝大多数应用这种差异不影响结论但在做对结果一致性要求很高的线上服务时建议全程使用同一种硬件推理环境。4.5 一个完整的落地参考流程把“苹果好吃吗变成一串数字”真正用于产品通常会走这样一条流水线清洗文本去掉表情符号、乱码→ 分词/分字 → 查词表或者过BERT → 得到句子向量 → 存入向量数据库 → 查询时用余弦相似度召回。向量数据库方面的选择很多常见的有基于局部敏感哈希的轻量方案和基于HNSW的高性能方案。在这个过程中最耗时的是调优阶段。不要指望一次跑通所有句子。我自己的经验是先选50条典型句子计算两两相似度人工检查结果是否合逻辑然后针对明显不合理的句子调整预处理规则比如是否保留疑问词、是否做同义词替换等。这样迭代几轮之后效果通常能稳定在一个可用的水平。## 5. 真实体验与后续拓展最后聊点实际的体会。我自己做过多个短文本处理的小项目包括评价情感分析、客服问题归类和相似问题检索。一个反复出现的经验是对于“苹果好吃吗”这种超短文本预处理方式和向量生成方式的选择对最终结果的影响甚至大于模型本身的选择。举个例子如果你做的是相似问题检索把用户问题归为“苹果好吃吗”这类包含具体实体的短句那么word2vec加TF-IDF加权已经能做得非常好了不一定非要上BERT。但如果做外贸评论的情感分类句子长、语义复杂再叠加一词多义问题BERT的优势就会凸显出来。项目不同选型自然不同。如果你想把这件事继续做深还有几个方向值得尝试一是引入对比学习让模型在训练时更注意“苹果好吃吗”和“苹果难吃吗”的差异相似度任务会自动排开情感相反的句子二是做数据增强把训练语料里的主宾语随机替换让模型学到更稳健的语义结构三是把句子向量接入大语言模型让模型根据向量内容做生成但这已经属于另一个技术栈了。把一句话变成一串数字本质上不是在“理解”而是在“映射”。AI不需要知道苹果是什么味道它只需要在向量空间里让“苹果”离“香蕉”比离“手机”更近一点。理解这一点你就理解了自然语言处理最底层的逻辑。后面的工程化技巧都建立在这个朴素的原理之上。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑