1.数据来源:http://files.grouplens.org/datasets/movielens/
2.开发工具:Pycharm
3.开发环境:Python3.7
一、基于物品的协同过滤算法
基于物品的协同过滤算法主要分两步,首先是计算物品之间的相似度,其次根据物品的相似度和用户的历史行为给用户生成推荐列表。在计算物品之间相似度时可以采用杰卡德相似系数或者余弦相似度或皮尔逊相关系数,在此处我使用的是余弦相似度,它的计算公式如下所示:
这里分母|N(i)|是喜欢物品i的用户数,|N(j)|是表示喜欢物品j的用户数,分子|N(i)∩N(j)|是同时喜欢物品i和物品j的用户数。从公式的定义中可以看出,两个物品产生相似度是因为它们共同被很多用户喜欢,我们就可以根据用户的历史兴趣来计算相似度。
def ItemSimilarityBest(self):
print("开始计算物品之间的相似度")
# 每次运行都要计算物品之间的相似度很麻烦,所以为了节省时间,就直接建立一个物品相似度文件
if os.path.exists("item_sim.json"):
print("物品相似度从文件加载 ...")
itemSim = json.load(open("data/item_sim.json", "r"))
else:
# 相似度矩阵
itemSim = dict()
# 行为用户数
item_user_count = dict()
# 共现矩阵
count = dict()
# 此循环得到物品行为用户数item_user_count和两两物品共现矩阵count
# user表示用户,item表示物品及评分,self.trainData.items()的数据格式是这样的{'用户1': {'电影1': '4', '电影3': '5'.......},'用户2': {'电影1': '3','电影2': '4'......}, .....}
for user, item in self.trainData.items():
# 遍历该用户看过的电影及评分
for i in item.keys():
# 初始化行为用户数
item_user_count.setdefault(i, 0)
# 如果该用户看过i电影并且对i电影有评分
if self.trainData[user][i] > 0.0:
# 则行为用户数+1
item_user_count[i] += 1
# 再次遍历该用户的看过的电影及评分
for j in item.keys():
# 初始化共现矩阵
count.setdefault(i, {}).setdefault(j, 0)
# 如果i电影和j电影是一部电影则继续
if i == j:
continue
# 否则i电影和j电影的共现次数+1
count[i][j] += 1
# 共现矩阵 -> 相似度矩阵
# 遍历共现矩阵,i表示电影i,related_items表示与电影i相关的电影及共现次数
for i, related_items in count.items():
# 初始化相似度矩阵
itemSim.setdefault(i, dict())
for j, cuv in related_items.items(): # 遍历i物品的每一项关联物品(j物品,cuv共现次数)
itemSim[i].setdefault(j, 0)
# 计算物品i和物品j的相似度
itemSim[i][j] = cuv / math.sqrt(item_user_count[i] * item_user_count[j])
# 存入文件中
json.dump(itemSim, open('data/item_sim.json', 'w'))
return itemSim
在得到物品相似度以后,我们就可以根据物品相似度的大小来对用户进行物品的推荐:
def recommend(self, user, N):
# K表示相似系数
K = 10
# 推荐的电影集合
result = dict()
# 获取该用户的所看过的所有电影及评分
u_items = self.trainData.get(user, {})
# 遍历u_items,i表示电影,pi表示对电影的评分
for i, pi in u_items.items():
# 对该用户下的i电影在相似度矩阵中和与其他电影的相似度的值排序取前K个值
for j, wj in sorted(self.items_sim[i].items(), key=itemgetter(1), reverse=True)[0:K]:
# 如果该电影被用户看过,则不进行推荐
if j in u_items:
continue
result.setdefault(j, 0)
# 推荐的电影j及用户对它的兴趣值,兴趣值计算方法= pi评分 * wj相似度
result[j] += pi * wj
# 选取前N个兴趣值最高的电影给用户进行推荐
return dict(sorted(result.items(), key=lambda x: x[1], reverse=True)[0:N])
我们在得到推荐的结果后还需要检测结果的准确率,覆盖率,召回率和流行度,这里就不单独列出来了,直接全部代码奉上。
以下是全部代码:
import random
import math
import os
import json
import time
from operator import itemgetter
class ItemCF:
def __init__(self, datafile):
self.datafile = datafile
self.data = self.loadData()
self.trainData, self.testData = self.splitData(3, 47)
self.items_sim = self.ItemSimilarityBest()
def loadData(self):
print("加载数据...")
data = []
for line in open(self.datafile):
userid, itemid, record, time1 = line.split("::")
timestamp = int(time1)
time_local = time.localtime(timestamp)
dt = time.strftime("%Y-%m-%d %H:%M:%S", time_local)
data.append((userid, itemid, int(record), dt))
data1 = sorted(data, key=lambda x: x[3], reverse=False)
return data1
def splitData(self, k, seed, M=9):
print("训练数据集与测试数据集切分...")
train, test = {}, {}
random.seed(seed)
for user, item, record, time in self.data:
if random.randint(0, M) == k:
test.setdefault(user, {})
test[user][item] = record
else:
train.setdefault(user, {})
train[user][item] = record
return train, test
# 训练集和测试集的划分 https://blog.csdn.net/qingsi11/article/details/107322751
def ItemSimilarityBest(self):
print("开始计算物品之间的相似度")
# 每次运行都要计算物品之间的相似度很麻烦,所以为了节省时间,就直接建立一个物品相似度文件
if os.path.exists("item_sim.json"):
print("物品相似度从文件加载 ...")
itemSim = json.load(open("data/item_sim.json", "r"))
else:
# 相似度矩阵
itemSim = dict()
# 行为用户数
item_user_count = dict()
# 共现矩阵
count = dict()
# 此循环得到物品行为用户数item_user_count和两两物品共现矩阵count
# user表示用户,item表示物品及评分,self.trainData.items()的数据格式是这样的{'用户1': {'电影1': '4', '电影3': '5'.......},'用户2': {'电影1': '3','电影2': '4'......}, .....}
for user, item in self.trainData.items():
# 遍历该用户看过的电影及评分
for i in item.keys():
# 初始化行为用户数
item_user_count.setdefault(i, 0)
# 如果该用户看过i电影并且对i电影有评分
if self.trainData[user][i] > 0.0:
# 则行为用户数+1
item_user_count[i] += 1
# 再次遍历该用户的看过的电影及评分
for j in item.keys():
# 初始化共现矩阵
count.setdefault(i, {}).setdefault(j, 0)
# 如果i电影和j电影是一部电影则继续
if i == j:
continue
# 否则i电影和j电影的共现次数+1
count[i][j] += 1
# 共现矩阵 -> 相似度矩阵
# 遍历共现矩阵,i表示电影i,related_items表示与电影i相关的电影及共现次数
for i, related_items in count.items():
# 初始化相似度矩阵
itemSim.setdefault(i, dict())
for j, cuv in related_items.items(): # 遍历i物品的每一项关联物品(j物品,cuv共现次数)
itemSim[i].setdefault(j, 0)
# 计算物品i和物品j的相似度
itemSim[i][j] = cuv / math.sqrt(item_user_count[i] * item_user_count[j])
# 存入文件中
json.dump(itemSim, open('data/item_sim.json', 'w'))
return itemSim
def recommend(self, user, N):
# K表示相似系数
K = 10
# 推荐的电影集合
result = dict()
# 获取该用户的所看过的所有电影及评分
u_items = self.trainData.get(user, {})
# 遍历u_items,i表示电影,pi表示对电影的评分
for i, pi in u_items.items():
# 对该用户下的i电影在相似度矩阵中和与其他电影的相似度的值排序取前K个值
for j, wj in sorted(self.items_sim[i].items(), key=itemgetter(1), reverse=True)[0:K]:
# 如果该电影被用户看过,则不进行推荐
if j in u_items:
continue
result.setdefault(j, 0)
# 推荐的电影j及用户对它的兴趣值,兴趣值计算方法= pi评分 * wj相似度
result[j] += pi * wj
# 选取前N个兴趣值最高的电影给用户进行推荐
return dict(sorted(result.items(), key=lambda x: x[1], reverse=True)[0:N])
def precision_recall(self, N):
hit = 0
all = 0
total = 0
for user in self.trainData.keys():
tu = self.testData.get(user, '0')
rank = self.recommend(user, N)
for item in rank:
if item in tu:
hit += 1
all += N
total += len(tu)
all = hit / (all * 1.0)
total = hit / (total * 1.0)
return all, total # all表示准确率,推荐正确的电影数/推荐的总电影数 total表示召回率,推荐正确的电影数/测试集中每个用户评分过的电影的总数
def coverage_popularity(self, N):
recommend_items = set()
all_items = set()
item_popularity = dict()
for user, items in self.trainData.items():
for item in items.keys():
if item not in item_popularity:
item_popularity[item] = 0
item_popularity[item] += 1
ret = 0
n = 0
for user in self.trainData.keys():
for item in self.trainData[user].keys():
all_items.add(item)
rank = self.recommend(user, N)
for item in rank:
recommend_items.add(item)
ret += math.log(1 + item_popularity[item])
n += 1
ret /= n * 1.0
coverage = len(recommend_items) / (len(all_items) * 1.0)
return ret, coverage
# ret表示流行度,对训练集中推荐的每个电影被评分的次数之和/推荐电影的总数 coverage表示覆盖率,对训练集中所有用户推荐的电影数总和/训练集中所有用户的所有的电影数总和
if __name__ == "__main__":
N = 10
ib = ItemCF("D:/杂文件/ml-1m/ratings.dat")
all = list(ib.recommend("1", N))
print("用户1进行推荐的结果如下:{}".format(all))
all1 = ib.precision_recall(N)
print("准确率为: {}".format(all1[0]))
print("召回率为: {}".format(all1[1]))
all2 = ib.coverage_popularity(N)
print("覆盖率为: {}".format(all2[1]))
print("流行度为: {}".format(all2[0]))
基于用户的协同过滤算法请见我的下一篇博客,谢谢。
本人也是初学者,有错误敬请指正,谢谢。