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

bear_types = ('grizzly', 'black', 'teddy') #Creating a tuple of types of bears
path = Path('bears')
path
Path('bears')
type(bear_types)
tuple
for bear_type in bear_types:
    print(bear_type)
grizzly
black
teddy

Загрузка изображений для каждого вида медведя в отдельные подкаталоги в каталоге «медведи»

if not path.exists():
    path.mkdir() #Creating parent directory 'bears'
    for bear_type in bear_types:
        dest = (path/bear_type)
        dest.mkdir() #Creating directory for each type of bear
        results = search_images_ddg(f'{bear_type} bear', max_images = 100)
        download_images(dest, urls=results)

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

path.ls() #Shows the folders available at that path
(#3) [Path('bears/grizzly'),Path('bears/black'),Path('bears/teddy')]

get_image_files получает список путей ко всем изображениям в объекте Path.

fns = get_image_files(path)
fns
(#278) [Path('bears/grizzly/00000047.jpg'),Path('bears/grizzly/00000055.jpg'),Path('bears/grizzly/00000036.jpg'),Path('bears/grizzly/00000030.jpg'),Path('bears/grizzly/00000097.jpg'),Path('bears/grizzly/00000029.jpg'),Path('bears/grizzly/00000091.jpg'),Path('bears/grizzly/00000093.jpg'),Path('bears/grizzly/00000090.jpg'),Path('bears/grizzly/00000002.jpg')...]

Иногда изображения, загруженные из Интернета, повреждены. Итак, давайте проверим, так ли это.

failed = verify_images(fns)
failed
(#0) []

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

failed.map(Path.unlink);

Загрузчики данных

  • Мы будем использовать DataBlock API для создания DataLoaders, поскольку он предоставляет настраиваемые функции. Загрузчики данных загружают данные в требуемом для модели формате.
  • Нам нужно предоставить информацию о типе данных для независимой переменной и зависимой с помощью блоков. ImageBlock означает, что нашими независимыми переменными являются изображения, а CategoryBlock означает, что модели необходимо классифицировать изображения по разным категориям.
  • Мы должны рассказать, как получить список входных файлов. Для этого мы должны передать функцию параметру get_items. Как упоминалось выше, get_image_files возвращает список путей ко всем изображениям.
  • RandomSplitter случайным образом разбивает набор данных на два набора: наборы для обучения и наборы для проверки. valid_pct=0.2 указывает разделителю использовать 20 % от общего объема данных в качестве проверочного набора. seed=20 гарантирует, что мы получаем один и тот же набор изображений в проверочном наборе в разные эпохи, чтобы модель не могла видеть эти изображения как часть проверочного набора во время обучения.
  • get_y = parent_label указывает загрузчику данных получить имя родительской папки в качестве метки для всех изображений.
  • item_tfms используется для преобразования отдельных изображений. Resize(128) изменит размер изображения до 128x128 пикселей.
bears_data_block = DataBlock(
    blocks = (ImageBlock, CategoryBlock),
    get_items = get_image_files,
    splitter = RandomSplitter(valid_pct=0.2, seed=20),
    get_y = parent_label,
    item_tfms = Resize(128)
)

Загрузка данных с помощью datalaoder.

dls = bears_data_block.dataloaders(path)

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

Нам нужно указать, сколько изображений мы хотим отобразить в скольких строках. Значение по умолчанию max_n равно 9.

dls.train.show_batch(max_n=4, nrows=1)

dls.valid.show_batch(max_n=4, nrows=1)

  • По умолчанию Resize обрезает изображения в форме квадрата требуемого размера, используя полную высоту или вес изображения. Мы всегда должны помнить, что обрезка изображения приведет к потере некоторой информации. Давайте попробуем еще несколько типов метода изменения размера, чтобы понять это.
  • Чтобы применить преобразования к существующему блоку данных, используется метод new() блока данных. Здесь мы будем сжимать изображения в изображении размером 128x128 пикселей.
bears_data_block = bears_data_block.new(item_tfms = Resize(128, ResizeMethod.Squish))
dls = bears_data_block.dataloaders(path)
dls.valid.show_batch(max_n=4, nrows=1)

Здесь мы дополним изображения нулями (черными).

bears_data_block = bears_data_block.new(item_tfms = Resize(128, ResizeMethod.Pad, pad_mode='zeros'))
dls = bears_data_block.dataloaders(path)
dls.show_batch(max_n = 4, nrows = 1)

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

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

bears_data_block = bears_data_block.new(item_tfms = RandomResizedCrop(128, min_scale=0.3))
dls = bears_data_block.dataloaders(path)
dls.valid.show_batch(max_n=4, nrows=1)

Чтобы одно и то же изображение повторялось для каждой версии преобразования RandomResizedCrop, нам нужно использовать unique=True.

dls.valid.show_batch(max_n=4, nrows=1, unique=True)

Увеличение данных

  • Расширение данных означает создание разных версий входных данных, чтобы они выглядели по-разному, но имели одинаковое значение.
  • Некоторыми из распространенных методов увеличения данных являются вращение, отражение, деформация, изменение яркости, изменение контраста и т. д.
  • aug_transforms включает стандартный набор методов увеличения. Чтобы применить эти преобразования к пакету данных с использованием графического процессора, мы можем установить это в параметре batch_tfms в DataBlock.
  • Значение mult определяет степень увеличения данных.
bears_data_block = bears_data_block.new(item_tfms = Resize(128), batch_tfms = aug_transforms(mult=2))
dls = bears_data_block.dataloaders(path)
dls.train.show_batch(max_n=4, nrows=1, unique=True)

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

bears_data_block = bears_data_block.new(item_tfms = Resize(128), batch_tfms = aug_transforms(mult=3))
dls = bears_data_block.dataloaders(path)
dls.train.show_batch(max_n=4, nrows=1, unique=True)

На приведенных выше изображениях отчетливо виден эффект изменения значения mult.

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

Теперь мы будем обучать нашу модель. Здесь я создаю новый DataBlock с RandomResizedCrop для преобразования отдельных элементов и aug_transforms для пакетного преобразования.

bears_data_block = DataBlock(
                        blocks = (ImageBlock, CategoryBlock),
                        get_items = get_image_files,
                        splitter = RandomSplitter(valid_pct=0.2, seed=4),
                        get_y = parent_label,
                        item_tfms = RandomResizedCrop(224, min_scale = 0.3),
                        batch_tfms = aug_transforms(mult=1)
                    )
dls = bears_data_block.dataloaders(path)
learn = cnn_learner(dls, resnet18, metrics=error_rate)

Downloading: "https://download.pytorch.org/models/resnet18-f37072fd.pth" to /root/.cache/torch/hub/checkpoints/resnet18-f37072fd.pth

0%| | 0.00/44.7M [00:00<?, ?B/s]

learn.fine_tune(4)

Мы достигли почти 98,2% точности всего за 4 эпохи во время обучения.

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

  • Матрица путаницы используется, чтобы увидеть, сколько входных данных помечено неправильно. Диагональ матрицы показывает правильно классифицированные изображения. Другие ячейки представляют собой неправильно классифицированные изображения. В этом случае один черный медведь ошибочно классифицируется как медведь гризли, остальные изображения классифицируются правильно.
  • Примечание. Матрица путаницы рассчитывается с использованием проверочного набора.
interp = ClassificationInterpretation.from_learner(learn)
interp.plot_confusion_matrix()

plot_top_losses показывает изображения с наибольшими потерями. Он также включает правильно классифицированные изображения с низкой достоверностью. Изображения будут помечены четырьмя параметрами: прогноз, фактическое значение, потеря и вероятность.

interp.plot_top_losses(5, nrows=1)

Очистка данных после обучения

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

ImageClassifierCleaner не удаляет изображения. Это позволяет нам выбирать изображения, где изображения отображаются в порядке убывания потерь (от самых высоких потерь к самым низким). Он предоставляет графический интерфейс с меню с возможностью сохранения, удаления или переименования.

cleaner = ImageClassifierCleaner(learn)
cleaner

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

for i in cleaner.delete():
    cleaner.fns[i].unlink()

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

for i, category in cleaner.change():
    shutil.move(str(cleaner.fns[i]), path/category)

После того, как неправильная маркировка будет удалена или перемаркирована после обучения, мы можем повторно обучить модель. Но поскольку наша модель уже достигла 100% точности на проверочном наборе, мы не собираемся повторно обучать модель.

Использование модели для вывода

  • Теперь, когда наша модель обучена, пришло время вывода. Поскольку модель обучается в самой этой записной книжке, мы можем напрямую сделать вывод. Но я собираюсь показать, как мы можем сделать вывод в случае, если бы обучение происходило в каком-то другом блокноте.
  • Во-первых, нам нужно экспортировать модель. Мы будем использовать метод export(). Он экспортирует модель в формат .pkl, который мы можем использовать в дальнейшем для вывода в любом месте в этом блокноте, в другом блокноте или в любом приложении.
learn.export()

Давайте подтвердим, есть ли файл .pkl, так как мы экспортировали модель.

path = Path()
path.ls(file_exts='.pkl')
(#1) [Path('export.pkl')]

Мы видим, что есть файл .pkl, который является нашей экспортированной моделью.

Прежде чем делать вывод/предсказание, нам нужно загрузить экспортированную модель.

predictor = load_learner(path/'export.pkl')

Поскольку наша модель загружена обратно, мы можем сделать прогноз/вывод.

predictor.predict('../clean/images/grizzly.jpg')
('grizzly', TensorBase(1), TensorBase([3.1470e-05, 9.9994e-01, 2.5491e-05]))
  • Метод predict() дает нам 3 вещи.
  1. Прогнозируемое значение
  2. Индекс прогнозируемой категории
  3. Список вероятностей
  • Второе значение сообщает, какое значение искать в списке вероятностей. Здесь значение в индексе 1 — это вероятность того, что данный медведь является медведем гризли, то есть 0,999998.

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

predictor.dls.vocab
['black', 'grizzly', 'teddy']

Создание графического интерфейса для приложения для ноутбука

В этом разделе мы собираемся создать графический интерфейс внутри самого ноутбука. Это будет похоже на мини-веб-приложение для вывода нашей модели. Для этой цели мы будем использовать виджеты iPython. Fast.ai также предоставляет нам эти виджеты.

from fastai.vision.widgets import *

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

btn_upload = widgets.FileUpload()
btn_upload

img = PILImage.create(btn_upload.data[-1])

Создание кнопки вывода для отображения изображения.

out_pl = widgets.Output()
out_pl.clear_output()
with out_pl:
    display(img.to_thumb(128,128))
out_pl

Получение предсказания загруженного изображения.

pred, pred_idx, prob = predictor.predict(img)

Создание виджетов Label, которые будут отображать прогноз в виде предложения.

label = widgets.Label()
label.value = f'Prediction: {pred}, Probability: {prob[pred_idx]:.04f}'
label

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

predict_btn = widgets.Button(description='Classify')
predict_btn

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

upload_btn = widgets.FileUpload()
output = widgets.Output()
label_btn = widgets.Label()
predict_btn = widgets.Button(description='Classify')

Определение поведения при нажатии кнопки классифицировать :

def on_click_classify(change):
    img = PILImage.create(upload_btn.data[-1])
    output.clear_output()
    with output:
        display(img.to_thumb(128,128))
    pred, pred_idx, prob = predictor.predict(img)
    label_btn.value = f'Prediction: {pred}, Probability: {prob[pred_idx]:.04f}'
predict_btn.on_click(on_click_classify)

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

VBox([widgets.Label('Upload the picture of a bear'), upload_btn, predict_btn, output, label_btn])

Примечание. Доступ к этому блогу также можно получить по ссылке ниже:



Источник: команда fast.ai, Джереми Ховард