Funkcjonalne API

Zobacz na TensorFlow.org Uruchom w Google Colab Wyświetl źródło na GitHub Pobierz notatnik

Ustawiać

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

Wstęp

Keras API funkcjonalny jest sposobem tworzenia modeli, które są bardziej elastyczne niż tf.keras.Sequential API. Funkcjonalne API może obsługiwać modele z nieliniową topologią, współdzielonymi warstwami, a nawet wieloma wejściami lub wyjściami.

Główną ideą jest to, że model głębokiego uczenia się jest zwykle ukierunkowanym grafem acyklicznym (DAG) warstw. Więc API funkcjonalnej jest sposób, aby zbudować wykresy warstwach.

Rozważ następujący model:

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

To jest podstawowy wykres z trzema warstwami. Aby zbudować ten model przy użyciu funkcjonalnego interfejsu API, zacznij od utworzenia węzła wejściowego:

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

Kształt danych jest ustawiony jako 784-wymiarowy wektor. Wielkość partii jest zawsze pomijana, ponieważ określany jest tylko kształt każdej próbki.

Jeśli, na przykład, masz wejście obrazu z kształtem (32, 32, 3) , należy użyć:

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

Do inputs , który jest zwracany zawiera informacje na temat kształtu i dtype danych wejściowych, które paszy dla danego modelu. Oto kształt:

inputs.shape
TensorShape([None, 784])

Oto typ d:

inputs.dtype
tf.float32

Utworzyć nowy węzeł na wykresie warstw wywołując warstwę na tej inputs obiektu:

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

Akcja „wywołanie warstwy” przypomina rysowanie strzałki z „wejść” do utworzonej warstwy. Jesteś „przejście” wejść do dense warstwy, a otrzymasz x jak na wyjściu.

Dodajmy jeszcze kilka warstw do wykresu warstw:

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

W tym momencie można stworzyć Model , podając jego wejść i wyjść na wykresie warstw:

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

Sprawdźmy, jak wygląda podsumowanie modelu:

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
_________________________________________________________________

Możesz również wykreślić model jako wykres:

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

png

I opcjonalnie wyświetl kształty wejściowe i wyjściowe każdej warstwy na wykresie:

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

png

Ten rysunek i kod są prawie identyczne. W wersji kodowej strzałki połączenia są zastępowane operacją wywołania.

„Wykres warstw” to intuicyjny obraz mentalny dla modelu uczenia głębokiego, a funkcjonalny interfejs API to sposób na tworzenie modeli, które dokładnie to odzwierciedlają.

Szkolenie, ocena i wnioskowanie

Szkolenia, ocena, wnioskowanie i pracy dokładnie w ten sam sposób dla modeli zbudowanych przy użyciu API funkcjonalne, jak na Sequential modeli.

W Model oferty klasa wbudowaną pętlę szkoleniowej (The fit() metoda) i wbudowaną pętlę oceny (The evaluate() metoda). Należy pamiętać, że można łatwo dostosować te pętle do wdrożenia procedury szkoleniowe poza nadzorowanego uczenia się (np Gans ).

Tutaj załaduj dane obrazu MNIST, przekształć je w wektory, dopasuj model do danych (podczas monitorowania wydajności w podziale walidacyjnym), a następnie oceń model na danych testowych:

