前面提到,有一些证据表明,用户通常不使用细粒度的区分机制,而是倾向于要不给最高评分要不给最低评分。这种非此即彼的极端评级方式有时可能会导致结果无法使用。本章将考察对协同过滤的调优方法,以便更高效低产生更精确的推荐结果。
显示评级:指用户显示地给出物品的评级结果。如点赞/点差/评分
隐式评级:观察用户的行为来获得结果。如跟踪用户在纽约时报在线上的点击轨迹,对某个用户的点击行为观察几周之后,就能够构建该用户的合理画像(profile),比如,他不喜欢体育新闻但是好像喜欢技术新闻。如果他点击了iPhone的广告,那么或许他对该产品感兴趣。另一种隐式评级来自用户实际的购买结果。
一、显示评级的问题
问题1:用户大都具有惰性,不愿对物品评级
问题2:用户可能撒谎或者只给出部分信息
问题3:用户不会更新其评级结果
隐式评级有哪些问题呢?
可能并不是给自己买,导致用户画像很奇怪
二、隐式数据
网页: 点击指向某个网页的链接
浏览页面的时间
重复的访问
将一个网页指向其他网页
在Hulu上观看的视频
音乐播放器: 用户播放的歌曲
用户跳过的歌曲
某首歌曲播放的次数
无论是显示数据还是隐式数据,第二章介绍的算法都可以适用
三、成功带来的问题
假设你有100万用户,进行一次推荐时需要计算100万次距离计算
所以,基于邻居的推荐系统的最主要缺点是延迟性太差。幸运的是,该问题有办法解决
1、基于用户的过滤(也称为基于内存的协同过滤)
有两个问题:扩展性和稀疏性
2、基于物品的过滤(基于模型的协同过滤)
可以计算出最相似的两件物品
四、调整后的余弦相似度
论文Item-based collaborative filtering recommendation algorithms
(Ru,i - Ru) 指的是用户u给物品i的评分减去用户u对所有物品的评分的平均值。S(i, j)指物品i和物品j之间的相似度、
def computeSimilarity(band1, band2, userRatings):
averages = {}
for (key, ratings) in userRatings.items():
averages[key] = (float(sum(ratings.values()))
/ len(ratings.values()))
num = 0 # numerator
dem1 = 0 # first half of denominator
dem2 = 0
for (user, ratings) in userRatings.items():
if band1 in ratings and band2 in ratings:
avg = averages[user]
num += (ratings[band1] - avg) * (ratings[band2] - avg)
dem1 += (ratings[band1] - avg)**2
dem2 += (ratings[band2] - avg)**2
return num / (sqrt(dem1) * sqrt(dem2))
我们已经得到了相似度矩阵,如果能够利用该矩阵进行预测那就太好了(比如,我想知道David有多喜欢Kacey Musgraves?)
p(u, i) 指用户对物品i的喜欢程度
五、Slope One算法
另一种流行的基于物品过滤的算法是Slop One,主要优点是简洁性,因此它很容易实现
论文Slope One Predictors for online Rating-Based Collaborative Filtering 值得阅读
可以将Slope One看成两部分:
第一部分,事先计算的部分(批处理模式,可以是半夜或任何时间进行),称为每对物品之间的偏差(deviation),可以得到物品偏差构成的数据库
第二部分,利用偏差实际预测,利用加权Slope One算法进行预测
基于Python的实现
def computeDeviations(self):
# for each person in the data:
# get their ratings
for ratings in self.data.values():
# for each item & rating in that set of ratings:
for (item, rating) in ratings.items():
self.frequencies.setdefault(item, {})
self.deviations.setdefault(item, {})
# for each item2 & rating2 in that set of ratings:
for (item2, rating2) in ratings.items():
if item != item2:
# add the difference between the ratings to our
# computation
self.frequencies[item].setdefault(item2, 0)
self.deviations[item].setdefault(item2, 0.0)
self.frequencies[item][item2] += 1
self.deviations[item][item2] += rating - rating2
for (item, ratings) in self.deviations.items():
for item2 in ratings:
ratings[item2] /= self.frequencies[item][item2]
def slopeOneRecommendations(self, userRatings):
recommendations = {}
frequencies = {}
# for every item and rating in the user's recommendations
for (userItem, userRating) in userRatings.items():
# for every item in our dataset that the user didn't rate
for (diffItem, diffRatings) in self.deviations.items():
if diffItem not in userRatings and \
userItem in self.deviations[diffItem]:
freq = self.frequencies[diffItem][userItem]
recommendations.setdefault(diffItem, 0.0)
frequencies.setdefault(diffItem, 0)
# add to the running sum representing the numerator
# of the formula
recommendations[diffItem] += (diffRatings[userItem] +
userRating) * freq
# keep a running sum of the frequency of diffitem
frequencies[diffItem] += freq
recommendations = [(self.convertProductID2name(k),
v / frequencies[k])
for (k, v) in recommendations.items()]
# finally sort and return
recommendations.sort(key=lambda artistTuple: artistTuple[1],
reverse = True)
# I am only going to return the first 50 recommendations
return recommendations[:50]
六、MovieLens数据集
明尼苏达大学GroupLens研究项目所收集的MovieLens数据集包含用户对影片的评分 www.grouplens.org
这里使用了其中最小规模的数据集ml-100k
>>> import recommender3
>>> r = recommender3.recommender(0)
>>> r.loadMovieLens('ml-100k/')
102625
>>> r.showUserTopItems('1', 50)
When Harry Met Sally... (1989) 5
Jean de Florette (1986) 5
Godfather, The (1972) 5
...
>>> r.computeDeviations() #在我的笔记本上大概需要30秒
>>> r.slopeOneRecommendations(r.data['1'])
>>> r.slopeOneRecommendations(r.data['25'])
最后:
1、你可以对MovieLens数据集中的10部影片进行评级,看看Slope One推荐系统会给你推荐什么影片?你是否喜欢
2、实现调整的余弦相似度计算方法,将其性能与Slope One进行比较
3、运行Booking Crossing数据集,dataset有27万本书被评分,因此需要一个270000x270000的字典存储偏差值,这大概需要730亿个字典条目。对于MovieLens数据集,其字典的稀疏度如何?修改代码以便能够处理更大的数据集