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

Ответ наверняка: «Да!»

Может ли сверточная нейронная сеть (CNN), решающая эту задачу, быть построена с нуля на обычной локальной машине?

Ответ здесь должен быть немного более расплывчатым:

«Теоретически да, но это требует много времени, данных и ресурсов».

В этой статье я хочу показать, как я строю классификатор пород собак на основе CNN, сравнивая два подхода:

  1. Создание CNN с нуля
  2. Использование подхода, основанного на трансферном обучении

Набор данных

Для обучения CNN я использую набор данных, предоставленный как часть программы по исследованию данных udacity нанодипломная программа. Набор данных содержит 8351 изображение. Каждое изображение помечено и отображает 1 из 133 пород собак.

Помеченные изображения собак изображают собак на разном фоне и под разными углами.

Изображения представлены в разных размерах и форматах и ​​выбраны случайным образом. Средняя ширина составляет 571 пиксель, а средняя высота — 532 пикс. Есть некоторые выбросы с высотой или шириной более 3500 пикселей.

Количество изображений для каждой породы собак показывает следующее общее распределение.

В среднем для каждой породы собак доступно 60,6 изображений со стандартным отклонением 37,5. Хотя мы можем наблюдать небольшой перекос данных вправо, в целом мы можем предположить довольно сбалансированный набор данных.

Показатели

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

Я использую 20 эпох для обеих моделей, чтобы получить сопоставимые результаты.

Используемые библиотеки глубокого обучения

Благодаря интуитивно понятному высокоуровневому синтаксису для построения CNN я использую основанную на Python библиотеку глубокого обучения Keras, тогда как TensorFlow служит бэкендом. Keras обеспечивает прямой доступ к предварительно обученным CNN. Это необходимое условие для использования подхода, основанного на трансфертном обучении, в последней части проекта.

Подготовка данных

Чтобы передать изображения в CNN, я разбиваю набор данных на 3 подмножества: «Обучающий набор (6680 изображений)», «Проверочный набор (835 изображений)», «Тестовый набор (836 изображений)».

Для лучшей сопоставимости обычной практикой является изменение размера всех изображений до квадратного формата одинаковой формы. Кроме того, TensorFlow требует, чтобы изображения передавались в виде 4D-массива (или 4D-тензора).

Чтобы выполнить необходимые преобразования данных, я реализую функцию, которая преобразует заданные изображения в квадрат 244x244 и выводит четырехмерный тензор формы (1 244 244,3). 1 — количество образцов (или количество изображений), а 3 — количество цветовых каналов на изображение (красный, зеленый, синий).

from keras.preprocessing import image                  
from tqdm import tqdm

def path_to_tensor(img_path):
    # loads RGB image as PIL.Image.Image type
    img = image.load_img(img_path, target_size=(224, 224))
    # convert PIL.Image.Image type to 3D tensor with shape (224, 224, 3)
    x = image.img_to_array(img)
    # convert 3D tensor to 4D tensor with shape (1, 224, 224, 3) and return 4D tensor
    return np.expand_dims(x, axis=0)

def paths_to_tensor(img_paths):
    list_of_tensors = [path_to_tensor(img_path) for img_path in tqdm(img_paths)]
    return np.vstack(list_of_tensors)

Теперь, когда функции для предоставления изображений в требуемом формате подготовлены, я загружаю обучающие, проверочные и тестовые наборы. Перед передачей изображений функциям необходимо нормализовать все значения пикселей. Все значения должны быть представлены в одном масштабе в диапазоне от 0 до 1. Изменение масштаба может быть достигнуто путем деления всех значений пикселей на 255.

ImageFile.LOAD_TRUNCATED_IMAGES = True                 

# pre-process the data for Keras
train_tensors = paths_to_tensor(train_files).astype('float32')/255
valid_tensors = paths_to_tensor(valid_files).astype('float32')/255
test_tensors = paths_to_tensor(test_files).astype('float32')/255
  1. Создание CNN с нуля

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

Архитектура CNN

Я использую макет Keras Sequential для настройки CNN. Модель настроена со следующими свойствами:

  • Сверточные слои и слои MaxPooling связаны каскадом.
  • Все слои MaxPooling используют relu в качестве функции активации, что означает, что положительные значения остаются неизменными, тогда как отрицательные значения устанавливаются на 0.
  • Вывод последнего слоя MaxPooling выравнивается до вектора
  • Затем векторвводится в полносвязный плотный слой.
  • Плотный слой соединяется с другим конечным плотным слоем с помощью функции активации softmax со 133 выходными узлами, по 1 узлу на потенциальную породу собак.
from keras.layers import Conv2D, MaxPooling2D, GlobalAveragePooling2D
from keras.layers import Dropout, Flatten, Dense
from keras.models import Sequential

model = Sequential()

model.add(Conv2D(filters=16, kernel_size=2, padding='same',activation='relu',input_shape=(224,224,3)))
model.add(MaxPooling2D(pool_size=2))
model.add(Conv2D(filters=16, kernel_size=2, padding='same',activation='relu'))
model.add(MaxPooling2D(pool_size=2))
model.add(Flatten())
model.add(Dense(500,activation='relu'))
model.add(Dense(133,activation='softmax'))

