Deep Learning 脱初心者めざして

主にDeepLearningや画像処理について扱います

DNNを実装してみた ( 2 )

前回の続きで、転移学習を扱っていこうと思います。

ちなみに↓が前回の記事です。

mamomamoru.hatenablog.com

前回の記事をみてもらえればわかると思いますが、前回はMNISTデータの0〜4までの数字を使ってDNNを作成しました。今回は、そのモデルを再利用して、残りの5〜9の数字についての学習をしたいと思います。

前問の全ての訓練済み隠れ層(5層)を再利用

隠れ層を全て凍結し、新しいソフトマックス出力層を追加

プレトレーニング済み層の再利用
reset_graph()

restore_saver = tf.train.import_meta_graph("./my_best_mnist_model_0_to_4.meta")

X = tf.get_default_graph().get_tensor_by_name("X:0")
y = tf.get_default_graph().get_tensor_by_name("y:0")
loss = tf.get_default_graph().get_tensor_by_name("loss:0")
Y_proba = tf.get_default_graph().get_tensor_by_name("Y_proba:0")
logits = Y_proba.op.inputs[0]
accuracy = tf.get_default_graph().get_tensor_by_name("accuracy:0")

(./my_best_mnist_model_0_to_4.metaに前回作成したモデルが保存されています。)

隠れ層の凍結

重みを凍結するとは、下位層の重みが固定することであり、無駄な訓練をしなくて済むため上位層の重みの訓練が簡単になる。

  • tf.get_collection()関数
    • 下位層の変数を除外して、オプティマイザに訓練する変数のリストを渡す
learning_rate = 0.01

output_layer_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, scope="logits")  # logitsの下位層は除外
optimizer = tf.train.AdamOptimizer(learning_rate, name="Adam2")
training_op = optimizer.minimize(loss, var_list=output_layer_vars)

correct = tf.nn.in_top_k(logits, y, 1)
accuracy = tf.reduce_mean(tf.cast(correct, tf.float32), name="accuracy")

init = tf.global_variables_initializer()
five_frozen_saver = tf.train.Saver()

数字1つあたり100個の画像で5から9までの数字について新しいDNNを訓練

学習

n_epochs = 1000
batch_size = 20

max_checks_without_progress = 20
checks_without_progress = 0
best_loss = np.infty

