500行SQL快速实现UCF

写在前面话
UCF通常是快速User-base Collaborative Filter的简写;大体的算法思路是根据用户行为计算相似群体(邻居),为用户推荐其邻居喜好的实现内容;感觉是不是很简单、那废话不多说先撸个SQL。快速
SQL
select uid1,实现uid2,sim from ( select uid1 ,uid2 ,cnt12 / sqrt(cnt1*cnt2) sim ,row_number() over(partition by uid1 order by cnt12 / sqrt(cnt1*cnt2) desc) sim_rn from ( select a.uid uid1 ,b.uid uid2 ,count(a.iid) cnt12 from tb_behavior a join tb_behavior b on a.iid = b.iid where a.uid <> b.uid group by a.uid,b.uid ) a12 join (select uid,count(iid) cnt1 from tb_behavior group by uid) a1 on a12.uid1 = a1.uid join (select uid,count(iid) cnt2 from tb_behavior group by uid) a2 on a12.uid1 = a2.uid ) tb_neighbour where sim > 0.1 and sim_rn <= 30读者实现的话只需要把上面的tb_behavior表替换成自己业务的用户行为即可;iid,uid分别对应物品id和用户id;
根据共现相似度,即共同喜好的快速物品个数比上各自喜好物品总数乘积取平方;最后截断用户最相似的前30个邻居作为推荐的依据。
上面构造了邻居表,实现下面就是快速根据邻居的喜好为用户推荐了,具体sql如下:
select uid1,实现iid from ( select uid1 ,iid ,max(sim) score ,row_number() over(partition by uid1 order by max(sim) desc) user_rn from tb_neighbour a12 join (select uid,iid from tb_behavior) a2 on a12.uid2 = a2.uid join (select uid,collect_set(iid) iids1 from tb_behavior group by uid) a1 on a12.uid1 = a1.uid where not array_contaions(iids1,a2.iid) group by uid1,iid ) tb_rec where user_rn <= 500这里说明下包括上面的top30邻居和用户top500的最大推荐列表都是工程优化,截断节约些存储;具体读者可以根据自己业务需要进行设置;
然后大概说下各个表的含义:a1表是用户已消费过的物品,a2表是快速用户每个邻居喜好的物品;那么也就是说从邻居喜好的物品中过滤掉已经消费的云南idc服务商
物品整体根据共现相似度进行排序。
思考
但思路很简单、实现实际作者开发中总会遇到各种各样的快速问题,下面就捡几个主要的实现和大家一起讨论下:
1.join引起的数据倾斜问题:tb_neighbour表很大,往往热点物品会占据80%的快速曝光和消费记录,如何解决?实现 2.增量更新问题:上面的框架,tb_behavior表每次都是快速全量计算,是否能改造成增量更新邻居表和推荐结果,并减少计算时间呢?join引起的数据倾斜问题
先思考问题1,既然我们目的是求相似邻居,物品join只是为了关联上一组用户对,那自然的想法是可以根据feed做近似采样、相似度精度也几乎无损失。
下面我试着实现下这种思路:
with tb_behavior_sample as ( select uid,iid from ( select uid ,iid ,row_number() over(partition by iid order by rand()) feed_rn from tb_behavior ) bh where feed_rn <= 50000 ) select uid1,uid2,sim from ( select uid1 ,uid2 ,cnt12 / sqrt(cnt1*cnt2) sim ,row_number() over(partition by uid1 order by cnt12 / sqrt(cnt1*cnt2) desc) sim_rn from ( select a.uid uid1 ,b.uid uid2 ,count(a.iid) cnt12 from tb_behavior_sample a join tb_behavior_sample b on a.iid = b.iid where a.uid <> b.uid group by a.uid,b.uid ) a12 join (select uid,count(iid) cnt1 from tb_behavior group by uid) a1 on a12.uid1 = a1.uid join (select uid,count(iid) cnt2 from tb_behavior group by uid) a2 on a12.uid1 = a2.uid ) tb_neighbour where sim > 0.1 and sim_rn <= 30这里用了hive的with as语法,读者可自行查阅,篇幅有限,就不展开了;feed_rn就是随机采样了50000条,实际操作时读者可以先统计下item的企商汇分布、大概找到一个阈值;
比如取top10的item的出现次数作为阈值;那计算相似度时分子最多减小10,分母不变。这对大多数情况精度应该足够了,而且因为避免了数据倾斜,大大降低了计算时间。
增量更新问题
问题2是一个工程问题,lambda架构能使初始结果效果不错,可直接上线灰度了;在此基础上再加小时或者天增量;kappa架构相对就比较繁琐、需要一开始就设计增量流程。
精度方面也需要一定的累积;不过如何选择,读者可以根据自己的数据量和熟悉程度自行选择;作者这里仅以kappa架构说明。
重新review上面sql,我们发现我们仅需要记录下cnt12,cnt1,cnt2,iids1这些计算关键即可,其中iids2是用户邻居喜好的物品数组;数值类型可累加更新、
数组类型合并起来比较麻烦,一种解决方案是注册UDF;这里采取另一种这种的方案:把iids1合并成字符串,过滤的时候再分割为字符串数组。
with tb_behavior_sample_incr as ( select uid,iid from ( select uid ,iid ,row_number() over(partition by iid order by rand()) feed_rn from tb_behavior_incr ) bh where feed_rn <= 50000 ) insert overwrite table tb_neighbour select uid1,uid2,sim from ( select uid1 ,uid2 ,sum(cnt12) / sqrt(sum(cnt1)*sum(cnt2)) sim ,row_number() over(partition by uid1 order by sum(cnt12) / sqrt(sum(cnt1)*sum(cnt2)) desc) sim_rn from ( select uid1,uid2,cnt12,cnt1,cnt2 from tb_neighbour union all select a.uid uid1 ,b.uid uid2 ,count(a.iid) cnt12 ,cnt1 ,cnt2 from tb_behavior_sample_incr a join tb_behavior_sample_incr b on a.iid = b.iid where a.uid <> b.uid group by a.uid,b.uid ) a12 join (select uid,count(iid) cnt1 from tb_behavior_incr group by uid) a1 on a12.uid1 = a1.uid join (select uid,count(iid) cnt2 from tb_behavior_incr group by uid) a2 on a12.uid1 = a2.uid group by uid1,uid2 ) tb_neighbour where sim > 0.1 and sim_rn <= 30其中tb_behavior_sample_incr,tb_behavior_incr是相应tb_behavior_sample,tb_behavior的增量表;使用union all和group by聚合相同用户对的结果
kappa架构初次计算即是增量,香港云服务器不断累积每次增量的结果更新tb_neighbour;相当于lambda初始全量计算的一种回放,直至追到最新的时间分区。
insert overwrite table tb_user_consume select uid,substring_index(concat_ws(",",collect_list(iids1)),",",10000) iids1 from ( select uid,concat_ws(",",collect_set(cast(iid as string))) iids1 from tb_behavior_incr union all select uid,iids1 from tb_user_consume ) a group by uid select uid1,iid from ( select uid1 ,iid ,max(sim) score ,row_number() over(partition by uid1 order by max(sim) desc) user_rn from tb_neighbour a12 join (select uid,cast(iid as string) iid from tb_behavior_incr) a2 on a12.uid2 = a2.uid join (select uid,split(iids1,",") iids1 from tb_user_consume) a1 on a12.uid1 = a1.uid where not array_contaions(iids1,a2.iid) group by uid1,iid ) tb_rec where user_rn <= 500使用tb_user_consume缓存用户最近消费的前10000条记录,将用户邻居最新喜好物品推荐给用户。
写在后面的话
呼!终于写完了;虽然说有了上面这一套操作,UCF推荐基本完成;但有没有更好的方式呢?我想应该就是embedding大法了吧;比如item2vec对用户聚类,根据聚类
推荐;再或者根据好友关系,推荐好友喜好的物品。前者表征更细致,值得一说的是其也有负采样策略和checkpoint增量更新;后者好友信任度更高,解释性更强。
相关文章
探究电脑密码错误的原因与解决方法(密码保护下的安全隐患与应对策略)
摘要:在当今数字化时代,我们越来越依赖于电脑以及互联网,而密码作为我们信息安全的第一道防线,保护着我们重要的个人和机密信息。然而,我们有时会遭遇电脑密码错误的情况,这给我们的正常工作和生...2025-11-04尼康6300微单相机的强大功能和性能(探索尼康6300微单相机的专业拍摄体验)
摘要:作为一款专业级别的微单相机,尼康6300拥有出色的性能和丰富的功能,让摄影爱好者可以轻松拍摄出精美的照片和视频。本文将深入探讨尼康6300微单相机的特点和功能,帮助读者更好地了解这...2025-11-04威刚16008g的性能和使用体验详解(一款强悍内存条的尖子选手,让你的电脑更强大)
摘要:作为电脑的重要组成部分,内存条对电脑的性能和稳定性起着至关重要的作用。威刚16008g内存条以其出色的性能和稳定性,在市场上备受青睐。本文将详细解析威刚16008g的性能以及使用体...2025-11-04三洋PLC-XU50的特点与性能分析(探究三洋PLC-XU50的可靠性与高效性能)
摘要:在现代科技发展迅速的背景下,投影仪作为一种重要的多媒体设备,广泛应用于教育、商业和娱乐领域。三洋PLC-XU50作为一款知名的投影仪产品,其出色的性能和可靠性备受消费者好评。本文将...2025-11-04飞利浦SHB6250音质的真实评价(揭秘SHB6250的音质表现,解锁你的音乐世界)
摘要:在如今快节奏的生活中,音乐已经成为了人们放松心情、享受生活的重要方式之一。而对于追求音质的音乐爱好者来说,一款优质的耳机无疑是必备的装备之一。飞利浦SHB6250作为一款备受追捧的...2025-11-04使用通用U盘启动安装系统的步骤详解(将U盘制作成启动盘,并使用它来安装操作系统)
摘要:随着科技的不断发展,安装系统已不再局限于光盘。现如今,我们可以利用通用U盘来制作启动盘,并通过它来轻松安装操作系统。本文将详细介绍如何使用通用U盘来启动安装系统,方便读者快速掌握这...2025-11-04

最新评论