一、簡介:
推薦系統是最常見的數據分析應用之一,包含淘寶、豆瓣、今日頭條都是利用推薦系統來推薦用戶內容。推薦算法的方式分為兩種,一種是根據用戶推薦,一種是根據商品推薦,根據用戶推薦主要是找出和這個用戶興趣相近的其他用戶,再推薦其他用戶也喜歡的東西給這個用戶,而根據商品推薦則是根據喜歡這個商品的人也喜歡哪些商品區進行推薦,現在很多是基於這兩種算法去進行混合應用。本文會用python演示第一種算法,目標是對用戶推薦電影。
二、獲取數據:
在movielens上,有許多的用戶對電影評價數據,可以至(https://grouplens.org/datasets/movielens/)進行下載,下載完后打開資料夾,有個叫u.data的資料夾,打開會看到以下的數據
第一列代表用戶ID,第二列代表電影的ID,第三列代表評分(1-5分),第四列是時間戳
三、數據預處理
拿到了原始數據后,我們會發現幾個問題,就是1、我根本不需要時間戳。2、同一個用戶的評價散落在不連續好幾行,不好進行分析。這個時候我們就需要進行數據的預處理,首先觀察這份數據發現由於每個用戶評價的電影都不相同,可能在200部里隨機挑了5-10部來評分,所以如果用表格來顯示的話會有很多的空格,這個時候KV型數據儲存方式就很好用,利用一個鍵(key)對應一個值(value),這個時候就可以利用python的字典(dict),他可以記錄鍵值對應。舉例來說,我用戶ID為‘941’的用戶,對電影ID為‘763’的評價是3分,那我只需要儲存【941】【763】=3這樣就可以了,並把user合並,讓界面更美觀,如下圖
可以看到我把用戶ID為‘941’評價的電影都列了出來,后面還跟了評分值,這樣我后面在做分析的時候讀取數據就比較方便了,下面是數據讀取到處理的代碼
def load_data(): f = open('u.data') user_list={} for line in f: (user,movie,rating,ts) = line.split('\t') user_list.setdefault(user,{}) user_list[user][movie] = float(rating) return user_list
user_list就是我們建立用來分析的名單
三、算法:
這邊是使用最簡單的歐幾里得距離算法,簡單來說就是將兩人對同一部電影的評價相減平方再開根號,比如A看了蝙蝠俠給了5分,B看了給了5分,但C看了給分,AB距離是0, AC距離是2,可以得知A和B的喜好比較相近,當然現在推薦系統算法很多,這邊只是挑了一個比較簡單的算法,下面是算法的代碼
def calculate(): list = load_data() user_diff = {} for movies in list['7']: for people in list.keys(): user_diff.setdefault(people,{}) for item in list[people].keys(): if item == movies: diff = sqrt(pow(list['7'][movies] - list[people][item],2)) user_diff[people][item] = diff return user_diff
這邊挑了其中一位ID為7的用戶,我的任務是幫他找出他可能會感興趣的電影,所以先計算所有用戶跟7的距離,由於7跟其他用戶都看了不同的電影,所以要先找出共同看過的電影再將所有電影的距離列出來。
接下來再把所有電影的距離取平均值,由於我們想知道的是相似度,相似度與平均值成反比,所以我們將距離倒過來就是相似度,另外為了讓相似度這個數介於0~1,所以用了1/(1+距離)這個算法,以下為代碼
def people_rating(): user_diff = calculate() rating = {} for people in user_diff.keys(): rating.setdefault(people,{}) a = 0 b = 0 for score in user_diff[people].values(): a+=score b+=1 rating[people] = float(1/(1+(a/b))) return rating
可以看到雖然代碼有點丑,不過還是可以跑出個結果,下面就是結果,可以看到跟所有用戶的相似度,可以看到跟ID為12的用戶相似度為0.58, 跟ID為258的用戶相似度為0.333
現在就是要從這里面找出幾個相似度比較高的用戶,也很簡單,利用sort排個序,再選出前五個,下面為代碼
def top_list(): list = people_rating() items = list.items() top = [[v[1],v[0]] for v in items] top.sort(reverse=True) print(top[0:5])
下面為結果
可以看到第一名就是他自己,然后547和384也和他非常契合,不過45名的相似度就下降的非常的快,現在我們來檢視547跟384的菜單吧
我們先來驗證一下547跟384跟我們7號用戶的相似度為什么這么高,下面代碼可以找出547跟7號用戶共同看過的電影跟評分
list = load_data() for k,v in list['7'].items(): for kk,vv in list['547'].items(): if k == kk: print(k,v,kk,vv)
跑出來的結果如下
原來他們只共同看了三部電影,給的評分還一樣,所以距離才為0
再用同樣方法來看384跟7號
可以看到他們也是剛好看了3部一樣的電影,給的分數也是一樣,有趣的事他們三個都看了ID為258的電影,也都給了4分
接下來就是最后一步了,我們要找出547和384看過但7號沒看過的電影,再從里面找出評分高的推薦給7號,下面為代碼
def find_rec(): rec_list = top_list() first = rec_list[1][1] second = rec_list[2][1] all_list = load_data() for k,v in all_list[first].items(): if k not in all_list['7'].keys() and v == 5: print (k) for k,v in all_list[second].items(): if k not in all_list['7'].keys() and v == 5: print (k)
最后跑出來的結果為
以上為547跟384的推薦名單,可以看到316/302/313都是兩人共同推薦,代表這三部片應該是很好看,所以如果要推薦給7號用戶的話可以選擇這三片來推薦。
四、總結
在演練的過程里面我們可以看出這個算法的許多缺點,例如最高分的其實是因為他們共同看過的電影少,分數又剛好相同,很難說明這就是有共同的興趣,然后相似度的落差太大,前兩名都是1,三四名就掉到了0.75,可見三四名應該是與7號共同看過4部電影,但有其中一部的評分差了一分,導致分數驟降,而大部分的用戶都集中在0.3-0,5之間,分數曲線極度不平滑。雖然有這些缺點,但這些算出來的推薦結果還是有代表了一定的意義,至少可以代表是與7號用戶品味相似的人給出的高分電影,而7號尚未給過評分。