使用scipy的cdist计算马氏距离报错,与paired_distances差异咨询
为什么
paired_distances和cdist计算马氏距离时表现不同? 这背后的关键差异在于两个函数计算马氏距离时使用的协方差矩阵来源完全不同,而欧氏距离不需要协方差矩阵,所以两者结果一致。让我详细拆解:
1. 欧氏距离的一致性
欧氏距离的计算逻辑非常直接:对于两个点x和y,距离就是sqrt(sum((x-y)^2))。无论是paired_distances还是cdist,在计算逐对/逐个点的欧氏距离时,都是单纯计算a的每行与b的行(或tile后的行)的欧氏距离,逻辑完全一致,所以结果自然相同。
2. 马氏距离的核心差异:协方差矩阵的计算方式
马氏距离的公式是:
d(x,y) = sqrt( (x-y)^T * Σ⁻¹ * (x-y) )
其中Σ是数据集的协方差矩阵,Σ⁻¹是它的逆矩阵。两个函数在Σ的选择上完全不同:
情况一:sklearn的paired_distances
当你调用paired_distances(a, vector_array_tiled, metric='mahalanobis')且不指定协方差逆矩阵VI时:
- sklearn会自动合并输入的两个矩阵
a和vector_array_tiled,然后基于合并后的完整数据集计算协方差矩阵,再求逆。 - 在你的例子中,合并后有4个样本(
a的2行 + tile后的b的2行),即使这些样本存在线性相关性,sklearn也会处理奇异矩阵的情况(比如使用伪逆),所以不会报错。
情况二:scipy的cdist
当你调用cdist(a, b, metric='mahalanobis')且不指定VI时:
- scipy会仅使用第一个输入矩阵
a的协方差矩阵来计算逆。 - 你的
a只有2个3维样本,无偏协方差矩阵的自由度是2-1=1,导致协方差矩阵是奇异的(秩远小于维度3),无法求逆。scipy的报错信息明确指出:3维数据至少需要4个观测值(这样自由度为3,协方差矩阵才可能满秩可逆),所以抛出ValueError。
如何让cdist和paired_distances得到相同的马氏距离结果?
你需要手动计算和sklearn相同的协方差矩阵的逆,然后传给cdist的VI参数:
import numpy as np from sklearn.metrics.pairwise import paired_distances from scipy.spatial.distance import cdist a = np.array([[1,2,3],[4,5,6]]) b = np.array([[7,8,9]]) # 简化b的定义,不需要额外转置 METRIC = 'mahalanobis' # 先按sklearn的方式合并数据集 vector_array_tiled = np.tile(b, (a.shape[0], 1)) combined_data = np.vstack((a, vector_array_tiled)) # 计算协方差矩阵(和sklearn保持一致,使用无偏估计) cov_matrix = np.cov(combined_data.T, bias=False) # 如果协方差矩阵奇异,用伪逆替代普通逆 cov_inv = np.linalg.pinv(cov_matrix) if np.linalg.det(cov_matrix) == 0 else np.linalg.inv(cov_matrix) # 用cdist计算,传入协方差逆矩阵 distances_2 = cdist(a, b, metric=METRIC, VI=cov_inv).ravel() # 对比结果 distances_1 = paired_distances(a, vector_array_tiled, metric=METRIC) print(distances_1) print(distances_2)
这样两者就会输出相同的马氏距离结果了。
内容的提问来源于stack exchange,提问作者Johannes Wiesner




