写在前面话

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表是用户每个邻居喜好的物品;那么也就是说从邻居喜好的物品中过滤掉已经消费的

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

思考

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

  • 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增量更新;后者好友信任度更高,解释性更强。

500行SQL快速实现UCF的更多相关文章

  1. 使用C#+Linq+SQL快速开发业务

    C#开发桌面程序的效率确实很高,今天就来总结下如何使用C#+Linq+SQL快速开发一个新的业务系统. Linq是微软官方的轻量级的ORM工具,使用它结合SQL可以快速的生成实体类,再通过Linq操作 ...

  2. 删除反复行SQL举例

    删除反复行SQL实验简单举例 说明:实验按顺序进行.前后存在关联性.阅读时请注意.打开文件夹更便于查看. 构造实验环境: SQL> select count(*) from emp;   COU ...

  3. 如何对于几百行SQL语句进行优化?

    1.最近在开发中遇到的一些关于几百行SQL语句做查询的问题,需要如何的解决优化SQL这确实是个问题,对于当下的ORM 框架 EF 以及其他的一些的开源的框架例如Drapper ,以及Sqlite-Su ...

  4. 【转】Oracle中如何用一条SQL快速生成10万条测试数据

    转自http://blog.csdn.net/welken/article/details/4971887   做数据库开发或管理的人经常要创建大量的测试数据,动不动就需要上万条,如果一条一条的录入, ...

  5. [500lines]500行代码写web server

    项目地址:https://github.com/aosabook/500lines/tree/master/web-server.作者是来自Mozilla的Greg Wilson.项目是用py2写成. ...

  6. PreparedStatement執行sql語句

    import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import org ...

  7. 使用批处理文件命令行方式快速启动和停止IIS、SqlServer

    原文:使用批处理文件命令行方式快速启动和停止IIS.SqlServer 虽然现在内存便宜了,但是自己还是嫌自己的512M内存太小,没办法,后台运行的东西太多了,有很多都是有用的没法关闭的.IIS和SQ ...

  8. 用SQL快速删除U8账套

    一.问题提出 通过"系统管理"来删除999账套,首先要求你备份然后才能删除.头痛的是: 1)备份需要发费很长的时间,特别是账套数据文件比较大时. 2)备份时,你的本本基本处于死机状 ...

  9. Spark2.x学习笔记:Spark SQL快速入门

    Spark SQL快速入门 本地表 (1)准备数据 [root@node1 ~]# mkdir /tmp/data [root@node1 ~]# cat data/ml-1m/users.dat | ...

随机推荐

  1. 醒醒!Python已经支持中文变量名啦!

    最近,我在翻阅两本比较新的 Python 书籍时,发现它们都犯了一个严重的低级错误! 这两本书分别是<Python编程:从入门到实践>和<父与子的编程之旅>,它们都是畅销书,都 ...

  2. SpringBoot对静态资源的映射规则

    在WebMvcAutoConfiguration类中有相对应的方法addResourceHandlers public void addResourceHandlers(ResourceHandler ...

  3. maven生命周期与插件

    目录 Maven生命周期 clean default site 命令与对应周期 插件与绑定 插件目标 插件绑定 内置绑定 自定义绑定 插件配置 本文主要是针对<maven实战>书中关键知识 ...

  4. 【Oracle】权限相关

    系统权限 SELECT * FROM DBA_SYS_PRIVS WHERE GRANTEE = 'CHAXUN' UNION ALL SELECT * FROM DBA_SYS_PRIVS WHER ...

  5. CTF------pwn笔记

    地址:http://pwnable.kr/play.php 题目: 使用MobaXterm连接(当然也可以使用别的软件进行连接,用的顺手就行) 连接成功后所以"ls"命令查看目录 ...

  6. MySQL全面瓦解18:自定义函数

    定义 我们之前学习了MySQL的内置函数,非常丰富,满足了我们对数据操作的大部分需求. 但是如果有一些复杂的业务逻辑在数据库层面就可以完成,无需在程序层面完成的时候,这时候就可以写成MySQL自定义函 ...

  7. kafka(一)入门

    一.消息引擎系统 这类系统引以为豪的消息传递属性,像引擎一样,具备某种能量转换传输的能力 消息引擎系统是一组规范,企业利用这组规范在不同系统之间传递语义准确的消息,实现松耦合的异步式数据传递.通俗地讲 ...

  8. JMM在X86下的原理与实现

    JMM在X86下的原理与实现 Java的happen-before模型 众所周知 Java有一个happen-before模型,可以帮助程序员隔离各个平台多线程并发的复杂性,只要Java程序员遵守ha ...

  9. 1.5V转3.3V升压电路图和1.5V转3.3V的电源芯片

    1.5V转3.3V的电路图需要材料:PW5100芯片,2个贴片电容,1个贴片电感.即可组成一个DC-DC同步升压高效率电路图,可提供稳定的3.3V输出电压. 1.5V转3.3V的电源芯片 1.5V转3 ...

  10. 迈凯伦765LT/600LT/720S/650S/570S维修手册电路图Mclaren车间手册接线图

    全套迈凯伦维修手册电路图Mclaren车间手册线路图:语言:English,German,French,Spanish,Chinese,Japanese.McLaren迈凯伦新GT维修手册电路图零配件 ...