在不在 Tensorflow 中复制张量的情况下批量计算成对距离?

Compute pairwise distance in a batch without replicating tensor in Tensorflow?

我想在 Tensorflow 中计算一批特征的成对平方距离。我有一个使用 + 和 * 操作的简单实现 平铺原始张量:

def pairwise_l2_norm2(x, y, scope=None):
    with tf.op_scope([x, y], scope, 'pairwise_l2_norm2'):
        size_x = tf.shape(x)[0]
        size_y = tf.shape(y)[0]
        xx = tf.expand_dims(x, -1)
        xx = tf.tile(xx, tf.pack([1, 1, size_y]))

        yy = tf.expand_dims(y, -1)
        yy = tf.tile(yy, tf.pack([1, 1, size_x]))
        yy = tf.transpose(yy, perm=[2, 1, 0])

        diff = tf.sub(xx, yy)
        square_diff = tf.square(diff)

        square_dist = tf.reduce_sum(square_diff, 1)

        return square_dist

此函数将大小为 (m,d) 和 (n,d) 的两个矩阵作为输入,并计算每个行向量之间的平方距离。输出是一个大小为 (m,n) 且元素为 'd_ij = dist(x_i, y_j)'.

的矩阵

问题是我有大批量和高暗淡特征 'm, n, d' 复制张量会消耗大量内存。 我正在寻找另一种方法来实现它而不增加内存使用量并且只存储最终距离张量。有点双重循环原始张量。

您可以使用一些线性代数将其转换为矩阵运算。请注意,您需要的矩阵 D 其中 a[i] 是原始矩阵的第 i 行,

D[i,j] = (a[i]-a[j])(a[i]-a[j])'

您可以将其重写为

D[i,j] = r[i] - 2 a[i]a[j]' + r[j]

其中 r[i] 是原始矩阵第 i 行的平方范数。

在支持标准 broadcasting rules 的系统中,您可以将 r 视为列向量并将 D 写为

D = r - 2 A A' + r'

在 TensorFlow 中你可以这样写

A = tf.constant([[1, 1], [2, 2], [3, 3]])
r = tf.reduce_sum(A*A, 1)

# turn r into column vector
r = tf.reshape(r, [-1, 1])
D = r - 2*tf.matmul(A, tf.transpose(A)) + tf.transpose(r)
sess = tf.Session()
sess.run(D)

结果

array([[0, 2, 8],
       [2, 0, 2],
       [8, 2, 0]], dtype=int32)

使用squared_difference

def squared_dist(A): 
    expanded_a = tf.expand_dims(A, 1)
    expanded_b = tf.expand_dims(A, 0)
    distances = tf.reduce_sum(tf.squared_difference(expanded_a, expanded_b), 2)
    return distances

我注意到的一件事是,这个使用 tf.squared_difference 的解决方案让我对非常大的向量内存不足 (OOM),而 @YaroslavBulatov 的方法却没有。所以,我认为分解操作会产生更小的内存占用(我认为 squared_difference 会在后台处理得更好)。

如果您想要计算其他方法,请更改 tf 模块的顺序。

def compute_euclidean_distance(x, y):
    size_x = x.shape.dims[0]
    size_y = y.shape.dims[0]
    for i in range(size_x):
        tile_one = tf.reshape(tf.tile(x[i], [size_y]), [size_y, -1])
        eu_one = tf.expand_dims(tf.sqrt(tf.reduce_sum(tf.pow(tf.subtract(tile_one, y), 2), axis=1)), axis=0)
        if i == 0:
            d = eu_one
        else:
            d = tf.concat([d, eu_one], axis=0)
return d

这里是两个张量坐标 AB 的更通用的解决方案:

def squared_dist(A, B):
  assert A.shape.as_list() == B.shape.as_list()

  row_norms_A = tf.reduce_sum(tf.square(A), axis=1)
  row_norms_A = tf.reshape(row_norms_A, [-1, 1])  # Column vector.

  row_norms_B = tf.reduce_sum(tf.square(B), axis=1)
  row_norms_B = tf.reshape(row_norms_B, [1, -1])  # Row vector.

  return row_norms_A - 2 * tf.matmul(A, tf.transpose(B)) + row_norms_B

注意这是平方距离。如果要将其更改为欧氏距离,请对结果执行 tf.sqrt。如果你想这样做,不要忘记添加一个小常量来补偿浮点不稳定性:dist = tf.sqrt(squared_dist(A, B) + 1e-6).