Q:

Dimensions produce by PyTorch convolution and pooling

In the basic PyTorch tutorial there is a comment in the code for an example network that has m confused:
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 6, 3)
        self.conv2 = nn.Conv2d(6, 16, 3)
        self.fc1 = nn.Linear(16 * 6 * 6, 120)  # 6*6 from image dimension
        # ...

    def forward(self, x):
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)
        # 16 * 6 * 6 is used here (after flattening)

I'm not sure where the $6$ comes from. Later in the tutorial the images are assumed to be $32\times 32$, so is it simply 
$$\left \lfloor{(\left \lfloor{(32-2)/2}\right \rfloor-2)/2}\right \rfloor    = 6$$
or is there something I'm missing about how PyTorch does padding (or some other beginner misunderstanding)?

A:

According to the documentation, the height of the output of a nn.Conv2d layer is given by
$$
H_\text{out} = \left\lfloor \frac{H_\text{in} + 2 \times \text{padding}_0 - \text{dilation}_0 \times (\text{kernel size}_0 - 1) - 1}{\text{stride}_0} +1 \right\rfloor
$$
and analogously for the width, where $\text{padding}_0$ etc are arguments provided to the class.
The same formulae are used for nn.MaxPool2d. The documentation tells us that the default stride of nn.MaxPool2d is the kernel size.
When we apply these operations sequentially, the input to each operation is the output of the previous operation. So we can verify that the final dimension is $6 \times 6$ because

first convolution output: $ 30 \times 30$
first max pool output: $ 15 \times 15$
second convolution output: $ 13 \times 13$
second max pool output: $ 6 \times 6$

The largest reductions in size come from the max pooling, due to its default configuration using a stride equal to the kernel size, which is $2$ in this example. The convolutional filters have a stride of $1$ by default. 

