Распознавание изображений в последние годы продвинулось вперед благодаря доступности больших наборов данных и мощных графических процессоров, которые позволили обучать очень глубокие архитектуры. Симонян и др. Авторы VGG продемонстрировали, что, просто складывая больше слоев, мы можем повысить точность. До этого в 2009 Йошуа Бенжио в своей монографии Изучение глубинных архитектур для ИИ дал убедительный теоретический анализ эффективности глубоких архитектур.
В предыдущих сообщениях я продемонстрировал, как применять различные методы, включая пакетную нормализацию, отсев и увеличение данных, к сверточным нейтральным сетям. Можем ли мы построить более точные системы, просто складывая все больше и больше слоев слоев свертки-пакетной нормализации-повторения? В какой-то момент точность улучшится, но за пределами 25+ слоев точность скорее упадет.
Kaiming et al. 2015 впервые продемонстрировал проблему глубины и предложил замечательное решение, которое с тех пор позволило обучить более 2000 слоев! с возрастающей точностью.
В этом посте я расскажу об их технике и о том, как ее применять.
Во-первых, точность снижалась на многих слоях из-за исчезающих градиентов, поскольку слои уходят вглубь, градиенты становятся маленькими, что приводит к ухудшению производительности. Это не имеет ничего общего с переобучением, следовательно, бросившие школу не могут его спасти.
Конечным решением, разработанным Каймингом Хе и его коллегами из Microsoft Research Asia, было введение остаточных соединений. Это простой термин для описания соединения вывода предыдущих слоев с выводом новых слоев.
Предположим, у вас есть семиуровневая сеть. В остаточной настройке вы не только передадите выходные данные уровня 1 на уровень 2 и далее, но также добавите выходные данные уровня 1 к выходным данным уровня 2.
Обозначив каждый слой f (x)
В стандартной сети y = f (x)
Однако в остаточная сеть,
y = f (x) + x

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

Хватит базовой математики, давайте поработаем руками с кодами.

Стандартный двухслойный модуль показан ниже.

def Unit(x,filters):

    out = BatchNormalization()(x)
    out = Activation("relu")(out)
    out = Conv2D(filters=filters, kernel_size=[3, 3], strides=[1, 1], padding="same")(out)

    out = BatchNormalization()(out)
    out = Activation("relu")(out)
    out = Conv2D(filters=filters, kernel_size=[3, 3], strides=[1, 1], padding="same")(out)

    return out
 

Напомним, что в этом модуле мы передаем вход x, пропускаем его через пакетную нормализацию - relucon2d, и вывод берется через тот же стек.

Ниже представлен модуль resnet

def Unit(x,filters):
    res = x
    out = BatchNormalization()(x)
    out = Activation("relu")(out)
    out = Conv2D(filters=filters, kernel_size=[3, 3], strides=[1, 1], padding="same")(out)

    out = BatchNormalization()(out)
    out = Activation("relu")(out)
    out = Conv2D(filters=filters, kernel_size=[3, 3], strides=[1, 1], padding="same")(out)

    out = keras.layers.add([res,out])

    return out

это выглядит очень похоже, но с одним существенным отличием: во-первых, мы сохраняем ссылку «res» на исходный ввод, а после прохождения через слои batchnorm-relu-conv добавляем вывод к остатку, для ясности это было сделано в линия

out = keras.layers.add([res,out])

Эта часть соответствует уравнению y = f (x) + x

Таким образом, мы можем построить реснет, объединив многие из этих модулей вместе.

Перед этим нам нужно немного изменить код, чтобы учесть пул.

def Unit(x,filters,pool=False):
    res = x
    if pool:
        x = MaxPooling2D(pool_size=(2, 2))(x)
        res = Conv2D(filters=filters,kernel_size=[1,1],strides=(2,2),padding="same")(res)
    out = BatchNormalization()(x)
    out = Activation("relu")(out)
    out = Conv2D(filters=filters, kernel_size=[3, 3], strides=[1, 1], padding="same")(out)

    out = BatchNormalization()(out)
    out = Activation("relu")(out)
    out = Conv2D(filters=filters, kernel_size=[3, 3], strides=[1, 1], padding="same")(out)

    out = keras.layers.add([res,out])

    return out

Обратите внимание на что-то выше, когда мы объединяем, тогда размеры нашего вывода больше не будут соответствовать измерению нашего остатка, следовательно, мы не только применим объединение к входу, но остаток также будет преобразован с помощью последовательного преобразования 1 x 1. при этом фильтры будут такими же, как на выходе, а шаг 2 будет вдвое меньше размеров, как и при максимальном объединении.

Объяснив это, я представлю полный реснет.

def Unit(x,filters,pool=False):
    res = x
    if pool:
        x = MaxPooling2D(pool_size=(2, 2))(x)
        res = Conv2D(filters=filters,kernel_size=[1,1],strides=(2,2),padding="same")(res)
    out = BatchNormalization()(x)
    out = Activation("relu")(out)
    out = Conv2D(filters=filters, kernel_size=[3, 3], strides=[1, 1], padding="same")(out)

    out = BatchNormalization()(out)
    out = Activation("relu")(out)
    out = Conv2D(filters=filters, kernel_size=[3, 3], strides=[1, 1], padding="same")(out)

    out = keras.layers.add([res,out])

    return out
