简介:这份资源围绕LSTM-GAN生成逼真ECG信号展开,面向具备Python与深度学习基础、关注生物医学信号处理与数据增强的研究者和开发者。项目以长短期记忆网络捕捉心电信号的周期性与波形模式,配合生成器与判别器的对抗训练,产出可用于异常检测测试或扩充训练集的合成心电数据。压缩包共13个文件,约4.46MB,包含5个py脚本、3张png结果图、2个h5权重文件,以及1个ipynb交互式笔记、1个md说明和1个gitignore,覆盖模型定义、训练、噪声生成与数据扩展等环节。已有313人学习下载。读者可据此理解LSTM-GAN在序列建模中的实现思路,参考生成器与判别器的权重保存方式,并借助可视化图片对比真假信号,快速复现实验或迁移到其他时序生成任务。
1. 拆开这份 LSTM-GAN 心电图生成包:它到底能跑出什么
第一次拿到用于生成似是而非的ECG信号的LSTM-GAN_Jupyter Notebook_Python_下载.zip时,我脑子里冒出的不是论文里的公式,而是一个很实际的问题:如果手上只有几十条真实 ECG 记录,能不能靠它扩出几百条“看起来像那么回事”的波形,去喂给异常检测模型做数据增强。这个包正好就是干这个的——用 LSTM 当生成器骨架,用 GAN 的对抗框架逼着生成器吐出形态逼真的心电信号。它适合两类人:一类是做生理信号深度学习、想快速验证生成数据是否管用的研究者;另一类是刚学完 Python 和 Jupyter Notebook、想找一个能跑通的序列生成项目练手的工程师。包里已经带了generator_80e.h5和discriminator_80e.h5两个训练了 80 轮的权重,意味着你不必从零开始等 GPU 烧几个小时,直接加载就能看生成效果。但“似是而非”这四个字很关键——它生成的是统计上像 ECG 的信号,不是医学上可诊断的 ECG,这一点后面会反复提到。
2. LSTM-GAN 生成 ECG 的骨架:生成器、判别器与序列建模
2.1 为什么是 LSTM 而不是普通全连接
ECG 信号的本质是一段随时间演化的电压序列,P 波、QRS 波群、T 波之间的间隔和形态都有强时序依赖。普通全连接网络把每个采样点当独立特征处理,会丢掉“前一个 R 峰之后多久出现 T 波”这类信息。LSTM 的遗忘门和输入门能记住长距离的节律模式,比如心率变异性带来的 RR 间期波动。在这个包里,生成器接收一段随机噪声向量,通过若干 LSTM 层逐步展开成固定长度的波形序列。常见做法是把噪声维度设成 100 左右,序列长度设成 256 或 512 个采样点,对应 2 到 4 秒的 ECG 片段。判别器同样用 LSTM 结构,但它做的是二分类:输入一段波形,输出它是真实 ECG 还是生成 ECG 的概率。两者交替训练,生成器努力骗过判别器,判别器努力不被骗。
2.2 包内文件分工与加载顺序
解压后先别急着点ecgGAN.ipynb里的全部运行。我一般会按这个顺序摸清结构:
# 查看包内文件树,确认权重和脚本都在 find ecgGAN-master -maxdepth 2 -type f | sort你会看到model.py定义网络层,main.py是训练入口,gan-testing目录放测试脚本,noise_generator.py负责产生输入噪声,cleanup_ecg.py和expand_ecg.py处理原始数据。weights目录下的两个.h5文件是已经训练好的权重,images目录里generator.png和discriminator.png是网络结构图,generated_ecg.png是生成样本的预览。加载权重时注意 Keras 版本兼容性,老版本用keras.models.load_model直接读,新版本可能需要compile=False再手动编译。
# 加载已训练生成器并生成一段假 ECG from keras.models import load_model import numpy as np generator = load_model('weights/generator_80e.h5', compile=False) noise = np.random.normal(0, 1, (1, 100)) # 1 条样本,噪声维度 100 fake_ecg = generator.predict(noise) print(fake_ecg.shape) # 预期输出 (1, 256, 1) 或类似序列长度这段代码里compile=False是为了跳过优化器和损失函数的反序列化,避免版本不匹配报错。np.random.normal产生标准正态噪声,和训练时输入分布保持一致。输出形状取决于model.py里生成器最后一层的Dense单元数,通常是序列长度乘以通道数。
2.3 训练循环里的对抗节奏
main.py里的训练循环是典型的 GAN 交替更新。每个 batch 先取真实 ECG 片段,再生成等量假片段,判别器在两者上各算一次损失并更新权重。然后冻结判别器,生成器通过判别器的反馈更新自己。这里有个容易翻车的点:如果判别器太强,生成器梯度会消失,输出变成无意义的直线;如果判别器太弱,生成器会输出模式单一的波形。常见做法是训练判别器时用标签平滑,把真实样本标签从 1 改成 0.9,假样本标签从 0 改成 0.1,缓解判别器过度自信。
# 标签平滑示例,在 main.py 的 train_step 里替换硬标签 real_labels = np.ones((batch_size, 1)) * 0.9 fake_labels = np.zeros((batch_size, 1)) + 0.1 d_loss_real = discriminator.train_on_batch(real_ecg, real_labels) d_loss_fake = discriminator.train_on_batch(fake_ecg, fake_labels)参数batch_size在 ECG 场景下通常设 32 或 64,太大显存吃紧,太小梯度噪声大。学习率建议生成器和判别器都从 0.0002 起步,用 Adam 优化器,beta_1 设 0.5 而不是默认的 0.9,这是 DCGAN 留下的血泪经验,对稳定对抗训练很管用。
3. 从 Jupyter Notebook 到命令行:把生成流程跑通
3.1 环境准备与依赖安装
这个包基于 Python 和 Keras/TensorFlow,Jupyter Notebook 只是交互入口。如果你本地还没装 Python,建议直接用 Anaconda 建一个独立环境,避免和系统里的包打架。安装命令如下:
conda create -n ecggan python=3.8 conda activate ecggan pip install tensorflow==2.4.0 keras==2.4.3 numpy matplotlib jupyter选 Python 3.8 是因为 TensorFlow 2.4 对它的支持最稳,再新的版本可能和包里的旧 API 冲突。jupyter装好后,在ecgGAN-master目录下运行jupyter notebook,浏览器会自动打开文件列表,点ecgGAN.ipynb就能逐格执行。如果你习惯 VS Code,也可以直接打开.ipynb文件,选好内核后一样跑。
3.2 数据预处理:cleanup_ecg.py 做了什么
真实 ECG 数据往往带基线漂移、工频干扰和导联脱落造成的伪迹。cleanup_ecg.py一般会做三件事:去均值、带通滤波、按固定长度切片。去均值消除直流偏置,带通滤波保留 0.5 到 40 Hz 的有效成分,切片则把长记录切成等长片段方便批量训练。如果你用自己的数据,注意采样率要和包内默认值一致,常见是 360 Hz 或 500 Hz。采样率不匹配会导致生成的波形在时间轴上被拉伸或压缩,看起来“似是而非”但节律完全不对。
# 简易带通滤波示例,用 scipy 实现 from scipy.signal import butter, filtfilt def bandpass_filter(signal, lowcut=0.5, highcut=40.0, fs=360, order=4): nyq = 0.5 * fs low = lowcut / nyq high = highcut / nyq b, a = butter(order, [low, high], btype='band') return filtfilt(b, a, signal)filtfilt做零相位滤波,避免波形在时间上偏移。order=4是常用折中,阶数太高会引入振铃,太低则滤波不干净。处理完的数据存成.npy或.h5,训练脚本直接读。
3.3 用 expand_ecg.py 做数据增强
expand_ecg.py的用途是把少量真实样本扩增成更多训练片段。常见做法是加高斯白噪声、随机时间缩放、幅度扰动。注意增强后的数据仍然要保留 P-QRS-T 的基本形态,否则判别器学到的“真实”分布就被污染了。我一般会控制噪声标准差在原始信号幅度的 5% 以内,时间缩放范围在 0.9 到 1.1 倍之间。
# 对单条 ECG 做幅度扰动和时间缩放 import numpy as np from scipy.interpolate import interp1d def augment_ecg(ecg, noise_std=0.05, scale_range=(0.9, 1.1)): ecg = ecg + np.random.normal(0, noise_std * np.std(ecg), ecg.shape) scale = np.random.uniform(*scale_range) x_old = np.arange(len(ecg)) x_new = np.linspace(0, len(ecg) - 1, int(len(ecg) * scale)) f = interp1d(x_old, ecg, kind='linear', fill_value='extrapolate') return f(x_new)interp1d做线性插值,fill_value='extrapolate'防止边界外推报错。增强后的序列长度会变,后续要统一裁剪或填充到固定长度。
3.4 生成与评估:gan-testing 目录怎么用
gan-testing里通常有加载权重、生成样本、画对比图的脚本。跑通后你会得到类似generated_ecg.png的图,上面是真实 ECG,下面是生成 ECG。评估生成质量不能只看图,常见定量指标有 Fréchet Inception Distance 的变体,或者简单算生成样本和真实样本在频域上的功率谱差异。如果生成波形的 QRS 波群宽度明显偏离真实分布,说明 LSTM 没学到局部形态,需要增加层数或调整序列长度。
# 对比真实与生成 ECG 的功率谱 import matplotlib.pyplot as plt from scipy.signal import welch f_real, p_real = welch(real_ecg.flatten(), fs=360, nperseg=256) f_fake, p_fake = welch(fake_ecg.flatten(), fs=360, nperseg=256) plt.semilogy(f_real, p_real, label='real') plt.semilogy(f_fake, p_fake, label='generated') plt.legend() plt.show()welch做功率谱估计,nperseg=256控制频率分辨率。如果生成信号在高频段能量异常高,说明噪声没滤干净;低频段缺失则可能是 LSTM 遗忘了长程节律。
4. 避坑与排查:权重加载、模式崩溃与显存溢出
4.1 加载 .h5 权重报 “Unknown layer” 或 “bad marshal data”
现象:运行load_model('generator_80e.h5')时抛出ValueError: Unknown layer: LSTM或bad marshal data。原因通常是 Keras 版本和保存权重时的版本不一致,或者自定义层没有注册。解决:先确认model.py里有没有自定义层类,如果有,在加载时用custom_objects传进去;如果是版本问题,降级到 Keras 2.4.3 再试。另一个办法是用compile=False跳过编译,只加载结构权重。
4.2 生成器输出全是直线或单一波形
现象:生成几百条样本,画出来几乎一模一样,或者全是接近零的直线。原因:模式崩溃,判别器太强导致生成器梯度消失,或者学习率设得太大。解决:先检查判别器损失是否降到接近零,如果是,降低判别器学习率或增加 dropout;给生成器加一点噪声输入多样性;把标签平滑加上。我一般会把判别器训练次数设为生成器的 1 倍而不是 2 倍,避免它过早碾压。
4.3 训练中途显存溢出
现象:跑了几十个 batch 后报ResourceExhaustedError。原因:序列长度或 batch size 设得太大,LSTM 的中间状态占显存。解决:把序列长度从 512 降到 256,batch size 从 64 降到 32,或者用tf.keras.backend.clear_session()在每个 epoch 结束后清理。如果还不行,把 LSTM 单元数从 128 降到 64。
4.4 Jupyter Notebook 里 matplotlib 不显示图
现象:plt.show()执行后没有图像输出。原因:没加%matplotlib inline魔术命令,或者内核没选对。解决:在 notebook 第一个 cell 里加%matplotlib inline,确认内核是刚建的ecggan环境。如果用的是 VS Code,检查是否安装了 Jupyter 扩展并选对了 Python 解释器。
4.5 生成信号在医学上不可用
现象:波形看起来像 ECG,但 QRS 波群宽度、PR 间期明显不符合生理范围。原因:训练数据量太少或多样性不足,LSTM 只学到了表面纹理。解决:这不是代码 bug,是数据问题。需要更多真实记录,或者用数据增强扩充。记住这个包的定位是“似是而非”,不是临床诊断工具,拿它生成的数据去训练异常检测模型时,验证集必须用真实 ECG,否则评估结果会虚高。
5. 进阶技巧:用生成数据做异常检测增强的验证方法
如果你打算把这个包生成的假 ECG 拿去增强异常检测模型,别直接混进训练集就完事。我习惯先做一轮“生成数据质量门禁”:从真实数据里留出一小部分不参与 GAN 训练,只用来评估生成样本的分布覆盖度。具体做法是,把真实 ECG 和生成 ECG 分别过同一个预训练的特征提取器(比如一个简单的 1D CNN),得到嵌入向量,然后算两者的最大均值差异。如果 MMD 值比真实数据内部不同折之间的 MMD 大一个数量级,说明生成样本偏离太远,不能直接用。
# 用 MMD 粗略评估生成分布与真实分布的差距 import numpy as np from sklearn.metrics.pairwise import rbf_kernel def mmd_rbf(x, y, gamma=1.0): kxx = rbf_kernel(x, x, gamma).mean() kyy = rbf_kernel(y, y, gamma).mean() kxy = rbf_kernel(x, y, gamma).mean() return kxx + kyy - 2 * kxy # real_feat 和 fake_feat 是特征提取器输出的二维数组 score = mmd_rbf(real_feat, fake_feat, gamma=0.5) print('MMD score:', score)gamma控制核宽度,一般取特征维度倒数再调。这个分数没有绝对阈值,要和真实数据内部不同子集的 MMD 对比着看。另一个验证手段是训练一个分类器区分真假,如果分类器准确率很快冲到 95% 以上,说明生成质量还不够,判别器太容易识破。
还有一个容易被忽略的点:生成样本的标签怎么定。做异常检测增强时,生成样本通常当作正常类加入,但如果你用生成器去补少数类异常样本,需要条件 GAN 的变体,这个包不直接支持。我一般会先用它扩正常类,观察异常检测模型的召回率有没有提升,如果没提升甚至下降,说明生成样本引入了噪声,得回头调 GAN 的训练轮数或网络容量。
从那以后我每次用生成数据做增强,都强制走一遍 MMD 门禁和真假分类器测试,不通过就不往训练集里放。希望帮到你。
本文还有配套的精品资源,点击获取