本文完整项目代码已托管至github,下载地址:https://github.com/gitren111/Movie-Recommender-By-user-based_cf
User_based_CF算法简介
User-based Collaborative Filtering(基于用户的协同过滤)算法通过计算用户之间的相似度来进行推荐。首先,构建用户-电影评分矩阵,然后利用皮尔逊相关系数衡量用户之间的相似度。预测某用户对电影的评分时,基于其与其他用户的相似度以及近邻用户的评分进行加权计算。该算法通过基准评分和相似度加权评分的组合,预测目标用户可能的兴趣,广泛应用于个性化推荐系统中。
概念与公式
-
皮尔逊相关系数:协方差/各自标准差,通过归一化解决量纲差异大问题,取值在-1到1之间,越接近1说明越相似
-
用户相似度计算:构建用户的评分矩阵,然后用户两两之间用皮尔逊相关系数公式计算用户的相似度
-
预测用户u对电影的评分:
- 基准评分:ru是用户u对所有电影的平均评分
- 分子项:筛选近邻用户(与u用户正相关且对i电影有过评分的客户),求近邻客户相似度*(近邻客户对i电影评分-近邻客户平均评分)
- 分母项:近邻客户的相似度数据绝对值和进行相似度归一化
基于User_based_CF算法实现电影推荐功能
数据集加载,转换为用户-电影评分矩阵
ra_data_path = 'ml-latest-small/ml-latest-small/ratings.csv'
ra_cache_dir = 'cache'
cache_path = os.path.join(ra_cache_dir,'rating_matrix_cache')
def load_data(ra_data_path,cache_path):
print('开始分批加载数据集...')
if not os.path.exists(ra_cache_dir):
os.makedirs(ra_cache_dir)
if os.path.exists(cache_path):
print('加载缓冲中...')
ratings_matrix = pd.read_pickle(cache_path)
print('从缓存加载数据集完毕')
else:
dtype = {'userId': np.int32, 'movieId': np.int32, 'rating': np.float32}
print('加载新数据中')
#加载前三列数据:用户ID、电影ID、评分
ratings = pd.read_csv(ra_data_path,dtype=dtype,usecols=range(3))
#转换为用户-电影评分矩阵
ratings_matrix = pd.pivot_table(data=ratings,index=['userId'],columns=['movieId'],values='rating')
ratings_matrix.to_pickle(cache_path)
print('数据加载完毕')
return ratings_matrix
ratings_matrix = load_data(ra_data_path,cache_path)
print(ratings_matrix.shape)
print(ratings_matrix.head())
用户相似度计算
def compute_persion_similarity(ratings_matrix,based='user'):
user_similarity_cache_path = os.path.join(ra_cache_dir,'user_similarity_cache')
#基于皮尔森相关系数计算相似度
if not os.path.exists(ra_cache_dir):
os.makedirs(ra_cache_dir)
if os.path.exists(user_similarity_cache_path):
print('正从缓存加载用户相似度矩阵')
similarity = pd.read_pickle(user_similarity_cache_path)
else:
print('开始计算用户相似度矩阵')
similarity = ratings_matrix.T.corr()
#转置为行标签是电影ID,列标签是用户ID,计算用户间相似度矩阵(默认皮尔森)
similarity.to_pickle(user_similarity_cache_path)
print('相似度矩阵加载完毕')
return similarity
user_similar = compute_persion_similarity(ratings_matrix,based='user')
print('用户相似度矩阵预览和大小')
print(user_similar.shape)
print(user_similar.head())
预测评分函数(基于用户协同过滤):user-based CF
公式:目标用户u的平均评分+k个近邻客户加总(近邻客户相似度*(近邻客户对i电影评分-近邻客户平均评分))/相似度
def predict_userBasedCF(uid,itemid,ratings_matrix,user_similar):
#找到与用户u相关性的所有其他用户
similar_user = user_similar[uid].drop(labels=[uid]).dropna()
#(2)相似用户筛选
similar_user = similar_user.where(similar_user>0).dropna()
if similar_user.empty is True:
raise Exception('客户{}没有相似客户'.format(uid))
#(3)从客户u的相似客户中找出对物品i有评分的近邻客户
id_user = set(ratings_matrix[itemid].dropna().index)&set(similar_user.index)
finally_similar_users = similar_user.loc[list(id_user)]
#(4)计算用户u的平均评分
user_ratings = ratings_matrix.loc[uid].dropna()
user_mean_rat = user_ratings.mean()
#(5)公式计算
sum_up = 0
sum_down = 0
for sim_uid,similarity in finally_similar_users.items():
#近邻用户v所有电影的平均评分
sim_user_rated_movies = ratings_matrix.loc[sim_uid].dropna()
sim_user_rat = sim_user_rated_movies.mean()
#近邻用户对物品i的评分
sim_user_rating_for_item = sim_user_rated_movies[itemid]
#分子计算:近邻客户与目标客户u相似度*(近邻客户对i评分-近邻客户平均评分)
sum_up += similarity*(sim_user_rating_for_item - sim_user_rat)
#分母计算
sum_down += abs(similarity)
#最后将所有相似客户的累加再除分母累加
if sum_down == 0:
print('分母为0不可计算')
raise None
predict_rating = user_mean_rat + sum_up/sum_down
predict_rating = min(predict_rating,5)
return round(predict_rating,2)
根据筛选条件来预测评分
def userBase_predict_all(uid,item_ids,ratings_matrix,user_similar):
for iid in item_ids:
try:
rating = predict_userBasedCF(uid,iid,ratings_matrix,user_similar)
except Exception as e:
print('基于用户的协同过滤算法无法预测结果')
else:
yield uid,iid,rating
def predict_all(uid,ratings_matrix,similar_matrix,filter_rule=None):
if not filter_rule:
item_ids = ratings_matrix.columns
#筛选掉非热门电影
elif filter_rule == 'unhot':
count = ratings_matrix.count()
item_ids = count.where(count>10).dropna().index
# 筛选没有评分的电影
elif filter_rule == 'rated':
user_ratings = ratings_matrix.loc[uid]
item_ids = user_ratings[user_ratings.between(0,5)].index
#筛选掉非热门且无评分电影
elif set(filter_rule) == set(['unhot','rated']):
count = ratings_matrix.count()
ids1 = count.where(count>10).dropna().index
user_ratings = ratings_matrix.loc[uid]
ids2 = user_ratings[user_ratings.between(0,5)].index
item_ids = set(ids1)&set(ids2)
else:
raise Exception('无效的筛选参数')
yield from userBase_predict_all(uid, item_ids, ratings_matrix, similar_matrix)
预测电影评分
def topk_userBase_predict(k,filter_rule=None):
results = predict_all(1,ratings_matrix,user_similar,filter_rule)
results = sorted(results, key=lambda x: x[2], reverse=True)[:k] # 截取前面k个
print('基于用户的协同过滤评分预测,用户最喜欢的十部电影:')
for uid, itemid, predict_rating in results:
print('预测出用户{}对电影{}的评分为:{:.2f}'.format(uid, itemid, predict_rating))
return results
topk_userBase_predict(10,filter_rule=['unhot','rated'])
输出结果
基于用户的协同过滤评分预测,用户最喜欢的十部电影:
预测出用户1对电影2571的评分为:5.00
预测出用户1对电影527的评分为:5.00
预测出用户1对电影50的评分为:5.00
预测出用户1对电影1089的评分为:5.00
预测出用户1对电影1617的评分为:5.00
预测出用户1对电影101的评分为:5.00
预测出用户1对电影1136的评分为:5.00
预测出用户1对电影2700的评分为:5.00
预测出用户1对电影3740的评分为:5.00
预测出用户1对电影1196的评分为:5.00
总结:基于用户的协同过滤算法优缺点
- 优点:适合用户评分数据密集场景
- 缺点:如果用户数量多或者评分矩阵稀疏(比如电商就是用户数量远大于商品),计算复杂度高,最终效果下降