A genetic algorithm Twitter art bot in Clojure
I recently released a project called
generation-p: an interactive art project driven by a genetic algorithm. The interface for this project is a Twitter bot. Once per day, the bot posts a new “individual” – a 32x32 pixel image – from the genetic algorithm’s population.
This project explores the emergence of meaning from randomness. Can a population of images, which begins as noise, evolve into something worthy of attention? Retweets and favorites are a proxy for attention, and so they serve as inputs to the algorithm’s fitness function.
Over time, those images which garner more attention have a higher likelihood of being selected for reproduction, and the idea is that future generations will tend to produce more interesting images. In this way the bot is (very slowly) solving an optimization problem, where the target function is akin to attention-worthiness.
When I began work on this project, the output was textual rather than visual. I quickly pivoted to instead use images. Around early December I was looking at a lot of pixel art, and I loved how the artists were able to eek so much information (and evoke emotion) from so few colors and such little area. This project is partly inspired by pixel art, even if the results don’t look like much of anything. I think nascent patterns in images can be more quickly apparent, and reactions to them can be more immediate, and so images are well suited for this bot.
There are two crossover mechanisms at play. One method is k-point crossover, if the images were first flattened to one dimension. The other method is similar, but based on taking 2D regions from each of the parents. Also, the crossover methods and parameters are themselves inherited and undergo mutation. The selection mechanism is fitness proportional.
Below are examples of how the crossover methods control inheritance from either parent with different parameters.
Crossover with runs of length 2:
Patch crossover with patch-size 2:
The number of individuals per generation, the size of the images, the mutation rates, and other inputs are all hard-coded hyperparameters that are pretty much arbitrary and mostly un-tuned. I suspect that whatever the output converges to will probably just look like noise, since the attention-worthiness fitness function is noisy. Even if everybody who interacted with the bot had a cohesive idea of what they wanted the output to look like, the continuous macaronage of crossover might prevent any “signal” in the image from emerging. But we shall see! I like the idea of a long-running bot like this where the output will change over months and years, because it mirrors the biological evolution we observe playing out in slow, animal time.
Writing this in Clojure was a joyful experience. I like REPL-driven development a lot, and it’s especially useful when experimenting with visual output. I found my crossover implementation to be sort of inelegant, but I think that’s just because my Clojure is rusty. Overall it was easy to get into a flow state with Clojure, and I’m really happy I got to use the language again after having not written anything in it for a long time.