
简介面向计算机、软件工程、医学信息类专业的毕业生及机器学习与Web开发初学者这份毕业设计论文围绕Python与Django框架完整梳理了糖尿病预测系统从需求分析到测试交付的全流程。全文涵盖绪论、相关技术Python与Django、系统需求分析与概要/详细设计、数据预处理与特征选择、模型构建与训练含逻辑回归、决策树等算法及评估指标优化、Django框架下的系统实现、系统测试与性能分析等内容目录结构清晰章节安排规范。尤其是对数据清洗、缺失值处理、特征筛选、模型评估和Django视图/模板/数据库设计的阐述能为毕业设计写作和开发实践提供较完整的参考。资源为单个docx论文文档压缩包大小约31KB已有超过240人学习使用适合需要快速搭建论文结构、了解预测系统实现思路的学生直接借鉴。1. 为什么一个糖尿病预测系统值得你拆开看把一份基于 Python 和 Django 的糖尿病预测系统毕业设计当作工程样本去读能同时摸到两条线机器学习模型从数据预处理到训练评估的完整链路以及 Django 从 Model 到 View 再到 Template 的 Web 落地方式。这个系统本身并不复杂核心是拿 PIMA 印第安人糖尿病数据集这类结构化临床数据用 scikit-learn 训练分类模型再通过 Django 包一层用户注册、数据录入、风险查询的界面。适合正在做 Python 毕业设计、想复现 sklearn Django 组合的开发者也适合想了解模型如何被 Web 应用调用的后端工程师。我拆这份设计稿时重点放在了三处特征工程怎么做才不让模型虚高、模型训练完如何被 Django 视图稳定调用、以及上线前哪些细节容易被忽略。2. 数据预处理与特征工程模型精度的地基2.1 先弄清数据从哪来、长什么样论文里提到的数据来源包括医院临床记录、生物指标数据这类场景下最常用的公开数据集是 PIMA Indians Diabetes Database。它包含 768 条女性患者记录字段覆盖怀孕次数、口服葡萄糖耐量试验 2 小时血浆葡萄糖浓度、舒张压、三头肌皮褶厚度、2 小时血清胰岛素、BMI、糖尿病谱系函数、年龄以及结果标签 Outcome0 表示无糖尿病1 表示有糖尿病。字段数量不多但缺失值和零值污染明显非常适合用来演示预处理的价值。在真实项目中数据收集阶段就要定义好字段规范。常见做法是建一张患者临床指标表字段类型、单位、取值范围都预先约定避免后续模型训练时做无谓的类型转换。对于这份毕业设计直接用 pandas 读取 CSV 即可import pandas as pd df pd.read_csv(diabetes.csv) print(df.shape) # (768, 9) print(df.isnull().sum()) # 初始没有显式 NaN print(df.describe())逻辑说明先确认数据规模与字段分布再检查是否存在显式缺失。PIMA 数据集的特殊性在于部分字段的 0 值在医学上不可能出现这些 0 值本质上就是缺失值需要在清洗阶段统一处理。参数说明shape用于确认行列数isnull().sum()统计每个字段的空值数量describe()输出均值、标准差、最小值、最大值等统计量用来初步判断数据分布是否合理。2.2 缺失值处理不要直接 drop也不要盲目 fillna对于 Glucose、BloodPressure、SkinThickness、Insulin、BMI 这几个字段0 值在医学上不合理应该被视为缺失。处理方式有两种如果缺失比例低可以用中位数填充如果缺失比例高则要考虑删除该字段或使用模型预测填充。PIMA 数据集中 Insulin 字段的 0 值占比接近一半直接删除字段或整行删除都不划算常见做法是保留字段但用中位数填充。import numpy as np cols_with_zero [Glucose, BloodPressure, SkinThickness, Insulin, BMI] for col in cols_with_zero: df[col] df[col].replace(0, np.nan) # 按标签分组填充中位数避免引入分布偏移 for col in cols_with_zero: df[col] df[col].fillna(df.groupby(Outcome)[col].transform(median))逻辑说明先对不合理零值做掩码替换再按 Outcome 分组计算中位数填充。按标签分组填充比全局填充更精细保留了两类样本各自的特征分布。参数说明replace(0, np.nan)将 0 转换为空值groupby(Outcome)[col].transform(median)返回与原始索引对齐的中位数序列transform保证填充后的 DataFrame 索引不变。也可以像论文中提到的那样用插值方法填充比如df.interpolate()做线性插值。但要注意插值法对时间序列数据效果较好对截面医疗数据并不比中位数填充更有优势我一般会优先用分组中位数。2.3 特征选择相关性分析加上嵌入法筛选论文里提到了过滤法、包裹法和嵌入法。对这个数据集最直接的做法是先看特征与目标的相关性再训练一个随机森林或逻辑回归模型借助特征重要性做二次筛选。以随机森林为例from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import train_test_split X df.drop(Outcome, axis1) y df[Outcome] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) rf RandomForestClassifier(n_estimators200, random_state42) rf.fit(X_train, y_train) importance pd.Series(rf.feature_importances_, indexX.columns).sort_values(ascendingFalse) print(importance)逻辑说明随机森林的特征重要性是基于不纯度下降计算的每个特征的重要性分数代表它对分类决策的贡献程度。这里先用默认参数训练一次用来观察特征排序而不是直接作为最终模型。参数说明n_estimators200控制树的数量越大越稳定但训练越慢random_state42固定随机种子保证可复现stratifyy保证训练集和测试集的正负样本比例一致对类别不平衡数据很重要。特征选择时通常会保留 Glucose、BMI、Age、DiabetesPedigreeFunction 这几个头部特征。相关性分析可以用df.corr()[Outcome].sort_values()来辅助判断两者结果通常一致。2.4 标准化与样本划分防止特征尺度干扰模型糖尿病数据中各字段的量纲差异很大Insulin 的数值范围可能是 0 到 800而 Age 只有 21 到 81。对基于距离的模型或需要梯度下降的模型来说量纲不一致会让训练过程偏向大数值特征。常见做法是 Z-score 标准化。from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test) # 用字典或 numpy 数组保存 scaler供 Django 视图后续调用 import joblib joblib.dump(scaler, scaler.pkl)逻辑说明fit_transform在训练集上计算均值和标准差并完成转换transform在测试集上复用训练集的统计量不能重新计算。这里最容易被忽略的是测试集和后续真实预测数据都必须用同一个 scaler 转换否则模型输入的分布和训练时不匹配。参数说明StandardScaler的默认行为是每个特征减去均值再除以标准差joblib.dump将 scaler 保存为文件供 Django 后端加载使用。3. 模型训练与调优从基线模型到最佳实践3.1 算法选型先跑基线再谈优化论文提到了朴素贝叶斯、支持向量机、随机森林。实际项目我一般会再加逻辑回归和梯度提升树。逻辑回归作为线性基线随机森林和梯度提升树作为非线性基线SVM 视样本量决定是否纳入。先把几个模型用默认参数跑一遍观察准确率、召回率和 F1 分数再做有针对性的调优。算法准确率默认参数召回率F1训练耗时逻辑回归0.7790.5710.628 1s朴素贝叶斯0.7530.5940.624 1s随机森林0.7660.5740.6193s 左右SVMRBF0.7920.5870.6442s 左右梯度提升树0.7790.5980.6465s 左右以上数值是基于同一种数据划分的大致表现具体数值会因随机种子和数据预处理方式不同而波动。观察这张表能发现默认参数下各模型准确率差距不大真正的差距体现在召回率和 F1。对糖尿病预测场景来说漏诊的代价高于误诊所以调优目标应该优先提高召回率而不是单纯追求准确率。from sklearn.linear_model import LogisticRegression from sklearn.svm import SVC from sklearn.ensemble import GradientBoostingClassifier from sklearn.metrics import classification_report models { lr: LogisticRegression(max_iter1000), svm: SVC(kernelrbf, probabilityTrue), gbt: GradientBoostingClassifier(random_state42) } for name, model in models.items(): model.fit(X_train_scaled, y_train) y_pred model.predict(X_test_scaled) print(f {name} ) print(classification_report(y_test, y_pred, target_names[No, Yes]))逻辑说明统一用标准化后的数据训练classification_report输出精度、召回率、F1 和宏平均/加权平均。这里没有对超参数做任何调整目的是拿到一个公平的基线。参数说明LogisticRegression(max_iter1000)提高迭代上限避免不收敛SVC(probabilityTrue)让 SVM 输出概率值Django 端展示风险百分比时需要它GradientBoostingClassifier(random_state42)固定随机种子保证结果可复现。3.2 交叉验证与网格搜索调参要看着泛化误差来调论文里提到了交叉验证和网格搜索。这里的关键是网格搜索的评估指标要选对。默认的 scoring 是准确率但在医疗场景下我们会希望模型对正类样本更敏感所以一般把 scoring 设为recall或f1。另一个坑是网格搜索要在训练集内部再做交叉验证而不是直接在测试集上调参。from sklearn.model_selection import GridSearchCV, StratifiedKFold param_grid { n_estimators: [100, 200], max_depth: [3, 5, 7], min_samples_split: [2, 5], } cv StratifiedKFold(n_splits5, shuffleTrue, random_state42) grid GridSearchCV( RandomForestClassifier(random_state42), param_grid, scoringrecall, cvcv, n_jobs-1 ) grid.fit(X_train_scaled, y_train) print(grid.best_params_) print(grid.best_score_)逻辑说明GridSearchCV会遍历参数组合在每一折上训练并验证模型然后返回在指定 scoring 下表现最好的参数组合。这里StratifiedKFold保证每折的正负样本比例与整体一致避免某折全是没有糖尿病的样本。参数说明scoringrecall表明我们更关注正类召回率n_jobs-1启用全部 CPU 核心加速搜索cv5做 5 折交叉验证。调优后的随机森林通常可以把召回率提升到 0.65 以上但要注意不要为了召回率牺牲太多精度否则系统会大量误报让医生对预测结果失去信任。实际项目中要在业务层面定一个可接受的误诊率阈值再反推模型参数。另一种做法是使用GridSearchCV的refitTrue这样搜索完成后会自动用全部训练数据重新训练一个最佳模型这个模型可以直接保存供 Django 调用。3.3 模型评估与阈值选择准确率不是唯一指标分类模型输出的是预测概率predict()默认把 0.5 当作正负类划分阈值。但对糖尿病预测场景0.5 未必是最优阈值。可以通过 ROC 曲线找到更合适的阈值比如把阈值调到 0.3让更多高风险样本被识别出来。from sklearn.metrics import roc_curve, roc_auc_score y_proba grid.predict_proba(X_test_scaled)[:, 1] fpr, tpr, thresholds roc_curve(y_test, y_proba) # 找到约登指数最大时对应的阈值 J tpr - fpr best_idx np.argmax(J) best_threshold thresholds[best_idx] print(fbest threshold: {best_threshold:.3f}) print(fAUC: {roc_auc_score(y_test, y_proba):.3f})逻辑说明predict_proba输出每个样本属于正类的概率roc_curve根据不同的阈值计算假正率与真正率约登指数tpr 减去 fpr最大处对应的阈值就是理论上的最优划分点。参数说明[:, 1]取第二列也就是正类的概率roc_auc_score衡量模型排序能力AUC 在 0.8 左右说明模型有较好的区分度。拿到最佳阈值后Django 端在展示预测结果时就不要再简单比较 0.5 了而是用y_proba best_threshold判断是否为高风险。这个细节论文里没有展开但实际做系统时非常关键因为阈值直接影响医生看到的判断结果。4. Django 框架下的系统实现让模型跑成 Web 服务4.1 项目初始化与配置用 Django 搭建这个系统第一步是创建项目和应用。论文里提到 Django 基于 MVC 模式实际 Django 的实现方式是 MVTModel-View-TemplateModel 对应数据模型View 对应业务逻辑Template 对应页面渲染。初始化命令如下django-admin startproject diabetes_web cd diabetes_web python manage.py startapp predictor逻辑说明startproject生成项目骨架startapp创建独立应用。这里的predictor应用负责核心的预测功能包括数据模型、视图和模板。接下来需要在settings.py中注册应用并配置数据库。开发阶段直接用 SQLite 即可生产环境再切换 PostgreSQL。配置示例# settings.py INSTALLED_APPS [ django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages, django.contrib.staticfiles, predictor, ] DATABASES { default: { ENGINE: django.db.backends.sqlite3, NAME: BASE_DIR / db.sqlite3, } } # 静态文件和模板文件路径 STATIC_URL static/ STATICFILES_DIRS [BASE_DIR / static]逻辑说明INSTALLED_APPS中的predictor让 Django 识别这个应用DATABASES使用 SQLite 文件数据库适合开发环境。参数说明BASE_DIR / db.sqlite3是路径拼接写法生成项目根目录下的数据库文件。4.2 数据模型设计用户在表结构里是什么、字段怎么存系统涉及两类数据用户信息和患者检测记录。Django 用户认证用的是内置的auth.User所以自定义模型只需要关注检测记录。字段名和数据预处理时的特征列要一一对应这样 Django 视图从数据库取值后可以直接传给模型。# predictor/models.py from django.db import models from django.contrib.auth.models import User class PatientRecord(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, verbose_name所属用户) pregnancies models.IntegerField(default0, verbose_name怀孕次数) glucose models.FloatField(verbose_name葡萄糖浓度) blood_pressure models.FloatField(verbose_name舒张压) skin_thickness models.FloatField(verbose_name皮褶厚度) insulin models.FloatField(verbose_name血清胰岛素) bmi models.FloatField(verbose_nameBMI) diabetes_pedigree models.FloatField(verbose_name糖尿病谱系函数) age models.IntegerField(verbose_name年龄) risk_prediction models.BooleanField(defaultFalse, verbose_name预测结果) risk_probability models.FloatField(nullTrue, blankTrue, verbose_name风险概率) created_at models.DateTimeField(auto_now_addTrue, verbose_name创建时间) class Meta: db_table patient_record def __str__(self): return f{self.user.username} - {self.created_at}逻辑说明ForeignKey建立与内置 User 模型的一对多关系一条用户记录可以关联多条检测记录。risk_prediction保存模型判断结果risk_probability保存风险概率值。on_deletemodels.CASCADE表示用户被删除时其检测记录一并删除。字段名用下划线风格与 Python 变量命名一致Django ORM 会自动映射为数据库列名。创建完模型后执行python manage.py makemigrations和python manage.py migrate生成并应用数据库表。4.3 视图函数模型加载、预测调用、结果存储视图层是整个系统的核心负责接收表单数据、调用模型、返回结果。模型文件diabetes_model.pkl放在应用的根目录或专门目录中视图启动时加载一次避免每个请求重复读文件。# predictor/views.py import joblib import numpy as np from django.shortcuts import render, redirect from django.contrib.auth.decorators import login_required from .forms import PatientRecordForm from .models import PatientRecord MODEL_PATH predictor/ml_model/diabetes_model.pkl SCALER_PATH predictor/ml_model/scaler.pkl THRESHOLD 0.4 model joblib.load(MODEL_PATH) scaler joblib.load(SCALER_PATH) login_required def predict_view(request): if request.method POST: form PatientRecordForm(request.POST) if form.is_valid(): # 提取表单字段保持与训练时一致的顺序 feature_names [ pregnancies, glucose, blood_pressure, skin_thickness, insulin, bmi, diabetes_pedigree, age ] features np.array([[form.cleaned_data[f] for f in feature_names]]) features_scaled scaler.transform(features) prob model.predict_proba(features_scaled)[0][1] pred int(prob THRESHOLD) record form.save(commitFalse) record.user request.user record.risk_prediction pred record.risk_probability prob record.save() return render(request, predictor/result.html, { probability: prob, prediction: pred, threshold: THRESHOLD, }) else: form PatientRecordForm() return render(request, predictor/predict.html, {form: form})逻辑说明视图函数先判断请求方法POST 时校验表单并提取特征值使用之前保存的scaler做标准化再调用模型的predict_proba获取风险概率与自定义的THRESHOLD比较得出最终判断结果最后把结果存入数据库。这里的关键点是特征顺序必须与训练时完全一致scaler 必须复用训练时保存的那个。参数说明commitFalse让表单创建模型实例但不立即保存数据库这样可以在保存前追加user、risk_prediction、risk_probability字段。4.4 表单设计与模板渲染CSRF 保护和用户输入验证Django 的 Form 类承担输入验证工作字段类型和校验规则在 Form 中定义。这里需要注意表单字段与模型字段不能完全划等号因为表单要排除user、risk_prediction等由后端生成的字段。# predictor/forms.py from django import forms from .models import PatientRecord class PatientRecordForm(forms.ModelForm): class Meta: model PatientRecord fields [ pregnancies, glucose, blood_pressure, skin_thickness, insulin, bmi, diabetes_pedigree, age ] widgets { glucose: forms.NumberInput(attrs{step: 1, min: 0}), bmi: forms.NumberInput(attrs{step: 0.1, min: 0}), } def clean_glucose(self): glucose self.cleaned_data.get(glucose) if glucose 0: raise forms.ValidationError(血糖浓度必须大于 0) return glucose逻辑说明ModelForm根据模型字段自动生成表单字段fields限定用户可输入的项目。widgets定义 HTML 渲染属性比如step控制数字输入框的步长min设置最小值。clean_glucose是字段级校验方法Django 会在is_valid()时自动调用确保输入数据落在合理区间。模板中渲染表单时需要在form标签内加上{% csrf_token %}否则 Django 会拒绝 POST 请求。!-- predictor/templates/predictor/predict.html -- form methodpost {% csrf_token %} {{ form.as_p }} button typesubmit开始预测/button /form逻辑说明{{ form.as_p }}将表单字段渲染为段落式 HTML{% csrf_token %}生成隐藏的 CSRF token 字段用于防止跨站请求伪造攻击。视图中的login_required装饰器保证只有登录用户可以访问预测页面。5. 模型持久化与部署几个容易翻车的细节5.1 用管道对象把 preprocessing 和 model 打包在一起第 2 章和第 3 章的训练过程中scaler 和模型是分开保存的。Django 视图调用时代码会变得繁琐而且容易出现忘记转换数据的情况。更稳妥的做法是用 scikit-learn 的 Pipeline 把标准化和分类器串成一个对象训练时整体保存。from sklearn.pipeline import Pipeline pipeline Pipeline([ (scaler, StandardScaler()), (classifier, RandomForestClassifier(n_estimators200, max_depth5, random_state42)) ]) pipeline.fit(X_train, y_train) joblib.dump(pipeline, pipeline.pkl) # Django 端加载后直接调用 loaded_pipeline joblib.load(pipeline.pkl) prob loaded_pipeline.predict_proba(features)[0][1]逻辑说明Pipeline 的fit会先执行scaler.fit_transform再执行classifier.fitpredict时自动用已拟合的 scaler 转换输入再传给分类器。这避免了在 Django 代码中手动维护两步转换。参数说明Pipeline 中的每一步必须是「转换器 估计器」的组合StandardScaler实现了fit_transform所以能作为管道第一步。5.2 类别不平衡处理class_weight 比硬上采样更省事PIMA 数据集中正负样本比例大约是 35比65并没有严重失衡不需要做复杂的采样处理。但如果换成其他数据集正类占比低于 20% 时建议在模型参数中设置类别权重而不是先做 SMOTE。做法是在RandomForestClassifier或LogisticRegression中加入class_weightbalanced让模型自动根据类别频率调整权重。5.3 阈值不要写在视图的魔法数字里第 3 章提到最佳阈值来自 ROC 曲线计算。实际项目中这个阈值应该放在配置文件中例如 Django 的settings.py里加一个PREDICTION_THRESHOLD 0.4或者在.env环境变量中配。这样调阈值时只需要改配置不用动视图代码也不会在版本控制中留下魔法数字。5.4 记录预测日志方便审计和后置分析医疗场景的系统对审计有要求每次预测请求的输入特征、输出概率、阈值、判断结果、请求时间都应该记录。简单做法是通过 Django 的 logging 模块写入本地日志文件核心字段直接存库这样后续可以回看误判案例反推模型是否需要更新。日志格式建议采用 JSON 结构化输出方便接入日志采集系统。import logging import json from django.utils import timezone logger logging.getLogger(diabetes.predict) def log_prediction(user, features, prob, pred, threshold): log_entry { user: user.username, features: features, probability: prob, prediction: pred, threshold: threshold, timestamp: timezone.now().isoformat() } logger.info(json.dumps(log_entry, ensure_asciiFalse))逻辑说明日志记录的核心原则是「入参可回溯」后续模型迭代时可以用这些日志复盘误判的样本。这里将结构化数据序列化为 JSON 写入日志方便按字段检索。至此从数据处理到模型训练再到 Django 系统集成的完整链路已经走通。本文还有配套的精品资源点击获取