当前位置:   article > 正文

代码实践:基于LSTM网络的DEAP情感数据集情感分类_deapdataset

deapdataset

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)代码实践。


五、代码部分

  1. import numpy as np
  2. import matplotlib.pyplot as plt
  3. import mne
  4. import scipy.io as scio
  5. import warnings
  6. warnings.filterwarnings("ignore", category=RuntimeWarning)
  7. import torch
  8. import torch.nn as nn
  9. from torchvision import transforms
  10. import os
  11. from torch.utils.tensorboard import SummaryWriter
  12. from sklearn.model_selection import train_test_split
  13. # 超参数
  14. num_epochs = 500 # 训练轮数
  15. num_sample = 60 # 时间轴上时间点数量,num_sample + t_max < 63(数据持续时间)
  16. learing_rate = 0.0003 # 学习率
  17. t_max = 3 # 每一个mne.epoch的持续长度
  18. output_types = 16 # LSTM输出种类数量
  19. class my_lstm(nn.Module):
  20. def __init__(self):
  21. super(my_lstm, self).__init__()
  22. self.rnn = nn.LSTM( # if use nn.RNN(), it hardly learns
  23. input_size=160, # 输入维度
  24. hidden_size=50, # 隐藏层神经元节点个数
  25. num_layers=2, # 神经元层数
  26. batch_first=True, # input & output will has batch size as 1s dimension. e.g. (batch, time_step, input_size)
  27. bidirectional=True,
  28. )
  29. self.out = nn.Sequential(
  30. nn.Dropout(p=0.5),
  31. nn.Linear(100, 64),
  32. nn.ReLU(),
  33. nn.Linear(64, output_types)
  34. )
  35. def forward(self, x):
  36. # x shape (batch, time_step, input_size)
  37. # r_out shape (batch, time_step, output_size)
  38. # h_n shape (n_layers * n_directions, batch, hidden_size)
  39. # h_c shape (n_layers * n_directions, batch, hidden_size)
  40. r_out, (h_n, h_c) = self.rnn(x, None)
  41. # choose r_out at the last time step
  42. out = self.out(r_out[:, -1, :])
  43. return out
  44. def train():
  45. # 这里初始空值维度设置为第一个文件第一个视频中的维度,便于拼接,之后需要去掉
  46. features = np.empty((40, num_sample, 160))
  47. labels = []
  48. # 要遍历的文件夹路径
  49. folder_path = "data/data_preprocessed_matlab"
  50. # 遍历文件夹下的文件名
  51. file_names = [f for f in os.listdir(folder_path) if os.path.isfile(os.path.join(folder_path, f))]
  52. # print(file_names)
  53. test_file = ['s01.mat', 's02.mat', 's03.mat', 's04.mat', 's05.mat', 's06.mat', 's07.mat', 's09.mat']
  54. for i in file_names:
  55. path = folder_path + "/" + i
  56. input_features, y = dataset(path)
  57. features = np.concatenate([features, input_features], axis=0)
  58. labels = np.append(labels, y)
  59. # print(features.shape)
  60. # print(labels.shape)
  61. # print(labels)
  62. labels = labels.astype(np.int32)
  63. # print(labels)
  64. print(labels.shape)
  65. features = features[40:]
  66. # print(features)
  67. print(features.shape)
  68. # 将特征数组和labels转换成PyTorch tensor
  69. train_features, test_features, train_labels, test_labels = train_test_split(features, labels, test_size=0.2,
  70. random_state=1)
  71. train_features = torch.from_numpy(train_features).float()
  72. train_labels = torch.from_numpy(train_labels).long()
  73. test_features = torch.from_numpy(test_features).float()
  74. test_labels = torch.from_numpy(test_labels).long()
  75. for epoch in range(num_epochs):
  76. # 前向传播
  77. train_outputs = lstm(train_features)
  78. print(train_outputs.shape)
  79. train_loss = criterion(train_outputs, train_labels)
  80. # 反向传播和优化
  81. optimizer.zero_grad()
  82. train_loss.backward()
  83. optimizer.step()
  84. # 在训练集上计算准确率
  85. train_predicted = torch.argmax(train_outputs, dim=1)
  86. print(train_labels)
  87. print(train_predicted)
  88. train_accuracy = (train_predicted == train_labels).sum().item() / train_labels.shape[0]
  89. # 在测试集上计算准确率
  90. test_outputs = lstm(test_features)
  91. test_predicted = torch.argmax(test_outputs, dim=1)
  92. test_accuracy = (test_predicted == test_labels).sum().item() / test_labels.shape[0]
  93. # 打印损失值和准确率
  94. print('Epoch [%d/%d], Train Loss: %.4f, Train Accuracy: %.4f, Test Accuracy: %.4f' % (
  95. epoch + 1, num_epochs, train_loss, train_accuracy, test_accuracy))
  96. # 将训练集的loss和accuracy写入TensorBoard
  97. writer.add_scalar('Train/Loss', train_loss, epoch)
  98. writer.add_scalar('Train/Accuracy', train_accuracy, epoch)
  99. writer.add_scalar('test/Accuracy', test_accuracy, epoch)
  100. def eeg_power_band(epochs):
  101. """
  102. 该函数根据epochs的特定频段中的相对功率来创建eeg特征
  103. """
  104. # 特定频带
  105. FREQ_BANDS = {"delta": [0.5, 4.5],
  106. "theta": [4.5, 8.5],
  107. "alpha": [8.5, 11.5],
  108. "sigma": [11.5, 15.5],
  109. "beta": [15.5, 30]}
  110. spectrum = epochs.compute_psd(method='welch', picks='eeg', fmin=0.5, fmax=30., n_fft=256, n_overlap=16)
  111. psds, freqs = spectrum.get_data(return_freqs=True)
  112. # 归一化 PSDs
  113. psds /= np.sum(psds, axis=-1, keepdims=True)
  114. X = []
  115. for fmin, fmax in FREQ_BANDS.values():
  116. psds_band = psds[:, :, (freqs >= fmin) & (freqs < fmax)].mean(axis=-1)
  117. X.append(psds_band.reshape(len(psds), -1))
  118. return np.concatenate(X, axis=1)
  119. def label_trans(raw_label):
  120. # 对值进行二进制编码(小于5.5为0, 大于为1)
  121. # 该函数在调整分类类别的时候需要跟随目标类别进行调整
  122. binary_arr = np.where(raw_label < 5.5, 0, 1) # 小于 5.5 的值设置为 0,大于等于 5.5 的值设置为 1
  123. decimal_arr = binary_arr.dot([8, 4, 2, 1]) # 将二进制数组转化为十进制数组
  124. # print(decimal_arr) # 输出生成的数组
  125. return decimal_arr
  126. def dataset(file):
  127. """
  128. dataset key_word:
  129. labels.shape (40, 4)
  130. 解释:
  131. labels用于标记被试看到每一个视频时的状态
  132. 第二维度中的四个数据代表四个评价标准arousal(唤醒度), valence(愉悦度), dominance(支配度), like(喜爱度)
  133. data.shape (40, 40, 8064)
  134. 解释:
  135. 第一个维度代表40段观看视频产生的脑电数据
  136. 第二个维度代表每段数据存在40个通道
  137. 第三个维度为采样点,长度在63s左右
  138. 根据官方网站得采样率为128hz
  139. """
  140. real_feature = np.empty((0, 160))
  141. # print(real_feature.shape)
  142. original_data = scio.loadmat(file)
  143. # print(original_data.keys())
  144. sample_data = original_data['data']
  145. sample_labels = original_data['labels']
  146. sample_data = sample_data[:, :32, :]
  147. k = sample_data.shape[0]
  148. # print(sample_labels)
  149. # 根据官方文档设置通道
  150. channel_names = ['Fp1', 'AF3', 'F7', 'F3', 'FC1', 'FC5', 'T7', 'C3',
  151. 'CP1', 'CP5', 'P7', 'P3', 'Pz', 'PO3', 'O1', 'Oz',
  152. 'O2', 'PO4', 'P4', 'P8', 'CP6', 'CP2', 'C4', 'T8',
  153. 'FC6', 'FC2', 'F4', 'F8', 'AF4', 'Fp2', 'Fz', 'Cz']
  154. # 设置采样率
  155. sfreq = 128
  156. info = mne.create_info(channel_names, sfreq)
  157. # 设置所有通道种类为eeg
  158. channel_types = {}
  159. for i in channel_names:
  160. channel_types[i] = 'eeg'
  161. # 将观看者看第i个视频的感受提取出来创建raw
  162. for i in range(0, k):
  163. slice_data = sample_data[i, :, :]
  164. raw = mne.io.RawArray(slice_data, info)
  165. raw.set_channel_types(channel_types)
  166. """
  167. 查看EEG信号图
  168. raw.plot(title="The "+str(i)+" raw", bgcolor='pink', color='steelblue', n_channels=10, duration=10)
  169. plt.pause(0)
  170. """
  171. # 构建事件数组
  172. events = np.zeros((num_sample, 3))
  173. for i in range(num_sample):
  174. events[i][0] = i*sfreq
  175. # print(events)
  176. events = events.astype(int)
  177. # 每一个epoch长度为从事件开始的采样点到 t_max 秒后的采样点,这里就不设置基线了
  178. epochs = mne.Epochs(raw=raw, events=events, tmin=0, tmax=t_max, preload=True, baseline=None)
  179. # print(epochs)
  180. features = eeg_power_band(epochs)
  181. # features = features.reshape((160, 10))
  182. # print(features.shape)
  183. """
  184. features.shape = (60, 160)
  185. 解读:在此段raw中一共取得60个epoch,每一个epoch都有32个通道,每一个通道中都会提取5个功率谱特征,因此得到32*5=160个数据
  186. """
  187. real_feature = np.concatenate((real_feature, features), axis=0)
  188. # print(real_feature.shape)
  189. """
  190. 该文件中所有数据特征读取完之后重整维度,与40个标签数量配合
  191. """
  192. # print(real_feature.shape)
  193. input_feature = real_feature.reshape((-1, num_sample, 160))
  194. y = label_trans(sample_labels) # 重整labels
  195. # print(input_feature.shape)
  196. # train(input_feature, y)
  197. # 返回当前文件下的40个视频中的特征以及labels
  198. return input_feature, y
  199. # 创建LSTM模型
  200. lstm = my_lstm()
  201. # 定义损失函数和优化器
  202. class_weight = torch.ones(output_types)
  203. class_weight[0] = class_weight[-1] = 0.7 # 为0和15这两个类别分配不一样的权重,其它类别权重为1.0,避免0和15过多的问题
  204. criterion = nn.CrossEntropyLoss(weight=class_weight)
  205. optimizer = torch.optim.Adam(lstm.parameters(), lr=learing_rate)
  206. # 添加TensorBoard的配置信息
  207. writer = SummaryWriter('logs')
  208. train()
  209. # 关闭TensorBoard的输出
  210. writer.close()


六、结果分析

        在本次实验中取得的准确率并不高,在16分类方面取得的准确率大概在0.2到0.3左右,而在2分类方面正确率基本上也在0.6左右,与预期差距较大。此篇文章拖的时间较久且长时间进展微小,故现在发布,省出时间进行下一个工作。

        16分类正确率低的原因大部分在于被试打分的主观性,且每一个情感层级的差别其实并没有想象中的大,最重要的是各个类别之间的占比极不均匀,0和15占比过高,就算为各个类别设置权重仍然达不到较好的效果。另一个原因主要在于对lstm模型的不熟练,不知道如何调整参数、如何增加和设置隐藏层以达到最好的训练效果。

        下一个目标主要在于找一篇论文进行简单的复现,体会一下别人解决问题的思路。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/72869
推荐阅读
  

闽ICP备14008679号