使用矩陣分解找到類似的音樂

參考:
http://www.itdecent.cn/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

接下來會(huì)通過矩陣分解來做推薦系統(tǒng)的內(nèi)容。下面會(huì)講到如何使用幾種不同的矩陣分解算法計(jì)算尋找類似的音樂。

加載數(shù)據(jù)

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
#加載數(shù)據(jù)
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")

# 根據(jù)上一步生成的id到index的映射,及df的id的關(guān)系,生成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萬條目。每個(gè)條目都是用戶播放藝術(shù)家的次數(shù),其中包含從2008年以來Last.fm API收集的數(shù)據(jù)。

矩陣分解

簡單原理

矩陣分解的原理其實(shí)很好理解。

就是把我們的評分矩陣分解為兩個(gè)矩陣乘積的算法,如下圖所示。

在圖2中,等號的左邊是評分矩陣,也就是圖1的那個(gè)矩陣,是已知數(shù)據(jù),它被MF算法分解為等號右邊的兩個(gè)矩陣的乘積,其中一個(gè)被稱為User矩陣,另一個(gè)被稱為Item矩陣。如果評分矩陣有 n 行 m 列(即 n 個(gè)用戶, m 個(gè)item),那幺分解出來的User矩陣就會(huì)有 n 行 k 列,其中,第 i 行構(gòu)成的向量用于表示第 i 個(gè)用戶。Item矩陣則有 k 行 m 列,其中,第 j 列構(gòu)成的向量用于表示第 j 個(gè)item。這里的k是一個(gè)遠(yuǎn)小于 n 和 m 的正整數(shù)。當(dāng)我們要計(jì)算第 i 個(gè)用戶對第 j 個(gè)item的預(yù)測評分時(shí),我們就可以用User矩陣的第i行和Item矩陣的第 j 列做內(nèi)積,這個(gè)內(nèi)積的值就是預(yù)測評分了。

那MF是如何從評分矩陣中分解出User矩陣和Item矩陣的呢?簡單來說,MF把User矩陣和Item矩陣作為未知量,用它們表示出每個(gè)用戶對每個(gè)item的預(yù)測評分,然后通過最小化預(yù)測評分跟實(shí)際評分的差異,學(xué)習(xí)出User矩陣和Item矩陣。也就是說,圖2中只有等號左邊的矩陣是已知的,等號右邊的User矩陣和Item矩陣都是未知量,由MF通過最小化預(yù)測評分跟實(shí)際評分的差異學(xué)出來的。

隱式交替最小二乘法

推薦系統(tǒng)經(jīng)常使用矩陣分解模型來 為用戶生成個(gè)性化推薦。 已發(fā)現(xiàn)這些模型在推薦項(xiàng)目時(shí)效果很好,并且可以很容易地重復(fù)用于計(jì)算相關(guān)因子。

推薦系統(tǒng)中使用的許多MF模型都采用了明確的數(shù)據(jù),用戶使用類似5星級評定標(biāo)準(zhǔn)評估了他們喜歡和不喜歡的內(nèi)容。它們通常通過將丟失的數(shù)據(jù)視為未知數(shù)來工作,然后使用SGD最小化重建錯(cuò)誤。

這里的數(shù)據(jù)是隱含的 - 我們可以假設(shè)用戶是喜歡這個(gè)artist的,但我們沒有相應(yīng)的信號,用戶不喜歡artist。隱式數(shù)據(jù)通常比顯式數(shù)據(jù)更豐富,更容易收集 - 即使您讓用戶給出5星評級,絕大多數(shù)評級都只是積極的,因此您無論如何都需要考慮隱含行為。

這意味著我們不能僅僅將丟失的數(shù)據(jù)視為未知數(shù),而是將不聽artist的用戶視為用戶可能不喜歡該artist的信號。

這對學(xué)習(xí)分解表示提出了一些挑戰(zhàn)。
1、有效地進(jìn)行這種因子分解:通過將未知數(shù)視為負(fù)數(shù),天真的實(shí)現(xiàn)將查看輸入矩陣中的每個(gè)條目。由于這里的維數(shù)大約是360K乘300K - 總共有超過1000億條目要考慮,而只有1700萬非零條目。
2、我們不能確定沒有聽artist的用戶實(shí)際上意味著他們不喜歡它??赡苓€有其他原因?qū)е耡rtist沒有被收聽,特別是考慮到我們在數(shù)據(jù)集中每個(gè)用戶只有前50位最多的artist。

隱式反饋數(shù)據(jù)集協(xié)作過濾以優(yōu)雅的方式解釋了這兩個(gè)挑戰(zhàn)

這種方法使用二元偏好的不同置信水平來學(xué)習(xí)分解矩陣表示:看不見的項(xiàng)目被視為負(fù)面且置信度低,其中當(dāng)前項(xiàng)目被視為正面更高的置信度。

然后,目標(biāo)是通過最小化平方誤差損失函數(shù)的置信加權(quán)和來學(xué)習(xí)用戶因子Xu和artist因子Yi:

為了最小化用戶因素,我們將項(xiàng)目因子固定為常數(shù),然后采用損失函數(shù)的導(dǎo)數(shù)直接計(jì)算Xu:

項(xiàng)目因子的計(jì)算方法和上面的方法一樣。

可以仔細(xì)看看后面這篇文章,原理講的很詳細(xì):https://flashgene.com/archives/40915.html

代碼

其實(shí)我們可以用numpy或者是自己寫公式完成矩陣分解的過程,但是會(huì)比較慢,這里提供一個(gè)比較快的方法就是利用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

成功后會(huì)有下面這個(gè)結(jié)果

100%|██████████████████████████████████████████████████████████████████████████████████████| 50/50 [09:51<00:00, 11.78s/it]

獲取相似artists

這位大牛寫得很好:http://www.itdecent.cn/p/968c45bef58e

生成兩矩陣后,表示用戶u和artist i的低維稠密向量分別為user_factors[u]與artist_factors[i],它們維數(shù)相同。在顯示反饋中可用來做用戶u對物品i的評分預(yù)測,兩向量求點(diǎn)積即可;同時(shí),對于兩個(gè)artists的隱因子向量artist_factors[i1]與artist_factors[i2],依然可以使用余弦相似度公式來計(jì)算兩者之間的相似度。
下面來看看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中找到與我們的目標(biāo)artist[i]最相似的artist,需要遍歷30萬隱因子向量計(jì)算并排序,這個(gè)代價(jià)依然是巨大的。有一個(gè)庫annoy(需要了解點(diǎn)藍(lán)色annoy),這是一個(gè)用于解決這種近臨搜索問題的庫使用起來就是建立一個(gè)索引把向量加入進(jìn)去,搜索時(shí)拿著索引去搜很快能得到結(jié)果,Approximate Nearest Neighbors,這個(gè)Approximate是指在時(shí)間與相似程度準(zhǔn)確度上進(jìn)行了取舍。

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

假設(shè)我們要尋找與yes相似的artist(前三個(gè))

>>> 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

最后的結(jié)果就是出來三首歌。
在古二白的文章中,他的最后出結(jié)果的代碼是

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

可是當(dāng)n超過4后,我這邊就會(huì)報(bào)如下錯(cuò)誤:

IndexError: index 312092 is out of bounds for axis 0 with size 292365

可能是在某一部分的操作出了問題,這邊就不深究了,如果有找到解決方法的朋友們,麻煩指教一下哦,嘻嘻。有任何問題,都可以在評論區(qū)提出來哦。

下面會(huì)深究一下implicit包中的bm25算法。。感覺這要花費(fèi)不少功夫了。。。。。。。。一入算法深似海呀。。。。。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容