Spaces:
Sleeping
Sleeping
| """ | |
| Multi-Modal 1D ResNet + Dense Multi-Task Network V2. | |
| Improvements: SE attention, deeper backbone, better regularization. | |
| """ | |
| import torch | |
| import torch.nn as nn | |
| from config import NUM_CLASSES, IN_CHANNELS, SEQ_LEN, RESNET_DIM, DENSE_DIM, FUSED_DIM, NUM_FIT_PARAMS | |
| class SEBlock1d(nn.Module): | |
| """Squeeze-and-Excitation block for channel attention.""" | |
| def __init__(self, channels, reduction=4): | |
| super().__init__() | |
| self.squeeze = nn.AdaptiveAvgPool1d(1) | |
| self.excitation = nn.Sequential( | |
| nn.Linear(channels, channels // reduction), | |
| nn.ReLU(inplace=True), | |
| nn.Linear(channels // reduction, channels), | |
| nn.Sigmoid(), | |
| ) | |
| def forward(self, x): | |
| b, c, _ = x.shape | |
| w = self.squeeze(x).squeeze(-1) # (b, c) | |
| w = self.excitation(w).unsqueeze(-1) # (b, c, 1) | |
| return x * w | |
| class ResBlock1d(nn.Module): | |
| """1D Residual Block with SE attention and optional downsampling.""" | |
| def __init__(self, in_channels, out_channels, stride=1, use_se=True): | |
| super().__init__() | |
| self.conv1 = nn.Conv1d(in_channels, out_channels, kernel_size=3, | |
| stride=stride, padding=1, bias=False) | |
| self.bn1 = nn.BatchNorm1d(out_channels) | |
| self.relu = nn.ReLU(inplace=True) | |
| self.conv2 = nn.Conv1d(out_channels, out_channels, kernel_size=3, | |
| stride=1, padding=1, bias=False) | |
| self.bn2 = nn.BatchNorm1d(out_channels) | |
| self.se = SEBlock1d(out_channels) if use_se else nn.Identity() | |
| self.shortcut = nn.Sequential() | |
| if stride != 1 or in_channels != out_channels: | |
| self.shortcut = nn.Sequential( | |
| nn.Conv1d(in_channels, out_channels, kernel_size=1, | |
| stride=stride, bias=False), | |
| nn.BatchNorm1d(out_channels) | |
| ) | |
| def forward(self, x): | |
| out = self.relu(self.bn1(self.conv1(x))) | |
| out = self.bn2(self.conv2(out)) | |
| out = self.se(out) | |
| out += self.shortcut(x) | |
| out = self.relu(out) | |
| return out | |
| class ResNetBackbone(nn.Module): | |
| """1D ResNet backbone with SE attention.""" | |
| def __init__(self, in_channels=IN_CHANNELS, base_dim=64, out_dim=RESNET_DIM): | |
| super().__init__() | |
| self.conv1 = nn.Conv1d(in_channels, base_dim, kernel_size=7, | |
| stride=2, padding=3, bias=False) | |
| self.bn1 = nn.BatchNorm1d(base_dim) | |
| self.relu = nn.ReLU(inplace=True) | |
| self.maxpool = nn.MaxPool1d(kernel_size=3, stride=2, padding=1) | |
| self.layer1 = nn.Sequential( | |
| ResBlock1d(base_dim, base_dim), | |
| ResBlock1d(base_dim, base_dim) | |
| ) | |
| self.layer2 = nn.Sequential( | |
| ResBlock1d(base_dim, base_dim * 2, stride=2), | |
| ResBlock1d(base_dim * 2, base_dim * 2) | |
| ) | |
| self.layer3 = nn.Sequential( | |
| ResBlock1d(base_dim * 2, base_dim * 4, stride=2), | |
| ResBlock1d(base_dim * 4, base_dim * 4) | |
| ) | |
| self.avgpool = nn.AdaptiveAvgPool1d(1) | |
| def forward(self, x): | |
| x = self.relu(self.bn1(self.conv1(x))) | |
| x = self.maxpool(x) | |
| x = self.layer1(x) | |
| x = self.layer2(x) | |
| x = self.layer3(x) | |
| x = self.avgpool(x) | |
| x = x.squeeze(-1) | |
| return x | |
| class DenseParamBranch(nn.Module): | |
| """Dense MLP branch for processing 4 fit parameters.""" | |
| def __init__(self, in_features=NUM_FIT_PARAMS, out_features=DENSE_DIM): | |
| super().__init__() | |
| self.net = nn.Sequential( | |
| nn.Linear(in_features, 32), | |
| nn.ReLU(inplace=True), | |
| nn.BatchNorm1d(32), | |
| nn.Dropout(0.1), | |
| nn.Linear(32, out_features), | |
| nn.ReLU(inplace=True), | |
| nn.BatchNorm1d(out_features), | |
| ) | |
| def forward(self, x): | |
| return self.net(x) | |
| class ClassificationHead(nn.Module): | |
| """Classification head with dropout.""" | |
| def __init__(self, in_features=FUSED_DIM, num_classes=NUM_CLASSES, dropout=0.4): | |
| super().__init__() | |
| self.net = nn.Sequential( | |
| nn.Dropout(dropout), | |
| nn.Linear(in_features, 128), | |
| nn.ReLU(inplace=True), | |
| nn.BatchNorm1d(128), | |
| nn.Dropout(dropout * 0.5), | |
| nn.Linear(128, num_classes), | |
| ) | |
| def forward(self, x): | |
| return self.net(x) | |
| class RabiMultiTaskNet(nn.Module): | |
| """ | |
| Multi-Modal Multi-Task Network V2 with SE attention. | |
| """ | |
| def __init__(self): | |
| super().__init__() | |
| self.signal_backbone = ResNetBackbone() | |
| self.param_branch = DenseParamBranch() | |
| self.data_head = ClassificationHead() | |
| self.fit_head = ClassificationHead() | |
| def forward(self, signal, params): | |
| signal_features = self.signal_backbone(signal) | |
| param_features = self.param_branch(params) | |
| fused = torch.cat([signal_features, param_features], dim=1) | |
| data_quality = self.data_head(fused) | |
| fit_quality = self.fit_head(fused) | |
| return data_quality, fit_quality | |
| def count_parameters(model): | |
| """Count total trainable parameters.""" | |
| return sum(p.numel() for p in model.parameters() if p.requires_grad) | |
| if __name__ == '__main__': | |
| from config import DEVICE | |
| model = RabiMultiTaskNet() | |
| print(f"Total parameters: {count_parameters(model):,}") | |
| model = model.to(DEVICE) | |
| x = torch.randn(4, 2, SEQ_LEN).to(DEVICE) | |
| params = torch.randn(4, 4).to(DEVICE) | |
| dq, fq = model(x, params) | |
| print(f"Data quality output: {dq.shape}") | |
| print(f"Fit quality output: {fq.shape}") | |
| print(f"Device: {DEVICE}") | |