В этой статье я хочу использовать трансферное обучение и сравнить три лучшие предварительно обученные модели для повышения точности классификации изображений: VGG-16, RESNET-50 и EfficientNet-B7. Вы можете перейти на веб-сайт Keras в разделе Приложение, чтобы узнать больше об этих моделях, и по этой ссылке GitHub для получения полного кода.

Но прежде всего, что такое трансферное обучение и предварительно обученная модель?

Проще говоря, трансферное обучение — это методология, которая применяет предварительно обученную модель к новому набору задач. Представьте, что мы хотим узнать что-то новое, и мы учим это с нуля. Это займет много времени, так как нам нужно найти и отфильтровать слишком много информации. Но есть один лучший способ: попросить опытного эксперта передать нам свои знания, сделав его более эффективным и простым. Вот что делает трансферное обучение.

Что касается предварительно обученной модели, то со ссылкой на сайт Keras это:

Модель глубокого обучения, которая доступна вместе с предварительно обученными весами и может использоваться для прогнозирования, извлечения признаков и точной настройки.

Существует несколько способов применения трансферного обучения с использованием предварительно обученных моделей. Во-первых, мы можем создать собственную обученную модель сверточной нейронной сети, добавить полносвязный слой, а затем обучить его нашему набору данных. Во-вторых, вместо того, чтобы создавать собственную модель CNN и пытаться найти лучшие гиперпараметры, мы можем использовать доступные модели в Keras.

В этом проекте мы будем использовать трансферное обучение для повышения точности нашей базовой модели с использованием упомянутых выше трех моделей Keras. Перед запуском нашего кода нам нужно загрузить набор данных, который мы будем использовать, данные Imagenett. Imagenette — это подмножество набора данных ImageNet, подготовленного Джереми Ховардом из FastAI. Он состоит из десяти классифицированных классов с меньшим объемом данных, чем ImageNet, что делает его применимым для быстрых экспериментов. Чтобы загрузить набор данных, запустите эти строки на терминале:

$ wget https://s3.amazonaws.com/fast-ai-imageclas/imagenette2.tgz 
$ tar -xf imagenette2.tgz

Как только вы загрузите его, вы найдете папки train и val, а также CSV-файл шумных меток. Мы будем использовать только изображения, хранящиеся в подфайлах папок train и val. Чтобы начать проект, нам нужно импортировать все необходимые библиотеки.

import pandas as pd
import warnings
warnings.filterwarnings('ignore')
import os

import numpy as np
import matplotlib.pyplot as plt
import random

import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, MaxPool2D, Dense, Flatten, BatchNormalization, Dropout, InputLayer
from tensorflow.keras import regularizers
from tensorflow.keras.metrics import binary_crossentropy
from tensorflow.keras.preprocessing import image_dataset_from_directory

from keras.preprocessing.image import ImageDataGenerator
from keras.applications import VGG16, ResNet50, EfficientNetB7 
from keras.utils import to_categorical

Теперь мы загружаем набор данных с помощью ImageDataGenerator для создания пакетов тензорных изображений путем преобразования их в определенный вывод. Поскольку выходом для этой модели являются категории, мы устанавливаем class_mode с категориальным вводом. Введите (244, 244) размеры для target_size, до которых будут изменены размеры всех изображений. Отключите перемешивание и введите 32 для размера партии по умолчанию.

# Creating a generator to load train and val data
img_generator = ImageDataGenerator()
train = img_generator.flow_from_directory("imagenette/train/", class_mode="categorical", target_size=(224, 224), shuffle=False, batch_size=32)
val = img_generator.flow_from_directory("imagenette/val/", class_mode="categorical", target_size=(224, 224), shuffle=False, batch_size=32)

После того, как у нас есть готовый набор данных, мы делаем последовательную модель. Есть несколько способов сделать последовательную модель. Мы можем объявить последовательную модель, а затем добавить слои. Или поместите все слои в последовательный код, как мы собираемся сделать сейчас. Эта последовательная модель состоит из слоя изучения признаков (сверточные слои + объединение слоев) и полностью связанного (ANN) слоя.

