utmosv2-torch-native / modeling_efficientnet.py
Project Beatrice
Add UTMOSv2 torch-native model
dc6da71
Raw
History Blame Contribute Delete
6.68 kB
"""Small, timm-compatible EfficientNetV2-S inference implementation.
The Apache-2.0 timm implementation was reduced to the layers exercised by
``tf_efficientnetv2_s.in21k_ft_in1k`` and modified to remove its runtime
dependency. Module names intentionally match timm so the official UTMOS v2
state dictionary loads; see LICENSE.
"""
from __future__ import annotations
import math
import torch
import torch.nn.functional as F
from torch import nn
class Conv2dSame(nn.Conv2d):
def forward(self, x: torch.Tensor) -> torch.Tensor:
height, width = x.shape[-2:]
stride_h, stride_w = self.stride
kernel_h, kernel_w = self.kernel_size
dilation_h, dilation_w = self.dilation
output_h = math.ceil(height / stride_h)
output_w = math.ceil(width / stride_w)
pad_h = max(
(output_h - 1) * stride_h
+ (kernel_h - 1) * dilation_h
+ 1
- height,
0,
)
pad_w = max(
(output_w - 1) * stride_w
+ (kernel_w - 1) * dilation_w
+ 1
- width,
0,
)
if pad_h or pad_w:
x = F.pad(
x,
(
pad_w // 2,
pad_w - pad_w // 2,
pad_h // 2,
pad_h - pad_h // 2,
),
)
return F.conv2d(
x,
self.weight,
self.bias,
self.stride,
0,
self.dilation,
self.groups,
)
class BatchNormAct2d(nn.BatchNorm2d):
def __init__(self, channels: int, *, activate: bool) -> None:
super().__init__(channels, eps=1e-3, momentum=0.1)
self.activate = activate
def forward(self, x: torch.Tensor) -> torch.Tensor:
x = super().forward(x)
return F.silu(x, inplace=True) if self.activate else x
def conv3x3(
in_channels: int,
out_channels: int,
stride: int,
*,
groups: int = 1,
) -> nn.Conv2d:
if stride == 2:
return Conv2dSame(
in_channels,
out_channels,
3,
stride=2,
groups=groups,
bias=False,
)
return nn.Conv2d(
in_channels,
out_channels,
3,
stride=1,
padding=1,
groups=groups,
bias=False,
)
class ConvBnAct(nn.Module):
def __init__(self) -> None:
super().__init__()
self.conv = conv3x3(24, 24, 1)
self.bn1 = BatchNormAct2d(24, activate=True)
def forward(self, x: torch.Tensor) -> torch.Tensor:
residual = x
x = self.bn1(self.conv(x))
return x + residual
class EdgeResidual(nn.Module):
def __init__(
self,
in_channels: int,
out_channels: int,
expansion: int,
stride: int,
) -> None:
super().__init__()
expanded_channels = in_channels * expansion
self.conv_exp = conv3x3(in_channels, expanded_channels, stride)
self.bn1 = BatchNormAct2d(expanded_channels, activate=True)
self.conv_pwl = nn.Conv2d(expanded_channels, out_channels, 1, bias=False)
self.bn2 = BatchNormAct2d(out_channels, activate=False)
self.has_residual = stride == 1 and in_channels == out_channels
def forward(self, x: torch.Tensor) -> torch.Tensor:
residual = x
x = self.bn1(self.conv_exp(x))
x = self.bn2(self.conv_pwl(x))
return x + residual if self.has_residual else x
class SqueezeExcite(nn.Module):
def __init__(self, expanded_channels: int, reduced_channels: int) -> None:
super().__init__()
self.conv_reduce = nn.Conv2d(expanded_channels, reduced_channels, 1)
self.conv_expand = nn.Conv2d(reduced_channels, expanded_channels, 1)
def forward(self, x: torch.Tensor) -> torch.Tensor:
scale = x.mean((2, 3), keepdim=True)
scale = F.silu(self.conv_reduce(scale), inplace=True)
return x * torch.sigmoid(self.conv_expand(scale))
class InvertedResidual(nn.Module):
def __init__(
self,
in_channels: int,
out_channels: int,
expansion: int,
stride: int,
) -> None:
super().__init__()
expanded_channels = in_channels * expansion
self.conv_pw = nn.Conv2d(in_channels, expanded_channels, 1, bias=False)
self.bn1 = BatchNormAct2d(expanded_channels, activate=True)
self.conv_dw = conv3x3(
expanded_channels,
expanded_channels,
stride,
groups=expanded_channels,
)
self.bn2 = BatchNormAct2d(expanded_channels, activate=True)
self.se = SqueezeExcite(expanded_channels, in_channels // 4)
self.conv_pwl = nn.Conv2d(expanded_channels, out_channels, 1, bias=False)
self.bn3 = BatchNormAct2d(out_channels, activate=False)
self.has_residual = stride == 1 and in_channels == out_channels
def forward(self, x: torch.Tensor) -> torch.Tensor:
residual = x
x = self.bn1(self.conv_pw(x))
x = self.bn2(self.conv_dw(x))
x = self.se(x)
x = self.bn3(self.conv_pwl(x))
return x + residual if self.has_residual else x
def make_stage(
block_type: type[EdgeResidual | InvertedResidual],
in_channels: int,
out_channels: int,
expansion: int,
repeats: int,
stride: int,
) -> nn.Sequential:
blocks = [block_type(in_channels, out_channels, expansion, stride)]
blocks.extend(
block_type(out_channels, out_channels, expansion, 1)
for _ in range(repeats - 1)
)
return nn.Sequential(*blocks)
class EfficientNetV2S(nn.Module):
"""Feature-only TF EfficientNetV2-S with timm-compatible parameter names."""
def __init__(self) -> None:
super().__init__()
self.conv_stem = Conv2dSame(3, 24, 3, stride=2, bias=False)
self.bn1 = BatchNormAct2d(24, activate=True)
self.blocks = nn.Sequential(
nn.Sequential(ConvBnAct(), ConvBnAct()),
make_stage(EdgeResidual, 24, 48, 4, 4, 2),
make_stage(EdgeResidual, 48, 64, 4, 4, 2),
make_stage(InvertedResidual, 64, 128, 4, 6, 2),
make_stage(InvertedResidual, 128, 160, 6, 9, 1),
make_stage(InvertedResidual, 160, 256, 6, 15, 2),
)
self.conv_head = nn.Conv2d(256, 1280, 1, bias=False)
self.bn2 = BatchNormAct2d(1280, activate=True)
def forward(self, x: torch.Tensor) -> torch.Tensor:
x = self.bn1(self.conv_stem(x))
x = self.blocks(x)
x = self.bn2(self.conv_head(x))
return x