当完成数据表注册,并且数据同步到召回引擎之后,可以定义召回服务。
X2I 召回链路定义
数据表注册
我们以U2I的召回举例说明,假设通过PAI-Rec产生了u2i的MaxCompute表,表名称为 module_test_tf_etrec_u2i_test1_u2i2i_score_v1。MaxCompute里的建表信息如下:
pairec_test_mc.module_test_tf_etrec_u2i_test1_u2i2i_score_v1(
user_id STRING COMMENT '用户ID',
item_ids STRING COMMENT '根据u2i2i计算得到topN的item')
PARTITIONED BY (ds STRING COMMENT '分区')
ROW FORMAT SERDE 'com.aliyun.apsara.serde.AliOrcSerDe' TBLPROPERTIES ('columnar.nested.type'='true', 'comment'='etrec_u2i_test1 u2i2i召回表') LIFECYCLE 7;
注册参考如下:

点击数据同步:

填入有数据的表分区,并且确认数据同步:

点击同步历史,可以查看同步详情。

同步成功后,可以看到生效的数据版本。

召回定义
点击新建服务,输入服务名称、描述。

然后点击编辑,进行具体的设置:


我们只先新建一路u2i_recall的召回

定义完成就可以做测试了。 如果测试完成,可以把此版本发布上线。

很多情况下,召回链路不会那么简单。很多时候需要获取物品属性以及状态过滤。 分两种情况进行说明。
发布版本后,在线版本只能查看,不能编辑。我们首先新建版本,可以把当前的在线版本进行克隆,然后在新版本上进行编辑。

召回后链路设置(归并配置)
如果多路召回都走同样的逻辑,获取物品表字段,并且进行过滤。 下面的例子中,会把 tag、category 属性在召回里返回。在过滤表达式里,如果引用上下文参数(user侧特征),需要加前缀 query 。

算子设置
若仅需针对某一路召回进行独立设置,可通过组合算子实现相应操作:添加 Join 算子,将物品表中的属性字段关联并提取至当前结果集中。

也可以继续设置 Filter 算子对数据进行过滤。由于先前已经设置了 Join 算子,这里可直接引用上一步已获取的物品属性。


除了物品属性的过滤之外,我们经常使用伪曝光、真实曝光的组合来过滤数据,这个可以在归并配置里设置。

上文提到的 Filter 算子中的过滤表达式,以及归并配置中的过滤表达式,均基于 go expr 实现。
随机召回链路定义
数据表注册
随机召回一般会实时更新候选池,注册表如下。在表中,还注册了BITMAP字段。 如果对随机召回物品没有过滤需求,那无需要设置 BITMAP 字段。 如果对某些字段有强过滤需求,并且字段的唯一值数量不多(万以下),可以设置成 BITMAP 字段。

建表之后,只需要写入数据即可。
召回定义
新建召回服务后,定义召回子链路。这里物品筛选条件可根据索引字段进行灵活设置,例如"tag in [1,2,3] && category in query.categories"表示满足 tag 字段值为 1、2 或 3,且 category 字段值与查询上下文中的 query.categories 参数匹配的物品,其中categories作为上下文传递的参数需要添加query前缀以确保正确引用。

如果物品条件有 or(||) 的关系,可以输入表达式进行定义。 比如:
这里的物品条件限制比较多,只支持 &&, || , ==, !=, >, >=, <, <=, in 。 如果物品条件过滤之后, 还需要更灵活的或者更复杂的语义过滤, 可以使用上文提到的 Filter 算子或者归并配置里的过滤表达式。
U2I2I 召回链路定义
上文提到的都是单表进行召回。 召回服务里,可以提供更灵活的召回方式。 有时候,会涉及2个表或者多个表的召回。 比如u2i 召回之后,再从 i2i 表进行召回。
数据表注册
u2i 表注册如下:

i2i 表注册如下:

召回定义
首先对 u2i 的数据进行召回。 召回表设置是 u2i_table 。然后通过算子与 i2i 召回关联起来。

u2i 召回之后,item_id 是当作triggerId 去召回 i2i 的。 如果想限制trigger,对 trigger 进行调整的话,可以设置 trigger 算子。
trigger 算子可以对triggerId 进行排序,然后进行数量的截取。

然后通过 Join 算子设置 i2i 的召回。无需选择similar_item_id 字段,此处返回的item_id即为 i2i 表中对应的物品字段。

向量召回链路定义
数据表注册
MaxCompute里的建表信息如下:
CREATE TABLE IF NOT EXISTS recall_test.test_item_emb (
item_id STRING,
item_emb ARRAY<FLOAT>
)
PARTITIONED BY (ds STRING);CREATE TABLE IF NOT EXISTS recall_test.test_user_emb (
user_id STRING,
user_emb ARRAY<FLOAT>
)
PARTITIONED BY (ds STRING);注册表:
用户向量表:

物品向量表:

勾选正确的主键字段和对应的向量字段。
召回定义
向量召回:


创建对应的服务,创建对应的召回,选择向量召回,配置对应的用户向量表和对应的物品向量表。
{
"Name": "recall_engine_vector_recall",
"RecallType": "RecallEngineRecall",
"RecallEngineConf": {
"RecallEngineName": "pairec-re",
"ServiceName": "vector_recall_test",
"VersionName": "V1",
"RecallEngineParams": [
{
"RecallType": "vector",
"RecallName": "vector_recall",
"Count": 10,
"TriggerType": "user",
"UserTriggers": [
{
"TriggerKey": "uid"
}
]
}
],
"FilterNames": []
}
}基于当前用户的 ID,从向量召回服务中检索与其兴趣最相似的 10 个物品,不做额外过滤,直接返回结果。

测试结果如图所示。
实时u2i召回链路定义
数据表注册
这个也是需要注册一个i2i的表

还需要注册一个FeatureStore的特征视图



召回定义


创建对应的服务和召回配置。
{
"Name": "recall_engine_u2i_realtime_recall",
"RecallType": "RecallEngineRecall",
"RecallEngineConf": {
"RecallEngineName": "pairec-re",
"ServiceName": "u2i_realtime_recall_test",
"VersionName": "V1",
"RecallEngineParams": [
{
"RecallType": "x2i",
"RecallName": "u2i_realtime_recall",
"Count": 100,
"TriggerType": "u2i_realtime",
"UserTriggerDaoConf": {
"AdapterType": "featurestore",
"FeatureStoreName": "fs_info",
"FeatureStoreViewName": "seq_fea",
"TriggerCount": 100,
"EventWeight": "click:1;expr:1",
"WeightExpression": "exp((-0.2)*((currentTime-eventTime)/3600/24))",
"WeightMode": "sum",
"NoUsePlayTimeField": false,
"ItemIdFieldName": "item_id",
"EventFieldName": "event",
"PlayTimeFieldName": "playtime",
"TimestampFieldName": "event_time"
}
}
],
"FilterNames": []
}
}基于用户最近的实时行为(点击/曝光),从特征存储中提取最多 100 条行为记录,对每个行为按事件类型赋初值权重,并结合时间衰减计算最终权重,然后将这些带权重的 item 列表进行i2i的召回处理,请求召回 100 个相关物品,不进行额外过滤。

测试结果如图所示。