Feast 特征存储:离线与在线特征管理

FreeGuideOnline 最新 2026-06-17

Feast 特征存储简介:连接离线训练与在线推理的桥梁

在构建机器学习系统时,我们经常面临一个核心矛盾:模型训练依赖海量历史数据,而线上预测需要实时、低延迟的特征向量。如果分别维护两套特征逻辑(一套用于离线批处理,一套用于在线服务),不仅工作重复,还极易引发“训练-服务偏差”。Feast(Feature Store)正是为解决这一问题而生的开源特征平台。它提供统一的框架来定义、管理、发现和提供特征,让离线特征与在线特征在同一个逻辑视图下保持一致。

本教程将带你从零开始理解 Feast 的核心理念,重点掌握离线与在线特征管理的方式,并能够动手搭建一个可用的特征存储。

什么是 Feast?核心概念速览

Feast 不是数据库本身,而是位于数据工程与机器学习之间的编排层。它通过几个关键抽象来组织特征:

  • 特征视图(Feature View):一个具有时间意识的特征集合,通常映射到一个数据源(如 BigQuery 表、Parquet 文件)中的一组列,并带有实体和 timestamp。它是 Feast 管理离线与在线特征的核心单元。
  • 实体(Entity):业务中可识别的对象,例如用户、产品、商户。实体通常带有一个主键(如 user_id)。
  • 特征服务(Feature Service):多个特征视图的集合,代表模型实际需要的一组特征。一个特征服务可以用于生成训练数据集,也可以用于线上取值。
  • 在线存储(Online Store):低延迟键值存储(如 Redis、Datastore),用于实时服务。
  • 离线存储(Offline Store):存储历史特征数据的仓库(如 BigQuery、Snowflake、文件),用于生成大规模训练数据。
  • 注册表(Registry):集中存储特征定义和元数据的目录,可以用本地文件或云存储(GCS、S3)实现。

这些概念共同构成了 Feast 的基本工作流:定义特征 → 应用注册到注册表 → 将数据导入在线和离线存储 → 通过 SDK 获取训练数据或在线特征

离线特征管理:构建历史训练数据集

离线特征管理的目标是从数据仓库或数据湖中为模型训练生成准确、一致的样本。Feast 通过“时间点正确连接”(Point-in-Time Correct Joins)确保不会引入未来信息。

定义离线数据源与特征视图

首先,假设我们有一个 BigQuery 表存储用户的日活跃数据,包含 user_id, event_date, daily_clicks, daily_views。我们需要将这些转变为 Feast 特征。

  1. 定义实体:一个用户实体。

    from feast import Entity
    user = Entity(name="user", join_keys=["user_id"])
    
  2. 定义数据源:指向 BigQuery 表。

    from feast import BigQuerySource
    user_activity_source = BigQuerySource(
        table="my_project.user_data.daily_activity",
        timestamp_field="event_date",
        created_timestamp_column="created_at"
    )
    

    这里 timestamp_field 极为关键,它告诉 Feast 每条记录所对应的业务发生时间,后续时间点连接依赖此字段。

  3. 定义特征视图:将数据源中的列映射为特征。

    from feast import FeatureView
    activity_fv = FeatureView(
        name="user_activity",
        entities=[user],
        ttl=timedelta(days=7),  # 在线特征有效期
        source=user_activity_source,
        schema=[
            Field(name="daily_clicks", dtype=Int64),
            Field(name="daily_views", dtype=Int64),
        ]
    )
    

    ttl 参数设定了在线存储中特征值的最长存活时间。对于离线场景,它可用于过滤陈旧数据。

注册和应用定义

使用 feast apply 命令将定义同步到注册表。

feast apply

此时,Feast 已了解这些特征的存在,但离线存储中还没有历史数据可供查询。Feast 的离线检索直接查询原始数据源(例如 BigQuery),无需事先离线物化。这是 Feast 设计的一大特点:训练数据生成是实时查询数据源并按时间逻辑连接,保证了与源数据的一致性和灵活性。

使用时间点连接获取训练数据

当训练需要样本数据时,我们提供一个包含实体 ID 和时间戳的 DataFrame,Feast 会拉取该时间点之前的最新特征。

from feast import FeatureStore
store = FeatureStore(repo_path=".")

# 假设我们有一批训练事件:用户在某时间点做出动作
entity_df = pd.DataFrame({
    "user_id": [1001, 1002, 1003],
    "event_timestamp": pd.to_datetime(["2024-08-01", "2024-08-02", "2024-08-03"])
})

training_df = store.get_historical_features(
    entity_df=entity_df,
    features=[
        "user_activity:daily_clicks",
        "user_activity:daily_views"
    ]
).to_df()

Feast 内部会生成 SQL 查询,将每个事件时间与数据源中该时间之前最接近的记录进行连接。这种逐点精确的时间穿越查询(AS OF JOIN)从根本上解决了标签泄露问题,保证离线训练环境与线上环境逻辑一致。

何时需要离线物化(Batch Materialization)

如果数据源查询速度很慢,或需要定期快照以节省成本,Feast 也支持将特征数据物化到离线存储(如另一组 Parquet 文件),后续调用 get_historical_features 时直接从物化表读取。不过对于大多数场景,直接查询源表并提供计算下推已足够。

在线特征管理:为实时预测提供低延迟取值

模型部署到线上后,每接收一个请求都需要立即构建特征向量。Feast 通过在线存储实现毫秒级 KV 查找。

配置在线存储

feature_store.yaml 中指定在线存储类型,例如使用 Redis:

project: my_project
registry: gs://my_feast_registry/registry.db
provider: gcp
online_store:
  type: redis
  connection_string: "redis://:password@host:6379/0"
offline_store:
  type: bigquery

对于本地开发,可以方便地使用 Sqlite:

online_store:
  type: sqlite
  path: data/online_store.db

将特征数据同步到在线存储

离线特征视图定义中的 ttl 在这里生效。我们需要定期将最新的特征值推送到在线存储,这个过程称为物化(Materialize)。用命令完成:

feast materialize-incremental 2024-08-15T00:00:00

这会将 2024-08-15T00:00:00 到当前时间的最新特征值从离线源加载到在线存储。通常在 Airflow 等调度系统中每隔若干分钟或小时运行一次。

Python API 也可以实现同等操作:

store.materialize_incremental(end_date=datetime.now())

一旦物化完成,线上服务就可以通过 get_online_features 直接获取特征。

在线获取特征向量

在模型推理服务中,只需几行代码即可获得实时特征:

online_features = store.get_online_features(
    features=[
        "user_activity:daily_clicks",
        "user_activity:daily_views"
    ],
    entity_rows=[{"user_id": 1001}]
).to_dict()
# 输出: {"user_id": [1001], "daily_clicks": [42], "daily_views": [105]}

如果在线存储中特征缺失(例如新用户尚未物化),Feast 可以配置在线检索回退或返回默认值,但这需要合理的 TTL 和物化策略来保证覆盖率。

离在线统一架构最佳实践

1. 特征定义一次,处处使用 所有特征逻辑只存在于特征视图中。无论是离线训练、在线推理还是特征发现,都引用同一套定义,彻底杜绝训练服务偏差。

2. 时间感知是基石 务必为每个数据源设置正确的 timestamp_field,并在生成训练数据时提供 event_timestamp。这是 Feast 时间点连接准确性的前提。

3. 在线特征 TTL 设置需谨慎 TTL 太短会导致数据频繁过期,在线查询命中降低;TTL 太长可能提供陈旧特征。应根据特征更新频率和业务容忍度设置:例如分钟级更新的特征可设 TTL 为 1 小时,每日批特征可设 TTL 为 48 小时。

4. 监控特征漂移和覆盖率 Feast 提供了 Web UI(实验性)和 API 可查询特征视图元数据。应定期检查在线存储是否缺失关键特征值,以及离线与在线特征分布是否一致。

5. 逐步引入特征服务抽象 当项目特征数量增多,直接列出所有特征会变得混乱。使用特征服务将“模型 V1”、“模型 V2”等所需特征封装起来,使代码更清晰,并方便版本管理。

动手实操:从零搭建一个迷你特征存储

下面通过一个本地示例,演示完整的离线训练和在线推理流程。

环境准备

pip install feast[redis]
feast init my_feature_store
cd my_feature_store/feature_repo

1. 定义实体和特征视图example_repo.py 中修改:

driver = Entity(name="driver", join_keys=["driver_id"])
driver_stats_source = FileSource(
    path="data/driver_stats.parquet",
    timestamp_field="event_timestamp",
    created_timestamp_column="created"
)
driver_stats_fv = FeatureView(
    name="driver_statistics",
    entities=[driver],
    ttl=timedelta(days=2),
    source=driver_stats_source,
    online=True,
    schema=[
        Field(name="conv_rate", dtype=Float32),
        Field(name="acc_rate", dtype=Float32),
        Field(name="avg_daily_trips", dtype=Int64),
    ]
)

2. 生成示例数据并注册

import pandas as pd
pd.DataFrame({
    "driver_id": [1001, 1002, 1001],
    "event_timestamp": pd.date_range("2024-08-01", periods=3, freq="D"),
    "conv_rate": [0.45, 0.61, 0.47],
    "acc_rate": [0.78, 0.82, 0.79],
    "avg_daily_trips": [12, 8, 13]
}).to_parquet("data/driver_stats.parquet")

运行 feast apply

3. 生成离线训练数据

from feast import FeatureStore
store = FeatureStore(".")
entity_df = pd.DataFrame({
    "driver_id": [1001, 1002],
    "event_timestamp": pd.to_datetime(["2024-08-02", "2024-08-03"])
})
train_data = store.get_historical_features(
    entity_df=entity_df,
    features=["driver_statistics:conv_rate", "driver_statistics:avg_daily_trips"]
).to_df()
print(train_data)

你会看到每个 driver_id 根据时间点拉取了最接近的历史值,成功避免使用事件时间之后的数据。

4. 物化并在线查询

feast materialize-incremental 2024-08-04T00:00:00

然后在 Python 中测试:

online = store.get_online_features(
    features=["driver_statistics:conv_rate", "driver_statistics:acc_rate"],
    entity_rows=[{"driver_id": 1001}]
).to_dict()
print(online)

这就是 Feast 特征存储的完整离在线协作流程。

总结

Feast 通过特征视图、实体、离线存储和在线存储的抽象,构建了一座连接数据工程与机器学习工程的标准化桥梁。它的价值不仅在于消除训练服务偏差,还在于通过集中化的特征目录提升团队协作效率。掌握了离线历史特征获取和在线实时推送的核心模式后,你便可以为任何规模的 ML 系统建立稳固的特征基础。