查看全课大纲(第 22 / 82 节)
  1. 1.函数与数据泛化
  2. 2.向量:从几何直觉到高维空间
  3. 3.矩阵与张量
  4. 4.特征值与特征向量:矩阵变换的不动方向
  5. 5.凸函数:从向量求导的矩阵记号到好优化的函数形状
  6. 6.概率与统计:机器学习的数学基础
  7. 7.独立性与随机变量:贝叶斯公式与二项分布
  8. 8.期望、方差与协方差:随机变量的数字画像
  9. 9.联合概率:当一条轴不够用的时候
  10. 10.概率分布(一):从伯努利到伽马函数
  11. 11.概率分布(二):贝塔、泊松、正态与两个贝叶斯故事
  12. 12.机器学习的模型:找输入与输出的关系
  13. 13.机器学习的应用
  14. 14.过拟合
  15. 15.机器学习的分类
  16. 16.有监督学习:从房价预测说起
  17. 17.损失函数
  18. 18.梯度下降(上):更新规则与学习速率
  19. 19.梯度下降(2)
  20. 20.牛顿方法
  21. 21.KNN简介
  22. 22.距离测算
  23. 23.朴素贝叶斯
  24. 24.朴素贝叶斯的实际应用
  25. 25.信息熵:决策树构建的度量基础
  26. 26.信息增益与决策树的构建
  27. 27.ID3算法
  28. 28.决策树节点的划分
  29. 29.CART树
  30. 30.Bagging
  31. 31.Boosting:自适应提升算法
  32. 32.Gradient Boosting
  33. 33.线性模型:回归与分类的同一副骨架
  34. 34.最小二乘:把最优参数直接解出来
  35. 35.最小二乘法的几何与概率视角
  36. 36.极大似然:给最小二乘一个概率出身
  37. 37.逻辑回归(1):把线性模型接到分类上
  38. 38.逻辑回归(2):损失函数与多分类
  39. 39.---- 手造数据: 5 个样本, 2 个特征; x2 几乎 = 2*x1 (偏差手工指定, 无随机) ----
  40. 40.讲师例: 向量 "23014" -- 三把范数尺子量同一个向量
  41. 41.优化方法(1):坐标下降与前向选择
  42. 42.优化方法(2):前向策略与最小角回归
  43. 43.拉格朗日乘子法(1):从无约束到等式约束
  44. 44.拉格朗日乘子法(2):等式约束下的平行条件与拉格朗日函数
  45. 45.拉格朗日乘子法(3):不等式约束与 KKT 条件
  46. 46.最大间隔:把分类问题变成优化问题
  47. 47.拉格朗日对偶问题:把 w 和 b 全部换成 α
  48. 48.支持向量:边界上的少数派
  49. 49.SVM 计算过程:一笔到底算完,再走进核方法
  50. 50.贝叶斯公式:概率即信念
  51. 51.隐含变量和概率模型:给看不见的原因建模
  52. 52.EM 算法:期望与最大化的迭代
  53. 53.琴生不等式:EM 的数学钥匙
  54. 54.三个硬币问题:EM 的复盘与衔接
  55. 55.高斯混合模型:用隐变量解释双峰数据
  56. 56.隐马尔可夫模型
  57. 57.隐马尔可夫模型(2):前向算法与动态混合模型
  58. 58.隐马尔可夫模型在 NLP 中的运用
  59. 59.EM 算法:把「为什么收敛」推到底
  60. 60.HMM 在 NLP 中的运用:从分词到机器翻译
  61. 61.HMM 参数估计:Baum-Welch 的三块拉格朗日
  62. 62.高维下的高斯模型
  63. 63.主题模型:从词袋到隐含主题
  64. 64.Dirichlet 分布(1)
  65. 65.Dirichlet 分布(2)
  66. 66.统计学习内容回顾
  67. 67.K-means 算法
  68. 68.PCA 降维
  69. 69.SVD 降维
  70. 70.从聚类到流形
  71. 71.局部线性嵌入
  72. 72.随机邻域嵌入:SNE 与 t-SNE
  73. 73.无参模型:让数据自己说话
  74. 74.高斯过程:函数空间里的正态分布
  75. 75.高斯过程(2):噪声、正则化与核的线性组合
  76. 76.神经网络简介:从感知器到自编码器
  77. 77.反向传播:把误差一层层传回去
  78. 78.卷积神经网络(CNN):让特征自己学出来
  79. 79.循环神经网络(RNN):给网络装上记忆
  80. 80.深度学习回顾:静态一类,动态一类
  81. 81.增强学习(1):蒙特卡洛、树搜索与 Q-learning
  82. 82.强化学习(2):从乌鸦吃核桃到井字棋的 value 回传

