MLOps Zoomcamp 2022 第 1 周作业实战:基于 NYC FHV 出租车数据训练乘车时长预测模型
【免费下载链接】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 2022 期第 1 模块(Introduction)的作业 homework.md:以纽约市 For-Hire Vehicle(FHV)出租车 2021 年 1—2 月行程数据为素材,走一遍“下载数据 → 计算时长 → 剔除离群值 → 处理缺失值 → 独热编码 → 训练线性回归 → 跨月验证”的端到端流程。读完后你可以独立完成该作业的全部 6 道题,并理解每个步骤的代码细节与常见陷阱,为后续模块的实验追踪与部署打下基础。
1. 作业目标与数据来源
作业目标与模块示例保持一致:训练一个简单的模型来预测一次行程的时长(duration,单位:分钟),正如 01-intro/README.md 中 “1.3 (Optional) Training a ride duration prediction model” 一节所做的那样,示例代码见 duration-prediction.ipynb。
与模块示例使用的 Green Taxi(绿色出租车)数据不同,2022 期作业换用了同一 NYC 出租车数据集中的 “For-Hire Vehicle Trip Records”(FHV,租车/商务车行程记录),并且有两个关键注意事项(原文档明确要求):
- 需要下载2021 年 1 月和 2 月的数据,1 月作为训练集,2 月作为验证集;
- 必须选用 “For-Hire Vehicle Trip Records”,而不是“High Volume For-Hire Vehicle Trip Records”(高量版本),两者字段与统计口径不同。
数据为 parquet 格式(NYC 出租车数据在 2021 年前后已从 CSV 迁移到 parquet,01-intro/README.md 中特别给出了读取 parquet 的视频说明),文件命名为fhv_tripdata_2021-01.parquet、fhv_tripdata_2021-02.parquet,解题 notebook homework.ipynb 正是按此路径读取:
df = pd.read_parquet('./data/fhv_tripdata_2021-01.parquet')依赖库方面,解题 notebook 只用到 pandas、scikit-learn 三件套:
import pandas as pd import seaborn as sns from sklearn.feature_extraction import DictVectorizer from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_squared_error2. Q1:读取数据并核对记录数
第一步是读入 1 月数据并回答“1 月共有多少条记录”。四个选项为:1054112 / 1154112 / 1254112 / 1354112。
old_len = len(df) # 记录过滤前的总行数,供后续统计“丢了多少行”使用结合解题 notebook 的中间输出可以印证:经过后续 1–60 分钟过滤后,数据剩余1,109,826行(见X_train.shape的输出(1109826, 525)),被剔除的离群值只有数万行量级,因此总量应在 115 万行一档,正确答案是第二个选项1,154,112。
3. Q2:计算 duration 变量
duration定义为下车时间减上车时间,并换算为分钟:
df['duration'] = df.dropOff_datetime - df.pickup_datetime df['duration'] = df.duration.dt.total_seconds() / 60两点细节值得注意:
- FHV 数据的列名是小驼峰
pickup_datetime/dropOff_datetime,与 Green Taxi 数据的lpep_pickup_datetime/lpep_dropoff_datetime(见 duration-prediction.ipynb)不同,换数据集时列名必须跟着换; dt.total_seconds() / 60把时间差(timedelta)转为分钟浮点数。
题目问“1 月的平均行程时长是多少”,选项为 15.16 / 19.16 / 24.16 / 29.16。解题 notebook 中该单元格保存了执行结果:
df.duration.mean() # 输出:19.1672240937939即正确答案为19.16。
4. 数据准备:剔除时长离群值
检查duration的分布(可用sns.histplot等,示例 notebook 曾用sns.distplot绘制预测值与真实值分布的叠加图)可以发现存在离群值。作业要求只保留 duration 在 1 到 60 分钟(含边界)的记录,并回答“丢掉了多少条记录”:
df = df[(df.duration >= 1) & (df.duration <= 60)].copy()注意两点实现细节:
- 布尔索引之后加
.copy(),避免 pandas 的 SettingWithCopyWarning,也保证后续fillna、astype原地修改的是独立副本; - 边界是闭区间(
>=与<=),这与模块示例中的过滤条件一致。
解题 notebook 保存的特征矩阵形状为(1109826, 525),即过滤后剩1,109,826行。由 Q1 的总量 1,154,112 可以推断,这一步约丢弃44,286条记录(总量减去过滤后行数)。
5. Q3:处理缺失值并统计占比
模型只使用两个特征:上车地点 ID(PUlocationID)和下车地点 ID(DOlocationID)。FHV 数据中这两列存在大量缺失值,作业要求用-1填充:
categorical = ['PUlocationID', 'DOlocationID'] df[categorical] = df[categorical].fillna(-1).astype('int')题目问“填充 NA 之后,上车地点 ID 中-1的占比是多少”,选项为 53% / 63% / 73% / 83%。解题 notebook 未保存该单元格的输出,但 FHV 数据的地点 ID 稀疏度明显高于出租车数据(出租车数据缺失极少,模块示例甚至不需要 fillna),结合选项档位可以推断正确答案为53%——即超过一半的 FHV 行程没有上车地点 ID。这一步的占比也可以理解为后续独热编码中-1这一列在特征矩阵里所代表的样本比例。
6. Q4:独热编码与特征矩阵维度
作业要求对两个地点 ID 做 one-hot 编码,步骤固定为三步(这也是课程反复使用的模式):
- 把 DataFrame 转成字典列表;
- 用
DictVectorizer拟合(fit); - 得到特征矩阵。
df[categorical] = df[categorical].astype('str') train_dicts = df[categorical].to_dict(orient='records') dv = DictVectorizer() X_train = dv.fit_transform(train_dicts) X_train.shape # 输出:(1109826, 525) len(dv.feature_names_) # 输出:525关键陷阱:DictVectorizer遇到数值型键会做 label encoding 而不是 one-hot,所以必须先astype('str')把 ID 转成字符串(解题 notebook 先fillna(-1).astype('int')再统一astype('str'),最终键为'0'、'1'… 以及'-1'这样的字符串)。2025 期作业 homework.md 在 Q4 中也专门提示了这一点(“remember to re-cast the ids to strings - otherwise it will label encode them”)。
矩阵维度选项为 2 / 152 / 352 / 525 / 725,正确答案是525:训练集(1 月)中出现过的不同上车/下车地点 ID(含填充的-1)去重后共 525 个,每个 ID 独占一列。dv.feature_names_会给出形如DOlocationID=-1、DOlocationID=102、PUlocationID=...的列名,可用于解释模型权重。
7. Q5:训练线性回归并计算训练集 RMSE
使用默认参数的普通线性回归(Plain Linear Regression):
y_train = df.duration.values lr = LinearRegression() lr.fit(X_train, y_train) y_pred = lr.predict(X_train) mean_squared_error(y_train, y_pred, squared=False) # 输出:10.528519107212292注意:这里用的是旧版 APImean_squared_error(..., squared=False)来得到 RMSE;较新的 scikit-learn 版本已直接提供root_mean_squared_error(模块示例 duration-prediction.ipynb 导入的正是后者),两者结果等价。训练集 RMSE 选项为 5.52 / 10.52 / 15.52 / 20.52,正确答案为10.52,即模型在训练集上的平均绝对偏差约为 10.5 分钟。
8. Q6:在 2021 年 2 月验证集上评估模型
验证集是 2 月的 FHV 数据。为了保证训练/验证两侧预处理口径一致,解题 notebook 把 Q1–Q4 的步骤封装成了read_data函数:
def read_data(filename): df = pd.read_parquet(filename) df['duration'] = df.dropOff_datetime - df.pickup_datetime df['duration'] = df.duration.dt.total_seconds() / 60 df = df[(df.duration >= 1) & (df.duration <= 60)].copy() df[categorical] = df[categorical].fillna(-1).astype('int').astype('str') return df df_val = read_data('./data/fhv_tripdata_2021-02.parquet')验证侧的特征转换有一个必须区分的细节:用dv.transform而不是dv.fit_transform——词表(525 列)必须沿用训练集学到的,否则 2 月新出现的地点 ID 会额外增加列,导致与模型权重形状不匹配:
val_dicts = df_val[categorical].to_dict(orient='records') X_val = dv.transform(val_dicts) y_pred = lr.predict(X_val) y_val = df_val.duration.values mean_squared_error(y_val, y_pred, squared=False) # 输出:11.014283211122269验证集 RMSE 选项为 6.01 / 11.01 / 16.01 / 21.01,正确答案为11.01。验证 RMSE(11.01)仅比训练 RMSE(10.53)略高,说明这个极简模型没有明显过拟合——这也正是后续模块要用 MLflow 做实验追踪、用 Kinesis/Lambda 做部署的基线模型。
9. 答案汇总
| 题目 | 问题 | 选项 | 答案 | 依据 |
|---|---|---|---|---|
| Q1 | 1 月 FHV 记录数 | 1054112 / 1154112 / 1254112 / 1354112 | 1,154,112 | 由 notebook 过滤后剩余 1,109,826 行印证档位 |
| Q2 | 1 月平均时长(分钟) | 15.16 / 19.16 / 24.16 / 29.16 | 19.16 | notebook 输出 19.1672 |
| — | 过滤 1–60 分钟丢弃的记录数 | 开放题 | 约 44,286 条 | 1,154,112 − 1,109,826(推断) |
| Q3 | 上车地点 ID 缺失占比 | 53% / 63% / 73% / 83% | 53% | 推断 |
| Q4 | 特征矩阵列数 | 2 / 152 / 352 / 525 / 725 | 525 | notebook 输出(1109826, 525) |
| Q5 | 训练集 RMSE | 5.52 / 10.52 / 15.52 / 20.52 | 10.52 | notebook 输出 10.5285 |
| Q6 | 验证集 RMSE | 6.01 / 11.01 / 16.01 / 21.01 | 11.01 | notebook 输出 11.0143 |
原始文档提示:如果你的结果与选项不完全一致,选择最接近的一项即可(parquet 版本、pandas 版本差异都可能导致末位小数偏差)。作业提交截止时间为 2022 年 5 月 24 日(周二)23:00 CET,提交表单链接在原文档 homework.md 中给出;官方解题材料为解题 notebook homework.ipynb 及文档中附带的解题视频。
10. 与模块示例及后续 cohort 的对照
- 模块示例:duration-prediction.ipynb 使用 Green Taxi 2021 年 1—2 月数据,流程与本作业几乎一一对应(读 parquet → 计算 duration → 1–60 分钟过滤 → DictVectorizer → LinearRegression),额外还引入了数值特征
trip_distance、交叉特征PU_DO(上车_下车地点组合),并用Lasso(0.01)与LinearRegression做对比(验证集 RMSE 分别约为 7.48 与 11.17),最后用pickle.dump((dv, lr), f_out)把向量化器和模型一起序列化保存。本作业刻意只用两个类别特征,是示例的精简版; - 2025 期作业:cohorts/2025/01-intro/homework.md 换成了 Yellow Taxi 2023 年 1—2 月数据,问题改为“列数”“标准差”“保留比例”,但 Q4–Q6 的编码、训练、验证流程完全相同,可对照练习;
- 后续模块的衔接:本作业产出的
(DictVectorizer, LinearRegression)组合正是第 4 周批量打分服务(如 cohorts/2022/04-deployment/homework/batch.py)和第 6 周 Kinesis 流式管道(如 cohorts/2022/03-orchestration/homework.py)所加载的模型形态,第 1 周作业实质上是整门课程部署链路的起点。
11. 实践要点清单
- 下载数据时认准For-Hire Vehicle Trip Records(非 High Volume 版),只取 2021-01 与 2021-02 两个文件;
- duration 用
(dropOff_datetime - pickup_datetime).dt.total_seconds() / 60计算,FHV 列名是小驼峰; - 离群值过滤用闭区间
1 <= duration <= 60,布尔索引后加.copy(); - 缺失 ID 先
fillna(-1)再astype('int').astype('str'),确保 DictVectorizer 走 one-hot 而非 label encoding; - 训练集用
dv.fit_transform,验证集用dv.transform,词表必须来自训练集; - RMSE 在新版 scikit-learn 中用
root_mean_squared_error替代mean_squared_error(squared=False); - 结果与选项不一致时选最接近值,差异通常来自数据快照或库版本。
【免费下载链接】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),仅供参考