写在前面,UCF通常是User-baseCollaborativeFilter的缩写;总体算法思路是根据用户行为计算相似的群体(邻居),为用户推荐邻居喜欢的内容;感觉是不是很简单,废话不多说,先从SQL说起吧。SQLselectuid1,uid2,simfrom(selectuid1,uid2,cnt12/sqrt(cnt1*cnt2)sim,row_number()over(partitionbyuid1orderbycnt12/sqrt(cnt1*cnt2)desc)sim_rnfrom(selecta.uiduid1,b.uiduid2,count(a.iid)cnt12fromtb_behaviorajointb_behaviorbona.iid=b.iidwherea.uid<>b.uidgroupbya.uid,b.uid)a12join(selectuid,count(iid)cnt1fromtb_behaviorgroupbyuid)a1ona12.uid1=a1.uidjoin(selectuid,count(iid)cnt2fromtonb_behaviorbyuid)a2groupby12.uid1=a2.uid)tb_neighborwheresim>0.1andsim_rn<=30读者只需要把上面的tb_behavior表替换成自己业务的用户行为即可;iid和uid分别对应itemid和userid;根据共现相似度,即共同喜欢的item个数乘以喜欢的item总数,取平方;最后截取与用户最相似的前30个邻居作为推荐依据。上面构建了邻居表,下面根据邻居的喜好为用户推荐。具体sql如下:selectuid1,iidfrom(selectuid1,iid,max(sim)score,row_number()over(partitionbyuid1orderbymax(sim)desc)user_rnfromtb_neighboura12join(selectuid,iidfromtb_behavior)a2ona12.uid2=a2.uidjoin(selectuid,collect_set(iid)iids1fromtb_behaviorgroupbyuid)a1ona12.uid1=a1.uidwherenotarray_contaions(iids1,a2.iid)groupbyuid1,iid)tb_recwhereuser0rn<=5top500的最大推荐列表为工程优化,截断以节省一些存储空间;具体读者可以根据自己的业务需要进行设置;然后大致解释一下各个表的含义:a1表是用户消费过的商品,a2表是用户的每一个商品。邻居最喜欢的物品;也就是说,从邻居最喜欢的物品中,过滤掉已经消费过的物品,按照共现相似度排序。想不过思路很简单。其实笔者在开发中总会遇到各种各样的问题。下面主要跟大家讨论一下:1.join导致的数据倾斜问题:tb_neighbor表很大,经常热点item会占据80%的曝光和消费记录,如何解决?2.增量更新问题:在上面的框架中,tb_behavior表每次都是全量计算的。能不能转化为邻居表和推荐结果的增量更新,减少计算时间?join引起的数据倾斜问题首先考虑问题1,由于我们的目的是寻找相似的邻居,而item的join只是关联上一组用户对,自然的想法是可以根据feed,相似度准确率几乎没有损失。下面我尝试实现这个想法:withtb_behavior_sampleas(selectuid,iidfrom(selectuid,iid,row_number()over(partitionbyiidorderbyrand())feed_rnfromtb_behavior)bhwherefeed_rn<=50000)selectuid1,uid2,simfrom(selectuid1,uids/2,qnt(1cnt1*cnt2)sim,row_number()over(partitionbyuid1orderbycnt12/sqrt(cnt1*cnt2)desc)sim_rnfrom(selecta.uiduid1,b.uiduid2,count(a.iid)cnt12fromtb_behavior_samplejointb_behavior_samplebona.iidb=b.iid.wherea.uiduidgroupbya.uid,b.uid)a12join(selectuid,count(iid)cnt1fromtb_behaviorgroupbyuid)a1ona12.uid1=a1.uidjoin(selectuid,count(iid)cnt2fromtb_behaviorgroupbyuid)a2ona12.uid1=a2.uid)n=a2.uid)n=a2.uid)0rwhere使用了hive的withas语法,读者可以自行查看。空间有限,就不展开了;feed_rn是50,000个项目的随机抽样。在实际操作中,读者可以先统计物品的分布情况,找出一个阈值;例如,取top10项出现的次数作为阈值;计算相似度时,分子最多减10,分母不变。对于大多数情况,这应该足够准确,并且由于避免了数据倾斜,因此大大减少了计算时间。增量更新问题问题2是一个工程问题。lambda架构可以让初期效果不错,灰度可以直接上线;在此基础上,增加每小时或每天的增量;kappa架构相对繁琐,需要从一开始就更新设计增量流程。准确性也需要一定的积累;但是,读者可以根据自己的数据量和熟悉程度来选择如何选择;笔者这里只用kappa架构来说明。重新查看上面的sql,发现我们只需要记录cnt12、cnt1、cnt2、iids1的计算键,其中iids2是用户邻居喜欢的物品数组;值类型可以累加更新,数组类型组合麻烦。解决方法是注册UDF;这里还有一个解决办法:将iids1组合成一个字符串,过滤的时候再拆分成一个字符串数组。withtb_behavior_sample_incras(selectuid,iidfrom(selectuid,iid,row_number()over(partitionbyiidorderbyrand())feed_rnfromtb_behavior_incr)bhwherefeed_rn<=50000)insertoverwritetabletb_neighbourselectuid1,uid2,simfrom(selectuid1,uid2,sum(cnt12)/sqrt(sum(cnt1)*cnt2))sim,row_number()over(partitionbyuid1orderbysum(cnt12)/sqrt(sum(cnt1)*sum(cnt2))desc)sim_rnfrom(selectuid1,uid2,cnt12,cnt1,cnt2fromtb_neighbourunionallselecta.uiduid1,b.uiduid2,count(a.iid)cnt12,cnt1,cnt2fromtb_behavior_sample_incrajointb_behavior_sample_incrbona.iid=b.iidwherea.uid<>b.uidgroupbya.uid,b.uid)a12join(selectuid,count(iid)cnt1fromtb_behavior_incrgroupbyuid)a1ona12.uid1=selectuidgroupbyuid(iid)cnt2fromtb_behavior_incrgroupbyuid)a2ona12.uid1=a2.uidgroupbyuid1,uid2)tb_neighbourwheresim>0.1andsim_rn<=30其中tb_behavior_sample_incr,tb_behavior_incr是相应tb_behavior_sample,tb_behavior的增量表;使用unionall和groupby聚合相同用户对的结果kappa架构初次计算是递增的,每次递增的结果都是不断累积的d更新tb_neighbor;相当于la重播mbda的初始完整计算,直到捕获到最新时间分区insertoverwritetabletb_user_consumeselectuid,substring_index(concat_ws(",",collect_list(iids1)),",",10000)iids1from(selectuid,concat_ws(",",collect_set(cast(iidasstring)))iids1fromtb_behavior_incrunionallselectuid,iids1fromtb_user_consume)agroupbyuidselectuid1,iidfrom(selectuid1,iid,max(sim)score,row_number()over(partitionbyuid1orderbymax(sim)desc)user_rnfromtb_neighboura12join(selectuid,cast(iidasstring)aid2uidfromtb2a2.uidjoin(selectuid,split(iids1,",")iids1fromtb_user_consume)a1ona12.uid1=a1.uidwherenotarray_contaions(iids1,a2.iid)groupbyuid1,iid)tb_recwhereuser_rn<=500使用tb_user_consume缓存用户最近消费的前10000条记录.将用户的邻居最近最喜欢的物品推荐给用户。后面写着call的字样!终于写完了;虽然有了上面的一套操作,UCF的推荐就基本完成了;但是有更好的方法吗?我觉得应该是embedding方式;例如item2vec对用户进行聚类,并根据聚类进行推荐;或者根据好友关系推荐好友喜欢的物品。前者有更详细的表示,值得一提的是它还有负采样策略和检查点增量更新;后者具有更高的朋友信任度和更强的解释力。
