Phase 05 / Neural network training
Debug unstable learning
Revision v0.7.0 · Updated since your last visit. Review the changes; your progress is saved.
Lesson · about 85 minutes · CPU practice
Introduce a bad learning rate and diagnose it using loss and gradient evidence.
Phase study guide: preparation, milestones and project evidence
Before you start
Before this: Train a CNN on a small dataset
Hardware: CPU
Setup help: Python, notebook, or Cloud TPU VM
Use the tested CPU packages (the recorded environment uses Python 3.14.3). Other Python versions and accelerator environments require separate checks. The optional Cloud TPU specialization explains the target-specific execution boundaries. Logical CPU devices practice partitioning; they do not simulate TPU performance.
Download the repository ZIP or use your existing checkout. Extract it and open a terminal in the folder containing requirements-cpu.txt. The beginner ZIP is separate; these lessons use the full repository folder.
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements-cpu.txtpy -3 -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements-cpu.txtAfter activation, run the complete worked example from that same folder:
python phases/05-networks/05-debug-unstable-learning/code/main.py.\.venv\Scripts\python.exe phases/05-networks/05-debug-unstable-learning/code/main.pyFor an incremental build, create main.py in your own practice folder, append each build block in order, and run python main.py with this environment active. Keep that file separate from the downloaded reference solution.
Download this lesson’s notebook. Open it with a Python kernel using the course environment. Run cells from the top after restarting the kernel; experiments depend on earlier definitions.
For local Jupyter, first install the course requirements using the Local Python steps, then use your environment’s Python to start Jupyter:
python -m pip install jupyterlab
python -m jupyter lab.\.venv\Scripts\python.exe -m pip install jupyterlab
.\.venv\Scripts\python.exe -m jupyter labSelect the downloaded networks-05.ipynb file in JupyterLab. The notebook contains reference solutions; make your prediction and attempt each practice task before opening them. Restart the kernel before changing logical device setup.
The worked reference for this lesson is CPU-validated. Some references explicitly configure logical CPU devices; setting JAX_PLATFORMS does not adapt them to a TPU. Follow the execution matrix and reviewed multi-host example before attempting a target run.
For accelerator preparation, study TPU setup, observed runtime receipts, checkpointed jobs and precision and retained traces. These target paths remain unqualified on physical TPU hardware until an actual receipt is recorded. Cloud provisioning is optional for CPU learning.
Check: Rate approaches weight . Rate produces alternating growing errors; consecutive unstable losses have ratio before overflow.
Revisit the guided CPU setup · Open 19: TPU Setup. Package instructions are CPU instructions; Windows and accelerator environments need their own validation.
What you will be able to do
- Derive the gradient and curvature on paper
- Record pre-update and post-update quantities distinctly
- Use gradient clipping for a bounded symptom, not a diagnosis
- Change the feature scale and predict the new bound
The problem
The loss is growing. Should you immediately lower the learning rate? Let’s first gather evidence. We’ll use a one-weight model whose update we can derive, deliberately make it unstable, and inspect its losses, gradients and weights. You’ll learn how to connect a symptom to a cause.
The idea
Unstable learning is a symptom with several possible causes. Connect the first unexpected value to inputs, forward computation, loss, gradients, optimizer state or the applied update. That ordering turns a vague failure into a testable diagnosis.
Locate the first broken boundary
Start with a reproducible failing batch. Check inputs and forward loss before differentiating. If the loss is finite but gradients are not, inspect derivative-sensitive operations. If gradients are finite but the next loss explodes, inspect update size and parameter change.
Change one factor at a time and keep the original failing case. Clipping can limit damage without repairing the cause, so test whether the intervention predicts behavior on a changed input or learning rate.
The loss curve shows when behavior diverges. To explain why, add the relevant evidence: gradient norm, parameter norm, update norm and the first nonfinite intermediate. Keep those quantities distinct; a large parameter norm does not automatically imply a large gradient.
Derive the gradient and curvature on paper
The training pairs are and . The head predicts , so the mean squared loss is . The gradient is , and the constant curvature is . Gradient descent transforms the weight error into . Stability requires the magnitude of that multiplier to be less than one, hence for this specific objective. This calculation lets us predict failure before observing a plot.
Record pre-update and post-update quantities distinctly
At each iteration, record the current weight, loss, and absolute gradient, then compute the new weight. The history therefore describes the parameter entering each update. At rate the error multiplier is about ; the weight converges quickly. At rate the multiplier is ; errors alternate sign and grow in magnitude, with loss increasing by a factor per update until precision limits intervene. We run only six unstable steps to preserve a readable finite trace. A finite-number check catches overflow, but finite values can still represent a badly diverging run.
Use gradient clipping for a bounded symptom, not a diagnosis
Clipping a scalar gradient to magnitude one limits each rate-0.3 update to at most . That prevents the explosive trajectory in this small example, but it is separate from a generally correct learning rate, good final accuracy, or a solved numerical problem. Inspect the original norm as well as the clipped update. A repeatedly clipped run may indicate badly scaled features or excessive rate. Diagnose the data and objective before adopting clipping as a permanent fix. For cross-entropy models, large steps can produce confidently wrong finite predictions; monitoring only NaNs would miss them.
Change the feature scale and predict the new bound
Multiply every and by ten while keeping the true weight two. The loss and curvature multiply by , and the stable learning-rate ceiling divides by . A rate of that was stable on the original data is now unstable. This demonstrates why a copied learning rate can fail after preprocessing changes. Standardization, objective normalization, adaptive optimizers, and clipping affect update behavior in different ways. Preserve an independent analytic check when possible, then instrument real networks with finite checks, gradient norms, prediction ranges, class balance, and evaluation isolation. Do not apply the quadratic bound to an unrelated model.
Correct gradients can still produce unstable training
Predict before looking: Which learning rate crosses the stability limit?
Read the figure
The horizontal axis is the pre-update step: step shows the initial loss before training. The vertical axis is mean squared loss on a logarithmic scale. Both runs start at approximately , but the solid rate- curve falls while the dashed rate- curve rises.
After the first update, the smaller rate gives loss about , whereas the larger rate gives about . By the last plotted step, the losses differ by many orders of magnitude. Equal vertical distances represent equal ratios, not equal absolute changes.
Connect it to the computation
The loss here has curvature . A step multiplies the weight error by . At rate , this factor is ; at rate , it is . The larger rate crosses the optimum and increases the error’s magnitude, so squared loss grows by a factor of per update.
The derivative can be correct while the update is unstable. That is the diagnosis this comparison isolates: change the step size before assuming a rising loss proves broken autodiff. The loss curve itself hides the sign alternation; the update equation explains it.
Open full-size figureDownload data and provenance
The notebook contains the data experiment, plotting code, and recorded figure output. Rerun it to test your changes.
Where to put & run this code: Work in phases/05-networks/05-debug-unstable-learning/code/main.py (or downloaded exercises/networks-05.py / exercises/networks-05.ipynb) and execute from the repository root with python3 phases/05-networks/05-debug-unstable-learning/code/main.py.
1 Define the controlled objective
Create main.py. Calculate and the gradient at before running this block.
# Step 1 — 1. Define the controlled objective: The initial loss is 56/3 and the initial gradient is -56/3.
import jax
import jax.numpy as jnp
import numpy as np
x=jnp.array([1.,2.,3.])
y=2*x
def loss(w,features=x,targets=y):return jnp.mean((w*features-targets)**2)
def analytic_gradient(w):return (28/3)*(w-2)
np.testing.assert_allclose(loss(0.),56/3,rtol=1e-6)
# Differentiate the objective to obtain gradients ``.
np.testing.assert_allclose(jax.grad(loss)(0.),-56/3,rtol=1e-6)Code building blocks in this step
jnp.array(values, dtype=...)— Constructs an immutable device-backed JAX array from Python/NumPy values.x.sum(axis=...) / x.mean(axis=..., keepdims=...)— Reduces values along the namedaxis(the axis you name is collapsed unlesskeepdims=True).jnp.allclose(actual, expected, rtol=..., atol=...)— Checks that two arrays match elementwise within floating-point tolerance.jax.grad(loss_fn)(params, ...)— Transforms a scalar-output function into a function returning the gradient PyTree with the same structure asparams.
The initial loss is and the initial gradient is . These values independently anchor the autodiff trace.
2 Instrument the update loop
Append the runner. Each history row stores pre-update weight, loss, and gradient magnitude; no optimizer library hides the update.
# Step 2 — 2. Instrument the update loop: The rate changes while initial state, data, and objective stay fixed.
# Define `run(rate, steps, features, targets...)` to evaluate the objective and its automatic derivatives:
def run(rate,steps,features=x,targets=y,clip=None):
w=jnp.array(0.)
history=[]
objective=lambda value:loss(value,features,targets)
for _ in range(steps):
# Differentiate the objective to obtain `(value, grad)` via automatic differentiation.
value,grad=jax.value_and_grad(objective)(w)
history.append([float(w),float(value),float(jnp.abs(grad))])
# Confirm that all computed values remain finite (no NaN or Inf).
assert np.all(np.isfinite(history[-1]))
# Combine or mask array elements to form `update`.
update=jnp.clip(grad,-clip,clip) if clip is not None else grad
w=w-rate*update
return float(w),np.asarray(history)
stable,good=run(.1,6)
unstable,bad=run(.3,6)
assert abs(stable-2)<1e-5 and bad[-1,1]>bad[0,1]*100Code building blocks in this step
jnp.array(values, dtype=...)— Constructs an immutable device-backed JAX array from Python/NumPy values.jnp.isfinite(x)— Returns a boolean mask verifying that no element isNaNorInf.jax.value_and_grad(loss_fn)(params, ...)— Evaluates both the scalar loss and its gradient PyTree(loss_val, grads)in a single forward+backward pass.
The rate changes while initial state, data, and objective stay fixed. This isolates the intervention.
3 Match the recorded failure to the exact recurrence
Append analytic comparisons for every recorded iteration, then print the finite divergence trace.
# Step 3 — 3. Match the recorded failure to the exact recurrence: An alternating parameter error and a 3.24 loss multiplier support...
for rate,history in [(.1,good),(.3,bad)]:
expected_w=2-2*(1-rate*28/3)**np.arange(len(history))
expected_loss=(14/3)*(expected_w-2)**2
np.testing.assert_allclose(history[:,0],expected_w,rtol=2e-5,atol=1e-5)
np.testing.assert_allclose(history[:,1],expected_loss,rtol=2e-5,atol=1e-8)
np.testing.assert_allclose(bad[1:,1]/bad[:-1,1],3.24,rtol=1e-5)
print('Stable final weight:',stable,'unstable pre-update [weight,loss,|gradient|]:\n',bad)Code building blocks in this step
jnp.allclose(actual, expected, rtol=..., atol=...)— Checks that two arrays match elementwise within floating-point tolerance.
An alternating parameter error and a loss multiplier support the learning-rate diagnosis. Near zero, float32 cancellation makes relative loss error misleading, so the analytic comparison uses a absolute floor. No timing or accelerator result is claimed.
View the cumulative build in main.py
These blocks append in order to the same file.
# Step 1 — 1. Define the controlled objective: The initial loss is 56/3 and the initial gradient is -56/3.
import jax
import jax.numpy as jnp
import numpy as np
x=jnp.array([1.,2.,3.])
y=2*x
def loss(w,features=x,targets=y):return jnp.mean((w*features-targets)**2)
def analytic_gradient(w):return (28/3)*(w-2)
np.testing.assert_allclose(loss(0.),56/3,rtol=1e-6)
# Differentiate the objective to obtain gradients ``.
np.testing.assert_allclose(jax.grad(loss)(0.),-56/3,rtol=1e-6)
# Step 2 — 2. Instrument the update loop: The rate changes while initial state, data, and objective stay fixed.
# Define `run(rate, steps, features, targets...)` to evaluate the objective and its automatic derivatives:
def run(rate,steps,features=x,targets=y,clip=None):
w=jnp.array(0.)
history=[]
objective=lambda value:loss(value,features,targets)
for _ in range(steps):
# Differentiate the objective to obtain `(value, grad)` via automatic differentiation.
value,grad=jax.value_and_grad(objective)(w)
history.append([float(w),float(value),float(jnp.abs(grad))])
# Confirm that all computed values remain finite (no NaN or Inf).
assert np.all(np.isfinite(history[-1]))
# Combine or mask array elements to form `update`.
update=jnp.clip(grad,-clip,clip) if clip is not None else grad
w=w-rate*update
return float(w),np.asarray(history)
stable,good=run(.1,6)
unstable,bad=run(.3,6)
assert abs(stable-2)<1e-5 and bad[-1,1]>bad[0,1]*100
# Step 3 — 3. Match the recorded failure to the exact recurrence: An alternating parameter error and a 3.24 loss multiplier support...
for rate,history in [(.1,good),(.3,bad)]:
expected_w=2-2*(1-rate*28/3)**np.arange(len(history))
expected_loss=(14/3)*(expected_w-2)**2
np.testing.assert_allclose(history[:,0],expected_w,rtol=2e-5,atol=1e-5)
np.testing.assert_allclose(history[:,1],expected_loss,rtol=2e-5,atol=1e-8)
np.testing.assert_allclose(bad[1:,1]/bad[:-1,1],3.24,rtol=1e-5)
print('Stable final weight:',stable,'unstable pre-update [weight,loss,|gradient|]:\n',bad)Run the example
python3 phases/05-networks/05-debug-unstable-learning/code/main.pyAbout this command
This shortcut uses an activated macOS/Linux course environment in the repository folder. For Windows, use the explicit interpreter command in “Setup help” above. The script includes worked examples and reference solutions.
Inspect the complete assembled example
# Step 1 — 1. Define the controlled objective: The initial loss is 56/3 and the initial gradient is -56/3.
import jax
import jax.numpy as jnp
import numpy as np
x=jnp.array([1.,2.,3.])
y=2*x
def loss(w,features=x,targets=y):return jnp.mean((w*features-targets)**2)
def analytic_gradient(w):return (28/3)*(w-2)
np.testing.assert_allclose(loss(0.),56/3,rtol=1e-6)
# Differentiate the objective to obtain gradients ``.
np.testing.assert_allclose(jax.grad(loss)(0.),-56/3,rtol=1e-6)
# Step 2 — 2. Instrument the update loop: The rate changes while initial state, data, and objective stay fixed.
# Define `run(rate, steps, features, targets...)` to evaluate the objective and its automatic derivatives:
def run(rate,steps,features=x,targets=y,clip=None):
w=jnp.array(0.)
history=[]
objective=lambda value:loss(value,features,targets)
for _ in range(steps):
# Differentiate the objective to obtain `(value, grad)` via automatic differentiation.
value,grad=jax.value_and_grad(objective)(w)
history.append([float(w),float(value),float(jnp.abs(grad))])
# Confirm that all computed values remain finite (no NaN or Inf).
assert np.all(np.isfinite(history[-1]))
# Combine or mask array elements to form `update`.
update=jnp.clip(grad,-clip,clip) if clip is not None else grad
w=w-rate*update
return float(w),np.asarray(history)
stable,good=run(.1,6)
unstable,bad=run(.3,6)
assert abs(stable-2)<1e-5 and bad[-1,1]>bad[0,1]*100
# Step 3 — 3. Match the recorded failure to the exact recurrence: An alternating parameter error and a 3.24 loss multiplier support...
for rate,history in [(.1,good),(.3,bad)]:
expected_w=2-2*(1-rate*28/3)**np.arange(len(history))
expected_loss=(14/3)*(expected_w-2)**2
np.testing.assert_allclose(history[:,0],expected_w,rtol=2e-5,atol=1e-5)
np.testing.assert_allclose(history[:,1],expected_loss,rtol=2e-5,atol=1e-8)
np.testing.assert_allclose(bad[1:,1]/bad[:-1,1],3.24,rtol=1e-5)
print('Stable final weight:',stable,'unstable pre-update [weight,loss,|gradient|]:\n',bad)Expected result: Rate approaches weight . Rate produces alternating growing errors; consecutive unstable losses have ratio before overflow.
Recorded reference execution
Reference output captured from executing the complete lesson script (code/main.py) and its assertions in the course CPU environment.
Stable final weight: 1.999999761581421 unstable pre-update [weight,loss,|gradient|]:
[[ 0.00000000e+00 1.86666679e+01 1.86666679e+01]
[ 5.60000038e+00 6.04800110e+01 3.36000061e+01]
[-4.48000145e+00 1.95955292e+02 6.04800148e+01]
[ 1.36640034e+01 6.34895264e+02 1.08864037e+02]
[-1.89952106e+01 2.05706152e+03 1.95955322e+02]
[ 3.97913895e+01 6.66488281e+03 3.52719635e+02]]
Stable final weight: 1.999999761581421 unstable pre-update [weight,loss,|gradient|]:
[[ 0.00000000e+00 1.86666679e+01 1.86666679e+01]
[ 5.60000038e+00 6.04800110e+01 3.36000061e+01]
[-4.48000145e+00 1.95955292e+02 6.04800148e+01]
[ 1.36640034e+01 6.34895264e+02 1.08864037e+02]
[-1.89952106e+01 2.05706152e+03 1.95955322e+02]
[ 3.97913895e+01 6.66488281e+03 3.52719635e+02]]
Reference / compensated / unchanged-rate updates: 1.8833334 1.8833332 8.416667
PASS: networks-05
Download execution record · The notebook includes executed cells and figure outputs. Compare your run with these values; timings depend on the machine.
Bounded clipped updates
# Experiment — Bounded clipped updates: Clipping changes the effective update.
clipped,clipped_history=run(.3,6,clip=1.)
assert np.all(np.abs(np.diff(clipped_history[:,0]))<=.300001)
assert clipped_history[-1,1]<clipped_history[0,1]
assert clipped_history[0,2]>1.Expected: Each recorded displacement is at most while the original gradient norm initially exceeds one.
Clipping changes the effective update. Its boundedness is a useful invariant, not proof of optimal training.
Feature scaling changes curvature
# Experiment — Feature scaling changes curvature: The gradient multiplies by 100 because both prediction and...
scaled_final,scaled=run(.1,3,x*10,y*10)
assert scaled[-1,1]>scaled[0,1]
adjusted_final,adjusted=run(.001,6,x*10,y*10)
np.testing.assert_allclose(adjusted[:,0],good[:,0],rtol=1e-5,atol=1e-5)
np.testing.assert_allclose(adjusted[:,1],good[:,1]*100,rtol=1e-4,atol=1e-8)Expected: Rate diverges after scaling. Dividing the rate by restores the original weight trajectory.
The gradient multiplies by because both prediction and target are scaled; adjusting rate compensates for this fixture.
Make it yours
Where to put your code: Append your solution at the bottom of phases/05-networks/05-debug-unstable-learning/code/main.py (or inside the Make it yours cell of exercises/networks-05.ipynb) and run python3 phases/05-networks/05-debug-unstable-learning/code/main.py to verify your prediction before opening the reference solution.
Use features and targets . Predict the new stability ceiling. Compare stable rate and unstable rate .
Show a writing guide and starter scaffold
How to write this exercise code: step-by-step recipe & starter scaffold
1. Key functions & syntax to use:
jnp.zeros / jnp.ones(shape, dtype=...)— Allocates a tensor of the givenshapeinitialized with constants.jnp.allclose(actual, expected, rtol=..., atol=...)— Checks that two arrays match elementwise within floating-point tolerance.
2. Step-by-step implementation plan:
- Set up the changed input or configuration parameters requested in the prompt.
- Run the target function or transformation and assert that the output matches your prediction.
# Exercise solution: Use features [1, 1, 1] and targets 2.
ones = jnp.ones(...) # TODO: compute ones
exact,history = run(...) # TODO: compute exact,history
divergent,divergent_history = run(...) # TODO: compute divergent,divergent_history
np.testing.assert_allclose(exact,2.,atol = ... # TODO: compute np.testing.assert_allclose(exact,2.,atol
assert divergent_history[-1,1] # TODO: complete assertion check
np.testing.assert_allclose(divergent_history[1:,1]/divergent_history[:-1,1],1.44,rtol=1e-5)
Reveal the reference solution
# Exercise solution: Use features [1, 1, 1] and targets 2.
ones=jnp.ones(3)
exact,history=run(.5,3,ones,2*ones)
divergent,divergent_history=run(1.1,6,ones,2*ones)
np.testing.assert_allclose(exact,2.,atol=1e-6)
assert divergent_history[-1,1]>divergent_history[0,1]
np.testing.assert_allclose(divergent_history[1:,1]/divergent_history[:-1,1],1.44,rtol=1e-5)Rescale the objective and compensate the update
Transfer
Multiply the squared-loss objective by five. Compare one plain gradient-descent update at the old rate and at one fifth of that rate. Explain why this compensation does not automatically describe Adam or clipping.
Show a writing guide and starter scaffold
How to write this practice solution: step-by-step recipe & starter scaffold
1. Key functions & syntax to use:
jnp.array(values, dtype=...)— Constructs an immutable device-backed JAX array from Python/NumPy values.jnp.allclose(actual, expected, rtol=..., atol=...)— Checks that two arrays match elementwise within floating-point tolerance.jax.grad(loss_fn)(params, ...)— Transforms a scalar-output function into a function returning the gradient PyTree with the same structure asparams.
2. Step-by-step implementation plan:
- Differentiate the objective to obtain
base_gradientvia automatic differentiation. - Differentiate the objective to obtain
scaled_gradientvia automatic differentiation.
# Rescale the objective and compensate the update (Transfer): Scaling the objective scales the gradient.
w0 = jnp.array(...) # TODO: compute w0
# Differentiate the objective to obtain `base_gradient` via automatic differentiation.
base_gradient = jax.grad(...) # TODO: compute base_gradient
# Differentiate the objective to obtain `scaled_gradient` via automatic differentiation.
scaled_gradient = jax.grad(...) # TODO: compute scaled_gradient
np.testing.assert_allclose(scaled_gradient,5*base_gradient,rtol=1e-6)
reference = ... # TODO: compute reference
compensated = ... # TODO: compute compensated
uncompensated = ... # TODO: compute uncompensated
np.testing.assert_allclose(compensated,reference,rtol = ... # TODO: compute np.testing.assert_allclose(compensated,reference,rtol
assert not np.isclose(uncompensated,reference) # TODO: complete assertion check
print('Reference / compensated / unchanged-rate updates:',reference,compensated,uncompensated)
Show a hint
Differentiate the scaled objective; plain gradient descent multiplies its gradient by the learning rate.
Reveal reference solution and reasoning
# Rescale the objective and compensate the update (Transfer): Scaling the objective scales the gradient.
w0=jnp.array(.25)
# Differentiate the objective to obtain `base_gradient` via automatic differentiation.
base_gradient=jax.grad(loss)(w0)
# Differentiate the objective to obtain `scaled_gradient` via automatic differentiation.
scaled_gradient=jax.grad(lambda w:5*loss(w))(w0)
np.testing.assert_allclose(scaled_gradient,5*base_gradient,rtol=1e-6)
reference=w0-.1*base_gradient
compensated=w0-(.1/5)*scaled_gradient
uncompensated=w0-.1*scaled_gradient
np.testing.assert_allclose(compensated,reference,rtol=1e-6)
assert not np.isclose(uncompensated,reference)
print('Reference / compensated / unchanged-rate updates:',reference,compensated,uncompensated)Scaling the objective scales the gradient. For this plain update the inverse rate change cancels it exactly. Stateful normalization, clipping and regularization need their own analysis.
Diagnose a nonfinite input before an update
Intermediate
Insert one NaN feature. Show that loss/gradient finite checks fail, then repair the source data and recheck the independent initial gradient.
Show a writing guide and starter scaffold
How to write this practice solution: step-by-step recipe & starter scaffold
1. Key functions & syntax to use:
jnp.array(values, dtype=...)— Constructs an immutable device-backed JAX array from Python/NumPy values.jnp.allclose(actual, expected, rtol=..., atol=...)— Checks that two arrays match elementwise within floating-point tolerance.jnp.isfinite(x)— Returns a boolean mask verifying that no element isNaNorInf.jax.value_and_grad(loss_fn)(params, ...)— Evaluates both the scalar loss and its gradient PyTree(loss_val, grads)in a single forward+backward pass.
2. Step-by-step implementation plan:
- Diagnose a nonfinite input before an update (Intermediate): Nonfinite data is a different cause than excessive step size.
- Differentiate the objective to obtain
(bad_value, bad_grad)via automatic differentiation. - Confirm that all computed values remain finite (no NaN or Inf).
- Differentiate the objective to obtain
(repaired_value, repaired_grad)via automatic differentiation. - Confirm that all computed values remain finite (no NaN or Inf).
# Diagnose a nonfinite input before an update (Intermediate): Nonfinite data is a different cause than excessive step size.
corrupted = ... # TODO: compute corrupted
# Differentiate the objective to obtain `(bad_value, bad_grad)` via automatic differentiation.
bad_value,bad_grad = jax.value_and_grad(...) # TODO: compute bad_value,bad_grad
# Confirm that all computed values remain finite (no NaN or Inf).
assert not np.isfinite(float(bad_value)) # TODO: complete assertion check
repaired = jnp.array(...) # TODO: compute repaired
# Differentiate the objective to obtain `(repaired_value, repaired_grad)` via automatic differentiation.
repaired_value,repaired_grad = jax.value_and_grad(...) # TODO: compute repaired_value,repaired_grad
np.testing.assert_allclose(repaired_grad,-56/3,rtol=1e-6)
# Confirm that all computed values remain finite (no NaN or Inf).
assert np.isfinite(float(repaired_value)) # TODO: complete assertion check
Show a hint
Changing the learning rate cannot make a NaN observation valid.
Reveal reference solution and reasoning
# Diagnose a nonfinite input before an update (Intermediate): Nonfinite data is a different cause than excessive step size.
corrupted=x.at[1].set(jnp.nan)
# Differentiate the objective to obtain `(bad_value, bad_grad)` via automatic differentiation.
bad_value,bad_grad=jax.value_and_grad(lambda w:loss(w,corrupted,y))(0.)
# Confirm that all computed values remain finite (no NaN or Inf).
assert not np.isfinite(float(bad_value)) and not np.isfinite(float(bad_grad))
repaired=jnp.array([1.,2.,3.])
# Differentiate the objective to obtain `(repaired_value, repaired_grad)` via automatic differentiation.
repaired_value,repaired_grad=jax.value_and_grad(lambda w:loss(w,repaired,y))(0.)
np.testing.assert_allclose(repaired_grad,-56/3,rtol=1e-6)
# Confirm that all computed values remain finite (no NaN or Inf).
assert np.isfinite(float(repaired_value))Nonfinite data is a different cause than excessive step size. Repair it at ingestion and validate the objective before taking another update.
Diagnose the result
Start with finite input and loss checks, then inspect shape contracts and independent objective values. Record gradient norm, parameter norm, learning rate, and step index. Alternating growing errors can support a curvature diagnosis; NaNs from corrupted inputs require data repair. Preserve separate train and held-out metrics before declaring the fix successful.
Carry forward
- Derive the gradient and curvature on paper
- Record pre-update and post-update quantities distinctly
- Use gradient clipping for a bounded symptom, not a diagnosis
- Change the feature scale and predict the new bound
Keep your evidence
Keep the analytic error recurrence, per-step weight/loss/gradient norm for rates 0.1 and 0.3, observed 3.24 loss multiplier, changed-scale comparison, clipping bound, and one repaired nonfinite-input case.
Save your modified code, its output, and an explanation of your prediction. Checkpoint answers and exercise evidence are tracked separately.
Saved in this browser. Export or back up your portfolio anytime in My learning.
Verified on local CPU. Stage to a Cloud TPU VM via Cloud TPU setup to record accelerator execution receipts (TODO: add automated Cloud TPU CI execution receipts).
Primary references
Phase 05 Integration Project
Bring this phase together: Build and audit a neural classifier
Checkpoint to prove: Keep train and evaluation behavior separate and reproduce metrics from a saved configuration.
Copy projects/mlp-classifier/starter/model.py to projects/mlp-classifier/my_model.py and write your code in projects/mlp-classifier/my_model.py. Run python3 projects/mlp-classifier/tests/check.py --implementation projects/mlp-classifier/my_model.py --stage 1 from the top-level folder to verify all 3 stages.