在这种情况下,您可以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))
该图如下所示:
请注意,除了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号航线.
为了验证这个关于导致问题的小向量的理论,这里有一种显示问题的替代方法.我采用了您的数组,并反复简化它,直到我有了一个尽可能简单的测试用例,但仍然显示了问题.
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.换句话说,它表示这两个向量是正交的.它为什么要这么做?让我们来看看余弦距离的定义,以了解为什么.
图片来源:本网站文档
在这个等式中,如果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.]]