4. 特征工程生产化

在真实生产环境中,特征工程往往不是“算完一次就结束”,而是要在训练、批处理评分、实时推理三条路径上持续复用同一批特征。如果每个团队各自写 SQL、各自维护一份特征逻辑,就会出现训练/推理不一致(training-serving skew)、重复计算、口径漂移等问题。Databricks Feature Store 的核心价值,就是把特征从“散落在 Notebook 里的代码”变成“可发现、可复用、可追溯、可在线服务”的一等资产。

一、Feature Store 到底解决什么问题

Feature Store 是一个集中登记特征定义、存储特征数据、并对外提供读写接口的组件。它同时承担四件事:

  • 特征表(Feature Table):以 Delta 表形式持久化特征值,带主键(entity key)和时间戳(timestamp key),支持按时间点正确回看历史特征。
  • 元数据与血缘:记录特征来源、计算逻辑、所属实体、版本,便于跨团队检索与审计。
  • 离线服务:通过 create_training_set 按时间点 join,生成无泄漏的训练集。
  • 在线服务:把特征同步到低延迟存储(如在线表/键值库),供实时推理按实体键毫秒级读取。

关键概念是实体(Entity)与时间戳键。实体是特征的主体,例如 user_id、product_id;时间戳键保证“用某时刻之前已知的特征”去预测该时刻的目标,避免未来信息泄漏。离线与在线服务共享同一份特征定义,这是消除训练/推理不一致的根本手段。

Feature Store 离线 / 在线双通道架构 原始数据源 事件日志 / 订单表 用户画像 / 埋点流 特征计算 Spark / SQL 聚合 窗口统计 · 编码 特征表 (Delta) PK: user_id TS: event_ts f: click_7d, gmv_30d f: cat_pref_score 离线服务 create_training_set 时间点 join → 训练集 在线服务 在线表 / 键值存储 按 user_id 毫秒级读取 模型消费 批量评分 / 实时推理 训练 · 上线 · 监控 离线读取 在线同步 同一份定义

二、创建特征表:从 DataFrame 到可服务特征

在 Databricks 中,创建特征表通常从一段特征计算逻辑开始。假设我们要为电商推荐场景构建用户级特征,主键为 user_id,时间戳为 event_ts,包含近 7 天点击数和近 30 天成交额。

from databricks.feature_store import FeatureStoreClient
from pyspark.sql.functions import col, count, sum, window

fs = FeatureStoreClient()

# 1. 计算特征(示例逻辑)
user_features = (
    events
    .groupBy("user_id", window("event_ts", "1 day").alias("event_ts"))
    .agg(
        count("click_id").alias("click_7d"),
        sum("gmv").alias("gmv_30d")
    )
    .select("user_id", col("event_ts.start").alias("event_ts"), "click_7d", "gmv_30d")
)

# 2. 创建特征表
fs.create_table(
    name="prod_ml.reco.user_features",
    primary_keys=["user_id"],
    timestamp_keys=["event_ts"],
    df=user_features,
    description="用户行为特征:近7天点击、近30天成交额"
)

这里有两个容易踩坑的点。第一,主键必须唯一且非空,否则在线写入会失败;第二,时间戳键决定历史回看语义,如果缺失,训练集 join 只能做“最新值”匹配,容易引入未来信息。创建完成后,特征表会以 Delta 表形式出现在 Unity Catalog 中,同时注册进 Feature Store 元数据。

特征表命名建议采用 catalog.schema.table 三段式,并按业务域划分 schema,例如 prod_ml.reco、prod_ml.risk,便于权限隔离与检索。

三、离线特征服务:时间点正确的训练集

离线服务的核心是 create_training_set。它接收一个带主键和时间戳的标签 DataFrame,按时间点把特征“回看”到标签发生之前,从而避免泄漏。

# 标签表:user_id, label, label_ts
training_set = fs.create_training_set(
    df=labels,
    feature_lookups=[
        FeatureLookup(
            table_name="prod_ml.reco.user_features",
            feature_names=["click_7d", "gmv_30d"],
            lookup_key="user_id",
            timestamp_lookup_key="label_ts"
        )
    ],
    label="label",
    exclude_columns=["event_ts"]
)

training_df = training_set.load_df()

执行后,training_df 会包含 user_id、label 以及两个特征列。判断结果是否正确,可以检查三点:特征列无空值比例是否合理、时间戳是否严格小于标签时间、行数是否与标签表一致。若某用户在某时刻没有历史特征,Databricks 会返回 null,此时需要在训练前做填充或过滤。

四、在线特征服务:低延迟读取

在线服务需要把特征表发布到在线存储。Databricks 支持通过 publish_table 将 Delta 表同步到在线表(Online Table),底层通常是低延迟键值存储。

fs.publish_table(
    name="prod_ml.reco.user_features",
    online_table_name="prod_ml.reco.user_features_online",
    mode="merge"   # 增量合并,避免全量重刷
)

发布后,推理服务可以通过 Feature Store SDK 或在线表 REST 接口按 user_id 读取特征。典型延迟目标是个位数毫秒到几十毫秒。需要注意:在线表只保留每个主键的最新值,历史版本仅存在于离线 Delta 表中。因此在线服务适合“当前状态”类特征,而需要时间点回看的训练场景必须走离线通道。

五、离线与在线的协同关系

维度离线服务在线服务
读取方式批量 join,按时间点回看按主键点查最新值
延迟分钟级到小时级毫秒级
数据范围全量历史每主键最新一行
典型用途训练集生成、批量评分实时推理、在线打分
一致性保障共享同一份特征定义由 publish 同步保证
在线表同步存在延迟窗口。若业务要求“写入即可见”,需要评估 publish 频率与在线表刷新周期,必要时改用流式写入或双写方案,并在监控中跟踪同步滞后指标。

把这三件事串起来看:特征表是唯一事实来源,离线服务负责“正确地训练”,在线服务负责“快速地推理”,两者共享同一份定义,才真正实现特征工程的生产化。实际落地时,建议先从一个高价值实体(如用户)和 3~5 个稳定特征开始,跑通训练到在线的完整链路,再逐步扩展特征数量和实体类型。