-
Notifications
You must be signed in to change notification settings - Fork 339
/
chap3.tex
2552 lines (2197 loc) · 171 KB
/
chap3.tex
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
% file: chap3.tex
\chapter{改进神经网络的学习方法}
\label{ch:ImprovingTheWayNeuralNetworksLearn}
当一个高尔夫球员刚开始学习打高尔夫时,他们通常会在挥杆的练习上花费大多数时间。慢
慢地他们才会在基本的挥杆上通过变化发展其他的击球方式,学习低飞球、左曲球和右曲球。
类似的,我们现在仍然聚焦在反向传播算法的理解上。这就是我们的“基本挥杆”,神经网络
中大部分工作学习和研究的基础。本章,我会解释若干技术能够用来提升我们关于%
\gls*{bp}的初级的实现,最终改进网络学习的方式。
本章涉及的技术包括:更好的\gls*{cost-func}的选择~——~%
\hyperref[sec:the_cross-entropy_cost_function]{交叉熵}\gls*{cost-func};四种称为%
\hyperref[sec:overfitting_and_regularization]{“\gls*{regularization}”的方法}(L1 和 L2 \gls*{regularization},
\gls*{dropout}和训练数据的人为扩展),这会让我们的网络在训练集之外的数据上更好地泛化;%
\hyperref[sec:weight_initialization]{更好的\gls*{weight}初始化方法};还有%
\hyperref[sec:how_to_choose_a_neural_network's_hyper-parameters]{帮助选择好的超
参数的启发式想法}。同样我也会再给出一些简要的%
\hyperref[sec:other_techniques]{其他技术介绍}。这些讨论之间的独立性比较大,所有
你们可以随自己的意愿挑着看。另外我还会在代码中%
\hyperref[sec:handwriting_recognition_revisited_the_code]{实现}这些技术,使用他
们来提高在\hyperref[ch:UsingNeuralNetsToRecognizeHandwrittenDigits]{第一章}中的
分类问题上的性能。
当然,我们仅仅覆盖了大量已经在神经网络中研究发展出的技术的一点点内容。从林林总总
的已有技术中入门的最佳策略,是深入研究一小部分最重要的,这是本书的观点。掌握了这
些关键技术不仅仅对这些技术本身的理解很有用,而且会深化你对使用神经网络时会遇到哪
些问题的理解。这会让你做好在需要时快速学会其他技术的充分准备。
\section{交叉熵代价函数}
\label{sec:the_cross-entropy_cost_function}
我们大多数人不喜欢被指出错误。在开始学习弹奏钢琴不久后,我在一个听众前做了处女秀。
我很紧张,开始时将八度音阶的曲段演奏得很低。我很困惑,因为不能继续演奏下去了,直
到有个人指出了其中的错误。当时,我非常尴尬。不过,尽管不开心,我们却能够因为明显
的犯错快速地学习到正确的东西。你应该相信下次我再演奏肯定会是正确的!相反,在我们
的错误不是很好地定义的时候,学习的过程会变得更加缓慢。
理想地,我们希望和期待神经网络可以从错误中快速地学习。在实践中,这种情况经常出现
吗?为了回答这个问题,让我们看看一个小例子。这个例子包含一个只有一个输入的神经元:
\begin{center}
\includegraphics{tikz28}
\end{center}
我们会训练这个神经元来做一件非常简单的事:让输入 $1$ 转化为 $0$。当然,这很简单
了,手工找到合适的\gls*{weight}和\gls*{bias}就可以了,不需要什么学习算法。然而,
看起来使用梯度下降的方式来学习\gls*{weight}和\gls*{bias}是很有启发的。所以,我们
来看看神经元如何学习。
为了让这个例子更明确,我会首先将\gls*{weight}和\gls*{bias}初始化为 $0.6$ 和$0.9$。
这些就是一般的开始学习的选择,并没有任何刻意的想法。一开始的神经元的输出是
$0.82$,所以这离我们的目标输出 $0.0$ 还差得很远。从下图来看看神经元如何学习到让
输出接近 $0.0$ 的。注意这些图像实际上是正在进行梯度的计算,然后使用梯度更新来对%
\gls*{weight}和\gls*{bias}进行更新,并且展示结果。设置\gls*{learning-rate}
$\eta=0.15$ 进行学习一方面足够慢的让我们跟随学习的过程,另一方面也保证了学习的时
间不会太久,几秒钟应该就足够了。\gls*{cost-func}是我们在第一章用到的二次函数,$C$。这里
我也会给出准确的形式,所以不需要翻到前面查看定义了。
\begin{center}
\includegraphics{saturation1-0}
\end{center}
随着\gls*{epoch}的增加,神经元的输出、\gls*{weight}、\gls*{bias}和代价的变化如下面
一系列图形所示:
\begin{center}
\includegraphics{saturation1-50}\includegraphics{saturation1-100}\\
\includegraphics{saturation1-150}\includegraphics{saturation1-200}\\
\includegraphics{saturation1-250}\includegraphics{saturation1-300}
\end{center}
正如你所见,神经元快速地学到了使得\gls*{cost-func}下降的\gls*{weight}和\gls*{bias},给出
了最终的输出为 $0.09$。这虽然不是我们的目标输出 $0.0$,但是已经挺好了。假设我们
现在将初始\gls*{weight}和\gls*{bias}都设置为 $2.0$。此时初始输出为 $0.98$,这是
和目标值的差距相当大的。现在看看神经元学习的过程。
\begin{center}
\includegraphics{saturation2-0}
\end{center}
你将看到如下的一系列变化:\label{saturation2_anchor}
\begin{center}
\includegraphics{saturation2-50}\includegraphics{saturation2-100}\\
\includegraphics{saturation2-150}\includegraphics{saturation2-200}\\
\includegraphics{saturation2-250}\includegraphics{saturation2-300}
\end{center}
虽然这个例子使用的了同样的\gls*{learning-rate}($\eta=0.15$),我们可以看到刚开
始的学习速度是比较缓慢的。对前 $150$ 左右的学习次数,\gls*{weight}和\gls*{bias}
并没有发生太大的变化。随后学习速度加快,与上一个例子中类似了,神经网络的输出也迅
速接近 $0.0$。
这种行为看起来和人类学习行为差异很大。正如我在此节开头所说,我们通常是在犯错比较
明显的时候学习的速度最快。但是我们已经看到了人工神经元在其犯错较大的情况下其实学
习很有难度。而且,这种现象不仅仅是在这个小例子中出现,也会在更加一般的神经网络中
出现。为何学习如此缓慢?我们能够找到避免这种情况的方法吗?
为了理解这个问题的源头,想想我们的神经元是通过改变\gls*{weight}和\gls*{bias},并
以一个\gls*{cost-func}的偏导数($\partial C/\partial w$ 和 $\partial C/\partial b$)决定
的速度学习。所以,我们在说“学习缓慢”时,实际上就是说这些偏导数很小。理解他们为
何这么小就是我们面临的挑战。为了理解这些,让我们计算偏导数看看。我们一直在用的是
方程~\eqref{eq:6}表示的二次\gls*{cost-func},定义如下
\begin{equation}
C = \frac{(y-a)^2}{2}
\label{eq:54}\tag{54}
\end{equation}
其中 $a$ 是神经元的输出,训练输入为 $x=1$,$y=0$ 则是目标输出。显式地使用%
\gls*{weight}和偏置来表达这个,我们有 $a = \sigma(z)$,其中 $z = wx+b$。使用链式
法则来求\gls*{weight}和\gls*{bias}的偏导数就有:
\begin{align}
\frac{\partial C}{\partial w} &= (a-y)\sigma'(z) x = a \sigma'(z)\label{eq:55}\tag{55}\\
\frac{\partial C}{\partial b} &= (a-y)\sigma'(z) = a \sigma'(z)\label{eq:56}\tag{56}
\end{align}
其中我已经将 $x = 1$ 和 $y = 0$ 代入了。为了理解这些表达式的行为,让我们仔细看
$\sigma'(z)$ 这一项。首先回忆一下 $\sigma$ 函数图像:
\begin{center}
\includegraphics{sigmoid_function}
\end{center}
我们可以从这幅图看出,当神经元的输出接近 $1$ 的时候,曲线变得相当平,所以
$\sigma'(z)$ 就很小了。方程~\eqref{eq:55} 和~\eqref{eq:56} 也告诉我们 $\partial
C/\partial w$ 和 $\partial C/\partial b$ 会非常小。这其实就是学习缓慢的原因所在。
而且,我们后面也会提到,这种学习速度下降的原因实际上也是更加一般的神经网络学习缓
慢的原因,并不仅仅是在这个特例中特有的。
\subsection{引入交叉熵代价函数}
\label{sec:introducing_the_cross-entropy_cost_function}
那么我们如何解决这个问题呢?研究表明,我们可以通过使用交叉熵\gls*{cost-func}来替换二次代
价函数。为了理解什么是交叉熵,我们稍微改变一下之前的简单例子。假设,我们现在要训
练一个包含若干输入变量的的神经元,$x_1, x_2, \ldots$ 对应的\gls*{weight}为 $w_1,
w_2, \ldots$ 和\gls*{bias} $b$:
\begin{center}
\includegraphics{tikz29}
\end{center}
神经元的输出就是 $a = \sigma(z)$,其中 $z = \sum_j w_j x_j+b$ 是输入的带权和。我
们如下定义这个神经元的交叉熵\gls*{cost-func}:
\begin{equation}
C = -\frac{1}{n} \sum_x \left[y \ln a + (1-y ) \ln (1-a) \right]
\label{eq:57}\tag{57}
\end{equation}
其中 $n$ 是训练数据的总数,求和是在所有的训练输入 $x$ 上进行的,$y$ 是对应的目标
输出。
表达式~\eqref{eq:57} 是否解决学习缓慢的问题并不明显。实际上,甚至将这个定义看做
是\gls*{cost-func}也不是显而易见的!在解决学习缓慢前,我们来看看交叉熵为何能够解释成一个
\gls*{cost-func}。
将交叉熵看做是\gls*{cost-func}有两点原因。第一,它是非负的,$C > 0$。可以看出:(a)
\eqref{eq:57} 中的求和中的所有独立的项都是负数的,因为对数函数的定义域是 $(0,1)$;
(b)求和前面有一个负号。
第二,如果对于所有的训练输入 $x$,神经元实际的输出接近目标值,那么交叉熵将接近
$0$\footnote{为了证明它我需要假设目标输出 $y$ 都是 $0$或$1$。这通常是解决分类问
题的情况,例如,当计算布尔函数时。要了解当我们不做这个假设时会发生什么,看看在
本节结束时的练习。}。假设在这个例子中,$y=0$ 而 $a\approx 0$。这是我们想要得到
的结果。我们看到公式 \eqref{eq:57} 中第一个项就消去了,因为 $y=0$,而第二项实际
上就是 $-\ln (1-a)\approx 0$。反之,$y=1$ 而 $a\approx 1$。所以在实际输出和目标
输出之间的差距越小,最终的交叉熵的值就越低了。
综上所述,交叉熵是非负的,在神经元达到很好的正确率的时候会接近 $0$。这些其实就是
我们想要的\gls*{cost-func}的特性。其实这些特性也是二次\gls*{cost-func}具备的。所以,交叉熵就是很
好的选择了。但是交叉熵\gls*{cost-func}有一个比二次\gls*{cost-func}更好的特性就是它避免了学习速度
下降的问题。为了弄清楚这个情况,我们来算算交叉熵函数关于\gls*{weight}的偏导数。
我们将 $a=\sigma(z)$ 代入到 \eqref{eq:57} 中应用两次链式法则,得到:
\begin{align}
\frac{\partial C}{\partial w_j} &= -\frac{1}{n} \sum_x \left(
\frac{y }{\sigma(z)} -\frac{(1-y)}{1-\sigma(z)} \right)
\frac{\partial \sigma}{\partial w_j} \label{eq:58}\tag{58}\\
&= -\frac{1}{n} \sum_x \left(
\frac{y}{\sigma(z)}
-\frac{(1-y)}{1-\sigma(z)} \right)\sigma'(z) x_j \label{eq:59}\tag{59}
\end{align}
将结果合并一下,简化成:
\begin{equation}
\frac{\partial C}{\partial w_j} = \frac{1}{n} \sum_x \frac{\sigma'(z)
x_j}{\sigma(z) (1-\sigma(z))} (\sigma(z)-y)
\label{eq:60}\tag{60}
\end{equation}
根据 $\sigma(z) = 1/(1+e^{-z})$ 的定义,和一些运算,我们可以得到 $\sigma'(z) =
\sigma(z)(1-\sigma(z))$。后面在练习中会要求你计算这个,现在可以直接使用这个结果。
我们看到 $\sigma'(z)$ 和 $\sigma(z)(1-\sigma(z))$ 这两项在方程中直接约去了,所以
最终形式就是:
\begin{equation}
\frac{\partial C}{\partial w_j} = \frac{1}{n} \sum_x x_j(\sigma(z)-y)
\label{eq:61}\tag{61}
\end{equation}
这是一个优美的公式。它告诉我们\gls*{weight}学习的速度受到 $\sigma(z)-y$,也就是
输出中的误差的控制。更大的误差,更快的学习速度。这是我们直觉上期待的结果。特别地,
这个\gls*{cost-func}还避免了像在二次\gls*{cost-func}中类似方程中 $\sigma'(z)$ 导致的学习缓慢,见
方程~\eqref{eq:55}。当我们使用交叉熵的时候,$\sigma'(z)$ 被约掉了,所以我们不再
需要关心它是不是变得很小。这种约除就是交叉熵带来的特效。实际上,这也并不是非常奇
迹的事情。我们在后面可以看到,交叉熵其实只是满足这种特性的一种选择罢了。
根据类似的方法,我们可以计算出关于\gls*{bias}的偏导数。我这里不再给出详细的过程,
你可以轻易验证得到
\begin{equation}
\frac{\partial C}{\partial b} = \frac{1}{n} \sum_x (\sigma(z)-y)
\label{eq:62}\tag{62}
\end{equation}
再一次,这避免了二次\gls*{cost-func}中类似方程~\eqref{eq:56} 中 $\sigma'(z)$ 项导致的学习
缓慢。
\subsection*{练习}
\begin{itemize}
\item 验证 $\sigma'(z) = \sigma(z)(1-\sigma(z))$。
\end{itemize}
让我们重回最初的例子,来看看换成了交叉熵之后的学习过程。现在仍然按照前面的参数配
置来初始化网络,开始\gls*{weight}为 $0.6$,而\gls*{bias}为 $0.9$。
\begin{center}
\includegraphics{saturation3-0}
\end{center}
看看在换成交叉熵之后网络的学习情况,你将看到如下变化的曲线:
\begin{center}
\includegraphics{saturation3-50}\includegraphics{saturation3-100}\\
\includegraphics{saturation3-150}\includegraphics{saturation3-200}\\
\includegraphics{saturation3-250}\includegraphics{saturation3-300}
\end{center}
毫不奇怪,在这个例子中,神经元学习得相当出色,跟之前差不多。现在我们再看看之前出
问题的那个例子(\hyperref[saturation2_anchor]{链接}),\gls*{weight}和%
\gls*{bias}都初始化为$2.0$:
\begin{center}
\includegraphics{saturation4-0}
\end{center}
你将看到如下变化的曲线:
\begin{center}
\includegraphics{saturation4-50}\includegraphics{saturation4-100}\\
\includegraphics{saturation4-150}\includegraphics{saturation4-200}\\
\includegraphics{saturation4-250}\includegraphics{saturation4-300}
\end{center}
成功了!这次神经元的学习速度相当快,跟我们预期的那样。如果你观测的足够仔细,你可
以发现\gls*{cost-func}曲线要比二次\gls*{cost-func}训练前面部分要陡很多。那个交叉熵导致的陡度让我
们高兴,这正是我们期待的当神经元开始出现严重错误时能以最快速度学习。
我还没有提及刚才的示例用了什么\gls*{learning-rate}。刚开始使用二次\gls*{cost-func}的时候,
我们使用了 $\eta = 0.15$。在新例子中,我们还应该使用同样的\gls*{learning-rate}吗?
实际上,根据不同的\gls*{cost-func},我们不能够直接去套用同样的\gls*{learning-rate}。这好
比苹果和橙子的比较。对于这两种\gls*{cost-func},我只是通过简单的实验来找到一个能够让我们
看清楚变化过程的\gls*{learning-rate}的值。尽管我不愿意提及,但如果你仍然好奇,这
个例子中我使用了 $\eta = 0.005$。
你可能会反对说,上面\gls*{learning-rate}的改变使得上面的图失去了意义。谁会在意当%
\gls*{learning-rate}的选择是任意挑选的时候神经元学习的速度?!这样的反对其实没有
抓住重点。上面的图例不是想讨论学习的绝对速度。而是想研究学习速度的变化情况。
特别地,当我们使用二次\gls*{cost-func}时,学习在神经元犯了明显的错误的时候却比学习快接近
真实值的时候缓慢;而使用交叉熵学习正是在神经元犯了明显错误的时候速度更快。特别地,
当我们使用二次\gls*{cost-func}时,当神经元在接近正确的输出前犯了明显错误的时候,学习变得%
\textbf{更加缓慢};而使用交叉熵,在神经元犯明显错误时学习得更快。这些现象并不依
赖于如何设置\gls*{learning-rate}。
我们已经研究了一个神经元的交叉熵。不过,将其推广到有很多神经元的多层神经网络上也
是很简单的。特别地,假设 $y = y_1, y_2, \ldots$ 是输出神经元上的目标值,而
$a^L_1, a^L_2, \ldots$ 是实际输出值。那么我们定义交叉熵如下
\begin{equation}
C = -\frac{1}{n} \sum_x \sum_j \left[y_j \ln a^L_j + (1-y_j) \ln (1-a^L_j) \right]
\label{eq:63}\tag{63}
\end{equation}
除了这里需要对所有输出神经元进行求和 $\sum_j$ 外,这个其实和我们早前的公
式~\eqref{eq:57} 是一样的。这里不会给出一个推算的过程,但需要说明的是使用公
式~\eqref{eq:63} 确实会在很多的神经网络中避免学习的缓慢。如果你感兴趣,你可以尝
试一下下面问题的推导。
那么我们应该在什么时候用交叉熵来替换二次\gls*{cost-func}?实际上,如果在输出神经元是%
\gls*{sigmoid-neuron}时,交叉熵一般都是更好的选择。为什么?考虑一下我们初始化网
络的\gls*{weight}和\gls*{bias}的时候通常使用某种随机方法。可能会发生这样的情况,
这些初始选择会对某些训练输入误差相当明显~——~比如说,目标输出是 $1$,而实际值是
$0$,或者完全反过来。如果我们使用二次\gls*{cost-func},那么这就会导致学习速度的下降。它
并不会完全终止学习的过程,因为这些\gls*{weight}会持续从其他的样本中进行学习,但
是显然这不是我们想要的效果。
\subsection*{练习}
\begin{itemize}
\item 一个小问题就是刚接触交叉熵时,很难一下子记住那些诸如 $y$ 和 $a$ 的表达式对
应的角色。又比如说,表达式的正确形式是 $-[y \ln a + (1-y) \ln (1-a)]$ 还是
$-[a \ln y + (1-a) \ln (1-y)]$。在 $y=0$ 或者 $1$ 的时候第二个表达式的结果怎样?
这个问题会困扰第一个表达式吗?为什么?
\item 在对单个神经元讨论中,我们指出如果对所有的训练数据有 $\sigma(z) \approx y$,
交叉熵会很小。这个论断其实是和 $y$ 只是等于 $1$ 或者 $0$ 有关。这在分类问题一
般是可行的,但是对其他的问题(如回归问题)$y$ 可以取 $0$ 和 $1$ 之间的中间值的。
证明,交叉熵对所有训练输入在 $\sigma(z) = y$ 时仍然是最小化的。此时交叉熵的表
示是:
\begin{equation}
C = -\frac{1}{n} \sum_x [y \ln y+(1-y) \ln(1-y)]
\label{eq:64}\tag{64}
\end{equation}
而其中 $-[y \ln y+(1-y)\ln(1-y)]$ 有时候被称为%
\href{http://en.wikipedia.org/wiki/Binary_entropy_function}{二元熵}。
\end{itemize}
\subsection*{问题}
\begin{itemize}
\item \textbf{多层多神经元网络}\quad 用%
\hyperref[ch:HowTheBackpropagationAlgorithmWorks]{上一章}的定义符号,证明对二
次\gls*{cost-func},关于输出层的\gls*{weight}的偏导数为
\begin{equation}
\frac{\partial C}{\partial w^L_{jk}} = \frac{1}{n}
\sum_x a^{L-1}_k (a^L_j-y_j) \sigma'(z^L_j)
\label{eq:65}\tag{65}
\end{equation}
项 $\sigma'(z^L_j)$ 会在一个输出神经元困在错误值时导致学习速度的下降。证明对于
交叉熵\gls*{cost-func},针对一个训练样本 $x$ 的输出误差 $\delta^L$为
\begin{equation}
\delta^L = a^L-y
\label{eq:66}\tag{66}
\end{equation}
使用这个表达式来证明关于输出层的\gls*{weight}的偏导数为
\begin{equation}
\frac{\partial C}{\partial w^L_{jk}} = \frac{1}{n} \sum_x
a^{L-1}_k (a^L_j-y_j)
\label{eq:67}\tag{67}
\end{equation}
这里 $\sigma'(z^L_j)$ 就消失了,所以交叉熵避免了学习缓慢的问题,不仅仅是在一个
神经元上,而且在多层多元网络上都起作用。这个分析过程稍作变化对\gls*{bias}也是一样的。
如果你对这个还不确定,那么请仔细研读一下前面的分析。
\item \textbf{在输出层使用线性神经元时使用二次\gls*{cost-func}}\quad 假设我们有一个多层
多神经元网络,最终输出层的神经元都是\textbf{线性神经元},输出不再是 \gls*{sigmoid-func}作
用的结果,而是 $a^L_j = z^L_j$。证明如果我们使用二次\gls*{cost-func},那么对单个训练样
本 $x$ 的输出误差就是
\begin{equation}
\delta^L = a^L-y
\label{eq:68}\tag{68}
\end{equation}
类似于前一个问题,使用这个表达式来证明关于输出层的\gls*{weight}和\gls*{bias}的
偏导数为
\begin{align}
\frac{\partial C}{\partial w^L_{jk}} &= \frac{1}{n} \sum_x
a^{L-1}_k (a^L_j-y_j) \label{eq:69}\tag{69}\\
\frac{\partial C}{\partial b^L_{j}} &= \frac{1}{n} \sum_x
(a^L_j-y_j) \label{eq:70}\tag{70}
\end{align}
这表明如果输出神经元是线性的那么二次\gls*{cost-func}不再会导致学习速度下降的问题。在此
情形下,二次\gls*{cost-func}就是一种合适的选择。
\end{itemize}
\subsection{使用交叉熵来对 MNIST 数字进行分类}
交叉熵很容易作为使用梯度下降和反向传播方式进行模型学习的一部分来实现。我们将会在
% \hyperref[sec:handwriting_recognition_revisited_the_code]{这一章的后面}进行对%
\hyperref[sec:implementing_our_network_to_classify_digits]{前面的程序}
\lstinline!network.py! 的改进。新的程序写在 \lstinline!network2.py! 中,不仅使
用了交叉熵,还有本章中介绍的其他的技术\footnote{代码可从
\href{https://github.com/mnielsen/neural-networks-and-deep-learning/blob/master/src/network2.py}{GitHub}
获取。}。现在我们看看新的程序在进行 MNIST 数字分类问题上的表现。如在第一章中那
样,我们会使用一个包含 $30$ 个隐藏元的网络,而\gls*{mini-batch}的大小也设置为
$10$。我们将\gls*{learning-rate}设置为 $\eta=0.5$ \footnote{在第一章里我们用了二
次代价和 $\eta = 3.0$ 的\gls*{learning-rate}。上面已经讨论过,准确地说出当代价
函数改变时用“相同的”学习速率意味什么,这是不可能的。对于两种\gls*{cost-func},在给出其
它超参数的选择后,我都做了实验来找出一个接近优化性能的\gls*{learning-rate},顺
便提一下,有一个非常粗略的将交叉熵和二次代价的\gls*{learning-rate}联系起来的推
导。正如我们之前看到的,二次代价的梯度项中有一个额外的 $\sigma' =
\sigma(1-\sigma)$。假设我们把这按照 $\sigma$ 的值平均,$\int_0^1 d\sigma
\sigma(1-\sigma) = 1/6$。我们(非常粗略地)看到二次代价对于相同的%
\gls*{learning-rate},以平均慢 $6$ 倍的速度进行学习。这提示我们一个合理的起点
是把二次代价的\gls*{learning-rate}除以 $6$。当然,这个理由非常不严谨,不应被认
真对待。不过,有时候这是个有用的起点。} 然后训练 $30$ 个\gls*{epoch}。
\lstinline!network2.py! 的接口和 \lstinline!network.py! 稍微不同,但是过程还是很
清楚的。你可以使用如 \lstinline!help(network2.Network.SGD)! 这样的命令来查看
\lstinline!network2.py! 的接口文档。
\begin{lstlisting}[language=Python]
>>> import mnist_loader
>>> training_data, validation_data, test_data = \
... mnist_loader.load_data_wrapper()
>>> import network2
>>> net = network2.Network([784, 30, 10], cost=network2.CrossEntropyCost)
>>> net.large_weight_initializer()
>>> net.SGD(training_data, 30, 10, 0.5, evaluation_data=test_data,
... monitor_evaluation_accuracy=True)
\end{lstlisting}
注意看下,\lstinline!net.large_weight_initializer()! 命令使用和第一章介绍的同样
的方式来进行\gls*{weight}和\gls*{bias}的初始化。我们需要运行这个命令,是因为我们
要在这一章的后面才改变默认的\gls*{weight}初始化命令。运行上面的代码我们得到了一
个 95.49\% 准确率的网络。这跟我们在第一章中使用二次\gls*{cost-func}得到的结果相当接近了,
95.42\%。
同样也来试试使用 $100$ 个隐藏神经元,交叉熵及其他参数保持不变的情况。在这个情形
下,我们获得了 96.82\% 的准确率。相比第一章使用二次\gls*{cost-func}的结果 96.59\%,这有
一定的提升。这看起来是一个小小的变化,但是考虑到误差率已经从 3.41\% 下降到3.18\%
了。我们已经消除了原误差的 $1/14$。这其实是可观的改进。
令人振奋的是交叉熵\gls*{cost-func}给了我们和二次代价相比类似的或者更好的结果。然而,这些
结果并没有能够明确地证明交叉熵是更好的选择。原因在于我花费了少许努力在选择诸如学
习速率,\gls*{mini-batch}大小等等这样的超参数上。为了让这些提升更具说服力,我们
需要对超参数进行深度的优化。然而,这些结果仍然是令人鼓舞的,巩固了我们早先关于交
叉熵优于二次代价的理论论断。
这只是本章后面的内容和整本书剩余内容中的更为一般的模式的一部分。我们将给出一个新
的技术,然后进行尝试,随后获得“提升的”结果。当然,看到这些进步是很好的。但是这些
提升的解释一般来说都困难重重。它们只有在我们进行了大量工作来优化所有其他的超参数
时,才确实具有说服力。工作量很大,需要大量的计算能力,我们通常也不会进行这样彻底
的调研。相反,我采用上面进行的那些不正式的测试来达成目标。然而你要记住这样的测试
仍然缺乏权威的证明,需要注意那些使得论断失效的迹象。
至此,我们已经花了大量篇幅介绍交叉熵。为何对一个只能给我们的 MNIST 结果带来一点
点性能提升的技术花费这么多的精力?后面,我们会看到其他的技术~——~%
\hyperref[sec:overfitting_and_regularization]{\gls*{regularization}},会带来更大的提升效果。所
以为何要这么细致地讨论交叉熵?部分原因在于交叉熵是一种广泛使用的\gls*{cost-func},值得深
入理解。但是更加重要的原因是神经元的饱和是神经网络中一个关键的问题,整本书都会不
断回归到这个问题上。因此我现在深入讨论交叉熵就是因为这是一种开始理解神经元饱和以
及如何解决这个问题的很好的实验。
\subsection{交叉熵的含义?源自哪里?}
我们对于交叉熵的讨论聚焦在代数分析和代码实现。这虽然很有用,但是也留下了一个未能
回答的更加宽泛的概念上的问题,如:交叉熵究竟表示什么?存在一些直觉上的思考交叉熵
的方法吗?我们如何想到这个概念?
让我们从最后一个问题开始回答:什么能够激发我们想到交叉熵?假设我们发现学习速度下
降了,并理解其原因是因为在公式~\eqref{eq:55} 和~\eqref{eq:56} 中的 $\sigma'(z)$
那一项。在研究了这些公式后,我们可能就会想到选择一个不包含 $\sigma'(z)$ 的代价函
数。所以,这时候对一个训练样本 $x$,其代价 $C = C_x$ 满足:
\begin{align}
\frac{\partial C}{\partial w_j} &= x_j(a-y) \label{eq:71}\tag{71}\\
\frac{\partial C}{\partial b } &= (a-y) \label{eq:72}\tag{72}
\end{align}
如果我们选择的\gls*{cost-func}满足这些条件,那么它们就能以简单的方式呈现这样的特性:初始
误差越大,神经元学习得越快。这也能够解决学习速度下降的问题。实际上,从这些公式开
始,现在我们就看看凭着我们数学的直觉推导出交叉熵的形式是可行的。我们来推一下,由
链式法则,我们有
\begin{equation}
\frac{\partial C}{\partial b} = \frac{\partial C}{\partial a}
\sigma'(z)
\tag{73}
\end{equation}
使用 $\sigma'(z) = \sigma(z)(1-\sigma(z)) = a(1-a)$,上个等式就变成
\begin{equation}
\frac{\partial C}{\partial b} = \frac{\partial C}{\partial a}
a(1-a)
\label{eq:74}\tag{74}
\end{equation}
对比等式~\eqref{eq:72},我们有
\begin{equation}
\frac{\partial C}{\partial a} = \frac{a-y}{a(1-a)}
\label{eq:75}\tag{75}
\end{equation}
对此方程关于 $a$ 进行积分,得到
\begin{equation}
C = -[y \ln a + (1-y) \ln (1-a)]+ {\rm constant}
\label{eq:76}\tag{76}
\end{equation}
其中 {\rm constant} 是积分常量。这是一个单独的训练样本 $x$ 对\gls*{cost-func}的贡献。为
了得到整个的\gls*{cost-func},我们需要对所有的训练样本进行平均,得到了
\begin{equation}
C = -\frac{1}{n} \sum_x [y \ln a +(1-y) \ln(1-a)] + {\rm constant}
\label{eq:77}\tag{77}
\end{equation}
而这里的常量就是所有单独的常量的平均。所以我们看到方程~\eqref{eq:71}
和~\eqref{eq:72} 唯一确定了交叉熵的形式,并加上了一个常量的项。这个交叉熵并不是
凭空产生的。而是一种我们以自然和简单的方法获得的结果。
那么交叉熵直觉含义又是什么?我们如何看待它?深入解释这一点会将我们带到一个不大愿
意讨论的领域。然而,还是值得提一下,有一种源自信息论的解释交叉熵的标准方式。粗略
地说,交叉熵是“不确定性”的一种度量。特别地,我们的神经元想要计算函数 $x
\rightarrow y = y(x)$。但是,它用函数 $x \rightarrow a = a(x)$ 进行了替换。假设
我们将 $a$ 想象成我们神经元估计为 $y = 1$ 的概率,而 $1-a$ 则是 $y=0$ 的概率。那
么交叉熵衡量我们学习到 $y$ 的正确值的平均起来的不确定性。 如果输出我们期望的结果,
不确定性就会小一点;反之,不确定性就大一些。当然,我这里没有严格地给出“不确定性”
到底意味着什么,所以看起来像在夸夸其谈。但是实际上,在信息论中有一种准确的方式来
定义不确定性究竟是什么。不过,我也不清楚在网络上,哪里有好的、短小的、包含有对这
个主题的讨论。但如果你要深入了解的话,维基百科包含一个%
\href{http://en.wikipedia.org/wiki/Cross_entropy#Motivation}{简短的总结},这会指
引你正确地探索这个领域。而更加细节的内容,你们可以阅读
\href{http://books.google.ca/books?id=VWq5GG6ycxMC}{Cover and Thomas} 的第五章涉
及 Kraft 不等式的有关信息论的内容。
\subsection*{问题}
\begin{itemize}
\item 我们已经深入讨论了使用二次\gls*{cost-func}的网络中在输出神经元饱和时候学习缓慢的问
题,另一个可能会影响学习的因素就是在方程~\eqref{eq:61} 中的 $x_j$ 项。由于此项,
当输入 $x_j$ 接近 $0$ 时,对应的\gls*{weight} $w_j$ 会学习得相当缓慢。解释为何
不可以通过改变\gls*{cost-func}来消除 $x_j$ 项的影响。
\end{itemize}
\subsection{柔性最大值}
\label{subsec:softmax}
本章,我们大多数情况会使用交叉熵来解决学习缓慢的问题。但是,我希望简要介绍一下另
一种解决这个问题的方法,基于\textbf{\gls{softmax}}神经元层。我们不会实际在剩下的
章节中使用\gls*{softmax}层,所以你如果赶时间,就可以跳到下一个小节了。不过,%
\gls*{softmax}仍然有其重要价值,一方面它本身很有趣,另一方面,因为我们会在%
\hyperref[ch:Deeplearning]{第六章}在对深度神经网络的讨论中使用\gls*{softmax}层。
\gls*{softmax}的想法其实就是为神经网络定义一种新式的输出层。开始时和 S 型层一样
的,首先计算带权输入\footnote{在描述\gls*{softmax}的过程中我们会经常使用%
\hyperref[ch:HowTheBackpropagationAlgorithmWorks]{上一章}中介绍的符号。如果你需
要回想下那些符号,你可能需要看下那一章。} $z^L_j = \sum_{k} w^L_{jk} a^{L-1}_k +
b^L_j$。不过,这里我们不会使用\gls*{sigmoid-func}来获得输出。而是,会在这一层
上应用一种叫做\text{\gls{softmax-func}}在 $z^L_j$ 上。根据这个函数,第 $j$ 个
神经元的激活值 $a^L_j$ 就是
\begin{equation}
a^L_j = \frac{e^{z^L_j}}{\sum_k e^{z^L_k}}
\label{eq:78}\tag{78}
\end{equation}
其中,分母中的求和是在所有的输出神经元上进行的。
如果你不熟悉这个\gls*{softmax-func},方程~\eqref{eq:78} 可能看起来会比较难理解。
因为对于使用这个函数的原因你不清楚。这能帮我们解决学习缓慢的问题也不明显。为了更
好地理解方程~\eqref{eq:78},假设我们有一个包含四个输出神经元的神经网络,对应四个
带权输入,表示为 $z^L_1, z^L_2, z^L_3$ 和 $z^L_4$。下面的例子中的条块显示带权输
入的可取值,和对应输出激活值的图形。要探索它,一个好的开始的地方是增加 $z^L_4$:
\begin{center}
\includegraphics{softmax}
\end{center}
当你增加 $z^L_4$ 的时候,你可以看到对应激活值 $a^L_4$ 的增加,而其他的激活值就在
下降。例如下图显示 $z^L_4$ 为 $2$ 时:
\begin{center}
\includegraphics{softmax-1}
\end{center}
下图显示 $z^L_4$ 为 $5$ 时:
\begin{center}
\includegraphics{softmax-2}
\end{center}
类似地,如果你降低 $z^L_4$ 那么 $a^L_4$ 就随之下降,而其它激活值则增加。例如下图
显示 $z^L_4$ 为 $-2$ 时:
\begin{center}
\includegraphics{softmax-3}
\end{center}
下图显示 $z^L_4$ 为 $-5$ 时:
\begin{center}
\includegraphics{softmax-4}
\end{center}
实际上,如果你仔细看,你会发现在两种情形下,其他激活值的整个改变恰好填补
了 $a^L_4$的变化的空白。原因很简单,根据定义,输出的激活值加起来正好为 $1$,使用
公式~\eqref{eq:78} 我们可以证明:
\begin{equation}
\sum_j a^L_j = \frac{\sum_j e^{z^L_j}}{\sum_k e^{z^L_k}} = 1
\label{eq:79}\tag{79}
\end{equation}
所以,如果 $a^L_4$ 增加,那么其他输出激活值肯定会总共下降相同的量,来保证所有激活
值的和为 $1$。当然,类似的结论对其他的激活值也需要满足。
方程~\eqref{eq:78} 同样保证输出激活值都是正数,因为指数函数是正的。将这两点结合起
来,我们看到柔性最大值层的输出是一些相加为 $1$ 正数的集合。换言之,柔性最大值层的
输出可以被看做是一个概率分布。
这样的效果很令人满意。在很多问题中,能够将输出激活值 $a^L_j$ 理解为网络对于正确输
出为 $j$ 的概率的估计是非常方便的。所以,比如在 MNIST 分类问题中,我们可以
将 $a^L_j$ 解释成网络估计正确数字分类为 $j$ 的概率。
对比一下,如果输出层是 S 型层,那么我们肯定不能假设激活值形成了一个概率分布。我不
会证明这一点,但是源自 S 型层的激活值是不能够形成一种概率分布的一般形式的。所以使
用 S 型输出层,我们没有这样一个关于输出激活值的简单解释。
\subsection*{练习}
\begin{itemize}
\item 构造例子表明在使用 S 型输出层的网络中输出激活值 $a^L_j$ 的和并不会确保为
$1$。
\end{itemize}
我们现在开始体会到柔性最大值函数的形式和行为特征了。来回顾一下:在公
式~\eqref{eq:78} 中的指数确保了所有的输出激活值是正数。然后方程~\eqref{eq:78} 中
分母的求和又保证了\gls*{softmax}的输出和为 $1$。所以这个特定的形式不再像之前那样
难以理解了:反而是一种确保输出激活值形成一个概率分布的自然的方式。你可以将其想象
成一种重新调节 $z^L_j$ 的方法,然后将这个结果整合起来构成一个概率分布。
\subsection*{练习}
\begin{itemize}
\item \textbf{\gls*{softmax}的单调性}\quad 证明如果 $j=k$ 则 $\partial a^L_j /
\partial z^L_k$ 为正,$j \neq k$ 时为负。结果是,增加 $z^L_j$ 会提高对应的输出
激活值 $a^L_j$ 并降低其他所有输出激活值。我们已经在滑动条示例中实验性地看到了
这一点,这里需要你给出一个严格证明。
\item \textbf{\gls*{softmax}的非局部性}\quad S 型层的一个好处是输出 $a^L_j$ 是对
应带权输入 $a^L_j = \sigma(z^L_j)$ 的函数。解释为何对于\gls*{softmax}层来说,
并不是这样的情况:任何特定的输出激活值 $a^L_j$ 依赖\textbf{所有的}带权输入。
\end{itemize}
\subsection*{问题}
\begin{itemize}
\item \textbf{逆转\gls*{softmax}层}\quad 假设我们有一个使用\gls*{softmax}输出层
的神经网络,然后激活值 $a^L_j$ 已知。证明对应带权输入的形式为 $z^L_j = \ln
a^L_j + C$,其中常量 $C$ 是独立于 $j$ 的。
\end{itemize}
\textbf{学习缓慢问题:} 我们现在已经对柔性最大值神经元层有了一定的认识。但是我们
还没有看到一个柔性最大值层会怎么样解决学习缓慢问题。为了理解这点,让我们先定义一
个\textbf{\gls{log-likelihood}}\gls*{cost-func}。我们使用 $x$ 表示网络的训练输入,$y$ 表
示对应的目标输出。然后关联这个训练输入的\gls*{log-likelihood}\gls*{cost-func}就是
\begin{equation}
C \equiv -\ln a^L_y
\label{eq:80}\tag{80}
\end{equation}
所以,如果我们训练的是 MNIST 图像,输入为 $7$ 的图像,那么对应的%
\gls*{log-likelihood}代价就是 $-\ln a_7^L$。看看这个直觉上的含义,想想当网络表现
很好的时候,也就是确认输入为 $7$ 的时候。这时,他会估计一个对应的概率 $a_7^L$ 跟
$1$ 非常接近,所以代价 $-\ln a_7^L$ 就会很小。反之,如果网络的表现糟糕时,概率
$a_7^L$ 就变得很小,代价 $-\ln a_7^L$ 随之增大。所以对数似然\gls*{cost-func}也是满足我们
期待的\gls*{cost-func}的条件的。
那关于学习缓慢问题呢?为了分析它,回想一下学习缓慢的关键就是量 $\partial C /
\partial w^L_{jk}$ 和 $\partial C / \partial b^L_j$ 的变化情况。我不会显式地给出
详细的推导~——~我会在下面的问题中要求你们完成推导~——~但是通过一点代数运算你会得
到\footnote{注意这里的表示上的差异,这里的 $y$ 和上一段中的不同。在上一段中我们
用 $y$ 来表示网络的目标输出,例如,如果输入是 7 的图像输出一个 $7$。但是接下来
的方程中,我用 $y$ 表示对应于 $7$ 的输出激活值的向量,即,它是一个除了
第 7 位为 $1$,其它所有位都是 $0$ 的向量。}:
\begin{align}
\frac{\partial C}{\partial b^L_j} &= a^L_j-y_j \label{eq:81}\tag{81}\\
\frac{\partial C}{\partial w^L_{jk}} &= a^{L-1}_k (a^L_j-y_j) \label{eq:82}\tag{82}
\end{align}
这些方程其实和我们前面对交叉熵得到的类似。就拿方程~\eqref{eq:82} 和~\eqref{eq:67}比
较。尽管后者我对整个训练样本进行了平均,不过形式还是一致的。而且,正如前面的分析,
这些表达式确保我们不会遇到学习缓慢的问题。事实上,把一个具有%
\gls*{log-likelihood}代价的\gls*{softmax}输出层,看作与一个具有交叉熵代价的 S 型
输出层非常相似,这是很有用的。
有了这样的相似性,你应该使用一个具有交叉熵代价的 S 型输出层,还是一个具有对数似然
代价的柔性最大值输出层呢?实际上,在很多应用场景中,这两种方式的效果都不错。本章
剩下的内容,我们会使用一个 S 型输出层和交叉熵代价的组合。后面,
在\hyperref[ch:Deeplearning]{第六章}中,我们有时候会使用柔性最大值输出层和对数似
然代价的组合。切换的原因就是为了让我们的网络和某些在具有影响力的学术论文中的形式
更为相似。作为一种更加通用的视角,柔性最大值加上对数似然的组合更加适用于那些需要
将输出激活值解释为概率的场景。那并不总是一个需要关注的问题,但是在诸如 MNIST 这种
有着不重叠的分类问题上确实很有用。
\subsection*{问题}
\begin{itemize}
\item 推导方程~\eqref{eq:81} 和~\eqref{eq:82}
\item \textbf{柔性最大值这个名称从何处来?}\quad 假设我们改变一下柔性最大值函数,
使得输出激活值定义如下
\begin{equation}
a^L_j = \frac{e^{c z^L_j}}{\sum_k e^{c z^L_k}}
\label{eq:83}\tag{83}
\end{equation}
其中 $c$ 是正的常量。注意 $c=1$ 对应标准的柔性最大值函数。但是如果我们使用不同
的 $c$ 得到不同的函数,其本质上和原来的柔性最大值函数是很相似的。特别地,证明
输出激活值也会形成一个概率分布,正如通常的柔性最大值函数。假设我们允许 $c$ 足
够大,比如说 $c\rightarrow \infty$。那么输出激活值 $a_j^L$ 的极限值是什么?在
解决了这个问题后,你应该能够清楚为什么我们把 $c=1$ 对应的函数看作是一个最大化
函数的变柔和的版本。这就是柔性最大值术语的来源。
\item \textbf{柔性最大值和对数似然的\gls*{bp}}\quad 上一章,我们推导了使用 S 型层
的\gls*{bp}算法。为了应用在柔性最大值层的网络上,我们需要搞清楚最后一层上误差的
表示 $\delta^L_j \equiv \partial C / \partial z^L_j$。证明形式如下:
\begin{equation}
\delta^L_j = a^L_j -y_j
\label{eq:84}\tag{84}
\end{equation}
使用这个表达式,我们可以在使用柔性最大值层和对数似然代价的网络上应用\gls*{bp}。
\end{itemize}
\section{过度拟合和正则化}
\label{sec:overfitting_and_regularization}
诺贝尔奖获得者,物理学家恩里科·费米有一次被问到他对一些同僚提出的一个数学模型的
意见,这个数学模型尝试解决一个重要的未解决的物理难题。模型和实验非常匹配,但是费
米却对其产生了怀疑。他问模型中需要设置的自由参数有多少个。答案是“4”。费米回答
道\footnote{这个引用的故事来自一篇
\href{http://www.nature.com/nature/journal/v427/n6972/full/427297a.html}{Freeman
Dyson} 所写的引人入胜的文章。他是提出这个有瑕疵的模型的人之一。一个关于四个
参数模拟大象的例子可以在%
\href{http://www.johndcook.com/blog/2011/06/21/how-to-fit-an-elephant/}{这里}%
找到。}:“我记得我的朋友约翰·冯·诺伊曼过去常说,有四个参数,我可以模拟一头大
象,而有五个参数,我还能让他卷鼻子。”
这里,其实是说拥有大量的自由参数的模型能够描述特别神奇的现象。即使这样的模型能够
很好的拟合已有的数据,但并不表示是一个好模型。因为这可能只是因为模型中足够的自由
度使得它可以描述几乎所有给定大小的数据集,而不需要真正洞察现象的本质。所以发生这
种情形时,模型对已有的数据会表现的很好,但是对新的数据很难泛化。对一个模型真正的
测验就是它对没有见过的场景的预测能力。
费米和冯·诺伊曼对有四个参数的模型就开始怀疑了。我们用来对 MNIST 数字分类的 30 个
隐藏神经元神经网络拥有将近 24,000 个参数!当然很多。我们有 100 个隐藏元的网络拥有
将近 80,000 个参数,而目前最先进的深度神经网络包含百万级或者十亿级的参数。我们应
当信赖这些结果么?
让我们通过构造一个网络泛化能力很差的例子使这个问题更清晰。我们的网络有 30 个隐藏
神经元,共 23,860 个参数。但是我们不会使用所有 50,000 幅 MNIST 训练图像。相反,我
们只使用前 1,000 幅图像。使用这个受限的集合,会让泛化的问题突显。我们按照之前同样
的方式,使用交叉熵\gls*{cost-func},\gls*{learning-rate}设置为 $\eta = 0.5$ 而\gls*{mini-batch}大小
设置为 $10$。不过这里我们要训练 400 个\gls*{epoch},比前面的要多一些,因为我们只用了
少量的训练样本。我们现在使用 \lstinline!network2! 来研究\gls*{cost-func}改变的情况:
\begin{lstlisting}[language=Python]
>>> import mnist_loader
>>> training_data, validation_data, test_data = \
... mnist_loader.load_data_wrapper()
>>> import network2
>>> net = network2.Network([784, 30, 10], cost=network2.CrossEntropyCost)
>>> net.large_weight_initializer()
>>> net.SGD(training_data[:1000], 400, 10, 0.5, evaluation_data=test_data,
... monitor_evaluation_accuracy=True, monitor_training_cost=True)
\end{lstlisting}
使用上面的结果,我们可以画出当网络学习时代价变化的情况\footnote{这个图形以及接下
来的四个由程序
\href{https://github.com/mnielsen/neural-networks-and-deep-learning/blob/master/fig/overfitting.py}{overfitting.py}
生成。}:
\begin{center}
\includegraphics[width=.6\textwidth]{overfitting1}
\end{center}
这看起来令人振奋,因为\gls*{cost-func}有一个光滑的下降,跟我们预期一致。注意,我只是展示
了 $200$ 到 $399$ \gls*{epoch}的情况。这给出了很好的近距离理解训练后期的情况,也是出
现有趣现象的地方。
让我们看看分类准确率在测试集上的表现:
\begin{center}
\includegraphics[width=.6\textwidth]{overfitting2}
\end{center}
这里我还是聚焦到了后面的过程。 在前 200 \gls*{epoch}(图中没有显示)中准确率提升
到了 82\%。然后学习逐渐变缓。最终,在 280 \gls*{epoch}左右分类准确率就停止了增长。
后面的\gls*{epoch},仅仅看到了在 280 \gls*{epoch}准确率周围随机的小波动。将这幅
图和前面的图进行对比,前面的图中和训练数据相关的代价持续平滑下降。如果我们只看那
个代价,会发现我们模型的表现变得“更好”。但是测试准确率展示了提升只是一种假象。
就像费米不大喜欢的那个模型一样,我们的网络在 280 \gls*{epoch}后就不再能够推广到
测试数据上。所以这不是有用的学习。我们说网络在 280 \gls*{epoch}后就%
\textbf{\gls{overfitting}}或者\textbf{\gls{overtraining}}了。
你可能想知道这里的问题是不是由于我们看的是训练数据的\textbf{代价},而对比的却是
测试数据上的\textbf{分类准确率}导致的。换言之,可能我们这里在进行苹果和橙子的对
比。如果我们比较训练数据上的代价和测试数据上的代价,会发生什么,我们是在比较类似
的度量吗?或者可能我们可以比较在两个数据集上的分类准确率啊?实际上,不管我们使用
什么度量的方式,尽管细节会变化,但本质上都是一样的。让我们来看看测试数据集上的代
价变化情况:
\begin{center}
\includegraphics[width=.6\textwidth]{overfitting3}
\end{center}
我们可以看到测试集上的代价在 15 \gls*{epoch}前一直在提升,随后越来越差,尽管训练
数据集上的代价表现是越来越好的。这其实是另一种模型\gls*{overfitting}的迹象。尽管,这里带来
了关于我们应当将 15 还是 280 \gls*{epoch}当作是\gls*{overfitting}开始影响学习的时间点的困
扰。从一个实践角度,我们真的关心的是提升测试数据集上的分类准确率,而测试集合上的
代价不过是分类准确率的一个反应。所以更加合理的选择就是将 280 \gls*{epoch}看成是
\gls*{overfitting}开始影响学习的时间点。
另一个\gls*{overfitting}的迹象在训练数据上的分类准确率上也能看出来:
\begin{center}
\includegraphics[width=.6\textwidth]{overfitting4}
\end{center}
准确率一直在提升接近 100\%。也就是说,我们的网络能够正确地对所有 $1000$ 幅图像进
行分类!而在同时,我们的测试准确率仅仅能够达到 82.27\%。所以我们的网络实际上在学
习训练数据集的特例,而不是能够一般地进行识别。我们的网络几乎是在单纯记忆训练集合,
而没有对数字本质进行理解能够泛化到测试数据集上。
\gls*{overfitting}是神经网络的一个主要问题。这在现代网络中特别正常,因为网络\gls*{weight}
和\gls*{bias}数量巨大。为了高效地训练,我们需要一种检测\gls*{overfitting}是不是发生的技术,
这样我们不会过度训练。并且我们也想要找到一些技术来降低\gls*{overfitting}的影响。
检测\gls*{overfitting}的明显方法是使用上面的方法~——~跟踪测试数据集合上的准确率随训练变化情
况。如果我们看到测试数据上的准确率不再提升,那么我们就停止训练。当然,严格地说,
这其实并非是\gls*{overfitting}的一个必要现象,因为测试集和训练集上的准确率可能会同时停止提
升。当然,采用这样的策略是可以阻止\gls*{overfitting}的。
实际上,我们会使用这种策略的变化形式来试验。记得之前我们载入 MNIST 数据时用了三
个数据集:
\begin{lstlisting}[language=Python]
>>> import mnist_loader
>>> training_data, validation_data, test_data = \
... mnist_loader.load_data_wrapper()
\end{lstlisting}
到现在我们一直在使用 \lstinline!training_data! 和 \lstinline!test_data!,没有用
过 \lstinline!validation_data!。\lstinline!validation_data! 中包含了 $10,000$ 幅
数字图像,这些图像和 MNIST 训练数据集中的 $50,000$ 幅图像以及测试数据集中的
$10,000$ 幅都不相同。我们会使用 \lstinline!validation_data! 而不是
\lstinline!test_data! 来防止\gls*{overfitting}。我们会为 \lstinline!test_data! 使用和上面
提到的相同的策略。我们在每个\gls*{epoch}的最后都计算在 \lstinline!validation_data!
上的分类准确率。一旦分类准确率已经饱和,就停止训练。这个策略被称为\textbf{提前停
止}。当然,实际应用中,我们不会立即知道什么时候准确率会饱和。
相反,我们会一直训练直到我们确信准确率已经饱和\footnote{这里需要一些判定标准来确
定什么时候停止。在我前面的图中,将 280 \gls*{epoch}看成是饱和的地方。可能这有点太
悲观了。因为神经网络有时候会训练过程中处在一个稳定期,然后又开始提升。如果在
400 \gls*{epoch}后,性能又开始提升(也许只是一些少量提升),那我也不会诧异。所以,
对提前停止采取更多或更少激进的策略也是可以的。}。
\label{validation_explanation}
为何要使用 \lstinline!validation_data! 来替代 \lstinline!test_data! 防止\gls*{overfitting}
问题?实际上,这是一个更为一般的策略的一部分,这个一般的策略就是使
用 \lstinline!validation_data! 来衡量不同的超参数(如\gls*{epoch},\gls*{learning-rate},
最好的网络架构等等)的选择的效果。我们使用这样方法来找到超参数的合适值。因此,尽
管到现在我并没有提及这点,但其实本书前面已经稍微介绍了一些超参数选择的方法。(更多内容在%
\hyperref[sec:how_to_choose_a_neural_network's_hyper-parameters]{本章后面}讨论)
当然,这仍然没有回答为什么我们用 \lstinline!validation_data! 而不是
\lstinline!test_data! 来防止\gls*{overfitting}的问题。实际上,有一个更加一般的问题,就是为
何用 \lstinline!validation_data! 取代 \lstinline!test_data! 来设置更好的超参数?
为了理解这点,想想当设置超参数时,我们想要尝试许多不同的超参数选择。如果我们设置
超参数是基于 \lstinline!test_data! 的话,可能最终我们就会得到\gls*{overfitting}于
\lstinline!test_data! 的超参数。也就是说,我们可能会找到那些符合
\lstinline!test_data! 特点的超参数,但是网络的性能并不能够泛化到其他数据集合上。
我们借助 \lstinline!validation_data! 来克服这个问题。然后一旦获得了想要的超参数,
最终我们就使用 \lstinline!test_data! 进行准确率测量。这给了我们在
\lstinline!test_data! 上的结果是一个网络泛化能力真正的度量方式的信心。换言之,你
可以将验证集看成是一种特殊的训练数据集能够帮助我们学习好的超参数。这种寻找好的超
参数的方法有时候被称为\textbf{hold out}方法,因为 \lstinline!validation_data! 是从
\lstinline!traning_data! 训练集中留出或者“拿出”的一部分。
在实际应用中,甚至在衡量了 \lstinline!test_data! 的性能后,我们可能也会改变想法并
去尝试另外的方法~——~也许是一种不同的网络架构~——~这将会引入寻找新的超参数的过程。
如果我们这样做,难道不会产生\gls*{overfitting}于 \lstinline!test_data! 的困境么?我们是不是
需要一种数据集的潜在无限回归,这样才能够确信模型能够泛化?去除这样的疑惑其实是一
个深刻而困难的问题。但是对我们实际应用的目标,我们不会担心太多。相反,我们会继续
采用基
于 \lstinline!training_data!,\lstinline!validation_data!,和
\lstinline!test_data! 的基本 Hold-Out 方法。
我们已经研究了只使用 $1,000$ 幅训练图像时的\gls*{overfitting}问题。那么如果我们使用所有的
50,000 幅图像的训练数据会发生什么?我们会保留所有其它的参数都一样($30$ 个隐藏元,
\gls*{learning-rate} $0.5$,\gls*{mini-batch}规模为 $10$),但是\gls*{epoch}为 30 次。下图
展示了分类准确率在训练和测试集上的变化情况。注意我们使用的测试数据,而不是验证集
合,为了让结果看起来和前面的图更方便比较。
\begin{center}
\includegraphics[width=.6\textwidth]{code_samples/fig/overfitting_full}
\end{center}
如你所见,测试集和训练集上的准确率相比我们使用 $1,000$ 个训练数据时相差更小。特
别地,在训练数据上的最佳的分类准确率 97.86\% 只比测试集上的 95.33\% 准确率高了
1.53\%。而之前的例子中,这个差距是 17.73\%!\gls*{overfitting}仍然发生了,但是已经减轻了不少。
我们的网络从训练数据上更好地泛化到了测试数据上。一般来说,最好的降低\gls*{overfitting}的方式
之一就是增加训练样本的量。有了足够的训练数据,就算是一个规模非常大的网络也不大容
易\gls*{overfitting}。不幸的是,训练数据其实是很难或者很昂贵的资源,所以这不是一种太切实际的
选择。
\subsection{正则化}
增加训练样本的数量是一种减轻\gls*{overfitting}的方法。还有其他的方法能够减轻\gls*{overfitting}的程度
吗?一种可行的方式就是降低网络的规模。然而,大的网络拥有一种比小网络更强的潜力,
所以这里存在一种应用冗余性的选项。
幸运的是,还有其他的技术能够缓解\gls*{overfitting},即使我们只有一个固定的网络和固定的训练集
合。这种技术就是\textbf{\gls{regularization}}。本节,我会给出一种最为常用的\gls*{regularization}
手段~——~有时候被称为\textbf{\gls{weight-decay}}或者 \textbf{L2 \gls*{regularization}}。L2 \gls*{regularization}的想法是增加一个额外的
项到\gls*{cost-func}上,这个项叫做\textbf{\gls{regularization-term}}。下面是\gls*{regularization}的交叉熵:
\begin{equation}
C = -\frac{1}{n} \sum_{xj} \left[ y_j \ln a^L_j+(1-y_j) \ln
(1-a^L_j)\right] + \frac{\lambda}{2n} \sum_w w^2
\label{eq:85}\tag{85}
\end{equation}
其中第一个项就是常规的交叉熵的表达式。第二个现在加入的就是所有\gls*{weight}的平方的和。然
后使用一个因子 $\lambda / 2n$ 进行量化调整,其中 $\lambda > 0$ 可以称为\textbf{\gls*{regularization}参数},而 $n$ 就是训练集合的大小。我们会在后面讨论
$\lambda$ 的选择策略。需要注意的是,\gls*{regularization}项里面并\textbf{不}包含\gls*{bias}。这点我们后
面也会再讲述。
当然,对其他的\gls*{cost-func}也可以进行\gls*{regularization},例如二次\gls*{cost-func}。类似的\gls*{regularization}的形式如下:
\begin{equation}
C = \frac{1}{2n} \sum_x \|y-a^L\|^2 + \frac{\lambda}{2n} \sum_w w^2
\label{eq:86}\tag{86}
\end{equation}
两者都可以写成这样:
\begin{equation}
C = C_0 + \frac{\lambda}{2n} \sum_w w^2
\label{eq:87}\tag{87}
\end{equation}
其中 $C_0$ 是原始的\gls*{cost-func}。
直觉地看,\gls*{regularization}的效果是让网络倾向于学习小一点的\gls*{weight},其他的东西都一样的。
大的\gls*{weight}只有能够给出\gls*{cost-func}第一项足够的提升时才被允许。换言之,\gls*{regularization}可以当做一种寻找
小的\gls*{weight}和最小化原始的\gls*{cost-func}之间的折中。这两部分之间相对的重要性就由 $\lambda$
的值来控制了:$\lambda$ 越小,就偏向于最小化原始\gls*{cost-func},反之,倾向于小的\gls*{weight}。
现在,对于这样的折中为何能够减轻\gls*{overfitting}还不是很清楚!但是,实际表现表明了这点。我
们会在下一节来回答这个问题。但现在,我们来看看一个\gls*{regularization}的确减轻\gls*{overfitting}的例子。
为了构造这个例子,我们首先需要弄清楚如何将\gls*{sgd}算法应用在一个\gls*{regularization}的神经
网络上。特别地,我们需要知道如何计算对网络中所有\gls*{weight}和\gls*{bias}的偏导数 $\partial
C/\partial w$ 和 $\partial C/\partial b$。对方程~\eqref{eq:87} 进行求偏导数得:
\begin{align}
\frac{\partial C}{\partial w} & = \frac{\partial C_0}{\partial w} +
\frac{\lambda}{n} w \label{eq:88}\tag{88} \\
\frac{\partial C}{\partial b} & = \frac{\partial C_0}{\partial b} \label{eq:89}\tag{89}
\end{align}
$\partial C_0/\partial w$ 和 $\partial C_0/\partial b$ 可以通过\gls*{bp}进行计算,
正如\hyperref[ch:HowTheBackpropagationAlgorithmWorks]{上一章}中描述的那样。所以
我们看到其实计算\gls*{regularization}的\gls*{cost-func}的梯度是很简单的:仅仅需要\gls*{bp},然后加上
$\frac{\lambda}{n} w$ 得到所有\gls*{weight}的偏导数。而\gls*{bias}的偏导数就不要变化,所以\gls*{bias}的
梯度下降学习规则不会发生变化:
\begin{equation}
b \rightarrow b -\eta \frac{\partial C_0}{\partial b}
\label{eq:90}\tag{90}
\end{equation}
\gls*{weight}的学习规则就变成:
\begin{align}
w & \rightarrow w-\eta \frac{\partial C_0}{\partial
w}-\frac{\eta \lambda}{n} w \label{eq:91}\tag{91}\\
& = \left(1-\frac{\eta \lambda}{n}\right) w -\eta \frac{\partial
C_0}{\partial w} \label{eq:92}\tag{92}
\end{align}
这正和通常的梯度下降学习规则相同,除了通过一个因子 $1-\frac{\eta\lambda}{n}$ 重
新调整了\gls*{weight} $w$。这种调整有时被称为\textbf{\gls*{weight}衰减},因为它使得\gls*{weight}变小。粗看,
这样会导致\gls*{weight}会不断下降到 $0$。但是实际不是这样的,因为如果在原始\gls*{cost-func}中造成
下降的话其他的项可能会让\gls*{weight}增加。
好的,这就是梯度下降工作的原理。那么\gls*{sgd}呢?正如在没有\gls*{regularization}的随机梯度下
降中,我们可以通过平均 $m$ 个训练样本的\gls*{mini-batch}来估计 $\partial C_0/\partial
w$。因此,为了\gls*{sgd}的\gls*{regularization}学习规则就变成(参考方程~\eqref{eq:20})
\begin{equation}
w \rightarrow \left(1-\frac{\eta \lambda}{n}\right) w -\frac{\eta}{m}
\sum_x \frac{\partial C_x}{\partial w}
\label{eq:93}\tag{93}
\end{equation}
其中后面一项是在训练样本的\gls*{mini-batch} $x$ 上进行的,而 $C_x$ 是对每个训练样本的(无\gls*{regularization}%
的)代价。这其实和之前通常的\gls*{sgd}的规则是一样的,除了有一个\gls*{weight}下降
的因子 $1-\frac{\eta \lambda}{n}$。最后,为了完整,我给出\gls*{bias}的\gls*{regularization}的学习规则。
这当然是和我们之前的非\gls*{regularization}的情形一致了(参考公式~\eqref{eq:21}),
\begin{equation}
b \rightarrow b - \frac{\eta}{m} \sum_x \frac{\partial C_x}{\partial b}
\label{eq:94}\tag{94}
\end{equation}
这里求和也是在训练样本的\gls*{mini-batch} $x$ 上进行的。
让我们看看\gls*{regularization}给网络带来的性能提升吧。这里还会使用有 $30$ 个隐藏神经元、\gls*{mini-batch}
大小为 $10$,\gls*{learning-rate}为 $0.5$,使用交叉熵的神经网络。然而,这次我们会使用\gls*{regularization}%
参数为 $\lambda = 0.1$。注意在代码中,我们使用的变量名字为 \lstinline!lmbda!,
这是因为在 Python 中 \lstinline!lambda! 是关键字,有着不相关的含义。我也会再次使
用 \lstinline!test_data!,而不是 \lstinline!validation_data!。不过严格地讲,我们
应当使用 \lstinline!validation_data! 的,因为前面已经讲过了。这里我这样做,是因
为这会让结果和非\gls*{regularization}的结果对比起来效果更加直接。你可以轻松地调整为
\lstinline!validation_data!,你会发现有相似的结果。
\begin{lstlisting}[language=Python]
>>> import mnist_loader
>>> training_data, validation_data, test_data = \
... mnist_loader.load_data_wrapper()
>>> import network2
>>> net = network2.Network([784, 30, 10], cost=network2.CrossEntropyCost)
>>> net.large_weight_initializer()
>>> net.SGD(training_data[:1000], 400, 10, 0.5,
... evaluation_data=test_data, lmbda = 0.1,
... monitor_evaluation_cost=True, monitor_evaluation_accuracy=True,
... monitor_training_cost=True, monitor_training_accuracy=True)
\end{lstlisting}
训练集上的\gls*{cost-func}持续下降,和前面无\gls*{regularization}的情况一样的规律\footnote{这个以及下一
个图像由程序
\href{https://github.com/mnielsen/neural-networks-and-deep-learning/blob/master/fig/overfitting.py}{overfitting.py}
生成。}:
\begin{center}
\includegraphics[width=.6\textwidth]{regularized1}
\end{center}
但是这次测试集上的准确率在整个 400 \gls*{epoch}内持续增加:
\begin{center}
\includegraphics[width=.6\textwidth]{regularized2}
\end{center}
显然,\gls*{regularization}的使用能够解决\gls*{overfitting}的问题。而且,准确率相当高了,最高处达到了
87.1\%,相较于之前的 82.27\%。因此,我们几乎可以确信在 400 \gls*{epoch}之后持续训
练会有更加好的结果。看起来,经实践检验,\gls*{regularization}让网络具有更好的泛化能力,显著地减
轻了\gls*{overfitting}的影响。
如果我们摆脱人为的仅用 1,000 个训练图像的环境,转而用所有 50,000 图像的训练集,
会发生什么?当然,我们之前已经看到\gls*{overfitting}在大规模的数据上其实不是那么明显了。那%
\gls*{regularization}能不能起到相应的作用呢?保持超参数和之前一样,30 \gls*{epoch}, \gls*{learning-rate}为 0.5,
\gls*{mini-batch}大小为 10。不过我们这里需要改变\gls*{regularization}参数。原因在于训练数据的大小已
经从 $n=1,000$ 改成了 $n=50,000$,这个会改变\gls*{weight}衰减因子
$1-\frac{\eta\lambda}{n}$。如果我们持续使用 $\lambda = 0.1$ 就会产生很小的\gls*{weight}衰
减,因此就将\gls*{regularization}的效果降低很多。我们通过修改为 $\lambda = 5.0$ 来补偿这种下降。
好了,来训练网络,重新初始化\gls*{weight}:
\begin{lstlisting}[language=Python]
>>> net.large_weight_initializer()
>>> net.SGD(training_data, 30, 10, 0.5,
... evaluation_data=test_data, lmbda = 5.0,
... monitor_evaluation_accuracy=True, monitor_training_accuracy=True)
\end{lstlisting}
我们得到:
\begin{center}
\includegraphics[width=.6\textwidth]{code_samples/fig/regularized_full}
\end{center}
这个结果很不错。第一,我们在测试集上的分类准确率在使用\gls*{regularization}后有了提升,从