今更ながらDropoutを検証してみる~その2~

TL;DR

  • dropoutを入れてmnistで99.66%を達成した

dropoutの本気を見た

前回の記事でdropoutは安定しただけ、と書いたが、 もうちょい試すと99.66%を達成したのでこちらにて。

modelというかソース

このようにPReLUで活性化したレイヤの後全てにDropout(0.1)を入れた。

from keras import datasets
import numpy as np
((train_x,train_y),(test_x,test_y)) = datasets.mnist.load_data()
train_x = (train_x/255).astype("float32").reshape(-1,28,28,1)
test_x  = (test_x /255).astype("float32").reshape(-1,28,28,1)
train_y = np.eye(10)[train_y]
test_y = np.eye(10)[test_y]

from tensorflow.keras.layers import *
from tensorflow.keras.models import *
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import ModelCheckpoint
from tensorflow.keras.losses import categorical_crossentropy

prelu_model_drop01 = Sequential()
prelu_model_drop01.add(Conv2D(filters=32,kernel_size=(3,3),input_shape=(28,28,1),padding="same"))
prelu_model_drop01.add(PReLU())
prelu_model_drop01.add(Dropout(0.1))
prelu_model_drop01.add(BatchNormalization())
prelu_model_drop01.add(Conv2D(filters=32,kernel_size=(3,3),padding="same"))
prelu_model_drop01.add(PReLU())
prelu_model_drop01.add(Dropout(0.1))
prelu_model_drop01.add(BatchNormalization())
prelu_model_drop01.add(MaxPool2D(pool_size=(2,2),padding="same")) # 28,28 -> 14,14
prelu_model_drop01.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
prelu_model_drop01.add(PReLU())
prelu_model_drop01.add(Dropout(0.1))
prelu_model_drop01.add(BatchNormalization())
prelu_model_drop01.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
prelu_model_drop01.add(PReLU())
prelu_model_drop01.add(Dropout(0.1))
prelu_model_drop01.add(BatchNormalization())
prelu_model_drop01.add(MaxPool2D(pool_size=(2,2),padding="same")) # 14,14 -> 7,7
prelu_model_drop01.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
prelu_model_drop01.add(PReLU())
prelu_model_drop01.add(Dropout(0.1))
prelu_model_drop01.add(BatchNormalization())
prelu_model_drop01.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
prelu_model_drop01.add(PReLU())
prelu_model_drop01.add(Dropout(0.1))
prelu_model_drop01.add(BatchNormalization())
prelu_model_drop01.add(MaxPool2D(pool_size=(2,2),padding="same")) # 7,7 -> 4,4
prelu_model_drop01.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
prelu_model_drop01.add(PReLU())
prelu_model_drop01.add(Dropout(0.1))
prelu_model_drop01.add(BatchNormalization())
prelu_model_drop01.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
prelu_model_drop01.add(PReLU())
prelu_model_drop01.add(Dropout(0.1))
prelu_model_drop01.add(BatchNormalization())
prelu_model_drop01.add(MaxPool2D(pool_size=(2,2),padding="same")) # 4,4 -> 2,2
prelu_model_drop01.add(Conv2D(filters=128,kernel_size=(2,2),padding="same"))
prelu_model_drop01.add(PReLU())
prelu_model_drop01.add(Dropout(0.1))
prelu_model_drop01.add(BatchNormalization())
prelu_model_drop01.add(MaxPool2D(pool_size=(2,2),padding="same")) # 2,2 -> 1,1
prelu_model_drop01.add(Flatten())
prelu_model_drop01.add(Dense(10,activation="softmax"))
prelu_model_drop01.summary()

prelu_model_drop01.compile(optimizer=Adam(lr=0.0001),metrics=['accuracy'],loss="categorical_crossentropy")

prelu_model_drop01_history = prelu_model_drop01.fit(train_x,train_y,batch_size=16,epochs=64,validation_data=(test_x,test_y))

結果

32 epochで99.66%を達成しました。

f:id:kazuhitogo:20190906175130p:plain
drop01

また引き続き安定しているように見えます。

結論

弱いdropoutを活性化層の後に入れまくるべし。(ホントか?)

今回もふざけた広告を

今更ながらDropoutを検証してみる

TL;DR;

  • dropoutで適切なパラメータを振ると学習が安定するよ。

  • val_accの最大値は更新しませんでした。

Dropoutの効果は本当にあるのか

今までいろんなモデル(つっても画像の異常判定とか、U-NET、オートエンコーダーくらいだけど)を組んできたが、

Dropoutがあんまり機能した記憶がないので、ホンマかいな、というのを試してみた。

みなさん大好きmnistで。

(というより誰でもいつでも使えるものになるとmnistに)

検証モデル

まずは下記投稿で最高結果を出したモデルで。

kazuhitogo.hateblo.jp

f:id:kazuhitogo:20190905103651p:plain
prelu_model

from keras import datasets
import numpy as np
((train_x,train_y),(test_x,test_y)) = datasets.mnist.load_data()
train_x = (train_x/255).astype("float32").reshape(-1,28,28,1)
test_x  = (test_x /255).astype("float32").reshape(-1,28,28,1)
train_y = np.eye(10)[train_y]
test_y = np.eye(10)[test_y]

from tensorflow.keras.layers import *
from tensorflow.keras.models import *
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import ModelCheckpoint
from tensorflow.keras.losses import categorical_crossentropy

