Обзор

В этой статье представлен обзор проекта классификатора пород собак, созданного с помощью глубокого обучения. Это было частью моей степени Udacity Data Science Nano, и в ней представлены все этапы, связанные с предварительной обработкой данных, обучением модели, тестированием и оценкой.

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

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

Этапы:

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

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

Загрузите соответствующие файлы в свой репозиторий.

  1. набор данных собак: разархивируйте папку и поместите ее в репозиторий по адресу path/to/dog-project/dogImages.
  2. человеческий набор данных. Разархивируйте папку и поместите ее в репозиторий по адресу path/to/dog-project/lfw.
  3. Функции узкого места VGG-16 для набора данных о собаках. Поместите его в репозиторий по адресу path/to/dog-project/bottleneck_features.

Импортируйте соответствующие библиотеки в блокноты Jupyter. Важными из них, которые мы будем использовать, являются:

  1. keras: для построения, обучения и оценки нашей модели.
  2. OpenCV: для задач предварительной обработки данных и идентификации человеческих лиц на изображениях.
  3. sklearn: который будет использоваться на промежуточных этапах обработки и оценки.

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

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

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

def load_dataset(path):
    data = load_files(path)
    dog_files = np.array(data['filenames'])
    dog_targets = np_utils.to_categorical(np.array(data['target']), 133)
    return dog_files, dog_targets

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

train_files, train_targets = load_dataset('../../../data/dog_images/train')
valid_files, valid_targets = load_dataset('../../../data/dog_images/valid')
test_files, test_targets = load_dataset('../../../data/dog_images/test')

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

dog_names = [item[20:-1] for item in sorted(glob("../../../data/dog_images/train/*/"))]

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

human_files = np.array(glob("../../../data/lfw/*/*"))
random.shuffle(human_files)

Давайте также изучим данные, пока мы этим занимаемся.

plt.imshow(cv2.imread(train_files[1]));
plt.show()
plt.imshow(cv2.imread(train_files[24]));
plt.show()
plt.imshow(cv2.imread(train_files[863]));
plt.show()
plt.imshow(cv2.imread(train_files[3573]));
plt.show()
plt.imshow(cv2.imread(train_files[463]));
plt.show()
plt.imshow(cv2.imread(train_files[4233]));
plt.show()

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

Размеры кажутся немного не те. Давайте визуализируем варианты:

# loop through each photo and save height and width
train_height = []
train_width = []
for idx in range(len(train_files)):
    train_image = cv2.imread(train_files[idx])
    train_height.append(train_image.shape[0])
    train_width.append(train_image.shape[1])
# plot scatter graph
plt.scatter(train_height,train_width, s=1)
plt.ylabel('Height of training data');
plt.xlabel('Width pf training data');
plt.show();

Некоторые фотографии могут содержать:

1. Более одного объекта. Возможно, 2 собаки или 2 человека, или собака и человек, или какие-то неодушевленные предметы, как показано выше
2. Рядом с ними могут быть отвлекающие объекты, например новогодняя елка.
3. У них может быть нестандартный вид. откуда они показаны. Их лицо может быть наклонено в сторону и т. д.

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

Теперь давайте посмотрим на категории каждой собаки и их размеры.

# Use Groupby to find count of each category
dog_files_cat = list(train_files)
dog_files_cat.sort()
category_count = []
for dog_fil in dog_files_cat:
 category_count.append(dog_fil.split(‘/’)[-2])
category_count = pd.Series(category_count)
category_count.value_counts().plot.bar(figsize=(19,6));

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

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

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

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

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

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

detected_true_human = 0
detected_true_dog = 0
for human in human_files_short:
    detect = face_detector(human)
    if(detect):
        detected_true_human = detected_true_human + 1
for dog in dog_files_short:
    detect_dog = face_detector(dog)
    if(detect_dog):
        detected_true_dog = detected_true_dog + 1

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

print('human faces detected accurately', (detected_true_human / len(human_files_short))*100, '%')
print('dog faces detected accurately', (detected_true_dog / len(dog_files_short))*100, '%')

Его точность составляет 100% для идентификации людей, но он выявляет ложные срабатывания, идентифицируя 11% набора данных о собаках как людей.

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

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

