'''
1、将所有的用户-电影-评分条目8:2分成训练集和测试集;
2、根据训练集,构建用户-用户的协同过滤矩阵,找到每个用户和其他用户共同点评过的电影的数量:#{1: {3: 4, 4: 2, 5: 6, 2: 5}, 3: {1: 4, 4: 4, 5: 3,
2: 9}, 4: {1: 2, 3: 4, 2: 5, 5: 2}, 5: {1: 6, 2: 15, 3: 3, 4: 2}, 2: {1: 5, 5: 15, 3: 9, 4: 5}} ;
3、根据训练集,统计训练集中每个用户点评的电影数量: {1: 45, 4: 20, 5: 155, 2: 100, 3: 41};
4、根据用户-用户的协同过滤矩阵和训练集中每个用户点评的电影数量,计算用户-用户的相似度矩阵:sim[u][v] = sim[u][v] / np.sqrt(num[u] * num[v]) ;
5、根据训练集,设置K个最高相似度用户,N个待推荐物品;
5、根据测试集,找到测试集的所有用户作为目标用户;
6、根据训练集中的目标用户 和 用户-用户的相似度矩阵,找到每个用户相似度最高的K个其他用户,倒序排列;
7、根据训练集进行推荐,使用80个相似度最高的其他用户的每个评价过的电影,且这些电影未在目标用户的训练集中被评价过,进行相似度累加,存入目标用户-电
影推荐表中;
8、根据训练集训练的结果,对每个目标用户进行 Top-N 推荐,将目标用户-电影推荐表中的前N项作为推荐的电影。
9、测试集的意义:代表了用户根据训练集的Top-N推荐电影,是否都在测试集中对这些电影进行评价了。
9、召回率计算:根据测试集,上一步中给目标用户推荐的Top-N电影是否在测试集的目标用户的电影评分表中,统计出总数,除以 测试集中所有目标用户评分的电
影总数。
10、准确率计算:根据测试集,上一步中给目标用户推荐的Top-N电影是否在测试集的目标用户的电影评分表中,统计出总数,除以 推荐给所有目标用户的总的
top-N电影。
11、计算覆盖率:通过训练集,训练出来的 给所有目标用户推荐的Top-N电影总数(去除了重复项),除以 训练集中 所有目标用户评过分的电影总数(去除了重复
项)。
12、计算流行度:首先遍历训练集,统计每个评过分的电影出现的总次数(有的电影总次数是多次),然后遍历目标用户-电影推荐表,统计给所有的目标用户推荐
的电影总数,统计每个评过分的电影出现的总次数的累加和: 用所有的目标用户推荐的电影总数 除以 每个评过分的电影出现的总次数的累加和,计算流行度。
'''
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
import os
from tqdm import tqdm
import math
import tensorflow as tf
import torch
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 = 0
for user_id, items in val_dict.items():
# 测试集中真实的点击列表
real_set = items
#print(real_set)
# 推荐算法返回的推荐列表
rec_set = rec_dict[user_id]
#print(rec_set)
# 当前用户推荐列表中有多少是实际点击的
for item in rec_set:
if item in real_set:
hit_items += 1 # 求推荐的总数 \ 总的
#print(hit_items,item)
#print('')
all_items += len(real_set)
#print(all_items)
#print('')
print('Calc Recall...')
print(hit_items)
print(all_items)
return round(hit_items / all_items * 100, 2) #all_items 表示测试数据集中的中的物品数量,hit_items 表示当前系统推荐给 用户的 总的 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 = 0
all_items = 0
for user_id, items in val_dict.items():
real_set = items
#print(real_set)
rec_set = rec_dict[user_id]
#print(rec_set)
for item in rec_set:
if item in real_set:
hit_items += 1
#print('*********')
#print(hit_items,item)
#print('')
# 注意这里和 Recall 的区别,Recall 统计测试集里的样本,Precision 统计推荐列表中的样本
all_items += len(rec_set)
#print(all_items)
#print('')
print('Calc Precision...')
print(hit_items)
print(all_items)
return round(hit_items / all_items * 100, 2) #all_items 表示测试数据集中的项目数量,hit_items表示系统推荐给用户的 总共的 N 个物品
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,....}}
函数的主要逻辑是遍历rec_dict中的每个用户,然后将该用户在train_dict中的所有物品添加到all_items集合中。
接着,将该用户在rec_dict中的所有物品添加到hit_items集合中, 再求比值。
"""
hit_items = set()
all_items = set()
for user_id in rec_dict:
for item in train_dict[user_id]:
all_items.add(item)
#print(all_items)
#print('')
for item in rec_dict[user_id]: #这里在统计总的推荐电影数量时,去除了重复项
hit_items.add(item)
#print(hit_items)
#print('')
print(hit_items)
print('Calc Coverage...')
print(len(hit_items))
print(len(all_items))
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,....}}
这段代码是一个计算推荐列表的流行度的函数。
最后,将pop除以推荐物品的总数num,并保留三位小数返回结果。
"""
pos_item = {}
#函数首先遍历训练集train_dict,统计每个物品出现的次数,保存在pos_item字典中。
for user_id in train_dict:
#print(user_id)
for item in train_dict[user_id]:
# print(train_dict[user_id])
if item not in pos_item:
pos_item[item] = 0
pos_item[item] += 1
#print(pos_item)
#第一次循环:{260: 1, 1029: 1, 1028: 1, 1287: 1, 1545: 1, 1035: 1, 783: 1, 527: 1, 2321: 1, 914: 1,
#531: 1, 661: 1, 919: 1, 3105: 1, 1193: 1, 3114: 1, 1961: 1, 1836: 1, 1197: 1,
#48: 1, 2355: 1, 1207: 1, 1097: 1, 3408: 1, 720: 1,
#594: 1, 2398: 1, 608: 1, 2018: 1, 2918: 1, 2791: 1, 2028: 1, 2797: 1, 1907: 1, 2294: 1, 1270: 1, 1022: 1, 2687: 1}
#然后遍历推荐列表rec_dict,对每个推荐的物品,根据其在训练集中出现的次数计算其流行度,并累加到pop变量中。
pop, num = 0, 0
for user_id in rec_dict:
for item in rec_dict[user_id]:
#print(pos_item[item])
#print(math.log(pos_item[item] + 1))
pop += math.log(pos_item[item] + 1) # 由于物品流行度满足长尾分布,取对数可以使得平均值更加稳定
num += 1
print('Calc Popularity...')
print(pop)
print(num)
return 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)
print(ratings)
# 划分训练集和测试集
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)
print(train_user_item[1]) #{1, 2692, 260, 1028, 1545, 1035, 527, 783, 2321, 914, 531, 150, 919, 1566, 3105, 2340, 1961,
#938, 3114, 1962, 1836, 1193, 2355, 1207, 1721, 1097, 2762, 588, 3408, 720, 594, 595, 2398, 1246, 608, 2018, 2918, 745, 2028, 2797, 2294, 1270, 2687}
return train_user_item, val_user_item
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()
items_users[item].add(user_id)
print(items_users)
return items_users
def 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] = 0
num[u] += 1 #{1: 41, 3: 42, 4: 19, 5: 152, 2: 107}
#print(num)
# 统计每个用户与其它用户共同交互的项目个数
if u not in CFMatrix:
CFMatrix[u] = {}
for v in users:
if v != u:
if v not in CFMatrix[u]:
CFMatrix[u][v] = 0
CFMatrix[u][v] += 1
#{1: {3: 4, 4: 2, 5: 6, 2: 5}, 3: {1: 4, 4: 4, 5: 3, 2: 9}, 4: {1: 2, 3: 4, 2: 5, 5: 2}, 5: {1: 6, 2: 15, 3: 3, 4: 2}, 2: {1: 5, 5: 15, 3: 9, 4: 5}}
print(CFMatrix)
print(num)
return CFMatrix, num
def ComputeSimilarity(CFMatrix, num):
sim = CFMatrix
print("构建用户相似度矩阵....")
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])
#print(sim)
#这里得到的是一个相似度值的协同过滤矩阵
return sim
def RecForUser(sim, train_user_item, val_user_item, K, N):
print("给测试用户进行推荐....")
items_rank = {}
''' 这段代码是对用户评分数据进行处理的过程。具体来说,它使用了一个循环来遍历每个用户和他们评分的电影集合。
根据80个相似度最高的用户,将目标用户未评分的电影推荐前N个给目标用户。
然后,它创建了一个空列表来存储每个用户的电影评分。接下来,它根据用户的相似度分数对相似用户进行排序,并选择前80个最高相似度的用户。
然后,它遍历这些相似用户的评分电影,并将它们添加到当前用户的电影评分列表中。
如果某个电影已经在当前用户的列表中,则跳过该电影。最后,根据相似度分数将电影的评分累加到当前用户的电影评分中。
'''
for u, _ in tqdm(val_user_item.items()): #得到用户,每个用户评分的电影集合
items_rank[u] = {} #创建目标用户的空列表
#针对用户的相似度用户按相似度分数排序,取前80个最高的相似度用户,倒序排列
for v, score in sorted(sim[u].items(), key=lambda x:x[1], reverse=True)[:K]:
for item in train_user_item[v]: #取出前80个用户的评分电影
if item in train_user_item[u]: #如果80个用户评分的电影在训练集的目标用户-电影评分表中,不做操作
continue
else:
if item not in items_rank[u]:
items_rank[u][item] = 0 #创建测试用户的目标用户-未评分电影矩阵
items_rank[u][item] += score
print('')
#print(items_rank)
print('')
print("为每个用户进行Top-N推荐....")
items_rank = {k: sorted(v.items(), key=lambda x: x[1], reverse=True)[:N] for k, v in items_rank.items()}
#print(items_rank)
items_rank = {k: set([x[0] for x in v]) for k, v in items_rank.items()} # 将输出整合成合适的格式输出
print('')
print(items_rank)
return items_rank
if __name__ == "__main__":
if torch.cuda.is_available():
device = torch.device("cuda") # 使用GPU
else:
device = torch.device("cpu")
#with tf.device('/GPU:0'):
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) #根据训练集,最相似的前80个用户,进行TOP-10的推荐
rec_eval(rec_items, val_user_item, train_user_item)
"""
推荐模型评估:
Recall: 10.26
Precision 33.99
Coverage 19.41
Popularity 7.228
"""