当前位置:   article > 正文

使用脉冲神经网络(SNN)实现DEAP脑电情绪识别(完整代码分享)

snn

目录

一、SNN基础知识讲解

Spiking Neural Network(脉冲神经网络,SNN)简介

SNN和传统ANN的区别

 ANN转SNN的理论基础

SNN的实现——spikingjelly

 二、DEAP数据集介绍

​DEAP数据集组成(Python预处理版128Hz采样)

​分类依据——基于valence 和 arousal的值将情绪分为四类

​DEAP中关于40个通道的解释

本文使用的数据集预处理介绍

​ 三、使用SNN搭建一维Resnet网络进行情绪分类

SNN_Resnet代码实现

​重点代码解读——IF神经元

尾言


本文概述:SNN全称为脉冲神经网络是第三代神经网络,目前对SNN的研究相对来说还是比较少的,因此可以作为一个论文创新点。本文展示了如何使用SNN搭建一个ResNet网络去对DEAP数据集进行情绪四分类。

作者介绍:作者本人是一名人工智能炼丹师,目前在实验室主要研究的方向为生成式模型,对其它方向也略有了解,希望能够在CSDN这个平台上与同样爱好人工智能的小伙伴交流分享,一起进步。谢谢大家鸭~~~

 如果你觉得这篇文章对您有帮助,麻烦点赞、收藏或者评论一下,这是对作者工作的肯定和鼓励。  

一、SNN基础知识讲解

       Spiking Neural Network(脉冲神经网络,SNN)简介

                第一代神经网络(感知器),第二代神经网络(ANN)它们都是基于神经脉冲的发放频率进行编码,但是神经元的脉冲发放频率并不能完全捕获脉冲序列种包含的信息,因此第三代神经网络(SNN)登场了。第三代神经网络具有更强的生物可解释性的,神经网络内部的信息传递是由脉冲序列完成的。

       SNN和传统ANN的区别

                在传统的前馈人工神经网络中,两个神经元之间仅有一个突触连接,即输出只有一个权值去决定,这些权值受到的是神经脉冲的发放频率的影响。

                在SNN中的前馈型脉冲神经网络种,两个神经元之间是由多个突触连接的方式,每个突触具有不同的延时和权值,因此可以使得突触前神经元输入的脉冲能影响一段时间范围内突触后神经元发放的脉冲
 

       ANN转SNN的理论基础

                SNN相比于ANN,产生的脉冲是离散的,这有利于高效的通信,但是SNN直接训练需要比较多的资源,且代码实现也比较复杂,因此我们自然会想到使用现在非常成熟的ANN转换到SNN。

                现在SNN主流的方式是采用频率编码,因此对于输出层,我们会用神经元输出脉冲数来判断类别。发放率和ANN有没有关系呢?答案是肯定的,ANN中的ReLU神经元非线性激活和SNN中IF神经元(采用减去阈值 V_threshold 方式重置)的发放率有着极强的相关性,我们可以借助这个特性来进行转换

190d764162ee469b8c8448c31bc8a8ff.png

                如上图(左图为IF脉冲神经元,右图为ReLU激活函数)所示,在[0,1]范围内,IF脉冲神经元和ReLU激活函数的曲线是一样的,因此在这个范围可以用IF脉冲神经元替换掉ReLU激活函数从而将ANN转变为SNN。

                但是需要注意的是,脉冲频率不可能高于1,因此IF神经元无法拟合ANN中ReLU的输入大于1的情况。那对于输入不在这个范围的数据有两种解决办法,一种是先对数据进行归一化到(0,1)范围,然后在经过一个泊松编码器转化为0,1脉冲。另一种是直接将第一次出现IF脉冲神经元之前的网络层视作一个能将数据编码到[0,1]的编码层

       SNN的实现——spikingjelly

                spikingjelly 是一个基于 PyTorch ,使用脉冲神经网络(Spiking Neural Network, SNN)进行深度学习的框架。

                中文文档连接:SpikingJelly(惊蛰)

                强烈推荐使用这个库,能省去很多麻烦

 二、DEAP数据集介绍

       ​DEAP数据集组成(Python预处理版128Hz采样)

                DEAP数据集包含32个.mat文件,一部分字典键为data的数据,内含每名被实验者的脑电实验数据,数据采样频率为128Hz,另一部分为字典键为labels的数据,形状为40*4的矩阵,四列分别代表效价、唤醒度、支配和喜欢的值,其值由被试者在观看40段视频后进行打分得到。

       ​分类依据——基于valence 和 arousal的值将情绪分为四类

                

