项目1:LSTM 预测mean_temp
数据集介绍
该数据集提供印度德里市 2013 年 1 月 1 日至 2017 年 4 月 24 日的数据。这里的 4 个参数是
平均温度、湿度、wind_speed、平均压力。
数据预处理
归一化
调用MinMaxScaler()函数,把平均温度这一列给归一化。
from sklearn.preprocessing import MinMaxScaler # 创建MinMaxScaler对象 scaler = MinMaxScaler() # 将数据进行归一化 meantemp = scaler.fit_transform(meantemp.reshape(-1,1)) print(meantemp)
整理时间数据
将时间序列数据转换为适合深度学习模型(如 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
划分训练集和测试集
根据比例把数据集分成训练集和测试集
#划分训练集和测试集的函数 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
定义网络结构
这里我们的网络结构使用了一个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)
模型训练前的准备工作
定义模型--->迭代次数--->批次的数量--->优化器--->损失函数
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=[]
模型的训练和评估
每一轮的batch都是从 整个数据集中里随机抽的 batch_size条,几乎不会重复,每个50代,保
存一次训练集和测试集的损失。
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}")
假设真实值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)(
把训练时用的输入数据转换成模型能认的格式(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)}")
可视化
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()
项目2:LSTM 预测mean_temp
数据处理
标准分 =(原始值 - 平均值)÷ 标准差,给原始的数据增加了4列特征。
从日期里抽出月份(1-12);把月份当成 “一年 360 度周期里的角度”,算出对应的正弦和余弦
值;新增两列存这些值,让模型能看懂 “月份的周期性”。
编辑
如果有 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)) # 调用函数生成序列
划分训练集和测试集
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)
定义模型
假设输入是 “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
模型训练和评估
和上面一样,也是需要创建模型,定义随时函数和优化器,这个有个比较创新的点在于这里定义了
一个学习率调整和早停机制。
# 创建模型实例:输入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轮(但可能提前停止)
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
测试
# 准备存预测结果的空列表 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()
把一张纸分成 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() ```