推荐算法-基于协同过滤的推荐算法
在如今信息量呈爆炸式增长的时代,谷歌百度等搜索引擎为人们查找信息提供了便利,帮助用户快速查找有价值的信息。然而此类查询方式是大众化的,无法根据个人兴趣为用户展示相关的信息,此外搜索引擎要求用户给出明确的信息需求或者是关键字。推荐系统则用于提供个性化的信息,可通过分析用户行为或社交关系在用户需求不明确的情况下为用户提供信息。传统的推荐算法包括基于内容的推荐算法和基于协同过滤的推荐算法以及混合推荐。这篇博客主要是对协同过滤算法进行介绍,进一步的,协同过滤又可分为基于用户和基于物品两类。
基于用户的协同过滤推荐算法代码借鉴于Danni_hgc的电影推荐算法实例代码
基于物品的推荐算法代码主要借鉴于海天一树X
基于用户的协同过滤推荐算法
基本思想
基于用户的协同过滤推荐算法的基本思想就是根据用户朋友的兴趣爱好来推测用户的兴趣爱好,在日常生活中,当你的朋友看了某部电影时,往往会将这部电影分享给你,而你去观看这部电影的概率也会高于陌生人为你推荐这部电影(除电影本来就有很高的热度以外)。简而言之,可以说相似的用户具有相同的兴趣。基于用户的协同过滤方法可分为两个步骤:
1.计算用户与目标用户之间的相似度。
2.将与目标用户兴趣相似的用户喜欢且目标用户未接触的物品推荐给目标用户。
可以通过计算余弦相似度,或者是杰卡德相似系数等方式来计算用户之间的相似度,设 N(u) 为用户 u 喜欢的物品集合,N(v) 为用户 v 喜欢的物品集合,Wuv表示用户u和用户v之间的相似度:
1)余弦相似度
2)杰卡德相似系数
计算余弦相似度的代码:
def calcCosDistSpe(user1,user2):
avg_x=0.0
avg_y=0.0
for key in user1:
avg_x+=key[1]
avg_x=avg_x/len(user1)
for key in user2:
avg_y+=key[1]
avg_y=avg_y/len(user2)
u1_u2=0.0
for key1 in user1:
for key2 in user2:
if key1[1] > avg_x and key2[1]>avg_y and key1[0]==key2[0]:
u1_u2+=1
u1u2=len(user1)*len(user2)*1.0
sx_sy=u1_u2/math.sqrt(u1u2)
return sx_sy
推荐物品,计算出用户相似度之后,需要找出与用户最相似的k个邻居,并将k个邻居看过且用户感兴趣程度较高的电影(除开用户看过的电影)推荐给用户。
寻找用户最相似的k个邻居代码如下:
def calcNearestNeighbor(userid,users_dic,item_dic):
neighbors=[]
#neighbors.append(userid)
for item in users_dic[userid]:
for neighbor in item_dic[item[0]]:
if neighbor != userid and neighbor not in neighbors:
neighbors.append(neighbor)
neighbors_dist=[]
for neighbor in neighbors:
dist=calcCosDistSpe(users_dic[userid],users_dic[neighbor])
neighbors_dist.append([dist,neighbor])
neighbors_dist.sort(reverse=True)
return neighbors_dist
算法实现
数据介绍
数据:mk-100k
u.data内容
user id movie id 评分 其他
196 242 3 881250949
186 302 3 891717742
22 377 1 878887116
244 51 2 880606923
166 346 1 886397596
u.item内容
movie_id movie_name release
1|Toy Story (1995)|01-Jan-1995|
2|GoldenEye (1995)|01-Jan-1995|
3|Four Rooms (1995)|01-Jan-1995|
完整代码
# -*- coding=utf-8 -*-
import math
from texttable import Texttable
# 读取文件
def readFile(file_name):
contents_lines=[]
f=open(file_name,"r",encoding='ISO-8859-1')#The encoding was "ISO-8859-1"
contents_lines=f.readlines()
f.close()
return contents_lines
#
# 解压rating信息,格式:用户id\t电影id\t用户rating\t时间
# 输入:数据集合
# 输出:已经解压的排名信息
#
def getRatingInformation(ratings):
rates=[]
for line in ratings:
rate=line.split("\t")
rates.append([int(rate[0]),int(rate[1]),int(rate[2])])
return rates
#
# 生成用户评分的数据结构
#
# 输入:所有数据 [[2,1,5],[2,4,2]...]
# 输出:1.用户打分字典 2.电影字典
# 使用字典,key是用户id,value是用户对电影的评价,
# rate_dic[2]=[(1,5),(4,2)].... 表示用户2对电影1的评分是5,对电影4的评分是2
#
def createUserRankDic(rates):
user_rate_dic={}
item_to_user={}
for i in rates:
user_rank=(i[1],i[2])#电影id和评分
if i[0] in user_rate_dic:
user_rate_dic[i[0]].append(user_rank)
else:
user_rate_dic[i[0]]=[user_rank]
if i[1] in item_to_user:
item_to_user[i[1]].append(i[0])
else:
item_to_user[i[1]]=[i[0]]
return user_rate_dic,item_to_user
# 使用UserFC进行推荐
# 输入:文件名,用户ID,邻居数量
# 输出:推荐的电影ID,输入用户的电影列表,电影对应用户的反序表,邻居列表
#
def recommendByUserFC(file_name,userid,k=5):
#读取文件数据
test_contents=readFile(file_name)
#文件数据格式化成二维数组 List[[用户id,电影id,电影评分]...]
test_rates=getRatingInformation(test_contents)
#格式化成字典数据
# 1.用户字典:dic[用户id]=[(电影id,电影评分)...]
# 2.电影字典:dic[电影id]=[用户id1,用户id2...]
test_dic,test_item_to_user=createUserRankDic(test_rates)
#寻找邻居
neighbors=calcNearestNeighbor(userid,test_dic,test_item_to_user)[:k]
recommend_dic={}
for neighbor in neighbors:
neighbor_user_id=neighbor[1]
movies=test_dic[neighbor_user_id]
for movie in movies:
#print movie
if movie[0] not in recommend_dic:
recommend_dic[movie[0]]=neighbor[0]
else:
recommend_dic[movie[0]]+=neighbor[0]
#print len(recommend_dic)
#建立推荐列表
recommend_list=[]
for key in recommend_dic:
#print key
recommend_list.append([recommend_dic[key],key])
recommend_list.sort(reverse=True)
#print recommend_list
user_movies = [ i[0] for i in test_dic[userid]]
return [i[1] for i in recommend_list],user_movies,test_item_to_user,neighbors
# 获取电影的列表
def getMoviesList(file_name):
#print sys.getdefaultencoding()
movies_contents=readFile(file_name)
movies_info={}
for movie in movies_contents:
movie_info=movie.split("|")
movies_info[int(movie_info[0])]=movie_info[1:]
return movies_info
#主程序
#输入 : 测试数据集合
if __name__ == '__main__':
movies=getMoviesList("D:/PycharmProject/recomender system/data/ml-100k/u.item")
recommend_list,user_movie,items_movie,neighbors=recommendByUserFC("D:/PycharmProject/recomender system/data/ml-100k/u.data",305,80)
# 推荐列表(电影,用户相似度),用户看的所有电影,看某电影的所有用户,用户邻居(相似度,邻居id)
neighbors_id=[ i[1] for i in neighbors]
table = Texttable()
table.set_deco(Texttable.HEADER)
table.set_cols_dtype(['t', # text
't', # float (decimal)
't']) # automatic
table.set_cols_align(["l", "l", "l"])
rows=[]
rows.append([u"movie name",u"release", u"from userid"])
for movie_id in recommend_list[:20]:#寻找与推荐项目相关的邻居用户
from_user=[]
for user_id in items_movie[movie_id]:
if user_id in neighbors_id:
from_user.append(user_id)
rows.append([movies[movie_id][0],movies[movie_id][1],from_user[0:5]])#只显示与推荐结果相关的5个邻居用户
table.add_rows(rows)
print (table.draw())
运行结果
from userid表示这些用户都看过这些电影,为了结果的美观,只打印了五个用户id。
上述计算用户相似度时仅仅考虑到用户与项目之间的关系,而忽略了外在因素。比如最近新上映了两部大导演制作的电影,该导演具有很高的知名度且作品宣传力度大,因此很多用户都会选择去看这两部电影,此时根据用户A和用户B都观看了电影,就简单的认为两个用户相似比较局限。
基于物品的协同过滤推荐算法
基本思想
基于物品的推荐算法主要是根据用户的历史行为信息来推测用户未来的兴趣,将基于物品的协同过滤推荐算法应用于电影推荐中,最基本的思想就是为用户推荐最近看过的电影列表中相似的电影。该方法也可分为计算物品相似度和物品推荐两个步骤,计算物品相似度的方法与计算用户相似度的方法类似。
算法实现
基于物品的协同过滤算法使用的数据集同上,不再作详细的介绍。
1.获取电影的列表
def getMoviesList(file_name):
#print sys.getdefaultencoding()
movies_contents=readFile(file_name)
movies_info={}
for movie in movies_contents:
movie_info=movie.split("|")
movies_info[int(movie_info[0])]=movie_info[1:]
return movies_info
2.读取数据
def readData(self):
# 读取文件,并生成用户-物品的评分表和测试集
self.train = dict()
# 用户-物品的评分表
for line in open(self.train_file):
user, item, score,other = line.strip().split("\t")
self.train.setdefault(user, {})
self.train[user][item] = int(float(score))
3.3.计算物品相似度
def ItemSimilarity(self):
# 建立物品-物品的共现矩阵
cooccur = dict() # 物品-物品的共现矩阵
buy = dict() # 物品被多少个不同用户购买N
for user, items in self.train.items():
for i in items.keys():
buy.setdefault(i, 0)
buy[i] += 1
cooccur.setdefault(i, {})
for j in items.keys():
if i == j: continue
cooccur[i].setdefault(j, 0)
cooccur[i][j] += 1
# 计算相似度矩阵
self.similar = dict()
for i, related_items in cooccur.items():
self.similar.setdefault(i, {})
for j, cij in related_items.items():
self.similar[i][j] = cij / (math.sqrt(buy[i] * buy[j]))
return self.similar
4.给用户做推荐
给用户user推荐,前K个相关用户,前N个物品
def Recommend(self, user, K=3, N=10):
rank = dict()
action_item = self.train[user]
# 用户user产生过行为的item和评分
for item, score in action_item.items():
sortedItems = sorted(self.similar[item].items(), key=lambda x: x[1], reverse=True)[0:K]
for j, wj in sortedItems:
if j in action_item.keys():
continue
rank.setdefault(j, 0)
rank[j] += score * wj
return dict(sorted(rank.items(), key=lambda x: x[1], reverse=True)[0:N])
5.创建表格,显示推荐结果
table = Texttable()
table.set_deco(Texttable.HEADER)
table.set_cols_dtype(['t', # text
't']) # automatic
table.set_cols_align(["l", "l"])
rows=[]
rows.append([u"movie name",u"release"])
6.main()函数
movies=getMoviesList("D:/PycharmProject/recomender system/data/ml-100k/u.item")#得到电影Item的信息
item = ItemBasedCF("D:/PycharmProject/recomender system/data/ml-100k/u.data")
item.ItemSimilarity()
recommedDict = item.Recommend("166")
for movieid in recommedDict:
movieid=int(movieid)
rows.append([movies[movieid][0], movies[movieid][1]])
table.add_rows(rows)
print(table.draw())
结果展示
结论
基于物品的推荐算法可以预先计算所有物品之间的相似度,对于在线实验来说,基于物品的协同过滤推荐算法优于基于用户的协同过滤算法,但二者都面临着数据稀疏性和冷启动问题。此外,传统的这两类算法的推荐覆盖率和多样性低,难以解决长尾问题。长尾理论中的头部代表的是绝大部分用户的日常需求,尾部则代表少数用户的个性化需求。