RC1 / models.py
bxding's picture bd's picture
Release
db208b4
Raw History Blame Contribute Delete
2.56 kB
"""Small pretrained models for independent tiles and continuous grids."""
import timm
from torch import nn
BACKBONE = "mobilenetv4_conv_medium.e250_r384_in12k_ft_in1k"
class GridModel(nn.Module):
def __init__(self, tiles, classes, pretrained=True, backbone=BACKBONE):
super().__init__()
if tiles not in (9, 16):
raise ValueError("Grid size must be 9 or 16 tiles")
self.tiles = tiles
if tiles == 9:
self.backbone = timm.create_model(backbone, pretrained=pretrained)
self.backbone.reset_classifier(0)
channels = self.backbone.head_hidden_size
self.head = nn.Linear(channels, classes)
else:
# The stride-16 stage: 448 px input gives a 28x28 map, 7x7 per cell.
stage = (
timm.create_model(backbone, features_only=True).feature_info.reduction().index(16)
)
self.backbone = timm.create_model(
backbone, pretrained=pretrained, features_only=True, out_indices=(stage,)
)
channels = self.backbone.feature_info.channels()[0]
self.head = nn.Sequential(
nn.Conv2d(channels, 128, 3, padding=1),
nn.ReLU(),
nn.Conv2d(128, classes, 1),
# Max, not mean: any part of the target selects the cell.
nn.AdaptiveMaxPool2d((4, 4)),
)
def forward(self, images):
features = self.backbone(images)
return self.head(features if self.tiles == 9 else features[0])
def freeze_backbone(self, frozen):
for parameter in self.backbone.parameters():
parameter.requires_grad_(not frozen)
def select_target(logits, targets):
"""Only the requested target is supervised; other targets are unknown."""
import torch
return logits[torch.arange(logits.shape[0], device=logits.device), targets]
def transfer_backbone(model, state):
"""Copy matching backbone weights, e.g. from a tile classifier into the 4x4 feature extractor."""
source = {k.removeprefix("backbone."): v for k, v in state.items() if k.startswith("backbone.")}
target = model.backbone.state_dict()
matched = {k: v for k, v in source.items() if k in target and target[k].shape == v.shape}
missing = sorted(set(target) - set(matched))
if missing:
raise ValueError(
f"Backbone weights missing for {len(missing)} parameters, e.g. {missing[:3]}"
)
model.backbone.load_state_dict(matched)
return len(matched)