今日のローカルTensorFlowEverywhereイベントの出欠確認!

カスタムレイヤー

View on TensorFlow.org Run in Google Colab View source on GitHub Download notebook

ニューラルネットワークの構築には、ハイレベルの API である tf.keras を使うことを推奨します。しかしながら、TensorFlow API のほとんどは、eager execution でも使用可能です。

import tensorflow as tf

レイヤー:有用な演算の共通セット

機械学習モデルのコーディングでは、個々の演算やひとつひとつの変数のオペレーションよりは、より高度に抽象化されたオペレーションを行いたいのがほとんどだと思います。

多くの機械学習モデルは、比較的単純なレイヤーの組み合わせや積み重ねによって表現可能です。TensorFlow では、多くの一般的なレイヤーのセットに加えて、アプリケーションに特有なレイヤーを最初から記述したり、既存のレイヤーの組み合わせによって作るための、簡単な方法が提供されています。

TensorFlow には、tf.keras パッケージにKeras APIのすべてが含まれています。Keras のレイヤーは、独自のモデルを構築する際に大変便利です。

# tf.keras.layers パッケージの中では、レイヤーはオブジェクトです。
# レイヤーを構築するためにすることは、単にオブジェクトを作成するだけです。
# ほとんどのレイヤーでは、最初の引数が出力の次元あるいはチャネル数を表します。
layer = tf.keras.layers.Dense(100)
# 入力の次元数は多くの場合不要となっています。それは、レイヤーが最初に使われる際に
# 推定可能だからです。ただし、引数として渡すことで手動で指定することも可能です。
# これは複雑なモデルを構築する場合に役に立つでしょう。
layer = tf.keras.layers.Dense(10, input_shape=(None, 5))

既存のレイヤーのすべての一覧は、ドキュメントを参照してください。Dense(全結合レイヤー)、Conv2D、LSTM、BatchNormalization、Dropoutなどのたくさんのレイヤーが含まれています。

# レイヤーを使うには、単純にcallします。
layer(tf.zeros([10, 5]))
<tf.Tensor: shape=(10, 10), dtype=float32, numpy=
array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]], dtype=float32)>
# レイヤーにはたくさんの便利なメソッドがあります。例えば、`layer.variables`を使って
# レイヤーのすべての変数を調べることができます。訓練可能な変数は、 `layer.trainable_variables`
# でわかります。この例では、全結合レイヤーには重みとバイアスの変数があります。
layer.variables
[<tf.Variable 'dense_1/kernel:0' shape=(5, 10) dtype=float32, numpy=
 array([[ 0.11475551,  0.06970757,  0.3077973 , -0.32507244,  0.24251252,
         -0.03514624, -0.19063246, -0.34674838,  0.02365488, -0.41029423],
        [ 0.4117133 , -0.26640266, -0.52261496, -0.18195471,  0.21620661,
         -0.6023835 , -0.37698945, -0.07256156, -0.3821837 , -0.41075024],
        [ 0.3776303 ,  0.1881805 , -0.23990116, -0.07637244,  0.6027561 ,
          0.00191575, -0.28902376, -0.17382067, -0.08489913,  0.07040226],
        [-0.63206816,  0.23944938, -0.5087077 , -0.17376098,  0.14101517,
         -0.05821729,  0.57915026,  0.04935628,  0.5421639 , -0.5079661 ],
        [-0.3013111 , -0.19906491,  0.26680648, -0.2349402 ,  0.41128987,
          0.62836045,  0.10773581, -0.4004402 ,  0.59058136, -0.3181666 ]],
       dtype=float32)>,
 <tf.Variable 'dense_1/bias:0' shape=(10,) dtype=float32, numpy=array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], dtype=float32)>]
