Анализ статьи FPN: использование внутренней пирамиды.
Понимание того, как FPN позволяет моделям глубокого обучения обнаруживать мелкие объекты, и как реализовать его с нуля.
Делиться

В моей предыдущей статье я рассказывал о YOLOv3 [1]. Одним из факторов, делающих эту версию YOLO лучше своих предшественников, является ее способность обнаруживать мелкие объекты благодаря использованию в модели структуры, подобной FPN. К сожалению, мое объяснение FPN в той статье было не совсем полным, поскольку я больше сосредоточился на самой YOLOv3. Поэтому в этой статье я решил написать конкретно о FPN, используя оригинальную статью под названием «Feature Pyramid Networks for Object Detection» [2], чтобы вы лучше поняли, что это такое и как это работает. Более того, здесь я также продемонстрирую, как реализовать FPN с нуля и как соединить ее с CNN-базой и RPN-головой.
Позвоночник, шея и голова
Прежде чем перейти к FPN, необходимо знать, что структура модели обнаружения объектов отличается от структуры модели классификации, причем основное различие заключается в самом последнем слое. В типичной модели классификации последний слой включает в себя ряд нейронов, каждый из которых соответствует каждому классу, доступному в наборе данных. Или, в случае бинарной классификации, выходной слой состоит всего из одного нейрона, который отвечает за предсказание, принадлежит ли образец к классу 0 или 1. Такой выходной слой на самом деле не подходит для задачи обнаружения, поскольку он также требует нейронов, предназначенных для предсказания местоположения и размера объекта в дополнение к его классу.
Таким образом, для того чтобы модель могла предсказывать местоположение и размер объекта, необходимо заменить выходной слой, то есть классификационный слой, так называемым слоем обнаружения. Остальные слои (все, кроме основного слоя) обычно называются базовым слоем. Некоторые модели, использующие такую структуру, — это YOLOv1 и YOLOv2, где в качестве базового слоя используется стек сверточных слоев, а для предсказания местоположения и размера объекта на изображении, а также его класса, используется специальный слой.
Более старые модели обнаружения объектов, такие как YOLOv1 и YOLOv2, упомянутые выше, состояли только из базовой структуры и головной части. Со временем исследователи обнаружили, что эта структура все еще не совсем оптимальна, поэтому они, наконец, предложили идею добавления нового компонента, называемого «шейкой». Как следует из названия, это, по сути, нечто, что располагается между базовой структурой и головной частью. И FPN, о которой мы поговорим в этой статье, является одной из первых предложенных «шейк» для моделей обнаружения объектов. На рисунке 1 ниже представлен общий архитектурный вид старых и современных моделей обнаружения объектов.

Основная часть модели, отвечающая за извлечение признаков, включает в себя «основу» (backbone), «шею» (neck), которая способствует повышению качества признаков, и «голову» (head), отвечающую за прогнозирование. Исходя из этого, можно сказать, что с помощью FPN сеть потенциально может достичь более высокой точности благодаря механизму повышения качества признаков, реализуемому «шеей».
Эволюция многомасштабного механизма обнаружения
Ранее я упоминал, что использование базовой сети и детектора без шеи не совсем оптимально. Это особенно касается его способности обнаруживать мелкие объекты. Теперь давайте посмотрим на рисунок 2 ниже. Первые две версии YOLO, которые я упоминал ранее, используют структуру на изображении (b), где ограничивающая рамка и предсказания класса объекта выполняются исключительно на основе карты признаков, созданной самым глубоким слоем в базовой сети. Этот метод допустим, но эффективен только для крупных объектов. Причина довольно проста: по мере того, как изображение углубляется в сеть, пространственное измерение уменьшается, и, что более важно, пиксельная информация, содержащаяся в более глубоких картах признаков, становится представлением нескольких соседних пикселей в более мелких, что приводит к смешиванию пространственной информации. Благодаря этому карты признаков из более глубоких слоев получают большое рецептивное поле, что позволяет легко обнаруживать и распознавать крупные объекты. Однако ухудшение пространственной информации по мере углубления препятствует точному обнаружению мелких объектов, поскольку нам необходимо точное местоположение пикселя для предсказания точных координат объектов.
Кроме того, размер рецептивного поля карты признаков положительно коррелирует с количеством содержащейся в ней семантической информации. На рисунке ниже карта признаков с высоким содержанием семантической информации обозначена толстой синей рамкой. Именно поэтому самая глубокая карта признаков на рисунке (b) имеет самую толстую рамку.

