当前位置: 首页 > news >正文

HGD运动想象脑电数据集预处理实战:从数据加载到特征标准化

1. HGD数据集简介与下载指南

HGD(High Gamma Dataset)是目前运动想象脑电研究领域最常用的公开数据集之一,由德国柏林工业大学团队采集并开源。这个数据集包含了14名受试者在执行左手、右手、脚部和休息四种运动想象任务时的高密度脑电信号,采样率高达500Hz,电极数量达到128个(实际使用中常选取44个运动相关通道)。

我第一次接触这个数据集是在做一个脑机接口项目时,当时被它清晰的标签和高质量的原始数据所吸引。不过下载过程确实有点头疼,官方GitHub仓库位于https://github.com/robintibor/high-gamma-dataset,但由于服务器在国外,国内下载速度可能较慢。这里分享一个实测可用的百度网盘备份链接(提取码:dk3w),包含完整的训练集和测试集EDF文件。

数据集的文件结构是这样的:

/data /train 1.edf 2.edf ... 14.edf /test 1.edf 2.edf ... 14.edf

每个.edf文件都包含了原始脑电数据和事件标记(annotations),其中事件标记对应四种运动想象任务。在实际使用时,我建议先下载1-2个受试者的数据测试流程,确认无误后再下载完整数据集。

2. 环境配置与依赖安装

处理HGD数据集需要配置特定的Python环境。我推荐使用Anaconda创建独立环境,以下是经过多次踩坑后总结出的最稳定依赖组合:

conda create -n hgd python=3.8 conda activate hgd pip install mne==1.0.3 numpy==1.21.6 scipy==1.7.3 scikit-learn==1.0.2

这里有几个关键点需要注意:

  1. MNE版本:1.0.3版本对EDF文件的支持最稳定,新版有时会报奇怪的解码错误
  2. NumPy兼容性:1.21.x版本与后续滤波处理兼容性最好
  3. 避免混用conda和pip:要么全部用conda安装,要么全部用pip,混用容易导致库冲突

我曾经遇到过因为scipy版本过高导致filtfilt函数行为异常的问题,症状是滤波后的信号出现奇怪的震荡。后来锁定1.7.3版本后问题解决。如果你在滤波环节遇到类似问题,不妨先检查scipy版本。

3. 数据加载与通道选择

原始EDF文件包含128个通道,但运动想象任务主要关注运动皮层区域的44个通道。以下是经过优化的数据加载代码:

def load_bbci_like_from_edf(edf_path, low_cut_hz=0): raw = mne.io.read_raw_edf(edf_path, preload=True) raw.pick_types(eeg=True, stim=False) # 标准化通道名称(去除EEG前缀) raw.rename_channels({ ch: ch.replace('EEG ', '').replace('EOG ', '').replace('EMG ', '') for ch in raw.ch_names }) # 关键运动通道选择 motor_44 = ['FC5', 'FC1', 'FC2', 'FC6', 'C3', 'C4', 'CP5', 'CP1', 'CP2', 'CP6', 'FC3', 'FCz', 'FC4', 'C5', 'C1', 'C2', 'C6', 'CP3', 'CPz', 'CP4', 'FFC5h', 'FFC3h', 'FFC4h', 'FFC6h', 'FCC5h', 'FCC3h', 'FCC4h', 'FCC6h', 'CCP5h', 'CCP3h', 'CCP4h', 'CCP6h', 'CPP5h', 'CPP3h', 'CPP4h', 'CPP6h', 'FFC1h', 'FFC2h', 'FCC1h', 'FCC2h', 'CCP1h', 'CCP2h', 'CPP1h', 'CPP2h'] # 检查缺失通道 avail = set(raw.ch_names) miss = set(motor_44) - avail if miss: raise RuntimeError(f'缺失运动想象关键通道: {miss}') raw.reorder_channels(motor_44) raw.pick_channels(motor_44) return raw

这段代码做了三件重要的事情:

  1. 加载EDF文件:使用MNE的read_raw_edf函数,设置preload=True将数据全部读入内存
  2. 通道清洗:去除EOG(眼电)、EMG(肌电)等非EEG通道
  3. 运动通道选择:选取覆盖运动皮层的44个关键通道

在实际项目中,我发现有些受试者的数据会缺少个别通道(特别是h结尾的高密度通道)。这时有几种处理方案:

  • 直接跳过该受试者(简单但损失数据)
  • 用邻近通道插值(需要专业知识)
  • 修改motor_44列表(影响模型一致性)

4. 信号重采样与滤波处理

HGD原始数据的采样率是500Hz,但运动想象任务通常不需要这么高的时间分辨率。重采样到250Hz既能减少计算量,又不会损失有效信息:

def resample_cnt(raw, new_sfreq): """mne.Raw 重采样""" return raw.resample(new_sfreq, npad='auto') def highpass_cnt(data, low_cut_hz, sfreq, filt_order=3, axis=-1): if low_cut_hz <= 0: return data nyq = 0.5 * sfreq b, a = butter(filt_order, low_cut_hz / nyq, btype='high') return filtfilt(b, a, data, axis=axis) # 使用示例 raw = resample_cnt(raw, 250.) raw._data = highpass_cnt(raw.get_data(), low_cut_hz=1.0, sfreq=250.)

