File size: 2,955 Bytes
93cf011
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import torch
import torch.nn as nn
from torchvision import models


class ChannelAttention(nn.Module):
    def __init__(self, channels: int, reduction: int = 16):
        super().__init__()
        hidden = max(channels // reduction, 16)
        self.avg_pool = nn.AdaptiveAvgPool2d(1)
        self.max_pool = nn.AdaptiveMaxPool2d(1)
        self.mlp = nn.Sequential(
            nn.Conv2d(channels, hidden, kernel_size=1, bias=False),
            nn.ReLU(inplace=True),
            nn.Conv2d(hidden, channels, kernel_size=1, bias=False),
        )
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        attn = self.mlp(self.avg_pool(x)) + self.mlp(self.max_pool(x))
        return x * self.sigmoid(attn)


class SpatialAttention(nn.Module):
    def __init__(self, kernel_size: int = 7):
        super().__init__()
        padding = kernel_size // 2
        self.conv = nn.Conv2d(2, 1, kernel_size=kernel_size, padding=padding, bias=False)
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        avg_map = torch.mean(x, dim=1, keepdim=True)
        max_map, _ = torch.max(x, dim=1, keepdim=True)
        attn = torch.cat([avg_map, max_map], dim=1)
        attn = self.sigmoid(self.conv(attn))
        return x * attn


class CBAMBlock(nn.Module):
    def __init__(self, channels: int, reduction: int = 16, kernel_size: int = 7):
        super().__init__()
        self.ca = ChannelAttention(channels, reduction)
        self.sa = SpatialAttention(kernel_size)

    def forward(self, x):
        return self.sa(self.ca(x))


class ConvNeXtWithCBAM(nn.Module):
    def __init__(self, backbone: nn.Module, n_class: int, dropout: float = 0.65):
        super().__init__()
        self.features = backbone.features
        self.avgpool = backbone.avgpool
        self.norm = backbone.classifier[0]
        out_channels = backbone.classifier[2].in_features

        self.attn = CBAMBlock(out_channels, reduction=16, kernel_size=7)
        self.head = nn.Sequential(
            nn.Flatten(1),
            nn.Dropout(p=dropout),
            nn.Linear(out_channels, n_class),
        )

    def forward(self, x):
        x = self.features(x)
        x = self.attn(x)
        x = self.avgpool(x)
        x = self.norm(x)
        x = self.head(x)
        return x


def set_convnext_stochastic_depth(model: nn.Module, p: float = 0.2):
    for m in model.modules():
        if hasattr(m, "stochastic_depth") and hasattr(m.stochastic_depth, "p"):
            m.stochastic_depth.p = p


def build_convnext_cbam(

    n_class: int,

    dropout: float = 0.65,

    sd_prob: float = 0.2,

    weights=models.ConvNeXt_Small_Weights.IMAGENET1K_V1,

):
    backbone = models.convnext_small(weights=weights)
    set_convnext_stochastic_depth(backbone, p=sd_prob)
    model = ConvNeXtWithCBAM(backbone=backbone, n_class=n_class, dropout=dropout)
    return model