Рубрики
Без рубрики

Классификация изображений с передачей обучения и высоты тона

Автор оригинала: Dan Nelson.

Классификация изображений с передачей обучения и высоты тона

Вступление

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

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

В этой статье мы рассмотрим теорию трансфертного обучения и посмотрим, как выполнить пример трансфертного обучения на сверточных нейронных сетях (CNN) в PyTorch.

  • Что такое PyTorch?
  • Определение Необходимых Терминов
  • Теория Трансфертного обучения
  • Классификация изображений с обучением передаче в Python
  • Вывод

Что такое PyTorch?

Pytorch – это библиотека, разработанная для Python, специализирующаяся на глубоком обучении и обработке естественного языка . Pitch использует преимущества мощности графических процессоров (GPU), чтобы сделать реализацию глубокой нейронной сети более быстрой, чем обучение сети на процессоре.

PyTorch стал все более популярным среди исследователей глубокого обучения благодаря своей скорости и гибкости. PyTorch продает себя на трех различных функциях:

  • Простой и удобный в использовании интерфейс
  • Полная интеграция со стеком Python data science stack
  • Гибкие/динамические вычислительные графики, которые могут быть изменены во время выполнения (что значительно облегчает обучение нейронной сети, когда вы понятия не имеете, сколько памяти потребуется для вашей задачи).

Шаг совместим с NumPy и позволяет преобразовывать массивы NumPy в тензоры и наоборот.

Определение Необходимых Терминов

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

Что такое Глубокое обучение?

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

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

Нейронные сети имеют три различных компонента: входной слой , скрытый слой или средний слой и выходной слой .

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

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

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

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

Что такое Сверточная нейронная сеть?

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

Сверточные нейронные сети (CNN) – это особые типы нейронных сетей, искусно создающие представления визуальных данных. Данные в CNN представлены в виде сетки , которая содержит значения, представляющие, насколько ярким и какого цвета является каждый пиксель изображения.

CNN разбивается на три различных компонента : сверточные слои , объединяющие слои и полностью связанные слои .

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

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

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

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

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

Существуют различные функции, которые можно использовать для суммирования значений региона, такие как среднее значение по соседству или Среднее объединение. Можно также взять средневзвешенное значение окрестности, а также норму L2 региона. Наиболее распространенным методом объединения является Max Pooling , где берется максимальное значение области и используется для представления окрестности.

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

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

Существует множество различных нелинейных активационных функций, которые могут быть использованы для того, чтобы сеть могла правильно интерпретировать данные изображения. Наиболее популярной нелинейной активационной функцией является ReLU, или Выпрямленная линейная единица|/. Функция ReLU превращает нелинейные входные данные в линейное представление, сжимая реальные значения только до положительных значений выше 0. Другими словами, функция ReLU принимает любое значение выше нуля и возвращает его как есть, в то время как если значение ниже нуля, оно возвращается как ноль.

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

Две другие популярные нелинейные функции-это функция sigmoid и функция Tanh .

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

Между тем, функция Tanh работает аналогично сигмоиде, за исключением того, что ее выход центрирован около нуля и сжимает значения между -1 и 1.

Обучение и тестирование

Существует два различных этапа создания и реализации глубокой нейронной сети: обучение и тестирование .

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

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

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

Что такое Трансфертное обучение?

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

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

Теория Трансфертного обучения

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

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

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

Существует большое разнообразие предварительно обученных моделей, которые могут быть использованы в PyTorch. Некоторые из предварительно подготовленных CNN включают в себя:

  • AlexNet
  • CaffeResNet
  • Начало
  • Серия ResNet
  • Серия VGG

Эти предварительно обученные модели доступны через API PyTorch, и после получения инструкций PyTorch загрузит их спецификации на вашу машину. Конкретная модель , которую мы будем использовать, – это ResNet34 , часть серии Resnet.

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

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

Как объясняет документация PyTorch по transfer learning , существует два основных способа использования transfer learning: тонкая настройка CNN или использование CNN в качестве экстрактора фиксированных функций.

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

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

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

Работа с моделями обучения переносу в Pitch означает выбор того, какие слои замораживать , а какие размораживать . Замораживание модели означает указание PyTorch сохранить параметры (веса) в указанных вами слоях. Разморозить модель означает сказать PyTorch, что вы хотите, чтобы слои, которые вы указали, были доступны для обучения, чтобы их веса были обучаемыми.

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

Классификация изображений с обучением передаче в Python

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

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

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

Обязательно разделите набор данных на два набора одинакового размера: “train” и “val”.

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

Вот один из способов подготовки данных к использованию:

import os
import shutil
import re

base_dir = "PetImages/"

# Create training folder
files = os.listdir(base_dir)

# Moves all training cat images to cats folder, training dog images to dogs folder
def train_maker(name):
  train_dir = f"{base_dir}/train/{name}"
  for f in files:
        search_object = re.search(name, f)
        if search_object:
          shutil.move(f'{base_dir}/{name}', train_dir)

train_maker("Cat")
train_maker("Dog")

# Make the validation directories
try:
    os.makedirs("val/Cat")
    os.makedirs("val/Dog")
except OSError:
    print ("Creation of the directory %s failed")
else:
    print ("Successfully created the directory %s ")

# Create validation folder

cat_train = base_dir + "train/Cat/"
cat_val = base_dir + "val/Cat/"
dog_train = base_dir + "train/Dog/"
dog_val = base_dir + "val/Dog/"

cat_files = os.listdir(cat_train)
dog_files = os.listdir(dog_train)

# This will put 1000 images from the two training folders
# into their respective validation folders

for f in cat_files:
    validationCatsSearchObj = re.search("5\d\d\d", f)
    if validationCatsSearchObj:
        shutil.move(f'{cat_train}/{f}', cat_val)

for f in dog_files:
    validationCatsSearchObj = re.search("5\d\d\d", f)
    if validationCatsSearchObj:
        shutil.move(f'{dog_train}/{f}', dog_val)

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

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

Нам нужен numpy для обработки создания массивов данных, а также нескольких других различных модулей:

from __future__ import print_function, division

import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim import lr_scheduler
import torchvision
from torchvision import datasets, models, transforms
import matplotlib.pyplot as plt
import numpy as np
import time
import os
import copy

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

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

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

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

# Make transforms and use data loaders

# We'll use these a lot, so make them variables
mean_nums = [0.485, 0.456, 0.406]
std_nums = [0.229, 0.224, 0.225]

chosen_transforms = {'train': transforms.Compose([
        transforms.RandomResizedCrop(size=256),
        transforms.RandomRotation(degrees=15),
        transforms.RandomHorizontalFlip(),
        transforms.ToTensor(),
        transforms.Normalize(mean_nums, std_nums)
]), 'val': transforms.Compose([
        transforms.Resize(256),
        transforms.CenterCrop(224),
        transforms.ToTensor(),
        transforms.Normalize(mean_nums, std_nums)
]),
}

Теперь мы установим каталог для ваших данных и используем функцию PyTorch Image Folder для создания наборов данных:

# Set the directory for the data
data_dir = '/data/'

# Use the image folder function to create datasets
chosen_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x),
  chosen_transforms[x])
                  for x in ['train', 'val']}

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

# Make iterables with the dataloaders
dataloaders = {x: torch.utils.data.DataLoader(chosen_datasets[x], batch_size=4,
  shuffle=True, num_workers=4)
              for x in ['train', 'val']}

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

dataset_sizes = {x: len(chosen_datasets[x]) for x in ['train', 'val']}
class_names = chosen_datasets['train'].classes

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

Теперь давайте попробуем визуализировать некоторые из наших изображений с помощью функции. Мы возьмем входные данные, создадим из них массив Numpy и транспонируем его. Затем мы нормализуем входные данные, используя среднее и стандартное отклонение. Наконец, мы обрезаем значения в диапазоне от 0 до 1, чтобы не было большого диапазона возможных значений массива, а затем показываем изображение:

# Visualize some images
def imshow(inp, title=None):
    inp = inp.numpy().transpose((1, 2, 0))
    mean = np.array([mean_nums])
    std = np.array([std_nums])
    inp = std * inp + mean
    inp = np.clip(inp, 0, 1)
    plt.imshow(inp)
    if title is not None:
        plt.title(title)
    plt.pause(0.001)  # Pause a bit so that plots are updated

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

# Grab some of the training data to visualize
inputs, classes = next(iter(dataloaders['train']))

# Now we construct a grid from batch
out = torchvision.utils.make_grid(inputs)

imshow(out, title=[class_names[x] for x in classes])

Настройка предварительно подготовленной модели

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

При использовании предварительно обученных моделей PyTorch по умолчанию устанавливает модель размороженной (ее вес будет скорректирован). Так что мы будем тренировать всю модель:

# Setting up the model
# load in pretrained and reset final fully connected

res_mod = models.resnet34(pretrained=True)

num_ftrs = res_mod.fc.in_features
res_mod.fc = nn.Linear(num_ftrs, 2)

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

for name, child in res_mod.named_children():
    print(name)

Вот что это возвращает:

conv1
bn1
relu
maxpool
layer1
layer2
layer3
layer4
avgpool
fc

Обратите внимание , что последняя часть-это fc , или “Полностью подключенный”. Это единственный слой, который мы изменяем форму, давая ему наши два класса для вывода.

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

Теперь нам нужно отправить нашу модель на наше тренировочное устройство. Нам также нужно выбрать критерий потерь и оптимизатор, который мы хотим использовать с моделью. Cross Entropy Loss и оптимизатор SGD – хороший выбор, хотя есть и много других.

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

res_mod = res_mod.to(device)
criterion = nn.CrossEntropyLoss()

# Observe that all parameters are being optimized
optimizer_ft = optim.SGD(res_mod.parameters(), lr=0.001, momentum=0.9)

# Decay LR by a factor of 0.1 every 7 epochs
exp_lr_scheduler = lr_scheduler.StepLR(optimizer_ft, step_size=7, gamma=0.1)

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

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

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

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

  1. Уменьшите скорость обучения
  2. Обнуление градиентов
  3. Выполните тренировочный проход вперед
  4. Подсчитайте потери
  5. Выполните обратное распространение и обновите веса с помощью оптимизатора

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