Наиболее простой подход, позволяющий нейронной сети одновременно обнаруживать крупные и мелкие объекты, заключается в использовании пирамиды признаковых изображений (a). Этот метод позволяет достичь высокой точности, поскольку мы можем делать прогнозы на основе изображений с различным разрешением. По сути, здесь происходит масштабирование входного изображения до нескольких масштабов, независимое извлечение признаков для каждого масштаба и прогнозирование на основе полученных карт признаков. Карта признаков меньшего размера отвечает за обнаружение крупных объектов, тогда как карта признаков большего размера специализируется на обнаружении мелких объектов благодаря своей подробной пространственной информации. Однако этот метод является вычислительно затратным, поскольку необходимо обрабатывать несколько исходных изображений разных масштабов одновременно.
Другое решение было предложено авторами SSD (Single Shot Multibox Detector), которое на рисунке 2 выше обозначено как пирамидальная иерархия признаков (c). Таким образом, вместо подачи в сеть одного и того же изображения разных размеров, авторы SSD попытались использовать только самое большое изображение и задействовать внутреннюю пирамидальную структуру базовой сети CNN для прогнозирования различных масштабов. Такой подход делает систему более вычислительно эффективной, чем вариант (a). Тем не менее, здесь мы фактически получили компромисс, подобный варианту (b), где карта признаков из более глубокого слоя содержит большое количество семантической информации, но имеет минимальное количество пространственной информации, в то время как карта признаков из более мелкого слоя содержит много пространственной информации, но не так много семантической. Важно отметить, что подробная пространственная информация может быть не очень важна для больших объектов, поскольку мы можем лишь приблизительно определить общую форму этого объекта. Однако как пространственная, так и семантическая информация необходимы для обнаружения малых объектов, поскольку модели необходимо понимать не только подробные координаты, но и то, что находится внутри ограничивающего прямоугольника. Таким образом, хотя метод (c) действительно способен обнаруживать как крупные, так и мелкие объекты, его способность обнаруживать последние еще не оптимальна.
И вот здесь на помощь приходит FPN. Если мы посмотрим на изображение (d) на рисунке 2, то увидим, что предсказания делаются на основе соответствующих карт признаков, которые все обладают богатой семантической информацией. Это, по сути, позволяет точно обнаруживать объекты различного масштаба, включая более мелкие. Подробно о том, как FPN обогащает карты признаков, мы поговорим в следующем разделе.
Как работает FPN
Идея FPN заключается в том, чтобы внедрять информацию из более глубоких карт признаков в более мелкие, и таким образом более мелкие карты признаков будут содержать не только высокую пространственную информацию, но и высокую семантическую информацию, поступающую из более глубокой части сети. Теоретически это должно привести к повышению точности обнаружения мелких объектов, поскольку большие карты признаков теперь обогащены большим количеством семантической информации. Для достижения этого вводятся так называемые нисходящие пути и боковые связи. Полную архитектуру FPN можно увидеть на рисунке 3 ниже, который, по сути, является подробной версией архитектуры на рисунке 2 (d).

Авторы данной статьи решили использовать ResNet-50 и ResNet-101 в качестве базовой сети. Предположим, мы бы использовали первую, тогда бы слои conv2, conv3, conv4 и conv5 повторялись 3, 4, 6 и 3 раза соответственно, как показано на архитектурных деталях ResNet на рисунке 4. C2, C3, C4 и C5, которые представляют собой тензоры, полученные последним слоем соответствующего этапа, будут передаваться по нисходящему пути через боковые соединения, то есть стрелки, исходящие из базовой сети.

