Theano sqrt 返回 NaN 值

Theano sqrt returning NaN values

在我的代码中,我使用 theano 来计算欧氏距离矩阵(来自 here 的代码):

import theano
import theano.tensor as T
MAT = T.fmatrix('MAT')
squared_euclidean_distances = (MAT ** 2).sum(1).reshape((MAT.shape[0], 1)) + (MAT ** 2).sum(1).reshape((1, MAT.shape[0])) - 2 * MAT.dot(MAT.T)
f_euclidean = theano.function([MAT], T.sqrt(squared_euclidean_distances))
def pdist_euclidean(mat):
    return f_euclidean(mat)

但是下面的代码导致矩阵的一些值是NaN。我读到在计算 theano.tensor.sqrt()here 时会发生这种情况,建议

Add an eps inside the sqrt (or max(x,EPs))

所以我在我的代码中添加了一个 eps:

import theano
import theano.tensor as T

eps = 1e-9

MAT = T.fmatrix('MAT')

squared_euclidean_distances = (MAT ** 2).sum(1).reshape((MAT.shape[0], 1)) + (MAT ** 2).sum(1).reshape((1, MAT.shape[0])) - 2 * MAT.dot(MAT.T)

f_euclidean = theano.function([MAT], T.sqrt(eps+squared_euclidean_distances))

def pdist_euclidean(mat):
    return f_euclidean(mat)

我在执行 sqrt 之前添加它。我得到的 NaN 越来越少,但我仍然得到它们。解决问题的正确方法是什么?我还注意到,如果 MATT.dmatrix(),则没有 NaN

计算欧几里得距离时,NaN 可能有两个来源。

  1. 浮点表示近似问题导致负距离,而实际上它实际上只是零。负数的平方根未定义(假设您对复数解不感兴趣)。

    假设 MAT 具有值

    [[ 1.62434536 -0.61175641 -0.52817175 -1.07296862  0.86540763]
     [-2.3015387   1.74481176 -0.7612069   0.3190391  -0.24937038]
     [ 1.46210794 -2.06014071 -0.3224172  -0.38405435  1.13376944]
     [-1.09989127 -0.17242821 -0.87785842  0.04221375  0.58281521]]
    

    现在,如果我们分解计算,我们会看到 (MAT ** 2).sum(1).reshape((MAT.shape[0], 1)) + (MAT ** 2).sum(1).reshape((1, MAT.shape[0])) 具有值

    [[ 10.3838024   -9.92394296  10.39763039  -1.51676099]
     [ -9.92394296  18.16971188 -14.23897281   5.53390084]
     [ 10.39763039 -14.23897281  15.83764622  -0.65066204]
     [ -1.51676099   5.53390084  -0.65066204   4.70316652]]
    

    2 * MAT.dot(MAT.T)有价值

    [[ 10.3838024   14.27675714  13.11072431   7.54348446]
     [ 14.27675714  18.16971188  17.00367905  11.4364392 ]
     [ 13.11072431  17.00367905  15.83764622  10.27040637]
     [  7.54348446  11.4364392   10.27040637   4.70316652]]
    

    这两个值的对角线应该相等(一个向量和它自身之间的距离为零),从这个文本表示看起来是这样,但实际上它们略有不同——差异太大当我们像这样打印浮点值时显示的很小

    当我们打印完整表达式的值(从第一个减去上面的第二个矩阵)时,这变得很明显

    [[  0.00000000e+00   2.42007001e+01   2.71309392e+00   9.06024545e+00]
     [  2.42007001e+01  -7.10542736e-15   3.12426519e+01   5.90253836e+00]
     [  2.71309392e+00   3.12426519e+01   0.00000000e+00   1.09210684e+01]
     [  9.06024545e+00   5.90253836e+00   1.09210684e+01   0.00000000e+00]]
    

    对角线几乎由零组成,但第二行第二列的项目现在是一个非常小的负值。当您计算所有这些值的平方根时,您会在该位置得到 NaN,因为负数的平方根未定义(对于实数)。

    [[ 0.          4.91942071  1.64714721  3.01002416]
     [ 4.91942071         nan  5.58951267  2.42951402]
     [ 1.64714721  5.58951267  0.          3.30470398]
     [ 3.01002416  2.42951402  3.30470398  0.        ]]
    
  2. 计算欧氏距离表达式相对于函数输入内变量的梯度。如上所述,不仅由于浮点近似值生成负数,而且任何输入的长度为零,都会发生这种情况。

    如果 y = sqrt(x)dy/dx = 1/(2 * sqrt(x))。因此,如果 x=0 或者,为了您的目的,如果 squared_euclidean_distances=0 那么梯度将是 NaN 因为 2 * sqrt(0) = 0 并且除以零是未定义的。

第一个问题的解决方案可以通过强制它们不小于零来确保平方距离永远不会为负来实现:

T.sqrt(T.maximum(squared_euclidean_distances, 0.))

要解决这两个问题(如果你需要梯度),那么你需要确保平方距离永远不会为负或零,所以用一个小的正 epsilon 绑定:

T.sqrt(T.maximum(squared_euclidean_distances, eps))

第一个解决方案是有道理的,因为问题仅来自近似表示。第二个更值得怀疑,因为真实距离为零,所以从某种意义上说,梯度应该是未定义的。您的特定用例可能会产生一些替代解决方案,这些解决方案可以在没有人为限制的情况下维护语义(例如,通过确保梯度永远不会 computed/used 对于零长度向量)。但是 NaN 值可能是有害的:它们可以像杂草一样蔓延。

只是检查

squared_euclidian_distances 中,您要添加一列、一行和一个矩阵。你确定这是你想要的吗?

更准确地说,如果 MAT 的形状为 (n, p),则您要添加形状为 (n, 1)、(1, n) 和 (n, n) 的矩阵。

Theano 似乎默默地重复每个一维成员的行(分别是列)以匹配点积的行数和列数。

如果这是你想要的

在reshape中,你可能应该根据basic tensor functionality : reshape指定ndim=2

If the shape is a Variable argument, then you might need to use the optional ndim parameter to declare how many elements the shape has, and therefore how many dimensions the reshaped Variable will have.

此外,似乎 squared_euclidean_distances 应该始终为正,除非差异中的不精确错误将零值更改为小的负值。如果这是真的,并且如果负值是您所看到的 NaN 的原因,那么您确实可以通过将 squared_euclidean_distances 包围在 abs(...).

中来消除它们而不会破坏您的结果