再帰型ニューラルネットワークの勉強：周期関数を再現できるか


深層学習（ディープラーニング）の中で、時系列データなどの解析に使われる再帰型ニューラルネットワーク（Recurrent Neural Network）の勉強のため、周期関数をどの程度再現できるか試してみました。

# ３種類の周期関数の定義

ここでは、以下の `f` `g` `h` ３種類の周期関数を定義しました。

```python
import numpy as np

def f(t, freq=25):
    return np.sin(2. * np.pi * t / freq)

def g(t, freq=25, amp=10, threshold = 10):
    return 1/(1 + np.exp(10 * np.sin(2 * np.pi * t / freq) + 10))

def h(t, freqs=[11, 23, 31, 41, 53, 61, 71, 83, 97]):
    value = np.zeros_like(t)
    for freq in freqs:
        value += f(t, freq)
    return value
```

## 関数f

関数`f`は、三角関数 $sin$ です。ここでは、周期を $25$ にしました。

```python
%matplotlib inline
import matplotlib.pyplot as plt

total_time_length = 1000
times = np.linspace(0, total_time_length, total_time_length + 1)

plt.figure(figsize=(15, 6))
plt.plot(f(times))
plt.xticks(np.linspace(0, 1000, 11))
plt.grid()
```

![output_2_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/a574d8a6-d1c7-b104-96c5-e20eb82a3d39.png)


## 関数g

関数`g`は、三角関数 $sin$ をシグモイド関数で変換して、変な形にしました。ここでも、周期を $25$ にしました。


```python
%matplotlib inline
import matplotlib.pyplot as plt

total_time_length = 1000
times = np.linspace(0, total_time_length, total_time_length + 1)

plt.figure(figsize=(15, 6))
plt.plot(g(times))
plt.xticks(np.linspace(0, 1000, 11))
plt.grid()
```

![output_3_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/fa15d67e-06f1-b027-f6d3-858540196b97.png)


## 関数h

関数`h`は、いくつかの素数を周期とする三角関数 $sin$ の和にしました。


```python
%matplotlib inline
import matplotlib.pyplot as plt

total_time_length = 1000
times = np.linspace(0, total_time_length, total_time_length + 1)

plt.figure(figsize=(15, 6))
plt.plot(h(times))
plt.xticks(np.linspace(0, 1000, 11))
plt.grid()
```

![output_4_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/04d9e1bb-303b-4410-304d-b44e659362df.png)

これらの関数を、再帰型ニューラルネットワークで再現できるでしょうか？

## フーリエ変換

再帰型ニューラルネットワークを使う前に、上の３つの関数をフーリエ変換で周波数に変換し、その逆数をとって「周期」を確認してみましょう。

```python
plt.figure(figsize=(6,6))

sp = np.fft.fft(f(times))
freq = np.fft.fftfreq(times.shape[-1])

plt.subplot(311)
plt.plot(1/freq, abs(sp.real) + abs(sp.imag), label="f")
plt.plot(1/freq, abs(sp.real))
plt.plot(1/freq, abs(sp.imag), alpha=0.5)
plt.legend()
plt.xlim([0, 150])
plt.xticks(np.linspace(0, 150, 16))
plt.grid()

sp = np.fft.fft(g(times))
freq = np.fft.fftfreq(times.shape[-1])

plt.subplot(312)
plt.plot(1/freq, abs(sp.real) + abs(sp.imag), label="g")
plt.plot(1/freq, abs(sp.real))
plt.plot(1/freq, abs(sp.imag), alpha=0.5)
plt.legend()
plt.xlim([0, 150])
plt.xticks(np.linspace(0, 150, 16))
plt.grid()

sp = np.fft.fft(h(times))
freq = np.fft.fftfreq(times.shape[-1])

plt.subplot(313)
plt.plot(1/freq, abs(sp.real) + abs(sp.imag), label="h")
plt.plot(1/freq, abs(sp.real))
plt.plot(1/freq, abs(sp.imag), alpha=0.5)
plt.legend()
plt.xlim([0, 150])
plt.xticks(np.linspace(0, 150, 16))
plt.grid()
```

![output_6_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/912a4b29-33ff-bdb1-646c-6815d6c78aed.png)

関数`f`は、周期 $25$ の sin 波のみから成ります。関数`h`は、指定した素数の周期の組み合わせから成っていることが分かります。一方、関数`g`は、指定した周期 $25$ 以外にも様々な成分が含まれていることが分かります。


# 時系列データセットの作成

それでは、再帰型ニューラルネットワークに学習させるための時系列データセットを作成しましょう。再帰型ニューラルネットワークでは、時刻 $t$ 〜 $t+w$ までの出力 $X_{t, t+w}$ を入力すると、次の時刻の出力 $X_{t+w+1}$ を予測するモデルの作成を目指します。

