Пакетная нормализация с трехмерными свертками в TensorFlow

Я реализую модель, основанную на трехмерных свертках (для задачи, которая похожа на распознавание действий), и я хочу использовать пакетную нормализацию (см. [Ioffe & Szegedy 2015]). Я не смог найти ни одного учебника, посвященного трехмерным хитростям, поэтому я делаю короткое руководство, которое я хотел бы рассмотреть с вами.

Приведенный ниже код ссылается на TensorFlow r0.12 и он явно создает переменные - я имею в виду, что я не использую tf.contrib.learn, за исключением функции tf.contrib.layers.batch_norm(). Я делаю это и для того, чтобы лучше понять, как все работает под капотом, и чтобы иметь больше свободы реализации (например, сводки переменных).

Я легко перейду к случаю трехмерной свертки, написав сначала пример для полностью связанного слоя, затем для двумерной свертки и, наконец, для трехмерного случая. Просматривая код, было бы замечательно, если бы вы могли проверить, все ли сделано правильно - код работает, но я не уверен на 100% в том, как я применяю пакетную нормализацию. Я заканчиваю этот пост более подробным вопросом.

import tensorflow as tf

# This flag is used to allow/prevent batch normalization params updates
# depending on whether the model is being trained or used for prediction.
training = tf.placeholder_with_default(True, shape=())

Полностью подключенный (FC) корпус

# Input.
INPUT_SIZE = 512
u = tf.placeholder(tf.float32, shape=(None, INPUT_SIZE))

# FC params: weights only, no bias as per [Ioffe & Szegedy 2015].
FC_OUTPUT_LAYER_SIZE = 1024
w = tf.Variable(tf.truncated_normal(
    [INPUT_SIZE, FC_OUTPUT_LAYER_SIZE], dtype=tf.float32, stddev=1e-1))

# Layer output with no activation function (yet).
fc = tf.matmul(u, w)

# Batch normalization.
fc_bn = tf.contrib.layers.batch_norm(
    fc,
    center=True,
    scale=True,
    is_training=training,
    scope='fc-batch_norm')

# Activation function.
fc_bn_relu = tf.nn.relu(fc_bn)
print(fc_bn_relu)  # Tensor("Relu:0", shape=(?, 1024), dtype=float32)

2D сверточный (CNN) слой

# Input: 640x480 RGB images (whitened input, hence tf.float32).
INPUT_HEIGHT = 480
INPUT_WIDTH = 640
INPUT_CHANNELS = 3
u = tf.placeholder(tf.float32, shape=(None, INPUT_HEIGHT, INPUT_WIDTH, INPUT_CHANNELS))

# CNN params: wights only, no bias as per [Ioffe & Szegedy 2015].
CNN_FILTER_HEIGHT = 3  # Space dimension.
CNN_FILTER_WIDTH = 3  # Space dimension.
CNN_FILTERS = 128
w = tf.Variable(tf.truncated_normal(
    [CNN_FILTER_HEIGHT, CNN_FILTER_WIDTH, INPUT_CHANNELS, CNN_FILTERS],
    dtype=tf.float32, stddev=1e-1))

# Layer output with no activation function (yet).
CNN_LAYER_STRIDE_VERTICAL = 1
CNN_LAYER_STRIDE_HORIZONTAL = 1
CNN_LAYER_PADDING = 'SAME'
cnn = tf.nn.conv2d(
    input=u, filter=w,
    strides=[1, CNN_LAYER_STRIDE_VERTICAL, CNN_LAYER_STRIDE_HORIZONTAL, 1],
    padding=CNN_LAYER_PADDING)

# Batch normalization.
cnn_bn = tf.contrib.layers.batch_norm(
    cnn,
    data_format='NHWC',  # Matching the "cnn" tensor which has shape (?, 480, 640, 128).
    center=True,
    scale=True,
    is_training=training,
    scope='cnn-batch_norm')

# Activation function.
cnn_bn_relu = tf.nn.relu(cnn_bn)
print(cnn_bn_relu)  # Tensor("Relu_1:0", shape=(?, 480, 640, 128), dtype=float32)

3D сверточный (CNN3D) слой

# Input: sequence of 9 160x120 RGB images (whitened input, hence tf.float32).
INPUT_SEQ_LENGTH = 9
INPUT_HEIGHT = 120
INPUT_WIDTH = 160
INPUT_CHANNELS = 3
u = tf.placeholder(tf.float32, shape=(None, INPUT_SEQ_LENGTH, INPUT_HEIGHT, INPUT_WIDTH, INPUT_CHANNELS))

