Статьи TensorFlow | TensorFlow 2.x Построение модели на основе Keras

TensorFlow

"Введение«С момента выпуска TensorFlow 2.0 Keras был глубоко интегрирован в инфраструктуру TensorFlow, и Keras API стал лучшим выбором для построения моделей глубокой сети. Разработка модели и итерация с помощью Keras — это фундаментальный навык, которым должен овладеть каждый разработчик данных.Давайте погрузимся в мир Keras и выясним.

Введение в Керас

  1. Kerasэто использованиеPythonРасширенная нейронная сеть написанаAPI, которая представляет собой автономную библиотеку, способнуюTensorFlow,CNTKилиTheanoРаботает как бэкенд.TensorFlowот1.0версия, чтобы начать пробовать сKerasсделать интеграцию, чтобы2.0После выхода версии он более глубоко интегрированKeras, и находится в тесной зависимости отtf.kerasкак его центральный старшийAPI, официальный также настоятельно рекомендуется использоватьkeras APIзавершить построение глубинной модели.

  2. tf.kerasИмеет три ключевых преимущества:

    1. Удобно для новичков:KerasИмеет простой и последовательный интерфейс с четкими и действенными предложениями по исправлению пользовательских ошибок.TensorFlow 2.0Предыдущая версия, из-за сложности ее кодировки,APIИнтерфейс сбивает с толку, а совместимость между различными версиями плохая, что неоднократно подвергалось критике.KerasПосле унификации нагрузка на разработчиков сильно сократится.

    2. Модульный и компонуемый:KerasМодели связаны между собой сборными модулями без каких-либо ограничений, структура модели понятна, а код легко читается.

    3. Простота расширения: при написании новых пользовательских модулей очень удобно расширять на основе существующего интерфейса.

  3. KerasсделатьTensorFlowЛегче в использовании без ущерба для гибкости и производительности.

Керас модельное здание

существуетTensorFlow 2.xверсия, вы можете использовать три способа сборкиKerasмодели, которыеSequential,函数式 (Functional) APIа также自定义模型 (Subclassed). Три метода построения описаны ниже.

Sequential Model