1ab96fe69cbc4b089c5537b40c95991f.png

       ​DEAP中关于40个通道的解释

                DEAP数据集中有32个脑电通道,另外8个通道是其它传感器采集的信号,在32个EEG通道/电极中,14个通道用于收集情绪数据,它们被称为情绪通道。这些情绪通道列表,分别是:AF3、F3、F7、FC5、T7、P7、O1、AF4、F4、F8、FC6、T8、P8、O2。

                分别对应索引为:1,2,3,4,7,11,13,17,19,20,21,25,29,31
 

       ​本文使用的数据集预处理介绍

                原始读取到的字典键为data的数据是为40*40*8064的矩阵,第一个40是40次实验,第二个40是40个通道,8064是(63秒是采集时间*128Hz的采样频率)。然后我们预处理后的数据是40*40*14*15*512,即输入的形状为(268800,512)

                原始读取到的字典键为labels的数据,形状为40*4的矩阵,第一个40为40次实验,4代表效价、唤醒度、支配和喜欢的值,然后我们预处理后标签的形状为(268800,4)

  1. def data_preprocessed():
  2. def label_mapping(valence, arousal):
  3. # 根据valence 和arousal的值将数据划分为四类
  4. if (valence > 4.5 and arousal > 4.5):
  5. label = 0
  6. elif (valence > 4.5 and arousal <= 4.5):
  7. label = 1
  8. elif (valence <= 5 and arousal > 4.5):
  9. label = 2
  10. elif (valence <= 4.5 and arousal <= 4.5):
  11. label = 3
  12. return label
  13. def map_label(label):
  14. new_label = label_mapping(label[0], label[1])
  15. return new_label
  16. channel = [1, 2, 3, 4, 7, 11, 13, 17, 19, 20, 21, 25, 29, 31] # 要读取那个通道的数据进行预测,这里只取了14个通道进行预测
  17. window_size = 512 #滑动窗口长
  18. step_size = 512 #滑动窗口步长
  19. # 将path替换为自己的路径
  20. def epoching(sub, channel, window_size, step_size):
  21. signal = []
  22. # 这个路径是DEAPpython预处理数据的文件夹路径,
  23. with open("path" + sub + '.dat', 'rb') as file:
  24. subject = pickle.load(file, encoding='latin1') # resolve the python 2 data problem by encoding : latin1
  25. for i in range(0, 40):
  26. # loop over 0-39 trials
  27. data = subject["data"][i]
  28. labels = subject["labels"][i]
  29. labels = np.array(map_label(labels))
  30. start = 0;
  31. while start + window_size <= data.shape[1]:
  32. array = []
  33. for j in channel:
  34. X = data[j][start: start + window_size]
  35. array.append(np.array(X))
  36. array.append(np.array(labels))
  37. signal.append(np.array(array))
  38. start = start + step_size
  39. signal = np.array(signal)
  40. np.save('path' + sub, signal, allow_pickle=True, fix_imports=True)
  41. for subjects in subject_list:
  42. epoching(subjects, channel, window_size, step_size)
  43. print(subjects)
  44. data = []
  45. label = []
  46. for subjects in subject_list:
  47. with open('path' + subjects + '.npy', 'rb') as file:
  48. sub = np.load(file, allow_pickle=True)
  49. for i in range(0, sub.shape[0]):
  50. data.append(sub[i][0])
  51. label.append(sub[i][1])

