《互联网大厂推荐算法实战》笔记

发布于 2023-12-08  372 次阅读


推荐系统简介

第二步,建立倒排索引,将所有物料组织起来,如所示。倒排索引类似一个HashMap,键是标签,值是一个列表,包含着被打上这个标签的所有视频。

业务端的以标签检索为主的物料搜索引擎,如ElasticSearch

第三步,推荐系统接到一个用户的推荐请求。推荐系统根据从请求中提取出来的用户ID,从数据库中检索出该用户的兴趣爱好。在本例中,我们用一种非常简单的方式表示用户的兴趣爱好。假设该用户过去看过10个视频,其中7个带有"喜剧"的标签,3个带有"足球"的标签,则提取出来的该用户的兴趣爱好就是{"喜剧":0.7,"足球":0.3},其中键表示用户感兴趣的标签,后面的数字表示用户对这个标签的喜好程度。

使用过往行为数据进行用户偏好建模

  • 推荐系统是一个体现"人人为我,我为人人"的地方。你通过划动拇指为每个物料投票,作为"义务"标注员,帮推荐系统认清各个物料的优劣。优质的内容会通过推荐系统这个"影响放大器"扩散给更多用户,为更多与你有相似兴趣爱好的人提供价值。

共现矩阵机制,类似nlp中的分布式原理,某个字符的含义由周围字符来决定。

  • 召回是从一大堆物料中排除与用户兴趣八杆子打不着的,留下还比较合用户品味的。举个例子,召回好比经历过社会历练,无论哪种"不靠谱",他都见识过。

召回更注重查全率

  • 推广搜都遵循"先由召回模块粗筛,再由排序模块精挑细选"的功能架构。
  • 数据架构上,推广搜都遵循Lambda架构。
  • 因为从业务本质到系统架构都是高度相似的,很多算法、模型在"推广搜"三个领域都是通用的。一个领域发表的论文,很容易在其他两个领域复现;由于技术栈也是相同的,一个领域的工程师也很容易转到其他两个领域。
  • 推广搜都需要高度的个性化。大家对个性化推荐已经习以为常了,而事实上广告对个性化的需求更高。毕竟推荐结果不符合用户兴趣,只是有损用户体验而已,而广告如果不满足用户需求的话,浪费的可是真金白银。搜索也不能仅仅满足于返回的文档的确包含了搜索关键词。比如不同用户搜索"苹果价格",显示的结果肯定是不同的,至于显示的是水果价格还是手机价格,就取决于搜索系统对用户画像的掌握与利用。

个性化程度:广告>推荐>搜索

广告点击率容错率更加低,查准率要求更高,效果直接和营收挂钩,推荐到业务仍然有一些距离

搜索首先要考虑检索词本身,之后再考虑与用户的相关性的定制化

推荐系统中的特征工程

认为深度学习能够进行"自动化特征工程",传统的"人工特征工程"已经落伍、过时。

DNN隐式特征交叉的缺点:

  • 是否能很好的模拟高阶特征交叉仍存疑
  • 计算劣势,特别是全连接结构,可能大部分神经元都是无用的

值得注意的是,在大型推荐系统中,物料的唯一标识(Item ID)也是重要的特征。可能有些读者不理解,觉得Item ID就是一串无意义的字母数字组合,里面能有啥信息?而且Item ID作为一个类别(Categorical)特征,可能包含有几十万、上百万的特征值,特征空间膨胀得厉害,模型学得出来吗?

首先,模型无须理解Item ID那串字符的含义,只要记住就好。

ID类型特征应当用记忆性强的网络结构(如lr)

随着深度学习的发展,另一种从物料内容中提取信息的思路是,同样还是用CNN或BERT模型,这回拿模型的某一层的输出,当成物料特征,喂入上层模型。尽管这个向量不如那几个标签好理解,但是它有32位或64位那么长,里面蕴含的信息要比几个标签丰富一些。

Embedding的维度更丰富,且描述的是一种分布

物料的动态画像,指它们的后验统计数据,反映了物料的受欢迎程度,是物料侧最最重要的特征。

