![image](https://habrastorage.org/webt/yw/la/cw/ywlacwzln0s9f-yo489pjhznpao.jpeg)
Классификация изображений с помощью PyTorch
Учебники по глубокому обучению пестрят профессиональной непонятной терминологией. Я стараюсь свести ее к минимуму и всегда приводить один пример, который можно легко расширить, когда вы привыкнете работать с PyTorch. Мы используем этот пример на протяжении всей книги, чтобы продемонстрировать, как отладить модель (глава 7) или развернуть ее в рабочей среде (глава 8).
С этого момента и до конца главы 4 мы будем компилировать классификатор изображений. Нейронные сети обычно используются в качестве классификаторов изображений; сети предлагают картинку и задают простой вопрос: «Что это?».
Давайте начнем с создания нашего приложения в PyTorch.
Проблема классификации
Здесь мы создадим простой классификатор, который может отличить рыбку от кошки. Мы будем итерировать дизайн и процесс разработки нашей модели, чтобы сделать ее более точной.
На рис. 2.1 и 2.2 изображены рыбка и кошка во всей своей красе. Не уверен, есть ли у рыбки имя, а вот кошку зовут Гельветика.
Давайте начнем с обсуждения стандартных проблем, связанных с классификацией.
![image](https://habrastorage.org/webt/ar/j-/a3/arj-a33u50zsxphzsgheti3kffu.png)
![image](https://habrastorage.org/webt/ul/ix/bf/ulixbfhc40ktaszf-9yxpmm5hsq.png)
Стандартные трудности
Как написать программу, которая сможет отличить рыбку от кошки? Возможно, вы бы написали набор правил, описывающих, что у кошки есть хвост или что у рыбки есть чешуя, и применили бы эти правила к изображению, чтобы программа классифицировала изображение. Но для этого потребуется время, усилия и навыки. А что делать, если вам попадется мэнская кошка? Хотя это явно кошка, у нее нет хвоста.
Эти правила становятся все сложнее и сложнее, когда вы пытаетесь с их помощью описать все возможные сценарии. Кроме того, должен признаться, что визуальное программирование у меня выходит кошмарно, поэтому мысль о необходимости вручную писать код для всех этих правил приводит в ужас.
Нужна функция, которая при вводе изображения возвращает кошку или рыбку. Сложно построить такую функцию, просто перечислив полностью все критерии. Но глубокое обучение, по сути, заставляет компьютер выполнять тяжелую работу по созданию всех этих правил, о которых мы только что говорили, при условии, что мы создаем структуру, предоставляем сети много данных и даем ей возможность понять, правильный ли ответ она дала. Именно это мы собираемся сделать. Кроме того, вы узнаете некоторые основные методы использования PyTorch.
Но сначала данные
Для начала нам нужны данные. Сколько данных? Зависит от разных факторов. Как вы увидите в главе 4, идея о том, что для работы любой техники глубокого обучения нужны огромные объемы данных для обучения нейронной сети, не обязательно верна. Однако сейчас мы собираемся начинать с нуля, что обычно требует доступа к большому количеству данных. Требуется много изображений рыбок и кошек.
Можно было бы потратить какое-то время на загрузку кучи изображений из поиска изображений в Google, но есть более легкий путь: стандартная коллекция изображений, используемая для обучения нейронных сетей, — ImageNet. Она содержит более 14 миллионов изображений и 20 тысяч категорий изображений. Это стандарт, по которому все классификаторы изображений проводят сравнение. Поэтому я беру изображения оттуда, хотя, если хотите, то можете выбрать другие варианты.
Кроме данных у PyTorch должен быть способ определить, что такое кошка и что такое рыбка. Это достаточно просто для нас, но для компьютера задача сложнее (именно поэтому мы и создаем программу!). Мы используем маркировку, прикрепленную к данным, и такое обучение называется машинное обучение с учителем. (Если у вас нет доступа к каким-либо маркировкам, то используется, как вы наверняка догадались, машинное обучение без учителя.)
Если мы используем данные ImageNet, их маркировки не будут полезными, потому что содержат слишком много информации. Маркировка полосатого кота или форели для компьютера — не то же самое, что кот или рыбка.
Требуется перемаркировать их. Поскольку ImageNet представляет собой обширную коллекцию изображений, я собрал URL-адреса изображений и маркировки рыбок и кошек (https://oreil.ly/NbtEU).
Вы можете запустить скрипт download.py в этом каталоге, и он загрузит изображения с URL-адресов и разместит их в соответствующих местах для обучения. Перемаркировка проста; скрипт хранит изображения кошек в каталоге train/cat и изображения рыбок в каталоге train/fish. Если не хотите использовать скрипт для загрузки, просто создайте эти каталоги и разместите соответствующие изображения в нужных местах. Теперь у нас есть данные, но нужно преобразовать их в формат, понятный PyTorch.
PyTorch и загрузчики данных
Загрузка и преобразование данных в готовые к обучению форматы часто оказываются одной из областей data science, которая отнимает слишком много времени. PyTorch разработал установленные требования взаимодействия с данными, которые делают его довольно понятным, независимо от того, работаете ли вы с изображениями, текстом или аудио.
Двумя основными условиями работы с данными являются наборы данных и загрузчики данных. Набор данных — это класс Python, позволяющий получать данные, которые мы отправляем в нейронную сеть.
Загрузчик данных — это то, что передает данные из набора данных в сеть. (Может включать в себя такую информацию, как: Сколько рабочих процессов передают данные в сеть? Сколько изображений мы передаем одновременно?)
Давайте сначала посмотрим на набор данных. Каждый набор данных, независимо от того, содержит ли он изображения, аудио, текст, 3D-ландшафты, информацию о фондовом рынке или что-либо еще, может взаимодействовать с PyTorch, если отвечает требованиям этого абстрактного класса Python:
class Dataset(object):
def __getitem__(self, index):
raise NotImplementedError
def __len__(self):
raise NotImplementedError
Это довольно просто: мы должны воспользоваться методом, который возвращает размер нашего набора данных (len), и тем, который может извлечь элемент из набора данных в паре (label, tensor). Это вызывается загрузчиком данных, поскольку передает данные в нейронную сеть для обучения. Таким образом, мы должны написать тело для метода getitem, которое может взять изображение, преобразовать его в тензор и вернуть его и маркировку обратно, чтобы PyTorch мог с ним работать. Все понятно, но, очевидно, этот сценарий встречается достаточно часто, так что, может быть, PyTorch облегчит задачу?
Создание обучающего набора данных
В пакет torchvision включен класс ImageFolder, который делает практически все, при условии, что наши изображения находятся в структуре, где каждый каталог представляет собой маркировку (например, все кошки находятся в каталоге с именем cat). Вот что нужно для примера с кошками и рыбками:
import torchvision
from torchvision import transforms
train_data_path = "./train/"
transforms = transforms.Compose([
transforms.Resize(64),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225] )
])
train_data = torchvision.datasets.ImageFolder
(root=train_data_path,transform=transforms)
Здесь добавляется кое-что еще, потому что torchvision также позволяет указать список преобразований, которые будут применены к изображению, прежде чем оно попадет в нейронную сеть. Преобразование по умолчанию состоит в том, чтобы взять данные изображения и превратить их в тензор (метод trans forms.ToTensor(), показанный в предыдущем коде), но также выполняется несколько других действий, которые могут быть не такими очевидными.
Во-первых, GPU созданы для быстрого выполнения вычислений стандартного размера. Но у нас, вероятно, есть ассортимент изображений во многих разрешениях. Чтобы повысить производительность обработки, мы масштабируем каждое входящее изображение до того же разрешения 64 × 64 с помощью преобразования Resize (64). Затем конвертируем изображения в тензор и, наконец, нормализуем тензор вокруг определенного набора средних и стандартных точек отклонения.
Нормализация важна, поскольку предполагается выполнение большого количества операций умножения, когда входные данные проходят через слои нейронной сети; поддержание входящих значений от 0 до 1 предотвращает серьезное увеличение значений во время фазы обучения (известное как проблема взрывающихся градиентов). Это волшебное воплощение — всего лишь среднее и стандартное отклонение набора данных ImageNet в целом. Можно рассчитать его специально для подмножества рыбок и кошек, но эти значения достаточно надежны. (Если бы вы работали над совершенно другим набором данных, нужно было бы вычислить это среднее значение и отклонение, хотя многие просто используют константы ImageNet и сообщают о приемлемых результатах.)
Компонуемые преобразования также позволяют легко выполнять такие действия, как поворот и сдвиг изображения для аугментации данных, к которой мы вернемся в главе 4.
В этом примере мы изменяем размеры изображений до 64 × 64. Я сделал такой случайный выбор, чтобы ускорить вычисления в нашей первой сети. Большинство существующих архитектур, которые вы увидите в главе 3, используют для своих входных изображений разрешение 224 × 224 или 299 × 299.. Как правило, чем больше размер входного файла, тем больше данных, по которым сеть может обучаться.. Обратная сторона медали в том, что обычно можно разместить меньшую партию изображений в памяти GPU.
О наборах данных есть множество другой информации, и это далеко не все. Но зачем нам знать больше, чем нужно, если мы уже знаем о наборе данных для обучения?
Валидация и контрольные наборы данных
Наш набор данных для обучения настроен, но теперь нужно повторить те же шаги с набором данных для валидации. Какая здесь разница? Одним из подводных камней глубокого обучения (и фактически всего машинного обучения) является переобучение: модель действительно хорошо распознает то, на чем была обучена, но не работает на примерах, которых не видела. Модель видит изображение кота, и если все другие изображения котов не очень похожи на это, модель решает, что это не кот, хотя очевидно обратное. Чтобы нейросеть так себя не вела, мы загружаем контрольную выборку в download.py, то есть в серию изображений кошек и рыбок, которых нет в наборе данных для обучения. В конце каждого цикла обучения (также известного как эпоха) мы сравниваем этот набор, чтобы убедиться, что сеть не ошиблась. Не пугайтесь, код для этой проверки невероятно прост: это тот же код с несколькими измененными именами переменных:
val_data_path = "./val/"
val_data = torchvision.datasets.ImageFolder(root=val_data_path,
transform=transforms)
Мы просто использовали цепочку transforms вместо того, чтобы определять ее снова.
В дополнение к набору данных для валидации мы также должны создать набор данных для проверки. Он используется для тестирования модели после завершения всего обучения:
test_data_path = "./test/"
test_data = torchvision.datasets.ImageFolder(root=test_data_path,
transform=transforms)
На первый взгляд разные типы наборов могут показаться сложными и сбивать с толку, поэтому я составил таблицу, чтобы указать, в какой части обучения используется каждый набор (табл. 2.1).
![image](https://habrastorage.org/webt/ff/g-/28/ffg-28irpplax6dn8nnsyjs-dy0.png)
Теперь мы можем создать загрузчики данных с помощью еще нескольких строк кода на Python:
batch_size=64
train_data_loader = data.DataLoader(train_data, batch_size=batch_size)
val_data_loader = data.DataLoader(val_data, batch_size=batch_size)
test_data_loader = data.DataLoader(test_data, batch_size=batch_size)
Новое и достойное упоминания в этом коде — это команда batch_size. Она говорит, сколько изображений пройдет через сеть, прежде чем мы обучим и обновим ее. Теоретически мы могли бы присвоить batch_size ряду изображений в тестовом и обучающем наборах данных, чтобы сеть видела каждое изображение перед обновлением. На практике это обычно не делается, потому что меньшие пакеты (более широко известные в литературе как мини-пакеты) требуют меньше памяти и нет необходимости хранить всю информацию о каждом изображении в наборе данных, а меньший размер пакета приводит к более быстрому обучению, поскольку сеть обновляется намного быстрее. Для загрузчиков данных PyTorch значение batch_size по умолчанию установлено на 1. Скорее всего, вы захотите его изменить. Хотя я выбрал 64, вы можете поэкспериментировать, чтобы понять, сколько мини-пакетов можно использовать, не исчерпав память GPU. Поэкспериментируйте с некоторыми дополнительными параметрами: например, можно указать, как будет осуществляться выборка наборов данных, будет ли перемешиваться весь набор при каждом запуске и сколько рабочих процессов задействовано для извлечения данных из набора. Все это можно найти в документации PyTorch.
Это касается передачи данных в PyTorch, поэтому давайте теперь представим простую нейронную сеть, которая начнет классифицировать наши изображения.
И наконец, нейронная сеть!
Мы начнем с самой простой сети глубокого обучения — входного слоя, который будет работать со входными тензорами (нашими изображениями); выходного слоя размером с число наших выходных классов (2); и скрытого слоя между ними. В первом примере будем использовать полностью связанные слои. На рис. 2.3 показан входной слой из трех узлов, скрытый
слой из трех узлов и выход из двух узлов.
В этом примере каждый узел в одном слое влияет на узел в следующем слое и каждое соединение имеет вес, который определяет силу сигнала от этого узла, поступающего в следующий слой. (Именно эти веса будут обновляться, когда мы обучаем сеть, обычно из случайной инициализации.) Когда входные данные проходят через сеть, мы (или PyTorch) можем просто произвести матричное умножение весов и смещений этого слоя на входные данные. Перед передачей их в следующую функцию этот результат входит в функцию активации, которая является просто способом введения нелинейности в нашу систему.
![image](https://habrastorage.org/webt/tq/6z/cu/tq6zcu8j5jsjf9f9y2lxztbbfxs.png)
Функции активации
Функция активации — звучит мудрено, однако наиболее распространенная функция активации, которую вы сейчас можете встретить, — это ReLU, или выпрямленный линейный блок. Опять заумно! Но это всего лишь функция, которая реализует max(0,x), поэтому результат равен 0, если входное значение отрицательное, или просто входному значению (x), если x положительное. Все просто!
Другая функция активации, с которой вы, скорее всего, столкнетесь, — это многомерная логистическая функция (softmax), которая в математическом смысле немного сложнее. По сути, она генерирует набор значений от 0 до 1, который в сумме дает 1 (вероятности!), и взвешивает значения таким образом, чтобы увеличить разность, то есть выдает один результат в векторе, который будет больше всех остальных. Вы часто будете видеть, как она используется в конце сети классификации, чтобы удостовериться, что эта сеть сделает определенный прогноз о том, к какому классу, по ее мнению, относятся входные данные.
Теперь, имея все эти строительные блоки, мы можем начать создавать нашу первую нейронную сеть.
Создание нейронной сети
Создание нейронной сети в PyTorch напоминает программирование на Python. Мы наследуем от класса под названием torch.nn.Network и заполняем методы __init__ и forward:
class SimpleNet(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.fc1 = nn.Linear(12288, 84)
self.fc2 = nn.Linear(84, 50)
self.fc3 = nn.Linear(50,2)
def forward(self):
x = x.view(-1, 12288)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = F.softmax(self.fc3(x))
return x
simplenet = SimpleNet()
Повторюсь, это несложно. Мы делаем необходимые настройки в init(), в этом случае вызываем конструктор суперкласса и три полносвязных слоя (называемых в PyTorch Linear, в Keras они называются Dense). Метод forward() описывает, как данные передаются по сети как при обучении, так и при предсказании (inference). Во-первых, мы должны преобразовать трехмерный тензор (x и y плюс трехканальная цветовая информация — красный, зеленый, синий) в изображении — внимание! — в одномерный тензор, чтобы его можно было передавать в первый слой Linear, и мы делаем это с помощью view(). Таким образом мы применяем слои и функции активации по порядку, возвращая выходные данные softmax, чтобы получить прогноз для этого изображения.
Числа в скрытых слоях являются произвольными, за исключением выходных данных последнего слоя, который равен 2, совпадающих с нашими двумя классами — кошки или рыбки. Требуется, чтобы данные в слоях сжимались по мере их уменьшения в стеке. Если в слой идет, скажем, от 50 входных данных на 100 выходных данных, то сеть может обучиться, просто передав 50 связей на пятьдесят из ста выходных данных, и считать свою работу выполненной. Сокращая размер выходных данных по отношению ко входным данным, мы заставляем эту часть сети выучить репрезентативность исходных входных данных с меньшим количеством ресурсов, что предположительно означает, что сеть определяет некоторые отличительные признаки изображений: например, научилась распознавать плавник или хвост.
У нас есть прогноз, и можно сравнить его с фактической маркировкой исходного изображения, чтобы увидеть, был ли он правильный. Но нужен какой-то способ, позволяющий PyTorch количественно определять не только правильность или неправильность прогноза, но и то, насколько он верный или неверный. Этим занимается функция потерь.
ОБ АВТОРЕ
Ян Пойнтер (Ian Pointer) — инженер data science, специализирующийся на решениях для машинного обучения (включая методы глубокого обучения) для нескольких клиентов из списка Fortune 100. В настоящее время Ян работает в Lucidworks, где занимается передовыми приложениями и разработкой NLP.
» Более подробно с книгой можно ознакомиться на сайте издательства
» Оглавление
» Отрывок
Для Хаброжителей скидка 25% по купону — PyTorch
По факту оплаты бумажной версии книги на e-mail высылается электронная книга.