Scipy:通过 cdist 计算标准化欧几里得

tan*_*ngy 2 python scipy

该公式在文档中可用并在此答案中指出。但是,当我尝试应用它时,我没有得到匹配的答案。我确定我在某处犯了一些愚蠢的错误,所以感谢您的支持:


设置

假设我有 2 个矩阵:

X: array([[0, 1, 0],
       [1, 1, 1]])
X2: array([[1, 1, 0],
       [1, 1, 1],
       [1, 2, 0]])
Run Code Online (Sandbox Code Playgroud)

现在申请Xans = scipy.spatial.distance.cdist(X, X2, 'seuclidean')给出:

Xans: array([[2.23606798, 2.88675135, 3.16227766],
       [1.82574186, 0.        , 2.88675135]])
Run Code Online (Sandbox Code Playgroud)

我们只关注Xans[0][0] = 2.23606798,这应该是通过申请获得的seuclidean(X[0], X2[0])


方法 1:使用 pdist

我尝试通过这样做pdist但得到一个 NaN:

In [104]: scipy.spatial.distance.pdist([X[0], X2[0]], metric='seuclidean')
Out[104]: array([nan])
Run Code Online (Sandbox Code Playgroud)

为什么会这样?


方法二:直接公式申请

我尝试使用上面答案中链接的公式手动尝试,如下所示:

In [107]: (((X[0] - X2[0])**2).sum()/(np.var([X[0], X2[0]])))**0.5
Out[107]: 2.0
Run Code Online (Sandbox Code Playgroud)

可以看出这是给 2.0?

我显然做错了什么 - 这是什么?

War*_*ser 6

标准化欧几里德距离的权重的每个变量与单独的方差。如果您不提供V参数的方差,它会从输入数组中计算它们。这在 下“参数”部分的pdist文档字符串中提到**kwargs,其中显示:

V : ndarray
The variance vector for standardized Euclidean.
Default: var(X, axis=0, ddof=1)
Run Code Online (Sandbox Code Playgroud)

例如:

In [39]: A
Out[39]: 
array([[3, 0, 2],
       [2, 1, 2],
       [0, 0, 1],
       [3, 1, 2],
       [1, 0, 0]])

In [40]: from scipy.spatial.distance import pdist

In [41]: pdist(A, metric='seuclidean')
Out[41]: 
array([ 1.98029509,  2.55814731,  1.82574186,  2.71163072,  2.63368079,
        0.76696499,  2.9868995 ,  3.14284123,  1.35581536,  3.26898677])
Run Code Online (Sandbox Code Playgroud)

如果我们提供按照文档字符串中的解释计算的方差,我们会得到相同的结果:

In [42]: pdist(A, metric='seuclidean', V=np.var(A, axis=0, ddof=1))
Out[42]: 
array([ 1.98029509,  2.55814731,  1.82574186,  2.71163072,  2.63368079,
        0.76696499,  2.9868995 ,  3.14284123,  1.35581536,  3.26898677])
Run Code Online (Sandbox Code Playgroud)

当然,如果您提供全为 1 的方差,您将得到常规的欧几里德距离:

In [43]: pdist(A, metric='seuclidean', V=np.ones(A.shape[1]))
Out[43]: 
array([ 1.41421356,  3.16227766,  1.        ,  2.82842712,  2.44948974,
        1.        ,  2.44948974,  3.31662479,  1.41421356,  3.        ])

In [44]: pdist(A, metric='euclidean')
Out[44]: 
array([ 1.41421356,  3.16227766,  1.        ,  2.82842712,  2.44948974,
        1.        ,  2.44948974,  3.31662479,  1.41421356,  3.        ])
Run Code Online (Sandbox Code Playgroud)

“方法 1”的问题在于,在只有两个点的输入数组(即[X[0], X2[0]])中,点的第二个和第三个分量不会改变,因此与这些分量相关的方差为 0:

In [45]: p = np.array([X[0], X2[0]])

In [46]: p
Out[46]: 
array([[0, 1, 0],
       [1, 1, 0]])

In [47]: np.var(p, axis=0, ddof=1)
Out[47]: array([ 0.5,  0. ,  0. ])
Run Code Online (Sandbox Code Playgroud)

当代码seuclidean除以这些方差时,结果要么是无穷大,要么是 NaN——如果分子也是 0,则是后者,这是 input 的第三个分量的情况[X[0], X2[0]]

要解决此问题,您必须决定如何处理组件方差为 0 的情况,并明确处理它。例如,如果您希望它在这种情况下表现得像方差为 1(只是为了避免除以 0),您可以执行以下操作。

假设B是我们的点数组。的第三列全B是 1。

In [63]: B
Out[63]: 
array([[3, 0, 1],
       [2, 1, 1],
       [0, 0, 1],
       [3, 1, 1],
       [1, 0, 1]])
Run Code Online (Sandbox Code Playgroud)

计算列的方差:

In [64]: V = np.var(B, axis=0, ddof=1)

In [65]: V
Out[65]: array([ 1.7,  0.3,  0. ])
Run Code Online (Sandbox Code Playgroud)

用 1 替换为 0 的方差:

In [66]: V[V == 0] = 1

In [67]: V
Out[67]: array([ 1.7,  0.3,  1. ])
Run Code Online (Sandbox Code Playgroud)

使用V计算标准化欧氏距离:

In [68]: pdist(B, metric='seuclidean', V=V)
Out[68]: 
array([ 1.98029509,  2.30089497,  1.82574186,  1.53392998,  2.38459106,
        0.76696499,  1.98029509,  2.93725228,  0.76696499,  2.38459106])
Run Code Online (Sandbox Code Playgroud)

这与简单地删除常量列具有相同的效果:

In [69]: pdist(B[:, :2], metric='seuclidean')
Out[69]: 
array([ 1.98029509,  2.30089497,  1.82574186,  1.53392998,  2.38459106,
        0.76696499,  1.98029509,  2.93725228,  0.76696499,  2.38459106])
Run Code Online (Sandbox Code Playgroud)

你的“方法 2”是错误的,因为你的公式是错误的。您必须保留每个组件的差异。 np.var([X[0], X2[0]])计算输入中所有值的(单一)方差。相反,您需要使用上面显示的axisddof参数。