基於鄰域的協同過濾

此篇使用樸素的代碼介紹基於鄰域的協同過濾算法機制。html

爲了使說明過程更清楚,這裏使用自已編造的數據。每一行記錄着某用戶對某本書的評分,評分區間爲1至5。python

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
複製代碼

生成用戶物品關係矩陣

如今根據加載進來的數據生成推薦系統中相當重要的用戶物品關係矩陣。能夠理解爲數據庫中的一張表,一本書爲一列,一行對應一個用戶,當用戶看過某本書並進行評分後,在對應的位置填入分數,其餘位置均置爲0,表示還沒有看過。git

須要注意的是,矩陣取值要用下標表示,好比matrix[2][2]對應的是第三個用戶對第三本書的評分狀況,因此這裏要作一個user_id, book_id到該矩陣座標的對應關係,使用pandas的Series表示。github

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, book_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 ('用戶關係矩陣長這樣:')
print ('-----')
print (user_book_matrix)
複製代碼
用戶關係矩陣長這樣:
-----
[[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]]
複製代碼

計算類似度矩陣

所謂類似度,咱們這裏使用餘弦類似度,其餘的還有皮爾遜相關度、歐式距離、傑卡德類似度等,箇中差異暫不細表。 計算公式爲:算法

如今已經拿到了user_book_matrix,每一個用戶、每一個物品均可以對應一個向量,好比user_book_matrix[2]爲表明user_003的向量等於[4, 0, 5, 3, 4, 0],而user_book_matrix[:,2]則表明了book_003[0, 4, 5, 0, 0, 2]數據庫

這樣基於用戶和基於物品便分別能夠計算出用戶類似度矩陣與物品類似度矩陣。架構

以用戶類似度矩陣爲例,計算後會獲得一個形狀爲(user_count, user_count)的矩陣,好比user_similarity_matrix[2][3]的值爲0.5,則表示user_002user_003的餘弦類似度爲0.5。 此矩陣爲對稱矩陣,相應地,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.  ]]
複製代碼

推薦

有了類似度矩陣,能夠開始進行推薦。
首先能夠爲用戶推薦與其品味相同的用戶列表,這在知乎、豆瓣、網易雲音樂這樣具備社交屬性的產品中頗有意義。url

作法很簡單,要爲用戶A推薦K位品味類似的用戶(此處K取3),則將user_similarity_matrix中關於A的那一行的值排序從最高往下取出K位便可。spa

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
複製代碼

同時在物品維度,相似的推薦也是頗有用的,好比QQ音樂給用戶正在聽的音樂推薦類似的歌曲,還有亞馬遜中對用戶剛購買的物品推薦類似的物品。
代碼與推薦類似用戶相同,無需作其餘處理。

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個用戶,而後找出這K個用戶評過度的書籍的集合,再去掉該用戶已經評過度的部分。 在剩下的書籍中,根據下面的公式,計算出該用戶爲某書籍的預計評分,將評分從高到低排序輸出便可。

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 # 類似用戶未看過該書則不計入統計.
            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
複製代碼

以上是利用用戶類似度矩陣來爲用戶推薦物品,一樣也能夠反過來爲利用物品類似度矩陣來爲用戶推薦書籍。
作法是,找出該用戶讀過的全部書,爲每本書找出兩本與該書最類似的書籍,將找出來的全部書去掉用戶已讀過的,而後爲書籍預測被用戶評分的分值。

這裏的確有些繞,容易與上文纏在一塊兒搞亂掉,遂舉例以下:
好比user_001讀過書book_001, book_002book_005,找到的書籍集合再去掉用戶已讀過的結果爲{'book_003', 'book_006'},要爲book_003預測分數,須要注意到它同時被book_001book_005找出,要根據它們、用戶對book_001book_005的評分以及類似度套用至上文公式,來得出對book_003的分數爲:(4*0.79+5*0.71)/(0.79+0.71)=4.47

則基於物品爲用戶推薦物品的函數爲:

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
複製代碼

總結

以上是基於領域的協同過濾的運做機制介紹,只用了兩個簡單的數學公式,加上各類代碼處理,即可覺得用戶作出一些推薦。

就給用戶推薦物品而言,基於用戶與基於物品各有特色。
基於用戶給出的推薦結果,更依賴於當前用戶相近的用戶羣體的社會化行爲,考慮到計算代價,它適合於用戶數較少的狀況,同時,對於新加入的物品的冷啓動問題比較友好,然而相對於物品的類似性,根據用戶之間的類似性作出的推薦的解釋性是比較弱的,實時性方面,用戶新的行爲不必定會致使結果的變化。 基於物品給出的推薦結果,更側重於用戶自身的個體行爲,適用於物品數較少的狀況,對長尾物品的發掘好於基於用戶,同時,新加入的用戶能夠很快獲得推薦,而且物品之間的關聯性更易懂,是更易於解釋的,並且用戶新的行爲必定能致使結果的變化。

顯然,基於物品整體上要優於基於用戶,歷史上,也的確是基於用戶先被髮明出來,以後Amazon發明了基於物品的算法,如今基於用戶的產品已經比較少了。

參考

Intro to Recommender Systems: Collaborative Filtering
【近鄰推薦】人以羣分,你是什麼人就看到什麼世界
架構師特刊:推薦系統(理論篇)
美團推薦算法實踐

相關文章
相關標籤/搜索