☰
ECG心电二分类实战:从MIT-BIH数据预处理到机器学习模型训练全流程
2026/10/1 17:59:19 网站建设 项目流程

简介:这是一份面向医疗AI入门者与信号处理开发者的心电信号二分类代码包,用于区分正常与异常心电波形。包体共5个文件,包含2个Python脚本(分别对应数据加载、特征提取与分类主流程)、1个Markdown说明文档、1个开源许可证及1个gitignore配置文件,压缩包整体仅3KB,轻量精简,适合快速阅读与二次开发。已有268人浏览学习。项目虽小,却覆盖了心电分类的关键环节:信号预处理、特征提取(如RR间期、PQRST波形参数)、二分类模型训练与评估,并可通过MIT-BIH等公开数据集进行验证。代码结构清晰,对理解交叉验证、超参数调优以及心电标准波形识别均有参考价值,可直接作为课题原型或入门练习,帮助开发者快速上手心电数据处理与机器学习分类任务。

1. 心电分类没想象中那么玄:一个能跑的 ECG 二分类项目,从 data.py 到 run.py

心电分类(ECG classification)在医疗 AI 里是高频需求,但一落到“正常 / 异常”二分类,很多团队习惯性上深度学习,结果被数据量、标注质量和类别不平衡反复折磨。这个项目是个反例:它用传统机器学习路径走完了完整流程——data.py 负责把 MIT-BIH 心律失常数据库的原始信号切成固定长度的心拍窗口,run.py 负责特征工程、模型训练和评估,用 SVM、随机森林甚至 XGBoost 二分类模型就能得到可解释的基线。我完整复现过一遍,从零到出混淆矩阵大约一个下午。和早期 MATLAB 原型相比,仓库里的 Python 版本更贴近工程部署,适合刚接触心电处理的算法工程师,也适合需要在标注数据上快速建立基线的医疗 AI 团队。

2. data.py 拆解:把 MIT-BIH 原始心电切成能训练的心拍样本

data.py 虽然只负责数据预处理,但它决定了后面所有环节能不能成立。跑之前先把依赖装齐:wfdb 用来读 MIT-BIH 数据,scipy 做滤波和峰值检测,pywt 做小波分解,scikit-learn 和 xgboost 负责模型部分。这个仓库的文件结构很直接:README.md 说明整体思路,data.py 管数据,run.py 管训练和评估。拿到数据的第一件事不是写模型,而是先把 data.py 的输出日志跑出来,确认心拍数量和标注数量对得上。

2.1 数据源与标注:record、annotation 和采样范围如何配对

MIT-BIH 心律失常数据库是公开数据集,几乎所有 ECG 二分类研究都用它做基准。它不是一张 CSV 表——每条记录由 .dat、.hea、.atr 三个文件组成,分别存原始信号、参数头文件和医生标注,双导联采样率 360Hz。读取时常见做法是用 wfdb 库,一条记录对应一个编号,比如 101 号:

import wfdb import numpy as np # 读取101号记录前5000个采样点;sampfrom/sampto单位是采样点,不是秒 record, fields = wfdb.rdsamp('101', sampfrom=0, sampto=5000) # 医生标注在atr文件里,必须和信号用相同的采样范围 annotation = wfdb.rdann('101', 'atr', sampfrom=0, sampto=5000) fs = fields['fs'] # 采样率,MIT-BIH通常为360Hz print('signal shape:', record.shape) # (采样点数, 导联数) print('first symbols:', annotation.symbol[:20])

逻辑说明:rdsamp 和 rdann 必须用相同的 sampfrom/sampto,否则信号和标注错位,后面分割出来的标签全部对不上。record 返回二维数组,MIT-BIH 通常是两导联,实际使用中取第一导联(MLII)作为主信号就够。annotation.symbol 里记录了每个心拍的类型,常见的有 N(正常)、V(室早)、L(左束支阻滞)、R(右束支阻滞)等。做“正常 vs 异常”二分类时,把 N 归为阴性,其余所有非正常类型合并为阳性。这个合并决定了两件事:一是标签空间变简单了,二是正常与异常的比例关系直接被确定。MIT-BIH 里正常心拍通常占 80% 以上,不处理这个不平衡,后面模型大概率退化成“全部输出正常”。

