MATLAB环境下EEG与RNN表征相似性分析(RSA)完整实践指南
简介这套MATLAB代码围绕“人类EEG与递归神经网络在语音识别中呈现共同时间动态”这一研究主题完整实现了论文分析流程适用于认知神经科学、语音识别与计算建模方向的研究人员也适合具备一定MATLAB基础的学习者复现与二次开发。压缩包体量精简共51个文件含50个.m格式脚本和1个.md格式说明文档总大小仅77KB代码按频域表征、刺激包络与网络输出/EEG相关性分析、表征相似性分析RSA、统计检验等模块清晰组织可依序运行。当前已有161人学习/下载。除核心脚本外资源还附带电极数据映射、模型与EEG的RDM比较、随机/训练网络对比等辅助工具并给出路径配置与运行顺序说明帮助读者理解语音加工中神经信号与人工网络的对齐关系同时脚本命名规范、功能边界清楚迁移到自采EEG数据或更换网络模型时也容易定位和修改。 做EEG研究的人大概都有过这种尴尬模型训练得很好准确率也很高但审稿人一句“这只是堆出来的结果怎么证明模型学到了和人脑一致的表征”就能把人问住。这个项目rsamatlab代码-EEGAndRNNAnalysis就是为了回答这种问题而生的。它不是单纯的EEG分类流程也不是单纯训练一个RNN而是把两者用RSARepresentational Similarity Analysis表征相似性分析串起来在MATLAB环境下完成从脑电预处理、RNN建模到人脑和模型表征比对的完整链路。写这篇文章的时候我假设你已经用过MATLAB也会一点EEG信号处理但对RSA和RNN内部表征可能还停留在“听说过”的阶段。我会从一个实际跑项目的角度把这个代码库的核心模块、数据转换路径、我踩过的坑以及最后怎么解读结果都过一遍。对于准备拿RSA发论文、做课题或者单纯想把“脑袋里的想法”和“RNN学到的想法”做对比的同学这篇内容应该能让你少走不少弯路。1. 一个项目里同时出现EEG、RNN和RSA到底在解决什么问题1.1 三者的分工脑电、循环网络和表征比较先说结论EEG提供的是人脑在真实刺激下的神经响应记录RNN提供的是人工神经网络在同样刺激下的内部状态轨迹而RSA则是一把用来比较这两套轨迹的“尺子”。很多刚接触的人容易把RSA当成一种分类器或者特征提取工具其实它更像一种“关系对比”方法。比如你设计了20种视觉刺激你想知道人脑加工这些刺激时的空间关系和RNN前向计算时学到的空间关系是不是一致的。人脑这边你可以用每一类刺激诱发的EEG活动模式来算RNN那边你可以用每一类刺激输入后某一隐藏层的激活模式来算。两边各自得到一个“条件×条件”的距离矩阵最后比较这两个矩阵的相关性。这个项目名字里的“EEGAndRNNAnalysis”也点明了它的主线程先把EEG信号整理成可建模的时间序列再让RNN处理同样结构的序列信号最后用RSA来衡量两者在表征空间上是否有共鸣。1.2 我建议先画数据流图再动手跑代码拿到这套代码的第一件事别急着run。先把数据流程写清楚原始EEG数据从哪来预处理后的数据格式是什么RNN输入的长度和维度是什么RSA输入的RDM又是怎么从时间序列里提取的我习惯把整个流程拆成4个阶段EEG预处理读取、滤波、分段、去伪迹输出为“试次×通道×时间”或“条件×通道×时间”的三维数据。特征模式提取在感兴趣的时间窗内对每个条件得到一组稳定的神经响应模式。RNN训练与内部状态提取把EEG条件序列喂给RNN训练后在指定层提取隐藏状态。RSA计算用两边的模式矩阵分别构建RDM计算RDM之间的相关并做统计检验。这个项目的一大好处是每个阶段都有独立的MATLAB脚本变量名基本对得上像我这种喜欢改别人代码的人也能很快定位问题。2. RSA的核心逻辑不对比原始信号而对比表征空间之间的距离关系2.1 从激活模式到RDM的构造RSA的第一个关键产物叫表征相异矩阵Representational Dissimilarity Matrix简称RDM。假设你有N个刺激条件每个条件都有一个对应的激活模式向量RDM就是N×N的方阵第(i,j)个元素表示第i个条件和第j个条件在神经网络或脑电表征上的不相似程度。这个不相似度怎么算在MATLAB里我常用的是1 - corr(patterns)也就是说用皮尔逊相关系数取反。两个激活模式越相似距离越接近0越不相似距离越接近2。也可以用欧氏距离或者曼哈顿距离但相关性对模式整体的缩放和偏移不敏感在EEG数据里更稳。比如你有一个patterns矩阵行是条件列是特征维度那么构造RDM只需要一行RDM 1 - corr(patterns); imagesc(RDM);corr(patterns)计算的是条件之间的相关系数矩阵1 -之后就成了相异矩阵。这个项目里也沿用了这个思路脑电和RNN两边都生成这样的RDM后续才能做比较。2.2 为什么用Spearman相关比较两个RDM有了人脑RDM和模型RDM之后其实你把它们当作两个“距离几何”来看待。问题是怎么量化它们有多像。这里不用皮尔逊相关而用Spearman等级相关会更合适因为RDM的数值分布往往不是线性的而RSA关心的是条件之间相对的排序关系不是绝对距离大小。rho corr(EEG_RDM(:), RNN_RDM(:), Type, Spearman);这个rho就是你最终要的报告值。它越高说明RNN内部表征空间越接近人脑对同一种刺激的表征空间。项目中一般还会为每个受试者分别计算再取组水平平均也有会遇到个体差异很大的情况。2.3 对比准确率和对比表征空间有什么不一样如果你的目标是直接区分不同刺激准确率可能已经够用。但它给不了你“结构”层面的信息分类准确率高可能是靠一两个明显特征区分开的并不代表模型完整刻画了人脑的相似性结构。RSA则把一个高维空间里的几何关系压缩成N×N的距离矩阵来比较。这种对比更严格也更适合回答“模型内部的状态转移模式是否和人脑在加工序列时的时间动态一致”。尤其在EEGRNN这个组合里空间和时间往往是纠缠的RSA可以告诉你模型在哪些时间尺度上和人脑更贴近。3. 脑电信号到RDMMATLAB预处理和特征矩阵构建的完整路径3.1 通道与ROI怎么选10-20系统的实际处理项目里默认用的电极帽是标准10-20系统这在国内实验室很常见。不做源定位的话通常不会用到全部通道而是按脑区分成几个ROI感兴趣区比如额叶、中央区、顶叶、枕叶分别对应不同编号的电极。这里有个经验RSA对通道选择特别敏感。如果你研究的视觉刺激枕叶通道的效应通常最强但顶叶通道可能有更高级的抽象表征。建议先分别用全通道和分ROI方式各做一次RSA看看结果差异。如果全通道的相关系数远高于单ROI可能是全通道里有多个区域的混合表征并不一定是噪声。数据导入之后我习惯用EEGLAB或FieldTrip做大部分预处理然后导出成MATLAB矩阵再喂给这个项目。核心步骤是带通滤波比如1~40Hz、按刺激onset分段一般取刺激前200ms到刺激后800ms、基线校正、手动或自动去掉明显伪迹。这一步没有捷径每个环节都可能影响后面的RDM。3.2 用激活模式构建条件RDM单试次与平均的取舍预处理完成后你会得到每个条件若干个试次的数据矩阵。这时候有一个经典问题是先把所有试次平均再用平均后的激活模式构建RDM还是不平均直接用单试次来算我的建议是至少不要只做“平均后构建RDM”这一种。因为平均会抹掉试次间的波动这些波动在神经科学里往往包含真实信息。单试次构建RDM的做法是对每个条件分别从多个试次中随机抽取一个代表组成一组条件模式计算RDM重复多次后平均。这种方法和Nili等人提出的RSA流程思路是一致的。但这带来的问题是计算量上来了尤其通道×时间点的矩阵叠加在一起内存容易爆。我在跑这个项目时会先把特征维度做点降维比如对全通道平均到ROI或者在时间窗内做平均这样计算RDM时只需要处理十几个条件×几十个维度的矩阵非常轻量。下面是一个典型的特征矩阵构建流程% 假设 data 为 [cond, time, channel, trial] win 100:250; % 感兴趣的时间窗 patterns squeeze(mean(mean(data(:, win, :, :), 4), 2)); % 结果为 cond×channel再按ROI合并注意mean(..., 4)是对试次求平均mean(..., 2)是对时间窗求平均。如果你要做单试次版本就把mean(..., 4)去掉换成循环抽样。3.3 时间窗选择正确与否直接影响RDM相似度很多新人会全时间段计算一个RDM其实这是把好几种认知加工阶段混在一起。比如刺激出现后100ms是感觉加工300ms可能已经进入语义加工600ms之后可能是反应准备。不同阶段的RSA相关性可能完全不同。这个项目里通常会做“滑动时间窗RSA”从刺激开始每50ms或100ms一个窗口逐窗计算脑电RDM再和同一个模型RDM算相关。这样你能看到人脑何时开始与RNN的某个隐藏层进入“同步”。这也是脑电RSA比fMRI RSA有优势的地方——时间分辨率足够高。另外时间窗不能太窄不然噪声会主导相关值。我试过20ms窗口结果相关系数基本在0附近乱跳换到100ms窗口后就能看到清晰的时间动态曲线。具体窗口宽度没有绝对标准顺着你的实验条件和采样率调但至少要包含几十个采样点。4. RNN在MATLAB里怎么训、隐藏状态怎么抽这几个坑我不希望你踩4.1 标准RNN的核心公式和输入端设计标题里特意提到RNN那我们简单回顾下标准循环神经网络的隐藏状态更新公式[ h_t \tanh(U x_t W h_{t-1} b) ]在MATLAB里如果你不想自己写反向传播一般就用深度学习工具箱的lstmLayer或gruLayer来近似。虽然这些是LSTM/Gated循环单元但项目里保留“RNN分析”概念主要是强调时序建模这个性质。真正训练的时候LSTM比vanilla RNN更容易收敛尤其是EEG这类长序列信号。输入端有一个关键设计每个试次的EEG通常都是“通道×时间”二维矩阵但深度学习工具箱要求输入是“时间×特征”的序列。所以你要转置变成T×F然后每个试次作为一个元素放进cell数组。如果文件夹里存的是trials × time × channel转换起来并不复杂sequences cell(numTrials, 1); for i 1:numTrials sequences{i} squeeze(data(i, :, :)); % time x channel end4.2 网络层结构和训练选项的设置一个够用的序列分类网络大概长这样layers [ sequenceInputLayer(numChannels) lstmLayer(64, OutputMode, last) fullyConnectedLayer(numConditions) softmaxLayer classificationLayer]; options trainingOptions(adam, ... MiniBatchSize, 32, ... MaxEpochs, 60, ... SequenceLength, longest, ... GradientThreshold, 1, ... Plots, training-progress, ... Verbose, false); net trainNetwork(sequences, labels, layers, options);这里有几个需要注意的坑。第一OutputMode, last只输出最后一个时间步适合分类但如果你要提取全程隐藏状态需要改成OutputMode, sequence。第二EEG数据长度往往很长直接用完整800ms序列会导致训练太慢可以先做降采样或在一个小时间窗里训练。第三如果使用GPUactivations也必须传入GPU数据不然会报类型不匹配。4.3 从网络中间层抽取表征而不是用输出层这是整个项目里最容易出差错的一步。很多人以为RNN的输出就是模型的表征其实最后接的那几个全连接层已经把信息压缩成类别概率了空间结构早就扭曲。真正的表征应该在LSTM层输出的隐藏状态上。用MATLAB的activations函数可以拿到任意中间层的激活% 提取每个试次在LSTM层的隐藏状态序列 acts activations(net, sequences, lstmLayer, OutputMode, sequence);注意activations返回的acts形式通常是time × 1 × hiddenUnits × batch或者类似结构需要想办法挤压成一个time × hiddenUnits的矩阵。如果你想对比EEG的条件RDM那每个条件需要聚合整个隐藏状态序列。可以取时间维度的平均值也可以对每个时间窗分别计算RDM再平均。我的经验是不要直接简单平均整个序列因为RNN前期的隐藏状态通常只编码低层特征后期才编码任务相关特征。我建议在RNN的时间轴上也像EEG那边做滑动窗口RDM尤其要关注最后一个时间步附近的窗口那里语义信息最丰富。4.4 Pad带来的隐藏状态污染问题由于不同试次EEG序列长度可能不一样很多人会用SequenceLength, longest自动补零。问题是补出来的尾部零输入也会让RNN继续产生隐藏状态这部分隐藏状态没有任何生理意义。提取特征时如果把padding部分的激活也一起拿进来会直接把噪声带进RDM。解决方式有两种。第一种是训练时统一截取一个固定时间窗比如从刺激开始到刺激后300ms这样所有序列长度一致不用padding。第二种是在提取激活后根据原始序列长度T只取前T个时间步的激活丢弃后面的padding部分。这个项目里我倾向第二种因为不用截断数据保留更多信息。5. 跑通整个流程后的结果解读和统计检验怎么看RSA靠不靠谱5.1 单受试者相关如何汇总当你有多个被试时一种常见做法是对每个被试分别计算EEG-RDM与模型RDM的Spearman相关得到每个被试的rho然后做单样本t检验比如检验是否显著大于0。我建议不要把多个被试的EEG数据直接混在一起做RSA因为脑电信号在不同被试之间存在很大的形态差异混在一起后会引入虚假的个体间变异。如果条件数量比较少比如只有4~6个单个被试的RDM只有6~15个数值Spearman相关的稳定性会很差。这时候可以尝试把不同的RNN模型当作“模型条件”用重复测量方差分析来比较不同模型的RSA相关性而不是只看单值。5.2 用置换检验算显著性不要只靠t检验t检验的零假设是相关值来自均值为0的正态分布但这在RSA上不一定成立尤其当RDM不是完全独立时。更稳妥的做法是置换检验把条件标签随机打乱重新计算EEG-RDM和模型RDM的对应关系重复1000次得到零分布看真实相关值在零分布的哪个位置从而得到经验p值。在MATLAB里实现起来不复杂核心是重复“打乱条件顺序→计算相关”。要注意的是对于多个时间窗做RSA会产生多重比较问题所以需要做FDR校正或最大统计量置换检验。5.3 单试次噪声导致的相关低估和校正思路我做这个项目时踩过一个大坑直接用单试次数据算RDM相关值很低甚至接近零。后来发现不是模型不行而是EEG本身的噪声太高单试次激活模式之间的相关系数全被噪声压低了。这时RSA会偏向高噪声模型因为高噪声会平均掉真实差异。目前常用的做法是做split-half分解校正把每个条件的试次随机分成两半分别计算两个RDM取平均后做Spearman-Brown校正。这样校正后得到的相关值会更接近真实值。在MATLAB里也没有现成工具箱我是自己写了个函数循环几百次随机切分然后取平均。另外一个实用技巧在比较多个RNN模型时不要只看绝对相关值一定要做“模型间差异检验”。比如随机初始化同一个网络结构或者切掉输入一半通道算出一堆基线RSA值。如果目标模型的相关值达不到基线区间之上那说明它的表征优势并不显著。6. 除了RSA还有哪些补充验证手段值得放进工具箱RSA是一个很好的宏观工具但它不是万能钥匙。至少我在实际分析中会把下面几种方法与RSA组合使用否则很难说服审稿人。6.1 时间泛化矩阵时间泛化矩阵Temporal Generalization Matrix也是基于距离矩阵的扩展但它关心的是用一个时间点训练好的分类器在另一个时间点是否仍然有效。如果脑电活动在不同时间窗有相似的神经网络表征时间泛化矩阵就会呈“对角线附近”或“块状”结构。这可以和RNN的隐藏状态轨迹作对比看模型是否也出现了同样的时间动态。6.2 编码模型编码模型Encoding Model更直接用RNN的隐藏状态去线性回归预测EEG每个通道/ROI的响应。如果RNN内部某个时间步的状态能显著预测某一时间段EEG信号说明二者存在可映射的线性关系。RSA告诉你形状相似编码模型告诉你映射可解释两者互补性很强。6.3 论文里怎么串起证据链我建议在写结果时把RSA作为第一层证据说明模型和人脑在表征距离结构上有多接近然后用时间泛化或滑窗RSA展示时间对齐情况最后用编码模型或选择概率分析说明哪些特征最被依赖。三层证据都指向同一个结论的话比单纯的一个相关值可信得多。这套方法用下来我最大的体会是RSA并不能替你决定“哪个RNN模型更好”但它能逼你去思考“什么样的空间结构才是被实验数据支持的”。也许做完一遍你会发现RNN虽然准确率高但它在和人脑的距离上并不占优这时候反而值得去调整个网络结构或训练目标。就像我之前说的拿到这份代码别只想着跑通一个正相关想办法去理解RDM里的几何关系最后对你实验设计的帮助会大得多。本文还有配套的精品资源点击获取