Обзор проекта

В этом проекте я обучил сверточные нейронные сети (CNN) классифицировать породу собак на изображении! CNN — это класс глубоких нейронных сетей, которые невероятно эффективны в анализе и изучении закономерностей в визуальных данных. Однако для обучения CNN могут потребоваться большие наборы данных и много часов вычислений. Чтобы достичь приемлемого уровня точности классификации при ограниченном количестве обучающих данных, может быть невероятно полезно применять трансферное обучение во время обучения.

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

Цель проекта

Цель состоит в том, чтобы создать алгоритм классификатора пород собак, который правильно классифицирует не менее 60% собак в тестовом наборе данных.

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

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

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

Стратегия достижения цели

Стратегия, заложенная для достижения цели, выглядит следующим образом:

  • Шаг 0: Импорт наборов данных
  • Шаг 1: Обнаружение людей
  • Шаг 2: Обнаружение собак
  • Шаг 3: Создайте CNN для классификации пород собак (с нуля)
  • Шаг 4: Создайте CNN для классификации пород собак (с использованием трансферного обучения)
  • Шаг 5: Напишите алгоритм
  • Шаг 6: Тестирование алгоритма

Метрики

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

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

Исследовательский анализ данных

Чтобы обучить и протестировать наш классификатор пород собак, Udacity любезно предоставил мне множество помеченных изображений собак. Изучая данные, мы видим, что было 8300 помеченных изображений собак, где метка является одной из 133 пород собак. Чтобы обучить классификатор болотных пород, я разделил данные на набор данных для обучения, проверки и тестирования.

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

Визуализация данных

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

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

Выполнение

Шаг 0. Импорт наборов данных и необходимых библиотек

Следующие 2 набора данных предоставлены Udacity.

  1. изображения собак для обучения модели
  2. человеческие лица для детектора

И библиотеки, которые использовались в блокноте, следующие:

from sklearn.datasets import load_files 
from keras.utils import np_utils
import numpy as np
from glob import glob
from keras.applications.resnet50 import ResNet50
from keras.preprocessing import image
from tqdm import tqdm
from keras.applications.resnet50 import preprocess_input, decode_predictions
from keras.layers import Conv2D, MaxPooling2D, GlobalAveragePooling2D
from PIL import ImageFile
from keras.layers import Dropout, Flatten, Dense
from keras.models import Sequential
from keras.callbacks import ModelCheckpoint
from extract_bottleneck_features import *
import random
import cv2                
import matplotlib.pyplot as plt

Шаг 1. Обнаружение людей на изображении

Реализация OpenCV каскадных классификаторов на основе признаков Хаара используется для обнаружения человеческих лиц в этом проекте. OpenCV предоставляет множество предварительно обученных детекторов лиц, хранящихся в виде XML-файлов на GitHub. Мы скачали один из этих детекторов и сохранили его в каталоге haarcascades.

Ниже приведена кодовая ячейка, которая демонстрирует, как использовать этот детектор для поиска человеческих лиц на образце изображения.

# extract pre-trained face detector
face_cascade = cv2.CascadeClassifier('haarcascades/haarcascade_frontalface_alt.xml')
def face_detector(img_path):
    img = cv2.imread(img_path)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    faces = face_cascade.detectMultiScale(gray)
    return len(faces) > 0
human_files_short = human_files[:100]
dog_files_short = train_files[:100]
# Do NOT modify the code above this line.
face_dec = np.vectorize(face_detector)
human_face_detected =face_dec(human_files_short)
dog_detected = face_dec(dog_files_short)
print(" Human Face Detected with {:.1f}%accuracy".format(sum(human_face_detected)))
print("Dog detected with  {:.1f}% error".format(sum(dog_detected)))
  • Количество правильно идентифицированных людей: 100
  • Количество собак, признанных людьми: 11

Результаты не идеальны, но приемлемы.

Шаг 2. Обнаружение собак на изображении

В этом разделе мы используем предварительно обученную модель ResNet-50 для обнаружения собак на изображениях.

from keras.applications.resnet50 import ResNet50# define ResNet50 model
ResNet50_model = ResNet50(weights='imagenet')

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

Предварительная обработка

