Функциональный API

Посмотреть на TensorFlow.org Запустить в Google Colab Посмотреть исходный код на GitHub Скачать блокнот

Настраивать

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

Вступление

Функциональный API Keras является способом создания моделей , которые являются более гибкими , чем tf.keras.Sequential API. Функциональный API может обрабатывать модели с нелинейной топологией, общими слоями и даже с несколькими входами или выходами.

Основная идея заключается в том, что модель глубокого обучения обычно представляет собой ориентированный ациклический граф (DAG) слоев. Таким образом , функциональный API является способом построения графиков слоев.

Рассмотрим следующую модель:

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

Это базовый график с тремя слоями. Чтобы построить эту модель с использованием функционального API, начните с создания входного узла:

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

Форма данных задается как 784-мерный вектор. Размер партии всегда опускается, поскольку указывается только форма каждого образца.

Если, например, у Вас есть входное изображение с формой (32, 32, 3) , можно использовать:

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

На inputs , возвращаемые содержат информацию о форме и dtype входных данных , которые вы кормите к вашей модели. Вот форма:

inputs.shape
TensorShape([None, 784])

Вот dtype:

inputs.dtype
tf.float32

Вы можете создать новый узел в графе слоев, вызывая слой на этом inputs объекта:

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

Действие «вызова слоя» похоже на рисование стрелки от «входов» к созданному вами слою. Вы «проходя» входы в dense слой, и вы получите x как выход.

Добавим еще несколько слоев в граф слоев:

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

На данный момент, вы можете создать Model , указав свои входы и выходы в графе слоев:

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

Давайте посмотрим, как выглядит сводка модели:

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
_________________________________________________________________

Вы также можете построить модель в виде графика:

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

PNG

И, при желании, отобразите входные и выходные формы каждого слоя на построенном графике:

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

PNG

Этот рисунок и код практически идентичны. В кодовой версии стрелки подключения заменены операцией вызова.

«Граф слоев» - это интуитивно понятный мысленный образ модели глубокого обучения, а функциональный API - это способ создания моделей, который точно отражает это.

Обучение, оценка и выводы

Обучение, оценка и вывод работы точно так же , как для моделей , построенных с использованием функционала API как для Sequential моделей.

В Model предложения класса встроенный в тренировочном цикле ( fit() метод) и встроенный в цикле оценки ( evaluate() метод). Обратите внимание , что вы можете легко настроить эту петлю реализовать учебные процедуры вне контролируемого обучения (например , Gans ).

Здесь загрузите данные изображения MNIST, преобразуйте их в векторы, поместите модель в данные (при мониторинге производительности на проверочном разбиении), затем оцените модель на тестовых данных:

(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

Для дальнейшего чтения, см обучения и оценки руководства.

Сохранить и сериализовать

Сохранение модели , а Сериализация работать таким же образом для моделей , построенных с использованием функционального API , как они делают для Sequential моделей. Стандартный способ сохранения функциональной модели является вызов model.save() , чтобы сохранить всю модель в виде отдельного файла. Позже вы можете воссоздать ту же модель из этого файла, даже если код, построивший модель, больше не доступен.

Этот сохраненный файл включает:

  • модель архитектуры
  • модельные значения веса (которые были изучены во время обучения)
  • модель обучения конфигурации, если таковые имеются (как передается compile )
  • оптимизатор и его состояние, если есть (чтобы перезапустить обучение с того места, где вы остановились)
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

Подробности читайте модель сериализации & экономии руководство.

Используйте один и тот же график слоев для определения нескольких моделей

В функциональном API модели создаются путем указания их входных и выходных данных в виде графа слоев. Это означает, что один граф слоев может использоваться для создания нескольких моделей.

В приведенном ниже примере используется один и тот же стек слоев два экземпляра модели: encoder модель , которая превращает изображение входа в 16-мерных векторов, а конец в конец autoencoder модель для обучения.

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
_________________________________________________________________

Здесь декодирование архитектура строго симметрична по отношению к архитектуре кодирования, так что форма выходной такой же , как форма входного сигнала (28, 28, 1) .

Реверс Conv2D слоя является Conv2DTranspose слой, а обратный из MaxPooling2D слоя является UpSampling2D слой.

Все модели вызываемые, как и слои

Вы можете относиться к любой модели , как если бы он был слой, вызывая его на Input или на выходе другого слоя. Вызывая модель, вы не просто повторно используете архитектуру модели, вы также повторно используете ее веса.

Чтобы увидеть это в действии, вот другой подход к примеру автокодера, который создает модель кодировщика, модель декодера, и объединяет их в два вызова для получения модели автокодера:

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
_________________________________________________________________

Как видите, модель может быть вложенной: модель может содержать подмодели (поскольку модель подобна слою). Обычный пример использования модели вложенности ensembling. Например, вот как объединить набор моделей в единую модель, которая усредняет их прогнозы:

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)

