A/B服务集成

更新时间:
复制为 MD 格式

在推荐场景下,用户经常需要调整召回策略、排序模型以及模型参数以测试新思路。为此,PAI-Rec开发了一款轻量级A/B测试服务,旨在最小化对现有系统的干扰,并支持快速实验。目前,该服务已提供服务器端的实验功能。

A/B服务介绍

A/B服务主要包括以下组件:

  • AB Web控制台:作为A/B服务后台管理系统,用于实验配置。数据会持久化到MySQL里。

  • AB Server:提供HTTP API服务,并部署在PAI-EAS中。该服务从MySQL中读取数据,要求EAS能够直接访问MySQL数据库,确保网络连通性。详情请参见EAS访问公网或内网资源

  • AB SDK:需集成到服务端程序中,支持实验配置与流量分配策略。通过SDK可实现请求分流及实验匹配,并依据返回结果采取进一步行动。当前版本支持Go、PythonJava语言。

image

集成A/B服务实验功能

操作步骤

选择环境

引擎服务的环境值可设为dailyprepubproduct,设置方法有两种:

  • config.json中配置RunMode

  • 设置环境变量PAIREC_ENVIRONMENT,其优先级高于config.json中的设置。

预发的引擎服务会加载预发环境下的实验信息,注意环境变量要对齐。

配置实验参数

PAI-Rec预先定义了一些实验参数,设置这些参数时必须严格遵守命名规则,否则即使匹配到实验也无法找到相应参数。

召回参数

引用已有召回:类目+".RecallNames",其中类目默认为 default

{
    "default.RecallNames": [
        "hot_group_recall",
        "hot_global_recall"
    ]
}
重要

注意:此处所引用的召回名称,必须在配置单中(RecallConfs下)已经定义好

过滤参数
{
    "filterNames": [
        "UniqueFilter",
        "UserExposureFilter"
    ]
}
重要

注意:此处所引用的过滤名称,除了UniqueFilter,其余的必须在配置单中(FilterConfs下)已经定义好。多个过滤模块按照顺序执行。

排序参数&特征参数

不同的排序模型,通常是由不同的特征集合训练出来的,所以这两个参数经常配套使用。

{
    "rankconf": {
        "RankAlgoList": [
            "pai_homepage_fm"
        ],
        "RankScore": "${pai_homepage_fm}",
        "Processor": "EasyRec"
    },
    "features.scene.name": "xxx",
    "user_features.scene.name": "xxx"
}
说明

当模型服务是EasyRec时,rankconf中 Processor 也须填写 EasyRec 的值。

如果只想在实验中调整 RankScore 参数,也可以简单地写为如下配置:

{
    "rankscore": "xxx"
}

重要

注意:RankAlgoList中的值,需要在引擎配置单中(AlgoConfs)中已经定义好,这里才能引用。features.scene.name 和 user_features.scene.name 两个参数选择其中一个即可,主要看特征配置是定义在 FeatureConfs 中,还是 UserFeatureConfs 中。

重排参数

重排参数也支持引用配置单已有的重排配置和覆盖已有重排配置参数两种方式。

  • 引用已有的重排配置:类目+".SortNames",其中类目默认为 default。

    {
        "default.SortNames": [
            "ItemRankScore",
            "BoostScoreSort"
        ]
    }
重要

注意:此处所引用的重排名称,除了ItemRankScore,其余的必须在配置单中(SortConfs下)已经定义好。多个重排模块按照顺序执行。

  • 覆盖已有重排配置:"sort.${重排名称}"。例如在引擎配置中已经定义了一个提权重排(BoostScoreSort),我们想通过实验调整此提权重排的某些参数,可以做如下操作:下面配置为引擎配置单中的重排定义。

    {
        "SortConfs": [
            {
                "Name": "BoostScoreSort",
                "SortType": "BoostScoreSort",
                "Debug": false,
                "BoostScoreConditions": [
                    {
                        "Conditions": [
                            {
                                "Name": "sex",
                                "Domain": "item",
                                "Type": "string",
                                "Value": "gender",
                                "Operator": "equal"
                            }
                        ],
                        "Expression": "score * 2"
                    }
                ]
            }
        ]
    }

    如果我们想要在实验中修改此提权重排的配置,例如,调整 Conditions 和 Expression,那么我们可以在实验配置中,调整如下:

    {
        "sort.${SortName}": {
            "BoostScoreConditions": [
                {
                    "Conditions": [
                        {
                            "Name": "age",
                            "Domain": "item",
                            "Type": "int",
                            "Value": "18",
                            "Operator": "equal"
                        }
                    ],
                    "Expression": "score * 10"
                }
            ]
        }
    }
    说明

    注意:要把配置中的 ${SortName} 替换为 BoostScoreSort,此时流经此实验的请求,提权重排的策略会调整为实验中的内容。