```python
import numpy as np
from sklearn.model_selection import train_test_split

func = f # ここを書き換えれば関数を変更できる
#func = g
#func = h

total_time_length = 10000 # 取り扱う全ての時間幅
pred_length = 1000 # 予測する時間幅
learning_time_length = 100 # 学習に用いる時間幅

time_series_T = np.linspace(0, total_time_length, total_time_length + 1) # 予測する時間Tを刻む（グラフの横軸にあたる）
time_series_X = func(time_series_T) # 関数の出力X（グラフの縦軸にあたる）

X_learn = [] # 時刻 t 〜 t+learning_time_length までの X を格納
Y_learn = [] # 時刻 t+learning_time_length+1 の X を格納
for i in range(total_time_length - learning_time_length):
    X_learn.append(time_series_X[i:i+learning_time_length].reshape(1, learning_time_length).T)
    Y_learn.append([time_series_X[i+learning_time_length]])

# トレーニングデータ・検証データに分割
# 時系列データのときは shuffle=False にしなければいけない
X_train, X_val, Y_train, Y_val = \
train_test_split(X_learn, Y_learn, test_size=0.2, shuffle=False)

# scikit-learn 用のデータ型に変換
X_train2sklearn = [list(x.reshape(1, len(x))[0]) for x in X_train]
Y_train2sklearn = [y[0] for y in Y_train]
```

# 深層学習手法

## MLP (Multi-Layer Perceptron)

比較対象として、深層学習の最も単純なモデルである多層パーセプトロン（Multi-Layer Perceptron, MLP）を使います。多層パーセプトロンは scikit-learn が手軽かつ高速なので、それを使いました。

```python
%%time
from sklearn.neural_network import MLPRegressor
regressor = MLPRegressor(hidden_layer_sizes=(100, 100, 100), 
                         early_stopping=True, max_iter=10000) 
regressor.fit(X_train2sklearn, Y_train2sklearn) 
```

    CPU times: user 2.84 s, sys: 1.41 s, total: 4.25 s
    Wall time: 2.22 s


学習曲線は次のようにして描きました。

```python
plt.plot(regressor.loss_curve_)
%matplotlib inline
import matplotlib.pyplot as plt
plt.subplot(211)
plt.plot(regressor.loss_curve_, label='train_loss')
plt.legend()
plt.grid()
plt.subplot(212)
plt.plot(regressor.loss_curve_, label='train_loss')
plt.yscale('log')
plt.legend()
plt.grid()
```
![output_11_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/e9ee975a-97b9-3f4b-3f13-a2b24259af25.png)


学習済みのモデルに、最初の数時刻（`pred_length` の長さだけの時刻）を入力として与え、次の時刻の出力を予測させます。出力された予測値を入力に追加して、その次の時刻の出力を予測させます。それを延々と繰り返します。


```python
%%time
pred_length = 1000
X_pred_length = np.linspace(0, pred_length , pred_length + 1)
Y_observed = func(X_pred_length)
Y_pred = Y_observed[:learning_time_length+1]

for i in range(pred_length):
    X_ = [Y_pred[i:i+learning_time_length]]
    Y_ = regressor.predict(X_)
    Y_pred = np.append(Y_pred, Y_)
```

    CPU times: user 383 ms, sys: 279 ms, total: 662 ms
    Wall time: 351 ms


そうして得られた予測値の曲線と、実際の曲線とを図示して比較します。

```python
plt.figure(figsize=(36, 6))
times = np.linspace(0, Y_pred.shape[0] - 1, Y_pred.shape[0])
plt.plot(func(times), label="time series")
plt.plot(Y_pred, alpha=0.5, label="predicted")
plt.xticks(np.linspace(0, 1000, 11))
plt.xlim([0, 1000])
plt.grid()
plt.legend()
```

![output_13_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/b947e886-5a71-5a33-84f1-e61e4d91bff7.png)

図が小さくて見にくい場合は、図をクリックすると拡大できると思います。時刻 $100$ までは、学習用データをそのまま使っていますので一致していますが、時刻 $100$ 以降、実際の値（関数`f`の値）と予測値がだんだんズレていってるのが分かります。

フーリエ変換した結果を比べてみましょう。

