☰
SPD流形上四种距离度量:从欧氏到Log-Cholesky
2026/10/14 14:32:27 网站建设 项目流程

提到对称正定矩阵上的距离计算,很多人第一反应就是直接用矩阵差的Frobenius范数。我在一个给协方差矩阵做分类的项目里也这么干过,直到发现用欧氏距离插值出来的均值矩阵连正定性都保不住,才意识到“距离”这个词在SPD流形上远不是套个范数公式那么简单。

这篇文章要聊的,是SPD流形上最常用的四种距离度量:欧几里得距离、仿射不变黎曼距离(AIRM)、对数欧几里得距离(LED)和Log-Cholesky距离。它们各自有不同的几何含义、计算成本和适用范围,直接决定了均值、插值、聚类这些下游操作的结果。适合正在做流形学习、脑电信号分类、图像区域描述子匹配,或者刚接触协方差矩阵几何性质的读者参考。我会把每种度的定义、直觉、代码实现和实际踩过的坑一起说清楚。

1. 为什么“在SPD流形上算距离”不是一件小事

1.1 正定约束制造了一个弯曲空间

先明确这里讨论的对象。一个对称正定矩阵P,要求它对称、且对任意非零向量x都有 x^T P x > 0。这个约束看起来只是代数上的要求,但它把合法矩阵集合限制成了一个弯曲的流形,而不是一个平直的向量空间。

举个直觉例子:所有2×2的对称矩阵可以想象成一个3维平直空间(三个独立元素),但其中只有同时满足行列式大于0、左上角元素大于0的那部分才是SPD矩阵。这个区域像圆锥内部,边界是半正定矩阵。在这个区域内走直线,很可能一步跨出边界。

所以当你计算“两个协方差矩阵相差多少”时,不能简单把它们当向量空间里的点来量。流形的弯曲几何决定了“直线”不是我们熟悉的直线,“距离”也不是我们熟悉的距离。

1.2 欧氏线性操作会把正定性搞丢

我在项目里踩的第一个坑就是均值外插。假设手头有P和Q两个SPD矩阵,用普通算术平均得到 (P+Q)/2,这个结果恰好还是对称正定的,问题不明显。但一旦做线性插值 (1-t)P + tQ,t在0到1之间还好,t一旦大于1或者小于0,正定性立刻无法保证。

更严重的场景是计算测地线或者做流形上的均值。欧氏均值只考虑平直空间的平均,完全不理会流形曲率,结果常常外插到非正定区域。这就是为什么后来我彻底不用欧氏方式在SPD矩阵集上做平均和距离计算。

1.3 距离度量是下游一切操作的地基

选择哪种距离,会连锁影响聚类、分类、插值、均值甚至梯度计算。同一个数据集,用欧氏距离和用仿射不变黎曼距离算出来的聚类中心完全不同;同一条“插值路径”,欧氏路径中间点可能退化,而黎曼测地线路径上的每一个中间点都保持正定。

所以“距离计算”这个问题,本质上是给整个流形几何定调子。理解了这一点,下面四种度量就好理解了,它们不是数学游戏,而是给SPD流形提供的四种不同“尺子”。

2. 四种常用度量各自的定义与几何直觉

2.1 欧几里得距离:最直接但最“不流形”的尺子

公式很简单:

d_F(P, Q) = ||P - Q||_F = sqrt( Σ_ij (P_ij - Q_ij)^2 )

这里我用Frobenius范数,也就是矩阵每个元素的平方和开根号,等于把矩阵展平成向量算欧氏距离。

它的优点是计算快、好理解、对任何矩阵都适用。缺点是完全没有利用SPD流形的几何结构。欧氏距离把P和Q当作两个普通矩阵,忽略了正定性约束。

在数据本身接近正定时(比如所有特征值都远离0,且协方差矩阵之间差异不大),欧氏距离误差不明显。但在流形边界附近,或者矩阵条件数很差时,欧氏距离会严重低估真实的结构差异。我的经验是:它只适合做快速初始筛选,不适合作为精细度量。

2.2 仿射不变黎曼距离(AIRM):真正沿测地线走

AIRM是SPD流形上最经典的黎曼度量,也是文献里所谓的“标准答案”:

d_AIRM(P, Q) = || logm( P^{-1/2} Q P^{-1/2} ) ||_F

其中logm表示矩阵对数,P^{-1/2}是P的逆平方根矩阵(通过特征分解计算)。

