-
Notifications
You must be signed in to change notification settings - Fork 30
Expand file tree
/
Copy pathneural_networks.qmd
More file actions
1416 lines (1088 loc) · 73.2 KB
/
Copy pathneural_networks.qmd
File metadata and controls
1416 lines (1088 loc) · 73.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Neural Networks {#sec-neural_networks}
You've probably been hearing a lot about "neural networks." Now that we have several useful machine-learning concepts (hypothesis classes, classification, regression, gradient descent, regularization, etc.), we are well equipped to understand neural networks in detail.
This is, in some sense, the "third wave" of neural nets. The basic idea is founded on the 1943 model of neurons of McCulloch and Pitts and the learning ideas of Hebb. There was a great deal of excitement, but not a lot of practical success: there were good training methods (e.g., perceptron) for linear functions, and interesting examples of non-linear functions, but no good way to train non-linear functions from data. Interest died out for a while, but was re-kindled in the 1980s when several people came up with a way to train neural networks with "back-propagation," which is a particular style of implementing gradient descent, that we will study here.
:::{.column-margin}
As with many good ideas in science,
the basic idea for how to train non-linear neural networks with
gradient descent was independently developed by more than one
researcher.
:::
By the mid-90s, the enthusiasm waned again, because although we could train non-linear networks, the training tended to be slow and was plagued by a problem of getting stuck in local optima. Support vector machines (SVMs) that use regularization of high-dimensional hypotheses by seeking to maximize the margin, alongside kernel methods that provide an efficient and beautiful way of using feature transformations to non-linearly transform data into a higher-dimensional space, provided reliable learning methods with guaranteed convergence and no local optima.
However, during the SVM enthusiasm, several groups kept working on neural networks, and their work, in combination with an increase in available data and computation, has made neural networks rise again. They have become much more reliable and capable, and are now the method of choice in many applications. There are many, many variations of neural networks, which we can't even begin to survey. We will study the core "feed-forward" networks with "back-propagation" training, and then, in later chapters, address some of the major advances beyond this core.
:::{.column-margin}
The number of neural network variants increases daily, as may be seen on `arxiv.org`.
:::
We can view neural networks from several different perspectives:
**View 1**: An application of stochastic gradient descent for classification and regression with a potentially very rich hypothesis class.
**View 2**: A brain-inspired network of neuron-like computing elements that learn distributed representations.
**View 3**: A method for building applications that make predictions based on huge amounts of data in very complex domains.
We will mostly take view 1, with the understanding that the techniques we develop will enable the applications in view 3. View 2 was a major motivation for the early development of neural networks, but the techniques we will study do not seem to actually account for the biological learning processes in brains.
:::{.column-margin}
Some prominent researchers are, in fact, working hard to find analogues of these methods in the brain.
:::
## Basic element
The basic element of a neural network is a "neuron," pictured schematically below. We will also sometimes refer to a neuron as a "unit" or "node."
:::{.imagify}
\begin{tikzpicture}[
main node/.style={thick,circle,font=\Large},
background rectangle/.style={fill=white},
show background rectangle
]
\node[main node,draw](sum) at (0,0) {$\sum$};
\node[main node](x1) at (-2,1) {$x_1$};
\node[main node](dots) at (-2,0) {$\vdots$};
\node[main node](xm) at (-2,-1) {$x_m$};
\coordinate (w0) at (0,-1.5);
\node[main node, draw] (f) at (2,0) {$f(\cdot)$};
\node[main node] (y) at (4,0) {$a$};
\draw[->, above right] (x1) -- node {$w_1$} (sum);
\draw (dots) -- node {} (sum);
\draw[->, below right] (xm) -- node {$w_m$} (sum);
\draw[above right] (w0) node {$w_0$} -- (sum) ;
\draw[->, above] (sum) -- node (z) {$z$} (f);
\draw[->, above] (f) -- (y);
\node[black!70] (inp) at ($(xm) + (0,-1.5)$) {input};
\node[black!70] (pre) at ($(z) + (0,1)$) {pre-activation};
\node[black!70] (out) at ($(y) + (0,1)$) {output};
\node[black!70] (act) at ($(f) - (0,1.5)$) {activation function};
\draw[->,black!70] (inp) -- (xm);
\draw[->,black!70] (pre) -- (z);
\draw[->,black!70] (out) -- (y);
\draw[->,black!70] (act) -- (f);
\end{tikzpicture}
:::
It is a (generally non-linear) function of an input vector $x \in \mathbb{R}^m$ to a single output value $a \in \mathbb{R}$.
:::{.column-margin}
Sorry for changing our notation here. We were using $d$ as the dimension of the input, but we are trying to be consistent here with many other accounts of neural networks. It is impossible to be consistent with all of them though---there are many different ways of telling this story.
:::
It is parameterized by a vector of *weights*
$(w_1, \ldots, w_m) \in \mathbb{R}^m$ and an *offset* or *threshold* $w_0 \in \mathbb{R}$.
:::{.column-margin}
This should remind you of our $\theta$ and $\theta_0$ for linear models.
:::
We also specify an *activation function* $f : \mathbb{R}\rightarrow \mathbb{R}$. In general, this is chosen to be a non-linear function, which means the neuron is non-linear. In the case that the activation function is the identity ($f(x) = x$) or another linear function, then the neuron is a linear function of $x$. The activation can theoretically be any function, though we will only be able to work with it if it is differentiable.
The function represented by the neuron is expressed as: $$a = f(z) = f\left(\left(\sum_{j=1}^m x_jw_j\right) + w_0\right) = f(w^Tx + w_0)\;\;.$$
Before thinking about a whole network, we can consider how to train a single unit. Given a loss function $\mathcal{L}(\text{guess}, \text{actual})$ and a dataset $\{(x^{(1)}, y^{(1)}), \ldots,
(x^{(n)},y^{(n)})\}$, we can do (stochastic) gradient descent, adjusting the weights $w, w_0$ to minimize $$J(w, w_0) = \frac{1}{n}\sum_{i} \mathcal{L}\left(\text{NN}(x^{(i)}; w, w_0), y^{(i)}\right)\;,$$ where $\text{NN}$ is the output of our single-unit neural net for a given input.
We have already studied two special cases of the neuron: linear logistic classifiers (LLCs) and regressors! The activation function for the LLC is $f(x) =
\sigma(x)$ and for linear regression it is simply $f(x) = x$.
:::{.study-question-callout}
Just for a single neuron, imagine for some reason, that we decide to
use activation function $f(z) = e^z$ and loss function $\mathcal{L}(\text{guess}, \text{actual}) = (\text{guess} - \text{actual})^2$. Derive a gradient descent update for $w$ and $w_0$.
:::
## Networks
Now, we'll put multiple neurons together into a *network*. A neural network in general takes in an input $x \in \mathbb{R}^m$ and generates an output $a \in \mathbb{R}^n$. It is constructed out of multiple neurons; the inputs of each neuron might be elements of $x$ and/or outputs of other neurons. The outputs of the neural network are generated by $n$ *output units*.
In this chapter, we will only consider *feed-forward* networks. In a feed-forward network, you can think of the network as defining a computational graph that is *acyclic*: that is, the input to a neuron can never depend on that neuron's output. Data flows one way, from the inputs to the outputs, and the function computed by the network is just a composition of the functions computed by the individual neurons.
Although the graph structure of a feed-forward neural network can really be anything (as long as it satisfies the feed-forward constraint), for simplicity in software and analysis, we usually organize them into *layers*. A layer is a group of neurons that are essentially "in parallel": their inputs are the outputs of neurons in the previous layer, and their outputs are the inputs to the neurons in the next layer. We'll start by describing a single layer, and then go on to the case of multiple layers.
### Single layer
A *layer* is a set of units that, as we have just described, are not connected to each other. The layer is called *fully connected* if, as in the diagram below, all of the inputs (i.e., $x_1, x_2, \ldots x_m$ in this case) are connected to every unit in the layer. A layer has input $x \in \mathbb{R}^m$ and output (also known as *activation*) $a \in \mathbb{R}^n$.
:::{.imagify}
\begin{tikzpicture}[
main node/.style={circle},
background rectangle/.style={fill=white},
show background rectangle
]
\coordinate (soff) at (0,1.4);
\node[main node,draw](s1) at ($2*(soff)$) {$\sum$};
\node[main node,draw](s2) at (soff) {$\sum$};
\node[main node,draw](s3) at (0,0) {$\sum$};
\node[main node](sdots) at ($(0,0)-(soff)$) {$\vdots$};
\node[main node,draw](sn) at ($(0,0)-2*(soff)$) {$\sum$};
% inputs
\coordinate (x) at (-2,0);
\coordinate (xoff) at (0,1);
\node[main node](x1) at ($(x) + 1.5*(xoff)$) {$x_1$};
\node[main node](x2) at ($(x) + .5*(xoff)$) {$x_2$};
\node[main node](xdots) at ($(x) - .5*(xoff)$) {$\vdots$};
\node[main node](xm) at ($(x) - 1.5*(xoff)$) {$x_m$};
% activations
\coordinate (foff) at (1.5,0);
\node[main node, draw](f1) at ($(s1) + (foff)$) {$f$};
\node[main node, draw](f2) at ($(s2) + (foff)$) {$f$};
\node[main node, draw](f3) at ($(s3) + (foff)$) {$f$};
\node[main node] at ($(sdots) + (foff)$) {$\vdots$};
\node[main node, draw](fn) at ($(sn) + (foff)$) {$f$};
% outputs
\node[main node](y1) at ($(s1) + 2*(foff)$) {$a_1$};
\node[main node](y2) at ($(s2) + 2*(foff)$) {$a_2$};
\node[main node](y3) at ($(s3) + 2*(foff)$) {$a_3$};
\node[main node] at ($(sdots) + 2*(foff)$) {$\vdots$};
\node[main node](yn) at ($(sn) + 2*(foff)$) {$a_n$};
% arrows
\foreach \b in {(s1), (s2), (s3), (sn)}
\foreach \a in {(x1), (x2), (xm)}
\draw[->] \a -- \b;
\foreach \x/\y/\z in {(s1)/(f1)/(y1), (s2)/(f2)/(y2),
(s3)/(f3)/(y3), (sn)/(fn)/(yn)}{
\draw[->] \x -- \y;
\draw[->] \y -- \z;
}
%\foreach \x in {(s1), (s2), (s3), (sn)}
% \draw \x -- +(0,-.65);
\node[main node, ,below left] (weights) at ($(xm)!0.5!(sn)$)
{$W,W_0$};
\end{tikzpicture}
:::
Since each unit has a vector of weights and a single offset, we can think of the weights of the whole layer as a matrix, $W$, and the collection of all the offsets as a vector $W_0$. If we have $m$ inputs, $n$ units, and $n$ outputs, then
- $W$ is an $m\times n$ matrix,
- $W_0$ is an $n \times 1$ column vector,
- $X$, the input, is an $m \times 1$ column vector,
- $Z = W^T X + W_0$, the *pre-activation*, is an $n \times
1$ column vector,
- $A$, the *activation*, is an $n \times 1$ column vector,
and the output vector is $$A = f(Z) = f(W^TX + W_0)\;\;.$$ The activation function $f$ is applied element-wise to the pre-activation values $Z$.
### Many layers
A single neural network generally combines multiple layers, most typically by feeding the outputs of one layer into the inputs of another layer.
We have to start by establishing some nomenclature. We will use $l$ to name a layer, and let $m^l$ be the number of inputs to the layer and $n^l$ be the number of outputs from the layer. Then, $W^l$ and $W^l_0$ are of shape $m^l \times n^l$ and $n^l \times 1$, respectively. Note that the input to layer $l$ is the output from layer $l-1$, so we have $m^l= n^{l-1}$, and as a result $A^{l-1}$ is of shape $m^l \times 1$, or equivalently $n^{l-1} \times 1$. Let $f^l$ be the activation function of layer $l$. Then, the pre-activation outputs are the $n^l \times 1$ vector $$Z^l = {W^l}^TA^{l-1} + W^l_0$$ and the activation outputs are simply the $n^l \times 1$ vector
$$
A^l = f^l(Z^l)\;\;.
$$
:::{.column-margin}
It is technically possible to have different activation functions within the same layer, but, again, for convenience in specification and implementation, we generally have the same activation function within a layer.
:::
Here's a diagram of a many-layered network, with two blocks for each layer, one representing the linear part of the operation and one representing the non-linear activation function. We will use this structural decomposition to organize our algorithmic thinking and implementation.
:::{.imagify}
\begin{tikzpicture}[
background rectangle/.style={fill=white},
show background rectangle
]
\coordinate (x) at (0,0);
\node[inner sep=0em] (w1) at (1.7,0)
{\begin{tabular}{c} $W^1$ \\ $W^1_0$\end{tabular}};
\node[inner sep=1em] (f1) at ($2*(w1)$) {$f^1$};
\node[inner sep=0em] (w2) at ($3*(w1)$)
{\begin{tabular}{c} $W^2$ \\ $W^2_0$\end{tabular}};
\node[inner sep=1em] (f2) at ($4*(w1)$) {$f^2$};
\node (dots) at ($5*(w1)$) {$\cdots$};
\node[inner sep=0em] (wL) at ($6*(w1)$)
{\begin{tabular}{c} $W^L$ \\ $W^L_0$\end{tabular}};
\node[inner sep=1em] (fL) at ($7*(w1)$) {$f^L$};
\coordinate (y) at ($8*(w1) - (.3,0)$);
\draw[->] (x) -- node[above] {$X = A^0$} (w1);
\draw[->] (w1) -- node[above] {$Z^1$} (f1);
\draw[->] (f1) -- node[above] {$A^1$} (w2);
\draw[->] (w2) -- node[above] {$Z^2$} (f2);
\draw[->] (f2) -- node[above] {$A^2$} (dots);
\draw[->] (dots) -- node[above] {$A^{L-1}$} (wL);
\draw[->] (wL) -- node[above] {$Z^L$} (fL);
\draw[->] (fL) -- node[above] {$A^L$} (y);
\draw [decorate,decoration={brace,mirror,amplitude=10pt}]
($(w1) + (-.4, -.7)$) -- node[yshift=-1.8em] {layer 1}
($(f1) + (.4,-.7)$);
\draw [decorate,decoration={brace,mirror,amplitude=10pt}]
($(w2) + (-.4, -.7)$) -- node[yshift=-1.8em] {layer 2}
($(f2) + (.4,-.7)$);
\draw [decorate,decoration={brace,mirror,amplitude=10pt}]
($(wL) + (-.4, -.7)$) -- node[yshift=-1.8em, align=center] {layer $L$}
($(fL) + (.4,-.7)$);
\foreach \point in {w1, f1, w2, f2, wL, fL}{
\draw ($(\point) + (-.3,-.5)$) rectangle ($(\point) + (.3,.5)$);
}
\end{tikzpicture}
:::
## Choices of activation function
There are many possible choices for the activation function. We will start by thinking about whether it's really necessary to have an $f$ at all.
What happens if we let $f$ be the identity? Then, in a network with $L$ layers (we'll leave out $W_0$ for simplicity, but keeping it wouldn't change the form of this argument),
$$
A^L = {W^L}^T A^{L-1} = {W^L}^T {W^{L-1}}^T \cdots {W^1}^T X\;\;.
$$
So, multiplying out the weight matrices, we find that $$A^L = W^\text{total}X\;\;,$$ which is a *linear* function of $X$! Having all those layers did not change the representational capacity of the network: the non-linearity of the activation function is crucial.
:::{.study-question-callout}
Convince yourself that any function representable by any number of linear layers (where $f$ is the identity function) can be represented by a single layer.
:::
Now that we are convinced we need a non-linear activation, let's examine a few common choices. These are shown mathematically below, followed by plots of these functions.
**Step function:** $$\text{step}(z) =
\begin{cases}
0 & \text{if $z<0$} \\
1 & \text{otherwise}
\end{cases}$$
**Rectified linear unit (ReLU):** $$\text{ReLU}(z) =
\begin{cases}
0 & \text{if $z<0$} \\
z & \text{otherwise}
\end{cases} = \max(0,z)$$
**Sigmoid function:** Also known as a *logistic* function. This can sometimes be interpreted as probability, because for any value of $z$ the output is in $(0, 1)$: $$\sigma(z) = \frac{1}{1+e^{-z}}$$
**Hyperbolic tangent:** Always in the range $(-1, 1)$: $$\tanh(z) = \frac{e^z - e^{-z}}{e^z + e^{-z}}$$
**Softmax function:** Takes a whole vector $Z \in \mathbb{R}^n$ and generates as output a vector $A \in (0, 1)^n$ with the property that $\sum_{i = 1}^n A_i = 1$, which means we can interpret it as a probability distribution over $n$ items:
$$
\text{softmax}(z) =
\begin{bmatrix}
\exp(z_1) / \sum_{i} \exp(z_i) \\
\vdots \\
\exp(z_n) / \sum_{i} \exp(z_i)
\end{bmatrix}
$$
:::{.imagify}
\begin{tikzpicture}[
background rectangle/.style={fill=white},
show background rectangle
]
\begin{axis}[
name=topleft,
axis lines=middle, axis equal image,
xmin=-2, xmax=2,
ymin=-.5, ymax=1.5,
xlabel={$z$}, ylabel={$\text{step}(z)$},
]
\addplot [domain=-2:0, samples=2, ultra thick] {0};
\addplot [domain=0:2, samples=2, ultra thick] {1};
\end{axis}
\begin{axis}[
name=topright,
axis lines=middle, axis equal image,
xmin=-2, xmax=2,
ymin=-.5, ymax=1.5,
xlabel={$z$}, ylabel={$\text{ReLU}(z)$},
at = (topleft.east), anchor=west, xshift=.7cm
]
\addplot [domain=-2:0, samples=2, ultra thick] {0};
\addplot [domain=0:2, samples=2, ultra thick] {x};
\end{axis}
\begin{axis}[
name=bottomleft,
axis lines=middle, %axis equal image,
xmin=-4, xmax=4,
ymin=-1.25, ymax=1.25,
xlabel={$z$}, ylabel={$\sigma(z)$},
at = (topleft.south), anchor=north, yshift=-.7cm
]
\addplot [domain=-4:4, samples=100, ultra thick] {1/(1+exp(-x))};
\addplot [domain=-4:4, samples=2, dashed] {1};
\addplot [domain=-4:4, samples=2, dashed] {0};
\end{axis}
\begin{axis}[
name=bottomright,
axis lines=middle, %axis equal image,
xmin=-4, xmax=4,
ymin=-1.25, ymax=1.25,
xlabel={$z$}, ylabel={$\tanh(z)$},
at = (bottomleft.east), anchor=west, xshift=.7cm
]
\addplot [domain=-4:4, samples=100, ultra thick]
{(exp(x) - exp(-x))/(exp(x) + exp(-x))};
\addplot [domain=-4:4, samples=2, dashed] {1};
\addplot [domain=-4:4, samples=2, dashed] {-1};
\end{axis}
\end{tikzpicture}
:::
The original idea for neural networks involved using the **step** function as an activation, but because the derivative of the step function is zero everywhere except at the discontinuity (and there it is undefined), gradient-descent methods won't be useful in finding a good setting of the weights, and so we won't consider the step function further. Step functions have been replaced, in a sense, by the sigmoid, ReLU, and tanh activation functions.
:::{.study-question-callout}
Consider sigmoid, ReLU, and tanh activations. Which one is most like
a step function? Is there an additional parameter you could add to a
sigmoid that would make it be more like a step function?
:::
:::{.study-question-callout}
What is the derivative of the ReLU function? Are there some values of
the input for which the derivative vanishes?
:::
ReLUs are especially common in internal ("hidden") layers, sigmoid activations are common for the output for binary classification, and softmax activations are common for the output for multi-class classification (see @sec-classification-logistic for an explanation).
## Loss functions and activation functions
At layer $L,$ which is the output layer, we need to specify a loss function, and possibly an activation function as well. Different loss functions make different assumptions about the range of values they will get as input and, as we have seen, different activation functions will produce output values in different ranges. When you are designing a neural network, it's important to make these things fit together well. In particular, we will think about matching loss functions with the activation function in the last layer, $f^L$. Here is a table of loss functions and activations that make sense for them:
::: center
| Loss | $f^L$ | task |
|:-------:|:-------:|:---------------------------|
| squared | linear | regression |
| nll | sigmoid | binary classification |
| nllm | softmax | multi-class classification |
:::
We explored squared loss in @sec-regression and (nll and nllm) in @sec-classification.
## Error back-propagation
We will train neural networks using gradient descent methods. It's possible to use *batch* gradient descent, in which we sum up the gradient over all the points (as in @sec-gd of @sec-gradient) or stochastic gradient descent (SGD), in which we take a small step with respect to the gradient considering a single point at a time (as in @sec-sgd of @sec-gradient).
Our notation is going to get pretty hairy pretty quickly. To keep it as simple as we can, we'll focus on computing the contribution of one data point $x^{(i)}$ to the gradient of the loss with respect to the weights, for SGD; you can simply sum up these gradients over all the data points if you wish to do batch descent.
So, to do SGD for a training example $(x, y)$, we need to compute $\nabla_W \mathcal{L}(\text{NN}(x;W),y)$, where $W$ represents all weights $W^l, W_0^l$ in all the layers $l = (1, \ldots, L)$. This seems terrifying, but is actually quite easy to do using the chain rule.
:::{.column-margin}
Remember the chain rule! If $a = f(b)$ and $b = g(c)$, so that $a = f(g(c))$, then
$$
\begin{aligned}
\frac{d a}{d c} &= \frac{d a}{d b} \cdot \frac{d b}{d c} \\
&= f'(b) g'(c) \\
&= f'(g(c)) g'(c)
\end{aligned}
$$
:::
Remember that we are always computing the gradient of the loss function *with respect to the weights* for a particular value of $(x, y)$. That tells us how much we want to change the weights, in order to reduce the loss incurred on this particular training example.
### Notation and Setup
We consider supervised learning with training data
$$
\mathcal{D}_n = \{(x^{(i)}, y^{(i)})\}_{i=1}^n.
$$
For a single example $(x,y)$, the forward pass through an $L$-layer network is
$$
Z^l = (W^l)^\top A^{\,l-1} + W_0^l,
\qquad A^l = f^l(Z^l),
\qquad l=1,\dots,L,
$$
with $A^0 = x$ as the input and $g = A^L$ as the network’s output.
If layer $l$ has $d_l$ units, then:
* $A^l, Z^l \in \mathbb{R}^{d_l \times 1}$,
* $W^l \in \mathbb{R}^{d_{l-1} \times d_l}$,
* $W_0^l \in \mathbb{R}^{d_l \times 1}$.
Each column of $W^l$ contains the incoming weights for one neuron in layer $l$.
**Shape check:** $(W^l)^\top \in \mathbb{R}^{d_l \times d_{l-1}}$, so $(W^l)^\top A^{l-1}$ is $d_l \times 1$, consistent with $Z^l$.
The per-example loss is $\mathcal{L}(g,y)$, and the overall training objective is
$$
J = \frac{1}{n} \sum_{i=1}^n \mathcal{L}(g^{(i)}, y^{(i)}).
$$
---
### A Single Neuron
A single neuron computes
$$
z = w^\top x + w_0, \qquad g = f(z).
$$
Here $x \in \mathbb{R}^d$ is the input vector, $w \in \mathbb{R}^d$ is the weight vector, $w_0$ is the offset, $z$ is a scalar and $f$ is an activation function. The loss is $\mathcal{L}(g,y)$.
:::{.imagify}
\begin{tikzpicture}[
main node/.style={thick,circle,font=\Large},
background rectangle/.style={fill=white},
show background rectangle
]
\node[main node,draw](sum) at (0,0) {$\sum$};
\node[main node](x1) at (-2,1) {$x_1$};
\node[main node](dots) at (-2,0) {$\vdots$};
\node[main node](xm) at (-2,-1) {$x_m$};
\coordinate (w0) at (0,-1.5);
\node[main node, draw] (f) at (2,0) {$f(\cdot)$};
\node[main node] (y) at (4,0) {$a$};
\draw[->, above right] (x1) -- node {$w_1$} (sum);
\draw (dots) -- node {} (sum);
\draw[->, below right] (xm) -- node {$w_m$} (sum);
\draw[above right] (w0) node {$w_0$} -- (sum) ;
\draw[->, above] (sum) -- node (z) {$z$} (f);
\draw[->, above] (f) -- (y);
\node[black!70] (pre) at ($(z) + (0,1)$) {pre-activation};
\node[black!70] (out) at ($(y) + (0,1)$) {output};
\draw[->,black!70] (pre) -- (z);
\draw[->,black!70] (out) -- (y);
\end{tikzpicture}
:::
Before computing the derivatives, it helps to build some intuition:
- A change in the weights $w$ changes the pre-activation $z$ in proportion to the input $x$.
- A change in $z$ changes the output $g$ according to the slope $f'(z)$.
- A change in $g$ changes the loss according to $\tfrac{\partial \mathcal{L}}{\partial g}$.
By the chain rule:
$$
\begin{align*}
\frac{\partial \mathcal{L}}{\partial w}
&= \frac{\partial z}{\partial w} \frac{\partial g}{\partial z} \frac{\partial \mathcal{L}}{\partial g} \\
&=\; x \; f'(z)\; \frac{\partial \mathcal{L}}{\partial g},
\end{align*}
$$
$$
\begin{align*}
\frac{\partial \mathcal{L}}{\partial w_0}
&= \frac{\partial z}{\partial w_0} \frac{\partial g}{\partial z} \; \frac{\partial \mathcal{L}}{\partial g} \\
&= f'(z) \; \frac{\partial \mathcal{L}}{\partial g}.
\end{align*}
$$
**Shape check:**
* Since the loss and $g$ are scalars, $\tfrac{\partial \mathcal{L}}{\partial g}$ and $f'(z)$ are scalars.
* Because $z$ is a scalar and $w \in \mathbb{R}^{d_0 \times 1}$, $\tfrac{\partial z}{\partial w}$ is a vector with the same shape as $w$, which is consistent here.
Also note the arrangement of the terms. Once we move to neural networks with vector outputs and multiple layers, the matrix form of the chain rule (under the denominator-layout convention we are using) requires working from right to left. Conveniently, this order matches how computations are drawn in network block diagrams.
As a final connection to earlier models:
- If $f(z) = z$ and $\mathcal{L}(g,y) = \tfrac{1}{2}(g-y)^2$, this is **linear regression**.
- If $f(z) = \sigma(z)$ (the sigmoid) and $\mathcal{L}$ is the negative log-likelihood loss, this is **logistic regression**.
So linear regressors and logistic classifiers are simply single-neuron networks!
:::{.study-question-callout}
Suppose you choose squared loss. What is $\partial \text{loss} / \partial a^L$?
:::
:::{.study-question-callout}
Check the derivations above yourself. You should use the chain rule and also solve for the individual derivatives that arise in the chain rule.
:::
---
### A One-Hidden-Layer, Single Output Neuron Network
Building on the previous case, consider a network with one hidden layer:
$$
Z^1 = (W^1)^\top A^0 + W_0^1, \qquad A^1 = f^1(Z^1),
$$
$$
z^2 = (w^2)^\top A^1 + w_0^2, \qquad g = f^2(z^2).
$$
We want gradients of $\mathcal{L}(g,y)$ with respect to $W^1, W_0^1, w^2, w_0^2$.
Here $w^2$ is a vector and $w_0^2$ is a scalar, written this way to emphasize continuity with the single-neuron case. In general, of course, a network can have multiple output neurons.
:::{.imagify}
\begin{tikzpicture}[
main node/.style={circle},
background rectangle/.style={fill=white},
show background rectangle
]
% Input nodes
\coordinate (x) at (-2,0);
\coordinate (xoff) at (0,1);
\node[main node](x1) at ($(x) + 1.5*(xoff)$) {$x_1$};
\node[main node](x2) at ($(x) + .5*(xoff)$) {$x_2$};
\node[main node](xdots) at ($(x) - .5*(xoff)$) {$\vdots$};
\node[main node](xm) at ($(x) - 1.5*(xoff)$) {$x_m$};
% Hidden layer (single-layer network)
\coordinate (soff) at (0,1.4);
\node[main node,draw](s1) at ($2*(soff)$) {$\sum$};
\node[main node,draw](s2) at (soff) {$\sum$};
\node[main node,draw](s3) at (0,0) {$\sum$};
\node[main node](sdots) at ($(0,0)-(soff)$) {$\vdots$};
\node[main node,draw](sn) at ($(0,0)-2*(soff)$) {$\sum$};
% Activation layer
\coordinate (foff) at (1.5,0);
\node[main node, draw](f1) at ($(s1) + (foff)$) {$f^1$};
\node[main node, draw](f2) at ($(s2) + (foff)$) {$f^1$};
\node[main node, draw](f3) at ($(s3) + (foff)$) {$f^1$};
\node[main node] at ($(sdots) + (foff)$) {$\vdots$};
\node[main node, draw](fn) at ($(sn) + (foff)$) {$f^1$};
% Single output neuron
\node[main node, draw](sout) at ($(s3) + 3*(foff)$) {$\sum$};
\node[main node, draw](fout) at ($(sout) + (foff)$) {$f^2$};
\node[main node](y) at ($(fout) + (foff)$) {$g$};
% Connections from inputs to hidden layer
\foreach \b in {(s1), (s2), (s3), (sn)}
\foreach \a in {(x1), (x2), (xm)}
\draw[->] \a -- \b;
% Hidden layer to activation
\foreach \x/\y in {(s1)/(f1), (s2)/(f2), (s3)/(f3), (sn)/(fn)}
\draw[->] \x -- \y;
% Activations to single output neuron
\foreach \a in {(f1), (f2), (f3), (fn)}
\draw[->] \a -- (sout);
% Output connections
\draw[->] (sout) -- (fout);
\draw[->] (fout) -- (y);
% Weight label
\node[main node, below left] (weights1) at ($(xm)!0.5!(sn)$) {$W^1,W^1_0$};
\node[main node, below right] (weights2) at ($(fn)!0.5!(sout)$) {$w^2,w^2_0$};
% pre- and post- activation labels
\node[main node, below] (Z1) at ($(sn)!0.5!(fn)$) {$Z^1$};
\node[main node, right=1cm of Z1] (A1) {$A^1$};
\node[main node, below] (z2) at ($(sout)!0.5!(fout)$) {$z^2$};
\end{tikzpicture}
:::
We have already worked through the gradients of the loss with respect to $w^2$ and $w_0^2$:
$$
\begin{align*}
\frac{\partial \mathcal{L}}{\partial w^2}
&= \frac{\partial z^2}{\partial w^2} \frac{\partial g}{\partial z^2} \frac{\partial \mathcal{L}}{\partial g} \\
&=\; A^1 \; {f^2}'(z^2)\; \frac{\partial \mathcal{L}}{\partial g},
\end{align*}
$$
$$
\begin{align*}
\frac{\partial \mathcal{L}}{\partial w^2_0}
&= \frac{\partial z^2}{\partial w^2_0} \frac{\partial g}{\partial z^2} \; \frac{\partial \mathcal{L}}{\partial g} \\
&= {f^2}'(z^2) \; \frac{\partial \mathcal{L}}{\partial g}.
\end{align*}
$$
**Shape check:**
* $A^1 \in \mathbb{R}^{d_1 \times 1}$,
* the scalar ${f^2}'(z^2)\tfrac{\partial \mathcal{L}}{\partial g}$ multiplies it,
* so $\tfrac{\partial \mathcal{L}}{\partial w^2} \in \mathbb{R}^{d_1 \times 1}$, consistent with $w^2$.
Now, we need the gradients with respect to $W^1$ and $W^1_0$.
Writing out the chain rule shows that
$$
\frac{\partial \mathcal{L}}{\partial W^1}
= \frac{\partial Z^1}{\partial W^1} \frac{\partial A^1}{\partial Z^1} \frac{\partial z^2}{\partial A^1} \underbrace{\frac{\partial g}{\partial z^2} \frac{\partial \mathcal{L}}{\partial g}}_{\text{shared}},
$$
$$
\frac{\partial \mathcal{L}}{\partial W^1_0}
= \frac{\partial Z^1}{\partial W^1_0} \frac{\partial A^1}{\partial Z^1} \frac{\partial z^2}{\partial A^1} \underbrace{\frac{\partial g}{\partial z^2} \frac{\partial \mathcal{L}}{\partial g}}_{\text{shared}}.
$$
The final two terms are exactly the same as in the gradients for $w^2$ and $w^2_0$.
This illustrates the backward reuse that makes back-propagation efficient.
Carrying out the derivatives gives:
$$
\begin{align*}
\frac{\partial \mathcal{L}}{\partial W^1}
&= A^0 \Big( \underbrace{{f^1}'(Z^1) \big(w^2 {f^2}'(z^2) \tfrac{\partial \mathcal L}{\partial g}\big)}_{\tfrac{\partial \mathcal L}{\partial Z^1}}\Big)^T
\end{align*}
$$
$$
\begin{align*}
\frac{\partial \mathcal{L}}{\partial W^1_0}
&= \underbrace{{f^1}'(Z^1) \big(w^2 {f^2}'(z^2) \tfrac{\partial \mathcal L}{\partial g}\big)}_{\tfrac{\partial \mathcal L}{\partial Z^1}}
\end{align*}
$$
**Shape check:**
* $A^0 \in \mathbb{R}^{d_0 \times 1}$,
* $(\tfrac{\partial \mathcal{L}}{\partial Z^1})^\top \in \mathbb{R}^{1 \times d_1}$,
so the product is $d_0 \times d_1$, consistent with $W^1$.
:::{.callout-note collapse="true"}
### Where did the transpose come from? A note on the shape of things.
Note that $Z^1$ is a vector and $W^1$ is a matrix; strictly speaking,
$\tfrac{\partial Z^1}{\partial W^1}$ is a vector-by-matrix derivative, which produces a tensor.
Why, then, do we always end up with something as simple as an outer product?
To see why, recall
$$
Z^1 = (W^1)^\top A^0 + W_0^1,
$$
so for the $j$-th coordinate,
$$
z^1_j = \sum_{k=1}^{d_0} w^1_{k,j}\,a^0_k + w^1_{0,j}.
$$
Now consider $\tfrac{\partial z^1_j}{\partial w^1_{k,j'}}$:
- If $j \neq j'$, then $z^1_j$ does not depend on $w^1_{k,j'}$, so the derivative is $0$.
- If $j = j'$, then
$$
\frac{\partial z^1_j}{\partial w^1_{k,j}} = a^0_k.
$$
Thus, every entry of the derivative tensor is either zero or one of the inputs $a^0_k$.
The tensor has a very simple structure: for each $j$, the slice corresponding to $z^1_j$ is just the input vector $A^0$ in the $j$-th column, with zeros elsewhere.
When we apply the chain rule to compute the gradient of the loss,
$$
\frac{\partial \mathcal{L}}{\partial W^1}
= \sum_j \frac{\partial \mathcal{L}}{\partial z^1_j} \, \frac{\partial z^1_j}{\partial W^1},
$$
each $\frac{\partial z^1_j}{\partial W^1}$ contributes a column equal to $A^0$ scaled by
$\tfrac{\partial \mathcal{L}}{\partial z^1_j}$.
Stacking these columns gives exactly the outer product:
$$
\frac{\partial \mathcal{L}}{\partial W^1} = A^0 \Big(\frac{\partial \mathcal{L}}{\partial Z^1}\Big)^\top.
$$
So although $\tfrac{\partial Z^1}{\partial W^1}$ is formally a tensor,
its sparse structure ensures that, once contracted with $\tfrac{\partial \mathcal{L}}{\partial Z^1}$,
it collapses neatly into a matrix. This is why in practice we never need to form the tensor explicitly.
:::
---
### General $L$-Layer Network
Putting this together, we arrive at the general **back-propagation recursion**:
$$
\frac{\partial \mathcal{L}}{\partial Z^l}
= {f^l}'(Z^l) \Big(W^{l+1}\frac{\partial \mathcal{L}}{\partial Z^{l+1}}\Big).
$$
This rule is the heart of back-propagation: once the sensitivity
$\tfrac{\partial \mathcal{L}}{\partial Z^{l+1}}$ is known for layer $l{+}1$,
we can compute $\tfrac{\partial \mathcal{L}}{\partial Z^l}$ for the previous layer
by (1) multiplying by the next layer’s weights, and (2) applying the derivative of
the activation function.
If we unroll this recursion all the way to the output layer $L$, we can explicitly see the full chain of dependencies. This expanded view is particularly useful for understanding how gradients flow backwards through the entire network:
$$
\begin{aligned}
\frac{\partial \mathcal{L}}{\partial Z^l}
&= \frac{\partial A^l}{\partial Z^l} W^{l+1} \cdots \frac{\partial A^{L-1}}{\partial Z^{L-1}} W^{L} \frac{\partial A^{L}}{\partial Z^{L}} \frac{\partial \mathcal{L}}{\partial A^L} \;.
\end{aligned}
$${#eq-gradz}
**Shape check:**
- $W^{l+1}\in\mathbb{R}^{d_l \times d_{l+1}}$,
- $\tfrac{\partial \mathcal{L}}{\partial Z^{l+1}}\in\mathbb{R}^{d_{l+1}\times 1}$,
- so $W^{l+1}\tfrac{\partial \mathcal{L}}{\partial Z^{l+1}} \in\mathbb{R}^{d_l\times 1}$.
- ${f^l}'(Z^l)\in\mathbb{R}^{d_l\times d_l}$ keeps the shape consistent.
For a general neural network with $L$ layers, the same reasoning applies.
**Forward pass:**
$$
Z^l = (W^l)^\top A^{l-1} + W_0^l, \qquad A^l = f^l(Z^l), \qquad l=1,\dots,L.
$$
**Start at the output:**
$$
\frac{\partial \mathcal{L}}{\partial Z^L}
= {f^L}'(Z^L) \frac{\partial \mathcal{L}}{\partial g},
$$
$$
\frac{\partial \mathcal{L}}{\partial W^L}
= A^{L-1}\Big(\frac{\partial \mathcal{L}}{\partial Z^L}\Big)^\top,
\qquad
\frac{\partial \mathcal{L}}{\partial W_0^L}
= \frac{\partial \mathcal{L}}{\partial Z^L}.
$$
**Then for each hidden layer $l=L-1,\dots,1$:**
1. Pass sensitivity back through the weights:
$$
\frac{\partial \mathcal{L}}{\partial A^l}
= W^{l+1}\frac{\partial \mathcal{L}}{\partial Z^{l+1}}.
$$
2. Apply the elementwise activation derivative:
$$
\frac{\partial \mathcal{L}}{\partial Z^l}
= {f^l}'(Z^l) \frac{\partial \mathcal{L}}{\partial A^l}.
$$
3. Compute parameter gradients:
$$
\frac{\partial \mathcal{L}}{\partial W^l}
= A^{l-1}\Big(\frac{\partial \mathcal{L}}{\partial Z^l}\Big)^\top,
\qquad
\frac{\partial \mathcal{L}}{\partial W_0^l}
= \frac{\partial \mathcal{L}}{\partial Z^l}.
$$
**Shape check:**
* $A^{l-1}\in\mathbb{R}^{d_{l-1}\times 1}$,
* $(\tfrac{\partial \mathcal{L}}{\partial Z^l})^\top \in \mathbb{R}^{1\times d_l}$,
so their product is $d_{l-1}\times d_l$, consistent with $W^l$.
:::{.study-question-callout}
Check that the final layer ($l=L$) case is a special case of the general layer $l$ case above.
:::
---
### From per-example loss to the objective
All of the derivations above apply to a single training example $(x,y)$. For the full objective
$$
J = \frac{1}{n} \sum_{i=1}^n \mathcal{L}\big(g^{(i)},y^{(i)}\big),
$$
the gradients are the averages:
$$
\frac{\partial J}{\partial W^l} = \frac{1}{n}\sum_{i=1}^n \frac{\partial \mathcal{L}^{(i)}}{\partial W^l},
\qquad
\frac{\partial J}{\partial W_0^l} = \frac{1}{n}\sum_{i=1}^n \frac{\partial \mathcal{L}^{(i)}}{\partial W_0^l}.
$$
**Shape check:** Each $\tfrac{\partial \mathcal{L}^{(i)}}{\partial W^l}$ has the same dimensions as $W^l$ ($d_{l-1}\times d_l$), so averaging preserves the shape.
### Reflecting on backpropagation
This general process of computing the gradients of the loss with respect to the weights is called *error back-propagation*.
:::{.column-margin}
\note{We could call this
``blame propagation''. Think of $\text{loss}$ as how mad we
are about the prediction just made. Then $\partial
\text{loss}/ \partial A^L$ is how much we blame $A^L$ for the loss.
The last module has to take in $\partial \text{loss}/ \partial A^L$
and compute $\partial \text{loss}/ \partial Z^L$, which is how much
we blame $Z^L$ for the loss. The next module (working backwards)
takes in $\partial \text{loss}/ \partial Z^L$ and computes $\partial
\text{loss}/ \partial A^{L-1}$. So every module is accepting its
blame for the loss, computing how much of it to allocate to each of
its inputs, and passing the blame back to them.}
:::
The idea is that we first do a *forward pass* to compute all the $a$ and $z$ values at all the layers, and finally the actual loss. Then, we can work backward and compute the gradient of the loss with respect to the weights in each layer, starting at layer $L$ and going back to layer 1.
:::{.imagify}
\begin{tikzpicture}[
scale=.98,
background rectangle/.style={fill=white},
show background rectangle
]
\coordinate (x) at (0,0);
\node[inner sep=0em] (w1) at (1.7,0)
{\begin{tabular}{c} $W^1$ \\ $W^1_0$\end{tabular}};
\node[inner sep=1em] (f1) at ($2*(w1)$) {$f^1$};
\node[inner sep=0em] (w2) at ($3*(w1)$)
{\begin{tabular}{c} $W^2$ \\ $W^2_0$\end{tabular}};
\node[inner sep=1em] (f2) at ($4*(w1)$) {$f^2$};
\node (dots) at ($5*(w1)$) {$\cdots$};
\node[inner sep=0em] (wL) at ($6*(w1)$)
{\begin{tabular}{c} $W^L$ \\ $W^L_0$\end{tabular}};
\node[inner sep=1em] (fL) at ($7*(w1)$) {$f^L$};
\node (loss) at ($8*(w1)$) {Loss};
\coordinate (y) at ($8*(w1) + (0,1.5)$);
\draw[->] (x) -- node[above] {$X = A^0$} (w1);
\draw[->] (w1) -- node[above] {$Z^1$} (f1);
\draw[->] (f1) -- node[above] {$A^1$} (w2);
\draw[->] (w2) -- node[above] {$Z^2$} (f2);
\draw[->] (f2) -- node[above] {$A^2$} (dots);
\draw[->] (dots) -- node[above] {$A^{L-1}$} (wL);
\draw[->] (wL) -- node[above] {$Z^L$} (fL);
\draw[->] (fL) -- node[above] {$A^L$} (loss);
\draw[->] (y) -- node[right] {$y$} ($(loss)+(0,.65)$);
%\draw[->,yshift=0.5cm] (loss.west) to [out=150,in=30] (fL.east);
\foreach \s/\e/\t in {loss/fL/A^L,
fL/wL/Z^L,
wL/dots/A^{L-1},
dots/f2/A^2,
f2/w2/Z^2,
w2/f1/A^1,
f1/w1/Z^1}{
\path[->] ($(\s.west) - (0,.5)$) edge[out=210,in=-30] node[below]
{$\frac{\partial \text{loss}}{\partial \t}$}
($(\e.east) - (0,.5)$);
}
\foreach \point in {w1, f1, w2, f2, wL, fL}{
\draw ($(\point) + (-.3,-.5)$) rectangle ($(\point) + (.3,.5)$);
}
\draw ($(loss) + (-.4,-.5)$) rectangle ($(loss) + (.4,.5)$);
\end{tikzpicture}
:::
If we view our neural network as a sequential composition of modules (in our work so far, it has been an alternation between a linear transformation with a weight matrix, and a component-wise application of a non-linear activation function), then we can define a simple API for a module that will let us compute the forward and backward passes, as well as do the necessary weight updates for gradient descent. Each module has to provide the following "methods." We are already using letters $a, x, y, z$ with particular meanings, so here we will use $u$ as the vector input to the module and $v$ as the vector output:
- forward: $u \rightarrow v$
- backward: $u, v, \partial L /
\partial v \rightarrow \partial L / \partial u$
:::{.column-margin}
Notice that the backward pass does not output $\partial v / \partial u$, even though the forward pass maps from $u$ to $v$. In the backward pass, we are always directly computing and ``passing around'' gradients of the loss.
:::
- weight grad: $u, \partial L / \partial v \rightarrow \partial L
/ \partial W$ only needed for modules that have weights $W$
In homework we will ask you to implement these modules for neural network components, and then use them to construct a network and train it as described in the next section.
<!--
### First, suppose everything is one-dimensional
To get some intuition for how these derivations work, we'll first suppose everything in our neural network is one-dimensional. In particular, we'll assume there are $m^l = 1$ inputs and $n^l = 1$ outputs at every layer. So layer $l$ looks like: $$a^l = f^l(z^l), \quad z^l = w^l a^{l-1} + w^l_0.$$ In the equation above, we're using the lowercase letters $a^l, z^l, w^l, a^{l-1}, w^l_0$ to emphasize that all of these quantities are scalars just for the moment. We'll look at the more general matrix case below.
To use SGD, then, we want to compute $\partial \mathcal{L}(\text{NN}(x;W),y) / \partial w^l$ and $\partial \mathcal{L}(\text{NN}(x;W),y) / \partial w_0^l$ for each layer $l$ and each data point $(x,y)$. Below we'll write "loss" as an abbreviation for $\mathcal{L}(\text{NN}(x;W),y)$. Then our first quantity of interest is $\partial \text{loss} / \partial w^l$. The chain rule gives us the following.
:::{.column-margin}
Check your understanding: why do we need exactly these quantities for SGD?
:::
First, let's look at the case $l=L$:
$$\begin{aligned}
\frac{ \partial \text{loss} }{ \partial w^L }
&= \frac{ \partial \text{loss} }{ \partial a^L }
\cdot \frac{ \partial a^L }{ \partial z^L }
\cdot \frac{ \partial z^L }{ \partial w^L } \\
&= \frac{ \partial \text{loss} }{ \partial a^L } \cdot (f^L)'(z^L) \cdot a^{L-1}.
\end{aligned}$$
Now we can look at the case of general $l$:
$$\begin{aligned}
\frac{ \partial \text{loss} }{ \partial w^l }
&= \frac{ \partial \text{loss} }{ \partial a^L }
\cdot \frac{ \partial a^L }{ \partial z^L }
\cdot \frac{ \partial z^L }{ \partial a^{L-1} }
\cdot \frac{ \partial a^{L-1} }{ \partial z^{L-1} }
\cdots
\frac{ \partial z^{l+1} }{ \partial a^{l} }
\cdot \frac{ \partial a^{l} }{ \partial z^l }
\cdot \frac{ \partial z^l }{ \partial w^l } \\
&= \frac{ \partial \text{loss} }{ \partial a^L } \cdot (f^L)'(z^L) \cdot w^L
\cdot (f^{L-1})'(z^{L-1})
\cdots
\cdot w^{l+1}
\cdot (f^{l})'(z^{l})
\cdot a^{l-1} \\
&= \frac{ \partial \text{loss} }{ \partial z^l } \cdot a^{l-1}.
\end{aligned}$$
Note that every multiplication above is scalar multiplication because every term in every product above is a scalar. And though we solved for all the other terms in the product, we haven't solved for $\partial \text{loss} / \partial a^L$ because the derivative will depend on which loss function you choose. Once you choose a loss function though, you should be able to compute this derivative.
:::{.study-question-callout}
Suppose you choose squared loss. What is $\partial \text{loss} / \partial a^L$?
:::
:::{.study-question-callout}
Check the derivations above yourself. You should use the chain rule and also solve for the individual derivatives that arise in the chain rule.
:::
:::{.study-question-callout}
Check that the final layer ($l=L$) case is a special case of the general layer $l$ case above.
:::
:::{.study-question-callout}
Derive $\partial \mathcal{L}(\text{NN}(x;W),y) / \partial w_0^l$ for yourself, for both the final layer ($l=L$) and general $l$.
:::
:::{.study-question-callout}
Does the $L=1$ case remind you of anything from earlier in this course?
:::
:::{.study-question-callout}
Write out the full SGD algorithm for this neural network.
:::
It's pretty typical to run the chain rule from left to right like we did above. But, for where we're going next, it will be useful to notice that it's completely equivalent to write it in the other direction. So we can rewrite our result from above as follows:
$$\begin{aligned}
\frac{ \partial \text{loss} }{ \partial w^l }
&= a^{l-1} \cdot \frac{ \partial \text{loss} }{ \partial z^l } \\
\end{aligned}
$${#eq-gradloss_oned}
$$\begin{aligned}
\frac{ \partial \text{loss} }{ \partial z^l }
&= \frac{ \partial a^{l} }{ \partial z^l }
\cdot \frac{ \partial z^{l+1} }{ \partial a^{l} }
\cdots
\frac{ \partial a^{L-1} }{ \partial z^{L-1} }
\cdot \frac{ \partial z^L }{ \partial a^{L-1} }
\cdot \frac{ \partial a^L }{ \partial z^L }
\cdot \frac{ \partial \text{loss} }{ \partial a^L } \\
\end{aligned}
$${#eq-gradz_intermediate_oned}
$$
\begin{aligned}
&= \frac{ \partial a^{l} }{ \partial z^l } \cdot w^{l+1}
\cdots
\frac{ \partial a^{L-1} }{ \partial z^{L-1} } \cdot w^L \cdot \frac{ \partial a^L }{ \partial z^L }
\cdot \frac{ \partial \text{loss} }{ \partial a^L }.
\end{aligned}
$${#eq-gradz_oned}
:::{.column-margin}
Even though we have reordered the gradients for notational convenience, when actually computing the product in @eq-gradz_oned, it is computationally much cheaper to run the multiplications from right-to-left than from left-to-right. Convince yourself of this, by reasoning through the cost of the matrix multiplications in each case.
:::
### The general case
Next we're going to do everything that we did above, but this time we'll allow any number of inputs $m^l$ and outputs $n^l$ at every layer. First, we'll tell you the results that correspond to our derivations above. Then we'll talk about why they make sense. And finally we'll derive them carefully.
OK, let's start with the results! Again, below we'll be using "loss" as an abbreviation for $\mathcal{L}(\text{NN}(x;W),y)$. Then,
$$
\begin{aligned}
\underbrace{\frac{\partial \text{loss}}{\partial W^l}}_{m^l \times n^l}
&=
\underbrace{A^{l-1}}_{m^l \times 1} \;
\underbrace{\left(\frac{\partial \text{loss}}{\partial