⌈ 传知代码 ⌋ CNN实现脑电信号的情感识别

news2024/12/24 0:57:09

💛前情提要💛

本文是传知代码平台中的相关前沿知识与技术的分享~

接下来我们即将进入一个全新的空间,对技术有一个全新的视角~

本文所涉及所有资源均在传知代码平台可获取

以下的内容一定会让你对AI 赋能时代有一个颠覆性的认识哦!!!

以下内容干货满满,跟上步伐吧~


📌导航小助手📌

  • 💡本章重点
  • 🍞一. 概述
  • 🍞二. 演示效果
  • 🍞三.实现
  • 🍞四.网络的训练
  • 🫓总结


💡本章重点

  • CNN实现脑电信号的情感识别

🍞一. 概述

情绪(或情感)识别(或检测)正日益引起来自多学科背景研究者的关注。情感计算,作为Picart提出的一个新兴研究领域,旨在使计算机系统能够准确地处理、识别和理解人类表达的情感信息,从而实现自然的人机交互(HCI),这是情感计算中的前沿科学问题。作为一种复杂的心理状态,反映在生理行为和生理活动中。过去十年里,研究人员一直致力于通过收集各种生理行为和生理活动中的情感信息来识别情感,例如来自麦克风的声音信号、神经生理活动测量设备的数据、摄像头的视频以及网站的文本等。情感检测研究的核心是利用统计机器学习技术(如分类、回归或聚类)实时或在线识别用户的不同情感状态。本文将基于DEAP数据集,利用CNN方法进行情感识别。

在这里插入图片描述

信号是一串时间序列,而由于不可能只通过检测人大脑皮层单一位置的信号来获得大脑活动的全貌,因此一般是多个通道的信号,即多条时间序列。

  • DEAP数据集

    DEAP(Dataset for Emotion Analysis using Physiological Signals)数据集在脑电情感识别领域的文献中被广泛使用,它是一个开放和免费的数据集,其中包含从视听刺激中记录的生物信号以及参与实验的个体对其情感状态的主观评估。该数据集由伦敦玛丽皇后大学、特文特大学、日内瓦大学和EPFL的研究人员所采集并且标定。

在这里插入图片描述


🍞二. 演示效果

在这里插入图片描述

其中红色的波形代表兴奋的情感,蓝色代表平淡的情感,旁边的黑色块内是模型的预测结果,0代表预测对应情绪为平淡,1代表预测对应情绪为兴奋。


🍞三.实现

提取数据

首先,我们从DEAP数据集中得到的数据是关于32个受试者32种不同电影40个不同的信号,因此我们需要对数据集进行预处理。我们下载得到的数据集的格式文件是32个.dat格式的文件,利用python打开这类文件,并观察该数据集的全貌

def getDataFromDeap(filename: str):
    data = pickle._Unpickler(open(filename, 'rb'))
    data.encoding = 'latin1'
    return data.load()


labels = []
deap = []

for i in range(1, 33):
    data = getDataFromDeap( "./DEAP/data_preprocessed_python/raw/s" + (str(i) if i >= 10 else '0' + str(i) ) + '.dat')
    labels.append(data['labels'])
    deap.append(data['data'])
    
labels = np.array(labels)
deap = np.array(deap)

print(labels.shape)
print(deap.shape)

labels = labels.reshape(1280, 4)
deap = deap.reshape(1280, 40, 8064)

print(labels.shape)
print(deap.shape)

通过输出结果我们可以看到对于每一个数据文件(这代表是一个人测试数据)有32个测试结果,每一个测试结果对应着40条不同的生理信号(其中前32为脑电信号通道),每个测试结果还对应着4个不同的情感标签,用来表示对应的情感特征。 8064代表共有63秒采样时间以及128hz的采样频率。

数据预处理

在分类过程中我们需要提取相应的特征,这个过程便是数据的预处理过程。需要对数据进行哪些处理呢?一个在现有的实验中非常有效的特征是信号的微分熵,即我们需要对每个通道的脑电信号进行成分划分并且得到相应成分的微分熵

在这里插入图片描述

也就是通过计算信号(可以理解为一串序列)的方差从而得到

def diffEntropy(signal):
    variance = np.var(signal, ddof = 1)
    return math.log(2 * math.pi * math.e * variance) / 2

信号的成分常采用波频过滤的方式即常见的alpha, beta, gamma, theta四个不同的频段

在这里插入图片描述

其中delat成分由于只与深度睡眠有关故不做考虑。其核心代码为:

