В чем идея использования nn.Идентификатор для остаточного обучения?

#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. Спасибо, что так хорошо объяснили это.