推荐系统的召回算法(一)——协同过滤法(基于用户)

推荐系统的召回算法(⼀)——协同过滤法(基于⽤户)
  姗姗来迟的第⼆篇博客,最近在了解有关推荐系统⽅⾯的基本知识和算法,先总结其中⼀类经典常⽤的算法——协同过滤法。⽹上已有很多介绍其原理的好⽂章,所以本⽂⽤较多篇幅来写⼀些⾃⾝对算法实现的理解和疑惑,希望有经验的⼩伙伴能够解惑,感激不尽!  写的⽐较仓促,如有描述或理解错误,敬请指出,望共同进步~
⽬录
为何做推荐系统
  先介绍下推荐系统中经常出现的两个术语:User 和 Item,即⽤户和物品,这⾥的物品可以是书籍、电影、新闻、视频等。
  什么情况下需要推荐系统?——信息过载且⽤户没有明确的需求。简单理解信息过载就是Item数量⾮常多,⽤户⼀时半会⼉不知道想要什么,这时候推荐系统就可以主动向⽤户提供他们可能感兴趣的物品。所以在做推荐系统前需要了解两件事⼉:
(1)平台上Item的数量是否过多;如果数量不⼤,可以⽤分类⽬录的形式对Item进⾏分门别类,更⽅便⽤户到适合⾃⼰的Item.
(2)⽤户每次使⽤平台时,⾃⾝的需求是否明确.
  以抖⾳app为例⼦,⾥⾯的短视频千千万,⽤户⼀般并不清楚⾃⼰具体想看什么,推荐就成了留住⽤户的最好⽅式,所以抖⾳的主界⾯就是个推荐场景(其实还有个搜索界⾯);还有淘宝app和云⾳乐app等,⾥⾯的推荐场景随处可见。
召回和排序
  回归正题,推荐系统⼀般包含两个算法部分:召回阶段(recall)和 排序阶段(rank)。先说排序阶段的作⽤,也是传统机器学习、深度学习在推荐系统中发挥威⼒的地⽅,它们可以预测⽤户对物品的点击率、转化率等(类似⼴告中CTR模型),再以特定的规则进⾏排序后展⽰给⽤户,更符合规则的物品做优先展⽰。但是不可能把所有的物品⼀次性喂⼊排序模型做预测排序(数量太多,算完⽤户都跑了),所以召回阶段的作⽤就是把所有物品做初步的筛选形成⼀个物品候选集,再进⼊排序阶段。
  排序阶段⾥往往都是⾼⼤上的模型,似乎直接决定推荐结果,其实召回阶段产⽣的Item候选集也会对最终推荐结果产⽣很⼤影响。⽐如⼩明购买过⼀个⽔杯,然后召回阶段产⽣的候选集⼏乎由杯⼦这⼀类的物品组成,导致排序阶段输出的Top-N物品全是五花⼋门的杯⼦。。。显然这样的召回策略缺乏多样性,使得排序模型⽆论多精确都⽆法摆脱只推荐杯⼦的结局。
  本⽂重点介绍召回阶段最常见的⼀类算法——协同过滤法(Collaborative Filtering),做推荐的⼩伙伴们都应该很熟悉吧。相⽐ML、DL这些模型算法,CF⼏乎没有什么深奥的数学原理,注重的是背后原理和思想。
协同过滤法—基于⽤户(UserCF )
  协同过滤法属于召回阶段的算法,简单有效的特点使其仍未过时,现有的各⼤推荐场景中都能看到这类算法的影⼦。基本思想就是基于⽤户或物品的相似度做推荐,下⾯就介绍其中⼀种。
