birth
This commit is contained in:
12
segmentation/mmseg_custom/models/__init__.py
Normal file
12
segmentation/mmseg_custom/models/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
from .backbones import * # noqa: F401,F403
|
||||
from .decode_heads import * # noqa: F401,F403
|
||||
from .losses import * # noqa: F401,F403
|
||||
from .plugins import * # noqa: F401,F403
|
||||
from .segmentors import * # noqa: F401,F403
|
||||
from .utils import * # noqa: F401,F403
|
||||
9
segmentation/mmseg_custom/models/backbones/__init__.py
Normal file
9
segmentation/mmseg_custom/models/backbones/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
# --------------------------------------------------------
|
||||
# FlashInternImage
|
||||
# Copyright (c) 2023 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
from .flash_intern_image import FlashInternImage
|
||||
|
||||
__all__ = ['FlashInternImage']
|
||||
763
segmentation/mmseg_custom/models/backbones/flash_intern_image.py
Normal file
763
segmentation/mmseg_custom/models/backbones/flash_intern_image.py
Normal file
@@ -0,0 +1,763 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from collections import OrderedDict
|
||||
import torch.utils.checkpoint as checkpoint
|
||||
from timm.models.layers import trunc_normal_, DropPath
|
||||
from mmcv.runner import _load_checkpoint
|
||||
from mmcv.cnn import constant_init, trunc_normal_init
|
||||
from mmseg.utils import get_root_logger
|
||||
from mmseg.models.builder import BACKBONES
|
||||
import torch.nn.functional as F
|
||||
import DCNv4
|
||||
|
||||
|
||||
class to_channels_first(nn.Module):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def forward(self, x):
|
||||
return x.permute(0, 3, 1, 2)
|
||||
|
||||
|
||||
class to_channels_last(nn.Module):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def forward(self, x):
|
||||
return x.permute(0, 2, 3, 1)
|
||||
|
||||
|
||||
def build_norm_layer(dim,
|
||||
norm_layer,
|
||||
in_format='channels_last',
|
||||
out_format='channels_last',
|
||||
eps=1e-6):
|
||||
layers = []
|
||||
if norm_layer == 'BN':
|
||||
if in_format == 'channels_last':
|
||||
layers.append(to_channels_first())
|
||||
layers.append(nn.BatchNorm2d(dim))
|
||||
if out_format == 'channels_last':
|
||||
layers.append(to_channels_last())
|
||||
elif norm_layer == 'LN':
|
||||
if in_format == 'channels_first':
|
||||
layers.append(to_channels_last())
|
||||
layers.append(nn.LayerNorm(dim, eps=eps))
|
||||
if out_format == 'channels_first':
|
||||
layers.append(to_channels_first())
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f'build_norm_layer does not support {norm_layer}')
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
|
||||
def build_act_layer(act_layer):
|
||||
if act_layer == 'ReLU':
|
||||
return nn.ReLU(inplace=True)
|
||||
elif act_layer == 'SiLU':
|
||||
return nn.SiLU(inplace=True)
|
||||
elif act_layer == 'GELU':
|
||||
return nn.GELU()
|
||||
|
||||
raise NotImplementedError(f'build_act_layer does not support {act_layer}')
|
||||
|
||||
|
||||
class CrossAttention(nn.Module):
|
||||
r""" Cross Attention Module
|
||||
Args:
|
||||
dim (int): Number of input channels.
|
||||
num_heads (int): Number of attention heads. Default: 8
|
||||
qkv_bias (bool, optional): If True, add a learnable bias to q, k, v.
|
||||
Default: False.
|
||||
qk_scale (float | None, optional): Override default qk scale of
|
||||
head_dim ** -0.5 if set. Default: None.
|
||||
attn_drop (float, optional): Dropout ratio of attention weight.
|
||||
Default: 0.0
|
||||
proj_drop (float, optional): Dropout ratio of output. Default: 0.0
|
||||
attn_head_dim (int, optional): Dimension of attention head.
|
||||
out_dim (int, optional): Dimension of output.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
dim,
|
||||
num_heads=8,
|
||||
qkv_bias=False,
|
||||
qk_scale=None,
|
||||
attn_drop=0.,
|
||||
proj_drop=0.,
|
||||
attn_head_dim=None,
|
||||
out_dim=None):
|
||||
super().__init__()
|
||||
if out_dim is None:
|
||||
out_dim = dim
|
||||
self.num_heads = num_heads
|
||||
head_dim = dim // num_heads
|
||||
if attn_head_dim is not None:
|
||||
head_dim = attn_head_dim
|
||||
all_head_dim = head_dim * self.num_heads
|
||||
self.scale = qk_scale or head_dim ** -0.5
|
||||
assert all_head_dim == dim
|
||||
|
||||
self.q = nn.Linear(dim, all_head_dim, bias=False)
|
||||
self.k = nn.Linear(dim, all_head_dim, bias=False)
|
||||
self.v = nn.Linear(dim, all_head_dim, bias=False)
|
||||
|
||||
if qkv_bias:
|
||||
self.q_bias = nn.Parameter(torch.zeros(all_head_dim))
|
||||
self.k_bias = nn.Parameter(torch.zeros(all_head_dim))
|
||||
self.v_bias = nn.Parameter(torch.zeros(all_head_dim))
|
||||
else:
|
||||
self.q_bias = None
|
||||
self.k_bias = None
|
||||
self.v_bias = None
|
||||
|
||||
self.attn_drop = nn.Dropout(attn_drop)
|
||||
self.proj = nn.Linear(all_head_dim, out_dim)
|
||||
self.proj_drop = nn.Dropout(proj_drop)
|
||||
|
||||
def forward(self, x, k=None, v=None):
|
||||
B, N, C = x.shape
|
||||
N_k = k.shape[1]
|
||||
N_v = v.shape[1]
|
||||
|
||||
q_bias, k_bias, v_bias = None, None, None
|
||||
if self.q_bias is not None:
|
||||
q_bias = self.q_bias
|
||||
k_bias = self.k_bias
|
||||
v_bias = self.v_bias
|
||||
|
||||
q = F.linear(input=x, weight=self.q.weight, bias=q_bias)
|
||||
q = q.reshape(B, N, 1, self.num_heads,
|
||||
-1).permute(2, 0, 3, 1,
|
||||
4).squeeze(0) # (B, N_head, N_q, dim)
|
||||
|
||||
k = F.linear(input=k, weight=self.k.weight, bias=k_bias)
|
||||
k = k.reshape(B, N_k, 1, self.num_heads, -1).permute(2, 0, 3, 1,
|
||||
4).squeeze(0)
|
||||
|
||||
v = F.linear(input=v, weight=self.v.weight, bias=v_bias)
|
||||
v = v.reshape(B, N_v, 1, self.num_heads, -1).permute(2, 0, 3, 1,
|
||||
4).squeeze(0)
|
||||
|
||||
q = q * self.scale
|
||||
attn = (q @ k.transpose(-2, -1)) # (B, N_head, N_q, N_k)
|
||||
|
||||
attn = attn.softmax(dim=-1)
|
||||
attn = self.attn_drop(attn)
|
||||
|
||||
x = (attn @ v).transpose(1, 2).reshape(B, N, -1)
|
||||
x = self.proj(x)
|
||||
x = self.proj_drop(x)
|
||||
|
||||
return x
|
||||
|
||||
|
||||
class AttentiveBlock(nn.Module):
|
||||
r"""Attentive Block
|
||||
Args:
|
||||
dim (int): Number of input channels.
|
||||
num_heads (int): Number of attention heads. Default: 8
|
||||
qkv_bias (bool, optional): If True, add a learnable bias to q, k, v.
|
||||
Default: False.
|
||||
qk_scale (float | None, optional): Override default qk scale of
|
||||
head_dim ** -0.5 if set. Default: None.
|
||||
drop (float, optional): Dropout rate. Default: 0.0.
|
||||
attn_drop (float, optional): Attention dropout rate. Default: 0.0.
|
||||
drop_path (float | tuple[float], optional): Stochastic depth rate.
|
||||
Default: 0.0.
|
||||
norm_layer (nn.Module, optional): Normalization layer. Default: nn.LayerNorm.
|
||||
attn_head_dim (int, optional): Dimension of attention head. Default: None.
|
||||
out_dim (int, optional): Dimension of output. Default: None.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
dim,
|
||||
num_heads,
|
||||
qkv_bias=False,
|
||||
qk_scale=None,
|
||||
drop=0.,
|
||||
attn_drop=0.,
|
||||
drop_path=0.,
|
||||
norm_layer="LN",
|
||||
attn_head_dim=None,
|
||||
out_dim=None):
|
||||
super().__init__()
|
||||
|
||||
self.norm1_q = build_norm_layer(dim, norm_layer, eps=1e-6)
|
||||
self.norm1_k = build_norm_layer(dim, norm_layer, eps=1e-6)
|
||||
self.norm1_v = build_norm_layer(dim, norm_layer, eps=1e-6)
|
||||
self.cross_dcn = CrossAttention(dim,
|
||||
num_heads=num_heads,
|
||||
qkv_bias=qkv_bias,
|
||||
qk_scale=qk_scale,
|
||||
attn_drop=attn_drop,
|
||||
proj_drop=drop,
|
||||
attn_head_dim=attn_head_dim,
|
||||
out_dim=out_dim)
|
||||
|
||||
self.drop_path = DropPath(
|
||||
drop_path) if drop_path > 0. else nn.Identity()
|
||||
|
||||
def forward(self,
|
||||
x_q,
|
||||
x_kv,
|
||||
pos_q,
|
||||
pos_k,
|
||||
bool_masked_pos,
|
||||
rel_pos_bias=None):
|
||||
x_q = self.norm1_q(x_q + pos_q)
|
||||
x_k = self.norm1_k(x_kv + pos_k)
|
||||
x_v = self.norm1_v(x_kv)
|
||||
|
||||
x = self.cross_dcn(x_q, k=x_k, v=x_v)
|
||||
|
||||
return x
|
||||
|
||||
|
||||
class AttentionPoolingBlock(AttentiveBlock):
|
||||
|
||||
def forward(self, x):
|
||||
x_q = x.mean(1, keepdim=True)
|
||||
x_kv = x
|
||||
pos_q, pos_k = 0, 0
|
||||
x = super().forward(x_q, x_kv, pos_q, pos_k,
|
||||
bool_masked_pos=None,
|
||||
rel_pos_bias=None)
|
||||
x = x.squeeze(1)
|
||||
return x
|
||||
|
||||
|
||||
class StemLayer(nn.Module):
|
||||
r""" Stem layer of InternImage
|
||||
Args:
|
||||
in_chans (int): number of input channels
|
||||
out_chans (int): number of output channels
|
||||
act_layer (str): activation layer
|
||||
norm_layer (str): normalization layer
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_chans=3,
|
||||
out_chans=96,
|
||||
act_layer='GELU',
|
||||
norm_layer='BN'):
|
||||
super().__init__()
|
||||
self.conv1 = nn.Conv2d(in_chans,
|
||||
out_chans // 2,
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1)
|
||||
self.norm1 = build_norm_layer(out_chans // 2, norm_layer,
|
||||
'channels_first', 'channels_first')
|
||||
self.act = build_act_layer(act_layer)
|
||||
self.conv2 = nn.Conv2d(out_chans // 2,
|
||||
out_chans,
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1)
|
||||
self.norm2 = build_norm_layer(out_chans, norm_layer, 'channels_first',
|
||||
'channels_last')
|
||||
|
||||
def forward(self, x):
|
||||
x = self.conv1(x)
|
||||
x = self.norm1(x)
|
||||
x = self.act(x)
|
||||
x = self.conv2(x)
|
||||
x = self.norm2(x)
|
||||
return x
|
||||
|
||||
|
||||
|
||||
class DownsampleLayer(nn.Module):
|
||||
r""" Downsample layer of InternImage
|
||||
Args:
|
||||
channels (int): number of input channels
|
||||
norm_layer (str): normalization layer
|
||||
"""
|
||||
|
||||
def __init__(self, channels, norm_layer='LN'):
|
||||
super().__init__()
|
||||
self.conv = nn.Conv2d(channels,
|
||||
2 * channels,
|
||||
kernel_size=3,
|
||||
stride=2,
|
||||
padding=1,
|
||||
bias=False)
|
||||
self.norm = build_norm_layer(2 * channels, norm_layer,
|
||||
'channels_first', 'channels_first')
|
||||
|
||||
|
||||
def forward(self, x, shape=None):
|
||||
H, W = shape
|
||||
N, HW, C = x.shape
|
||||
|
||||
x = x.view(N, H, W, C)
|
||||
x = self.conv(x.permute(0, 3, 1, 2))
|
||||
x = self.norm(x) # B C H W
|
||||
H, W = x.size(2), x.size(3)
|
||||
x = x.flatten(2).permute(0, 2, 1)
|
||||
|
||||
return x, (H, W)
|
||||
|
||||
|
||||
|
||||
class MLPLayer(nn.Module):
|
||||
r""" MLP layer of InternImage
|
||||
Args:
|
||||
in_features (int): number of input features
|
||||
hidden_features (int): number of hidden features
|
||||
out_features (int): number of output features
|
||||
act_layer (str): activation layer
|
||||
drop (float): dropout rate
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
in_features,
|
||||
hidden_features=None,
|
||||
out_features=None,
|
||||
act_layer='GELU',
|
||||
mlp_fc2_bias=False,
|
||||
drop=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, bias=True)
|
||||
self.act = build_act_layer(act_layer)
|
||||
self.fc2 = nn.Linear(hidden_features, out_features, bias=mlp_fc2_bias)
|
||||
self.drop = nn.Dropout(drop)
|
||||
|
||||
|
||||
def forward(self, x, shape, level_idx=0):
|
||||
x = self.fc1(x)
|
||||
x = self.act(x)
|
||||
x = self.drop(x)
|
||||
x = self.fc2(x)
|
||||
x = self.drop(x)
|
||||
return x
|
||||
|
||||
|
||||
class InternImageLayer(nn.Module):
|
||||
r""" Basic layer of InternImage
|
||||
Args:
|
||||
core_op (nn.Module): core operation of InternImage
|
||||
channels (int): number of input channels
|
||||
groups (list): Groups of each block.
|
||||
mlp_ratio (float): ratio of mlp hidden features to input channels
|
||||
drop (float): dropout rate
|
||||
drop_path (float): drop path rate
|
||||
act_layer (str): activation layer
|
||||
norm_layer (str): normalization layer
|
||||
post_norm (bool): whether to use post normalization
|
||||
layer_scale (float): layer scale
|
||||
offset_scale (float): offset scale
|
||||
with_cp (bool): whether to use checkpoint
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
core_op,
|
||||
channels,
|
||||
groups,
|
||||
mlp_ratio=4.,
|
||||
drop=0.,
|
||||
drop_path=0.,
|
||||
act_layer='GELU',
|
||||
norm_layer='LN',
|
||||
post_norm=False,
|
||||
layer_scale=None,
|
||||
offset_scale=1.0,
|
||||
with_cp=False,
|
||||
dcn_output_bias=False,
|
||||
mlp_fc2_bias=False,
|
||||
dw_kernel_size=None, # for InternImage-H/G
|
||||
res_post_norm=False, # for InternImage-H/G
|
||||
center_feature_scale=False): # for InternImage-H/G
|
||||
super().__init__()
|
||||
self.channels = channels
|
||||
self.groups = groups
|
||||
self.mlp_ratio = mlp_ratio
|
||||
self.with_cp = with_cp
|
||||
|
||||
self.norm1 = build_norm_layer(channels, 'LN')
|
||||
self.post_norm = post_norm
|
||||
self.dcn = core_op(
|
||||
channels=channels,
|
||||
group=groups,
|
||||
offset_scale=offset_scale,
|
||||
dw_kernel_size=dw_kernel_size,
|
||||
output_bias=dcn_output_bias,
|
||||
)
|
||||
self.drop_path = DropPath(drop_path) if drop_path > 0. \
|
||||
else nn.Identity()
|
||||
self.norm2 = build_norm_layer(channels, 'LN')
|
||||
self.mlp = MLPLayer(in_features=channels,
|
||||
hidden_features=int(channels * mlp_ratio),
|
||||
act_layer=act_layer,
|
||||
drop=drop,
|
||||
mlp_fc2_bias=mlp_fc2_bias
|
||||
)
|
||||
self.layer_scale = layer_scale is not None
|
||||
if self.layer_scale:
|
||||
self.gamma1 = nn.Parameter(layer_scale * torch.ones(channels),
|
||||
requires_grad=True)
|
||||
self.gamma2 = nn.Parameter(layer_scale * torch.ones(channels),
|
||||
requires_grad=True)
|
||||
self.res_post_norm = res_post_norm
|
||||
if res_post_norm:
|
||||
self.res_post_norm1 = build_norm_layer(channels, 'LN')
|
||||
self.res_post_norm2 = build_norm_layer(channels, 'LN')
|
||||
def forward(self, x, shape, level_idx=0):
|
||||
|
||||
def _inner_forward(x, shape, level_idx):
|
||||
if not self.layer_scale:
|
||||
if self.post_norm:
|
||||
x = x + self.drop_path(self.norm1(self.dcn(x, shape, level_idx)))
|
||||
x = x + self.drop_path(self.norm2(self.mlp(x, shape, level_idx)))
|
||||
elif self.res_post_norm: # for InternImage-H/G
|
||||
x = x + self.drop_path(self.res_post_norm1(self.dcn(self.norm1(x), shape, level_idx)))
|
||||
x = x + self.drop_path(self.res_post_norm2(self.mlp(self.norm2(x), shape, level_idx)))
|
||||
|
||||
else:
|
||||
x = x + self.drop_path(self.dcn(self.norm1(x), shape, level_idx))
|
||||
x = x + self.drop_path(self.mlp(self.norm2(x), shape, level_idx))
|
||||
return x
|
||||
if self.post_norm:
|
||||
x = x + self.drop_path(self.gamma1 * self.norm1(self.dcn(x, shape)))
|
||||
x = x + self.drop_path(self.gamma2 * self.norm2(self.mlp(x, shape, level_idx)))
|
||||
else:
|
||||
x = x + self.drop_path(self.gamma1 * self.dcn(self.norm1(x), shape))
|
||||
x = x + self.drop_path(self.gamma2 * self.mlp(self.norm2(x), shape, level_idx))
|
||||
return x
|
||||
|
||||
if self.with_cp and x.requires_grad:
|
||||
x = checkpoint.checkpoint(_inner_forward, x, shape, level_idx)
|
||||
else:
|
||||
x = _inner_forward(x, shape, level_idx)
|
||||
|
||||
return x
|
||||
|
||||
|
||||
class InternImageBlock(nn.Module):
|
||||
r""" Block of InternImage
|
||||
Args:
|
||||
core_op (nn.Module): core operation of InternImage
|
||||
channels (int): number of input channels
|
||||
depths (list): Depth of each block.
|
||||
groups (list): Groups of each block.
|
||||
mlp_ratio (float): ratio of mlp hidden features to input channels
|
||||
drop (float): dropout rate
|
||||
drop_path (float): drop path rate
|
||||
act_layer (str): activation layer
|
||||
norm_layer (str): normalization layer
|
||||
post_norm (bool): whether to use post normalization
|
||||
layer_scale (float): layer scale
|
||||
offset_scale (float): offset scale
|
||||
with_cp (bool): whether to use checkpoint
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
core_op,
|
||||
channels,
|
||||
depth,
|
||||
groups,
|
||||
downsample=True,
|
||||
downsample_layer=DownsampleLayer,
|
||||
mlp_ratio=4.,
|
||||
drop=0.,
|
||||
drop_path=0.,
|
||||
act_layer='GELU',
|
||||
norm_layer='LN',
|
||||
post_norm=False,
|
||||
offset_scale=0.5,
|
||||
layer_scale=None,
|
||||
with_cp=False,
|
||||
dcn_output_bias=False,
|
||||
mlp_fc2_bias=False,
|
||||
dw_kernel_size=None, # for InternImage-H/G
|
||||
post_norm_block_ids=None, # for InternImage-H/G
|
||||
res_post_norm=False, # for InternImage-H/G
|
||||
center_feature_scale=False): # for InternImage-H/G
|
||||
super().__init__()
|
||||
self.channels = channels
|
||||
self.depth = depth
|
||||
self.post_norm = post_norm
|
||||
self.center_feature_scale = center_feature_scale
|
||||
|
||||
self.blocks = nn.ModuleList([
|
||||
InternImageLayer(
|
||||
core_op=core_op,
|
||||
channels=channels,
|
||||
groups=groups,
|
||||
mlp_ratio=mlp_ratio,
|
||||
drop=drop,
|
||||
drop_path=drop_path[i] if isinstance(
|
||||
drop_path, list) else drop_path,
|
||||
act_layer=act_layer,
|
||||
norm_layer=norm_layer,
|
||||
post_norm=post_norm,
|
||||
layer_scale=layer_scale,
|
||||
offset_scale=offset_scale,
|
||||
with_cp=with_cp,
|
||||
dcn_output_bias=dcn_output_bias,
|
||||
mlp_fc2_bias=mlp_fc2_bias,
|
||||
dw_kernel_size=dw_kernel_size, # for InternImage-H/G
|
||||
res_post_norm=res_post_norm, # for InternImage-H/G
|
||||
center_feature_scale=center_feature_scale # for InternImage-H/G
|
||||
) for i in range(depth)
|
||||
])
|
||||
if not self.post_norm or center_feature_scale:
|
||||
self.norm = build_norm_layer(channels, 'LN')
|
||||
self.post_norm_block_ids = post_norm_block_ids
|
||||
if post_norm_block_ids is not None: # for InternImage-H/G
|
||||
self.post_norms = nn.ModuleList(
|
||||
[build_norm_layer(channels, 'LN', eps=1e-6) for _ in post_norm_block_ids]
|
||||
)
|
||||
self.downsample = downsample_layer(
|
||||
channels=channels, norm_layer=norm_layer) if downsample else None
|
||||
|
||||
|
||||
def forward(self, x, return_wo_downsample=False, shape=None, level_idx=0
|
||||
):
|
||||
for i, blk in enumerate(self.blocks):
|
||||
x = blk(x, shape=shape, level_idx=level_idx)
|
||||
if (self.post_norm_block_ids is not None) and (i in self.post_norm_block_ids):
|
||||
index = self.post_norm_block_ids.index(i)
|
||||
x = self.post_norms[index](x) # for InternImage-H/G
|
||||
if not self.post_norm or self.center_feature_scale:
|
||||
x = self.norm(x)
|
||||
if return_wo_downsample:
|
||||
x_ = x.clone()
|
||||
if self.downsample is not None:
|
||||
x, shape = self.downsample(x, shape=shape)
|
||||
|
||||
if return_wo_downsample:
|
||||
return x, x_, shape
|
||||
return x, shape
|
||||
|
||||
@BACKBONES.register_module()
|
||||
class FlashInternImage(nn.Module):
|
||||
r""" FlashInternImage
|
||||
A PyTorch impl based on :
|
||||
`InternImage: Exploring Large-Scale Vision Foundation Models with Deformable Convolutions` -
|
||||
https://arxiv.org/pdf/2103.14030
|
||||
'DCNv4': TODO: add arxiv
|
||||
Args:
|
||||
core_op (str): Core operator. Default: 'DCNv4'
|
||||
channels (int): Number of the first stage. Default: 64
|
||||
depths (list): Depth of each block. Default: [3, 4, 18, 5]
|
||||
groups (list): Groups of each block. Default: [3, 6, 12, 24]
|
||||
num_classes (int): Number of classes. Default: 1000
|
||||
mlp_ratio (float): Ratio of mlp hidden dim to embedding dim. Default: 4.
|
||||
drop_rate (float): Probability of an element to be zeroed. Default: 0.
|
||||
drop_path_rate (float): Stochastic depth rate. Default: 0.2
|
||||
act_layer (str): Activation layer. Default: 'GELU'
|
||||
norm_layer (str): Normalization layer. Default: 'LN'
|
||||
layer_scale (bool): Whether to use layer scale. Default: False
|
||||
cls_scale (bool): Whether to use class scale. Default: False
|
||||
with_cp (bool): Use checkpoint or not. Using checkpoint will save some
|
||||
dw_kernel_size (int): Size of the dwconv. Default: None
|
||||
use_clip_projector (bool): Whether to use clip projector. Default: False
|
||||
level2_post_norm (bool): Whether to use level2 post norm. Default: False
|
||||
level2_post_norm_block_ids (list): Indexes of post norm blocks. Default: None
|
||||
res_post_norm (bool): Whether to use res post norm. Default: False
|
||||
center_feature_scale (bool): Whether to use center feature scale. Default: False
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
core_op='DCNv4',
|
||||
channels=64,
|
||||
depths=[3, 4, 18, 5],
|
||||
groups=[3, 6, 12, 24],
|
||||
num_classes=1000,
|
||||
mlp_ratio=4.,
|
||||
drop_rate=0.,
|
||||
drop_path_rate=0.2,
|
||||
drop_path_type='linear',
|
||||
act_layer='GELU',
|
||||
norm_layer='LN',
|
||||
layer_scale=None,
|
||||
offset_scale=0.5,
|
||||
post_norm=False,
|
||||
with_cp=False,
|
||||
mlp_fc2_bias=False,
|
||||
dcn_output_bias=False,
|
||||
dw_kernel_size=None, # for InternImage-H/G
|
||||
level2_post_norm=False, # for InternImage-H/G
|
||||
level2_post_norm_block_ids=None, # for InternImage-H/G
|
||||
res_post_norm=False, # for InternImage-H/G
|
||||
center_feature_scale=False, # for InternImage-H/G
|
||||
out_indices=(0, 1, 2, 3),
|
||||
init_cfg=None,
|
||||
**kwargs):
|
||||
super().__init__()
|
||||
self.core_op = core_op
|
||||
self.num_levels = len(depths)
|
||||
self.depths = depths
|
||||
self.channels = channels
|
||||
self.num_features = int(channels * 2**(self.num_levels - 1))
|
||||
self.post_norm = post_norm
|
||||
self.mlp_ratio = mlp_ratio
|
||||
self.init_cfg = init_cfg
|
||||
self.out_indices = out_indices
|
||||
self.level2_post_norm_block_ids = level2_post_norm_block_ids
|
||||
logger = get_root_logger()
|
||||
logger.info(f'using core type: {core_op}')
|
||||
logger.info(f'using activation layer: {act_layer}')
|
||||
logger.info(f'using main norm layer: {norm_layer}')
|
||||
logger.info(f'using dpr: {drop_path_type}, {drop_path_rate}')
|
||||
logger.info(f"level2_post_norm: {level2_post_norm}")
|
||||
logger.info(f"level2_post_norm_block_ids: {level2_post_norm_block_ids}")
|
||||
logger.info(f"res_post_norm: {res_post_norm}")
|
||||
|
||||
in_chans = 3
|
||||
self.patch_embed = StemLayer(in_chans=in_chans,
|
||||
out_chans=channels,
|
||||
act_layer=act_layer,
|
||||
norm_layer=norm_layer)
|
||||
self.pos_drop = nn.Dropout(p=drop_rate)
|
||||
|
||||
dpr = [
|
||||
x.item() for x in torch.linspace(0, drop_path_rate, sum(depths))
|
||||
]
|
||||
if drop_path_type == 'uniform':
|
||||
for i in range(len(dpr)):
|
||||
dpr[i] = drop_path_rate
|
||||
|
||||
self.levels = nn.ModuleList()
|
||||
for i in range(self.num_levels):
|
||||
post_norm_block_ids = level2_post_norm_block_ids if level2_post_norm and (
|
||||
i == 2) else None # for InternImage-H/G
|
||||
|
||||
level = InternImageBlock(
|
||||
core_op=getattr(DCNv4, core_op),
|
||||
channels=int(channels * 2**i),
|
||||
depth=depths[i],
|
||||
groups=groups[i],
|
||||
mlp_ratio=self.mlp_ratio,
|
||||
drop=drop_rate,
|
||||
drop_path=dpr[sum(depths[:i]):sum(depths[:i + 1])],
|
||||
act_layer=act_layer,
|
||||
norm_layer=norm_layer,
|
||||
post_norm=post_norm,
|
||||
downsample=(i < self.num_levels - 1),
|
||||
downsample_layer = DownsampleLayer,
|
||||
layer_scale=layer_scale,
|
||||
offset_scale=offset_scale,
|
||||
with_cp=with_cp,
|
||||
mlp_fc2_bias=mlp_fc2_bias,
|
||||
dcn_output_bias=dcn_output_bias,
|
||||
dw_kernel_size=dw_kernel_size, # for InternImage-H/G
|
||||
post_norm_block_ids=post_norm_block_ids, # for InternImage-H/G
|
||||
res_post_norm=res_post_norm, # for InternImage-H/G
|
||||
center_feature_scale=center_feature_scale # for InternImage-H/G
|
||||
)
|
||||
self.levels.append(level)
|
||||
|
||||
self.num_layers = len(depths)
|
||||
self.apply(self._init_weights)
|
||||
self.apply(self._init_deform_weights)
|
||||
|
||||
def init_weights(self):
|
||||
logger = get_root_logger()
|
||||
if self.init_cfg is None:
|
||||
logger.warn(f'No pre-trained weights for '
|
||||
f'{self.__class__.__name__}, '
|
||||
f'training start from scratch')
|
||||
for m in self.modules():
|
||||
if isinstance(m, nn.Linear):
|
||||
trunc_normal_init(m, std=.02, bias=0.)
|
||||
elif isinstance(m, nn.LayerNorm):
|
||||
constant_init(m, 1.0)
|
||||
else:
|
||||
assert 'checkpoint' in self.init_cfg, f'Only support ' \
|
||||
f'specify `Pretrained` in ' \
|
||||
f'`init_cfg` in ' \
|
||||
f'{self.__class__.__name__} '
|
||||
ckpt = _load_checkpoint(self.init_cfg.checkpoint,
|
||||
logger=logger,
|
||||
map_location='cpu')
|
||||
if 'state_dict' in ckpt:
|
||||
_state_dict = ckpt['state_dict']
|
||||
elif 'model_ema' in ckpt:
|
||||
_state_dict = ckpt['model_ema']
|
||||
elif 'model' in ckpt:
|
||||
_state_dict = ckpt['model']
|
||||
else:
|
||||
_state_dict = ckpt
|
||||
|
||||
state_dict = OrderedDict()
|
||||
for k, v in _state_dict.items():
|
||||
if k.startswith('backbone.'):
|
||||
state_dict[k[9:]] = v
|
||||
else:
|
||||
state_dict[k] = v
|
||||
|
||||
# strip prefix of state_dict
|
||||
if list(state_dict.keys())[0].startswith('module.'):
|
||||
state_dict = {k[7:]: v for k, v in state_dict.items()}
|
||||
|
||||
# load state_dict
|
||||
meg = self.load_state_dict(state_dict, False)
|
||||
logger.info(meg)
|
||||
|
||||
def _init_weights(self, 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)
|
||||
|
||||
def _init_deform_weights(self, m):
|
||||
if isinstance(m, getattr(DCNv4, self.core_op)):
|
||||
m._reset_parameters()
|
||||
|
||||
@torch.jit.ignore
|
||||
def lr_decay_keywards(self, decay_ratio=0.87):
|
||||
lr_ratios = {}
|
||||
|
||||
# blocks
|
||||
idx = 0
|
||||
for i in range(4):
|
||||
layer_num = 3 - i # 3 2 1 0
|
||||
for j in range(self.depths[layer_num]):
|
||||
block_num = self.depths[layer_num] - j - 1
|
||||
tag = 'levels.{}.blocks.{}.'.format(layer_num, block_num)
|
||||
decay = 1.0 * (decay_ratio**idx)
|
||||
lr_ratios[tag] = decay
|
||||
idx += 1
|
||||
# patch_embed (before stage-1)
|
||||
lr_ratios["patch_embed"] = lr_ratios['levels.0.blocks.0.']
|
||||
# levels.0.downsample (between stage-1 and stage-2)
|
||||
lr_ratios["levels.0.downsample"] = lr_ratios['levels.1.blocks.0.']
|
||||
lr_ratios["levels.0.norm"] = lr_ratios['levels.1.blocks.0.']
|
||||
# levels.1.downsample (between stage-2 and stage-3)
|
||||
lr_ratios["levels.1.downsample"] = lr_ratios['levels.2.blocks.0.']
|
||||
lr_ratios["levels.1.norm"] = lr_ratios['levels.2.blocks.0.']
|
||||
# levels.2.downsample (between stage-3 and stage-4)
|
||||
lr_ratios["levels.2.downsample"] = lr_ratios['levels.3.blocks.0.']
|
||||
lr_ratios["levels.2.norm"] = lr_ratios['levels.3.blocks.0.']
|
||||
return lr_ratios
|
||||
|
||||
def forward(self, x):
|
||||
x = self.patch_embed(x)
|
||||
N, H, W, C = x.shape
|
||||
x = x.view(N, H*W, C)
|
||||
|
||||
shape=(H, W)
|
||||
seq_out = []
|
||||
for level_idx, level in enumerate(self.levels):
|
||||
old_shape = shape
|
||||
x, x_ , shape = level(x, return_wo_downsample=True, shape=shape, level_idx=level_idx)
|
||||
if level_idx in self.out_indices:
|
||||
h, w= old_shape
|
||||
seq_out.append(x_.reshape(N, h, w, -1).permute(0, 3, 1, 2))
|
||||
return seq_out
|
||||
23
segmentation/mmseg_custom/models/builder.py
Normal file
23
segmentation/mmseg_custom/models/builder.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import warnings # noqa: F401,F403
|
||||
|
||||
from mmcv.utils import Registry
|
||||
|
||||
TRANSFORMER = Registry('Transformer')
|
||||
MASK_ASSIGNERS = Registry('mask_assigner')
|
||||
MATCH_COST = Registry('match_cost')
|
||||
|
||||
|
||||
def build_match_cost(cfg):
|
||||
"""Build Match Cost."""
|
||||
return MATCH_COST.build(cfg)
|
||||
|
||||
|
||||
def build_assigner(cfg):
|
||||
"""Build Assigner."""
|
||||
return MASK_ASSIGNERS.build(cfg)
|
||||
|
||||
|
||||
def build_transformer(cfg):
|
||||
"""Build Transformer."""
|
||||
return TRANSFORMER.build(cfg)
|
||||
@@ -0,0 +1,8 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from .mask2former_head import Mask2FormerHead
|
||||
from .maskformer_head import MaskFormerHead
|
||||
from .msda import CustomMultiScaleDeformableAttention
|
||||
__all__ = [
|
||||
'MaskFormerHead',
|
||||
'Mask2FormerHead',
|
||||
]
|
||||
@@ -0,0 +1,579 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import copy
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from mmcv.cnn import Conv2d, build_plugin_layer, caffe2_xavier_init
|
||||
from mmcv.cnn.bricks.transformer import (build_positional_encoding,
|
||||
build_transformer_layer_sequence)
|
||||
from mmcv.ops import point_sample
|
||||
from mmcv.runner import ModuleList, force_fp32
|
||||
from mmseg.models.builder import HEADS, build_loss
|
||||
from mmseg.models.decode_heads.decode_head import BaseDecodeHead
|
||||
|
||||
from ...core import build_sampler, multi_apply, reduce_mean
|
||||
from ..builder import build_assigner
|
||||
from ..utils import get_uncertain_point_coords_with_randomness
|
||||
|
||||
|
||||
@HEADS.register_module()
|
||||
class Mask2FormerHead(BaseDecodeHead):
|
||||
"""Implements the Mask2Former head.
|
||||
|
||||
See `Masked-attention Mask Transformer for Universal Image
|
||||
Segmentation <https://arxiv.org/pdf/2112.01527>`_ for details.
|
||||
|
||||
Args:
|
||||
in_channels (list[int]): Number of channels in the input feature map.
|
||||
feat_channels (int): Number of channels for features.
|
||||
out_channels (int): Number of channels for output.
|
||||
num_classes (int): Number of classes.
|
||||
num_things_classes (int): Number of things.
|
||||
num_stuff_classes (int): Number of stuff.
|
||||
num_queries (int): Number of query in Transformer decoder.
|
||||
pixel_decoder (:obj:`mmcv.ConfigDict` | dict): Config for pixel
|
||||
decoder. Defaults to None.
|
||||
enforce_decoder_input_project (bool, optional): Whether to add
|
||||
a layer to change the embed_dim of tranformer encoder in
|
||||
pixel decoder to the embed_dim of transformer decoder.
|
||||
Defaults to False.
|
||||
transformer_decoder (:obj:`mmcv.ConfigDict` | dict): Config for
|
||||
transformer decoder. Defaults to None.
|
||||
positional_encoding (:obj:`mmcv.ConfigDict` | dict): Config for
|
||||
transformer decoder position encoding. Defaults to None.
|
||||
loss_cls (:obj:`mmcv.ConfigDict` | dict): Config of the classification
|
||||
loss. Defaults to None.
|
||||
loss_mask (:obj:`mmcv.ConfigDict` | dict): Config of the mask loss.
|
||||
Defaults to None.
|
||||
loss_dice (:obj:`mmcv.ConfigDict` | dict): Config of the dice loss.
|
||||
Defaults to None.
|
||||
train_cfg (:obj:`mmcv.ConfigDict` | dict): Training config of
|
||||
Mask2Former head.
|
||||
test_cfg (:obj:`mmcv.ConfigDict` | dict): Testing config of
|
||||
Mask2Former head.
|
||||
init_cfg (dict or list[dict], optional): Initialization config dict.
|
||||
Defaults to None.
|
||||
"""
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
feat_channels,
|
||||
out_channels,
|
||||
num_classes=80,
|
||||
num_things_classes=None,
|
||||
num_stuff_classes=None,
|
||||
num_queries=100,
|
||||
num_transformer_feat_level=3,
|
||||
pixel_decoder=None,
|
||||
enforce_decoder_input_project=False,
|
||||
transformer_decoder=None,
|
||||
positional_encoding=None,
|
||||
loss_cls=None,
|
||||
loss_mask=None,
|
||||
loss_dice=None,
|
||||
train_cfg=None,
|
||||
test_cfg=None,
|
||||
init_cfg=None,
|
||||
**kwargs):
|
||||
super(Mask2FormerHead, self).__init__(
|
||||
in_channels=in_channels,
|
||||
channels=feat_channels,
|
||||
num_classes=num_classes,
|
||||
init_cfg=init_cfg,
|
||||
input_transform='multiple_select',
|
||||
**kwargs)
|
||||
self.num_classes = num_classes
|
||||
self.num_queries = num_queries
|
||||
self.num_transformer_feat_level = num_transformer_feat_level
|
||||
self.num_heads = transformer_decoder.transformerlayers. \
|
||||
attn_cfgs.num_heads
|
||||
self.num_transformer_decoder_layers = transformer_decoder.num_layers
|
||||
assert pixel_decoder.encoder.transformerlayers. \
|
||||
attn_cfgs.num_levels == num_transformer_feat_level
|
||||
pixel_decoder_ = copy.deepcopy(pixel_decoder)
|
||||
pixel_decoder_.update(
|
||||
in_channels=in_channels,
|
||||
feat_channels=feat_channels,
|
||||
out_channels=out_channels)
|
||||
self.pixel_decoder = build_plugin_layer(pixel_decoder_)[1]
|
||||
self.transformer_decoder = build_transformer_layer_sequence(
|
||||
transformer_decoder)
|
||||
self.decoder_embed_dims = self.transformer_decoder.embed_dims
|
||||
|
||||
self.decoder_input_projs = ModuleList()
|
||||
# from low resolution to high resolution
|
||||
for _ in range(num_transformer_feat_level):
|
||||
if (self.decoder_embed_dims != feat_channels
|
||||
or enforce_decoder_input_project):
|
||||
self.decoder_input_projs.append(
|
||||
Conv2d(
|
||||
feat_channels, self.decoder_embed_dims, kernel_size=1))
|
||||
else:
|
||||
self.decoder_input_projs.append(nn.Identity())
|
||||
self.decoder_positional_encoding = build_positional_encoding(
|
||||
positional_encoding)
|
||||
self.query_embed = nn.Embedding(self.num_queries, feat_channels)
|
||||
self.query_feat = nn.Embedding(self.num_queries, feat_channels)
|
||||
# from low resolution to high resolution
|
||||
self.level_embed = nn.Embedding(self.num_transformer_feat_level,
|
||||
feat_channels)
|
||||
|
||||
self.cls_embed = nn.Linear(feat_channels, self.num_classes + 1)
|
||||
self.mask_embed = nn.Sequential(
|
||||
nn.Linear(feat_channels, feat_channels), nn.ReLU(inplace=True),
|
||||
nn.Linear(feat_channels, feat_channels), nn.ReLU(inplace=True),
|
||||
nn.Linear(feat_channels, out_channels))
|
||||
self.conv_seg = None # fix a bug here (conv_seg is not used)
|
||||
|
||||
self.test_cfg = test_cfg
|
||||
self.train_cfg = train_cfg
|
||||
if train_cfg:
|
||||
self.assigner = build_assigner(self.train_cfg.assigner)
|
||||
self.sampler = build_sampler(self.train_cfg.sampler, context=self)
|
||||
self.num_points = self.train_cfg.get('num_points', 12544)
|
||||
self.oversample_ratio = self.train_cfg.get('oversample_ratio', 3.0)
|
||||
self.importance_sample_ratio = self.train_cfg.get(
|
||||
'importance_sample_ratio', 0.75)
|
||||
|
||||
self.class_weight = loss_cls.class_weight
|
||||
self.loss_cls = build_loss(loss_cls)
|
||||
self.loss_mask = build_loss(loss_mask)
|
||||
self.loss_dice = build_loss(loss_dice)
|
||||
|
||||
def init_weights(self):
|
||||
for m in self.decoder_input_projs:
|
||||
if isinstance(m, Conv2d):
|
||||
caffe2_xavier_init(m, bias=0)
|
||||
|
||||
self.pixel_decoder.init_weights()
|
||||
|
||||
for p in self.transformer_decoder.parameters():
|
||||
if p.dim() > 1:
|
||||
nn.init.xavier_normal_(p)
|
||||
|
||||
def get_targets(self, cls_scores_list, mask_preds_list, gt_labels_list,
|
||||
gt_masks_list, img_metas):
|
||||
"""Compute classification and mask targets for all images for a decoder
|
||||
layer.
|
||||
|
||||
Args:
|
||||
cls_scores_list (list[Tensor]): Mask score logits from a single
|
||||
decoder layer for all images. Each with shape [num_queries,
|
||||
cls_out_channels].
|
||||
mask_preds_list (list[Tensor]): Mask logits from a single decoder
|
||||
layer for all images. Each with shape [num_queries, h, w].
|
||||
gt_labels_list (list[Tensor]): Ground truth class indices for all
|
||||
images. Each with shape (n, ), n is the sum of number of stuff
|
||||
type and number of instance in a image.
|
||||
gt_masks_list (list[Tensor]): Ground truth mask for each image,
|
||||
each with shape (n, h, w).
|
||||
img_metas (list[dict]): List of image meta information.
|
||||
|
||||
Returns:
|
||||
tuple[list[Tensor]]: a tuple containing the following targets.
|
||||
|
||||
- labels_list (list[Tensor]): Labels of all images.
|
||||
Each with shape [num_queries, ].
|
||||
- label_weights_list (list[Tensor]): Label weights of all
|
||||
images.Each with shape [num_queries, ].
|
||||
- mask_targets_list (list[Tensor]): Mask targets of all images.
|
||||
Each with shape [num_queries, h, w].
|
||||
- mask_weights_list (list[Tensor]): Mask weights of all images.
|
||||
Each with shape [num_queries, ].
|
||||
- num_total_pos (int): Number of positive samples in all
|
||||
images.
|
||||
- num_total_neg (int): Number of negative samples in all
|
||||
images.
|
||||
"""
|
||||
(labels_list, label_weights_list, mask_targets_list, mask_weights_list,
|
||||
pos_inds_list,
|
||||
neg_inds_list) = multi_apply(self._get_target_single, cls_scores_list,
|
||||
mask_preds_list, gt_labels_list,
|
||||
gt_masks_list, img_metas)
|
||||
|
||||
num_total_pos = sum((inds.numel() for inds in pos_inds_list))
|
||||
num_total_neg = sum((inds.numel() for inds in neg_inds_list))
|
||||
return (labels_list, label_weights_list, mask_targets_list,
|
||||
mask_weights_list, num_total_pos, num_total_neg)
|
||||
|
||||
def _get_target_single(self, cls_score, mask_pred, gt_labels, gt_masks,
|
||||
img_metas):
|
||||
"""Compute classification and mask targets for one image.
|
||||
|
||||
Args:
|
||||
cls_score (Tensor): Mask score logits from a single decoder layer
|
||||
for one image. Shape (num_queries, cls_out_channels).
|
||||
mask_pred (Tensor): Mask logits for a single decoder layer for one
|
||||
image. Shape (num_queries, h, w).
|
||||
gt_labels (Tensor): Ground truth class indices for one image with
|
||||
shape (num_gts, ).
|
||||
gt_masks (Tensor): Ground truth mask for each image, each with
|
||||
shape (num_gts, h, w).
|
||||
img_metas (dict): Image informtation.
|
||||
|
||||
Returns:
|
||||
tuple[Tensor]: A tuple containing the following for one image.
|
||||
|
||||
- labels (Tensor): Labels of each image. \
|
||||
shape (num_queries, ).
|
||||
- label_weights (Tensor): Label weights of each image. \
|
||||
shape (num_queries, ).
|
||||
- mask_targets (Tensor): Mask targets of each image. \
|
||||
shape (num_queries, h, w).
|
||||
- mask_weights (Tensor): Mask weights of each image. \
|
||||
shape (num_queries, ).
|
||||
- pos_inds (Tensor): Sampled positive indices for each \
|
||||
image.
|
||||
- neg_inds (Tensor): Sampled negative indices for each \
|
||||
image.
|
||||
"""
|
||||
# sample points
|
||||
num_queries = cls_score.shape[0]
|
||||
num_gts = gt_labels.shape[0]
|
||||
|
||||
point_coords = torch.rand((1, self.num_points, 2),
|
||||
device=cls_score.device)
|
||||
# shape (num_queries, num_points)
|
||||
mask_points_pred = point_sample(
|
||||
mask_pred.unsqueeze(1), point_coords.repeat(num_queries, 1,
|
||||
1)).squeeze(1)
|
||||
# shape (num_gts, num_points)
|
||||
gt_points_masks = point_sample(
|
||||
gt_masks.unsqueeze(1).float(), point_coords.repeat(num_gts, 1,
|
||||
1)).squeeze(1)
|
||||
|
||||
# assign and sample
|
||||
assign_result = self.assigner.assign(cls_score, mask_points_pred,
|
||||
gt_labels, gt_points_masks,
|
||||
img_metas)
|
||||
sampling_result = self.sampler.sample(assign_result, mask_pred,
|
||||
gt_masks)
|
||||
pos_inds = sampling_result.pos_inds
|
||||
neg_inds = sampling_result.neg_inds
|
||||
|
||||
# label target
|
||||
labels = gt_labels.new_full((self.num_queries, ),
|
||||
self.num_classes,
|
||||
dtype=torch.long)
|
||||
labels[pos_inds] = gt_labels[sampling_result.pos_assigned_gt_inds]
|
||||
label_weights = gt_labels.new_ones((self.num_queries, ))
|
||||
|
||||
# mask target
|
||||
mask_targets = gt_masks[sampling_result.pos_assigned_gt_inds]
|
||||
mask_weights = mask_pred.new_zeros((self.num_queries, ))
|
||||
mask_weights[pos_inds] = 1.0
|
||||
|
||||
return (labels, label_weights, mask_targets, mask_weights, pos_inds,
|
||||
neg_inds)
|
||||
|
||||
def loss_single(self, cls_scores, mask_preds, gt_labels_list,
|
||||
gt_masks_list, img_metas):
|
||||
"""Loss function for outputs from a single decoder layer.
|
||||
|
||||
Args:
|
||||
cls_scores (Tensor): Mask score logits from a single decoder layer
|
||||
for all images. Shape (batch_size, num_queries,
|
||||
cls_out_channels). Note `cls_out_channels` should includes
|
||||
background.
|
||||
mask_preds (Tensor): Mask logits for a pixel decoder for all
|
||||
images. Shape (batch_size, num_queries, h, w).
|
||||
gt_labels_list (list[Tensor]): Ground truth class indices for each
|
||||
image, each with shape (num_gts, ).
|
||||
gt_masks_list (list[Tensor]): Ground truth mask for each image,
|
||||
each with shape (num_gts, h, w).
|
||||
img_metas (list[dict]): List of image meta information.
|
||||
|
||||
Returns:
|
||||
tuple[Tensor]: Loss components for outputs from a single \
|
||||
decoder layer.
|
||||
"""
|
||||
num_imgs = cls_scores.size(0)
|
||||
cls_scores_list = [cls_scores[i] for i in range(num_imgs)]
|
||||
mask_preds_list = [mask_preds[i] for i in range(num_imgs)]
|
||||
(labels_list, label_weights_list, mask_targets_list, mask_weights_list,
|
||||
num_total_pos,
|
||||
num_total_neg) = self.get_targets(cls_scores_list, mask_preds_list,
|
||||
gt_labels_list, gt_masks_list,
|
||||
img_metas)
|
||||
# shape (batch_size, num_queries)
|
||||
labels = torch.stack(labels_list, dim=0)
|
||||
# shape (batch_size, num_queries)
|
||||
label_weights = torch.stack(label_weights_list, dim=0)
|
||||
# shape (num_total_gts, h, w)
|
||||
mask_targets = torch.cat(mask_targets_list, dim=0)
|
||||
# shape (batch_size, num_queries)
|
||||
mask_weights = torch.stack(mask_weights_list, dim=0)
|
||||
|
||||
# classfication loss
|
||||
# shape (batch_size * num_queries, )
|
||||
cls_scores = cls_scores.flatten(0, 1)
|
||||
labels = labels.flatten(0, 1)
|
||||
label_weights = label_weights.flatten(0, 1)
|
||||
|
||||
class_weight = cls_scores.new_tensor(self.class_weight)
|
||||
loss_cls = self.loss_cls(
|
||||
cls_scores,
|
||||
labels,
|
||||
label_weights,
|
||||
avg_factor=class_weight[labels].sum())
|
||||
|
||||
num_total_masks = reduce_mean(cls_scores.new_tensor([num_total_pos]))
|
||||
num_total_masks = max(num_total_masks, 1)
|
||||
|
||||
# extract positive ones
|
||||
# shape (batch_size, num_queries, h, w) -> (num_total_gts, h, w)
|
||||
mask_preds = mask_preds[mask_weights > 0]
|
||||
|
||||
if mask_targets.shape[0] == 0:
|
||||
# zero match
|
||||
loss_dice = mask_preds.sum()
|
||||
loss_mask = mask_preds.sum()
|
||||
return loss_cls, loss_mask, loss_dice
|
||||
|
||||
with torch.no_grad():
|
||||
points_coords = get_uncertain_point_coords_with_randomness(
|
||||
mask_preds.unsqueeze(1), None, self.num_points,
|
||||
self.oversample_ratio, self.importance_sample_ratio)
|
||||
# shape (num_total_gts, h, w) -> (num_total_gts, num_points)
|
||||
mask_point_targets = point_sample(
|
||||
mask_targets.unsqueeze(1).float(), points_coords).squeeze(1)
|
||||
# shape (num_queries, h, w) -> (num_queries, num_points)
|
||||
mask_point_preds = point_sample(
|
||||
mask_preds.unsqueeze(1), points_coords).squeeze(1)
|
||||
|
||||
# dice loss
|
||||
loss_dice = self.loss_dice(
|
||||
mask_point_preds, mask_point_targets, avg_factor=num_total_masks)
|
||||
|
||||
# mask loss
|
||||
# shape (num_queries, num_points) -> (num_queries * num_points, )
|
||||
mask_point_preds = mask_point_preds.reshape(-1,1)
|
||||
# shape (num_total_gts, num_points) -> (num_total_gts * num_points, )
|
||||
mask_point_targets = mask_point_targets.reshape(-1)
|
||||
loss_mask = self.loss_mask(
|
||||
mask_point_preds,
|
||||
mask_point_targets,
|
||||
avg_factor=num_total_masks * self.num_points)
|
||||
|
||||
return loss_cls, loss_mask, loss_dice
|
||||
|
||||
@force_fp32(apply_to=('all_cls_scores', 'all_mask_preds'))
|
||||
def loss(self, all_cls_scores, all_mask_preds, gt_labels_list,
|
||||
gt_masks_list, img_metas):
|
||||
"""Loss function.
|
||||
|
||||
Args:
|
||||
all_cls_scores (Tensor): Classification scores for all decoder
|
||||
layers with shape [num_decoder, batch_size, num_queries,
|
||||
cls_out_channels].
|
||||
all_mask_preds (Tensor): Mask scores for all decoder layers with
|
||||
shape [num_decoder, batch_size, num_queries, h, w].
|
||||
gt_labels_list (list[Tensor]): Ground truth class indices for each
|
||||
image with shape (n, ). n is the sum of number of stuff type
|
||||
and number of instance in a image.
|
||||
gt_masks_list (list[Tensor]): Ground truth mask for each image with
|
||||
shape (n, h, w).
|
||||
img_metas (list[dict]): List of image meta information.
|
||||
|
||||
Returns:
|
||||
dict[str, Tensor]: A dictionary of loss components.
|
||||
"""
|
||||
num_dec_layers = len(all_cls_scores)
|
||||
all_gt_labels_list = [gt_labels_list for _ in range(num_dec_layers)]
|
||||
all_gt_masks_list = [gt_masks_list for _ in range(num_dec_layers)]
|
||||
img_metas_list = [img_metas for _ in range(num_dec_layers)]
|
||||
losses_cls, losses_mask, losses_dice = multi_apply(
|
||||
self.loss_single, all_cls_scores, all_mask_preds,
|
||||
all_gt_labels_list, all_gt_masks_list, img_metas_list)
|
||||
|
||||
loss_dict = dict()
|
||||
# loss from the last decoder layer
|
||||
loss_dict['loss_cls'] = losses_cls[-1]
|
||||
loss_dict['loss_mask'] = losses_mask[-1]
|
||||
loss_dict['loss_dice'] = losses_dice[-1]
|
||||
# loss from other decoder layers
|
||||
num_dec_layer = 0
|
||||
for loss_cls_i, loss_mask_i, loss_dice_i in zip(
|
||||
losses_cls[:-1], losses_mask[:-1], losses_dice[:-1]):
|
||||
loss_dict[f'd{num_dec_layer}.loss_cls'] = loss_cls_i
|
||||
loss_dict[f'd{num_dec_layer}.loss_mask'] = loss_mask_i
|
||||
loss_dict[f'd{num_dec_layer}.loss_dice'] = loss_dice_i
|
||||
num_dec_layer += 1
|
||||
return loss_dict
|
||||
|
||||
def forward_head(self, decoder_out, mask_feature, attn_mask_target_size):
|
||||
"""Forward for head part which is called after every decoder layer.
|
||||
|
||||
Args:
|
||||
decoder_out (Tensor): in shape (num_queries, batch_size, c).
|
||||
mask_feature (Tensor): in shape (batch_size, c, h, w).
|
||||
attn_mask_target_size (tuple[int, int]): target attention
|
||||
mask size.
|
||||
|
||||
Returns:
|
||||
tuple: A tuple contain three elements.
|
||||
|
||||
- cls_pred (Tensor): Classification scores in shape \
|
||||
(batch_size, num_queries, cls_out_channels). \
|
||||
Note `cls_out_channels` should includes background.
|
||||
- mask_pred (Tensor): Mask scores in shape \
|
||||
(batch_size, num_queries,h, w).
|
||||
- attn_mask (Tensor): Attention mask in shape \
|
||||
(batch_size * num_heads, num_queries, h, w).
|
||||
"""
|
||||
decoder_out = self.transformer_decoder.post_norm(decoder_out)
|
||||
decoder_out = decoder_out.transpose(0, 1)
|
||||
# shape (num_queries, batch_size, c)
|
||||
cls_pred = self.cls_embed(decoder_out)
|
||||
# shape (num_queries, batch_size, c)
|
||||
mask_embed = self.mask_embed(decoder_out)
|
||||
# shape (num_queries, batch_size, h, w)
|
||||
mask_pred = torch.einsum('bqc,bchw->bqhw', mask_embed, mask_feature)
|
||||
attn_mask = F.interpolate(
|
||||
mask_pred,
|
||||
attn_mask_target_size,
|
||||
mode='bilinear',
|
||||
align_corners=False)
|
||||
# shape (num_queries, batch_size, h, w) ->
|
||||
# (batch_size * num_head, num_queries, h, w)
|
||||
attn_mask = attn_mask.flatten(2).unsqueeze(1).repeat(
|
||||
(1, self.num_heads, 1, 1)).flatten(0, 1)
|
||||
attn_mask = attn_mask.sigmoid() < 0.5
|
||||
attn_mask = attn_mask.detach()
|
||||
|
||||
return cls_pred, mask_pred, attn_mask
|
||||
|
||||
def forward(self, feats, img_metas):
|
||||
"""Forward function.
|
||||
|
||||
Args:
|
||||
feats (list[Tensor]): Multi scale Features from the
|
||||
upstream network, each is a 4D-tensor.
|
||||
img_metas (list[dict]): List of image information.
|
||||
|
||||
Returns:
|
||||
tuple: A tuple contains two elements.
|
||||
|
||||
- cls_pred_list (list[Tensor)]: Classification logits \
|
||||
for each decoder layer. Each is a 3D-tensor with shape \
|
||||
(batch_size, num_queries, cls_out_channels). \
|
||||
Note `cls_out_channels` should includes background.
|
||||
- mask_pred_list (list[Tensor]): Mask logits for each \
|
||||
decoder layer. Each with shape (batch_size, num_queries, \
|
||||
h, w).
|
||||
"""
|
||||
batch_size = len(img_metas)
|
||||
mask_features, multi_scale_memorys = self.pixel_decoder(feats)
|
||||
# multi_scale_memorys (from low resolution to high resolution)
|
||||
decoder_inputs = []
|
||||
decoder_positional_encodings = []
|
||||
for i in range(self.num_transformer_feat_level):
|
||||
decoder_input = self.decoder_input_projs[i](multi_scale_memorys[i])
|
||||
# shape (batch_size, c, h, w) -> (h*w, batch_size, c)
|
||||
decoder_input = decoder_input.flatten(2).permute(2, 0, 1)
|
||||
level_embed = self.level_embed.weight[i].view(1, 1, -1)
|
||||
decoder_input = decoder_input + level_embed
|
||||
# shape (batch_size, c, h, w) -> (h*w, batch_size, c)
|
||||
mask = decoder_input.new_zeros(
|
||||
(batch_size, ) + multi_scale_memorys[i].shape[-2:],
|
||||
dtype=torch.bool)
|
||||
decoder_positional_encoding = self.decoder_positional_encoding(
|
||||
mask)
|
||||
decoder_positional_encoding = decoder_positional_encoding.flatten(
|
||||
2).permute(2, 0, 1)
|
||||
decoder_inputs.append(decoder_input)
|
||||
decoder_positional_encodings.append(decoder_positional_encoding)
|
||||
# shape (num_queries, c) -> (num_queries, batch_size, c)
|
||||
query_feat = self.query_feat.weight.unsqueeze(1).repeat(
|
||||
(1, batch_size, 1))
|
||||
query_embed = self.query_embed.weight.unsqueeze(1).repeat(
|
||||
(1, batch_size, 1))
|
||||
|
||||
cls_pred_list = []
|
||||
mask_pred_list = []
|
||||
cls_pred, mask_pred, attn_mask = self.forward_head(
|
||||
query_feat, mask_features, multi_scale_memorys[0].shape[-2:])
|
||||
cls_pred_list.append(cls_pred)
|
||||
mask_pred_list.append(mask_pred)
|
||||
|
||||
for i in range(self.num_transformer_decoder_layers):
|
||||
level_idx = i % self.num_transformer_feat_level
|
||||
# if a mask is all True(all background), then set it all False.
|
||||
attn_mask[torch.where(
|
||||
attn_mask.sum(-1) == attn_mask.shape[-1])] = False
|
||||
|
||||
# cross_attn + self_attn
|
||||
layer = self.transformer_decoder.layers[i]
|
||||
attn_masks = [attn_mask, None]
|
||||
query_feat = layer(
|
||||
query=query_feat,
|
||||
key=decoder_inputs[level_idx],
|
||||
value=decoder_inputs[level_idx],
|
||||
query_pos=query_embed,
|
||||
key_pos=decoder_positional_encodings[level_idx],
|
||||
attn_masks=attn_masks,
|
||||
query_key_padding_mask=None,
|
||||
# here we do not apply masking on padded region
|
||||
key_padding_mask=None)
|
||||
cls_pred, mask_pred, attn_mask = self.forward_head(
|
||||
query_feat, mask_features, multi_scale_memorys[
|
||||
(i + 1) % self.num_transformer_feat_level].shape[-2:])
|
||||
|
||||
cls_pred_list.append(cls_pred)
|
||||
mask_pred_list.append(mask_pred)
|
||||
|
||||
return cls_pred_list, mask_pred_list
|
||||
|
||||
def forward_train(self, x, img_metas, gt_semantic_seg, gt_labels,
|
||||
gt_masks):
|
||||
"""Forward function for training mode.
|
||||
|
||||
Args:
|
||||
x (list[Tensor]): Multi-level features from the upstream network,
|
||||
each is a 4D-tensor.
|
||||
img_metas (list[Dict]): List of image information.
|
||||
gt_semantic_seg (list[tensor]):Each element is the ground truth
|
||||
of semantic segmentation with the shape (N, H, W).
|
||||
train_cfg (dict): The training config, which not been used in
|
||||
maskformer.
|
||||
gt_labels (list[Tensor]): Each element is ground truth labels of
|
||||
each box, shape (num_gts,).
|
||||
gt_masks (list[BitmapMasks]): Each element is masks of instances
|
||||
of a image, shape (num_gts, h, w).
|
||||
|
||||
Returns:
|
||||
losses (dict[str, Tensor]): a dictionary of loss components
|
||||
"""
|
||||
|
||||
# forward
|
||||
all_cls_scores, all_mask_preds = self(x, img_metas)
|
||||
|
||||
# loss
|
||||
losses = self.loss(all_cls_scores, all_mask_preds, gt_labels, gt_masks,
|
||||
img_metas)
|
||||
|
||||
return losses
|
||||
|
||||
def forward_test(self, inputs, img_metas, test_cfg):
|
||||
"""Test segment without test-time aumengtation.
|
||||
|
||||
Only the output of last decoder layers was used.
|
||||
|
||||
Args:
|
||||
inputs (list[Tensor]): Multi-level features from the
|
||||
upstream network, each is a 4D-tensor.
|
||||
img_metas (list[dict]): List of image information.
|
||||
test_cfg (dict): Testing config.
|
||||
|
||||
Returns:
|
||||
seg_mask (Tensor): Predicted semantic segmentation logits.
|
||||
"""
|
||||
all_cls_scores, all_mask_preds = self(inputs, img_metas)
|
||||
cls_score, mask_pred = all_cls_scores[-1], all_mask_preds[-1]
|
||||
ori_h, ori_w, _ = img_metas[0]['ori_shape']
|
||||
|
||||
# semantic inference
|
||||
cls_score = F.softmax(cls_score, dim=-1)[..., :-1]
|
||||
mask_pred = mask_pred.sigmoid()
|
||||
seg_mask = torch.einsum('bqc,bqhw->bchw', cls_score, mask_pred)
|
||||
return seg_mask
|
||||
519
segmentation/mmseg_custom/models/decode_heads/maskformer_head.py
Normal file
519
segmentation/mmseg_custom/models/decode_heads/maskformer_head.py
Normal file
@@ -0,0 +1,519 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from mmcv.cnn import Conv2d, build_plugin_layer, kaiming_init
|
||||
from mmcv.cnn.bricks.transformer import (build_positional_encoding,
|
||||
build_transformer_layer_sequence)
|
||||
from mmcv.runner import force_fp32
|
||||
from mmseg.models.builder import HEADS, build_loss
|
||||
from mmseg.models.decode_heads.decode_head import BaseDecodeHead
|
||||
|
||||
from ...core import multi_apply, reduce_mean
|
||||
from ..builder import build_assigner, build_transformer
|
||||
|
||||
|
||||
@HEADS.register_module()
|
||||
class MaskFormerHead(BaseDecodeHead):
|
||||
"""Implements the MaskFormer head.
|
||||
|
||||
See `paper: Per-Pixel Classification is Not All You Need
|
||||
for Semantic Segmentation<https://arxiv.org/pdf/2107.06278>`
|
||||
for details.
|
||||
|
||||
Args:
|
||||
in_channels (list[int]): Number of channels in the input feature map.
|
||||
feat_channels (int): Number channels for feature.
|
||||
out_channels (int): Number channels for output.
|
||||
num_things_classes (int): Number of things.
|
||||
num_stuff_classes (int): Number of stuff.
|
||||
num_queries (int): Number of query in Transformer.
|
||||
pixel_decoder (obj:`mmcv.ConfigDict`|dict): Config for pixel decoder.
|
||||
Defaults to None.
|
||||
enforce_decoder_input_project (bool, optional): Whether to add a layer
|
||||
to change the embed_dim of tranformer encoder in pixel decoder to
|
||||
the embed_dim of transformer decoder. Defaults to False.
|
||||
transformer_decoder (obj:`mmcv.ConfigDict`|dict): Config for
|
||||
transformer decoder. Defaults to None.
|
||||
positional_encoding (obj:`mmcv.ConfigDict`|dict): Config for
|
||||
transformer decoder position encoding. Defaults to None.
|
||||
loss_cls (obj:`mmcv.ConfigDict`|dict): Config of the classification
|
||||
loss. Defaults to `CrossEntropyLoss`.
|
||||
loss_mask (obj:`mmcv.ConfigDict`|dict): Config of the mask loss.
|
||||
Defaults to `FocalLoss`.
|
||||
loss_dice (obj:`mmcv.ConfigDict`|dict): Config of the dice loss.
|
||||
Defaults to `DiceLoss`.
|
||||
train_cfg (obj:`mmcv.ConfigDict`|dict): Training config of Maskformer
|
||||
head.
|
||||
test_cfg (obj:`mmcv.ConfigDict`|dict): Testing config of Maskformer
|
||||
head.
|
||||
init_cfg (dict or list[dict], optional): Initialization config dict.
|
||||
Defaults to None.
|
||||
"""
|
||||
def __init__(self,
|
||||
out_channels,
|
||||
num_queries=100,
|
||||
pixel_decoder=None,
|
||||
enforce_decoder_input_project=False,
|
||||
transformer_decoder=None,
|
||||
positional_encoding=None,
|
||||
loss_cls=dict(
|
||||
type='CrossEntropyLoss',
|
||||
bg_cls_weight=0.1,
|
||||
use_sigmoid=False,
|
||||
loss_weight=1.0,
|
||||
class_weight=1.0),
|
||||
loss_mask=dict(
|
||||
type='FocalLoss',
|
||||
use_sigmoid=True,
|
||||
gamma=2.0,
|
||||
alpha=0.25,
|
||||
loss_weight=20.0),
|
||||
loss_dice=dict(
|
||||
type='DiceLoss',
|
||||
use_sigmoid=True,
|
||||
activate=True,
|
||||
naive_dice=True,
|
||||
loss_weight=1.0),
|
||||
assigner=dict(
|
||||
type='MaskHungarianAssigner',
|
||||
cls_cost=dict(type='ClassificationCost', weight=1.),
|
||||
dice_cost=dict(type='DiceCost', weight=1.0, pred_act=True,
|
||||
eps=1.0),
|
||||
mask_cost=dict(type='MaskFocalLossCost', weight=20.0)),
|
||||
**kwargs):
|
||||
super(MaskFormerHead, self).__init__(input_transform='multiple_select',
|
||||
**kwargs)
|
||||
self.num_queries = num_queries
|
||||
|
||||
pixel_decoder.update(
|
||||
in_channels=self.in_channels,
|
||||
feat_channels=self.channels,
|
||||
out_channels=out_channels)
|
||||
self.pixel_decoder = build_plugin_layer(pixel_decoder)[1]
|
||||
self.transformer_decoder = build_transformer_layer_sequence(
|
||||
transformer_decoder)
|
||||
self.decoder_embed_dims = self.transformer_decoder.embed_dims
|
||||
pixel_decoder_type = pixel_decoder.get('type')
|
||||
if pixel_decoder_type == 'PixelDecoder' and (
|
||||
self.decoder_embed_dims != self.in_channels[-1]
|
||||
or enforce_decoder_input_project):
|
||||
self.decoder_input_proj = Conv2d(
|
||||
self.in_channels[-1], self.decoder_embed_dims, kernel_size=1)
|
||||
else:
|
||||
self.decoder_input_proj = nn.Identity()
|
||||
self.decoder_pe = build_positional_encoding(positional_encoding)
|
||||
self.query_embed = nn.Embedding(self.num_queries, out_channels)
|
||||
|
||||
self.cls_embed = nn.Linear(self.channels, self.num_classes + 1)
|
||||
self.mask_embed = nn.Sequential(
|
||||
nn.Linear(self.channels, self.channels), nn.ReLU(inplace=True),
|
||||
nn.Linear(self.channels, self.channels), nn.ReLU(inplace=True),
|
||||
nn.Linear(self.channels, out_channels))
|
||||
|
||||
self.assigner = build_assigner(assigner)
|
||||
|
||||
self.bg_cls_weight = 0
|
||||
class_weight = loss_cls.get('class_weight', None)
|
||||
if class_weight is not None and (self.__class__ is MaskFormerHead):
|
||||
assert isinstance(class_weight, float), 'Expected ' \
|
||||
'class_weight to have type float. Found ' \
|
||||
f'{type(class_weight)}.'
|
||||
# NOTE following the official MaskFormerHead repo, bg_cls_weight
|
||||
# means relative classification weight of the VOID class.
|
||||
bg_cls_weight = loss_cls.get('bg_cls_weight', class_weight)
|
||||
assert isinstance(bg_cls_weight, float), 'Expected ' \
|
||||
'bg_cls_weight to have type float. Found ' \
|
||||
f'{type(bg_cls_weight)}.'
|
||||
class_weight = (self.num_classes + 1) * [class_weight]
|
||||
# set VOID class as the last indice
|
||||
class_weight[self.num_classes] = bg_cls_weight
|
||||
loss_cls.update({'class_weight': class_weight})
|
||||
if 'bg_cls_weight' in loss_cls:
|
||||
loss_cls.pop('bg_cls_weight')
|
||||
self.bg_cls_weight = bg_cls_weight
|
||||
|
||||
assert loss_cls['loss_weight'] == assigner['cls_cost']['weight'], \
|
||||
'The classification weight for loss and matcher should be' \
|
||||
'exactly the same.'
|
||||
assert loss_dice['loss_weight'] == assigner['dice_cost']['weight'], \
|
||||
f'The dice weight for loss and matcher' \
|
||||
f'should be exactly the same.'
|
||||
assert loss_mask['loss_weight'] == assigner['mask_cost']['weight'], \
|
||||
'The focal weight for loss and matcher should be' \
|
||||
'exactly the same.'
|
||||
self.loss_cls = build_loss(loss_cls)
|
||||
self.loss_mask = build_loss(loss_mask)
|
||||
self.loss_dice = build_loss(loss_dice)
|
||||
|
||||
self.init_weights()
|
||||
|
||||
def init_weights(self):
|
||||
kaiming_init(self.decoder_input_proj, a=1)
|
||||
|
||||
def get_targets(self, cls_scores_list, mask_preds_list, gt_labels_list,
|
||||
gt_masks_list, img_metas):
|
||||
"""Compute classification and mask targets for all images for a decoder
|
||||
layer.
|
||||
|
||||
Args:
|
||||
cls_scores_list (list[Tensor]): Mask score logits from a single
|
||||
decoder layer for all images. Each with shape [num_queries,
|
||||
cls_out_channels].
|
||||
mask_preds_list (list[Tensor]): Mask logits from a single decoder
|
||||
layer for all images. Each with shape [num_queries, h, w].
|
||||
gt_labels_list (list[Tensor]): Ground truth class indices for all
|
||||
images. Each with shape (n, ), n is the sum of number of stuff
|
||||
type and number of instance in a image.
|
||||
gt_masks_list (list[Tensor]): Ground truth mask for each image,
|
||||
each with shape (n, h, w).
|
||||
img_metas (list[dict]): List of image meta information.
|
||||
|
||||
Returns:
|
||||
tuple[list[Tensor]]: a tuple containing the following targets.
|
||||
|
||||
- labels_list (list[Tensor]): Labels of all images.
|
||||
Each with shape [num_queries, ].
|
||||
- label_weights_list (list[Tensor]): Label weights of all
|
||||
images.Each with shape [num_queries, ].
|
||||
- mask_targets_list (list[Tensor]): Mask targets of all images.
|
||||
Each with shape [num_queries, h, w].
|
||||
- mask_weights_list (list[Tensor]): Mask weights of all images.
|
||||
Each with shape [num_queries, ].
|
||||
- num_total_pos (int): Number of positive samples in all
|
||||
images.
|
||||
- num_total_neg (int): Number of negative samples in all
|
||||
images.
|
||||
"""
|
||||
(labels_list, label_weights_list, mask_targets_list, mask_weights_list,
|
||||
pos_inds_list,
|
||||
neg_inds_list) = multi_apply(self._get_target_single, cls_scores_list,
|
||||
mask_preds_list, gt_labels_list,
|
||||
gt_masks_list, img_metas)
|
||||
|
||||
num_total_pos = sum((inds.numel() for inds in pos_inds_list))
|
||||
num_total_neg = sum((inds.numel() for inds in neg_inds_list))
|
||||
return (labels_list, label_weights_list, mask_targets_list,
|
||||
mask_weights_list, num_total_pos, num_total_neg)
|
||||
|
||||
def _get_target_single(self, cls_score, mask_pred, gt_labels, gt_masks,
|
||||
img_metas):
|
||||
"""Compute classification and mask targets for one image.
|
||||
|
||||
Args:
|
||||
cls_score (Tensor): Mask score logits from a single decoder layer
|
||||
for one image. Shape [num_queries, cls_out_channels].
|
||||
mask_pred (Tensor): Mask logits for a single decoder layer for one
|
||||
image. Shape [num_queries, h, w].
|
||||
gt_labels (Tensor): Ground truth class indices for one image with
|
||||
shape (n, ). n is the sum of number of stuff type and number
|
||||
of instance in a image.
|
||||
gt_masks (Tensor): Ground truth mask for each image, each with
|
||||
shape (n, h, w).
|
||||
img_metas (dict): Image informtation.
|
||||
|
||||
Returns:
|
||||
tuple[Tensor]: a tuple containing the following for one image.
|
||||
|
||||
- labels (Tensor): Labels of each image.
|
||||
shape [num_queries, ].
|
||||
- label_weights (Tensor): Label weights of each image.
|
||||
shape [num_queries, ].
|
||||
- mask_targets (Tensor): Mask targets of each image.
|
||||
shape [num_queries, h, w].
|
||||
- mask_weights (Tensor): Mask weights of each image.
|
||||
shape [num_queries, ].
|
||||
- pos_inds (Tensor): Sampled positive indices for each image.
|
||||
- neg_inds (Tensor): Sampled negative indices for each image.
|
||||
"""
|
||||
target_shape = mask_pred.shape[-2:]
|
||||
gt_masks_downsampled = F.interpolate(
|
||||
gt_masks.unsqueeze(1).float(), target_shape,
|
||||
mode='nearest').squeeze(1).long()
|
||||
# assign and sample
|
||||
assign_result = self.assigner.assign(cls_score, mask_pred, gt_labels,
|
||||
gt_masks_downsampled, img_metas)
|
||||
# pos_ind: range from 1 to (self.num_classes)
|
||||
# which represents the positive index
|
||||
pos_inds = torch.nonzero(assign_result.gt_inds > 0,
|
||||
as_tuple=False).squeeze(-1).unique()
|
||||
neg_inds = torch.nonzero(assign_result.gt_inds == 0,
|
||||
as_tuple=False).squeeze(-1).unique()
|
||||
pos_assigned_gt_inds = assign_result.gt_inds[pos_inds] - 1
|
||||
|
||||
# label target
|
||||
labels = gt_labels.new_full((self.num_queries, ),
|
||||
self.num_classes,
|
||||
dtype=torch.long)
|
||||
labels[pos_inds] = gt_labels[pos_assigned_gt_inds]
|
||||
label_weights = gt_labels.new_ones(self.num_queries)
|
||||
|
||||
# mask target
|
||||
mask_targets = gt_masks[pos_assigned_gt_inds, :]
|
||||
mask_weights = mask_pred.new_zeros((self.num_queries, ))
|
||||
mask_weights[pos_inds] = 1.0
|
||||
|
||||
return (labels, label_weights, mask_targets, mask_weights, pos_inds,
|
||||
neg_inds)
|
||||
|
||||
@force_fp32(apply_to=('all_cls_scores', 'all_mask_preds'))
|
||||
def loss(self, all_cls_scores, all_mask_preds, gt_labels_list,
|
||||
gt_masks_list, img_metas):
|
||||
"""Loss function.
|
||||
|
||||
Args:
|
||||
all_cls_scores (Tensor): Classification scores for all decoder
|
||||
layers with shape [num_decoder, batch_size, num_queries,
|
||||
cls_out_channels].
|
||||
all_mask_preds (Tensor): Mask scores for all decoder layers with
|
||||
shape [num_decoder, batch_size, num_queries, h, w].
|
||||
gt_labels_list (list[Tensor]): Ground truth class indices for each
|
||||
image with shape (n, ). n is the sum of number of stuff type
|
||||
and number of instance in a image.
|
||||
gt_masks_list (list[Tensor]): Ground truth mask for each image with
|
||||
shape (n, h, w).
|
||||
img_metas (list[dict]): List of image meta information.
|
||||
|
||||
Returns:
|
||||
dict[str, Tensor]: A dictionary of loss components.
|
||||
"""
|
||||
num_dec_layers = len(all_cls_scores)
|
||||
all_gt_labels_list = [gt_labels_list for _ in range(num_dec_layers)]
|
||||
all_gt_masks_list = [gt_masks_list for _ in range(num_dec_layers)]
|
||||
img_metas_list = [img_metas for _ in range(num_dec_layers)]
|
||||
losses_cls, losses_mask, losses_dice = multi_apply(
|
||||
self.loss_single, all_cls_scores, all_mask_preds,
|
||||
all_gt_labels_list, all_gt_masks_list, img_metas_list)
|
||||
|
||||
loss_dict = dict()
|
||||
# loss from the last decoder layer
|
||||
loss_dict['loss_cls'] = losses_cls[-1]
|
||||
loss_dict['loss_mask'] = losses_mask[-1]
|
||||
loss_dict['loss_dice'] = losses_dice[-1]
|
||||
# loss from other decoder layers
|
||||
num_dec_layer = 0
|
||||
for loss_cls_i, loss_mask_i, loss_dice_i in zip(
|
||||
losses_cls[:-1], losses_mask[:-1], losses_dice[:-1]):
|
||||
loss_dict[f'd{num_dec_layer}.loss_cls'] = loss_cls_i
|
||||
loss_dict[f'd{num_dec_layer}.loss_mask'] = loss_mask_i
|
||||
loss_dict[f'd{num_dec_layer}.loss_dice'] = loss_dice_i
|
||||
num_dec_layer += 1
|
||||
return loss_dict
|
||||
|
||||
def loss_single(self, cls_scores, mask_preds, gt_labels_list,
|
||||
gt_masks_list, img_metas):
|
||||
"""Loss function for outputs from a single decoder layer.
|
||||
|
||||
Args:
|
||||
cls_scores (Tensor): Mask score logits from a single decoder layer
|
||||
for all images. Shape [batch_size, num_queries,
|
||||
cls_out_channels].
|
||||
mask_preds (Tensor): Mask logits for a pixel decoder for all
|
||||
images. Shape [batch_size, num_queries, h, w].
|
||||
gt_labels_list (list[Tensor]): Ground truth class indices for each
|
||||
image, each with shape (n, ). n is the sum of number of stuff
|
||||
types and number of instances in a image.
|
||||
gt_masks_list (list[Tensor]): Ground truth mask for each image,
|
||||
each with shape (n, h, w).
|
||||
img_metas (list[dict]): List of image meta information.
|
||||
|
||||
Returns:
|
||||
tuple[Tensor]:Loss components for outputs from a single decoder
|
||||
layer.
|
||||
"""
|
||||
num_imgs = cls_scores.size(0)
|
||||
cls_scores_list = [cls_scores[i] for i in range(num_imgs)]
|
||||
mask_preds_list = [mask_preds[i] for i in range(num_imgs)]
|
||||
|
||||
(labels_list, label_weights_list, mask_targets_list, mask_weights_list,
|
||||
num_total_pos,
|
||||
num_total_neg) = self.get_targets(cls_scores_list, mask_preds_list,
|
||||
gt_labels_list, gt_masks_list,
|
||||
img_metas)
|
||||
# shape [batch_size, num_queries]
|
||||
labels = torch.stack(labels_list, dim=0)
|
||||
# shape [batch_size, num_queries]
|
||||
label_weights = torch.stack(label_weights_list, dim=0)
|
||||
# shape [num_gts, h, w]
|
||||
mask_targets = torch.cat(mask_targets_list, dim=0)
|
||||
# shape [batch_size, num_queries]
|
||||
mask_weights = torch.stack(mask_weights_list, dim=0)
|
||||
|
||||
# classfication loss
|
||||
# shape [batch_size * num_queries, ]
|
||||
cls_scores = cls_scores.flatten(0, 1)
|
||||
# shape [batch_size * num_queries, ]
|
||||
labels = labels.flatten(0, 1)
|
||||
# shape [batch_size* num_queries, ]
|
||||
label_weights = label_weights.flatten(0, 1)
|
||||
|
||||
class_weight = cls_scores.new_ones(self.num_classes + 1)
|
||||
class_weight[-1] = self.bg_cls_weight
|
||||
|
||||
loss_cls = self.loss_cls(
|
||||
cls_scores,
|
||||
labels,
|
||||
label_weights,
|
||||
avg_factor=class_weight[labels].sum())
|
||||
|
||||
num_total_masks = reduce_mean(cls_scores.new_tensor([num_total_pos]))
|
||||
num_total_masks = max(num_total_masks, 1)
|
||||
|
||||
# extract positive ones
|
||||
mask_preds = mask_preds[mask_weights > 0]
|
||||
target_shape = mask_targets.shape[-2:]
|
||||
|
||||
if mask_targets.shape[0] == 0:
|
||||
# zero match
|
||||
loss_dice = mask_preds.sum()
|
||||
loss_mask = mask_preds.sum()
|
||||
return loss_cls, loss_mask, loss_dice
|
||||
|
||||
# upsample to shape of target
|
||||
# shape [num_gts, h, w]
|
||||
mask_preds = F.interpolate(
|
||||
mask_preds.unsqueeze(1),
|
||||
target_shape,
|
||||
mode='bilinear',
|
||||
align_corners=False).squeeze(1)
|
||||
|
||||
# dice loss
|
||||
loss_dice = self.loss_dice(
|
||||
mask_preds, mask_targets, avg_factor=num_total_masks)
|
||||
|
||||
# mask loss
|
||||
# FocalLoss support input of shape [n, num_class]
|
||||
h, w = mask_preds.shape[-2:]
|
||||
# shape [num_gts, h, w] -> [num_gts * h * w, 1]
|
||||
mask_preds = mask_preds.reshape(-1, 1)
|
||||
# shape [num_gts, h, w] -> [num_gts * h * w]
|
||||
mask_targets = mask_targets.reshape(-1)
|
||||
# target is (1 - mask_targets) !!!
|
||||
print("mask_pred:", mask_preds.shape)
|
||||
print("mask_targets:", mask_targets.shape)
|
||||
loss_mask = self.loss_mask(
|
||||
mask_preds, 1 - mask_targets, avg_factor=num_total_masks * h * w)
|
||||
|
||||
return loss_cls, loss_mask, loss_dice
|
||||
|
||||
def forward(self, feats, img_metas):
|
||||
"""Forward function.
|
||||
|
||||
Args:
|
||||
feats (list[Tensor]): Features from the upstream network, each
|
||||
is a 4D-tensor.
|
||||
img_metas (list[dict]): List of image information.
|
||||
|
||||
Returns:
|
||||
all_cls_scores (Tensor): Classification scores for each
|
||||
scale level. Each is a 4D-tensor with shape
|
||||
[num_decoder, batch_size, num_queries, cls_out_channels].
|
||||
Note `cls_out_channels` should includes background.
|
||||
all_mask_preds (Tensor): Mask scores for each decoder
|
||||
layer. Each with shape [num_decoder, batch_size,
|
||||
num_queries, h, w].
|
||||
"""
|
||||
batch_size = len(img_metas)
|
||||
input_img_h, input_img_w = img_metas[0]['pad_shape'][:-1]
|
||||
# input_img_h, input_img_w = img_metas[0]['batch_input_shape']
|
||||
padding_mask = feats[-1].new_ones(
|
||||
(batch_size, input_img_h, input_img_w), dtype=torch.float32)
|
||||
for i in range(batch_size):
|
||||
img_h, img_w, _ = img_metas[i]['img_shape']
|
||||
padding_mask[i, :img_h, :img_w] = 0
|
||||
padding_mask = F.interpolate(
|
||||
padding_mask.unsqueeze(1),
|
||||
size=feats[-1].shape[-2:],
|
||||
mode='nearest').to(torch.bool).squeeze(1)
|
||||
# when backbone is swin, memory is output of last stage of swin.
|
||||
# when backbone is r50, memory is output of tranformer encoder.
|
||||
mask_features, memory = self.pixel_decoder(feats, img_metas)
|
||||
pos_embed = self.decoder_pe(padding_mask)
|
||||
memory = self.decoder_input_proj(memory)
|
||||
# shape [batch_size, c, h, w] -> [h*w, batch_size, c]
|
||||
memory = memory.flatten(2).permute(2, 0, 1)
|
||||
pos_embed = pos_embed.flatten(2).permute(2, 0, 1)
|
||||
# shape [batch_size, h * w]
|
||||
padding_mask = padding_mask.flatten(1)
|
||||
# shape = [num_queries, embed_dims]
|
||||
query_embed = self.query_embed.weight
|
||||
# shape = [num_queries, batch_size, embed_dims]
|
||||
query_embed = query_embed.unsqueeze(1).repeat(1, batch_size, 1)
|
||||
target = torch.zeros_like(query_embed)
|
||||
# shape [num_decoder, num_queries, batch_size, embed_dims]
|
||||
out_dec = self.transformer_decoder(
|
||||
query=target,
|
||||
key=memory,
|
||||
value=memory,
|
||||
key_pos=pos_embed,
|
||||
query_pos=query_embed,
|
||||
key_padding_mask=padding_mask)
|
||||
# shape [num_decoder, batch_size, num_queries, embed_dims]
|
||||
out_dec = out_dec.transpose(1, 2)
|
||||
|
||||
# cls_scores
|
||||
all_cls_scores = self.cls_embed(out_dec)
|
||||
|
||||
# mask_preds
|
||||
mask_embed = self.mask_embed(out_dec)
|
||||
all_mask_preds = torch.einsum('lbqc,bchw->lbqhw', mask_embed,
|
||||
mask_features)
|
||||
|
||||
return all_cls_scores, all_mask_preds
|
||||
|
||||
def forward_train(self,
|
||||
x,
|
||||
img_metas,
|
||||
gt_semantic_seg,
|
||||
gt_labels,
|
||||
gt_masks):
|
||||
"""Forward function for training mode.
|
||||
|
||||
Args:
|
||||
x (list[Tensor]): Multi-level features from the upstream network,
|
||||
each is a 4D-tensor.
|
||||
img_metas (list[Dict]): List of image information.
|
||||
gt_semantic_seg (list[tensor]):Each element is the ground truth
|
||||
of semantic segmentation with the shape (N, H, W).
|
||||
train_cfg (dict): The training config, which not been used in
|
||||
maskformer.
|
||||
gt_labels (list[Tensor]): Each element is ground truth labels of
|
||||
each box, shape (num_gts,).
|
||||
gt_masks (list[BitmapMasks]): Each element is masks of instances
|
||||
of a image, shape (num_gts, h, w).
|
||||
|
||||
Returns:
|
||||
losses (dict[str, Tensor]): a dictionary of loss components
|
||||
"""
|
||||
|
||||
# forward
|
||||
all_cls_scores, all_mask_preds = self(x, img_metas)
|
||||
|
||||
# loss
|
||||
losses = self.loss(all_cls_scores, all_mask_preds, gt_labels, gt_masks,
|
||||
img_metas)
|
||||
|
||||
return losses
|
||||
|
||||
def forward_test(self, inputs, img_metas, test_cfg):
|
||||
"""Test segment without test-time aumengtation.
|
||||
|
||||
Only the output of last decoder layers was used.
|
||||
|
||||
Args:
|
||||
inputs (list[Tensor]): Multi-level features from the
|
||||
upstream network, each is a 4D-tensor.
|
||||
img_metas (list[dict]): List of image information.
|
||||
test_cfg (dict): Testing config.
|
||||
|
||||
Returns:
|
||||
seg_mask (Tensor): Predicted semantic segmentation logits.
|
||||
"""
|
||||
all_cls_scores, all_mask_preds = self(inputs, img_metas)
|
||||
cls_score, mask_pred = all_cls_scores[-1], all_mask_preds[-1]
|
||||
ori_h, ori_w, _ = img_metas[0]['ori_shape']
|
||||
|
||||
# semantic inference
|
||||
cls_score = F.softmax(cls_score, dim=-1)[..., :-1]
|
||||
mask_pred = mask_pred.sigmoid()
|
||||
seg_mask = torch.einsum('bqc,bqhw->bchw', cls_score, mask_pred)
|
||||
return seg_mask
|
||||
355
segmentation/mmseg_custom/models/decode_heads/msda.py
Normal file
355
segmentation/mmseg_custom/models/decode_heads/msda.py
Normal file
@@ -0,0 +1,355 @@
|
||||
# --------------------------------------------------------
|
||||
# DCNv4
|
||||
# Copyright (c) 2024 OpenGVLab
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# --------------------------------------------------------
|
||||
|
||||
|
||||
import torch
|
||||
from torch.cuda.amp import custom_bwd, custom_fwd
|
||||
from torch.autograd.function import Function, once_differentiable
|
||||
from mmcv.utils import ext_loader
|
||||
ext_module = ext_loader.load_ext(
|
||||
'_ext', ['ms_deform_attn_backward', 'ms_deform_attn_forward'])
|
||||
|
||||
class MultiScaleDeformableAttnFunction_fp16(Function):
|
||||
|
||||
@staticmethod
|
||||
@custom_fwd(cast_inputs=torch.float16)
|
||||
def forward(ctx, value, value_spatial_shapes, value_level_start_index,
|
||||
sampling_locations, attention_weights, im2col_step):
|
||||
"""GPU version of multi-scale deformable attention.
|
||||
|
||||
Args:
|
||||
value (Tensor): The value has shape
|
||||
(bs, num_keys, mum_heads, embed_dims//num_heads)
|
||||
value_spatial_shapes (Tensor): Spatial shape of
|
||||
each feature map, has shape (num_levels, 2),
|
||||
last dimension 2 represent (h, w)
|
||||
sampling_locations (Tensor): The location of sampling points,
|
||||
has shape
|
||||
(bs ,num_queries, num_heads, num_levels, num_points, 2),
|
||||
the last dimension 2 represent (x, y).
|
||||
attention_weights (Tensor): The weight of sampling points used
|
||||
when calculate the attention, has shape
|
||||
(bs ,num_queries, num_heads, num_levels, num_points),
|
||||
im2col_step (Tensor): The step used in image to column.
|
||||
|
||||
Returns:
|
||||
Tensor: has shape (bs, num_queries, embed_dims)
|
||||
"""
|
||||
ctx.im2col_step = im2col_step
|
||||
output = ext_module.ms_deform_attn_forward(
|
||||
value,
|
||||
value_spatial_shapes,
|
||||
value_level_start_index,
|
||||
sampling_locations,
|
||||
attention_weights,
|
||||
im2col_step=ctx.im2col_step)
|
||||
ctx.save_for_backward(value, value_spatial_shapes,
|
||||
value_level_start_index, sampling_locations,
|
||||
attention_weights)
|
||||
return output
|
||||
|
||||
@staticmethod
|
||||
@once_differentiable
|
||||
@custom_bwd
|
||||
def backward(ctx, grad_output):
|
||||
"""GPU version of backward function.
|
||||
|
||||
Args:
|
||||
grad_output (Tensor): Gradient
|
||||
of output tensor of forward.
|
||||
|
||||
Returns:
|
||||
Tuple[Tensor]: Gradient
|
||||
of input tensors in forward.
|
||||
"""
|
||||
value, value_spatial_shapes, value_level_start_index, \
|
||||
sampling_locations, attention_weights = ctx.saved_tensors
|
||||
grad_value = torch.zeros_like(value)
|
||||
grad_sampling_loc = torch.zeros_like(sampling_locations)
|
||||
grad_attn_weight = torch.zeros_like(attention_weights)
|
||||
|
||||
ext_module.ms_deform_attn_backward(
|
||||
value,
|
||||
value_spatial_shapes,
|
||||
value_level_start_index,
|
||||
sampling_locations,
|
||||
attention_weights,
|
||||
grad_output.contiguous(),
|
||||
grad_value,
|
||||
grad_sampling_loc,
|
||||
grad_attn_weight,
|
||||
im2col_step=ctx.im2col_step)
|
||||
|
||||
return grad_value, None, None, \
|
||||
grad_sampling_loc, grad_attn_weight, None
|
||||
|
||||
|
||||
|
||||
|
||||
class MultiScaleDeformableAttnFunction_fp32_old(Function):
|
||||
|
||||
@staticmethod
|
||||
@custom_fwd(cast_inputs=torch.float32)
|
||||
def forward(ctx, value, value_spatial_shapes, value_level_start_index,
|
||||
sampling_locations, attention_weights, im2col_step):
|
||||
"""GPU version of multi-scale deformable attention.
|
||||
|
||||
Args:
|
||||
value (Tensor): The value has shape
|
||||
(bs, num_keys, mum_heads, embed_dims//num_heads)
|
||||
value_spatial_shapes (Tensor): Spatial shape of
|
||||
each feature map, has shape (num_levels, 2),
|
||||
last dimension 2 represent (h, w)
|
||||
sampling_locations (Tensor): The location of sampling points,
|
||||
has shape
|
||||
(bs ,num_queries, num_heads, num_levels, num_points, 2),
|
||||
the last dimension 2 represent (x, y).
|
||||
attention_weights (Tensor): The weight of sampling points used
|
||||
when calculate the attention, has shape
|
||||
(bs ,num_queries, num_heads, num_levels, num_points),
|
||||
im2col_step (Tensor): The step used in image to column.
|
||||
|
||||
Returns:
|
||||
Tensor: has shape (bs, num_queries, embed_dims)
|
||||
"""
|
||||
|
||||
ctx.im2col_step = im2col_step
|
||||
output = ext_module.ms_deform_attn_forward(
|
||||
value.to(torch.float),
|
||||
value_spatial_shapes,
|
||||
value_level_start_index,
|
||||
sampling_locations.to(torch.float),
|
||||
attention_weights.to(torch.float),
|
||||
im2col_step=ctx.im2col_step).to(torch.float16)
|
||||
ctx.save_for_backward(value, value_spatial_shapes,
|
||||
value_level_start_index, sampling_locations,
|
||||
attention_weights)
|
||||
return output
|
||||
|
||||
@staticmethod
|
||||
@once_differentiable
|
||||
@custom_bwd
|
||||
def backward(ctx, grad_output):
|
||||
"""GPU version of backward function.
|
||||
|
||||
Args:
|
||||
grad_output (Tensor): Gradient
|
||||
of output tensor of forward.
|
||||
|
||||
Returns:
|
||||
Tuple[Tensor]: Gradient
|
||||
of input tensors in forward.
|
||||
"""
|
||||
value, value_spatial_shapes, value_level_start_index, \
|
||||
sampling_locations, attention_weights = ctx.saved_tensors
|
||||
grad_value = torch.zeros_like(value)
|
||||
grad_sampling_loc = torch.zeros_like(sampling_locations)
|
||||
grad_attn_weight = torch.zeros_like(attention_weights)
|
||||
|
||||
ext_module.ms_deform_attn_backward(
|
||||
value,
|
||||
value_spatial_shapes,
|
||||
value_level_start_index,
|
||||
sampling_locations,
|
||||
attention_weights,
|
||||
grad_output.contiguous(),
|
||||
grad_value,
|
||||
grad_sampling_loc,
|
||||
grad_attn_weight,
|
||||
im2col_step=ctx.im2col_step)
|
||||
|
||||
return grad_value, None, None, \
|
||||
grad_sampling_loc, grad_attn_weight, None
|
||||
|
||||
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import math
|
||||
import warnings
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from torch.autograd.function import Function, once_differentiable
|
||||
|
||||
from mmcv import deprecated_api_warning
|
||||
from mmcv.cnn import constant_init, xavier_init
|
||||
from mmcv.cnn.bricks.registry import ATTENTION
|
||||
from mmcv.runner import BaseModule
|
||||
|
||||
ext_module = ext_loader.load_ext(
|
||||
'_ext', ['ms_deform_attn_backward', 'ms_deform_attn_forward'])
|
||||
import functools
|
||||
import time
|
||||
from collections import defaultdict
|
||||
import torch
|
||||
from mmcv.ops import MultiScaleDeformableAttention
|
||||
@ATTENTION.register_module()
|
||||
class CustomMultiScaleDeformableAttention(MultiScaleDeformableAttention):
|
||||
"""An attention module used in Deformable-Detr.
|
||||
|
||||
`Deformable DETR: Deformable Transformers for End-to-End Object Detection.
|
||||
<https://arxiv.org/pdf/2010.04159.pdf>`_.
|
||||
|
||||
Args:
|
||||
embed_dims (int): The embedding dimension of Attention.
|
||||
Default: 256.
|
||||
num_heads (int): Parallel attention heads. Default: 64.
|
||||
num_levels (int): The number of feature map used in
|
||||
Attention. Default: 4.
|
||||
num_points (int): The number of sampling points for
|
||||
each query in each head. Default: 4.
|
||||
im2col_step (int): The step used in image_to_column.
|
||||
Default: 64.
|
||||
dropout (float): A Dropout layer on `inp_identity`.
|
||||
Default: 0.1.
|
||||
batch_first (bool): Key, Query and Value are shape of
|
||||
(batch, n, embed_dim)
|
||||
or (n, batch, embed_dim). Default to False.
|
||||
norm_cfg (dict): Config dict for normalization layer.
|
||||
Default: None.
|
||||
init_cfg (obj:`mmcv.ConfigDict`): The Config for initialization.
|
||||
Default: None.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
use_flash=False,
|
||||
use_softmax=True,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.use_flash = use_flash
|
||||
self.use_softmax = use_softmax
|
||||
|
||||
@deprecated_api_warning({'residual': 'identity'},
|
||||
cls_name='FlashMultiScaleDeformableAttention')
|
||||
# @run_time('ms_attention')
|
||||
def forward(self,
|
||||
query,
|
||||
key=None,
|
||||
value=None,
|
||||
identity=None,
|
||||
query_pos=None,
|
||||
key_padding_mask=None,
|
||||
reference_points=None,
|
||||
spatial_shapes=None,
|
||||
level_start_index=None,
|
||||
**kwargs):
|
||||
"""Forward Function of MultiScaleDeformAttention.
|
||||
|
||||
Args:
|
||||
query (torch.Tensor): Query of Transformer with shape
|
||||
(num_query, bs, embed_dims).
|
||||
key (torch.Tensor): The key tensor with shape
|
||||
`(num_key, bs, embed_dims)`.
|
||||
value (torch.Tensor): The value tensor with shape
|
||||
`(num_key, bs, embed_dims)`.
|
||||
identity (torch.Tensor): The tensor used for addition, with the
|
||||
same shape as `query`. Default None. If None,
|
||||
`query` will be used.
|
||||
query_pos (torch.Tensor): The positional encoding for `query`.
|
||||
Default: None.
|
||||
key_pos (torch.Tensor): The positional encoding for `key`. Default
|
||||
None.
|
||||
reference_points (torch.Tensor): The normalized reference
|
||||
points with shape (bs, num_query, num_levels, 2),
|
||||
all elements is range in [0, 1], top-left (0,0),
|
||||
bottom-right (1, 1), including padding area.
|
||||
or (N, Length_{query}, num_levels, 4), add
|
||||
additional two dimensions is (w, h) to
|
||||
form reference boxes.
|
||||
key_padding_mask (torch.Tensor): ByteTensor for `query`, with
|
||||
shape [bs, num_key].
|
||||
spatial_shapes (torch.Tensor): Spatial shape of features in
|
||||
different levels. With shape (num_levels, 2),
|
||||
last dimension represents (h, w).
|
||||
level_start_index (torch.Tensor): The start index of each level.
|
||||
A tensor has shape ``(num_levels, )`` and can be represented
|
||||
as [0, h_0*w_0, h_0*w_0+h_1*w_1, ...].
|
||||
|
||||
Returns:
|
||||
torch.Tensor: forwarded results with shape
|
||||
[num_query, bs, embed_dims].
|
||||
"""
|
||||
|
||||
if value is None:
|
||||
value = query
|
||||
|
||||
if identity is None:
|
||||
identity = query
|
||||
if query_pos is not None:
|
||||
query = query + query_pos
|
||||
if not self.batch_first:
|
||||
# change to (bs, num_query ,embed_dims)
|
||||
query = query.permute(1, 0, 2)
|
||||
value = value.permute(1, 0, 2)
|
||||
|
||||
bs, num_query, _ = query.shape
|
||||
bs, num_value, _ = value.shape
|
||||
assert (spatial_shapes[:, 0] * spatial_shapes[:, 1]).sum() == num_value
|
||||
|
||||
value = self.value_proj(value)
|
||||
if key_padding_mask is not None:
|
||||
value = value.masked_fill(key_padding_mask[..., None], 0.0)
|
||||
value = value.view(bs, num_value, self.num_heads, -1)
|
||||
query = query.to(value.dtype)
|
||||
sampling_offsets = self.sampling_offsets(query).view(
|
||||
bs, num_query, self.num_heads, self.num_levels, self.num_points, 2)
|
||||
attention_weights = self.attention_weights(query).view(
|
||||
bs, num_query, self.num_heads, self.num_levels * self.num_points)
|
||||
|
||||
if not self.use_flash:
|
||||
if self.use_softmax:
|
||||
attention_weights = attention_weights.softmax(-1)
|
||||
attention_weights = attention_weights.view(bs, num_query,
|
||||
self.num_heads,
|
||||
self.num_levels,
|
||||
self.num_points)
|
||||
|
||||
else:
|
||||
attention_weights = attention_weights.view(bs, num_query,
|
||||
self.num_heads,
|
||||
self.num_levels,
|
||||
self.num_points, 1)
|
||||
|
||||
if reference_points.shape[-1] == 2:
|
||||
offset_normalizer = torch.stack(
|
||||
[spatial_shapes[..., 1], spatial_shapes[..., 0]], -1)
|
||||
sampling_locations = reference_points[:, :, None, :, None, :] \
|
||||
+ sampling_offsets \
|
||||
/ offset_normalizer[None, None, None, :, None, :]
|
||||
elif reference_points.shape[-1] == 4:
|
||||
sampling_locations = reference_points[:, :, None, :, None, :2] \
|
||||
+ sampling_offsets / self.num_points \
|
||||
* reference_points[:, :, None, :, None, 2:] \
|
||||
* 0.5
|
||||
else:
|
||||
raise ValueError(
|
||||
f'Last dim of reference_points must be'
|
||||
f' 2 or 4, but get {reference_points.shape[-1]} instead.')
|
||||
sampling_locations = sampling_locations.to(sampling_offsets.dtype)
|
||||
if torch.cuda.is_available() and value.is_cuda:
|
||||
if self.use_flash:
|
||||
assert False
|
||||
else:
|
||||
MultiScaleDeformableAttnFunction = MultiScaleDeformableAttnFunction_fp32_old
|
||||
|
||||
output = MultiScaleDeformableAttnFunction.apply(
|
||||
value, spatial_shapes, level_start_index, sampling_locations,
|
||||
attention_weights, self.im2col_step)
|
||||
|
||||
else:
|
||||
output = multi_scale_deformable_attn_pytorch(
|
||||
value, spatial_shapes, sampling_locations, attention_weights)
|
||||
|
||||
output = self.output_proj(output.to(value.dtype))
|
||||
|
||||
if not self.batch_first:
|
||||
# (num_query, bs ,embed_dims)
|
||||
output = output.permute(1, 0, 2)
|
||||
|
||||
return self.dropout(output) + identity
|
||||
|
||||
13
segmentation/mmseg_custom/models/losses/__init__.py
Normal file
13
segmentation/mmseg_custom/models/losses/__init__.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from .cross_entropy_loss import (CrossEntropyLoss, binary_cross_entropy,
|
||||
cross_entropy, mask_cross_entropy)
|
||||
from .dice_loss import DiceLoss
|
||||
from .focal_loss import FocalLoss
|
||||
from .match_costs import (ClassificationCost, CrossEntropyLossCost, DiceCost,
|
||||
MaskFocalLossCost)
|
||||
|
||||
__all__ = [
|
||||
'cross_entropy', 'binary_cross_entropy', 'mask_cross_entropy',
|
||||
'CrossEntropyLoss', 'DiceLoss', 'FocalLoss', 'ClassificationCost',
|
||||
'MaskFocalLossCost', 'DiceCost', 'CrossEntropyLossCost'
|
||||
]
|
||||
291
segmentation/mmseg_custom/models/losses/cross_entropy_loss.py
Normal file
291
segmentation/mmseg_custom/models/losses/cross_entropy_loss.py
Normal file
@@ -0,0 +1,291 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import warnings
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from mmseg.models.builder import LOSSES
|
||||
from mmseg.models.losses.utils import get_class_weight, weight_reduce_loss
|
||||
|
||||
|
||||
def cross_entropy(pred,
|
||||
label,
|
||||
weight=None,
|
||||
class_weight=None,
|
||||
reduction='mean',
|
||||
avg_factor=None,
|
||||
ignore_index=-100,
|
||||
avg_non_ignore=False):
|
||||
"""cross_entropy. The wrapper function for :func:`F.cross_entropy`
|
||||
|
||||
Args:
|
||||
pred (torch.Tensor): The prediction with shape (N, 1).
|
||||
label (torch.Tensor): The learning label of the prediction.
|
||||
weight (torch.Tensor, optional): Sample-wise loss weight.
|
||||
Default: None.
|
||||
class_weight (list[float], optional): The weight for each class.
|
||||
Default: None.
|
||||
reduction (str, optional): The method used to reduce the loss.
|
||||
Options are 'none', 'mean' and 'sum'. Default: 'mean'.
|
||||
avg_factor (int, optional): Average factor that is used to average
|
||||
the loss. Default: None.
|
||||
ignore_index (int): Specifies a target value that is ignored and
|
||||
does not contribute to the input gradients. When
|
||||
``avg_non_ignore `` is ``True``, and the ``reduction`` is
|
||||
``''mean''``, the loss is averaged over non-ignored targets.
|
||||
Defaults: -100.
|
||||
avg_non_ignore (bool): The flag decides to whether the loss is
|
||||
only averaged over non-ignored targets. Default: False.
|
||||
`New in version 0.23.0.`
|
||||
"""
|
||||
|
||||
# class_weight is a manual rescaling weight given to each class.
|
||||
# If given, has to be a Tensor of size C element-wise losses
|
||||
loss = F.cross_entropy(
|
||||
pred,
|
||||
label,
|
||||
weight=class_weight,
|
||||
reduction='none',
|
||||
ignore_index=ignore_index)
|
||||
|
||||
# apply weights and do the reduction
|
||||
# average loss over non-ignored elements
|
||||
# pytorch's official cross_entropy average loss over non-ignored elements
|
||||
# refer to https://github.com/pytorch/pytorch/blob/56b43f4fec1f76953f15a627694d4bba34588969/torch/nn/functional.py#L2660 # noqa
|
||||
if (avg_factor is None) and avg_non_ignore and reduction == 'mean':
|
||||
avg_factor = label.numel() - (label == ignore_index).sum().item()
|
||||
if weight is not None:
|
||||
weight = weight.float()
|
||||
loss = weight_reduce_loss(
|
||||
loss, weight=weight, reduction=reduction, avg_factor=avg_factor)
|
||||
|
||||
return loss
|
||||
|
||||
|
||||
def _expand_onehot_labels(labels, label_weights, target_shape, ignore_index):
|
||||
"""Expand onehot labels to match the size of prediction."""
|
||||
bin_labels = labels.new_zeros(target_shape)
|
||||
valid_mask = (labels >= 0) & (labels != ignore_index)
|
||||
inds = torch.nonzero(valid_mask, as_tuple=True)
|
||||
|
||||
if inds[0].numel() > 0:
|
||||
if labels.dim() == 3:
|
||||
bin_labels[inds[0], labels[valid_mask], inds[1], inds[2]] = 1
|
||||
else:
|
||||
bin_labels[inds[0], labels[valid_mask]] = 1
|
||||
|
||||
valid_mask = valid_mask.unsqueeze(1).expand(target_shape).float()
|
||||
|
||||
if label_weights is None:
|
||||
bin_label_weights = valid_mask
|
||||
else:
|
||||
bin_label_weights = label_weights.unsqueeze(1).expand(target_shape)
|
||||
bin_label_weights = bin_label_weights * valid_mask
|
||||
|
||||
return bin_labels, bin_label_weights, valid_mask
|
||||
|
||||
|
||||
def binary_cross_entropy(pred,
|
||||
label,
|
||||
weight=None,
|
||||
reduction='mean',
|
||||
avg_factor=None,
|
||||
class_weight=None,
|
||||
ignore_index=-100,
|
||||
avg_non_ignore=False,
|
||||
**kwargs):
|
||||
"""Calculate the binary CrossEntropy loss.
|
||||
|
||||
Args:
|
||||
pred (torch.Tensor): The prediction with shape (N, 1).
|
||||
label (torch.Tensor): The learning label of the prediction.
|
||||
Note: In bce loss, label < 0 is invalid.
|
||||
weight (torch.Tensor, optional): Sample-wise loss weight.
|
||||
reduction (str, optional): The method used to reduce the loss.
|
||||
Options are "none", "mean" and "sum".
|
||||
avg_factor (int, optional): Average factor that is used to average
|
||||
the loss. Defaults to None.
|
||||
class_weight (list[float], optional): The weight for each class.
|
||||
ignore_index (int): The label index to be ignored. Default: -100.
|
||||
avg_non_ignore (bool): The flag decides to whether the loss is
|
||||
only averaged over non-ignored targets. Default: False.
|
||||
`New in version 0.23.0.`
|
||||
|
||||
Returns:
|
||||
torch.Tensor: The calculated loss
|
||||
"""
|
||||
if pred.size(1) == 1:
|
||||
# For binary class segmentation, the shape of pred is
|
||||
# [N, 1, H, W] and that of label is [N, H, W].
|
||||
assert label.max() <= 1, \
|
||||
'For pred with shape [N, 1, H, W], its label must have at ' \
|
||||
'most 2 classes'
|
||||
pred = pred.squeeze()
|
||||
if pred.dim() != label.dim():
|
||||
assert (pred.dim() == 2 and label.dim() == 1) or (
|
||||
pred.dim() == 4 and label.dim() == 3), \
|
||||
'Only pred shape [N, C], label shape [N] or pred shape [N, C, ' \
|
||||
'H, W], label shape [N, H, W] are supported'
|
||||
# `weight` returned from `_expand_onehot_labels`
|
||||
# has been treated for valid (non-ignore) pixels
|
||||
label, weight, valid_mask = _expand_onehot_labels(
|
||||
label, weight, pred.shape, ignore_index)
|
||||
else:
|
||||
# should mask out the ignored elements
|
||||
valid_mask = ((label >= 0) & (label != ignore_index)).float()
|
||||
if weight is not None:
|
||||
weight = weight * valid_mask
|
||||
else:
|
||||
weight = valid_mask
|
||||
# average loss over non-ignored and valid elements
|
||||
if reduction == 'mean' and avg_factor is None and avg_non_ignore:
|
||||
avg_factor = valid_mask.sum().item()
|
||||
|
||||
loss = F.binary_cross_entropy_with_logits(
|
||||
pred, label.float(), pos_weight=class_weight, reduction='none')
|
||||
# do the reduction for the weighted loss
|
||||
loss = weight_reduce_loss(
|
||||
loss, weight, reduction=reduction, avg_factor=avg_factor)
|
||||
|
||||
return loss
|
||||
|
||||
|
||||
def mask_cross_entropy(pred,
|
||||
target,
|
||||
label,
|
||||
reduction='mean',
|
||||
avg_factor=None,
|
||||
class_weight=None,
|
||||
ignore_index=None,
|
||||
**kwargs):
|
||||
"""Calculate the CrossEntropy loss for masks.
|
||||
|
||||
Args:
|
||||
pred (torch.Tensor): The prediction with shape (N, C), C is the number
|
||||
of classes.
|
||||
target (torch.Tensor): The learning label of the prediction.
|
||||
label (torch.Tensor): ``label`` indicates the class label of the mask'
|
||||
corresponding object. This will be used to select the mask in the
|
||||
of the class which the object belongs to when the mask prediction
|
||||
if not class-agnostic.
|
||||
reduction (str, optional): The method used to reduce the loss.
|
||||
Options are "none", "mean" and "sum".
|
||||
avg_factor (int, optional): Average factor that is used to average
|
||||
the loss. Defaults to None.
|
||||
class_weight (list[float], optional): The weight for each class.
|
||||
ignore_index (None): Placeholder, to be consistent with other loss.
|
||||
Default: None.
|
||||
|
||||
Returns:
|
||||
torch.Tensor: The calculated loss
|
||||
"""
|
||||
assert ignore_index is None, 'BCE loss does not support ignore_index'
|
||||
# TODO: handle these two reserved arguments
|
||||
assert reduction == 'mean' and avg_factor is None
|
||||
num_rois = pred.size()[0]
|
||||
inds = torch.arange(0, num_rois, dtype=torch.long, device=pred.device)
|
||||
pred_slice = pred[inds, label].squeeze(1)
|
||||
return F.binary_cross_entropy_with_logits(
|
||||
pred_slice, target, weight=class_weight, reduction='mean')[None]
|
||||
|
||||
|
||||
@LOSSES.register_module(force=True)
|
||||
class CrossEntropyLoss(nn.Module):
|
||||
"""CrossEntropyLoss.
|
||||
|
||||
Args:
|
||||
use_sigmoid (bool, optional): Whether the prediction uses sigmoid
|
||||
of softmax. Defaults to False.
|
||||
use_mask (bool, optional): Whether to use mask cross entropy loss.
|
||||
Defaults to False.
|
||||
reduction (str, optional): . Defaults to 'mean'.
|
||||
Options are "none", "mean" and "sum".
|
||||
class_weight (list[float] | str, optional): Weight of each class. If in
|
||||
str format, read them from a file. Defaults to None.
|
||||
loss_weight (float, optional): Weight of the loss. Defaults to 1.0.
|
||||
loss_name (str, optional): Name of the loss item. If you want this loss
|
||||
item to be included into the backward graph, `loss_` must be the
|
||||
prefix of the name. Defaults to 'loss_ce'.
|
||||
avg_non_ignore (bool): The flag decides to whether the loss is
|
||||
only averaged over non-ignored targets. Default: False.
|
||||
`New in version 0.23.0.`
|
||||
"""
|
||||
def __init__(self,
|
||||
use_sigmoid=False,
|
||||
use_mask=False,
|
||||
reduction='mean',
|
||||
class_weight=None,
|
||||
loss_weight=1.0,
|
||||
loss_name='loss_ce',
|
||||
avg_non_ignore=False):
|
||||
super(CrossEntropyLoss, self).__init__()
|
||||
assert (use_sigmoid is False) or (use_mask is False)
|
||||
self.use_sigmoid = use_sigmoid
|
||||
self.use_mask = use_mask
|
||||
self.reduction = reduction
|
||||
self.loss_weight = loss_weight
|
||||
self.class_weight = get_class_weight(class_weight)
|
||||
self.avg_non_ignore = avg_non_ignore
|
||||
if not self.avg_non_ignore and self.reduction == 'mean':
|
||||
warnings.warn(
|
||||
'Default ``avg_non_ignore`` is False, if you would like to '
|
||||
'ignore the certain label and average loss over non-ignore '
|
||||
'labels, which is the same with PyTorch official '
|
||||
'cross_entropy, set ``avg_non_ignore=True``.')
|
||||
|
||||
if self.use_sigmoid:
|
||||
self.cls_criterion = binary_cross_entropy
|
||||
elif self.use_mask:
|
||||
self.cls_criterion = mask_cross_entropy
|
||||
else:
|
||||
self.cls_criterion = cross_entropy
|
||||
self._loss_name = loss_name
|
||||
|
||||
def extra_repr(self):
|
||||
"""Extra repr."""
|
||||
s = f'avg_non_ignore={self.avg_non_ignore}'
|
||||
return s
|
||||
|
||||
def forward(self,
|
||||
cls_score,
|
||||
label,
|
||||
weight=None,
|
||||
avg_factor=None,
|
||||
reduction_override=None,
|
||||
ignore_index=-100,
|
||||
**kwargs):
|
||||
"""Forward function."""
|
||||
assert reduction_override in (None, 'none', 'mean', 'sum')
|
||||
reduction = (reduction_override
|
||||
if reduction_override else self.reduction)
|
||||
if self.class_weight is not None:
|
||||
class_weight = cls_score.new_tensor(self.class_weight)
|
||||
else:
|
||||
class_weight = None
|
||||
# Note: for BCE loss, label < 0 is invalid.
|
||||
loss_cls = self.loss_weight * self.cls_criterion(
|
||||
cls_score,
|
||||
label,
|
||||
weight,
|
||||
class_weight=class_weight,
|
||||
reduction=reduction,
|
||||
avg_factor=avg_factor,
|
||||
avg_non_ignore=self.avg_non_ignore,
|
||||
ignore_index=ignore_index,
|
||||
**kwargs)
|
||||
return loss_cls
|
||||
|
||||
@property
|
||||
def loss_name(self):
|
||||
"""Loss Name.
|
||||
|
||||
This function must be implemented and will return the name of this
|
||||
loss function. This name will be used to combine different loss items
|
||||
by simple sum operation. In addition, if you want this loss item to be
|
||||
included into the backward graph, `loss_` must be the prefix of the
|
||||
name.
|
||||
|
||||
Returns:
|
||||
str: The name of this loss item.
|
||||
"""
|
||||
return self._loss_name
|
||||
179
segmentation/mmseg_custom/models/losses/dice_loss.py
Normal file
179
segmentation/mmseg_custom/models/losses/dice_loss.py
Normal file
@@ -0,0 +1,179 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from mmseg.models.builder import LOSSES
|
||||
from mmseg.models.losses.utils import weight_reduce_loss
|
||||
|
||||
|
||||
def dice_loss(pred,
|
||||
target,
|
||||
weight=None,
|
||||
eps=1e-3,
|
||||
reduction='mean',
|
||||
avg_factor=None):
|
||||
"""Calculate dice loss, which is proposed in
|
||||
`V-Net: Fully Convolutional Neural Networks for Volumetric
|
||||
Medical Image Segmentation <https://arxiv.org/abs/1606.04797>`_.
|
||||
|
||||
Args:
|
||||
pred (torch.Tensor): The prediction, has a shape (n, *)
|
||||
target (torch.Tensor): The learning label of the prediction,
|
||||
shape (n, *), same shape of pred.
|
||||
weight (torch.Tensor, optional): The weight of loss for each
|
||||
prediction, has a shape (n,). Defaults to None.
|
||||
eps (float): Avoid dividing by zero. Default: 1e-3.
|
||||
reduction (str, optional): The method used to reduce the loss into
|
||||
a scalar. Defaults to 'mean'.
|
||||
Options are "none", "mean" and "sum".
|
||||
avg_factor (int, optional): Average factor that is used to average
|
||||
the loss. Defaults to None.
|
||||
"""
|
||||
|
||||
input = pred.flatten(1)
|
||||
target = target.flatten(1).float()
|
||||
|
||||
a = torch.sum(input * target, 1)
|
||||
b = torch.sum(input * input, 1) + eps
|
||||
c = torch.sum(target * target, 1) + eps
|
||||
d = (2 * a) / (b + c)
|
||||
loss = 1 - d
|
||||
if weight is not None:
|
||||
assert weight.ndim == loss.ndim
|
||||
assert len(weight) == len(pred)
|
||||
loss = weight_reduce_loss(loss, weight, reduction, avg_factor)
|
||||
return loss
|
||||
|
||||
|
||||
def naive_dice_loss(pred,
|
||||
target,
|
||||
weight=None,
|
||||
eps=1e-3,
|
||||
reduction='mean',
|
||||
avg_factor=None):
|
||||
"""Calculate naive dice loss, the coefficient in the denominator is the
|
||||
first power instead of the second power.
|
||||
|
||||
Args:
|
||||
pred (torch.Tensor): The prediction, has a shape (n, *)
|
||||
target (torch.Tensor): The learning label of the prediction,
|
||||
shape (n, *), same shape of pred.
|
||||
weight (torch.Tensor, optional): The weight of loss for each
|
||||
prediction, has a shape (n,). Defaults to None.
|
||||
eps (float): Avoid dividing by zero. Default: 1e-3.
|
||||
reduction (str, optional): The method used to reduce the loss into
|
||||
a scalar. Defaults to 'mean'.
|
||||
Options are "none", "mean" and "sum".
|
||||
avg_factor (int, optional): Average factor that is used to average
|
||||
the loss. Defaults to None.
|
||||
"""
|
||||
input = pred.flatten(1)
|
||||
target = target.flatten(1).float()
|
||||
|
||||
a = torch.sum(input * target, 1)
|
||||
b = torch.sum(input, 1)
|
||||
c = torch.sum(target, 1)
|
||||
d = (2 * a + eps) / (b + c + eps)
|
||||
loss = 1 - d
|
||||
if weight is not None:
|
||||
assert weight.ndim == loss.ndim
|
||||
assert len(weight) == len(pred)
|
||||
loss = weight_reduce_loss(loss, weight, reduction, avg_factor)
|
||||
return loss
|
||||
|
||||
|
||||
@LOSSES.register_module(force=True)
|
||||
class DiceLoss(nn.Module):
|
||||
def __init__(self,
|
||||
use_sigmoid=True,
|
||||
activate=True,
|
||||
reduction='mean',
|
||||
naive_dice=False,
|
||||
loss_weight=1.0,
|
||||
eps=1e-3):
|
||||
"""Dice Loss, there are two forms of dice loss is supported:
|
||||
|
||||
- the one proposed in `V-Net: Fully Convolutional Neural
|
||||
Networks for Volumetric Medical Image Segmentation
|
||||
<https://arxiv.org/abs/1606.04797>`_.
|
||||
- the dice loss in which the power of the number in the
|
||||
denominator is the first power instead of the second
|
||||
power.
|
||||
|
||||
Args:
|
||||
use_sigmoid (bool, optional): Whether to the prediction is
|
||||
used for sigmoid or softmax. Defaults to True.
|
||||
activate (bool): Whether to activate the predictions inside,
|
||||
this will disable the inside sigmoid operation.
|
||||
Defaults to True.
|
||||
reduction (str, optional): The method used
|
||||
to reduce the loss. Options are "none",
|
||||
"mean" and "sum". Defaults to 'mean'.
|
||||
naive_dice (bool, optional): If false, use the dice
|
||||
loss defined in the V-Net paper, otherwise, use the
|
||||
naive dice loss in which the power of the number in the
|
||||
denominator is the first power instead of the second
|
||||
power.Defaults to False.
|
||||
loss_weight (float, optional): Weight of loss. Defaults to 1.0.
|
||||
eps (float): Avoid dividing by zero. Defaults to 1e-3.
|
||||
"""
|
||||
|
||||
super(DiceLoss, self).__init__()
|
||||
self.use_sigmoid = use_sigmoid
|
||||
self.reduction = reduction
|
||||
self.naive_dice = naive_dice
|
||||
self.loss_weight = loss_weight
|
||||
self.eps = eps
|
||||
self.activate = activate
|
||||
|
||||
def forward(self,
|
||||
pred,
|
||||
target,
|
||||
weight=None,
|
||||
reduction_override=None,
|
||||
avg_factor=None):
|
||||
"""Forward function.
|
||||
|
||||
Args:
|
||||
pred (torch.Tensor): The prediction, has a shape (n, *).
|
||||
target (torch.Tensor): The label of the prediction,
|
||||
shape (n, *), same shape of pred.
|
||||
weight (torch.Tensor, optional): The weight of loss for each
|
||||
prediction, has a shape (n,). Defaults to None.
|
||||
avg_factor (int, optional): Average factor that is used to average
|
||||
the loss. Defaults to None.
|
||||
reduction_override (str, optional): The reduction method used to
|
||||
override the original reduction method of the loss.
|
||||
Options are "none", "mean" and "sum".
|
||||
|
||||
Returns:
|
||||
torch.Tensor: The calculated loss
|
||||
"""
|
||||
|
||||
assert reduction_override in (None, 'none', 'mean', 'sum')
|
||||
reduction = (reduction_override
|
||||
if reduction_override else self.reduction)
|
||||
|
||||
if self.activate:
|
||||
if self.use_sigmoid:
|
||||
pred = pred.sigmoid()
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
if self.naive_dice:
|
||||
loss = self.loss_weight * naive_dice_loss(
|
||||
pred,
|
||||
target,
|
||||
weight,
|
||||
eps=self.eps,
|
||||
reduction=reduction,
|
||||
avg_factor=avg_factor)
|
||||
else:
|
||||
loss = self.loss_weight * dice_loss(
|
||||
pred,
|
||||
target,
|
||||
weight,
|
||||
eps=self.eps,
|
||||
reduction=reduction,
|
||||
avg_factor=avg_factor)
|
||||
|
||||
return loss
|
||||
180
segmentation/mmseg_custom/models/losses/focal_loss.py
Normal file
180
segmentation/mmseg_custom/models/losses/focal_loss.py
Normal file
@@ -0,0 +1,180 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from mmcv.ops import sigmoid_focal_loss as _sigmoid_focal_loss
|
||||
from mmseg.models.builder import LOSSES
|
||||
from mmseg.models.losses.utils import weight_reduce_loss
|
||||
|
||||
|
||||
# This method is only for debugging
|
||||
def py_sigmoid_focal_loss(pred,
|
||||
target,
|
||||
weight=None,
|
||||
gamma=2.0,
|
||||
alpha=0.25,
|
||||
reduction='mean',
|
||||
avg_factor=None):
|
||||
"""PyTorch version of `Focal Loss <https://arxiv.org/abs/1708.02002>`_.
|
||||
|
||||
Args:
|
||||
pred (torch.Tensor): The prediction with shape (N, C), C is the
|
||||
number of classes
|
||||
target (torch.Tensor): The learning label of the prediction.
|
||||
weight (torch.Tensor, optional): Sample-wise loss weight.
|
||||
gamma (float, optional): The gamma for calculating the modulating
|
||||
factor. Defaults to 2.0.
|
||||
alpha (float, optional): A balanced form for Focal Loss.
|
||||
Defaults to 0.25.
|
||||
reduction (str, optional): The method used to reduce the loss into
|
||||
a scalar. Defaults to 'mean'.
|
||||
avg_factor (int, optional): Average factor that is used to average
|
||||
the loss. Defaults to None.
|
||||
"""
|
||||
pred_sigmoid = pred.sigmoid()
|
||||
target = target.type_as(pred)
|
||||
pt = (1 - pred_sigmoid) * target + pred_sigmoid * (1 - target)
|
||||
focal_weight = (alpha * target + (1 - alpha) *
|
||||
(1 - target)) * pt.pow(gamma)
|
||||
loss = F.binary_cross_entropy_with_logits(
|
||||
pred, target, reduction='none') * focal_weight
|
||||
if weight is not None:
|
||||
if weight.shape != loss.shape:
|
||||
if weight.size(0) == loss.size(0):
|
||||
# For most cases, weight is of shape (num_priors, ),
|
||||
# which means it does not have the second axis num_class
|
||||
weight = weight.view(-1, 1)
|
||||
else:
|
||||
# Sometimes, weight per anchor per class is also needed. e.g.
|
||||
# in FSAF. But it may be flattened of shape
|
||||
# (num_priors x num_class, ), while loss is still of shape
|
||||
# (num_priors, num_class).
|
||||
assert weight.numel() == loss.numel()
|
||||
weight = weight.view(loss.size(0), -1)
|
||||
assert weight.ndim == loss.ndim
|
||||
loss = weight_reduce_loss(loss, weight, reduction, avg_factor)
|
||||
return loss
|
||||
|
||||
|
||||
def sigmoid_focal_loss(pred,
|
||||
target,
|
||||
weight=None,
|
||||
gamma=2.0,
|
||||
alpha=0.25,
|
||||
reduction='mean',
|
||||
avg_factor=None):
|
||||
r"""A warpper of cuda version `Focal Loss
|
||||
<https://arxiv.org/abs/1708.02002>`_.
|
||||
|
||||
Args:
|
||||
pred (torch.Tensor): The prediction with shape (N, C), C is the number
|
||||
of classes.
|
||||
target (torch.Tensor): The learning label of the prediction.
|
||||
weight (torch.Tensor, optional): Sample-wise loss weight.
|
||||
gamma (float, optional): The gamma for calculating the modulating
|
||||
factor. Defaults to 2.0.
|
||||
alpha (float, optional): A balanced form for Focal Loss.
|
||||
Defaults to 0.25.
|
||||
reduction (str, optional): The method used to reduce the loss into
|
||||
a scalar. Defaults to 'mean'. Options are "none", "mean" and "sum".
|
||||
avg_factor (int, optional): Average factor that is used to average
|
||||
the loss. Defaults to None.
|
||||
"""
|
||||
# Function.apply does not accept keyword arguments, so the decorator
|
||||
# "weighted_loss" is not applicable
|
||||
loss = _sigmoid_focal_loss(pred.contiguous(), target.contiguous(), gamma,
|
||||
alpha, None, 'none')
|
||||
if weight is not None:
|
||||
if weight.shape != loss.shape:
|
||||
if weight.size(0) == loss.size(0):
|
||||
# For most cases, weight is of shape (num_priors, ),
|
||||
# which means it does not have the second axis num_class
|
||||
weight = weight.view(-1, 1)
|
||||
else:
|
||||
# Sometimes, weight per anchor per class is also needed. e.g.
|
||||
# in FSAF. But it may be flattened of shape
|
||||
# (num_priors x num_class, ), while loss is still of shape
|
||||
# (num_priors, num_class).
|
||||
assert weight.numel() == loss.numel()
|
||||
weight = weight.view(loss.size(0), -1)
|
||||
assert weight.ndim == loss.ndim
|
||||
loss = weight_reduce_loss(loss, weight, reduction, avg_factor)
|
||||
return loss
|
||||
|
||||
|
||||
@LOSSES.register_module(force=True)
|
||||
class FocalLoss(nn.Module):
|
||||
def __init__(self,
|
||||
use_sigmoid=True,
|
||||
gamma=2.0,
|
||||
alpha=0.25,
|
||||
reduction='mean',
|
||||
loss_weight=1.0):
|
||||
"""`Focal Loss <https://arxiv.org/abs/1708.02002>`_
|
||||
|
||||
Args:
|
||||
use_sigmoid (bool, optional): Whether to the prediction is
|
||||
used for sigmoid or softmax. Defaults to True.
|
||||
gamma (float, optional): The gamma for calculating the modulating
|
||||
factor. Defaults to 2.0.
|
||||
alpha (float, optional): A balanced form for Focal Loss.
|
||||
Defaults to 0.25.
|
||||
reduction (str, optional): The method used to reduce the loss into
|
||||
a scalar. Defaults to 'mean'. Options are "none", "mean" and
|
||||
"sum".
|
||||
loss_weight (float, optional): Weight of loss. Defaults to 1.0.
|
||||
"""
|
||||
super(FocalLoss, self).__init__()
|
||||
assert use_sigmoid is True, 'Only sigmoid focal loss supported now.'
|
||||
self.use_sigmoid = use_sigmoid
|
||||
self.gamma = gamma
|
||||
self.alpha = alpha
|
||||
self.reduction = reduction
|
||||
self.loss_weight = loss_weight
|
||||
|
||||
def forward(self,
|
||||
pred,
|
||||
target,
|
||||
weight=None,
|
||||
avg_factor=None,
|
||||
reduction_override=None):
|
||||
"""Forward function.
|
||||
|
||||
Args:
|
||||
pred (torch.Tensor): The prediction.
|
||||
target (torch.Tensor): The learning label of the prediction.
|
||||
weight (torch.Tensor, optional): The weight of loss for each
|
||||
prediction. Defaults to None.
|
||||
avg_factor (int, optional): Average factor that is used to average
|
||||
the loss. Defaults to None.
|
||||
reduction_override (str, optional): The reduction method used to
|
||||
override the original reduction method of the loss.
|
||||
Options are "none", "mean" and "sum".
|
||||
|
||||
Returns:
|
||||
torch.Tensor: The calculated loss
|
||||
"""
|
||||
assert reduction_override in (None, 'none', 'mean', 'sum')
|
||||
reduction = (
|
||||
reduction_override if reduction_override else self.reduction)
|
||||
if self.use_sigmoid:
|
||||
if torch.cuda.is_available() and pred.is_cuda:
|
||||
calculate_loss_func = sigmoid_focal_loss
|
||||
else:
|
||||
num_classes = pred.size(1)
|
||||
target = F.one_hot(target, num_classes=num_classes + 1)
|
||||
target = target[:, :num_classes]
|
||||
calculate_loss_func = py_sigmoid_focal_loss
|
||||
|
||||
loss_cls = self.loss_weight * calculate_loss_func(
|
||||
pred,
|
||||
target,
|
||||
weight,
|
||||
gamma=self.gamma,
|
||||
alpha=self.alpha,
|
||||
reduction=reduction,
|
||||
avg_factor=avg_factor)
|
||||
|
||||
else:
|
||||
raise NotImplementedError
|
||||
return loss_cls
|
||||
233
segmentation/mmseg_custom/models/losses/match_costs.py
Normal file
233
segmentation/mmseg_custom/models/losses/match_costs.py
Normal file
@@ -0,0 +1,233 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from ..builder import MATCH_COST
|
||||
|
||||
|
||||
@MATCH_COST.register_module()
|
||||
class FocalLossCost:
|
||||
"""FocalLossCost.
|
||||
|
||||
Args:
|
||||
weight (int | float, optional): loss_weight
|
||||
alpha (int | float, optional): focal_loss alpha
|
||||
gamma (int | float, optional): focal_loss gamma
|
||||
eps (float, optional): default 1e-12
|
||||
|
||||
Examples:
|
||||
>>> from mmdet.core.bbox.match_costs.match_cost import FocalLossCost
|
||||
>>> import torch
|
||||
>>> self = FocalLossCost()
|
||||
>>> cls_pred = torch.rand(4, 3)
|
||||
>>> gt_labels = torch.tensor([0, 1, 2])
|
||||
>>> factor = torch.tensor([10, 8, 10, 8])
|
||||
>>> self(cls_pred, gt_labels)
|
||||
tensor([[-0.3236, -0.3364, -0.2699],
|
||||
[-0.3439, -0.3209, -0.4807],
|
||||
[-0.4099, -0.3795, -0.2929],
|
||||
[-0.1950, -0.1207, -0.2626]])
|
||||
"""
|
||||
def __init__(self, weight=1., alpha=0.25, gamma=2, eps=1e-12):
|
||||
self.weight = weight
|
||||
self.alpha = alpha
|
||||
self.gamma = gamma
|
||||
self.eps = eps
|
||||
|
||||
def __call__(self, cls_pred, gt_labels):
|
||||
"""
|
||||
Args:
|
||||
cls_pred (Tensor): Predicted classification logits, shape
|
||||
[num_query, num_class].
|
||||
gt_labels (Tensor): Label of `gt_bboxes`, shape (num_gt,).
|
||||
|
||||
Returns:
|
||||
torch.Tensor: cls_cost value with weight
|
||||
"""
|
||||
cls_pred = cls_pred.sigmoid()
|
||||
neg_cost = -(1 - cls_pred + self.eps).log() * (
|
||||
1 - self.alpha) * cls_pred.pow(self.gamma)
|
||||
pos_cost = -(cls_pred + self.eps).log() * self.alpha * (
|
||||
1 - cls_pred).pow(self.gamma)
|
||||
cls_cost = pos_cost[:, gt_labels] - neg_cost[:, gt_labels]
|
||||
return cls_cost * self.weight
|
||||
|
||||
|
||||
@MATCH_COST.register_module()
|
||||
class MaskFocalLossCost(FocalLossCost):
|
||||
"""Cost of mask assignments based on focal losses.
|
||||
|
||||
Args:
|
||||
weight (int | float, optional): loss_weight.
|
||||
alpha (int | float, optional): focal_loss alpha.
|
||||
gamma (int | float, optional): focal_loss gamma.
|
||||
eps (float, optional): default 1e-12.
|
||||
"""
|
||||
def __call__(self, cls_pred, gt_labels):
|
||||
"""
|
||||
Args:
|
||||
cls_pred (Tensor): Predicted classfication logits
|
||||
in shape (N1, H, W), dtype=torch.float32.
|
||||
gt_labels (Tensor): Ground truth in shape (N2, H, W),
|
||||
dtype=torch.long.
|
||||
|
||||
Returns:
|
||||
Tensor: classification cost matrix in shape (N1, N2).
|
||||
"""
|
||||
cls_pred = cls_pred.reshape((cls_pred.shape[0], -1))
|
||||
gt_labels = gt_labels.reshape((gt_labels.shape[0], -1)).float()
|
||||
hw = cls_pred.shape[1]
|
||||
cls_pred = cls_pred.sigmoid()
|
||||
neg_cost = -(1 - cls_pred + self.eps).log() * (
|
||||
1 - self.alpha) * cls_pred.pow(self.gamma)
|
||||
pos_cost = -(cls_pred + self.eps).log() * self.alpha * (
|
||||
1 - cls_pred).pow(self.gamma)
|
||||
|
||||
cls_cost = torch.einsum('nc,mc->nm', pos_cost, gt_labels) + \
|
||||
torch.einsum('nc,mc->nm', neg_cost, (1 - gt_labels))
|
||||
return cls_cost / hw * self.weight
|
||||
|
||||
|
||||
@MATCH_COST.register_module()
|
||||
class ClassificationCost:
|
||||
"""ClsSoftmaxCost.Borrow from
|
||||
mmdet.core.bbox.match_costs.match_cost.ClassificationCost.
|
||||
|
||||
Args:
|
||||
weight (int | float, optional): loss_weight
|
||||
|
||||
Examples:
|
||||
>>> import torch
|
||||
>>> self = ClassificationCost()
|
||||
>>> cls_pred = torch.rand(4, 3)
|
||||
>>> gt_labels = torch.tensor([0, 1, 2])
|
||||
>>> factor = torch.tensor([10, 8, 10, 8])
|
||||
>>> self(cls_pred, gt_labels)
|
||||
tensor([[-0.3430, -0.3525, -0.3045],
|
||||
[-0.3077, -0.2931, -0.3992],
|
||||
[-0.3664, -0.3455, -0.2881],
|
||||
[-0.3343, -0.2701, -0.3956]])
|
||||
"""
|
||||
def __init__(self, weight=1.):
|
||||
self.weight = weight
|
||||
|
||||
def __call__(self, cls_pred, gt_labels):
|
||||
"""
|
||||
Args:
|
||||
cls_pred (Tensor): Predicted classification logits, shape
|
||||
[num_query, num_class].
|
||||
gt_labels (Tensor): Label of `gt_bboxes`, shape (num_gt,).
|
||||
|
||||
Returns:
|
||||
torch.Tensor: cls_cost value with weight
|
||||
"""
|
||||
# Following the official DETR repo, contrary to the loss that
|
||||
# NLL is used, we approximate it in 1 - cls_score[gt_label].
|
||||
# The 1 is a constant that doesn't change the matching,
|
||||
# so it can be omitted.
|
||||
cls_score = cls_pred.softmax(-1)
|
||||
cls_cost = -cls_score[:, gt_labels]
|
||||
return cls_cost * self.weight
|
||||
|
||||
|
||||
@MATCH_COST.register_module()
|
||||
class DiceCost:
|
||||
"""Cost of mask assignments based on dice losses.
|
||||
|
||||
Args:
|
||||
weight (int | float, optional): loss_weight. Defaults to 1.
|
||||
pred_act (bool, optional): Whether to apply sigmoid to mask_pred.
|
||||
Defaults to False.
|
||||
eps (float, optional): default 1e-12.
|
||||
"""
|
||||
def __init__(self, weight=1., pred_act=False, eps=1e-3):
|
||||
self.weight = weight
|
||||
self.pred_act = pred_act
|
||||
self.eps = eps
|
||||
|
||||
def binary_mask_dice_loss(self, mask_preds, gt_masks):
|
||||
"""
|
||||
Args:
|
||||
mask_preds (Tensor): Mask prediction in shape (N1, H, W).
|
||||
gt_masks (Tensor): Ground truth in shape (N2, H, W)
|
||||
store 0 or 1, 0 for negative class and 1 for
|
||||
positive class.
|
||||
|
||||
Returns:
|
||||
Tensor: Dice cost matrix in shape (N1, N2).
|
||||
"""
|
||||
mask_preds = mask_preds.reshape((mask_preds.shape[0], -1))
|
||||
gt_masks = gt_masks.reshape((gt_masks.shape[0], -1)).float()
|
||||
numerator = 2 * torch.einsum('nc,mc->nm', mask_preds, gt_masks)
|
||||
denominator = mask_preds.sum(-1)[:, None] + gt_masks.sum(-1)[None, :]
|
||||
loss = 1 - (numerator + self.eps) / (denominator + self.eps)
|
||||
return loss
|
||||
|
||||
def __call__(self, mask_preds, gt_masks):
|
||||
"""
|
||||
Args:
|
||||
mask_preds (Tensor): Mask prediction logits in shape (N1, H, W).
|
||||
gt_masks (Tensor): Ground truth in shape (N2, H, W).
|
||||
|
||||
Returns:
|
||||
Tensor: Dice cost matrix in shape (N1, N2).
|
||||
"""
|
||||
if self.pred_act:
|
||||
mask_preds = mask_preds.sigmoid()
|
||||
dice_cost = self.binary_mask_dice_loss(mask_preds, gt_masks)
|
||||
return dice_cost * self.weight
|
||||
|
||||
|
||||
@MATCH_COST.register_module()
|
||||
class CrossEntropyLossCost:
|
||||
"""CrossEntropyLossCost.
|
||||
|
||||
Args:
|
||||
weight (int | float, optional): loss weight. Defaults to 1.
|
||||
use_sigmoid (bool, optional): Whether the prediction uses sigmoid
|
||||
of softmax. Defaults to True.
|
||||
"""
|
||||
def __init__(self, weight=1., use_sigmoid=True):
|
||||
assert use_sigmoid, 'use_sigmoid = False is not supported yet.'
|
||||
self.weight = weight
|
||||
self.use_sigmoid = use_sigmoid
|
||||
|
||||
def _binary_cross_entropy(self, cls_pred, gt_labels):
|
||||
"""
|
||||
Args:
|
||||
cls_pred (Tensor): The prediction with shape (num_query, 1, *) or
|
||||
(num_query, *).
|
||||
gt_labels (Tensor): The learning label of prediction with
|
||||
shape (num_gt, *).
|
||||
Returns:
|
||||
Tensor: Cross entropy cost matrix in shape (num_query, num_gt).
|
||||
"""
|
||||
cls_pred = cls_pred.flatten(1).float()
|
||||
gt_labels = gt_labels.flatten(1).float()
|
||||
n = cls_pred.shape[1]
|
||||
pos = F.binary_cross_entropy_with_logits(
|
||||
cls_pred, torch.ones_like(cls_pred), reduction='none')
|
||||
neg = F.binary_cross_entropy_with_logits(
|
||||
cls_pred, torch.zeros_like(cls_pred), reduction='none')
|
||||
cls_cost = torch.einsum('nc,mc->nm', pos, gt_labels) + \
|
||||
torch.einsum('nc,mc->nm', neg, 1 - gt_labels)
|
||||
cls_cost = cls_cost / n
|
||||
|
||||
return cls_cost
|
||||
|
||||
def __call__(self, cls_pred, gt_labels):
|
||||
"""
|
||||
Args:
|
||||
cls_pred (Tensor): Predicted classification logits.
|
||||
gt_labels (Tensor): Labels.
|
||||
Returns:
|
||||
Tensor: Cross entropy cost matrix with weight in
|
||||
shape (num_query, num_gt).
|
||||
"""
|
||||
if self.use_sigmoid:
|
||||
cls_cost = self._binary_cross_entropy(cls_pred, gt_labels)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
return cls_cost * self.weight
|
||||
179
segmentation/mmseg_custom/models/losses/match_loss.py
Normal file
179
segmentation/mmseg_custom/models/losses/match_loss.py
Normal file
@@ -0,0 +1,179 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from ..builder import MATCH_COST
|
||||
|
||||
|
||||
@MATCH_COST.register_module()
|
||||
class FocalLossCost:
|
||||
"""FocalLossCost.
|
||||
|
||||
Args:
|
||||
weight (int | float, optional): loss_weight
|
||||
alpha (int | float, optional): focal_loss alpha
|
||||
gamma (int | float, optional): focal_loss gamma
|
||||
eps (float, optional): default 1e-12
|
||||
|
||||
Examples:
|
||||
>>> from mmdet.core.bbox.match_costs.match_cost import FocalLossCost
|
||||
>>> import torch
|
||||
>>> self = FocalLossCost()
|
||||
>>> cls_pred = torch.rand(4, 3)
|
||||
>>> gt_labels = torch.tensor([0, 1, 2])
|
||||
>>> factor = torch.tensor([10, 8, 10, 8])
|
||||
>>> self(cls_pred, gt_labels)
|
||||
tensor([[-0.3236, -0.3364, -0.2699],
|
||||
[-0.3439, -0.3209, -0.4807],
|
||||
[-0.4099, -0.3795, -0.2929],
|
||||
[-0.1950, -0.1207, -0.2626]])
|
||||
"""
|
||||
def __init__(self, weight=1., alpha=0.25, gamma=2, eps=1e-12):
|
||||
self.weight = weight
|
||||
self.alpha = alpha
|
||||
self.gamma = gamma
|
||||
self.eps = eps
|
||||
|
||||
def __call__(self, cls_pred, gt_labels):
|
||||
"""
|
||||
Args:
|
||||
cls_pred (Tensor): Predicted classification logits, shape
|
||||
[num_query, num_class].
|
||||
gt_labels (Tensor): Label of `gt_bboxes`, shape (num_gt,).
|
||||
|
||||
Returns:
|
||||
torch.Tensor: cls_cost value with weight
|
||||
"""
|
||||
cls_pred = cls_pred.sigmoid()
|
||||
neg_cost = -(1 - cls_pred + self.eps).log() * (
|
||||
1 - self.alpha) * cls_pred.pow(self.gamma)
|
||||
pos_cost = -(cls_pred + self.eps).log() * self.alpha * (
|
||||
1 - cls_pred).pow(self.gamma)
|
||||
cls_cost = pos_cost[:, gt_labels] - neg_cost[:, gt_labels]
|
||||
return cls_cost * self.weight
|
||||
|
||||
|
||||
@MATCH_COST.register_module()
|
||||
class MaskFocalLossCost(FocalLossCost):
|
||||
"""Cost of mask assignments based on focal losses.
|
||||
|
||||
Args:
|
||||
weight (int | float, optional): loss_weight.
|
||||
alpha (int | float, optional): focal_loss alpha.
|
||||
gamma (int | float, optional): focal_loss gamma.
|
||||
eps (float, optional): default 1e-12.
|
||||
"""
|
||||
def __call__(self, cls_pred, gt_labels):
|
||||
"""
|
||||
Args:
|
||||
cls_pred (Tensor): Predicted classfication logits
|
||||
in shape (N1, H, W), dtype=torch.float32.
|
||||
gt_labels (Tensor): Ground truth in shape (N2, H, W),
|
||||
dtype=torch.long.
|
||||
|
||||
Returns:
|
||||
Tensor: classification cost matrix in shape (N1, N2).
|
||||
"""
|
||||
cls_pred = cls_pred.reshape((cls_pred.shape[0], -1))
|
||||
gt_labels = gt_labels.reshape((gt_labels.shape[0], -1)).float()
|
||||
hw = cls_pred.shape[1]
|
||||
cls_pred = cls_pred.sigmoid()
|
||||
neg_cost = -(1 - cls_pred + self.eps).log() * (
|
||||
1 - self.alpha) * cls_pred.pow(self.gamma)
|
||||
pos_cost = -(cls_pred + self.eps).log() * self.alpha * (
|
||||
1 - cls_pred).pow(self.gamma)
|
||||
|
||||
cls_cost = torch.einsum('nc,mc->nm', pos_cost, gt_labels) + \
|
||||
torch.einsum('nc,mc->nm', neg_cost, (1 - gt_labels))
|
||||
return cls_cost / hw * self.weight
|
||||
|
||||
|
||||
@MATCH_COST.register_module()
|
||||
class ClassificationCost:
|
||||
"""ClsSoftmaxCost.Borrow from
|
||||
mmdet.core.bbox.match_costs.match_cost.ClassificationCost.
|
||||
|
||||
Args:
|
||||
weight (int | float, optional): loss_weight
|
||||
|
||||
Examples:
|
||||
>>> import torch
|
||||
>>> self = ClassificationCost()
|
||||
>>> cls_pred = torch.rand(4, 3)
|
||||
>>> gt_labels = torch.tensor([0, 1, 2])
|
||||
>>> factor = torch.tensor([10, 8, 10, 8])
|
||||
>>> self(cls_pred, gt_labels)
|
||||
tensor([[-0.3430, -0.3525, -0.3045],
|
||||
[-0.3077, -0.2931, -0.3992],
|
||||
[-0.3664, -0.3455, -0.2881],
|
||||
[-0.3343, -0.2701, -0.3956]])
|
||||
"""
|
||||
def __init__(self, weight=1.):
|
||||
self.weight = weight
|
||||
|
||||
def __call__(self, cls_pred, gt_labels):
|
||||
"""
|
||||
Args:
|
||||
cls_pred (Tensor): Predicted classification logits, shape
|
||||
[num_query, num_class].
|
||||
gt_labels (Tensor): Label of `gt_bboxes`, shape (num_gt,).
|
||||
|
||||
Returns:
|
||||
torch.Tensor: cls_cost value with weight
|
||||
"""
|
||||
# Following the official DETR repo, contrary to the loss that
|
||||
# NLL is used, we approximate it in 1 - cls_score[gt_label].
|
||||
# The 1 is a constant that doesn't change the matching,
|
||||
# so it can be omitted.
|
||||
cls_score = cls_pred.softmax(-1)
|
||||
cls_cost = -cls_score[:, gt_labels]
|
||||
return cls_cost * self.weight
|
||||
|
||||
|
||||
@MATCH_COST.register_module()
|
||||
class DiceCost:
|
||||
"""Cost of mask assignments based on dice losses.
|
||||
|
||||
Args:
|
||||
weight (int | float, optional): loss_weight. Defaults to 1.
|
||||
pred_act (bool, optional): Whether to apply sigmoid to mask_pred.
|
||||
Defaults to False.
|
||||
eps (float, optional): default 1e-12.
|
||||
"""
|
||||
def __init__(self, weight=1., pred_act=False, eps=1e-3):
|
||||
self.weight = weight
|
||||
self.pred_act = pred_act
|
||||
self.eps = eps
|
||||
|
||||
def binary_mask_dice_loss(self, mask_preds, gt_masks):
|
||||
"""
|
||||
Args:
|
||||
mask_preds (Tensor): Mask prediction in shape (N1, H, W).
|
||||
gt_masks (Tensor): Ground truth in shape (N2, H, W)
|
||||
store 0 or 1, 0 for negative class and 1 for
|
||||
positive class.
|
||||
|
||||
Returns:
|
||||
Tensor: Dice cost matrix in shape (N1, N2).
|
||||
"""
|
||||
mask_preds = mask_preds.reshape((mask_preds.shape[0], -1))
|
||||
gt_masks = gt_masks.reshape((gt_masks.shape[0], -1)).float()
|
||||
numerator = 2 * torch.einsum('nc,mc->nm', mask_preds, gt_masks)
|
||||
denominator = mask_preds.sum(-1)[:, None] + gt_masks.sum(-1)[None, :]
|
||||
loss = 1 - (numerator + self.eps) / (denominator + self.eps)
|
||||
return loss
|
||||
|
||||
def __call__(self, mask_preds, gt_masks):
|
||||
"""
|
||||
Args:
|
||||
mask_preds (Tensor): Mask prediction logits in shape (N1, H, W).
|
||||
gt_masks (Tensor): Ground truth in shape (N2, H, W).
|
||||
|
||||
Returns:
|
||||
Tensor: Dice cost matrix in shape (N1, N2).
|
||||
"""
|
||||
if self.pred_act:
|
||||
mask_preds = mask_preds.sigmoid()
|
||||
dice_cost = self.binary_mask_dice_loss(mask_preds, gt_masks)
|
||||
return dice_cost * self.weight
|
||||
8
segmentation/mmseg_custom/models/plugins/__init__.py
Normal file
8
segmentation/mmseg_custom/models/plugins/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
||||
# Copyright (c) Shanghai AI Lab. All rights reserved.
|
||||
from .msdeformattn_pixel_decoder import MSDeformAttnPixelDecoder
|
||||
from .pixel_decoder import PixelDecoder, TransformerEncoderPixelDecoder
|
||||
|
||||
__all__ = [
|
||||
'PixelDecoder', 'TransformerEncoderPixelDecoder',
|
||||
'MSDeformAttnPixelDecoder'
|
||||
]
|
||||
@@ -0,0 +1,268 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from mmcv.cnn import (PLUGIN_LAYERS, Conv2d, ConvModule, caffe2_xavier_init,
|
||||
normal_init, xavier_init)
|
||||
from mmcv.cnn.bricks.transformer import (build_positional_encoding,
|
||||
build_transformer_layer_sequence)
|
||||
from mmcv.runner import BaseModule, ModuleList
|
||||
|
||||
from ...core.anchor import MlvlPointGenerator
|
||||
from ..utils.transformer import MultiScaleDeformableAttention
|
||||
|
||||
|
||||
@PLUGIN_LAYERS.register_module()
|
||||
class MSDeformAttnPixelDecoder(BaseModule):
|
||||
"""Pixel decoder with multi-scale deformable attention.
|
||||
|
||||
Args:
|
||||
in_channels (list[int] | tuple[int]): Number of channels in the
|
||||
input feature maps.
|
||||
strides (list[int] | tuple[int]): Output strides of feature from
|
||||
backbone.
|
||||
feat_channels (int): Number of channels for feature.
|
||||
out_channels (int): Number of channels for output.
|
||||
num_outs (int): Number of output scales.
|
||||
norm_cfg (:obj:`mmcv.ConfigDict` | dict): Config for normalization.
|
||||
Defaults to dict(type='GN', num_groups=32).
|
||||
act_cfg (:obj:`mmcv.ConfigDict` | dict): Config for activation.
|
||||
Defaults to dict(type='ReLU').
|
||||
encoder (:obj:`mmcv.ConfigDict` | dict): Config for transformer
|
||||
encoder. Defaults to `DetrTransformerEncoder`.
|
||||
positional_encoding (:obj:`mmcv.ConfigDict` | dict): Config for
|
||||
transformer encoder position encoding. Defaults to
|
||||
dict(type='SinePositionalEncoding', num_feats=128,
|
||||
normalize=True).
|
||||
init_cfg (:obj:`mmcv.ConfigDict` | dict): Initialization config dict.
|
||||
"""
|
||||
def __init__(self,
|
||||
in_channels=[256, 512, 1024, 2048],
|
||||
strides=[4, 8, 16, 32],
|
||||
feat_channels=256,
|
||||
out_channels=256,
|
||||
num_outs=3,
|
||||
norm_cfg=dict(type='GN', num_groups=32),
|
||||
act_cfg=dict(type='ReLU'),
|
||||
encoder=dict(
|
||||
type='DetrTransformerEncoder',
|
||||
num_layers=6,
|
||||
transformerlayers=dict(
|
||||
type='BaseTransformerLayer',
|
||||
attn_cfgs=dict(
|
||||
type='MultiScaleDeformableAttention',
|
||||
embed_dims=256,
|
||||
num_heads=8,
|
||||
num_levels=3,
|
||||
num_points=4,
|
||||
im2col_step=64,
|
||||
dropout=0.0,
|
||||
batch_first=False,
|
||||
norm_cfg=None,
|
||||
init_cfg=None),
|
||||
feedforward_channels=1024,
|
||||
ffn_dropout=0.0,
|
||||
operation_order=('self_attn', 'norm', 'ffn', 'norm')),
|
||||
init_cfg=None),
|
||||
positional_encoding=dict(
|
||||
type='SinePositionalEncoding',
|
||||
num_feats=128,
|
||||
normalize=True),
|
||||
init_cfg=None):
|
||||
super().__init__(init_cfg=init_cfg)
|
||||
self.strides = strides
|
||||
self.num_input_levels = len(in_channels)
|
||||
self.num_encoder_levels = \
|
||||
encoder.transformerlayers.attn_cfgs.num_levels
|
||||
assert self.num_encoder_levels >= 1, \
|
||||
'num_levels in attn_cfgs must be at least one'
|
||||
input_conv_list = []
|
||||
# from top to down (low to high resolution)
|
||||
for i in range(self.num_input_levels - 1,
|
||||
self.num_input_levels - self.num_encoder_levels - 1,
|
||||
-1):
|
||||
input_conv = ConvModule(
|
||||
in_channels[i],
|
||||
feat_channels,
|
||||
kernel_size=1,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=None,
|
||||
bias=True)
|
||||
input_conv_list.append(input_conv)
|
||||
self.input_convs = ModuleList(input_conv_list)
|
||||
|
||||
self.encoder = build_transformer_layer_sequence(encoder)
|
||||
self.postional_encoding = build_positional_encoding(
|
||||
positional_encoding)
|
||||
# high resolution to low resolution
|
||||
self.level_encoding = nn.Embedding(self.num_encoder_levels,
|
||||
feat_channels)
|
||||
|
||||
# fpn-like structure
|
||||
self.lateral_convs = ModuleList()
|
||||
self.output_convs = ModuleList()
|
||||
self.use_bias = norm_cfg is None
|
||||
# from top to down (low to high resolution)
|
||||
# fpn for the rest features that didn't pass in encoder
|
||||
for i in range(self.num_input_levels - self.num_encoder_levels - 1, -1,
|
||||
-1):
|
||||
lateral_conv = ConvModule(
|
||||
in_channels[i],
|
||||
feat_channels,
|
||||
kernel_size=1,
|
||||
bias=self.use_bias,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=None)
|
||||
output_conv = ConvModule(
|
||||
feat_channels,
|
||||
feat_channels,
|
||||
kernel_size=3,
|
||||
stride=1,
|
||||
padding=1,
|
||||
bias=self.use_bias,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=act_cfg)
|
||||
self.lateral_convs.append(lateral_conv)
|
||||
self.output_convs.append(output_conv)
|
||||
|
||||
self.mask_feature = Conv2d(
|
||||
feat_channels, out_channels, kernel_size=1, stride=1, padding=0)
|
||||
|
||||
self.num_outs = num_outs
|
||||
self.point_generator = MlvlPointGenerator(strides)
|
||||
|
||||
def init_weights(self):
|
||||
"""Initialize weights."""
|
||||
for i in range(0, self.num_encoder_levels):
|
||||
xavier_init(
|
||||
self.input_convs[i].conv,
|
||||
gain=1,
|
||||
bias=0,
|
||||
distribution='uniform')
|
||||
|
||||
for i in range(0, self.num_input_levels - self.num_encoder_levels):
|
||||
caffe2_xavier_init(self.lateral_convs[i].conv, bias=0)
|
||||
caffe2_xavier_init(self.output_convs[i].conv, bias=0)
|
||||
|
||||
caffe2_xavier_init(self.mask_feature, bias=0)
|
||||
|
||||
normal_init(self.level_encoding, mean=0, std=1)
|
||||
for p in self.encoder.parameters():
|
||||
if p.dim() > 1:
|
||||
nn.init.xavier_normal_(p)
|
||||
|
||||
# init_weights defined in MultiScaleDeformableAttention
|
||||
for layer in self.encoder.layers:
|
||||
for attn in layer.attentions:
|
||||
if isinstance(attn, MultiScaleDeformableAttention):
|
||||
attn.init_weights()
|
||||
|
||||
def forward(self, feats):
|
||||
"""
|
||||
Args:
|
||||
feats (list[Tensor]): Feature maps of each level. Each has
|
||||
shape of (batch_size, c, h, w).
|
||||
|
||||
Returns:
|
||||
tuple: A tuple containing the following:
|
||||
|
||||
- mask_feature (Tensor): shape (batch_size, c, h, w).
|
||||
- multi_scale_features (list[Tensor]): Multi scale \
|
||||
features, each in shape (batch_size, c, h, w).
|
||||
"""
|
||||
# generate padding mask for each level, for each image
|
||||
batch_size = feats[0].shape[0]
|
||||
encoder_input_list = []
|
||||
padding_mask_list = []
|
||||
level_positional_encoding_list = []
|
||||
spatial_shapes = []
|
||||
reference_points_list = []
|
||||
for i in range(self.num_encoder_levels):
|
||||
level_idx = self.num_input_levels - i - 1
|
||||
feat = feats[level_idx]
|
||||
feat_projected = self.input_convs[i](feat)
|
||||
h, w = feat.shape[-2:]
|
||||
|
||||
# no padding
|
||||
padding_mask_resized = feat.new_zeros(
|
||||
(batch_size, ) + feat.shape[-2:], dtype=torch.bool)
|
||||
pos_embed = self.postional_encoding(padding_mask_resized)
|
||||
level_embed = self.level_encoding.weight[i]
|
||||
level_pos_embed = level_embed.view(1, -1, 1, 1) + pos_embed
|
||||
# (h_i * w_i, 2)
|
||||
reference_points = self.point_generator.single_level_grid_priors(
|
||||
feat.shape[-2:], level_idx, device=feat.device)
|
||||
# normalize
|
||||
factor = feat.new_tensor([[w, h]]) * self.strides[level_idx]
|
||||
reference_points = reference_points / factor
|
||||
|
||||
# shape (batch_size, c, h_i, w_i) -> (h_i * w_i, batch_size, c)
|
||||
feat_projected = feat_projected.flatten(2).permute(2, 0, 1)
|
||||
level_pos_embed = level_pos_embed.flatten(2).permute(2, 0, 1)
|
||||
padding_mask_resized = padding_mask_resized.flatten(1)
|
||||
|
||||
encoder_input_list.append(feat_projected)
|
||||
padding_mask_list.append(padding_mask_resized)
|
||||
level_positional_encoding_list.append(level_pos_embed)
|
||||
spatial_shapes.append(feat.shape[-2:])
|
||||
reference_points_list.append(reference_points)
|
||||
# shape (batch_size, total_num_query),
|
||||
# total_num_query=sum([., h_i * w_i,.])
|
||||
padding_masks = torch.cat(padding_mask_list, dim=1)
|
||||
# shape (total_num_query, batch_size, c)
|
||||
encoder_inputs = torch.cat(encoder_input_list, dim=0)
|
||||
level_positional_encodings = torch.cat(
|
||||
level_positional_encoding_list, dim=0)
|
||||
device = encoder_inputs.device
|
||||
# shape (num_encoder_levels, 2), from low
|
||||
# resolution to high resolution
|
||||
spatial_shapes = torch.as_tensor(
|
||||
spatial_shapes, dtype=torch.long, device=device)
|
||||
# shape (0, h_0*w_0, h_0*w_0+h_1*w_1, ...)
|
||||
level_start_index = torch.cat((spatial_shapes.new_zeros(
|
||||
(1, )), spatial_shapes.prod(1).cumsum(0)[:-1]))
|
||||
reference_points = torch.cat(reference_points_list, dim=0)
|
||||
reference_points = reference_points[None, :, None].repeat(
|
||||
batch_size, 1, self.num_encoder_levels, 1)
|
||||
valid_radios = reference_points.new_ones(
|
||||
(batch_size, self.num_encoder_levels, 2))
|
||||
# shape (num_total_query, batch_size, c)
|
||||
memory = self.encoder(
|
||||
query=encoder_inputs,
|
||||
key=None,
|
||||
value=None,
|
||||
query_pos=level_positional_encodings,
|
||||
key_pos=None,
|
||||
attn_masks=None,
|
||||
key_padding_mask=None,
|
||||
query_key_padding_mask=padding_masks,
|
||||
spatial_shapes=spatial_shapes,
|
||||
reference_points=reference_points,
|
||||
level_start_index=level_start_index,
|
||||
valid_radios=valid_radios)
|
||||
# (num_total_query, batch_size, c) -> (batch_size, c, num_total_query)
|
||||
memory = memory.permute(1, 2, 0)
|
||||
|
||||
# from low resolution to high resolution
|
||||
num_query_per_level = [e[0] * e[1] for e in spatial_shapes]
|
||||
outs = torch.split(memory, num_query_per_level, dim=-1)
|
||||
outs = [
|
||||
x.reshape(batch_size, -1, spatial_shapes[i][0],
|
||||
spatial_shapes[i][1]) for i, x in enumerate(outs)
|
||||
]
|
||||
|
||||
for i in range(self.num_input_levels - self.num_encoder_levels - 1, -1,
|
||||
-1):
|
||||
x = feats[i]
|
||||
cur_feat = self.lateral_convs[i](x)
|
||||
y = cur_feat + F.interpolate(
|
||||
outs[-1],
|
||||
size=cur_feat.shape[-2:],
|
||||
mode='bilinear',
|
||||
align_corners=False)
|
||||
y = self.output_convs[i](y)
|
||||
outs.append(y)
|
||||
multi_scale_features = outs[:self.num_outs]
|
||||
|
||||
mask_feature = self.mask_feature(outs[-1])
|
||||
return mask_feature, multi_scale_features
|
||||
237
segmentation/mmseg_custom/models/plugins/pixel_decoder.py
Normal file
237
segmentation/mmseg_custom/models/plugins/pixel_decoder.py
Normal file
@@ -0,0 +1,237 @@
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from mmcv.cnn import PLUGIN_LAYERS, Conv2d, ConvModule, kaiming_init
|
||||
from mmcv.cnn.bricks.transformer import (build_positional_encoding,
|
||||
build_transformer_layer_sequence)
|
||||
from mmcv.runner import BaseModule, ModuleList
|
||||
|
||||
|
||||
@PLUGIN_LAYERS.register_module()
|
||||
class PixelDecoder(BaseModule):
|
||||
"""Pixel decoder with a structure like fpn.
|
||||
|
||||
Args:
|
||||
in_channels (list[int] | tuple[int]): Number of channels in the
|
||||
input feature maps.
|
||||
feat_channels (int): Number channels for feature.
|
||||
out_channels (int): Number channels for output.
|
||||
norm_cfg (obj:`mmcv.ConfigDict`|dict): Config for normalization.
|
||||
Defaults to dict(type='GN', num_groups=32).
|
||||
act_cfg (obj:`mmcv.ConfigDict`|dict): Config for activation.
|
||||
Defaults to dict(type='ReLU').
|
||||
encoder (obj:`mmcv.ConfigDict`|dict): Config for transorformer
|
||||
encoder.Defaults to None.
|
||||
positional_encoding (obj:`mmcv.ConfigDict`|dict): Config for
|
||||
transformer encoder position encoding. Defaults to
|
||||
dict(type='SinePositionalEncoding', num_feats=128,
|
||||
normalize=True).
|
||||
init_cfg (obj:`mmcv.ConfigDict`|dict): Initialization config dict.
|
||||
Default: None
|
||||
"""
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
feat_channels,
|
||||
out_channels,
|
||||
norm_cfg=dict(type='GN', num_groups=32),
|
||||
act_cfg=dict(type='ReLU'),
|
||||
init_cfg=None):
|
||||
super().__init__(init_cfg=init_cfg)
|
||||
self.in_channels = in_channels
|
||||
self.num_inputs = len(in_channels)
|
||||
self.lateral_convs = ModuleList()
|
||||
self.output_convs = ModuleList()
|
||||
self.use_bias = norm_cfg is None
|
||||
for i in range(0, self.num_inputs - 1):
|
||||
l_conv = ConvModule(
|
||||
in_channels[i],
|
||||
feat_channels,
|
||||
kernel_size=1,
|
||||
bias=self.use_bias,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=None)
|
||||
o_conv = ConvModule(
|
||||
feat_channels,
|
||||
feat_channels,
|
||||
kernel_size=3,
|
||||
stride=1,
|
||||
padding=1,
|
||||
bias=self.use_bias,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=act_cfg)
|
||||
self.lateral_convs.append(l_conv)
|
||||
self.output_convs.append(o_conv)
|
||||
|
||||
self.last_feat_conv = ConvModule(
|
||||
in_channels[-1],
|
||||
feat_channels,
|
||||
kernel_size=3,
|
||||
padding=1,
|
||||
stride=1,
|
||||
bias=self.use_bias,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=act_cfg)
|
||||
self.mask_feature = Conv2d(
|
||||
feat_channels, out_channels, kernel_size=3, stride=1, padding=1)
|
||||
|
||||
def init_weights(self):
|
||||
"""Initialize weights."""
|
||||
for i in range(0, self.num_inputs - 2):
|
||||
kaiming_init(self.lateral_convs[i].conv, a=1)
|
||||
kaiming_init(self.output_convs[i].conv, a=1)
|
||||
|
||||
kaiming_init(self.mask_feature, a=1)
|
||||
kaiming_init(self.last_feat_conv, a=1)
|
||||
|
||||
def forward(self, feats, img_metas):
|
||||
"""
|
||||
Args:
|
||||
feats (list[Tensor]): Feature maps of each level. Each has
|
||||
shape of [bs, c, h, w].
|
||||
img_metas (list[dict]): List of image information. Pass in
|
||||
for creating more accurate padding mask. #! not used here.
|
||||
|
||||
Returns:
|
||||
tuple: a tuple containing the following:
|
||||
|
||||
- mask_feature (Tensor): Shape [bs, c, h, w].
|
||||
- memory (Tensor): Output of last stage of backbone.
|
||||
Shape [bs, c, h, w].
|
||||
"""
|
||||
y = self.last_feat_conv(feats[-1])
|
||||
for i in range(self.num_inputs - 2, -1, -1):
|
||||
x = feats[i]
|
||||
cur_fpn = self.lateral_convs[i](x)
|
||||
y = cur_fpn + \
|
||||
F.interpolate(y, size=cur_fpn.shape[-2:], mode='nearest')
|
||||
y = self.output_convs[i](y)
|
||||
|
||||
mask_feature = self.mask_feature(y)
|
||||
memory = feats[-1]
|
||||
return mask_feature, memory
|
||||
|
||||
|
||||
@PLUGIN_LAYERS.register_module()
|
||||
class TransformerEncoderPixelDecoder(PixelDecoder):
|
||||
"""Pixel decoder with transormer encoder inside.
|
||||
|
||||
Args:
|
||||
in_channels (list[int] | tuple[int]): Number of channels in the
|
||||
input feature maps.
|
||||
feat_channels (int): Number channels for feature.
|
||||
out_channels (int): Number channels for output.
|
||||
norm_cfg (obj:`mmcv.ConfigDict`|dict): Config for normalization.
|
||||
Defaults to dict(type='GN', num_groups=32).
|
||||
act_cfg (obj:`mmcv.ConfigDict`|dict): Config for activation.
|
||||
Defaults to dict(type='ReLU').
|
||||
encoder (obj:`mmcv.ConfigDict`|dict): Config for transorformer
|
||||
encoder.Defaults to None.
|
||||
positional_encoding (obj:`mmcv.ConfigDict`|dict): Config for
|
||||
transformer encoder position encoding. Defaults to
|
||||
dict(type='SinePositionalEncoding', num_feats=128,
|
||||
normalize=True).
|
||||
init_cfg (obj:`mmcv.ConfigDict`|dict): Initialization config dict.
|
||||
Default: None
|
||||
"""
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
feat_channels,
|
||||
out_channels,
|
||||
norm_cfg=dict(type='GN', num_groups=32),
|
||||
act_cfg=dict(type='ReLU'),
|
||||
encoder=None,
|
||||
positional_encoding=dict(
|
||||
type='SinePositionalEncoding',
|
||||
num_feats=128,
|
||||
normalize=True),
|
||||
init_cfg=None):
|
||||
super(TransformerEncoderPixelDecoder, self).__init__(
|
||||
in_channels,
|
||||
feat_channels,
|
||||
out_channels,
|
||||
norm_cfg,
|
||||
act_cfg,
|
||||
init_cfg=init_cfg)
|
||||
self.last_feat_conv = None
|
||||
|
||||
self.encoder = build_transformer_layer_sequence(encoder)
|
||||
self.encoder_embed_dims = self.encoder.embed_dims
|
||||
assert self.encoder_embed_dims == feat_channels, 'embed_dims({}) of ' \
|
||||
'tranformer encoder must equal to feat_channels({})'.format(
|
||||
feat_channels, self.encoder_embed_dims)
|
||||
self.positional_encoding = build_positional_encoding(
|
||||
positional_encoding)
|
||||
self.encoder_in_proj = Conv2d(
|
||||
in_channels[-1], feat_channels, kernel_size=1)
|
||||
self.encoder_out_proj = ConvModule(
|
||||
feat_channels,
|
||||
feat_channels,
|
||||
kernel_size=3,
|
||||
stride=1,
|
||||
padding=1,
|
||||
bias=self.use_bias,
|
||||
norm_cfg=norm_cfg,
|
||||
act_cfg=act_cfg)
|
||||
|
||||
def init_weights(self):
|
||||
"""Initialize weights."""
|
||||
for i in range(0, self.num_inputs - 2):
|
||||
kaiming_init(self.lateral_convs[i].conv, a=1)
|
||||
kaiming_init(self.output_convs[i].conv, a=1)
|
||||
|
||||
kaiming_init(self.mask_feature, a=1)
|
||||
kaiming_init(self.encoder_in_proj, a=1)
|
||||
kaiming_init(self.encoder_out_proj.conv, a=1)
|
||||
|
||||
def forward(self, feats, img_metas):
|
||||
"""
|
||||
Args:
|
||||
feats (list[Tensor]): Feature maps of each level. Each has
|
||||
shape of [bs, c, h, w].
|
||||
img_metas (list[dict]): List of image information. Pass in
|
||||
for creating more accurate padding mask.
|
||||
|
||||
Returns:
|
||||
tuple: a tuple containing the following:
|
||||
|
||||
- mask_feature (Tensor): shape [bs, c, h, w].
|
||||
- memory (Tensor): shape [bs, c, h, w].
|
||||
"""
|
||||
feat_last = feats[-1]
|
||||
bs, c, h, w = feat_last.shape
|
||||
input_img_h, input_img_w = img_metas[0]['pad_shape'][:-1]
|
||||
# input_img_h, input_img_w = img_metas[0]['batch_input_shape']
|
||||
padding_mask = feat_last.new_ones((bs, input_img_h, input_img_w),
|
||||
dtype=torch.float32)
|
||||
for i in range(bs):
|
||||
img_h, img_w, _ = img_metas[i]['img_shape']
|
||||
padding_mask[i, :img_h, :img_w] = 0
|
||||
padding_mask = F.interpolate(
|
||||
padding_mask.unsqueeze(1),
|
||||
size=feat_last.shape[-2:],
|
||||
mode='nearest').to(torch.bool).squeeze(1)
|
||||
|
||||
pos_embed = self.positional_encoding(padding_mask)
|
||||
feat_last = self.encoder_in_proj(feat_last)
|
||||
# [bs, c, h, w] -> [nq, bs, dim]
|
||||
feat_last = feat_last.flatten(2).permute(2, 0, 1)
|
||||
pos_embed = pos_embed.flatten(2).permute(2, 0, 1)
|
||||
padding_mask = padding_mask.flatten(1) # [bs, h, w] -> [bs, h*w]
|
||||
memory = self.encoder(
|
||||
query=feat_last,
|
||||
key=None,
|
||||
value=None,
|
||||
query_pos=pos_embed,
|
||||
query_key_padding_mask=padding_mask)
|
||||
# [nq, bs, em] -> [bs, c, h, w]
|
||||
memory = memory.permute(1, 2, 0).view(bs, self.encoder_embed_dims, h,
|
||||
w)
|
||||
y = self.encoder_out_proj(memory)
|
||||
for i in range(self.num_inputs - 2, -1, -1):
|
||||
x = feats[i]
|
||||
cur_fpn = self.lateral_convs[i](x)
|
||||
y = cur_fpn + \
|
||||
F.interpolate(y, size=cur_fpn.shape[-2:], mode='nearest')
|
||||
y = self.output_convs[i](y)
|
||||
|
||||
mask_feature = self.mask_feature(y)
|
||||
return mask_feature, memory
|
||||
5
segmentation/mmseg_custom/models/segmentors/__init__.py
Normal file
5
segmentation/mmseg_custom/models/segmentors/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from .encoder_decoder_mask2former import EncoderDecoderMask2Former
|
||||
from .encoder_decoder_mask2former_aug import EncoderDecoderMask2FormerAug
|
||||
|
||||
__all__ = ['EncoderDecoderMask2Former', 'EncoderDecoderMask2FormerAug']
|
||||
@@ -0,0 +1,285 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from mmseg.core import add_prefix
|
||||
from mmseg.models import builder
|
||||
from mmseg.models.builder import SEGMENTORS
|
||||
from mmseg.models.segmentors.base import BaseSegmentor
|
||||
from mmseg.ops import resize
|
||||
|
||||
|
||||
@SEGMENTORS.register_module()
|
||||
class EncoderDecoderMask2Former(BaseSegmentor):
|
||||
"""Encoder Decoder segmentors.
|
||||
|
||||
EncoderDecoder typically consists of backbone, decode_head, auxiliary_head.
|
||||
Note that auxiliary_head is only used for deep supervision during training,
|
||||
which could be dumped during inference.
|
||||
"""
|
||||
def __init__(self,
|
||||
backbone,
|
||||
decode_head,
|
||||
neck=None,
|
||||
auxiliary_head=None,
|
||||
train_cfg=None,
|
||||
test_cfg=None,
|
||||
pretrained=None,
|
||||
init_cfg=None):
|
||||
super(EncoderDecoderMask2Former, self).__init__(init_cfg)
|
||||
if pretrained is not None:
|
||||
assert backbone.get('pretrained') is None, \
|
||||
'both backbone and segmentor set pretrained weight'
|
||||
backbone.pretrained = pretrained
|
||||
self.backbone = builder.build_backbone(backbone)
|
||||
if neck is not None:
|
||||
self.neck = builder.build_neck(neck)
|
||||
decode_head.update(train_cfg=train_cfg)
|
||||
decode_head.update(test_cfg=test_cfg)
|
||||
self._init_decode_head(decode_head)
|
||||
self._init_auxiliary_head(auxiliary_head)
|
||||
|
||||
self.train_cfg = train_cfg
|
||||
self.test_cfg = test_cfg
|
||||
|
||||
assert self.with_decode_head
|
||||
|
||||
def _init_decode_head(self, decode_head):
|
||||
"""Initialize ``decode_head``"""
|
||||
self.decode_head = builder.build_head(decode_head)
|
||||
self.align_corners = self.decode_head.align_corners
|
||||
self.num_classes = self.decode_head.num_classes
|
||||
|
||||
def _init_auxiliary_head(self, auxiliary_head):
|
||||
"""Initialize ``auxiliary_head``"""
|
||||
if auxiliary_head is not None:
|
||||
if isinstance(auxiliary_head, list):
|
||||
self.auxiliary_head = nn.ModuleList()
|
||||
for head_cfg in auxiliary_head:
|
||||
self.auxiliary_head.append(builder.build_head(head_cfg))
|
||||
else:
|
||||
self.auxiliary_head = builder.build_head(auxiliary_head)
|
||||
|
||||
def extract_feat(self, img):
|
||||
"""Extract features from images."""
|
||||
x = self.backbone(img)
|
||||
if self.with_neck:
|
||||
x = self.neck(x)
|
||||
return x
|
||||
|
||||
def encode_decode(self, img, img_metas):
|
||||
"""Encode images with backbone and decode into a semantic segmentation
|
||||
map of the same size as input."""
|
||||
x = self.extract_feat(img)
|
||||
out = self._decode_head_forward_test(x, img_metas)
|
||||
out = resize(
|
||||
input=out,
|
||||
size=img.shape[2:],
|
||||
mode='bilinear',
|
||||
align_corners=self.align_corners)
|
||||
return out
|
||||
|
||||
def _decode_head_forward_train(self, x, img_metas, gt_semantic_seg,
|
||||
**kwargs):
|
||||
"""Run forward function and calculate loss for decode head in
|
||||
training."""
|
||||
losses = dict()
|
||||
loss_decode = self.decode_head.forward_train(x, img_metas,
|
||||
gt_semantic_seg, **kwargs)
|
||||
|
||||
losses.update(add_prefix(loss_decode, 'decode'))
|
||||
return losses
|
||||
|
||||
def _decode_head_forward_test(self, x, img_metas):
|
||||
"""Run forward function and calculate loss for decode head in
|
||||
inference."""
|
||||
seg_logits = self.decode_head.forward_test(x, img_metas, self.test_cfg)
|
||||
return seg_logits
|
||||
|
||||
def _auxiliary_head_forward_train(self, x, img_metas, gt_semantic_seg):
|
||||
"""Run forward function and calculate loss for auxiliary head in
|
||||
training."""
|
||||
losses = dict()
|
||||
if isinstance(self.auxiliary_head, nn.ModuleList):
|
||||
for idx, aux_head in enumerate(self.auxiliary_head):
|
||||
loss_aux = aux_head.forward_train(x, img_metas,
|
||||
gt_semantic_seg,
|
||||
self.train_cfg)
|
||||
losses.update(add_prefix(loss_aux, f'aux_{idx}'))
|
||||
else:
|
||||
loss_aux = self.auxiliary_head.forward_train(
|
||||
x, img_metas, gt_semantic_seg, self.train_cfg)
|
||||
losses.update(add_prefix(loss_aux, 'aux'))
|
||||
|
||||
return losses
|
||||
|
||||
def forward_dummy(self, img):
|
||||
"""Dummy forward function."""
|
||||
seg_logit = self.encode_decode(img, None)
|
||||
|
||||
return seg_logit
|
||||
|
||||
def forward_train(self, img, img_metas, gt_semantic_seg, **kwargs):
|
||||
"""Forward function for training.
|
||||
|
||||
Args:
|
||||
img (Tensor): Input images.
|
||||
img_metas (list[dict]): List of image info dict where each dict
|
||||
has: 'img_shape', 'scale_factor', 'flip', and may also contain
|
||||
'filename', 'ori_shape', 'pad_shape', and 'img_norm_cfg'.
|
||||
For details on the values of these keys see
|
||||
`mmseg/datasets/pipelines/formatting.py:Collect`.
|
||||
gt_semantic_seg (Tensor): Semantic segmentation masks
|
||||
used if the architecture supports semantic segmentation task.
|
||||
|
||||
Returns:
|
||||
dict[str, Tensor]: a dictionary of loss components
|
||||
"""
|
||||
|
||||
x = self.extract_feat(img)
|
||||
|
||||
losses = dict()
|
||||
|
||||
loss_decode = self._decode_head_forward_train(x, img_metas,
|
||||
gt_semantic_seg,
|
||||
**kwargs)
|
||||
losses.update(loss_decode)
|
||||
|
||||
if self.with_auxiliary_head:
|
||||
loss_aux = self._auxiliary_head_forward_train(
|
||||
x, img_metas, gt_semantic_seg)
|
||||
losses.update(loss_aux)
|
||||
|
||||
return losses
|
||||
|
||||
# TODO refactor
|
||||
def slide_inference(self, img, img_meta, rescale):
|
||||
"""Inference by sliding-window with overlap.
|
||||
|
||||
If h_crop > h_img or w_crop > w_img, the small patch will be used to
|
||||
decode without padding.
|
||||
"""
|
||||
|
||||
h_stride, w_stride = self.test_cfg.stride
|
||||
h_crop, w_crop = self.test_cfg.crop_size
|
||||
batch_size, _, h_img, w_img = img.size()
|
||||
num_classes = self.num_classes
|
||||
h_grids = max(h_img - h_crop + h_stride - 1, 0) // h_stride + 1
|
||||
w_grids = max(w_img - w_crop + w_stride - 1, 0) // w_stride + 1
|
||||
preds = img.new_zeros((batch_size, num_classes, h_img, w_img))
|
||||
count_mat = img.new_zeros((batch_size, 1, h_img, w_img))
|
||||
for h_idx in range(h_grids):
|
||||
for w_idx in range(w_grids):
|
||||
y1 = h_idx * h_stride
|
||||
x1 = w_idx * w_stride
|
||||
y2 = min(y1 + h_crop, h_img)
|
||||
x2 = min(x1 + w_crop, w_img)
|
||||
y1 = max(y2 - h_crop, 0)
|
||||
x1 = max(x2 - w_crop, 0)
|
||||
crop_img = img[:, :, y1:y2, x1:x2]
|
||||
crop_seg_logit = self.encode_decode(crop_img, img_meta)
|
||||
preds += F.pad(crop_seg_logit,
|
||||
(int(x1), int(preds.shape[3] - x2), int(y1),
|
||||
int(preds.shape[2] - y2)))
|
||||
|
||||
count_mat[:, :, y1:y2, x1:x2] += 1
|
||||
assert (count_mat == 0).sum() == 0
|
||||
if torch.onnx.is_in_onnx_export():
|
||||
# cast count_mat to constant while exporting to ONNX
|
||||
count_mat = torch.from_numpy(
|
||||
count_mat.cpu().detach().numpy()).to(device=img.device)
|
||||
preds = preds / count_mat
|
||||
if rescale:
|
||||
preds = resize(
|
||||
preds,
|
||||
size=img_meta[0]['ori_shape'][:2],
|
||||
mode='bilinear',
|
||||
align_corners=self.align_corners,
|
||||
warning=False)
|
||||
return preds
|
||||
|
||||
def whole_inference(self, img, img_meta, rescale):
|
||||
"""Inference with full image."""
|
||||
|
||||
seg_logit = self.encode_decode(img, img_meta)
|
||||
if rescale:
|
||||
# support dynamic shape for onnx
|
||||
if torch.onnx.is_in_onnx_export():
|
||||
size = img.shape[2:]
|
||||
else:
|
||||
size = img_meta[0]['ori_shape'][:2]
|
||||
seg_logit = resize(
|
||||
seg_logit,
|
||||
size=size,
|
||||
mode='bilinear',
|
||||
align_corners=self.align_corners,
|
||||
warning=False)
|
||||
|
||||
return seg_logit
|
||||
|
||||
def inference(self, img, img_meta, rescale):
|
||||
"""Inference with slide/whole style.
|
||||
|
||||
Args:
|
||||
img (Tensor): The input image of shape (N, 3, H, W).
|
||||
img_meta (dict): Image info dict where each dict has: 'img_shape',
|
||||
'scale_factor', 'flip', and may also contain
|
||||
'filename', 'ori_shape', 'pad_shape', and 'img_norm_cfg'.
|
||||
For details on the values of these keys see
|
||||
`mmseg/datasets/pipelines/formatting.py:Collect`.
|
||||
rescale (bool): Whether rescale back to original shape.
|
||||
|
||||
Returns:
|
||||
Tensor: The output segmentation map.
|
||||
"""
|
||||
|
||||
assert self.test_cfg.mode in ['slide', 'whole']
|
||||
ori_shape = img_meta[0]['ori_shape']
|
||||
assert all(_['ori_shape'] == ori_shape for _ in img_meta)
|
||||
if self.test_cfg.mode == 'slide':
|
||||
seg_logit = self.slide_inference(img, img_meta, rescale)
|
||||
else:
|
||||
seg_logit = self.whole_inference(img, img_meta, rescale)
|
||||
output = F.softmax(seg_logit, dim=1)
|
||||
flip = img_meta[0]['flip']
|
||||
if flip:
|
||||
flip_direction = img_meta[0]['flip_direction']
|
||||
assert flip_direction in ['horizontal', 'vertical']
|
||||
if flip_direction == 'horizontal':
|
||||
output = output.flip(dims=(3,))
|
||||
elif flip_direction == 'vertical':
|
||||
output = output.flip(dims=(2,))
|
||||
|
||||
return output
|
||||
|
||||
def simple_test(self, img, img_meta, rescale=True):
|
||||
"""Simple test with single image."""
|
||||
seg_logit = self.inference(img, img_meta, rescale)
|
||||
seg_pred = seg_logit.argmax(dim=1)
|
||||
if torch.onnx.is_in_onnx_export():
|
||||
# our inference backend only support 4D output
|
||||
seg_pred = seg_pred.unsqueeze(0)
|
||||
return seg_pred
|
||||
seg_pred = seg_pred.cpu().numpy()
|
||||
# unravel batch dim
|
||||
seg_pred = list(seg_pred)
|
||||
return seg_pred
|
||||
|
||||
def aug_test(self, imgs, img_metas, rescale=True):
|
||||
"""Test with augmentations.
|
||||
|
||||
Only rescale=True is supported.
|
||||
"""
|
||||
# aug_test rescale all imgs back to ori_shape for now
|
||||
assert rescale
|
||||
# to save memory, we get augmented seg logit inplace
|
||||
seg_logit = self.inference(imgs[0], img_metas[0], rescale)
|
||||
for i in range(1, len(imgs)):
|
||||
cur_seg_logit = self.inference(imgs[i], img_metas[i], rescale)
|
||||
seg_logit += cur_seg_logit
|
||||
seg_logit /= len(imgs)
|
||||
seg_pred = seg_logit.argmax(dim=1)
|
||||
seg_pred = seg_pred.cpu().numpy()
|
||||
# unravel batch dim
|
||||
seg_pred = list(seg_pred)
|
||||
return seg_pred
|
||||
@@ -0,0 +1,289 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from mmseg.core import add_prefix
|
||||
from mmseg.models import builder
|
||||
from mmseg.models.builder import SEGMENTORS
|
||||
from mmseg.models.segmentors.base import BaseSegmentor
|
||||
from mmseg.ops import resize
|
||||
|
||||
|
||||
@SEGMENTORS.register_module()
|
||||
class EncoderDecoderMask2FormerAug(BaseSegmentor):
|
||||
"""Encoder Decoder segmentors.
|
||||
|
||||
EncoderDecoder typically consists of backbone, decode_head, auxiliary_head.
|
||||
Note that auxiliary_head is only used for deep supervision during training,
|
||||
which could be dumped during inference.
|
||||
"""
|
||||
def __init__(self,
|
||||
backbone,
|
||||
decode_head,
|
||||
neck=None,
|
||||
auxiliary_head=None,
|
||||
train_cfg=None,
|
||||
test_cfg=None,
|
||||
pretrained=None,
|
||||
init_cfg=None):
|
||||
super(EncoderDecoderMask2FormerAug, self).__init__(init_cfg)
|
||||
if pretrained is not None:
|
||||
assert backbone.get('pretrained') is None, \
|
||||
'both backbone and segmentor set pretrained weight'
|
||||
backbone.pretrained = pretrained
|
||||
self.backbone = builder.build_backbone(backbone)
|
||||
if neck is not None:
|
||||
self.neck = builder.build_neck(neck)
|
||||
decode_head.update(train_cfg=train_cfg)
|
||||
decode_head.update(test_cfg=test_cfg)
|
||||
self._init_decode_head(decode_head)
|
||||
self._init_auxiliary_head(auxiliary_head)
|
||||
|
||||
self.train_cfg = train_cfg
|
||||
self.test_cfg = test_cfg
|
||||
|
||||
assert self.with_decode_head
|
||||
|
||||
def _init_decode_head(self, decode_head):
|
||||
"""Initialize ``decode_head``"""
|
||||
self.decode_head = builder.build_head(decode_head)
|
||||
self.align_corners = self.decode_head.align_corners
|
||||
self.num_classes = self.decode_head.num_classes
|
||||
|
||||
def _init_auxiliary_head(self, auxiliary_head):
|
||||
"""Initialize ``auxiliary_head``"""
|
||||
if auxiliary_head is not None:
|
||||
if isinstance(auxiliary_head, list):
|
||||
self.auxiliary_head = nn.ModuleList()
|
||||
for head_cfg in auxiliary_head:
|
||||
self.auxiliary_head.append(builder.build_head(head_cfg))
|
||||
else:
|
||||
self.auxiliary_head = builder.build_head(auxiliary_head)
|
||||
|
||||
def extract_feat(self, img):
|
||||
"""Extract features from images."""
|
||||
x = self.backbone(img)
|
||||
if self.with_neck:
|
||||
x = self.neck(x)
|
||||
return x
|
||||
|
||||
def encode_decode(self, img, img_metas):
|
||||
"""Encode images with backbone and decode into a semantic segmentation
|
||||
map of the same size as input."""
|
||||
x = self.extract_feat(img)
|
||||
out = self._decode_head_forward_test(x, img_metas)
|
||||
out = resize(
|
||||
input=out,
|
||||
size=img.shape[2:],
|
||||
mode='bilinear',
|
||||
align_corners=self.align_corners)
|
||||
return out
|
||||
|
||||
def _decode_head_forward_train(self, x, img_metas, gt_semantic_seg,
|
||||
**kwargs):
|
||||
"""Run forward function and calculate loss for decode head in
|
||||
training."""
|
||||
losses = dict()
|
||||
loss_decode = self.decode_head.forward_train(x, img_metas,
|
||||
gt_semantic_seg, **kwargs)
|
||||
|
||||
losses.update(add_prefix(loss_decode, 'decode'))
|
||||
return losses
|
||||
|
||||
def _decode_head_forward_test(self, x, img_metas):
|
||||
"""Run forward function and calculate loss for decode head in
|
||||
inference."""
|
||||
seg_logits = self.decode_head.forward_test(x, img_metas, self.test_cfg)
|
||||
return seg_logits
|
||||
|
||||
def _auxiliary_head_forward_train(self, x, img_metas, gt_semantic_seg):
|
||||
"""Run forward function and calculate loss for auxiliary head in
|
||||
training."""
|
||||
losses = dict()
|
||||
if isinstance(self.auxiliary_head, nn.ModuleList):
|
||||
for idx, aux_head in enumerate(self.auxiliary_head):
|
||||
loss_aux = aux_head.forward_train(x, img_metas,
|
||||
gt_semantic_seg,
|
||||
self.train_cfg)
|
||||
losses.update(add_prefix(loss_aux, f'aux_{idx}'))
|
||||
else:
|
||||
loss_aux = self.auxiliary_head.forward_train(
|
||||
x, img_metas, gt_semantic_seg, self.train_cfg)
|
||||
losses.update(add_prefix(loss_aux, 'aux'))
|
||||
|
||||
return losses
|
||||
|
||||
def forward_dummy(self, img):
|
||||
"""Dummy forward function."""
|
||||
seg_logit = self.encode_decode(img, None)
|
||||
|
||||
return seg_logit
|
||||
|
||||
def forward_train(self, img, img_metas, gt_semantic_seg, **kwargs):
|
||||
"""Forward function for training.
|
||||
|
||||
Args:
|
||||
img (Tensor): Input images.
|
||||
img_metas (list[dict]): List of image info dict where each dict
|
||||
has: 'img_shape', 'scale_factor', 'flip', and may also contain
|
||||
'filename', 'ori_shape', 'pad_shape', and 'img_norm_cfg'.
|
||||
For details on the values of these keys see
|
||||
`mmseg/datasets/pipelines/formatting.py:Collect`.
|
||||
gt_semantic_seg (Tensor): Semantic segmentation masks
|
||||
used if the architecture supports semantic segmentation task.
|
||||
|
||||
Returns:
|
||||
dict[str, Tensor]: a dictionary of loss components
|
||||
"""
|
||||
|
||||
x = self.extract_feat(img)
|
||||
|
||||
losses = dict()
|
||||
|
||||
loss_decode = self._decode_head_forward_train(x, img_metas,
|
||||
gt_semantic_seg,
|
||||
**kwargs)
|
||||
losses.update(loss_decode)
|
||||
|
||||
if self.with_auxiliary_head:
|
||||
loss_aux = self._auxiliary_head_forward_train(
|
||||
x, img_metas, gt_semantic_seg)
|
||||
losses.update(loss_aux)
|
||||
|
||||
return losses
|
||||
|
||||
# TODO refactor
|
||||
def slide_inference(self, img, img_meta, rescale, unpad=True):
|
||||
"""Inference by sliding-window with overlap.
|
||||
|
||||
If h_crop > h_img or w_crop > w_img, the small patch will be used to
|
||||
decode without padding.
|
||||
"""
|
||||
|
||||
h_stride, w_stride = self.test_cfg.stride
|
||||
h_crop, w_crop = self.test_cfg.crop_size
|
||||
batch_size, _, h_img, w_img = img.size()
|
||||
num_classes = self.num_classes
|
||||
h_grids = max(h_img - h_crop + h_stride - 1, 0) // h_stride + 1
|
||||
w_grids = max(w_img - w_crop + w_stride - 1, 0) // w_stride + 1
|
||||
preds = img.new_zeros((batch_size, num_classes, h_img, w_img))
|
||||
count_mat = img.new_zeros((batch_size, 1, h_img, w_img))
|
||||
for h_idx in range(h_grids):
|
||||
for w_idx in range(w_grids):
|
||||
y1 = h_idx * h_stride
|
||||
x1 = w_idx * w_stride
|
||||
y2 = min(y1 + h_crop, h_img)
|
||||
x2 = min(x1 + w_crop, w_img)
|
||||
y1 = max(y2 - h_crop, 0)
|
||||
x1 = max(x2 - w_crop, 0)
|
||||
crop_img = img[:, :, y1:y2, x1:x2]
|
||||
crop_seg_logit = self.encode_decode(crop_img, img_meta)
|
||||
preds += F.pad(crop_seg_logit,
|
||||
(int(x1), int(preds.shape[3] - x2), int(y1),
|
||||
int(preds.shape[2] - y2)))
|
||||
|
||||
count_mat[:, :, y1:y2, x1:x2] += 1
|
||||
assert (count_mat == 0).sum() == 0
|
||||
if torch.onnx.is_in_onnx_export():
|
||||
# cast count_mat to constant while exporting to ONNX
|
||||
count_mat = torch.from_numpy(
|
||||
count_mat.cpu().detach().numpy()).to(device=img.device)
|
||||
preds = preds / count_mat
|
||||
|
||||
if unpad:
|
||||
unpad_h, unpad_w = img_meta[0]['img_shape'][:2]
|
||||
# logging.info(preds.shape, img_meta[0])
|
||||
preds = preds[:, :, :unpad_h, :unpad_w]
|
||||
if rescale:
|
||||
preds = resize(preds,
|
||||
size=img_meta[0]['ori_shape'][:2],
|
||||
mode='bilinear',
|
||||
align_corners=self.align_corners,
|
||||
warning=False)
|
||||
return preds
|
||||
|
||||
def whole_inference(self, img, img_meta, rescale):
|
||||
"""Inference with full image."""
|
||||
|
||||
seg_logit = self.encode_decode(img, img_meta)
|
||||
if rescale:
|
||||
# support dynamic shape for onnx
|
||||
if torch.onnx.is_in_onnx_export():
|
||||
size = img.shape[2:]
|
||||
else:
|
||||
size = img_meta[0]['ori_shape'][:2]
|
||||
seg_logit = resize(
|
||||
seg_logit,
|
||||
size=size,
|
||||
mode='bilinear',
|
||||
align_corners=self.align_corners,
|
||||
warning=False)
|
||||
|
||||
return seg_logit
|
||||
|
||||
def inference(self, img, img_meta, rescale):
|
||||
"""Inference with slide/whole style.
|
||||
|
||||
Args:
|
||||
img (Tensor): The input image of shape (N, 3, H, W).
|
||||
img_meta (dict): Image info dict where each dict has: 'img_shape',
|
||||
'scale_factor', 'flip', and may also contain
|
||||
'filename', 'ori_shape', 'pad_shape', and 'img_norm_cfg'.
|
||||
For details on the values of these keys see
|
||||
`mmseg/datasets/pipelines/formatting.py:Collect`.
|
||||
rescale (bool): Whether rescale back to original shape.
|
||||
|
||||
Returns:
|
||||
Tensor: The output segmentation map.
|
||||
"""
|
||||
|
||||
assert self.test_cfg.mode in ['slide', 'whole']
|
||||
ori_shape = img_meta[0]['ori_shape']
|
||||
assert all(_['ori_shape'] == ori_shape for _ in img_meta)
|
||||
if self.test_cfg.mode == 'slide':
|
||||
seg_logit = self.slide_inference(img, img_meta, rescale)
|
||||
else:
|
||||
seg_logit = self.whole_inference(img, img_meta, rescale)
|
||||
output = F.softmax(seg_logit, dim=1)
|
||||
flip = img_meta[0]['flip']
|
||||
if flip:
|
||||
flip_direction = img_meta[0]['flip_direction']
|
||||
assert flip_direction in ['horizontal', 'vertical']
|
||||
if flip_direction == 'horizontal':
|
||||
output = output.flip(dims=(3, ))
|
||||
elif flip_direction == 'vertical':
|
||||
output = output.flip(dims=(2, ))
|
||||
|
||||
return output
|
||||
|
||||
def simple_test(self, img, img_meta, rescale=True):
|
||||
"""Simple test with single image."""
|
||||
seg_logit = self.inference(img, img_meta, rescale)
|
||||
seg_pred = seg_logit.argmax(dim=1)
|
||||
if torch.onnx.is_in_onnx_export():
|
||||
# our inference backend only support 4D output
|
||||
seg_pred = seg_pred.unsqueeze(0)
|
||||
return seg_pred
|
||||
seg_pred = seg_pred.cpu().numpy()
|
||||
# unravel batch dim
|
||||
seg_pred = list(seg_pred)
|
||||
return seg_pred
|
||||
|
||||
def aug_test(self, imgs, img_metas, rescale=True):
|
||||
"""Test with augmentations.
|
||||
|
||||
Only rescale=True is supported.
|
||||
"""
|
||||
# aug_test rescale all imgs back to ori_shape for now
|
||||
assert rescale
|
||||
# to save memory, we get augmented seg logit inplace
|
||||
seg_logit = self.inference(imgs[0], img_metas[0], rescale)
|
||||
for i in range(1, len(imgs)):
|
||||
cur_seg_logit = self.inference(imgs[i], img_metas[i], rescale)
|
||||
seg_logit += cur_seg_logit
|
||||
seg_logit /= len(imgs)
|
||||
seg_pred = seg_logit.argmax(dim=1)
|
||||
seg_pred = seg_pred.cpu().numpy()
|
||||
# unravel batch dim
|
||||
seg_pred = list(seg_pred)
|
||||
return seg_pred
|
||||
13
segmentation/mmseg_custom/models/utils/__init__.py
Normal file
13
segmentation/mmseg_custom/models/utils/__init__.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# Copyright (c) Shanghai AI Lab. All rights reserved.
|
||||
from .assigner import MaskHungarianAssigner
|
||||
from .point_sample import get_uncertain_point_coords_with_randomness
|
||||
from .positional_encoding import (LearnedPositionalEncoding,
|
||||
SinePositionalEncoding)
|
||||
from .transformer import (DetrTransformerDecoder, DetrTransformerDecoderLayer,
|
||||
DynamicConv, Transformer)
|
||||
|
||||
__all__ = [
|
||||
'DetrTransformerDecoderLayer', 'DetrTransformerDecoder', 'DynamicConv',
|
||||
'Transformer', 'LearnedPositionalEncoding', 'SinePositionalEncoding',
|
||||
'MaskHungarianAssigner', 'get_uncertain_point_coords_with_randomness'
|
||||
]
|
||||
165
segmentation/mmseg_custom/models/utils/assigner.py
Normal file
165
segmentation/mmseg_custom/models/utils/assigner.py
Normal file
@@ -0,0 +1,165 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
from ..builder import MASK_ASSIGNERS, build_match_cost
|
||||
|
||||
try:
|
||||
from scipy.optimize import linear_sum_assignment
|
||||
except ImportError:
|
||||
linear_sum_assignment = None
|
||||
|
||||
|
||||
class AssignResult(metaclass=ABCMeta):
|
||||
"""Collection of assign results."""
|
||||
def __init__(self, num_gts, gt_inds, labels):
|
||||
self.num_gts = num_gts
|
||||
self.gt_inds = gt_inds
|
||||
self.labels = labels
|
||||
|
||||
@property
|
||||
def info(self):
|
||||
info = {
|
||||
'num_gts': self.num_gts,
|
||||
'gt_inds': self.gt_inds,
|
||||
'labels': self.labels,
|
||||
}
|
||||
return info
|
||||
|
||||
|
||||
class BaseAssigner(metaclass=ABCMeta):
|
||||
"""Base assigner that assigns boxes to ground truth boxes."""
|
||||
@abstractmethod
|
||||
def assign(self, masks, gt_masks, gt_masks_ignore=None, gt_labels=None):
|
||||
"""Assign boxes to either a ground truth boxes or a negative boxes."""
|
||||
pass
|
||||
|
||||
|
||||
@MASK_ASSIGNERS.register_module()
|
||||
class MaskHungarianAssigner(BaseAssigner):
|
||||
"""Computes one-to-one matching between predictions and ground truth for
|
||||
mask.
|
||||
|
||||
This class computes an assignment between the targets and the predictions
|
||||
based on the costs. The costs are weighted sum of three components:
|
||||
classification cost, regression L1 cost and regression iou cost. The
|
||||
targets don't include the no_object, so generally there are more
|
||||
predictions than targets. After the one-to-one matching, the un-matched
|
||||
are treated as backgrounds. Thus each query prediction will be assigned
|
||||
with `0` or a positive integer indicating the ground truth index:
|
||||
|
||||
- 0: negative sample, no assigned gt
|
||||
- positive integer: positive sample, index (1-based) of assigned gt
|
||||
|
||||
Args:
|
||||
cls_cost (obj:`mmcv.ConfigDict`|dict): Classification cost config.
|
||||
mask_cost (obj:`mmcv.ConfigDict`|dict): Mask cost config.
|
||||
dice_cost (obj:`mmcv.ConfigDict`|dict): Dice cost config.
|
||||
"""
|
||||
def __init__(self,
|
||||
cls_cost=dict(type='ClassificationCost', weight=1.0),
|
||||
dice_cost=dict(type='DiceCost', weight=1.0),
|
||||
mask_cost=dict(type='MaskFocalCost', weight=1.0)):
|
||||
self.cls_cost = build_match_cost(cls_cost)
|
||||
self.dice_cost = build_match_cost(dice_cost)
|
||||
self.mask_cost = build_match_cost(mask_cost)
|
||||
|
||||
def assign(self,
|
||||
cls_pred,
|
||||
mask_pred,
|
||||
gt_labels,
|
||||
gt_masks,
|
||||
img_meta,
|
||||
gt_masks_ignore=None,
|
||||
eps=1e-7):
|
||||
"""Computes one-to-one matching based on the weighted costs.
|
||||
|
||||
This method assign each query prediction to a ground truth or
|
||||
background. The `assigned_gt_inds` with -1 means don't care,
|
||||
0 means negative sample, and positive number is the index (1-based)
|
||||
of assigned gt.
|
||||
The assignment is done in the following steps, the order matters.
|
||||
|
||||
1. assign every prediction to -1
|
||||
2. compute the weighted costs
|
||||
3. do Hungarian matching on CPU based on the costs
|
||||
4. assign all to 0 (background) first, then for each matched pair
|
||||
between predictions and gts, treat this prediction as foreground
|
||||
and assign the corresponding gt index (plus 1) to it.
|
||||
|
||||
Args:
|
||||
mask_pred (Tensor): Predicted mask, shape [num_query, h, w]
|
||||
cls_pred (Tensor): Predicted classification logits, shape
|
||||
[num_query, num_class].
|
||||
gt_masks (Tensor): Ground truth mask, shape [num_gt, h, w].
|
||||
gt_labels (Tensor): Label of `gt_masks`, shape (num_gt,).
|
||||
img_meta (dict): Meta information for current image.
|
||||
gt_masks_ignore (Tensor, optional): Ground truth masks that are
|
||||
labelled as `ignored`. Default None.
|
||||
eps (int | float, optional): A value added to the denominator for
|
||||
numerical stability. Default 1e-7.
|
||||
|
||||
Returns:
|
||||
:obj:`AssignResult`: The assigned result.
|
||||
"""
|
||||
assert gt_masks_ignore is None, \
|
||||
'Only case when gt_masks_ignore is None is supported.'
|
||||
num_gts, num_queries = gt_labels.shape[0], cls_pred.shape[0]
|
||||
|
||||
# 1. assign -1 by default
|
||||
assigned_gt_inds = cls_pred.new_full((num_queries, ),
|
||||
-1,
|
||||
dtype=torch.long)
|
||||
assigned_labels = cls_pred.new_full((num_queries, ),
|
||||
-1,
|
||||
dtype=torch.long)
|
||||
if num_gts == 0 or num_queries == 0:
|
||||
# No ground truth or boxes, return empty assignment
|
||||
if num_gts == 0:
|
||||
# No ground truth, assign all to background
|
||||
assigned_gt_inds[:] = 0
|
||||
return AssignResult(
|
||||
num_gts, assigned_gt_inds, labels=assigned_labels)
|
||||
|
||||
# 2. compute the weighted costs
|
||||
# classification and maskcost.
|
||||
if self.cls_cost.weight != 0 and cls_pred is not None:
|
||||
cls_cost = self.cls_cost(cls_pred, gt_labels)
|
||||
else:
|
||||
cls_cost = 0
|
||||
|
||||
if self.mask_cost.weight != 0:
|
||||
# mask_pred shape = [nq, h, w]
|
||||
# gt_mask shape = [ng, h, w]
|
||||
# mask_cost shape = [nq, ng]
|
||||
mask_cost = self.mask_cost(mask_pred, gt_masks)
|
||||
else:
|
||||
mask_cost = 0
|
||||
|
||||
if self.dice_cost.weight != 0:
|
||||
dice_cost = self.dice_cost(mask_pred, gt_masks)
|
||||
else:
|
||||
dice_cost = 0
|
||||
cost = cls_cost + mask_cost + dice_cost
|
||||
|
||||
# 3. do Hungarian matching on CPU using linear_sum_assignment
|
||||
cost = cost.detach().cpu()
|
||||
if linear_sum_assignment is None:
|
||||
raise ImportError('Please run "pip install scipy" '
|
||||
'to install scipy first.')
|
||||
|
||||
matched_row_inds, matched_col_inds = linear_sum_assignment(cost)
|
||||
matched_row_inds = torch.from_numpy(matched_row_inds).to(
|
||||
cls_pred.device)
|
||||
matched_col_inds = torch.from_numpy(matched_col_inds).to(
|
||||
cls_pred.device)
|
||||
|
||||
# 4. assign backgrounds and foregrounds
|
||||
# assign all indices to backgrounds first
|
||||
assigned_gt_inds[:] = 0
|
||||
# assign foregrounds based on matching results
|
||||
assigned_gt_inds[matched_row_inds] = matched_col_inds + 1
|
||||
assigned_labels[matched_row_inds] = gt_labels[matched_col_inds]
|
||||
return AssignResult(num_gts, assigned_gt_inds, labels=assigned_labels)
|
||||
87
segmentation/mmseg_custom/models/utils/point_sample.py
Normal file
87
segmentation/mmseg_custom/models/utils/point_sample.py
Normal file
@@ -0,0 +1,87 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch
|
||||
from mmcv.ops import point_sample
|
||||
|
||||
|
||||
def get_uncertainty(mask_pred, labels):
|
||||
"""Estimate uncertainty based on pred logits.
|
||||
|
||||
We estimate uncertainty as L1 distance between 0.0 and the logits
|
||||
prediction in 'mask_pred' for the foreground class in `classes`.
|
||||
|
||||
Args:
|
||||
mask_pred (Tensor): mask predication logits, shape (num_rois,
|
||||
num_classes, mask_height, mask_width).
|
||||
|
||||
labels (list[Tensor]): Either predicted or ground truth label for
|
||||
each predicted mask, of length num_rois.
|
||||
|
||||
Returns:
|
||||
scores (Tensor): Uncertainty scores with the most uncertain
|
||||
locations having the highest uncertainty score,
|
||||
shape (num_rois, 1, mask_height, mask_width)
|
||||
"""
|
||||
if mask_pred.shape[1] == 1:
|
||||
gt_class_logits = mask_pred.clone()
|
||||
else:
|
||||
inds = torch.arange(mask_pred.shape[0], device=mask_pred.device)
|
||||
gt_class_logits = mask_pred[inds, labels].unsqueeze(1)
|
||||
return -torch.abs(gt_class_logits)
|
||||
|
||||
|
||||
def get_uncertain_point_coords_with_randomness(mask_pred, labels, num_points,
|
||||
oversample_ratio,
|
||||
importance_sample_ratio):
|
||||
"""Get ``num_points`` most uncertain points with random points during
|
||||
train.
|
||||
|
||||
Sample points in [0, 1] x [0, 1] coordinate space based on their
|
||||
uncertainty. The uncertainties are calculated for each point using
|
||||
'get_uncertainty()' function that takes point's logit prediction as
|
||||
input.
|
||||
|
||||
Args:
|
||||
mask_pred (Tensor): A tensor of shape (num_rois, num_classes,
|
||||
mask_height, mask_width) for class-specific or class-agnostic
|
||||
prediction.
|
||||
labels (list): The ground truth class for each instance.
|
||||
num_points (int): The number of points to sample.
|
||||
oversample_ratio (int): Oversampling parameter.
|
||||
importance_sample_ratio (float): Ratio of points that are sampled
|
||||
via importnace sampling.
|
||||
|
||||
Returns:
|
||||
point_coords (Tensor): A tensor of shape (num_rois, num_points, 2)
|
||||
that contains the coordinates sampled points.
|
||||
"""
|
||||
assert oversample_ratio >= 1
|
||||
assert 0 <= importance_sample_ratio <= 1
|
||||
batch_size = mask_pred.shape[0]
|
||||
num_sampled = int(num_points * oversample_ratio)
|
||||
point_coords = torch.rand(
|
||||
batch_size, num_sampled, 2, device=mask_pred.device)
|
||||
point_logits = point_sample(mask_pred, point_coords)
|
||||
# It is crucial to calculate uncertainty based on the sampled
|
||||
# prediction value for the points. Calculating uncertainties of the
|
||||
# coarse predictions first and sampling them for points leads to
|
||||
# incorrect results. To illustrate this: assume uncertainty func(
|
||||
# logits)=-abs(logits), a sampled point between two coarse
|
||||
# predictions with -1 and 1 logits has 0 logits, and therefore 0
|
||||
# uncertainty value. However, if we calculate uncertainties for the
|
||||
# coarse predictions first, both will have -1 uncertainty,
|
||||
# and sampled point will get -1 uncertainty.
|
||||
point_uncertainties = get_uncertainty(point_logits, labels)
|
||||
num_uncertain_points = int(importance_sample_ratio * num_points)
|
||||
num_random_points = num_points - num_uncertain_points
|
||||
idx = torch.topk(
|
||||
point_uncertainties[:, 0, :], k=num_uncertain_points, dim=1)[1]
|
||||
shift = num_sampled * torch.arange(
|
||||
batch_size, dtype=torch.long, device=mask_pred.device)
|
||||
idx += shift[:, None]
|
||||
point_coords = point_coords.view(-1, 2)[idx.view(-1), :].view(
|
||||
batch_size, num_uncertain_points, 2)
|
||||
if num_random_points > 0:
|
||||
rand_roi_coords = torch.rand(
|
||||
batch_size, num_random_points, 2, device=mask_pred.device)
|
||||
point_coords = torch.cat((point_coords, rand_roi_coords), dim=1)
|
||||
return point_coords
|
||||
161
segmentation/mmseg_custom/models/utils/positional_encoding.py
Normal file
161
segmentation/mmseg_custom/models/utils/positional_encoding.py
Normal file
@@ -0,0 +1,161 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import math
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from mmcv.cnn.bricks.transformer import POSITIONAL_ENCODING
|
||||
from mmcv.runner import BaseModule
|
||||
|
||||
|
||||
@POSITIONAL_ENCODING.register_module()
|
||||
class SinePositionalEncoding(BaseModule):
|
||||
"""Position encoding with sine and cosine functions.
|
||||
|
||||
See `End-to-End Object Detection with Transformers
|
||||
<https://arxiv.org/pdf/2005.12872>`_ for details.
|
||||
|
||||
Args:
|
||||
num_feats (int): The feature dimension for each position
|
||||
along x-axis or y-axis. Note the final returned dimension
|
||||
for each position is 2 times of this value.
|
||||
temperature (int, optional): The temperature used for scaling
|
||||
the position embedding. Defaults to 10000.
|
||||
normalize (bool, optional): Whether to normalize the position
|
||||
embedding. Defaults to False.
|
||||
scale (float, optional): A scale factor that scales the position
|
||||
embedding. The scale will be used only when `normalize` is True.
|
||||
Defaults to 2*pi.
|
||||
eps (float, optional): A value added to the denominator for
|
||||
numerical stability. Defaults to 1e-6.
|
||||
offset (float): offset add to embed when do the normalization.
|
||||
Defaults to 0.
|
||||
init_cfg (dict or list[dict], optional): Initialization config dict.
|
||||
Default: None
|
||||
"""
|
||||
def __init__(self,
|
||||
num_feats,
|
||||
temperature=10000,
|
||||
normalize=False,
|
||||
scale=2 * math.pi,
|
||||
eps=1e-6,
|
||||
offset=0.,
|
||||
init_cfg=None):
|
||||
super(SinePositionalEncoding, self).__init__(init_cfg)
|
||||
if normalize:
|
||||
assert isinstance(scale, (float, int)), 'when normalize is set,' \
|
||||
'scale should be provided and in float or int type, ' \
|
||||
f'found {type(scale)}'
|
||||
self.num_feats = num_feats
|
||||
self.temperature = temperature
|
||||
self.normalize = normalize
|
||||
self.scale = scale
|
||||
self.eps = eps
|
||||
self.offset = offset
|
||||
|
||||
def forward(self, mask):
|
||||
"""Forward function for `SinePositionalEncoding`.
|
||||
|
||||
Args:
|
||||
mask (Tensor): ByteTensor mask. Non-zero values representing
|
||||
ignored positions, while zero values means valid positions
|
||||
for this image. Shape [bs, h, w].
|
||||
|
||||
Returns:
|
||||
pos (Tensor): Returned position embedding with shape
|
||||
[bs, num_feats*2, h, w].
|
||||
"""
|
||||
# For convenience of exporting to ONNX, it's required to convert
|
||||
# `masks` from bool to int.
|
||||
mask = mask.to(torch.int)
|
||||
not_mask = 1 - mask # logical_not
|
||||
y_embed = not_mask.cumsum(1, dtype=torch.float32)
|
||||
x_embed = not_mask.cumsum(2, dtype=torch.float32)
|
||||
if self.normalize:
|
||||
y_embed = (y_embed + self.offset) / \
|
||||
(y_embed[:, -1:, :] + self.eps) * self.scale
|
||||
x_embed = (x_embed + self.offset) / \
|
||||
(x_embed[:, :, -1:] + self.eps) * self.scale
|
||||
dim_t = torch.arange(
|
||||
self.num_feats, dtype=torch.float32, device=mask.device)
|
||||
dim_t = self.temperature**(2 * (dim_t // 2) / self.num_feats)
|
||||
pos_x = x_embed[:, :, :, None] / dim_t
|
||||
pos_y = y_embed[:, :, :, None] / dim_t
|
||||
# use `view` instead of `flatten` for dynamically exporting to ONNX
|
||||
B, H, W = mask.size()
|
||||
pos_x = torch.stack(
|
||||
(pos_x[:, :, :, 0::2].sin(), pos_x[:, :, :, 1::2].cos()),
|
||||
dim=4).view(B, H, W, -1)
|
||||
pos_y = torch.stack(
|
||||
(pos_y[:, :, :, 0::2].sin(), pos_y[:, :, :, 1::2].cos()),
|
||||
dim=4).view(B, H, W, -1)
|
||||
pos = torch.cat((pos_y, pos_x), dim=3).permute(0, 3, 1, 2)
|
||||
return pos
|
||||
|
||||
def __repr__(self):
|
||||
"""str: a string that describes the module"""
|
||||
repr_str = self.__class__.__name__
|
||||
repr_str += f'(num_feats={self.num_feats}, '
|
||||
repr_str += f'temperature={self.temperature}, '
|
||||
repr_str += f'normalize={self.normalize}, '
|
||||
repr_str += f'scale={self.scale}, '
|
||||
repr_str += f'eps={self.eps})'
|
||||
return repr_str
|
||||
|
||||
|
||||
@POSITIONAL_ENCODING.register_module()
|
||||
class LearnedPositionalEncoding(BaseModule):
|
||||
"""Position embedding with learnable embedding weights.
|
||||
|
||||
Args:
|
||||
num_feats (int): The feature dimension for each position
|
||||
along x-axis or y-axis. The final returned dimension for
|
||||
each position is 2 times of this value.
|
||||
row_num_embed (int, optional): The dictionary size of row embeddings.
|
||||
Default 50.
|
||||
col_num_embed (int, optional): The dictionary size of col embeddings.
|
||||
Default 50.
|
||||
init_cfg (dict or list[dict], optional): Initialization config dict.
|
||||
"""
|
||||
def __init__(self,
|
||||
num_feats,
|
||||
row_num_embed=50,
|
||||
col_num_embed=50,
|
||||
init_cfg=dict(type='Uniform', layer='Embedding')):
|
||||
super(LearnedPositionalEncoding, self).__init__(init_cfg)
|
||||
self.row_embed = nn.Embedding(row_num_embed, num_feats)
|
||||
self.col_embed = nn.Embedding(col_num_embed, num_feats)
|
||||
self.num_feats = num_feats
|
||||
self.row_num_embed = row_num_embed
|
||||
self.col_num_embed = col_num_embed
|
||||
|
||||
def forward(self, mask):
|
||||
"""Forward function for `LearnedPositionalEncoding`.
|
||||
|
||||
Args:
|
||||
mask (Tensor): ByteTensor mask. Non-zero values representing
|
||||
ignored positions, while zero values means valid positions
|
||||
for this image. Shape [bs, h, w].
|
||||
|
||||
Returns:
|
||||
pos (Tensor): Returned position embedding with shape
|
||||
[bs, num_feats*2, h, w].
|
||||
"""
|
||||
h, w = mask.shape[-2:]
|
||||
x = torch.arange(w, device=mask.device)
|
||||
y = torch.arange(h, device=mask.device)
|
||||
x_embed = self.col_embed(x)
|
||||
y_embed = self.row_embed(y)
|
||||
pos = torch.cat(
|
||||
(x_embed.unsqueeze(0).repeat(h, 1, 1), y_embed.unsqueeze(1).repeat(
|
||||
1, w, 1)),
|
||||
dim=-1).permute(2, 0,
|
||||
1).unsqueeze(0).repeat(mask.shape[0], 1, 1, 1)
|
||||
return pos
|
||||
|
||||
def __repr__(self):
|
||||
"""str: a string that describes the module"""
|
||||
repr_str = self.__class__.__name__
|
||||
repr_str += f'(num_feats={self.num_feats}, '
|
||||
repr_str += f'row_num_embed={self.row_num_embed}, '
|
||||
repr_str += f'col_num_embed={self.col_num_embed})'
|
||||
return repr_str
|
||||
1083
segmentation/mmseg_custom/models/utils/transformer.py
Normal file
1083
segmentation/mmseg_custom/models/utils/transformer.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user