Всем привет!

Сегодня мы пройдемся по классификации изображений различных видов собак и кошек, используя пакет Vision для fastai2, деконструируя каждую строку кода. Моя цель не в том, чтобы вы просто получили смутное представление о том, как работать с блокнотом, который можно найти на https://walkwithfastai.com/Introduction и https://course.fast.ai/videos/? урок=2, но для того, чтобы вы (и я) могли самостоятельно создавать собственный код. Спасибо StackOverflow, Google и Quora за помощь в заполнении пробелов. Итак, начнем!

Установка пакетов

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

!pip install fastai -q --upgrade

Самая простая функция — это !pip install ‹имя пакета›, но добавление -q позволяет нам заглушить любой подробный вывод из системы, похожий на мысли компьютера. Кроме того, с помощью обновления мы получаем самую последнюю версию пакета. На самом деле это очень важно и немного странно, потому что почему бы ему не установить самую последнюю версию? Однако я сравнил выполнение кода с расширением для обновления и без него и получил две совершенно разные версии, поэтому, пожалуйста, включите обновление при установке пакета.

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

from fastai.basics import *
from fastai.vision.all import *
from fastai.callback.all import *

Код в основном работает так: из «этой библиотеки» импортируйте «эту функцию». Например, функция fromnumpyimportmedian импортирует функцию median из библиотеки numpy. Однако, поскольку мы будем использовать множество функций, «*» позволяет нам импортировать все функции в этой конкретной библиотеке.

Обратите внимание, что мы не импортируем все из fastai. Это было бы крайне избыточно, но мы импортируем все из подбиблиотек/модулей в fastai, отсюда и .basics, .vision.all и .callback.all.

Теперь о хорошем!

Извлечение и преобразование данных

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

path = untar_data(URLs.PETS)

untar_data — это функция, которая позволяет загружать и извлекать все URL-адреса данных в одно конкретное место назначения, которое в данном случае мы называем путь. Все наборы данных относятся к классу URL-адресов, поэтому мы выбираем, в частности, набор данных PETS, а значит, и .PETS. С тем же успехом я мог бы набрать shoothbox = untar_data(URLs.CARS), если я хочу поместить все данные об автомобилях, отправленные в коробку для обуви. .ai/data.external#URLs»

Если вы затем запустите этот код:

path.ls()

Вы увидите список файлов в пути:

(#2) [Путь(‘/root/.fastai/data/oxford-iiit-pet/annotations’), Путь(‘/root/.fastai/data/oxford-iiit-pet/images’)

Есть только два элемента (отсюда и номер 2), и тот, который мы будем использовать, — это второй, который содержит изображения, необходимые для этого проекта.

Затем нам нужно преобразовать и упаковать изображения в формы, понятные компьютеру. Для этого мы будем создавать DataBlocks и DataLoaders. Я нашел действительно хорошее объяснение того, что они собой представляют в контексте fastai, здесь: https://muttoni.github.io/blog/machine-learning/fastai/2020/12/26/datablocks-vs-dataloaders.html , так что обязательно прочтите это, прежде чем двигаться дальше.

Итак, давайте создадим наш блок данных для этого проекта:

pets = DataBlock(
blocks = (ImageBlock, CategoryBlock),
get_items = get_image_files,
splitter = RandomSplitter(valid_pct = 0.2, seed= 100),
get_y = using_attr(RegexLabeller(pat = r'^(.*)_\d+\..*$'), 'name'),
item_tfms = Resize(460),
batch_tfms = aug_transforms(size = 224, min_scale = 0.75)
)

Во-первых, у нас есть блоки: я создал ImageBlock (входная переменная) и CategoryBlock (целевая переменная, которую мы пытаемся предсказать). Затем я передал функцию get_image_files, которая предоставляет все изображения по пути, который я укажу позже. Далее идет мой сплиттер = RandomSplitter. Зачем нам нужен RandomSplitter и что это за числа в скобках?

Что ж, нам нужно разделить наши данные на обучающий набор и проверочный набор. Учебный набор — это то, что модель использует, чтобы научиться выполнять эту классификацию, а проверочный набор — это то, что модель использует для проверки своих знаний и проверки точности (почти как викторина). valid_pct — это процент наших данных, которые входят в набор проверки, который в нашем случае составляет 20% (обычно хорошее число, потому что он может обучаться на большинстве данных [оставшиеся 80%] и тестировать 20% отдельно).

Но как мы можем убедиться, что каждый раз, когда мы запускаем этот код, программа разбивает одни и те же отдельные данные на одни и те же отдельные наборы? В дело вступает переменная seed. Это гарантирует, что разбиение выполняется случайным образом, но последовательно, если мы используем одно и то же число каждый раз, когда запускаем код. В данном случае я выбрал 100, но вы можете использовать 4 или 6709928 или что угодно. Просто убедитесь, что вы не меняете его каждый раз, когда запускаете код.

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

В приведенном выше примере нам нужно извлечь самоеда, но как мы можем это сделать? Что ж, давайте протянем руку помощи Regex! Я не буду объяснять, как использовать регулярное выражение в этом посте, чтобы не отвлекаться слишком много, но посмотрите этот обучающий сайт: https://regexone.com/. Я бы порекомендовал глубоко погрузиться в регулярное выражение ПОСЛЕ прочтения статьи, так как это немного техническое, но ничего, с чем вы не можете справиться :)

В основных терминах (говоря от человека, который только что быстро изучил Regex прямо сейчас), странные символы (r '\/([^\/]+)_\d+.*$') ищут текст вида в вашем имя файла, а затем захватывает его. Он делает это путем поиска шаблона в тексте (обратите внимание, что самоед — единственный между обратными косыми чертами, у которого после подчеркивания стоит цифра, а затем точка).

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

Наша цель — перейти от sayomed_72.jpg к просто sayomed. Итак, теперь, когда RegexLabeller имеет сокращенное имя, мы теперь берем функцию using_attr, которая представляет собой функцию, которая применяет другую функцию к атрибуту аргумента. Все это звучит очень запутанно, но мы заменяем атрибут «имя» текущего файла тем, что есть в функции RegexLabeller. Проще говоря, представьте RegexLabeller как средство для создания этикеток: pat — это метка, которую мы вводим в средстве создания, а затем RegexLabeller берет эту метку и прикрепляет ее к атрибуту «имя» нашего файла.

Суть в том, что он получает название категории и передает его в DataBlock.

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

Преобразование данных и пакетное дополнение

Это будет заключительный раздел Pt. 1, чтобы не делать это слишком длинным, но преобразование данных — полезный инструмент, который не только помогает стандартизировать наши данные, но и учитывать различия в таких данных.

Что я имею в виду?

Если мы выполним item_tfms = Resize(128), то этот код обрежет все элементы изображения до одинакового размера 128 x 128, потому что наша модель обрабатывает эти изображения пакетами. Только подумайте, как трудно расположить множество книг или, что еще хуже, бумаг разного размера. Очень сложно. Поэтому компьютеру нужны версии этих изображений стандартного размера.

Однако откуда нам знать, что мы не вырезаем из картинки всю полезную информацию? Мы не знаем. Поэтому вместо этого мы выбираем относительно большой размер изображения, например 460, чтобы он захватывал либо всю ширину, либо высоту изображения, в зависимости от того, что наступит раньше. Это работает, потому что если у нас есть изображение размером 500 на 320, оно будет занимать всю высоту 320 и изменять размер до 320 x 320. Это гарантирует, что мы не потеряем полезную информацию из данных. Забавная часть заключается в том, что он также делает обрезку случайным образом для большего разнообразия элементов изображения.

item_tfms = Resize(460)

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

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

Например, min_scale = 0,3 и max_scale = 0,8 сообщают функции, что мы хотим выполнить это переворачивание и вращение, а также все эти преобразования не менее чем на 30% изображения в нашей обрезке, но не более чем на 80%. Итак, скажем, она выбирает 40% для первого пакета, функция берет 40% всех соответствующих пикселей изображения, а затем изменяет их размер до стандартного размера изображения, который мы определяем, который в идеале должен быть меньше размера, чем тот, который мы сначала использовали в item_tfms. Случайность этой функции означает, что она может выбрать 60% для следующей партии и так далее.

Обратите внимание, что он сначала берет процент изображения, а затем изменяет его размер до размера, определенного в параметрах, а не наоборот.

batch_tfms = aug_transforms(size = 224, min_scale =0.75)

В нашем примере мы берем не менее 75% пикселей изображения в пакете, а затем изменяем его размер до 224 x 224.

Цель всех этих шагов — помочь варьировать данные, которые мы передаем в модель, чтобы ее можно было обучить на нескольких вариантах одних и тех же данных.

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

Вот краткое изложение кода, который мы набрали в предыдущем разделе:

pets = DataBlock(
blocks = (ImageBlock, CategoryBlock),
get_items = get_image_files,
splitter = RandomSplitter(valid_pct = 0.2, seed= 100),
get_y = using_attr(RegexLabeller(pat = r'^(.*)_\d+\..*$'), 'name'),
item_tfms = Resize(460),
batch_tfms = aug_transforms(size = 224, min_scale = 0.75)
)

Ух ты!

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

dls = pets.dataloaders(path/'images')

Здесь мы загружаем URL-адреса изображений по пути, который мы создали ранее, в загрузчик данных, вызывая функцию .dataloaders в нашем блоке данных pets (то есть в нашем шаблоне). Теперь давайте заглянем внутрь наших данных, используя функцию show_batch. max_n определяет, сколько изображений мы хотим видеть, а figsize — это именно то, каким должен быть размер этих рисунков. Альтернатива также работает, где nrows — это количество отображаемых строк, а ncols — количество отображаемых столбцов.

dls.show_batch(max_n=4, figsize = (10,15))
or
dls.show_batch(nrows=1, ncols=3)

Вот пример того, как выглядит мой:

Наконец, мы хотим убедиться, что все было сделано правильно, поэтому мы извлекаем сводку построения нашего DataBlock со стороны компьютера, выполнив следующее:

pets.summary(path/'images')

Если в конце вы не получите код ошибки, тогда ура! Готово! Вы успешно написали 16 строк кода, и вы круты, потому что поняли его на 100% (исключая регулярное выражение). Код также можно найти в моем репозитории, если вы хотите получить его четкое изложение: https://github.com/OlabisiBello/FastAIPractice/blob/main/PETS_tutorial_part_one.py.

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

dls.vocab
dls.vocab.o2i

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

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

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

А пока, выздоравливайте, мои друзья-программисты ❤