参数这里有一个高频误解:sampto 不是秒,是采样点索引。想读前 10 秒的数据,360Hz 下应该写 sampto=3600;写成 sampto=10 只会读出 10 个点,分类器拿到这么少的数据什么也学不到。我拿到任何数据集都会先打印 shape 和标注数量,这个几十秒的检查能省掉后面一整个下午的排查。

2.2 R 峰检测与窗口切片:对齐比什么都重要

拿到原始信号后第一步是找 R 峰。R 峰是 QRS 波群中最陡峭的尖峰,几乎所有 ECG 分割都围绕它展开。常见做法是先用 uniform_filter1d 做轻度平滑,再用 scipy 的 find_peaks 找局部极大值,关键在于峰间最小距离和高度阈值:

from scipy.signal import find_peaks from scipy.ndimage import uniform_filter1d def detect_r_peaks(ecg_signal, fs, min_distance_ms=300): # 20ms窗口平滑,去掉高频毛刺但不改变R峰位置 smoothed = uniform_filter1d(ecg_signal.astype(float), size=int(fs * 0.02)) min_distance = int(fs * min_distance_ms / 1000) # 高度阈值用70分位数自适应,不同记录的幅值差异很大 threshold = np.percentile(smoothed, 70) peaks, _ = find_peaks(smoothed, distance=min_distance, height=threshold) return peaks

参数说明:min_distance_ms=300 对应心率上限 200bpm。设太小(比如 150ms)会把 T 波误检成 R 峰,因为部分异常心拍的 T 波幅度很夸张;设太大又会漏检。height 阈值不建议写死,MIT-BIH 不同记录的增益和幅值差异非常大,用 70 分位数自适应比固定阈值稳定得多。检测到 R 峰之后,以 R 峰为中心取前 0.25 秒、后 0.35 秒的窗口作为单个心拍,360Hz 下正好 216 个采样点:

def slice_beats(ecg_signal, r_peaks, fs, before=0.25, after=0.35): beats = [] for idx in r_peaks: start = idx - int(before * fs) end = idx + int(after * fs) if start < 0 or end > len(ecg_signal): continue beats.append(ecg_signal[start:end]) return np.array(beats)

窗口选 0.6 秒这件事有讲究。太短(0.3 秒)会把 P 波或 T 波截掉一半,特征不完整;太长(1 秒以上)会把相邻心拍带进来,给分类器增加无关噪声。如果后续要提取 QT 间期,窗口必须包含完整 T 波,after 至少留 0.35 秒。我一般用 before=0.25/after=0.35 这个组合,因为它在“尽量包含完整波形”和“避免拖入相邻心拍”之间是比较公认的平衡点。

2.3 滤波与基线漂移:0.5–45Hz 带通不是随便写的

ECG 原始信号里有三类干扰:呼吸造成的基线漂移(通常低于 0.5Hz)、肌电噪声(数十 Hz 以上)、工频干扰(50Hz 或 60Hz)。所以带通范围取 0.5-45Hz 在 ECG 处理里几乎是约定俗成。实现时最需要注意的是用零相位滤波,避免 R 峰位置被相位失真挪动:

from scipy.signal import butter, filtfilt def bandpass_filter(data, fs, low=0.5, high=45.0, order=2): b, a = butter(order, [low / (0.5 * fs), high / (0.5 * fs)], btype='band') # filtfilt是零相位滤波,R峰位置不会被偏移 return filtfilt(b, a, data)

order=2 就够用。初学者容易把 order 调到 5、6,以为滤波更干净,结果波形两端出现明显的振铃过冲,PQRST 被压出假峰。另一个常见的坑是直接用 lfilter 做 IIR 滤波,它会引入非线性相位,R 峰位置发生偏移,后续切割窗口全部错位——这个坑我踩过,排查了很久才发现问题出在滤波上。我建议的处理顺序是:先带通滤波,再检测 R 峰,最后切片。顺序反过来问题更大,切片后再滤波会在窗口边缘产生边界效应,白白丢失采样点。

2.4 数据保存与样本平衡

预处理完成后,把心拍数组和标签一起存成 npy 文件,run.py 可以反复读取,不用每次从头处理一遍:

np.save('beats_normal.npy', normal_beats) np.save('beats_abnormal.npy', abnormal_beats) np.save('labels.npy', labels)