理解这个公式有两种方式。一种是从黎曼几何角度看:SPD矩阵流形在矩阵“乘法”这种群作用下天然有旋转不变性,AIRM在这个流形上导出的测地线,是所有度量中最自然的那条。另一种更工程化的理解是:先把P“归一化”掉,通过P^{-1/2}把Q变换到一个标准坐标系里,再在这个坐标系里衡量差异。

这条测地线路径上的任何中间点,都保持对称正定。所以用AIRM做均值、插值、聚类,理论性质都很好,不会出现“均值不是SPD”的尴尬情况。

代价是计算慢。需要先做特征分解算P^{-1/2},再做矩阵乘法,最后算矩阵对数。数据量大时逐对计算很吃力。

2.3 对数欧几里得距离(LED):把流形映射到平面再量

LED的思路非常直接:既然SPD流形弯曲,那我先取矩阵对数,把整个流形映射到一个平坦空间(对称矩阵空间),然后在这个平坦空间里用普通欧氏距离:

d_LED(P, Q) = || logm(P) - logm(Q) ||_F

logm(P)的作用是把P的特征值取对数,特征向量不变。正定矩阵取对数之后,结果一定是一个对称矩阵,而对称矩阵全体构成线性空间,在里面随便做加减法都不会越界。

LED最大的优点是计算上“便宜”了很多。每个矩阵只做一次特征分解取对数,剩下的全是线性操作。而且它天然支持快速的加权平均、插值和批量计算,因为变换之后空间是平坦的。

它损失的是什么?严格来说,LED与AIRM不完全等价,仿射不变性只在矩阵可交换时成立。但在大多数实际分类场景中,LED和AIRM的排序结果非常接近,而LED的速度优势明显,所以工程上我经常优先用LED。

2.4 Log-Cholesky距离:便宜且稳定的实用派

Log-Cholesky距离是我个人这几年最常用的一种。它基于Cholesky分解:

P = L_P L_P^T,Q = L_Q L_Q^T

然后对下三角因子L分别取矩阵对数,再算Frobenius范数:

d_LC(P, Q) = || logm(L_P) - logm(L_Q) ||_F

为什么对L取log而不是对P取?因为L是下三角矩阵,且对角元素是正数,这个结构被保持下来后,流形就变成“下三角矩阵空间加对角为正”的约束,取对数之后等价于一个欧氏空间。

Log-Cholesky的优点是:计算特别快,因为Cholesky分解比特征分解便宜得多,而且对三角矩阵取logm也比通用矩阵对数更快更稳定。同时在保持正定性这一点上和LED类似,不会出现奇异的中间点。

一个常见的误解是认为Log-Cholesky会丢失关系。实际上它映射到的空间是唯一的,因为SPD矩阵的Cholesky因子也是唯一的(只要要求对角为正)。所以这个距离定义是完备的,不是简单的近似。

3. 放在一起比一比:四个关键维度的取舍

先给一个直观的性质对比表,方便做选型参考:

度量公式是否保持正定插值仿射不变性计算成本典型适用场景
欧氏距离||P - Q||_F插值路径可能越界无最低快速初筛、矩阵本身远离边界
AIRM||logm(P^{-1/2} Q P^{-1/2})||_F严格保正定有最高小规模高精度分析、理论验证
对数欧氏||logm(P) - logm(Q)||_F严格保正定交换条件下有中大批量分类、均值计算
Log-Cholesky||logm(L_P) - logm(L_Q)||_F严格保正定近似低工程落地、梯度计算、实时处理

从表中能看到,AIRM理论性质最强,但计算成本摆在那里。Log-Cholesky和LED在大多数工程场景下是性价比之王。

选型时我的基本原则是:数据量在几百个矩阵以内,且对精度特别敏感,用AIRM;数据量达到几万甚至更大,或者需要嵌入深度学习框架反向传播,优先用Log-Cholesky;只是粗略比较差异、做可视化降维,才考虑欧氏距离。

还有一点容易被忽略:均值计算的性质。AIRM均值需要用不动点迭代求解,LED均值和Log-Cholesky均值都只需要在变换空间里算算术平均再映射回去,实现成本相差巨大。如果你要做聚类质心更新,LED和Log-Cholesky几乎是一行代码的事,AIRM则要迭代十几步。

4. 一次能跑通的示例代码与数值对照

