一句话总结
Embedding 把词映射到一个连续的向量空间中,语义相近的词在空间中距离更近.
理解这个"空间"的几何意义--距离,方向,聚类--是后续理解模型为什么能工作的关键直觉.
从查找表到语义空间
上一篇我们知道了 Embedding 是一张查找表:token ID → 向量. 但如果只把它理解为"查表",会错过最重要的东西--这些向量在空间中的分布是有结构的.
想象一个二维坐标系(实际是 768 维,但思路相同),每个词是空间中的一个点:
|
训练出来的向量不是随机散落的,而是自动按语义关系聚集--辱骂词扎堆在右下区域,日常词聚在左上区域. 这不是人为设计的,是训练算法从大量文本的词语共现模式中"涌现"出来的.
Embedding 空间 = Go 中 net/http 的路由表. 注册 handler 时只给了路径和函数的映射关系,但 ServeMux 内部会按前缀树组织--结构是从数据中自动产生的. Embedding 空间也一样:只给了共现数据,空间结构自动形成.
距离 = 语义相似度
在 Embedding 空间中,两个向量之间的距离直接反映语义关系. 最常用的距离度量是余弦相似度(Cosine Similarity):
|
为什么用余弦而不是欧氏距离? 因为余弦只看方向,不看长度. 两个向量即使数值大小不同(比如一个词出现频率高导致向量被"拉长"),只要方向一致就判为相似.
类比两个 API 的请求模式:一个日均 1000 QPS,另一个日均 10 QPS,但它们的流量曲线形状完全相同(都是早高峰+午低谷+晚高峰). 余弦相似度关注的是"形状"(方向),不是"量级"(长度).
|
维度意味着什么
BERT 的 Embedding 是 768 维--这 768 个数字各自代表什么?
诚实的回答:没有人确切知道. 每一维不像数据库字段那样有明确定义(不存在"第 42 维 = 情感正负"这种对应关系).
但从宏观上可以这样理解:
- 某些维度的组合涌现出语义属性(情感倾向,正式程度,领域归属等)
- 维度越多,能编码的语义细节越丰富(就像描述一个人:3 个特征不够,300 个特征能区分大多数人)
- 768 维是效果和计算成本的平衡点--更多维度效果提升递减,但计算量线性增长
| 模型 | 维度 | 能编码的信息量 | 推理速度 |
|---|---|---|---|
| Word2Vec 小模型 | 50-100 | 基本语义关系 | 极快 |
| Word2Vec / FastText | 300 | 丰富的语义+部分语法信息 | 很快 |
| BERT-base | 768 | 深层语义+上下文+语法+世界知识 | 快 |
| BERT-large / GPT | 1024-4096 | 更精细的语义 | 较慢 |
对工程决策的影响
维度直接影响推理延迟和内存占用. 768 维向量做一次余弦相似度需要 768 次乘法+加法. 如果对骂检测要求 <5ms 延迟,每秒处理上万条消息,可能需要考虑用 128 或 256 维的蒸馏模型(阶段三会讲).
从词向量到句子向量
到目前为止讨论的都是词级的向量. 但分类任务(对骂检测)需要的是整个句子的向量--一句话 → 一个向量 → 一个分类标签.
怎么从词向量得到句子向量? 三种主流方式:
方法一:平均池化(最简单)
把一句话中所有词的向量逐维取平均:
|
优点:零成本,不需要训练. 缺点:丢失词序信息("我打你"和"你打我"结果相同).
方法二:TF-IDF 加权平均
用 TF-IDF 权重决定每个词对句子向量的贡献度:
|
比纯平均好--重要的词("废物""垃圾")贡献更多,虚词("的""了")贡献更少.
方法三:BERT 的 [CLS] 向量(最强)
先回顾:[CLS] 是 BERT 在句子开头自动加的特殊标记(Classification 的缩写). 它本身没有含义--不代表任何实际的字或词. 那它的向量是怎么"变成"句子向量的?
关键在于理解 Transformer 层做了什么. 分步展开:
第一步:Embedding 查表(所有位置平等)
输入的每个 token(包括 [CLS])都去 Embedding 表查出自己的初始向量. 此时 [CLS] 的向量只是表里第 101 行的固定值,和句子内容无关.
|
第二步:Transformer 层的自注意力(核心魔法)
进入 Transformer 层后,每个位置都会"查看"其他所有位置,并把看到的信息融合进自己的向量中. 这个过程叫自注意力(Self-Attention).
|
第三步:12 层叠加(信息层层融合)
这个过程重复 12 次(BERT-base 有 12 层). 每一层 [CLS] 都会再次看所有位置--但此时其他位置的向量已经被上一层更新过了,包含了更丰富的信息.
|
[CLS] 经过 Transformer = Go 中 HTTP 请求经过 middleware 链. 一个请求经过 12 层中间件,每层都往 context 里注入一些信息(认证状态,用户身份,权限,限流标记...). 到最后一层时,context 已经聚合了所有中间件的输出. [CLS] 就是这个 context--经过 12 层 Transformer "中间件",每层都从句子中其他字那里收集信息,最终成为整句话的浓缩表示.
为什么是 \[CLS\] 而不是其他位置
[CLS] 天生适合做句子汇总,因为它没有自己的语义负担. "你"的向量既要表达"你"本身的含义,又要参与上下文计算;而 [CLS] 不代表任何字,它唯一的任务就是当"信息收集器".
BERT 预训练时就是让 [CLS] 承担句子级任务(判断两句话是否连续),所以训练完成后这个位置天然擅长汇总全句语义.
最终的使用方式很简单--取 [CLS] 对应位置的输出向量,作为整个句子的表示:
|
这是效果最好的方式--因为 [CLS] 的向量经过 12 层注意力计算,已经融合了整句话的上下文信息,包括词序,语气和语义关系.
| 方法 | 考虑词序 | 考虑上下文 | 需要训练 | 效果 |
|---|---|---|---|---|
| 平均池化 | 否 | 否 | 否 | 够用做 baseline |
| TF-IDF 加权平均 | 否 | 否 | 否 | 比纯平均好 |
| BERT [CLS] | 是 | 是 | 是(微调) | 最好 |
语义空间中的聚类现象
一个重要的直觉:如果把大量文本的句子向量画在空间中,同类文本会自然聚成簇.
分类器做的事情就是在这个空间中画边界线--把不同簇分开. Embedding 质量越好(同类越聚,异类越散),分类器的工作就越简单.
阴阳怪气:分类的难点
"你可真行啊"和"你真行! 太厉害了"在静态词向量空间中可能很近(用词相似),但语义相反. 这就是为什么需要上下文感知的 BERT--它能通过上下文判断"真行"是褒还是贬,把它们映射到空间中不同的位置.
预训练向量 vs 微调向量
预训练的 Embedding(如 Word2Vec 通用模型)是从通用语料学来的,它的语义空间是"通用"的. 但业务场景有特定的语义结构:
- 通用空间:"nmsl" 可能不在词表中,或者和 "nms" 等无关缩写混在一起
- 业务场景:"nmsl" 应该紧靠"废物""垃圾"等辱骂词
微调(Fine-tuning)就是用自己的标注数据对 Embedding 空间做调整--让模型的语义空间适配业务定义.
|
预训练 = 招了一个通用型工程师(什么都懂一点).
微调 = 让他在项目上工作几周后(熟悉了业务术语和上下文), 他对 "MR""SOP""上线" 这些词的理解从泛化变成了和团队一致.
全流程串联
把阶段一四篇的知识点放回完整的推理流程中:
|
蓝色部分是本篇的核心--从 ID 变成向量,向量在空间中有结构,最终取句子向量做分类. 阶段一到此完成概念建设,接下来阶段二开始实际训练分类器.
阶段一的闭环:向量有了,但还不会分类
到这里可能会有一个疑问:向量空间里同类文本是聚集了,但模型怎么知道哪个簇是"骂人",哪个簇是"正常"?
答案是:模型不知道. "哪个是骂人"这件事,是通过标注数据告诉它的.
阶段一做的事情,是把文本变成向量空间中的点--画了一张地图. 但地图上还没有国界线. 阶段二要做的事情,就是用标注数据告诉模型怎么画边界:
|
这个"学分界线"的过程就是训练. 分类层做的事极其简单--就是一个线性方程:
|
其中 W 和 b 是通过标注数据训练出来的参数. 本质上就是在 768 维空间中画了一条分界线.
标注数据是真正的瓶颈
模型能不能分对,完全取决于标注数据有没有清晰地定义"什么算对骂". 如果标注不一致(同一句话有人标对骂有人标正常),就相当于在地图上画了一条歪歪扭扭的国界线--模型照着学,结果也是歪的.
这就是为什么阶段二会花大篇幅讲标注规范设计.
总结阶段一到阶段二的衔接:
| 阶段 | 做什么 | 类比 | 输出 |
|---|---|---|---|
| 阶段一(已完成) | 文本 → 向量 | 画地图:把城市标在坐标上 | 每条文本对应一个向量坐标 |
| 阶段二(即将开始) | 标注 + 训练分类器 | 画国界线:告诉模型哪些点属于哪国 | 一个能对新文本做分类的模型 |
| 阶段三 | BERT 微调 | 重新画地图:让同国城市更紧,异国更远 | 边界线更好画,分类更准 |
快速回顾
- 语义空间:Embedding 向量不是随机的,语义相近的词/句自动在空间中聚集
- 距离 = 相似度:余弦相似度衡量两个向量的方向一致性,是最常用的语义相似度度量
- 维度:越多越精细但越慢,768 维是效果和成本的平衡点
- 句子向量:平均池化(最简) → TF-IDF 加权(稍好) → BERT [CLS](最强)
- 微调的意义:让通用的语义空间适配业务场景--"nmsl" 该在辱骂簇里,不是随机角落
动手练习
- 获取 CLS 向量:用 BERT Tokenizer + Model 获取种子数据集中每条文本的
[CLS]向量(768 维),保存为 numpy 数组 - 对比类间距离:计算对骂组和正常组的
[CLS]向量平均余弦相似度,与上一篇 TF-IDF 实验的结果对比--看 BERT 是否把两类分得更开 - 可视化聚类:用 sklearn 的 PCA 把 768 维降到 2 维,画散点图观察两类文本是否聚成不同簇