Ниже я показываю сводку модели, напечатанную с помощью model.summary().

Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_1 (Conv2D)            (None, 224, 224, 16)      208       
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 112, 112, 16)      0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 112, 112, 16)      1040      
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 56, 56, 16)        0         
_________________________________________________________________
flatten_2 (Flatten)          (None, 50176)             0         
_________________________________________________________________
dense_1 (Dense)              (None, 500)               25088500  
_________________________________________________________________
dense_2 (Dense)              (None, 133)               66633     
=================================================================
Total params: 25,156,381
Trainable params: 25,156,381
Non-trainable params: 0
_________________________________________________________________

После компиляции модели с использованием rmspropв качествеоптимизатораи категориальной кросс-энтропиив качествефункции потерь

model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy'])

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

from keras.callbacks import ModelCheckpoint  

epochs = 5

checkpointer = ModelCheckpoint(filepath='saved_models/weights.best.from_scratch.hdf5', verbose=1, save_best_only=True)

model.fit(train_tensors, train_targets, 
          validation_data=(valid_tensors, valid_targets),
          epochs=epochs, batch_size=20, callbacks=[checkpointer], verbose=1)

При загрузке модели с лучшими потерями при проверке достигается точность теста прибл. 7,18%

model.load_weights('saved_models/weights.best.from_scratch.hdf5')
# get index of predicted dog breed for each image in test set
dog_breed_predictions = [np.argmax(model.predict(np.expand_dims(tensor, axis=0))) for tensor in test_tensors]

# report test accuracy
test_accuracy = 100*np.sum(np.array(dog_breed_predictions)==np.argmax(test_targets, axis=1))/len(dog_breed_predictions)
print('Test accuracy: %.4f%%' % test_accuracy)

7,18% после 20 эпох уже лучше, чем общая случайная вероятность для 133 потенциальных пород собак. Но кажется, что для достижения хорошей производительности подход к построению CNN с нуля не очень эффективен.

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

CNN, вероятно, потребует множества связанных слоев свертки и объединения, слоев исключения и т. Д., Что приведет к довольно большому времени вычислений для достижения достаточно хороших результатов. Беглый взгляд на отмеченные наградами архитектуры CNN показывает сложность и требуемые ресурсы.

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

2. Использование подхода, основанного на трансферном обучении

К счастью, в Keras есть возможность использовать предварительно обученные CNN. Доступно несколько моделей с предварительно обученными весами. Я использую ResNet-50, который был обучен на imagenet, крупномасштабной базе данных с помеченными изображениями. Для более подробного ознакомления с ResNet, который выиграл несколько конкурсов по классификации изображений, также обратитесь к следующему сообщению в блоге.

Я буду использовать ResNet-50 в основном как детектор формы, шаблона, а также функции для изображений. Таким образом, сама модель остается неизменной благодаря своим предварительно обученным весам. Изменяются и обучаются только выходные слои. Я добавляю 2 дополнительных слоя в конец модели:

  1. Слой объединения GlobalAverage для уменьшения размерности данных.
  2. плотный слой, активированный softmax, с 133 выходными узлами (по 1 на породу собак).
from keras.layers import Dense, Flatten
from keras.models import Sequential

model = Sequential()
model.add(GlobalAveragePooling2D(input_shape=valid_Resnet50.shape[1:]))
model.add(Dense(133, activation='softmax'))

Архитектура, возвращаемая функцией model.summary(), показана ниже:

Layer (type)                 Output Shape              Param #   
=================================================================
global_average_pooling2d_2 ( (None, 2048)              0         
_________________________________________________________________
dense_4 (Dense)              (None, 133)               272517    
=================================================================
Total params: 272,517
Trainable params: 272,517
Non-trainable params: 0
_________________________________________________________________

Чтобы скомпилировать и обучить модель, я использую настройку, аналогичную приведенной выше, при попытке построить CNN с нуля. Я снова тренируюсь с тем же количеством 20 эпох на том же графическом процессоре.

model.compile(loss='categorical_crossentropy', optimizer='rmsprop', metrics=['accuracy'])
from keras.callbacks import ModelCheckpoint

checkpointer = ModelCheckpoint(filepath="resnet50_transfer_best.hdf5", verbose=1,
                              save_best_only=True)
model.fit(train_Resnet50, train_targets,epochs=50, validation_data=(valid_Resnet50, valid_targets),
         callbacks=[checkpointer], verbose=1,shuffle=True)

Затем я оцениваю лучшую обученную модель на тестовых изображениях.

model.load_weights("resnet50_transfer_best.hdf5")
score = model.evaluate(test_Resnet50,test_targets, verbose=0)

Производительность весьма примечательна. Прибл. 0,814 тестовых изображений, отображающих породы собак, были правильно классифицированы лучшей моделью. И это уже без добавления сложной архитектуры модели и в течение ограниченного времени обработки ок. 5 минут на графическом процессоре. При трансферном обучении модель получает толчок с точностью прибл. 0,77 из коробки.

В отличие от описанного выше подхода построения модели с нуля, мы видим улучшение набора валидаций в заданном диапазоне 20 эпох.

Я тестирую вывод модели с учетом следующего изображения. Порода собак правильно определена как лабрадор-ретривер.

Очки улучшения

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

  1. Дальнейшее уточнение архитектуры модели, например. с использованием подхода, основанного на поиске по сетке
  2. Использование увеличения данных, например. обрезка, отражение, добавление шума к тренировочным изображениям
  3. Сбор дополнительных обучающих данных

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

Обучение

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

Поэтому трансферное обучение представляется разумным и действенным подходом. Используя возможности предварительно обученных CNN, можно сразу же приступить к созданию классификаторов изображений, тогда как модель можно дополнительно адаптировать к конкретному варианту использования, например, к обнаружению пород собак, очень экономичным способом.