基于鄰域的協(xié)同過濾

此篇使用樸素的代碼介紹基于鄰域的協(xié)同過濾算法機(jī)制插龄。

為了使說明過程更清楚刽肠,這里使用自已編造的數(shù)據(jù)唇兑。每一行記錄著某用戶對(duì)某本書的評(píng)分坎弯,評(píng)分區(qū)間為1至5纺涤。

import pandas as pd

data_url = 'https://gist.githubusercontent.com/guerbai/3f4964350678c84d359e3536a08f6d3a/raw/f62f26d9ac24d434b1a0be3b5aec57c8a08e7741/user_book_ratings.txt'
df = pd.read_csv(data_url, 
            sep = ',',
            header = None,                   
            names = ['user_id', 'book_id', 'rating'])
print (df.head())
print ('-----')
user_count = df['user_id'].unique().shape[0]
book_count = df['book_id'].unique().shape[0]
print ('user_count: ', user_count)
print ('book_count: ', book_count)
    user_id   book_id  rating
0  user_001  book_001       4
1  user_001  book_002       3
2  user_001  book_005       5
3  user_002  book_001       5
4  user_002  book_003       4
-----
user_count:  6
book_count:  6

生成用戶物品關(guān)系矩陣

現(xiàn)在根據(jù)加載進(jìn)來的數(shù)據(jù)生成推薦系統(tǒng)中至關(guān)重要的用戶物品關(guān)系矩陣译暂。可以理解為數(shù)據(jù)庫中的一張表撩炊,一本書為一列外永,一行對(duì)應(yīng)一個(gè)用戶,當(dāng)用戶看過某本書并進(jìn)行評(píng)分后拧咳,在對(duì)應(yīng)的位置填入分?jǐn)?shù)伯顶,其他位置均置為0,表示尚未看過骆膝。

需要注意的是祭衩,矩陣取值要用下標(biāo)表示,比如matrix[2][2]對(duì)應(yīng)的是第三個(gè)用戶對(duì)第三本書的評(píng)分情況阅签,所以這里要做一個(gè)user_id, book_id到該矩陣坐標(biāo)的對(duì)應(yīng)關(guān)系掐暮,使用pandas的Series表示。

user_id_index_series = pd.Series(range(user_count), index=['user_001', 'user_002', 'user_003', 'user_004', 'user_005', 'user_006'])
book_id_index_series = pd.Series(range(book_count), index=['book_001', 'book_002', 'book_003', 'book_004', 'book_005', 'book_006'])
import numpy as np

def construct_user_item_matrix(df):
    user_item_matrix = np.zeros((user_count, user_count), dtype=np.int8)
    for row in df.itertuples():
        user_id = row[1]
        book_id = row[2]
        rating = row[3]
        user_item_matrix[user_id_index_series[user_id], book_id_index_series[book_id]] = rating
    return user_item_matrix

user_book_matrix = construct_user_item_matrix(df)
print ('用戶關(guān)系矩陣長這樣:')
print ('-----')
print (user_book_matrix)
用戶關(guān)系矩陣長這樣:
-----
[[4 3 0 0 5 0]
 [5 0 4 0 4 0]
 [4 0 5 3 4 0]
 [0 3 0 0 0 5]
 [0 4 0 0 0 4]
 [0 0 2 4 0 5]]

計(jì)算相似度矩陣

所謂相似度政钟,我們這里使用余弦相似度路克,其他的還有皮爾遜相關(guān)度、歐式距離养交、杰卡德相似度等精算,個(gè)中差別暫不細(xì)表。
計(jì)算公式為:

現(xiàn)在已經(jīng)拿到了user_book_matrix碎连,每個(gè)用戶灰羽、每個(gè)物品都可以對(duì)應(yīng)一個(gè)向量,比如user_book_matrix[2]為代表user_003的向量等于[4, 0, 5, 3, 4, 0]鱼辙,而user_book_matrix[:,2]則代表了book_003[0廉嚼, 4, 5座每, 0前鹅, 0, 2]峭梳。

