ML-For-Beginners 网页应用练习:用回归课的训练模型重建 Flask 预测 Web App(UFO 案例的模型替换实战)
本篇围绕 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 路由 → 按训练方式调整输入特征"这条完整链路,并理解作业评分标准中对输入特征对齐与云端部署的要求。
1. 练习背景与原始要求
第 3 周的 Web App 课程(见 课程主文档)用 NUFORC 的 8 万条 UFO 目击记录训练了一个逻辑回归模型:输入 Seconds、Latitude、Longitude 三个数值特征,预测目击国家(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 应用的有两条线:
- 线性回归(第 3 课):在 2-Regression/3-Linear/README.md 中,课程用美国南瓜批发数据 US-pumpkins.csv 训练了
DayOfYear → Price的价格预测模型,并给出了完整的 Scikit-learn 代码:
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]),输入端则要与该课程的特征集保持一致(如Package、Variety、Origin等编码后的字段)。
下面以线性回归价格模型为主线展开改造。
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 月底一篮南瓜的价格
两点提醒:
- 课程正文明确提示 pickle 并非内建安全机制,在生产环境中不要反序列化来源不明的
.pkl文件;本练习中模型由自己训练,风险可控。 - 序列化时模型的特征顺序被"冻结"进
.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 本身虽是整数,但 LinearRegression 的 predict 返回连续价格),若沿用 int(x),用户输入 270.5 会直接抛 ValueError,且即使输入整数,int 转换也会把价格模型的浮点语义人为截断。改造后:
float_features = [float(x) for x in request.form.values()]
final_features = [np.array(float_features)]
② 表单字段:从三个输入框变为一个。
原 index.html 模板 有三个输入项(seconds、latitude、longitude,其中 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 环节提出的核心问题——模型与应用之间的接口由训练方式决定,换模型必然伴随输入表单与输出解析的联动修改,这正是"训练者"与"消费者"之间必须对齐的契约。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00

