LSTMは深層学習を用いた時系列データなどの予測モデルに利用できる手法で、RNNが抱える問題点を改善する手法となっています。
さまざまな論文等でLSTMがベースラインで利用されるなど、広く利用されている手法です。
今回はLSTMを用いて、多変量時系列の予測を行っていきます。
実装は深層学習のライブラリであるpytorchを用いて行います。
多変量時系列で用いるデータセット
今回は気象庁が提供している気象データセットを利用していきます。気象データセットの詳しい詳細はこちらの記事をご覧ください。
まず、実装に必要なライブラリ群をまとめてimportします。
# ライブラリのインポート
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import train_test_split
import torch
from torch import nn,optim
from torch.utils.data import DataLoader, TensorDataset, Dataset
from torchvision import transforms
from torchinfo import summary
from torch.autograd import Variable
続いて、データセットをダウンロードして、DataFrame化します。
df = pd.read_csv("https://raw.githubusercontent.com/aweglteo/tokyo_weather_data/main/data.csv", parse_dates=True, index_col=0)
このようなデータセットを準備することができました。
この多変量時系列の予測をLSTMで行なっていきます。今回は、学習データはこれらの6系列を利用し、推論は平均気温の予測をしていきたいと思います。
つまり学習に多変量データを用い、1系列のデータを予測します。
続いて、データの正規化を行なっていきます。正規化には、scikit-learnのpreprocessingモジュールに含まれている、MinMaxScalerを利用します。
scaler = MinMaxScaler()
df_scaled = scaler.fit_transform(df[['cloud', 'wind', 'ave_tmp', 'max_tmp', 'min_tmp', 'rain']])
続いて、データセットを準備します。まず訓練データとテストデータに時系列を分割し、それぞれモデルに入力する形に変換します。
# テスト用と訓練用で分割
df_train, df_test = train_test_split(df_scaled, test_size=0.3, shuffle=False)
window_size = 20
n_data = len(df) - window_size + 1 -1
n_dim = df_train.shape[1]
n_train = len(df_train) - window_size + 1 - 1
n_test = len(df_test) - window_size + 1 - 1
# 正解データを準備
train = np.zeros((n_train, window_size, n_dim))
train_labels = np.zeros((n_train, n_dim))
for i in range(n_train):
train[i] = df_train[i:i+window_size]
train_labels[i] = df_train[i+window_size]
# テストデータを準備
test = np.zeros((n_test, window_size, n_dim))
test_labels = np.zeros((n_test, n_dim))
for i in range(n_test):
test[i] = df_test[i:i+window_size]
test_labels[i] = df_test[i+window_size]
# 訓練ラベルの用意。今回は平均気温を予測する
train_labels =train_labels[:, 2]
最後にpytorchのtensor形式に変換します。
train = torch.tensor(train, dtype=torch.float)
labels = torch.tensor(train_labels, dtype=torch.float)
dataset = torch.utils.data.TensorDataset(train, labels)
train_loader = DataLoader(dataset, batch_size=4, shuffle=True)
LSTMでの多変量時系列の予測
続いてモデルを定義します。
今回はLSTMのネットワークをMyLSTMという名前をつけたクラスで定義します。
# 多変量を入力して、1変数の予測結果を返すLSTNモデル.
class MyLSTM(nn.Module):
def __init__(self, feature_size, hidden_dim, n_layers):
super(MyLSTM, self).__init__()
self.feature_size = feature_size
self.hidden_dim = hidden_dim
self.n_layers = n_layers
self.n_output = 1
self.lstm = nn.LSTM(feature_size, hidden_dim, n_layers, batch_first=True)
self.fc = nn.Linear(hidden_dim, self.n_output)
def forward(self, x):
# hidden state
h_0 = Variable(torch.zeros(self.n_layers, x.size(0), self.hidden_dim))
# cell state
c_0 = Variable(torch.zeros(self.n_layers, x.size(0), self.hidden_dim))
output, (hn, cn) = self.lstm(x, (h_0, c_0)) # (input, hidden, and internal state)
hn = hn.view(-1, self.hidden_dim)
y = self.fc(hn)
y = y.reshape(self.n_output, -1)
return y
feature_size = 6
n_hidden = 64
n_layers = 1
net = MyLSTM(feature_size, n_hidden, n_layers)
続いて、モデルの全体像を確認してみましょう。torchinfoライブラリに含まれているsummary関数がモデルの構造を理解するために便利な出力を出してくれます。
summary(net)
このようになりました。パラメータ数はおよそ、18000個ほどあることがわかります。
LSTMモデルの学習
ここまででモデルの実装が出来ました。続いて、モデルを学習させていきます。損失関数やOptimizer、学習epochなど学習に関するパラメータを指定します。
func_loss = nn.MSELoss()
optimizer = optim.Adam(net.parameters(), lr=0.001)
loss_history = []
device = torch.device("cuda:0" if torch.cuda. is_available() else "cpu")
epochs = 200
net.to(device)
実際に学習をおこなっていきます。
for i in range(epochs+1):
net.train()
tmp_loss = 0.0
for j, (x, t) in enumerate(train_loader):
x = x.to(device)
optimizer.zero_grad()
y = net(x)
y = y.to('cpu')
loss = func_loss(y, t)
loss.backward()
optimizer.step()
tmp_loss += loss.item()
tmp_loss /= j+1
loss_history.append(tmp_loss)
print('Epoch:', i, 'Loss_Train:', tmp_loss)
学習が収束してきています。学習の進捗を可視化しましょう。
# 損失関数を描く
plt.plot(range(len(loss_history)), loss_history, label='train')
plt.legend()
plt.xlabel("epochs")
plt.ylabel("loss")
plt.show()
学習がうまく進んでいることが分かりますね。
学習したLSTMモデルで予測を行う
最後に学習したモデルを用いて、時系列の予測をおこなっていきましょう。
まずは訓練データで試してみます。
predicted_train_plot = []
net.eval()
for k in range(n_train):
x = torch.tensor(train[k])
x = x.reshape(1, window_size, feature_size)
x = x.to(device).float()
y = net(x)
y = y.to('cpu')
predicted_train_plot.append(y[0].item())
結果はこのうようになり、学習データを用いた予測だけあって、きちんと予測できていることがわかります。
続いてテストデータに対する予測結果はこのようになりました。
plt.plot(range(len(df_test)), df_test[:, 2], label='Correct')
plt.plot(range(window_size, window_size+len(predicted_test_plot)), predicted_test_plot, label='Test result')
plt.legend()
plt.show()
こちらも平均気温をピッタリと予測できていますね。
気温のように、年間を通してトレンドがあるようなデータに対して、LSTMがほぼ完璧に予測できていることは驚きですね。