GUI-Decisions / slotread /numbers.py
infinitylogesh's picture
GUI-Decisions Space
d0e7c02 verified
Raw History Blame Contribute Delete
2.95 kB
"""Numbers as bins: soft ordinal targets for training, and readouts from a slot's label log-probs.
A number lives in [0, 1]. Positions are linear; sizes and radii are log-warped first, because UI
elements and objects span three orders of magnitude and a uniform grid would spend its resolution
where nothing lives.
A number is one fine slot over `arm.bins` labels, plus (coarse arms only) a coarse slot over the ten
digits: the tenth of the range the number falls in.
"""
import math
import torch
LOG_MIN = 1 / 512 # the smallest size / radius a log-warped number can say, as a fraction
def warp(v, kind):
if kind == "lin":
return min(max(v, 0.0), 1.0)
return min(max(math.log(max(v, LOG_MIN) / LOG_MIN) / math.log(1 / LOG_MIN), 0.0), 1.0)
def unwarp(u, kind):
return u if kind == "lin" else LOG_MIN * (1 / LOG_MIN) ** u
def slots_for(number, arm):
"""The slots that write one number: [x] or, in a coarse arm, [xc, x]."""
return [number + "c", number] if arm.coarse else [number]
def centers(n):
return (torch.arange(n) + 0.5) / n
def gauss(c, mu, sigma):
"""A normalized Gaussian over bin centres c: the soft ordinal target (one bin off costs little)."""
w = torch.exp(-((c - mu) ** 2) / (2 * sigma ** 2))
return w / w.sum()
def number_targets(number, u, sigma, arm):
"""Soft targets for one number at warped value u; sigma in warped units, floored at a bin."""
out = {number: gauss(centers(arm.bins), u, max(0.6 / arm.bins, sigma))}
if arm.coarse:
out[number + "c"] = gauss(centers(10), u, max(0.6 / 10, sigma))
return out
def local_peak(p):
"""The argmax bin refined by the probability-weighted mean of its +-2 neighbours, in [0, 1]."""
p = p.detach().float().cpu()
k, n = int(p.argmax()), len(p)
lo, hi = max(0, k - 2), min(n, k + 3)
w = p[lo:hi]
return float(((torch.arange(lo, hi, dtype=torch.float32) + 0.5) * w).sum() / w.sum()) / n
def read_number(lp, number, arm, how="default"):
"""A number's warped value from its slots' label log-probabilities.
peak the argmax bin
local2 the peak refined by +-2 bins (the default without a coarse slot)
fused the fine distribution times the coarse slot's probability for the tenth each bin falls
in, then local2 (the default with a coarse slot)
expected the mean over all bins
"""
p = torch.softmax(lp[number].detach().float().cpu(), -1)
c = centers(len(p))
if how == "default":
how = "fused" if arm.coarse else "local2"
if how == "peak":
return float(c[int(p.argmax())])
if how == "expected":
return float((c * p).sum())
if how == "fused" and number + "c" in lp:
prior = torch.softmax(lp[number + "c"].detach().float().cpu(), -1)[(c * 10).long().clamp(max=9)]
p = p * prior
return local_peak(p)
def one_hot(n, i):
t = torch.zeros(n)
t[i] = 1.0
return t