Введение в проблему

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

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

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

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

Этот блог будет разбит на 5 конкретных шагов.

  1. Сбор данных
  2. Предварительная обработка данных
  3. Моделирование данных
  4. Анализ модели с помощью TensorBoard
  5. Развертывание и оценка модели

1. Сбор данных

Как и в любом проекте Data Science, первый компонент, который мы должны искать, - это данные, в этом случае данные, над которыми мы будем работать, представляют собой набор изображений, вырезанных с одного и того же веб-сайта Avito.ma для ноутбуков и телефонов обеих категорий, полученная папка будет содержать два подкаталога, называемых соответственно ноутбук и телефон, где форма загружаемых изображений варьируется от 120 x 90 до 67 x 90, с 3 каналами RGB для каждого один. Вот снимок кода, выполняющего эту задачу, а полный код для нее находится в записной книжке.

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

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

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

2.1 Удаление зашумленных данных

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

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

2.2 Изменение размера изображения

Этот шаг полностью зависит от принятой архитектуры глубокого обучения, например, при использовании модели Alexnet для классификации изображений форма ввода должна быть 227 x 227, а для VGG-19 форма ввода - 224 x 224.

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

Для выполнения этой задачи мы создаем еще один каталог с именем preprocessed_data в двух подкаталогах phone и laptop, затем перебираем каждое изображение в исходной папке. of raw_data, чтобы изменить его размер и сохранить в новом созданном каталоге.

Следовательно, мы получаем новый сгенерированный набор данных обоих классов в соответствующей форме 64 x 64.

2.3 Разделение данных

После изменения размера набора данных мы разбиваем его на 80% для обучающего набора, а остальное оставляем для проверки. Для выполнения этой задачи мы создаем новый каталог с именем data ,, в котором мы устанавливаем два других новых каталога train и validation ,, где мы устанавливаем оба классы изображений для phones и laptops.

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

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

3. Моделирование данных

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

В компьютерном зрении операция свертки является одним из основных строительных блоков сверточных нейронных сетей, для которых требуются 4 обязательных компонента:

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

Слой свертки

model.add(Conv2D(filters=32, kernel_size=(3, 3), input_shape=(64, 64, 3), activation=‘relu’))

После создания экземпляра последовательного объекта как model мы используем метод add, чтобы добавить слой свертки с именем Conv2D, где первые параметры - это filters, который представляет собой размерность выходного объема, как показано в сводке модели, форма выходных данных первого слоя была (None, 62, 62, 32).

Поскольку второй параметр kernel_size определяет длину окна одномерной свертки, здесь мы выбираем размер окна 3 x 3 для свертки входного объема.

Третий параметр обозначает input_shape, который соответствует объему 64 x 64 x 3, соответственно для image_width x image_height x color channels (RGB), и последний, но не менее важный, activation_function, который отвечает за добавление нелинейного преобразования в сеть, в этом случае мы выбираем функцию активации relu. .

Максимальный уровень пула

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

Чтобы дать сети высокий уровень обобщения, не имеет значения, есть ли у нас вертикальный край, идущий от x до y, но что есть приблизительно вертикальный край примерно на 1/3 от левого края примерно на 2/3 высоты изображения.

Весь этот процесс возобновляется одной строкой кода с помощью Keras:

model.add(MaxPooling2D(pool_size = (2, 2)))

просто здесь мы вводим еще один уровень максимального пула, называемый MaxPooling2D, используя метод add, где pool_size является окном (2, 2) и по умолчанию strides=None и padding='valid'.

Сглаживание вывода

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

model.add(Flatten())

Что делает здесь Keras, так это просто добавляет Flatten слой в сеть, что просто эквивалентно reshape функции в numpy с ‘C’ порядком.

Полностью связанный слой

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

model.add(Dense(units = 128, activation = 'relu'))
model.add(Dense(units = 2, activation = 'sigmoid'))

Keras делает это легко, добавляя функцию Dense в сеть, для этого просто требуются два параметра units и activation, которые соответственно обозначают количество выходных единиц, которые у нас будут, поскольку мы выполняем двоичную классификацию, поэтому она принимает значение 2 и активацию функция для использования.