物料的动态画像可以从以下两个维度来进行刻画:

  • 时间粒度:全生命周期、过去一周、过去1天、过去1小时、......;
  • 统计对象:CTR、平均播放进度、平均消费时长、排名、......;

 

  • 我们需要辩证地看待这些后验统计数据。一方面,它们肯定是有偏的,一个物料的后验指标好,只能说明推荐系统把它推荐给了对的人,并不意味着把它推给任何人都能取得这么好的效果,这里面存在着"幸存者偏差"。另一方面,如果这些后验指标参与精排,"幸存者偏差"的影响还不至于那么严重,毕竟交给精排模型的物料都已经通过了召回、粗排两环节的筛选,多多少少是和当前用户相关的,它们之前的后验指标还是有参考意义。
  • 利用这些后验统计数据做特征,多少有些纵容马太效应,不利于新物料的冷启。后验指标好的物料会被排得更靠前,获得更多曝光与点击的机会,后验指标会更好,形成正向循环;而新物料的后验指标不好甚至没有,排名靠后而较少获得曝光机会,后验指标迟迟得不到改善,形成负向循环。我们将在2.3.1节讨论解决这个问题的方法。

不展开的另外一个原因是,根据笔者个人观点,人口属性等信息对推荐算法的作用并不大,纯属"食之无味,弃之可惜"的鸡肋。对于老用户,他们丰富的历史行为已经足够反映他们的兴趣爱好,自然轮不上人口属性等静态画像发挥重要使用。

用户特征一般使用他们所交互过的物料来做表征。

至于如何解决"位置偏差",一种方法是从数据入手,比如更加严格地定义正负样本。比如有一种Above Click的作法规定,只有在点击物料上方的未点击物料,才能被纳入训练数据当成负样本,如图2‑3所示。

Dummy Feature只能通过一个线性层加入模型,绝对不能和其他正常特征一起喂入DNN,如图2‑4所示。

推荐系统中的Embedding

只要我们喂入算法的,不是细粒度的概念,而是粗粒度的特征向量,即便是LR这样强记忆的算法,也能够具备扩展能力。

词汇概念需要经过额外预训练大模型才可以抽取出一些丰富的特征,特征向量可以用距离度量相关性。

简单来说,Embedding将推荐算法从"精确匹配"转化为"模糊查找",从而让模型能够"举一反三"。

模型拟合的是一种分布,而不是精确离散值。

精排

 

召回

基于物料属性的倒排索引

基于历史交互物料的属性值进行频率统计,直接使用频率高的属性值来反向检索物料

协同过滤

ItemCF用于召回时先使用物品用户共现矩阵转置(是否先转置取决于矩阵形状和是计算用户相似度还是物品相似度)后乘以自身得到物品相似度矩阵,之后针对指定用户计算n个物品的分数,排序后取topk作为召回物料

也可以使用UserCF维护每个用户的相似用户列表,在召回时,根据相似度权重对每个相似用户拉取一定数目的近期交互物品作为召回物料

如何合并多路召回

类似多路归并的思想

  • 召回的目标,是将用户可能喜欢的,和海量跟用户兴趣"八杆子打不着"的,分隔开,所以,召回所面对的候选物料集合,可谓"鱼龙混杂,良莠不齐"。

和排序的最大差异就是面对的数据集的分布是不一样的,排序的数据是召回筛选过的,而召回面对的是全量物料。随机采样也需要有一定策略(热门物品过采样,冷门物品欠采样、按业务区分困难负样本与简单负样本)

向量化召回统一建模框架

向量化召回

  • 训练模型将用户与物品(或只是物品)映射到一个向量空间
  • 特征实时变化较低的实体(例如物品)预先计算其特征向量,是存储至向量检索引擎,建立索引
  • 在线serving时,若查询为用户则为u2i召回,查询为物料则是i2i召回

向量化召回建模时回答以下4个问题

  • 如何定义正样本,即哪些qt在向量空间内应该相近;

    • I2I召回:检索与结果都为物料

      • 同一会话中的物料
      • 二部图中个性化pagerank分数相近的物料(pinsage)
    • U2I召回:用户检索物料

      • 用户与物料交互对
    • U2U召回:检索与结果都为用户

      • 对比学习,目标是使用用户交互的物料序列代表用户,那么指定用户的交互序列可以使用数据增强技术来生成正样本
  • 如何定义负样本,即哪些qt在向量空间内应该较远;

    • 负样本主要靠随机采样
    • 负样本区分困难负样本和简单负样本
    • 热门物料过采样、冷门物料欠采样
  • 如何将qt映射成Embedding;

  • 如何定义优化目标,即如何制订损失函数;

如何定义优化目标

  • Listwise:Sampled Softmax Loss
  • Pairwise:Marginal Hinge Los、Bayesian Personalized Ranking(BPR) Loss
  • Pointwise:BCE(一般不使用)

不同的优化目标对于数据集样本结构有较大不同,Listwise需要构建一个正样本-多个负样本,若使用batch内负样本,可以简化构建复杂度;Pairwise需要anchor-pos-neg三元组;Pointwise则是常规的用户与样本

