协同过滤:基于用户的协同过滤itemCF

基于用户的协同过滤算法也被称为最近邻协同过滤或KNN (K.Nearest-Neighbor,K最近邻算法)。其核心思想就是,首先根据相似度计算出目标用户的邻居集合,然后用邻居用户评分的加权组合来为目标用户作推荐。

通常这些算法都可以总结成三步:

  1. 首先,使用用户已有的评分来计算用户之间的相似度;
  2. 然后,选择与目标用户相似度最高的K个用户,通常把这些用户称为邻居;
  3. 最后,通过对邻居用户的评分的加权平均来预测目标用户的评分。为了方便说明,我们把系统中用户的集合记为U。物品的集合记为I,用户u,v∈U,物品i,j∈I, 是用户对物品的评分,而用户u和v之间的相似度记为 ,用一个m×n的矩阵来表示所个用户对玎个物品的评分情况。
用来衡量用户之间的相似性方法有很多,最常见的有两种:Pearson相关系数、余弦相似度以及调整余弦相似度。

Pearson相关系数将两个用户共同评分的n个项目看做一组向量,计算两个用户在这n个项目上评分的相关性,减去用户平均评分是基于用户评分尺度的考量,公式如下:

协同过滤:基于用户的协同过滤itemCF_第1张图片

其中是用户u和v都评过分的项目的集合,是用户u所有评分的平均分。

余弦相似度则是把用户的评分(包括所有项目,未评过分的项目分数则为0)看作是一个向量,通过计算两个向量夹角的余弦来衡量用户之间的相似性,其定义如公式如下:


得到用户相似度后,接下来的工作就是对近邻用户下载过的应用进行评分预测,公式如下:


其中得到的就是用户u对物品i的评分的预测,K是邻居的集合也就是和用户u最相似的用户的集合。

Python代码如下:

<pre name="code" class="html">#########################################
# 基于用户的最近邻协同过滤算法
# author:SU
# date:2015-03-06
# e-mail:[email protected]

# 基于用户的协同过滤推荐原理:
# 根据所有用户对物品或者信息的偏好,发现与当前用户口味和爱好相似的'邻居'用户群,在一般的应用中采用'KNN近邻算法',
# 然后基于这k个邻居的历史偏好信息,为当前用户进行推荐。
# 假设用户 A 喜欢物品 A,物品 C,用户 B喜欢物品 B,用户 C喜欢物品 A ,物品 C 和物品 D;从这些用户的历史喜好信息中,
# 我们可以发现用户 A和用户 C 的口味和偏好是比较类似的,同时用户 C还喜欢物品 D,那么我们可以推断用户 A可能也喜欢物品 D,
# 因此可以将物品D推荐给用户 A。
# 基于用户的协同过滤推荐机制和基于人口统计学的 推荐机制都是计算用户的相似度,并基于“邻居”用户群计算推荐,
# 但它们所不同的是如何计算用户的相似度,基于人口统计学的机制只考虑用户本身的特征,而基于用户的协同过滤机
# 制可是在用户的历史偏好的数据上计算用户的相似度,它的基本假设是,喜欢类似物品的用户可能有相同或者相似的口味和偏好。
#
# 基于用户的协同过滤(最近邻协同过滤KNN),根据相似度计算出目标用户的邻居集合,然后用邻居用户评分的加权组合来为目标用户做推荐
# 这些算法总结成三点:
# 首先,使用用户已有的评分来计算用户之间的相似度;
# 然后,选择与目标用户相似度最高的K个用户,通常把这些用户称为邻居;
# 最后,通过对邻居用户的评分的加权平均来预测目标用户的评分。为了方便说明,我们把系统中用户的集合记为U。物品的集合记为I,用户u,v∈U,物品i,j∈I, 是用户对物品的评分,而用户u和v之间的相似度记为 ,用一个m×n的矩阵来表示所个用户对玎个物品的评分情况。
# 代码网址:http://blog.csdn.net/anryyang/article/details/23563237
#########################################

'''
数据集包括:
	* 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 


后期改动:
1、在利用皮尔逊相关系数计算公式计算用户相似度时,原代码在计算用户的的评分均值是调用计算用户评分均值公式进行计算;
然而计算用户相似度时针对的是两个用户都评价过的项目,故而此时的用户的评分均值应该是在用户对评分交集项目上取均值。
2、在取前N个最近邻居进行预测评分时,原代码直接根据相似度大小选择最相近的N个邻居;然而常用的做法是将活跃用户u的最近邻居按相似度大小进行排序Nu,
并获取训练集中评价过目标项目m的所有用户Nm,对Nu和Nm取交集并取前N项得到,若不足N项则直接取交集。
参考博文:
http://blog.csdn.net/qq_20599123/article/details/39227527
'''
from math import sqrt
import numpy as np
import matplotlib.pyplot as plt

