ML-For-Beginners 实战:用南瓜回归模型重建 Flask Web 应用(“Try a different model“作业指南)
2026/9/10 15:41:04 网站建设 项目流程

ML-For-Beginners 实战:用南瓜回归模型重建 Flask Web 应用("Try a different model"作业指南)

【免费下载链接】ML-For-Beginners12 weeks, 26 lessons, 52 quizzes, classic Machine Learning for all项目地址: https://gitcode.com/GitHub_Trending/ml/ML-For-Beginners

本篇技术指南围绕 ML-For-Beginners 课程第 3 单元(3-Web-App)作业任务展开:当你已经用 UFO 数据集训练逻辑回归模型并构建了一个可用的 Flask Web 应用后,如何从之前的回归课程中挑选另一个模型,重新设计这个 Web 应用,并正确调整输入以匹配新模型的训练方式。读完本文,你将掌握"模型消费端"的完整迁移方法论——包括数据清理、模型 pickle 序列化、Flask 表单与预测路由的联动,以及如何让输入输出契约跟随模型一起更换,最终交付一个可以部署到云端的新 Web 应用。

一、作业任务解读:本质是"换模型,而不是换框架"

本作业(translations/el/3-Web-App/1-Web-App/assignment.md,英文原版见 3-Web-App/1-Web-App/assignment.md)的要求非常明确:

现在你已经使用一个训练好的回归模型构建了一个 Web 应用,请使用之前回归课程中的一个模型来重新设计这个应用。你可以保持原有风格,也可以重新设计样式以反映南瓜数据。务必修改输入字段,使其反映你所选模型的训练方法。

这句话拆解出三个硬性验收点:

  1. 必须换模型:UFO 应用里的模型不能再直接沿用,需要从第 2 单元(2-Regression)的回归课程中挑选替代模型;
  2. 必须换输入:原表单的三个输入(SecondsLatitudeLongitude)与 UFO 模型的特征一一对应;换了模型,表单输入就必须对应新模型的特征,这是最容易出错、也是作业特别提醒的环节;
  3. 必须能运行并部署:评估标准明确要求应用"按预期运行并部署到云端"。

对应的评估标准(Rubric)整理如下:

标准优秀(Exemplary)合格(Adequate)需改进(Needs Improvement)
Web 应用功能与部署应用按预期运行,并已部署到云端应用存在缺陷或出现意外结果应用无法正常运行

也就是说,仅仅"本地能跑"只能算合格,完整得分的前提是部署上云。本文后续会给出一个可操作的部署检查清单(云部署需注意关闭调试模式等)。


二、先回顾原应用:UFO 预测 Web App 是怎么构建的

要"重新设计"这个应用,首先必须吃透原应用的构建链路。原应用完整教程见 3-Web-App/1-Web-App/README.md,完整可运行源码见 solution 目录。整条链路分四步:清理数据 → 训练模型 → pickle 序列化 → Flask 消费

2.1 为什么模型要"序列化"?

生产环境里,训练和推理往往是分离的:数据科学团队在 Notebook 里训练模型,应用团队在 Web 服务里消费模型。本课程使用两个 Python 工具完成衔接:

  • Flask:微框架,提供路由、模板渲染等 Web 基础能力,用来搭建消费模型的页面;
  • Pickle:Python 自带的序列化/反序列化模块。pickle.dump将模型对象"压平"成二进制文件(后缀.pkl),pickle.load再还原。⚠️ 注意:pickle 本身不安全,不要反序列化来路不明的文件——这在换模型时同样适用,请只加载你自己训练的.pkl

2.2 数据清理:从 80,000 条 UFO 目击记录到训练集

本课使用 NUFORC(美国国家 UFO 报告中心)收集的约 8 万条目击记录,数据位于 3-Web-App/1-Web-App/data/ufos.csv,包含citystatecountryshapelatitudelongitude等列。在配套的 notebook.ipynb 中按以下步骤处理:

import pandas as pd import numpy as np ufos = pd.read_csv('./data/ufos.csv') ufos.head()
# 只保留建模需要的四列,并查看 Country 的唯一值 ufos = pd.DataFrame({'Seconds': ufos['duration (seconds)'], 'Country': ufos['country'], 'Latitude': ufos['latitude'], 'Longitude': ufos['longitude']}) ufos.Country.unique()
# 丢弃空值,只保留目击时长 1-60 秒的记录,减少数据规模 ufos.dropna(inplace=True) ufos = ufos[(ufos['Seconds'] >= 1) & (ufos['Seconds'] <= 60)] ufos.info()
# LabelEncoder 按字母顺序把国家文本编码为数字 from sklearn.preprocessing import LabelEncoder ufos['Country'] = LabelEncoder().fit_transform(ufos['Country']) ufos.head()

编码后数据形如(Seconds/Country/Latitude/Longitude):

2 20.0 3 53.200000 -2.916667 3 20.0 4 28.978333 -96.645833 14 30.0 4 35.823889 -80.253611 23 60.0 4 45.582778 -122.352222 24 3.0 3 51.783333 -0.783333

换模型的第一个启发:文本类别 → 数字的转换(LabelEncoder)是在训练前完成的,新模型的表单提交数值必须与编码后的语义一致。

2.3 训练模型:输入三个特征,输出国家编号

from sklearn.model_selection import train_test_split Selected_features = ['Seconds','Latitude','Longitude'] X = ufos[Selected_features] y = ufos['Country'] X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)
from sklearn.metrics import accuracy_score, classification_report from sklearn.linear_model import LogisticRegression model = LogisticRegression() model.fit(X_train, y_train) predictions = model.predict(X_test) print(classification_report(y_test, predictions)) print('Predicted labels: ', predictions) print('Accuracy: ', accuracy_score(y_test, predictions))

该模型准确率约95%——并不意外,因为Country与经纬度天然强相关。作者也明确提示:这个模型"并不革命性",它的意义在于演示"从清理数据 → 导出模型 → 在 Web 中消费"的完整工程链路。

2.4 pickle 序列化与验证

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]]))

输出'3',即英国(UK)的国家编码。仓库中已训练好的模型文件位于 solution/ufo-model.pkl。

2.5 Flask 应用:三个文件撑起一个预测页面

在原 README 的指导下,web-app目录结构如下:

web-app/ static/ css/ templates/ notebook.ipynb ufo-model.pkl

① requirements.txt(作用类似 JavaScript 项目的package.json,声明依赖):

scikit-learn pandas numpy flask

安装:cd web-app后执行pip install -r requirements.txt。仓库中的真实依赖清单见 solution/web-app/requirements.txt。

② static/css/styles.css:黑色背景的简约深色样式,核心代码见 solution/web-app/static/css/styles.css:

body { width: 100%; height: 100%; font-family: 'Helvetica'; background: black; color: #fff; text-align: center; letter-spacing: 1.4px; font-size: 30px; } input { min-width: 150px; } .grid { width: 300px; border: 1px solid #2d2d2d; display: grid; justify-content: center; margin: 20px auto; } .box { color: #fff; background: #2d2d2d; padding: 12px; display: inline-block; }

③ templates/index.html:注意两个 Jinja2 模板语法——{{ }}插值由后端注入的变量,url_for('predict')生成路由地址,表单以 POST 提交到/predict路由。完整代码见 solution/web-app/templates/index.html:

<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>🛸 UFO Appearance Prediction! 👽</title> <link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}"> </head> <body> <div class="grid"> <div class="box"> <p>According to the number of seconds, latitude and longitude, which country is likely to have reported seeing a UFO?</p> <form action="{{ url_for('predict')}}" method="post"> <input type="number" name="seconds" placeholder="Seconds" required="required" min="0" max="60" /> <input type="text" name="latitude" placeholder="Latitude" required="required" /> <input type="text" name="longitude" placeholder="Longitude" required="required" /> <button type="submit" class="btn">Predict country where the UFO is seen</button> </form> <p>{{ prediction_text }}</p> </div> </div> </body> </html>

④ app.py:模型消费的核心逻辑,见 solution/web-app/app.py:

import numpy as np from flask import Flask, request, render_template import pickle app = Flask(__name__) model = pickle.load(open("./ufo-model.pkl", "rb")) @app.route("/") def home(): return render_template("index.html") @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]) ) if __name__ == "__main__": app.run(debug=True)

