PyTorch深度学习实战 | 基于LSTM的时间序列预测任务

本文涉及的产品
RDS DuckDB + QuickBI 企业套餐,8核32GB + QuickBI 专业版
简介: 本文介绍了使用LSTM模型预测印度德里市平均温度的两个项目。项目1对温度数据进行归一化处理,采用滑动窗口法构建监督学习样本,设计5层LSTM网络结构,并详细说明了模型训练过程及评估方法。项目2在数据处理上增加了标准化和周期性特征,改进了网络架构,引入了学习率调整和早停机制优化训练过程。两个项目均通过可视化对比预测值和真实值,验证了LSTM模型在时间序列预测中的有效性。文章从数据处理、模型构建到训练优化,完整呈现了温度预测的实现流程,为时序预测任务提供了实用参考。

 项目1:LSTM 预测mean_temp

数据集介绍

    该数据集提供印度德里市 2013 年 1 月 1 日至 2017 年 4 月 24 日的数据。这里的 4 个参数是

平均温度、湿度、wind_speed、平均压力

image.gif

数据预处理

归一化

          调用MinMaxScaler()函数,把平均温度这一列给归一化。

from sklearn.preprocessing import MinMaxScaler
# 创建MinMaxScaler对象
scaler = MinMaxScaler()
# 将数据进行归一化
meantemp = scaler.fit_transform(meantemp.reshape(-1,1))
print(meantemp)

image.gif

image.gif

整理时间数据

     将时间序列数据转换为适合深度学习模型(如 LSTM、GRU 等时序模型)输入的格式,核心是

构建 “输入特征序列” 和 “目标值” 的对应关系。通过 “滑动窗口法” 将连续的时间序列数据拆分

“历史序列→未来时刻” 的监督学习样本。这里的时间步长是12,然后输出特征是1(只有平均

温度这一个特征),一定要牢记LSTM的输出数据的格式的(批次数,时间步,特征数)

def split_data(data,time_step=12):
    dataX=[]
    datay=[]
    for i in range(len(data)-time_step):
        dataX.append(data[i:i+time_step])
        datay.append(data[i+time_step])
    dataX=np.array(dataX).reshape(len(dataX),time_step,-1)
    datay=np.array(datay)
    return dataX,datay

image.gif

划分训练集和测试集

      根据比例把数据集分成训练集和测试集

#划分训练集和测试集的函数
def train_test_split(dataX,datay,shuffle=True,percentage=0.8):
    """
    将训练数据X和标签y以numpy.array数组的形式传入
    划分的比例定为训练集:测试集=8:2
    """
    if shuffle:
        random_num=[index for index in range(len(dataX))]
        np.random.shuffle(random_num)
        dataX=dataX[random_num]
        datay=datay[random_num]
    split_num=int(len(dataX)*percentage)
    train_X=dataX[:split_num]
    train_y=datay[:split_num]
    test_X=dataX[split_num:]
    test_y=datay[split_num:]
    return train_X,train_y,test_X,test_y

image.gif

image.gif

定义网络结构

     这里我们的网络结构使用了一个5层的LSTM,然后加上一个全连接层(输入是LSTM的h0,输

出是下一天的平均温度。)

input_size = 1,这个是输入特征的维度, 这里每次输入的是 meantemp 这 1 个指标,所以维度是

1。也是输入神经元的个数。

hidden_size = 64,LSTM 内部 “记忆单元” 的数量,数值越大,模型能记住的细节越多,但计算

越慢(类似记事本页数越多,能记的东西越多,但翻起来越费劲)。

num_layers = 5,LSTM 的 “层数”(可以理解为 “处理信息的流水线级数”)。5 层表示信息要经过

5 个 LSTM 模块依次处理:层数越多,模型能捕捉的规律越复杂(但层数过多可能 “想太多”,反而

记混)。

output_size = 1,输出结果的维度。我们要预测的是 “未来 1 天的 平均温度” 这 1 个指标,所

以输出维度是 1。(最后一层神经元的输入是,LSTM最后一层的隐藏层的状态,输出神经元的个

数是1个)

# 定义LSTM模型类
class LSTMModel(nn.Module):
    def __init__(self, input_size, hidden_size, num_layers, output_size):
        super(LSTMModel, self).__init__()
        self.hidden_size = hidden_size
        self.num_layers = num_layers
        self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)
        self.fc = nn.Linear(hidden_size, output_size)
    
    def forward(self, x):
        h0 = torch.zeros(self.num_layers,x.size(0), self.hidden_size) # 初始化隐藏状态h0
        c0 = torch.zeros(self.num_layers,x.size(0), self.hidden_size)  # 初始化记忆状态c0
        #print(f"x.shape:{x.shape},h0.shape:{h0.shape},c0.shape:{c0.shape}")
        out, _ = self.lstm(x, (h0, c0))  # LSTM前向传播
        out = self.fc(out[:, -1, :])  # 取最后一个时间步的输出作为预测结果
        return out
