アラカン"BOKU"のITな日常

文系システムエンジニアの”BOKU”が勉強したこと、経験したこと、日々思うことを書いてます。

ネットワークモデルを簡潔に記述できる「tf.contrib.slim」を試してみる/tensorflow r1.4/1,5

ニューラルネットワークで思いつきをちょっと試してみたい。

 

これは、よくあります。 

 

そういう時は、「keras」とか、「Neural Network Libraries」のような、モデルを簡潔にかけるライブラリを使いたくなります。

 

kerasなんか、tensorflowに統合されて、わざわざインストールしなくても「tf.keras」で使えるようになっているっぽいですし。

 

だけど、どうせならtensorflowで、そういうことできないのかな。

 

そう思って、ドキュメントを見てたら、ありました。

 

tf.contrib.slimです。

 

早速、試してみます。 

 

tf.contrib.slimはモデルを簡潔に定義できます 

 

TensorFlow で複雑なモデルを簡潔に定義し、訓練しそして評価するための軽量ライブラリという位置づけのものみたいです。

 

今回はシンプルに以下のモデルを組んでみます。

 

#Layer01

  • Convolution(畳み込み)
  • ReLu(活性化関数)
  • MaxPooling(プーリング)

#Layer02

  • Convolution(畳み込み)
  • ReLu(活性化関数)
  • MaxPooling(プーリング)

#Layer03

  • Fully Connected(全結合層)
  • ReLu(活性化関数)

#Layer04

  • Fully Connected(全結合層)

 

これを、tf.contrib.slimを使って、ソースコードに書くとこんな感じになります。 

import tensorflow as tf
import tensorflow.contrib.slim as slim

def inference():
    with slim.arg_scope([slim.conv2d, slim.fully_connected],
                      activation_fn=tf.nn.relu,                      weights_initializer=tf.truncated_normal_initializer(stddev=0.1),
                      biases_initializer=tf.constant_initializer(0.1)):

         with slim.arg_scope([slim.max_pool2d], padding='SAME'):
             # Layer01
             h_conv1 = slim.conv2d(x_image, 32, [5, 5])
             h_pool1 = slim.max_pool2d(h_conv1, [2, 2])
             # Layer02
             h_conv2 = slim.conv2d(h_pool1, 64, [5, 5])
             h_pool2 = slim.max_pool2d(h_conv2, [2, 2])
             # Layer03
             h_pool2_flat = slim.flatten(h_pool2)
             h_fc1 = slim.fully_connected(h_pool2_flat, 1024)

             # Layer04
             y_conv = slim.fully_connected(h_fc1 , 10, activation_fn=None)
    return y_conv

 

かなり、すっきりと直感的な感じがします。

 

デフォルト引数の定義ができます

 

まず、importです。 

import tensorflow.contrib.slim as slim 

 

続いて、ユーザーがそのスコープ内の特定の操作のデフォルト引数を定義する部分が続きます。 

with slim.arg_scope([slim.conv2d, slim.fully_connected],

              activation_fn=tf.nn.relu, 

              weights_initializer=tf.truncated_normal_initializer(stddev=0.1),
              biases_initializer=tf.constant_initializer(0.1))

 

これいいですね。

 

まとめて、デフォルト引数を指定するには「tf.contrib.slim.arg_scope」を使います。

 

上の例では、slim.conv2d と  slim.fully_connected の2つのレイヤーに対して、活性化関数として「tf.nn.relu」、重みの初期値に「tf.truncated_normal_initializer(stddev=0.1)」、バイアスの初期値に「tf.constant_initializer(0.1))」をデフォルト引数として設定してます。

 

同様に次のWithブロックでも、デフォルト引数を定義しています。

with slim.arg_scope([slim.max_pool2d], padding='SAME'):

 

 こちらは、max_pool2dの、パディングを'SAME'に指定しています。

 

こうやって、デフォルト引数を設定できるのは、続くLayer定義は、ごくシンプルにかけるのでいいです。

 

レイヤーが簡潔に定義できます

 

Layer01とLayer02は、Convolution>ReLu>MaxPoolingの繰り返しです。

h_conv1 = slim.conv2d(x_image, 32, [5, 5])
h_pool1 = slim.max_pool2d(h_conv1, [2, 2])

 

slim.conv2dは「Convolution(畳み込み)層」ですね。

 

x_imageは、入力データ。

32は、出力チャンネル数。

[5,5]は、フィルターの横と縦のサイズ。

ストライドは、未指定の場合は(1,1)になってます。

 

入力データの各チャネル毎にストライド分だけずらしながら、[5,5]サイズのフィルタで演算ていくわけです。

 

