Cats vs Dogs — это проект начального уровня для классификации изображений. Может ли предварительно обученная нейронная сеть Resnet V2 точно идентифицировать собак и кошек на изображении?
Примечание!
Эта статья является копией и вставкой моего блокнота Kaggle: Computer Vision: 🐱Cats vs Dogs🐶 с Resnet V2 101
Введение
Задача Cats vs. Dogs — это классическая проблема в области компьютерного зрения. Он часто служит вводным проектом для тех, кто начинает применять нейронные сети для классификации изображений.
В этой записной книжке мы будем использовать набор данных Microsoft’s Cats-vs-Dogs. Этот набор данных, состоящий из набора изображений кошек и собак, обычно используется для обучения нейронных сетей задачам бинарной классификации.
Для решения этой задачи мы будем использовать нейронную сеть pre-trained ResNet V2 101. Это очень мощная модель, известная своей точностью в задачах классификации изображений. Загружая модель из моделей Kaggle, мы используем ее предварительно обученные веса, полученные в результате обширного обучения на большом количестве изображений. Это позволяет нам иметь прочную основу для нашей конкретной задачи классификации.
В приведенных ниже функциях мы импортируем все необходимые библиотеки и определяем некоторые полезные функции.
# Importing Libraries # Numpy and Pandas import numpy as np import pandas as pd # Plotly for Data-Viz from plotly.subplots import make_subplots import plotly.subplots as sp import plotly.graph_objs as go import plotly.express as px from plotly.offline import init_notebook_mode init_notebook_mode(connected=True) # Library for OS interactivity import os # Image library from PIL import Image # Rnadom generations lib import random # TensorFlow for Deep Learning import tensorflow as tf import tensorflow_hub as hub from tensorflow.keras.preprocessing.image import ImageDataGenerator from tensorflow.keras.utils import plot_model # Creating temporary files and directories import tempfile # Module for copying files from shutil import copyfile # Module for finding all the pathnames matching a specified pattern import glob # Ignore warnings. import warnings warnings.filterwarnings("ignore") def plot_images_list(images, title): ''' This functions helps to plot a matrix of images in a list ''' fig = sp.make_subplots(rows=3, cols=3) for i in range(min(9, len(images))): img = go.Image(z=images[i]) fig.add_trace(img, row=i//3+1, col=i%3+1) fig.update_layout( title={'text': f'<b>{title}<br> <sub>Image matrix</sub></b>'}, height=950, width=950, margin=dict(t=100, l=80), template='simple_white' ) fig.show() def plot_images_from_generator(images, labels, title): ''' This functions helps to plot a matrix of images and their labels ''' subplot_titles = [label_map[int(labels[i])] for i in range(min(9, len(images)))] fig = sp.make_subplots(rows=3, cols=3, subplot_titles=subplot_titles) for i in range(min(9, len(images))): img_data = np.clip(images[i] * 255, 0, 255).astype(np.uint8) img = go.Image(z=img_data) fig.add_trace(img, row=i//3+1, col=i%3+1) fig.update_layout( title={'text': f'<b>{title}<br> <sub>Image matrix</sub></b>'}, height=950, width=950, margin=dict(t=150, l=80), template='simple_white' ) fig.show()
Исследовательский анализ
Мы начинаем записную книжку с определения каталогов для изображений, содержащих изображения кошек и собак, cat_dir
и dog_dir
.
Функция os.listdir( )
получает список всех имен файлов в каждом из каталогов для кошек и собак. Наконец, функция random.sample( )
выбирает 9 имен файлов для каждого списка файлов кошек и собак, чтобы позже отобразить случайные образцы изображений для каждой метки домашних животных.
cat_dir = '/kaggle/input/microsoft-catsvsdogs-dataset/PetImages/Cat' dog_dir = '/kaggle/input/microsoft-catsvsdogs-dataset/PetImages/Dog' cat_files = os.listdir(cat_dir) dog_files = os.listdir(dog_dir) cat_files = random.sample(cat_files, 9) dog_files = random.sample(dog_files, 9)
В ячейке ниже мы создаем список из Image
объектов. С помощью Image.open()
мы можем открыть и визуализировать файл изображения.
cat_images = [Image.open(os.path.join(cat_dir, f)) for f in cat_files] dog_images = [Image.open(os.path.join(dog_dir, f)) for f in dog_files]
Давайте теперь посмотрим на случайные образцы изображений в каталоге кошек и в каталоге собак.
Предварительная обработка
При работе с данными изображения для нейронных сетей этап предварительной обработки включает преобразование необработанных изображений в соответствующий формат, который можно передать в модель. Мы должны стандартизировать входные данные, чтобы уменьшить сложность, удалить шум и сделать все возможное, чтобы улучшить прогностическую эффективность модели.
Во-первых, мы собираемся создать временный базовый каталог с именем cats-v-dogs
. Впредь мы создаем подкаталоги, в которых будем сохранять файлы изображений как для собак, так и для кошек, разделенные на три отдельных набора: обучение, проверка и тест.
base_dir = '/tmp/cats-v-dogs' # Base directory # Subdirectories sub_dirs = ['training/cat', 'training/dog', 'validation/cat', 'validation/dog', 'test/cat', 'test/dog'] # Adding the sub_dirs into the base_dir for sub_dir in sub_dirs: os.makedirs(os.path.join(base_dir, sub_dir), exist_ok=True) # Creating a directory for each set for cats training_cats_dir = os.path.join(base_dir, 'training/cat') validation_cats_dir = os.path.join(base_dir, 'validation/cat') test_cats_dir = os.path.join(base_dir, 'test/cat') # Creating a directory for each set for dogs training_dogs_dir = os.path.join(base_dir, 'training/dog') validation_dogs_dir = os.path.join(base_dir, 'validation/dog') test_dogs_dir = os.path.join(base_dir, 'test/dog')
Сначала наши только что созданные папки совершенно пусты. Вы можете увидеть это, распечатав длину списка имен файлов в '/training/cat'
.
len(os.listdir('/tmp/cats-v-dogs/training/cat')) 0
Приведенная ниже функция split_data
предназначена для разделения набора данных на наборы для обучения, проверки и тестирования.
Сначала он собирает все пути к файлам из базового каталога. После этого он случайным образом перемешивает эти пути к файлам.
Затем он определяет индексы, по которым нужно разделить данные, и определяет, что обучающий набор будет содержать первые 80% файлов, а проверочный и тестовый наборы будут содержать следующие 10% каждый.
Наконец, мы копируем пути к файлам в их каталоги.
def split_data(base_dir, training_dir, validation_dir, test_dir, split_size=0.8): files = glob.glob(os.path.join(base_dir, '*')) np.random.shuffle(files) train_idx = int(len(files) * split_size) val_idx = int(len(files) * (split_size + (1 - split_size) / 2)) train_files = files[:train_idx] val_files = files[train_idx:val_idx] test_files = files[val_idx:] for file in train_files: copyfile(file, os.path.join(training_dir, os.path.basename(file))) for file in val_files: copyfile(file, os.path.join(validation_dir, os.path.basename(file))) for file in test_files: copyfile(file, os.path.join(test_dir, os.path.basename(file))) # Applying fuction to the 'cats' directories split_data(cat_dir, training_cats_dir, validation_cats_dir, test_cats_dir) # Applying fuction to the 'dogs' directories split_data(dog_dir, training_dogs_dir, validation_dogs_dir, test_dogs_dir)
Теперь мы можем вывести общую длину списков имен файлов в каждом каталоге.
print('Cat files by directories: \n') print('\n') print(f"\nTraining Directory: {format(len(os.listdir('/tmp/cats-v-dogs/training/cat')), ',')} files") print(f"\nValidation Directory: {format(len(os.listdir('/tmp/cats-v-dogs/validation/cat')), ',')} files") print(f"\nTest Directory: {format(len(os.listdir('/tmp/cats-v-dogs/test/cat')), ',')} files") Cat files by directories: Training Directory: 10,000 files Validation Directory: 1,250 files Test Directory: 1,251 files print('Dog files by directories: \n') print('\n') print(f"\nTraining Directory: {format(len(os.listdir('/tmp/cats-v-dogs/training/dog')), ',')} files") print(f"\nValidation Directory: {format(len(os.listdir('/tmp/cats-v-dogs/validation/dog')), ',')} files") print(f"\nTest Directory: {format(len(os.listdir('/tmp/cats-v-dogs/test/dog')), ',')} files") Dog files by directories: Training Directory: 10,000 files Validation Directory: 1,250 files Test Directory: 1,251 files
Приведенная ниже функция remove_corrupted_images
предназначена для поиска поврежденных файлов и их удаления из каталогов. Это сделано для того, чтобы избежать проблем во время обучения.
Функция os.walk()
используется для прохода сверху вниз или снизу вверх для каждого каталога в цикле for, инициированном в начале.
При обнаружении поврежденного файла или файла, не являющегося изображением, функция возвращает распечатку с указанием имени этого файла. Наконец, используя os.remove()
, мы удаляем эти плохие файлы из наших каталогов.
def remove_corrupted_images(dir_path): for subdir, dirs, files in os.walk(dir_path): for file in files: file_path = os.path.join(subdir, file) try: img = Image.open(file_path) # open the image file img.verify() # verify that it is, in fact an image except (IOError, SyntaxError) as e: print('Bad file:', file_path) # print out the names of corrupt files os.remove(file_path) remove_corrupted_images('/tmp/cats-v-dogs') Bad file: /tmp/cats-v-dogs/training/dog/11702.jpg Bad file: /tmp/cats-v-dogs/training/dog/Thumbs.db Bad file: /tmp/cats-v-dogs/training/cat/Thumbs.db Bad file: /tmp/cats-v-dogs/training/cat/666.jpg
ImageDataGenerator
— это класс Keras, который генерирует пакеты данных тензорного изображения с увеличением данных в реальном времени, чтобы их можно было обрабатывать с помощью модели Keras.
Вкратце, дополнение данных — это просто создание вариаций входных изображений путем поворота, смещения, масштабирования, отражения и многих других преобразований.
В нашем случае мы используем параметр rescale
для изменения масштаба значений пикселей из диапазона 0–225 в диапазон 0–1. По сути, мы нормализуем входные данные, чтобы они правильно передавались в нейронную сеть.
train_datagen = ImageDataGenerator(rescale=1./255) val_datagen = ImageDataGenerator(rescale=1./255) test_datagen = ImageDataGenerator(rescale=1./255)
Теперь мы можем успешно использовать flow_from_directory
для чтения изображений, их предварительной обработки, организации их в пакеты и подачи их в модель Keras.
Вот что делает каждый параметр:
.target_size
: определяет размеры, до которых будут изменены все изображения (высота 227 x ширина 227).
. batch_size
: размер пакетов данных
.class_mode
: определяет тип возвращаемых массивов меток. В данном случае это одномерные бинарные метки.
.shuffle
: Перетасовывать ли данные. Для обучения и проверки установлено значение True, чтобы модель получала разнообразные данные в разном порядке, и False для проверки, чтобы данные сохранялись в исходном порядке.
.classes
: соответствует списку имен классов classes.
classes = ['cat', 'dog'] train_generator = train_datagen.flow_from_directory( os.path.join(base_dir, 'training'), target_size=(224, 224), batch_size=32, class_mode='binary', shuffle=True, classes=classes ) validation_generator = val_datagen.flow_from_directory( os.path.join(base_dir, 'validation'), target_size=(224, 224), batch_size=32, class_mode='binary', shuffle=True, classes=classes ) test_generator = test_datagen.flow_from_directory( os.path.join(base_dir, 'test'), target_size=(224, 224), batch_size=32, class_mode='binary', shuffle=False, classes=classes ) Found 19996 images belonging to 2 classes. Found 2500 images belonging to 2 classes. Found 2502 images belonging to 2 classes.
Затем мы извлекаем одну партию изображений и их метки как из данных обучения, так и из данных проверки. Затем мы создаем карту меток, чтобы изменить числовые метки на имена классов.
train_images, train_labels = next(train_generator) val_images, val_labels = next(validation_generator) label_map = {v: k for k, v in train_generator.class_indices.items()}
Теперь мы можем построить набор изображений поезда и проверки.
Моделирование
Теперь мы готовы начать процесс обучения нашей модели для классификации изображений как кошек или собак.
Мы строим последовательную модель, используя класс Keras Sequential
, укладывая слои линейно, один за другим.
Затем мы используем TensorFlow Hub
для загрузки предварительно обученной модели ResNet V2. TensorFlow Hub служит хранилищем для многих предварительно обученных моделей.
Наконец, мы используем метод build
, чтобы установить входную форму для слоев. Наша модель предназначена для приема четырехмерных тензоров в качестве входных данных, при этом второе и третье измерения представляют собой высоту и ширину изображения (оба 224), а четвертое измерение представляет количество цветовых каналов (3 для красного, зеленого и синего). Для первого измерения установлено значение «Нет», чтобы учесть переменный размер партии.
# Importing and building model model = tf.keras.Sequential([ hub.KerasLayer('https://www.kaggle.com/models/google/resnet-v2/frameworks/TensorFlow2/variations/101-classification/versions/2') ]) model.build([None, 224, 224, 3])
Модель ResNet 101 V2
— это вариант архитектуры ResNet (Residual Network), популярной модели глубокого обучения, предназначенной для многих задач распознавания изображений. 101 относится к глубине сети, т. е. состоит из 101 слоя. С другой стороны, V2 указывает, что это вторая версия модели ResNet с улучшенной производительностью.
Когда мы используем hub.KerasLayer('...')
, мы загружаем предварительно обученную модель из TensorFlow Hub с предопределенными весами. Принимая во внимание, что когда мы просто используем from tensorflow.keras.applications import ResNet101V2
, мы импортируем модель без предварительно определенных весов, что может потребовать дальнейшего обучения. Согласно описанию модели здесь на Kaggle, веса модели, которую мы здесь используем, 'были первоначально получены путем обучения набору данных ILSVRC-2012-CLS для классификации изображений ("Imagenet").
Прямо ниже я нарисовал архитектуру модели ResNet 101 V2, которая может помочь в дальнейшем понимании того, как работает эта модель.
Поскольку мы используем предварительно обученную модель, которая была обучена на большом наборе данных, мы установим для trainable
значение False, чтобы существующие слои в модели не изменялись во время обучения. учитывая, что они уже изучили полезные функции.
Однако мы собираемся добавить два новых слоя:
Сначала мы добавляем в модель новый слой Dense. Этот слой имеет 512 нейронов и использует функцию активации ReLU.
Во-вторых, мы добавляем еще один слой Dense всего с одним нейроном. Это выходной слой нашей модели. Он использует сигмовидную функцию активации для вывода значения от 0 до 1, эффективно классифицируя входные изображения.
for layer in model.layers: layer.trainable = False model.add(tf.keras.layers.Dense(512, activation='relu')) model.add(tf.keras.layers.Dense(1, activation='sigmoid')) # 'sigmoid' function used for binary classification
Затем мы можем скомпилировать модель для настройки всего процесса обучения.
Сначала мы определяем функцию потерь, которую модель попытается минимизировать для повышения производительности. Функция binary_crossentropy
— очень распространенная функция для задач бинарной классификации. Это просто логарифмическая потеря, и она определяется следующим уравнением:
Где:
. L(y,ŷ)– функция потерь, оцениваемая между истинными значениями y и прогнозируемые значения ŷ.
. N — это просто количество образцов.
. yᵢ — это истинная метка для образца iᵗʰ.
. ŷᵢ – прогнозируемая вероятность того, что выборка iᵗʰ относится к классу = 1.
. ∑ представляет собой сумму по всем образцам в партии.
Мы также собираемся определить adam
как оптимизатор. Это также широко используемый алгоритм оптимизации для минимизации функции потерь.
Наконец, в metrics
мы определяем accuracy
для оценки модели во время тренировки и отдыха. Точность просто дает нам процент правильно предсказанных меток.
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
Мы также собираемся определить две функции обратного вызова. Во-первых, мы используем ModelCheckpoint
для сохранения лучших весов после каждой эпохи в соответствии с точностью проверочного набора.
С другой стороны, функция EarlyStopping
остановит процесс обучения, когда отслеживаемая метрика перестанет улучшаться в течение последних 5 эпох. Это полезно, чтобы сэкономить время на обучении модели, которая не станет лучше.
checkpoint_cb = tf.keras.callbacks.ModelCheckpoint("best_model.h5", save_best_only=True, monitor='val_accuracy', verbose = 1) early_stopping_cb = tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=5, verbose = 1)
Наконец-то мы можем начать тренироваться, используя model.fit()
!
history = model.fit(train_generator, epochs=20, validation_data=validation_generator, callbacks=[checkpoint_cb, early_stopping_cb]) Epoch 1/20 625/625 [==============================] - ETA: 0s - loss: 0.0548 - accuracy: 0.9856 Epoch 1: val_accuracy improved from -inf to 0.99480, saving model to best_model.h5 625/625 [==============================] - 128s 172ms/step - loss: 0.0548 - accuracy: 0.9856 - val_loss: 0.0180 - val_accuracy: 0.9948 Epoch 2/20 625/625 [==============================] - ETA: 0s - loss: 0.0183 - accuracy: 0.9939 Epoch 2: val_accuracy did not improve from 0.99480 625/625 [==============================] - 106s 169ms/step - loss: 0.0183 - accuracy: 0.9939 - val_loss: 0.0317 - val_accuracy: 0.9880 Epoch 3/20 625/625 [==============================] - ETA: 0s - loss: 0.0112 - accuracy: 0.9958 Epoch 3: val_accuracy did not improve from 0.99480 625/625 [==============================] - 107s 170ms/step - loss: 0.0112 - accuracy: 0.9958 - val_loss: 0.0614 - val_accuracy: 0.9872 Epoch 4/20 625/625 [==============================] - ETA: 0s - loss: 0.0122 - accuracy: 0.9959 Epoch 4: val_accuracy did not improve from 0.99480 625/625 [==============================] - 107s 171ms/step - loss: 0.0122 - accuracy: 0.9959 - val_loss: 0.0383 - val_accuracy: 0.9908 Epoch 5/20 625/625 [==============================] - ETA: 0s - loss: 0.0087 - accuracy: 0.9971 Epoch 5: val_accuracy did not improve from 0.99480 625/625 [==============================] - 106s 170ms/step - loss: 0.0087 - accuracy: 0.9971 - val_loss: 0.0417 - val_accuracy: 0.9900 Epoch 6/20 625/625 [==============================] - ETA: 0s - loss: 0.0074 - accuracy: 0.9973 Epoch 6: val_accuracy did not improve from 0.99480 625/625 [==============================] - 106s 170ms/step - loss: 0.0074 - accuracy: 0.9973 - val_loss: 0.0347 - val_accuracy: 0.9904 Epoch 6: early stopping
Ниже мы можем видеть графики, отображающие как точность, так и потери по эпохам.
Важно отметить, что, глядя на accuracy over epochs
, мы стремимся к максимально возможному значению. Глядя на loss over epochs
, мы стремимся к наименьшему возможному значению.
💡 Наивысшая точность на проверочном наборе была получена в течение первой эпохи.
💡 Наименьшие потери на проверочном наборе также были получены в течение первой эпохи.
Ниже мы загружаем лучшие веса, полученные в процессе обучения и проверки. Затем мы можем предсказать метки на test_generator
и измерить точность обучающих пакетов.
model.load_weights('best_model.h5') predictions = model.predict(test_generator) predicted_labels = (predictions >= 0.5).astype(int) actual_labels = test_generator.classes accuracy = np.mean(predicted_labels.flatten() == actual_labels) print(f"Accuracy: {accuracy * 100:.2f}%") 79/79 [==============================] - 12s 149ms/step Accuracy: 98.68%
💡 Мы правильно предсказали 98,68 % этикеток в тестовых партиях.
Наконец, ниже мы запускаем прогнозы для одной партии test_generator
и строим изображения в этой партии с истинными и предсказанными метками.
test_images, test_labels = next(test_generator) predicted_probs = model.predict(test_images) predicted_labels = (predicted_probs >= 0.5).astype(int) plot_images_from_generator(test_images, test_labels, predicted_labels, "Test Images, Labels & Predictions") 1/1 [==============================] - 0s 40ms/step
💡 Всем изображениям, кроме первого, были правильно присвоены ярлыки.
Развертывание
Для развертывания модели мы используем метод .save()
. Модель .ℎ5 будет сохранена в папке Output здесь, на Kaggle, в /kaggle/working
, которую вы можете легко загрузить.
model.save('cats_vs_dogs.h5') # Saving model for deployment
Я использовал модель, сохраненную выше, для создания приложения с Gradio.
Вы можете получить доступ и протестировать приложение, а также проверить файлы и то, как я их закодировал, в репозитории 🐱 x 🐶 Распознавание изображений — Кошки против собак с Resnet 101 V2 🐱 x 🐶, доступном на Hugging Face.
Заключение
В приведенном выше исследовании мы приступили к одной из самых известных задач в Computer Vision — задаче классификации изображений «Кошки против собак».
Мы изучили изображения в наших данных, а затем выполнили эффективную предварительную обработку данных для использования этих изображений в качестве входных данных для предварительно обученной модели ResNet 101 V2, доступной здесь, на Kaggle.
Используя предварительно обученную модель, мы смогли получить высокую точность за очень короткое время обучения и проверки.
Я также предоставил ссылку на онлайн-приложение Hugging Face, где развернул эту модель. Вы можете использовать его для бесплатной загрузки изображений кошек и собак и получения прогноза от модели.
Я надеюсь, что вы нашли этот эксперимент полезным. Пожалуйста, поделитесь своими мыслями и отзывами в комментариях и не стесняйтесь голосовать, если вы нашли эту работу полезной.
Спасибо за чтение.
Луис Фернандо Торрес
Подключаемся!🔗
LinkedIn • Kaggle • HuggingFace