当我们使用具有N个观测值和M个特征的N x M矩阵时,常见的任务是计算N个观测值之间的成对距离,从而得到N x N距离矩阵.流行的Python库scipyscikit-learn都提供了执行此任务的方法,我们希望它们为两者都已实现的指标产生相同的结果.以下函数测试名为arr的给定矩阵的等价性:

import numpy as np
from sklearn.metrics import pairwise_distances
from scipy.spatial.distance import pdist, squareform

def test_equivalence(arr: np.array, metric="cosine") -> bool:
    scipy_result = squareform(pdist(arr, metric=metric))
    sklearn_result = pairwise_distances(arr, metric=metric)
    return np.isclose(scipy_result, sklearn_result).all()

现在,我碰巧有这个1219 x 37652数组arr,其中每行和为1(标准化),test_equivalence(arr)产生True,不出所料.也就是说,这两个库返回的N x N个余弦距离矩阵可以互换使用.然而,当我剔除最后i列时,test_equivalence(arr[:, -i])得到True only up to a certain value(恰好是i = 25676).从这个值开始,等价性就不成立了.

我完全不知道为什么会这样,有什么指导吗?如果有人能告诉我怎么做,我可能会将数组分享为.npz个文件进行调试,但也许有人已经有了预感.当然,最终的问题是,我应该使用哪种实现?

我还用这些其他指标测试了失败的arr[:, -25675]个指标:["braycurtis", "canberra", "chebyshev", "cityblock", "correlation", "euclidean", "hamming", "matching", "minkowski", "rogerstanimoto", "russellrao", "seuclidean", "sokalmichener", "sokalsneath", "sqeuclidean", "yule"]个指标中,除"相关性"外,所有指标都是等同的.

Edit:未通过等价性测试的简化(1219 x 96)数组可以从https://drive.switch.ch/index.php/s/B19JbTL5aZ4pY3f/download下载并通过np.load("tf_matrix.npz")["arr_0"]加载.

推荐答案

在这种情况下,您可以try 几种诊断方法.可以try 的一种诊断方法是绘制两种计算距离的方法不一致的地方.

# Modified version of test_equivalence() that returns boolean matrix of disagreements
def test_equivalence(arr: np.array, metric="cosine"):
    scipy_result = squareform(pdist(arr, metric=metric))
    sklearn_result = pairwise_distances(arr, metric=metric)
    return np.isclose(scipy_result, sklearn_result)
plt.imshow(test_equivalence(arr))

该图如下所示:

equivalence plot

请注意,除了1200附近的水平线和1200附近的垂直线外,其他任何地方都是真的.因此,这两种方法并不是在所有向量上都不一致--它们在涉及特定向量的所有比较中都不一致.

让我们找出哪一列包含他们不同意的向量:

>>> row, col = np.where(~test_equivalence(arr))
>>> print(col[0])
1168

向量1168有什么奇怪的地方吗?

>>> print(arr[1168])
[1.20000016e-23 1.20000016e-23 1.20000016e-23 1.20000016e-23
 1.20000016e-23 1.20000016e-23 1.20000016e-23 1.20000016e-23
 1.20000016e-23 1.20000016e-23 1.20000016e-23 1.20000016e-23
 1.20000016e-23 1.20000016e-23 1.20000016e-23 1.20000016e-23
 1.20000016e-23 1.20000016e-23 1.20000016e-23 1.20000016e-23
 1.20000016e-23 1.20000016e-23 1.20000016e-23 1.20000016e-23
 1.20000016e-23 1.20000016e-23 1.20000016e-23 1.20000016e-23
 1.20000016e-23 1.20000016e-23 1.20000016e-23 1.20000016e-23
 1.20000016e-23 1.20000016e-23 1.20000016e-23 1.20000016e-23
 1.20000016e-23 1.20000016e-23 1.20000016e-23 1.20000016e-23
 1.20000016e-23 1.20000016e-23 1.20000016e-23 1.20000016e-23
 1.20000016e-23 1.20000016e-23 1.20000016e-23 1.20000016e-23
 1.20000016e-23 1.20000016e-23 1.20000016e-23 1.20000016e-23
 1.20000016e-23 1.20000016e-23 1.20000016e-23 1.20000016e-23
 1.20000016e-23 1.20000016e-23 1.20000016e-23 1.20000016e-23
 1.20000016e-23 1.20000016e-23 1.20000016e-23 1.20000016e-23
 1.20000016e-23 1.20000016e-23 1.20000016e-23 1.20000016e-23
 1.20000016e-23 1.20000016e-23 1.20000016e-23 1.20000016e-23
 1.20000016e-23 1.20000016e-23 1.20000016e-23 1.20000016e-23
 1.20000016e-23 1.20000016e-23 1.20000016e-23 1.20000016e-23
 1.20000016e-23 1.20000016e-23 1.20000016e-23 1.20000016e-23
 1.20000016e-23 1.20000016e-23 1.20000016e-23 1.20000016e-23
 1.20000016e-23 1.20000016e-23 1.20000016e-23 1.20000016e-23
 1.20000016e-23 1.20000016e-23]