粗排与重排

粗排

粗排双塔与召回双塔的异同

  • 都需要隔离用户塔与物品塔
  • serving时,用户embedding和物品embedding(做缓存)实时进行内积计算,找到精确的topk相似度物品
  • 粗排,可以和精排一样,拿"曝光未点击"当负样本
  • 粗排建模时类似精排,使用BCE作为损失函数
  • 粗排可以使用embedding作为输入喂入一个简单的DNN打分模型,得到物品和用户的相似性分数,而不是仅仅像召回使用l2正则化后的点积等价余弦

双塔模型改进思路

  • senet进行特征赋权

  • 重要特征短路跳接

  • ...

  • 以精排为教师模型,知识蒸馏出粗排模型

    • 以精排为教师模型,可以提升粗排精排一致性,避免粗排筛选的物料在精排阶段分数过低而被过滤

    • 训练方法

      • 联合训练,在训练精排的时候,同时训练粗排
      • 两阶段蒸馏法,让粗排拟合线上精排模型的输出(实际上的知识蒸馏也是这么做的)

模型上的改进思路其实和精排差不多,不过粗排终究是需要比精排更高的效率,结构上复杂改进需要谨慎

粗排模型的职责是为精排提升高质量的候选物料集,而质量高低是由精排说了算。理论上来讲,被精排排在前面的物料应该作为粗排的正样本,反之被精排排在后面的物料应该作为粗排的负样本。

其实如果使用知识蒸馏,这一点已经隐含的实现了。不使用知识蒸馏的话才需要将精排过滤的当负样本,靠前的当正样本

轻量全连接改进思路

  • 特征筛选
  • 削减全连接层的规模,层数或者层内单元个数
  • 底层框架的一些工程优化
  • 缺点:计算复杂度与召回列表长度成线性关系

个人感觉可以直接用预定于小模型结构,来拟合精排的输出

重排

基于启发式规则

  • 滑窗打散:即在一个长度为K的滑动窗口(Sliding Window, SW)内,相似物料最多出现n次。窗口内不满足则和窗口后物料进行交换

  • 分桶打散:使用规则进行分桶,桶内元素在规则上趋于同质。按照不同桶每次取一个元素出桶构成曝光列表

  • MMR:

    • 量化了相关性和多样性
    • 决策过程为每次从候选里选一个能带来最大边际收益的物料,边际收益组合考虑了新物料对用户的相关性与对整个集合的多样性影响

基于行列式点过程

基于上下文感知排序

多任务与多场景

为什么不为每个目标单独建模?

与广义的多任务学习的优势相同(推荐中多任务和通用多任务很大的不同是,其多个任务往往都存在前后等依赖关系)

  • 多个任务共享一个模型,占用内存量减少;(训练开销)
  • 多个任务一次前向计算得出结果,推理速度增加;(serving算力开销)
  • 关联任务通过共享信息,相互补充,可以提升彼此的表现。(知识迁移)

虽然多任务学习有着诸多好处,但是多任务学习也包含着许多挑战。多任务学习经常会出现跷跷板现象,即两个任务联合学习的时候,可能一个任务效果变好,另一个任务效果变差。究其本质,核心是训练过程中存在以下3个方面问题:

  • 多任务梯度方向不一致:同一组参数,不同的任务更新方向不一致,导致模型参数出现震荡,任务之间出现负迁移的现象,一般出现在多个任务之间差异较大的场景;
  • 多任务收敛速度不一致:不同的任务收敛速度不一样,有的任务比较简单收敛速度快,有的任务比较困难收敛速度慢,导致模型训练一定轮数后,有的任务已经overfitting,有的任务还是underfitting的状态;
  • 多任务loss取值量级差异大:不同的任务loss取值范围差异大,模型被loss比较大的任务主导,这种情况在两个任务使用不同损失函数,或者拟合值的取值差异大等情况下最为常见。

针对上述问题,业界通常有以下解决方法:

  • 设计网络结构
  • 平衡损失
  • 对梯度进行操作

本书该章节主要论述网络结构改进

为什么不直接建模终极目标?

  • 任务与任务之间有gap,每个任务都代表了一种层次的指标,这些指标都很重要不等价且可能相互依赖
  • 在链路越靠后的任务,实际创造价值越大的任务,其数据量越少

并行建模

多个任务在级别被一视同仁的看待

Share Bottom

共享底层编码器,做到知识迁移

Multi-gate Mixture-of-Experts