prelu_model = Sequential()
prelu_model.add(Conv2D(filters=32,kernel_size=(3,3),input_shape=(28,28,1),padding="same"))
prelu_model.add(PReLU())
prelu_model.add(BatchNormalization())
prelu_model.add(Conv2D(filters=32,kernel_size=(3,3),padding="same"))
prelu_model.add(MaxPool2D(pool_size=(2,2),padding="same")) # 28,28 -> 14,14
prelu_model.add(PReLU())
prelu_model.add(BatchNormalization())
prelu_model.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
prelu_model.add(PReLU())
prelu_model.add(BatchNormalization())
prelu_model.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
prelu_model.add(PReLU())
prelu_model.add(MaxPool2D(pool_size=(2,2),padding="same")) # 14,14 -> 7,7
prelu_model.add(BatchNormalization())
prelu_model.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
prelu_model.add(PReLU())
prelu_model.add(BatchNormalization())
prelu_model.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
prelu_model.add(PReLU())
prelu_model.add(MaxPool2D(pool_size=(2,2),padding="same")) # 7,7 -> 4,4
prelu_model.add(BatchNormalization())
prelu_model.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
prelu_model.add(PReLU())
prelu_model.add(BatchNormalization())
prelu_model.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
prelu_model.add(PReLU())
prelu_model.add(MaxPool2D(pool_size=(2,2),padding="same")) # 4,4 -> 2,2
prelu_model.add(BatchNormalization())
prelu_model.add(Conv2D(filters=128,kernel_size=(2,2),padding="same"))
prelu_model.add(PReLU())
prelu_model.add(BatchNormalization())
prelu_model.add(Conv2D(filters=128,kernel_size=(2,2),padding="same"))
prelu_model.add(PReLU())
prelu_model.add(MaxPool2D(pool_size=(2,2),padding="same")) # 2,2 -> 1,1
prelu_model.add(BatchNormalization())
prelu_model.add(Flatten())
prelu_model.add(Dense(10,activation="softmax"))
prelu_model.summary()
prelu_model.compile(optimizer=Adam(lr=0.0001),metrics=['accuracy'],loss="categorical_crossentropy")
prelu_model_history = prelu_model.fit(train_x,train_y,batch_size=16,epochs=64,validation_data=(test_x,test_y))

このモデルに対して各アクティベーション層後にDropoutを設定する。

この記事によると、

deepage.net

それではDropoutを適用していこう。 まずは、Hinton氏の提案通り入力層は0.2、隠れ層は0.5にしておく。 TFLearnのdropout関数は1-dropout率を指定する。

ヒントン氏が入力層0.2,隠れ層は0.5と言っているので、それに習い、そのように設定。

ただ、なんとなく出力層直前は0.2にしてみました。

ヒントン氏に敗れた人はなんというか知りませんが…

togetter.com

f:id:kazuhitogo:20190905104359p:plain
prelu_model_drop05

prelu_model_drop05 = Sequential()
prelu_model_drop05.add(Conv2D(filters=32,kernel_size=(3,3),input_shape=(28,28,1),padding="same"))
prelu_model_drop05.add(PReLU())
prelu_model_drop05.add(Dropout(0.2))
prelu_model_drop05.add(BatchNormalization())
prelu_model_drop05.add(Conv2D(filters=32,kernel_size=(3,3),padding="same"))
prelu_model_drop05.add(Dropout(0.5))
prelu_model_drop05.add(MaxPool2D(pool_size=(2,2),padding="same")) # 28,28 -> 14,14
prelu_model_drop05.add(PReLU())
prelu_model_drop05.add(Dropout(0.5))
prelu_model_drop05.add(BatchNormalization())
prelu_model_drop05.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
prelu_model_drop05.add(PReLU())
prelu_model_drop05.add(Dropout(0.5))
prelu_model_drop05.add(BatchNormalization())
prelu_model_drop05.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
prelu_model_drop05.add(PReLU())
prelu_model_drop05.add(Dropout(0.5))
prelu_model_drop05.add(MaxPool2D(pool_size=(2,2),padding="same")) # 14,14 -> 7,7
prelu_model_drop05.add(BatchNormalization())
prelu_model_drop05.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
prelu_model_drop05.add(PReLU())
prelu_model_drop05.add(Dropout(0.5))
prelu_model_drop05.add(BatchNormalization())
prelu_model_drop05.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
prelu_model_drop05.add(PReLU())
prelu_model_drop05.add(Dropout(0.5))
prelu_model_drop05.add(MaxPool2D(pool_size=(2,2),padding="same")) # 7,7 -> 4,4
prelu_model_drop05.add(BatchNormalization())
prelu_model_drop05.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
prelu_model_drop05.add(PReLU())
prelu_model_drop05.add(Dropout(0.5))
prelu_model_drop05.add(BatchNormalization())
prelu_model_drop05.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
prelu_model_drop05.add(PReLU())
prelu_model_drop05.add(Dropout(0.5))
prelu_model_drop05.add(MaxPool2D(pool_size=(2,2),padding="same")) # 4,4 -> 2,2
prelu_model_drop05.add(BatchNormalization())
prelu_model_drop05.add(Conv2D(filters=128,kernel_size=(2,2),padding="same"))
prelu_model_drop05.add(PReLU())
prelu_model_drop05.add(Dropout(0.5))
prelu_model_drop05.add(BatchNormalization())
prelu_model_drop05.add(Conv2D(filters=128,kernel_size=(2,2),padding="same"))
prelu_model_drop05.add(PReLU())
prelu_model_drop05.add(Dropout(0.2))
prelu_model_drop05.add(MaxPool2D(pool_size=(2,2),padding="same")) # 2,2 -> 1,1
prelu_model_drop05.add(BatchNormalization())
prelu_model_drop05.add(Flatten())
prelu_model_drop05.add(Dense(10,activation="softmax"))
prelu_model_drop05.summary()

結果

Dropoutはポンコツでした。

f:id:kazuhitogo:20190905104517p:plain
val_acc

Dropout無しが99.47%のval_accだったのに対し(あれ、99.5%超えてない)

Dropout有りが97.47%が限界でした。

検証2

たぶんDropoutの設計がイケてなかったのではないでしょうか…。

Dropoutレイヤが多すぎたか、パラメータの0.2,0.5が行けなかった気がします。

ヒントン氏を信じてパラメータはそのままに、 レイヤを減らしてみましょう。

で、作成したモデルがこちら。 (dropoutしてないヤツは変わらず)

prelu_model_drop05 = Sequential()
prelu_model_drop05.add(Conv2D(filters=32,kernel_size=(3,3),input_shape=(28,28,1),padding="same"))
prelu_model_drop05.add(PReLU())
prelu_model_drop05.add(Dropout(0.2))
prelu_model_drop05.add(BatchNormalization())
prelu_model_drop05.add(Conv2D(filters=32,kernel_size=(3,3),padding="same"))
prelu_model_drop05.add(Dropout(0.5))
prelu_model_drop05.add(MaxPool2D(pool_size=(2,2),padding="same")) # 28,28 -> 14,14
prelu_model_drop05.add(PReLU())
prelu_model_drop05.add(BatchNormalization())<figure class="figure-image figure-image-fotolife" title="dropout減らし">[f:id:kazuhitogo:20190906102602p:plain]<figcaption>dropout減らし</figcaption></figure>
prelu_model_drop05.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
prelu_model_drop05.add(PReLU())
prelu_model_drop05.add(BatchNormalization())
prelu_model_drop05.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
prelu_model_drop05.add(PReLU())
prelu_model_drop05.add(Dropout(0.5))
prelu_model_drop05.add(MaxPool2D(pool_size=(2,2),padding="same")) # 14,14 -> 7,7
prelu_model_drop05.add(BatchNormalization())
prelu_model_drop05.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
prelu_model_drop05.add(PReLU())
prelu_model_drop05.add(BatchNormalization())
prelu_model_drop05.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
prelu_model_drop05.add(PReLU())
prelu_model_drop05.add(Dropout(0.5))
prelu_model_drop05.add(MaxPool2D(pool_size=(2,2),padding="same")) # 7,7 -> 4,4
prelu_model_drop05.add(BatchNormalization())
prelu_model_drop05.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
prelu_model_drop05.add(PReLU())
prelu_model_drop05.add(BatchNormalization())
prelu_model_drop05.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
prelu_model_drop05.add(PReLU())
prelu_model_drop05.add(Dropout(0.5))
prelu_model_drop05.add(MaxPool2D(pool_size=(2,2),padding="same")) # 4,4 -> 2,2
prelu_model_drop05.add(BatchNormalization())
prelu_model_drop05.add(PReLU())
prelu_model_drop05.add(BatchNormalization())
prelu_model_drop05.add(Conv2D(filters=128,kernel_size=(2,2),padding="same"))
prelu_model_drop05.add(PReLU())
prelu_model_drop05.add(Dropout(0.2))
prelu_model_drop05.add(MaxPool2D(pool_size=(2,2),padding="same")) # 2,2 -> 1,1
prelu_model_drop05.add(BatchNormalization())
prelu_model_drop05.add(Flatten())
prelu_model_drop05.add(Dense(10,activation="softmax"))
prelu_model_drop05.summary()

結果2

f:id:kazuhitogo:20190906102602p:plain

うーん、いまいちすぎる。 が、dropout無し版がepoch 61にしてval_acc99.6%超えましたね。笑

検証3

ヒントン教授を疑うことにして、 dropoutを全て(0.2)で検証します。

prelu_model_drop02 = Sequential()
prelu_model_drop02.add(Conv2D(filters=32,kernel_size=(3,3),input_shape=(28,28,1),padding="same"))
prelu_model_drop02.add(PReLU())
prelu_model_drop02.add(Dropout(0.2))
prelu_model_drop02.add(BatchNormalization())
prelu_model_drop02.add(Conv2D(filters=32,kernel_size=(3,3),padding="same"))
prelu_model_drop02.add(Dropout(0.2))
prelu_model_drop02.add(MaxPool2D(pool_size=(2,2),padding="same")) # 28,28 -> 14,14
prelu_model_drop02.add(PReLU())
prelu_model_drop02.add(BatchNormalization())
prelu_model_drop02.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
prelu_model_drop02.add(PReLU())
prelu_model_drop02.add(BatchNormalization())
prelu_model_drop02.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
prelu_model_drop02.add(PReLU())
prelu_model_drop02.add(Dropout(0.2))
prelu_model_drop02.add(MaxPool2D(pool_size=(2,2),padding="same")) # 14,14 -> 7,7
prelu_model_drop02.add(BatchNormalization())
prelu_model_drop02.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
prelu_model_drop02.add(PReLU())
prelu_model_drop02.add(BatchNormalization())
prelu_model_drop02.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
prelu_model_drop02.add(PReLU())
prelu_model_drop02.add(Dropout(0.2))
prelu_model_drop02.add(MaxPool2D(pool_size=(2,2),padding="same")) # 7,7 -> 4,4
prelu_model_drop02.add(BatchNormalization())
prelu_model_drop02.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
prelu_model_drop02.add(PReLU())
prelu_model_drop02.add(BatchNormalization())
prelu_model_drop02.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
prelu_model_drop02.add(PReLU())
prelu_model_drop02.add(Dropout(0.2))
prelu_model_drop02.add(MaxPool2D(pool_size=(2,2),padding="same")) # 4,4 -> 2,2
prelu_model_drop02.add(BatchNormalization())
prelu_model_drop02.add(PReLU())
prelu_model_drop02.add(BatchNormalization())
prelu_model_drop02.add(Conv2D(filters=128,kernel_size=(2,2),padding="same"))
prelu_model_drop02.add(PReLU())
prelu_model_drop02.add(Dropout(0.2))
prelu_model_drop02.add(MaxPool2D(pool_size=(2,2),padding="same")) # 2,2 -> 1,1
prelu_model_drop02.add(BatchNormalization())
prelu_model_drop02.add(Flatten())
prelu_model_drop02.add(Dense(10,activation="softmax"))
prelu_model_drop02.summary()