距离测算

约 18 分钟

📺 正在播放小象官方高清录播(支持倍速与清晰度调节)

小象实战讲义 · 机器学习算法精讲

上一节讲 KNN 时,有一个词从头到尾没离开过:distance(距离)。要说 nearest neighbor——最近邻,就得先说清什么是 near、什么是 far;而距离的定义一换,“最近”的邻居可能就换了一批,结果也就跟着变。本节专门把”距离”这件事讲透:它要满足哪些数学条件、经典距离家族长什么样、二进制和高维向量各有什么专用度量、乃至面对《辛普森一家》里的卡通人物,我们怎么亲手定义一个距离。

💡 核心导读

  • 距离是你定义的:“好朋友”其实是一种距离——跟你好就是距离短;不同问题要定义不同的 distance measure。
  • 认知距离不等于像素距离:两张人像照片点对点做差并不远,人与狗反而难分——机器学习很多时候是关于 human perception 的。
  • 四个条件:对称、自己与自己的距离为零(且仅此时为零)、非负、三角不等式——满足才算数学上 consistent 的距离。
  • Look familiar:距离和向量求模只差”一个向量 vs 两个向量”——欧氏(L2)、曼哈顿(L1)、闵可夫斯基(Lp)一脉相承。
  • 三种特殊兵器:二进制用汉明距离(异或数一数)、高维向量用余弦相似度(看夹角)、复杂对象用自定义距离(逐特征打分)。

好朋友,其实是你定义的一种距离

先回味上一节那个”猜英语成绩”的例子:物以类聚,人以群分——找几个最好的朋友,用他们成绩的均值来估计你的成绩。讲师现在把这件事挑明了:“好朋友”这个概念,实际上就是你定义的一种距离——有些人跟你比较好,代表你们俩之间的距离比较短;你跟某些人关系一般、甚至不好,代表你跟他的距离比较远。

所以根据不同的问题,需要定义不同的 distance measure——它在整个研究里都非常重要。讲师举了一个扎心的图像例子:

  • 两张都是”人”的照片,背景、光照各不相同——把它们的像素点对点直接做差,你会发现距离其实并没有多远(对应位置上的点大体能对上);
  • 但再来第三张照片:一个人,背景、头发颜色完全不一样,甚至人站的位置都不同——你也仍然认为他们和前两张是同一类(human),尽管点对点做差会得到很大的距离;而画面里那只 dog,才是真正”不同的东西”。

也就是说:数据的底层特征距离(low-level feature distance),跟人的认知距离(human perception distance)相差得很远。机器学习的很多问题,恰恰是关于 human perception 的——在人的认知距离空间里,谁跟谁更相近,也许更重要。怎么定义 distance measure,对以后研究各种问题很重要,这门课后面还会反复遇到。

一个合法的距离:四个条件

不是随便一个数都能叫”距离”。一个 distance measure dd 要被认可、能用于计算、在数学上 consistent,需要满足四个条件:

  1. 对称性:d(a,b)=d(b,a)d(a, b) = d(b, a)——A 到 B 的距离,和 B 到 A 的距离是一样的;
  2. 自身距离为零,且仅有此时为零:d(a,a)=0d(a, a) = 0——“我跟我之间的差别,一定是世界上最小的差别”;而且只有 a 是它自己时距离才为 0(if and only if a=ba = b),其他任何两个不同的点,距离都不为 0;
  3. 非负性:d(a,b)≥0d(a, b) \ge 0——距离不可能是负的;
  4. 三角不等式:d(a,b)≤d(a,c)+d(b,c)d(a, b) \le d(a, c) + d(b, c)——像三角形一样,两边之和不小于第三边:直着走,不会比绕道更远。

Look familiar:从模到距离

