[02-layers/03-dropout] computes a genuinely DIFFERENT function depending on whether the network is currently training or being evaluated: during training, it randomly zeroes activations; during evaluation, it should pass everything through unchanged (no randomness at all, since you want a model's predictions on the same input to be deterministic and reproducible at inference time). Batch normalization (covered conceptually later in this Part, Internal covariate shift, and what BatchNorm was actually designed to fix) has an even sharper version of the same problem: it computes statistics from the CURRENT batch during training, but at inference time (often on a single sample, where "batch statistics" would be meaningless or wildly unstable) it needs to use FIXED statistics accumulated during training instead.
Both layers need to know, at the moment forward is called, which mode the network is currently in. Asking every training script to manually pass a training=True/False flag into every single layer's forward call, all the way through a deeply nested network, would be extremely error-prone (easy to forget for one layer buried three levels deep). The standard solution: give every layer a single shared self.training flag, and one recursive train()/eval() call on the TOP-LEVEL model that flips it for every layer in the whole tree at once.
Implement TrainableModule, a subclass of [02-layers/05-module-base-class]'s Module (already provided, reused via load_solution), adding a self.training flag (starts True, since a freshly constructed model should default to training mode) and two methods: train(mode=True), which sets self.training = mode on THIS module and recursively calls .train(mode) on every child module too (so calling .train() on the top-level model flips the flag for EVERY layer in the whole tree, no matter how deeply nested), and eval(), a convenience shortcut equal to train(False).
train(mode) must set self.training on the CURRENT module AND recursively propagate the same mode to every child module in self._modules (at any depth, since a child's own train() call recurses into ITS children too).train() (called with no arguments) must default to mode=True.eval() must be exactly equivalent to calling train(False).train and eval should return self, so calls can be chained (matching PyTorch's own convention, e.g. model.train().to(device)).self.training = mode sets the flag on the current module. Then loop for submodule in self._modules.values(): submodule.train(mode), calling the SAME method recursively, exactly the pattern [05-module-base-class]'s parameters() used for recursive parameter collection.
eval is a one-liner: return self.train(False). Since train already returns self, eval gets the chaining behavior for free.
Don't forget return self at the end of train, after the loop, this is what allows model.train() to be chained with further calls.
Click "Run Tests" to test your implementation