【推荐系统】基于用户的协同过滤算法(UserCF)原理及电影推荐Python实例+附基于用户SVD矩阵分解算法改进

 

【推荐系统】基于用户的协同过滤算法(UserCF)原理及电影推荐Python实例+附基于用户SVD矩阵分解算法改进

算法原理

基于用户的协同过滤算法

            协同过滤简单来说是利用某兴趣相投、拥有共同经验之群体的喜好来推荐用户感兴趣的信息,个人通过合作的机制给予信息相当程度的回应(如评分)并记录下来以达到过滤的目的进而帮助别人筛选信息,回应不一定局限于特别感兴趣的,特别不感兴趣信息的纪录也相当重要。

            基于用户的协同过滤算法其简单应用情景是:当用户需要个性化推荐时,可以先找到与他相似其他用户(通过兴趣、爱好或行为习惯等,然后把那些用户喜欢的并且自己不知道的物品推荐给用户。两个用户对流行物品的有相似兴趣,丝毫不能说明他们有相似的兴趣,此时要增加惩罚力度。

            简单来讲,基于用户协同过滤,可以分为以下几步:

1.收集用户信息

收集可以代表用户兴趣的信息。一般的网站系统使用评分的方式或是给予评价,这种方式被称为“主动评分”。另外一种是“被动评分”,是根据用户的行为模式由系统代替用户完成评价,不需要用户直接打分或输入评价数据。电子商务网站在被动评分的数据获取上有其优势,用户购买的商品记录是相当有用的数据。

2.最近邻搜索(Nearest neighbor search, NNS)

以用户为基础(User-based)的协同过滤的出发点是与用户兴趣爱好相同的另一组用户,就是计算两个用户的相似度。例如:查找n个和A有相似兴趣用户,把他们对M的评分作为A对M的评分预测。一般会根据数据的不同选择不同的算法,较多使用的相似度算法有Pearson Correlation Coefficient、Cosine-based Similarity、Adjusted Cosine Similarity。

3.产生推荐结果

有了最近邻集合,就可以对目标用户的兴趣进行预测,产生推荐结果。依据推荐目的的不同进行不同形式的推荐,较常见的推荐结果有Top-N 推荐和关系推荐。Top-N 推荐是针对个体用户产生,对每个人产生不一样的结果,例如:通过对A用户的最近邻用户进行统计,选择出现频率高且在A用户的评分项目中不存在的,作为推荐结果。关系推荐是对最近邻用户的记录进行关系规则(association rules)挖掘。

            上面文字抽象后,可以得出算法步骤:

  1. 使用某一种选定的相似度计算当前待计算用户(活跃用户)a的最近k个邻居,这k个邻居是距离a最近的用户,即可以看成与用户a最相似的k个用户
  2. 选定k个用户后,通过某种方法计算这些用户中a不曾购买或知道的物品i对于用户a的预测兴趣度
  3. 为了选择最好的n个推荐物品,从上面2中选择兴趣度最高的n个物品。

            计算用户a对i物品的预测兴趣度,如下公式:

 

【推荐系统】基于用户的协同过滤算法(UserCF)原理及电影推荐Python实例+附基于用户SVD矩阵分解算法改进_第1张图片

            其中S(u,K)表示与用户u兴趣最接近的K个用户,N(i)表示对物品i有过行为的用户集合,Wuv表示用户u和用户v的相似度,rvi表示用户v对物品i的感兴趣程度。

 

数据集

原始movielens数据集,分为test集和train集,每个数据集都包含4个字段:

字段

User

Item

Rating

Last_movie

含义

用户id

电影id

给电影评分

最后一次观看的电影

【推荐系统】基于用户的协同过滤算法(UserCF)原理及电影推荐Python实例+附基于用户SVD矩阵分解算法改进_第2张图片

训练集共有:80670条记录,610条用户id,8991条电影id(下图为excel整理过的train数据集)

【推荐系统】基于用户的协同过滤算法(UserCF)原理及电影推荐Python实例+附基于用户SVD矩阵分解算法改进_第3张图片

测试集共有:20168条记录,610条用户id,5133条电影id(下图为excel整理过的test数据集)

【推荐系统】基于用户的协同过滤算法(UserCF)原理及电影推荐Python实例+附基于用户SVD矩阵分解算法改进_第4张图片

 

      首先对数据集进行操作,用excel打开,使用excel数据透视图,User设为行标签,Item设置为列标签,Rating为值,则可以得到用户-电影评分矩阵,空白单元格意为未打分,用0填补。整理完毕得到如下图所示数据集:

 

 

代码简述


1、导入项目编程所需库,以及读取整理后的数据文件:

import math
import numpy as np
import pandas as pd
testPath = 
u'/Users/mr.sun/Desktop/rating_test.csv'
trainPath = u'/Users/mr.sun/Desktop/rating_train.csv'
读取csv 第一行、第一列为索引
testDF = pd.read_csv(testPathindex_col=0header=0)
trainDF = pd.read_csv(trainPath
index_col=0header=0)

 

2、构造用户id,电影id与其索引的映射关系

moviesMap = dict(enumerate(list(trainDF.columns)))
usersMap = 
dict(enumerate(list(trainDF.index)))
# trainValues trainDFlist存储 
trainValues = trainDF.values.tolist()

 

 

  1. 构造用户相似度计算函数
  2. 计算用户相似度 list表示
    def calCosineSimilarity(list1list2):
        res = 
    0
        
    denominator1 = 0
        
    denominator2 = 0
        
    for (val1val2) in zip(list1list2):
            res += (val1 * val2)
            denominator1 += val1 ** 
    2
            
    denominator2 += val2 ** 2
        
    return res / (math.sqrt(denominator1 * denominator2))

 

5、计算用户的相似度矩阵(610,610)

根据用户对电影的评分,来判断每个用户间相似度 userSimMatrix为相似度矩阵,值为ij用户的相似度
userSimMatrix = np.zeros((len(trainValues)len(trainValues))dtype=np.float32)
for in range(len(trainValues) - 1):
    
for in range(i + 1len(trainValues)):
        userSimMatrix[i
j] = calCosineSimilarity(trainValues[i]trainValues[j])
        userSimMatrix[j
i] = userSimMatrix[ij]

 

6、接下来,我们要找到与每个用户最相近的K个用户,用这K个用户的喜好来对目标用户进行物品推荐,这里K=10,下面的代码用来计算与每个用户最相近的10个用户:

userMostSimDict = dict()
for in range(len(trainValues)):
    userMostSimDict[i] = 
sorted(enumerate(list(userSimMatrix[i]))key=lambda x: x[1]reverse=True)[:10]

 

7、得到了每个用户对应10趣最相近的用之后,我根据 公式算用户对每个没有影的趣分:

用这K个用户的喜好中目标用户没有看过的电影进行推荐
userRecommendValues = np.zeros((len(trainValues)len(trainValues[0]))dtype=np.float32)
for in range(len(trainValues)):
    
for in range(len(trainValues[i])):
        
if trainValues[i][j] == 0:
            val = 
0
            
for (usersim) in userMostSimDict[i]:
                val += (trainValues[user][j] * sim)
            userRecommendValues[i
j] = val

 

8、为用户推荐10步电影

# userRecommendDict 其索引为用户,值list[(电影序号,分值),(),()] list元素个数为K推荐电影个数
userRecommendDict = dict()
for in range(len(trainValues)):
    userRecommendDict[i] = 
sorted(enumerate(list(userRecommendValues[i]))key=lambda x: x[1]reverse=True)[:10]

 

9、利用id与索引的映射,映射出推荐后的矩阵,并进行存储,存储为trained.csv数据文件

trainedDF = trainDF.copy(deep=True)
for keyvalue in userRecommendDict.items():
    
for (movieIdval) in value:
        trainedDF.iat[key
movieId] = val
trainedDF.to_csv("trained.csv")

 

10、对rating_train.csv数据集训练完毕后,利用rating_test.csv数据集进行评价,计算RMSE

RMse = 0
n = 0
V = 0
for keyvalue in userRecommendDict.items():
    
for (movieIdval) in value:
        user_rec = usersMap[key]
        movies_rec = moviesMap[movieId]
        
if movies_rec in testDF.columns.tolist():
            n = n+
1
            
V = V+pow(val-testDF.loc[user_recmovies_rec]2)
RMse = 
pow(V/n0.5)
print(RMse)

 

代码运行后得到RMSE= 7.009053150680466

同时运行得到了如下图的trained.csv数据文件

 

 

 

 

完整代码如下:

import math
import numpy as np
import pandas as pd
testPath = u'/Users/mr.sun/Desktop/rating_test.csv'
trainPath = u'/Users/mr.sun/Desktop/rating_train.csv'
# 读取csv 第一行、第一列为索引
testDF = pd.read_csv(testPath, index_col=0, header=0)
trainDF = pd.read_csv(trainPath, index_col=0, header=0)
## print(trainDF)
## print("---------------------------------------------------------------------------")

# moviesMap为电影id与序号的映射关系,序号从0开始,序号为索引
moviesMap = dict(enumerate(list(trainDF.columns)))
usersMap = dict(enumerate(list(trainDF.index)))
# trainValues 将trainDF以list存储,DF的每行为list一个值
trainValues = trainDF.values.tolist()
## print(moviesMap)
## print(usersMap)
## print(trainValues)
## print("---------------------------------------------------------------------------")

# 计算用户相似度 list表示
def calCosineSimilarity(list1, list2):
    res = 0
    denominator1 = 0
    denominator2 = 0
    for (val1, val2) in zip(list1, list2):
        res += (val1 * val2)
        denominator1 += val1 ** 2
        denominator2 += val2 ** 2
    return res / (math.sqrt(denominator1 * denominator2))

# 根据用户对电影的评分,来判断每个用户间相似度 userSimMatrix为相似度矩阵,值为i与j用户的相似度
userSimMatrix = np.zeros((len(trainValues), len(trainValues)), dtype=np.float32)
for i in range(len(trainValues) - 1):
    for j in range(i + 1, len(trainValues)):
        userSimMatrix[i, j] = calCosineSimilarity(trainValues[i], trainValues[j])
        userSimMatrix[j, i] = userSimMatrix[i, j]

# 找到与每个用户最相近的前K个用户,userMostSimDict索引为用户序号,值为list[(最相似用户序号,相似度),(,),(,)]
userMostSimDict = dict()
for i in range(len(trainValues)):
    userMostSimDict[i] = sorted(enumerate(list(userSimMatrix[i])), key=lambda x: x[1], reverse=True)[:10]
## print(userMostSimDict)
## print("---------------------------------------------------------------------------")

# 用这K个用户的喜好中目标用户没有看过的电影进行推荐
userRecommendValues = np.zeros((len(trainValues), len(trainValues[0])), dtype=np.float32)
for i in range(len(trainValues)):
    for j in range(len(trainValues[i])):
        if trainValues[i][j] == 0:
            val = 0
            for (user, sim) in userMostSimDict[i]:
                val += (trainValues[user][j] * sim)
            userRecommendValues[i, j] = val
## print(userRecommendValues)
## print("---------------------------------------------------------------------------")

# userRecommendDict 其索引为用户,值list[(电影序号,分值),(),()] list元素个数为K推荐电影个数
userRecommendDict = dict()
for i in range(len(trainValues)):
    userRecommendDict[i] = sorted(enumerate(list(userRecommendValues[i])), key=lambda x: x[1], reverse=True)[:10]
## print(userRecommendDict)
## print("---------------------------------------------------------------------------")

# 将一开始的索引转换为原来用户id与电影id
userRecommendList = []
for key, value in userRecommendDict.items():
    user = usersMap[key]
    for (movieId, val) in value:
        userRecommendList.append([user, moviesMap[movieId]])

recommendDF = pd.DataFrame(userRecommendList, columns=['userId', 'movieId'])
## print(recommendDF)
## print("---------------------------------------------------------------------------")
trainedDF = trainDF.copy(deep=True)
for key, value in userRecommendDict.items():
    for (movieId, val) in value:
        trainedDF.iat[key, movieId] = val
## print(trainedDF)
trainedDF.to_csv("trained.csv")


RMse = 0
n = 0
V = 0
for key, value in userRecommendDict.items():
    for (movieId, val) in value:
        user_rec = usersMap[key]
        movies_rec = moviesMap[movieId]
        if movies_rec in testDF.columns.tolist():
            n = n+1
            V = V+pow(val-testDF.loc[user_rec, movies_rec], 2)
RMse = pow(V/n, 0.5)
print(RMse)

基于用户SVD矩阵分解

若基于SVD矩阵分解可参考如下代码,数据集不变。

import numpy as np
from numpy import linalg as la
import numpy as np
import xlrd2

# 相似度
def cosSim(inA, inB):
    num = float(inA.T * inB)
    denom = la.norm(inA) * la.norm(inB)
    return 0.5 + 0.5 * (num / denom)

# 导入外部数据
def excel2m(path):  #读excel数据转为矩阵函数
    data = xlrd2.open_workbook(path)
    table = data.sheets()[0] # 获取excel中第一个sheet表
    nrows = table.nrows  # 行数
    ncols = table.ncols  # 列数
    datamatrix = np.zeros((nrows, ncols))
    for x in range(ncols):
        cols = table.col_values(x)
        cols1 = np.matrix(cols)  # 把list转换为矩阵进行矩阵操作
        datamatrix[:, x] = cols1 # 把数据进行存储
    return np.mat(datamatrix)

# 给定item  以及未对该item评分的用户user,通过相似用户,预测该user的评分
def svdEst2(dataMat, user, simMeas, item):
    # 给定item  以及未对该item评分的用户
    # 得到数据集中的用户数目
    n = np.shape(dataMat)[1]
    # 初始化两个评分值
    simTotal = 0.0
    ratSimTotal = 0.0
    # 奇异值分解
    # 在SVD分解之后,我们只利用包含90%能量值的奇异值,这些奇异值会以Numpy数组形式得以保存
    U, Sigma, VT = la.svd(dataMat)
    # 如果要进行矩阵运算,就必须要用这些奇异值构造出一个对角阵
    Sig4 = np.mat(np.eye(4) * Sigma[: 4])
    # 利用U矩阵将用户转换到低维空间中,构建转换后的用户(用户的4个主要特征)
    xformedItems = dataMat.T * U[:, :4] * Sig4.I # shape(11,4)
    # 遍历选定item行的每个用户(对item对应未平方的用户进行遍历,并将它与其他用户比较)
    for j in range(n):
        userRating = dataMat[item, j]
        # 如果某个用户的评分值为0,则跳过这个用户
        if userRating == 0:
            continue
        # 相似度的计算也会作为一个参数传递给该函数
        similarity = simMeas(xformedItems[user, :].T, xformedItems[j, :].T)
        # print('用户 %d 和用户 %d 相似度similarity is: %f' % (user, j, similarity))
        # 相似度会不断累加,每次计算时还考虑相似度和当前用户评分的乘积
        # similarity 用户相似度   userRating 用户评分
        simTotal += similarity
        ratSimTotal += similarity * userRating
    if simTotal == 0:
        return 0
    # 通过除以所有的评分和,对上述相似度评分的乘积进行归一化,使得最后评分在0~5之间,这些评分用来对预测值进行排序
    else:
        return ratSimTotal / simTotal

# 返回recUser的推荐item以及分数
def recommend3(dataMat, recUser, simMeas=cosSim, estMethod=svdEst2):
    items = np.shape(dataMat)[0]
    # 循环矩阵的item,找到recUser未评分项,进行预测评分,排序出最高分,即推荐电影
    recUserScores = []
    for item in range(items):
        if dataMat[item, recUser] == 0:
            estimatedScore = estMethod(dataMat, recUser, simMeas, item)
            recUserScores.append((item, estimatedScore))
    return sorted(recUserScores, key=lambda x: x[1], reverse=True)[0]

if __name__ == '__main__':
    path = u'/Users/mr.sun/Desktop/rating_test.xlsx'
    myMat = excel2m(path)
    userNum = np.shape(myMat)[1]
    # 这里的用户和电影都为其序号,从0开始
    for u in range(userNum):
        A = recommend3(myMat, u, estMethod=svdEst2, simMeas=cosSim)
        print("{}+{}".format(A[0], A[1]))
        # print("给该用户{}推荐的电影是{},预计评分为{}".format(u, A[0], A[1]))

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(python,算法,数据分析,推荐系统,机器学习)