模型结构图

  1. существуетKeras, обычно несколько слоев (layer) собираются в модель (model), наиболее распространенным способом является укладка слоев, которую можно использоватьtf.keras.Sequentialсделать это легко. В качестве примера возьмем модель, показанную на рисунке выше, и ее кодовая реализация выглядит следующим образом:

    import tensorflow as tf
    from tensorflow.keras import layers
    
    model = tf.keras.Sequential()
    # Adds a densely-connected layer with 64 units to the model:
    model.add(layers.Dense(64, activation='relu', input_shape=(16,)))
    # This is identical to the following:
    # model.add(layers.Dense(64, activation='relu', input_dim=16))
    # model.add(layers.Dense(64, activation='relu', batch_input_shape=(None, 16)))
    # Add another:
    model.add(layers.Dense(64, activation='relu'))
    # Add an output layer with 10 output units:
    model.add(layers.Dense(10))
    # model.build((None, 16))
    print(model.weights)
    
  2. Примечание дляSequentialПервый добавленный слой может содержатьinput_shapeилиinput_dimилиbatch_input_shapeдля указания размерности входных данных, подробности см. в разделе комментариев. когда указаноinput_shapeПосле ожидания параметров каждый разaddДля новых слоев модель находится в процессе непрерывного создания, что означает, что весовая матрица каждого слоя в модели была инициализирована и может быть вызвана вызовомmodel.weightsдля печати информации о весе модели.

  3. Конечно, первый слой также не может содержать информацию о размерности входных данных, что называется режимом отложенного создания, то есть модель в это время еще не создана, и матрица весов не существует. позвонивmodel.build(batch_input_shape)метод создания модели вручную. Если не создается вручную, то только при вызовеfitИли другие методы обучения и оценки, модель будет создана, весовая матрица будет инициализирована, и модель автоматически выведет информацию о своих размерах на основе входных данных.

  4. input_shapeне указано вbatchразмер и установите его наNone, потому что обучение и оценкаbatchРазмеры могут не совпадать. Если установлено фиксированное значение, оно будет генерировать ошибки во время обучения или оценки, а если установлено таким образом, оно может быть автоматически выведено моделью.batchРазмер и расчет более надежны.

  5. В дополнение к этому последовательному добавлению (add), также можноlayersпередается в качестве параметраSequentialчтобы построить модель. Пример кода выглядит следующим образом:

    import tensorflow as tf
    from tensorflow.keras import layers
    
    model = tf.keras.Sequential([
        layers.Dense(64, activation='relu', input_shape=(16, )),
        layers.Dense(64, activation='relu'),
        layers.Dense(10)
    ])
    # model.build((None, 16))
    print(model.weights)
    

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

  1. Kerasиз函数式 APIчемSequentialБолее гибкий способ создания моделей. Он может обрабатывать модели с нелинейной топологией, с общими слоями (layers) модели и модели с несколькими входами и выходами. Модели глубокого обучения обычно состоят из слоев (layers) — ориентированный ациклический граф, и函数式 APIявляется эффективным способом построения такого графа.

  2. отSequential ModelВзяв модель, упомянутую в разделе в качестве примера, используйте函数式 APIСпособ его реализации следующий:

    from tensorflow import keras
    from tensorflow.keras import layers
    
    inputs = keras.Input(shape=(16, ))
    dense = layers.Dense(64, activation='relu')
    x = dense(inputs)
    x = layers.Dense(64, activation='relu')(x)
    outputs = layers.Dense(10)(x)
    model = keras.Model(inputs=inputs, outputs=outputs, name='model')
    model.summary()
    
  3. и использоватьSequentialСпособ, которым метод строит модель, отличается тем, что:函数式 APIпройти черезkeras.Inputввод указанinputsи через函数调用произвел выводoutputs, и, наконец, используйтеkeras.Modelметод строит всю модель.

  4. почему это называется函数式 API, как видно из кода, различные слои можно использовать как вызовы функций (layers), такие как определенныеdenseслой, вы можете напрямуюinputsв видеdenseввод и получение выводаx, а потомxВ качестве входных данных для следующего уровня конечное возвращаемое значение функции является выходом всей модели.

  5. 函数式 APIТот же слой (layers) в составе нескольких моделей пример кода выглядит следующим образом:

    from tensorflow import keras
    from tensorflow.keras import layers
    
    encoder_input = keras.Input(shape=(16, ), name='encoder_input')
    x = layers.Dense(32, activation='relu')(encoder_input)
    x = layers.Dense(64, activation='relu')(x)
    encoder_output = layers.Dense(128, activation='relu')(x)
    
    encoder = keras.Model(encoder_input, encoder_output, name='encoder')
    encoder.summary()
    
    x = layers.Dense(64, activation='relu')(encoder_output)
    x = layers.Dense(32, activation='relu')(x)
    decoder_output = layers.Dense(16, activation='relu')(x)
    
    autoencoder = keras.Model(encoder_input, decoder_output, name='autoencoder')
    autoencoder.summary()
    

    Код содержит две модели, энкодер (encoder) и автоэнкодер (autoencoder), вы можете видеть, что две модели разделяютencoder_outслоев, включая, конечно,encoder_outВсе слои перед слоем.

  6. 函数式 APIВсе созданные модели (models) могут быть как слои (layers) тоже называется. Также с автоэнкодером (autoencoder) в качестве примера, теперь разделите его на энкодеры (encoder) и декодер (decoder) на две части, затем используйтеencoderиdecoderгенерироватьautoencoder, код показан ниже:

    from tensorflow import keras
    from tensorflow.keras import layers
    
    encoder_input = keras.Input(shape=(16, ), name='encoder_input')
    x = layers.Dense(32, activation='relu')(encoder_input)
    x = layers.Dense(64, activation='relu')(x)
    encoder_output = layers.Dense(128, activation='relu')(x)
    
    encoder = keras.Model(encoder_input, encoder_output, name='encoder')
    encoder.summary()
    
    decoder_input = keras.Input(shape=(128, ), name='decoder_input')
    x = layers.Dense(64, activation='relu')(decoder_input)
    x = layers.Dense(32, activation='relu')(x)
    decoder_output = layers.Dense(16, activation='relu')(x)
    
    decoder = keras.Model(decoder_input, decoder_output, name='decoder')
    decoder.summary()
    
    autoencoder_input = keras.Input(shape=(16), name='autoencoder_input')
    encoded = encoder(autoencoder_input)
    autoencoder_output = decoder(encoded)
    autoencoder = keras.Model(
        autoencoder_input,
        autoencoder_output,
        name='autoencoder',
    )
    autoencoder.summary()
    

    Код сначала генерирует две моделиencoderиdecoder, а затем сгенерироватьautoencoderмодель, используя模型函数调用кстати, напрямуюautoencoder_inputиencodedсоответственно какencoderиdecoderввод двух моделей и, наконец, получитьautoencoderМодель.

  7. 函数式 APIМодели с несколькими входами и несколькими выходами могут быть легко обработаны, чтоSequential APIневозможно. Например, часть входных данных нашей модели类别型特征, вообще надо пройтиEmbeddingобработка и часть数值型特征, как правило, никакой специальной обработки не требуется, и очевидно, что эти две функции не могут быть напрямую объединены как один вход для общей обработки, и в настоящее время будет использоваться несколько входов. Иногда мы хотим, чтобы модель возвращала несколько выходных данных для использования в последующих вычислениях, и в этом случае используется модель с несколькими выходными данными. Пример кода для модели с несколькими входами и несколькими выходами показан ниже:

    from tensorflow import keras
    from tensorflow.keras import layers
    
    categorical_input = keras.Input(shape=(16, ))
    numeric_input = keras.Input(shape=(32, ))
    categorical_features = layers.Embedding(
        input_dim=100,
        output_dim=64,
        input_length=16,
    )(categorical_input)
    categorical_features = layers.Reshape([16 * 64])(categorical_features)
    numeric_features = layers.Dense(64, activation='relu')(numeric_input)
    x = layers.Concatenate(axis=-1)([categorical_features, numeric_features])
    x = layers.Dense(128, activation='relu')(x)
    
    binary_pred = layers.Dense(1, activation='sigmoid')(x)
    categorical_pred = layers.Dense(3, activation='softmax')(x)
    
    model = keras.Model(
        inputs=[categorical_input, numeric_input],
        outputs=[binary_pred, categorical_pred],
    )
    model.summary()
    

    В коде два входаcategorical_inputиnumeric_input, после различных слоев обработки, два проходят черезConcatenateОбъединены вместе и, наконец, через разные слои обработки, чтобы получить два выхода.binary_predиcategorical_pred. Структура модели показана на следующем рисунке:

    多输入输出结构图

  8. 函数式 APIЕще одно хорошее применение — совместное использование слоев моделей, т. е. в модели, в которой слои повторно используются несколько раз, она изучает разные функции из разных входных данных. Общим общим слоем является слой внедрения (Embedding), код показан ниже:

    from tensorflow import keras
    from tensorflow.keras import layers
    
    categorical_input_one = keras.Input(shape=(16, ))
    categorical_input_two = keras.Input(shape=(24, ))
    
    shared_embedding = layers.Embedding(100, 64)
    
    categorical_features_one = shared_embedding(categorical_input_one)
    categorical_features_two = shared_embedding(categorical_input_two)
    
    categorical_features_one = layers.Reshape([16 * 64])(categorical_features_one)
    categorical_features_two = layers.Reshape([16 * 64])(categorical_features_two)
    
    x = layers.Concatenate(axis=-1)([
        categorical_features_one,
        categorical_features_two,
    ])
    x = layers.Dense(128, activation='relu')(x)
    outputs = layers.Dense(1, activation='sigmoid')(x)
    
    model = keras.Model(
        inputs=[categorical_input_one, categorical_input_two],
        outputs=outputs,
    )
    model.summary()
    

    В коде два входаcategorical_input_oneиcategorical_input_two, они разделяютEmbeddingЭтажshared_embedding. Структура модели показана на следующем рисунке:

    共享层结构图