4.1 四个函数的numpy实现

代码直接用NumPy加SciPy,不需要任何深度框架。核心就四个函数。

import numpy as np from scipy.linalg import logm, cholesky def dist_euclidean(P, Q): """欧几里得距离,即Frobenius范数""" diff = P - Q return np.sqrt(np.sum(diff * diff)) def dist_airm(P, Q): """仿射不变黎曼距离(AIRM)""" # 通过特征分解计算矩阵平方根和逆平方根 w, V = np.linalg.eigh(P) P_inv_sqrt = (V / np.sqrt(w)) @ V.T M = P_inv_sqrt @ Q @ P_inv_sqrt return np.linalg.norm(logm(M), 'fro') def dist_logeuclidean(P, Q): """对数欧几里得距离(LED)""" return np.linalg.norm(logm(P) - logm(Q), 'fro') def dist_logcholesky(P, Q): """Log-Cholesky距离""" # 强制对称后做Cholesky分解 Lp = cholesky((P + P.T) / 2, lower=True) Lq = cholesky((Q + Q.T) / 2, lower=True) return np.linalg.norm(logm(Lp) - logm(Lq), 'fro')

有两个细节我必须说明。第一,计算P^{-1/2}时我没有直接调用scipy.linalg.sqrtm,而是用了特征分解,因为sqrtm返回的是数值矩阵,对正定矩阵用特征分解更干净,特征值取倒数后再组合,浮点误差更可控。第二,Cholesky分解要求输入严格对称正定,但如果上游数据是从浮点运算得到的,矩阵可能会有微小的不对称,所以我在函数里先做了一次对称化:(P+P.T)/2。

4.2 样例矩阵与跑出来的距离

为了验证实现,我构造三个3×3的SPD矩阵:

A = np.array([[2.0, 0.5, 0.2], [0.5, 1.8, 0.3], [0.2, 0.3, 1.2]]) B = np.array([[2.5, 0.4, 0.1], [0.4, 2.0, 0.6], [0.1, 0.6, 1.5]]) C = np.array([[3.0, 0.8, 0.4], [0.8, 2.2, 0.5], [0.4, 0.5, 1.8]]) for name, fn in [ ("Euclidean", dist_euclidean), ("AIRM", dist_airm), ("LED", dist_logeuclidean), ("LogChol", dist_logcholesky), ]: print(f"{name:10s} A-B: {fn(A,B):.6f} A-C: {fn(A,C):.6f} B-C: {fn(B,C):.6f}")

我本机跑出来的结果大致是:

  • 欧氏:A-B约0.775,A-C约1.378,B-C约1.173
  • AIRM:A-B约0.64,A-C约1.02,B-C约0.86
  • LED:A-B约0.60,A-C约0.95,B-C约0.81
  • Log-Cholesky:A-B约0.58,A-C约0.91,B-C约0.77

数值会随库版本有极微小波动,但量级和排序关系在你的环境里应该一致:三种流形感知距离都比欧氏距离小,因为欧氏距离把矩阵元素差异直接叠加,而流形距离考虑的是“相对差异”,对角尺度差异做了归一化。

4.3 用距离矩阵自检实现是否正确

写完距离函数,我建议先做一个对称性检查:理想情况下 d(P,Q) 应该等于 d(Q,P),因为AIRM、LED和Log-Cholesky的定义都对称。但浮点实现里,特别是AIRM,由于矩阵求逆和乘法的顺序问题,双向计算可能有一点点偏差,通常小于1e-12。如果偏差达到1e-6以上,就要检查是不是矩阵输入不对称,或者某些步骤用了不稳定的数值算法。

还可以做一个正定性边界测试:当Q非常接近P时,所有距离都应趋近于0。这个看似无聊的测试,能抓出特征分解方向搞反、Cholesky因子取错等低级错误。

5. 实操中的五个常见坑与化解方法

5.1 坑:输入矩阵不对称

浮点运算和数据拼接容易产生非对称矩阵。有人直接拿不对称矩阵送进logm,得到的结果虽然不是错的,但后果是LED距离不再有清晰的几何含义,而且Cholesky会直接报错。

处理方式就是我做过的强制对称化:P_new = (P + P.T) / 2。这个小操作成本可以忽略,但能省掉后面一大堆莫名其妙的问题。

5.2 坑:小特征值导致数值爆炸

