总结这些东西好累……
1. 背景
在过去的一段时间里我和同事们分别在推荐系统的精排环节做了很多探索,最初大部分尝试都是基于一些底层思考,零散的想法,在不成系统的情况下进行单点优化取得了各种各样的效果,往往都是有了结果才反推整个系统层面更深层次的原理,现在就算是给过去成功的一些实验和优化找个成功的理由吧。
基于过去很长一段时间的观察,虽然语料/模型结构/特征三板斧仍还能给模型带来一些提升的空间,能在短期ab实验中取得提升,但这种朝着春短期目标进行优化,带来的马太效应我们很难观察到。对于整个推荐系统而言,三板斧其只能说是解决问题的工具或者是一种手段,而问题是什么,我们这么做是解决了什么问题,我觉得是更加值得深入和思考的。
(开始废话,机械吟唱)众所周知,推荐系统的核心目标很多时候都是短期目标(例如短期的点击、交易),单纯朝短期目标进行优化将会导致严重的”马太效应“,即热门的商品受到更多的关注,冷门商品则愈发的会被遗忘,产生了系统中的系统偏差,并且系统的迭代大都依赖于页面浏览数据,而曝光数据是实际候选中经过模型选择的一个子集,不断地依赖模型选择的数据与反馈再进行训练,将形成选择性偏差。上述系统偏差与选择性偏差不断积累,就会导致系统中的“马太效应”越来越严重,短期数据向好,长期来看系统会变得越发诡异。当完成一轮用户筛选后,系统基本已经很难“自救”了( 很遗憾,我现在做的系统就是这个样子了。),我能想到的只有强有力的外力(运营干预,加大力度用钱干预,再加上算法分发上的配合)才能扭转(只能指望大力出奇迹了)。而我们最近所做的事情都可以粗浅的理解为优化整个推荐系统的公平性,缓解马太效应来解释。
具体做的事情对照起来可以从用户和资源两个层面来划分:
用户层面:改变模型输入的训练语料,从用户层面对语料进行新的采样(选择偏差)
资源层面:
- 模型中加入各类bias特征,降低曝光位置带来的样本偏差(选择偏差)
- 模型排序中加入 dibiasing 策略,降低系统头部资源分发(系统偏差)
至于选择偏差和系统偏差是我个人概括的
选择偏差:是因为外部客观条件造成的,比如前端样式导致的数据在不同位置被曝光的位置不同,或者是日志打点筛选造成的
系统偏差:是因为内部迭代自循环造成的,比如马太效应,冷启动时候大规模使用低俗数据,只有喜欢这部分数据的用户留了下来,这批用户又再次强化低俗数据,循环往复,马太效应越来越显著。
2. 样本采样
对于语料采样,我了解到的是很多场景都有在应用,但该部分工作都非常依赖具体业务场景,都是根据业务经验而来,没有啥系统性的经验可以学习,而且因为改变了样本分布,离线评估和旧版本往往也是不可比较的,只能靠线上效果来评估,很少有人深入介绍(人类的悲欢并不相通,我只觉得他们吵闹)。直到去年的时候看到了负样本为王:评Facebook的向量化召回算法 这篇文章,同时去翻看了Embedding-based Retrieval in Facebook Search 这篇文章。文章内容我觉得对与Embedding召回来讲,无论是模型结构上还是Loss设计、离线评估,概括来讲都是很常见的做法,但对于语料选取的内容以及HNM(Hard Negative Mining)这种概念的提出以及深入挖掘绝对是文章中最突出的部分。
2.1 采样的几轮迭代
最开始的时候做语料采样,我们所要解决的问题只是说样本ctr过低,大部分无点击用户贡献了大量的负样本,这导致模型正负样本不够均衡,使得模型学习困难。我们最开始也是仅仅采用了点击用户语料做第一阶段的语料采样,模型才能够初步上线。
到第二个版本迭代的时候就需要理解我们的业务场景了:一个容器类型的推荐流,上方有各种搜索框,各种跳转超链接等(类似于美团首页下面的猜你喜欢)但pc端版面原因,到这这部数据在前端开来已经曝光给用户了,但实际上用户来的意图可能是用超链接或者是搜索框,这部分用户的“曝光”数据真的可以叫食之无味,我们尝试使用最近5min有点击用户的语料,用来寻找真的在使用整个推荐流的用户,过滤掉那些使用其他功能的用户曝光。经过这样一轮调整后我们又迎来了一个令人惊喜的提升。

