MLOps Zoomcamp 第5周模型监控实战:用 Evidently + Prefect + PostgreSQL + Grafana 搭建出租车时长预测的批量监控流水线
2026/9/14 4:09:43 网站建设 项目流程

MLOps Zoomcamp 第5周模型监控实战:用 Evidently + Prefect + PostgreSQL + Grafana 搭建出租车时长预测的批量监控流水线

【免费下载链接】mlops-zoomcampFree MLOps course from DataTalks.Club. Register here 👇🏼 to get notified about the next cohort项目地址: https://gitcode.com/GitHub_Trending/ml/mlops-zoomcamp

本篇技术文章以 MLOps Zoomcamp 2023 年第 5 周(Model Monitoring)作业文档 cohorts/2023/05-monitoring/homework.md 为主体,系统讲解如何为 ML 批量服务(batch service)搭建完整的监控链路:用 Evidently 计算数据质量与漂移指标、用 Prefect 组织任务流、用 PostgreSQL 存储时序指标、用 Grafana 可视化大盘。读完本文,你将掌握从数据集准备、指标扩展、Prefect 任务配置,到运行批量监控并保存 Grafana Dashboard 配置的完整实操方法,并能逐题对照完成该周作业。

1. 作业目标与监控体系全景

作业文档开宗明义:本作业的目标是让学习者熟悉 ML 批量服务的监控(monitoring for ML batch services),使用 PostgreSQL 数据库存储指标(metrics),并用 Grafana 进行可视化(visualization)

整个监控场景依托一个贯穿课程的基线模型:基于纽约 Green Taxi 数据训练一个出租车行程时长(duration_min)预测模型。该基线模型由 05-monitoring/baseline_model_nyc_taxi_data.ipynb 生成,其关键产出是两份工件:

  • models/lin_reg.bin:用 joblib 序列化的LinearRegression模型;
  • data/reference.parquet:作为漂移对比基准的参考数据集(1 月数据切出的验证集)。

模型使用的特征在基线 notebook 与监控脚本中保持一致(见 05-monitoring/evidently_metrics_calculation.py):

num_features = ['passenger_count', 'trip_distance', 'fare_amount', 'total_amount'] cat_features = ['PULocationID', 'DOLocationID'] column_mapping = ColumnMapping( prediction='prediction', numerical_features=num_features, categorical_features=cat_features, target=None )

从仓库结构看,整套监控栈的运行时依赖由 05-monitoring/requirements.txt 锁定,其中evidently==0.6.7是关键版本约束,其余核心依赖包括prefectpsycopg(PostgreSQL 驱动)、joblibpandasscikit-learn等。仓库同时在 05-monitoring/post-evidently-0.7/ 目录提供了一份适配 Evidently >= 0.7.0 的并行示例,供新版 API 用户参考。

2. 环境准备:Docker Compose 一键拉起三个服务

按 05-monitoring/README.md 的流程,准备工作分三步:

  1. 创建并激活虚拟环境(如python -m venv venv && source ./venv/bin/activate);
  2. 安装依赖:pip install -r requirements.txt
  3. 运行 baseline_model_nyc_taxi_data.ipynb,完成数据集下载、模型训练与参考数据集生成。

基线 notebook 中下载数据的方式是直接从 S3 公共 CDN 流式拉取 parquet 文件:

files = [('green_tripdata_2022-02.parquet', './data'), ('green_tripdata_2022-01.parquet', './data')] for file, path in files: url = f"https://d37ci6vzurychx.cloudfront.net/trip-data/{file}" resp = requests.get(url, stream=True) ...

作业中换成 2023 年 3 月数据时,只需把文件名替换为green_tripdata_2023-03.parquet,下载逻辑完全一致。

启动监控服务则只需在05-monitoring目录下执行:

docker-compose up

对应的编排文件是 05-monitoring/docker-compose.yml,它定义了三个服务与两张网络(front-tier/back-tier):

服务镜像端口作用
dbpostgres5432:5432存储指标数据,密码通过环境变量POSTGRES_PASSWORD: example注入
admineradminer8080:8080数据库管理工具,方便手工核查指标表
grafanagrafana/grafana-enterprise3000:3000可视化大盘,以只读方式挂载数据源与大盘配置,并挂载./dashboards到容器内/opt/grafana/dashboards

