在python中创建NxN相似/距离矩阵的有效方法



我需要在python中创建一个N = 943的NxN相似矩阵。我最初使用sklearn实现cosine_similarity,但现在我需要使用更复杂和非标准的距离度量。

下午好,我有一个用户-电影数据帧(表中的NaN表示用户没有对这些电影进行评级)

| movie_id | 1 | 2 | 3 | 4 | 5 |
|----------|---|---|---|---|---|
| user_id  |   |   |   |   |   |
| 1        | 1 | 1 | NaN | 4 | 5 |
| 2        | NaN | 1 | 1 | 5 | 5 |
| 3        | 4 | NaN | 4 | 1 | 2 |

我需要对用户-电影数据帧应用3个单独的函数:接近度、影响和受欢迎程度。
两个用户之间的最终相似度由接近度、影响力和受欢迎程度的乘积给出。
现在棘手的部分是,我只需要将上述3个函数应用于"co "每个用户的项。例如,在计算user1和user2的相似度时,我们应该只考虑movie_ids 2、4和5。

现在我将确切地定义这三个函数应该做什么。

  1. 首先,我定义了一个名为"agreement">
    的助手方法,给定两个用户的两个评分,如果两个评分都在中位数的同一侧,则该函数返回True。在我们的例子中,中位数是2.5。其他假。
def agreement(rating1: int, rating2: int) -> bool:
if ((rating1 > 2.5 and rating2 < 2.5) or (rating1 < 2.5 and rating2 > 2.5)):
return False 
else:
True 
  1. 接近度
    给定2个用户的2个评分,如果2个评分一致,此函数仅计算绝对差。如果评级不一致,则适用处罚。
def proximity(rating1: int, rating2: int) -> float: 
if(agreement(rating1, rating2)):
dist = np.absolute(rating1 - rating2)
else: 
dist = 2 * np.absolute(rating1 - rating2)
prox = ((2*(rating_max - rating_min) + 1) - dist) ** 2
return prox
  1. Impact
    给定2个用户的2个评分,如果2个评分一致,此函数计算一个impact_score。如果两个评分不一致,则返回1/impact_score
def impact(rating1: int, rating2: int) -> float: 
impact_score = (np.absolute(rating1 - rating_median) + 1) * (np.absolute(rating2 - rating_median) + 1)
if(agreement(rating1, rating2)):
return impact_score 
else: 
return 1/impact_score 
  • 的声望。
    给定2个用户的2个评分和给定movie_id(mu_k)的平均评分,如果这2个评分都大于(或小于)给定电影的平均评分,则此方法计算pop_score。
  • def popularity(rating1: int, rating2: int, mu_k) -> float: 
    pop = 1
    if((rating1 > mu_k and rating2 > mu_k) or (rating1 < mu_k and rating2 < mu_k)):
    pop = 1 + ((rating1 + rating2)/2 - mu_k)**2
    return pop
    

    最终的相似矩阵应该是这样的:

    #           0          1          2
    #0   1.000000  60.972245  12.761905
    #1  60.972245   1.000000   9.790476
    #2  12.761905   9.790476   1.000000
    

    问题是我当前的实现非常慢。计算N=943的矩阵大约需要1.5小时。

    我当前循环遍历NxN矩阵的每个单元并单独应用所有3个函数(当前实现代码:https://pastebin.com/zfcyBhJz)。

    所以我想知道是否有一种更快,更有效的方法来生成所需的相似性矩阵给定3个函数要使用?

    使用numpyp.ma.MaskedArray,在充分发挥广播功能的同时,可以获得非常好的性能。

    首先得到df:

    values
    import numpy as np
    from numpy import nan
    
    ratings = np.array([[1., 1., nan, 4., 5.],
    [nan, 1., 1., 5., 5.],
    [4., nan, 4., 1., 2.]])
    # ratings = df_ratings.values
    

    转换成MaskedArray:

    from numpy.ma import masked_invalid
    
    ratings = masked_invalid(ratings)
    # masked_array(
    #   data=[[1.0, 1.0, --, 4.0, 5.0],
    #         [--, 1.0, 1.0, 5.0, 5.0],
    #         [4.0, --, 4.0, 1.0, 2.0]],
    #   mask=[[False, False,  True, False, False],
    #         [ True, False, False, False, False],
    #         [False,  True, False, False, False]],
    #   fill_value=1e+20)
    

    计算每对用户之间所有评级的agrement的负值:

    temp = ratings - 2.5
    not_agreements = temp[:, None] * temp[None] < 0
    # Equivalent to
    # from numpy.ma import masked_array
    # not_argeements = masked_array([masked_array([(i - 2.5) * (j - 2.5) < 0 for j in ratings]) for i in ratings])
    
    同样,计算所有proximity,impactpopularity,这里我假设rating_max,rating_minrating_median都是标量:
    dist = np.abs(ratings[:, None] - ratings[None])
    dist[not_agreements] *= 2
    prox = ((2 * (rating_max - rating_min) + 1) - dist) ** 2
    temp = np.abs(ratings - rating_median) + 1
    impact_score = temp[:, None] * temp[None]
    impact_score[not_agreements] = 1 / impact_score[not_agreements]
    mu_k = ratings.mean(0)
    temp = ratings - mu_k
    shape = ratings.shape
    pop = np.ones(shape[:1] + shape)
    mask = temp[:, None] * temp[None] > 0
    pop[mask] += ((temp[:, None] + temp[None]) / 2)[mask] ** 2
    

    将它们相乘并沿着最后一个轴求和,然后将对角线上的值设置为1,最后得到您想要的结果:

    similarity_matrix = (prox * impact_score * pop).sum(-1)
    similarity_matrix[np.diag_indices_from(similarity_matrix)] = 1
    similarity_matrix_df = pd.DataFrame(similarity_matrix, index=df_ratings.index, columns=df_ratings.index)
    

    经过测试,你的遍历方法的运行时间与你的例子中我的方法相似,但是随着数组的扩展,你的方法的运行时间增加得非常快。当数组的形状达到(48,50)时,需要10s,而我的矢量化方法只需要0.06s。

    最新更新