Использование ResNet моделей. Связь ResNet с Dropout и бустингом

Смотреть материал на YouTube | RuTube

Сети ResNet завоевали широкую популярность, и мы с вами уже видели, что некоторые из этих моделей доступны во фреймворке PyTroch. Их можно найти в ветке:

torchvision.models

которую импортируем следующим образом:

torchvision import models

В частности здесь представлены сети архитектур:

  • models.resnet18;
  • models.resnet34;
  • models.resnet50;
  • models.resnet101;
  • models.resnet152.

Для примера создадим модель ResNet50. В самом простом варианте достаточно прописать команду:

model = models.resnet50()

Будет создана нейронная сеть со случайными весовыми коэффициентами, то есть, необученная. Выведем на экран ее структуру:

print(model)

Увидим наборы слоев и Bottleneck блоков, о которых подробно говорили на предыдущих занятиях. В конце идет линейный слой с 1000 выходами – числом классов, на которые делятся подаваемые входные изображения. Архитектура этой сети была заточена под конкурс ImageNet, где нужно выполнять классификацию полноцветных изображений на 1000 классов. При необходимости можно использовать обученную сеть по базе ImageNet. Для этого (так же, как и для сетей VGG) достаточно прописать дополнительный параметр weights одним из следующих способов:

model = models.resnet50(weights=models.ResNet50_Weights.DEFAULT)
model = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V1)
model = models.resnet50(weights='DEFAULT')
model = models.resnet50(weights='IMAGENET1K_V1')

Все эти команды будут давать один и тот же результат, поэтому применяется тот вариант, который удобнее. Также отдельно можно воспользоваться объектом:

resnet_weights = models.ResNet50_Weights.DEFAULT

который, затем, можно указывать в параметре weights:

model = models.resnet50(weights=resnet_weights)

Что нам это дает вы уже знаете из рассмотрения сетей VGG. Объект resnet_weights содержит довольно полезную информацию о классах в словаре:

cats = resnet_weights.meta['categories']

и применяемые трансформации к входному сигналу:

transforms = resnet_weights.transforms()

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

И пропустим через обученную сеть ResNet50. Для этого загрузим изображение:

img = Image.open('horse_1.jpg').convert('RGB')

Применим к нему трансформации сети ResNet50:

img = transforms(img).unsqueeze(0)

Напомню, что метод unsqueeze(0) добавляет первую ось, чтобы получить тензор формата:

(1, 3, 224, 224)

Здесь первая размерность – это число наблюдений в пакете (batch), в данном случае одно изображение.

Пропускаем тензор img через сеть ResNet50:

model.eval()
p = model(img_net).squeeze() # (1000)

Обратите внимание на обязательный вызов метода eval, который переводит модель в режим эксплуатации. Если этого не сделать, то сеть будет формировать не совсем корректные результаты. А метод squeeze удаляет первую ось, которую мы добавляли ранее методом unsqueeze.

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

res = p.softmax(dim=0).sort(descending=True)

Выведем пять наиболее вероятных значений следующим образом:

for s, i in zip(res[0][:5], res[1][:5]):
    print(f"{cats[i]}: {s:.4f}")

После выполнения программы получим результат:

sorrel: 0.3327
collie: 0.0037
hartebeest: 0.0027
muzzle: 0.0019
Shetland sheepdog: 0.0018

Не очень уверенно, но с наибольшей вероятностью выдается класс sorrel, который можно перевести, как «щавель» или «гнедая лошадь». Для примера возьмем еще одно изображение:

Получим результат:

sports car: 0.3674
racer: 0.0341
convertible: 0.0245
car wheel: 0.0169
beach wagon: 0.0050

Класс «sports car» уже не вызывает никаких сомнений – сеть верно классифицировала входное изображение.

Развертка сетей ResNet в ширину

Давайте вернемся к общей архитектуре сетей ResNet и внимательнее посмотрим на пути распространения сигнала от входа к выходу. Исходную сеть, условно, можно представить в виде последовательно соединенных блоков с обходными связями (skip connections):

Однако эту же структуру можно разложить на несколько независимых информационных потоков:

Очевидно, сигнал с выхода первого блока может напрямую поступать на вход пятого блока. Или же проходить через второй блок сразу на пятый. Или же идти непосредственно на третий, либо через второй блок, а затем, сразу на пятый. Наконец, сигнал может следовать с учетом четвертого блока. Получаем множество возможных комбинаций прохождения входного сигнала по сети с обходными связями. Это, своего рода, развертка нейронной сети ResNet в ширину. Ее можно интерпретировать, как набор нескольких сетей, соединенных параллельно в единый ансамбль. Ровно так же можно воспринимать работу алгоритма Dropout, который мы с вами ранее подробно рассматривали. Из-за того, что Dropout отключает случайным образом часть нейронов, получаем многочисленные вариации урезанных сетей, которые обучаются независимо друг от друга, а общий результат – это их усреднение. И сейчас видим, что похожий принцип заложен непосредственно на уровне архитектуры сетей ResNet, а значит, они должны быть более устойчивыми к переобучению. Однако это лишь предположение. Так это или нет, всегда приходится проверять непосредственно на практике.

ResNet, как аналог бустинга алгоритмов

Еще одна интересная точка зрения на архитектуру ResNet связана с ее восприятием, как бустинга остаточных блоков (residual blocks). О бустинге алгоритмов мы с вами подробно говорили на предыдущем курсе «Основы машинного обучения». Его принцип хорошо демонстрирует следующий известный рисунок, взятый с просторов Интернета:

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

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

Видео по теме