查看原文
其他

初来乍到:帮助新用户冷启的算法技巧

石塔西 石塔西的说书馆 2024-01-29

开篇

今天的文章还是由一个我亲身经历的故事来开篇。在一次面试中,面试官问我怎么处理“新用户冷启动”的问题,当时,我是从算法的角度来回答的,比如通过online learning,加快线上模型的更新频率;再比如,我当时想把PinSAGE由item-2-item升级为user-2-item,这样只要新用户点击过一个item A,我就可以将A的信息、点击过A的其他user的信息、点击过A的其他用户点击过的item信息、......都聚合到新用户身上。巴拉巴拉讲了一堆,面试官显得不是很满意,只见他大手一挥,中气十足地说到:“没那么复杂,处理新用户,归根结底就一句话,尽可能多地找这个用户的数据!!!”。当时我还是有点小受震撼的,感叹道,面试官一眼看到问题本质,怪不得是人家面试我呢。于是,我立刻切换到产品思路,比如构建产品矩阵,从其他产品了解用户;通过弹窗问卷的方式,收集用户的兴趣爱好;......,这下面试官方才满意,于是面试愉快地进行下去。

现在回过头来,重新思考“新用户冷启”这个问题。毫无疑问,针对我们所知甚少的新用户做推荐,是一个极其困难的问题,尽一切努力收集用户信息,也绝对是top priority。但是,因此我们算法就无事可做,等着产品和前端同学把新用户的数据送过来?

根据我近年来的经验,新用户的数据是极其重要的,但是并非问题的全部。比如,新老用户在app中的行为模式有很大不同(e.g., 比如一些产品设计不允许新用户做点赞、评论等动作)。但是,在训练的时候,老用户的样本在数量级上碾压新用户的样本(本来dau就主要是由老用户贡献,再加上一个老用户能够贡献多条样本),导致模型主要拟合老用户的行为模式,忽略了新用户。在这种情况下,增加几个对新用户友好的特征(比如年龄、性别等),因为主导模型的老用户不care(影响老用户推荐结果的主要是其过去历史,而非人口属性),加了也是白加。

噢,看似是一个样本不均衡的问题,也不是毫无办法。比如:

  • 给新用户的样本加权。行是行,但是有两个问题不太好解决,一是如何设定给这些新用户的权重,二是,毕竟老用户才是给我们贡献KPI的主力,给新用户加权,等于变相削弱老用户,可能得不偿失。
  • 干脆分家,就只拿新用户的数据单独训练一套模型,只服务新用户。也不是不行。但是,也有两问题不好解决,一是新用户数据量少,底层embedding和中间的dense weight可能训练不好;二来,单独训练,单独部署,有点浪费资源。

对付新用户,以上方法虽然简单,但是真的不妨一试。如果也觉得以上方法的缺点无法接受,不妨向下读,看看本文替你总结出的一些从算法层面助力新用户冷启的技巧。

正文开始之前,先提前声明几点:

  • 本文中所提及的新用户是一个泛称,未必是第一次登录、毫无动作的纯新用户,也包括那些虽然距首次登录已经过去许久,但是鲜有活动的用户。其实,文中的概念算是“低活用户”,但是为了行文方便,下文中还是用“新用户”统一称呼这些用户;
  • 本文中所提观点,和“收集用户信息是新用户冷启top priority”的观点并不矛盾。相反,以下算法技巧的目的,就是为了能够让模型更重视那些对新用户友好的特征,让这些特征真正发挥作用。
  • 算法毕竟是实验科学,就像股票点评之后总要加上一句“不构成投资建议”一样,以下介绍的技巧未必颗颗是银弹。究竟哪个管用,就让GPU和AB平台告诉我们答案吧。

加强重要区分性特征

既然我们通过数据分析,先验已知新老用户的行为模式有着较大差异,我们希望把这一先验知识告诉模型,让模型对新老用户能够区别对待。至于如何注入这样的先验知识,我已经在我的另一篇文章《先入为主:将先验知识注入推荐模型》介绍过了,简述如下。

首先,我们要构造能够显著标识新用户的特征,比如:

  • 一个标识当前用户是否是新用户的0/1标志位
  • 用户是否登录
  • 首次登录距今时间
  • 交互过的item的个数
  • ......

