旅行好きなソフトエンジニアの備忘録

プログラミングや技術関連のメモを始めました

【Python】 LSTMによる時系列データの予測

前回SimpleRNNによる時系列データの予測を行いましたが、今回はLSTMを用いて時系列データの予測を行ってみます。

ni4muraano.hatenablog.com

LSTMはSimpleRNNと比較すると長期依存性の高いデータに有効とのことなので、50回に一回パルスが発生する信号に対する予測をSimpleRNNとLSTMで行ってみました。

import numpy as np
import matplotlib.pyplot as plt
from keras.models import Sequential
from keras.layers.core import Dense, Activation
from keras.layers.recurrent import SimpleRNN, LSTM
from keras.optimizers import Adam
from keras.callbacks import EarlyStopping

np.random.seed(0)

def generate_periodic_data():
    pulse = []
    for i in range(1000):
        if i%25 == 0:
            pulse.append(1.0)
        else:
            pulse.append(0.0)

    return np.array(pulse)


def generate_data(pulse, length_per_unit, dimension):
    sequences = []
    target = []
    for i in range(0, pulse.size - length_per_unit):
        sequences.append(pulse[i:i + length_per_unit])
        target.append(pulse[i + length_per_unit])

    X = np.array(sequences).reshape(len(sequences), length_per_unit, dimension)
    Y = np.array(target).reshape(len(sequences), dimension)

    N_train = int(len(sequences)*0.8)
    X_train = X[:N_train]
    X_validation = X[N_train:]
    Y_train = Y[:N_train]
    Y_validation = Y[N_train:]

    return (X_train, X_validation, Y_train, Y_validation)


def build_model(input_shape, hidden_layer_count):
    model = Sequential()
    model.add(LSTM(hidden_layer_count, input_shape=input_shape))
    model.add(Dense(input_shape[1]))
    model.add(Activation('linear'))
    model.compile(loss='mse', optimizer=Adam())
    return model


# 一つの時系列データの長さ
LENGTH_PER_UNIT = 201
# 一次元データを扱う
DIMENSION = 1
# パルスの生成
pulse = generate_periodic_data()
# トレーニング、バリデーション用データの生成
X_train, X_validation, Y_train, Y_validation = generate_data(pulse, LENGTH_PER_UNIT, DIMENSION)

# LSTM隠れ層の数
HIDDEN_LAYER_COUNT = 25
# 入力の形状
input_shape=(LENGTH_PER_UNIT, DIMENSION)
# モデルの生成
model = build_model(input_shape, HIDDEN_LAYER_COUNT)

# モデルのトレーニング
epochs = 500
batch_size = 10
early_stopping = EarlyStopping(monitor='val_loss', patience=10, verbose=1)
model.fit(X_train, Y_train,
          batch_size=batch_size,
          epochs=epochs,
          validation_data=(X_validation, Y_validation),
          callbacks=[early_stopping])

# 予測を行う
part_of_sequence = np.array([pulse[i] for i in range(LENGTH_PER_UNIT)])
predicted = [None for i in range(LENGTH_PER_UNIT)]
Z = X_train[:1, :, :]
for i in range(pulse.size - LENGTH_PER_UNIT + 1):
    y_ = model.predict(Z)
    # 予測結果を入力として利用するため、第0項を削除し予測結果をひっつける
    Z = np.concatenate(
            (Z.reshape(LENGTH_PER_UNIT, DIMENSION)[1:], y_),
            axis=0).reshape(1, LENGTH_PER_UNIT, DIMENSION)
    predicted.append(y_.reshape(-1))
predicted = np.array(predicted)

# 予測結果の描画
plt.rc('font', family='serif')
plt.figure()
plt.ylim([0.0, 1.1])
plt.plot(pulse, linestyle='dotted', color='#aaaaaa')
plt.plot(part_of_sequence, linestyle='dashed', color='black')
plt.plot(predicted, color='black')
plt.show()

