基于用户的协同过滤算法
协同过滤简单来说是利用某兴趣相投、拥有共同经验之群体的喜好来推荐用户感兴趣的信息,个人通过合作的机制给予信息相当程度的回应(如评分)并记录下来以达到过滤的目的进而帮助别人筛选信息,回应不一定局限于特别感兴趣的,特别不感兴趣信息的纪录也相当重要。
基于用户的协同过滤算法其简单应用情景是:当用户需要个性化推荐时,可以先找到与他相似其他用户(通过兴趣、爱好或行为习惯等,然后把那些用户喜欢的并且自己不知道的物品推荐给用户。两个用户对流行物品的有相似兴趣,丝毫不能说明他们有相似的兴趣,此时要增加惩罚力度。
简单来讲,基于用户协同过滤,可以分为以下几步:
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)挖掘。
上面文字抽象后,可以得出算法步骤:
计算用户a对i物品的预测兴趣度,如下公式:
其中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 |
给电影评分 |
最后一次观看的电影 |
训练集共有:80670条记录,610条用户id,8991条电影id(下图为excel整理过的train数据集)
测试集共有:20168条记录,610条用户id,5133条电影id(下图为excel整理过的test数据集)
首先对数据集进行操作,用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(testPath, index_col=0, header=0)
trainDF = pd.read_csv(trainPath, index_col=0, header=0)
2、构造用户id,电影id与其索引的映射关系
moviesMap = dict(enumerate(list(trainDF.columns)))
usersMap = dict(enumerate(list(trainDF.index)))
# trainValues 将trainDF以list存储
trainValues = trainDF.values.tolist()
5、计算用户的相似度矩阵(610,610)
# 根据用户对电影的评分,来判断每个用户间相似度 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]
6、接下来,我们要找到与每个用户最相近的K个用户,用这K个用户的喜好来对目标用户进行物品推荐,这里K=10,下面的代码用来计算与每个用户最相近的10个用户:
userMostSimDict = dict()
for i 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 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
8、为用户推荐10步电影
# 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]
9、利用id与索引的映射,映射出推荐后的矩阵,并进行存储,存储为trained.csv数据文件
trainedDF = trainDF.copy(deep=True)
for key, value in userRecommendDict.items():
for (movieId, val) 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 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)
代码运行后得到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矩阵分解可参考如下代码,数据集不变。
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]))