prelu_model_drop02.compile(optimizer=Adam(lr=0.0001),metrics=['accuracy'],loss="categorical_crossentropy")
prelu_model_drop02_history = prelu_model_drop05.fit(train_x,train_y,batch_size=16,epochs=64,validation_data=(test_x,test_y))

はてさて。

結果3

かなり改善されました。

f:id:kazuhitogo:20190906135018p:plain
drop0.2

最高のval_accこそ負けるものの(dropout0.2有り99.58%,無し99.6%) 学習がかなり安定しております。

もう少しまんべんなくdropoutしてパラメータが0.1とかだとさらなる改善が期待できそうです。

あとは学習率のスケジュールとかをすると更によいかもしれません。

今回はふざけた広告を笑

ReLUファミリー活性化関数を検証してみる(ReLU,leakyReLU,PReLU)

TL;DR

ReLUファミリー最強の活性化関数は今の所PReLU。

ただしRReLUは未検証。

mnistでモデルのみで99.5%を超えたい

How to score 97%, 98%, 99%, and 100% | Kaggle 上記によると

99.5%: If you design a good CNN architecture and then add special features like pooling layers, data augmentation, dropout, batch normalization, decaying learning rate, advanced optimization; You'll have no problem breaking the landmark 99.5% in only 20 epochs!

(意訳) まともなDLモデラーならaccuracy99.5%超えないとやべーよ!!

とのことなので、DL歴2年になった私が改めてやってみようと思った次第。

ただし、このままじゃ面白くないので多少の縛りを。

  • DataAugmentationを使わない

    つまり今日このブログに載せているソースコードをそのまま実行すれば乱数の問題があるにせよそれっぽく再現できる。

    というよりDLモデラーならオーグメンテーション無しでも実現したいところ。

  • epoch数は48までヨシとする(上記の英文では20 epoch以内との記載)

    乱数ガチャが辛いため

  • dropoutを使わない

    なんとなく気分、というか別途検証予定のため

で、とりあえず、↓のモデル(keras)で、29epoch目で99.5%を超えたわけですが…。

from keras import datasets
import numpy as np
((train_x,train_y),(test_x,test_y)) = datasets.mnist.load_data()
train_x = (train_x/255).astype("float32").reshape(-1,28,28,1)
test_x  = (test_x /255).astype("float32").reshape(-1,28,28,1)
train_y = np.eye(10)[train_y]
test_y = np.eye(10)[test_y]

from tensorflow.keras.layers import *
from tensorflow.keras.models import *
from tensorflow.keras.optimizers import Adam

leaky_model = Sequential()
leaky_model.add(Conv2D(filters=32,kernel_size=(3,3),input_shape=(28,28,1),padding="same"))
leaky_model.add(LeakyReLU(alpha=0.1))
leaky_model.add(BatchNormalization())
leaky_model.add(Conv2D(filters=32,kernel_size=(3,3),padding="same"))
leaky_model.add(MaxPool2D(pool_size=(2,2),padding="same")) # 28,28 -> 14,14
leaky_model.add(LeakyReLU(alpha=0.1))
leaky_model.add(BatchNormalization())
leaky_model.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
leaky_model.add(LeakyReLU(alpha=0.1))
leaky_model.add(BatchNormalization())
leaky_model.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
leaky_model.add(LeakyReLU(alpha=0.1))
leaky_model.add(MaxPool2D(pool_size=(2,2),padding="same")) # 14,14 -> 7,7
leaky_model.add(BatchNormalization())
leaky_model.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
leaky_model.add(LeakyReLU(alpha=0.1))
leaky_model.add(BatchNormalization())
leaky_model.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
leaky_model.add(LeakyReLU(alpha=0.1))
leaky_model.add(MaxPool2D(pool_size=(2,2),padding="same")) # 7,7 -> 4,4
leaky_model.add(BatchNormalization())
leaky_model.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
leaky_model.add(LeakyReLU(alpha=0.1))
leaky_model.add(BatchNormalization())
leaky_model.add(Conv2D(filters=64,kernel_size=(3,3),padding="same"))
leaky_model.add(LeakyReLU(alpha=0.1))
leaky_model.add(MaxPool2D(pool_size=(2,2),padding="same")) # 4,4 -> 2,2
leaky_model.add(BatchNormalization())
leaky_model.add(Conv2D(filters=128,kernel_size=(2,2),padding="same"))
leaky_model.add(LeakyReLU(alpha=0.1))
leaky_model.add(BatchNormalization())
leaky_model.add(Conv2D(filters=128,kernel_size=(2,2),padding="same"))
leaky_model.add(LeakyReLU(alpha=0.1))
leaky_model.add(MaxPool2D(pool_size=(2,2),padding="same")) # 2,2 -> 1,1
leaky_model.add(BatchNormalization())
leaky_model.add(Flatten())
leaky_model.add(Dense(10,activation="softmax"))
leaky_model.summary()

leaky_model.compile(optimizer=Adam(lr=0.0001),metrics=['accuracy'],loss="categorical_crossentropy")
leaky_model_history = leaky_model.fit(train_x,train_y,batch_size=16,epochs=48,validation_data=(test_x,test_y))

活性化関数って本当にこれでいいの?というのが気なったので、

  1. LeakyReLU(0.0) (= ReLU)
  2. LeakyReLU(0.1)
  3. LeakyReLU(0.2)
  4. LeakyReLU(0.3)
  5. PReLU

でどれが一番いいのか、を上記モデルのLeakReLU部分だけを変えて検証してみました。 (他は一切手を加えておりません)

[1505.00853] Empirical Evaluation of Rectified Activations in Convolutional Network

によると、Rondomized ReLU(RReLU)がいいらしいですが、

kerasにまだ実装されていないとのことなので↓

【ReLU, PReLU, シグモイド etc...】ニューラルネットでよく使う活性化関数の効果をKerasで調べてみた - プロクラシスト

今回はこの5通りを試しました。

結果

val_acc

f:id:kazuhitogo:20190904184424p:plain
val_acc

