3、流式机器学习管道概述:离线训练 vs 在线推理、流式推理架构、模型版本管理
在构建流式机器学习管道时,需要明确区分离线训练与在线推理的职责边界。离线训练侧重于利用历史批量数据构建高精度模型,而在线推理则要求模型在毫秒级延迟内对实时数据做出预测。本章将系统阐述这两种模式的差异、流式推理的典型架构以及模型版本管理的核心策略。
3.1 离线训练 vs 在线推理
离线训练与在线推理在数据来源、计算模式、延迟要求、资源消耗等方面存在本质区别。下表从多个维度对比两者的关键差异:
| 对比维度 | 离线训练 | 在线推理 |
|---|---|---|
| 数据来源 | 历史批量数据(HDFS、S3、数据湖) | 实时流数据(Kafka、Kinesis、Pulsar) |
| 计算模式 | 批处理(Spark Batch、MapReduce) | 流处理(Spark Structured Streaming、Flink) |
| 延迟要求 | 分钟级到小时级 | 毫秒级到秒级 |
| 资源消耗 | 高吞吐、可弹性伸缩 | 低延迟、持续运行 |
| 模型更新频率 | 按天/周/月触发 | 实时或近实时(增量更新) |
| 数据量级 | TB 到 PB 级 | MB 到 GB 级/秒 |
| 容错机制 | 任务重试、Checkpoint | 状态一致性、Exactly-Once 语义 |
关键原则:离线训练与在线推理应解耦运行。训练管道负责模型生产与验证,推理管道负责模型加载与预测。两者通过模型注册中心(如 MLflow Model Registry)进行版本化衔接,避免训练任务影响推理稳定性。
3.2 流式推理架构
流式推理架构的核心是将训练好的模型部署到流处理引擎中,对实时数据流进行逐条或微批预测。典型的流式推理架构包含数据接入、特征工程、模型推理、结果输出四个阶段。
下图展示了基于 Spark Structured Streaming 的流式推理架构:
实践建议:在 Spark Structured Streaming 中,推荐使用
mapGroupsWithState 或 flatMapGroupsWithState 算子实现带状态的模型推理。对于深度学习模型,可结合 UDF 加载 ONNX 或 TensorFlow SavedModel 格式,避免 Python 进程间通信开销。
3.3 模型版本管理
模型版本管理是流式机器学习管道中衔接训练与推理的关键环节。没有完善的版本管理,模型漂移监控将无法准确追溯模型行为变化,回滚操作也会变得混乱。
下表列出了模型版本管理中的核心概念与推荐实践:
| 概念 | 说明 | 推荐工具/方法 |
|---|---|---|
| 模型注册 | 将训练完成的模型及其元数据(算法、超参数、训练数据范围)注册到中央仓库 | MLflow Model Registry、DVC |
| 版本标签 | 为每个模型版本分配语义化版本号(如 v1.2.3)或时间戳标签 | SemVer 规范 + Git Tag |
| 阶段管理 | 模型经历 Staging → Production → Archived 三个阶段 | MLflow 阶段标记、自定义 Pipeline |
| 模型回滚 | 当新版本模型出现漂移或性能下降时,快速切换回稳定版本 | 蓝绿部署、金丝雀发布 |
| 版本对比 | 对比不同版本模型在相同数据集上的指标差异 | MLflow 实验对比、自定义 Dashboard |
下图展示了模型版本管理的生命周期流程:
常见陷阱:在流式推理中,模型版本切换不能中断正在处理的流数据。建议采用“双缓冲”策略:新版本模型加载完成后,先进行影子测试(Shadow Testing),确认无误后再切换主推理路径。避免直接覆盖模型文件导致推理线程读取到不完整的模型。
3.4 流式推理代码示例
以下代码展示了如何在 Spark Structured Streaming 中加载 MLflow 模型并进行实时推理:
import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.functions._
import org.mlflow.spark.autologging.MlflowAutologEventPublisher
import org.mlflow.tracking.MlflowClient
// 初始化 Spark Session
val spark = SparkSession.builder()
.appName("StreamingInference")
.config("spark.sql.streaming.schemaInference", "true")
.getOrCreate()
// 从 MLflow Model Registry 加载模型(指定版本)
val modelUri = "models:/fraud_detection_model/Production"
val model = spark.read.mlflow.load(modelUri)
// 定义流式数据源
val streamDF = spark.readStream
.format("kafka")
.option("kafka.bootstrap.servers", "localhost:9092")
.option("subscribe", "transactions")
.load()
.selectExpr("CAST(value AS STRING) as json")
.select(from_json(col("json"), schema).as("data"))
.select("data.*")
// 特征工程与推理
val predictions = model.transform(streamDF)
.withColumn("prediction_timestamp", current_timestamp())
.withColumn("model_version", lit("v2.1.0"))
// 输出结果到 Kafka
val query = predictions.selectExpr("to_json(struct(*)) AS value")
.writeStream
.format("kafka")
.option("kafka.bootstrap.servers", "localhost:9092")
.option("topic", "predictions")
.option("checkpointLocation", "/tmp/checkpoint")
.outputMode("append")
.start()
query.awaitTermination()
版本切换策略:在实际生产环境中,建议将模型版本号作为配置参数从外部传入(如环境变量或配置中心),避免硬编码。当漂移监控触发版本切换时,只需更新配置并重启流式查询,即可无缝切换到新版本模型。