此算法的思想是:在一个电影推荐系统中,当一个用户A需要个性化推荐时,可以先找到和他有相似兴趣的其他用户,然后把那些用户喜欢的、而用户A没有看过的电影推荐给A。
在我看来可以说主要是两部分:
以代码的顺序,具体步骤如下:
基于物品的协同过滤算法的思想是:在一个电影推荐系统中,当一个电影A需要推荐给用户时,可以先找到和此电影相似的其他电影,然后把电影A推荐给观看过相似电影但是没看过A的用户们。具体步骤和基于用户的协同过滤类似,只不过先建立用户到电影的字典,然后计算电影相似性矩阵,以此类比,具体实现步骤就不再赘述。
import pandas as pd
import numpy as np
import pickle
import math
import copy
import time
save_flag=False#是否保存
S_path="./UserCF/S.file"
W_path="./UserCF/W.file"
predict_path="./UserCF/predict.file"
model_result_path="./UserCF/model_result.xlsx"
train_data_path="./train_data.file"
test_data_path="./test_data.file"
#写数据
def writeFile(data,path):
with open(path, "wb") as f:
pickle.dump(data, f)
#读数据
def readFile(path):
with open(path, "rb") as f:
return pickle.load(f)
global mv2user,user2mv,uid_list
mv2user={}#电影到用户 字典
user2mv={} #用户到电影 字典
uid_list=[]#存放用户id
#计算用户相似度
def getUserSimilarity(train_data):
#step1 电影到用户的倒排表
for index,item in train_data.iterrows():
mid=item['MovieID']
uid=item['UserID']
mv2user.setdefault(mid,[]).append(uid)
user2mv.setdefault(uid,[]).append(mid)
uid_list=list(set(train_data['UserID']))
uid_list.sort()#先赋值uid
#初始化用户矩阵
N=len(uid_list)
#S=np.zeros([N,N])
S=pd.DataFrame(data=np.zeros([N,N]),columns=uid_list,index=uid_list)
W=pd.DataFrame(data=np.zeros([N,N]),columns=uid_list,index=uid_list)
##S 计算出用户两两都看过的电影数量
#1.0
#for k,v in mv2user.items():
# for user1 in v:
# for user2 in v:
# S.loc[user1][user2]+=1
#2.0 改进每次只计算右上角矩阵
for k,v in mv2user.items():
v1=sorted(v)
v2=copy.deepcopy(v1)
for user1 in v1:
S.loc[user1][user1]+=1
v2.remove(user1)
for user2 in v2:
S.loc[user1][user2]+=1
np_S=S.values
np_S=np_S+np_S.T-np.diag(np_S.diagonal())#右上角+左下角-1次对角线
S=pd.DataFrame(data=np_S,columns=uid_list,index=uid_list)
#print(S)
#W 计算用户两两的相似度
#只计算右上角
uid_list2=copy.deepcopy(uid_list)#uid_list已经是从小到大排列过的list
for user1 in uid_list:
W.loc[user1][user1]=1#和自己的相似度是1
uid_list2.remove(user1)
for user2 in uid_list2:
if(S.loc[user1][user1]==0 and S.loc[user2][user2]==0):
W.loc[user1][user2]=0
else:
W.loc[user1][user2]=S.loc[user1][user2]/math.sqrt(S.loc[user1][user1]*S.loc[user2][user2])
W_value=W.values+W.values.T-np.diag(W.values.diagonal())#右上角+左下角-1次对角线
W=pd.DataFrame(W_value,columns=uid_list,index=uid_list)
print(W)
if(save_flag):
writeFile(S,S_path)
writeFile(W,W_path)
return W
#输入 用户相似度矩阵-uid-K值-电影推荐数
def getRecommend(W,uid,K,Num):
#step 1:找出最相似的K个用户
Wu=W[uid].sort_values(axis = 0, ascending=False)#uid和其他用户的相似度
kusers=list(Wu[1:K+1].index)#和uid最相似的K个用户id列表
#step 2:找出这些用户看过的电影 同时以 [电影id-感兴趣程度]做键值 计算兴趣程度词典
mdict={}
#遍历K个用户 每个用户的观影记录
for u in kusers:
for m in user2mv[u]:
#这个电影 uid没有看过的话 再存储
if(m not in user2mv[uid]):
mdict.setdefault(m,0)#mid第一次出现 设置兴趣程度为0
mdict[m]+=Wu[u]
#step 3:将电影集合按兴趣程度降序排列 取前Num个作为推荐
P=pd.Series(mdict)
P=P.sort_values(axis=0,ascending=False)
return list(P[:Num].index)
#对每个uid进行电影推荐
def getPrediction(W,K,Num):
prediction=[]
#这里很奇葩 全局变量uid_list在这里是[] 所以重新获取字典的key作为uid_list
#写成类会方便的多 对于全局变量的应用不熟悉 以后尽量不用这种全局变量了。。
uid_list=list(user2mv.keys())
uid_list.sort()
#
for uid in uid_list:
mlist=getRecommend(W,uid,K,Num)
for mid in mlist:
prediction.append([uid,mid])
prediction=pd.DataFrame(data=prediction,columns=['UserID','MovieID'])
if(save_flag):
writeFile(prediction,predict_path)
return prediction
#评价推荐结果
def getEvaluation(test_data,predict):
pre_right=pd.merge(predict,test_data,on=['UserID','MovieID'])
num_pre=predict.shape[0]
num_test=test_data.shape[0]
num_right=pre_right.shape[0]
precision=num_right/num_pre
recall=num_right/num_test
print(precision,recall)
return precision,recall
K=10
Num=10
train_data,test_data=readFile(train_data_path),readFile(test_data_path)
print(train_data.shape[0])
print(test_data.shape[0])
def UserCF():
start = time.clock()
W=getUserSimilarity(train_data)
print('W ok')
predict=getPrediction(W,K,Num)
precision,recall=getEvaluation(test_data,predict)
end = time.clock()
model=[]
model.append([K,Num,end-start,precision,recall])
model=pd.DataFrame(data=model,columns=['K','Num','Time','Precision','Recall'])
print(model)
#保存模型的参数和准确率
if(save_flag):
model.to_excel(model_result_path)
UserCF()
4.1 代码应该写成类 乱用了全局变量
一定要写成类,会方便的多 两个多月前写的 直接用的全局变量 自己对于全局变量的应用不熟悉 以后尽量不用这种全局变量了
4.2 这是最基本的算法
用户的相似度可以有多种衡量方法,而且还可以对于活跃用户或热门物品进行一定的惩罚等
4.3 随机选择训练集和按时间选择训练集的结果对比
#划分train test数据
def getTrainData(self,num,train_rate):
sample_data=self.ratings[:num]
train_data=pd.DataFrame(columns=self.ratings.columns)
test_data=pd.DataFrame(columns=self.ratings.columns)
##每个uid挑选出1-rate比例的数据作为test
user_group=sample_data['UserID'].groupby(sample_data['UserID']).count()
for uid,ucount in user_group.items():
n=ucount-math.ceil(ucount*train_rate)
if(n>0):
udf=sample_data[sample_data['UserID']==uid].sample(n)#随机选择n个
#udf=sample_data[sample_data['UserID']==uid].sort_values('Timestamps',ascending=False)[:n]#选择最新的n个记录
test_data=test_data.append(udf,sort=False)
train_data=sample_data.append(test_data)
train_data=train_data.drop_duplicates(subset=self.ratings.columns,keep=False)
return train_data,test_data
以下是前1w条数据方法1和方法2的结果对比
结果方法1的结果要比方法2的结果好很多 (单纯记录一下 因为我按照方法2做的 发现结果与参考中的差很多 一直以为哪里写错了。。)
参考:
项亮. 推荐系统实践[M]. 人民邮电出版社,2012.