2023/4/5 -4/17 脑机接口学习内容一览:
这一篇文章主要对DEAP数据集转化为python可以处理的格式,并且进一步使用LSTM网络进行分类工作。
一、数据集分析
详情见于官网:DEAPdataset
若想使用数据集可自行在官网申请下载。
以下是对该数据集的简要介绍:
文件夹界面:
mat文件的解释如下:
dataset key_word:
(1)labels
shape (40, 4)
解释:
labels用于标记被试看到每一个视频时的状态
第二维度中的四个数据代表四个评价标准,即arousal(唤醒度), valence(愉悦度), dominance(支配度), like(喜爱度)
(2)data
shape (40, 40, 8064)
解释:
第一个维度代表40段观看视频产生的脑电数据
第二个维度代表每段数据存在40个通道
第三个维度为采样点,长度在63s左右
根据官方网站得采样率为128hz
数据已经经过预处理,预处理手段如下:
1、降低采样率至128hz;
2、移除眼动伪迹;
3、设置平均参考;
4、重新排序脑电通道;
5、数据被分割为60秒试验和3秒的基线;
6、试验从演示顺序重新排序为视频(Experiment_id)顺序;
7、应用了4至45hz的带通滤波。
由于第33至第40通道的数据与EEG信号用不上,稍后在预处理阶段移除,只保留前32个通道进行处理。
二、了解情感轮(emotion wheel)
在每个labels中存在四个数值,即arousal(唤醒度), valence(愉悦度), dominance(支配度), like(喜爱度),前三个指标构成了情感轮。对于情感轮我不是很了解,故下面借助chatgpt来解释:
1、如何分析与Valence,Arousal,Dominance相关的情感轮?
情感轮通常会将情感分为三个维度:Valence,Arousal,Dominance(VAD)。这三个维度可以帮助人们更好地理解和表达情感的复杂性。
Valence:Valence表示情感的积极或消极性。积极的情感通常被认为是愉悦、高兴、爱和满意,而消极的情感通常被认为是悲伤、恐惧、愤怒和厌恶。在情感轮中,通常将情感分为“正向”和“负向”,从左到右分别表示积极和消极的情感。
Arousal:Arousal表示情感的强度或活跃程度。高度的情感通常表示兴奋、兴趣和激动,而低度的情感通常表示冷静、平静和沉闷。在情感轮中,从中心向外的距离表示情感的强度,中心表示低度的情感,外围表示高度的情感。
Dominance:Dominance表示情感的控制程度。高度的情感控制通常表示自信、主动和支配,而低度的情感控制通常表示无助、被动和受控。
在情感轮中,从底部到顶部的距离表示情感的控制程度,底部表示低度的控制,顶部表示高度的控制。 通过将情感分为这三个维度,情感轮可以帮助人们更好地理解和表达情感的复杂性。例如,一个积极的情感可以是高度的兴奋和控制(如自信和决心),也可以是低度的兴奋和控制(如放松和满足)。对于情感分析,这些维度可以帮助人们更好地识别和分类情感,并为情感分类提供更准确和精细的方法。
2、将这三个指标量化为1到9这几个分级后,如何通过分级判断情感状态,举出具体例子
通过将Valence、Arousal、Dominance这三个指标量化为1到9这几个分级,情感状态可以根据不同的数值组合来进行判断。以下是一些具体例子:
1. Valence=1,Arousal=1,Dominance=1:这表示情感是消极、低度兴奋和控制。这可能代表着一种沮丧和无助的情感状态。
2. Valence=9,Arousal=9,Dominance=1:这表示情感是积极、高度兴奋和低度控制。这可能代表着一种兴奋和激动的情感状态,但无法完全掌控。
3. Valence=5,Arousal=1,Dominance=9:这表示情感是中性的、低度兴奋和高度控制。这可能代表着一种平静和冷静的情感状态,但同时也感到有掌控力。
4. Valence=3,Arousal=9,Dominance=3:这表示情感是消极的、高度兴奋和中度控制。这可能代表着一种情绪激动和不安的情感状态。
通过这些示例,可以看出Valence、Arousal、Dominance这三个指标的不同数值组合可以代表不同的情感状态。将这些指标量化为1到9这些分级,可以更加精细地表达和描述情感状态。
3、like指标的作用
在情感轮中,Valence、Arousal和Dominance用来描述情感的基本属性,而Like则是一种情感的表达方式。Like可以表示喜欢、欣赏、支持、赞赏等情感,通常与积极的情感相关。 在分析与Valence、Arousal、Dominance、Like相关的情感轮时,可以考虑如下几点:
1. Like与Valence的关系:Like通常与积极的情感相关,因此高Valence的情感通常更容易引起Like的表达。例如,一个高度积极的情感,如喜悦或爱,更容易引起Like的表达。
2. Like与Arousal的关系:Like通常与低度兴奋的情感相关,因为低度兴奋的情感更容易引起人们的喜欢和赞赏。但是,在某些情况下,高度兴奋的情感也可以引起Like的表达,例如在一场比赛或者一个演出中,高度兴奋的情感也可以引起人们的喜欢和赞赏。
3. Like与Dominance的关系:Like通常与低度控制的情感相关,因为低度控制的情感更容易引起人们的喜欢和赞赏。但是,在某些情况下,高度控制的情感也可以引起Like的表达,例如在领导和支配的情境中,高度控制的情感也可以引起人们的喜欢和赞赏。
4. Like与情感的表达方式:Like是一种情感的表达方式,它可以通过语言、行为、肢体语言等方式表达出来。在情感轮中,Like通常在外圈表示,因为它是一种情感的表达方式,而不是情感本身。
通过考虑这些因素,可以更好地理解和分析与Valence、Arousal、Dominance、Like相关的情感轮。例如,一个积极的情感,如喜悦,通常具有高Valence和低Arousal,同时可能具有低度控制和Like的表达。
4、EEG信号中代表arousal(唤醒度), valence(愉悦度), dominance(支配度), like(喜爱度)的特征有哪些?
EEG信号中代表arousal(唤醒度), valence(愉悦度), dominance(支配度), like(喜爱度)的特征有以下几种:
1. 频谱特征:EEG信号的频谱特征可以反映出脑电活动的能量分布和频率特征。例如,alpha波的增加与愉悦度的增加有关系,而beta波的增加则与唤醒度的增加有关系。
2. 时域特征:EEG信号的时域特征可以反映出脑电活动的振幅、周期和相位。例如,P300波的振幅与注意力的集中程度有关系。
3. 熵特征:EEG信号的熵特征可以反映出脑电活动的复杂程度和不规则程度。例如,样本熵(Sample Entropy)可以反映出脑电活动的复杂程度和唤醒度的变化。
4. 聚类特征:EEG信号的聚类特征可以反映出脑电活动的空间分布和相关性。例如,相干性(Coherence)可以反映出不同脑区之间的相关性和支配关系。
5. 事件相关电位(ERP):ERP是一种脑电信号的特殊形式,是由于特定的刺激或任务产生的脑电响应。例如,N170波可以反映出面孔识别的加工过程,而P300波可以反映出注意力的集中程度和识别的准确性。
6. 睡眠特征:睡眠是一种自然的状态,可以通过EEG信号的睡眠特征来反映。例如,睡眠深度和REM睡眠可以通过EEG的频谱和时域特征来判断。 需要注意的是,不同的特征可能对应着不同的脑区和神经环路,因此在分析和识别中需要考虑到多种特征的综合作用。
根据PAD三维情感模型理论,我们可能将进行一个将情感进行16(?)分类的任务。
根据arousal(唤醒度), valence(愉悦度), dominance(支配度), like(喜爱度)这四个维度,可以将人的情绪分为以下16种:
1. 高愉悦、高唤醒、高支配、高喜爱:这种情绪状态通常被称为“狂喜”、“兴奋”等。
2. 高愉悦、高唤醒、高支配、低喜爱:这种情绪状态通常被称为“自恋”、“傲慢”等。
3. 高愉悦、高唤醒、低支配、高喜爱:这种情绪状态通常被称为“热情”、“激动”等。
4. 高愉悦、高唤醒、低支配、低喜爱:这种情绪状态通常被称为“激昂”、“冷漠”等。
5. 高愉悦、低唤醒、高支配、高喜爱:这种情绪状态通常被称为“自豪”、“自信”等。
6. 高愉悦、低唤醒、高支配、低喜爱:这种情绪状态通常被称为“得意”、“傲慢”等。
7. 高愉悦、低唤醒、低支配、高喜爱:这种情绪状态通常被称为“放松”、“愉悦”等。
8. 高愉悦、低唤醒、低支配、低喜爱:这种情绪状态通常被称为“宁静”、“平静”等。
9. 低愉悦、高唤醒、高支配、高喜爱:这种情绪状态通常被称为“愤怒”、“恼怒”等。
10. 低愉悦、高唤醒、高支配、低喜爱:这种情绪状态通常被称为“愤慨”、“不满”等。
11. 低愉悦、高唤醒、低支配、高喜爱:这种情绪状态通常被称为“焦虑”、“紧张”等。
12. 低愉悦、高唤醒、低支配、低喜爱:这种情绪状态通常被称为“悲伤”、“失落”等。
13. 低愉悦、低唤醒、高支配、高喜爱:这种情绪状态通常被称为“嫉妒”、“惊讶”等。
14. 低愉悦、低唤醒、高支配、低喜爱:这种情绪状态通常被称为“羞愧”、“尴尬”等。
15. 低愉悦、低唤醒、低支配、高喜爱:这种情绪状态通常被称为“温柔”、“感动”等。
16. 低愉悦、低唤醒、低支配、低喜爱:这种情绪状态通常被称为“沮丧”、“无聊”等。
需要注意的是,这些情绪状态的划分也不是非常明确,而且在不同的文化和语境中可能存在差异。因此,在具体应用中,需要根据实际情况进行调整和适配。
三、LSTM神经网络
对于脑电信号这种时序信号,使用LSTM神经网络进行处理能够得到较好的效果。
LSTM神经网络的具体细节不再赘述。
四 、处理流程
1、重整数据集为mne格式;
2、重整labels,根据二中分类标准进行编码,编码为0~15,一共16个类别;
3、根据对情感轮的分析,本次主要采用频谱特征提取的方式提取频谱功率的时序变化特征;
4、将提取特征输入LSTM网络进行预测;
5、根据预测值和loss绘制tensorboard图像。
探索部分:
(1)查找deap数据集相关资料,了解数据集结构;(这一步花费时间较多)
(2)查找LSTM相关资料,了解LSTM的工作原理;
(3)代码实践。
五、代码部分
import numpy as np
import matplotlib.pyplot as plt
import mne
import scipy.io as scio
import warnings
warnings.filterwarnings("ignore", category=RuntimeWarning)
import torch
import torch.nn as nn
from torchvision import transforms
import os
from torch.utils.tensorboard import SummaryWriter
from sklearn.model_selection import train_test_split# 超参数
num_epochs = 500 # 训练轮数
num_sample = 60 # 时间轴上时间点数量,num_sample + t_max < 63(数据持续时间)
learing_rate = 0.0003 # 学习率
t_max = 3 # 每一个mne.epoch的持续长度
output_types = 16 # LSTM输出种类数量class my_lstm(nn.Module):def __init__(self):super(my_lstm, self).__init__()self.rnn = nn.LSTM( # if use nn.RNN(), it hardly learnsinput_size=160, # 输入维度hidden_size=50, # 隐藏层神经元节点个数num_layers=2, # 神经元层数batch_first=True, # input & output will has batch size as 1s dimension. e.g. (batch, time_step, input_size)bidirectional=True,)self.out = nn.Sequential(nn.Dropout(p=0.5),nn.Linear(100, 64),nn.ReLU(),nn.Linear(64, output_types))def forward(self, x):# x shape (batch, time_step, input_size)# r_out shape (batch, time_step, output_size)# h_n shape (n_layers * n_directions, batch, hidden_size)# h_c shape (n_layers * n_directions, batch, hidden_size)r_out, (h_n, h_c) = self.rnn(x, None)# choose r_out at the last time stepout = self.out(r_out[:, -1, :])return outdef train():# 这里初始空值维度设置为第一个文件第一个视频中的维度,便于拼接,之后需要去掉features = np.empty((40, num_sample, 160))labels = []# 要遍历的文件夹路径folder_path = "data/data_preprocessed_matlab"# 遍历文件夹下的文件名file_names = [f for f in os.listdir(folder_path) if os.path.isfile(os.path.join(folder_path, f))]# print(file_names)test_file = ['s01.mat', 's02.mat', 's03.mat', 's04.mat', 's05.mat', 's06.mat', 's07.mat', 's09.mat']for i in file_names:path = folder_path + "/" + iinput_features, y = dataset(path)features = np.concatenate([features, input_features], axis=0)labels = np.append(labels, y)# print(features.shape)# print(labels.shape)# print(labels)labels = labels.astype(np.int32)# print(labels)print(labels.shape)features = features[40:]# print(features)print(features.shape)# 将特征数组和labels转换成PyTorch tensortrain_features, test_features, train_labels, test_labels = train_test_split(features, labels, test_size=0.2,random_state=1)train_features = torch.from_numpy(train_features).float()train_labels = torch.from_numpy(train_labels).long()test_features = torch.from_numpy(test_features).float()test_labels = torch.from_numpy(test_labels).long()for epoch in range(num_epochs):# 前向传播train_outputs = lstm(train_features)print(train_outputs.shape)train_loss = criterion(train_outputs, train_labels)# 反向传播和优化optimizer.zero_grad()train_loss.backward()optimizer.step()# 在训练集上计算准确率train_predicted = torch.argmax(train_outputs, dim=1)print(train_labels)print(train_predicted)train_accuracy = (train_predicted == train_labels).sum().item() / train_labels.shape[0]# 在测试集上计算准确率test_outputs = lstm(test_features)test_predicted = torch.argmax(test_outputs, dim=1)test_accuracy = (test_predicted == test_labels).sum().item() / test_labels.shape[0]# 打印损失值和准确率print('Epoch [%d/%d], Train Loss: %.4f, Train Accuracy: %.4f, Test Accuracy: %.4f' % (epoch + 1, num_epochs, train_loss, train_accuracy, test_accuracy))# 将训练集的loss和accuracy写入TensorBoardwriter.add_scalar('Train/Loss', train_loss, epoch)writer.add_scalar('Train/Accuracy', train_accuracy, epoch)writer.add_scalar('test/Accuracy', test_accuracy, epoch)def eeg_power_band(epochs):"""该函数根据epochs的特定频段中的相对功率来创建eeg特征"""# 特定频带FREQ_BANDS = {"delta": [0.5, 4.5],"theta": [4.5, 8.5],"alpha": [8.5, 11.5],"sigma": [11.5, 15.5],"beta": [15.5, 30]}spectrum = epochs.compute_psd(method='welch', picks='eeg', fmin=0.5, fmax=30., n_fft=256, n_overlap=16)psds, freqs = spectrum.get_data(return_freqs=True)# 归一化 PSDspsds /= np.sum(psds, axis=-1, keepdims=True)X = []for fmin, fmax in FREQ_BANDS.values():psds_band = psds[:, :, (freqs >= fmin) & (freqs < fmax)].mean(axis=-1)X.append(psds_band.reshape(len(psds), -1))return np.concatenate(X, axis=1)def label_trans(raw_label):# 对值进行二进制编码(小于5.5为0, 大于为1)# 该函数在调整分类类别的时候需要跟随目标类别进行调整binary_arr = np.where(raw_label < 5.5, 0, 1) # 小于 5.5 的值设置为 0,大于等于 5.5 的值设置为 1decimal_arr = binary_arr.dot([8, 4, 2, 1]) # 将二进制数组转化为十进制数组# print(decimal_arr) # 输出生成的数组return decimal_arrdef dataset(file):"""dataset key_word:labels.shape (40, 4)解释:labels用于标记被试看到每一个视频时的状态第二维度中的四个数据代表四个评价标准arousal(唤醒度), valence(愉悦度), dominance(支配度), like(喜爱度)data.shape (40, 40, 8064)解释:第一个维度代表40段观看视频产生的脑电数据第二个维度代表每段数据存在40个通道第三个维度为采样点,长度在63s左右根据官方网站得采样率为128hz"""real_feature = np.empty((0, 160))# print(real_feature.shape)original_data = scio.loadmat(file)# print(original_data.keys())sample_data = original_data['data']sample_labels = original_data['labels']sample_data = sample_data[:, :32, :]k = sample_data.shape[0]# print(sample_labels)# 根据官方文档设置通道channel_names = ['Fp1', 'AF3', 'F7', 'F3', 'FC1', 'FC5', 'T7', 'C3','CP1', 'CP5', 'P7', 'P3', 'Pz', 'PO3', 'O1', 'Oz','O2', 'PO4', 'P4', 'P8', 'CP6', 'CP2', 'C4', 'T8','FC6', 'FC2', 'F4', 'F8', 'AF4', 'Fp2', 'Fz', 'Cz']# 设置采样率sfreq = 128info = mne.create_info(channel_names, sfreq)# 设置所有通道种类为eegchannel_types = {}for i in channel_names:channel_types[i] = 'eeg'# 将观看者看第i个视频的感受提取出来创建rawfor i in range(0, k):slice_data = sample_data[i, :, :]raw = mne.io.RawArray(slice_data, info)raw.set_channel_types(channel_types)"""查看EEG信号图raw.plot(title="The "+str(i)+" raw", bgcolor='pink', color='steelblue', n_channels=10, duration=10)plt.pause(0)"""# 构建事件数组events = np.zeros((num_sample, 3))for i in range(num_sample):events[i][0] = i*sfreq# print(events)events = events.astype(int)# 每一个epoch长度为从事件开始的采样点到 t_max 秒后的采样点,这里就不设置基线了epochs = mne.Epochs(raw=raw, events=events, tmin=0, tmax=t_max, preload=True, baseline=None)# print(epochs)features = eeg_power_band(epochs)# features = features.reshape((160, 10))# print(features.shape)"""features.shape = (60, 160)解读:在此段raw中一共取得60个epoch,每一个epoch都有32个通道,每一个通道中都会提取5个功率谱特征,因此得到32*5=160个数据"""real_feature = np.concatenate((real_feature, features), axis=0)# print(real_feature.shape)"""该文件中所有数据特征读取完之后重整维度,与40个标签数量配合"""# print(real_feature.shape)input_feature = real_feature.reshape((-1, num_sample, 160))y = label_trans(sample_labels) # 重整labels# print(input_feature.shape)# train(input_feature, y)# 返回当前文件下的40个视频中的特征以及labelsreturn input_feature, y# 创建LSTM模型
lstm = my_lstm()
# 定义损失函数和优化器
class_weight = torch.ones(output_types)
class_weight[0] = class_weight[-1] = 0.7 # 为0和15这两个类别分配不一样的权重,其它类别权重为1.0,避免0和15过多的问题
criterion = nn.CrossEntropyLoss(weight=class_weight)
optimizer = torch.optim.Adam(lstm.parameters(), lr=learing_rate)# 添加TensorBoard的配置信息
writer = SummaryWriter('logs')
train()
# 关闭TensorBoard的输出
writer.close()
六、结果分析
在本次实验中取得的准确率并不高,在16分类方面取得的准确率大概在0.2到0.3左右,而在2分类方面正确率基本上也在0.6左右,与预期差距较大。此篇文章拖的时间较久且长时间进展微小,故现在发布,省出时间进行下一个工作。
16分类正确率低的原因大部分在于被试打分的主观性,且每一个情感层级的差别其实并没有想象中的大,最重要的是各个类别之间的占比极不均匀,0和15占比过高,就算为各个类别设置权重仍然达不到较好的效果。另一个原因主要在于对lstm模型的不熟练,不知道如何调整参数、如何增加和设置隐藏层以达到最好的训练效果。
下一个目标主要在于找一篇论文进行简单的复现,体会一下别人解决问题的思路。