MSI: 基于多元同步索引的SSVEP频率识别算法

news2025/1/18 9:47:07

MSI: 基于多元同步索引的SSVEP频率识别算法

  • 1.算法背景
  • 2.算法原理
  • 3.Python代码实现

1.算法背景

脑机接口(Brain-Computer Interface, BCI)因其在神经工程与神经科学中的广泛应用价值而备受研究者们的关注。BCI系统可以在人类或动物被试外部设备之间提供一个在线交流通道, 而无需依赖于常规的外围神经与肌肉输出通路。因其较高的时间分辨率、相对低的成本与高便携性, 从头皮采集的脑电图(electroencephalograhy, EEG)是被用于构建BCI系统的最广泛使用的数据模态。诸如皮层慢电位(slow cortical potentials)、感觉运动节律(sensorimotor rhythms)、P300诱发电位(P300 evoked potentials)、稳态视觉诱发电位(steady-state visual evoked potentials, SSVEPs)等脑电信号均可用于构建BCI系统。其中, SSVEPs由于其较高的信息传输速率(information transfer rate, ITR)、较高的信噪比(signal-to-noise, SNR)与仅依赖于较少的训练时间/无需训练等优点而备受研究者的青睐, 并已成为BCI系统重要的研究分支之一

SSVEPs是由受试者注视某个快速重复的闪光或反转刺激后在受试者大脑枕叶区与顶叶区诱发的响应信号。SSVEPs实际上是近乎正弦的信号, 其信号成分中包含一系列离散的频率成分, 包括诱发刺激的基频与谐波成分。在基于SSVEPs的BCI系统中, 目标由单个刺激频率或不同刺激频率之间的组合来进行编码, 不同的目标编码则可以代表不同的控制命令。BCI系统则可以通过识别不同的目标编码以帮助BCI用户控制外围设备, 常见的外围设备有仿生机械腿、无人机、拨号面板、与虚拟键盘。

然而, 不同的用户的SSVEPs在振幅、相位以及数据分布上具有很大的差异。这个问题直接导致许多SSVEP频率识别算法依赖于很多参数调优步骤(如通道挑选或选择合适的数据长度), 以使得算法对于每个被试可发挥出尽可能高的效用。但是, 这些算法的优化需求将限制SSVEP-BCI系统的实际使用。因此, 一个无需依赖于调优步骤或仅依赖于少量调优过程的SSVEP频率识别算法亟待提出, 以使得BCI系统的使用变得便捷高效。

在这里插入图片描述

2.算法原理

从头皮采集的EEG信号可以被视作为大脑电生理活动反映在皮质层水平上的各电极的线性混合信号。多元同步索引(Multivariate Synchronization Index, MSI)算法旨在估计实际混合信号和参考信号之间的同步指数,以作为识别刺激频率的潜在指标。 具体而言, MSI算法使用S-estimator作为估计指数。

S-estimator基于多元信号相关矩阵的归一化特征值熵值。因为特征谱的方差与数据维度相关, 故数据维度是一个非常好的同步估计量。测量的同步量反比于数据嵌入维度(embedding dimensionality)。若两个时间序列数据完全无关, 数据嵌入维度将会最大化; 反之, 若两者之间高度相关(即完美同步), 则数据嵌入维度将会最小化。

该机理背后的逻辑在于: 数据嵌入维度越小, 多元信号相关矩阵的原始特征谱越稀疏, 则归一化后的特征值的熵值越大。反之, 数据嵌入维度越大, 多元信号相关矩阵的原始特征谱越稠密, 则归一化后的特征值熵值越小

对于SSVEP频率识别任务而言, MSI算法中的多元信号对应于待测的SSVEP信号 X ∈ R N × M X \in \mathbb{R}^{N \times M} XRN×M与人工生成的正余弦参考信号 Y ∈ R 2 N h × M Y \in \mathbb{R}^{2N_h \times M} YR2Nh×M。其中, N N N 表示通道数量, M M M 代表时间点个数, 而 N h N_h Nh 为谐波数量, 正余弦参考信号 Y Y Y生成方式与CCA算法相同。MSI算法则通过计算 X X X Y Y Y 之间的S-estimator指数以估计多元同步量, 并以此识别目标刺激的SSVEP频率。

