Преодоление дилеммы хот-догов

Воссоздание приложения See Food из Кремниевой долины с помощью сверточной нейронной сети VGG16

Дорогое человечество, добро пожаловать!

Никогда больше в жизни вы не будете страдать от дилеммы хот-догов.

Вы никогда не подумаете: «Хммм… Это хот-дог, который я ем?» пока жует собачий бейгл. Вас никогда не обманут, если вы купите бутерброд с итальянской колбасой, хотя то, чего вы жаждете, — это хот-дог. Я обещаю вам, что!

Вдохновение

Кто здесь фанат знаменитого сериала HBO «Силиконовая долина»? Если да, отлично! Если нет, посмотрите короткое видео ниже 👇.

Видите приложение See Food? Представьте, чего вы могли бы достичь, если бы у вас в руках было это приложение. Небо это предел.

Чтобы не волноваться. С помощью модели VGG16 мы можем воссоздать это приложение «Shazam of Food»!

Примечание. VGG16 — это сверточная нейронная сеть, разработанная К. Симоняном и А. Зиссерманом из Оксфордского университета. Он был использован для победы в конкурсе ILSVR (Imagenet) в 2014 году. На сегодняшний день он считается одной из лучших архитектур модели компьютерного зрения.

Данные

Для обучения нашей модели VGG16 мы будем использовать набор данных хот-дог, а не хот-дог от Kaggle. Этот набор данных содержит два подмножества:

  • Набор обучающих данных с 249 изображениями хот-догов и 249 изображениями различных продуктов, кроме хот-догов.
  • Тестовый набор данных с 250 изображениями хот-догов и 250 изображениями различных продуктов, кроме хот-догов.

Масштабирование и изменение размера изображений

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

test_path = "../input/hot-dog-not-hot-dog/test"
train_path = "../input/hot-dog-not-hot-dog/train"

Далее мы импортируем пакет ImageDataGenerator от Keras, чтобы масштабировать и изменять размер наших изображений.

from keras.preprocessing.image import ImageDataGenerator

Здесь мы используем метод «.flow_from_directory()» для загрузки изображений из набора обучающих данных. Затем мы изменим масштаб изображений, разделив значения интенсивности пикселей на 255, чтобы сохранить значения от 0 до 1. Мы также изменим размер изображений до 227 x 277. Оба процесса необходимы для модели VGG16.

train_generator = ImageDataGenerator(rescale=1./255).                                     flow_from_directory(train_path, target_size=(227, 227), batch_size=500)

Давайте сделаем то же самое для изображений в тестовом наборе данных.

test_generator = ImageDataGenerator(rescale=1./255). flow_from_directory(test_path, target_size=(227, 227), 
batch_size = 500)

Разделение наборов данных

Далее мы будем использовать функцию Python «.next()» для назначения переменных для данных изображения и меток наших наборов данных.

train_images, train_labels = next(train_generator)
test_images, test_labels = next(test_generator)

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

Чтобы создать этот набор данных, мы разделим текущий набор обучающих данных на два — 75% будут сохранены для обучения, а 25% будут использоваться для проверки.

Для этого мы будем использовать функцию Scikit-Learn «.train_test_split()». Параметр «test_size» установлен равным 0,25, чтобы захватить 25%.

from sklearn.model_selection import train_test_split
train_images, val_images, train_labels, val_labels = train_test_split(train_images, train_labels, test_size=0.25, random_state=42)

Примечание. В идеале данные для обучения должны скомпрометировать 70 % всего набора данных. Но наши данные были разделены как 50–50 для подмножеств обучения и тестирования, наш окончательный набор данных для обучения меньше, чем для тестирования.

Увеличение данных изображения

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

  • Случайный поворот изображений на 30°
  • Переворачивание изображений по горизонтали
  • Заполните точки вне границ изображения значениями, вычисленными из ближайших точек

Используя эти методы увеличения, мы увеличиваем размер нашего обучающего набора данных, создавая модифицированные версии изображений. Давайте настроим эти дополнения с помощью метода «.ImageDataGenerator()».