这段代码的运行流程是:加载依赖并启动应用 →pickle.load导入模型 → 首页路由渲染index.html;当表单 POST 到/predict时,按表单字段顺序收集数值并转为 numpy 数组,交给model.predict,再把预测出的国家编号映射回可读的国家名,最后回填模板。

💡 提示:debug=True让 Flask 在代码改动后自动重载,便于本地调试,但生产环境严禁开启(存在安全隐患)。本作业要求"部署到云端",部署前务必移除或改为debug=False

关键工程经验:用 Flask + pickle 消费模型的难点不在框架,而在弄清楚模型预测所需的输入数据形状(shape)——它完全取决于模型训练时的特征。原模型训练用了 3 个特征,所以表单有 3 个输入框;这正是作业要求"修改输入以反映模型训练方法"的根本原因。


三、挑选替代模型:从第 2 单元回归课程取材

作业指定"使用之前回归课程中的一个模型"。第 2 单元(2-Regression)提供了多个候选:

  • 3-Linear 线性回归:用南瓜价格数据集(2-Regression/data/US-pumpkins.csv),以MonthDayOfYearVariety等特征预测每蒲式耳南瓜价格(数值型输出),是"换模型重做"最贴合的选择——因为作业明确提到"重新设计以反映南瓜数据";
  • 4-Logistic 逻辑回归:同样是南瓜数据,但预测的是二分类目标(Color:橙色/白色),特征包括City NamePackageVarietyOriginItem Size等;
  • 其他课程:1-Tools、2-Data 侧重于工具与数据可视化。

线性回归预测南瓜价格为例(对应课程 2-Regression/3-Linear/README.md),模型的输入输出契约与 UFO 模型对比如下:

维度UFO 原模型(逻辑回归)南瓜新模型(线性回归)
输入特征SecondsLatitudeLongitude(3 个数值)Month/DayOfYearVarietyPackageItem Size
输出国家编号(0-4,类别)每蒲式耳价格(连续数值)
输入处理全部为数值,直接int()转换含文本类别特征,需先LabelEncoder或 one-hot 编码
展示映射countries[output]映射国家名直接展示数值,或格式化为货币

从源码结构看,solution/web-app/app.py 中int_features = [int(x) for x in request.form.values()]隐含假设"表单所有字段都是可直接转 int 的数值"。一旦换成含类别特征的南瓜模型,这一行就必须重写——这正是作业强调"改变输入"的落点。


四、迁移实现:把 UFO 应用改造成南瓜价格预测应用

下面给出基于 solution 源码风格的重建方案,重点演示"输入契约"如何跟随模型变化。注意:新模型需要先按 3-Linear 课程 的 notebook 流程训练(课程指出其数据已预清理,并计算了DayOfYear列),再将模型 pickle 为pumpkin-model.pkl放到 web 应用目录。

4.1 修改 requirements.txt

保持四个基础依赖不变(scikit-learn、pandas、numpy、flask),若新模型使用了Pipeline等额外模块也无需新增依赖,它们都在 scikit-learn 内。

4.2 重写 app.py:加载模型、改造路由

核心变化有三处:模型加载路径表单数值收集逻辑(处理类别特征)、结果展示(价格而非国家名)。

import numpy as np from flask import Flask, request, render_template import pickle from sklearn.preprocessing import LabelEncoder app = Flask(__name__) # 加载新模型(自行训练的南瓜价格模型) model = pickle.load(open("./pumpkin-model.pkl", "rb")) @app.route("/") def home(): return render_template("index.html") @app.route("/predict", methods=["POST"]) def predict(): # 注意:这里需要按新模型的训练方式组织特征。 # 若模型包含对文本类别(如 Variety)的 LabelEncoder, # 需先读取表单中的文本值并做同样的编码转换。 form = request.form month = int(form["month"]) day_of_year = int(form["day_of_year"]) variety = LabelEncoder().fit(["PIE TYPE", "FAIRYTALE"]).transform([form["variety"]])[0] final_features = [np.array([month, day_of_year, variety])] prediction = model.predict(final_features) price = prediction[0] return render_template( "index.html", prediction_text="Predicted price per bushel: ${:.2f}".format(price), ) if __name__ == "__main__": app.run(debug=False) # 部署到云端前必须关闭 debug

上面LabelEncoder().fit(...)仅用于说明编码思路——实际实现必须复用训练阶段保存的同一个 encoder(训练时一并 pickle,或在模型中用Pipeline封装),否则编码不一致会导致预测错乱。这是换模型后最隐蔽的坑。

4.3 修改 index.html:输入字段对应新特征

原表单的secondslatitudelongitude三个输入框需要替换为南瓜模型的特征字段,例如月份、一年中的第几天、品种等:

<form action="{{ url_for('predict')}}" method="post"> <input type="number" name="month" placeholder="Month (1-12)" required="required" min="1" max="12" /> <input type="number" name="day_of_year" placeholder="Day of year (1-365)" required="required" min="1" max="365" /> <input type="text" name="variety" placeholder="Variety (e.g. PIE TYPE)" required="required" /> <button type="submit" class="btn">Predict pumpkin price</button> </form> <p>{{ prediction_text }}</p>

样式层(styles.css)可完全保留深色风格,也可以按作业提示"重新设计以反映南瓜数据"调整配色——样式不影响模型契约,属于自由发挥项。

4.4 本地运行与验证

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

浏览器打开本地地址,填入表单值即可看到预测结果。验证要点:用训练集里已知的样本数据(如某月、某品种的真实价格)回填,检查输出是否与训练时的预测一致——这一步能快速暴露"输入契约错位"的问题。

4.5 部署到云端(达到"优秀"等级的关键)

按 Rubric,"已部署到云端"才得满分。通用做法是把web-app目录推送到支持 Python 的 PaaS 平台(如 Azure App Service、Render、Railway 等),平台会根据requirements.txt自动安装依赖并启动app.py。部署前务必检查:

  1. app.run(debug=False)(或使用平台注入的端口环境变量);
  2. ufo-model.pkl/pumpkin-model.pklapp.py的相对路径在部署环境同样有效(建议使用os.path.join基于脚本目录构造绝对路径);
  3. requirements.txt完整列出运行依赖。

五、完成度自检清单(对照 Rubric 自查)

提交前对照评估标准逐项检查:

  • 新模型来自第 2 单元回归课程(线性/逻辑回归皆可,本文以南瓜价格线性回归为例);
  • 表单输入已改为新模型的特征,且与训练时的特征顺序、编码方式完全一致;
  • app.pypredict路由能正确处理新模型输出(数值价格 vs. 类别编号的展示方式不同);
  • 本地python app.py运行无报错,用已知样本验证预测结果合理;
  • 已部署到云端,线上地址可访问,未开启debug=True
  • 样式可选:重新设计以反映南瓜数据主题。

六、进阶挑战:在 Flask 应用内直接训练模型

原 README 的 🚀 Challenge 提出了一个延伸方向:不经过 Notebook 和 pickle,直接在 Flask 应用里训练模型——即在数据清理完成后,为应用增加一个train路由,在应用进程内完成model.fit(),预测路由直接复用内存中的模型对象。

这种方法的好处是省去了"训练环境 → 部署环境"的模型搬运和版本同步问题;代价是每次启动应用都要重新训练(耗时随数据量增长)、Web 进程与训练负载耦合(不利于扩展),且调试与实验迭代体验不如 Notebook。这也是作业要求之外值得思考的架构权衡:模型应该"驻留在应用内"还是"存放在云端由 API 提供",会直接影响整个应用的技术选型。


七、小结

本作业的核心不在"换一个模型跑通",而在于理解模型输入输出契约(contract)对 Web 应用形态的约束。回顾整条链路:UFO 应用是"3 个数值特征 → 类别编号",南瓜价格应用则是"混合特征(含类别)→ 连续数值",差异直接体现在表单字段、编码逻辑和结果展示三处。通过 solution 目录的完整源码对照练习,你就能掌握"训练与消费解耦"的通用能力——这也是数据科学团队与 Web 工程团队协作时最关键的接口知识。

【免费下载链接】ML-For-Beginners12 weeks, 26 lessons, 52 quizzes, classic Machine Learning for all项目地址: https://gitcode.com/GitHub_Trending/ml/ML-For-Beginners

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

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

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

立即咨询