SabaPivot's picture
download
raw
10.3 kB
"""
Claim 3 (Theorem 3.2): a SINGLE floating-point network whose AD gradient varies
with the loss derivative y = phi'_x(f(x)), subject to the antisymmetry condition
g*(x,-y) = -g*(x,y).
Two independent halves:
(A) NECESSITY of the antisymmetry condition. IEEE-754 multiplication and addition
are exactly sign-symmetric, so D_{f,x}(-y) = -D_{f,x}(y) holds identically for
*every* floating-point network. Any target g* violating g*(x,-y) = -g*(x,y) is
therefore unrepresentable. We verify the underlying identity EXHAUSTIVELY over
all of binary16 (all 2^16 x 2^16 ordered pairs) and over random binary32 nets.
(B) EXISTENCE of non-proportional dependence on y. Under exact real arithmetic the
chain rule forces grad(phi o f) = phi'(f(x)) * grad f(x), i.e. exactly
proportional to y. We build a floating-point network whose AD gradient is a
prescribed, arbitrary, antisymmetric, wildly non-proportional function of y on a
finite set Y of input gradients, using the underflow / subnormal-collapse of the
backward multiplications.
"""
import json
import os
import sys
import numpy as np
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from fpnet import Act, Builder, FPNet # noqa: E402
OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "outputs")
os.makedirs(OUT, exist_ok=True)
# ------------------------------------------------------------------ (A)
def exhaustive_sign_symmetry():
"""for all a,b in binary16 : fl(-a*b) == -fl(a*b) and fl(-a-b) == -fl(a+b)."""
allf = np.arange(2**16, dtype=np.uint16).view(np.float16)
allf = allf[np.isfinite(allf)]
n = allf.size
bad_mul = bad_add = 0
for i in range(0, n, 512):
a = allf[i : i + 512][:, None]
b = allf[None, :]
m1, m2 = (-a) * b, -(a * b)
bad_mul += int(np.sum((m1 != m2) & ~(np.isnan(m1) & np.isnan(m2))))
s1, s2 = (-a) + (-b), -(a + b)
bad_add += int(np.sum((s1 != s2) & ~(np.isnan(s1) & np.isnan(s2))))
return dict(
format="binary16",
finite_values=int(n),
ordered_pairs=int(n) ** 2,
multiplication_violations=bad_mul,
addition_violations=bad_add,
)
def network_antisymmetry(trials=200, seed=7):
"""D_{f,x}(-y) == -D_{f,x}(y) on random float32 networks, exactly."""
rng = np.random.default_rng(seed)
bad = 0
checked = 0
for t in range(trials):
name = ["relu", "elu", "gelu", "swish", "sigmoid", "tanh"][t % 6]
act = Act(name, np.float32)
dims = [2, 6, 5, 1]
As = [
rng.normal(scale=3, size=(dims[i + 1], dims[i])).astype(np.float32)
for i in range(3)
]
bs = [
rng.normal(scale=3, size=dims[i + 1]).astype(np.float32) for i in range(3)
]
net = FPNet(As, bs, act)
x = rng.normal(size=2).astype(np.float32)
for y in rng.normal(scale=10, size=8).astype(np.float32):
net.forward(x)
gp = net.backward(np.array([y], dtype=np.float32))
net.forward(x)
gm = net.backward(np.array([-y], dtype=np.float32))
checked += 1
if not np.array_equal(gp, -gm):
bad += 1
return dict(random_networks=trials, gradient_pairs=checked, violations=bad)
# ------------------------------------------------------------------ (B)
def build_y_dependent(dtype=np.float32, n_band=24, seed=11):
"""
3-layer sigma-network f : F -> F with f(x) = 0 for every x (its forward
accumulation is annihilated by a +C/-C constant pair) whose AD gradient
D_{f,x}(y) = (+)_j ( ( y (x) p_j ) (x) w ) (x) a_j
is a prescribed function of y. p_j w = omega * 2^-e_j, so band j contributes
exactly 0 while |y| < 2^{e_j} (the backward product UNDERFLOWS) and contributes
a_j * omega once |y| >= 2^{e_j}. Under exact real arithmetic every term would be
exactly proportional to y; under IEEE-754 they are not.
"""
dt = dtype
act = Act("relu", dt)
fi = np.finfo(dt)
P0 = int(np.log2(float(fi.smallest_subnormal))) + 79 # p_j = 2^(P0-e_j)
w = dt(2.0) ** -79
B = dt(2.0) ** 10
exps = list(range(0, n_band))
rep = 3 # units per band (residual refinement)
ps, bld = [], Builder(1, 3)
u, v = [], []
for e in exps:
for _ in range(rep):
ps.append(dt(2.0) ** (P0 - e))
u.append(bld.add(0, {0: 1.0}, float(B)))
for j in range(len(u)):
v.append(bld.add(1, {u[j]: float(w)}, float(B)))
exps = [e for e in exps for _ in range(rep)]
kp = bld.add(1, {}, float(B))
km = bld.add(1, {}, float(B))
ins = {v[j]: float(ps[j]) for j in range(len(exps))}
ins[kp] = 0.0
ins[km] = 0.0
out = bld.add(2, ins, 0.0)
return bld, exps, u, v, (kp, km), out, ps, act, dt
def _set_output_wipe(bld, exps, v, kpm, out, ps, act, dt, xs):
kp, km = kpm
net = bld.build(act)
worst = dt(0.0)
for x in xs:
net.forward(np.array([x], dtype=dt))
vv = act.s(net._pres[1])
acc = dt(0.0)
for j in range(len(exps)):
acc = dt(acc + dt(dt(ps[j]) * dt(vv[v[j]])))
worst = max(worst, abs(acc))
vk = dt(vv[kp])
e = int(np.ceil(np.log2(max(float(worst), 1e-300)))) + int(np.finfo(dt).nmant) + 6
C = dt(2.0) ** e
bld.set_weight(2, out, kp, float(dt(C) / vk))
bld.set_weight(2, out, km, float(-dt(C) / vk))
return bld.build(act)
def fit_y_dependent(targets, seed=11):
"""choose the layer-1 weights a_j so that D_{f,x}(y) hits `targets` exactly."""
bld, exps, u, v, kpm, out, ps, act, dt = build_y_dependent(seed=seed)
bands = sorted(set(exps))
idx_of = {e: [j for j, ee in enumerate(exps) if ee == e] for e in bands}
ys = [dt(2.0) ** e for e in bands]
xs = [0.0, 0.25, 0.5, 1.0, -1.0]
net = bld.build(act)
x0 = np.array([1.0], dtype=dt)
for _ in range(40):
ok = True
for bi, e in enumerate(bands):
for idx in idx_of[e]:
y = ys[bi]
net.forward(x0)
_, tr = net.backward(np.array([y], dtype=dt), debug=True)
mult = dt(tr[("post_act", 0)][u[idx]])
got = dt(net.backward(np.array([y], dtype=dt))[0])
tgt = dt(targets[bi])
if got == tgt:
continue
ok = False
if mult == 0:
continue
cur = dt(net.As[0][u[idx], 0])
new = dt(cur + dt(np.float64(dt(tgt - got)) / np.float64(mult)))
if not np.isfinite(new):
continue
bld.set_weight(0, u[idx], 0, float(new))
# keep the layer-1 pre-activation strictly positive (sigma' = 1) for every
# probe x, whatever the weight: bias dominates.
B_j = dt(2.0) ** int(np.ceil(np.log2(max(4.0 * abs(float(new)), 1024.0))))
assert np.isfinite(B_j), "bias overflow: target too large for 3 layers"
bld.set_bias(0, u[idx], float(B_j))
net = bld.build(act)
if ok:
break
net = _set_output_wipe(bld, exps, v, kpm, out, ps, act, dt, xs)
rows = []
for bi, e in enumerate(bands):
for s_ in (1, -1):
y = dt(s_) * ys[bi]
net.forward(x0)
g = dt(net.backward(np.array([y], dtype=dt))[0])
tt = dt(s_) * dt(targets[bi])
rows.append(dict(y=float(y), target=float(tt), got=float(g),
exact=bool(g == tt)))
fwd = [float(net.forward(np.array([xx], dtype=dt))[0]) for xx in xs]
ratios = [abs(r["got"] / r["y"]) for r in rows if r["y"] != 0 and r["got"] != 0]
return net, dict(
bands=len(bands), units=len(u), checks=rows,
all_exact=bool(all(r["exact"] for r in rows)),
n_exact=int(sum(r["exact"] for r in rows)),
forward_values=fwd, forward_all_zero=bool(all(f == 0 for f in fwd)),
ratio_min=min(ratios) if ratios else None,
ratio_max=max(ratios) if ratios else None,
ratio_spread_orders=float(np.log10(max(ratios) / min(ratios)))
if ratios else None)
def exact_arithmetic_proportionality(net, targets):
"""under exact real arithmetic the same graph gives D(y) exactly proportional to y."""
from fractions import Fraction
dt = np.float32
def relu(t):
return t if t > 0 else Fraction(0)
def relup(t):
return Fraction(1) if t > 0 else Fraction(0)
x0 = np.array([1.0], dtype=dt)
_, pres = net.forward_exact(x0, relu, relup)
out = []
for e in (0, 5, 10, 15, 20):
y = 2.0**e
g = net.backward_exact([y], pres, relup)[0]
out.append(
dict(y=y, exact_grad=float(g), exact_grad_over_y=float(g / Fraction(y)))
)
ratios = [o["exact_grad_over_y"] for o in out]
return dict(
samples=out,
exact_ratio_constant=bool(len(set(ratios)) == 1),
exact_ratio=ratios[0] if ratios else None,
)
if __name__ == "__main__":
res = {}
print("exhaustive binary16 sign symmetry ...")
res["antisymmetry_necessity_exhaustive"] = exhaustive_sign_symmetry()
print(res["antisymmetry_necessity_exhaustive"])
res["antisymmetry_random_networks"] = network_antisymmetry()
print(res["antisymmetry_random_networks"])
rng = np.random.default_rng(3)
n = 24
targets = (
rng.choice([-1.0, 1.0], size=n)
* (1.0 + rng.integers(0, 2**20, size=n) / 2.0**20)
* 2.0 ** rng.integers(-40, -28, size=n)
).astype(np.float32)
net, r = fit_y_dependent(targets)
res["y_dependent_gradient"] = r
print(
"bands",
r["bands"],
"exact",
r["n_exact"],
"/",
2 * r["bands"],
"fwd all zero",
r["forward_all_zero"],
"ratio spread (orders of magnitude)",
r["ratio_spread_orders"],
)
res["exact_arithmetic_contrast"] = exact_arithmetic_proportionality(net, targets)
print(res["exact_arithmetic_contrast"]["exact_ratio_constant"])
with open(os.path.join(OUT, "claim3_theorem32.json"), "w") as f:
json.dump(res, f, indent=1, default=float)
print("wrote claim3_theorem32.json")

Xet Storage Details

Size:
10.3 kB
·
Xet hash:
ea54ca46ae94354eecdf4a0258d8bdaf81753dcd155e740a91d9fcce1d40a016

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.