from keras.applications.resnet50 import ResNet50

# define ResNet50 model
ResNet50_model = ResNet50(weights='imagenet')

Наша первая строка кода загружает модель ResNet-50 вместе с весами, которые были обучены в ImageNet. При наличии изображения эта предварительно обученная модель ResNet-50 возвращает прогноз (полученный из доступных категорий в ImageNet) для объекта, содержащегося в изображении.

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

Большая часть предварительной обработки данных требует от нас преобразования входного изображения в четырехмерный массив (или тензор) с формой (nb_samples, rows, columns, channels). nb_samples относится к общему количеству изображений, где строки, столбцы и каналы относятся к ширине, высоте и каналам каждого изображения.

Приведенная ниже функция path_to_tensor принимает в качестве входных данных путь к файлу со строковым значением к цветному изображению и возвращает 4D-тензор, подходящий для предоставления в Keras CNN, где функция paths_to_tensor принимает пустой массив путей к изображению со строковым значением как input и возвращает четырехмерный тензор формы (nb_samples, rows, columns, channels).

def path_to_tensor(img_path):
    # loads RGB image as PIL.Image.Image type
    img = image.load_img(img_path, target_size=(224, 224))
    x = image.img_to_array(img)
    
    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)

Обе функции:

  1. Загрузите изображения и измените их размер на квадратные изображения размером 224 x 224 пикселя.
  2. Затем изображения преобразуются в массив, если предоставлено более одного из них, размер которого затем изменяется до четырехмерного тензора.

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

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))

Поскольку категории собак соответствуют ключам словаря от 151 до 268, следующая функция возвращает значение true, если значение, предсказанное моделью, находится в этом диапазоне (включительно).

### returns "True" if a dog is detected in the image stored at img_path
def dog_detector(img_path):
    prediction = ResNet50_predict_labels(img_path)
    return ((prediction <= 268) & (prediction >= 151))

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

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

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

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

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='softmax'))
_________________________________________________________________
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, 32)      2080      
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 56, 56, 32)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 56, 56, 64)        8256      
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 28, 28, 64)        0         
_________________________________________________________________
global_average_pooling2d_1 ( (None, 64)                0         
_________________________________________________________________
dense_1 (Dense)              (None, 133)               8645      
=================================================================
Total params: 19,189
Trainable params: 19,189
Non-trainable params: 0
_________________________________________________________________

Результатом является функция soft-max, которая дает прогностические значения от 0 до 1 вероятности принадлежности собаки к каждой категории. Затем выбирается самый высокий прогностический рейтинг.

Эта довольно простая модель была обучена с использованием 100 эпох и дала точность 11,24%. Ничего необычного, учитывая, насколько мелкой была нейронная сеть.

Шаг 4. Используйте CNN для классификации пород собак

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

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

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

Количество эпох, использованных для обучения, составляло 20, а точность теста — 46,6507%. Это значительный скачок по сравнению с нашими предыдущими 11%, и есть большая вероятность, что если бы мы увеличили количество эпох, точность была бы еще лучше.

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

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

На этом этапе мы построим нашу модель на архитектуре Resnet50, используем трансферное обучение для повышения нашей точности и используем средство извлечения переменных узкого места, чтобы удалить его (модели) последние слои и добавить наш собственный плотный слой с функцией softmax, которая у нас есть. уже обучены до достижения максимальной точности.

Я выбрал Resnet50, потому что он уже был тщательно обучен данным ImageNET, а его количество слоев, например 50, кажется достаточно глубоким, чтобы изучить различные функции, необходимые для правильной идентификации и классификации собак. Они также, в отличие от Resnet101 или других CNN, не были очень глубокими, поэтому для обучения требовалось больше времени.

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

Udacity уже предоставил нам узкие места Resnet50, поэтому мы продолжим и извлечем их.

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

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

Resnet50_model = Sequential()
Resnet50_model.add(GlobalAveragePooling2D(input_shape=train_Resnet50.shape[1:]))
Resnet50_model.add(Dense(133, activation='softmax'))
  
_________________________________________________________________ Layer (type)                 Output Shape              Param #    ================================================================= global_average_pooling2d_4 ( (None, 2048)              0          _________________________________________________________________ dense_4 (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'])

..и обучение этому.

Resnet50_model.fit(train_Resnet50, train_targets, 
          validation_data=(valid_Resnet50, valid_targets),
          epochs=20, batch_size=20, callbacks=[checkpointer1], verbose=1)

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

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

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

Шаг 5а. Показатели

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

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

Resnet50_model_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_model_predictions)==np.argmax(test_targets, axis=1))/len(Resnet50_model_predictions)
print('Test accuracy: %.4f%%' % test_accuracy)

