推荐系统–基于用户的协同过滤算法(UserCF)

基本概念

基本思想:向用户 uuu 推荐时,我们可以先找到和 uuu 相似的用户集合 NuN_uNu​,然后把这些用户喜欢的物品,但 uuu 没有看过的物品推荐给他。

步骤

  • 找到和目标用户相似的用户集合。
  • 找到这个集合中的用户喜欢的,且目标用户还没有听说过的物品,然后推荐给目标用户。

步骤1 - - 找相似用户

为了找到目标用户的相似用户,我们需要计算不同用户之间的相似度。协同过滤算法主要利用行为的相似度计算兴趣的相似度。对于用户 uuu 和 vvv,Nu,NvN_u, N_vNu​,Nv​ 分别表示他们有交互的物品集合。我们通过 Jaccard 公式计算用户之间的相似度:
sim(u,v)=∣Nu∩Nv∣∣Nu∪Nv∣sim_{(u,v)}=\frac{\vert N_u \cap N_v \vert}{\vert N_u \cup N_v \vert}sim(u,v)​=∣Nu​∪Nv​∣∣Nu​∩Nv​∣​
同样,我们也可以利用余弦相似度、皮尔逊系数来计算用户之间的相似度。具体可见 推荐系统–协同过滤(Collaborative Filtering)

具体案例

我们以电影评分预测为例,数据集为 MovieLens-1M,有关数据集详情,可见 推荐系统–MovieLens数据集。

读取数据集

