1、首先读取数据,是一个电影的评分数据
import os
import pandas as pd
import numpy as np
DATA_PATH = "./datasets/ml-latest-small/ratings.csv"
CACHE_DIR = "./datasets/cache/"
def load_data(data_path):
'''
加载数据
:param data_path:数据集路径
:param cache_path:数据集缓存路径
:return:用户-物品评分矩阵
'''
# 数据集缓存地址
cache_path = os.path.join(CACHE_DIR,"ratings_matrix.cache")
print("开始加载数据集...")
if os.path.exists(cache_path): # 判断是否存在缓存文件
print("加载缓存中...")
ratings_matrix = pd.read_pickle(cache_path)
print("从缓存加载数据集完毕")
else:
print("加载新数据中...")
# 设置要加载的数据字段的类型
dtype = {'userId':np.int32,'movieId':np.int32,'rating':np.float32}
# 加载数据,我们只用到三列数据,分布是用户Id,电影id,以及用户对电影的对应的评分
ratings = pd.read_csv(data_path,dtype=dtype,usecols=range(3)) # usecols=range(3)只用前3列的数据
# 透视表,将电影id转换为列名称,转换为一个user-movie的评分矩阵
ratings_matrix = ratings.pivot_table(index=['userId'],columns=['movieId'],values='rating')
# 存入缓存文件
ratings_matrix.to_pickle(cache_path)
print("数据集加载完毕")
return ratings_matrix
2、应用数据,计算用户及物品的两两相似度(因为是数值评分,故用皮尔逊相关系数来计算相似度)
def compute_pearson_similarity(ratings_matrix,based="user"):
'''
计算皮尔逊相关系数
:param ratings_matrix:用户-物品评分矩阵
:param based:"user" or "item"
:return:相似度
'''
user_similarity_cache_path = os.path.join(CACHE_DIR,"user_similarity.cache")
item_similarity_cache_path = os.path.join(CACHE_DIR,"item_similarity.cache")
# 基于皮尔逊相关系数计算用户相似度
if based == "user":
if os.path.exists(user_similarity_cache_path):
print("正从缓存加载用户相似度矩阵...")
similarity = pd.read_pickle(user_similarity_cache_path)
else:
print("开始计算用户相似度矩阵...")
similarity = ratings_matrix.T.corr()
similarity.to_pickle(user_similarity_cache_path)
elif based == "item":
if os.path.exists(item_similarity_cache_path):
print("正从缓存加载物品相似度矩阵...")
similarity = pd.read_pickle(item_similarity_cache_path)
else:
print("开始计算物品相似度矩阵...")
similarity = ratings_matrix.corr()
similarity.to_pickle(item_similarity_cache_path)
else:
raise Exception("Unhandled 'based' Value: %s" % based)
print("相似度矩阵计算加载完毕")
return similarity
3、User-based CF评分预测:使用用户间的相似度进行预测
关于评分预测的方法也有很多方案,建议用以下这种效果比较好的方案,该方案考虑了用户本身的评分以及近邻用户的加权平均相似度打分来进行预测:
其中sim(u,v)代表相似度,rvi代表实际评分
def predict(uid,iid,ratings_matrix,user_similar):
'''
预测给定用户对给定物品的评分值
:param uid:用户id
:param iid:物品id
:param ratings_matrix:用户-物品评分矩阵
:param user_similar:用户两两相似度矩阵
:return:预测的评分值
'''
print("开始预测用户<%d>对电影<%d>的评分..." % (uid,iid))
# 1.找出uid用户的相似用户
similar_users = user_similar[uid].drop([uid]).dropna()
# 相似用户筛选规则:正相关的用户
similar_users = similar_users.where(similar_users>0).dropna()
if similar_users.empty is True:
raise Exception("用户<%d>没有相似的用户" % uid)
# 2.从uid用户的近邻相似用户中筛选出对iid物品有评分记录的近邻用户
ids = set(ratings_matrix[iid].dropna().index)&set(similar_users.index)
finally_similar_users = similar_users.loc[list(ids)]
# 3.结合uid用户与其近邻用户的相似度预测uid用户对iid物品的评分
sum_up = 0 # 评分预测公式的分子部分的值
sum_down = 0 # 评分预测公式的分母部分的值
for sim_uid,similarity in finally_similar_users.iteritems():
# 近邻用户的评分数据
sim_user_rated_movies = ratings_matrix.loc[sim_uid].dropna()
# 近邻用户对iid物品的评分
sim_user_rating_for_item = sim_user_rated_movies[iid]
# 计算分子的值
sum_up += similarity * sim_user_rating_for_item
# 计算分母的值
sum_down += similarity
# 计算预测的评分值并返回
predict_rating = sum_up / sum_down
print("预测出用户<%d>对电影<%d>的评分:%0.2f" % (uid,iid,predict_rating))
return round(predict_rating,2)
4、调用预测评分函数,我们针对全体用户进行预测并增加过滤规则,比如用户只想看高分电影、或是用户只想看没看过的电影等等
# 添加过滤规则
def _predict_all(uid,item_ids,ratings_matrix,user_similar):
'''
预测全部评分
:param uid:用户id
:param item_ids:要预测的物品id列表
:param ratings_matrix:用户-物品打分矩阵
:param user_similar:用户两两间的相似度
:return:生成器,逐个返回预测评分
'''
# 逐个预测
for iid in item_ids:
try:
rating = predict(uid,iid,ratings_matrix,user_similar)
except Exception as e:
print(e)
else:
yield uid,iid,rating
def predict_all(uid,ratings_matrix,user_similar,filter_rule=None):
'''
预测全部评分,并可根据条件进行前置过滤
:param uid:用户id
:param ratings_matrix:用户-物品打分矩阵
:param user_similar:用户两两间的相似度
:param filter_rule:过滤规则,只能是四选一,否则将抛异常:"unhot","rated",["unhot","rated"],None
:return:生成器,逐个返回预测评分
'''
if not filter_rule:
item_ids = ratings_matrix.columns
elif isinstance(filter_rule,str) and filter_rule == "unhot":
'''过滤非热门电影'''
# 统计每部电影的评分数
count = ratings_matrix.count()
# 过滤出评分数高于10的电影,作为热门电影
item_ids = count.where(count>10).dropna().index
elif isinstance(filter_rule,str) and filter_rule == "rated":
'''过滤用户评分过的电影'''
# 获取用户对所有电影的评分记录
user_ratings = ratings_matrix.loc[uid]
# 评分范围是1-5,小于6的都是评分过的,除此以外的都是没有评分的
_ = user_ratings<6
item_ids = _.where(_==False).dropna().index
elif isinstance(filter_rule,list) and set(filter_rule) == set(["unhot","rated"]):
'''过滤非热门和用户已经评分过的电影'''
count = ratings_matrix.count()
ids1 = count.where(count > 10).dropna().index
user_ratings = ratings_matrix.ix[uid]
_ = user_ratings < 6
ids2 = _.where(_ == False).dropna().index
# 取二者交集
item_ids = set(ids1)&set(ids2)
else:
raise Exception("无效的过滤参数")
yield from _predict_all(uid,item_ids,ratings_matrix,user_similar)
5、根据预测评分为指定用户进行TOP-N推荐(只对前N个预测高分的电影进行推荐)
def top_k_rs_result(k):
ratings_matrix = load_data(DATA_PATH)
user_similar = compute_pearson_similarity(ratings_matrix,based="user")
results = predict_all(1,ratings_matrix,user_similar,filter_rule=["unhot","rated"])
return sorted(results,key=lambda x: x[2],reverse=True)[:k]
6、调用函数,并完成推荐
if __name__ == '__main__':
from pprint import pprint
result = top_k_rs_result(20)
pprint(result)
结果如下: