图片来源:https://unsplash.com/photos/ZiQkhI7417A
在机器学习全流程的生命周期中,Feature Store是连接Data和Model之间的桥梁。他通过存储和管理ML过程中的数据集和数据管道,减少特征工程的重复工作,以实现高效率的特征数据开发,缩短模型迭代周期。
标准的机器学习系统由“数据”、、“模型”、“代码”三个部分组织而成,其分别对应着“特征工程”、“模型训练”、“模型部署”三个阶段。他们彼此关联和依赖,并在各自的阶段承担着重要的职责和功能,以完成整个机器学习过程的使命。
随着AI应用的快速发展,并在人脸识别、广告、搜索、个性化推荐等领域有了大规模应用后,人们开始重视AI系统能力的基础建设。各大云平台厂商陆续推出了一些通用的AI平台来加速“模型训练”和“模型部署”流程,例如:AWS SageMaker、Google Vertex AI、阿里PAI等,这块流程和系统,我们可以统一称之为ML-Ops[^1]。
the extension of the DevOps methodology to include Machine Learning and Data Science assets as first class citizens within the DevOps ecology.
随着AI平台的普及和应用,“模型训练”和“模型部署”效率得到了极大的提升,而“特征工程”作为整个机器学习流程的最初步骤,还停留在应用传统的数据开发流程阶段。为了满足机器学习对数据开发的各种定制化要求,AI领域逐步开始探索针对机器学习场景的数据开发解决方案,于是承接ML-Ops的Feature-Ops诞生了,业内随之也推出了一系列面向特征工程的系统,并称之为Feature Store,例如:Feast、Tecton、AWS SageMaker Feature Store、Databricks Feature Store。
最早提出并明确Feature Store的概念,来自2017年Uber的Michelangelo Platform[^2]。他描述了Feature Store的主要目的是为了在机器学习过程中,促进特征的注册、发现以及复用,并且保证特征数据在离线批处理和在线应用程序读取时的一致性。其能够提供高性能、低延迟的数据服务(面向在线的预估场景)和高吞吐、大容量的数据服务(面向离线的训练和批预测场景)以供模型使用。
一个简单而标准的Feature Store如下所示:
在云音乐,我们通过识别云音乐算法场景特有的业务问题,打造了云音乐自研的Feature Store - Music FeatureBox。致力于解决以下问题:
综上所述,FeatureBox是一套针对机器学习场景定制的数据系统,用来解决Feature-Ops中所描述的问题,主要包括以下三个方面:
FeatureBox并不是单一的服务或者代码库,而是一套完整的面向机器学习流程的数据系统。
FeatureBox是基于云音乐自研的数据服务管理系统 - "Datahub" 构建起来的,整体的架构图如下:
这里面模块分别有什么作用?他们之间的关系是怎么样的?下面我们来对其中几个核心模块进行详细的介绍:
"Datahub"是FeatureBox中最核心的模块,可以说是整个FeatureBox的基石。他构造了一套抽象的特征元数据,并且封装各种不同物理存储的API,将所有对物理数据的读写都抽象成对特征的操作。我们可以通过Datahub获取特征的Schema和Storage元数据,并且可以在任意语言和环境中使用Datahub API访问到你需要的特征数据。通过Datahub,FeatureBox能够让算法工程师对特征数据的操作在离线/实时/在线等各种环境下,保持一致的体验。
同时作为访问Storage的Proxy,Datahub也包含了序列化、压缩、埋点监控等切面化功能,以帮助用户屏蔽一些技术优化项,实现更高的读写效率。此外,Datahub还能作为数据和物理存储交互的拦截处理管道,添加各种自定义的处理过程(语法过滤、安全处理、缓存优化等)。
Schema&序列化
要想所有的存储数据都有元数据,首先要做的第一步就是设计一套标准的table schema,能够表达目前所有业务数据的格式。而对于schema实现来说,最重要的就是value的序列化方案选型,我们需要考虑以下几点目标:
根据以上几点,我们很容易想到两个备选方案,一是json,二是protobuff,这两个选型各有利弊,我们来分析一下。
json:
优点 - 很容易理解,扩展性也非常好,能够兼容各种语言。
缺点 - 是string明文存储,压缩比和编解码性能都不高。
protobuff:
优点 - 作为google老牌序列化方式,拥有非常好的编解码性能和压缩比,也有很好的跨语言支持能力。
缺点 - 需要生成.proto来维护schema,不利于字段动态扩展。(一个table增加字段,可能涉及线上应用、flink应用、etl应用、spark训练脚本等多个地方变更schema)。
那么有没有办法,即能拥有pb的高效性能,又能拥有json的扩展能力呢?答案是肯定的!
我们调研通过PB库中的com.google.protobuf.DynamicMessage
和com.google.protobuf.Descriptors.Descriptor
类来实现基于protobuff的元数据管理和转换,并通过开源库protostuff来实现.proto文件的动态编译,从而将protobuff格式做到像json一样可以直接通过Map<String,Object>来操作的便利性,并且不用多端同时更新发布.proto文件。
确定了value的序列化方式之后,构建table schema就容易多了。由于Datahub对于特征服务只提供KV/KKV的数据接口,那么我们定义的table schema只要在增加最为pk和sk的列就可以了,剩下的列就是value的pb schema。这样我们就能即保证存储引擎对于高效读写的要求,又保证了业务系统对于简单易用的要求。
例子:music_alg:fm_dsin_user_static_ftr_dpb
自动生成protobuff
syntax = "proto3";
package alg.datahub.dto.proto;
message UserStaticFeature {
repeated float userTag = 1;
repeated float userLan = 2;
repeated float userRedTag = 3;
repeated float userRedLan = 4;
SparseVector userMultiStyleSparseVector = 5;
repeated float userRedSongTimespan = 6;
repeated int32 userBaseFeatureStr = 7;
float userAgeType = 8;
float userRank = 9;
repeated float userSong2VectorEmbedding = 10;
repeated float userChineseTag = 11;
repeated float userTagPlayEndRate = 12;
repeated float userLanPlayEndRate = 13;
repeated float userPubTimePlayEndRateAll = 14;
SparseVector artistPlayEndRatioSparseVector = 15;
repeated float dsUserTag = 16;
repeated float dsUserLan = 17;
repeated float dsUserRedTag = 18;
repeated float dsUserRedLan = 19;
repeated float fatiRatio = 20;
}
message SparseVector {
int32 size = 1;
repeated int32 indices = 2;
repeated double values = 3;
}
"Transform"是FeatureBox除Datahub外的另一核心模块,他主要管理从特征读取到模型输入的整个过程,是机器学习系统中特征工程-模型工程衔接的纽带。Transform是由FeatureBox中注册的Feature元数据、算子元数据等编排配置而成,他能够跨语言、跨引擎的表达特征抽取的执行过程。
与业内的Transform定义不同,这里的Transform只是一个自定义DSL的配置描述,他表示的是整个特征抽取的的计算过程,并不包括具体的任务和任务管道(相关部分在Job Generator和Web Console的任务管理功能中)。
Transform根据实际的应用场景不同,可以分为三种情况:
场景 | 描述 | 特征获取 | 算子语言(兼容) | 输出类型 |
---|---|---|---|---|
离线训练 | 用于离线环境模型训练的批量Transform | 从Hive/Hdfs获取一个DataSet | java/scala/c++ | TFRecord文件 |
在线预测 | 用于在线环境模型预测的指定特征集合的Transform | 从Redis/Tair通过Key查询特征集合 | java/scala/c++ | Vector |
实时特征(规划) | 用于实时特征生产的流式数据Transform | 从Kafka/Nydus获取Streaming数据 | java/scala/c++ | 动态ProtoBuf对象 |
我们可以通过同样的Transform语法(MFDL)来表达不同环境和计算引擎的特征计算执行过程,以产出最终需要特征值:
关于我们Transform模块中的MFDL是如何实现和应用的,可以阅读上篇文章云音乐预估系统建设与实践[1]的内容,其详细描述了MFDL在线上预估系统中的使用。
当机器学习系统出现问题时,大部分的原因来自于数据问题。因为FeatureBox包含了所有的特征存储、特征元数据、特征服务信息等功能,所以他能成为一个非常好的特征监控中心服务,来帮助整个机器学习流程定位和发现各种特征数据问题。一般的情况下,我们主要会统计和监控以下三类指标:
关于特征基础指标和偏移检测,FeatureBox的Monitor模块主要集成TFX中的Data Validation[2]组件来实现对数据集的分析和监控。我们主要提供以下三种分析和监控功能:
下图详细描述Monitor模块在整个机器学习流程中的位置和作用。
示例:针对数据集的基础统计信息和分布提供可视化的视图,以方便算法同学排查数据异常问题。(原生的TFDV通过jupyter notebook执行脚本以生成可视化信息,我们也可以通过采集每次统计的stats数据以展示到FeatureBox界面中)
统计视图会将特征分为连续值和离散值两类,两者都会有分布统计(连续值采用标准直方分布),另外连续值会有中位数、方差、标准差等统计。
"Storage"是FeatureBox中的物理存储层,负责存储真实的特征数据,并对上游的数据服务层提供数据的读写服务。根据不同的特征应用场景,Storage模块可以分为离线存储和在线存储。
离线存储:离线存储通常应用在训练或批预测场景,存储近月/近年来TB级别的特征数据,提供小时级/天级的批量读写能力。常见的离线存储有HIVE/HDFS等。
在线存储:在线存储通用应用在实时预测场景,只存储特征数据的最新值,并有着高响应、低延迟的要求。常见的在线存储有Redis/Tair/MySQL等。在云音乐,我们为了满足不同类型的特征存储要求和不同场景的响应要求,还基于Tair架构定制了存储引擎内核,他们分别是:
FeatureBox通过Datahub/DataService作为路由代理,将上层业务对特征数据的读写路由并转化到实际对应的Storage连接进行操作。所以用户对底层的Storage的API和运维其实是不感知的,他们只是通过Web Console来定义Schema与选择他们特征数据更适用的Storage。这也促成了FeatureBox可以让特征存储的管理、运维、数据迁移、快速失败、扩缩容等工作变得更加方便。
以上就是本篇文章的全部内容,我们简单的介绍了FeatureOps和FeatureStore的定义和他所解决的问题,并以此展开讲述了云音乐自建Feature Store - FeatureBox的主要设计和模块功能,希望能给对特征工程感兴趣的小伙伴带来启发和帮助。因为篇幅问题,在整个Featur Store中还有非常多的细节没有展开,大家可以关注后续的文章。
[^1]: MLOps SIG[4][^2]: Michelangelo Platform[5]
云音乐预估系统建设与实践: https://zhuanlan.zhihu.com/p/529175986
[2]Data Validation: https://github.com/tensorflow/data-validation
[3]自研磁盘型特征存储引擎RDB在云音乐的实践: https://zhuanlan.zhihu.com/p/481746458
[4][MLOps SIG: https://github.com/cdfoundation/sig-mlops/blob/master/roadmap/2020/MLOpsRoadmap2020.md
[5][Michelangelo Platform: https://eng.uber.com/michelangelo-machine-learning-platform