codexlayer-normalization-epsilon-response.py
layer-normalization-epsilon-response.py
"""Analytic layer-normalization response; writes a PNG in the working directory.
The three input coordinates are (-t, 0, t), with unit gains and zero biases.
No data or illustrations from another publication are used.
"""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def response(t, epsilon):
return t / np.sqrt(2 * t**2 / 3 + epsilon)
def slope(t, epsilon):
return epsilon / (2 * t**2 / 3 + epsilon) ** 1.5
def verify():
for epsilon in (0.04, 0.25, 1.0):
np.testing.assert_allclose(slope(0.0, epsilon), 1 / np.sqrt(epsilon))
for t in (-0.8, -0.1, 0.0, 0.2, 0.7):
step = 1e-6
finite_difference = (
response(t + step, epsilon) - response(t - step, epsilon)
) / (2 * step)
np.testing.assert_allclose(
finite_difference, slope(t, epsilon), rtol=1e-8, atol=1e-9
)
z = np.array([-0.3, 0.0, 0.3])
centered = z - z.mean()
q = np.mean(centered**2)
u = centered / np.sqrt(q + epsilon)
np.testing.assert_allclose(np.mean(u**2), q / (q + epsilon))
projection = np.eye(3) - np.ones((3, 3)) / 3
jacobian = projection / np.sqrt(q + epsilon) - np.outer(
centered, centered
) / (3 * (q + epsilon) ** 1.5)
np.testing.assert_allclose(jacobian @ np.ones(3), 0, atol=1e-14)
np.testing.assert_allclose(
jacobian @ centered,
epsilon * centered / (q + epsilon) ** 1.5,
atol=1e-14,
)
assert np.linalg.norm(jacobian, 2) <= 1 / np.sqrt(epsilon) + 1e-12
def main():
verify()
plt.rcParams.update({"font.size": 10, "axes.titlesize": 11})
fig, (forward, derivative) = plt.subplots(
1, 2, figsize=(8.6, 4.1), dpi=100, facecolor="white"
)
fig.subplots_adjust(left=0.075, right=0.98, bottom=0.18, top=0.79, wspace=0.3)
fig.suptitle("What epsilon changes in layer normalization", y=0.97, fontsize=14)
fig.text(
0.5, 0.88,
r"Input $(-t,0,t)$; plot the third normalized coordinate; gain = 1, bias = 0",
ha="center", fontsize=10,
)
t = np.linspace(-3, 3, 1201)
near_zero = np.linspace(-1, 1, 1201)
for epsilon, color in zip((0.04, 0.25, 1.0), ("#0072b2", "#d55e00", "#009e73")):
label = rf"$\varepsilon={epsilon:g}$"
forward.plot(t, response(t, epsilon), color=color, lw=2, label=label)
derivative.plot(near_zero, slope(near_zero, epsilon), color=color, lw=2)
asymptote = np.sqrt(3 / 2)
for sign in (-1, 1):
forward.axhline(sign * asymptote, color="#888888", ls="--", lw=0.8)
forward.axhline(0, color="#999999", lw=0.7)
forward.axvline(0, color="#999999", lw=0.7)
forward.set(xlim=(-3, 3), ylim=(-1.4, 1.4), xlabel="Input amplitude t",
ylabel="Normalized coordinate", title="Forward response")
forward.legend(loc="lower right", framealpha=0.95, fontsize=9)
derivative.set(xlim=(-1, 1), ylim=(0, 6.15), xlabel="Input amplitude t",
ylabel="Derivative of normalized coordinate",
title="Sensitivity near a constant input")
derivative.text(0.5, 0.96, r"Slope at $t=0$: $1/\sqrt{\varepsilon}$",
transform=derivative.transAxes, ha="center", va="top", fontsize=10)
for ax in (forward, derivative):
ax.set_facecolor("white")
ax.grid(alpha=0.22)
ax.spines[["top", "right"]].set_visible(False)
fig.savefig(Path.cwd() / "layer-normalization-epsilon-response.png",
dpi=100, facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()