# 定义输入、隐藏状态和输出维度
input_size = 1  # 输入特征维度
hidden_size = 64  # LSTM隐藏状态维度
num_layers = 5  # LSTM层数
output_size = 1  # 输出维度(预测目标维度)
# 创建LSTM模型实例
model = LSTMModel(input_size, hidden_size, num_layers, output_size)

image.gif

模型训练前的准备工作

定义模型--->迭代次数--->批次的数量--->优化器--->损失函数

model就是前面已经设计好了 LSTM 模型,num_epochs就是模型要把训练集的数据 “学几遍”,

batch_size一次学多少条数据,optimizer“优化器”,相当于模型的 “学习方法调整器”。criterion

损失函数”,相当于给模型 “打分的标准”。

# 创建LSTM模型实例
model = LSTMModel(input_size, hidden_size, num_layers, output_size)
#训练周期为500次
num_epochs=500
batch_size=64#一次训练的数量
#优化器
optimizer=optim.Adam(model.parameters(),lr=0.0001,betas=(0.5,0.999))
#损失函数
criterion=nn.MSELoss()
train_losses=[]
test_losses=[]

image.gif

模型的训练和评估

    每一轮的batch都是从 整个数据集中里随机抽的 batch_size条,几乎不会重复,每个50代,保

存一次训练集和测试集的损失。

image.gif

print(f"start")
for epoch in range(num_epochs):
    
    random_num=[i for i in range(len(train_X))]
    np.random.shuffle(random_num)
    
    train_X=train_X[random_num]
    train_y=train_y[random_num]
    
    train_X1=torch.Tensor(train_X[:batch_size])
    train_y1=torch.Tensor(train_y[:batch_size])
    
    #训练
    model.train()
    #将梯度清空
    optimizer.zero_grad()
    #将数据放进去训练
    output=model(train_X1)
    #计算每次的损失函数
    train_loss=criterion(output,train_y1)
    #反向传播
    train_loss.backward()
    #优化器进行优化(梯度下降,降低误差)
    optimizer.step()
    
    if epoch%50==0:
        model.eval()
        with torch.no_grad():
            output=model(test_X1)
            test_loss=criterion(output,test_y1)
        train_losses.append(train_loss)
        test_losses.append(test_loss)
        print(f"epoch:{epoch},train_loss:{train_loss},test_loss:{test_loss}")

image.gif

    假设真实值true_y:[34, 36, 38](实际测的结果),预测值pred_y:[35, 36, 39](模型猜的结

果),这段代码就是为了计算,mes。

