基于领域的协同过滤算法原理及Python实现

目录

    • 1 相关说明
    • 2 基于领域的协同过滤算法原理
      • 2.1 基于用户的协同过滤
      • 2.2 基于物品的协同过滤
    • 3 代码实现
    • 4 一些问题

1 相关说明

  • 针对于TopN推荐问题
  • 协同过滤算法:推荐算法的一类,利用相似用户之间具有相似兴趣偏好的原理,来发现用户对物品的潜在偏好,仅需要用户对物品的行为数据,是目前应用最为成功的推荐方法
  • 基于领域的协同过滤:推荐系统中基本的算法,分为基于用户的协同过滤(UserCF)和基于物品的协同过滤(ItemCF)

2 基于领域的协同过滤算法原理

2.1 基于用户的协同过滤

此算法的思想是:在一个电影推荐系统中,当一个用户A需要个性化推荐时,可以先找到和他有相似兴趣的其他用户,然后把那些用户喜欢的、而用户A没有看过的电影推荐给A。
在我看来可以说主要是两部分:

  • 计算用户相似度(后续操作:对于每个用户 找到K个最相似用户看过但是该用户没看过的电影 作为电影推荐集合1)
  • 计算每个用户对电影推荐集合1中的电影的感兴趣程度(后续操作:按照兴趣程度为每个用户选出N个推荐电影)

以代码的顺序,具体步骤如下:

  • (1)建立电影到用户的字典,以每个电影作为键,观看过这个电影的用户列表作为值
  • (2)初始化用户相似矩阵S,S[u][v]代表用户u和用户v共同看过电影的个数,初始化全为0
  • (3)遍历电影到用户的字典,每个电影的观看用户集合中的两两用户在S矩阵中对应的元素加1
  • (4)计算用户的相似度,得到最终的用户相似矩阵S;有多种计算相似度的方法,本次代码中以余弦公式进行计算,分子是用户u和用户v看过共同电影的数量,分母是两个用户的观看过电影数 相乘的 开根号,Wuv越大,相当于cos越大,夹角越小,两个用户更相似
    在这里插入图片描述
  • (5)为每个用户挑选出相似度最大的K个用户,挑选出这K个用户观看过但是该用户没有观看过的电影作为推荐候选列表
  • (6)按照以下公式计算用户对电影推荐候选列表中的每部电影的感兴趣程度,然后将电影按照感兴趣程度降序排名,其中S(u,K)代表和用户u最相似的K个用户,Ν"(" i")" 表示观看过电影i的用户集合,Wuv代表用户u和用户v的相似度,Rvi代表用户v对电影i的兴趣程度,由于是TopN推荐问题,Rvi取1
    在这里插入图片描述

2.2 基于物品的协同过滤

基于物品的协同过滤算法的思想是:在一个电影推荐系统中,当一个电影A需要推荐给用户时,可以先找到和此电影相似的其他电影,然后把电影A推荐给观看过相似电影但是没看过A的用户们。具体步骤和基于用户的协同过滤类似,只不过先建立用户到电影的字典,然后计算电影相似性矩阵,以此类比,具体实现步骤就不再赘述。

3 代码实现

  • 使用的MovieLens数据 只使用评分记录中的UserID MovieID,代码展示的是基于用户的协同过滤;如果要实现基于物品的协同过滤 将UserID和MovieID,相当于电影是用户,用户是电影即可
  • 参数是K(取K个最相似用户)、N(要为每个用户推荐N部电影)
  • 划分训练集和测试集时 从每个用户的观影记录中随机挑选出固定比例的记录作为训练集(测试集) 本次挑选了80%作为训练集
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()

前10000条数据运行截图
基于领域的协同过滤算法原理及Python实现_第1张图片

4 一些问题

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
  • 方法1 从每个用户观影记录中随机选取20%作为测试集
  • 方法2 从每个用户观影记录中选取最新的20%作为测试集

以下是前1w条数据方法1和方法2的结果对比
基于领域的协同过滤算法原理及Python实现_第2张图片
基于领域的协同过滤算法原理及Python实现_第3张图片
结果方法1的结果要比方法2的结果好很多 (单纯记录一下 因为我按照方法2做的 发现结果与参考中的差很多 一直以为哪里写错了。。)

参考:
项亮. 推荐系统实践[M]. 人民邮电出版社,2012.

你可能感兴趣的:(推荐系统)