機能API

TensorFlow.orgで表示 GoogleColabで実行 GitHubでソースを表示ノートブックをダウンロードする

セットアップ

import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

前書き

Keras機能APIは、 tf.keras.Sequentialよりも柔軟なモデルを作成する方法です。機能APIは、非線形トポロジ、共有レイヤー、さらには複数の入力または出力を持つモデルを処理できます。

主なアイデアは、深層学習モデルは通常、レイヤーの有向非巡回グラフ(DAG)であるということです。したがって、機能APIは、レイヤーのグラフを作成する方法です。

次のモデルを検討してください。

(input: 784-dimensional vectors)
       ↧
[Dense (64 units, relu activation)]
       ↧
[Dense (64 units, relu activation)]
       ↧
[Dense (10 units, softmax activation)]
       ↧
(output: logits of a probability distribution over 10 classes)

これは3層の基本的なグラフです。機能APIを使用してこのモデルを構築するには、入力ノードを作成することから始めます。

inputs = keras.Input(shape=(784,))

データの形状は784次元のベクトルとして設定されます。各サンプルの形状のみが指定されているため、バッチサイズは常に省略されます。

たとえば、 (32, 32, 3)形状の画像入力がある場合は、次を使用します。

# Just for demonstration purposes.
img_inputs = keras.Input(shape=(32, 32, 3))

返されるinputsには、モデルにフィードする入力データの形状とdtypeに関する情報が含まれています。形状は次のとおりです。

inputs.shape
TensorShape([None, 784])

dtypeは次のとおりです。

inputs.dtype
tf.float32

このinputsオブジェクトでレイヤーを呼び出すことにより、レイヤーのグラフに新しいノードを作成します。

dense = layers.Dense(64, activation="relu")
x = dense(inputs)

「レイヤー呼び出し」アクションは、「入力」から作成したこのレイヤーに矢印を描くようなものです。入力をdenseレイヤーに「渡す」と、出力としてxが取得されます。

レイヤーのグラフにさらにいくつかのレイヤーを追加しましょう。

x = layers.Dense(64, activation="relu")(x)
outputs = layers.Dense(10)(x)

この時点で、レイヤーのグラフで入力と出力を指定することにより、 Modelを作成できます。

model = keras.Model(inputs=inputs, outputs=outputs, name="mnist_model")

モデルの概要がどのように見えるかを確認してみましょう。

model.summary()
Model: "mnist_model"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         [(None, 784)]             0         
_________________________________________________________________
dense (Dense)                (None, 64)                50240     
_________________________________________________________________
dense_1 (Dense)              (None, 64)                4160      
_________________________________________________________________
dense_2 (Dense)              (None, 10)                650       
=================================================================
Total params: 55,050
Trainable params: 55,050
Non-trainable params: 0
_________________________________________________________________

モデルをグラフとしてプロットすることもできます。

keras.utils.plot_model(model, "my_first_model.png")

png

また、オプションで、プロットされたグラフに各レイヤーの入力形状と出力形状を表示します。

keras.utils.plot_model(model, "my_first_model_with_shape_info.png", show_shapes=True)

png

この図とコードはほとんど同じです。コードバージョンでは、接続矢印は呼び出し操作に置き換えられています。

「レイヤーのグラフ」は深層学習モデルの直感的なイメージであり、機能APIはこれを厳密に反映したモデルを作成する方法です。

トレーニング、評価、および推論

トレーニング、評価、推論は、 Sequentialモデルの場合と機能APIを使用して構築されたモデルの場合とまったく同じように機能します。

Modelクラスは、組み込みのトレーニングループ( fit()メソッド)と組み込みの評価ループ( evaluate()メソッド)を提供します。これらのループを簡単にカスタマイズして、教師あり学習( GANなど)を超えたトレーニングルーチンを実装できることに注意してください。

ここでは、MNIST画像データをロードし、それをベクトルに再形成し、モデルをデータに適合させ(検証分割でパフォーマンスを監視しながら)、テストデータでモデルを評価します。