# Building a sequential model
model = Sequential([    
    # 1st conv layer
    # Initiate input_shape, declare padding param for adding layers of zeros to input images
    Conv2D(25, (3, 3), activation='relu', strides=(1, 1), padding='same', input_shape=(224, 224, 3)),
    MaxPool2D(pool_size=(2, 2), padding='same'),
    
    # 2nd conv layer
    Conv2D(50, (5, 5), activation='relu', strides=(1, 1), padding='same'),
    MaxPool2D(pool_size=(2, 2), padding='same'),
    BatchNormalization(), # To maintain the mean output close to 0 and the output standard deviation close to 1
    
    # 3rd conv layer
    Conv2D(70, (7, 7), activation='relu', strides=(2, 2), padding='same'),
    MaxPool2D(pool_size=(2, 2), padding='same'),
    BatchNormalization(),
    
    # ANN (fully connected) layer
    ## Fully connected input layer
    Flatten(),
    ## Fully connected hidden layer (using regularization and dropout to prevent overfitting)
    Dense(units=128, activation='relu', kernel_regularizer=regularizers.l2(0.001)),
    Dropout(0.3),
    Dense(units=128, activation='relu', kernel_regularizer=regularizers.l2(0.001)),
    Dropout(0.3), 
    ## Fully connected output layer
    Dense(units=10, activation='softmax'), 
])

# Compiling the model
model.compile(loss='categorical_crossentropy', optimizer="adam", metrics=['accuracy'])

Для большего пояснения возьмем пример первого слоя Conv2D. Но прежде чем я объясню это, я рекомендую вам получить общее представление о CNN с помощью этого видео. Мы установили 25 нейронов с размерностью ядра (3, 3) и установили relu в качестве функции активации. Шаг (высота и ширина шага свертки входного объема) равен (1, 1), а заполнение также установлено на то же самое для добавления нулевого значения к обработке ядром. Поскольку это первый слой, мы должны указать input_size, что в данном случае мы поместили (224, 224) в качестве размера изображения и (3) в качестве изображения RGB, но нам не нужно помещать этот параметр в следующие слои.

Для слоя пула (MaxPool2D) мы установим все размеры окна пула как (2, 2) и добавим отступы. Что касается 2-го и 3-го сверточных слоев, мы устанавливаем BatchNormalization() для среднего вывода, близкого к 0, и выходного стандартного отклонения, близкого к 1.

Закончив со слоем изучения признаков, мы теперь переходим к полностью связанному слою, который состоит из входного слоя с использованием Flatten(), двух скрытых слоев Dense() с регуляризацией и отсевом для минимизации переобучения, а последний — выходной слой с функция активации «softmax».

После построения последовательной модели мы компилируем ее как процесс доработки. Мы устанавливаем функцию потерь на categorical_crossentropy, поскольку выходных меток больше двух, выбираем обычно используемый оптимизатор «адам» и устанавливаем «точность» для метрики оценки.

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

# fit on data for 10 epochs
history = model.fit(train, epochs=10, validation_data=val)

# Model Evaluation
train_loss, train_acc = model.evaluate(train)
print(train_loss, train_acc)

val_loss, val_acc = model.evaluate(val)
print(val_loss, val_acc)

Как видно ниже, созданная нами модель могла работать лучше. Он имеет только менее 20% точности и показатель потерь более 2,4 в данных поезда и вал.

Чтобы решить эту проблему, мы выполним трансферное обучение с использованием предварительно обученных моделей от Keras. Во-первых, мы используем модель VGG-16, на которую можно сослаться по этой ссылке. Установите include_top как False, чтобы удалить полностью подключенный слой, потому что мы добавим его после этого.

# Make a pretrained model VGG-16
# Set include_top as False to remove the fully connected layer 
vgg16_pretrained_model = VGG16(include_top=False,
                         weights='imagenet') 

vgg16_pretrained_model.summary()

Вот сводка предварительно обученной модели VGG-16:

Мы используем VGG-16 для обучения нашего набора данных, получаем из него прогнозируемое значение, а затем используем его для обучения в нашем новом полносвязном слое.

# Extracting features 
x_train_vgg16 = vgg16_pretrained_model.predict(train) 
x_val_vgg16 = vgg16_pretrained_model.predict(val)

# Converting target value to categorical
y_train_vgg16 = to_categorical(train.labels) 
y_val_vgg16 = to_categorical(val.labels)

Мы пока сохраняем этот новый набор данных и создадим последовательную модель полностью связанного слоя.

# Make a sequential model
vgg16_model = Sequential()
vgg16_model.add(Flatten(input_shape=(7, 7, 512)))
vgg16_model.add(Dense(512, activation='relu', kernel_regularizer=regularizers.l2(0.001)))
vgg16_model.add(Dropout(0.5))
vgg16_model.add(BatchNormalization())
vgg16_model.add(Dense(512, activation='relu', kernel_regularizer=regularizers.l2(0.001)))
vgg16_model.add(Dropout(0.5))
vgg16_model.add(BatchNormalization())
vgg16_model.add(Dense(10, activation='softmax'))

vgg16_model.summary()

Как и в случае с базовой моделью, нам также необходимо доработать ее, скомпилировав и подогнав модель.

# Compile model
vgg16_model.compile(metrics=['accuracy'], loss='categorical_crossentropy', optimizer='adam')

# Fit the model
vgg16_history = vgg16_model.fit(x_train_vgg16, y_train_vgg16, epochs=10, validation_data=(x_val_vgg16, y_val_vgg16), batch_size=128)

Теперь запустите оценку модели и посмотрите результат ниже. Что вы думаете?

vgg16_train_loss, vgg16_train_acc = vgg16_model.evaluate(x_train_vgg16, y_train_vgg16)
print(vgg16_train_loss, vgg16_train_acc)

vgg16_val_loss, vgg16_val_acc = vgg16_model.evaluate(x_val_vgg16, y_val_vgg16)
print(vgg16_val_loss, vgg16_val_acc)

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

Вот почему нам нужно найти и сравнить другие предварительно обученные модели, чтобы найти лучшую модель для этого набора данных. Как упоминалось ранее, мы также будем запускать Resnet-50 и EfficientNet-B7.

По сути, мы можем использовать почти все коды для VGG-16 и применять их к другим предварительно обученным моделям. Отличие только в начальной генерации модели.

Для Реснет-50 используйте этот код:

# Make a pretrained model of Resnet-50
resnet50_pretrained_model = ResNet50(
                            include_top=False,
                            weights="imagenet")

resnet50_pretrained_model.summary()

И используйте этот ниже для EfficientNet-B7:

# Make a pretrained model
efficientnetb7_pretrained_model = EfficientNetB7(
                            include_top=False,
                            weights="imagenet")

efficientnetb7_pretrained_model.summary()

Мы можем добавить другие параметры по мере необходимости, но этих параметров, которые у нас есть, достаточно для основных нужд этого проекта. Еще одна вещь, которую нужно изменить, — это полностью связанный слой input_shape. Позвольте мне показать вам, где изменить.

# Make a sequential model
efficientnetb7_model = Sequential()

