news 2026/9/29 18:57:43

一维CNN处理时间序列:从滑窗到PyTorch实战与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
一维CNN处理时间序列:从滑窗到PyTorch实战与避坑指南

简介:面向深度学习初学者与算法工程师,资源围绕一维卷积神经网络(1D CNN)处理序列数据展开,覆盖时间序列预测、文本分类、音频信号分析等典型场景,提供Python完整实现与训练好的模型文件。包内共25个文件,以17个h5模型权重为主,记录了不同训练轮次下的准确率(如0.62~0.75),配合2个py脚本可用于复现模型构建、训练与评估;另有5个txt说明文档和1个arff格式的WISDM转换数据,方便理解数据集结构及预处理流程。整个压缩包约31.67MB,结构清晰,适合对照学习一维卷积层的卷积、激活、池化、全连接等关键步骤。资源已获10808人学习下载,既可直接加载最佳h5模型快速验证效果,也可基于源码调整卷积核大小、层数与激活函数进行二次实验,是入门1D CNN序列建模的实用参考资料。

1. 一维CNN处理序列数据:为什么我先把LSTM放到一边

如果你手头是一段传感器振动波形,或者一组股票日线收盘价,第一反应往往是找LSTM。但我在最近两个序列预测项目里,都先把一维CNN跑成了基线:几十行Python代码、十几分钟就能看到可用结果,训练速度比LSTM快一大截,在小样本条件下反而不容易过拟合。一维CNN不是“穷人的LSTM”,它把CNN卷积神经网络的核心思路平移到了单条时间轴上,用若干个卷积核在时间窗口上滑动,同时提取局部形状。你可以在时间序列预测、时间序列异常检测、波形分类这些任务里,把它当成一个低成本、可复现、能快速验证想法的起点。这篇笔记会从滑窗构造、Conv1d建模、训练评估讲到五个高频踩坑点。适合有Python基础、想把序列数据真正跑通,但还没有系统用过一维CNN的读者。

2. 时间序列怎么喂给一维CNN:滑窗构造与数据归一化

CNN和LSTM最大的区别是输入结构。LSTM可以按时间步一个个喂进去,而Conv1d要求每个样本是一段固定长度的向量。你不能把一条任意长度的连续序列直接塞进卷积层,所以必须先通过滑窗,把长序列切成固定形状的样本,再把每个样本对应的未来一段值做成标签。这一步做不对,后面所有网络结构都是空谈。

2.1 为什么是滑窗:把序列变成样本

滑窗的意思很简单:从序列开头取一段长度为input_len的数据作为特征X,再取紧接着未来的horizon个点作为目标y,然后沿着时间轴向右挪动step个点,重复这个动作。

import numpy as np def make_windows(series, input_len=48, horizon=6, step=3): X, y = [], [] for i in range(0, len(series) - input_len - horizon + 1, step): X.append(series[i:i + input_len]) y.append(series[i + input_len:i + input_len + horizon]) return np.array(X, dtype=np.float32), np.array(y, dtype=np.float32) # 造一段示例数据:趋势 + 正弦 + 噪声 raw = np.sin(np.linspace(0, 20, 1000)) + 0.1 * np.random.randn(1000) X, y = make_windows(raw, input_len=48, horizon=6, step=3) print("X:", X.shape, "y:", y.shape)

循环结束条件里的+1是为了保证最后一个窗口的y不越界。X的形状是(样本数, input_len),y的形状是(样本数, horizon)。这里所有样本都按原始顺序排列,因为时间序列一旦打乱,相邻样本的先后关系就废了。后面训练模型前,还要把X从(batch, seq_len)改成(batch, 1, seq_len),这就是Conv1d需要的通道维度,后面第3章会详细说。

滑窗的参数选择直接影响效果。input_len至少要覆盖一个完整周期,如果数据是小时粒度、存在日周期,窗口小于24就看不到周期;如果数据是日粒度、存在周周期,窗口至少7。horizon取决于你的业务预测需求,想预测未来6个点就设6。step控制样本密度,设成1会让相邻两个窗口只差一个点,样本量爆炸且高度重复;我一般取input_len // 2或更稀疏,既保留足够样本,又减少重叠带来的过拟合。如果样本量不够,再逐步调小。

