Остаточные сети (ResNet) - глубокое обучение

Опубликовано: 18 Февраля, 2022

После первой архитектуры на основе CNN (AlexNet), которая выиграла соревнование ImageNet 2012, каждая последующая победившая архитектура использует больше слоев в глубокой нейронной сети, чтобы уменьшить количество ошибок. Это работает для меньшего количества слоев, но когда мы увеличиваем количество слоев, в глубоком обучении возникает общая проблема, связанная с тем, что называется градиентом исчезновения / взрыва. Это приводит к тому, что градиент становится 0 или слишком большим. Таким образом, когда мы увеличиваем количество слоев, также увеличивается частота ошибок при обучении и тестировании.

На приведенном выше графике мы можем заметить, что 56-слойная CNN дает больше ошибок как для обучающего, так и для тестового набора данных, чем 20-слойная архитектура CNN. Если это было результатом чрезмерной подгонки, то у нас должна быть меньшая ошибка обучения в 56. -уровень CNN, но тогда он также имеет более высокую ошибку обучения. Проанализировав более подробно частоту ошибок, авторы смогли прийти к выводу, что она вызвана исчезающим / увеличивающимся градиентом.

ResNet, предложенная в 2015 году исследователями Microsoft Research, представила новую архитектуру под названием Residual Network.

Остаточный блок:
Чтобы решить проблему исчезающего / увеличивающегося градиента, в этой архитектуре была введена концепция, называемая остаточной сетью. В этой сети мы используем технику, называемую пропуском подключений . Пропустить соединение пропускает обучение с нескольких слоев и подключается непосредственно к выходу.

Подход, лежащий в основе этой сети, заключается в том, что вместо того, чтобы изучать слои, базовое отображение, мы позволяем сети соответствовать остаточному отображению. Итак, вместо того, чтобы говорить H (x), начальное отображение , пусть сеть соответствует, F (x): = H (x) - x, что дает H (x): = F (x) + x .

Преимущество добавления этого типа пропуска соединения заключается в том, что если какой-либо уровень ухудшает производительность архитектуры, он будет пропущен регуляризацией. Таким образом, это приводит к обучению очень глубокой нейронной сети без проблем, вызванных исчезающим / увеличивающимся градиентом. Авторы статьи экспериментировали со 100-1000 слоями на наборе данных CIFAR-10.

Существует аналогичный подход, называемый «магистральными сетями», в этих сетях также используется пропускное соединение. Подобно LSTM, эти пропускаемые соединения также используют параметрические вентили. Эти ворота определяют, сколько информации проходит через пропускаемое соединение. Однако эта архитектура не обеспечивает большей точности, чем архитектура ResNet.

Сетевая архитектура:

Эта сеть использует 34-уровневую простую сетевую архитектуру, вдохновленную VGG-19, в которую затем добавляется ярлык подключения. Эти быстрые соединения затем преобразуют архитектуру в остаточную сеть.

Реализация:

Using the Tensorflow and Keras API, we can design ResNet architecture (including Residual Blocks) from scratch. Below is the implementation of different ResNet architecture. For this implementation we use CIFAR-10 dataset. This dataset contains 60, 000 32×32 color images in 10 different classes (airplanes, cars, birds, cats, deer, dogs, frogs, horses, ships, and trucks) etc. This datasets can be assessed  from keras.datasets API function.

  • First, we import the keras module and its APIs. These APIs help in building architecture of the ResNet model.
Code: Importing Libraries
# Import Keras modules and its important APIs
import keras
from keras.layers import Dense, Conv2D, BatchNormalization, Activation
from keras.layers import AveragePooling2D, Input, Flatten
from keras.optimizers import Adam
from keras.callbacks import ModelCheckpoint, LearningRateScheduler
from keras.callbacks import ReduceLROnPlateau
from keras.preprocessing.image import ImageDataGenerator
from keras.regularizers import l2
from keras import backend as K
from keras.models import Model
from keras.datasets import cifar10
import numpy as np
import os
  • Now, We set different hyper parameters that is required for ResNet architecture. We also done some preprocess our datasets to prepare it for training.
