Skip to content
32intermediate

Neural Network Module System

Covers the Module base class: writing a custom module, listing parameters, saving and loading a state dict, train and eval modes, freezing parameters, forward hooks and the Sequential container. It also shows when forward returns a GradTensor and when it returns a plain Tensor.

What you will learn

  • Use deepbox/ndarray for tensor, noGrad, GradTensor.isGradTensor.
  • Use deepbox/nn for Module, Linear, ReLU, Sequential, stateDict, freezeParameters, hooks.
  • Covers the Module base class: writing a custom module, listing parameters, saving and loading a state dict, train and eval modes, freezing parameters, forward hooks and the Sequential container. It also shows when forward returns a GradTensor and when it returns a plain Tensor.

Source

index.ts
/** * Example 32: Neural Network Module System * * The Module base class: parameter registration, state dicts, train/eval * modes, freeze/unfreeze, forward hooks, and the Sequential container. */ import { type AnyTensor, GradTensor, noGrad, tensor } from "deepbox/ndarray";import { Linear, Module, ReLU, Sequential } from "deepbox/nn"; console.log("=== Neural Network Module System ===\n"); // ---------------------------------------------------------------------------// Part 1: Custom Module with parameter registration// ---------------------------------------------------------------------------console.log("--- Part 1: Custom Module ---"); class MyNet extends Module {  fc1: Linear;  relu: ReLU;  fc2: Linear;   constructor(inputDim: number, hiddenDim: number, outputDim: number) {    super();    this.fc1 = new Linear(inputDim, hiddenDim);    this.relu = new ReLU();    this.fc2 = new Linear(hiddenDim, outputDim);    this.registerModule("fc1", this.fc1);    this.registerModule("relu", this.relu);    this.registerModule("fc2", this.fc2);  }   override forward(x: AnyTensor): AnyTensor {    return this.fc2.forward(this.relu.forward(this.fc1.forward(x)));  }} const net = new MyNet(4, 8, 2);console.log("MyNet(4 -> 8 -> 2)"); // ---------------------------------------------------------------------------// Part 2: Parameter enumeration// ---------------------------------------------------------------------------console.log("\n--- Part 2: Parameters ---"); // parameters() and namedParameters() yield GradTensors, the type optimizers takeconst named = Array.from(net.namedParameters());console.log(`Total parameter tensors: ${named.length}`);for (const [name, p] of named) {  console.log(`  ${name.padEnd(11)} shape [${p.shape.join(", ")}]`);} // ---------------------------------------------------------------------------// Part 3: State dict (serialization & loading)// ---------------------------------------------------------------------------console.log("\n--- Part 3: State Dict ---"); const stateDict = net.stateDict();console.log("State dict parameter keys:");for (const key of Object.keys(stateDict.parameters)) {  console.log(`  ${key}`);} // Load the state dict back, for example from a saved checkpointnet.loadStateDict(stateDict);console.log("State dict loaded"); // ---------------------------------------------------------------------------// Part 4: Train/Eval mode// ---------------------------------------------------------------------------console.log("\n--- Part 4: Train/Eval Mode ---"); net.train();console.log(`After train(): training = ${net.training}`); net.eval();console.log(`After eval():  training = ${net.training}`);console.log("  eval() turns dropout off and makes batch norm use its running statistics");console.log("  It does not stop gradient tracking. Use noGrad() for that."); // ---------------------------------------------------------------------------// Part 5: Freeze/Unfreeze parameters// ---------------------------------------------------------------------------console.log("\n--- Part 5: Freeze/Unfreeze ---"); const countTrainable = (module: Module): number =>  Array.from(module.parameters()).filter((p) => p.requiresGrad).length; console.log(`Trainable parameter tensors: ${countTrainable(net)}`); net.freezeParameters();console.log(`After freezeParameters(): ${countTrainable(net)}`); // Freeze or unfreeze selected parameters by namenet.unfreezeParameters(["fc2.weight", "fc2.bias"]);console.log(`After unfreezeParameters(["fc2.weight", "fc2.bias"]): ${countTrainable(net)}`); net.unfreezeParameters();console.log(`After unfreezeParameters(): ${countTrainable(net)}`); // ---------------------------------------------------------------------------// Part 6: Sequential container// ---------------------------------------------------------------------------console.log("\n--- Part 6: Sequential Container ---"); const seqModel = new Sequential(new Linear(4, 8), new ReLU(), new Linear(8, 2)); console.log("Sequential(Linear(4,8), ReLU, Linear(8,2))");console.log(`Parameter tensors: ${Array.from(seqModel.parameters()).length}`); // A plain tensor goes in, no parameter(...) wrapping neededconst input = tensor([[1, 2, 3, 4]]); // Training: the output is a GradTensor that tracks the weightsconst trainOutput = seqModel.forward(input);console.log(`Input shape:  [${input.shape.join(", ")}]`);console.log(`Output shape: [${trainOutput.shape.join(", ")}]`);console.log(`Output is a GradTensor: ${GradTensor.isGradTensor(trainOutput)}`);console.log(  `Output requiresGrad: ${GradTensor.isGradTensor(trainOutput) && trainOutput.requiresGrad}`); // Inference: inside noGrad() no graph is built and the output is a plain tensorconst inferOutput = noGrad(() => seqModel.forward(input));console.log(`Inside noGrad(), output is a GradTensor: ${GradTensor.isGradTensor(inferOutput)}`); // A model with every parameter frozen also returns a plain tensorseqModel.freezeParameters();console.log(  `With frozen parameters, output is a GradTensor: ${GradTensor.isGradTensor(seqModel.forward(input))}`);seqModel.unfreezeParameters(); // ---------------------------------------------------------------------------// Part 7: Forward hooks// ---------------------------------------------------------------------------console.log("\n--- Part 7: Forward Hooks ---"); // call() runs the hooks around forward(). Calling forward() directly skips them.const removeHook = seqModel.registerForwardHook((_module, _inputs, output) => {  console.log(`  hook saw output shape [${output.shape.join(", ")}]`);  return undefined; // keep the output unchanged});noGrad(() => seqModel.call(input));removeHook();console.log("Hook removed: the next call prints nothing");noGrad(() => seqModel.call(input)); console.log("\n=== Module System Complete ===");

Output

Console output
Console output only: parameter names and shapes, state dict keys, the `training` flag, counts of trainable parameters after freezing, the type returned by `forward` in each situation, and a forward hook firing once.