PReLUがepoch 30で瞬間最大風速99.55%を記録しました。

次点でleaky 0.3がepoch 43で99.53%ですね。

ただ、一番悪かったのもreluとleaky0.2で99.45%なのでまぁ誤差といえば誤差な気がします。

もう一回バッチを回すと結果が変わるきがしてならない。

ねんのためval_lossも。

f:id:kazuhitogo:20190904184847p:plain
val_loss

まぁほぼval_accと一緒ですね。

まとめ

PReLUが一番よい。accuracyにして0.1%程度。

有意差はわからない。

とりあえずデータサイエンティストを名乗るための99.5%は超えた。

以上。

現場で使える! TensorFlow開発入門 Kerasによる深層学習モデル構築手法 (AI & TECHNOLOGY)

現場で使える! TensorFlow開発入門 Kerasによる深層学習モデル構築手法 (AI & TECHNOLOGY)

overpassを使ってサンフランシスコの緯度経度半径1km以内にバーガーキングがあるかを探す

どうしようもなくバーガーキングを食べたくなったあなたへ。

サンフランシスコにバーガーキングを目当てに旅行に行く人はなかなか居ないとは思いますが、 そんな人向けに。

実装

import overpass
import urllib

# proxy設定する人
## proxies = {
##     "http":"http://hogehoge",
##     "https":"http://hogehoge"
## }
api = overpass.API()
api = overpass.API(endpoint="https://overpass-api.de/api/interpreter",timeout=600)

## api.proxies=proxies

# サンフランシスコの緯度経度から半径1km以内のfast food
response = api.get('node(around:1000.0,37.77493,-122.419416)["amenity"="fast_food"];out;')
for data in response["features"]:
    if 'name' in data["properties"] and data["properties"]["name"] == "Burger King":
        print(data)

結果

2件ありました。

{"geometry": {"coordinates": [-122.421096, 37.7831831], "type": "Point"}, "id": 432817342, "properties": {"addr:city": "San Francisco", "addr:housenumber": "819", "addr:street": "Van Ness Avenue", "amenity": "fast_food", "name": "Burger King", "source": "survey"}, "type": "Feature"}
{"geometry": {"coordinates": [-122.421096, 37.7831831], "type": "Point"}, "id": 432817342, "properties": {"addr:city": "San Francisco", "addr:housenumber": "819", "addr:street": "Van Ness Avenue", "amenity": "fast_food", "name": "Burger King", "source": "survey"}, "type": "Feature"}

解説

overpassというライブラリがあり、

openstreetmapAPIpythonからコールできるようにしたもの。

python-overpy.readthedocs.io

よくわからないクエリ方式(overpass QL)もしくは、XMLでリクエストを投げると、JSONでデータを返してくれる。

今回はサンフランシスコの緯度経度と1km以内のfastfoodというoverpass QLを投げて、

そのなかのバーガーキングを表示しました。

pythonのheapqが気に入らないので自分で実装しなおす(優先度付きキュー)

優先度付きキュー

って何?

って方はこちらをご参照。

ufcpp.net

こちらの本でお勉強もよろし(わかりやすかった)

アルゴリズム図鑑 絵で見てわかる26のアルゴリズム

アルゴリズム図鑑 絵で見てわかる26のアルゴリズム

ざっくり言うと、あるデータの塊から、

最小(あるいは最大)の値を取り出すために特化したデータ構造を、

ヒープ木とか二分ヒープとか優先度付き待ち行列とか優先度付きキューとかと言う。

ここらへんの表記ゆれはよくわからない。

またデータを追加するときにenqueue(エンキュー)、

最小値を取り出す(そして削除)のをdequeue(デキュー)という。

特徴として、先頭の値を取り出すのがO(1)と高速だし、 追加や削除もO(log n)とそこそこ高速である。

また最後までdequeueし続けるとヒープソートの完成である。

pythonの優先度付きキュー

pythonにはheapqというライブラリが備わっており、

enqueueはheappush,dequeueはheappopで備わっているのだが、

下記事項が気に食わないので作り直した。

  • オブジェクト志向ではない

    事前に配列を自分で宣言し、heappushやheappopに配列を引数で突っ込む必要があり、直感的でない

  • 降順をサポートしない

    優先度は昇順しか使えないので、降順にしたい場合は別途正負反転させる等の工夫が必要

  • heappush,heappopってなんやねん

    enqueue,dequeueのほうがかっこよくない?

ということで実装。

実装

pure pythonで実装。

class heap:
    def __init__(self,order="asc"):
        self._heap = []
        self.order=order
    def get_head(self):
        return self._heap[0] if self.order == "asc" else -self._heap[0]
    def get_heap(self):
        return self._heap if self.order == "asc" else list(map(lambda x:-x,self._heap))
    def enqueue(self,items):
        if type(items) != list:
            items = [items]
        # 降順ソートの場合は正負反転
        if self.order!="asc":
            items = list(map(lambda x:-x,items))
        for item in items:
            self._heap.append(item)
            self_idx = len(self._heap)-1
            parent_idx = self_idx//2
            while self_idx > 0:
                if self._heap[self_idx] <= self._heap[parent_idx]:
                    self._heap[self_idx], self._heap[parent_idx] = self._heap[parent_idx], self._heap[self_idx]
                    self_idx = parent_idx
                    parent_idx = self_idx//2
                else:
                    break
    def dequeue(self):
        head = self._heap[0]
        self._heap[0] = self._heap[-1]
        self._heap.pop(-1)
        self_idx = 0
        child_idx = [1,2]
        while True:
            # 子供が存在しなかったらbreak
            if child_idx[0] > len(self._heap)-1:
                break
            # 左の子供しか存在しない場合
            elif child_idx[0] == len(self._heap)-1:
                # 左の子供のほうが大きい場合
                if self._heap[self_idx] < self._heap[child_idx[0]]:
                    break
                else:
                    self._heap[self_idx],self._heap[child_idx[0]] = self._heap[child_idx[0]],self._heap[self_idx]
                    self_idx = (self_idx*2)+1
                    child_idx = [self_idx*2+1,self_idx*2+2]
            # 両方の子供が存在する場合
            else:
                # 子供のほうが大きい場合
                if self._heap[self_idx] < min([self._heap[child_idx[0]],self._heap[child_idx[1]]]):
                    break
                # 子供のほうが小さい場合
                else:
                    # 左の子供のほうが小さい場合は左の子供と親の値を交換
                    if self._heap[child_idx[0]] <= self._heap[child_idx[1]]:
                        self._heap[self_idx],self._heap[child_idx[0]] = self._heap[child_idx[0]],self._heap[self_idx]
                        self_idx = (self_idx*2)+1
                        child_idx = [self_idx*2+1,self_idx*2+2]
                    # 右の子供のほうが小さい場合は右の子供と親の値を交換
                    else:
                        self._heap[self_idx],self._heap[child_idx[1]] = self._heap[child_idx[1]],self._heap[self_idx]
                        self_idx = (self_idx*2)+2
                        child_idx = [self_idx*2+1,self_idx*2+2]
        return head  if self.order == "asc" else -head

