使用 TF 后端,我需要构建两个 3D 向量的相似度矩阵,它们的形状均为 (batch_size, N, M),分别为 N 和 M 个自然数。
该函数tf.losses.cosine_distance
仅在一维张量之间。我需要构建一个张量矩阵 batch_sizexNxM 使得矩阵 [k][i][j] 将是 Tensor1[k][i] 和 Tensor2[k][j] 的余弦相似度。
我怎样才能做到这一点?
使用 TF 后端,我需要构建两个 3D 向量的相似度矩阵,它们的形状均为 (batch_size, N, M),分别为 N 和 M 个自然数。
该函数tf.losses.cosine_distance
仅在一维张量之间。我需要构建一个张量矩阵 batch_sizexNxM 使得矩阵 [k][i][j] 将是 Tensor1[k][i] 和 Tensor2[k][j] 的余弦相似度。
我怎样才能做到这一点?
我知道 Keras 或 tensorflow 中没有成对的距离操作。但是矩阵数学可以在 TF/Keras 后端代码中实现,然后放在 Keras 层中。
这是两个向量的余弦相似度的矩阵表示:
我将展示代码和确认它有效的测试。首先,生成非平凡的测试数据。
import numpy as np
import keras
import keras.backend as K
# set up test data
n_batch = 100
n = 400 # number of points in the first set
m = 500 # number of points in the second set
d = 200 # number of dimensions
A = np.random.rand(n_batch, n, d)
B = np.random.rand(n_batch, m, d)
定义成对余弦相似度函数。
# convenience l2_norm function
def l2_norm(x, axis=None):
"""
takes an input tensor and returns the l2 norm along specified axis
"""
square_sum = K.sum(K.square(x), axis=axis, keepdims=True)
norm = K.sqrt(K.maximum(square_sum, K.epsilon()))
return norm
def pairwise_cosine_sim(A_B):
"""
A [batch x n x d] tensor of n rows with d dimensions
B [batch x m x d] tensor of n rows with d dimensions
returns:
D [batch x n x m] tensor of cosine similarity scores between each point i<n, j<m
"""
A, B = A_B
A_mag = l2_norm(A, axis=2)
B_mag = l2_norm(B, axis=2)
num = K.batch_dot(A_tensor, K.permute_dimensions(B_tensor, (0,2,1)))
den = (A_mag * K.permute_dimensions(B_mag, (0,2,1)))
dist_mat = num / den
return dist_mat
围绕函数构建一个浅层 Keras 模型。
# build dummy model
A_tensor = K.constant(A)
B_tensor = K.constant(B)
A_input = keras.Input(tensor=A_tensor)
B_input = keras.Input(tensor=B_tensor)
dist_output = keras.layers.Lambda(pairwise_cosine_sim)([A_input, B_input])
dist_model = keras.Model(inputs=[A_input, B_input], outputs=dist_output)
dist_model.compile("sgd", "mse")
与 sklearn 实现比较
sk_dist = np.zeros( (n_batch, n, m) )
for i in range(n_batch):
sk_dist[i,...] = cosine_similarity(A[i,...], B[i,...])
keras_dist = dist_model.predict(None, steps=1)
np.allclose(sk_dist, keras_dist)
> True