| |
| import torch |
| import torch.nn as nn |
| import torch.nn.functional as F |
| from torchvision import ops |
| from basicsr.utils.registry import ARCH_REGISTRY |
| from einops import rearrange |
| from basicsr.archs.arch_util import trunc_normal_ |
|
|
| from itertools import repeat |
| import collections.abc |
| from typing import Tuple |
|
|
| from pdb import set_trace as st |
| import numpy as np |
|
|
| f""" |
| cust_arch.py |
| """ |
| |
| |
| |
|
|
| |
| |
| class LayerNorm(nn.Module): |
| def __init__(self, normalized_shape, eps=1e-6, channel_first=True): |
| super().__init__() |
| self.channel_first = channel_first |
| self.normalized_shape = normalized_shape |
| self.eps = eps |
| self.norm = nn.LayerNorm(normalized_shape, eps=eps) |
|
|
| def forward(self, x): |
| if self.channel_first == False: |
| return self.norm(x) |
|
|
| elif self.channel_first == True: |
| x = x.permute(0, 2, 3, 1) |
| x = self.norm(x) |
| x = x.permute(0, 3, 1, 2) |
| return x |
|
|
| |
| class dwconv(nn.Module): |
| def __init__(self, hidden_features, kernel_size=5): |
| super(dwconv, self).__init__() |
|
|
| self.depthwise_conv = nn.Conv2d( |
| hidden_features, hidden_features, |
| kernel_size=kernel_size, stride=1, |
| padding=(kernel_size - 1) // 2, |
| groups=hidden_features, |
| ) |
|
|
| def forward(self, x, x_size): |
| |
| B, L, C = x.shape |
| H, W = x_size |
| x = x.transpose(1, 2).reshape(B, C, H, W) |
| |
| x = self.depthwise_conv(x) |
| x = x.view(B, C, -1).transpose(1, 2) |
| return x |
|
|
| class ConvFFN(nn.Module): |
| def __init__(self, in_features, hidden_features=None, out_features=None, kernel_size=5): |
| super().__init__() |
| out_features = out_features or in_features |
| hidden_features = hidden_features or in_features |
| |
| self.fc1 = nn.Linear(in_features, hidden_features) |
| self.dwconv = dwconv(hidden_features=hidden_features, kernel_size=kernel_size) |
| self.fc2 = nn.Linear(hidden_features, out_features) |
| self.act = nn.GELU() |
|
|
| def forward(self, x, x_size): |
| x = self.fc1(x) |
| x = self.act(x) |
| x = x + self.dwconv(x, x_size) |
| x = self.fc2(x) |
| return x |
|
|
| |
| |
| class CUSTAttention(nn.Module): |
| def __init__(self, |
| dim, |
| window_size=8, |
| group_size=9): |
| super().__init__() |
| |
| |
| self.window_size = window_size |
| self.group_size = group_size |
| self.scale = dim ** -0.5 |
|
|
| hidden_dim = dim |
| |
| self.to_q = nn.Linear(dim, hidden_dim) |
| self.to_k = nn.Linear(dim, hidden_dim) |
| self.to_v = nn.Linear(dim, dim) |
| self.proj = nn.Linear(dim, dim) |
| |
| |
| self.gate_proj = nn.Linear(dim, dim) |
| self.act = nn.Sigmoid() |
| |
| |
| def window_group_partition(self, x): |
| B, C, H, W = x.shape |
| ws, gs = self.window_size, self.group_size |
| |
| |
| |
| |
| target_unit = ws * gs |
| pad_h = (target_unit - H % target_unit) % target_unit |
| pad_w = (target_unit - W % target_unit) % target_unit |
|
|
| if pad_h > 0 or pad_w > 0: |
| x = F.pad(x, (0, pad_w, 0, pad_h), mode='reflect') |
| |
| H_pad, W_pad = x.shape[2], x.shape[3] |
| gh, gw = H_pad // target_unit, W_pad // target_unit |
|
|
| |
| |
| |
| |
| |
| x = x.view(B, C, gh, gs, ws, gw, gs, ws) |
| x = x.permute(0, 2, 5, 3, 6, 4, 7, 1) |
|
|
| x = x.contiguous().view(B, gh * gw, gs * gs, ws * ws, C) |
| |
| return x, pad_h, pad_w |
| |
| def window_group_reverse(self, x, original_shape, padded_size): |
| b, ng, gs_sq, ws_sq, chan = x.shape |
| ws, gs = self.window_size, self.group_size |
| _, _, H, W = original_shape |
| |
| |
| |
| |
| H_pad, W_pad = H + padded_size[0], W + padded_size[1] |
| gh, gw = H_pad // (ws * gs), W_pad // (ws * gs) |
| |
| |
| |
| |
| |
| x = x.view(b, gh, gw, gs, gs, ws, ws, chan) |
| x = x.permute(0, 7, 1, 3, 5, 2, 4, 6) |
| x = x.contiguous().view(b, chan, H_pad, W_pad) |
| |
| if padded_size[0] > 0 or padded_size[1] > 0: |
| x = x[:, :, :H, :W] |
| |
| return x |
|
|
| |
| |
| |
| def cana(self, x_grouped, sim): |
| f""" |
| 다음 청크를 키/밸류에 추가. |
| 단, 다른 window를 높은 유사도로 갖는 패치는 -inf 처리 |
| |
| sim : [B, ng, gs, ws, gs] : 각 패치들(ws)과, 그룹 내의 윈도우들(gs) 간의 유사도 |
| """ |
| B, ng, gs, ws, chan = x_grouped.shape |
| device = x_grouped.device |
|
|
| x_grouped = x_grouped.view(B*ng, gs*ws, chan) |
| |
| assign_id = sim.argmax(dim=-1).view(B*ng, gs*ws) |
| sorting_indices = torch.argsort(assign_id, dim=1) |
| |
| |
| gather_idx = sorting_indices.unsqueeze(-1).expand(-1, -1, chan) |
| x_sorted = torch.gather(x_grouped, 1, gather_idx) |
| id_sorted = torch.gather(assign_id, 1, sorting_indices) |
|
|
| cs = self.window_size ** 2 |
| nc = (gs*ws) // cs |
| |
| |
| q_chunks = x_sorted.view(B * ng, nc, cs, chan) |
| q_ids = id_sorted.view(B * ng, nc, cs) |
| |
| |
| |
| pad_x = torch.zeros(B*ng, cs//2, chan, device=device) |
| pad_x = torch.cat([pad_x, x_sorted, pad_x], dim=1) |
| pad_id = torch.full((B*ng, cs//2), -1, device=device) |
| pad_id = torch.cat([pad_id, id_sorted, pad_id], dim=1) |
| |
| |
| |
| kv_chunks = pad_x.unfold(1, cs*2, cs).permute(0, 1, 3, 2) |
| kv_ids = pad_id.unfold(1, cs*2, cs) |
| |
| |
| |
| |
| q = self.to_q(q_chunks) |
| k = self.to_k(kv_chunks) |
| v = self.to_v(kv_chunks) |
| attn = (q @ k.transpose(-2, -1)) * self.scale |
|
|
| |
| |
| |
| mask = (q_ids.unsqueeze(-1) == kv_ids.unsqueeze(-2)) |
| |
| |
| min_val = -1e4 |
| attn = attn.masked_fill(~mask, min_val) |
|
|
| attn = attn.softmax(dim=-1) |
| out = attn @ v |
|
|
| gate = self.act(self.gate_proj(x_sorted)).view(B*ng, gs, ws, -1) |
| out = out * gate |
| |
| |
| |
| |
| out = out.view(B * ng, gs*ws, chan) |
| out = self.proj(out) |
|
|
| inverse_indices = torch.argsort(sorting_indices, dim=1) |
| inverse_indices = inverse_indices.unsqueeze(-1).expand(-1, -1, chan) |
|
|
| out = torch.gather(out, 1, inverse_indices) |
| out = out.view(B, ng, gs, ws, chan) |
| |
| return out |
|
|
| def forward(self, x): |
| |
| batch, chan, H, W = x.shape |
|
|
| |
| |
| |
| x_grouped, pad_h, pad_w = self.window_group_partition(x) |
| |
| |
| |
| |
| sim = x_grouped.detach().mean(dim=3) |
| sim = torch.einsum('b g w p c, b g k c -> b g w p k', x_grouped, sim) |
| cana_out = self.cana(x_grouped, sim) |
| |
| |
| |
| |
| x = self.window_group_reverse(cana_out, x.shape, (pad_h, pad_w)) |
| |
| return x |
| |
| |
| class CUSTBlock(nn.Module): |
| def __init__(self, |
| dim, |
| window_size=8, |
| group_size=9, |
| ffn_scale=2.0,): |
| super().__init__() |
| self.pe = nn.Conv2d(dim, dim, kernel_size=3, padding=1, groups=dim) |
| |
| |
| self.norm1 = LayerNorm(dim) |
| self.attn = CUSTAttention(dim, window_size, group_size) |
| |
| |
| self.norm2 = LayerNorm(dim) |
| self.ffn = ConvFFN(dim, int(dim * ffn_scale)) |
|
|
| def forward(self, x): |
| x = x + self.pe(x) |
| |
| |
| x = x + self.attn(self.norm1(x)) |
| |
| |
| shortcut = x |
| x = self.norm2(x) |
| |
| B, C, H, W = x.shape |
| x = rearrange(x, 'b c h w -> b (h w) c') |
| x = self.ffn(x, (H, W)) |
| x = rearrange(x, 'b (h w) c -> b c h w', h=H, w=W) |
| |
| x = shortcut + x |
| return x |
|
|
|
|
| |
| |
| def patch_divide(x, step, ps): |
| """Crop image into patches(이미지를 지정된 크기(ps)로 자르되, 서로 겹치게 자른다.) |
| Args: |
| x (Tensor): Input feature map of shape(b, c, h, w). |
| step (int): Divide step. 'ps-2' |
| ps (int): Patch size. [16, 20, 24, 28, 16, 20, 24, 28] |
| Returns: |
| crop_x (Tensor): Cropped patches. |
| nh (int): Number of patches along the horizontal direction. |
| nw (int): Number of patches along the vertical direction. |
| """ |
| b, c, h, w = x.size() |
| if h == ps and w == ps: |
| step = ps |
| crop_x = [] |
| nh = 0 |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| for i in range(0, h + step - ps, step): |
| top = i |
| down = i + ps |
| if down > h: |
| top = h - ps |
| down = h |
| nh += 1 |
| for j in range(0, w + step - ps, step): |
| left = j |
| right = j + ps |
| if right > w: |
| left = w - ps |
| right = w |
| crop_x.append(x[:, :, top:down, left:right]) |
| nw = len(crop_x) // nh |
| |
| |
| |
| |
| |
| crop_x = torch.stack(crop_x, dim=0) |
| crop_x = crop_x.permute(1, 0, 2, 3, 4).contiguous() |
| return crop_x, nh, nw |
|
|
|
|
| def patch_reverse(crop_x, x, step, ps): |
| """Reverse patches into image. |
| Args: |
| crop_x (Tensor): Cropped patches. [B, num_crop, dim, ps, ps] |
| x (Tensor): Feature map of shape(b, c, h, w). |
| step (int): Divide step. |
| ps (int): Patch size. |
| Returns: |
| output (Tensor): Reversed image. [B, dim(40), H, W] |
| """ |
| b, c, h, w = x.size() |
| output = torch.zeros_like(x) |
| index = 0 |
| |
| |
| |
| |
| |
| |
| for i in range(0, h + step - ps, step): |
| top = i |
| down = i + ps |
| if down > h: |
| top = h - ps |
| down = h |
| for j in range(0, w + step - ps, step): |
| left = j |
| right = j + ps |
| if right > w: |
| left = w - ps |
| right = w |
| output[:, :, top:down, left:right] += crop_x[:, index] |
| index += 1 |
| |
| |
| |
| |
| |
| |
| for i in range(step, h + step - ps, step): |
| top = i |
| down = i + ps - step |
| if top + ps > h: |
| top = h - ps |
| output[:, :, top:down, :] /= 2 |
| |
| for j in range(step, w + step - ps, step): |
| left = j |
| right = j + ps - step |
| if left + ps > w: |
| left = w - ps |
| output[:, :, :, left:right] /= 2 |
| |
| return output |
|
|
| class Attention(nn.Module): |
| """Attention module. |
| Args: |
| dim (int): Base channels. |
| heads (int): Head numbers. |
| qk_dim (int): Channels of query and key. |
| """ |
|
|
| def __init__(self, dim, heads, qk_dim): |
| super().__init__() |
|
|
| self.heads = heads |
| self.dim = dim |
| self.qk_dim = qk_dim |
| self.scale = qk_dim ** -0.5 |
|
|
| |
| self.qkv = nn.Linear(dim, dim*3, bias=False) |
| self.gate = nn.Linear(dim, dim) |
| self.proj = nn.Linear(dim, dim, bias=False) |
| self.act = nn.GELU() |
| self.pe = nn.Conv2d(dim, dim, kernel_size=3, padding=1, groups=dim) |
| |
|
|
| def forward(self, x): |
| B, N, C = x.shape |
| ws = int(N**0.5) |
| |
| qkv = self.qkv(x) |
| q, k, v = qkv.split([self.qk_dim, self.qk_dim, self.dim], dim=-1) |
| z = self.act(self.gate(x)) |
| |
| |
| pe = self.pe(q.transpose(1,2).view(B, C, ws, ws)).view(B, C, N).transpose(1,2) |
| |
| attn = (q @ k.transpose(-2, -1)) * self.scale |
| attn = attn.softmax(dim=-1) |
| out = (attn @ v) + pe |
| |
| |
| out = out * z |
| |
| return self.proj(out) |
|
|
|
|
| |
| class Low_to_high_MS_v2(nn.Module): |
| def __init__(self, dim): |
| super().__init__() |
| |
| self.error_refiner = nn.Sequential( |
| nn.Conv2d(dim, dim, kernel_size=3, padding=2, dilation=2, groups=dim, bias=False), |
| nn.GELU(), |
| nn.Conv2d(dim, dim, 1) |
| ) |
| |
| self.gate_gen = nn.Sequential( |
| nn.Conv2d(dim * 2, dim // 4, kernel_size=1), |
| nn.GELU(), |
| nn.Conv2d(dim // 4, 1, kernel_size=1), |
| nn.Sigmoid() |
| ) |
| |
| self.scale = nn.Parameter(torch.zeros(1, dim, 1, 1)) |
|
|
| def forward(self, x): |
| B, C, H, W = x.shape |
| |
| |
| |
| |
| x_d2 = F.adaptive_avg_pool2d(x, (H // 2, W // 2)) |
| x_u2 = F.interpolate(x_d2, size=(H, W), mode='bilinear', align_corners=False) |
| err2 = x - x_u2 |
|
|
| x_d4 = F.adaptive_avg_pool2d(x_d2, (H // 4, W // 4)) |
| x_u4 = F.interpolate(x_d4, size=(H, W), mode='bilinear', align_corners=False) |
| err4 = x_u2 - x_u4 |
| |
| |
| |
| |
| refined_error = self.error_refiner(err2 + err4) |
| error_energies = torch.cat([err2.abs(), err4.abs()], dim=1) |
| spatial_gate = self.gate_gen(error_energies) |
| |
| return x + (self.scale * refined_error * spatial_gate) |
| |
|
|
| class MEDA(nn.Module): |
| """Attention module. |
| Args: |
| dim (int): Base channels. |
| num (int): Number of blocks. |
| qk_dim (int): Channels of query and key in Attention. |
| mlp_dim (int): Channels of hidden mlp in Mlp. |
| heads (int): Head numbers of Attention. |
| |
| patch_divide 및 reverse (with overlapping)의 목표 : |
| step(stride)를 ps(patch_size)보다 작게 해서, 경계면에 있는 애들의 정보를 더 잘 파악하기 위함. |
| """ |
|
|
| def __init__(self, |
| dim, |
| qk_dim, |
| ffn_scale=2.0, |
| heads=1): |
| super().__init__() |
|
|
| self.norm1 = LayerNorm(dim, channel_first=False) |
| self.norm2 = LayerNorm(dim, channel_first=False) |
| |
| self.lth = Low_to_high_MS_v2(dim) |
| self.attn = Attention(dim, heads, qk_dim) |
| self.ffn = ConvFFN(dim, int(dim * ffn_scale)) |
|
|
|
|
| def forward(self, x, ps): |
| B, C, H, W = x.shape |
| step = ps - 2 |
| |
| x = self.lth(x) |
| |
| |
| |
| |
| |
| |
| crop_x, nh, nw = patch_divide(x, step, ps) |
| b, n, c, ph, pw = crop_x.shape |
| crop_x = rearrange(crop_x, 'b n c h w -> (b n) (h w) c') |
|
|
| crop_x = self.attn(self.norm1(crop_x)) + crop_x |
| crop_x = rearrange(crop_x, '(b n) (h w) c -> b n c h w', n=n, w=pw) |
| |
| |
| |
| |
| |
| |
| x = patch_reverse(crop_x, x, step, ps) |
| _, _, h, w = x.shape |
| x = rearrange(x, 'b c h w-> b (h w) c') |
| x = self.ffn(self.norm2(x), x_size=(h, w)) + x |
| x = rearrange(x, 'b (h w) c->b c h w', h=h) |
| |
| return x |
| |
| |
| |
| |
| class MainBlock(nn.Module): |
| def __init__(self, |
| dim, |
| ffn_scale=2.0, |
| drop=0., |
| attn_drop=0., |
| drop_path=0., |
| patch_size=16, |
| window_size=8, |
| group_size=9,): |
| |
| super().__init__() |
|
|
| self.patch_size = patch_size |
| |
| |
| self.cust = CUSTBlock(dim, |
| ffn_scale=ffn_scale, |
| window_size=window_size, |
| group_size=group_size,) |
| self.meda = MEDA(dim, |
| dim, |
| ffn_scale=ffn_scale, |
| ) |
| |
| |
| self.mid_conv = nn.Conv2d(dim, dim, 3, 1, 1) |
|
|
| def forward(self, x): |
| residual = x |
| x = self.cust(x) |
| x = self.meda(x, self.patch_size) |
| x = self.mid_conv(x) + residual |
| return x |
| |
|
|
| |
| |
| |
| class CUSTNet(nn.Module): |
| def __init__(self, |
| dim, |
| ffn_scale=2.0, |
| upscaling_factor=4, |
| drop_rate=0., |
| attn_drop_rate=0., |
| drop_path_rate=0., |
| patch_size=[12, 16, 20, 24, 12, 16, 20, 24], |
| window_size=8, |
| group_size=10,): |
| |
| super().__init__() |
| self.to_feat = nn.Conv2d(3, dim, 3, 1, 1) |
| self.dim = dim |
| n_blocks = len(patch_size) |
| |
| self.pos_drop = nn.Dropout(p=drop_rate) |
| dpr = [x.item() for x in torch.linspace(0, drop_path_rate, n_blocks)] |
| |
| self.feats = nn.Sequential(*[MainBlock(dim, |
| ffn_scale, |
| drop=drop_rate, |
| attn_drop=attn_drop_rate, |
| drop_path=dpr[i], |
| patch_size=patch_size[i], |
| window_size=window_size, |
| group_size=group_size, |
| ) |
| for i in range(n_blocks)]) |
|
|
| |
| |
| |
| |
| |
| self.upscale = upscaling_factor |
| if self.upscale == 4: |
| self.upconv1 = nn.Conv2d(self.dim, self.dim * 4, 3, 1, 1, bias=True) |
| self.upconv2 = nn.Conv2d(self.dim, self.dim * 4, 3, 1, 1, bias=True) |
| self.pixel_shuffle = nn.PixelShuffle(2) |
| |
| elif self.upscale == 2 or self.upscale == 3: |
| self.upconv = nn.Conv2d(self.dim, self.dim * (self.upscale ** 2), 3, 1, 1, bias=True) |
| self.pixel_shuffle = nn.PixelShuffle(self.upscale) |
| |
| self.last_conv = nn.Conv2d(self.dim, 3, 3, 1, 1) |
| if self.upscale != 1: |
| self.lrelu = nn.LeakyReLU(negative_slope=0.1, inplace=True) |
| |
| self.apply(self._init_weights) |
| |
| 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 check_img_size(self, x): |
| _, _, h, w = x.size() |
| downsample_scale = 8 |
| scaled_size = self.window_size * downsample_scale |
| |
| mod_pad_h = (scaled_size - h % scaled_size) % scaled_size |
| mod_pad_w = (scaled_size - w % scaled_size) % scaled_size |
| x = F.pad(x, (0, mod_pad_w, 0, mod_pad_h), 'reflect') |
| return x |
| |
| |
| def forward(self, x): |
| B, C, H, W = x.shape |
| |
| |
| x_feat = self.to_feat(x) |
| |
| |
| x_feat = self.feats(x_feat) + x_feat |
| x_feat = x_feat[:, :, :H, :W] |
| |
| |
| if self.upscale == 4: |
| x_feat = self.lrelu(self.pixel_shuffle(self.upconv1(x_feat))) |
| x_feat = self.lrelu(self.pixel_shuffle(self.upconv2(x_feat))) |
| elif self.upscale == 1: |
| x_feat = x_feat |
| else: |
| x_feat = self.lrelu(self.pixel_shuffle(self.upconv(x_feat))) |
| |
| x_feat = self.last_conv(x_feat) |
| |
| if self.upscale != 1: |
| base = F.interpolate(x, scale_factor=self.upscale, mode='bilinear', align_corners=False) |
| else: |
| base = x |
| |
| x_out = x_feat + base |
| return x_out |
|
|
|
|
| if __name__== '__main__': |
| |
| from fvcore.nn import flop_count_table, FlopCountAnalysis, ActivationCountAnalysis |
| |
| |
| x, upscaling_factor = torch.randn(1, 3, 320, 180), 4 |
| |
|
|
| window_size, group_size = 8, 10 |
| |
| |
| |
| |
| |
| |
| branch_dim = [30] |
| patch_size = [18,18,18,18,18,18,18,18] |
| |
| dim = sum(branch_dim) |
| |
| model = CUSTNet(dim=dim, |
| ffn_scale=2.0, |
| upscaling_factor=upscaling_factor, |
| window_size=window_size, |
| patch_size=patch_size,) |
| |
| |
| print(f'params: {sum(map(lambda x: x.numel(), model.parameters()))}') |
| print(flop_count_table(FlopCountAnalysis(model, x), activations=ActivationCountAnalysis(model, x))) |
| |
| |
|
|