Picture of small brains with arms and legs in random colors.
Deep Learning,  Machine Learning,  R

Introducing MobNet

I have been running a homebrew (i.e., designed from scratch) Dungeons & Dragons game for the last five years. This past New Year\'s Eve, my five players were victorious, saving their multiverse from certain annihilation.

I\'m excited about starting a second campaign, but was struggling to come up with new creatures to challenge and surprise them. Then I realized I could use artificial intelligence to help (special thanks to Jacqueline Nolis at SaturnCloud a demonstration using a neural net to generate pet names).

More specifically, I trained a neural network on a list of 1,368 names from existing creatures. MobNet produces names like Orze, Garez, or Wartus.

(Header image is BRAIN HUE Collection by Emilio Gracia from lapolab, CC BY-NY 2.0 )

Getting the Data

For training data, I searched online for lists of creatures for 5th Edition Dungeons & Dragons. I used the creatures published by Wizards of the Coast and the first two volumes of Tome of Beasts from Kobold Press.

I next built a lookup table to assign numbers for the 26 letters plus space, ., -, and +. Then I split the creature names into character vectors, and expanded and padded them so I had a large rectangular matrix of one-hot encoded values.

Training the Model

For the actual model training, I used keras and a five-layered neural network:

  1. Long Short-Term Memory (LSTM) layer to start learning the letter sequencing
  2. Another LSTM to learn more about the sequencing
  3. Dropout layer to prevent overfitting
  4. Dense layer to generate predictions for each character
  5. Activation (softmax) layer to ensure probabilities sum to 1

Using MobNet

Once the model was trained, it was time to use it to generate fake monster names.

I used a wrapper function to take character-by-character model predictions and transform them back into English letters using the same lookup table. All of the code is in the github repository, but here\'s that function if you can\'t wait:

generate_name <- function(model, character_lookup, max_length, temperature = 1){
  choose_next_char <- function(preds, character_lookup, temperature){
    preds <- log(preds)/temperature
    exp_preds <- exp(preds)
    preds <- exp_preds/sum(exp(preds))
    next_index <- which.max(as.integer(rmultinom(1, 1, preds)))
    character_lookup$character[next_index-1]
  }

  in_progress_name <- character(0)
  next_letter <-
  while(next_letter != + && length(in_progress_name) < 30){
    previous_letters_data <- 
      lapply(list(in_progress_name), function(.x){
        character_lookup$character_id[match(.x,character_lookup$character)]
      })
    previous_letters_data <- pad_sequences(previous_letters_data,
                                           maxlen = max_length)
    previous_letters_data <- to_categorical(previous_letters_data,
                                            num_classes = 31)

    next_letter_probabilities <- 
      predict(model,previous_letters_data)
    next_letter <- choose_next_char(next_letter_probabilities,
                                    character_lookup,
                                    temperature)
    if(next_letter != +)
      in_progress_name <- c(in_progress_name,next_letter)
  }
  raw_name <- paste0(in_progress_name, collapse=)
  capitalized_name <-gsub(\\b(\\w),\\U\\1,raw_name,perl=TRUE)
  capitalized_name
}

The next step is to wrap this all up in a Shiny app, so that anyone with an internet connection can use MobNet.