500行SQL快速实现UCF

开发 前端
UCF通常是User-base Collaborative Filter的简写;大体的算法思路是根据用户行为计算相似群体(邻居),为用户推荐其邻居喜好的内容;感觉是不是很简单、那废话不多说先撸个SQL。

[[381092]]

写在前面话

UCF通常是User-base Collaborative Filter的简写;大体的算法思路是根据用户行为计算相似群体(邻居),为用户推荐其邻居喜好的内容;感觉是不是很简单、那废话不多说先撸个SQL。

SQL

  1. select uid1,uid2,sim 
  2. from ( 
  3.     select uid1 
  4.         ,uid2 
  5.         ,cnt12 / sqrt(cnt1*cnt2) sim 
  6.         ,row_number() over(partition by uid1 order by cnt12 / sqrt(cnt1*cnt2) desc) sim_rn 
  7.     from ( 
  8.         select a.uid uid1 
  9.             ,b.uid uid2 
  10.             ,count(a.iid) cnt12  
  11.         from tb_behavior a 
  12.         join tb_behavior b 
  13.         on a.iid = b.iid 
  14.         where a.uid <> b.uid 
  15.         group by a.uid,b.uid 
  16.     ) a12 
  17.     join (select uid,count(iid) cnt1 from tb_behavior group by uid) a1 
  18.     on a12.uid1 = a1.uid 
  19.     join (select uid,count(iid) cnt2 from tb_behavior group by uid) a2 
  20.     on a12.uid1 = a2.uid 
  21. ) tb_neighbour 
  22. where sim > 0.1 and sim_rn <= 30 

读者实现的话只需要把上面的tb_behavior表替换成自己业务的用户行为即可;iid,uid分别对应物品id和用户id;

根据共现相似度,即共同喜好的物品个数比上各自喜好物品总数乘积取平方;最后截断用户最相似的前30个邻居作为推荐的依据。

上面构造了邻居表,下面就是根据邻居的喜好为用户推荐了,具体sql如下:

  1. select uid1,iid 
  2. from ( 
  3.     select uid1 
  4.         ,iid 
  5.         ,max(sim) score 
  6.         ,row_number() over(partition by uid1 order by max(sim) desc) user_rn 
  7.     from tb_neighbour a12 
  8.     join (select uid,iid from tb_behavior) a2 
  9.     on a12.uid2 = a2.uid 
  10.     join (select uid,collect_set(iid) iids1 from tb_behavior group by uid) a1 
  11.     on a12.uid1 = a1.uid 
  12.     where not array_contaions(iids1,a2.iid) 
  13.     group by uid1,iid 
  14. ) tb_rec 
  15. where user_rn <= 500 

这里说明下包括上面的top30邻居和用户top500的最大推荐列表都是工程优化,截断节约些存储;具体读者可以根据自己业务需要进行设置;

然后大概说下各个表的含义:a1表是用户已消费过的物品,a2表是用户每个邻居喜好的物品;那么也就是说从邻居喜好的物品中过滤掉已经消费的

物品整体根据共现相似度进行排序。

思考

但思路很简单、实际作者开发中总会遇到各种各样的问题,下面就捡几个主要的和大家一起讨论下:

  • 1.join引起的数据倾斜问题:tb_neighbour表很大,往往热点物品会占据80%的曝光和消费记录,如何解决?
  • 2.增量更新问题:上面的框架,tb_behavior表每次都是全量计算,是否能改造成增量更新邻居表和推荐结果,并减少计算时间呢?

join引起的数据倾斜问题

先思考问题1,既然我们目的是求相似邻居,物品join只是为了关联上一组用户对,那自然的想法是可以根据feed做近似采样、相似度精度也几乎无损失。