UserCF 原理
  基于⽤户的协同过滤法,基本思想是寻⽤户A的相似⽤户B,把⽤户B接触过的物品b推荐给⽤户A。借鉴《推荐系统实践》⼀书中的说法,该算法主要包括两个步骤:
  (1)到和⽬标⽤户A兴趣相似的⽤户集合;
  (2)到这个集合中的⽤户喜欢的,且⽬标⽤户A没有发⽣⾏为的物品推荐给⽬标⽤户A。
  如何衡量⽤户的相似度呢?⾸先确定计算相似度所需的⽤户数据来源,我第⼀反应就是⽤户的画像数据(性别、⾝⾼、收⼊等),但是仔细想想画像数据并不能完全反映⼀个⽤户在平台⾥的兴趣偏好。相⽐之下,⽤户在平台上的⾏为数据更能体现其在平台中的兴趣偏好,也更适⽤于计算⽤户相似度,⽐如抖⾳中的点赞、页⾯停留时长;淘宝中的浏览、加购、购买、评分等⾏为数据。
  其次确定采⽤何种相似度指标,以下列举⼏个常见的相似度计算公式:
  (⼀)对于停留时长、评分等连续型⾏为数据
  假设⽤户a和⽤户b对所有Item的评分(停留时长)向量分别为 A 和 B,其中, ,则有:
A =(a ,a ,...,a )12n
B =(b ,b ,...,b )12n
余弦相似度
  最初⽤于⽂本相似度的计算。从⼆维和三维的⾓度来看,该指标就是两个向量间夹⾓的余弦值,取值范围为[0,1],越接近1说明两个向量越相似。
  公式中分母做了类似标准化(去量纲)的操作,图中可看出该指标以向量间的夹⾓来衡量相似度,所以忽略了向量本⾝的模长。
欧式距离(Euclidean)
  欧⽒距离是⾮常直观的距离指标,属于闵可夫斯基距离(Minkowski)的⼀种特殊形式,类似的还有曼哈顿距离(Manhattan)、切⽐雪夫距离(Chebyshev),这些距离指标在K-means、KNN等算法中⼀般都有详细介绍。
  可将欧⽒距离做变型:
,⽤该值衡量A,B向量的相似度更合适。相⽐余弦相似度,欧⽒距离从向量各个分量间的差值体现两者距离(差异)。
Pearson相关系数
其中, 。
  这是统计学中常⽤的相关性指标,⽤于描述两个变量间的线性相关性。在余弦相似度的基础上,对分⼦分母均做中⼼化(去均值)处理,所以也被称为调整余弦相似度。
  个⼈对采⽤这个指标有些疑惑:⾸先相关系数取值为[-1,1],如果,说明⽤户A和⽤户B对item的某⾏为数据呈负相关关系,假设该⾏为是浏览时长,然后把⽤户B浏览时长短的item推给⽤户A?还是只截取与⽤户A相关系数为正的⽤户,作为推荐给A的相似⽤户集?
  (⼆)对于购买、浏览等离散型⾏为数据
  假设⽤户A和⽤户B的购买(浏览等)物品集合分别为N(a)和N(b),则有:
cos A ,B =⟨⟩=∣∣A ∣∣×∣∣B ∣∣A ⋅B ×a ∑i =1n (i )2b ∑i =1n (i )2a b ∑i =1n
i i d (A ,B )==(a −b )+(a −b )+...+(a −b )112222n n 2(∣a −b ∣)i =1∑n
i i 21/21+d (A ,B )1cor A ,B =()=σ⋅σA B E [(A −μ)(B −μ)]
A B ×(a −)n 1∑i =1n i a ˉ2(b −)n 1∑i =1n i b ˉ2(a −)(b −)n 1∑i =1n
i a ˉi b ˉ=a ˉa n 1∑i =1n i =b ˉb n 1∑i =1n i cor (A ,B )<0
Jaccard相似度
余弦相似度
  以上两个指标中,冷门物品和热门物品对⽤户相似度的权重是相同的,但实际上两个⽤户对冷门物品的⾏为更能说明他们兴趣的相似度——《推荐系统实践》,所以John S. Breese提出了⼀种改进的相似度指标。
改进的相似度
离线浏览  其中为对物品i有过购买(浏览等)⾏为的⽤户集,
表⽰物品i对相似度的影响,不难发现物品的购买⼈越多(越热门),其对相似度的影响越⼩,反之冷门物品的影响越⼤;所以改进后的指标更能刻画⽤户间的个⼈偏好相似度。
本节总结和思考
  1、⼀般平台埋点数据返回的⾏为字段有很多,可以计算⽤户间多个⾏为的相似度,⽐如购买⾏为的相似度、浏览时长的相似度等等,那么如何整合这些相似度来得到最终的候选集呢?