```python
plt.figure(figsize=(6,4))

sp = np.fft.fft(func(times))
freq = np.fft.fftfreq(times.shape[-1])

plt.subplot(211)
plt.plot(1/freq, abs(sp.real) + abs(sp.imag), label="observed")
plt.plot(1/freq, abs(sp.real))
plt.plot(1/freq, abs(sp.imag), alpha=0.5)
plt.legend()
plt.xlim([0, 150])
plt.xticks(np.linspace(0, 150, 16))
plt.grid()

sp = np.fft.fft(Y_pred)
freq = np.fft.fftfreq(times.shape[-1])

plt.subplot(212)
plt.plot(1/freq, abs(sp.real) + abs(sp.imag), label="predicted")
plt.plot(1/freq, abs(sp.real))
plt.plot(1/freq, abs(sp.imag), alpha=0.5)
plt.legend()
plt.xlim([0, 150])
plt.xticks(np.linspace(0, 150, 16))
plt.grid()
```

![output_14_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/27215313-a50a-fa39-0d21-611ee884d0fb.png)

予測された周期は、実際の周期と近いですが少しだけズレているように見えます。


## 再帰型ニューラルネットワーク

それでは、いよいよ再帰型ニューラルネットワークを構築しましょう。ここでは深層学習用ライブラリである PyTorch を用います。

まず、デバイスの設定を行います。次のようにして、GPUが使える環境ならGPUを、そうでないならCPUを使うようにします。

```python
import torch
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
```

EarlyStopping（早期終了）のための関数を用意します。早期終了とは、学習がこれ以上進まないと判断した時に、学習を打ち切る処理です。

深層学習では予測誤差のことを損失（loss）と呼びます。過適合（overfitting, 過学習ともいう）を防ぐため、ここではトレーニングデータの`loss`ではなく、トレーニングに用いなかった検証用データの`loss`がこれ以上下がらないと判断した時に学習を打ち切ります。その判断として`patience`という概念を導入します。`patience=20`なら、最近 $20$ 回の`loss`の最小値と、それ以前の`loss`の最小値を比較し、前者の方が大きければ「これ以上良くならない」と判断し学習を打ち切ります。


```python
def EarlyStopping(log, patience=20):
    if len(log) <= patience:
        return False
    min1 = log[:len(log)-patience].min()
    min2 = log[len(log)-patience:].min()
    if min1 <= min2:
        return True
    else:
        return False
```

### RNN（Recurrent Neural Network)

ちょっと紛らわしいですが、RNN（Recurrent Neural Network, 再帰型ニューラルネットワーク）には「広義のRNN」と「狭義のRNN」があります。狭義のRNNは、PyTorchで次のように実装できます。


```python
import torch
class RNN(torch.nn.Module):
    def __init__(self, hidden_dim):
        super().__init__()
        self.l1 = torch.nn.RNN(1, hidden_dim,
                         nonlinearity='tanh',
                         batch_first=True)
        self.l2 = torch.nn.Linear(hidden_dim, 1)
        torch.nn.init.xavier_normal_(self.l1.weight_ih_l0)
        torch.nn.init.orthogonal_(self.l1.weight_hh_l0)

    def forward(self, x):
        h, _ = self.l1(x)
        y = self.l2(h[:, -1])
        return y
```

### LSTM (Long Short-Term Memory)

LSTM (Long Short-Term Memory, 長・短期記憶)は、長いのか短いのかどっちだヨ！！と突っ込みたくなるネーミングですが、「広義のRNN」の一種です。「狭義のRNN」よりも長期記憶が優れていると言われています。PyTorchで次のように実装できます。

```python
import torch
class LSTM(torch.nn.Module):
    def __init__(self, hidden_dim):
        super().__init__()
        self.l1 = torch.nn.LSTM(1, hidden_dim, batch_first=True)
        self.l2 = torch.nn.Linear(hidden_dim, 1)
        torch.nn.init.xavier_normal_(self.l1.weight_ih_l0)
        torch.nn.init.orthogonal_(self.l1.weight_hh_l0)

    def forward(self, x):
        h, _ = self.l1(x)
        y = self.l2(h[:, -1])
        return y
```

### GRU (Gated Recurrent Unit)

GRUは、ロシア連邦軍参謀本部情報総局（Glavnoye Razvedyvatelnoye Upravleniye）ではなくて、ゲート付き回帰型ユニット(Gated Recurrent Unit)です。LSTMと同等以上の性能を持ちながら計算時間が少ないと言われています。

```python
import torch
class GRU(torch.nn.Module):
    def __init__(self, hidden_dim):
        super().__init__()
        self.l1 = torch.nn.GRU(1, hidden_dim, batch_first=True)
        self.l2 = torch.nn.Linear(hidden_dim, 1)
        torch.nn.init.xavier_normal_(self.l1.weight_ih_l0)
        torch.nn.init.orthogonal_(self.l1.weight_hh_l0)

    def forward(self, x):
        h, _ = self.l1(x)
        y = self.l2(h[:, -1])
        return y
```



### RNNによる学習実行

