大家看今日头条的推荐会发现,每次刷新都会推荐不同的内容,而且翻页也很少出现重复内容。
那么这种个性化推荐系统的架构是什么样的呢?
粗排召回
所有文章都会存在一个大候选池中,比如用Elasticsearch存储。
我们难以对大候选池中的所有文章做CTR模型预估,也难以在这么大的候选池中实现业务要求的排序策略,所以大候选池的作用只是粗粒度的文章召回。
在粗排阶段,我们召回的文章集合应该在千级别,比如:3000篇。
最简单的召回策略就是返回最近24小时的热门文章,当然如果可以结合用户偏好的文章类型更精细化的召回也可以考虑结合进来。
另外,大候选池只是数据源之一,我们还需要取一些运营位,广告位,它们整体作为我们的召回集合。
精排
这个阶段的输入文章规模是3000篇,我们遍历3000篇文章做CTR预估得到模型打分并排序,靠前的文章点击率越高。
配比
单纯依靠CTR得分排序,会造成内容不均衡,比如靠前的可能是大量的娱乐花边新闻,导致用户没法看到重要的时事政治新闻。
所以这个阶段需要结合业务特点,首先将3000篇文章按照业务类型等策略需求划分到多个小候选池,单个类型的候选池内可以进一步根据业务策略精细化排序。
最后,我们的目标是从众多候选池中,通过合理的分类配比生成要返回的20篇文章,也就是一页的内容。
这里配比就靠策略指定了,比如2篇体育的,2篇娱乐的,2篇政治的,大家可以根据公司需要决定。
广告和运营位在这个阶段可以插入到20篇文章的中间合适位置,一起返回给客户端。
去重
我们从3000篇粗排结果中甄选出20篇文章,返回给客户端。
那么当用户再次下拉或者刷新的时候,我们需要推荐与这20篇不同文章,这样用户才不会觉得重复推荐,所以这里需要去重。
所以当我们返回20篇文章之前,需要在redis里记录下这20篇文章ID。
当再次请求推荐接口时,我们仍旧从粗排召回3000篇文章(也许仍旧有部分文章属于我们之前返回的20篇),然后从redis里取出已经推荐过的文章ID,从3000篇中过滤掉推荐过的文章。
对于剩余的文章,我们继续走精排 -> 配比的逻辑即可。
因为大候选池内容不断生成新的,用户不断访问推荐接口,那么该用户的redis访问历史集合会越来越大,对我们去重计算会造成很大压力(也占用了很多内存),所以我们需要让每个用户的redis历史推荐集合自动淘汰,在设计时可以利用滑动窗口的方式,只保存最近N小时推荐过的文章ID即可。
极端情况
如果业务的内容生产速度较慢,而某用户持续的请求推荐接口,那么粗排召回的3000篇可能全部都被用户读过,导致去重后没有内容可以推荐。
这种极端情况下,代码可以直接取redis中的历史推荐文章ID(可以用zset保存,key是文章ID,score是推荐的时间点),直接按历史推荐集合timeline展现给用户即可。
如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~

感觉推荐过很久的文章id可以放在memcache之类的缓存里,做备份
去重集合越来越大,计算量也越来越大,肯定是要有淘汰机制的,不可能永远去重。
bloom filter 做去重如何
我们去重就是要把存在的删除掉,但是布隆过滤器只能明确哪些是不存在的,不太合适吧。
所以这个阶段需要结合业务特点,首先将3000篇文章按照业务类型等策略需求划分到多个小候选池,单个类型的候选池内可以进一步根据业务策略精细化排序。
—-
这一句。这样的话,岂不是要每个小池子单独都要去排序一遍,如果多次调用精排算法,性能是否会降低;
是不是可以统一排序后,再划分到多个桶里啊?
噢噢,这个你理解有点偏差。
多个候选池相当于多个数据源,比如广告系统是一个候选池,文章系统是一个候选池,从每个池子捞出一堆数据回来,一共凑出3000篇,具体比例根据产品需求定。
接下来是对3000篇做CTR排序。
然后,为了多样性,需要根据产品需求,对文章按品类啊或者按XXX去分桶。
最后,按照产品需求,依次从各个桶里取出一定数量的文章,凑成最终的一页20篇,加入去重集合,并且返回用户。