TL;DR: Why painstakingly craft color pallettes by hand when I can train an AI to learn how to infinitely replicate palettes I like? I did, and with Khroma anyone can.

Background

Early summer of 2016, after months of dabbling and skimming, I decided that I was really going to try to "get into" machine learning. Despite having no experience in computer science and lackuster high school math grades, I couldn't help but find ML facinating. Not long after poking around, I came across synaptic.js. It's a neural network library written in javascript for node and the browser - something I could wrap my head around. After reading the docs and fiddling a little bit, I had it ready to go - all I needed was something to do with it.

I decided to train basic sigmoid model (outputs a probability between 0 and 1) — basically hotdog/not hotdog. Looking at how synaptic's model accepted training data, I first thought of rgb color values. At the time I was making a lot of demos in codepen.io and trying to make landing pages for some JS libraries I’d made. I was struggling to find good color palettes to use. Could I train a neural network to predict whether or not I like a color?

I made a quick voting app that displayed a full-screen color for which a user could vote like or dislike. I voted hundreds, maybe thousands of times and got some (generous) colleagues do it too. I ran the data through a feed-forward neural net and brute force generated blocks of colors when they reached a high enough threshold. The results were surpsisingly good but the colors had no relation to one another. Ultimately the voting was a tedious slog (I'd realize later I was doing this all wrong). Excited by some success, I quickly moved on to more complex challenges like predicting bitcoin prices (fail), achieving perfect standard play blackjack (pass), next character generation (fail), generating images (?), and more. I spent a lot of time being frustrated by limited knowledge, limited laptop compute power, and the limitations of the library.

102 nice handmade color combinations. I've been convinced that desipite conventional opinion, you shouldn't need more than 2 colors for the majority of web design (not including success/warning etc).

The concept is Tobias, a well known designer, was asked about his color workflow often enough that he created a public collection of palettes he'd created and saved over the years. I loved how the colors had context with typography. It felt useful. This got me to take a second look at the color net.

Why painstakingly craft color pallettes by hand when I can train an AI to learn how to infinitely replicate palettes I like?

If the machine knows what I like, why not make it do the hard work? That's when I started building Khroma.

So what's the "AI"?

Getting the results I wanted took countless hours of experimenting, researching, tweaking, and simply waiting. I tried training the model with every color space I could (rgb, hsv, hsl, lab, yuv). Then there was was question of how much and what kind of data to use. Do people need to vote yes and no? There are around 16M rgb colors - do I need 100, 500, 1000 votes? After months of trial and error, I found a satisfactory solution. By choosing 50 well balanced colors (likes only), Khroma uses the deltaE color distance algorithm to extrapolate 9000 dislikes and likes at a 5:1 ratio, which then trains a local feed-forward neural network. Training in the browser typically takes a little over 2 minutes with a training accuracy between 96-99%. Not too bad.

We now have an algorithm that, when given a random color, can tell whether or not you'll like it with 96-99% accuracy. When I tested it with people, they often asked "how are they paired together?" The answer was "randomly", which didn't feel like a strong response.

Let me just note here that I wanted to steer away from color theory, as I don't think our perception and the theory always align. Ever seen a beautiful red/green gradient?

Khroma has two other ML algorithms, one for matching two colors together and another for matching four together (people wanted the option to see more than two colors, I agreed). They're both trained from a sample of 2000+ popular color palettes I managed to pull from myriad sources. They're distinct because the two color one needed a minimum constrast restriction.

Before figuring out how to construct the model, I took some time to try and understand what was going on under the hood of good looking palettes. I took this first palette and swapped out its lightness values for those from a different palette. Although they shared the same hue and saturation, the result was terrible. I tried the same but swapped out the hue, the results were much better. Inverse saturation/lightness seems to have a strong "goodness" correlation. Observations like these helped inform my model/data construction.

Luckily I quickly found success in implementing these improvements. By first generating colors you like with the network trained by you, they are matched with the algorithms trained by ideal examples.

Some thoughts on the user experience

A lot of palette generator sites feature a single palette with a refresh button or a hover and click to set interaction. You either get it right, refresh single colors, or tweak parameters. This to me has always felt too "lean in". I wanted something more about discovery, more like digging for gold. There are also plenty of sites with premade palettes too, but I like others I hope, want to stumble accross something ostensibly original.

I decided to go with an infinite scroll UI to feel more like browsing Pinterest than laborously tweaking range sliders.

I did some front-end hackery to keep the page light and fast. The rendering code recycles the DOM elements rather than junking up the memory with nodes.

With that in place I starting playing around with use cases. How do people want to see their colors? What do they want to know about them? Luckily this was a case of designing for myself, so after a lot of playing around I eventually settled typeography, swatch, gradient, image, and palette. Each pair has its respective names, hex and rgb values, and WCAG accessibility rating.

Communicating the effect of the AI was a challenge since the neural network produces colors using probability.

myNeuralNet.activate(randomColor) // -> 0.7912443

The net thinks there's a 79% chance I'll like randomColor. Is that good enough? If it's set to nothing less than 99.99% then there won't be much variation outside of the original training set. If it allows colors in the 1-10% range then it would likely produce some I don't like, but i'll see a lot more variation. So while not perfect, I added an adjustable bias per color — I think it's important to let the user control the strictness of the generator. I like to have mine around 70%.

Going forward

While there's much more to say about it, I'm mostly looking forward to getting feedback from those interested in seeing Khroma continue to evolve. If you're still reading this then you should probably give it a try if you haven't already. I would love to hear how Khroma works for you.