"The loss curve told me the model was fine. The dead filter told me the truth."
A Skeptical AI Agent
The Big Picture
Every previous section in this chapter built a representation: windows became tensors (Section 13.1), kernels learned local shape (Section 13.2), dilation stretched their reach (Section 13.3), and inductive biases nudged what they could express (Section 13.6). This section asks the uncomfortable question: did any of it actually work? A 1D CNN can reach a respectable validation accuracy while half its filters are dead, a quarter are duplicates, and the "gait" detector you imagined is really a clock that fires on a data-loader artifact. Because a learned filter is just a short weight vector, you can look straight at it, and the tools are cheap: plot the kernel, take its spectrum, visualize which inputs excite it, and watch its gradients. A trained sensor model that you have inspected is worth far more than one you merely trust, especially before it ships to a fleet where you cannot see the raw signal anymore.
This section assumes you have trained at least a small 1D convolutional network and are comfortable with the frequency-domain view of a filter from Chapter 7. Everything below treats a learned kernel exactly as Chapter 7 treats any FIR filter: a short impulse response with a magnitude response you can read off with an FFT. The novelty is only that gradient descent, not you, chose the taps.
Read the kernel, then read its spectrum
What. The first-layer weights of a 1D CNN are literally a bank of FIR filters, one per output channel per input axis. For a layer with tensor shape \((C_\text{out}, C_\text{in}, k)\), each row \(w[c,:,:]\) is a filter you can plot in the time domain and, more revealingly, transform to see its passband. A learned kernel \(w\) of length \(k\) has magnitude response
$$ |H(f)| = \left| \sum_{n=0}^{k-1} w[n]\, e^{-j 2\pi f n / f_s} \right|, $$and the peak of \(|H(f)|\) tells you the frequency each filter is tuned to.
Why. A time-domain kernel is hard to interpret by eye, but its spectrum is not. On a wrist accelerometer you expect some filters to concentrate near 1 to 3 Hz (walking cadence and its harmonics) and others to spread across a broad band (impacts, noise). If instead every filter has a flat, feature-less spectrum, the layer has learned nothing structured and is probably acting as a random projection. Reading spectra also exposes redundancy: two filters with near-identical passbands are wasting capacity.
How. Pull the weights off the first layer, FFT each kernel (zero-padded for resolution), and sort by peak frequency. The snippet below does exactly this for a trained model and flags the two failure modes we care about most: dead filters and duplicated filters.
import torch, torch.nn.functional as F
def inspect_first_layer(conv, fs, pad=256):
W = conv.weight.detach() # (C_out, C_in, k)
W = W.mean(dim=1) # collapse input axes -> (C_out, k)
# Time-domain health: L2 norm per filter; near-zero == dead
norms = W.norm(dim=1)
dead = (norms < 0.05 * norms.median()).nonzero().flatten()
# Frequency-domain view
H = torch.fft.rfft(W, n=pad, dim=1).abs() # (C_out, pad//2+1)
freqs = torch.fft.rfftfreq(pad, 1.0/fs)
peak_hz = freqs[H.argmax(dim=1)]
# Duplicate detection via cosine similarity of spectra
Hn = F.normalize(H, dim=1)
sim = Hn @ Hn.t()
sim.fill_diagonal_(0.0)
dup_pairs = (sim > 0.98).nonzero()
return dict(peak_hz=peak_hz, dead=dead, n_dup=len(dup_pairs)//2, norms=norms)
# report = inspect_first_layer(model.conv1, fs=50.0)
# print(report["peak_hz"].sort().values) # where each filter is tuned
# print("dead:", report["dead"].tolist(), "dup pairs:", report["n_dup"])
The inspect_first_layer function is deliberately blunt: a healthy sensor front end should show peak frequencies spread across the physically meaningful band, a norm distribution with no cluster pinned at zero, and few near-duplicate spectra. If peak_hz collapses onto one value, or half the entries land in dead, the diagnosis is architectural, not cosmetic.
Key Insight
A first-layer convolution weight is not a black box; it is an FIR filter, and Chapter 7 already taught you to read one. The single most informative debugging plot for a sensor CNN is the sorted list of first-layer peak frequencies overlaid on the signal's own power spectrum. When the filters cluster where the physics lives, learning worked. When they scatter randomly or pile onto DC, the model is compensating for something upstream: unnormalized inputs, a leaked constant, or a learning rate that never let the front end specialize.
Dead, saturated, and duplicate filters: the common pathologies
Dead filters are units whose weights and gradients have collapsed toward zero, usually a ReLU that got pushed permanently negative early in training. They cost parameters and compute while contributing nothing. You catch them two ways: a near-zero weight norm (as above), and a near-zero activation rate measured over a validation batch (the fraction of windows for which the channel ever fires). A channel that activates on under one percent of windows is effectively dead.
Saturated filters are the opposite: they fire on almost everything, so they carry no discriminative information. On sensor data this frequently means the filter locked onto a DC offset or a slow drift that survived because the input was not standardized (a direct consequence of skipping the normalization discipline of Section 13.5).
Duplicate filters waste capacity: two channels that learned the same passband double the cost of that feature and halve the diversity of the bank. A light dose of weight decay, or an explicit orthogonality penalty, usually restores spread.
The reason to hunt these before trusting accuracy is that all three can coexist with a good validation number. A model with 40 percent dead filters may still classify walking versus running because the task is easy; the dead capacity only bites when the deployment distribution shifts and you needed those unused features. This is the same story you will formalize under distribution shift in Chapter 66.
Practical Example: The Filter That Learned the Wall Clock
A team building a bearing-fault classifier for a factory conveyor (an industrial predictive-maintenance setting like Chapter 36) trained a 1D CNN on 25 kHz vibration windows and celebrated 96 percent accuracy. Filter inspection told a darker story. The single most important first-layer filter, ranked by gradient-times-activation, had a sharp passband at exactly the frequency of the data-acquisition system's line-sync tone, not any mechanical fault. The "fault detector" had learned that healthy recordings and faulty recordings came from two different acquisition sessions, and the sessions had slightly different mains interference. The spectrum plot took ten minutes to make and saved them from shipping a leakage artifact. They re-split the data by session (leakage-safe splitting, per Chapter 5), retrained, and the honest accuracy was 81 percent: lower, real, and deployable.
Which inputs excite a filter: activation maximization and saliency
What. Reading a kernel's spectrum tells you the frequency it prefers; it does not tell you which real signal shapes drive it hardest, especially for filters in deeper layers where the FIR interpretation breaks down. Two complementary tools fill the gap. Real-example maximization ranks validation windows by how strongly they activate a chosen channel and shows you the top few, giving an honest, in-distribution picture of what the filter fires on. Gradient saliency computes \(\partial a_c / \partial x\), the sensitivity of a unit's activation to each input sample, highlighting which timesteps and axes matter.
Why. For a wearable model you want to confirm that a filter tagged "step detector" actually fires on heel-strike impulses and not on the moment the data loader zero-pads a short window. Real-example maximization is the most trustworthy check because it never leaves the data manifold, unlike synthetic activation maximization which can drift into adversarial nonsense on time series.
When. Use real-example maximization first for any per-channel sanity check; reach for saliency and integrated gradients when you need per-sample attribution for a specific prediction, which is the interpretability toolkit developed in full in Chapter 67. A useful ranking that combines both signals is filter importance by mean gradient-times-activation over a validation set:
$$ I_c = \frac{1}{N}\sum_{i=1}^{N} \Big| \, a_c(x_i)\, \frac{\partial \mathcal{L}}{\partial a_c}(x_i) \, \Big|, $$which approximates how much each channel contributes to reducing the loss and cheaply orders filters from load-bearing to ignorable.
Library Shortcut
Hand-rolling gradient saliency, integrated gradients, and occlusion means registering forward and backward hooks, managing baselines, and accumulating gradients along an interpolation path: easily 60 to 80 lines with subtle sign and normalization bugs. Captum wraps each method behind one call, for example IntegratedGradients(model).attribute(x, target=y), and handles the baseline path, batching, and convergence check for you. That is roughly a 70-line reduction per method, and the attributions come out in the same tensor layout as your input window so you can plot them directly over the signal.
Watch training, not just the final model
What. Many filter pathologies are born in the first few hundred steps, so the highest-leverage habit is to log filter health during training, not to autopsy it afterward. Cheap, decisive signals: the per-layer gradient norm (a layer whose gradients vanish is not learning), the fraction of dead ReLU channels per epoch, and the drift of first-layer peak frequencies over time (they should migrate toward the signal band and then stabilize).
Why. A vanishing first-layer gradient with a still-falling loss means the deeper layers are memorizing while the front end stays random: the network is not building the reusable low-level features you wanted, and it will transfer poorly to the self-supervised and foundation-model settings of Chapter 17. Catching it at epoch two costs a rerun; catching it at deployment costs a recall.
How. A backward hook that records each layer's gradient norm, plus the dead-channel and peak-frequency reports from the earlier snippet logged once per epoch, gives you a three-line dashboard that turns silent representation failures into visible ones. When you see peak frequencies that never leave DC, revisit input normalization; when you see gradient norms that collapse, revisit initialization, learning rate, or the residual paths of Section 13.6.
Exercise
Take the 1D CNN you trained for Lab 13 on human activity recognition (see Chapter 26 for the task). (a) Run inspect_first_layer and plot the sorted peak frequencies over the signal's own power spectrum; identify how many filters are dead and how many are near-duplicates. (b) Add a small weight decay and retrain; report how the dead count and duplicate count change. (c) For the three highest-importance filters by \(I_c\), retrieve the top five activating validation windows and label, in one sentence each, what physical event each filter appears to detect. (d) Deliberately remove input standardization, retrain for one epoch, and describe what happens to the first-layer peak frequencies.
Self-Check
- Why can a sensor CNN reach high validation accuracy while a large fraction of its filters are dead, and when does that dead capacity finally hurt you?
- You plot first-layer spectra and every filter peaks at 0 Hz. What upstream problem does this most strongly suggest, and which earlier section fixes it?
- Why is ranking real validation windows by activation a more trustworthy filter-inspection method than synthesizing an input that maximizes the activation?
Lab 13
train a 1D CNN for human activity recognition; visualize learned filters.
Bibliography
Attribution and saliency methods
Sundararajan, Taly, and Yan (2017). Axiomatic Attribution for Deep Networks. ICML.
Introduces integrated gradients, the baseline-path attribution method that underlies most principled per-sample saliency on time-series inputs; the axioms (sensitivity, implementation invariance) explain why naive gradients can mislead.
The origin of gradient-based saliency and activation maximization; the same recipes port directly from images to 1D sensor windows.
The library behind this section's shortcut; one API for integrated gradients, occlusion, and layer attribution, with the tensor layout that makes plotting attributions over a signal trivial.
What learned filters represent
Olah, Mordvintsev, and Schubert (2017). Feature Visualization. Distill.
The clearest treatment of what activation maximization does and how it can lie; its cautions about off-manifold synthesis are exactly why real-example maximization is preferred for sensor data.
Ravanelli and Bengio (2018). Speaker Recognition from Raw Waveform with SincNet. IEEE SLT.
Shows learned first-layer filters converging to interpretable band-pass shapes on raw 1D signals, the concrete evidence that reading a kernel's spectrum is meaningful.
He, Zhang, Ren, and Sun (2015). Deep Residual Learning for Image Recognition. CVPR.
Residual connections are the standard remedy when first-layer gradients vanish during training; essential background for the "watch the gradient norm" diagnostic.
Failure modes and honest evaluation
A careful reference on why a good validation number can hide leakage; pairs directly with the session-split lesson of the bearing-fault example.
Analyzes when ReLU units die and how initialization prevents it; the theory behind the dead-filter audit and its cure.
What's Next
In Chapter 14, we leave the purely convolutional view and give the network explicit memory. Recurrent cells and temporal convolutional networks carry state across a stream, which raises new inspection questions: instead of asking what a single filter detects, we will ask what a hidden state remembers, how far back its influence reaches, and how to keep that memory honest under streaming, stateful inference.