Diese Seite wurde von der Cloud Translation API übersetzt.
Switch to English

Die funktionale API

Ansicht auf TensorFlow.org In Google Colab ausführen Quelle auf GitHub anzeigen Notizbuch herunterladen

Konfiguration

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

Einführung

Mit der Keras- Funktions-API können Modelle erstellt werden, die flexibler sind als die tf.keras.Sequential API. Die funktionale API kann Modelle mit nichtlinearer Topologie, gemeinsam genutzten Ebenen und sogar mehreren Ein- oder Ausgängen verarbeiten.

Die Hauptidee ist, dass ein Deep-Learning-Modell normalerweise ein gerichteter azyklischer Graph (DAG) von Schichten ist. Die funktionale API ist also eine Möglichkeit, Diagramme von Ebenen zu erstellen.

Betrachten Sie das folgende Modell:

(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)

Dies ist ein grundlegendes Diagramm mit drei Ebenen. Um dieses Modell mithilfe der Funktions-API zu erstellen, erstellen Sie zunächst einen Eingabeknoten:

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

Die Form der Daten wird als 784-dimensionaler Vektor festgelegt. Die Chargengröße wird immer weggelassen, da nur die Form jeder Probe angegeben wird.

Wenn Sie beispielsweise eine Bildeingabe mit der Form (32, 32, 3) , würden Sie Folgendes verwenden:

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

Die zurückgegebenen inputs enthalten Informationen zu Form und dtype der Eingabedaten, die Sie Ihrem Modell zuführen. Hier ist die Form:

inputs.shape
TensorShape([None, 784])

Hier ist der dtype:

inputs.dtype
tf.float32

Sie erstellen einen neuen Knoten im Diagramm der Ebenen, indem Sie eine Ebene für dieses inputs aufrufen:

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

Die Aktion "Ebenenaufruf" ähnelt dem Zeichnen eines Pfeils von "Eingaben" zu dieser von Ihnen erstellten Ebene. Sie "übergeben" die Eingaben an die dense Ebene und erhalten x als Ausgabe.

Fügen wir dem Diagramm der Ebenen noch einige weitere Ebenen hinzu:

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

Zu diesem Zeitpunkt können Sie ein Model erstellen, indem Sie seine Ein- und Ausgänge im Layerdiagramm angeben:

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

Schauen wir uns an, wie die Modellzusammenfassung aussieht:

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
_________________________________________________________________

Sie können das Modell auch als Diagramm darstellen:

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

png

Optional können Sie die Eingabe- und Ausgabeformen jeder Ebene im Diagramm anzeigen:

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

png

Diese Figur und der Code sind fast identisch. In der Codeversion werden die Verbindungspfeile durch die Aufrufoperation ersetzt.

Ein "Graph of Layers" ist ein intuitives mentales Bild für ein Deep-Learning-Modell, und die funktionale API ist eine Möglichkeit, Modelle zu erstellen, die dies genau widerspiegeln.

Training, Bewertung und Inferenz

Training, Evaluierung und Inferenz funktionieren für Modelle, die mit der funktionalen API erstellt wurden, genauso wie für Sequential Modelle.

Laden Sie hier die MNIST-Bilddaten, formen Sie sie in Vektoren um, passen Sie das Modell an die Daten an (während Sie die Leistung bei einer Validierungsaufteilung überwachen) und bewerten Sie das Modell anhand der Testdaten:

(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])
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11493376/11490434 [==============================] - 0s 0us/step
Epoch 1/2
750/750 [==============================] - 2s 2ms/step - loss: 0.3423 - accuracy: 0.9006 - val_loss: 0.1740 - val_accuracy: 0.9482
Epoch 2/2
750/750 [==============================] - 2s 2ms/step - loss: 0.1585 - accuracy: 0.9531 - val_loss: 0.1326 - val_accuracy: 0.9607
313/313 - 0s - loss: 0.1313 - accuracy: 0.9608
Test loss: 0.13132457435131073
Test accuracy: 0.9607999920845032

