| --- |
| license: other |
| license_name: proprietary-internal |
| license_link: LICENSE |
| library_name: pytorch |
| tags: |
| - figure-skating |
| - action-recognition |
| - skeleton-based |
| - time-series-classification |
| - transformer |
| - pytorch |
| --- |
| |
| # Figure Skating Transformer Action Classifier |
|
|
| A Conv1D backbone + Transformer-encoder baseline for classifying figure-skating actions (jumps, |
| spins, sequences) from pose-skeleton time series. Sinusoidal positional encoding + a real |
| 3-layer `nn.TransformerEncoder` (self-attn + FFN per layer) on top of the same Conv1D residual |
| backbone used across this model family. |
|
|
| This repo holds **two checkpoints** trained on the same architecture and data split, differing |
| only in label granularity: |
|
|
| | file | label space | classes | |
| |---|---|---| |
| | `model_transformer_fine.pt` | fine-grained | 28 (jump rotation-count preserved, e.g. `3Lutz`, `2Axel`) | |
| | `model_transformer_coarse.pt` | coarse | 11 (jump rotation-count collapsed, e.g. `Lutz`, `Axel`) | |
|
|
| Not `transformers`-compatible in the HF sense — this is a plain PyTorch `nn.Module` + |
| `state_dict` checkpoint (custom architecture, not a Hub `AutoModel`). Model source code |
| (`SkatingActionClassifier` in the project's `model_transformers.py`, which depends on |
| `model.py`) is **not included in this repo** — you'll need the project source to reconstruct |
| the class before loading the state dict. |
|
|
| ## How to load |
|
|
| ```python |
| from huggingface_hub import hf_hub_download |
| import torch |
| |
| repo_id = "Mercity/figure-skating-transformer" |
| ckpt_path = hf_hub_download(repo_id, "model_transformer_fine.pt") |
| ckpt = torch.load(ckpt_path, map_location="cpu", weights_only=False) |
| |
| # reconstruct with the project's SkatingActionClassifier (model_transformers.py): |
| # model = SkatingActionClassifier(ckpt["in_features"], ckpt["num_classes"]) |
| # model.load_state_dict(ckpt["state_dict"]) |
| # model.eval() |
| |
| # ckpt also carries the standardization stats used at train time and the taxonomy: |
| mu, sd = ckpt["feature_mean"], ckpt["feature_std"] # normalize inputs with (x - mu) / sd |
| taxonomy = ckpt["taxonomy"] # {0: "3Toeloop", 1: "3Loop", ...} |
| ``` |
|
|
| Swap `model_transformer_fine.pt` for `model_transformer_coarse.pt` to load the 11-class variant. |
|
|
| ## Config |
|
|
| | | | |
| |---|---| |
| | Backbone | Conv1D stem(128) → ConvBlock(192,k3) → ConvBlock(256,k3) → ConvBlock(384,k5) | |
| | Temporal block | Sinusoidal positional encoding + `nn.TransformerEncoder`, 3 layers, d_model=384, nhead=8, dim_feedforward=1536 (4×d_model), dropout=0.1, GELU, post-norm | |
| | Head | Dense(384→1024,p0.5) → Dense(1024→512,p0.4) → Dense(512→256,p0.3) → LayerNorm(256) → Linear(256→num_classes) | |
| | Optimizer | Adam, lr=5e-4, weight_decay=1e-4 | |
| | Batch size | 64 | |
| | Grad clip | 1.0 | |
| | Class weight cap | 5.0 | |
| | Early stop | patience 20 on val macro-F1, 100-epoch cap | |
| | Seed | 42 | |
| | Params | 7.31M (both) | |
| |
| ## Data |
| |
| Trained on skeleton-extracted figure-skating clips (YOLO11n-pose, COCO-17 joints, imgsz=384) |
| from an internal Mercity dataset. Stratified train/val/test split, seed 42. |
| **Not included in this repo** (private internal dataset). |
| |
| ## Results (test split) |
| |
| ### Fine-grained (28-class) |
| Early-stopped epoch 42 (best val macro-F1 0.572) |
| |
| **Overall:** accuracy 0.8348 · precision (macro/weighted) 0.4511 / 0.8364 · recall 0.5129 / 0.8348 · **F1 0.4642 / 0.8287** |
| |
| | class | precision | recall | f1 | support | |
| |---|---|---|---|---| |
| | 3Toeloop | 0.514 | 0.500 | 0.507 | 38 | |
| | 3Loop | 0.607 | 0.810 | 0.694 | 21 | |
| | 2Axel | 0.919 | 0.791 | 0.850 | 43 | |
| | CamelSpin | 0.991 | 0.912 | 0.950 | 125 | |
| | SitSpin | 0.969 | 0.955 | 0.962 | 132 | |
| | UprightSpin | 0.934 | 0.917 | 0.925 | 108 | |
| | 2Salchow | 0.286 | 0.400 | 0.333 | 5 | |
| | 2Toeloop | 0.658 | 0.781 | 0.714 | 32 | |
| | 3Salchow | 0.381 | 0.381 | 0.381 | 21 | |
| | 3Axel | 0.867 | 0.867 | 0.867 | 15 | |
| | 3Flip | 0.538 | 0.389 | 0.452 | 36 | |
| | 3Lutz | 0.667 | 0.244 | 0.357 | 41 | |
| | NoBasic | 0.757 | 0.903 | 0.824 | 31 | |
| | 2Lutz | 0.214 | 0.429 | 0.286 | 7 | |
| | 4Salchow | 0.200 | 0.333 | 0.250 | 3 | |
| | 4Flip | 0.000 | 0.000 | 0.000 | 1 | |
| | 4Toeloop | 0.200 | 0.167 | 0.182 | 6 | |
| | 4Lutz | 0.000 | 0.000 | 0.000 | 1 | |
| | 4Loop | 0.000 | 0.000 | 0.000 | 1 | |
| | 2Flip | 0.333 | 0.500 | 0.400 | 8 | |
| | 2Loop | 0.500 | 0.750 | 0.600 | 8 | |
| | 1Axel | 0.333 | 0.333 | 0.333 | 3 | |
| | 1Loop | 0.500 | 1.000 | 0.667 | 1 | |
| | 1Salchow | 0.000 | 0.000 | 0.000 | 1 | |
| | 1Toeloop | 0.333 | 1.000 | 0.500 | 1 | |
| | 1Flip | 0.000 | 0.000 | 0.000 | 2 | |
| | 1Lutz | 0.000 | 0.000 | 0.000 | 1 | |
| | Sequence | 0.930 | 1.000 | 0.964 | 307 | |
| | **macro avg** | **0.451** | **0.513** | **0.464** | 999 | |
| | **weighted avg** | **0.836** | **0.835** | **0.829** | 999 | |
| |
| ### Coarse (11-class) |
| Early-stopped epoch 61 (best val macro-F1 0.860) |
| |
| **Overall:** accuracy 0.8989 · precision (macro/weighted) 0.8390 / 0.8989 · recall 0.8158 / 0.8989 · **F1 0.8233 / 0.8970** |
| |
| | class | precision | recall | f1 | support | |
| |---|---|---|---|---| |
| | Axel | 0.879 | 0.951 | 0.913 | 61 | |
| | Toeloop | 0.728 | 0.870 | 0.793 | 77 | |
| | Salchow | 0.737 | 0.467 | 0.571 | 30 | |
| | Loop | 0.793 | 0.742 | 0.767 | 31 | |
| | Flip | 0.714 | 0.638 | 0.674 | 47 | |
| | Lutz | 0.750 | 0.720 | 0.735 | 50 | |
| | CamelSpin | 0.976 | 0.976 | 0.976 | 125 | |
| | SitSpin | 0.976 | 0.917 | 0.945 | 132 | |
| | UprightSpin | 0.871 | 0.935 | 0.902 | 108 | |
| | Sequence | 0.977 | 0.984 | 0.981 | 307 | |
| | NoBasic | 0.828 | 0.774 | 0.800 | 31 | |
| | **macro avg** | **0.839** | **0.816** | **0.823** | 999 | |
| | **weighted avg** | **0.899** | **0.899** | **0.897** | 999 | |
| |
| ## Notable failure modes (from confusion matrix analysis, coarse label space) |
| |
| - Best `Axel` (95.1%) and `Toeloop` (87.0%) recall of all architectures tested, but by far the |
| **worst `Salchow` recall (46.7%)** — heavily biased toward calling Salchow "Toeloop" |
| (11/30 test samples, the single worst confusion pair measured across every model tried). |
| - `Flip ↔ Lutz` confusion present but less severe than in other architectures. |
| - Fine-grained macro-F1 is dragged down almost entirely by 1-2-sample tail classes |
| (`4Flip`, `4Lutz`, `4Loop`, `1Salchow`, `1Toeloop`, `1Flip`, `1Lutz`) — a data scarcity issue, |
| not an architecture weakness. |
| |
| ## License |
| |
| Internal/proprietary — not licensed for external use. Contact the Mercity team for access terms. |
| |