代码之家  ›  专栏  ›  技术社区  ›  Funzo

如何计算一个矩阵中所有行相对于另一个矩阵中所有行的余弦_相似性

  •  20
  • Funzo  · 技术社区  · 6 年前

    在pytorch中,假设我有两个矩阵,我如何计算每一行中所有行与另一行中所有行的余弦相似性。

    例如

    考虑到输入=

    matrix_1 = [a b] 
               [c d] 
    matrix_2 = [e f] 
               [g h]
    

    我希望输出是

    输出=

     [cosine_sim([a b] [e f])  cosine_sim([a b] [g h])]
     [cosine_sim([c d] [e f])  cosine_sim([c d] [g h])] 
    

    目前我正在使用手电筒。nn。功能的cosine_相似度(矩阵_1,矩阵_2),返回该行与另一个矩阵中对应行的余弦。

    在我的示例中,我只有2行,但我希望有一个适用于多行的解决方案。我甚至想处理每个矩阵中的行数不同的情况。

    我意识到我可以使用expand,但是我不想使用这么大的内存占用。

    4 回复  |  直到 6 年前
        1
  •  26
  •   benjaminplanche    6 年前

    通过手动计算相似度并使用矩阵乘法+换位:

    import torch
    from scipy import spatial
    import numpy as np
    
    a = torch.randn(2, 2)
    b = torch.randn(3, 2) # different row number, for the fun
    
    # Given that cos_sim(u, v) = dot(u, v) / (norm(u) * norm(v))
    #                          = dot(u / norm(u), v / norm(v))
    # We fist normalize the rows, before computing their dot products via transposition:
    a_norm = a / a.norm(dim=1)[:, None]
    b_norm = b / b.norm(dim=1)[:, None]
    res = torch.mm(a_norm, b_norm.transpose(0,1))
    print(res)
    #  0.9978 -0.9986 -0.9985
    # -0.8629  0.9172  0.9172
    
    # -------
    # Let's verify with numpy/scipy if our computations are correct:
    a_n = a.numpy()
    b_n = b.numpy()
    res_n = np.zeros((2, 3))
    for i in range(2):
        for j in range(3):
            # cos_sim(u, v) = 1 - cos_dist(u, v)
            res_n[i, j] = 1 - spatial.distance.cosine(a_n[i], b_n[j])
    print(res_n)
    # [[ 0.9978022  -0.99855876 -0.99854881]
    #  [-0.86285472  0.91716063  0.9172349 ]]
    
        2
  •  15
  •   Zhang Yu    5 年前

    添加 eps 对于基于benjaminplanche答案的数值稳定性:

    def sim_matrix(a, b, eps=1e-8):
        """
        added eps for numerical stability
        """
        a_n, b_n = a.norm(dim=1)[:, None], b.norm(dim=1)[:, None]
        a_norm = a / torch.max(a_n, eps * torch.ones_like(a_n))
        b_norm = b / torch.max(b_n, eps * torch.ones_like(b_n))
        sim_mt = torch.mm(a_norm, b_norm.transpose(0, 1))
        return sim_mt
    
        3
  •  3
  •   Theis Jendal    3 年前

    等同于 Zhang Yu 但是使用钳形代替max,并且不创建新的张量。我用timeit做了一个小测试,这表明夹钳更快,尽管我不擅长使用那个工具。

    def sim_matrix(a, b, eps=1e-8):
        """
        added eps for numerical stability
        """
        a_n, b_n = a.norm(dim=1)[:, None], b.norm(dim=1)[:, None]
        a_norm = a / torch.clamp(a_n, min=eps)
        b_norm = b / torch.clamp(b_n, min=eps)
        sim_mt = torch.mm(a_norm, b_norm.transpose(0, 1))
        return sim_mt
    
        4
  •  0
  •   lawson    2 年前

    在计算矩阵中行/列向量之间的相似性时,不需要使用循环。这里有一个例子。

    import torch as t
    a = t.randn(2,4)
    print(a)
    
    # step 1. 计算行向量的长度
    len_a = t.sqrt(t.sum(a**2,dim=-1))
    print(len_a)
    
    b = len_a.unsqueeze(1).expand(-1,2)
    c = len_a.expand(2,-1)
    # print(b)
    # print(c)
    
    # step2. 计算乘积
    x = a @ a.T
    print(x)
    
    # step3. 计算最后的结果
    res = x/(b*c)
    print(res)