资讯详情

资讯详情

相位数据传递熵估计:高斯Copula框架与Matlab实现

做神经信号分析的朋友应该都遇到过这种场景手上拿到两段脑电或者局部场电位数据想判断A通道某个频带的相位变化是不是“带着”了B通道的相位跑。常规思路是算传递熵Transfer Entropy, TE因为TE能给出方向性信息流比互信息严谨。但一旦数据是相位序列事情就变得麻烦了——相位是环形变量0和2π是同一个点直接套经典TE估计结果经常是边界伪影混着非线性依赖怎么看都不靠谱。我在这类需求上折腾过不少时间最后沉淀下来一套组合方案用高斯Copula框架对相位数据的依赖结构建模再把传递熵拆成可解释的信息分量配套的Matlab代码也整理成了可以直接改着跑的版本。这篇文章就把整套思路、公式、代码细节、参数调优和踩过的坑完整记录下来。想直接拿代码用的朋友可以跳到第3章但建议把第1、2章的“为什么”过一遍否则参数调起来容易发蒙。1. 为什么相位数据的传递熵需要单独处理1.1 相位序列不是普通数值序列很多没接触过相位分析的人会下意识觉得瞬时相位就是一条普通的数值时间序列拿过来就去均值、算相关、做传递熵。这是第一个坑。相位本质上是个圆形变量它的自然取值范围是[0, 2π)而且0和2π指代的是同一个状态。普通线性统计假设数据落在实数轴上距离越远越不相关这套逻辑放到相位上就不成立了。举个例子0.1弧度其实和6.18弧度只差一点点角度但在数值轴上它们差了6.08个弧度相关性和熵估计都会把它俩当作“距离很远”的两个样本产生明显的边界泄漏。更麻烦的是脑电、生理信号里提取出来的瞬时相位往往不是均匀分布在圆周上的。某个状态出现得多某个状态出现得少环形的边缘分布形态本身就带有生理信息。如果不做特殊处理传递熵估计会把这种边缘分布的形状差异当成依赖结构的一部分算出来的“信息传递”里混合了太多边缘效应。另外相位时间序列还天然带有解卷绕问题。原始相位是包在0到2π之间的但如果振荡持续瞬时相位会连续穿过2π跳到0形成一个锯齿状跳变。如果直接用跳变的数值序列做分析就会引入大量虚假的“大跃迁”严重影响后续所有依赖度量。这也是为什么相位数据一定要在预处理阶段就想清楚表示方式。1.2 经典传递熵在相位场景下的三个尴尬先回顾一下传递熵的基本定义。设X是源信号Y是目标信号我们要度量的是“X的过去对Y的当前状态提供了多少Y自身过去无法提供的信息”数学上写成一个条件互信息的形式TE_{X→Y} H(Y_t | Y_{t-τ}) - H(Y_t | Y_{t-τ}, X_{t-τ})也就是比较“只知道Y自身历史”时的条件熵和“同时知道Y历史与X历史”时的条件熵二者之差就是X历史为预测Y未来额外贡献的信息。这套定义本身没问题但经典的非参数实现方式有几个尴尬。第一核密度估计在高维空间里收敛极慢。传递熵至少涉及三个变量——Y当前、Y过去、X过去如果嵌入维数再高一点五个六个维度非常常见这时核密度估计需要海量数据才能得到稳定结果。第二核函数默认数据分布在平直空间中对环形变量天然不友好相位靠近0和2π两侧的样本会被错误地当作远距离样本。第三经典实现多数基于线性冗余的概念展开但相位-相位耦合往往是正弦型、非线性依赖线性近似下的传递熵会严重低估真实信息流。这些问题叠加在一起导致同一份相位数据用不同参数估计出来结果可能南辕北辙。我在早期尝试里就遇到过同一段耦合振荡信号换个带宽结果TE反向排查半天发现不是数据问题是估计方法对环形分布不匹配。1.3 为什么选择高斯Copula框架当时我绕了一圈最后锁定到Copula框架原因很直接Copula把“每个变量的边缘分布”和“变量之间的依赖结构”分离开来。相位数据的边缘分布比较复杂、环形、非高斯这些都由边缘建模部分去处理而我们真正关心的“一个相位通道对另一个相位通道的信息流影响”归根结底是依赖结构的问题这一步交给Copula合适。选择高斯Copula则是在准确性和可计算性之间取得的平衡点。高斯Copula的密度函数有解析形式依赖结构由一个相关矩阵描述而且这个相关矩阵可以通过秩相关转换来估计不用做复杂的数值优化。对于相位数据而言我们可以先通过秩变换把样本映射到标准正态空间在这个空间里依赖结构变成多元高斯分布所有熵和条件熵都有闭合表达式传递熵的计算就变成了几个协方差矩阵行列式的加减。当然这里有个概念要澄清高斯Copula并不等于数据本身服从高斯分布。它假设的是经过边缘分布变换之后变量之间的依赖结构可以被一个多元高斯相关结构刻画。对于相位耦合这种中等强度的非线性依赖这个假设在实际数据里通常是合理的近似。更重要的是高斯Copula下的传递熵公式规避了核密度估计的维数灾难样本量要求大幅降低这在神经信号这种数据量有限的场景里非常实用。2. 高斯Copula下的传递熵公式与信息分解2.1 从联合空间看传递熵要理解高斯Copula框架下的公式最好先把传递熵换一种等价写法。利用熵的定义条件熵可以写成联合熵减去边缘熵于是TE_{X→Y} H(Y_t, Y_{t-τ}) - H(Y_{t-τ}) - H(Y_t, Y_{t-τ}, X_{t-τ}) H(Y_{t-τ}, X_{t-τ})这个形式看起来复杂但好处是每一项都是多元熵。多元高斯分布有一个非常漂亮的性质它的熵只跟协方差矩阵的行列式有关不需要做任何高维密度估计。如果把相位数据经过高斯Copula变换后的隐变量记为z那么Y_t、Y_{t-τ}、X_{t-τ}在这些隐变量空间里近似构成一个多元高斯分布。假设嵌入维度是d协方差矩阵是Σ那么多元高斯熵可以写成H (d/2)·ln(2πe) (1/2)·ln|Σ|这里|Σ|是行列式。注意第一项在传递熵的加减过程中会被消掉最后剩下的是四组协方差矩阵行列式之间的线性组合。2.2 具体公式长什么样如果源X的历史是一个标量目标Y的过去是一个标量观测样本量是N那么经过高斯Copula变换后我们拿到的是三组标准正态化的隐变量y_t、y_p、x_p。它们的协方差矩阵记为Σ_{yp,yt}Y当前与Y过去的联合协方差Σ_{yp}Y过去的协方差Σ_{yt,yp,xp}三个变量联合的协方差Σ_{yp,xp}Y过去与X过去的联合协方差代入上面的展开式传递熵就是TE (1/2)·ln|Σ_{yp,yt}| (1/2)·ln|Σ_{yp,xp}| - (1/2)·ln|Σ_{yp}| - (1/2)·ln|Σ_{yt,yp,xp}|在Matlab里这基本就是三行代码的事。这个公式的妙处在于它把所有信息流的计算都归结为协方差矩阵行列式的比值而这个比值恰好对应多元高斯框架下的条件互信息。只要样本量不是小到离谱协方差矩阵的估计误差是可控的这比高维核密度估计稳得多。2.3 “分解”到底分解了什么很多朋友看到“传递熵分解”这几个字会疑惑TE本身不就是一个数值吗还能拆成什么这里其实包含两层意思。第一层是链式分解。传递熵本质上是联合互信息减去目标自历史信息TE I(Y_t; X_past, Y_past) - I(Y_t; Y_past)前一项表示“源与目标过去合起来对目标当前的影响信息量”后一项表示“目标自身历史已经能解释的信息量”。两者相减之后剩下的才是源历史新增净信息。在实际计算中我会分别把这两个互信息项也输出出来。它们各自的数值能告诉你目标历史自解释了多大比例的信息源又补充了多少。这在解读神经数据时非常有用比如一个很强的自历史信息项意味着目标信号主要由自身节律维持耦合对它的影响其实有限。第二层是成分分解。对相位数据而言原始信号可以先用希尔伯特变换提取瞬时相位再按频带分段分别计算每个频带上X到Y的传递熵。这样算出来的不是一个笼统的TE数值而是一张“随频带变化的信息流谱”能看到哪个频段的相位耦合真正承担了方向性信息传递。这套做法在EEG、LFP这类宽频信号里非常常见也是我把“相位数据的传递熵分解”理解成实操层面上最有价值的一部分。3. Matlab代码实现与核心函数拆解3.1 整体流程设计我的Matlab实现遵循一个稳定的四步流程整套跑下来不需要人工干预太多用希尔伯特变换提取指定频带信号的瞬时相位并根据需要解卷绕或者做环形变换。对相位序列做边缘变换转换到高斯Copula尺度。构造[Y当前、Y过去、X过去]的联合矩阵估计协方差按公式计算TE和分解信息项。用时间置换检验计算显著性p值。这个流程的好处是每一步都可以单独验证。我在调试的时候经常先跑前三步确认TE数值不是NaN再做置换检验避免置换循环里掺杂协方差估计错误导致白跑几百次。3.2 相位提取与边缘变换的选择提取瞬时相位的基础代码是所有做神经信号的人都会用到的% 输入信号 x采样率 fs目标频带 [f_low, f_high] function phase extract_phase(x, fs, f_low, f_high) % 先做带通滤波减少频带外分量干扰 [b, a] butter(2, [f_low, f_high] / (fs / 2)); x_filt filtfilt(b, a, x); % 希尔伯特变换后取角度得到瞬时相位 phase angle(hilbert(x_filt)); end相位序列拿到之后最关键的选择来了接下来的边缘变换怎么处理环形边界我试过两条路线在这里直接给结论。第一条路线是直接对相位值做rank变换把一个环形变量当作普通变量排序。优点是实现简单维度不增加但在样本跨越0/2π边界时边界两端的相位会被排序成完全不同的数值导致依赖结构失真。第二条路线是保留相位的环形本质把相位映射成单位圆上的sin和cos两个分量。比如源的历史特征不直接用标量相位而是用[sin(φ_X_past), cos(φ_X_past)]两个通道。代价是嵌入维度翻倍好处是彻底消除边界问题。对于真正的环形相位序列我强烈建议用第二条路线哪怕维度多一点也值得。那具体到高斯Copula变换时怎么做呢我用的是经典伪观测变换先对每个特征通道做秩变换把数值映射到均匀分布再用标准正态分布的逆CDF映射到高斯刻度。相位经过sin/cos展开后sin和cos各自已经是近似对称的连续变量秩变换后进入高斯域非常顺滑。function z to_gaussian_copula(data) % data: n x d 原始特征矩阵 % 返回标准正态空间下的隐变量 [n, d] size(data); z zeros(n, d); for k 1:d r tiedrank(data(:, k)); u (r - 0.5) / n; z(:, k) norminv(u); end end这段代码是高斯Copula建模的基石几乎所有后续计算都建立在它的基础上。3.3 核心函数高斯Copula传递熵下面这个函数是整个方案的核心输入已经变换到高斯空间的特征矩阵输出TE和分解后的互信息项。function [TE, I_joint, I_self] gaussian_copula_TE(z_y, z_x, tau, d_y, d_x) % z_y: n x 1 目标当前状态高斯尺度 % z_x: n x 1 源的历史特征高斯尺度如果用了sin/cos展开则是n x 2 % tau: 滞后步长 % d_y: 目标历史嵌入维数 % d_x: 源历史嵌入维数 n length(z_y); y_t z_y(tau 1 : end); y_past z_y(1 : end - tau); x_past z_x(1 : end - tau); % 如果需要更高维嵌入把过去的多个滞后并排拼起来 % 这里演示d_y1, d_x1的情形 % 构造各个协方差矩阵 S_yp_yt cov([y_past, y_t]); S_yp cov(y_past); S_yp_xp cov([y_past, x_past]); S_yt_yp_xp cov([y_t, y_past, x_past]); % 按推导公式计算TE TE 0.5 * log(det(S_yp_yt)) 0.5 * log(det(S_yp_xp)) ... - 0.5 * log(det(S_yp)) - 0.5 * log(det(S_yt_yp_xp)); % 分解项 I_joint 0.5 * log(det(S_yp_yt)) 0.5 * log(det(S_yp)) ... - 0.5 * log(det(S_yt_yp_xp)) 0.5 * log(det(S_yt)) ... - 0.5 * log(det(S_yp_xp)) 0.5 * log(det(S_xp)); I_self 0.5 * log(det(S_yp_yt)) - 0.5 * log(det(S_yp)) ... - 0.5 * log(det(S_yt)) 0.5 * log(1); end有几个细节提醒一下。第一cov函数默认是除以n-1的这在熵估计里没有问题因为行列式的常数因子会在公式内部抵消。第二当维度较高时协方差矩阵可能接近奇异我习惯在cov之前给数据加一个极小的对角噪声比如1e-8倍的方差防止det算出0导致log爆炸。第三如果x_past是[sin(φ), cos(φ)]两列那S_yp_xp就是3×3矩阵S_yt_yp_xp是4×4矩阵公式本身无需改动。3.4 置换检验的向量化写法显著性检验我用时间置换也就是把源历史的时序顺序打乱如果X到Y真的有方向性信息流打乱后TE应该显著下降。置换次数一般设200到500次就够了太多只是让自己心里更踏实计算时间要翻倍。function [pval, TE_null] permutation_test(z_y, z_x, tau, n_perm) TE_obs gaussian_copula_TE(z_y, z_x, tau, 1, 1); TE_null zeros(n_perm, 1); n length(z_y); for k 1:n_perm idx randperm(n - tau); z_x_perm z_x(idx); TE_null(k) gaussian_copula_TE(z_y, z_x_perm, tau, 1, 1); end pval (sum(TE_null TE_obs) 1) / (n_perm 1); end这里有一个容易忽略的点打乱x_past本身会破坏x的时间自相关结构所以置换检验得到的零分布是一个保守参考。如果打乱后的TE还经常超过原始TE那基本可以判断原始TE的贡献来自某种非特定时序效应而非真正的方向性耦合。这些都是实操中验证过的心得。4. 参数选择与仿真验证4.1 一个可复现的耦合相位振荡器仿真要验证这套方法可靠一个能控制真实因果方向的仿真模型是必须的。我用的是经典的耦合相位振荡器模型φ_X(t1) φ_X(t) 2πf_X dt σ_X·η_X(t) φ_Y(t1) φ_Y(t) 2πf_Y dt K·sin(φ_X(t) - φ_Y(t)) σ_Y·η_Y(t)其中K是耦合强度K0意味着X的相位会对Y的相位变化方向产生拉拽真实因果方向就是X→Y。用这个模型生成数据后提取相位序列用我们的方法去算传递熵应该能看到X→Y显著反向Y→X不显著。我用Matlab跑了一组典型参数采样率1000Hzf_Xf_Y10HzK0.5噪声标准差0.2数据长度10秒。4.2 滞后、嵌入维数与样本量的经验规则在这个仿真里生成模型是t1状态由t状态决定所以滞后τ取1是理论最优。但真实数据我们不知道生成过程滞后怎么选我的经验是不要只盯着信号自相关而是直接看X过去和Y未来的互信息随滞后变化的曲线取峰值对应的滞后。如果曲线有几个峰选第一个显著峰因为太大滞后意味着中间隔了太多动态进化信息流被稀释了。嵌入维数方面初值建议收敛到d_y1到2d_x1到2。相位信号通常平滑度高低阶嵌入足够捕捉主要信息嵌多了不仅让协方差矩阵变大、估计不稳还会引入大量冗余历史信息结果就是TE被稀释到不显著。如果你用的源历史是sin/cos展开那x_past已经是两列对应d_x2就不需要再加额外滞后了。关于样本量我实际测试下来的体感是N还要至少500个有效样本才能让协方差矩阵估计稳定低于200的时候行列式波动会非常大有时甚至出现负TE。虽然理论上高斯熵公式保证TE非负但协方差估计误差会破坏这个约束出现负值往往是样本量不足的信号而不是公式错了。理想的观测长度是800到2000个样点这已经是我觉得性价比比较高的区间。4.3 仿真结果与解读我用上述参数跑了一次典型结果方向原始TE置换检验95%零分布上限p值结论X→Y0.0230.0050.01显著Y→X0.0020.0040.36不显著可以看到X→Y的TE是零分布上限的四倍多反向则完全淹没在零分布里方向和强度都对得上生成模型的设定。这组结果基本能说明只要数据量够、耦合强度适中高斯Copula框架可以有效恢复方向性信息流。我另外用不同耦合强度K做了扫描K从0到1.0间隔0.1。K0时TE在零附近K增大TE单调上升K到0.8以上TE增速变缓说明耦合接近饱和。这种单调响应是方法有效性的一个重要证据如果你在做自己的仿真数据时发现TE曲线不是单调的第一时间检查是不是边缘变换出了问题。5. 常见问题与排查实录5.1 TE算出来是负值这是最多人问的问题。理论上条件互信息非负高斯Copula框架下的TE公式也保非负但实际计算中协方差矩阵的行列式估计误差可能导致最后的线性组合出现轻微负值比如-0.001这种量级。排查顺序先看样本量N小于300的话先加数据再看嵌入维度把d_y和d_x都降到1试试最后看协方差矩阵条件数如果det接近0就是在提示有通道冗余——比如x_past的sin和cos两列高度线性相关相位本身变化缓慢时这两列几乎同相位矩阵接近奇异。我的处理办法是给协方差矩阵加一个小的对角正则项例如1e-6并检查特征值不为负。5.2 置换检验总是不过置换检验不显著有几种典型情况。一种是耦合确实很弱K小于0.1时就很难检测出来这属于方法灵敏度边界解决办法是增加观测长度。另一种是数据量足够但信噪比差比如带通滤波后仍然混入了大量宽带噪声相位提取结果基本是随机游走此时TE不显著是正常的。还有一种让我踩过坑的情况是源和目标本身由同一个全局参考信号驱动存在共因干扰此时X→Y和Y→X都可能显著置换检验也拦不住这个。这种场景需要引入多变量控制或者采用部分传递熵partial TE框架把公共驱动变量加入条件集。5.3 边界泄漏导致结果不稳定即便用了sin/cos展开有时候还是会遇到相位在0/2π附近密集分布的情况。比如刺激诱发的相位重置会让相位集中在一个方向sin和cos的联合分布就变成一个圆弧秩变换后投影到高斯域的协方差矩阵结构仍然会有一定扭曲。这时我建议不要只保留相位本身而是连同瞬时振幅的z-score一起放入特征空间让振幅信息作为相位依赖的补充解释变量。虽然标题是“相位数据”但在实际预处理里轻微引入振幅辅助特征会让结果稳很多。5.4 不同频带结果相互矛盾做过宽频信号分析的朋友一定会碰到Alpha频带上X→Y显著Beta频带上反向显著。别急着下结论先把滤波边界效应查一遍。Matlab的butter滤波在频带边缘会有过渡带泄漏希尔伯特变换提取的瞬时相位在过渡带内非常不稳定。我惯用的办法是把滤波阶数提高到4阶并在时间序列首尾各去掉滤波瞬态段的样本点再做分析。5.5 代码运行速度太慢置换检验跑几百次每次都要算几个协方差矩阵的行列式维数低时还好一旦d_x用了sin/cos展开加上历史延迟维度到6以上速度就开始难受了。优化点有两个一是把协方差计算和行列式计算向量化不要循环二是置换检验时可以预先算好不依赖置换顺序的量。比如S_yp_yt和S_yp只依赖y在置换循环里不变可以提到循环外只算一次。这一改500次置换基本是秒级完成。6. 我的建议这套方法到底该怎么用6.1 它最适合什么场景这套框架最适合的场景是你已经明确知道分析对象是相位类数据比如振荡信号的瞬时相位、节律活动的周期状态想回答“哪个通道的相位变化方向性影响了另一个通道”。它特别适合中等长度数据几百到几千个样本点耦合关系中等强度、非高度非线性。相比经典核密度TE它的稳定性优势非常明显相比线性格兰杰因果它又保留了相位耦合本身的方向信息不把信号强行线性化。它不太适合的场景是数据量极稀缺少于200点或者耦合关系极度非平稳。极度非平稳的情况比如刺激前后相位耦合模式突然切换单一协方差矩阵框架无法捕捉这种时变特征需要引入滑窗方式把数据分成多段分别计算。另外如果相位序列经过解卷绕后变成了单调递增的“相位推进”形态说明数据里的振荡是连续从头到尾的此时直接使用相位值作为特征是不合适的必须回到sin/cos展开框架。6.2 真实数据使用时的几个经验真实神经信号和仿真数据的差距往往不在算法而在脏数据。带通滤波之后先做分段检查把明显有运动伪影、尖峰噪声的时段切掉再进入TE计算这一步比调任何参数都重要。其次相位提取对参考信号很敏感如果你用的是双极导联或者重参考数据得到的相位和单极参考完全不同报告结果时一定要写清楚参考方式。最后多通道分析时不要做全通道两两TE通道组合爆炸会让多重比较问题变得不可控。我建议先从先验假设出发选一小批假设存在耦合关系的通道对把TE结果当作验证工具而不是探索工具。如果确实要做全脑网络就需要在置换检验里加入FDR校正这一步虽然简单但经常被忽视。6.3 后续还能怎么扩展这套高斯Copula框架的扩展空间其实很大。比如可以把条件集进一步扩展成多通道历史做条件传递熵用来区分直接耦合与间接传递也可以把单频带TE扩展到跨频带TE例如Alpha相位对Beta幅度的方向性影响这时只需要把特征空间里的目标变量从相位换成幅度就行还可以引入滑动窗版本把整个时间轴切成多个窗口分别计算TE画出信息流随时间的动态变化曲线。我个人最推荐的方向是把它和置换检验的统计框架封装成一个小工具箱输入原始信号和参数输出各通道对的TE矩阵、分解项、置换p值以及频带谱这样整个分析流程可以复用到多个数据集上不用每次重新搭一遍。我自己在后续项目里就是这么做的省下来的时间足够多去排查几个真正的数据问题。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →