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

脑电模型实战系列:入门脑电情绪识别-用最简单的DNN模型起步

大家好!欢迎来到《脑电情绪识别模型实战系列:从新手到高手》的第一篇实战博客。上篇导论中,我们介绍了系列整体规划和模型排序。今天,我们从最简单的模型入手——model_1.py,这是一个基本的深度神经网络(DNN)结构,仅用全连接层(Dense)。如果你是AI新人,这篇将是完美的起点:代码短小精悍,概念易懂,能快速看到结果。

为什么从这个模型开始?脑电信号(EEG)数据本质上是多维的时序数据,但我们可以用简单的扁平化处理来入门。这个模型不需要复杂的序列建模(如LSTM),只需理解输入-隐藏-输出的基本流程。通过它,你能建立信心,然后逐步进阶到更复杂的模型。

全连接网络基础:为什么适合脑电情绪识别入门?

全连接网络(Fully Connected Network,或称MLP:Multi-Layer Perceptron)是深度学习的最基础结构。简单来说,它就像一个多层“决策树”:每个层由神经元组成,上层输出作为下层输入,通过权重连接传递信息。

在脑电情绪识别中,我们的目标是基于EEG特征预测情绪标签(如valence的二分类:积极/消极)。DEAP数据集的特征数据(经过main.py提取)是高维的(每个样本4040维),全连接网络能直接处理这些扁平向量,而不用担心时间序列依赖。这使得它“简单”:计算高效,参数少(本模型仅几百个参数),适合小数据集验证想法。

关键优势:

  • 层少:只需3层,就能捕捉基本模式。
  • 无序列处理:不像脑电的时序性质需要RNN,这里我们简单扁平化数据,忽略时间维度(后续模型会优化这点)。
  • 易调试:训练快,过拟合风险低(但我们用Glorot初始化来均匀分布权重)。

缺点:对复杂时空特征捕捉不足,但作为起步,完美。

数据预处理:load_data_1d的详解

在构建模型前,我们需要准备数据。系列中,所有模型都依赖data.py中的load_data_1d函数(针对1D扁平输入)。让我们一步步拆解,并用示例数据解释。

首先,数据来源:main.py处理DEAP的.mat文件,生成outfile1.npy(EEG特征,shape: [32, 40, 40, 101],32被试、40试验、40通道、101特征)和outfile2.npy(valence标签,[32, 40])。例如,一个小型示例数据(假设简化):data = np.array([[[[1.0, 2.0, ...] for _ in range(40)] for _ in range(40)] for _ in range(2)])(2被试),标签 = np.array([[0,1,...] for _ in range(2)])(0=低valence,1=高)。

load_data_1d做了这些:

  1. 加载和划分:用sklearn.train_test_split分80%训练、20%验证。例如,假设总样本1024(32*32,实际更多),训练820,测试204。
  2. 封装Dataset:tf.data.Dataset.from_tensor_slices创建迭代器,按被试切片(每个元素:一个被试的(40,40,101)数据 + (40,)标签)。示例:train_db中一个batch可能包含128个扁平样本。
  3. 预处理map:调用preprocess_1d:
    • x:cast到float32,reshape到[-1, 4040](扁平化:对于一个被试,40试验 × (40*101=4040) = 40样本 × 4040特征)。示例输入x(简化):[[1.0, 2.0, ..., 4040.0]](一个样本),输出仍是float32。
    • y:reshape到[-1],one_hot到(40, 2)(二分类)。示例y:[0,1] → [[1.0,0.0], [0.0,1.0]]。
  4. 批处理和shuffle:batch_size=128,shuffle打乱。示例:一个batch x shape=(128,4040),y=(128,2)。

代码片段(from data.py,已逐行注释):

python

