Figure Skating CTM (Continuous Thought Machine) Action Classifiers
Continuous Thought Machine architecture for classifying figure-skating actions (jumps, spins, sequences) from pose-skeleton time series: a Conv1D backbone feeding a CTM "thinking loop" (recurrent neuron model + synchronization-based attention over the backbone features), run for a fixed or adaptively-halted number of steps before classification.
This repo holds four checkpoints — two thinking-step configurations, each trained on both label granularities:
| file | variant | thinking steps | label space | classes |
|---|---|---|---|---|
model_ctm-10_fine.pt |
CTM-10 (vanilla, fixed) | exactly 10, uniform weighting | fine-grained | 28 |
model_ctm-10_coarse.pt |
CTM-10 (vanilla, fixed) | exactly 10, uniform weighting | coarse | 11 |
model_ctm-10+act_fine.pt |
CTM-10+ACT (adaptive halting) | up to 10, learned per-sample halting | fine-grained | 28 |
model_ctm-10+act_coarse.pt |
CTM-10+ACT (adaptive halting) | up to 10, learned per-sample halting | coarse | 11 |
Not transformers-compatible — plain PyTorch nn.Module + state_dict checkpoints. Model
source code (SkatingCTM in the project's model_ctm.py, which depends on ctm_components.py,
model.py, and model_gcn.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-ctm"
ckpt_path = hf_hub_download(repo_id, "model_ctm-10_fine.pt")
ckpt = torch.load(ckpt_path, map_location="cpu", weights_only=False)
# reconstruct with the project's SkatingCTM (model_ctm.py):
# model = SkatingCTM(
# ckpt["in_features"], ckpt["num_classes"],
# max_iterations=ckpt["max_iterations"], memory_length=ckpt["memory_length"],
# use_act=ckpt["use_act"], backbone=ckpt["backbone"],
# node_features=ckpt["node_features"], gcn_layers=ckpt["gcn_layers"],
# )
# 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 the filename to load any of the other three variants.
Config
Shared across all four checkpoints:
| Backbone | conv (Conv1D stem(128) → ConvBlock(192,k3) → ConvBlock(256,k3) → ConvBlock(384,k5), d_backbone=384) |
| CTM core | d_model=256, memory_length=50, n_synch=64, num_heads=8, dropout=0.1 |
| Optimizer | AdamW, lr=3e-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 | 2.18M (fine) / 2.15M (coarse) |
Where CTM-10 vs CTM-10+ACT differ:
| CTM-10 | CTM-10+ACT | |
|---|---|---|
| Thinking steps | fixed 10 every sample, uniform loss/prediction weight (1/10 each) | up to 10, learned per-sample halting (use_act=True); halt bias init −3.0 |
| ponder_lambda | 0.0 (no ACT term) | 0.01 |
| act_epsilon | n/a | 0.01 |
⚠️ Note: in both fine and coarse CTM-10+ACT runs, test-time mean thinking steps measured 10.0 — every sample used the full step budget; no sample ever halted early. With a max of 10 iterations and a halt bias initialized to favor ~20 steps, ACT never got the chance to demonstrate adaptive depth in these checkpoints — treat CTM-10+ACT here as "CTM-10 + a ponder-cost regularizer on a fixed-depth computation," not genuinely adaptive-depth inference.
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)
CTM-10 — Fine-grained (28-class)
Early-stopped epoch 99 (best val macro-F1 0.638) · test mean thinking steps: 10.0
Overall: accuracy 0.8799 · precision (macro/weighted) 0.5055 / 0.8756 · recall 0.4962 / 0.8799 · F1 0.4948 / 0.8749
| class | precision | recall | f1 | support |
|---|---|---|---|---|
| 3Toeloop | 0.692 | 0.711 | 0.701 | 38 |
| 3Loop | 0.739 | 0.810 | 0.773 | 21 |
| 2Axel | 0.771 | 0.860 | 0.813 | 43 |
| CamelSpin | 0.976 | 0.968 | 0.972 | 125 |
| SitSpin | 0.963 | 0.992 | 0.978 | 132 |
| UprightSpin | 0.953 | 0.944 | 0.949 | 108 |
| 2Salchow | 0.800 | 0.800 | 0.800 | 5 |
| 2Toeloop | 0.833 | 0.781 | 0.806 | 32 |
| 3Salchow | 0.609 | 0.667 | 0.636 | 21 |
| 3Axel | 0.714 | 0.667 | 0.690 | 15 |
| 3Flip | 0.727 | 0.444 | 0.552 | 36 |
| 3Lutz | 0.612 | 0.732 | 0.667 | 41 |
| NoBasic | 0.962 | 0.806 | 0.877 | 31 |
| 2Lutz | 0.500 | 0.714 | 0.588 | 7 |
| 4Salchow | 0.000 | 0.000 | 0.000 | 3 |
| 4Flip | 0.000 | 0.000 | 0.000 | 1 |
| 4Toeloop | 0.000 | 0.000 | 0.000 | 6 |
| 4Lutz | 0.000 | 0.000 | 0.000 | 1 |
| 4Loop | 0.000 | 0.000 | 0.000 | 1 |
| 2Flip | 0.667 | 0.250 | 0.364 | 8 |
| 2Loop | 0.667 | 0.750 | 0.706 | 8 |
| 1Axel | 0.000 | 0.000 | 0.000 | 3 |
| 1Loop | 1.000 | 1.000 | 1.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.968 | 0.997 | 0.982 | 307 |
| macro avg | 0.505 | 0.496 | 0.495 | 999 |
| weighted avg | 0.876 | 0.880 | 0.875 | 999 |
CTM-10 — Coarse (11-class)
Early-stopped epoch 96 (best val macro-F1 0.849) · test mean thinking steps: 10.0
Overall: accuracy 0.8929 · precision (macro/weighted) 0.8188 / 0.8977 · recall 0.8276 / 0.8929 · F1 0.8161 / 0.8925
| class | precision | recall | f1 | support |
|---|---|---|---|---|
| Axel | 0.966 | 0.934 | 0.950 | 61 |
| Toeloop | 0.663 | 0.714 | 0.688 | 77 |
| Salchow | 0.773 | 0.567 | 0.654 | 30 |
| Loop | 0.630 | 0.935 | 0.753 | 31 |
| Flip | 0.683 | 0.596 | 0.636 | 47 |
| Lutz | 0.690 | 0.580 | 0.630 | 50 |
| CamelSpin | 0.992 | 0.960 | 0.976 | 125 |
| SitSpin | 0.975 | 0.902 | 0.937 | 132 |
| UprightSpin | 0.963 | 0.954 | 0.958 | 108 |
| Sequence | 0.974 | 0.993 | 0.984 | 307 |
| NoBasic | 0.698 | 0.968 | 0.811 | 31 |
| macro avg | 0.819 | 0.828 | 0.816 | 999 |
| weighted avg | 0.898 | 0.893 | 0.892 | 999 |
CTM-10+ACT — Fine-grained (28-class)
Early-stopped epoch 66 (best val macro-F1 0.550) · test mean thinking steps: 10.0
Overall: accuracy 0.8559 · precision (macro/weighted) 0.4840 / 0.8645 · recall 0.5175 / 0.8559 · F1 0.4775 / 0.8573
| class | precision | recall | f1 | support |
|---|---|---|---|---|
| 3Toeloop | 0.622 | 0.737 | 0.675 | 38 |
| 3Loop | 0.650 | 0.619 | 0.634 | 21 |
| 2Axel | 0.829 | 0.791 | 0.810 | 43 |
| CamelSpin | 0.976 | 0.968 | 0.972 | 125 |
| SitSpin | 0.962 | 0.955 | 0.958 | 132 |
| UprightSpin | 0.950 | 0.889 | 0.919 | 108 |
| 2Salchow | 1.000 | 0.400 | 0.571 | 5 |
| 2Toeloop | 0.792 | 0.594 | 0.679 | 32 |
| 3Salchow | 0.565 | 0.619 | 0.591 | 21 |
| 3Axel | 0.500 | 0.733 | 0.595 | 15 |
| 3Flip | 0.564 | 0.611 | 0.587 | 36 |
| 3Lutz | 0.643 | 0.659 | 0.651 | 41 |
| NoBasic | 0.818 | 0.871 | 0.844 | 31 |
| 2Lutz | 0.667 | 0.571 | 0.615 | 7 |
| 4Salchow | 0.000 | 0.000 | 0.000 | 3 |
| 4Flip | 0.000 | 0.000 | 0.000 | 1 |
| 4Toeloop | 0.250 | 0.500 | 0.333 | 6 |
| 4Lutz | 0.000 | 0.000 | 0.000 | 1 |
| 4Loop | 0.000 | 0.000 | 0.000 | 1 |
| 2Flip | 0.800 | 0.500 | 0.615 | 8 |
| 2Loop | 0.400 | 0.500 | 0.444 | 8 |
| 1Axel | 0.000 | 0.000 | 0.000 | 3 |
| 1Loop | 0.333 | 1.000 | 0.500 | 1 |
| 1Salchow | 0.000 | 0.000 | 0.000 | 1 |
| 1Toeloop | 0.250 | 1.000 | 0.400 | 1 |
| 1Flip | 0.000 | 0.000 | 0.000 | 2 |
| 1Lutz | 0.000 | 0.000 | 0.000 | 1 |
| Sequence | 0.980 | 0.974 | 0.977 | 307 |
| macro avg | 0.484 | 0.518 | 0.477 | 999 |
| weighted avg | 0.864 | 0.856 | 0.857 | 999 |
CTM-10+ACT — Coarse (11-class)
Early-stopped epoch 57 (best val macro-F1 0.823) · test mean thinking steps: 10.0
Overall: accuracy 0.8979 · precision (macro/weighted) 0.8201 / 0.9012 · recall 0.8362 / 0.8979 · F1 0.8262 / 0.8986
| class | precision | recall | f1 | support |
|---|---|---|---|---|
| Axel | 0.917 | 0.902 | 0.909 | 61 |
| Toeloop | 0.720 | 0.766 | 0.742 | 77 |
| Salchow | 0.645 | 0.667 | 0.656 | 30 |
| Loop | 0.711 | 0.871 | 0.783 | 31 |
| Flip | 0.682 | 0.638 | 0.659 | 47 |
| Lutz | 0.717 | 0.660 | 0.688 | 50 |
| CamelSpin | 0.976 | 0.968 | 0.972 | 125 |
| SitSpin | 1.000 | 0.917 | 0.957 | 132 |
| UprightSpin | 0.934 | 0.917 | 0.925 | 108 |
| Sequence | 0.984 | 0.990 | 0.987 | 307 |
| NoBasic | 0.737 | 0.903 | 0.812 | 31 |
| macro avg | 0.820 | 0.836 | 0.826 | 999 |
| weighted avg | 0.901 | 0.898 | 0.899 | 999 |
Notable failure modes (from confusion matrix analysis, coarse label space)
- Flip ↔ Lutz is the dominant confusion in both variants — edge jumps, genuinely hard to distinguish from skeleton motion alone (mirrors real judging difficulty).
- CTM-10 (fixed) shows the field's worst
Salchowrecall (56.7%) andLutzrecall (58.0%), plus unusually strongSitSpin→NoBasicleakage (9/132) not seen in other architectures. - CTM-10+ACT improves slightly over CTM-10 on most coarse classes but never surpasses CNN-BiLSTM or GCN on macro-F1.
- Fine-grained macro-F1 in both variants 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.