| """ |
| All five model classes used in the project. |
| |
| Each class is copied verbatim from its training notebook so that the saved |
| state dicts load without any key mismatch. Constructors default to |
| pretrained=False because the demo overwrites weights from the HF checkpoints. |
| """ |
|
|
| import timm |
| import torch |
| import torch.nn as nn |
| from torchvision import models |
|
|
|
|
| |
| |
| |
| |
|
|
| class XceptionFrameAverager(nn.Module): |
| """Run Xception on each frame independently, average the logits to get a clip score.""" |
|
|
| def __init__(self, pretrained=False): |
| super().__init__() |
| self.backbone = timm.create_model("legacy_xception", pretrained=pretrained, num_classes=1) |
|
|
| def forward(self, clip): |
| |
| batch_size, number_of_frames, channels, height, width = clip.shape |
|
|
| flat_frames = clip.view(batch_size * number_of_frames, channels, height, width) |
| per_frame_logits = self.backbone(flat_frames) |
|
|
| per_frame_logits = per_frame_logits.view(batch_size, number_of_frames, -1) |
| clip_logits = per_frame_logits.mean(dim=1) |
| return clip_logits |
|
|
|
|
| |
| |
| |
| |
|
|
| class EfficientNetFrameAverager(nn.Module): |
| """Run EfficientNet-B3 on each frame independently, average the logits to get a clip score.""" |
|
|
| def __init__(self, pretrained=False): |
| super().__init__() |
| self.backbone = timm.create_model("tf_efficientnet_b3", pretrained=pretrained, num_classes=1) |
|
|
| def forward(self, clip): |
| batch_size, number_of_frames, channels, height, width = clip.shape |
|
|
| flat_frames = clip.view(batch_size * number_of_frames, channels, height, width) |
| per_frame_logits = self.backbone(flat_frames) |
|
|
| per_frame_logits = per_frame_logits.view(batch_size, number_of_frames, -1) |
| clip_logits = per_frame_logits.mean(dim=1) |
| return clip_logits |
|
|
|
|
| |
| |
| |
| |
|
|
| class CnnLstmClipModel(nn.Module): |
| """ResNet-18 features per frame, then a 2-layer BiLSTM over the 24 frames, then a binary head.""" |
|
|
| def __init__(self, lstm_hidden_size=256, lstm_num_layers=2, lstm_dropout=0.3, pretrained=False): |
| super().__init__() |
| resnet_weights = models.ResNet18_Weights.IMAGENET1K_V1 if pretrained else None |
| resnet = models.resnet18(weights=resnet_weights) |
| self.feature_dim = resnet.fc.in_features |
| resnet.fc = nn.Identity() |
| self.cnn_feature_extractor = resnet |
|
|
| self.bilstm = nn.LSTM( |
| input_size=self.feature_dim, |
| hidden_size=lstm_hidden_size, |
| num_layers=lstm_num_layers, |
| batch_first=True, |
| bidirectional=True, |
| dropout=lstm_dropout if lstm_num_layers > 1 else 0.0, |
| ) |
|
|
| self.classifier = nn.Sequential( |
| nn.Linear(lstm_hidden_size * 2, 128), |
| nn.ReLU(), |
| nn.Dropout(0.3), |
| nn.Linear(128, 1), |
| ) |
|
|
| def forward(self, clip): |
| batch_size, number_of_frames, channels, height, width = clip.shape |
|
|
| flat_frames = clip.view(batch_size * number_of_frames, channels, height, width) |
| per_frame_features = self.cnn_feature_extractor(flat_frames) |
| per_frame_features = per_frame_features.view(batch_size, number_of_frames, self.feature_dim) |
|
|
| lstm_output, (final_hidden, _) = self.bilstm(per_frame_features) |
| |
| forward_last = final_hidden[-2] |
| backward_last = final_hidden[-1] |
| clip_representation = torch.cat([forward_last, backward_last], dim=1) |
|
|
| clip_logits = self.classifier(clip_representation) |
| return clip_logits |
|
|
|
|
| |
| |
| |
| |
|
|
| class ViTFrameAverager(nn.Module): |
| """Run ViT-Base/16 on each frame independently, average the logits to get a clip score.""" |
|
|
| def __init__(self, pretrained=False): |
| super().__init__() |
| self.backbone = timm.create_model("vit_base_patch16_224", pretrained=pretrained, num_classes=1) |
|
|
| def forward(self, clip): |
| batch_size, number_of_frames, channels, height, width = clip.shape |
|
|
| flat_frames = clip.view(batch_size * number_of_frames, channels, height, width) |
| per_frame_logits = self.backbone(flat_frames) |
|
|
| per_frame_logits = per_frame_logits.view(batch_size, number_of_frames, -1) |
| clip_logits = per_frame_logits.mean(dim=1) |
| return clip_logits |
|
|
|
|
| |
| |
| |
| |
|
|
| class HybridCNNTransformer(nn.Module): |
| """EfficientNet-B3 per-frame backbone, then a Transformer encoder over the |
| frame sequence with a CLS token.""" |
|
|
| def __init__( |
| self, |
| pretrained=False, |
| number_of_frames=24, |
| number_of_transformer_layers=4, |
| number_of_heads=8, |
| transformer_feedforward_dimensions=2048, |
| dropout=0.1, |
| ): |
| super().__init__() |
|
|
| |
| |
| self.backbone = timm.create_model( |
| "tf_efficientnet_b3", pretrained=pretrained, num_classes=0 |
| ) |
| feature_dimensions = self.backbone.num_features |
|
|
| self.cls_token = nn.Parameter(torch.zeros(1, 1, feature_dimensions)) |
| nn.init.trunc_normal_(self.cls_token, std=0.02) |
|
|
| self.positional_embeddings = nn.Parameter( |
| torch.zeros(1, number_of_frames + 1, feature_dimensions) |
| ) |
| nn.init.trunc_normal_(self.positional_embeddings, std=0.02) |
|
|
| encoder_layer = nn.TransformerEncoderLayer( |
| d_model=feature_dimensions, |
| nhead=number_of_heads, |
| dim_feedforward=transformer_feedforward_dimensions, |
| dropout=dropout, |
| activation="gelu", |
| batch_first=True, |
| norm_first=True, |
| ) |
| self.transformer_encoder = nn.TransformerEncoder( |
| encoder_layer, num_layers=number_of_transformer_layers |
| ) |
| self.transformer_norm = nn.LayerNorm(feature_dimensions) |
|
|
| self.classifier = nn.Sequential( |
| nn.Linear(feature_dimensions, feature_dimensions // 2), |
| nn.ReLU(inplace=True), |
| nn.Dropout(dropout), |
| nn.Linear(feature_dimensions // 2, 1), |
| ) |
|
|
| def forward(self, clip): |
| batch_size, number_of_frames, channels, height, width = clip.shape |
|
|
| flat_frames = clip.view(batch_size * number_of_frames, channels, height, width) |
| frame_features = self.backbone(flat_frames) |
| frame_features = frame_features.view(batch_size, number_of_frames, -1) |
|
|
| cls_tokens = self.cls_token.expand(batch_size, -1, -1) |
| sequence = torch.cat([cls_tokens, frame_features], dim=1) |
| sequence = sequence + self.positional_embeddings |
|
|
| sequence = self.transformer_encoder(sequence) |
| sequence = self.transformer_norm(sequence) |
| cls_output = sequence[:, 0] |
| clip_logits = self.classifier(cls_output) |
| return clip_logits |
|
|