Keras - 计算两个 3D 张量的余弦相似度矩阵

数据挖掘 机器学习 Python 深度学习 张量流 喀拉斯
2022-02-24 13:05:58

使用 TF 后端,我需要构建两个 3D 向量的相似度矩阵,它们的形状均为 (batch_size, N, M),分别为 N 和 M 个自然数。

该函数tf.losses.cosine_distance仅在一维张量之间。我需要构建一个张量矩阵 batch_sizexNxM 使得矩阵 [k][i][j] 将是 Tensor1[k][i] 和 Tensor2[k][j] 的余弦相似度。

我怎样才能做到这一点?

1个回答

我知道 Keras 或 tensorflow 中没有成对的距离操作。但是矩阵数学可以在 TF/Keras 后端代码中实现,然后放在 Keras 层中。

这是两个向量的余弦相似度的矩阵表示:

cos(θ)=ABA2B2

我将展示代码和确认它有效的测试。首先,生成非平凡的测试数据。

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