社内でKDD2017論文を紹介した
社内の有志でKDD2017の論文紹介をしました。
紹介した内容はスパース推定に関するアルゴリズムの話です。
発表資料をslideshareに公開したので、そのリンクをこちらにもはっておきます。
Qualityがよくないかもしれませんので、ご質問やご意見がありましたらご連絡いただけると幸いです。
www.slideshare.net
KerasでDeep Learning:LSTMで日経平均株価を予測してみる
導入
前回までで、画像データに関してDeep Learningを試してきました。画像データは、各データが独立と期待されるようなタイプのデータです。しかしながら、Deep Learningはこのような各データが独立であるような場合だけでしかできないというわけではありません。データ間に相関がある場合の代表例として、時系列データがあります。今回は、時系列データに対して威力を発揮するネットワークをKerasで実装してみます。
使用データ
人工データを使うのもあれなので、より現実的で身近なデータを使ってみます。今回は、日経平均株価の終値(日次)を使います。日経平均株価のデータは、以下のサイトからダウンロードしました。
日経平均株価 1時間足 時系列データ CSVダウンロード
あるだけ(2007年以降)全てダウンロードし、それらを結合して一つのファイルを作っておきます。
終値(finish)をプロットすると、以下のようになっています。
今回は、このデータの90%を学習と検証に使用し、残り10%で予実比較をします。
参考
ネットワークの組み方は、以下を参考にしました。
qiita.com
ただし、こちらのKerasのVersionは古いので、いくつかオプション名が変わっています。そこはdocumentなどを参照して適宜書き換えます。
前処理
今回、予測の仕方を少しだけ工夫します。直前の50営業日の株価データを用いて、次の営業日の株価を予想する、という方法です。その用途に即したデータ加工を以下の関数で行います。
import numpy as np import pandas as pd from pandas import Series, DataFrame # 直前のn_prev 日分のデータと、その次の営業日のデータを生成 def _load_data(data, n_prev = 50): docX, docY = [], [] for i in range(len(data)-n_prev): docX.append(data.iloc[i:i+n_prev].as_matrix()) docY.append(data.iloc[i+n_prev].as_matrix()) alsX = np.array(docX) alsY = np.array(docY) return alsX, alsY # 学習用とテスト用データを分割、ただし分割する際に_load_data()を適用 def train_test_split(df, test_size=0.1, n_prev = 50): """ This just splits data to training and testing parts """ ntrn = round(len(df) * (1 - test_size)) ntrn = int(ntrn) X_train, y_train = _load_data(df.iloc[0:ntrn], n_prev) X_test, y_test = _load_data(df.iloc[ntrn:], n_prev) return (X_train, y_train), (X_test, y_test)
train_test_split()にしかるべき株価データを投入すれば、所定の形式の学習・テストデータを生成することができます。株価データは、dfというDataFrameに格納されており、終値のカラム名はfinishであるとします。スケールを狭めるのに、全体の平均値で割った操作をした終値を目的変数とします。
# 全体の平均値で割る df['obs'] = df['finish'] / df['finish'].mean() length_of_sequences = 50 (X_train, y_train), (X_test, y_test) = train_test_split(df[['obs']], test_size = 0.1, n_prev = length_of_sequences)
LSTM
ここでは、時系列データを扱うネットワークであるRecurrent Neural Network(RNN)の一つである、LSTMというネットワークを用いてモデル化します。LSTMに関する詳しい説明は、例えば以下の書籍やサイトなどを参照ください*1。
bookclub.kodansha.co.jp
qiita.com
Kerasで必要なライブラリをインポートし、前回までと同じような感じでネットワークを組んでみます。
from keras.models import Sequential from keras.layers.core import Dense, Activation from keras.layers.recurrent import LSTM in_out_neurons = 1 hidden_neurons = 100 model = Sequential() model.add(LSTM(hidden_neurons, batch_input_shape=(None, length_of_sequences, in_out_neurons), return_sequences=False)) model.add(Dense(in_out_neurons)) model.add(Activation("linear"))
これは可視化すると以下のようなネットワークを組んだことになっています。
書籍などで勉強するとわかりますが、このLSTMというところは、中身は結構複雑です。
学習をさせてみます。
model.compile(loss="mean_squared_error", optimizer="adam") history = model.fit(X_train, y_train, batch_size=600, epochs=50, validation_split=0.2)
Train on 1822 samples, validate on 456 samples Epoch 1/50 1822/1822 [==============================] - 2s - loss: 0.5133 - val_loss: 0.4263 Epoch 2/50 1822/1822 [==============================] - 2s - loss: 0.0918 - val_loss: 0.0283 Epoch 3/50 1822/1822 [==============================] - 2s - loss: 0.0574 - val_loss: 0.0162 Epoch 4/50 1822/1822 [==============================] - 2s - loss: 0.0108 - val_loss: 0.0325 Epoch 5/50 1822/1822 [==============================] - 2s - loss: 0.0109 - val_loss: 0.0707 Epoch 6/50 1822/1822 [==============================] - 2s - loss: 0.0179 - val_loss: 0.0485 Epoch 7/50 1822/1822 [==============================] - 2s - loss: 0.0080 - val_loss: 0.0116 Epoch 8/50 1822/1822 [==============================] - 1s - loss: 0.0025 - val_loss: 0.0016 Epoch 9/50 1822/1822 [==============================] - ETA: 0s - loss: 0.006 - 2s - loss: 0.0065 - val_loss: 0.0016 Epoch 10/50 1822/1822 [==============================] - 2s - loss: 0.0043 - val_loss: 0.0070 Epoch 11/50 1822/1822 [==============================] - 2s - loss: 0.0020 - val_loss: 0.0177 Epoch 12/50 1822/1822 [==============================] - 2s - loss: 0.0032 - val_loss: 0.0188 Epoch 13/50 1822/1822 [==============================] - 2s - loss: 0.0028 - val_loss: 0.0108 Epoch 14/50 1822/1822 [==============================] - 2s - loss: 0.0018 - val_loss: 0.0049 Epoch 15/50 1822/1822 [==============================] - 2s - loss: 0.0019 - val_loss: 0.0039 Epoch 16/50 1822/1822 [==============================] - 2s - loss: 0.0018 - val_loss: 0.0056 Epoch 17/50 1822/1822 [==============================] - 2s - loss: 0.0015 - val_loss: 0.0075 Epoch 18/50 1822/1822 [==============================] - 2s - loss: 0.0015 - val_loss: 0.0072 Epoch 19/50 1822/1822 [==============================] - 2s - loss: 0.0014 - val_loss: 0.0057 Epoch 20/50 1822/1822 [==============================] - 2s - loss: 0.0013 - val_loss: 0.0045 Epoch 21/50 1822/1822 [==============================] - 1s - loss: 0.0012 - val_loss: 0.0041 Epoch 22/50 1822/1822 [==============================] - 2s - loss: 0.0012 - val_loss: 0.0039 Epoch 23/50 1822/1822 [==============================] - 2s - loss: 0.0011 - val_loss: 0.0037 Epoch 24/50 1822/1822 [==============================] - 2s - loss: 0.0010 - val_loss: 0.0036 Epoch 25/50 1822/1822 [==============================] - 2s - loss: 9.9451e-04 - val_loss: 0.0035 Epoch 26/50 1822/1822 [==============================] - 2s - loss: 9.4513e-04 - val_loss: 0.0028 Epoch 27/50 1822/1822 [==============================] - 2s - loss: 9.0099e-04 - val_loss: 0.0026 Epoch 28/50 1822/1822 [==============================] - 2s - loss: 8.5874e-04 - val_loss: 0.0023 Epoch 29/50 1822/1822 [==============================] - 1s - loss: 8.1982e-04 - val_loss: 0.0020 Epoch 30/50 1822/1822 [==============================] - 2s - loss: 7.9007e-04 - val_loss: 0.0018 Epoch 31/50 1822/1822 [==============================] - 2s - loss: 7.5337e-04 - val_loss: 0.0019 Epoch 32/50 1822/1822 [==============================] - 2s - loss: 7.1939e-04 - val_loss: 0.0019 Epoch 33/50 1822/1822 [==============================] - 2s - loss: 6.9869e-04 - val_loss: 0.0016 Epoch 34/50 1822/1822 [==============================] - 2s - loss: 6.7346e-04 - val_loss: 0.0013 Epoch 35/50 1822/1822 [==============================] - 2s - loss: 6.7397e-04 - val_loss: 0.0013 Epoch 36/50 1822/1822 [==============================] - 2s - loss: 6.4443e-04 - val_loss: 0.0013 Epoch 37/50 1822/1822 [==============================] - 2s - loss: 6.2074e-04 - val_loss: 0.0014 Epoch 38/50 1822/1822 [==============================] - 2s - loss: 6.3547e-04 - val_loss: 0.0014 Epoch 39/50 1822/1822 [==============================] - 2s - loss: 6.0775e-04 - val_loss: 0.0012 Epoch 40/50 1822/1822 [==============================] - 2s - loss: 6.3238e-04 - val_loss: 0.0012 Epoch 41/50 1822/1822 [==============================] - 2s - loss: 6.0300e-04 - val_loss: 0.0014 Epoch 42/50 1822/1822 [==============================] - 2s - loss: 6.1697e-04 - val_loss: 0.0012 Epoch 43/50 1822/1822 [==============================] - 2s - loss: 6.3159e-04 - val_loss: 0.0012 Epoch 44/50 1822/1822 [==============================] - 2s - loss: 6.1184e-04 - val_loss: 0.0013 Epoch 45/50 1822/1822 [==============================] - 2s - loss: 6.1628e-04 - val_loss: 0.0013 Epoch 46/50 1822/1822 [==============================] - 2s - loss: 5.9740e-04 - val_loss: 0.0012 Epoch 47/50 1822/1822 [==============================] - 2s - loss: 6.0511e-04 - val_loss: 0.0012 Epoch 48/50 1822/1822 [==============================] - 2s - loss: 6.0267e-04 - val_loss: 0.0014 Epoch 49/50 1822/1822 [==============================] - 2s - loss: 6.1846e-04 - val_loss: 0.0012 Epoch 50/50 1822/1822 [==============================] - 2s - loss: 6.0198e-04 - val_loss: 0.0012
途中少しぶれがあれど、train、validationともにepoch数が大きくなるにつれてlossが減少しています。
念のため可視化すると以下の感じになっています。
予測
いよいよ予実比較してみます。
# 予測値算出 predicted = model.predict(X_test) # 実績と比較 dataf = pd.DataFrame(predicted) dataf.columns = ["predict"] dataf["input"] = y_test dataf.plot(figsize=(15, 5))
可視化すると以下のようになっています。
うーん、見た目的には似てる感じですが、これをもって予測がうまくいっていると言えるのか…?色々試して考察しなきゃいけない事案な気がするので、いったんはコメントを控えておこう(笑)ここでのメッセージはKerasを使うとDeep Learningによる株価予測をお手軽にできるよ、ということで。
まとめ
今回は株価データに対してLSTMを適用してみました。今回はかなり単純なセットアップをとったので、まだまだやりようはありそうです*2。そういった試行錯誤は気が向いたらやります(笑)
次回以降はまた画像に戻って画像生成とか考えてみようかな、と妄想中です。
KerasでDeep Learning:CNNを組んでみる
導入
前回はMNISTデータに対してネットワークを構築して、精度を見ました。
tekenuko.hatenablog.com
今回は、より画像処理に特化したネットワークを構築してみて、その精度検証をします。
参考
KerasのGithubにあるexampleのほぼ丸パクリです。
github.com
畳み込みニューラルネットワーク
畳み込みニューラルネットワーク(Convolutional Neural Network, 以下CNN)は、畳み込み層とプーリング層というもので構成されるネットワークです。CNNは画像データに対して威力を発揮します。このあたりの詳しい説明は講談社の書籍や、ネット上の解説で見ることができるので、省略します。
bookclub.kodansha.co.jp
postd.cc
ざっくりとは、ニューラルネットのリンク部分を全結合にしないで、うまく一部だけ結合させ、その一部の平均なり最大値なりをとるなどして画像の特徴をうまく抽出する手法になっています。このようなネットワークを何層も組むと、入力層に近い層では原始的な特徴(エッジ部分など)を、より深い層では抽象的な特徴(猫っぽいなど)が表現されるようです。
データ加工
今回も前回と同様、MNISTデータを使います。ただし、前回は28ピクセル×28ピクセルのデータを784次元のベクトルに変換して入力としましたが、今回は行列として入力します。
# 必要なライブラリのインポート import keras from keras.datasets import mnist from keras import backend as K #Kerasの関数でデータの読み込み。データをシャッフルして学習データと訓練データに分割 (x_train, y_train), (x_test, y_test) = mnist.load_data() # 行列として入力するための加工 batch_size = 128 num_classes = 10 epochs = 20 img_rows, img_cols = 28, 28 # 場合分け if K.image_data_format() == 'channels_first': x_train = x_train.reshape(x_train.shape[0], 1, img_rows, img_cols) x_test = x_test.reshape(x_test.shape[0], 1, img_rows, img_cols) input_shape = (1, img_rows, img_cols) else: x_train = x_train.reshape(x_train.shape[0], img_rows, img_cols, 1) x_test = x_test.reshape(x_test.shape[0], img_rows, img_cols, 1) input_shape = (img_rows, img_cols, 1)
ここで、引数の与え方に関して、注意点があります。それを確認するのに、以下のjsonファイルを見てみます。
$HOME/.keras/keras.json
私の環境では、中身は以下のようになっています。
{ "image_data_format": "channels_last", "epsilon": 1e-07, "floatx": "float32", "backend": "tensorflow" }
このindexの詳細は以下です(Kerasの公式ページより)。
- image_data_format: 文字列,"channels_last" か "channels_first" のいずれか.Kerasが従うデータのフォーマット規則を指定します. (keras.backend.image_data_format() がこれを返します.)
- 2次元データ (例えば画像) に対しては, "channels_last" は (rows, cols, channels) とみなし,"channels_first" は (channels, rows, cols)とみなします.
- 3次元データに対しては, "channels_last" は (conv_dim1, conv_dim2, conv_dim3, channels) とみなし, "channels_first" は (channels, conv_dim1, conv_dim2,conv_dim3) とみなします.
- epsilon: float,いくつかの操作で0除算を避けるために使う微小量定数.floatx: 文字列,"float16","float32",か "float64".デフォルトの浮動小数点精度.
- backend: 文字列,"tensorflow" か "theano" か "cntk".
この説明から、画像データの場合、"image_data_format"が"channels_first"か"channels_last"かによって指定する引数の順序が異なります。今回は"channels_last"だったので、先ほどのコードの後者の加工がされるようになっています。
最後に、入力データの正規化とラベルの処理を行っておきます。
# 入力データ x_train = x_train.astype('float32') x_test = x_test.astype('float32') x_train /= 255 x_test /= 255 # ラベルはone-hot encodingを施す y_train = y_train.astype('int32') y_test = y_test.astype('int32') y_train = keras.utils.np_utils.to_categorical(y_train, num_classes) y_test = keras.utils.np_utils.to_categorical(y_test, num_classes)
ネットワーク構築
Kerasのexampleに従い、2回畳み込みをしてPoolingを行うようなネットワークを組んでみます。
# 必要なパッケージのインポート from keras.models import Sequential from keras.layers import Dense, Dropout, Flatten from keras.layers import Conv2D, MaxPooling2D model = Sequential() model.add(Conv2D(32, kernel_size=(3, 3), activation='relu', input_shape=input_shape)) model.add(Conv2D(64, (3, 3), activation='relu')) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Dropout(0.25)) model.add(Flatten()) model.add(Dense(128, activation='relu')) model.add(Dropout(0.5)) model.add(Dense(num_classes, activation='softmax'))
2次元画像データに対する畳み込みを行うということで、Conv2D()を使用します。kernel_sizeはフィルタリングの際に着目するピクセルのサイズになっています。プーリングに関しては、MaxPooling2D()を使用しています。pool_sizeで最大値を取る領域を指定しています。
この畳み込みとプーリングですが、明示的にオプションとして指定していないものがいくつかあります。つまり、Kerasではデフォルト設定でよしなにやってくれている操作がいくつかあります。このあたりはTensorflowを使用している人が見るととても気になる点のようです*1。私は、このあたりはモデルを作りこんでいく際に細かく見ていけばよい点かなと思っており、まずは見よう見まねでもネットワークを組んで試せるようになることが大事だと思っておりますので、細かいオプションについてはここでは述べないことにします。
ネットワークを組んだら、あとは前回までと同様にモデルをコンパイルして学習させればOKです。
model.compile(loss=keras.losses.categorical_crossentropy, optimizer=keras.optimizers.Adadelta(), metrics=['accuracy']) history = model.fit(x_train, y_train, batch_size=batch_size, epochs=epochs, verbose=1, validation_data=(x_test, y_test)) score = model.evaluate(x_test, y_test, verbose=0) print('Test loss:', score[0]) print('Test accuracy:', score[1])
Train on 60000 samples, validate on 10000 samples Epoch 1/20 60000/60000 [==============================] - 60s - loss: 0.3299 - acc: 0.9012 - val_loss: 0.0805 - val_acc: 0.9744 Epoch 2/20 60000/60000 [==============================] - 60s - loss: 0.1168 - acc: 0.9653 - val_loss: 0.0548 - val_acc: 0.9819 Epoch 3/20 60000/60000 [==============================] - 60s - loss: 0.0889 - acc: 0.9730 - val_loss: 0.0445 - val_acc: 0.9853 Epoch 4/20 60000/60000 [==============================] - 60s - loss: 0.0732 - acc: 0.9785 - val_loss: 0.0382 - val_acc: 0.9870 Epoch 5/20 60000/60000 [==============================] - 60s - loss: 0.0617 - acc: 0.9816 - val_loss: 0.0343 - val_acc: 0.9884 Epoch 6/20 60000/60000 [==============================] - 60s - loss: 0.0553 - acc: 0.9831 - val_loss: 0.0315 - val_acc: 0.9890 Epoch 7/20 60000/60000 [==============================] - 60s - loss: 0.0515 - acc: 0.9850 - val_loss: 0.0301 - val_acc: 0.9899 Epoch 8/20 60000/60000 [==============================] - 60s - loss: 0.0477 - acc: 0.9856 - val_loss: 0.0310 - val_acc: 0.9894 Epoch 9/20 60000/60000 [==============================] - 60s - loss: 0.0437 - acc: 0.9869 - val_loss: 0.0289 - val_acc: 0.9900 Epoch 10/20 60000/60000 [==============================] - 60s - loss: 0.0417 - acc: 0.9874 - val_loss: 0.0261 - val_acc: 0.9917 Epoch 11/20 60000/60000 [==============================] - 60s - loss: 0.0376 - acc: 0.9888 - val_loss: 0.0271 - val_acc: 0.9913 Epoch 12/20 60000/60000 [==============================] - 60s - loss: 0.0382 - acc: 0.9882 - val_loss: 0.0274 - val_acc: 0.9914 Epoch 13/20 60000/60000 [==============================] - 60s - loss: 0.0364 - acc: 0.9892 - val_loss: 0.0253 - val_acc: 0.9918 Epoch 14/20 60000/60000 [==============================] - 60s - loss: 0.0343 - acc: 0.9898 - val_loss: 0.0266 - val_acc: 0.9914 Epoch 15/20 60000/60000 [==============================] - 60s - loss: 0.0346 - acc: 0.9897 - val_loss: 0.0262 - val_acc: 0.9915 Epoch 16/20 60000/60000 [==============================] - 60s - loss: 0.0319 - acc: 0.9905 - val_loss: 0.0274 - val_acc: 0.9916 Epoch 17/20 60000/60000 [==============================] - 60s - loss: 0.0330 - acc: 0.9898 - val_loss: 0.0263 - val_acc: 0.9918 Epoch 18/20 60000/60000 [==============================] - 60s - loss: 0.0320 - acc: 0.9903 - val_loss: 0.0284 - val_acc: 0.9906 Epoch 19/20 60000/60000 [==============================] - 60s - loss: 0.0312 - acc: 0.9907 - val_loss: 0.0285 - val_acc: 0.9915 Epoch 20/20 60000/60000 [==============================] - 60s - loss: 0.0309 - acc: 0.9905 - val_loss: 0.0268 - val_acc: 0.9906 Test loss: 0.0267664063053 Test accuracy: 0.9906
テストデータで約99%のAccuracyをたたき出しました。前回組んだネットワークよりも精度が上がっています。
epochごとにAccuracyとLossがどう変化していくかも確認しておきましょう。
#Accuracy plt.plot(history.history['acc']) plt.plot(history.history['val_acc']) plt.title('model accuracy') plt.ylabel('accuracy') plt.xlabel('epoch') plt.legend(['train', 'test'], loc='upper left') plt.show() #loss plt.plot(history.history['loss']) plt.plot(history.history['val_loss']) plt.title('model loss') plt.ylabel('loss') plt.xlabel('epoch') plt.legend(['train', 'test'], loc='upper left') plt.show()
train, validationともにかなり良い性能を持っていそうな振舞いをしています。Early stoppingとか気にしなくていっか、と思ってしまいました(笑)
まとめ
駆け足でしたが、今回はMNISTという画像データに対してCNNを構築して精度を見てみました。Kerasのexampleをまねただけですが、後はこれをベースにいろいろと試行錯誤していけばよいのかな、という感じです。
画像データに関する手法ですが、ベーシックな話はいったんここまでにします。次回以降は未定ですが、時系列に特化したネットワークの話や、画像に関して応用的な話などを候補として考えています。
ヒアリチェッカーを作ってみた:とにかく出してみる
(注)中身はまだ非公開
導入
最近、日本に危機が迫っています。
そう、ヒアリです。
ヒアリは在来種と違った繁殖方法を持ち、毒性もあるので、あれよあれよという間に我々の周りに広がっていく可能性もあります。その場合、ヒアリをきちんと見分けられないといつ痛い目にあってもおかしくありません。
備えたい。
というわけでヒアリチェッカーを作ろうと思いました。
類似物
作ろうと決意したものの、ヒアリの画像を検索して
- どれがヒアリだ~
- 画像集まんないな~
- 手間かかるな~
などとうだうだしてたら以下が先にリリースされてしまいました。
alfredplpl.softether.net
くやしい。こっちはまだアプリ化してないよ。
というわけで、いろいろ精緻化する前に、現時点でできているもので判別させてみたものを出してしまおうと思いました。
ヒアリチェッカーの中身について
中身については、いったん非公開としておきます。非公開とするほど素晴らしいものではないんですが、カイシャーで使うかもしれないので。
画像判別例
先ほどのヒアリ画像を作ったヒアリチェッカーに入力した結果は以下になります。
(この画像から、まだJupyterに書きおこしたものしかないのがバレバレ(笑))
とりあえず、簡易的にある閾値を超えたらヒアリとするようにしていますが、このヒアリはヒアリと判別されたようです。
別のアリ(種類は不明)も入力してみます。
シロアリ(っぽいアリ)の精度は若干落ちますが、ヒアリでないアリはヒアリでないと判別されています*1。間違えやすいアリについてはまだ試していませんので、性能としては未知数ですが、わかりやすいものは判別できるのではないでしょうか。
*1:実は、一番最初のプロトタイプはシロアリがうまく判別できていませんでした。少し工夫してマシにしました。
KerasでDeep Learning:KerasでMNISTデータを扱ってみる
導入
前回は人工データを用いたネットワーク構築について紹介しました。
tekenuko.hatenablog.com
今回は、異なるデータ(MNIST)に対してモデルを作成してみます。
MNIST
MNISTとは、「Mixed National Institute of Standards and Technology database」の略で、手書きの数字(0~9)に正解ラベルが与えられているデータセットです。
MNIST handwritten digit database, Yann LeCun, Corinna Cortes and Chris Burges
無料で入手かつ手軽に分析できるデータセットなので、機械学習(特に画像処理系)ではよく用いられます。
手書き文字は28ピクセル×28ピクセルの画像で与えられており、データ数は学習:テスト = 60000枚 : 10000枚です。
Kerasでは、MNISTをダウンロードするメソッドが実装されているので、それを利用して中身を少し覗いてみます。
# 必要なライブラリのインポート import keras from keras.datasets import mnist # Jupyter notebookを利用している際に、notebook内にplot結果を表示するようにする import matplotlib.pyplot as plt %matplotlib inline #Kerasの関数でデータの読み込み。データをシャッフルして学習データと訓練データに分割 (x_train, y_train), (x_test, y_test) = mnist.load_data() #MNISTデータの表示 fig = plt.figure(figsize=(9, 9)) fig.subplots_adjust(left=0, right=1, bottom=0, top=0.5, hspace=0.05, wspace=0.05) for i in range(81): ax = fig.add_subplot(9, 9, i + 1, xticks=[], yticks=[]) ax.imshow(x_train[i].reshape((28, 28)), cmap='gray')
なんというか、温かみのある文字たちが出現します。
データ加工
次に、MNISTデータを加工します。ここでは、28ピクセル×28ピクセルのデータを28×28=784次元のベクトルに変換し、ベクトルの成分を0~1の範囲に正規化します。
# 2次元データを数値に変換 x_train = x_train.reshape(60000, 784) x_test = x_test.reshape(10000, 784) # 型変換 x_train = x_train.astype('float32') x_test = x_test.astype('float32') # 255で割ったものを新たに変数とする x_train /= 255 x_test /= 255
Deep Learningによる画像データの解析では、畳み込みニューラルネットワーク(Convolutional Neural Network : CNN)がよく知られており、その場合は28ピクセル×28ピクセルのデータを行列として入力します。今回は、よりプリミティブなネットワークを作る予定なので、ベクトルデータに変換しています。
また、ベクトルの成分を0~1に変換していますが、これは必須かどうかちょっと判断に迷う操作です。正規化をしないで学習もしてみたのですが、今回は精度が全然出ない結果になりました。しかしながら、別のデータセットや(Kerasのバックエンドで動いているTensorflowをはじめとした)別フレームワークでは必ずしも精度が出ないというわけでもなかったからです。この挙動に関しては、まだ要因を分解できていないため、ここでは深く立ち入らず、Kerasのチュートリアルの通りにデータを正規化して話を進めます。
次に、目的変数(0~9の10種類のラベル)の加工を行います。Kerasでは、0と1が成分となっている配列でラベルを表示する必要があります。そのため、加工に必要なライブラリをインポートし、処理をします。
# one-hot encodingを施すためのメソッド from keras.utils.np_utils import to_categorical # クラス数は10 num_classes = 10 y_train = y_train.astype('int32') y_test = y_test.astype('int32') # one-hot encoding y_train = to_categorical(y_train, num_classes) y_test = to_categorical(y_test, num_classes)
ここで、one-hot encodingは以下のような変換を数値に対して施しています。
0 → [0,0,0,0,0,0,0,0,0,0] 1 → [0,1,0,0,0,0,0,0,0,0] 2 → [0,0,1,0,0,0,0,0,0,0] 3 → [0,0,0,1,0,0,0,0,0,0] ... 9 → [0,0,0,0,0,0,0,0,0,1]
また、num_classes = 10と明示的に指定しておくのが安全です。なぜなら、学習データとテストデータをランダムに分割しているので、分け方によってはどちらかのデータセットのクラス数が10にならない可能性があるためです。
これでデータの準備は完了です。
ネットワーク構築
前回紹介した手法の一つ、Sequentialモデルで作成します。
# 必要なライブラリのインポート、最適化手法はAdamを使う from keras.models import Sequential from keras.layers import Dense, Dropout from keras.optimizers import Adam # モデル作成 model = Sequential() model.add(Dense(512, activation='relu', input_shape=(784,))) model.add(Dropout(0.2)) model.add(Dense(512, activation='relu')) model.add(Dropout(0.2)) model.add(Dense(10, activation='softmax'))
基本は全結合で活性化関数はReLUですが、過学習防止のため、Dropoutという操作もしています。これは、学習の最中にネットワークのノードをランダムに削ることで、過学習が進みすぎるのを防止するような仕組みです。0.2という引数は20%削ることを意味しています。
model.summary()でサマリを見てみます。
model.summary()
Layer (type) Output Shape Param # ================================================================= dense_1 (Dense) (None, 512) 401920 _________________________________________________________________ dropout_1 (Dropout) (None, 512) 0 _________________________________________________________________ dense_2 (Dense) (None, 512) 262656 _________________________________________________________________ dropout_2 (Dropout) (None, 512) 0 _________________________________________________________________ dense_3 (Dense) (None, 10) 5130 ================================================================= Total params: 669,706 Trainable params: 669,706 Non-trainable params: 0 _________________________________________________________________
上からネットワークの種類やノード数、パラメータ数などが順繰り表示されます。わかりやすい。
別の可視化
以下のように書いても、ネットワークの情報を表示できます。ただし、keras.utils.vis_utils を使用するためにはgraphvizというソフトウェアが別途必要なため、Macユーザは例えばbrew installで、Linux(Ubuntuとか)ユーザはapt-get installであらかじめ入れておいてください。
from IPython.display import SVG from keras.utils.vis_utils import model_to_dot SVG(model_to_dot(model).create(prog='dot', format='svg'))
学習、テスト
実際に学習をしてみましょう。学習結果はhistoryという変数で残しておきます。model.evaluate()でテストデータに対してAccuracy(正答率)を計算し、それも別途出力します。
# バッチサイズ、エポック数 batch_size = 128 epochs = 20 model.compile(loss='categorical_crossentropy', optimizer=Adam(), metrics=['accuracy']) history = model.fit(x_train, y_train, batch_size=batch_size, epochs=epochs, verbose=1, validation_data=(x_test, y_test)) score = model.evaluate(x_test, y_test, verbose=0) print('Test loss:', score[0]) print('Test accuracy:', score[1])
Train on 60000 samples, validate on 10000 samples Epoch 1/20 60000/60000 [==============================] - 7s - loss: 0.2521 - acc: 0.9244 - val_loss: 0.1085 - val_acc: 0.9652 Epoch 2/20 60000/60000 [==============================] - 7s - loss: 0.1044 - acc: 0.9680 - val_loss: 0.0889 - val_acc: 0.9707 Epoch 3/20 60000/60000 [==============================] - 7s - loss: 0.0714 - acc: 0.9777 - val_loss: 0.0621 - val_acc: 0.9811 Epoch 4/20 60000/60000 [==============================] - 7s - loss: 0.0562 - acc: 0.9822 - val_loss: 0.0752 - val_acc: 0.9773 Epoch 5/20 60000/60000 [==============================] - 7s - loss: 0.0457 - acc: 0.9848 - val_loss: 0.0627 - val_acc: 0.9817 Epoch 6/20 60000/60000 [==============================] - 7s - loss: 0.0384 - acc: 0.9872 - val_loss: 0.0602 - val_acc: 0.9828 Epoch 7/20 60000/60000 [==============================] - 7s - loss: 0.0326 - acc: 0.9892 - val_loss: 0.0661 - val_acc: 0.9820 Epoch 8/20 60000/60000 [==============================] - 7s - loss: 0.0304 - acc: 0.9894 - val_loss: 0.0658 - val_acc: 0.9816 Epoch 9/20 60000/60000 [==============================] - 7s - loss: 0.0267 - acc: 0.9914 - val_loss: 0.0691 - val_acc: 0.9820 Epoch 10/20 60000/60000 [==============================] - 7s - loss: 0.0276 - acc: 0.9905 - val_loss: 0.0719 - val_acc: 0.9796 Epoch 11/20 60000/60000 [==============================] - 7s - loss: 0.0218 - acc: 0.9925 - val_loss: 0.0698 - val_acc: 0.9825 Epoch 12/20 60000/60000 [==============================] - 7s - loss: 0.0232 - acc: 0.9920 - val_loss: 0.0841 - val_acc: 0.9786 Epoch 13/20 60000/60000 [==============================] - 7s - loss: 0.0206 - acc: 0.9929 - val_loss: 0.0979 - val_acc: 0.9790 Epoch 14/20 60000/60000 [==============================] - 7s - loss: 0.0211 - acc: 0.9930 - val_loss: 0.0775 - val_acc: 0.9803 Epoch 15/20 60000/60000 [==============================] - 7s - loss: 0.0189 - acc: 0.9938 - val_loss: 0.0726 - val_acc: 0.9832 Epoch 16/20 60000/60000 [==============================] - 7s - loss: 0.0174 - acc: 0.9941 - val_loss: 0.0820 - val_acc: 0.9823 Epoch 17/20 60000/60000 [==============================] - 7s - loss: 0.0159 - acc: 0.9944 - val_loss: 0.0819 - val_acc: 0.9820 Epoch 18/20 60000/60000 [==============================] - 7s - loss: 0.0161 - acc: 0.9946 - val_loss: 0.0776 - val_acc: 0.9831 Epoch 19/20 60000/60000 [==============================] - 7s - loss: 0.0195 - acc: 0.9935 - val_loss: 0.0818 - val_acc: 0.9819 Epoch 20/20 60000/60000 [==============================] - 7s - loss: 0.0141 - acc: 0.9952 - val_loss: 0.0751 - val_acc: 0.9826 Test loss: 0.0751098209593 Test accuracy: 0.9826
テストデータのAccuracyが98%を超えるんですか。そんなに凝ったネットワークでもないのにすごいですね。
可視化
Accuracyとloss functionをepoch数でプロットしてみましょう。
#Accuracy plt.plot(history.history['acc']) plt.plot(history.history['val_acc']) plt.title('model accuracy') plt.ylabel('accuracy') plt.xlabel('epoch') plt.legend(['train', 'test'], loc='upper left') plt.show() #loss plt.plot(history.history['loss']) plt.plot(history.history['val_loss']) plt.title('model loss') plt.ylabel('loss') plt.xlabel('epoch') plt.legend(['train', 'test'], loc='upper left') plt.show()
学習データに関しては、学習を進めると精度がどんどん良くなっている様が見えます。一方で、テストデータに関しては、(十分良いのですが)あるepochからloss functionが微妙に増加するような振舞いをしています。過学習(今あるデータに特化しすぎて未知のデータに対する性能が落ちる)の傾向が見えているようです。
Early stopping
過学習を防ぐ方法の一つとして、予測性能が下がる前に学習を打ち切る、というものがあります。こういった方法をEarly stoppingといいます。検証用データ(validation data)を学習データの中からいくらか選び、学習した結果をその検証用データに適用し、精度やloss functionが劣化したら学習を打ち切ります(すぐに打ち切るか少し様子見をするかはオプションで指定できます)。
実際にコードを動かしてみます。
# 必要なライブラリのインポート from keras.callbacks import EarlyStopping # モデルの訓練 history_ES = model.fit(x_train, y_train, batch_size=batch_size, epochs=epochs, verbose=1, validation_split=0.1, callbacks=[early_stopping])
今回は、以下のように途中で学習が止まります。
Train on 54000 samples, validate on 6000 samples Epoch 1/20 54000/54000 [==============================] - 5s - loss: 0.0073 - acc: 0.9975 - val_loss: 0.0080 - val_acc: 0.9980 Epoch 2/20 54000/54000 [==============================] - 5s - loss: 0.0078 - acc: 0.9978 - val_loss: 0.0264 - val_acc: 0.9938 Epoch 00001: early stopping
Accuracyとloss functionをepoch数でプロットすると以下のようになります。
#Accuracy plt.plot(history_ES.history['acc']) plt.plot(history_ES.history['val_acc']) plt.title('model accuracy') plt.ylabel('accuracy') plt.xlabel('epoch') plt.legend(['train', 'test'], loc='upper left') plt.show() #loss plt.plot(history_ES.history['loss']) plt.plot(history_ES.history['val_loss']) plt.title('model loss') plt.ylabel('loss') plt.xlabel('epoch') plt.legend(['train', 'test'], loc='upper left') plt.show()
まとめとNext Step
今回はMNISTという画像データに対してネットワーク構築をしてみました。あまり複雑なネットワークでないにも関わらず、高い性能をもつものができたのでした。
次回は、より画像データの処理に強みを持ったネットワークの構築を目指していきます。やっとCNNの出番ですね。