强bias特征加得浅

可以把这些强bias特征加得离final logit近一些,让它们对最终loss的影响更直接一些。比如:

  • 由其他特征获得 需要经过复杂且深的DNN结构
  • 而由以上强bias特征贡献的,只是将这些强bias特征通过一个LR或一层fully connection就得到

强bias特征当裁判

方法二,将这些强bias的特征,喂入SENet或或LHUC(Learn Hidden Unit Contribution,如下图所示),用来决定其他特征的重要性

  • 强bias特征作为LHUC的输入,经过sigmiod激活函数后,输出是一个N维度向量,N是所有fileld的个数

  • N维向量就是各field的重要性,将其按位乘到各field的embedding上,起到增强或削弱的作用。比如:

    • 比如产品设计不允许未登录用户点赞、评论,那么新用户样本中的相关特征的重要性“应该”为0
    • 用户点击哪个站外广告被引入app,这个信息只对新用户有用(权重应该大),对老用户意义就不大了(权重应该小)
  • 加权后的各field embedding再拼接起来,喂入上层DNN

这样做,相比于将所有特征“一视同仁”、一古脑地喂入DNN最底层等候上层DNN筛选,更能发挥那些对新用户友好的重要特征的作用。

借鉴多领域学习

正如开篇所说,为了避免新用户数据被老用户淹没,干脆分家,用新用户的数据单独训练一个模型只服务新用户。但是有两个问题不好解决,一是新用户数据少,单独训模型可能训不好;二是,单独训练+部署,有点浪费资源。

其实这个问题属于multi-domain learning的解决范畴。不要和multi-task learning相混淆,multi-domain learning研究的是如何将多个相关渠道的数据放在一起训练,而能够相互促进,而非相互干扰。这方面的工作并不多,让我们看看阿里近年来在这一领域发表的两篇文章。提醒读者,尽管两篇文章的初衷都不是针对新用户,但是在阅读过程中,我们可以代入new user domain vs. old user domain的场景


STAR结构

一篇是《One Model to Serve All: Star Topology Adaptive Recommender for Multi-Domain CTR Prediction》。这篇文章研究的是,如何训练并部署一个模型,就能胜任“首页推荐”、“猜你喜欢”等多个领域。前两个改进也很“直觉”(嘿嘿,知道我模仿的是谁吗?)

  • 每个域学习各自的batch normalization
  • , 是要全局学习的scaling和bias
  • , 是某个domain "p"独有的scaling和bias
  • 将domani id这样的bias特征,通过一个浅层网络,加得离final logit近一些。这一点就是我们在上一章节介绍的技巧。

第三个改进,就是所谓的星型拓扑结构,也就是论文标题的来源。简单来说,就是每个domain要经过的final dense weight是domain specific part和shared part融合而成

  • 是domain 'p'的样本最终要经过的final weight,

  • 是domain "p"独有的weight,只让domain "p"的样本参与迭代更新

  • W是shared weight,所有domain的数据都要参与更新。

  • 是element-wise multiplication

我有点不太好评价这种结构。其实想结合domain specific information与shared information也不算什么新概念,比如下面两种:

  • domain specific weight与shared weight是并联的关系,样本信息同时经过domain specific weight和shared weight向上传递,
  • 模仿早先multi-task learning中常见的share bottom结构,先有共享的底层shared weight,各个domain再在上面串联上domain specific weight。预测时,样本先经过shared weight,再经过domain specific weight向上传递,

文中并没有给出这种融合方式,相对于我在上文提出的两种融合方式的优势,或许没啥大道理,又是实验得出的结论。我自己给出的理由只能是:

  • 无论是domain specific weight与shared weight是并联还是串联,前代回代中,信息所经过的每一层,都只属于单一通道(要么是domain specific,要么是share),融合得并不彻底;
  • ,信息流过的每一层都是domain specific weight和shared weight融合后的,融合得更充分。

HMoE结构

AliExpress的文章《Improving Multi-Scenario Learning to Rank in E-commerce by Exploiting Task Relationships in the Label Space》聚焦于多国家场景下的推荐。这个场景,和我们新用户的场景更加类似一些,多个国家的人口迥异,市场进入有早有晚,相对于先发国家庞大的用户基数和丰富的行为,后发国家的用户少、行为少,可以类比于“新用户冷启”的场景

