推荐系统二章:推荐系统中的召回算法(1)

学习搜广推主要根据FunRec的教程,面向有机器学习、深度学习基础的搜广推学习笔记。

地址:FunRec

本博客系列所有笔记都是对其中的重点内容进行总结。

如有侵权,请联系我删除。

1. 什么是协同过滤

这里借用原博客的讲解做介绍

协同过滤(Collaborative Filtering)推荐算法是最经典、最常用的推荐算法。基本思想是:

根据用户之前的喜好以及其他兴趣相近的用户的选择来给用户推荐物品。

  • 基于对用户历史行为数据的挖掘发现用户的喜好偏向, 并预测用户可能喜好的产品进行推荐。
  • 一般是仅仅基于用户的行为数据(评价、购买、下载等), 而不依赖于项的任何附加信息(物品自身特征)或者用户的任何附加信息(年龄, 性别等)。

目前应用比较广泛的协同过滤算法是基于邻域的方法,主要有:

  • 基于用户的协同过滤算法(UserCF):给用户推荐和他兴趣相似的其他用户喜欢的产品。

  • 基于物品的协同过滤算法(ItemCF):给用户推荐和他之前喜欢的物品相似的物品。

不管是 UserCF 还是 ItemCF 算法, 重点是计算用户之间(或物品之间)的相似度。

2. 相似度

相似度有很多,这里介绍两种最常用的:余弦距离(Cosine similarity)和皮尔逊相关系数(Pearson coefficient)

2.1 余弦距离

余弦距离是机器学习中最常见的相似度之一了,它度量了两个向量的夹角距离,其公式为

其中都是向量。

2.2 皮尔逊相关系数

皮尔逊相关系数本质上就是标准化的余弦距离,公式如下

其中是向量中的元素,表示向量均值。

现在很多Python库都直接封装有这两个相似度了,这里以scipy为例。

1
2
3
4
5
6
7
8
9
10
11
12
13
from scipy.spatial.distance import cosine
from scipy.stats import pearsonr
import numpy as np

# 计算余弦距离
vec1 = np.array([1, 2, 3])
vec2 = np.array([4, 5, 6])
print(cosine(vec1, vec2))

# 计算皮尔逊系数
x = np.array([1, 2, 3, 4, 5])
y = np.array([5, 4, 3, 2, 1])
print(pearsonr)

3. UserCF与ItemCF

UserCF(基于用户过滤)的核心思想,是通过分析与用户1爱好相似的其他用户2,3,4...,将物品推送到用户1面前。

ItemCF(基于物品过滤)的核心思想,是通过计算物体1与其他物品2,3,4...之间的相似性,将用户喜欢可能喜欢的物品推送到用户面前。

UserCF
ItemCF

3.1 UserCF

例如我们有几个用户对5件商品的评分,现在想知道Alice对物品5的评分是多少,记这个表格为用户-物品评分矩阵

物品1 物品2 物品3 物品4 物品5
Alice 5 3 4 4 ?
用户1 3 1 2 3 3
用户2 4 3 4 3 5
用户3 3 3 1 5 4
用户4 1 5 5 2 1

3.1.1 UserCF算法流程

  1. 计算除了物品5以外的所有物品中,Alice与其他所有用户的相似度矩阵,记为,选择其中最相似的个用户。
  2. 预测Alice对新物品的评分。
  • 预测方法1:直接预测。记为用户对商品的评分,同理,为用户的相似度

  • 预测方法2:考虑到用户评分会有偏置,有的爱打高分有的爱打低分,可以做标准化处理。

其中,是用户对物品的历史均分。

  1. 对所有用户做相同的操作,排序后得到最终结果。

3.1.2 UserCF算法实现

我们可以将上面的表格,除去物品5的第五列,剩下的四列视为一个矩阵,其中每一行都是一个向量

第一步:计算分别的相似度或皮尔逊系数,组成一个新的矩阵

直接调用代码

1
2
3
import numpy as np
data = [[5, 3, 4, 4], [3, 1, 2, 3], [4, 3, 4, 3], [3, 3, 1, 5], [1, 5, 5, 2]]
print(np.corrcoef(data))

输出:

1
2
3
4
5
[[ 1.          0.85280287  0.70710678  0.         -0.79211803]
[ 0.85280287 1. 0.30151134 0.42640143 -0.88662069]
[ 0.70710678 0.30151134 1. -0.70710678 -0.14002801]
[ 0. 0.42640143 -0.70710678 1. -0.59408853]
[-0.79211803 -0.88662069 -0.14002801 -0.59408853 1. ]]

可以看出这个矩阵是对称的,与Alice最相似的用户是用户1和用户2。

第三步:计算Alice对物品5的得分。直接套公式

可得知Alice对物品5打分为4.87分

第四步:对得分排序,很显然将物品5填入表格后并排序后,应该将物品1和物品5推荐给Alice。

代码实践:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import numpy as np

data = np.array(
[
[5, 3, 4, 4, -1], # Alice, 物品5为未知量-1
[3, 1, 2, 3, 3], # user 1
[4, 3, 4, 3, 5], # user 2
[3, 3, 1, 5, 4], # user 3
[1, 5, 5, 2, 1],
]
) # user 4

sim_mat = np.corrcoef(data[:-1, :-1])


# argsort是从小到大,这里需要取反,从大到小
similar_user = np.argsort(-sim_mat[0])

# 最大的一定是1,也就是自己,从第二大开始取
num = 2
similar_user = similar_user[1:num+1]

# 获取相似用户的评分
similar_user_data = np.array([data[i, :] for i in similar_user])

# 获取相似用户的相似度
similar_user_sim = np.array([sim_mat[0, i] for i in similar_user])

# 计算相似用户均值
similar_user_means = [np.mean(similar_user_data[i, :]) for i in range(len(similar_user_data))]


alice_mean = np.mean(data[0,:-1])

#计算公式
score = alice_mean + sum([similar_user_sim[i]*(similar_user_data[i, -1]-similar_user_means[i]) for i in range(len(similar_user))])/sum(similar_user_sim)

print(score)

3.1.3 UserCF优缺点

UserCF存在两个问题:

  1. 数据稀疏

    • 大型推荐系统中物品很多,用户买的占比很低,导致不同用户之间买的重叠性较低,很难找到偏好相似的用户
    • UserCF不适用于正返回获取困难的应用场景(如酒店预订、大件商品购买等低频应用)
  2. 算法扩展性

    • UserCF需要维护用户相似度矩阵,并找出TopK用户,矩阵存储开销庞大,随用户量呈现增长。因此不适用于用户数据量大的情况使用

3.2 ItemCF

3.2.1 ItemCF算法流程

ItemCF算法流程与UserCF类似,还是以UserCF中的表格为例,想要知道Alice对物品5打多少分

物品1 物品2 物品3 物品4 物品5
Alice 5 3 4 4 ?
用户1 3 1 2 3 3
用户2 4 3 4 3 5
用户3 3 3 1 5 4
用户4 1 5 5 2 1
  1. 通过除了Alice以外的其他用户,计算物品5其他物品之间的相似性。
  2. 找到与物品5相似的个物品。
  3. 根据Alice对这个物品打分计算物品5的打分。预测公式跟UserCF极其相似,只不过是符号代表的含义不同,如下式所示,代表用户对物品打分,代表物品之间的相似度。

这里就可以明显看出,ItemCF是以物品为导向的,计算的都是物品相似度,而UserCF都是以用户为导向,计算用户之间的相似度。实际上,ItemCF是对原用户-物品评分矩阵的转置进行一遍UserCF的过程。

代码实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import numpy as np

data = np.array(
[
[5, 3, 4, 4, -1], # Alice, 物品5为未知量-1
[3, 1, 2, 3, 3], # user 1
[4, 3, 4, 3, 5], # user 2
[3, 3, 1, 5, 4], # user 3
[1, 5, 5, 2, 1], # user 4
]
)

data_transpose = data.T
sim_mat = np.corrcoef(data[1:, :].T)


# argsort是从小到大,这里需要取反,从大到小
similar_item = np.argsort(-sim_mat[-1])
print(similar_item)
# 最大的一定是1,也就是自己,从第二大开始取
num = 2
similar_item = similar_item[1:num+1]
print(similar_item)
# 获取相似物品的评分
similar_item_data = np.array([data_transpose[i, :] for i in similar_item])
print(similar_item_data)
# 获取相似物品的相似度
similar_item_sim = np.array([sim_mat[-1, i] for i in similar_item])
print(similar_item_sim)
# 计算相似物品均值
similar_item_means = [np.mean(similar_item_data[i, :]) for i in range(len(similar_item_data))]
print(similar_item_means)


item5_mean = np.mean(data_transpose[-1,1:])

# 计算公式
score = item5_mean + sum([similar_item_sim[i]*(similar_item_data[i, 0]-similar_item_means[i]) for i in range(len(similar_item))])/sum(similar_item_sim)



print(score)