File size: 6,882 Bytes
4985e90
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
"""LW-Vit"""

import torch 
import torch.nn as nn
import torch.nn.functional as F

class HSwish(nn.Module):
    """Hard Swish activation function"""
    def forward(self, x):
        out = x * F.relu6(x + 3, inplace=True) / 6
        return out

class MV2_Block(nn.Module):
    """MobileNetV2 Inverted Residual Block with h-swish activation"""
    def __init__(self, in_channels, expand_channels, out_channels, stride):
        super().__init__()
        self.stride = stride
        self.use_res_connect = self.stride == 1 and in_channels == out_channels
        
        self.expand = nn.Sequential(
            nn.Conv2d(in_channels, expand_channels, kernel_size=1, stride=1, padding=0, bias=False),
            nn.BatchNorm2d(expand_channels),
            HSwish()
        )

        self.depthwise = nn.Sequential(
            nn.Conv2d(expand_channels, expand_channels, kernel_size=3, stride=stride, 
                     padding=1, groups=expand_channels, bias=False),
            nn.BatchNorm2d(expand_channels),
            HSwish()
        )

        self.project = nn.Sequential(
            nn.Conv2d(expand_channels, out_channels, kernel_size=1, stride=1, padding=0, bias=False),
            nn.BatchNorm2d(out_channels)
        )

    def forward(self, x):
        if self.use_res_connect:
            return x + self.project(self.depthwise(self.expand(x)))
        else:
            return self.project(self.depthwise(self.expand(x)))
        
class LW_ViT_Transformer_Block(nn.Module):
    """Lightweight Vision Transformer Block"""
    def __init__(self, in_channels, out_channels, patch_size=2, heads=4, dim=128):
        super().__init__()
        self.patch_size = patch_size
        self.in_channels = in_channels
        self.out_channels = out_channels
        self.dim = dim
        
        # Downsampling and channel adjustment
        self.downsample = nn.Conv2d(in_channels, in_channels, kernel_size=3, stride=2, padding=1)
        self.channel_adjust = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=1)
        
        # Calculate patch dimension
        patch_dim = out_channels * patch_size * patch_size
        # Add projection layer if needed
        self.projection = nn.Identity() if patch_dim == dim else nn.Linear(patch_dim, dim)
        
        # Transformer components
        self.norm1 = nn.LayerNorm(dim)
        self.attn = nn.MultiheadAttention(embed_dim=dim, num_heads=heads, batch_first=True)
        self.norm2 = nn.LayerNorm(dim)
        self.ffn = nn.Sequential(
            nn.Linear(dim, dim * 4),
            nn.GELU(),
            nn.Linear(dim * 4, dim)
        )
        
        # Final processing
        self.final_conv = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=False)
        self.norm_out = nn.BatchNorm2d(out_channels)
        self.act = HSwish()

    def forward(self, x):
        # Initial downsampling and channel adjustment
        x = self.downsample(x)
        x = self.channel_adjust(x)
        
        B, C, H, W = x.shape
        
        # Convert feature map to patches and embed
        # Reshape to [B, C, H/P, P, W/P, P]
        x_reshaped = x.reshape(B, C, H // self.patch_size, self.patch_size, 
                              W // self.patch_size, self.patch_size)
        # Permute to [B, H/P, W/P, C, P, P]
        x_permuted = x_reshaped.permute(0, 2, 4, 1, 3, 5)
        # Reshape to [B, H/P * W/P, C * P * P]
        patches = x_permuted.reshape(B, (H // self.patch_size) * (W // self.patch_size), -1)
        
        # Apply projection if needed
        patches = self.projection(patches)
        
        # Apply transformer operations
        normed_patches = self.norm1(patches)
        attn_out, _ = self.attn(normed_patches, normed_patches, normed_patches)
        patches = patches + attn_out
        
        normed_patches = self.norm2(patches)
        ffn_out = self.ffn(normed_patches)
        patches = patches + ffn_out
        
        # Reshape back to feature map
        # Reshape to [B, H/P, W/P, C, P, P]
        x_back = patches.reshape(B, H // self.patch_size, W // self.patch_size, 
                                C, self.patch_size, self.patch_size)
        # Permute to [B, C, H/P, P, W/P, P]
        x_back = x_back.permute(0, 3, 1, 4, 2, 5)
        # Reshape to [B, C, H, W]
        x_out = x_back.reshape(B, C, H, W)
        
        # Final processing
        out = self.final_conv(x_out)
        out = self.norm_out(out)
        out = self.act(out)
        
        return out

class LW_ViT(nn.Module):
    """Lightweight Vision Transformer for Chinese Character Recognition"""
    def __init__(self, base_channels=16, num_classes=3892):
        super().__init__()
        
        # Stem layer
        self.stem = nn.Sequential(
            nn.Conv2d(3, base_channels, 3, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(base_channels),
            HSwish()
        )

        # Feature extraction layers
        self.features = nn.Sequential(
            # MV2 blocks
            MV2_Block(base_channels, 4*base_channels, base_channels, stride=1),
            MV2_Block(base_channels, 4*base_channels, base_channels, stride=2),
            MV2_Block(base_channels, 4*base_channels, base_channels, stride=1),
            MV2_Block(base_channels, 4*base_channels, base_channels, stride=2),
            
            # Transformer block
            LW_ViT_Transformer_Block(
                in_channels=base_channels, 
                out_channels=2*base_channels, 
                patch_size=2, 
                heads=4, 
                dim=128
            ),
            
            # Final convolutional layer
            nn.Conv2d(2*base_channels, 4*base_channels, kernel_size=1, stride=1),
            nn.BatchNorm2d(4*base_channels),
            HSwish()
        )
        
        # Global pooling and classifier
        self.global_pool = nn.AdaptiveAvgPool2d(1)
        self.dropout = nn.Dropout(0.1)  # Added dropout for regularization
        
        self.classifier = nn.Sequential(
            nn.Linear(4*base_channels, 8*base_channels),
            nn.BatchNorm1d(8*base_channels),
            HSwish(),
            nn.Dropout(0.1),  # Added dropout for regularization
            nn.Linear(8*base_channels, num_classes)
        )

    def forward(self, x):
        x = self.stem(x)
        x = self.features(x)
        x = self.global_pool(x)
        x = torch.flatten(x, 1)
        x = self.dropout(x)
        x = self.classifier(x)
        return x
    

num_cls = 3892
num_epochs = 100
user_device = "cuda" if torch.cuda.is_available() else "cpu"
LW_model = LW_ViT(base_channels=16, num_classes=num_cls)