We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Flax step 8 of 100
Three Levels of Module Nesting
A Flax Linen module can call another module, which can call another. This lets you group a network into reusable parts. In this exercise, three custom modules organize a calculation that contains just two Dense layers and one ReLU.
Follow the calculation
InnerDense(features)applies onenn.Dense(features=features).MiddleBlock(features)callsInnerDense(features)and applies ReLU.OuterModel(hidden)callsMiddleBlock(hidden)and applies a final Dense layer withfeatures=x.shape[-1].
After the middle block, x has hidden features. The final Dense therefore
maps hidden features to hidden features, regardless of the original input
length. There is no final activation, so output values may be negative.
Use these class names and @nn.compact on each __call__ method. Construct
and call the child modules inside those methods, as in
MiddleBlock(features=self.hidden)(x). Merely constructing a child does not
run its computation or initialize the parameters its forward method would create.
Read the parameter paths
For input shape (3,) and hidden=4,
jax.tree_util.tree_map(jnp.shape, variables["params"]) gives:
{
"MiddleBlock_0": {
"InnerDense_0": {
"Dense_0": {"kernel": (3, 4), "bias": (4,)}
}
},
"Dense_0": {"kernel": (4, 4), "bias": (4,)}
}
The first Dense belongs to InnerDense_0, inside MiddleBlock_0. The final
Dense belongs directly to the outer module. Both are called Dense_0, but
their full paths differ, so they have separate parameters. The root module
has no additional OuterModel key in this mapping.
Automatic names are local to the containing module, with separate counters for each child class. A module that has no parameters or parameter-bearing children need not have an entry in the params collection. The tree records parameter paths, not every operation in the forward calculation: ReLU adds no parameter entry.
To inspect the first kernel, read:
variables["params"]["MiddleBlock_0"]["InnerDense_0"]["Dense_0"]["kernel"]
Names and paths also affect random initialization. Flattening this network into two Dense calls in one module preserves its mathematical form but can change the initialized weights for the same seed. Keep the nesting and automatic names specified here.
Reuse and selective updates
Calling the same child module instance twice shares its parameters; creating two separate instances gives separate parameter sets. Parameter paths let you identify which arrays to inspect, load or update.
If you later freeze a subtree with Optax, note that optax.masked alone is
not a freeze operation: unselected updates pass through unchanged. One option
is to append optax.masked(optax.set_to_zero(), freeze_mask) after the optimizer,
using boolean True for leaves whose updates should be zero. This exercise
does not perform training or require an optimizer.
Problem
Implement deep_nested_forward(seed, x, hidden) using the three modules above.
Use default Dense settings and biases, with no explicit layer names. Cast
seed and hidden to int, construct OuterModel(hidden=hidden), and initialize
it with jax.random.PRNGKey(seed) and x. Do not split the key beforehand.
Pass the whole returned variables mapping to model.apply with the same x.
x is a finite float32 JAX array of shape (D,), with D >= 1. hidden
is a positive integer-valued scalar, and seed is a nonnegative integer-valued
scalar; either may arrive as a float. Return a JAX array of shape (hidden,)
and leave x unchanged.
Understand the ideas behind this exercise. The Problem tab contains the exact implementation requirements.
A Flax Linen module can call another module, which can call another. This lets you group a network into reusable parts. In this exercise, three custom modules organize a calculation that contains just two Dense layers and one ReLU.
Follow the calculation
InnerDense(features)applies onenn.Dense(features=features).MiddleBlock(features)callsInnerDense(features)and applies ReLU.OuterModel(hidden)callsMiddleBlock(hidden)and applies a final Dense layer withfeatures=x.shape[-1].
After the middle block, x has hidden features. The final Dense therefore
maps hidden features to hidden features, regardless of the original input
length. There is no final activation, so output values may be negative.
Use these class names and @nn.compact on each __call__ method. Construct
and call the child modules inside those methods, as in
MiddleBlock(features=self.hidden)(x). Merely constructing a child does not
run its computation or initialize the parameters its forward method would create.
Read the parameter paths
For input shape (3,) and hidden=4,
jax.tree_util.tree_map(jnp.shape, variables["params"]) gives:
{
"MiddleBlock_0": {
"InnerDense_0": {
"Dense_0": {"kernel": (3, 4), "bias": (4,)}
}
},
"Dense_0": {"kernel": (4, 4), "bias": (4,)}
}
The first Dense belongs to InnerDense_0, inside MiddleBlock_0. The final
Dense belongs directly to the outer module. Both are called Dense_0, but
their full paths differ, so they have separate parameters. The root module
has no additional OuterModel key in this mapping.
Automatic names are local to the containing module, with separate counters for each child class. A module that has no parameters or parameter-bearing children need not have an entry in the params collection. The tree records parameter paths, not every operation in the forward calculation: ReLU adds no parameter entry.
To inspect the first kernel, read:
variables["params"]["MiddleBlock_0"]["InnerDense_0"]["Dense_0"]["kernel"]
Names and paths also affect random initialization. Flattening this network into two Dense calls in one module preserves its mathematical form but can change the initialized weights for the same seed. Keep the nesting and automatic names specified here.
Reuse and selective updates
Calling the same child module instance twice shares its parameters; creating two separate instances gives separate parameter sets. Parameter paths let you identify which arrays to inspect, load or update.
If you later freeze a subtree with Optax, note that optax.masked alone is
not a freeze operation: unselected updates pass through unchanged. One option
is to append optax.masked(optax.set_to_zero(), freeze_mask) after the optimizer,
using boolean True for leaves whose updates should be zero. This exercise
does not perform training or require an optimizer.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
JAX reference solution
Sign in to track your attempts toward unlocking the reference solution.