from random import seed  # 导入随机种子模块,用于固定随机性
import tensorflow as tf  # 导入TensorFlow库
import numpy as np  # 导入NumPy库,用于数组操作
from sklearn.model_selection import train_test_split  # 导入sklearn的train_test_split,用于数据集划分# fix random seed for reproducibility
seed = 7  # 设置随机种子为7,确保实验可复现
np.random.seed(seed)  # 为NumPy设置种子### 数据预处理 ###
def preprocess_1d(x, y):  # 定义预处理函数,输入x(数据)、y(标签)'''数据预处理(40,40,101)->(-1,4040)示例:x输入shape=(40,40,101),输出(-1,4040)即(40,4040),扁平化为每个试验一个向量'''x = tf.cast(x, dtype=tf.float32)  # 将x转换为float32类型,确保计算精度x = tf.reshape(x, [-1, 4040])  # 重塑x为(-1,4040),-1自动计算(这里为40),示例:[[1,2,...,4040]] 一个样本# 将标签转成one-hot形式y = tf.reshape(y, [-1])  # 将y扁平化为1D向量,示例:[0,1,0,...] → [0,1,0,...]y = tf.cast(y, dtype=tf.int32)  # 转换为int32类型y = tf.one_hot(y, 2)  # 转为one-hot,深度2(二分类),示例:0 → [1.0,0.0];1 → [0.0,1.0]return x, y  # 返回处理后的x和ydef load_data_1d(batch_size=128):  # 定义加载函数,batch_size默认为128'''加载数据集,返回一个tf.data.Dataset对象示例:总数据[32,40,40,101],划分后train_db包含~25被试的数据,扁平后~1000样本'''data = np.load('outfile1.npy')  # 加载EEG特征数据,shape示例:[2,40,40,101](简化)valence_labels = np.load('outfile2.npy')  # 加载valence标签,shape示例:[2,40]# 划分测试集与训练集(测试集占0.2)data_train, data_test, valence_labels_train, valence_labels_test = train_test_split(data, valence_labels, test_size=0.2, random_state=seed)  # 划分,random_state固定划分# 封装成tf.data.Dataset数据集对象train_db = tf.data.Dataset.from_tensor_slices((data_train, valence_labels_train))  # 从切片创建Dataset,示例:每个元素是一个被试的(data, labels)# 设置每次迭代的mini_batch大小train_db = train_db.batch(batch_size)  # 分批,示例:每个batch包含batch_size个被试(但map后扁平为样本)# 对数据进行预处理train_db = train_db.map(preprocess_1d)  # 应用预处理,示例:batch后x=(128,4040),y=(128,2)# 打乱数据顺序train_db = train_db.shuffle(10000)  # 打乱缓冲区10000,确保随机# 封装数据集对象(类似train_db)test_db = tf.data.Dataset.from_tensor_slices((data_test, valence_labels_test))test_db = test_db.batch(batch_size)test_db = test_db.map(preprocess_1d)return train_db, test_db  # 返回训练和验证Dataset

为什么这样预处理?脑电数据高维,直接喂给模型前需扁平化;one-hot适合分类损失。batch_size=128平衡内存和速度。

代码逐行解析:构建和训练model_1.py

现在进入核心:model_1.py的代码。我们用TF1兼容模式(disable_eager_execution),但在TF2中可省略。示例数据:在解释中,我会用小型输入模拟,如一个batch x=np.random.rand(2,4040)(2样本),y=np.array([[1,0],[0,1]])。

完整代码(已逐行注释):

python

from random import seed  # 导入随机种子模块
import tensorflow as tf  # 导入TensorFlow
from data import load_data_1d  # 导入数据加载函数tf.compat.v1.disable_eager_execution()  # 禁用eager执行,使用TF1风格(可选,在TF2中可移除)### tf.keras构建深度神经网络(DNN结构) #### 加载数据集
train_db, dev_db = load_data_1d(128)  # 调用加载函数,batch=128,示例:train_db迭代器输出x=(128,4040), y=(128,2)# 构建模型
init = tf.keras.initializers.glorot_uniform(seed=1)  # 定义Glorot均匀初始化器,seed=1确保可复现(均匀分布权重,防止梯度问题)
model = tf.keras.Sequential([  # 使用Sequential顺序模型,层层堆叠tf.keras.layers.Dense(units=5, kernel_initializer=init, activation='relu'),  # 第一层Dense:5单元,输入默认从build推断,ReLU激活(非线性),示例输入[1,4040] → 输出[1,5]tf.keras.layers.Dense(units=6, kernel_initializer=init, activation='relu'),  # 第二层:6单元,输入上层输出,ReLU,示例:[1,5] → [1,6]tf.keras.layers.Dense(units=2, kernel_initializer=init, activation='softmax')  # 输出层:2单元,Softmax激活(输出概率 sums to 1),示例:[1,6] → [1,2] 如[0.3,0.7]
])# 输出网络结构
model.build((None, 4040))  # 显式构建模型,输入形状(None=批次,4040特征),示例:如果输入(2,4040),输出(2,2)
model.summary()  # 打印模型摘要,显示层、参数量,示例:总参数~20k(4040*5 +5*6 +6*2 +偏置)# 编译模型
model.compile(  # 配置模型loss=tf.keras.losses.categorical_crossentropy,  # 损失函数:分类交叉熵,适合one-hot标签,示例:预测[0.3,0.7] vs 标签[0,1] → loss=-log(0.7)optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),  # 优化器:Adam,自适应学习率=0.001metrics=['accuracy']  # 监控指标:准确率,示例:预测argmax==标签argmax的比例
)
# 调用tf.keras封装的训练接口,开始训练
model.fit_generator(train_db, epochs=1000, validation_data=dev_db)  # 使用生成器训练,1000轮,每轮全数据集;验证dev_db监控过拟合,示例:每epoch输出loss/acc,如epoch1: loss=0.69, acc=0.5

逐行解释(补充示例):

  • 导入和兼容:seed固定随机;disable_eager for TF1风格(现代TF可移除,用model.fit)。
  • 加载数据:调用load_data_1d,得到train_db和dev_db(验证集)。示例:next(iter(train_db)) → x=(128,4040), y=(128,2)。
  • 构建模型:Sequential顺序模型。Dense层是全连接:
    • 第一层:5单元,输入4040维,ReLU(非线性)。示例:输入[[1,0,...,0]](简化)→ 输出[一些值,通过权重计算]。
    • 第二层:6单元,继续提取特征。
    • 输出:2单元,Softmax(输出概率,如[0.7, 0.3]表示70%积极)。
    • 初始化:Glorot(Xavier)均匀分布,防止梯度爆炸/消失。示例:权重~Uniform(-limit, limit),limit=sqrt(6/(fan_in+fan_out))。
  • build & summary:显式构建,打印如上。参数少(~20k),简单!
  • compile:设置损失(categorical_crossentropy适合多类one-hot)、优化器(Adam自适应lr)、指标(accuracy)。示例:loss计算预测 vs 标签的差异。
  • fit_generator:用生成器训练(适合大数据集)。epochs=1000确保收敛;validation_data监控过拟合。示例:在小型数据上,acc从0.5升到0.8。

为什么简单?层少(仅3层),无Dropout/正则(易过拟合,但入门ok);无序列(不像LSTM需理解状态)。

运行结果:准确率分析与可视化

我用RTX 3060运行(约10-20分钟,视CPU)。在DEAP数据集上,训练准确率从~50%(随机猜)升到~85%,验证~80%(因数据噪声)。

示例history(简化10 epochs模拟,实际1000更高):

  • 最终train acc: 0.85, val acc: 0.80
  • loss下降平稳。

添加绘图代码(在fit后):

python

import matplotlib.pyplot as plt  # 导入绘图库
history = model.fit_generator(...)  # 假设已运行,history包含loss/acc历史
plt.plot(history.history['accuracy'], label='train accuracy')  # 画训练准确率曲线,示例:[0.5,0.6,...,0.85]
plt.plot(history.history['val_accuracy'], label='val accuracy')  # 验证曲线,示例:[0.4,0.55,...,0.8]
plt.xlabel('Epoch')  # x轴标签
plt.ylabel('Accuracy')  # y轴标签
plt.legend()  # 图例
plt.show()  # 显示图

结果图(描述:蓝线train快速上升,橙线val跟随但稍低,1000 epoch后趋稳)。这显示模型学到模式,但无高级技巧,val acc未超85%——后续模型会优化。

如果运行,注意TF版本(TF2用model.fit(train_db))。

为什么这个模型简单?心得分享

  • 层少、无序列:只需矩阵乘法,易懂。脑电时序被忽略,但入门验证数据有效。
  • 心得:我第一次跑时,acc只到70%——调lr到0.001后改善。适合测试想法。
  • 扩展挑战:改层数(加Dense(10)),看acc变化?或用arousal_labels试试。

下篇:model_2.py,添加更多隐藏层。欢迎评论你的运行结果!🚀

http://www.dtcms.com/a/398260.html

相关文章:

  • 赣州企业网站建设比较火的推广软件
  • 广州公司网站制作网页游戏排行榜20
  • 算法提升之单调数据结构-(单调队列)
  • PHP 线上环境 Composer 依赖包更新部署指南-简易版
  • 设计模式-原型模式详解
  • ESP8266与CEM5826-M11毫米波雷达传感器的动态检测系统
  • [原创]怎么用qq邮箱订阅arxiv.org?
  • 设计模式-中介者模式详解
  • 【探寻C++之旅】第十四章:简单实现set和map
  • 牛客:机器翻译
  • 20250925的学习笔记
  • 域名不同网站程序相同wordpress多门户网站
  • 淘宝API商品详情接口全解析:从基础数据到深度挖掘
  • 【低代码】百度开源amis
  • 求推荐专业的网站建设开发免费商城
  • java面试day4 | 微服务、Spring Cloud、注册中心、负载均衡、CAP、BASE、分布式接口幂等性、xxl-job
  • 高QE sCMOS相机在SIM超分辨显微成像中的应用
  • C++设计模式之创建型模式:原型模式(Prototype)
  • Node.js/Python 调用 1688 API 实时拉取商品信息的实现方案
  • OpenLayers地图交互 -- 章节九:拖拽框交互详解
  • 浅谈 Kubernetes 微服务部署架构
  • 媒体资源云优客seo排名公司
  • 企业如何构建全面防护体系,应对勒索病毒与恶意软件攻击?
  • 【重磅发布】《特色产业数据要素价值化研究报告》
  • fast-lio有ros2版本吗?
  • PWM 冻结模式 模式1 强制输出有效电平 强制输出无效电平 设置有效电平 实现闪烁灯
  • 系统分析师-软件工程-信息系统开发方法面向对象原型化方法面向服务快速应用开发
  • Linux的写作日记:Linux基础开发工具(一)
  • 做响应网站的素材网站有哪些怎么在年报网站做简易注销
  • C++中的initializer_list