Нисходящий путь используется для передачи семантической информации из более глубоких слоев, тогда как боковые связи используются для сохранения пространственной информации. Мы объединяем их путем поэлементного суммирования, подробный процесс которого показан на рисунке 5 ниже. Для тензоров, поступающих из базовой сети (C), сначала необходимо применить к ним свертку 1×1. Этот сверточный слой отвечает за корректировку количества каналов таким образом, чтобы оно соответствовало тензору, поступающему из нисходящего пути. Сам тензор из нисходящего пути (M+1) подвергается двукратному увеличению разрешения методом ближайших соседей. Эти процессы, по сути, выполняются потому, что нам необходимо, чтобы оба тензора имели одинаковую размерность для выполнения поэлементного суммирования. После выполнения суммирования результирующий тензор теперь обозначается как M. Этот тензор имеет некоторый эффект наложения спектров из-за процесса увеличения разрешения, который мы выполнили ранее, поэтому нам необходимо применить свертку 3×3 для уменьшения этого эффекта. В итоге мы получили тензор P, готовый к передаче на детектор.

Следует помнить, что все процессы, описанные на рисунке 5 выше, применимы только к M2, M3 и M4. Вычисление M5 на самом деле намного проще (см. рисунок 6), где нам нужно лишь скорректировать количество каналов с помощью свертки 1×1, чтобы оно совпадало с тензорами в других боковых соединениях. Сам тензор M5 не нуждается в дальнейшей обработке с помощью свертки 3×3, поскольку сглаживать нечего из-за отсутствия механизма повышения разрешения. Таким образом, можно сказать, что P5 — это тот же самый тензор, что и M5.

Думаю, на этом теория FPN завершена. В следующем разделе я расскажу о низкоуровневой архитектуре, реализовав её с нуля с помощью PyTorch.
FPN с нуля
Основная сеть CNN
Как видно из приведенного ниже блока кода 1, первое, что нам нужно сделать в коде, — это импортировать необходимые модули.
# Codeblock 1 import torch import torch.nn as nn
Поскольку основное внимание в этой статье уделяется FPN, для упрощения я буду использовать фиктивную модель в качестве базовой сети вместо реальной ResNet. Тем не менее, слои в коде названы в соответствии с рисунками 3 и 4: conv1 , conv2 , conv3 , conv4 и conv5 , как показано в блоке кода 2 ниже. Размерность выходного тензора каждого этапа также устанавливается в соответствии с оригинальной архитектурой ResNet. Таким образом, хотя эта базовая сеть представляет собой просто модель на основе CNN, вы можете рассматривать ее как обычную ResNet.
Далее, внутри метода forward() мы соединяем все слои. Если вы внимательно посмотрите на код, вы заметите, что за каждым сверточным слоем следует функция активации ReLU и слой максимального пулинга. Сам слой максимального пулинга имеет шаг 2, что фактически уменьшает вдвое пространственную размерность карты признаков. Повторяя слои максимального пулинга несколько раз, мы будем постепенно уменьшать размерность карты признаков по мере углубления в сеть. Это, по сути, создает пирамидальную структуру в основе CNN, которая используется FPN для достижения высокой точности обнаружения объектов различного масштаба. В CNN уменьшение пространственной размерности таким образом является стандартной практикой для снижения вычислительной сложности и компенсации увеличения количества каналов.
Оставаясь в методе forward() , не забудьте клонировать основной тензор x как показано в строках, отмеченных #(1) , #(2) и #(3) . Скопированные тензоры, которые называются c2 , c3 и c4 , будут возвращаемыми значениями класса CNN вместе с картой признаков из основного потока ( c5 ) ( #(4) ).
# Codeblock 2 class CNN(nn.Module): def __init__(self): super().__init__() self.relu = nn.ReLU() self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) self.conv1 = nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3, padding=1) self.conv2 = nn.Conv2d(in_channels=64, out_channels=256, kernel_size=3, padding=1) self.conv3 = nn.Conv2d(in_channels=256, out_channels=512, kernel_size=3, padding=1) self.conv4 = nn.Conv2d(in_channels=512, out_channels=1024, kernel_size=3, padding=1) self.conv5 = nn.Conv2d(in_channels=1024, out_channels=2048, kernel_size=3, padding=1) def forward(self, x): print(f'originalt: {x.size()}n') x = self.relu(self.conv1(x)) print(f'after conv1t: {x.size()}') x = self.maxpool(x) print(f'after maxpoolt: {x.size()}n') x = self.relu(self.conv2(x)) print(f'after conv2t: {x.size()}') x = self.maxpool(x) print(f'after maxpoolt: {x.size()}n') c2 = x.clone() #(1) x = self.relu(self.conv3(x)) print(f'after conv3t: {x.size()}') x = self.maxpool(x) print(f'after maxpoolt: {x.size()}n') c3 = x.clone() #(2) x = self.relu(self.conv4(x)) print(f'after conv4t: {x.size()}') x = self.maxpool(x) print(f'after maxpoolt: {x.size()}n') c4 = x.clone() #(3) x = self.relu(self.conv5(x)) print(f'after conv5t: {x.size()}') c5 = self.maxpool(x) print(f'after maxpoolt: {c5.size()}n') return c2, c3, c4, c5 #(4)
Поскольку класс CNN завершен, теперь мы попробуем пропустить через сеть фиктивное RGB-изображение размером 224×224. Размерность этого тензора выбрана на основе формы входных данных исходной сети ResNet.
# Codeblock 3 cnn = CNN() x = torch.randn(1, 3, 224, 224) out_cnn = cnn(x)
Ниже представлен результат. Здесь видно, что количество каналов после каждого сверточного слоя точно соответствует структуре ResNet, показанной на рисунке 4. Более того, благодаря слоям максимального пулинга пространственная размерность нашего фиктивного тензора успешно уменьшилась вдвое после каждого этапа. Это, по сути, указывает на то, что наша простая модель CNN действительно имитирует общую структуру модели ResNet.
# Codeblock 3 Output original : torch.Size([1, 3, 224, 224]) after conv1 : torch.Size([1, 64, 224, 224]) after maxpool : torch.Size([1, 64, 112, 112]) after conv2 : torch.Size([1, 256, 112, 112]) after maxpool : torch.Size([1, 256, 56, 56]) after conv3 : torch.Size([1, 512, 56, 56]) after maxpool : torch.Size([1, 512, 28, 28]) after conv4 : torch.Size([1, 1024, 28, 28]) after maxpool : torch.Size([1, 1024, 14, 14]) after conv5 : torch.Size([1, 2048, 14, 14]) after maxpool : torch.Size([1, 2048, 7, 7])
Мы также можем проверить, как выглядят возвращаемые тензоры, запустив приведенный ниже код. В результате вы увидите, что тензор c2 имеет форму 256×56×56, c3 — форму 512×28×28 и так далее. Кстати, число 1 на нулевой оси можно просто игнорировать, поскольку оно указывает только количество образцов, прошедших через один пакет.
# Codeblock 4 c2, c3, c4, c5 = out_cnn print(c2.shape) print(c3.shape) print(c4.shape) print(c5.shape)
# Codeblock 4 Output torch.Size([1, 256, 56, 56]) torch.Size([1, 512, 28, 28]) torch.Size([1, 1024, 14, 14]) torch.Size([1, 2048, 7, 7])
FPN Шея
Поскольку основная часть CNN завершена, перейдем к узлу FPN. В приведенном ниже блоке кода 5 мы сначала инициализируем слой увеличения разрешения ( #(1) ), который мы будем использовать каждый раз, когда хотим удвоить пространственную размерность тензора M. Здесь я устанавливаю параметр mode на nearest , как предложено в статье, что на самом деле является очень простым методом интерполяции, позволяющим ускорить процесс. Взгляните на рисунок 7, чтобы увидеть, как выглядит интерполяция методом ближайшего соседа.
# Codeblock 5 class FPN(nn.Module): def __init__(self): super().__init__() self.upsample = nn.Upsample(scale_factor=2, mode='nearest') #(1) self.lateral_c5 = nn.Conv2d(in_channels=2048, out_channels=256, kernel_size=1) self.lateral_c4 = nn.Conv2d(in_channels=1024, out_channels=256, kernel_size=1) self.lateral_c3 = nn.Conv2d(in_channels=512, out_channels=256, kernel_size=1) self.lateral_c2 = nn.Conv2d(in_channels=256, out_channels=256, kernel_size=1) self.smooth_m4 = nn.Conv2d(in_channels=256, out_channels=256, kernel_size=3, padding=1) self.smooth_m3 = nn.Conv2d(in_channels=256, out_channels=256, kernel_size=3, padding=1) self.smooth_m2 = nn.Conv2d(in_channels=256, out_channels=256, kernel_size=3, padding=1) def forward(self, c2, c3, c4, c5): m5 = self.lateral_c5(c5) p5 = m5 m4 = self.upsample(m5) + self.lateral_c4(c4) p4 = self.smooth_m4(m4) m3 = self.upsample(m4) + self.lateral_c3(c3) p3 = self.smooth_m3(m3) m2 = self.upsample(m3) + self.lateral_c2(c2) p2 = self.smooth_m2(m2) return p2, p3, p4, p5

Если вы вернетесь к блоку кода 4, вы увидите, что тензоры c{5,4,3,2} , возвращаемые базовой сетью, имеют разное количество каналов. В основном, именно поэтому мы инициализируем слои lateral_c{5,4,3,2} для обработки этих тензоров, чтобы результирующее количество каналов было равномерным. Согласно статье, нам необходимо настроить эти сверточные слои на создание 256 выходных каналов, поэтому мы используем это число для параметра out_channels .
Далее, исходя из рисунка 7, вы можете представить, насколько пикселизированными становятся результирующие карты признаков после увеличения разрешения. Таким образом, нам необходимо дополнительно обработать тензоры m{4,3,2} с помощью сверточных слоев 3×3, которые я называю smooth_m{4,3,2} . Поскольку все слои инициализированы, нам нужно собрать их в методе forward() в соответствии со структурой, которую я показал ранее на рисунке 3.
Кроме того, в статье также упоминается, что нам не нужно реализовывать какие-либо нелинейности внутри FPN, поэтому все сверточные слои в классе FPN описанном выше, не сопровождаются функцией активации ReLU. Теперь в приведенном ниже блоке кода 6 я пытаюсь пропустить полученные ранее тензоры C через созданный нами сегмент FPN. На выходе мы видим, что результирующие тензоры имеют разное пространственное разрешение. Позже тензор p2 (размером 56×56) будет передан в детектор для обнаружения мелких объектов, тогда как p5 (тензор 7×7) будет отвечать за обнаружение крупных объектов.
# Codeblock 6 fpn = FPN() out_fpn = fpn(c2, c3, c4, c5) p2, p3, p4, p5 = out_fpn print(p2.shape) print(p3.shape) print(p4.shape) print(p5.shape)
# Codeblock 6 Output torch.Size([1, 256, 56, 56]) torch.Size([1, 256, 28, 28]) torch.Size([1, 256, 14, 14]) torch.Size([1, 256, 7, 7])
Здесь мы уже завершили часть, касающуюся FPN. Помните, что FPN — это всего лишь «шейка» модели обнаружения, что по сути означает, что на данном этапе у нас еще нет предсказания ограничивающей рамки. Для получения фактического результата предсказания нам необходимо подключить к FPN определенный «головной» элемент, и в данном случае я буду использовать головной элемент RPN (Region Proposal Network).
Руководитель RPN
Если вы еще не знакомы с RPN, то это, по сути, головная часть модели обнаружения объектов, используемая для создания ограничивающих рамок, которая впервые была предложена в статье Faster R-CNN. Обратите внимание, что хотя в этой демонстрации мы называем RPN головной частью, имейте в виду, что это на самом деле не полноценная головная часть обнаружения, поскольку она не способна выполнять классификацию обнаруженных объектов.
Как видно из представленной ниже архитектуры RPN, она использует так называемые слои cls и reg, которые генерируют оценку объектности и координаты ограничивающей рамки соответственно. Тензор оценки объектности имеет длину 2k, где k — количество предопределенных якорных рамок, а 2 — вероятность наличия соответствующей якорной рамки. Это можно рассматривать как бинарную классификацию, обработанную с помощью one-hot представления (объект/не объект). При этом число 4 в тензоре координат длиной 4k просто соответствует предсказанию xywh.

Возвращаясь к нашей реализации кода, в приведенном ниже блоке кода 7 мы инициализируем intermediate , cls и слой reg в методе __init__() класса RPN. Обратите внимание, что только промежуточный слой использует свертку 3×3, тогда как слои cls и reg используют свертку 1×1. Что касается количества каналов, intermediate слой отображает входной тензор в 256 каналов, в то время как слои cls и reg отображают его в 2k и 4k соответственно. Наконец, мы можем просто соединить эти слои в методе forward() .
# Codeblock 7 NUM_ANCHORS = 3 class RPN(nn.Module): def __init__(self): super().__init__() self.intermediate = nn.Conv2d(in_channels=256, out_channels=256, kernel_size=3, padding=1) self.cls = nn.Conv2d(in_channels=256, out_channels=NUM_ANCHORS*2, kernel_size=1) self.reg = nn.Conv2d(in_channels=256, out_channels=NUM_ANCHORS*4, kernel_size=1) def forward(self, x): x = self.intermediate(x) objectness_scores = self.cls(x) bbox_regressions = self.reg(x) return objectness_scores, bbox_regressions
Теперь давайте проверим, правильно ли работает наш класс RPN, запустив приведенный ниже фрагмент кода 8. Здесь я тестирую его на карте объектов p2 , полученной из фрагмента кода 6.
# Codeblock 8 rpn = RPN() p2_objectness, p2_bbox = rpn(p2) print(p2_objectness.shape) print(p2_bbox.shape)
Ниже представлен результат выполнения. Видно, что p2_objectness — это тензор размером 6×56×56, что указывает на то, что каждый пиксель в пространственном измерении 56×56 содержит 6 значений предсказания, где первые 2 значения относятся к первому якорному боксу, следующие 2 значения — ко второму якорному боксу, а последние 2 значения — к третьему. Аналогичное правило применяется и к тензору p2_bbox , который в данном случае содержит значения xywh.
# Codeblock 8 Output torch.Size([1, 6, 56, 56]) torch.Size([1, 12, 56, 56])
Вся модель обнаружения
В приведенном ниже блоке кода 9 мы построим всю модель обнаружения, чтобы вы лучше поняли, как FPN работает вместе с другими компонентами. Здесь, в методе __init__() , я инициализирую узкое место CNN, узкую часть FPN и головку RPN. В методе forward() мы сначала передаем тензор изображения в CNN ( #(1) ). Эта базовая сеть возвращает 4 тензора, которые готовы к соединению с FPN через боковые соединения. Далее, в строке #(2) мы подаем все тензоры C на вход FPN, получая тензоры P. Наконец, мы используем все тензоры P в качестве входных данных для RPN ( #(3–4) ). Имейте в виду, что RPN разделяет свои параметры между всеми головками обнаружения, поэтому нам нужно инициализировать его только один раз и использовать для всех карт признаков разных масштабов.
# Codeblock 9 class DetectionModel(nn.Module): def __init__(self): super().__init__() self.cnn = CNN() self.fpn = FPN() self.rpn = RPN() def forward(self, x): c2, c3, c4, c5 = self.cnn(x) #(1) p2, p3, p4, p5 = self.fpn(c2, c3, c4, c5) #(2) p2_pred = self.rpn(p2) #(3) p3_pred = self.rpn(p3) p4_pred = self.rpn(p4) p5_pred = self.rpn(p5) #(4) return p2_pred, p3_pred, p4_pred, p5_pred
Теперь, когда блок обнаружения готов, мы можем протестировать его с помощью приведенного ниже блока кода 10. Здесь я пытаюсь передать фиктивный тензор размером 1×3×224×224, имитирующий одно RGB-изображение размером 224×224 ( #(1) ). Затем мы можем просто передать его через detection_model ( #(2) ) и распаковать результаты предсказания ( #(3–4) ).
# Codeblock 10 detection_model = DetectionModel() x = torch.randn(1, 3, 224, 224) #(1) p2_pred, p3_pred, p4_pred, p5_pred = detection_model(x) #(2) p2_objectness, p2_bbox = p2_pred #(3) p3_objectness, p3_bbox = p3_pred p4_objectness, p4_bbox = p4_pred p5_objectness, p5_bbox = p5_pred #(4) print(p2_objectness.shape) print(p3_objectness.shape) print(p4_objectness.shape) print(p5_objectness.shape) print() print(p2_bbox.shape) print(p3_bbox.shape) print(p4_bbox.shape) print(p5_bbox.shape)
Ниже показан результат. Как видно, размеры полученных тензоров соответствуют задуманному: тензоры objectness и bbox содержат 6 и 12 значений для каждой ячейки сетки соответственно. Поэтому я считаю, что эта реализация корректна и готова к обучению для задачи обнаружения объектов.
# Codeblock 10 Output torch.Size([1, 6, 56, 56]) torch.Size([1, 6, 28, 28]) torch.Size([1, 6, 14, 14]) torch.Size([1, 6, 7, 7]) torch.Size([1, 12, 56, 56]) torch.Size([1, 12, 28, 28]) torch.Size([1, 12, 14, 14]) torch.Size([1, 12, 7, 7])
Завершение
Думаю, это практически всё, что касается базовой теории и реализации FPN с нуля. Здесь я предлагаю вам попробовать реализовать FPN на реальной ResNet вместо фиктивной модели CNN, как я продемонстрировал выше. У меня есть отдельная статья о ResNet, которую вы можете посмотреть в качестве ссылки [5]. Или же вы можете использовать другие модели, такие как VGG, ResNeXt, ConvNeXt и т. д., поскольку FPN может работать практически на любой модели на основе CNN. Более того, было бы лучше, если бы вы могли реализовать голову в стиле YOLO в качестве замены RPN, примеры которой можно увидеть в моих предыдущих статьях, ссылки на которые приведены в [6] для YOLOv1, [7] для YOLOv2 и [1] для YOLOv3.
Пожалуйста, сообщите мне, если в моем тексте или в коде есть ошибки. Спасибо за чтение! Кстати, код, использованный в этой статье, вы можете найти в моем репозитории GitHub [8].
Ссылки
[1] Мухаммад Арди. Прохождение YOLOv3 Paper: Еще лучше, но не настолько. Medium. https://ai.gopubby.com/yolov3-paper-walkthrough-even-better-but-not-that-much-4dc6c0c1b42c [Дата обращения: 1 июня 2026].
[2] Цун-И Лин. Пирамидальные сети признаков для обнаружения объектов. Arxiv. https://arxiv.org/abs/1612.03144 [Дата обращения: 9 сентября 2025].
[3] Изображение создано автором.
[4] Шаоцин Рен. Faster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks. Arxiv. https://arxiv.org/abs/1506.01497 [Дата обращения: 9 сентября 2025].
[5] Мухаммад Арди. Обзор статьи: Остаточная сеть (ResNet). Python на простом английском языке. https://medium.com/python-in-plain-english/paper-walkthrough-residual-network-resnet-62af58d1c521 [Дата обращения: 9 сентября 2025].
[6] Мухаммад Арди. Пошаговое руководство по YOLOv1: День, когда YOLO впервые увидел мир. Medium. https://medium.com/ai-advances/yolov1-paper-walkthrough-the-day-yolo-first-saw-the-world-ccff8b60d84b [Дата обращения: 1 июня 2026].
[7] Мухаммад Арди. Пошаговое руководство по YOLOv2 и YOLO9000: лучше, быстрее, сильнее. Medium. https://ai.gopubby.com/yolov2-yolo9000-paper-walkthrough-better-faster-stronger-c9906e0438a3 [Дата обращения: 1 июня 2026].
[8] МухаммадАрдиПутра. ФПН. Гитхаб. https://github.com/MuhammadArdiPutra/medium_articles/blob/main/FPN.ipynb [Проверено 9 сентября 2025 г.].
Мухаммад Арди. Все материалы от Мухаммада Арди.
Источник: towardsdatascience.com

Добавить комментарий
Для отправки комментария вам необходимо авторизоваться.