Die funktionale API

Auf TensorFlow.org ansehen Quelle auf GitHub anzeigen Notizbuch herunterladen

Aufstellen

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

Einführung

Die Keras funktionelle API ist eine Möglichkeit , Modelle zu erstellen , die flexibler sind als die tf.keras.Sequential API. Die funktionale API kann Modelle mit nichtlinearer Topologie, gemeinsam genutzten Schichten und sogar mehreren Ein- oder Ausgängen verarbeiten.

Die Grundidee ist, dass ein Deep-Learning-Modell normalerweise ein gerichteter azyklischer Graph (DAG) von Schichten ist. So die funktionalen API ist eine Möglichkeit , grafische Darstellungen von Schichten zu bauen.

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 Schichten. Um dieses Modell mit der funktionalen 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 zum Beispiel eine Bildeingabe mit einer Form haben (32, 32, 3) , verwenden Sie:

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

Die inputs , die zurückgegeben wird , enthält Informationen über die Form und dtype der Eingangsdaten , dass Sie zu Ihrem Modell füttern. Hier ist die Form:

inputs.shape
TensorShape([None, 784])

Hier ist der dtype:

inputs.dtype
tf.float32

Sie erstellen einen neuen Knoten in der graphischen Darstellung der Schichten , die durch eine Schicht auf diesem Aufruf inputs Objekt:

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

Die Aktion "Ebenenaufruf" entspricht dem Zeichnen eines Pfeils von "Eingaben" zu dieser von Ihnen erstellten Ebene. Sie sind die Eingaben in der „passing“ dense Schicht, und Sie erhalten x als Ausgang.

Fügen wir dem Layer-Diagramm einige weitere Layer hinzu:

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

An dieser Stelle können Sie ein erstellen Model durch Angabe seiner Ein- und Ausgänge in der graphischen Darstellung der Schichten:

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

Sehen 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 Graph darstellen:

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

png

Und zeigen Sie optional die Eingabe- und Ausgabeformen jedes Layers im gezeichneten Diagramm an:

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

png

Diese Abbildung und der Code sind fast identisch. In der Code-Version werden die Verbindungspfeile durch die Rufoperation 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, Auswertung und Schlussfolgerung Arbeit genau auf die gleiche Art und Weise für die Modelle des funktionalen API wie für integrierte Sequential - Modelle.

Die Model bietet einen eingebauten in der Ausbildung Schleife (die fit() Methode) und eine eingebaute Beurteilungsschleife (die evaluate() Methode). Beachten Sie, dass Sie leicht können diese Schleifen anpassen Training Routinen über wachtes Lernen (zB zur Umsetzung GANS ).

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 eines Validierungssplits überwachen), und bewerten Sie dann 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])
Epoch 1/2
750/750 [==============================] - 3s 3ms/step - loss: 0.3430 - accuracy: 0.9035 - val_loss: 0.1851 - val_accuracy: 0.9463
Epoch 2/2
750/750 [==============================] - 2s 3ms/step - loss: 0.1585 - accuracy: 0.9527 - val_loss: 0.1366 - val_accuracy: 0.9597
313/313 - 0s - loss: 0.1341 - accuracy: 0.9592
Test loss: 0.13414572179317474
Test accuracy: 0.9592000246047974

Weiterführende Literatur finden Sie in der Ausbildung und Bewertung Führung.

Speichern und serialisieren

Speichern des Modells und Serialisierung Arbeit die gleiche Art und Weise für die Modelle der funktionalen API gebaut , wie sie für tun Sequential Modelle. Der üblicher Weg ein Funktionsmodell zu retten ist , ruft model.save() das gesamte Modell als einzelne Datei zu speichern. Sie können das gleiche Modell später aus dieser Datei neu erstellen, auch wenn der Code, der das Modell erstellt hat, nicht mehr verfügbar ist.

Diese gespeicherte Datei enthält Folgendes:

  • Modellarchitektur
  • Modellgewichtswerte (die während des Trainings gelernt wurden)
  • Modelltraining Config, wenn überhaupt (als bestanden compile )
  • Optimierer und sein Status, falls vorhanden (um das Training dort wieder aufzunehmen, 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")
2021-08-25 17:50:55.989736: W tensorflow/python/util/util.cc:348] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them.
INFO:tensorflow:Assets written to: path_to_my_model/assets

