Files

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