Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Utility as a max of experts

A candidate is as good as its best style thinks it is. Two more obvious designs cannot represent a cross-island comparison at all.

For a technical audience: the taste model and the search as the code computes them, and why each piece has the form it does. 2:46 · chapters and transcript

The form

A candidate xx has the feature vector φ(x)\varphi(x), which the standardizer maps to z(x)z(x) with per-feature means μ\mu and scales ss. The taste model holds KK styles, and style kk is a weight vector θk\theta_k: a linear functional on zz. Utility is the maximum over styles, not a weighted mixture:

u(x)=max⁡k∈1..K  θk⊤z(x),z(x)=φ(x)−μsu(x) = \max_{k \in 1..K} ; \theta_k^\top z(x), \qquad z(x) = \frac{\varphi(x) - \mu}{s}

At K=1K = 1 this reduces exactly to Bayesian linear regression on zz, which is a useful property: the mixture is a strict generalization with no special-casing at the boundary.

One posterior draw’s utility (TasteSample::utility_mix):

pub fn utility_mix(&self, phi: &[f64]) -> f64 {
    self.theta.iter()
        .map(|t| dot(t, phi))
        .fold(f64::NEG_INFINITY, f64::max)
}

Why a maximum

Taste is multi-modal. One person can love dark drones and bright plucks (“ambient-me” and “acid-me”), and those are not points on one axis. A single linear utility would average them into a preference for neither, and would then be confidently wrong about both.

The max form gives each island its own style, and every judgment, including a duel across two islands, compares candidates on the shared scale u=max⁡kuku = \max_k u_k. A dark drone and a bright pluck are both scored, each by whichever style rates it highest, and the comparison is well-formed.

Drag either arrow. Each arrow is a lens (a style): one direction in feature space. Each candidate is colored by the style that scores it highest and lit by how highly, the same encoding the instrument’s taste map uses. Pull the arrows apart and two islands appear, each with its own idea of what “good” points at. Then press compare K = 1: one direction has to explain both islands at once, and the only direction that does lies between them, describing a taste nobody has.

Two rejected designs, and why

A per-session style latent zsz_s

“One mood per session: sample which style is active, then use it.”

Fails because it cannot represent several islands inside a session. A user who auditions a pad, then a bass, then a pad in one sitting is not switching moods; they have two tastes at once. Whenever the session’s latent is wrong for the current candidate, every observation in that session is scored by the wrong style.

A per-observation marginalized style

“Marginalize over which style judges each observation.”

Fails on a sharper point: it forces both duel items through the same style, so a cross-island comparison is unrepresentable. There is no style under which “the drone beats the pluck” is a sensible statement if the drone lives in style 1 and the pluck in style 2, and a duel between them is exactly the question the acquisition rule will ask.

This is not a theoretical objection. A synthetic bimodal user exposed it: the marginalized mixture failed to beat K=1K = 1. Adding capacity made the model no better, which is the signature of capacity the likelihood cannot use.

What max-utility buys structurally

There are no discrete latent sites at all. No style assignment to sample, no categorical variables, no label-switching during inference to fight. Every site in the model is an f64, which means fugue’s generic adaptive single-site MH applies unchanged, with no custom kernel and no Rao-Blackwellization.

Label permutation is resolved post hoc instead, by TastePosterior::aligned.

KK is an upper bound, not a claim

K=5K = 5 by default (SessionConfig::k_styles, in engine.rs), and the fitted number of live styles grows with evidence.

Nothing enforces that; it falls out. A style with no evidence to explain stays near its prior, and style_share reports what fraction of the pool each style actually claims as its best. A style claiming ≈0% is idle: the player’s taste has fewer islands than KK, and the app dims it rather than inventing a name for it.

So KK is capacity, and the data decides how much gets used.

The prior, and the correction KK forces

θk,j∼N(0,σθ2),σθ=1d  sK\theta_{k,j} \sim \mathcal{N}(0, \sigma_\theta^2), \qquad \sigma_\theta = \frac{1}{\sqrt{d}; s_K}

The 1/d1/\sqrt{d} factor is standard: with ∥z∥2≈d\lVert z \rVert^2 \approx d for a standardized vector, it makes the prior utility of a candidate roughly unit-variance, so likelihood scales stay sane at any feature count.

The sKs_K factor is the correction the max form forces, and it is easy to miss.

Under the prior each uku_k is marginally N(0,1)\mathcal{N}(0,1), so u=max⁡kuku = \max_k u_k is the maximum of KK iid standard normals, whose standard deviation falls with KK (MAX_NORMAL_SD, in auracle-taste’s model.rs):

KK12345
sKs_K1.0000.8260.7480.7010.669

The mean shift cancels in duels (both sides shift equally) and is absorbed by τ\tau and the cutpoints elsewhere. The variance shrinkage does not cancel. Left uncorrected, Var(ua−ub)\mathrm{Var}(u_a - u_b) drops from 2.0 at K=1K=1 to 0.90 at K=5K=5, so growing KK mid-session would quietly make the model less able to express a strong preference.

That is the opposite of what adding capacity should do, and a player would meet it as a model that grows vaguer the longer they play.

Dividing by sKs_K restores invariance: Var(ua−ub)\mathrm{Var}(u_a - u_b) is the same at every KK.

What the interface reads off this

QuantityIs
utility_mix(z)(mean,sd)(\text{mean}, \text{sd}) of uu over posterior draws: the glow and size on the taste map
utility(z, k)Style kk’s rating specifically
best_style(z)Which style claims this candidate: the hue on the map
responsibilities(z)Posterior probability that each style is the best one for this candidate
slope(z, ê)The slope of uu along a direction e^\hat e at zz, through the style that claims zz in each draw: PERFORM’s lean on a control
style_share(pool)Per-style share of the pool, averaged over candidates
prob_prefers(a, b)Eθ [σ(u(a)−u(b))]\E_\theta,[\sigma(u(a) - u(b))]: the forecast the app shows for a pair

responsibilities is a posterior distribution over which style applies, which is strictly more informative than an argmax and is what lets a candidate sit visibly between two islands.