
EE 541 - Unit 7A
Fall 2026
ImageNet classification: \(1.2\) million training photographs, \(1{,}000\) classes
Lowest error each year (figure): layers in bold, parameters under each name
Same parameter count, \(8\) and \(152\) layers: about \(60\) million
Not depth alone

Task: transcribing multi-digit house numbers from Street View photographs (Goodfellow et al. 2014)
Three families, each grown by adding parameters to some of its layers
At about \(16\) million parameters: \(95.2\%\) with \(11\) layers, \(93.2\%\) with \(3\)
More parameters can lower accuracy: both \(3\)-layer curves fall past about \(20\) million, which the book attributes to overfitting

Goodfellow, Bengio, Courville, Deep Learning, Fig. 6.7
Layer \(l\): \(f^{(l)}(\mathbf{a}) = \sigma^{(l)}\big(\mathbf{W}^{(l)}\mathbf{a} + \mathbf{b}^{(l)}\big)\), \(\;\mathbf{W}^{(l)} \in \mathbb{R}^{n_l \times n_{l-1}}\), \(\;n_l n_{l-1} + n_l\) parameters
\(L\) layers, \(L - 1\) of them hidden:
\[\hat{\mathbf{y}} = f^{(L)}\Big(f^{(L-1)}\big(\cdots f^{(1)}(\mathbf{x})\cdots\big)\Big)\]
Two ways to grow a network (figure)
Equal parameter count, \(784\) inputs and \(10\) classes, \(269{,}322\) parameters: one hidden layer of \(339\) units, two of \(256\), four of \(195\), or eight of \(147\)

Network: four hidden ReLU layers of \(256\) units, trained on FashionMNIST, \(88.7\%\) test accuracy
Measured on \(2{,}000\) test images

Regression function \(\mathbb{E}[Y \mid X = x]\): the MMSE predictor
Any predictor \(f\), under squared loss:
\[\mathrm{MSE}(f) = \mathbb{E}[\mathrm{Var}(Y \mid X)] + \mathbb{E}\big[(f(X) - \mathbb{E}[Y \mid X])^2\big]\]
Model class \(\mathcal{F}\), with best member \(f^*\): the approximation error is the second term at \(f^*\)
Example (figure): \(g(x) = 0.2 + 0.4x^2 + 0.3x \sin 15x + 0.05 \cos 50x\), \(X\) uniform on \([0, 1]\), noise standard deviation \(0.05\)

Fixed features \(\phi_j\), chosen before the data: linear in the weights, fit by least squares
One hidden layer, the same sum with features fit from the data:
\[h(x) = \sum_{j=1}^{m} v_j\, \sigma(w_j x + b_j) + v_0\]
Each unit is a step (figure): \(\sigma(w_j x + b_j) = \sigma\big(w_j (x - c_j)\big)\), \(\;c_j = -b_j / w_j\)

Approximation theorem (Cybenko 1989; Leshno, Lin, Pinkus, Schocken 1993)
\[\max_{\mathbf{x} \in K} \big| h(\mathbf{x}) - g(\mathbf{x}) \big| < \varepsilon\]
Uniform: within \(\varepsilon\) at every input in \(K\), not on average
In network terms: \(m\) hidden units and a linear output
For regression, \(g = \mathbb{E}[Y \mid X = \mathbf{x}]\): approximation error below \(\varepsilon^2\)
Existence only: the theorem gives neither \(m\) nor the weights

Two units with the same steepness \(w\), steps at \(c_1 < c_2\), output weights \(+v\) and \(-v\):
\[v\,\big[\sigma(w(x - c_1)) - \sigma(w(x - c_2))\big]\]
Example, \(c_1 = 0.4\), \(c_2 = 0.6\), \(w = 50\), \(v = 0.8\)
Bump: height \(v\) on the cell \([c_1, c_2]\) of width \(\Delta = c_2 - c_1\), zero elsewhere

Construction for one input: \([0, 1]\) cut into cells of width \(\Delta\), one bump per cell with height \(g\) at the cell’s center
Example, every weight set from \(g\) (figure)
| \(\Delta\) | 0.2 | 0.1 | 0.05 | 0.025 | 0.0125 |
|---|---|---|---|---|---|
| hidden units | 10 | 20 | 40 | 80 | 160 |
| \(\max_x |h_\Delta - g|\) | 0.42 | 0.22 | 0.13 | 0.069 | 0.035 |
The theorem for \(d = 1\): \(g\) continuous, so a small enough \(\Delta\) keeps \(g\) within \(\varepsilon\) of its value at each center, and a large enough \(w\) makes each bump a box

