LexHo's picture
a3v21_s42: frozen weights (S-fast + recording FiLM), predict.py, submission.csv (official 0.26306)
1b0d082 verified
Raw History Blame Contribute Delete
3.45 kB
#!/usr/bin/env python3
"""UnifiedIMUNet -- dilated 1-D ResNet with attention pooling.
Architecture ported verbatim from sample_code/ts-tcmpio-unified-temporal-resnet.ipynb
(cell 15). Single shared trunk, one velocity head; the platform head is an
auxiliary training-time signal only and is not used at inference, so the model
stays a single unified network with no external routing.
"""
from __future__ import annotations
import torch
import torch.nn as nn
def make_norm(kind: str, channels: int) -> nn.Module:
"""'bn' BatchNorm1d; 'gn' GroupNorm with 8 groups -- statistics per sample,
so a mixed-platform batch no longer normalises a hovering drone's features
against a running car's."""
if kind == "bn":
return nn.BatchNorm1d(channels)
if kind == "gn":
return nn.GroupNorm(min(8, channels), channels)
raise ValueError(f"norm must be bn or gn, got {kind!r}")
class ResidualBlock(nn.Module):
def __init__(self, in_channels, out_channels, stride=1, dilation=1, norm="bn"):
super().__init__()
padding = 2 * dilation
self.main = nn.Sequential(
nn.Conv1d(in_channels, out_channels, 5, stride=stride, padding=padding,
dilation=dilation, bias=False),
make_norm(norm, out_channels),
nn.GELU(),
nn.Conv1d(out_channels, out_channels, 5, padding=padding, dilation=dilation, bias=False),
make_norm(norm, out_channels),
)
self.skip = (nn.Conv1d(in_channels, out_channels, 1, stride=stride, bias=False)
if stride != 1 or in_channels != out_channels else nn.Identity())
self.activation = nn.GELU()
def forward(self, x):
return self.activation(self.main(x) + self.skip(x))
class UnifiedIMUNet(nn.Module):
def __init__(self):
super().__init__()
self.encoder = nn.Sequential(
nn.Conv1d(6, 64, 7, padding=3, bias=False), nn.BatchNorm1d(64), nn.GELU(),
ResidualBlock(64, 64),
ResidualBlock(64, 96, stride=2),
ResidualBlock(96, 96, dilation=2),
ResidualBlock(96, 128, stride=2, dilation=2),
ResidualBlock(128, 128, dilation=4),
ResidualBlock(128, 192, stride=2, dilation=4),
ResidualBlock(192, 192, dilation=4),
)
self.attention = nn.Conv1d(192, 1, 1)
self.shared = nn.Sequential(nn.LayerNorm(576), nn.Linear(576, 256), nn.GELU(), nn.Dropout(0.2))
self.velocity_head = nn.Linear(256, 3)
self.platform_head = nn.Linear(256, 4)
def features_and_heads(self, x):
"""(N, 6, 200) -> (feature (N, 256), velocity (N, 3), platform_logits (N, 4)).
Auxiliary heads need the shared feature, which forward() discards.
"""
x = self.encoder(x)
weights = torch.softmax(self.attention(x), dim=-1)
features = torch.cat([(x * weights).sum(-1), x.mean(-1), x.std(-1, unbiased=False)], dim=1)
features = self.shared(features)
return features, self.velocity_head(features), self.platform_head(features)
def forward(self, x):
_, velocity, platform_logits = self.features_and_heads(x)
return velocity, platform_logits
def vector_huber(prediction, target, beta=0.25):
error = torch.linalg.vector_norm(prediction - target, dim=1)
return torch.where(error < beta, 0.5 * error.square() / beta, error - 0.5 * beta).mean()