Tensorflow Keras の RNN と scikit-learn の線形回帰で Sin 波の予測

Google Colaboratory で試す: https://colab.research.google.com/gist/propella/1ada5171f9884dbf15a96290aa7369a3

以下の記事と同じ事をするだけです。さらに LinearRegression との比較もしてみます。
* [詳解ディープラーニング TensorFlow Keras による時系列データ処理](https://www.amazon.co.jp/dp/4839962510/ref=cm_sw_em_r_mt_dp_U_G1RpCbTN7FZT2) 第五章 リカレントニューラルネットワーク
* [深層学習ライブラリKerasでRNNを使ってsin波予測](https://qiita.com/yukiB/items/5d5b202af86e3c587843)
* [RNNにsin波を学習させて予測してみた](https://qiita.com/yuyakato/items/ab38064ca215e8750865)
* https://github.com/sunsided/tensorflow-lstm-sin



```python
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score
from sklearn.model_selection import train_test_split
from tensorflow import keras
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import tensorflow as tf
```

## データの作成

周期が 100 のノイズ入りサイン波を 200 個作ります。このなかで任意の 25 の連続した値から次の値を予測するというのをやります。

ノイズを乗せても予測される値はノイズの消えたきれいなカーブになりますよ、という事がしたいです。


```python
period = 100
np.random.seed(2)

def sin_curve(length, ampl=0.05):
    signal = np.sin(2 * np.pi * np.arange(0, length) / period)
    noise = ampl * np.random.uniform(low=-1.0, high=1.0, size=length)
    return signal + noise

source = sin_curve(200)
plt.plot(source)
plt.show()
```


![output_3_0.png](https://qiita-image-store.s3.amazonaws.com/0/35350/1898158d-fef4-4ba1-1589-3971f1117a4f.png)


ひと区間を 25 + 1 個として、先頭から区間をずらしながらデータを作ります。


```python
chunk = 25
data = np.array([source[start: start + chunk + 1] for start in range(0, len(source) - chunk)])
print('Shape of the data:', data.shape)
print('First 3 data:\n', data[:3])
```

    Shape of the data: (175, 26)
    First 3 data:
     [[-0.00640051  0.01538314  0.13029948  0.18091355  0.24072667  0.29205048
       0.33858942  0.43770639  0.46171914  0.51250952  0.59989864  0.6403382
       0.6480051   0.73032644  0.73895723  0.83755051  0.87972545  0.87573036
       0.9394832   0.88774103  0.95158113  0.92511181  0.97509948  0.95176779
       0.96074273  1.00967453]
     [ 0.01538314  0.13029948  0.18091355  0.24072667  0.29205048  0.33858942
       0.43770639  0.46171914  0.51250952  0.59989864  0.6403382   0.6480051
       0.73032644  0.73895723  0.83755051  0.87972545  0.87573036  0.9394832
       0.88774103  0.95158113  0.92511181  0.97509948  0.95176779  0.96074273
       1.00967453  0.97062793]
     [ 0.13029948  0.18091355  0.24072667  0.29205048  0.33858942  0.43770639
       0.46171914  0.51250952  0.59989864  0.6403382   0.6480051   0.73032644
       0.73895723  0.83755051  0.87972545  0.87573036  0.9394832   0.88774103
       0.95158113  0.92511181  0.97509948  0.95176779  0.96074273  1.00967453
       0.97062793  0.95280927]]


ある区間の先頭 25 個を予測に使う特徴データ (X) とし、最後を正解データ (y) とします。


```python
X = data[:, :-1]
y = data[:, -1]
print('First 3 features (X):\n', X[:3])
print('First 3 labels (y):', y[:3])
```

    First 3 features (X):
     [[-0.00640051  0.01538314  0.13029948  0.18091355  0.24072667  0.29205048
       0.33858942  0.43770639  0.46171914  0.51250952  0.59989864  0.6403382
       0.6480051   0.73032644  0.73895723  0.83755051  0.87972545  0.87573036
       0.9394832   0.88774103  0.95158113  0.92511181  0.97509948  0.95176779
       0.96074273]
     [ 0.01538314  0.13029948  0.18091355  0.24072667  0.29205048  0.33858942
       0.43770639  0.46171914  0.51250952  0.59989864  0.6403382   0.6480051
       0.73032644  0.73895723  0.83755051  0.87972545  0.87573036  0.9394832
       0.88774103  0.95158113  0.92511181  0.97509948  0.95176779  0.96074273
       1.00967453]
     [ 0.13029948  0.18091355  0.24072667  0.29205048  0.33858942  0.43770639
       0.46171914  0.51250952  0.59989864  0.6403382   0.6480051   0.73032644
       0.73895723  0.83755051  0.87972545  0.87573036  0.9394832   0.88774103
       0.95158113  0.92511181  0.97509948  0.95176779  0.96074273  1.00967453
       0.97062793]]
    First 3 labels (y): [1.00967453 0.97062793 0.95280927]


訓練データとテストデータに分けます。


```python
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
```

データの最初の区間分から残りの部分を予想してプロットする関数です。


```python
def plot_predict(predict, start, length):
    history = start
    for i in range(length):
        feature = history[-chunk:].reshape(1, -chunk)
        pred = predict(feature)
        history = np.append(history, pred)
    plt.figure(figsize=(16,4))
    plt.plot(np.sin(2 * np.pi * np.arange(0, len(start) + length) / period), '--', label='reference')
    plt.plot(history, label='predict')
    plt.legend()
    plt.show()
```

## scikit-learn の LinearRegression で予測

比較対象として、単純な線形回帰でやってみます。


```python

def sklearn_linear():
    reg = LinearRegression()
    reg.fit(X_train, y_train) # 学習
    
    def predict(features):
        return reg.predict(features)
    
    y_test_pred = predict(X_test)
    print('r2_score: %.3f' % r2_score(y_test, y_test_pred)) # 検証
    plot_predict(predict, source[:chunk], 300)
    
%time sklearn_linear()
```

    r2_score: 0.998



![output_13_1.png](https://qiita-image-store.s3.amazonaws.com/0/35350/95225c5c-7b7d-9dfd-a8e3-93a090b6abdb.png)


    CPU times: user 557 ms, sys: 74.8 ms, total: 632 ms
    Wall time: 264 ms


滅茶苦茶良過ぎでは。。。

小さすぎて分かりづらいですが、グラフの前の方の赤いギザギザした部分が入力データです。ここは学習に使った物と同じです(本来なら新しく作り直すべきですね)。x = 25 以降のなめらかな赤いカーブは予測で得られた物です。学習データにノイズが乗っていたにも関わらず、ノイズが除去されて綺麗なカーブになっています。バックの青い点線は参考までに普通のサインカーブを描いた物です。予測がかなり正確である事が分かります。

## TensorFlow の RNN で予測

RNN の一種 LSTM というのを使ってみます。


```python
def tensorflow_lstm():

    model = keras.Sequential([
        keras.layers.LSTM(256, batch_input_shape=(None, chunk, 1)),
        keras.layers.Dense(1)
    ])

    model.compile(optimizer=tf.train.AdamOptimizer(), 
              loss='mean_squared_error',
              metrics=['accuracy'])
    model.summary()

    model.fit(X_train.reshape(-1, chunk, 1), y_train, epochs=20)
    
    def predict(features):
        return model.predict(features.reshape(-1, chunk, 1))[:,0]
    
    y_test_pred = predict(X_test)
    print('r2_score: %.3f' % r2_score(y_test, y_test_pred))
    plot_predict(predict, source[:chunk], 300)

%time tensorflow_lstm()
```

    _________________________________________________________________
    Layer (type)                 Output Shape              Param #   
    =================================================================
    lstm (LSTM)                  (None, 256)               264192    
    _________________________________________________________________
    dense (Dense)                (None, 1)                 257       
    =================================================================
    Total params: 264,449
    Trainable params: 264,449
    Non-trainable params: 0
    _________________________________________________________________
    Epoch 1/20
    131/131 [==============================] - 1s 7ms/step - loss: 0.3450 - acc: 0.0000e+00
    Epoch 2/20
    131/131 [==============================] - 0s 2ms/step - loss: 0.0709 - acc: 0.0000e+00
    Epoch 3/20
    131/131 [==============================] - 0s 2ms/step - loss: 0.0202 - acc: 0.0000e+00
    Epoch 4/20
    131/131 [==============================] - 0s 2ms/step - loss: 0.0075 - acc: 0.0000e+00
    Epoch 5/20
    131/131 [==============================] - 0s 2ms/step - loss: 0.0042 - acc: 0.0000e+00
    Epoch 6/20
    131/131 [==============================] - 0s 2ms/step - loss: 0.0046 - acc: 0.0000e+00
    Epoch 7/20
    131/131 [==============================] - 0s 2ms/step - loss: 0.0032 - acc: 0.0000e+00
    Epoch 8/20
    131/131 [==============================] - 0s 2ms/step - loss: 0.0020 - acc: 0.0000e+00
    Epoch 9/20
    131/131 [==============================] - 0s 2ms/step - loss: 0.0020 - acc: 0.0000e+00
    Epoch 10/20
    131/131 [==============================] - 0s 3ms/step - loss: 0.0014 - acc: 0.0000e+00
    Epoch 11/20
    131/131 [==============================] - 0s 2ms/step - loss: 0.0015 - acc: 0.0000e+00
    Epoch 12/20
    131/131 [==============================] - 0s 2ms/step - loss: 0.0013 - acc: 0.0000e+00
    Epoch 13/20
    131/131 [==============================] - 0s 2ms/step - loss: 0.0010 - acc: 0.0000e+00
    Epoch 14/20
    131/131 [==============================] - 0s 2ms/step - loss: 0.0013 - acc: 0.0000e+00
    Epoch 15/20
    131/131 [==============================] - 0s 2ms/step - loss: 0.0011 - acc: 0.0000e+00
    Epoch 16/20
    131/131 [==============================] - 0s 2ms/step - loss: 0.0011 - acc: 0.0000e+00
    Epoch 17/20
    131/131 [==============================] - 0s 2ms/step - loss: 0.0012 - acc: 0.0000e+00
    Epoch 18/20
    131/131 [==============================] - 0s 2ms/step - loss: 0.0014 - acc: 0.0000e+00
    Epoch 19/20
    131/131 [==============================] - 0s 2ms/step - loss: 0.0016 - acc: 0.0000e+00
    Epoch 20/20
    131/131 [==============================] - 0s 2ms/step - loss: 0.0016 - acc: 0.0000e+00
    r2_score: 0.998



![output_15_1.png](https://qiita-image-store.s3.amazonaws.com/0/35350/552025c0-18aa-29c2-b12b-8f1334c489c5.png)


    CPU times: user 33.9 s, sys: 8.18 s, total: 42.1 s
    Wall time: 9.5 s


決定係数 r2_score だけ見ると似たような結果ですが、グラフを描いてみると線形回帰の方が断然良いです。

## LinearRegression で Sin 波の予測が出来る理由

相関関係を見つける LinearRegression で何故このような周期的な振る舞いを検出出来るのか不思議です。予測する値が直近の値に強く影響されるのは当然なのですが、予測する値が上り坂の位置にいるのか下り坂の位置にいるのか何故分かるのだろう? 試しに予測器の偏回帰係数を見てみます。


```python
def sklearn_linear_coef():
    reg = LinearRegression()
    reg.fit(X_train, y_train)
    plt.plot(reg.coef_)
sklearn_linear_coef()
```


![output_17_0.png](https://qiita-image-store.s3.amazonaws.com/0/35350/c6ba7f6e-9653-981f-3d5e-d7f0f320849f.png)



予想どうり直近のデータの影響が強く、昔のデータの影響が低いという事が分かります。これで LinearRegression が上り坂か下り坂かをどう判断するのかちょっと考えてみます。

本題からはずれますが、LinearRegression は説明変数に定数を掛けて足し合わせるだけなのに、綺麗なサインカーブが描けるのは非常に面白いなと思いました。


## 結論

サインカーブの予想には予想に反して RNN (LSTM) より LinearRegression の方が良いことが分かりました。 LinearRegression にこのような使い方が出来るとは驚きでした。

ちなみに色々試すと LinearRegression では過去データの数 chunk = 8 くらいでも完璧に予測しますが、LSTM では全然だめでした。