這樣基于用戶和基于物品便分別可以計(jì)算出用戶相似度矩陣與物品相似度矩陣舰绘。

以用戶相似度矩陣為例,計(jì)算后會(huì)得到一個(gè)形狀為(user_count, user_count)的矩陣葱椭,比如user_similarity_matrix[2][3]的值為0.5捂寿,則表示user_002user_003的余弦相似度為0.5。
此矩陣為對(duì)稱矩陣孵运,相應(yīng)地秦陋,user_similarity_matrix[3][2]亦為0.5,而用戶與自己自然是最相似的治笨,遂有user_similarity_matrix[n][n]總是等于1驳概。

def cosine_similarity(vec1, vec2):
    return round(vec1.dot(vec2)/(np.linalg.norm(vec1)*np.linalg.norm(vec2)), 2)


def construct_similarity_matrix(user_item_matrix, dim='user'):
    if dim == 'user':
        similarity_matrix = np.zeros((user_count, user_count))
        count = user_count
    else:
        similarity_matrix = np.zeros((book_count, book_count))
        count = book_count
    get_vector = lambda i: user_item_matrix[i] if dim == 'user' else user_item_matrix[:,i]
    for i in range(user_count):
        i_vector = get_vector(i)
        similarity_matrix[i][i] = cosine_similarity(i_vector, i_vector)
        for j in range(i, count):
            j_vector = get_vector(j)
            similarity = cosine_similarity(i_vector, j_vector)
            similarity_matrix[i][j] = similarity
            similarity_matrix[j][i] = similarity
    return similarity_matrix

user_similarity_matrix = construct_similarity_matrix(user_book_matrix)
book_similarity_matrix = construct_similarity_matrix(user_book_matrix, dim='book')
print ('user_similarity_matrix:')
print (user_similarity_matrix)
print ('book_similarity_matrix:')
print (book_similarity_matrix)
user_similarity_matrix:
[[1.   0.75 0.63 0.22 0.3  0.  ]
 [0.75 1.   0.91 0.   0.   0.16]
 [0.63 0.91 1.   0.   0.   0.4 ]
 [0.22 0.   0.   1.   0.97 0.64]
 [0.3  0.   0.   0.97 1.   0.53]
 [0.   0.16 0.4  0.64 0.53 1.  ]]
book_similarity_matrix:
[[1.   0.27 0.79 0.32 0.98 0.  ]
 [0.27 1.   0.   0.   0.34 0.65]
 [0.79 0.   1.   0.69 0.71 0.18]
 [0.32 0.   0.69 1.   0.32 0.49]
 [0.98 0.34 0.71 0.32 1.   0.  ]
 [0.   0.65 0.18 0.49 0.   1.  ]]

推薦

有了相似度矩陣赤嚼,可以開始進(jìn)行推薦。
首先可以為用戶推薦與其品味相同的用戶列表顺又,這在知乎更卒、豆瓣、網(wǎng)易云音樂這樣具有社交屬性的產(chǎn)品中很有意義稚照。

做法很簡單蹂空,要為用戶A推薦K位品味相似的用戶(此處K取3),則將user_similarity_matrix中關(guān)于A的那一行的值排序從最高往下取出K位即可果录。

def recommend_similar_users(user_id, n=3):
    user_index = user_id_index_series[user_id]
    similar_users_index = pd.Series(user_similarity_matrix[user_index]).drop(index=user_index).sort_values(ascending=False).index[:n]
    return np.array(similar_users_index)

print ('recommend user_indexes %s to user_001' % recommend_similar_users('user_001'))
recommend user_indexes [1 2 4] to user_001

同時(shí)在物品維度上枕,類似的推薦也是很有用的,比如QQ音樂給用戶正在聽的音樂推薦相似的歌曲弱恒,還有亞馬遜中對(duì)用戶剛購買的物品推薦相似的物品辨萍。
代碼與推薦相似用戶相同,無需做其他處理斤彼。