这个矢量非常非常小.但是,与其他载体相比,它是否异常小?你可以通过绘制每个向量在数组中的位置的欧几里得长度来测试这一点.

plt.scatter(np.arange(len(arr)), np.linalg.norm(arr, axis=1))
plt.yscale('log')

该图显示,大多数向量的欧几里得长度约为0.1,只有一个向量除外,它小了20个数量级.又是1168号航线.

log plot of vector norms

为了验证这个关于导致问题的小向量的理论,这里有一种显示问题的替代方法.我采用了您的数组,并反复简化它,直到我有了一个尽可能简单的测试用例,但仍然显示了问题.

arr_small = np.array([[1, 0], [1e-15, 1e-15]])
print(test_equivalence(arr_small))
print(squareform(pdist(arr_small, metric="cosine")))
print(pairwise_distances(arr_small, metric="cosine"))

输出:

[[ True False]
 [False  True]]
[[0.         0.29289322]
 [0.29289322 0.        ]]
[[0. 1.]
 [1. 0.]]

我声明两个向量,一个坐标为(1,0),另一个坐标为(1 e-15,1 e-15).它们之间应该有45度角.在余弦距离方面,应该是1 - cos(45 degrees) = 0.292.pdist()函数与此计算一致.

但是,pairwise_Distance()表示距离为1.换句话说,它表示这两个向量是正交的.它为什么要这么做?让我们来看看余弦距离的定义,以了解为什么.

cosine distance definition

图片来源:本网站文档

在这个等式中,如果u或v中的任何一个都包含零,那么分母将是零,你将得到一个除以零,这是未定义的.在这种情况下,pairwise_Distance()所做的是,在向量的欧几里德长度"太小"的任何情况下,向量的长度被替换为1,以避免除以0.这会导致分子比分母小得多,因此分数为0,距离变为1.

更准确地说,当向量的长度小于相关类型的machine epsilon的10倍(64位浮点数的长度约为2.22e-15)时,该向量就"太小".(Source.)

相比之下,pdist()不包含任何代码来避免被零除.

>>> print(squareform(pdist(np.array([[1, 0], [0, 0]]), metric="cosine")))
[[ 0. nan]
 [nan  0.]]

Python相关问答推荐

从今天起的future 12个月内使用Python迭代

用gekko解决的ADE方程系统突然不再工作,错误消息异常:@错误:模型文件未找到.& &

仅从风格中获取 colored颜色 循环

Pandas 第二小值有条件

DataFrame groupby函数从列返回数组而不是值

Gekko:Spring-Mass系统的参数识别

. str.替换pandas.series的方法未按预期工作

删除所有列值,但判断是否存在任何二元组

无法定位元素错误404

如何更改分组条形图中条形图的 colored颜色 ?

Streamlit应用程序中的Plotly条形图中未正确显示Y轴刻度

如何并行化/加速并行numba代码?

如何在FastAPI中为我上传的json文件提供索引ID?

使用Python和文件进行模糊输出

如果初始groupby找不到满足掩码条件的第一行,我如何更改groupby列,以找到它?

matplotlib + python foor loop

Discord.py -

Python类型提示:对于一个可以迭代的变量,我应该使用什么?

Python日志(log)库如何有效地获取lineno和funcName?

Pandas:计数器的滚动和,复位