现在看常用的距离定义。课件上的标题就是一句反问:Look familiar(看起来眼熟吗)?——确实眼熟:还记得第一章求向量的模(norm)吗?L2 norm 是 x12+x22+⋯+xn2x_1^2 + x_2^2 + \cdots + x_n^2 再开二次根号,也就是向量的长度;还有 L1 norm、Lp norm 一家子。

模和距离就差一步:模是关于一个向量的长度,而 distance 是关于两个向量的。把这一步跨过去——两个点的距离,就定义为它们之差的模。讲师的原话:两个量之间的距离,可以定义为这两个量之差的模。

  • 欧氏距离(Euclidean distance,L2):差的 L2 模。这就是我们最熟悉的”连接两点的直线”——欧几里得空间里最自然的度量。
  • 曼哈顿距离(Manhattan distance,L1):差的 L1 模,即各维绝对值之和。为什么叫曼哈顿?曼哈顿是纽约的 downtown,修满了街区——想从 A 点到 B 点,你没法穿过楼房走直线,必须沿着街区走:在 x 方向走一段,再在 y 方向走一段。课件配图里:绿色的直线是 L2 distance,红色的折线是 L1 distance。
  • 闵可夫斯基距离(Minkowski distance,Lp):把”差的模”推广到一般的 Lp norm——各维之差的 r 次方加和,再开 r 次根号。r=2 是欧氏,r=1 是曼哈顿。

(有兴趣的同学,可以把这几个公式带回上面四个条件里验证——你会发现它们完全满足,所以都能放心地拿来衡量两点之间的远近。)

用代码感受一下”直线”与”沿街区走”的差别:

# 演示一:同一个 A 到 B,两种距离——直线 vs 沿街区走
A, B = (1, 1), (4, 5)

euclidean = ((A[0] - B[0])**2 + (A[1] - B[1])**2) ** 0.5   # L2:两点连直线
manhattan = abs(A[0] - B[0]) + abs(A[1] - B[1])            # L1:沿街区走

print(f"A{A} -> B{B}  欧氏距离(L2,直线): {euclidean:.0f}")
print(f"A{A} -> B{B}  曼哈顿距离(L1,沿街区): {manhattan:.0f}")
A(1, 1) -> B(4, 5)  欧氏距离(L2,直线): 5
A(1, 1) -> B(4, 5)  曼哈顿距离(L1,沿街区): 7

横向差 3、纵向差 4:直线距离是漂亮的 5(3-4-5 直角三角形),而沿街区要走 3+4=7。同一个起点终点,度量的定义不同,“远近”就不同——这正是本节的主题。

二进制数据:汉明距离与 MNIST 反例

前面是点与点(数值向量)之间的距离,其实还有很多不同的距离。第一个特殊场景:当两个特征都是二进制(binary)的时候——汉明距离(Hamming distance)。

它想做的事说白了很简单:有两串 0/1 字符,想知道这两串之间的距离是多少;有三串的时候,能知道哪两串之间距离更小、哪两串更大。算法也不难——求异或(XOR):按位比较,相同(0 对 0、1 对 1)记 0,不同(0 对 1、1 对 0)记 1,然后把结果累加。

# 演示二:汉明距离——按位异或(XOR),数一数有几个 1
def hamming(s1, s2):
    return sum(c1 != c2 for c1, c2 in zip(s1, s2))

x, y = "110011", "101010"
print(f"x = {x}")
print(f"y = {y}")
print("按位异或(不同记1):", "".join("1" if c1 != c2 else "0" for c1, c2 in zip(x, y)))
print(f"汉明距离 = {hamming(x, y)}(三个 1:有三个位置上的 0/1 不同)")
x = 110011
y = 101010
按位异或(不同记1): 011001
汉明距离 = 3(三个 1:有三个位置上的 0/1 不同)

讲师算例的锚点正是”三个一”:异或出来有三个 1,距离就为 3——代表有三位上的 binary 不同。

MNIST:汉明距离的用武之地与翻车现场

汉明距离看着简单到”似乎没什么用”,讲师立刻给了一个真实的用武之地:MNIST 手写数字数据集(0~9 的手写数字)。把一张 20×20 的数字图像变成 01 向量:黑色的点为 1、白色的点为 0——于是每一个数字图像都变成了一串 01 字符,可以直接拿去比较汉明距离。

