很多人毕设选题时都会陷入一个误区:要么选一个“过于玩具”的课题,比如套个现成框架跑个MNIST手写识别;要么选一个“过于宏大”的课题,比如什么基于深度学习的多模态医疗影像辅助决策系统,最后发现自己连数据都搞不定,代码写不出来,只能干着急。
我过去几年看过不少毕设和技术方案,也带过一些即将毕业的同学做项目。说实话,基于人工智能+机器学习的肝病患者智能诊断及系统实现这类题目,是我认为最适合作为毕设的“黄金选题”之一。它踩中了几个关键点:有明确的应用场景、有公开可获取的数据集、技术栈覆盖机器学习和深度学习、能做成一个“看得见摸得着”的系统,而不是只交一堆训练曲线图。
这篇内容我会从选题思路、数据准备、模型设计、系统实现到毕设答辩的常见坑,完整拆解一遍。无论你是想做机器学习方向的分类模型,还是想接一个web系统做展示,这篇都能给你一个可以直接落地的参考框架。
1. 选题拆解与整体设计思路
1.1 项目定位:为什么选“肝病诊断”而不是其他病种
先说一个很现实的问题:毕设选题时,医学AI方向很多,什么肺炎X光片识别、皮肤癌分类、心电图异常检测,为什么偏偏选肝病?
原因有三层。
第一层是数据门槛。肝病相关的公开数据集相对成熟,尤其是各种体检指标类的结构化数据,特征维度清晰,样本量也够用。相比医学影像数据需要大量标注、需要GPU跑深度学习模型,结构化医疗数据用常规机器学习算法就能出效果。这意味着哪怕你只有一台普通的笔记本电脑,也能完成整个实验。
第二层是业务可解释性。肝病的诊断指标——比如转氨酶、胆红素、白蛋白、凝血酶原时间——每一项都有明确的临床意义。这意味着你做特征工程时,可以通过查阅资料理解每个特征的医学含义,然后在论文里写出“为什么选择这些特征”的合理动机。这一点对于毕设的评分非常重要。评审老师不一定懂深度学习,但他能看懂你选的特征有没有道理。
第三层是扩展空间大。肝病诊断不是二分类就完了。你可以做“健康/患病”二分类,也可以做“健康/肝病/疑似”三分类,还可以接着做肝硬化、肝炎、脂肪肝等细分病种的判别。扩展方向多,就说明你的毕设“有深度可挖”,答辩时有东西可以讲。
再说说系统实现。毕设不只是训练一个模型,还需要交付一个能跑起来的东西。我之前见过不少同学,模型准确率做到95%,结果展示的时候只有一张混淆矩阵图,没有任何一个可以交互的界面,最后答辩被老师连问“你的成果怎么落地应用”,直接卡壳。所以在设计阶段就要决定,系统要采用什么架构。
这里我更推荐本地单机Web系统,而不是去折腾什么分布式微服务。理由很简单,毕设的核心评分点在于“算法有依据、系统能演示、逻辑讲得通”。一个前端页面加一个Python后端的轻量系统,已经完全够用。
1.2 功能需求拆解
把题目拆开看,这个毕设其实包含两条主线:智能诊断技术链和系统功能链。
技术链是:数据预处理 → 特征工程 → 模型训练 → 模型评估 → 模型导出。
系统链是:用户注册登录 → 患者信息录入 → 检验指标填写 → 调用模型推理 → 返回诊断结果 → 历史记录存储。
很多同学会忽略一个关键点:系统不是模型的附属品,它是模型的“载体”。在课题设计阶段,就要把两条线合并成一张流程图。比如前端填完一份肝功检验单后,数据要转成和训练时完全一致的格式,再传给模型推理接口。如果连“字段顺序一致”这种细节都没考虑到,后面联调时会吃大亏。
1.3 技术选型说明
这里给出我常用的方案组合,也是在多个项目里验证过比较省心的选型:
- 后端:Python + Flask。轻量,便捷,模型推理接口写起来很快。
- 前端:HTML + CSS + JavaScript,或者Vue也可以。如果不想花太多时间,可以先用一个单独页面搞定信息录入和结果展示。
- 模型框架:scikit-learn用于传统机器学习模型,PyTorch或Keras用于MLP之类的简单深度学习模型。
- 数据库:SQLite。毕设这个量级,SQLite比MySQL省事太多。
既然题目写了“人工智能+机器学习”,那你至少要覆盖两类技术点:机器学习模型(逻辑回归、随机森林、XGBoost)和深度学习模型(比如多层感知机 MLP)。两个都要出现在你的实验对比里,不能只做一个就完事。
2. 数据准备与特征工程
2.1 数据集来源与格式说明
肝病诊断最常用的公开数据集,通常是某地区肝病患者的体检记录,包含各类血液检验指标。这种数据集一般是CSV格式,一行一个样本,列代表不同的检验指标。
字段一般会包括:年龄、性别、总胆红素、直接胆红素、碱性磷酸酶、丙氨酸氨基转移酶、天冬氨酸氨基转移酶、总蛋白、白蛋白、白蛋白/球蛋白比值,以及一个标签列表示是否患病。
拿到数据之后,第一步不要急着训练,先做数据体检。我习惯先用Pandas加载数据,然后逐项检查:
import pandas as pd df = pd.read_csv('liver_dataset.csv') print(df.shape) print(df.info()) print(df.describe()) print(df.isnull().sum())这一步能快速发现几个问题:有没有空值、各列的数值范围差异大不大、标签分布是否均衡。
关于标签列,默认的数据集里通常是二分类标签(1代表患病,0代表健康),有些数据集会用字符串表示类别,比如'liver patient'和'no liver disease',需要映射成0和1。
2.2 数据清洗的要点
我见过很多同学在这个环节翻车。数据清洗不是把空值删掉就完事。
一个需要特别标注的问题是数据集民族/地域差异导致的偏置。真实场景下不同地区的肝病发病率、检验指标参考范围可能存在差异。这个话题在医学AI领域很严肃,在毕设里不用过度展开,但要在论文中承认“本模型基于特定公开数据集,泛化性需要进一步验证”,这个表述在答辩时会让老师觉得你有风险意识。
另外,有时间一定要做一下标签均衡性检查。如果患病样本和健康样本比例严重失衡,比如9:1,那模型即使“全部预测为多数类”,准确率也有90%。这看起来分数很高,实际毫无价值。这时候就要考虑采用采样策略,比如 Synthetic Minority Over-sampling Technique(SMOTE),或者调整评估指标,不要只看Accuracy。
我建议画出标签分布图和特征相关性热力图,这些图放进论文里非常加分,能证明你做了探索性数据分析(EDA),而不是直接套模型。
2.3 特征工程的实操细节
结构化医疗数据的特征工程,重心不在“特征创造”,而在“特征解释”和“特征标准化”。
肝病数据集包含年龄和性别这类基础属性,也包含多项血液指标。不同指标的数值范围差异很大,比如年龄是20到80,碱性磷酸酶可能是30到200,总胆红素可能是0.3到10。如果不做标准化,逻辑回归这类模型会天然偏向数值更大的特征,导致模型学习效果变差。
我推荐做标准化(StandardScaler)或者归一化(MinMaxScaler),具体二选一看模型。树模型如随机森林和XGBoost对尺度不敏感,可以用也可以不用;逻辑回归、MLP这类基于梯度下降的模型必须用。
有一个细节要记住:只用训练集的数据拟合标准化器,然后用同一套参数去转换验证集和测试集。这个顺序很多人弄反,导致数据泄露,实验结论不可信。正确做法是:
from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test)注意random_state固定一个数字,这样你的实验结果可以复现,写论文时数据是一致的。stratify=y表示分层采样,让训练集和测试集保持同样的类别比例。
性别这种类别特征,直接用0/1编码即可。有些数据集里性别列可能写的是“Male/Female”字符串,处理方式很简单:
df['Gender'] = df['Gender'].map({'Male': 1, 'Female': 0})2.4 样本不均衡的处理方案
我在实际项目里遇到过肝病数据集的正负样本比例接近9:1的情况,训练出来的模型表现得“极其自信”,把所有样本都判为患病,然后告诉你准确率有90%多。这种模型放到临床绝对是个灾难。
处理不均衡样本的常见思路有两个方向:一是数据层面,二是算法层面。
数据层面最常用SMOTE(Synthetic Minority Over-sampling Technique),原理是在少数类样本之间的连线上插值合成新样本。实操时只对训练集做SMOTE,测试集保持原始分布,否则会严重高估模型泛化能力。
from imblearn.over_sampling import SMOTE smote = SMOTE(random_state=42) X_train_resampled, y_train_resampled = smote.fit_resample(X_train_scaled, y_train)算法层面则是调整类别权重。比如在逻辑回归里设置class_weight='balanced',或者在XGBoost里设置scale_pos_weight参数。这两招可以结合使用,在答辩时也能展示你处理问题的方法论。
3. 模型选型与实验对比
3.1 机器学习基线模型
做这个题目,至少要训练并对比四到五个模型,才能撑起“实验对比”这一章节。我推荐的基线组合是:
- 逻辑回归(Logistic Regression):作为最简单的线性基线,速度快,可解释性好。
- K近邻(KNN):直观,但特征维度高时性能会下降。
- 支持向量机(SVM):适合中小规模数据集,核函数选RBF通常效果不错。
- 随机森林(Random Forest):集成学习代表,基本不掉链子。
- XGBoost / LightGBM:梯度提升树,往往能拿到最好的表格数据效果。
这里要说一个经验:不要在写代码之前就预设哪个模型最好。正确做法是把所有模型都跑一遍,用同一套评估流程对比,然后论文里写“实验结果表明,XGBoost在准确率、召回率、F1分数上整体最优”。这种结论是有数据支撑的,比拍脑袋选模型靠谱得多。
为了对比公平,每个模型最好都做一次简单的网格搜索或者随机搜索,找到相对合适的超参数。下面给一个随机森林网格搜索的最小示例:
from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import GridSearchCV param_grid = { 'n_estimators': [50, 100, 200], 'max_depth': [None, 10, 20], 'min_samples_split': [2, 5] } rf = RandomForestClassifier(random_state=42) grid = GridSearchCV(rf, param_grid, cv=5, scoring='f1') grid.fit(X_train_scaled, y_train) print(grid.best_params_)交叉验证用5折就够了,数据量不大时10折会拖慢速度。
3.2 深度学习模型:多层感知机(MLP)
题目里强调了“人工智能+机器学习”,所以深度学习部分不能缺席。对于肝病结构化数据,不需要上CNN或RNN,一个结构合理的MLP就能说明问题。
这里有一种常见误解:MLP结构变深效果一定更好。就我的实验经验,肝病数据集的样本量通常在几百到几千之间,特征维度在10个左右,网络太深反而容易过拟合。推荐结构是三层全连接:输入层(10维左右)→ 隐藏层32 → 隐藏层16 → 输出层1,激活函数用ReLU,输出层用Sigmoid。
用PyTorch搭建一个简单MLP:
import torch import torch.nn as nn class MLP(nn.Module): def __init__(self, input_dim): super(MLP, self).__init__() self.fc1 = nn.Linear(input_dim, 32) self.fc2 = nn.Linear(32, 16) self.fc3 = nn.Linear(16, 1) self.relu = nn.ReLU() self.sigmoid = nn.Sigmoid() def forward(self, x): x = self.relu(self.fc1(x)) x = self.relu(self.fc2(x)) x = self.sigmoid(self.fc3(x)) return x训练循环里要注意几点:loss用BCELoss、优化器用Adam、学习率设1e-3左右、训练轮次设50到100轮,配合早停(Early Stopping)防止过拟合。
这里想强调一下欠拟合比过拟合更常见。很多同学第一次跑深度学习模型,发现loss降不下去,就疯狂调大网络,结果反而更差。先检查数据标准化是否做了、学习率是不是太大、激活函数对不对,再调整结构。
3.3 模型评估指标怎么选
肝病诊断属于医疗场景,评估指标不能只看准确率。这一点值得在论文中专门写一节。
医疗场景里最核心的指标是召回率(Recall),也叫敏感度。它表示“在所有真正患病的样本中,模型成功识别出多少”。漏诊的代价远高于误诊——你宁可把健康的人叫回来复查,也不能让真正患病的人以为自己是健康的。
混淆矩阵里四个基础概念要先搞明白:
- TP(True Positive):实际患病,预测患病。
- TN(True Negative):实际健康,预测健康。
- FP(False Positive):实际健康,预测患病,误诊。
- FN(False Negative):实际患病,预测健康,漏诊。
在这个场景下,FN的代价比FP更高。所以选模型时,如果两个模型准确率差不多,优先选Recall更高的那一个。
另外还要画出ROC曲线,计算AUC值。AUC能综合反映模型在不同阈值下的区分能力,不依赖于具体阈值选择。这部分图表是论文里的核心素材。
3.4 实验记录与对比表
做实验不要只在自己脑子里记录结果,一定要写成结构化表格。我之前见过有同学做了一堆实验,最后答辩前整理数据时,想不起来某个模型的参数是哪个版本跑了,不得不全部重跑。
推荐实验记录表至少包含这些列:模型名称、参数配置、准确率、精确率、召回率、F1、AUC、训练耗时。
这里我提供一个示例对比结果,帮你建立参考手感(具体数值会随数据集不同而变化):
| 模型 | 准确率 | 召回率 | F1 | AUC |
|---|---|---|---|---|
| 逻辑回归 | 0.87 | 0.89 | 0.87 | 0.92 |
| KNN | 0.82 | 0.85 | 0.82 | 0.88 |
| SVM | 0.88 | 0.90 | 0.88 | 0.94 |
| 随机森林 | 0.91 | 0.92 | 0.91 | 0.96 |
| XGBoost | 0.93 | 0.94 | 0.93 | 0.97 |
| MLP | 0.90 | 0.91 | 0.90 | 0.95 |
最后你选择哪个模型作为系统最终使用的模型,不能只凭“最高分”定。要综合考虑训练速度、推理速度、部署难度、可解释性。我的建议是:最终系统里用随机森林或XGBoost,MLP在论文中作为深度学习方案的对比出现。如果导师强调“要有深度学习”,可以把MLP同时集成到系统里,做模型对比切换功能。
4. 系统实现与部署
4.1 系统整体架构
系统实现部分最推荐的模式是:前端页面 + Flask后端 + 已训练好的模型文件。
架构并不复杂:
- 前端负责收集用户输入的检验指标数据。
- 后端接收请求,对数据做与训练时完全相同的预处理,加载模型文件,进行推理,返回诊断结果。
- 顺便把每次诊断记录存到SQLite数据库,方便查看历史。
这里有一个训练和部署阶段的隐藏坑:训练代码里的预处理流程,必须在部署代码里原样复现。很多同学训练时把StandardScaler对象保存下来了,但部署时忘了加载,直接对原始数据做预测,导致结果完全错误。正确做法是,训练结束后用joblib或者pickle把模型和标准化器一起保存:
import joblib joblib.dump(model, 'model.pkl') joblib.dump(scaler, 'scaler.pkl')预测时,先加载模型和标准化器,再对新数据进行标准化转换:
model = joblib.load('model.pkl') scaler = joblib.load('scaler.pkl') new_data_scaled = scaler.transform(new_data_array) pred = model.predict(new_data_scaled)4.2 Flask后端接口设计
后端只需要三个接口:
- 用户注册/登录接口,可以简化成用户名密码验证。
- 诊断接口
/predict,接收POST请求,返回预测结果和概率。 - 历史记录查询接口
/history,返回当前用户的历史诊断记录。
Flask代码的核心部分可以这样写:
from flask import Flask, request, jsonify app = Flask(__name__) @app.route('/predict', methods=['POST']) def predict(): data = request.get_json() # 按训练时的特征顺序构造数组 features = [ data['age'], data['gender'], data['total_bilirubin'], data['direct_bilirubin'], data['alkaline_phosphatase'], data['alt'], data['ast'], data['total_protein'], data['albumin'], data['ag_ratio'] ] import numpy as np features_array = np.array([features]) features_scaled = scaler.transform(features_array) probability = model.predict_proba(features_scaled)[0][1] prediction = 1 if probability >= 0.5 else 0 return jsonify({'prediction': int(prediction), 'probability': float(probability)})注意,字段顺序必须和训练时完全一致,这一点怎么强调都不为过。训练时特征是10列,推理时少传一个字段,模型不会报错,但结果会错得非常离谱。
4.3 前端页面设计要点
前端不需要做得多华丽,但要把核心功能展示清楚。我建议至少包含三个页面:
- 登录/注册页。
- 诊断页:一个表单,每个检验指标一个输入框,一个“开始诊断”按钮,下面显示结果(患病概率、风险等级)。
- 历史记录页:一个表格,展示历次诊断时间、输入的关键指标和结果。
如果想让系统看起来更专业,可以在诊断结果旁边加一个“指标说明”折叠面板,展示每项指标的正常参考范围。这个细节看似不起眼,但在答辩演示时非常加分。
前端建议用一个单独的index.html加原生JS实现,不要引入复杂的前端工程化。毕设时间有限,省时间去做实验和写论文,远比花时间优化CSS重要。
4.4 模型展示与结果可视化
系统里如果只有一堆输入框和一个结果文字,其实还是不够“智能化”。我建议在系统里增加两个图表:
- 特征重要性图(如果最终模型是树模型,可以展示特征重要性Top10柱状图),让用户直观看到哪些指标对诊断结果影响最大。
- ROC曲线图,放在系统“模型说明”页面,展示模型整体性能。
这些图可以用Flask在启动时渲染一次,存成静态图片,也可以前端用Chart.js或者ECharts动态绘制。前者更简单,后者更炫。
另一个值得做的功能是风险等级划分。预测概率0.9以上显示“高风险”,0.6到0.9显示“中风险”,0.6以下显示“低风险”。这种展示比单纯给一个数字更接近真实医疗分诊的逻辑。
4.5 保存模型与打包交付
毕设最终交付的“系统”,不能是一堆不能运行的源代码。理想的情况是,拿到一台干净电脑,配好Python环境,能一行命令启动,浏览器打开就能用。
这里有一个很实用的技巧:在项目根目录放一个requirements.txt,写清楚所有依赖包及其版本。另外写一个README.md,把启动步骤写详细,包括“先安装Python 3.9及以上版本”、“执行pip install -r requirements.txt”、“运行python app.py”、“浏览器访问localhost:5000”。这些内容能让答辩演示时少出很多意外。
还可以考虑给前端页面做简单的响应式适配,保证在老师用的笔记本上打开不变形。不要假设老师的电脑是顶配大屏。
5. 行进中的坑与解决实录
5.1 数据字段踩坑:顺序错乱导致结果离谱
自己在联调时遇到过一次,当时训练阶段我把特征顺序定成了“性别、年龄、总胆红素……”,但前端页面表单的录入顺序是“年龄、性别、总胆红素……”,结果后端拼特征数组时没注意,模型预测的结果和实际应该得到的结果差了十万八千里,而且模型不报任何错误。
后来排查了很久才发现是特征顺序错位。根本原因就是前后端字段顺序没有对齐。
解决方案是:在项目里维护一个FEATURE_COLUMNS常量列表,训练代码、后端推理代码、前端表单展示全部引用同一个顺序。后端接收请求后,先按这个列表顺序组装数据,再传入模型。
5.2 样本不均衡带来的假高准确率
上面提过,这是常见问题。最初跑出来的模型准确率很高,但是仔细观察混淆矩阵后发现,几乎所有样本都被判定为患病,真正健康的人一个都没被识别出来。这种模型没有实际意义。后来通过SMOTE和class_weight='balanced'双管齐下,模型才开始学会区分两类样本。
在毕设答辩时,你可以主动讲这件事。老师会认为你不仅会用模型,还知道怎么发现模型的问题、怎么解决,这是非常好的加分点。
5.3 过拟合问题
另一个常见问题是模型在训练集上表现很好,测试集上一塌糊涂。典型原因包括:树模型深度太大、深度学习模型训练轮次太多、数据量太小。
我见过一个同学,随机森林的max_depth设成了50,结果训练集F1高达0.98,测试集只有0.75。后面用网格搜索把深度限制在10左右,测试集分数才回升。
解决思路:训练时做交叉验证,不要迷信训练集分数;树模型限制最大深度和叶子节点最小样本数;深度学习模型用早停。
from sklearn.model_selection import cross_val_score scores = cross_val_score( RandomForestClassifier(random_state=42), X_train_scaled, y_train, cv=5, scoring='f1' ) print(f'CV F1: {scores.mean():.4f} ± {scores.std():.4f}')5.4 系统启动依赖缺失
答辩现场最尴尬的事不是模型效果差,而是系统启动不起来。最常见的坑是缺包,或者Python版本不对,或者模型文件路径写成了绝对路径,换一台机器就找不到文件。
预防措施有三条:用相对路径加载模型和静态文件;在requirements.txt锁版本;自己找一台干净电脑做一次完整部署演练。你可以在下午花半小时,把自己当成一个从没碰过这个项目的人,从头按README走一遍。只要能顺利跑通,答辩时基本不会出大问题。
6. 关于选题的几点个人体会
6.1 这个课题的伸缩性
这个课题最好的一点是——下限低,上限高。
如果时间紧张,做二分类加一个Web系统,模型用随机森林,系统用Flask加一个页面,两周就能完整交付。如果时间和精力充裕,可以做肝病类型多分类、加上模型可解释性分析(比如SHAP值分析)、做成微服务架构、加医生端和患者端两种角色权限,甚至可以用Docker容器化部署。同样的题目,可以做到让人眼前一亮的深度。
我个人更建议中等方案:二分类模型作为核心,加MLP对比,加SHAP特征解释,加一个带历史记录的系统。这个体量既不会让工作量失控,又能覆盖“机器学习+深度学习+系统实现”这三个评分关键词。
6.2 论文侧重点的平衡
写论文的时候要警惕一种倾向:花太多篇幅写系统实现,算法部分草草带过。这类题目核心是“诊断”,重点应该在数据分析和模型实验上,系统只是把模型能力呈现出来的载体。
论文建议章节结构:
- 绪论(背景、国内外现状、研究意义)
- 相关技术介绍(机器学习、深度学习、Flask框架)
- 数据来源与预处理(EDA、清洗、特征工程、样本均衡)
- 模型设计与实验对比(各模型原理简述、评估指标、实验设计、结果分析)
- 系统设计与实现(架构、功能模块、关键代码、系统截图)
- 总结与展望
把重心放在第3和第4章,各写两三千字都不是问题。系统章节配合截图和关键代码,写满一千字以上就够了。
6.3 关于“AI辅助诊断”的边界意识
最后想多说一句。做医疗AI方向的项目,无论是毕设还是以后的工作,都要对自己系统的定位有清醒认知。算法模型的作用是“辅助医生决策”,而不是替代医生诊断。你在论文里写结论时,不要写“本系统可以独立诊断肝病并替代医生”,而要写“本系统可为医生提供辅助参考,减少漏诊风险,降低筛查门槛”。这个表述既是事实,也是对你作品的恰当定位。
我在实际项目的过程中,最大的感悟是这个题目虽然技术难度不是最高的,但它的工程完整度和场景价值都很高。只要你不是完全不动手,认认真真把数据和模型跑一遍、把系统搭起来,你不仅能收获一个拿得出手的毕设,还能顺带把机器学习从数据到部署的完整链路走通。这套能力,等到工作或者读研时,都能直接用上。
如果你正在纠结毕设选题,又希望选一个工作量可控、方向清晰、有实用价值的题目,这个“肝病患者智能诊断及系统实现”的方向,值得认真考虑。剩下的,就是从下载数据、跑通第一个模型开始,一步一个脚印把它做下去。