import torch import torch.nn as nn import torch.nn.functional as F class LeNet5(nn.Module): def __init__(self, num_classes=10): super().__init__() self.conv1 = nn.Conv2d(1, 6, kernel_size=5) self.conv2 = nn.Conv2d(6, 16, kernel_size=5) self.pool = nn.AvgPool2d(2) self.fc1 = nn.Linear(16 * 5 * 5, 120) self.fc2 = nn.Linear(120, 84) self.fc3 = nn.Linear(84, num_classes) def forward(self, x): x = self.pool(torch.tanh(self.conv1(x))) x = self.pool(torch.tanh(self.conv2(x))) x = torch.flatten(x, 1) x = torch.tanh(self.fc1(x)) x = torch.tanh(self.fc2(x)) return self.fc3(x) class VGGBlock(nn.Module): def __init__(self, in_c, out_c): super().__init__() self.conv1 = nn.Conv2d(in_c, out_c, kernel_size=3, padding=1) self.bn1 = nn.BatchNorm2d(out_c) self.conv2 = nn.Conv2d(out_c, out_c, kernel_size=3, padding=1) self.bn2 = nn.BatchNorm2d(out_c) self.pool = nn.MaxPool2d(2) def forward(self, x): x = F.relu(self.bn1(self.conv1(x))) x = F.relu(self.bn2(self.conv2(x))) return self.pool(x) class MiniVGG(nn.Module): def __init__(self, num_classes=10): super().__init__() self.stack = nn.Sequential( VGGBlock(3, 32), VGGBlock(32, 64), VGGBlock(64, 128), ) self.head = nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Flatten(), nn.Linear(128, num_classes), ) def forward(self, x): return self.head(self.stack(x)) class BasicBlock(nn.Module): def __init__(self, in_c, out_c, stride=1): super().__init__() self.conv1 = nn.Conv2d(in_c, out_c, kernel_size=3, stride=stride, padding=1, bias=False) self.bn1 = nn.BatchNorm2d(out_c) self.conv2 = nn.Conv2d(out_c, out_c, kernel_size=3, stride=1, padding=1, bias=False) self.bn2 = nn.BatchNorm2d(out_c) if stride != 1 or in_c != out_c: self.shortcut = nn.Sequential( nn.Conv2d(in_c, out_c, kernel_size=1, stride=stride, bias=False), nn.BatchNorm2d(out_c), ) else: self.shortcut = nn.Identity() def forward(self, x): out = F.relu(self.bn1(self.conv1(x))) out = self.bn2(self.conv2(out)) out = out + self.shortcut(x) return F.relu(out) class TinyResNet(nn.Module): def __init__(self, num_classes=10): super().__init__() self.stem = nn.Sequential( nn.Conv2d(3, 32, kernel_size=3, stride=1, padding=1, bias=False), nn.BatchNorm2d(32), nn.ReLU(inplace=True), ) self.layer1 = self._make_group(32, 32, num_blocks=2, stride=1) self.layer2 = self._make_group(32, 64, num_blocks=2, stride=2) self.layer3 = self._make_group(64, 128, num_blocks=2, stride=2) self.layer4 = self._make_group(128, 256, num_blocks=2, stride=2) self.head = nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Flatten(), nn.Linear(256, num_classes), ) def _make_group(self, in_c, out_c, num_blocks, stride): blocks = [BasicBlock(in_c, out_c, stride=stride)] for _ in range(num_blocks - 1): blocks.append(BasicBlock(out_c, out_c, stride=1)) return nn.Sequential(*blocks) def forward(self, x): x = self.stem(x) x = self.layer1(x) x = self.layer2(x) x = self.layer3(x) x = self.layer4(x) return self.head(x) def summary(name, net, x): net.eval() with torch.no_grad(): y = net(x) params = sum(p.numel() for p in net.parameters()) trainable = sum(p.numel() for p in net.parameters() if p.requires_grad) print(f"{name:12s} input {tuple(x.shape)} -> output {tuple(y.shape)} " f"params {params:>10,} trainable {trainable:>10,}") def per_group_params(net): return {name: sum(p.numel() for p in mod.parameters()) for name, mod in net.named_children()} def main(): summary("LeNet5", LeNet5(), torch.randn(1, 1, 32, 32)) summary("MiniVGG", MiniVGG(), torch.randn(1, 3, 32, 32)) summary("TinyResNet", TinyResNet(), torch.randn(1, 3, 32, 32)) print("\nTinyResNet parameters by group:") for name, n in per_group_params(TinyResNet()).items(): print(f" {name:8s} {n:>10,}") if __name__ == "__main__": main()