Остаточные сети (ResNet) - глубокое обучение
После первой архитектуры на основе 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.
# Import Keras modules and its important APIsimport kerasfrom keras.layers import Dense, Conv2D, BatchNormalization, Activationfrom keras.layers import AveragePooling2D, Input, Flattenfrom keras.optimizers import Adamfrom keras.callbacks import ModelCheckpoint, LearningRateSchedulerfrom keras.callbacks import ReduceLROnPlateaufrom keras.preprocessing.image import ImageDataGeneratorfrom keras.regularizers import l2from keras import backend as Kfrom keras.models import Modelfrom keras.datasets import cifar10import numpy as npimport 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.
# Setting Training Hyperparametersbatch_size = 32 # original ResNet paper uses batch_size = 128 for trainingepochs = 200data_augmentation = Truenum_classes = 10 # Data Preprocessing subtract_pixel_mean = Truen = 3 # Select ResNet Versionversion = 1 # Computed depth of if version == 1: depth = n * 6 + 2elif version == 2: depth = n * 9 + 2 # Model name, depth and versionmodel_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") / 255x_test = x_test.astype("float32") / 255 # If subtract pixel mean is enabledif 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 Epochsdef 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.
# Basic ResNet Building Blockdef 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:
# 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:
# ResNet V2 architecturedef 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:
# 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_typeif not os.path.isdir(save_dir): os.makedirs(save_dir)filepath
|