上一章节说到,每个 token 是一个 1,536 维的向量——高维空间中的一个点。含义相近的词离得近,含义不同的词离得远。
但「离得近」「离得远」具体怎么量化?两个向量之间的相似度怎么算出一个数字?
这个问题的答案是点积(dot product)。它是注意力机制的数学基础,理解了点积,后面的注意力、softmax、多头机制就都有了着力点。
从二维开始
先在最容易理解的二维空间里建立直觉。
假设有两个二维向量:
a = (3, 4)
b = (4, 3)
你可以在纸上画出来:从原点(0, 0)出发,a 指向坐标 (3, 4) 的位置,b 指向 (4, 3) 的位置。两个箭头的方向很接近——都大致指向右上方。
它们的点积这样算:对应位置的数字相乘,然后加起来。
a · b = 3×4 + 4×3 = 12 + 12 = 24
再看两个方向完全不同的向量:
c = (3, 4)
d = (-4, 3)
c 指向右上方,d 指向左上方——两者垂直。
c · d = 3×(-4) + 4×3 = -12 + 12 = 0
点积是 0!两个垂直的向量,点积恰好为零。
最后看两个方向相反的向量:
e = (3, 4)
f = (-3, -4)
e · f = 3×(-3) + 4×(-4) = -9 + (-16) = -25
点积是负数。
点积衡量的是什么
从上面三个例子中可以看到一个规律:
| 两个向量的关系 | 点积 |
|---|---|
| 方向相同 | 正数,且越大方向越一致 |
| 方向垂直 | 0 |
| 方向相反 | 负数 |
点积衡量的是两个向量在方向上的一致程度。 方向越接近,点积越大。
这正是衡量词相似度所需要的:如果「猫」和「狗」的向量方向接近(都是动物),它们的点积就大;如果「猫」和「经济」的向量方向很不同,它们的点积就小。
推广到高维
在 1,536 维空间里,点积的算法完全一样——对应位置相乘再求和:
a · b = a₁×b₁ + a₂×b₂ + a₃×b₃ + … + a₁₅₃₆×b₁₅₃₆
1,536 次乘法,1,535 次加法,得到一个数字。这个数字就是两个 token 向量的相似度分数。
用 Python 写出来:
def dot_product(a, b):
total = 0
for i in range(len(a)):
total += a[i] * b[i]
return total
# 假设 vec_cat 和 vec_dog 是"猫"和"狗"的 1536 维向量
# similarity = dot_product(vec_cat, vec_dog)
# 返回一个数字,越大表示越相似
这个计算快得惊人——1,536 次乘加对现代 CPU 来说是纳秒级别的事。对 GPU 来说更快,因为 GPU 天生擅长这种「大量数字同时做乘加」的操作。
余弦相似度
点积有一个问题:它不仅受方向影响,还受长度影响。
一个很长的向量(数字的绝对值普遍很大)和另一个向量的点积,会比一个短向量和同一个向量的点积大——即使方向完全一样。
a = (3, 4) 长度 = 5
b = (6, 8) 长度 = 10(a 的两倍长,但方向完全一样)
c = (4, 3)
a · c = 3×4 + 4×3 = 24
b · c = 6×4 + 8×3 = 48 ← 翻了一倍,但 b 和 c 的方向关系跟 a 和 c 完全一样
如果你只想比较方向、不想被长度干扰,可以先把两个向量各自除以自己的长度(归一化),然后再算点积。这个操作的结果叫余弦相似度:
cos(a, c) = (a · c) / (|a| × |c|) = 24 / (5 × 5) = 0.96
cos(b, c) = (b · c) / (|b| × |c|) = 48 / (10 × 5) = 0.96
归一化之后,结果一样了。余弦相似度的范围是 -1(完全相反)到 +1(完全一致),0 表示垂直(无关)。
在注意力机制中,点积和归一化以一种特定的方式组合使用——第 14 章节会具体看到。现在只需要记住核心概念:点积是衡量两个向量方向一致性的基本操作。
为什么注意力需要点积
在语言模型中,一个词需要「看到」前面的其他词来理解上下文。但它不需要均匀地看所有词——有些词对当前的理解更重要,有些不太相关。
比如在「她养了一只很可爱的猫」这句话里,如果模型正在处理「猫」这个词,它应该更关注「养」和「可爱」,而不太需要关注「了」和「一」。
怎么决定关注哪些词?用点积。模型计算当前词和前面每个词的点积——点积大的词更值得关注,点积小的词可以忽略。
这就是注意力机制的核心直觉:用点积来计算「两个词有多相关」,然后把注意力更多地分配给相关的词。具体的计算流程是第四单元的内容。
下一章节先补一个数学工具:矩阵乘法。神经网络的几乎所有计算——嵌入、注意力、前馈网络——归根结底都是矩阵乘法。理解了矩阵乘法,后面的所有架构细节都能落到具体的计算上。