(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()

x_train = x_train.reshape(60000, 784).astype("float32") / 255
x_test = x_test.reshape(10000, 784).astype("float32") / 255

model.compile(
    loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    optimizer=keras.optimizers.RMSprop(),
    metrics=["accuracy"],
)

history = model.fit(x_train, y_train, batch_size=64, epochs=2, validation_split=0.2)

test_scores = model.evaluate(x_test, y_test, verbose=2)
print("Test loss:", test_scores[0])
print("Test accuracy:", test_scores[1])
Epoch 1/2
750/750 [==============================] - 3s 3ms/step - loss: 0.5848 - accuracy: 0.8332 - val_loss: 0.1880 - val_accuracy: 0.9480
Epoch 2/2
750/750 [==============================] - 2s 2ms/step - loss: 0.1699 - accuracy: 0.9503 - val_loss: 0.1490 - val_accuracy: 0.9563
313/313 - 0s - loss: 0.1463 - accuracy: 0.9563
Test loss: 0.14626088738441467
Test accuracy: 0.9563000202178955

詳細については、トレーニングおよび評価ガイドを参照してください。

保存してシリアル化します

モデルの保存とシリアル化は、 Sequentialモデルの場合と同じように機能APIを使用して構築されたモデルの場合と同じように機能します。機能モデルを保存する標準的な方法は、 model.save()を呼び出して、モデル全体を1つのファイルとして保存することです。モデルを作成したコードが使用できなくなった場合でも、後でこのファイルから同じモデルを再作成できます。

この保存されたファイルには、次のものが含まれます。

  • モデルアーキテクチャ
  • モデルの重み値(トレーニング中に学習された)
  • モデルトレーニング構成(ある場合)( compile渡される)
  • オプティマイザーとその状態(ある場合)(中断したところからトレーニングを再開するため)
model.save("path_to_my_model")
del model
# Recreate the exact same model purely from the file:
model = keras.models.load_model("path_to_my_model")
INFO:tensorflow:Assets written to: path_to_my_model/assets

詳細については、モデルのシリアル化と保存のガイドをお読みください。

レイヤーの同じグラフを使用して、複数のモデルを定義します

関数型APIでは、レイヤーのグラフで入力と出力を指定することでモデルが作成されます。つまり、レイヤーの1つのグラフを使用して、複数のモデルを生成できます。

以下の例では、同じレイヤースタックを使用して、2つのモデルをインスタンス化します。画像入力を16次元ベクトルに変換するencoderモデルと、トレーニング用のエンドツーエンドのautoencoder encoderモデルです。

encoder_input = keras.Input(shape=(28, 28, 1), name="img")
x = layers.Conv2D(16, 3, activation="relu")(encoder_input)
x = layers.Conv2D(32, 3, activation="relu")(x)
x = layers.MaxPooling2D(3)(x)
x = layers.Conv2D(32, 3, activation="relu")(x)
x = layers.Conv2D(16, 3, activation="relu")(x)
encoder_output = layers.GlobalMaxPooling2D()(x)

encoder = keras.Model(encoder_input, encoder_output, name="encoder")
encoder.summary()

x = layers.Reshape((4, 4, 1))(encoder_output)
x = layers.Conv2DTranspose(16, 3, activation="relu")(x)
x = layers.Conv2DTranspose(32, 3, activation="relu")(x)
x = layers.UpSampling2D(3)(x)
x = layers.Conv2DTranspose(16, 3, activation="relu")(x)
decoder_output = layers.Conv2DTranspose(1, 3, activation="relu")(x)

autoencoder = keras.Model(encoder_input, decoder_output, name="autoencoder")
autoencoder.summary()
Model: "encoder"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
img (InputLayer)             [(None, 28, 28, 1)]       0         
_________________________________________________________________
conv2d (Conv2D)              (None, 26, 26, 16)        160       
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 24, 24, 32)        4640      
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 8, 8, 32)          0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 6, 6, 32)          9248      
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 4, 4, 16)          4624      
_________________________________________________________________
global_max_pooling2d (Global (None, 16)                0         
=================================================================
Total params: 18,672
Trainable params: 18,672
Non-trainable params: 0
_________________________________________________________________
Model: "autoencoder"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
img (InputLayer)             [(None, 28, 28, 1)]       0         
_________________________________________________________________
conv2d (Conv2D)              (None, 26, 26, 16)        160       
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 24, 24, 32)        4640      
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 8, 8, 32)          0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 6, 6, 32)          9248      
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 4, 4, 16)          4624      
_________________________________________________________________
global_max_pooling2d (Global (None, 16)                0         
_________________________________________________________________
reshape (Reshape)            (None, 4, 4, 1)           0         
_________________________________________________________________
conv2d_transpose (Conv2DTran (None, 6, 6, 16)          160       
_________________________________________________________________
conv2d_transpose_1 (Conv2DTr (None, 8, 8, 32)          4640      
_________________________________________________________________
up_sampling2d (UpSampling2D) (None, 24, 24, 32)        0         
_________________________________________________________________
conv2d_transpose_2 (Conv2DTr (None, 26, 26, 16)        4624      
_________________________________________________________________
conv2d_transpose_3 (Conv2DTr (None, 28, 28, 1)         145       
=================================================================
Total params: 28,241
Trainable params: 28,241
Non-trainable params: 0
_________________________________________________________________

ここで、デコードアーキテクチャはエンコードアーキテクチャと厳密に対称であるため、出力形状は入力形状(28, 28, 1)と同じです。

Conv2D層であるConv2DTranspose層との逆MaxPooling2D層であるUpSampling2D層。

レイヤーと同じように、すべてのモデルを呼び出すことができます

Inputまたは別のレイヤーの出力でモデルを呼び出すことにより、任意のモデルをレイヤーであるかのように扱うことができます。モデルを呼び出すことにより、モデルのアーキテクチャを再利用するだけでなく、その重みも再利用します。

これが実際に動作することを確認するために、エンコーダーモデルとデコーダーモデルを作成し、それらを2つの呼び出しにチェーンしてオートエンコーダーモデルを取得する、オートエンコーダーの例の別の見方を次に示します。

encoder_input = keras.Input(shape=(28, 28, 1), name="original_img")
x = layers.Conv2D(16, 3, activation="relu")(encoder_input)
x = layers.Conv2D(32, 3, activation="relu")(x)
x = layers.MaxPooling2D(3)(x)
x = layers.Conv2D(32, 3, activation="relu")(x)
x = layers.Conv2D(16, 3, activation="relu")(x)
encoder_output = layers.GlobalMaxPooling2D()(x)

encoder = keras.Model(encoder_input, encoder_output, name="encoder")
encoder.summary()

decoder_input = keras.Input(shape=(16,), name="encoded_img")
x = layers.Reshape((4, 4, 1))(decoder_input)
x = layers.Conv2DTranspose(16, 3, activation="relu")(x)
x = layers.Conv2DTranspose(32, 3, activation="relu")(x)
x = layers.UpSampling2D(3)(x)
x = layers.Conv2DTranspose(16, 3, activation="relu")(x)
decoder_output = layers.Conv2DTranspose(1, 3, activation="relu")(x)

decoder = keras.Model(decoder_input, decoder_output, name="decoder")
decoder.summary()

autoencoder_input = keras.Input(shape=(28, 28, 1), name="img")
encoded_img = encoder(autoencoder_input)
decoded_img = decoder(encoded_img)
autoencoder = keras.Model(autoencoder_input, decoded_img, name="autoencoder")
autoencoder.summary()
Model: "encoder"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
original_img (InputLayer)    [(None, 28, 28, 1)]       0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 26, 26, 16)        160       
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 24, 24, 32)        4640      
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 8, 8, 32)          0         
_________________________________________________________________
conv2d_6 (Conv2D)            (None, 6, 6, 32)          9248      
_________________________________________________________________
conv2d_7 (Conv2D)            (None, 4, 4, 16)          4624      
_________________________________________________________________
global_max_pooling2d_1 (Glob (None, 16)                0         
=================================================================
Total params: 18,672
Trainable params: 18,672
Non-trainable params: 0
_________________________________________________________________
Model: "decoder"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
encoded_img (InputLayer)     [(None, 16)]              0         
_________________________________________________________________
reshape_1 (Reshape)          (None, 4, 4, 1)           0         
_________________________________________________________________
conv2d_transpose_4 (Conv2DTr (None, 6, 6, 16)          160       
_________________________________________________________________
conv2d_transpose_5 (Conv2DTr (None, 8, 8, 32)          4640      
_________________________________________________________________
up_sampling2d_1 (UpSampling2 (None, 24, 24, 32)        0         
_________________________________________________________________
conv2d_transpose_6 (Conv2DTr (None, 26, 26, 16)        4624      
_________________________________________________________________
conv2d_transpose_7 (Conv2DTr (None, 28, 28, 1)         145       
=================================================================
Total params: 9,569
Trainable params: 9,569
Non-trainable params: 0
_________________________________________________________________
Model: "autoencoder"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
img (InputLayer)             [(None, 28, 28, 1)]       0         
_________________________________________________________________
encoder (Functional)         (None, 16)                18672     
_________________________________________________________________
decoder (Functional)         (None, 28, 28, 1)         9569      
=================================================================
Total params: 28,241
Trainable params: 28,241
Non-trainable params: 0
_________________________________________________________________

ご覧のとおり、モデルはネストできます。モデルにはサブモデルを含めることができます(モデルはレイヤーのようなものであるため)。モデルの入れ子の一般的な使用例は、アンサンブルです。たとえば、モデルのセットを1つのモデルにまとめて、予測を平均化する方法は次のとおりです。

def get_model():
    inputs = keras.Input(shape=(128,))
    outputs = layers.Dense(1)(inputs)
    return keras.Model(inputs, outputs)


model1 = get_model()
model2 = get_model()
model3 = get_model()

inputs = keras.Input(shape=(128,))
y1 = model1(inputs)
y2 = model2(inputs)
y3 = model3(inputs)
outputs = layers.average([y1, y2, y3])
ensemble_model = keras.Model(inputs=inputs, outputs=outputs)

複雑なグラフトポロジを操作する

複数の入力と出力を持つモデル

機能的なAPIを使用すると、複数の入力と出力を簡単に操作できます。これは、 Sequential処理できません。

たとえば、顧客の発行チケットを優先度でランク付けし、それらを正しい部門にルーティングするシステムを構築している場合、モデルには次の3つの入力があります。

  • チケットのタイトル(テキスト入力)、
  • チケットの本文(テキスト入力)、および
  • ユーザーが追加したタグ(カテゴリ入力)

このモデルには2つの出力があります。

  • 0から1の間の優先度スコア(スカラーシグモイド出力)、および
  • チケットを処理する必要のある部門(部門のセットに対するsoftmax出力)。

機能APIを使用して、このモデルを数行で構築できます。

num_tags = 12  # Number of unique issue tags
num_words = 10000  # Size of vocabulary obtained when preprocessing text data
num_departments = 4  # Number of departments for predictions

title_input = keras.Input(
    shape=(None,), name="title"
)  # Variable-length sequence of ints
body_input = keras.Input(shape=(None,), name="body")  # Variable-length sequence of ints
tags_input = keras.Input(
    shape=(num_tags,), name="tags"
)  # Binary vectors of size `num_tags`

# Embed each word in the title into a 64-dimensional vector
title_features = layers.Embedding(num_words, 64)(title_input)
# Embed each word in the text into a 64-dimensional vector
body_features = layers.Embedding(num_words, 64)(body_input)

# Reduce sequence of embedded words in the title into a single 128-dimensional vector
title_features = layers.LSTM(128)(title_features)
# Reduce sequence of embedded words in the body into a single 32-dimensional vector
body_features = layers.LSTM(32)(body_features)

# Merge all available features into a single large vector via concatenation
x = layers.concatenate([title_features, body_features, tags_input])

# Stick a logistic regression for priority prediction on top of the features
priority_pred = layers.Dense(1, name="priority")(x)
# Stick a department classifier on top of the features
department_pred = layers.Dense(num_departments, name="department")(x)

# Instantiate an end-to-end model predicting both priority and department
model = keras.Model(
    inputs=[title_input, body_input, tags_input],
    outputs=[priority_pred, department_pred],
)

次に、モデルをプロットします。

keras.utils.plot_model(model, "multi_input_and_output_model.png", show_shapes=True)

png

このモデルをコンパイルするときに、各出力に異なる損失を割り当てることができます。各損失に異なる重みを割り当てて、トレーニング損失全体への寄与を調整することもできます。

model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss=[
        keras.losses.BinaryCrossentropy(from_logits=True),
        keras.losses.CategoricalCrossentropy(from_logits=True),
    ],
    loss_weights=[1.0, 0.2],
)

出力レイヤーの名前は異なるため、次のように損失を指定することもできます。

model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss={
        "priority": keras.losses.BinaryCrossentropy(from_logits=True),
        "department": keras.losses.CategoricalCrossentropy(from_logits=True),
    },
    loss_weights=[1.0, 0.2],
)

入力とターゲットのNumPy配列のリストを渡すことにより、モデルをトレーニングします。

# Dummy input data
title_data = np.random.randint(num_words, size=(1280, 10))
body_data = np.random.randint(num_words, size=(1280, 100))
tags_data = np.random.randint(2, size=(1280, num_tags)).astype("float32")

# Dummy target data
priority_targets = np.random.random(size=(1280, 1))
dept_targets = np.random.randint(2, size=(1280, num_departments))

model.fit(
    {"title": title_data, "body": body_data, "tags": tags_data},
    {"priority": priority_targets, "department": dept_targets},
    epochs=2,
    batch_size=32,
)
Epoch 1/2
40/40 [==============================] - 4s 11ms/step - loss: 1.2978 - priority_loss: 0.7067 - department_loss: 2.9554
Epoch 2/2
40/40 [==============================] - 0s 11ms/step - loss: 1.2947 - priority_loss: 0.7023 - department_loss: 2.9621
<tensorflow.python.keras.callbacks.History at 0x7fe18923e6a0>

Datasetオブジェクトを使用してfitを呼び出す場合、 ([title_data, body_data, tags_data], [priority_targets, dept_targets])ようなリストのタプルまたは({'title': title_data, 'body': body_data, 'tags': tags_data}, {'priority': priority_targets, 'department': dept_targets})

詳細な説明については、トレーニングおよび評価ガイドを参照してください。

おもちゃのResNetモデル

複数の入力と出力を持つモデルに加えて、機能APIを使用すると、非線形接続トポロジを簡単に操作できます。これらは、順次接続されていないレイヤーを持つモデルであり、 Sequentialでは処理できません。

これの一般的な使用例は、残りの接続です。これを実証するために、CIFAR10用のおもちゃのResNetモデルを作成してみましょう。

inputs = keras.Input(shape=(32, 32, 3), name="img")
x = layers.Conv2D(32, 3, activation="relu")(inputs)
x = layers.Conv2D(64, 3, activation="relu")(x)
block_1_output = layers.MaxPooling2D(3)(x)

x = layers.Conv2D(64, 3, activation="relu", padding="same")(block_1_output)
x = layers.Conv2D(64, 3, activation="relu", padding="same")(x)
block_2_output = layers.add([x, block_1_output])

x = layers.Conv2D(64, 3, activation="relu", padding="same")(block_2_output)
x = layers.Conv2D(64, 3, activation="relu", padding="same")(x)
block_3_output = layers.add([x, block_2_output])

x = layers.Conv2D(64, 3, activation="relu")(block_3_output)
x = layers.GlobalAveragePooling2D()(x)
x = layers.Dense(256, activation="relu")(x)
x = layers.Dropout(0.5)(x)
outputs = layers.Dense(10)(x)

model = keras.Model(inputs, outputs, name="toy_resnet")
model.summary()
Model: "toy_resnet"
__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
img (InputLayer)                [(None, 32, 32, 3)]  0                                            
__________________________________________________________________________________________________
conv2d_8 (Conv2D)               (None, 30, 30, 32)   896         img[0][0]                        
__________________________________________________________________________________________________
conv2d_9 (Conv2D)               (None, 28, 28, 64)   18496       conv2d_8[0][0]                   
__________________________________________________________________________________________________
max_pooling2d_2 (MaxPooling2D)  (None, 9, 9, 64)     0           conv2d_9[0][0]                   
__________________________________________________________________________________________________
conv2d_10 (Conv2D)              (None, 9, 9, 64)     36928       max_pooling2d_2[0][0]            
__________________________________________________________________________________________________
conv2d_11 (Conv2D)              (None, 9, 9, 64)     36928       conv2d_10[0][0]                  
__________________________________________________________________________________________________
add (Add)                       (None, 9, 9, 64)     0           conv2d_11[0][0]                  
                                                                 max_pooling2d_2[0][0]            
__________________________________________________________________________________________________
conv2d_12 (Conv2D)              (None, 9, 9, 64)     36928       add[0][0]                        
__________________________________________________________________________________________________
conv2d_13 (Conv2D)              (None, 9, 9, 64)     36928       conv2d_12[0][0]                  
__________________________________________________________________________________________________
add_1 (Add)                     (None, 9, 9, 64)     0           conv2d_13[0][0]                  
                                                                 add[0][0]                        
__________________________________________________________________________________________________
conv2d_14 (Conv2D)              (None, 7, 7, 64)     36928       add_1[0][0]                      
__________________________________________________________________________________________________
global_average_pooling2d (Globa (None, 64)           0           conv2d_14[0][0]                  
__________________________________________________________________________________________________
dense_6 (Dense)                 (None, 256)          16640       global_average_pooling2d[0][0]   
__________________________________________________________________________________________________
dropout (Dropout)               (None, 256)          0           dense_6[0][0]                    
__________________________________________________________________________________________________
dense_7 (Dense)                 (None, 10)           2570        dropout[0][0]                    
==================================================================================================
Total params: 223,242
Trainable params: 223,242
Non-trainable params: 0
__________________________________________________________________________________________________

モデルをプロットします。

keras.utils.plot_model(model, "mini_resnet.png", show_shapes=True)

png

次に、モデルをトレーニングします。

(x_train, y_train), (x_test, y_test) = keras.datasets.cifar10.load_data()

x_train = x_train.astype("float32") / 255.0
x_test = x_test.astype("float32") / 255.0
y_train = keras.utils.to_categorical(y_train, 10)
y_test = keras.utils.to_categorical(y_test, 10)

model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss=keras.losses.CategoricalCrossentropy(from_logits=True),
    metrics=["acc"],
)
# We restrict the data to the first 1000 samples so as to limit execution time
# on Colab. Try to train on the entire dataset until convergence!
model.fit(x_train[:1000], y_train[:1000], batch_size=64, epochs=1, validation_split=0.2)
Downloading data from https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
170500096/170498071 [==============================] - 6s 0us/step
13/13 [==============================] - 2s 35ms/step - loss: 2.2992 - acc: 0.1273 - val_loss: 2.2629 - val_acc: 0.1850
<tensorflow.python.keras.callbacks.History at 0x7fe210396ef0>

共有レイヤー

機能APIのもう1つの良い使用法は、共有レイヤーを使用するモデルです。共有レイヤーは、同じモデルで複数回再利用されるレイヤーインスタンスです。レイヤーのグラフ内の複数のパスに対応する機能を学習します。

共有レイヤーは、類似したスペース(たとえば、類似した語彙を特徴とする2つの異なるテキスト)からの入力をエンコードするためによく使用されます。これらは、これらの異なる入力間で情報を共有することを可能にし、より少ないデータでそのようなモデルをトレーニングすることを可能にします。特定の単語が入力の1つに含まれている場合、共有レイヤーを通過するすべての入力の処理に役立ちます。

機能APIでレイヤーを共有するには、同じレイヤーインスタンスを複数回呼び出します。たとえば、2つの異なるテキスト入力間で共有されるEmbeddingレイヤーは次のとおりです。

# Embedding for 1000 unique words mapped to 128-dimensional vectors
shared_embedding = layers.Embedding(1000, 128)

# Variable-length sequence of integers
text_input_a = keras.Input(shape=(None,), dtype="int32")

# Variable-length sequence of integers
text_input_b = keras.Input(shape=(None,), dtype="int32")

# Reuse the same layer to encode both inputs
encoded_input_a = shared_embedding(text_input_a)
encoded_input_b = shared_embedding(text_input_b)

レイヤーのグラフでノードを抽出して再利用する

操作しているレイヤーのグラフは静的なデータ構造であるため、アクセスして検査することができます。そして、これが機能モデルを画像としてプロットする方法です。

これは、中間レイヤー(グラフの「ノード」)のアクティベーションにアクセスして、他の場所で再利用できることも意味します。これは、特徴抽出などに非常に役立ちます。

例を見てみましょう。これは、ImageNetで事前トレーニングされた重みを持つVGG19モデルです。

vgg19 = tf.keras.applications.VGG19()
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/vgg19/vgg19_weights_tf_dim_ordering_tf_kernels.h5
574717952/574710816 [==============================] - 7s 0us/step

そして、これらは、グラフデータ構造をクエリすることによって取得されたモデルの中間アクティベーションです。

features_list = [layer.output for layer in vgg19.layers]

これらの特徴を使用して、中間層のアクティベーションの値を返す新しい特徴抽出モデルを作成します。

feat_extraction_model = keras.Model(inputs=vgg19.input, outputs=features_list)

img = np.random.random((1, 224, 224, 3)).astype("float32")
extracted_features = feat_extraction_model(img)

これは、特にニューラルスタイルの転送などのタスクに役立ちます。

カスタムレイヤーを使用してAPIを拡張する

tf.kerasは、次のようなさまざまなtf.keras含まれています。

  • 畳み込み層: Conv1DConv2DConv3DConv2DTranspose
  • プーリングレイヤー: MaxPooling1DMaxPooling2DMaxPooling3DAveragePooling1D
  • RNNレイヤー: GRULSTMConvLSTM2D
  • BatchNormalizationDropoutEmbeddingなど。

ただし、必要なものが見つからない場合は、独自のレイヤーを作成してAPIを簡単に拡張できます。すべてのレイヤーは、 Layerクラスをサブクラス化し、以下を実装します。

  • レイヤーによって実行される計算を指定するメソッドをcallます。
  • レイヤーの重みを作成buildメソッド( __init__でも重みを作成できるため、これは単なるスタイル規則です)。

レイヤーを最初から作成する方法の詳細については、カスタムレイヤーとモデルガイドをご覧ください。

以下は、 tf.keras.layers.Dense基本的な実装です。

class CustomDense(layers.Layer):
    def __init__(self, units=32):
        super(CustomDense, self).__init__()
        self.units = units

    def build(self, input_shape):
        self.w = self.add_weight(
            shape=(input_shape[-1], self.units),
            initializer="random_normal",
            trainable=True,
        )
        self.b = self.add_weight(
            shape=(self.units,), initializer="random_normal", trainable=True
        )

    def call(self, inputs):
        return tf.matmul(inputs, self.w) + self.b


inputs = keras.Input((4,))
outputs = CustomDense(10)(inputs)

model = keras.Model(inputs, outputs)

カスタムレイヤーでシリアル化をサポートするには、レイヤーインスタンスのコンストラクター引数を返すget_configメソッドを定義します。

class CustomDense(layers.Layer):
    def __init__(self, units=32):
        super(CustomDense, self).__init__()
        self.units = units

    def build(self, input_shape):
        self.w = self.add_weight(
            shape=(input_shape[-1], self.units),
            initializer="random_normal",
            trainable=True,
        )
        self.b = self.add_weight(
            shape=(self.units,), initializer="random_normal", trainable=True
        )

    def call(self, inputs):
        return tf.matmul(inputs, self.w) + self.b

    def get_config(self):
        return {"units": self.units}


inputs = keras.Input((4,))
outputs = CustomDense(10)(inputs)

model = keras.Model(inputs, outputs)
config = model.get_config()

new_model = keras.Model.from_config(config, custom_objects={"CustomDense": CustomDense})

オプションで、構成ディクショナリを指定してレイヤーインスタンスを再作成するときに使用されるクラスメソッドfrom_config(cls, config)を実装します。 from_configのデフォルトの実装はfrom_configです。

def from_config(cls, config):
  return cls(**config)

機能APIを使用する場合

Keras関数APIを使用して新しいモデルを作成する必要がありますか、それともModelクラスを直接サブクラス化する必要がありますか?一般に、機能APIはより高レベルで、より簡単で安全であり、サブクラス化されたモデルがサポートしていない多くの機能を備えています。

ただし、モデルのサブクラス化は、レイヤーの有向非巡回グラフとして簡単に表現できないモデルを構築するときに、より大きな柔軟性を提供します。たとえば、機能APIを使用してTree-RNNを実装することはできず、 Model直接サブクラス化する必要があります。

機能APIとモデルサブクラスの違いの詳細については、 「TensorFlow2.0のシンボリックAPIと命令型APIとは」をご覧ください。

機能的なAPIの強み:

次のプロパティは、シーケンシャルモデル(データ構造でもあります)にも当てはまりますが、サブクラス化されたモデル(Pythonバイトコードであり、データ構造ではありません)には当てはまりません。

冗長性が少ない

super(MyClass, self).__init__(...)def call(self, ...):などはありません。

比較:

inputs = keras.Input(shape=(32,))
x = layers.Dense(64, activation='relu')(inputs)
outputs = layers.Dense(10)(x)
mlp = keras.Model(inputs, outputs)

サブクラス化されたバージョンの場合:

class MLP(keras.Model):

  def __init__(self, **kwargs):
    super(MLP, self).__init__(**kwargs)
    self.dense_1 = layers.Dense(64, activation='relu')
    self.dense_2 = layers.Dense(10)

  def call(self, inputs):
    x = self.dense_1(inputs)
    return self.dense_2(x)

# Instantiate the model.
mlp = MLP()
# Necessary to create the model's state.
# The model doesn't have a state until it's called at least once.
_ = mlp(tf.zeros((1, 32)))

接続グラフを定義する際のモデル検証

機能APIでは、入力仕様(形状とdtype)が事前に作成されます( Inputを使用)。レイヤーを呼び出すたびに、レイヤーは渡された仕様がその仮定と一致することを確認し、一致しない場合は有用なエラーメッセージを表示します。

これにより、機能APIを使用して構築できるすべてのモデルが実行されることが保証されます。コンバージェンス関連のデバッグを除くすべてのデバッグは、実行時ではなく、モデルの構築中に静的に行われます。これは、コンパイラの型チェックに似ています。

機能モデルはプロット可能で検査可能です

モデルをグラフとしてプロットでき、このグラフの中間ノードに簡単にアクセスできます。たとえば、中間層のアクティベーションを抽出して再利用するには(前の例で見たように):

features_list = [layer.output for layer in vgg19.layers]
feat_extraction_model = keras.Model(inputs=vgg19.input, outputs=features_list)

機能モデルはシリアル化または複製できます

機能モデルはコードではなくデータ構造であるため、安全にシリアル化でき、元のコードにアクセスしなくてもまったく同じモデルを再作成できる単一のファイルとして保存できます。シリアル化と保存のガイドを参照してください

サブクラスモデルをシリアライズするためには、実装者が指定する必要があるget_config()from_config()モデルのレベルでの方法を。

機能的なAPIの弱点:

動的アーキテクチャをサポートしていません

機能APIは、モデルをレイヤーのDAGとして扱います。これはほとんどのディープラーニングアーキテクチャに当てはまりますが、すべてではありません。たとえば、再帰型ネットワークやツリーRNNはこの仮定に従わず、機能APIに実装できません。

ミックスアンドマッチAPIスタイル

機能APIまたはモデルサブクラス化のどちらを選択するかは、モデルの1つのカテゴリに制限するバイナリ決定ではありません。 tf.keras APIのすべてのモデルは、 Sequentialモデル、機能モデル、またはゼロから作成されたサブクラス化されたモデルであるかどうかに関係なく、相互に対話できます。

サブクラス化されたモデルまたはレイヤーの一部として、機能モデルまたはSequentialモデルをいつでも使用できます。

units = 32
timesteps = 10
input_dim = 5

# Define a Functional model
inputs = keras.Input((None, units))
x = layers.GlobalAveragePooling1D()(inputs)
outputs = layers.Dense(1)(x)
model = keras.Model(inputs, outputs)


class CustomRNN(layers.Layer):
    def __init__(self):
        super(CustomRNN, self).__init__()
        self.units = units
        self.projection_1 = layers.Dense(units=units, activation="tanh")
        self.projection_2 = layers.Dense(units=units, activation="tanh")
        # Our previously-defined Functional model
        self.classifier = model

    def call(self, inputs):
        outputs = []
        state = tf.zeros(shape=(inputs.shape[0], self.units))
        for t in range(inputs.shape[1]):
            x = inputs[:, t, :]
            h = self.projection_1(x)
            y = h + self.projection_2(state)
            state = y
            outputs.append(y)
        features = tf.stack(outputs, axis=1)
        print(features.shape)
        return self.classifier(features)


rnn_model = CustomRNN()
_ = rnn_model(tf.zeros((1, timesteps, input_dim)))
(1, 10, 32)

次のパターンのいずれかに従うcallメソッドを実装している限り、機能APIで任意のサブクラス化されたレイヤーまたはモデルを使用できます。

  • call(self, inputs, **kwargs) -ここで、 inputsはテンソルまたはテンソルのネストされた構造(たとえば、テンソルのリスト)であり、 **kwargsは非テンソル引数(非入力)です。
  • call(self, inputs, training=None, **kwargs) -ここで、 trainingは、レイヤーがトレーニングモードと推論モードのどちらで動作するかを示すブール値です。
  • call(self, inputs, mask=None, **kwargs) -ここで、 maskはブールマスクテンソルです(たとえば、RNNに役立ちます)。
  • call(self, inputs, training=None, mask=None, **kwargs) -もちろん、マスキングとトレーニング固有の動作の両方を同時に行うことができます。

さらに、カスタムレイヤーまたはモデルにget_configメソッドを実装した場合でも、作成した機能モデルはシリアル化およびクローン化できます。

これは、機能モデルで使用されている、ゼロから作成されたカスタムRNNの簡単な例です。

units = 32
timesteps = 10
input_dim = 5
batch_size = 16


class CustomRNN(layers.Layer):
    def __init__(self):
        super(CustomRNN, self).__init__()
        self.units = units
        self.projection_1 = layers.Dense(units=units, activation="tanh")
        self.projection_2 = layers.Dense(units=units, activation="tanh")
        self.classifier = layers.Dense(1)

    def call(self, inputs):
        outputs = []
        state = tf.zeros(shape=(inputs.shape[0], self.units))
        for t in range(inputs.shape[1]):
            x = inputs[:, t, :]
            h = self.projection_1(x)
            y = h + self.projection_2(state)
            state = y
            outputs.append(y)
        features = tf.stack(outputs, axis=1)
        return self.classifier(features)


# Note that you specify a static batch size for the inputs with the `batch_shape`
# arg, because the inner computation of `CustomRNN` requires a static batch size
# (when you create the `state` zeros tensor).
inputs = keras.Input(batch_shape=(batch_size, timesteps, input_dim))
x = layers.Conv1D(32, 3)(inputs)
outputs = CustomRNN()(x)

model = keras.Model(inputs, outputs)

rnn_model = CustomRNN()
_ = rnn_model(tf.zeros((1, 10, 5)))