Ta strona została przetłumaczona przez Cloud Translation API.
Switch to English

Funkcjonalny interfejs 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

Wprowadzenie

Funkcjonalne API Keras to sposób na tworzenie modeli, które są bardziej elastyczne niż tf.keras.Sequential API. Funkcjonalny interfejs API może obsługiwać modele o nieliniowej topologii, współdzielonych warstwach, a nawet wielu wejściach lub wyjściach.

Główną ideą jest to, że model głębokiego uczenia jest zwykle skierowanym acyklicznym grafem (DAG) warstw. Zatem funkcjonalne API to sposób na tworzenie wykresów warstw .

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 wektor o 784 wymiarach. Wielkość partii jest zawsze pomijana, ponieważ określa się tylko kształt każdej próbki.

Jeśli na przykład masz wejście obrazu o kształcie (32, 32, 3) , użyjesz:

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

Zwracane inputs zawierają informacje o kształcie i dtype danych wejściowych przekazanych do modelu. Oto kształt:

inputs.shape
TensorShape([None, 784])

Oto typ:

inputs.dtype
tf.float32

Tworzysz nowy węzeł na wykresie warstw, wywołując warstwę w tym obiekcie inputs :

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

Akcja „wywołanie warstwy” jest podobna do rysowania strzałki od „danych wejściowych” do utworzonej warstwy. „Przekazujesz” dane wejściowe do dense warstwy i otrzymujesz x jako wynik.

Dodajmy jeszcze kilka warstw do wykresu warstw:

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

W tym momencie możesz utworzyć Model , określając jego wejścia i wyjścia 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

Ta liczba i kod są prawie identyczne. W wersji kodu strzałki połączeń są zastępowane operacją wywołania.

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

Szkolenie, ocena i wnioskowanie

Trenowanie, ocena i wnioskowanie działają dokładnie w taki sam sposób w przypadku modeli zbudowanych przy użyciu funkcjonalnego interfejsu API, jak w przypadku modeli Sequential .

Tutaj załaduj dane obrazu MNIST, przekształć je w wektory, dopasuj model do danych (podczas monitorowania wydajności w podziale walidacji), 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 [==============================] - 2s 2ms/step - loss: 0.3388 - accuracy: 0.9032 - val_loss: 0.1877 - val_accuracy: 0.9457
Epoch 2/2
750/750 [==============================] - 2s 2ms/step - loss: 0.1636 - accuracy: 0.9521 - val_loss: 0.1559 - val_accuracy: 0.9543
313/313 - 0s - loss: 0.1454 - accuracy: 0.9551
Test loss: 0.14535436034202576
Test accuracy: 0.9550999999046326

Więcej informacji można znaleźć w przewodniku dotyczącym szkoleń i ewaluacji .

Zapisz i serializuj

Zapisywanie modelu i serializacja działają w ten sam sposób w przypadku modeli zbudowanych przy użyciu funkcjonalnego interfejsu API, jak w przypadku modeli Sequential . Standardowym sposobem zapisania modelu funkcjonalnego jest wywołanie model.save() celu zapisania całego modelu jako pojedynczego pliku. Możesz później odtworzyć ten sam model z tego pliku, nawet jeśli kod, który utworzył model, nie jest już dostępny.

Ten zapisany plik zawiera:

  • architektura modelu
  • wzorcowe wartości masy (które zostały wyuczone podczas treningu)
  • konfiguracja szkolenia modelu, jeśli istnieje (przekazana do compile )
  • optymalizator i jego stan, jeśli istnieje (aby wznowić trening od miejsca, w którym został przerwany)
model.save("path_to_my_model")
del model
# Recreate the exact same model purely from the file:
model = keras.models.load_model("path_to_my_model")
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/training/tracking/tracking.py:111: Model.state_updates (from tensorflow.python.keras.engine.training) is deprecated and will be removed in a future version.
Instructions for updating:
This property should not be used in TensorFlow 2.0, as updates are applied automatically.
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/training/tracking/tracking.py:111: Layer.updates (from tensorflow.python.keras.engine.base_layer) is deprecated and will be removed in a future version.
Instructions for updating:
This property should not be used in TensorFlow 2.0, as updates are applied automatically.
INFO:tensorflow:Assets written to: path_to_my_model/assets

Aby uzyskać szczegółowe informacje, przeczytaj przewodnik serializacji i zapisywania modelu.

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

W funkcjonalnym interfejsie API modele są tworzone przez określenie ich danych wejściowych i wyjściowych na wykresie warstw. Oznacza to, że pojedynczy wykres warstw może służyć do generowania wielu modeli.

W poniższym przykładzie użyjesz tego samego stosu warstw do utworzenia wystąpienia dwóch modeli: modelu encoder który zamienia dane wejściowe obrazu na wektory 16-wymiarowe oraz autoencoder model autoencoder do 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 symetryczna w stosunku do architektury kodowania, więc kształt wyjściowy jest taki sam jak kształt wejściowy (28, 28, 1) .

Conv2D warstwą Conv2D jest warstwa Conv2DTranspose , a odwrotnością warstwy MaxPooling2D jest warstwa UpSampling2D .

Wszystkie modele są wywoływalne, podobnie jak warstwy

Każdy model można traktować tak, jakby był warstwą, wywołując go na Input lub na wyjściu innej warstwy. 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 autoenkodera, który tworzy model kodera, model dekodera i łączy je w dwa wywołania w celu uzyskania modelu autoenkodera:

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). Typowym przypadkiem użycia zagnieżdżania modeli jest składanie . Na przykład, oto jak zebrać 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)

Manipuluj 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. Nie można tego obsłużyć za pomocą Sequential API.

Na przykład, jeśli budujesz system do oceniania biletów wydania klientów według priorytetu i kierowania ich do właściwego działu, model będzie miał trzy dane wejściowe:

  • tytuł biletu (wpisywanie tekstu),
  • treść biletu (wprowadzanie tekstu) oraz
  • wszelkie tagi dodane przez użytkownika (kategoryczne dane wejściowe)

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

  • wynik priorytetu od 0 do 1 (skalarne wyjście sigmoidalne), a
  • dział, który powinien obsługiwać bilet (wyjście softmax dla zbioru działów).

Możesz zbudować ten model w kilku wierszach 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

Podczas kompilowania tego modelu do każdego wyjścia można przypisać różne straty. 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żesz również określić stratę w następujący sposób:

model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss={
        "priority": keras.losses.BinaryCrossentropy(from_logits=True),
        "department": keras.losses.CategoricalCrossentropy(from_logits=True),
    },
    loss_weights=[1.0, 0.2],
)

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

# Dummy input data
title_data = np.random.randint(num_words, size=(1280, 10))
body_data = np.random.randint(num_words, size=(1280, 100))
tags_data = np.random.randint(2, size=(1280, num_tags)).astype("float32")

# Dummy target data
priority_targets = np.random.random(size=(1280, 1))
dept_targets = np.random.randint(2, size=(1280, num_departments))

model.fit(
    {"title": title_data, "body": body_data, "tags": tags_data},
    {"priority": priority_targets, "department": dept_targets},
    epochs=2,
    batch_size=32,
)
Epoch 1/2
40/40 [==============================] - 0s 11ms/step - loss: 1.2819 - priority_loss: 0.7088 - department_loss: 2.8653
Epoch 2/2
40/40 [==============================] - 0s 10ms/step - loss: 1.2723 - priority_loss: 0.6992 - department_loss: 2.8655

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

Wywołując fit z obiektem Dataset , powinno to dać krotkę list, takich jak ([title_data, body_data, tags_data], [priority_targets, dept_targets]) lub krotkę słowników, takich jak ({'title': title_data, 'body': body_data, 'tags': tags_data}, {'priority': priority_targets, 'department': dept_targets}) .

Bardziej szczegółowe wyjaśnienia można znaleźć w przewodniku dotyczącym szkoleń i ewaluacji .

Zabawkowy model ResNet

Oprócz modeli z wieloma wejściami i wyjściami funkcjonalny interfejs API ułatwia manipulowanie nieliniowymi topologiami łączności - są to modele z warstwami, które nie są połączone sekwencyjnie, czego nie obsługuje Sequential interfejs API.

Typowym przypadkiem użycia w tym przypadku są szczątkowe połączenia. 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 wytrenuj model:

EA10112645
13/13 [==============================] - 0s 21ms/step - loss: 2.3574 - acc: 0.1037 - val_loss: 2.3011 - val_acc: 0.1000

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

Współdzielone warstwy

Innym dobrym zastosowaniem funkcjonalnego interfejsu API są modele wykorzystujące współdzielone warstwy . Warstwy współdzielone to wystąpienia warstw, które są wielokrotnie używane w tym samym modelu - uczą się funkcji, które odpowiadają wielu ścieżkom na wykresie warstw.

Współdzielone warstwy są często używane do kodowania danych wejściowych z podobnych przestrzeni (powiedzmy dwóch różnych fragmentów tekstu, które mają podobne słownictwo). Umożliwiają one wymianę informacji w ramach tych różnych danych wejściowych 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 współdzieloną warstwę.

Aby udostępnić warstwę w funkcjonalnym interfejsie API, wywołaj tę samą instancję warstwy wiele razy. Na przykład, oto warstwa Embedding współdzielona z dwoma różnymi wejściami tekstowymi:

# 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 uzyskać do niego dostęp i go sprawdzić. W ten sposób możesz wykreślić modele funkcjonalne jako obrazy.

Oznacza to również, że możesz uzyskać dostęp do aktywacji warstw pośrednich („węzłów” 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 obciążeniami wstępnie wytrenowanymi 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 [==============================] - 20s 0us/step

A oto pośrednie aktywacje modelu, uzyskane przez odpytanie struktury danych wykresu:

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)

Przydaje się to między innymi w przypadku zadań, takich jak transfer stylu neuronowego .

Rozszerz interfejs API za pomocą niestandardowych warstw

tf.keras zawiera szeroką gamę wbudowanych warstw, na przykład:

  • Warstwy splotowe: Conv1D , Conv2D , Conv3D , Conv2DTranspose
  • Łączenie warstw: MaxPooling1D , MaxPooling2D , MaxPooling3D , AveragePooling1D
  • Warstwy RNN: 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 podklasują klasę Layer i implementują:

  • call , która określa obliczenia wykonywane przez warstwę.
  • build , która tworzy wagi warstwy (jest to tylko konwencja stylów, ponieważ możesz tworzyć wagi również w __init__ ).

Aby dowiedzieć się więcej o tworzeniu warstw od podstaw, przeczytaj przewodnik po niestandardowych warstwach i modelach .

Poniżej przedstawiono podstawową implementację 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ć obsługę serializacji w warstwie niestandardowej, zdefiniuj metodę get_config , 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 zaimplementuj metodę klasy from_config(cls, config) która jest używana podczas odtwarzania instancji warstwy, biorąc pod uwagę jej słownik konfiguracyjny. Domyślna implementacja from_config to:

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

Kiedy używać funkcjonalnego interfejsu API

Czy powinieneś używać funkcjonalnego interfejsu API Keras do tworzenia nowego modelu, czy po prostu bezpośrednio podklasować klasę Model ? Ogólnie rzecz biorąc, funkcjonalny interfejs API jest wyższego poziomu, łatwiejszy i bezpieczniejszy oraz ma wiele funkcji, których nie obsługują modele podklas.

Jednak podklasy modeli zapewniają większą elastyczność podczas budowania modeli, które nie są łatwe do wyrażenia jako skierowane acykliczne wykresy warstw. Na przykład, nie można zaimplementować Tree-RNN z funkcjonalnym interfejsem API i musiałbyś bezpośrednio podklasować Model .

Aby uzyskać szczegółowe informacje na temat różnic między funkcjonalnym interfejsem API a podklasą modelu, przeczytaj artykuł Czym są symboliczne i imperatywne interfejsy API w TensorFlow 2.0? .

Mocne strony funkcjonalnego API:

Następujące 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__(...) , no def call(self, ...): itd.

Porównać:

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

Z wersją podklasową:

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 podczas definiowania jego wykresu łączności

W funkcjonalnym API specyfikacja wejściowa (kształt i typ) jest tworzona z wyprzedzeniem (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 będzie działać każdy model, który można zbudować za pomocą funkcjonalnego interfejsu API. Wszelkie debugowanie - inne niż debugowanie związane ze zbieżnością - odbywa się statycznie podczas konstrukcji modelu, a nie w czasie wykonywania. Jest to podobne do sprawdzania typów w kompilatorze.

Model funkcjonalny można drukować 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 sklonować

Ponieważ model funkcjonalny jest strukturą danych, a nie fragmentem kodu, można go bezpiecznie serializować i zapisać jako pojedynczy plik, który umożliwia odtworzenie dokładnie tego samego modelu bez dostępu do żadnego z oryginalnego kodu. Zobacz przewodnik serializacji i zapisywania .

Aby serializować model podklasy, osoba wdrażająca musi określić get_config() i from_config() na poziomie modelu.

Słabość funkcjonalnego interfejsu API:

Nie obsługuje dynamicznych architektur

Funkcjonalne API traktuje modele jako DAG warstw. Dotyczy to większości architektur głębokiego uczenia, ale nie wszystkich - na przykład sieci rekurencyjne lub drzewa RNN nie są zgodne z tym założeniem i nie można ich zaimplementować w funkcjonalnym API.

Mieszaj i dopasowuj style API

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

Zawsze możesz użyć modelu funkcjonalnego lub modelu Sequential jako części modelu lub warstwy podklasowej:

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)

W funkcjonalnym interfejsie API można używać dowolnej warstwy lub modelu podklasowego, o ile implementuje on metodę call , która jest zgodna z jednym z następujących wzorców:

  • call(self, inputs, **kwargs) - gdzie inputs są tensorami lub zagnieżdżoną strukturą tensorów (np. lista tensorów), a **kwargs to argumenty inne niż tensorowe (non- **kwargs ).
  • call(self, inputs, training=None, **kwargs) - gdzie training jest wartością logiczną wskazującą, czy warstwa powinna zachowywać się w trybie uczenia i wnioskowania.
  • call(self, inputs, mask=None, **kwargs) - gdzie mask jest logicznym tensorem maski (przydatne na przykład dla RNN).
  • call(self, inputs, training=None, mask=None, **kwargs) - Oczywiście możesz mieć jednocześnie zachowanie maskowania i treningu.

Ponadto, jeśli zaimplementujesz metodę get_config w niestandardowej warstwie lub modelu, utworzone modele funkcjonalne będą nadal możliwe do serializacji i klonowania.

Oto krótki przykład niestandardowego RNN, napisanego od podstaw, używanego 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)))