このページは Cloud Translation API によって翻訳されました。
Switch to English

機能API

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

セットアップ

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

前書き

Keras 関数APIは、 tf.keras.Sequential APIよりも柔軟なモデルを作成する方法です。関数型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を使用して構築されたモデルの場合とまったく同じように機能します。

ここでは、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 [==============================] - 2s 3ms/step - loss: 0.3458 - accuracy: 0.9013 - val_loss: 0.1860 - val_accuracy: 0.9463
Epoch 2/2
750/750 [==============================] - 2s 2ms/step - loss: 0.1588 - accuracy: 0.9524 - val_loss: 0.1242 - val_accuracy: 0.9645
313/313 - 1s - loss: 0.1297 - accuracy: 0.9604
Test loss: 0.12967276573181152
Test accuracy: 0.9603999853134155

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

保存してシリアル化

モデルの保存とシリアル化は、関数型APIを使用して構築されたモデルの場合、 Sequentialモデルの場合と同じように機能します。機能モデルを保存する標準的な方法は、 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")
 
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/ops/resource_variable_ops.py:1817: calling BaseResourceVariable.__init__ (from tensorflow.python.ops.resource_variable_ops) with constraint is deprecated and will be removed in a future version.
Instructions for updating:
If using Keras pass *_constraint arguments to layers.
INFO:tensorflow:Assets written to: path_to_my_model/assets

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

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

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

以下の例では、同じレイヤーのスタックを使用して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 (Model)              (None, 16)                18672     
_________________________________________________________________
decoder (Model)              (None, 28, 28, 1)         9569      
=================================================================
Total params: 28,241
Trainable params: 28,241
Non-trainable params: 0
_________________________________________________________________

ご覧のとおり、モデルは入れ子にすることができます。モデルはサブモデルを含むことができます(モデルはレイヤーのようなものなので)。モデルの入れ子の一般的な使用例は、 エンセンブルです。たとえば、一連のモデルをそれらの予測を平均化する単一のモデルにアンサンブルする方法は次のとおりです。

 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 APIでは処理できません。

たとえば、カスタム発行チケットを優先度でランク付けして正しい部門にルーティングするシステムを構築している場合、モデルには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 [==============================] - 0s 12ms/step - loss: 1.3372 - priority_loss: 0.7055 - department_loss: 3.1586
Epoch 2/2
40/40 [==============================] - 0s 11ms/step - loss: 1.3285 - priority_loss: 0.6998 - department_loss: 3.1438

<tensorflow.python.keras.callbacks.History at 0x7f6ad8133160>

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 APIが処理できないもの。

これの一般的な使用例は、残余接続です。これを示すために、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 [==============================] - 0s 25ms/step - loss: 2.3091 - acc: 0.1037 - val_loss: 2.2991 - val_acc: 0.1300

<tensorflow.python.keras.callbacks.History at 0x7f6ac00f31d0>

共有レイヤー

関数型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 [==============================] - 5s 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とモデルサブクラス化の違いの詳細については、TensorFlow 2.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)))