为了计算 X X X Y Y Y 之间的S-estimator指数, MSI算法首先要求 X X X Y Y Y 归一化为均值0, 方差1的标准化矩阵。之后, 相关矩阵可被计算为:

C = [ C 11 C 12 C 21 C 22 ] (1) C=\begin{bmatrix} C11 & C12 \\ C21 & C22 \\ \end{bmatrix} \tag{1} C=[C11C21C12C22](1)
其中,

C 11 = 1 M X X T (2) C11=\frac{1}{M}XX^T \tag{2} C11=M1XXT(2)

C 22 = 1 M Y Y T (3) C22=\frac{1}{M}YY^T \tag{3} C22=M1YYT(3)

C 12 = 1 M X Y T (4) C12=\frac{1}{M}XY^T \tag{4} C12=M1XYT(4)

C 21 = ( C 12 ) T = 1 M Y X T (5) C21=(C12)^T=\frac{1}{M}YX^T \tag{5} C21=(C12)T=M1YXT(5)

在公式 (1) 中, 相关矩阵 C C C 包含 X X X 的自相关, Y Y Y 的自相关以及 X X X Y Y Y 的跨相关信息。由于自相关会影响同步的测量过程, 故如下的线性转换将在算法的实际实施过程中被采纳:

U = [ C 1 1 ( − 1 / 2 ) 0 0 C 2 2 ( − 1 / 2 ) ] (6) U=\begin{bmatrix} C11^{(-1/2)} & 0 \\ 0 & C22^{(-1/2)} \\ \end{bmatrix} \tag{6} U=[C11(1/2)00C22(1/2)](6)

由此, 经线性转换后的相关矩阵 R R R 将被表示为:

R = U C U T = [ I N × N C 1 1 ( − 1 / 2 ) C 12 C 2 2 ( − 1 / 2 ) C 2 2 ( − 1 / 2 ) C 21 C 1 1 ( − 1 / 2 ) I 2 N h × 2 N h ] (7) R=UCU^T=\begin{bmatrix} I_{N \times N }& C11^{(-1/2)}C12C22^{(-1/2)} \\ C22^{(-1/2)}C21C11^{(-1/2)} & I_{2N_h \times 2N_h}\\ \end{bmatrix} \tag{7} R=UCUT=[IN×NC22(1/2)C21C11(1/2)C11(1/2)C12C22(1/2)I2Nh×2Nh](7)

其中, I N × N I_{N \times N} IN×N I 2 N h × 2 N h I_{2N_h \times 2N_h} I2Nh×2Nh分别表示维度为 N N N 2 N h 2N_h 2Nh的单位矩阵。在经过公式 (6) 中的线性转换后, 公式 (7) 中的自相关信息将被抵消。

λ 1 , λ 2 , . . . , λ P \lambda_1, \lambda_2, ..., \lambda_P λ1,λ2,...,λP 为相关矩阵 R R R P P P 个特征值, 那么归一化后的特征值将被计算为如下结果:

λ i ′ = λ i ∑ i = 1 P λ i = λ i t r ( R ) (8) \lambda_i^{'}=\frac{\lambda_i}{\sum_{i=1}^{P}\lambda_i}=\frac{\lambda_i}{tr(R)} \tag{8} λi=i=1Pλiλi=tr(R)λi(8)

其中, P = N + 2 N h P=N+2N_h P=N+2Nh。最终, X X X Y Y Y 之间的同步索引由以下公式计算得到:

S = 1 + ∑ i = 1 P λ i ′ l o g 2 ( λ i ′ ) l o g 2 ( P ) (9) S=1+\frac{\sum_{i=1}^{P}\lambda_i^{'}log_2(\lambda_i^{'})}{log_2(P)} \tag{9} S=1+log2(P)i=1Pλilog2(λi)(9)