Grafana 服务的三个挂载点(见 docker-compose.yml)决定了后续“保存大盘配置该放哪里”的答案,这一点在第 5 节会展开。

3. 逐题精讲:五道作业题的实操路径

3.1 Q1:准备数据集 —— 下载 2023 年 3 月 Green Taxi 数据并确认 shape

作业要求从baseline_model_nyc_taxi_data.ipynb出发,下载2023 年 3 月的 Green Taxi 数据,用这批数据模拟出租车行程时长预测服务在生产环境中的持续使用。随后回答“下载数据的 shape 是多少行”,备选项为:

  • 72044
  • 78537
  • 62495
  • 54396

验证方法沿用基线 notebook 的模式:下载green_tripdata_2023-03.parquet./data后,pd.read_parquet(...).shape即可得到行数,再选择最接近的选项。这一步的意义在于:监控的“current data”必须来自与训练/参考期不同的时间段,才能真实暴露数据随时间演化的漂移。

3.2 Q2:扩展指标 —— 为fare_amount增加分位数监控

作业要求“扩展想监控的数据质量指标数量:任选一个指标,并为fare_amount列增加一个分位数(quantile=0.5)取值”,提示使用from evidently.metrics import ColumnQuantileMetric

理解这一题,先看仓库现有脚本监控了哪三项指标。05-monitoring/evidently_metrics_calculation.py 构建的 Report 包含:

report = Report(metrics=[ ColumnDriftMetric(column_name='prediction'), # 预测值漂移 DatasetDriftMetric(), # 数据集级漂移(漂移列数) DatasetMissingValuesMetric() # 缺失值占比 ])

在此基础上扩展时,典型写法是把ColumnQuantileMetric加入metrics列表,例如:

from evidently.metrics import ColumnQuantileMetric report = Report(metrics=[ ColumnDriftMetric(column_name='prediction'), DatasetDriftMetric(), DatasetMissingValuesMetric(), ColumnQuantileMetric(column_name='fare_amount', quantile=0.5), # 新增:车费中位数 # ... 以及你自己任选的那个指标 ])

分位数监控的工程价值在于:均值容易被极端值拉偏,而中位数(0.5 分位)能反映“典型一单”的价格水平,是发现计费口径变化、乘客结构变化的敏感信号。同时注意,requirements.txt中固定的是evidently==0.6.7ColumnQuantileMetric在该版本下可用;如果升级到 Evidently 0.7+,需要参考 post-evidently-0.7 目录中的新写法。

新增指标后,calculate_metrics_postgresql任务里通过report.as_dict()提取结果的逻辑也要同步扩展——现有脚本正是按索引从result['metrics'][i]['result']中读取drift_scorenumber_of_drifted_columnsshare_of_missing_values三个值(见 evidently_metrics_calculation.py),新指标同样以字典方式取出后写入数据库新列。

3.3 Q3:Prefect 任务流 —— 正确命名、重试与延迟

作业要求给 Prefect 任务加上“有意义名称、重试次数与延迟”,并以evidently_metrics_calculation.py为起点实现。四个候选装饰器为:

  • @task(retries_num=2, retry_seconds=5, task_name="calculate metrics")
  • @task(retries_num=2, retry_delay_seconds=5, name="calculate metrics")
  • @task(retries=2, retry_seconds=5, task_name="calculate metrics")
  • @task(retries=2, retry_delay_seconds=5, name="calculate metrics")

从源码看,仓库脚本的导入方式(evidently_metrics_calculation.py)是from prefect import task, flow,即 Prefect 2.x 风格——基线脚本中的@task/@flow均为裸装饰器。在 Prefect 2.x 的 API 中,@task的重试相关参数为retriesretry_delay_seconds,任务命名参数为name,因此正确写法是第四个:

@task(retries=2, retry_delay_seconds=5, name="calculate metrics") def calculate_metrics_postgresql(curr, i): ...

对批量监控系统而言,这两个参数不是可有可无的装饰:指标计算任务依赖模型加载、数据切片和数据库写入,任一环节偶发失败(如网络抖动、连接池耗尽)都会导致当批指标缺失。retries=2加上 5 秒间隔能在不打断整个 flow 的前提下自愈瞬时故障,而name参数则让 Prefect UI 中的任务节点具备可读性。

3.4 Q4:运行扩展后的监控 —— 2023 年 3 月逐日回补

完成指标扩展后,对 2023 年 3 月新批次运行监控,并回答“fare_amountquantile=0.5指标在 3 月期间(按天计算)的最大值是多少”,选项为 10 / 12.5 / 14 / 14.8。具体数值需按 3.2 节的扩展脚本实际运行后,从数据库查询 3 月每天的插入记录取得。

这里值得把“批量模拟”的运行机制讲透。evidently_metrics_calculation.py 的 flowbatch_monitoring_backfill完整模拟了“每天一个批次”的在线监控:

@flow def batch_monitoring_backfill(): prep_db() last_send = datetime.datetime.now() - datetime.timedelta(seconds=10) with psycopg.connect("host=localhost port=5432 dbname=test user=postgres password=example", autocommit=True) as conn: for i in range(0, 27): with conn.cursor() as curr: calculate_metrics_postgresql(curr, i) new_send = datetime.datetime.now() seconds_elapsed = (new_send - last_send).total_seconds() if seconds_elapsed < SEND_TIMEOUT: # SEND_TIMEOUT = 10 time.sleep(SEND_TIMEOUT - seconds_elapsed) ...

三个关键设计:

  1. 按日切片calculate_metrics_postgresql内部以begin为起点,用lpep_pickup_datetime切出第i天到第i+1天的数据(源码 L64-L84),先经model.predict生成prediction列,再跑 Evidently Report;
  2. 节流模拟SEND_TIMEOUT = 10(源码 L20)让每 10 秒写入一条“当日”指标,把 27 天的回补数据在几分钟内摊开成一条时间序列,便于在 Grafana 上观察曲线;
  3. 数据库自举prep_db任务先检查并创建test数据库,然后drop table if exists dummy_metrics并重建四列指标表(timestampprediction_driftnum_drifted_columnsshare_missing_values,见 源码 L23-L31)。扩展作业时需要给这张表加上新指标列(如fare_amount_quantile_05与自选指标列),并在插入语句中同步补齐字段。

3.5 Q5:保存 Grafana 大盘配置 —— 该放在哪个目录?

作业最后一步是:给大盘添加新指标的 Panel,自定义后点 “Save dashboard” 导出 JSON 配置并保存到本地,然后回答“配置文件应放在哪里”,选项为:

  • project_folder(05-monitoring)
  • project_folder/config(05-monitoring/config)
  • project_folder/dashboards(05-monitoring/dashboards)
  • project_folder/data(05-monitoring/data)

从仓库配置看,答案是05-monitoring/dashboards,证据链非常完整:

  1. docker-compose.yml 将宿主机./dashboards挂载进容器/opt/grafana/dashboards
  2. config/grafana_dashboards.yaml 定义了一个type: file的 provider,path: /opt/grafana/dashboardsupdateIntervalSeconds: 10,即 Grafana 每 10 秒扫描一次该目录,从其中的 JSON 文件自动加载/刷新大盘(仓库已内置 dashboards/data_drift.json 作为预配置示例);
  3. 该 provider 设置allowUiUpdates: falsedisableDeletion: false——从源码结构看,配置以文件为权威来源,UI 中的改动最终仍需落盘到该目录才能被版本化管理,这正是“Save dashboard 导出 JSON 后存入dashboards/”这一答案的设计意图。

config/目录放的是数据源与 provider 的 provisioning YAML,不是大盘 JSON;data/目录放 parquet 数据;根目录05-monitoring则过于宽泛。把大盘 JSON 与 provisioning 配置分目录管理,是 Grafana 文件级供给(file provisioning)的标准实践。

4. 数据源对接细节:Grafana 如何“看到”这些指标

Grafana 能查表的前提是 provisioning 的数据源与prep_db建的库严格对齐。config/grafana_datasources.yaml 的内容为:

datasources: - name: PostgreSQL type: postgres access: proxy url: db:5432 # 容器网络内的服务名 database: test # 对应 prep_db 创建的 test 库 user: postgres secureJsonData: password: 'example' # 对应 docker-compose 中 POSTGRES_PASSWORD jsonData: sslmode: 'disable'

对照 docker-compose.yml 的POSTGRES_PASSWORD: example与监控脚本的连接串host=localhost port=5432 user=postgres password=example(宿主机视角),三处凭据完全一致——容器内用服务名db:5432访问,宿主机脚本用localhost:5432访问同一个实例。这也是排查“Grafana 面板无数据”问题的第一切入点:数据源连的库名、账号密码必须与写库脚本一致。

5. 两条辅助路径:假指标冒烟测试与 Ad-hoc 调试

仓库还提供了两条与作业主线互补的路径,建议一并了解:

假指标脚本 05-monitoring/dummy_metrics_calculation.py:不依赖模型与 Evidently,仅以rand/uuid生成随机值写入结构相同的表,同样采用“每 10 秒一条”的节流循环。它的用途是冒烟测试——在指标逻辑还没跑通之前,先确认 PostgreSQL 与 Grafana 链路(连接、授权、面板渲染)没有问题。

调试 notebook 05-monitoring/debugging_nyc_taxi_data.ipynb:针对监控大盘报警后的 ad-hoc 排查,加载参考数据与模型后,截取某天的“问题数据”,分别运行:

test_suite = TestSuite(tests=[DataDriftTestPreset()]) test_suite.run(reference_data=ref_data, current_data=problematic_data, column_mapping=column_mapping) report = Report(metrics=[DataDriftPreset()]) report.run(reference_data=ref_data, current_data=problematic_data, column_mapping=column_mapping)

TestSuite给出“通过/未通过”的判定,Report给出可视化细节,二者配合可以定位是哪一列、何种统计量发生了漂移。这正好衔接作业文档第 4 题中“指标异动后如何下钻分析”的实际工作流。

6. 运行环境说明与注意事项

结合仓库现状,实操前需要确认的前提与限制:

  • Evidently 版本:主示例锁定evidently==0.6.7(requirements.txt);仓库 README 明确提示 Evidently 自 0.7.0 起有大规模更新,若使用新版应切换 post-evidently-0.7 目录下的示例;
  • Prefect 地位:课程 README(05-monitoring/README.md)注明 Prefect 在 2024 版课程中已非官方支持路线,但本 2023 作业及其脚本仍以 Prefect 为编排器,按其文档参数(retriesretry_delay_secondsname)作答即可;
  • 数据时效:基线脚本下载的是 2022 年 1/2 月数据并以此训练模型与参考集;作业要求把“current data”换成 2023 年 3 月,下载 URL 模式不变;
  • 作业提交:原文档附带的提交表单与截止时间(7 月 7 日 23:00 CEST)属于当年队列的时效信息,本文仅存档作业内容本身,提交请以当期队列通知为准。

7. 小结

这份 2023 年第 5 周作业的设计逻辑是一条完整的批量监控闭环:Q1 用新时间窗数据模拟生产输入 → Q2 用ColumnQuantileMetric证明你会按业务敏感度扩展指标 → Q3 用 Prefect 的retries/retry_delay_seconds/name参数保证采集任务可靠 → Q4 实际回补一个月并读出指标极值 → Q5 把 Grafana 大盘 JSON 落盘到05-monitoring/dashboards,与文件级 provisioning 机制对齐。仓库中 docker-compose.yml、evidently_metrics_calculation.py、grafana_datasources.yaml 与 data_drift.json 分别对应了链路中的服务编排、指标计算与入库、数据源对接、大盘供给四个环节,逐一对照阅读,即可完整复现并扩展这套监控体系。

【免费下载链接】mlops-zoomcampFree MLOps course from DataTalks.Club. Register here 👇🏼 to get notified about the next cohort项目地址: https://gitcode.com/GitHub_Trending/ml/mlops-zoomcamp

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询