Weitere Informationen finden Sie im Schulungs- und Bewertungshandbuch .

Speichern und serialisieren

Das Speichern des Modells und die Serialisierung funktionieren für Modelle, die mit der Funktions-API erstellt wurden, genauso wie für Sequential Modelle. Die Standardmethode zum Speichern eines Funktionsmodells besteht darin, model.save() , um das gesamte Modell als einzelne Datei zu speichern. Sie können später dasselbe Modell aus dieser Datei neu erstellen, auch wenn der Code, mit dem das Modell erstellt wurde, nicht mehr verfügbar ist.

Diese gespeicherte Datei enthält:

  • Modellarchitektur
  • Modellgewichtswerte (die während des Trainings gelernt wurden)
  • gegebenenfalls Modell-Trainingskonfiguration (wie zum compile )
  • Optimierer und sein Status, falls vorhanden (um das Training dort neu zu starten, wo Sie aufgehört haben)
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

Weitere Informationen finden Sie im Handbuch zur Serialisierung und zum Speichern von Modellen.

Verwenden Sie dasselbe Ebenendiagramm, um mehrere Modelle zu definieren

In der funktionalen API werden Modelle erstellt, indem ihre Ein- und Ausgaben in einem Diagramm von Ebenen angegeben werden. Das bedeutet, dass ein einzelnes Diagramm von Ebenen verwendet werden kann, um mehrere Modelle zu generieren.

Im folgenden Beispiel verwenden Sie denselben Ebenenstapel, um zwei Modelle zu instanziieren: ein encoder Modell, das autoencoder in 16-dimensionale Vektoren autoencoder , und ein End-to-End- autoencoder Modell für das Training.

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
_________________________________________________________________

Hier ist die Decodierungsarchitektur streng symmetrisch zur Codierungsarchitektur, sodass die Ausgabeform dieselbe ist wie die Eingabeform (28, 28, 1) .

Die Rückseite einer Conv2D Ebene ist eine Conv2DTranspose Ebene, und die Rückseite einer MaxPooling2D Ebene ist eine UpSampling2D Ebene.

Alle Modelle sind wie Ebenen aufrufbar

Sie können jedes Modell so behandeln, als wäre es eine Ebene, indem Sie es auf einer Input oder auf der Ausgabe einer anderen Ebene aufrufen. Wenn Sie ein Modell aufrufen, verwenden Sie nicht nur die Architektur des Modells, sondern auch dessen Gewichte.

Um dies in Aktion zu sehen, sehen Sie sich das Autoencoder-Beispiel anders an, bei dem ein Encoder-Modell, ein Decoder-Modell, erstellt und in zwei Aufrufen verkettet wird, um das Autoencoder-Modell zu erhalten:

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
_________________________________________________________________

Wie Sie sehen, kann das Modell verschachtelt sein: Ein Modell kann Untermodelle enthalten (da ein Modell wie eine Ebene ist). Ein häufiger Anwendungsfall für das Modell Verschachtelung wird ensembling. Hier erfahren Sie beispielsweise, wie Sie eine Reihe von Modellen zu einem einzigen Modell zusammenfassen, das ihre Vorhersagen mittelt:

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)

Bearbeiten Sie komplexe Graphentopologien

Modelle mit mehreren Ein- und Ausgängen

Die funktionale API erleichtert die Bearbeitung mehrerer Ein- und Ausgänge. Dies kann mit der Sequential API nicht behandelt werden.

Wenn Sie beispielsweise ein System erstellen, mit dem Kundenausgabetickets nach Priorität eingestuft und an die richtige Abteilung weitergeleitet werden, verfügt das Modell über drei Eingaben:

  • den Titel des Tickets (Texteingabe),
  • den Textkörper des Tickets (Texteingabe) und
  • vom Benutzer hinzugefügte Tags (kategoriale Eingabe)

