Dropoutのハイパーパラメータの決定について

ここ1ヶ月ほど、会社のアルバイトで6クラスの画像分類モデルをTensorflowを使って作ることを任された
色々詰まった、考えさせられたことがあったのでメモ

画像分類だったらCNNで、ネットワークはVGG16だったりAlexNetで作りましたべろ〜んwって感じでいいと思うが、そのモデルをProtocolBufferファイルにしてラズパイなどに乗せて動かすということなのでネットワークの層が深すぎるとProtocolBufferファイルが400~700MBになって重すぎて敵わん...となってしまうので6層ほどで適当に作ることにした

f:id:Owatank:20171121151707p:plain

全然6層じゃなかった
畳み込みのkernel_sizeは3~5で、Dropoutは0.5で調整した

いざ学習させたらなぜか学習が進まなかった。損失は減っているが精度が0.23~0.25間で向上しなかった。
別の2層の畳み込み層と2層の全結合層のモデルは学習が進んでいたことから、自分のモデルのパラメータの設定がおかしいのだろうかと悩んだ
畳み込み層、学習係数のハイパーパラメータは同じだったので、全結合層のパラメータやLocal response Normalization層が原因なのかと考えたりした

試行錯誤した結果、上記のせいで学習が滞っていたのではなく、Dropoutのパラメータが原因だったということがわかった
上のネットワーク図からわかるように2つの隠れ層である全結合層にはDropoutを適用している。どちらもkeep_probは0.5に設定した
このkeep_probをどちらも0.8に変更した結果、学習が進んだ

Dropoutについては青いイルカさんの本とかでは仕組みや嬉しいことの説明はあったが最適なパラメータの設定値の説明はなかった。なのでいつも0.5で設定していた
調べたところ最初に来る全結合層に対してDropoutを適用するとき、そのパラメータを0.5にするのはあまり良くないそうだ

sonickun.hatenablog.com

というわけで学習が進まない問題は回避できてなんとかなった

その後、全結合層のノード数とDropoutのkeep_probパラメータの間に何か関係があるのか気になったので調べることにした
使うデータはTensorflowのチュートリアルで有名なMNISTで全結合層のみのネットワークを構築した

mport tensorflow as tf
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data

mnist = input_data.read_data_sets('MNIST_data', one_hot=True)

t = tf.placeholder(tf.float32, shape=(None,10))
X = tf.placeholder(tf.float32, shape=(None,28*28),name="input")
keep_prob_1 = tf.placeholder(tf.float32) # ドロップアウトする割合
keep_prob_2 = tf.placeholder(tf.float32) # ドロップアウトする割合

stddev = np.sqrt(2.0 / 28*28)
h_W1 = tf.Variable(tf.truncated_normal([28*28,256], stddev=stddev))
h_b1 = tf.Variable(tf.constant(0.1, shape=[256]))
h_fc1 = tf.nn.relu(tf.matmul(X,h_W1) + h_b1)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob_1)

stddev = np.sqrt(2.0 / 256)
h_W_fc2 = tf.Variable(tf.truncated_normal([256,512], stddev=stddev))
h_b_fc2 = tf.Variable(tf.constant(0.1, shape=[512]))
h_fc2 = tf.nn.relu(tf.matmul(h_fc1_drop, h_W_fc2) + h_b_fc2)
h_fc2_drop = tf.nn.dropout(h_fc2, keep_prob_2)

stddev = np.sqrt(2.0 / 512)
W_fc3 = tf.Variable(tf.truncated_normal([512,10], stddev=stddev))
b_fc3 = tf.Variable(tf.constant(0.1, shape=[10]))
fc3 = tf.matmul(h_fc2_drop, W_fc3) + b_fc3
p = tf.nn.softmax(fc3,name="output")

loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=fc3, labels=t))

optimizer = tf.train.AdamOptimizer(1e-4)
train_step = optimizer.minimize(loss)

correct_prediction = tf.equal(tf.argmax(fc3,1), tf.argmax(t,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

saver = tf.train.Saver()

### 学習の実行
sess = tf.Session()
sess.run(tf.global_variables_initializer())
i = 0
for i in range(0,10000):
    batch = mnist.train.next_batch(30)
    sess.run(train_step, feed_dict={X:batch[0],t:batch[1],keep_prob_1:0.5,keep_prob_2:0.5})
    if i % 1000 == 0:
        train_acc, train_loss = sess.run([accuracy,loss], feed_dict={X:batch[0],t:batch[1],keep_prob_1:1.0,keep_prob_2:1.0})
        print("[Train] step: %d, loss: %f, acc: %f" % (i, train_loss, train_acc))
        # テストデータによるモデルの評価
        test_acc, test_loss = sess.run([accuracy,loss], feed_dict={X:mnist.test.images,t:mnist.test.labels,keep_prob_1:1.0,keep_prob_2:1.0})
        print("[Test] loss: %f, acc: %f" % (test_loss, test_acc))

        saver.save(sess, "./ckpt/mnist")
sess.close()

h_fc1h_fc2にdropoutを適用したりしなかったり、keep_prob_1keep_prob_2の値を変えて訓練結果を確認した
まずh_fc1h_fc2のノード数が、48,96でdropoutを適用しなかった時の結果(多いので2000step毎だけ)

[Train] step: 0, loss: 15.007373, acc: 0.066667
[Test] loss: 12.547914, acc: 0.097100
[Train] step: 2000, loss: 0.960308, acc: 0.666667
[Test] loss: 0.894258, acc: 0.735600
[Train] step: 4000, loss: 0.381689, acc: 0.800000
[Test] loss: 0.603150, acc: 0.818700
[Train] step: 6000, loss: 0.460051, acc: 0.866667
[Test] loss: 0.484288, acc: 0.855700
[Train] step: 8000, loss: 0.595815, acc: 0.866667
[Test] loss: 0.419885, acc: 0.873900

精度はいまいち...次にノード数はそのままでdropoutを適用し、keep_prob_1keep_prob_2が共に0.5に設定したとき

[Train] step: 0, loss: 18.346718, acc: 0.033333
[Test] loss: 12.756997, acc: 0.122000
[Train] step: 2000, loss: 1.504397, acc: 0.500000
[Test] loss: 1.692765, acc: 0.451800
[Train] step: 4000, loss: 1.380499, acc: 0.566667
[Test] loss: 1.422444, acc: 0.552400
[Train] step: 6000, loss: 1.460326, acc: 0.600000
[Test] loss: 1.400168, acc: 0.609900
[Train] step: 8000, loss: 1.548799, acc: 0.533333
[Test] loss: 1.289341, acc: 0.667200

適用なしの時より悪化しているのがわかる

色々試した結果、keep_prob_1は1.0(dropoutを適用しない)keep_prob_2を0.8に設定した結果がdropout適用しないときより良かった

mnist-dropout-48-96-10-08-ckpt

[Train] step: 0, loss: 17.872959, acc: 0.066667
[Test] loss: 16.914766, acc: 0.103000
[Train] step: 2000, loss: 1.499189, acc: 0.666667
[Test] loss: 1.236634, acc: 0.663800
[Train] step: 4000, loss: 0.986408, acc: 0.666667
[Test] loss: 0.778916, acc: 0.762500
[Train] step: 6000, loss: 0.436568, acc: 0.866667
[Test] loss: 0.605089, acc: 0.813300
[Train] step: 8000, loss: 0.290827, acc: 0.933333
[Test] loss: 0.504056, acc: 0.846000

次に上記の結果はノード数が少なすぎるからkeep_prob_1keep_prob_2が共に0.5の結果は悪かったのでは?と疑問に思い、各ノード数を256,512にした時でも同様に結果を観察することにした
まずはdropout適用しないときの結果

[Train] step: 0, loss: 9.332975, acc: 0.166667
[Test] loss: 10.855021, acc: 0.127700
[Train] step: 2000, loss: 0.380751, acc: 0.966667
[Test] loss: 0.349467, acc: 0.905500
[Train] step: 4000, loss: 0.222563, acc: 0.966667
[Test] loss: 0.241931, acc: 0.931800
[Train] step: 6000, loss: 0.108663, acc: 0.966667
[Test] loss: 0.199128, acc: 0.943000
[Train] step: 8000, loss: 0.077383, acc: 0.966667
[Test] loss: 0.172284, acc: 0.948200

元々の表現力が上がったせいか48,96のときより精度が良くなっている
次にkeep_prob_1keep_prob_2が共に0.5のときの結果

[Train] step: 0, loss: 7.612897, acc: 0.200000
[Test] loss: 9.190090, acc: 0.129300
[Train] step: 2000, loss: 0.934076, acc: 0.733333
[Test] loss: 0.541602, acc: 0.850500
[Train] step: 4000, loss: 0.303545, acc: 0.966667
[Test] loss: 0.453306, acc: 0.869100
[Train] step: 6000, loss: 0.434249, acc: 0.900000
[Test] loss: 0.529966, acc: 0.884700
[Train] step: 8000, loss: 0.707538, acc: 0.866667
[Test] loss: 0.521837, acc: 0.899100

精度自体はdropoutなしの方より悪い気するけど48,96でkeep_prob_1keep_prob_2が共に0.5のときの結果と比べて、学習が停滞しているようには感じない
やはりノード数が多いからだろうか?

最後にkeep_prob_1は1.0(dropoutを適用しない)keep_prob_2を0.8に設定した結果

[Train] step: 0, loss: 15.822262, acc: 0.000000
[Test] loss: 15.912350, acc: 0.069200
[Train] step: 2000, loss: 0.096453, acc: 0.966667
[Test] loss: 0.338137, acc: 0.914300
[Train] step: 4000, loss: 0.138627, acc: 0.966667
[Test] loss: 0.221914, acc: 0.936700
[Train] step: 6000, loss: 0.005314, acc: 1.000000
[Test] loss: 0.170819, acc: 0.948000
[Train] step: 8000, loss: 0.288768, acc: 0.866667
[Test] loss: 0.142067, acc: 0.958000

dropoutなしよりとても良くなった

Dropoutのパラメータの設定でここまで結果が変わるとは思っていなかったので調べてて面白かった