Примеры и код для создания пользовательских моделей с помощью Keras Functional API

Функциональный API Keras позволяет создавать гибкие и сложные нейронные сети в TensorFlow. Функциональный API используется для проектирования нелинейных сетей. В этой статье вы узнаете, что функциональный API Keras используется для создания сетей, которые:

  • Нелинейны.
  • Делитесь слоями.
  • Иметь несколько входов и выходов.

Керас Последовательные модели

Мы использовали Sequential API в Учебнике CNN для построения модели классификации изображений с помощью Keras и TensorFlow. Sequential API включает в себя наложение слоев. За одним слоем следует другой слой до окончательного плотного слоя. Это делает проектирование сетей с помощью Sequential API простым и понятным.

parameters = {"shape":28, "activation": "relu", "classes": 10, "units":12, "optimizer":"adam", "epochs":1,"kernel_size":3,"pool_size":2, "dropout":0.5}
# Setup the layers
model = keras.Sequential(
  [
      layers.Conv2D(32, kernel_size=(parameters["kernel_size"], parameters["kernel_size"]), input_shape =(parameters["shape"], parameters["shape"], 1),activation=parameters["activation"]),
      layers.MaxPooling2D(pool_size=(parameters["pool_size"], parameters["pool_size"])),
      layers.Conv2D(64, kernel_size=(parameters["kernel_size"], parameters["kernel_size"]), activation=parameters["activation"]),
      layers.MaxPooling2D(pool_size=(parameters["pool_size"], parameters["pool_size"])),
      layers.Flatten(),
      layers.Dropout(parameters["dropout"]),
      layers.Dense(parameters["classes"], activation="softmax"),
  ]
)

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

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

Керас Функциональные модели

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

Определение ввода

Первое отличие заключается в необходимости создания входного слоя. С Sequential API вам не нужно определять входной слой. Достаточно определить входную форму в первом слое.

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

inputs = keras.Input(shape=(parameters["shape"], parameters["shape"], 1))
inputs.shape
# TensorShape([None, 28, 28, 1])
inputs.dtype
# tf.float32

Входной слой определяется без размера пакета, если данные одномерные.

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

Соединительные слои

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

conv2D = layers.Conv2D(32)
x = conv2D(inputs)
x
# <KerasTensor: shape=(None, 26, 26, 32) dtype=float32 (created by layer 'conv2d_7')>

В приведенном выше примере мы создаем слой Conv2D, вызываем его как функцию и передаем входные данные. Форма результирующего вывода отличается от исходной формы inputs в результате передачи на слой свертки.

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

В приведенном выше примере показано, как подробно определить и соединить сети. Однако синтаксис можно упростить. Упрощенная версия выглядит так:

conv2D = Conv2d(...) (inputs)

conv2D() похож на conv2D.__call__(self,....). Объекты Python реализуют метод __call__(). Слои Keras также реализуют этот метод. Метод возвращает выходные данные с учетом входного тензора.

inputs = keras.Input(shape=(parameters["shape"], parameters["shape"], 1))
conv2D = layers.Conv2D(32, kernel_size=(parameters["kernel_size"], parameters["kernel_size"]), input_shape =(parameters["shape"], parameters["shape"], 1),activation=parameters["activation"])(inputs)
conv2D
# <KerasTensor: shape=(None, 26, 26, 32) dtype=float32 (created by layer 'conv2d_8')>

Создание модели

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

parameters = {"shape":28, "activation": "relu", "classes": 10, "units":12, "optimizer":"adam", "epochs":1,"kernel_size":3,"pool_size":2, "dropout":0.5}
inputs = keras.Input(shape=(parameters["shape"], parameters["shape"], 1))
conv2D = layers.Conv2D(32, kernel_size=(parameters["kernel_size"], parameters["kernel_size"]), input_shape =(parameters["shape"], parameters["shape"], 1),activation=parameters["activation"])(inputs)
maxPooling2D = layers.MaxPooling2D(pool_size=(parameters["pool_size"], parameters["pool_size"]))(conv2D)
conv2D_2 =layers.Conv2D(64, kernel_size=(parameters["kernel_size"], parameters["kernel_size"]), activation=parameters["activation"])(maxPooling2D)
maxPooling2D_2 = layers.MaxPooling2D(pool_size=(parameters["pool_size"], parameters["pool_size"]))(conv2D_2)
flatten =   layers.Flatten()(maxPooling2D_2)
dropout = layers.Dropout(parameters["dropout"])(flatten)
ouputs = layers.Dense(parameters["classes"], activation="softmax")(dropout)

Модель Keras создается с помощью функции keras.Model при передаче inputs и outputs.

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

Мы можем построить модель, чтобы убедиться, что она похожа на ту, которую мы определили с помощью Sequential API.

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

Обучение и оценка моделей функционального API

Модели обучения и оценки одинаковы в Functional API и Sequential API. keras.Model использует методы fit и evaluate.