(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

Dla dalszego czytania, zobacz szkolenia i oceny przewodnika.

Zapisz i serializuj

Zapisywanie Wzorcowa praca i serializacji w ten sam sposób dla modeli zbudowanych przy użyciu API funkcjonalny jak robią dla Sequential modeli. Standardowy sposób, aby zapisać model funkcjonalny jest wywołanie model.save() , aby zapisać cały model jako pojedynczy plik. Możesz później odtworzyć ten sam model z tego pliku, nawet jeśli kod, który zbudował model, nie jest już dostępny.

Ten zapisany plik zawiera:

  • architektura modelu
  • wzorcowe wartości masy (które zostały wyuczone podczas treningu)
  • Model szkolenia config, jeśli w ogóle (jak przeszedł do compile )
  • optymalizator i jego stan, jeśli istnieje (aby wznowić trening od miejsca, w którym zostało przerwane)
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

Aby uzyskać szczegółowe informacje, zapoznaj się z modelu serializacji i zapisywanie instrukcji.

Użyj tego samego wykresu warstw, aby zdefiniować wiele modeli

W funkcjonalnym API modele tworzone są poprzez określenie ich wejść i wyjść na wykresie warstw. Oznacza to, że do generowania wielu modeli można użyć jednego wykresu warstw.

W poniższym przykładzie, należy użyć tego samego stosu warstw instancji dwa modele: o encoder model, który Wejścia zakręty obraz na 16-wymiarowych wektorów i koniec-koniec autoencoder model szkolenia.

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
_________________________________________________________________

Tutaj architektura dekodowania jest ściśle symetryczne względem architektury kodowania, tak aby kształt wyjściowe są takie same jak kształt wejściowym (28, 28, 1) .

Odwrotna z Conv2D warstwy stanowi Conv2DTranspose warstwy i na odwrót z MaxPooling2D warstwa jest UpSampling2D warstwy.

Wszystkie modele można wywoływać, podobnie jak warstwy

Można leczyć dowolny model, jak gdyby był warstwę powołując go na Input lub wyjście na innej warstwie. Wywołując model, nie tylko ponownie wykorzystujesz architekturę modelu, ale także ponownie wykorzystujesz jego wagi.

Aby zobaczyć to w akcji, oto inne podejście do przykładu autokodera, który tworzy model kodera, model dekodera i łączy je w dwa wywołania, aby uzyskać model autokodera:

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
_________________________________________________________________

Jak widać, model może być zagnieżdżony: model może zawierać podmodele (ponieważ model jest jak warstwa). Częstym przypadkiem użycia dla modelu zagnieżdżenia jest ensembling. Na przykład, oto jak połączyć zestaw modeli w jeden model, który uśrednia ich przewidywania:

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)

Manipulowanie złożonymi topologiami grafów

Modele z wieloma wejściami i wyjściami

Funkcjonalny interfejs API ułatwia manipulowanie wieloma wejściami i wyjściami. To nie mogą być obsługiwane z Sequential API.

Na przykład, jeśli budujesz system do rankingu zgłoszeń problemów klientów według priorytetu i kierując je do właściwego działu, model będzie miał trzy dane wejściowe:

  • tytuł biletu (wprowadzanie tekstu),
  • treść biletu (wprowadzanie tekstu) oraz
  • dowolne tagi dodane przez użytkownika (wejście kategoryczne)

Ten model będzie miał dwa wyjścia:

  • wynik priorytetu od 0 do 1 (wyjście skalarne sigmoidalne), oraz
  • dział, który powinien obsługiwać zgłoszenie (wyjście softmax nad zbiorem działów).

Możesz zbudować ten model w kilku linijkach za pomocą funkcjonalnego API:

num_tags = 12  # Number of unique issue tags
num_words = 10000  # Size of vocabulary obtained when preprocessing text data
num_departments = 4  # Number of departments for predictions

title_input = keras.Input(
    shape=(None,), name="title"
)  # Variable-length sequence of ints
body_input = keras.Input(shape=(None,), name="body")  # Variable-length sequence of ints
tags_input = keras.Input(
    shape=(num_tags,), name="tags"
)  # Binary vectors of size `num_tags`

# Embed each word in the title into a 64-dimensional vector
title_features = layers.Embedding(num_words, 64)(title_input)
# Embed each word in the text into a 64-dimensional vector
body_features = layers.Embedding(num_words, 64)(body_input)

# Reduce sequence of embedded words in the title into a single 128-dimensional vector
title_features = layers.LSTM(128)(title_features)
# Reduce sequence of embedded words in the body into a single 32-dimensional vector
body_features = layers.LSTM(32)(body_features)

# Merge all available features into a single large vector via concatenation
x = layers.concatenate([title_features, body_features, tags_input])

# Stick a logistic regression for priority prediction on top of the features
priority_pred = layers.Dense(1, name="priority")(x)
# Stick a department classifier on top of the features
department_pred = layers.Dense(num_departments, name="department")(x)

# Instantiate an end-to-end model predicting both priority and department
model = keras.Model(
    inputs=[title_input, body_input, tags_input],
    outputs=[priority_pred, department_pred],
)

Teraz wykreśl model:

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

png

Kompilując ten model, możesz przypisać różne straty do każdego wyjścia. Możesz nawet przypisać różne wagi do każdej straty - aby modulować ich udział w całkowitej utracie treningu.

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

Ponieważ warstwy wyjściowe mają różne nazwy, można również określić straty i wagi strat za pomocą odpowiednich nazw warstw:

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

Trenuj model, przekazując listy tablic NumPy danych wejściowych i docelowych:

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

Podczas wywoływania pasowanie z Dataset obiektu, należy otrzymując albo krotki list jak ([title_data, body_data, tags_data], [priority_targets, dept_targets]) lub krotki słowników jak ({'title': title_data, 'body': body_data, 'tags': tags_data}, {'priority': priority_targets, 'department': dept_targets}) .