で、各チャネルの出力の和をとって、出力のチャネルの値にするという演算を、出力チャネルの数だけ行う。

 

ざっくり、そんな感じで処理されるみたいです。

 

ただ、出力チャネルの数を、Layer01で32、Layer02で64としていたり、フィルタサイズを[5,5]とかにしているのは、参考にしたチュートリアルの設定を真似ただけです。

 

最適な数値を論理的に説明できるだけの理解は、まだできてません。

 

すいません。

 

max_pool2dの[2,2]もPooling Windowのサイズです。

 

プーリング層で、上記の指定だと、2X2 の中で最大のものを選択して情報圧縮しながら、スライドしていく感じですかね。

 

その後は全結合層で処理していきます。

#Layer03

h_pool2_flat = slim.flatten(h_pool2)
h_fc1 = slim.fully_connected(h_pool2_flat, 1024)

 # Layer04
 y_conv = slim.fully_connected(h_fc1 , 10, activation_fn=None)

 

全結合層は、1次元のインプットに対して、1次元のアウトプットなので、Convolution・Poolingで処理した2次元のデータを1次元に圧縮する必要があります。

 

それをやってくれるのが、「slim.flatten」で、バッチサイズを維持したまま、1次元にフラット化してくれます。

 

あとは、全結合を2回。

 

1回目はデフォルト引数で活性化関数に指定したReLuを使うので特に指定せず、2回めはアウトプットなので活性化関数をNoneにしてるわけですね。

 

で、最終的な出力サイズは「10」。

 

MNISTが、0-9の10分類なので。

 

これでモデルの構築は終わりです。

 

かなり簡潔で記述量も抑えることができます。

 

kerasやNNablaなんかと比べても、遜色ない感じがしますね。

 

普通にtensorflowと融和できます

 

さて、残りの部分です。

 

tf.contrib.slimを使う最大のメリットは、モデルを簡潔に書きつつ、tensorflowの通常の書き方との親和性が高いってことですね。

 

残りの関数(loss、training、accuracy)ですけど、この辺は普通にtensorflowです。

def loss(y_,y_conv):
    return tf.losses.softmax_cross_entropy(y_,y_conv)

 

def training(loss):
    return tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)

 

def accuracy(y_,y_conv):
    correct_prediction = tf.equal(tf.argmax(y_conv,1), tf.argmax(y_,1))
    return tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

 

2018/02/12追記

>tensorflow v1.5にすると、「tf.losses.softmax_cross_entropy(y_,y_conv)」の部分で警告がでます。

>ただ、内容的に内部で呼んでいる「softmax_cross_entropy_with_logits」が将来削除されるみたいなメッセージなので、Google側で修正されるのではないか?と思ったので、特に修正してません。

 

従来の書き方とあまり変わりません。

 

必要な関数はできました。

 

あとは、それを使ったメインの処理ですね。

 

データはMNISTを使います。

 

from tensorflow.examples.tutorials.mnist import input_data

x = tf.placeholder(tf.float32, shape=[None, 784])
y_ = tf.placeholder(tf.float32, shape=[None, 10])
x_image = tf.reshape(x, [-1, 28, 28, 1])

 

model = inference()
cross_entropy = loss(y_,model)
train_step = training(cross_entropy)
accuracy = accuracy(y_,model)

 

sess.run(tf.global_variables_initializer())
for i in range(2000):
    batch = mnist.train.next_batch(100)
    if i%100 == 0:
        train_accuracy = accuracy.eval(feed_dict={
            x:batch[0], y_:batch[1]})
        print("step %d, training accuracy %5f"%(i, train_accuracy))
    train_step.run(feed_dict={x:batch[0], y_:batch[1]})

 

 説明用に、3つのブロックに区切ってます。

 

最初がMNISTのデータを取得しているところ。

 

2つ目が前で定義した関数を使って、グラフを構築しているところ。

 

3つ目が学習・評価を実行しているところです。 

 

 見たままなので、直感的にわかります。

 

まとめ

 

tensorflowなのに、モデルが簡潔に書ける。

 

tf.contrib.slimはいい感じです。

 

でも、tf.contribなので、いつまでも、このままの形ではないでしょうけど。

 

2018/02/12追記

>tensorflow  v1.5では、そのまま使えました。

 

それが、「tf.slim」みたいに、contribから格上げになるのか、「tf.layers」などに吸収されていくのかはわからないですけど、一度使うと、なんか戻れない感じがします。

 

ほんと、どんどん良くなっていくなあ。

 

文系人間にも優しくなって、いよいよ実用フェーズにはいってきた感はありますね。

 

関連情報

tensorflow入門の入門カテゴリの記事一覧はこちらです。

arakan-pgm-ai.hatenablog.com