# CNN params: wights only, no bias as per [Ioffe & Szegedy 2015].
CNN3D_FILTER_LENGHT = 3  # Time dimension.
CNN3D_FILTER_HEIGHT = 3  # Space dimension.
CNN3D_FILTER_WIDTH = 3  # Space dimension.
CNN3D_FILTERS = 96
w = tf.Variable(tf.truncated_normal(
    [CNN3D_FILTER_LENGHT, CNN3D_FILTER_HEIGHT, CNN3D_FILTER_WIDTH, INPUT_CHANNELS, CNN3D_FILTERS],
    dtype=tf.float32, stddev=1e-1))

# Layer output with no activation function (yet).
CNN3D_LAYER_STRIDE_TEMPORAL = 1
CNN3D_LAYER_STRIDE_VERTICAL = 1
CNN3D_LAYER_STRIDE_HORIZONTAL = 1
CNN3D_LAYER_PADDING = 'SAME'
cnn3d = tf.nn.conv3d(
    input=u, filter=w,
    strides=[1, CNN3D_LAYER_STRIDE_TEMPORAL, CNN3D_LAYER_STRIDE_VERTICAL, CNN3D_LAYER_STRIDE_HORIZONTAL, 1],
    padding=CNN3D_LAYER_PADDING)

# Batch normalization.
cnn3d_bn = tf.contrib.layers.batch_norm(
    cnn3d,
    data_format='NHWC',  # Matching the "cnn" tensor which has shape (?, 9, 120, 160, 96).
    center=True,
    scale=True,
    is_training=training,
    scope='cnn3d-batch_norm')

# Activation function.
cnn3d_bn_relu = tf.nn.relu(cnn3d_bn)
print(cnn3d_bn_relu)  # Tensor("Relu_2:0", shape=(?, 9, 120, 160, 96), dtype=float32)

Я хотел бы убедиться в том, что приведенный выше код точно реализует пакетную нормализацию, как описано в [Ioffe & Szegedy 2015] в конце гл. 3,2:

Для сверточных слоев мы дополнительно хотим, чтобы нормализация подчинялась сверточному свойству - чтобы разные элементы одной и той же карты объектов в разных местах нормировались одинаково. Чтобы добиться этого, мы совместно нормализуем все активации в мини-пакете во всех местоположениях. [...] Алг.2 модифицируется аналогичным образом, так что во время вывода преобразование BN применяет одно и то же линейное преобразование к каждой активации в данной карте признаков.

ОБНОВЛЕНИЕ Я полагаю, что приведенный выше код также является правильным для случая 3D конв. Фактически, когда я определяю свою модель, если я печатаю все обучаемые переменные, я также вижу ожидаемые числа бета- и гамма-переменных. Например:

Tensor("conv3a/conv3d_weights/read:0", shape=(3, 3, 3, 128, 256), dtype=float32)
Tensor("BatchNorm_2/beta/read:0", shape=(256,), dtype=float32)
Tensor("BatchNorm_2/gamma/read:0", shape=(256,), dtype=float32)

Это выглядит нормально для меня, так как благодаря BN, одна пара бета и гамма выучены для каждой карты характеристик (всего 256).


[Ioffe & Szegedy 2015]: нормализация партий: ускорение глубокого обучения работе с сетью за счет уменьшения внутреннего смещения ковариат

1 ответ

Это отличный пост о 3D batchnorm, часто не замечают, что batchnorm может быть применен к любому тензору ранга больше 1. Ваш код верен, но я не мог не добавить несколько важных замечаний по этому поводу:

  • "Стандартный" двумерный пакет (принимает четырехмерный тензор) может быть значительно быстрее в тензорном потоке, чем трехмерный или выше, поскольку он поддерживает fused_batch_norm реализация, которая применяет одну операцию ядра:

    Объединенная норма пакетов объединяет несколько операций, необходимых для нормализации пакетов, в одно ядро. Пакетная норма - это дорогостоящий процесс, который для некоторых моделей составляет большой процент времени работы. Использование нормы слитых партий может привести к ускорению на 12-30%.

    В GitHub есть проблема с поддержкой 3D-фильтров, но в последнее время не было никаких действий, и на данный момент проблема закрыта нерешенной.

  • Хотя в оригинальной статье предписывается использовать batchnorm перед активацией ReLU (и это то, что вы сделали в приведенном выше коде), есть доказательства того, что, вероятно, лучше использовать batchnorm после активации. Вот комментарий к Keras GitHub от Франсуа Шоле:

    ... Я могу гарантировать, что недавний код, написанный Кристианом [Сегеди], применяет relu перед BN. Тем не менее, иногда это тема дебатов.

  • Для тех, кто заинтересован в применении идеи нормализации на практике, недавно были разработаны исследования этой идеи, а именно нормализация веса и нормализация слоев, которые исправляют некоторые недостатки оригинального пакетного алгоритма, например, они лучше работают для LSTM и рекуррентных сетей.

Другие вопросы по тегам