“亿”论AI之四 | eBay离线特征仓库的构建与应用

文摘   2022-10-28 11:00  




通过《“亿”论AI》系列文章,读者对eBay的统一特征仓库(Unified Feature Store)已经有一定了解。eBay AI团队成员分别介绍了特征的管理,近实时特征工程,以及如何做特征的在线服务。接下来,我们将开始分析离线特征仓库的构建与应用。


首先我们再来回顾一下特征仓库的整体架构。

特征仓库是一种双模(dual mode)的数据仓库。在线仓库负责和实时的在线应用对接,提供的是特征的实时值。而与之相对应的,离线仓库负责和训练系统对接,提供的是特征在某个特定时刻的值(point-in-time)。举个例子,如下图所示,假设交易1001在 2021/2/1 15:20:30发生,这时候在线系统对这个交易做了预测,它或许是个欺诈交易(Lable=Y),或许不是(Label=N)。那么,后续这条交易数据被用作训练的时候,其特征也只能使用2021/2/1 15:20:30这个时刻的值。对应到图中,cnt_user_calls_7d,uniqcnt_mgid在2021/2/1 15:20:30这个时刻的特征,分别是c,x。同理,而对于1002,这两个特征值应该取:d,y。

在离线场景下,获取准确的point-in-time特征对于模型训练意义重大,可以说特征仓库最核心的功能之一。一个好的离线特征仓库,可以大大缩短特征的TTM(Time-to-Market),数倍提高模型训练的效率。


那么,有哪些挑战呢?在模型训练时,离线特征和在线特征的不一致经常被诟病。典型地,离线计算逻辑是数据科学家用的Python实现的,而在线计算逻辑是应用开发工程师用Java实现的,那么在重复实现的时候,就容易不一致。获取point-in-time correct的离线特征,对模型训练非常的关键。此外,作为特征仓库,还有一个需要考虑的问题就是特征如何被复用,分享和复用才能够带来模型训练效率的提高。既要考虑到特征的离线在线一致性,又要做好特征的复用,这不是一件容易的事情。各大领先的特征仓库,都在这方面有很大的投入。


01 

训练集 (Training Set)

离线特征仓库是对接训练系统的。一般来说,离线特征仓库可以根据用户的要求生成一个Training Set(训练集)。训练系统拿到训练集以后,就可以通过Pytorch,Tensorflow这样的框架开始模型的训练。


那么什么是训练集呢?训练集是机器学习训练模型所需要的数据集,它通常包含一系列的特征,并带有Label(标注)。下面是一个训练集的例子。

第一列是交易id,第二列PIT是point-in-time的缩写,它表示的是交易发生的时间。接下来的六个列是不同的特征,更准确的说,是特征在交易发生时刻的值(point-in-time)。一般来说,训练集里的特征,是经过处理的,能直接给模型学习训练的特征,而并非是原始的特征。最后一列Lable(标注)代表的是每条数据属于哪种结果,又可以分为正样本和负样本。

  • 正样本是指属于某一类别的样本;

  • 反样本是指不属于某一类别的样本。例如:风控场景下,无撤单,欺诈等问题的良好交易,标记Label = N。反之,若存在撤单,欺诈等问题的不良交易,则标记Label = Y。


02 

特征离线/在线的一致性问题

构建训练集的一个难点在于,离线生成的特征,如何可以与在线预测时候获取到的实际值保持一致。机器学习领域,这种一致性是很重要的。试想,如果离线特征的数据分布和在线特征的数据分布并不一致,那就很容易做出错误的预测,会直接会影响线上的交易,给公司带来GMV的损失。机器学习的特征可以不是那么精确,但是需要离线在线是一致的。这个一致性问题,很多地方也称为Training-Serving Skew。

在一个典型的机器学习项目的生命周期里面,数据科学家总是从数据开始。他们离线清洗数据,处理特征,然后训练模型。当离线模型的性能调好了之后,再上线小流量实验,如果实验结果不错,就会正式上线。模型运行了一阵子之后,数据的特性可能会发生变化,这时候就需要针对新的数据进行重新训练,将再次经历上面的流程。


