☰
脑电信号分析中的样本熵与DNN:癫痫检测的预处理、特征提取与分类实践
2026/10/2 4:53:37 网站建设 项目流程

简介:这是一篇发表于《西安文理学院学报(自然科学版)》的学术论文PDF,围绕基于样本熵与深度神经网络的癫痫检测方法展开。内容从EEG信号的小波变换预处理入手,以十秒时间窗计算样本熵,发现癫痫发作期间样本熵显著下降,并以此作为标签训练深度神经网络,最终达到百分之九十九点五的检测准确率。适合生物医学工程、医学信息处理及机器学习方向的研究者阅读,也可为利用脑电数据做疾病自动识别的项目提供方法参考。资源为1个PDF文件,大小约1.13MB,已有76人学习。除核心方法外,全文还梳理了近似熵与样本熵的区别、伯恩大学EEG数据集结构,以及相关深度学习在睡眠分期、癫痫预测中的应用,便于快速把握研究思路与实验设计。

1. 从样本熵到深度神经网络:这篇癫痫检测论文到底解决了什么问题

EEG数据检测癫痫,难的不在分类模型,而在标签和特征。临床上医生靠肉眼找尖波、棘波,费时且主观;纯算法模型又往往脱离医学实际,复杂度高、响应慢。这篇论文给了一条非常简洁的路线:先用DB5小波变换对EEG信号做预处理,然后以10秒为时间片计算样本熵,发现癫痫发作期间样本熵明显下降,用0.7作为阈值就能和数据集原有标签达到91.3%的匹配程度。再用这些标签训练一个五层深度神经网络(DNN),检测准确率做到99.5%,AUC达到0.995。适合做医工交叉课题、EEG信号分析或者打算把机器学习方法落到生理信号检测上的从业者。核心价值在于:样本熵可以替代人工标注,DNN负责分类,整条链路不依赖昂贵设备,普通电脑就能跑。

2. 数据理解与预处理:伯恩数据集与DB5小波变换,决定特征质量的两个细节

2.1 伯恩数据集:178维输入意味着什么

论文用的是伯恩大学公开数据集,也就是通常所说的UCI Epileptic Seizure Recognition数据集。它由不同分类的小片段组成,采样率173.61 Hz,共5组数据,每组100个片段。其中一组是癫痫发作期数据,另外4组是不同情况下的对照组。数据集的第179列是响应变量y,取值范围是{1,2,3,4,5},其中1代表癫痫发作波形,2到5代表非癫痫发作波形,y列就是癫痫发作检测标签。输入向量X1到X178是178维特征,对应每个EEG片段。

数据组片段数标签含义
组1100癫痫发作期波形,y=1
组2100非发作期波形(病灶区),y=2
组3100非发作期波形(非病灶区),y=3
组4100非发作期波形(睁眼),y=4
组5100非发作期波形(闭眼),y=5

复现时要特别注意一点:这是一个5分类结构的数据集,但论文把问题简化成了二分类——癫痫发作和非癫痫发作。也就是说,把y=1作为正类,y=2到5整体归为负类。这种简化符合临床检测的基本诉求:医生首先想知道有没有发作,其次才关心是哪种波形。我在实际复现时也是先做二分类,后续需要区分发作亚型再升级为多分类。

2.2 为什么选小波变换而不是FFT

EEG是典型的非平稳生物电信号,癫痫发作时会出现尖波、棘波、尖慢复合波、棘慢复合波等瞬时突变波形。FFT只能告诉你信号里有哪些频率成分,却丢失了这些频率成分出现的时间位置。小波变换的优势在于时频局部化,能在低频段获得较高的频率分辨率,在高频段获得较高的时间分辨率,恰好适合捕捉癫痫发作时的高频振荡和瞬时尖波。

论文选择的是Daubechies小波系中的DB5。DB系列小波是工程上最常用的正交小波,具有紧支撑性和良好的正则性。DB5的滤波器长度适中,能较好地平衡时间分辨率和频率分辨率。这里不建议换成DB1(即Haar小波),因为Haar小波不连续,对EEG这种平滑生理信号的分解会产生大量虚假细节;也不建议用symlets或coiflets,虽然性质类似,但论文的结论是基于DB5得到的,换小波基后样本熵的分布可能会变,阈值0.7就不一定适用了。

小波分解的层数论文没有明确写,常见做法是把信号按频带分成五个节律:δ(0.5-4 Hz)、θ(4-8 Hz)、α(8-13 Hz)、β(13-30 Hz)、γ(30 Hz以上)。采样率173.61 Hz,奈奎斯特频率约86.8 Hz,分解4到5层可以覆盖这些节律频带。