Dieses Modell hat zwei Ausgänge:

  • die Prioritätsbewertung zwischen 0 und 1 (skalare Sigmoidausgabe) und
  • die Abteilung, die das Ticket bearbeiten soll (Softmax-Ausgabe über die Gruppe von Abteilungen).

Mit der funktionalen API können Sie dieses Modell in wenigen Zeilen erstellen:

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],
)

Zeichnen Sie nun das Modell:

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

png

Bei der Kompilierung dieses Modells können Sie jedem Ausgang unterschiedliche Verluste zuweisen. Sie können jedem Verlust sogar unterschiedliche Gewichte zuweisen, um ihren Beitrag zum gesamten Trainingsverlust zu modulieren.

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],
)

Da die Ausgabeebenen unterschiedliche Namen haben, können Sie den Verlust auch folgendermaßen angeben:

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],
)

Trainieren Sie das Modell, indem Sie Listen mit NumPy-Arrays von Eingaben und Zielen übergeben:

# 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.3092 - priority_loss: 0.7020 - department_loss: 3.0361
Epoch 2/2
40/40 [==============================] - 0s 10ms/step - loss: 1.2944 - priority_loss: 0.6983 - department_loss: 2.9801

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

Wenn Sie fit mit einem Dataset Objekt aufrufen, sollte es entweder ein Tupel von Listen wie ([title_data, body_data, tags_data], [priority_targets, dept_targets]) oder ein Tupel von Wörterbüchern wie ({'title': title_data, 'body': body_data, 'tags': tags_data}, {'priority': priority_targets, 'department': dept_targets}) .

Ausführlichere Erläuterungen finden Sie im Schulungs- und Bewertungshandbuch .

Ein Spielzeug-ResNet-Modell

Zusätzlich zu Modellen mit mehreren Ein- und Ausgängen erleichtert die funktionale API die Bearbeitung nichtlinearer Konnektivitätstopologien. Hierbei handelt es sich um Modelle mit Schichten, die nicht sequentiell verbunden sind und die die Sequential API nicht verarbeiten kann.

Ein häufiger Anwendungsfall hierfür sind Restverbindungen. Erstellen wir ein Spielzeug-ResNet-Modell für CIFAR10, um dies zu demonstrieren:

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
__________________________________________________________________________________________________

Zeichnen Sie das Modell:

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

png

Trainieren Sie nun das Modell:

(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 22ms/step - loss: 2.3002 - acc: 0.1412 - val_loss: 2.3585 - val_acc: 0.1300

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

Geteilte Schichten

Eine weitere gute Verwendung für die funktionale API sind Modelle, die gemeinsam genutzte Ebenen verwenden . Freigegebene Ebenen sind Ebeneninstanzen, die im selben Modell mehrmals wiederverwendet werden. Sie lernen Funktionen, die mehreren Pfaden im Diagramm der Ebenen entsprechen.

Gemeinsame Ebenen werden häufig verwendet, um Eingaben aus ähnlichen Räumen zu codieren (z. B. zwei verschiedene Textteile mit ähnlichem Vokabular). Sie ermöglichen den Austausch von Informationen über diese verschiedenen Eingaben hinweg und ermöglichen es, ein solches Modell mit weniger Daten zu trainieren. Wenn ein bestimmtes Wort in einer der Eingaben angezeigt wird, wirkt sich dies auf die Verarbeitung aller Eingaben aus, die die gemeinsam genutzte Schicht durchlaufen.

Um eine Ebene in der Funktions-API freizugeben, rufen Sie dieselbe Ebeneninstanz mehrmals auf. Hier ist beispielsweise eine Embedding , die von zwei verschiedenen Texteingaben gemeinsam genutzt wird:

# 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)

Extrahieren und Wiederverwenden von Knoten im Layer-Diagramm

Da das Diagramm der Ebenen, die Sie bearbeiten, eine statische Datenstruktur ist, kann darauf zugegriffen und es überprüft werden. Und so können Sie Funktionsmodelle als Bilder darstellen.

Dies bedeutet auch, dass Sie auf die Aktivierungen von Zwischenebenen ("Knoten" im Diagramm) zugreifen und diese an anderer Stelle wiederverwenden können - was für die Feature-Extraktion sehr nützlich ist.

Schauen wir uns ein Beispiel an. Dies ist ein VGG19-Modell mit Gewichten, die in ImageNet vorab trainiert wurden:

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 [==============================] - 8s 0us/step

Dies sind die Zwischenaktivierungen des Modells, die durch Abfragen der Diagrammdatenstruktur erhalten werden:

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

Verwenden Sie diese Features, um ein neues Feature-Extraktionsmodell zu erstellen, das die Werte der Zwischenschichtaktivierungen zurückgibt:

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)

