5.4 Пользовательские слои
Одна из причин успеха глубокого обучения — гибкость нейронных сетей: мы можем комбинировать различные слои самыми разными способами и создавать архитектуры, подходящие для самых разных задач. Например, исследователи изобрели слои, специально предназначенные для обработки изображений, текста, последовательных данных и для выполнения динамического программирования. Иногда нам встречается слой, которого ещё нет в фреймворке глубокого обучения, или мы сами хотим придумать новый слой. В таких случаях приходится создавать пользовательский слой. В этом разделе показано, как это сделать.
5.4.1 Слои без параметров
Сначала создадим пользовательский слой без параметров.
Вспомним введение блоков в разделе 5.1: этот материал должен быть знаком.
Приведённый ниже класс CenteredLayer вычитает среднее значение из своего входа.
Чтобы создать его, нужно унаследоваться от базового класса слоя и реализовать функцию прямого распространения.
MXNet:
from mxnet import np, npx
from mxnet.gluon import nn
npx.set_np()
class CenteredLayer(nn.Block):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def forward(self, X):
return X - X.mean()
PyTorch:
import torch
from torch import nn
import torch.nn.functional as F
class CenteredLayer(nn.Module):
def __init__(self):
super().__init__()
def forward(self, X):
return X - X.mean()
TensorFlow:
import tensorflow as tf
class CenteredLayer(tf.keras.Model):
def __init__(self):
super().__init__()
def call(self, inputs):
return inputs - tf.reduce_mean(inputs)
PaddlePaddle:
import warnings
warnings.filterwarnings(action='ignore')
import paddle
from paddle import nn
import paddle.nn.functional as F
class CenteredLayer(nn.Layer):
def __init__(self):
super().__init__()
def forward(self, X):
return X - X.mean()
Подадим в этот слой какие-нибудь данные, чтобы проверить, что он работает как ожидается.
MXNet:
layer = CenteredLayer()
layer(np.array([1, 2, 3, 4, 5]))
PyTorch:
layer = CenteredLayer()
layer(torch.FloatTensor([1, 2, 3, 4, 5]))
TensorFlow:
layer = CenteredLayer()
layer(tf.constant([1, 2, 3, 4, 5]))
PaddlePaddle:
layer = CenteredLayer()
layer(paddle.to_tensor([1, 2, 3, 4, 5], dtype='float32'))
Теперь мы можем включить слой как компонент в более сложную модель.
MXNet:
net = nn.Sequential()
net.add(nn.Dense(128), CenteredLayer())
net.initialize()
PyTorch, PaddlePaddle:
net = nn.Sequential(nn.Linear(8, 128), CenteredLayer())
TensorFlow:
net = tf.keras.Sequential([tf.keras.layers.Dense(128), CenteredLayer()])
В качестве дополнительной проверки здравого смысла мы можем отправить в сеть случайные данные и убедиться, что среднее значение выхода равно 0. Поскольку мы работаем с числами с плавающей точкой, из-за ограниченной точности хранения мы всё равно можем получить очень маленькое ненулевое число.
MXNet:
Y = net(np.random.uniform(size=(4, 8)))
Y.mean()
PyTorch:
Y = net(torch.rand(4, 8))
Y.mean()
TensorFlow:
Y = net(tf.random.uniform((4, 8)))
tf.reduce_mean(Y)
PaddlePaddle:
Y = net(paddle.rand([4, 8]))
Y.mean()
5.4.2 Слои с параметрами
Выше мы разобрали, как определять простые слои. Теперь определим слои, которые имеют параметры, настраиваемые в процессе обучения. Для создания параметров мы можем использовать встроенные функции, которые обеспечивают базовое управление: доступом, инициализацией, совместным использованием, сохранением и загрузкой параметров модели. Одно из преимуществ этого подхода в том, что нам не нужно писать собственный механизм сериализации для каждого пользовательского слоя.
Теперь реализуем пользовательскую версию полносвязного слоя.
Напомним, что этому слою нужны два параметра: один для весов и другой для смещения.
В этой реализации в качестве функции активации используем ReLU.
Слой принимает два входных аргумента: in_units и units, которые задают количество входов и количество выходов соответственно.
MXNet:
class MyDense(nn.Block):
def __init__(self, units, in_units, **kwargs):
super().__init__(**kwargs)
self.weight = self.params.get('weight', shape=(in_units, units))
self.bias = self.params.get('bias', shape=(units,))
def forward(self, x):
linear = np.dot(x, self.weight.data(ctx=x.ctx)) + self.bias.data(
ctx=x.ctx)
return npx.relu(linear)
PyTorch:
class MyLinear(nn.Module):
def __init__(self, in_units, units):
super().__init__()
self.weight = nn.Parameter(torch.randn(in_units, units))
self.bias = nn.Parameter(torch.randn(units,))
def forward(self, X):
linear = torch.matmul(X, self.weight.data) + self.bias.data
return F.relu(linear)
TensorFlow:
class MyDense(tf.keras.Model):
def __init__(self, units):
super().__init__()
self.units = units
def build(self, X_shape):
self.weight = self.add_weight(name='weight',
shape=[X_shape[-1], self.units],
initializer=tf.random_normal_initializer())
self.bias = self.add_weight(
name='bias', shape=[self.units],
initializer=tf.zeros_initializer())
def call(self, X):
linear = tf.matmul(X, self.weight) + self.bias
return tf.nn.relu(linear)
PaddlePaddle:
class MyLinear(nn.Layer):
def __init__(self, in_units, units):
super().__init__()
self.weight = paddle.create_parameter(shape=(in_units, units), dtype='float32')
self.bias = paddle.create_parameter(shape=(units,), dtype='float32')
def forward(self, X):
linear = paddle.matmul(X, self.weight) + self.bias
return F.relu(linear)
MXNet, TensorFlow: Далее мы создаём экземпляр класса MyDense и обращаемся к его параметрам модели.
PyTorch, PaddlePaddle: Далее мы создаём экземпляр класса MyLinear и обращаемся к его параметрам модели.
MXNet:
dense = MyDense(units=3, in_units=5)
dense.params
PyTorch, PaddlePaddle:
linear = MyLinear(5, 3)
linear.weight
TensorFlow:
dense = MyDense(3)
dense(tf.random.uniform((2, 5)))
dense.get_weights()
Мы можем выполнять прямое распространение, используя пользовательский слой напрямую.
MXNet:
dense.initialize()
dense(np.random.uniform(size=(2, 5)))
PyTorch:
linear(torch.rand(2, 5))
TensorFlow:
dense(tf.random.uniform((2, 5)))
PaddlePaddle:
linear(paddle.randn([2, 5]))
Мы также можем построить модель с помощью пользовательского слоя, используя его точно так же, как встроенный полносвязный слой.
MXNet:
net = nn.Sequential()
net.add(MyDense(8, in_units=64),
MyDense(1, in_units=8))
net.initialize()
net(np.random.uniform(size=(2, 64)))
PyTorch:
net = nn.Sequential(MyLinear(64, 8), MyLinear(8, 1))
net(torch.rand(2, 64))
TensorFlow:
net = tf.keras.models.Sequential([MyDense(8), MyDense(1)])
net(tf.random.uniform((2, 64)))
PaddlePaddle:
net = nn.Sequential(MyLinear(64, 8), MyLinear(8, 1))
net(paddle.rand([2, 64]))
Резюме
- Мы можем создавать пользовательские слои на основе базового класса слоя. Это позволяет определять гибкие новые слои, поведение которых отличается от любого существующего слоя фреймворка глубокого обучения.
- После определения пользовательского слоя его можно вызывать в любой среде и в любой архитектуре сети.
- Слои могут иметь локальные параметры, которые можно создавать с помощью встроенных функций.
Упражнения
- Разработайте слой, который принимает входные данные и вычисляет понижение размерности тензора: он возвращает .
- Разработайте слой, который возвращает первую половину коэффициентов Фурье входных данных.
MXNet: Обсуждение
PyTorch: Обсуждение
TensorFlow: Обсуждение
PaddlePaddle: Обсуждение