233 lines
7.3 KiB
Python
233 lines
7.3 KiB
Python
"""
|
|
Based on my earlier work:
|
|
https://github.com/hideyukiinada/cifar10/blob/master/project/keras_25
|
|
"""
|
|
from typing import List, Dict, Any, Tuple
|
|
import torch
|
|
import torch.nn as nn
|
|
import torch.nn.functional as F
|
|
|
|
class SingleConvBlock(nn.Module):
|
|
"""
|
|
pragma: skip_doc
|
|
"""
|
|
|
|
def __init__(self, in_channels, out_channels, kernel_size, strides):
|
|
super().__init__()
|
|
padding = kernel_size // 2
|
|
self.conv = nn.Conv2d(
|
|
in_channels=in_channels,
|
|
out_channels=out_channels,
|
|
kernel_size=kernel_size,
|
|
stride=strides,
|
|
padding=padding,
|
|
bias=False)
|
|
self.bn = nn.BatchNorm2d(out_channels)
|
|
self.relu = nn.ReLU()
|
|
|
|
torch.nn.init.kaiming_normal_(self.conv.weight,
|
|
nonlinearity='relu')
|
|
|
|
|
|
def forward(self, x):
|
|
x = self.conv(x)
|
|
x = self.bn(x)
|
|
x = self.relu(x)
|
|
return x
|
|
|
|
|
|
class SingleConvBlockNoActivation(nn.Module):
|
|
"""
|
|
pragma: skip_doc
|
|
"""
|
|
|
|
def __init__(self, in_channels, out_channels, kernel_size, strides):
|
|
super().__init__()
|
|
padding = kernel_size // 2
|
|
self.conv = nn.Conv2d(
|
|
in_channels=in_channels,
|
|
out_channels=out_channels,
|
|
kernel_size=kernel_size,
|
|
stride=strides,
|
|
padding=padding,
|
|
bias=False)
|
|
self.bn = nn.BatchNorm2d(out_channels)
|
|
|
|
torch.nn.init.xavier_normal_(self.conv.weight)
|
|
|
|
def forward(self, x):
|
|
x = self.conv(x)
|
|
x = self.bn(x)
|
|
return x
|
|
|
|
|
|
class SingleResnetBlock(nn.Module):
|
|
"""
|
|
pragma: skip_doc
|
|
"""
|
|
|
|
def __init__(self, in_channels, out_channels, kernel_size=3, downsample=False):
|
|
super().__init__()
|
|
self.in_channels = in_channels
|
|
self.out_channels = out_channels
|
|
self.kernel_size = kernel_size
|
|
self.downsample = downsample
|
|
|
|
if self.downsample:
|
|
self.single_conv_k1_s2_na = \
|
|
SingleConvBlockNoActivation(
|
|
in_channels=self.in_channels,
|
|
out_channels=self.out_channels,
|
|
kernel_size=1,
|
|
strides=2) # Downsample
|
|
|
|
self.first_conv_block = \
|
|
SingleConvBlock(
|
|
in_channels=self.in_channels,
|
|
out_channels=self.out_channels,
|
|
kernel_size=self.kernel_size,
|
|
strides=2)
|
|
else:
|
|
self.first_conv_block = \
|
|
SingleConvBlock(
|
|
in_channels=self.in_channels,
|
|
out_channels=self.out_channels,
|
|
kernel_size=self.kernel_size,
|
|
strides=1)
|
|
|
|
if self.in_channels != self.out_channels:
|
|
self.single_conv_k1_s1_na = \
|
|
SingleConvBlockNoActivation(
|
|
in_channels=self.in_channels,
|
|
out_channels=self.out_channels,
|
|
kernel_size=1,
|
|
strides=1) # Channel change without downsampling
|
|
|
|
self.second_conv = \
|
|
SingleConvBlockNoActivation(
|
|
in_channels=self.out_channels,
|
|
out_channels=self.out_channels,
|
|
kernel_size=self.kernel_size,
|
|
strides=1)
|
|
|
|
self.relu = nn.ReLU()
|
|
|
|
def forward(self, inputs):
|
|
original_tens = inputs
|
|
tens = inputs
|
|
|
|
if self.downsample:
|
|
original_tens = self.single_conv_k1_s2_na(original_tens)
|
|
elif self.in_channels != self.out_channels:
|
|
original_tens = self.single_conv_k1_s1_na(original_tens)
|
|
|
|
tens = self.first_conv_block(tens)
|
|
|
|
# Second conv
|
|
tens = self.second_conv(tens)
|
|
tens = tens + original_tens
|
|
tens = self.relu(tens)
|
|
return tens
|
|
|
|
|
|
class NResnetBlocks(nn.Module):
|
|
"""
|
|
pragma: skip_doc
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
num_blocks,
|
|
in_channels,
|
|
out_channels,
|
|
kernel_size=3,
|
|
downsample=False):
|
|
super().__init__()
|
|
self.num_blocks = num_blocks
|
|
self.in_channels = in_channels
|
|
self.out_channels = out_channels
|
|
self.kernel_size = kernel_size
|
|
self.downsample = downsample
|
|
|
|
if self.downsample:
|
|
self.single_resnet_block = SingleResnetBlock(in_channels=self.in_channels,
|
|
out_channels=self.out_channels,
|
|
kernel_size=kernel_size,
|
|
downsample=True)
|
|
block_count = self.num_blocks - 1
|
|
else:
|
|
block_count = self.num_blocks
|
|
|
|
resnet_blocks = []
|
|
for i in range(block_count):
|
|
if i == 0 and not self.downsample:
|
|
channels = self.in_channels
|
|
else:
|
|
channels = self.out_channels
|
|
resnet_blocks.append(
|
|
SingleResnetBlock(
|
|
in_channels=channels,
|
|
out_channels=self.out_channels,
|
|
kernel_size=self.kernel_size)
|
|
)
|
|
self.resnet_blocks = nn.ModuleList(resnet_blocks)
|
|
|
|
def forward(self, inputs):
|
|
tens = inputs
|
|
if self.downsample:
|
|
tens = self.single_resnet_block(tens)
|
|
for block in self.resnet_blocks:
|
|
tens = block(tens)
|
|
|
|
return tens
|
|
|
|
class ResnetModel(nn.Module):
|
|
"""
|
|
pragma: skip_doc
|
|
"""
|
|
|
|
def __init__(self,
|
|
num_blocks,
|
|
in_channels,
|
|
input_height,
|
|
input_width,
|
|
output_dim):
|
|
super().__init__()
|
|
self.num_blocks = num_blocks
|
|
self.in_channels = in_channels
|
|
|
|
self.single_conv_block = SingleConvBlock(
|
|
in_channels, out_channels=64, kernel_size=3, strides=1)
|
|
|
|
self.n_resnet_blocks64 = NResnetBlocks(
|
|
num_blocks, in_channels=64, out_channels=64, kernel_size=3)
|
|
self.n_resnet_blocks128 = NResnetBlocks(
|
|
num_blocks, in_channels=64, out_channels=128, kernel_size=3, downsample=True)
|
|
self.n_resnet_blocks256 = NResnetBlocks(
|
|
num_blocks, in_channels=128, out_channels=256, kernel_size=3, downsample=True)
|
|
|
|
self.gap = nn.AdaptiveAvgPool2d((1, 1)) # Global Average Pooling
|
|
|
|
# Compute output size dynamically
|
|
with torch.no_grad():
|
|
dummy_input = torch.zeros(1, in_channels, input_height, input_width)
|
|
dummy_output = self._forward_features(dummy_input)
|
|
feature_dim = dummy_output.shape[1] * dummy_output.shape[2] * dummy_output.shape[3]
|
|
|
|
self.linear = nn.Linear(feature_dim, output_dim) # Use computed feature size
|
|
|
|
def _forward_features(self, inputs):
|
|
"""Forward pass to compute feature map dimensions."""
|
|
x = self.single_conv_block(inputs)
|
|
x = self.n_resnet_blocks64(x)
|
|
x = self.n_resnet_blocks128(x)
|
|
x = self.n_resnet_blocks256(x)
|
|
return self.gap(x) # Before flattening
|
|
|
|
def forward(self, inputs):
|
|
tens = self._forward_features(inputs)
|
|
tens = torch.flatten(tens, start_dim=1) # Flatten to (batch_size, computed_feature_dim)
|
|
tens = self.linear(tens)
|
|
return tens
|
|
|