滤波处理中有几个容易踩坑的地方:

  1. 滤波顺序:一定要先重采样再滤波,否则会引入混叠噪声
  2. 滤波器类型:使用IIR滤波器(butter)比FIR计算量小,但要注意相位延迟
  3. filtfilt使用:零相位滤波需要双向滤波,会导致信号时间长度变化

我建议先用一个受试者的数据画出滤波前后的频谱对比,确认滤波效果符合预期。常见问题是截止频率设置不合理导致有效信号被滤除,可以通过调整low_cut_hz参数(通常在0.5-4Hz之间)来优化。

5. 数据分段与事件标记提取

HGD数据集中的事件标记(annotations)对应四种运动想象任务:

def create_signal_target_from_raw_mne(raw, marker_def, ival, events=None): tmin, tmax = ival[0], ival[1] if events is None: events, _ = mne.events_from_annotations(raw) # 建立事件编码到类别的映射 code_to_class = {v[0]: idx for idx, (k, v) in enumerate(marker_def.items())} # 只保留我们感兴趣的事件类型 keep_mask = np.isin(events[:, 2], list(code_to_class.keys())) events = events[keep_mask] # 创建Epochs对象 epochs = mne.Epochs(raw, events, event_id=None, tmin=tmin, tmax=tmax, baseline=None, preload=True, verbose=False) # 获取数据矩阵和标签 X = epochs.get_data() # 形状为(n_epochs, n_channels, n_times) y = np.array([code_to_class[c] for c in events[:, 2]], dtype=np.int64) return SimpleDataset(X, y) # 标签定义 label_map = {'Right Hand': 1, 'Left Hand': 2, 'Rest': 3, 'Feet': 4} marker_def = OrderedDict([(k, [v]) for k, v in label_map.items()]) ival = [-0.5, 4.0] # -500ms ~ 4000ms

这里有几个关键参数需要特别注意:

  • ival时间窗:-0.5到4.0秒是运动想象任务的典型分析时段
  • 基线校正:设置baseline=None是因为我们后面会做标准化处理
  • 事件筛选:确保只保留四种目标事件(1-4),排除其他无关事件

在实际应用中,我发现有些受试者的标记时间不太准确,可以考虑将ival调整为[-0.3, 3.8]来避免边缘效应。另外,epochs对象的get_data()方法返回的是(n_epochs, n_channels, n_times)的三维数组,这正是深度学习模型需要的输入格式。

6. 信号标准化与伪迹去除

脑电信号的幅值受多种因素影响,标准化是必不可少的一步:

def exponential_running_standardize(data, factor_new=1e-3, init_block_size=1000, eps=1e-4): data = data.copy() n_samples, n_channels = data.shape # 初始区块统计量计算 if init_block_size is not None: mean = np.mean(data[:init_block_size], axis=0) std = np.std(data[:init_block_size], axis=0) else: mean = np.zeros(n_channels) std = np.ones(n_channels) current_mean = mean.copy() current_var = std ** 2 standardized = np.zeros_like(data) # 指数移动平均标准化 for i in range(n_samples): current_mean = (1 - factor_new) * current_mean + factor_new * data[i] current_var = (1 - factor_new) * current_var + factor_new * (data[i] - current_mean) ** 2 standardized[i] = (data[i] - current_mean) / np.sqrt(current_var + eps) return standardized # 应用标准化 raw._data = exponential_running_standardize( raw.get_data().T, factor_new=1e-3, init_block_size=1000, eps=1e-4 ).T # 去除异常epoch clean_mask = np.max(np.abs(dataset.X), axis=(1, 2)) < 800 dataset.X = dataset.X[clean_mask] dataset.y = dataset.y[clean_mask]

这种标准化方法有三大优势:

  1. 在线处理能力:适合实时脑机接口系统
  2. 鲁棒性:对瞬态伪迹不敏感
  3. 保持信号特性:不像z-score那样会压缩动态范围

参数factor_new控制着新样本的权重,我通常设置在1e-4到1e-3之间。值太大会导致标准化过于敏感,太小则响应迟缓。init_block_size建议设为1000个样本左右,相当于4秒的数据量(250Hz采样率下)。

7. 完整流程封装与使用示例

将上述步骤封装成端到端的处理管道:

def HGD_data(Sub, data_path='../data'): """加载单个受试者的训练和测试数据""" train_edf = f'{data_path}/train/{Sub}.edf' test_edf = f'{data_path}/test/{Sub}.edf' def load_set(edf_path): raw = load_bbci_like_from_edf(edf_path, low_cut_hz=1.0) raw = resample_cnt(raw, 250.) raw._data = highpass_cnt(raw.get_data(), 1.0, 250.) raw._data = exponential_running_standardize( raw.get_data().T, factor_new=1e-3, init_block_size=1000, eps=1e-4 ).T events, _ = mne.events_from_annotations(raw) label_map = {'Right Hand': 1, 'Left Hand': 2, 'Rest': 3, 'Feet': 4} events = events[np.isin(events[:, 2], list(label_map.values()))] marker_def = OrderedDict([(k, [v]) for k, v in label_map.items()]) ival = [-0.5, 4.0] dataset = create_signal_target_from_raw_mne(raw, marker_def, ival, events=events) clean_mask = np.max(np.abs(dataset.X), axis=(1, 2)) < 800 dataset.X = dataset.X[clean_mask] dataset.y = dataset.y[clean_mask] return dataset train_set = load_set(train_edf) test_set = load_set(test_edf) return train_set.X, test_set.X, train_set.y, test_set.y # 使用示例 X_train, X_test, y_train, y_test = HGD_data('1') # 加载1号受试者的数据

