![]() | ![]() | ![]() | ![]() |
Impostare
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
introduzione
L'API funzionale Keras è un modo per creare modelli che sono più flessibili rispetto al tf.keras.Sequential
API. L'API funzionale può gestire modelli con topologia non lineare, livelli condivisi e persino più input o output.
L'idea principale è che un modello di deep learning sia solitamente un grafo aciclico diretto (DAG) di strati. Così l'API funzionale è un modo per costruire grafici di strati.
Considera il seguente modello:
(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)
Questo è un grafico di base con tre livelli. Per creare questo modello utilizzando l'API funzionale, inizia creando un nodo di input:
inputs = keras.Input(shape=(784,))
La forma dei dati è impostata come un vettore a 784 dimensioni. La dimensione del lotto viene sempre omessa poiché viene specificata solo la forma di ciascun campione.
Se, per esempio, si ha un input un'immagine con una forma di (32, 32, 3)
, si usa:
# Just for demonstration purposes.
img_inputs = keras.Input(shape=(32, 32, 3))
Gli inputs
che viene restituito contiene informazioni sulla forma e dtype
dei dati di ingresso che si posiziona al modello. Ecco la forma:
inputs.shape
TensorShape([None, 784])
Ecco il tipo d:
inputs.dtype
tf.float32
Si crea un nuovo nodo nel grafico di strati chiamando uno strato su questa inputs
oggetto:
dense = layers.Dense(64, activation="relu")
x = dense(inputs)
L'azione "chiamata livello" è come disegnare una freccia da "input" a questo livello che hai creato. Si è "passare" gli ingressi al dense
strato, e si ottiene x
come l'uscita.
Aggiungiamo altri livelli al grafico dei livelli:
x = layers.Dense(64, activation="relu")(x)
outputs = layers.Dense(10)(x)
A questo punto, è possibile creare un Model
specificando gli ingressi e le uscite nel grafico di strati:
model = keras.Model(inputs=inputs, outputs=outputs, name="mnist_model")
Diamo un'occhiata a come appare il riepilogo del modello:
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 _________________________________________________________________
Puoi anche tracciare il modello come un grafico:
keras.utils.plot_model(model, "my_first_model.png")
E, facoltativamente, visualizzare le forme di input e output di ciascun livello nel grafico tracciato:
keras.utils.plot_model(model, "my_first_model_with_shape_info.png", show_shapes=True)
Questa cifra e il codice sono quasi identici. Nella versione a codice, le frecce di connessione sono sostituite dall'operazione di chiamata.
Un "grafico dei livelli" è un'immagine mentale intuitiva per un modello di apprendimento profondo e l'API funzionale è un modo per creare modelli che lo rispecchiano da vicino.
Formazione, valutazione e inferenza
Formazione, valutazione, e il lavoro di inferenza esattamente nello stesso modo per i modelli costruiti utilizzando l'API funzionale per Sequential
modelli.
Le Model
offerte di classe dotato di un ciclo di formazione (il fit()
metodo) e un built-in loop di valutazione (la evaluate()
metodo). Nota che si può facilmente personalizzare questi cicli di implementare le routine di allenamento al di là di apprendimento supervisionato (ad es Gans ).
Qui, carica i dati dell'immagine MNIST, rimodellali in vettori, adatta il modello ai dati (monitorando le prestazioni su una divisione di convalida), quindi valuta il modello sui dati del test:
(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
Per approfondimenti, consultare la formazione e valutazione di guida.
Salva e serializza
Salvare il modello e la serializzazione di lavoro allo stesso modo per i modelli costruiti utilizzando l'API funzionale come fanno per Sequential
modelli. Il metodo standard per salvare un modello funzionale è quello di chiamare model.save()
per salvare l'intero modello come un singolo file. In seguito puoi ricreare lo stesso modello da questo file, anche se il codice che ha creato il modello non è più disponibile.
Questo file salvato include:
- architettura del modello
- valori di peso del modello (appresi durante l'allenamento)
- modello di formazione config, se del caso (come passato alla
compile
) - ottimizzatore e il suo stato, se presente (per riprendere l'allenamento da dove eri rimasto)
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
Per ulteriori informazioni, leggere il modello di serializzazione e il salvataggio manuale.
Usa lo stesso grafico dei livelli per definire più modelli
Nell'API funzionale, i modelli vengono creati specificando i loro input e output in un grafico di livelli. Ciò significa che un singolo grafico di livelli può essere utilizzato per generare più modelli.
Nel seguente esempio, si utilizza la stessa pila di strati istanziare due modelli: un encoder
modello che input immagine assume vettori 16-dimensionale, e un end-to-end autoencoder
modello per la formazione.
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 _________________________________________________________________
Qui, l'architettura decodifica è rigorosamente simmetrico all'architettura codifica, così la forma di uscita è uguale alla forma di ingresso (28, 28, 1)
.
L'inverso di un Conv2D
strato è un Conv2DTranspose
strato, e l'inverso di un MaxPooling2D
strato è un UpSampling2D
strato.
Tutti i modelli sono richiamabili, proprio come i livelli
Si può trattare qualsiasi modello come se fosse uno strato invocando su di un Input
o in uscita di un altro livello. Chiamando un modello non stai solo riutilizzando l'architettura del modello, stai anche riutilizzando i suoi pesi.
Per vedere questo in azione, ecco una versione diversa dell'esempio di autoencoder che crea un modello di codificatore, un modello di decodificatore e li concatena in due chiamate per ottenere il modello di codifica automatica:
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 _________________________________________________________________
Come puoi vedere, il modello può essere nidificato: un modello può contenere dei sottomodelli (poiché un modello è proprio come un livello). Un caso di uso comune per il modello di nidificazione è classificatori binari. Ad esempio, ecco come riunire un insieme di modelli in un unico modello che calcola la media delle loro previsioni:
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)
Manipolare topologie di grafi complesse
Modelli con più ingressi e uscite
L'API funzionale semplifica la manipolazione di più input e output. Questo non può essere gestito con l' Sequential
API.
Ad esempio, se stai creando un sistema per classificare i ticket di emissione dei clienti in base alla priorità e indirizzarli al reparto corretto, il modello avrà tre input:
- il titolo del biglietto (testo inserito),
- il corpo del testo del ticket (input di testo) e
- eventuali tag aggiunti dall'utente (input categoriale)
Questo modello avrà due uscite:
- il punteggio di priorità compreso tra 0 e 1 (uscita sigmoidea scalare) e
- il dipartimento che dovrebbe gestire il ticket (output softmax sull'insieme dei dipartimenti).
Puoi costruire questo modello in poche righe con l'API funzionale:
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],
)
Ora traccia il modello:
keras.utils.plot_model(model, "multi_input_and_output_model.png", show_shapes=True)
Quando si compila questo modello, è possibile assegnare perdite diverse a ciascuna uscita. Puoi anche assegnare pesi diversi a ciascuna perdita, per modulare il loro contributo alla perdita totale di allenamento.
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],
)
Poiché i livelli di output hanno nomi diversi, puoi anche specificare le perdite e i pesi delle perdite con i nomi dei livelli corrispondenti:
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},
)
Addestra il modello passando elenchi di matrici NumPy di input e target:
# 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>
Quando si chiama in forma con un Dataset
oggetto, dovrebbe produrre come una tupla di liste come ([title_data, body_data, tags_data], [priority_targets, dept_targets])
o una tupla di dizionari come ({'title': title_data, 'body': body_data, 'tags': tags_data}, {'priority': priority_targets, 'department': dept_targets})
.
Per una spiegazione più dettagliata, fare riferimento alla formazione e valutazione di guida.
Un modello ResNet giocattolo
Oltre ai modelli con più ingressi e uscite, l'API funzionale rende facile manipolare topologie connettività non lineari - questi sono modelli con strati che non sono collegati in sequenza, che il Sequential
API non può gestire.
Un caso d'uso comune per questo sono le connessioni residue. Costruiamo un modello ResNet giocattolo per CIFAR10 per dimostrarlo:
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 __________________________________________________________________________________________________
Traccia il modello:
keras.utils.plot_model(model, "mini_resnet.png", show_shapes=True)
Ora allena il modello:
(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>
Livelli condivisi
Un altro uso buono per l'API funzionale sono modelli che utilizzano livelli condivisi. I livelli condivisi sono istanze di livello che vengono riutilizzate più volte nello stesso modello: apprendono funzionalità che corrispondono a più percorsi nel grafico dei livelli.
I livelli condivisi vengono spesso utilizzati per codificare gli input da spazi simili (ad esempio, due diverse parti di testo che presentano un vocabolario simile). Consentono la condivisione di informazioni tra questi diversi input e consentono di addestrare un tale modello su meno dati. Se una determinata parola viene vista in uno degli input, ciò andrà a vantaggio dell'elaborazione di tutti gli input che passano attraverso il livello condiviso.
Per condividere un livello nell'API funzionale, chiama più volte la stessa istanza del livello. Ad esempio, ecco un Embedding
strato condiviso tra due ingressi di testo differenti:
# 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)
Estrai e riutilizza i nodi nel grafico dei livelli
Poiché il grafico dei livelli che stai manipolando è una struttura di dati statica, è possibile accedervi e controllarlo. Ed è così che puoi tracciare modelli funzionali come immagini.
Ciò significa anche che puoi accedere alle attivazioni dei livelli intermedi ("nodi" nel grafico) e riutilizzarli altrove, il che è molto utile per qualcosa come l'estrazione di funzionalità.
Diamo un'occhiata a un esempio. Questo è un modello VGG19 con pesi preaddestrati su ImageNet:
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
E queste sono le attivazioni intermedie del modello, ottenute interrogando la struttura dati del grafico:
features_list = [layer.output for layer in vgg19.layers]
Utilizzare queste funzionalità per creare un nuovo modello di estrazione delle funzionalità che restituisca i valori delle attivazioni del livello intermedio:
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)
Questo è utile per compiti come il trasferimento stile neurale , tra le altre cose.
Estendi l'API utilizzando livelli personalizzati
tf.keras
comprende una vasta gamma di built-in strati, ad esempio:
- Strati convoluzionali:
Conv1D
,Conv2D
,Conv3D
,Conv2DTranspose
- Strati pooling:
MaxPooling1D
,MaxPooling2D
,MaxPooling3D
,AveragePooling1D
- Strati RNN:
GRU
,LSTM
,ConvLSTM2D
-
BatchNormalization
,Dropout
,Embedding
, etc.
Ma se non trovi quello che ti serve, è facile estendere l'API creando i tuoi livelli. Tutti i livelli sottoclasse il Layer
classe e implementare:
-
call
metodo, che specifica il calcolo svolto dallo strato. -
build
il metodo, che crea i pesi dello strato (questa è solo una convenzione stile dal momento che è possibile creare i pesi in__init__
, pure).
Per ulteriori informazioni sulla creazione di livelli da zero, leggere livelli personalizzati e modelli di guida.
Quanto segue è un'implementazione di base di 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)
Per il supporto di serializzazione nel vostro livello personalizzato, definire un get_config
metodo che restituisce gli argomenti del costruttore dell'istanza livello:
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})
Facoltativamente, implementare il metodo della classe from_config(cls, config)
che viene utilizzato quando ricreare un'istanza strato data la sua configurazione dizionario. L'implementazione predefinita di from_config
è:
def from_config(cls, config):
return cls(**config)
Quando utilizzare l'API funzionale
Si dovrebbe utilizzare l'API funzionale Keras per creare un nuovo modello, o semplicemente sottoclasse il Model
direttamente classe? In generale, l'API funzionale è di livello superiore, più semplice e più sicura e presenta una serie di funzionalità che i modelli di sottoclassi non supportano.
Tuttavia, la sottoclasse del modello offre una maggiore flessibilità durante la creazione di modelli che non sono facilmente esprimibili come grafici aciclici diretti di livelli. Ad esempio, non si poteva realizzare un albero-RNN con l'API funzionale e avrebbe dovuto creare una sottoclasse Model
direttamente.
Per uno sguardo approfondito le differenze tra le API funzionale e modello di creazione di sottoclassi, leggere Quali sono le API simbolici e imperativo tensorflow 2.0? .
Punti di forza dell'API funzionale:
Le seguenti proprietà sono vere anche per i modelli sequenziali (che sono anche strutture dati), ma non sono vere per i modelli sottoclassi (che sono bytecode Python, non strutture dati).
Meno prolisso
Non v'è alcun super(MyClass, self).__init__(...)
, non def call(self, ...):
, etc.
Confrontare:
inputs = keras.Input(shape=(32,))
x = layers.Dense(64, activation='relu')(inputs)
outputs = layers.Dense(10)(x)
mlp = keras.Model(inputs, outputs)
Con la versione sottoclasse:
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)))
Convalida del modello durante la definizione del suo grafico di connettività
Nel API funzionale, specifiche di ingresso (forma e dtype) è creato in anticipo (utilizzando Input
). Ogni volta che si chiama un livello, il livello controlla che la specifica passata ad esso corrisponda ai suoi presupposti e in caso contrario genererà un utile messaggio di errore.
Ciò garantisce l'esecuzione di qualsiasi modello che puoi creare con l'API funzionale. Tutto il debug, diverso dal debug relativo alla convergenza, avviene in modo statico durante la costruzione del modello e non al momento dell'esecuzione. Questo è simile al controllo del tipo in un compilatore.
Un modello funzionale è tracciabile e ispezionabile
Puoi tracciare il modello come un grafico e puoi accedere facilmente ai nodi intermedi in questo grafico. Ad esempio, per estrarre e riutilizzare le attivazioni degli strati intermedi (come visto in un esempio precedente):
features_list = [layer.output for layer in vgg19.layers]
feat_extraction_model = keras.Model(inputs=vgg19.input, outputs=features_list)
Un modello funzionale può essere serializzato o clonato
Poiché un modello funzionale è una struttura di dati piuttosto che un pezzo di codice, è serializzabile in modo sicuro e può essere salvato come un unico file che consente di ricreare lo stesso identico modello senza avere accesso al codice originale. Consultare la guida di serializzazione e il risparmio .
Per serializzare un modello sottoclasse, è necessario che il realizzatore di specificare un get_config()
e from_config()
il metodo a livello di modello.
Punto debole dell'API funzionale:
Non supporta architetture dinamiche
L'API funzionale tratta i modelli come DAG di livelli. Questo è vero per la maggior parte delle architetture di deep learning, ma non per tutte, ad esempio, le reti ricorsive o gli RNN ad albero non seguono questo presupposto e non possono essere implementati nell'API funzionale.
Combina stili API
La scelta tra l'API funzionale o la sottoclasse del modello non è una decisione binaria che ti limita a una categoria di modelli. Tutti i modelli della tf.keras
API possono interagire con l'altro, se sono Sequential
modelli, modelli funzionali, o modelli sottoclasse che sono scritte da zero.
È sempre possibile utilizzare un modello funzionale o Sequential
modello come parte di un modello sottoclasse o un livello:
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)
È possibile utilizzare qualsiasi strato sottoclasse o modello nella API funzionale fintanto che implementa una call
metodo che segue uno dei seguenti schemi:
-
call(self, inputs, **kwargs)
- Doveinputs
è un tensore o una struttura annidata dei tensori (ad esempio, un elenco dei tensori), e dove**kwargs
sono argomenti non tensoriali (non input). -
call(self, inputs, training=None, **kwargs)
- dovetraining
è un valore booleano che indica se lo strato deve comportarsi in modalità allenamento e la modalità di inferenza. -
call(self, inputs, mask=None, **kwargs)
- Dovemask
è una maschera tensore booleana (utile per RNR, per esempio). -
call(self, inputs, training=None, mask=None, **kwargs)
- Naturalmente, si possono avere entrambe mascheratura e comportamenti specifici di formazione allo stesso tempo.
Inoltre, se si implementa il get_config
metodo sul vostro livello personalizzato o modello, i modelli funzionali creati saranno ancora serializzabile e clonabile.
Ecco un rapido esempio di un RNN personalizzato, scritto da zero, utilizzato in un modello funzionale:
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)))