def butter_bandpass(lowcut, highcut, fs, order=5):
    nyq = 0.5 * fs
    low = lowcut / nyq
    high = highcut / nyq
    b, a = butter(order, [low, high], btype='band')
    return b, a

def butter_bandpass_filter(data, lowcut, highcut, fs, order=5):
    b, a = butter_bandpass(lowcut, highcut, fs, order=order)
    y = lfilter(b, a, data)
    return y

提取空间特征

现在我们已经得到了对应通道信号的微分熵,然后简单的按照这些数据进行分类是不靠谱的,因为通道代表大脑皮层,而大脑皮层的各个部位存在着一定的空间位置关系,因此我们需要再提取一个特征,可以更好的被CNN进行捕获,一个常用的特征是ISO的脑电标准图,它将脑电极的区域进行划分,这种划分可以被我们认为是这些脑电极对应的脑电信号存在空间关系,其中的字母分布代表不同的通道,这可以在数据集的官方网站中进行查询。

在这里插入图片描述
因此这一步就是把我们得到对应通道的微分熵数据按照上面的位置摆放,得到有4个不同脑电成分的二维数据

在这里插入图片描述核心代码为

class eeged():
...
    def convert2d(self, data, h = 9, w = 9):
        data_2d = np.zeros([h, w])
        data_2d[0] = (0., 0, 0, data[0], 0, data[16], 0, 0, 0)
        data_2d[1] = (0., 0, 0, data[1], 0, data[17], 0, 0, 0)
        data_2d[2] = (data[3], 0., data[2], 0, data[18], 0, data[19], 0, data[20])
        data_2d[3] = (0., data[4], 0, data[5], 0, data[22], 0, data[21], 0)
        data_2d[4] = (data[7], 0., data[6], 0, data[23], 0, data[24], 0, data[25])
        data_2d[5] = (0., data[8], 0, data[9], 0, data[27], 0, data[26], 0)
        data_2d[6] = (data[11], 0., data[10], 0, data[15], 0, data[28], 0, data[29])
        data_2d[7] = (0., 0, 0, data[12], 0, data[30], 0, 0, 0)
        data_2d[8] = (0., 0, 0, data[13], data[14], data[31], 0, 0, 0)
...

标签处理

前面提到,数据集给我们的标签是4个,在实际过程中根据不同的情感模型,我们可以选择不同的标签进行识别工作,在这里我们只选择’variance’标签进行识别,这个标签代表的是人的情感强度,值越大人越兴奋。原始的标签为1-9的实数,在这里我们简单转为二分类任务,这也是现在研究领域中常用的处理

...
    def get_labels(self):

        valence_labels = self.label[:, 0] > 5
        arousal_labels = self.label[:, 1] > 5

        v = []
        a = []

        for i in range(len(valence_labels)):
            for j in range(0, 60):
                v.append(valence_labels[i])
                a.append(arousal_labels[i])
        
        v = np.array(v, dtype=float)
        a = np.array(a, dtype=float)

        # print("labels:", v.shape)
        return a, v
...

卷积神经网络

现在我们已经提取了脑电信号的特征,在这种数据处理的背景下,仅仅需要一个比较简单的CNN网络就能够得到较为显著的情感识别效果,其网络结构为:

class CNN(nn.Module):
    def __init__(self, classes = 2):
        super(CNN, self).__init__()

        # input: batch, 4, 9, 9 output: batch, 24, 5, 5
        self.conv1 = nn.Conv2d(in_channels =  4, out_channels =  24, kernel_size = 5)
        self.relu1 = nn.ReLU()

        # input: batch, 24, 5, 5 output: batch, 128, 4, 4
        self.conv2 = nn.Conv2d(in_channels = 24, out_channels = 128, kernel_size = 2)
        self.relu2 = nn.ReLU()

        # input: batch, 128, 4, 4 output: batch, 128, 2, 2
        self.maxpool = torch.nn.MaxPool2d(kernel_size = (2,2))


        self.fc1 = nn.Linear(in_features = 128 * 2 * 2, out_features = 96)
        self.relu3 = nn.ReLU()

        self.fc2 = nn.Linear(in_features = 96, out_features = classes)

    def forward(self, x):  # input:  9 X 9 after padding
        x = self.conv1(x)
        x = self.relu1(x)

        x = self.conv2(x)
        x = self.relu2(x)

        x = self.maxpool(x)
        x = x.view(-1, np.prod(x.shape[1:]))

        x = self.fc1(x)
        x = self.relu3(x)

        x = self.fc2(x)


        return x