下面我试着实现下这种思路:

  1. with tb_behavior_sample as ( 
  2.     select uid,iid  
  3.     from ( 
  4.         select uid 
  5.             ,iid 
  6.             ,row_number() over(partition by iid order by rand()) feed_rn 
  7.         from tb_behavior 
  8.     ) bh 
  9.     where feed_rn <= 50000 
  10. )  
  11.  
  12. select uid1,uid2,sim 
  13. from ( 
  14.     select uid1 
  15.         ,uid2 
  16.         ,cnt12 / sqrt(cnt1*cnt2) sim 
  17.         ,row_number() over(partition by uid1 order by cnt12 / sqrt(cnt1*cnt2) desc) sim_rn 
  18.     from ( 
  19.         select a.uid uid1 
  20.             ,b.uid uid2 
  21.             ,count(a.iid) cnt12  
  22.         from tb_behavior_sample a 
  23.         join tb_behavior_sample b 
  24.         on a.iid = b.iid 
  25.         where a.uid <> b.uid 
  26.         group by a.uid,b.uid 
  27.     ) a12 
  28.     join (select uid,count(iid) cnt1 from tb_behavior group by uid) a1 
  29.     on a12.uid1 = a1.uid 
  30.     join (select uid,count(iid) cnt2 from tb_behavior group by uid) a2 
  31.     on a12.uid1 = a2.uid 
  32. ) tb_neighbour 
  33. 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合并成字符串,过滤的时候再分割为字符串数组。

  1. with tb_behavior_sample_incr as ( 
  2.     select uid,iid  
  3.     from ( 
  4.         select uid 
  5.             ,iid 
  6.             ,row_number() over(partition by iid order by rand()) feed_rn 
  7.         from tb_behavior_incr 
  8.     ) bh 
  9.     where feed_rn <= 50000 
  10. )  
  11.  
  12. insert overwrite table tb_neighbour 
  13. select uid1,uid2,sim 
  14. from ( 
  15.     select uid1 
  16.         ,uid2 
  17.         ,sum(cnt12) / sqrt(sum(cnt1)*sum(cnt2)) sim 
  18.         ,row_number() over(partition by uid1 order by sum(cnt12) / sqrt(sum(cnt1)*sum(cnt2)) desc) sim_rn 
  19.     from ( 
  20.         select uid1,uid2,cnt12,cnt1,cnt2 
  21.         from tb_neighbour 
  22.         union all 
  23.         select a.uid uid1 
  24.             ,b.uid uid2 
  25.             ,count(a.iid) cnt12  
  26.             ,cnt1 
  27.             ,cnt2 
  28.         from tb_behavior_sample_incr a 
  29.         join tb_behavior_sample_incr b 
  30.         on a.iid = b.iid 
  31.         where a.uid <> b.uid 
  32.         group by a.uid,b.uid  
  33.     ) a12 
  34.     join (select uid,count(iid) cnt1 from tb_behavior_incr group by uid) a1 
  35.     on a12.uid1 = a1.uid 
  36.     join (select uid,count(iid) cnt2 from tb_behavior_incr group by uid) a2 
  37.     on a12.uid1 = a2.uid 
  38.     group by uid1,uid2 
  39. ) tb_neighbour 
  40. 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初始全量计算的一种回放,直至追到最新的时间分区。

  1. insert overwrite table tb_user_consume 
  2. select uid,substring_index(concat_ws(",",collect_list(iids1)),",",10000) iids1  
  3. from ( 
  4.     select uid,concat_ws(",",collect_set(cast(iid as string))) iids1 
  5.     from tb_behavior_incr 
  6.     union all 
  7.     select uid,iids1 
  8.     from tb_user_consume 
  9. ) a 
  10. group by uid 
  11.  
  12. select uid1,iid 
  13. from ( 
  14.     select uid1 
  15.         ,iid 
  16.         ,max(sim) score 
  17.         ,row_number() over(partition by uid1 order by max(sim) desc) user_rn 
  18.     from tb_neighbour a12 
  19.     join (select uid,cast(iid as string) iid from tb_behavior_incr) a2 
  20.     on a12.uid2 = a2.uid 
  21.     join (select uid,split(iids1,",") iids1 from tb_user_consume) a1 
  22.     on a12.uid1 = a1.uid 
  23.     where not array_contaions(iids1,a2.iid) 
  24.     group by uid1,iid 
  25. ) tb_rec 
  26. where user_rn <= 500 

使用tb_user_consume缓存用户最近消费的前10000条记录,将用户邻居最新喜好物品推荐给用户。

写在后面的话

呼!终于写完了;虽然说有了上面这一套操作,UCF推荐基本完成;但有没有更好的方式呢?我想应该就是embedding大法了吧;比如item2vec对用户聚类,根据聚类

推荐;再或者根据好友关系,推荐好友喜好的物品。前者表征更细致,值得一说的是其也有负采样策略和checkpoint增量更新;后者好友信任度更高,解释性更强。

责任编辑:张燕妮 来源: 博客园
相关推荐

2021-06-28 16:05:19

数据库代码技术

2021-06-30 13:45:49

SQL数据库LSM

2009-09-14 13:17:51

LINQ to SQLLINQ to SQL

2022-10-28 10:18:53

代码绩效Java

2021-12-16 22:59:21

SQL报表应用

2014-05-15 09:45:58

Python解析器

2019-10-17 21:37:28

微信飞机大战Python

2010-11-16 16:02:08

曙光天河星云

2009-11-17 09:47:54

Oracle SQL语

2022-08-19 07:13:45

SQL方法编程

2011-05-16 09:15:22

SQL语言

2011-05-16 09:22:28

SQL语言

2010-09-01 15:30:24

SQL删除

2019-06-03 08:52:39

打卡考勤数据库

2011-11-30 09:46:32

超算TOP500超级计算机

2010-09-01 17:19:49

SQL删除TABLE

2020-05-21 14:50:48

代码机器学习图像

2010-05-19 16:45:59

信息安全网络安全管理任子行网络

2010-09-26 13:40:14

SQL查询
点赞
收藏

51CTO技术栈公众号