LSTMに、サイン波を複数のGPUを使って学習させる。

##目的

LSTMに、サイン波を複数のGPUを使って学習させる。

__※コードは[github](https://github.com/AtomScott/chainer_sinewave_multigpu)にありますが、間違えもあるはずです。なにかあれば教えてくれるとすごくありがたいです。__
###LSTMについて
LSTMとは時系列のデータを扱うRNN（再起的ニューラルネットワーク）の一種で、普通のRNNだと起こる勾配が爆発・消失する問題がないので、よく使われています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/295666/6851da72-abae-b12a-7fd3-10cc1ea578ca.png)

###Many2Many LSTM
ここで作るのは以下の図の5番目のLSTMです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/295666/b210043b-7d45-4f05-7c40-1a5e8c1d5f97.png)

###Model
n_unitsは下記の図のxたちの次元を示します。

大きければ、大きいほど学習するパラメータの数が増えます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/295666/44dc80f2-f82c-f277-4361-203cad76f327.png)

>each line carries an entire vector, from the output of one node to the inputs of others.

本記事で使ったコードはここのものを参考にしました
→https://qiita.com/chachay/items/052406176c55dd5b9a6a
ただいろいろ修正・改変しているところがあるので注意してください。

````python
class LSTM(Chain):
    n_input  = 1
    n_output = 1 
    n_units  = 5 #大きければ、大きいほど学習するパラメータの数が増える。

    def __init__(self):
        super(LSTM, self).__init__(
            l1 = L.Linear(self.n_input, self.n_units),
            l2 = L.LSTM(self.n_units, self.n_units),
            l3 = L.Linear(self.n_units, self.n_output),
        )

    def reset_state(self):
        self.l2.reset_state()

    def __call__(self, x):
        h1 = self.l1(x)
        h2 = self.l2(h1)
        return self.l3(h2)
````

## データ
````python
import numpy as np
import matplotlib.pyplot as plt
import chainer
import chainer.functions as F
import chainer.links as L
from chainer import report, training, Chain, cuda, datasets, iterators, optimizers, Variable, function
from chainer.training import extensions
from chainer.datasets import tuple_dataset
import cupy
import six
# データ作成
N_data  = 1000
N_Loop  = 3
t = np.linspace(0., 2*np.pi*N_Loop, num=N_data)

X = 0.8*np.sin(2.0*t)

# データセット
N_train = int(N_data*0.8)
N_test  = int(N_data*0.2)

tmp_DataSet_X= np.array(X).astype(np.float32)

x_train, x_test = np.array(tmp_DataSet_X[:N_train]),np.array(tmp_DataSet_X[N_train:])

train = tuple_dataset.TupleDataset(x_train)
test  = tuple_dataset.TupleDataset(x_test)

plt.scatter(range(0,len(x_train)), x_train,c="red")
plt.scatter(range(0,len(x_test)), x_test, c="blue")
plt.show()
````
![image.png](https://qiita-image-store.s3.amazonaws.com/0/295666/5da8b641-fbbf-11f7-c2eb-656da3402b1c.png)

##学習の流れ
###Chainer trainer for LSTM
https://docs.chainer.org/en/stable/guides/trainer.html より

ニューラルネットワークを学習させたいときは、パラメータを何度も更新するトレーニングループを実行する必要があります。

典型的なトレーニングループは以下の手順で構成されています。

1. 訓練データセットの反復
2. 抽出ミニバッチの前処理
3. ニューラルネットワークのforward/backward計算
4. パラメータの更新
5. 検証データセットで現パラメータの評価
6. ログ記録と表示

GPUを複数有効活用したいときは、２〜４の工程を並列に行うことになるのでここを自分で書く必要があります。
参考にした[記事](→https://qiita.com/chachay/items/052406176c55dd5b9a6a)はCPUで学習させていたので、GPUでも走るようにちょっとコードを変えました。

複数のGPUで走るらせるには、LSTM_std_updaterとLSTM_prl_updaterにupdaterを分ける必要がありました。
これはChainerでそもそもStandardUpdaterとParallelUpdaterを分けているためしょうがないかと思います。

残りのコードは[github](https://github.com/AtomScott/chainer_sinewave_multigpu)から取ってください。

````python
class LSTM_std_updater(training.StandardUpdater):
    def __init__(self, train_iter, optimizer, device):
        super().__init__(train_iter, optimizer, device=device)
        self.seq_length = train_iter.seq_length

    def update_core(self):
        loss = 0

        train_iter = self.get_iterator('main')
        optimizer = self.get_optimizer('main')

        for i in range(self.seq_length):
            batch = train_iter.__next__()
            x, t  = self.converter(batch, self.device)
            loss += optimizer.target(chainer.Variable(x.reshape((-1, 1))), chainer.Variable(t.reshape((-1, 1))))
        optimizer.target.zerograds()
        loss.backward()
        loss.unchain_backward()
        optimizer.update()
        
class LSTM_prl_updater(training.ParallelUpdater):
    def __init__(self, train_iter, optimizer, devices):
        super().__init__(train_iter, optimizer, devices=devices)
        self.seq_length = train_iter.seq_length

    def update_core(self):
        losses = [0 for i in self._models]

        train_iter = self.get_iterator('main')
        optimizer = self.get_optimizer('main')

        for i in range(self.seq_length):
            batch = train_iter.__next__()

            #ここから並列計算用の処理
            n = len(self._models)
            xt_list = {}
            for i, key in enumerate(six.iterkeys(self._models)):
                xt_list[key] = self.converter(
                    batch[i::n], self._devices[key])

            for model in six.itervalues(self._models):
                model.cleargrads()

            _losses = []
            for model_key, model in six.iteritems(self._models):
                x, t = xt_list[model_key]
                loss_func = self.loss_func or model

                with function.force_backprop_mode():
                    dev_id = self._devices[model_key]
                    dev_id = dev_id if 0 <= dev_id else None
                    with cuda.get_device_from_id(dev_id):
                        _loss = loss_func(x, t)
                        
                _losses.append(_loss)
        
        losses = [sum(x) for x in zip(losses, _losses)]
        optimizer.target.zerograds()
        for loss in losses:
            loss.backward()
            loss.unchain_backward()
        optimizer.update()
````

##結果

上記のデータで学習させた結果です。
初期データ(N_data*0.1個)を用いてt+1を予測
予測したt+1を用いてt+2を予測
予測を用いて予測するの繰り返し

````python
model_cpu = run_LSTM(gpu0=-1, batchsize=10, seqlen=10, epoch=10000)
plot_test(model_cpu)
model_1gpu = run_LSTM(gpu0=0,batchsize=10,seqlen=10, epoch=10000)
plot_test(model_1gpu)
model_2gpu = run_LSTM(gpu0=0,gpu1=1,batchsize=10,seqlen=10, epoch=10000)
plot_test(model_2gpu)
````

![image.png](https://qiita-image-store.s3.amazonaws.com/0/295666/7b13c79b-652c-dbdd-0087-f714b470cdd3.png)

GPUのほうが遅いのは、ハイパーパラメータの設定で、batchsizeやLSTMのn_unitsが小さくなっているため、GPUを活かせてないことが原因にあるかと思います。