A-B测试框架PlanOut简介

背景 希望了解A/B测试框架,主要的原因是在我们自管理服务里应用了很多策略。这些策略初期是根据广告运营以及自身经验获得,但在一段时间后,有必要用数据支撑策略的上线,因此决定研究是否现有的开源A/B测试框架可以满足需求。

根据Quora上关于A/B测试框架的问题,再结合我服务的语言特点(Python),我决定调研PlanOut是否可以应用于我的场景。

PlanOut简介 它是由Facebook开发的一款开源A/B测试框架,它本身是支持多语言的,但它的基础语言和样例文档都是Python提供的,Github地址在这里。应该说,Python语言支持,以及Github Star数量是我首选它的主要原因。

关于A/B测试 这是一个在UE领域非常重要的概念,通过将研究对象划分为实验组和测试组,保证除实验元素外的其它因素一致,来判断实验效果。在做A/B测试时,需要保证实验组和对照组不交叉,即实验组用户只看到实验信息,而对照组用户只看到对照信息。

PlanOut安装应用 安装:

pip install planout

模块引入:

from planout.experiment import SimpleExperiment
from planout.ops.random import *

PlanOut基础概念

1.定义实验 所有实验都是通过继承SimpleExperiment类来实现的。SimpleExperiment类继承自Experiment,但PlanOut建议使用者继承SimpleExperiment而不是Experiment,因为前者包含了日志相关的工作。

示例:

from planout.experiment import SimpleExperiment
from planout.ops.random import *

class VotingExperiment(SimpleExperiment):
  def assign(self, params, userid):
    params.button_color = UniformChoice(choices=["#ff0000", "#00ff00"],
      unit=userid)
    params.button_text = UniformChoice(choices=["I'm voting", "I'm a voter"],
      unit=userid)

解释: 前两行是引入相关文件,SimpleExperiment如上所述,planout.ops.random包含了一些常用的随机数产生函数(比如Uniform,RandomInteger) 类定义里,每个Experiment类都需要实现自己的assign函数,它是用户构建实验的位置。 param函数用于定义实验的属性,在上面的例子里,我们定义了按钮颜色和按钮文字两个实验属性,它们的取值都是均匀采样(UniformChoice)自choices中定义的字段。

这里可以注意到,PlanOut支持交叉实验,即对一个输入(user_id)可以同时实验多个属性(button.color, button.text),另外,PlanOut也支持多个输入,每个输入或每几个输入决定一个输出属性,在后面会具体提到。通过这种方式,PlanOut能够提供一种灵活的多对多实验框架。

2.添加实验

在需要调用实验的代码里:

exp = VotingExperiment(userid=14)
call_to_action_color = exp.get('button_color')
call_to_action = exp.get('button_text')

根据指定user返回展示给用户button_color和button_text。采样依据user_id,对于相同user_id,Experiment类会返回固定的button.color和button.text,而对不同user_id,Experiment类保证对应的随机数不一致。

3.日志 对于实验而言,提供准确清晰的日志记录非常重要。PlanOut在上述代码get调用时会输出一条日志,默认日志文件名为定义Experiemnt的类名,日志位置为当前路径,日志以json形式展示:

{
  "inputs": {
    "userid": 14
  },
  "name": "VotingExperiment",
  "params": {
    "button_color": "#ff0000",
    "button_text": "I'm voting"
  },
  "time": 1396487778,
  "salt": "VotingExperiment",
  "event": "exposure"
}

4.在Web方面的应用 随机实验很多时候会直接用在网站UX上,官网提供了一个基于Flask的样例,代码可以直接看Github上的demo,演示在这里

5.盐的生成方法 PlanOut的随机盐来自三方面的设置:

(1)Experiment-level,指的是在Experiment的setup函数里定义的变量,如果没有定义,那么会采用Experiment类名作为变量 (2)Parameter-level,指的是在assign函数unit里指定的变量取值,比如user_id (3)Namespace-level,指的是同一个实验但不同时间段的信息

6.NameSpace PlanOut提供NameSpace,主要是解决同一个实验,但在不同时间段执行的问题。可能由于某些设置问题,需要重新执行实验,但因为各类设置和之前一致,因此无法区分数据来自前一个还是更新后的实验。引入NameSpace进行区分

7.Operators PlanOut提供了以下默认的操作符:

(1)UniformChoice

params.x = UniformChoice(choices=['a', 'b'], unit=userid)

根据userid均匀设置choices中的属性到param.x

(2)WeightedChoice

params.x = WeightedChoice(choices=['a', 'b', 'c'], weights=[0.8, 0.1, 0.1],unit=userid)

根据userid加权均匀设置choices中的属性到param.x

(3)BernoulliTrial

params.y = BernoulliTrial(p=0.2, unit=userid)

params.y有20%可能性为1,80%可能性为0

(4)RandomFloat/RandomInteger

params.x = RandomFloat(min=0.0, max=10.0), unit=userid)
params.x = RandomFloat(min=0, max=10), unit=userid)

在min, max之间随机返回值

结论 通过基础调研,我认为PlanOut有一些很好的特性:

1.叉乘维度的实验
2.日志自动添加
3.多种随机方案

它很适合web端的随机分组和实验。 在我们的问题里,因为我们的策略应用是在campaign或者account维度,我们并没有像userid这样大数量的数据,我们并不太关心campaign id是否足够随机,而更关心究竟是哪些campaign id参加了实验组,换句话说,我们希望确定性的指定参加实验的campaign/account,或者只需要简单的ID划分(比如结尾为1的campaign为实验组),因此最终我们没有在项目中应用PlanOut。但通过调研,我们仍然认为它是一个很好的A/B测试框架,在将来需要的场景下会考虑应用。