基于邻域的协同过滤

此篇使用朴素的代码介绍基于邻域的协同过滤算法机制。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
【近邻推荐】人以群分,你是什么人就看到什么世界
架构师特刊:推荐系统(理论篇)
美团推荐算法实践

相关文章
相关标签/搜索