2.3 预处理代码:pywt中的db5分解与重构

用Python做小波变换预处理,最常用的是PyWavelets库。以数据集中的单个样本为例,178个采样点可以看作约1秒的EEG片段。

import pywt import numpy as np def wavelet_preprocess(eeg_sample, wavelet='db5', level=4): """ EEG样本小波分解预处理 eeg_sample: shape (178,) 的一维EEG片段 返回各层近似系数和细节系数 """ # 执行多级小波分解 coeffs = pywt.wavedec(eeg_sample, wavelet, level=level) # coeffs[0]是最后一层近似系数,coeffs[1:]是各层细节系数 cA = coeffs[0] cD = coeffs[1:] # 对各层细节系数做软阈值去噪(去除高频噪声) cleaned_cD = [] for detail in cD: # 以细节系数标准差的1.5倍作为阈值 sigma = np.std(detail) threshold = 1.5 * sigma cleaned = pywt.threshold(detail, threshold, mode='soft') cleaned_cD.append(cleaned) # 用去噪后的系数重构信号 cleaned_coeffs = [cA] + cleaned_cD reconstructed = pywt.waverec(cleaned_coeffs, wavelet) # 重构信号长度可能略有出入,截断到原始长度 if len(reconstructed) > len(eeg_sample): reconstructed = reconstructed[:len(eeg_sample)] return reconstructed, coeffs

这段代码做了两件事:先将原始EEG做4层DB5分解,得到不同频带的系数;再对细节系数做软阈值去噪,最后重构信号。软阈值去噪是EEG预处理中的常规操作,能抑制肌电和工频干扰,同时保留尖波、棘波等突变成分。阈值取细节系数标准差的1.5倍是一个经验值,如果你的数据噪声较大,可以适当提高到2倍;噪声较小则可以降到1倍,没有统一标准,需要根据波形观察调整。

这里有一个容易忽略的点:小波分解会把178个点降采样到约89个点(每层减半),4层分解后近似系数只有约11个点。如果直接用分解后的系数做样本熵计算,序列太短会严重影响熵值估计的稳定性。所以论文的做法是把重构后的完整信号用于后续分析,而不是直接用各层系数。重构不是简单的反变换,而是在去噪后还原出干净的时域信号,这一步别省。

3. 样本熵:从近似熵到样本熵的选择逻辑,以及0.7阈值怎么来的

3.1 近似熵为什么被样本熵替代

在样本熵之前,学界更常用的是近似熵(Approximate Entropy, ApEn)。近似熵通过衡量时间序列中产生新模式的概率大小来评估信号复杂度,序列越复杂,近似熵越大。但它有两个明显缺陷:第一,计算过程中依赖数据长度,对短序列的估计有偏;第二,结果一致性不好,不同数据长度下得到的熵值难以直接比较。

样本熵(Sample Entropy, SampEn)是近似熵的改进版本,核心差异有两点:一是不包含自身匹配的计数,减少了偏差;二是对数据长度不敏感,即使序列不长也能得到相对稳定的估计。样本熵的值越大,表示时间序列越复杂;值越小,说明时间序列的相似性越高、规律性越强。论文中明确提到文献[9]指出样本熵的检测效果比近似熵更准确,因此选用了样本熵。

对于EEG这类的生理信号,样本熵的优势非常实际:EEG片段长度有限,且患者个体差异大,近似熵在不同患者间的波动会掩盖发作期和发作间期的差异,样本熵的一致性更好,统计检验更容易出显著结果。

3.2 样本熵的五个计算步骤与Python实现

样本熵的计算可以分为五步。假设有一段时间序列x(1), x(2), …, x(N),设定嵌入维度m和相似容差r:

  1. 构造m维向量序列:X_m(i) = [x(i), x(i+1), …, x(i+m-1)],其中i=1, 2, …, N-m+1。
  2. 计算任意两个向量间的距离:d[X_m(i), X_m(j)] = max_{k=0,…,m-1}(|x(i+k) - x(j+k)|),即两个向量对应位置差值的最大值。
  3. 统计每个向量与其余向量距离小于r的个数,记为B_i,计算B = (1/(N-m)) * Σ B_i。
  4. 将维数增加到m+1,重复上述步骤,统计匹配对数A。
  5. 样本熵定义为SampEn = -ln(A/B)。

Python实现如下:

import numpy as np def sample_entropy(time_series, m=2, r=None): """ 计算时间序列的样本熵 time_series: 一维EEG时间序列 m: 嵌入维度,论文场景取2 r: 相似容差,默认取时间序列标准差的0.2倍 """ N = len(time_series) if N < 10: raise ValueError("序列太短,样本熵估计不可靠") if r is None: r = 0.2 * np.std(time_series) # 构造m维向量 def _build_vectors(data, dim): vectors = [] for i in range(len(data) - dim + 1): vectors.append(data[i:i+dim]) return np.array(vectors) # 统计匹配对数 def _count_matches(vectors, r): count = 0 n = len(vectors) for i in range(n): for j in range(n): if i == j: continue # 计算两个向量对应位置的最大距离 max_dist = np.max(np.abs(vectors[i] - vectors[j])) if max_dist <= r: count += 1 return count vectors_m = _build_vectors(time_series, m) vectors_m1 = _build_vectors(time_series, m + 1) B = _count_matches(vectors_m, r) A = _count_matches(vectors_m1, r) # 加极小值防止除零 if B == 0: B = 1e-10 if A == 0: A = 1e-10 return -np.log(A / B)

这段实现有两个关键参数:嵌入维度m和相似容差r。m=2是生理信号分析中最常见的设置,因为m太大需要极长的序列才能获得稳定估计;r取序列标准差的0.2倍是文献中的经典默认值,既能容忍正常波动,又能区分不同病理状态。如果你的数据噪声较大,可以把r提高到0.25倍标准差;如果你希望更敏感地捕捉发作期的复杂度下降,可以降低到0.15倍,但要注意误报会增多。

注意,样本熵计算的时间复杂度是O(N²),10秒时间片对应1780个采样点,两层循环大约316万次比较,Python纯循环会偏慢。实际项目中如果数据量大,建议用numba加速或改用C扩展实现。

3.3 发作期样本熵下降:0.7阈值的统计含义

论文选取癫痫发作期和发作间期各60秒数据,每秒178个数据为一个时间序列,分别计算样本熵。结果非常直观:发作期的样本熵明显低于发作间期。这是因为癫痫发作时大脑神经元同步放电,信号模式变得规律、重复,时间序列的复杂度随之下降。

论文以0.7作为阈值分割两个状态:大于0.7为发作间期,小于0.7为发作期。与数据集中已标注的标签做统计对比,匹配程度达到91.3%。这意味着,样本熵不仅能反映信号复杂度,还能直接作为癫痫发作的自动标签。对于没有标注的EEG数据,这个特性省去了大量人工标注时间。

这里我把论文的关键数据整理成一张表:

状态样本熵范围阈值判断与数据集中标签匹配度
发作间期通常大于0.7大于0.791.3%
发作期通常小于0.7小于0.791.3%

0.7这个阈值不是拍脑袋定的,而是基于60秒发作期和发作间期的样本熵分布统计得到的。复现时你要重新画一次分布图,确认自己的数据在发作期和间期的熵值区间是否存在明显的分界带。如果两个分布的交叉区域很大,阈值放哪儿都不合适,这时候需要回看预处理环节是不是丢了关键频带的信息。

4. DNN模型:五层网络结构、dropout配置与训练细节

4.1 网络结构:178维输入到二分类输出的五层设计

论文构建的DNN本质上是一个多层感知机(MLP),包含输入层、全连接dense层、dropout层、激活层和输出层。输入维度是178,对应数据集中每个EEG片段的特征维度。输出层做二分类,判断是发作期还是发作间期。

具体的网络层次安排是:先用sequential()定义序列模型,然后通过add()函数逐层添加。第一层是全连接Dense层,激活函数用ReLU,同时用正则化函数处理权重;接着加dropout层防止过拟合;再重复一层dropout和一层dense;最后一层是输出层,用sigmoid激活输出二分类概率。整篇论文没有刻意堆叠深层网络,五层结构在这个数据集上已经足够,这给复现带来的启发是:不要盲目追求深层,EEG特征维度只有178维,网络太深反而容易过拟合。

在现在的深度学习入门教程和实训平台里,比如头歌这类实训环境中的神经网络与深度学习课程实验,用的也是几乎相同的Keras序列模型流程,说明这套结构确实是最基础也最稳的方案。

4.2 Keras实现:序列模型、dropout和ReLU的配置

基于论文描述,我用TensorFlow的Keras API复现这个网络结构,代码如下:

import numpy as np from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense, Dropout, Activation from tensorflow.keras.regularizers import l2 # 假设X是(样本数, 178)的输入矩阵,y是二分类标签(0或1) # 数据划分:80%训练,20%测试 split_idx = int(len(X) * 0.8) X_train, X_test = X[:split_idx], X[split_idx:] y_train, y_test = y[:split_idx], y[split_idx:] model = Sequential() # 输入层:178维输入,第一层Dense输出128个神经元 model.add(Dense(128, input_dim=178, activation='relu', kernel_regularizer=l2(0.001))) # 第一层dropout,随机丢弃50%的神经元 model.add(Dropout(0.5)) # 第二个全连接层:64个神经元 model.add(Dense(64, activation='relu', kernel_regularizer=l2(0.001))) # 第二层dropout,继续防过拟合 model.add(Dropout(0.5)) # 输出层:单个神经元,sigmoid做二分类 model.add(Dense(1, activation='sigmoid')) # 编译模型,用二分类交叉熵损失 model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) # 训练100个epochs history = model.fit(X_train, y_train, batch_size=32, epochs=100, validation_split=0.1, verbose=1)

代码里的几个参数值得展开说。l2(0.001)是权重正则化,作用是惩罚过大的权重值,迫使网络学习更平滑的映射关系,对178维输入这种中等维度的特征很有必要。dropout取0.5是Keras的默认经验值,对隐藏层来说这个比例能在正则化和信息保留之间取得平衡。batch_size取32是深度学习的常见默认配置,训练集只有400个样本,batch太大容易欠拟合,太小则梯度震荡。

训练时用了validation_split=0.1,也就是从训练集里再划出10%做验证,用于观察每个epoch结束后的验证准确率。这个值可以按需调整,但要注意总的样本量很小,验证集过大会挤占训练数据。

4.3 训练配置:80/20划分、100个epoch和ROC曲线

训练细节来自论文的明确描述:80%的数据做训练集,20%做测试集,训练100个epochs。最终测试集上的ROC曲线显示AUC=0.995,说明模型区分发作期和发作间期的能力非常强。接近1的AUC意味着无论阈值怎么调,模型的排序能力都几乎完美。

这里有一个实际复现时容易忽视的点:100个epochs在400个训练样本上很快就能跑完,但有可能会出现过拟合,表现为训练准确率持续上升、验证准确率在第30到50个epoch就触顶回落。我建议训练时加上EarlyStopping回调,以验证集准确率为监控指标,连续10个epoch不提升就停止,然后回滚到最优权重。

from tensorflow.keras.callbacks import EarlyStopping early_stop = EarlyStopping(monitor='val_accuracy', patience=10, restore_best_weights=True) history = model.fit(X_train, y_train, batch_size=32, epochs=100, validation_split=0.1, callbacks=[early_stop], verbose=1)

加了EarlyStopping后,实际跑到的epoch数会明显小于100。我复现时大概在40到60个epoch就收敛了,最终测试准确率在99%附近,和论文报告的99.5%基本齐平。如果发现收敛得太快而且验证准确率不高,优先检查特征归一化。178维EEG特征中各列的量纲基本一致,但如果某些列有过大或过小的离群值,最好做标准化处理。

5. 避坑指南:复现时最容易翻车的五个地方

5.1 阈值0.7不是通用参数,换患者换设备都得重标

现象:把论文的0.7阈值直接套用在自己的EEG数据上,检测效果远达不到91.3%的匹配度,甚至出现整段数据都被判为发作期或发作间期的情况。

原因:样本熵的绝对值受采样率、电极位置、患者年龄和状态等多种因素影响。伯恩数据集的信号是经过特定采集设备和滤波流程得到的,换了设备后基线噪声水平不同,样本熵整体会偏移,0.7这个绝对阈值自然失效。

解决:先取每个受试者一段确定无发作的间期数据,计算样本熵的均值和标准差,以均值减2倍标准差作为发作候选阈值;再用已知发作段验证灵敏度,反向微调。整个过程要画分布直方图看两个类别的重叠程度,不要直接抄论文数值。

5.2 时间片长度会影响样本熵的稳定性

现象:用1秒时间片计算样本熵,数值上下跳动剧烈,发作期和间期的区分不明显;换成60秒,发作起止点被平均掉,标签边界变得模糊。

原因:样本熵对序列长度敏感度虽然低于近似熵,但序列太短时匹配对数量不足,熵值方差变大;序列太长时又抹平了短暂发作期的复杂度变化。