这个封装好的函数可以直接返回NumPy数组格式的数据,方便输入到scikit-learn或PyTorch等机器学习框架中。输出数据的形状为:

  • X_train: (n_samples, 44, 1126)
  • y_train: (n_samples,)

其中1126对应4.5秒的时间窗(250Hz * 4.5s = 1125,多出的1个点是包含两端点)。

8. 常见问题与解决方案

在实际使用HGD数据集的过程中,我遇到过不少问题,这里分享几个典型case的解决方法:

问题1:内存不足错误症状:加载大EDF文件时出现MemoryError 解决方法:

# 改用以下方式分块加载 raw = mne.io.read_raw_edf(edf_path, preload=False) # 不立即加载 raw.crop(tmax=60) # 先加载前60秒测试流程 raw.load_data()

问题2:滤波后信号失真症状:滤波后的信号出现平直线或剧烈震荡 检查清单:

  1. 确认采样率在滤波前已正确重采样
  2. 检查截止频率是否合理(运动想象通常用1-40Hz)
  3. 尝试降低滤波器阶数(filt_order从3降到2)

问题3:标签不匹配症状:y的长度与X不匹配 解决方法:

# 确保事件标记筛选正确 events = events[np.isin(events[:, 2], [1, 2, 3, 4])]

问题4:跨受试者数据不一致症状:不同受试者的通道数量或名称不一致 解决方案:

# 使用通道交集而非固定列表 common_ch = set(motor_44) & set(raw.ch_names) raw.pick_channels(list(common_ch))

对于想进一步优化模型性能的开发者,我建议尝试以下进阶处理:

  1. 独立成分分析(ICA):去除眼动和心电伪迹
  2. 时频分析:提取mu节律(8-12Hz)和beta节律(13-30Hz)特征
  3. 空间滤波:使用CSP或xDAWN等空间滤波方法增强信号
http://www.jsqmd.com/news/578823/

相关文章:

  • PyTorch入门指南——从概念到实践
  • MySQL中的索引
  • DAgent:从数据到洞察,智能体如何重塑企业报告自动化
  • Python智能自动化:JianYingApi赋能视频处理新范式
  • 告别手机热点!用这招让公司笔记本同时访问内外网(附一键切换脚本)
  • OpenAI创始人学AI的底层逻辑,普通人照着做就能上手!
  • PostgreSQL 18远程访问:从‘允许所有IP’到‘最小权限’的安全进阶配置实战
  • C++27契约编程安全校验配置(仅限首批通过WG21 Security Review的12家头部厂商内部文档节选)
  • STM32与MPU6050实现高精度姿态检测与报警系统
  • 先被日本汽车打败,再被中国汽车冲击,欧洲车面临崩盘,已累计裁员50万人!
  • 编写程序实现智能无人机电池电量检测,低电量自动提示返航,避免炸机。
  • 手把手解读:如何用Diffusion Transformer(DiT)让机器人‘动’得更丝滑
  • 数据库的第一、二、三范式分别解决了什么问题?一文详解
  • 基于Matlab的时滞系统GPC算法仿真:不同控制参数对控制效果的影响对比及程序调试说明
  • 【测试】认识测试
  • 海南全铝定制好口碑公司
  • 服务器异常流量如何识别?从监控定位到防御处置全流程
  • OpenClaw 的 “安全卫士”:Jeddak AgentArmor 运行时防护全解析
  • 三步打造你的专属AI对话伙伴:SillyTavern完整指南
  • Hooks(钩子)介绍
  • OpenClaw异常监控:Kimi-VL-A3B-Thinking长任务中断自恢复方案
  • 一、基础知识学习(Transformer + 上下文窗口 + Token 计算 + Embedding 向量)
  • 镜像视界|数字孪生公安新范式:视频不再监控,而是主动控制——基于视频空间反演与跨镜连续追踪的无感定位与轨迹预测系统
  • 全网可达作业
  • leetcode 1572. 矩阵对角线元素的和-耗时100-Matrix Diagonal Sum
  • 面向对象分析模型深入分析
  • 实现一个宿主机两个不通网桥的上的容器的互通 容器A内部访问容器B的容器名以及端口 容器A内部用宿主机ip+B容器端口映射的端口访问容器B 反之亦然
  • 何为多态?
  • 一篇文章让你彻底区分#define和typedef
  • 收藏!2026年小白/程序员转大模型:避坑+实战路线全拆解(亲测可落地)