ResNet: from scratch
By: Tanmay Debnath
Tags: Cnn, Convolutional Network, AI, Convolution Neural Net, Machine Learning

CNN architectures are some of the important forms of analyzing images and video-graphics materials. Some of the important applications of the same can be noticed in the Biomedical Engineering department as well, especially in the BioImaging section. As per the literature review, a lot of such CNN architectures have been devised out and have been implemented to study the detection of abnormalities in the human body. Here, I am going to explore the “making of ResNet: from scratch”

Modules: PyTorch, Cuda (optional)

If you are confused about how to install PyTorch in your system then you might want to check out this link here. It would help you! Moving forward…

ResNet:

This is a deep-architecture with a lot of deeper strands. Deep Networks are used for a better understanding of the important aspects and features of the image available. Let’s build…

import torch

import torch.nn as nn

These are the important two lines that you would import into your environment. Now, LET ME WARN YOU! This is a hefty network and would take some time to finish up its job with the dataset. For newbies, if you see your environment screen stuck at one position for a long time, don’t freak out because the computer is just doing what you have asked it to do!

A little experience in coding is expected because we are going to be defining classes and this would eventually involve some inheritance questions and this might whirl your brain. So, to stop that from happening, binge into some tutorials on OOPs.

class resnet_block(nn.Module):

def __init__(self, in_channels, out_channels,

idt_downsample=None, stride=1):

super(resnet_block, self).__init__()

self.expansion = 4

self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=1, padding=0)

self.bn1 = nn.BatchNorm2d(out_channels)

self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=stride, padding=1)

self.bn2 = nn.BatchNorm2d(out_channels)

self.conv3 = nn.Conv2d(out_channels, out_channels*self.expansion, kernel_size=1, stride=1, padding=0)

self.bn3 = nn.BatchNorm2d(out_channels*self.expansion)

self.relu = nn.ReLU()

self.identity_downsample = idt_downsample

The code has been structured for repeated usage and minimal effort in remembering stuff. For the initialization stage, a resnet block has been created that would contain all the necessary parameters mentioned in the paper. Since ResNet works on the residual networks theory hence we require an extra identical matrix of the commensurate the requirements of the same.

Now, since we have created our resnet block, it’s time that we move ‘forward’ with it and define the ‘forward’ function.

def forward(self, x):

identity = x

x = self.conv1(x)

x = self.bn1(x)

x = self.relu(x)

x = self.conv2(x)

x = self.bn2(x)

x = self.relu(x)

x = self.conv3(x)

x = self.bn3(x)



if self.identity_downsample is not None:

identity = self.identity_downsample(identity)



x += identity

x = self.relu(x)

return x

The ‘forward’ function is defined to work after the initialization phase and contains all the necessary steps (as defined in the research paper). Now, whenever the resnet block would be called, it would be the ‘forward’ function that would do the main task for the class. Now Since the ‘forward’ function is written, we need to move forward with the actual ResNet modeling.

For ResNet modeling, we are going to follow the actual structure, as defined in the paper. Now, here is the case where we would be finding the inheritance concepts in action. We would take the instance of the original class and would be implementing it in our current class. If you understand the paper well then it would be completely logical for you and you would be following up in no time!

Now, formulating the ‘ResNet’ block.

class ResNet(nn.Module):

def __init__(self, resnet_block, layers, img_channels, num_classes):

super(ResNet, self).__init__()

self.in_channels = 64

self.conv1 = nn.Conv2d(img_channels, 64, kernel_size=7, stride=2, padding=3)

self.bn1 = nn.BatchNorm2d(64)

self.relu = nn.ReLU()

self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)



# ResNet Layers

self.layer1 = self._layers(resnet_block, layers[0], out_channels=64, stride=1)

self.layer2 = self._layers(resnet_block, layers[1], out_channels=128, stride=2)

self.layer3 = self._layers(resnet_block, layers[2], out_channels=256, stride=2)

self.layer4 = self._layers(resnet_block, layers[3], out_channels=512, stride=2)



self.avgpool = nn.AdaptiveAvgPool2d((1,1))

self.fc = nn.Linear(512*4, num_classes)

From the above block, you can observe that there is another method called the ‘_layers’. We haven’t defined it yet. This would be the original defining block for our model. The ‘_layers’ block would be having the definition for the identity downsample part (mentioned in the research paper).

Now, the main part of the ResNet class that would actually run our algorithm is the ‘forward’ definition function. This function would be the basics for the implementation of the ResNet architecture and each time we call for the function, our model would find the ‘forward’ definition for the implementation.

def forward(self, x):

x = self.conv1(x)

x = self.bn1(x)

x = self.relu(x)

x = self.maxpool(x)



x = self.layer1(x)

x = self.layer2(x)

x = self.layer3(x)

x = self.layer4(x)



x = self.avgpool(x)

x = x.reshape(x.shape[0], -1)

x = self.fc(x)

return x

Now, for the mysterious ‘_layers’ function. This has been speculation for many, I would guess. This is the place where we would be stacking up layers and based on the inputs given, it would be modeling the desired architecture versions (eg: ResNet50, ResNet101, etc).

def _layers(self, resnet_block, no_residual_blocks, out_channels, stride):

identity_downsample = None

layers = []



if stride != 1 or self.in_channels != out_channels * 4:

identity_downsample = nn.Sequential(nn.Conv2d(self.in_channels, out_channels*4, kernel_size=1,

stride=stride),

nn.BatchNorm2d(out_channels*4))



layers.append(resnet_block(self.in_channels, out_channels, identity_downsample, stride))

self.in_channels = out_channels * 4



for i in range(no_residual_blocks - 1):

layers.append(resnet_block(self.in_channels, out_channels))



return nn.Sequential(*layers)

So, we have defined everything and it’s time for us to determine whether our speculations are correct or not. We can implement the code block given below to test the architecture.

def ResNet50(img_channels=3, num_classes=1000):

return ResNet(resnet_block, [3,4,6,3], img_channels, num_classes)

And we are done! That was a hefty job to go through. Thanks for sticking up to the last. This is an important architecture in the domains of CNN and understanding it is difficult indeed! If you need further help, see the sections below…

Help on the way!

If you are still confused and want to have a full look into the actual methods and how have everything being applied then kindly direct yourself to this link.

References

The entire segment is inspired by the original works of the ResNet researchers. Kindly do have a look!