次のようにして学習しました。以下のコードはRNN用のコードですが、１箇所書き換えるだけでLSTMやGRUに変更できます。

```python
%%time
from sklearn.utils import shuffle
model = RNN(50).to(device) # ここを書き換えればネットワークを変更できる
#model = LSTM(50).to(device)
#model = GRU(50).to(device)
criterion = torch.nn.MSELoss(reduction='mean')
optimizer = torch.optim.Adam(model.parameters(), lr=0.001, 
                            betas=(0.9, 0.999), amsgrad=True)

epochs = 1000
batch_size = 100
n_batches_train = len(X_train) // batch_size - 1
n_batches_test = len(X_val) // batch_size - 1
hist = {'train_loss':[], 'val_loss':[]}

for epoch in range(epochs):
    train_loss = 0.
    val_loss = 0.
    X_, Y_ = shuffle(X_train, Y_train)

    for batch in range(n_batches_train):
        start = batch * batch_size
        end = start + batch_size
        X = torch.Tensor(X_[start:end])
        Y = torch.Tensor(Y_[start:end])
        model.train()
        Y_pred = model(X)
        loss = criterion(Y, Y_pred)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        train_loss += loss.item()

    for batch in range(n_batches_test):
        start = batch * batch_size
        end = start + batch_size
        X = torch.Tensor(X_val[start:end])
        Y = torch.Tensor(Y_val[start:end])
        model.eval()
        Y_pred = model(X)
        loss = criterion(Y, Y_pred)
        val_loss += loss.item()
    
    train_loss /= n_batches_train
    val_loss /= n_batches_test
    hist['train_loss'].append(train_loss)
    hist['val_loss'].append(val_loss)
    print("Epoch:", epoch + 1, "Train loss:", train_loss, "Val loss:", val_loss)

    if EarlyStopping(np.array(hist['val_loss'])):
        print("Early stopping at epoch", epoch + 1)
        break
```

    Epoch: 1 Train loss: 0.024917872337913975 Val loss: 6.828008190495893e-05
    Epoch: 2 Train loss: 3.570798083110742e-05 Val loss: 3.464157634880394e-05
    Epoch: 3 Train loss: 2.720728588638639e-05 Val loss: 1.954806430148892e-05
    ...
    Epoch: 580 Train loss: 5.4337909078255436e-08 Val loss: 4.0113718569045886e-08
    Epoch: 581 Train loss: 6.47745306281422e-08 Val loss: 5.6099906942108646e-08
    Epoch: 582 Train loss: 5.797503896896836e-08 Val loss: 1.620698952820021e-07
    Early stopping at epoch 582
    CPU times: user 26min 9s, sys: 21.6 s, total: 26min 31s
    Wall time: 26min 39s


学習が終わりました。学習曲線を出力します。

```python
%matplotlib inline
import matplotlib.pyplot as plt
plt.subplot(211)
plt.plot(hist['train_loss'], label='train_loss')
plt.plot(hist['val_loss'], label='val_loss')
plt.legend()
plt.grid()
plt.subplot(212)
plt.plot(hist['train_loss'], label='train_loss')
plt.plot(hist['val_loss'], label='val_loss')
plt.yscale('log')
plt.legend()
plt.grid()
```

![output_27_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/9188858a-1cca-c81c-1f4f-6134b24f7ba3.png)


学習済みのモデルに、最初の数時刻（`pred_length` の長さだけの時刻）を入力として与え、次の時刻の出力を予測させます。出力された予測値を入力に追加して、その次の時刻の出力を予測させます。それを延々と繰り返します。


```python
%%time
total_time_length = 10000
pred_length = 1000
learning_time_length = 100

X_pred_length = np.linspace(0, pred_length , pred_length + 1)
Y_observed = func(X_pred_length)
Y_pred = Y_observed[:learning_time_length+1]

for i in range(pred_length):
    X_ = Y_pred[i:i+learning_time_length+1].reshape(1, learning_time_length + 1, 1)
    Y_ = model(torch.Tensor(X_)).detach().numpy()
    Y_pred = np.append(Y_pred, Y_)
```

    CPU times: user 2.54 s, sys: 5.97 ms, total: 2.55 s
    Wall time: 2.55 s

そうして得られた予測値の曲線と、実際の曲線とを図示して比較します。


```python
plt.figure(figsize=(36, 6))
times = np.linspace(0, Y_pred.shape[0] - 1, Y_pred.shape[0])
plt.plot(func(times), label="time series")
plt.plot(Y_pred, alpha=0.5, label="predicted")
plt.xticks(np.linspace(0, 1000, 11))
plt.xlim([0, 1000])
plt.grid()
plt.legend()
```

![output_29_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/d859399d-c609-86a2-37fa-5d05702c8a60.png)



