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 回溯校验要点
- 时间对齐:确保回溯时间戳与事件时间戳在同一时区,避免因时区偏移导致特征错位。
- 空值处理:回溯不到特征时,需明确填充策略(默认值 / 前向填充 / 丢弃样本)。
- 版本一致性:同一批次样本应使用同一特征版本回溯,避免因特征更新导致样本间不一致。
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%),需考虑采样或加权策略。
- 时间截断:对于窗口未结束的样本,需明确处理方式(丢弃 / 标记为未观测)。
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% |
核心原则:样本生成流程的每个环节都可能引入偏差。建议在流水线中嵌入自动化校验节点,一旦校验失败立即告警,避免错误样本流入训练流程。