Code: Setting Training Hyperparameters
# Setting Training Hyperparameters
batch_size = 32  # original ResNet paper uses batch_size = 128 for training
epochs = 200
data_augmentation = True
num_classes = 10
  
# Data Preprocessing 
subtract_pixel_mean = True
n = 3
  
# Select ResNet Version
version = 1
  
# Computed depth of 
if version == 1:
    depth = n * 6 + 2
elif version == 2:
    depth = n * 9 + 2
  
# Model name, depth and version
model_type = "ResNet % dv % d" % (depth, version)
  
# Load the CIFAR-10 data.
(x_train, y_train), (x_test, y_test) = cifar10.load_data()
  
# Input image dimensions.
input_shape = x_train.shape[1:]
  
# Normalize data.
x_train = x_train.astype("float32") / 255
x_test = x_test.astype("float32") / 255
  
# If subtract pixel mean is enabled
if subtract_pixel_mean:
    x_train_mean = np.mean(x_train, axis = 0)
    x_train -= x_train_mean
    x_test -= x_train_mean
  
# Print Training and Test Samples 
print("x_train shape:", x_train.shape)
print(x_train.shape[0], "train samples")
print(x_test.shape[0], "test samples")
print("y_train shape:", y_train.shape)
  
# Convert class vectors to binary class matrices.
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)
  • In this step, we set the learning rate according to the number of epochs. As the number of epochs the learning rate must be decreased to ensure better learning.

Code: Setting LR for different number of Epochs

# Setting LR for different number of Epochs
def lr_schedule(epoch):
    lr = 1e-3
    if epoch > 180:
        lr *= 0.5e-3
    elif epoch > 160:
        lr *= 1e-3
    elif epoch > 120:
        lr *= 1e-2
    elif epoch > 80:
        lr *= 1e-1
    print("Learning rate: ", lr)
    return lr
  • In this step we define basic ResNet building block that can be used for defining the ResNet V1 and V2 architecture.