図が小さくて見にくい場合は、図をクリックすると拡大できると思います。時刻 $100$ までは、学習用データをそのまま使っていますので一致して当然ですが、時刻 $100$ 以降も、完全に一致しているの分かります。

フーリエ変換した結果を比べてみましょう。


```python
plt.figure(figsize=(6,4))

sp = np.fft.fft(func(times))
freq = np.fft.fftfreq(times.shape[-1])

plt.subplot(211)
plt.plot(1/freq, abs(sp.real) + abs(sp.imag), label="observed")
plt.plot(1/freq, abs(sp.real))
plt.plot(1/freq, abs(sp.imag), alpha=0.5)
plt.legend()
plt.xlim([0, 150])
plt.xticks(np.linspace(0, 150, 16))
plt.grid()

sp = np.fft.fft(Y_pred)
freq = np.fft.fftfreq(times.shape[-1])

plt.subplot(212)
plt.plot(1/freq, abs(sp.real) + abs(sp.imag), label="predicted")
plt.plot(1/freq, abs(sp.real))
plt.plot(1/freq, abs(sp.imag), alpha=0.5)
plt.legend()
plt.xlim([0, 150])
plt.xticks(np.linspace(0, 150, 16))
plt.grid()
```

![output_30_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/dcad4702-8442-9bbb-2055-9f8e1b9ae321.png)

周期が完全に一致しているようです。



### LSTMによる学習実行

上に述べたように、LSTMの学習は、上のコードの `model = RNN(50).to(device)` を `model = LSTM(50).to(device)` に書き換えるだけです。


    Epoch: 1 Train loss: 0.24947839844315192 Val loss: 0.0037629783619195223
    Epoch: 2 Train loss: 0.0010665786028720248 Val loss: 0.0004544752591755241
    Epoch: 3 Train loss: 0.000281030429528656 Val loss: 0.00014765093510504812
    ...
    Epoch: 397 Train loss: 1.9865108783006072e-08 Val loss: 1.99065262052045e-08
    Epoch: 398 Train loss: 1.840841412067617e-08 Val loss: 1.814414751777349e-08
    Epoch: 399 Train loss: 1.7767042196444784e-08 Val loss: 1.9604467382805524e-08
    Early stopping at epoch 399
    CPU times: user 48min 40s, sys: 51.2 s, total: 49min 31s
    Wall time: 49min 41s

![output_33_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/0f8d93b8-e072-3a9a-18b8-63e0cc1fb701.png)


    CPU times: user 7.67 s, sys: 14 ms, total: 7.68 s
    Wall time: 7.69 s


![output_35_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/35e17302-06ea-76e1-3409-d455a9022386.png)

![output_36_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/32b99356-54f3-9d4d-9678-8f38e5a335a5.png)

こちらも完璧な予測ができたようです。

### GRUによる学習実行

上に述べたように、GRUの学習は、上のコードの `model = RNN(50).to(device)` を `model = GRU(50).to(device)` に書き換えるだけです。


    Epoch: 1 Train loss: 0.2067998453276232 Val loss: 0.0007729934877716005
    Epoch: 2 Train loss: 0.0005770771786979495 Val loss: 0.00023205751494970173
    Epoch: 3 Train loss: 0.00018625847849015816 Val loss: 0.00014329736586660147
    ...
    Epoch: 315 Train loss: 5.816128262764026e-09 Val loss: 5.750611098420677e-09
    Epoch: 316 Train loss: 5.757192062114896e-09 Val loss: 5.7092033323158375e-09
    Epoch: 317 Train loss: 5.780735246610847e-09 Val loss: 5.6715170337895415e-09
    Early stopping at epoch 317
    CPU times: user 34min 51s, sys: 42.1 s, total: 35min 33s
    Wall time: 35min 40s


![output_39_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/d50020d6-5b27-d4db-e841-cecb1cccb756.png)


    CPU times: user 8.81 s, sys: 7.04 ms, total: 8.81 s
    Wall time: 8.82 s


![output_41_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/7a74b0a4-d9f0-8354-4aa1-7a7f73e1c205.png)

![output_42_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/6e367855-869a-4f28-02e4-3ef4ff9bed36.png)

こちらも完璧な予測ができたようです。

# 関数の形や周期を変える実験

さて、動かし方はだいぶ分かった気がするので、最後に、関数の形や周期を変える実験をしてみましょう。結果は以下の通り。

## 関数 f の周期を長くする

### MLP, 関数f

#### MLP, 関数f, 周期 = 25

学習曲線

![output_11_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/e9ee975a-97b9-3f4b-3f13-a2b24259af25.png)

予測曲線

![output_13_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/b947e886-5a71-5a33-84f1-e61e4d91bff7.png)