def get_data(data_path):# 重新定义列名col_names = ["user_id", "movie_id", "rating", "timestamp"]# 加载评分数据ratings = pd.read_csv(os.path.join(data_path, "ratings.dat"), sep="::", engine="python", names=col_names)"""ratings 的格式为user_id  movie_id  rating  timestamp0        1      1193       5  9783007601        1       661       3  9783021092        1       914       3  9783019683        1      3408       4  9783002754        1      2355       5  978824291"""# 划分训练集和测试集train_data, val_data, _, _ = train_test_split(ratings, ratings, test_size=0.2)# 将数据按照用户进行分组train_data = train_data.groupby("user_id")["movie_id"].apply(list).reset_index()"""train_data 的格式为 user_id                                           movie_id0        1  [1097, 1566, 3114, 2797, 48, 661, 1197, 2687, ...1        2  [1954, 1217, 1265, 498, 1196, 3256, 434, 2002,...2        3  [3534, 2167, 2871, 648, 2081, 1197, 1580, 1379...3        4  [2947, 1214, 1387, 3418, 1210, 1198, 1201, 346...4        5  [3260, 1610, 2952, 1268, 202, 2384, 3624, 3418...
"""val_data = val_data.groupby("user_id")["movie_id"].apply(list).reset_index()# 将数组构造成字典的形式{user_id:[item1, item2,...], user_id2:{item1,item2,....},...}train_user_item = {}val_user_item = {}for user_id, movie in zip(*(list(train_data["user_id"]), list(train_data["movie_id"]))):train_user_item[user_id] = set(movie)"""转为字典格式之后,就是我们需要的输入格式{1:{1, 1028, 1029, 1287, 1545, 1035, 783, 527, 914, 531, 661, 150, 919, 1566, 3105, 2340, 1961, 3114, 1193, 1962, 1197, 2355, 1721, 1097, 2762, 588, 720, 3408, 595, 1246, 2398, 608, 2018, 2918, 2791, 745, 2028, 2797, 3186, 1907, 2804, 1270, 2294}2:{...},...}"""for user_id, movie in zip(*(list(val_data["user_id"]), list(val_data["movie_id"]))):val_user_item[user_id] = set(movie)return train_user_item, val_user_item

建立倒查表 items–>users

我们根据训练集合中用户和物品的交互信息建立倒查表 items_users。

倒排表的格式为:{item_id1:{user_id1, user_id2,…}, item_id2:{user_id1,…},…} ,它表示每个物品都与哪些用户有交互,建立倒排表的目的是为了更好的统计用户之间的共同交互的物品数量。

def item_user_list(train_user_item):print("建立倒排表....")items_users = {}# 遍历训练集中的每个用户以及他们所交互过的物品for user_id, items in tqdm(train_user_item.items()):for item in items: # 遍历出现过的物品if item not in items_users:items_users[item] = set() # 采用 set() 是可以自动去除重复items_users[item].add(user_id) # 将与这个物品有交互的用户添加到集合中return items_users

建立协同过滤矩阵

根据倒排表 items_users 来统计用户之间共同交互的物品数量。

协同过滤矩阵的形式为: {user_id1:{user_id2:num1, user_id3:num2}, user_id2:{user_id1:num1, user_id3:num2},…},它是一个双层字典,表示不同用户之间共同交互的物品数量。

在计算协同过滤矩阵的同时,还要记录每个用户所交互的物品数量,形式为:num = {user_id1:num1, user_id2:num2,…}

def CollaborativeFilterMatrix(train_user_item, items_users):#  {user_id1:{user_id2:num1, user_id3:num2}, user_id2:{user_id1:num1, user_id3:num2},…}CFMatrix = {}# {user_id1:num1, user_id2:num2,…}num = {}print("构建协同过滤矩阵....")# 遍历所有的物品,统计用户两两之间交互的物品数for item, users in tqdm(items_users.items()):# 首先统计每个用户交互的物品个数for u in users:if u not in num:num[u] = 0num[u] += 1# 统计每个用户与其它用户共同交互的物品个数if u not in CFMatrix:CFMatrix[u] = {}for v in users:if v != u:if v not in CFMatrix[u]:CFMatrix[u][v] = 0CFMatrix[u][v] += 1return CFMatrix, num"""CFMatrix[1] = {6:8, 8:7, ....} 表示用户 1 与用户 6 有8个共同交互物品num[1] = 43 表示用户 1 共与 43 个物品有交互"""

计算相似度矩阵

我们使用余弦相似度来计算用户之间的相似度。用户的协同过滤矩阵可以看作余弦相似度的分子部分,还需要处理分母,分母就是两个用户分别交互物品数量的乘积,每个用户所交互物品的个数在num字典中保存。

def ComputeSimilarity(CFMatrix, num):sim = CFMatrixprint("构建用户相似度矩阵....")for u, other_user in tqdm(CFMatrix.items()):for v, score in other_user.items():sim[u][v] = sim[u][v] / np.sqrt(num[u] * num[v]) # 分子 / 分母return sim"""sim[1] = {6: 0.14333552726125529, 8: 0.07106690545187015,...} 表示用户 1 与用户 6 的相似度为 0.14333552726125529"""

步骤2 - - 进行 Top-N 推荐

根据与用户 uuu 相似的前 KKK 个用户的喜好为 uuu 进行 Top-N 推荐

首先,需要根据用户相似度矩阵得到与当前用户最相似的前 K 个用户,然后对这 K 个用户所交互物品集中但当前用户并未交互过的物品计算相似度分数,最终推荐的候选物品的相似度分数是由多个用户对该物品分数的一个累加和。

def RecForUser(sim, train_user_item, val_user_item, K, N):print("给测试用户进行推荐....")items_rank = {}for u, _ in tqdm(val_user_item.items()):items_rank[u] = {}# sim[u] 的格式为 {user_id: similarity,....} # 按照相似度进行排序,然后取前 K 个for v, score in sorted(sim[u].items(), key=lambda x:x[1], reverse=True)[:K]:# 找出相似用户中有交互的物品,但当前用户并未交互过的物品进行推荐for item in train_user_item[v]:if item in train_user_item[u]:continueelse:if item not in items_rank[u]:items_rank[u][item] = 0# 其实就是相似度的累加和items_rank[u][item] += score"""items_rank 的格式为 {user_id:{item1:score1, item2:score2,...},...}"""print("为每个用户进行Top-N推荐....")# 将每个用户的推荐列表进行重排序,按照物品得分排序并取前 N 个items_rank = {k: sorted(v.items(), key=lambda x: x[1], reverse=True)[:N] for k, v in items_rank.items()}# 将格式转换为和训练集一样的,{user_id:{item1,...},...}items_rank = {k: set([x[0] for x in v]) for k, v in items_rank.items()} # 将输出整合成合适的格式输出return items_rank

评测指标

我们一般使用 Precision@N、Recall@N等指标来衡量推荐系统的性能。有关评测指标的内容可以参考 推荐系统–评估方法和评估指标。

def Precision(rec_dict, val_dict):"""rec_dict: 推荐列表或评分列表,形式为:{user_id:{item1, item2,....}, user_id:{item1, item2,....}}val_dict: 用户实际的点击列表或评分列表(测试集),形式为:{user_id:{item1, item2,....}, user_id:{item1, item2,....}}"""hit_items = 0all_items = 0for user_id, items in val_dict.items():real_set = itemsrec_set = rec_dict[user_id]for item in rec_set:if item in real_set:hit_items += 1# 注意这里和 Recall 的区别,Recall 统计测试集里的样本,Precision 统计推荐列表中的样本all_items += len(rec_set)return round(hit_items / all_items * 100, 2)
def Recall(rec_dict, val_dict):"""rec_dict: 推荐列表或评分列表,形式为:{user_id:{item1, item2,....}, user_id:{item1, item2,....}}val_dict: 用户实际的点击列表或评分列表(测试集),形式为:{user_id:{item1, item2,....}, user_id:{item1, item2,....}}"""# 推荐列表中用户点击的项目数hit_items = 0# 所有的项目all_items = 0for user_id, items in val_dict.items():# 测试集中真实的点击列表real_set = items# 推荐算法返回的推荐列表rec_set = rec_dict[user_id]# 当前用户推荐列表中有多少是实际点击的for item in rec_set:if item in real_set:hit_items += 1all_items += len(real_set)return round(hit_items / all_items * 100, 2)

案例–基于用户的协同过滤(电影评分预测)

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
import os
from tqdm import tqdmdef Recall(rec_dict, val_dict):"""rec_dict: 推荐列表或评分列表,形式为:{user_id:{item1, item2,....}, user_id:{item1, item2,....}}val_dict: 用户实际的点击列表或评分列表(测试集),形式为:{user_id:{item1, item2,....}, user_id:{item1, item2,....}}"""# 推荐列表中用户点击的项目数hit_items = 0# 所有的项目all_items = 0for user_id, items in val_dict.items():# 测试集中真实的点击列表real_set = items# 推荐算法返回的推荐列表rec_set = rec_dict[user_id]# 当前用户推荐列表中有多少是实际点击的for item in rec_set:if item in real_set:hit_items += 1all_items += len(real_set)return round(hit_items / all_items * 100, 2)def Precision(rec_dict, val_dict):"""rec_dict: 推荐列表或评分列表,形式为:{user_id:{item1, item2,....}, user_id:{item1, item2,....}}val_dict: 用户实际的点击列表或评分列表(集),形式为:{user_id:{item1, item2,....}, user_id:{item1, item2,....}}"""hit_items = 0all_items = 0for user_id, items in val_dict.items():real_set = itemsrec_set = rec_dict[user_id]for item in rec_set:if item in real_set:hit_items += 1# 注意这里和 Recall 的区别,Recall 统计测试集里的样本,Precision 统计推荐列表中的样本all_items += len(rec_set)return round(hit_items / all_items * 100, 2)def Coverage(rec_dict, train_dict):"""rec_dict: 推荐列表或评分列表,形式为:{user_id:{item1, item2,....}, user_id:{item1, item2,....}}train_dict: 用户实际的点击列表或评分列表(训练集),形式为:{user_id:{item1, item2,....}, user_id:{item1, item2,....}}"""hit_items = set()all_items = set()for user_id in rec_dict:for item in train_dict[user_id]:all_items.add(item)for item in rec_dict[user_id]:hit_items.add(item)return round(len(hit_items) / len(all_items) * 100, 2)def Popularity(rec_dict, train_dict):"""rec_dict: 推荐列表或评分列表,形式为:{user_id:{item1, item2,....}, user_id:{item1, item2,....}}train_dict: 用户实际的点击列表或评分列表(训练集),形式为:{user_id:{item1, item2,....}, user_id:{item1, item2,....}}"""pos_item = {}for user_id in train_dict:for item in train_dict[user_id]:if item not in pos_item:pos_item[item] = 0pos_item[item] += 1pop, num = 0, 0for user_id in rec_dict:for item in rec_dict[user_id]:pop += math.log(pos_item[item] + 1) # 由于物品流行度满足长尾分布,取对数可以使得平均值更加稳定num += 1return round(pop / num, 3)def rec_eval(val_rec_items, val_user_items, trn_user_items):print('Recall:',Recall(val_rec_items, val_user_items))print('Precision',Precision(val_rec_items, val_user_items))print('Coverage',Coverage(val_rec_items, trn_user_items))print('Popularity',Popularity(val_rec_items, trn_user_items))def get_data(data_path):col_names = ["user_id", "movie_id", "rating", "timestamp"]ratings = pd.read_csv(os.path.join(data_path, "ratings.dat"), sep="::", engine="python", names=col_names)# 划分训练集和测试集train_data, val_data, _, _ = train_test_split(ratings, ratings, test_size=0.2)train_data = train_data.groupby("user_id")["movie_id"].apply(list).reset_index()val_data = val_data.groupby("user_id")["movie_id"].apply(list).reset_index()# 将数组构造成字典的形式{user_id: [item_id1, item_id2,...,item_idn]}train_user_item = {}val_user_item = {}for user_id, movie in zip(*(list(train_data["user_id"]), list(train_data["movie_id"]))):train_user_item[user_id] = set(movie)for user_id, movie in zip(*(list(val_data["user_id"]), list(val_data["movie_id"]))):val_user_item[user_id] = set(movie)return train_user_item, val_user_itemdef item_user_list(train_user_item):print("建立倒排表....")items_users = {}for user_id, items in tqdm(train_user_item.items()):for item in items:if item not in items_users:items_users[item] = set()items_users[item].add(user_id)return items_usersdef CollaborativeFilterMatrix(train_user_item, items_users):CFMatrix = {}num = {}print("构建协同过滤矩阵....")# 遍历所有的项目,统计用户两两之间交互的项目数for item, users in tqdm(items_users.items()):# 首先统计每个用户交互的项目个数for u in users:if u not in num:num[u] = 0num[u] += 1# 统计每个用户与其它用户共同交互的项目个数if u not in CFMatrix:CFMatrix[u] = {}for v in users:if v != u:if v not in CFMatrix[u]:CFMatrix[u][v] = 0CFMatrix[u][v] += 1return CFMatrix, numdef ComputeSimilarity(CFMatrix, num):sim = CFMatrixprint("构建用户相似度矩阵....")for u, other_user in tqdm(CFMatrix.items()):for v, score in other_user.items():sim[u][v] = sim[u][v] / np.sqrt(num[u] * num[v])return simdef RecForUser(sim, train_user_item, val_user_item, K, N):print("给测试用户进行推荐....")items_rank = {}for u, _ in tqdm(val_user_item.items()):items_rank[u] = {}for v, score in sorted(sim[u].items(), key=lambda x:x[1], reverse=True)[:K]:for item in train_user_item[v]:if item in train_user_item[u]:continueelse:if item not in items_rank[u]:items_rank[u][item] = 0items_rank[u][item] += scoreprint("为每个用户进行Top-N推荐....")items_rank = {k: sorted(v.items(), key=lambda x: x[1], reverse=True)[:N] for k, v in items_rank.items()}items_rank = {k: set([x[0] for x in v]) for k, v in items_rank.items()} # 将输出整合成合适的格式输出return items_rank if __name__ == "__main__":root_path = './data/ml-1m/'train_user_item, val_user_item = get_data(root_path)items_users = item_user_list(train_user_item)CFMatrix, num = CollaborativeFilterMatrix(train_user_item, items_users)sim = ComputeSimilarity(CFMatrix, num)rec_items = RecForUser(sim, train_user_item, val_user_item, K=80,N=10)rec_eval(rec_items, val_user_item, train_user_item)"""推荐模型评估:Recall: 10.26Precision 33.99Coverage 19.41Popularity 7.228"""

代码参考了 https://github.com/datawhalechina/fun-rec?spm=5176.21852664.0.0.7c1147a9gwgeMq

推荐系统--基于用户的协同过滤算法(UserCF)相关推荐

  1. 推荐系统实践(一)----基于用户的协同过滤算法(UserCF)

      随着信息技术和互联网的发展,人们逐渐从信息匮乏的时代走入了信息过载的时代.在这个时代,无论是信息消费者还是信息生产者都遇到了很大的挑战:如何从大量信息中找到自己感兴趣的信息是一件非常困难的事情,这 ...

  2. 推荐系统--基于用户的协同过滤算法

    1.         概述 和搜索引擎一样,推荐系统是为了帮助人们更快速的获得对自己有用的信息. 和搜索引擎不同,推荐系统是人们被动的获取,由系统根据用户行为或其他的信息推荐给用户的,儿搜索引擎是用户 ...

  3. 基于用户的协同过滤算法(UserCF)

    用户相似度计算 协同过滤算法主要利用行为的相似度计算兴趣的相似度.给定用户 和用户 ,令 表示用户 感兴趣的物品集合,令 为用户 感兴趣的物品集合.那么我们可以通过  公式或者余弦公式来计算用户 , ...

  4. UserCF,基于用户的协同过滤算法

    转载自   UserCF,基于用户的协同过滤算法 UserCF:User  Collaboration   Filter,基于用户的协同过滤 算法核心思想:在一个在线推荐系统中,当用户A需要个性化推荐 ...

  5. 【推荐系统】{1} —— 基于用户的协同过滤算法

    协同过滤(英语:Collaborative Filtering,简称CF),简单来说是利用某兴趣相投.拥有共同经验之群体的喜好来推荐用户感兴趣的信息,个人透过合作的机制给予信息相当程度的回应(如评分) ...

  6. 推荐系统实践----基于用户的协同过滤算法(python代码实现书中案例)

    本文参考项亮的<推荐系统实践>中基于用户的协同过滤算法内容.因其中代码实现部分只有片段,又因本人初学,对python还不是很精通,难免头大.故自己实现了其中的代码,将整个过程走了一遍. 1 ...

  7. [推荐算法]UserCF,基于用户的协同过滤算法

    UserCF:UserCollaborationFilter,基于用户的协同过滤 算法核心思想:在一个在线推荐系统中,当用户A需要个性化推荐时,可以先找到和他有相似兴趣的其它用户,然后把那些用户喜欢的 ...

  8. 【推荐系统】基于用户的协同过滤算法

    基于用户的协同过滤算法 基础算法 在一个在线个性化推荐系统中,当一个用户A需要个性化推荐时,可以先找到和他有相似兴趣的其他用户,然后把那些用户喜欢的.而用户A没有听说过的物品推荐给A.这种方法称为基于 ...

  9. 如何使用Java+SSM(Spring+SpringMVC+Mybatis)开发个性化新闻推荐系统 在线新闻推荐系统 基于用户项目协同过滤、内容、聚类、关联规则推荐算法实现WebNewsRSMEx

    如何使用Java+SSM(Spring+SpringMVC+Mybatis)开发个性化新闻推荐系统 在线新闻推荐系统 基于用户项目协同过滤.内容.聚类.关联规则推荐算法实现WebNewsRSMEx 一 ...

  10. mysql数据推荐算法_Java+Mysql实现简单在线电影、音乐、图书推荐系统 基于用户的协同过滤推荐算法实现 源代码下载...

    # Java+Mysql实现简单在线电影.音乐.图书等推荐系统(基于用户的协同过滤推荐算法) 一.项目简介 1.开发工具和实现技术 MyEclipse10,jdk1.7,mysql5.5,tomca ...

最新文章

  1. fanuc机器人与plc的通讯_S7-1200PLC与FANUC机器人Profinet通讯方法
  2. 游戏服务器哪个系统困难些,游戏服务器哪个系统困难些
  3. 计算机图形学基础考试题,计算机图形学基础复习题
  4. (转)虚函数和纯虚函数区别
  5. 单细胞一站式分析网站CeDR Atlas使用指南
  6. Oracle执行计划显示
  7. 子乐云音乐播放器源码V1.2php源码
  8. SAP License:PS中的成本控制
  9. java 对话框打开与保存
  10. iOS-UITextField中给placeholder动态设置颜色的四种方法
  11. ScrollView中Spinner问题
  12. 在eclipse中引入mybatis和spring的约束文件
  13. Zabbix离线安装部署
  14. SYS/BIOS与SRIO应用实例
  15. Java案例:功夫熊猫
  16. 什么是电子商务——百科
  17. 心知天气html,esp8266初级入门实用教程一之访问心知天气读取实时天气数据
  18. linux下解压iso文件
  19. iOS-Runtime之SEL、IMP、Method
  20. 有多厉害?首互联网卫星发射 飞机高铁WiFi就靠它了

热门文章

  1. IDEA 2021首个大版本发布,Java开发者感动哭了(附新亮点演示)
  2. 【2021】15天通过阿里云云计算架构师认证考试(ACE)- 经验分享
  3. android拖拽 字体变形,字体变形的几种方法与技巧
  4. 关于华为2019全联接大会,精华内容都在这里!
  5. 在线html编辑器 菜鸟,菜鸟教程
  6. html图片白色背景怎么去掉,怎么把PPT图片的白色背景去掉 PPT去除图片背景颜色技巧...
  7. ERP的主要功能模块简介
  8. Linux三剑客之awk精讲
  9. 电梯轿厢场景下的电动车数据集
  10. 汇聚内容页关键词优化的小窍门