Управление топологиями сложных графов

Модели с несколькими входами и выходами

Функциональный API позволяет легко управлять несколькими входами и выходами. Это не может быть обработан с помощью Sequential API.

Например, если вы создаете систему для ранжирования заявок клиентов по приоритету и направления их в нужный отдел, то модель будет иметь три входа:

  • название билета (ввод текста),
  • текстовое тело заявки (ввод текста) и
  • любые теги, добавленные пользователем (категориальный ввод)

У этой модели будет два выхода:

  • оценка приоритета между 0 и 1 (скалярный сигмовидный выход), и
  • отдел, который должен обрабатывать заявку (вывод softmax по набору отделов).

Вы можете построить эту модель в несколько строк с помощью функционального 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],
)

Теперь постройте модель:

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

PNG

При составлении этой модели вы можете назначить разные потери для каждого выхода. Вы даже можете присвоить разный вес каждой потере, чтобы модулировать их вклад в общую потерю тренировки.

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

Поскольку выходные слои имеют разные имена, вы также можете указать потери и веса потерь с соответствующими именами слоев:

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

Обучите модель, передав списки массивов 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 [==============================] - 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>

При вызове подгонку с Dataset объекта, он должен давать либо кортеж списков , таких как ([title_data, body_data, tags_data], [priority_targets, dept_targets]) или кортеж словарей , таких как ({'title': title_data, 'body': body_data, 'tags': tags_data}, {'priority': priority_targets, 'department': dept_targets}) .

Для более подробного объяснения, обратитесь к обучению и оценке руководства.

Игрушечная модель ResNet

В дополнение к модели с несколькими входами и выходами, функциональный API , позволяет легко манипулировать нелинейные топологии подключения - это модели со слоями, которые не соединены последовательно, которых Sequential API , не может обрабатывать.

Обычный вариант использования для этого - остаточные соединения. Давайте создадим игрушечную модель ResNet для CIFAR10, чтобы продемонстрировать это:

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
__________________________________________________________________________________________________

Постройте модель:

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

PNG

Теперь обучите модель:

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

Общие слои

Другое использование хорошо для функционального API являются моделями , которые используют общие слои. Общие слои - это экземпляры слоев, которые многократно используются в одной и той же модели - они изучают функции, соответствующие нескольким путям в графе слоев.

Общие слои часто используются для кодирования входных данных из одинаковых пространств (скажем, двух разных фрагментов текста с одинаковым словарным запасом). Они позволяют обмениваться информацией между этими различными входами и позволяют обучать такую ​​модель на меньшем количестве данных. Если заданное слово присутствует на одном из входов, это улучшит обработку всех входных данных, которые проходят через общий уровень.

Чтобы поделиться слоем в функциональном API, вызовите один и тот же экземпляр слоя несколько раз. Например, вот Embedding слой разделяют на два различных текстовых входов:

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

Извлечение и повторное использование узлов в графе слоев

Поскольку граф слоев, с которыми вы работаете, представляет собой статическую структуру данных, к нему можно получить доступ и проверить. И вот как вы можете строить функциональные модели в виде изображений.

Это также означает, что вы можете получить доступ к активациям промежуточных слоев («узлы» на графике) и повторно использовать их в другом месте - что очень полезно для чего-то вроде извлечения признаков.

Давайте посмотрим на пример. Это модель VGG19 с весами, предварительно обученными в 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

А это промежуточные активации модели, полученные при запросе структуры данных графа:

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

Используйте эти функции для создания новой модели извлечения признаков, которая возвращает значения активаций промежуточного слоя:

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)

Это удобно для таких задач , как нейронные передачи стиля , среди других вещей.

Расширьте API с помощью пользовательских слоев

tf.keras включает в себя широкий спектр встроенных слоев, например:

  • Сверточные слои: Conv1D , Conv2D , Conv3D , Conv2DTranspose
  • Объединение средств слоев: MaxPooling1D , MaxPooling2D , MaxPooling3D , AveragePooling1D
  • РНН слои: GRU , LSTM , ConvLSTM2D
  • BatchNormalization , Dropout , Embedding и т.д.

