文章目录
协同过滤(Collaborative Filtering,简写CF)
一种推荐算法,分为基于用户和基于物品两种
- 算法优点
①能够过滤机器难以自动内容分析的信息,如艺术品,音乐等;
②推荐个性化、自动化程度高、能够有效的利用其他相似用户的回馈信息、加快个性化学习的速度;
③可以帮助用户发现新的兴趣。 - 算法缺点
①稀疏性问题:用户对商品的评价非常稀疏,这样基于用户的评价所得到的用户间的相似性可能不准确;
②随着用户和物品的增多,系统的性能会越来越低;
③对于新用户或者新物品,推荐的质量会较差。
相似度计算
计算
a
a
a和
b
b
b的相似度时,用
s
i
m
sim
sim表示,即,
s
i
m
(
a
,
b
)
sim(a,b)
sim(a,b)表示
a
a
a和
b
b
b的相似度。
下面介绍相似度计算常用的三个经典算法:余弦定理相似性度量、欧氏距离相似度度量和杰卡德相似性度量。
2.1 余弦定理相似性度量
余弦距离通过向量空间中两个向量夹角的余弦值作为衡量两个个体间差异的大小的度量。
三角形余弦定理公式:
c
o
s
A
=
b
2
+
c
2
−
a
2
2
b
c
cosA=\frac{b^{2}+c^{2}-a^{2}}{2bc}
cosA=2bcb2+c2−a2
由三角形余弦定理公式可知,角A越小,bc两边越接近。当A为0度时,bc两边完全重合。
在向量空间中,对于向量a和向量b符合公式:
c
o
s
C
=
<
a
⃗
,
b
⃗
>
∣
a
⃗
∣
∣
b
⃗
∣
cosC=\frac{<\vec a,\vec b>}{|\vec a||\vec b|}
cosC=∣a∣∣b∣<a,b>
当a和b越接近(越相似)时,余弦值就越大,最大为1.
所以在比对a和b的相似度时,可以将其向量化后,计算它的余弦值,从而比较其相似度。即:
s
i
m
(
a
,
b
)
=
c
o
s
(
a
⃗
b
⃗
)
=
a
⃗
⋅
b
⃗
∣
a
⃗
∣
⋅
∣
b
⃗
∣
sim(a,b)=cos(\vec a\vec b)=\frac{\vec a·\vec b}{|\vec a|·|\vec b|}
sim(a,b)=cos(ab)=∣a∣⋅∣b∣a⋅b
类似的也可以推广到多个样本的相似性度量公式:
s
i
m
(
a
,
b
,
⋅
⋅
⋅
)
=
c
o
s
θ
=
x
1
y
1
+
x
2
y
2
+
⋅
⋅
⋅
+
x
n
y
n
x
1
2
+
x
2
2
+
⋅
⋅
⋅
+
x
n
2
⋅
y
1
2
+
y
2
2
+
⋅
⋅
⋅
+
y
n
2
sim(a,b,···)=cosθ=\frac{x_{1}y_{1}+x_{2}y_{2}+···+x_{n}y_{n}}{\sqrt{x_{1}^{2}+x_{2}^{2}+···+x_{n}^{2}}·\sqrt{y_{1}^{2}+y_{2} ^{2}+···+y_{n}^{2}}}
sim(a,b,⋅⋅⋅)=cosθ=x12+x22+⋅⋅⋅+xn2⋅y12+y22+⋅⋅⋅+yn2x1y1+x2y2+⋅⋅⋅+xnyn
对于集合A和集合B的相似性度量,为了方便计算,我们往往会用到下面的公式(计算的结果大小和上面的公式计算的结果是一样的):
s
i
m
(
A
,
B
)
=
A
∩
B
∣
A
∣
⋅
∣
B
∣
sim(A,B)=\frac{A∩B}{\sqrt{|A|·|B|}}
sim(A,B)=∣A∣⋅∣B∣A∩B
2.2 欧氏距离相似度度量
相对来说,余弦定理体现的是方向上的差异,而欧氏距离更倾向于样本之间的绝对距离,也就是数值上的绝对差异。简单的理解就是,余弦定理受绝对数值影响小,对方向敏感;欧式距离对绝对数值敏感。
比较相似性时,计算的欧式距离越大,表明样本相差的距离越大,也就是相似度越低;计算的欧氏距离越小,表明相似度越高。
2.3 杰卡德相似性度量
简单的说,就是用两个集合的交集在该两个集合的并集所占的比例,来度量两个集合的相似度。
2.3.1 杰卡德相似系数
杰卡德相似系数一般用
J
(
A
,
B
)
J(A,B)
J(A,B)表示,有时也写作
J
a
c
c
a
r
d
(
A
,
B
)
Jaccard(A,B)
Jaccard(A,B),其计算公式为:
J
(
A
,
B
)
=
∣
A
∩
B
∣
∣
A
∪
B
∣
J(A,B)=\frac{|A∩B|}{|A∪B|}
J(A,B)=∣A∪B∣∣A∩B∣
杰卡德相似系数越大,说明相似度越高,当
A
A
A和
B
B
B都为空时,
J
(
A
,
B
)
=
1
J(A,B)=1
J(A,B)=1。
2.3.2 杰卡德距离
杰卡德距离与杰卡德相似系数相反,可以用如下公式表示:
J
δ
=
1
−
J
(
A
,
B
)
=
∣
A
∪
B
∣
−
∣
A
∩
B
∣
∣
A
∪
B
∣
J_{δ}=1-J(A,B)=\frac{|A∪B|-|A∩B|}{|A∪B|}
Jδ=1−J(A,B)=∣A∪B∣∣A∪B∣−∣A∩B∣
杰卡德距离描述的是两个集合的不相似度,距离越大,相似度越低;距离越小,相似度越高。
基于用户(user)的协同过滤
算法介绍
基于用户的 CF 就是从用户出发,基于用户对物品的偏好找到和目标用户兴趣相似的用户集合;然后找到这个集合中的用户喜欢的,且目标用户没有听说过的物品推荐给目标用户。找到的用户集合中的用户可以是一个,也可以是多个,一般默认为一个用户,也就是默认只找到找到一个最相似用户。
下面给出了一个例子,其中用户A为目标用户,画√表示用户购买过该物品(下面的算法过程与代码实现都使用这个案例):
用户/物品 | 物品a | 物品b | 物品c | 物品d |
---|---|---|---|---|
用户A | √ | √ | ||
用户B | √ | √ | √ | |
用户C | √ | √ | √ | √ |
我们肉眼观察可以发现用户A最相似用户是用户C,用户B最相似用户也是用户C,而用户C已经购买过所有物品,所以我们会根据用户C的爱好向其他两个用户进行推荐。
算法过程
-
计算各个用户之间的相似度(使用余弦定理相似性度量);
可以构建如下的相似度矩阵:
用户\用户 用户A 用户B 用户C 用户A \ 1 6 \frac{1}{\sqrt{6}} 61 1 2 \frac{1}{\sqrt{2}} 21 用户B 1 6 \frac{1}{\sqrt{6}} 61 \ 3 2 \frac{\sqrt{3}}{2}\qquad 23 用户C 1 2 \frac{1}{\sqrt{2}} 21 3 2 \frac{\sqrt{3}}{2}\qquad 23 \ -
根据相似度的高低找到各用户的相似用户;
用户A的相似用户为用户C;
用户B的相似用户为用户C;
用户C的相似用户为用户B。 -
找到相似用户购买过而目标用户不知道的物品,计算目标用户对这样的物品感兴趣的预测值(就是预测目标用户购买的可能性),向目标用户推荐这些物品。
计算公式为:
p ( u , i ) = ∑ v ∈ S ( u , k ) ∩ N ( i ) w u v r v i p(u,i)=\sum_{v∈S(u,k)∩N(i)}w_{uv}r_{vi} p(u,i)=v∈S(u,k)∩N(i)∑wuvrvi
其中, S ( u , K ) S(u, K) S(u,K)包含和目标用户 u u u兴趣最接近的K个用户, N ( i ) N(i) N(i)是购买过物品i的用户集合, w u v w_{uv} wuv是目标用户 u u u和用户 v v v的兴趣相似度, r v i r_{vi} rvi代表用户v对物品i的兴趣,因为使用的是单一行为的隐反馈数据,所以所有的 r v i = 1 r_{vi}=1 rvi=1。
例如:我们默认 K = 1 K=1 K=1, p ( A , b ) = 1 2 p(A,b)=\frac{1}{\sqrt{2}} p(A,b)=21, p ( B , c ) = 3 2 p(B,c)=\frac{\sqrt{3}}{2}\qquad p(B,c)=23
- 给用户生成推荐列表,将被推荐物品展示给相应的用户。
代码实现放在最后
基于物品(item)的协同过滤
算法介绍
基于物品的$ CF $的原理和基于用户的 C F CF CF 类似,只是在计算邻居时采用物品本身,而不是从用户的角度,即基于用户对物品的偏好找到相似的物品,然后根据用户的历史偏好,推荐相似的物品给他。计算上,就是计算物品之间的相似度,得到物品的相似物品后,根据用户历史的偏好预测当前用户还没有表示偏好的物品,计算得到一个排序的物品列表作为推荐。
说白了就是找相似物品
下面给出了一个例子,画√表示用户购买过该物品(下面的算法过程与代码实现都使用这个案例):
用户/物品 | 物品a | 物品b | 物品c |
---|---|---|---|
用户A | √ | √ | |
用户B | √ | √ | |
用户C | √ | √ |
算法过程
-
建立 物品—用户 倒排表;
物品a:用户A、用户B、用户C
物品b:用户C
物品c:用户A、用户B -
计算各个物品之间的相似度(使用余弦定理相似性度量);
物品\物品 物品a 物品b 物品c 物品a \ 1 3 \frac{1}{\sqrt{3}} 31 2 6 \frac{2}{\sqrt{6}} 62 物品b 1 3 \frac{1}{\sqrt{3}} 31 \ 0 物品c 2 6 \frac{2}{\sqrt{6}} 62 0 \ 物品a的相似物品为物品c;
物品b的相似物品为物品a;
物品c的相似物品为物品a。
-
根据物品的相似度和用户的历史行为给用户生成推荐列表
通过如下公式计算用户u对一个物品j的购买预测值:
p ( u , j ) = ∑ i ∈ N ( u ) ∩ S ( i , k ) w j i r u i p(u,j)=\sum_{i∈N(u)∩S(i,k)}w_{ji}r_{ui} p(u,j)=i∈N(u)∩S(i,k)∑wjirui
其中, p ( u , j ) p(u,j) p(u,j)表示用户 u u u对物品 j j j的兴趣, N ( u ) N(u) N(u)表示用户喜欢的物品集合( i i i是该用户喜欢的某一个物品), S ( i , k ) S(i,k) S(i,k)表示和物品 i i i最相似的 K K K个物品集合( j j j是这个集合中的某一个物品), w j i w_{ji} wji表示物品j和物品i的相似度, r u i r_{ui} rui表示用户 u u u对物品 i i i的兴趣(这里简化 r u i r_{ui} rui都等于1)。如: p ( C , c ) = 2 6 p(C,c)=\frac{2}{\sqrt{6}} p(C,c)=62
-
对于物品 a a a,它的相似物品为物品 c c c,购买过物品 a a a的用户中,用户 C C C没有购买过物品 c c c,所以我们要向用户 C C C推荐物品c,用户 C C C对物品 c c c感兴趣的预测值为 p ( C , c ) = 2 6 p(C,c)=\frac{2}{\sqrt{6}} p(C,c)=62;
对于物品 b b b,它的相似物品为物品 a a a,但是购买过物品 b b b的用户都购买过物品 a a a;
对于物品 c c c,它的相似物品为物品 a a a,但是购买过物品 c c c的用户也都购买过物品 a a a。
代码实现
样例数据均来自用户CF
import numpy as np
from math import sqrt
user_item = np.array([[1, 0, 1, 0], # 用户商品矩阵
[1, 1, 0, 1],
[1, 1, 1, 1]])
item_user = user_item.T # 商品用户矩阵
user_num = user_item.shape[0] # 用户数
item_num = user_item.shape[1] # 商品数
user_mat = np.empty((user_num, user_num)) # 用户相似度矩阵
item_mat = np.empty((item_num, item_num)) # 商品相似度矩阵
sim = [] # 和每个用户最相似的用户编号
sim2 = [] # 和每个商品最相似的商品编号
user_rem_item = [[], [], []] # 给每个用户推荐的商品
user_rem_sco = [[], [], []] # 给每个用户推荐商品对应评分
user_name = ['用户一', '用户二', '用户三'] # 打印对应名字
item_name = ['物品一', '物品二', '物品三', '物品四']
def cosine(ls1, ls2): # 余弦相似度
fz = np.sum(ls1 * ls2)
fm = sqrt(np.linalg.norm(ls1) * np.linalg.norm(ls2)) # 模长api
return fz / fm
def predict(w_uv, r_vs = 1): # 评分预测
return w_uv * r_vs
if __name__ == '__main__':
# 打印商品物品矩阵
print(user_item)
print()
# 基于用户CF
for i in range(user_num): # 计算用户相似度
for j in range(user_num):
if(i < j):
user_mat[i][j] = cosine(user_item[i], user_item[j])
user_mat[j][i] = user_mat[i][j]
elif (i == j):
user_mat[i][j] = 0
print('用户相似矩阵:')
print(user_mat)
for i in range(user_num):
for j in range(user_num):
if(user_mat[i][j] == max(user_mat[i])): # 找最匹配的用户
for k in range(item_num):
if(user_item[i][k] == 0 and user_item[j][k] == 1): # 别人喜欢,指定用户还没看到的商品
user_rem_item[i].append(k) # 推荐
user_rem_sco[i].append(predict(user_mat[i][j])) # 给它打分
sim.append(j) # 加入最相似的列表
break
# 基于商品CF
for i in range(item_num): # 计算商品相似度
for j in range(item_num):
if(i < j):
item_mat[i][j] = cosine(item_user[i], item_user[j])
item_mat[j][i] = item_mat[i][j]
elif(i == j):
item_mat[i][j] = 0
print('商品相似矩阵:')
print(item_mat)
for i in range(item_num):
for j in range(item_num):
if(item_mat[i][j] == max(item_mat[i])): # 找最匹配的商品
for k in range(user_num):
if(user_item[k][i] == 0 and user_item[k][j] == 1): # 此用户喜欢一个商品,还没看到相似商品
user_rem_item[k].append(i) # 推荐
user_rem_sco[k].append(predict(item_mat[i][j])) # 给它打分
sim2.append(j) # 加入最相似的列表
break # 只找最相似的一个
# 打印推荐结果
print()
# 商品最相似结果
# for i in range(item_num):
# print('和{}最相似的是{}'.format(item_name[i],item_name[sim2[i]]))
for i in range(user_num): # 打印结果
print('给{}其推荐的商品如下:'.format(user_name[i]))
for j in range(len(user_rem_item[i])):
if(j != len(user_rem_item[i])-1): # 打印格式,处理最后的逗号
print('{}({})'.format(item_name[user_rem_item[i][j]],user_rem_sco[i][j]), end=',')
else:
print('{}({})'.format(item_name[user_rem_item[i][j]],user_rem_sco[i][j]))
print()