def recommend_similar_items(item_id, n=3):
    item_index = book_id_index_series[item_id]
    similar_item_index = pd.Series(book_similarity_matrix[item_index]).drop(index=item_index).sort_values(ascending=False).index[:n]
    return np.array(similar_item_index)
    
print ('recommend item_indexes %s to book_001' % recommend_similar_items('book_001'))
recommend item_indexes [4 2 3] to book_001

接下來是為用戶推薦書籍分瘦,首先選出與該用戶最相似的K個(gè)用戶,然后找出這K個(gè)用戶評(píng)過分的書籍的集合琉苇,再去掉該用戶已經(jīng)評(píng)過分的部分。
在剩下的書籍中悦施,根據(jù)下面的公式并扇,計(jì)算出該用戶為某書籍的預(yù)計(jì)評(píng)分,將評(píng)分從高到低排序輸出即可抡诞。

def recommend_item_to_user(user_id):
    user_index = user_id_index_series[user_id]
    similar_users = recommend_similar_users(user_id, 2)
    recommend_set = set()
    for similar_user in similar_users:
        recommend_set = recommend_set.union(np.nonzero(user_book_matrix[similar_user])[0])
    recommend_set = recommend_set.difference(np.nonzero(user_book_matrix[user_index])[0])
    predict = pd.Series([0.0]*len(recommend_set), index=list(recommend_set))
    for book_index in recommend_set:
        fenzi = 0
        fenmu = 0
        for j in similar_users:
            if user_book_matrix[j][book_index] == 0:
                continue # 相似用戶未看過該書則不計(jì)入統(tǒng)計(jì).
            fenzi += user_book_matrix[j][book_index] * user_similarity_matrix[j][user_index]
            fenmu += user_similarity_matrix[j][user_index]
        if fenmu == 0:
            continue
        predict[book_index] = round(fenzi/fenmu, 2)
    return predict.sort_values(ascending=False)
            

recommend_item_to_user('user_005')
3    4.0
2    2.0
dtype: float64

以上是利用用戶相似度矩陣來為用戶推薦物品穷蛹,同樣也可以反過來為利用物品相似度矩陣來為用戶推薦書籍。
做法是昼汗,找出該用戶讀過的所有書肴熏,為每本書找出兩本與該書最相似的書籍,將找出來的所有書去掉用戶已讀過的顷窒,然后為書籍預(yù)測被用戶評(píng)分的分值蛙吏。

這里的確有些繞,容易與上文纏在一起搞亂掉鞋吉,遂舉例如下:
比如user_001讀過書book_001, book_002鸦做,book_005,找到的書籍集合再去掉用戶已讀過的結(jié)果為{'book_003', 'book_006'}谓着,要為book_003預(yù)測分?jǐn)?shù)泼诱,需要注意到它同時(shí)被book_001book_005找出,要根據(jù)它們赊锚、用戶對(duì)book_001book_005的評(píng)分以及相似度套用至上文公式治筒,來得出對(duì)book_003的分?jǐn)?shù)為:(4*0.79+5*0.71)/(0.79+0.71)=4.47屉栓。

則基于物品為用戶推薦物品的函數(shù)為:

def recommend_item_to_user_ib(user_id):
    user_index = user_id_index_series[user_id]
    user_read_books = np.nonzero(user_book_matrix[user_index])[0]
    book_set = set()
    book_relation = dict()
    for book in user_read_books:
        relative_books = recommend_similar_items(book, 2)
        book_set = book_set.union(relative_books)
        book_relation[book] = relative_books
    book_set = book_set.difference(user_read_books)
    predict = pd.Series([0.0]*len(book_set), index=list(book_set))
    for book in book_set:
        fenzi = 0
        fenmu = 0
        for similar_book, relative_books in book_relation.items():
            if book in relative_books:
                fenzi += book_similarity_matrix[book][similar_book] * user_book_matrix[user_index][similar_book]
                fenmu += book_similarity_matrix[book][similar_book]
        predict[book] = round(fenzi/fenmu, 2)
    return predict.sort_values(ascending=False)

