基于聚类的推荐算法笔记——以豆瓣电影为例(三)(附源代码)
第一章 聚类算法介绍
基于聚类的推荐算法笔记一
第二章 数据介绍
基于聚类的推荐算法笔记二
第三章 实现推荐算法
基于聚类的推荐算法笔记三
第四章 评价推荐算法
基于聚类的推荐算法笔记四
前言
本文记载一下本科毕设所研究的课题步骤以及一些细节,由于此次毕设对于推荐领域很感兴趣,发表一些浅显见解,希望大佬们不吝赐教。
将用户划分到不同簇内后,在每簇内进行协同过滤计算用户间相似度找到最近邻,实验中使用基于用户的协同过滤推荐。
一、协同过滤推荐
最早诞生的推荐算法就是协同过滤,且应用广泛。主要功能是预测和推荐,算法通过对用户历史记录行为数据的挖掘发现用户的偏好,基于不同偏好对用户进行组群划分并推荐品味相似的商品。协同推荐算法分为两类,分别是基于用户的协同过滤算法,和基于物品的协同过滤算法。
协同过滤的推荐算法不需要领域知识就可以发现新兴趣,并且随着时间的推移性能逐渐提高,能够处理非结构化对象。但是大部分应用会面临矩阵的稀疏问题,并且可拓展性差,新用户则得不到准确推荐。
1.1 基于用户的协同过滤推荐
基于用户的协同过滤算法是通过用户的历史行为数据发现用户对目标物的喜欢,并对其进行评分、评论等。根据不同用户对相同目标物的态度和偏好计算用户间的关系。在有相同喜好的用户间进行目标物推荐。目前的研究是选取若干用户的历史数据生成评分矩阵,然后计算相似度之后选取最近邻。假如用户A和B有相似的行为,可以通过计算相似度来说明用户A和B是相似用户,然后将A用户有记录且B用户没记录的观影行为推荐给B用户,从而达到推荐的目的。
1.2 基于物品的协同过滤推荐
基于物品的协同过滤算法就是将基于用户协同过滤中用户-目标物的关系颠倒,基于物品间的关系对用户进行相似物品推荐,假如用户A评论了电影a和b,这样说明电影a和b的相关度较高,这样当用户B也评论了电影a时,就可以推断出他可能会喜欢电影b,从而达到个性化推荐目的。
1.3 遇到的问题
在计算用户间相似度矩阵时,矩阵很稀疏,过程中很多都不需要计算。
例如:稀疏矩阵(用户ABC观看电影abc,观看为1,未观看为0)
A | B | C | D | |
---|---|---|---|---|
a | 1 | 1 | 0 | 0 |
b | 1 | 0 | 1 | 0 |
c | 0 | 1 | 0 | 1 |
d | 1 | 0 | 0 | 1 |
e | 0 | 0 | 1 | 1 |
利用余弦相似度公式计算:
结果所得:
A | B | C | D | |
---|---|---|---|---|
A | 1/根号6 | 1/根号6 | 1/3 | |
B | 1/根号6 | 0 | 1/根号6 | |
C | 1/根号6 | 0 | 1/根号6 | |
D | 1/3 | 1/根号6 | 1/根号6 |
为了方便计算,参考项亮《推荐系统实践》中方法,建立倒排表,先计算出用户uv交集不等于0的用户对,然后再除以分母。
首先用户-电影矩阵为:
A->a、b、d
B->a、c
C->b、e
D->c、d、e
将其倒排:
a->A、B
b->A、C
c->B、D
d->A、D
e->C、D
遍历倒排表,在矩阵C中C[u][v]和C[v][u]中加一:
A | B | C | D | |
---|---|---|---|---|
A | 0 | 1 | 1 | 1 |
B | 1 | 0 | 0 | 1 |
C | 1 | 0 | 0 | 1 |
D | 1 | 1 | 1 | 0 |
表中数字为余弦公式分子部分,最后除以分母得到余弦相似度
A | B | C | D | |
---|---|---|---|---|
A | 1/根号6 | 1/根号6 | 1/3 | |
B | 1/根号6 | 0 | 1/根号6 | |
C | 1/根号6 | 0 | 1/根号6 | |
D | 1/3 | 1/根号6 | 1/根号6 |
计算得到相似度矩阵W,找到k个最近邻,得到最近邻用户的观影列表,对该观影列表计算每部电影的兴趣值P,从大到小排列得到目标用户的推荐列表。
表示用户u对电影i的喜好程度,S(u,k)表示用户u的k个最近邻,N(i))表示看过电影i的用户,也就是说用户v属于看过电影i且和用户u为最近邻的集合中,找到Wuv乘上用户v对电影i的喜好程度(可以是评分,本实验中设为1),累计求和最终得到用户u对电影i的喜好程度。
1.4 具体实现
def Usersim(dicc):#传入用户-电影字典,键为用户id,值为看过的电影名列表
# 把用户-电影字典转成电影-用户字典
item_user = dict()#创建一个字典
#建立倒排表
for u, items in dicc.items():
for i in items: # 不带评分的,所以用的是元组而不是嵌套字典。
if i not in item_user.keys():
item_user[i] = set() # i键所对应的值是一个集合(不重复)。
item_user[i].add(u) # 向集合中添加用户。
#print(item_user)
#item_user是倒排表,电影-用户字典
C = dict() # 这里是字符,这边还用字典。C是余弦相似度中的分子部分,u,v的交集
N = dict() #N的键是电影值是用户数量
for item, users in item_user.items():
for u in users:
if u not in N.keys():
N[u] = 0 # 设定字典初始值
N[u] += 1 # 每个电影下用户出现一次就加一次,就是计算每个用户一共观看电影次数。
for v in users:
if u == v:
continue
if (u, v) not in C.keys(): # 同上,没有初始值不能+=
C[u, v] = 0
C[u, v] += 1 #存用户u和用户v的交集数量
# 到这里倒排阵就建立好了,下面是计算相似度。
W = dict() #存用户间相似度
for co_user, cuv in C.items(): #co_user是[u,v]
W[co_user] = cuv / math.sqrt(N[co_user[0]] * N[co_user[1]]) #cuv是用户u和用户v的交集数量,余弦相似度计算方法
return W
def Recommend(user, dicc, W2, K):#传入用户列表,用户-电影字典、相似度矩阵,最近邻数量
rvi = 1 # 这里都是1
rank = dict()
related_user = []
interacted_items = dicc[user]#得到用户的观影列表
for co_user, item in W2.items():
if co_user[0] == user:
related_user.append((co_user[1], item)) # 先建立一个和待推荐用户兴趣相关的所有的用户列表。
for v, wuv in sorted(related_user, key=itemgetter(1), reverse=True)[0:K]:
# 找到K个相关用户以及对应兴趣相似度,按兴趣相似度从大到小排列。itemgetter要导包。
for i in dicc[v]: #找到相关用户v的观影列表,遍历每一部电影
if i in interacted_items: #观看过该部电影,跳过
continue
if i not in rank.keys():
rank[i] = 0 #电影推荐列表中没有该部电影,记为0
rank[i] += wuv * rvi #记录该部电影的推荐值,wuv是用户u和用户v之间的相似度,rvi表示用户v对电影i的喜好程度为1,公式见论文
return rank
总结
在不同簇内计算用户相似度降低了矩阵运算的时间复杂度,同时将用户更直观的分类。