目录
使用协同过滤算法,需要统计用户(User)对物品(Item)的评分(Score),然后依托这些数据进行协同过滤的计算(关于什么是协同过滤算法,可以查看博主另一篇博文:推荐算法之基于协同过滤推荐算法)。如果是在视频推荐中,可以根据用户对视频的评分(比如豆瓣)来进行计算。但是在电商中,没有用户对 商品/品类/风格 等的评分,那就只能通过其他相关数据(比如用户对商品的点击、加购、收藏、购买等)来统计每个用户对相关商品的评分,然后根据该商品归属于哪个 品类/风格 下面,来将对应数据映射到相关主题上。这样就能通过协同过滤来计算这些没有显示评分的业务。
如豆瓣中那些有具体的评分数据的业务,是显示的评分;如电商中对商品没有具体的评分数据的业务,是隐式的评分(当然,电商中也可以使用用户对该商品的售后评价来统计)。
用户偏好得分 = 行为类型权重 * 行为次数 * 时间衰减系数
行为权重是指用户浏览、搜索、收藏、加购、下单等不同的行为,反映了物品对用户重要程度的差异;代价越高的行为权重值越高,一般情况下为:下单 > 加购 > 收藏 > 点击
博主所使用的行为类型对应的权重值如下所示:
点击商品:1分
收藏商品:2.17分
加购商品:5.19分
购买商品:15.65分
行为次数是指用户行为次数,提现了用户对该物品的重要性。
比如:
点击次数
收藏次数
加购次数
购买件数
用户的行为会随着时间的过去,历史行为和当前的相关性不断减弱;越接近当前的行为,对当下的影响越强,衰减系数越高;时间衰减系数的取值范围为 (0,1]。
博主这里使用的是以半衰期公式计算出来的时间衰减系数(其中包括 以半衰期为7天的时间衰减系数来统计30天的数据, 以半衰期为15天的时间衰减系数来统计60天的数据, 以半衰期为30天的时间衰减系数来统计120天的数据),具体公式为: 时间衰减系数 = EXP(n/7*LN(0.5)) 。
如下统计了以半衰期为7天的时间衰减系数来统计30天的数据:
1 0.905723664
2 0.820335356
3 0.742997145
4 0.672950096
5 0.609506827
6 0.552044757
7 0.5
8 0.452861832
9 0.410167678
10 0.371498572
11 0.336475048
12 0.304753414
13 0.276022378
14 0.25
15 0.226430916
16 0.205083839
17 0.185749286
18 0.168237524
19 0.152376707
20 0.138011189
21 0.125
22 0.113215458
23 0.10254192
24 0.092874643
25 0.084118762
26 0.076188353
27 0.069005595
28 0.0625
29 0.056607729
30 0.05127096
注意:在具体计算中会用到特有的业务名词,具体的名词解释可以参考博主的另一篇博文:基于协同过滤的电商推荐系统(1):名次解释
注意:在此博文中,博主使用的是用户对商品的30天偏好得分来统计,使用的时间衰减系数为 以半衰期为7天的时间衰减系数来统计30天的数据
需求:统计每个用户对每一款商品(goods_no)在这30天中的点击偏好得分
具体口径:以用户id(user_id)和商品款号(goods_no)为主键,对偏好得分进行聚合(每一次的点击都会有一个得分)
建表语句:
- drop table if EXISTS xxx_recommendation_system.user_goods_click_score_30day;
- CREATE EXTERNAL table if not EXISTS xxx_recommendation_system.user_goods_click_score_30day(
- user_id BIGINT COMMENT '用户id'
- , goods_no BIGINT COMMENT '商品款号'
- , goods_click_score_30day DOUBLE COMMENT '30天商品点击评分'
- ) COMMENT '用户商品点击评分表(30天)'
- STORED AS orc LOCATION 'obs://xxx-bigdata/xxx_recommendation_system.db/user_goods_click_score_30day'
- ;
计算脚本:
- insert overwrite table xxx_recommendation_system.user_goods_click_score_30day
- select
- main_table.user_id
- , goods_table.goods_no
- , sum(main_table.goods_click_score_30day) as goods_click_score_30day
- from (
- SELECT
- user_id
- , goods_id
- , 1 * EXP(datediff1(to_date1(${bdp.system.bizdate},'yyyymmdd'),to_date1(dt,'yyyymmdd'),'dd') / 7 * LN(0.5)) AS goods_click_score_30day
- FROM xxx_data.dwd_log_goods_detail_page_dt
- WHERE dt BETWEEN TO_CHAR( DATEADD( to_date1(${bdp.system.bizdate},'yyyymmdd') ,-30 ,'dd') ,'yyyymmdd') and ${bdp.system.bizdate}
- ) as main_table
-
- left join xxx_data.dim_goods_id_info as goods_table
- on main_table.goods_id = goods_table.goods_id
-
- GROUP BY main_table.user_id, goods_table.goods_no
- DISTRIBUTE by floor(rand() * 10)
- ;
其中的 dwd_log_goods_detail_page_dt 表,即为商品详情页面曝光表,每1条记录代表的该用户对该商品的一次点击
其中的 dim_goods_id_info 表,即为商品上架维度表,每次商品上架都会有对应的goods_id和goods_no,其中goods_id唯一,goods_no不唯一
需求:统计每个用户对每一款商品(goods_no)在这30天中的收藏偏好得分
具体口径:以用户id(user_id)和商品款号(goods_no)为主键,对偏好得分进行聚合(每一个收藏都会有一个得分)
建表语句:
- insert overwrite table xxx_recommendation_system.user_goods_collect_score_30day
- select
- main_table.user_id
- , goods_table.goods_no
- , sum(main_table.goods_collect_score_30day) as goods_collect_score_30day
- from (
- SELECT
- user_id
- , goods_id
- , 2.17 * EXP(datediff1(to_date1(${bdp.system.bizdate},'yyyymmdd'),to_date1(dt,'yyyymmdd'),'dd') / 7 * LN(0.5)) AS goods_collect_score_30day
- FROM xxx_data.dwd_log_goods_collect_dt
- WHERE dt between TO_CHAR( DATEADD( to_date1(${bdp.system.bizdate},'yyyymmdd') ,-30 ,'dd') ,'yyyymmdd') and ${bdp.system.bizdate}
- ) as main_table
-
- left join xxx_data.dim_goods_id_info as goods_table
- on main_table.goods_id = goods_table.goods_id
-
- GROUP BY main_table.user_id, goods_table.goods_no
- DISTRIBUTE by floor(rand() * 10)
- ;
其中的 dwd_log_goods_collect_dt 表,即为商品收藏表,每1条记录代表的该用户对该商品的一次收藏
其中的 dim_goods_id_info 表,即为商品上架维度表,每次商品上架都会有对应的goods_id和goods_no,其中goods_id唯一,goods_no不唯一
需求:统计每个用户对每一款商品(goods_no)在这30天中的加购偏好得分
具体口径:以用户id(user_id)和商品款号(goods_no)为主键,对偏好得分进行聚合(每一个收藏都会有一个得分)
建表语句:
- drop table if EXISTS xxx_recommendation_system.user_goods_add_cart_score_30day;
- CREATE EXTERNAL table if not EXISTS xxx_recommendation_system.user_goods_add_cart_score_30day(
- user_id BIGINT COMMENT '用户id'
- , goods_no BIGINT COMMENT '商品货号'
- , goods_add_cart_score_30day DOUBLE COMMENT '30天商品加购评分'
- ) COMMENT '用户商品加购评分表(30天)'
- STORED AS orc LOCATION 'obs://xxx-bigdata/xxx_recommendation_system.db/user_goods_add_cart_score_30day'
- ;
计算脚本:
- insert overwrite table xxx_recommendation_system.user_goods_add_cart_score_30day
- select
- main_table.user_id
- , goods_table.goods_no
- , sum(main_table.goods_add_cart_score_30day) as goods_add_cart_score_30day
- from (
- SELECT
- user_id
- , goods_id
- , 5.19 * EXP(datediff1(to_date1(${bdp.system.bizdate},'yyyymmdd'),to_date1(dt,'yyyymmdd'),'dd') / 7 * LN(0.5)) AS goods_add_cart_score_30day
- FROM xxx_data.dwd_log_add_cart_dt
- WHERE dt between TO_CHAR( DATEADD( to_date1(${bdp.system.bizdate},'yyyymmdd') ,-30 ,'dd') ,'yyyymmdd') and ${bdp.system.bizdate}
- ) as main_table
-
- left join xxx_data.dim_goods_id_info as goods_table
- on main_table.goods_id = goods_table.goods_id
-
- GROUP BY main_table.user_id, goods_table.goods_no
- DISTRIBUTE by floor(rand() * 10)
- ;
其中的 dwd_log_add_cart_dt 表,即为商品加购表,每1条记录代表的该用户对该商品的一次加购
其中的 dim_goods_id_info 表,即为商品上架维度表,每次商品上架都会有对应的goods_id和goods_no,其中goods_id唯一,goods_no不唯一
需求:统计每个用户对每一款商品(goods_no)在这30天中的购买偏好得分
具体口径:以用户id(user_id)和商品款号(goods_no)为主键,对偏好得分进行聚合(每一个收藏都会有一个得分)
建表语句:
- drop table if EXISTS xxx_recommendation_system.user_goods_buy_score_30day;
- CREATE EXTERNAL table if not EXISTS xxx_recommendation_system.user_goods_buy_score_30day(
- user_id BIGINT COMMENT '用户id'
- , goods_no BIGINT COMMENT '商品款号'
- , goods_buy_score_30day DOUBLE COMMENT '30天购买评分'
- ) COMMENT '用户商品购买偏好得分表(30天)'
- STORED AS orc LOCATION 'obs://xxx-bigdata/xxx_recommendation_system.db/user_goods_buy_score_30day'
- ;
计算脚本:
- insert overwrite table xxx_recommendation_system.user_goods_buy_score_30day
- select
- main_table.user_id
- , goods_table.goods_no
- , sum(main_table.goods_buy_score_30day) as goods_buy_score_30day
- from (
- SELECT
- user_id
- , goods_id
- , 15.65 * EXP(datediff1(to_date1(${bdp.system.bizdate},'yyyymmdd'),to_date1(dt,'yyyymmdd'),'dd') / 7 * LN(0.5)) AS goods_buy_score_30day
- FROM xxx_data.dwd_ys_sale_sp_order_info_final_dt
- WHERE dt between TO_CHAR( DATEADD( to_date1(${bdp.system.bizdate},'yyyymmdd') ,-30 ,'dd') ,'yyyymmdd') and ${bdp.system.bizdate}
- AND pay_status = 1
- AND is_real_pay = 1
- ) as main_table
-
- left join xxx_data.dim_goods_id_info as goods_table
- on main_table.goods_id = goods_table.goods_id
-
- GROUP BY main_table.user_id, goods_table.goods_no
- DISTRIBUTE by floor(rand() * 10)
- ;
其中的 dwd_ys_sale_sp_order_info_final_dt 表,即为商品购买表(通过订单、订单详情、订单取消表聚合而来),每1条记录代表的该用户对该商品的一次购买
其中的 dim_goods_id_info 表,即为商品上架维度表,每次商品上架都会有对应的goods_id和goods_no,其中goods_id唯一,goods_no不唯一
需求:将用户对商品的点击偏好得分、收藏偏好得分、加购偏好得分、购买偏好得分进行聚合,并且由于不同商品之间可能差距过大,所以还需要进行归一化
具体口径:针对不同用户、不同商品,之间聚合即可,并使用 ln() 函数进行归一化
归一化:LN(该用户对该商品的评分 + 2)/LN(MAX(该用户对所有商品的最大评分 + 2))] ,因为其中的值存在0到1的,所以需要加2
建表语句:
- drop table if EXISTS xxx_recommendation_system.user_goods_score_30day;
- CREATE EXTERNAL table if not EXISTS xxx_recommendation_system.user_goods_score_30day(
- user_id BIGINT COMMENT '用户id'
- , goods_no BIGINT COMMENT '商品款号'
- , score double comment '用户针对商品的偏好得分'
- , user_max_score double comment '该用户最大的对商品的得分'
- , score_normalization double comment '用户针对商品的偏好得分归一化后结果'
- ) COMMENT '用户商品偏好得分表(30天)'
- STORED AS orc LOCATION 'obs://xxx-bigdata/xxx_recommendation_system.db/user_goods_score_30day'
- ;
计算脚本:
- INSERT OVERWRITE TABLE xxx_recommendation_system.user_goods_score_30day
- select
- user_id
- , goods_no
- , score
- , max(score) over(partition by user_id) as user_max_score
- , ln(score + 2) / ln(max(score) over(partition by user_id) + 2) as score_normalization
- from (
- select
- main_table.user_id
- , main_table.goods_no
- , coalesce(click_table.goods_click_score_30day, 0)
- + coalesce(collect_table.goods_collect_score_30day, 0)
- + coalesce(add_cart_table.goods_add_cart_score_30day, 0)
- + coalesce(buy_table.goods_buy_score_30day, 0) as score
- from (
- SELECT user_id, goods_no FROM xxx_recommendation_system.user_goods_click_score_30day
- union
- SELECT user_id, goods_no FROM xxx_recommendation_system.user_goods_collect_score_30day
- union
- SELECT user_id, goods_no FROM xxx_recommendation_system.user_goods_add_cart_score_30day
- union
- SELECT user_id, goods_no FROM xxx_recommendation_system.user_goods_buy_score_30day
- ) as main_table
-
- left join xxx_recommendation_system.user_goods_click_score_30day as click_table
- on main_table.user_id = click_table.user_id and main_table.goods_no = click_table.goods_no
-
- left join xxx_recommendation_system.user_goods_collect_score_30day as collect_table
- on main_table.user_id = collect_table.user_id and main_table.goods_no = collect_table.goods_no
-
- left join xxx_recommendation_system.user_goods_add_cart_score_30day as add_cart_table
- on main_table.user_id = add_cart_table.user_id and main_table.goods_no = add_cart_table.goods_no
-
- left join xxx_recommendation_system.user_goods_buy_score_30day as buy_table
- on main_table.user_id = buy_table.user_id and main_table.goods_no = buy_table.goods_no
- )
- ;
求出数据,如下图所示,其中 score_normalization 字段表示进行归一化后的评分:
注:其他推荐系统相关文章链接由此进 -> 推荐系统文章汇总