Back to AI architectures leaderboard
hobby Color polygraph · feature engineering 441 features per session

Feature engineering

The tree-based architectures on the leaderboard all consume the same 441-dim vector built by features.py. The sequence models do not - they re-parse the raw session and build their own per-step embeddings - but the cleaning rules below decide which sessions enter any of the pipelines.

At a glance

Features per session
441
all numeric, all float32
Clean sessions
6,710
out of ~6,800 raw rows
Feature blocks
15
grouped by what they describe

What the raw input looks like

Each row of save.ligma describes one session. A session is a 21-step elimination tournament:

Stored fields per row: an id, a UNIX timestamp, a censored IP, age (years), mood (0–60), gender, the 21-character pick string, the 21 cumulative millisecond timestamps, and the colour arrays [64 offered, 16 r1 winners, 4 r2 winners, final].

Validation rules

A row enters the dataset only if it passes all of these checks (see is_valid() in features.py). The same rules are duplicated in every sequence-model script so the train sets are identical:

About 1.2% of raw rows fail these checks. The rest become the 6,710-row dataset every model on the leaderboard is trained on.

Feature blocks

Jump to a block:

36 features

Per-stage colour summary stats

Mean and standard deviation of R, G, B, H, S, L (12 numbers) across each of the three colour stages: the 64 offered colours, the 16 round-1 winners, and the 4 round-2 winners. Captures things like "this user gravitates toward dark saturated colours" without committing to which dimensions actually matter.

off_*, r1_*, r2_* with suffixes r_mean g_mean b_mean r_std g_std b_std h_mean s_mean l_mean h_std s_std l_std.

11 features

Final colour

The colour the user ultimately chose, encoded redundantly so the trees can split on whichever colour space happens to be informative.

final_r, final_g, final_b (RGB, 0–1) · final_h, final_s, final_l (HSL) · final_y, final_u, final_v (YUV) · final_warmth = (R−B) / 255 · final_chroma = (max−min) / 255.

6 features

Selectivity deltas

How much the round-1 mean colour differs from the offered-set mean, per channel, and how much the round-2 mean differs from the round-1 mean. A large positive sel_r1_dr means "this user pushed the selection toward redder colours when given the chance".

sel_r1_dr, sel_r1_dg, sel_r1_db, sel_r2_dr, sel_r2_dg, sel_r2_db.

2 features

Voxel diversity

How many distinct 8×8×8 RGB voxels the round-1 and round-2 winners spread across, normalised by 16 and 4 respectively. Close to 1.0 means the user picked all over the colour space; close to 0.0 means they stayed in one neighbourhood.

voxel_div_r1, voxel_div_r2.

1 feature

Round-1 internal spread

Mean pairwise Euclidean distance between the 16 round-1 winners in normalised RGB. A second flavour of "did this user stay close to one colour or wander?".

r1_internal_spread.

208 features · 13 per question × 16 questions

Per round-1 question

For every one of the 16 round-1 questions the model gets a full picture of what was on screen and what the user picked. Per question (q00 through q15):

qNN_chosen_* — the same 8-number colour block as the "Final colour" group (r, g, b, h, s, l, warmth, chroma) but for the colour the user picked in question NN.
qNN_dr, qNN_dg, qNN_db — the channel-wise delta from the chosen colour to the mean of the three rejected colours.
qNN_decisive — magnitude of that delta vector. High = the chosen colour was very different from the rejects.
qNN_pos — which of the 4 corners (0–3) the chosen colour was in.

2 features

Decisiveness aggregate

Mean and standard deviation of the 16 per-question decisiveness magnitudes. A blunt summary of "how strong are this user's preferences?".

mean_decisiveness, std_decisiveness.

36 features · 9 per question × 4 questions

Per round-2 question

Same colour block as round-1 plus the corner position the user picked, for each of the 4 round-2 questions. No "delta from rejects" here because by round 2 every option was already approved in round 1, so the delta is less informative.