多个底层编码器,使用单个或者多个门控来自适应他们对不同任务的相关度

Progressive Layered Extraction

增加了底层编码器分为任务独占和任务共享,编码器分为多层

串行建模

考虑多个任务的层次性依赖关系

image-20231219143912417

ESMM

  • 将CVR与CTR、CTCVR一样,都建模在"曝光样本空间"上(理论上,这些任务都应该建模在粗排结果集上,但是实现起来有难度,所以大家都约定俗成建模在"曝光样本"上了,其中的SSB就忽略不计了)。

  • 但是,毕竟"曝光未点击"的样本不符合CVR的定义,因此CVR只能作为隐藏目标,在其他目标被优化的同时,被间接优化。

    cvr定义是点击后购买的转化率,其面对的样本应该是所有点击的数据,因此这两个的样本不应该一致,分开建模

  • cvr模型和ctr模型共享embedding层实现知识迁移,由CTCVR=CTR x CVR,将CTCVR作为另一个建模目标,将两个模型在末尾再次做联结

ESM2

引入了淘宝业务上的其他相关行为作为信号(加购,加愿望清单),依靠条件概率公式加入模型作为辅助损失

更通用的知识迁移

提取前端环节的隐层输出,喂给后端环节,辅助训练后端环节。需要注意的是,在将前端环节的输出作为后端环节的输入之前,需要要先stop gradient,防止后端环节反而将训练好的前端隐层带偏。

类似embedding技术

image-20231219145617664

ESCM2

多个损失的融合

目前,设置损失权重主要还是依靠人工经验,边调节权重,边观察离线、在线指标,来确定各损失的最优权重。但是,业界也有了一些半自动化的算法,帮我们缩小搜索空间,比如阿里在2019年提出的基于帕累托有效(Pareto-Efficient)[10]的算法PEC。Pareto-Efficient代表了多目标优化时的理想状态,彼时,多目标中的任何一个目标想要继续优化,只能以损失其他目标为代价。

  • UncertaintyWeight
  • GradNorm
  • DWA
  • PCGrad
  • GradVec

多个打分的融合

尽管模型会为每个目标都预测一个分数,但是排序时我们只能依靠一个指标,因此排序前我们必须将多个目标的打分融合成一个分数。

 

多场景推荐

 

冷启动

 

 

评估与调试

离线评估

  • 新老模型都重新初始化做周级离线数据评估测试
  • 新模型用全量历史数据训练追平老模型性能,之后接入线上
  • 线上流量划分为对照组和实验组进行ab测试
  • 在线评估的优点是,线上真实的环境,真实的用户反馈,能比较客观反映模型对业务的影响。缺点是,准备阶段回溯历史要等,上线后搜集用户反馈也要等,时间成本非常高。

在线评估也有导致实验组用户体验降低的风险,使得大盘数据变差。

评估排序算法

AUC

  • False Positive Rate(FPR)为横坐标、True Positive Rate(TPR)为纵坐标

  • 这些点就连与一条ROC(Receiver Operating Characteristic)曲线,ROC曲线与X轴围成的面积就是AUC

  • ROC曲线越向左上角靠拢,AUC越大,模型的分类性能越好

  • 让模型给一堆样本(正负类别标签已知)打分,然后将这堆样本按模型打分从大到小排序,正样本能够正确地排在负样本前面的概率就是AUC

    image-20231207151210509

  • AUC就是为了衡量模型的排序性能而设计的。如果说一个模型的AUC=0.7,按这个模型的打分排序,有70%的概率能够将正样本排在负样本前面。

  • 普通AUC将所有用户混为一谈,误将不同用户之间的排序也考虑了进去,解决办法就是改用Groupwise AUC(GAUC),也就是将测试样本划分为更小粒度的Group,每个Group统计一个AUC,再将不同Group的AUC加权平均,这样就能避免不同Group的样本相互干扰的问题

NDCG

  • 用AUC衡量排序性能的缺点,就在于其不能反映排序位置的这个"折扣"效应。为了弥补AUC的这个缺陷,业界发明了Discounted Cumulative Gain(DCG)这个指标,综合考虑每个位置上的物料能带来的收益和该位置的折扣效应

    从分母上,我们可以看出,越靠后的位置,对物料价值打的折扣就越大。

    使用了排序位置做正则,排序越靠后,惩罚越大

  • 不同推荐请求的结果长度不同,计算出来的DCG不好直接比较,定义Ideal DCG(IDCG)(理想排序计算出的DCG就叫IDCG),使用其对DCG进行归一化,得到的就是Normalized DCG(NDCG)

