使用 nn.Identity 进行残差学习背后的想法是什么?

What is the idea behind using nn.Identity for residual learning?

所以,我已经阅读了大约一半的原始 ResNet 论文,并且正在尝试弄清楚如何为表格数据制作我的版本。

我已经阅读了一些关于它在 PyTorch 中如何工作的博客 post,并且我看到大量使用 nn.Identity()。现在,论文也经常使用术语 identity mapping。但是,它只是指以元素方式将堆栈层的输入添加到同一堆栈的输出。如果输入和输出维度不同,那么本文讨论用零填充输入或使用矩阵 W_s 将输入投影到不同的维度。

这是我在博客中找到的一个残差块的抽象post:


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) 除了模仿原始论文中的数学术语外,我不确定它的用途。我敢肯定情况并非如此,而且它有一些我还没有看到的隐藏用途。可能是什么?

EDIT 这是实现残差学习的另一个例子,这次是在 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

What is the idea behind using nn.Identity for residual learning?

还有none(差不多,见post的结尾),nn.Identity所做的只是转发给它的输入(基本上是no-op)。

如图PyTorch repo issue you linked in comment this idea was first rejected, later merged into PyTorch, due to other use (see the rationale in this PR)。这个基本原理 没有 连接到 ResNet 块本身,请参阅答案结尾。

ResNet 实现

我能想到的最简单的投影通用版本是这样的:

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),
    ...
)

并且在打印网络时,它始终具有相同数量的子模块(使用 BatchNormIdentity),这也使整个事情在我看来更加顺畅。

提到的另一个用例 here 可能是删除现有神经网络的一部分:

net = tv.models.alexnet(pretrained=True)
# Assume net has two parts
# features and classifier
net.classifier = Identity()

现在,您可以 运行 net(input) 而不是 运行ning net.features(input),这对其他人来说也更容易阅读。