Python
機械学習
MachineLearning
TensorFlow

TensorFlowの増大するAPIについて少し考えてみた

More than 1 year has passed since last update.

イントロ

ついに TensorFlow Dev SummitにてTensorFLow 1.0 がアナウンスされた.Graph コンパイラ XLA の試験リリースの話題もあるが,個人的には,TensorFlowのAPI (Application Programming Interface) 構成がどうなるかが気になっていた.

(Google Developers Blog "Announcing TensorFlow 1.0" より抜粋.)

  • Higher-level API modules tf.layers, tf.metrics, and tf.losses - brought over from tf.contrib.learn after incorporating skflow and TF Slim

ここ最近,乱立模様を呈していた High-level API について,どう整理されるかversion 1.0に期待していたので,状況を少し調べてみた.

初期のCNNモデルのコード

以前,High-level API が多くなかった状況にて,CNN(Convolutional Neural Network)モデルを扱うにあたり,以下のように自前のクラスを用意してコーディングを行っていた.

#   my_lib_nn.py
#   例えば...  Convolution 2-D Layer
class Convolution2D(object):
    '''
      constructor's args:
          input     : input image (2D matrix)
          input_siz ; input image size
          in_ch     : number of incoming image channel
          out_ch    : number of outgoing image channel
          patch_siz : filter(patch) size
          weights   : (if input) (weights, bias)
    '''
    def __init__(self, input, input_siz, in_ch, out_ch, patch_siz, activation='relu'):
        self.input = input      
        self.rows = input_siz[0]
        self.cols = input_siz[1]
        self.in_ch = in_ch
        self.activation = activation

        wshape = [patch_siz[0], patch_siz[1], in_ch, out_ch]
                w_cv = tf.Variable(tf.truncated_normal(wshape, stddev=0.1), 
                            trainable=True)
        b_cv = tf.Variable(tf.constant(0.1, shape=[out_ch]), 
                            trainable=True)
        self.w = w_cv
        self.b = b_cv
        self.params = [self.w, self.b]

    def output(self):
        shape4D = [-1, self.rows, self.cols, self.in_ch]

        x_image = tf.reshape(self.input, shape4D)  # reshape to 4D tensor
        linout = tf.nn.conv2d(x_image, self.w, 
                  strides=[1, 1, 1, 1], padding='SAME') + self.b
        if self.activation == 'relu':
            self.output = tf.nn.relu(linout)
        elif self.activation == 'sigmoid':
            self.output = tf.sigmoid(linout)
        else:
            self.output = linout

        return self.output

下請けのライブラリとして tf.nn.xxx() の関数を用いるが,それを使いやすくするwrapperを作って利用するやり方である.自作ライブラリはカスタマイズも容易であるが,それがあだとなることもあって,細かいメンテナンスも自分で行わなければならない.(大したライブラリではありませんが...)

Keras を知ってからは「Kerasを使うべきか」と選択肢に入れていたが,慣れや細かいデバッグのしやすさ,柔軟性を考え,TensorFlowライブラリを直接使うコーディングスタイルが多かった.

TensorFlow Slim vs. tf.layers

「薄いTensorFlow wapper」という視点で注目していたのが,"Slim" である.Qiitaでも何件か取り上げられているようである.これを用いてMNISTを分類するためのコードを書くと以下のようになった.

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

mnist = input_data.read_data_sets("../MNIST_data/", one_hot=True)

# Create the model
def my_nn(images, keep_prob):
   net = slim.layers.conv2d(images, 32, [5,5], scope='conv1')
   net = slim.layers.max_pool2d(net, [2,2], scope='pool1')
   net = slim.layers.conv2d(net, 64, [5,5], scope='conv2')
   net = slim.layers.max_pool2d(net, [2,2], scope='pool2')
   net = slim.layers.flatten(net, scope='flatten3')
   net = slim.layers.fully_connected(net, 1024, scope='fully_connected4')
   net = slim.layers.dropout(net, keep_prob)
   net = slim.layers.fully_connected(net, 10, activation_fn=None, 
                                        scope='fully_connected5')
   return net

def inference(x, y_, keep_prob):
    x_image = tf.reshape(x, [-1, 28, 28, 1])
    y_pred = my_nn(x_image, keep_prob)

    slim.losses.softmax_cross_entropy(y_pred, y_)
    total_loss = slim.losses.get_total_loss()
    correct_prediction = tf.equal(tf.argmax(y_pred, 1), tf.argmax(y_, 1))
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

    return total_loss, accuracy, y_pred