Dies ist praktisch für Aufgaben wie die Übertragung neuronaler Stile , unter anderem.

Erweitern Sie die API mithilfe benutzerdefinierter Ebenen

tf.keras enthält eine Vielzahl integrierter Ebenen, zum Beispiel:

  • Faltungsebenen: Conv1D , Conv2D , Conv3D , Conv2DTranspose
  • Pooling-Ebenen: MaxPooling1D , MaxPooling2D , MaxPooling3D , AveragePooling1D
  • RNN-Schichten: GRU , LSTM , ConvLSTM2D
  • BatchNormalization , Dropout , Embedding usw.

Wenn Sie jedoch nicht das finden, was Sie benötigen, können Sie die API einfach erweitern, indem Sie eigene Ebenen erstellen. Alle Layer unterordnen die Layer Klasse und implementieren:

  • call , die die von der Ebene durchgeführte Berechnung angibt.
  • build , mit der die Gewichte der Ebene erstellt werden (dies ist nur eine Stilkonvention, da Sie Gewichte auch in __init__ erstellen können).

Weitere Informationen zum Erstellen von Ebenen von Grund auf erhalten Sie im Handbuch zu benutzerdefinierten Ebenen und Modellen .

Das Folgende ist eine grundlegende Implementierung von 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)

Definieren Sie zur Unterstützung der Serialisierung in Ihrer benutzerdefinierten Ebene eine get_config Methode, die die Konstruktorargumente der get_config zurückgibt:

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})

Implementieren Sie optional die Klassenmethode from_config(cls, config) die beim Neuerstellen einer Layer-Instanz from_config(cls, config) ihres Konfigurationswörterbuchs verwendet wird. Die Standardimplementierung von from_config ist:

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

Wann wird die funktionale API verwendet?

Sollten Sie verwenden , um die Keras funktionalen API ein neues Modell zu erstellen, oder einfach nur die Unterklasse Model Klasse direkt? Im Allgemeinen ist die funktionale API übergeordnet, einfacher und sicherer und verfügt über eine Reihe von Funktionen, die von Modellen der Unterklasse nicht unterstützt werden.

Die Modellunterklasse bietet jedoch eine größere Flexibilität beim Erstellen von Modellen, die nicht einfach als gerichtete azyklische Diagramme von Ebenen ausgedrückt werden können. Beispielsweise könnten Sie kein Tree-RNN mit der funktionalen API implementieren und müssten Model direkt unterklassifizieren.

Weitere Informationen zu den Unterschieden zwischen der funktionalen API und der Modellunterklasse finden Sie unter Was sind symbolische und imperative APIs in TensorFlow 2.0? .

Funktionale API-Stärken:

Die folgenden Eigenschaften gelten auch für sequentielle Modelle (die ebenfalls Datenstrukturen sind), jedoch nicht für untergeordnete Modelle (die Python-Bytecode und keine Datenstrukturen sind).

Weniger ausführlich

Es gibt kein super(MyClass, self).__init__(...) , keinen def call(self, ...): usw.

Vergleichen Sie:

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

Mit der untergeordneten Version:

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)))

Modellvalidierung beim Definieren des Konnektivitätsdiagramms