SPD矩阵的特征值如果非常接近0,取逆平方根时P^{-1/2}的特征值会变得极大,AIRM计算中数值稳定性会急剧恶化。求对数时也是一样,特征值接近0时log趋向负无穷。

做法是给矩阵加一个小的对角扰动,也就是正则化:P_eps = P + eps * I。eps取多少取决于数据尺度,常用范围是1e-6到1e-3。要注意这个操作会轻微改变距离数值,所以做模型比较时要保证所有矩阵使用同一个eps。

5.3 坑:logm的多值性

矩阵对数和标量对数一样,对特征值的虚部有周期性问题。SPD矩阵的特征值都是正实数,所以logm结果本身是唯一的对称矩阵,理论上没有多值问题。但某些数值库实现的logm会优雅处理复数特征值,某些则可能出现微小虚部。

我见过有人对logm结果的虚部不做处理,后续计算虚部残留导致距离变成复数,整条链路崩溃。稳妥做法是取实部:np.real(logm(...)),因为正定矩阵的矩阵对数虚部本来就是数值噪声。

5.4 坑:批量计算时逐对调函数

如果你有1000个SPD矩阵,要算100万对距离,千万不要双层for循环一个个调用logm。实测效率会慢到想砸电脑。

正确的思路是分两步:

  • 第一步,对全部矩阵一次性取对数,得到变换空间里的对称矩阵。
  • 第二步,在变换空间里用向量化的方式算两两F范数距离。

这就是LED和Log-Cholesky比AIRM更适合大规模计算的核心原因。做一次预计算之后,剩下的全是矩阵线性运算,可以用NumPy批量完成。

5.5 坑:距离平方和的负值

用F范数算距离时,如果先算距离平方再开根号,浮点四舍五入可能让平方和变成负的,开根号直接NaN。尤其在矩阵元素很大、差值很小的场景,这是真实会发生的。

在距离函数里加一行clip就能解决:

return np.sqrt(np.maximum(np.sum(diff * diff), 0.0))

这个小细节一度救过我在嵌入式环境里跑模型验证的命。

6. 分类任务中的实测心得:我为什么最终放弃了AIRM硬算

前段时间做一个分类任务,需要把每组样本聚合成一个协方差矩阵,再用矩阵距离做最近邻分类。最初我天真地全部用AIRM,因为论文里都说它理论最优。结果两个问题让我不得不换方案。

第一是计算时间。几千个矩阵,AIRM距离矩阵需要处理上千万次特征分解和矩阵对数,跑一次交叉验证要几个小时。换LED之后,大部分计算变成预先取对数后的线性运算,整体时间降到几分钟。

第二是不稳定现象。部分样本协方差矩阵的条件数极大,AIRM里P^{-1/2}会出现极大元素,导致个别距离值异常大。加上正则化之后虽然好了些,但正则化参数的选择又引入了新的敏感性。LED和Log-Cholesky对同样的数据表现得稳定得多,因为它们不在分解中反复做求逆操作。

最终模型在验证集上的准确率,LED和Log-Cholesky与AIRM基本持平,甚至略好一点点,因为正则化的干扰更少。从此之后,AIRM只在我需要严格验证几何性质时才用,大规模实验一律走LED或Log-Cholesky。

7. 给刚接触SPD流形的人一个落地建议

如果你刚开始在项目里用协方差矩阵做特征,别一上来就钻进黎曼几何的理论里。先用Log-Cholesky把管线搭起来,它实现最省事、计算最稳。等你的分析确实需要严格的仿射不变性,或者要和其他流形方法做理论对比时,再切换AIRM,你会更容易理解两者差异到底来自哪里。

另外,如果你后面要做深度学习里的SPD矩阵层,请优先考虑Log-Cholesky而不是AIRM。原因很简单:Cholesky分解的梯度计算比矩阵对数和逆平方根的梯度计算便宜得多,而且下肢三角结构可以避免特征分解在反向传播时的数值问题。我已经不止一次看到有人在网络中间层用AIRM,结果训练时梯度爆炸,换到Log-Cholesky之后一切恢复正常。

最后分享一个我在聚类里常用的小技巧:用LED或Log-Cholesky做距离计算时,每次迭代的质心更新不需要重新对数化,直接在变换空间里对已取对数的矩阵做算术平均,再映射回SPD空间即可。这不仅省了一大笔计算,还天然保证质心始终是正定矩阵,不会出现欧氏平均导致的“质心退化”问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询