我需要在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。
现在我将确切地定义这三个函数应该做什么。
- 首先,我定义了一个名为"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
- 接近度
给定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
- 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
,impact
和popularity
,这里我假设rating_max
,rating_min
和rating_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。