引言
繼搜尋引擎之後,推薦系統改變了使用者與網站之間的互動方式,在提高使用者參與度和多樣化推薦產品方面有重要的應用。亞馬遜有35%的利潤來源於它的推薦系統,Netflix有75%的使用者根據推薦系統選擇電影。
推薦系統是一個非常大的話題,本文介紹一種常用的基於模型的協同過濾演算法——SVD(奇異值分解),在python中的使用。
假設我們用m個使用者,n個商品,每個使用者對每個商品的評分可以組成一個m*n的二維矩陣。當然,這個矩陣中會有非常多的值是不知道的,可能是使用者沒有用過這個商品,也有可能使用者使用後沒有進行評分。如下圖所示
圖中空白位置即未知的值。接下來,我們需要做的是根據這個殘缺的二維矩陣中已知的值,預測出未知的值,即預測出每一個使用者對每一個商品的評分。
可以想象,當矩陣被預測值補充完整之後,矩陣的每一行即表示一個使用者對所有商品的評分,可以從這些評分中提取評分最高的幾個商品推薦給使用者,這樣我們就完成了一個推薦系統模型。
接下來,就是如何通過已知值預測未知值的問題了,這裡我們採用矩陣分解的方式,如圖所示
中間矩陣可以拆分為左邊和上邊兩個矩陣的乘積,這就是奇異值分解,一個矩陣總是可以拆分成兩個矩陣相乘,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_title
和cosine_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
3.視訊配套程式碼