如果异常类别占比太低(少于 10%),先统计各记录的类别分布再决定方案。我一般不做 SMOTE——ECG 的异常本身包含多种形态(室早、束支阻滞、节律异常),在特征空间插值很容易生成医学上不存在的波形组合,模型学到的规律是假的。更常见也更稳妥的做法是随机下采样,把训练集正常与异常的比例拉到 2:1 到 3:1,然后再交给分类器。如果这样 recall 还上不来,再考虑在模型层面加 class_weight。

2.5 数据增强:幅度扰动与时间偏移

心拍层面的数据增强不像图像那样花哨,但有两招很实用。一是对单个心拍做小幅度缩放,模拟采集设备的增益差异;二是在 R 峰位置左右平移 2-3 个采样点,模拟 QRS 检测的微小误差。这两步都能提升模型对采集差异的鲁棒性。注意平移幅度不要超过 5 个采样点,否则会制造出心电学上不存在的形态。

提示:增强只在训练集上做,验证集和测试集保持原始波形,否则评估指标会被“看过”的扰动形态污染。

3. 特征工程与模型选型:让二分类器真正看懂 PQRST 波形

心拍切片完成后,数据还是采样点序列。直接把 216 维原始数值丢给分类器不是不行,但泛化能力和可解释性都比较差。常见做法是把每个心拍压缩成一组特征:时域特征描述波形形态,小波或频域特征描述能量分布,再加上 RR 间期这类节律上下文。三者合起来,SVM、随机森林、XGBoost 这类传统模型才有足够的信息去区分正常与异常。理解 P 波、QRS 波群和 T 波是设计特征的前提,比如哪个特征对应 ST 段改变、哪个特征对应 QRS 变宽,心里要有数。

3.1 时域特征:RR 间期、QT 间期与 PQRST 幅度怎么提

时域特征最容易解释,对医生也最友好。围绕单个心拍,我通常提取五类数值:

def extract_time_features(beat, fs, rr_interval): duration = len(beat) / fs # 心拍时长(秒) qrs_amp = np.max(beat) - np.min(beat) # 主波峰谷差 energy = np.sum(beat ** 2) / len(beat) # 波形能量 zcr = np.sum(np.diff(np.sign(beat)) != 0) / len(beat) # 过零率 return { 'duration': duration, 'qrs_amp': qrs_amp, 'energy': energy, 'zero_cross_rate': zcr, 'rr_interval': rr_interval }

参数说明:qrs_amp 用整段窗口的最大最小差近似 QRS 幅度,只要窗口对齐 R 峰,这个值就是 R 波到 S 波谷的峰峰值。zero_cross_rate 偏低表示波形平滑、形态规整,偏高说明有额外高频成分或切进了干扰段。rr_interval 是当前心拍与前一个 R 峰的时间差,它是区分室早和正常窦性心律最重要的特征之一。特别要注意的是 rr_interval 必须在切片之前就算好,它属于节律上下文而不是单心拍本身,所以放进特征向量时要单独一列,别混在波形数值里。

3.2 频域与小波特征:用细节系数能量描述形态复杂度

单心拍只有 0.6 秒,直接做 FFT 的频率分辨率不够,更常用的是离散小波变换。db4 在 ECG 分析里用得最多,因为它和 QRS 波形态相近。4 层分解把信号分成低频近似加 4 层高频细节,取各层细节系数的能量作为特征:

import pywt def extract_wavelet_features(beat, wavelet='db4', level=4): coeffs = pywt.wavedec(beat, wavelet, level=level) # coeffs[0]是近似分量,coeffs[1:]是各层细节分量 return [np.sum(c ** 2) / len(c) for c in coeffs[1:]]

得到的 4 个数值分别表示不同频段上的能量占比。比如 ST 段改变时低频细节能量会明显变化,室早时 QRS 变宽会让中频段能量分布改变。这类特征不如时域特征直观,但配合后面的特征重要性分析,能帮你确认模型究竟在用哪些信号成分做判断。如果模型给某个细节系数的权重异常高,而临床上看不到对应波形特征,就需要怀疑是不是数据管道里有 bug。

3.3 特征拼接与维度检查

所有特征算完后拼成一行的向量。比如时域 5 维加小波 4 维,再加上 RR 间期上下文,每个心拍约 10 维特征。保存成二维数组,shape 是(样本数, 特征数)。拼接后第一件事是检查列尺度:

import numpy as np # X_all shape: (n_samples, n_features) print('X_all shape:', X_all.shape) print('per-column max:', X_all.max(axis=0)) print('per-column min:', X_all.min(axis=0))

如果某列数值从 0.01 到 0.05,另一列从 0 到上百万,那么 SVM 这类距离模型会被大数值列主导,很小的真实信号被淹没。项目里的常见做法是统一过 StandardScaler,但一定要放在 Pipeline 里,在划分数据之后再 fit。先全局标准化再切分数据集,等于让 scaler 偷看了验证集的统计量,属于一种隐式数据泄漏。特征数量本身不用贪多,10 到 20 维对二分类已经足够,刻意堆到上百维反而容易过拟合。

3.4 模型选型:SVM、随机森林还是 XGBoost 二分类模型

特征矩阵就绪后进入模型选择。项目只有一个 run.py,设计意图就是在脚本里快速切换模型对比。按我的心电任务经验,三个模型的取舍大致如下:

模型需要标准化对异常值鲁棒性小样本表现调参成本
SVM(RBF)必须差好中
随机森林不需要好中低
XGBoost不需要好依赖数据量高

SVM 在小样本上最稳,但不做缩放基本没法用;随机森林对脏数据容忍度高,几乎不需要预处理参数;XGBoost 二分类模型在数据量上来后精度上限最高,代价是参数组合多。三个模型的常用配置如下:

from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.svm import SVC from sklearn.ensemble import RandomForestClassifier from xgboost import XGBClassifier models = { 'svm': Pipeline([ ('scaler', StandardScaler()), ('clf', SVC(kernel='rbf', C=1.0, probability=True, class_weight='balanced', random_state=42)) ]), 'rf': RandomForestClassifier(n_estimators=200, max_depth=8, class_weight='balanced', random_state=42), 'xgb': XGBClassifier(n_estimators=200, max_depth=4, learning_rate=0.1, eval_metric='logloss', random_state=42) }

参数说明:SVC 的 probability=True 是为了后面画 ROC 曲线,代价是训练稍慢;class_weight='balanced' 让模型自动补偿正常与异常样本数量差异。XGBoost 的 eval_metric='logloss' 对应的就是二分类交叉熵损失,也就是常说的 BCE(Binary Cross Entropy);如果你换成神经网络,默认损失函数还是它。我的习惯是先用三个模型默认参数各跑一轮交叉验证,选最好的那个再去网格搜索调参,而不是一上来就调参数。这样能最快确认特征工程是不是有效——如果三个模型都很差,大概率问题出在特征提取而不是模型上。

4. run.py 主流程:训练管道、评估指标与分组交叉验证

run.py 是项目入口。data.py 产出的 npy 文件在这里加载,然后完成特征工程、训练、评估和可视化。我熟悉的流程是:先搭好 Pipeline,再塞进交叉验证看指标,最后用完整训练集重新训练并保存模型。先评估后训练,能避免在测试集上调参这种坏习惯。

4.1 训练管道怎么搭

特征矩阵 X 和标签 y 准备好后,放进 Pipeline 把标准化和分类器串起来。这样做的最大好处是交叉验证时 scaler 只会在训练折上拟合,不会偷看验证折的统计量:

from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from xgboost import XGBClassifier X = np.load('features.npy') y = np.load('labels.npy') pipeline = Pipeline([ ('scaler', StandardScaler()), ('clf', XGBClassifier(n_estimators=200, max_depth=4, eval_metric='logloss', random_state=42)) ])

逻辑说明:Pipeline 每一步实现 fit/transform,交叉验证时 sklearn 只对训练折调用 fit,对验证折调用 transform,所以 scaler 的均值和方差全部来自训练折,评估结果不会被污染。我见过不少项目先全局标准化再划分数据集,得到的准确率虚高,一上真实数据就崩。这个坑隐藏在代码顺序里,表面上看不出来。

模型训练完成后,用 joblib 把整个 Pipeline 保存下来,部署时直接加载:

import joblib joblib.dump(pipeline, 'ecg_classifier.joblib')

这一步别看简单,它能保证训练时用的预处理参数和部署时完全一致。很多上线的模型“效果不稳定”,追根溯源是部署脚本里重新写了预处理,写法和训练时对不上。

