用surprise實現SVD協同過濾推薦算法對本地數據做推薦

引入

surpriseSimple Python Recommendation System Engine的縮寫,是一個爲了實現推薦系統的框架。它自帶了SVD,user-based,item-based協同過濾算法等多種推薦算法,接口簡單,功能強大。但官方文檔寫的並不好,筆者花了不少時間,都沒有找到如何預測某個user對某個item進行打分這樣的基礎用法,所以把摸索後得到的經驗分享於此。

數據集

爲了驗證算法結果的正確性,自己定義了一個數據集,如下所示(保存爲mydata.csv)

1,1,1
1,2,2
1,3,3
1,4,4
1,5,5
2,1,1
2,2,2
2,3,3
2,4,4
2,5,5
3,1,1
3,2,2
3,3,3
3,4,4
3,5,5
4,1,1
4,2,2
4,3,3
4,4,4
4,5,5
5,2,2
5,3,3

這個數據集中,每一行說明了user對item的評分,比如“2,1,1”,是指user-2對item-1的評分是1。
可見,這個數據集中一共有5位user,5個item。每一位user對第i個item的打分都是i。這樣的簡化是爲了驗證算法的準確性。user-5只對item-2、item-3進行了評分,根據數據集中體現出來的規律,user-5沒打分的item,應該滿足如下規律:

  • user-5對item-1打分是1分
  • user-5對item-4打分是4分
  • user-5對item-5打分是5分

如果我們的算法得到的打分和上面的一樣,就說明算法的結果是100%正確的。如果算法得到的結果有所不同,但user-5對item-i的打分是依次升高的,也說明算法的結果是正確的。

某個user對某個item的評分

接下來我們講解如何實現user-5對item-i打分的預測值。每段代碼的講解寫在代碼段後面。

from surprise import SVD
from surprise import Dataset, Reader
import os

首先,導入surprise中必須的class:SVD是SVD算法,Dataset是創建滿足surprise需要的數據集所需的類,Reader是做數據讀取,類似pandas

# 指定文件所在路徑
file_path = os.path.expanduser('mydata.csv')
# 告訴文本閱讀器,文本的格式是怎麼樣的
reader = Reader(line_format='user item rating', sep=',')
# 加載數據
data = Dataset.load_from_file(file_path, reader=reader)
trainset = data.build_full_trainset()

上面這一段代碼讀取之前構建的本地數據集’mydata.csv’,並用Reader讀取數據,將數據加載爲Dataset結構。

這裏要注意的是,Dataset結構並不是surprise結構直接進行計算的最終結構,還需要通過data.build_full_trainset()將其轉換爲Trainset這樣的數據結構,才能在後續過程中直接用於訓練。

algo = SVD()
algo.fit(trainset)

這裏用SVD對數據進行訓練,也就是SVD推薦算法,將原始得分矩陣分解後,對未知得分進行重新計算的過程。

# we can now query for specific predicions
uid = str(5)  # raw user id
iid = str(1)  # raw item id

# get a prediction for specific users and items.
pred = algo.predict(uid, iid)
print('rating of user-{0} to item-{1} is '.format(uid, iid), pred.est)# rating of user-5 to item-1

接下來,我們就能計算user-5對item-1的打分(預測值)了。這裏要注意,user-id和item-id,都要將id轉換爲string類型。然後調用algo.predict(uid, iid)就能得到預測值了,原始的pred變量內容如下所示

user: 5          item: 1          r_ui = None   est = 2.38   {'was_impossible': False}

這其中,est就是預測值,我們這裏預測得到:

  • user-5對item-1打分是:2.3690
  • user-5對item-2打分是:2.3836
  • user-5對item-3打分是:3.0076
  • user-5對item-4打分是:3.44
  • user-5對item-5打分是:3.54

可見,user-5對item-i的打分是依次升高的,和預想的情況相同,說明算法的結果是正確的。

獲取評分最高的TOP-N

可以迭代用algo.predict(uid, iid)對每個得分進行計算,然後遍歷所有得分得到TOP-N。但這樣做稍顯笨拙,其實surprise作爲一個優秀的推薦系統框架,已經給出了更好的接口,下文進行詳述。每段代碼的講解寫在代碼段後面。

from collections import defaultdict
from surprise import SVD
from surprise import Dataset,Reader
import os

# 指定文件所在路徑
file_path = os.path.expanduser('mydata.csv')
# 告訴文本閱讀器,文本的格式是怎麼樣的
reader = Reader(line_format='user item rating', sep=',')
# 加載數據
data = Dataset.load_from_file(file_path, reader=reader)
trainset = data.build_full_trainset()

algo = SVD()
algo.fit(trainset)

首先是讀入數據,轉換爲Trainset結構,並用SVD算法進行訓練。這個過程和上一節的內容相同。

def get_top_n(predictions, n=10):

    # First map the predictions to each user.
    top_n = defaultdict(list)
    # uid: 用戶ID
    # iid: item ID
    # true_r: 真實得分
    # est:估計得分
    for uid, iid, true_r, est, _ in predictions:
        top_n[uid].append((iid, est))

    # Then sort the predictions for each user and retrieve the k highest ones.
    # 爲每一個用戶都尋找K個得分最高的item
    for uid, user_ratings in top_n.items():
        user_ratings.sort(key=lambda x: x[1], reverse=True)
        top_n[uid] = user_ratings[:n]

    return top_n

接下來定義get_top_n()函數,它能根據predictions結果進行解析,獲取top_n字典,該字典的key是user-id,value是該user打分(預測值)最高的n個item-id。

predictions的數據結構,是surprise中的算法自帶接口algo.test()的輸出值。

testset =  [
    ('5','1',0),# 想獲取第5個用戶對第1個item的得分
    ('5','4',0),# 0這個位置是真實得分,不知道時可以寫0
    ('5','5',0),# 但寫0後,就沒法進行算法評估了,因爲不知道真實值
]
predictions = algo.test(testset)

這裏是將需要計算的user-id,item-id,true-rating-value構建成測試集。注意真實得分true-rating-value我們可能無法得到,那用0來代替也是可以的。但寫0後,就沒法進行算法評估了,因爲不知道真實值,算法評估得到的值也是不準的。

algo.test()的輸出值predictions中的數據如下所示:

[Prediction(uid='5', iid='1', r_ui=0, est=2.2154870570408325, details={'was_impossible': False}),
 Prediction(uid='5', iid='4', r_ui=0, est=3.037552499639195, details={'was_impossible': False}),
 Prediction(uid='5', iid='5', r_ui=0, est=3.434713829035328, details={'was_impossible': False})]

uid就是user-id,iid就是item-id,其中est是預測得分值。

top_n = get_top_n(predictions, n=2)

# Print the recommended items for each user
for uid, user_ratings in top_n.items():
    print(uid, [iid for (iid, _) in user_ratings])

使用get_top_n()函數,獲取測試集中所有用戶得分最高的n(等於2)個item-id,並將print出來,得到結果如下

5 ['5', '4']

可見,對user-5來說,得分最高的是item-5和item-4。這與數據集要體現出來的結果是一樣的。

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章