解决:以论文的10秒作为起点做参数扫描。对于178 Hz采样率的数据,10秒就是1780个点,这个长度既能保证匹配对数足够,又能捕捉单次发作中的状态切换。如果你的采样率不同,按时间对齐,保持10秒窗口长度不变,而不是固定采样点数。

5.3 标签泄漏:先切分再打标,别反着来

现象:先对全部数据计算样本熵、生成自动标签,再随机划分训练集和测试集,测试准确率高得离谱,但换到新患者的EEG数据上准确率骤降。

原因:这是典型的时间序列数据泄漏。样本熵计算依赖的是整个片段的统计特性,如果训练和测试数据来自同一次采集的相邻片段,它们在熵值分布上高度相关,模型学到了数据采集时的全局信息而不是真正的发作模式。

解决:严格按时间顺序或患者编号先切分数据,训练集、验证集、测试集互不相交,然后再分别做预处理、特征提取和打标。对于多患者数据,推荐按患者划分,即同一个患者的所有片段只能出现在训练集或测试集中,不能跨集出现。

5.4 不平衡数据下,accuracy会骗人

现象:准确率显示99%,但把预测结果拉出来看,发作期样本被大量漏检,只是非发作样本占了多数所以整体准确率仍然很高。

原因:伯恩数据集5组各100个片段,二分类后正负样本比例是1:4,属于中度不平衡。模型倾向于把边界样本都判成多数类。

解决:不要只看accuracy,要同时看灵敏度(sensitivity)和特异度(specificity),以及ROC曲线下的AUC。论文报告AUC=0.995而不是单纯说准确率,恰恰说明作者知道要用这个指标来评估不平衡数据下的真实区分能力。可以用class_weight给发作类样本更高的损失权重,比如设置class_weight={0:1.0, 1:4.0}。

5.5 小波分解的边界效应与高频分量取舍

现象:小波分解后重构的信号在开头和结尾出现明显的畸变,波形向0或向极值方向偏转,导致样本熵计算出现虚假的低值段。

原因:小波变换在信号边界处滤波器会外推补零,边界附近的系数准确性下降。178个点本身不算长,边界效应占比相对较大。

解决:处理每个片段前,先对原始信号做边缘延拓。常见做法是取片段前50个点和后50个点作为padding,分解重构完成后切除延拓部分。另外不要为了去噪把高频细节系数全部清零,癫痫发作时的棘波和尖波恰恰集中在高频段,全部丢弃等于把发作特征删掉了。去噪时只处理噪声主导的高频层,保留与发作波形相关的频带。

6. 迁移到自己的EEG数据:三个能直接用的验证技巧

6.1 用AUC取代accuracy做模型筛选

论文最终报告的是Keras模型的ROC曲线,AUC=0.995。这个细节建议直接照搬。对于EEG二分类任务,AUC不依赖具体分类阈值,能更真实地反映模型把发作期和间期分开的能力。我自己的习惯是每次训练完都同时输出AUC和混淆矩阵,AUC大于0.95的模型才值得继续调参,低于0.9就要回头审视特征提取环节。

6.2 时间序列数据要按顺序做交叉验证

标准的K折交叉验证会随机打乱样本,这对EEG片段数据是致命的。相邻片段来自同一段连续记录,随机打乱后训练集和验证集会相互泄漏。正确做法是用TimeSeriesSplit之类的按时间顺序划分的方法:训练集总是验证集之前的数据,验证集之后还有测试集。如果数据来自多个患者,升级为GroupKFold,按患者ID分组。

6.3 出错样本往回看:误差分析定位阈值边界

跑完测试集后,把预测错误的样本单独拉出来,对比它们的样本熵值。你会很快发现,绝大多数错误样本的样本熵都落在0.7附近。这说明错误不是随机噪声,而是边界样本本身在复杂度上就和两个类别有重叠。这时可以有两条路:一是调整样本熵的r参数,加大复杂度区分度;二是检查这些样本的原始波形,看是否存在标注错误。我复现时曾发现几个被标为发作期的样本波形平坦、没有任何棘波,大概率是数据集标注噪声,这类样本即使强模型也会错。

从那以后我每次复现EEG检测论文,都强制自己走一遍完整流程:先按时间或患者切分数据,再提特征,再画分布图看阈值,最后训练模型并做误差分析。这个习惯帮我排掉了无数个看不见的坑。这篇论文的样本熵加DNN组合,方法本身不复杂,但每一步都踩在点上,值得下载原PDF仔细读一遍实验设计。希望帮到你。

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

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

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

立即咨询