DepthPolyp / model /modules /MiT_Encoder.py
ReaganWZY's picture
Upload DepthPolyp model artifacts
5acc7ae verified
Raw
History Blame Contribute Delete
16.7 kB
"""
Based on NVIDIA's SegFormer code, cleaned and made independent
"""
import math
import torch
import torch.nn as nn
import torch.nn.functional as F
from functools import partial
from typing import Dict, Sequence, List, Optional, Union, Callable, Any
import warnings
# ============================================================================
# Utility Functions
# ============================================================================
def _no_grad_trunc_normal_(tensor, mean, std, a, b):
"""Truncated normal initialization (from timm)"""
def norm_cdf(x):
return (1. + math.erf(x / math.sqrt(2.))) / 2.
with torch.no_grad():
l = norm_cdf((a - mean) / std)
u = norm_cdf((b - mean) / std)
tensor.uniform_(2 * l - 1, 2 * u - 1)
tensor.erfinv_()
tensor.mul_(std * math.sqrt(2.))
tensor.add_(mean)
tensor.clamp_(min=a, max=b)
return tensor
def trunc_normal_(tensor, mean=0., std=1., a=-2., b=2.):
"""Truncated normal initialization"""
return _no_grad_trunc_normal_(tensor, mean, std, a, b)
def to_2tuple(x):
"""Convert input to 2-tuple"""
if isinstance(x, (list, tuple)):
return tuple(x)
return (x, x)
class DropPath(nn.Module):
"""Drop paths (Stochastic Depth) per sample"""
def __init__(self, drop_prob=None):
super(DropPath, self).__init__()
self.drop_prob = drop_prob
def forward(self, x):
if self.drop_prob == 0. or not self.training:
return x
keep_prob = 1 - self.drop_prob
shape = (x.shape[0],) + (1,) * (x.ndim - 1)
random_tensor = keep_prob + torch.rand(shape, dtype=x.dtype, device=x.device)
random_tensor.floor_()
output = x.div(keep_prob) * random_tensor
return output
# ============================================================================
# Core Modules
# ============================================================================
class LayerNorm(nn.LayerNorm):
"""LayerNorm that supports both 3D (B, N, C) and 4D (B, C, H, W) inputs"""
def forward(self, x: torch.Tensor) -> torch.Tensor:
if x.ndim == 4:
batch_size, channels, height, width = x.shape
x = x.view(batch_size, channels, -1).transpose(1, 2)
x = F.layer_norm(x, self.normalized_shape, self.weight, self.bias, self.eps)
x = x.transpose(1, 2).view(batch_size, channels, height, width)
else:
x = F.layer_norm(x, self.normalized_shape, self.weight, self.bias, self.eps)
return x
class DWConv(nn.Module):
"""Depthwise Convolution"""
def __init__(self, dim=768):
super(DWConv, self).__init__()
self.dwconv = nn.Conv2d(dim, dim, 3, 1, 1, bias=True, groups=dim)
def forward(self, x: torch.Tensor, height: int, width: int) -> torch.Tensor:
batch_size, _, channels = x.shape
x = x.transpose(1, 2).view(batch_size, channels, height, width)
x = self.dwconv(x)
x = x.flatten(2).transpose(1, 2)
return x
class Mlp(nn.Module):
"""MLP with depthwise convolution"""
def __init__(
self,
in_features,
hidden_features=None,
out_features=None,
act_layer=nn.GELU,
drop=0.0,
):
super().__init__()
out_features = out_features or in_features
hidden_features = hidden_features or in_features
self.fc1 = nn.Linear(in_features, hidden_features)
self.dwconv = DWConv(hidden_features)
self.act = act_layer()
self.fc2 = nn.Linear(hidden_features, out_features)
self.drop = nn.Dropout(drop)
self.apply(self._init_weights)
def _init_weights(self, m):
if isinstance(m, nn.Linear):
trunc_normal_(m.weight, std=0.02)
if isinstance(m, nn.Linear) and m.bias is not None:
nn.init.constant_(m.bias, 0)
elif isinstance(m, nn.Conv2d):
fan_out = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
fan_out //= m.groups
m.weight.data.normal_(0, math.sqrt(2.0 / fan_out))
if m.bias is not None:
m.bias.data.zero_()
def forward(self, x: torch.Tensor, height: int, width: int) -> torch.Tensor:
x = self.fc1(x)
x = self.dwconv(x, height, width)
x = self.act(x)
x = self.drop(x)
x = self.fc2(x)
x = self.drop(x)
return x
class Attention(nn.Module):
"""Efficient Multi-head Self-Attention with Spatial Reduction"""
def __init__(
self,
dim,
num_heads=8,
qkv_bias=False,
qk_scale=None,
attn_drop=0.0,
proj_drop=0.0,
sr_ratio=1,
):
super().__init__()
assert dim % num_heads == 0, (
f"dim {dim} should be divided by num_heads {num_heads}."
)
self.dim = dim
self.num_heads = num_heads
head_dim = dim // num_heads
self.scale = qk_scale or head_dim**-0.5
self.q = nn.Linear(dim, dim, bias=qkv_bias)
self.kv = nn.Linear(dim, dim * 2, bias=qkv_bias)
self.attn_drop = nn.Dropout(attn_drop)
self.proj = nn.Linear(dim, dim)
self.proj_drop = nn.Dropout(proj_drop)
self.sr_ratio = sr_ratio
if sr_ratio > 1:
self.sr = nn.Conv2d(dim, dim, kernel_size=sr_ratio, stride=sr_ratio)
self.norm = LayerNorm(dim)
else:
self.sr = nn.Identity()
self.norm = nn.Identity()
self.apply(self._init_weights)
def _init_weights(self, m):
if isinstance(m, nn.Linear):
trunc_normal_(m.weight, std=0.02)
if isinstance(m, nn.Linear) and m.bias is not None:
nn.init.constant_(m.bias, 0)
elif isinstance(m, LayerNorm):
nn.init.constant_(m.bias, 0)
nn.init.constant_(m.weight, 1.0)
elif isinstance(m, nn.Conv2d):
fan_out = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
fan_out //= m.groups
m.weight.data.normal_(0, math.sqrt(2.0 / fan_out))
if m.bias is not None:
m.bias.data.zero_()
def forward(self, x: torch.Tensor, height: int, width: int) -> torch.Tensor:
batch_size, N, C = x.shape
q = (
self.q(x)
.reshape(batch_size, N, self.num_heads, C // self.num_heads)
.permute(0, 2, 1, 3)
)
if self.sr_ratio > 1:
x_ = x.permute(0, 2, 1).reshape(batch_size, C, height, width)
x_ = self.sr(x_).reshape(batch_size, C, -1).permute(0, 2, 1)
x_ = self.norm(x_)
kv = (
self.kv(x_)
.reshape(batch_size, -1, 2, self.num_heads, C // self.num_heads)
.permute(2, 0, 3, 1, 4)
)
else:
kv = (
self.kv(x)
.reshape(batch_size, -1, 2, self.num_heads, C // self.num_heads)
.permute(2, 0, 3, 1, 4)
)
k, v = kv[0], kv[1]
attn = (q @ k.transpose(-2, -1)) * self.scale
attn = attn.softmax(dim=-1)
attn = self.attn_drop(attn)
x = (attn @ v).transpose(1, 2).reshape(batch_size, N, C)
x = self.proj(x)
x = self.proj_drop(x)
return x
class Block(nn.Module):
"""Transformer Block"""
def __init__(
self,
dim,
num_heads,
mlp_ratio=4.0,
qkv_bias=False,
qk_scale=None,
drop=0.0,
attn_drop=0.0,
drop_path=0.0,
act_layer=nn.GELU,
norm_layer=LayerNorm,
sr_ratio=1,
):
super().__init__()
self.norm1 = norm_layer(dim)
self.attn = Attention(
dim,
num_heads=num_heads,
qkv_bias=qkv_bias,
qk_scale=qk_scale,
attn_drop=attn_drop,
proj_drop=drop,
sr_ratio=sr_ratio,
)
self.drop_path = DropPath(drop_path) if drop_path > 0.0 else nn.Identity()
self.norm2 = norm_layer(dim)
mlp_hidden_dim = int(dim * mlp_ratio)
self.mlp = Mlp(
in_features=dim,
hidden_features=mlp_hidden_dim,
act_layer=act_layer,
drop=drop,
)
self.apply(self._init_weights)
def _init_weights(self, m):
if isinstance(m, nn.Linear):
trunc_normal_(m.weight, std=0.02)
if isinstance(m, nn.Linear) and m.bias is not None:
nn.init.constant_(m.bias, 0)
elif isinstance(m, LayerNorm):
nn.init.constant_(m.bias, 0)
nn.init.constant_(m.weight, 1.0)
elif isinstance(m, nn.Conv2d):
fan_out = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
fan_out //= m.groups
m.weight.data.normal_(0, math.sqrt(2.0 / fan_out))
if m.bias is not None:
m.bias.data.zero_()
def forward(self, x: torch.Tensor) -> torch.Tensor:
batch_size, _, height, width = x.shape
x = x.flatten(2).transpose(1, 2)
x = x + self.drop_path(self.attn(self.norm1(x), height, width))
x = x + self.drop_path(self.mlp(self.norm2(x), height, width))
x = x.transpose(1, 2).view(batch_size, -1, height, width)
return x
class OverlapPatchEmbed(nn.Module):
"""Image to Patch Embedding with Overlapping Patches"""
def __init__(self, img_size=224, patch_size=7, stride=4, in_chans=3, embed_dim=768):
super().__init__()
img_size = to_2tuple(img_size)
patch_size = to_2tuple(patch_size)
self.img_size = img_size
self.patch_size = patch_size
self.H, self.W = img_size[0] // patch_size[0], img_size[1] // patch_size[1]
self.num_patches = self.H * self.W
self.proj = nn.Conv2d(
in_chans,
embed_dim,
kernel_size=patch_size,
stride=stride,
padding=(patch_size[0] // 2, patch_size[1] // 2),
)
self.norm = LayerNorm(embed_dim)
self.apply(self._init_weights)
def _init_weights(self, m):
if isinstance(m, nn.Linear):
trunc_normal_(m.weight, std=0.02)
if isinstance(m, nn.Linear) and m.bias is not None:
nn.init.constant_(m.bias, 0)
elif isinstance(m, LayerNorm):
nn.init.constant_(m.bias, 0)
nn.init.constant_(m.weight, 1.0)
elif isinstance(m, nn.Conv2d):
fan_out = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
fan_out //= m.groups
m.weight.data.normal_(0, math.sqrt(2.0 / fan_out))
if m.bias is not None:
m.bias.data.zero_()
def forward(self, x: torch.Tensor) -> torch.Tensor:
x = self.proj(x)
x = self.norm(x)
return x
# ============================================================================
# Mix Vision Transformer (Encoder)
# ============================================================================
class MixVisionTransformer(nn.Module):
"""Mix Vision Transformer - Hierarchical Transformer Encoder"""
def __init__(
self,
img_size=224,
in_chans=3,
embed_dims=[64, 128, 256, 512],
num_heads=[1, 2, 4, 8],
mlp_ratios=[4, 4, 4, 4],
qkv_bias=False,
qk_scale=None,
drop_rate=0.0,
attn_drop_rate=0.0,
drop_path_rate=0.0,
norm_layer=LayerNorm,
depths=[3, 4, 6, 3],
sr_ratios=[8, 4, 2, 1],
):
super().__init__()
self.depths = depths
# Patch embeddings for each stage
self.patch_embed1 = OverlapPatchEmbed(
img_size=img_size,
patch_size=7,
stride=4,
in_chans=in_chans,
embed_dim=embed_dims[0],
)
self.patch_embed2 = OverlapPatchEmbed(
img_size=img_size // 4,
patch_size=3,
stride=2,
in_chans=embed_dims[0],
embed_dim=embed_dims[1],
)
self.patch_embed3 = OverlapPatchEmbed(
img_size=img_size // 8,
patch_size=3,
stride=2,
in_chans=embed_dims[1],
embed_dim=embed_dims[2],
)
self.patch_embed4 = OverlapPatchEmbed(
img_size=img_size // 16,
patch_size=3,
stride=2,
in_chans=embed_dims[2],
embed_dim=embed_dims[3],
)
# Stochastic depth decay rule
dpr = [x.item() for x in torch.linspace(0, drop_path_rate, sum(depths))]
# Transformer blocks for each stage
cur = 0
self.block1 = nn.Sequential(
*[
Block(
dim=embed_dims[0],
num_heads=num_heads[0],
mlp_ratio=mlp_ratios[0],
qkv_bias=qkv_bias,
qk_scale=qk_scale,
drop=drop_rate,
attn_drop=attn_drop_rate,
drop_path=dpr[cur + i],
norm_layer=norm_layer,
sr_ratio=sr_ratios[0],
)
for i in range(depths[0])
]
)
self.norm1 = norm_layer(embed_dims[0])
cur += depths[0]
self.block2 = nn.Sequential(
*[
Block(
dim=embed_dims[1],
num_heads=num_heads[1],
mlp_ratio=mlp_ratios[1],
qkv_bias=qkv_bias,
qk_scale=qk_scale,
drop=drop_rate,
attn_drop=attn_drop_rate,
drop_path=dpr[cur + i],
norm_layer=norm_layer,
sr_ratio=sr_ratios[1],
)
for i in range(depths[1])
]
)
self.norm2 = norm_layer(embed_dims[1])
cur += depths[1]
self.block3 = nn.Sequential(
*[
Block(
dim=embed_dims[2],
num_heads=num_heads[2],
mlp_ratio=mlp_ratios[2],
qkv_bias=qkv_bias,
qk_scale=qk_scale,
drop=drop_rate,
attn_drop=attn_drop_rate,
drop_path=dpr[cur + i],
norm_layer=norm_layer,
sr_ratio=sr_ratios[2],
)
for i in range(depths[2])
]
)
self.norm3 = norm_layer(embed_dims[2])
cur += depths[2]
self.block4 = nn.Sequential(
*[
Block(
dim=embed_dims[3],
num_heads=num_heads[3],
mlp_ratio=mlp_ratios[3],
qkv_bias=qkv_bias,
qk_scale=qk_scale,
drop=drop_rate,
attn_drop=attn_drop_rate,
drop_path=dpr[cur + i],
norm_layer=norm_layer,
sr_ratio=sr_ratios[3],
)
for i in range(depths[3])
]
)
self.norm4 = norm_layer(embed_dims[3])
self.apply(self._init_weights)
def _init_weights(self, m):
if isinstance(m, nn.Linear):
trunc_normal_(m.weight, std=0.02)
if isinstance(m, nn.Linear) and m.bias is not None:
nn.init.constant_(m.bias, 0)
elif isinstance(m, LayerNorm):
nn.init.constant_(m.bias, 0)
nn.init.constant_(m.weight, 1.0)
elif isinstance(m, nn.Conv2d):
fan_out = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
fan_out //= m.groups
m.weight.data.normal_(0, math.sqrt(2.0 / fan_out))
if m.bias is not None:
m.bias.data.zero_()
def forward(self, x: torch.Tensor) -> List[torch.Tensor]:
outs = []
# Stage 1: H/4, W/4
x = self.patch_embed1(x)
x = self.block1(x)
x = self.norm1(x).contiguous()
outs.append(x)
# Stage 2: H/8, W/8
x = self.patch_embed2(x)
x = self.block2(x)
x = self.norm2(x).contiguous()
outs.append(x)
# Stage 3: H/16, W/16
x = self.patch_embed3(x)
x = self.block3(x)
x = self.norm3(x).contiguous()
outs.append(x)
# Stage 4: H/32, W/32
x = self.patch_embed4(x)
x = self.block4(x)
x = self.norm4(x).contiguous()
outs.append(x)
return outs