
Привет, Хабр!
Keras предоставляет мощные инструменты для создания сложных нейронных сетей. Однако иногда стандартного набора слоев недостаточно для решения некоторых задач. В таких случаях на помощь приходят кастомные слои.
Кастомные слои позволяют адаптировать архитектуру модели под особенности данных, улучшая тем самым производительность и точность моделек.
Создание кастомных слоев
Каждый кастомный слой начинается с определения нового класса, наследующего от tf.keras.layers.Layer. В __init__ происходит инициализация слоя, где можно задать параметры, необходимые для работы слоя:
import tensorflow as tf class CustomLayer(tf.keras.layers.Layer): def __init__(self, units=32, activation=None, **kwargs): super(CustomLayer, self).__init__(**kwargs) self.units = units self.activation = tf.keras.activations.get(activation)
Тут units определяет количество нейронов, а activation указывает функцию активации. super(CustomLayer, self).__init__(**kwargs) вызывает конструктор базового класса Layer.
Метод build вызывается Keras при первом использовании слоя. Его юзают для создания параметров слоя, которые зависят от размера входных данных:
def build(self, input_shape): self.kernel = self.add_weight(shape=(input_shape[-1], self.units), initializer='glorot_uniform', trainable=True) self.bias = self.add_weight(shape=(self.units,), initializer='zeros', trainable=True) super(CustomLayer, self).build(input_shape)
В методе создаются веса kernel и bias. Функция add_weight создает и регистрирует переменные слоя, которые будут обновляться во время тренировки.
Метод call содержит основную логику вычислений слоя. Он принимает входные данные и возвращает выходные:
def call(self, inputs): output = tf.matmul(inputs, self.kernel) + self.bias if self.activation is not None: output = self.activation(output) return output
В этом методе выполняется умножение входных данных на веса и добавление смещения. Если определена функция активации, она применяется к выходным данным.
После определения кастомного слоя его можно использовать в моделях Keras как обычный слой:
model = tf.keras.Sequential([ tf.keras.layers.Input(shape=(8,)), CustomLayer(units=64, activation='relu'), tf.keras.layers.Dense(10, activation='softmax') ]) model.compile(optimizer='adam', loss='sparse_categorical_crossentropy')
Другие полезные методы:
add_weight: Добавляет переменную веса в слой.compute_output_shape: Возвращает форму выходных данных на основе формы входных.get_config: Возвращает конфигурацию слоя в виде словаря, что полезно для сериализации.
Примеры реализации: Dense, Convolutional и еще три типа других слоев
Dense слой выполняет простую линейную операцию: умножение входного вектора на матрицу весов и добавление смещения, а затем применяется функция активации:
import tensorflow as tf class CustomDenseLayer(tf.keras.layers.Layer): def __init__(self, units=32, activation=None): super(CustomDenseLayer, self).__init__() self.units = units self.activation = tf.keras.activations.get(activation) def build(self, input_shape): self.w = self.add_weight(shape=(input_shape[-1], self.units), initializer='glorot_uniform', trainable=True) self.b = self.add_weight(shape=(self.units,), initializer='zeros', trainable=True) def call(self, inputs): z = tf.matmul(inputs, self.w) + self.b if self.activation is not None: return self.activation(z) return z # example model = tf.keras.Sequential([ tf.keras.layers.Input(shape=(8,)), CustomDenseLayer(units=64, activation='relu'), tf.keras.layers.Dense(10, activation='softmax') ])
Convolutional слои применяют свертку фильтра к входным данным, что позволяет выделять пространственные особенности:
class CustomConvLayer(tf.keras.layers.Layer): def __init__(self, filters, kernel_size, strides=(1, 1), padding='valid', activation=None): super(CustomConvLayer, self).__init__() self.filters = filters self.kernel_size = kernel_size self.strides = strides self.padding = padding self.activation = tf.keras.activations.get(activation) def build(self, input_shape): self.kernel = self.add_weight(shape=(*self.kernel_size, input_shape[-1], self.filters), initializer='glorot_uniform', trainable=True) def call(self, inputs): conv = tf.nn.conv2d(inputs, self.kernel, strides=self.strides, padding=self.padding.upper()) if self.activation is not None: return self.activation(conv) return conv # example model = tf.keras.Sequential([ tf.keras.layers.Input(shape=(28, 28, 1)), CustomConvLayer(filters=32, kernel_size=(3, 3), activation='relu'), tf.keras.layers.Flatten(), tf.keras.layers.Dense(10, activation='softmax') ])
Recurrent слои используются для обработки последовательных данных. Один из наиболее распространнных типов рекуррентных слоев — это LSTM:
class CustomLSTMLayer(tf.keras.layers.Layer): def __init__(self, units): super(CustomLSTMLayer, self).__init__() self.units = units def build(self, input_shape): self.lstm_cell = tf.keras.layers.LSTMCell(self.units) def call(self, inputs, states): return self.lstm_cell(inputs, states) # example model = tf.keras.Sequential([ tf.keras.layers.Input(shape=(None, 8)), tf.keras.layers.RNN(CustomLSTMLayer(units=64)), tf.keras.layers.Dense(10, activation='softmax') ])
Dropout слой используется для регуляризации модели, предотвращая переобучение путем случайного зануления некоторых нейронов во время тренировки:
class CustomDropoutLayer(tf.keras.layers.Layer): def __init__(self, rate): super(CustomDropoutLayer, self).__init__() self.rate = rate def call(self, inputs, training=None): return tf.nn.dropout(inputs, rate=self.rate) if training else inputs # example model = tf.keras.Sequential([ tf.keras.layers.Input(shape=(8,)), tf.keras.layers.Dense(64, activation='relu'), CustomDropoutLayer(rate=0.5), tf.keras.layers.Dense(10, activation='softmax') ])
BatchNormalization слой нормализует активации предыдущего слоя, улучшая скорость обучения модельки:
class CustomBatchNormalizationLayer(tf.keras.layers.Layer): def __init__(self): super(CustomBatchNormalizationLayer, self).__init__() def build(self, input_shape): self.gamma = self.add_weight(shape=(input_shape[-1],), initializer='ones', trainable=True) self.beta = self.add_weight(shape=(input_shape[-1],), initializer='zeros', trainable=True) self.moving_mean = self.add_weight(shape=(input_shape[-1],), initializer='zeros', trainable=False) self.moving_variance = self.add_weight(shape=(input_shape[-1],), initializer='ones', trainable=False) def call(self, inputs, training=None): if training: mean, variance = tf.nn.moments(inputs, axes=[0]) self.moving_mean.assign(self.moving_mean * 0.9 + mean * 0.1) self.moving_variance.assign(self.moving_variance * 0.9 + variance * 0.1) else: mean, variance = self.moving_mean, self.moving_variance return tf.nn.batch_normalization(inputs, mean, variance, self.beta, self.gamma, variance_epsilon=1e-3) # example model = tf.keras.Sequential([ tf.keras.layers.Input(shape=(8,)), CustomBatchNormalizationLayer(), tf.keras.layers.Dense(64, activation='relu'), tf.keras.layers.Dense(10, activation='softmax') ])
Больше практических инструм��нтов и кейсов коллеги из OTUS рассматривают в рамках практических онлайн-курсов. Напомню, что с полным каталогом курсов можно ознакомиться по ссылке.