Weitere Einzelheiten finden Sie in der Modell - Serialisierung und Speichern Führung.

Verwenden Sie dasselbe Layer-Diagramm, um mehrere Modelle zu definieren

In der funktionalen API werden Modelle erstellt, indem ihre Eingaben und Ausgaben in einem Schichtendiagramm angegeben werden. Das bedeutet, dass ein einzelner Graph von Layern verwendet werden kann, um mehrere Modelle zu generieren.

Im Beispiel unten verwenden Sie den gleichen Stapel von Schichten zwei Modelle zu instanziiert: ein encoder Modell , dass abwechselnd Bildeingänge in 16-dimensionale Vektoren und ein End-to-End - autoencoder Modell für die Ausbildung.

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
_________________________________________________________________

Hierbei wird die Decodierarchitektur streng symmetrisch zur Codierung Architektur, so dass die Ausgangsform der gleiche wie der Eingangs Form (28, 28, 1) .

Die Rückseite einer Conv2D Schicht eine Conv2DTranspose Schicht und die Rückseite eines MaxPooling2D Schicht eine UpSampling2D Schicht.

Alle Modelle sind aufrufbar, genau wie Layer

Sie können eine beliebige Modell behandeln , als ob sie eine Schicht waren , indem sie auf einen Aufruf Input oder am Ausgang einer anderen Schicht. Wenn Sie ein Modell aufrufen, verwenden Sie nicht nur die Architektur des Modells, sondern auch seine Gewichtungen.

Um dies in Aktion zu sehen, sehen Sie hier ein anderes Beispiel für das Autoencoder-Beispiel, das ein Encoder-Modell und ein Decoder-Modell erstellt und sie in zwei Aufrufen verkettet, 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 werden: Ein Modell kann Untermodelle enthalten (da ein Modell wie eine Ebene ist). Ein häufiger Anwendungsfall für das Modell Verschachtelung wird ensembling. So stellen Sie beispielsweise eine Reihe von Modellen zu einem einzigen Modell zusammen, das deren 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)

Komplexe Graphtopologien manipulieren

Modelle mit mehreren Ein- und Ausgängen

Die funktionale API macht es einfach, mehrere Ein- und Ausgänge zu manipulieren. Dies kann nicht mit der behandelt werden Sequential API.

Wenn Sie beispielsweise ein System zum Einstufen von Kundenproblemtickets nach Priorität und zum Weiterleiten an die richtige Abteilung erstellen, hat das Modell drei Eingaben:

  • der Titel des Tickets (Texteingabe),
  • der Textkörper des Tickets (Texteingabe) und
  • alle vom Benutzer hinzugefügten Tags (kategoriale Eingabe)

Dieses Modell hat zwei Ausgänge:

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

Sie können dieses Modell in wenigen Zeilen mit der funktionalen API 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],
)

Plotten Sie nun das Modell:

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

png

Bei der Zusammenstellung dieses Modells können Sie jedem Ausgang unterschiedliche Verluste zuordnen. Sie können jedem Verlust sogar unterschiedliche Gewichte zuweisen – um seinen 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 Ausgabeschichten unterschiedliche Namen haben, können Sie die Verluste und Verlustgewichte auch mit den entsprechenden Schichtnamen 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={"priority": 1.0, "department": 0.2},
)

Trainieren Sie das Modell, indem Sie Listen von NumPy-Arrays mit 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 [==============================] - 5s 9ms/step - loss: 1.2899 - priority_loss: 0.7186 - department_loss: 2.8564
Epoch 2/2
40/40 [==============================] - 0s 9ms/step - loss: 1.2668 - priority_loss: 0.6991 - department_loss: 2.8389
<keras.callbacks.History at 0x7fc1a66dc790>

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

Weitere Erläuterungen finden Sie in der Ausbildung und Bewertung Führung.

Ein ResNet-Spielzeugmodell