第三个阶段的时候是针对第二阶段采样结果的进一步探索,分析发现大量进入训练数据的日志用户在5分钟内点击次数仅为1次,这部分用户点击率很低贡献了很大一部分负样本。这时候发现了一个问题:我们的系统是10%的用户贡献了80%的点击,而我们训练的时候可能选择了40%用户的样本训练模型,试图去更好的为这10%的用户服务,这10%的用户很大程度上决定了整个系统最终的表现,可以理解为训练数据(40%用户)的分布可能和真正价值(10%用户)的分布不一致,里面包含了大量冷用户的噪声。
基于这种思考,我们尝试采取了更加严格的语料采样逻辑:仅保留了5分钟内有连续刷屏行为的用户的负样本,同时对这些负样本进行重复采样,来保证训练样本整体的正负样本比例。这样实验之后提升效果也是显著的:大约收获了4%的提升。
2.2 从公平性以及负样本增强的角度理解样本采样
说到这里,其实还没有讲到任何和公平性有关的东西,说来惭愧,直到我做到这个阶段都没有从公平性的角度对问题进行一点点思考,纯属就是各种摸索出来的采样逻辑。如果从公平性的角度考虑问题,我们在解决的核心问题就是:我们模型的训练语料对用户是否是公平的?可能有一种声音是说:我们是在认为的加速不公平,大量用户的负样本都被我们抛弃了,每个用户组上都是不公平的。但恰恰相反:模型的服务用户是所有用户是没有错,但并不是所有用户都产生价值了,公平性是要和最终目标相匹配。不是说绝对的一碗水端平,而是要让价值最大的用户群对模型的贡献也最大化。
下面这张图反应了在不同用户组上(根据用户活跃度从低到高分为了0-7总共8个组别)的负样本占比情况v0、v1
、v2分别代表了三次迭代的情况。两次迭代都朝着让价值更高的用户贡献更多的负样本的方向优化:数据对模型的贡献和模型对结果的贡献更加匹配。

