參考:
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í)很好理解。

在圖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)目因子的計(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)不少功夫了。。。。。。。。一入算法深似海呀。。。。。