ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

马氏距离:原理、几何意义与异常检测实践

马氏距离:原理、几何意义与异常检测实践 做多维数据分析和机器学习这几年我越来越觉得“距离度量”这事儿被很多人低估了。模型调参调得再多特征工程做得再花哨底层如果用的是一种不适合场景的距离计算方式那所有上层工作都等于在沙子上盖楼。欧氏距离大家都会用但一旦数据各个维度之间不是独立的、方差还各不相同欧氏距离给出的答案就可能错得离谱。这时候马氏距离Mahalanobis Distance就派上用场了。马氏距离这个名字你可能听过但很多教程要么只丢出一个公式要么把几何意义讲得云里雾里。这篇文章我想把马氏距离从定义到推导、再到几何意义和实际代码完整地捋一遍。核心解决一个问题当数据的各个维度存在相关性且尺度不一致时如何正确地衡量一个点到一群数据之间的距离。适合正在做异常检测、分类聚类、或者对统计度量方法有需求的朋友阅读尤其是那些已经用过欧氏距离但总觉得哪里不对劲的读者。1. 从欧氏距离说起为什么需要马氏距离1.1 欧氏距离的两个致命伤先回忆一下欧氏距离的公式。在二维平面上点 (A(x_1, y_1)) 和点 (B(x_2, y_2)) 之间的距离就是[ d \sqrt{(x_1 - x_2)^2 (y_1 - y_2)^2} ]推广到 (n) 维就是各维度差值的平方和再开方。这个公式本身没有任何错误但它隐含了两个非常强的假设现实中经常不成立。第一个假设是各维度之间相互独立。欧氏距离在计算时每个维度是单独平方求和完全忽略了维度与维度之间的关联。现实中的数据比如身高和体重这俩从来就不是独立的个子高的人体重往往也更重。如果你直接拿欧氏距离去衡量两个人的“体型差异”高瘦的人和一个矮壮的人算出来距离可能很大但在医学上这两个人的体型特征可能非常接近“正常”。第二个假设是各维度的尺度方差相同。还是举身高和体重的例子一个维度单位是厘米范围可能在150到190之间另一个维度单位是公斤范围可能在40到100之间。身高的数值天然就比体重大欧氏距离计算的时候身高那项的贡献会碾压体重那项。你相当于在做加权的时候稀里糊涂地给方差大的维度赋予了更大的权重这往往是反的。1.2 一个直观的二维案例我们用一组模拟数据说明。假设有两个特征 (x) 和 (y)它们之间有较强的正相关性散点图呈现一个沿着45度方向拉长的椭圆分布。数据均值在 ((\mu_x, \mu_y))。现在我来问你一个很实际的问题均值点附近的这两类点用欧氏距离看哪个离中心更远点 A 在 (x) 方向上偏离了1个标准差在 (y) 方向上偏离了1个标准差方向是沿着椭圆短轴。点 B 在 (x) 方向上偏离了2个标准差在 (y) 方向上偏离了2个标准差方向是沿着椭圆长轴。如果把数据标准化到方差为1点A的欧氏距离是 (\sqrt{1^21^2} \approx 1.41)点B的欧氏距离是 (\sqrt{2^22^2} \approx 2.83)。欧氏距离告诉你点B离中心更远。但在真实的分布里点B所在的椭圆长轴方向数据本来就非常分散偏离3个标准差在长轴方向上太常见了。而点A所在的短轴方向数据极其紧凑偏离1个标准差已经属于极端离群。因此如果你要做异常检测点A才是那个更该被标记的点。这就是欧氏距离的盲区。它不关心数据的分布形状只关心绝对坐标差。马氏距离要解决的正是这个问题它读懂了数据的“方差”和各维度的“相关性”把分布的形状考虑进去之后再来算距离。这才是它被称为“马氏距离”而不是马氏发明的欧氏距离的根本原因——它在做一件语义上完全不同的事情。2. 马氏距离的数学定义与完整推导2.1 定义式与符号约定马氏距离的定义简洁得令人惊讶。对于一个均值为 (\mu)、协方差矩阵为 (\Sigma) 的多维数据集任意一点 (x) 到该分布的马氏距离是[ D_M(x) \sqrt{(x - \mu)^T \Sigma^{-1} (x - \mu)} ]其中(x) 是一个 (n) 维列向量是你要计算距离的样本点(\mu) 是数据集的均值向量也是 (n) 维列向量(\Sigma) 是 (n \times n) 的协方差矩阵刻画了各维度之间的相关性和各自的方差(\Sigma^{-1}) 是协方差矩阵的逆矩阵。这个公式如果拿掉中间的 (\Sigma^{-1})剩下的 ((x - \mu)^T (x - \mu)) 就是欧氏距离的平方。所以你可以把马氏距离理解为在协方差矩阵的“逆”作为度量矩阵下的二次型距离。关键在于这个 (\Sigma^{-1}) 到底做了什么。2.2 推导演练从标准化到去相关我们先来看一个特殊情况假设数据各维度独立且方差都为1也就是 (\Sigma I)单位矩阵。那么 (\Sigma^{-1} I)马氏距离退化成欧氏距离。这很好理解在标准化的独立空间中两者没有区别。再进一步假设数据各维度独立但方差不同比如二维数据 (x) 方向的方差是4(y) 方向的方差是1[ \Sigma \begin{pmatrix} 4 0 \ 0 1 \end{pmatrix} ]这时候 (\Sigma^{-1} \begin{pmatrix} 1/4 0 \ 0 1 \end{pmatrix})。你看逆矩阵的对角线元素其实就是在给每个维度的差值平方做“归一化”加权。(x) 方向的方差大除以一个大的数等于把该维度的贡献缩小了(y) 方向的方差小除以一个小的数等于把该维度的贡献放大了。这一步做完相当于把原始数据在各维度上按照标准差进行标准化转换到方差为1的“圆”形空间中。这就是马氏距离处理尺度差异的方式。但是如果各维度之间存在相关性协方差矩阵的非对角线元素不为0光做标准化还不够。这时需要通过线性变换把数据去相关这一步才是真正把马氏距离和欧氏距离拉开差距的地方。我们做一次矩阵分解。因为马氏距离是二次型(\Sigma^{-1}) 是实对称正定矩阵可以进行 Cholesky 分解或者从几何角度做特征值分解[ \Sigma V \Lambda V^T ]其中 (V) 是特征向量矩阵(\Lambda) 是特征值对角矩阵。那么[ \Sigma^{-1} V \Lambda^{-1} V^T ]把这个代回马氏距离公式[ D_M(x) \sqrt{(x - \mu)^T V \Lambda^{-1} V^T (x - \mu)} ]令 (y V^T (x - \mu))也就是把 (x - \mu) 旋转到特征向量的坐标轴上。此时[ D_M(x) \sqrt{y^T \Lambda^{-1} y} \sqrt{\sum_{i1}^{n} \frac{y_i^2}{\lambda_i}} ]看到没有这个式子就是若干个标准化后的分量的平方和再开方。整个过程分了两步旋转用 (V^T) 把坐标轴旋转到与数据的主轴方向对齐缩放除以对应主轴的 (\lambda_i)把各个方向的尺度归一化。旋转把相关性去掉了缩放把方差差异去掉了。做完这两步数据在新空间里变成一团近似球形的分布此时再用欧氏距离就真正合理了。这就是马氏距离的全部推导核心它等价于“白化”或“Sphere”变换之后的欧氏距离。2.3 特殊情况马氏距离如何退化为欧氏距离理解退化条件能帮你更清醒地判断该用哪个。当 (\Sigma I) 时协方差矩阵是单位阵各维度独立且同方差马氏距离 欧氏距离。当 (\Sigma) 是对角阵时各维度独立但方差不同马氏距离 在各维度上用标准差标准化后的欧氏距离。当 (\Sigma) 是任意正定矩阵时存在相关性马氏距离带回相关性修正的欧氏距离。这意味着欧氏距离其实是马氏距离在一个非常严格条件下的特例。如果你的数据维度之间强相关直接用欧氏距离本质上就是在用一个错误的度量矩阵 (I) 去替代真实的 (\Sigma)结果自然不准。反过来如果你确定数据各维度独立且同方差马氏距离没什么特别的优势用欧氏距离反而省去了估计协方差矩阵的风险。所以我的建议是不确定就先算一下数据协方差矩阵看一眼非对角元素多大再决定距离度量的选型。这个习惯能帮你规避掉大量基础性的误差。3. 几何意义一句话讲透3.1 白化变换视角马氏距离的几何意义用一句话概括在原始坐标空间里用一个与数据分布形状相匹配的椭圆或椭球来衡量距离而不是用一个固定的圆。我们回到那个椭圆分布的例子。数据分布拉成一个45度方向的椭圆长轴方向方差大短轴方向方差小。欧氏距离走的是圆——不管哪个方向走相同的长度就是相同的距离完全不理会椭圆的形状。马氏距离走的是椭圆——它定义了一个“与数据分布同形状”的椭圆在这个椭圆内马氏距离小于某个阈值在椭圆外马氏距离大于某个阈值。沿着短轴方向走一点点就很容易跑出椭圆因为椭圆在短轴方向很窄沿着长轴方向走很多可能还在椭圆内因为椭圆在长轴方向很宽。这个思路可以通俗地比作“鱼眼中的世界”。数据分布的大致范围是一块鱼塘鱼塘是个狭长的湖。欧氏距离问你“从湖中心直线游到某个点有多远”马氏距离问你“从湖中心到某个点需要按湖的轮廓走多远”——在狭窄的方向上稍微偏离就出界了。3.2 特征分解视角从线性代数的角度看协方差矩阵的每个特征向量定义了椭球的一个主轴方向特征值定义了该主轴方向的长度平方。 (\Sigma^{-1}) 的特征向量和 (\Sigma) 一致但特征值是 (\Sigma) 特征值的倒数。这个“取倒数”太关键了。特征值大的方向原始数据方差大、分布散这也是马氏距离认为“数据在这个方向上本来就会跑得远”的地方所以对距离的惩罚反而小特征值小的方向数据分布紧凑稍微偏离一点就非常反常所以对距离的惩罚极大。于是马氏距离的等距面就是一个与数据分布同长轴、但长短轴互换过的椭圆——本质上是把原始椭圆校准成标准圆。几何上我们经常这么说马氏距离把原始数据空间映射到标准化的特征空间在这个空间里每一维的方差都是1且维度之间正交去相关然后在这个规范空间里算欧氏距离。这正是白化Whitening变换。用任何工具做白化时你实际上就是在算马氏距离的前半段。3.3 椭球与协方差矩阵等马氏距离面其实就是一个椭球。对于给定阈值 (c)满足[ (x - \mu)^T \Sigma^{-1} (x - \mu) c^2 ]的所有点构成了一个以为 (\mu) 中心的超椭球。这个超椭球在数据分布的形状协方差矩阵下包含了数据点总数的某个百分比。在多元高斯分布的假设下这个 (c^2) 服从卡方分布自由度 数据维度 (n)所以可以通过卡方分布的分位数来确定异常阈值。比如二维数据取 (c \sqrt{\chi_{2, 0.95}^2} \approx \sqrt{5.991} \approx 2.448)马氏距离超过这个值的点在95%置信度下可视为离群点。这就是马氏距离在统计学上比欧氏距离最大的优势——它不是拍脑门定的阈值而是有严格概率分布的理论支撑。3.4 与高斯分布的深度关联如果 (x) 服从多元高斯分布 (N(\mu, \Sigma))其概率密度函数的指数部分为[ -\frac{1}{2}(x - \mu)^T \Sigma^{-1} (x - \mu) ]你发现了什么括号里的部分就是马氏距离的平方。所以马氏距离的平方与高斯分布的指数项只差一个 (\frac{1}{2}) 的系数。换句话说马氏距离越小样本点在该分布下的概率密度就越高马氏距离越大密度就越低。在多个多维高斯分布做分类时如高斯判别分析GDA比较“一个点到哪个类的马氏距离更小”等于比较它在哪个高斯分布下的概率密度更大这和贝叶斯决策规则是自洽的。这也是为什么在马氏距离的监控中我经常跟团队说“马氏距离不是一个拍脑袋发明的距离公式它是从高斯分布和统计推断中自然长出来的。” 理解了这层关系你在做分类器、判别分析、异常检测时才能真正把工具用对地方。4. 实操代码实现与应用要点4.1 手写一个马氏距离函数代码是最直接的检验方式。用 NumPy 十几行就能写出马氏距离的函数。第一步计算均值向量和协方差矩阵第二步直接用 np.linalg.pinv 求伪逆用伪逆而不是逆有讲究见后文第三步就是按公式批量计算。import numpy as np from scipy.spatial.distance import mahalanobis def compute_mahalanobis_distance(X, xNone): 计算数据集中每个点相对于整体分布的马氏距离 X: (n_samples, n_features) 原始数据 x: 单个待测点默认为None即计算X中所有点的马氏距离 返回: (n_samples,) 或标量 mu np.mean(X, axis0) cov np.cov(X, rowvarFalse) cov_inv np.linalg.pinv(cov) if x is not None: delta x - mu return np.sqrt(delta cov_inv delta) deltas X - mu # 批量计算 (n_samples, n_features) (n_features, n_features) (n_samples, n_features).T return np.sqrt(np.einsum(ij,jk,ik-i, deltas, cov_inv, deltas)) # 模拟数据身高体重存在正相关 rng np.random.default_rng(42) height rng.normal(170, 10, 500) weight 0.6 * height rng.normal(0, 8, 500) X np.column_stack([height, weight]) # 用自己写的函数 dist_mine compute_mahalanobis_distance(X) # 用scipy内置函数交叉验证 dist_scipy np.array([mahalanobis(x, X.mean(axis0), np.cov(X, rowvarFalse)) for x in X]) print(np.allclose(dist_mine, dist_scipy)) # 输出 True说明实现正确这里有两个细节值得说说。第一协方差矩阵计算用np.cov(X, rowvarFalse)表示每一列是一个特征千万别把rowvar参数搞反了否则你算出来的是维度之间的协方差矩阵的转置结果全错。第二求逆用np.linalg.pinv伪逆而不是np.linalg.inv是因为在实际数据中特征之间若存在高度相关协方差矩阵可能接近奇异直接求逆会爆出无穷大的距离或者内存溢出伪逆能对这种病态问题给出一个最小范数解虽然不完美但至少不会直接崩溃。4.2 异常检测案例马氏距离在异常检测里的用法非常成熟。我拿一个自己跑过的工业场景举例某个设备有温度、振动幅度、转速三个传感器每天采集24小时的均值连续采集60天形成一个60行3列的数据矩阵。目标找出设备运行状态发生异常的日期。具体操作步骤如下数据清洗剔除明显超出物理量程的坏点计算均值向量与协方差矩阵用全部60天的数据估算计算每天的马氏距离确定阈值假设数据近似服从三维高斯分布查卡方表三维自由度下95%分位数约为 (\chi_{3, 0.95}^2 \approx 7.815)所以马氏距离阈值取 (\sqrt{7.815} \approx 2.795)标记异常马氏距离大于阈值的日期标记为异常。当我按照这个流程跑完一遍数据发现第37天的马氏距离高达5.6远远超过阈值。但如果用欧氏距离排序第37天连前10都进不去。为什么因为这设备在第37天温度和振动都只是小幅波动但两者原本强相关的模式被打破了。协方差矩阵捕捉到了两个特征之间的同步性而欧氏距离完全感知不到这种“结构性异常”。这就是马氏距离在异常检测中不可替代的一个体现。4.3 应用中的几个大坑马氏距离虽好远没有到无脑用的程度。实际操作中我踩过不少坑也见过团队里的同学踩过类似的列出来给大家提个醒。第一个坑是样本量不足导致协方差矩阵估计不可靠。协方差矩阵要估计的参数数量是 (n(n1)/2) 个对称矩阵的上三角加对角线如果特征维数 (n) 是50就意味着要估计1275个参数而样本量如果只有80个估计出来的矩阵噪声很大算出的马氏距离不稳定。经验法则是样本量至少要是特征维数的5到10倍否则慎用马氏距离。第二个坑是协方差矩阵奇异。当特征维数大于样本量高维小样本或者特征之间存在强共线性时(\Sigma) 不可逆。前面提到用伪逆是一种权宜之计但更稳的做法是先做主成分分析降维保留主成分数不超过样本量再用降维后的主成分算马氏距离。第三个坑是异常值污染了协方差矩阵本身。你想用马氏距离找离群点但如果数据本身就含有了大量离群点它们会直接影响均值向量和协方差矩阵的估计导致“掩盖效应”——异常值把协方差拉大使得自己反而变成不异常。这种事情听着矛盾实际中太常见了。解决方案是使用稳健协方差估计器比如 FastMCDscikit-learn 的MinCovDet类它会用子集采样迭代拟合出不受离群点污染的协方差矩阵再用这个稳健矩阵计算马氏距离。第三个坑的延伸如果你开发了一套异常检测系统上线后需要定期用旧分布更新协方差矩阵。但设备工况缓变时协方差矩阵跟着变马氏距离的基准也在漂移。这时候需要引入滑动窗口或EWMA式的协方差估计否则系统会越来越“宽容”。这个点我在生产环境里就吃过亏一开始用全量历史估计后来模型对轻微异常越来越不敏感换成时间衰减加权之后才恢复正常敏感度。5. 常见问题与排查技巧实录5.1 问题速查表整理一个速查表基本覆盖了我这些年被问到的高频问题。建议收藏真遇到问题了翻出来对照。现象可能原因排查方向马氏距离计算出负数或NaN协方差矩阵非正定或包含缺失值检查缺失值检查协方差矩阵特征值是否为负马氏距离整体偏大几乎全部点都超阈值协方差矩阵被离群点污染方差被高估改用稳健协方差估计先剔除明显离群点某些特征贡献异常大距离主要由单个特征主导协方差矩阵对角线上该特征方差极小逆矩阵对应元素极大检查特征是否包含非法范围的值或接近常数特征特征维度高协方差矩阵求逆报错样本量小于特征数或共线性严重先做PCA降维再计算马氏距离马氏距离和欧氏距离结果几乎一致各特征本身独立且方差相近核对协方差矩阵的非对角元素如果近似为0结果一致是正常的用全部样本估计协方差后用同一样本计算距离发现部分点距离过大这是正常的马氏距离近似服从卡方分布约有一半的点在均值和分布中心之间但也有百分之几的点超过95%分位阈值按卡方分布分位数设也可以先排序取95%分位数换了批次数据阈值失效数据分布发生漂移概念漂移定期重新估计均值向量和协方差矩阵或使用指数衰减更新5.2 几次实战心得我第一次真正用透马氏距离是在给一批工业设备做健康度打分的时候。当时的困扰是多个监测指标之间高度相关直接加权求和做成“健康指数”不同指标的权重怎么定都存在争议业务方也不认可。后来把原始指标转为马氏距离用马氏距离的大小作为健康度反向指标加权那一步完全消失了数据本身的协方差结构自动决定了一切。商务汇报时解释路径也很简单距离越大说明设备状态偏离“正常群体”越远对应健康度越低。这个方案落地后业务方理解成本很低因为这个分数背后有统计分布做支撑。后来做金融反欺诈场景时我又学到一个关键经验马氏距离计算时到底该用整体数据的协方差矩阵还是该用目标类别的协方差矩阵在异常检测场景如果目标是找“与绝大多数正常样本不同的点”应该用全部数据或者正常样本的分布来算这样离群点会被识别。但在分类场景比如判断一个交易样本更接近“正常”还是“欺诈”应该分别计算两类各自的马氏距离然后比较谁更小而不是用全局协方差。这两者的逻辑差异很细微但在应用上是两个方向很多新人会混淆。另一个值得分享的心得是关于可视化。对于高维数据马氏距离最后可以投影到一维做时序监控图横轴是时间纵轴是马氏距离再加上一条阈值线。这个图是我在团队里最常用的监控看板形式简单、直观、信息密度高。比起看一堆特征曲线自己去发现异常看一条马氏距离曲线显然高效得多。有了这条曲线我甚至可以做一个简单的自动化告警只要马氏距离超过阈值就触发工单让业务人员去核实状态。这个流程上线后省掉了大量人工盯盘的时间。最后再分享一个小技巧如果直接用np.cov得到的协方差矩阵和MinCovDet得到的稳健协方差矩阵在马氏距离上差异很大那么基本可以断定数据里存在离群点。你可以通过对比两个矩阵的特征值差异反向定位是哪些样本在“拉偏”分布这是排查脏数据的一个非常高效的手段我经常用它来快速扫描一批没有标签的历史数据帮我把精力集中在真正的可疑样本上。
返回列表