基于用户协同过滤算法的思考
本文对借鉴文章进行了学习,发现了一些问题,产生了一些思考,然后基于思考实现了一个matrixChain工具,该工具可以显示批量矩阵情况,像链式一样展示,便于更好的学习文章内容,特别是首次接触矩阵转换和计算的算法。
借鉴文章:Python推荐系统算法实现---------基于用户协同过滤算法
实现工具matrixChain:github地址
问题发现
原文中的预测分部分,应用使用的是加权处理,但是后面发现,原文结果获取的电影推荐没有去掉用户本身的评分电影,也就是说可能重复推荐给我,之前用户已经看过且评过分的电影。
所以,下面的代码中有对该的优化。
复盘思考
在初次看代码时,一些矩阵的引入和转换,自己疯狂print,对学习造成了一些障碍,所以就想着是否有工具可以协助学习,就产生了matrixChain工具。这里简单介绍下使用
# 引入matrixChain
from matrixChain import matrix_chain as mc
mc = mc() # 创建matrixChain对象
# 添加matrixChain
mc.marix_dict(matrix=your_matrix,matrix_title=your_matrix_title,order_num=matrix_order_num)
# 函数 marix_dict 参数
# matrix= 预展示的矩阵
# matrix_title= 预展示矩阵的标题
# order_num= 预展示矩阵在展示链中的顺序
mc.machain_run() # 运行matrixChain主函数
代码优化
这里基于上面借鉴文章,因此数据录入,可以去借鉴文章中获取,不再进行搬运了。
而且,由于原文代码更简洁,是基于函数对象进行码文,但是这种对初学者有些障碍,所以下面对此展开了,便于顺序执行和结果分析。
最后,加入了matrixChain工具,可以初步理解和后面复盘使用。
# -*- coding:utf-8 -*-
"""
-- Time: 2025/01/03 9:36
-- Title: userBaseRs.py
-- Software: PyCharm
-- 基于用户的协同过滤推荐算法(基于皮尔逊相关度)
"""
from matrixChain import matrix_chain as mc
import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings('ignore')
# 堆排序模块
import heapq
mc = mc() # 创建matrixChain对象
# 一、数据录入
# 1.用户评分数据
df = pd.read_csv(r'./data/ml-100k/u.data', sep='\t', names=['user_id', 'item_id', 'rating', 'titmestamp'])
df.head()
# 2.电影数据 电影标题
movie_titles = pd.read_csv(r'./data/ml-100k/u.item', sep='|', encoding='ISO-8859-1', header=None)
movie_titles.rename(columns = {0: "item_id", 1: "title"}, inplace=True)
movie_titles.head()
# 3.合并数据
df1 = pd.merge(df, movie_titles[['item_id','title']], on='item_id')
df1.head()
mc.marix_dict(matrix=df1.to_numpy(),matrix_title="原始矩阵",order_num=1) # 添加matrixChain
# 二、数据处理
# 4.评分标准化
df1['rating'] = (df1['rating'] - df1['rating'].min()) / (df1['rating'].max() - df1['rating'].min())
# 5.将数据透视为一个表格:index行索引、columns列索引、values要透视的值
user_matrix1 = df1.pivot_table(index='user_id', columns='title', values='rating')
mc.marix_dict(matrix=user_matrix1.to_numpy(),matrix_title="透视矩阵",order_num=2) # 添加matrixChain
# 6.返回电影标题数组
title = user_matrix1.columns
# 7.生成"用户-电影"关于评分的矩阵m*n
m = df1['user_id'].max() # 用户最大数量,考虑id从0开始,所有+1 943
n = df1['item_id'].max() # 电影最大数量 1682
df1.sort_values(by=['user_id', 'item_id'], inplace=True)
user_matrix_num = np.zeros((m, n))
for line in df1.itertuples():
# 0矩阵 赋值 矩阵索引从0开始所以-1 line[1]是user_id、line[2]是item_id、line[3]是rating
user_matrix_num[int(line[1]) - 1, int(line[2]) - 1] = line[3]
mc.marix_dict(matrix=user_matrix_num,matrix_title="用户评分矩阵",order_num=3) # 添加matrixChain
# 三、计算用户之间的皮尔逊相关度
# 8.相关度计算(皮尔逊)并对缺失值进行填充和将其转换为numpy数组,用户两两之间的相关度
user_pearson = pd.DataFrame(user_matrix_num).T.corr(method='pearson')
user_pearson = user_pearson.fillna(0)
user_pearson_num = user_pearson.to_numpy()
mc.marix_dict(matrix=user_pearson_num,matrix_title="皮尔逊矩阵",order_num=4) # 添加matrixChain
# 四、推荐分预测 -使用加权求和法
# 9.首先,创建评分记录矩阵record,含有评分记录为1,否则为0 即筛选
record = user_matrix_num > 0 # 出来为布尔值
record = np.array(record, dtype=int) # 布尔值转为整型 943 1682
mc.marix_dict(matrix=record,matrix_title="评分布尔矩阵",order_num=5) # 添加matrixChain
# 然后,创建用户对所评电影的平均评分矩阵
mean_user_rating = np.zeros((m, 1)) # 用户每部电影的平均得分 m=943
for i in range(m):
idx = record[i, :] != 0 # 用户i对每部电影的是否评分,[i,:]表示每一行的所有列
# 平均评分矩阵结合"用户-电影"评分矩阵,只取用户i对电影已评分的行记录矩阵,计算平均然后赋值给平均评分矩阵
mean_user_rating[i] = np.mean(user_matrix_num[i, idx]) # 第i行,用户i对已评分电影的平均得分
# 接着,获得减去平均得分后得"用户-电影"评分矩阵,即"用户-电影"评分去均矩阵
ratings_diff = (user_matrix_num - mean_user_rating) # 列中每一个元素减去平均数0
mc.marix_dict(matrix=ratings_diff,matrix_title="去均评分矩阵",order_num=6) # 添加matrixChain
# 计算推荐结果:[皮尔逊系数矩阵(矩阵乘)"用户-电影"评分去均矩阵的结果矩阵]比上[(皮尔逊系绝对值矩阵按照行求和)转置结果矩阵],然后加上用户电影评分均值
# 获得预测推荐分矩阵:使用加权求和法
user_prediction = mean_user_rating + user_pearson_num.dot(ratings_diff) / np.array([np.abs(user_pearson_num).sum(axis=0)]).T
mc.marix_dict(matrix=user_prediction,matrix_title="预测矩阵",order_num=7) # 添加matrixChain
mc.machain_run() # 运行matrixChain
# 10.命令行循环交互
while True:
userid = int(input("请输入用户id(如:3):"))
while userid not in range(0, 944): # 考虑id从1开始,所有+1
userid = int(input("抱歉您输入的用户id不存在:"))
if userid == 0:
print("感谢使用")
break
# 用户推荐十个
n = 10
# 这里利用record矩阵,对已经已经评过分(认为已经看过了)的电影,乘以一个比较大的负数,比如-1000,这样后面排序会过滤掉
a1 = user_prediction[userid-1, :] # 因为上面+1,这里复原-1
a = record[userid-1, :]*(-1000) + a1
# 堆排序 在数据集中找到最大的N个,这里用于指定排序的函数为 a.take,返回内容为range(len(a))
max_indexs = heapq.nlargest(n, range(len(a)), a.take)
print('已为用户id为{}用户推荐以下十部电影:'.format(userid))
k = 0
for i in max_indexs:
k = k + 1
print('{}.{},推荐值为:{}'.format(k, title[i], user_prediction[userid-1, :][i]))
print()