Вы тоже хотите определить породу любой собаки всего за 5 секунд?

Вступление

Вы знаете породу собаки на картинке выше? Если нет, то ничего страшного, потому что я тоже не знаю. Что ж, мы встречаем много разных пород собак, гуляя по улице, и второе, что мы хотим узнать, - это его порода (интересно, что первое ?! Его имя!). Зачем тогда тратить время, и давайте воспользуемся одним из самых популярных методов машинного обучения, а именно сверточной нейронной сетью (CNN), чтобы определить породу собаки. В этой статье мы рассмотрим полный алгоритм определения породы собаки с использованием данного набора данных. Мы также увидим, как использовать предварительно обученную модель ResNet50 для определения породы собаки.

Шаг за шагом!

  • Импортировать набор данных
  • Обнаружение людей с помощью CV2
  • Обнаружение собак
  • Создайте CNN для классификации пород собак (с нуля)
  • Используйте CNN для классификации пород собак (с использованием трансферного обучения)
  • Создайте CNN для классификации пород собак (с использованием трансферного обучения)
  • Напишите алгоритм
  • Протестируйте алгоритм

ШАГ-1 Импортируйте набор данных

Наш набор данных содержит всего 8351 изображение собак 133 различных категорий пород.

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

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

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

ШАГ-2 Обнаружение людей

Чтобы добавить в нашу модель функцию, при которой любой человек, замаскированный в костюм собаки, не обманывает результаты нашей классификации, мы будем обнаруживать людей с помощью реализации в OpenCV каскадных классификаторов Хаара на основе функций. Чтобы реализовать это, следующий код:

import cv2
# extract pre-trained face detector
# cv2.CascadeClassifier is the model for detecting faces
face_cascade = cv2.CascadeClassifier('haarcascades/haarcascade_frontalface_alt.xml')
# load color (BGR) image
# cv2.imread(image_file_name) reads an imageimg = cv2.imread(human_files[3])
# convert BGR image to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# find faces in image
faces = face_cascade.detectMultiScale(gray)
# print number of faces detected in the image
print('Number of faces detected:', len(faces))

Вы можете ожидать чего-то вроде этого:

Результаты испытаний показали, что 11% собак имеют человеческие лица.

ШАГ-3 Обнаружение собак

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

# This line will download the ResNet-50 model, along with weights that have been trained on ImageNet
from keras.applications.resnet50 import ResNet50
# define ResNet50 model
ResNet50_model = ResNet50(weights='imagenet')

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

При использовании TensorFlow в качестве бэкэнда для CNN Keras требуется 4-мерный массив (который мы также будем называть 4-мерным тензором) в качестве входных данных с формой:

		(nb_samples,rows,columns,channels)

где nb_samples соответствует общему количеству изображений (или образцов), а rows, columns и channels соответствуют количеству строк, столбцов и каналов для каждого изображения соответственно.

Приведенная ниже функция path_to_tensor принимает в качестве входных данных путь к файлу со строковым значением к цветному изображению и возвращает четырехмерный тензор, подходящий для передачи в CNN Keras. Функция сначала загружает изображение и изменяет его размер до квадратного изображения размером 224 × 224224 × 224 пикселей. Затем изображение преобразуется в массив, размер которого затем изменяется до 4-мерного тензора. В этом случае, поскольку мы работаем с цветными изображениями, каждое изображение имеет три канала. Аналогичным образом, поскольку мы обрабатываем одно изображение (или образец), возвращаемый тензор всегда будет иметь форму

			(1,224,224,3)

Функция paths_to_tensor принимает в качестве входных данных массив множества путей к изображениям со строковыми значениями и возвращает 4D тензор с формой

		    (nb_samples,224,224,3)

Здесь nb_samples - это количество выборок или количество изображений в предоставленном массиве путей к изображениям. Лучше всего думать о nb_samples как о количестве трехмерных тензоров (где каждый трехмерный тензор соответствует другому изображению) в вашем наборе данных!

Следующий код выполняет предварительную обработку данных:

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)

Прогнозирование с помощью ResNet50:

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

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

ШАГ-3 Создание собственной CNN с нуля

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

model = Sequential()
model.add(Conv2D(filters = 6, kernel_size=5, strides=1, padding='same', activation='relu', input_shape = (224,224,3)))
model.add(MaxPooling2D(pool_size=2))
model.add(Conv2D(filters=16, kernel_size=5, activation='relu', padding='same', strides=1))
model.add(MaxPooling2D(pool_size=2))
model.add(Dropout(0.2))
model.add(Flatten())
model.add(Dense(200, activation='relu'))
model.add(Dropout(0.4))
model.add(Dense(133,activation='softmax'))
model.summary()

