|
Download README.md from nsaroiu/mtg-deck-completion: direct link, hf CLI and curl.
- Browser
- Download file 6.8 kB
-
https://huggingface.co/nsaroiu/mtg-deck-completion/resolve/main/README.md
- Command line
-
hf download hf://nsaroiu/mtg-deck-completion/README.md
-
curl -L -o README.md https://huggingface.co/nsaroiu/mtg-deck-completion/resolve/main/README.md
6.8 kB
| 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. | |