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

Фон

В эти дни, в связи с определенными обстоятельствами, я обнаружил, что занимаюсь бесплатным курсом Pytorch, разработанным с помощью fast.ai, материала, на котором полностью основана эта статья (урок 2) и который очаровал меня, учитывая его простоту, полезность и солидность этих библиотек.

В этой статье мы познакомимся с некоторыми инструментами fast.ai для Pytorch, после прочтения мы узнаем:

  • Как создать набор данных с помощью системы поиска изображений Google
  • Как разработать и настроить нейронную сеть для классификации изображений
  • Как использовать ImageCleaner для очистки набора данных обучения и его улучшения.
  • Пример с реальным случаем и примененным частным набором данных
  • Наглядное руководство по развертыванию модели

Создание набора данных из поисковой системы Google

Первое, что я увидел в онлайн-курсе fast.ai, напрямую связано не с Pytorch или fast.ai, а со способом создания набора данных непосредственно из поисковой системы изображений Google.

Эта техника вдохновлена ​​работами Франсиско Ингама и Джереми Ховарда / Адриана Роузброка.

Создание списка URL-адресов для каждой категории

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

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

Чтобы уточнить поиск, мы можем использовать все инструменты поисковой системы, такие как исключение нежелательных элементов в имени изображения (используя -textToExclude, например: -camera -airport) или фильтровать только определенные категории с помощью кнопок тегов.

Для публичного использования набора данных вы должны отфильтровать «Инструменты - Права на использование» и уважать авторские права в зависимости от случая.

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

«Нажмите Ctrl-Shift-J в Windows / Linux и Cmd-Opt-J в Mac, и появится небольшое окно с javascript« Консоль ». Вам нужно будет получить URL-адреса каждого изображения. Перед запуском следующих команд вы можете отключить расширения блокировки рекламы (uBlock, AdBlockPlus и т. Д.) В Chrome. В противном случае команда window.open () не сработает. Затем вы можете выполнить следующие команды: «²

urls = Array.from(document.querySelectorAll('.rg_di .rg_meta')).map(el=>JSON.parse(el.textContent).ou);
window.open('data:text/csv;charset=utf-8,' + escape(urls.join('\n')));

Этот код начнет загрузку файла, который мы должны переименовать в urls_ [ClassName] .csv. Мы использовали в качестве соглашения для имен файлов префикс «urls_», а затем имя класса; это очень важно для работы кода, который мы увидим позже,

Мы повторяем операцию фильтрации изображений в браузере и загрузки их URL-адресов в файле, переименованном в CSV для каждой из трех других категорий, пока вы не получите:

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

Скачивание и проверка изображений по каждому пути

Когда у нас будет список URL-адресов для каждой категории в наших файлах CSV, мы запустим код для загрузки изображений и создания набора данных. В моем случае я создал путь «данные / фотографии», который является моим основным путем. Там я разместил три CSV-файла категорий, а затем мы выполняем код для создания подкаталогов, в которые будут загружаться изображения.

from fastai.vision import *
classes = ['birds','flowers','aircrafts']
path = Path('data/photos')
for folder in classes:
    dest = path/folder
    dest.mkdir(parents=True, exist_ok=True)
path.ls()

Для реальной загрузки изображений мы используем

for cla in classes:
   print (cla)
   file = 'urls_'+cla+'.csv'
   dest = path/cla
   download_images(path/file, dest, max_pics=200)

Затем мы выполняем код для проверки правильности всех загруженных изображений и удаляем изображения, которые могут быть повреждены (delete = True).

for c in classes:
   print(c)
   verify_images(path/c, delete=True, max_size=500)

Чтобы просмотреть документацию по каждому из этих операторов, вы можете запустить команду doc (), например:

doc(verify_images)

Создание и визуализация набора данных

После загрузки и проверки изображений по пути, соответствующему каждой категории, мы можем создать группу данных fast.ai (аналогичный DataFrame), поместить внутрь помеченных изображений и начать визуализацию или работу с ними.

np.random.seed(7)
data = ImageDataBunch.from_folder(path, train=".", valid_pct=0.2, ds_tfms=get_transforms(), size=224, num_workers=4).normalize(imagenet_stats)

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

