ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

基于MATLAB的QRLSTM分位数回归时间序列区间预测

基于MATLAB的QRLSTM分位数回归时间序列区间预测 简介这份MATLAB资源面向需要开展时间序列区间预测的研究人员与工程师尤其适用于风速、负荷、功率等单变量数据的预测场景。基于LSTM神经网络与分位数回归方法程序可输出不同置信水平下的预测区间而不仅是单点预测能较好捕捉非线性时间序列的长期依赖与不确定性。压缩包体积仅20KB共包含5个文件其中4个为.m脚本涵盖主程序、分位数回归层定义及区间覆盖率指标计算等功能模块另有1个Excel数据文件便于直接替换数据验证模型。已有496人学习下载。资源运行环境为MATLAB 2018及以上版本输入输出均为单个变量结构紧凑、注释清晰适合刚接触区间预测的读者快速上手也方便在此基础上扩展多变量或不同数据集应用是一份兼具教学与实用价值的小型示例程序包。1. 为什么用QRLSTM分位数回归给LSTM补上不确定性的短板时间序列区间预测最怕的不是预测不准而是模型只会给一个点估计却答不上“这个预测有多大的可能落在哪个范围里”。QRLSTM就是来解决这个问题的把分位数回归与长短期记忆神经网络结合起来用LSTM提取时序依赖用分位数损失训练多个输出头一次性给出低、中、高分位数对应的预测区间。MATLAB里实现这套QRLSTM不需要复杂环境Deep Learning Toolbox自带LSTM和自定义回归层配合完整程序和数据能直接复现并接到自己的业务数据上。适合做风速、负荷、销量、水位这类单变量或多变量时间序列区间预测的工程师和学生。要跑通它关键在于分位数损失函数、序列滑窗构造以及输出层的分位数个数设计。2. 区间预测的数学基础分位数损失、LSTM结构与预测区间构建2.1 分位数回归与pinball lossLSTM的损失函数怎么改普通回归网络用均方误差学的是给定输入下的条件均值。但区间预测需要多个条件分位数比如5%分位数和95%分位数。分位数回归做的事情不是换个网络结构而是把损失函数换成pinball loss也叫分位数损失。对于第τ个分位数定义误差e y - y_hat损失函数为当y ≥ y_hat即预测偏低损失为 τ * |e|当y y_hat即预测偏高损失为 (1-τ) * |e|。写成MATLAB风格就是max(τ*e, (τ-1)*e)。这个表达式把两种情况统一了因为当τ0.5时退化为绝对误差的一半所以中位数分位数对应的就是最小绝对偏差回归。τ越接近0模型对低于预测值的误差惩罚越重于是预测值会偏向低分位τ越接近1则偏向高分位。QRLSTM的核心就是把LSTM最后一个时间步的隐藏状态接到一个全连接层上输出一组分数每个分数对应一个τ然后对每个输出分别计算pinball loss并求和取平均。这样LSTM学到的隐藏特征被共享给所有分位数而不是为每个分位数单独训练一个模型。为什么不能用MSE再加个残差方差因为MSE假设残差是正态分布且方差恒定而时间序列的波动经常是异方差的预测残差在不同时段宽度完全不同。直接用分位数回归不需要假设分布形态只要损失函数是凸的理论上就能逼近条件分位数。这是QRLSTM的吸引力所在也是后续所有实现步骤的根据。2.2 LSTM为什么适合分位数回归共享特征与序列依赖LSTM长短期记忆神经网络通过输入门、遗忘门和输出门控制信息流动能记住几百步之前的相关信息这对风速、负荷、水流这类有明显时序依赖的数据很关键。在MATLAB的Deep Learning Toolbox里调用lstmLayer就能拿到一个可训练的网络层。QRLSTM的结构不复杂sequenceInputLayer输入过去一段时间的观测值lstmLayer输出最后一个时间步的状态再经过全连接层输出多个分位数。这里有个很有用的特性分位数输出共享同一个LSTM隐状态。这意味着网络只用一次前向计算就能同时得到所有分位数预测训练速度比分别训练五个模型快得多。而且不同分位数之间通过共享特征相互约束一个分位数学习到的趋势信息会影响另一个分位数整体上比独立模型更平滑。实践中常见做法是把分位数个数控制在5到9个太少画不出区间形状太多会让输出层参数量变大在中小样本上反而容易过拟合。还有一种做法是把分位数作为额外输入让网络自己学习分位数条件但在MATLAB里直接多输出的方式更直观。fullyConnectedLayer(numQuantiles)的输出维度就是分位数个数不需要额外条件输入。要注意的是输出层不要接Softmax或Sigmoid分位数回归输出在实数域上激活函数会让边界分位数失真。2.3 从多个分位数到预测区间覆盖率和区间宽度的权衡有了τ0.05、0.25、0.5、0.75、0.95这组输出后预测区间怎么拼出来最直观的做法是取一对对称分位数比如0.05和0.95对应90%区间0.25和0.75对应50%区间。区间上界和下界直接就是对应分位数的预测值。中位数0.5分位数可以作为确定性的点预测比直接用MSE模型多点一个信息点预测和边界同时给出来。但区间不是越窄越好。一个始终预测整个训练数据范围的区间覆盖率高但没有信息量一个只覆盖一个点的区间信息量高但覆盖率低。实际评估常用两个指标预测区间覆盖率PICP统计测试集里真实值落入上下界的比例区间平均宽度在满足覆盖率的前提下越窄越好。QRLSTM训练时通过不同τ的损失权重自然平衡这两个目标但训练数据不均匀时容易出现某个分位数偏移需要在验证集上做校准这个第6章再展开。3. MATLAB完整实现QRLSTM数据构造、自定义损失层、训练与预测3.1 数据准备与滑窗建序列我平时跑通一个方案第一步是先验证流程再用真实数据。下面的程序生成一段带趋势、季节项和噪声的模拟时间序列长度1000点方便复现和检查代码逻辑。如果手里有真实数据把data替换成自己的列向量就可以但要保证它是等间隔采样的。% 生成模拟时间序列趋势 季节 噪声 rng(42); T 1000; t (1:T); data 10 * sin(t / 50) 2 * sin(t / 10) 0.02 * t 0.6 * randn(T, 1);这段数据有基础的周期性和逐渐上升的趋势加上高斯噪声能看出区间预测在中波动时段的表现。参数0.6控制噪声幅度可以根据需要调大调小。然后是滑窗构造特征和响应核心是保证每个样本都用过去inputSteps个点预测下一个点。inputSteps 24; % 用过去24个时刻预测下一个时刻 numSamples T - inputSteps; X cell(numSamples, 1); Y zeros(numSamples, 1); for i 1:numSamples X{i} data(i:iinputSteps-1, :); % 24x1 序列 Y(i) data(iinputSteps); % 真实未来值 end这里X是cell数组每个元素是一个24×1的序列MATLAB的trainNetwork自带的序列分块机制能直接处理这种格式。Y是标量响应但后面会被复制成多个分位数对应的矩阵。参数inputSteps是最敏感的旋钮之一对于小时级负荷数据24步可能代表一天对于分钟级数据24步可能不够周期信息要加大到48或72。滑窗重叠意味着相邻训练样本有19个点是重复的这不会造成信息泄漏因为预测目标始终在当前窗口之后的那个点。3.2 自定义分位数回归层继承RegressionLayer实现pinball lossMATLAB训练网络有两种路径一种是用trainNetwork搭配自定义层代码简洁另一种是用dlnetwork写训练循环灵活但繁琐。QRLSTM的分位数损失不是内置层所以需要自定义一个回归层。下面是完整的层定义保存为quantileRegressionLayer.m。classdef quantileRegressionLayer nnet.layer.RegressionLayer properties Quantiles end methods function layer quantileRegressionLayer(quantiles, name) layer.Quantiles quantiles(:); layer.Name name; end function loss forwardLoss(layer, Y, T) q layer.Quantiles; % numQuantiles x 1 diff T - Y; % numQuantiles x N loss mean(mean(max(q .* diff, (q - 1) .* diff), 1), 2); end end end这个层的作用是计算整个mini-batch的平均pinball loss。Y是网络输出形状是numQuantiles x NT是目标值在训练前需要把真实值复制成同样形状。max(q.*diff, (q-1).*diff)同时处理了正负误差两种情况。mean(mean(...))先对分位数维度平均再对样本维度平均得到标量损失。这个向量化写法比for循环快不少亲测在GPU训练时差距很大。注意T必须预先复制否则forwardLoss里的尺寸对不上报错。如果你在MATLAB R2018a之前版本上跑nnet.layer.RegressionLayer的接口略有不同建议至少用R2021a之后的版本Deep Learning Toolbox对自定义层的支持更稳定。3.3 网络定义与训练选项设置网络层定义不复杂关键是确定隐藏单元数和分位数个数。下面的代码把五个分位数作为输出用两层结构提取特征。quantiles [0.05, 0.25, 0.5, 0.75, 0.95]; numQuantiles numel(quantiles); layers [ sequenceInputLayer(1, Normalization, zscore) lstmLayer(64, OutputMode, last) fullyConnectedLayer(32) reluLayer() fullyConnectedLayer(numQuantiles) quantileRegressionLayer(quantiles, qrloss) ];序列输入层的Normalization选项自动做标准化但注意它用的是整个训练集的均值和方差。这里是先滑窗后训练标准化只会对输入特征做不会影响目标。lstmLayer(64)输出模式必须设为last因为我们要的是最后一个时间步的隐藏状态不是所有时间步。reluLayer之后接全连接输出分位数这里不加激活让输出在实数域自由取值。接下来构造训练目标矩阵。trainNetwork要求响应矩阵与网络输出层输出的大小一致即numQuantiles x numSamples所以每一列都是同一真实值复制了numQuantiles份。% 训练集/测试集按时间顺序切分 trainRatio 0.8; numTrain floor(numSamples * trainRatio); XTrain X(1:numTrain); XTest X(numTrain1:end); YTrain repmat(Y(1:numTrain), numQuantiles, 1); YTest Y(numTrain1:end);这里numTrain是前80%样本测试集严格排在训练集之后不做随机切分。如果随机打乱测试样本可能早于部分训练样本相当于用未来预测过去区间评估全失真。YTrain用repmat扩展成5×numTrain矩阵每一行对应一个分位数的目标数值相同。因为分位数损失本身不要求目标分位数有区别区别只在于损失权重所以复制是没问题的。训练选项我习惯这么设options trainingOptions(adam, ... MaxEpochs, 80, ... MiniBatchSize, 32, ... InitialLearnRate, 0.005, ... Shuffle, every-epoch, ... GradientThreshold, 2, ... Plots, training-progress, ... Verbose, 1); net trainNetwork(XTrain, YTrain, layers, options);InitialLearnRate设为0.005是常见起点LSTM对学习率比全连接网络更敏感太大容易NaN太小收敛慢。GradientThreshold设为2是为了防止LSTM梯度爆炸这类分位数回归的损失曲线不如MSE平滑梯度裁剪几乎是必备的。MiniBatchSize32在小数据集上比较平衡如果显存紧张可以降到16。3.4 预测与可视化区间怎么画出来训练完成后用predict对测试集做前向计算得到的是5×N的矩阵每一行是一个分位数的预测序列。画区间时取0.05和0.95作为90%预测区间边界0.5作为点预测中线。YPred predict(net, XTest, MiniBatchSize, 32); YLo YPred(1, :); % 0.05分位数 YMid YPred(3, :); % 0.5分位数 YHi YPred(5, :); % 0.95分位数 % 画测试集的区间 idx 1:numel(YTest); figure; fill([idx, fliplr(idx)], [YHi, fliplr(YLo)], ... [0.8 0.9 0.95], FaceAlpha, 0.4, EdgeColor, none); hold on; plot(idx, YMid, b-, LineWidth, 1.2); plot(idx, YTest, r-, LineWidth, 1); legend(90%区间, 中位数预测, 真实值, Location, best);YPred每一行的顺序和创建层时传入的quantiles顺序一致所以第一行是0.05第三行是0.5第五行是0.95。fill函数先画上界从左到右再画下界从右到左形成闭合区域。FaceAlpha控制透明度方便观察真实值曲线与区间的重合程度。这里我习惯把区间画成浅蓝色真实值画成红色叠加起来一眼能看出预测是否在波动大的时段明显发散。4. 参数与超参数调试QRLSTM最值得花时间的三个旋钮4.1 分位数的数量与位置怎么选才合理分位数集合决定了输出层维度和区间形状。常用做法是选对称分位数对比如[0.05,0.5,0.95]对应90%区间[0.1,0.5,0.9]对应80%区间。如果想同时看多个覆盖级别可以设[0.05,0.25,0.5,0.75,0.95]这样既能画50%区间又能画90%区间。分位数个数增加会直接增加全连接输出层的参数但影响不大真正的影响是损失函数里各分位数之间的平衡。如果数据噪声是对称的对称选择没问题如果数据有右偏比如销售数据偶尔有爆发性高值低分位数和高分位数之间的不对称性更强可以适当把高分位数往0.99推低分位数放到0.01。我一般不会一开始就用19个分位数那是为了画精细分布才做的。绝大多数业务场景5个分位数足够太多会让某些极端分位数在训练集里得不到足够样本约束反而出现交叉或摆动。训练完先看中位数序列是否合理再看0.05和0.95之间是否始终保持下界低于上界如果交叉严重先减少分位数。4.2 序列长度、隐藏单元和学习率的联动关系inputSteps决定模型看到多长的历史。序列太短周期信息学不到太长样本数减少训练时间增加而且LSTM要学习长期依赖的难度变大。我的经验是先用数据自相关图判断周期。比如小时负荷数据有24小时周期inputSteps至少要看48小时覆盖两个周期如果计算资源紧张24也可以但需要调高隐藏单元。隐藏单元lstmLayer的维度通常从32到128之间试。64对于几百到几千样本的时间序列是安全的起点。注意隐藏单元增加会显著增加训练时间因为LSTM四个门控矩阵的参数量和隐藏单元平方成正比。这三个参数是联动的序列长、隐藏单元多时模型容量大学习率要降低否则容易震荡。我自己调试时会把学习率从0.01开始如果损失曲线前期下降过快但后期震荡就降到0.003如果前20个epoch几乎不下降先检查数据标准化和损失层有没有写错再考虑调大学习率。Adam优化器对初始学习率没那么敏感但LSTM还是建议加上梯度裁剪这能避免很多翻车现场。4.3 区间评估PICP、区间平均宽度与Winkler得分模型好不好不能只看损失曲线区间预测的评价指标要单独算。PICP是测试集真实值落在预测区间内的比例比如90%区间理论上PICP应接近0.9。区间平均宽度则是上下界差的均值。只看PICP会骗人把小界扩到无穷宽PICP直接100%。只看宽度也会骗人区间窄但漏掉很多点。更综合的是Winkler得分它同时惩罚窄区间和超出区间的点。在MATLAB里可以手写一段picp mean((YTest YLo) (YTest YHi)); avgWidth mean(YHi - YLo); fprintf(PICP %.3f, avgWidth %.3f\n, picp, avgWidth);如果PICP低于名义覆盖水平比如90%区间实际只有70%说明网络对数据波动估计不足需要放大边界分位数或做后校准。如果PICP接近但区间宽度偏大说明边界分位数被过度外推可以考虑在损失函数里对边界分位数加一个宽度惩罚项。QRLSTM本身没有显式的宽度控制靠pinball loss在训练数据上的分布自动平衡所以业务上对覆盖率要求更严格时后处理校准几乎不可避免。5. 常见问题与避坑MATLAB实现QRLSTM的五个翻车场景5.1 训练损失变成NaN或发散现象训练迭代到几十步后损失变成NaN或者从初始值开始就一路飙升。原因学习率过大导致梯度爆炸输入数据含有NaN或极端值LSTM的梯度在长序列上累积爆炸。解决先检查数据里有没有NaN用any(isnan(data))确认。然后把InitialLearnRate降到0.001以下同时设置GradientThreshold为1或2。如果仍然NaN检查自定义层的forwardLoss返回值是不是标量MATLAB在某些版本下如果返回0×0数组也会触发奇怪行为。我在实际中遇到过数据标准化没做某个极端值把LSTM门控饱和损失直接跳到NaN做了zscore之后问题消失。5.2 预测区间上下界交叉现象测试集某些时刻0.95分位数预测值小于0.05分位数区间反了。原因多个分位数输出在训练时是相互独立的网络没有在结构上保证单调性。如果训练样本少或数据波动大某些时刻的极端分位数可能相互挤压。解决最简单的方法是在画图前排序YPred sort(YPred, 1)让每一时刻的分位数按从小到大排列。这个方法听起来有点粗糙但实践中很常用因为真正评估覆盖率时只需要排序后的上下界。如果想从训练层面缓解可以把分位数个数减少到3个或者让低分位数和高分位数共享更多特征比如全连接层先减少维度再分出多列。也可以在后处理时做保序回归但MATLAB里没有现成函数重写太费劲排序法足够应对大多数情况。5.3 训练速度慢到无法忍受现象一个5000样本、序列长度50的简单任务用CPU训练LSTM要跑几小时。原因LSTM是循环计算无法像卷积那样大规模并行且序列越长计算量越大。自定义层的for循环还会放大问题。解决先把自定义损失层改成向量化写法就是3.2节那个版本性能提升非常明显。其次检查MiniBatchSize太小会让每次GPU利用率低太大显存不够32到128之间调。再有就是确认trainNetwork是否真的检测到GPU运行gpuDevice查看如果返回空则说明还没配置。如果数据规模实在大可以把inputSteps缩短到关键周期长度或者用sequenceInputLayer加Normalization减少训练负担。最后还有一个血泪经验不要用MATLAB Live Script跑训练图形渲染会拖慢迭代编辑窗口直接运行会更快。5.4 数据集划分顺序错误导致未来信息泄漏现象训练损失很低测试集表现却差得离谱但区间覆盖率反而高整体看起来很怪。原因随机切分测试集或标准化时用了全量数据统计量导致测试集信息混进训练过程。解决严格按时间顺序切分。我习惯在滑窗之前就留出最后20%作为测试集先切分再做滑窗而且标准化参数只用训练集计算。如果你用sequenceInputLayer的Normalization选项它会在训练时自动用训练集统计量归一化这个没问题。但如果自己手动做(data-mean(data))/std(data)请一定只对训练集算mean和std再对测试集应用。只要未来信息进入训练集分位数区间就会显得过于自信部署时直接翻车。5.5 目标矩阵复制维度不匹配现象运行trainNetwork时报错提示输出层大小与响应大小不一致。原因fullyConnectedLayer(numQuantiles)的输出是numQuantiles x N而如果YTrain还是1×N的普通向量trainNetwork无法对齐。解决用repmat把目标复制成numQuantiles x numTrain每一行都是真实值的重复。检查方式size(YTrain)应为[numQuantiles, numTrain]size(YPred)预测后也应为[numQuantiles, N_test]。如果改了分位数个数记住目标矩阵也要同步重新复制。这个坑很常见因为常规回归网络输出一个标量很多人会忘记多输出时响应矩阵要扩维。6. 进阶用分位数校准让区间更可靠QRLSTM训练完直接输出的90%区间可能只有82%的实际覆盖率这在中位数附近表现尚可边界分位数经常由于训练数据有限而偏离名义水平。我习惯在测试集之外再留一个小的验证集专门做分位数校准。校准思路很简单对于每个分位数τ统计真实值低于该分位数预测的比例记为τ_hat。如果τ_hat小于τ说明该分位数预测偏低需要往高调反之则往低调。具体做法是给每个分位数预测值加一个偏移量偏移量由验证集上的偏差决定。对0.05分位数如果验证集里实际只有2%的点低于预测说明预测太高了把预测整体减去一点对0.95分位则相反。更平滑的做法是做一个线性变换y_calibrated a * y_raw b在验证集上用分位数回归或单纯线性插值拟合a和b。这相当于把QRLSTM的输出当做事后校准的特征而不是最终结果。我自己的习惯是训练时用5个分位数预测后在验证集上计算每个分位数的经验偏差然后把偏移量加到测试集预测上。如果偏移后覆盖率仍然不足我会把边界分位数从0.05/0.95挪到0.02/0.98重新训练一次。这比修改损失函数更可控因为模型主体不动后处理逻辑很容易解释给业务方。另一个提升稳定性的技巧是多轮训练取平均。LSTM初始化和随机梯度下降会导致不同训练轮次的分位数曲线有微小差异单独一次的结果可能在某几段区间上偏窄。可以把同一个QRLSTM训练三次每个分位数取三次预测的平均值区间宽度和覆盖率都会更平滑。代价是训练时间三倍但成果是区间更稳定值得尝试。QRLSTM不是黑匣子它的数学原理和落地方案都很清晰。真正决定使用体验的是数据切分、分位数选择和后校准这几个环节。你只要把第3章的完整程序跑通再加上第5章的避坑经验就能在自己的时间序列数据上得到可解释、可评估的预测区间。希望帮到你。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表