【王喆-推荐系统】评估篇-(task4)服务器内部实现A/B测试

学习总结

  • 上次学习的一坨推荐系统的离线评估指标和方法,离线评估不能还原线上的所有变量,如视频网站需要提高的【用户观看时长】指标等。几乎所有的互联网公司,线上 A/B 测试都是验证新模型、新功能、新产品是否能够提升效果的主要测试方法。
  • 线上 A/B 测试的基本原理和评估指标,并且在 SparrowRecsys 上实现了 A/B 测试的模块:模块的基本框架就是针对不同的 userId,随机分配给不同的实验桶,每个桶对应着不同的实验设置(实验组和对照组)。

分别了解 A/B 测试的定义和优势、设计原则以及在线评估指标(如下3点)。

  • A/B 测试,又叫“分流测试”或“分桶测试”,它把被测对象随机分成 A、B 两组,通过对照测试的方法得出实验结论。在线上评估指标的制定过程中,要尽量保证这些指标与线上业务的核心指标保持一致,从而知道是否达成了公司的商业目标。
  • 相比于离线评估,A/B 测试有三个优势:实验环境就是线上的真实生产环境;可以直接得到线上的商业指标;不受离线数据“数据有偏”现象的影响。
  • 在 A/B 测试的设计过程中,要遵循google提出的层与层之间的流量“正交”,同层之间的流量“互斥”原则,这样才能既正确又高效地同时完成多组 A/B 测试

一、理解A/B测试

A/B 测试又被称为“分流测试”或“分桶测试”,它通过把被测对象随机分成 A、B 两组,分别对它们进行对照测试的方法得出实验结论。具体到推荐模型测试的场景下,它的流程:

  • 先将用户随机分成实验组和对照组,
  • 然后给实验组的用户施以新模型,给对照组的用户施以旧模型,
  • 再经过一定时间的测试后,计算出实验组和对照组各项线上评估指标,来比较新旧模型的效果差异,
  • 最后挑选出效果更好的推荐模型。

A/B测试的三大优点:

1.1 还原线上环境

离线评估无法完全还原线上的工程环境。
离线评估往往不考虑线上环境的延迟、数据丢失、标签数据缺失等情况,或者说很难还原线上环境的这些细节(如变量,ex:视频网站需要提高的【用户观看时长】指标等)。因此,离线评估环境只能说是理想状态下的工程环境,得出的评估结果存在一定的失真现象。

1.2 模型相关的其他指标

线上系统的某些商业指标在离线评估中无法计算。
离线评估一般是针对模型本身进行评估的,无法直接获得与模型相关的其他指标,特别是商业指标。离线评估关注的往往是 ROC 曲线、PR 曲线的改进,而线上评估却可以全面了解推荐模型带来的用户点击率、留存时长、PV 访问量这些指标的变化。

这些指标才是最重要的商业指标,跟公司要达成的商业目标紧密相关,而它们都要由 A/B 测试进行更全面准确的评估。

1.3 数据有偏现象

离线评估无法完全消除数据有偏(Data Bias)现象的影响。
什么叫“数据有偏”呢?因为离线数据都是系统利用当前算法生成的数据,因此这些数据本身就不是完全客观中立的,它是用户在当前模型下的反馈。所以说,用户本身有可能已经被当前的模型“带跑偏了”,你再用这些有偏的数据来衡量你的新模型,得到的结果就可能不客观。

二、A/B 测试的“分桶”和“分层”原则

A/B 测试的原理就是把用户分桶后进行对照测试。
问题:比如到底怎样才能对用户进行一个公平公正的分桶呢?如果有多组实验在同时做 A/B 测试,怎样做才能让它们互不干扰?

样本的独立性和分桶过程的无偏性。
“独立性”:指的是同一个用户在测试的全程只能被分到同一个桶中。
“无偏性”:指的是在分桶过程中用户被分到哪个实验桶中应该是一个纯随机的过程。

2.1 分桶

