""" All neural network blocks and architectures in models/enhanced_cnn.py are custom implementations, developed to expand the model registry for advanced polymer spectral classification. While inspired by established deep learning concepts (such as residual connections, attention mechanisms, and multi-scale convolutions), they are are unique to this project and tailored for 1D spectral data. Registry expansion: The purpose is to enrich the available models. Literature inspiration: SE-Net, ResNet, Inception. """ import torch import torch.nn as nn import torch.nn.functional as F class AttentionBlock1D(nn.Module): """1D attention mechanism for spectral data.""" def __init__(self, channels: int, reduction: int = 8): super().__init__() self.channels = channels self.global_pool = nn.AdaptiveAvgPool1d(1) self.fc = nn.Sequential( nn.Linear(channels, channels // reduction), nn.ReLU(inplace=True), nn.Linear(channels // reduction, channels), nn.Sigmoid(), ) def forward(self, x): # x shape: [batch, channels, length] b, c, _ = x.size() # Global average pooling y = self.global_pool(x).view(b, c) # Fully connected layers y = self.fc(y).view(b, c, 1) # Apply attention weights return x * y.expand_as(x) class EnhancedResidualBlock1D(nn.Module): """Enhanced residual block with attention and improved normalization.""" def __init__( self, in_channels: int, out_channels: int, kernel_size: int = 3, use_attention: bool = True, dropout_rate: float = 0.1, ): super().__init__() padding = kernel_size // 2 self.conv1 = nn.Conv1d(in_channels, out_channels, kernel_size, padding=padding) self.bn1 = nn.BatchNorm1d(out_channels) self.relu = nn.ReLU(inplace=True) self.conv2 = nn.Conv1d(out_channels, out_channels, kernel_size, padding=padding) self.bn2 = nn.BatchNorm1d(out_channels) self.dropout = nn.Dropout1d(dropout_rate) if dropout_rate > 0 else nn.Identity() # Skip connection self.skip = ( nn.Identity() if in_channels == out_channels else nn.Sequential( nn.Conv1d(in_channels, out_channels, kernel_size=1), nn.BatchNorm1d(out_channels), ) ) # Attention mechanism self.attention = ( AttentionBlock1D(out_channels) if use_attention else nn.Identity() ) def forward(self, x): identity = self.skip(x) out = self.conv1(x) out = self.bn1(out) out = self.relu(out) out = self.dropout(out) out = self.conv2(out) out = self.bn2(out) # Apply attention out = self.attention(out) out = out + identity return self.relu(out) class MultiScaleConvBlock(nn.Module): """Multi-scale convolution block for capturing features at different scales.""" def __init__(self, in_channels: int, out_channels: int): super().__init__() # Different kernel sizes for multi-scale feature extraction self.conv1 = nn.Conv1d(in_channels, out_channels // 4, kernel_size=3, padding=1) self.conv2 = nn.Conv1d(in_channels, out_channels // 4, kernel_size=5, padding=2) self.conv3 = nn.Conv1d(in_channels, out_channels // 4, kernel_size=7, padding=3) self.conv4 = nn.Conv1d(in_channels, out_channels // 4, kernel_size=9, padding=4) self.bn = nn.BatchNorm1d(out_channels) self.relu = nn.ReLU(inplace=True) def forward(self, x): # Parallel convolutions with different kernel sizes out1 = self.conv1(x) out2 = self.conv2(x) out3 = self.conv3(x) out4 = self.conv4(x) # Concatenate along channel dimension out = torch.cat([out1, out2, out3, out4], dim=1) out = self.bn(out) return self.relu(out) class EnhancedCNN(nn.Module): """Enhanced CNN with attention, multi-scale features, and improved architecture.""" def __init__( self, input_length: int = 500, num_classes: int = 2, dropout_rate: float = 0.2, use_attention: bool = True, ): super().__init__() self.input_length = input_length self.num_classes = num_classes # Initial feature extraction self.initial_conv = nn.Sequential( nn.Conv1d(1, 32, kernel_size=7, padding=3), nn.BatchNorm1d(32), nn.ReLU(inplace=True), nn.MaxPool1d(kernel_size=2), ) # Multi-scale feature extraction self.multiscale_block = MultiScaleConvBlock(32, 64) self.pool1 = nn.MaxPool1d(kernel_size=2) # Enhanced residual blocks self.res_block1 = EnhancedResidualBlock1D(64, 96, use_attention=use_attention) self.pool2 = nn.MaxPool1d(kernel_size=2) self.res_block2 = EnhancedResidualBlock1D(96, 128, use_attention=use_attention) self.pool3 = nn.MaxPool1d(kernel_size=2) self.res_block3 = EnhancedResidualBlock1D(128, 160, use_attention=use_attention) # Global feature extraction self.global_pool = nn.AdaptiveAvgPool1d(1) # Calculate feature size after convolutions self.feature_size = 160 # Enhanced classifier with dropout self.classifier = nn.Sequential( nn.Linear(self.feature_size, 256), nn.BatchNorm1d(256), nn.ReLU(inplace=True), nn.Dropout(dropout_rate), nn.Linear(256, 128), nn.BatchNorm1d(128), nn.ReLU(inplace=True), nn.Dropout(dropout_rate), nn.Linear(128, 64), nn.BatchNorm1d(64), nn.ReLU(inplace=True), nn.Dropout(dropout_rate / 2), nn.Linear(64, num_classes), ) # Initialize weights self._initialize_weights() def _initialize_weights(self): """Initialize model weights using Xavier initialization.""" for m in self.modules(): if isinstance(m, nn.Conv1d): nn.init.xavier_uniform_(m.weight) if m.bias is not None: nn.init.constant_(m.bias, 0) elif isinstance(m, nn.Linear): nn.init.xavier_uniform_(m.weight) nn.init.constant_(m.bias, 0) elif isinstance(m, nn.BatchNorm1d): nn.init.constant_(m.weight, 1) nn.init.constant_(m.bias, 0) def forward(self, x): # Ensure input is 3D: [batch, channels, length] if x.dim() == 2: x = x.unsqueeze(1) # Feature extraction x = self.initial_conv(x) x = self.multiscale_block(x) x = self.pool1(x) x = self.res_block1(x) x = self.pool2(x) x = self.res_block2(x) x = self.pool3(x) x = self.res_block3(x) # Global pooling x = self.global_pool(x) x = x.view(x.size(0), -1) # Classification x = self.classifier(x) return x def get_feature_maps(self, x): """Extract intermediate feature maps for visualization.""" if x.dim() == 2: x = x.unsqueeze(1) features = {} x = self.initial_conv(x) features["initial"] = x x = self.multiscale_block(x) features["multiscale"] = x x = self.pool1(x) x = self.res_block1(x) features["res1"] = x x = self.pool2(x) x = self.res_block2(x) features["res2"] = x x = self.pool3(x) x = self.res_block3(x) features["res3"] = x return features class EfficientSpectralCNN(nn.Module): """Efficient CNN designed for real-time inference with good performance.""" def __init__(self, input_length: int = 500, num_classes: int = 2): super().__init__() # Efficient feature extraction with depthwise separable convolutions self.features = nn.Sequential( # Initial convolution nn.Conv1d(1, 32, kernel_size=7, padding=3), nn.BatchNorm1d(32), nn.ReLU(inplace=True), nn.MaxPool1d(2), # Depthwise separable convolutions self._make_depthwise_sep_conv(32, 64), nn.MaxPool1d(2), self._make_depthwise_sep_conv(64, 96), nn.MaxPool1d(2), self._make_depthwise_sep_conv(96, 128), nn.MaxPool1d(2), # Final feature extraction nn.Conv1d(128, 160, kernel_size=3, padding=1), nn.BatchNorm1d(160), nn.ReLU(inplace=True), nn.AdaptiveAvgPool1d(1), ) # Lightweight classifier self.classifier = nn.Sequential( nn.Linear(160, 64), nn.ReLU(inplace=True), nn.Dropout(0.1), nn.Linear(64, num_classes), ) self._initialize_weights() def _make_depthwise_sep_conv(self, in_channels, out_channels): """Create depthwise separable convolution block.""" return nn.Sequential( # Depthwise convolution nn.Conv1d( in_channels, in_channels, kernel_size=3, padding=1, groups=in_channels ), nn.BatchNorm1d(in_channels), nn.ReLU(inplace=True), # Pointwise convolution nn.Conv1d(in_channels, out_channels, kernel_size=1), nn.BatchNorm1d(out_channels), nn.ReLU(inplace=True), ) def _initialize_weights(self): """Initialize model weights.""" for m in self.modules(): if isinstance(m, nn.Conv1d): nn.init.kaiming_normal_(m.weight, mode="fan_out", nonlinearity="relu") if m.bias is not None: nn.init.constant_(m.bias, 0) elif isinstance(m, nn.Linear): nn.init.xavier_uniform_(m.weight) nn.init.constant_(m.bias, 0) elif isinstance(m, nn.BatchNorm1d): nn.init.constant_(m.weight, 1) nn.init.constant_(m.bias, 0) def forward(self, x): if x.dim() == 2: x = x.unsqueeze(1) x = self.features(x) x = x.view(x.size(0), -1) x = self.classifier(x) return x class HybridSpectralNet(nn.Module): """Hybrid network combining CNN and attention mechanisms.""" def __init__(self, input_length: int = 500, num_classes: int = 2): super().__init__() # CNN backbone self.cnn_backbone = nn.Sequential( nn.Conv1d(1, 64, kernel_size=7, padding=3), nn.BatchNorm1d(64), nn.ReLU(inplace=True), nn.MaxPool1d(2), nn.Conv1d(64, 128, kernel_size=5, padding=2), nn.BatchNorm1d(128), nn.ReLU(inplace=True), nn.MaxPool1d(2), nn.Conv1d(128, 256, kernel_size=3, padding=1), nn.BatchNorm1d(256), nn.ReLU(inplace=True), ) # Self-attention layer self.attention = nn.MultiheadAttention( embed_dim=256, num_heads=8, dropout=0.1, batch_first=True ) # Final pooling and classification self.global_pool = nn.AdaptiveAvgPool1d(1) self.classifier = nn.Sequential( nn.Linear(256, 128), nn.ReLU(inplace=True), nn.Dropout(0.2), nn.Linear(128, num_classes), ) def forward(self, x): if x.dim() == 2: x = x.unsqueeze(1) # CNN feature extraction x = self.cnn_backbone(x) # Prepare for attention: [batch, length, channels] x = x.transpose(1, 2) # Self-attention attn_out, _ = self.attention(x, x, x) # Back to [batch, channels, length] x = attn_out.transpose(1, 2) # Global pooling and classification x = self.global_pool(x) x = x.view(x.size(0), -1) x = self.classifier(x) return x def create_enhanced_model(model_type: str = "enhanced", **kwargs): """Factory function to create enhanced models.""" models = { "enhanced": EnhancedCNN, "efficient": EfficientSpectralCNN, "hybrid": HybridSpectralNet, } if model_type not in models: raise ValueError( f"Unknown model type: {model_type}. Available: {list(models.keys())}" ) return models[model_type](**kwargs)