Cells in \(d\) dimensions: \(\Delta^{-d}\), one value each (figure)
Error proportional to \(\Delta\): half the error needs \(2^d\) times the cells, \(1{,}024\) times at \(d = 10\)
Networks, \(g\) with \(s\) bounded derivatives (DeVore, Howard, Micchelli 1989; Mhaskar 1996; Yarotsky 2017)

Training: one hidden layer of sigmoid units, full-batch Adam, \(20{,}000\) steps, three seeds, on \(1{,}000\) noiseless points of \(g\) itself
Maximum error \(\max_x |h(x) - g(x)|\)
| hidden units | 10 | 20 | 40 | 80 | 160 |
|---|---|---|---|---|---|
| construction | 0.42 | 0.22 | 0.13 | 0.069 | 0.035 |
| trained, noiseless | 0.065 | 0.061 | 0.061 | 0.064 | 0.064 |
Trained fits are smooth (figure): they miss the fast oscillation, at \(10\) units and at \(160\)

Hidden unit \(j\): \(\mathrm{ReLU}(\mathbf{w}_j^T \mathbf{x} + b_j)\)
Fixed on/off pattern: the network is the sum of its on units, a linear function of \(\mathbf{x}\)
Example, three units on two inputs (figure): \(\mathbf{w}_1 = (1, 0)\), \(\mathbf{w}_2 = (0, 1)\), \(\mathbf{w}_3 = (1, 1)\), \(\mathbf{b} = (-0.3, -0.4, -1.2)\), \(\mathbf{v} = (1, -2, 1.5)\)

Network: one hidden layer of \(20\) ReLU units, FashionMNIST T-shirts/tops against trousers, \(99.05\%\) test accuracy
Weight change \(\mathbf{w}_j - \mathbf{w}_j^{\text{start}}\) of each unit, one value per pixel (figure)
T-shirt units: weights rose over the shoulders, sleeves, and body
Trouser units: weights rose along the legs and fell in the gap between them
Match: \(\mathbf{w}_j^T \mathbf{x}\) is large when bright pixels meet positive weights

Same network
On/off pattern of the \(20\) units on \(2{,}000\) test images (figure): red on, blue off
Each pattern selects one linear function (Yu, Chang, Shi, EE 541 project)

Layer 1: three steep steps, each near \(1\) on one side of a line
Layer 2: one unit, \(2\,\mathrm{ReLU}(h_1 + h_2 + h_3 - 2.5)\)
One hidden layer: a sum of steps, each constant along its whole line, so no single unit is confined to a bounded region

Two ReLU units: \(t(x) = \mathrm{ReLU}(2x - 1) + \mathrm{ReLU}(1 - 2x) = |2x - 1|\)
\(L\) layers \(t \circ \cdots \circ t\) (figure): \(2^L\) linear pieces from \(2L\) units
One hidden layer of \(m\) ReLU units, \(h(x) = \sum_{j} v_j\, \mathrm{ReLU}(w_j x + b_j) + v_0\)
| layers \(L\) | 3 | 10 | 20 |
|---|---|---|---|
| \(L\) two-unit layers | 6 units | 20 units | 40 units |
| one hidden layer | \(\geq 7\) | \(\geq 1{,}023\) | \(\geq 1{,}048{,}575\) |
Scope

Networks: \(1\) to \(32\) hidden ReLU layers of \(256\) units, FashionMNIST
Training
Starting weights, two choices
Test accuracy (top)
Training loss (bottom), \(\mathcal{N}(0, 1/n_{l-1})\): at \(32\) hidden layers, \(\log 10 = 2.30\) on all \(469\) batches, the loss of a uniform guess over \(10\) classes

