โž• Evaluation handling

โž• Evaluation handling#

Serket uses dispatching over layers to determine the evaluation counterpart of a layer.For instance, during evaluation, the Dropout layer is replaced by an Identity layer, and the BatchNorm layer is replaced by an EvalBatchNorm layer. This ensures that when evaluating a tree of layers, users can follow the โ€œWhat you see is what you getโ€ (WYSIWYG) principle, meaning that Seeing a Dropout/BatchNorm in the model have one meaning.

The dispatch design choice allow user to define layers like Dropout and BatchNorm that have different behaviors during training and evaluation without having to threading a training/eval flag through the layers.

[1]:
!pip install git+https://github.com/ASEM000/serket --quiet

Imports#

[2]:
import serket as sk
import jax.numpy as jnp
import jax.random as jr
import matplotlib.pyplot as plt
import functools as ft
import jax

Example: Dropout#

The following example, train a sequential of layers with Dropout layer. then applies tree_eval to replace the Dropout with Identity

[3]:
class Net(sk.TreeClass):
    def __init__(self, *, key: jax.Array):
        k1, k2 = jax.random.split(key)
        self.linear1 = sk.nn.Linear(1, 10, key=k1)
        self.dropout = sk.nn.Dropout(0.125)
        self.linear2 = sk.nn.Linear(10, 1, key=k2)

    def __call__(self, input: jax.Array, *, key: jax.Array):
        input = self.linear1(input)
        input = self.dropout(input, key=key)
        input = jnp.tanh(input)
        input = self.linear2(input)
        return input


net = sk.tree_mask(Net(key=jr.key(0)))
x = jnp.linspace(-1, 1, 100)[..., None]
y = jnp.sin(x * 3.14)


@jax.jit
def train_step(net, x, y, key):
    keys = jax.random.split(key, len(x))

    def loss_func(net, x, y):
        net = sk.tree_unmask(net)
        ypred = jax.vmap(net)(x, key=keys)
        return jnp.mean((ypred - y) ** 2)

    grad = jax.grad(loss_func)(net, x, y)
    net = jax.tree_util.tree_map(lambda p, g: p - 1e-2 * g, net, grad)
    return net


key = jax.random.key(0)

print("Before training", "-" * 80)
print(repr(net))
for i in range(10_000):
    key, subkey = jax.random.split(key)
    net = train_step(net, x, y, subkey)

net = sk.tree_unmask(net)

print("After training", "-" * 80)
print(repr(net))
Before training --------------------------------------------------------------------------------
Net(
  linear1=Linear(
    in_features=(#1),
    out_features=(#10),
    in_axis=(#-1),
    out_axis=(#-1),
    weight_init=#glorot_uniform,
    bias_init=#zeros,
    weight=f32[10,1](ฮผ=-0.06, ฯƒ=0.42, โˆˆ[-0.54,0.54]),
    bias=f32[10](ฮผ=0.00, ฯƒ=0.00, โˆˆ[0.00,0.00])
  ),
  dropout=Dropout(drop_rate=0.125, drop_axes=None),
  linear2=Linear(
    in_features=(#10),
    out_features=(#1),
    in_axis=(#-1),
    out_axis=(#-1),
    weight_init=#glorot_uniform,
    bias_init=#zeros,
    weight=f32[1,10](ฮผ=-0.03, ฯƒ=0.43, โˆˆ[-0.70,0.60]),
    bias=f32[1](ฮผ=0.00, ฯƒ=0.00, โˆˆ[0.00,0.00])
  )
)
After training --------------------------------------------------------------------------------
Net(
  linear1=Linear(
    in_features=(1),
    out_features=(10),
    in_axis=(-1),
    out_axis=(-1),
    weight_init=glorot_uniform,
    bias_init=zeros,
    weight=f32[10,1](ฮผ=-0.20, ฯƒ=1.25, โˆˆ[-2.35,2.49]),
    bias=f32[10](ฮผ=0.00, ฯƒ=0.00, โˆˆ[-0.00,0.00])
  ),
  dropout=Dropout(drop_rate=f32[](ฮผ=0.12, ฯƒ=0.00, โˆˆ[0.12,0.12]), drop_axes=None),
  linear2=Linear(
    in_features=(10),
    out_features=(1),
    in_axis=(-1),
    out_axis=(-1),
    weight_init=glorot_uniform,
    bias_init=zeros,
    weight=f32[1,10](ฮผ=-0.01, ฯƒ=0.42, โˆˆ[-0.72,0.55]),
    bias=f32[1](ฮผ=0.00, ฯƒ=0.00, โˆˆ[0.00,0.00])
  )
)

Apply tree_eval

Note how Dropout is replaced by Identity. No need to thread a train/eval flag!

[4]:
print("Dropout before eval", "-" * 80)
print(repr(net.dropout))
net = sk.tree_eval(net)
print("Dropout after eval", "-" * 80)
print(repr(net.dropout))
Dropout before eval --------------------------------------------------------------------------------
Dropout(drop_rate=f32[](ฮผ=0.12, ฯƒ=0.00, โˆˆ[0.12,0.12]), drop_axes=None)
Dropout after eval --------------------------------------------------------------------------------
Identity()
[5]:
plt.plot(x, y, label="true")
plt.plot(x, jax.vmap(lambda input: net(input, key=None))(x), label="pred")
[5]:
[<matplotlib.lines.Line2D at 0x178e05850>]
../_images/notebooks_%5Bguides%5D%5Bcore%5Devaluation_9_1.png

Custom evaluation rule#

The following shows how to define an evaluation behavior for a layer that exhibits different behavior based on eval/train status.

[6]:
class AddOne(sk.TreeClass):
    def __call__(self, x: jax.Array) -> jax.Array:
        return x + 1


x = jax.numpy.ones([3, 3])

add_one = AddOne()


class AddOneEval(sk.TreeClass):
    def __call__(self, x: jax.Array) -> jax.Array:
        return x  # no-op


@sk.tree_eval.def_eval(AddOne)
def _(_: AddOne) -> AddOneEval:
    return AddOneEval()


print(add_one(x))
print(sk.tree_eval(add_one)(x))
[[2. 2. 2.]
 [2. 2. 2.]
 [2. 2. 2.]]
[[1. 1. 1.]
 [1. 1. 1.]
 [1. 1. 1.]]