做毕设选这个题目的时候,我其实有点犹豫。机器学习、决策树、深度学习、Django,几个词拼在一起看似很“热门”,但担心会不会只是一个技术堆砌的大杂烩。做完之后回头想,这个项目真正值钱的地方不在于用了多少算法,而在于它完整覆盖了一条从数据理解、特征工程、模型对比到Web系统落地的工业级流水线。你要做毕业设计、找实习作品、或者想入门数据科学又不想只停留在跑通Notebook,这个项目都能给你一个非常扎实的落脚点。
我能用一句话说清楚这东西是什么:用机器学习模型对肥胖成因做预测分析,并基于决策树、集成模型和深度学习算法做横向对比,最后用Django把模型封装成可以在浏览器里直接使用的Web系统。它能做什么?采集一个人的饮食、运动、遗传等特征,预测肥胖等级,还能解释哪些因素影响最大。适合谁?计算机、数据科学、软件工程方向的同学,尤其想走机器学习工程路线但手里缺一个“能演示、能答辩、能写论文”项目的朋友。
1. 毕设选题不能只看热度,更要看“能不能做完”
很多人选“基于机器学习的XX预测系统”这类题目,是为了蹭热度,但真正动手才发现数据集难找、特征太乱、算法结果像玄学、Web端又不会集成。我为什么说“肥胖成因分析与对比”这个方向是相对稳妥的?因为它在数据、算法、系统三个维度上都刚好卡在“有挑战但可控”的位置。
1.1 选题逻辑:一个题目同时覆盖数据、算法、系统的闭环
肥胖成因预测本质上是一个有监督分类问题,目标是根据个体特征预测其肥胖等级,常见分类有 Insufficient_Weight、Normal_Weight、Overweight_Level_I、Obesity_Type_I 等。这个场景在公开数据领域有现成的、质量不错的数据集,比如UCI机器学习库中的Obesity Dataset,样本量不大(2000多行),特征维度适中(16列左右),既有连续变量又有类别变量,还带明显的类别不平衡。这些特点恰好让它成为教学、毕设场景的“黄金数据”。
更妙的是,肥胖成因本身存在多因素耦合:遗传(家族史)、饮食习惯、运动频率、作息、交通方式都会影响结果。它不是简单的线性关系,所以用决策树、随机森林、甚至深度神经网络来做都有发挥空间,且不同模型的表现差异能直观展示“为什么需要算法对比”。这比单纯拿鸢尾花数据集做的“假项目”要有说服力得多。
1.2 系统目标和技术路线:不只是“算法跑个分”
我给自己定的目标是:做一个能实际使用的“个人肥胖风险分析系统”,而不是交一个训练好的模型文件完事。系统要能完成四件事:
- 用户录入个人基本信息和生活习惯;
- 后台调用训练好的机器学习模型,实时输出肥胖等级预测;
- 通过决策树可视化或特征重要性,解释“为什么是这个结果”;
- 展示不同算法(决策树、随机森林、XGBoost、MLP)在同一测试集上的性能对比。
技术路线也很直接:Python 3.8 + Django 3.2 + pandas + scikit-learn + TensorFlow/Keras + 前端Bootstrap。有人会问,深度学习在大数据场景里不是很吃资源吗?我这里的“深度学习”并没有上大模型,而是针对表格数据设计了一个MLP多层感知机,参数量十万以内,普通笔记本就能跑。这部分后面会细说。
2. 数据处理:机器学习里的“七分靠数据”不是玩笑
无论你打算用决策树还是深度学习,数据没洗干净,后面所有模型都是自欺欺人。这个项目里的原始数据是英文编码的,比如字段FAF、FCVC、CH2O,第一次打开可能看得一脸懵。需要先理解每个字段的业务含义,再决定怎么清洗和构造特征。
2.1 字段理解与业务映射
我用的UCI肥胖数据集字段大致如下:
| 字段 | 含义 | 类型 |
|---|---|---|
| Gender | 性别 | 类别 |
| Age | 年龄 | 连续 |
| Height / Weight | 身高(m)、体重(kg) | 连续 |
| family_history_with_overweight | 是否有肥胖家族史 | 二分类 |
| FAVC | 是否频繁吃高热量食物 | 二分类 |
| FCVC | 吃蔬菜的频率(1-3分) | 离散 |
| NCP | 正餐次数 | 连续 |
| CAEC | 两餐之间是否吃东西 | 类别 |
| CH2O | 每天饮水升数 | 连续 |
| SCC | 是否监测卡路里摄入 | 二分类 |
| FAF | 每周运动频率 | 连续 |
| TUE | 每天使用电子设备时间 | 连续 |
| CALC | 饮酒频率 | 类别 |
| MTRANS | 常用交通方式 | 类别 |
| NObeyesdad | 体重等级标签 | 分类目标 |
这里有个很重要的业务理解:Height和Weight可以合并计算出BMI,而BMI和最终的肥胖等级标签存在极强的相关关系。如果直接把BMI作为一个特征输入模型,模型很容易“作弊”——几乎不用看其他特征就能靠BMI猜出肥胖等级,准确率会虚高。但在实际应用中,BMI本身就是肥胖等级划分标准的一部分,这么做会导致数据泄露,掩盖真实成因。所以我在特征工程中会选择不使用BMI,或者有意保留原始身高体重,让模型从身高体重、生活习惯、遗传因素的组合中学到因果关系。这一点在论文里是很好的讨论素材。
2.2 清洗与特征工程实操
清洗步骤不复杂,但一个都不能少:
- 检查缺失值,通常这个数据集没有缺失,但必须验证;
- 类别变量编码:二分类用LabelEncoder,多分类用OneHotEncoder,避免给类别强加数字大小顺序;
- 连续变量标准化:决策树不要求标准化,但MLP和K近邻需要;所以我都做了一遍标准化并保存scaler对象,供预测时复用;
- 划分训练集和测试集:按80/20,设定random_state=42保证可复现;
- 类别不平衡处理:肥胖等级中Normal_Weight和Obesity_Type_I样本偏多,而Insufficient_Weight较少,直接用accuracy容易被“多数类”迷惑。这里我用SMOTE做少数类过采样,注意只在训练集上做,不要在测试集上做,否则会数据泄漏。
2.3 最容易犯的错误:特征顺序不一致
这个坑我踩得很重。训练模型时,我用pandas的DataFrame传入X_train,模型记住了特征的顺序;但在Django里接收前端表单数据后,如果手动拼dict,稍不留神字段顺序就和训练时不一致,那模型预测结果就是一团乱码。解决办法是:训练时把特征列表单独保存为一个变量,比如feature_columns = [...],用joblib保存特征列表文件;在Django预测时,先按这个列表创建空DataFrame,再逐一填充,顺序绝对不能临时拼。
3. 从决策树到深度学习:算法选型与对比策略
这一部分是项目的核心灵魂。很多人把多个模型跑一遍,比个AUC就完事,但“为什么选这个模型”“怎么调参”“结果差异说明了什么”才是答辩时老师最想听的。
3.1 决策树分类器:可解释性拉满的基线模型
肥胖成因分析最看重解释性,比如“爱吃高热量食物且缺乏运动的人,更可能肥胖”这种规则,决策树天然能输出。我选用的CART决策树,核心思想是每次选一个特征和切分点,让切分后子节点的Gini系数下降最大;递归下去,长出一棵树。
决策树有个好处:不需要太多预处理,类别编码后就能直接训练,计算也快。但裸的决策树非常容易过拟合,深度设到5的时候甚至能记住噪声。我实验里,默认参数的决策树准确率约70%,但决策边界很碎;限制max_depth=5、min_samples_split=10后,准确率稍有下降,但泛化能力明显增强。这也能引出“树模型剪枝”的讨论。
补充说明:用Graphviz导出树的可视化结构,在Django前端展示成图片,效果比干巴巴的特征重要性柱状图好得多。
3.2 随机森林与XGBoost:用集成思路甩开单棵树的瓶颈
单棵决策树不稳定,稍微改一点样本结果就变。随机森林的思路是**“三个臭皮匠顶个诸葛亮”**:训练很多棵用随机子样本、随机特征子集构建的树,投票决定结果。这样方差变小,能有效抑制过拟合。
XGBoost则是用梯度提升的思想去“纠错”:每一棵新树都在拟合前面所有树预测结果的残差。在表格数据上,XGBoost往往是性价比最高的选择。我对比下来:
| 模型 | 准确率 | F1加权 | AUC宏平均 | 训练耗时 |
|---|---|---|---|---|
| 决策树(剪枝) | 0.78 | 0.77 | 0.92 | 秒级 |
| 随机森林(500棵树) | 0.86 | 0.85 | 0.98 | 秒级 |
| XGBoost(默认) | 0.89 | 0.88 | 0.99 | 秒级 |
| MLP(两隐层) | 0.88 | 0.87 | 0.98 | 分钟级 |
这里有个值得注意的现象:XGBoost和MLP表现差距不大,甚至在个别类别上树模型更稳。原因在于表格数据有大量离散化、数值稀疏的特征,树模型对特征变换的适应机制更好;深度学习更擅长图像、语言这种稠密高维数据。但MLP的加入能在论文里引出“神经网络在结构化数据上的边界”这一深度讨论,这是拿高分的关键。
3.3 用Keras搭一个能跑通的MLP模型
深度学习部分不用太华丽,我用了一个三层MLP:
from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense, Dropout, BatchNormalization from tensorflow.keras.callbacks import EarlyStopping model = Sequential([ Dense(64, activation='relu', input_shape=(X_train.shape[1],)), BatchNormalization(), Dropout(0.3), Dense(32, activation='relu'), BatchNormalization(), Dropout(0.2), Dense(7, activation='softmax') ]) model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy']) early_stop = EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True) history = model.fit(X_train_scaled, y_train_encoded, validation_split=0.15, epochs=200, batch_size=32, callbacks=[early_stop], verbose=1)注意三个细节:
- 标准化:一定要用训练集的scaler去transform测试集,不能重新fit,否则信息泄漏;
- EarlyStopping:早停是防止过拟合最实用的手段,看val_loss而非val_accuracy;
- 类别的编码:标签需要变成0到6的整数序列,预测结果再映射回原来的类别名。
3.4 模型对比怎么才说服力
对比不能只看准确率,因为样本不平衡时准确率会骗人。我最终报告里放了四类指标:准确率、加权精确率、加权召回率、加权F1,还有一个“宏平均AUC”。另外做了5折交叉验证,报告均值±标准差,避免因为固定训练测试集划分产生偏差。这样做完之后,你会发现不同模型在不同肥胖等级上的表现差异很有意思,比如深度学习对Obesity_Type_II的召回率可能更高,而XGBoost在整体平衡性上更优。
4. Django系统实现:把模型从Jupyter搬到Web
算法跑完只是开始。有相当多同学卡在“模型怎么让人用起来”这一步,而Django正好解决了这个难题。项目里我用Django写了一个MVT架构的Web应用,让用户通过表单输入数据,后台调用模型,返回结果和解释。
4.1 系统页面与交互设计
我做了一个很朴素但完整的系统,包含这些页面:
- 首页:项目介绍、算法对比图表总览;
- 预测页面:表单包含所有输入字段,每个字段带提示;
- 结果页:显示预测的肥胖等级、各类别概率条形图、决策树路径解释;
- 对比页面:用柱状图/折线图展示各模型评估指标。
前端用了Bootstrap和ECharts,图表数据由Django视图序列化为JSON传给页面。这里关键是接口设计要稳:前端只管收集表单,后端统一校验和封装,最后返回一个JSON结构,包括prediction、probability、explanation。
4.2 模型加载与预测的完整流程
我在ml_utils/predictor.py里封装了统一预测接口:
import joblib import numpy as np import pandas as pd from tensorflow.keras.models import load_model class ObesityPredictor: def __init__(self): self.feature_columns = joblib.load('models/feature_columns.pkl') self.scaler = joblib.load('models/scaler.pkl') self.smote_encoder = joblib.load('models/label_encoder.pkl') self.xgb_model = joblib.load('models/xgb_model.pkl') self.mlp_model = load_model('models/mlp_model.h5') def _build_frame(self, form_dict): df = pd.DataFrame([{col: form_dict.get(col, 0) for col in self.feature_columns}]) df = df[self.feature_columns] return df def predict(self, form_dict): df = self._build_frame(form_dict) df_scaled = self.scaler.transform(df) xgb_prob = self.xgb_model.predict_proba(df)[0] mlp_prob = self.mlp_model.predict(df_scaled.reshape(1, -1))[0] # 这里可以融合或分别展示,我选择展示两个模型结果供对比 final_class = int(np.argmax(xgb_prob)) return self.smote_encoder.inverse_transform([final_class])[0]几个实操要点:
- 加载模型放在视图外层,避免每次请求都重新加载;在Django中我把它放到
apps.py的ready()方法里,或者用全局单例。 - Keras的HDF5模型文件可能比较大,如果部署到服务器,要注意内存。测试时用
load_model('mlp_model.h5')没问题,但生产环境建议转成TensorFlow SavedModel格式。 - 异常处理:如果前端传了非法数值(比如身高写成了负数),后端要捕获并返回友好提示,不能直接抛500。
4.3 表单校验与用户输入体验
这个项目的数据字段多,如果用户填错了很麻烦。我用了Django Form框架,每个FloatField设置min_value和max_value,对于选择类的字段用ChoiceField,限制可选范围。比如FAF(每周运动频率)范围0-10,TUE(设备使用时间)范围0-24。同时前端加了简单的HTML5校验,但后端校验不能省,因为接口是可以被直接调用的。
4.4 部署层面的小建议
如果只是本地演示,python manage.py runserver 0.0.0.0:8000就够了。但如果要部署到云服务器或者答辩现场演示,最好用gunicorn + nginx。模型文件夹不要提交到Git仓库,用django-environ配置路径。静态文件和数据库都要按Django正式部署的方式收集,避免同学演示时图片加载不出来。
5. 论文写作与答辩现场的加分细节
毕设项目做完之后,论文和答辩是另一场硬仗。这部分不是技术问题,而是“怎么把技术价值讲清楚”的问题。
5.1 论文结构怎么搭
我的论文目录大体是这样:
- 绪论:背景、国内外研究现状、研究内容;
- 相关技术介绍:决策树、随机森林、深度学习、Django框架;
- 数据获取与预处理:数据集来源、清洗、特征工程;
- 肥胖预测模型构建:四个模型的原理、参数设置、训练过程;
- 模型对比与结果分析:指标评估、混淆矩阵、特征重要性分析;
- 系统设计与实现:需求分析、架构设计、功能模块实现;
- 总结与展望。
这里有一个常见的误区:不要大篇幅抄算法原理,导师更关心你在项目里怎么用、怎么调、怎么选择。比如第三章写“我为什么保留身高体重而不是直接计算BMI”,第四章写“决策树剪枝前后准确率的变化”,这些才是有效内容。
5.2 常见问题与排查技巧实录
这个项目跑通不难,但调试过程有几个坑值得记录:
- sklearn版本兼容问题:用joblib保存的模型,如果换了一台电脑且sklearn版本不匹配,加载可能报错。解决办法是固定环境,写requirements.txt并注明版本号。
- Django加载模型后内存占用高:MLP模型文件大概几百KB,但TensorFlow运行时占了几个GB的内存。如果服务器内存紧张,可以只在预测页面所在进程加载模型,或将Keras模型转为
.tflite(不过这会稍微改变预测结果)。 - 预测结果全部是同一个类别:一般是特征顺序错乱或标准化不对,把训练特征列表打出来逐列核对,看预测分布是否合理。
- ECharts图表中文乱码:检查后端返回的JSON是否经过ensure_ascii=False,前端html是否设置
<meta charset="utf-8">。 - 表单输入0被当成缺失:Django表单里
FloatField(required=False)配合clean_方法要注意,0是合法值,不能当空值过滤掉。 - 决策树可视化乱码/报错:Graphviz需要本机安装dot程序,不只是pip install graphviz;如果只是展示用,也可以直接用matplotlib画树节点。
5.3 答辩演示的加分操作
答辩时别只对着PPT念,我会建议做一个“现场预测”环节:准备一份典型用户数据,比如“20岁、女性、有家族史、每周运动不到1次、爱吃快餐、饮水少”,当场在系统里输入,展示预测结果为Overweight_Level_I并指出主要成因。这个操作比任何图表都有冲击力,也直观体现了系统的实用价值。
另外,单独准备一张特征重要性排序图,大多是FAF(运动频率)、FCVC(蔬菜频率)、CAEC(餐饮习惯)排前面。答辩时老师问“哪个因素影响最大”,你能直接回答,并解释这和医学常识是吻合的,项目就真正立住了。
最后再说一个实际项目里的小技巧:训练模型时,别只用一个random_state。我一开始固定为42,后面改成2025后结果波动,心态差点崩了。后来学乖了,无论是划分数据集还是模型初始化,都设好随机种子,同时跑几个不同的随机状态,看均值和方差。这样既稳得住实验结果,又能让答辩老师觉得你懂“可复现性”这件事。这个项目的后续还可以继续扩展,比如把年龄、性别分别做分层分析,或者采集真实问卷数据做增量训练,加上权限管理做成多用户系统,发展空间很足。如果你也正在为毕业设计挠头,这个题目真的值得认真做一回。