Tensor Flow 📂 Neural Network From Scratch · 1 of 6 25 min read

tf.Module Explained: The Base for Layers and Models in TensorFlow

Learn how tf.Module tracks variables and sub-modules for you. Build a module, nest layers inside a model, list trainable_variables, freeze parts with trainable=False, add tf.function and save weights with tf.train.Checkpoint. Stack layers in the live Model Tree Builder and watch every variable and parameter appear.

Section 01

The Story — A Box That Knows Its Own Parts

The Toolbox With a Built-in Checklist
Imagine a toolbox that can answer one question: "What tools are inside you?" It checks its own trays, and every smaller box inside it, and gives you the full list.

When you build a neural network, you need exactly this. A model has layers. Layers have weights and biases. To train, you must hand every weight to the optimizer. Tracking them by hand is slow and easy to get wrong.

tf.Module is that smart toolbox. Put tf.Variables and other modules inside it as attributes, and it will find all of them for you.

In this module you will build a complete neural network from scratch: layers, activations, losses, optimizers and a training loop. No Keras layers, no model.fit(). Everything starts with tf.Module. (Keras layers and models are themselves built on top of it.)


Section 02

Your First Module

A module is a Python class. Create variables in __init__. Do the maths in __call__.

import tensorflow as tf

class Scale(tf.Module):
    def __init__(self, name=None):
        super().__init__(name=name)
        self.a = tf.Variable(2.0, name="a")                   # trainable
        self.b = tf.Variable(1.0, name="b")                   # trainable
        self.calls = tf.Variable(0, trainable=False, name="calls")

    def __call__(self, x):
        self.calls.assign_add(1)
        return self.a * x + self.b

s = Scale(name="scale")
print(s(tf.constant([1.0, 2.0, 3.0])))
print("all variables      :", [v.name for v in s.variables])
print("trainable variables:", [v.name for v in s.trainable_variables])
OUTPUT
tf.Tensor([3. 5. 7.], shape=(3,), dtype=float32) all variables : ['a:0', 'b:0', 'calls:0'] trainable variables: ['a:0', 'b:0']

You never wrote a list of variables. The module found them by looking at its own attributes. The non-trainable counter is in variables but not in trainable_variables.


Section 03

Modules Inside Modules

A model is a module that holds layer modules. The tracking goes all the way down.

class Linear(tf.Module):
    def __init__(self, in_dim, out_dim, name=None, trainable=True):
        super().__init__(name=name)
        with self.name_scope:                                  # prefix names with the module name
            self.w = tf.Variable(tf.random.normal([in_dim, out_dim]), name="w", trainable=trainable)
            self.b = tf.Variable(tf.zeros([out_dim]), name="b", trainable=trainable)

    def __call__(self, x):
        return x @ self.w + self.b

class TwoLayerNet(tf.Module):
    def __init__(self, name=None):
        super().__init__(name=name)
        self.hidden = Linear(4, 8, name="hidden")
        self.out = Linear(8, 3, name="out")

    def __call__(self, x):
        return self.out(tf.nn.relu(self.hidden(x)))

net = TwoLayerNet(name="net")
print("output shape:", net(tf.ones([2, 4])).shape)
print("submodules  :", [m.name for m in net.submodules])
for v in net.trainable_variables:
    print(f"  {v.name:12s} {v.shape}")
print("parameters  :", sum(int(tf.size(v)) for v in net.trainable_variables))
OUTPUT
output shape: (2, 3) submodules : ['hidden', 'out'] hidden/b:0 (8,) hidden/w:0 (4, 8) out/b:0 (3,) out/w:0 (8, 3) parameters : 67
Animated Diagram — How a Module Finds Its Variables
net (TwoLayerNet) hidden (Linear) out (Linear) w (4, 8) b (8,) w (8, 3) b (3,) → net.trainable_variables = [w, b, w, b]

Each purple dot is a variable being collected. The search walks every attribute, then every submodule's attributes.


Section 04

Try It — Model Tree Builder

🌳 Stack Layers, Watch the Variables Appear Interactive

Set the input size and type the units of each layer, like 16, 8, 3. The module tree and variable list update live. Click a layer in the tree to freeze it: its variables are created with trainable=False and leave trainable_variables.

Module tree

net.trainable_variables

nameshapecount

Section 05

Fast Calls and Saving

Add tf.function

Decorate __call__ (or wrap the whole model) to run it as a graph. Everything from the last module still applies.

class FastNet(TwoLayerNet):
    @tf.function(input_signature=[tf.TensorSpec([None, 4], tf.float32)])
    def __call__(self, x):
        return super().__call__(x)

fast = FastNet(name="fast")
print(fast(tf.ones([5, 4])).shape)
OUTPUT
(5, 3)

Save and Restore Weights With a Checkpoint

ckpt = tf.train.Checkpoint(model=net)
path = ckpt.write("/tmp/net_ckpt")

before = net.out.b.numpy().copy()
net.out.b.assign([9.0, 9.0, 9.0])               # damage the weights
ckpt.read(path)                                   # restore them
print("restored:", (net.out.b.numpy() == before).all())
OUTPUT
restored: True

Section 06

tf.Module vs Keras Layer

Featuretf.Moduletf.keras.layers.Layer
Tracks variables and submodulesYesYes
Checkpoint and SavedModelYesYes
Builds weights from the first input shapeYou write itbuild() does it
fit(), compile(), callbacksNoYes (in a Model)
Best forLearning how it works, research, full controlEveryday model building
🎓
Why Learn It the Hard Way?

When you build a network from tf.Module, nothing is hidden. Later, when a Keras model gives a strange result, you will know exactly what each piece is doing inside.


Section 07

Golden Rules

🧰 tf.Module — Rules to Remember
1
Subclass tf.Module, call super().__init__(name=name), create variables in __init__.
2
Store variables and sub-modules as attributes (or in lists/dicts on attributes). That is how they are found.
3
Give the optimizer model.trainable_variables. Never build that list by hand.
4
Use trainable=False for counters and frozen parts.
5
Save weights with tf.train.Checkpoint; add @tf.function for speed.