"""Stage 1: measure each of Krea 2's 12 text-layer taps for attribute selectivity. Consumes the per-tap conditioning signatures saved by the KG Conditioning Probe node (probe_node/) over a CONTROLLED set of reference images, and computes - offline, no rendering - a tap x attribute selectivity matrix: for each tap, how much of its contribution is explained by palette vs. structure vs. texture vs. lighting. Method (multivariate eta-squared / variance decomposition): delta_i(ref) = tap_i signature of (prompt+ref) minus tap_i of the baseline. For each tap i and attribute A (a categorical label per reference): eta2(i, A) = SS_between(A) / SS_total in [0, 1] where SS uses squared distances between per-ref delta vectors and group means. A tap with high eta2 for palette and low for subject is palette-selective. This is only interpretable if the reference set is a CONTROLLED design (each attribute varied across levels, others balanced) - see README section 5 for how to build the sets. Run: python probe_selectivity.py --selftest # validate the math (no data needed) python probe_selectivity.py --probes --manifest # analyze real probe output """ import argparse import json import sys from pathlib import Path LAYER_COUNT = 12 # -- vector helpers (pure python; runs anywhere) --------------------------- def vsub(a, b): return [x - y for x, y in zip(a, b)] def vadd(a, b): return [x + y for x, y in zip(a, b)] def vscale(a, s): return [x * s for x in a] def sq_dist(a, b): return sum((x - y) ** 2 for x, y in zip(a, b)) def vmean(vectors): n = len(vectors) out = [0.0] * len(vectors[0]) for v in vectors: for i, x in enumerate(v): out[i] += x return [x / n for x in out] # -- core selectivity ------------------------------------------------------ def eta_squared(vectors, levels): """Fraction of variance in `vectors` explained by categorical `levels`.""" grand = vmean(vectors) ss_total = sum(sq_dist(v, grand) for v in vectors) if ss_total <= 1e-12: return 0.0 groups = {} for v, lvl in zip(vectors, levels): groups.setdefault(lvl, []).append(v) ss_between = 0.0 for members in groups.values(): gm = vmean(members) ss_between += len(members) * sq_dist(gm, grand) return ss_between / ss_total def selectivity_matrix(deltas_by_ref, attrs_by_ref, attributes): """deltas_by_ref: {ref: [tap0_vec, ... tap11_vec]}. Returns eta2[tap][attr].""" refs = list(deltas_by_ref) matrix = [] for tap in range(LAYER_COUNT): row = {} vectors = [deltas_by_ref[r][tap] for r in refs] for attr in attributes: levels = [attrs_by_ref[r][attr] for r in refs] row[attr] = eta_squared(vectors, levels) matrix.append(row) return matrix, refs def report(matrix, attributes): print("tap | " + " | ".join("{:>9}".format(a) for a in attributes) + " | dominant") print("-" * (6 + 12 * len(attributes) + 12)) for tap in range(LAYER_COUNT): row = matrix[tap] dom = max(attributes, key=lambda a: row[a]) cells = " | ".join("{:>9.2f}".format(row[a]) for a in attributes) print(" {:>2} | {} | {}".format(tap, cells, dom if row[dom] > 0.15 else "(none)")) print("\nPer-attribute tap ranking (most selective first):") for attr in attributes: ranked = sorted(range(LAYER_COUNT), key=lambda t: -matrix[t][attr]) print(" {:>9}: {}".format(attr, ranked)) # -- loading real probe output --------------------------------------------- def load_deltas(probe_dir, manifest): """manifest: {"baseline": "