#python #neural-network #pytorch #deep-residual-networks
#python #нейронная сеть #pytorch #глубокие остаточные сети
Вопрос:
Итак, я прочитал около половины оригинальной статьи ResNet и пытаюсь понять, как создать свою версию для табличных данных.
Я прочитал несколько сообщений в блоге о том, как это работает в PyTorch, и я вижу интенсивное использование nn.Identity()
. Теперь в документе также часто используется термин сопоставление идентификаторов. Однако это просто относится к добавлению входных данных для стека слоев к выходным данным этого же стека поэлементным образом. Если входные и выходные измерения разные, то в документе говорится о заполнении входных данных нулями или использовании матрицы W_s
для проецирования входных данных в другое измерение.
Вот абстракция остаточного блока, которую я нашел в сообщении в блоге:
class ResidualBlock(nn.Module):
def __init__(self, in_channels, out_channels, activation='relu'):
super().__init__()
self.in_channels, self.out_channels, self.activation = in_channels, out_channels, activation
self.blocks = nn.Identity()
self.shortcut = nn.Identity()
def forward(self, x):
residual = x
if self.should_apply_shortcut: residual = self.shortcut(x)
x = self.blocks(x)
x = residual
return x
@property
def should_apply_shortcut(self):
return self.in_channels != self.out_channels
block1 = ResidualBlock(4, 4)
И мое собственное приложение к фиктивному тензору:
x = tensor([1, 1, 2, 2])
block1 = ResidualBlock(4, 4)
block2 = ResidualBlock(4, 6)
x = block1(x)
print(x)
x = block2(x)
print(x)
>>> tensor([2, 2, 4, 4])
>>> tensor([4, 4, 8, 8])
Итак, в конце концов, x = nn.Identity(x)
и я не уверен, в чем смысл его использования, кроме как имитировать математический жаргон, найденный в оригинальной статье. Я уверен, что это не так, и что у него есть какое-то скрытое применение, которое я просто пока не вижу. Что это может быть?
РЕДАКТИРОВАТЬ Вот еще один пример реализации остаточного обучения, на этот раз в Keras. Он делает именно то, что я предложил выше, и просто сохраняет копию входных данных для добавления к выходным:
def residual_block(x: Tensor, downsample: bool, filters: int, kernel_size: int = 3) -> Tensor:
y = Conv2D(kernel_size=kernel_size,
strides= (1 if not downsample else 2),
filters=filters,
padding="same")(x)
y = relu_bn(y)
y = Conv2D(kernel_size=kernel_size,
strides=1,
filters=filters,
padding="same")(y)
if downsample:
x = Conv2D(kernel_size=1,
strides=2,
filters=filters,
padding="same")(x)
out = Add()([x, y])
out = relu_bn(out)
return out
Комментарии:
1. Мне показали ответ здесь: github.com/pytorch/pytorch/issues/9160 . Я расширю его до ответа через пару дней, если никто не сделает это первым.
Ответ №1:
В чем идея использования nn.Идентификатор для остаточного обучения?
Его нет (почти, см. Конец сообщения), все nn.Identity
, что нужно, это пересылка введенных в него данных (в основном no-op
).
Как показано в проблеме с репозиторием PyTorch, на которую вы ссылались в комментарии, эта идея была сначала отклонена, а затем объединена в PyTorch из-за другого использования (см. Обоснование в этом PR). Это обоснование не связано с самим блоком ResNet, см. Конец ответа.
Реализация ResNet
Самая простая общая версия, о которой я могу думать с помощью projection, была бы примерно такой:
class Residual(torch.nn.Module):
def __init__(self, module: torch.nn.Module, projection: torch.nn.Module = None):
super().__init__()
self.module = module
self.projection = projection
def forward(self, inputs):
output = self.module(inputs)
if self.projection is not None:
inputs = self.projection(inputs)
return output inputs
Вы можете передавать такие module
вещи, как две сложенные свертки, и добавлять 1x1
свертку (с заполнением или с шагом или чем-то еще) в качестве модуля проекции.
Для tabular
данных вы можете использовать это как module
(при условии, что ваш ввод имеет 50
функции):
torch.nn.Sequential(
torch.nn.Linear(50, 50),
torch.nn.ReLU(),
torch.nn.Linear(50, 50),
torch.nn.ReLU(),
torch.nn.Linear(50, 50),
)
По сути, все, что вам нужно сделать, это добавить input
в некоторый модуль к его выводу, и все.
Обоснование nn.Identity
Возможно, было бы проще создавать нейронные сети (и читать их впоследствии), например, для пакетной нормы (взято из вышеупомянутого PR):
batch_norm = nn.BatchNorm2d
if dont_use_batch_norm:
batch_norm = Identity
Теперь вы можете использовать его с nn.Sequential
легкостью:
nn.Sequential(
...
batch_norm(N, momentum=0.05),
...
)
И при печати сети она всегда имеет одинаковое количество подмодулей (с любым BatchNorm
или Identity
), что также делает все это немного более плавным, IMO.
Другим вариантом использования, упомянутым здесь, может быть удаление частей существующих нейронных сетей:
net = tv.models.alexnet(pretrained=True)
# Assume net has two parts
# features and classifier
net.classifier = Identity()
Теперь вместо запуска net.features(input)
вы можете запустить net(input)
, что может быть проще для чтения другими.
Комментарии:
1. Отлично, это точный вывод, к которому я пришел, обнаружив эту проблему с GitHub. Спасибо, что так хорошо объяснили это.