如何计算卷积神经网络第一层线性层的维度

3

目前,我正在使用一个带有全连接层的CNN,并且我正在使用大小为32x32的3通道图像。我想知道是否有一致的公式可以用来计算第一个线性层的输入维度,以及如何从最后的卷积/最大池化层的输入中计算第一个线性层的维度。换句话说,我想能够仅凭借最后的卷积和最大池化层的信息计算出第一个线性层的维度,而不需要使用之前层的信息(这样我就不必手动计算非常深的网络的权重维度)。

我还想了解可接受维度的计算方式,比如这些计算的原理是什么?

出于某种原因,这些计算可以工作,并且Pytorch接受了这些维度:

val = int((32*32)/4)
self.fc1 = nn.Linear(val, 200)

并且这也起作用了

self.fc1 = nn.Linear(64*4*4, 200)

这些值为什么有效,这些方法的计算是否有限制?例如,如果我更改步幅距离或内核大小,会导致错误。请注意以下是我使用的一般模型架构:
# define the CNN architecture
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        # convolutional layer
        self.conv1 = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, padding=1)
        # max pooling layer
        self.pool = nn.MaxPool2d(2, 2)  


        self.conv2 = nn.Conv2d(in_channels=16, out_channels=32,kernel_size=3)
        self.pool2 = nn.MaxPool2d(2,2)

        self.conv3 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3)
        self.pool3 = nn.MaxPool2d(2,2)
        
        self.dropout = nn.Dropout(0.25)

        # H*W/4
        val = int((32*32)/4)
        #self.fc1 = nn.Linear(64*4*4, 200)
        ################################################
        self.fc1 = nn.Linear(val, 200)  # dimensions of the layer I wish to calculate
        ###############################################
        self.fc2 = nn.Linear(200,100)
        self.fc3 = nn.Linear(100,10)


    def forward(self, x):
        # add sequence of convolutional and max pooling layers
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool2(F.relu(self.conv2(x)))
        x = self.pool3(F.relu(self.conv3(x)))
        #print(x.shape)
        x = torch.flatten(x, 1) # flatten all dimensions except batch
        
        x = F.relu(self.fc1(x))
        x = self.dropout(x)
        x = F.relu(self.fc2(x))
        x = self.dropout(x)
        x = self.fc3(x)

        return x

# create a complete CNN
model = Net()
print(model)

有人能告诉我如何计算第一个线性层的尺寸并解释其原理吗?

2个回答

6

给定输入空间维度 w,一个二维卷积层将会在此维度输出一个张量,其大小如下:

int((w + 2*p - d*(k - 1) - 1)/s + 1)
nn.MaxPool2d同样如此。有关参考资料,您可以在PyTorch文档上查看。
卷积部分由三个(Conv2d + MaxPool2d)块组成。您可以使用此辅助函数轻松推断输出的空间维度大小:
def conv_shape(x, k=1, p=0, s=1, d=1):
    return int((x + 2*p - d*(k - 1) - 1)/s + 1)

递归调用后,您将得到相应的空间维度:

>>> w = conv_shape(conv_shape(32, k=3, p=1), k=2, s=2)
>>> w = conv_shape(conv_shape(w, k=3), k=2, s=2)
>>> w = conv_shape(conv_shape(w, k=3), k=2, s=2)

>>> w
2

由于您的卷积具有平方核和相同步长,填充(水平等于垂直),因此上述计算对于张量的宽度和高度维度成立。最后,查看最后一个卷积层 conv3 ,其具有64个过滤器,在全连接层之前每批元素的结果元素数量为: w * w * 64 ,即 256


然而,您可以调用您的图层以查找输出形状!

class Net(nn.Module):
    def __init__(self):
        super().__init__()
        
        self.feature_extractor = nn.Sequential(
            nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(2, 2),
            nn.Conv2d(in_channels=16, out_channels=32, kernel_size=3),
            nn.ReLU(),
            nn.MaxPool2d(2,2),
            nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3),
            nn.ReLU(),
            nn.MaxPool2d(2,2),
            nn.Flatten())

        n_channels = self.feature_extractor(torch.empty(1, 3, 32, 32)).size(-1)

        self.classifier = nn.Sequential(
            nn.Linear(n_channels, 200),
            nn.ReLU(),
            nn.Dropout(0.25),
            nn.Linear(200, 100),
            nn.ReLU(),
            nn.Dropout(0.25),
            nn.Linear(100, 10))

    def forward(self, x):
        features = self.feature_extractor(x)
        out = self.classifier(features)
        return out

model = Net()

谢谢您详细的回复!我有一个问题,就是对于您的递归方法,如果输入不是一个正方形,那么我需要递归两次(计算高度和宽度),这样我才能计算出 HW(最后卷积层的输出通道数),对吧?另外我注意到,使用“6444”作为尺寸也会被网络接受。这是为什么呢? - Vanstorm
1
确实,您需要为两个空间维度'h'和'w'分别计算'hwout_channels',并进行两次操作。注意:您可以使用'6444'初始化'fc1',但这并不意味着推理是有效的:只需使用'model(torch.empty(1,3,32,32))'进行检查即可。如果您没有将'fc1'的'in_channels'设置为'256',它将在'F.relu(self.fc1(x))'处引发错误! - Ivan

-1
很晚才回复这个问题,但是以防有人在网上搜索时找到这里,特别适用于Pytorch的情况:
已经给出的答案已经足够了,并且能够很好地理解卷积和池化层的工作原理,从长远来看这是最好的。
然而,对于Pytorch来说,有更快的方法来解决这个问题。可以使用torchinfo库中的summary方法,并将模型与一个虚拟输入一起传入。这将打印出模型所有层中图像维度的摘要信息,以及其他信息。
另一种更快的方法是,如果不想在完全连接层之前麻烦于模型的架构,可以使用Pytorch的Lazy模块。这些是标准模块(被标记为实验性版本),如conv2d的推断版本,它们可以自动推断输入特征的数量。因此,在设置架构时不需要将形状作为参数传递。 对于这种情况,有一个名为nn.LazyLinear的模块,它是一个只需要所需输出数量作为参数的nn.Linear模块。

Check https://pytorch.org/docs/stable/generated/torch.nn.modules.lazy.LazyModuleMixin.html#torch.nn.modules.lazy.LazyModuleMixin

对于整个模块家族及其限制。

虽然这个链接可能回答了问题,但最好在这里包含答案的关键部分,并提供链接作为参考。仅有链接的答案如果链接页面发生变化,就可能失效。- 来自审查 - Chenmunka

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接