​ 三、使用SNN搭建一维Resnet网络进行情绪分类

       ​​SNN_Resnet代码实现

                

  1. class Bottlrneck(torch.nn.Module):
  2. def __init__(self,In_channel,Med_channel,Out_channel,downsample=False):
  3. super(Bottlrneck, self).__init__()
  4. self.stride = 1
  5. if downsample == True:
  6. self.stride = 2
  7. self.layer = torch.nn.Sequential(
  8. torch.nn.Conv1d(In_channel, Med_channel, 1, self.stride,bias=False),
  9. torch.nn.BatchNorm1d(Med_channel),
  10. neuron.IFNode(surrogate_function=surrogate.ATan()),
  11. torch.nn.Conv1d(Med_channel, Med_channel, 3, padding=1,bias=False),
  12. torch.nn.Dropout(0.5),
  13. torch.nn.BatchNorm1d(Med_channel),
  14. neuron.IFNode(surrogate_function=surrogate.ATan()),
  15. torch.nn.Dropout(0.5),
  16. torch.nn.Conv1d(Med_channel, Out_channel, 1,bias=False),
  17. torch.nn.BatchNorm1d(Out_channel),
  18. neuron.IFNode(surrogate_function=surrogate.ATan()),
  19. )
  20. if In_channel != Out_channel:
  21. self.res_layer = torch.nn.Conv1d(In_channel, Out_channel,1,self.stride)
  22. else:
  23. self.res_layer = None
  24. def forward(self,x):
  25. if self.res_layer is not None:
  26. residual = self.res_layer(x)
  27. else:
  28. residual = x
  29. return self.layer(x)+residual
  30. class SNN_ResNet(torch.nn.Module):
  31. def __init__(self,in_channels=1):
  32. super(SNN_ResNet, self).__init__()
  33. self.conv = torch.nn.Sequential(
  34. torch.nn.Conv1d(in_channels, 16, kernel_size=7, stride=2, padding=3, bias=False),
  35. torch.nn.MaxPool1d(3, 2, 1),
  36. )
  37. self.features = torch.nn.Sequential(
  38. torch.nn.Conv1d(in_channels, 16, kernel_size=7, stride=2, padding=3, bias=False),
  39. torch.nn.MaxPool1d(3, 2, 1),
  40. Bottlrneck(16, 64, 256, False),
  41. Bottlrneck(256, 128, 512, True),
  42. Bottlrneck(512, 256, 1024, True),
  43. Bottlrneck(1024, 512, 2048, True),
  44. torch.nn.AdaptiveAvgPool1d(1)
  45. )
  46. self.classifer = torch.nn.Sequential(
  47. torch.nn.Linear(2048,4,bias=False),
  48. )
  49. def forward(self,x):
  50. x=x.permute(0,2,1)
  51. out=0
  52. for i in range(50):
  53. k = self.features(x)
  54. k = k.view(-1,2048)
  55. out+= self.classifer(k)
  56. out=out/50.0
  57. return out

       ​重点代码解读——IF神经元

                ca4d1511437d47a3abb1dcb609367675.png

                上文提到,只要将ANN中的激活函数换成脉冲神经元,那么就可以将ANN转化为SNN,因此,这是将ANN转化为SNN最关键的代码。

                作用:脉冲神经元接受脉冲输入,输入脉冲输出

                IF脉冲神经元和LIF脉冲神经元的区别在于,在T次重复刺激中,LIF每次膜电位会先衰减在充电。而IF脉冲神经元不会衰减。

                脉冲神经元是有记忆的,脉冲神经元在接受脉冲输入后便会进行充电,当膜电位不超过阈值电压v_threshold 时,其膜电位会一直保持,并输出0,当持续充电后膜电位超过阈值电压,便会放电输出1,然后该脉冲神经元便会重置回V_reset

                一层IFNode里面所有脉冲神经元放电便形成脉冲,但是单次输入刺激获得的脉冲输出是不能作为分类依据的,因为神经元要持续刺激才会产生变化,所以我们要将同一个x 输入进网络 循环T个仿真步长 目的就是对输出层所有神经元的输出脉冲进行累加,得到输出层脉冲释放次数 使用脉冲释放次数除以仿真时长T,得到输出层脉冲发放频率,然后以这个频率进行分类,如代码所示   

  1. out=0
  2. for i in range(50):
  3. k = self.features(x)
  4. k = k.view(-1,2048)
  5. out+= self.classifer(k)
  6. out=out/50.0

尾言

 如果您觉得这篇文章对您有帮忙,请点赞、收藏。您的点赞是对作者工作的肯定和鼓励,这对作者来说真的非常重要。如果您对文章内容有任何疑惑和建议,欢迎在评论区里面进行评论,我将第一时间进行回复。 

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

闽ICP备14008679号