data.show_batch(rows=3, figsize=(7,8))

Модель первая версия

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

Конфигурация resnet34, в принципе, достаточно эффективна и проста для предлагаемой области применения.

learn = cnn_learner(data, models.resnet34, metrics=error_rate)
learn.fit_one_cycle(4)

После первого обучения мы видим, что сеть вообще не пошла неправильно, достигнув точности 95% (error_rate = 0,045455), а время обучения идеальное.

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

learn.lr_find()

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

learn.recorder.plot()

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

Эта среда, отмеченная нижними красными полосами на графике справа, дает нам начало посередине между 1e-⁶ и 1e-⁵, поэтому мы можем грубо сказать, что начало почти (1e-⁶ / 2); окончательный диапазон явно находится на уровне 1e- ³

learn.fit_one_cycle(2, max_lr=slice(1e-6/2,1e-3))

И мы видим, что точность модели улучшается, достигнув 96,5%, с error_rate = 0,034091.

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

learn.save('stage-2')

Очистка тренировочного набора данных

Матрица путаницы

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

learn.load('stage-2');
interp = ClassificationInterpretation.from_learner(learn)
interp.plot_confusion_matrix()

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

ImageCleaner для максимальных потерь

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

Виджет не удаляет изображения по соответствующему пути, а создает новый CSV-файл (cleaned.csv) со списком изображений «не для удаления» и с правильной категорией в случае переназначения

from fastai.widgets import *
db = (ImageList.from_folder(path)
.split_none()
.label_from_folder()
.transform(get_transforms(), size=224)
.databunch())
learn_cln = cnn_learner(db, models.resnet34, metrics=error_rate)
learn_cln.load('stage-2');

Мы создаем новую модель learn_cln, читающую из папок (from_folder), и берем веса второго этапа, сохраненные на предыдущем этапе.

Затем разделите набор данных и индексы, взятые из основных потерь при обучении. С этими объектами мы вызываем ImageCleaner, который будет отображать на экране элементы путаницы (from_toplosses), чтобы мы могли устранить их или переназначить их правильному классу.

ds, idxs = DatasetFormatter().from_toplosses(learn_cln)
ImageCleaner(ds, idxs, path)

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

ImageCleaner для дубликатов

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

ds, idxs = DatasetFormatter().from_similars(learn_cln)
ImageCleaner(ds, idxs, path, duplicates=True)

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

Модель после очистки изображения

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

Здесь мы должны быть осторожны с кодом, поскольку необходимо регенерировать DataBunch (Data Frame с изображениями и их метками). Тем не менее, в этом случае мы собираемся указать, что источником является «from_csv» (в предыдущем примере это было «from_folder»).

db = (ImageList.from_csv(path, 'cleaned.csv', folder='.')
.no_split()
.label_from_df()
.transform(get_transforms(), size=224)
.databunch())
learn_cln = cnn_learner(db, models.resnet34, metrics=error_rate)
learn_cln.fit_one_cycle(2, max_lr=slice(1e-6/2,1e-3))

Повторно обучая модель, вы достигнете 100% точности без нахождения запутанных значений и без error_rate в обучающей модели. Большой!

Реальный случай использования

До сих пор мы работали с моделью классификации изображений с использованием «resnet34» на наборе данных, созданном нами из Google.

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

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

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

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

import os
import sys
from shutil import copyfile
dir_ori = 'attach'
dest = 'attach/images'
os.chdir(dir_ori)
for x in os.listdir('.'):
   if os.path.isdir(x):
       newdir = os.path.join(dir_ori,x)
       os.chdir(newdir)
       for w in os.listdir(newdir):
           if os.path.isfile(w):
               file, ext = os.path.splitext(w)
               if ext.lower() == '.jpg':
                  if sys.platform == "win32":
                     os.popen('copy '+w+' '+dest)
                  else
                     os.popen('cp '+w+' '+dest)
       os.chdir(dir_ori)

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

Мы близки к начальной точности (до ImageCleaner) чуть более 85%.

