このページは 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 2ms/step - loss: 0.3475 - accuracy: 0.9017 - val_loss: 0.2012 - val_accuracy: 0.9401
Epoch 2/2
750/750 [==============================] - 2s 2ms/step - loss: 0.1524 - accuracy: 0.9552 - val_loss: 0.1336 - val_accuracy: 0.9619
313/313 - 0s - loss: 0.1297 - accuracy: 0.9610
Test loss: 0.12966538965702057
Test accuracy: 0.9610000252723694

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

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

モデルの保存とシリアル化は、 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")
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/training/tracking/tracking.py:111: Model.state_updates (from tensorflow.python.keras.engine.training) is deprecated and will be removed in a future version.
Instructions for updating:
This property should not be used in TensorFlow 2.0, as updates are applied automatically.
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/training/tracking/tracking.py:111: Layer.updates (from tensorflow.python.keras.engine.base_layer) is deprecated and will be removed in a future version.
Instructions for updating:
This property should not be used in TensorFlow 2.0, as updates are applied automatically.
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 (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 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 11ms/step - loss: 1.3184 - priority_loss: 0.7015 - department_loss: 3.0842
Epoch 2/2
40/40 [==============================] - 0s 10ms/step - loss: 1.3183 - priority_loss: 0.6988 - department_loss: 3.0971

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

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が処理できない、 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 [==============================] - 11s 0us/step
13/13 [==============================] - 0s 20ms/step - loss: 2.3143 - acc: 0.1013 - val_loss: 2.3050 - val_acc: 0.1150

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

共有レイヤー

機能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 [==============================] - 15s 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層: 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)))