网站建设成本控制,北京的电商平台网站有哪些,专门做蛋糕面包的网站,平顶山股票配资网站建设1. 从零开始#xff1a;认识DEAP数据集与我们的目标 大家好#xff0c;我是老张#xff0c;一个在脑机接口和AI领域摸爬滚打了十来年的工程师。今天咱们不聊那些虚头巴脑的概念#xff0c;直接上手干点实在的——用DEAP数据集#xff0c;一步步搞定EEG脑电信号的情感分类。…1. 从零开始认识DEAP数据集与我们的目标大家好我是老张一个在脑机接口和AI领域摸爬滚打了十来年的工程师。今天咱们不聊那些虚头巴脑的概念直接上手干点实在的——用DEAP数据集一步步搞定EEG脑电信号的情感分类。我知道很多刚入门的朋友一看到原始的.bdf数据文件、复杂的通道配置头就大了。别怕我当初也一样踩过的坑、绕过的弯路今天都给你捋直了。DEAP数据集是什么简单说它就是一个“看电影测情绪”的经典数据库。研究人员找了32位志愿者让他们看40段一分钟的音乐视频同时用头戴设备记录他们的脑电波EEG还测了皮肤电、心率这些身体反应。看完后志愿者要给自己当时的情绪打分主要从两个维度效价Valence简单理解就是心情好坏从非常负面到非常正面和唤醒度Arousal可以理解为兴奋程度从非常平静到非常兴奋。我们的终极目标就是让电脑学会“读心术”——只分析这些杂乱无章的脑电波曲线就能猜出这个人当时是开心、平静、焦虑还是兴奋。这有什么用那可太多了。往近了说能做出更懂你的音乐推荐App、能评估广告或电影对观众的情绪影响往远了看是情感计算、心理健康辅助诊断、下一代人机交互的基础。但所有高大上的应用都得从最基础的“数据预处理”这座大山爬起。原始EEG信号里混杂了太多“噪音”眼球的转动、肌肉的颤动、设备的工频干扰……不把这些脏东西洗干净再牛的模型也学不出个所以然。所以咱们这个系列的第一期核心就四个字“净身出户”。我们要把原始数据读进来把信号清理干净提取出能代表情绪的关键特征最后打好标签为后续训练分类模型准备好一顿干净、规整的“食材”。我保证只要你跟着步骤走哪怕你是Python新手也能亲手跑通这个情绪识别的完整Pipeline。2. 实战第一步获取与理解你的数据工欲善其事必先利其器。咱们先得把数据拿到手并彻底搞懂它。2.1 数据下载与原始结构DEAP数据集官网提供了两种格式的数据。对于想快速上手、验证想法的朋友我强烈推荐直接使用预处理好的Python版本Data_preprocessed_python.zip。这个版本的数据已经帮我们做了降采样从512Hz降到128Hz、去除了明显的伪迹并且分割好了试次开箱即用能让我们把精力集中在核心的算法和模型上。如果你是一名严谨的研究者需要最原始的信息或者你的方法对预处理流程有特殊要求那么可以挑战原始数据版本Data_original.zip。这里面是32个.bdf文件每个文件对应一位参与者观看全部40个视频的连续记录。文件可以用pyedflib或MNE-Python这类专业库来读取。不过我得提醒你处理原始数据就像处理生鲜食材步骤繁琐坑也多比如两个采集地点的电极顺序、皮肤电单位都不一致需要额外转换。咱们这个指南为了最大化“上手容易”会以预处理好的Python版本作为起点。假设你已经下载并解压了Data_preprocessed_python.zip你会看到32个.dat文件例如s01.dat,s02.dat…s32.dat。这些文件是用Python的pickle模块序列化存储的。我们来写第一段代码看看里面到底有什么。import pickle import numpy as np # 加载第一位参与者的数据 with open(data/s01.dat, rb) as f: data pickle.load(f, encodinglatin-1) # 注意编码 # 看看这个字典里都有什么钥匙 print(数据字典的键, data.keys())运行后你大概率会看到类似这样的输出dict_keys([data, labels, sampling_rate])。这就对了data里存放着EEG和外周生理信号labels就是效价、唤醒度等评分sampling_rate是采样率应该是128。让我们再深入看看形状print(EEG数据形状, data[data].shape) print(标签数据形状, data[labels].shape) print(采样率, data[sampling_rate])典型的输出可能是(40, 40, 8064)和(40, 4)。这什么意思呢第一个形状表示40个试次视频x 40个通道 x 8064个数据点。第二个形状表示40个试次 x 4个评分标签通常是效价、唤醒度、支配度、喜爱度。8064个点除以128Hz的采样率刚好是63秒对应了1分钟视频加3秒的前后缓冲。2.2 关键信息拆解通道与标签理解每个维度的含义是后续所有操作的基础。我们得把通道和标签掰开揉碎了看。首先是通道。预处理数据通常包含32个EEG通道和8个外周生理通道。EEG通道的名字遵循国际10-20系统比如Fp1左前额、O2右枕叶。不同脑区对情绪的贡献不同比如前额叶和情绪调节、决策密切相关而颞叶则涉及听觉处理和记忆。在后续分析中我们可能会重点关照某些通道。我们可以先提取EEG通道的索引# 假设我们有一个包含所有通道名称的列表你需要根据数据集的说明文档来确定 # 这里是一个示例实际顺序请以你数据集的README为准 all_channel_names [Fp1, Fp2, F3, F4, C3, C4, P3, P4, O1, O2, F7, F8, T7, T8, P7, P8, Fz, Cz, Pz, Oz, FC1, FC2, CP1, CP2, FC5, FC6, CP5, CP6, FT9, FT10, TP9, TP10, EXG1, EXG2, EXG3, EXG4, EXG5, EXG6, EXG7, EXG8] eeg_channel_indices [i for i, name in enumerate(all_channel_names) if not name.startswith(EXG)] print(EEG通道索引, eeg_channel_indices) print(EEG通道数量, len(eeg_channel_indices))然后是标签。对我们情感分类任务最重要的就是valence效价和arousal唤醒度。它们的评分范围是1到9。但在机器学习中我们更习惯处理分类问题。所以一个常见的做法是设定一个阈值比如中位数5.5将连续评分二分为“高”和“低”。更进一步我们可以将两个维度组合得到四分类的情感状态低效价低唤醒度 (LVHA)平静、放松甚至有些无聊。低效价高唤醒度 (LVHA)焦虑、愤怒、紧张。高效价低唤醒度 (HVLA)愉悦、满足、宁静。高效价高唤醒度 (HVHA)兴奋、激动、快乐。这就是我们本期最终要完成的分类任务目标。理解了这个目标我们所有的预处理和特征提取工作就有了明确的指向性——我们要提取那些能够区分这四种状态的大脑活动特征。3. 数据预处理核心滤波与伪迹去除拿到数据后千万别急着往里扔模型。原始EEG信号就像一条浑浊的河流我们的任务是滤出清澈的“情绪之水”。这一步直接决定了后续特征的质量和模型的性能上限。3.1 为什么一定要滤波EEG信号非常微弱只有微伏级别极易受到干扰。主要噪声包括工频干扰来自电源的50Hz或60Hz的固定频率噪声非常强。眼电伪迹眨眼、眼球运动产生的电位比脑电大几个数量级主要影响前额Fp1 Fp2等通道。肌电伪迹皱眉、咬牙等面部肌肉活动产生的高频噪声。基线漂移由于皮肤出汗、电极接触不良等引起的缓慢变化。滤波的目的就是保留我们感兴趣的“情绪相关”脑电频段同时尽可能剔除这些噪声。通常认为与情绪相关的脑电活动主要集中在Delta (1-4Hz) Theta (4-8Hz) Alpha (8-13Hz) Beta (13-30Hz) Gamma (30-45Hz)这几个频段。因此一个标准的带通滤波范围是0.5Hz 到 45Hz。0.5Hz的高通滤波可以去除基线漂移45Hz的低通滤波可以去除高频肌电和工频干扰虽然工频是50Hz但考虑到衰减和过渡带设45Hz更安全。3.2 手把手实现巴特沃斯带通滤波理论说再多不如一行代码。我们使用scipy.signal库中的butter和filtfilt函数来实现一个零相移的带通滤波器。零相移非常重要它能保证滤波不会扭曲信号的时序关系。from scipy.signal import butter, filtfilt import numpy as np def butter_bandpass_filter(data, lowcut, highcut, fs, order4): 对数据应用巴特沃斯带通滤波器零相移。 参数 data: 输入信号可以是一维或多维数组最后一个是时间维度。 lowcut: 通带低截止频率 (Hz)。 highcut: 通带高截止频率 (Hz)。 fs: 采样频率 (Hz)。 order: 滤波器阶数阶数越高过渡带越陡峭但计算量越大相位失真风险越高filtfilt已消除。 返回 滤波后的信号。 # 计算奈奎斯特频率 nyq 0.5 * fs # 将截止频率归一化到[0, 1]区间相对于奈奎斯特频率 low lowcut / nyq high highcut / nyq # 设计巴特沃斯带通滤波器得到系数b分子和a分母 b, a butter(order, [low, high], btypeband) # 使用filtfilt进行前向-后向滤波实现零相移 y filtfilt(b, a, data, axis-1) # 确保沿最后一个维度时间滤波 return y # 假设我们已经从数据中提取了第一位参与者、第一个试次的EEG数据 # sample_eeg_data 形状为 (32个EEG通道, 8064个时间点) sample_eeg_data data[data][0, eeg_channel_indices, :] # 取第一个试次所有EEG通道 # 设置滤波参数 fs data[sampling_rate] # 128 Hz lowcut 0.5 highcut 45.0 order 4 # 应用滤波器 filtered_eeg butter_bandpass_filter(sample_eeg_data, lowcut, highcut, fs, order) print(f原始数据形状{sample_eeg_data.shape} 滤波后形状{filtered_eeg.shape})这段代码就是我们的“净水器核心”。你可以通过调整lowcut和highcut来探索不同频段对情绪的作用。比如有研究认为Alpha波8-13Hz与放松低唤醒相关而Gamma波30-45Hz与高唤醒的复杂认知处理有关。3.3 滤波效果可视化眼见为实光说不练假把式我们得亲眼看看滤波前后信号的变化。这里我们选一个受眼电影响大的前额通道如Fp1和一个相对“干净”的枕叶通道如O1来对比。import matplotlib.pyplot as plt # 选择两个通道进行可视化 fp1_idx all_channel_names.index(Fp1) o1_idx all_channel_names.index(O1) # 创建时间轴 time np.arange(sample_eeg_data.shape[1]) / fs # 单位秒 fig, axes plt.subplots(2, 2, figsize(14, 8)) # 绘制Fp1通道 axes[0, 0].plot(time, sample_eeg_data[fp1_idx, :], b-, linewidth0.5, label原始) axes[0, 0].set_title(Fp1通道 - 原始信号 (易受眼电干扰)) axes[0, 0].set_xlabel(时间 (秒)) axes[0, 0].set_ylabel(幅值 (μV)) axes[0, 0].legend() axes[0, 0].grid(True, alpha0.3) axes[1, 0].plot(time, filtered_eeg[fp1_idx, :], r-, linewidth0.5, label滤波后) axes[1, 0].set_title(Fp1通道 - 滤波后信号) axes[1, 0].set_xlabel(时间 (秒)) axes[1, 0].set_ylabel(幅值 (μV)) axes[1, 0].legend() axes[1, 0].grid(True, alpha0.3) # 绘制O1通道 axes[0, 1].plot(time, sample_eeg_data[o1_idx, :], b-, linewidth0.5, label原始) axes[0, 1].set_title(O1通道 - 原始信号 (视觉皮层)) axes[0, 1].set_xlabel(时间 (秒)) axes[0, 1].set_ylabel(幅值 (μV)) axes[0, 1].legend() axes[0, 1].grid(True, alpha0.3) axes[1, 1].plot(time, filtered_eeg[o1_idx, :], r-, linewidth0.5, label滤波后) axes[1, 1].set_title(O1通道 - 滤波后信号) axes[1, 1].set_xlabel(时间 (秒)) axes[1, 1].set_ylabel(幅值 (μV)) axes[1, 1].legend() axes[1, 1].grid(True, alpha0.3) plt.tight_layout() plt.show()运行这段代码你会直观地看到滤波后的信号“毛刺”明显减少波形变得平滑尤其是Fp1通道那些由眨眼造成的巨大尖峰被有效抑制了。这就是预处理的力量它让信号从“难以解读”变得“有章可循”。4. 特征工程灵魂从波形到信息微分熵详解滤波后的信号干净了但我们还不能直接把长达8064个点的时序数据扔给分类器。那样维度太高计算量大且包含大量冗余信息。我们需要进行特征提取把长长的波形压缩成能代表其统计或频谱特性的几个数字。在EEG情绪识别领域微分熵是一个被广泛验证有效的特征。4.1 什么是微分熵一个通俗的理解你可以把微分熵理解为衡量一段信号“复杂程度”或“不确定性”的指标。想象一下一个完全规律的正弦波比如工频噪声它的变化是可预测的不确定性低微分熵就低。而一段复杂的、充满随机起伏的脑电信号不确定性高微分熵就高。更重要的是研究发现不同情绪状态下大脑在不同频段的“活动复杂度”会发生变化。例如平静状态下Alpha波增强信号在特定频段可能变得更规律微分熵降低而兴奋状态下全脑活动激烈且复杂微分熵可能升高。数学上对于一段服从高斯分布很多生物信号近似满足的信号其在某个频带上的微分熵有一个非常漂亮的性质它等价于对该频带信号进行傅里叶变换后功率谱对数和的线性变换。这就为我们提供了极其高效的计算方法。4.2 分段与频带划分捕捉动态变化情绪不是60秒内一成不变的它可能在视频的高潮部分发生变化。因此更好的做法是将每个试次63秒的数据分成若干段例如每段3秒共21段。然后在每一段内计算每个EEG通道在不同频带Delta Theta Alpha Beta Gamma上的微分熵。这样我们就得到了一个三维特征张量[段数 通道数 频带数]。这比原始的[通道数 时间点]结构化得多信息密度也高得多。4.3 代码实战计算微分熵特征下面我们来实现这个核心的特征提取流程。我们将使用短时傅里叶变换来获取功率谱。import numpy as np from scipy import signal, stats def compute_de_features(eeg_data, fs, segment_duration3.0, bandsNone): 计算微分熵特征。 参数 eeg_data: 滤波后的EEG数据形状 (通道数, 时间点)。 fs: 采样频率。 segment_duration: 每段的时长秒。 bands: 频带字典例如 {delta: (1,4), theta: (4,8), alpha: (8,13), beta: (13,30), gamma: (30,45)}。 返回 de_features: 微分熵特征形状 (段数 通道数 频带数)。 if bands is None: bands { delta: (1, 4), theta: (4, 8), alpha: (8, 13), beta: (13, 30), gamma: (30, 45) } n_channels, n_points eeg_data.shape segment_length int(segment_duration * fs) # 每段的数据点数 n_segments n_points // segment_length # 计算完整的段数 # 初始化特征数组 n_bands len(bands) de_features np.zeros((n_segments, n_channels, n_bands)) # 遍历每一段 for seg_idx in range(n_segments): start seg_idx * segment_length end start segment_length segment_data eeg_data[:, start:end] # 取当前段的数据 # 遍历每个通道 for ch_idx in range(n_channels): # 计算该通道该段数据的功率谱密度 freqs, psd signal.welch(segment_data[ch_idx, :], fs, nperseg256) # 遍历每个频带计算微分熵 for band_idx, (band_name, (low, high)) in enumerate(bands.items()): # 找到频带对应的频率索引 idx_band np.logical_and(freqs low, freqs high) # 计算该频带内的功率和 band_power np.sum(psd[idx_band]) # 根据公式计算微分熵 (假设信号在该频带内服从高斯分布) # 公式: DE log(2πe * σ^2) / 2 其中σ^2正比于功率 # 对于离散功率谱一个常用的近似是DE log(band_power) # 为了数值稳定加上一个小常数 de np.log(band_power 1e-10) de_features[seg_idx, ch_idx, band_idx] de return de_features # 使用我们之前滤波后的数据第一个试次来计算特征 de_feat compute_de_features(filtered_eeg, fs, segment_duration3.0) print(f微分熵特征形状{de_feat.shape} (段数 通道数 频带数))运行后你可能得到类似(21, 32, 5)的输出。这意味着我们从第一个试次中提取了21个时间片段、32个通道、5个频带的微分熵特征。现在每个试次不再是806432258048个原始数据点而是2132*53360个特征值数据得到了极大的压缩并且包含了丰富的频域-空间-时域信息。5. 构建分类数据集整合与标签映射我们已经掌握了从单个试次提取特征的方法。现在我们需要将32位参与者、40个试次的数据全部处理一遍构建一个完整的、可供机器学习模型使用的数据集。5.1 批量处理所有数据我们需要写一个循环遍历所有的.dat文件对每个试次的EEG数据进行滤波、特征提取并收集对应的效价和唤醒度标签。import os import pickle import numpy as np from tqdm import tqdm # 用于显示进度条 def create_deap_feature_dataset(data_path, participantsrange(1, 33), lowcut0.5, highcut45.0): 从DEAP预处理数据创建微分熵特征数据集。 参数 data_path: 存放所有 .dat 文件的文件夹路径。 participants: 参与者编号列表默认1到32。 lowcut, highcut: 滤波参数。 返回 all_features: 特征数组形状 (总试次数*段数 通道数 频带数) 或展平后的形状。 all_labels: 标签数组形状 (总试次数 2) 或 (总试次数 ) 对于四分类。 all_features_list [] all_valence_list [] all_arousal_list [] fs 128 # DEAP预处理数据的采样率 for subj in tqdm(participants, desc处理参与者): filename fs{subj:02d}.dat file_path os.path.join(data_path, filename) try: with open(file_path, rb) as f: data pickle.load(f, encodinglatin-1) except FileNotFoundError: print(f警告文件 {filename} 未找到跳过。) continue # 提取数据 eeg_data_all_trials data[data][:, :32, :] # 取所有试次前32个通道EEG labels data[labels][:, :2] # 取效价和唤醒度 n_trials eeg_data_all_trials.shape[0] for trial_idx in range(n_trials): # 1. 提取单个试次EEG数据 trial_eeg eeg_data_all_trials[trial_idx, :, :] # (32, 8064) # 2. 滤波 filtered_trial_eeg butter_bandpass_filter(trial_eeg, lowcut, highcut, fs, order4) # 3. 计算微分熵特征 de_features compute_de_features(filtered_trial_eeg, fs, segment_duration3.0) # de_features 形状: (21, 32, 5) # 4. 将特征展平或聚合。这里我们选择将段维度与试次维度合并。 # 即每个试次的21段视为21个独立的样本假设段间情绪稳定或变化缓慢。 n_segments de_features.shape[0] flattened_features de_features.reshape(n_segments, -1) # 形状 (21, 32*5160) # 5. 收集特征和标签 # 每个段都使用该试次整体的情绪标签这是一种简化更精细的做法可以分段标注 for _ in range(n_segments): all_features_list.append(flattened_features) all_valence_list.append(labels[trial_idx, 0]) all_arousal_list.append(labels[trial_idx, 1]) # 转换为NumPy数组 all_features np.array(all_features_list) all_valence np.array(all_valence_list) all_arousal np.array(all_arousal_list) print(f数据集构建完成。) print(f特征形状{all_features.shape}) print(f效价标签形状{all_valence.shape}) print(f唤醒度标签形状{all_arousal.shape}) return all_features, all_valence, all_arousal # 使用函数 data_dir ./data/preprocessed_python # 你的数据文件夹路径 X, y_valence, y_arousal create_deap_feature_dataset(data_dir, participantsrange(1, 5)) # 先处理前4个人试试水这个函数是本期内容的集大成者。它串联了数据读取、滤波、特征提取的全流程。处理全部32名参与者可能需要一些时间你可以先用前几位参与者的数据跑通流程。5.2 生成四分类任务标签最后我们根据阈值将连续的效价和唤醒度评分映射为我们之前定义的四分类标签。def create_four_class_labels(valence_scores, arousal_scores, threshold5.0): 根据效价和唤醒度创建四分类标签 (0,1,2,3)。 阈值默认为5.0评分范围1-9的中值附近。 labels [] for v, a in zip(valence_scores, arousal_scores): if v threshold and a threshold: labels.append(3) # HVHA: 高效价高唤醒 elif v threshold and a threshold: labels.append(2) # HVLA: 高效价低唤醒 elif v threshold and a threshold: labels.append(1) # LVHA: 低效价高唤醒 else: labels.append(0) # LVLA: 低效价低唤醒 return np.array(labels) # 应用映射 y_four_class create_four_class_labels(y_valence, y_arousal, threshold5.0) # 查看类别分布 unique, counts np.unique(y_four_class, return_countsTrue) print(四分类标签分布) for cls, cnt in zip(unique, counts): print(f 类别 {cls}: {cnt} 个样本)5.3 数据标准化与分割在送入模型前还有两个标准步骤标准化不同通道、不同频带的微分熵值范围可能不同。我们需要对特征进行标准化例如Z-score标准化使每个特征维度均值为0方差为1有助于模型收敛。数据集分割将数据划分为训练集、验证集和测试集。切记必须按参与者进行分割而不是随机打乱所有样本。因为我们的目标是让模型学会泛化到从未见过的、新的人的大脑活动模式这才是真正的实用价值。如果把同一个人的数据既用于训练又用于测试模型可能只是记住了这个人的特定模式导致虚假的高性能。from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split # 假设我们有一个 participant_ids 列表长度与 X 相同标识每个样本属于哪位参与者 # 这里需要你根据之前构建数据集的循环逻辑来生成这个列表 # 示例我们以前4位参与者为例手动构造一个参与者ID列表实际中应在create_deap_feature_dataset函数中生成 # 假设我们处理了4个人每人40个试次每个试次21段共4*40*213360个样本 participant_ids [] for subj in range(1, 5): participant_ids.extend([subj] * (40 * 21)) # 每人贡献40*21个样本 participant_ids np.array(participant_ids) # 按参与者分割例如参与者123的数据作为训练验证集参与者4的数据作为测试集 train_val_mask participant_ids 3 test_mask participant_ids 4 X_train_val X[train_val_mask] y_train_val y_four_class[train_val_mask] participant_ids_train_val participant_ids[train_val_mask] X_test X[test_mask] y_test y_four_class[test_mask] # 在训练验证集内部再按参与者分割出验证集例如用参与者3的数据做验证 val_mask participant_ids_train_val 3 X_train X_train_val[~val_mask] y_train y_train_val[~val_mask] X_val X_train_val[val_mask] y_val y_train_val[val_mask] print(f训练集形状{X_train.shape}) print(f验证集形状{X_val.shape}) print(f测试集形状{X_test.shape}) # 标准化只使用训练集的数据拟合scaler然后转换所有集合 scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_val_scaled scaler.transform(X_val) X_test_scaled scaler.transform(X_test)至此我们完成了DEAP数据集EEG情感分类任务的全部数据准备工作。我们拥有了干净、标准化、并且按参与者正确分割的特征数据(X_train_scaled, X_val_scaled, X_test_scaled)和对应的四分类标签(y_train, y_val, y_test)。在下一期我们就可以直接使用这些数据来训练和评估各种机器学习模型如SVM、随机森林甚至深度学习模型如CNN、LSTM、图神经网络看看我们的“情绪读心术”到底能有多准。记住扎实的预处理是成功的一半你现在已经拥有了一个非常高的起点。