2.2 用Python构造监督学习样本的代码

上面那段就是最小可用的滑窗代码。真正上项目时,通常还要处理多变量、缺失值、以及归一化。多变量的思路是让series变成二维数组(时间步, 特征数),滑窗后X变成(样本数, input_len, 特征数),这样每个样本里不仅有时间顺序,还包含同一时刻的多个特征。一维CNN会把特征数当通道数处理,这比把特征硬拼成一维要合理得多。

构造样本时有一个容易忽略的点:紧挨着的两个样本窗口重叠度非常高。例如input_len=48时,step=1会让相邻两个样本有47个点完全一样,模型很容易从训练集“背住”这些重复模式,到验证集上就露馅。一种常见做法是把滑窗写成一个可配置类,便于后面做时间序列交叉验证和切分。

class SlidingWindowDataset: def __init__(self, series, input_len=48, horizon=6, step=3): self.series = np.asarray(series, dtype=np.float32) self.input_len = input_len self.horizon = horizon self.step = step def __len__(self): return (len(self.series) - self.input_len - self.horizon) // self.step + 1 def __getitem__(self, idx): start = idx * self.step x = self.series[start:start + self.input_len] y = self.series[start + self.input_len:start + self.input_len + self.horizon] return x, y

这个类的__len__需要和前面函数里的循环边界完全一致,否则训练时DataLoader会越界。实际我用 PyTorch 时更喜欢这种写法,因为它天然支持懒加载,内存压力小。如果你构造的序列有几百万个点,一次性np.array可能吃掉好几个G内存,而数据集类只在每次__getitem__时切片,靠谱得多。

2.3 归一化:按训练集算,还是按窗口算

归一化这里有个时间序列专属的坑:不能像图像分类那样,在整个数据集上算出mean和std,然后一次性缩放。原因是你做的是预测任务,测试阶段是未来的数据,未来数据的均值在训练时是未知的。如果在全部数据上归一化,相当于把未来信息泄漏进了训练集,验证时不明显,上线后模型会突然变差。

常见做法有两种。第一种是全局归一化:只用训练集部分计算mean和std,然后把这个统计量同时应用到训练集、验证集和测试集。它适合相对平稳的序列,比如固定场景下的工业传感器数据。第二种是窗口内归一化:对每个滑窗独立减去该窗口均值、除以窗口标准差,标签也按同样的均值标准差缩放,预测后再还原。它对趋势和水平漂移更鲁棒,因为卷积核学到的是局部形态,而不是绝对数值。

def normalize_window(window, target): w_mean = window.mean() w_std = window.std() + 1e-5 return (window - w_mean) / w_std, (target - w_mean) / w_std # 在训练循环里这样调用 x_norm, y_norm = normalize_window(x, y)

加1e-5是防止窗口内数值完全相等时标准差为0。窗口归一化也有代价:如果在做时间序列异常检测,全局均值的漂移本身就是重要信号,窗口归一化会把这种慢漂移抹掉;但如果你更关心预测短期形态,它带来的稳定性通常大于损失。

多变量序列归一化时,还要注意按特征维度分别归一化,不要把5个特征拉平后混在一起算均值。不同特征的量纲可能差几个数量级,混在一起算会导致数值大的特征主导损失,卷积核学不到小量纲特征的模式。每个特征通道单独算mean/std,送入模型前再在通道维度拼接。

注意:滑窗构造和归一化顺序不能反过来。一旦先归一化再滑窗,窗口边界信息会因为全局统计量出现泄漏;正确顺序是先按训练集统计量缩放,再做滑窗,或者先滑窗再做窗口内归一化。

3. 用PyTorch搭一个可落地的1D CNN:网络结构与参数选择

滑窗做完,数据已经是(样本, seq_len)的形状,下一步就是建网络。很多人初次接触一维CNN处理序列数据,会直接把二维CNN的代码抄过来换个Conv2d为Conv1d,结果要么报维度错误,要么训练出来一团糟。这里需要理解一维卷积到底在卷什么。

3.1 为什么用Conv1d,而不是把序列转成二维图