Особенности моей архитектуры:

  • Моя архитектура содержит два сверточных слоя для извлечения функций из изображений.
  • Первый сверточный слой состоит из 6 фильтров размером 5x5 с ReLu в качестве функции активации, поскольку он решает проблему исчезающего градиента, с которой мы сталкиваемся в Sigmoid. Точно так же во втором сверточном слое у нас есть 16 фильтров одинакового размера и одинаковой функции активации.
  • Чтобы уменьшить количество параметров и извлечь только наиболее важные функции, после каждого сверточного слоя добавляются два слоя Max Pooling размером 2x2.
  • Далее добавляется слой Dropout с вероятностью 0,2 для предотвращения переобучения.
  • Ближе к концу у нас есть полностью связанный слой с 200 входными числами узлов и функцией активации ReLu.
  • Еще один слой исключения 0,4 добавлен для ускорения процесса и предотвращения переобучения.
  • Наконец, у нас есть выходной слой с количеством узлов, равным количеству пород собак, которые у нас есть в наборе данных с функцией активации softmax для прогнозирования вероятностей различных пород.

Обучение CNN

Для обучения нашей модели мы возьмем количество эпох = 20 с размером пакета = 20 и сохраним лучшие веса модели с помощью ModelCheckpoint. Вот код:

# Compile the model
model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy'])
epochs = 20
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)
# load the best weights
model.load_weights('saved_models/weights.best.from_scratch.hdf5')
# Test the model
# 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)

Точность теста, которую я получил, составила 5,8612%. Однако вы можете увеличить количество эпох и попробовать настройку гиперпараметров для дальнейшей настройки параметров и повышения точности.

ШАГ-4 Использование трансферного обучения для классификации пород

Трансферное обучение - это метод, который экономит время на создание CNN с нуля, и мы можем просто извлечь узкие места из предварительно обученного классификатора и внести несколько изменений в эту модель, чтобы использовать ее для другого набора данных. Например, мы можем удалить последний плотный слой из этой модели и добавить еще один плотный слой с другим количеством выходов, используя функцию активации softmax. Здесь мы собираемся использовать VGG16, извлекать из него функции и передавать их на уровень глобального среднего пула, чтобы уменьшить количество параметров, и, наконец, добавить полностью связанный уровень со 133 выходными узлами, используя функцию активации softmax.

Особенности узких мест

bottleneck_features = np.load('bottleneck_features/DogVGG16Data.npz')
train_VGG16 = bottleneck_features['train']
valid_VGG16 = bottleneck_features['valid']
test_VGG16 = bottleneck_features['test']

Добавьте слои в конце

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

Точно так же после компиляции модели и тестирования модели на тестовом наборе данных вы должны получить точность около 43%.

ШАГ-5 Создайте свою собственную CNN для классификации пород собак с использованием трансферного обучения

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

Модель Архитектура

ResNet50_model_transfer = Sequential()
ResNet50_model_transfer.add(GlobalAveragePooling2D(input_shape=train_ResNet.shape[1:]))
ResNet50_model_transfer.add(Dense(133, activation='softmax'))
ResNet50_model_transfer.summary()

После запуска с 20 эпохами и размером пакета = 20 вы увидите точность теста 82%.

Вы можете попробовать следующие другие модели:

ШАГ 6 Напишите свой алгоритм

Теперь мы объединим все вышеперечисленные шаги, чтобы преобразовать его в законченный алгоритм, который будет делать следующее:

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

Ниже перечислены функции, выполняющие указанные выше задачи:

from PIL import Image
def dog_classifier(img_path):
    if dog_detector(img_path):
        print('Lemme guess, Hey! You are a Dog!')
        image = Image.open(img_path)
        plt.imshow(image, interpolation='nearest')
        plt.axis('off')
        plt.show()
        breed = Resnet_predict_breed(img_path)
        print('OMG! You are {}\n\n'.format(breed))
    
    elif face_detector(img_path):
        print('Lemme guess, Hey! You are Human!')
        image = Image.open(img_path)
        plt.imshow(image, interpolation='nearest')
        plt.axis('off')
        plt.show()
        breed = Resnet_predict_breed(img_path)
        print('Hahahha! You look like {}\n\n'.format(breed))
    
    else:
        print('Sorry, you are neither a dog nor a human')
        image = Image.open(img_path)
        plt.imshow(image, interpolation='nearest')
        plt.axis('off')
        plt.show()

ШАГ-7 Время тестирования!

Давайте протестируем несколько изображений и посмотрим на результаты:

Зоны улучшения

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

Заключение

Надеюсь, вам понравилось читать мою статью, и, должно быть, вы узнали что-то новое сегодня. Весь код этого проекта загружен на мой github. Не стесняйтесь взглянуть на это.