基于用户的协同过滤算法也被称为最近邻协同过滤或KNN (K.Nearest-Neighbor,K最近邻算法)。其核心思想就是,首先根据相似度计算出目标用户的邻居集合,然后用邻居用户评分的加权组合来为目标用户作推荐。
通常这些算法都可以总结成三步:
Pearson相关系数将两个用户共同评分的n个项目看做一组向量,计算两个用户在这n个项目上评分的相关性,减去用户平均评分是基于用户评分尺度的考量,公式如下:
其中是用户u和v都评过分的项目的集合,是用户u所有评分的平均分。
余弦相似度则是把用户的评分(包括所有项目,未评过分的项目分数则为0)看作是一个向量,通过计算两个向量夹角的余弦来衡量用户之间的相似性,其定义如公式如下:
得到用户相似度后,接下来的工作就是对近邻用户下载过的应用进行评分预测,公式如下:
其中得到的就是用户u对物品i的评分的预测,K是邻居的集合也就是和用户u最相似的用户的集合。
Python代码如下:
''' 数据集包括 * 943个用户对1682部电影的100,000 评分 (1-5) . * 每个用户至少评分20个电影. *每部电影的信息 u.data -- 943个用户对1682部电影的100,000 评分(1-5). 每个用户至少评分20个电影. 用户和电影的编号都从1开始. 数据随机排列. 每行的信息为: user id | item id | rating | timestamp. time stamps是从1/1/1970 UTC起的unix 秒数 u.item -- 所有电影的信息,每行记录的信息有: movie id | movie title | release date | video release date | IMDb URL | unknown | Action | Adventure | Animation | Children's | Comedy | Crime | Documentary | Drama | Fantasy | Film-Noir | Horror | Musical | Mystery | Romance | Sci-Fi | Thriller | War | Western | 最后19个属性是电影的类型,是该类型该属性值为1,否者为0,一个电影可以同时为多种类型 u1.base -- The data sets u1.base and u1.test through u5.base and u5.test u1.test are 80%/20% splits of the u data into training and test data. u2.base Each of u1, ..., u5 have disjoint test sets; this if for u2.test 5 fold cross validation (where you repeat your experiment u3.base with each training and test set and average the results). u3.test These data sets can be generated from u.data by mku.sh. u4.base u4.test u5.base u5.test ''' from math import sqrt def loadData(): trainSet = {} testSet = {} movieUser = {} u2u = {} TrainFile = 'ml-100k/u1.base' #指定训练集 TestFile = 'ml-100k/u1.test' #指定测试集 #加载训练集 for line in open(TrainFile): (userId, itemId, rating, timestamp) = line.strip().split('\t') trainSet.setdefault(userId,{}) trainSet[userId].setdefault(itemId,float(rating)) movieUser.setdefault(itemId,[]) movieUser[itemId].append(userId.strip()) #加载测试集 for line in open(TestFile): (userId, itemId, rating, timestamp) = line.strip().split('\t') testSet.setdefault(userId,{}) testSet[userId].setdefault(itemId,float(rating)) #生成用户用户共有电影矩阵 for m in movieUser.keys(): for u in movieUser[m]: u2u.setdefault(u,{}) for n in movieUser[m]: if u!=n: u2u[u].setdefault(n,[]) u2u[u][n].append(m) return trainSet,testSet,u2u #计算一个用户的平均评分 def getAverageRating(user): average = (sum(trainSet[user].values())*1.0) / len(trainSet[user].keys()) return average #计算用户相似度 def getUserSim(u2u,trainSet): userSim = {} # 计算用户的用户相似度 for u in u2u.keys(): #对每个用户u userSim.setdefault(u,{}) #将用户u加入userSim中设为key,该用户对应一个字典 average_u_rate = getAverageRating(u) #获取用户u对电影的平均评分 for n in u2u[u].keys(): #对与用户u相关的每个用户n userSim[u].setdefault(n,0) #将用户n加入用户u的字典中 average_n_rate = getAverageRating(n) #获取用户n对电影的平均评分 part1 = 0 #皮尔逊相关系数的分子部分 part2 = 0 #皮尔逊相关系数的分母的一部分 part3 = 0 #皮尔逊相关系数的分母的一部分 for m in u2u[u][n]: #对用户u和用户n的共有的每个电影 part1 += (trainSet[u][m]-average_u_rate)*(trainSet[n][m]-average_n_rate)*1.0 part2 += pow(trainSet[u][m]-average_u_rate, 2)*1.0 part3 += pow(trainSet[n][m]-average_n_rate, 2)*1.0 part2 = sqrt(part2) part3 = sqrt(part3) if part2 == 0 or part3 == 0: #若分母为0,相似度为0 userSim[u][n] = 0 else: userSim[u][n] = part1 / (part2 * part3) return userSim #寻找用户最近邻并生成推荐结果 def getRecommendations(N,trainSet,userSim): pred = {} for user in trainSet.keys(): #对每个用户 pred.setdefault(user,{}) #生成预测空列表 interacted_items = trainSet[user].keys() #获取该用户评过分的电影 average_u_rate = getAverageRating(user) #获取该用户的评分平均分 userSimSum = 0 simUser = sorted(userSim[user].items(),key = lambda x : x[1],reverse = True)[0:N] for n, sim in simUser: average_n_rate = getAverageRating(n) userSimSum += sim #对该用户近邻用户相似度求和 for m, nrating in trainSet[n].items(): if m in interacted_items: continue else: pred[user].setdefault(m,0) pred[user][m] += (sim * (nrating - average_n_rate)) for m in pred[user].keys(): pred[user][m] = average_u_rate + (pred[user][m]*1.0) / userSimSum return pred #计算预测分析准确度 def getMAE(testSet,pred): MAE = 0 rSum = 0 setSum = 0 for user in pred.keys(): #对每一个用户 for movie, rating in pred[user].items(): #对该用户预测的每一个电影 if user in testSet.keys() and movie in testSet[user].keys() : #如果用户为该电影评过分 setSum = setSum + 1 #预测准确数量+1 rSum = rSum + abs(testSet[user][movie]-rating) #累计预测评分误差 MAE = rSum / setSum return MAE if __name__ == '__main__': print u'正在加载数据...' trainSet,testSet,u2u = loadData() print u'正在计算用户间相似度...' userSim = getUserSim(u2u,trainSet) print u'正在寻找最近邻...' for N in (5,10,20,30,40,50,60,70,80,90,100): #对不同的近邻数 pred = getRecommendations(N,trainSet,userSim) #获得推荐 mae = getMAE(testSet,pred) #计算MAE print u'邻居数为:N= %d 时 预测评分准确度为:MAE=%f'%(N,mae) raw_input('按任意键继续...')
结果如下: