4. 样本生成流程:特征回溯、特征拼接、样本标注、样本存储

样本生成是特征平台连接离线特征与模型训练的桥梁。一个完整的样本生成流程包含四个核心阶段:特征回溯(获取历史特征)、特征拼接(关联标签与特征)、样本标注(生成监督信号)和样本存储(序列化与分区)。本章将逐一拆解每个阶段的技术细节与校验要点。

4.1 特征回溯:时间旅行与快照还原

特征回溯的核心能力是“回到过去某个时间点,获取当时应有的特征值”。这要求特征存储支持时间旅行(Time Travel)或增量快照(Snapshot)机制。

关键概念:特征回溯不是简单地取最新特征,而是根据样本的event_timestamp,还原该时刻的特征状态。例如,用户A在2024-01-15 10:00:00发生点击事件,特征回溯应取该时刻之前最新的特征版本,而非当前最新值。

4.1.1 回溯实现方式对比

实现方式 存储引擎 延迟 存储成本 适用场景
全量快照表 Hive / Iceberg 高(T+1) 高(每日全量) 离线训练样本
增量变更日志 Kafka + HBase 低(近实时) 中(仅存储变更) 实时特征校验
版本化存储 Feast / Tecton 中(分钟级) 中(按版本压缩) 特征平台原生
Delta Lake 时间旅行 Delta Lake 中(小时级) 低(仅元数据) 数据湖架构

4.1.2 回溯校验要点

  • 时间对齐:确保回溯时间戳与事件时间戳在同一时区,避免因时区偏移导致特征错位。
  • 空值处理:回溯不到特征时,需明确填充策略(默认值 / 前向填充 / 丢弃样本)。
  • 版本一致性:同一批次样本应使用同一特征版本回溯,避免因特征更新导致样本间不一致。
特征回溯时间线示意图 T-3 T-2 T-1 T(事件时间) 特征 v1 特征 v2 特征 v3 回溯取 v3 回溯规则:事件时间 T 发生时,特征 v3 已生效,因此回溯结果取 v3 而非最新 v4。 若 v3 在 T 之后才生效,则回溯应取 v2,避免未来信息泄露。

4.2 特征拼接:多源特征关联

特征拼接是将回溯后的特征与样本主键(如 user_id、item_id)进行关联,形成宽表。拼接方式分为点查拼接和批量拼接两种模式。

4.2.1 拼接模式对比

模式 实现方式 延迟 吞吐量 典型工具
点查拼接 逐条查询在线存储 高(单条毫秒级) 低(千级 QPS) Redis / HBase
批量拼接 Spark / Flink 批量 Join 低(分钟级) 高(亿级/小时) Spark SQL / Hive
实践建议:离线训练样本推荐使用批量拼接,保证特征一致性;在线推理使用点查拼接,保证低延迟。两者应使用同一特征逻辑,避免训练/推理特征不一致。

4.2.2 拼接校验清单

  • 主键唯一性:检查拼接后主键是否出现重复,重复会导致样本膨胀或标签错位。
  • 特征覆盖度:统计每个特征的 null 比例,若某特征 null 率超过阈值(如 30%),需排查回溯或拼接逻辑。
  • 类型一致性:确保拼接后的特征类型与模型输入类型一致(如 int64 转 float32)。

4.3 样本标注:标签生成与时间窗口

样本标注是根据业务目标生成监督信号(标签)。常见标注方式包括固定窗口标注、事件触发标注和多任务标注。

4.3.1 标注方式对比

标注方式 标签定义 窗口长度 适用场景
固定窗口 事件后 N 天内是否转化 固定(如 7 天) CTR / CVR 预估
事件触发 下一个事件类型 可变(到下一事件) 序列推荐
多任务 同时预测多个目标 多个窗口 多目标排序

4.3.2 标注校验要点

  • 标签泄漏:确保标签窗口不包含未来信息。例如,用 T+7 天的转化作为标签,但特征回溯不能包含 T+7 天的数据。
  • 标签分布:统计正负样本比例,若严重失衡(如正样本 < 1%),需考虑采样或加权策略。
  • 时间截断:对于窗口未结束的样本,需明确处理方式(丢弃 / 标记为未观测)。