Но если вы не найдете то, что вам нужно, легко расширить API, создав свои собственные слои. Все слои подкласс Layer класса и реализация:

  • call метода, что определяет вычисление делается слоем.
  • build метод, который создает вес слоя (это только условность стиля , так как вы можете создать веса в __init__ , а).

Для более узнать о создании слоев с нуля, прочитать пользовательские слои и модели руководства.

Ниже приводится базовая реализация 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)

Для поддержки сериализации в пользовательском уровне, определить get_config метод , который возвращает аргументы конструктора экземпляра слоя:

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

Необязательно, реализовать метод класса from_config(cls, config) , который используется при воссоздании экземпляра слоя с учетом его конфигурации словаря. Реализация по умолчанию from_config является:

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

Когда использовать функциональный API

Если вы используете функциональный API Keras для создания новой модели, или просто подкласс Model класса напрямую? В общем, функциональный API более высокоуровневый, проще и безопаснее и имеет ряд функций, которые не поддерживаются подклассами моделей.

Однако создание подклассов моделей обеспечивает большую гибкость при построении моделей, которые нелегко выразить как ориентированные ациклические графы слоев. Например, вы не могли бы реализовать Tree-РНН с функциональным API и будет иметь подкласс Model непосредственно.

Для взгляда углубленных на различиях между функциональным API и моделью подклассами, читайте Что Символический и императивный API , в TensorFlow 2.0? .

Сильные стороны функционального API:

Следующие свойства также верны для последовательных моделей (которые также являются структурами данных), но не верны для моделей с подклассами (которые являются байт-кодом Python, а не структурами данных).

Менее подробный

Там нет super(MyClass, self).__init__(...) , нет def call(self, ...): и т.д.

Сравнивать:

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

С версией с подклассом:

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

Проверка модели при определении ее графа связности

В функциональном API, спецификация входного сигнала (форма и DTYPE) создается заранее (используя Input ). Каждый раз, когда вы вызываете уровень, он проверяет, соответствует ли переданная ему спецификация его предположениям, и в противном случае выдает полезное сообщение об ошибке.

Это гарантирует, что любая модель, которую вы можете построить с помощью функционального API, будет работать. Вся отладка, кроме отладки, связанной с конвергенцией, происходит статически во время построения модели, а не во время выполнения. Это похоже на проверку типов в компиляторе.

Функциональная модель может быть построена и проверена.

Вы можете построить модель в виде графика и легко получить доступ к промежуточным узлам на этом графике. Например, чтобы извлечь и повторно использовать активации промежуточных слоев (как показано в предыдущем примере):

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

Функциональная модель может быть сериализована или клонирована.

Поскольку функциональная модель представляет собой структуру данных, а не фрагмент кода, ее можно безопасно сериализовать и можно сохранить в виде единого файла, который позволяет воссоздать ту же самую модель без доступа к исходному коду. См сериализации и экономии руководство .

Для сериализации подклассы модели, необходимо для реализатора указать get_config() и from_config() метод на уровне модели.

Слабость функционального API:

Он не поддерживает динамические архитектуры.

Функциональный API обрабатывает модели как группы DAG слоев. Это верно для большинства архитектур глубокого обучения, но не для всех - например, рекурсивные сети или Tree RNN не следуют этому предположению и не могут быть реализованы в функциональном API.

Сочетание стилей API

Выбор между функциональным API или подклассом модели - это не бинарное решение, которое ограничивает вас одной категорией моделей. Все модели в tf.keras API могут взаимодействовать друг с другом, являются ли они Sequential модели, функциональные модели, или подклассов модели, которые написаны с нуля.

Вы всегда можете использовать функциональную модель или Sequential модель как часть подклассы модели или слоя:

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)

Вы можете использовать любую подклассы слой или модель в функциональном API до тех пор , как он реализует call метод , который следует один из следующих моделей:

  • call(self, inputs, **kwargs) - Где inputs является тензором или вложенная структура тензоров (например, список тензоров), и где **kwargs не являются тензорных аргументов (не-входы).
  • call(self, inputs, training=None, **kwargs) - Где training является логическое значение , указывающее , должен ли слой вести себя в тренировочном режиме и режиме вывода.
  • call(self, inputs, mask=None, **kwargs) - Где mask представляет булев тензор маски (полезно для RNNs, например).
  • call(self, inputs, training=None, mask=None, **kwargs) - Конечно, вы можете иметь как маскировку и поведение обучения конкретным одновременно.

Кроме того, если вы реализуете get_config метод на пользовательском уровне или модели, функциональные модели , которые вы создаете еще будет сериализации и клонируемыми.

Вот краткий пример использования пользовательской RNN, написанной с нуля, в функциональной модели:

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