使い方

# heapインスタンス生成
heap = heap()

# [10,9,8,7,6,5,4,3,2,1]というデータを突っ込む
heap.enqueue([10-i for i in range(10)])

# 先頭のデータを取得(参照のみでデータ変化なし)
heap.get_head() # 1

# heap全体の取得(参照のみでデータ変化なし)
heap.get_heap() # [1, 2, 3, 5, 4, 8, 9, 6, 10, 7] ←先頭が最小値であることと、木構造で見たときに「親の値<子供の値」のみ保証

# データを突っ込むその2(先頭に0を突っ込む)
heap.enqueue(0)

# 先頭が書き換わったことの確認
heap.get_head() # 0

# dequeue
print(heap.dequeue()) # 0が返り0は消滅

# 先頭が書き換わる
heap.get_head() # 1

# heapsort
heap_sort_result = []
for i in range(len(heap.get_heap())):
    heap_sort_result.append(heap.dequeue())
print(heap_sort_result) # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

こんな感じで使えます。

同等のことをheapqでやってみる。

import heapq
# heapインスタンス生成相当(ただのリスト宣言)
Q = []
# データを突っ込む
for i in [10-i for i in range(10)]:
    heapq.heappush(Q,i)

# 先頭の値を取得
print(Q[0])

# heap全体の取得
print(Q)

# データを突っ込むその2(先頭に0を突っ込む)
heapq.heappush(Q,0)

# 先頭が書き換わったことの確認
print(Q[0])

# dequeue
heapq.heappop(Q)

# heapsort
heap_sort_result = []
for _ in range(len(Q)):
    heap_sort_result.append(heapq.heappop(Q))
print(heap_sort_result)

毎回リストを引数につっこまなくてよくなりました。

また、降順はこんな感じ。

# インスタンス生成時に降順を指定
heap = heap(order = "desc")

# 昇順にデータを突っ込む
heap.enqueue([i+1 for i in range(10)]) # [1,2,3,4,5,6,7,8,9,10]を格納

#10が先頭に来ている
heap.get_head() 

# heap sort(降順)
heap_sort_result = []
for i in range(len(heap.get_heap())):
    heap_sort_result.append(heap.dequeue())
print(heap_sort_result) # [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

これをheapqでやるとこうなる

import heapq

# インスタンス生成時に降順を指定(という名のただのリスト生成)
Q = []

# 昇順にデータを突っ込む
for i in [i+1 for i in range(10)]:
    heapq.heappush(Q,-i)

#10が先頭に来ている
print(-Q[0])

# heap sort(降順)
heap_sort_result = []
for i in range(len(Q)):
    heap_sort_result.append(heapq.heappop(Q))
heap_sort_result = list(map(lambda x:-x,heap_sort_result))
print(heap_sort_result) # [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

heapqは降順ソートをサポートしていないので、 毎回正負反転が必要で頭がこんがらがってしまう。

なので、こちらをオススメをしたい。

処理速度

とはいえ、処理速度はどうだろう。 1~1,000,000を突っ込んでheap sortしてみる

今回作ったやつ

%%time
heap = heap(order = "desc")
heap.enqueue([i+1 for i in range(1000000)])
heap_sort_result = []
for i in range(len(heap.get_heap())):
    heap_sort_result.append(heap.dequeue())

Wall time: 31.2 s

heapq

%%time
import heapq
# インスタンス生成時に降順を指定(という名のただのリスト生成)
Q = []
# 昇順にデータを突っ込む
for i in [i+1 for i in range(1000000)]:
    heapq.heappush(Q,-i)
# heap sort(降順)
heap_sort_result = []
for i in range(len(Q)):
    heap_sort_result.append(heapq.heappop(Q))
heap_sort_result = list(map(lambda x:-x,heap_sort_result))

Wall time: 1.16 s

ぐえ、ボロ負け。

仕方ないのでnumbaで高速化を図る…

のは次回以降。

mnistをDataRobotに解かせてみる

DataRobotってどうなのよ

DataRobotを触れる機械があったので触ってみた。

画像は扱えない、とのことだったけれども、

画像を画像として扱うからであって、

それぞれのピクセルの輝度を列展開して、

輝度表にしてやればできないことはないだろ…ってのを、

無理やりやってみた。

結果から言うと、悪くない。

Datarobot Third Edition

Datarobot Third Edition

データ準備

あまり大きいデータは辛いので28*28のライトなあのデータを使いました。

そうですね、mnistですね。

udemy.benesse.co.jp

リンクを開くのがめんどい人のために一行で説明すると、

手書きで書かれた数字の判別に使われるデータセットです。

ご存知の通りDLはこちらから。

