Missed TensorFlow World? Check out the recap. Learn more

カスタムレイヤー

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

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

from __future__ import absolute_import, division, print_function, unicode_literals
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: id=29, 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.57319224, -0.05628675,  0.43850452,  0.2750516 , -0.36089227,
          0.552977  ,  0.43153566, -0.19140887,  0.4837026 , -0.512648  ],
        [-0.4156625 ,  0.48146337,  0.49905092,  0.5967714 ,  0.09800148,
         -0.27804992, -0.595674  ,  0.28840673,  0.04730797,  0.02291167],
        [ 0.42469054, -0.25067505,  0.46670705, -0.2777352 , -0.47733992,
          0.3806426 , -0.5935412 ,  0.62259585,  0.39044803,  0.38674897],
        [ 0.60829324, -0.6305674 , -0.3670636 , -0.52269566, -0.0973742 ,
         -0.04248804,  0.5888346 , -0.12907493, -0.08762616, -0.5177772 ],
        [ 0.36248475,  0.5528315 , -0.15986183, -0.00805593,  0.2327987 ,
         -0.01143813, -0.5837641 , -0.33895817, -0.08444887,  0.21532011]],
       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.57319224, -0.05628675,  0.43850452,  0.2750516 , -0.36089227,
          0.552977  ,  0.43153566, -0.19140887,  0.4837026 , -0.512648  ],
        [-0.4156625 ,  0.48146337,  0.49905092,  0.5967714 ,  0.09800148,
         -0.27804992, -0.595674  ,  0.28840673,  0.04730797,  0.02291167],
        [ 0.42469054, -0.25067505,  0.46670705, -0.2777352 , -0.47733992,
          0.3806426 , -0.5935412 ,  0.62259585,  0.39044803,  0.38674897],
        [ 0.60829324, -0.6305674 , -0.3670636 , -0.52269566, -0.0973742 ,
         -0.04248804,  0.5888346 , -0.12907493, -0.08762616, -0.5177772 ],
        [ 0.36248475,  0.5528315 , -0.15986183, -0.00805593,  0.2327987 ,
         -0.01143813, -0.5837641 , -0.33895817, -0.08444887,  0.21532011]],
       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)
WARNING:tensorflow:From <ipython-input-8-8c935db19b9e>:9: Layer.add_variable (from tensorflow.python.keras.engine.base_layer) is deprecated and will be removed in a future version.
Instructions for updating:
Please use `layer.add_weight` method instead.
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.35459197, -0.32899946, -0.37756562,  0.4932348 , -0.33047476,
        -0.2801204 ,  0.63049155,  0.43156654,  0.42018086, -0.51254   ],
       [ 0.37951833,  0.34136796, -0.09169698,  0.48750573, -0.19557142,
        -0.27532515,  0.13141531,  0.5765063 , -0.03354257,  0.37986082],
       [ 0.58512324, -0.5507328 , -0.5889465 ,  0.45542723,  0.37907666,
        -0.15468118,  0.31584084, -0.6057541 ,  0.40021282, -0.05838645],
       [ 0.11201525,  0.29118305,  0.54187304,  0.0843994 ,  0.50153273,
         0.05179411,  0.13350344, -0.04961056, -0.22379282,  0.49863726],
       [ 0.44033176,  0.47249812, -0.4580816 , -0.5916508 ,  0.47953182,
        -0.0465551 ,  0.06650388,  0.6203458 , -0.03331065, -0.5152447 ]],
      dtype=float32)>]

できるだけ標準のレイヤーを使ったほうが、概してコードは読みやすく保守しやすくなります。コードを読む人は標準的なレイヤーの振る舞いに慣れているからです。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: id=746, 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)>

次のステップ

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