三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

CNN在时间序列预测中的应用与实践

CNN在时间序列预测中的应用与实践

1. 项目概述

最近在时间序列预测领域尝试了一个有趣的实验:用CNN处理一维信号。传统观念认为CNN更适合处理图像这类二维数据,但在实际测试中发现,对一维时间序列数据也能取得不错的效果。本文将完整记录从数据准备到模型评估的全过程,特别适合刚接触时间序列预测的开发者参考。

这个实验使用Matlab作为主要工具,原因有三:一是其内置的深度学习工具箱对新手友好;二是数据处理和可视化功能完善;三是在信号处理领域有大量现成函数可用。整个过程涉及数据生成、标准化、窗口划分、模型构建、训练调参和评估六个关键环节。

提示:虽然本文使用Matlab演示,但核心思路同样适用于Python等其他平台,关键是要理解每个步骤的设计原理。

2. 数据准备与预处理

2.1 生成模拟数据

我们先创建一个具有明显周期特性的合成数据,这样便于验证模型效果。使用以下代码生成包含正弦波和高斯噪声的混合信号:

fs = 100; % 采样频率(Hz) t = 0:1/fs:10; % 时间向量 f1 = 1; % 基频(Hz) f2 = 3; % 谐波频率(Hz) % 生成复合信号 clean_signal = 2*sin(2*pi*f1*t) + 0.5*sin(2*pi*f2*t); noise = 0.5*randn(size(t)); % 高斯白噪声 raw_signal = clean_signal + noise; % 可视化 figure plot(t, raw_signal) title('原始信号') xlabel('时间(s)') ylabel('幅值')

这种信号模拟了现实中常见的周期性观测数据,如电力负荷、温度变化等。加入噪声是为了让数据更接近真实场景。

2.2 数据标准化

不同量纲的数据会严重影响CNN的训练效果。我们采用Z-score标准化:

signal_mean = mean(raw_signal); signal_std = std(raw_signal); normalized_signal = (raw_signal - signal_mean)/signal_std;

标准化后数据均值为0,标准差为1,这能加速模型收敛并提高数值稳定性。

2.3 滑动窗口处理

时间序列预测通常采用滑动窗口方法构造样本。这里设置窗口大小为64个时间点,预测未来16个点:

window_size = 64; horizon = 16; num_samples = length(normalized_signal) - window_size - horizon + 1; % 初始化数据矩阵 X = zeros(num_samples, window_size, 1); % 添加通道维度 y = zeros(num_samples, horizon); % 填充数据 for i = 1:num_samples X(i,:,1) = normalized_signal(i:i+window_size-1); y(i,:) = normalized_signal(i+window_size:i+window_size+horizon-1); end

注意:窗口大小和预测范围需要根据信号特性调整。一般来说,窗口应包含至少1-2个完整周期。

3. CNN模型构建

3.1 网络架构设计

采用经典的编码器-解码器结构,但针对一维信号做了调整:

layers = [ sequenceInputLayer([window_size 1], 'Name', 'input') % 输入层 convolution1dLayer(3, 64, 'Padding', 'same', 'Name', 'conv1') % 卷积层1 batchNormalizationLayer('Name', 'bn1') reluLayer('Name', 'relu1') maxPooling1dLayer(2, 'Stride', 2, 'Name', 'pool1') % 池化层 convolution1dLayer(3, 128, 'Padding', 'same', 'Name', 'conv2') % 卷积层2 batchNormalizationLayer('Name', 'bn2') reluLayer('Name', 'relu2') globalAveragePooling1dLayer('Name', 'gap') % 全局池化 fullyConnectedLayer(horizon, 'Name', 'fc') % 全连接层 regressionLayer('Name', 'output') % 回归层 ];

这个设计有几个关键点:

  1. 使用小尺寸卷积核(3)捕捉局部特征
  2. 逐步增加通道数(64→128)提取多尺度特征
  3. 全局平均池化替代展平操作,减少参数量
  4. 最终输出维度与预测范围(horizon)一致

3.2 关键参数解析

参数作用选择依据
卷积核大小3特征提取平衡感受野和计算量
初始通道数64特征维度从经验值开始,逐步增加
池化大小2降采样常见选择,保留主要特征
学习率0.001优化步长Adam优化器的默认值

4. 模型训练与调优

4.1 训练配置

options = trainingOptions('adam', ... 'MaxEpochs', 50, ... 'MiniBatchSize', 32, ... 'InitialLearnRate', 0.001, ... 'LearnRateSchedule', 'piecewise', ... 'LearnRateDropFactor', 0.1, ... 'LearnRateDropPeriod', 30, ... 'Shuffle', 'every-epoch', ... 'ValidationData', {X_val, y_val}, ... 'Plots', 'training-progress', ... 'Verbose', true);

这里采用分阶段学习率策略:前30轮用0.001,之后降为0.0001。这能在训练后期更精细地调整参数。

4.2 数据划分