4.2 评估指标:准确率会骗人,recall 和 F1 才是重点

ECG 二分类天然不平衡,正常心拍占 70%-90%。此时准确率基本没有参考价值——假设 90% 正常,一个无脑模型全输出正常就有 90% 准确率,但它一个异常都抓不到。评估至少要看 recall、precision、F1,最好再加 ROC-AUC:

from sklearn.model_selection import cross_val_predict from sklearn.metrics import classification_report, roc_auc_score y_pred = cross_val_predict(pipeline, X, y, cv=5) print(classification_report(y, y_pred, target_names=['normal', 'abnormal'])) print('ROC-AUC:', roc_auc_score(y, y_pred))

代码说明:cross_val_predict 返回每个样本在“被当验证样本”时的预测,相当于把 5 折验证的预测拼起来,用这个结果算指标是公平的。在医疗场景里,漏诊比误报严重得多,所以重点盯 abnormal 类别的 recall——它表示异常心拍里有多少被正确抓出来。如果 recall 低于正常类别的 precision,说明模型倾向于保守,宁可漏掉也不误报,这在临床上通常不可接受。

4.3 交叉验证按病人分折而不是按样本分折

这是心电分类里最重要的一条工程经验,也是这个项目让我觉得最值钱的地方:同一个病人的心拍之间高度相关,心率、体态、电极位置全一致。如果按样本随机分折,同一个人的心拍会同时出现在训练集和测试集,模型实际在记病人身份而不是病理特征,测试分数虚高。正确做法是按病人编号分组:

from sklearn.model_selection import GroupKFold # patient_ids 长度等于 X.shape[0],标记每个样本来自哪位病人 gkf = GroupKFold(n_splits=5) for train_idx, val_idx in gkf.split(X, y, groups=patient_ids): X_train, X_val = X[train_idx], X[val_idx] y_train, y_val = y[train_idx], y[val_idx] # 每折在这个循环里独立训练和验证

参数说明:n_splits=5 表示折数,groups 传入每个样本的病人编号,同一编号的样本永远不会被拆到两个不同的折里。我第一次跑 ECG 项目时,用随机 KFold 拿到 95% 准确率,换成 GroupKFold 直接掉到 78%,差距就是数据泄漏的“水分”。拿到项目数据之后,第一件事就应该是给每条心拍打上病人 ID 标签。如果没有这个标签,无法做分组交叉验证,模型的泛化能力就永远是黑匣子。

4.4 混淆矩阵可视化

评估完数字指标,再画混淆矩阵。这一步不只是为了配图,更重要的是定位模型错在哪——把异常分成正常,在临床上就是漏诊:

import matplotlib.pyplot as plt from sklearn.metrics import ConfusionMatrixDisplay ConfusionMatrixDisplay.from_predictions( y, y_pred, display_labels=['normal', 'abnormal'] ) plt.title('ECG binary classification confusion matrix') plt.savefig('confusion_matrix.png', dpi=150)

从矩阵里能直接读出两个数:假阴性(abnormal 被预测成 normal)和假阳性。做医疗 AI 时,假阴性的容忍度远低于假阳性,这个矩阵能帮你在训练结束后决定是否调整决策阈值。比如把分类概率阈值从 0.5 下调到 0.4,可能用 10% 的假阳性换回一倍的假阴性,对临床场景往往是划算的。

5. 避坑与常见问题:ECG 二分类里不跑一遍发现不了的问题

这个项目藏得最深的坑,往往不在“跑不起来”,而在“跑起来但结果不可信”。我把复现时踩过的记录按“现象、原因、解决”列出来,每一条都对应真实的翻车经历。

5.1 测试集准确率很高,但一上真实数据就崩

现象:交叉验证 F1 有 0.9,部署到新采集设备的数据上立即崩盘。原因:最常见的是按样本随机划分造成的数据泄漏。同一个病人的心拍被分到训练集和测试集,模型记住了病人特征而非病理特征,比如电极贴的位置、个体的心率变异性,换一个病人全部失效。解决:改用 GroupKFold 按病人分折;同时把滤波和标准化全部放进 Pipeline,均值和方差只从训练折统计,从源头堵住泄漏。

5.2 R 峰检测把 T 波当成 R 峰