Backward pass, in row form, with \(\mathbf{D}^{(k)} = \mathrm{diag}\big(\sigma'(\mathbf{s}^{(k)})\big)\):
\[\boldsymbol{\delta}^{(l)T} = \boldsymbol{\delta}^{(L)T}\, \mathbf{W}^{(L)} \mathbf{D}^{(L-1)} \cdots \mathbf{W}^{(l+1)} \mathbf{D}^{(l)}\]
Measured: \(\|\partial \mathcal{L} / \partial \mathbf{W}^{(1)}\|_F\) before training, the networks of the depth sweep (figure)
Backward pass exact at every depth: the gradient itself is small

The NumPy training program, five blocks
D1 is ReLU’s derivativeChanging the network: a new activation, layer, or loss changes the forward lines, and the backward lines must be derived again

PyTorch: an open-source Python library for building and training neural networks, first released by Facebook AI Research in 2016
loss.backward() computes every gradient from that recordtorch.nn: layers that hold parameterstorch.optim: update rulestorch.utils.data: loading data in batches
Tensor: an array, such as a batch, a weight matrix, or a loss
Operation: a function of tensors, such as X @ W.T, torch.relu, F.cross_entropy
Module: an operation packaged with the tensors it keeps between calls, its parameters
nn.Linear(784, 256) holds \(\mathbf{W}\) and \(\mathbf{b}\)nn.ReLU() holds noneModel: a module whose forward applies modules and operations in any order Python allows (figure)
Optimizer: holds the model’s parameters and updates them
DataLoader: produces the batch tensors

forward Between Its HooksSpecial methods: Python turns syntax into method calls
a + b runs type(a).__add__(a, b)model(X) runs type(model).__call__(model, X)Hook: a function attached to a module that runs every time the module runs, for example to record its output
nn.Module.__call__, in order (figure)
self.forward(X), the computation the class definesmodel.forward(X) called directly: steps 1 and 3 are skipped

Assignment is a method call: self.name = value runs nn.Module.__setattr__
nn.Parameter goes into the model’s registryThe registry feeds (figure)
model.parameters(), which the optimizer is givenmodel.to(device)model.state_dict(), which saving usesLayers kept in a Python list: outside the registry
Linear layers, \(203{,}530\) with nn.ModuleList
Devices
float32 only)Moves (figure)
model.to(device): every registered parameter, onceX.to(device): each batch, as it arrivesInstall and check
Same as NumPy
@, broadcasting, slicingdim= where NumPy has axis=Added (figure)
device: the memory holding the valuesrequires_grad: operations on the tensor are recordedgrad: filled by backward(), the tensor’s shapegrad_fn: the recorded operation that produced itCatches
float32 by default, while torch.from_numpy keeps NumPy’s float64; a float64 batch into a float32 layer is an errortorch.from_numpy(a) and t.numpy() share memory with the other object.item() returns a plain number from a one-element tensortorch.Size([128, 1, 28, 28])
torch.Size([128, 784])
Xb.flatten(1) or nn.Flatten(): a new shape, the same memoryFlattened: one row of \(d = 784\) pixels per image, the \(m \times d\) input matrix \(\mathbf{X}\) of mini-batch backpropagation
With samples on the first axis, one matrix product computes a layer for the whole batch

nn Modules Hold Parameters, Functional Calls Take Them as Argumentstorch.nn.functional (F): operations as plain functions
torch.nn: the same operations as modules
nn.Linear creates and stores its \(\mathbf{W}\) and \(\mathbf{b}\), registered for the optimizernn.ReLU stores nothing: a module wrapper around F.relu, so a chain of modules can include itInside forward: an operation with no parameters works either way, F.relu(x) or a stored nn.ReLU()
forward, nn.Sequential Runs a ChainCustom module: a class that inherits nn.Module
__init__ creates the layers that hold parametersforward can branch, loop, reuse a layer, or return two outputsCreated once, with the module (figure, left)
nn.Linear(784, 256): \(\mathbf{W}\) of \(256 \times 784\) and \(\mathbf{b}\) of \(256\), sized by the two argumentsnn.ReLU(), nn.Flatten(): nothing stored, so no sizesCreated at every forward pass (figure, right)
backward() uses themA size mismatch: an error at the first forward pass, naming the two shapes



Dataset: one sample at a time
train_set[i]: an image, \((1, 28, 28)\), and its classlen(train_set): \(60{,}000\)ToTensor() here, resizing, normalizing, or random crops elsewhereDataLoader: delivers samples to the model in batches
Shuffling: different samples share a gradient step each epoch
One epoch: \(469\) batches of \(128\), so \(469\) gradient steps

Training set: gradient descent fits the weights to it
Validation set: compares the settings gradient descent does not fit
Test set: used once, after every choice
FashionMNIST has \(60{,}000\) training and \(10{,}000\) test images
Size: an accuracy near \(88\%\) measured on \(n\) images has standard error \(\sqrt{0.88 \cdot 0.12 / n}\)

full = datasets.FashionMNIST('data', train=True,
transform=transforms.ToTensor())
train_set, val_set = random_split(
full, [50_000, 10_000],
generator=torch.Generator().manual_seed(0))
loader = DataLoader(train_set, batch_size=128,
shuffle=True)
val_loader = DataLoader(val_set, batch_size=1000)
print(len(train_set), len(val_set), len(loader))50000 10000 391
CrossEntropyLoss Applies the Softmax, So Models Output Logits
Logits: the model’s raw class scores, any real numbers
nn.CrossEntropyLoss(): log-softmax and negative log-likelihood in one function
A softmax in the model too: the loss then applies a second softmax, to probabilities
Two classes: one output with nn.BCEWithLogitsLoss(), equivalent to two outputs with one score fixed at \(0\)
Regression: nn.MSELoss() broadcasts outputs \((m, 1)\) against targets \((m,)\) to \((m, m)\), with only a warning
backward() Computes Every Gradient From a Recorded Forward Pass
Forward pass: each operation on a tensor that requires gradients is recorded, with what its local derivative needs
backward(): the chain rule over the record, from the loss back to each parameter
.gradChecked: a tanh, ReLU, and sigmoid network whose gradients were never derived by hand

zero_grad() Clears Them.
backward() adds to the existing .grad
tensor(8.)
Without opt.zero_grad(): each step uses the sum of all gradients so far, and training fails
Accumulation on purpose: backward() on \(k\) small batches, each loss divided by \(k\), then one step

opt.step() Applies the Update Rule to Every Parameter
The optimizer receives model.parameters() once and keeps a reference to each tensor
opt.step() for SGD, written out
no_grad(), so the update is not recordedA different update rule: a different optimizer class, and nothing else changes
model.eval() and torch.no_grad() Are Independent Switchesmodel.train() and model.eval(): a flag that layers read
nn.Linear and nn.ReLU ignore the flagwith torch.no_grad():: operations inside are not recorded
backward() possible, and less memory, since no record is keptThe template program, with a validation set
Recorded every epoch
loss.item() from each of the \(391\) batches, averagedmodel.eval() and torch.no_grad()loss instead of loss.item() keeps every batch’s record in memoryMeasured
Test, once, at the end: loss \(0.32\), accuracy \(88.9\%\)

Same network, starting weights, and batch order: \(784\)-\(256\)-\(256\)-\(10\), ReLU, SGD with \(\eta = 0.1\), batch \(128\), one epoch
Test accuracy
Loss per batch
In float64: within \(10^{-15}\) for the whole epoch, so the drift is float32 rounding, amplified by training
Reproducibility: the same program on two frameworks or two devices agrees to rounding at first, not bit for bit over a run

A GPU: thousands of arithmetic units, and a fixed cost for every call (launching the work, waiting for the result)
Work per step, forward and weight gradient of a square ReLU layer: \(4mn^2\) operations
Operations per second achieved
Small layers: too little work to cover the GPU’s fixed cost

Time per step

Copying: the training set to the Apple GPU and back takes 20 ms, against 0.9 s for one CPU epoch
Forward hook: a function called with a layer’s output each time the layer runs
Tensor hook: a function called with a tensor’s gradient during backward()
Uses: recording outputs or gradients, extracting features, changing a gradient before it is used (a hook that returns a value replaces the gradient)
Example: recording the size of every hidden layer’s output and gradient
torch.manual_seed(0)
layers = [nn.Linear(784, 256), nn.ReLU()]
for _ in range(5):
layers += [nn.Linear(256, 256), nn.ReLU()]
deep = nn.Sequential(*layers, nn.Linear(256, 10))
fwd, bwd = [], []
def record(layer, inputs, out):
fwd.append(out.std().item())
out.register_hook(
lambda g: bwd.insert(0, g.std().item()))
for layer in deep[:-1]:
if isinstance(layer, nn.Linear):
layer.register_forward_hook(record)
X_h = torch.randn(512, 784)
y_h = torch.randint(0, 10, (512,))
F.cross_entropy(deep(X_h), y_h).backward()
Network: \(10\) hidden layers of \(256\) ReLU units, \(10\) outputs, biases zero
Measured with forward hooks, before any training: the spread of each layer’s pre-activations \(\mathbf{s}^{(l)}\)
Factor per layer, hidden layers \(2\) to \(10\)
Output: \(1.2 \times 10^{12}\) and \(1.2 \times 10^{-10}\), from a factor of \(100\) in \(\sigma_w\)

Training: SGD, \(\eta = 0.01\), batch \(128\), \(500\) steps
\(\sigma_w = 1\)
\(\sigma_w = 0.01\)
Relative step \(\eta\, \|\nabla \mathbf{W}^{(l)}\| / \|\mathbf{W}^{(l)}\|\): the same order of magnitude at every layer, so the overall scale is wrong, not particular layers

Conditions: \(w\) and \(x\) independent, \(\mathbb{E}[w] = 0\)
\[\mathrm{Var}(wx) = \mathbb{E}[w^2 x^2] - \big(\mathbb{E}[w]\,\mathbb{E}[x]\big)^2 = \mathbb{E}[w^2]\,\mathbb{E}[x^2]\]
A sum of \(n\) such products, the weights independent of each other and of the \(x_i\)
\[\mathrm{Var}\Big(\sum_{i=1}^{n} w_i x_i\Big) = n\, \mathbb{E}[w^2]\, \mathbb{E}[x^2]\]
Example: \(w \sim \mathcal{N}(0, 0.5^2)\), \(x \sim \mathcal{N}(1, 1)\)
A pre-activation \(s_i = \sum_j w_{ij} a_j\) is such a sum

Symmetric \(s\): \(s\) and \(-s\) have the same distribution, as a zero-mean Gaussian does
ReLU sets the negative half to zero and keeps the positive half unchanged
\[\mathbb{E}\big[\mathrm{ReLU}(s)^2\big] = \mathbb{E}\big[s^2\, \mathbf{1}\{s > 0\}\big] = \tfrac{1}{2}\, \mathbb{E}[s^2]\]
ReLU’s slope: \(1\) for \(s > 0\), \(0\) otherwise
\[\mathbb{E}\big[\mathrm{ReLU}'(s)^2\big] = P(s > 0) = \tfrac{1}{2}\]
Example: \(s \sim \mathcal{N}(0, 4)\)

Pre-activation of unit \(i\) in layer \(l\)
\[s_i^{(l)} = \sum_{j=1}^{n_{l-1}} w_{ij}^{(l)}\, a_j^{(l-1)}, \qquad \mathrm{Var}\big(s^{(l)}\big) = n_{l-1}\, \sigma_w^2\, \mathbb{E}\big[(a^{(l-1)})^2\big]\]
ReLU: \(\mathbb{E}\big[(a^{(l-1)})^2\big] = \tfrac{1}{2} \mathrm{Var}\big(s^{(l-1)}\big)\), so
\[\mathrm{Var}\big(s^{(l)}\big) = \kappa\, \mathrm{Var}\big(s^{(l-1)}\big), \qquad \kappa = \frac{n_{l-1}\, \sigma_w^2}{2}\]
The example, \(n_{l-1} = 256\)
Measured: within \(25\%\) of the recursion at every hidden layer

Backward recursion, from the output toward the input
\[\boldsymbol{\delta}^{(l)} = \big(\mathbf{W}^{(l+1)T} \boldsymbol{\delta}^{(l+1)}\big) \odot \sigma'\big(\mathbf{s}^{(l)}\big)\]
Approximation: the weights treated as independent of \(\boldsymbol{\delta}^{(l+1)}\), checked against the measurement
\[\mathrm{Var}\big(\delta^{(l)}\big) = \frac{n_{l+1}\, \sigma_w^2}{2}\, \mathrm{Var}\big(\delta^{(l+1)}\big)\]
Measured with tensor hooks: within \(10\%\) of \(128\) and \(0.0128\) per layer

Weight gradient, one example
\[\frac{\partial \ell}{\partial \mathbf{W}^{(l)}} = \boldsymbol{\delta}^{(l)} \mathbf{a}^{(l-1)T}, \qquad \text{entry variance } \mathrm{Var}\big(\delta^{(l)}\big)\, \mathbb{E}\big[(a^{(l-1)})^2\big]\]
Two factors, opposite directions
Measured, hidden layers \(1\) to \(10\)
The output’s \(\boldsymbol{\delta}^{(L)} = \hat{\mathbf{y}} - \mathbf{y}\) is set by the loss, so the depth sets the size: \(\kappa^{L-1}\) in total

Measured in the Depth section: the first layer’s gradient shrinks by a fixed factor per added layer
Predicted: each layer multiplies the gradient’s variance by \(\kappa\), so its norm by \(\sqrt{\kappa}\)
\(\mathcal{N}(0, 1/n_{l-1})\) weights: \(\sigma_w^2 = 1/n_{l-1}\)
PyTorch’s default nn.Linear weights: uniform on \(\pm 1/\sqrt{n_{l-1}}\)
Both starts: \(\kappa < 1\), so deep networks start with vanishing gradients

Gain per layer, forward and backward
\[\kappa = \underbrace{n_{l-1}}_{\text{fan-in}} \times \underbrace{\sigma_w^2}_{\text{weight variance}} \times \underbrace{\tfrac{1}{2}}_{\text{ReLU}}\]
After \(k\) layers: the variance times \(\kappa^k\)
A deep network needs \(\kappa\) close to \(1\) at every layer, not only of order \(1\)

Signal Propagation’s recursions with a general activation \(\sigma\), \(n\) units per layer
\[\mathrm{Var}\big(s^{(l)}\big) = n\, \sigma_w^2\; \mathbb{E}\big[\sigma(s^{(l-1)})^2\big]\]
\[\mathrm{Var}\big(\delta^{(l)}\big) = n\, \sigma_w^2\; \mathbb{E}\big[\sigma'(s^{(l)})^2\big]\, \mathrm{Var}\big(\delta^{(l+1)}\big)\]
Activation factors, for \(s \sim \mathcal{N}(0, v)\)
ReLU: both factors \(\tfrac{1}{2}\) at every \(v\)
Tanh and sigmoid: each factor depends on \(v\), and the two factors differ

Sigmoid \(\dfrac{1}{1 + e^{-s}}\), slope \(\mathrm{sigmoid}(s)\big(1 - \mathrm{sigmoid}(s)\big)\)
Tanh \(= 2\, \mathrm{sigmoid}(2s) - 1\), slope \(1 - \tanh^2(s)\)
Saturation: the share of units with \(|s| > 2\), for \(s \sim \mathcal{N}(0, v)\)

Network: Signal Propagation’s, \(10\) hidden layers of \(256\) units, biases zero, PyTorch’s default weights: \(\mathrm{Var}(w) = 1/(3n)\), so \(n\, \sigma_w^2 = \tfrac{1}{3}\)
Sigmoid
Tanh, close to the identity at small \(s\)
Same-size weight gradients need the forward and backward factors to match

Weights from \(\mathcal{N}(0, 1)\): \(n\, \sigma_w^2 = 256\), same network with tanh
Forward: \(|a| \leq 1\), so \(\mathrm{Var}(s) \leq 256\)
Backward: \(256\, \mathbb{E}[\sigma'(s)^2]\) at \(v = 244\), predicted \(8.7\) per layer, measured \(8.3\) to \(9.1\)
Repeated composition: inputs on the line between two training images, through layers \(1\) to \(6\)

Scaling: for \(c > 0\)
\[\mathrm{ReLU}(c\, s) = c\, \mathrm{ReLU}(s), \qquad \mathrm{ReLU}'(c\, s) = \mathrm{ReLU}'(s)\]
One gain, \(\kappa = n\, \sigma_w^2 / 2\), at every layer and in both directions
At \(s = 0\) ReLU has no derivative, and PyTorch uses \(0\)

Dead unit: \(s \leq 0\) on every training input, so its slope is \(0\) on every input
At PyTorch’s default start, with its default biases: \(4\) hidden layers of \(256\) units, \(3\) seeds

Units off in layer 4 after one epoch of SGD from the same start, batch \(128\), \(3\) seeds
Mechanism
Moderate steps turn most of the start’s dead units back on: updates to earlier layers move their inputs

Same as ReLU for large positive \(s\), different for \(s < 0\)
No unit has slope exactly \(0\) on every input
Backward factor at \(v = 0.1,\ 1,\ 10\)
Cost per element, one CPU thread
In PyTorch, one module changes
