简介:这份资源面向生物医学工程与运动科学领域的研究者,聚焦肌肉协同作用分析中的非负矩阵分解(NNMF)与正则化平移非负矩阵分解(rShiftNMF)算法,提供可运行的Matlab实现方案,帮助从复杂的肌肉电生理信号中提取潜在协同模式,进而理解运动控制的神经机制。压缩包共6个文件,约104KB,包含4个m脚本文件、1个jpeg示意图和1份docx论文文档,脚本分别承担主流程调度、NNMF与rShiftNMF核心计算及辅助函数,论文文档则补充算法推导与实验说明。目前已有720人学习下载,适合具备一定Matlab基础、希望将分解算法落地到实际肌电数据的研究人员。读者可借助完整代码与文档,快速复现算法流程,结合自身实验数据测试验证,并在此基础上调整参数、对比两种方法的效果,为康复医学与运动科学相关课题提供可参考的实现思路与排错依据。
1. 肌肉协同提取为什么绕不开 NNMF 与 rShiftNMF
做表面肌电(sEMG)信号分析的人,迟早会撞上「肌肉协同」这个概念。简单说,中枢神经系统并不单独控制每一块肌肉,而是通过少数几个协同模式(synergy)组合激活多块肌肉完成动作。要把这些协同模式从多通道 sEMG 里挖出来,矩阵分解是主流手段,而 NNMF(非负矩阵分解)几乎是默认起点。问题在于,标准 NNMF 假设各通道信号严格同步,可实际采集时电极位置差异、传导延迟、发力先后都会让通道间出现时间偏移,分解出来的协同模式就会糊成一团。rShiftNMF 就是冲着这个偏移来的——它在分解时允许每个通道有一个时间平移量,把对齐和分解放在一个优化框架里做。这篇笔记面向正在用 matlab 做 sEMG 协同分析的研究生和工程师,从数据准备、NNMF 基线、rShiftNMF 实现到参数调试和踩坑,一步步走完。
2. 从 sEMG 到协同矩阵:数据预处理与 NNMF 基线
2.1 为什么必须先做非负化和归一化
NNMF 的数学前提是输入矩阵 V 所有元素非负,分解出 W(协同模式)和 H(激活系数)也非负。sEMG 原始信号经过带通滤波和整流之后基本非负,但如果你做了全波整流后又减均值,就会出现负值,直接喂给 NNMF 会报错或者出垃圾结果。常见做法是:带通滤波(20–450 Hz)→ 全波整流 → 低通滤波(约 6 Hz)包络 → 逐通道除以该通道最大自主收缩(MVC)时的幅值做归一化。归一化这一步很多人偷懒跳过,结果就是幅值大的通道主导分解,协同模式里全是那几个大通道的权重。
另一个容易翻车的地方是通道数远大于协同数。比如你用了 16 通道 sEMG,想提取 3 个协同,那 V 就是 16×T 的矩阵(T 是时间采样点),NNMF 要把它分解成 16×3 的 W 和 3×T 的 H。如果 T 不够大(比如只截了 1 秒数据),分解会严重过拟合。我一般建议每个协同至少对应 200 个以上有效时间点。
2.2 用 matlab 内置函数跑通 NNMF 最小示例
matlab 从 R2018a 开始有nnmf函数,不用自己写乘性迭代。下面是一个最小可复现的脚本,假设你已经把 sEMG 包络矩阵存成了emg_env.mat,变量名V,尺寸为 channels×time。
% 加载预处理后的 sEMG 包络矩阵 load('emg_env.mat'); % V: channels x time,已非负归一化 % 检查非负性,有负值就截断到 0 V(V < 0) = 0; % 设定协同数量,通常 2-5 个,根据任务复杂度定 k = 3; % 调用 matlab 内置 NNMF,使用乘性更新算法 rng(42); % 固定随机种子,保证可复现 [W, H, D] = nnmf(V, k, 'algorithm', 'mult', 'replicates', 10); % W: channels x k,每列是一个协同模式 % H: k x time,每行是对应协同的激活曲线 % D: 分解的均方根残差 % 可视化第一个协同模式 figure; subplot(2,1,1); bar(W(:,1)); title('协同模式 1 的通道权重'); xlabel('通道编号'); ylabel('权重'); subplot(2,1,2); plot(H(1,:)); title('协同模式 1 的激活曲线'); xlabel('时间采样点'); ylabel('激活水平');这段代码里几个参数值得说清楚。'algorithm','mult'指定乘性更新,比交替最小二乘更适合非负数据,收敛稳但慢一些。'replicates',10表示用 10 个不同随机初始值各跑一遍,取残差最小的结果——NNMF 是局部最优算法,不设 replicates 很容易掉进差解。rng(42)是为了让每次运行结果一致,写论文时尤其重要,否则你换个时间跑结果就变了,审稿人问起来说不清。
2.3 协同数量 k 怎么定:残差曲线与 VAF
k 选几个不是拍脑袋。标准流程是令 k 从 1 到 8 逐个跑 NNMF,每次记录残差 D 和方差解释率 VAF(Variance Accounted For)。VAF 的计算方式是1 - sum(sum((V - W*H).^2)) / sum(sum(V.^2))。一般当 VAF 超过 90% 且再增加 k 时 VAF 提升小于 2%,就认为 k 够了。但 sEMG 协同分析里更常用的是「残差曲线拐点法」——把 D 对 k 画出来,找曲线从陡降变平缓的那个拐点。
k_range = 1:8; vaf = zeros(size(k_range)); resid = zeros(size(k_range)); for i = 1:length(k_range) rng(42); [W, H, D] = nnmf(V, k_range(i), 'algorithm', 'mult', 'replicates', 10); resid(i) = D; vaf(i) = 1 - sum(sum((V - W*H).^2)) / sum(sum(V.^2)); end figure; yyaxis left; plot(k_range, vaf, '-o'); ylabel('VAF'); yyaxis right; plot(k_range, resid, '-s'); ylabel('残差'); xlabel('协同数量 k'); title('协同数量选择曲线');实际跑下来,上肢 reach-to-grasp 任务通常 3–5 个协同,步行任务 4–6 个。如果你跑出来 k=8 还没收敛,先别怀疑算法,回去查数据质量——大概率是某个通道噪声太大或者电极松了。
3. rShiftNMF 的核心改动:把时间偏移放进优化目标
3.1 标准 NNMF 在通道不同步时为什么会失效
标准 NNMF 的目标函数是min ||V - WH||_F^2,它隐含假设 V 的每一列(同一时刻各通道的激活)是严格对齐的。但 sEMG 采集时,不同肌肉的传导延迟可以差 10–40 ms,如果采样率是 1000 Hz,那就是 10–40 个采样点的偏移。这个偏移在 NNMF 里会被当成「噪声」或者「协同模式本身的形状差异」,导致分解出的 W 里同一个协同被拆成两个相似但不完全一样的模式,H 的激活曲线也会出现不该有的双峰。
rShiftNMF 的思路很直接:给每个通道引入一个整数平移量 τ_c,在计算残差之前先把该通道的信号沿时间轴平移 τ_c,然后最小化平移后的重构误差。目标函数变成min ||V_shifted - WH||_F^2,其中 V_shifted 的第 c 行是原始第 c 行平移 τ_c 个采样点后的结果。τ_c 和 W、H 一起优化。
3.2 rShiftNMF 的 matlab 实现:交替优化框架
rShiftNMF 没有 matlab 内置函数,得自己写。核心是交替优化:固定 τ 更新 W、H(用标准 NNMF 的乘性更新),固定 W、H 更新 τ(对每个通道搜索使残差最小的平移量)。下面是一个可运行的实现。
function [W, H, tau, resid] = rshift_nnmf(V, k, max_tau, max_iter) % rshift_nnmf 带通道时间平移的非负矩阵分解 % V: channels x time,非负 % k: 协同数量 % max_tau: 最大平移量(采样点数),通常取 50 % max_iter: 最大交替迭代次数 [channels, T] = size(V); tau = zeros(channels, 1); % 每个通道的平移量,初始为 0 % 初始化 W 和 H,用标准 NNMF 跑一次 rng(42); [W, H] = nnmf(V, k, 'algorithm', 'mult', 'replicates', 5); for iter = 1:max_iter % 步骤 1:固定 tau,构造平移后的 V_shift,更新 W 和 H V_shift = zeros(size(V)); for c = 1:channels V_shift(c, :) = shift_channel(V(c, :), tau(c)); end % 用乘性更新迭代若干次(这里直接调 nnmf 的底层更新) [W, H] = nnmf_update(V_shift, W, H, 20); % 步骤 2:固定 W 和 H,对每个通道搜索最优 tau recon = W * H; for c = 1:channels best_tau = tau(c); best_err = inf; for t = -max_tau:max_tau V_shifted = shift_channel(V(c, :), t); err = sum((V_shifted - recon(c, :)).^2); if err < best_err best_err = err; best_tau = t; end end tau(c) = best_tau; end % 计算当前残差 V_shift = zeros(size(V)); for c = 1:channels V_shift(c, :) = shift_channel(V(c, :), tau(c)); end resid(iter) = sqrt(sum(sum((V_shift - W*H).^2)) / numel(V)); % 收敛判断 if iter > 5 && abs(resid(iter) - resid(iter-1)) < 1e-4 break; end end end function y = shift_channel(x, tau) % 对单通道信号做整数平移,超出部分补零 if tau == 0 y = x; elseif tau > 0 y = [zeros(1, tau), x(1:end-tau)]; else y = [x(1-tau:end), zeros(1, -tau)]; end end function [W, H] = nnmf_update(V, W, H, n_iter) % 乘性更新规则,迭代 n_iter 次 eps_val = 1e-9; for i = 1:n_iter H = H .* (W' * V) ./ (W' * W * H + eps_val); W = W .* (V * H') ./ (W * H * H' + eps_val); end end这段代码的逻辑说明:外层循环交替做两件事。第一件是固定当前平移量,把所有通道对齐后跑 NNMF 更新 W 和 H;第二件是固定 W 和 H,对每个通道在[-max_tau, max_tau]范围内穷举搜索使重构误差最小的平移量。shift_channel函数处理正负平移,超出边界的部分补零——这里有个细节,补零会引入人为的非负值,如果平移量很大,补零区域会干扰分解,所以max_tau不宜超过信号长度的 5%。
参数方面,max_tau根据你的采样率和预期最大延迟定。1000 Hz 采样、预期最大延迟 50 ms,那max_tau=50。max_iter一般 30–50 次足够,配合残差收敛判断提前退出。nnmf_update里的eps_val是防止除零,乘性更新对零值敏感,加一个小常数是血泪经验。
3.3 平移量初始化:别让算法从零开始瞎搜
上面的实现里 tau 初始为全零,这意味着第一轮交替时算法还没对齐就开始分解,容易陷入局部最优。更稳的做法是先做一个互相关粗对齐:选一个参考通道(比如信噪比最高的那个),计算其他通道与参考通道的互相关,取峰值位置作为 tau 的初始值。
% 互相关粗对齐初始化 tau ref_ch = 1; % 假设通道 1 是参考 tau_init = zeros(channels, 1); for c = 1:channels [xcorr_vals, lags] = xcorr(V(c,:), V(ref_ch,:), max_tau, 'coeff'); [~, idx] = max(xcorr_vals); tau_init(c) = lags(idx); end把tau_init传进 rshift_nnmf 替换全零初始化,通常能少迭代 10 次以上,而且最终残差更低。这个技巧在通道数多的时候效果尤其明显。
4. 参数调试与结果验证:怎么判断分解靠不靠谱
4.1 重构误差、VAF 与协同相似度三指标联查
单看重构误差不够,因为 rShiftNMF 比 NNMF 多了一组自由参数(tau),重构误差天然会更低,但这不代表分解更有意义。我一般同时看三个指标:VAF 要超过 90%,tau 的绝对值不能大到离谱(如果某个通道 tau 接近 max_tau,说明要么该通道信号质量差,要么 max_tau 设小了),以及分解出的协同模式在不同试次之间的一致性。
一致性用余弦相似度衡量:把同一受试者同一任务的多组试次分别跑 rShiftNMF,得到多组 W,两两计算协同模式列向量的余弦相似度,取平均。如果平均相似度低于 0.8,说明分解不稳定,要么数据太短,要么 k 选大了。
% 计算两组 W 之间的协同模式匹配相似度 function sim = synergy_similarity(W1, W2) k = size(W1, 2); sim_matrix = zeros(k, k); for i = 1:k for j = 1:k sim_matrix(i,j) = dot(W1(:,i), W2(:,j)) / ... (norm(W1(:,i)) * norm(W2(:,j))); end end % 用匈牙利算法做最优匹配,取匹配后的平均相似度 assignment = matchpairs(-sim_matrix, 1e3); sim = mean(sim_matrix(sub2ind(size(sim_matrix), ... assignment(:,1), assignment(:,2)))); end这里用到了匈牙利算法做最优匹配,matlab 的matchpairs函数直接可用。不匹配直接取对角线的相似度是常见误用,因为 NNMF 每次跑出来的协同顺序是随机的。
4.2 tau 的物理意义检验:别让算法替你编故事
rShiftNMF 跑出来的 tau 不是纯数学产物,它应该对应真实的生理延迟。如果你发现某个通道的 tau 是 -45 个采样点(1000 Hz 下就是提前 45 ms),而这块肌肉在解剖上不可能比参考肌肉早激活那么多,那就要警惕了。常见原因是该通道信噪比太低,算法把噪声对齐当成了信号对齐。
检验方法:把 tau 按通道位置画在人体示意图上,看是否符合运动链的远近端延迟规律。比如上肢任务里,近端肌肉(三角肌)通常比远端肌肉(指屈肌)早激活 20–40 ms,如果 tau 显示相反的顺序,大概率是分解出了问题。另一个办法是把 tau 和该通道的 SNR 做相关,如果低 SNR 通道的 tau 明显更极端,说明是噪声在驱动平移。
5. 避坑与排查:rShiftNMF 落地时最容易翻车的五个地方
5.1 现象:分解出的协同模式全是噪声形状,VAF 低于 70%
原因:最常见的是输入矩阵没有做逐通道归一化,某个幅值特别大的通道主导了整个分解。其次是数据段太短,时间点少于通道数的 10 倍。
解决:回去检查预处理流程,确保每个通道除以自己的 MVC 幅值。数据段至少截取 2 秒以上,采样率 1000 Hz 的话就是 2000 个点起步。如果还是不行,先把 k 降到 2 跑一次看看能不能出合理结果。
5.2 现象:tau 全部收敛到 max_tau 边界
原因:max_tau 设得太小,真实延迟超出了搜索范围,算法只能顶到边界。或者参考通道选得不好,互相关初始化给了一个错误的方向。
解决:把 max_tau 翻倍再跑一次,观察 tau 是否还顶边界。如果翻倍后 tau 分布合理了,说明之前确实设小了。参考通道换成 SNR 最高的通道,别随便选第一个。
5.3 现象:每次运行结果差异很大,协同模式对不上
原因:NNMF 的随机初始化和 rShiftNMF 的交替优化都是局部最优算法,不固定随机种子、不设 replicates 就会这样。
解决:rng固定种子,nnmf的replicates至少设 10,rShiftNMF 外层交替也跑 3–5 次不同初始化取最优。写论文时报告结果要注明随机种子和 replicates 次数。
5.4 现象:rShiftNMF 比 NNMF 的 VAF 只高了不到 1%
原因:如果你的数据本身通道间同步就很好(比如用同一块采集板、电极间距很近),那 rShiftNMF 的优势体现不出来。这不是算法问题,是数据问题。
解决:先跑 NNMF 看残差曲线,如果 NNMF 的 VAF 已经 95% 以上,说明偏移不严重,用 NNMF 就够了。rShiftNMF 的价值在通道间有明显延迟的场景,比如跨关节的多肌肉采集或者无线电极不同步的情况。
5.5 现象:代码跑得特别慢,16 通道 3 协同要跑十几分钟
原因:tau 搜索是穷举的,每个通道每次交替要搜2*max_tau+1次,每次都要算全时间轴的重构误差。通道数一多就爆炸。
解决:把 tau 搜索改成粗搜加细搜两步——先以 5 个采样点为步长粗搜,找到大致范围后再在附近以 1 个采样点为步长细搜。另外nnmf_update里的内层迭代次数从 20 降到 10,对外层交替的最终结果影响很小,但速度能快一倍。
6. 进阶技巧:用 rShiftNMF 的 tau 做通道质量筛查
跑完 rShiftNMF 之后,tau 向量其实是一个被低估的通道质量指标。我现在的习惯是:每次分解完,先把 tau 的绝对值排序,取最大的两个通道单独看它们的原始信号。十有八九,这两个通道要么有工频干扰,要么电极接触不良,要么在任务过程中被碰松了。这个筛查方法比看 SNR 更直接,因为 SNR 高不代表通道间同步好,而 tau 异常大说明这个通道和整体运动模式脱节。
具体操作上,我会把 tau 筛查做成一个固定流程:rShiftNMF 跑完后,计算abs(tau)的中位数和四分位距,标记出abs(tau) > median + 2*IQR的通道,把这些通道的信号单独画出来和参考通道对比。如果确认是噪声,就剔除后重新跑分解。通常剔除 1–2 个坏通道后,VAF 能提升 3–5 个百分点,协同模式的生理可解释性也明显变好。
另一个进阶用法是把 rShiftNMF 的 tau 当作特征做分类。比如你想区分健康受试者和某类运动功能障碍患者,tau 的分布差异可能比协同模式本身更敏感。我试过用 tau 的均值和标准差做输入,配合一个简单的 LDA 分类器,在区分不同疲劳状态时效果比直接用 W 更好。当然这取决于你的具体问题,不是万能药。
最后说一个我踩过的坑:rShiftNMF 的 tau 是整数采样点,如果你的采样率只有 200 Hz,那 tau 的分辨率就是 5 ms,对于延迟只有几毫秒的通道来说精度不够。这种情况要么提高采样率,要么在 tau 搜索时做插值实现亚采样点精度。我一般建议 sEMG 采集至少 1000 Hz,这样 tau 的分辨率是 1 ms,足够覆盖生理延迟范围。希望帮到你。
本文还有配套的精品资源,点击获取