Solomon / src /solomon /semantics.py
Archer Hume
Solomon v1.1
105f9ef
Raw
History Blame Contribute Delete
2.91 kB
"""Solomon answer semantics (design note, not distributed).
Yes/no, entity and multi-label candidates are Nouls: one probability P(yes). Gold yes only when the
document clearly establishes it; not stated and conflicting are no. Choice (single, ordered) is a
distribution over the listed options only; reserved-gold items have no Solomon target.
Works for both readouts: four_collapsed (four-state letter logits A/B/C/D, collapsed) and two_letter (A/B).
"""
import numpy as np
YES, NO, NOT_STATED, CONFLICTING = 0, 1, 2, 3
def softmax(x, t=1.0):
z = np.asarray(x, np.float64) / t
z = z - z.max()
e = np.exp(z)
return e / e.sum()
def noul_gold(gold4):
"""Four-state (or two-state) gold -> 1 for yes, 0 for no."""
return int(int(gold4) == YES)
def noul_logit(letter_logits):
"""Binary log-odds z = log P(yes)/P(no) of a Noul branch: letter A against everything else."""
logits = np.asarray(letter_logits, np.float64)
if len(logits) not in (2, 4):
raise ValueError(f'Noul branch must have 2 or 4 letters, got {len(logits)}')
rest = logits[1:] - logits[1:].max()
return float(logits[YES] - (logits[1:].max() + np.log(np.exp(rest).sum())))
def p_yes(letter_logits, t=1.0):
"""P(yes) from a Noul branch: letter A of a 2-letter (two_letter) or 4-state (four_collapsed) readout.
Temperature applies to the COLLAPSED binary logit, not to the letters: a Noul is a binary unit whose
'no' mass may be spread over several reserved letters, so p_yes(t) = sigmoid(z/t) with z = noul_logit.
At t = 1 this is exactly softmax over the letters at A (the two forms only differ once t != 1, where the
letterwise form would decay toward 1/len(letters) instead of toward 1/2). Solomon.qualification.p_yes and
abstention_refit/readout.py fit and evaluate the collapsed form, so the serving path must match it.
"""
logits = np.asarray(letter_logits, np.float64)
if len(logits) not in (2, 4):
raise ValueError(f'Noul branch must have 2 or 4 letters, got {len(logits)}')
if t == 1.0:
return float(softmax(logits)[YES])
z = noul_logit(logits) / float(t)
return float(1.0 / (1.0 + np.exp(-z))) if z > -700 else 0.0
def noul_confidence(p):
return max(p, 1.0 - p)
def listed_gold(gold, n_options):
"""Listed option index, or None when the old gold was a reserved slot (not stated / none-of-listed / conflicting)."""
return int(gold) if isinstance(gold, (int, np.integer)) and 0 <= int(gold) < n_options else None
def listed_probs(letter_logits, n_options, t=1.0):
"""Choice distribution over the listed options only (reserved slots, if present, are discarded)."""
return softmax(np.asarray(letter_logits, np.float64)[:n_options], t)
def complement_deviation(p, p_negated):
"""G3a under Solomon: a statement and its negation should sum to 1."""
return abs(p - (1.0 - p_negated))