这篇文章提出的HMoE与STAR完全相反,没有所谓的shared weight。其原理假设是:

  • A国家中,有些用户a的行为和B国的用户很像,所以,让B国的DNN给a打分也很靠谱,异域模型打分的借鉴意义大;反之,异域模型打分的借鉴意义小。
  • 回到我们新用户的场景下,用老用户模型给新用户打分,可能对部分新用户也是大有裨益的

具体做法上:

  • 无论哪个域的样本,都要经过所有domain的DNN打分(下面公式中的S),
  • 最终得分,是在各个域打分上的加权平均。权重(下面公式中的W)由一个gate netowrk根据输入样本,个性化决定。
  • 训练时,domain=t的样本,只能更新自己的,对其他domain的DNN都要stop gradient。这一点也相当直觉,毕竟其他domain的DNN帮domain t打分是副业,不能强求其他domain的DNN为domain t上的loss负责。

获得对新用户友好的初值

这种方法的思路是,新用户训练困难是因为数据少。但是,如果新用户的初始值并非随机,而是已经非常靠谱了,通过少量数据+几次迭代,就能收敛到不错的用户表示,岂不妙哉!


强烈吐槽MeLU

上述思路简直就是为MAML(Model-Agnostic Meta-Learning)量身定做的。MAML主打的招牌就是Fast Adaptation

  • 先通过多个task的学习,将参数学习到一个不错的状态
  • 有了新任务1(or 2 or 3),该新任务只需要提供少量数据,就能够从出发,快速收敛到这个新任务的最优参数(or or

于是基于MAML,韩国人提出了MeLU想学习出对新用户友好的初始值。文章的细节,我就不多说了,因为如果基础错了,剩下的实现细节也就毫无价值,浪费笔墨罢了。

我就问两个问题:

  • 注意上图中的是从派生出三组完全不同的参数。而MeLU中的task粒度是每个用户算一个task。这也就意味着,哪怕真有fast adapation的能力,来了N个新用户,就要衍生出N组不同的参数?!想想在一个真实的推荐系统中,要包括所有dense weight和一些能够跨user共享的embedding(e.g., tag embedding之类的),参数量还是不小的,而MeLU竟然要为每个新用户都学习出单独的一套出来???!!!
  • 新用户训练和预测的难点就在于,他的一些特征是之前从来就没出现过的。比如一个用户最具个性化的特征就是user id embedding,新用户的user id embedding压根就不会在中出现, adapt to nowhere !!!

我也就纳闷了,这种脱离推荐系统实际、毫无实战价值的文章,是怎么被KDD录入的?还被那么多讲新用户冷启的文章引用!简直是误人子弟!!!


用Group Embedding代替新用户的Id Embedding

介绍一种我自己设计的方法,用来获取良好的new user id embedding初值,在一些场景下的效果还不错。

我的方法的前提假设是:

  • user id embedding是最具个性化的特征输入,在老用户推荐中发挥了极其重要的作用,DNN也非常重视这个特征(e.g., 接在user id embedding上方的weight比较大)。
  • 但是问题就在于,由于新用户的user id embedding出现次数少,经过有限次训练,new user id embedding还离最初随机初始化的结果不远;而在预测的时候,由于PS有特征准入规则,新用户的id embedding压根就没有被PS收录,导致inference server向PS请求该新用户的id embedding时返回一个全0向量,导致DNN所期待的重磅特征user id embedding落了空。

既然user id embedding那么重要,而新用户的user id embedding质量却那么差(训练时得到的随机向量,预测时得到的是全0向量),那么能不能找到一个替代方案?这时,Airbnb的经典文章《Real-time Personalization using Embeddings for Search Ranking at Airbnb》给我提供了一种思路。在Airbnb的实践中,由于booking这个行为太稀疏,所以绝大多数用户都是“新用户”,而Airbnb的思路就是根据规则将用户划分为若干用户组(比如,“讲英语,使用苹果手机”的用户群,和“讲西班牙语,使用Android手机”的用户群),然后用user group embedding代替单独的user id embedding参与后续训练与预测

基于以上假设,解决思路是:

  • 将user id embedding(无论新老用户)拆解成

    • 注意这个分群是“初始用户群”,划分时,只能依靠那些对新用户友好的特征,比如:年龄、性别、安装那些(种类)的app,是被哪个(类)广告拉新进来的、......
    • 是“初始的用户群”的embedding。

    • 代表自用户进入app之后,随着在app内部行为越来越丰富,他距离当初“初始用户群”embedding的个性化残差

  • 对于老用户,原来的方法是直接学习每人各自的user id embedding,现在是改学,所有个性化信息都包含在这个个性化残差里了。

  • 对于新用户

    • 训练时,尽管一开始还是随机向量,但是毕竟是在已经训练好的周围(怎么训练,下面会讲),对于上层的DNN来说,也算是一个不错的初始值
    • 预测时,inference server遇到纯新用户,由于未收录该新用户的从而返回全0向量,结果从DNN的角度来看,。也就是,新用户的user id embedding被用户群embedding代替,强于之前模型用全0向量填充的方法。

目前还遗留一个问题,就是怎么得到?我的方法是采用预训练的方法

  • 如上所述,分群是“初始用户群”,划分时,只能依靠那些对新用户友好的特征,比如:年龄、性别、安装那些(种类)的app,是被哪个(类)广告拉新进来的、......

  • 借鉴Airbnb的方法,用类似word2vec的方法获得各,具体来说,

    • 正样本:某个新用户的,与其交互过的item embedding,在向量空间上应该足够近
    • 负样本:随机采样一些item embedding
  • 注意,在我的实践中,进行以上预训练,只采用新用户的数据,不用老用户的数据

    • 这么做是因为,老用户交互的item,主要是由推荐系统根据用户历史推荐出去的,和用户的初始元信息(比如,年龄、性别、App等)关系已经不大了。引入老用户数据,反而引入了噪声
  • 预训练完毕,将各init user group embedding保存起来

注意,在正式训练的时候,

  • 对于老用户要stop gradient,毕竟年代久远,init user group embedding不应该再为老用户推荐结果上的loss负责。也防止规模巨大的老用户数据,将已经训练好的带偏
  • 至于新用户,最好对也stop gradient,一来新老用户一致,方便实现;二来,我们希望信息都积累在上,而不是在group embedding上。

最后提一下,既然要利用new user friendly feature分群,再用user group embedding代替新用户的user id embedding,那为什么不直接将这些特征接入DNN,直接让DNN学习不就完了,何必这么麻烦?如果有小伙伴能够想到这一层,首先要为你自己的独立思考能力点赞。问题的关键还是这些new user friendly feature的接入位置

  • 就像我在开篇中所说,直接将new user friendly feature接入DNN底层,很可能起不到提升的作用。还是因为老用户数据已经主导了模型,模型对新用户特征压根不重视(e.g., 接在这些特征的weight都很小)。
  • 但是模型是非常重视user id embedding这个特征的(e.g., 接在它上面的weight都很大),因此只有将user group embedding接在user id embedding相同的位置,才能真正发挥作用

总结

本文聚集于新用户冷启这一让推荐算法工程师头疼的问题,从以下几个方面总结了我个人的看法与经验:

  • 对于新用户冷启,top priority肯定还是尽力收集用户的数据。
  • 但是仅靠收集数据也是不够的。比如:在一个成熟的app中,老用户的样本碾压新用户样本,主导了模型。徒增了几个new user friendly的特征,模型也不会重视。
  • 因此,也还需要一些算法上的技巧,让收集到的新用户信息,能够真正发挥作用。
  • 技巧1:要把new user friendly特征加到合适的位置,比如加得离loss更近一些,再比如作为gate的输入,衡量其他特征的重要性。
  • 技巧2:将新老用户看成两个domain,借鉴multi-domain learning的经验,让老用户模型能够助力新用户,又不至于过分主导。
  • 技巧3:新用户的初值非常重要。先吐槽了MeLU这种毫无实战价值、误人子弟的文章;接着介绍了我自己设计的用user group embedding代替user id embedding的算法,帮大家打开思路。

本人的其他文章,感兴趣的小伙伴可以继续阅读:

- END -


继续滑动看下一个

初来乍到:帮助新用户冷启的算法技巧

石塔西 石塔西的说书馆
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存