def MiniModel(input_shape):
    images = Input(input_shape)
    net = Conv2D(filters=32, kernel_size=[3, 3], strides=[1, 1], padding="same")(images)
    net = Unit(net,32)
    net = Unit(net,32)
    net = Unit(net,32)

    net = Unit(net,64,pool=True)
    net = Unit(net,64)
    net = Unit(net,64)

    net = Unit(net,128,pool=True)
    net = Unit(net,128)
    net = Unit(net,128)

    net = Unit(net, 256,pool=True)
    net = Unit(net, 256)
    net = Unit(net, 256)

    net = BatchNormalization()(net)
    net = Activation("relu")(net)
    net = Dropout(0.25)(net)

    net = AveragePooling2D(pool_size=(4,4))(net)
    net = Flatten()(net)
    net = Dense(units=10,activation="softmax")(net)

    model = Model(inputs=images,outputs=net)

    return model

Это исключает обучающий код, как вы можете видеть, ниже приведен обучающий код с эпохами, установленными на 50 эпох.

Вы можете запустить это бесплатно на GPU с Google Colab.

#import needed classes
import keras
from keras.datasets import cifar10
from keras.layers import Dense,Conv2D,MaxPooling2D,Flatten,AveragePooling2D,Dropout,BatchNormalization,Activation
from keras.models import Model,Input
from keras.optimizers import Adam
from keras.callbacks import LearningRateScheduler
from keras.callbacks import ModelCheckpoint
from math import ceil
import os
from keras.preprocessing.image import ImageDataGenerator


def Unit(x,filters,pool=False):
    res = x
    if pool:
        x = MaxPooling2D(pool_size=(2, 2))(x)
        res = Conv2D(filters=filters,kernel_size=[1,1],strides=(2,2),padding="same")(res)
    out = BatchNormalization()(x)
    out = Activation("relu")(out)
    out = Conv2D(filters=filters, kernel_size=[3, 3], strides=[1, 1], padding="same")(out)

    out = BatchNormalization()(out)
    out = Activation("relu")(out)
    out = Conv2D(filters=filters, kernel_size=[3, 3], strides=[1, 1], padding="same")(out)

    out = keras.layers.add([res,out])

    return out

#Define the model


def MiniModel(input_shape):
    images = Input(input_shape)
    net = Conv2D(filters=32, kernel_size=[3, 3], strides=[1, 1], padding="same")(images)
    net = Unit(net,32)
    net = Unit(net,32)
    net = Unit(net,32)

    net = Unit(net,64,pool=True)
    net = Unit(net,64)
    net = Unit(net,64)

    net = Unit(net,128,pool=True)
    net = Unit(net,128)
    net = Unit(net,128)

    net = Unit(net, 256,pool=True)
    net = Unit(net, 256)
    net = Unit(net, 256)

    net = BatchNormalization()(net)
    net = Activation("relu")(net)
    net = Dropout(0.25)(net)

    net = AveragePooling2D(pool_size=(4,4))(net)
    net = Flatten()(net)
    net = Dense(units=10,activation="softmax")(net)

    model = Model(inputs=images,outputs=net)

    return model

#load the cifar10 dataset
(train_x, train_y) , (test_x, test_y) = cifar10.load_data()

#normalize the data
train_x = train_x.astype('float32') / 255
test_x = test_x.astype('float32') / 255

#Subtract the mean image from both train and test set
train_x = train_x - train_x.mean()
test_x = test_x - test_x.mean()

#Divide by the standard deviation
train_x = train_x / train_x.std(axis=0)
test_x = test_x / test_x.std(axis=0)


datagen = ImageDataGenerator(rotation_range=10,
                             width_shift_range=5. / 32,
                             height_shift_range=5. / 32,
                             horizontal_flip=True)

# Compute quantities required for featurewise normalization
# (std, mean, and principal components if ZCA whitening is applied).
datagen.fit(train_x)



#Encode the labels to vectors
train_y = keras.utils.to_categorical(train_y,10)
test_y = keras.utils.to_categorical(test_y,10)

#define a common unit


input_shape = (32,32,3)
model = MiniModel(input_shape)

#Print a Summary of the model

model.summary()
#Specify the training components
model.compile(optimizer=Adam(0.001),loss="categorical_crossentropy",metrics=["accuracy"])



epochs = 50
steps_per_epoch = ceil(50000/128)

# Fit the model on the batches generated by datagen.flow().
model.fit_generator(datagen.flow(train_x, train_y, batch_size=128),
                    validation_data=[test_x,test_y],
                    epochs=epochs,steps_per_epoch=steps_per_epoch, verbose=1, workers=4)


#Evaluate the accuracy of the test dataset
accuracy = model.evaluate(x=test_x,y=test_y,batch_size=128)
model.save("cifar10model.h5")

Надеюсь, вам это понравилось, будущие посты будут посвящены способам простого масштабирования остаточных сетей по ширине и глубине, а также способам регулировки скорости обучения.

Хорошо проведите время и не забудьте оставить несколько аплодисментов!

Вы всегда можете связаться со мной в твиттере через @johnolafenwa