atiwari751 commited on
Commit
bf843fe
·
0 Parent(s):

ResNet 50 base for CIFAR-10

Browse files
Files changed (4) hide show
  1. .gitignore +6 -0
  2. README.md +13 -0
  3. resnet_execute.py +82 -0
  4. resnet_model.py +100 -0
.gitignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ data/
2
+ .venv
3
+ __pycache__
4
+ ResNet 50_Model.xlsx
5
+ ~$ResNet 50_Model.xlsx
6
+
README.md ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ImageNet 1k Image Classification with ResNet 50
2
+
3
+
4
+ ## Model Architecture
5
+
6
+
7
+ ## Data Augmentations
8
+
9
+
10
+
11
+ ## Model Results
12
+
13
+
resnet_execute.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torchvision
3
+ import torchvision.transforms as transforms
4
+ from torch.utils.data import DataLoader
5
+ import torch.nn as nn
6
+ import torch.optim as optim
7
+ from resnet_model import ResNet50
8
+ from tqdm import tqdm
9
+
10
+ # Define transformations
11
+ transform = transforms.Compose([
12
+ transforms.ToTensor(),
13
+ transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2470, 0.2435, 0.2616))
14
+ ])
15
+
16
+ # Load CIFAR-10 dataset
17
+ trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
18
+ trainloader = DataLoader(trainset, batch_size=128, shuffle=True, num_workers=4)
19
+
20
+ testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
21
+ testloader = DataLoader(testset, batch_size=1000, shuffle=False, num_workers=4)
22
+
23
+ # Initialize model, loss function, and optimizer
24
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
25
+ model = ResNet50().to(device)
26
+ criterion = nn.CrossEntropyLoss()
27
+ optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9, weight_decay=5e-4)
28
+
29
+ # Training function
30
+ def train(model, device, train_loader, optimizer, criterion, epoch):
31
+ model.train()
32
+ running_loss = 0.0
33
+ correct = 0
34
+ total = 0
35
+ pbar = tqdm(train_loader)
36
+
37
+ for batch_idx, (inputs, targets) in enumerate(pbar):
38
+ inputs, targets = inputs.to(device), targets.to(device)
39
+ optimizer.zero_grad()
40
+
41
+ outputs = model(inputs)
42
+ loss = criterion(outputs, targets)
43
+ loss.backward()
44
+ optimizer.step()
45
+
46
+ running_loss += loss.item()
47
+ _, predicted = outputs.max(1)
48
+ total += targets.size(0)
49
+ correct += predicted.eq(targets).sum().item()
50
+
51
+ pbar.set_description(desc=f'Epoch {epoch} | Loss: {loss.item():.4f} | Accuracy: {100.*correct/total:.2f}%')
52
+
53
+ return 100.*correct/total
54
+
55
+ # Testing function
56
+ def test(model, device, test_loader, criterion):
57
+ model.eval()
58
+ test_loss = 0
59
+ correct = 0
60
+ total = 0
61
+
62
+ with torch.no_grad():
63
+ for inputs, targets in test_loader:
64
+ inputs, targets = inputs.to(device), targets.to(device)
65
+ outputs = model(inputs)
66
+ loss = criterion(outputs, targets)
67
+
68
+ test_loss += loss.item()
69
+ _, predicted = outputs.max(1)
70
+ total += targets.size(0)
71
+ correct += predicted.eq(targets).sum().item()
72
+
73
+ test_accuracy = 100.*correct/total
74
+ print(f'Test Loss: {test_loss/len(test_loader):.4f}, Accuracy: {test_accuracy:.2f}%')
75
+ return test_accuracy
76
+
77
+ # Main execution
78
+ if __name__ == '__main__':
79
+ for epoch in range(1, 6): # 20 epochs
80
+ train_accuracy = train(model, device, trainloader, optimizer, criterion, epoch)
81
+ test_accuracy = test(model, device, testloader, criterion)
82
+ print(f'Epoch {epoch} | Train Accuracy: {train_accuracy:.2f}% | Test Accuracy: {test_accuracy:.2f}%')
resnet_model.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ from torchsummary import summary
4
+
5
+ class Bottleneck(nn.Module): # Bottleneck module as a single class which will be used to create the ResNet model. Each bottleneck as 3 convolutions.
6
+ expansion = 4 # sets how much the bottleneck will expand the output channels of the last block to. Used 4 as per original paper.
7
+
8
+ def __init__(self, in_channels, out_channels, stride=1, downsample=None):
9
+ super(Bottleneck, self).__init__()
10
+ self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=1, bias=False)
11
+ self.bn1 = nn.BatchNorm2d(out_channels)
12
+ self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False) # note this is the convolution which will use a stride of 2 to match the input and output dimensions. This happens in the first block of layers 2, 3 and 4 only. All convolutions in all subsequent blocks in each of the layers use a stride of 1, as per the ResNet model.
13
+ self.bn2 = nn.BatchNorm2d(out_channels)
14
+ self.conv3 = nn.Conv2d(out_channels, out_channels * self.expansion, kernel_size=1, bias=False) # this is the convolutions where number of channels is expanded, as per the ResNet model.
15
+ self.bn3 = nn.BatchNorm2d(out_channels * self.expansion)
16
+ self.relu = nn.ReLU(inplace=True) # this will modify the original tensor rather than operating on a copy. Significant memory savings as this module is the fundamental repeating unit. Makes sense to use only in the last layer so that we're not unintentionally corrupting the input tensor in the previous layers.
17
+ self.downsample = downsample # helps match the output dimensions to the input dimensions for the special skip connection.
18
+
19
+ def forward(self, x):
20
+ identity = x
21
+
22
+ out = self.conv1(x)
23
+ out = self.bn1(out)
24
+ out = self.relu(out)
25
+
26
+ out = self.conv2(out)
27
+ out = self.bn2(out)
28
+ out = self.relu(out)
29
+
30
+ out = self.conv3(out)
31
+ out = self.bn3(out)
32
+
33
+ # Special skip connection - triggered only in the first block of layers 2, 3 and 4, where we need to downsample the input x to match the dimensions of F(x) after convolutions, to be able to add them up.
34
+ if self.downsample is not None:
35
+ identity = self.downsample(x)
36
+
37
+ out += identity # The ResNet addition is here; H(x) = F(x) + x. Skip connection by virtue of adding identity variable, which is the original input without convolutions.
38
+ out = self.relu(out)
39
+
40
+ return out
41
+
42
+ class ResNet50(nn.Module):
43
+ def __init__(self, num_classes=10): # num_classes to be set as per the dataset. 10 for CIFAR-10, 1000 for ImageNet 1k.
44
+ super(ResNet50, self).__init__()
45
+ self.in_channels = 64 # only used for the initiation of the first bottleneck block in the first layer.
46
+
47
+ ## See Excel sheet for Model Architecture
48
+
49
+ # Adjusted Initial Conv Layer for CIFAR-10
50
+ self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False)
51
+ self.bn1 = nn.BatchNorm2d(64)
52
+ self.relu = nn.ReLU(inplace=True) # as before, this will modify the input tensor. Good memory savings here as the input image will be large in size here.
53
+
54
+ # Layers with Bottleneck Blocks
55
+ self.layer1 = self._make_layer(Bottleneck, 64, 3) # stride is 1 here, so the downsampling will only adjust for the channel size in the first block of this layer
56
+ self.layer2 = self._make_layer(Bottleneck, 128, 4, stride=2) #stride input here used only in the first block of the layer for downsampling. This layer onwards, downsampling will adjust for both the image size and channel dimensions.
57
+ self.layer3 = self._make_layer(Bottleneck, 256, 6, stride=2)
58
+ self.layer4 = self._make_layer(Bottleneck, 512, 3, stride=2)
59
+
60
+ # Final Layers
61
+ self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
62
+ self.fc = nn.Linear(512 * Bottleneck.expansion, num_classes)
63
+
64
+ def _make_layer(self, block, out_channels, blocks, stride=1):
65
+ downsample = None
66
+ if stride != 1 or self.in_channels != out_channels * block.expansion: # triggered for all layers - for layer 1 this only adjusts the channel size as stride is 1, for layers 2,3 and 4 this adjusts both the channel size and dimensions with the stride.
67
+ downsample = nn.Sequential(
68
+ nn.Conv2d(self.in_channels, out_channels * block.expansion, kernel_size=1, stride=stride, bias=False),
69
+ nn.BatchNorm2d(out_channels * block.expansion),
70
+ )
71
+
72
+ layers = []
73
+ layers.append(block(self.in_channels, out_channels, stride, downsample)) # first bottleneck block of the layer which takes the stride and downsample inputs
74
+ self.in_channels = out_channels * block.expansion # sets the number of input channels for every subsequent block as the expanded output of the bottleneck from the first block. This is squeezed again by the first convolution of the new block, and expanded again by the last layer, continuing the trend.
75
+ for _ in range(1, blocks):
76
+ layers.append(block(self.in_channels, out_channels)) # all subsequent bottleneck blocks take default stride and downsample inputs, so no downsampling happens in any of the later blocks of any layer.
77
+
78
+ return nn.Sequential(*layers) #unpack the list layers[] defined above so that each block in the list is passed as a separate argument to nn.sequential, effectively creating all the blocks for a layer this function is called for.
79
+
80
+ def forward(self, x):
81
+ x = self.conv1(x)
82
+ x = self.bn1(x)
83
+ x = self.relu(x)
84
+
85
+ x = self.layer1(x)
86
+ x = self.layer2(x)
87
+ x = self.layer3(x)
88
+ x = self.layer4(x)
89
+
90
+ x = self.avgpool(x)
91
+ x = torch.flatten(x, 1)
92
+ x = self.fc(x)
93
+
94
+ return x
95
+
96
+ if __name__ == '__main__':
97
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
98
+ print(device)
99
+ model = ResNet50().to(device)
100
+ summary(model, input_size=(3, 32, 32))