Figure Skating CNN-BiLSTM Action Classifier

A CNN-BiLSTM baseline for classifying figure-skating actions (jumps, spins, sequences) from pose-skeleton time series. Conv1D residual backbone for local temporal feature extraction, followed by a bidirectional LSTM for long-range temporal modeling.

This repo holds two checkpoints trained on the same architecture and data split, differing only in label granularity:

file label space classes
model_bilstm_fine.pt fine-grained 28 (jump rotation-count preserved, e.g. 3Lutz, 2Axel)
model_bilstm_coarse.pt coarse 11 (jump rotation-count collapsed, e.g. Lutz, Axel)

Not transformers-compatible β€” this is a plain PyTorch nn.Module + state_dict checkpoint. Model source code (SkatingBiLSTMClassifier in the project's model_bilstm.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-cnn-bilstm"
ckpt_path = hf_hub_download(repo_id, "model_bilstm_fine.pt")
ckpt = torch.load(ckpt_path, map_location="cpu", weights_only=False)

# reconstruct with the project's SkatingBiLSTMClassifier (model_bilstm.py):
# model = SkatingBiLSTMClassifier(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_bilstm_fine.pt for model_bilstm_coarse.pt to load the 11-class variant.

Config

Backbone Conv1D stem(128) β†’ ConvBlock(192,k3) β†’ ConvBlock(256,k3) β†’ ConvBlock(384,k5)
Temporal block 2-layer BiLSTM, hidden=192/direction (β†’384), dropout 0.2
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 (balanced, clipped)
Early stop patience 20 on val macro-F1, 100-epoch cap
Seed 42
Input (T=128, F=94) per-clip skeleton feature tensor (COCO-17 joints β†’ positions/velocities/angles/angular-velocities/bone-vectors)
Params 3.77M (fine) / 3.76M (coarse)

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)

Stopped epoch 51 (best @ ep31, val macro-F1 0.638)

Overall: accuracy 0.8689 Β· precision (macro/weighted) 0.5072 / 0.8800 Β· recall 0.5179 / 0.8689 Β· F1 0.5047 / 0.8697

class precision recall f1 support
3Toeloop 0.743 0.684 0.712 38
3Loop 0.800 0.952 0.870 21
2Axel 0.792 0.884 0.835 43
CamelSpin 0.967 0.952 0.960 125
SitSpin 0.969 0.962 0.966 132
UprightSpin 0.960 0.889 0.923 108
2Salchow 0.400 0.400 0.400 5
2Toeloop 0.839 0.812 0.825 32
3Salchow 0.611 0.524 0.564 21
3Axel 0.923 0.800 0.857 15
3Flip 0.531 0.722 0.612 36
3Lutz 0.800 0.390 0.525 41
NoBasic 0.781 0.806 0.794 31
2Lutz 0.571 0.571 0.571 7
4Salchow 0.500 0.333 0.400 3
4Flip 0.000 0.000 0.000 1
4Toeloop 0.200 0.333 0.250 6
4Lutz 0.000 0.000 0.000 1
4Loop 0.000 0.000 0.000 1
2Flip 0.625 0.625 0.625 8
2Loop 0.467 0.875 0.609 8
1Axel 0.750 1.000 0.857 3
1Loop 0.000 0.000 0.000 1
1Salchow 0.000 0.000 0.000 1
1Toeloop 0.000 0.000 0.000 1
1Flip 0.000 0.000 0.000 2
1Lutz 0.000 0.000 0.000 1
Sequence 0.971 0.984 0.977 307
macro avg 0.507 0.518 0.505 999
weighted avg 0.880 0.869 0.870 999

Coarse (11-class)

Stopped epoch 45 (best @ ep25, val macro-F1 0.857)

Overall: accuracy 0.9089 Β· precision (macro/weighted) 0.8557 / 0.9126 Β· recall 0.8506 / 0.9089 Β· F1 0.8506 / 0.9089

class precision recall f1 support
Axel 0.824 0.918 0.868 61
Toeloop 0.877 0.649 0.746 77
Salchow 0.833 0.833 0.833 30
Loop 0.857 0.774 0.814 31
Flip 0.627 0.681 0.653 47
Lutz 0.617 0.740 0.673 50
CamelSpin 0.984 0.984 0.984 125
SitSpin 0.977 0.962 0.969 132
UprightSpin 0.936 0.954 0.945 108
Sequence 0.981 0.990 0.985 307
NoBasic 0.900 0.871 0.885 31
macro avg 0.856 0.851 0.851 999
weighted avg 0.913 0.909 0.909 999

Notable failure modes (from confusion matrix analysis)

  • Flip ↔ Lutz is the dominant confusion (edge jumps β€” genuinely hard to distinguish from skeleton motion alone, mirrors real judging difficulty).
  • 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.
  • Spins (CamelSpin, SitSpin, UprightSpin) and Sequence are solved territory: F1 β‰₯ 0.92 in both label spaces.

License

Internal/proprietary β€” not licensed for external use. Contact the Mercity team for access terms.

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support

Collection including Mercity/figure-skating-cnn-bilstm