Skip to content

层和参数

层是一个逻辑结构,表示您将针对其进行实验的用户群。通常,每个层对应于您系统中的一个实际模块。例如,您可能为网页的用户界面建立一个层,并为支持您的页面的后端服务器建立另一个层。

在每个模块中,许多功能可能需要实验。考虑您登录页面上的一个按钮:您可能希望对三个参数进行实验 - 按钮的大小、文本颜色和背景颜色。需要注意的是,同一模块内的参数通常是相互依赖的。例如,蓝色文本颜色可能与蓝色背景颜色不兼容。

这种相互依赖性为参数与层之间的关系提供了另一个视角。我们可以将所有参数划分为 N 个子集,每个子集对应一个层。来自不同子集的参数彼此完全独立,而来自同一子集的参数通常是相关的。因此,它们应该属于同一层。 Screenshot 2024-04-02 at 12.30.08.png

在一个层内并行运行实验

每个层允许为每个实验分配互斥的流量,从而实现多个实验的同时执行。假设已创建一个名为 layer_on_landing_page 的层,其中包含三个参数:button_sizebutton_text_colorbutton_background_color。这些参数的当前值分别为 12whiteblue,我们可以将其设置为默认值。目标是确定优化按钮的 CTR(点击率)的组合。为实现这一目标,同时进行三个实验。第一个实验测试按钮大小,第二个测试文本和背景颜色的组合,第三个测试所有三个参数的组合。每个实验使用总流量的 10%,在层内留下 70% 的未分配流量。

实验屏幕截图

experiment_for_button_size 实验屏幕截图

experiment_for_button_color 实验屏幕截图

experiment_for_button_combination 实验屏幕截图

对于 experiment_for_button_size,没有为参数 button_text_colorbutton_background_color 设置显式值。在运行时,它们将默认为层的默认值,稍后将进行解释。

利用参数检索实验分配

使用层参数运行实验可以显著提高工程效率和迭代速度。这在部署新代码可能需要几天或几周的时间时尤为关键,例如移动应用程序需要进行构建发布周期的情况。如果不利用参数进行实验,您的代码可能如下所示:

    buttonSize := DEFAULT_SIZE
    buttonTextColor := DEFAULT_TEXT_COLOR
    buttonBackgroundColor := DEFAULT_BACKGROUND_COLOR
    experiment, err := abcUserContext.GetExperiment(context.TODO(), projectID, "layer_on_landing_page")

    if experiment.ExperimentName == "experiment_for_button_size" && experiment.GroupName == "Control" {
        buttonSize = 12
    } else if experiment.ExperimentName == "experiment_for_button_size" && experiment.GroupName == "Treatment_A" {
        buttonSize = 16
    } else if experiment.ExperimentName == "experiment_for_button_color" && experiment.GroupName == "Control" {
        buttonTextColor = "white"
        buttonBackgroundColor = "blue"
    } else if experiment.ExperimentName == "experiment_for_button_color" && experiment.GroupName == "Treatment_A" {
        buttonTextColor = "yellow"
        buttonBackgroundColor = "green"
    } else if experiment.ExperimentName == "experiment_for_button_combination" && experiment.GroupName == "Control" {
        buttonSize = 12
        buttonTextColor = "white"
        buttonBackgroundColor = "blue"
    } else if experiment.ExperimentName == "experiment_for_button_combination" && experiment.GroupName == "Treatment_A" {
        buttonSize = 16
        buttonTextColor = "yellow"
        buttonBackgroundColor = "green"
    }

这段代码冗长、容易出错,并且每次进行新实验时都需要更新和部署新代码。然而,通过利用层键和强类型 API 来检索实验分配,过程变得更加简单:

experiment, err := abcUserContext.GetExperiment(context.TODO(), projectID, "layer_on_landing_page")
buttonSize := experiment.GetInt64WithDefault("button_size", DEFAULT_SIZE)
buttonTextColor := experiment.GetStringWithDefault("button_text_color", DEFAULT_TEXT_COLOR)
buttonBackgroundColor := experiment.GetStringWithDefault("button_background_color", DEFAULT_BACKGROUND_COLOR)

现在的代码更简洁、更清晰。添加新实验只需通过 UI 在同一层 layer_on_landing_page 下创建新实验并配置相应参数。这允许在不修改代码的情况下执行新实验。如果参数没有在实验下明确设置,API 将默认为层的默认值。API 默认值(如 DEFAULT_TEXT_COLOR)是在参数被遗漏添加到层时的后备,其优先级低于层的默认值。当用户未分配到任何实验,如 layer_on_landing_page 下剩余的 70% 流量时,API 将返回三个参数的层默认值。

基于参数的实验启动

在进行了几天的实验后,假设我们观察到 experiment_for_button_size 的处理组的点击率明显高于对照组。这表明对于参数 button_size16 的值优于 12。如果我们希望为剩余用户启动这个获胜值,我们只需执行两个步骤:

  1. 归档实验 experiment_for_button_size,因为我们已经得出结论。这使得层内 80% 的流量未分配。

  2. 在层页面上,将参数 button_size 的默认值从 12 修改为 16。这个修改将影响 80% 未分配流量的 button_size 参数值,以及 experiment_for_button_color 下的流量,因为 experiment_for_button_color 没有为 button_size 明确设置值。对于 experiment_for_button_combination 下的流量,button_size 的值应继续遵循该实验内的明确设置(优先级高于层默认值),因为我们不希望在进行实验时修改用户行为。

下图说明了完整的流程:Screenshot 2024-04-02 at 21.38.52.png