Nueral network モデルを短いコードでわかりやすく記述できる.また,関数"inference"内で用いたように,損失関数も slim のAPIで書くことができた.かなり使いやすい印象をもった.

次に今回,TensorFlow 1.0 で用意された tf.layers のモジュールを調べてみた.APIのドキュメントにしっかり説明があったので,それを参考にコーディングを行った.

Fig. TensorFlow APIドキュメントより(イメージ抜粋)
Module_tf_layers.png

上記Googleアナウンスの抜粋にもあるが,tf.contrib.layers というのもドキュメントに記載があるが,今回の tf.layers は別物なので要注意である.

以下は,tf.layersを用いたCNNのコードである.

import tensorflow as tf
from tensorflow.python.layers import layers
from tensorflow.examples.tutorials.mnist import input_data

mnist = input_data.read_data_sets("../MNIST_data/", one_hot=True)

# Create the model
def my_nn(images, drop_rate):
   net = tf.layers.conv2d(images, 32, [5,5], padding='same', 
                                activation=tf.nn.relu, name='conv1')
   net = tf.layers.max_pooling2d(net, pool_size=[2,2], strides=[2,2], 
                                name='pool1')
   net = tf.layers.conv2d(net, 64, [5,5], padding='same', 
                                activation=tf.nn.relu, name='conv2')
   net = tf.layers.max_pooling2d(net, pool_size=[2,2], strides=[2,2], 
                                name='pool2')
   net = tf.reshape(net, [-1, 7*7*64])
   net = tf.layers.dense(net, 1024, activation=tf.nn.relu, name='dense1')
   net = tf.layers.dropout(net, rate=drop_rate)
   net = tf.layers.dense(net, 10, activation=None, name='dense2')
   return net

def inference(x, y_, keep_prob):
    x_image = tf.reshape(x, [-1, 28, 28, 1])
    drop_rate = 1.0 - keep_prob
    y_pred = my_nn(x_image, drop_rate)

    loss = tf.losses.softmax_cross_entropy(y_, y_pred)
    correct_prediction = tf.equal(tf.argmax(y_pred, 1), tf.argmax(y_, 1))
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

    return loss, accuracy, y_pred

当初,「slimみたいなものだろう」という予断を持ってslimのコードから始めたのだが,意外に関数のスペックに違いがあり戸惑った.("my_nn()" 関数内のコードに着目下さい.)

関数の名前違い (max_pool2d <-> max_pooling2d,fully_connected <-> dense) は「ありがち」として受け入れるとして,引数のキーワードが違っていたり,引数のディフォルトに相違があったりと,細かい調整が必要であった.特に気になった(許せない)ポイントとして,Dropout のパラメータとして,処理後にユニット影響を残す割合 "keep_prob" を与える仕様となっていたものを,効果を省く(落とす)割合を与える仕様に変更している点である.(対策として drop_rate = 1.0 - keep_prob の一行を入れています.)プログラマの「好み」で分かれる点なのかも知れないが,ここはこれまでとの互換性を考えて欲しかった...

これでは,TensorFlow API の整理,整頓ということにはなっていないなと,今の仕様に,やや期待外れな印象を持った.

さてどうしよう?

以上の状況を考慮して選択肢をあげてみる.

  • 準備中の Keras2 を待つ.ユーザー数も多いので,APIの洗練度も継続的に向上される期待も大きい.
  • まだ登場直後であることを考慮し, tf.layers, tf.metrics, tf.losses の今後の完成度upに期待する.
    (オープンソースなのだから,「こうした方がいい」とGitHubで積極的に発言していくのがベストですが.)
  • さらに別のAPIを調べてみる.(tf.contrib.layers, TFLearn など.)
  • 自分の持っているクラスライブラリを見捨てず,メンテしながら使っていく. (気になるAPIのいいところを取り入れていくという作戦もとれます.)

Dropoutのパラメータ(残す割合 or 捨てる割合)のように詳細については「好み」が反映されることが多いので,「どれがベストなAPIか」であまり悩んでも仕方ない気もしてきた.今回取り上げたのが,画像を扱うCNN用関数が中心であったが,Deep Learning の柔軟なモデリング能力(例えば RNN や生成系モデルなど)を考えると,APIの細かいところにこだわることなく(適宜,使い分けながら),広範囲な技術内容をフォローしていく方が建設的かも知れない.

(ご意見,アドバイス等ありましたら,コメント承ります.)
(執筆時のプログラミング環境は,以下になります: Python 3.5.2, TensorFlow 1.0.0 )

参考文献,Web site