In der Funktions-API wird die Eingabespezifikation (Form und Typ) im Voraus erstellt (mithilfe von Input ). Jedes Mal, wenn Sie eine Ebene aufrufen, überprüft die Ebene, ob die an sie übergebene Spezifikation ihren Annahmen entspricht, und gibt eine hilfreiche Fehlermeldung aus, wenn nicht.

Dies garantiert, dass jedes Modell ausgeführt wird, das Sie mit der funktionalen API erstellen können. Das gesamte Debugging - außer dem konvergenzbezogenen Debugging - erfolgt statisch während der Modellkonstruktion und nicht zur Ausführungszeit. Dies ähnelt der Typprüfung in einem Compiler.

Ein Funktionsmodell ist plottbar und inspizierbar

Sie können das Modell als Diagramm darstellen und problemlos auf Zwischenknoten in diesem Diagramm zugreifen. Zum Extrahieren und Wiederverwenden der Aktivierungen von Zwischenschichten (wie in einem vorherigen Beispiel gezeigt):

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

Ein Funktionsmodell kann serialisiert oder geklont werden

Da ein Funktionsmodell eher eine Datenstruktur als ein Teil des Codes ist, ist es sicher serialisierbar und kann als einzelne Datei gespeichert werden, mit der Sie genau dasselbe Modell neu erstellen können, ohne Zugriff auf den ursprünglichen Code zu haben. Siehe die Serialisierungs- und Speicheranleitung .

Um ein Modell mit Unterklassen zu serialisieren, muss der Implementierer auf get_config() eine get_config() und from_config() angeben.

Funktionale API-Schwäche:

Dynamische Architekturen werden nicht unterstützt

Die funktionale API behandelt Modelle als DAGs von Ebenen. Dies gilt für die meisten Deep-Learning-Architekturen, jedoch nicht für alle. Beispielsweise folgen rekursive Netzwerke oder Tree-RNNs dieser Annahme nicht und können nicht in der funktionalen API implementiert werden.

Mix-and-Match-API-Stile

Die Wahl zwischen der funktionalen API oder der Modellunterklasse ist keine binäre Entscheidung, die Sie auf eine Modellkategorie beschränkt. Alle Modelle in der tf.keras API können miteinander interagieren, unabhängig davon, ob es sich um Sequential Modelle, Funktionsmodelle oder Modelle mit Unterklassen handelt, die von Grund auf neu geschrieben wurden.

Sie können jederzeit ein Funktionsmodell oder ein Sequential Modell als Teil eines untergeordneten Modells oder Layers verwenden:

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)

Sie können jede untergeordnete Ebene oder jedes Modell in der Funktions-API verwenden, sofern eine call implementiert ist, die einem der folgenden Muster folgt:

  • call(self, inputs, **kwargs) - Wenn inputs ein Tensor oder eine verschachtelte Struktur von Tensoren sind (z. B. eine Liste von Tensoren) und **kwargs Nicht-Tensor-Argumente (Nicht-Eingaben) sind.
  • call(self, inputs, training=None, **kwargs) - Wenn training ein Boolescher call(self, inputs, training=None, **kwargs) ist, der angibt, ob sich die Ebene im Trainingsmodus und im Inferenzmodus verhalten soll.
  • call(self, inputs, mask=None, **kwargs) - Wobei mask ein boolescher call(self, inputs, mask=None, **kwargs) ist (nützlich beispielsweise für RNNs).
  • call(self, inputs, training=None, mask=None, **kwargs) - Natürlich können Sie gleichzeitig maskieren und trainingspezifisches Verhalten zeigen.

Wenn Sie die Methode get_config auf Ihrem benutzerdefinierten Layer oder Modell implementieren, sind die von Ihnen erstellten Funktionsmodelle weiterhin serialisierbar und klonbar.

Hier ist ein kurzes Beispiel für eine benutzerdefinierte RNN, die von Grund auf neu geschrieben und in einem Funktionsmodell verwendet wird:

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)))