但马上就有翻车现场。有同学说:这可能比得不好——比如”1”这个数字,把它写到另一个位置,它仍然是”1”,可两幅图几乎没有任何相交的部分,按位一比处处不同,汉明距离会非常大;但实际上它们俩是同一个数字——这种情况下也许就判错了。用 5×5 的小网格把这幕演出来:

# 演示三:MNIST 式反例——同一个"1"挪个位置,汉明距离就抓瞎
one_center = "00100" "00100" "00100" "00100" "00100"   # 居中的"1"
one_shift  = "00001" "00001" "00001" "00001" "00001"   # 右移两列的"1"(还是同一个数字)
two_center = "01110" "00010" "00100" "01000" "11111"   # 居中的"2"

def hamming(s1, s2):
    return sum(c1 != c2 for c1, c2 in zip(s1, s2))

print(f"d(居中1, 右移1) = {hamming(one_center, one_shift)}   # 同一个数字,只是挪了位置")
print(f"d(居中1, 居中2) = {hamming(one_center, two_center)}   # 真正不同的两个数字")
print(f"d(右移1, 居中2) = {hamming(one_shift, two_center)}")
d(居中1, 右移1) = 10   # 同一个数字,只是挪了位置
d(居中1, 居中2) = 10   # 真正不同的两个数字
d(右移1, 居中2) = 14

同一个”1”只是往右挪了两列,汉明距离(10)就已经和”1 vs 2”(也是 10)打成了平手——位置一变,汉明距离就抓瞎,这正是讲师说的”判错”情形。

但讲师紧接着给了一句很有分量的话:任何模型没有对错之分。如果你还不会更复杂的算法(比如 CNN 或其他),你至少可以用汉明距离做比较——仍然会得到一些准确率:5 和 5 之间、5 和 3 之间,总有相似的笔画能对上一些位置。这个 measure 或算法虽然很差,但确实仍然可以解决部分问题。

高维向量:余弦相似度

再介绍一个:余弦相似度(Cosine Similarity)。它想做的事情是——高维空间的两个向量 X 和 Y,求它们的余弦夹角:

cos⁡(x,y)=x⋅y∥x∥ ∥y∥\cos(x, y) = \frac{x \cdot y}{\Vert x \Vert \, \Vert y \Vert}

(分子是点积,分母是两个模的乘积。)余弦夹角代表这两个向量是比较相近还是不同:夹角小、方向一致,相似度高;夹角大,相似度低。

它在词向量上特别有用。以后我们会学到:把自然语言里的词映射成高维空间的向量(比如 LSA 一类的方法,映射到的语义空间 semantic space 以后会讲,现在不用理解是什么)。讲师给的例子正是三个词:apple、banana、boat——把它们映射到一个语义空间里,你会发现 banana 跟 apple 之间的夹角很小,而它们俩跟 boat 的夹角都很大——香蕉和苹果是一类东西,船是完全不同的另一类。

# 演示四:余弦相似度——词向量夹角,apple / banana / boat
def cosine(v, w):
    dot = sum(a * b for a, b in zip(v, w))
    nv = sum(a * a for a in v) ** 0.5
    nw = sum(b * b for b in w) ** 0.5
    return dot / (nv * nw)

apple  = [2.3, 1.8, 0.5, 1.1]
banana = [2.1, 1.9, 0.6, 1.0]
boat   = [0.3, -0.5, 2.8, -1.2]

print(f"cos(apple,  banana) = {cosine(apple, banana):+.4f}   # 夹角很小:一类东西")
print(f"cos(apple,  boat)   = {cosine(apple, boat):+.4f}   # 夹角很大:不同东西")
print(f"cos(banana, boat)   = {cosine(banana, boat):+.4f}")
cos(apple,  banana) = +0.9969   # 夹角很小:一类东西
cos(apple,  boat)   = -0.0133   # 夹角很大:不同东西
cos(banana, boat)   = +0.0168

