← All notes

Adding an output to a trained network without moving the old ones

By · · 6 min read

Claim. A new output can be added to a trained network so that the old outputs stay bit-identical, but only if the new output can read features the old network kept. In my experiment a head on the frozen deep features could not learn it at all, and a small branch on the early features learnt it almost perfectly.

A network that already does its job well often needs one more output: a new class, a new attribute, a new kind of measurement. The tempting move is to add a head and fine-tune everything on data for both. The old outputs then move too, and the question is whether anyone notices. The cautious move is to freeze the network and train only the new head, which keeps the old outputs exact. I wanted to know what each option costs, and in particular what decides whether the cautious option can work.

What the old network kept

The answer turns on a fact about features that Yosinski et al. (2014)1 made concrete: early layers of a network compute general features, and later layers become increasingly specific to the task the network was trained for. A classifier with pooling layers has no reason to keep the exact position of something irrelevant to the class, so by its last layer that information may simply be gone. A head on top of those features, however large, cannot recover what isn't there.

So I designed the new output to need exactly such information. A small convolutional network was trained to classify noisy 32×32 images into ten classes. Each image also contains a thin vertical line, one pixel wide, at a random column. The classifier never needed the line; the new output is its column.

Four ways to add the output

I trained the classifier for 2,500 steps, reaching 99.8% accuracy, and then added the new output four ways, three seeds each:

  1. fine-tune everything, with a new head on the deep features and the old and new losses together;
  2. freeze the network and train a linear readout on its deep features;
  3. freeze the network and train a two-layer head on its deep features;
  4. freeze the network and train a small convolutional branch that reads its early, full-resolution features, two layers in.
Learning the new output, four ways — A re-run of one seed of the note's experiment. GPU training isn't bit-for-bit reproducible, so its numbers sit close to the table's rather than on them. (interactive figure)
Way of adding the outputOld accuracy afterLargest change in an old outputError of the new output
Fine-tune everything99.9%21.5 logits0.27 px
Linear readout on frozen deep features99.8%05.41 px
Two-layer head on frozen deep features99.8%05.28 px
Branch on frozen early features99.8%00.0015 px
How far the old outputs move while the new one is learnt — A re-run of one seed of the note's experiment. GPU training isn't bit-for-bit reproducible, so its numbers sit close to the table's rather than on them. (interactive figure)

Guessing the average column gives an error of about 7 pixels.

Fine-tuning learnt the new output well, to about a quarter of a pixel, and the old accuracy even rose a little. But the old outputs moved: some logits changed by more than 21. Accuracy is blind to that, because the top class stayed the same. Anything downstream that reads the scores themselves, such as a threshold, a calibrated probability or a ranking across inputs, would see a different model.

The two heads on the frozen deep features kept the old outputs exactly; their largest change was zero. Neither could learn the new output: 5.4 and 5.3 pixels against 7 for guessing. The bigger head did barely better than the linear one, which says the limit is not the head's capacity. The deep features don't contain the line's position.

The branch on the early features kept the old outputs exactly and placed the line to within two thousandths of a pixel. It is a small network with two convolutions, and it worked because it reads features from before the pooling layers threw the position away.

Why the branch is safe by construction

The guarantee of the frozen options comes from the graph, not from a penalty. The old outputs are computed entirely by parameters that receive no updates, from inputs that the new branch only reads. No gradient from the new loss can reach them, so nothing the new output learns can change them. A softer alternative is to fine-tune everything while penalising changes to the old outputs, as Learning without Forgetting (Li and Hoiem, 20162) does by distilling the old network's responses into the new one. That keeps the old outputs close on the data it is trained on, and only there.

Adapter modules (Houlsby et al., 20193) and side-tuning (Zhang et al., 20204) generalise the same idea. Keep the pretrained network fixed, add a small trainable path, and let only that path learn the new task. My result adds one point to that picture: where the new path taps into the frozen network matters as much as its size. A side network attached at the end could not do what one attached near the input did easily.

How I would choose

From this experiment, the order I would try things in:

  1. Check what the frozen features contain, with a quick probe from several depths. If the deep features already carry the information, a head on them is enough and costs nothing.
  2. If they don't, attach a small branch at the depth where the information still exists, before the layers that discard it.
  3. Fine-tune everything only if the old outputs are allowed to change. If so, measure that change on the scores themselves, not only on accuracy.

When the whole network is fine-tuned instead, how far the backbone moves decides what it keeps, in how far a pretrained backbone moves. The reason a head on deep features cannot recover a thin structure is the same reason a cell cannot place what it cannot see, in a dense predictor cannot place what its cell cannot see.

What I still don't know

My new output is the cleanest possible case: information present at full resolution and destroyed by pooling. Real new outputs may need information that is partly present at every depth, and then the best tap point is a trade-off between resolution and context. Branches that read several depths at once are the obvious design, and I haven't tried one.

I also didn't check how far the fine-tuned model's old outputs moved on inputs unlike the training data. That is where an uncontrolled change usually shows, and it is the measurement that would show whether the drift I saw matters in practice.

References

  1. How transferable are features in deep neural networks? — Jason Yosinski, Jeff Clune, Yoshua Bengio and Hod Lipson, NeurIPS 2014
  2. Learning without Forgetting — Zhizhong Li and Derek Hoiem, ECCV 2016
  3. Parameter-Efficient Transfer Learning for NLP — Neil Houlsby, Andrei Giurgiu, Stanislaw Jastrzebski, Bruna Morrone, Quentin de Laroussilhe, Andrea Gesmundo, Mona Attariyan and Sylvain Gelly, ICML 2019
  4. Side-Tuning: A Baseline for Network Adaptation via Additive Side Networks — Jeffrey O. Zhang, Alexander Sax, Amir Zamir, Leonidas Guibas and Jitendra Malik, ECCV 2020