6 min read

Digital Oracle

You can find the final product with some simple instructions to spin it up on my github.
Digital Oracle
The Fool in ASCII.

Digital Oracle: ML-generated Tarot for Computers

Create your own cards here. You can find the code with some simple instructions to spin it up on my github.

In creating an oracle deck, the questions I was most interested in answering were what it might look like for a computer to be both querent and reader. I spent a while brainstorming with pen and paper on what this might look like.

I decided to start by focusing on computer as querent. What would a computer ask? How would it act on a reading? Can a computer have any degree of agency in this process?

It seems to me that the closest thing to relationships, or life events, or anything one might ask a tarot reader about that a computer has are its processes. So I decided to write a script that would lead the computer to act on its reading by either starting or killing a process, dependent on whether the reading was determined to be negative or positive. Since I wanted to have a “computer” do the reading as well, I decided to include sentiment analysis.

Designing The Reading

I started by writing a simple node script to pull data for three random cards from ekelen’s helpful tarot API, then ran that through NPM’s sentiment analysis package to determine whether the reading was positive or negative.

Sample output for the Tarot API.

From there, I picked a random process using psList and killed it if the reading’s rating was below a certain sentiment score.

This whole process went pretty quickly, so I started thinking about actually making my own oracle deck. What I’d done thus far was a digital reading, but was still reliant on an existent deck.

Creating The Deck

I started by toying with the idea of translating the Rider-Waite deck into ASCII art. I found some beautiful JPGs of the original deck on a Japanese blog, and attempted turning them into ASCII using TEXT-IMAGE.com.

You can find some sample images in the cards folder in in my github repo, but after trying this for a bit I decided that it was (a) extremely tedious and (b) not really a computer-generated card — I was just re-rendering something that a person had made.

Test AI-generated images for the Seven of Wands description shown above.

From there, I decided to try generating card images with DeepAI’s text2img API. You can find the script I used in the github repo under deepai_script.js. While testing on their website got me some interesting results, I was unable to get the API to work with me to create images in bulk.

Having a bad time with the DeepAi API

This seemed like a non-starter, and besides, on further thought, having an image for a computer oracle deck seemed unnecessary. It’s not like the computer knows the difference — it’s all data.

Having come to this conclusion, I went back to thinking about what constitutes an oracle deck, specifically in the context of a computer as both reader and querent. If it’s all data to a computer, then an oracle deck is basically just a file with discrete data for each card. This led me to the decision to generate cards as a JSON file of titles and descriptions. But how could I make them computer-created?

I’d been itching to play around with ml5.js, so I decided to use their char-rnn LSTM to generate my cards. This ended up being the most time-consuming part of the whole process — I’d worked with tensorflow before, but limiting myself to working within the browser ended up being a much bigger hassle than I expected.

The hated CORS strikes again.

I followed their LSTM training tutorial with data pulled from the Tarot API for meanings, titles, and descriptions. But when I attempted to run the models in the browser, I ran into a bunch of dreaded CORS violations.

I had been trying desperately to avoid incorporating a server, but I ended up having to compromise and use a simple node http-server.

After this nightmare, I was able to run the model in the browser, but predictably found that the small amount of text data I had pulled from the Tarot API wasn’t enough to generate robust cards.

Some sample cards generated and logged in the browser.

I decided to train a new model with a larger dataset, so I pulled the .txt file of Mark McElroy’s A Guide To Tarot Meanings, trimmed all its extraneous information and punctuation, and trained a new model with it.

A MUUUUCH bigger model.

After playing around with this, I settled on a card model for the final product: I’d keep the title generation model pulled from the titles of the original 78 cards, while using the model from McElroy’s guide for their descriptions. Although the title dataset was small, it generated something closer to a sensible title than the guide did, and besides I usually found the nonsensical titles it generated to be funny. I used a seed of “the” for the title generation to get something closer to a real title than I might otherwise. This led us to a model generating weird cards like “The Moand The Hen of Pentac” and “The The High of Wands Sie”.

(Accidentally) Making A Web App

I decided I was ready to generate a full deck, but discovered that simply running a script to generate 78 cards in the browser basically just crashed my browser after about twenty cards, and also produced completely nonsensical cards (I’m guessing the char-rnn has trouble running in the browser if you attempt to generate multiple samples at a time).

Some nonsense strings generated when I was attempting to make all the cards at once.

As I mentioned above, I’d been hoping to avoid the browser entirely and stick to scripting, but by wedding myself to ml5 I found myself forced to go back to my front-end developer roots and make an actual interactive frontend to generate the cards. I would generate a card on button press, store it to an array of cards, and allow the user to download them at their discretion. Basically, I’d wait for the rnn to finish generating a new card before creating another one.

This was also extremely tedious, and I still ran into the problem of the RNN slowing down/churning out nonsense when I hit about 20 cards, so I did the process to about ten cards myself several times and then combined all the generated JSON files into one 31-card deck. You can find this in the cards folder of the repo as rnn_cards.json.

Once I’d generated the cards, I made a new version of my original reading script that would draw three cards from the JSON file, then act on them accordingly. I couldn’t figure out a good way to get all possible processes that *aren’t* running, so I was unable to get it to start a random process. Instead, I made it a little more interesting by filtering processes by impact on system memory. If the reading has a positive score, it will pick a random process that has impact on system memory, it runs its start command and log it — trying to make the script “cherish” an otherwise insignificant process. If the reading has a negative score, it picks a random process impacting system memory and kills it — unloading negative baggage on the system.

Reflections

I’m not sure if what I ended up with accomplishes my goal of figuring out computer as querent or computer as reader, but I did learn a lot in the process, and I’d like to think that at the very least the rnn-based Tarot Card generator could be fun for someone else to use in the future.

My initial interest in figuring out computer agency, or what questions a computer might ask, still feel unanswered, but I think by toying with what I’ve built further and digging deeper into the relationship of a computer with its running processes I might find a response that satisfies me.