Zusätzlich zu den Modellen mit mehreren Ein- und Ausgängen macht die funktionelle API es einfach , nicht-linearen Verbindungstopologien zu manipulieren - das sind Modelle mit Schichten , die nicht aufeinander folgend verbunden sind, die die Sequential API nicht verarbeiten kann.

Ein häufiger Anwendungsfall hierfür sind Restverbindungen. Lassen Sie uns ein Spielzeug-ResNet-Modell für CIFAR10 erstellen, 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
170508288/170498071 [==============================] - 11s 0us/step
13/13 [==============================] - 2s 29ms/step - loss: 2.3364 - acc: 0.1063 - val_loss: 2.2986 - val_acc: 0.0850
<keras.callbacks.History at 0x7fc19df22610>

Gemeinsame Ebenen

Eine weitere gute Verwendung für die funktionale API sind Modelle , die gemeinsam genutzte Schichten verwenden. Gemeinsam genutzte Layer sind Layer-Instanzen, die im selben Modell mehrmals wiederverwendet werden. Sie lernen Features, die mehreren Pfaden im Layer-Graph entsprechen.

Gemeinsame Ebenen werden häufig verwendet, um Eingaben aus ähnlichen Bereichen zu codieren (z. B. zwei verschiedene Textstücke 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 zu sehen ist, kommt dies der Verarbeitung aller Eingaben zugute, die die gemeinsam genutzte Schicht durchlaufen.

Um eine Schicht in der funktionalen API freizugeben, rufen Sie dieselbe Schichtinstanz mehrmals auf. Zum Beispiel, hier ist eine Embedding Schicht über zwei verschiedene Texteingaben geteilt:

# 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 es sich bei dem von Ihnen bearbeiteten Layer-Diagramm um eine statische Datenstruktur handelt, kann darauf zugegriffen und geprüft werden. Und so können Sie Funktionsmodelle als Bilder plotten.

Dies bedeutet auch, dass Sie auf die Aktivierungen von Zwischenschichten ("Knoten" im Diagramm) zugreifen und sie an anderer Stelle wiederverwenden können - was für so etwas wie die Merkmalsextraktion sehr nützlich ist.

Schauen wir uns ein Beispiel an. Dies ist ein VGG19-Modell mit auf ImageNet vortrainierten Gewichten:

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

Und dies sind die Zwischenaktivierungen des Modells, die durch Abfragen der Graphdatenstruktur 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 Zwischenlayer-Aktivierungen 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)

Das ist praktisch für Aufgaben wie neuronale Stil Übertragung , unter anderem.

Erweitern Sie die API mit benutzerdefinierten Ebenen

tf.keras umfasst eine Vielzahl von integrierten in Schichten, zum Beispiel:

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

Wenn Sie jedoch nicht finden, was Sie brauchen, können Sie die API ganz einfach erweitern, indem Sie Ihre eigenen Layer erstellen. Alle Schichten Unterklasse der Layer - Klasse und implementieren:

  • call die angibt , die durch die Schicht durchgeführt Berechnung.
  • build - Methode, die die Gewichte der Schicht erzeugt (dies ist nur eine Art Konvention , da Sie Gewichte in erstellen können __init__ , als auch).

Um mehr zu erfahren über Schichten von Grund auf neu erstellen, lesen Sie benutzerdefinierte Schichten und Modelle Führer.

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

Für die Serialisierung Unterstützung in der benutzerdefinierten Schicht, definieren eine get_config Methode, die die Konstruktorargumente der Schicht Instanz 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})

Wahlweise implementieren die Klassenmethode from_config(cls, config) , die verwendet wird , wenn eine Schicht Instanz seine Konfigurations Wörterbuch gegeben neu erstellt. Die Standardimplementierung von from_config ist:

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

Wann sollte die funktionale API verwendet werden?

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 auf höherer Ebene, einfacher und sicherer und weist eine Reihe von Funktionen auf, die Unterklassenmodelle nicht unterstützen.

Die Modellunterklassenbildung bietet jedoch eine größere Flexibilität beim Erstellen von Modellen, die sich nicht leicht als gerichtete azyklische Graphen von Schichten ausdrücken lassen. Zum Beispiel könnten Sie keinen Baum-RNN mit den funktionellen API implementieren und mußten Unterklasse Model direkt.

