首页
/ ML-For-Beginners 网页应用练习:用回归课的训练模型重建 Flask 预测 Web App(UFO 案例的模型替换实战)

ML-For-Beginners 网页应用练习:用回归课的训练模型重建 Flask 预测 Web App(UFO 案例的模型替换实战)

2026-09-06 15:20:18作者:冯梦姬Eddie

本篇围绕 ML-For-Beginners 课程第 3 周 "Build a Web App to use a ML Model" 的课后练习 Try a different model 展开:在完成 UFO 国别预测 Flask 应用后,用第 2 周 Regression 系列课程中训练过的模型(如南瓜价格线性回归、颜色 Logistic 回归)替换 UFO 应用中的模型,并同步修改应用的输入表单与输出映射。读完本篇,你将掌握"训练模型 → pickle 序列化 → 接入 Flask 路由 → 按训练方式调整输入特征"这条完整链路,并理解作业评分标准中对输入特征对齐与云端部署的要求。

UFO 目击数据:本练习基于该课程中的 UFO Flask 应用进行模型替换

1. 练习背景与原始要求

第 3 周的 Web App 课程(见 课程主文档)用 NUFORC 的 8 万条 UFO 目击记录训练了一个逻辑回归模型:输入 SecondsLatitudeLongitude 三个数值特征,预测目击国家(Country),再将其 pickle 为 ufo-model.pkl,用 Flask 的 /predict 路由暴露成网页表单。课程配套的可运行参考实现位于 solution 目录,其中模型加载路径为:

# 3-Web-App/1-Web-App/solution/web-app/app.py
model = pickle.load(open("../ufo-model.pkl", "rb"))

可以注意到,参考实现中 .pkl 文件放在 web-app/ 的上一级(即与 notebook.ipynb 同级),因此路径是 ../ufo-model.pkl;如果按课程正文的做法把模型与 notebook 放在同一目录并在 web-app 内部引用,则写成 "./ufo-model.pkl"。两种布局都成立,关键是运行 Flask 时的相对路径要指向真实文件

在此基础上,本练习(作业原文 及其中文语境的阿拉伯语译文 translations/ar/3-Web-App/1-Web-App/assignment.md)提出了核心要求:

在已经用训练好的回归模型构建了 Web 应用之后,请选用早期 Regression 课程中的某个模型来重做这个 Web 应用。你可以保留原有样式,也可以围绕南瓜(pumpkin)数据重新设计;务必把输入项改为与你模型的训练方式相匹配的特征

这句话里最关键的技术约束是最后一句:表单字段必须与模型的训练特征严格对齐,否则 model.predict() 要么报错(特征数量/类型不符),要么输出无意义的结果。下面以一个具体模型——第 2 周线性回归课训练的"南瓜价格预测"模型——完整走一遍改造过程。

2. 评分标准(Rubric)

作业原文给出了单一维度的三档评分表,做练习时应对照自检:

标准 优秀(Exemplary) 合格(Adequate) 待改进(Needs Improvement)
(整体表现) Web 应用按预期运行,并且已部署到云端 应用存在缺陷或出现非预期结果 应用不能正常工作

"部署到云端"是优秀档的额外要求,意味着本地 python app.py 跑通只是及格线;完整交付还应考虑把 ufo-model.pkl(或替换后的 .pkl)随应用一起打包、在服务器端执行 pip install -r requirements.txt 的流程。本地自测时可逐条验证:表单提交后 /predict 返回 200、预测文本正确渲染、输入越界值(如负数经纬度)时的表现是否符合预期。

3. 选定替换模型:南瓜价格的线性回归

Regression 系列课程(第 2 周)共覆盖工具、数据、线性回归与 Logistic 回归四节,其中可直接迁移到 Web 应用的有两条线:

from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split

# 特征需 reshape 成 N×1 的二维数组,因为只有一个输入特征
X = pie_pumpkins['DayOfYear'].to_numpy().reshape(-1, 1)
y = pie_pumpkins['Price']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)

该模型输入一个浮点特征(一年中的第几天),输出连续的美元价格——这恰好与 UFO 模型的"三整数特征 → 国家代码"形成鲜明对比,是本练习"修改输入以匹配训练方式"要求的最佳演示素材。

  • Logistic 回归(第 4 课)2-Regression/4-Logistic/README.md 中用同一份南瓜数据预测 Color(橙色/白色二分类)。选它做替换练习时,输出端可复用 UFO 应用"代码 → 可读文本"的映射模式(countries[output] 换成 ["orange", "white"][output]),输入端则要与该课程的特征集保持一致(如 PackageVarietyOrigin 等编码后的字段)。

南瓜数据:替换模型所用的 US-pumpkins.csv 对应的商品

下面以线性回归价格模型为主线展开改造。

4. 训练并 pickle 替换模型

UFO 课程中模型导出只用了三行(见 课程正文 的 "'pickle' your model" 小节,参考实现为 solution notebook):

