Введение

Основным вкладом архитектуры VGGNet (https://arxiv.org/pdf/1409.1556.pdf) было создание модели машинного обучения с очень маленькими (3 x 3) свёрточными фильтрами, которые можно обучать до большей глубины ( 16–19 весовых слоев) и тем самым получить классификационную модель с очень высокой точностью. Архитектура VGGNet построена на двух ключевых компонентах:

(1). Все сверточные слои в VGGNet используют очень маленькие сверточные фильтры размером 3 x 3.

(2). Архитектура VGGNet объединяет несколько наборов комбинаций слоев CONV => RELU, тем самым увеличивая глубину сети перед применением операции MaxPool.

В этой статье я придумал пользовательскую реализацию VGGNet для классификации набора данных CIFAR10.

Уровни пользовательской реализации VGGNet

Куда действительно подходит BN?

В большинстве реализаций NN BN предшествует уровню активации. В моем пользовательском примере VGGNet BN идет после активации, поскольку я специально хочу игнорировать функции с отрицательным значением, поскольку функция активации, такая как ReLU, убьет любые активации меньше нуля. Размещение BN после ReLU нормализует только функции с положительным значением.

Реализация Python

Шаг 1. Импортируйте необходимые пакеты

from keras.models import Sequential
from keras.layers.normalization import BatchNormalization
from keras.layers.convolutional import Conv2D, MaxPooling2D
from keras.layers.core import Dense, Flatten, Dropout, Activation
from keras import backend as K
from sklearn.preprocessing import LabelBinarizer
from sklearn.metrics import classification_report
from keras.optimizers import SGD
from keras.datasets import cifar10
import matplotlib.pyplot as plt
import numpy as np

Шаг 2. Пользовательская архитектура VGGNet

class CustomVGGNet:
    @staticmethod
    def build(width, height, depth, classes):
        model = Sequential()
        inputShape = (height,width,depth)
        chanDim = -1
                  
        model.add(Conv2D(32,(3,3), padding="same", input_shape=inputShape))
        model.add(Activation("relu"))
        model.add(BatchNormalization(axis=chanDim))
        model.add(Conv2D(32,(3,3), padding="same"))
        model.add(Activation("relu"))
        model.add(BatchNormalization(axis=chanDim))
        model.add(Conv2D(32,(3,3), padding="same"))
        model.add(Activation("relu"))
        model.add(BatchNormalization(axis=chanDim))
        model.add(MaxPooling2D(pool_size=(2,2), strides=(2,2)))
        model.add(Dropout(0.25))
        
        model.add(Conv2D(64,(3,3), padding="same"))
        model.add(Activation("relu"))
        model.add(BatchNormalization(axis=chanDim))
        model.add(Conv2D(64,(3,3), padding="same"))
        model.add(Activation("relu"))
        model.add(BatchNormalization(axis=chanDim))
        model.add(Conv2D(64,(3,3), padding="same"))
        model.add(Activation("relu"))
        model.add(BatchNormalization(axis=chanDim))
        model.add(MaxPooling2D(pool_size=(2,2), strides=(2,2)))
        model.add(Dropout(0.25))
        
        model.add(Flatten())
        model.add(Dense(1024))
        model.add(Activation("relu"))
        model.add(BatchNormalization())
        model.add(Dropout(0.25))
        
        model.add(Dense(classes))
        model.add(Activation("softmax"))
        
        return model

Шаг 3. Загрузите набор данных CIFAR-10 и увеличьте интенсивность пикселей до [0,1]

((trainX,trainY), (testX,testY)) = cifar10.load_data() //data load
// scaling pixel intestities
trainX = trainX.astype("float")/255.0
testX = testX.astype("float")/255.0

Шаг 4. Преобразование выходных меток из целых чисел в векторы и инициализация имен меток

le = LabelBinarizer()
trainY = le.fit_transform(trainY)
testY = le.transform(testY)
labels = ["airplane", "automobile", "bird", "cat", "deer", "dog", "frog", "horse", "ship", "truck"]

Шаг 5. Скомпилируйте и обучите модель CustomVGGNet

opt = SGD(lr=0.01,momentum=0.9,nesterov=True,decay=0.001/5)
model = MiniVGGNet.build(width=32, height=32, depth=3, classes=10)
model.compile(loss=”categorical_crossentropy”, optimizer=opt, 
metrics=[“accuracy”])
// model training
H = model.fit(trainX, trainY, validation_data=(testX,testY), batch_size=128, epochs=5)

Шаг 6. Прогнозирование модели

predictions = model.predict(testX, batch_size=128)
print(classification_report(testY.argmax(axis=1), predictions.argmax(axis=1), target_names=labels))

Примечание.Ниже представлен отчет о классификации для 5 эпох. Точность близка к ~88% для 50 эпох.

Отчет о классификации (5 эпох):

Шаг 7. График точности и потерь

plt.style.use("ggplot")
plt.figure()
plt.plot(np.arange(0,3), H.history["loss"], label="train_loss")
plt.plot(np.arange(0,3), H.history["val_loss"], label="val_loss")
plt.plot(np.arange(0,3), H.history["acc"], label="train_acc")
plt.plot(np.arange(0,3), H.history["val_acc"], label="val_acc")
plt.legend()
plt.show()

Действительно ли полезна пакетная нормализация (BN)?

Архитектура CustomVGGNetобучается быстрее без слоя BN, а точность меньше (~80%) по сравнению с 88% с BN. BN делает сеть более стабильной и предотвращает переоснащение модели. Без BN сеть имеет тенденцию переопределять данные, и точность проверки становится предельной после 23 эпох.

Заключение/Основные выводы

В этой статье я реализовал пользовательскую архитектуру VGG, состоящую из двух наборов (CONV => RELU) * 3 => MAXPOOL => FC => RELU => FC => слои SOFTMAX. Использование слоя BN после слоя активации привело к более быстрой сходимости модели с относительно более высокой точностью при ее реализации без BN.