mtg-deck-completion / README.md
nsaroiu's picture
Upload README.md with huggingface_hub
26a35d0 verified
|
Raw History Blame Contribute Delete
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.