Code: Basic ResNet Building Block
# Basic ResNet Building Block
def resnet_layer(inputs,
                 num_filters = 16,
                 kernel_size = 3,
                 strides = 1,
                 activation ="relu",
                 batch_normalization = True,
    conv = Conv2D(num_filters,
                  kernel_size = kernel_size,
                  strides = strides,
                  padding ="same",
                  kernel_initializer ="he_normal",
                  kernel_regularizer = l2(1e-4))
  
    x = inputs
    if conv_first:
        x = conv(x)
        if batch_normalization:
            x = BatchNormalization()(x)
        if activation is not None:
            x = Activation(activation)(x)
    else:
        if batch_normalization:
            x = BatchNormalization()(x)
        if activation is not None:
            x = Activation(activation)(x)
        x = conv(x)
    return x
  • In this step we define ResNet V1 architecture that is based on the ResNet building block we defined above:
Code: ResNet V1 architecture
def resnet_v1(input_shape, depth, num_classes = 10):
      
    if (depth - 2) % 6 != 0:
        raise ValueError("depth should be 6n + 2 (eg 20, 32, 44 in [a])")
    # Start model definition.
    num_filters = 16
    num_res_blocks = int((depth - 2) / 6)
  
    inputs = Input(shape = input_shape)
    x = resnet_layer(inputs = inputs)
    # Instantiate the stack of residual units
    for stack in range(3):
        for res_block in range(num_res_blocks):
            strides = 1
            if stack > 0 and res_block == 0# first layer but not first stack
                strides = 2  # downsample
            y = resnet_layer(inputs = x,
                             num_filters = num_filters,
                             strides = strides)
            y = resnet_layer(inputs = y,
                             num_filters = num_filters,
                             activation = None)
            if stack > 0 and res_block == 0# first layer but not first stack
                # linear projection residual shortcut connection to match
                # changed dims
                x = resnet_layer(inputs = x,
                                 num_filters = num_filters,
                                 kernel_size = 1,
                                 strides = strides,
                                 activation = None,
                                 batch_normalization = False)
            x = keras.layers.add([x, y])
            x = Activation("relu")(x)
        num_filters *= 2
  
    # Add classifier on top.
    # v1 does not use BN after last shortcut connection-ReLU
    x = AveragePooling2D(pool_size = 8)(x)
    y = Flatten()(x)
    outputs = Dense(num_classes,
                    activation ="softmax",
                    kernel_initializer ="he_normal")(y)
  
    # Instantiate model.
    model = Model(inputs = inputs, outputs = outputs)
    return model
  • In this step we define ResNet V2 architecture that is based on the ResNet building block we defined above:
Code: ResNet V2 architecture
# ResNet V2 architecture
def resnet_v2(input_shape, depth, num_classes = 10):
    if (depth - 2) % 9 != 0:
        raise ValueError("depth should be 9n + 2 (eg 56 or 110 in [b])")
    # Start model definition.
    num_filters_in = 16
    num_res_blocks = int((depth - 2) / 9)
  
    inputs = Input(shape = input_shape)
    # v2 performs Conv2D with BN-ReLU on input before splitting into 2 paths
    x = resnet_layer(inputs = inputs,
                     num_filters = num_filters_in,
                     conv_first = True)
  
    # Instantiate the stack of residual units
    for stage in range(3):
        for res_block in range(num_res_blocks):
            activation = "relu"
            batch_normalization = True
            strides = 1
            if stage == 0:
                num_filters_out = num_filters_in * 4
                if res_block == 0# first layer and first stage
                    activation = None
                    batch_normalization = False
            else:
                num_filters_out = num_filters_in * 2
                if res_block == 0# first layer but not first stage
                    strides = 2    # downsample
  
            # bottleneck residual unit
            y = resnet_layer(inputs = x,
                             num_filters = num_filters_in,
                             kernel_size = 1,
                             strides = strides,
                             activation = activation,
                             batch_normalization = batch_normalization,
                             conv_first = False)
            y = resnet_layer(inputs = y,
                             num_filters = num_filters_in,
                             conv_first = False)
            y = resnet_layer(inputs = y,
                             num_filters = num_filters_out,
                             kernel_size = 1,
                             conv_first = False)
            if res_block == 0:
                # linear projection residual shortcut connection to match
                # changed dims
                x = resnet_layer(inputs = x,
                                 num_filters = num_filters_out,
                                 kernel_size = 1,
                                 strides = strides,
                                 activation = None,
                                 batch_normalization = False)
            x = keras.layers.add([x, y])
  
        num_filters_in = num_filters_out
  
    # Add classifier on top.
    # v2 has BN-ReLU before Pooling
    x = BatchNormalization()(x)
    x = Activation("relu")(x)
    x = AveragePooling2D(pool_size = 8)(x)
    y = Flatten()(x)
    outputs = Dense(num_classes,
                    activation ="softmax",
                    kernel_initializer ="he_normal")(y)
  
    # Instantiate model.
    model = Model(inputs = inputs, outputs = outputs)
    return model
  • The code below is used to train and test the ResNet v1 and v2 architecture we defined above:
Code: Main function
# Main function 
if version == 2:
    model = resnet_v2(input_shape = input_shape, depth = depth)
else:
    model = resnet_v1(input_shape = input_shape, depth = depth)
  
model.compile(loss ="categorical_crossentropy",
              optimizer = Adam(learning_rate = lr_schedule(0)),
              metrics =["accuracy"])
model.summary()
print(model_type)
  
# Prepare model model saving directory.
save_dir = os.path.join(os.getcwd(), "saved_models")
model_name = "cifar10_% s_model.{epoch:03d}.h5" % model_type
if not os.path.isdir(save_dir):
    os.makedirs(save_dir)