フーリエ変換

![output_14_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/27215313-a50a-fa39-0d21-611ee884d0fb.png)


#### MLP, 関数f, 周期 = 50

学習曲線

![output_11_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/d1acf158-909c-16b5-65ae-363efb4f87da.png)

予測曲線

![output_13_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/003bb124-bd2b-a18e-699c-36809d096653.png)

フーリエ変換

![output_14_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/a04aea2e-424d-eb56-086d-c27b4ea6738f.png)


#### MLP, 関数f, 周期 = 100

学習曲線

![output_11_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/60c85fed-57ba-81c3-2f06-677427c79f41.png)

予測曲線

![output_13_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/837eb45d-db7f-b93d-69c7-17c44a43b47c.png)

フーリエ変換

![output_14_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/df3a7566-9ad8-97e8-f1c6-9494b355aeb3.png)

#### MLP, 関数f まとめ

周期が大きくなっても、早期終了までに費やしたエポック数に大きな変化なし。カーブの形は大きく崩れないが、周期にズレが生じた。出力の高さ（振幅）は、周期が短い場合は保存されていたが、周期が長くなると短くなっていく傾向が見出された。

### RNN, 関数f

#### RNN, 関数f, 周期 = 25

学習曲線

![output_27_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/9188858a-1cca-c81c-1f4f-6134b24f7ba3.png)

予測曲線

![output_29_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/d859399d-c609-86a2-37fa-5d05702c8a60.png)

フーリエ変換

![output_30_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/dcad4702-8442-9bbb-2055-9f8e1b9ae321.png)

#### RNN, 関数f, 周期 = 50

学習曲線

![output_27_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/3a7c0354-bbea-28e3-2f84-169862af446d.png)

予測曲線

![output_29_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/7cbb7ce8-2d22-41fe-1a54-7ffac90aab59.png)

フーリエ変換

![output_30_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/156a913b-bc04-9d4f-3639-68110b45ebd4.png)

#### RNN, 関数f, 周期 = 100

学習曲線

![output_27_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/7da64c76-49dc-001f-e2d9-58d81fc9a179.png)

予測曲線

![output_29_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/9e2be2d7-6f15-0581-1e9f-d2787f7f7800.png)

フーリエ変換

![output_30_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/0df18055-90a0-51c2-9cda-433a67d18712.png)

#### RNN, 関数f まとめ

早期終了までに費やしたエポック数は、周期が大きくなると少なくなる傾向があった。予測曲線は、短周期（25）または中周期（50）のときは良い予測ができたが、長周期（100）になると大きく形が崩れた。長周期の予測では、変なところに鋭いピークを観測した。

### LSTM, 関数f

#### LSTM, 関数f, 周期 = 25

学習曲線

![output_33_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/0f8d93b8-e072-3a9a-18b8-63e0cc1fb701.png)

予測曲線

![output_35_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/35e17302-06ea-76e1-3409-d455a9022386.png)

フーリエ変換

![output_36_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/32b99356-54f3-9d4d-9678-8f38e5a335a5.png)

#### LSTM, 関数f, 周期 = 50

学習曲線

![output_33_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/f20eccd6-dd5e-bd8f-db62-dcc1b8856e99.png)

予測曲線

![output_35_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/b2034813-564c-5e59-4596-3fd78890b2a0.png)

フーリエ変換

![output_36_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/d42213e3-84d9-c320-57cd-dfc36dc439ed.png)

#### LSTM, 関数f, 周期 = 100

学習曲線

![output_33_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/acb8562b-bef0-790f-ece7-9d37b9167cc7.png)

予測曲線

![output_35_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/3ca43599-8443-85b5-969e-be22bcec1889.png)

フーリエ変換

![output_36_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/3153254c-8393-85f6-d864-6ecfad172ccc.png)

#### LSTM, 関数f まとめ

早期終了までに費やしたエポック数は、（はっきりしないが）周期が長くなると少なくなるかもしれない。短周期（25）中周期（50）長周期（100）全てで良好な予測ができた。

### GRU, 関数f

#### GRU, 関数f, 周期 = 25

学習曲線

![output_39_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/d50020d6-5b27-d4db-e841-cecb1cccb756.png)

予測曲線

![output_41_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/7a74b0a4-d9f0-8354-4aa1-7a7f73e1c205.png)

フーリエ変換

![output_42_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/6e367855-869a-4f28-02e4-3ef4ff9bed36.png)

#### GRU, 関数f, 周期 = 50

学習曲線

![output_39_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/020fa397-e55c-6e9d-8044-dc6ccc6746ec.png)

予測曲線

![output_41_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/a1b68508-6296-6f8f-8d55-914c989fa0a6.png)

