尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Python+PyQt5实战:心脏病预测GUI从模型到界面全流程

Python+PyQt5实战:心脏病预测GUI从模型到界面全流程 简介这份源码资源面向具备Python基础、希望入门机器学习与桌面应用开发的学习者围绕心脏病风险预测这一医疗健康场景提供了一套可运行的图形界面实现方案。压缩包共16个文件约22KB以6个py脚本为核心配合3个xml与1个ui界面文件、2个csv数据集、1个pkl与1个model模型文件以及备份与工程配置文件覆盖界面搭建、数据处理、模型加载与预测推理等环节。项目采用PyQt5构建主窗口与输入控件用户可录入年龄、血压、胆固醇等指标经Pandas预处理后交由预训练模型计算患病概率并附带输入校验与结果展示逻辑。已有144人学习下载适合作为课程设计、毕业设计或机器学习入门练手项目帮助读者理解GUI事件绑定、数据清洗流程与模型调用方式并在此基础上替换算法或扩展数据集。1. 心脏病预测 GUI 到底解决什么问题从 UCI 数据到可点击的诊断面板很多做机器学习入门的人卡在同一个地方模型在 Jupyter Notebook 里跑出 0.85 的准确率截图发完朋友圈就结束了没人能真正用上它。基于 Python 实现心脏病预测图形界面本质上是把「数据清洗 → 特征工程 → 模型训练 → 结果可视化」这条链路封装成一个医生或普通人能点按钮操作的桌面程序。标题里的 PyQt5 是 Python 生态里最成熟的桌面 GUI 框架之一配合 scikit-learn 或 XGBoost 做推理后端就能做出一个输入 13 项临床指标、输出患病概率的交互工具。这类项目适合三类人正在找课程设计或毕设题目的学生、想练手「模型落地」全流程的 Python 初学者、以及需要快速验证医疗 AI 交互逻辑的产品原型开发者。它不追求临床级精度核心价值在于打通「算法 → 界面 → 用户」的最后一公里。热搜里高频出现的 pyqt5界面设计、python安装教程、vscode python环境配置恰好说明大多数人卡在环境和界面这两步而不是模型本身。下面按实际落地顺序拆开讲。2. 环境搭建与数据准备把 UCI 心脏病数据集跑通2.1 Python 与 PyQt5 的安装组合拳环境是第一道坎。我一般推荐 Python 3.9 到 3.11 之间的版本太新的版本比如 3.13有时 PyQt5 的 wheel 还没跟上pip 装到一半报编译错误。Windows 用户直接去 python.org 下载安装包安装时务必勾选「Add Python to PATH」这一步漏了后面全是坑。Linux 用户如果用 openEuler 或 Ubuntu系统自带 Python 但可能缺 tkinter 和 Qt 依赖需要额外补。# 创建独立虚拟环境避免污染系统 Python python -m venv heart_env # Windows 激活 heart_env\Scripts\activate # Linux / macOS 激活 source heart_env/bin/activate # 安装核心依赖指定版本区间避免兼容问题 pip install PyQt55.15,6.0 pip install scikit-learn pandas numpy joblib pip install xgboost # 可选想要更高精度时用逻辑说明虚拟环境是后悔药装崩了直接删目录重来不影响系统。PyQt5 锁在 5.x 是因为 6.x 的 API 有破坏性变更网上大部分教程代码基于 5.x。scikit-learn 负责模型训练和推理joblib 用来持久化模型文件。参数上5.15保证有高 DPI 支持6.0避免 API 不兼容。提示如果 pip 安装 PyQt5 时报「Could not find a version」先执行python -m pip install --upgrade pip再重试。国内网络可以加-i https://pypi.tuna.tsinghua.edu.cn/simple加速。2.2 心脏病数据集的结构与清洗要点常用的是 UCI Heart Disease 数据集 Cleveland 子集有 303 条样本、14 个字段。13 个特征包括年龄、性别、胸痛类型cp、静息血压trestbps、胆固醇chol、空腹血糖fbs、静息心电图restecg、最大心率thalach、运动诱发心绞痛exang、ST 段压低oldpeak、坡度slope、主血管数ca、thal 类型目标字段是是否患病。import pandas as pd from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler # 加载数据假设 csv 已放在项目 data 目录 df pd.read_csv(data/heart.csv) # 检查缺失值UCI 原始数据常用 ? 表示缺失 df df.replace(?, pd.NA).dropna() # 特征与标签分离 X df.drop(target, axis1) y df[target] # 划分训练集和测试集固定随机种子保证可复现 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) # 标准化年龄、血压、胆固醇量纲差异大必须归一化 scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)逻辑说明replace(?, pd.NA).dropna()处理原始数据里的缺失标记UCI 数据集有 6 条含缺失直接删掉对 303 条样本影响很小。stratifyy保证训练集和测试集的正负样本比例一致避免某一类偏少导致模型偏向。StandardScaler 的fit只在训练集上做测试集用transform这是防止数据泄露的基本纪律。参数random_state42是约定俗成的种子换成别的数字结果会略有波动但可复现性必须保证。注意标准化器要和模型一起保存推理时新输入的数据必须用同一个 scaler 转换否则预测结果会完全错乱。这是新手最常见的翻车点之一。3. 模型训练与持久化选逻辑回归还是 XGBoost3.1 两个基线模型的训练与对比心脏病预测这种中小规模表格数据逻辑回归和 XGBoost 是两个最实用的基线。逻辑回归可解释性强系数能直接看出哪个特征影响大XGBoost 精度通常高几个点但调参空间大。我一般两个都跑用交叉验证对比后再决定。from sklearn.linear_model import LogisticRegression from sklearn.ensemble import GradientBoostingClassifier from sklearn.model_selection import cross_val_score import numpy as np # 逻辑回归基线 lr LogisticRegression(max_iter1000, C1.0, random_state42) lr_scores cross_val_score(lr, X_train_scaled, y_train, cv5, scoringaccuracy) # 梯度提升树基线 gb GradientBoostingClassifier(n_estimators100, learning_rate0.1, max_depth3, random_state42) gb_scores cross_val_score(gb, X_train_scaled, y_train, cv5, scoringaccuracy) print(f逻辑回归 5折准确率: {lr_scores.mean():.4f} (/- {lr_scores.std():.4f})) print(f梯度提升 5折准确率: {gb_scores.mean():.4f} (/- {gb_scores.std():.4f}))逻辑说明cross_val_score做 5 折交叉验证比单次划分更可靠。max_iter1000是因为逻辑回归默认 100 次迭代在标准化数据上有时不收敛。C1.0是正则化强度的倒数调大容易过拟合调小容易欠拟合1.0 是安全起点。梯度提升的n_estimators100是树的数量learning_rate0.1控制每棵树的贡献max_depth3限制单棵树复杂度防止过拟合。这三个参数是调参时最先动的。参数对比表参数逻辑回归梯度提升树调整方向正则化C1.0无直接对应C 减小增强正则树数量不适用n_estimators100增大可能过拟合学习率不适用learning_rate0.1减小需增树数最大深度不适用max_depth3增大拟合能力增强迭代上限max_iter1000不适用不收敛时增大3.2 模型持久化与推理接口封装训练完的模型必须存成文件GUI 启动时加载而不是每次点按钮都重新训练。joblib 是 scikit-learn 官方推荐的持久化工具比 pickle 对 numpy 数组更高效。import joblib # 训练最终模型用全部训练数据 final_model GradientBoostingClassifier( n_estimators100, learning_rate0.1, max_depth3, random_state42 ) final_model.fit(X_train_scaled, y_train) # 保存模型和标准化器两个必须成对保存 joblib.dump(final_model, model/heart_model.pkl) joblib.dump(scaler, model/scaler.pkl) # 推理函数输入原始 13 个特征输出患病概率 def predict_heart_disease(features: list) - float: features: 长度为 13 的列表顺序与训练特征一致 返回患病概率 0~1 model joblib.load(model/heart_model.pkl) scaler joblib.load(model/scaler.pkl) arr np.array(features).reshape(1, -1) arr_scaled scaler.transform(arr) prob model.predict_proba(arr_scaled)[0][1] return float(prob)逻辑说明predict_proba返回两个概率索引 1 是患病概率。reshape(1, -1)把单条样本变成二维数组因为 sklearn 要求输入是二维的。特征顺序必须和训练时完全一致这是最容易出错的地方——GUI 界面上输入框的顺序如果和 DataFrame 列顺序不同预测结果就是垃圾。我一般会在代码里写一个FEATURE_ORDER常量训练和推理都引用它。提示模型文件不要提交到 Git 仓库体积大且每次训练都变。用.gitignore排除model/目录部署时单独拷贝。4. PyQt5 界面搭建从布局到信号槽的完整实现4.1 主窗口布局与 13 个输入控件的组织PyQt5 的界面设计有两种方式Qt Designer 拖拽生成.ui文件或者纯代码写。课程设计和源码分享场景下纯代码更透明别人拿到就能跑不依赖额外工具。13 个特征里连续值用 QLineEdit 或 QDoubleSpinBox分类值用 QComboBox。from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QLabel, QLineEdit, QComboBox, QPushButton, QGridLayout, QVBoxLayout, QMessageBox) from PyQt5.QtCore import Qt import sys class HeartPredictWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle(心脏病预测系统) self.setFixedSize(600, 500) self.init_ui() def init_ui(self): central QWidget() self.setCentralWidget(central) grid QGridLayout() # 连续值特征用输入框 self.age_input QLineEdit() self.trestbps_input QLineEdit() self.chol_input QLineEdit() self.thalach_input QLineEdit() self.oldpeak_input QLineEdit() # 分类值特征用下拉框 self.sex_combo QComboBox() self.sex_combo.addItems([女, 男]) self.cp_combo QComboBox() self.cp_combo.addItems([典型心绞痛, 非典型, 非心绞痛, 无症状]) # 按行摆放左侧标签右侧控件 grid.addWidget(QLabel(年龄), 0, 0) grid.addWidget(self.age_input, 0, 1) grid.addWidget(QLabel(性别), 1, 0) grid.addWidget(self.sex_combo, 1, 1) grid.addWidget(QLabel(胸痛类型), 2, 0) grid.addWidget(self.cp_combo, 2, 1) grid.addWidget(QLabel(静息血压), 3, 0) grid.addWidget(self.trestbps_input, 3, 1) grid.addWidget(QLabel(胆固醇), 4, 0) grid.addWidget(self.chol_input, 4, 1) grid.addWidget(QLabel(最大心率), 5, 0) grid.addWidget(self.thalach_input, 5, 1) grid.addWidget(QLabel(ST段压低), 6, 0) grid.addWidget(self.oldpeak_input, 6, 1) # 预测按钮 self.predict_btn QPushButton(开始预测) self.predict_btn.clicked.connect(self.on_predict) grid.addWidget(self.predict_btn, 7, 0, 1, 2) # 结果显示 self.result_label QLabel(等待输入...) self.result_label.setAlignment(Qt.AlignCenter) grid.addWidget(self.result_label, 8, 0, 1, 2) central.setLayout(grid) def on_predict(self): try: features [ float(self.age_input.text()), float(self.sex_combo.currentIndex()), float(self.cp_combo.currentIndex()), float(self.trestbps_input.text()), float(self.chol_input.text()), # ... 其余特征按训练顺序补齐 ] prob predict_heart_disease(features) self.result_label.setText(f患病概率{prob:.2%}) except ValueError: QMessageBox.warning(self, 输入错误, 请检查所有字段是否填写正确) if __name__ __main__: app QApplication(sys.argv) window HeartPredictWindow() window.show() sys.exit(app.exec_())逻辑说明QGridLayout按行列摆放控件比绝对定位更适应不同分辨率。clicked.connect(self.on_predict)是 PyQt5 的信号槽机制按钮点击触发on_predict方法。currentIndex()把下拉框选项转成 0、1、2 的数值和训练时的编码方式必须一致。try/except ValueError捕获空输入或非数字输入弹窗提示而不是直接崩溃。setFixedSize固定窗口大小避免布局拉伸变形。参数说明addWidget(widget, row, col, rowspan, colspan)后两个参数控制跨行跨列按钮跨两列更美观。Qt.AlignCenter让结果文字居中。sys.exit(app.exec_())是 PyQt5 的标准退出方式exec_()带下划线是历史遗留PyQt6 里改成了exec()。4.2 信号槽与多线程避免界面卡死模型推理虽然快但如果未来换成深度学习模型或者要批量预测主线程会被阻塞界面显示「无响应」。正确做法是把推理放到 QThread 里通过信号把结果传回主线程更新 UI。from PyQt5.QtCore import QThread, pyqtSignal class PredictWorker(QThread): finished pyqtSignal(float) # 推理完成信号携带概率 error pyqtSignal(str) # 错误信号 def __init__(self, features): super().__init__() self.features features def run(self): try: prob predict_heart_disease(self.features) self.finished.emit(prob) except Exception as e: self.error.emit(str(e)) # 在主窗口中这样调用 def on_predict(self): features self.collect_features() self.worker PredictWorker(features) self.worker.finished.connect(self.update_result) self.worker.error.connect(self.show_error) self.worker.start() def update_result(self, prob): self.result_label.setText(f患病概率{prob:.2%})逻辑说明pyqtSignal定义自定义信号finished携带 float 类型结果。run()方法在子线程执行不能直接操作 UI 控件必须通过信号通知主线程。self.worker存为实例属性防止被垃圾回收导致线程提前结束。这是 PyQt5 多线程的标准写法比QThreadPool更直观。注意子线程里绝对不能调用self.result_label.setText()会直接崩溃或报「QObject: Cannot create children for a parent that is in a different thread」。所有 UI 更新必须回到主线程。5. 避坑与排查源码跑不起来时先查这五处5.1 现象pip 装 PyQt5 报错找不到 Qt 库原因Linux 系统缺少 Qt 的底层动态库或者 Python 版本太新没有对应 wheel。Windows 上通常是 pip 版本过低。解决Linux 执行sudo apt install libxcb-xinerama0 libxcb-cursor0Ubuntu/Debian或sudo yum install mesa-libGLCentOS/openEuler。Windows 先python -m pip install --upgrade pip再重装。如果还不行换 Python 3.10 版本这个版本的 PyQt5 wheel 最全。5.2 现象界面能打开点预测按钮没反应原因信号槽没连上或者on_predict里抛了异常被吞掉。常见于复制代码时漏了self.predict_btn.clicked.connect(self.on_predict)这一行。解决在on_predict第一行加print(按钮被点击)看控制台有没有输出。没有输出就是信号没连上检查 connect 语句。有输出但没结果就是推理部分异常把try/except里的异常打印出来看具体错误。5.3 现象预测结果永远是同一个值原因输入特征没有做标准化或者特征顺序和训练时不一致。模型收到的是原始量纲的数据标准化器期望的是归一化后的值输出自然恒定。解决确认推理时调用了scaler.transform()并且FEATURE_ORDER和训练时的 DataFrame 列顺序完全一致。打印arr_scaled看数值是否在合理范围标准化后应该在 -3 到 3 之间。5.4 现象打包成 exe 后闪退原因PyInstaller 没有把 PyQt5 的插件目录打进去或者模型文件路径用了相对路径打包后工作目录变了。解决用pyinstaller --add-data model;model --add-data data;data main.py显式包含资源目录。代码里读文件用os.path.join(os.path.dirname(os.path.abspath(__file__)), model, heart_model.pkl)不要用model/heart_model.pkl这种相对路径。5.5 现象中文显示成方块原因PyQt5 默认字体不含中文字形或者系统缺少中文字体。解决在QApplication创建后设置全局字体app.setFont(QFont(Microsoft YaHei, 10))Windows或app.setFont(QFont(WenQuanYi Micro Hei, 10))Linux。如果系统没装中文字体Linux 上sudo apt install fonts-wqy-microhei补上。6. 进阶技巧用 Qt Designer 加速界面迭代与模型可解释性展示纯代码写界面适合理解原理但实际做项目时Qt Designer 能省大量时间。它生成的.ui文件用pyuic5转成 Python 代码布局调整只需拖拽不用改代码。安装pip install pyqt5-tools后Designer 在site-packages/qt5_applications/Qt/bin/designer.exeWindows或对应路径下。转换命令是pyuic5 mainwindow.ui -o ui_mainwindow.py然后在主程序里from ui_mainwindow import Ui_MainWindow用继承方式加载。另一个值得加的功能是特征贡献可视化。逻辑回归的系数可以直接展示每个特征对预测的影响方向和大小用横向条形图放在界面右侧。这样用户不仅看到概率还能理解「为什么是这个结果」。实现上用 matplotlib 嵌入 PyQt5通过FigureCanvasQTAgg把图表挂到界面上。from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg from matplotlib.figure import Figure class FeatureChart(FigureCanvasQTAgg): def __init__(self, parentNone): fig Figure(figsize(4, 3), dpi100) self.axes fig.add_subplot(111) super().__init__(fig) def plot_coefficients(self, coefs, names): self.axes.clear() colors [red if c 0 else green for c in coefs] self.axes.barh(names, coefs, colorcolors) self.axes.set_xlabel(特征影响) self.draw()逻辑说明FigureCanvasQTAgg是 matplotlib 官方提供的 Qt 后端把 Figure 嵌入 Qt 控件。barh画横向条形图红色表示增加患病风险绿色表示降低。self.draw()触发重绘不调用的话图表不会更新。这个组件放在主界面右侧每次预测后刷新用户能直观看到哪些指标在「推高」风险。我自己的习惯是任何模型落地项目先保证「输入 → 推理 → 输出」这条最小链路跑通再考虑界面美化。很多人一上来就纠结配色和动画结果模型还没跑通就放弃了。先把 13 个输入框和预测按钮做出来能弹出概率数字这个项目就成功了 80%。剩下的可解释性、多线程、打包都是锦上添花。希望帮到你。本文还有配套的精品资源点击获取
返回列表