Für einen eingehenden Blick auf den Unterschieden zwischen der funktionalen API und Modell Subklassifizieren, liest Was ist symbolische und Imperative APIs in TensorFlow 2.0? .

Funktionale API-Stärken:

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

Weniger ausführlich

Es gibt keine super(MyClass, self).__init__(...) , nicht def call(self, ...): etc.

Vergleichen:

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ätsgraphen

In der funktionalen API, die Eingangsspezifikation (Form und dtype) im Voraus (unter Verwendung erstellt Input ). Jedes Mal, wenn Sie eine Schicht aufrufen, überprüft die Schicht, ob die an sie übergebene Spezifikation ihren Annahmen entspricht, und gibt andernfalls eine hilfreiche Fehlermeldung aus.

Dies garantiert, dass jedes Modell, das Sie mit der funktionalen API erstellen können, ausgeführt wird. Sämtliches Debugging – mit Ausnahme des konvergenzbezogenen Debuggings – erfolgt statisch während der Modellkonstruktion und nicht zur Ausführungszeit. Dies ist vergleichbar mit der Typüberprüfung in einem Compiler.

Ein Funktionsmodell ist plotbar und inspizierbar

Sie können das Modell als Diagramm darstellen und Sie können problemlos auf Zwischenknoten in diesem Diagramm zugreifen. Um beispielsweise die Aktivierungen von Zwischenschichten zu extrahieren und wiederzuverwenden (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 eine Datenstruktur und kein Codestück ist, ist es sicher serialisierbar und kann als einzelne Datei gespeichert werden, die es Ihnen ermöglicht, genau dasselbe Modell neu zu erstellen, ohne auf den Originalcode zugreifen zu müssen. Siehe die Serialisierung und Speichern Führung .

Um ein Modell subclassed zu serialisieren, ist es erforderlich , dass der Implementierer ein spezifizieren get_config() und from_config() Verfahren auf Modellebene.

Funktionelle API-Schwäche:

Es unterstützt keine dynamischen Architekturen

Die funktionale API behandelt Modelle als DAGs von Schichten. Dies gilt für die meisten Deep-Learning-Architekturen, aber nicht für alle – zum Beispiel folgen rekursive Netzwerke oder Baum-RNNs dieser Annahme nicht und können nicht in der funktionalen API implementiert werden.

API-Stile mischen und anpassen

Die Wahl zwischen der funktionalen API oder der Modellunterklasse ist keine binäre Entscheidung, die Sie auf eine Modellkategorie beschränkt. Alle Modelle der tf.keras API können miteinander interagieren, ob sie Sequential Modelle, Funktionsmodelle oder subclassed Modelle , die von Grund auf neu geschrieben werden.

Sie können immer ein Funktionsmodell oder verwenden Sequential Modell als Teil einer Unterklasse - Modell oder Schicht:

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 so lange jede subclassed Schicht oder Modell in der Funktions API verwenden , da es ein implementiert call , das eines des folgenden Musters folgt:

  • call(self, inputs, **kwargs) - Wo inputs ein Tensor oder eine verschachtelte Struktur von Tensoren ist (zB eine Liste von Tensoren), und wo **kwargs sind nicht-Tensor Argumente (nicht-Eingänge).
  • call(self, inputs, training=None, **kwargs) - Wo training ein boolean ist , der anzeigt , ob die Schicht im Trainingsmodus und Inferenz - Modus verhalten soll.
  • call(self, inputs, mask=None, **kwargs) - Wo mask einen boolean Maske Tensor (nützlich für RNNs, zum Beispiel) ist.
  • call(self, inputs, training=None, mask=None, **kwargs) - Natürlich können Sie sowohl Maskierung und Ausbildung spezifische Verhalten zur gleichen Zeit haben.

Wenn Sie darüber hinaus die Umsetzung get_config Methode auf Ihrem benutzerdefinierten Layer - Modell, die Funktionsmodelle werden Sie noch serializable und klonbar sein erstellen.

Hier ist ein kurzes Beispiel für ein benutzerdefiniertes RNN, das von Grund auf neu geschrieben wurde 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)))