(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
x_train = x_train.astype("float32") / 255
x_test = x_test.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])

Сохраняйте и сериализуйте функциональные модели API

Сохранение и сериализация модели работают одинаково в Functional API и Sequential API. Например, мы можем сохранить всю модель, используя model.save().

model.save("saved_model")
del model
model = keras.models.load_model("saved_model")
model.summary()

Как преобразовать функциональную модель в последовательную модель API

Функциональную модель с линейными слоями можно преобразовать в последовательную модель, создав экземпляр Sequential и добавив слои.

seq_model = keras.models.Sequential()
for layer in model.layers:
    seq_model.add(layer)
seq_model.summary()

Как преобразовать последовательную модель в функциональную модель API

Точно так же мы можем преобразовать последовательные сети в функциональные модели.

inputs = keras.Input(batch_shape=seq_model.layers[0].input_shape)
x = inputs
for layer in seq_model.layers:
    x = layer(x) 
outputs = x
func_model = keras.Model(inputs=inputs, outputs=outputs, name="func_mnist_model")
func_model.summary()

Стандартные сетевые модели

Давайте посмотрим, как определить стандартные нейронные сети с помощью функционального API Keras.

Многослойное восприятие

Мы начинаем с определения нейронной сети с несколькими скрытыми слоями и построения модели.

inputs = keras.Input(shape=(parameters["shape"], parameters["shape"], 1))
dense1 = layers.Dense(128)(inputs)
dropout = layers.Dropout(parameters["dropout"])(dense1)
dense2 = layers.Dense(128)(dropout)
dropout1 = layers.Dropout(parameters["dropout"])(dense2)
outputs = layers.Dense(parameters["classes"], activation="softmax")(dropout1)
model = keras.Model(inputs=inputs, outputs=outputs, name="mnist_model")
keras.utils.plot_model(model, "model.png",show_shapes=True)

Сверточная нейронная сеть

Далее мы рассмотрим, как определить Сверточные нейронные сети с помощью функционального API. Сеть имеет свертки, пулы, плоские и плотные слои.

inputs = keras.Input(shape=(parameters["shape"], parameters["shape"], 1))
conv2D = layers.Conv2D(32, kernel_size=(parameters["kernel_size"], parameters["kernel_size"]), input_shape =(parameters["shape"], parameters["shape"], 1),activation=parameters["activation"])(inputs)
maxPooling2D = layers.MaxPooling2D(pool_size=(parameters["pool_size"], parameters["pool_size"]))(conv2D)
conv2D_2 =layers.Conv2D(64, kernel_size=(parameters["kernel_size"], parameters["kernel_size"]), activation=parameters["activation"])(maxPooling2D)
maxPooling2D_2 = layers.MaxPooling2D(pool_size=(parameters["pool_size"], parameters["pool_size"]))(conv2D_2)
flatten =   layers.Flatten()(maxPooling2D_2)
dropout = layers.Dropout(parameters["dropout"])(flatten)
outputs = layers.Dense(parameters["classes"], activation="softmax")(dropout)
model = keras.Model(inputs=inputs, outputs=outputs, name="mnist_model")
keras.utils.plot_model(model, "model.png",show_shapes=True)

Рекуррентная нейронная сеть

Давайте посмотрим на определение двунаправленного LSTM с использованием функционального API. Сеть содержит Слой встраивания.

inputs = keras.Input(784,)
embedding = layers.Embedding(512, 64, input_length=1024)(inputs)
bidirectional1 = layers.Bidirectional(layers.LSTM(64, return_sequences=True))(embedding)
bidirectional2 = layers.Bidirectional(layers.LSTM(64,))(bidirectional1)
dense1 = layers.Dense(32, activation='relu')(bidirectional2)
outputs = layers.Dense(1, activation='sigmoid')(dense1)
model = keras.Model(inputs=inputs, outputs=outputs, name="lstm_model")
keras.utils.plot_model(model, "model.png",show_shapes=True)

Модель общих слоев

Определение слоев с помощью функционального API позволяет создавать сети с общими слоями. Общие слои используются в сети несколько раз.

Общий входной слой

Этот пример определяет CNN с одним входным слоем, общим для двух блоков свертки. Затем мы соединяем выходные данные этих блоков, используя слой concatenate. После этого мы передаем результат на слой DropOut и, наконец, на полносвязный слой.