Bardziej szczegółowe informacje znajdują się na szkolenia i oceny przewodnika.

Zabawkowy model ResNet

Oprócz wzorów z wieloma wejściami i wyjściami interfejsu API funkcjonalne ułatwia manipulowanie nieliniowych Topologie połączeń - to modele z warstw, które nie są połączone kolejno, którym Sequential API nie można obsłużyć.

Typowym przypadkiem użycia tego są połączenia szczątkowe. Zbudujmy zabawkowy model ResNet dla CIFAR10, aby to zademonstrować:

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
__________________________________________________________________________________________________

Wykreśl model:

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

png

Teraz trenuj model:

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

Udostępnione warstwy

Innym dobrym zastosowaniem funkcjonalnego API są modele, które używają wspólnych warstw. Warstwy udostępnione to instancje warstw, które są wielokrotnie używane w tym samym modelu — uczą się funkcji odpowiadających wielu ścieżkom na wykresie warstw.

Warstwy współdzielone są często używane do kodowania danych wejściowych z podobnych przestrzeni (np. dwóch różnych fragmentów tekstu o podobnym słownictwie). Umożliwiają wymianę informacji między różnymi danymi wejściowymi i umożliwiają trenowanie takiego modelu na mniejszej ilości danych. Jeśli dane słowo jest widoczne w jednym z danych wejściowych, będzie to korzystne dla przetwarzania wszystkich danych wejściowych, które przechodzą przez warstwę współdzieloną.

Aby udostępnić warstwę w funkcjonalnym interfejsie API, wywołaj wielokrotnie tę samą instancję warstwy. Na przykład, oto Embedding warstwy wspólna dla dwóch różnych wejść tekstowych:

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

Wyodrębnij i ponownie wykorzystaj węzły na wykresie warstw

Ponieważ wykres warstw, którymi manipulujesz, jest statyczną strukturą danych, można do niego uzyskać dostęp i go sprawdzić. I w ten sposób możesz kreślić modele funkcjonalne jako obrazy.

Oznacza to również, że możesz uzyskać dostęp do aktywacji warstw pośrednich ("węzły" na wykresie) i użyć ich ponownie w innym miejscu - co jest bardzo przydatne do czegoś takiego jak wyodrębnianie cech.

Spójrzmy na przykład. To jest model VGG19 z wagami przeszkolonymi w 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

A oto pośrednie aktywacje modelu, uzyskane przez odpytywanie struktury danych grafu:

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

Użyj tych funkcji, aby utworzyć nowy model wyodrębniania cech, który zwraca wartości aktywacji warstwy pośredniej:

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)

Jest to przydatne dla zadań, takich jak nerwowej przeniesienia stylu , między innymi.

Rozszerz API za pomocą niestandardowych warstw

tf.keras obejmuje szeroki zakres wbudowanych warstw, na przykład:

  • Splotu warstwy: Conv1D , Conv2D , Conv3D , Conv2DTranspose
  • Poolingu warstwy: MaxPooling1D , MaxPooling2D , MaxPooling3D , AveragePooling1D
  • RNN warstwy: GRU , LSTM , ConvLSTM2D
  • BatchNormalization , Dropout , Embedding , etc.

Ale jeśli nie znajdziesz tego, czego potrzebujesz, możesz łatwo rozszerzyć interfejs API, tworząc własne warstwy. Wszystkie warstwy podklasy Layer klasy i wdrożenia:

  • call metody, która określa obliczenia wykonywane przez warstwę.
  • build sposób, że tworzy masę warstwy (jest to tylko konwencja styl, ponieważ można utworzyć ciężarów w __init__ , jak również).

Aby dowiedzieć się więcej na temat tworzenia warstwy od podstaw, przeczytaj niestandardowych warstw i modeli przewodnika.

Poniżej przedstawiono podstawowe wdrożenie 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)

Aby uzyskać pomoc w swojej warstwie serializacji niestandardowej zdefiniować get_config metodę, która zwraca argumenty konstruktora instancji warstwy:

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

Opcjonalnie, zastosowania sposobu klasy from_config(cls, config) , który jest stosowany przy odtwarzaniu wystąpienie warstwy ze względu na jego słownika konfiguracyjnych. Domyślna implementacja from_config jest:

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

Kiedy używać funkcjonalnego API

Należy użyć API funkcjonalny Keras stworzyć nowy model, lub po prostu podklasy Model klasy bezpośrednio? Ogólnie rzecz biorąc, funkcjonalny interfejs API jest wyższy, łatwiejszy i bezpieczniejszy oraz ma wiele funkcji, których modele podklasy nie obsługują.

Jednak tworzenie podklas modeli zapewnia większą elastyczność podczas budowania modeli, które nie dają się łatwo wyrazić jako skierowane acykliczne grafy warstw. Na przykład, nie można wdrożyć drzewa RNN z API funkcjonalnej i musiałby podklasy Model bezpośrednio.

Dla dogłębnie na różnicach między API i funkcjonalnego modelu dziedziczenia, przeczytaj Jakie są symboliczne i bezwzględne API w TensorFlow 2.0? .

Funkcjonalne atuty API:

Poniższe właściwości są również prawdziwe dla modeli sekwencyjnych (które są również strukturami danych), ale nie są prawdziwe dla modeli podklas (które są kodem bajtowym Pythona, a nie strukturami danych).

Mniej gadatliwy

Nie ma super(MyClass, self).__init__(...) , nie def call(self, ...): , itd.

Porównywać:

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

W wersji z podklasami:

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

Walidacja modelu przy definiowaniu jego grafu łączności

W API funkcjonalnych, specyfikacja wejścia (kształt i dtype) jest utworzony z góry (za pomocą Input ). Za każdym razem, gdy wywołujesz warstwę, warstwa sprawdza, czy przekazana do niej specyfikacja jest zgodna z jej założeniami, a jeśli nie, wyświetli pomocny komunikat o błędzie.

Gwarantuje to, że każdy model, który możesz zbudować za pomocą funkcjonalnego API, będzie działał. Wszelkie debugowanie — inne niż debugowanie związane z konwergencją — odbywa się statycznie podczas konstruowania modelu, a nie w czasie wykonywania. Jest to podobne do sprawdzania typu w kompilatorze.

Funkcjonalny model można kreślić i sprawdzać

Możesz wykreślić model jako wykres i łatwo uzyskać dostęp do węzłów pośrednich na tym wykresie. Na przykład, aby wyodrębnić i ponownie wykorzystać aktywacje warstw pośrednich (jak widać w poprzednim przykładzie):

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

Model funkcjonalny można serializować lub klonować

Ponieważ model funkcjonalny jest strukturą danych, a nie fragmentem kodu, można go bezpiecznie serializować i można go zapisać jako pojedynczy plik, który umożliwia odtworzenie dokładnie tego samego modelu bez dostępu do oryginalnego kodu. Patrz podręcznik serializacji i oszczędności .

Serializować podklasy modelu, jest to niezbędne dla realizator określić get_config() i from_config() metody na poziomie modelu.

Funkcjonalna słabość API:

Nie obsługuje dynamicznych architektur

Funkcjonalne API traktuje modele jako DAG warstw. Dotyczy to większości architektur uczenia głębokiego, ale nie wszystkich — na przykład sieci rekurencyjne lub Tree RNN nie przestrzegają tego założenia i nie mogą być zaimplementowane w funkcjonalnym interfejsie API.

Mieszaj i dopasowuj style API

Wybór między funkcjonalnym interfejsem API a podklasą modelu nie jest decyzją binarną, która ogranicza Cię do jednej kategorii modeli. Wszystkie modele w tf.keras API mogą współdziałać ze sobą, czy są one Sequential modele, modele funkcyjnych lub podklasy modeli, które są pisane od podstaw.

Zawsze można użyć funkcjonalnego modelu lub Sequential modelu jako część podklasy modelu lub warstwy:

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)

Można użyć dowolnej podklasy warstwę lub model w API funkcjonalnej tak długo, jak to realizuje call metody, która podąża jedną z następujących wzorów:

  • call(self, inputs, **kwargs) - W przypadku, gdy inputs jest napinacz lub zagnieżdżone struktura tensorów (na przykład listę tensorów) i gdzie **kwargs argumenty nie tensor (nie-wejścia).
  • call(self, inputs, training=None, **kwargs) - W przypadku, gdy training jest wartością logiczną wskazującą, czy warstwa powinna zachowywać się w trybie treningu i tryb wnioskowania.
  • call(self, inputs, mask=None, **kwargs) - W przypadku, gdy mask jest logiczną maski tensor (przydatne RNNs, na przykład).
  • call(self, inputs, training=None, mask=None, **kwargs) - Oczywiście, można mieć zarówno maskowanie i zachowania specyficzne szkolenie w tym samym czasie.

Dodatkowo, jeśli wdrożyć get_config sposób na niestandardowym warstwa lub modelu, funkcjonalne modele tworzone nadal będzie możliwy do serializacji i Cloneable.

Oto krótki przykład niestandardowej sieci RNN, napisanej od podstaw, używanej w modelu funkcjonalnym:

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