粗排参数
{
    "generalRankConf": {
        "FeatureLoadConfs": [
            {
                "FeatureDaoConf": {}
            }
        ],
        "RankConf": {},
        "ActionConfs": []
    }
}
重要

实验中的粗排参数和引擎配置单中粗排配置,基本相同,详情可参考粗排配置

流量调控参数

流量调控属于重排(Sort)的一种类型,如果是对流量调控整体做实验,可以使用重排参数,如:

{
    "default.SortNames": [
        "TrafficControlSort" // 注意,此参数需要在引擎配置单中SortConfs下已经定义好
    ]
}

但是流量调控会出现多任务,每个任务又有多个目标的情况,只有一个总开关,使用起来不太容易查看每个调控任务的效果,我们提供了可以针对每个调控任务、每个调控目标的实验参数,如下:

{
    "pid_task_params":{
        "${task_id}" : {
            "turn_off":false,
            "kp": 5,
            "ki": 1,
            "kd": 1
        }
    },
    "pid_target_params":{
        "${target_id}" :{
            "turn_off":false,
            "kp": 5,
            "ki": 1,
            "kd": 1
        }
    }
}
说明

整个配置是一个 json 结构,pid_task_params 和 pid_target_params 可以同时存在,也可以只有一个。

pid_task_params 下可以管理每个调控任务是否上线以及pid参数。

pid_target_params 下可以管理每个调控目标是否上线以及pid参数。

其中 trun_off 为 true 时,代表会关闭调控任务或者调控目标,为 false 时,代表会启动此调控任务或者调控目标。

当实验参数有冲突时,pid_target_params 中的参数有最高优先级,其次是pid_task_params,最后是引擎配置单中的参数。

注意把${task_id}${target_id}替换为具体的值。

匹配实验

每次推荐请求均需匹配实验,并构建上下文。示例代码如下:

func (c *HomeFeedController) makeRecommendContext() {
	c.context = context.NewRecommendContext()
	c.context.Size = c.param.Limit
	c.context.Param = &c.param
	c.context.RecommendId = c.RequestId
	c.context.Config = recconf.Config
	c.context.Debug = c.param.Debug
	abcontext := model.ExperimentContext{
		Uid:         c.param.DistinctId,
		RequestId:   c.RequestId,
		FilterParams: map[string]interface{}{},
	}

	if abtest.GetExperimentClient() != nil {
		c.context.ExperimentResult = abtest.GetExperimentClient().MatchExperiment(c.param.SceneId, &abcontext)
		log.Info(c.context.ExperimentResult.Info())
	}
}
调整实验参数

RecommendContext对象可通过context.ExperimentResult获取特定的实验参数。使用GetLayerParams获取某一层上的实验参数,支持Get、GetInt、GetFloat、GetInt64方法。第一个参数是参数名,第二个参数是默认值,在找不到指定参数时返回默认值。

count := r.recallCount
if context.ExperimentResult != nil {
	count = context.ExperimentResult.GetLayerParams("").GetInt("recall.base.count", count)
}
fmt.Println("test count", count)
  1. 对召回做实验

    对于特定的召回做实验时,可通过不同参数动态调整recall配置。需要遵循以下规则:

    • 参数名称格式为:"recall."+已有的recall name

    • 参数配置应采用JSON映射形式。

    • 相关召回实例必须实现CloneWithConfig方法,用于基于给定参数生成新的召回实例。例如:

      type MyRecall struct {
      	version string
      }
      
      func NewMyRecall() *MyRecall {
      	r := MyRecall{version: "v1"}
      
      	return &r
      }
      func (m *MyRecall) CloneWithConfig(params map[string]interface{}) *MyRecall {
      
      	r := MyRecall{}
      	if _, ok := params["version"]; ok {
      		r.version = params["version"].(string)
      	}
      	return &r
      }

集成A/B服务参数功能

使用方式如下:

// 获取到具体的场景名称
scene := context.GetParameter("scene").(string)

// 根据场景获取参数列表,然后用具体的 Get* function 获取具体的参数值
count := abtest.GetParams(scene).GetInt("count", 100)
fmt.Println("recall count:", count)