Feast on Amazon 解決方案

亞馬遜雲開發者發表於2023-04-05

背景&引言

眾所周知,AI 演算法模型開發落地有三個主要階段:資料準備、模型訓練、模型部署。目前已經有較多廠商及開源社群推出通用的 AI、MLOps 平臺支撐模型訓練與部署階段,但主要偏重於機器學習模型開發,部署,服務層面,自 2019 年後才陸續有各廠商推出資料準備支撐階段的產品及服務,即特徵平臺(如 Amazon Sagemaker feature Store)。

亞馬遜雲科技開發者社群為開發者們提供全球的開發技術資源。這裡有技術文件、開發案例、技術專欄、培訓影片、活動與競賽等。幫助中國開發者對接世界最前沿技術,觀點,和專案,並將中國優秀開發者或技術推薦給全球雲社群。如果你還沒有關注/收藏,看到這裡請一定不要匆匆劃過,點這裡讓它成為你的技術寶庫!

特徵平臺的主要能力包含:特徵註冊中心、離線儲存&消費、線上儲存&消費、離線&線上特徵同步,特徵版本,尤其特徵版本最為重要,實現特徵 point-in-time cross join,避免特徵穿越造成 train-server skew 的重要功能特性。

各個廠商在特徵平臺的架構和實現方式方面迥然不同,缺乏跨平臺的通用的特徵庫方案。

Feast (Feature Store) 是一套開源特徵庫框架,純 python 框架,與 Pandas dataframe 無縫整合,對 ML,AI 演算法工程師友好,它提供了線上,離線特徵庫註冊,特徵庫儲存,特徵資料攝取、訓練資料檢索、特徵版本、離線-線上特徵同步等功能;且具有云原生親和力,可以構建在多個公有云平臺上。

本文介紹了 Feast 框架的整體架構及設計思路,並 step by step 詳細說明了 Feast on Amazon 整合和使用,包括安裝部署離線/線上特徵庫、使用特徵庫、特徵庫同步的方法等。對於使用 Feast 開源框架構建 MLOps 平臺的使用者,本文可以作為快速構建和開發指南。

Feast 整體架構


Feast的主要功能元件:

  • Feast Repo&Registry:輕量級的目錄級及 Split 檔案資料庫格式 Repository,用於特徵庫基礎設施及後設資料註冊
  • Feast Python SDK/CLI: 開發構建及使用特徵庫的主要功能元件
    1. Feast Apply:命令列工具執行安裝部署配置的特徵庫到底層基礎設施,並且註冊特徵庫後設資料到 Runtime 執行態

    1. Feast Materialize:離線-線上特徵庫版本同步工具
    2. Get Online Features:線上特徵資料提取,呼叫對應的線上特徵庫基礎設施 API 抽取特徵資料,用於模型推斷
    3. Get Historical Features:離線特徵資料抽取,呼叫對應的離線特徵庫基礎設施 API 抽取歷史特徵資料,用於模型訓練或者特徵組合
  • Online Store: 線上特徵庫,根據不同雲廠商的 nosql 資料庫承載,儲存特徵快照版本資料
  • Offline Store:離線特徵庫,根據不同雲廠商數倉承載,儲存特徵歷史版本資料

    Feast On Amazon 安裝部署方案

    依賴準備

  • Feast on Amazon 使用 Redshift 作為離線特徵庫,需要 Redshift 叢集(如果採用 Spectrum 外部表,還需要 Spectrum 角色及 Glue Catalog 許可權)
  • Feast on Amazon 使用 DynamoDB 作為線上特徵庫,需要 DynamoDB 讀寫許可權
  • 可以用 Terraform 或者 CloudFormation 準備需要的 Redshift,DDB,IAM 角色等
  • 以下使用 Terraform 為例安裝部署 Feast 需要的 Redshift,S3,IAM 角色等各種基礎設施

    1. 安裝部署 Terraform
sudo yum install python3-devel
sudo yum install -y yum-utils
sudo yum-config-manager —add-repo https://rpm.releases.hashicorp.com/AmazonLinux/hashicorp.repo
sudo yum -y install terraform
  1. 編寫 Terraform 配置檔案
project: feast_aws_repo
registry: data/registry.db
provider: aws
online_store:
  type: dynamodb
  region: ap-southeast-1
offline_store:
  type: redshift
  cluster_id: feast-demo2-redshift-cluster
  region: ap-southeast-1
  database: flinkstreamdb
  user: awsuser
  s3_staging_location: s3://feastdemobucket
  iam_role: arn:aws:iam::**********:role/s3_spectrum_role
  1. 構建基礎設施
cd infra
sudo terraform init
sudo terraform plan -var="admin_password=xxxxx"
sudo terraform apply -var="admin_password=xxxxx"
  1. 如果需要 Spectrum 承載離線特徵庫,需要在 Redshift 中建立 Spectrum 外部 schema,以便指向Glue Catalog 中的 s3 外部表
aws redshift-data execute-statement \
    —region ap-southeast-1 \
    —cluster-identifier feast-demo-redshift-cluster \
    —db-user awsuser \
    —database dev —sql "create external schema spectrum from data catalog database 'flinkstreamdb' iam_role \
    'arn:aws:iam::**********:role/s3_spectrum_role' create external database if not exists;“

Feast 特徵庫 Repository 準備

  1. 依賴安裝及升級
pip3 install -U numpy==1.21
pip3 install feast[aws]
  1. 初始化 repository
feast init -t xxxxx(repository_name)
AWS Region (e.g. us-west-2): ap-southeast-1
Redshift Cluster ID: feast-demo-redshift-cluster
Redshift Database Name: flinkstreamdb
Redshift User Name: awsuser
Redshift S3 Staging Location (s3://*): s3://feastdemobucket
Redshift IAM Role for S3 (arn:aws:iam::*:role/*): arn:aws:iam::xxxxxx:role/s3_spectrum_role

建立好的特徵庫的 schema 及骨架示例:

$ tree ./feast_aws_repo/
./feast_aws_repo/
├── data
│   └── registry.db
├── driver_repo.py
├── feature_store.yaml
  • *.yam l 配置指定 Feast repository 的基礎環境資源(s3、Redshift、DDB 等)
  • *.py 配置特徵庫後設資料,特徵 view 及 schema 等
  • db 儲存基於 *.py 後設資料構建後的特徵組,特徵庫物件例項,以便執行態使用

安裝部署後的 feature_store.yaml 示例:

project: feast_aws_repo
registry: data/registry.db
provider: aws
online_store:
  type: dynamodb
  region: ap-southeast-1
offline_store:
  type: redshift
  cluster_id: feast-demo2-redshift-cluster
  region: ap-southeast-1
  database: flinkstreamdb
  user: awsuser
  s3_staging_location: s3://feastdemobucket
  iam_role: arn:aws:iam::xxxxxxx:role/s3_spectrum_role

driver_repo 的司機行程特徵庫後設資料示例:

from datetime import timedelta
from feast import Entity, Feature, FeatureView, RedshiftSource, ValueType
driver = Entity(
    name="driver_id",
    join_key="driver_id",
    value_type=ValueType.INT64,
)
driver_stats_source = RedshiftSource(
    table="feast_driver_hourly_stats",
    event_timestamp_column="event_timestamp",
    created_timestamp_column="created",
)

driver_stats_fv = FeatureView(
    name="driver_hourly_stats",
    entities=["driver_id"],
    ttl=timedelta(weeks=52),
    features=[
        Feature(name="conv_rate", dtype=ValueType.FLOAT),
        Feature(name="acc_rate", dtype=ValueType.FLOAT),
        Feature(name="avg_daily_trips", dtype=ValueType.INT64),
    ],
    batch_source=driver_stats_source,
    tags={"team": "driver_performance"},
)

部署成功後可以在 Redshift 看到離線特徵庫的 Spectuam schema 及庫表,DDB 中可以看到線上特徵庫的表

Redshift 離線特徵庫:

DDB 線上特徵庫:

使用Feast SDK API進行特徵庫操作

連線特徵庫

安裝部署完成後,在 python 程式碼中,可以方便的透過載入註冊的 repository 路徑,來連線到特徵庫及特徵組

在 repository 中註冊的特徵組,也可以直接 import 例項化

from datetime import datetime, timedelta
import pandas as pd
from feast import FeatureStore
from driver_repo import driver, driver_stats_fv
fs = FeatureStore(repo_path="./")
>>> print(fs)
<feast.feature_store.FeatureStore object at 0x7f48d47098d0>
>>> print(driver_stats_fv)
{
  "spec": {
    "name": "driver_hourly_stats",
    "entities": [
      "driver_id"
    ],
    "features": [
      {
        "name": "conv_rate",
        "valueType": "FLOAT"
      },
      {
        "name": "acc_rate",
        "valueType": "FLOAT"
      },
      {
        "name": "avg_daily_trips",
        "valueType": "INT64"
      }
    ],
    "tags": {
      "team": "driver_performance"
    },
    "ttl": "31449600s",
    "batchSource": {
      "type": "BATCH_REDSHIFT",
      "eventTimestampColumn": "event_timestamp",
      "createdTimestampColumn": "created",
      "redshiftOptions": {
        "table": "feast_driver_hourly_stats"
      },
      "dataSourceClassType": "feast.infra.offline_stores.redshift_source.RedshiftSource"
    },
    "online": true
  },
  "meta": {}
}

離線特徵資料提取

透過 Feast get_historical_features API,可以抽取離線特徵庫資料用於離線訓練或特徵組合

features = ["driver_hourly_stats:conv_rate", "driver_hourly_stats:acc_rate"]
entity_df = pd.DataFrame(
         {
             "event_timestamp": [
                 pd.Timestamp(dt, unit="ms", tz="UTC").round("ms")
                 for dt in pd.date_range(
                     start=datetime.now() - timedelta(days=3),
                     end=datetime.now(),
                     periods=3,
                 )
             ],
             "driver_id": [1001, 1002, 1003],
         }
     )
 training_df = fs.get_historical_features(
         features=features, entity_df=entity_df
     ).to_df()

如上我們抽取特徵標識(entity 欄位為 driver_id)為 1001,1002,1003, 時間版本為最近 3 天的離線特徵庫資料

>>> training_df
          event_timestamp  driver_id  conv_rate  acc_rate
0 2022-07-04 02:33:54.114       1001   0.036082  0.707744
1 2022-07-05 14:33:54.114       1002   0.522306  0.983233
2 2022-07-07 02:33:54.114       1003   0.734294  0.034062

離線特徵組合

多個特徵組需要聯合並抽取作為模型訓練時,get_historical_features 可以指定多個特徵 view 的 features,基於 event_timestamp 做 point-in-time 關聯,從而得到同一時間版本的離線特徵組合的資料

feast_features = [
       "zipcode_features:city",
       "zipcode_features:state",
       "zipcode_features:location_type",
       "zipcode_features:tax_returns_filed",
       "zipcode_features:population",
       "zipcode_features:total_wages",
       "credit_history:credit_card_due",
       "credit_history:mortgage_due",
       "credit_history:student_loan_due",
       "credit_history:vehicle_loan_due",
       "credit_history:hard_pulls",
       "credit_history:missed_payments_2y",
       "credit_history:missed_payments_1y",
       "credit_history:missed_payments_6m",
       "credit_history:bankruptcies",
   ]
training_df = self.fs.get_historical_features(
           entity_df=entity_df, features=feast_features
).to_df()

如上程式碼示例,在抽取離線特徵時,關聯了 credit_history 和 zipcode_features 兩個離線特徵庫的相應特徵欄位,Feast 會在後臺拼接 Redshift Sql 關聯對應的庫表及 event_timestamp 等條件

離線特徵資料同步線上特徵庫

透過Feast 提供的 materialize cli,可以將指定時間版本的 Redshift 離線特徵資料同步到 DynamoDB 的線上特徵庫中

materialize-incremental cli 會記錄該 repository 特徵庫下每次同步的增量時間版本,因此每次執行會把自上次執行至今的最新資料增量同步到 DynamoDB

CURRENT_TIME=$(date -u +"%Y-%m-%dT%H:%M:%S")
feast materialize-incremental $CURRENT_TIME

Materializing 1 feature views to 2022-07-07 08:00:03+00:00 into the sqlite online
store.
driver_hourly_stats from 2022-07-06 16:25:47+00:00 to 2022-07-07 08:00:03+00:00:
100%|████████████████████████████████████████████| 5/5 [00:00<00:00, 592.05it/s]

當然也可以使用 materialize 顯式指定開始時間(startdt)和截止時間(enddt), feast 會將指定時間版本的離線特徵庫資料同步到線上特徵庫

feast materialize 2022-07-13T00:00:00 2022-07-19T00:00:00

Materializing 1 feature views from 2022-07-13 00:00:00+00:00 to 2022-07-19 00:00:00+00:00 into the dynamodb online store.
driver_hourly_stats:
100%|█████████████████████████████████████████████████████████████████| 5/5 [00:00<00:00, 51.18it/s]

線上特徵查詢

>>> online_features = fs.get_online_features(
         features=features, entity_rows=[{"driver_id": 1001}, {"driver_id": 1002}],
     ).to_dict()
>>> print(pd.DataFrame.from_dict(online_features))
   acc_rate  conv_rate  driver_id
0  0.179407   0.984951       1001
1  0.023422   0.069323       1002

Feast offline store on Spark 方案

上文我們看到的是 Feast 依託 Amazon Redshift 作為離線特徵庫儲存和特徵抽取的方案,雖然安裝部署簡介明快,上手方便,但 Redshift 定位是雲服務資料倉儲,雖然在 sql 相容性、擴充套件性上優秀,但靈活性不足,如:

  • 離線特徵抽取必須要指定 event_timestamp 版本,無法直接查詢最新 snapshot
  • point-in-time 關聯查詢直接拼接 partition over 分組 sql 並下壓,海量資料情況下,多歷史版本的特徵庫 time travel 抽取時會膨脹數倍,存在效能瓶頸

Feast 自0.19版本開始,支援 Spark 作為離線特徵庫歷史資料提取,版本查詢,同步線上特徵庫的計算框架

Spark 作為高效能分散式計算引擎,在海量資料場景下效能優異,且使用 Spark 時,Feast FeatureView 的 DataSource 既可以是指向 Hive 中的表,也可以是指向物件儲存上的檔案,透過 Hive 表可以相容諸如 Hudi、iceberg 等多種資料湖架構。

同時,透過 Spark 離線特徵庫抽取的特徵資料,Feast 將其封裝為 Spark DataFrame,從而可以方便的載入到 S3分散式儲存,因而也避免了 Pandas DataFrame 儲存在本地磁碟的儲存空間問題。

Feast point-in-time correct join Spark 實現

point-in-time correct join,根據原始碼來看,使用 pySpark+SparkSQL 實現,因此整體思路和 Redshift 類似:

  • 將 entity_df 由 DataFrame 轉化為 Spark DataFrame,並註冊成臨時表
  • 根據使用者指定要關聯的 features,找到對應的 FeatureView,進而找到底層的 DataSource 和相關的後設資料
  • 根據以上資訊,即 query_context,透過 jinjia 渲染一個 SparkSQL,並提交給 Spark 叢集計算
  • 計算完成的結果就是實現 point-in-time correct join 之後的 training dataset

Feast offline store on Amazon EMR安裝部署

Amazon EMR 是全託管的 hadoop 大資料叢集,提供了良好的彈性伸縮,高可用,存算分離等特性,且透過 EMRFS 原生整合 Amazon S3雲端儲存,用於承載 Feast 的 Spark 離線特徵庫具有天然的親和力。

以下詳細介紹 Feast Spark 離線特徵庫在 Amazon EMR 的安裝部署步驟及使用方法

啟動 Amazon EMR 叢集

Amazon EMR 的啟動方法本文不再贅述,感興趣的同學可以參閱 Amazon EMR 文件

此處選擇 emr 6.5版本,Spark 3.1.2

Offline store on EMR 特徵庫配置

我們在 emr 主節點上可以 feast init 特徵庫,從而直接利用 Amazon EMR上spark 與 S3的原生整合,透過 emrfs 讀寫 S3資料湖上各種格式檔案,不再需要 hadoop s3開源 lib 的支援

feast init my_project 後,在該特徵庫的 yaml 配置檔案中,指定 Feast spark 的對應引數即可:

project: feast_spark_project
registry: data/registry.db
provider: local
offline_store:
    type: spark
    spark_conf:
        spark.master: yarn
        spark.ui.enabled: "true"
        spark.eventLog.enabled: "true"
        spark.sql.catalogImplementation: "hive"
        spark.sql.parser.quotedRegexColumnNames: "true"
        spark.sql.session.timeZone: "UTC"

配置完成後,透過 feast apply cli 同樣部署到 EMR spark

注:在 EMR master 節點上 pyspark lib 路徑需要在環境變數中設定,以便 feast 找到 spark 的 home 目錄及相應配置

source /etc/spark/conf/spark-env.sh
export PYTHONPATH="${SPARK_HOME}/python/:$PYTHONPATH"
export PYTHONPATH="${SPARK_HOME}/python/lib/py4j-0.10.9-src.zip:$PYTHONPATH"

Feast on Spark 離線特徵庫後設資料

from feast.infra.offline_stores.contrib.spark_offline_store.spark_source import ( SparkSource,)

driver_hourly_stats= SparkSource(
        name="driver_hourly_stats",
        query="SELECT event_timestamp as ts, created_timestamp as  created, conv_rate,conv_rate,conv_rate FROM emr_feature_store.driver_hourly_stats",
        event_timestamp_column="ts",
        created_timestamp_column="created"
        )

Feast 的 sparkSource 提供了 query, table,及原始 raw 檔案路徑幾種初始化方法,本文中使用 query 方式。

需要注意 query 方式中,需要指定 event timestamp field 特徵欄位以便 Feast 識別作為 point-in-time cross join 時間版本抽取及特徵 join 的依據

Feast Spark offline store 執行

配置 Spark 作為 Feast offline store 後,透過 Amazon EMR 上 spark history UI,可以清楚的看到其 get_historical_features 方法,底層 Feast 使用 SparkSQL 建立臨時檢視,拼接 event time join 的 sql,並查詢上文中 source 資料湖上 hive 庫表等各個步驟的業務邏輯:

跟蹤 Spark history UI 上,Spark Sql 的各個 query 可以看到,Feast 的 get_historical_features 方法執行時,會構造臨時表 entity_dataframe,即使用者呼叫 get_historical_features 方法時,傳入的樣本列表。再構建 driver_hourly_stats_base,即需要 join 及 point-in-time 查詢的即樣例特徵時序表

== Parsed Logical Plan ==
'CreateViewStatement [driver_hourly_stats__cleaned], (

    WITH driver_hourly_stats__entity_dataframe AS (
        SELECT
            driver_id,
            entity_timestamp,
            driver_hourly_stats__entity_row_unique_id
        FROM entity_dataframe
        GROUP BY
            driver_id,
            entity_timestamp,
            driver_hourly_stats__entity_row_unique_id
    ),

driver_hourly_stats__base AS (
        SELECT
            subquery.*,
            entity_dataframe.entity_timestamp,
            entity_dataframe.driver_hourly_stats__entity_row_unique_id
        FROM driver_hourly_stats__subquery AS subquery
        INNER JOIN driver_hourly_stats__entity_dataframe AS entity_dataframe
        ON TRUE
            AND subquery.event_timestamp <= entity_dataframe.entity_timestamp

            
            AND subquery.event_timestamp >= entity_dataframe.entity_timestamp - 86400 * interval '1' second
            

            
            AND subquery.driver_id = entity_dataframe.driver_id
            
    ),

後續的 subquery、dedup 及 cleaned 子查詢,會基於以上的兩張基礎表,進行基於特徵標識欄位 driver_id 和時序時間戳欄位 event_timestamp 的分組排序,剔重等操作,最後 join 樣本列表臨時表 entity_dataframe,整個流程與 Redshift 上基本一致

driver_hourly_stats__subquery AS (
        SELECT
            ts as event_timestamp,
            created as created_timestamp,
            driver_id AS driver_id,
            
                conv_rate as conv_rate, 
            
                acc_rate as acc_rate
            
        FROM (SELECT driver_id,event_timestamp as ts, created_timestamp as  created, conv_rate,acc_rate,avg_daily_trips FROM emr_feature_store.driver_hourly_stats)
        WHERE ts <= '2022-07-25T03:27:05.903000'
        
        AND ts >= '2022-07-21T03:27:05.903000'
        
    ),

 driver_hourly_stats__dedup AS (
        SELECT
            driver_hourly_stats__entity_row_unique_id,
            event_timestamp,
            MAX(created_timestamp) as created_timestamp
        FROM driver_hourly_stats__base
        GROUP BY driver_hourly_stats__entity_row_unique_id, event_timestamp
    ),
driver_hourly_stats__latest AS (
        SELECT
            event_timestamp,
            created_timestamp,
            driver_hourly_stats__entity_row_unique_id
        FROM
        (
            SELECT *,
                ROW_NUMBER() OVER(
                    PARTITION BY driver_hourly_stats__entity_row_unique_id
                    ORDER BY event_timestamp DESC,created_timestamp DESC
                ) AS row_number
            FROM driver_hourly_stats__base
            
                INNER JOIN driver_hourly_stats__dedup
                USING (driver_hourly_stats__entity_row_unique_id, event_timestamp, created_timestamp)
            
        )
        WHERE row_number = 1
    )

API 結果返回可以 to_df 為 Spark 的 Dataframe,從而實現 remote 儲存離線特徵庫抽取結果資料的操作,這也從另一方面解決了原有 Redshift 離線特徵儲存,特徵抽取只能返回 pandas Dataframe 的劣勢,在大資料量離線特徵場景下更有優勢

總結

綜上所述,Feast 框架整體架構和在 Amazon 的構建是非常簡潔明快的,對構建 MLOps 平臺的使用者而言,其主要有價值的優勢如下:

  • 同時提供了離線,線上特徵庫,離線-線上特徵庫快照版本同步功能
  • 輕量級,快速部署使用, 程式碼即配置,feast apply 即可部署到 Amazon
  • 透過 repository 檔案系統隔離特徵庫,方便 MLOps 多租戶多 CICD 協同開發
  • API 抽象程度高,貼近 AI/ML 演算法工程師業務語言

對於海量離線特徵資料抽取時 point-in-time cross join 的版本查詢資料膨脹的業界難點,Feast 也可以透過 on EMR Spark 的構建方式,最佳化解決其效能問題

參考資料

Amazon Sagemaker Feature Store: https://docs.aws.amazon.com/zh_cn/sagemaker/latest/dg/feature-store.html?trk=cndc-detail

Feast官方:https://docs.feast.dev/getting-started/architecture-and-compo...

Amazon EMR叢集部署:https://docs.aws.amazon.com/zh_cn/emr/latest/ManagementGuide/...

本篇作者

唐清原
Amazon 資料分析解決方案架構師,負責 Amazon Data Analytic 服務方案架構設計以及效能最佳化,遷移,治理等 Deep Dive 支援。10+資料領域研發及架構設計經驗,歷任 Oracle 高階諮詢顧問,咪咕文化資料集市高階架構師,澳新銀行資料分析領域架構師職務。在大資料,資料湖,智慧湖倉,及相關推薦系統 /MLOps 平臺等專案有豐富實戰經驗

文章來源:https://dev.amazoncloud.cn/column/article/6309bf4a0c9a20404da...

相關文章