--- pretty_name: MTG Commander Deck Completion tags: - magic-the-gathering - mtg - commander - edh - pytorch - recommender-system license: mit datasets: - nsaroiu/moxfield-dump --- # MTG Commander Deck Completion Given a Commander (and optionally a partial decklist), suggests which other cards belong in the deck. Trained on 189,108 real public Commander decklists from [Moxfield](https://moxfield.com) ([dataset](https://huggingface.co/datasets/nsaroiu/moxfield-dump)). Training/evaluation code: [github.com/nsaroiu/mtg-deck-completion](https://github.com/nsaroiu/mtg-deck-completion) (the tokenizer + model + training/eval pipeline that produced these weights — not the Moxfield data-collection pipeline or the web UI built on top of this model, which live elsewhere and aren't public). ## Architecture Every card gets a hybrid embedding: a learned per-card vector, summed with a small MLP over structured features (color identity, mana cost, type, keywords) and a semantic sentence embedding of its oracle text — so even a rarely-seen card starts from a meaningful representation instead of a near-random one. A deck is treated as an unordered *set*: a permutation-invariant encoder (mean-pool or self-attention) pools the known cards + commander(s) into one vector, scored against the full card vocabulary via a tied dot product (word2vec-style). Production inference is an **ensemble of two specialized checkpoints**, not one model — a single model/loss was tried and found to trade off staple recognition against synergy ranking: 1. **Synergy phase** (`set_transformer_softmax_checkpoint.pt`, self-attention encoder): generates iteratively, a few picks at a time, re-conditioning on its own prior picks — this is what surfaces specific combo/synergy pieces rather than a generic "good stuff" pile. 2. **Staple backfill** (`deepsets_softmax_checkpoint.pt`, mean-pool encoder): tops up near-universal staples (Sol Ring, Command Tower, ...) to a realistic target count, since the synergy phase alone under-recommends them. 3. **Optional pruning pass** (`prune_checkpoint_inject_lowdensity.pt`): a separate discriminator model that re-scores the assembled deck and cuts the weakest fit, the one step no generation-only model can do. ## Files | file | role | |---|---| | `deepsets_softmax_checkpoint.pt` | staple-recognition specialist | | `set_transformer_softmax_checkpoint.pt` | synergy-ranking specialist | | `prune_checkpoint_inject_lowdensity.pt` | optional deck-pruning pass | | `tokenizer.json` | card vocabulary + structured features | | `tokenizer_text_embeddings.npy` | cached oracle-text sentence embeddings | | `card_tiers.json` | staple / mid-tier / long-tail classification per card | All three checkpoints share the same card vocabulary and expect `tokenizer.json` / `tokenizer_text_embeddings.npy` alongside them. ## Evaluation Recall@50 / Precision@50 / MRR on held-out real decks, by how much of the deck is already known (mask ratio — low = mostly complete, high = mostly empty), staple-recognition checkpoint, validation split: | mask ratio | Recall@50 | Precision@50 | MRR | |---|---|---|---| | 0.1 (deck mostly complete) | 0.57 | 0.10 | 0.121 | | 0.5 | 0.48 | 0.40 | 0.066 | | 0.9 (deck mostly empty) | 0.36 | 0.54 | 0.045 | Held-out-commander split (commanders never seen in training, testing generalization via card content rather than memorized co-occurrence): Recall@50 0.29–0.44 across the same ratio range — meaningfully above chance, confirming the hybrid content embeddings carry real signal for unfamiliar commanders. Full breakdown by card-popularity tier, plus the synergy/pruning checkpoints' own numbers, in the GitHub repo's eval output. ## Usage There's no standalone pip package yet — loading these checkpoints requires the tokenizer + model code from the GitHub repo above (`tokenizer/` and `training/`; no other part of that repo is needed to run inference): ```bash git clone https://github.com/nsaroiu/mtg-deck-completion cd mtg-deck-completion pip install -r requirements.txt ``` ```python from huggingface_hub import hf_hub_download repo = "nsaroiu/mtg-deck-completion" for f in ["deepsets_softmax_checkpoint.pt", "set_transformer_softmax_checkpoint.pt", "prune_checkpoint_inject_lowdensity.pt", "tokenizer.json", "tokenizer_text_embeddings.npy", "card_tiers.json"]: hf_hub_download(repo_id=repo, filename=f, local_dir=".") from training.evaluate import load_checkpoint from training.ensemble import ensemble_complete_deck, load_tiers from training.train import pick_device device = pick_device() staple_model, tok, _ = load_checkpoint("deepsets_softmax_checkpoint.pt", device) synergy_model, _, _ = load_checkpoint("set_transformer_softmax_checkpoint.pt", device) tiers = load_tiers(tok, path="card_tiers.json") # (name, score, source) triples, source in {"synergy", "staple"} results = ensemble_complete_deck( ["Atraxa, Praetors' Voice"], [], 20, tok, device, staple_model, synergy_model, tiers, ) for name, score, source in results: print(f"{score:.3f} [{source:7}] {name}") ``` `training/ensemble.py` is also a CLI covering the same flow shown above (`--commander` — repeatable, for partner commanders — `--card`, `--chunk-size`/`--temperature` for the iterative synergy phase, `--prune-checkpoint` to enable the pruning pass). `training/complete_deck.py` exposes just one specialist checkpoint at a time, without the ensemble fusion, if that's all you need. ## Limitations - **Staple-tier ranking is imprecise in a near-empty context.** The model confidently recognizes *that* a staple belongs (~90% precision when it predicts one) well before it reliably ranks *which specific* staple is the true target — the ensemble's backfill phase exists specifically to work around this. - **Color-identity legality is not learned** — it's enforced as a deterministic post-filter (see `legal_mask` in `training/complete_deck.py`). Using the raw logits without that filter can suggest illegal cards. - **Iterative generation (the synergy phase's default mode) is an experimental setting**, tuned on a sample of 11 commanders, not exhaustively validated. - Trained only on real, currently-popular Commander decks — a Commander format power-level/meta shift (new sets, ban changes) isn't reflected until retrained on fresher data. ## License The weights and code in this repository are released under the MIT license. The training data itself is a scrape of public Moxfield decklists (see the [dataset repo](https://huggingface.co/datasets/nsaroiu/moxfield-dump), used to train this model) with no asserted license — if that matters for your use case, that's Moxfield's terms to check, not a constraint this repo imposes.