カスタムレイヤー

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: 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.57394135, -0.34835398, -0.5605644 ,  0.22733498,  0.30396694,
         -0.08283782,  0.06671256, -0.18042704,  0.20183098,  0.08668685],
        [ 0.10939723, -0.29115906, -0.57065535, -0.20901337, -0.30996203,
          0.33914578,  0.38437742,  0.14241749,  0.01954377,  0.14185292],
        [ 0.34906495,  0.21819252, -0.548559  , -0.45001048,  0.6291463 ,
          0.2969857 , -0.6053269 ,  0.6229605 ,  0.5777176 , -0.26025695],
        [-0.5260139 , -0.09794736, -0.49369514, -0.39802817,  0.07894206,
          0.1885035 , -0.25163257, -0.40991986, -0.42845607,  0.25746673],
        [-0.5828061 , -0.43446892,  0.11215198,  0.3980921 , -0.3084484 ,
          0.16152322,  0.33866638, -0.51852196,  0.32609302, -0.18204549]],
       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.57394135, -0.34835398, -0.5605644 ,  0.22733498,  0.30396694,
         -0.08283782,  0.06671256, -0.18042704,  0.20183098,  0.08668685],
        [ 0.10939723, -0.29115906, -0.57065535, -0.20901337, -0.30996203,
          0.33914578,  0.38437742,  0.14241749,  0.01954377,  0.14185292],
        [ 0.34906495,  0.21819252, -0.548559  , -0.45001048,  0.6291463 ,
          0.2969857 , -0.6053269 ,  0.6229605 ,  0.5777176 , -0.26025695],
        [-0.5260139 , -0.09794736, -0.49369514, -0.39802817,  0.07894206,
          0.1885035 , -0.25163257, -0.40991986, -0.42845607,  0.25746673],
        [-0.5828061 , -0.43446892,  0.11215198,  0.3980921 , -0.3084484 ,
          0.16152322,  0.33866638, -0.51852196,  0.32609302, -0.18204549]],
       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.23194867, -0.4782997 ,  0.22690165, -0.01116008,  0.36611348,
        -0.1257307 , -0.42811874, -0.5670624 , -0.03915799, -0.3523668 ],
       [ 0.09017551, -0.47409618,  0.04921108,  0.37499267,  0.21028286,
        -0.4506219 ,  0.5931898 , -0.23534098, -0.62814474,  0.54720026],
       [-0.12562442,  0.23202819, -0.21531907,  0.07974666, -0.41746247,
        -0.11427164,  0.42008692, -0.19591913,  0.12765676, -0.5406865 ],
       [ 0.36046582,  0.26440108, -0.26986358,  0.09227258, -0.6190279 ,
         0.19952631, -0.5762475 ,  0.17766339, -0.5474585 ,  0.4721858 ],
       [-0.41938215,  0.3048584 ,  0.47793108,  0.5757049 , -0.03609377,
        -0.61189586,  0.5675399 ,  0.1344353 ,  0.3864035 ,  0.55143684]],
      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: 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)>

次のステップ

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