基于流行度的推荐是围绕着流行度的计算给出的推荐。那么,如何定义流行度呢?
流行度有很多定义或者计算方法,比如,
- 简单统计一段时间内的物品的购买次数,
- 或者更加复杂的基于概率论的计算方法
无论流行度计算方式如何,影响流行度的两个因素,大概是
- 时间因素,比如,不同时间段内的新闻播报的热度不同,今年和去年流行的衣服不同等等
- 空间因素,比如,位于网站首页的物品和位于多级子页面下的物品流行度不同,不同国家的明星在国内外的关注度也不同
我们将采用movielens数据集,采用最简单的流行度统计方式,即电影的观看总人数作为流行度。
movielens数据集已经上传,各位可以免费下载。
我们的思路是这样的,根据电影流行度定义用户相似度,再让最相似的几个用户对电影投票,按照票数排名,推荐给用户前几名电影。
具体的,
- 定义电影 i i i的流行度 p o p u l a r i t y ( i ) popularity(i) popularity(i)为电影 i i i的观看人数
- 相比于热门电影,如果两个用户同时看了很多冷门电影,那么更能说明这两个用户兴趣相似
- 我们给予流行度低的物品更大的权重,我们可以给出电影
i
i
i的权重
w
e
i
g
h
t
(
i
)
weight(i)
weight(i)为
w
e
i
g
h
t
(
i
)
=
平
均
流
行
度
p
o
p
u
l
a
r
i
t
y
(
i
)
weight(i)=\frac{平均流行度}{popularity(i)}
weight(i)=popularity(i)平均流行度
这里,我们认为低于平均流行度的物品为流行度低,高于平均流行度的为高流行度物品,对于低流行度物品我们给予大于1的权重,对于高流行度的物品我们给予小于1的权重 - 根据电影权重,计算用户 u u u和用户 v v v的相似度 s i m i l a r i t y ( u , v ) similarity(u, v) similarity(u,v),这里,我们说共同看过的电影权重和越大,越说明看过的冷门电影越多,越说明两个用户相似,相似度公式 s i m i l a r i t y ( u , v ) = ∑ i ∈ N ( u ) ∩ N ( v ) w e i g h t ( i ) ∑ i ∈ N ( u ) ∪ N ( v ) w e i g h t ( i ) similarity(u, v)=\frac{\sum_{i\in N(u)\cap N(v)}weight(i)}{\sum_{i\in N(u)\cup N(v)}weight(i)} similarity(u,v)=∑i∈N(u)∪N(v)weight(i)∑i∈N(u)∩N(v)weight(i)
- 根据用户相似度,找出用户 u u u最相似的10名用户
- 让这些用户对所有的电影进行投票,如果看过就投1票,如果没有看过,就不投票,记每个电影的投票结果为 n u m s ( i ) nums(i) nums(i)
- 显然,热门电影的投票结果显然比较高,但是如果热门电影用户没有看过,说明用户可能根本不喜欢热门电影,因此,在推荐结果上,我们还需要考虑流行度的影响,即电影 i i i的得分 s c o r e ( i ) score(i) score(i)为 s c o r e ( i ) = n u m s ( i ) l o g ( 1 + p o p u l a r i t y ( i ) ) score(i)=\frac{nums(i)}{log(1+popularity(i))} score(i)=log(1+popularity(i))nums(i)
- 根据最终得分,取得得分最多的 k k k个电影作为推荐
# 第三方库
import numpy as np
import pandas as pd
# 数据载入
data = pd.read_csv(r'D:\myfile\开课吧\推荐系统\第六节\movielens\ratings.csv', )
data.head()
# 去掉timestamp
data.drop('timestamp', axis=1, inplace=True)
data.head()
# 统计电影流行度popularity
popularity = data.groupby('movieId')['userId'].count()
popularity.head()
# 计算电影权重weight
weight = popularity.apply(lambda x: popularity.mean()/x)
weight.head()
# 所有用户的集合
users = list(data['userId'].unique())
# user_items字典,记录用户看过的电影集合
user_items = {}
for user, group in data.groupby('userId'):
user_items[user] = set(group['movieId'].tolist())
# 计算用户u和用户v的相似度similarity
# 我们仅需要计算 u<v的部分,因为u>v是一样的
similarity = {}
for u in users:
similarity.setdefault(u, {})
for v in users:
if u < v:
total_similarity, intersect_similarity = 0, 0
# 用户u和用户v看过的所有电影集合
total = user_items[u].union(user_items[v])
# 用户u和用户v看过电影集合的交集
intersect = user_items[u].intersection(user_items[v])
for i in total:
similarity_i = weight[i]
total_similarity + similarity_i
if i in intersect:
intersect_similarity += similarity_i
if total_similarity < 1e-6:
similarity[u][v] = 0
else:
similarity[u][v] = intersect_similarity / total_similarity
# 对于用户u,给出最相似的10个用户
def get_tenNeighbors(u):
return list(dic(sorted(similarity[u].items(), key=lambda x: x[1], reverse=True)[:10]).keys())
get_tenNeighbors(1)
# 所有物品集合items
items = list(popularity.index)
# 对于这些用户,计算所有物品的观看次数
def get_nums(u):
item_nums = {}
for v in get_tenNeighbors(u):
for i in items:
item_nums.setdefault(i, 0)
if i in user_items[v]:
item_nums[i] += 1
return item_nums
# 计算所有物品的score
def get_scores(u):
item_nums = get_nums(u)
scores = {}
for i, nums in item_nums.items():
scores[i] = nums / np.log(1 + popularity[i])
return scores
# 对scores进行排序,进行topN排序
def topN(u, N=4):
scores = get_scores(u)
return sorted(scores.items(), key=lambda x: x[1], reverse=True)[:N]
# 测试
topN(1)
由于计算用户之间的相似度非常耗费时间,实际上,这里的复杂度计算也是很高的,复杂度有 O ( n m 2 ) O(nm^2) O(nm2),这里, m m m为用户数, n n n为物品数。
实际上,这里我运行了二十分钟,还是没有跑出结果,原因就是复杂度高了。可能的解决方式是,先将数据量减小,在小的数据集上跑一跑,然后再在原本数据集上跑。