Conv1d的卷积核只在时间维度上滑动,输入形状必须是(batch, channels, seq_len)。这里的channels在图像里是RGB三个通道,在时间序列里就是特征数量。单变量序列只有1个通道,多变量序列可以有多个通道。卷积核在每个时间位置同时对所有通道做加权求和,输出一个或若干个滤波器响应。

这种结构天然适合时间序列中的局部模式。比如振动信号的异常冲击往往持续几个采样点,股价的短期反转往往围绕某根均线波动,卷积核能在3到7个点内捕捉这些局部形状。参数共享让同一套卷积核在序列不同位置复用,所以模型参数远少于全连接网络,也远少于LSTM。CNN和RNN的经典争论放在时间序列上,我的经验是:如果你的数据没有特别强的长距离依赖,先跑一维CNN,它的训练速度会快一个数量级,代码也更短。

一维CNN不是没有缺点。它没有显式的门控机制,想要看到很长的历史依赖,必须靠堆层、扩大卷积核或使用空洞卷积。所以很多项目会先用CNN做基线,效果不够再加LSTM或Transformer,这也符合先易后难的调参顺序。

3.2 最小可运行模型:两层Conv1d + 池化 + 全连接

给一个可以直接复制到Jupyter里的最小模型,输入是上一章滑窗得到的(batch, seq_len),输出是(batch, out_steps)。

