简介一份基于PyQt5与机器学习的心脏病预测图形界面源码面向希望将算法落地为桌面应用的Python学习者也适合作为医学数据分析课程设计参考。项目以逻辑回归、决策树等可解释模型为内核完整封装了从数据清洗、缺失值填充、特征标准化到模型训练与预测的流程并加入输入有效性检查与结果提示。源码包共16个文件包括6个Python脚本、UI布局文件、两个CSV数据集、训练好的模型文件以及XML配置等整体仅22KB结构精炼便于逐文件对照研读。已有144人学习下载。通过源码可掌握QMainWindow窗口搭建、按钮事件绑定、用户输入校验以及如何用Pandas处理年龄、血压等健康指标并调用预训练模型输出患病概率与风险等级界面与预测逻辑的分离设计尤为清晰适合新手拆解学习也可直接替换自身数据或模型进行二次开发。此外压缩包内附有UI文件与模型备份便于快速还原界面和对比不同算法的预测效果。1. 心脏病预测图形界面这类 PyQt5 源码项目到底在做什么从网上下载一个叫「基于python实现心脏病预测图形界面pyqt5源码.zip」的压缩包解压之后是几个.py文件双击却跑不起来这是很多人接触Python课程设计项目时遇到的第一个坎。这个标题指向的是一类典型的机器学习桌面应用用公开的心脏病数据集训练分类模型再用PyQt5把数据录入和结果展示做成窗口程序最后交付一个能脱离命令行运行的图形界面。它能解决的问题是「把模型从训练脚本变成可交互工具」适合想同时练机器学习和界面开发的初学者也适合需要快速搭出一套课程设计demo的人。下面按一个完整落地方案来讲数据建模、界面搭建、模型接入、打包发布每一步都能直接照着做。2. 先把预测模型做出来数据、特征与模型选型动手写界面前先把预测这件事做扎实。很多源码包界面很好看模型侧却漏洞百出原因就是建模环节被匆匆跳过了。这一章从一份公开数据出发走完「看清数据→训练模型→保存模型」三个步骤你会得到一个能被界面程序稳定调用的heart_model.pkl。2.1 数据长什么样先确定特征和标签常见做法是使用公开的心脏病数据集比如UCI Cleveland心脏病数据集特征是13项体检指标加1个目标列。典型字段包括age、sex、cp、trestbps、chol、fbs、restecg、thalach、exang、oldpeak、slope、ca、thaltarget列里0代表未患病、1代表患病。拿到数据第一件事不是训练而是看清每一列的含义和分布import pandas as pd df pd.read_csv(heart.csv) print(df.shape) print(df.head()) print(df[target].value_counts())逻辑说明shape返回行数和列数head看前五行的每个字段取值样例value_counts统计target里0和1的数量。这一步决定了后续所有工作的基础目标列名是什么、有没有缺失值、正负样本比例是否悬殊。如果正负样本差距过大后面评估指标就不能只看准确率要改用AUC和recall。参数说明读取文件如果报编码错误把read_csv改成read_csv(heart.csv, encodinggbk)或encodingutf-8分别试。字段名在不同来源的数据集里可能不一样比如静息血压有的叫trestbps有的叫rest_bp接手老代码时一定要先把字段名对齐否则后续特征顺序会全部错位。这一步省事后面就省心。2.2 建模思路为什么先说逻辑回归再对比随机森林对这种十几维特征、训练样本只有一两百条的小规模二分类数据我一般先上逻辑回归。原因是特征少、任务简单、可解释性强逻辑回归的系数直接告诉你每个特征对风险的影响方向。之后再跑一个随机森林做对比验证是否存在明显的非线性关系。不要在这个规模的数据上直接堆深度学习模型复杂度和样本量不匹配解释成本还很高。训练代码如下from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression from sklearn.metrics import roc_auc_score 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_s scaler.fit_transform(X_train) X_test_s scaler.transform(X_test) model LogisticRegression(max_iter1000, class_weightbalanced) model.fit(X_train_s, y_train) prob model.predict_proba(X_test_s)[:, 1] print(AUC:, roc_auc_score(y_test, prob))逻辑说明train_test_split里的stratifyy保证训练集和测试集的类别比例一致避免测试集全是健康样本导致评估失真StandardScaler只允许在训练集上fit再transform测试集这是防止数据泄漏的基本纪律。class_weightbalanced让模型在正负样本不平衡时不会一味倒向多数类。predict_proba返回的是概率而不是硬分类界面程序里显示风险百分比正是靠这一行。参数说明max_iter1000是为了防止逻辑回归在特征缩放后迭代次数不够导致的收敛告警random_state42固定划分结果换别的值也可以但定了就不要动否则每次跑出来的模型都不一样。AUC跑到0.85以上说明这套特征和模型已经可以支撑界面开发了。2.3 保存模型与归一化器界面程序只认这一份契约界面程序不会每次启动都重新训练而是在启动时加载训练好的文件。需要保存的对象不只是模型还有scaler常见做法是用joblibimport joblib joblib.dump(model, heart_model.pkl) joblib.dump(scaler, scaler.pkl) with open(feature_order.txt, w, encodingutf-8) as f: f.write(\n.join(X.columns))逻辑说明界面输入的是原始体检值而模型训练时看到的是标准化后的数值所以每次预测前必须用同一个scaler.transform否则特征尺度全面错位预测结果会非常不稳定。我有一个血泪教训早期只保存了模型忘了归一化器界面点什么都输出同样的概率排查半天才发现是这一步缺失。特征顺序同理训练时列顺序是年龄、性别、胸痛类型……界面组装数组就必须一字不差。参数说明joblib是sklearn官方的序列化方案能处理numpy数组这些对象换机器部署时pkl文件一般直接可用但如果Python大版本差异太大反序列化可能报错需要在目标机器上重新生成一次。模型、归一化器、特征顺序这三样东西必须绑定保存后续做图形界面时只认这份契约。注意heart_model.pkl、scaler.pkl、feature_order.txt三个文件建议放在项目根目录界面程序加载时统一用同一个路径变量避免散落各处导致路径混乱。3. 用 PyQt5 搭出可录入的表单界面布局与信号槽模型文件准备好了接下来搭界面。PyQt5的窗口程序核心就三件事放控件、连信号、写槽函数。许多新手拿到源码包直接看main.py里的一大段UI代码会发懵其实拆开看就是布局加信号槽。这一章按三步走完准备环境、搭表单、把按钮和响应函数连起来。3.1 环境准备与最简窗口先补齐运行环境。机器上先有Python 3环境没有的话先从官网下载安装然后在干净虚拟环境里执行pip install pyqt5。机器上如果同时有PyQt5和PyQt6会让老代码更麻烦所以这类源码项目最好独立环境运行。验证环境最快的方式是把一个最小窗口跑起来import sys from PyQt5.QtWidgets import QApplication, QWidget app QApplication(sys.argv) window QWidget() window.setWindowTitle(心脏病预测系统) window.resize(720, 640) window.show() sys.exit(app.exec_())逻辑说明QApplication是每个PyQt5程序都需要的应用对象负责管理事件循环QWidget是最基础的空窗口所有控件都要放在它里面。exec_()进入事件循环后窗口才会保持显示并响应点击和键盘事件sys.exit把程序退出码交给操作系统。老代码里常见exec_()写法是为了兼容Python 2时代的保留字问题新代码写成exec()也可以两种写法遇到时不用觉得奇怪。参数说明resize(720, 640)是窗口初始尺寸对13个行项目的表单刚好够用setWindowTitle设置标题栏文字。如果之后界面上中文显示成方块按第5章的方法设置字体即可这个最小窗口阶段就能提前发现字体问题。3.2 用分组表单把13个特征排布清楚界面设计上我习惯用QGroupBox做一个「体检指标录入」分组里面用QFormLayout排两列表单左边字段名右边输入控件。数值字段用QSpinBox或QDoubleSpinBox类别字段用QComboBox下拉选择。下面是一段可以运行的最小表单代码from PyQt5.QtWidgets import ( QWidget, QGroupBox, QFormLayout, QSpinBox, QDoubleSpinBox, QComboBox, QPushButton, QLabel, QVBoxLayout ) class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle(心脏病预测系统) self.resize(720, 640) self._build_ui() def _build_ui(self): root QVBoxLayout(self) group QGroupBox(体检指标录入) form QFormLayout(group) self.age QSpinBox() self.age.setRange(20, 100) self.age.setValue(50) form.addRow(年龄:, self.age) self.sex QComboBox() self.sex.addItems([女, 男]) form.addRow(性别:, self.sex) self.trestbps QSpinBox() self.trestbps.setRange(90, 200) self.trestbps.setValue(120) form.addRow(静息血压:, self.trestbps) self.chol QSpinBox() self.chol.setRange(120, 400) self.chol.setValue(200) form.addRow(血清胆固醇:, self.chol) self.oldpeak QDoubleSpinBox() self.oldpeak.setRange(0, 6) self.oldpeak.setSingleStep(0.1) form.addRow(ST段压低:, self.oldpeak) root.addWidget(group) self.result QLabel(等待输入) self.result.setWordWrap(True) root.addWidget(self.result) self.btn QPushButton(开始预测) root.addWidget(self.btn)逻辑说明QVBoxLayout把分组框、结果标签、按钮按垂直方向依次排布每个输入控件都存成self.age这样的实例属性为的是槽函数里能直接取值。QComboBox的下拉选项顺序是有意义的它对应后续模型输入里的整数编码性别是「女」在位置0、「男」在位置1那一栏进模型的值就是currentIndex()。参数说明setRange限制控件可输入范围setValue设置默认值。这里的90到200只是控件允许范围不等于业务校验规则是否真正合理要在第4章的validate里再拦一道。QDoubleSpinBox的setSingleStep(0.1)决定点上下箭头时数值步长对oldpeak这类连续值比较友好。setWordWrap(True)让结果文字超长时自动换行不会把窗口撑破。3.3 信号槽点按钮之后程序怎么响应只有控件没有联动窗口是死的。PyQt5的联动机制是信号槽控件在某种交互发生时发出信号connect把信号接到一个Python方法上这个方法就是槽。最小接线是这样self.btn.clicked.connect(self.on_predict)逻辑说明clicked是QPushButton被按下并抬起后发出的信号on_predict是我们稍后要写的槽函数。这一行就是「点击按钮→收集输入→预测→显示结果」整个事件链的入口。如果你想在界面里展示模型介绍或参考范围用QLabel显示纯文本就够PyQt5的QTextBrowser也能显示HTML但这个项目里表单加结果标签已经足够不需要引入额外的HTML展示。参数说明clicked信号连接时可以带参数也可以连接lambda表达式但建议直接连接具名函数方便复用和调试。如果你把类定义存在main.py里直接运行记得在模块底部补一个PyQt5程序的标准入口if __name__ __main__: app QApplication(sys.argv) window MainWindow() window.show() sys.exit(app.exec_())不加这个入口时双击运行会看到「程序已退出代码0」但没有任何窗口弹出来这是新手最容易忽略的一步。3.4 界面与模型对接前先把13个字段收集全前面只写了6个输入项剩余胸痛类型、空腹血糖、静息心电图、最大心率、运动心绞痛、ST段斜率、主要血管数、地中海贫血8个字段需要补齐。写法和前面同理这里列出几个关键字段的设计self.cp QComboBox() self.cp.addItems([无症状, 典型心绞痛, 非典型心绞痛, 其他]) form.addRow(胸痛类型:, self.cp) self.fbs QComboBox() self.fbs.addItems([否, 是]) form.addRow(空腹血糖120mg/dl:, self.fbs) self.thalach QSpinBox() self.thalach.setRange(60, 220) form.addRow(最大心率:, self.thalach) self.exang QComboBox() self.exang.addItems([否, 是]) form.addRow(运动诱发心绞痛:, self.exang)逻辑说明胸痛类型的四个选项对应编码0到3空腹血糖和运动心绞痛是二选一字段选项「否」「是」对应编码0和1最大心率用QSpinBox范围60到220。这里最容易踩的坑是显示文本和数值编码混淆currentText()是给人看的currentIndex()是给模型吃的。如果训练数据里的cp已经是0、1、2、3这样的整数直接用currentIndex()就能对齐。参数说明如果原始数据是英文字符串而不是整数编码就必须在模型训练前处理成整数或者在界面层做一次显示文本到数值的映射表不能想当然地往下传。这一节做完窗口的表单部分就完整了接下来是把窗口和模型用信号槽真正串起来。4. 把模型接进界面特征顺序、归一化与预测逻辑窗口和模型文件都有了剩下的接线是最关键的一步。预测功能本质是一个槽函数从控件取值、按训练时的特征顺序组装数组、用scaler标准化、让model输出概率、把结果写回标签。常见做法是把模型和scaler的加载放在MainWindow初始化里只做一次而不是每次点按钮都重新加载。4.1 槽函数里完成取值、变换、预测核心逻辑如下这是在MainWindow类里新增的方法import joblib import numpy as np from PyQt5.QtWidgets import QMessageBox class MainWindow(QWidget): def __init__(self): super().__init__() # 界面构建代码见第3章 self.model joblib.load(heart_model.pkl) self.scaler joblib.load(scaler.pkl) def on_predict(self): if not self.validate(): return try: sex 1 if self.sex.currentIndex() 1 else 0 features [ self.age.value(), sex, self.cp.currentIndex(), self.trestbps.value(), self.chol.value(), self.fbs.currentIndex(), self.restecg.currentIndex(), self.thalach.value(), self.exang.currentIndex(), self.oldpeak.value(), self.slope.currentIndex(), self.ca.value(), self.thal.currentIndex(), ] arr np.array([features], dtypefloat) arr_s self.scaler.transform(arr) prob self.model.predict_proba(arr_s)[0, 1] self.result.setText(f患病风险概率: {prob:.1%}) except Exception as e: QMessageBox.critical(self, 预测失败, str(e))逻辑说明features列表的顺序就是训练时的列顺序这一点是整个程序中最容易翻车的地方建议把训练脚本里的X.columns和这里的列表逐项对照一遍。性别不要直接把currentText()传给模型要先转成整数。arr必须reshape成(1, 13)二维结构因为sklearn的transform和predict都要求二维输入。predict_proba返回的是一个(1, 2)数组第0列是健康概率第1列是患病概率取[0, 1]得到正类风险值。参数说明模型和scaler默认加载当前工作目录下的pkl文件但如果程序从别的路径启动这里会报FileNotFoundError。建议在文件头部把路径改成绝对路径写法见第5.5节。如果第3章的界面里还没有定义self.cp、self.fbs这些控件请按3.4的描述补齐后再运行。显示结果用%.1%0.526会被格式化成52.6%比显示小数更符合医疗场景的阅读习惯。4.2 评估模型不能只看准确率阈值才是可调旋钮界面接好后下一步是验证可信度。常见做法是把模型拿到测试集上做全面评估from sklearn.metrics import confusion_matrix, classification_report y_pred (model.predict_proba(X_test_s)[:, 1] 0.5).astype(int) print(confusion_matrix(y_test, y_pred)) print(classification_report(y_test, y_pred))逻辑说明confusion_matrix输出四格表分别对应真正例、假正例、假负例、真负例classification_report输出precision、recall、F1。对于心脏病筛查场景假负例比假正例更危险漏掉一个病人比让一个健康人多做一次复查代价大所以如果recall偏低可以把阈值从0.5降到0.4界面里显示的就不再是0/1而是连续概率。参数说明如果AUC和classification_report不稳定先检查标准化是否只在训练集fit过、特征顺序是否错位不要急着换模型。想对比树模型时用RandomForestClassifier(n_estimators200, max_depth6, class_weightbalanced)重跑同一个测试集比较两边的AUC和recall再决定用哪个。如果你要把阈值做成界面可调项只需在MainWindow里加一个QDoubleSpinBox范围0.1到0.9在on_predict里把阈值判断改成self.threshold.value()就可以改动成本不大但演示效果提升明显。4.3 界面校验别全靠控件range业务规则再拦一道QSpinBox的setRange只管“能不能输入这个数”不代表这个数适合做预测。血压输90能通过控件下限但如果数据集里几乎没有90以下的样本模型就是在训练分布之外强行外推结果不可信。所以我在on_predict第一行调用validate()给业务规则单独设一道防线def validate(self): if self.trestbps.value() 90 or self.trestbps.value() 220: QMessageBox.warning(self, 输入异常, 静息血压建议范围 90-220 mmHg) return False if self.chol.value() 120 or self.chol.value() 550: QMessageBox.warning(self, 输入异常, 血清胆固醇建议范围 120-550 mg/dl) return False return True逻辑说明这套校验范围不是拍脑袋定的而是参照公开数据集的分布区间后留出余量让绝大多数真实样本能通过、明显异常的值会被拦住。模型本身没有能力拒绝异常输入它只会照算所以这个责任必须由界面承担。validate返回False时函数直接return不进入预测流程这和弹出一串错误后继续执行相比交互更干净。参数说明范围上下限建议写进一个常量区而不是散落在函数里后续调整阈值时不用满文件找。界面底部可以顺手显示这些建议范围让使用者在录入时就心里有数。提示预测结果属于概率估计用于学习和演示不建议直接作为临床判断依据。5. 避坑指南这类源码项目最常翻车的5个地方源码项目之所以让新手头疼是因为报错信息往往藏得很深。这一章整理我实际带项目时最常看到的5个典型问题每条按现象、原因、解决三步讲遇到类似情况可以直接对号入座。5.1 双击py文件没反应或一闪而过现象解压源码后双击main.py黑窗口闪一下就不见了或者什么反应都没有。原因这种项目本来就不是给双击准备的Windows默认用关联程序打开.py没安装Python或环境变量没配好时等于没执行即使执行了脚本里的异常会随窗口关闭一起消失你根本看不到报错内容。解决打开命令行cd到源码目录运行python main.py让所有报错停在屏幕上python main.py逻辑说明如果提示python不是内部或外部命令说明Python没装好或没加入PATH装Python时勾选Add Python to PATH即可解决。如果命令行里能看到Traceback就把报错信息贴到搜索引擎大多数情况下比乱改代码有效得多。我自己排查这类源码从来不在图形界面里猜第一步永远是命令行运行拿完整堆栈。5.2 PyQt5相关AttributeError版本差异造成的API漂移现象程序运行到一半报错例如AttributeError: module PyQt5.QtWidgets has no attribute QAction或者类似exec相关的属性找不到。原因PyQt5在5.14前后有API调整部分类在新版本里改了归属更常见的是机器上装了PyQt6PyQt6把QAction挪到了QtGui模块老代码还按PyQt5的路径导入自然找不到。解决先确认当前环境装的是什么pip show pyqt5逻辑说明如果显示的是PyQt6建议建一个虚拟环境重装pip install pyqt5。代码层面尽量写明确导入路径比如from PyQt5.QtGui import QAction不要依赖星号导入的命名空间。看到一个AttributeError先看模块名对不对再看版本号这两步能省下大量排查时间。5.3 预测结果永远偏向某一类先查特征顺序和归一化现象界面一切正常但无论怎么填输出概率始终低于30%或始终高于70%几乎不随输入变化。原因这类问题看似玄学其实九成出在两个地方。第一训练数据类别不平衡模型学会了「全部预测多数类也能拿到高准确率」的偷懒策略第二界面组装features的顺序和训练时列顺序不一致模型接收到的数值含义错位第三scaler没有加载或者加载的是另一个模型配套的scaler。解决按三条顺序排查。先在训练脚本里打印X.columns和界面features列表逐项核对再确认界面加载的pkl文件确实来自本次训练的model和scaler最后检查训练脚本里有没有class_weightbalanced。把这三件事做一遍这类问题基本能定位。5.4 界面中文全部变成方块字体与编码问题现象按钮、标签上的中文显示为方块英文和数字正常。原因PyQt5默认字体在部分Windows环境下对中文没有合理回退另一个常见原因是源码文件保存成了gbk编码Python 3虽然默认utf-8但从老项目里拷来的文件可能不是。解决源码文件统一另存为utf-8然后在QApplication创建后设置全局字体from PyQt5.QtGui import QFont app QApplication(sys.argv) app.setFont(QFont(Microsoft YaHei, 10))逻辑说明设置之后标题栏、按钮、标签会一起生效这是我最常用也最稳定的一条修法。如果仍然有问题再检查操作系统是否缺少中文字体装一个微软雅黑即可。参数说明QFont的第二个参数是字号10到11比较适合表单界面字号太大布局会被撑开太小在高分屏上会显得吃力。5.5 打包后模型文件找不到路径处理要懂sys._MEIPASS现象源码运行正常用PyInstaller打包成单文件exe后一运行就报FileNotFoundError: heart_model.pkl。原因单文件模式打包后exe运行时会把内部资源解压到临时目录当前工作目录并不是exe所在目录相对路径自然找不到模型文件。解决不要用裸文件名加载改成兼容两种模式的路径写法import os import sys BASE_DIR getattr(sys, _MEIPASS, os.path.dirname(os.path.abspath(__file__))) model_path os.path.join(BASE_DIR, heart_model.pkl) self.model joblib.load(model_path)逻辑说明getattr(sys, _MEIPASS, ...)是PyInstaller资源路径的标准处理方式。打包成单文件时资源在临时目录sys._MEIPASS指向它源码运行时这个属性不存在就退回脚本所在目录。scaler.pkl同样处理这一条能解决九成打包后无法启动的问题。6. 进阶打包、调参与让预测结果更可信6.1 用 PyInstaller 打包成免 Python 环境的 exe要把这套东西拿去给别人演示常见做法是打包成可执行文件。装好pyinstaller后执行pyinstaller -w -F main.py --hidden-import sklearn --hidden-import joblib逻辑说明-w去掉控制台窗口-F合并成单文件。sklearn和joblib在动态导入时经常不被PyInstaller识别不加hidden-importexe会在import处崩溃且报错不明显。参数说明打包完后模型文件可以放在exe同目录也可以按5.5节的方式打进包里。路径处理永远是打包环节最大的坑这一步做对了再看图标和体积优化才是有效果的。6.2 调参顺序先在数据侧动手再谈模型参数界面稳定后别急着堆参数。我的习惯顺序是先看类别分布和特征分布再调class_weight再看逻辑回归的C值最后才动随机森林的n_estimators和max_depth。比如C0.1能给逻辑回归更强的正则化n_estimators200和max_depth6对一百多条样本已经是够用的配置森林越大不一定越好。每次只改一个参数重新训练并对比AUC改完一个再动下一个。这个习惯帮我避开了很多调参调完还不如baseline的翻车场面也推荐你试试。6.3 界面上的两个可信度细节第一个细节是把模型版本、训练日期、特征清单显示在界面角落防止换错模型文件后拿着旧结果做演示。第二个细节是结果不要只写「有风险/无风险」而是同时展示概率和阈值。我现在做这类工具都会加一句「本结果仅供参考不构成医疗诊断」成本极低但让用户明白这是一个风险评估工具而不是诊断结论反而让演示更可信。走完打包这一步这条「训练模型→做成桌面工具→交付运行」的完整链路你就掌握了。后面换成别的预测主题改数据、改表单、改标签就能复用到新项目上希望帮到你。本文还有配套的精品资源点击获取