举栗:把用户 ID 是奇数的用户分到对照组,把用户 ID 是偶数的用户分到实验组,这个策略只有在用户 ID 完全是随机生成的前提下才能说是无偏的,如果用户 ID 的奇偶分布不均的前提,我们就无法保证分桶过程的无偏性。所以在实践的时候,我们经常会使用一些比较复杂的 Hash 函数,让用户 ID 尽量随机地映射到不同的桶中。

2.2 分层,做对照实验

问题:要知道,在实际的 A/B 测试场景下,同一个网站或应用往往要同时进行多组不同类型的 A/B 测试。比如,前端组正在进行不同 app 界面的 A/B 测试的时候,后端组也在进行不同中间件效率的 A/B 测试,同时算法组还在进行推荐场景 1 和推荐场景 2 的 A/B 测试。这个时候问题就来了,这么多 A/B 测试同时进行,我们怎么才能让它们互相不干扰呢?

低效方法:全部并行地做这些实验,线上测试资源非常紧张,如果不合理设计,很快所有流量资源都会被A/B测试占满。

2.3 正交与互斥

Google 在一篇关于实验测试平台的论文《Overlapping Experiment Infrastructure: More, Better, Faster Experimentation》中,详细介绍了 A/B 测试分层以及层内分桶的原则。
关键句:层与层之间的流量“正交”,同层之间的流量“互斥”

(1)层间流量正交

层与层之间的流量“正交”,它指的是层与层之间的独立实验的流量是正交的,一批实验用的流量穿越每层实验时,都会再次随机打散,然后再用于下一层的实验。

如下图,假设在X层的实验中,流量已经被随机平均分为X1(蓝色)和X2(白色)两部分。当他们穿越到Y层实验时,X1和X2的流量会被随机且均匀地分配给Y层的两个桶Y1和Y2——如果Y1和Y2的X层流量分配不均匀,那么Y层的样本就是有偏的,即Y层的实验结果会被X层的实验影响,无法客观地反应Y层实验组和对照组变量的影响。
在这里插入图片描述

层之间的流量正交示例

(2)层内流量互斥

同层之间的流量“互斥”。这里的“互斥”具体有 2 层含义:

  • 如果同层之间进行多组 A/B 测试,不同测试之间的流量不可以重叠;
  • 一组 A/B 测试中实验组和对照组的流量是不重叠的。
  • 在基于用户的 A/B 测试中,“互斥”的含义还可以为:不同实验之间以及 A/B 测试的实验组和对照组之间的用户是不重叠的。特别是对推荐系统来说,用户体验的一致性是非常重要的。也就是说我们不可以让同一个用户在不同的实验组之间来回“跳跃”,这样会严重损害用户的实际体验,也会让不同组的实验结果相互影响。因此在 A/B 测试中,保证同一用户始终分配到同一个组是非常有必要的。

小结:A/B 测试的“正交”与“互斥”原则共同保证了 A/B 测试指标的客观性,而且由于分层的存在,也让功能无关的 A/B 测试可以在不同的层上执行,充分利用了流量资源。

三、线上 A/B 测试的评估指标

在这里插入图片描述

  • 测试前:在A/B测试中需要多和产品、运营团队沟通,在测试开始前一期指定大家都认可的评估指标(可以参考上表中列出的电商类推荐模型、新闻类推荐模型、视频类推荐模型的主要线上 A/B 测试评估指标。);
  • 测试后:A/B测试一般是模型上线前的最后一道测试,通过该测试后的模型一般会直接服务于用户。
    来完成公司的商业目标。因此,A/B 测试的指标应该与线上业务的核心指标保持一致。

线上 A/B 测试的指标和离线评估的指标(诸如 AUC、F1- score 等)之间的差异非常大。
原因:离线评估不具备直接计算业务核心指标的条件,因此退而求其次,选择了偏向于技术评估的模型相关指标,但公司更关心的是能够驱动业务发展的核心指标,这也是 A/B 测试评估指标的选取原则。

四、SparrowRecSys 中 A/B 测试的实现方法

既然是线上测试,那我们肯定需要在推荐服务器内部来实现这个 A/B 测试的模块。
模块的基本框架就是针对不同的 userId,随机分配给不同的实验桶,每个桶对应着不同的实验设置。可以直接在实现过的“猜你喜欢”功能(【王喆-推荐系统】复习篇-Sparrow的个性化推荐功能)上进行实验。实验组的设置是算法 NerualCF,对照组的设置是 Item2vec Embedding 算法。

4.1 A/B测试模块

(1)建立一个ABTest模块(为每个用户分给实验设置),并且给不在A/B测试的用户设置了默认模型(默认模型是不在实验范围内的用户的设置)。

(2)分配实验组:

  • 使用getGonfigByUserId函数确定用户所在的实验组,该函数的唯一输入参数是userId
  • 然后利用userIdhashCode把数值型的ID打散;
  • 再利用userId的hashCodetrafficSplitNumber参数进行余数操作,根据余数值确定userId在哪一个实验组里。

A/B 测试模块的主要实现:

public class ABTest {
    final static int trafficSplitNumber = 5;
    final static String bucketAModel = "emb";
    final static String bucketBModel = "nerualcf";
    final static String defaultModel = "emb";
    public static String getConfigByUserId(String userId){
        if (null == userId || userId.isEmpty()){
            return defaultModel;
        }
        if(userId.hashCode() % trafficSplitNumber == 0){
            System.out.println(userId + " is in bucketA.");
            return bucketAModel;
        }else if(userId.hashCode() % trafficSplitNumber == 1){
            System.out.println(userId + " is in bucketB.");
            return bucketBModel;
        }else{
            System.out.println(userId + " isn't in AB test.");
            return defaultModel;
        }
    }
}

trafficSplitNumber参数:指把我们的全部用户分成几份。
分流操作:比如把所有用户分成了 5 份,让第 1 份用户参与 A 组实验,第 2 份用户参与 B 组实验,其余用户继续使用系统的默认设置。把流量划分之后,选取一部分参与 A/B 测试。

4.2 具体的业务逻辑中

在实际要进行 A/B 测试的业务逻辑中,需要调用 A/B 测试模块来获得正确的实验设置。比如,下面选用了《猜你喜欢》功能进行 A/B 测试,就需要在相应的实现 RecForYoService 类中添加 A/B 测试的代码,具体的实现如下:
(1)调用 ABTest.getConfigByUserId 函数获取用户对应的实验设置;
(2)然后把得到的参数 model 传入后续的业务逻辑代码。

注意:这里设置了一个全局的 A/B 测试使能标识 Config.IS_ENABLE_AB_TEST,在测试这部分代码的时候,要把这个使能标识改为 true

if (Config.IS_ENABLE_AB_TEST){
    model = ABTest.getConfigByUserId(userId);
}
//a simple method, just fetch all the movie in the genre
List<Movie> movies = RecForYouProcess.getRecList(Integer.parseInt(userId), Integer.parseInt(size), model);

上面就是经典的 A/B 测试核心代码的实现。在实际的应用中,A/B 测试的实现当然要更复杂一些:

  • 不同实验的设置往往是存储在数据库中的,需要我们从数据库中拿到它。
  • 为了保证分组时的随机性,往往会创建一些复杂的 hashCode 函数,保证能够均匀地把用户分到不同的实验桶中。
  • 尽管实现会更复杂,但整个 A/B 测试的核心逻辑没有变化。

五、作业

学习了A/B 测试的分层和分桶的原则。如果我们在测试模型的时候,一个实验是在首页测试新的推荐模型,另一个实验是在内容页测试新的推荐模型,你觉得这两个实验应该放在同一层,还是可以放在不同的层呢?为什么?

【答】对于问题,应该放在同一层,因为首页推荐可能会把一些有兴趣偏好的用户导入到对应的内容页,比如首页推荐球鞋,对于想购买球鞋的就会进入到球鞋内容页,这样对于内容页推荐来说 ,用户不是随机,是有偏的。

Reference

(1)https://github.com/wzhe06/Reco-papers
(2)《深度学习推荐系统实战》,王喆

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>