def mse(pred_y,true_y):
    return np.mean((pred_y-true_y) ** 2)(

image.gif

   把训练时用的输入数据转换成模型能认的格式(Tensor),把训练集(train_X1)放到模型中输出

(train_pred),把测试集(test_X1)放到模型中输出(test_pred),把训练集的预测结果和测试集的预测

结果 “拼到一起”,变成一个完整的预测列表(pred_y),把压缩后的预测值 “还原” 成原始的单位

(pred_y ),把训练集的真实值和测试集的真实值也 “拼到一起”,变成完整的真实值列表(true_y

)。然后计算预测值和真实值的mse。

train_X1=torch.Tensor(X_train)
train_pred=model(train_X1).detach().numpy()
test_pred=model(test_X1).detach().numpy()
pred_y=np.concatenate((train_pred,test_pred))
pred_y=scaler.inverse_transform(pred_y).T[0]
true_y=np.concatenate((y_train,test_y))
true_y=scaler.inverse_transform(true_y).T[0]
print(f"mse(pred_y,true_y):{mse(pred_y,true_y)}")

image.gif

可视化

plt.title("LSTM model")
x=[i for i in range(len(true_y))]
plt.plot(x,pred_y,marker="o",markersize=1,label="pred_y")
plt.plot(x,true_y,marker="x",markersize=1,label="true_y")
plt.legend()
plt.show()

image.gif



项目2:LSTM 预测mean_temp

数据处理

标准分 =(原始值 - 平均值)÷ 标准差,给原始的数据增加了4列特征。

image.gif

从日期里抽出月份(1-12);把月份当成 “一年 360 度周期里的角度”,算出对应的正弦和余弦

值;新增两列存这些值,让模型能看懂 “月份的周期性”。

image.gif 编辑

如果有 10 天数据,seq_len=7,会生成 3 组序列:

第 1 组:X = 第 1-7 天的 6 个特征,y = 第 8 天的温度

第 2 组:X = 第 2-8 天的 6 个特征,y = 第 9 天的温度

第 3 组:X = 第 3-9 天的 6 个特征,y = 第 10 天的温度

生成后X的形状是(993, 7, 6)(1000-7=993 组,每组 7 天,每天 6 个特征),y的形状是(993, 1)

(993 个目标温度)。(每一组数据的时间步数是7,特征数是6),对应是标签的后面一天的温

度。

batch = 32
seq_len = 7
input_character = ['Z_temp', 'Z_pressure', 'Z_wspeed', 'Z_hum', 'mon_sin', 'mon_cos']
X = torch.tensor(df[input_character].values, dtype = torch.float32)
y = torch.tensor(df['Z_temp'].values, dtype = torch.float32).reshape(-1, 1)
def creat_sequences(data, target, seq_len, len):
    X_seq = []  # 存输入序列(过去7天的特征)
    y_seq = []  # 存对应目标(第8天的温度)
    for i in range(len - seq_len):
        X_seq.append(data[i : i + seq_len])  # 取第i到i+6天(共7天)的特征
        y_seq.append(target[i + seq_len])    # 取第i+7天的温度
    return torch.stack(X_seq), torch.stack(y_seq)  # 转成Tensor格式
X, y = creat_sequences(X, y, seq_len, len(df))  # 调用函数生成序列

image.gif

划分训练集和测试集

X, y = creat_sequences(X, y, seq_len, len(df))
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.3, shuffle=False)
train_data = TensorDataset(X_train, y_train)
val_data = TensorDataset(X_val, y_val)
train_dataloader = DataLoader(dataset = train_data,batch_size = batch,
                              shuffle = True, num_workers = 2)
val_dataloader = DataLoader(dataset = val_data, batch_size = batch, 
                              shuffle = False, num_workers = 2)

image.gif

定义模型

假设输入是 “32 个样本,每个样本是过去 7 天的 6 个特征”(形状 [32,7,6]):

(1)进入lstm层后,每层 64 个记忆单元会 “记住” 7 天里的关键信息(比如温度变化趋势、湿度和

温度的关系),输出 7 天的中间结果(形状 [32,7,64])。

(2)因为要预测第 8 天的温度,最后一天(第 7 天)的中间结果最有用,所以只保留这一天的

64 个特征(形状 [32,64])。

(3)进入linear层后,先压缩成 32 个特征,过滤无效信息,再压缩成 1 个值(每个样本对应 1 个

预测温度),最终输出 [32,1]。

