1. 协同过滤算法是什么?
协同过滤(Collaborative Filtering)推荐算法是最经典、最常用的推荐算法。
1.1 基本思想
- 根据用户的之前的喜好以及其他兴趣相近的选择来给用户推荐物品(基于对用户历史数据的挖掘,发现用户的喜欢偏好,进而预测用户可能喜欢的产品进行推荐)。
- 一般仅仅基于用户的历史行为数据,不依赖于其他任何附加项的信息。
1.2 目前比较广泛的CF算法是基于邻域的方法,如下:
- 基于用户的协同过滤算法(UserCF):给用户推荐和他兴趣相似的其他用户喜欢的产品
- 基于物品的协同过滤算法(ItermCF):给用户推荐和他之前喜欢的物品相似的物品
2. 相似性度量方法
2.1 杰卡德(Jaccard)相似系数
这是衡量两个集合的相似度的一种指标,两个用户u和V交互商品交集占并集的数量的比值;称为两个集合的杰卡德相似系数,用符号sim_{uv}表示,其中N(u),N(v)分别表示用户u和用户v交互商品的集合。
常用来评估某用户是否会对某商品进行打分.
2.2 余弦相似度
余弦值在[0, π]的区间范围单调递减,故余弦值越小,角度越大;反之,余弦值越大,角度越小.
故在集合的角度来描述交互的商品数量的乘积, 公式如下:
这个在具体实现的时候, 可以使用sklearn库的cosine_similarity进行实现:
from sklearn.metrics.pairwise import cosine_similarity
i = [1, 0, 0, 0]
j = [1, 0.5, 0.5, 0]
consine_similarity([a, b])
2.3 皮尔逊相关系数
相比余弦相似度,皮尔逊相关系数通过使用用户的品均分对各个独立评分进行了修正,减少了用户评分偏置的影响(如用户A习惯打高分/用户B习惯只打低分).
调包实现皮尔逊评分:
from scipy.stats import pearsonr
i = [1, 0, 0, 0]
j = [1, 0.5, 0.5, 0]
pearsonr(i, j)
3. 基于用户的协同过滤(UserCF) : 猜测用户对某件物品的喜欢程度
最终结果的预测 加权近似估算
利用用户相似度和相似用户的评价加权平均获得用户的评价预测, 用下面式子表示:
(u,p为用户, s为商品, 权重w_{u,s}是用户u和用户s的相似度, R_{s,p}是用户s对物品p的评分。)
优化版本: 用户相似度作为权值, 但后面不单纯的是其他用户对物品的评分, 而是该物品的评分与此用户的所有评分的差值进行加权平均, 这时候考虑到了有的用户内心的评分标准不一的情况, 即有的用户喜欢打高分, 有的用户喜欢打低分的情况。
动手计算
计算余弦相似性
from sklearn.metrics.pairwise import cosine)similarity
计算协相关性
import numpy as np
np.corrcoef([...])
4. UserCF编程实现
总结简单三步骤: 计算用户相似性矩阵、得到前N个用户相似用户、计算最终得分。
# **4.1 首先, 准备物品-人员 历史评分表**
items = loadData()
item_df = pd.DataFrame(items).T
print(item_df)
"""
1 2 3 4 5
A 5.0 3.0 4.0 3.0 1.0
B 3.0 1.0 3.0 3.0 5.0
C 4.0 2.0 4.0 1.0 5.0
D 4.0 3.0 3.0 5.0 2.0
E NaN 3.0 5.0 4.0 1.0
"""
# **4.2 计算用户相似性矩阵**
user_similar_matrix = pd.DataFrame(np.zeros((5, 5)), index=['A', 'B', 'C', 'D', 'E'], columns=['A', 'B', 'C', 'D', 'E'])
pos_dict = 'ABCDE'
len = item_df.shape[0]
for i in range(0, len):
for j in range(0, len):
if i==j: continue
ith = np.array(item_df.iloc[i,:])
jth = np.array(item_df.iloc[j,:])
# 去除两者之一有None的商品
for ienum in range(ith.size - 1, -1, -1):
if np.isnan(ith[ienum]) or np.isnan(jth[ienum]):
ith = np.delete(ith, ienum)
jth = np.delete(jth, ienum)
# print(ith, '-', jth)
x = np.corrcoef(ith, jth, rowvar=False)
# print(x)
user_similar_matrix[pos_dict[i]][pos_dict[j]] = x[0][1]
print(user_similar_matrix)
"""
A B C D E
A 0.000000 -0.476731 -0.123091 0.532181 0.969458
B -0.476731 0.000000 0.645497 -0.310087 -0.478091
C -0.123091 0.645497 0.000000 -0.720577 -0.427618
D 0.532181 -0.310087 -0.720577 0.000000 0.581675
E 0.969458 -0.478091 -0.427618 0.581675 0.000000
"""
# **4.3 计算得出用户E的最相似的几个用户**
n=3
similarity_users = user_similar_matrix['E'].sort_values(ascending=False)[:n].index.tolist()[0:-1]
print('similarity_items:
', similarity_users)
# ['A', 'D', 'E']
# **4.4 近似加权求解** 只针对商品'1'
"""计算最终得分"""
base_score = np.mean(item_df[1])
weighted_scores = 0.
corr_values_sum = 0.
for user in similarity_users: # [2, 3]
corr_value = user_similar_matrix[user][1] # 两个用户之间的相似性
mean_user_score = np.mean(item_df.loc[user]) # 每个用户的打分平均值
weighted_scores += corr_value * (item_df.loc[user][1]-mean_user_score) # 加权分数
corr_values_sum += corr_value
final_scores = base_score + weighted_scores / corr_values_sum
print('用户E对物品1的打分: ', final_scores)
item_df.loc['E'][1] = final_scores
print(item_df)
"""
similarity_items: ['A', 'D']
用户E对物品1的打分: 5.327077237976979
1 2 3 4 5
A 5.000000 3.0 4.0 3.0 1.0
B 3.000000 1.0 3.0 3.0 5.0
C 4.000000 2.0 4.0 1.0 5.0
D 4.000000 3.0 3.0 5.0 2.0
E 5.327077 3.0 5.0 4.0 1.0
"""
5. UserCF优缺点
优点:
- 适用于用户规模较少,不需要掌握专门的领域知识
- 可以帮助用户发现潜在的兴趣点(基于相似人群的兴趣数据)
缺点:
- 数据稀疏性: 稀疏;一个网站的商品数量远大于用户数量,不同用户之间购买的商品重叠性极低;导致算法无法找到合适的用户邻居,即根据共同的购买经历来确定的相似用户。(这导致UserCF不适用于那些正反馈获取较为困难的应用场景,如酒店预订,大件商品的购买等低频应用)
- 算法扩展性:随着用户数量剧增后变差
基于用户的协同过滤需要维护用户相似度矩阵,以便于快速地找出TopN相似的用户,随着用户数量——平方倍数的增加,也不适用于用户量特别大的情况下使用。 - 无法冷启动,处理新的项目
综上,用于UserCF上的几点缺陷,导致很多电商平台没有使用这种算法,而是采用ItermCF的算法来实现最初的推荐系统。
6. 基于物品的协同过滤
基本思想
ItemCF不计算物品的属性相似度,而是根据用户的历史偏好数据来计算物品之间的相似性,比如很多人买了A之后也买了B,那么推荐用户A在买了A之后,也推荐B。
该算法认为, 物品a和物品c具有很大的相似度是因为喜欢物品a的用户大都喜欢物品c。
- 根据用户的历史行为数据, 计算物品之间的相似度 - 根据物品的相似度和用户的历史行为数据来给用户生成推荐列表 (购买了该商品的用户也经常购买了其他商品)
ItemCF步骤
- 将每个物品的评分集合,作为一个向量
- 计算向量之间最近的几个物品
- 找出需要评分的物品的最相近的几个物品去进行打分
ItermCF代码实践
总结简单三步骤: 计算物品相似性矩阵、得到前N个相似物品、计算最终得分。
# **6.1 首先, 准备物品-人员 历史评分表**
items = loadData()
item_df = pd.DataFrame(items).T
print(item_df)
"""
1 2 3 4 5
A 5.0 3.0 4.0 3.0 1.0
B 3.0 1.0 3.0 3.0 5.0
C 4.0 2.0 4.0 1.0 5.0
D 4.0 3.0 3.0 5.0 2.0
E NaN 3.0 5.0 4.0 1.0
"""
# 6.2 计算物品相似性矩阵
iterm_similar_matrix = pd.DataFrame(np.zeros((5, 5)), index=['1', '2', '3', '4', '5'], columns=['1', '2', '3', '4', '5'])
pos_dict = '12345'
len = item_df.shape[0]
for i in range(0, len):
for j in range(0, len):
if i==j: continue
ith = np.array(item_df[i+1])
jth = np.array(item_df[j+1])
# 去除两者之一有None的商品
for ienum in range(ith.size - 1, -1, -1):
if np.isnan(ith[ienum]) or np.isnan(jth[ienum]):
ith = np.delete(ith, ienum)
jth = np.delete(jth, ienum)
# print(ith, '-', jth)
x = np.corrcoef(ith, jth, rowvar=False)
# print(x)
iterm_similar_matrix[pos_dict[i]][pos_dict[j]] = x[0][1]
print(iterm_similar_matrix)
"""
1 2 3 4 5
1 0.000000 0.852803 0.707107 0.000000 -0.792118
2 0.852803 0.000000 0.467707 0.489956 -0.900149
3 0.707107 0.467707 0.000000 -0.161165 -0.466569
4 0.000000 0.489956 -0.161165 0.000000 -0.641503
5 -0.792118 -0.900149 -0.466569 -0.641503 0.000000
"""
# **6.3 计算得出和物品1的最相似的几个物品**
n=3
similarity_items = iterm_similar_matrix['1'].sort_values(ascending=False)[:n].index.tolist()[0:-1]
print('similarity_items:
', similarity_items)
# ['2', '3']
# 其他同UserCF
# **4.4 近似加权求解** 只针对商品'1'
"""计算最终得分"""
base_score = np.mean(item_df[1])
weighted_scores = 0.
corr_values_sum = 0.
for item in similarity_items: # [2, 3]
item = int(item)
corr_value = iterm_similar_matrix[str(item)][1] # 两个物品之间的相似性
mean_user_score = np.mean(item_df[item]) # 每个物品的打分平均值
weighted_scores += corr_value * (item_df.loc['E'][item] - mean_user_score) # 加权分数
corr_values_sum += corr_value
final_scores = base_score + weighted_scores / corr_values_sum
print('物品2,3 对物品1的打分: ', final_scores)
item_df.loc['E'][1] = final_scores
print(item_df)
"""
物品2,3 对物品1的打分: 5.2
1 2 3 4 5
A 5.0 3.0 4.0 3.0 1.0
B 3.0 1.0 3.0 3.0 5.0
C 4.0 2.0 4.0 1.0 5.0
D 4.0 3.0 3.0 5.0 2.0
E 5.2 3.0 5.0 4.0 1.0
"""
7. 算法评估&评测指标 (集合法)
基于用户或物品的协同过滤的评测指标基本一致,如下:
- R表示投放的推荐商品
- T表示实际喜欢的物品
- R ∩ T = 推荐成功的
- 召回率 (较全面的体现)
- 精确率 (在推荐次数有限时使用,需要有把握地推荐)
- 覆盖率
覆盖率反映了推荐算法发掘长尾的能力, 覆盖率越高, 说明推荐算法越能将长尾中的物品推荐给用户。
- 新颖度
用推荐列表中物品的平均流行度度量推荐结果的新颖度。 如果推荐出的物品都很热门, 说明推荐的新颖度较低. 由于物品的流行度分布呈长尾分布, 所以为了流行度的平均值更加稳定, 在计算平均流行度时对每个物品的流行度取对数。
8. 协同过滤算法的权重改进
- 基础算法
图1为最简单的计算物品相关度的公式, 分子为同时喜好itemi和itemj的用户数. - 对热门物品的惩罚 (避免过热的商品带偏
图1存在一个问题, 如果 item-j 是很热门的商品,导致很多喜欢 item-i 的用户都喜欢 item-j,这时 w_{ij} 就会非常大。同样,几乎所有的物品都和 item-j 的相关度非常高,这显然是不合理的。所以图2中分母通过引入 N(j) 来对 item-j 的热度进行惩罚。如果物品很热门, 那么N(j)就会越大, 对应的权重就会变小。 - 对热门物品的进一步惩罚
如果 item-j 极度热门,上面的算法还是不够的。举个例子,《Harry Potter》非常火,买任何一本书的人都会购买它,即使通过图2的方法对它进行了惩罚,但是《Harry Potter》仍然会获得很高的相似度。这就是推荐系统领域著名的 Harry Potter Problem。
如果需要进一步对热门物品惩罚,可以继续修改公式为如图3所示,通过调节参数 α,α 越大,惩罚力度越大,热门物品的相似度越低,整体结果的平均热门程度越低。 - 对活跃用户的惩罚
同样的,Item-based CF 也需要考虑活跃用户(即一个活跃用户(专门做刷单)可能买了非常多的物品)的影响,活跃用户对物品相似度的贡献应该小于不活跃用户。图4为集合了该权重的算法。
9. 协同过滤算法的问题分析
协同过滤算法存在的问题之一就是泛化能力弱, 即协同过滤无法将两个物品相似的信息推广到其他物品的相似性上。 导致的问题是热门物品具有很强的头部效应, 容易跟大量物品产生相似, 而尾部物品由于特征向量稀疏, 导致很少被推荐。
协同过滤的天然缺陷:推荐系统头部效应明显, 处理稀疏向量的能力弱。
矩阵分解技术(Matrix Factorization,MF)被提出, 该方法在协同过滤共现矩阵的基础上, 使用更稠密的隐向量表示用户和物品, 挖掘用户和物品的隐含兴趣和隐含特征, 在一定程度上弥补协同过滤模型处理稀疏矩阵能力不足的问题。
10. 参考资料
- datawhale github 《02-协同过滤》
- 基于用户的协同过滤来构建推荐系统:https://mp.weixin.qq.com/s/ZtnaQrVIpVOPJpqMdLWOcw
- 协同过滤算法概述:https://chenk.tech/posts/8ad63d9d.html
- B站黑马推荐系统实战课程