import pickle
model_filename = 'ufo-model.pkl'
pickle.dump(model, open(model_filename, 'wb'))

model = pickle.load(open('ufo-model.pkl', 'rb'))
print(model.predict([[50, 44, -12]]))

对南瓜价格模型,流程完全一致,只有文件名与测试输入不同:

import pickle
model_filename = 'pumpkin-price-model.pkl'
pickle.dump(model, open(model_filename, 'wb'))

# 训练特征只有一个:DayOfYear(浮点数),输出为连续价格
model = pickle.load(open('pumpkin-price-model.pkl', 'rb'))
print(model.predict([[270]]))   # 例如预测出 10 月底一篮南瓜的价格

两点提醒:

  1. 课程正文明确提示 pickle 并非内建安全机制,在生产环境中不要反序列化来源不明的 .pkl 文件;本练习中模型由自己训练,风险可控。
  2. 序列化时模型的特征顺序被"冻结"进 .pkl 的输入约定里。线性回归模型只吃 [[DayOfYear]] 一行一列的二维数组,而 UFO 模型吃 [[Seconds, Latitude, Longitude]] 一行三列——特征数量与顺序就是接口契约,这正是作业要求"change the inputs to reflect your model's training method"的原因。

5. 改造 Flask 应用:表单、路由与类型转换

UFO 参考实现的 app.py(L15-L28)核心逻辑如下:

@app.route("/predict", methods=["POST"])
def predict():
    int_features = [int(x) for x in request.form.values()]
    final_features = [np.array(int_features)]
    prediction = model.predict(final_features)

    output = prediction[0]
    countries = ["Australia", "Canada", "Germany", "UK", "US"]
    return render_template(
        "index.html", prediction_text="Likely country: {}".format(countries[output])
    )

把它改造成南瓜价格应用时,有三处必须修改,且每处都对应一个真实的坑:

① 类型转换:int(x) 必须改为 float(x) 线性回归模型的训练数据是浮点数(DayOfYear 本身虽是整数,但 LinearRegressionpredict 返回连续价格),若沿用 int(x),用户输入 270.5 会直接抛 ValueError,且即使输入整数,int 转换也会把价格模型的浮点语义人为截断。改造后:

float_features = [float(x) for x in request.form.values()]
final_features = [np.array(float_features)]

② 表单字段:从三个输入框变为一个。index.html 模板 有三个输入项(secondslatitudelongitude,其中 seconds 限定了 min="0" max="60")。替换为价格模型后,表单只需保留一个"Day of year"输入框(min="1" max="366"),并把文案从"which country reported a UFO"改为价格预测语义。字段数量必须与 model.predict 期望的列数一致,多一个字段都会让 np.array 的列数与训练形状不匹配。

③ 输出端:删除"代码 → 文本"映射,改为数值格式化。 countries[output] 依赖预测结果是 0-4 的离散代码;价格模型的输出是连续值,应直接格式化:

prediction = model.predict(final_features)
return render_template(
    "index.html",
    prediction_text="Predicted price: ${:.2f}".format(prediction[0])
)

样式与目录结构可原样保留——作业的评分项只要求"runs as expected",而 requirements.txt 在两种模型下完全通用:

scikit-learn
pandas
numpy
flask

安装与启动方式与 UFO 应用一致:

cd web-app
pip install -r requirements.txt
python app.py    # 或 python3 app.py

启动后在浏览器表单中输入一天序号(如 270),即可看到价格预测。若返回 500,优先检查两处:.pkl 相对路径是否随 web-app/ 目录结构变化,以及表单字段数是否与训练特征数一致。

6. 进阶思考:模型该放在哪里训练?

课程正文在 UFO 应用之后还留了一个挑战题:不走"notebook 训练 → pickle 导入 Flask"的路线,而是直接在 Flask 应用内(例如新增一个 /train 路由)加载 CSV、清洗并训练模型。对本练习同样适用——如果你选用 Logistic 回归的南瓜颜色模型,也可以尝试在 /train 路由里完成 US-pumpkins.csv 的加载与 LogisticRegression.fit。两条路线的权衡在于:

  • notebook 训练 + pickle 导入(本练习主路线):模型版本与 Web 服务解耦,可离线复现,UFO 课程的参考实现(solution 目录)即此模式;
  • 应用内训练:部署简单、无外部 .pkl 依赖,但每次启动/训练都消耗服务器资源,且训练逻辑散落在路由代码里,不利于独立评估(课程正文要求你自行总结 pros and cons)。

无论选哪条路线,验收口径都以第 2 节的评分表为准:本地行为符合预期、输出与训练目标一致,优秀档再补一步云端部署。完成本练习后,你实际上演练了课程 Review 环节提出的核心问题——模型与应用之间的接口由训练方式决定,换模型必然伴随输入表单与输出解析的联动修改,这正是"训练者"与"消费者"之间必须对齐的契约。

登录后查看全文
热门项目推荐
相关项目推荐