评估召回算法

AUC指标不适用于衡量召回模型:

  • 负样本选择困难,无法单纯使用曝光未点击作为负样本(链路上召回模型所面对的数据集分布就和实际曝光的不一致)
  • 拿召回结果中,除点击之外的其他物料当负样本也不可行,因为点击是对于最后的曝光列表来说的,曝光列表被后续粗排和精排过滤后得到的,其他物料没被点击的原因是可能根本没曝光机会(被过滤了)
  • 归根到底,召回的任务和精排不一样,他的任务不是排序,而是查找,因此用AUC不合适

在评估召回模型时,我们一般不用AUC这样强调排序性能的指标,也避免直接统计负样本,而是从预测正样本与真实正样本之间的"命中率"、"覆盖度"视角出发来进行评估。

这两个指标和真负例无关。

Precision & Recall

精确率 Precision

分母为所有预测为的情况,分子为预测为且真实值为的情况,则表示在所有预测为c的情况中,预测正确的比例为多少。表示对于每次预测为类别时,查得有多准。在二元分类中,表示预测为正样本的时候,有多大比例是预测对的。

召回率 Recall

分母为所有真实值为的情况(预测为且预测正确的情况加上预测为非且预测错误的情况),分子为预测为且真实值为的情况,表达在所有真实值为c的情况中,预测正确的比例为多少。表示真实值为类别时,多大的概率能预测准确。在二元分类中,表示正样本中有多大比例被正确预测。

MAP

一个更加全面的衡量方法是,假设召回物料是按与用户的相关度降序排列的(这一点很容易做到),每次只取前i个作为召回结果返回,将不同i下的Precision、Recall连接成曲线,然后计算这个Precision-Recall曲线下的面积,被称为Average Precision(AP)

逐渐增加召回个数,将导致Recall非严格单调增(全量召回,recall将达到百分百),Precision非严格单调减。

Hit Rate

Hit Rate@k,表示在N个样本对中,有多少物料可以在用户检索的召回列表中被找到

人工评测

主要是一些case study的感觉,人工对一些有限case进行查看

在线评估:A/B实验

A/B实验的缺点:

  • 基建成本高
  • 实验时间周期长
  • 保证单一变量之类的需要注意的细节多
  • 划分流量是随机的。一个用户被划分到Control Group还是Experiment Group是完全随机的,与他的基本属性和其对APP的使用习惯无关。只有这样,才能保证两组流量遵守"同分布"原则。
  • 划分流量又是确定的。用户在第一次访问时被划分到哪个组,今后的访问也一定会划分到相同组。唯有这样,才能保证用户体验连续一致,消除实验中的不稳定因素。

以session为粒度划分,同一个session必须要同一组,或者以user为粒度划分。

"分层重叠"划分流量

层按照数据流来水平划分,流量按照竖直方向进行切分

  • 同层实验互斥
  • 不同层实验正交(重新打散)

上下层实验的流量完全正交,用户流量在前面几层实验中经历的不同对待,并不会在后续实验中引入偏差(bias)

就算重新打散还是会有重叠的部分,小部分用户同时被不同层的实验策略覆盖,最后统计时不能确定是哪一层的策略起了作用,量化作用的多少

重视A/A实验

所谓A/A实验,就是在Control Group和Experiment Group采用完全相同的配置。一个值得推荐的习惯就是在正式的A/B实验之前,先进行一段时间的A/A实验,检验要用于实验的两组流量是否存在偏差(bias)。

确保分桶后,多个桶是独立同分布的

打开模型的黑盒

线下涨了,线上没效果?

特征穿越

其实是一种数据泄漏,未来的数据被提前使用。

  • 而对于根据用户行为预训练的向量,比如在精排的预测和训练阶段,都访问粗排双塔以获得的最新的用户向量、物料向量当特征,图9‑11的方法肯定会引入穿越问题,因此不推荐使用。

用户行为变化较快,有数据依赖就会导致泄漏

老汤模型

新模型与旧模型的通用模块(例如输入特征embedding层)参数进行复用初始化

链路一致性问题

离线实验中,我们都是基于用户真实反馈进行训练与评估。但是线上预测时,直接影响模型效果的,并非最终用户,而是链路下游的模型。

距离曝光越远的层例如召回,其效果越难直接显示,其效果依赖于下游模型。

让模型不仅要拟合用户的兴趣爱好,还要迎合下游模型的口味

召回训练中增加使用精排未过滤的当正样本,过滤的当负样本


面向ACG编程