2015/12/29

TensorFlowでニューラルネットワーク - MLP(Multi Layer Perceptron)

 TensorFlow でニューラルネットワーク(MLP)を作成したところ、MNISTデータセットで約98%の精度がでましたので、紹介します。

概要

TensorFlow は、TheanoやChainerのように計算グラフを定義・構築し、実行するという素敵な仕組みを持っています。なにより、これらに共通する仕組みである自動微分がすばらしい。
 詳細は、TensorFlow の White Paper に譲りますが、個人的には、Theanoよりもわかりやすい印象を受けています。 一方で、TensorFlow は、Chainer に比べると、基本的なニューラルネットワーク(MLP)を構築するのにも、手間がかかる印象です。そこで、TensorFlowを使ってライブラリ風に作成したMLPモジュールを、実行方法含め、紹介したいと思います。


準備

  • 実行環境(例)
    • OS: Ubuntu 15.10 64bit (Over ESXi)
    • 言語: Python (Over virtualenv)
    • virtualenvでの構築方法は、Virtualenv installation を参照ください。
    • TensorFlow: tensorflow-0.5.0-cp27-none-linux_x86_64.whl
  • ソースファイル
    • mlp.py - MLP をライブラリ風にまとめたファイル
    • run.py - mlp.py をライブラリ風に実行するファイル
    • input_data.py - TensorFlow の examples 配下の input_data.py

実行例

 上記ソースファイルをダウンロードしたら、以下のように実行します。(初回実行時は、MNISTデータセットのダウンロードを行うため、少なからず待つことになります。)

(tensorflow)$ ls
input_data.py  mlp.py  run.py
(tensorflow)$ python run.py


 tensorboard で可視化します。