# Use input_shape=(7, 7, 512) for VGG-16
efficientnetb7_model.add(Flatten(input_shape=(7, 7, 512))

# Use input_shape=(7, 7, 2048) for Resnet-50
# efficientnetb7_model.add(Flatten(input_shape=(7, 7, 2048))

# Use input_shape=(7, 7, 2560) for EfficientNet-B7
# efficientnetb7_model.add(Flatten(input_shape=(7, 7, 2560)))


efficientnetb7_model.add(Dense(512, activation='relu', kernel_regularizer=regularizers.l2(0.001)))
efficientnetb7_model.add(Dropout(0.5))
efficientnetb7_model.add(BatchNormalization())
efficientnetb7_model.add(Dense(512, activation='relu', kernel_regularizer=regularizers.l2(0.001)))
efficientnetb7_model.add(Dropout(0.5))
efficientnetb7_model.add(BatchNormalization())
efficientnetb7_model.add(Dense(10, activation='softmax'))

efficientnetb7_model.summary()

Приведенный выше код представляет собой полносвязный слой VGG-16, но мы можем использовать его для других моделей. Что касается Resnet-50, нам нужно только изменить значение входной формы на input_shape=(7, 7, 2048). Для EfficientNet-B7 мы меняем его на input_shape=(7, 7, 2560).

Эти две вещи - единственный код, который нам нужно изменить, а остальное - тот же корпус модели VGG-16.

Теперь, после запуска всех кодов, какой результат вы получите? Я могу показать вам эту таблицу сравнения.

# Accuracy and Loss Comparison
data = [['Base Model',train_loss, val_loss, train_acc, val_acc],
       ['Pre-trained VGG-16',vgg16_train_loss, vgg16_val_loss, vgg16_train_acc, vgg16_val_acc],
       ['Pre-trained Resnet-16',resnet50_train_loss, resnet50_val_loss, resnet50_train_acc, resnet50_val_acc],
       ['Pre-trained EfficientNet-B7',efficientnetb7_train_loss, efficientnetb7_val_loss, efficientnetb7_train_acc, efficientnetb7_val_acc]]
df_eval = pd.DataFrame(data, columns = ['Model', 'Train Loss', 'Val Loss', 'Train Accuracy', 'Val Accuracy'])
df_eval

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

# History of Accuracy 
fig,ax = plt.subplots(2, 2, figsize=(30, 20))

# Summarize history for accuracy of base model
ax[0,0].plot(history.history['accuracy'])
ax[0,0].plot(history.history['val_accuracy'])
ax[0,0].set_title('Base Model Accuracy', fontsize=20)
ax[0,0].set_ylabel('Accuracy')
ax[0,0].set_xlabel('Epoch')
ax[0,0].legend(['Train', 'Validation'], loc='upper left')

# Summarize history for accuracy of VGG16
ax[0,1].plot(vgg16_history.history['accuracy'])
ax[0,1].plot(vgg16_history.history['val_accuracy'])
ax[0,1].set_title('VGG16 Model Accuracy', fontsize=20)
ax[0,1].set_ylabel('Accuracy')
ax[0,1].set_xlabel('Epoch')
ax[0,1].legend(['Train', 'Validation'], loc='upper left')


# Summarize history for accuracy of Resnet-50
ax[1,0].plot(resnet50_history.history['accuracy'])
ax[1,0].plot(resnet50_history.history['val_accuracy'])
ax[1,0].set_title('Resnet-50 Model Accuracy', fontsize=20)
ax[1,0].set_ylabel('Accuracy')
ax[1,0].set_xlabel('Epoch')
ax[1,0].legend(['Train', 'Validation'], loc='upper left')

# Summarize history for accuracy of EfficientNet-B7
ax[1,1].plot(efficientnetb7_history.history['accuracy'])
ax[1,1].plot(efficientnetb7_history.history['val_accuracy'])
ax[1,1].set_title('EfficientNet-B7 Model Accuracy', fontsize=20)
ax[1,1].set_ylabel('Accuracy')
ax[1,1].set_xlabel('Epoch')
ax[1,1].legend(['Train', 'Validation'], loc='upper left')

plt.suptitle('Comparison of Accuracy History', fontsize=30)
plt.show()

# History of Loss 
fig,ax = plt.subplots(2, 2, figsize=(30, 20))

# Summarize history for loss of base model
ax[0,0].plot(history.history['loss'])
ax[0,0].plot(history.history['val_loss'])
ax[0,0].set_title('Base Model Loss', fontsize=20)
ax[0,0].set_ylabel('Loss')
ax[0,0].set_xlabel('Epoch')
ax[0,0].legend(['Train', 'Validation'], loc='upper left')

# Summarize history for loss of VGG16
ax[0,1].plot(vgg16_history.history['loss'])
ax[0,1].plot(vgg16_history.history['val_loss'])
ax[0,1].set_title('VGG16 Model Loss', fontsize=20)
ax[0,1].set_ylabel('Loss')
ax[0,1].set_xlabel('Epoch')
ax[0,1].legend(['Train', 'Validation'], loc='upper left')


# Summarize history for loss of Resnet-50
ax[1,0].plot(resnet50_history.history['loss'])
ax[1,0].plot(resnet50_history.history['val_loss'])
ax[1,0].set_title('Resnet-50 Model Loss', fontsize=20)
ax[1,0].set_ylabel('Loss')
ax[1,0].set_xlabel('Epoch')
ax[1,0].legend(['Train', 'Validation'], loc='upper left')

# Summarize history for loss of EfficientNet-B7
ax[1,1].plot(efficientnetb7_history.history['loss'])
ax[1,1].plot(efficientnetb7_history.history['val_loss'])
ax[1,1].set_title('EfficientNet-B7 Model Loss', fontsize=20)
ax[1,1].set_ylabel('Loss')
ax[1,1].set_xlabel('Epoch')
ax[1,1].legend(['Train', 'Validation'], loc='upper left')

plt.suptitle('Comparison of Loss History', fontsize=30)
plt.show()

Из сравнительной таблицы и сравнительных графиков выше можно сделать несколько выводов:

  • Доказано, что модель обучения может значительно повысить точность базовой модели.
  • Основываясь на оценке точности, модель с EfficientNet-B7 имеет наилучший результат, учитывая ее точность, самые низкие различия в точности между данными обучения и проверки, что указывает на наименьшее переоснащение.
  • VGG16 имеет самый низкий балл по данным обучения и проверки на основе функции потерь по сравнению с другими моделями. Хотя EfficientNet-B7 имеет наилучшую точность, его потери увеличиваются по мере увеличения эпохи.

Это все на данный момент. Не стесняйтесь, пишите мне, если мне нужно что-то оценить по содержанию этой статьи. Я ценю каждый комментарий и совет, чтобы сделать этот обмен знаниями еще лучше. Спасибо!

Использованная литература: