Simple Conv Net
I'm new on pytorch and I would like your light on a small net I try to setup.


class PzConv2d(nn.Module):
    """ Convolution 2D Layer followed by PReLU activation
    """
    def __init__(self, n_in_channels, n_out_channels, **kwargs):
        super(PzConv2d, self).__init__()
        self.conv = nn.Conv2d(n_in_channels, n_out_channels, bias=True,
                            **kwargs)
        self.activ = nn.ReLU()

    def forward(self, x):
        x = self.conv(x)
        return self.activ(x)


class PzPool2d(nn.Module):
    """ Average Pooling Layer
    """
    def __init__(self, kernel_size, stride, padding=0):
        super(PzPool2d, self).__init__()
        self.pool = nn.AvgPool2d(kernel_size=kernel_size,
                                 stride=stride,
                                 padding=padding,
                                 ceil_mode=True,
                                 count_include_pad=False)

    def forward(self, x):
        return self.pool(x)

class PzFullyConnected(nn.Module):
    """ Dense or Fully Connected Layer followed by ReLU
    """
    def __init__(self, n_inputs, n_outputs, withrelu=True, **kwargs):
        super(PzFullyConnected, self).__init__()
        self.withrelu = withrelu
        self.linear = nn.Linear(n_inputs, n_outputs, bias=True)
        self.activ = nn.ReLU()

    def forward(self, x):
        x = self.linear(x)
        if self.withrelu:
            x = self.activ(x)
        return x


class NetCNN(nn.Module):
    def __init__(self,n_input_channels,debug=False):
        super(NetCNN, self).__init__()

        self.n_bins = 180
        self.debug = debug
        self.conv0 = PzConv2d(n_in_channels=n_input_channels,
                              n_out_channels=64,
                              kernel_size=5,padding=2)
        self.pool0 = PzPool2d(kernel_size=2,stride=2,padding=0)

        self.conv1 = PzConv2d(n_in_channels=64,
                              n_out_channels=92,
                              kernel_size=3,padding=2)
        self.pool1 = PzPool2d(kernel_size=2,stride=2,padding=0)

        self.conv2 = PzConv2d(n_in_channels=92,
                              n_out_channels=128,
                              kernel_size=3,padding=2)
        self.pool2 = PzPool2d(kernel_size=2,stride=2,padding=0)


        self.fc0 = PzFullyConnected(n_inputs=12800,n_outputs=1024)
        self.fc1 = PzFullyConnected(n_inputs=1024,n_outputs=self.n_bins)

    def num_flat_features(self, x):
        size = x.size()[1:]  # all dimensions except the batch dimension
        num_features = 1
        for s in size:
            num_features *= s
        return num_features

    def forward(self, x, dummy):
        # x:image tensor N_batch, Channels, Height, Width
        #    size N, Channels:=5 filtres, H,W = 64 pixels
        # dummy: is not used

        # stage 0 conv 64 x 5x5
        x = self.conv0(x)
        x = self.pool0(x)

        # stage 1 conv 92 x 3x3
        x = self.conv1(x)
        x = self.pool1(x)

        # stage 2 conv 128 x 3x3
        x = self.conv2(x)
        x = self.pool2(x)

        x = self.fc0(x.view(-1,self.num_flat_features(x)))
        x = self.fc1(x)

        output = x


        return output


I have checked that the dimensions of the intermediate "x" tensor in the forward process is well shaped (at least when I send a random input image tensor). But if you see something strange let me know, please.

Now, I have seen code with F."function" sequence in the forward method, instead of declaring the different layers as I have done. Does it make a difference?

(Notice that I'm using  F.cross_entropy as loss function, so I do not end may network by a SoftMax.)

Thanks.

As you can see from pytorch doc there are "layers" and there are "functionals". You already noticed that they are quite similar, but there is a difference:
A layer is often more than just a "functional" it also wraps around trainable parameters.
Therefore, you can have a F.conv2d(...) in your forward() function, but you will have to manually provide (store/update) the weights/kernel for this convolution. On the other hand, if you are using nn.Conv2d pytorch takes care of managing/storing/updating the weights/kernels for you.
Some layers have no internal parameters/buffers (e.g., nn.ReLU, nn.Softmax etc.) thus you can choose if you want to have a "layer" for this operation or only calling the appropriate functional in your forward() function. It is a matter of convenience and habits, it's up to you.
