当前位置: 华文星空 > 知识

有哪些解决推荐系统中冷启动的思路和方法?

2011-09-07知识

开篇

今天的文章还是由一个我亲身经历的故事来开篇。在一次面试中,面试官问我怎么处理「新用户冷启动」的问题,当时,我是从算法的角度来回答的,比如通过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的影响更直接一些。比如:Logit_{final}=Logit_{ordinary} + Logit_{bias}

  • 由其他特征获得Logit_{ordinary}, 需要经过复杂且深的DNN结构
  • 而由以上强bias特征贡献的Logit_{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
  • \gamma , \beta 是要全局学习的scaling和bias
  • \gamma_p , \beta_p 是某个domain "p"独有的scaling和bias
  • 将domani id这样的bias特征,通过一个浅层网络,加得离final logit近一些。这一点就是我们在上一章节介绍的技巧。
  • 第三个改进,就是所谓的星型拓扑结构,也就是论文标题的来源。简单来说, 就是每个domain要经过的final dense weight是domain specific part和shared part融合而成

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

  • W_p 是domain "p"独有的weight,只让domain "p"的样本参与迭代更新
  • W是shared weight,所有domain的数据都要参与更新。
  • \otimes 是element-wise multiplication
  • 我有点不太好评价这种结构。其实想结合domain specific information与shared information也不算什么新概念,比如下面两种:

  • domain specific weight与shared weight是并联的关系,样本信息同时经过domain specific weight和shared weight向上传递,Logit_{final}=Logit_{domain}+Logit_{share}
  • 模仿早先multi-task learning中常见的share bottom结构,先有共享的底层shared weight,各个domain再在上面串联上domain specific weight。预测时,样本先经过shared weight,再经过domain specific weight向上传递,Logit_{final} = F_{domain}(F_{share}(x))
  • 文中并没有给出W_p \otimes W 这种融合方式,相对于我在上文提出的两种融合方式的优势,或许没啥大道理,又是实验得出的结论。我自己给出的理由只能是:

  • 无论是domain specific weight与shared weight是并联还是串联,前代回代中,信息所经过的每一层,都只属于单一通道(要么是domain specific,要么是share),融合得并不彻底;
  • 而W_p \otimes W ,信息流过的每一层都是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的样本,只能更新自己的DNN_t ,对其他domain的DNN都要stop gradient 。这一点也相当直觉,毕竟其他domain的DNN帮domain t打分是副业,不能强求其他domain的DNN为domain t上的loss负责。
  • 获得对新用户友好的初值

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

    强烈吐槽MeLU

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

  • 先通过多个task的学习,将参数学习到一个不错的状态\theta ;
  • 有了新任务1(or 2 or 3),该新任务只需要提供少量数据,就能够从\theta 出发,快速收敛到这个新任务的最优参数\theta_1^* (or \theta_2^* or \theta_3^* )
  • 于是基于MAML,韩国人提出了MeLU想学习出对新用户友好的初始值。文章的细节,我就不多说了,因为如果基础错了,剩下的实现细节也就毫无价值,浪费笔墨罢了。

    我就问两个问题:

  • 注意上图中的\theta_1^* 、 \theta_2^* 、\theta_3^* 是从\theta 派生出三组完全不同的参数。而MeLU中的task粒度是每个用户算一个task。这也就意味着,哪怕\theta 真有fast adapation的能力, 来了N个新用户,就要衍生出N组不同的参数 ?!想想在一个真实的推荐系统中,\theta 要包括所有dense weight和一些能够跨user共享的embedding(e.g., tag embedding之类的),参数量还是不小的, 而MeLU竟然要为每个新用户都学习出单独的一套出来???!!!
  • 新用户训练和预测的难点就在于,他的一些特征是之前从来就没出现过的。比如一个用户最具个性化的特征就是user id embedding,新用户的user id embedding压根就不会在\theta 中出现, \theta 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(无论新老用户)拆解成E_{uid} = E_{InitGroup} + E_{PersonalResidual}
  • E_{InitGroup} 是「初始的用户群」的embedding。
  • 注意这个分群是「初始用户群」,划分时,只能依靠那些对新用户友好的特征 ,比如:年龄、性别、安装那些(种类)的app,是被哪个(类)广告拉新进来的、......
  • E_{PersonalResidual} 代表自用户进入app之后,随着在app内部行为越来越丰富,他距离当初「初始用户群」embedding的个性化残差
  • 对于老用户,原来的方法是直接学习每人各自的user id embedding,现在是改学E_{PersonalResidual} ,所有个性化信息都包含在这个个性化残差里了。
  • 对于新用户
  • 训练时,尽管一开始E_{PersonalResidual} 还是随机向量,但是 毕竟是在已经训练好的E_{InitGroup} 周围 (怎么训练,下面会讲),对于上层的DNN来说,也算是一个不错的初始值
  • 预测时,inference server遇到纯新用户,由于未收录该新用户的E_{PersonalResidual} 从而返回全0向量,结果从DNN的角度来看, E_{uid} = E_{InitGroup} 。也就是,新用户的user id embedding被用户群embedding代替 ,强于之前模型用全0向量填充的方法。
  • 目前还遗留一个问题,就是E_{InitGroup} 怎么得到?我的方法是采用 预训练 的方法

  • 如上所述,分群是「初始用户群」,划分时,只能依靠那些对新用户友好的特征,比如:年龄、性别、安装那些(种类)的app,是被哪个(类)广告拉新进来的、......
  • 借鉴Airbnb的方法,用类似word2vec的方法获得各E_{InitGroup} ,具体来说,
  • 正样本:某个新用户的E_{InitGroup} ,与其交互过的item embedding,在向量空间上应该足够近
  • 负样本:随机采样一些item embedding
  • 注意,在我的实践中,进行以上预训练, 只采用新用户的数据,不用老用户的数据
  • 这么做是因为,老用户交互的item,主要是由推荐系统根据用户历史推荐出去的,和用户的初始元信息(比如,年龄、性别、App等)关系已经不大了。引入老用户数据,反而引入了噪声
  • 预训练完毕,将各init user group embedding保存起来
  • 注意,在正式训练的时候,

  • E_{InitGroup} 对于老用户要stop gradient ,毕竟年代久远,init user group embedding不应该再为老用户推荐结果上的loss负责。也防止规模巨大的老用户数据,将已经训练好的E_{InitGroup} 带偏
  • 至于新用户,最好对E_{InitGroup} 也stop gradient ,一来新老用户一致,方便实现;二来,我们希望信息都积累在E_{PersonalResidual} 上,而不是在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的算法,帮大家打开思路。
  • 本人的其他文章,感兴趣的小伙伴可以继续阅读:

  • 负样本为王:评Facebook的向量化召回算法
  • 无中生有:论推荐算法中的Embedding思想
  • 万变不离其宗:用统一框架理解向量化召回
  • 先入为主:将先验知识注入推荐模型
  • 久别重逢话双塔
  • 少数派报告:谈推荐场景下的对比学习
  • 刀功:谈推荐系统特征工程中的几个高级技巧
  • - END -