Мы
с вами видели, что относительно простые архитектуры нейронных сетей показывают
низкую эффективность классификации БД изображений CIFAR-10 на уровне
70-73 %. Давайте попробуем увеличить долю правильного распознавания за счет
использования более глубокой модели.
Как
мы уже с вами знаем, простое увеличение числа слоев не приводит к желаемым
результатам. Нужно создавать «обходные пути», которые впервые были применены в
архитектуре сети ResNet. На этом занятии я лишь приведу пример
нейросети, состоящей из 12 слоев, содержащей обходные пути. Этот пример целиком
взят из документации по Tensorflow по адресу:
https://www.tensorflow.org/guide/keras/functional
Структура
модели, следующая:
Как
видите, она имеет не последовательную структуру. Обходные связи позволяют
«перепрыгивать» некоторые слои. Поэтому, для описания такой архитектуры
воспользуемся функциональным подходом.
Вначале
импортируем необходимые зависимости и установим зерно датчика случайных чисел в
1, чтобы результаты были повторяемыми:
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.datasets import cifar10, mnist
tf.random.set_seed(1)
Затем,
сформируем обучающую и тестовую выборки БД изображений CIFAR-10:
(x_train, y_train), (x_test, y_test) = cifar10.load_data()
x_train = x_train / 255
x_test = x_test / 255
y_train = keras.utils.to_categorical(y_train, 10)
y_test = keras.utils.to_categorical(y_test, 10)
А,
далее, будем описывать архитектуру сети. Первый блок из последовательных слоев
можно представить, следующим образом:
inputs = keras.Input(shape=(32, 32, 3), name="img")
x = layers.Conv2D(32, 3, activation="relu")(inputs)
x = layers.Conv2D(64, 3, activation="relu")(x)
block_1_output = layers.MaxPooling2D(3)(x)
Его
выход связываем со следующим блоком:
x = layers.Conv2D(64, 3, activation="relu", padding="same")(block_1_output)
x = layers.Conv2D(64, 3, activation="relu", padding="same")(x)
И
формируем суммарный сигнал с выходов x и block_1_output:
block_2_output = layers.add([x, block_1_output])
Как
видите, в Keras имеется
множество вспомогательных слоев. В частности, с помощью слоя add мы описываем
операцию суммирования двух входов. Мало того, если потребуется создать
какой-либо слой с собственной функциональностью, мы всегда можем это сделать
через класс, унаследованный от базового класса:
tf.keras.layers.Layer
Далее,
суммарный выход block_2_output подаем на вход
следующего блока и по аналогии формируем суммарный слой для выходов x и block_2_output:
x = layers.Conv2D(64, 3, activation="relu", padding="same")(block_2_output)
x = layers.Conv2D(64, 3, activation="relu", padding="same")(x)
block_3_output = layers.add([x, block_2_output])
Наконец,
суммарный тензор block_3_output подаем на
последний блок и формируем на выходе классы изображений:
x = layers.Conv2D(64, 3, activation="relu")(block_3_output)
x = layers.GlobalAveragePooling2D()(x)
x = layers.Dense(256, activation="relu")(x)
x = layers.Dropout(0.5)(x)
outputs = layers.Dense(10, activation='softmax')(x)
Обратите
внимание, здесь, в последнем блоке, мы используем слой GlobalAveragePooling2D, который
вычисляет среднее арифметическое по всем каналам, формируя тензор размерностью:
[batch_size,
channels]
А
перед выходным слоем используется Dropout() для
увеличения обобщающей способности сети. Параметр 0,5 означает, что в среднем,
при обучении будет использоваться половина нейронов из 256 предыдущего
полносвязного слоя. Этим самым, мы пытаемся снизить (а в идеале исключить)
эффект переобучения.
Все
слои нейронной сети описаны с использованием функционального подхода. Теперь
создадим ее модель с помощью классаModel, указывая входные и выходные слои:
model = keras.Model(inputs, outputs, name="toy_resnet")
Ну
а далее идут стандартные операции для инициализации модели, обучения и проверки
качества работы сети на тестовой выборке:
model.compile(optimizer='adam',
loss='categorical_crossentropy',
metrics=['accuracy'])
model.fit(x_train, y_train, batch_size=64, epochs=15, validation_split=0.2)
print( model.evaluate(x_test, y_test) )
Я
здесь указал 15 эпох, чтобы не возникало переобучение сети. Также используется
выборка валидации для контроля за процессом переобучения. В глубоких нейронных
сетях это необходимо делать, так как они «склонны» к этому недостатку.
После
15 эпох обучения, были получены следующие результаты:
Epoch 15/15
625/625
[==============================] - 75s 121ms/step - loss: 0.3810 - accuracy:
0.8675 - val_loss: 0.7246 - val_accuracy: 0.7738
313/313
[==============================] - 3s 8ms/step - loss: 0.7722 - accuracy:
0.7649
[0.7721941471099854,
0.7649000287055969]
Как
видите, на тестовой выборке результаты повысились с 72% до 76% благодаря
использованию более глубокой сети. Причем, сходимость в процессе обучения
оказалась медленнее, чем для менее глубоких моделей. Но конечный результат стал
лучше.
Вообще
задача классификации БД изображений CIFAR-10 не такая
простая, как распознавание рукописных цифр. Здесь мы имеем дело с более сложной
структурой самих изображений. Поэтому улучшение результата даже на несколько
процентов имеет большое значение.
Вы
можете самостоятельно попробовать улучшить эти результаты, меняя модель сети,
увеличивая ее глубину. Как я уже отмечал, на сегодняшний день, качество
классификации этих изображений находится на уровне 96,5%.