将数据按7:2:1分为训练集、验证集和测试集:

[trainInd, valInd, testInd] = dividerand(num_samples, 0.7, 0.2, 0.1); X_train = X(trainInd,:,:); y_train = y(trainInd,:); X_val = X(valInd,:,:); y_val = y(valInd,:); X_test = X(testInd,:,:); y_test = y(testInd,:);

随机划分能确保各集合中的数据分布一致,避免偏差。

4.3 训练过程监控

训练时会显示损失曲线和指标变化。重点关注:

  1. 训练损失和验证损失的差距(判断过拟合)
  2. 验证集性能是否持续提升
  3. 学习率调整时机

如果发现验证损失开始上升而训练损失继续下降,可能是过拟合的信号,需要早停或增加正则化。

5. 模型评估与分析

5.1 预测效果可视化

y_pred = predict(net, X_test); % 随机选择一个测试样本展示 idx = randi(size(X_test,1)); figure plot(y_test(idx,:), 'b-o', 'DisplayName', '真实值') hold on plot(y_pred(idx,:), 'r--*', 'DisplayName', '预测值') legend title('单样本预测对比') xlabel('时间步') ylabel('标准化值')

好的预测结果应该能捕捉到信号的主要趋势和周期特性,细节可能有轻微偏差。

5.2 量化指标计算

使用三个常用指标评估性能:

% 均方根误差(RMSE) rmse = sqrt(mean((y_pred - y_test).^2, 'all')); % 平均绝对误差(MAE) mae = mean(abs(y_pred - y_test), 'all'); % 决定系数(R²) y_mean = mean(y_test, 'all'); ss_tot = sum((y_test - y_mean).^2, 'all'); ss_res = sum((y_test - y_pred).^2, 'all'); r2 = 1 - (ss_res / ss_tot);

指标解释:

  • RMSE对大误差更敏感
  • MAE更稳健,不受异常值过度影响
  • R²反映模型解释的方差比例,越接近1越好

5.3 误差分析

通过残差图检查系统偏差:

residuals = y_pred - y_test; figure histogram(residuals, 50) title('预测误差分布') xlabel('误差值') ylabel('频数')

理想情况下,残差应呈均值为0的正态分布。如果出现明显偏移或长尾,说明模型存在系统偏差。

6. 实战技巧与问题排查

6.1 数据不足时的对策

当训练数据有限时,可以尝试:

  1. 数据增强:添加轻微噪声、时间扭曲等
  2. 迁移学习:用预训练模型初始化部分层
  3. 简化模型:减少层数和通道数,防止过拟合
% 数据增强示例 - 添加噪声 augmented_X = X_train + 0.1*randn(size(X_train));

6.2 常见训练问题

问题现象可能原因解决方案
损失不下降学习率太低增大学习率或检查梯度
验证损失波动大批量大小不合适调整批量大小或学习率
预测结果平坦模型容量不足增加网络深度/宽度
过拟合明显训练数据不足增加数据或正则化

6.3 模型部署建议

将训练好的模型导出为MAT文件或ONNX格式,便于集成到其他系统:

save('ts_cnn_model.mat', 'net'); % 保存为MAT文件 exportONNXNetwork(net, 'ts_cnn.onnx'); % 导出为ONNX格式

对于实时预测,可以封装成函数:

function predictions = predict_ts(cnn_model, new_data) % 输入数据预处理 processed_data = (new_data - mean(new_data))/std(new_data); % 预测 predictions = predict(cnn_model, processed_data); % 后处理 predictions = predictions * std(new_data) + mean(new_data); end

7. 扩展思考

7.1 与传统方法的对比

与ARIMA等传统时间序列模型相比,CNN的优势在于:

  1. 自动特征提取,无需手动设计特征
  2. 能捕捉非线性关系
  3. 对噪声更鲁棒

但计算成本更高,且需要更多数据。对于简单周期信号,传统方法可能更高效。

7.2 混合架构探索

可以尝试CNN与LSTM结合的混合模型:

  • CNN提取局部特征
  • LSTM捕捉长期依赖
  • 最后接全连接层输出预测

这种架构在复杂时间序列上往往表现更好,但训练难度也更大。

7.3 超参数优化方向

进一步调优可以考虑:

  1. 使用贝叶斯优化自动搜索超参数
  2. 尝试不同卷积核尺寸的组合
  3. 调整网络深度和宽度平衡
  4. 实验不同的归一化方法
% 超参数优化示例框架 params = hyperparameters('fitrnet', X_train, y_train); params(1).Range = [16 128]; % 第一层通道数范围 params(2).Range = [3 7]; % 卷积核大小范围

在实际项目中,我发现信号预处理质量对最终性能的影响往往超过模型结构本身。特别是对于周期性强的信号,确保窗口包含完整周期非常关键。另外,适当添加Dropout层(如20%比率)能有效防止过拟合,尤其是在数据量不大时。

← 返回列表