Пользовательские слои и модели Keras

  1. tf.kerasПод модулем много встроенных слоев (layers), такой как тот, который мы использовали вышеDense,Embedding,ReshapeЖдать. Иногда мы обнаруживаем, что эти встроенные слои не соответствуют нашим потребностям, и очень удобно создавать собственные слои для расширения. пользовательские слои через наследованиеtf.keras.Layerкласс для реализации, а подкласс должен реализовать родительский классbuildиcallметод. для встроенногоDenseслой, если вы используете пользовательский слой для реализации, код выглядит следующим образом:

    import tensorflow as tf
    from tensorflow import keras
    from tensorflow.keras import layers
    
    class CustomDense(layers.Layer):
        def __init__(self, units=32):
            super().__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}
    
        @classmethod
        def from_config(cls, config):
            return cls(**config)
    
    inputs = keras.Input((4, ))
    layer = CustomDense(10)
    outputs = layer(inputs)
    
    model = keras.Model(inputs, outputs)
    model.summary()
    
    # layer recreate
    config = layer.get_config()
    new_layer = CustomDense.from_config(config)
    new_outputs = new_layer(inputs)
    print(new_layer.weights)
    print(new_layer.non_trainable_weights)
    print(new_layer.trainable_weights)
    
    # model recreate
    config = model.get_config()
    new_model = keras.Model.from_config(
        config,
        custom_objects={'CustomDense': CustomDense},
    )
    new_model.summary()
    
    1. в__init__Метод используется для инициализации некоторых основных параметров, необходимых для построения слоя,buildметод создания весовой матрицы, необходимой для этого слояwи матрица отклоненийb,callМетоды являются реальными исполнителями построения слоев, превращая ввод в вывод и обратно. На самом деле, создание весовых матриц и т. д. также может быть выполнено в__init__метод, но во многих случаях мы не можем заранее предсказать размерность входных данных, и нам нужно отложить создание весовой матрицы в определенной точке слоя инстанцирования, поэтому нам нужноbuildИнформация об измерении согласно входным данным в методеinput_shapeдля динамического создания весовой матрицы.

    2. Последовательность вызова вышеуказанных трех методов следующая:__init__,build,call__init__вызывается при создании экземпляра слоя, иbuildиcallвызывается после определения ввода. фактическиLayerВ классе есть встроенный метод__call__, метод вызывается первым при построении слоя, а внутри методаbuildиcall, и только первый звонок__call__сработает, когдаbuild, то естьbuildПеременные в могут быть созданы только один раз, в то время какcallЕго можно вызывать несколько раз, например, для обучения и оценки.

    3. Если вам нужна поддержка сериализации для этого уровня, вам нужно реализоватьget_configметод для возврата параметров конструктора этого экземпляра слоя в виде словаря. в данномconfigПосле словаря вы можете вызвать слой类方法 (classmethod) from_configчтобы воссоздать слой,from_configРеализация по умолчанию показана в коде, повторное создание слоя см.layer recreateКодовую часть, конечно, тоже можно переписатьfrom_configметоды класса для предоставления новых методов создания. при воссоздании новой модели (model) код сlayerВосстановленный код отличается, он требует помощиkeras.Model.from_configспособ завершения сборки, см.model recreateраздел кода.

  2. Пользовательские слои можно комбинировать рекурсивно, что означает, что один слой может использоваться как атрибут другого слоя. Обычно рекомендуется в__init__способ создания подслоя, потому что собственный подуровеньbuildметод будет во внешнем слоеbuildПри вызове он запускается для построения матрицы весов без явного создания ее в родительском слое. Также кSequential ModelМодель, упомянутая в разделе, взята в качестве примера, а код выглядит следующим образом:

    from tensorflow import keras
    from tensorflow.keras import layers
    
    class MLP(layers.Layer):
        def __init__(self):
            super().__init__()
            self.dense_1 = layers.Dense(64, activation='relu')
            self.dense_2 = layers.Dense(64, activation='relu')
            self.dense_3 = layers.Dense(10)
    
        def call(self, inputs):
            x = self.dense_1(inputs)
            x = self.dense_2(x)
            x = self.dense_3(x)
            return x
    
    inputs = keras.Input((16, ))
    mlp = MLP()
    
    y = mlp(inputs)
    print('weights:', len(mlp.weights))
    print('trainable weights:', len(mlp.trainable_weights))
    

    Как видно из кода, мы поставили триDenseслой какMLPподслои, а затем использовать их для завершенияMLP, что может быть достигнуто сSequential ModelТот же эффект, что и в , и матрицы весов всех подслоев будут существовать как матрицы весов нового слоя.

  3. Этаж (layers) во время процесса сборки, будет рекурсивно собирать потери, сгенерированные во время этого процесса сборки (losses). переписываниеcallметод, позвонивadd_lossметод добавления пользовательских потерь. Все потери слоя включают в себя и потери его подслоев, и все они могут быть получены путемlayer.lossesсвойство, которое представляет собой список (list), следует отметить, что потеря регулярного члена включается автоматически. Пример кода выглядит следующим образом:

    import tensorflow as tf
    from tensorflow import keras
    from tensorflow.keras import layers
    
    class CustomLayer(layers.Layer):
        def __init__(self, rate=1e-2, l2_rate=1e-3):
            super().__init__()
            self.rate = rate
            self.l2_rate = l2_rate
            self.dense = layers.Dense(
                units=32,
                kernel_regularizer=keras.regularizers.l2(self.l2_rate),
            )
    
        def call(self, inputs):
            self.add_loss(self.rate * tf.reduce_sum(inputs))
            return self.dense(inputs)
    
    inputs = keras.Input((16, ))
    layer = CustomLayer()
    x = layer(inputs)
    print(layer.losses)
    
  4. слой или модельcallПредустановка метода имеетtrainingпараметр, этоboolПеременная типа, указывающая, находится ли она в состоянии обучения, установит значение в соответствии с вызываемым методом, когда обучение будетTrue, когда оценивается какFalse. потому что есть несколько слоев, таких какBatchNormalizationиDropoutОбычно он используется только в процессе обучения и обычно не используется в процессе оценки и прогнозирования, поэтому этот параметр можно использовать для управления различными процессами вычислений, выполняемыми моделью в разных состояниях.

  5. Пользовательская модель реализуется аналогично пользовательскому слою, но модель должна наследоваться отtf.keras.Model,Modelнекоторые из классаAPIС участиемLayerКласс тот же, например, кастомная модель тоже должна быть реализована__init__,buildиcallметод. Однако между ними есть и различия.ModelИмеет интерфейсы обучения, оценки и прогнозирования, а во-вторых, к нему можно получить доступ черезmodel.layersПросмотр информации обо всех встроенных слоях, а такжеModelКласс также предоставляет интерфейс для сохранения и сериализации модели. отAutoEncoderНапример, пример кода для полной пользовательской модели выглядит следующим образом:

    from tensorflow import keras
    from tensorflow.keras import layers
    
    class Encoder(layers.Layer):
        def __init__(self, l2_rate=1e-3):
            super().__init__()
            self.l2_rate = l2_rate
    
        def build(self, input_shape):
            self.dense1 = layers.Dense(
                units=32,
                activation='relu',
                kernel_regularizer=keras.regularizers.l2(self.l2_rate),
            )
            self.dense2 = layers.Dense(
                units=64,
                activation='relu',
                kernel_regularizer=keras.regularizers.l2(self.l2_rate),
            )
            self.dense3 = layers.Dense(
                units=128,
                activation='relu',
                kernel_regularizer=keras.regularizers.l2(self.l2_rate),
            )
    
        def call(self, inputs):
            x = self.dense1(inputs)
            x = self.dense2(x)
            x = self.dense3(x)
            return x
    
    class Decoder(layers.Layer):
        def __init__(self, l2_rate=1e-3):
            super().__init__()
            self.l2_rate = l2_rate
    
        def build(self, input_shape):
            self.dense1 = layers.Dense(
                units=64,
                activation='relu',
                kernel_regularizer=keras.regularizers.l2(self.l2_rate),
            )
            self.dense2 = layers.Dense(
                units=32,
                activation='relu',
                kernel_regularizer=keras.regularizers.l2(self.l2_rate),
            )
            self.dense3 = layers.Dense(
                units=16,
                activation='relu',
                kernel_regularizer=keras.regularizers.l2(self.l2_rate),
            )
    
        def call(self, inputs):
            x = self.dense1(inputs)
            x = self.dense2(x)
            x = self.dense3(x)
            return x
    
    class AutoEncoder(keras.Model):
        def __init__(self):
            super().__init__()
            self.encoder = Encoder()
            self.decoder = Decoder()
    
        def call(self, inputs):
            x = self.encoder(inputs)
            x = self.decoder(x)
            return x
    
    model = AutoEncoder()
    model.build((None, 16))
    model.summary()
    print(model.layers)
    print(model.weights)
    

    Приведенный выше код реализуетAutoEncoder Modelкласс, который состоит из двух слоев, а именноEncoderиDecoder, и эти два слоя также являются пользовательскими. позвонивmodel.weightsВы можете просмотреть всю информацию о весе модели, конечно, вся информация о весе в подслое включена сюда.

  6. Для пользовательского слоя или модели при вызове егоsummary, weights, variables, trainable_weights,lossesПри ожидании метода или свойства убедитесь, что слой или модель созданы, иначе он может сообщить об ошибке или вернуть пустое значение.Обратите внимание на это при отладке модели.