v3版本上线后对照v2版本在最活跃的7号用户组上提升最为显著,也同样符合前面的理解。我最开始对于提升的认知仅仅局限在两种粗浅业务上的理解:
- 活跃用户的负样本比冷用户的负样本噪声更小
- 活跃用户的负样本资源多样性更好,冷用户负样本item绝对量比较少
后来到样本公平性的提升的理解更多的也是基于推荐系统业务的解释。至于底层的思考就要说回到前面负样本的挖掘了。
做负样本采样的本质上就是对负样本的再次挖掘,挖掘Hard Negative增强样本。对于我们业务场景来讲,冷用户启动过程中使用的数据往往都是以高热数据为主的优质数据,其候选空间在整个个性化推荐的资源空间中只占了很少一部分,而且这些用户行为偏少,特征也十分稀疏,却因为用户数量众多而产生了大量训练样本。这类样本对于精排模型来讲可以归类为Easy Negative,已经可以被复杂的精排模型一眼看穿,活跃用户上深度个性化,很少看到这类资源,达不到训练模型的目的。同样的活跃用户的负样本,因为用户本身行为丰富,召回的资源空间总量更大,特征更加稠密,能够做到与正样本足够接近,增大了模型的学习难度。
2.3 样本采样的延伸思考与尝试
语料采样会带来新的问题:被抛弃的用户的负样本真的就不重要么?这样筛选样本会不会越做越窄?
首先样本重要么?个人认为是不重要的,重不重要看其对于结果的贡献。
会不会越做越窄?通过模型上线后的结果反观效果:不会的,所用用户组上均有提升,最冷的用户组上相对别1-6号组上还高一些。分析是冷用户组上之前的负样本的噪声太大了,大部分负样本并不能反应这些用户的真实反馈。
当然在冷用户的样本上也有提升是令人惊喜的,就算是负收益,个人认为也要从全局出发先做整体的提升,能够明确到冷用户的影响,后续再单独去优化那部分效果,聚焦核心目标。
我们能不能不用样本采样来达到同样的目的?采样容易,后面流程就再也见不到这部分数据了,可能当前并不是最优解,会限制后面优化。既然样本采样本质上样本加权的一个特例,那么是否可以通过样本加权达到同样的目的,答案当然是可以的,但是我没有尝试……毕竟调权重这东西更蛋疼也难以解释,在别的场景我同事通过加特征的方式在非采样语料上达到了采样语料的效果,并没有选择采样后的语料。但是特征又是对样本的另一种理解了,个人也倾向这种方法,在这个场景下我没有进一步尝试。
除此之外,我尝试了google相关同学提出的Focused Learing的方法Beyond Globally Optimal: Focused Learning for Improved Recommendations 也可以参见知乎专栏文章推荐系统中的公平性——谷歌Focused Learning研究 Focus意即专注,也就是在原本全局模型中被忽略的种类我们把他们的数据集独立出来,作为新的数据集Focused Group,然后对于Focused Group和非Focused Group我们在模型中给出不同的权重,本质上也是一种加权,只不是过不是直接针对样本加权,而是通过调整模型的正则项使模型更加侧重Focused的数据集,从而更好的加强这些数据的准确度。以一个常规的矩阵分解模型来做表述的话就像下面这样:

理论上可以通过不调节样本权重,而是通过调节模型正则项系数(比如上面的$L_2$正则权重)让不同类型的样本都得到更好的学习。讲到这里已经可以猜到的是我并没有真正在线上试图用这个方法代替掉采样。这个方法和样本加权一样本质上都没有离开调整超参(我们用户分了8组,意味着有8个权重要调整,原谅我懒,而且随着系统不断变化,这些超参留在系统里也可能带来问题。不过说回来我还是在语料采样的基础上尝试将这个方法离线和在线都进行了简单的实验。离线上,在不同用户组上,模型的AUC分别有0.30%到0.98%的相对提升,上线后整体也能有1.5%-2.0%的收益。
3. 偏差修正
偏差修正的话,主要是解决因为各种诸如样式(展现位置/方式)环境(屏幕分辨率大小)等造成的样本点击概率不公平问题。从公平性的角度很好理解:如果我们将资源随机打乱展现给用户,如果资源在每个位置上的点击概率都相同,那么展现概率就是公平的。(但我个人没有遇见过这样的场景)在不不公平的情况下模型会对用户的好感度会出现偏差(用户点击1号位置的资源可能只是因为资源在1号位置)这种偏差会随着推荐系统feedback loop被进一步放大。
这里就以position bias作为代表来讲下我们的实践,这个问题已经很多地方都有讨论过了,每个资源最后是否会被用户关注会受到展现位置的影响,比如展现靠前的资源就是会比顺序考后的资源更容易被注意到,从而更容易获得点击;资源在前端展示前后是否存在广告等都会对ctr预估造成偏差,导致预估不准确。最早的时候就有COEC这种方案以标准化的方式去消除偏差,但COEC的假设太强了,我们也开始试图通过模型的方式去解决。
先总结下常见的几种思路吧:
- 随机呈现结果,获取数据计算position bias ,作为特征参与训练。
- 选择一种模型建模用户行为,其实就是上一种痛方法的模型版本,训练时候将该模型的输出参与主模型训练。
- 直接将position信息作为特征,参与模型训练
- 主模型中单独用一个子网络学习position bias
在实际的生产环境中一般训练的时候会保留相关特征无论是外部统计或模型输出或者直接作为特征输入主模型,但线上预估时我们无法获取(大多情况下我们很难把资源在每个位置的pctr都计算一遍,成本太高,当然也有这么做的场景),一般线上都会使用默认值,所有参与排序的资源保持一致即可。
我们尝试的方向主要集中在直接作为特征输入和自网络两个方向上,直接作为特征输入的情况:Google最早在Deep Neural Networks for YouTube Recommendations 就有实践过相关方法,只不是解决的是时间bias,这个后面单独讲,Airbnb Improving Deep Learning For Airbnb Search 中介绍的也是这个思路。子网络的相关的尝试有Google Recommending What Video to Watch Next: A Multitask Ranking System 使用的shallow tower的方法,以及华为在 PAL: a position-bias aware learning framework for CTR prediction in live recommender systems 介绍的PAL的方法。这几种方法我都有一些简单的尝试,可以简单总结下。
3.1 直接作为特征输出模型
这个部分其实没啥好讲的,就是训练的时候带上这个特征,线上预测的时候带着这个特征这么简单。如果线性模型的话就可以简单的解释,但如果是NN模型的话,解释起来就会有一些问题:NN网络部分做了隐式交叉,如果在训练的时候(100%的样本)包含这个特征,而在预测的时候全至为0或者其余的固定值,会直接影响到下一层网络输入数据的分布,而更高维度的特征表达也会受到影响,所以Airbnb这边对这个特征加了个dropout层来在预测的时候模拟线上遇到的情况,让模型能够捕捉到这种情况,也算是一种这种的方案吧。
剩下文章里的亮点就是关于position bias特征的离线评估了。在我们的环境中加入了这个特征后,训练集上和测试集上的auc应该都是提升的,但这种提升是虚假的,因为我们在预测的时候是没有bias特征的,我们需要将测试集中的bias feature移除。这时候因为训练和测试样本特征分布不一致,所以会出现测试集上auc明显下降的情况,对比不包含bias feature的模型,在相同测试集(去掉bias feature)下,auc甚至更低。
按照常规经验, auc降低的模型上线往往不会更好,但这个模型是个例外。在这个情况下,离线auc评估是不那么准确的,但同样带来了新问题,不同的bias feature 也好模型也罢,怎么离线对比?
Airbnb在他们调整dropout的参数的时候针对同一份测试数据,在保留position特征的情况下计算了$NDCG_{rel+pbias}$ 然后在通过计算positon全部设置为0的情况的$NDCG_{rel}$ 。通过比较NDCG的差异来评估position信息对排序结果的贡献,然后找到一个合适的dropout rate。我是觉得这只是强行给调参做个离线的评估(我骗我自己),降低线上实验成本。文章中为啥没有针对这份测试数据的在不包含bias feature的模型$NDCG_{base}$信息呢,盲猜一个$NDCG_{rel} < NDCG_{base}$ 说了还得出来解释这个问题…… 在我们的实验场景下评估是用的auc,实验模型在position为0的情况下auc比base低。
至于这个离线评估思路,仅供参考吧。在我们的场景下,这个方法上线后大约有1.5%的提升,也算是有收益。

3.2 子网络相关尝试
这部分理解起来也很简单,基本都是模型结构相关的了,可以直接放MMoE的模型结构上来了。

我们需要重点关注的是红框部分,是处理bias特征的核心,至于红框右侧则可以是任何深度模型。
- 输入就是 ‘Features for selection bias’
- shallow tower 可以是一个一层的线性变换,整体模型架构就和Wide&Deep很接近了;也可以是一个浅层网络,看需求
- shallow tower 的logit是直接拼接到主模型的
- 针对position特征加入10%的dropout,防止模型过度依赖position特征
- 特征里将device type和position feature交叉是因为在不同设备之间他们的position有较大差别。
不得不说Google的文章将的还是很细致,我们训练的过程中确实遇到了模型过度依赖position特征的问题,导致模型训练出现问题。甚至连特征的设计细节都讲了(为啥要和device type组合,我一开始想着也是这个特征我们一般都放到主模型里,训练和预测的时候都能获取,不会出现偏差)。这个版本的模型我们还在线上同步进行验证,等有结果的时候我再来同步。
剩下就是华为提出的PAL的思路了。

他们的思路主要是基于一些非常强的假设:
- 对于一个资源$x$是是否真正被用户$seen$关注到只和位置$pos$相关
- 用户是否点击资源$x$除了和资源本身相关外只和该资源是否被用户关注到有关
于是资源$x$在位置$pos$被点击即$y=1$的概率可以描述为
$p(y=1|x,pos)=p(seen|pos)p(y=1|x,seen)$.
而$p(seen|pos)$就是shallow tower想要求解的问题, 上面是模型做一个线性的融合,相当于pCTR的参数,这里直接就通过multiply的方式和pCTR进行融合,相当于直接对pCTR进行加权。模型的loss function变成了:
$L(\theta_{ps},\theta_{pCTR}=\frac{1}{N}\sum_{i=1}^{N}l(y_i,bCTR_i))=\frac{1}{N}\sum_{i=1}^{N}l(y_i,ProbSeen_i \times pCTR_i)$
至于线上排序部分,只使用$pCTR$进行排序,而不是$bCTR$
这个很好理解,也没有太多可讲的了,剩下他们实验里有个有趣的经验:他们把所有测试数据item的postion都分别固定为1-10(他们position应该就这10个值)进行离线测试: auc和logloss在所有样本的position指定为1的情况下是最差的,然后线上测试的时候反而是pos1的位置提升幅度最大,pos9离线auc最好,线上也比pos1差很多,提升幅度也几乎是最低的。一开始觉的还没问题,等后面越想越不对劲。如果一个测试集按照$pCTR$计算出来的auc为$AUC_1$ 将所有$pCTR$都乘以一个固定的$ProbSeen_1$重新计算auc得到的$AUC_2$应该和$AUC_1$是完全相同的吧?可能是文章表达有误 test data
指代的是dataset
,包含了训练和测试数据?
文章里讲他们这么用了之后提升至多有35%的提升,可能也主要是因为他们的应用场景好像是手机app商城那种轮播页面的推荐,一次只展现一条,但拉取推荐结果的时候可能拉取10条左右。这种场景确实受位置影响很大。另外可能就是他们baseline也很低吧(笑)。
## 3.3 一些有的没的
小流量全随机数据对位置偏差进行统计上的估计。
是个思路,直接通过统计的方式得倒真实的ctr信息:但有这么几个问题
1)小流量是多小?全随机的话代价还是很高的 。而且position bias也会受各种环境因素影响,不是一直不变的
2) 如果在排序结果按照分数讲数据按顺序呈现的话,排序本身会对纯随机的positon bias造成进一步的影响。
这个方案个人感觉只适合分析,至于解决,建议还是交给模型。
时间作为bias特征引入模型
最早Google介绍的时候,只是一个简单的特征,一带而过那种,但后来用起来的时候效果却异常惊艳。特征很简单训练最后一个时间减去日志产生的时间,在线上排序的时候统一设置为0,这不就上面处理posibion bias的思路嘛!如果没有时间bias,则认为资源在上线的第一个起到结束资源的点击率是固定的,然而实际上,随着资源在线上的汰换,ctr一直在波动。如果简单的资源的创建时间加入特征里,系统则会更倾向于旧资源:旧资源能够留在系统里说明数据表现足够好,大量新资源需要冷启动,能留下的只是一少部分,平均下来是不如旧资源的,这样从模型的角度出发,是会减少新资源的分发的。而用日志产生的时间和当前时间的差值则相当于模拟出了资源在时间维度上分布情况,移除这个bias后,对新旧资源都更加公平。之前尝试这个特征的时候,虽然排序的离线评估和在线效果上都无太大变化,但在新资源的分发能力上,确能够比base版本提升10%+,在不降低指标的情况下加速了数据的冷启动。