"""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": "