๐ ๊ณต๋ถํ๋ ์ง์ง์ํ์นด๋ ์ฒ์์ด์ง?
TimeSeries DeepLearing [Encoder-Decoder LSTM (=seq2seq)] ๋ณธ๋ฌธ
TimeSeries DeepLearing [Encoder-Decoder LSTM (=seq2seq)]
์ง์ง์ํ์นด 2022. 9. 5. 14:51220905 ์์ฑ
<๋ณธ ๋ธ๋ก๊ทธ๋ doheon, codlingual ๋์ ๋ธ๋ก๊ทธ๋ฅผ ์ฐธ๊ณ ํด์ ๊ณต๋ถํ๋ฉฐ ์์ฑํ์์ต๋๋ค :-) >
[์ฝ๋๊ตฌํ] Time Series Forecasting - LSTM (seq2seq)
Time Series Forecasting ํ๋ก์ ํธ
doheon.github.io
https://codlingual.tistory.com/90
Intro to Encoder-Decoder LSTM(=seq2seq) ๋ฒ์ญ ๋ฐ ์ ๋ฆฌ
์ถ์ฒ 1) Encoder-Decoder Long Short-Term Memory Networks 2) A Gentle Introduction to LSTM Autoencoders 3) Step-by-step Understanding LSTM Autoencoder layers Encoder-Decoder LSTM (=seq2seq) - input๋..
codlingual.tistory.com
๐1. Encoder-Decoder LSTM (=seq2seq)
- ์ฅ๋จ๊ธฐ ๊ธฐ์ต(LSTM) ์ ๊ฒฝ๋ง์ ์ฌ์ฉํ์ฌ ์๊ณ์ด ๋ฐ์ดํฐ๋ฅผ ์ ๋ง
- ์ํ์ค์ ๋ฏธ๋์ ์๊ฐ ์คํ ๊ฐ์ ์ ๋งํ๊ธฐ ์ํด ์๋ต ๋ณ์๊ฐ ๊ฐ์ด ์๊ฐ ์คํ ํ๋๋งํผ ์ด๋๋ ํ๋ จ ์ํ์ค์ธ sequence-to-sequence ํ๊ท LSTM ์ ๊ฒฝ๋ง์ ํ๋ จ
- LSTM ์ ๊ฒฝ๋ง์ ์ ๋ ฅ ์ํ์ค์ ๊ฐ ์๊ฐ ์คํ ๋ง๋ค ๋ค์ ์๊ฐ ์คํ ์ ๊ฐ์ ์์ธกํ๋๋ก ํ์ต
- input๋ sequencial ๋ฐ์ดํฐ, output๋ sequencial ๋ฐ์ดํฐ
- (๋ฌธ์ ) input๊ณผ output์ sequence ๊ธธ์ด๊ฐ ๋ค๋ฅผ ์ ์์
- (ํด๊ฒฐ) Encoding : ์ฌ๋ฌ ๊ธธ์ด์ input์ ๊ณ ์ ๊ธธ์ด ๋ฒกํฐ๋ก ๋ณํ → Decoding : ๊ณ ์ ๊ธธ์ด ๋ฒกํฐ๋ฅผ ํด๋
ํ์ฌ ์ถ๋ ฅ ํ๋ฆฐํธ
- Encoder-Decoder LSTM ๋ชจ๋ธ์ ๋ค์ํ ๊ธธ์ด์ ์๊ณ์ด ์ ๋ ฅ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์, ๋ค์ํ ๊ธธ์ด์ ์๊ณ์ด ์ถ๋ ฅ ๋ฐ์ดํฐ๋ฅผ ๋ง๋ค ์ ์์
- LSTM Autoencoder๋ ๋ค์ํ ๊ธธ์ด์ ์๊ณ์ด input ๋ฐ์ดํฐ๋ฅผ ๊ณ ์ ๊ธธ์ด ๋ฒกํฐ๋ก ์์ถํด Decoder์ ์ ๋ ฅ์ผ๋ก ์ ๋ฌํด์ค
- ์ ๋ ฅ ๋ฐ์ดํฐ๋ฅผ encoded feature vector๋ก ๋ณํํ๋ ๊ณผ์ ์ด ์์
- Teacher Forcing
- ๋ค์ ๋ฌธ์ฅ์ input์ผ๋ก ์ด์ ๊ฐ์ output์ด ์๋ ์๋ target ๊ฐ์ด input์ผ๋ก ๋ค์ด๊ฐ๊ฒ ๋๋ ๊ฒ
- ์ฅ์
- ํ์ต ์ด๊ธฐ ๋จ๊ณ์์๋ ๋ชจ๋ธ์ ์์ธก ์ฑ๋ฅ์ด ๋์๋ค
- Teacher Forcing์ ์ด์ฉํ์ง ์์ผ๋ฉด ์๋ชป๋ ์์ธก ๊ฐ์ ํ ๋๋ก Hidden State ๊ฐ์ด ์ ๋ฐ์ดํธ๋๊ณ , ์ด ๋๋ฌธ์ ๋ชจ๋ธ์ ํ์ต ์๋๋ ๋๋์ง๊ฒ ๋จ
- ํ์ต์ด ๋น ๋ฅด๋ค
- ๋จ์
- ์ถ๋ก (Inference) ๊ณผ์ ์์๋ Ground Truth๋ฅผ ์ ๊ณตํ ์ ์๋ค
- ๋ชจ๋ธ์ ์ ๋จ๊ณ์ ์๊ธฐ ์์ ์ ์ถ๋ ฅ๊ฐ์ ๊ธฐ๋ฐ์ผ๋ก ์์ธก์ ์ด์ด๊ฐ์ผํจ
- ๋ ธ์ถ ํธํฅ ๋ฌธ์ (Exposure Bias Problem)
- ์ฅ์
- ๋ค์ ๋ฌธ์ฅ์ input์ผ๋ก ์ด์ ๊ฐ์ output์ด ์๋ ์๋ target ๊ฐ์ด input์ผ๋ก ๋ค์ด๊ฐ๊ฒ ๋๋ ๊ฒ
๐2. ์ฝ๋๊ตฌํ
- ํ ์๊ฐ ๊ฐ๊ฒฉ์ผ๋ก ์ธก์ ๋์ด ์๋ ํ ๋ฌ์น ํน์ ๊ตฌ๊ฐ์ ํ๊ท ์๋ ๋ฐ์ดํฐ๋ฅผ ์ด์ฉํ์ฌ ๋ง์ง๋ง ์ผ์ฃผ์ผ ๊ฐ์ ํ๊ท ์๋๋ฅผ ์์ธก
1๏ธโฃ Load Data
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from tqdm import trange # ์์
์งํ๋ฅ ํ์
import random
data = pd.read_csv("์์ธ์ฒIC-๋ถํIC ํ๊ท ์๋.csv", encoding='CP949')
plt.figure(figsize=(20,5))
plt.plot(range(len(data)), data["ํ๊ท ์๋"])
data.head() # ๋
, ์, ์ผ, ์ ๊ฐ ์๋ column๊ณผ ํ๊ท ์๋์ ๊ฐ์ด ์๋ column
2๏ธโฃ Data Preprocessing
data = data.drop(columns='Unnamed: 0')
data
from sklearn.preprocessing import MinMaxScaler
# sklearn์ MinMaxScaler๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ์ ๋ฒ์๋ฅผ 0~1๋ก ๋ง๋ค์ด์ค๋ค.
min_max_scaler = MinMaxScaler()
# ์ค์ผ์ผ์ ์กฐ์ ํ๋ ์ ๊ทํ ํจ์
data["ํ๊ท ์๋"] = min_max_scaler.fit_transform(data["ํ๊ท ์๋"].to_numpy().reshape(-1,1))
- ๋ง์ง๋ง ์ผ์ฃผ์ผ์ ๋ฐ์ดํฐ๋ฅผ ์์ธกํ๋ ๊ฒ์ด ๋ชฉํ์ด๋ฏ๋ก train, test set์ ๋ง์ง๋ง ์ผ์ฃผ์ผ์ ๊ธฐ์ค์ผ๋ก ๋๋ ์ค
train = data[:-24*7]
train = train["ํ๊ท ์๋"].to_numpy()
test = data[-24*7:]
test = test["ํ๊ท ์๋"].to_numpy()
3๏ธโฃ Sliding Window Dataset
- ํ์ต์ ์ํด์๋ ์ธํ ๋ฐ์ดํฐ์ ์์ํ ๋ฐ์ดํฐ๊ฐ ํ์
- ์๊ณ์ด ์์ธก์ ์ํด ๋ฐ์ดํฐ์ ์ผ์ ํ ๊ธธ์ด์ input window, output window๋ฅผ ์ค์ ํ๊ณ , ๋ฐ์ดํฐ์ ์ฒ์ ๋ถ๋ถ๋ถํฐ ๋๋ถ๋ถ๊น์ง sliding ์์ผ์ ๋ฐ์ดํฐ์
์ ์์ฑ
- input window, output window, stride๋ฅผ ์ ๋ ฅ๋ฐ๊ณ iw+ow๋งํผ์ ๊ธธ์ด๋ฅผ stride๊ฐ๊ฒฉ์ผ๋ก slidingํ๋ฉด์ ๋ฐ์ดํฐ์ ์ ์์ฑ
- ๊ฒฐ๊ณผ์ ์ฒซ ๋ฒ์งธ ๊ฐ์ผ๋ก๋ input, ๋ ๋ฒ์งธ ๊ฐ์ผ๋ก๋ output์ด ์ถ๋ ฅ๋๋๋ก ์ ์ธ
# torch์ Dataset ํด๋์ค๋ฅผ ์ฌ์ฉํ์ฌ window dataset์ ์์ฑํ๋ ํด๋์ค๋ฅผ ์ ์ธ
from torch.utils.data import DataLoader, Dataset
class windowDataset(Dataset): # Sliding ํจ์
def __init__(self, y, input_window, output_window, stride=1):
#์ด ๋ฐ์ดํฐ์ ๊ฐ์
L = y.shape[0]
#stride์ฉ ์์ง์ผ ๋ ์๊ธฐ๋ ์ด sample์ ๊ฐ์
num_samples = (L - input_window - output_window) // stride + 1
#input๊ณผ output : shape = (window ํฌ๊ธฐ, sample ๊ฐ์)
X = np.zeros([input_window, num_samples])
Y = np.zeros([output_window, num_samples])
for i in np.arange(num_samples):
start_x = stride*i
end_x = start_x + input_window
X[:,i] = y[start_x:end_x]
start_y = stride*i + input_window
end_y = start_y + output_window
Y[:,i] = y[start_y:end_y]
X = X.reshape(X.shape[0], X.shape[1], 1).transpose((1,0,2))
Y = Y.reshape(Y.shape[0], Y.shape[1], 1).transpose((1,0,2))
self.x = X
self.y = Y
self.len = len(X)
def __getitem__(self, i):
return self.x[i], self.y[i]
def __len__(self):
return self.len
- ์ผ์ฃผ์ผ ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ์์ธกํด์ผ ํ๋ฏ๋ก output window์ ํฌ๊ธฐ๋ 24*7๋ก ์ค์ ํ๊ณ input window์ ํฌ๊ธฐ๋ ๊ทธ ๋๋ฐฐ๋ก ์ค์
- dataset์ ์ด์ฉํ์ฌ torch ์ DataLoader๋ฅผ ์์ฑํด์ ๋ฐฐ์น ํ์ต์ ์งํ
iw = 24*14
ow = 24*7
train_dataset = windowDataset(train, input_window=iw, output_window=ow, stride=1)
# ๋ชจ๋ธ ํ์ต ๊ณผ์ ์ ๊ฐ step ๋ง๋ค ๋ฐ์ดํฐ๋ฅผ batch size ํฌ๊ธฐ๋ก ๋ถํ
train_loader = DataLoader(train_dataset, batch_size=64)
4๏ธโฃ 4. Modeling
- torch์ nn.Module์ ์ด์ฉํ์ฌ encoder, decoder ๋ชจ๋ธ์ ๋ง๋ค๊ณ ์ด ๋์ ํฉ์ณ์ encoder decoder ๋ชจ๋ธ
- encoder : input์ ํตํด decoder์ ์ ๋ฌํ hidden state ์์ฑ
- decoder : intput์ ๋ง์ง๋ง ๊ฐ๊ณผ encoder์์ ๋ฐ์ hidden state๋ฅผ ์ด์ฉํ์ฌ ํ ๊ฐ์ ๊ฐ์ ์์ธก
- encoder decoder : ์์ ๋ ๋ชจ๋ธ์ ํฉ์ณ์ค
- ์ํ๋ ๊ธธ์ด์ ์์ํ์ด ๋์ฌ ๋๊น์ง decoder๋ฅผ ์ฌ๋ฌ๋ฒ ์คํ์์ผ์ ์ต์ข output์ ์์ฑ
- ์ํํ ํ์ต์ ์ํด ๋์ฝ๋์ ์ธํ์ผ๋ก ์ค์ ๊ฐ์ ๋ฃ๋ teach forcing์ ๊ตฌํ
- input ์ผ๋ก๋ถํฐ ์ ๋ ฅ์ ๋ฐ๊ณ lstm์ ์ด์ฉํ์ฌ ๋์ฝ๋์ ์ ๋ฌํ hidden state๋ฅผ ์์ฑ
# ๋ชจ๋ ๋ด๋ด ๋คํธ์ํฌ ๋ชจ๋์ ๊ธฐ๋ณธ ํด๋์ค
import torch.nn as nn
class lstm_encoder(nn.Module):
def __init__(self, input_size, hidden_size, num_layers = 1):
super(lstm_encoder, self).__init__()
self.input_size = input_size
self.hidden_size = hidden_size
self.num_layers = num_layers
self.lstm = nn.LSTM(input_size = input_size, hidden_size = hidden_size, num_layers = num_layers, batch_first=True)
def forward(self, x_input):
lstm_out, self.hidden = self.lstm(x_input)
return lstm_out, self.hidden
โถ lstm decoder
- sequence์ ์ด์ ๊ฐ ํ๋์, ์ด์ ๊ฒฐ๊ณผ์ hidden state๋ฅผ ์
๋ ฅ ๋ฐ์์ ๋ค์ ๊ฐ ํ๋๋ฅผ ์์ธก
- ๋ง์ง๋ง์ fc layer๋ฅผ ์ฐ๊ฒฐํด์ input size์ ๋์ผํ๊ฒ ํฌ๊ธฐ๋ฅผ ๋ง์ถฐ์ค
class lstm_decoder(nn.Module):
def __init__(self, input_size, hidden_size, num_layers = 1):
super(lstm_decoder, self).__init__()
self.input_size = input_size
self.hidden_size = hidden_size
self.num_layers = num_layers
self.lstm = nn.LSTM(input_size = input_size, hidden_size = hidden_size,num_layers = num_layers, batch_first=True)
self.linear = nn.Linear(hidden_size, input_size)
def forward(self, x_input, encoder_hidden_states):
lstm_out, self.hidden = self.lstm(x_input.unsqueeze(-1), encoder_hidden_states)
output = self.linear(lstm_out)
return output, self.hidden
โถ encoder decoder
- ์ ๋ ๋ชจ๋ธ์ ํฉ์น๊ธฐ
- ์ธ์ฝ๋๋ฅผ ํ๋ฒ ์คํ์ํค๊ณ ์ธ์ฝ๋์์ ์ ๋ฌ๋ฐ์ hidden state์ input์ ๋ง์ง๋ง๊ฐ์ decoder์ ์ ๋ฌํด์ ๋ค์ ์์ธก๊ฐ์ ๊ตฌํจ
- ์ฌ๊ธฐ์ ๋์จ ๊ฐ๊ณผ hidden state๋ฅผ ๋ฐ๋ณต์ ์ผ๋ก ์ฌ์ฉํด์ ์ํ๋ ๊ธธ์ด๊ฐ ๋ ๋ ๊น์ง decoder๋ฅผ ์คํ
- decoder์ ์ธํ์ผ๋ก ์ด์ ์์ธก๊ฐ์ด ์๋ ์ค์ ๊ฐ์ ์ฌ์ฉํ๋ teacher forcing๋ ๊ตฌํ
- Teacher Forcing : ๋ค์ ๋ฌธ์ฅ์ input์ผ๋ก ์ด์ ๊ฐ์ output์ด ์๋ ์๋ target ๊ฐ์ด input์ผ๋ก ๋ค์ด๊ฐ๊ฒ ๋๋ ๊ฒ
class lstm_encoder_decoder(nn.Module):
def __init__(self, input_size, hidden_size):
super(lstm_encoder_decoder, self).__init__()
self.input_size = input_size
self.hidden_size = hidden_size
self.encoder = lstm_encoder(input_size = input_size, hidden_size = hidden_size)
self.decoder = lstm_decoder(input_size = input_size, hidden_size = hidden_size)
def forward(self, inputs, targets, target_len, teacher_forcing_ratio):
batch_size = inputs.shape[0]
input_size = inputs.shape[2]
outputs = torch.zeros(batch_size, target_len, input_size)
_, hidden = self.encoder(inputs)
decoder_input = inputs[:,-1, :]
#์ํ๋ ๊ธธ์ด๊ฐ ๋ ๋๊น์ง decoder๋ฅผ ์คํํ๋ค.
for t in range(target_len):
out, hidden = self.decoder(decoder_input, hidden)
out = out.squeeze(1) # squeezeํจ์๋ ์ฐจ์์ด 1์ธ ์ฐจ์์ ์ ๊ฑฐ
# teacher forcing์ ๊ตฌํํ๋ค.
# teacher forcing์ ํด๋นํ๋ฉด ๋ค์ ์ธํ๊ฐ์ผ๋ก๋ ์์ธกํ ๊ฐ์ด ์๋๋ผ ์ค์ ๊ฐ์ ์ฌ์ฉํ๋ค.
if random.random() < teacher_forcing_ratio:
decoder_input = targets[:, t, :]
else:
decoder_input = out
outputs[:,t,:] = out
return outputs
# ํธ์์ฑ์ ์ํด ์์ธกํด์ฃผ๋ ํจ์๋ ์์ฑํ๋ค.
def predict(self, inputs, target_len):
self.eval()
inputs = inputs.unsqueeze(0) # unsqueezeํจ์๋ 1์ธ ์ฐจ์์ ์์ฑ
batch_size = inputs.shape[0]
input_size = inputs.shape[2]
outputs = torch.zeros(batch_size, target_len, input_size)
_, hidden = self.encoder(inputs)
decoder_input = inputs[:,-1, :]
for t in range(target_len):
out, hidden = self.decoder(decoder_input, hidden)
out = out.squeeze(1)
decoder_input = out
outputs[:,t,:] = out
return outputs.detach().numpy()[0,:,0]
5๏ธโฃ Train
- ์์ฑํ ๋ชจ๋ธ๊ณผ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ์ฌ ํ๋ จ ์์
import torch
device = 'cuda' if torch.cuda.is_available() else 'cpu'
model = lstm_encoder_decoder(input_size=1, hidden_size=16).to(device)
- ํ์ดํผํ๋ผ๋ฏธํฐ
- ์ํญ(epoch) ์ : ๋ฐ์ดํฐ์ ์ ๋ฐ๋ณตํ๋ ํ์
- ๋ฐฐ์น ํฌ๊ธฐ(batch size) : ๋งค๊ฐ๋ณ์๊ฐ ๊ฐฑ์ ๋๊ธฐ ์ ์ ๊ฒฝ๋ง์ ํตํด ์ ํ๋ ๋ฐ์ดํฐ ์ํ์ ์
- ํ์ต๋ฅ (learning rate) : ๊ฐ ๋ฐฐ์น/์ํญ์์ ๋ชจ๋ธ์ ๋งค๊ฐ๋ณ์๋ฅผ ์กฐ์ ํ๋ ๋น์จ
- ๊ฐ์ด ์์์๋ก ํ์ต ์๋๊ฐ ๋๋ ค์ง
- ๊ฐ์ด ํฌ๋ฉด ํ์ต ์ค ์์ธกํ ์ ์๋ ๋์์ด ๋ฐ์ํ ์ ์์
# ๋ค์ํ ์ต์ ํ ์๊ณ ๋ฆฌ์ฆ์ ๊ตฌํํ๋ ํจํค์ง
import torch.optim as optim
learning_rate=0.01
epoch = 3000
optimizer = optim.Adam(model.parameters(), lr = learning_rate)
criterion = nn.MSELoss()
from tqdm import tqdm
model.train()
with tqdm(range(epoch)) as tr:
for i in tr:
total_loss = 0.0
for x,y in train_loader:
optimizer.zero_grad()
x = x.to(device).float()
y = y.to(device).float()
output = model(x, y, ow, 0.6).to(device)
loss = criterion(output, y)
loss.backward()
optimizer.step()
total_loss += loss.cpu().item()
tr.set_postfix(loss="{0:.5f}".format(total_loss/len(train_loader)))
12๋ถ 49์ด ๊ฑธ๋ ธ๋ค...
6๏ธโฃ Evaluate
- ํ์ต๋ ๋ชจ๋ธ์ ์ฌ์ฉํด์ ํ๋ จ์งํฉ์๋ ํฌํจ๋์ง ์์๋ ๋ง์ง๋ง ์ผ์ฃผ์ผ์ ๋ฐ์ดํฐ๋ฅผ ์์ธก
predict = model.predict(torch.tensor(train[-24*7*2:]).reshape(-1,1).to(device).float(), target_len=ow)
real = data["ํ๊ท ์๋"].to_numpy()
# inverse_transform ์ด์ฉํ๋ฉด ์ด๋ฆ์ผ๋ก ๋ณํ, transform ์ด์ฉํ๋ฉด ์ซ์๋ก ๋ณํ
predict = min_max_scaler.inverse_transform(predict.reshape(-1,1))
real = min_max_scaler.inverse_transform(real.reshape(-1,1))
plt.figure(figsize=(20,5))
plt.plot(range(400,744), real[400:], label="real")
plt.plot(range(744-24*7,744), predict[-24*7:], label="predict")
plt.title("Test Set")
plt.legend()
plt.show()

