![]() | ![]() | ![]() | ![]() |
セットアップ
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")
また、オプションで、プロットされたグラフに各レイヤーの入力形状と出力形状を表示します。
keras.utils.plot_model(model, "my_first_model_with_shape_info.png", show_shapes=True)
この図とコードはほとんど同じです。コードバージョンでは、接続矢印は呼び出し操作に置き換えられています。
「レイヤーのグラフ」は深層学習モデルの直感的なイメージであり、機能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)
このモデルをコンパイルするときに、各出力に異なる損失を割り当てることができます。各損失に異なる重みを割り当てて、トレーニング損失全体への寄与を調整することもできます。
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)
次に、モデルをトレーニングします。
(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
含まれています。
- 畳み込み層:
Conv1D
、Conv2D
、Conv3D
、Conv2DTranspose
- プーリングレイヤー:
MaxPooling1D
、MaxPooling2D
、MaxPooling3D
、AveragePooling1D
- RNNレイヤー:
GRU
、LSTM
、ConvLSTM2D
-
BatchNormalization
、Dropout
、Embedding
など。
ただし、必要なものが見つからない場合は、独自のレイヤーを作成して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)))