1. 前言
笔者的几份工作都曾从事于特征平台方向,也与公司内各特征平台有所交流,在特征平台领域积累了一些建设经验。
这篇文章阐述了笔者对于特征平台这一领域的了解与设想。通过本文,希望能够为将要从事特征平台工作的同学提供一些框架与思路,同时也希望能够激起从事相关工作同学的讨论。
限于时间无法对该文章进行进一步的打磨与完善,敬请谅解,同时欢迎各位同学不吝指教。
以下从特征平台的建设方向、概念模型、组成部分、发展脉络四个方面展开论述,其中建设方向一节表述了笔者在调研多个特征平台后对业内特征平台建设方向的划分,概念模型一节描述了特征平台领域内较为通用的一种的模型结构,之后的组成部分一节阐述了笔者对于特征平台内各能力的总结,以及相关实现方案的探讨,最后的发展脉络一节描述了笔者观察到的特征平台常规演进路线。
2. 两大建设方向
业界特征平台大致可分为 featurestore 与泛化特征两大建设方向,两个方向建设内容基本一致,但建设目标和建设侧重点差异较大。
2.1 featurestore 方向
featurestore 方向其目标是专注于在 MLOps 领域内更好的服务模型训练&部署工作,在建设上侧重于支持丰富多样的特征生产、在线&离线消费、特征调研、质量监控等方面,是特征平台最初且最主要的发展方向,业界对外分享的特征平台相关文章也大多是描述这类方向。
参考平台:
- 开源 - Tecton 特征平台
2.2 泛化特征方向
什么是泛化特征:除特征平台自身生产的特征外,从各种在线数据源(RPC 服务、HTTP 服务、各类数据库)获取的数据,也是一种形式的特征,这种特征我们称之为泛化特征。
泛化特征方向是特征平台这一概念在国内泛化后,特征不仅提供给模型平台进行消费,还同时开始提供给上游业务服务、策略服务进行使用。
因此对于泛化特征方向的特征平台来说,其目标是专注于如何更好的服务上游业务平台,提升特征的开发&接入效率。这个方向侧重于在线数据源接入、特征衍生、特征打包等方面,同时具有了一部分数据中台的属性,只是更多的关注对在线数据的供给与管理。
参考平台:
- 滴滴 - 网约车 UFS 特征平台:该特征系统诞生于业务部门,后来变成了公司内特征获取场景的主要系统。
3. 平台概念模型
特征:实体的属性,平台最小的查询单位以及资产单位,是特征集与特征包之间的中间单位;
特征集:一组共同生产的特征集合,平台最小的生产单位与变更单位,平台由此接入外部数据源,封装了特征的数据源信息和处理逻辑;
特征包:打包了相同目标实体下的多个特征,支持跨源、跨实体查询多个特征,详情见 4.3.3 特征打包;
主键:特征/特征集/特征包的查询入参;
目标实体:标识主键代表的实体类型,例如用户 ID、设备 ID、IP 地址等,用于进行实体维度的特征检索,以及在特征打包时统一查询入参;
4. 五个组成部分
特征平台的五个组成部分
特征平台整体数据流
4.1 特征管理
4.1.1 注册&下线
特征注册&下线,是特征平台管理态的基本能力,能力如下:
- 支持将外部数据源与处理逻辑注册为特征集,并将注册信息下发为生产任务,进行特征生产;
- 支持变更管理,例如工单发起、工单审批、特征状态流转、草稿版本上线、线上版本回滚、变更历史查看等;
基于语义锁设计的特征状态流转图
特征状态 | 描述 | 上游可消费 | 平台可见 |
---|---|---|---|
draft | 特征处于草稿状态 | N | Y |
pendding | 特征处于上线审批中状态 | N | Y |
online | 特征处于上线状态 | Y | Y |
online_pendding | 特征处于上线变更中状态 | Y | Y |
deleted | 特征处于被删除状态 | N | N |
- 支持草稿版本、线上版本、历史版本的多版本管理;
- 支持特征集内特征字段变更时的向后兼容拦截检测;
- 支持特征集下线,并停止背后的特征生产任务;
- 支持下线时利用特征血缘能力,进行血缘依赖拦截检测,防止对上游消费方产生影响。
4.1.2 特征运维
特征运维的主要目的是达到对特征状态的可观测、特征问题的可感知,可拆分为特征监控与特征报警两部分。
- 特征监控
- 特征报警
4.1.3 生命周期
为方便用户进行灵活的模型训练和高效部署,并帮助平台进行特征淘汰工作,需要建设一套特征生命周期体系,在不同的生命周期阶段下提供不同的能力,但又能够保证每个阶段特征逻辑的一致性。
特征生命周期图
特征状态&生命周期 | 描述 | 离线消费 | 在线消费 | 变更审批、拦截检测 | 可继续被增量场景引用 |
---|---|---|---|---|---|
状态:online | 特征处于上线状态 | - | - | - | - |
周期:training | 特征被用于模型离线训练,特征的生产逻辑可以快速变更。 | Y | N | N | Y |
周期:serving | 特征被用于模型在线预测,特征的注册逻辑需要需要经过变更审批、拦截检测后才能修改。 | Y | N | Y | Y |
周期:deprecating | 特征被评估为低价值,处于特征淘汰阶段,不能再被上游增量场景使用,例如不能再被增量模型使用。 | Y | Y | Y | N |
状态:draft | 特征处于草稿状态 | - | - | - | - |
4.1.4 资产管理
在特征平台发展进入中期后,平台特征和平台用户逐渐增多,需要建设一系列的资产管理能力,满足各方需要。
- 特征分类&标签&检索
为提升特征的复用性,平台需要提供一套对特征资产进行的分类打标能力,建立起特征资产体系,满足用户在特征检索方面的需求。 - 特征权限
为满足特征生产者&消费者之间共享共建的授权需求,平台需要提供一套特征操作权限控制机制,分为 owner、管理、编辑、只读四种角色。 - 特征分级
分为数据敏感等级与可保障性等级两种 - 数据敏感等级(L1-L4)用来给平台的数据安全保障提供判定信息,被用在特征的可访问性控制上;
- 可保障性等级用来给平台的稳定性方面提供判定信息,如平台在紧急情况下降级时,可以优先降级哪些低优特征,以及对于高优特征提前进行高优保障。
- 特征可访问性控制
为保障平台的数据安全,提供运行态的可访问性权限控制。分为授权、鉴权两部分,在离线消费与在线消费时判断消费方是否有对应特征资产的权限,并做出相应的权限控制。 - 特征血缘
随着特征消费方的增多,需要采集特征的上下游的血缘信息,如特征关联到的模型名称、策略名称等,用以支持特征下线时的血缘依赖检测、特征价值评估等场景。 - 特征分布探查
- 数值统计:对于数值类特征,用户通常会关注一些统计类信息,例如最大值、最小值、平均值等,特征数值统计主要用于数值类特征的分析。
- 分组统计:对于数值类特征,用户通常会关注特征的分组统计值,特征分组统计主要用于数值类特征的分组统计分析。
4.1.5 价值评估
为支持特征治理工作,平台需要建设一套特征的价值评估体系,分为内容质量与消费情况两种评估角度。
- 内容质量:包括特征上次生产时间、特征覆盖率、特征生产延迟、特征稳定性 PSI 等;
- 消费情况:借助特征血缘信息,从上游消费方处得到被引用特征在业务上的正负向效果分,同时通过血缘信息查看特征在各策略、各模型的引用次数,以及通过运行态 metrics 打点获得特征调用次数以及查询成功率,帮助用户直观判断该特征的价值。
4.1.6 特征治理
背景:随着特征资产的逐渐增多,平台成本逐渐上涨,需要对特征资产进行治理,降低平台成本。
解决方式:利用特征价值评估的信息,提升高价值特征的复用率,下线低价值特征释放资源。
- 特征推荐:对于高价值特征,联动上游平台,在用户进行特征检索与特征消费时从平台产品侧进行显著提示,进行特征推荐;
- 特征淘汰:对于低价值的特征,触发特征生命周期流转,使对应特征进入 deprecating 阶段,禁止上游消费方增量引用,同时联动上游消费方对于存量引用进行逐步下线。
4.2 特征生产
4.2.1 特征计算
特征计算:消费流式/批式数据,将数据按照用户定义的特征生产 DSL 进行计算转换;
- 需要抽象一套统一的 DSL,统一对流式与批式数据的处理,降低用户的认知成本,这套 DSL 将被用在特征生产和特征回溯两个(见 4.5.1 特征回溯)场景;
- 对于批式数据的处理使用 Spark 引擎执行 DSL,对于流式数据的处理则使用 Flink 引擎执行 DSL;
- 管理态可以基于这套统一 DSL 提供不同的交互配置,如 low code 算子编排或对算法同学友好的 Python 接口编辑框等;
流式特征按照特征状态可以分为两类
- 无状态抽取类特征:从数据流中抽取某些字段加工计算后作为特征;
- 有状态窗口特征:例如序列特征、count/sum/avg/max/min/count distinct 等类型特征;
通常有状态窗口特征会使用 Flink 的原生窗口能力进行实现,但常常会遇到以下问题:Flink 滑动窗口每次滑动时会将窗口内的全量数据写到存储,大窗口大数据量下对 Flink 和数据库压力很大。因此在大窗口情况下的一种解决方案是,不使用 Flink 的原生窗口能力,而是将特征中间状态保存至外部数据库中,在每次存取数据时基于数据库里的中间状态数据进行计算。一些优化手段(参考):
- 对于 count distinct 这种需要存储全量数据的特征,可以使用 hyperLogLog 减少存储数据量,进行不精确去重计数;
- 对于大窗口特征可以使用时间坍缩的方式(1s、1m、1h、1d)进行不精确滑动窗口统计,减少存储数据量同时避免大 key 问题;
- 对于大窗口特征的另一种优化方式时,离线计算天级批式特征,近线计算流式特征,在线消费时对批式与流式两个特征进行汇总后进行在线计算;
- 对于长序列特征则建议引入更专业的序列存储数据库,或使用支持 KKV 结构的数据库进行存储;
4.2.2 特征导入
特征导入:将经过特征计算后的特征数据导入到在线存储中。
流式特征 - 生产时效性诉求
- 准实时(秒/分钟级)生产:通过 Flink 消费 MQ 即可;
- 毫秒级生产:需要提供一个在线特征生产的 RPC 接口,供上游服务直接写入;
导入方式
- 对于流式数据,通过实时写入的方式进行导入;
- 对于批式数据,通过 bulkload 的方式进行导入;
bulkload:在离线直接生成数据库底层存储文件然后加载到在线存储中,不经过在线写链路,skip 掉 rpc、log sync、flush 等所有消耗,大规模写入下成本可降低 10 倍,并且对线上影响极低,可错峰写入。
- 编码格式:如果采用 KV 格式以特征集粒度写入存储,可以使用动态 PB 编码的方式对特征集内多个特征值进行 PB 编码后写入,降低存储量,同时一定程度避免大 key 问题;
- 有状态特征的大 key 问题:对于将有状态特征的中间状态落到存储的方案,特征中间状态有时会较大,导致消费侧在高并发查询时引发大 Key 问题,对于这种情况可以使用读写分离的方式,将较大的中间状态数据存到一个中间状态 Key 中,将较小的最终结果数据存到另一个最终结果 Key 中,读取时查询较小的最终结果 Key 从而避免这个问题。
4.2.3 特征接入
特征接入:将各类外部在线数据源,如 RPC 服务、HTTP 服务、数据库等接入注册为平台特征的能力。
实现方案:
- 抽象统一的数据源接入接口,各类外部数据源只需要实现这个接口,即可被作为一种数据源注册到平台上;
type Request struct {
Params string // json string,数据源配置参数
Options string // json string,数据源选项参数
QueryParams map[string]interface{} // 数据源查询参数
}
type Response struct {
Err error
Value map[string]interface{}
}
type store interface {
MGet(ctx context.Context, reqs []*model.Request) []*model.Response
}
- 对于某一类下游的接入只开发一次,后续通过配置化的方式即可注册为特征,例如对于 RPC 服务的接入,可以通过 kitex 泛化调用能力实现对不同下游服务的调用;
- 对数据源的请求与返回参数可以进行配置化的数据处理,可通过算子编排(参考)或动态脚本(参考)进行实现。
4.3 特征消费
4.3.1 在线&离线消费
- 在线消费:对外提供统一的特征查询接口,供上游以特征、特征集、特征包为单位获取数据,接口请求参数通常为某一类实体 id 值,如果用户 ID、设备 ID、IP 地址等;
type QueryType int64
const (
QueryType_Feature QueryType = 1
QueryType_FeatureSet QueryType = 2
QueryType_FeaturePackage QueryType = 3
)
type QueryFeatureRequest struct {
QueryType QueryType `thrift:"queryType,1,required" json:"queryType"`
Name string `thrift:"name,2,required" json:"name"`
Params map[string]string `thrift:"params,3,required" json:"params"`
}
type QueryFeatureResponse struct {
Value string `thrift:"value,1,required" json:"value"`
// 用于标识特征是否存在,即是否查询到
Exist bool `thrift:"exist,2,required" json:"exist"`
}
func QueryFeature(ctx context.Context, r *server.QueryFeatureRequest) (*server.QueryFeatureResponse, error) {
// Impl
}
离线消费:
- 除满足初期的特征在线消费诉求后,特征平台在 MLOps 方向的后续发展中,为满足用户在模型训练场景的特征获取诉求,常常会提供一系列特征离线消费的能力;
- 消费形式通常为 PythonSDK 和 SQL UDF 两种形式,分别满足用户在 Notebook 和 Hive SQL 两种场景的特征离线获取诉求;
- 特征离线消费场景无论是查询模式,还是底层实现,都与在线消费差异较大,详细内容可看 3.5.3 离在线一致性一节。
- 特征工程:部分特征平台除满足特征的基本供给外,还会在消费侧增加一些特征工程算子,承担 MLOps 领域内一部分特征工程的工作;
4.3.2 特征衍生
定义:当特征平台具备基本的特征生产能力,能够将各类数据源接入注册为平台特征后,平台用户往往会产生对这些特征进一步组合加工的诉求,我们把满足这种诉求的能力称之为特征衍生。
实现方案:
在各平台的实现方案时,常常会包括特征依赖与加工逻辑两部分,用一套预定义的 DSL 进行表达。
加工逻辑常使用算子编排(参考)或动态脚本(参考)进行实现;
而在依赖特征的获取上,会有以下两种方案:
依赖特征按需获取:在执行 DSL 时,在运行时根据脚本内的执行逻辑按需查询特征集;
- 优势:查询模式非常灵活,基本可以 cover 用户所有的特征衍生诉求;
- 劣势:由于特征的执行顺序是在运行时根据脚本执行情况确定的,因此在运行时就不存在特征依赖 DAG 的概念,那也就无法对特征依赖做剪枝、查询合并、分层调度等方面的优化;同时也无法在离线消费环境下实现相同的特征衍生逻辑。
依赖特征前置获取:根据管理态元信息,在执行特征衍生逻辑前,构造特征依赖 DAG,前置查询其所依赖的特征数据;
- 优势:由于特征依赖 DAG 的执行顺序都是在管理态确定好的,因此可以对特征依赖 DAG 进行剪枝、查询合并、分层调度等方面的优化;
劣势:查询模式不够灵活,无法很好解决以下场景
- C 特征逻辑:先使用 uid 查询 A 特征,A 特征返回一批 app_id list,再使用 app_id list 扇出查询 B 特征,查询完毕后汇总结果返回;
- C 特征逻辑:先使用 uid 查询 A 特征,根据 A 特征的结果,选择是否查询 B 特征(需要运行时决定),然后把 A 和 B 特征(如果查了的话)的结果一起处理后再返回(起到一个流量过滤的作用)。
对于 DAG 的执行也有两种基本的执行模式:
基于分层思想的分层调度模式:依赖分层算法(如层序遍历、广度优先遍历)提前计算好每一层需要执行的节点;节点一批一批的调度,无需任何通知和驱动机制;
- 优势:可以对同层节点中的相同操作按照一定规则进行 Merge,如 Batch 查询下游;在多串行节点的图调度时,有较好的性能优势;代码实现较为简单;
- 劣势:在同层多节点时,由于各节点执行时间不同,容易出现长板效应,图的执行时长 = sum(每一层中最慢的节点)。
基于流水线思想的通知驱动模式:在父子节点之间建立通知机制,而后对 DAG 进行拓扑排序,再由主协程不断将后续节点分发入任务执行池中。某节点执行完成后,立即发送信号给被依赖节点,被依赖节点在收到信号后,开始执行自身。
- 优势:由于不关心兄弟节点的执行状态,如下图 DAG 中,D 执行完成后,B 收到通知开始执行,不需要关心 E 的状态,不会出现分层调度的长板效应,图的执行时长 = 图内最慢的一条路径。
- 劣势:难以对同层节点中的相同操作按照一定规则进行 Merge;在多并行节点的图调度时,有非常好的并行性能,但在多串行节点的图中,由于额外存在协程切换和通知开销,性能会稍差;代码实现较为复杂;
- 优势:由于不关心兄弟节点的执行状态,如下图 DAG 中,D 执行完成后,B 收到通知开始执行,不需要关心 E 的状态,不会出现分层调度的长板效应,图的执行时长 = 图内最慢的一条路径。
- 值得一提的是,当系统建设进入深水区之后,通常就不再局限于以上两种基本模式,会开始收集并利用 DAG 节点运行时的执行信息,用于动态生成更优的 DAG 执行计划。
落地实践:
在落地实践上,大多特征平台都会采取依赖特征前置获取的方式,这样平台对于依赖导致的扇出问题掌控力更强,便于做稳定性治理,也更易于在后期做性能优化,以及可以在离线消费阶段实现相同的特征衍生逻辑。
但也有部分平台,因其业务场景需要更灵活的特征衍生能力,会采用按需获取方案。
4.3.3 特征打包
在平台初期,用户的工作流通常都是将某个数据源内的数据导入/接入到平台,然后以特征集为粒度,调用平台接口进行在线查询。
随着进行到中期,平台内特征资产逐渐丰富,用户在使用上开始出现一些特征复用的诉求,希望在使用自建特征的同时,也能够引入平台已有特征,进行组合消费。
同时平台侧视角下,也会希望弱化特征集的概念,转而强化特征这一概念。在平台产品设计上以特征为中心,进行生产与消费解耦,以此提升平台特征资产的复用性,避免特征的重复建设。
基于以上诉求,平台通常会开始提供特征打包的能力,以特征包的形式,支持将分属不同特征集的特征进行组合打包后返回给用户。在具体的工程实现上又会有逻辑打包到物化打包的演进过程。
- 特征打包能力在初期落地时都会以逻辑打包方式为主。即在接到特征包请求时,特征系统内部并发查询特征包内的多个依赖特征,之后组合返回。这种方式的优点是配置灵活,实现简单,在查询存储时不存在大 key 问题。但劣势是,如果底层存储时是以特征集为粒度分开存储,那特征包的单次请求将会对底层存储扇出较大,且随着特征包内特征变多,扇出增多,请求长尾问题会变严重,从而影响到特征包性能(查询耗时与错误率);
- 因此,特征打包能力发展到中后期为解决以上问题,都会进一步落地物化打包的方式。即提前对特征包内依赖特征进行生产时预聚合存储,在查询时直接查询预聚合好的 key。这样的优势是,特征包单次请求对底层存储扇出始终为 1(理想情况下),可以降低平台的资源成本,且不会随着包内特征变多,而导致特征包性能劣化。但劣势是:需要实现预聚合生产链路,实现复杂;特征包内特征数据量较大情况下,会有大 key 问题;某些实现下会影响到特征生产时效性;需要考虑不同实体特征笛卡尔聚合后数据量膨胀问题;引入额外的预聚合生产成本。
4.3.4 性能优化
- 热 key 问题:由于业务场景上的聚集性,特征服务会有一些常见的热 key 流量,面对这种情况可以在损失一些特征时效性的情况下,使用本地缓存 or 分布式缓存进行优化;
- 耗时优化:利用特征消费侧的只读特性,通常可以允许上游访问特征服务 or 特征服务访问存储时,使用对冲请求机制,在只增加少量请求的情况下,降低 pct99 耗时;
- 空值过滤:针对数据源为批式数据,而查询流量空值率又较高的特征集,我们可以为其在特征导入时构建布隆过滤器,然后在线上特征服务中加载过滤器,即可过滤线上空值流量,降低下游特征存储 QPS;
- 大 key 问题:对于特征大 key 问题,可以增加自适应压缩机制,在写入时对 value 大小超过设定阈值的 key 进行压缩,同时 value 内增加相应标识,在读取时识别标识并进行解压缩。
4.4 特征存储
在特征存储上,相应于特征的在线消费与离线消费,特征存储也分为特征的在线存储与离线存储两种,在线存储专注于高并发、低延时、扇出问题的解决,离线存储专注于高吞吐、PIT 查询问题的解决;
4.4.1 在线存储
在在线存储的选型上,需要尽可能的满足两个主要诉求:
- 读取:高并发、低延时;
- 扇出友好,避免长尾问题;
进一步细化后的诉求:
- 查询模式:支持基本的 KV 格式点查,如果能支持按列查询更好;
- 写入模式:支持基本的 KV 格式写入,如果能支持按列写入更好;支持在线写入、也支持 bulkload 离线导入;
- 灵活性:可以在存储时低成本的增减列;
可扩展性:
- 随着数据量的增长,可以通过动态扩增资源,保障性能;
- 随着查询和存储的列数增多,查询性能不随之有显著劣化;
解决方案:
- 一个比较推荐的特征存储:HBase。
HBase 原生支持 bulkload 导入,同时它的存储结构设计上会将同一个 RowKey 的不同列存储在同一个底层文件块上,因此天然适合宽表存储、多列扇出的查询场景,不会随着查询时扇出列数的增加,而导致性能有明显下降。
不过 HBase 也有它的劣势,由于是基于 Java 技术栈,并且底层存储在 HDFS 上,延时和抖动问题相比其他在线存储都较为严重。
不过其中的 GC 问题上,如果使用 Java 最新 ZGC 会有很好的优化效果。
但是底层 HDFS 由于是针对离线场景下设计文件存储,所以它带来的时延和抖动基本很难优化和避免。一种能可能的优化方式是使用类似 bytestore 这种更偏在线设计的存储底座替换掉 HDFS,解决 HDFS 固有的时延和抖动问题。 - 在特征的存储上,有两种常见的存储方式;
Key 设计 | Value 设计 | 优势 | 劣势 | |
---|---|---|---|---|
KV 格式 | key = 特征集 id + 实体 id | 将特征集内的多个特征值 PB 序列化后存入 | 特征集是平台最小的生产单位,因此一个特征集内的特征都是同时更新的。以特征集为最小粒度进行存取,可以最大程度减小写入扇出。 | 如果上游在消费时需要批量查询同一个实体 ID 的多个特征集,由于每个特征集数据分布在不同 key 中,请求将会扇出至多个存储实例中,从而出现长尾问题,导致随着单次查询特征集数量的增多,查询性能也会逐渐劣化。 同时对特征打包能力也不友好,因为一个包内常常会打包多个特征集,使得包内特征集增多的情况下特征包性能劣化。 |
KKV 格式:即 Hash 格式 or 按列存取 | key = 实体 id field/column = 特征 id | 存入特征值 | 同一个实体 ID 下的特征存储在同一个存储实例下,甚至在某些存储实现下可以存储到同一个底层文件块内。极大程度减少了查询扇出过大导致的长尾问题,可以做到随着查询和存储特征数量的增多,查询性能不随之有显著劣化。如果采用这种存储方式,特征打包时就不需要实现物化打包方案了,因为没有了扇出导致的长尾问题。 | 相比 KV 格式在写入开销上大一点。单个特征集的写入需要按照特征数量扇出到多 field/column 写入,不过都是写入同一个存储实例,因此相比 KV 格式增加的开销也不会太大。 |
4.4.2 离线存储
在离线存储的选型上,需要尽可能的满足两个主要诉求:
- 离线消费时涉及大量数据的拼接,因此需要保证高吞吐;
- 为避免特征穿越问题,需要支持 point in time(时间对齐)查询;
进一步细化后的诉求:
- 查询模式:支持对列维度指定时间范围内最近一条数据的查询;
存储模式:
- 支持列维度的多版本数据存储;
- 支持按照 TTL(比如七天)对数据版本进行过期;
- 灵活性:可以在查询和存储时低成本的增减列;
可扩展性:
- 随着数据量的增长,可以通过动态扩增资源,保障性能;
- 随着查询和存储的列数与版本的增多,查询性能不随之有显著劣化;
解决方案:
方案 1 | 方案 2 | |
---|---|---|
流式特征 | 存储:Hive 表。方案:在在线生产时,同步将特征结果和生产时间 dump 到离线 hive 表,基于 dump 表建设天级快照表与天级增量表,支持 point in time 查询。 | 存储:HBase。方案:在生产时通过 hive 表 bulkload 或实时写入的方式将相同实体的特征集数据导入同一张 hbase 表中,在查询时可以利用 hbase 的按列查询、细粒度索引、列维度多版本等能力,高效的完成 PIT 查询。 |
批式特征 | 存储:Hive 表。方案:在在线生产时,同步将特征结果 dump 到离线 hive 表,每个特征集对应一张 hive 表,并以天级分区。 | 存储:Hudi。方案:在生产时即将相同实体的特征集导入同一张湖表中,等到离线消费阶段 join 这多个特征集时,即可避免 shuffle 开销。 |
4.5 特征调研
特征调研是在数据准备和预处理阶段进行的一种活动,主要目的是识别和分析那些有潜力对模型预测性能产生积极影响的特征。这个过程包括数据探索、特征的生成和初步的筛选,侧重于从数据集中找出对预测任务可能有帮助的信息。
特征平台在这个部分主要解决用户在特征调研阶段的数据探索、特征生成诉求,以及保证离线特征消费与在线特征消费时的一致性。
4.5.1 特征回溯
在特征平台建设初期,用户如果需要在离线验证流式特征尤其是窗口类的有效性,需要在线上进行特征积累,一般要多天以上,积累完成之后,再进行模型训练,离线效果评估达标之后,再上到线上。整个周期相当长,影响业务的迭代速度,因此需要能够回溯历史的特征数据,在训练前 join 到样本之中,更快进入模型训练阶段,验证特征离线效果,提高整体的实验速度。
因此特征回溯主要满足用户在离线环境下基于历史数据进行特征离线生产的诉求。
实现方式可以见 4.5.3 离在线一致性一节的场景 3。
特征回溯能力进一步可以发展为支持特征回填,即对于流式窗口类特征,验证回溯数据的有效性后,可以将数据快速回填至特征在线存储中,用于进行特征冷启,避免用户在在线消费时需要等待特征的窗口时间进行特征累计;
4.5.2 特征录制
特征录制:利用线上流量触发对已有特征的查询,将线上特征数据落到后续数据流中,使用户可以在离线获取到这些特征数据,进行后续的特征调研、模型训练工作。
常常是为解决以下两个问题:
- 数据源为在线数据源的特征,这类特征无法通过特征回溯能力在离线进行生产;
- 在特征平台建设初期,平台缺乏特征回溯能力情况下进行特征补充的解决方案。
4.5.3 离在线一致性
离在线一致性指的是平台需要保障用户在特征的离线消费阶段与在线消费阶段,读取到的特征是一致的。这一节的重点工作是平台需要抽象一套统一的 DSL,让用户可以使用这套 DSL 表述特征生产/衍生逻辑,同时平台可以在不同数据源、不同环境(离线/在线)下对 DSL 采用不同的方式进行实现,以此来保证逻辑的一致性。
平台需要解决以下 3 个主要场景的离在线一致性:
场景 1:离线消费阶段能够支持 point in time(时间对齐)查询,防止特征穿越问题;
解决方案:- 对于批式数据源特征,可以直接使用样本时间戳进行 t-1 后关联特征离线存储;
- 对于流式数据源特征,需要提前将流式特征的在线生产结果 dump 到离线存储,再以 [样本时间戳-特征 TTL,样本时间戳] 为时间区间关联到最近一条的特征写入记录;
- 对于在线数据源特征,使用查询样本时间戳与特征录制的离线数据进行关联;
场景 2:离线与在线消费两阶段特征衍生的执行结果一致;
解决方案:从三个方面保证一致- 源数据一致:需要保证离线阶段获取到的源数据与在线阶段一致,可使用场景 1 提到的方式进行保证;
- 逻辑一致:两阶段在进行特征衍生计算时,需要使用相同的 DSL 语句,这样就可以保证同一个衍生特征的衍生逻辑离在线是一致的;
- 执行一致:需要保证离线与在线环境下对 DSL 执行的实现是一致的;
场景 3:特征回溯与特征生产的执行结果保持一致;
解决方案,同场景 2- 源数据一致:确保是基于相同的数据源进行特征回溯和特征生产,例如特征生产时消费某个 MQ topic,特征回溯时使用的是同一个的 MQ topic dump 到 hive 的数据;
- 逻辑一致:使用相同的 DSL 语句;
- 执行一致:保证对 DSL 执行的实现是一致,离线通常基于 Spark 引擎进行实现,在线基于 Flink 引擎;
5. 平台发展脉络
特征平台发展脉络图
平台的整个发展都是以解决以下两个问题为目的进行功能迭代的。
- 平台特征资产、调用量、数据量增多,导致平台成本上升、平台在线性能下降;
- 用户对特征的生产和消费诉求逐渐复杂;
可以分为以下 5 个发展阶段:
初创期:建设基本的特征管理、生产、消费、存储能力,逐渐形成平台的雏形;
- 特征管理:具备接口层面的特征基本的配置化注册&下线能力,平台研发通过调用后端接口进行特征的注册&下线;
- 特征生产:具备基本的流式特征导入能力,此时对于批式数据源的导入仍然选择批式数据源转流式数据进行导入,例如 hive 数据写入 kafka,平台消费 kafka 将特征写入存储;
- 特征消费:支持统一的特征在线消费接口;
- 特征存储:完成特征在线存储的选型与搭建。
前中期:系统产品化,同时逐渐完善生产方面的能力,并建设一定的特征调研能力;
- 特征管理:随着用户变多,系统开始产品化,支持用户通过前端界面操作特征的注册&下线;
- 特征生产:由于平台批式数据量的增多,流式导入链路压力较大,对于批式数据源开始采用 bulkload 的方式进行导入优化;为满足用户将在线数据源特征接入平台的诉求,开始建设特征接入能力;
- 特征存储:采用 hive 表作为前期的特征离线存储;
- 特征调研:为满足用户在离线场景消费特征的诉求,开始建设特征录制能力,将特征写入至离线存储。
中期:开始在特征管理、生产、消费方面进行进一步的迭代建设;
- 特征管理:随着平台特征资产逐渐增多,为增加特征复用度,开始建设特征资产管理能力;为保障特征稳定性,开始建设特征运维能力;
- 特征生产:为应对日益复杂的业务诉求,帮助用户减少一部分特征开发的成本,平台侧开始建设特征计算能力,实现用户对特征计算逻辑的快速配置;
- 特征消费:为应对日益复杂的业务诉求,支持特征复用,减少重复开发,开始建设特征衍生和特征打包能力;随着平台调用量的增大,平台成本逐渐增高、在线性能逐渐下降,开始对特征在线消费进行性能优化。
中后期:针对平台成本、性能等问题,进行进一步优化,同时开始建设特征离线消费能力;
- 特征管理:为应对平台成本逐渐增高,降低平台成本,开始建设特征生命周期、价值评估、特征治理等能力,提升特征复用度,下线平台低价值资产;
- 特征生产:为解决流式特征大窗口的性能问题,开始采用各种优化手段;
- 特征消费:随着特征包内特征逐渐变多,特征包性能逐渐劣化,同时扇出调用成本较高,开始建设物化打包能力,用以保障特征包性能,同时降低调用成本;为收敛用户离线场景特征消费的入口,避免用户直接访问离线存储,开始提供特征离线消费能力;
- 特征调研:随着特征离线消费能力的提供,需要开始解决特征消费时的离在线一致性问题。
后期:继续优化特征在线与离线消费性能,完善特征调研能力,提升调研效率;
- 特征消费:随着特征依赖的逐渐复杂,特征衍生能力开始进入深水区,需要采集运行时信息,优化特征依赖 DAG 的执行效率;
- 特征存储:为优化特征离线拼接时的成本,以及 PIT 查询的效率,开始迭代特征离线存储;
- 特征调研:为帮助用户更快的验证特征效果,开始建设特征回溯能力。
6. 后记
以上代表了笔者截止到 2024.10.03 对于特征平台领域的整体认知。
限于篇幅没有对笔者较熟悉的特征管理与特征消费部分进行更详细的阐述;
限于认知无法对特征调研的实现方式部分作出更完整的论述。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。