(tensorflow)$ ls -F
data/  input_data.py  input_data.pyc  log/  mlp.py  mlp.pyc  run.py
(tensorflow)$ tensorboard --logdir=`pwd`/log
Starting TensorBoard on port 6006
(You can navigate to http://localhost:6006)

可視化

  • 構成
    • 全体構成

    • MLP構成
     今回は、多層化を前提として、中間層を6層で構築しました。入力層に近いほど、勾配が残るようにReLUを配置し、出力層の前段では勾配がサチりやすいsigmoidを配置してみました。(実は、ここまで多層化しなくても、1層のみでも同程度の精度がでたりしますが・・・^-^;)

    ソースコードでは、以下のように定義します。(run.py)
        layers = [  # - input and hidden layers
          Layer("input", i_dim, activate=None, o=nh.X),
          Layer("01_relu", 512, activate=tf.nn.relu),
          Layer("02_relu6", 256, activate=tf.nn.relu6),
          Layer("03_tanh", 128, activate=tf.nn.tanh),
          Layer("04_linear", 256, activate=None),
          Layer("05_sigmoid", 128, activate=tf.nn.sigmoid),
          Layer("06_linear", o_dim, activate=None)
        ]
    

  • 実行結果

    • 認識精度

     run.py を実行した結果の認識精度です。約98%の精度が出ました。評価方法は、TensorFlowのexamplesのfully_connected_feed.pyと同じです。TensorFlowの畳み込みニューラルネットワーク(サンプル)の99.2%には、及ばずながら、そこそこの精度が出たと思います。(約7000ステップあたりから、認識精度が頭打ちになります。)
    • 目的関数
     目的関数のグラフです。目的関数は、交差エントロピー(tf.nn.softmax_cross_entropy_with_logits)の平均値と正則化項を加えたものです。概ね収束していることがわかります。(若干振動しているのは、dropoutを適用しているからだと考えています。)
    • ヒストグラム
     各層の変数(biases, weights)のヒストグラムです。横軸がステップ数(ミニバッチ処理回数)で、縦軸が変数(biases, weights)の値です。どの層のweightsを見ても、訓練のステップ数が増えても(右方向にすすんでも)値の分布がほとんど変わっていないことがわかります。また、正則化項(run.py上では、regularizer)を含んでいるため、訓練ステップが後半になると、biasesの次元における値の裾(上下の薄いところ)の広がりが若干抑制されていることが見てとれます。(つまり、正則化項の効果がでています。実は、正則化項を除いた方が若干(気持0.1%程度)精度が向上しました。)

発展

  • チューニング
 今回は、主に、learning_rate, momentum, 各層のユニット数を変更して認識精度の違いを検証しました。試した限りでは、learning_rate を0.2~0.25の辺りにするのが、比較的良い結果になりました。momentumは、learning_rateと同程度に設定し、各層のユニット数は、MNISTデータセットの画像のサイズ(28x28=784)を参考に、512以下で設定しました。run.pyの以下のコードを変更すれば、独自にチューニングできます。
  parameter.DEFINE_float('learning_rate', 0.25, 'Learning rate.')
  parameter.DEFINE_float('momentum', 0.25, 'Momentum rate.')
  parameter.DEFINE_float('regular_rate', 5e-5, 'Regularization rate. 0.0 means no regularization.')
  parameter.DEFINE_integer('batch_size', 100, 'Batch size.  Must divide evenly into the dataset sizes.')
  parameter.DEFINE_integer('n_steps', 10000, 'Training steps.')
  parameter.DEFINE_float('dropout', 0.25, 'dropout rate.')

 また、今回は、オプティマイザとして、MomentumOptimizerを使いましたが、以下のようにrun.pyのコメントを変更することで、異なるオプティマイザを利用することができます。試した限りでは、どのオプティマイザを使っても、認識精度には大きな差はでませんでした。
    #optimizer = tf.train.GradientDescentOptimizer(PARAM.learning_rate)
    optimizer = tf.train.MomentumOptimizer(PARAM.learning_rate, PARAM.momentum)
    #optimizer = tf.train.AdagradOptimizer(PARAM.learning_rate)
    #optimizer = tf.train.AdamOptimizer()
  • 考察
チューニングとしては、やはり、learning_rate, momentum が大きく認識精度に影響しました。正則化項により、各層の変数(biases, weights)の分布の広がりが抑えられ、分布が収束傾向にあることを確認しました。今回、パラメータ等を変更をしてチューニングを試みましたが、精度が頭打ちになる結果になりました。今のところ、MLPでは約98%が限界のようです。もしかしたら、別のチューニング方法があるかもしれませんが・・・

 今後は、AutoencoderやRBM等の多層化について、でき次第、載せたいと思っています。

参考

  • run.py
#!/usr/bin/env python

# =====
# - main
if __name__ == '__main__':
  # -----
  class NodeHolder(object):
    pass
  # -----

  import tensorflow as tf
  import input_data   # - from current dir
  from mlp import MLP, Layer, LossCrossEntropy, Trainer, Evaluator
  parameter = tf.app.flags
  parameter.DEFINE_float('learning_rate', 0.25, 'Learning rate.')
  parameter.DEFINE_float('momentum', 0.25, 'Momentum rate.')
  parameter.DEFINE_float('regular_rate', 5e-5, 'Regularization rate. 0.0 means no regularization.')
  parameter.DEFINE_integer('batch_size', 100, 'Batch size.  Must divide evenly into the dataset sizes.')
  parameter.DEFINE_integer('n_steps', 10000, 'Training steps.')
  parameter.DEFINE_float('dropout', 0.25, 'dropout rate.')
  PARAM = tf.app.flags.FLAGS

  data_sets = input_data.read_data_sets(train_dir='./data')
  i_dim = data_sets.train.images.shape[1]
  o_dim = len(set(data_sets.train.labels))
  nh = NodeHolder()

  with tf.Graph().as_default():
    nh.X = tf.placeholder(tf.float32, shape=(None, i_dim))
    layers = [  # - input and hidden layers
      Layer("input", i_dim, activate=None, o=nh.X),
      Layer("01_relu", 512, activate=tf.nn.relu),
      Layer("02_relu6", 256, activate=tf.nn.relu6),
      Layer("03_tanh", 128, activate=tf.nn.tanh),
      Layer("04_linear", 256, activate=None),
      Layer("05_sigmoid", 128, activate=tf.nn.sigmoid),
      Layer("06_linear", o_dim, activate=None)
    ]
    nh.y = tf.placeholder(tf.int32, shape=(PARAM.batch_size))
    nh.kp = tf.placeholder(tf.float32)

    # - build mlp
    mlp = MLP('mlp', layers, node_holder=nh)
    regularizer = PARAM.regular_rate * mlp.regularizer
    loss = LossCrossEntropy(nh, regularizer)
    #loss = LossCrossEntropy(nh)

    #optimizer = tf.train.GradientDescentOptimizer(PARAM.learning_rate)
    optimizer = tf.train.MomentumOptimizer(PARAM.learning_rate, PARAM.momentum)
    #optimizer = tf.train.AdagradOptimizer(PARAM.learning_rate)
    #optimizer = tf.train.AdamOptimizer()
    trainer = Trainer(loss, optimizer)
    init = tf.initialize_all_variables()

    # - create session
    sess = tf.Session()
    sess.run(init)

    # - setup summary
    trainer.setup(nh, sess, evaluator=Evaluator(nh))

    # - run train
    mlp.run(sess, trainer, data_sets, PARAM)


0 件のコメント:

コメントを投稿