1. 程式人生 > >用python寫一個簡單的推薦系統

用python寫一個簡單的推薦系統

前言

在上篇文章豆瓣電影,電視劇DM實戰中提及到,我和室友們產生了劇荒,萌生出要做一個個人用的推薦系統,解決劇荒的問題,經過一輪的死纏爛打,這個個人推薦系統終於成型了。

今天來分享一下心得,對此感興趣的朋友可以自己對著寫一個。

傳統推薦系統演算法

首先介紹一下傳統的推薦系統方法,之所以叫它傳統,是因為大部分學習資料上都是用這一個方法。

我們來假設有這麼一個矩陣(用python的列表表示):

  [# A B C D E
    [2,0,0,4,4], #1
    [5,5,5,3,3], #2
    [2,4,2,1,2]  #3
    ......
  ]

矩陣的行代表使用者,列表示物品,其交點表示使用者對該物品的評分。

假設現在使用者1需要選商品,推薦系統則假設其會選擇並未選擇過的商品,因此,系統會在第一行中尋找評分為0的物品,顯然會找到BC。這時,該怎麼知道是推薦B還是C呢?(假設使用者只需推薦一個),這時則需要計算B、C和使用者以前選擇過的物品(已評分)的相似度。

僅僅算出相似度還不夠,因為你不能判斷這到底是好的那一部分相似還是壞的部分相似。所以這時,我們需要引入使用者的評分作為相似度計算的權重評分X相似度得到最後的得分(該得分會一直累加,則B的推薦得分會是B與A,D,E的相似得分的累加和)。這樣一來,評分低物品的最後得分自然就低,評分高的物品自然得分就高,這時問題就簡化成排序問題了。

顯然,上述問題的核心在於如何計算相似度。

這裡給出計算相似度的兩種方法:

  • 歐式距離法
    BA的相似度為例:
    similar = 1/sqrt((0-2)^2 + (5-5)^2 + (4-2)^2 ……) 最後B與A的相似得分還得乘上評分,score = similar * 2

  • 餘弦相似度

    costheta=fracAcdotB||A||||B||
    AB為兩列向量,||A||表示A的2範數
    特別注意一點的是,cos的取值是-1~1,我們需要將其歸一化,即把範圍弄成在0~1上。於是相似度計算公司變成0.5 + 0.5*cos

少使用者推薦系統的創新

在上述的內容中,我們可以發現傳統的方法有一個特出的問題,傳統的演算法需要大量的使用者評分,即矩陣的行數需要較多才能得出的結果才值得參考

。這一個需求咋看起來是沒什麼問題,也符合我們的邏輯,唯有資料量足夠,我們才能找到較為準確的規律嘛。

但回到我的需求上來說,這可是一個明顯的缺點,在前言我說明的需求上說過這是一個給宿舍甚至是個人使用的推薦系統。

也就是說:

  • 我們無法提供大量資料。

  • 我們很懶,我們最可能是告訴系統我從它的推薦中採納了哪一部的電影,而不會去評分,我們可能告訴它質量是否可以接受,但不會像豆瓣使用者那樣給出0~10的準確分數。

因此,傳統的推薦演算法有很多不適合我需求的地方,但是看問題要看本質。無非就是根據使用者的特性,或者根據商品特性,進行與訓練好的模型進行相似性比較。抓住這些特點,我做了少少的"創新"

  • 不基於使用者的評分作相似度,而是用商品的label做標準
    現在很多商品尤其是音樂或者電影,都會具有自己的label,比如說喜劇懸疑,其次還有主演導演等可以作為其特徵。電商平臺上也有諸如商品種類衣服,女鞋包包,等,而某些物品,例如衣服,那麼衣服的品牌size,都可以作為使用者的一個選擇的特徵。

  • 使用者模型是動態更新的
    這一點不難理解,如果一個使用者長期使用使用該系統,那麼他的選擇中很可能已經覆蓋了大量的label,這時基於label的推薦系統則很難區分該使用者的喜好。這是我們有兩個解決方法。第一個是允許使用者自定義label,比如SF就可以自定義問題或文章的標籤,這樣增大了label的多樣性。當然,這個解決方案只能算一個緩解的方案,要想徹底解決,我覺得需要給特徵選定有效期。
    增加有效期後,使用者的選擇可以反應出一個時間段內的需求。假設這樣一個場景,一名使用者準備去旅遊了,他可能會大量瀏覽旅遊用品的出售頁面,例如一次性牙膏等,這時,就可以向該使用者推薦出售旅行用品的網站了。而超過了特徵的有效期,例如一週,這時使用者已經旅遊回來,因為特徵已經無效,推薦系統不再推薦旅遊用品(這樣使用者不會覺得莫名其妙。個人經歷,現在某些網站就往往會出現明顯已經超過我感興趣時限的推薦),而是開始重新收集使用者新一週瀏覽的特徵,動態構建使用者模型,推薦使用者下一階段他可能需要的物品

實現上述想法,在python中,我們可以這麼做,實現如下字典

record = {
    "labelName":(weight,time),
    "labelName2":(weight,time)
    ……
}

#labelName是標籤名稱,在該標籤下有一個元組,元組的第一個欄位是這個標籤的權重。
#權重越大,表示使用者越喜歡這個標籤。
#第二個欄位是建立該標籤的起始時間

在實現推薦時,則較為容易實現,給定testList。這時需要:

  • 建立名res的空字典

  • 遍歷testList,每一個物件命名為t

  • 遍歷t具有的label,根據labelrecord上獲取資訊。

  • 同時獲取當前時間time2,如果time2-time超出了規定時限,則該標籤的資訊無效,忽略該label,同時刪除record裡面的對應的欄位。

  • 若該標籤有效,則t的得分加1,並將t的下標index作為key假如到一個res

  • 遍歷完成後,對res字典按value排序

  • 最後,可以根據需要對排序結果進行訪問。併入只獲取最高的前5名。

這樣,一個適合少使用者的推薦系統就弄出來啦~

現在正在宿舍投入執行,至於效果如何可能要一段時間才知道了

後話

說明一下,github上只是提供了一個實現了上述改進後思路的類recommend.py,並不是一個成型的推薦系統,你可以下載後,根據這個類進行二次開發,比如:

  • 利用flask框架包裝成一個web應用

  • 結合該類並利用SMTP協議,弄一個自動往郵箱發信息的指令碼,推薦的電影資訊

  • 將類例項化,弄出簡單的命令列應用

遲下我會上傳一個使用falsk封裝的一個簡單的webserver去github,可以通過web API請求,返回json格式的電影資訊。

如有錯誤,望指正。

from: https://segmentfault.com/a/1190000005152849?utm_source=tuicool&utm_medium=referral