一、特征生产
特征生产在推荐广告、风控、记忆学习等领域都有广泛的应用,特征平台的特征生产功能是要降低特征工程的复杂度,将特征工程中常见的功能固定下来,通过配置的方式实现特征生产。后续还会推出支持跨平台功能,比如同一套特征定义可以在 MaxCompute、Flink 等多个平台运行,尽可能减少用户的工作量,提升一致性。
1. 特征平台介绍
特征生产是特征平台的一个子功能,特征平台 FeatureStore 是阿里云 PAI 平台下的特征管理工具和共享平台,用于组织、存储和管理机器学习及训练中使用的特征数据,通过特征平台可以方便地向多人、多团队共享特征,保证离在线的一致性,提供高效的特征访问。
如上图所示是特征平台的整体链路,特征平台包含在线和离线两条链路,离线链路可以注册实体、注册特征视图、存储某个具体的特征,可以和具体的表关联起来;注册完特征视图之后,可以构建模型,训练模型;在训练之前,可以用特征生产的功能来生成更丰富的特征,训练完后可以导出模型,把模型部署到线上服务,就可以进行推理模型。线上推理时 user 侧的实时特征可以放在在线的存储引擎,目前平台支持的存储引擎主要包括 Hologres、OTS、GraphCompute,还有自建的存储引擎 FeatureDB。还可以支持 item 侧的实时特征,item 侧的特征如果是离线特征可以放到服务内存里,取特征更快,基本上在几毫秒之内就可以完成,而实时特征可以通过轮询的方式取,比如可以从 feature DB 每隔固定几秒轮询到内存里面,这样不会对在线服务造成很大的压力;整个链路是收到请求之后读取用户特征,然后打分。
特征平台除了支持离线链路,还支持实时链路,比如从消息队列 DataHub,接到 Flink 计算,做特征的统计和计算等特征工程,然后写到在线存储引擎(Hologres、OTS、GraphCompute,FeatureDB 等)里面,这是在线的链路。离线链路可以通过大数据平台 MaxCompute 来存储数据,同时可以把数据表同步到在线存储引擎里。特征平台也提供了很多的 SDK,有 Go、C + +、Java,Python 的 SDK。Java、Go 的 SDK 在 GitHub 是开源的可以直接获取到。Python SDK 可以做数据分析,比如读特征、特征提取、特征生产等,特征生产是 Feature Store Python SDK 里面支持的功能。
上图所示是特征平台的页面,目前已经在人工智能 PAI 产品页面下开放,特征平台包含几个页面:特征实体、特征视图、label 表,模型特征、任务中心。比如在推荐系统中,特征实体是 user 和 item 侧的特征,可以注册为 user 和 item。特征视图包含离线特征视图、在线特征视图、实时特征视图、序列特征视图,可以根据不同的功能来管理特征。Label 表是训练时指定的 label。模型特征可以指定构建样本表的特征,在线和离线只需要指定模型特征,就可以自动分析出你要使用的特征,进行特征的加载,不需要指定需要用多少张特征表,比如你用了 10 张特征表,只需要指定一个模型特征,就可以自动分析出特征表中你要使用的特征。任务中心包括数据同步,从离线同步到在线,可以保证特征一致性,还有样本表的导出,比如训练的时候,我们使用样本来训练,构建样本表的过程任务可以自动完成,比如涉及离线表的 join,序列表的 join,还有实时表的 join。
2. 特征生产
为什么需要特征生产?
- 捕捉时间趋势,比如在用户行为数据中,最近一段时间的行为可能对当前的状态有比较大的影响,用户最近几天的点击率可能比一个月之前的点击率更能反映当前的兴趣偏好。
- 降低噪音。原始数据中可能包含大量的噪音,我们通过统计变换,或者聚合操作可以减少噪音的影响。比如简单的点击次数可能受广告位的随机影响比较大,但是一段时间内的平均点击率则更稳定。
- 丰富特征。通过特征工程可以生成新的特征,增强模型的表达能力。比如除了阅读数特征,还可以引入阅读增长率、平均阅读时长,为模型提供更多的信息,可以让模型更好地拟合标签。
- 提高模型性能。通过引入一些统计特征,可以显著提高模型的预测性能,因为这些特征更稳定,在多天或更长的窗口上进行统计特征,更能准确地反映潜在的模式。
- 增强解释性。统计特征更加易于解释和理解,使得问题的诊断和分析更为方便,比如可通过查看一些长期和短期的统计数据推断某些行为的变化趋势和原因。
- 数据压缩。在某些情况下,统计特征可以有效地减少数据的维度,比如用过去一年的平均值代替每日的数据,从而简化模型的复杂度。
面对不同的大数据平台,通常会根据不同大数据平台的文档,去写特征生产的语句,然后执行不同的特征生产逻辑。
但是如果换一个大数据平台,又要根据另外一个大数据平台来写特征生产语句,难以复用,这就会带来一些问题:
- 特征生产的实现过程比较复杂,有可能每实现一步,就要进行一些校验,然后判断逻辑实现是否正确,还需要写一些冗长的语句,会耗费较多时间。
- 计算过程难以优化,需要对大数据平台进行比较深入的了解,阅读文档,需要使用哪些关键字、哪些功能,也是非常耗时的。
- 线上线下不一致现象频出,在推荐广告、风控或者其它一些场景中都经常出现。
我们做特征生产功能,就是为了解决上述问题,实现以下功能:
- 同一套特征生产定义。
- 根据定义生产出各种特征。
- 特征生产出来是一个执行过程,支持本地进行调试,也适配各个大数据平台运行,如果从大数据平台 a 切换到 b 大数据平台,不需要整个的定义过程,只需要改其中的某个参数,指定大数据平台就可以。比如从 MaxCompute,到 Flink,特征生产定义都是同一套,可以在多个平台运行,还包括 Spark。
- 保证在线和离线的一致性。
- 优化计算过程,节省计算资源。通过引入中间表,做中间结果的存储可以极大地节省计算时间。如果在大数据平台训练,其计算也会节省一些费用,计算时间也会更短。
- 统计特征的实时更新。
上图所示是特征生产的整体链路,开始是特征定义的部分,针对不同的大数据平台可以有相同的定义,就可以不用因为切换大数据平台而修改特征定义,便于各个平台的移植,特征定义完成之后有编译的过程,根据指定的不同的大数据平台进行分别的编译,编译完之后的执行过程就可以在对应的大数据平台上运行。
特征定义对于新手比较友好。如果熟悉 Python 的话,有 Python 的定义方式,比较简单直接;还支持 JSON 的定义。Python 定义会转成自动 JSON 的定义,通过唯一的输入,就能有一个固定的输出,不受各个平台的限制也不受语言的限制。
上图所示左边是 Python 的特征定义,比如定义 TableTtransform,指定字段表变换的名称、去重字段和排序字段等,还做 ComboTransform,把两个特征连接起来。还能对特征做一些处理,比如对 play time 特征除以 10 这些操作都可以在一个特征生产链路里面执行。最后会自动生成右边的 JSON 格式,包含输入原表,特征处理(表变换、特征变换等)操作,输出结果表;通过这样唯一的定义,就可以生成整个的执行过程,在不同的大数据平台运行。
整个编译的过程根据不同的平台来分别实现,我们尽可能针对平台的特性做对应的优化。平台 command 命令也放在包里面,用户就可以指定同一个执行的命令,在不同大数据平台运行。
上图所示是连接不同变换的编译过程,是一个 pipeline 的形式,可以进行表变换、特征变换,可以指定多个表变换,也可以指定很多的特征变换,不同的特征变换之间利用了 SQL 的公域表达式,比如左边是一个 SQL,看起来很复杂,但转变成右边的一个语句的形式,看起来会更简单直接,在每一个语句实现的时候,只需要关心本身的输入和变换过程,不需要关心前一个和后一个变化是什么。当这个变换完成之后输出出来,程序就可以根据本次变换的输出,和下一个变换的过程,再进行下一个语句的实现,整个过程以流水线的形式来执行,可以定义任意的变换,最后拿到结果。
编译过程中内部变换优化分为两个方面:
- 优化用户使用功能。支持定义多个根据不同的 group key 进行连接, 还有考虑如何让用户使用起来更方便等相关优化。
- 优化计算过程。特征的产出需要非常多的时间,通过存储一些中间表,把中间的结果存储下来,可以节省资源,提高运行效率。
下面介绍两个有代表性的优化的例子:AggregationTransform和 WindowTransform。
AggregationTransform,根据某个聚合做统计,比如 user_sum_click_count_3d,计算某个用户最近三天点击过所有物品的总点击数,如上图所示左边是一个行为宽表,有请求 ID、用户 ID、行为、物品 ID、物品历史总点击数和时间分区,做近三天点击数特征聚合统计,得到 u1 用户 50 次,u2 用户 50 次,这是 AggregationTransform 的计算内容。
AggregationTransform 实际定义的过程比较复杂,但是用户只需要通过一个简单的定义就可以实现复杂的计算,以下是一些功能的优化:
- 支持同窗口的自动归并,不同窗口自动连接,比如点击数可能会定义 1 天、3 天、7 天、15 天、30 天、45 天,甚至是 60 天、90 天等长时间窗口的统计。还能定义很多别的特征,比如点赞的统计、评论的统计,使用的过程中可能会做特征变换,在变换的过程中,对于同窗口会自动的归并,不同窗口会自动地做连接,比如用 left join,把 1 天、3 天、7 天、15 天、30 天等窗口 join 起来。
- 同一个 group key 一起计算,不同的分组关键字自动连接,与不同窗口类似,不同的 group key 不需要放在多个 SQL 中实现,只需要放在一个 pipeline 流程里面即可实现自动连接。
- 类型的自动推导。用户不需要指定输出类型,会根据输入特征的类型和聚合函数,做类型的自动推导,极大方便用户使用的门槛。
- 内置自动扩展函数,支持特征变换自动扩展,只需要根据扩展函数输入要做的特征的一些统计,就能生成成百上千个特征,如上图左侧单个特征示例,单个特征定义比较简单,指定聚合函数、判断条件和 group key、窗口,就可以进行单个特征清晰地定义,但是如果要添加多个特征,单个特征地加比较麻烦,就可以用自动扩展函数生成成百上千个特征,比如上图右侧示例指定了要做输入的一些统计,可以生成 96 个特征,这个函数用户可以根据自己的场景来进行一些修改,生成出适配自己使用场景的函数。
WindowTransform 用于计算不同实体的交叉统一特征,如上图示例 user_kv_category_click_sum_3d 是计算用户最近三天点击过的不同类目的所有物品的总点击数,是一个 KV 特征,输出不同实体的交叉统计特征 map,线上要排序打分需要取到 item 和 user 特征,通过 uid 和 item 类目进行特征查找,比如 u1 和 item 类目 1 取出来就是 30。根据上图所示的历史行为宽表去做的汇总统计,得到右边的 kv 特征表,存储到在线的存储引擎里面。线上取特征时,做一个 lookup 查找,根据 k 来查找对应的 value,然后再做模型的推理;如果是离线的话再去做模型的训练。
WindowTransform 也做了一些功能上和计算过程上的优化,还有一些计算过程的优化:
- 类型自动推导。
- 支持不同的分组关键字的自动连接。
- 统计不同窗口特征的时候计算量很大,比较浪费计算资源,我们引入了中间临时表,将每天的计算过程临时存储下来,当计算新的一天时,只需要汇总结果即可。这个过程的优化大幅缩短了计算时间,提高计算效率。比如对 n 天的样本统计,计算完⼀次后,下⼀次不需要再重新计算 n 天,只需计算第 n + 1 天,然后汇总。执行的过程还支持第一次执行自动补全中间临时表数据。
- 内置自动扩展函数,支持特征的自动变换扩展。
WindowTransform 同样也支持单个特征的定义和自动扩展。
特征生产功能支持很多变换,包括表变换、特征变换、JoinTransform、ComboTransform、CustomTransform、AggregationTransform、WindowTransform。
二、组件化建模
组件化建模是 EasyRec 算法框架的一个新功能,EasyRec 框架是一个训练框架,已经在 GitHub 开源,它支持 20 多种行业的经典模型,可以和多种数据源对接,支持大规模分布式训练评估,支持自动超参搜索、知识蒸馏等功能。框架里包含了召回模型、排序模型,多目标模型,重排模型。这个框架的优势在于它支持多个平台,只需要指定一个 config 就可以在各个平台上运行和训练模型。支持多种经典模型,如果模型不满足要求,通过组件化建模就可以根据自己的需求用组件来搭建自己所需模型。并且提供了丰富的组件,常用模型都可以通过组件化的形式像搭积木一样搭建出来,当组件不满足要求时,也支持自定义组件。
组件化灵活搭建模型,可以做到所思即所得,通过搭积木的方式来搭建想要的模型结构,组件间无缝衔接,还可以复用组件,做到一次开发就能使用多种模型,通过配置的方式即可生成新的模型。过去开发一个新的公共模块到现有模型中,需要修改模型的代码才能用上新特性,过程繁琐。组件化之后,可以快速地通过配置的方式来搭建新的模型,极大提高了实验的迭代效率,想法可以得到快速地验证。
上图示例是组件化流程示例,有 backbone 网络和一些 blocks,整体组成一个 DAG 图。组件化的目标是不需要新的模型,只需要新的组件。新的模型由已有的组件通过拼接形成,各个组件只需要专注于实现自身的功能和单一的任务即可。组件化的 EasyRec 网络,可以通过一个可配置的主干网络作为核心部件,主干网络是由多个模块组成的一个 DAG 图构成,输出到顶部的 MLP 层,然后到最终的预测层。
上图所示具体的案例是比较常用的模型 Wide&Deep 网络。主干的网络由很多 block 组成,图中左边是原来的一个示例,更改比较麻烦,中间部分是用组件化升级之后,通过几个组件来拼接来生成想要的模型,一个 block 输入可以关联一个 feature group 或者 keras layer 对象,实现了一个可复用的子模块网络。
上图所示是 DCN 模型的示例,用了一个 recurrent 组件,实现循环调用某个模块多次的效果,另外还加了一个 MLP 模块,如果想尝试其它组件的效果也可以加到这里面。
上图示例是组件化升级之前实现的 DLRM 模型,模型代码都是固定的,使用这个模型时只能修改输入层特征,和一些 DN 层的改变,其它特殊模块改变不了。
而升级用组件化搭建 DLRM 网络只需要实现一个 dot block,案例中的 dot block 第一个输入是一个 Tensor,第二个输入是一个 list,第一个输入会插入到 list 当中,形成一个更大的 list 作为 block 输入。我们还有数值 embedding 组件,效果非常好,也可以很方便地加入模型结构中,组件化具有很强的灵活性和可扩展性。
支持丰富的组件,包括 MLP、Highway、Gate 等可以实际使用体验其功能,还有一些特征交叉的组件,包括二阶交叉、二阶内积交叉、双线性等。
我们还支持一些特征重要度学习的组件,序列特征编码组件(DIN 和 BST),多目标学习的组件 MMoE。
当现有组件没有办法满足使用要求的时候,支持自定义组件。自定义组件实现初始化和 call 函数即可。因为代码是开源的,根据已实现组件可以参考开源代码是怎么实现的,可以直接模仿开源代码去实现。
特征平台的特征⽣产 Notebook 已在 PAI-DSW Gallery 发布,可以直接运行 Notebook 进行体验。EasyRec 代码是开源的,通过上图 GitHub 地址可以体验,有问题可以加群沟通交流咨询。
三、Q&A
Q1:这个特征工程的产品是在云上可以体验的吗?
A1:是可以体验的,在阿里云 PAI-DSW Gallery 已经发布,它是一个 Notebook,只需要 Notebook 就可以实现整个特征变换过程。我们还提供了示例表,模拟真实场景的一些示例,包括行为表、用户表和物品表,通过这些表可以直接体验产品功能。另外还提供了最佳实践的文档,还可直接加群,群公告里面有相关地址,使用过程当中如果有什么问题都可以在群里提问。
Q2:FeatureStore 的 SDK 和 EasyRecProcessor 的一些集成案例有开放吗?
A2:FeatureStore SDK 目前是集成在 EasyRec Processor 里面,EasyRec Processor 可以直接在阿里云 ES 上运行,我们有文档,直接搜索一下 EasyRec Processor,就可以在阿里云上搜到这篇文档,就可以直接使用了,里面内置了 Feature Store SDK,我们做了非常多的优化,包括存储优化、取特征优化,还支持了实时特征,只需要指定一些参数就可以运行。
Q3:有一些 transform,只是基线上有,线上没有,如何知道哪些是线上,哪些是用于离线的?
A3:这是离线和在线特征不一致的问题。如果用特征平台,注册的时候特征视图关联的是一张表,但是我们会同时在离线和在线引擎里面构建同样的两张表,保证离线数据平台和在线存储的 schema 是一模一样的,这样可以保证一致性。上线过程中取特征只需要指定你的模型特征,就可以从离线和在线拉取特征,我们还会有一致性检查工具可以排查出来,把在线的特征和离线的特征拿出来,然后做一个特征的对比,最后打分对比,如果打分一致,那就说明在线和离线是一致的,打分不一致的话,我们还会做特征的分析,判断在线特征和离线特征哪里不一样,可以通过差别来看哪个地方有问题。
Q4:EasyREC 有拖拽组件的形式给用户用吗?
A4:EasyRec目前还没有拖拽的组件,但是目前 config 配置其实非常方便。直接复制一下样例就可以跑起来。
Q5:特征量非常大,不论是离线训练还是在线 serving,特征的生产到使用链路挺长的,那在咱们平台能否看到哪些特征在用,哪些特征不在用等一些血缘关系,在特征治理场景还是比较有强诉求去看到这种信息的,平台上是怎么去实现这部分功能?
A5:这个是一个非常重要的功能,EasyRec 里面有特征重要度的分析,通过组件进行训练之后,可以分析出来哪些特征是重要的,哪些特征是不重要的,如果想做特征的裁剪,可以通过这一功能把那些重要的特征保留出来,把不重要的去掉,比如你之前有 1000 多个特征,然后做了特征重要度的分析,发现只需要保留前 200 个就可以,那你保留了前 200 个特征之后,在线上的时候,我们会自动分析你的模型,它用了 200 个特征,那就做这 200 个特征的加载,可以有效地减少特征的冗余。特征重要度和特征有没有在用都可以分析出来。
Q6:在线存储引擎里面去做特征抽取,在实际线上 serving 的过程中,它的吞吐和性能如何去优化?因为可能需要拉取大量的特征,从特征引擎里是非常大的传输量。
A6:特殊拉取到在线存储引擎里面分为两种情况,如果是离线特征,只是天级拉取调度,拉取的过程也非常快,拉取的过程中,线上数据是可以正常取的,不会影响线上数据。如果是实时特征,毫秒级更新,它整个链路就不是从大数据平台,一般是通过 Flink 实时计算数据流,然后写入到存储引擎里面,取的时候我们会通过服务,支持轮询的方式,通过设置时间间隔来轮询,如果线上要求非常精准,轮询的时间可以设得短一点。如果想节省一些成本的话,把轮询时间设得稍微长一点,这都是可以自动调整的。
Q7:如上所述 EasyREC 里面其实是做了一些特征的一些缓存是吗?
A7:如果是离线特征的话,我们会直接通过大数据平台拉到内存里面,不通过存储引擎,可以给存储引擎减少很多压力。存储引擎目前只存储了 user 特征和一些实时特征,因为 user 特征非常大,不适合放在内存里面,还有一些实时特征,因为它是要实时去更新,它需要 Flink 往里面写,静态的 item 侧的特征,可以直接从大数据平台拉到内存里面。
Q8:如上所述做这种特征缓存的话,其实要做一些时效性上的权衡?
A8:对,是要做时效性的权衡。