LSTM:长短期记忆网络

长短期记忆网络 LSTM

长短期记忆网络(Long Short-Term Memory,简称LSTM)是一种特殊的递归神经网络(RNN),它被设计用来解决传统RNN在处理长序列数据时遇到的梯度消失或梯度爆炸问题。LSTM通过引入三个门控机制来控制信息的流动,从而能够学习到长期依赖关系。

LSTM的关键组成部分包括:

  1. 遗忘门(Forget Gate):决定从上一个状态中丢弃哪些信息。遗忘门通过sigmoid函数输出0到1之间的值,表示保留信息的程度。

  2. 输入门:由两个部分组成,一个是更新门(Update Gate),它决定哪些新信息将被存储在单元状态中;另一个是候选状态(Candidate State),它通过tanh函数生成新的候选值,这些值将被加入到当前单元状态中。

  3. 输出门(Output Gate):决定下一个隐藏状态的值,这通常用于预测下一个时间步的输出。

LSTM单元的工作原理可以概括为以下几个步骤:

  • 遗忘门决定从细胞状态中丢弃哪些信息。
  • 输入门的更新门决定哪些新信息将被加入到细胞状态中,而候选状态生成这些新信息。
  • 更新后的细胞状态通过细胞状态向量和隐藏状态向量更新。
  • 输出门决定隐藏状态的输出,这通常用于下一个时间步的预测。

LSTM因其能够处理长序列数据而广泛应用于自然语言处理、语音识别、时间序列预测等领域。

传统的循环神经网络(RNN)虽然建立了不同时刻隐藏层之间的关系,实现了记忆的效果,但只是基于前一时刻,是一种short memory。为了实现长期记忆,RNN开始引入一种门电路结构来“记笔记”,从而实现长期记忆。

  • 我们先看RNN的平面结构,如下图所示:

  • 和RNN相比,LSTM增加了一条新的时间链,记录长期记忆(Long Term Memory),我们这里用C表示,同时它增加了两条链之间的关系,见下图所示

  • 新增加的链条C就是相当于记事本。以t时刻为例,和RNN相比,t时刻的隐层输入除了来自输入\(x_t\)\(s_{t-1}\),还包含记事\(c_t\)的信息,如下图所示:

  • 让我们把\(S_t\)\(c_t\)之间的关联放大看清楚:一条线拆成三条线:

将删除和增加操作结合起来得到输出:

\[ c_t = f_1 * c_{t-1} + f2 \]

\(c_t\)除了会被向下传递,还会用来更新当前短期记忆\(s_t\),最后可以计算输出得到\(y_t\),同时保持短期记忆链\(s\)和长期记忆链\(c\)并相互更新.

聊聊LSTM的梯度消失与梯度爆炸 LSTM的梯度消失 首先明确,真正意义上来说,LSTM是不会梯度消失的(解决了RNN的问题,所以为啥呢?)。

LSTM的梯度消失与MLP 或者CNN中梯度消失不一样。MLP/CNN中不同层有不同的参数,梯度各算各的;RNN中同样的权重在各时间步共享参数,最终的梯度是各时间步梯度之和。即使梯度越传越弱,远距离的梯度接近消失,近距离的梯度还在,所以总的梯度之和不会消失。进一步说,LSTM的梯度由RNN结构的梯度和线性变换函数的梯度组成。即使RNN的梯度接近0,线性变换函数的梯度就是其斜率,是一个常数,LSTM的梯度趋向于一个常数,这就解决了梯度消失的问题。 LSTM所谓的梯度消失:梯度被近距离梯度主导,导致模型难以学到远距离的依赖关系。

一个简单且熟悉的例子:使用LSTM预测大气臭氧浓度

  • 数据预处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

import torch
import numpy as np
import pandas as pd
import xgboost as xgb
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
from tqdm import tqdm
import os
from sklearn.preprocessing import StandardScaler
from torch.utils.data import DataLoader,Dataset
from sklearn.model_selection import train_test_split,GridSearchCV
from sklearn.metrics import mean_squared_error,mean_absolute_error,r2_score

data=pd.read_csv("./datasets/datasets_beijing.csv")
data["AQI"]=data["AQI"].apply(lambda x:int(str(x).replace("—","0")))
data["CO"]=data["CO"].apply(lambda x:float(str(x).replace("—","0")))
data["CO_24h"]=data["CO_24h"].apply(lambda x:float(str(x).replace("—","0")))
data["NO2"]=data["NO2"].apply(lambda x:float(str(x).replace("—","0")))
data["PM10"]=data["PM10"].apply(lambda x:float(str(x).replace("—","0")))
data["PM2_5"]=data["PM2_5"].apply(lambda x:float(str(x).replace("—","0")))
data["SO2"]=data["SO2"].apply(lambda x:float(str(x).replace("—","0")))
data["O3"]=data["O3"].apply(lambda x:float(str(x).replace("—","0")))