SimpleRNNの予測結果がこちらでf:id:ni4muraano:20170617162126p:plain

LSTMの予測結果がこうなりました。f:id:ni4muraano:20170617162147p:plain

LSTMはもう少し綺麗に予測できることを期待していたのですが、パラメータチューニングを行っていないので上図のような結果になったのかなと思います。とはいえ閾値決めてあげることで後処理でどうにでもなりそうなのでSimpleRNNよりは良い結果なのではと思います。

詳解 ディープラーニング ~TensorFlow・Kerasによる時系列データ処理~

詳解 ディープラーニング ~TensorFlow・Kerasによる時系列データ処理~

【Python】 SimpleRNNで月平均気温を予測する

画像だけでなく時系列データにも手を出してみたい、ということで書籍「詳解ディープラーニング」を購入しました。書籍第5章から時系列データを扱っているのですが、そこで紹介されているSimpleRNNの例を写経します。書籍ではノイズの入ったサイン波の予測を行っているのですが、ここでは気象庁から取得した年別の月平均気温を使って予測を行います。

www.data.jma.go.jp


import numpy as np
import matplotlib.pyplot as plt
from keras.models import Sequential
from keras.layers.core import Dense, Activation
from keras.layers.recurrent import SimpleRNN
from keras.optimizers import Adam
from keras.callbacks import EarlyStopping

np.random.seed(0)

def generate_temperatures():
    # 月平均気温
    temperatures = [[7.6, 6.0, 9.4, 14.5, 19.8, 22.5, 27.7, 28.3, 25.6, 18.8, 13.3, 8.8], #2000年
                    [4.9, 6.6, 9.8, 15.7, 19.5, 23.1, 28.5, 26.4, 23.2, 18.7, 13.1, 8.4], #2001年
                    [7.4, 7.9, 12.2, 16.1, 18.4, 21.6, 28.0, 28.0, 23.1, 19.0, 11.6, 7.2],#2002年
                    [5.5, 6.4, 8.7, 15.1, 18.8, 23.2, 22.8, 26.0, 24.2, 17.8, 14.4, 9.2], #2003年
                    [6.3, 8.5, 9.8, 16.4, 19.6, 23.7, 28.5, 27.2, 25.1, 17.5, 15.6, 9.9], #2004年
                    [6.1, 6.2, 9.0, 15.1, 17.7, 23.2, 25.6, 28.1, 24.7, 19.2, 13.3, 6.4],
                    [5.1, 6.7, 9.8, 13.6, 19.0, 22.5, 25.6, 27.5, 23.5, 19.5, 14.4, 9.5],
                    [7.6, 8.6, 10.8, 13.7, 19.8, 23.2, 24.4, 29.0, 25.2, 19.0, 13.3, 9.0],
                    [5.9, 5.5, 10.7, 14.7, 18.5, 21.3, 27.0, 26.8, 24.4, 19.4, 13.1, 9.8],
                    [6.8, 7.8, 10.0, 15.7, 20.1, 22.5, 26.3, 26.6, 23.0, 19.0, 13.5, 9.0],
                    [7.0, 6.5, 9.1, 12.4, 19.0, 23.6, 28.0, 29.6, 25.1, 18.9, 13.5, 9.9],
                    [5.1, 7.0, 8.1, 14.5, 18.5, 22.8, 27.3, 27.5, 25.1, 19.5, 14.9, 7.5],
                    [4.8, 5.4, 8.8, 14.5, 19.6, 21.4, 26.4, 29.1, 26.2, 19.4, 12.7, 7.3],
                    [5.5, 6.2, 12.1, 15.2, 19.8, 22.9, 27.3, 29.2, 25.2, 19.8, 13.5, 8.3],
                    [6.3, 5.9, 10.4, 15.0, 20.3, 23.4, 26.8, 27.7, 23.2, 19.1, 14.2, 6.7],
                    [5.8, 5.7, 10.3, 14.5, 21.1, 22.1, 26.2, 26.7, 22.6, 18.4, 13.9, 9.3],
                    [6.1, 7.2, 10.1, 15.4, 20.2, 22.4, 25.4, 27.1, 24.4, 18.7, 11.4, 8.9]]#2016年
    temperatures = np.array(temperatures)
    temperatures = np.reshape(temperatures, (temperatures.size))
    return temperatures


def generate_data(temperatures, length_per_unit, dimension):
    sequences = []
    target = []
    for i in range(0, temperatures.size - length_per_unit):
        sequences.append(temperatures[i:i + length_per_unit])
        target.append(temperatures[i + length_per_unit])

    X = np.array(sequences).reshape(len(sequences), length_per_unit, dimension)
    Y = np.array(target).reshape(len(sequences), dimension)

    N_train = int(len(sequences) * 0.9)
    X_train = X[:N_train]
    X_validation = X[N_train:]
    Y_train = Y[:N_train]
    Y_validation = Y[N_train:]

    return (X_train, X_validation, Y_train, Y_validation)


def build_model(input_shape, hidden_layer_count):
    model = Sequential()
    model.add(SimpleRNN(hidden_layer_count, input_shape=input_shape))
    model.add(Dense(input_shape[1]))
    model.add(Activation('linear'))
    model.compile(loss='mse', optimizer=Adam())
    return model


# 一つの時系列データの長さ
LENGTH_PER_UNIT = 24
# 一次元データを扱う
DIMENSION = 1
# 年別月平均気温の生成
temperatures = generate_temperatures()
# トレーニング、バリデーション用データの生成
X_train, X_validation, Y_train, Y_validation = generate_data(temperatures, LENGTH_PER_UNIT, DIMENSION)

# SimpleRNN隠れ層の数
HIDDEN_LAYER_COUNT = 25
# 入力の形状
input_shape=(LENGTH_PER_UNIT, DIMENSION)
# モデルの生成
model = build_model(input_shape, HIDDEN_LAYER_COUNT)

# モデルのトレーニング
epochs = 500
batch_size = 10
early_stopping = EarlyStopping(monitor='val_loss', patience=10, verbose=1)
model.fit(X_train, Y_train,
          batch_size=batch_size,
          epochs=epochs,
          validation_data=(X_validation, Y_validation),
          callbacks=[early_stopping])

# 予測を行う
part_of_sequence = np.array([temperatures[i] for i in range(LENGTH_PER_UNIT)])
predicted = [None for i in range(LENGTH_PER_UNIT)]
Z = X_train[:1, :, :]
for i in range(temperatures.size - LENGTH_PER_UNIT + 1):
    y_ = model.predict(Z)
    # 予測結果を入力として利用するため、第0項を削除し予測結果をひっつける
    Z = np.concatenate(
            (Z.reshape(LENGTH_PER_UNIT, DIMENSION)[1:], y_),
            axis=0).reshape(1, LENGTH_PER_UNIT, DIMENSION)
    predicted.append(y_.reshape(-1))
predicted = np.array(predicted)

# 予測結果の描画
plt.rc('font', family='serif')
plt.figure()
plt.ylim([0.0, 35.0])
plt.plot(temperatures, linestyle='dotted', color='#aaaaaa')
plt.plot(part_of_sequence, linestyle='dashed', color='black')
plt.plot(predicted, color='black')
plt.show()


割と無難な結果を得ることが出来ました。 f:id:ni4muraano:20170615213133p:plain

詳解 ディープラーニング ~TensorFlow・Kerasによる時系列データ処理~

詳解 ディープラーニング ~TensorFlow・Kerasによる時系列データ処理~

【料理】 たらこパスタの実装

下記を参考に作りました。 cookpad.com