Прежде чем мы сможем передать наши данные в модель Resnet 50 для прогнозирования, нам нужно выполнить некоторую предварительную обработку данных изображения. Мы делаем это, потому что CNN Keras, использующие TensorFlow в качестве серверной части, требуют, чтобы вход был массивом 4D. Входной массив будет иметь вид:

(количество выборок, строк, столбцов, каналов)

Для достижения следующих функций были определены:

Path_to_tensor: эта функция принимает строковый путь файла к цветному изображению в качестве входных данных и возвращает четырехмерный тензор, подходящий для передачи в Keras CNN. Сначала функция загружает изображение и изменяет его размер до квадратного изображения размером 224×224 пикселя. Затем изображение преобразуется в массив, размер которого затем изменяется до четырехмерного тензора. В данном случае, поскольку мы работаем с цветными изображениями, каждое изображение имеет три канала.

Paths_to_tensor: эта функция принимает массив NumPy строковых путей изображения в качестве входных данных и возвращает четырехмерный тензор с требуемой формой. Последний шаг, который нужно сделать перед тем, как наши данные будут готовы для модели, включает в себя дополнительный шаг нормализации, заключающийся в получении средних значений пикселей всех пикселей во всех изображениях в ImageNet, выраженных в RGB как [103,939,116,779,123,68], и вычитании их из каждый пиксель в каждом изображении. Это реализовано в импортированной функции preprocess_input, которую можно найти здесь.

Код для обеих функций показан ниже.

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)

Составление прогнозов

Функция, показанная ниже, используется для прогнозирования. Она использует функцию прогнозирования для получения массива для 1000 классов ImageNet. Затем мы используем функцию argmax NumPy, чтобы изолировать класс с наибольшей вероятностью.

В выходном словаре классов ImageNet (найден здесь) записи с 151 по 269 представляют собак, поэтому мы пишем вторую функцию, которая использует этот словарь, чтобы определить, попадает ли какой-либо из идентифицированных объектов на изображении в этот диапазон словаря.

from keras.applications.resnet50 import preprocess_input, decode_predictions
def ResNet50_predict_labels(img_path):
    # returns prediction vector for image located at img_path
    img = preprocess_input(path_to_tensor(img_path))
    return np.argmax(ResNet50_model.predict(img))
def dog_detector(img_path):
    prediction = ResNet50_predict_labels(img_path)
    return ((prediction <= 268) & (prediction >= 151))

Приведенный ниже код использовался для проверки точности детектора собак, и в результате лица были обнаружены на 0 % изображений людей, а собаки — на 100 % изображений собак.

human_files_short = human_files[:100]
dog_files_short = train_files[:100]

dog_dec = np.vectorize(dog_detector)

human_face_detected =dog_dec(human_files_short)
dog_detected = dog_dec(dog_files_short)
print("{:.1f}% of Humans are detected as dogs".format(sum(human_face_detected)))
print("{:.1f}% of Dogs detected in the first 100 files".format(sum(dog_detected)))

Шаг 3. Создайте CNN для классификации пород собак (с нуля)

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

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

from PIL import ImageFile 
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

Мы создали CNN для классификации пород собак, используя приведенный ниже код.

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=32, kernel_size=2, padding='same', activation='relu'))
model.add(MaxPooling2D(pool_size=2))
model.add(Conv2D(filters=64, kernel_size=2, padding='same', activation='relu'))
model.add(MaxPooling2D(pool_size=2))
model.add(GlobalAveragePooling2D())
model.add(Dense(133, activation='relu'))
model.summary()

Контур:

  1. Мы создали 3 сверточных слоя с 3 максимальными объединяющими слоями между ними, чтобы изучить иерархию функций высокого уровня. Слой максимального пула добавлен для уменьшения размерности.
  2. Мы использовали 3 фильтра ’16, 32, 64’ в 3 сверточных слоях.
  3. Выпрямленная линейная функция активации или сокращенно ReLU — это кусочно-линейная функция, которая будет выводить входные данные напрямую, если они положительны, в противном случае она будет выводить ноль. Итак, мы использовали функцию активации ReLu для всех слоев.
  4. GlobalAveragePooling2D делает что-то другое. Он применяет усреднение к пространственным измерениям до тех пор, пока каждое пространственное измерение не станет единым, а остальные измерения остаются неизменными.
  5. Количество узлов в последнем полносвязном слое было установлено равным 133 вместе с функцией активации ReLu для получения вероятностей прогноза.

