目录
兴趣度计算
前面学习了关联规则的相关知识,接下来我们来学习协同过滤。
什么是协同过滤呢?我们以一个简单的问题来了解一下:如果你现在想看个电影,但你不知道具体看哪部,你会怎么做?
大部分的人会问问周围的朋友,看看最近有什么好看的电影推荐。朋友有很多,每一个都去问吗?
不是,我们一般更倾向于从口味比较类似的朋友那里得到推荐。
协同过滤是利用集体智慧的一种方法,但又稍有不同,它保留了个体的特征。简单来说是利用兴趣相投的、拥有共同经验的群体的喜好来向用户进行推荐他感兴趣的东西。
从这里我们可以发现协同过滤的核心问题就是:如何找到用户的兴趣?如何找到兴趣相投的其他用户?
协同过滤算法是最早也是最著名的推荐方法,主要有两类:基于用户的协同过滤算法(user-based collaboratIve filtering),和基于物品的协同过滤算法(item-based collaborative filtering)。简单的说就是:人以类聚,物以群分。
总结来说,协同过滤推荐分为三步:
第一步:发现用户的偏好
第二步:找到相似的用户或物品
第三步:计算推荐
电影推荐案例
接下来我们就以一个案例来了解一下基于用户的系统过滤:
假设有A-G,7个人分别看了如图所示的电影并且给电影有如下评分(5分最高,没看过的不评分),我们目的是要向A用户推荐一部电影
首先我们来理一下思路:我们要基于A看过的电影,向A推荐他未看过的电影
协同过滤算法
协同过滤的整体思路只有两步,非常简单:寻找相似用户邻居,用相似邻居偏好来推荐物品
寻找相似用户,有很多种方法可以用来判断相似性,,其实是对于电影品味的相似,也就是说需要将A与其他几位用户做比较,判断是不是品味相似。
用我们刚刚讲过的协同过滤的三步来分析这个问题,首先我们根据用户A的评分发现,他偏爱像老炮儿这样性质的电影;然后我们要根据他的爱好找到类似用户?如何寻找类似用户呢?如何找到跟用户A一样偏爱老炮儿这种电影的用户呢?从数学的角度出发,我们是不是可以理解为计算两个用户之间的相似度,越相似,证明他们口味越接近。
那么问题来了,相似度计算方式很多,我们采用什么样的相似度计算方法呢?
通过相似度的计算算法找出相似用户、物品
常见的相似度计算算法有:欧几里德距离,余弦相似度,皮尔逊相关系数,杰卡德相似系数等
很多同学会说采用效果最好的相似度计算方法,那怎么知道那种相似度计算方法最好呢?这需要大家在学习和使用过程中去分辨。
欧式距离案例
这里我们使用“欧式距离”来作为相似度计算方法。把每一部电影看成N维空间中的一个维度,这样每个用户对于电影的评分相当于维度的坐标,那么每一个用户的所有评分,相当于就把用户固定在这个N维空间的一个点上,然后利用欧几里德距离计算N维空间两点的距离。距离越短说明品味越接近。
在本例中用户A只看过两部电影(《老炮儿》和《唐人街探案》),那我们只能通过这两部电影来判断用户A的品味了,也就是说我们要计算A和其他几位用户的欧式距离
前面我们说过欧式距离越近,越相似。对相似度的衡量我们习惯用[0,1]表示,1表示完全一样,0表示完全不一样。
当相似度为1时,我们可以理解为此时欧式距离为0;当相似度为0时,我们可以理解为此时欧式距离为无穷大
因此,欧式距离要与相似性要做一个转换,变换方法为:相似性 = 1/(1+欧几里德距离)
通过计算我们的出用户A与其他用户的相似度分别为:
B:0.27,C:0.28,D:0.27,E:0.50,F:0.25,G:0.47
从这些数据中我们可以看出E用户与A用户最为相似,相似度为50%,其次是G用户,相似度为47%
计算推荐
接下来我们就要进行协同过滤推荐的第三步:计算推荐。
如何计算推荐呢?我们把用户的相似度看作一个电影评分的一个加权相似,对A未看过的电影计算其他电影的加权评分,从而判别用户A会感兴趣的电影。
我们将用户A与其他用户的相似度以及其他用户对A未看过的电影分别看作两个矩阵,计算其他电影的加权评分就是计算两个矩阵对应位置的数的乘积。
除了加权,还要做少量的计算:总分是每个电影加权评分的总和,总相似度是对这个电影有评分的人的相似性综合,推荐度是总分/总相似性,目的是排除看电影人数对于总分的影响
最后一行就是电影的推荐度(因为是根据品味相同的人打分加权算出的分,可以近似认为如果A看了这部电影,预期的评分会是多少)
有了电影的加权得分,通常做法还要设定一个阈值,如果超过了阈值再给用户推荐,要不怎么推荐都是烂片,如果这里我们设置阈值为4,那么最终推荐给A的电影就是《寻龙诀》。
如果我们把一开始的评分表的行列调换,其他过程都不变,那么就变成了把电影推荐给合适的受众。因此,要根据不同场景选择不同的思考维度。
基于用户协同过滤的缺点
基于用户的协同过滤算法,一般应用于用户较少,物品比较多,时效性比较强的场合,比如新闻推荐等
代码实操
# -*- coding: utf-8 -*-
"""
Created on Mon Mar 7 09:52:09 2022
@author: Administrator
"""
import numpy as np
import pandas as pd
from math import sqrt
#%% 字典{user:{item:rating}}
critics = {
'A': {'老炮儿':3.5,'唐人街探案': 1.0},
'B': {'老炮儿':2.5,'唐人街探案': 3.5,'星球大战': 3.0, '寻龙诀': 3.5,
'神探夏洛克': 2.5, '小门神': 3.0},
'C': {'老炮儿':3.0,'唐人街探案': 3.5,'星球大战': 1.5, '寻龙诀': 5.0,
'神探夏洛克': 3.0, '小门神': 3.5},
'D': {'老炮儿':2.5,'唐人街探案': 3.5,'寻龙诀': 3.5, '神探夏洛克': 4.0},
'E': {'老炮儿':3.5,'唐人街探案': 2.0,'星球大战': 4.5, '神探夏洛克': 3.5,
'小门神': 2.0},
'F': {'老炮儿':3.0,'唐人街探案': 4.0,'星球大战': 2.0, '寻龙诀': 3.0,
'神探夏洛克': 3.0, '小门神': 2.0},
'G': {'老炮儿':4.5,'唐人街探案': 1.5,'星球大战': 3.0, '寻龙诀': 5.0,
'神探夏洛克': 3.5}
}
print(critics['A'])
df = pd.DataFrame(critics)
pcu = df.corr()
pci = df.T.corr()
#df = pd.DataFrame(critics)
#%%
#加载数据
def ratings_pivot_table():
ratings_pivot = pd.pivot_table(ratings_data,index="userId",columns="movieId",values="rating")
ratings_pivot = ratings_pivot.fillna(0)
return ratings_pivot
ratings_data = pd.read_csv(".\\movie_data\\ratings2.dat")
ratings_data.columns=["userId","movieId","rating","time"]
#print(ratings_data.describe())
ratings_pivot = ratings_pivot_table()
ratingdict = ratings_pivot.T.to_dict()
#%% 计算相似性 方法一:欧氏距离
from math import sqrt
def sim_distance(dict_user_item_rating, user1, user2):
si = {} # 看过的相同的电影片名的字典
x = dict_user_item_rating[user1] # 先获取用户1的字典
y = dict_user_item_rating[user2] # 先获取用户2的字典
for item in x: # 从每一个用户的评分字典 {'老炮儿': 3.5, '唐人街探案': 1.0} 中获取片名
if x[item]<=0: continue
if item in y and y[item]>0: si[item] = 1 # 表示这个电影 user2也看过
# 如果user2没有看过和user1相同的电影
if len(si) == 0: return 0 # 不能计算距离
# 计算欧式距离
sum_of_squares = sum([pow(x[item] - y[item], 2) for item in x if item in y])
distance = sqrt(sum_of_squares)
# 计算基于欧式距离的相似度
sim = 1 / (1 + distance)
return sim
import numpy as np
# sim_dict = {}
# for user in critics.keys():
# if user!="A":
# sim = sim_distance(critics, 'A', user)
# #print(f"相似度:A-{user}",round(sim_distance(critics, 'A', user),2))
# sim_dict[user] = sim
# print("欧氏距离相似度:")
# print(sim_dict)
# 皮尔逊相关度
def sim_pearson(prefs, p1, p2):
si = {}
for item in prefs[p1]:
if item in prefs[p2]: si[item] = 1
if len(si) == 0: return 0
n = len(si) # N
# 计算开始
sum1 = sum([prefs[p1][it] for it in si]) # X 的和
sum2 = sum([prefs[p2][it] for it in si]) # Y 的和
sum1Sq = sum([pow(prefs[p1][it], 2) for it in si]) # X平方的和
sum2Sq = sum([pow(prefs[p2][it], 2) for it in si]) # Y平方的和
pSum = sum([prefs[p1][it] * prefs[p2][it] for it in si]) # XY的和
num = pSum - (sum1 * sum2 / n) # 分子 : XY的和 - X的和乘Y的和/N
den = sqrt((sum1Sq - pow(sum1, 2) / n) * (sum2Sq - pow(sum2, 2) / n)) # 分母
# 计算结束
if den == 0: return 0
r = num / den
return r
print("皮尔逊相关系数:")
sim_dict = {}
for user in critics.keys():
if user!="A":
sim = sim_pearson(critics, 'A', user)
#print(f"相似度:A-{user}",round(sim_distance(critics, 'A', user),2))
sim_dict[user] = sim
# print(sim_dict)
# Gets recommendations for a person by using a weighted average
# of every other user's rankings
def getRecommendations(prefs, person, similarity=sim_distance):
totals = {}
simSums = {}
for other in prefs:
# don't compare me to myself
if other == person: continue
sim = similarity(prefs, person, other)
# ignore scores of zero or lower
if sim <= 0: continue
for item in prefs[other]: # {'老炮儿':2.5,'唐人街探案': 3.5,'星球大战': 3.0, '寻龙诀': 3.5,'神探夏洛克': 2.5, '小门神': 3.0}
# only score movies I haven't seen yet
if item not in prefs[person] or prefs[person][item] == 0:
# Similarity * Score
totals.setdefault(item, 0)
totals[item] += prefs[other][item] * sim
# Sum of similarities
simSums.setdefault(item, 0)
simSums[item] += sim
# Create the normalized list
rankings = [(total / simSums[item], item) for item, total in totals.items()] # {movie:total_rating}
# Return the sorted list
rankings.sort()
rankings.reverse()
return rankings
print("使用欧氏距离计算相似度推荐电影:")
print(getRecommendations(ratingdict, 20, similarity=sim_distance))
#print("使用皮尔逊相关系数计算相似度推荐电影:")
#print(getRecommendations(critics, 'B', similarity=sim_pearson))
# -*- coding: utf-8 -*-
"""
Created on Fri Mar 11 16:33:22 2022
@author: Administrator
"""
from math import sqrt
import numpy as np
import pandas as pd
#%% 数据模型
critics = {
'A': {'老炮儿':3.5,'唐人街探案': 1.0},
'B': {'老炮儿':2.5,'唐人街探案': 3.5,'星球大战': 3.0, '寻龙诀': 3.5,
'神探夏洛克': 2.5, '小门神': 3.0},
'C': {'老炮儿':3.0,'唐人街探案': 3.5,'星球大战': 1.5, '寻龙诀': 5.0,
'神探夏洛克': 3.0, '小门神': 3.5},
'D': {'老炮儿':2.5,'唐人街探案': 3.5,'寻龙诀': 3.5, '神探夏洛克': 4.0},
'E': {'老炮儿':3.5,'唐人街探案': 2.0,'星球大战': 4.5, '神探夏洛克': 3.5,
'小门神': 2.0},
'F': {'老炮儿':3.0,'唐人街探案': 4.0,'星球大战': 2.0, '寻龙诀': 3.0,
'神探夏洛克': 3.0, '小门神': 2.0},
'G': {'老炮儿':4.5,'唐人街探案': 1.5,'星球大战': 3.0, '寻龙诀': 5.0,
'神探夏洛克': 3.5}
}
#print(critics['B']['星球大战'])
import pandas as pd
df = pd.DataFrame(critics)
df.to_dict(orient='index')
#%% 相似度计算函数
def sim_distance(prefs, p1, p2):
# prefs: 用户偏好数据字典 {用户:{物品:评分}}
# p1: 用户1
# p2: 用户2
r1 = prefs[p1]
r2 = prefs[p2]
si = {} # 存放用户1和用户2共同评价过的物品字典
for item in r1: # 对于每一个用户1评价过的物品
if item in r2: # 如果这个物品用户2也评价过
si[item] = 1 # 这个物品就被记录下来
# 如果用户1和用户2没有共同评价过的物品,相似度为0
if len(si) == 0: return 0
# 计算欧式距离
# sum_of_squares = sum([pow(prefs[p1][item] - prefs[p2][item], 2) for item in prefs[p1] if item in prefs[p2]])
# distance = sqrt(sum_of_squares)
distance = sqrt(sum([pow(r1[i]-r2[i],2) for i in si]))
# 计算相似度
sim = 1 / (1 + distance)
# 返回相似度数据
return sim
# for u in critics:
# print(f"欧氏距离相似度 A-{u}:",round(sim_distance(critics, 'A', u),2))
#%% 相似度加权计算推荐分
def getRecommendations(prefs, person, similarity=sim_distance):
# prefs: 用户偏好数据字典 {用户:{物品:评分}}
# person: 推荐对象用户
# similarity: 相似度计算函数
totals = {}
simSums = {}
for other in prefs: # 对于每一个用户
if other == person: continue # 如果是被推荐者本人,不用计算了
sim = similarity(prefs, person, other) # 计算被推荐者和其他用户的相似度
if sim <= 0: continue # 如果相似度小于等于0,没有交集不计算
for item in prefs[other]: # 对于其他人评价过的每一个物品
if item not in prefs[person] or prefs[person][item] == 0: # 如果这个物品被推荐者没有评价过,才继续推荐
# 计算 加权分 = 相似度 x 用户评分
totals.setdefault(item, 0) # 将这个物品放到totals字典中
totals[item] += prefs[other][item] * sim # 计算这个物品的加权分,并且将加权分合计到totals总分中
# 计算 总相似度 (所有评价过该物品的人和被推荐者相似度的和)
simSums.setdefault(item, 0) # 将这个物品放到simSums总相似度字典中
simSums[item] += sim # 将相似度累加到总相似度中
# 计算推荐分 = 总分 / 总相似分
rankings = [(total / simSums[item], item) for item, total in totals.items()]
# Return the sorted list
rankings.sort()
rankings.reverse() # 按推荐分从大到小排列
return rankings # [(推荐分, 物品1),(推荐分, 物品2),...]
print("对于A用户的推荐度:", getRecommendations(critics, 'A' ))