フーリエ変換

![output_42_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/0107aba6-9d83-4849-ce95-f3fa00e4ecff.png)

#### GRU, 関数f, 周期 = 100

学習曲線

![output_39_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/0650273f-1b45-de4d-4cef-9692cf5acc9f.png)

予測曲線

![output_41_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/ea53ef08-c752-af7b-fff1-cd6e1711660c.png)

フーリエ変換

![output_42_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/7fbc26bb-2967-3ad6-e0d8-51ab4ccc5423.png)

#### GRU, 関数f

GRUはLSTMより速いと聞いていたが、早期終了までに費やしたエポック数はむしろ長くなった（1000エポックに達してしまった）。短周期（25）中周期（50）長周期（100）全てで良好な予測ができた。


## 関数 g の周期を長くする

### MLP, 関数g

#### MLP, 関数g, 周期 = 25

学習曲線

![output_11_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/a822a716-0787-0025-37b9-002795434ba2.png)

予測曲線

![output_13_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/bdb7a795-ccbd-04f9-6d7a-761fe350c972.png)

フーリエ変換

![output_14_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/cb0fa144-b8fc-f8ae-b5a1-2f19b024014a.png)


#### MLP, 関数g, 周期 = 50

学習曲線

![output_11_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/40a33299-f77b-2d7e-54f0-bf912977d5a8.png)

予測曲線

![output_13_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/1cc9671c-b933-3ff5-4f33-3c7119142f96.png)

フーリエ変換

![output_14_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/b32b3a7c-bee0-96e4-4210-77dfd1775e51.png)


#### MLP, 関数g, 周期 = 100

学習曲線

![output_11_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/16dbf818-04c3-99b1-79fd-99988a2b172e.png)

予測曲線

![output_13_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/bd1a1531-1aaf-bf55-7302-9d2983b35da7.png)

フーリエ変換

![output_14_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/3f43c098-37c1-a0ea-452a-264901f88b9b.png)

#### MLP, 関数g まとめ

早期終了までに費やしたエポック数は、周期が変化してもほぼ変化なし。おおよその形は保存されているが、足元がゴニョゴニョしているのと、ピークの高さが低くなるのと、周期がズレてしまうという傾向がある。フーリエ変換の図を見てみると、（ズレてはいるが）多くのピーク（周期）を拾えているらしい。


### RNN, 関数g

#### RNN, 関数g, 周期 = 25

学習曲線

![output_27_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/5e89288a-f35c-2116-828f-74b4496632b9.png)

予測曲線

![output_29_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/97a5c6ca-2014-2fe9-803a-7b471168ee11.png)

フーリエ変換

![output_30_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/d70cfb63-65f7-96ee-d164-d71159e370dc.png)

#### RNN, 関数g, 周期 = 50

学習曲線

![output_27_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/fba2a442-1281-7cb1-56ff-cf69e5a90c61.png)

予測曲線

![output_29_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/09390cb8-8477-f528-3b00-71b9fef3b292.png)

フーリエ変換

![output_30_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/62bbaa94-344a-a884-b1f7-335795613ef4.png)

#### RNN, 関数g, 周期 = 100

学習曲線

![output_27_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/b194876f-067a-cf0e-8fee-cf43d2e43f89.png)

予測曲線

![output_29_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/ffabc428-b05e-c1d5-cd74-a0edf0602b08.png)

フーリエ変換

![output_30_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/00552e9a-0dc3-150e-8564-43b4153e3c56.png)

#### RNN, 関数g まとめ

短周期（25）のものは良好な予測。中周期（50）のものは、主なピークを拾っているけどズレてしまっている。長周期（100）では、RNNは早々に予測を諦めてしまったっぽい。たくさんある周期の中で、比較的短周期のものだけ拾ってるっぽい。


### LSTM, 関数g

#### LSTM, 関数g, 周期 = 25

学習曲線

![output_33_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/0c90db5a-e3a4-6ad2-1b8b-1f761d03723b.png)

予測曲線

![output_35_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/b84bd75e-1ffc-9b81-0d50-df682f55fae9.png)

フーリエ変換

![output_36_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/42d2cc64-0f0b-7456-dfd7-0a7b3a329512.png)

#### LSTM, 関数g, 周期 = 50

学習曲線

![output_33_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/b1620614-786e-8b9d-41c3-7f4a663b8310.png)

予測曲線

![output_35_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/15cd0772-a32e-6259-1c71-27d4b281679f.png)

フーリエ変換

![output_36_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/d1b76573-ea07-b20f-5461-2aace1975d51.png)

#### LSTM, 関数g, 周期 = 100

学習曲線

![output_33_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/3fd004f2-9e4a-a3b4-f1c2-96260fa0152b.png)

