第 4 课我们用余弦相似度把"词义像不像"变成了一个数,还见证了 king−man+woman≈queen。但当时公式只是脚注。这课把它拆开——它总共涉及两个运算(点积、长度),10 分钟手算就能吃透,回报却极大:注意力机制的打分公式就是它的近亲。
一、点积:最自然的相似度,和它的缺陷
两个向量有多"像"?最直接的想法是点积:对应位置相乘,再全部加起来:
手算:点积
[3, 4] · [1, 2] = 3×1 + 4×2 = 11
同方向越对齐、数值越大 → 点积越大越"像"。看起来完美?
缺陷藏在下面这组对比里。[6, 8] 和 [3, 4] 方向完全相同(它就是 [3,4] 的两倍长),但:
| 向量对 | 点积 | 直觉上应该 |
| [3,4] 和 [6,8](同方向,B 长 2 倍) | 3×6 + 4×8 = 50 | "最像"——数值也最大 ✓ |
| [3,4] 和 [3,4](完全相同) | 3×3 + 4×4 = 25 | "最像"——但只有 50 的一半 ✗ |
| [3,4] 和 [-4,3](恰好垂直) | 3×(−4) + 4×3 = 0 | "无关"——0 ✓ |
点积的缺陷:长度污染
[6,8] 和 [3,4] 明明是同一个意思(同方向),点积却是自身与自身比较的 2 倍——分数被向量长度绑架了。放在词嵌入里:向量长度往往反映词频高低这类"音量"因素,而方向才编码语义。用点积排名,长向量会霸榜。
二、余弦:先除掉长度,只比方向
修法顺理成章:把点积除以两个向量各自的长度,把"音量"归一掉:
余弦 = 点积除以两个长度。A 与 B 同方向,余弦 = 1;垂直 = 0;反向 = −1。
公式(唯一需要记的)+ 三个锚点
$$\cos\theta = \frac{A \cdot B}{\lVert A \rVert \times \lVert B \rVert}$$
其中 $\lVert A \rVert$ = 向量长度(各分量平方和开根号)。
手算:$\lVert[3,4]\rVert = \sqrt{3^2+4^2} = 5$,$\lVert[6,8]\rVert = 10$,于是 $\cos = 50/(5\times10) = $ 1(同向)。
三个锚点:1 = 同方向(最像);0 = 垂直(无关);−1 = 反方向(相反)。其余角度落在中间,越锐越像。
三、玩会它:拖拽实验室
下面的实验室里,拖动 A、B 两个箭头的端点,读数实时变化。先点「垂直(无关)」看余弦归零,再拖 B 转到和 A 同方向看它爬向 1——注意拖长向量时余弦几乎不变,这就是"只看方向":
四、回到词嵌入:为什么必须用余弦
现在能准确回答第 4 课留下的问题了。词向量由训练产生,长度没有语义含义(高频词的向量往往偏长),方向才有。用点积排名,"长向量"会霸榜;用余弦排名,才轮到真正语义相近的词。看一组故意把 king 造成超长向量的演示:
# ============================================================
# 【演示目标】同一个"和 cat 最像"的问题:点积榜 vs 余弦榜,答案完全不同
# ——证明点积会被长度污染,余弦只认方向
# 【思路】给 4 个词造向量,故意把 king 的向量做成超长(示意高频词的长度偏差),
# 但它的方向和 cat 毫不相似。然后分别用点积、余弦给"谁最像 cat"排名:
# 点积榜:king 靠长度霸榜(错!) 余弦榜:dog 第一(对!)
# ============================================================
import numpy as np
# ---- 向量:cat/dog/fish 方向相近(都是动物);king 故意超长但方向无关 ----
emb = {
"cat": [0.30, 0.95, 0.10],
"dog": [0.25, 0.85, 0.15],
"fish": [0.15, 0.80, 0.20],
"king": [2.80, 0.30, 0.10], # 长度 ≈ 2.83,是 cat 的近 3 倍(长度偏差的示意)
}
def dot(a, b): # 点积:对应位相乘求和
return float(np.dot(a, b))
def cos(a, b): # 余弦:点积除以两个长度——只比方向
a, b = np.array(a), np.array(b)
return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))
# ---- 两个榜单:"谁最像 cat?" ----
dot_rank = sorted(((dot(emb["cat"], emb[w]), w) for w in emb if w != "cat"), reverse=True)
cos_rank = sorted(((round(cos(emb["cat"], emb[w]), 3), w) for w in emb if w != "cat"), reverse=True)
print("点积榜(被长度污染):")
for s, w in dot_rank:
print(" %-5s %.2f" % (w, s))
print("余弦榜(只看方向):")
for s, w in cos_rank:
print(" %-5s %.3f" % (w, s))
# 读法:点积榜第一名是 king(错!它和 cat 毫不相似,只是长);
# 余弦榜第一名是 dog(对!)。这就是嵌入相似度都用余弦的原因。
五、往后的路:它和注意力机制是什么关系
记住一句话就够:注意力机制的打分,就是查询向量和键向量的点积(第 7 课正式拆)。而点积有本课讲的长度污染 + 维度变大后数值膨胀两个问题,所以原论文在点积后除以 √d(键维度)——那是"缩放"版的点积;如果把向量先归一化成单位长度再点积,得到的就是余弦。三兄弟是一家人:点积 → 缩放点积 → 余弦。第 7 课你会再见到它们。
检索练习
本课主读材料
去复习(约 10 分钟)
回看第 4 课第三节的语义地图:现在你完全拥有读懂那张图的两个工具——共现(语境)+ 余弦(方向比较)。试着在它的代码块里「单步」到 SIM 计算那一行,「求值」SIM[0,1]、SIM[idx['cat']] 看看。
视频(可选,睡前看):3Blue1Brown 深度学习第 5 章前 20 分钟——注意他多次强调"嵌入的方向/夹角承载语义",就是本课的内容。
我是你的老师,别客气
卡在哪一步(比如"|A| 为什么要开根号"或者 lab 拖不动),原样贴回来问我。下一课回到主干:位置编码。