r2qN_chosen_* (r, g, b, h, s, l, warmth, chroma), r2qN_pos.

5 features

Position picks aggregate

What fraction of the 16 round-1 picks landed in each of the four corner positions, plus the Shannon entropy of that distribution. Bored kids hit the same corner; engaged users spread out.

pos_0_frac, pos_1_frac, pos_2_frac, pos_3_frac, pos_entropy.

64 features · 4×4×4 voxel grid

Round-1 voxel histogram

The 16 round-1 winners binned into a 4×4×4 RGB voxel grid (so each bin has 64-cube edges), then each bin divided by 16. Trees love this kind of dense low-resolution histogram for picking out "this user prefers the dark-blue corner of colour space".

hist_v00 through hist_v63.

13 features · 12 distances + nearest

Reference colour distances

Euclidean distance in normalised RGB from the final-pick colour to each of 12 hand-picked reference colours (pink, red, orange, yellow, green, cyan, blue, purple, brown, gray, black, white), plus the index of the closest one. The point is to give the model a head start at carving the colour space along axes that already mean something to humans, without forcing the model to discover "near pink" from raw RGB.

final_to_pink, final_to_red, ..., final_to_white, final_closest_ref.

2 features

Final-to-round means

Distance in normalised RGB from the final pick to the round-1 winner mean, and from the final pick to the round-2 winner mean. Did the user converge throughout the tournament or pick something off-trend at the end?

final_to_r1_mean, final_to_r2_mean.

6 features

Round-1 trajectory

Treats the 16 round-1 winners as a path through RGB space. Mean and std of step-to-step distance, mean distance to the centroid, and the slopes of warmth, lightness, and saturation across question index. Picks up drifts like "got tired and started clicking blue".

r1_consec_mean, r1_consec_std, r1_centroid_mean_dist, r1_warmth_slope, r1_light_slope, r1_sat_slope.

6 features

Extreme-pick counts

For each round-1 question, was the chosen colour the warmest / coolest / lightest / darkest / most-saturated / least-saturated of the 4 offered? Each feature is the fraction of the 16 questions for which the answer is yes.

extreme_warmest_frac, extreme_coolest_frac, extreme_lightest_frac, extreme_darkest_frac, extreme_most_sat_frac, extreme_least_sat_frac.

5 features

Round-1 time buckets

Per-question time deltas for the 16 round-1 questions binned into five buckets: under 1s, 1–3s, 3–7s, 7–15s, 15s+. Each value is a fraction summing to ~1. Picks up "skimmer vs reader" patterns.

r1_tbucket_0 through r1_tbucket_4.

2 features

Time × behaviour interactions

Two hand-crafted interactions that the trees might construct on their own eventually but are useful to feed them directly:

time_x_low_entropy = mean question time × (1 − position entropy / log 4). High = slow and concentrated picking, the "deliberate corner-banger" signal.
time_per_decisive = mean question time / (mean decisiveness + 50). How much time the user spends per unit of preference.

12 features

Timing summary

Aggregate statistics over the 21 per-question time deltas plus the round means and the linear slope across the whole session.

total_sec, mean_q_ms, std_q_ms, min_q_ms, max_q_ms, median_q_ms, first5_mean_ms, last5_mean_ms, time_slope, r1_mean_ms, r2_mean_ms, final_ms.

21 features

Per-question raw times

The raw time spent on each of the 21 questions, in seconds.

t_q00 through t_q20.

3 features

Hour of day

Time of day the session was recorded, encoded as sine and cosine of the fractional hour in Oslo time (so midnight and noon are not adjacent in feature space). The hour_known flag is 0 when the timestamp parse failed, which lets the trees treat unknown-hour sessions separately.

hour_sin, hour_cos, hour_known.

Output files

Running python features.py writes three files into training/ that every tree-based architecture loads:

Project links

Back to AI architectures leaderboard