使用矩阵分解找到类似的音乐

参考:
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算法。。感觉这要花费不少功夫了。。。。。。。。一入算法深似海呀。。。。。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容