Google I / O возвращается 18-20 мая! Зарезервируйте место и составьте свое расписание Зарегистрируйтесь сейчас

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

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

Настраивать

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

Вступление

Функциональный API tf.keras.Sequential - это способ создания моделей, более гибких, чем API tf.keras.Sequential . Функциональный 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() метод evaluate() ). Обратите внимание, что вы можете легко настроить эти циклы для реализации программ обучения, выходящих за рамки контролируемого обучения (например, GAN ).

Здесь загрузите данные изображения 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.5848 - accuracy: 0.8332 - val_loss: 0.1880 - val_accuracy: 0.9480
Epoch 2/2
750/750 [==============================] - 2s 2ms/step - loss: 0.1699 - accuracy: 0.9503 - val_loss: 0.1490 - val_accuracy: 0.9563
313/313 - 0s - loss: 0.1463 - accuracy: 0.9563
Test loss: 0.14626088738441467
Test accuracy: 0.9563000202178955

Дополнительную информацию см. В руководстве по обучению и оценке .

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

Сохранение модели и сериализация работают так же для моделей, построенных с использованием функционального 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")
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 слоя Conv2D является слой Conv2DTranspose , а обратной MaxPooling2D слоя 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
_________________________________________________________________

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

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=[1.0, 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 [==============================] - 4s 11ms/step - loss: 1.2978 - priority_loss: 0.7067 - department_loss: 2.9554
Epoch 2/2
40/40 [==============================] - 0s 11ms/step - loss: 1.2947 - priority_loss: 0.7023 - department_loss: 2.9621
<tensorflow.python.keras.callbacks.History at 0x7fe18923e6a0>

При вызове fit с объектом Dataset он должен ([title_data, body_data, tags_data], [priority_targets, dept_targets]) либо кортеж списков, например ([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 [==============================] - 6s 0us/step
13/13 [==============================] - 2s 35ms/step - loss: 2.2992 - acc: 0.1273 - val_loss: 2.2629 - val_acc: 0.1850
<tensorflow.python.keras.callbacks.History at 0x7fe210396ef0>

Общие слои

Еще одно хорошее применение функционального 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 [==============================] - 7s 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 : 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 по from_config :

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

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

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

Однако создание подклассов моделей обеспечивает большую гибкость при построении моделей, которые нелегко выразить как ориентированные ациклические графы слоев. Например, вы не можете реализовать Tree-RNN с функциональным 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 спецификация ввода (shape и 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 - это логический тензор маски (полезен, например, для RNN).
  • call(self, inputs, training=None, mask=None, **kwargs) - Конечно, вы можете одновременно использовать как маскировку, так и поведение, специфичное для обучения.

Кроме того, если вы реализуете метод get_config на своем пользовательском слое или модели, 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)))