Здесь мы видим наглядный пример ImageCleaner с поиском похожих, чего мы не могли увидеть в предыдущей модели.

После очистки обучающего набора данных и перемаркировки сбивающих с толку случаев мы повторно берем dataBuch из cleaned.csv, а при повторном обучении модели, взяв в качестве источника данных чистую базу, мы снова получаем 100% точность.

Отлично отлично!

Разверните модель

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

Прогнозирование в рабочей среде

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

learn.export()

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

defaults.device = torch.device('cpu')
img = open_image(path/'birds'/'00000022.jpg')
img

Теперь, когда нам нужно взглянуть на файл 00000022.jpg, мы протестируем модель:

learn = load_learner(path)
pred_class,pred_idx,outputs = learn.predict(img)
pred_class

И в результате, конечно же, появилась категория птиц!

Прогнозирование в сервисе со Starlette⁵ и Uvicorn⁶

Теперь займемся запуском модели в производство. Для этого мы используем его фреймворк на Python 3.6+, и чтобы установить его, мы запускаем его из среды Python.

$ pip3 install starlette or $ pip install starlette

Нам также потребуется построить сервер; в этом случае мы будем использовать Uvicorn [vii], который мы устанавливаем

$ pip3 install uvicorn o $ pip install uvicorn

Давайте попробуем простой код Python, чтобы узнать, сможем ли мы создать API

from starlette.applications import Starlette
from starlette.responses import JSONResponse
import uvicorn
app = Starlette(debug=True)
@app.route('/')
async def homepage(request):
    return JSONResponse({'MySAMPLE': 'It works!'})
if __name__ == '__main__':
    uvicorn.run(app, host='0.0.0.0', port=8000)

Когда вы запустите его, будет построен сервер Uvicorn, который предоставит вам доступ из вашего браузера к http: // localhost: 8000 /

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

Http: // localhost: 8000 / classify-url? Url = imagen.jpg

Здесь код Саймона Уиллисона в качестве справочного материала для полного развертывания (помните, что нам нужно взять файл model.pkl в среде развертывания)

from fastai.vision import
@app.route("/classify-url", methods=["GET"])
async def classify_url(request):
   bytes = await get_bytes(request.query_params["url"])
   img = open_image(BytesIO(bytes))
   _,_,losses = learner.predict(img)
   return JSONResponse({
"predictions": sorted(zip(cat_learner.data.classes, map(float, losses)),key=lambda p: p[1],reverse=True)
})

Примечание. Этот код отображается как справочник и взят непосредственно из курса, упомянутого выше. В задачу данной статьи не входит пошаговое объяснение или проверка. Как правило, код получает из запроса в качестве параметра URL-адрес изображения, открывает его, делает прогноз и возвращает JSON с вероятностями каждой категории. Вы можете просмотреть полный код Саймона Уилсона, спасибо ему.

Резюме

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

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

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

Все это осуществляется через fast.ai для Pytorch, в несколько коротких строк кода.

Как только мы вспоминаем, что эта статья вдохновлена ​​курсом https://course.fast.ai, урок 2, на котором мы проводили наши тесты и из которого мы берем основу для нашего реального случая.

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

использованная литература

[1] В упомянутом курсе fast.ai эта техника вдохновлена: Франсиско Ингамом и Джереми Ховардом / [Адриан Роузброк]

[2] https://course.fast.ai, урок 2, Jupyter Notebook: lesson2-download

[3] https://towardsdatascience.com/an-overview-of-resnet-and-its-variants-5281e2f56035

[4] https://medium.com/@14prakash/understanding-and-implementing-architectures-of-resnet-and-resnext-for-state-of-the-art-image-cf51669e1624

[*] В последнее время со мной случалось, что матрицы путаницы с более чем двумя классами выглядят странно и ошибочно: очевидно, это тема версии matplotlib, поэтому я все равно решил включить ее.

[5] Uvicorn - это молниеносный ASGI-сервер, построенный на uvloop и httptools.

[6] Starlette - это легкий фреймворк / инструментарий ASGI, который идеально подходит для создания высокопроизводительных асинхронных сервисов.

[7] Саймон Уиллисон составил структуру этого кода.