yann.lecun.com

手動で解凍するも、いらないバイトがある、とのことなので、↓

qiita.com

ちょこっと改変してこんな感じでCSV化。

import numpy as np

def load_img(file_name):
    with open(file_name, 'rb') as f:
        data = np.frombuffer(f.read(), np.uint8, offset=16)
    data = data.reshape(-1, 784)
    return data

def load_label(file_name):
    with open(file_name, 'rb') as f:
        labels = np.frombuffer(f.read(), np.uint8, offset=8)
    return labels

train_x = load_img("./train-images.idx3-ubyte")
test_x = load_img("./t10k-images.idx3-ubyte")
train_y = load_label("./train-labels.idx1-ubyte")
test_y = load_label("./t10k-labels.idx1-ubyte")

np.savetxt("train_x.csv",train_x,delimiter=",")
np.savetxt("train_y.csv",train_y,delimiter=",")
np.savetxt("test_x.csv",test_x,delimiter=",")
np.savetxt("test_y.csv",test_y,delimiter=",")

さて、これで1行1画像784カラムに展開したcsvがtrain_x,test_xに、

それぞれのラベルがtrain_y,test_yに出来たので、

trainデータをdatarobotに食わせました。

結果

リーダボード(某kaggleを意識してますね)にはこんな感じで表示。

f:id:kazuhitogo:20190408111126p:plain

ホールドアウトを外して60000枚すべてで学習した時の絵ですが、 Log Lossで0.07弱は…まぁそれなりですね。

表示されているBlender(アンサンブルのことをDataRobotではBlenderというらしい…)を除いた、 LightGBMとXGBoostでtest_xを予測してみたところ、

XGBoost:

正答:9816 誤答:184 acc:98.16%

LightGBM:

正答:9815 誤答:185 acc:98.15%

でした。

わずか(0.01%というか1問というか)ながらXGBoostが勝ちましたが、

データセットを変えたら違う結果が出るだろうし間違いなく誤差でしょう。

性能としてはコサイン類似度を使ったK近傍法と同レベルではありますね…。

一つ不満があるとすれば、

DataRobotは自動で最適と思われるモデルを選んでくれるはずなのに、

XGBoostは私が手動で突っ込んだモデルだ、ということでしょうか。

(XGBoostをやらないという選択肢が基本的に私にはないのですが)

考察

さて、DataRobotには特徴量を自動で選択してくれる機能があるので、それを見てみる。

↓がDataRobotが有用と判断したピクセルである。

f:id:kazuhitogo:20190408112151p:plain

595の変数を有用と判断したようです。 (596と表示されていますが実際はLabelがあるので595)

ただここに書かれているXXXpxだけだとどこを見ているのかわからないので、 採用されたピクセル箇所をヒートマップにしてみました。

f:id:kazuhitogo:20190408114426p:plain

上下の端っこが使われないのはたぶん文字が書かれていなから、だとしても、

右上から左下にかけて、斜めに全く使われないのはなんでなんですかね。

ここに文字が書かれていない、ということはないので、 すべて塗りつぶされていて判別に使えない…?

右利きの人が多いのでその特徴…?

そんなこともないか…?

わかりませんが、DataRobotさん的には使えないと判断したようです。

逆に左端右端はがっつり使えるようですね。

また、どの特徴量が一番使えたか、もDataRobotは見ることができます。

f:id:kazuhitogo:20190408114946p:plain

一番使えたピクセルを100%としてそれを相対的にどれくらい使えたか、をプロットしているもので、

212pxだけで判別できる、という意味ではありません。

ところで212pxってどこなんでしょう。

あたりが気になるのでトップ5をプロットしてみました。

色が濃い順です。

f:id:kazuhitogo:20190408115352p:plain

ここらへんのピクセルに数字判別の特徴が出るっぽいですね。

知らんけど。

間違えた例

さて、間違えた例も一個くらい見ておきましょう

4を6と間違えた例

f:id:kazuhitogo:20190408120331p:plain

うーん、畳み込みで形を覚えているわけではなく、

ピクセルの輝度の決定木がベースなので、6が書かれているところに4が乗っちゃった感じですね。

きっと。

7を2と間違えた例

f:id:kazuhitogo:20190408120543p:plain

これは人間でも間違える…。

最後に

DataRobotを嫌うData Scientistは多いですが、

個人的にはその問題が機械学習で解けるか、

を手早く知るツールとしては優秀なのではないでしょうか。

今回は画像だったので、当然CNNを使えばもっとよい性能は出ますし、

チューニングの余地もあるかとは思いますが、

仕事をAIで…の文脈だと、出来る出来ないの判断は簡単にすぐにしてくれる、

という意味では、

個人的には良いツールかな、と思います。

(全モデルの学習時間半日程度)

現場で使える!TensorFlow開発入門 Kerasによる深層学習モデル構築手法

現場で使える!TensorFlow開発入門 Kerasによる深層学習モデル構築手法

移動の軌跡をpythonを用いて地図に表示する&画像化する

移動したときの軌跡を表示

今やGPSがくっついた端末を持ち運ぶことも多いので、 移動の軌跡を表示したい、なんていう要望もあるかと。

そんなことをやってみました。 本当は業務で必要になって土日に勉強しただけだが…。

Python for Data Visualization

Python for Data Visualization

【改訂新版】[オープンデータ+QGIS]統計・防災・環境情報がひと目でわかる地図の作り方

【改訂新版】[オープンデータ+QGIS]統計・防災・環境情報がひと目でわかる地図の作り方

folium

pythonに地図表示ライブラリのfoliumというのがあったのでこちらを使ってみた。

python-visualization.github.io

これはブラウザ上でいろんなレイヤを作成するライブラリなので、 基本的にはhtml/css/svgを吐き出す装置だと思ってくれればよい。

使い方

一般的には下記の使い方手順。

  1. マップを作成する(キャンバスのようなもの)

    権利とかにうるさくないopenstreetmapで生成が基本

  2. 地図の上に表示したいものを追加する

    線とか、アイコンとか、ポリゴンとか

  3. 表示する or 保存する

    jupyter notebookを使っていればノートブックに表示もできるし、 htmlを出力して別途使うことができる。

表示するデータ

今回は山手線の駅と東海道新幹線の駅をAさんとBさんが歩いてみたことにする。

とてもこんな速度で歩けるとも思えないが…。

日時 緯度 経度
A 2019/2/22 12:00 35.619700 139.728553
A 2019/2/22 12:30 35.626446 139.723444
A 2019/2/22 13:00 35.626446 139.723444
A 2019/2/22 13:30 35.633998 139.715828
A 2019/2/22 14:00 35.633998 139.715828
B 2019/2/22 12:00 35.681382 139.766084
B 2019/2/22 12:30 35.655767 139.753262
B 2019/2/22 13:00 35.655767 139.753262
B 2019/2/22 13:30 35.630152 139.740440
B 2019/2/22 14:00 35.630152 139.740440

こんなデータの持ち方をしているとする。

(githubに使ったcsvは用意)

GitHub - kazuhitogo/gps_vis

表示の仕様

2種類やるとする。

  1. 一枚に2つの移動を色を変えて表示

    ついでに各線をクリックするとポップアップで誰が通ったのかも表示する

  2. 一人に一枚の地図を用意し、それをhtmlに保存し、画像にも保存する。

一枚に2つの移動を色を変えて表示

さて、コーディング

# あとで使うライブラリも併せて読み込み
import os
import folium
import pandas as pd
from time import sleep as slp
from selenium import webdriver
import glob
from selenium.webdriver.chrome.options import Options

# csvをpandas dataframeに保存
data = pd.read_csv("./gps.csv",encoding="shift_jis")

# 今回はA,Bの人しか居ないが、
# 一応何人来てもいいように人のリストを作り、
# それをforループで回す
person_list = data["人"].unique()

# 地図に表示する色
# foliumでサポートしているのは下記19色
color_list=['red','blue','green','purple','orange','darkred','lightred','beige','darkblue','darkgreen','cadetblue','darkpurple','white','pink','lightblue','lightgreen','gray','black','lightgray']

# 地図オブジェクトを作成
m = folium.Map(tiles='OpenStreetMap')

for idx,person in enumerate(person_list):
    # 一人分のデータだけをdata_tempに格納する
    data_temp = data[data["人"] == person]
    
    # data_tempの順番を日時で昇順ソート
    data_temp = data_temp.sort_values('日時', ascending=True)
    
    # data_tempの緯度経度だけを
    data_temp_lat_lon = data_temp[["緯度","経度"]]
    
    # 緯度経度を配列に格納
    locs = data_temp_lat_lon.values
    
    # 色を指定
    line_color = color_list[idx%len(color_list)]
    
    # 地図に線を追加する。緯度経度の配列をそのまま線として使う
    folium.PolyLine(locs,color=line_color,popup=person).add_to(m)
    
# 地図の表示範囲を緯度経度の最低最大とする
m.fit_bounds([[data["緯度"].min(),data["経度"].min()], [data["緯度"].max(),data["経度"].max()]])

# 地図を表示する
m

そうするといい感じに表示されます。

f:id:kazuhitogo:20190324125616p:plain
一枚に二人を表示

一人に一枚の地図を用意し、それをhtmlに保存し、画像にも保存する

さて、こちらのほう。

htmlに保存するところまではfolium側でメソッドが用意されています。

m = folium.Map(tiles='OpenStreetMap')
m.save("hoge.html")

で保存できるのですが、

そこで表示される画像を保存したい、だとfoliumだけでは対応できません。

そこで、ご存知seleniumを使ってスクショを取って保存を使いました。

さて、コーディング(続き)

for person in person_list:
    # 一人分のデータだけをdata_tempに格納する
    data_temp = data[data["人"] == person]
    
    # data_tempの順番を日時で昇順ソート
    data_temp = data_temp.sort_values('日時', ascending=True)
    
    # data_tempの緯度経度だけをデータフレームに残す
    data_temp_lat_lon = data_temp[["緯度","経度"]]
    
    # 地図オブジェクトを生成
    m = folium.Map(tiles='OpenStreetMap')
    
    # 緯度経度を配列に格納
    locs = data_temp_lat_lon.values
    
    # 地図に線を追加する。緯度経度の配列をそのまま線として使う
    folium.PolyLine(locs).add_to(m)
    
    # 地図の表示範囲を緯度経度の最低最大とする
    m.fit_bounds([[data_temp["緯度"].min(),data_temp["経度"].min()], [data_temp["緯度"].max(),data_temp["経度"].max()]])
    
    # htmlに保存する
    # ./html/xx.htmlに保存される
    m.save(outfile="./html/"+person+".html")

    # seleniumでブラウザを開く
    browser = webdriver.Chrome()
    
    # 画面を最大化
    browser.maximize_window()
    
    # URLを指定(ローカルファイル)
    tmpurl="file:///./html/" + person + ".html"

    # URLを開く
    browser.get(tmpurl)
    
    # 一応3秒寝かす
    slp(3)
    
    # スクリーンショットを取る
    browser.save_screenshot("./png/"+person+".png")
    
    # ブラウザを閉じる
    browser.quit()

そうするといい感じに画像を保存してくれる。 f:id:kazuhitogo:20190325114716p:plain

f:id:kazuhitogo:20190325114725p:plain

ちなみに線ではなくMarkerやCircleで表示したい場合は、

    folium.PolyLine(locs).add_to(m)

の部分を

    for loc in locs:
        folium.Circle(loc).add_to(m)

ないし

    for loc in locs:
        folium.Marker(loc).add_to(m)

にするとよろし。

これをまたパワポに自動で貼り付けるというのもやったのだが、それはまた別の話。