| """ |
| model.py |
| ======== |
| Định nghĩa kiến trúc mô hình EfficientNet-B4 + CBAM (Convolutional Block Attention Module) |
| phục vụ bài toán phân loại Mức độ Bệnh Võng mạc Tiểu đường (Diabetic Retinopathy - 5 lớp ICDR). |
| """ |
|
|
| from __future__ import annotations |
| import torch |
| import torch.nn as nn |
| from torchvision import models |
|
|
|
|
| class ChannelAttention(nn.Module): |
| """ |
| Channel Attention Sub-module của CBAM. |
| Tính toán trọng số chú ý cho từng kênh đặc trưng dựa trên AvgPool và MaxPool. |
| """ |
| def __init__(self, in_planes: int, ratio: int = 16): |
| super().__init__() |
| self.avg_pool = nn.AdaptiveAvgPool2d(1) |
| self.max_pool = nn.AdaptiveMaxPool2d(1) |
| self.fc = nn.Sequential( |
| nn.Conv2d(in_planes, in_planes // ratio, 1, bias=False), |
| nn.ReLU(inplace=True), |
| nn.Conv2d(in_planes // ratio, in_planes, 1, bias=False), |
| ) |
| self.sigmoid = nn.Sigmoid() |
|
|
| def forward(self, x: torch.Tensor) -> torch.Tensor: |
| avg_out = self.fc(self.avg_pool(x)) |
| max_out = self.fc(self.max_pool(x)) |
| return self.sigmoid(avg_out + max_out) |
|
|
|
|
| class SpatialAttention(nn.Module): |
| """ |
| Spatial Attention Sub-module của CBAM. |
| Tập trung vào các vùng không gian quan trọng (xuất huyết, vi phình mạch, xuất tiết). |
| """ |
| def __init__(self, kernel_size: int = 7): |
| super().__init__() |
| self.conv = nn.Conv2d(2, 1, kernel_size, padding=kernel_size // 2, bias=False) |
| self.sigmoid = nn.Sigmoid() |
|
|
| def forward(self, x: torch.Tensor) -> torch.Tensor: |
| avg_out = torch.mean(x, dim=1, keepdim=True) |
| max_out, _ = torch.max(x, dim=1, keepdim=True) |
| return self.sigmoid(self.conv(torch.cat([avg_out, max_out], dim=1))) |
|
|
|
|
| class CBAM(nn.Module): |
| """ |
| Convolutional Block Attention Module (CBAM). |
| Kết hợp Channel Attention và Spatial Attention nối tiếp. |
| """ |
| def __init__(self, in_planes: int, ratio: int = 16, kernel_size: int = 7): |
| super().__init__() |
| self.ca = ChannelAttention(in_planes, ratio) |
| self.sa = SpatialAttention(kernel_size) |
|
|
| def forward(self, x: torch.Tensor) -> torch.Tensor: |
| x = x * self.ca(x) |
| x = x * self.sa(x) |
| return x |
|
|
|
|
| class EfficientNetB4_CBAM(nn.Module): |
| """ |
| Kiến trúc mô hình chính: |
| - Backbone: EfficientNet-B4 (Feature Extractor: 1792 channels) |
| - Attention: CBAM Module đặt ngay sau feature extractor |
| - Global Pooling: AdaptiveAvgPool2d(1) |
| - Classifier: Dropout(0.3) + Linear(1792 -> 5 classes) |
| """ |
| def __init__( |
| self, |
| num_classes: int = 5, |
| drop_rate: float = 0.3, |
| cbam_ratio: int = 16, |
| pretrained: bool = False, |
| ): |
| super().__init__() |
| if pretrained: |
| weights = models.EfficientNet_B4_Weights.DEFAULT |
| backbone = models.efficientnet_b4(weights=weights) |
| else: |
| backbone = models.efficientnet_b4(weights=None) |
|
|
| self.features = backbone.features |
| in_planes = 1792 |
| self.cbam = CBAM(in_planes, ratio=cbam_ratio) |
| self.avgpool = nn.AdaptiveAvgPool2d(1) |
| self.classifier = nn.Sequential( |
| nn.Dropout(p=drop_rate), |
| nn.Linear(in_planes, num_classes), |
| ) |
|
|
| def forward(self, x: torch.Tensor) -> torch.Tensor: |
| x = self.features(x) |
| x = self.cbam(x) |
| x = self.avgpool(x) |
| x = torch.flatten(x, 1) |
| return self.classifier(x) |
|
|
|
|
| def build_model( |
| num_classes: int = 5, |
| drop_rate: float = 0.3, |
| cbam_ratio: int = 16, |
| pretrained: bool = False, |
| ) -> EfficientNetB4_CBAM: |
| """Hàm helper khởi tạo mô hình và in số lượng tham số.""" |
| model = EfficientNetB4_CBAM( |
| num_classes=num_classes, |
| drop_rate=drop_rate, |
| cbam_ratio=cbam_ratio, |
| pretrained=pretrained, |
| ) |
| return model |
|
|