予測曲線

![output_35_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/63d4c637-3a69-2486-513b-8747df207aeb.png)

フーリエ変換

![output_36_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/8108fb4b-277c-5ad3-b316-ab5033c89c18.png)

#### LSTM, 関数g まとめ

意外にも、全ての周期でうまく行かなかった。短周期はRNNではうまくいったが、LSTMではうまくいかず、ありもしないピークを拾っていた。中周期の結果が一番マシだが、それでも周期がズレていた。長周期はもう予測を諦めたーって声が聞こえそう。


### GRU, 関数g

#### GRU, 関数g, 周期 = 25

学習曲線

![output_39_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/720aefb7-10f1-39f0-f616-81bc5b9e88bd.png)

予測曲線

![output_41_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/d7d345fb-0fa2-3fc2-7a3e-5e6f29a25a34.png)

フーリエ変換

![output_42_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/faec4ff6-a581-f6cd-aba6-56305f47ce83.png)


#### GRU, 関数g, 周期 = 50

学習曲線

![output_39_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/e38d6aca-a814-4186-d8fa-9cb478681e7f.png)

予測曲線

![output_41_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/64fc607a-bc2b-4e43-b497-5cf0016d6231.png)

フーリエ変換

![output_42_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/7a58988b-4dc6-4a3a-56d2-9769b4945bf5.png)

#### GRU, 関数g, 周期 = 100

学習曲線

![output_39_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/d417e5c9-fbb2-fb12-9244-e1e640d854bb.png)

予測曲線

![output_41_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/aeeb5b84-2c88-7513-a3d4-83a3a22a27cb.png)

フーリエ変換

![output_42_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/b8651241-a52a-d872-e877-7e930bee90bb.png)

#### GRU, 関数g まとめ

これも意外。LSTMはRNNより悪かったが、GRUはもっと悪かった。短周期のは、いくぶんかマシだが、中周期でもう諦めてしまった感じ。

## 多様な周期を持つ関数 h

当初、もっと多様な周期をミックスしてやろうと思っていましたが、上の結果を見て、こりゃもうちょっと少なめのミックスにしたほうがいいなと思いました。

### MLP, 関数h

学習曲線

![output_11_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/6d11deec-91d8-fc65-306a-6af7f8371b9b.png)

予測曲線

![output_13_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/00aa9a2c-88bd-a093-2f29-da13efd84641.png)

フーリエ変換

![output_14_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/21439de9-56e2-3edb-44dd-5b1d889c4562.png)

周期を拾ってくるのは、比較的うまく行ってるっぽい。しかし、振幅がうまく拾えていないので、結果、誤差の大きな予測になってしまっている。

### RNN, 関数h

学習曲線

![output_27_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/9788ae03-469f-c1c2-07c1-1e3039acbe73.png)

予測曲線

![output_29_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/89d3fd0d-3cec-f6f4-1547-215644438fae.png)

フーリエ変換

![output_30_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/9f54f8e5-cad7-11b8-14ba-db4f0f0aa663.png)

周期はおおむね拾ってるけど、なんかズレている。振幅はなぜか大きくなってしまっている。

### LSTM, 関数h

学習曲線

![output_33_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/be231f1b-2a9a-77d5-5d1d-18e9f58b2c19.png)

予測曲線

![output_35_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/544f1634-52cf-d090-f511-32da6eda6596.png)

フーリエ変換

![output_36_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/70bceab6-3a38-6236-617b-72dd3f54e711.png)

いくつかの周期は拾えているけど、そこにない周期もけっこう拾ってしまっている。そういう意味ではRNNのほうが少しマシ？

### GRU, 関数h

学習曲線

![output_39_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/5801dc2b-ac01-f353-0cab-a9b938392474.png)

予測曲線

![output_41_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/7a33416c-1a06-cb84-8d69-7cee7251ac1e.png)

フーリエ変換

![output_42_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/b5ecdc63-4d1d-33f1-47d3-ebde05d0cb0e.png)

LSTMよりもさらにひどくなった感じ。


# まとめ

以上、まとめです。周期関数をいくつか扱ってみた限りでは、

* 短周期のsin波は、再帰型ニューラルネットワークで再現可能。
* 長周期のsin波は、RNNでは無理。LSTMとGRUでは再現可能。
* 形が崩れた波は、短周期ならRNNで再現可能。LSTMとGRUではどちらも難しい。
* 能力は RNN < LSTM < GRU かと思っていたが、意外と逆（RNN > LSTM > GRU）になるケースも多いみたい。

まだまだ勉強不足なので、おかしな部分があるかもしれませんが、お気付きの点がございましたらご指摘いただきますと幸いです...（ヽ´ω`）