recommend_item_to_user_ib('user_001')
2    4.47
5    3.00
dtype: float64

總結(jié)

以上是基于領(lǐng)域的協(xié)同過濾的運(yùn)作機(jī)制介紹,只用了兩個(gè)簡單的數(shù)學(xué)公式耸袜,加上各種代碼處理友多,便可以為用戶做出一些推薦。

就給用戶推薦物品而言句灌,基于用戶與基于物品各有特點(diǎn)夷陋。
基于用戶給出的推薦結(jié)果,更依賴于當(dāng)前用戶相近的用戶群體的社會(huì)化行為胰锌,考慮到計(jì)算代價(jià)骗绕,它適合于用戶數(shù)較少的情況,同時(shí)资昧,對(duì)于新加入的物品的冷啟動(dòng)問題比較友好酬土,然而相對(duì)于物品的相似性,根據(jù)用戶之間的相似性做出的推薦的解釋性是比較弱的格带,實(shí)時(shí)性方面撤缴,用戶新的行為不一定會(huì)導(dǎo)致結(jié)果的變化。
基于物品給出的推薦結(jié)果叽唱,更側(cè)重于用戶自身的個(gè)體行為屈呕,適用于物品數(shù)較少的情況,對(duì)長尾物品的發(fā)掘好于基于用戶棺亭,同時(shí)虎眨,新加入的用戶可以很快得到推薦,并且物品之間的關(guān)聯(lián)性更易懂镶摘,是更易于解釋的嗽桩,而且用戶新的行為一定能導(dǎo)致結(jié)果的變化。

顯然凄敢,基于物品總體上要優(yōu)于基于用戶碌冶,歷史上,也的確是基于用戶先被發(fā)明出來涝缝,之后Amazon發(fā)明了基于物品的算法扑庞,現(xiàn)在基于用戶的產(chǎn)品已經(jīng)比較少了。

參考

Intro to Recommender Systems: Collaborative Filtering
【近鄰?fù)扑]】人以群分俊卤,你是什么人就看到什么世界
架構(gòu)師特刊:推薦系統(tǒng)(理論篇)
美團(tuán)推薦算法實(shí)踐

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末嫩挤,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子消恍,更是在濱河造成了極大的恐慌岂昭,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異约啊,居然都是意外死亡邑遏,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門恰矩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來记盒,“玉大人,你說我怎么就攤上這事外傅〖退保” “怎么了?”我有些...
    開封第一講書人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵萎胰,是天一觀的道長碾盟。 經(jīng)常有香客問我,道長技竟,這世上最難降的妖魔是什么冰肴? 我笑而不...
    開封第一講書人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮榔组,結(jié)果婚禮上熙尉,老公的妹妹穿的比我還像新娘。我一直安慰自己搓扯,他們只是感情好检痰,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著锨推,像睡著了一般攀细。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上爱态,一...
    開封第一講書人閱讀 50,084評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音境钟,去河邊找鬼锦担。 笑死,一個(gè)胖子當(dāng)著我的面吹牛慨削,可吹牛的內(nèi)容都是我干的洞渔。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼缚态,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼磁椒!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起玫芦,我...
    開封第一講書人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤浆熔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后桥帆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體医增,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡慎皱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了叶骨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片茫多。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖忽刽,靈堂內(nèi)的尸體忽然破棺而出天揖,到底是詐尸還是另有隱情,我是刑警寧澤跪帝,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布今膊,位于F島的核電站,受9級(jí)特大地震影響歉甚,放射性物質(zhì)發(fā)生泄漏万细。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一纸泄、第九天 我趴在偏房一處隱蔽的房頂上張望赖钞。 院中可真熱鬧,春花似錦聘裁、人聲如沸雪营。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽献起。三九已至,卻和暖如春镣陕,著一層夾襖步出監(jiān)牢的瞬間谴餐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來泰國打工呆抑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留岂嗓,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓鹊碍,卻偏偏與公主長得像厌殉,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子侈咕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351