最近看了几篇研究最大信息系数的文章,最大信息系数是一种度量两个变量之间关联关系的新方法。传统度量两个变量之间关系的指标包括皮尔逊相关系数和信息论中的互信息。皮尔逊相关系数在度量具有线性相关关系的两个变量时具有较好的效果,但若两个变量之间的关系不是线性时,不能进行准确地度量。互信息是信息论的鼻祖香农老先生在研究通信理论时引入的,后来研究者发现互信息这个度量在研究两个变量之间的关系具有非常强的作用,在统计学界广受欢迎。
本文在这里不去研究以上三个度量之间的区别。受传统用户最近邻模型在计算用户相似度时通常使用皮尔逊相似度计算方法启发,我想在我的研究中使用最大信息系数来计算用户之间的相关度,不过计算最大信息系数的过程还没完全理解透。幸好已经有前辈大牛在网上公开了各种代码。但是其中的Matlab代码一直是有问题的,Python代码是可以跑通的。因此本人在网上找了找Python做最近邻模型的协同过滤的代码也找到了一个前辈的代码(点击打开链接)。不过感觉这个结果跟我以前做的结果有点不同,仔细研读了几天,发现有几点与我的理解不同,这里将我的代码附上。说明:由于我对Python的语法很不熟悉所以在读取数据和最后输出格式方面几乎是复制这位前辈的代码。不过核心算法部分有两处改动,下面一一说明(涉及到另外一位作者的代码时,本文以原代码代替)。
1.在利用皮尔逊相关系数计算公式计算用户相似度时,原代码在计算用户的的评分均值是调用计算用户评分均值公式进行计算;然而计算用户相似度时针对的是两个用户都评价过的项目,故而此时的用户的评分均值应该是在用户对评分交集项目上取均值。文献[1]中的原文为"All the summations and averages in the formula are computed only over those articles that u1 and u2 both rated."。
2.在取前N个最近邻居进行预测评分时,原代码直接根据相似度大小选择最相近的N个邻居;然而常用的做法是将活跃用户u的最近邻居按相似度大小进行排序Nu,并获取训练集中评价过目标项目m的所有用户Nm,对Nu和Nm取交集并取前N项得到,若不足N项则直接取交集。
3.写此篇博客纯为探讨交流,欢迎讨论。
附上代码。
#-------------------------------------------------------------------------------
# Name: PearsonUserNeighCF
# Purpose: Personalized Recommendation
#
# Author: Jinkun Wang
# Email: [email protected], if you have any question about the
# code, please do not hesitate to contact me.
#
# Created: 10/09/2014
# Copyright: (c) Jinkun Wang 2014
#-------------------------------------------------------------------------------
from math import sqrt
import numpy as np
import matplotlib.pyplot as plt
def loadData():
trainSet = {}
testSet = {}
movieUser = {}
u2u = {}
TrainFile = 'ml-100k/u1.base' #指定训练集
TestFile = 'ml-100k/u1.test' #指定测试集
#加载训练集,生成电影用户的倒排序表 movieUser
for line in open(TrainFile):
(userId, itemId, rating, _) = line.strip().split('\t')
trainSet.setdefault(userId,{})
trainSet[userId].setdefault(itemId,float(rating))
movieUser.setdefault(itemId,[])
movieUser[itemId].append(userId.strip())
#防止测试集有训练集中没有出现过的项目
item_in_train = []
for m in movieUser.keys():
item_in_train.append(m)
#加载测试集
for line in open(TestFile):
(userId, itemId, rating, _) = line.strip().split('\t')
testSet.setdefault(userId,{})
testSet[userId].setdefault(itemId,float(rating))
return trainSet,testSet,movieUser,item_in_train
#计算一个用户的平均评分
def getAverageRating(user):
average = (sum(trainSet[user].values()) * 1.0) / len(trainSet[user].keys())
return average
#计算用户相似度
def UserSimPearson(trainSet):
userSim = {}
for u1 in trainSet.keys():
userSim.setdefault(u1,{})
u1_rated = trainSet[u1].keys()
for u2 in trainSet.keys():
userSim[u1].setdefault(u2,0)
if u1 != u2:
u2_rated = trainSet[u2].keys()
co_rated = list(set(u1_rated).intersection(set(u2_rated)))
if co_rated == []:
userSim[u1][u2] = 0
else:
num = 0 #皮尔逊计算公式的分子部分
den1 = 0 #皮尔逊计算公式的分母部分1
den2 = 0 #皮尔逊计算公式的分母部分2
sigma_u1_m = 0 #计算用户u1对共同评价项目的评分均值
sigma_u2_m = 0 #计算用户u2对共同评价项目的评分均值
for m in co_rated:
sigma_u1_m += trainSet[u1][m]
sigma_u2_m += trainSet[u2][m]
ave_u1_m = sigma_u1_m / len(co_rated)
ave_u2_m = sigma_u2_m / len(co_rated)
for m in co_rated:
num += (trainSet[u1][m] - ave_u1_m) * (trainSet[u2][m] - ave_u2_m) * 1.0
den1 += pow(trainSet[u1][m] - ave_u1_m, 2) * 1.0
den2 += pow(trainSet[u2][m] - ave_u2_m, 2) * 1.0
den1 = sqrt(den1)
den2 = sqrt(den2)
if den1 == 0 or den2 ==0 :
userSim[u1][u2] = 0
else:
userSim[u1][u2] = num / (den1 * den2)
else:
userSim[u1][u2] = 0
return userSim
#对用户相似度表进行排序处理
def sortSimMatrix(userSim):
neighSorted = {}
for u in userSim.keys():
neigh_sorted = sorted(userSim[u].items(), key = lambda x:x[1], reverse = True)
for key, value in neigh_sorted:
neighSorted.setdefault(u,[])
neighSorted[u].append(key)
return neighSorted
#寻找用户最近邻并生成推荐结果;与测试集比较获得算法的准确度
def getAccuracyMetric(N,trainSet,testSet,movieUser,neighSorted, userSim, item_in_train):
#寻找用户最近邻并生成推荐结果
pred = {}
for user, item in testSet.items(): #对测试集中的每个用户
pred.setdefault(user,{}) #生成用户User的预测空列表
ave_u_rating = getAverageRating(user)
neigh_uninterseced = neighSorted[user] #获取用户user的邻居集合(已按相似度大小降序排列)
for m in item.keys():
if m not in item_in_train:
pred[user][m] = ave_u_rating
else:
rated_m_user = movieUser[m] #测试集中评价过电影m的用户
neigh_intersected = sorted(rated_m_user,key = lambda x:neigh_uninterseced.index(x))
if len(neigh_intersected) > N:
neigh = neigh_intersected[0:N]
else:
neigh = neigh_intersected
neighRating = 0
neighSimSum = 0
for neighUser in neigh:
neighRating += (trainSet[neighUser][m] - getAverageRating(neighUser)) * userSim[user][neighUser]
neighSimSum += abs(userSim[user][neighUser])
if neighSimSum == 0:
pred[user][m] = ave_u_rating
else:
pred[user][m] = ave_u_rating + (neighRating * 1.0) / neighSimSum
#与测试集比较获得算法的准确度
mae = 0
rmse = 0
error_sum = 0
sqrError_sum = 0
setSum = 0
for user,item in pred.items():
for m in item.keys():
error_sum += abs(pred[user][m] - testSet[user][m])
sqrError_sum += pow(pred[user][m] - testSet[user][m],2)
setSum += 1
mae = error_sum / setSum
rmse = sqrt(sqrError_sum / setSum)
return mae, rmse
if __name__ == '__main__':
print '正在加载数据...'
trainSet,testSet,movieUser,item_in_train = loadData()
print '正在计算用户间相似度...'
userSim = UserSimPearson(trainSet)
print '对相似度列表按相似度大小进行排列...'
neighSorted = sortSimMatrix(userSim)
print '正在寻找最近邻...'
NeighborSize = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
MAE = []
RMSE = []
for N in NeighborSize: #对不同的近邻数
mae, rmse = getAccuracyMetric(N,trainSet,testSet,movieUser,neighSorted, userSim, item_in_train) #获得算法推荐精度
MAE.append(mae)
RMSE.append(rmse)
plt.subplot(211)
plt.plot(NeighborSize,MAE)
plt.xlabel('NeighborSize')
plt.ylabel('Mean Absolute Error')
plt.title('Pearson User Neighbor Model Collaborative Filtering')
plt.subplot(212)
plt.plot(NeighborSize,RMSE)
plt.xlabel('NeighborSize')
plt.ylabel('Root Mean Square Error')
plt.title('Pearson User Neighbor Model Collaborative Filtering')
plt.show()
raw_input('按任意键继续...')
[1] Resnick P, Iacovou N, Suchak M, et al. GroupLens: an open architecture for collaborative filtering of netnews[C]//Proceedings of the 1994 ACM conference on Computer supported cooperative work. ACM, 1994: 175-186.