PyCon 2018: SVD推薦系統在Python中的實踐

dwzb發表於2018-06-01

引言

繼搜尋引擎之後,推薦系統改變了使用者與網站之間的互動方式,在提高使用者參與度和多樣化推薦產品方面有重要的應用。亞馬遜有35%的利潤來源於它的推薦系統,Netflix有75%的使用者根據推薦系統選擇電影。

推薦系統是一個非常大的話題,本文介紹一種常用的基於模型的協同過濾演算法——SVD(奇異值分解),在python中的使用。

假設我們用m個使用者,n個商品,每個使用者對每個商品的評分可以組成一個m*n的二維矩陣。當然,這個矩陣中會有非常多的值是不知道的,可能是使用者沒有用過這個商品,也有可能使用者使用後沒有進行評分。如下圖所示

PyCon 2018: SVD推薦系統在Python中的實踐

圖中空白位置即未知的值。接下來,我們需要做的是根據這個殘缺的二維矩陣中已知的值,預測出未知的值,即預測出每一個使用者對每一個商品的評分。

可以想象,當矩陣被預測值補充完整之後,矩陣的每一行即表示一個使用者對所有商品的評分,可以從這些評分中提取評分最高的幾個商品推薦給使用者,這樣我們就完成了一個推薦系統模型。

接下來,就是如何通過已知值預測未知值的問題了,這裡我們採用矩陣分解的方式,如圖所示

PyCon 2018: SVD推薦系統在Python中的實踐

中間矩陣可以拆分為左邊和上邊兩個矩陣的乘積,這就是奇異值分解,一個矩陣總是可以拆分成兩個矩陣相乘,SVD的原理可以見這篇部落格,SVD方法在推薦系統中應用的原理可以參考這篇部落格,下面,我們主要來講講如何在python中使用。

初始化及建立模型

首先需要安裝surprise庫,用下面這條命令

pip install scikit-surprise
複製程式碼

假設我們現在有這樣的資料(注:load_movielens函式不是庫內建的,需要拿資料檔案並自己定義,詳情見文末連結中的作者原始碼)

movielens_df: pd.DataFrame = load_movielens()
movielens_df.head(5)

        user_id	    movie_title	            rating
36649	User 742	Jerry Maguire (1996)	4
2478	User 908	Usual Suspects, The (1995)	3
82838	User 758	Real Genius (1985)	4
69729	User 393	Things to Do in Denver when You're Dead (1995)	3
36560	User 66	    Jerry Maguire (1996)	4
複製程式碼

即使用者與電影之間的評分對應關係,下面匯入需要的模組

from surprise import SVD
from surprise import Dataset, Reader
from surprise.model_selection import cross_validate, train_test_split
複製程式碼

下面開始正式建模

第一步:初始化reader,指定評分範圍為1分到5分

reader = Reader(rating_scale=(1, 5))
複製程式碼

第二步:初始化資料,傳入的資料只能有3列,必須按照這樣的順序[user_id, product_id, rating]

data = Dataset.load_from_df(movielens_df, reader)
複製程式碼

這裡需要注意:data變數已經不是DataFrame型別了,而是surprise庫中的一種資料型別。從上面movielens_df的結果可以看出,我們的資料不是本文最初提到的矩陣形式,所以這一步轉換就會將資料轉化成surprise庫需要的形式,便於之後的演算法求解。

第三步:拆分訓練集與測試集,75%的樣本作為訓練集,25%的樣本作為測試集

trainset, testset = train_test_split(data, test_size=.25)
複製程式碼

這裡的trainset的型別是surprise.dataset.Trainset型別,我們可以檢視資料的基本資訊

trainset.n_users # 943
trainset.n_items # 596
複製程式碼

這說明我們要用於訓練的樣本共有943個使用者,596個商品。

第四步:訓練模型,指定有100個隱含特徵,使用訓練集進行訓練

model = SVD(n_factors=100)
model.fit(trainset)
複製程式碼

這裡需要說明一下,100個隱含特徵是指,原本943*596的矩陣會被拆分成943*100和100*596的兩個矩陣乘積,n_factors值可以任意指定只要不超過596即可,但是設定不同的值將會擬合出不同的模型,需要選擇使結果較優的值。

我們也可以檢視拆分出來的兩個矩陣

model.pu.shape # (943, 100)
model.qi.shape # (596, 100)
複製程式碼

根據模型結果進行推薦

預測一個使用者對一個電影的評分

指定使用者和電影名即可

a_user = "User 196"
a_product = "Toy Story (1995)"
model.predict(a_user, a_product)

# Prediction(uid='User 196', iid='Toy Story (1995)', r_ui=None, est=3.93380711688207, details={'was_impossible': False})
複製程式碼

電影之間相關性

這裡我們需要寫get_vector_by_movie_titlecosine_distance函式(詳情見文末連結中作者原始碼)

之後我們就可以實現輸入兩個電影名稱,即可獲得他們之間的相關性

toy_story_vec = get_vector_by_movie_title('Toy Story (1995)', model)
wizard_of_oz_vec = get_vector_by_movie_title('Wizard of Oz, The (1939)', model)

similarity_score = cosine_distance(toy_story_vec, wizard_of_oz_vec)
similarity_score
# 0.9461284008856982
複製程式碼

這是完全不考慮導演等電影特徵計算出來的電影相似度,因為我們只使用了評分資料。

尋找與一個電影最相似的電影

首先需要實現get_top_similarities函式,獲得最相似的五個電影,最後效果如下

get_top_similarities('Star Wars (1977)', model)

	vector cosine distance	movie title
0	0.000000	            Star Wars (1977)
1	0.262668	            Empire Strikes Back, The (1980)
2	0.295667	            Return of the Jedi (1983)
3	0.435423	            Raiders of the Lost Ark (1981)
複製程式碼

參考資料

1.視訊 Daniel Pyrathon - A practical guide to Singular Value Decomposition in Python - PyCon 2018

2.surprise幫助文件

3.視訊配套程式碼

相關文章