File size: 2,949 Bytes
d0e7c02
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
"""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