apple 与 banana 的余弦相似度高达 0.9969——方向几乎重合;它们与 boat 的相似度都在 0 附近——接近正交、方向完全不同。(补充两条标准性质:余弦相似度取值在 [−1,1][-1, 1]——同向取 1、反向取 −1、垂直取 0;当距离用时通常取 1−cos⁡1 - \cos,称为余弦距离。)

复杂对象:自定义距离

最后一种情形最有意思:对象本身就不是向量——怎么衡量两个卡通人物的距离? 第 3 章讲聚类时我们用过《辛普森一家》的例子(按性别聚成两类、按是否一家人聚成两类)。讲师这次问的是:Marge、Patty、Selma 三个角色之间的距离应该怎么衡量? 这听起来就很难——总不能去求 Marge 跟 Patty 的 cosine 夹角吧?

办法是:我们人为地设计一些特征,逐项打分——指定若干特征,看两个角色在每个特征上是相同还是不同:不同记 1 分,相同记 0 分,累加起来就是距离。讲师带着大家数的四项:裙子颜色是否不同(一个绿色、一个红色,记 1 分)、是否戴耳环(一个有、一个没有,记 1 分)、高度是否相同(Marge 比较高、Selma 比较矮,记 1 分)、是否抽烟(Marge 不抽、Selma 抽,记 1 分);再加上最显眼的发型差别,凑成五项特征。结论:Marge 跟 Selma 的距离为 5,而 Patty 跟 Selma 的距离只有 3——Patty 和 Selma 都抽烟、体型也类似,自然更近。

# 演示五:自定义距离——Simpson 角色逐特征打分(不同记 1 分,相同记 0 分)
def role_distance(r1, r2, features):
    return sum(r1[f] != r2[f] for f in features)

features = ["裙子颜色", "戴耳环", "身高", "是否抽烟", "发型"]

marge = {"裙子颜色": "绿", "戴耳环": "否", "身高": "高", "是否抽烟": "不抽", "发型": "蓝色高发"}
selma = {"裙子颜色": "红", "戴耳环": "是", "身高": "矮", "是否抽烟": "抽",  "发型": "紫色短发"}
patty = {"裙子颜色": "紫", "戴耳环": "否", "身高": "矮", "是否抽烟": "抽",  "发型": "灰色卷发"}

print(f"distance(Marge, Selma) = {role_distance(marge, selma, features)}")
print(f"distance(Patty, Selma) = {role_distance(patty, selma, features)}")
distance(Marge, Selma) = 5
distance(Patty, Selma) = 3

课件还给这类”自定义度量”起了名字——edited measure,并给了它一个原味的定义:把一个对象变成另一个对象,量一量花了多大力气——“力气”的大小,就是距离。沿着这个思想,还能长出编辑距离(改几个字符能把一个词变成另一个词)这类常用度量——它们和逐特征打分一样,都是人为设计的 measure。

收尾:KNN 的两个旋钮

到这里,KNN 这一族算法的”可调旋钮”就齐了。讲师的收尾总结:KNN 是一个比较简单、直接的算法——找最近的邻居,直接投票,就能分类或预测。但里面可做的变形有几个部分:

  1. k 怎么选:k 特别小,容易 overfitting;k 比较大,得出的结果可能没有任何实际作用——选一个好的 k 非常重要(上一节已展开);
  2. 距离怎么选:如何选择 measure 来找到最近的这些邻居——本节给出的就是各种不同的可能性,尤其在不同向量空间下要选不同的距离;如果数据只是 binary,还有汉明距离。

所以,不是把这个算法拿来就用就好了——根据不同的数据、不同的问题,要重新考量这两件事。

📝 动手练一练

  1. 概念题:为什么说”好朋友”其实是你定义的一种距离?一个合法的 distance measure 需要满足哪四个条件?“两张人像照片点对点做差距离不远”的例子又说明了什么?
👉 点击查看参考答案

