Трансферное обучение (Transfer Learning) в Python – это мощный метод обучения глубоких нейронных сетей, который позволяет использовать знания, полученные об одной проблеме глубокого обучения, и применять их к другой, но со схожей задачей.
Использование трансферного обучения может значительно ускорить развертывание разрабатываемого вами приложения, делая как обучение, так и внедрение вашей глубокой нейронной сети проще.
В этой статье мы рассмотрим теорию, лежащую в основе трансферного обучения, и посмотрим, как реализовать пример в сверточных нейронных сетях (CNN) в PyTorch.
Что такое PyTorch?
Pytorch – это библиотека, разработанная для Python, специализирующаяся на глубоком обучении и обработке естественного языка. PyTorch использует преимущества графических процессоров (GPU), чтобы реализовать глубокую нейронную сеть быстрее, чем обучение сети на ЦП.
PyTorch пользуется все большей популярностью среди исследователей глубокого обучения благодаря своей скорости и гибкости, и предлагает три функции:
- Простой и удобный интерфейс.
- Полная интеграция со стеком науки о данных Python.
- Гибкие и динамические вычислительные графики, которые можно изменять во время выполнения (что значительно упрощает обучение нейронной сети, когда вы не знаете, сколько памяти потребуется для вашей задачи).
PyTorch совместим с NumPy и позволяет преобразовывать массивы NumPy в тензоры и наоборот.
Определение необходимых терминов
Прежде чем мы пойдем дальше, давайте определим некоторые термины, относящиеся к трансферному обучению. Четкое понимание наших определений упростит понимание теории, лежащей в основе трансферного обучения, и реализацию экземпляра.
Что такое глубокое обучение?
Глубокое обучение – это подраздел машинного обучения, которое можно описать как просто действие, позволяющее компьютерам выполнять задачи, не будучи явно запрограммированными на это.
В системах глубокого обучения используются нейронные сети, которые представляют собой вычислительные структуры, смоделированные по образцу человеческого мозга.
Нейронные сети состоят из трех различных компонентов: входного слоя, скрытого или среднего уровня и выходного уровня.
На входном уровне просто обрабатываются данные, отправляемые в нейронную сеть, в то время как промежуточные и скрытые слои состоят из структуры, называемой узлом или нейроном.
Эти узлы представляют собой математические функции, которые каким-либо образом изменяют входную информацию и передают измененные данные на последний или выходной уровень. Простые нейронные сети могут различать простые шаблоны во входных данных путем корректировки предположений или параметров о том, как точки данных связаны друг с другом.
Глубокая нейронная сеть получила свое название от того факта, что она состоит из множества обычных нейронных сетей, соединенных вместе. Чем больше нейронных сетей связаны друг с другом, тем более сложные шаблоны может различать глубокая нейронная сеть и тем больше у нее возможностей. Существуют разные виды нейронных сетей, каждый из которых имеет свою специализацию.
Например, глубокие нейронные сети с долгосрочной краткосрочной памятью – это сети, которые очень хорошо работают при обработке чувствительных ко времени задач, где важен хронологический порядок данных, таких как текстовые или речевые данные.
Что такое сверточная нейронная сеть?
Эта статья будет посвящена сверточным нейронным сетям, типу нейронной сети, которая отлично справляется с манипулированием данными изображений.
Сверточные нейронные сети (CNN) – это особые типы нейронных сетей, способные создавать представления визуальных данных. Данные в CNN представлены в виде сетки, которая содержит значения, которые представляют, насколько ярким и какого цвета является каждый пиксель изображения.
CNN разбита на три различных компонента: сверточные слои, объединяющие слои и полносвязные слои.
Сверточный слой отвечает за создание представления изображения путем скалярного произведения двух матриц.
Первая матрица – это набор обучаемых параметров, называемых ядром. Другая матрица – это часть анализируемого изображения, которая будет иметь высоту, ширину и цветовые каналы. Сверточные слои – это то место, где в CNN происходит больше всего вычислений. Ядро перемещается по всей ширине и высоте изображения, в конечном итоге создавая представление всего изображения, которое является двухмерным, известное как карта активации.
Из-за огромного количества информации, содержащейся в сверточных слоях CNN, обучение сети может занять очень много времени. Функция позволяет уменьшить количество информации, содержащейся в сверточных слоях CNN, принимая вывод одного сверточного слоя и уменьшая его масштаб, чтобы упростить представление.
Слой объединения выполняет это, просматривая разные точки в выходных данных сети и «объединяя» соседние значения, получая одно значение, которое представляет все соседние значения. Другими словами, требуется сводная статистика значений в выбранном регионе.
Суммирование значений в области означает, что сеть может значительно уменьшить размер и сложность своего представления, сохраняя при этом релевантную информацию, которая позволит сети распознавать эту информацию и извлекать значимые шаблоны из изображения.
Существуют различные функции, которые можно использовать для суммирования значений региона, например, получение среднего значения по соседству или среднего пула. Также может быть взято средневзвешенное значение окрестности, равно как и норма L2 региона. Наиболее распространенным методом объединения является метод максимального объединения, при котором берется максимальное значение региона и используется для представления соседства.
Полностью связанный слой – это то место, где все нейроны связаны друг с другом, с подключениями между каждым предыдущим и последующим слоями в сети. Здесь анализируется информация, которая была извлечена сверточными слоями и объединена слоями, где изучаются закономерности в данных. Вычисления здесь выполняются посредством умножения матриц в сочетании с эффектом смещения.
В CNN также присутствует несколько нелинейностей. Учитывая, что изображения сами по себе являются нелинейными объектами, сеть должна иметь нелинейные компоненты, чтобы иметь возможность интерпретировать данные изображения. Нелинейные слои обычно вставляются в сеть сразу после сверточных слоев, так как это придает карте активации нелинейность.
Существует множество различных функций нелинейной активации, которые можно использовать для того, чтобы сеть могла правильно интерпретировать данные изображения. Самая популярная функция – это ReLu или выпрямленный линейный блок.
Функция ReLu превращает нелинейные входные данные в линейное представление путем сжатия реальных значений только до положительных значений выше 0. Другими словами, функция ReLu принимает любое значение выше нуля и возвращает его как есть, в то время как, если значение ниже нуля, оно возвращается как ноль.
Функция ReLu популярна из-за ее надежности и скорости, выполняя ее примерно в шесть раз быстрее, чем другие функции активации. Обратной стороной ReLu является то, что он может легко застрять при работе с большими градиентами, никогда не обновляя нейроны. Эту проблему можно решить, установив скорость обучения для функции.
Двумя другими популярными нелинейными функциями являются сигмоидальная функция и функция Tanh.
Сигмоидная функция работает, принимая реальные значения и сжимая их до диапазона от 0 до 1, хотя у нее есть проблемы с обработкой активаций, близких к крайним значениям градиента, поскольку значения становятся почти нулевыми.
Между тем, функция Tanh работает аналогично сигмоиде, за исключением того, что ее выход центрируется около нуля, а значения сжимаются до значений от -1 до 1.
Обучение и тестирование
Есть два разных этапа создания и реализации глубокой нейронной сети: обучение и тестирование.
На этапе обучения в сеть поступают данные, и она начинает изучать шаблоны, которые содержат данные, регулируя параметры сети, которые являются предположениями о том, как точки данных связаны друг с другом. Другими словами, на этапе обучения сеть «узнает» о том, какие данные были переданы.
На этапе тестирования оценивается то, что узнала сеть. Сети предоставляется новый набор данных, который она не видела раньше, а затем сеть просят применить свои предположения о паттернах, которые она усвоила, к новым данным. Оценивается точность модели, и обычно модель настраивается и переобучается, а затем повторно тестируется, пока архитектор не будет удовлетворен производительностью.
В случае трансферного обучения используемая сеть была предварительно обучена. Параметры сети уже настроены и сохранены, поэтому нет причин заново обучать всю сеть с нуля. Это означает, что сеть может быть немедленно использована для тестирования или только определенные уровни сети могут быть настроены и затем повторно обучены. Это значительно ускоряет развертывание глубокой нейронной сети.
Что такое трансферное обучение?
Идея трансферного обучения состоит в том, чтобы взять модель, обученную одной задаче, и применить ее ко второй, аналогичной задаче. Тот факт, что модель уже имеет некоторые или все данные для второй обученной задачи, означает, что она может быть реализована намного быстрее. Это позволяет быстро оценивать производительность и настраивать модель, обеспечивая более быстрое развертывание в целом.
Трансферное обучение становится все более популярным в области глубокого обучения благодаря огромному количеству вычислительных ресурсов и времени, необходимых для обучения моделей глубокого обучения в дополнение к большим и сложным наборам данных.
Основным ограничением трансферного обучения является то, что особенности модели, изученные во время первой задачи, являются общими, а не специфичными для первой. На практике это означает, что модели, обученные распознавать определенные типы изображений, могут быть повторно использованы для распознавания других изображений, если общие характеристики изображений аналогичны.
Теория трансферного обучения
Использование трансферного обучения имеет несколько важных концепций. Чтобы понять реализацию, нам нужно рассмотреть, как выглядит предварительно обученная модель и как ее можно настроить для ваших нужд.
Выбрать модель для трансферного обучения можно двумя способами. Можно создать модель с нуля для собственных нужд, сохранить параметры и структуру модели, а затем повторно использовать ее позже.
Второй способ реализовать трансферное обучение – просто взять уже существующую модель и повторно использовать ее, настраивая при этом ее параметры и гиперпараметры. В этом случае мы будем использовать предварительно обученную модель и модифицировать ее. После того, как вы решили, какой подход вы хотите использовать, выберите модель (если вы используете предварительно обученную модель).
В PyTorch можно использовать множество предварительно обученных моделей. Некоторые из CNN включают:
- AlexNet;
- CaffeResNet;
- Inception;
- Серия ResNet;
- Серия VGG.
Эти предварительно обученные модели доступны через API PyTorch, и после получения инструкций PyTorch загрузит их спецификации на ваш компьютер. Конкретная модель, которую мы собираемся использовать, – это ResNet34, часть серии Resnet.
Модель Resnet была разработана и обучена на наборе данных ImageNet, а также на наборе данных CIFAR-10. Таким образом, он оптимизирован для задач визуального распознавания и показал заметное улучшение по сравнению с серией VGG, поэтому мы будем его использовать.
Однако существуют и другие предварительно обученные модели, и вы можете поэкспериментировать с ними, чтобы увидеть, как они сравниваются.
Как объясняется в документации PyTorch по трансферному обучению, существует два основных способа использования: точная настройка или использование CNN в качестве средства извлечения фиксированных функций.
При тонкой настройке CNN вы используете параметры, которые имеет предварительно обученная сеть, вместо их случайной инициализации, а затем тренируетесь как обычно. Напротив, подход экстрактора признаков означает, что вы будете поддерживать все параметры CNN, за исключением тех, которые находятся на последних нескольких уровнях, которые будут инициализированы случайным образом и обучены как обычно.
Точная настройка модели важна, потому что, хотя модель была предварительно обучена другой (хотя, надеюсь, схожей) задаче. Плотно связанных параметров, с которыми поставляется предварительно обученная модель, вероятно, будет недостаточно, поэтому вы, вероятно, захотите переобучить последние несколько слоев сети.
Напротив, поскольку первые несколько слоев сети – это просто слои выделения признаков, и они будут работать аналогично с похожими изображениями, их можно оставить как есть. Следовательно, если набор данных небольшой и похожий, единственное обучение, которое необходимо выполнить, – это обучить нескольких последних слоев. Чем больше и сложнее будет набор данных, тем больше потребуется переобучения модели.
Помните, что трансферное обучение работает лучше всего, когда набор данных, который вы используете, меньше, чем исходная предварительно обученная модель, и похож на изображения, подаваемые в предварительно обученную модель.
Работа с моделями обучения передачи в Pytorch означает выбор слоев, которые нужно заморозить и разморозить. Замораживание модели означает указание PyTorch сохранить параметры в указанных вами слоях. Размораживание модели означает сообщение PyTorch о том, что вы хотите, чтобы указанные вами слои были доступны для обучения.
После того, как вы завершили обучение выбранных вами слоев предварительно обученной модели, вы, вероятно, захотите сохранить параметры для использования в будущем. Несмотря на то, что использование предварительно обученных моделей происходит быстрее, чем обучение модели с нуля, обучение по-прежнему требует времени, поэтому вы захотите скопировать лучшие параметры модели.
Классификация изображений
Мы готовы приступить к реализации трансферного обучения для набора данных. Мы рассмотрим как тонкую настройку ConvNet, так и использование сети в качестве средства извлечения фиксированных функций.
Предварительная обработка данных
Прежде всего, нам нужно выбрать набор данных для использования. Давайте выберем что-нибудь, на чем можно будет тренироваться с большим количеством действительно четких изображений. Набор данных Stanford Cats and Dogs – это очень часто используемый набор данных.
Обязательно разделите набор данных на два набора одинакового размера: «train» и «val».
Вы можете сделать это в любом случае, вручную переместив файлы или написав функцию для его обработки. Вы также можете ограничить набор данных меньшим размером, так как он содержит почти 12000 изображений в каждой категории, и это займет много времени для обучения. Вы можете сократить это число примерно до 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. Нам нужно убедиться, что изображения в обучающем наборе и наборе проверки имеют одинаковый размер, поэтому мы будем использовать преобразования.
Мы также будем немного увеличивать данные, пытаясь улучшить производительность нашей модели, заставляя ее узнавать об изображениях под разными углами и обрезками, поэтому мы будем произвольно обрезать и вращать изображения.
Далее мы сделаем тензоры из изображений, поскольку 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 ImageFolder для создания наборов данных:
# 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']}
Теперь, когда мы выбрали нужные папки изображений, нам нужно использовать DataLoaders для создания повторяемых объектов, с которыми мы будем работать. Мы сообщаем ему, какие наборы данных хотим использовать, задаем размер пакета и перемешиваем данные.
# 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
Теперь давайте воспользуемся этой функцией и фактически визуализируем некоторые данные. Мы собираемся получить входные данные и имя классов из DataLoader и сохранить их для дальнейшего использования. Затем мы создадим сетку для отображения входов и их отображения:
# 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
По сути, мы собираемся изменить выходы последней полностью связанной части всего на два класса и скорректировать параметры для всех остальных слоев.
Теперь нам нужно отправить нашу модель на обучающее устройство. Нам также нужно выбрать критерий потерь и оптимизатор, который мы хотим использовать с моделью. CrossEntropyLoss и оптимизатор 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.
Теперь для каждой эпохи в выбранном количестве эпох, если мы находимся на этапе обучения, мы будем:
- Уменьшать скорость обучения.
- Обнулять градиенты.
- Выполнять передовой тренировочный проход.
- Рассчитывать убыток.
- Выполнять обратное распространение и обновление параметров с помощью оптимизатора.
Мы также будем отслеживать точность модели на этапе обучения, и если мы перейдем к этапу проверки и точность улучшится, мы сохраним текущие параметры как лучшие:
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.
Замените раздел, в котором определена предварительно обученная модель, версией, которая фиксирует параметры и не содержит наших вычислений градиента или обратного распространения.
Он выглядит очень похоже на предыдущий, за исключением того, что мы указываем, что градиенты не нуждаются в вычислении:
# 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)
Итак, теперь вы знаете, что можете настроить всю сеть, только последний уровень или что-то среднее.
Заключение
Мы реализовали трансферное обучение в PyTorch. Было бы неплохо сравнить реализацию настроенной сети с использованием экстрактора фиксированных функций, чтобы увидеть, как отличается производительность.
Также рекомендуется поэкспериментировать с замораживанием и размораживанием определенных слоев, поскольку это позволяет вам лучше понять, как вы можете настроить модель в соответствии со своими потребностями.