FastICA双麦语音分离:白化、负熵与频域对齐实战 简介这份FastICA语音分离资料面向学习盲源分离、语音信号处理与多说话人分离的初学者和进阶读者围绕独立成分分析的核心假设、非高斯性度量、预处理、非线性函数选择、固定点迭代及分离矩阵收敛等环节展开可用于理解算法原理并借助示例音频复现实验。压缩包共28个文件以26个wav语音样本为主另含1个m脚本和1个log日志整体约18.16MBwav便于试听与分离前后对比m脚本对应算法实现log可用于排查运行过程。已有480人学习下载。读者可结合描述中的评价思路从主观听感、信噪比提升、BSS-EVAL和语音识别率等角度检验分离效果并延伸到多说话人分离、噪声抑制、语音增强与音乐解混等场景形成从理论到实验的完整学习路径。1. 双口麦克风录到两人同时说话FastICA 语音分离在解什么方程会议室装了两支麦克风两个人几乎同时开口录下来的两路音频里各自都混进了对方的声音。FastICA 语音分离要处理的就是这样一种情况既不知道两个人说了什么也不知道房间的声学传递特性只凭两路观测信号把两个原始语音和那个混合过程一起反推出来。它属于盲源分离里最经典的一条技术路线——假设各声源在统计上相互独立、混合是瞬时完成的再用非高斯性最大化把独立成分逐个分离出来。会议转写前的预处理、双麦设备的方向性前端、嵌入式语音交互的抗干扰模块都常见到它的身影。但它不是降噪器也不是去混响器如果把这三件事混为一谈后面的问题就会从这里埋下伏笔。2. 中心化、白化与负熵FastICA 的两步预处理为什么决定成败2.1 中心化与白化为什么协方差矩阵必须先压成单位阵两支麦克风录到的信号可以写成x(t) A s(t) n(t)其中x是两路观测s是两个源语音A是 2×2 的混合矩阵。ICA 的目标是把A的逆矩阵求出来然后作用到观测上。求逆听上去简单问题在于A和s都未知方程的解有无穷多组。ICA 靠的就是一条额外约束源信号之间统计独立。即使加上独立性假设解也不是唯一的而是只在一个“等价类”里唯一——分离出来的分量顺序可以任意交换幅度可以任意缩放甚至整体乘一个负数也不改变独立性。很多人在这一步犯的错是以为自己拿到的是“第 1 个说话人”“第 2 个说话人”其实算法给的是“分量 A”“分量 B”谁是谁要靠后续匹配。排列歧义和尺度歧义这两个问题根源就在这一步后面频域处理时还会再出现一次。中心化是第一道处理把每路观测减去它自己的均值让数据零均值。ICA 模型里源信号默认零均值如果观测带直流偏置非高斯性度量会被这个偏置污染迭代可能收敛到一个只反映直流分量的方向上。这一步看起来简单却常被人漏做尤其在从 WAV 文件读进来直接转浮点的时候PCM 的直流偏置往往还在。白化是第二道也是最关键的一道。白化就是找一个线性变换V让变换后的数据协方差矩阵变成单位阵cov(x) E D E^T # 特征值分解E 是特征向量矩阵D 是特征值对角阵 V D^(-1/2) E^T z V x # 此时 E[z z^T] I白化之后有个非常重要的性质新的混合矩阵A V A是正交矩阵在源数等于通道数、源本身已白化的前提下。这意味着原本需要估计n²个参数的混合矩阵现在只需要估计一个旋转角度参数自由度从 4 降到 1两通道情形。FastICA 的收敛速度之所以快白化占了很大功劳。白化还顺带完成了降维。实际工程里麦克风数量经常多于声源数量比如 4 个麦、2 个人说话这时候协方差矩阵有 4 个特征值只保留最大的 2 个剩下的丢弃既降维也去掉了部分噪声子空间。预处理步骤数学操作起的作用漏做的典型后果中心化每通道减均值去掉直流偏置满足零均值假设迭代收敛到直流方向分离结果全是低频嗡声白化特征值分解后缩放去相关、降维、把混合矩阵变正交收敛慢、两路解到同一个分量、幅度差几个数量级归一化可选每通道除 RMS平衡两路麦克风增益差异增益大的通道主导协方差弱通道信息被淹没2.2 负熵的近似公式FastICA 凭什么用四阶矩以外的东西ICA 的核心判据是“非高斯性越强越接近真实源”。度量非高斯性有几种方式最直观的是峰度四阶累积量但峰度对离群点极其敏感语音里一个爆破音就能把峰度拉爆所以 FastICA 用的不是峰度而是负熵。负熵的定义是J(y) H(y_gauss) - H(y)其中H是微分熵y_gauss是同样方差的高斯变量。高斯分布在所有同方差分布里熵最大所以负熵恒非负且只有在y是高斯分布时才为零。最大化负熵等价于最大化非高斯性。问题是微分熵需要知道概率密度函数而概率密度函数正是我们要求的东西这就出现了循环依赖。FastICA 的做法是用一个近似式绕开概率密度估计J(y) ≈ [E(G(y)) - E(G(v))]^2其中v是标准高斯变量G是一个“增长不太快”的偶函数。这个近似成立的条件是G的选择要和源分布的超高斯/次高斯性质匹配。FastICA 里的非线性函数g就是G的一阶导数常见的有三种logcosh对应的g(u) tanh(a u)exp对应的g(u) u exp(-u²/2)cube对应的g(u) u³。这三种的选择直接决定了算法对什么样的分布敏感第 4 章会展开。不动点迭代的推导值得看一眼。在约束E[(w^T x)²] 1下最大化E[G(w^T x)]写拉格朗日函数对w求导置零得到一阶条件E[x g(w^T x)] - β w 0。对这个方程用牛顿法求解并且忽略掉 Hessian 里的高阶项这一步近似是关键也是 FastICA 比普通梯度法快一个量级的原因就得到那个著名的更新式w E[x g(w^T x)] - E[g(w^T x)] w多分量情形下对W的每一行都做这个更新然后整体做一次对称正交化W ← (W W^T)^(-1/2) W防止多个分量收敛到同一个方向。对称正交化相比逐行正交化Gram-Schmidt不会让先算出的分量“欺负”后算的各分量地位平等这对分离质量影响不小。2.3 语音信号的非高斯性为什么语音既适合 ICA 又最容易翻车语音的幅度分布是典型的超高斯分布大量样本集中在零附近静音段和低能量段少数样本幅度很大浊音起始、爆破音。它的峰度为正稀疏性强这正好是 ICA 最喜欢的信号类型——非高斯性越强独立成分越容易分辨。从这一点看语音几乎是 ICA 的理想食材。问题出在“非平稳”三个字上。语音的统计特性在几十毫秒量级上就会变化一个音节从清音到浊音分布形状完全不一样。而标准 FastICA 假设整个过程统计量恒定把几秒钟的语音整段丢进去做时域 ICA相当于用一个平均的混合矩阵去解释所有时刻结果往往是所有音素糊成一团。这就是工程上几乎不做时域 ICA、而要做频域 ICA 的根本原因短时傅里叶变换之后每个频点内的信号在几十毫秒的帧长里可以近似看作平稳。第二个麻烦是静音段。两个人说话总有停顿静音帧的能量极低协方差矩阵在这些帧上接近奇异。如果整段一起算协方差静音帧会把特征值谱拉得很扁白化矩阵的条件数变得极大数值上就是一个定时炸弹。常见的处理是先做语音活动检测或者在协方差估计时对每帧能量加权把静音帧的权重压下去。第三个麻烦是混响。x A s假设的是瞬时混合也就是声源到麦克风只有直达声、没有反射。真实房间里声音要经过墙壁、桌面、天花板的多次反射传递函数变成一个带延迟的卷积核模型变成x(t) Σ A_τ s(t - τ)。瞬时 ICA 处理卷积混合在混响时间超过 100 ms 的房间里基本失效这也是一条硬边界。3. 从 40 行 numpy 到双麦分轨FastICA 语音分离的最小可跑通流程3.1 白化函数与不动点迭代核心代码逐段拆时域版本不是为了直接用在语音上而是为了把算法骨架跑通、验证白化和迭代是否正确。40 行左右的 numpy 代码就够不依赖任何 ICA 库。import numpy as np def center(X): 按通道去均值X: (n_ch, n_sample) return X - X.mean(axis1, keepdimsTrue) def whiten(X, n_compNone, eps1e-8): 特征值分解白化n_comp 指定保留几个主成分 cov X X.T / X.shape[1] d, E np.linalg.eigh(cov) idx np.argsort(d)[::-1] # 特征值从大到小 d, E d[idx], E[:, idx] if n_comp is not None: d, E d[:n_comp], E[:, :n_comp] D np.diag(1.0 / np.sqrt(d eps)) # 加 eps 防止除零 return D E.T X def _fp_update(W, X, gfun): 不动点更新一步W: (n_comp, n_ch) WX W X # (n_comp, n_sample) gx, gp gfun(WX) return (gx X.T) / X.shape[1] - gp.mean(axis1)[:, None] * W def fastica(X, n_compNone, gfunNone, max_iter500, tol1e-4, seed0): 对称 FastICA返回分离矩阵 W 与分量 S Xc center(X) Z whiten(Xc, n_comp) n Z.shape[0] rng np.random.default_rng(seed) # 随机正交初始化比单位阵初始化更不容易卡在退化点 W np.linalg.qr(rng.standard_normal((n, n)))[0] for it in range(max_iter): W_old W.copy() W _fp_update(W, Z, gfun) # 对称正交化W - (W W^T)^(-1/2) W s, U np.linalg.eigh(W W.T) W U np.diag(1.0 / np.sqrt(s)) U.T W # 收敛判据新 W 与旧 W 对应行内积的绝对值接近 1 lim np.max(np.abs(np.abs(np.diag(W W_old.T)) - 1)) if lim tol: break return W, W Z, it这段代码的核心只有三块。whiten里用np.linalg.eigh而不是svd因为协方差矩阵是对称的eigh保证特征值实数、特征向量正交数值更稳。_fp_update直接对应 2.2 节的更新式gx X.T / T是E[x g(w^T x)]的样本估计gp.mean(axis1)是E[g(w^T x)]的样本估计。fastica里的对称正交化必须在每轮迭代后做否则两个分量会往同一个方向跑。参数说明n_comp不传时默认等于通道数当麦克风数大于声源数时应显式传声源数这样白化阶段就完成了降维。tol取1e-4是经验值取太小会白烧迭代次数取太大会提前停在一个没收敛的位置。max_iter给 500 足够正常情况下 2050 轮就收敛跑到 500 轮还没停说明数据有问题。seed固定随机初始化方便复现——FastICA 对初始化敏感换一个随机种子结果可能不一样这是它的固有毛病。非线性函数gfun单独抽出来返回(g(x), g(x))两个值def g_logcosh(a1.0): def f(u): gx np.tanh(a * u) return gx, a * (1 - gx ** 2) return f def g_exp(): def f(u): e np.exp(-u ** 2 / 2) return u * e, (1 - u ** 2) * e return f def g_cube(): def f(u): return u ** 3, 3 * u ** 2 return flogcosh里的a一般取 1取值越大对超高斯越敏感但太大容易在迭代初期发散。exp对超高斯更激进收敛快但稳定性差。cube只适合源分布峰度为正且数值范围受控的情况语音幅度一旦没归一化就会溢出。3.2 频域 FastICA为什么要在每个频点上单独解一次语音做 ICA 必须转到频域。做法是分帧做短时傅里叶变换把每个频点当成一个独立的小 ICA 问题在该频点上观测是X[k, t]两路麦克风、t是帧索引模型是X[k, t] A_k S[k, t]混合矩阵A_k逐频点不同但同一频点内假设不变。频域信号是复数标准 FastICA 要扩展到复值。复值情形下的固定点更新式是w E[x (w^H x)* g(|w^H x|²)] - E[g(|w^H x|²) |w^H x|² g(|w^H x|²)] w取g(u) u对应复值下的峰度型判据第二项简化为2 E[|w^H x|²] w实现起来最省事也是工程里用得最多的形式。def complex_fastica(X, max_iter200, tol1e-5, seed0): X: (n_mic, n_frame) 复数矩阵返回 W 与分离结果 Xc X - X.mean(axis1, keepdimsTrue) # 复白化cov X X^H / THermitian 矩阵用 eigh cov Xc Xc.conj().T / Xc.shape[1] d, E np.linalg.eigh(cov) D np.diag(1.0 / np.sqrt(d 1e-10)) Z D E.conj().T Xc n Z.shape[0] rng np.random.default_rng(seed) W np.linalg.qr(rng.standard_normal((n, n)) 1j * rng.standard_normal((n, n)))[0] for it in range(max_iter): W_old W.copy() Y W Z p np.abs(Y) ** 2 # 复值不动点更新g(u)u 的情形 W (Y.conj() * p) Z.conj().T / Z.shape[1] \ - 2 * p.mean(axis1)[:, None] * W s, U np.linalg.eigh(W W.conj().T) W U np.diag(1.0 / np.sqrt(s)) U.conj().T W lim np.max(np.abs(np.abs(np.diag(W W_old.conj().T)) - 1)) if lim tol: break return W, W Z这段代码和时域版的差别集中在三处协方差用X X.conj().T白化矩阵用E.conj().T正交化用W W.conj().T。收敛判据里的W W_old.conj().T也不能漏掉共轭否则判据会算出一个复数np.abs之后意义就变了。频点循环要跳过能量极低的频点。语音能量集中在 100 Hz4 kHz16 kHz 采样下对应的频点索引大概在 6256 之间nperseg1024时每个频点 15.6 Hz。低于 100 Hz 的频点基本是工频干扰和空调噪声高于 7 kHz 的频点能量低、信噪比差逐点做 ICA 出来的结果纯属噪声直接置零或者复制参考通道更省事。这一步能省掉近一半计算量。帧数也有限制。一个频点上的“样本”就是帧数2 秒音频、16 ms 帧移也就 125 帧。用 125 个复样本估计 2×2 的协方差矩阵勉强够用如果只有几十帧估计出来的白化矩阵会非常不稳分离结果随机性极大。这是频域 ICA 对短音频不友好的原因也是很多人拿 1 秒素材试出来效果很差的原因。3.3 频点对齐与 ISTFT 重组把 513 个小结果拼成两段音频每个频点独立做完 ICA 之后直接逆变换会得到一堆废音因为每个频点的分量顺序和尺度都是任意的。假设第 10 个频点把说话人 A 分到了第 1 行第 11 个频点可能分到了第 2 行这样拼起来的频谱在频点交界处会突然交换听感上就是每隔十几赫兹跳一下的“电子音”。尺度问题同样要处理。ICA 的分离结果Y W X里Y的幅度是任意的可能第 1 个频点幅度是 0.1第 2 个频点是 100。常见的解决方式是投影回参考麦克风算出A_k pinv(W_k)然后Y_i[k] ← Y_i[k] * A_k[ref_mic, i]。这一步的物理含义是把分离出的第 i 个源按它在参考麦克风上的实际贡献重新定标这样各频点的尺度就一致了同时也顺带解决了正负号问题。排列对齐的常用做法是基于相邻频点的包络相关性。同一个说话人在相邻频点上的幅度包络变化趋势是相似的而不同说话人之间相关性低所以逐频点做一次匹配就能把顺序串起来。import numpy as np from scipy.optimize import linear_sum_assignment def align_permutation(Y): Y: (n_src, n_freq, n_frame)逐频点对齐到前一频点的顺序 n_src, n_freq, _ Y.shape env np.abs(Y) for k in range(1, n_freq): C np.zeros((n_src, n_src)) for i in range(n_src): for j in range(n_src): a, b env[i, k - 1], env[j, k] if a.std() 1e-8 or b.std() 1e-8: C[i, j] 0.0 continue C[i, j] np.corrcoef(a, b)[0, 1] # 匈牙利算法求最大权匹配取负号转成最小化 row, col linear_sum_assignment(-C) Y[:, k, :] Y[col, k, :] return Ylinear_sum_assignment返回的col[i]是匹配到第i行的当前频点分量索引Y[col, k, :]就是按这个索引重排。相关性计算前要判断标准差静音频点的包络几乎是常数相关系数会算出 NaN这一步不做判断整个频谱都会被 NaN 污染。对齐完成之后就可以逐源做逆短时傅里叶变换得到两段时域音频。整个流程串起来大概是读入双通道 WAV → 逐通道 STFT → 逐频点复值 FastICA → 尺度归一 → 排列对齐 → 逐源 ISTFT。提示对齐只能保证相邻频点顺序一致如果某个频点分离本身失败两路解到同一个源错误会沿着频点一路传播下去。实践中会在对齐后加一道检查如果某个频点的匹配相关系数普遍低于 0.3就把这个频点的结果丢弃用相邻频点插值补上。4. 参数与硬件怎么配g 函数选型、收敛阈值与麦克风间距4.1 g 函数三选一logcosh、exp、cube 分别在什么信号上更稳三种非线性函数对应三种不同的分布假设选错了不是精度差一点而是直接不收敛。g 函数表达式匹配的分布语音场景表现logcoshtanh(a u)a 常取 1超高斯、次高斯都适用默认首选数值最稳收敛稍慢expu exp(-u²/2)强超高斯收敛快静音段多时容易震荡cubeu³峰度为正且幅度受控必须严格归一化否则溢出语音是超高斯分布理论上exp最匹配它的负熵近似对稀疏信号更敏感。但实际跑下来exp在帧级数据上经常震荡原因是某一帧突然出现一个大幅值样本u exp(-u²/2)在u很大时反而趋近于零梯度信号消失迭代就停在原地。logcosh的tanh在u大时饱和到 ±1梯度不会消失鲁棒性明显好一截所以大多数实现默认用logcosh。cube的问题更直接。它对大幅值样本的惩罚是三次方增长如果输入没有做幅度归一化第一次迭代就可能算出1e10量级的数然后整个矩阵变成 NaN。用cube的前提是每个频点的观测先除以其 RMS并且加一道np.clip兜底。即便如此遇到爆破音还是容易炸。logcosh里的a参数一般不动保持 1.0。把它调到 2.0 会让tanh更陡对超高斯更敏感但收敛区间变窄。我的习惯是先按 1.0 跑通确认分离有效之后再试 2.0看 SDR 有没有提升没有就退回去。4.2 tol 与 max_iter收敛参数的取值和该看哪个量来判断tol控制的是收敛判据max | |diag(W W_old^T)| - 1 |物理含义是新旧分离矩阵对应行的内积绝对值有多接近 1。取1e-4时分离矩阵基本稳定取1e-6能多榨出一点精度但迭代次数可能翻倍而 SDR 提升往往不到 0.1 dB性价比很低。max_iter给 200 对频域 ICA 足够。真正需要关注的不是迭代上限而是有多少频点跑满了上限还没收敛。正常情况下 90% 以上的频点应该在 30 轮内收敛。如果跑满上限的频点超过三成说明数据有问题要么帧数太少要么白化矩阵条件数太大要么g函数选错。加一行统计把未收敛频点数量打出来比调max_iter有用得多。n_not_converged 0 for k in range(k_lo, k_hi 1): Wk, Yk complex_fastica(Z[:, k, :], max_iter200, tol1e-5) if not converged_flag: # complex_fastica 内部返回是否收敛 n_not_converged 1 print(f未收敛频点: {n_not_converged}/{k_hi - k_lo 1})样本数这个参数没法直接调它由音频时长决定。经验值是每个频点至少 200 帧低于这个数就把帧移调小比如从 256 降到 128用重叠换样本量。代价是帧间相关性变强独立性假设被削弱属于两害相权取其轻。4.3 麦克风间距与采样率物理条件不满足时算法再好也白搭FastICA 能不能分开两个人很大程度上在录音那一刻就决定了。麦克风间距是最关键的物理参数。间距太小两路观测几乎是同一路信号混合矩阵接近奇异白化之后两个特征值一大一小小的那个被eps吃掉实际上只剩一个有效通道分离必然失败。间距太大超过语音最高频率的半波长会出现空间混叠两个源的相对延迟在频域上绕圈符号翻转对齐算法也救不回来。宽带语音300 Hz7 kHz的常用折中是 48 cm。这个间距下7 kHz 对应的半波长约 2.4 cm不会混叠同时两路信号又有足够的差异混合矩阵条件数健康。间距超过 15 cm高频段的相位差会超过 π频域 ICA 在那些频点上会解出反相的分量听起来就是高频段左右互换。采样率建议 16 kHz。8 kHz 会丢掉 4 kHz 以上的信息语音的可懂度受影响但对 ICA 本身没坏处32 kHz 及以上会让频点数翻倍计算量成倍增长而语音能量在 8 kHz 以上已经很少多出来的频点基本在做无用功。参数推荐取值取值理由偏离后的后果麦克风间距48 cm兼顾通道差异与空间混叠太小混合矩阵病态太大高频反相采样率16 kHz覆盖语音主要能量带8 kHz 丢可懂度32 kHz 徒增算力帧长1024 点64 ms短于常见混响时间量级太长卷积效应显现太短频率分辨率不足帧移256 点16 ms75% 重叠保证帧数充足太小帧间相关强太大样本数不够有效频点100 Hz7 kHz避开工频与高频噪声全频点都做一半算力浪费在噪声上帧长的选择还要看房间。混响时间 200 ms 的会议室64 ms 的帧长勉强能撑住瞬时混合假设混响 500 ms 的大厅帧长压到 32 ms 也救不回来因为反射声的延迟跨度远超一帧。这不是参数问题是模型边界问题。5. FastICA 语音分离的 5 个常见坑与排查顺序5.1 输出全是噪声、或者一路静音先查白化和通道数现象跑完整个流程两路输出里一路是持续的沙沙声另一路几乎静音或者两路都是噪声完全听不出人声。原因通常出在白化阶段。如果协方差矩阵的特征值差异极大比如两路麦克风增益差 20 dB最小特征值接近零1/sqrt(d)会算出巨大的数白化后的数据被放大到1e6量级后续迭代直接溢出分离矩阵变成随机噪声。另一种情况是n_comp传错本来是 2 个源传了 1白化只保留一个主成分输出自然只有一路有效。解决打印白化前后的协方差特征值看条件数。条件数超过1e4就先做通道增益归一化每通道除 RMS再白化。检查n_comp是否等于声源数而不是麦克风数。另外确认读入的音频确实是多通道有些 WAV 读进来是(n_sample, n_ch)而不是(n_ch, n_sample)转置漏了会导致白化在时间轴上做结果全是垃圾。5.2 两路分离结果几乎一样初始化与对称正交化的问题现象两路输出听起来是同一段音频波形高度相似相关系数超过 0.9。原因有两个。一是初始化用了单位阵W I两个分量从相同方向出发在对称性强的数据上会收敛到同一个解。二是对称正交化漏做或者写错比如把W W.T写成了W.T W正交化的对象错了两个分量无法保持分离。解决初始化用随机正交矩阵也就是np.linalg.qr(randn(n, n))并且在文档里记下随机种子方便复现。对称正交化的公式核对一遍(W W^T)^(-1/2) W其中W是(n_comp, n_ch)W W^T是n_comp × n_compeigh出来的特征向量矩阵U作用在左边。如果两路相关系数还是高检查是不是n_comp设成了 1 之后又强行拆成两路。5.3 频谱图上出现“音乐噪声”排列歧义与帧长的连锁反应现象分离结果的频谱图上有大量细碎的横条纹听感上像有人在快速切换声道或者像金属刮擦的“音乐噪声”。原因几乎都是排列没对齐。每个频点独立做 ICA顺序随机如果投影回参考麦的尺度归一做对了、但对齐漏做相邻频点的分量会随机交换重组成时域信号后就是频点级别的跳变。另一种情况是对齐做了但某个频点的分离结果本身是错的两路解到同一源匹配相关系数很低匈牙利算法强行配了一个错误的结果错误顺着频点传播。解决先确认对齐函数真的被执行了打印几个频点的匹配相关系数正常应该在 0.6 以上。如果某段频点的相关系数普遍低于 0.3把这个频点标记为失败用左右邻频点的结果插值替代。帧长也要复查64 ms 帧在混响较强的房间里会让相邻频点的相关性下降对齐难度上升可以试 32 ms 帧长代价是频率分辨率降低。5.4 混响房间里彻底失效卷积混合超出瞬时模型边界现象在消声室或者近场录音里效果不错换到普通会议室分离度明显下降两个人声还是混在一起只是比例变了。原因不是参数没调好是模型假设不成立。x A s是瞬时混合真实房间是卷积混合声音经过多次反射到达麦克风每个频点的传递函数不仅有幅度差还有相位差而且相位随频率快速旋转。瞬时 ICA 在每个频点上估计的是一个复数标量理论上能吸收单一路径的延迟但多路径叠加之后同一个源在不同频点上的“混合方式”不再一致逐频点独立求解就失去了统一性。解决接受边界比硬调参数更实际。混响时间 100 ms 以内的小房间频域 ICA 加对齐还能用200 ms 以上需要换成能处理卷积的模型或者先做去混响预处理谱减、加权预测误差之类把尾巴压短再送进 ICA。把麦克风挪近声源近场录音是最便宜有效的办法直达声占比一高瞬时假设就成立得多。5.5 NaN 与数值溢出中心化漏做、尺度失衡的典型症状现象迭代跑了几轮之后W里出现nan或inf分离结果全是零或者全是nan程序不报错但输出无效。原因有三个层次。最表层是cube函数配了没归一化的数据三次方直接把数值推到浮点上限。中间层是中心化漏做数据带直流偏置tanh饱和之后梯度全为零正交化时W W^T接近奇异1/sqrt(s)算出inf。最深层是白化时eps加得太小特征值里有一个接近1e-12开方倒数之后放大到1e6后续所有运算都在悬崖边上。解决按顺序排查。第一确认center在whiten之前被调用。第二eps从1e-8提到1e-6牺牲一点白化精度换数值稳定实际听感几乎没差别。第三如果必须用cube在_fp_update里对gx加np.clip(gx, -1e3, 1e3)。第四在迭代循环里加一个断言np.isfinite(W).all()为假就立刻退出并打印当前迭代轮次别让它静默跑完 500 轮。6. 别只靠耳朵验收SDR 打分、排列对齐修复与改造边界6.1 SDR 的计算方式与及格线判断靠耳朵听只能判断“有没有分开”判断不了“分开了多少”。SDR信号失真比是最常用的客观指标计算方式是把估计信号投影到参考信号上投影系数之外的成分都算误差import numpy as np def sdr(est, ref): 单源 SDRest/ref: (T,) alpha np.dot(est, ref) / (np.dot(ref, ref) 1e-12) target alpha * ref err est - target if np.dot(err, err) 1e-12: return 120.0 return 10 * np.log10(np.dot(target, target) / np.dot(err, err))两源场景不能只算单源 SDR因为估计里混着另一个源的能量要把干扰项单独拆出来分别算 SIR信号干扰比和 SAR信号伪影比。完整的分解需要把估计投影到“目标源 干扰源”张成的子空间上实现起来比上面这段长不少工程里常用现成的盲源分离评估实现输入参考信号和估计信号直接输出三项指标。及格线怎么定双麦、近场、混响 100 ms 以内SDR 提升 58 dB 算可用提升 3 dB 以下基本没实用价值因为原始混合信号本身就有 0 dB 左右的基线。消声室条件下做到 10 dB 以上是可能的但别拿这个数去对标真实房间。6.2 排列对齐的一个实用做法包络相关性 匈牙利匹配3.3 节的逐频点对齐有个弱点错误会沿着频点单向传播。更稳的做法是用全频段的平均包络做一次初对齐再逐频点修正。def align_two_stage(Y, n_src2): Y: (n_src, n_freq, n_frame) env np.abs(Y) # 第一阶段用全频段平均包络作为参考模板 ref_env env.mean(axis1) # (n_src, n_frame) for k in range(env.shape[1]): C np.zeros p a hrefhttps://download.csdn.net/download/hdg34jk/6530605 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p