def train_model(model, criterion, optimizer, scheduler, num_epochs=10):
    since = time.time()

    best_model_wts = copy.deepcopy(model.state_dict())
    best_acc = 0.0

    for epoch in range(num_epochs):
        print('Epoch {}/{}'.format(epoch, num_epochs - 1))
        print('-' * 10)

        # Each epoch has a training and validation phase
        for phase in ['train', 'val']:
            if phase == 'train':
                scheduler.step()
                model.train()  # Set model to training mode
            else:
                model.eval()   # Set model to evaluate mode

            current_loss = 0.0
            current_corrects = 0

            # Here's where the training happens
            print('Iterating through data...')

            for inputs, labels in dataloaders[phase]:
                inputs = inputs.to(device)
                labels = labels.to(device)

                # We need to zero the gradients, don't forget it
                optimizer.zero_grad()

                # Time to carry out the forward training poss
                # We only need to log the loss stats if we are in training phase
                with torch.set_grad_enabled(phase == 'train'):
                    outputs = model(inputs)
                    _, preds = torch.max(outputs, 1)
                    loss = criterion(outputs, labels)

                    # backward + optimize only if in training phase
                    if phase == 'train':
                        loss.backward()
                        optimizer.step()

                # We want variables to hold the loss statistics
                current_loss += loss.item() * inputs.size(0)
                current_corrects += torch.sum(preds == labels.data)

            epoch_loss = current_loss / dataset_sizes[phase]
            epoch_acc = current_corrects.double() / dataset_sizes[phase]

            print('{} Loss: {:.4f} Acc: {:.4f}'.format(
                phase, epoch_loss, epoch_acc))

            # Make a copy of the model if the accuracy on the validation set has improved
            if phase == 'val' and epoch_acc > best_acc:
                best_acc = epoch_acc
                best_model_wts = copy.deepcopy(model.state_dict())

        print()

    time_since = time.time() - since
    print('Training complete in {:.0f}m {:.0f}s'.format(
        time_since // 60, time_since % 60))
    print('Best val Acc: {:4f}'.format(best_acc))

    # Now we'll load in the best model weights and return it
    model.load_state_dict(best_model_wts)
    return model

Наши тренировочные распечатки должны выглядеть примерно так:

Epoch 0/25
----------
Iterating through data...
train Loss: 0.5654 Acc: 0.7090
Iterating through data...
val Loss: 0.2726 Acc: 0.8889

Epoch 1/25
----------
Iterating through data...
train Loss: 0.5975 Acc: 0.7090
Iterating through data...
val Loss: 0.2793 Acc: 0.8889

Epoch 2/25
----------
Iterating through data...
train Loss: 0.5919 Acc: 0.7664
Iterating through data...
val Loss: 0.3992 Acc: 0.8627

Визуализация

Теперь мы создадим функцию, которая позволит нам увидеть предсказания, сделанные нашей моделью.

def visualize_model(model, num_images=6):
    was_training = model.training
    model.eval()
    images_handeled = 0
    fig = plt.figure()

    with torch.no_grad():
        for i, (inputs, labels) in enumerate(dataloaders['val']):
            inputs = inputs.to(device)
            labels = labels.to(device)

            outputs = model(inputs)
            _, preds = torch.max(outputs, 1)

            for j in range(inputs.size()[0]):
                images_handeled += 1
                ax = plt.subplot(num_images//2, 2, images_handeled)
                ax.axis('off')
                ax.set_title('predicted: {}'.format(class_names[preds[j]]))
                imshow(inputs.cpu().data[j])

                if images_handeled == num_images:
                    model.train(mode=was_training)
                    return
        model.train(mode=was_training)

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

base_model = train_model(res_mod, criterion, optimizer_ft, exp_lr_scheduler, num_epochs=3)
visualize_model(base_model)
plt.show()

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

Экстрактор фиксированных функций

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

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

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

# Setting up the model
# Note that the parameters of imported models are set to requires_grad=True by default

res_mod = models.resnet34(pretrained=True)
for param in res_mod.parameters():
    param.requires_grad = False

num_ftrs = res_mod.fc.in_features
res_mod.fc = nn.Linear(num_ftrs, 2)

res_mod = res_mod.to(device)
criterion = nn.CrossEntropyLoss()

# Here's another change: instead of all parameters being optimized
# only the params of the final layers are being optimized

optimizer_ft = optim.SGD(res_mod.fc.parameters(), lr=0.001, momentum=0.9)

exp_lr_scheduler = lr_scheduler.StepLR(optimizer_ft, step_size=7, gamma=0.1)

Что, если мы хотим выборочно разморозить слои и вычислить градиенты только для нескольких выбранных слоев? Разве это возможно? Да, это так.

Давайте снова распечатаем дочерние элементы модели, чтобы вспомнить, какие слои/компоненты она имеет:

for name, child in res_mod.named_children():
    print(name)

Вот эти слои:

conv1
bn1
relu
maxpool
layer1
layer2
layer3
layer4
avgpool
fc

Теперь, когда мы знаем, что это за слои, мы можем разморозить те, которые нам нужны, например, слои 3 и 4:

for name, child in res_mod.named_children():
    if name in ['layer3', 'layer4']:
        print(name + 'has been unfrozen.')
        for param in child.parameters():
            param.requires_grad = True
    else:
        for param in child.parameters():
            param.requires_grad = False

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

optimizer_conv = torch.optim.SGD(filter(lambda x: x.requires_grad, res_mod.parameters()), lr=0.001, momentum=0.9)

Итак, теперь вы знаете, что можете настроить всю сеть, только последний слой или что-то среднее.

Вывод

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

Вот еще несколько вещей, которые вы можете попробовать:

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

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

Код для этой статьи можно найти в этом репо GitHub .