datasets=data.loc[:,['AQI', 'CO', 'CO_24h', 'Latitude', 'Longitude', 'NO2', 'PM10', 'PM2_5', 'SO2','O3']].values
datasets=StandardScaler().fit_transform(datasets)
datasets_lstm,labels=[],[]
n_window=10
for i in tqdm(range(len(data)-n_window)):
data_temp=datasets[i:i+n_window,:-1]
data_temp_flatten=data_temp.flatten()
datasets_lstm.append(data_temp)
datasets_xgb.append(data_temp_flatten)
labels.append(datasets[i+n_window,-1])

datasets_lstm=np.array(datasets_lstm)
labels=np.array(labels)
print(datasets_lstm.shape,labels.shape)

datasets_lstm_train,datasets_lstm_test,labels_train,labels_test=train_test_split(datasets_lstm,labels,train_size=0.8)

  • 构造数据loader
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class DataGenerator(Dataset):

def __init__(self,x,y):
super(DataGenerator, self).__init__()
self.x,self.y=x,y

def __len__(self):
return len(self.y)

def __getitem__(self, item):
return torch.FloatTensor(self.x[item,:,:]),torch.FloatTensor([self.y[item]])

train_loader=DataLoader(DataGenerator(x=datasets_lstm_train,y=labels_train),batch_size=64,shuffle=True)
val_loader=DataLoader(DataGenerator(x=datasets_lstm_test,y=labels_test),batch_size=128,shuffle=False)
  • LSTM模型构建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class LstmModel(nn.Module):

def __init__(self):
super(LstmModel, self).__init__()
self.lstm1=nn.LSTM(input_size=9,hidden_size=16,bidirectional=True,batch_first=True)
self.fc=nn.Sequential(
nn.Linear(32,128),
nn.LeakyReLU(),
nn.Linear(128,1)
)

def forward(self,x):
outputs1,(h1,c1)=self.lstm1(x)
out1=torch.cat([h1[0,:,:],h1[1,:,:]],dim=-1)

return self.fc(out1)
  • 训练模型以及可视化训练效果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def train(model_name):
device=torch.device("cpu")
model=LstmModel().to(device)

optimizer=optim.Adam(model.parameters(),lr=1e-5)
loss_func=nn.MSELoss()

scheduler=optim.lr_scheduler.StepLR(optimizer,step_size=5,gamma=0.9)

# 训练准确率train_accs和训练损失train_losses
train_accs,train_losses=[],[]
val_accs,val_losses=[],[]
min_val_loss=1000
for epoch in range(30):

train_loss = train_one_epoch(model,train_loader,optimizer,scheduler,loss_func,device,epoch)

val_loss=get_val_result(model,val_loader,loss_func,device)

train_losses.append(train_loss)

val_losses.append(val_loss)

print(f"model:{model_name},epoch:{epoch + 1},train loss:{train_loss},val loss:{val_loss}")

if val_loss<min_val_loss:
torch.save(model,f"models/{model_name}_best.pth")
min_val_loss=val_loss
if (epoch+1) % 5 == 0:
torch.save(model,f"models/{model_name}_epoch{epoch+1}.pth")

plot_acc_loss(train_losses,val_losses,model_name)

def train_one_epoch(model,train_loader,optimizer,scheduler,loss_func,device,epoch):

model.train()

data_tqdm=tqdm(train_loader)

losses=[]

for batch,(x,y) in enumerate(data_tqdm):
# x_train,y_train=x.to(device),y.to(device)
output=model(x_train)
loss=loss_func(output,y_train)
losses.append(loss.item())
optimizer.zero_grad()
loss.backward()
optimizer.step()
data_tqdm.set_description_str(f"epoch:{epoch+1},batch:{batch+1},loss:{loss.item()},lr:{scheduler.get_last_lr()[0]:.7f}")
scheduler.step()

losses=float(np.mean(losses))

return losses


def get_val_result(model,val_loader,loss_func,device):
model.eval()
data_tqdm=tqdm(val_loader)
losses=[]
with torch.no_grad():
for x,y in data_tqdm:
x_val,y_val=x.to(device),y.to(device)
output=model(x_val)
loss=loss_func(output,y_val)
losses.append(loss.item())

return float(np.mean(losses))

def plot_acc_loss(train_losses,val_losses,model_name):
plt.figure(figsize=(10,5))
plt.plot(range(1,len(train_losses)+1),train_losses,"r",label="train")
plt.plot(range(1,len(val_losses)+1),val_losses,"g",label="val")
plt.title(f"{model_name}_loss-epoch")
plt.xlabel("epoch")
plt.ylabel("loss")
plt.legend()
plt.savefig(f"images/{model_name}_epoch_acc_loss.jpg")

LSTM:长短期记忆网络
https://fu-jingqi.github.io/2024/08/01/LSTM:长短期记忆网络/
作者
coderfjq
发布于
2024年8月1日
许可协议