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
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%) andToeloop(87.0%) recall of all architectures tested, but by far the worstSalchowrecall (46.7%) β heavily biased toward calling Salchow "Toeloop" (11/30 test samples, the single worst confusion pair measured across every model tried). Flip β Lutzconfusion 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.