И альт! Точность теста составляет 81,2201%.

Это неплохо, учитывая, что исходная точность модели, которую мы создали сами, составляла около 11 %.

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

График слева показывает, что точность нашего обучения увеличивалась по мере увеличения количества эпох, при этом точность проверки оставалась неизменной, колеблясь от 80% до 82,5%.

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

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

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)]

Вышеупомянутая функция будет:

  1. Извлеките узкие места Resnet50.
  2. Предоставьте функции узкого места в качестве входных данных для модели, чтобы вернуть предсказанный вектор. Обратите внимание, что argmax этого вектора прогнозирования дает индекс прогнозируемой породы собак.
  3. Используйте массив dog_names, определенный на шаге 0 этой записной книжки, чтобы вернуть соответствующую породу.

Шаг 6. Напишите алгоритм

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

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

Следующий:

  • если на изображении обнаружена собака, возвращается предсказанная порода.
  • если на изображении обнаружен человек, возвращается похожая порода собаки.
  • если ни один не обнаружен на изображении, он предоставляет вывод, указывающий на ошибку.
def predict_breed(img_path):
    
    is_human = face_detector(img_path)
    is_dog = dog_detector(img_path)
    
    if(is_dog):
        print('Identified photo of a dog, predicting its breed..')
    elif(is_human):
        print('It seems that the photo is of a human!')
        print('Lets find out which dog breed it closely resembles to..')
    else:
        error_str = 'Error: This photo is neither human nor dog. We are sorry, please insert a correct photo to classify.'
        print(error_str)
        return error_str
    
    find_breed = Resnet50_predict_breed(img_path)
    
    # In case error found when cleaning string
    try:
        breed_type = find_breed.split('/')[-1].split('.')[-1]
        print('We predicted the breed to be', breed_type, '.')
    except:
        breed_type = find_breed
        print('We predicted the breed to be', breed_type, '.')
    
    return breed_type

Шаг 7. Алгоритм тестирования

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

Я использовал следующие изображения:

Первый — это Foo Foo Cuddly Poops — мое любимое животное из серии Аватар: Маг воздуха.

Lets see what the classifier thinks it to be..
..
...
This photo is neither human nor dog. We are sorry, please insert a correct photo to classify.
----------------------------------------------

Да, классификатор смог идентифицировать его как нечеловека! Довольно аккуратно.

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

Он определил каждого из них правильно! А именно (сверху по часовой стрелке), как Golden_retriever, Alaskan_malamute American_staffordshire_terrier, Bearded_collie!

Затем я ввел свое изображение вместе с Бреттом Дайером (мой любимый актер) и Майклом Фелпсом.

Он идентифицировал нас всех как людей! и предсказал Бретта и меня как английского спрингер-спаниеля, а Майкла Фелпса как шелковистого терьера. Если вы погуглите этих собак, вы найдете некоторое сходство между нами!

Выводы:

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

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

Несмотря на полученную хорошую точность, улучшения, которые я, вероятно, внес бы, включают:

1) Обучение на архитектуре Resnet101 или более глубоких архитектурах нейронных сетей, это дало бы гораздо лучшую точность.

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

3) Увеличьте размер набора данных до гораздо большего набора данных, чтобы он мог изучить больше функций, и увеличьте количество эпох, чтобы модель правильно изучила больше функций.

4) Алгоритм должен предоставлять три (или n) лучших категорий с их прогностическими рейтингами, а не только одну. Это позволит пользователю решить, к какой категории, вероятно, принадлежит собака, если прогностические значения очень близки друг к другу.

5) Реализуйте его как рабочее приложение для людей!