import torch import torch.nn as nn class TimeSeriesCNN(nn.Module): def __init__(self, in_channels=1, seq_len=48, out_steps=6, kernel_size=3, hidden=32, dropout=0.1): super().__init__() self.conv1 = nn.Conv1d(in_channels, hidden, kernel_size=kernel_size, padding=kernel_size // 2) self.bn1 = nn.BatchNorm1d(hidden) self.conv2 = nn.Conv1d(hidden, hidden * 2, kernel_size=kernel_size, padding=kernel_size // 2) self.bn2 = nn.BatchNorm1d(hidden * 2) self.pool = nn.MaxPool1d(2, 2) self.relu = nn.ReLU() self.dropout = nn.Dropout(dropout) # 两层卷积 + 一次池化后,时间长度变为 seq_len // 2 self.fc = nn.Linear(hidden * 2 * (seq_len // 2), out_steps) def forward(self, x): # 输入形状是 (batch, seq_len, in_channels) x = x.transpose(1, 2) # 转成 (batch, in_channels, seq_len) x = self.relu(self.bn1(self.conv1(x))) x = self.relu(self.bn2(self.conv2(x))) x = self.pool(x) x = self.dropout(x) x = x.flatten(1) return self.fc(x)

代码里最关键的一行是x.transpose(1, 2)。PyTorch中Conv1d默认输入是(batch, channels, seq_len),而我们滑窗出来的(batch, seq_len)补上通道维后通常是(batch, seq_len, 1),所以必须把通道维挪到第二位。如果在训练时报错“Expected 3D input”,90%是这一步没做。

kernel_size用了3,padding=kernel_size // 2是为了让卷积前后时间长度保持不变,这样两层卷积后依然是seq_len,再经过MaxPool1d(2, 2)变成seq_len // 2。全连接层的输入维度是hidden*2乘以seq_len // 2。窗口长度、池化次数一旦改了,这里要跟着改,后面第5章会讲一个更省心的写法。

BatchNorm1d放在激活之前,训练会比裸卷积稳定得多。时间序列数据的分布经常随环境变化,BatchNorm能让每一层的输入尺度相对稳定。dropout设0.1,防止模型在重复窗口上硬记。

3.3 必调参数:kernel_size、padding、dilation与感受野

Conv1d可调参数很多,但时间序列场景下真正需要盯住的主要是下面这几个。

参数名常用值说明
kernel_size3, 5, 7卷积核覆盖的时间点数,越大感受野越大,但容易把噪声学进去
paddingkernel_size // 2保持时间长度不变的常用做法,等效于“same”
dilation1, 2, 4空洞卷积,在不增加参数的前提下扩大感受野
stride1卷积层通常为1,降采样交给池化
in_channels1或特征数单变量序列为1,多变量序列为特征数量
out_channels32, 64第一层卷积核数量,自己是个超参数

感受野决定了一个输出点能看到多长的输入区间。最简单估算方法是:一层卷积的感受野等于kernel_size,两层卷积加一起大约是kernel_size + (kernel_size - 1)乘以中间空洞系数。如果你只有两层的3卷积核,感受野大约5个点,很多周期信号根本看不全。解决办法不是把kernel加到15,而是通过dilation扩大感受野。比如第一层dilation=1、第二层dilation=2,就能在两层的深度下看到大约9个点,参数量不变。

时间序列里我很少用stride>1的卷积层,因为每步都做下采样会让网络的输出对时间对齐太敏感。降采样交给MaxPool1d或者AvgPool1d更稳妥。池化不是必须的,如果序列本身不长,比如窗口只有16,干脆不池化,全连接层直接接卷积输出,避免信息损失。

这里还涉及一个常见选择:要不要在第3章的网络里加入LSTM层?我的建议是先把纯一维CNN调通,再考虑混合。混合模型一旦出现问题,你很难判断是卷积部分还是循环部分在拖后腿。CNN作为特征提取器,后面接一个轻量LSTM的做法确实有效,但不要一开始就上。

4. 训练与评估:损失函数、验证切分和时序模拟的边界

网络搭好之后,训练流程也不能照搬图像分类。很多人拿着train_test_split(random_state=42)就开跑,结果验证loss低得惊人,上线后立刻翻车。时间序列的样本之间有时间先后关系,随机切分会把未来样本混进训练集,这种泄漏会造成一个看起来完美的黑匣子模型。

4.1 时间序列切分:不能按分类任务那样随机打乱

滑窗样本之间存在大量重叠,相邻样本可能只有step个点不同。如果随机切分,同一个时间段既出现在训练集又出现在验证集,模型相当于提前看过答案。正确做法是按时间顺序切分:前80%的数据做训练,后20%做验证。

需要调超参数时,用TimeSeriesSplit而不是普通K折交叉验证。

from sklearn.model_selection import TimeSeriesSplit tscv = TimeSeriesSplit(n_splits=4) X = np.random.randn(800, 48) y = np.random.randn(800, 6) for fold, (train_idx, val_idx) in enumerate(tscv.split(X)): X_train, X_val = X[train_idx], X[val_idx] y_train, y_val = y[train_idx], y[val_idx] print(f"fold {fold}: train {train_idx.min()}-{train_idx.max()}, " f"val {val_idx.min()}-{val_idx.max()}")

TimeSeriesSplit不会打乱顺序,它把数据按时间分成连续块,每次用前面的k块做训练、后面的1块做验证。这能暴露模型在不同时间段的泛化能力。最终报告效果时,我习惯再用一次“只训练到某个时间点,预测未来”的严格切分,因为那才是最接近真实上线的场景。

4.2 训练循环:稳定复现的最小PyTorch代码

下面这段训练代码可以直接跑,已经包含了归一化占位、验证集评估和模型保存。

import torch from torch.utils.data import TensorDataset, DataLoader # X, y 来自滑窗数据集,假设已经按 8:2 时间切分 trainX = torch.from_numpy(X[:800]).float().unsqueeze(1) # (batch, 1, seq_len) trainY = torch.from_numpy(y[:800]).float() valX = torch.from_numpy(X[800:]).float().unsqueeze(1) valY = torch.from_numpy(y[800:]).float() model = TimeSeriesCNN(in_channels=1, seq_len=48, out_steps=6, hidden=32) criterion = nn.MSELoss() optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau( optimizer, mode='min', factor=0.5, patience=5) epochs = 80 best_loss = float('inf') for epoch in range(epochs): model.train() loader = DataLoader(TensorDataset(trainX, trainY), batch_size=64, shuffle=False) running_loss = 0.0 for xb, yb in loader: optimizer.zero_grad() out = model(xb) loss = criterion(out, yb) loss.backward() optimizer.step() running_loss += loss.item() * xb.size(0) train_loss = running_loss / len(trainX) model.eval() with torch.no_grad(): val_pred = model(valX) val_loss = criterion(val_pred, valY).item() scheduler.step(val_loss) if val_loss < best_loss: best_loss = val_loss torch.save(model.state_dict(), 'best_cnn.pt') if (epoch + 1) % 10 == 0: print(f'{epoch+1:02d} train {train_loss:.5f} val {val_loss:.5f}')

关键参数说明:学习率从1e-3开始,如果loss震荡,优先降到3e-4;shuffle=False是因为时间顺序本身不是需要打乱的信息,而且保持False更容易复现结果。ReduceLROnPlateau会在验证loss连续5个epoch不下降时把学习率减半,缓解训练后期震荡。

训练集和验证集归一化要在切分之前只基于训练集统计量完成,代码里注释已经标出。如果在真实项目里用窗口归一化,要在DataLoader里逐个样本做,不能统一处理好后缓存,否则后面验证集会引用训练窗口的均值。

4.3 评估指标:为什么不能只看RMSE

MSE和RMSE是默认损失,但时间序列预测里有个几乎必然出现的问题:如果预测值永远等于最后一个观测值,RMSE也可能很低。原因是很多真实序列的一阶自相关很高,直接把当前值复制到所有未来点,就已经是相当强的基线了。这时候要加一个和业务强相关的指标:方向命中率。

import numpy as np def direction_accuracy(pred, true): # pred 和 true 的形状都是 (样本数, 多步) pred_dir = np.sign(np.diff(pred, axis=1)) true_dir = np.sign(np.diff(true, axis=1)) return float((pred_dir == true_dir).mean())

方向命中率只看预测值相对输入窗口最后一个点是否涨跌正确。它对均值回归特别敏感,如果模型只是在做平滑复制,方向命中率会明显偏低。预测多步时,可以分别计算每一步的方向命中率,比如第3步的方向命中率往往比第1步低,这能告诉你模型的有效预测范围到底多长。

时间序列异常检测场景里,评估指标又不一样。你通常不是直接看预测值误差,而是把预测误差和某个阈值比较。这时更多用F1分数、精确率和召回率,因为异常样本通常稀少。MSE会把几个极端误差放大,掩盖大量小幅误差,所以异常检测里我会改用MAE或HuberLoss。

criterion = nn.SmoothL1Loss() # HuberLoss,对离群点更稳健

SmoothL1Loss在误差小的时候像MSE,误差大的时候像MAE,不会让单个异常样本主导梯度。这个损失在时间序列异常检测里比纯MSE好用得多。

5. 避坑:一维CNN处理时间序列的5个常见问题

这一章全部来自真实项目里的“翻车”记录,按照出现频率从高到低排列。每一条都是现象、原因、解决三层。你照着检查一遍,能省下好几天调试时间。

5.1 输入形状报错:Conv1d期待的到底是几维

现象:代码跑到model(xb)时直接报RuntimeError: Expected 3D input,而且提示输入是2维。

原因:很多人做完滑窗后拿到(batch, seq_len)就直接喂给模型,但Conv1d要求(batch, channels, seq_len)。单变量序列的channels就是1,这个维度不能省。

解决:在训练循环里给输入补一个通道维,或者在__getitem__阶段直接返回3维数组。

# 原输入形状 (batch, seq_len) x = x.unsqueeze(1) # 多变量输入形状 (batch, seq_len, features) x = x.transpose(1, 2)

我建议统一在Dataset.__getitem__里返回(1, seq_len),这样后续模型不用关心数据处理细节。多变量时返回(features, seq_len)。

5.2 训练loss不稳,验证loss乱跳

现象:前10个epoch训练loss下降很快,之后开始震荡;验证loss忽高忽低,像随机游走。

原因:滑窗重叠度高,相邻样本高度相关,小批量内的样本彼此太像,梯度方向非常不稳定。另一个常见原因是学习率太大,Adam默认1e-3有时候对时间序列并不友好。

解决:把滑窗step从1调到3或5,降低样本重复度;学习率先降到3e-4;在网络里加一个BatchNorm1d。如果还不稳定,检查是不是每个batch里混入了量纲差异巨大的特征。多变量序列每个通道单独归一化,否则大数值通道会主导梯度更新。

5.3 预测曲线像是延迟复制,方向命中率不堪入目

现象:把预测值和真实值画在一张图里,发现预测曲线比真实曲线整体“晚”了一会儿,像把输入往后平移了一样。你拿到RMSE还过得去,但方向命中率不到50%,基本等于白做。

原因:序列自身一阶自相关太高,模型学到的最优策略就是把输入窗口最后一两个点复制到未来。这在纯回归指标下是最优的,但业务上毫无价值。也可能是输入窗口太短,没有覆盖完整周期,模型根本没有可用的周期性信息。

解决:对原始序列做一阶差分,让模型去预测变化量,而不是绝对值。

diff_series = np.diff(raw) X, y = make_windows(diff_series, input_len=48, horizon=6, step=3) # 预测得到的是未来的差分值,评估时需要累加还原: pred_cum = raw[input_len] + np.cumsum(pred_diff, axis=-1)

差分之后,模型不再能靠复制当前值偷懒。如果预测目标本身是涨跌方向,直接预测分类标签也可能是更好的选择。这一步是时间序列预测里最常见的翻车点,宁可先差分,不要先叠网络。

5.4 训练loss很低,验证loss很高:过拟合是滑窗出来的

现象:训练集RMSE降到接近0,验证集RMSE是训练集的几倍,模型没什么泛化能力。

原因:step=1时相邻窗口几乎完全重叠,训练集信息高度冗余,模型很容易把特定时间段的绝对数值背下来。卷积核参数数并不算多,但冗余样本把训练变成了记忆任务。

解决:增加step,减少样本重叠。同时加噪声增广,让模型不能记住原始数值,只能学模式。我给序列数据做增广时,一般给输入加均值为0、标准差为该窗口数值标准差1%(或5%)的高斯噪声。

def add_noise(x, noise_std=0.01): noise = torch.randn_like(x) * x.std(dim=-1, keepdim=True) * noise_std return x + noise

噪声只加在训练集的输入上,标签不变,验证集保持干净。数据增广配合Dropout,能明显缓解滑窗重叠带来的过拟合。

5.5 全连接层维度对不上:池化把序列长度算错了

现象:模型前向传播到flatten之后,Linear层报mat1 and mat2 shapes cannot be multiplied。

原因:网络里写了MaxPool1d(2, 2),但没注意到池化只在整除时才能把长度严格减半。如果seq_len是奇数,或者中间做了非对称padding,实际展平长度和hidden * 2 * (seq_len // 2)对不上。

解决:不用手算flatten长度,改用全局平均池化。

# 在最后一层卷积之后用 mean(dim=-1) x = self.relu(self.bn2(self.conv2(x))) x = x.mean(dim=-1) # 每个通道压成一个数,长度维度消失 self.fc = nn.Linear(hidden * 2, out_steps)

全局平均池化把每个通道在时间维度上取平均,输出维度固定为hidden*2,和输入长度彻底解耦。这样换窗口长度时不用改网络结构,模型更普适。缺点是会丢失时间上的精确对齐信息,但作为全连接层之前的过渡,通常利大于弊。

6. 进阶:多变量输入、因果卷积与模型验证技巧

6.1 多变量特征放通道,而不是硬拼成一个数

多变量时间序列不只有一个特征,比如同时采集温度、压力、振动。一维CNN处理序列数据时,最自然的做法是把每个特征作为一个通道,让卷积核同时跨时间和跨特征做组合。

model = TimeSeriesCNN(in_channels=5, seq_len=48, out_steps=6) # forward 时输入形状 (batch, 5, 48)

这比把5个特征上下拼成一条长序列更合理,因为卷积核会在每个时间步同时对5个特征加权,特征之间的交互是局部的、可解释的。注意每个通道单独归一化,否则量纲差异会让模型偏向大数值通道。

6.2 用因果卷积做在线预测

如果你做的是未来时间点的预测,用普通的中心padding会让第t个输出在训练时看到t+1乃至更后面的输入。离线分析无所谓,但一旦上线,数据还没发生,模型就会用“未来”做预测,表现和测试集完全脱节。常见做法是把padding改成kernel_size - 1,也就是所谓的因果卷积。卷积核在当前位置左侧补零,右侧不补,输出只依赖当前及之前的输入。

nn.Conv1d(in_channels, hidden, kernel_size=3, padding=2, dilation=1)

当kernel_size=3时,padding=2可以配合后面的切片只取到seq_len - 1,但更简单的是自己用F.pad(x, (kernel_size - 1, 0))。我在做实时预测项目时一定用因果填充,它能避免一个非常隐蔽的黑匣子问题:训练指标漂亮,上线后第一条预测就走样。

6.3 验证技巧:滚动后测比固定切分更接近真实场景

固定切分只评估一个时间点的模型,滚动后测能评估模型在持续更新下的表现。常见做法是:训练到第T天,预测T+1到T+H,然后把这一段时间的真实值并入训练集,滚动到下一个窗口。用滑窗构造出来的样本,可以复用前面的SlidingWindowDataset来实现。

我自己每次跑完模型,还会画一张“预测 vs 真实 vs 输入窗口末尾”的对齐图。把每个预测点对应到输入窗口末尾之后,如果预测曲线明显贴着输入末尾走,方向命中率又低,就先去做差分或缩短horizon,而不是继续堆网络层数。这个习惯比任何指标都直接,救过我好几次。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 18:57:27

从零开始学大模型应用开发:RAG、Agent与MCP十天实战路线

如果你准备从零开始学 AI 大模型应用开发&#xff0c;最关心的通常不是理论&#xff0c;而是三件事&#xff1a;跑通一个真实可用的 RAG 知识库、写出能调用工具的 Agent、搞懂 MCP 怎么接。这份学习路线围绕这三件事展开&#xff0c;目标是用十天时间完成从调用大模型 API 到做…

作者头像 李华
网站建设 2026/9/29 18:56:12

招聘智能体系统:LangGraph4j+RAG+React全栈实践

1. 这不是又一个“AI招聘页面”&#xff0c;而是一套能自主决策的招聘智能体系统最近帮三家公司重构招聘流程&#xff0c;发现一个扎心事实&#xff1a;90%的所谓“AI招聘系统”只是把关键词搜索包装成“智能推荐”&#xff0c;HR每天仍要手动筛简历、反复追问候选人、协调面试…

作者头像 李华
网站建设 2026/9/29 18:55:58

C# WinForm换肤实战:SkinEngine驱动64套皮肤体系

简介&#xff1a;面向C# WinForm开发者的界面换肤完整源码方案&#xff0c;针对默认界面较为朴素、难以满足个性化需求的问题&#xff0c;提供从皮肤资源管理、皮肤库设计到动态切换与封装复用的实现思路。工程基于Visual Studio 2012开发&#xff0c;代码组织清晰&#xff0c;…

作者头像 李华
网站建设 2026/9/29 18:55:30

AD9361快速锁定机制详解:Profile寄存器配置与BPSK跳频实战

用 AD9361 做过跳频或 TDD 项目的人&#xff0c;大概率都卡过同一道坎&#xff1a;不算复杂的收发链路在低速调试时一切正常&#xff0c;可一旦进入真正的跳频流程&#xff0c;射频本振切换时那几十毫秒校准时间就成了整个系统的瓶颈。网速慢、时序乱、状态机超时&#xff0c;这…

作者头像 李华
网站建设 2026/9/29 18:55:29

ComfyUI+Wan2.2实战:关键词驱动图像超分转视频

简介&#xff1a;面向ComfyUI进阶用户的Wan2.2智能关键词驱动图像超分视频生成工作流配置资源&#xff0c;配套作者CSDN博文讲解ComfyUI使用教程与TauriDjango开源工具平台搭建思路&#xff0c;适合希望借助大模型生成视频、提升图像分辨率并自动匹配关键词的AIGC实践者。压缩包…

作者头像 李华
网站建设 2026/9/29 18:55:20

Hindsight反思机制:在Dify工作流中实现AI自我纠错

我最早看到 Hindsight 这个项目&#xff0c;是被它的名字吸引的。Hindsight&#xff0c;事后聪明&#xff0c;说白了就是我们常说的"事后诸葛亮"。但在一线搞 LLM 应用开发的朋友都知道&#xff0c;这年头不缺事前预判&#xff0c;缺的恰恰是事后的精准复盘和动态修正…

作者头像 李华