Уровень конфигурации (уровень)

существуетtf.keras.layersПод модулем находится множество предопределенных слоев, и большинство из этих слоев имеют одинаковые параметры конструктора. Ниже представлены некоторые часто используемые параметры.Чтобы узнать об уникальных параметрах каждого слоя и значении параметров, при их использовании вы можете запросить официальные документы.Объяснения документов, как правило, очень подробные.

  1. activationОтносится к функции активации, которая может быть установлена ​​в виде строки, такой какreluилиactivationsобъектtf.keras.activations.relu(), что по умолчаниюNone, что означает линейную зависимость.

  2. kernel_initializerиbias_initializer, указывающий метод инициализации матрицы весов и матрицы смещения в слое, который можно задать в виде строки, напримерGlorotuniformилиinitializersобъектtf.keras.initializers.GlorotUniform(), что по умолчаниюGlorotuniformметод инициализации.

  3. kernel_regularizerиbias_regularizer, указывающий метод регуляризации матрицы весов и матрицы отклонений, как описано выше, может бытьL1илиL2регуляризация, напримерtf.keras.regularizers.l2(1e-3), по умолчанию нет обычного термина.

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

  1. При построении более простых моделей используйтеSequentialКонечно способ самый удобный и быстрый, можно воспользоваться существующимLayerЗавершите процесс быстрого построения и проверки.

  2. Если модель более сложная, лучше использовать函数式 APIили нестандартная модель. как правило函数式 APIЭто более продвинутый, простой и безопасный способ реализации, а также он имеет некоторые функции, которых нет в пользовательских моделях. Однако пользовательские модели обеспечивают большую гибкость при построении моделей, которые нелегко представить в виде ориентированных ациклических графов.

  3. 函数式 APIПроверка модели может быть выполнена заблаговременно, поскольку она проходитInputМетод задает размерность входных данных модели заранее, поэтому, когда входные данные не соответствуют спецификации, они будут найдены раньше, что полезно для отладки, в то время как пользовательская модель не указывает размерность входных данных в начале. начало, оно основано на входных данных во время запущенного процесса.

  4. использовать函数式 APIМодульность написания кода несильна, и его немного сложно читать, но, настроив модель, можно четко понять общую структуру модели, которую легко понять.

  5. В реальном использовании,函数式 APIИспользуется в сочетании с пользовательскими моделями для удовлетворения наших различных потребностей в построении моделей.

Советы по созданию модели Keras

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

  2. ищете то, что вам нужноtensorflowметод, еслиkerasЕсли в модуле предусмотрена реализация, этот метод будет использоваться первым, если нет, найтиtfДостаточно метода под модулем, который может сделать код более совместимым и надежным.

  3. В процессе создания модели используйте встроенные методы и свойства моделей и слоев, такие какsummary,weightsИ т. д., чтобы мы могли посмотреть на структуру модели с глобальной точки зрения и помочь найти некоторые потенциальные проблемы.

  4. так какTensorFlow 2.xМодель использует значение по умолчаниюEager ExecutionМеханизм динамического графика для запуска кода, поэтому вы можете печатать прямо в любом месте кодаTensorдля просмотра его значений и размеров и другой информации, что очень полезно при отладке модели.

использованная литература

  1. Последовательная модель Кераса
  2. Функциональный API Keras
  3. Keras пишет собственные слои и модели