我们可以看到,这整个处理是比较长的,也比较复杂。那么,在基建不那么完善的情况下,就很有可能会出现:同一个特征,离线训练和在线预测的时候并不一致。导致这种一致性问题的,可能有以下几种情况。


数据源不一致

数据科学家往往对离线数据仓库的表(HDFS/Hive)是很熟悉的,他们通常会根据离线数仓的数据提炼特征,训练模型。最终,数据科学通过离线的尝试,会确认一些他们认为好的特征。这时候,他们就会找到做应用开发的工程师,要求把这些特征上线。应用开发工程师拿这些特征以后,往往会发现,这些特征并不是那么容易被移到线上。这是因为离线的数据源,在线是不一定有的。即使有,字段的含义和计算方式也未必完全一样,有的时候甚至可以说是差之毫厘,谬以千里。并且,离线的ETL是很复杂的,如果涉及到多表连接,放到线上就更加困难了。试想,这么多的离线表,怎么搬到线上呢?在实时计算的场景,这个问题就更明显了。因为很多实时数据只有线上有,离线是没有的。这时候,数据科学家往往会去数仓里找个他们认为和线上对应的表来模拟。这时候风险是挺大的,离线的数据和在线常常差别不小,这样的特征上线以后是很容易导致不一致的。


计算不一致

在离线训练的时候,数据科学家常用的是Python,PySpark或者SQL,而到了线上的时候,应用开发工程师需要理解数据科学家的代码,再翻译成C++或者Java。这样的翻译,很容易带来计算的不一致性。它可能是人为理解的差异,也可能是代码库的差异等等。举个极端的例子: city=shanghai这样的字符串,离线做数据处理的时候,用的是Spark计算,假设计算出的hash值为1001。而到了在线计算的时候,因为用的语言不一样,hash算法也不一样,同样一个字符串被hash到了不同的值:1002。


03 

业界的常见方案

Feature Store(特征仓库)最早是Uber在2017年提出的。随后的几年,很多行业的知名公司,纷纷投入这个领域,现在比较知名的特征仓库主要是下面的这些。我们可以看到,这里面既包括领先的业务型公司,如Uber,Airbnb,Linkedin,也包括各大云产商,如Google,AWS,还有优秀的创业公司,如Databricks,Tecton,国内的第四范式等等。

  • Uber: Michelangelo Palette

  • Airbnb: zipline A Declarative Feature Engineering Framework

  • Google: Vertex Feature Store

  • AWS: SageMaker Feature Store

  • Databricks: https://www.databricks.com/product/feature-store

  • Feast/Tecton: https://github.com/feast-dev/feast

  • OpenMLDB: https://github.com/4paradigm/OpenMLDB

  • Feathr: https://github.com/linkedin/feathr

这些特征仓库为特征的离线与在线一致性,提供了很多的思路与方法。归纳起来,最主要有两类。第一种方法是特征快照(snapshot),简单的说就是线上预测发生的时候直接记录那个时刻特征的值。第二种方法是特征模拟(simulation),这种方法是事后通过join key和point-in-time从离线的特征库中查询出来。两种实现方式,各有优劣,也各有适用的场景,下面来详细分析下。


特征快照

为了确保离线特征的point-in-time值与在线预测时候的值总是一致的,最简单方法是预测时直接记录特征值。每当在线预测发生的时候,直接记录当时获得的特征值,并存到离线的数据湖中,然后训练集就从那些记录里面自动地构建。虽然这种基于记录的训练集生成方式看起来非常的简单优雅,但它有两个重要的缺点。


首先,添加新的特征需要时间。如果我们想向模型添加新的特征,我们就得先把这个新特征上线,然后必须立即开始记录这个特征的值,积累到了足够长的时间,才可以用来训练我们的模型。特征在任何的机器学习模型中都是非常重要的部分,应用机器学习的主要工作就是处理特征。而这种基于特征快照的模型训练方式使得数据科学家无法快速迭代和改进模型。


其次,快照生成的特征是不能在不同模型之间共享的。如果我们想训练一个在不同时间点执行预测的新模型,或者一个使用新的key的模型,我们并不能使用基于快照的特征值。相反,我们必须重新开始记录值。


特征模拟

Google的GCP和Tecton都提供特征的离线存储技术。在特征生成的时候,直接存储到离线的特征仓库中。在需要的时候,就可以随时通过join key,point-in-time和特征名字,从离线特征仓库中重新查询出来。使用这种方式,我们就可以仅仅通过离线的特征来构建训练集,而不必先上线特征,并经历长时间去等待和snapshot。另外,特征的复用,也不会再是问题。数据科学家可以轻易使用不同的join key,不同的时间,甚至不同的计算逻辑来为模型生成point-in-time特征。这样对训练是非常友好的,也是非常理想的。Google的GCP和Tecton的实现,对于批任务生成的特征是可以很好支持的。但是,对于近实时特征,支持起来就有诸多的限制,比如特征是一直在变化的,如何存储到离线的环境呢?AirBnb的Zipline,提供了一个很好的思路,他们实现了一种基于事件回放的技术来做近实时特征的模拟,这是非常适合近实时数据的一个方法。


总结来说,我们看到虽然业界有很多的离线特征仓库的实现,但是对于像eBay这样的企业来说,很难找到一个既满足我们业务需要,又可以开箱即用的方案。所以,我们走上了深度自研的道路。


04 

eBay的离线特征仓库

对于特征的离线在线一致性问题,eBay的解决方案是自建离线特征仓库。


首先,我们引入了统一的基于DSL的特征定义。这样,无论是离线环境还是在线环境,特征不再和具体的编程语言绑定,都是通过一致的DSL来定义。我们特征仓库的运行时,可以确保在离线和在线的环境中DSL总是被一致地执行的。这样,我们就可以在平台层保证数据的计算一致性。业界的很多特征仓库,是支持基于配置的特征定义的。这个思想和DSL特征定义其实是类似的,DSL相对于配置的好处在于,具有更高的灵活性。其次,对于可以预计算的特征,比如批作业生成的特征,近实时特征,我们提供的是特征模拟方式,这样就可以在最大程度上复用和分享特征。最后,对于来自应用上下文(application context)或者调用第三方服务的特征,我们采用snapshot的方式,这样也可以准确获得特征的point-in-time值,来帮助训练集的生成。

下面我们来详细分析eBay的离线特征仓库的实现。


特征的组织

在一个好的特征仓库中,特征总是应该被统一地规范化管理。eBay的每一个特征,都有一个唯一的特征坐标,遵循以下规则


{domain}:{feature type}:{feature group name}#{feature name}{^args (optional)}:{join key names}


{domain}: 业务部门的名字。eBay有很多的业务线,典型的包括:Risk(风控),Marketing(市场),Ads(广告)等等。

{feature type}: 特征的类型。特征是需要分类型管理的,典型的包括:Batch,NRT(Near Real Time),OTF(On-the-fly)等等,后面会有详细的介绍。

{feature group name}: 特征组(feature group)是特征仓库中很常用的一个概念,它标识一组相关特征的逻辑集合,往往是特征复用的单元。

{feature name}: 特征的名字。特征是通过DSL定义的,对每个被定义的特征,都应当取一个有意义的名字。

{join key names}: join key,也叫loading key或者entity key。被用来从特征仓库中查询属于某个特定实体的特征,典型的有BuyerId,SellerId,ItemId等等。


以特征Risk:NRT:page_view#page_view_count_userid^3d:BuyerId为例。它来自风控部门(Risk),是近实时类型(NRT)的特征,包含在特征组page_view内。page_view_ count_userid则是实际的特征名称。特征名之后是参数,3d表示我们需要获得3天的页面访问数量。那么,是谁的页面访问数量呢?最后的join key name告诉我们,需要用服务请求payload里的BuyerId作为join key去数据库里取。


特征的定义

为了实现严格的离线在线计算一致性,eBay的特征都是通过统一的DSL来定义。我们来看具体的例子。


下面一个批作业生成的特征。

特征的定义非常类似编程语言中的函数。它有一个函数头,为了获取这个特征,需要两个参数。一个是address,这即是join key。另一个是pit(point-in-time)。在线环境里面,pit一般取0,表示实时值。而在离线的环境里面,pit会被设置成一个特定的时刻,表示获取那个特定时刻(point-in-time)的特征值。


函数体里面是特征的计算逻辑。这个特征是比较简单的,使用address作为join key,把特征从特征仓库中load出来,并存到一个叫udo的变量中。load出来的udo变量是包含了7个特征,这是非常常见的情况,然后我们选择其中一个特征加以返回。这个特征的定义,在离线环境和在线环境是完全一致的,特征仓库的运行时会选择正确的数据库来加载特征。


再看一个近实时特征的例子。

函数头的含义和批作业生成的特征是类似。函数体里面的计算逻辑复杂了很多。特征被从数据库中加载出来以后,通过一个遍历,计算了用户在page上的逗留时间。然后生成一个Map结构加以返回。


可以看出,我们的DSL有以下几个特点:

  • 强类型的,这样就可以做静态的类型检查,减少错误的出现。

  • 功能强大,既支持表达式计算,也支持逻辑计算(if/for)等等。

  • 可以和Java互操作,这样就可以方便的利用现有的Java类库。

  • 支持UDF,这样用户可以方便地扩展,复用常见的计算逻辑。


Key Space管理

Key Space的管理非常的重要,它是特征复用的基础。试想,如果没有一个标准化的Key管理,数据科学家根本就不知道需要使用什么样的Key去加载别人创建好的特征,那么特征仓库里的特征就无法被不同的案例复用。在eBay的特征仓库里面,Key Space被分成两个维度进行管理,分别是Key Dimension和Key。


Key Dimension: 预计算的特征总是从属于某类实体(Entity),这个类别就是Key Dimension。常见的例如:account,ip,address,email,phone,deviceID等等。Key Dimension的Metadata如下:

Key: 这里的Key就是join key。Key总是属于某个Key Dimension,它是对数据更加细的分类。比如,Account是一个Key Dimension,它包含的Key,可以是BuyerId或者SellerId。再比如,IP是另一个Key Dimension,它包含的Key,可以是UserRegsterIP,UserLoginIP等等。Key的Metadata如下:

Key和Key Dimension存在的意义在于,我们可以对特征实现语义层的管理,进而实现特征在部门乃至公司的统一和复用。

特征工程与一致性

为了保证离线在线特征的一致性,针对不同的特征类型,需要执行不同的策略,并且和特征工程的实现方式也息息相关。我们将特征分成三种不同的类型:批作业生成特征,近实时特征和On-the-fly特征,来逐一介绍。


Batch Feature

数据科学家开发特征时往往先从批作业生成的特征开始,因为这类特征的数据源基本来自数据仓库或者是数据湖,这些都是数据科学家非常熟悉的。

对于这类特征,我们会对数据进行基于Protobuf格式的打包,然后在离线特征仓库和在线特征仓库中一致地存储。批作业的任务是周期性调度的,典型地如每天,每周,每两周,每月等等。每次生成特征快照后,特征仓库会自动地将其与之前的快照进行比较,生成Delta(改变量:新的、修改的、删除的、未改变的),然后分别加载到在线特征仓库和离线特征仓库中。对于离线特征仓库,它将保存每个时间点的特征更改。离线特征仓库中存储的数据保留时间可以基于每个数据集独立地进行配置。通过这种方式,我们可以确保原始特征在存储层的一致性。当给定join key,point-in-time和特征名,即可获取特定时刻(point-in-time)的原始特征。进一步地,派生特征是由DSL定义的,计算逻辑也是线上线下统一的。因此在同样的原始特征之上,再进行一致的逻辑计算,我们就可以确保派生特征的离线在线一致性。


对于批作业生成特征的离线存储方案,选择也是比较多的,HDFS或者HBase都可以。eBay的选择是HBase,优点在于非常灵活。HBase作为HDFS上的KV Store,与线上用的KV Store最为类似,可以方便地实现非常复杂的一致性计算。如下图所示,离线特征仓库和在线特征仓存储方式几乎一样,唯一不同的是离线特征仓库将point-in-time也编码到了Key中,这样就可以非常方便地实现point-in-time查找。

值得注意的是,Delta存储方式在eBay的实践中,被证明是非常有效的。相当大部分数据每天的变化量在5%以内,这样Delta存储可以大大地节省存储空间。

Playground是离线特征仓库中另一个重要的设计。前面所描述的场景,都是基于特征已经上线的情况。而对于未上线的特征,需要先存放到Playground,当被验证有效后,特征上线,Playgound中特征也被转存至生产的离线特征仓库中。


Neal Real Time Feature

批作业生成特征最大的问题是数据有较长延迟。通过一系列ETL任务把数据导入到数据仓库或者是数据湖,再进行离线特征计算,往往会有一天或以上的数据延迟。近实时特征通常是基于事件,进行流式数据处理来生成特征。它由于有着较低的延迟并且非常容易被重用,越来越受到业界的青睐。


下图是eBay AI部门推荐使用的流式特征的处理架构:

从左往右看,业务部门的应用产生Event日志,通过消息系统(BES/Rheos)生成Raw Event。这时的Event被称为Raw Event是因为它里面还缺乏很多属性,比如User的属性,Item的属性等等。于是,业务部门的应用再通过调用内部的各种数据服务,对原始的Event做补充,就得到了Enriched Event。紧接着,这些Enriched Event会被写入到Kafka这样的流式系统中。


接下来,AI的特征处理平台将会负责消费Enriched Event,生成一致的在线特征和离线特征。在线特征工程,在之前的系列推文中已经有了非常详细的介绍,这里不在重复。这里的重点是如何对特征做离线模拟。首先,需要将Enriched Event写入到数据湖中,这样做是为了保证离线环境和在线环境所使用的数据源是一致的。其次,计算的一致性,是对通过Enriched Event的重放来实现的。Event重放的原理和在线大致是一致的。回顾一下,数据科学家近实时特征,绝大部分是Roll-up特征,它们符合下面的计算抽象。

典型的Roll-up特征包括:Sliding Window,LastK,Time Decay, Event Sequence。举例来说,用户过去3小时的交易总额就是一个常见的Sliding Window特征。


和在线特征计算类似的,Roll-up类离线特征的模拟也可以分为两个阶段。

  • Delta生成:通过一个分布式Spark任务消费数据湖里的Event数据,对每一种特征的每一个Key,都生成一个独立的Delta事件列表,并且确保这些事件是按时间排序的;


  • Delta聚合与特征生成:回放上一步产生Delta事件列表,一边聚合,一边生成point-in-time特征。


通过上面的两个步骤,我们既可以支持原始存储特征的point-in-time模拟,也可以支持派生特征的模拟。原始存储特征的模拟,可以用来做近实时特征的生产环境快速回填,派生特征的模拟则可以用来做训练集的生成。


On-the-fly Feature

On-the-fly特征是一种常见的特征类型,它和Batch特征以及Near Real Time特征不太一样。Batch特征以及Near Real Time特征,都可以在离线的环境去模拟point-in-time值,而无需发布到生产环境。On-the-fly派生变量必须首先发布到生产环境,然后这些值将被snapshot并存储到离线特征仓库中。这样,用户才可以获取到On-the-fly特征的point-in-time值。值得注意的是,Batch特征和Near Real Time特征所使用的到的Key,也是通过这样的流程记录到离线特征仓库中的。

On-the-fly特征有个缺点,就是可重用性不好。Batch特征以及Near Real Time特征可以方便地用于不同的机器学习用例(use case),而On-the-fly特征仅适用于特定的机器学习用例(use case)。


训练集的自动生成

eBay的离线特征仓库,可以根据Driver Set和Feature List,自动创建Point-in-time correct的Training Set (训练集)。


Driver Set:可以是一系列的交易号(TxnID),或者是账号(AccountID) + Point-in-time。例如:

例子1

例子2

Feature List:包含了各种类型的特征,批作业生成的特征,近实时特征,On-the-fly特征等等。特征使用前文介绍的特征坐标方式指明。例如:


“risk:otf:fg1#connected_bad_slr:Buyer.Id”

“risk:nrt:fg1#sum_lstg_price_by_slr_sw_6h:Seller.Id”

“risk:batch:fg2#trust_mgid_txn_amt:Buyer.Id”


特征仓库在拿到Driver Set和Feature List之后,即可自动编排一个作业流来生成训练集。典型的作业流如下图所示:

首先,根据不同的ML Use Case和Driver Set的point-in-time,从Key Space中加载先前snapshot的Key,生成Enriched Driver Set。接着,模拟作业将Feature List里的特征拆分成三类:Batch 特征,Near Real Time特征和On-the-fly特征。这里,拆分的主要原因是,对于不同类型的特征,需要执行不同的模拟任务。于是,结合上一步中的key和point-in-time,即可生成三类point-in-time特征。最终这三类离线特征,根据交易号,或者用户Id + Point-in-time被重新组合到一起,得到的训练集如下图。如果用户提供的Driver Set中包含Label (标签),这样的训练集即可送入到机器学习框架中,进行模型的训练。

特征的重用

在很多情况下,特征都是公司非常有价值的数据资产,是应该被鼓励复用的。


特征的复用可以分成三个层次:

  • 用例(use case)特定的,这种是无法复用的


  • 用例(use case)无关,但是领域(domain)特定的,这种的复用性好了不少,是可以在一个领域内复用的,典型的,许多风控的模型用到很多特征是重叠的,这些特征就可以在风控业务部门中复用,广告业务部门中也有不少类似的例子。


  • 领域(domain)无关,这种是最理想的复用类型,意味着特征可以在eBay内被各个业务线分享和重用。

此外,On-the-fly变量的复用性是比较差的,而预计算特征,如批作业生成的特征或者近实时特征,重用性就会好很多。应该鼓励更多的特征进入特征仓库来管理起来,这样就可以更好的共享特征,减少重复劳动,大大加快新模型的开发和迭代。


特征的生命周期

最后,让我们再来看一下特征的生命周期。特征一般是从搜索开始的。数据科学家来到特征仓库,总是先看看特征仓库中是否存在满足要求的存量特征,这些特征的性能指标如何,是否可以直接应用到模型中。如果没有,他们就会创建新的特征。而有了新的特征以后,接下来有两种使用方式。对于feedback loop短的特征,是可以直接上线的,后续这些特征大多会进入到在线训练的过程。而对于feedback loop长的特征,我们会鼓励数据科学家,先开始特征的模拟。特征模拟的核心在于,使用特征仓库里的离线数据,模拟特征的上线情况,以帮助数据科学家在离线的环境快速地调整特征和模型。特征因为不用上线就可以被模拟,这样会大大加快模型的开发。


05 

模型自动化训练 (Training Set)

更进一步地,离线特征仓库可以和模型训练深度集成,联合构建自动化的模型训练部署管道(pipeline)。整体架构图如下所示:

前两步即是上文所提到的自动化训练集的生成。接下来是生成标签(Label)。标签往往也是重要的数据资产,通过Labeling API,可以帮助管理和自动化标签的集成。这时候,生成的训练集是存在HDFS中。再后面是和训练框架的集成。训练框架如Pytorch,Tensorflow等,一般是运行在是运行在Kubernetes系统中。这里需要有个方式,方便地访问存储在HDFS中的训练集,在分布式训练的时候,甚至需要分布式地访问数据。Petastorm这样的框架,可以帮助解决这个问题。在得到模型之后,模型的发布与部署上线也可以被自动化。至此,整个机器学习项目的生命周期,从特征生成,到模型训练,再到模型部署都可以高度自动化。





总结与展望



本文主要介绍了eBay在离线特征仓库方面的工作。离线特征仓库的意义在帮助数据科学家,通过离线的数据训练模型。它可以大大缩短特征的TTM,进而加快模型的迭代和开发。这其中的关键点是离线特征和在线特征的一致性处理,事件流的模拟以及特征的复用。我们对业界的主流方案进行了归纳梳理,并深入介绍了eBay自研的解决方案。此外,还有些技术点,本文并没有特别展开,但也是实现的时候需要注意的。


  • 特征模拟可以帮助快速地训练新的模型,但对于已有模型的再训练与更新,snapshot依然是最简单高效的方式。


  • 训练集的管理与复用:训练集的生成,往往需要消耗大量的计算资源。数据科学家的一个常见操作就是基于已有的训练集,加入更多的样本,或者是加入更多的特征,复用训练集将可以提升这些任务的效率。


  • 特征质量监控。复用特征离不开对现有特征性能的分析,高质量的特征指标对特征的复用有重要的意义。


eBay技术荟
eBay技术荟,与你分享最卓越的技术,最前沿的讯息,最多元的文化。
 最新文章