def loadData():
	trainSet = {}
	testSet = {}
	movieUser = {}
	u2u = {}

	TrainFile = 'D:\train\train.txt' # 指定训练集
	TestFile = 'D:\test\test.txt'  # 指定测试集

	# 加载训练集,生成电影用户的倒排序表 movieUser
	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())

	# 防止测试集中没有训练集中出现过的项目
	item_in_train = []
	for m in movieUser.keys():
		item_in_train.append(m)


	# 加载测试集
	for line in open(TestFile):
		(userId, itemId, rating, timestamp) = line.strip().split('\t')
		testSet.setdefault(userId,{})
		testSet[userId].setdefault(itemId, float(rating))

	return trainSet, testSet, movieUser, item_in_train

	# 生成用户共有电影矩阵
	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 getAverangeRating(user):
	averange = (sum(trainSet[user].values()) * 1.0)/len(trainSet[user].keys())
	return averange
'''
# 计算用户相似度
def getUserSim(u2u, trainSet):
	userSIm = {}
	# 计算用户的相似度
	for u in u2u.keys(): # 对每个用户u
		userSim.setdefault(u, {}) #将用户u加入userSim中设为key,该用户对应一个字典
		average_u_rate = getAverangeRating(u) # 获取用户u对电影的平均评分
		for n in u2u[u].keys(): #对于用户u相关的每个用户n
			userSim[u].setdefault(n, 0) #将用户n加入用户u的字典中

			average_n_rate =  getAverangeRating(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 getUserSim(u2u, 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(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]
					avg_u1_m = sigma_u1_m / len(co_rated)
					avg_u2_m = sigma_u2_m / len(co_rated)

					for m in co_rated:
						num += (trainSet[u1][m] - avg_u1_m) * (trainSet[u2][m] - avg_u2_m) * 1.0
						den1 += pow(trainSet[u1][m] - avg_u1_m) * 1.0
						den2 += pow(trainSet[u2][m] - avg_u2_m) * 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 = lamnda x:x[1], reverse = True)
		for key, value in neigh_sorted:
			neighSorted.setdefault(u, [])
			neighSorted[u].append(key)
	return neighSorted

'''
# 寻找最近邻并生成推荐结果
def getRecommendations(N, trainSet, userSim):
	pred = {}
	for user in trainSet.keys():
		pred.setdefault(user, {}) # 对每个用户生成预测空列表
		interacted_items = trainSet[user].keys() # 获取该用户评过分的电影
		average_u_rate = getAverangeRating(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 = getAverangeRating(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 getAccuracyMetric(N, trainSet, testSet, movieUser, neighSorted, userSim, item_in_train):
	# 寻找最近邻用户并生成推荐结果
	pred = {}
	for user, item in testSet.items(): #对每个用户做测试
		pred.setdefault(user, {}) # 生成用户user的预测空列表
		avg_u_rating = getAverangeRating(user)
		neigh_uninterseced = neighSorted[user] #获取用户user的邻居集合(已按相似度大小降序排列)
		for m in item.keys():
			if m not in item_in_train:
				pred[user][m] = avg_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] - getAverangeRating(neighUser))
					neighSimSum += abs(userSim[user][neighUser])
				if neighSimSum == 0:
					pred[user][m] = avg_u_rating
				else:
					pred[user][m] = avg_u_rating + (neighRating * 1.0) / neighSimSum
'''
	## 与测试集比较获得算法的准确度
	mae = 0
	rmse = 0
	erro_sum = 0
	sqrError_sum = 0
	setSum = 0
	for user, item in pred.items():
		for m in item.keys():
			erro_sum += abs(pred[user][m] - testSet[user][m])
			sqrError_sum += power(pred[user][m] - testSet[user][m], 2)
			setSum += 1
	mae = erro_sum / setSum
	rmse = sqrt(sqrError_sum / setSum)
	return mae, rmse
'''

# 计算预测分析准确度
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 'loading data...'
	trainSet, testSet, movieUser, item_in_train = loadData()

## print '正在计算用户间相似度...'
## userSim = UserSimPerson(trainSet)
	print '对相似度列表按相似度大小进行排列...'
	neighSorted = sortSimMatrix(userSim)

	print '正在寻找最近邻...'
	NeighborSize = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
	MAE = []
	MASE = []
	for N in NeighborSize:
		mae, rmse = getAccuracyMetric(N, trainSet, testSet, movieUser, neighSorted, userSim, item_in_train)  # 获取算法推荐精准度
		MAE.append(mae)
		MASE.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('按任意键继续...')'''



结果如下:

协同过滤:基于用户的协同过滤itemCF_第2张图片
协同过滤:基于用户的协同过滤itemCF_第3张图片


你可能感兴趣的:(协同过滤:基于用户的协同过滤itemCF)