class LSTM(nn.Module):
    def __init__(self, input_size, hidden_size, num_layers, output_size):
        super().__init__()
        
        self.lstm = nn.LSTM(
            input_size = input_size,
            hidden_size = hidden_size,
            num_layers = num_layers,
            batch_first = True,
            dropout = 0.2,
            bidirectional = False
        )
        self.linear = nn.Sequential(
            nn.Linear(hidden_size, hidden_size // 2),
            nn.ReLU(),
            nn.Dropout(0.2),
            nn.Linear(hidden_size // 2, output_size)
        )
    def forward(self, x):
        x, _ = self.lstm(x)
        x = x[:, -1, :]
        x = self.linear(x)
        return x

image.gif

模型训练和评估

和上面一样,也是需要创建模型,定义随时函数和优化器,这个有个比较创新的点在于这里定义了

一个学习率调整和早停机制。

# 创建模型实例:输入6个特征,隐藏层128个单元,2层LSTM,输出1个温度值
MODEL = LSTM(6, 128, 2, 1)
criterion = nn.MSELoss()  # 损失函数(打分标准,算预测值和真实值的差距)
opt = optim.Adam(MODEL.parameters(), lr=0.001)  # 优化器(学习方法,初始学习率0.001)
# 学习率调整器:如果验证损失不再下降,就把学习率缩小到原来的1/10(避免学过头)
scheduler = optim.lr_scheduler.ReduceLROnPlateau(opt, mode='min', factor=0.1, patience=5)
min_loss = np.inf  # 记录目前为止最小的验证损失(初始设为无穷大)
patient = 5  # 最多允许连续5轮损失不下降(超过就停止训练,避免浪费时间)
p_count = 0  # 记录当前连续多少轮损失没下降
epoch = 100  # 计划训练100轮(但可能提前停止)

image.gif

for i in range(epoch):  # 开始100轮训练
    # 第一步:模型切换到“学习模式”
    MODEL.train()
    
    # 用训练集的小批量数据学习
    for X_batch, y_batch in train_dataloader:  # 每次取32组数据(一个batch)
        output = MODEL(X_batch)  # 模型预测温度
        loss = criterion(output, y_batch)  # 算预测和真实值的差距(损失)
        
        opt.zero_grad()  # 清空之前的错误记录
        loss.backward()  # 分析错误原因(反向传播)
        opt.step()  # 优化器调整模型参数(改正错误)
    
    # 第二步:模型切换到“验证模式”(不更新参数,只看效果)
    MODEL.eval()
    
    with torch.no_grad():  # 关闭梯度计算(节省资源,不记录错误)
        total_loss = 0  # 记录验证集的总损失
        pred = []  # 存所有验证集的预测值
        label = []  # 存所有验证集的真实值
        
        # 用验证集检验学习效果
        for X_batch, y_batch in val_dataloader:
            output = MODEL(X_batch)  # 模型预测验证集温度
            val_loss = criterion(output, y_batch)  # 算验证损失
            pred.append(output.numpy())  # 存预测值(转成数组)
            label.append(y_batch.numpy())  # 存真实值
            total_loss += val_loss.item()  # 累加验证损失
    
    # 整理预测值和真实值(拼接成完整列表)
    pred = np.concatenate(pred).flatten()  # 把多个batch的预测值拼在一起,转成一维
    label = np.concatenate(label).flatten()  # 同理处理真实值
    
    # 计算R²分数(越接近1,说明预测和真实值越吻合)
    r2 = r2_score(label, pred)
    
    # 调整学习率:如果验证损失没下降,就缩小学习率
    scheduler.step(total_loss)
    
    # 每3轮打印一次成绩
    if not (i + 1) % 3:
        print(f'[{i + 1} / {100}] Loss = {total_loss:.4f} ; r2 = {r2:.4f}')
    
    # 保存最好的模型
    if total_loss < min_loss:  # 如果当前验证损失比之前最小的还小
        min_loss = total_loss  # 更新最小损失
        p_count = 0  # 重置连续不下降的计数
        torch.save(MODEL.state_dict(), 'Climate_LSTM.pth')  # 保存当前模型参数(相当于存档)
    else:  # 如果损失没下降
        p_count += 1  # 连续不下降计数+1
    
    # 早停机制:如果连续5轮损失没下降,就停止训练
    if p_count > patient:
        print('early stopped')
        break

image.gif

测试

# 准备存预测结果的空列表
y_pred = []
# 关闭梯度计算(只做预测,不训练模型)
with torch.no_grad():
    # 遍历测试集的每一批数据
    for X_batch, _ in tst_dataloader:
        # 用最终模型预测温度
        output_final = model_final(X_batch)
        # 把预测结果转成数组,存到列表里
        y_pred.append(output_final.numpy())
# 把所有批次的预测结果拼在一起,转成一维数组
y_pred = np.concatenate(y_pred).flatten()
# 把标准化的预测值转回原始温度单位(乘以标准差+加上平均值)
y_pred = y_pred * t_std + t_mean
# 画真实温度曲线:蓝色,标签为“真实温度”
plt.plot(df_t['date'].iloc[7:], df_t['meantemp'].iloc[7:], color = 'skyblue', label = 'true temp')
# 画预测温度曲线:绿色,标签为“预测温度”
plt.plot(df_t['date'].iloc[7:], y_pred, color = 'green', label = 'pred temp')
# 设置x轴刻度:从0到114,每隔15个点标一个日期,文字旋转45度
plt.xticks(range(0, 114, 15), rotation = 45)
# 显示图例(区分两条线)
plt.legend()
# 显示图像
plt.show()

image.gif


把一张纸分成 4 个小格子,分别画出 “第 1 年、第 2 年、第 3 年、第 4 年的前 120 天温度曲线。

```python
# 创建一个2行2列的子图布局(共4个子图),当前绘制第1个子图(左上位置)
plt.subplot(2, 2, 1)
# 绘制数据中前120天的日期与平均温度曲线(展示第1段时间的温度变化)
plt.plot(df['date'].iloc[:120], df['meantemp'].iloc[:120])
# 设置x轴刻度:从0到114(覆盖120天数据范围),每隔15天显示一个刻度,文字旋转45度避免重叠
plt.xticks(range(0, 114, 15), rotation = 45)
# 定位到2行2列布局中的第2个子图(右上位置)
plt.subplot(2, 2, 2)
# 绘制第365天到第485天的日期与平均温度曲线(约第2年同期,共120天)
plt.plot(df['date'].iloc[365:485], df['meantemp'].iloc[365:485])
# 同上设置x轴刻度,保证与其他子图显示格式一致
plt.xticks(range(0, 114, 15), rotation = 45)
# 定位到2行2列布局中的第3个子图(左下位置)
plt.subplot(2, 2, 3)
# 绘制第730天到第852天的日期与平均温度曲线(约第3年同期,共122天,接近120天)
plt.plot(df['date'].iloc[730:852], df['meantemp'].iloc[730:852])
# 统一x轴刻度格式
plt.xticks(range(0, 114, 15), rotation = 45)
# 定位到2行2列布局中的第4个子图(右下位置)
plt.subplot(2, 2, 4)
# 绘制第1096天到第1216天的日期与平均温度曲线(约第4年同期,共120天)
plt.plot(df['date'].iloc[1096:1216], df['meantemp'].iloc[1096:1216])
# 统一x轴刻度格式
plt.xticks(range(0, 114, 15), rotation = 45)
# 自动调整子图之间的间距,避免标题、刻度等元素重叠
plt.tight_layout()
# 显示绘制好的包含4个子图的整体图像
plt.show()
```

image.gif


目录
相关文章
|
16天前
|
人工智能 自然语言处理 文字识别
阿里云百炼Qwen3.7-Max简介:能力、优势、支持订阅计划参考
Qwen3.7-Max是阿里云百炼面向智能体时代推出的新一代旗舰模型,对标GPT-5.5、Claude Opus 4.7等闭源旗舰。该模型支持百万级token上下文窗口,具备顶级推理能力、多模态搜索与视觉理解增强、流式输出低延迟响应等核心优势,覆盖编程、办公、长周期自主执行等复杂场景。同时支持OpenAI接口兼容,便于系统快速迁移。用户可通过Token Plan团队或节省计划等订阅方式灵活调用,适合企业级高要求场景使用。
5871 30
阿里云百炼Qwen3.7-Max简介:能力、优势、支持订阅计划参考
|
1天前
|
数据采集 人工智能 前端开发
让 Coding Agent 从黑盒到透明:阿里云 Agent 观测审计数据采集实践
AI Agent 规模化落地带来执行黑盒、行为难追溯、成本难度量三大难题。阿里云基于 OTel 标准,面向 Coding Agent、个人通用助理和框架型 Agent,推出 LoongSuite Pilot、插件及探针等无侵入采集方案,让 Agent 实现可看见、可分析、可审计、可治理。
561 134
|
10天前
|
存储 定位技术 数据库
CodeGraph 如何让 Claude Code减少 7 成工具调用?
CodeGraph 为 Coding Agent 提供本地代码知识图谱,把函数、类、调用链和框架路由提前整理成“项目地图”,减少盲目搜索和文件读取。它不是新 Agent,而是上下文基础设施,让 Agent 更快找到正确代码路径,平均减少 7 成工具调用。
1177 2
|
8天前
|
人工智能 安全 定位技术
CodeGraph深度解析 让Claude Code工具调用直降七成的核心原理与实操教程
如今以Claude Code为代表的AI编程智能体已经成为开发者日常编码、项目重构、漏洞修复的必备工具。但在长期使用过程中,几乎所有开发者都会遇到同一个明显痛点:AI虽然具备强大的代码生成与分析能力,却常常陷入盲目探索的循环中。
959 1
|
17天前
|
人工智能 自然语言处理 供应链
|
8天前
|
人工智能 弹性计算 安全
阿里云618活动时间、活动入口、优惠活动详细解读
2026年阿里云618创新加速季已全面开启,作为年度力度最大的云产品促销活动,本次大促覆盖轻量应用服务器、ECS云服务器、GPU云服务器、数据库、AI算力、安全服务、CDN等全品类产品,推出5亿元算力补贴、新用户限时秒杀、普惠满减、企业专享、免费试用、云大使返佣等多重福利,个人开发者、中小企业、AI团队均可享受专属低价。本文将系统梳理2026年阿里云618活动的完整时间节点、官方参与入口、各类优惠细则、使用规则、热门产品推荐及实操代码,帮助用户精准参与、高效省钱,以最低成本完成上云部署。
764 4
|
8天前
|
运维
欢迎报名|2026 Agentic AICon—智能体基础设施与AgentOps专场,邀您参会
欢迎报名|2026 Agentic AICon—智能体基础设施与AgentOps专场,邀您参会
1432 0