# これらの変数には便利なアクセサを使ってアクセス可能です。
layer.kernel, layer.bias
(<tf.Variable 'dense_1/kernel:0' shape=(5, 10) dtype=float32, numpy=
 array([[ 0.11475551,  0.06970757,  0.3077973 , -0.32507244,  0.24251252,
         -0.03514624, -0.19063246, -0.34674838,  0.02365488, -0.41029423],
        [ 0.4117133 , -0.26640266, -0.52261496, -0.18195471,  0.21620661,
         -0.6023835 , -0.37698945, -0.07256156, -0.3821837 , -0.41075024],
        [ 0.3776303 ,  0.1881805 , -0.23990116, -0.07637244,  0.6027561 ,
          0.00191575, -0.28902376, -0.17382067, -0.08489913,  0.07040226],
        [-0.63206816,  0.23944938, -0.5087077 , -0.17376098,  0.14101517,
         -0.05821729,  0.57915026,  0.04935628,  0.5421639 , -0.5079661 ],
        [-0.3013111 , -0.19906491,  0.26680648, -0.2349402 ,  0.41128987,
          0.62836045,  0.10773581, -0.4004402 ,  0.59058136, -0.3181666 ]],
       dtype=float32)>,
 <tf.Variable 'dense_1/bias:0' shape=(10,) dtype=float32, numpy=array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], dtype=float32)>)

カスタムレイヤーの実装

独自のレイヤーを実装する最良の方法は、tf.keras.Layer クラスを拡張し、下記のメソッドを実装することです。

  • __init__ , 入力に依存しないすべての初期化を行う
  • build, 入力の shape を知った上で、残りの初期化を行う
  • call, フォワード計算を行う

build が呼ばれるまで変数の生成を待つ必要はなく、__init__ で作成できることに注意してください。しかしながら、build で変数を生成することの優位な点は、レイヤーがオペレーションをしようとする入力の shape に基づいて、後から定義できる点です。これに対して、__init__ で変数を生成するということは、そのために必要な shape を明示的に指定する必要があるということです。

class MyDenseLayer(tf.keras.layers.Layer):
  def __init__(self, num_outputs):
    super(MyDenseLayer, self).__init__()
    self.num_outputs = num_outputs

  def build(self, input_shape):
    self.kernel = self.add_variable("kernel", 
                                    shape=[int(input_shape[-1]), 
                                           self.num_outputs])

  def call(self, input):
    return tf.matmul(input, self.kernel)

layer = MyDenseLayer(10)
print(layer(tf.zeros([10, 5])))
print(layer.trainable_variables)
tf.Tensor(
[[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]], shape=(10, 10), dtype=float32)
[<tf.Variable 'my_dense_layer/kernel:0' shape=(5, 10) dtype=float32, numpy=
array([[ 0.2653473 , -0.01079637,  0.3780468 ,  0.37142426,  0.21437526,
        -0.30028182,  0.5738464 , -0.50313574, -0.42932943,  0.33047283],
       [-0.26841736, -0.42909962, -0.24391139,  0.2391457 ,  0.10558498,
        -0.2766319 ,  0.4379242 ,  0.28657913,  0.53846794,  0.4771306 ],
       [-0.43419403,  0.0822345 ,  0.28713316, -0.48987278,  0.5556113 ,
         0.32303274, -0.3706233 ,  0.29805708, -0.46732226,  0.23999768],
       [-0.23843926,  0.29534268, -0.5573072 ,  0.50020033, -0.28149998,
        -0.26134217, -0.17995027,  0.28124225,  0.34874243, -0.35252014],
       [-0.24468058, -0.1452389 ,  0.52247363, -0.16353023, -0.21748093,
        -0.49478307,  0.60995466, -0.04198635, -0.16793266,  0.5774358 ]],
      dtype=float32)>]

/tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/keras/engine/base_layer.py:2281: UserWarning: `layer.add_variable` is deprecated and will be removed in a future version. Please use `layer.add_weight` method instead.
  warnings.warn('`layer.add_variable` is deprecated and '

できるだけ標準のレイヤーを使ったほうが、概してコードは読みやすく保守しやすくなります。コードを読む人は標準的なレイヤーの振る舞いに慣れているからです。tf.keras.layers にはないレイヤーを使いたい場合には、githubのイシューを登録するか、もっとよいのはプルリクエストを送ることです。

モデル:レイヤーの組み合わせ

機械学習では、多くのレイヤーに類するものが、既存のレイヤーを組み合わせることで実装されています。例えば、ResNetの残差ブロックは、畳込み、バッチ正規化とショートカットの組み合わせです。

他のレイヤーからなるレイヤーに類するものを定義する際の主役は、tf.keras.Model クラスです。このクラスを継承することで実装できます。

class ResnetIdentityBlock(tf.keras.Model):
  def __init__(self, kernel_size, filters):
    super(ResnetIdentityBlock, self).__init__(name='')
    filters1, filters2, filters3 = filters

    self.conv2a = tf.keras.layers.Conv2D(filters1, (1, 1))
    self.bn2a = tf.keras.layers.BatchNormalization()

    self.conv2b = tf.keras.layers.Conv2D(filters2, kernel_size, padding='same')
    self.bn2b = tf.keras.layers.BatchNormalization()

    self.conv2c = tf.keras.layers.Conv2D(filters3, (1, 1))
    self.bn2c = tf.keras.layers.BatchNormalization()

  def call(self, input_tensor, training=False):
    x = self.conv2a(input_tensor)
    x = self.bn2a(x, training=training)
    x = tf.nn.relu(x)

    x = self.conv2b(x)
    x = self.bn2b(x, training=training)
    x = tf.nn.relu(x)

    x = self.conv2c(x)
    x = self.bn2c(x, training=training)

    x += input_tensor
    return tf.nn.relu(x)


block = ResnetIdentityBlock(1, [1, 2, 3])
print(block(tf.zeros([1, 2, 3, 3])))
print([x.name for x in block.trainable_variables])
tf.Tensor(
[[[[0. 0. 0.]
   [0. 0. 0.]
   [0. 0. 0.]]

  [[0. 0. 0.]
   [0. 0. 0.]
   [0. 0. 0.]]]], shape=(1, 2, 3, 3), dtype=float32)
['resnet_identity_block/conv2d/kernel:0', 'resnet_identity_block/conv2d/bias:0', 'resnet_identity_block/batch_normalization/gamma:0', 'resnet_identity_block/batch_normalization/beta:0', 'resnet_identity_block/conv2d_1/kernel:0', 'resnet_identity_block/conv2d_1/bias:0', 'resnet_identity_block/batch_normalization_1/gamma:0', 'resnet_identity_block/batch_normalization_1/beta:0', 'resnet_identity_block/conv2d_2/kernel:0', 'resnet_identity_block/conv2d_2/bias:0', 'resnet_identity_block/batch_normalization_2/gamma:0', 'resnet_identity_block/batch_normalization_2/beta:0']

しかし、ほとんどの場合には、モデルはレイヤーを次々に呼び出すことで構成されます。tf.keras.Sequential クラスを使うことで、これをかなり短いコードで実装できます。

my_seq = tf.keras.Sequential([tf.keras.layers.Conv2D(1, (1, 1), 
                                                    input_shape=(
                                                        None, None, 3)),
                             tf.keras.layers.BatchNormalization(),
                             tf.keras.layers.Conv2D(2, 1,
                                                    padding='same'),
                             tf.keras.layers.BatchNormalization(),
                             tf.keras.layers.Conv2D(3, (1, 1)),
                             tf.keras.layers.BatchNormalization()])
my_seq(tf.zeros([1, 2, 3, 3]))
<tf.Tensor: shape=(1, 2, 3, 3), dtype=float32, numpy=
array([[[[0., 0., 0.],
         [0., 0., 0.],
         [0., 0., 0.]],

        [[0., 0., 0.],
         [0., 0., 0.],
         [0., 0., 0.]]]], dtype=float32)>

次のステップ

それでは、前出のノートブックに戻り、線形回帰の例を、レイヤーとモデルを使って、より構造化された形で実装してみてください。