inputs = keras.Input(shape=(parameters["shape"], parameters["shape"], 1))
conv2D = layers.Conv2D(32, kernel_size=(parameters["kernel_size"], parameters["kernel_size"]), input_shape =(parameters["shape"], parameters["shape"], 1),activation=parameters["activation"])(inputs)
maxPooling2D = layers.MaxPooling2D(pool_size=(parameters["pool_size"], parameters["pool_size"]))(conv2D)
flatten1 =   layers.Flatten()(maxPooling2D)
conv2D_2 = layers.Conv2D(64, kernel_size=(parameters["kernel_size"], parameters["kernel_size"]), activation=parameters["activation"])(inputs)
maxPooling2D_2 = layers.MaxPooling2D(pool_size=(parameters["pool_size"], parameters["pool_size"]))(conv2D_2)
flatten2 =   layers.Flatten()(maxPooling2D_2)
# merge layers
merged_layers = layers.concatenate([flatten1, flatten2])
dropout = layers.Dropout(parameters["dropout"])(merged_layers)
outputs = layers.Dense(parameters["classes"], activation="softmax")(dropout)
model = keras.Model(inputs=inputs, outputs=outputs, name="mnist_model")
keras.utils.plot_model(model, "model.png",show_shapes=True)

Построение сети показывает связь между различными слоями.

Слой извлечения общих объектов

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

inputs = keras.Input(784,)
embedding = layers.Embedding(512, 64, input_length=1024)(inputs)
bidirectional1 = layers.Bidirectional(layers.LSTM(64, return_sequences=True))(embedding)
bidirectional2 = layers.Bidirectional(layers.LSTM(64, return_sequences=True))(embedding)
# merge layers
merged_layers = layers.concatenate([bidirectional1, bidirectional2])
dense1 = layers.Dense(32, activation='relu')(merged_layers)
outputs = layers.Dense(1, activation='sigmoid')(dense1)
model = keras.Model(inputs=inputs, outputs=outputs, name="lstm_model")
keras.utils.plot_model(model, "model.png",show_shapes=True)

Далее давайте обсудим сценарий с несколькими входами и выходами.

Несколько моделей ввода и вывода

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

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

В этом примере мы определяем сеть, которая принимает два входа разной длины. Мы передаем входные данные плотным слоям и суммируем их, используя слой add.

input1 = keras.Input(shape=(16,))
x1 =layers.Dense(8, activation='relu')(input1)
input2 = layers.Input(shape=(32,))
x2 = layers.Dense(8, activation='relu')(input2)
# equivalent to `added = tf.keras.layers.add([x1, x2])`
added = layers.Add()([x1, x2])
out = layers.Dense(4)(added)
model = keras.Model(inputs=[input1, input2], outputs=out)
keras.utils.plot_model(model, "model.png",show_shapes=True)

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

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

image_input = keras.Input(shape=(parameters["shape"], parameters["shape"], 3), name="images") 
x = layers.Conv2D(filters=32,kernel_size=(3,3),activation='relu')(image_input)
x = layers.MaxPooling2D(pool_size=(2,2))(x)
x = layers.Conv2D(filters=32,kernel_size=(3,3), activation='relu')(x)
x = layers.Dropout(0.25)(x)
x = layers.Conv2D(filters=64,kernel_size=(3,3), activation='relu')(x)
x = layers.MaxPooling2D(pool_size=(2,2))(x)
x = layers.Dropout(0.25)(x)
x = layers.Flatten()(x)
x = layers.Dense(128, activation='relu')(x)
x = layers.Dropout(0.25)(x)
gender_prediction = layers.Dense(3, activation='softmax')(x)
age_prediction = layers.Dense(3, activation='softmax')(x)
model = keras.Model(
    inputs=image_input,
    outputs=[gender_prediction, age_prediction],
)
keras.utils.plot_model(model, "model.png",show_shapes=True)

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

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

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

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

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

  • Это менее многословно по сравнению с подклассом класса Model.
  • Требование создать Input гарантирует, что все функциональные сети будут работать, поскольку передача неправильной формы приводит к немедленной ошибке.
  • Функциональную модель проще построить и проверить.
  • Легко сериализовать и сохранять функциональные модели, поскольку они представляют собой структуры данных.

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

Лучшие практики функционального API

Помните о лучших практиках при работе с функциональным API Keras:

  • Всегда печатайте сводку сети, чтобы убедиться, что формы различных слоев соответствуют ожидаемым.
  • Постройте сеть, чтобы убедиться, что слои соединены так, как вы ожидаете.
  • Назовите слои, чтобы их было легко идентифицировать на сетевом графике и в сводке. Например Conv2D(...,name="first_conv_layer").
  • Используйте имена переменных, связанные со слоями, например, conv1 и conv2 для слоев свертки. Это позволит уточнить тип слоя при проверке графиков и сводки по сети.
  • Вместо создания настраиваемого цикла обучения используйте keras.Model для создания моделей, поскольку это упрощает обучение моделей с помощью метода fit и их оценку с помощью метода evalaute.

Последние мысли

В этой статье вы узнали, что можете проектировать нейронные сети в Keras, используя Sequential API. В частности, мы охватили:

  • Как определить функциональные модели в Keras.
  • Как обучать и оценивать сети Keras Sequential.
  • Определение сетей Keras с несколькими входами и выходами.
  • Как строить и проверять модели Keras Sequential.
  • Извлечение признаков с помощью сетей Keras Sequential.

Подпишитесь на меня в LinkedIn для получения дополнительных технических ресурсов.