datagen = ImageDataGenerator(
        rotation_range=30,
        horizontal_flip=True,
        fill_mode='nearest')

Теперь мы применим данные нашего тренировочного изображения к аугментациям.

datagen.fit(train_images, augment=True)

Создание модели

Теперь, когда у нас есть готовые данные, мы подготовим модель VGG16. Перво-наперво импортируем необходимые пакеты —

from keras.applications.vgg16 import VGG16, preprocess_input
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import GlobalAveragePooling2D
from tensorflow.keras.layers import Dropout
from tensorflow.keras.layers import Dense
from keras import backend as K

Обратите внимание, что мы импортируем готовую модель VGG16 из Keras, а не создаем ее с нуля. Если вам интересно, вы можете увидеть реальную архитектуру модели на картинке ниже -

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

base_model = VGG16(include_top=False, input_shape = (227, 227, 3), weights = 'imagenet')
model = Sequential()
model.add(base_model)
model.add(GlobalAveragePooling2D())
model.add(Dropout(0.5))
model.add(Dense(2,activation='sigmoid'))
model.summary()

Следующая остановка, мы скомпилируем нашу модель, установив параметры оптимизатора, потерь и метрик. Мы будем использовать стохастический градиентный спуск (SGD) в качестве нашего оптимизатора. Параметр потерь будет «binary_crossentropy», поскольку нашей целью являются двоичные данные (то есть хот-дог или не хот-дог). Что касается показателей производительности, мы будем использовать показатель точности.

model.compile(optimizer="sgd", loss="binary_crossentropy", metrics=["accuracy"])

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

K.set_value(model.optimizer.learning_rate, 0.0015)

Обучение модели

Наконец-то мы можем обучить нашу модель! Мы будем обучать его на 100 эпох, т.е. 100 раундов алгоритм обучения модели будет работать на всем обучающем наборе данных.

Также обратите внимание, что мы устанавливаем параметр «validation_data» для набора данных проверки, который мы создали ранее.

results = model.fit(datagen.flow(train_images, train_labels, batch_size=32), epochs = 100, validation_data =(val_images, val_labels), steps_per_epoch=len(train_images) / 32)

Оценка модели

Наша модель обучена! И это выглядит неплохо, судя по показателю точности валидации последних эпох (77% на 100-й эпохе).

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

results_train = model.evaluate(train_images, train_labels)

results_test = model.evaluate(test_images, test_labels)

Точность 93,57% для обучения и 82,6% для тестирования. Это вполне прилично, так как случайный выбор из двух категорий (хот-дог или не хот-дог) даст вам только 50% точности.

Тестирование модели

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

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

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

import os
import numpy as np
from PIL import Image
from skimage.io import imread
import matplotlib.pyplot as plt

Теперь мы можем написать функцию. В этой функции мы также изменим размер входных изображений до 227 x 227, чтобы сделать их совместимыми с моделью VGG16.

def download_and_predict(url, filename):
    
    # download the image from the url and save
    os.system("curl -s {} -o {}".format(url, filename))
    
    # open the image
    img = Image.open(filename)
    
    # save the image
    img.save(filename)
    
    # convert it to RGB
    img = img.convert('RGB')
    
    # show image
    plt.imshow(img)
    plt.axis('off')
    
    # resize the image for VGG16 model
    img = img.resize((227, 227))
        
    # calculate probabilities of breeds
    img = imread(filename)
    img = preprocess_input(img)
    probs = model.predict(np.expand_dims(img, axis=0))
    pred = probs.argsort()[0][::-1][0]
    
    if pred == 1.:
        print("It's a Hot Dog!")
    else:
        print("It's not a hot dog :(")

Сделанный! Давайте сначала протестируем изображение хот-дога. Вот так!

Эврика! Сделаем еще один.

Все идет нормально. Что, если мы используем изображение пиццы?

Идеально! Как насчет гамбургера?

Как я уже сказал… Добро пожаловать, ребята. Теперь вы можете бродить по улицам в полупьяном виде и останавливаться у любого киоска с едой, чтобы со 100% уверенностью купить настоящий хот-дог!

Давайте проведем еще один тест!

Черт… 😭