现象:切出来的心拍窗口里有两条接近等高的尖峰,形态完全不是正常的单个 QRS。原因:min_distance_ms 设得太小,或者原始信号噪声过大。部分异常心拍的 T 波幅度接近甚至超过 R 峰,单纯的高度阈值判断失灵。解决:min_distance 至少 300ms;检测之前先过 0.5-45Hz 带通;仍误检的话加形态约束,比如峰高要超过邻峰一定比例、上升沿足够陡。用 annotation 里的 R 峰位置当 ground truth,计算检测器的召回率,低于 95% 就先不要进训练流程,否则所有标签都是歪的。

5.3 滤波后波形两端出现振铃过冲

现象:切片边缘出现一大一小的波动,幅度甚至盖过真实 QRS。原因:IIR 滤波器阶数太高(order>=5),加上直接用了 lfilter,相位失真叠加过冲。解决:换成 filtfilt 做零相位滤波,order 保持在 2-4。如果单独做带通仍有过冲,把信号边缘延拓几十个采样点再滤波,裁掉延拓部分。R 峰检测代码跑之前,一定要先把滤波后的波形画出来看一眼,不要只盯数值。画图这一两分钟能省掉很多无效排查。

5.4 训练 loss 正常,F1 却始终在 0.5 附近

现象:训练 loss 下降很快,验证集上的 F1 就是上不去。原因:类别极度不平衡,正常占 90%、异常占 10%,模型优化交叉熵时发现全输出正常也能得到很低的 loss——数学上完全合理,临床表现完全无用。解决:分类器加 class_weight='balanced';如果无效,在训练集做随机下采样,把正常与异常比例控制在 2:1 到 3:1。这会牺牲一点整体准确率,但 abnormal 类别的 recall 会显著上升。在医疗场景里,这才是真正要优化的目标。

5.5 读取数据时标注数量与 R 峰检测数量对不上

现象:annotation 里有 N 个心拍标签,find_peaks 只检测出 M 个峰,M 和 N 差不少。原因:annotation.symbol 里有些符号不表示心拍(比如“+”代表节律模式切换),同时部分噪声严重的段落在 R 峰检测中丢失。解决:先用 wfdb.rdann 的 symbol 过滤出 N、V、L、R 等真实心拍类别,再去检测 R 峰;比较检测位置和标注位置,若少量丢检就下调 height 阈值;大量丢失说明该段信号噪声过大,直接跳过不要硬算。数据质量是预处理的一部分,不是加载之后再处理的事。

6. 进阶用法:用特征重要性和错误样本反查模型到底在看什么

6.1 特征重要性验证与错分样本回查

二分类跑通只是第一步。在医疗场景里,你随时要回答“模型凭什么判断这是异常”。随机森林和 XGBoost 自带 feature_importances_,但它反映的是训练时的平均增益,容易被高基数特征带偏。我更推荐 permutation importance,直接在验证集上做,直观且不容易走样:

from sklearn.inspection import permutation_importance # n_repeats=10 表示每个特征打乱10次,观察指标平均下降多少 result = permutation_importance(pipeline, X_val, y_val, n_repeats=10, random_state=42) sorted_idx = result.importances_mean.argsort()[::-1] for i in sorted_idx: print(f"{feature_names[i]:20s}: {result.importances_mean[i]:.4f}")

逻辑说明:逐一打乱某个特征的值,指标下降越多,模型对这个特征依赖越强。如果排序靠前的是 rr_interval、qrs_amp,说明模型学到的东西与心电学常识一致;如果排最前面的是某个编号类的特征,就要回头检查数据管道是不是混进了泄漏变量。这是验证模型可信度最直接的方式,比看 AUC 数字有说服力得多。

另一个很有价值的验证手段是错分样本回查。取出所有假阴性样本,把原始波形画出来对照标注,你会发现很多有意思的规律。我之前的项目里,模型几乎漏掉所有“早搏后第一个窦性心拍”,原因是这类心拍的 RR 间期被前一个早搏改变,特征向量偏离了正常形态。这种问题不把波形画出来根本发现不了,靠调参也调不出来。

从那次以后,我每次跑完心电模型,第一步不是看准确率,而是先把特征重要性和错分样本波形打印出来,确认模型“看”的东西对得上临床常识,再决定调不调参。这个习惯帮我避免了好几次在错误模型上反复调参的浪费。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询