๋ง์ง๋ง ์ผ์ฃผ์ผ์ ๋ฐ์ดํฐ๋ฅผ ์์ธก ํด๋ดค๋๋ฐ.. ์ผ์ถ ๋น์ท..ํ๊ฐ..?
- MAPE(mean absolute percentage error)
- ๊ฒฐ์ธก ๊ตฌ๊ฐ์ MAPE๋ฅผ ๊ณ์ฐํด์ ์ ํํ ์ด๋์ ๋ ์์น๋ก ์ ํํ์ง ๊ณ์ฐ
- MAPE๊ฐ๋ ๊ฒฐ์ธก์น์ ๊ธธ์ด์ ์๊ด์์ด ๋๋ถ๋ถ ์ข์ ๊ฐ์ ๊ฐ์ง๊ณ ์์
- ํผ์ผํธ ๊ฐ์ ๊ฐ์ง๋ฉฐ 0์ ๊ฐ๊น์ธ์๋ก ํ๊ท ๋ชจํ์ ์ฑ๋ฅ์ด ์ข๋ค๊ณ ํด์ํ ์ ์์
- 0~100% ์ฌ์ด์ ๊ฐ์ ๊ฐ์ ธ ์ดํดํ๊ธฐ ์ฌ์ฐ๋ฏ๋ก ์ฑ๋ฅ ๋น๊ต ํด์์ด ๊ฐ๋ฅ
def MAPEval(y_pred, y_true):
return np.mean(np.abs((y_true - y_pred) / y_true)) * 100
MAPEval(predict[-24*7:],real[-24*7:])
๊ต์ฅํ.. ๋๋ค.. ์ ํ ์ข์ง ์๋ค...
ํ์ดํผ ํ๋ผ๋ฏธํฐ ์กฐ์ ํด์ ๋ค์ ๊ฒฐ๊ณผ๋ฅผ ๋ด๋ณด๊ฒ ์ต๋๋ค..
learning_rate = 0.001