np.cov生成非正定协方差矩阵致马氏距离计算出NaN及警告
解决马氏距离近邻检索中协方差矩阵非半正定导致的NaN问题
我之前也碰到过一模一样的问题!核心原因就是你提到的——np.cov返回的协方差矩阵不是半正定的,这会让马氏距离的计算彻底出问题:当计算(x - y)^T @ VI @ (x - y)时,结果可能是负数,开根号就会触发RuntimeWarning,最终得到全NaN的距离值。下面是几个实测有效的解决办法:
1. 给协方差矩阵添加正则化项
最简单的方法是给协方差矩阵的对角线加上一个极小的正数,强制让矩阵变成半正定。这种方法能快速解决浮点误差或者样本量不足导致的非正定问题:
import numpy as np from sklearn.neighbors import NearestNeighbors # 计算原始协方差矩阵 c = np.cov(arr) # 添加正则项,epsilon可以根据你的数据调整,比如1e-6到1e-4 epsilon = 1e-6 c_fixed = c + epsilon * np.eye(c.shape[0]) # 用修正后的矩阵计算逆 VI = np.linalg.inv(c_fixed) # 重新初始化近邻检索器 neigh = NearestNeighbors(n_neighbors=100, metric='mahalanobis', metric_params={'VI': VI}) neigh.fit(dfeatures)
2. 用SVD分解修正非正定矩阵
如果正则化的效果不够理想,可以通过奇异值分解(SVD)把协方差矩阵里的负奇异值替换成极小的正数,确保矩阵半正定:
u, s, vh = np.linalg.svd(c) # 把所有负的奇异值替换为1e-6,也可以根据你的数据调整阈值 s = np.maximum(s, 1e-6) # 重构修正后的协方差矩阵 c_fixed = u @ np.diag(s) @ vh VI = np.linalg.inv(c_fixed)
3. 使用sklearn的收缩协方差估计器
如果你的样本量小于特征维度,np.cov得到的矩阵天生就是奇异的(秩不足),这时候可以用sklearn提供的ShrunkCovariance或者LedoitWolf来估计更稳定的协方差矩阵:
from sklearn.covariance import ShrunkCovariance # 初始化收缩协方差估计器,shrinkage参数可以调整,范围0到1 cov_estimator = ShrunkCovariance(shrinkage=0.1) # 注意:np.cov默认是按行计算特征,而sklearn的协方差估计器需要按列传入样本,所以要转置arr c_fixed = cov_estimator.fit(arr.T).covariance_ VI = np.linalg.inv(c_fixed)
验证修正效果
在使用修正后的矩阵之前,可以先验证它是否是半正定的(允许微小的负数值,因为浮点误差):
eigenvals = np.linalg.eigvals(c_fixed) # 检查所有特征值是否都大于等于-1e-6(允许极小的负数,避免浮点误差) print(np.all(eigenvals >= -1e-6))
只要矩阵变成半正定的,马氏距离的计算就不会再出现负数开根号的问题,警告和NaN也就消失了。
内容的提问来源于stack exchange,提问作者Utkarsh Mall
相关产品推荐
相关产品推荐