Составление сети

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

model.compile(optimizer = 'adam', loss = 'binary_crossentropy', metrics = ['accuracy'])

Параметр loss, поскольку у нас есть двоичная классификация, в которой количество классов M равно 2, кросс-энтропия может быть рассчитана как:

где p - прогнозируемая вероятность, а y - двоичный индикатор (0 или 1).

Чтобы минимизировать эту целевую функцию, нам нужно вызвать оптимизатор, например adam, сокращение от Adaptive Moment Estimation, и по умолчанию его скорость обучения установлена ​​на 0,001, но это не закрывает окно настройки гиперпараметров. В завершение того, что мы сделали, ниже представлен полный код встроенной модели.

Загрузка изображений и преобразование данных

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

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

Когда наборы train и validation готовы для подачи питания в сеть, мы вызываем метод fit_generator, чтобы передать их модели.

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

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

После завершения сеанса обучения мы получаем точность около 87,7% и все еще имеем небольшую потерю 0,352, но высокая точность не обязательно означает, что у нас хорошее качество модели. Нам нужно отслеживать и визуализировать поведение модели во времени, для этого мы используем TensorBoard, который предоставляется вместе с Keras, в качестве функции обратного вызова, работающей с серверной частью TensorFlow.

4. Анализ модели с помощью TensorBoard

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

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

from keras.callbacks import TensorBoard
tensorboard = TensorBoard(log_dir='./tf-log', histogram_freq=0,
                          write_graph=True, write_images=False)

Потеря

Из приведенного выше графика мы можем четко визуализировать, что потеря уменьшается строго для обучающей линии, начиная с 0,39 до 0,13, в то время как она медленно уменьшается для линии проверки, начиная с 0,42 и тратится 25 эпох, чтобы достичь 0,35.

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

Точность

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

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

5. Развертывание модели с использованием Flask

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

# save models weights on hdf5 extension
models_dir = 'models/'
model.save(models_dir+'avito_model.h5')

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

Почему именно Flask?

Flask - это микро-фреймворк для Python, вдохновленный цитатой «Делай одну вещь и делай это хорошо», и именно поэтому я выбрал Flask для использования моей модели в качестве REST API.

Приложение Flask состоит из двух основных компонентов: приложения python (app.py) и HTML-шаблонов, для app.py оно будет содержать логический код, выполняющий прогноз, который будет отправлен в виде ответа HTTP. Этот файл содержит три основных компонента, которые можно представить следующим образом:

  1. Загрузите сохраненную модель.
  2. Преобразуйте загруженное изображение.
  3. Предскажите соответствующий класс, используя загруженную модель.

В следующем разделе мы обсудим наиболее важные из них.

Возвращаясь к основной проблеме.

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

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

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

{"class" : "laptop/phone", "categories_matched": True/False}

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

Демо-версия приложения

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

$ python3 app.py

затем мы просматриваем следующий URL-адрес, показанный на консоли: http://127.0.0.1:5000/, после отображения страницы индекса, выбираем категорию объявления и загружаем связанную с ней фотографию, за кулисами отправляется запрос на маршрут /upload, который сохранит фотографию в каталог, чтобы предсказать соответствующий класс.

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

Если и выбранный, и прогнозируемый классы совпадают, вы получите сообщение об успешном выполнении, говорящее, что все в порядке, иначе вы получите предупреждающее сообщение, и select box автоматически изменится на соответствующий прогнозируемый класс.

Заключение

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

Способы улучшения

  1. Увеличьте размер данных, убрав больше изображений для обоих классов и удалив шумные.
  2. Настройка гиперпараметров для скорости обучения и бета-значений.
  3. Попробуйте другую архитектуру, например Ленет-5.
  4. Используйте Dropout на полностью связанных (плотных) слоях.

Полезные ссылки

Полный проект + блокнот + набор данных: https://github.com/PaacMaan/avito_upload_classifier

Веб-приложение Flask: https://github.com/PaacMaan/avito_upload_classifier/tree/master/flask_app