样本标注时间窗口示意图 曝光事件 T=0 标注窗口(7天) 转化 T=3 未转化 T=10 标注规则:曝光后 7 天内发生转化 → 正样本(label=1);7 天内未转化 → 负样本(label=0)。 T=10 的未转化事件不在窗口内,不参与当前样本标注。

4.4 样本存储:TFRecord / Hive / Parquet

样本生成完成后,需要选择合适的存储格式。不同格式在读取性能、压缩比和生态兼容性上差异显著。

4.4.1 存储格式对比

格式 存储结构 压缩比 读取性能 框架兼容 典型用途
TFRecord 二进制序列化 中(支持压缩) 高(顺序读取) TensorFlow / PyTorch 深度学习训练
Parquet 列式存储 高(Snappy/Zstd) 高(列裁剪) Spark / Hive / Pandas 离线分析、特征探索
Hive ORC 列式 + 索引 高(Zlib) 中(需分区裁剪) Hive / Presto 大规模离线存储

4.4.2 存储校验要点

  • 分区策略:按日期/小时分区,避免单分区数据量过大(建议每分区 < 200GB)。
  • Schema 校验:存储前校验字段名、类型、nullable 属性是否与预期一致。
  • 数据完整性:对比存储前后的样本总数、特征均值、标签分布,确保无数据丢失。
常见陷阱:TFRecord 的 Example 协议要求所有特征必须预先定义 FeatureList 或 FixedLenFeature。若特征数量动态变化(如变长序列),需使用 VarLenFeature 或 SequenceExample,否则反序列化会报错。

4.4.3 样本存储代码示例

# TFRecord 写入示例(Python)
import tensorflow as tf

def serialize_sample(features, label):
    feature_dict = {
        'user_id': tf.train.Feature(int64_list=tf.train.Int64List(value=[features['user_id']])),
        'item_id': tf.train.Feature(int64_list=tf.train.Int64List(value=[features['item_id']])),
        'click_rate': tf.train.Feature(float_list=tf.train.FloatList(value=[features['click_rate']])),
        'label': tf.train.Feature(int64_list=tf.train.Int64List(value=[label])),
    }
    example = tf.train.Example(features=tf.train.Features(feature=feature_dict))
    return example.SerializeToString()

# 写入 TFRecord 文件
with tf.io.TFRecordWriter('train_samples.tfrecord') as writer:
    for sample in sample_batch:
        serialized = serialize_sample(sample['features'], sample['label'])
        writer.write(serialized)
# Parquet 写入示例(PySpark)
from pyspark.sql import SparkSession

spark = SparkSession.builder.appName("sample_generation").getOrCreate()

df = spark.createDataFrame([
    (1001, 2001, 0.35, 1),
    (1002, 2002, 0.12, 0),
], schema=['user_id', 'item_id', 'click_rate', 'label'])

df.write.mode("overwrite").parquet("hdfs://data/train_samples/dt=2024-01-15")

4.5 样本生成全流程校验清单

阶段 校验项 校验方法 通过标准
特征回溯 时间对齐 对比回溯时间戳与事件时间戳 差值 < 1 秒
特征回溯 空值比例 统计每个特征的 null 占比 < 5%
特征拼接 主键唯一性 检查拼接后主键重复数 重复率 < 0.1%
特征拼接 特征覆盖度 统计每个特征的非空样本数 覆盖度 > 95%
样本标注 标签分布 统计正负样本比例 正样本 > 1%
样本标注 标签泄漏 随机抽取 1000 条人工核查 无泄漏
样本存储 Schema 一致性 对比存储前后 Schema 完全一致
样本存储 数据完整性 对比存储前后样本总数 差异 < 0.01%
核心原则:样本生成流程的每个环节都可能引入偏差。建议在流水线中嵌入自动化校验节点,一旦校验失败立即告警,避免错误样本流入训练流程。