Vector clustering, with colors

A toy embedding pipeline: each color is a "document", and the embedding model is just hex → [r, g, b]. Because the embedding space is genuinely 3D, the cube on the right is the vector space — no PCA/t-SNE squashing required.

1 Corpus

    2 Embed

    Turns every hex string into a vector and places it in the cube.

    Learn with it — eight experiments
    1. Watch the pipeline. Press Embed, then hover any dot: hex in, vector out, and its position is the vector. No dimensionality reduction — this cube is the real space.
    2. Let k-means discover the groups. Set k = 4, press Init, then Step one iteration at a time. Notice the dots never move — only the centroid diamonds (the summaries) do. Press Init again: different random seeds, usually the same groups. The structure was in the embedding all along.
    3. Break brightness with cosine. Select darkred and red: real Euclidean distance, angle ≈ 0°. Tick "show what cosine sees" and watch dark red slide onto red. Re-cluster with the Cosine metric — brightness stops mattering.
    4. Swap the embedding model. Converge a clustering in RGB, then switch the model to OKLab: same corpus, new geometry, every distance rewritten. Hover the hollow ghost diamonds to light up each old cluster's former members and see whether the new space kept them together.
    5. Meet a different observer. Switch to Deuteranope and compare crimson vs forestgreen: distance ≈ 0.1, cosine ≈ 1.0. That model genuinely cannot tell red from green — which is exactly what a weak embedding model does to distinct concepts.
    6. Combine vectors. Select royalblue and gold and find the Mix row (and the marker halfway along the dashed line). Flip between RGB, OKLab, and CIELAB: the same average decodes to a different color in each space — operations on vectors inherit the geometry.
    7. Cluster without choosing k. Switch the algorithm to DBSCAN and press Play: clusters flood outward wherever points are dense, and loners get dimmed as noise — a verdict k-means can't give, since it force-assigns everything. Lower ε and re-Init to watch clusters fragment.
    8. Build a hierarchy. Switch to Hierarchical and Step: each step merges the two closest clusters and the status line shows the merge distance. When that number suddenly jumps, you've crossed a natural boundary — that's where you'd "cut the tree" to pick k after the fact. Play to the end, then drag the cut slider on the merge tree: the dashed line slides up and down the dendrogram and the 3D view shows exactly the clusters that cut produces. Then try single linkage and watch the grays chain distant groups together.
    Teaching notes
    • Clustering is a family, not one algorithm. k-means quietly assumes you know k and that a cluster is a ball around a made-up average. k-medoids insists the center be a real member (an exemplar). DBSCAN drops both assumptions: clusters are dense regions and stragglers are labeled noise. Hierarchical builds the whole merge tree and lets you choose k afterwards. Same embedding, different questions asked of it.
    • The layout is the embedding, not the clustering. Points never move when you cluster — k-means only discovers groups the embedding already placed near each other, and labels them. If the embedding is good, the clusters exist before anyone looks for them.
    • Reading the raw cube: the origin corner is black, the far corner is white, the three axes are pure red, green, and blue, and the main diagonal is the grayscale ramp. Direction ≈ hue; distance from the origin ≈ brightness.
    • Euclidean distance measures how far apart two colors are in the cube — it sees both hue and brightness.
    • Cosine similarity only measures the angle between vectors. Dark red [0.55, 0, 0] and pure red [1, 0, 0] point the same direction — cosine similarity 1.0 despite being far apart.
    • That means cosine collapses a whole dimension: the 3D cube flattens onto the 2D surface of a sphere of directions. Toggle "show what cosine sees" to watch it happen.
    • Black is the zero vector — it has no direction, so its cosine similarity to anything is undefined. It gets stuck at the origin.
    • All grays point the same diagonal direction, so under cosine they're identical to white.
    • Combining vectors is a technique too. Select two dots: the Mix row averages their vectors and decodes the midpoint back into a color (shown as the marker halfway along the dashed line). Try the same pair in RGB and then OKLab — the same average lands on different colors, because the operation inherits the space's geometry. This is why CSS's color-mix() and modern gradients interpolate in OKLab.
    How good is this embedding?
    • In one way, it's perfect. hex → [r, g, b] is lossless and invertible — the vector contains the entire color, and every dimension means something. Real embedding models never get this: they compress a whole paragraph into a few hundred numbers, so information is always thrown away.
    • But a "good" embedding means geometry that matches meaning. An embedding is good when its distances agree with the similarity you actually care about. If that's "how different do these colors look to a human?", RGB is only mediocre: it isn't perceptually uniform. The same Euclidean distance can be a glaring difference in one part of the cube and barely noticeable in another — human eyes are far more sensitive to changes in greens than in blues.
    • Better "models" exist for the same data. Color spaces like OKLab and CIELAB rearrange the exact same colors so that Euclidean distance ≈ perceived difference. Swapping RGB for OKLab is precisely what "upgrading to a better embedding model" means in ML: same corpus, better geometry — and every distance, cluster, and neighbor changes with it. Try it with the embedding model selector above and watch the dots re-arrange.
    • A different observer is a different model. The deuteranope and protanope models simulate red-green color blindness: the corpus doesn't change, but reds and greens now embed almost on top of each other — that geometry genuinely cannot tell them apart, the way a weaker text model conflates two concepts. Each dot keeps its true color, so you can see distinct inputs mapping to the same vector. (Dichromatic vision also squashes 3D color toward a 2D surface — the same dimension-collapse cosine pulled.)

    3 Cluster

    no clustering yet

    Similarity

    Click two dots to compare their vectors.

    Merge tree height = merge distance; dashed line = current cut