输入的数据为的维度为[batch, 4, 9, 9],后面的9来自于预先处理的二维结构,4来源于我们利用了脑电信号的4个成分。我们的网络结构采用了2层卷积,1层池化和两层全连接层。


🍞四.网络的训练

在训练之前,我们先将数据集分割成训练集和测试集

[x_train, l_train, x_test, l_test] = datasplit.getdata(path = './DEAP/de3d', device = 'cpu', labels = 'valence', splits = [0.6, 0.8], valid = False)
x_test = x_test.to(device)
l_test = l_test.to(device)
l_test = torch.argmax(l_test, dim=1)

其中datasplit.getdata需要做的事情就是将数据集的顺序打乱,然后按照一定的比例进行分割,其核心代码为:

def getdata(path = '../DEAP/de3d', device = 'cpu', labels = 'valence', splits = [0.6, 0.8], valid = False):
    train_data = np.empty([0, 4, 9, 9])
    test_data = np.empty([0, 4, 9, 9])

    train_label = np.empty([0, 2])
    test_label = np.empty([0, 2])

    if(valid):
        valid_data = np.empty([0, 4, 9, 9])
        valid_label = np.empty([0, 2])

    v, t = [int(2400 * i) for i in splits]


    for file in os.listdir(path):
        file = sio.loadmat(path + '/' + file)
        data = file['data']
        random_indices = np.random.permutation(data.shape[0])
        data = data[random_indices, :, :, :]
        
        if(valid):
            train_data = np.concatenate([train_data, data[:v, :, :, :]])
            valid_data = np.concatenate([valid_data, data[v:t, :, :, :]])
            test_data = np.concatenate([test_data, data[t:, :, :, :]])
            
        else:
            train_data = np.concatenate([train_data, data[:t, :, :, :]])
            test_data = np.concatenate([test_data, data[t:, :, :, :]])
            

        label = one_hot(np.int32(file[ labels + '_labels'][0]), 2)
        label = label[random_indices, :]

        
        if(valid):
            train_label = np.concatenate([train_label, label[:v, :]])
            valid_label = np.concatenate([valid_label, label[v:t, :]])
            test_label = np.concatenate([test_label, label[t:, :]])
        else:
            train_label = np.concatenate([train_label, label[:t, :]])
            test_label = np.concatenate([test_label, label[t:, :]])

    if(valid):
        x_train = torch.tensor(train_data, requires_grad=False, dtype=torch.float32, device=device)
        l_train = torch.tensor(train_label, requires_grad=False, dtype=torch.float32, device=device)
        x_valid = torch.tensor(valid_data, requires_grad=False, dtype=torch.float32, device=device)
        l_valid = torch.tensor(valid_label, requires_grad=False, dtype=torch.float32, device=device)
        x_test = torch.tensor(test_data, requires_grad=False, dtype=torch.float32, device=device)
        l_test = torch.tensor(test_label, requires_grad=False, dtype=torch.float32, device=device)

        print( x_train.shape, l_train.shape, x_valid.shape, l_valid.shape, x_test.shape, l_test.shape )
        return x_train, l_train, x_valid, x_valid, x_test, l_test
    else:
        x_train = torch.tensor(train_data, requires_grad=False, dtype=torch.float32, device=device)
        l_train = torch.tensor(train_label, requires_grad=False, dtype=torch.float32, device=device)
        x_test = torch.tensor(test_data, requires_grad=False, dtype=torch.float32, device=device)
        l_test = torch.tensor(test_label, requires_grad=False, dtype=torch.float32, device=device)

        print( x_train.shape, l_train.shape, x_test.shape, l_test.shape )
        return x_train, l_train, x_test, l_test

上面的代码还实现了需要验证集划分的情况。

现在就可以开始训练了,定义好每一步的训练函数:

def training(model, optimizer, x, y):
    criterion = nn.CrossEntropyLoss()
    optimizer.zero_grad()
    y_pred = model(x)
    loss = criterion(y_pred, y)
    loss.backward()
    optimizer.step()

这里采用较为常用的Adam优化器进行训练:

convnn =  model.CNN().to(device)
optimizer = torch.optim.Adam(convnn.parameters())

每一步训练结束我们都输出一次网络在训练集上的结果(只是为了可视化,并不按照这些结果进行调整),并得到最终的训练结果