(a)利⽤每个相似度指标,产⽣个候选集,取并集作为召回阶段最终输出的候选集;
(b)根据实际业务对每个相似度赋予合适的权重,⽐如认为购买⾏为⽐浏览⾏为更反应出⽤户的兴趣偏好,那么⽤购买⾏为数据算出的相似度指标权重应该更⼤,再通过加权平均的⽅式作为最终⽤户间的相似度,然后再得到输出的候选集.
【第⼀种得到的候选集感觉更偏重多样性,第⼆种做法中如果各权重定的好,相似⽤户似乎更准确;所以不知道⽤哪种⽅式整合两种相似度,或者说实际应⽤中还有更好的⽅案.】
  2、各类指标各有优劣,可根据对具体业务的理解进⾏取舍,如果不同指标对最终的推荐结果影响不⼤,优先采取计算时间短的。(对于⾼维稀疏向量,余弦相似度的计算速度应该⽐较快.)
  3、实际中离散型的⾏为数据应该更常见吧,业务平台初期可能没有太多的⾏为数据埋点,但肯定有最基本的交易⽇志数据(买卖交易平台),就可以从购买⾏为来计算⽤户的相似度,后期再增加新的⾏为埋点数据。
  4、冷启动问题,计算相似度的前提是⽤户发⽣过⾏为,所以对于新⽤户⽽⾔,该算法暂时会失效。列举两种常见的解决办法:
(a)直接向新⽤户推送近期的热门物品。毕竟受⼤部分⽤户喜爱的物品能⼤概率激发新⽤户的兴趣,或者说被新⽤户厌恶的概率很低,但显然缺乏个性化;
(b)当新⽤户第⼀次进⼊平台时,让其选择⾃⾝感兴趣的Item类别。我记得CSDN就有这个功能,给出了Java开发、⼈⼯智能等⼏个选项,然后根据新⽤户的选项做博客推送。
【也有很多平台会接⼊第三⽅的数据库得到新⽤户在其他平台(如vx、微博等)的画像数据,⽤于解决冷启动问题。】实现过程
  推荐系统需要具备⼀定的实时性,即⽤户进⼊业务平台后会⽴即给出对应的推荐结果,或者推荐结果随着⽤户的⾏为时时更新。⽐如在淘宝app中⽤户刚浏览过某物品后,类似的商品下⼀秒就出现在推荐列表中,⽤户不仅觉得体验很棒,还会惊叹这个系统好 “ 智能 ”的样
⼦ 。
  为了尽量实现上述流程的实时性,⼀般会把算法中涉及到的复杂对象提前计算好,需要时再做调⽤可⼤⼤降低在线的推荐耗时。w =AB ∣N (a )∪N (b )∣
∣N (a )∩N (b )∣
w =AB ∣N (a )∣∣N (b )∣
∣N (a )∩N (b )∣w =AB ∣N (a )∣∣N (b )∣∑i ∈N (a )∩N (b )log 1+N (i )(∣∣)1
N (i )log (1+∣N (i )∣)1w 、w ...w 12N w ,i ∈i 1,2,...N N r ∈i [0,1]w =r w ∑i =1n
i i
⽤户物品倒排表
  当给定⼀个⽤户id时,如何快速获取其近期发⽣过⾏为的Item集合?每次都⽤SQL语句获取感觉效率不⾼,所以需提前离线构建⼀个⽤户物品倒排表。
⽤户相似矩阵
  输⼊⽤户id,都要获取该⽤户与所有其他⽤户的相似度,为避免⼤量重复的计算,可以离线计算好⽤户间的相似度矩阵,并设置合适的时间间隔进⾏更新。
