ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

回声状态网络做时间序列回归:DeepESN原理、Matlab实现与调参避坑

回声状态网络做时间序列回归:DeepESN原理、Matlab实现与调参避坑 简介基于深度回声状态网络DeepESN实现数据回归预测的Matlab代码资源适合本科、硕士阶段开展教研学习也适用于需要快速搭建回归预测模型的研究人员。资源包含7个m脚本、2个csv数据文件、2个mat数据文件和1个readme说明文档共12个文件压缩包整体约7.13MB。其中DeepESN.m提供深度回声状态网络核心实现example_DeepESN_1.m与example_task_MC.m为典型任务示例MC_input.csv、MC_target.csv和MC100.mat则是配套数据集便于直接运行和验证模型效果。目前已有246人学习下载适合正在学习回声状态网络、深度递归神经网络或其回归预测应用的初学者和研究者。下载后可按README说明快速配置Matlab 2019a环境结合示例脚本理解深度ESN的层次结构与训练流程在现成数据集上完成回归预测实验并在此基础上替换输入输出数据开展自己的研究任务。1. 深度回声状态网络DeepESN做数据回归预测模型越深不一定越好但读出一层定胜负做小样本时间序列回归预测很多人第一反应是高斯过程回归调核函数、算协方差逆矩阵样本稍微多一点就慢下来。深度回声状态网络DeepESN是另一条路把若干层随机储备池串联只训练最后一层线性读出的权重训练速度比反传快几个量级又比单层ESN多出分层尺度信息。用Matlab实现时主要工作量集中在储备池权重生成、状态演化和岭回归读出三块踩坑也多在这三块。这篇文章写给两类人想在小样本仿真数据上快速出预测结果、又不想碰深度学习框架的Matlab用户以及已经跑过ESN、想知道加深网络到底值不值的人。2. DeepESN 的原理与分层状态传递单层 ESN 模型的深度版到底改了什么2.1 单层 ESN 到 DeepESN为什么需要把储备池“叠”起来ESN的核心是随机生成一个固定权重的循环网络储备池输入信号在里面被非线性地映射成高维状态。读出层用线性回归学一个权重矩阵把状态映射到目标值。由于循环权重的随机性网络天然具有短期记忆能力。但单层储备池的问题是状态只经历一次非线性扩张长期趋势和局部波动混在同一组状态分量里。谱半径调小了长程记忆不够调大了短期响应又变得毛躁。这种折中在信号本身包含多个时间尺度时尤其明显。DeepESN的常见做法是串联多个储备池层第l层的输入是原始输入与第l-1层状态的拼接。每一层不对目标直接负责而是把上一层的状态表征再加工一次。Gallicchio等人的早期工作表明这种分层结构在序列分类和预测上确实能给出比单层ESN更平滑的误差面。实际用下来我的体会是它最大的价值不是“深度带来更多参数”而是每层可以用不同的谱半径和泄漏率分别对准不同时间尺度。例如浅层用大谱半径保留慢趋势深层用小谱半径捕捉快波动这是单层模型做不到的解耦。2.2 状态传递公式与泄漏率用一条状态方程看清楚每一层做了什么先给出最常用的离散状态更新方程x_l(t) (1 - α_l) x_l(t-1) α_l tanh( W_in^(l) [u(t); x_(l-1)(t)] W^(l) x_l(t-1) b^(l) )这里u(t)是t时刻的输入向量x_l(t)是第l层储备池在t时刻的N维状态α_l是该层泄漏率W_in^(l)是输入权重W^(l)是储备池内部权重b^(l)是偏置。第一层忽略x_0(t)直接用u(t)作为输入。读出层把各层状态和原始输入拼起来z(t) [u(t); x_1(t); x_2(t); ...; x_L(t)]然后 y(t) W_out z(t)注意到W_out是唯一需要训练的矩阵其他所有权重生成后固定。训练方式就是线性回归或者岭回归。因为训练目标是线性读出整个模型没有反传样本量几百就能把W_out估计出来。这一点正好适合小样本仿真数据预测但反过来这种架构对权重初始化质量非常敏感因为它没有任何机制在训练中修正储备池的坏状态分布。2.3 与 ESN、高斯过程回归对比什么时候选 DeepESN方案训练复杂度需要的样本量多尺度信号主要坑单层ESN低几百即可一般谱半径折中DeepESN低仍是线性读出几百即可好可分层设参数状态饱和、尺度分配玄学高斯过程回归GPRO(n^3)小样本很好依赖核函数设计超参数难调、大样本慢实际选择时如果信号明显是慢趋势叠加快波动或者你想做多步滚动预测DeepESN的优势更明显。如果只有几十个点求稳还是先跑一版GPR做baseline再用DeepESN对比误差。不要一来就上5层后面避坑部分会说明原因。3. 用 Matlab 搭建最小可运行的 DeepESN回归预测代码与逐段说明3.1 生成仿真数据并划分训练测试序列先构造一个包含慢趋势和快波动的仿真序列用来验证模型行为。这个序列替换成你自己的真实CSV、.mat或Simulink导出数据后其余代码不需要改动。% 生成一个含慢趋势快波动的仿真序列作为回归目标 rng(42); n 2000; t (1:n); y sin(0.015*t) 0.3*sin(0.08*t 1.2) 0.15*sin(0.4*t 0.6) 0.05*randn(n,1); % 用过去 p 个点预测未来 1 个点 p 10; X zeros(n-p, p); Y zeros(n-p, 1); for i 1:n-p X(i,:) y(i:ip-1); Y(i) y(ip); end % 前70%训练后30%测试 split floor(0.7 * size(X,1)); Xtrain X(1:split,:); Ytrain Y(1:split); Xtest X(split1:end,:); Ytest Y(split1:end);逻辑说明仿真信号由三条不同频率的正弦叠加而成慢趋势对应低频快波动对应高频目标是从过去10个点预测下一点。p取10是为了构造输入矩阵对真实序列p可以理解为嵌入维数。注意Y和X要同时间对齐代码里第i行输入过去10个点输出第ip时刻的值。参数说明rng(42)固定随机种子是为了让每次运行结果一致正弦幅值和相位可以随便换。输入窗长p增加会增长记忆但特征维数也随之增加小样本下p不要超过50。3.2 储备池权重初始化谱半径归一化与输入缩放写在同一个循环里权重初始化是DeepESN里最不能偷懒的一步。每层输入权重和储备池权重都要做缩放否则状态矩阵很快饱和或退化。% 网络结构 L 3; % 储备池层数 N 100; % 每层神经元数 spectralRadius 0.9;% 谱半径 inputScale 0.3; % 输入权重缩放 leakRate 0.3; % 泄漏率各层可分别设置 sparsity 0.05; % 储备池稀疏度 ridgeLambda 1e-6; % 岭回归正则 % 生成每层权重 Win cell(L,1); Wres cell(L,1); for l 1:L if l 1 Win{l} (rand(N, p) - 0.5) * 2 * inputScale; else Win{l} (rand(N, p N) - 0.5) * 2 * inputScale; end W sprandn(N, N, sparsity) / sqrt(sparsity); maxEig abs(eigs(W, 1)); Wres{l} W * (spectralRadius / maxEig); end逻辑说明第一层输入是p维原始窗口后面每层输入是p维窗口与上一层N维状态的拼接因此Win矩阵列数为pN。sprandn生成稀疏随机矩阵除以sqrt(sparsity)是为了让非零元素幅度放大使得激活后的方差不会因为稀疏而过小。eigs(W,1)求谱半径把最大特征值缩放到spectralRadius。这种归一化是ESN的标准做法不要省。注意如果N很大eigs会慢可以改用abs(eigs(W,1))如果矩阵不可对角化eigs会警告只计算最大幅值特征值不影响谱半径控制。3.3 状态前向计算washout 之后才允许丢掉瞬态状态必须在原始序列上连续演化不能把每个样本独立初始化状态。训练段跑完后把最后时刻的状态保存下来留给测试段接力。% 前向状态收集训练段 washout 100; totalTrain split; xState cell(L,1); prevState cell(L,1); for l 1:L xState{l} zeros(N, totalTrain); prevState{l} zeros(N,1); end for t 1:totalTrain u Xtrain(t,:); for l 1:L if l 1 inVec u; else inVec [u; xState{l-1}(:, t)]; end cur tanh( Win{l}*inVec Wres{l}*prevState{l} ); xState{l}(:,t) (1 - leakRate) * prevState{l} leakRate * cur; prevState{l} xState{l}(:,t); end end % 保存每层最后时刻状态供测试段接力 finalState cell(L,1); for l 1:L finalState{l} prevState{l}; end % 洗掉washout构造特征矩阵S和目标向量 S zeros(totalTrain - washout, p L*N); for t washout1:totalTrain feat Xtrain(t,:); for l 1:L feat [feat; xState{l}(:,t)]; end S(t - washout, :) feat; end Yr Ytrain(washout1:end);逻辑说明washout100是因为起始状态是零向量网络需要一段时间进入“回声”状态。如果采样太密瞬态更长要适当加大washout。S的行数等于totalTrain-washout列数等于pLN这里的p是原始输入窗口后面LN是各层状态维度。目标Yr和S行数必须一致否则岭回归会报维度错误这也是最常见的低级坑。参数说明leakRate按单值写如果你想分图层设置泄漏率可以改成向量leakRate [0.1, 0.2, 0.4]循环里用leakRate(l)代替leakRate。第4章会讲怎么配。3.4 岭回归读出与测试集预测W_out 的维度别搞反读出层训练用岭回归求W_out这一步是整个模型唯一的学习过程。测试集预测需要把训练段最后状态传递给测试段起点保证回声状态连续。% 岭回归读出一层 M size(S,2); Wout (S * S ridgeLambda * eye(M)) \ (S * Yr); % 测试段前向用训练段最后状态作为初值保持状态连续性 testLen size(Xtest,1); xTest cell(L,1); for l 1:L xTest{l} zeros(N, testLen); end for t 1:testLen u Xtest(t,:); for l 1:L if l 1 inVec u; else inVec [u; xTest{l-1}(:,t)]; end if t 1 prev finalState{l}; else prev xTest{l}(:,t-1); end xTest{l}(:,t) (1 - leakRate) * prev leakRate * tanh( Win{l}*inVec Wres{l}*prev ); end end % 组装测试特征矩阵 Stest zeros(testLen, p L*N); for t 1:testLen feat Xtest(t,:); for l 1:L feat [feat; xTest{l}(:,t)]; end Stest(t,:) feat; end % 预测与评估 Ypred Stest * Wout; rmse sqrt(mean((Ypred - Ytest).^2)); ssRes sum((Ytest - Ypred).^2); ssTot sum((Ytest - mean(Ytest)).^2); R2 1 - ssRes / ssTot; fprintf(RMSE %.4f, R2 %.4f\n, rmse, R2);逻辑说明测试段状态必须用训练段最后状态初始化而不是全部重新从0开始否则状态演化断裂预测误差会很大。很多第一次上手的人把每个样本独立跑状态结果预测输出完全不对。测试段由于没有目标值参与状态更新读出的误差会自然放大这是回声状态网络的正常现象如果测试RMSE比训练RMSE大一个数量级先检查谱半径和泄漏率而不是怀疑代码。参数说明ridgeLambda写在岭回归表达式里防止SS奇异。小样本下S的列数310、行数1300一般不奇异但加上正则更稳。M写的是列数不要带错变量。4. 五个必调超参数谱半径、泄漏率、储备池规模与层数怎么配4.1 谱半径控制回声状态的长短记忆别迷信“越大越好”谱半径是储备池权重矩阵最大特征值的模。它决定网络内部状态的“回声”能维持多久。谱半径接近1状态的高频分量衰减慢、记忆长但更容易出现状态发散谱半径太小网络几乎没有历史记忆跟无状态的前馈网络区别不大。预测场景里我的习惯是0.7-0.95起步如果序列变化剧烈降到0.5左右试如果序列趋势平缓放到0.99。判断方法是在测试集上画出预测曲线如果预测的相位滞后明显就增大谱半径如果出现尖峰毛刺就减小。注意谱半径归一化依赖eigs求最大特征值每次随机生成的储备池矩阵不同所以即使谱半径设定相同实际状态行为也有波动。这也是为什么第3章要固定rng(42)的原因。4.2 泄漏率小样本预测里最容易翻车的参数泄漏率α控制了新状态覆盖旧状态的速度。α大表示对当前输入响应灵敏但记忆短α小状态变化缓慢适合捕捉慢变趋势。在DeepESN里各层可以用不同α。我的一个比较稳的配置浅层α0.1-0.2深层α0.3-0.5让浅层负责慢趋势、深层负责细节。如果数据是零均值平稳序列α可以取大一些如果序列有很强的趋势项α偏小能避免状态被单点突变带飞。注意α0.9以上会让tanh过早饱和状态矩阵退化成一个向量所有样本的读出特征几乎相同这是“预测结果变成一条水平线”的典型原因。小样本场景建议先从0.3开始扫每次按0.1步长向上加而不是一开始就调大。4.3 储备池规模与层数先定规模再叠层两层起步储备池规模N决定特征维数。N太小非线性表征不够N过大readout需要更多样本才能学稳。小样本场景N100通常是性价比最高的样本量低于500时建议不超过200。确定N后再调层数L从2开始逐层加。随着L增加深层输入拼接的维度也在涨训练时间线性增长。我的习惯是每加一层就把上一层的谱半径降一点比如从0.9降到0.8保持总记忆长度不变只增加表征的细粒度。对于300-2000个样本的回归任务L3基本够用L超过5后收益很小状态退化风险反而更大。调参时不要同时改N和L否则你分不清误差变化来自哪个参数。参数推荐范围小样本快速起步值主要影响谱半径0.5-0.990.9记忆长度状态稳定性泄漏率0.05-0.50.3状态响应速度储备池规模N50-500100特征维数训练时长层数L2-53表征层次状态退化风险inputScale0.1-1.00.3输入非线性化程度ridgeLambda0或1e-8~1e-31e-6读出稳定性这些参数相互耦合先固定其他参数再单独扫一个不要同时变动两个以上。经验是小样本时影响排第一的是leakRate第二是谱半径第三才是层数。高斯过程回归的核参数搜索同样有玄学成分但ESN参数更少且每次训练几乎瞬时完成扫参成本低得多。5. 避坑排查DeepESN 在 Matlab 里跑不动的常见原因与对策5.1 预测结果是一条水平线状态饱和把特征信息吃光了现象训练误差尚可测试集预测输出几乎为常数图像是一条横线。原因泄漏率过大或输入缩放inputScale过大tanh输入落入饱和区不同样本的状态向量高度相似readout只能学到均值。解决减小leakRate到0.1-0.3同时把inputScale降到0.1-0.5检查状态矩阵S的各列方差如果某一列几乎为0就是饱和特征。把训练数据做z-score标准化也能缓解。做完这步后让网络再跑一遍看S的heatmap是不是出现了明显的白色整列或黑色整列这种列必须处理掉。5.2 测试集发散谱半径设的值“理论上”没错但实际炸了现象训练集RMSE很小测试集预测在某个点开始指数级放大曲线飞走。原因谱半径接近1配合大inputScale或大泄漏率状态在长序列上累积误差测试段没有目标值校正发散是必然。解决先固定leakRate0.3、inputScale0.3单独把spectralRadius从0.9降到0.6看是否稳定或者提升washout长度让训练状态完全进入稳态再收集。如果数据本身有极值优先做平滑或差分后再训练不要直接喂原始值。5.3 每次运行结果差很多随机权重和状态初始化的影响现象同一脚本、同一数据两次运行RMSE相差20%以上。原因储备池权重随机生成谱半径归一化用的eigs对随机矩阵敏感状态初始值都是零但随机种子不同导致权重大相径庭。解决在脚本开头用rng(固定seed)需要汇报稳健性时跑20个种子记录RMSE的均值±标准差而不是只报一次最好结果。固定种子后若两次运行仍不同检查代码里是否有rand调用顺序受参数影响的地方。实际上你可以把rng放在脚本最前面后面所有rand和randn都跟着变这能复现出完全相同的结果。5.4 Matlab 内存被打满或 eigs 很慢深度与储备池规模的乘积被忽略了现象N500、L5时训练段1000个样本Matlab提示内存不足。原因状态矩阵xState{l}保存了每个时刻的完整N维状态共LNT个double50051000个元素已经超过20MB若同时保存xTest和处理S矩阵内存压力会更大eigs对非稀疏矩阵还要先稀疏化。解决不需要所有时刻的状态时washout段可以只跑不存在构造S时逐t拼接而不是先存完整xStateN超过300时把Wres转成sparse矩阵再eigs。对回归任务特征矩阵S本身才是readout需要的全部信息中间状态可以随用随丢。5.5 测试段状态初始化错误导致误差被高估一个常见逻辑坑现象测试RMSE比训练大一个数量级但预测曲线形状基本对。原因测试段重新从零初始化状态没有延续训练段的回声状态。解决按第3章的方式把训练段最后时刻的prevState传给测试段第一个时刻再逐t推进。这个坑在RNN类模型里都常见DeepESN也不例外。检查方法很简单把测试段第一个时刻的状态打印出来如果全是0说明状态链条没有接上。注意做上述任何修改之前建议先画一次状态矩阵S的heatmapimagesc(S)肉眼看看是不是存在大片全零列或饱和列。这一步能最快定位问题方向比看RMSE数字直观得多。6. 用 walk-forward 滚动预测验证 DeepESN别让单步预测的漂亮误差骗了你单步预测误差并不能代表真实应用。很多序列预测项目模型在单步RMSE上很漂亮一旦做多步滚动误差迅速放大。DeepESN由于状态本身带有递归记忆滚动预测比普通前馈模型稳定但也要验证。方法训练集上用前70%数据训练预测后30%的第一步把第一步预测值当作输入反馈给网络继续预测第二步以此类推直到预测长度达到预定的horizon。这样的walk-forward验证能暴露谱半径和泄漏率配合不当的问题。如果第二步就崩多半是谱半径太大如果趋势滞后严重多半是浅层泄漏率太小导致状态更新跟不上信号变化方向。具体做法把Xtest窗口中的前p-1个真实值保留预测点逐步用模型输出替换重新构造输入向量。在Matlab里就是一个for循环每次用当前窗口预测下一时刻的值然后丢弃窗口第一个元素、把预测值追加到末尾。相比单步评估多步滚动预测更接近实际部署形态。我自己的习惯是固定N和L后先看horizon1/5/10三步的RMSE曲线再决定要不要调参如果长期预测仍然稳定这个模型才值得集成进系统。另外可以做一次简单的鲁棒性检查对每个候选超参数组合跑5个随机种子取RMSE中位数而不是最小值防止“挑出来最好的那组只是运气好”。这个方法花不了多少时间但能避免在汇报结果时翻车。我的个人经验是DeepESN的参数不算多但每一个都不能默认动手前先固定rng、先看状态heatmap、先跑一次多步滚动这三步做完后面基本不会出原则性问题。希望帮到你。本文还有配套的精品资源点击获取
返回列表