for i in range(1, epoch + 1):
    print(f"Training {i} epoch....    ", end = '')
    for data in dataloader:
        x, y =  data
        x = x.to(device)
        y = y.to(device)
        training(model = convnn, optimizer = optimizer, x = x, y = y)
    with torch.no_grad():
        l_pre = convnn(x_test)
        l_pre = torch.argmax(l_pre, dim=1)
    print('Finished! Test acc after training:', end = "")
    print((l_test == l_pre).float().mean())

    
with torch.no_grad():
    l_pre = convnn(x_test)
    l_pre = torch.argmax(l_pre, dim=1)
print('Test acc after training:')
print((l_test == l_pre).float().mean())

在这里插入图片描述
在这里插入图片描述


🫓总结

综上,我们基本了解了“一项全新的技术啦” 🍭 ~~

恭喜你的内功又双叒叕得到了提高!!!

感谢你们的阅读😆

后续还会继续更新💓,欢迎持续关注📌哟~

💫如果有错误❌,欢迎指正呀💫

✨如果觉得收获满满,可以点点赞👍支持一下哟~✨

【传知科技 – 了解更多新知识】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1992375.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

怎么压缩ppt文件?4个常用的PPT压缩技巧分享!

在当今数字化的工作和学习环境中,PPT已经成为我们表达观点、展示成果的重要工具。然而,有时PPT文件的体积过大,给我们的分享和存储带来了诸多不便,表现在:无端占用宝贵的磁盘空间,接收下载耗费时间长等。 …

KSV1(KSU1)/KSV5(KSU5)-创建分配(分摊)规则/运行分配

将A成本中心费用分摊给B、C两个成本中心: 将B、C两个成本中心建一个成本中心组KSV1-创建分配规则 选择发送方的成本中心,选择接收方的成本中心 给不同成本中心分配比例。 点击保存 点击概览可以看到该条规则,可以在系统中创建多条规则。 K…

优阅达携手 HubSpot 助力出海企业营销、销售和服务自动化

2024 年 6 月 17 - 18 日,GTC 2024 全球流量大会在深圳福田会展中心圆满举办。作为跨境出海行业产业链最全、资源最丰富、规模最大的专业展会之一,本次大会聚集了近 3 万名从业者、超过 200 家海内外优质开发厂商,品牌方、服务商,…

【海贼王航海日志:前端技术探索】CSS你了解多少?(二)

目录 1 -> 字体属性 1.1 -> 设置字体 1.2 -> 字体大小 1.3 -> 字体粗细 1.4 -> 文字样式 2 -> 文本属性 2.1 -> 文本颜色 2.1.1 -> 认识RGB 2.1.2 -> 设置文本颜色 2.2 -> 文本对齐 2.3 -> 文本装饰 2.4 -> 文本缩进 2.5 -&g…

Go开发后端和Vue3开发前端的前后端分离框架中自己手戳一个OA流程审批、工作流引擎给新时代一个漂亮便捷的工作流引擎

前言 在软件项目开发中,我们都会接触到流程审批的需要业务,我们以往用的最多就是如下图这种流程编辑引擎插件: 以上截图中的流程工具是不是大家常见的呀!感觉很丑拿不出手呀!在当前行业内卷及竞争激烈情况下&#xff…

uniapp免费申请苹果证书教程每次7天可用于测试

准备一个苹果账号没有加入过任何组织的 然后下载appuploader下载链接 登录上去切记勾选上未付苹果688 然后点击苹果证书创建p12证书 创建描述文件 uniapp打包自定义基座 这就打包好了可以愉快地开发了,但每次生成只有7天,设备限制3个&#xff0c…

【C++】STL | priority_queue 堆(优先级队列)详解(使用+底层实现)、仿函数的引入、容器适配器的使用

目录 前言 总代码 堆的简介 仿函数 堆的基础框架建立size、empty、top、 向上调整法 and push 向上调整 push 向下调整法 and pop 向下调整法 pop 迭代器区间初始化(构造) 逻辑讲解 为何选择向下建堆? 建堆代码实现 结语 前言…

区块链的搭建和运维4

区块链的搭建和运维4 (1) 搭建基于MySQL分布式存储的区块链 1.构建单群组网络节点 使用开发部署工具构建单群组网络节点,命令如下: bash build_chain.sh -l 127.0.0.1:4 -p 30300,20200,85452. 启动 MySQL 并设置账户密码 输入如下命令,…

【mysql 第一篇章】系统和数据库的交互方法

