In [ ]:
#@title Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
Note: Вся информация в этом разделе переведена с помощью русскоговорящего Tensorflow сообщества на общественных началах. Поскольку этот перевод не является официальным, мы не гарантируем что он на 100% аккуратен и соответствует официальной документации на английском языке. Если у вас есть предложение как исправить этот перевод, мы будем очень рады увидеть pull request в tensorflow/docs репозиторий GitHub. Если вы хотите помочь сделать документацию по Tensorflow лучше (сделать сам перевод или проверить перевод подготовленный кем-то другим), напишите нам на docs-ru@tensorflow.org list.
In [ ]:
from __future__ import absolute_import, division, print_function, unicode_literals
try:
# %tensorflow_version существует только Colab.
%tensorflow_version 2.x
except Exception:
pass
import tensorflow as tf
tf.keras.backend.clear_session() # Для простого сброса состояния ноутбука.
Вы уже знакомы с использованием keras.Sequential()
для создания моделей.
Functional API позволяет создавать модели более гибко чем Sequential
:
он может обрабатывать модели с нелинейной топологией, модели с общими слоями,
и модели с несколькими входами или выходами.
Он основан на том, что модель глубоко обучения обычно представляет собой ориентированный ациклический граф (DAG) слоев. Functional API - это набор инструментов для построения графа слоев.
Рассмотрим следующую модель:
(вход: 784-мерный вектор)
↧
[Плотный слой (64 элемента, активация relu)]
↧
[Плотный слой (64 элемента, активация relu)]
↧
[Плотный слой (10 элементов, активация softmax)]
↧
(выход: вероятностное распределение на 10 классов)
Это простой граф из 3 слоев.
Для построения этой модели с помощью functional API, вам надо начать с создания входного узла:
In [ ]:
from tensorflow import keras
inputs = keras.Input(shape=(784,))
Здесь мы просто указываем размерность наших данных: 784-мерных векторов.
Обратите внимание, что количество данных всегда опускается, мы указываем только размерность каждого элемента.
Для ввода предназначенного для изображений размеров (32, 32, 3)
, мы бы использовали:
In [ ]:
img_inputs = keras.Input(shape=(32, 32, 3))
То, что возвращает inputs
, содержит информацию о размерах и типе данных
которые вы планируете передать в вашу модель:
In [ ]:
inputs.shape
In [ ]:
inputs.dtype
Вы создаете новый узел в графе слоев, вызывая слой на этом объекте inputs
:
In [ ]:
from tensorflow.keras import layers
dense = layers.Dense(64, activation='relu')
x = dense(inputs)
"Вызов слоя" аналогичен рисованию стрелки из "входных данных" в созданный нами слой.
Мы "передаем" входные данные в dense
слой, и мы получаем x
.
Давайте добавим еще несколько слоев в наш граф слоев:
In [ ]:
x = layers.Dense(64, activation='relu')(x)
outputs = layers.Dense(10, activation='softmax')(x)
Сейчас мы можем создать Model
указав его входы и выходы в графе слоев:
In [ ]:
model = keras.Model(inputs=inputs, outputs=outputs)
Напомним полный процесс определения модели:
In [ ]:
inputs = keras.Input(shape=(784,), name='img')
x = layers.Dense(64, activation='relu')(inputs)
x = layers.Dense(64, activation='relu')(x)
outputs = layers.Dense(10, activation='softmax')(x)
model = keras.Model(inputs=inputs, outputs=outputs, name='mnist_model')
Давайте посмотрим как выглядит сводка модели:
In [ ]:
model.summary()
Мы также можем начертить модель в виде графа:
In [ ]:
keras.utils.plot_model(model, 'my_first_model.png')
И опционально выведем размерности входа и выхода каждого слоя на построенном графе:
In [ ]:
keras.utils.plot_model(model, 'my_first_model_with_shape_info.png', show_shapes=True)
Это изображение и код который мы написали идентичны. В версии кода, связывающие стрелки просто заменены операциями вызова.
"Граф слоев" это очень интуитивный ментальный образ для модели глубокого обучения, а functional API это способ создания моделей которые близко отражают этот ментальный образ.
Обучение, оценка и вывод работают для моделей построенных с использованием Functional API точно так же как и в Sequential моделях.
Вот быстрая демонстрация.
Тут мы загружаем датасет изображений MNIST, преобразуем его в векторы, обучаем модель на данных (мониторя при этом качество работы на проверочной выборке), и наконец мы оцениваем нашу модель на тестовых данных:
In [ ]:
(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='sparse_categorical_crossentropy',
optimizer=keras.optimizers.RMSprop(),
metrics=['accuracy'])
history = model.fit(x_train, y_train,
batch_size=64,
epochs=5,
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])
Полное руководство посвященное обучению и оценки моделей, см. по ссылке руководство обучения и оценки.
Сохранение и сериализация для моделей построенных с использованием Functional API работает точно так же как и для Sequential моделей.
Стандартным способом сохранения Functional модели является вызов model.save()
позволяющий сохранить всю модель в один файл.
Позже вы можете восстановить ту же модель из этого файла, даже если у вас больше нет доступа к коду
создавшему модель.
Этот файл включает:
compile
)
In [ ]:
model.save('path_to_my_model.h5')
del model
# Восстановить в точности ту же модель исключительно из файла:
model = keras.models.load_model('path_to_my_model.h5')
Полное руководство по сохранению моделей см. в Руководство по сохранению и сериализации моделей.
В functional API, модели создаются путем указания входных и выходных данных в графе слоев. Это значит что один граф слоев может быть использован для генерации нескольких моделей.
В приведенном ниже примере мы используем один и тот же стек слоев для создания двух моделей:
модель кодировщика (encoder)
которая преобразует входные изображения в 16-мерные вектора,
и сквозную модель автокодировщика (autoencoder)
для обучения.
In [ ]:
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()
Обратите внимание, что мы делаем архитектуру декодирования строго симметричной архитектуре кодирования,
так что мы получим размерность выходных данных такую же как и входных данных (28, 28, 1)
.
Обратным к слою Conv2D
является слой Conv2DTranspose
, а обратным к слою MaxPooling2D
будет слой UpSampling2D
.
Вы можете использовать любую модель так, как если бы это был слой вызывая ее на Input
или на выход другого слоя.
Обратите внимание, что вызывая модель, вы не только переиспользуете ее архитектуру, вы также повторно используете ее веса.
Давайте увидим это в действии. Вот другой взгляд на пример автокодировщика, когда создается модель кодировщика, модель декодировщика, и они связываются в два вызова для получения модели автокодировщика:
In [ ]:
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()
Как вы видите, модель может быть вложена: модель может содержать подмодель (поскольку модель можно рассматривать как слой).
Распространенным вариантом использования вложения моделей является ensembling. В качестве примера, вот как можно объединить набор моделей в одну модель которая усредняет их прогнозы:
In [ ]:
def get_model():
inputs = keras.Input(shape=(128,))
outputs = layers.Dense(1, activation='sigmoid')(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)
Functional API упрощает манипуляции с несколькими входами и выходами. Это не может быть сделано с Sequential API.
Вот простой пример.
Допустим, вы создаете систему для ранжирования клиентских заявок по приоритетам и направления их в нужный отдел.
У вашей модели будет 3 входа:
У модели будет 2 выхода:
Давайте построим модель в несколько строк с помощью Functional API.
In [ ]:
num_tags = 12 # Количество различных тегов проблем
num_words = 10000 # Размер словаря полученный в результате предобработки текстовых данных
num_departments = 4 # Количество отделов для предсказаний
title_input = keras.Input(shape=(None,), name='title') # Последовательность целых чисел переменной длины
body_input = keras.Input(shape=(None,), name='body') # Последовательность целых чисел переменной длины
tags_input = keras.Input(shape=(num_tags,), name='tags') # Бинарный вектор размера `num_tags`
# Вложим каждое слово заголовка в 64-мерный вектор
title_features = layers.Embedding(num_words, 64)(title_input)
# Вложим каждое слово текста в 64-мерный вектор
body_features = layers.Embedding(num_words, 64)(body_input)
# Сокращаем последовательность вложенных слов заголовка до одного 128-мерного вектора
title_features = layers.LSTM(128)(title_features)
# Сокращаем последовательность вложенных слов содержимого до одного 32-мерного вектора
body_features = layers.LSTM(32)(body_features)
# Объединим все признаки в один вектор с помощью конкатенации
x = layers.concatenate([title_features, body_features, tags_input])
# Добавим логистическую регрессию для прогнозирования приоритета по признакам
priority_pred = layers.Dense(1, activation='sigmoid', name='priority')(x)
# Добавим классификатор отделов прогнозирующий на признаках
department_pred = layers.Dense(num_departments, activation='softmax', name='department')(x)
# Создание сквозной модели, прогнозирующей приоритет и отдел
model = keras.Model(inputs=[title_input, body_input, tags_input],
outputs=[priority_pred, department_pred])
Давайте начертим граф модели:
In [ ]:
keras.utils.plot_model(model, 'multi_input_and_output_model.png', show_shapes=True)
При компиляции этой модели, мы можем присвоить различные функции потерь каждому выходу. Вы даже можете присвоить разные веса каждой функции потерь, чтобы варьировать их вклад в общую функцию потерь обучения.
In [ ]:
model.compile(optimizer=keras.optimizers.RMSprop(1e-3),
loss=['binary_crossentropy', 'categorical_crossentropy'],
loss_weights=[1., 0.2])
Так как мы дали имена нашим выходным слоям, мы можем также указать функции потерь:
In [ ]:
model.compile(optimizer=keras.optimizers.RMSprop(1e-3),
loss={'priority': 'binary_crossentropy',
'department': 'categorical_crossentropy'},
loss_weights=[1., 0.2])
Мы можем обучить модель передавая списки массивов Numpy входных данных и меток:
In [ ]:
import numpy as np
# Учебные входные данные
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')
# Учебные целевые данные
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)
При вызове fit с объектом 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})
.
Для более подробного объяснения обратитесь к полному руководству руководство по обучению и оценке.
В дополнение к моделям с несколькими входами и выходами, Functional API упрощает манипулирование топологиями с нелинейной связностью, то есть моделями, в которых слои не связаны последовательно. Это также не может быть реализовано с помощью Sequential API (это видно из названия).
Распространенный пример использования этого - residual connections.
Давайте построим учебную ResNet модель для CIFAR10 чтобы продемонстрировать это.
In [ ]:
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, activation='softmax')(x)
model = keras.Model(inputs, outputs, name='toy_resnet')
model.summary()
Давайте начертим модель:
In [ ]:
keras.utils.plot_model(model, 'mini_resnet.png', show_shapes=True)
Давайте обучим ее:
In [ ]:
(x_train, y_train), (x_test, y_test) = keras.datasets.cifar10.load_data()
x_train = x_train.astype('float32') / 255.
x_test = x_test.astype('float32') / 255.
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='categorical_crossentropy',
metrics=['acc'])
model.fit(x_train, y_train,
batch_size=64,
epochs=1,
validation_split=0.2)
Другим хорошим использованием functional API являются модели, использующие общие слои. Общие слои - это экземпляры слоев, которые переиспользуются в одной и той же модели: они изучают признаки, которые относятся к нескольким путям в графе слоев.
Общие слои часто используются для кодирования входных данных, которые поступают из одинаковых пространств (скажем, из двух разных фрагментов текста, имеющих одинаковый словарь), поскольку они обеспечивают обмен информацией между этими различными данными, что позволяет обучать такие модели на меньшем количестве данных. Если определенное слово появилось на одном из входов, это будет способствовать его обработке на всех входах, которые проходят через общий уровень.
Чтобы совместно использовать слой в Functional API, просто вызовите тот же экземпляр слоя несколько раз. Например, здесь слой Embedding
используется совместно на двух текстовых входах:
In [ ]:
# Вложения для 1000 различных слов в 128-мерные вектора
shared_embedding = layers.Embedding(1000, 128)
# Целочисленные последовательности переменной длины
text_input_a = keras.Input(shape=(None,), dtype='int32')
# Целочисленные последовательности переменной длины
text_input_b = keras.Input(shape=(None,), dtype='int32')
# Мы переиспользуем тот же слой для кодирования на обоих входах
encoded_input_a = shared_embedding(text_input_a)
encoded_input_b = shared_embedding(text_input_b)
Поскольку граф слоев, которыми вы манипулируете в Functional API, является статической структурой данных, к ней можно получить доступ и проверить ее. Именно так мы строим Functional модели, например, в виде изображений.
Это также означает, что мы можем получить доступ к активациям промежуточных слоев ("узлов" в графе) и использовать их в других местах. Это чрезвычайно полезно для извлечения признаков, например!
Давайте посмотрим пример. Это модель VGG19 с весами предобученными на ImageNet:
In [ ]:
from tensorflow.keras.applications import VGG19
vgg19 = VGG19()
И это промежуточные активации модели, полученные путем запроса к структуре данных графа:
In [ ]:
features_list = [layer.output for layer in vgg19.layers]
Мы можем использовать эти признаки для создания новой модели извлечения признаков, которая возвращает значения активаций промежуточного уровня - и мы можем сделать все это в 3 строчки
In [ ]:
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)
Это удобно когда реализуется neural style transfer, как и в других случаях.
tf.keras обладает широким набором встроенных слоев. Вот несколько примеров:
Conv1D
, Conv2D
, Conv3D
, Conv2DTranspose
, и т.д.MaxPooling1D
, MaxPooling2D
, MaxPooling3D
, AveragePooling1D
, и т.д.GRU
, LSTM
, ConvLSTM2D
, и т.д.BatchNormalization
, Dropout
, Embedding
, и т.д.Если вы не нашли то, что вам нужно, легко расширить API создав собственный слой.
Все слои сабклассируют класс Layer
и реализуют:
call
, определяющий вычисления выполняемые слоем.build
, создающий веса слоя (заметим что это всего лишь стилевое соглашение; вы можете также создать веса в __init__
).Чтобы узать больше о создании слоев с нуля, проверьте руководство Руководство по написанию слоев и моделей с нуля.
Вот простая реализация Dense
слоя:
In [ ]:
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
,возвращающий аргументы конструктора экземпляра слоя:
In [ ]:
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)
Как определить когда лучше использовать Functional API для создания новой модели, или просто сабклассировать класс Model
напрямую?
В целом, Functional API более высокоуровневый и простой в использовании, он имеет ряд функций, которые не поддерживаются сабклассированными Model.
Однако, сабклассирование Model дает вам большую гибкость при создании моделей, которые не описываются легко в виде направленного ациклического графа слоев (например, вы не сможете реализовать Tree-RNN с Functional API, вам нужно сабклассировать напрямую Model
).
Свойства перечисленные ниже являются все верными и для Sequential моделей (которые также являются структурами данных), но они верны для сабклассированных моделей (которые представляют собой код 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)
# Создадим экземпляр модели.
mlp = MLP()
# Необходимо создать состояние модели.
# У модели нет состояния пока она не была вызвана хотя бы раз.
_ = mlp(tf.zeros((1, 32)))
В Functional API входные спецификации (shape и dtype) создаются заранее (через Input
), и каждый раз, когда вы вызываете слой, слой проверяет, что спецификации переданные ему соответствует его предположениям, если это не так то вы получите полезное сообщение об ошибке.
Это гарантирует, что любая модель которую вы построите с Functional API запустится. Вся отладка (не относящаяся к отладке сходимости) будет происходить статично во время конструирования модели, а не во время выполнения. Это аналогично проверке типа в компиляторе.
Вы можете начертить модель в виде графа, и вы легко можете получить доступ к промежуточным узлам графа, например, чтобы извлечь и переиспользовать активации промежуточных слоев, как мы видели в предыдущем примере:
features_list = [layer.output for layer in vgg19.layers]
feat_extraction_model = keras.Model(inputs=vgg19.input, outputs=features_list)
Поскольку Functional модель это скорее структура данных чем кусок кода, она безопасно сериализуется и может быть сохранена в виде одного файла который позволяет вам воссоздать в точности ту же модель без доступа к исходному коду. Смотрите наше руководство по сохранению и сериализации для деталей.
Functional API обрабатывает модели как DAG слоев. Это справедливо для большинства архитектур глубокого обучения, но не для всех: например, рекурсивные сети или Tree RNN не соответствуют этому предположению и не могут быть реализованы в Functional API.
При написании продвинутых архитектур вы можете захотеть сделать то, что выходит за рамки "определения DAG слоев": например, вы можете использовать несколько пользовательских методов обучения и вывода на экземпляре вашей модели. Это требует сабклассирования.
Чтобы глубже погрузиться в разницу между Functional API и сабклассированием Model, вы можете прочитать Что такое Symbolic и Imperative API в TensorFlow 2.0?.
Важно отметить, что выбор между Functional API или сабклассированием Model не является бинарным решением, которое ограничивает вас одной категорией моделей. Все модели в API tf.keras могут взаимодействовать друг с другом, будь то Sequential модели, Functional модели или сабклассированные Models/Layers, написанные с нуля.
Вы всегда можете использовать Functional модель или Sequential модель как часть сабклассированного Model/Layer:
In [ ]:
units = 32
timesteps = 10
input_dim = 5
# Define a Functional model
inputs = keras.Input((None, units))
x = layers.GlobalAveragePooling1D()(inputs)
outputs = layers.Dense(1, activation='sigmoid')(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)))
Обратно, вы можете использовать любой сабклассированный Layer или Model в Functional 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
на вашем пользовательском Layer или Model, Functional модели которые вы создадите с ним будут сериализуемы и клонируемы.
Далее приведем небольшой пример где мы используем кастомный RNN написанный с нуля Functional модели:
In [ ]:
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, activation='sigmoid')
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)
# Заметьте что мы задаем статичный размер пакета для входных данных с
# аргументом `batch_shape`, потому что внутренние вычисления `CustomRNN` требуют фиксированного размера пакета
# (когда мы создает нулевые тензоры `state`).
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)))
Это завершает наше руководство по Functional API!
Теперь у вас под рукой мощный набор инструментов для построения моделей глубокого обучения.