好朋友例:跟你比较好,就是你们之间距离短;关系一般甚至不好,就是距离远——“好朋友”这个概念就是一套自定义的距离。四个条件:对称性 d(a,b)=d(b,a)d(a,b)=d(b,a);自身距离为零、且仅当 a=ba=b 时为零(if and only if);非负性;三角不等式 d(a,b)≤d(a,c)+d(b,c)d(a,b) \le d(a,c)+d(b,c)。照片例子说明:数据的底层特征距离(点对点做差)与人的认知距离可能相差很远——机器学习很多问题是在人的认知距离空间里判断”谁跟谁更相近”(human perception),所以 distance measure 的定义是许多问题成败的关键。

  1. 手算余弦:取 v1=(3,4)v_1 = (3, 4)、v2=(6,8)v_2 = (6, 8)、v3=(−4,3)v_3 = (-4, 3)、v4=(−3,−4)v_4 = (-3, -4)。① 手算欧氏距离 d(v1,v2)d(v_1, v_2) 与 d(v1,v3)d(v_1, v_3);② 手算余弦相似度 cos⁡(v1,v2)\cos(v_1, v_2)、cos⁡(v1,v3)\cos(v_1, v_3)、cos⁡(v1,v4)\cos(v_1, v_4);③ 说说这组数把”方向 vs 大小”演成了什么样。
👉 点击查看参考答案

① d(v1,v2)=32+42=5d(v_1, v_2) = \sqrt{3^2 + 4^2} = 5;d(v1,v3)=72+12=50≈7.07d(v_1, v_3) = \sqrt{7^2 + 1^2} = \sqrt{50} \approx 7.07。

② v1⋅v2=18+32=50v_1 \cdot v_2 = 18 + 32 = 50,∥v1∥=5\Vert v_1 \Vert = 5、∥v2∥=10\Vert v_2 \Vert = 10,cos⁡(v1,v2)=50/50=1\cos(v_1, v_2) = 50 / 50 = 1;v1⋅v3=−12+12=0v_1 \cdot v_3 = -12 + 12 = 0,cos⁡(v1,v3)=0\cos(v_1, v_3) = 0;v1⋅v4=−9−16=−25v_1 \cdot v_4 = -9 - 16 = -25,∥v4∥=5\Vert v_4 \Vert = 5,cos⁡(v1,v4)=−1\cos(v_1, v_4) = -1。

③ v2v_2 只是 v1v_1 长度的两倍、方向完全相同:欧氏距离差出整整 5,余弦却说”完全一样”(1.0);v3v_3 与 v1v_1 垂直(0)、v4v_4 与 v1v_1 反向(−1)。这组数里余弦对长度完全免疫、只认方向——这正是它适合文本等高维场景的原因:一篇长文和一篇短文只要主题(方向)一致,余弦仍然认为它们很像。

本章小结

本节把 KNN 的”另一半”——距离——讲透了。距离不是天生给定,而是你定义的:好朋友是一种距离,不同问题需要不同的 distance measure;而底层特征距离与人的认知距离可能相差很远,机器学习很多时候正是在人的认知空间里工作。一个合法的距离要过四关:对称、自零(且仅有)、非负、三角不等式。从向量求模出发一步跨到距离(模管一个向量、距离管两个向量),欧氏(L2 直线)、曼哈顿(L1 沿街区)、闵可夫斯基(Lp)一脉相承。特殊数据有特殊兵器:二进制串用汉明距离(按位异或数 1)——MNIST 手写数字能用它做基线,但要小心”同一个数字挪个位置就判错”的翻车现场,而任何模型没有对错之分,简单模型也能解决部分问题;高维向量用余弦相似度看夹角(apple 与 banana 达 0.9969、与 boat 归零);复杂对象(Simpson 角色)则人为设计特征、逐项打分(Marge–Selma 为 5,Patty–Selma 为 3)。收尾把 KNN 的两个旋钮拧齐:k 怎么选、距离怎么选——算法简单,变形有学问。

📋 行动清单

学完本节,你可以立即:

  • 验一遍公式:把欧氏、曼哈顿、闵可夫斯基三个公式带回四个条件,亲手验证它们都满足(讲师留的作业)。
  • 设计一个距离:挑一个你熟悉的对象(两部电影、两首歌、两个 App),列 5 个特征,按”不同记 1 分”定义距离,给三件东西两两打分。
  • 跑一遍演示三:把”2”也右移两列再算汉明距离,体会位置失真对不同数字一样无情。

—— 小象教研组

配套资料 · 直接下载

课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。

  • 第5章课件:经典机器学习模型 I
    下载
  • 第5章笔记:朴素贝叶斯
    下载
  • 第5章代码包
    下载