第一次接触奇异谱分析(SSA)是在分析一组气象数据时。当时需要从杂乱的气温波动中提取长期趋势和季节周期,传统方法效果都不理想,直到发现了这个"时间序列瑞士军刀"。简单来说,SSA就像给时间序列做CT扫描 - 它能将混合在一起的趋势、周期、噪声成分层层剥离出来。
与傅里叶变换这类传统方法相比,SSA最大的优势在于不需要预先假设信号形态。比如分析股票数据时,你既不知道周期长度,也不清楚趋势线形状,SSA却能自动识别这些成分。我在处理EEG脑电信号时深有体会 - 当信号中同时存在alpha波、肌电干扰和设备噪声时,SSA的分组重构功能简直是救命稻草。
SSA的核心能力可以总结为:
轨迹矩阵是SSA的第一步,也是容易踩坑的地方。窗口长度M的选择直接影响分析效果 - 太短会丢失周期信息,太长则会导致计算量暴增。根据我的实战经验,M的最佳范围是N/5到N/3(N为序列长度),且最好是预期周期的整数倍。
举个例子,分析日均气温数据时(N=365),若已知存在年周期(约365天),我会选择M=60(约2个月)。这样既能捕捉年周期,又不会使矩阵过于庞大。实际构建时,MATLAB代码可以这样写:
matlab复制function X = build_trajectory(y, M)
N = length(y);
K = N - M + 1;
X = zeros(M, K);
for i = 1:K
X(:,i) = y(i:i+M-1);
end
end
对轨迹矩阵做SVD分解时,特征值的大小对应着成分的重要性。这就像把蛋糕分层 - 最大的特征值对应最显著的成分(通常是趋势),中等特征值对应周期分量,小特征值则是噪声。
我曾用这个特性做数据降噪:保留前几个大特征值对应的成分,重构后的信号信噪比提升了3倍。特征向量则揭示了成分的时域特征 - 缓慢变化的对应趋势,振荡的对应周期。
让我们用MATLAB完整走一遍SSA流程。首先生成包含趋势、周期和噪声的测试信号:
matlab复制%% 生成测试信号
n = 500; % 数据点数量
t = 1:n;
% 趋势成分(二次函数)
trend = 0.001*(t-250).^2;
% 周期成分(混合周期)
periodic = 2*sin(2*pi*t/50) + 1.5*cos(2*pi*t/25);
% 噪声成分
noise = 0.8*randn(1,n);
% 合成信号
signal = trend + periodic + noise;
figure;
plot(t, signal, 'b', t, trend, 'r--', 'LineWidth', 1.5);
legend('合成信号', '真实趋势');
窗口长度选择是核心技巧。对于周期信号,窗口长度应该是周期的整数倍。以下是完整的SSA分析代码:
matlab复制%% SSA分解
M = 50; % 窗口长度(与25天周期匹配)
K = n - M + 1;
% 构建轨迹矩阵
X = build_trajectory(signal, M);
% SVD分解
[U, S, V] = svd(X'*X);
singular_values = diag(S);
% 成分重构
RC = zeros(M, n); % 存储重构成分
for i = 1:M
Xi = sqrt(S(i,i)) * U(:,i) * V(:,i)';
RC(i,:) = reconstruct(Xi);
end
% 贡献率计算
contribution = cumsum(singular_values)/sum(singular_values);
retain = find(contribution > 0.95, 1); % 保留95%能量的成分
% 绘制特征值谱
figure;
plot(1:M, singular_values, 'bo-');
xlabel('成分序号');
ylabel('特征值大小');
w-correlation图是成分分组的利器。颜色越深表示成分相关性越高,通常需要将相关系数>0.5的成分归为一组:
matlab复制%% 计算w-correlation
wcorr = zeros(M,M);
for i = 1:M
for j = 1:M
wcorr(i,j) = weighted_corr(RC(i,:), RC(j,:), M);
end
end
figure;
imagesc(wcorr);
colorbar;
xlabel('成分序号');
ylabel('成分序号');
title('w-correlation矩阵');
根据w-correlation图,我们可以手动分组重构:
matlab复制%% 分组重构
% 趋势组(前2个成分)
trend_recon = sum(RC(1:2,:), 1);
% 周期组(3-6成分)
periodic_recon = sum(RC(3:6,:), 1);
% 噪声(剩余成分)
noise_recon = sum(RC(7:end,:), 1);
% 绘制结果
figure;
subplot(3,1,1);
plot(t, trend_recon);
title('趋势成分');
subplot(3,1,2);
plot(t, periodic_recon);
title('周期成分');
subplot(3,1,3);
plot(t, noise_recon);
title('噪声成分');
经过多个项目实践,我总结出这些经验值:
在分析股票数据时,我发现SSA对异常值非常敏感。一个实用的技巧是先用移动平均平滑数据,或者用中位数替代极端值。