How is it possible to get the output size of `n` Consecutive Convolutional layers?

anubhav4sachan: <p>Given network architecture, what are the possible ways to define fully connected layer <code>fc1</code> to have a generalized structure such as <code>nn.Linear($size_of_previous_layer$, 50)</code>?</p>
<p>The main issue arising is due to <code>x = F.relu(self.fc1(x))</code> in the forward function. After using the <code>flatten</code>, I need to incorporate numerous dense layers. But to my understanding, <code>self.fc1</code> must be initialized and hence, needs a size (to be calculated from previous layers). How can I declare the <code>self.fc1</code> layer in a generalized manner?</p>
<p><em>My Thought:</em><br>
To get the size, I can calculate the size of the outputs from each of Convolution layer, and since I have just 3, it is feasible. But, in case of <em>n</em> layers, how can you get the output size from the final convolutional layer?</p>
<pre><code class="lang-auto">class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()

        self.conv1 = nn.Conv2d(3, 10, kernel_size=3, padding = 1)
        self.conv2 = nn.Conv2d(10, 20, kernel_size=3, padding = 1)
        self.conv2_drop = nn.Dropout2d(0.4)
        self.conv3 = nn.Conv2d(20, 40, kernel_size=3, padding = 1)
        self.conv3_drop = nn.Dropout2d(0.4)

        self.fc1 = nn.Linear(360, 50)  # self.fc1 = nn.Linear($size_of_previous_layer$, 50)

    def forward(self, x):
        x = F.relu(F.max_pool2d(self.conv1(x), 2))
        x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2))
        x = F.relu(F.max_pool2d(self.conv3_drop(self.conv3(x)), 2))

        x = x.flatten(1)

        x = F.relu(self.fc1(x))
        return F.log_softmax(x)
</code></pre>
<ul>
<li>Input to the following architecture can assumed to be [3, 32, 32] (num_of_channels, height, width).</li>
</ul>
<p><a class="mention" href="/u/ptrblck">@ptrblck</a>, could you help me?</p>
<p>PS:</p>
<ul>
<li>For single convolutional layer, it is quite easy. The question refers, if you have <em>n</em> convolutional layers.</li>
</ul>

lfolle: <p>You could put the kernel sizes that will be used to initialize the Conv layers in a list.<br>
Then you could write a small function that calculates the output size given the list and the input size. The number of channels is given by the last Conv layers  <code>num_features</code>.</p>

anubhav4sachan: <p><a class="mention" href="/u/lfolle">@lfolle</a>  That will be good for a fixed number of convolution layers and I, ultimately, will have to calculate for every layer which is not required.</p>
<p>The code needs to be robust and hence, need a general solution for the problem which can be applied to any number of convolutional layers.</p>

aponcedeleonch: <p>Maybe I am missing something here but wouldn’t <a class="mention" href="/u/lfolle">@lfolle</a> solution would work for <code>n</code> convolutional layers? You put the <code>n</code> kernel sizes, the <code>n</code> padding sizes, <code>n</code> dilations and <code>n</code> strides in lists and then calculate in a function what would be the output size after the <code>n</code> layers. Then you can just call that function at <code>__init__</code></p>
<pre><code class="lang-auto">def __init__(self):
    ....
    out_h, out_w = self.calc_out_conv_layers(in_h, in_w, kernels,
                                             paddings, dilations, strides)
    self.fc1 = nn.Linear(out_h*out_w*out_c, 50)
</code></pre>

anubhav4sachan: <p>Can you show me the contents of the function <code>calc_out_conv_layers</code>?</p>
<p>To my understanding, the function is calculating the output size from an immediate previously stated convolutional layer. This means, the function <code>self.calc_out_conv_layers</code> has to be called upon whenever I’m declaring a convolutional layer. This is not what I want.</p>
<p>In addition to this, the forward function has <code>x = F.relu(F.max_pool2d(self.conv1(x), 2))</code>, hence, the calc_out_conv_layers function needs to adapted <em>manually</em> according to the case if or not I’m using Pooling or not.</p>

aponcedeleonch: <p>I haven’t tested but I think it can be something like this:</p>
<pre><code class="lang-auto">def calc_out_conv_layers(self, in_h, in_w, kernels, paddings, dilations, strides):
    out_h = in_h
    out_w = in_w
    for ker, pad, dil, stri in zip(kernels, paddings, dilations, strides):
        out_h = (out_h + 2*pad - dil * (ker-1) - 1)/stri + 1
        out_w = (out_w + 2*pad - dil * (ker-1) - 1)/stri + 1

    return out_h, out_w
</code></pre>
<p>You would call this function only when you have finished adding all the <code>kernels, paddings, ...</code> to the appropriate list. But yes, you would need to modify this function to also take into account Pooling or not. Probably something more robust is instead of having calls to <code>F.max_pool2d</code> in the <code>forward</code> function you can add the pooling as modules at <code>__init__</code>  with <code>nn.MaxPool2d</code>. Then you can have a piece of code that iterates over all the registered modules and make the necessary calculations.</p>
<pre><code class="lang-auto">for m in self.modules():
    if type(m) is nn.Conv2d:
        // do some calculations 
    elif type(m) is nn.MaxPool2d:
        // do more calculations
</code></pre>
<p>I think what you are looking for is a function that does not exist. There’s no function that tells you automatically what would be the last shape of the last layer or of a sequence of convolutional layers. At least not one that I know of. You always have to make the calculations at the side.</p>

anubhav4sachan: <p>Looks good. This was my first thought for doing the calculation but I anticipated that there might a better dynamic way to do it, but now, I reckon, I have to continue treating the function as a layer as suggested.</p>
<p>I am, still, in a doubt and would love to ask you if there is a mathematical/conceptual way to do this <em>(quoted)</em> or we just have to calculate the output size layer by layer?</p>
<aside class="quote no-group" data-username="aponcedeleonch" data-post="6" data-topic="87300">
<div class="title">
<div class="quote-controls"></div>
<img loading="lazy" alt="" width="24" height="24" src="https://discuss.pytorch.org/user_avatar/discuss.pytorch.org/aponcedeleonch/48/25885_2.png" class="avatar"> aponcedeleonch:</div>
<blockquote>
<p>function that tells you automatically what would be the last shape of the last layer or of a sequence of convolutional layers</p>
</blockquote>
</aside>
<p>And yes, thanks for the suggestion.</p>

aponcedeleonch: <p>As far as I know the mathematical/conceptual way of doing it is layer by layer. This is because different input image sizes will have different output shape i.e. the output shape will be different for an input of size <code>(3, 128, 128)</code> than for an input size of <code>(3, 1024, 1024)</code>. There is no generalization because you will always have the variable of the input size. But if you find out a way I would also like to know it <img src="https://discuss.pytorch.org/images/emoji/apple/slight_smile.png?v=9" title=":slight_smile:" class="emoji" alt=":slight_smile:"></p>

lfolle: <p><a class="mention" href="/u/aponcedeleonch">@aponcedeleonch</a> solution is probably the best way to do this.<br>
As another approach you could also try to input an example tensor into the network and print the shape of the intermediate tensor before passing it to the fully-connected layer.</p>

Egor_Kraev: <p>My perhaps inefficient but general (and correct by construction:) ) way of doing it is to start with an example input, and when building up the model in <code>__init__()</code> feed that input through each layer in turn, get the size of the output, use that to initialize the next layer, and repeat until done <img src="https://discuss.pytorch.org/images/emoji/apple/wink.png?v=9" title=":wink:" class="emoji" alt=":wink:"></p>

anubhav4sachan: <p>Nah, that would be way too much manual work and quite infeasible.</p>

anubhav4sachan: <p>Yes, this was the question, to get the output in a generalized manner, and <a class="mention" href="/u/aponcedeleonch">@aponcedeleonch</a> 's solution is the best known method right now i.e. to calculate layer by layer.</p>

MR_Rational: <p>Can a pytorch insider please advise why the function is not built into the torch.nn layer classes ? or at least, implement it in a container, such as sequential?  In tensorfkow, you only need specify the input shape of first layer, then TF automatically figures out the output shape of each layer and pass the information to the input of next layer. This feature is critical to train a complex network, as otherwise you would have to check/update each layer if you change feature vector dimension.  thanks</p>