步骤:
离线部分:
(1)构建⽤户物品倒排表并进⾏存储;
(2)计算好⽤户的相似度矩阵并进⾏存储;
在线部分:
输⼊:⽤户A的唯⼀id
(1)根据相似度矩阵出⽤户A对应的其他⽤户相似度,排序并截取前k个相似⽤户id;
(2)利⽤⽤户物品倒排表出k个相似⽤户的物品集;
(3)对(2)中得到的物品集做进⼀步过滤,如删除A已购的物品、已下架的物品等,得到最终候选集a;
输出:物品候选集a
Python代码
数据说明
  代码所⽤数据是随机产⽣的成交数据,字段包括⽤户id(user_id)和物品id(item_id),所以只有⼀种⾏为数据。数据包含5000+个不同⽤户,2100+个不同物品,成交记录⼀共有8w条。【数据有点简
洁,毕竟本⽂重点是了解算法应⽤的细节…】
⽤户物品倒排表(user_item.py)
  这⾥我⽤字典(dict)来储存⽤户物品倒排表,形如:{‘user 1’:[‘item 1’,‘item2’,…],‘user 2’:[‘item 2’,‘item 3’,…],…} 。
import pandas as pd
import numpy as np
'''
离线计算⽤户物品倒排表 user_item
'''
data = pd.read_csv('./data.csv')
user_item =dict()# 创建倒排表
for i in set(data.list()):
user_item[i]= data[data['userid']== i]['itemid'].tolist()
⽤户相似矩阵(user_similar.py)
  代码⾥⽤了离散⾏为数据情况下的余弦相似度公式,以下两类情况把相似度直接标0:(1)多数⽤户间的已购物品⽆交集;(2)⽤户间的相似度为1,说明两者的已购物品集完全相同,没有可以互相推荐的物品,后续通过对相似度进⾏排序查相似⽤户时应该排除此类⽤户,所以提前把这类相似度标0。
import pandas as pd
import numpy as np
'''
离线计算⽤户的相似度矩阵
'''
def common(A, B):
'''
计算集合A,B中共同元素的个数
输出形式:int
'''
if len(set(A))>=len(set(B)):
min_set =set(B)
else:
min_set =set(A)
num =0
for i in min_set:
num +=unt(i),B.count(i))
return num
user_id =list(set(data.userid))# 所有⽤户id集合
user_similar = np.zeros(shape=(len(user_id),len(user_id)))# ⽤户相似度矩阵
for i in range(len(user_id)):
for j in range(i):
common_num = common(user_item[user_id[i]],user_item[user_id[j]])
if  common_num ==0:
continue
consin_index = num / np.sqrt(len(user_item[user_id[i]])*len(user_item[user_id[j]]))
if consin_index ==1:# ⽤户相似度为1说明两者购买物品完全相同
continue
else:
user_similar[i][j]= user_similar[j][i]= consin_index
user_similar = pd.DataFrame(user_similar, columns=user_id, index=user_id)
【说实话,代码中对⽤户id做遍历的两层for循环让我有点慌了,⼀时半会⼉也想不到优化的⽅法…】
计算推荐物品候选集(User_CF.py)
  这⾥将UserCF写成函数的形式进⾏调⽤,即输⼊⼀个⽤户后返回相应的推荐物品候选集。【应⽤主程序写成接⼝是不是更⽅便其他语⾔的调⽤?】
import pandas as pd
import numpy as np
'''
给定⽤户id,在线计算其推荐物品候选集
'''
def UserCF(userid, k=20):
item =[]# 创建物品候选集
similar = user_similar[userid].values  # 获取输⼊⽤户与其他⽤户的相似度
top_k_user_id = lumns[-np.argsort(-similar)][:k]# 排序截取前k个⽤户id
for i in top_k_user_id:
item += user_item[i]
item =set(item)-set(user_item[userid])# 过滤输⼊⽤户已购的物品
return list(item)
注:以上代码分⽂件跑会报错!
本节总结和思考

本文发布于:2024-09-23 13:28:20,感谢您对本站的认可!

本文链接:https://www.17tex.com/tex/3/354852.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:物品   相似   推荐   数据   系统   算法   计算
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议