X X X Y Y Y 完全无关, 则跨相关 C 12 = C 21 = 0 C12 = C21=0 C12=C21=0, 相关矩阵 R R R 将呈对角化, λ i ′ = 1 / P , S = 0 \lambda_i^{'}=1 / P, S=0 λi=1/P,S=0。相反, 若 X X X Y Y Y 完美同步, 则仅有一个归一化的特征值为1, 其余特征值为全0, 此时 S = 1 S=1 S=1。对于其它情况, S S S 将介于0与1之间。

在实际的SSVEP频率识别任务中, 若刺激目标数量为 K K K, 即 f 1 , f 2 , . . . , f K f_1, f_2, ..., f_K f1,f2,...,fK, 那么正余弦模板信号为 Y = { Y 1 , Y 2 , . . . , Y K } ∈ R K × N × M Y=\{Y_1, Y_2, ..., Y_K\}\in\mathbb{R}^{K \times N \times M} Y={Y1,Y2,...,YK}RK×N×M。 由MSI算法识别的刺激目标频率为:

f t a r g e t = argmax ⁡ f i S i ( X , Y i ) ,    i = 1 , 2 , . . . , K (10) f_{target}=\underset {f_i} {\operatorname {argmax}} S_i(X, Y_i), \ \ i=1, 2, ..., K\tag{10} ftarget=fiargmaxSi(X,Yi),  i=1,2,...,K(10)

3.Python代码实现

 import numpy as np

class MSI_Base():
    def __init__(self, opt):
        super(MSI_Base, self).__init__()
        # number of trials
        self.Nh = opt.Nh
        # sample frequency
        self.Fs = opt.Fs
        # number of stimulus targets
        self.Nf = opt.Nf
        # window size
        self.ws = opt.ws
        # number of channels
        self.Nc = opt.Nc
        # number of time points
        self.T = int(self.Fs * self.ws)

    def get_Reference_Signal(self, num_harmonics, targets):
        reference_signals = []
        t = np.arange(0, (self.T / self.Fs), step=1.0 / self.Fs)
        for f in targets:
            reference_f = []
            for h in range(1, num_harmonics + 1):
                reference_f.append(np.sin(2 * np.pi * h * f * t)[0:self.T])
                reference_f.append(np.cos(2 * np.pi * h * f * t)[0:self.T])
            reference_signals.append(reference_f)
        reference_signals = np.asarray(reference_signals)
        return reference_signals

    def find_Synchronization_Index(self, X, Y):
        num_freq = Y.shape[0]
        num_harm = Y.shape[1]
        result = np.zeros(num_freq)
        for freq_idx in range(0, num_freq):
            y = Y[freq_idx]
            '''normalized X and Y to have a zero mean and unitary variance'''
            X = X[:] - np.mean(X).repeat(self.T * self.Nc).reshape(self.Nc, self.T)
            X = X[:] / np.std(X).repeat(self.T * self.Nc).reshape(self.Nc, self.T)

            y = y[:] - np.mean(y).repeat(self.T * num_harm).reshape(num_harm, self.T)
            y = y[:] / np.std(y).repeat(self.T * num_harm).reshape(num_harm, self.T)
			
			'''equation (2)-(5), auto-correlation and cross-correlation'''
            c11 = (1 / self.T) * (np.dot(X, X.T))
            c22 = (1 / self.T) * (np.dot(y, y.T))
            c12 = (1 / self.T) * (np.dot(X, y.T))
            c21 = c12.T
			
			'''equation (1), correlation matrix C'''
            C_up = np.column_stack([c11, c12])
            C_down = np.column_stack([c21, c22])
            C = np.row_stack([C_up, C_down])

            # print("c11:", c11)
            # print("c22:", c22)

            v1, Q1 = np.linalg.eig(c11)
            v2, Q2 = np.linalg.eig(c22)
            V1 = np.diag(v1 ** (-0.5))
            V2 = np.diag(v2 ** (-0.5))
            
            '''equation (6), linear transformation'''
            C11 = np.dot(np.dot(Q1, V1.T), np.linalg.inv(Q1))
            C22 = np.dot(np.dot(Q2, V2.T), np.linalg.inv(Q2))

            # print("Q1 * Q1^(-1):", np.dot(Q1, np.linalg.inv(Q1)))
            # print("Q2 * Q2^(-1):", np.dot(Q2, np.linalg.inv(Q2)))
			
            U_up = np.column_stack([C11, np.zeros((self.Nc, num_harm))])
            U_down = np.column_stack([np.zeros((y.shape[0], self.Nc)), C22])
            U = np.row_stack([U_up, U_down])
            
            '''equation (7), correlation matrix R'''
            R = np.dot(np.dot(U, C), U.T)
			
			'''equation (8), normalized eigevalues λ'''
            eig_val, _ = np.linalg.eig(R)
            # print("eig_val:", eig_val, eig_val.shape)
            E = eig_val / np.sum(eig_val)
			
			'''equation (9), synchronization index S'''
            S = 1 + np.sum(E * np.log(E)) / np.log(self.Nc + num_harm)
            result[freq_idx] = S

        return result

    def msi_classify(self, targets, test_data, num_harmonics=3):

        reference_signals = self.get_Reference_Signal(num_harmonics, targets)

        print("segmented_data.shape:", test_data.shape)
        print("reference_signals.shape:", reference_signals.shape)

        predicted_class = []
        labels = []
        num_segments = test_data.shape[0]
        num_perCls = num_segments // reference_signals.shape[0]

        for segment in range(0, num_segments):
            labels.append(segment // num_perCls)
            result = self.find_Synchronization_Index(test_data[segment], reference_signals)
            predicted_class.append(np.argmax(result) + 1)

        labels = np.array(labels) + 1
        predicted_class = np.array(predicted_class)
        return labels, predicted_class

完整实现代码: https://github.com/YuDongPan/Canonical_Classifier

参考资料与文献:

  1. 应用于SSVEP脑电信号识别的CCA算法——CSDN博客
  2. Zhang Y, Xu P, Cheng K, et al. Multivariate synchronization index for frequency recognition of SSVEP-based brain–computer interface[J]. Journal of neuroscience methods, 2014, 221: 32-40

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

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

相关文章

“深元AI”赋能传统加油站智能化转型,全力打造新一代智慧加油站

历届的全国两会和党代会上,“安全生产”始终是核心议题。党的二十大报告提出:推动公共安全治理模式向事前预防转型,并强调要加强重点行业、重点领域安全监管,提高防灾减灾救灾和重大突发公共事件处置保障能力。同时,国…

Linux_vim编辑器

Vi编辑器是所有Unix及Linux系统下标准的编辑器,类似于windows系统下的notepad(记事本)编辑器,由于在Unix及Linux系统的任何版本,Vi编辑器是完全相同的,因此可以在其他任何介绍vi的地方都能进一步了解它&…

Java的CPU 飙升700%优化的真实案例

最近负责的一个项目上线,运行一段时间后发现对应的进程竟然占用了700%的CPU,导致公司的物理服务器都不堪重负,频繁宕机。 那么,针对这类java进程CPU飙升的问题,我们一般要怎么去定位解决呢? 采用top命令定位进程 登…

spring初始项目创建

首先进入http://spring.p2hp.com/projects/spring-framework.html,点击git按钮 点击Access to Binaries中的链接 找到里程碑版本,要引入仓库地址 这里的spring-context依赖只是基础的spring框架的依赖 在resources目录下创建spring的xml文件&#xff0c…

中国31个主要城市绿地数据(空间分辨率为1m)

近年来,为了满足生态文明和可持续发展的理念,科学的城市绿地规划和管理在中国越来越受到重视。因此,提高UGS分类体系和布局布局的合理性,建设绿色宜居城市,是近年来政府和学者关注的重点。为此,本文选取中国…

ArcGIS、ENVI、InVEST、FRAGSTATS等多技术融合提升环境、生态、水文、土地、土壤、农业、大气等领域

专题一、空间数据获取与制图 1.1 软件安装与应用讲解 1.2 空间数据介绍 1.3海量空间数据下载 1.4 ArcGIS软件快速入门 1.5 Geodatabase地理数据库 专题二、ArcGIS专题地图制作 2.1专题地图制作规范 2.2 空间数据的准备与处理 2.3 空间数据可视化:地图符号与…

Terraform 系列-Terraform Cloud 比 Terraform OSS 有哪些增强?

系列文章 👉 Terraform 系列文章 前言 最近在使用 Terraform Cloud 来置备 OCI 的 Always Free Tier, 发现它非常好用,相比 Terraform OSS, 用起来省心多了。 也借此总结学习下:Terraform Cloud 比 Terraform OSS 有哪些增强,…

【从零开始学Skynet】实战篇《球球大作战》(一):功能设计

为了能把之前在基础篇中学习到的Skynet的各种知识结合起来,所以在实战篇中,我们准备开发一个完整的游戏案例《球球大作战》,介绍分布式游戏服务端的实现方法。 1、功能需求 《球球大作战》是一款多人对战游戏,下图是它的战斗场景…

C语言库函数(memcpy,memmove)的模拟实现

模拟实现memcpy函数 下面是memcpy的函数声明 void *memcpy(void *str1, const void *str2, size_t n) 参数 str1 -- 指向用于存储复制内容的目标数组,类型强制转换为 void* 指针。str2 -- 指向要复制的数据源,类型强制转换为 void* 指针。n -- 要被复…

stm32当中的EXTI外部中断系统

一. 中断系统 中断 : 在主程序运行过程中,出现特定的中断触发条件,使得CPU暂停当前正在运行的程序,而去处理中断程序,完成后,又返回原来被暂停的位置继续工作 中断优先 : 当有多个中断开始时&…

SSR初体验-结合Vue3全家桶

SSR初体验 基础搭建 安装依赖 先开启一个服务器 let express require("express");let server express();server.get("/", (req, res) > {res.send(Hello Node Server); });server.listen(3000, () > {console.log("start node server on …

vue3引入Element plus的详细步骤

目录 一、遇到问题 二、操作步骤 一、遇到问题 在用vue3去引用Element UI的时候,发现了白屏不能显示,一直检查是不是代码的问题。后面找到了问题的所在,原来是vue3对应兼容的是Element Plus,要去下载对应的Element plus版本来引…

为什么提升客户服务是长期成功的关键

当今互联网,服务越来约趋向个人化,但在这个在线互动的时代,当涉及到客户支持时,这种个人联系的感觉可能很难形成。当事情出错时,当客户需要支持时,个人联系的感觉最为强烈。在不远的过去,客户支…

网络安全如何入门?有哪些学习误区?

那年我高三毕业的时候要填志愿前几天 我妈问我想学什么专业。 我说,想学网络设计、或者计算机、网络安全工程师 那时候还比较年轻,也对网络,计算机这方面感兴趣嘛 于是我妈和我爸决定让我学网管。 我说不想做网管,想直接成为一…

给想涨薪和正在学习Android的朋友们一些建议

前言 相信很多从事Android开发工作的朋友,在入职一年后会有申请涨薪的想法,但由于某些原因,公司拒绝了您的加薪申请,在我看来,出现这种情况主要有两种原因:第一个原因可能是你在工作中就只知道埋头苦干&am…

81-82-83-84-85-86 - 文件系统设计与实现

---- 整理自狄泰软件唐佐林老师课程 查看所有文章链接:(更新中)深入浅出操作系统 - 目录 文章目录1. 问题1.1 硬盘上最最最简单的文件系统支持方式1.2 改进思路1.3 更多细节问题1.4 文件系统概要设计1.5 硬盘数据逻辑示意图1.6 硬盘数据物理组…

文心一言 VS chatgpt (8)-- 算法导论2.3 5~6题

五、回顾查找问题(参见练习 2.1-3),注意到,如果序列 A 已排好序,就可以将该序列的中点与v进行比较。根据比较的结果,原序列中有一半就可以不用再做进一步的考虑了。二分查找算法重复这个过程,每次都将序列剩余部分的规…

数据结构之七大排序

数据结构之七大排序🔆排序的概念及其运用排序的概念常见的排序算法🔆插入排序直接插入排序希尔排序🔆选择排序直接选择排序堆排序🔆交换排序冒泡排序快排🔆归并排序🔆非比较排序🔆结语&#x1f…

深度探索list

1.list的基本组成 list是一个双向链表,它的基本组成就是 成员作用prev指针指向上一个元素next指针指向下一个元素data用来保存数据2.list的迭代器 由于人们一般习惯于:迭代器是找到下一个元素,迭代器–是找到上一个元素。在双向链表list中…

C++的命名空间

C和C语言是有一些相似的地方的,而且C就是C语言的改进版本,所以学习C也得学习C语言,但是他们又是有很多不同的地方 下面我们就看一下C的命名空间 我们首先看一下 如果是这一段代码,那么这里输出的是多少呢? 很好这里输…