推荐系统-算法
本次内容:
-
UserCF(基于用户的协同过滤算法)
根据用户之前的喜好以及有相似用户行为画像的用户的喜好来给当前用户推荐物品,一般仅仅基于用户的行为数据而不依赖于任何的商品附加信息和用户基本信息
-
ItemCF(基于物品的协同过滤算法)
预先根据所有用户的历史行为数据,计算物品之间的相似性,然后推荐给用户
-
矩阵分解(隐语义)
-
用于解决协同过滤算法不能很好的处理稀疏矩阵的问题
矩阵稀疏的含义:比如一个表格中记录了所有用户对所有物品的喜好评分,但因为不是每个用户都会去对某个物品评分,所以这个矩阵就很稀疏。而矩阵分解的含义就是把这个稀疏的表格拆分成两个小一些的矩阵,这两个矩阵可以帮助我们发现用户和物品之间隐藏的关系:比如对于一张照片,你喜欢的不是里面的风景,而是里面有个你很喜欢的人在。
-
在协同过滤共现矩阵的基础上,使用更加稠密的隐向量表示用户和物品
-
通过挖掘用户和物品的隐含兴趣和隐含特征, 在一定程度上弥补协同过滤模型处理稀疏矩阵能力不足的问题。
-
首先了解几个前置的知识:
-
杰卡尔德相似系数:
- 计算某个颜色的球在这个盒子里面所占的比例
- s i m u v = ∣ N ( u ) ∩ N ( v ) ∣ ∣ N ( u ) ∪ N ( v ) ∣ sim_{uv} = \frac{|N(u) \cap N(v)|}{|N(u) \cup N(v)|} simuv=∣N(u)∪N(v)∣∣N(u)∩N(v)∣
- 这里,N(u)N(u) 和 N(v)N(v) 分别是用户 u 和用户 v 交互的物品集合。
- 适用于只关心用户是否对物品有行为(比如购买、点击),而不关心具体行为程度(比如评分)的情况。
-
余弦相似度:
-
计算两个向量之间夹角的余弦值。如果两个箭头方向相同,那么余弦相似度为1;如果方向完全相反,则为-1。
-
s i m u v = u ⃗ ⋅ v ⃗ ∥ u ⃗ ∥ ∥ v ⃗ ∥ sim_{uv} = \frac{\vec{u} \cdot \vec{v}}{\|\vec{u}\| \|\vec{v}\|} simuv=∥u∥∥v∥u⋅v
-
适用于文本相似度计算,也常用于用户或物品的相似度计算。
-
from sklearn.metrics.pairwise import cosine_similarity i = [1, 0, 0, 0] j = [1, 0, 1, 0] cosine_similarity([i, j])
-
-
皮尔逊相关系数:
-
余弦相似度的升级版,不仅考虑了两个向量的方向,还考虑了它们的长度和位置。它通过减去平均值来消除不同用户评分偏好的影响。
-
s i m ( u , v ) = ∑ i ∈ I ( r u i − r ˉ u ) ( r v i − r ˉ v ) ∑ i ∈ I ( r u i − r ˉ u ) 2 ∑ i ∈ I ( r v i − r ˉ v ) 2 sim_{(u,v)} = \frac{\sum_{i \in I} (r_{ui} - \bar{r}_u)(r_{vi} - \bar{r}_v)}{\sqrt{\sum_{i \in I} (r_{ui} - \bar{r}_u)^2} \sqrt{\sum_{i \in I} (r_{vi} - \bar{r}_v)^2}} sim(u,v)=∑i∈I(rui−rˉu)2∑i∈I(rvi−rˉv)2∑i∈I(rui−rˉu)(rvi−rˉv)
-
这里,ru 和 rv 是用户 u 和 v 对物品 i 的评分,rˉu 和 rˉv是它们的平均评分。
-
适用于有具体评分值的数据,可以更好地反映用户之间的相似性。
-
from scipy.stats import pearsonr i = [1, 0, 0, 0] j = [1, 0.5, 0.5, 0] pearsonr(i, j)
-
UserCF
UserCF基于用户的协同过滤,想找到用户A可能喜欢的物品,可以先通过找到与A有相同兴趣的用户B,然后将用户B喜欢的并且A在之前没有进行交互过的物品推荐给A。
UserCF算法的两个步骤:
-
首先,根据前面的这些打分情况(或者说已有的用户向量)计算一下 Alice 和用户1, 2, 3, 4的相似程度, 找出与 Alice 最相似的 n 个用户。
UserCF / itemCF在计算物品相似度的时候应该选多少个用户与物品的交互的矩阵呢
业务要求的不同,选取的范围也不同,要求准确率高就多选点;不要求准确率但关注性能,那就根据服务器性能,把那些活跃的用户选上就行。或是热度榜单类的业务,那就把最近几天发生的选上。构建矩阵的时候,设置一些过滤规则,把哪些潜水的用户都剔除掉。
-
根据这 n 个用户对物品 5 的评分情况和与 Alice 的相似程度会猜测出 Alice 对物品5的评分。如果评分比较高的话, 就把物品5推荐给用户 Alice, 否则不推荐。
-
手动计算过程:
用户向量 Alice:(5,3,4,4),user1:(3,1,2,3),user2:(4,3,4,3),user3:(3,3,1,5),user4:(1,5,5,2)Alice:(5,3,4,4),user1:(3,1,2,3),user2:(4,3,4,3),user3:(3,3,1,5),user4:(1,5,5,2)
计算Alice与user1的余弦相似性: s i m ( A l i c e , u s e r 1 ) = c o s ( A l i c e , u s e r 1 ) = 15 + 3 + 8 + 12 s q r t ( 25 + 9 + 16 + 16 ) ∗ s q r t ( 9 + 1 + 4 + 9 ) = 0.975 s i m ( A l i c e , u s e r 1 ) = c o s ( A l i c e , u s e r 1 ) = s q r t ( 25 + 9 + 16 + 16 ) ∗ s q r t ( 9 + 1 + 4 + 9 ) 15 + 3 + 8 + 12 = 0.975 sim( Alice, user1 )=cos( Alice, user 1)=15+3+8+12sqrt(25+9+16+16)∗sqrt(9+1+4+9)=0.975sim( Alice, user1 )=cos( Alice, user 1)=sqrt(25+9+16+16)∗sqrt(9+1+4+9)15+3+8+12=0.975 sim(Alice,user1)=cos(Alice,user1)=15+3+8+12sqrt(25+9+16+16)∗sqrt(9+1+4+9)=0.975sim(Alice,user1)=cos(Alice,user1)=sqrt(25+9+16+16)∗sqrt(9+1+4+9)15+3+8+12=0.975
计算Alice与user1皮尔逊相关系数:
对于Alice和user1:
- Alice的评分向量:(5, 3, 4, 4)
- Alice的平均评分 Aliceave=(5+3+4+4)/4=4
- user1的评分向量:(3, 1, 2, 3)
- user1的平均评分 user1ave=(3+1+2+3)/4=2.25
向量减去均值:
- Alice:(5−4,3−4,4−4,4−4)=(1,−1,0,0)
- user1:(3−2.25,1−2.25,2−2.25,3−2.25)=(0.75,−1.25,−0.25,0.75)
计算这俩新向量的余弦相似度和上面计算过程一致, 结果是 0.852 。
首先:皮尔逊相关系数的公式如下
r x y = ∑ ( x i − x ˉ ) ( y i − y ˉ ) ∑ ( x i − x ˉ ) 2 ∑ ( y i − y ˉ ) 2 r_{xy} = \frac{\sum{(x_i - \bar{x})(y_i - \bar{y})}}{\sqrt{\sum{(x_i - \bar{x})^2}} \sqrt{\sum{(y_i - \bar{y})^2}}} rxy=∑(xi−xˉ)2∑(yi−yˉ)2∑(xi−xˉ)(yi−yˉ)
分子部分:
∑ ( x i − x ˉ ) ( y i − y ˉ ) = ( 1 × 0.75 ) + ( − 1 × − 1.25 ) + ( 0 × − 0.25 ) + ( 0 × 0.75 ) \sum{(x_i - \bar{x})(y_i - \bar{y})} = (1 \times 0.75) + (-1 \times -1.25) + (0 \times -0.25) + (0 \times 0.75) ∑(xi−xˉ)(yi−yˉ)=(1×0.75)+(−1×−1.25)+(0×−0.25)+(0×0.75)
= 0.75 + 1.25 + 0 + 0 = 0.75 + 1.25 + 0 + 0 =0.75+1.25+0+0
= 2 = 2 =2分母部分:
对于Alice的标准差:
∑ ( x i − x ˉ ) 2 = ( 1 ) 2 + ( − 1 ) 2 + ( 0 ) 2 + ( 0 ) 2 \sqrt{\sum{(x_i - \bar{x})^2}} = \sqrt{(1)^2 + (-1)^2 + (0)^2 + (0)^2} ∑(xi−xˉ)2=(1)2+(−1)2+(0)2+(0)2
= 1 + 1 + 0 + 0 = \sqrt{1 + 1 + 0 + 0} =1+1+0+0
= 2 = \sqrt{2} =2对于user1的标准差:
∑ ( y i − y ˉ ) 2 = ( 0.75 ) 2 + ( − 1.25 ) 2 + ( − 0.25 ) 2 + ( 0.75 ) 2 \sqrt{\sum{(y_i - \bar{y})^2}} = \sqrt{(0.75)^2 + (-1.25)^2 + (-0.25)^2 + (0.75)^2} ∑(yi−yˉ)2=(0.75)2+(−1.25)2+(−0.25)2+(0.75)2
= 0.5625 + 1.5625 + 0.0625 + 0.5625 = \sqrt{0.5625 + 1.5625 + 0.0625 + 0.5625} =0.5625+1.5625+0.0625+0.5625
= 2.75 ≈ 1.658 = \sqrt{2.75} \approx 1.658 =2.75≈1.658结合到一起就是 2 × 2.75 = 2 × 2.75 = 5.5 ≈ 2.345 \sqrt{2} \times \sqrt{2.75} = \sqrt{2 \times 2.75} = \sqrt{5.5} \approx 2.345 2×2.75=2×2.75=5.5≈2.345
所以:
皮尔逊相关系数 r x y = 2 2.345 ≈ 0.853 r_{xy} = \frac{2}{2.345} \approx 0.853 rxy=2.3452≈0.853
现在获取到了这个相似度,根据相似度用户计算 Alice对物品5的最终得分
物品物品 P A l i c e , 物品 5 = R ˉ A l i c e + ∑ k = 1 2 ( w A l i c e , u s e r k × ( R u s e r k , 物品 5 − R ˉ u s e r k ) ) ∑ k = 1 2 w A l i c e , u s e r k 物品物品 P_{Alice, 物品5} = \bar{R}_{Alice} + \frac{\sum_{k=1}^{2} \left( w_{Alice,user_k} \times (R_{user_k, 物品5} - \bar{R}_{user_k}) \right)}{\sum_{k=1}^{2} w_{Alice,user_k}} 物品物品PAlice,物品5=RˉAlice+∑k=12wAlice,userk∑k=12(wAlice,userk×(Ruserk,物品5−Rˉuserk))
-
$ \bar{R}_{Alice}$是我们刚刚算的Alice评分向量的平均值。
对于Alice和user1:
- Alice的评分向量:(5, 3, 4, 4)
- Alice的平均评分 Aliceave=(5+3+4+4)/4=4
- user1的评分向量:(3, 1, 2, 3)
- user1的平均评分 user1ave=(3+1+2+3)/4=2.25
- Alice的评分向量:(5, 3, 4, 4)
-
这里面的w就是刚刚算的相似度,Alice对user1的相似度就是刚刚算的0.853。后面的自己算吧。
-
然后里面的第一个R是用户k对物品5的评分,第二个R是用户k对这几个物品评分的平均值。
同样的,我们可以算出Alice对其他物品可能的评分是多少,然后根据这个数据进行排序:
物品1>物品5>物品3=物品4>物品2
所以,如果要推荐给Alice物品的话,会推荐评分最高的物品1和物品5。
import numpy as np
import pandas as pd
# 构建数据表
def loadData():
users = {'Alice': {'A': 5, 'B': 3, 'C': 4, 'D': 4},
'user1': {'A': 3, 'B': 1, 'C': 2, 'D': 3, 'E': 3},
'user2': {'A': 4, 'B': 3, 'C': 4, 'D': 3, 'E': 5},
'user3': {'A': 3, 'B': 3, 'C': 1, 'D': 5, 'E': 4},
'user4': {'A': 1, 'B': 5, 'C': 5, 'D': 2, 'E': 1}
}
return users
#计算相似性矩阵
user_data = loadData()
similarity_matrix = pd.DataFrame(
np.identity(len(user_data)),
index=user_data.keys(),
columns=user_data.keys(),
)
# 遍历每条用户-物品评分数据
for u1, items1 in user_data.items():
for u2, items2 in user_data.items():
if u1 == u2:
continue
vec1, vec2 = [], []
for item, rating1 in items1.items():
rating2 = items2.get(item, -1)
if rating2 == -1:
continue
vec1.append(rating1)
vec2.append(rating2)
# 计算不同用户之间的皮尔逊相关系数
similarity_matrix[u1][u2] = np.corrcoef(vec1, vec2)[0][1]
print(similarity_matrix)
#结果如下:
# 1 2 3 4 5
# 1 1.000000 0.852803 0.707107 0.000000 -0.792118
# 2 0.852803 1.000000 0.467707 0.489956 -0.900149
# 3 0.707107 0.467707 1.000000 -0.161165 -0.466569
# 4 0.000000 0.489956 -0.161165 1.000000 -0.641503
# 5 -0.792118 -0.900149 -0.466569 -0.641503 1.000000
#计算与Alice最相似的k个用户
target_user = ' Alice '
num = 2
# 由于最相似的用户为自己,去除本身
sim_users = similarity_matrix[target_user].sort_values(ascending=False)[1:num+1].index.tolist()
print(f'与用户{target_user}最相似的{num}个用户为:{sim_users}')
# 预测用户 Alice 对物品 E 的评分
weighted_scores = 0.
corr_values_sum = 0.
target_item = 'E'
# 基于皮尔逊相关系数预测用户评分
for user in sim_users:
corr_value = similarity_matrix[target_user][user]
user_mean_rating = np.mean(list(user_data[user].values()))
weighted_scores += corr_value * (user_data[user][target_item] - user_mean_rating)
corr_values_sum += corr_value
target_user_mean_rating = np.mean(list(user_data[target_user].values()))
target_item_pred = target_user_mean_rating + weighted_scores / corr_values_sum
print(f'用户{target_user}对物品{target_item}的预测评分为:{target_item_pred}')
UserCF算法的缺点:
User-based算法存在两个重大问题:
- 数据稀疏性
- 一个大型的电子商务推荐系统一般有非常多的物品,用户可能买的其中不到1%的物品,不同用户之间买的物品重叠性较低,导致算法无法找到一个用户的邻居,即偏好相似的用户。
- 这导致UserCF不适用于那些正反馈获取较困难的应用场景(如酒店预订, 大件物品购买等低频应用)。
- 算法扩展性
- 基于用户的协同过滤需要维护用户相似度矩阵以便快速的找出 TopNTopN 相似用户, 该矩阵的存储开销非常大,存储空间随着用户数量的增加而增加。
- 故不适合用户数据量大的情况使用。
由于UserCF技术上的两点缺陷, 导致很多电商平台并没有采用这种算法, 而是采用了ItemCF算法实现最初的推荐系统。
ItemCF
- 首先根据所有用户的历史行为数据,计算物品之间的相似性
- 然后,把与用户喜欢的物品类似的物品推荐给用户
物品向量: 物品1(3,4,3,1),物品2(1,3,3,5),物品3(2,4,1,5),物品4(3,3,5,2),物品5(3,5,41)物品1(3,4,3,1),物品2(1,3,3,5),物品3(2,4,1,5),物品4(3,3,5,2),物品5(3,5,41)
下面计算物品 5 和物品 1 之间的余弦相似性:
物品物品物品物品 s i m ( 物品 1 , 物品 5 ) = c o s i n e ( 物品 1 , 物品 5 ) = 9 + 20 + 12 + 1 9 + 16 + 9 + 1 × 9 + 25 + 16 + 1 物品物品物品物品sim(物品1, 物品5) = cosine(物品1, 物品5) = \frac{9+20+12+1}{\sqrt{9+16+9+1} \times \sqrt{9+25+16+1}} 物品物品物品物品sim(物品1,物品5)=cosine(物品1,物品5)=9+16+9+1×9+25+16+19+20+12+1
然后计算5和1的皮尔逊相关系数
根据皮尔逊相关系数, 可以找到与物品5最相似的2个物品是 item1 和 item4, 下面基于上面的公式计算最终得分:
物品物品物品物品物品物品物品物品 P A l i c e , 物品 5 = R ˉ 物品 5 + ∑ k = 1 2 w 物品 k , 物品 5 ⋅ ( R A l i c e , 物品 k − R ˉ 物品 k ) ∑ k = 1 2 w 物品 5 , 物品 k = 4 13 + 0.97 × ( 5 − 3.2 ) + 0.58 × ( 4 − 3.4 ) 0.97 + 0.58 = 4.6 物品物品物品物品物品物品物品物品P_{Alice,物品5} = \bar{R}_{物品5} + \frac{\sum_{k=1}^{2} w_{物品k,物品5} \cdot (R_{Alice,物品k} - \bar{R}_{物品k})}{\sum_{k=1}^{2} w_{物品5,物品k}}= \frac{4}{13} + \frac{0.97 \times (5 - 3.2) + 0.58 \times (4 - 3.4)}{0.97 + 0.58} = 4.6 物品物品物品物品物品物品物品物品PAlice,物品5=Rˉ物品5+∑k=12w物品5,物品k∑k=12w物品k,物品5⋅(RAlice,物品k−Rˉ物品k)=134+0.97+0.580.97×(5−3.2)+0.58×(4−3.4)=4.6
协同过滤算法的权重改进
Base 公式
w i j = ∣ N ( i ) ∩ N ( j ) ∣ ∣ N ( i ) ∣ w_{ij} = \frac{|N(i) \cap N(j)|}{|N(i)|} wij=∣N(i)∣∣N(i)∩N(j)∣
该公式表示同时喜好物品 i 和物品 j 的用户数,占喜爱物品 i 的比例。
缺点:若物品 j 为热门物品,那么它与任何物品的相似度都很高。
对热门物品进行惩罚
w i j = ∣ N ( i ) ∩ N ( j ) ∣ ∣ N ( i ) ∣ ∣ N ( j ) ∣ w_{ij} = \frac{|N(i) \cap N(j)|}{|N(i)||N(j)|} wij=∣N(i)∣∣N(j)∣∣N(i)∩N(j)∣
根据 base 公式存在的问题,对物品 j 进行打压。就是在分母再除以一个物品 j 被购买的数量。此时,若物品 j 为热门物品,那么对应的 N(j) 也会很大,受到的惩罚更多。
控制对热门物品的惩罚力度
w i j = ∣ N ( i ) ∩ N ( j ) ∣ ∣ N ( i ) ∣ 1 − α ∣ N ( j ) ∣ α w_{ij} = \frac{|N(i) \cap N(j)|}{|N(i)|^{1-\alpha}|N(j)|^{\alpha}} wij=∣N(i)∣1−α∣N(j)∣α∣N(i)∩N(j)∣
除了第二点提到的办法,在计算物品之间相似度时可以对热门物品进行惩罚外,可以在此基础上进一步引入参数 α ,这样可以通过控制参数 α 来决定对热门物品的惩罚力度。
对活跃用户的惩罚
在计算物品之间的相似度时,可以进一步将用户的活跃度考虑进来。
w
i
j
=
∑
u
∈
N
(
i
)
∩
N
(
j
)
1
log
(
1
+
∣
N
(
u
)
∣
)
∣
N
(
i
)
∣
1
−
α
∣
N
(
j
)
∣
α
w_{ij} = \frac{\sum_{u \in N(i) \cap N(j)} \frac{1}{\log(1+|N(u)|)}}{|N(i)|^{1-\alpha}|N(j)|^{\alpha}}
wij=∣N(i)∣1−α∣N(j)∣α∑u∈N(i)∩N(j)log(1+∣N(u)∣)1
对于异常活跃的用户,在计算物品之间的相似度时,他的贡献应该小于非活跃用户。
import numpy as np
import pandas as pd
def loadData():
items = {'A': {'Alice': 5.0, 'user1': 3.0, 'user2': 4.0, 'user3': 3.0, 'user4': 1.0},
'B': {'Alice': 3.0, 'user1': 1.0, 'user2': 3.0, 'user3': 3.0, 'user4': 5.0},
'C': {'Alice': 4.0, 'user1': 2.0, 'user2': 4.0, 'user3': 1.0, 'user4': 5.0},
'D': {'Alice': 4.0, 'user1': 3.0, 'user2': 3.0, 'user3': 5.0, 'user4': 2.0},
'E': {'user1': 3.0, 'user2': 5.0, 'user3': 4.0, 'user4': 1.0}
}
return items
item_data = loadData()
similarity_matrix = pd.DataFrame(
np.identity(len(item_data)),
index=item_data.keys(),
columns=item_data.keys(),
)
# 遍历每条物品-用户评分数据
for i1, users1 in item_data.items():
for i2, users2 in item_data.items():
if i1 == i2:
continue
vec1, vec2 = [], []
for user, rating1 in users1.items():
rating2 = users2.get(user, -1)
if rating2 == -1:
continue
vec1.append(rating1)
vec2.append(rating2)
similarity_matrix[i1][i2] = np.corrcoef(vec1, vec2)[0][1]
print(similarity_matrix)
#从 Alice 购买过的物品中,选出与物品 E 最相似的 num 件物品。
target_user = 'Alice'
target_item = 'E'
num = 2
sim_items = []
sim_items_list = similarity_matrix[target_item].sort_values(ascending=False).index.tolist()
for item in sim_items_list:
# 如果target_user对物品item评分过
if target_user in item_data[item]:
sim_items.append(item)
if len(sim_items) == num:
break
print(f'与物品{target_item}最相似的{num}个物品为:{sim_items}')
#预测用户 Alice 对物品 E 的评分
target_user_mean_rating = np.mean(list(item_data[target_item].values()))
weighted_scores = 0.
corr_values_sum = 0.
target_item = 'E'
for item in sim_items:
corr_value = similarity_matrix[target_item][item]
user_mean_rating = np.mean(list(item_data[item].values()))
weighted_scores += corr_value * (item_data[item][target_user] - user_mean_rating)
corr_values_sum += corr_value
target_item_pred = target_user_mean_rating + weighted_scores / corr_values_sum
print(f'用户{target_user}对物品{target_item}的预测评分为:{target_item_pred}')
什么时候使用UserCF,什么时候使用ItemCF?为什么?
(1)UserCF
- 由于是基于用户相似度进行推荐, 所以具备更强的社交特性, 这样的特点非常适于用户少, 物品多, 时效性较强的场合。
- 比如新闻推荐场景, 因为新闻本身兴趣点分散, 相比用户对不同新闻的兴趣偏好, 新闻的及时性,热点性往往更加重要, 所以正好适用于发现热点,跟踪热点的趋势。
- 另外还具有推荐新信息的能力, 更有可能发现惊喜, 因为看的是人与人的相似性, 推出来的结果可能更有惊喜,可以发现用户潜在但自己尚未察觉的兴趣爱好。
(2)ItemCF
- 这个更适用于兴趣变化较为稳定的应用, 更接近于个性化的推荐, 适合物品少,用户多,用户兴趣固定持久, 物品更新速度不是太快的场合。
- 比如推荐艺术品, 音乐, 电影。
矩阵分解
介绍见文章开头部分。
- 潜在因子—— 用户矩阵Q 这个矩阵表示不同用户对于不同元素的偏好程度, 1代表很喜欢, 0代表不喜欢, 比如下面这样:
- 潜在因子——音乐矩阵P 表示每种音乐含有各种元素的成分, 比如下表中, 音乐A是一个偏小清新的音乐, 含有小清新的Latent Factor的成分是0.9, 重口味的成分是0.1, 优雅成分0.2…
选择张三和音乐A两个部分做内积操作, 0.6 ∗ 0.9 + 0.8 ∗ 0.1 + 0.1 ∗ 0.2 + 0.1 ∗ 0.4 + 0.7 ∗ 0 = 0.68 0.6*0.9+0.8*0.1+0.1*0.2+0.1*0.4+0.7*0=0.68 0.6∗0.9+0.8∗0.1+0.1∗0.2+0.1∗0.4+0.7∗0=0.68
所以张三对音乐A的喜欢程度为0.68
按照这个计算方式, 每个用户对每首歌其实都可以得到这样的分数, 最后就得到了我们的评分矩阵:
这个部分与深度学习中的embedding相似。
但在实际业务中,很难获得到如上表中的数据,实际数据是一个更加稀疏的矩阵。
在矩阵分解的算法框架下, 可以通过分解协同过滤的共现矩阵(评分矩阵)来得到用户和物品的隐向量,原理如下:
根据用户矩阵和物品矩阵来计算用户u对物品i的评分
公式如下:
r u i = p u T q i = ∑ k = 1 K p u , k q i , k r_{ui} = p_u^T q_i = \sum_{k=1}^{K} p_{u,k} q_{i,k} rui=puTqi=∑k=1Kpu,kqi,k
下面用一个完整的例子来演示如何用矩阵分解计算
以上图的稀疏物品矩阵为例,我们得到了用户矩阵和物品矩阵,这里的隐向量的值K = 2(2 x 2 = 4)
在实际情况下,这一步分解要用到梯度下降或者ALS算法来决定K的取值
此时计算用户A对物品Z的评分,计算过程如下:
$ r_{A3} = p_A^T q_3 = (1.2, 0.8) \cdot (0.8, 0.4)^T = 1.2 \times 0.8 + 0.8 \times 0.4 = 0.96 + 0.32 = 1.28 $
街头评测一般会定一个打分范围,如1到100分,而这个用户A对物品Z的评分也是如此,比如我们取评分范围为1-10。如果计算的评分超过10就按10算,同样,低于1就按1算。
import random
import math
class BiasSVD():
def __init__(self, rating_data, F=5, alpha=0.1, lmbda=0.1, max_iter=100):
self.F = F # 这个表示隐向量的维度
self.P = dict() # 用户矩阵P 大小是[users_num, F]
self.Q = dict() # 物品矩阵Q 大小是[item_nums, F]
self.bu = dict() # 用户偏置系数
self.bi = dict() # 物品偏置系数
self.mu = 0 # 全局偏置系数
self.alpha = alpha # 学习率
self.lmbda = lmbda # 正则项系数
self.max_iter = max_iter # 最大迭代次数
self.rating_data = rating_data # 评分矩阵
for user, items in self.rating_data.items():
# 初始化矩阵P和Q, 随机数需要和1/sqrt(F)成正比
self.P[user] = [random.random() / math.sqrt(self.F) for x in range(0, F)]
self.bu[user] = 0
for item, rating in items.items():
if item not in self.Q:
self.Q[item] = [random.random() / math.sqrt(self.F) for x in range(0, F)]
self.bi[item] = 0
# 采用随机梯度下降的方式训练模型参数
def train(self):
cnt, mu_sum = 0, 0
for user, items in self.rating_data.items():
for item, rui in items.items():
mu_sum, cnt = mu_sum + rui, cnt + 1
self.mu = mu_sum / cnt
for step in range(self.max_iter):
# 遍历所有的用户及历史交互物品
for user, items in self.rating_data.items():
# 遍历历史交互物品
for item, rui in items.items():
rhat_ui = self.predict(user, item) # 评分预测
e_ui = rui - rhat_ui # 评分预测偏差
# 参数更新
self.bu[user] += self.alpha * (e_ui - self.lmbda * self.bu[user])
self.bi[item] += self.alpha * (e_ui - self.lmbda * self.bi[item])
for k in range(0, self.F):
self.P[user][k] += self.alpha * (e_ui * self.Q[item][k] - self.lmbda * self.P[user][k])
self.Q[item][k] += self.alpha * (e_ui * self.P[user][k] - self.lmbda * self.Q[item][k])
# 逐步降低学习率
self.alpha *= 0.1
# 评分预测
def predict(self, user, item):
return sum(self.P[user][f] * self.Q[item][f] for f in range(0, self.F)) + self.bu[user] + self.bi[
item] + self.mu
# 通过字典初始化训练样本,分别表示不同用户(1-5)对不同物品(A-E)的真实评分
def loadData():
rating_data={1: {'A': 5, 'B': 3, 'C': 4, 'D': 4},
2: {'A': 3, 'B': 1, 'C': 2, 'D': 3, 'E': 3},
3: {'A': 4, 'B': 3, 'C': 4, 'D': 3, 'E': 5},
4: {'A': 3, 'B': 3, 'C': 1, 'D': 5, 'E': 4},
5: {'A': 1, 'B': 5, 'C': 5, 'D': 2, 'E': 1}
}
return rating_data
# 加载数据
rating_data = loadData()
# 建立模型
basicsvd = BiasSVD(rating_data, F=10)
# 参数训练
basicsvd.train()
# 预测用户1对物品E的评分
for item in ['E']:
print(item, basicsvd.predict(1, item))
# 预测结果:E 3.685084274454321
FunkSVD
原理:将原始评分矩阵 R R R分解为两个低秩矩阵 P P P(用户矩阵)和 Q Q Q(物品矩阵),并通过最小化误差来学习这两个矩阵。目标是使得对于每个已知评分 r u i r_{ui} rui,其预测值 r ^ u i = p u T q i \hat{r}_{ui} = p_u^T q_i r^ui=puTqi尽可能接近真实值。
优化目标:
min
q
∗
,
p
∗
1
2
∑
(
u
,
i
)
∈
K
(
r
u
i
−
p
u
T
q
i
)
2
\min_{q^*,p^*} \frac{1}{2} \sum_{(u,i) \in K}(r_{ui} - p_u^T q_i)^2
minq∗,p∗21∑(u,i)∈K(rui−puTqi)2
其中, K K K表示所有用户评分样本的集合。
梯度更新规则:
- 对于用户向量
p
u
,
k
p_{u,k}
pu,k:
p u , k = p u , k + η e u i q i , k p_{u,k} = p_{u,k} + \eta e_{ui} q_{i,k} pu,k=pu,k+ηeuiqi,k - 对于物品向量
q
i
,
k
q_{i,k}
qi,k:
q i , k = q i , k + η e u i p u , k q_{i,k} = q_{i,k} + \eta e_{ui} p_{u,k} qi,k=qi,k+ηeuipu,k
其中, e u i = r u i − r ^ u i e_{ui} = r_{ui} - \hat{r}_{ui} eui=rui−r^ui, η \eta η是学习率。
RSVD(加入正则项)
为了避免过拟合,在FunkSVD的基础上加入L2正则项:
优化目标:
min
q
∗
,
p
∗
1
2
∑
(
u
,
i
)
∈
K
(
r
u
i
−
p
u
T
q
i
)
2
+
λ
(
∥
p
u
∥
2
+
∥
q
i
∥
2
)
\min_{q^*,p^*} \frac{1}{2} \sum_{(u,i) \in K}(r_{ui} - p_u^T q_i)^2 + \lambda (\|p_u\|^2 + \|q_i\|^2)
minq∗,p∗21∑(u,i)∈K(rui−puTqi)2+λ(∥pu∥2+∥qi∥2)
这有助于控制模型复杂度,防止参数过大导致过拟合。
BiasSVD
考虑了用户和物品的偏置因素,改进了预测公式,使其更加准确地反映实际情况。
预测公式:
r
^
u
i
=
μ
+
b
u
+
b
i
+
p
u
T
⋅
q
i
\hat{r}_{ui} = \mu + b_u + b_i + p_u^T \cdot q_i
r^ui=μ+bu+bi+puT⋅qi
其中, μ \mu μ是全局平均评分, b u b_u bu是用户偏差系数, b i b_i bi是物品偏差系数。
优化目标:
min
q
∗
,
p
∗
1
2
∑
(
u
,
i
)
∈
K
(
r
u
i
−
(
μ
+
b
u
+
b
i
+
q
i
T
p
u
)
)
2
+
λ
(
∥
p
u
∥
2
+
∥
q
i
∥
2
+
b
u
2
+
b
i
2
)
\min_{q^*,p^*} \frac{1}{2} \sum_{(u,i) \in K}(r_{ui} - (\mu + b_u + b_i + q_i^T p_u))^2 + \lambda (\|p_u\|^2 + \|q_i\|^2 + b_u^2 + b_i^2)
minq∗,p∗21∑(u,i)∈K(rui−(μ+bu+bi+qiTpu))2+λ(∥pu∥2+∥qi∥2+bu2+bi2)
梯度更新规则(增加对偏置项的更新):
-
b
u
b_u
bu的更新:
b u = b u + η ( e u i − λ b u ) b_u = b_u + \eta (e_{ui} - \lambda b_u) bu=bu+η(eui−λbu) -
b
i
b_i
bi的更新:
b i = b i + η ( e u i − λ b i ) b_i = b_i + \eta (e_{ui} - \lambda b_i) bi=bi+η(eui−λbi)
假设我们有如下简化后的数据集和初始参数:
- 用户A对物品3的真实评分为5。
- 初始参数设置: p A = [ 0.6 , 0.8 ] p_A = [0.6, 0.8] pA=[0.6,0.8], q 3 = [ 2.5 , 6.5 ] q_3 = [2.5, 6.5] q3=[2.5,6.5], μ = 3 \mu=3 μ=3, b A = 0.5 b_A=0.5 bA=0.5, b 3 = 1 b_3=1 b3=1, λ = 0.01 \lambda=0.01 λ=0.01, η = 0.001 \eta=0.001 η=0.001。
首先计算预测评分:
r
^
A
3
=
3
+
0.5
+
1
+
0.6
×
2.5
+
0.8
×
6.5
=
10.2
\hat{r}_{A3} = 3 + 0.5 + 1 + 0.6 \times 2.5 + 0.8 \times 6.5 = 10.2
r^A3=3+0.5+1+0.6×2.5+0.8×6.5=10.2
计算误差:
e
A
3
=
5
−
10.2
=
−
5.2
e_{A3} = 5 - 10.2 = -5.2
eA3=5−10.2=−5.2
根据误差更新参数(仅展示一次迭代):
- 更新
p
A
p_A
pA和
q
3
q_3
q3(这里只展示一个维度作为例子):
p A , 1 = 0.6 + 0.001 × ( − 5.2 ) × 2.5 = 0.587 p_{A,1} = 0.6 + 0.001 \times (-5.2) \times 2.5 = 0.587 pA,1=0.6+0.001×(−5.2)×2.5=0.587
q 3 , 1 = 2.5 + 0.001 × ( − 5.2 ) × 0.6 = 2.469 q_{3,1} = 2.5 + 0.001 \times (-5.2) \times 0.6 = 2.469 q3,1=2.5+0.001×(−5.2)×0.6=2.469 - 更新偏置项:
b A = 0.5 + 0.001 × ( − 5.2 − 0.01 × 0.5 ) ≈ 0.494 b_A = 0.5 + 0.001 \times (-5.2 - 0.01 \times 0.5) \approx 0.494 bA=0.5+0.001×(−5.2−0.01×0.5)≈0.494
b 3 = 1 + 0.001 × ( − 5.2 − 0.01 × 1 ) ≈ 0.994 b_3 = 1 + 0.001 \times (-5.2 - 0.01 \times 1) \approx 0.994 b3=1+0.001×(−5.2−0.01×1)≈0.994
通过多次迭代上述过程,可以逐步优化模型参数,以减小预测误差,提高评分预测的准确性。
部分额外的专业术语
召回率(Recall)
召回率衡量的是推荐系统能够成功找到用户实际喜欢的物品的能力。它反映了在所有用户真正感兴趣的物品中,推荐系统能正确预测出的比例。
-
公式:
Recall = ∑ u ∣ R ( u ) ∩ T ( u ) ∣ ∑ u ∣ T ( u ) ∣ \text{Recall} = \frac{\sum_u |R(u) \cap T(u)|}{\sum_u |T(u)|} Recall=∑u∣T(u)∣∑u∣R(u)∩T(u)∣
其中 $ R(u) $ 是为用户 $ u $ 推荐的物品集合,$ T(u) $ 是用户 $ u $ 在测试集中实际喜欢的物品集合。 -
含义: 召回率越高,说明推荐系统越有能力覆盖到用户真正的兴趣点。
精确率(Precision)
精确率关注的是在推荐给用户的物品中,有多少是用户实际上感兴趣的。
-
公式:
Precision = ∑ u ∣ R ( u ) ∩ T ( u ) ∣ ∑ u ∣ R ( u ) ∣ \text{Precision} = \frac{\sum_u |R(u) \cap T(u)|}{\sum_u |R(u)|} Precision=∑u∣R(u)∣∑u∣R(u)∩T(u)∣
这里的 $ R(u) $ 和 $ T(u) $ 定义同上。 -
含义: 精确率高意味着推荐系统的推荐质量较高,推荐的物品大多数都是用户感兴趣的。
覆盖率(Coverage)
覆盖率用来评估推荐算法能否有效地向用户推荐长尾中的物品,即那些不那么流行的物品。
-
公式:
Coverage = ∣ ⋃ u ∈ U R ( u ) ∣ ∣ I ∣ \text{Coverage} = \frac{|\bigcup_{u \in U} R(u)|}{|I|} Coverage=∣I∣∣⋃u∈UR(u)∣
其中 $ U $ 是所有用户的集合,$ R(u) $ 是给用户 $ u $ 的推荐列表,$ I $ 是所有可能的物品集合。 -
含义: 覆盖率高表示推荐系统可以将更多种类的物品展示给用户,尤其是那些不太热门但仍然有价值的物品。
新颖度(Novelty)
新颖度指的是推荐系统推荐的物品的新鲜程度或独特性。通常通过推荐列表中物品的平均流行度来度量。由于流行度分布通常是长尾分布,因此在计算时会对每个物品的流行度取对数以稳定平均值。
-
公式:
Novelty ( R ( u ) ) = − 1 ∣ R ( u ) ∣ ∑ i ∈ R ( u ) log ( popularity ( i ) ) \text{Novelty}(R(u)) = -\frac{1}{|R(u)|} \sum_{i \in R(u)} \log(\text{popularity}(i)) Novelty(R(u))=−∣R(u)∣1i∈R(u)∑log(popularity(i))
其中 $ R(u) $ 是给用户 $ u $ 的推荐列表,$ \text{popularity}(i) $ 表示物品 $ i $ 的流行度(例如被浏览次数)。 -
含义: 高新颖度意味着推荐系统不仅能够发现并推荐热门物品,还能够挖掘并推荐相对较少人知道但同样有趣或有用的物品。