ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

机器学习距离度量全解析:从欧氏到马氏距离的选型与实践

机器学习距离度量全解析:从欧氏到马氏距离的选型与实践 先问大家一个问题你第一次跑KNN或者K-Means的时候有没有想过为什么有时候换个距离度量模型效果能差出一大截或者说sklearn里那么多个metric参数到底选哪个才合适这个问题我早些年学的时候压根没当回事默认就欧氏距离结果项目里做用户偏好匹配怎么调参都怪怪的后来一顿排查才发现问题出在距离选择上。所以这篇入门系列的第二篇专门把距离计算这件事讲透。这里面不只是几个公式的问题它直接决定你的模型到底在“以什么标准判断相似”标准错了后面全是白费。距离计算在机器学习里到底有多基础可以说凡是涉及“相似性”的算法背后几乎都藏着一个距离公式。KNN要算样本间的距离K-Means要算样本到质心的距离SVM的核函数本质上是在高维空间里重新定义距离推荐系统里的协同过滤、内容相似度说的也是某种距离。换句话说距离度量是这些算法共同的底层地基。这篇就沿着“为什么需要距离、常见距离怎么算、适用什么场景、有哪些坑”这条线展开把机器学习中最常用的一组距离计算方式逐个拆开讲清楚并且附上可以直接用的代码实现和真实项目里的选型经验。无论你是刚看完吴恩达课程、正在刷周志华《机器学习》的入门读者还是写完了课程作业、想在比赛里认真调一手相似度计算的同学这篇应该都能帮你省下不少自己撞墙的时间。1. 为什么距离计算在机器学习里绕不开先理解“相似即距离”先别急着记公式。距离计算的意义不在于会套公式而在于它把你对“相似”的直觉转换成机器能算的数字。我们人类说“这两个东西像”脑子里其实是很模糊的。比如你觉得篮球和足球像因为都是球但又觉得篮球和排球更近一点因为体积、重量、用途都更像。这种判断如果让机器来做就必须把“像”变成一个个特征向量然后在向量空间里量出它们之间的“远近”。离得近就是相似离得远就是不同这是KNN、聚类、异常检测等一大批算法的基本假设。那这个“远近”怎么量不同量法得到的结论可能完全不同。举一个最直观的例子假设你只用两个特征去描述用户一个是用网时长小时/天一个是月消费金额元。用户A是4小时、80元用户B是5小时、5000元。如果用欧氏距离AB之间差得多远根号下(4-5)^2(80-5000)^2几乎是5000量级的差距。但如果你把两个特征都归一化到0到1之间AB的距离就变成很小的数。这里还没有换公式只是换了个尺度距离的变化就如此剧烈。所以在正式讲各种距离之前必须先建立两个认知第一特征向量的量纲和尺度会直接影响距离的数值。距离计算永远是“带量纲的”除非你做了标准化或者选用对尺度不敏感的度量。第二不同的距离度量捕捉的是“不同维度的相似”。欧氏距离看重绝对数值接近余弦相似度看重方向一致但允许数值幅度不同马氏距离则把特征之间的相关性也考虑进去了。选哪种取决于业务上你希望“什么样的样本算相似”。很多入门教程直接用sklearn调包把距离计算当成黑盒这是比较可惜的。因为当你真正开始做特征工程、跑聚类、设计搜索排序策略时你一定会撞上“距离失效”或者“距离选择不合理”的问题。到那时候再来补数学代价就大了。所以这一篇我们直接把这些距离的来龙去脉捋一遍。2. 先上最常用的三件套欧氏、曼哈顿、切比雪夫距离这三类距离是机器学习里最基础、出现频次最高的度量。它们都属于闵可夫斯基距离的特例但各自刻画的空间直觉完全不一样适用场景也差别很大。2.1 欧氏距离大多数人默认的那个“直尺距离”欧氏距离就是你在初中几何里学的两点间直线距离。两个n维向量 x(x1,x2,...,xn) 和 y(y1,y2,...,yn) 的欧氏距离定义为d(x,y) sqrt( (x1-y1)^2 (x2-y2)^2 ... (xn-yn)^2 )在二维平面上它就是“走直线”的距离。在机器学习里它是最常用的默认距离特别是在特征经过标准化、各维度意义相近的场景下效果往往很稳。比如KNN做鸢尾花分类用欧氏距离就是教科书级别的经典组合。但欧氏距离有个非常容易踩的坑它对量纲极其敏感。假设两个特征分别是“身高cm”和“体重kg”身高数值动辄170、180体重大几十欧氏距离计算时不同单位造成的数值差异会直接主导距离结果造成特征之间“隐性加权”。你不在意的话模型就会莫名其妙地偏向数值大的特征。所以用欧氏距离前我几乎总会先问一句特征做标准化了吗如果你在实战中跑聚类发现聚类结果总是被某一列特征带着走赶紧回去检查一下是不是没做标准化。2.2 曼哈顿距离沿着坐标轴走的“城市街区距离”曼哈顿距离形容的是你在纽约曼哈顿街区里从一个路口走到另一个路口只能沿着横平竖直的街道走不能斜穿街区。公式是每个维度差的绝对值之和d(x,y) |x1-y1| |x2-y2| ... |xn-yn|一句话说就是欧氏距离是直线飞过去曼哈顿距离是绕路走过去。那什么场景适合用曼哈顿距离几个典型的例子一是高维稀疏数据比如文本的TF-IDF向量、用户行为向量这些向量里大量元素是0非零的维度很少此时曼哈顿距离在度量上比欧氏距离更稳健不会因为个别维度差得大而把整体距离拉得很夸张。二是在L1正则化、Lasso回归这类模型里你其实也在间接地用“曼哈顿式的思路”理解误差空间。三是当你明确知道特征维度之间互相独立、且不存在“斜向”的语义关联时曼哈顿距离的直观含义更贴合业务。我在实际项目里用曼哈顿距离最多的场景是推荐系统里的用户行为序列相似度计算。用户对物品的评分向量通常极其稀疏0占了绝大多数欧氏距离在这种情况下很容易受单个评分差异的影响而曼哈顿距离对这种异常值没那么敏感跑出来的人找相似结果通常也更符合直觉。2.3 切比雪夫距离只看“差距最大的那一维”有多远切比雪夫距离的公式是d(x,y) max(|x1-y1|, |x2-y2|, ..., |xn-yn|)意思很直白两个向量之间的距离等于所有维度里差距最大的那一个维度的差距。国际象棋里的国王走一步横竖斜都能走一格从格子A走到格子B需要的步数就是切比雪夫距离。在二维格子地图里它对应的是“8方向移动”的最短步数。这个距离在机器学习里出现的频率比前两个低但在特定场景非常有用。最典型的就是物流、游戏寻路、路径规划这类任务动作空间是离散网格、允许8方向移动那切比雪夫距离就是对真实代价的精确模拟。图像处理里如果做像素级别的比较有时也会用到。不过说实话在常规的表格数据机器学习任务里切比雪夫距离用得很少。因为它只关心最大差距容易忽略整体形态差异。如果两个样本在99个维度上都高度一致只在一个维度上有巨大差异切比雪夫距离会直接给出一个很大的数值这在有些场景下是合理的比如安检、异常检测里某个指标爆表就是大问题但在很多普通相似度场景里反而不够细腻。2.4 闵可夫斯基距离三件套的统一视角闵可夫斯基距离把上面三兄弟统一到了一个公式里d(x,y) ( ∑|xi - yi|^p )^(1/p)p1时就是曼哈顿距离p2时就是欧氏距离p→∞时就是切比雪夫距离。这个视角最大的价值不是“多了一个公式”而是帮你理解p值越大距离越受“差距大的维度”支配p值越小各维度的差距被更平均地纳入考量。如果你懒得纠结选p直接记住两条经验特征经过标准化、维度语义相近时p2欧氏通用性最好。特征稀疏、含异常值、或者你希望更均衡地看待各维度差距时p1曼哈顿往往更稳。p值越大比如p3、4对极端差距越敏感除非业务明确要求“某个维度差得离谱就算不相似”否则不要盲目调高。3. 方向比长度更重要余弦相似度与皮尔逊相关系数欧氏距离度量的是“绝对位置差异”但在很多场景里我们需要的是“方向是不是一致”。比如两个用户虽然评分高低不同但偏好结构完全一致这时候用欧氏距离一个人全打3分、一个人全打5分会被判定为不相似。但实际上这两个人的口味是高度吻合的只是打分尺度不一致而已。这种场景就是余弦相似度的主场。3.1 余弦相似度几何里的夹角哲学余弦相似度计算的是两个向量在n维空间里的夹角余弦值cos(θ) (x·y) / (|x| × |y|) ∑(xi × yi) / ( sqrt(∑xi^2) × sqrt(∑yi^2) )注意这里算出的是“相似度”数值越大越相似范围从-1到1在非负向量下是0到1。如果你非要把它变成“距离”通常会用 1 - cos(θ)或者 arccos(cos(θ)) 得到角度距离。余弦相似度最经典的应用场景就是文本分析和推荐系统。文本的TF-IDF向量天生是稀疏、高维、大量0的如果用欧氏距离文档长度差异会对结果造成巨大干扰而余弦相似度天然对向量的模长不敏感它只看方向所以无论一篇长文还是一篇短文只要词的分布比例接近就会被判定为相似。这在搜索引擎、文档聚类、文章去重里几乎是标配。我自己的体会是做文本相关任务时先把文本转向量、然后用余弦相似度是一个可以无脑起步的默认方案。等到效果不够理想了再去考虑TF-IDF的权重设置、降维、或者换语义向量不用一开始就整很复杂的模型。3.2 皮尔逊相关系数去中心化的余弦相似度皮尔逊相关系数公式是r ∑(xi - x̄)(yi - ȳ) / ( sqrt(∑(xi - x̄)^2) × sqrt(∑(yi - ȳ)^2) )如果你仔细看这个公式会发现它就是“先对每个向量做去均值处理再计算余弦相似度”。也就是说皮尔逊相关系数在计算之前先把每个用户、每个文档各自的特征均值减掉了所以它衡量的是“相对波动是否一致”而不只是绝对大小是否一致。这个特性在推荐系统里特别关键。假设用户A给《流浪地球》打5分、《满江红》打1分用户B给《流浪地球》打4分、《满江红》打2分。两个人的绝对分差了很多但相对偏好完全一致都更喜欢前者、不太喜欢后者。此时皮尔逊相关系数会给出很高的相似度而余弦相似度就会受到打分尺度差异的干扰。这里有一个很值得记住的补强视角如果业务数据里存在明显的“系统性偏差”比如部分用户天生爱打高分、部分用户爱打低分或者部分新闻天生阅读量高、部分偏低那用去中心化的皮尔逊相关系数通常比直接算余弦相似度更合理。但如果“数值的绝对大小”本身就代表业务含义比如收入、点击量、活跃时长那就应该谨慎使用去中心化直接把均值减掉反而会丢失关键信息。3.3 余弦相似度的两个经典坑第一个坑是余弦相似度只适用于“非负向量”时才能保证结果在0到1之间。如果向量里含负值余弦相似度可能出现负数此时解释起来就要小心负值到底意味着“相反”还是“无关”需要结合业务判断。第二个坑更隐蔽余弦相似度对向量的模长完全不敏感。也就是说一个用户只看了一部电影并给了5分另一个用户看了50部电影、平均分也是5分在余弦相似度下这两个用户的偏好向量可能被判定为高度相似。这在冷启动和小样本场景下会产出不靠谱的结果。解法也很简单如果业务希望“行为丰富的用户”和“行为稀疏的用户”区分开就不能只靠余弦相似度得结合向量模长、行为次数等做约束。4. 离散数据与集合数据的社会汉明距离、杰卡德距离、编辑距离前面讨论的距离都是基于连续数值向量的。但机器学习里面还有一大类数据是离散的、二值的、甚至是不定长的。处理这些数据时连续距离公式直接就不适用了得换一套度量逻辑。4.1 汉明距离两个等长字符串有多少位不一样汉明距离的公式非常简单对两个等长的字符串或等长的二进制向量统计对应位置不相同的个数。它衡量的不是“数值差距有多大”而是“有多少个位置对不上”。汉明距离最典型的应用是通信纠错编码、信息论但在机器学习里也有它的位置。比如DNA序列对比、二进制特征向量的相似度计算、图像感知哈希pHash的比对。用pHash做相似图片去重时两张图片算出的哈希指纹之间的汉明距离越小说明图片越相似。这是搜索引擎和相册应用里很常用的一个手段。汉明距离最大的限制是要求两个向量等长。如果你想比较“abcde”和“abxde”汉明距离是1非常简单但如果你比较“abcd”和“abxde”长度都不一致汉明距离就没法直接用了这时候得请出编辑距离。4.2 杰卡德距离集合视角的“重合度”杰卡德相似系数的定义是J(A,B) |A∩B| / |A∪B|也就是两个集合的交集大小除以并集大小。杰卡德距离则是 1 - J(A,B)。这个距离非常适合处理二值的稀疏向量。举个最典型的例子用户A买了商品{a, b, c}用户B买了商品{a, b, d, e}。交集是{a,b}并集是{a,b,c,d,e}杰卡德相似度是2/50.4。这个数字反映的是“重合的商品种类”相对于“总共涉及的商品种类”有多少非常自然。杰卡德距离在协同过滤推荐、标签系统、社交网络好友推荐里非常常见。跟余弦相似度相比杰卡德更看重“集合重合度”不会因为用户买了50个东西、另一个用户买了3个东西就简单地用比例关系硬套。它对稀疏二值数据更友好。我自己的经验是当特征是0/1的偏好标签、行为标记时优先考虑杰卡德距离而不是欧氏距离或余弦距离。比如做基于“用户收藏了哪些文章”的相似用户推荐直接用杰卡德相似度解释性强、效果好还零调参。4.3 编辑距离文本相似度的“最小代价”编辑距离Levenshtein Distance衡量的是把一个字符串变换成另一个字符串所需的最少编辑操作次数编辑操作包括插入、删除、替换。比如把“kitten”变成“sitting”k→s替换e→i替换插入g一共3次操作所以编辑距离是3。编辑距离在机器学习里最常见的应用就是文本纠错、模糊匹配、命名实体对齐。搜索引擎的“你是不是想找xxx”就大量依赖编辑距离在数据清洗阶段两个来源的客户名单要做记录匹配公司名称写法五花八门编辑距离是判断“这两条是不是同一个公司”的重要工具。不过要注意编辑距离虽然是文本相似度的经典方法但它的计算复杂度是O(m×n)比赛或者大规模场景直接跑会非常吃力。工程上通常会结合倒排索引、SimHash等手段先做粗筛只对候选集中的字符串细算编辑距离。5. 量纲与相关性的双重救星马氏距离以及你真的需要它吗前面讲的距离都有一个共同点默认各个特征维度是独立同尺度的。但真实数据里特征之间往往存在相关性。比如“身高”和“体重”在人群中就是强相关的体重的方差也大。欧氏距离在这种数据下会让人觉得体重维度的数值波动天然就大于是它很容易主导距离。马氏距离就是专门来解决这个问题的。5.1 马氏距离的核心思想把空间“矫正”成圆形再量马氏距离的公式是d(x,y) sqrt( (x-y)^T S^(-1) (x-y) )其中S是数据的协方差矩阵。用大白话说马氏距离在计算前先根据数据的协方差结构对空间做了一个“扭曲矫正”再量距离。这个变换的效果有几个非常明显的优点第一个优点是自动消除量纲影响。它不再需要你手动做标准化因为它自己考虑了每个维度的方差。第二个优点是考虑了特征之间的相关性。假设两个特征有很强的正相关数据点大体分布在一根斜线上。欧氏距离会觉得线上相距很远的两个点差异很大但马氏距离会认为沿着这个“数据的主趋势方向”移动并不算异常只有偏离趋势方向才算真正的差异。这跟业务逻辑非常吻合身高190的人配体重100kg很正常身高160配体重100kg就很异常。5.2 马氏距离的适用场景和现实问题马氏距离最经典的应用是多元异常检测。给定一堆正常样本计算样本总体的均值向量和协方差矩阵然后检测新样本的马氏距离是否过大超过阈值就判为异常。这在金融风控、工业质检、传感器故障检测里都有落地。但马氏距离有几个现实问题使用前要想清楚第一协方差矩阵需要足够多的样本才能估计稳定。如果你的特征维度是50维而样本量只有几百协方差矩阵估计会非常不稳定算出来的马氏距离基本没意义。经验上样本数至少应该是特征维度的5到10倍越多越好。第二协方差矩阵必须可逆也就是说特征之间不能存在完全共线性。如果你有两个特征是严格线性相关的S矩阵就奇异了公式直接算不了必须先做降维或正则化处理。第三马氏距离在特征关系非线性时也会失效。它假设数据大致服从一个椭圆形的多元正态分布如果真实数据结构是弓形、环形马氏距离的“矫正”方式就不对了。所以我的实践建议是普通入门项目先用欧氏距离或者曼哈顿距离配上标准化就够用当你的任务是异常检测或者你明显发现特征之间有强相关性、并且希望距离度量能体现这种相关性时再认真考虑马氏距离。5.3 和“先标准化再做欧氏距离”有什么区别这是很多人会问的一个问题先做标准化再算欧氏距离跟直接算马氏距离是不是一回事答案是不完全一样。标准化把所有特征都拉到方差为1但特征之间的相关结构没有被考虑。标准化后的欧氏距离默认把整个特征空间当成一个各向同性的球体而马氏距离会根据协方差矩阵把空间矫正成“数据实际形状”。如果特征完全不相关标准化后的欧氏距离和马氏距离是等价的一旦特征之间有相关关系两者就分道扬镳了。举一个场景两个特征强正相关数据整体落在一条斜线带里。标准化后的欧氏距离会把斜线带内的两个相距较远的点判为“远”而马氏距离会认为它们在数据主方向上是接近的。如果你的业务认为“沿着主趋势的差异不算异常”马氏距离就更合适如果你觉得“只要数值差得远就算不一样”那标准化的欧氏距离反而更符合直觉。6. 高维空间中的距离失效问题为什么特征多了距离反而不好用这个问题的严重性我一开始没意识到直到做文本分类时才深有体会。特征是几千维的TF-IDF向量算出来的样本间距离怎么看怎么不对劲所有样本之间的距离都变得差不多大KNN的分类准确率疯狂下降。后来才明白这就是所谓的“维数灾难”在距离度量上的一种体现。6.1 高维下为什么距离会趋同在高维空间里大部分成对样本的距离都会趋于同一个数值。直觉上可以这样理解欧氏距离是多个维度差值的平方和开根号。当维度特别多时每个维度上都有一些随机差异这些差异加在一起会使得“距离远近”主要被维度数决定而不是被你关心的“特定维度的相似性”决定。用极端一点的例子来感受如果每个特征在[0,1]均匀分布二维空间里两个随机点的距离分布还比较分散到了100维、1000维大多数点对的欧氏距离会集中在某个窄区间内。这种情况下“最近邻”和“最远邻”的距离差别变得微乎其微KNN想靠距离找近邻几乎是在一堆差不多的数值里硬挑一个效果自然不行。6.2 怎么应对距离失效面对高维数据最有效的办法通常不是换距离公式而是降维。PCA、SVD、t-SNE、UMAP这些方法把高维向量压缩到几十维甚至更低把有价值的结构保留下来再做距离计算效果往往会大幅提升。其次可以用对高维更鲁棒的距离度量。比如余弦相似度在文本高维向量上就明显比欧氏距离稳因为它在归一化方向后对向量的绝对长度不敏感。曼哈顿距离在高维下饱受维数灾难的影响也相对小于欧氏距离因为它是线性累加不会因为平方项让距离快速膨胀。还有一招是特征选择。强行把跟任务无关的维度去掉往往比硬着头皮在高维空间里调距离更管用。比如文本向量可以先做卡方检验、互信息筛选把噪声特征去掉再算距离。6.3 实际项目里识别距离失效的信号怎么判断你的项目里距离已经开始失效两个很明显的信号训练集里不同类别的样本算出来的类内距离和类间距离差别非常小模型几乎是在随机边界上徘徊。你对某个阈值做灵敏度分析时无论阈值怎么调结果都差不多。这说明距离数值已经失去了区分度。如果遇到这两种情况最该做的不是继续调距离公式而是回头审视特征表示和降维。很多时候问题不在“距离怎么算”而在“特征空间本身有没有信息量”。7. 距离选型速查与一个小实验讲了这么多距离计算公式和适用场景最后落地的时候还是需要一张速查表。基于我自己的实战经验整理如下场景推荐距离/相似度理由表格数据特征已标准化维度不高欧氏距离通用、直观、效果稳定稀疏数据含大量0如行为向量、文本TF-IDF余弦相似度 / 曼哈顿距离对稀疏和异常值更稳方向语义清晰用户打分存在评分尺度偏差皮尔逊相关系数去中心化关注相对偏好二值集合数据是否购买、是否点击杰卡德距离天然适合集合重合度等长离散序列、哈希指纹汉明距离直接统计位差异变长字符串匹配、纠错编辑距离支持插入删除替换异常检测特征相关性强马氏距离考虑协方差结构高维数据先降维再用欧氏距离或余弦缓解维数灾难我一直认为距离选型这事最忌讳的就是拍脑袋。反正我每次换一种距离之前都会先在数据上做个小小实验来验证不凭感觉。这里分享一个简单的实验思路从你的数据集里挑一批“业务上明确知道相似”的样本对和“明确不相似”的样本对分别用几种距离算出数值画出分布图看看哪种距离能把这两类样本对分开。哪个距离分得开就选哪个。这个办法不需要任何机器学习模型十行代码就能跑完但比纠结公式理论管用得多。我用Python的numpy手写过一个简单脚本做这种验证思路类似import numpy as np def euclidean(a, b): return np.sqrt(np.sum((a - b) ** 2)) def manhattan(a, b): return np.sum(np.abs(a - b)) def cosine_dist(a, b): cos_sim np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b) 1e-9) return 1 - cos_sim def jaccard_binary(a, b): # a, b 为0/1向量 inter np.sum((a 0) (b 0)) union np.sum((a 0) | (b 0)) return 1 - inter / union if union 0 else 0.0跑的时候把已知相似与不相似的样本对都过一遍对比它们在各种距离下的分布重叠程度很快就能选出最合适的度量。这个工作基本上在每个项目初始阶段我都会做一次花不了几分钟却能帮你避免在错误的方向上深耕。8. 我踩过的距离选型相关的坑写出来给大家避一避最后聊几个我自己在真实项目里踩过的坑。篇幅不多但每一条都是用出bug、调秃头换来的。第一个坑是不做标准化就直接上欧氏距离。当时做一个多特征聚类的项目特征是用户年龄、收入、消费频次收入这个特征的数值比其他特征大几个数量级结果跑出来的聚类结果几乎只按收入切分。后来把三个特征都做了标准化效果才正常。这件事教会我一个铁律用欧氏距离前先检查特征的数值范围差异必要时做标准化或者归一化。第二个坑是在高维稀疏数据上用欧氏距离做KNN。那时做一个文本分类需求直接把TF-IDF矩阵丢进KNN准确率只有50%多怎么调都上不去。后来意识到问题在于欧氏距离对高维稀疏向量不友好换成余弦相似度准确率爬到了80%以上。这在文本场景里几乎是一个可复现的经验规律。第三个坑是对用户评分数据直接用余弦相似度忽略了评分尺度偏差。两个用户一个喜欢打4~5分一个喜欢打2~3分但他们的相对喜好其实完全一致。直接算余弦相似度两个人的相似度不高。改成皮尔逊相关系数后相似用户推荐才有明显改善。从那以后我在做协同过滤时都会先看一眼打分的分布再决定用哪一种相似度。第四个坑可能也是最容易被忽略的把多个不同类型的距离混在一起用却不做归一化。比如说你要综合文本相似度和价格相似度文本相似度算出来是0附近的余弦值价格差异算出来可能是几百块两个数直接相加价格就完全主导了结果。这种跨类型的距离融合必须先分别做归一化或者排序转换再相加不然后果就是“融合了个寂寞”。讲真距离计算这块的坑大多不是因为公式难而是因为太容易被当成“理所当然的默认项”。入门阶段多花五分钟想一想“我选的这个距离到底在衡量什么”往往比多调一组超参数更能提升模型效果。这篇把基础的距离计算方式、适用场景和坑位都铺开讲了希望你在自己动手做KNN、聚类或者推荐的时候多一分对“相似性”本身的理解。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进