用意するもの(二名分)

  • たらこ   2腹
  • バター   30g
  • 牛乳    大さじ4
  • 醤油    大さじ1
  • きざみのり 適量
  • パスタ   180g

1 お湯を沸かす

2 ボールにたらこ、バター、牛乳、醤油を入れてソースを作る

3 パスタを茹でる

4 お湯を捨ててそこにソースを入れて良く混ぜる

5 容器に移してきざみのりをかける

【ディープラーニング】 不均衡データへの対処法

ディープラーニングというより、機械学習全般で起こりうる問題として不均衡データ問題があります。例えばラベル0のデータが99個あり、ラベル1のデータが1個しかない、といったように教師データのバランスが悪い場合、モデルは「とりあえず0と回答すれば99%の正解率が得られる」というようなずる賢い学習をしてしまいます。
この対処法の一つとしてこちらの論文(https://users.cs.fiu.edu/~chens/PDF/ISM15.pdf)ではデータの与え方を工夫しているのですが、自分のケースではかなり有効だったため、論文のpp.3に書いてある疑似コードをメモします。

!!!注意!!!
元の論文では"for N"の外側でCNNのトレーニングを行っていますが、kerasはデフォルトではトレーニングデータをシャッフルするので、内側でトレーニングした方が正例と負例の比率が意図した通りに保たれるので良いと考え意図的に変更しています

1. トレーニングデータを負例(大量)と正例(少量)に分割する
2. 負例をN個のバッチに分割する
3. for エポック
4.     for N
5.         ランダムにNp個の正例をピックアップする
6.         負例、正例を混ぜる
7.         CNNのトレーニング

f:id:ni4muraano:20170607225555p:plain

【WPF】 Contentに改行付きのテキストを書く

Contentに改行を付けたくて、その方法がここ(【WPF】コントロールのContentプロパティにテキストを改行して表示する - Microsoft.NET WPF - Project Group )で分かったのでメモします。

<!--&#10;が改行を表しています-->
<Button Content="Hello World&#10;\(^o^)/" IsHitTestVisible="False"/>

f:id:ni4muraano:20170602225033p:plain

【OpenCV】 画像を合成する(アルファブレンディング)

// 画像を読み込む
Mat lenna;
imread("lenna.jpg").copyTo(lenna);
if (lenna.empty())
{
    throw runtime_error("Failed to open image");
}

// 画像を読み込む
Mat clock;
imread("clock.jpg").copyTo(clock);
if (clock.empty())
{
    throw runtime_error("Failed to open image");
}

// 画像を1:1の割合で合成する
Mat destination;
addWeighted(lenna, 0.5, clock, 0.5, 0.0, destination);

f:id:ni4muraano:20170524221233j:plain:w150 f:id:ni4muraano:20170524221246j:plain:w150 f:id:ni4muraano:20170524221256j:plain:w150

【Python】 ndarrayのインデキシング

書籍「科学技術計算のためのPYTHON入門」のメモです。ndarrayのインデキシングについて今まで何も意識せず使っていたのですが、書籍にまとめられていたのでメモします。

  • 基本インデキシング(ビューが生成される)
# ndarray_viewの変更がndarrayにも影響を及ぼす
ndarray_view = ndarray[1, :]
  • 応用インデキシング(コピーが生成される)
    • ブール値インデキシング
    • 整数配列インデキシング
# ブール値インデキシングの例
# 0.5より大きい箇所のみ取り出す
# maskedを変更してもndarrayに影響を及ぼさない
mask = ndarray > 0.5
masked = ndarray[mask]

# 整数配列インデキシングの例
# (0,2), (2,0), (1,3)の位置の要素を取り出す
# extractedを変更してもndarrayに影響を及ぼさない
extracted = ndarray[[0, 2, 1], [2, 0, 3]]

科学技術計算のためのPython入門 ――開発基礎、必須ライブラリ、高速化

科学技術計算のためのPython入門 ――開発基礎、必須ライブラリ、高速化