# Copyright (c) Meta Platforms, Inc. and affiliates. # All rights reserved. # This source code is licensed under the license found in the # LICENSE file in the root directory of this source tree. from functools import partial import torch import torch.nn as nn import torch.nn.functional as F from timm.models.layers import trunc_normal_, DropPath from timm.models.registry import register_model import os import sys import torch.fft import math import traceback import torch.utils.checkpoint as checkpoint from detectron2.modeling import BACKBONE_REGISTRY, Backbone, ShapeSpec if 'DWCONV_IMPL' in os.environ: try: sys.path.append(os.environ['DWCONV_IMPL']) from depthwise_conv2d_implicit_gemm import DepthWiseConv2dImplicitGEMM def get_dwconv(dim, kernel, bias): return DepthWiseConv2dImplicitGEMM(dim, kernel, bias) print('Using Megvii large kernel dw conv impl') except: print(traceback.format_exc()) def get_dwconv(dim, kernel, bias): return nn.Conv2d(dim, dim, kernel_size=kernel, padding=(kernel-1)//2 ,bias=bias, groups=dim) print('[fail to use Megvii Large kernel] Using PyTorch large kernel dw conv impl') else: def get_dwconv(dim, kernel, bias): return nn.Conv2d(dim, dim, kernel_size=kernel, padding=(kernel-1)//2 ,bias=bias, groups=dim) print('Using PyTorch large kernel dw conv impl') class GlobalLocalFilter(nn.Module): def __init__(self, dim, h=14, w=8): super().__init__() self.dw = nn.Conv2d(dim // 2, dim // 2, kernel_size=3, padding=1, bias=False, groups=dim // 2) self.complex_weight = nn.Parameter(torch.randn(dim // 2, h, w, 2, dtype=torch.float32) * 0.02) trunc_normal_(self.complex_weight, std=.02) self.pre_norm = LayerNorm(dim, eps=1e-6, data_format='channels_first') self.post_norm = LayerNorm(dim, eps=1e-6, data_format='channels_first') def forward(self, x): x = self.pre_norm(x) x1, x2 = torch.chunk(x, 2, dim=1) x1 = self.dw(x1) x2 = x2.to(torch.float32) B, C, a, b = x2.shape x2 = torch.fft.rfft2(x2, dim=(2, 3), norm='ortho') weight = self.complex_weight if not weight.shape[1:3] == x2.shape[2:4]: weight = F.interpolate(weight.permute(3,0,1,2), size=x2.shape[2:4], mode='bilinear', align_corners=True).permute(1,2,3,0) weight = torch.view_as_complex(weight.contiguous()) x2 = x2 * weight x2 = torch.fft.irfft2(x2, s=(a, b), dim=(2, 3), norm='ortho') x = torch.cat([x1.unsqueeze(2), x2.unsqueeze(2)], dim=2).reshape(B, 2 * C, a, b) x = self.post_norm(x) return x class gnconv(nn.Module): def __init__(self, dim, order=5, gflayer=None, h=14, w=8, s=1.0): super().__init__() self.order = order self.dims = [dim // 2 ** i for i in range(order)] self.dims.reverse() self.proj_in = nn.Conv2d(dim, 2*dim, 1) if gflayer is None: self.dwconv = get_dwconv(sum(self.dims), 7, True) else: self.dwconv = gflayer(sum(self.dims), h=h, w=w) self.proj_out = nn.Conv2d(dim, dim, 1) self.pws = nn.ModuleList( [nn.Conv2d(self.dims[i], self.dims[i+1], 1) for i in range(order-1)] ) self.scale = s print('[gconv]', order, 'order with dims=', self.dims, 'scale=%.4f'%self.scale) def forward(self, x, mask=None, dummy=False): B, C, H, W = x.shape fused_x = self.proj_in(x) pwa, abc = torch.split(fused_x, (self.dims[0], sum(self.dims)), dim=1) dw_abc = self.dwconv(abc) * self.scale dw_list = torch.split(dw_abc, self.dims, dim=1) x = pwa * dw_list[0] for i in range(self.order -1): x = self.pws[i](x) * dw_list[i+1] x = self.proj_out(x) return x class Block(nn.Module): r""" HorNet block """ def __init__(self, dim, drop_path=0., layer_scale_init_value=1e-6, gnconv=gnconv): super().__init__() self.norm1 = LayerNorm(dim, eps=1e-6, data_format='channels_first') self.gnconv = gnconv(dim) # depthwise conv self.norm2 = LayerNorm(dim, eps=1e-6) self.pwconv1 = nn.Linear(dim, 4 * dim) # pointwise/1x1 convs, implemented with linear layers self.act = nn.GELU() self.pwconv2 = nn.Linear(4 * dim, dim) self.gamma1 = nn.Parameter(layer_scale_init_value * torch.ones(dim), requires_grad=True) if layer_scale_init_value > 0 else None self.gamma2 = nn.Parameter(layer_scale_init_value * torch.ones((dim)), requires_grad=True) if layer_scale_init_value > 0 else None self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity() def forward(self, x): B, C, H, W = x.shape if self.gamma1 is not None: gamma1 = self.gamma1.view(C, 1, 1) else: gamma1 = 1 x = x + self.drop_path(gamma1 * self.gnconv(self.norm1(x))) input = x x = x.permute(0, 2, 3, 1) # (N, C, H, W) -> (N, H, W, C) x = self.norm2(x) x = self.pwconv1(x) x = self.act(x) x = self.pwconv2(x) if self.gamma2 is not None: x = self.gamma2 * x x = x.permute(0, 3, 1, 2) # (N, H, W, C) -> (N, C, H, W) x = input + self.drop_path(x) return x class HorNet(nn.Module): r""" HorNet A PyTorch impl of : `HorNet: Efficient High-Order Spatial Interactions with Recursive Gated Convolutions` Args: in_chans (int): Number of input image channels. Default: 3 num_classes (int): Number of classes for classification head. Default: 1000 depths (tuple(int)): Number of blocks at each stage. Default: [3, 3, 9, 3] dims (int): Feature dimension at each stage. Default: [96, 192, 384, 768] drop_path_rate (float): Stochastic depth rate. Default: 0. layer_scale_init_value (float): Init value for Layer Scale. Default: 1e-6. head_init_scale (float): Init scaling value for classifier weights and biases. Default: 1. """ def __init__(self, in_chans=3, num_classes=1000, depths=[3, 3, 9, 3], base_dim=96, drop_path_rate=0., layer_scale_init_value=1e-6, head_init_scale=1., gnconv=gnconv, block=Block, pretrained=None, use_checkpoint=False, ): super().__init__() self.pretrained = pretrained self.use_checkpoint = use_checkpoint dims = [base_dim, base_dim*2, base_dim*4, base_dim*8] self.downsample_layers = nn.ModuleList() # stem and 3 intermediate downsampling conv layers stem = nn.Sequential( nn.Conv2d(in_chans, dims[0], kernel_size=4, stride=4), LayerNorm(dims[0], eps=1e-6, data_format="channels_first") ) self.downsample_layers.append(stem) for i in range(3): downsample_layer = nn.Sequential( LayerNorm(dims[i], eps=1e-6, data_format="channels_first"), nn.Conv2d(dims[i], dims[i+1], kernel_size=2, stride=2), ) self.downsample_layers.append(downsample_layer) self.stages = nn.ModuleList() # 4 feature resolution stages, each consisting of multiple residual blocks dp_rates=[x.item() for x in torch.linspace(0, drop_path_rate, sum(depths))] if not isinstance(gnconv, list): gnconv = [gnconv, gnconv, gnconv, gnconv] else: gnconv = gnconv assert len(gnconv) == 4 if isinstance(gnconv[0], str): print('[GConvNet]: convert str gconv to func') gnconv = [eval(g) for g in gnconv] if isinstance(block, str): block = eval(block) cur = 0 num_features = [] for i in range(4): stage = nn.Sequential( *[block(dim=dims[i], drop_path=dp_rates[cur + j], layer_scale_init_value=layer_scale_init_value, gnconv=gnconv[i]) for j in range(depths[i])] ) self.stages.append(stage) cur += depths[i] num_features.append(dims[i]) self.num_features = num_features norm_layer = partial(LayerNorm, eps=1e-6, data_format="channels_first") for i_layer in range(4): layer = norm_layer(dims[i_layer]) layer_name = f'norm{i_layer}' self.add_module(layer_name, layer) def init_weights(self): """Initialize the weights in backbone. Args: pretrained (str, optional): Path to pre-trained weights. Defaults to None. """ #pretrained = self.pretrained def _init_weights(m): if isinstance(m, nn.Linear): trunc_normal_(m.weight, std=.02) if isinstance(m, nn.Linear) and m.bias is not None: nn.init.constant_(m.bias, 0) elif isinstance(m, nn.LayerNorm): nn.init.constant_(m.bias, 0) nn.init.constant_(m.weight, 1.0) #if isinstance(pretrained, str): # self.apply(_init_weights) # logger = get_root_logger() # load_checkpoint(self, pretrained, strict=False, logger=logger) #elif pretrained is None: # raise NotImplementedError() self.apply(_init_weights) #else: # raise TypeError('pretrained must be a str or None') def forward_features(self, x): outs = dict() for i in range(4): x = self.downsample_layers[i](x) if self.use_checkpoint: x = checkpoint.checkpoint_sequential(self.stages[i], len(self.stages[i]), x) else: x = self.stages[i](x) norm_layer = getattr(self, f'norm{i}') x_out = norm_layer(x) outs["res%i"% (i+2)] = x_out return outs #tuple(outs) def forward(self, x): x = self.forward_features(x) return x class LayerNorm(nn.Module): r""" LayerNorm that supports two data formats: channels_last (default) or channels_first. The ordering of the dimensions in the inputs. channels_last corresponds to inputs with shape (batch_size, height, width, channels) while channels_first corresponds to inputs with shape (batch_size, channels, height, width). """ def __init__(self, normalized_shape, eps=1e-6, data_format="channels_last"): super().__init__() self.weight = nn.Parameter(torch.ones(normalized_shape)) self.bias = nn.Parameter(torch.zeros(normalized_shape)) self.eps = eps self.data_format = data_format if self.data_format not in ["channels_last", "channels_first"]: raise NotImplementedError self.normalized_shape = (normalized_shape, ) def forward(self, x): if self.data_format == "channels_last": return F.layer_norm(x, self.normalized_shape, self.weight, self.bias, self.eps) elif self.data_format == "channels_first": u = x.mean(1, keepdim=True) s = (x - u).pow(2).mean(1, keepdim=True) x = (x - u) / torch.sqrt(s + self.eps) x = self.weight[:, None, None] * x + self.bias[:, None, None] return x @BACKBONE_REGISTRY.register() class D2HorNet(HorNet, Backbone): def __init__(self, cfg, input_shape): depths=cfg.MODEL.HORNET.DEPTHS base_dim=cfg.MODEL.HORNET.BASE_DIM gnconv=cfg.MODEL.HORNET.GCONV drop_path_rate=cfg.MODEL.HORNET.DROP_PATH_RATE super().__init__( depths=depths, base_dim=base_dim, gnconv=gnconv, drop_path_rate=drop_path_rate, ) self._out_features = cfg.MODEL.HORNET.OUT_FEATURES self._out_feature_strides = { "res2": 4, "res3": 8, "res4": 16, "res5": 32, } self._out_feature_channels = { "res2": self.num_features[0], "res3": self.num_features[1], "res4": self.num_features[2], "res5": self.num_features[3], } def forward(self, x): """ Args: x: Tensor of shape (N,C,H,W). H, W must be a multiple of ``self.size_divisibility``. Returns: dict[str->Tensor]: names and the corresponding features """ assert ( x.dim() == 4 ), f"SwinTransformer takes an input of shape (N, C, H, W). Got {x.shape} instead!" outputs = {} y = super().forward(x) for k in y.keys(): if k in self._out_features: outputs[k] = y[k] return outputs def output_shape(self): return { name: ShapeSpec( channels=self._out_feature_channels[name], stride=self._out_feature_strides[name] ) for name in self._out_features } @property def size_divisibility(self): return 32