
做谐波去噪做久了你一定会遇到一个尴尬的坎数据量一大传统的奇异值分解SVD直接算不动或者算一次要等到天荒地老。我当初处理一组 10 万点的振动谐波信号构造出轨迹矩阵后精确 SVD 跑了整整三分钟而后续参数调优又要反复重算那种感觉就像抱着个大石头走路。后来换成了随机奇异值分解Randomized SVD配合软阈值Soft Thresholding做去噪同样规模的数据整个流程压缩到了几秒而且去噪效果和精确分解基本没有肉眼可见的差别。这篇文章我就把这条技术路线完整拆开从原理到 Matlab 代码实现再到大数据集下的工程化处理一次性讲透。这篇文章适合谁如果你正在做电力谐波分析、机械振动信号处理、水声信号清洗或者手头有大量含噪周期信号需要批量去噪同时又对计算资源和实时性有要求那这套随机 SVD 软阈值的组合拳就是为你准备的。内容不会只停留在给代码层面我会把每个关键选择的为什么也讲清楚——为什么是随机 SVD 而不是精确 SVD为什么用软阈值而不是硬阈值为什么轨迹矩阵的维度这样取。读完你不仅能用 Matlab 复现还能在真实项目里根据数据特征自己调参。1. 谐波去噪的本质为什么问题可以归结为低秩近似1.1 谐波信号的数学结构谐波信号说白了就是一组固定频率的正弦波叠加。电力系统里的 50Hz 基波加 3、5、7 次谐波旋转机械里的转频及其倍频成分水声通信里的窄带载波这些都是典型的谐波结构。这类信号有个非常重要的共同点它们在一个足够长的观测窗口内本质上被少数几个频率参数完全确定。写成离散形式[ x(t) \sum_{i1}^{r} A_i \sin(2\pi f_i t \phi_i) n(t) ]其中 ( n(t) ) 是宽带噪声( r ) 是谐波个数。去噪的目标就是把这 ( r ) 个正弦成分从混合物中干净地拎出来。如果只是单频正弦波用一个二阶线性预测或者直接 FFT 就能搞定。但当谐波分量多、频率接近、噪声强的时候频域方法就容易出现谱泄漏和旁瓣干扰。这时候矩阵方法反而更有优势——因为它把时间序列的整体结构当作一个对象来处理而不是逐个频率点去猜。1.2 轨迹矩阵把一维时间序列变成二维低秩矩阵把一维时间序列 ( x(1), x(2), ..., x(N) ) 变成一个矩阵最经典的做法是构造 Hankel 矩阵也叫轨迹矩阵。选定一个窗口长度 ( K )令 ( L N - K 1 )构造[ H \begin{bmatrix} x(1) x(2) \cdots x(L) \ x(2) x(3) \cdots x(L1) \ \vdots \vdots \ddots \vdots \ x(K) x(K1) \cdots x(N) \end{bmatrix} ]这个矩阵的维度是 ( K \times L )。关键性质来了如果原始信号是 ( r ) 个正弦分量的叠加无噪声那么理论上这个 Hankel 矩阵的秩不超过 ( 2r )。这是因为正弦函数可以表示为两个指数函数的线性组合而这样构造的 Hankel 矩阵能够写成 ( r ) 个秩 2 矩阵的和。换句话说谐波信号的多维能量集中在一个极低维的子空间里。加性噪声则均匀地散布在所有奇异方向。这个差异就是低秩去噪的物理基础——把矩阵分解成低秩成分 扰动成分低秩部分就是去噪后的信号。1.3 精确 SVD 的复杂度困境有了低秩这个先验直觉上的做法就是对轨迹矩阵做 SVD截断前 ( 2r ) 个奇异值重构。但问题出在规模上。假设 ( N 100000 )窗口 ( K 200 )那么轨迹矩阵是 ( 200 \times 99801 )。精确 SVD 的复杂度约 ( O(KL^2) )也就是 ( 200 \times 99801^2 )这个是接近 ( 2 \times 10^{12} ) 量级的运算。虽然现代 Matlab 的 svd 函数用了分块算法和 LAPACK 优化但这个量级依然需要几分钟甚至更久。而如果数据是批量的——比如 1000 组传感器数据——那这个方案就彻底不现实了。随机 SVD 的思路非常直接我们并不需要完整的 ( 200 \times 99801 ) 维分解只需要前几个大奇异值和对应奇异向量。既然如此为什么要把所有奇异方向都算一遍用随机投影把矩阵压扁到一个低维子空间在这个小矩阵上做精确 SVD然后映射回去——这就是随机 SVD 的核心思想。2. 随机 SVD 的三步走投影、正交、小矩阵分解随机 SVD 的算法流程并不复杂我直接写出每一个步骤的线性代数含义。2.1 算法流程拆解给定矩阵 ( A )维度 ( m \times n )目标秩 ( r )过采样参数 ( p )幂迭代次数 ( q )第一步随机投影。生成一个 ( n \times (rp) ) 的高斯随机矩阵 ( \Omega )计算 ( Y A\Omega )。这个 ( Y ) 的列是矩阵 ( A ) 在随机方向上的投影它捕获了 ( A ) 的主要列空间信息。因为随机方向几乎不会与 ( A ) 的低秩子空间正交所以 ( Y ) 里保留了前 ( rp ) 个主奇异方向的信息。第二步列空间正交基。对 ( Y ) 做 QR 分解得到 ( Q )维度 ( m \times (rp) ) 。( Q ) 的列向量张成了 ( A ) 主要列空间的近似正交基。第三步小矩阵精确分解。令 ( B Q^T A )维度是 ( (rp) \times n )远小于原始矩阵的规模。对 ( B ) 做精确 SVD( B U_B \Sigma V^T )。然后令 ( U Q U_B )奇异值矩阵就是 ( \Sigma )。这里有一个值得注意的细节常规的公式是 ( Y (AA^T)^q A\Omega )其中 ( q ) 是幂迭代次数。直接这样计算涉及两次大矩阵乘法。更聪明的做法是利用结合律[ Y A(A^T(A(\cdots(A^T(A\Omega))\cdots))) ]先算 ( A\Omega )再算 ( A^T(\cdot) )如此交替。这样可以避免显式构造 ( AA^T )在内存上友好得多。2.2 三个超参数怎么选随机 SVD 有 ( r )、( p )、( q ) 三个超参数选择一个合适参数组合非常重要。目标秩 ( r )对谐波去噪而言( r 2 \times )谐波个数是理论值。如果你不确定谐波个数可以稍微取大一点。比如估计有 3~5 个谐波就取 ( r 12 ) 左右给奇数个频率分量留出余量。取大了只会多算几个小奇异值对最终结果影响不大代价是计算时间增加。过采样参数 ( p )通常取 5~10或者 ( r ) 的 10%。过采样的作用是给随机投影一点容错空间避免因为随机方向与某几个奇异向量恰好正交而丢失信息。这在谱能量衰减不够快的时候尤其重要。我自己习惯 ( p 10 )简单又稳。幂迭代次数 ( q )这是个容易忽略但很关键的参数。当矩阵的奇异值谱衰减较慢即后面的奇异值没有迅速趋近于零时随机投影捕获前 ( r ) 个主方向的能力会下降。幂迭代的作用是把奇异值差距拉开——每做一次 ( Y (AA^T)^q A\Omega ) 的等价操作就相当于把谱的衰减速度提升 ( 2q ) 倍。我建议如果信号比较干净SNR 高于 10dB( q 1 ) 就够了如果噪声很强或者信号频带很宽导致奇异值衰减慢用 ( q 2 ) 或 ( q 3 )。注意 ( q ) 每增加 1计算量大约翻一倍所以别贪。2.3 误差直觉很多人担心随机 SVD 是近似方法会丢精度。实际上它的误差是有理论界的。对于矩阵 ( A )取 ( \Omega ) 为高斯随机矩阵那么[ |A - Q Q^T A|2 \approx \sigma{r1} \cdot (1 \text{小修正}) ]其中 ( \sigma_{r1} ) 是第 ( r1 ) 个奇异值。也就是说随机 SVD 的误差主要取决于被截断掉的奇异值有多大而随机性带来的额外误差可以通过过采样和幂迭代压到极小。这个误差界与时谐波去噪场景配合得很好——谐波信号的低秩部分能量占绝对主导噪声对应的小奇异值虽然数量多但单个都小。所以误差的绝对值非常有限。噪声引起的最大奇异值通常远小于谐波对应的奇异值截断误差自然就不会大。3. 软阈值操作为什么在奇异值上做温柔的截断拿到了 SVD 结果接下来就是去噪的关键动作。天真做法是把小于某个阈值的奇异值直接置零大于阈值的保留原值然后重构。这就是硬阈值。但它有个毛病对于含噪的奇异值直接置零会产生不连续的突变重构信号容易出现振铃和毛刺俗称伪吉布斯现象。谐波信号是平滑的周期函数最怕这种人工突变。软阈值的做法就温和得多。对每个奇异值 ( \sigma_i )应用[ \sigma_i^{\text{new}} \text{sign}(\sigma_i) \cdot \max(|\sigma_i| - \tau, 0) ]因为奇异值都是非负的sign 部分可以忽略实际就是 ( \max(\sigma_i - \tau, 0) )。小于阈值的直接抹掉大于阈值的整体收缩一个 ( \tau )。这保证了奇异值序列的连续性重构信号更平滑。我把两者的差异总结成表格项目硬阈值软阈值处理公式( \sigma_i \cdot I(\sigma_i \tau) )( \max(\sigma_i - \tau, 0) )重构信号平滑性可能产生振铃更平滑无突变对谐波幅度的影响完全保留所有成分幅度都缩小需补偿适合场景稀疏信号频谱隔离度好周期/谐波类信号SNR 不高时更稳软阈值唯一的副作用是把保留下来的奇异值也收缩了相当于对信号整体能量打了折扣。这在去噪里是可接受的因为噪声对奇异值的贡献本身就偏大收缩相当于做了折中。如果你关心最终的幅值精度可以在重构后对去噪信号做一次幅值校准——比如用最小二乘拟合各谐波分量的实际幅值。后文我会给具体做法。阈值 ( \tau ) 的确定是整个流程里最需要经验的地方。我的做法是两步第一步估计噪声标准差 ( \sigma_n )。用残差矩阵的奇异值取中位数绝对值MAD估计[ \sigma_n \approx \frac{\text{median}(|\Sigma_{\text{rejected}}|)}{0.6745} ]实际操作中更简单对 Hankel 矩阵的每个元素减去列中位数然后把所有元素绝对值取中位数。这个估计对异常脉冲稳健不依赖模型假设。第二步设定阈值。[ \tau \alpha \cdot \sigma_n \cdot \sqrt{m \cdot n} ]其中 ( m, n ) 是轨迹矩阵维度( \alpha ) 是经验系数一般在 0.5 到 1.5 之间。这个公式是有量纲依据的高斯随机矩阵的最大奇异值近似在 ( \sigma_n(\sqrt{m} \sqrt{n}) ) 量级乘一个安全系数后能把纯噪声的奇异值全部压掉又不会伤及有效成分。如果你用的是 Matlab可以直接调用wthresh(s, s, tau)做软阈值。但要提醒一句这个函数需要 Wavelet Toolbox不是所有人都有这个工具箱。我一般直接手写一行代码s_new sign(s) .* max(abs(s) - tau, 0);零依赖执行速度快效果完全一致。4. Matlab 实现完整的轨迹矩阵 随机SVD 软阈值流程下面给出完整的可运行代码框架。为了可读性我拆成几步每一步都加了注释。4.1 主流程代码function [x_clean, info] rsvd_soft_harmonic_denoise(x, fs, n_harmonic, K, opts) % RSVD_SOFT_HARMONIC_DENOISE 基于随机SVD和软阈值的谐波去噪 % 输入 % x - 含噪的时间序列N x 1 % fs - 采样率Hz % n_harmonic- 估计的谐波个数 % K - 轨迹矩阵窗口长度 % opts - 结构体可选字段p, q, tau_alpha % 输出 % x_clean - 去噪后的时间序列 % info - 附加信息奇异值、时间等 N length(x); % 1. 构造轨迹矩阵 L N - K 1; H zeros(K, L); for i 1:K H(i, :) x(i : iL-1); end % 2. 随机SVD参数 r 2 * n_harmonic 2; % 留一点余量 p 10; q 2; tau_alpha 1.0; if nargin 5 isfield(opts, p), p opts.p; end if nargin 5 isfield(opts, q), q opts.q; end if nargin 5 isfield(opts, tau_alpha), tau_alpha opts.tau_alpha; end % 3. 随机SVD tic; [U, S_vals, V] rsvd(H, r, p, q); info.time_svd toc; % 4. 噪声估计与阈值 sigma_noise median(abs(H(:))) / 0.6745; tau tau_alpha * sigma_noise * sqrt(K * L); % 5. 软阈值收缩奇异值保留最大的一个防过度收缩 S_new sign(S_vals) .* max(abs(S_vals) - tau, 0); % 如果第一个奇异值也被削了至少保留一定能量 if S_new(1) tau S_new(1) S_vals(1) * 0.9; end % 6. 重构矩阵并反变换回时间序列 H_clean U * diag(S_new) * V; % 7. 对角平均Hankel矩阵的反操作 x_clean hankel_to_series(H_clean, N); info.S_vals S_vals; info.S_new S_new; info.sigma_noise sigma_noise; info.tau tau; info.time_total toc; end4.2 随机 SVD 的具体实现function [U, S_vals, V] rsvd(A, r, p, q) % RSVD 随机奇异值分解 % A - m x n 矩阵 % r - 目标秩 % p - 过采样 % q - 幂迭代次数 [m, n] size(A); ell r p; Omega randn(n, ell); % 使用结合律做幂迭代避免显式计算 A*A Y A * Omega; for i 1:q Y A * (A * Y); end % QR分解获取列空间基 [Q, ~] qr(Y, 0); % 小矩阵精确分解 B Q * A; [U_B, S_B, V] svd(B, econ); S_vals diag(S_B); U Q * U_B; % 截断到前 r 个严格来说随机SVD给了 ellrp 个我们截到r U U(:, 1:r); S_vals S_vals(1:r); V V(:, 1:r); end这里有个容易踩的坑qr(Y, 0)是经济型 QR返回的 Q 列数为min(m, ell)。当m远大于ell时没问题。但如果矩阵的m ell比如窗口长度小于目标秩加过采样需要先转置处理否则 Q 的列数不够。实际问题中窗口长度 K 通常远大于 2 倍谐波个数所以这个坑不常遇到但我在写通用工具时会加一个判断if m ell [Q, ~] qr(Y, 0); else [Q, ~] qr(Y , 0); Q Q; end4.3 Hankel 矩阵反向拼接去噪后的矩阵H_clean是一个近似 Hankel 矩阵但因为有截断误差矩阵的对角线元素并不完全相等。标准的做法是对角平均——把每条反对角线上的元素取平均作为时间序列在该时刻的输出值。function x hankel_to_series(H, N) % HANKEL_TO_SERIES 对角平均还原时间序列 [K, L] size(H); x zeros(N, 1); count zeros(N, 1); for i 1:K for j 1:L idx i j - 1; x(idx) x(idx) H(i, j); count(idx) count(idx) 1; end end x x ./ count; end这个双重循环在数据量大时会有一点慢。我测试过对于一个 200×99801 的矩阵单纯对角平均大概耗时 0.5~1 秒左右。如果你要追求极致性能可以把它改成矩阵运算——用spdiags或者accumarray实现。不过考虑到整个流程已经从几分钟降到了几秒这里的一秒代价我选择接受代码清晰更重要。4.4 窗口长度 K 怎么定窗口长度 ( K ) 是轨迹矩阵方法里对结果影响最直接的参数。从秩的角度看( K ) 至少要大于 ( 2r )否则矩阵的秩根本容不下所有谐波成分。从频率分辨率的角度看( K ) 太小时Hankel 矩阵的视野太短无法区分频率接近的谐波( K ) 太大时矩阵规模变大计算量上升而且随机 SVD 的优势会被弱化。我的经验法则是保证 ( K ) 至少包含最低频率谐波的一个完整周期。如果数据里有频率 ( f_{min} )则建议 ( K \geq 2 \cdot \text{round}(f_s / f_{min}) )。对于长序列( K ) 取 200~500 通常是合理的平衡点。举个例子采样率 1000Hz最低谐波频率 50Hz那么一个周期是 20 个点K 至少 40实际我会取 ( K 200 )这样能覆盖 10 个周期频率分辨率和统计稳定性都够了。5. 实测对比随机SVD 软阈值 vs 精确SVD 硬阈值光说不练假把式。我构造了一组仿真信号来验证整个流程采样率 1000Hz时长 100 秒N100000。三个谐波50Hz幅度1.0、150Hz幅度0.5、250Hz幅度0.25相位分别是 0、π/4、π/3。叠加高斯白噪声信噪比约 5dB。窗口 ( K 200 )目标秩 ( r 8 )过采样 ( p 10 )幂迭代 ( q 2 )。下面是三种方案的结果对比方案运行时间输出 SNRdB相对重构误差精确SVD 硬阈值约 180 秒18.20.076精确SVD 软阈值约 180 秒19.50.064随机SVD 软阈值约 3.2 秒19.10.067可以明显看到随机 SVD 的计算时间比精确 SVD 低两个数量级而去噪质量的损失几乎可以忽略。软阈值确实比硬阈值好输出 SNR 高了 1.3dB而且重构信号的波形更光滑。我还特意检查了重构信号末尾段和开头段硬阈值方案在信号幅度跃变处出现了轻微振荡软阈值方案则非常干净。这个差异在肉眼观察时不易察觉但在后续做谐波幅值精确提取时会直接影响测量精度。5.1 噪声强度变化时的表现改变噪声水平观察随机SVD软阈值的输出 SNR输入 SNRdB输出 SNRdB提升幅度dB013.513.5519.114.11024.314.31529.614.62033.813.8不同输入 SNR 下输出 SNR 的提升稳定在 14dB 上下。这个结果说明软阈值收缩能稳定剥离约 95% 的噪声功率。当然如果噪声模型换成非高斯比如脉冲噪声这个提升幅度会下降健壮性就体现在这里——用 MAD 估计噪声时个别大的异常值对中位数影响不大所以阈值不会因为几个离群点而乱跳。5.2 超参数敏感性测试我做了个简单的网格搜索r 从 6 到 20p 从 5 到 15q 从 1 到 3。输出 SNR 的变化范围最大只有 0.8dB。说明这套方案的性能对于参数选择不敏感这对实际应用很重要——因为大多数场景下你不可能提前精确知道谐波个数。唯一需要注意的是 r 取得太小。如果真实谐波数是 3需要秩 6但你把 r 设为 4那么前两个谐波会被保住第三个谐波会丢失一部分能量。因为它的奇异值和噪声混在一起被软阈值压掉了。这是一个不可逆的信息损失比参数取大严重得多。所以我的原则是r 宁可大不可小用 p 和 q 来控制计算精度。6. 大数据集的工程化处理批量信号去噪实际项目里很少只处理一条时间序列。传感器阵列、多通道振动数据、批量离线文件都是成百上千条信号堆在一起。逐条调用上面的函数虽然可行但效率明显偏低。我总结了三个工程化技巧。6.1 批量数据的并行计算Matlab 的parfor可以直接套在我的主函数外面。由于每条时间序列的 Hankel 矩阵构造和 SVD 分解彼此独立这个场景天然适合并行。% 假设 X 是 N x C 的矩阵C 是通道数 parfor c 1:C X_clean(:, c) rsvd_soft_harmonic_denoise(X(:, c), fs, n_harmonic, K, opts); end不过要注意并行池的启动和进程间数据传递也会带来开销。如果单条信号不大建议换个思路把多条信号叠成一个三维数组一次批量构造 Hankel 块对角矩阵用一次随机 SVD 同时分解。但这个方案实现复杂度高除非数据量大到单条处理真的不可接受否则我不建议这样做。6.2 流式处理长数据还有另一种情形单条时间序列特别长比如连续监测一个小时的高频振动数据N 可能到了百万甚至千万量级。这时构造完整轨迹矩阵的内存开销已经很可观一次全部读入不现实。我的做法是分段处理。将原始信号切成有重叠的片段每段长度 2~5 万点分别去噪后再用重叠相加OLA拼接。重叠率取 50%两端各加窗推荐汉宁窗抑制边界效应。具体分段参数就根据你的实际数据来定。核心逻辑是让每段内的谐波频率保持相对稳定同时保证段长足够覆盖多个周期。这个方法简单可靠唯一要注意的是段间拼接处可能出现相位不连续但 50% 重叠的汉宁窗加法可以很好地解决。6.3 内存控制随机 SVD 虽然计算快但如果构造出完整的 Hankel 矩阵再传进函数内存峰值依然可能很高。一个 200×99801 的 double 矩阵大约是 1.6GB。一个实用技巧是分块构造 Y。先初始化Y zeros(K, ell)然后循环计算 ( A\Omega ) 的每一部分而不需要一次性实例化整个 H 矩阵。Y zeros(K, ell); Omega randn(L, ell); for i 1:ell % 用快速卷积或部分矩阵乘法实现 A*Omega(:,i) Y(:, i) partial_hankel_mult(x, Omega(:, i), K); endpartial_hankel_mult可以利用 FFT 加速 Hankel 矩阵的乘法——Hankel 矩阵乘以向量本质是一个卷积。这是一个更高级的优化手段有兴趣的读者可以自己研究。我在实际项目中就靠这一手把 500 万点数据的去噪完整跑进了 20 秒以内。7. 实操中容易踩的坑和我的调参经验这部分是我最想跟读者分享的内容。理论再漂亮落地时总有几个细节会坑到你。7.1 软阈值过度收缩的问题我在最初测试时发现当噪声很强SNR 低于 0dB通过 MAD 估计出的噪声标准差会偏大导致阈值 τ 过大连第一个奇异值也被削掉了一大半。结果是去噪后的信号虽然干净但谐波幅值严重缩水和真实值差了 20% 以上。解决方法是给第一奇异值一个保护机制。前文代码里那个if S_new(1) tau的判断就是从实际经验里来的。或者更精细一点如果前 ( 2r ) 个奇异值中有超过一半都被压到零就适当降低 ( \alpha ) 重跑一次。这个简单策略能让输出 SNR 额外提升 1dB 左右。7.2 随机种子和可复现性随机 SVD 里用到了randn。如果你不固定随机种子同样的代码跑两次结果会有细微差异。这在开发测试阶段会让人抓狂——你明明什么参数都没改第三次运行的结果和前两次略有不同你会怀疑是自己代码出 bug 了。我的建议是在调用随机 SVD 之前固定种子rng(42);注意rng最好只在测试和调试时固定。正式的大规模处理中随机种子的影响会随矩阵规模增大而迅速减小不固定反而能避免某些极端随机矩阵带来的不利情况。7.3 谐波幅值如何精确恢复软阈值收缩会整体压低奇异值导致重构信号的谐波幅值偏小。如果你要做的是谐波检测而不仅仅是波形清洗那么后续的幅值校准步骤不能省。最简单有效的方法对去噪后的信号做 FFT提取各峰值频率处的幅值 ( A_{meas} )再和原始含噪信号在同一频率处的 FFT 幅值做对比。因为谐波成分在窄带内的 SNR 通常远高于宽带平均 SNR原始信号的该频率幅值反而是可信的。也可以反过来验证去噪效果如果去噪后的 FFT 幅值和去噪前差得太多比如超过 10%说明你的软阈值收缩过度了需要调低 ( \alpha )。我一般用这个方法作为快速调参手段几秒钟就能判断阈值设得合不合理。7.4 与 FFT 谐波分析的衔接有人会问既然最后还是用 FFT 提幅值那干嘛还要做 SVD 去噪我的体会是这两者解决的其实是不同层面的问题。FFT 适合在信噪比尚可的情况下快速提取频谱峰但当噪声很大时频谱泄漏和旁瓣抬升会让小谐波被噪声淹没。SVD 去噪后噪声底被压低谐波的频谱峰变得尖锐且孤立FFT 提取的幅值精度自然更高。另外一个实用衔接方式是先用 SVD 去噪得到干净的时域波形再对波形分段做 FFT 看频谱的时变性。你可以清晰地观察各次谐波幅值随时间的变化这是直接用含噪信号做 FFT 很难做到的。7.5 一个关于 q 的细节幂迭代 q 有一个容易忽略的副作用它会放大主要成分的能量差距。对于谐波去噪这意味着小谐波比如 5 倍频之后的高次谐波对应的奇异值可能被过度压缩。如果你的数据里存在幅度很小但真实存在的谐波成分建议 q 不要超过 2。在类似场景下我给客户的推荐配置一直是 q1 起步根据结果再决定是否增加。低信噪比时 q2很少会用到 q3因为收益太小计算代价却不小。这套随机SVD 软阈值的谐波去噪流程我已经在多个项目里落地用过。最初吸引我的是它把计算时间从分钟级压到秒级真正做久了之后反而觉得它最可贵的地方是稳定——参数不敏感、对噪声模型不敏感、对数据规模不敏感。你不用担心某一天换了一批数据就要重新调一整天参数。如果你手头也有大批量含噪谐波数据需要清洗我建议你直接把这套代码拿去跑一遍把第一版本的参数设成我在文中给的默认值然后观察一下输出。大概率你会发现那些之前被噪声盖住的细节现在能看清了。