一、宏观的查看系统怎么和数据库交互 在我们刚刚接触系统和数据库的时候不明白其中的原理,只知道系统和数据库是需要交互的。所以我们会理解成上图的形式。 二、MYSQL 驱动 随着我们的学习时间的加长以及对程序的了解,发现链接数据库是需要有别的工具辅…

免费【2024】springboot 高校毕业生信息管理系统的设计与实现

博主介绍:✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术范围:SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化…

安卓Termux系统设备安装内网穿透工具实现远程使用SFTP传输文件

文章目录 前言1. 安装openSSH2. 安装cpolar3. 远程SFTP连接配置4. 远程SFTP访问4. 配置固定远程连接地址 前言 本教程主要介绍如何在安卓 Termux 系统中使用 SFTP 文件传输,并结合cpolar内网穿透工具生成公网地址,轻松实现无公网IP环境远程传输&#xf…

用 echarts 开发地图、点击展示自定义信息框

1、下载所需地市的json 链接&#xff1a;DataV.GeoAtlas地理小工具系列 在右侧输入需要的名称&#xff0c;然后下载json文件到本地 2、在html 中准备容器&#xff0c;并设置宽高 <div id"mapContent"> <div ref"mapChart" style"width:10…

全网详解LVS的四种工作模式及案例

目录 LVS&#xff08;Linux virual server&#xff09; 一、集群和分布式的简介 二、LVS的运行原理 1、LVS简介 2、LVS 相关术语 3、LVS的集群类型 三、LVS-NAT工作模式 部署NAT工作模式案例&#xff1a; 1、实验环境 2、实验环境说明 3、配置 四、LVS-DR工作模式 …

Http:八股

1、Https加密方式 1.1Https通过 摘要算法保证数据的完整性&#xff0c; 1、服务器将公钥注册到CA&#xff0c; CA用自己的私钥给 服务器的公钥进行数字签名。 2、客户端拿到服务器证书后&#xff0c;用CA的公钥确认数字证书的真实性。 3、获取服务器的公钥&#xff0c;使用它对…

SpringBoot Actuator

对应用进行观测,监控,预警 健康状况[组件状态,存活状态] health 健康端点:返回存活,死亡. Health对象 运行指标[CPU,内存,垃圾回收,吞吐量,响应成功率] Metrics 指标监控端点:访问次数/率等等 链路追踪等等 引入web和actuator依赖 在…

如何在不丢失数据的情况下绕过IPhone密码?

不幸的是&#xff0c;不可能在不丢失数据的情况下绕过 iPhone 密码。通过密码的唯一方法是使用iTunes或iCloud恢复设备。这将清除您设备的内容&#xff0c;因此请务必在恢复之前备份所有重要数据。如果您忘记了密码&#xff0c;请按照以下步骤操作&#xff1a; 1. 将您的 iPhon…

AI绘画 Stable Diffusion后期处理—无需ControlNet也能轻松高清放大图像与老旧照片修复,SD新手必看教程

大家好&#xff0c;我是设计师阿威 分享了这么多期AI绘画Stable DIffusion的入门教程和一些常用的插件玩法后&#xff0c;不知道大家有没有发现&#xff0c;SD还有一个功能&#xff0c;似乎没怎么用到过&#xff0c;它就是—后期处理。 今天就给大家分享一下SD中的 “后期处理…

VLSI | 计算CMOS反相器的负载电容

ref. 数字集成电路 电路、系统与设计&#xff08;第二版&#xff09;&#xff0c;周润德 译 为了计算方便&#xff0c;本人编写了MATLAB代码进行计算&#xff0c;需要可至&#xff1a;MATLAB计算CMOS反相器等效负载电容 。资源中也给出了PTM的MOS模型参数。对于MOS的模型参数&…

TMGM:日本加息预期被推迟,日元相对稳定

根据最新的日本银行《意见总结》&#xff0c;"实现通胀目标的可能性进一步增加"&#xff0c;预计将进一步上升。 "假设通胀目标将在2025财年下半年实现&#xff0c;央行应在那时将政策利率提高到中性利率水平。由于中性利率水平至少在1%左右&#xff0c;为避免…

injected stylesheet 导致 页面重置按钮文字样式改变,按钮中的文字看不清晰

项目场景&#xff1a; 相关背景&#xff1a; injected stylesheet 导致 页面重置按钮文字样式改变&#xff0c;看不清晰 问题描述 遇到的问题&#xff1a; 检查页面中该按钮的代码如下所示&#xff1a; <div class"el-form-item__content"> <button d…