<jupyter_start><jupyter_text>Introduction  *What is this project?* This project is an attempt to generate cards from the trading card game Magic: The Gathering, using a recurrent neural network. The basis for the model itself is the Pokemon name generator we coded in class, though the way that data is gathered and formatted for this project is a little different. Similar projects to this have been attempted in the past:* [RoboRosewater Twitter Account](https://twitter.com/roborosewater?lang=en)* [Generating Magic cards using deep, recurrent neural networks](https://www.mtgsalvation.com/forums/magic-fundamentals/custom-card-creation/612057-generating-magic-cards-using-deep-recurrent-neural)* [MAGIC AI Generate custom Magic: The Gathering cards from an AI using GPT-2](https://minimaxir.com/apps/gpt2-mtg/)  *What is a recurrent neural network?* An RNN is a variation of a neural network that is better at keeping track of previous data and using it to make future predictions. Our inputs do not have to all be of the same form, and can instead be sequences of variable length. The key quality of an RNN is that data is fedback into the network, allowing it to memorize parts of the input, as opposed to a standard feedforward neural network where data travels linearly through the model.  *What's in a Magic: The Gathering card?* Magic: The Gathering cards come in a very wide variety. Very broadly, they can be split into two different categories: creature and non-creature cards. For this project, I've decided to use only creature cards to train the model.A Magic: The Gathering creature card has many features, however we are only concerned with the following:* Card Name* Mana Cost* Type Line* Card Text* Power/Toughness ![alt text](https://drive.google.com/uc?id=1kAojrAySim-RLTzx60kSjLdcs6AdqNPE)  Card JSON Object<jupyter_code># {
#   "object": "card",
#   "id": "4ba02df9-4ccd-4e0f-a1f2-f0f6c7fd6329",
#   "oracle_id": "5fac139a-07d3-4e6c-98e3-d98b199f7a6f",
#   "multiverse_ids": [

#   ],
#   "tcgplayer_id": 204731,
#   "name": "<NAME>",
#   "lang": "en",
#   "released_at": "2019-11-07",
#   "uri": "https://api.scryfall.com/cards/4ba02df9-4ccd-4e0f-a1f2-f0f6c7fd6329",
#   "scryfall_uri": "https://scryfall.com/card/mb1/1105/young-pyromancer?utm_source=api",
#   "layout": "normal",
#   "highres_image": true,
#   "image_uris": {
#     "small": "https://img.scryfall.com/cards/small/front/4/b/4ba02df9-4ccd-4e0f-a1f2-f0f6c7fd6329.jpg?1573512918",
#     "normal": "https://img.scryfall.com/cards/normal/front/4/b/4ba02df9-4ccd-4e0f-a1f2-f0f6c7fd6329.jpg?1573512918",
#     "large": "https://img.scryfall.com/cards/large/front/4/b/4ba02df9-4ccd-4e0f-a1f2-f0f6c7fd6329.jpg?1573512918",
#     "png": "https://img.scryfall.com/cards/png/front/4/b/4ba02df9-4ccd-4e0f-a1f2-f0f6c7fd6329.png?1573512918",
#     "art_crop": "https://img.scryfall.com/cards/art_crop/front/4/b/4ba02df9-4ccd-4e0f-a1f2-f0f6c7fd6329.jpg?1573512918",
#     "border_crop": "https://img.scryfall.com/cards/border_crop/front/4/b/4ba02df9-4ccd-4e0f-a1f2-f0f6c7fd6329.jpg?1573512918"
#   },
#   "mana_cost": "{1}{R}",
#   "cmc": 2.0,
#   "type_line": "Creature — Human Shaman",
#   "oracle_text": "Whenever you cast an instant or sorcery spell, create a 1/1 red Elemental creature token.",
#   "power": "2",
#   "toughness": "1",
#   "colors": [
#     "R"
#   ],
#   "color_identity": [
#     "R"
#   ],
#   "legalities": {
#     "standard": "not_legal",
#     "future": "not_legal",
#     "historic": "not_legal",
#     "pioneer": "legal",
#     "modern": "legal",
#     "legacy": "legal",
#     "pauper": "not_legal",
#     "vintage": "legal",
#     "penny": "not_legal",
#     "commander": "legal",
#     "brawl": "not_legal",
#     "duel": "legal",
#     "oldschool": "not_legal"
#   },
#   "games": [
#     "paper"
#   ],
#   "reserved": false,
#   "foil": false,
#   "nonfoil": true,
#   "oversized": false,
#   "promo": false,
#   "reprint": true,
#   "variation": false,
#   "set": "mb1",
#   "set_name": "<NAME>",
#   "set_type": "masters",
#   "set_uri": "https://api.scryfall.com/sets/d13bfc70-6137-4179-aa96-da30fd84de29",
#   "set_search_uri": "https://api.scryfall.com/cards/search?order=set&q=e%3Amb1&unique=prints",
#   "scryfall_set_uri": "https://scryfall.com/sets/mb1?utm_source=api",
#   "rulings_uri": "https://api.scryfall.com/cards/4ba02df9-4ccd-4e0f-a1f2-f0f6c7fd6329/rulings",
#   "prints_search_uri": "https://api.scryfall.com/cards/search?order=released&q=oracleid%3A5fac139a-07d3-4e6c-98e3-d98b199f7a6f&unique=prints",
#   "collector_number": "1105",
#   "digital": false,
#   "rarity": "uncommon",
#   "flavor_text": "Immolation is the sincerest form of flattery.",
#   "card_back_id": "0aeebaf5-8c7d-4636-9e82-8c27447861f7",
#   "artist": "<NAME>",
#   "artist_ids": [
#     "9dfbdd58-65e6-40cf-951a-80e141061939"
#   ],
#   "illustration_id": "b0e1818e-dfc6-4d26-93ff-bbb4b0d711e6",
#   "border_color": "black",
#   "frame": "2015",
#   "full_art": false,
#   "textless": false,
#   "booster": false,
#   "story_spotlight": false,
#   "edhrec_rank": 596,
#   "prices": {
#     "usd": "0.71",
#     "usd_foil": null,
#     "eur": null,
#     "tix": null
#   },
#   "related_uris": {
#     "tcgplayer_decks": "https://decks.tcgplayer.com/magic/deck/search?contains=Young+Pyromancer&page=1&partner=Scryfall&utm_campaign=affiliate&utm_medium=api&utm_source=scryfall",
#     "edhrec": "https://edhrec.com/route/?cc=Young+Pyromancer",
#     "mtgtop8": "https://mtgtop8.com/search?MD_check=1&SB_check=1&cards=Young+Pyromancer"
#   },
#   "purchase_uris": {
#     "tcgplayer": "https://shop.tcgplayer.com/product/productsearch?id=204731&partner=Scryfall&utm_campaign=affiliate&utm_medium=api&utm_source=scryfall",
#     "cardmarket": "https://www.cardmarket.com/en/Magic/Products/Search?referrer=scryfall&searchString=Young+Pyromancer&utm_campaign=card_prices&utm_medium=text&utm_source=scryfall",
#     "cardhoarder": "https://www.cardhoarder.com/cards?affiliate_id=scryfall&data%5Bsearch%5D=Young+Pyromancer&ref=card-profile&utm_campaign=affiliate&utm_medium=card&utm_source=scryfall"
#   }
# }<jupyter_output><empty_output><jupyter_text>Structuring the Data<jupyter_code>import pandas as pd
import numpy as np
pd.set_option('display.max_colwidth', None)
data_path = '/content/drive/My Drive/Datasets Drive/final-project/scryfall-oracle-cards.json'
data = pd.read_json(data_path)
print('All done')
data = data[data.set_name != 'Unglued']
data = data[data.set_name != 'Unhinged']
data = data[data.set_name != 'Unstable']
data = data[data.set_name != 'Unsanctioned']

data = data[['name', 'mana_cost', 'type_line', 'oracle_text', 'power', 'toughness', 'colors']]
data = data.drop_duplicates(subset='name', keep='first')

data = data[data['type_line'].str.contains('Creature')]
data.loc[data['name'] == 'Young Pyromancer']
def clean(field):
    return field.to_string(index=False).strip()

def construct_card(card):
    text = clean(card.name) + ' ' + clean(card.mana_cost) + '\n'
    text += clean(card.colors) + ' ' + clean(card.type_line) + '\n'
    text += clean(card.oracle_text)
    if not clean(card.power) == 'NaN' and not clean(card.toughness) == 'Nan':
        text += '\n' + clean(card.power) + '/' + clean(card.toughness)
    return text.replace('\\n', '\n')
card = data.loc[data['name'] == 'Young Pyromancer']
print(construct_card(card))
card_list = []
for name in data['name']:
    card = data.loc[data['name'] == name]
    card_list.append(construct_card(card))<jupyter_output><empty_output><jupyter_text>Training the Model<jupyter_code>from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Activation, LSTM, TimeDistributed
epochs = 100
batch_size = 64
n_layers = 2
hidden_dim = 128
min_card_len = len(min(card_list, key=len))
max_card_len = len(max(card_list, key=len))

chars = sorted(list(set([char for card in card_list for char in card])))

vocab_size = len(chars)
n = len(card_list)

ix_to_char = {ix: char for ix, char in enumerate(chars)}
char_to_ix = {char: ix for ix, char in enumerate(chars)}
X = np.zeros((n, max_card_len, vocab_size))
Y = np.zeros((n, max_card_len, vocab_size))

for i in range(n):
    chars = list(card_list[i])
    for j in range(len(chars)):
        char_ix = char_to_ix[chars[j]]
        X[i, j, char_ix] = 1
        if j > 0:
            Y[i, j - 1, char_ix] = 1
model = Sequential()
model.add(LSTM(hidden_dim,
              input_shape=(max_card_len, vocab_size),
              return_sequences=True))
for i in range(n_layers - 1):
    model.add(LSTM(hidden_dim, return_sequences=True))
model.add(TimeDistributed(Dense(vocab_size)))
model.add(Activation('softmax'))

model.compile(loss='categorical_crossentropy', optimizer='adam')
model.fit(X, Y, batch_size=batch_size, verbose=2, epochs=epochs)<jupyter_output><empty_output><jupyter_text>The Results<jupyter_code>def make_card(model):
    name = []
    X = np.zeros((1, max_card_len, vocab_size))
    end = False
    i = 0
    
    while end == False:
        probs = list(model.predict(X)[0,i])
        probs = probs / np.sum(probs)
        index = np.random.choice(range(vocab_size), p=probs)
        if i == max_card_len-2:
            character = '.'
            end = True
        else:
            character = ix_to_char[index]
        name.append(character)
        X[0, i+1, index] = 1
        i += 1
        if character == '.':
            end = True
    
    return ''.join(name)
for _ in range(10):
    print(make_card(model), '\n')<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.16236084856122662, 'fraction_numerical': 0.0444234404536862, 'mean_word_length': 4.3650704225352115, 'pattern_counts': {'":': 85, '<': 20, '<?xml version=': 0, '>': 21, 'https://': 23, 'lorem ipsum': 0, 'www.': 3, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '4', 'dir': 'jupyter-structured-clean-dedup', 'id': '215227', 'license': 'MIT', 'n_tokens_mistral': 3934, 'n_tokens_neox': 3703, 'n_words': 693, 'path': 'project.ipynb', 'repo_name': 'danereno/ML-with-MTG', 'size': '9551'}