Download code/experiments/exp8_v2_strong_unified_encoder.py from luciayen/afrisign-encoder-best-models: direct link, hf CLI and curl.
- Browser
- Download file 6.36 kB
-
https://huggingface.co/luciayen/afrisign-encoder-best-models/resolve/main/code/experiments/exp8_v2_strong_unified_encoder.py
- Command line
-
hf download hf://luciayen/afrisign-encoder-best-models/code/experiments/exp8_v2_strong_unified_encoder.py
-
curl -L -o exp8_v2_strong_unified_encoder.py https://huggingface.co/luciayen/afrisign-encoder-best-models/resolve/main/code/experiments/exp8_v2_strong_unified_encoder.py
6.36 kB
| #!/usr/bin/env python | |
| """Experiment 8-v2: stronger unified RGB+pose AfriSign encoder. | |
| This file reuses the Exp8 data pipeline and training loop, but swaps in a | |
| stronger shared encoder: | |
| - temporal convolutional pose stem before the Transformer | |
| - language/modality/task-conditioned residual adapters | |
| - stronger projection head for supervised contrastive learning | |
| It is intentionally a thin wrapper around `exp8_unified_mixed_encoder.py` so the | |
| same manifests, loaders, metrics, and aggregation scripts remain compatible. | |
| """ | |
| from __future__ import annotations | |
| import sys | |
| from pathlib import Path | |
| import torch | |
| import torch.nn as nn | |
| ROOT = Path(__file__).resolve().parents[1] | |
| sys.path.insert(0, str(ROOT)) | |
| from experiments import exp8_unified_mixed_encoder as exp8 # noqa: E402 | |
| class TemporalPoseStem(nn.Module): | |
| """TransSLR-style temporal stem for landmark sequences.""" | |
| def __init__(self, feature_dim: int, hidden_dim: int, dropout: float) -> None: | |
| super().__init__() | |
| self.in_proj = nn.Linear(feature_dim, hidden_dim) | |
| self.blocks = nn.ModuleList( | |
| [ | |
| nn.Sequential( | |
| nn.LayerNorm(hidden_dim), | |
| nn.Conv1d(hidden_dim, hidden_dim, kernel_size=5, padding=2, groups=hidden_dim), | |
| nn.GELU(), | |
| nn.Conv1d(hidden_dim, hidden_dim, kernel_size=1), | |
| nn.Dropout(dropout), | |
| ) | |
| for _ in range(3) | |
| ] | |
| ) | |
| self.out_norm = nn.LayerNorm(hidden_dim) | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| x = self.in_proj(x) | |
| for block in self.blocks: | |
| y = block[0](x).transpose(1, 2) | |
| y = block[1](y) | |
| y = block[2](y) | |
| y = block[3](y).transpose(1, 2) | |
| y = block[4](y) | |
| x = x + y | |
| return self.out_norm(x) | |
| class ConditionalAdapter(nn.Module): | |
| """Small residual adapter conditioned by language/modality/level/task context.""" | |
| def __init__(self, hidden_dim: int, bottleneck: int, dropout: float) -> None: | |
| super().__init__() | |
| self.norm = nn.LayerNorm(hidden_dim) | |
| self.down = nn.Linear(hidden_dim, bottleneck) | |
| self.up = nn.Linear(bottleneck, hidden_dim) | |
| self.drop = nn.Dropout(dropout) | |
| self.gate = nn.Sequential(nn.Linear(hidden_dim, hidden_dim), nn.Sigmoid()) | |
| def forward(self, x: torch.Tensor, context: torch.Tensor) -> torch.Tensor: | |
| update = self.up(torch.nn.functional.gelu(self.down(self.norm(x)))) | |
| gate = self.gate(context).unsqueeze(1) | |
| return x + self.drop(update * gate) | |
| class StrongUnifiedAfriSignEncoder(nn.Module): | |
| def __init__( | |
| self, | |
| *, | |
| task_dims: dict[str, int], | |
| num_languages: int, | |
| hidden_dim: int, | |
| feature_dim: int, | |
| max_tokens: int, | |
| layers: int, | |
| heads: int, | |
| ff_dim: int, | |
| dropout: float, | |
| rgb_backbone: str, | |
| rgb_train_backbone: str, | |
| rgb_pretrained: bool, | |
| ) -> None: | |
| super().__init__() | |
| self.task_keys = list(task_dims) | |
| self.pose_stem = TemporalPoseStem(feature_dim, hidden_dim, dropout) | |
| if rgb_backbone == "small_cnn": | |
| self.rgb_cnn = exp8.SmallFrameCNN(hidden_dim) | |
| elif rgb_backbone == "efficientnet_b0": | |
| self.rgb_cnn = exp8.EfficientNetB0FrameEncoder(hidden_dim, rgb_train_backbone, rgb_pretrained) | |
| else: | |
| raise ValueError(f"Unknown rgb_backbone={rgb_backbone!r}") | |
| self.cls = nn.Parameter(torch.zeros(1, 1, hidden_dim)) | |
| self.pos = nn.Embedding(max_tokens + 1, hidden_dim) | |
| self.lang_emb = nn.Embedding(num_languages, hidden_dim) | |
| self.modality_emb = nn.Embedding(len(exp8.MODALITY_IDS), hidden_dim) | |
| self.level_emb = nn.Embedding(len(exp8.LEVEL_IDS), hidden_dim) | |
| self.task_emb = nn.Embedding(len(self.task_keys), hidden_dim) | |
| self.pre_adapter = ConditionalAdapter(hidden_dim, max(32, hidden_dim // 4), dropout) | |
| enc_layer = nn.TransformerEncoderLayer( | |
| d_model=hidden_dim, | |
| nhead=heads, | |
| dim_feedforward=ff_dim, | |
| dropout=dropout, | |
| batch_first=True, | |
| norm_first=True, | |
| ) | |
| self.encoder = nn.TransformerEncoder(enc_layer, layers, enable_nested_tensor=False) | |
| self.post_adapter = ConditionalAdapter(hidden_dim, max(32, hidden_dim // 4), dropout) | |
| self.norm = nn.LayerNorm(hidden_dim) | |
| self.drop = nn.Dropout(dropout) | |
| self.heads = nn.ModuleDict({key: nn.Linear(hidden_dim, dim) for key, dim in task_dims.items()}) | |
| self.projector = nn.Sequential( | |
| nn.Linear(hidden_dim, hidden_dim), | |
| nn.GELU(), | |
| nn.LayerNorm(hidden_dim), | |
| nn.Linear(hidden_dim, hidden_dim), | |
| ) | |
| nn.init.trunc_normal_(self.cls, std=0.02) | |
| def encode(self, batch: dict[str, torch.Tensor]) -> torch.Tensor: | |
| if "pose" in batch: | |
| x = self.pose_stem(batch["pose"]) | |
| elif "rgb" in batch: | |
| rgb = batch["rgb"] | |
| b, t, c, h, w = rgb.shape | |
| x = self.rgb_cnn(rgb.reshape(b * t, c, h, w)).reshape(b, t, -1) | |
| else: | |
| raise ValueError("Batch must contain pose or rgb") | |
| b, t, _ = x.shape | |
| positions = torch.arange(t + 1, device=x.device) | |
| context = ( | |
| self.lang_emb(batch["lang_idx"]) | |
| + self.modality_emb(batch["modality_idx"]) | |
| + self.level_emb(batch["level_idx"]) | |
| + self.task_emb(batch["task_idx"]) | |
| ) | |
| x = torch.cat([self.cls.expand(b, -1, -1), x], dim=1) | |
| x = x + self.pos(positions).unsqueeze(0) + context.unsqueeze(1) | |
| x = self.pre_adapter(x, context) | |
| x = self.encoder(x) | |
| x = self.post_adapter(x, context) | |
| return self.drop(self.norm(x[:, 0])) | |
| def forward(self, batch: dict[str, torch.Tensor], task_key: str) -> torch.Tensor: | |
| return self.heads[task_key](self.encode(batch)) | |
| def contrast_features(self, features: torch.Tensor) -> torch.Tensor: | |
| return torch.nn.functional.normalize(self.projector(features), dim=1) | |
| def main() -> None: | |
| exp8.UnifiedAfriSignEncoder = StrongUnifiedAfriSignEncoder | |
| exp8.main() | |
| if __name__ == "__main__": | |
| main() | |