协同过滤(collaborative filtering)是推荐算法里面最经典也是最常用的。
该算法通过分析用户的兴趣,在用户群中找到指定用户的相似用户,综合这些相似用户对某一信息的评价,形成系统对该指定用户的喜好程度预测。
比如,你现在想看一部电影,但是不知道具体看哪一部,你会怎么做?大部分人会问问周围的朋友,看看他们最近有什么好看的电影可以推荐给自己,而我们一般会倾向于从口味比较类似的朋友那里得到推荐信息。这就是协同过滤的核心思想。
要实现协同过滤,需要一下几个步骤:
从用户的行为和便好中发现规律,并基于此进行推荐。所以,收集用户便好信息成为系统推荐效果最基础的决定因素。用户有很多种方式向系统提供自己的信息便好,比如评分、投票、转发、保存书签、购买、点击、页面停留时间等
以上行为是通用的,在实际推荐引擎设计中自己可以多添加一些特定的用户行为,并用他们表示对物品的喜好程度。通常情况下,在一个推荐系统中,用户行为都会多于一个。那么。如何组合这些不同的行为呢?
基本上有两种方式,如下:
在对用户的行为进程分析得到用户的便好后,可以根据用户的便好计算相似用户或相似物品,然后可以根据相似用户和相似物品进行推荐。这就是协同过滤的两个分支,即基于用户的协同过滤和基于物品的协同过滤。
关于相似度的计算,现在有的几种算法都是基于向量的,其实就是计算向量的距离,距离越近,说明相似度值越大,在推荐系统中,用户-物品便好的二维矩阵中,我们可以将一个用户对所有物品的便好作为一个向量来计算用户之间的相似度,或者将所有用户对某个物品的便好作为一个向量来计算物品之间的相似度
几种常见的相似度计算方法:
同现相似度
物品A和物品B的同现相似度定义如下:
W A , B = ∣ N ( A ) ⋂ N ( B ) ∣ ∣ N ( A ) ∣ W_{A,B}=\frac{|N(A)\bigcap N(B)|}{|N(A)|} WA,B=∣N(A)∣∣N(A)⋂N(B)∣
N(A)是喜欢A的用户数, ∣ N ( A ) ⋂ N ( B ) ∣ |N(A)\bigcap N(B)| ∣N(A)⋂N(B)∣是同时喜欢A和B的用户数,即上式表示喜欢A的用户中有多少比例也喜欢物品B。
上式存在一个问题,如果B是热门物品,那么 W A , B W_{A,B} WA,B就会很大,接近1.因此该公式造成任何物品与热门物品的相似度都很大。为了避免避免推荐热门物品,可以采取下面的公式。
W A , B = ∣ N ( A ) ⋂ N ( B ) ∣ ∣ N ( A ) ∣ ∣ N ( B ) ∣ W_{A,B}=\frac{|N(A)\bigcap N(B)|}{|N(A)||N(B)|} WA,B=∣N(A)∣∣N(B)∣∣N(A)⋂N(B)∣
上式对物品B的权重进行了惩罚
,减小了热门物品与很多物品相似的可能性.
欧几里得距离
该距离最初用于计算两点的距离,假设 x , y x,y x,y是二维空间的两个点,他们之间的欧几里得距离如下:
d ( x , y ) = ∑ ( x i − y i ) n d(x,y)=\sqrt{\sum(x_i-y_i)^n} d(x,y)=∑(xi−yi)n
当n等于2时,欧几里得距离就是平面上两个点的距离
当用欧几里得表示相似度时,一般采用下面公式进行转换
s i m ( x , y ) = 1 1 + d ( x , y ) sim(x,y)=\frac{1}{1+d(x,y)} sim(x,y)=1+d(x,y)1
距离越小,相似度越大
余弦相似度
余弦相似度是文本相似度度量中使用比较多的一种方法,对于两个向量X和Y,其对应的形式如下
X = ( x 1 , x 2 , … … , x n ) ∈ R n X=(x_1,x_2,……,x_n)\in R^n X=(x1,x2,……,xn)∈Rn
Y = ( y 1 , y 2 , … … , y n ) ∈ R n Y=(y_1,y_2,……,y_n)\in R^n Y=(y1,y2,……,yn)∈Rn
对应的余弦相似度为
C o s S i m ( X , Y ) = ∑ i x i y i ( ∑ i x i 2 ) ( ∑ i y i 2 ) CosSim(X,Y)=\frac{\sum_ix_iy_i}{\sqrt{(\sum_ix_i^2)}\sqrt{(\sum_iy_i^2)}} CosSim(X,Y)=(∑ixi2)(∑iyi2)∑ixiyi
皮尔逊相关系数
皮尔逊相关系数一般用于计算两个定居变量间联系的紧密程度,取值范围为 [ − 1 , 1 ] [-1,1] [−1,1]
p ( x , y ) = ∑ x i y i − n x y ‾ ( n − 1 ) S x S y = n ∑ x i y i − ∑ x i ∑ y i n ∑ x i 2 − ( ∑ x i ) 2 n ∑ y i 2 − ( ∑ y i ) 2 p(x,y)=\frac{\sum x_iy_i-n\overline{xy}}{(n-1)S_xS_y}=\frac{n\sum x_iy_i-\sum x_i\sum y_i}{\sqrt{n\sum x_i^2-{(\sum x_i)^2}}\sqrt{n\sum y_i^2-{(\sum y_i)^2}}} p(x,y)=(n−1)SxSy∑xiyi−nxy=n∑xi2−(∑xi)2n∑yi2−(∑yi)2n∑xiyi−∑xi∑yi
S x , S y S_x,S_y Sx,Sy是x和y的样本标准差
Cosine相似度
Cosine相似度一般用于计算文档数据的相似度
C ( x , y ) = x , y ∣ ∣ x ∣ ∣ 2 ∣ ∣ y ∣ ∣ 2 = ∑ x i y i ∑ x i 2 ∑ y i 2 C(x,y)=\frac{x,y}{||x||^2||y||^2}=\frac{\sum x_iy_i}{\sqrt{\sum x_i^2}\sqrt{\sum y_i^2}} C(x,y)=∣∣x∣∣2∣∣y∣∣2x,y=∑xi2∑yi2∑xiyi
Tanimoto系数
Tanimoto系数也被称为Jaccard系数,Cosine相似度的扩展,一般用于计算文档数据的相似度
T ( x , y ) = x , y ∣ ∣ x ∣ ∣ 2 + ∣ ∣ y ∣ ∣ 2 − x ⋅ y = ∑ x i y i ∑ x i 2 + ∑ y i 2 − ∑ x i y i T(x,y)=\frac{x,y}{||x||^2+||y||^2-x·y}=\frac{\sum x_iy_i}{\sqrt{\sum x_i^2}+\sqrt{\sum y_i^2}-\sum x_iy_i} T(x,y)=∣∣x∣∣2+∣∣y∣∣2−x⋅yx,y=∑xi2+∑yi2−∑xiyi∑xiyi
在计算用户相似度时,是将一个用户对所有物品的便好作为一个向量,而在计算物品相似度时,是将所有用户对某个物品的便好作为一个向量。求出相似度后,接下来可以计算相邻用户和相邻物品。
基于用户协同过滤的思想很简单,基于用户对物品的便好,找到相邻的用户,然后将相邻用户喜欢的物品推荐给当前用户。在计算时,就是将一个用户对所有物品的便好作为一个向量来计算用户之间的相似度,找到K个相邻用户后,根据相邻用户的相似度权重以及他们对物品的偏好,预测当前用户喜欢的物品,计算得到一个排序的物品列表进行推荐。
如下表给出的例子
物品/用户 | 物品A | 物品B | 物品C | 物品D |
---|---|---|---|---|
用户A | √ | √ | 推荐 |
|
用户B | √ | |||
用户C | √ | √ | √ |
对于用户A,根据历史偏好,这里只计算一个邻居得到–用户C,然后可以将C喜欢的物品D推荐给A
基于物品的协同过滤原理与基于用户的协同过滤原理类似,只是在计算邻居时采用物品本身(而不是从用户角度),基于用户对物品的偏好找到相似的物品,然后根据用户的历史偏好推荐相似的物品从计算的角度看,就是将所有用户对某个物品的偏好作为一个相量,来计算物品之间的相似度,得到某物品的相似度后,如根据用户的历史偏好预测当前用户还没有表示偏好的物品,则计算得到一个排序的物品列表推荐。如下表
物品/用户 | 物品A | 物品B | 物品C |
---|---|---|---|
用户A | √ | √ | |
用户B | √ | √ | √ |
用户C | √ | 推荐 |
对于物品A,根据用户偏好–喜欢物品A的用户都喜欢C,得出物品A和物品C比较相似,而用户C喜欢物品A,纳闷可以推断用户C可能也喜欢物品C
def cos_sim(x,y):
'''
余弦相似性
input: x(mat):以行向量的形式存储,可以是用户或者商品
y(mat):以行向量的形式存储,可以是用户或者商品
output: x和y之间的余弦相似度
'''
numerator=np.dot(x,y.T)#x和y之间的内机
denominator = np.sqrt(np.dot(x,x.T))*np.sqrt(np.dot(y,y.T))
return (numerator/denominator)
接下来再定义一个计算相似度矩阵的函数
def similarity(data):
'''
计算矩阵中任意两行之间的相似度
input: data(mat):任意矩阵
output: w(mat):任意两行之间的相似度
'''
m = np.shape(data)[0]#用户的数量
#初始化相似度矩阵
w = np.mat(np.zeros((m,m)))
for i in range(m):
for j in range(i,m):
if i!=j:
#计算任意两行之间的相似度
w[i,j] = cos_sim(data[i,],data[j,])
w[j,i] = w[i,j]
else:
w[i,j]=0#约定自身的相似度为0
return w
简单定义一个数据
User\Item | I_1 | I_2 | I_3 | I_4 | I_5 |
---|---|---|---|---|---|
U_1 | 4 | 3 | - | 5 | - |
U_2 | 5 | - | 4 | 4 | - |
U_3 | 4 | - | 5 | - | 3 |
U_4 | 2 | 3 | - | 1 | - |
U_5 | - | 4 | 2 | - | 5 |
计算用户相似度
similarity(A)
matrix([[0. , 0.74926865, 0.32 , 0.83152184, 0.25298221],
[0.74926865, 0. , 0.74926865, 0.49559463, 0.1579597 ],
[0.32 , 0.74926865, 0. , 0.30237158, 0.52704628],
[0.83152184, 0.49559463, 0.30237158, 0. , 0.47809144],
[0.25298221, 0.1579597 , 0.52704628, 0.47809144, 0. ]])
计算完用户之间的相似度后,利用用户之间的相似度为用户中没有打分的商品打分,具体计算方法为:
p ( u , i ) = ∑ v ∈ N ( i ) w u , v r v , i p(u,i)=\sum_{v\in N(i)}w_{u,v}r_{v,i} p(u,i)=v∈N(i)∑wu,vrv,i
其中,N(i)表示对商品i打过分的用户集合,比如上表中,对与User1,商品D_3没有打分,要想计算商品D_3的分数。对商品D_3打过分的用户有User2、User3、User5。 w u , v w_{u,v} wu,v表示用户u和用户v 之间的相似度, r v , i r_{v,i} rv,i表示用户v对商品i的打分。
具体计算的代码如下:
def user_base_recommend(data,w,user):
'''
基于用户相似性为用户user推荐商品
input: data(mat):用户商品矩阵
w(mat):用户之间的相似度
user(int):用户的编号
output: predict(list):推荐列表
'''
m,n=np.shape(data)
interaction = data[user,]#用户user与商品信息
#先找到用户user没有打过分的商品
not_inter = []
for i in range(n):#n商品数量
if interaction[i]==0:#没有打分的商品
not_inter.append(i)
#print(not_inter)
#对没有打分的商品进行预测
predict={
}
for x in not_inter:
item = np.copy(data[:,x])#找到所有用户对商品x的打分信息
for i in range(m):#对所有的用户
if item[i] != 0:#如果用户对商品x打过分
if x not in predict:
predict[x]=w[user,i]*item[i]
else:
predict[x] = predict[x]+w[user,i]*item[i]
#按照预测的大小排序
return sorted(predict.items(),key=lambda d:d[1],reverse=True)
实际带入计算
user_base_recommend(A,w,0)
[(2, 5.10303902268836), (4, 2.2249110640673515)]
推荐TOP-N
def top_k(predict,k):
'''为用户推荐前k个商品
input: predict(list):排好序的商品列表
k(int):推荐的商品个数
output: top_recom(list):top_k个商品
'''
top_recom=[]
len_r = len(predict)#计算有多少待推荐商品
if k>=len_r:#如果需要推荐的个数大于待推荐的数量,直接输出
top_recom=predict
else:
for i in range(k):
top_recom.append(predict[i])
return top_recom
打印下结果
k=2
result=top_k(user_base_recommend(A,w,0),k)
for i in range(k):
print("给User_0推荐的商品有:商品%d,打分为%.3f"%(result[i][0],result[i][1]))
给User_0推荐的商品有:商品2,打分为5.103
给User_0推荐的商品有:商品4,打分为2.225
首先要计算Item之间的相似度,还是套用上面的similarity
函数,前提是先把矩阵进行转置。
之后利用商品之间的相似度为用户没有打分的商品计算得分,计算方法:
p ( u , i ) = ∑ j ∈ I ( u ) w i , j r j , u p(u,i)=\sum_{j\in I(u)}w_{i,j}r_{j,u} p(u,i)=j∈I(u)∑wi,jrj,u
I ( u ) I(u) I(u)表示用户u打过分的商品集合,比如在表中,用户User1打过分的商品有商品D_1,商品D_2,商品D_4, w i , j w_{i,j} wi,j表示商品i和商品j之间的相似度, r j , u r_{j,u} rj,u表示用户u对商品j的打分。
原理与基于用户是相同的
Item_base_recommend(B,similarity(B),0)
[(2, 5.507604598998138), (4, 2.8186967825714824)]
完整代码:https://github.com/Andyszl/Recommendation_algorithm/blob/master/collaborative-filtering_demo.ipynb