Полученный результат:

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_25 (Conv2D)           (None, 224, 224, 16)      208       
_________________________________________________________________
max_pooling2d_26 (MaxPooling (None, 112, 112, 16)      0         
_________________________________________________________________
conv2d_26 (Conv2D)           (None, 112, 112, 32)      2080      
_________________________________________________________________
max_pooling2d_27 (MaxPooling (None, 56, 56, 32)        0         
_________________________________________________________________
conv2d_27 (Conv2D)           (None, 56, 56, 64)        8256      
_________________________________________________________________
max_pooling2d_28 (MaxPooling (None, 28, 28, 64)        0         
_________________________________________________________________
global_average_pooling2d_2 ( (None, 64)                0         
_________________________________________________________________
dense_10 (Dense)             (None, 133)               8645      
=================================================================
Total params: 19,189
Trainable params: 19,189
Non-trainable params: 0
_________________________________________________________________

После компиляции приведенной выше модели и ее подгонки с использованием данных поезда и проверки я затем проверил модель на наших тестовых изображениях и увидел, что модель правильно предсказала породу собаки для 1,0766% тестовых изображений.

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

Шаг 4. Используйте CNN для классификации пород собак (с помощью трансферного обучения)

Чтобы сократить время обучения без ущерба для точности, мы можем обучить CNN с помощью трансферного обучения. На следующем этапе мы использовали трансферное обучение для обучения на собственном CNN. Чтобы попытаться повысить точность, я сначала попытался использовать модель VGG-16 в качестве отправной точки. Обучив эту модель на наших данных обучения и проверки, модель смогла правильно предсказать породу собаки на 42,7033% тестовых изображений. Ниже приведено краткое описание модели:

VGG16_model = Sequential()
VGG16_model.add(GlobalAveragePooling2D(input_shape=train_VGG16.shape[1:]))
VGG16_model.add(Dense(133, activation=’softmax’))
VGG16_model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
global_average_pooling2d_3 ( (None, 512)               0         
_________________________________________________________________
dense_11 (Dense)             (None, 133)               68229     
=================================================================
Total params: 68,229
Trainable params: 68,229
Non-trainable params: 0
_________________________________________________________________

Повышение точности — это огромное улучшение, но оно все еще довольно далеко от того, чего мы хотим добиться. Затем я решил построить свою CNN, используя Resnet 50 CNN, обученный на базе данных ImageNet. Существуют узкие места для модели Resnet 50, и мне нужно было только загрузить файл для нашей Resnet 50 CNN, прежде чем импортировать его с кодом, показанным ниже.

Извлечение узких мест, соответствующих обучающим, тестовым и проверочным наборам, запустив следующее:

bottleneck_features = np.load(‘bottleneck_features/DogResnet50Data.npz’)
train_Resnet50 = bottleneck_features[‘train’]
valid_Resnet50 = bottleneck_features[‘valid’]
test_Resnet50 = bottleneck_features[‘test’]

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

После применения модели Resnet50 она дала точность более 75%. Я также добавил полностью подключенный слой с 500 узлами и функцией активации ReLu для обнаружения большего количества шаблонов и Dropout, чтобы избежать переобучения.

from keras.layers import Dropout
Resnet50_model = Sequential()
Resnet50_model.add(GlobalAveragePooling2D(input_shape=train_Resnet50.shape[1:]))
Resnet50_model.add(Dense(133, activation='softmax'))
Resnet50_model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
global_average_pooling2d_6 ( (None, 2048)              0         
_________________________________________________________________
dense_16 (Dense)             (None, 133)               272517    
=================================================================
Total params: 272,517
Trainable params: 272,517
Non-trainable params: 0
_________________________________________________________________

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

Resnet50_model.compile(loss=’categorical_crossentropy’, optimizer=’rmsprop’, metrics=[‘accuracy’])

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

from keras.callbacks import ModelCheckpoint
checkpointer = ModelCheckpoint(filepath=’saved_models/weights.best.Resnet50.hdf5', 
 verbose=1, save_best_only=True)
Resnet50_model.fit(train_Resnet50, train_targets, 
 validation_data=(valid_Resnet50, valid_targets),
 epochs=20, batch_size=20, callbacks=[checkpointer], verbose=1)

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

Resnet50_model.load_weights(‘saved_models/weights.best.Resnet50.hdf5’)

Мы проверили точность модели, реализованной с помощью приведенного ниже кода, и получили точность 80,7416%.

# get index of predicted dog breed for each image in test set
Resnet50_predictions = [np.argmax(Resnet50_model.predict(np.expand_dims(feature, axis=0))) for feature in test_Resnet50]
# report test accuracy
test_accuracy = 100*np.sum(np.array(Resnet50_predictions)==np.argmax(test_targets, axis=1))/len(Resnet50_predictions)
print(‘Test accuracy: %.4f%%’ % test_accuracy)

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

from extract_bottleneck_features import *
def Resnet50_predict_breed(img_path):
 # extract bottleneck features
 bottleneck_feature = extract_Resnet50(path_to_tensor(img_path))
 # obtain predicted vector
 predicted_vector = Resnet50_model.predict(bottleneck_feature)
 # return dog breed that is predicted by the model
 return dog_names[np.argmax(predicted_vector)]

Шаг 5. Написание алгоритма

Здесь мы создаем наш алгоритм для анализа любого изображения. Алгоритм принимает путь к файлу и:

  • если на изображении обнаружена собака, вернуть предсказанную породу.
  • если на изображении обнаружен человек, вернуть похожую породу собаки.
  • если ни один не обнаружен на изображении, предоставьте вывод, указывающий на ошибку.

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

def display_img(img_path):
 img = cv2.imread(img_path)
 cv_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
 imgplot = plt.imshow(cv_rgb)
 return imgplot
def predict_breed(img_path):
 display_img(img_path)
 if dog_detector(img_path):
 return print(“It’s a Dog and the breed is {}”.format(Resnet50_predict_breed(img_path)))
 
 if face_detector(img_path):
 
 return print(“if you were a dog you’ll be a {}!!”.format(Resnet50_predict_breed(img_path)))
 
 else:
 return print(“Definitely not a Human or a Dog”)

Шаг 6. Тестирование алгоритма

Я протестировал алгоритм на случайных картинках, взятых из интернета, и получил следующие результаты:

Результаты

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

  • CNN с нуля: 1,0766%
  • CNN из VGG16: 42,7033%
  • CNN от Resnet50: 80,7416%

Оценка и проверка модели

CNN, обученная с помощью Resnet50, достигла приемлемой точности 80,7416% на тестовом наборе. Я считаю, что эта точность приемлема из-за цели приложения. Использование программы должно доставлять удовольствие, поэтому она должна быть достаточно точной, но небольшая неточность может добавить веселья!

Обоснование

Одной из причин различий в точности между моделями является количество параметров, используемых в каждой из них. CNN с нуля использовала 19 189 параметров, модель VGG-16 использовала 68 229 параметров, а модель Resnet50 использовала 272 517 параметров. Большее количество параметров может привести к переоснащению и изучению слишком специфических деталей тренировочного набора. Однако приведенные выше точности были рассчитаны на тестовом наборе с данными, которые не использовались при обучении. Из-за этого мы видим, что переоснащение не является проблемой из-за повышенной точности теста.

Отражение

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

Создание CNN с нуля с использованием Keras может быть простым, но явно неэффективным, поскольку правильно классифицирует только один процент тестовых изображений. К счастью, трансферное обучение пришло на помощь. Поэкспериментировав с двумя разными предварительно обученными моделями в качестве отправной точки, я наткнулся на модель Resnet50, которая помогла мне достичь точности 80,7416% на тестовом наборе, которую я счел приемлемой.

Улучшения

Я хотел бы попробовать следующие эксперименты, чтобы увидеть, могут ли они повысить точность еще выше:

  • Увеличение изображения
  • За счет увеличения глубины нейронной сети

Ссылка на GitHub: https://github.com/lakshman533/Dog-Breed-Identifier