使用矩阵分解找到类似的音乐
参考:
https://www.jianshu.com/p/968c45bef58e
https://www.benfrederickson.com/matrix-factorization/
https://github.com/benfred/implicit
https://blog.csdn.net/weixin_33913377/article/details/88249128
https://flashgene.com/archives/40626.html
接下来会通过矩阵分解来做推荐系统的内容。下面会讲到如何使用几种不同的矩阵分解算法计算寻找类似的音乐。
加载数据
import pandas
import scipy
import numpy as np
from scipy.sparse import csr_matrix, diags
from scipy.sparse.linalg import spsolve
from implicit.nearest_neighbours import bm25_weight
#加载数据
data =pandas.read_table("F:/recommendation/lastfm-dataset-360K/usersha1-artmbid-artname-plays.tsv",
usecols=[0, 2, 3], names=['user', 'artist', 'plays'],na_filter=False)
# 做user_id与artist_name到user_item_matrix index的映射.
data['user'] = data['user'].astype("category")
data['artist'] = data['artist'].astype("category")
# 根据上一步生成的id到index的映射,及df的id的关系,生成csr_matrix.
plays = csr_matrix((data['plays'].astype(float), (data['artist'].cat.codes, data['user'].cat.codes)))
>>> print ('user count ', plays.shape[0])
user count 292365
>>> print ('artist count ', plays.shape[1])
artist count 358868
>>> type(plays)
<class 'scipy.sparse.csr.csr_matrix'>
>>> print ('plays matrix memory usage: %d MB.' % (plays.data.nbytes/1024/1024))
plays matrix memory usage: 133 MB.
这里返回的矩阵有300,000名artist和360,000名user,总共有大约1700万条目。每个条目都是用户播放艺术家的次数,其中包含从2008年以来Last.fm API收集的数据。
矩阵分解
简单原理
矩阵分解的原理其实很好理解。
在图2中,等号的左边是评分矩阵,也就是图1的那个矩阵,是已知数据,它被MF算法分解为等号右边的两个矩阵的乘积,其中一个被称为User矩阵,另一个被称为Item矩阵。如果评分矩阵有 n 行 m 列(即 n 个用户, m 个item),那幺分解出来的User矩阵就会有 n 行 k 列,其中,第 i 行构成的向量用于表示第 i 个用户。Item矩阵则有 k 行 m 列,其中,第 j 列构成的向量用于表示第 j 个item。这里的k是一个远小于 n 和 m 的正整数。当我们要计算第 i 个用户对第 j 个item的预测评分时,我们就可以用User矩阵的第i行和Item矩阵的第 j 列做内积,这个内积的值就是预测评分了。
那MF是如何从评分矩阵中分解出User矩阵和Item矩阵的呢?简单来说,MF把User矩阵和Item矩阵作为未知量,用它们表示出每个用户对每个item的预测评分,然后通过最小化预测评分跟实际评分的差异,学习出User矩阵和Item矩阵。也就是说,图2中只有等号左边的矩阵是已知的,等号右边的User矩阵和Item矩阵都是未知量,由MF通过最小化预测评分跟实际评分的差异学出来的。
隐式交替最小二乘法
推荐系统经常使用矩阵分解模型来 为用户生成个性化推荐。 已发现这些模型在推荐项目时效果很好,并且可以很容易地重复用于计算相关因子。
推荐系统中使用的许多MF模型都采用了明确的数据,用户使用类似5星级评定标准评估了他们喜欢和不喜欢的内容。它们通常通过将丢失的数据视为未知数来工作,然后使用SGD最小化重建错误。
这里的数据是隐含的 - 我们可以假设用户是喜欢这个artist的,但我们没有相应的信号,用户不喜欢artist。隐式数据通常比显式数据更丰富,更容易收集 - 即使您让用户给出5星评级,绝大多数评级都只是积极的,因此您无论如何都需要考虑隐含行为。
这意味着我们不能仅仅将丢失的数据视为未知数,而是将不听artist的用户视为用户可能不喜欢该artist的信号。
这对学习分解表示提出了一些挑战。
1、有效地进行这种因子分解:通过将未知数视为负数,天真的实现将查看输入矩阵中的每个条目。由于这里的维数大约是360K乘300K - 总共有超过1000亿条目要考虑,而只有1700万非零条目。
2、我们不能确定没有听artist的用户实际上意味着他们不喜欢它。可能还有其他原因导致artist没有被收听,特别是考虑到我们在数据集中每个用户只有前50位最多的artist。
这种方法使用二元偏好的不同置信水平来学习分解矩阵表示:看不见的项目被视为负面且置信度低,其中当前项目被视为正面更高的置信度。
然后,目标是通过最小化平方误差损失函数的置信加权和来学习用户因子Xu和artist因子Yi: 为了最小化用户因素,我们将项目因子固定为常数,然后采用损失函数的导数直接计算Xu:项目因子的计算方法和上面的方法一样。
可以仔细看看后面这篇文章,原理讲的很详细:https://flashgene.com/archives/40915.html
代码
其实我们可以用numpy或者是自己写公式完成矩阵分解的过程,但是会比较慢,这里提供一个比较快的方法就是利用implicit库中的bm25算法。
from implicit.nearest_neighbours import bm25_weight
from implicit.als import AlternatingLeastSquares
model = AlternatingLeastSquares(factors=50, regularization=0.01, iterations = 50)
model.fit(bm25_weight(plays.T.tocsr()))
user_factors = model.user_factors
artist_factors = model.item_factors
成功后会有下面这个结果
100%|██████████████████████████████████████████████████████████████████████████████████████| 50/50 [09:51<00:00, 11.78s/it]
获取相似artists
这位大牛写得很好:https://www.jianshu.com/p/968c45bef58e
生成两矩阵后,表示用户u和artist i的低维稠密向量分别为user_factors[u]与artist_factors[i],它们维数相同。在显示反馈中可用来做用户u对物品i的评分预测,两向量求点积即可;同时,对于两个artists的隐因子向量artist_factors[i1]与artist_factors[i2],依然可以使用余弦相似度公式来计算两者之间的相似度。
下面来看看user_factors[0]与artist_factors[0]
>>> type(user_factors)
<class 'numpy.ndarray'>
>>> len(user_factors)
292365
>>> user_factors[0]
array([ 0.17887832, 0.23526746, 0.25481272, -0.18529443, 0.17364255,
-0.22991548, 0.6827341 , -0.34902394, 0.2247184 , 0.02096994,
0.17046905, 0.17111559, 0.60559326, 0.26339382, 0.435168 ,
-0.2582095 , 0.24824281, -0.7039106 , 0.33817557, -0.69538784,
0.22189862, -0.40739846, -0.2771761 , 0.1825778 , 0.647495 ,
-0.29386276, 0.761443 , 0.24021243, 0.1693532 , -0.7197878 ,
0.20174506, 0.10556635, -0.21810254, -0.4659131 , 0.7693232 ,
-0.08548593, -0.20704047, 0.64759 , 0.03080724, 0.03778579,
0.17944813, 0.09445609, -0.3446881 , -0.70857066, 0.05282495,
-0.12701207, -0.22004537, -1.4822898 , 0.7014724 , -0.34034714],
dtype=float32)
>>> artist_factors[0]
array([ 0.01531421, 0.00738083, 0.00726112, 0.00052878, 0.00919406,
0.01631618, -0.00322854, 0.00685999, 0.00893916, 0.00787042,
0.001885 , 0.00339057, -0.00515547, 0.01278357, 0.0082635 ,
0.00667606, 0.00356188, -0.00529897, 0.00978106, 0.01245166,
-0.00435195, 0.00503607, 0.01380022, 0.00373216, 0.00326023,
0.00760479, 0.00651983, -0.00292513, 0.02097974, -0.00161952,
-0.00326325, 0.00900247, 0.00173472, 0.00786265, -0.00038997,
0.00040146, -0.0089154 , -0.00221551, 0.00439892, 0.01851321,
-0.00616612, 0.00391144, 0.00284126, 0.00142677, 0.01758659,
0.00420626, -0.0038978 , 0.00297126, 0.0168034 , -0.00503627],
dtype=float32)
我们可以看到,维度是相同的。
下面我们就要在30万的artist中找到与我们的目标artist[i]最相似的artist,需要遍历30万隐因子向量计算并排序,这个代价依然是巨大的。有一个库annoy(需要了解点蓝色annoy),这是一个用于解决这种近临搜索问题的库使用起来就是建立一个索引把向量加入进去,搜索时拿着索引去搜很快能得到结果,Approximate Nearest Neighbors,这个Approximate是指在时间与相似程度准确度上进行了取舍。
from annoy import AnnoyIndex
import random
artist_nn_index = AnnoyIndex(50)
for i in range(artist_factors.shape[0]):
artist_nn_index.add_item(i, artist_factors[i])
artist_nn_index.build(25)
def get_similar_artists(artist, n = 5):
similar_artist_list = list()
for i in artist_nn_index.get_nns_by_item(artist, n):
similar_artist_list.append(data['artist'].cat.categories[i])
return similar_artist_list
def get_col_index_by_artist(artist):
for index, i in enumerate(data['artist'].cat.categories):
if i == artist:
return index
return None
假设我们要寻找与yes相似的artist(前三个)
>>> yes = get_col_index_by_artist('yes')
>>> for i in artist_nn_index.get_nns_by_item(yes, 3):
... print(data['artist'].cat.categories[i])
...
yes
daddy yankee - fama ¡a bailar!
the man behind c
最后的结果就是出来三首歌。
在古二白的文章中,他的最后出结果的代码是
def get_similar_artists(artist, n = 20):
similar_artist_list = list()
for i in artist_nn_index.get_nns_by_item(artist, n):
similar_artist_list.append(df['artist'].cat.categories[i])
return similar_artist_list
可是当n超过4后,我这边就会报如下错误:
IndexError: index 312092 is out of bounds for axis 0 with size 292365
可能是在某一部分的操作出了问题,这边就不深究了,如果有找到解决方法的朋友们,麻烦指教一下哦,嘻嘻。有任何问题,都可以在评论区提出来哦。
下面会深究一下implicit包中的bm25算法。。感觉这要花费不少功夫了。。。。。。。。一入算法深似海呀。。。。。