with tf.Session() as sess:
    init.run()
    restore_saver.restore(sess, "./my_best_mnist_model_0_to_4")
        
    for epoch in range(n_epochs):
        rnd_idx = np.random.permutation(len(X_train2))
        for rnd_indices in np.array_split(rnd_idx, len(X_train2) // batch_size):
            X_batch, y_batch = X_train2[rnd_indices], y_train2[rnd_indices]
            sess.run(training_op, feed_dict={X: X_batch, y: y_batch})
        loss_val, acc_val = sess.run([loss, accuracy], feed_dict={X: X_valid2, y: y_valid2})
        if loss_val < best_loss:
            save_path = five_frozen_saver.save(sess, "./my_mnist_model_5_to_9_five_frozen")
            best_loss = loss_val
            checks_without_progress = 0
        else:
            checks_without_progress += 1
            if checks_without_progress > max_checks_without_progress:
                print("Early stopping!")
                break
        print("{}\tValidation loss: {:.6f}\tBest loss: {:.6f}\tAccuracy: {:.2f}%".format(
            epoch, loss_val, best_loss, acc_val * 100))

with tf.Session() as sess:
    five_frozen_saver.restore(sess, "./my_mnist_model_5_to_9_five_frozen")
    acc_test = accuracy.eval(feed_dict={X: X_test2, y: y_test2})
    print("Final test accuracy: {:.2f}%".format(acc_test * 100))
結果
Total training time: 2.2s
Final test accuracy: 74.04%

重みの計算の部分を全て凍結しているから、学習速度はめちゃくちゃはやくなっている。各画像100枚ずつの学習だからなんとも言えないが、それにしても74%は低い。まあ、前回作成したモデルの隠れ層をそのまま使ってるから当たり前なんだけど...

再利用する隠れ層の数を変更(5 -> 4)

これ以降は上記のコードを少しいじればできるので、コードは省略します。前回と同じようにGitHubに置いてあるので、興味のある方は見てください。

github.com

Final test accuracy: 75.77%

まあ、さっきよりはほんの少し良くなったくらい。

上位2層の凍結を解除(隠れ層1,2のみを凍結)

Final test accuracy: 81.92%

少ない学習データに関わらず、そこそこの精度は得られてるけど前回ほど高い数値はでないなあ

最後に

今回はそこまで良い結果が得られなかったが、転移学習は深層学習に必須の知識なので、ぜひ使えるようになりたい。転移学習ができるようになれば、わざわざ下位層の重みの計算をしなくて済み、実行時間が大幅に短縮できるので非常に便利だと思った。

scikit-learnとTensorFlowによる実践機械学習

scikit-learnとTensorFlowによる実践機械学習

DNNを実装してみた ( 1 )

さて今回は、Google Colaboratory(ちなみにGoogle Colaboratoryは無料で深層学習に必須なGPUやTPUを使用できるので超便利)を使って、前回記事で紹介した実践的なガイドに沿ったDNNを構築してみました。

また今回はデフォルト通り、MNISTデータに対してモデルを構築しました。

mamomamoru.hatenablog.com

今回使用したコードはgithubに置いてあります。

github.com

基本的なDNNモデル

訓練するのは0から4までの数字とする
(5から9は後でやる転移学習を使って訓練する)

  • モデル
  • 実行
    • エポック数 : 1000
    • バッチサイズ : 20

結果

0    Validation loss: 0.111523   Best loss: 0.111523 Accuracy: 97.85%
1   Validation loss: 0.259246   Best loss: 0.111523 Accuracy: 95.00%
2   Validation loss: 0.116553   Best loss: 0.111523 Accuracy: 97.46%
3   Validation loss: 0.439698   Best loss: 0.111523 Accuracy: 96.72%
4   Validation loss: 0.124247   Best loss: 0.111523 Accuracy: 96.48%
5   Validation loss: 0.556761   Best loss: 0.111523 Accuracy: 78.93%
6   Validation loss: 0.488352   Best loss: 0.111523 Accuracy: 87.88%
7   Validation loss: 0.327603   Best loss: 0.111523 Accuracy: 96.56%
8   Validation loss: 0.153663   Best loss: 0.111523 Accuracy: 97.97%
9   Validation loss: 0.481955   Best loss: 0.111523 Accuracy: 84.17%
10  Validation loss: 0.657136   Best loss: 0.111523 Accuracy: 77.95%
11  Validation loss: 0.583860   Best loss: 0.111523 Accuracy: 80.02%
12  Validation loss: 0.384622   Best loss: 0.111523 Accuracy: 91.67%
13  Validation loss: 200.980087 Best loss: 0.111523 Accuracy: 96.56%
14  Validation loss: 0.147616   Best loss: 0.111523 Accuracy: 97.62%
15  Validation loss: 0.134488   Best loss: 0.111523 Accuracy: 97.62%
16  Validation loss: 0.470304   Best loss: 0.111523 Accuracy: 84.64%
17  Validation loss: 0.204020   Best loss: 0.111523 Accuracy: 97.26%
18  Validation loss: 0.774719   Best loss: 0.111523 Accuracy: 95.35%
19  Validation loss: 0.299993   Best loss: 0.111523 Accuracy: 92.57%
20  Validation loss: 0.147901   Best loss: 0.111523 Accuracy: 96.56%
Early stopping!
Total training time: 49.6s
INFO:tensorflow:Restoring parameters from ./my_mnist_model_0_to_4.ckpt
Final test accuracy: 97.94%

以上より、最終的な正確度としては

97.94%


しかも1分もかからずにここまでの精度を出せるってことに感動(やっぱGPU最強)

以下を追加したバージョン

ここからは途中経過も載せると長くなりすぎてしまうので、最終的な結果のみにします。

交差検証のみ

  • 交差検証したパラメータ
    • "n_neurons": [10, 30, 50, 70, 90, 100, 120, 140, 160],
    • "batch_size": [10, 50, 100, 500],
    • "learning_rate": [0.01, 0.02, 0.05, 0.1],
    • "activation": [tf.nn.relu, tf.nn.elu, leaky_relu(alpha=0.01), leaky_relu(alpha=0.1)]
  • 実行
結果

交差検証結果

{'activation': <function tensorflow.python.ops.gen_nn_ops.relu(features, name=None)>,
 'batch_size': 100,
 'dropout_rate': 0.2,
 'learning_rate': 0.01,
 'n_neurons': 160}

正確度

0.9904650710254913

交差検証すると時間が結構かかるけど、その分正確度も相当上がっている。モデル自体は変わっていないのに、パラメータを変えただけでここまで精度が上がるって考えると交差検証はやっぱり重要。


以下、基本的に交差検証したパラメータと実行の回数は同じとする。

バッチ正規化 + 交差検証

    • "batch_norm_momentum": [0.9, 0.95, 0.98, 0.99, 0.999]
結果

交差検証結果

{'activation': <function tensorflow.python.ops.gen_nn_ops.relu(features, name=None)>,
 'batch_norm_momentum': 0.99, 
 'batch_size': 50, 
 'learning_rate': 0.01, 
 'n_neurons': 70} 

正確度

0.9937731076084841

過学習を抑制しているにも関わらず、正確度が非常に高いなあ。 しかも次のドロップアウトと比べて実行時間がはやい。

ドロップアウト + 交差検証

    • "dropout_rate": [0.2, 0.3, 0.4, 0.5, 0.6]
結果

交差検証結果

{'activation': <function tensorflow.python.ops.gen_nn_ops.relu(features, name=None)>,
 'batch_size': 100,
 'dropout_rate': 0.2,
 'learning_rate': 0.01,
 'n_neurons': 160}

正確度

0.9926055652850749

今回だとドロップアウトの方がバッチ正規化と比べて正確度は少しだけ落ちてる...
(だからと言ってドロップアウトが劣っていると決めつけるのは早いけど)

次回

今回はMNISTデータの0から4までの数字を使ってDNNを構築したが、次回は今回構築したDNNを再利用して5から9までの数字についてのDNNを構築する転移学習を扱います。

DNNの基礎知識についてのまとめ

深層ニューラルネットの訓練

今回は、深層ニューラルネットワーク(以下DNN)の問題点を挙げ、その解決法を示します。

勾配消失/爆発問題

勾配消失

勾配降下法による更新では下位層の接続の重みがほとんど変わらず、訓練が良い解に収束しなくなること

勾配爆発

逆に勾配がどんどん急になり、多くの層の重みが更新によって膨大になり、アルゴリズムが発散してしまうこと
→ 層によって学習速度が大幅に変わってしまう

  • 理由
    • 活性化関数
      • 従来はロジスティックシグモイド関数(平均 : 0.5)
      • 入力が大きくなると関数は0か1で飽和し、導関数が極端に0に近くなってしまう → 勾配が消失
    • 初期値
      • 各層の出力の分散が、入力の分散よりもずっと大きくなる

Xavierの初期値とHeの初期値

信号を適切に流すために、各層の入出力の分散を等しくする必要があり、層を通過する前後の勾配の分散も等しい必要がある

活性化関数

  • ReLU(ランプ)関数
    深層ニューラルネットで主に使用される
    • dying ReLU 訓練中に一部のニューロンが0以外の値を出力しなくなる現象
    • leaky ReLU ReLU関数の変種 高い性能を発揮(リークが大きい方がより良い)

      • randomized leaky ReLU リークを無作為に選び、テスト中の平均を固定する 正則化器としても機能

      • parametric leaky ReLU 大規模な画像データセットでは良い性能を示す

    • ELU関数 ReLUの全ての変種よりも高い性能を示す
      ただし、計算に時間がかかる

バッチ正規化

  • 訓練中に前の層のパラメータの変化に伴い、各層の入力分布が変化する問題(内部共変量シフト)に対処するためのテクニック

  • 各層の活性化関数を実行する直前に、入力の0を中心とするセンタリングと正規化を行い、層ごとに二つの新しいパラメータをそれぞれ使ってスケーリングとシフトを行う
    → 各層の入力の最適なスケールと平均をモデルに学習させる

勾配クリッピング

バックプロパゲーションステップでは勾配をクリッピングし、一定の閾値を超えないようにするテクニック
ただ、一般にバッチ正規化の方が主流

プレトレーニング済み層の再利用

転移学習

既存のNNを探し、そのネットワークの下位層を再利用すること → 時間の大幅短縮、訓練データを減らせる

  • 事前に訓練したモデルを復元するためのSaver
  • 新しいモデルを保存するための別のSaver

model zoo

多くの人が様々なタスクのために機械学習モデルを訓練し、プレトレーニング済みのモデルを一般開放している

オプティマイザの高速化

Momentum最適化

それまでの勾配を重視

  • ハイパーパラメータ β
    0~1の値をとる(一般に0.9を用いる)

NAG(Nesterov Accelerated Gradient)

Momentum最適化の変種 元の位置の勾配ではなく、慣性の方向に少し進んだところで測定された勾配を使うことで、正確性が増した

AdaGrad

最も急な次元に沿って勾配ベクトルをスケールダウンする
→ パラメータの要素ごとに適応的に学習係数を調整しながら学習を行う手法 - 適応学習率(adaptive learning rate) 傾斜が急な次元では傾斜が緩やかな次元よりも早く学習率を下げる

ただし、学習率が大きく下がるため、全体の最適値に到達する前にアルゴリズムが止まってしまう

RMSProp

  • Adaの問題点を解決 過去の全ての勾配を均一に加算していくのではなく、過去の勾配を徐々に忘れて、新しい勾配の情報が反映されるように加算 → 指数平均移動 : 指数関数的に過去の勾配のスケールを減少させる

Adam(adaptive momentum estimation)

  • MomentumとRMSPropのアイデアの融合
    • Momentum 過去の勾配の指数関数的減衰平均を管理
    • RMSProp 過去の勾配の二乗の指数関数的減衰平均を管理
  • ハイパーパラメータ
    • 慣性減衰ハイパーパラメータ β1 : 0.9
    • スケーリング減衰ハイパーパラメータ β2 : 0.999

学習スケジュール

高い学習率でスタートし、コスト低減のペースが下がったら学習率を下げる

  • 部分ごとに一定の学習率をあらかじめ決めておく
  • 性能によるスケジューリング
  • 指数関数的スケジューリング
  • 累乗スケジューリング

正則化による過学習の防止

早期打ち切り

検証セットに対する性能が落ち始めたところで訓練を中止する
通常、他の正則化手法と組み合わせる

l1, l2正則化

重みに制限を加える → コスト関数に正則化項を追加

ドロップアウト

DNNで最もよく使われている正則化テクニック(ただし、収束は大幅に遅くなる)
ニューロンをランダムに消去しながら学習する手法
入出力層と任意の隠れ層にtf.layers.dropout()関数を適用

データ拡張

既存の訓練インスタンスから新しい訓練インスタンスを生成して人工的に訓練セットを膨らませる

実践的なガイドライン

  • 初期値 : Heの初期値
  • 活性化関数 : ELU
  • 正則化 : バッチ正規化、Dropout
  • オプティマイザ : NAG
  • 学習率のスケジューリング : なし

今回は以下の書籍のDNNについてまとめました。次回はこの実践的なガイドラインに沿って実際にプログラムを動かしてみようと思います。

scikit-learnとTensorFlowによる実践機械学習

scikit-learnとTensorFlowによる実践機械学習

今更ながらブログ始めました

大学三年生がほぼ終わり、今更ながら自分のアウトプットの意味もかねてブログを始めました。

今までさんざんアウトプットは大切だと周りの人に言われたにも関わらず、なかなか実行するに至りませんでした…
(ただめんどくさがってただけです)

主に扱う内容としては、最近流行りまくっている深層学習(deep learning)とたまに機械学習や画像処理系もやれたらいいなあと思っています。

ちなみにここ最近オライリー・ジャパンから出版されている以下の二つの書籍を読み始めたところなので、これらについて取り扱うことが多くなると思います。

  • scikit-learnとTensorFlowによる実践機械学習
  • ゼロから作るDeepLearning

scikit-learnとTensorFlowによる実践機械学習

scikit-learnとTensorFlowによる実践機械学習