Привет, Хабр!
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 рассматривают в рамках практических онлайн-курсов. Напомню, что с полным каталогом курсов можно ознакомиться по ссылке.
ressiwage
И вот так мы сделали из буханки троллейбус. Но зачем?