DL for Scratch 读书笔记

本书是日本学者-斋藤康毅所著的《深度学习入门》——O’Reilly出版图书的学习笔记还有一些基础概念的扩展

深度学习有时也称为端到端机器学习(end-to-end machine learning)——从原始数据(输入)中获得目标结果(输出)。

感知机(perceptron)

*感知机1*接收多个输入信号,输出一个信号。下图中 x 1 x_1 x1 x 2 x_2 x2为输入信号, w 1 w_1 w1 w 2 w_2 w2为权重, b b b为偏置。

数学式表达为:
y = { 0 , ( b + w 1 x 1 + w 2 x 2 ) ≤ θ 1 , ( b + w 1 x 1 + w 2 x 2 ) ≥ θ y= \begin{cases} 0, &(b+w_1 x_1+w_2 x_2)\leq\theta \\ 1, &(b+w_1 x_1+w_2 x_2)\geq\theta \\ \end{cases} y={0,1,(b+w1x1+w2x2)θ(b+w1x1+w2x2)θ
神经元计算送过来的信号总和,只有总和超过某个界限值时,才会输出——neuron被激活

感知机的多个输入信号有各自的权重,这些权重发挥这控制各个信号的重要性作用。
DL for Scratch 读书笔记_第1张图片

感知机无法表达异或门(XOR)——且非(NAND)、或门(OR)、与门(AND)组合叠加层以实现。

numpy的广播机制

形状不同的数组之间进行运算,标量10被扩展成 2 × 2 2\times{2} 2×2的形状
boardcast_1
同样的,低维数组可被扩展成高维
boardcast_2
numpy.dot(A,B)用于矩阵运算.对应维度的元素个数要保持一致
DL for Scratch 读书笔记_第2张图片

神经网络

神经网络一般由input layer,hidden layer(s)和output layer组成。
DL for Scratch 读书笔记_第3张图片

非线性激活函数汇总

将输入信号的”总和“转换成输出信号。神经网络的激活函数须使用非线性函数,否则加深网络就变得没有意义。

a = b + w 1 x 1 + w 2 x 2 a=b+w_1 x_1+w_2 x_2 a=b+w1x1+w2x2 y = h ( x ) y=h(x) y=h(x)
DL for Scratch 读书笔记_第4张图片
参考博文:深度学习-激活函数详解

因为深度学习模型中其它的层都是线性的函数拟合,即便是用很深的网络去拟合,其还是避免不了线性的特性,无法进行非线性建模,而加入非线性激活函数单元,当线性函数的输出层经过非线性激活单元的时候,其输出呈现一种非线性的变化,这样经过多层的拟合,就可以完成对输入的非线性建模操作。同时还可以起到一种特征组合的作用。

好的激活函数的特征一般应当是平滑的,而且对于负值有较好的处理。

饱和激活函数

饱和激活函数,即两者在输入值较大或较小的时候对输入值不再那么敏感。

Sigmoid

​ sigmoid平滑特性:“水车”,根据流过来的水量调整送出去的水量。不仅函数的输出(竖轴的值)是连续变化的,曲线的斜率(导数)也是连续变化的。也就是说,sigmoid函数的导数在任何地方都不为0。这对神经网络的学习非常重要。

sigmoid函数作为激活函数在Forward/Backward Propagation过程中存在的问题

​ 根据sigmoid函数的特点,会将输入值映射到0到1之间,即使输入值有很大的变化,经过sigmoid函数后变化也不会有多剧烈,所以随着hidden layer的叠加,经过sigmoid激活后的输出更新速率逐步衰减。

​ 在反向传播的过程中容易造成梯度的衰减:随着hidden layer的加深,训练误差反而变大——存在梯度弥散(vanishing gradient)问题。观察Simod导数曲线,呈现一个类似高斯分布的驼峰装,其取值范围在 [ 0 , 0.25 ] [0,0.25] [0,0.25]之间,而我们初始化后的网络权值通常小于1,因此当层数增多时,小于0的值不断相乘,最后就会导致梯度消失的情况出现。

​ 与之相对的是梯度爆炸(exploding gradient),随着hidden layer的加深,梯度呈指数级增长,最后到输入时,梯度将会非常大,我们会得到一个非常大的权重更新,导致其导数大于1,大于1的数不断相乘,发生梯度爆炸。,常见于循环神经网络(RNN)。——相应解决办法:设置阈值(threshold)并进行梯度剪裁(gradient clipping)
DL for Scratch 读书笔记_第5张图片

sigmoid激活函数的三个问题:

  • 饱和的神经元会"杀死"梯度,指离中心点较远的x处的导数接近于0,停止反向传播的学习过程;
  • sigmoid的输出不是以0为中心,而是0.5,这样在求权重w的梯度时,梯度总是正或负的;
  • 指数计算耗时。

:阶跃函数(以0为界,输出从0切换为1),值呈阶梯式变化——只传递0 or 1

如果使用阶跃函数作为激活函数,神经网络的学习将无法进行。因为阶跃函数对微小的参数变化基本上没有什么反应,即便有反应,它的值也是不连续地、突然地变化。
DL for Scratch 读书笔记_第6张图片
S ( x ) = 1 1 + e − x = e x e x + 1 x → ∞ 时,函数曲线趋于平滑 S ′ ( x ) = e x ( 1 + e x ) 2 x → ∞ 时, S m i n ′ ( x ) → 0 ; x → 0 时, S m a x ′ ( x ) → 1 4 S(x)=\frac{1}{1+e^{-x}}=\frac{e^x}{e^x+1}\quad x\rightarrow\infty时,函数曲线趋于平滑\\ S^{'}(x)=\frac{e^x}{(1+e^x)^2}\quad x\rightarrow\infty时,S_{min}^{'}(x)\rightarrow 0;x\rightarrow0时,S_{max}^{'}(x)\rightarrow \frac{1}{4} S(x)=1+ex1=ex+1exx时,函数曲线趋于平滑S(x)=(1+ex)2exx时,Smin(x)0;x0时,Smax(x)41

DL for Scratch 读书笔记_第7张图片 DL for Scratch 读书笔记_第8张图片

观察Sigmoid函数曲线,可以知道其输出分布在 [ 0 , 1 ] [0,1] [0,1]区间,而且在输入值较大和较小的情况,其输出值变化都会比较小,仅仅当输入值接近为0时,它们才对输入强烈敏感。Sigmod单元的广泛饱和性会使得基于梯度的学习变得非常困难。不提倡它们作为前馈网络的隐藏单元

Tanh

tanh函数为双曲正切函数,它的图像和sigmoid十分相似,区别在于它关于原点对称,其函数表达式如下:

t a n h ( x ) = e x − e − x e x + e − x t a n h ′ ( x ) = e x + e − x e x − e − x 设 f ( x ) = t a n h ( x ) f ′ ( x ) = 1 − [ f ( x ) ] 2 tanh(x)=\frac{e^x - e^{-x}}{e^x + e^{-x}} \\ tanh^{'}(x)=\frac{e^x+e^{-x}}{e^x-e^{-x}}\\ 设f(x)=tanh(x) \quad f^{'}(x)=1-[f(x)]^{2} tanh(x)=ex+exexextanh(x)=exexex+exf(x)=tanh(x)f(x)=1[f(x)]2

DL for Scratch 读书笔记_第9张图片

关于tanh为什么比simoid收敛快?

DL for Scratch 读书笔记_第10张图片

梯度消失问题程度
t a n h ′⁡ ( x ) = 1 − t a n h ⁡ ( x ) ∈ ( 0 , 1 ) tanh′⁡(x)=1−tanh⁡(x)∈(0,1) tanh′⁡(x)=1tanh(x)(0,1)
s ′ ( x ) = s ( x ) × ( 1 − s ( x ) ) ∈ ( 0 , 1 / 4 ) s′(x)=s(x)×(1−s(x))∈(0,1/4) s(x)=s(x)×(1s(x))(0,1/4)

可以看出tanh的梯度消失问题比sigmoid要轻。梯度如果过早消失,收敛速度较慢。

以零为中心的影响
如果当前参数 ( w 0 , w 1 ) (w_0,w_1) (w0,w1)的最佳优化方向是 ( + d 0 , − d 1 ) (+d_0, -d_1) (+d0,d1),则根据反向传播计算公式,我们希望 x 0 x_0 x0 x 1 x_1 x1 符号相反。但是如果上一级神经元采用 Sigmoid 函数作为激活函数,sigmoid不以0为中心,输出值恒为正,那么我们无法进行最快的参数更新,而是走 Z 字形逼近最优解。

幂运算的问题仍然存在

sigmoid和tanh在RNN(LSTM、注意力机制等)结构上有所应用,作为门控或者概率值。

hard-Sigmoid

h r a d    s i g m o i d ( x ) = c l i p ( x + 1 2 , 0 , 1 ) = m a x ( 0 , m i n ( 1 , 2 x + 5 10 ) ) hrad\;sigmoid(x)=clip(\frac{x+1}{2},0,1)=max(0,min(1,\frac{2x+5}{10})) hradsigmoid(x)=clip(2x+1,0,1)=max(0,min(1,102x+5))

x < − 2.5 x<-2.5 x<2.5输出0,当 x > 2.5 x>2.5 x>2.5时,输出1,当 − 2.5 < x & & x < 2.5 -2.52.5<x&&x<2.5时,输出为 ( 2 x + 5 ) / 10 (2x+5) / 10 (2x+5)/10,线性函数。

那么其导数,当 x < − 2.5 x<-2.5 x<2.5输出0,当 x > 2.5 x>2.5 x>2.5时,输出0,当 − 2.5 < x & & x < 2.5 -2.52.5<x&&x<2.5时,输出为 1 / 5 1/5 1/5

DL for Scratch 读书笔记_第11张图片 DL for Scratch 读书笔记_第12张图片

hard-Sigmoid函数是Sigmoid激活函数的分段线性近似。从公示和曲线上来看,其更易计算,因此会提高训练的效率,不过同时会导致一个问题:就是首次派生值为零可能会导致神经元death或者过慢的学习率。

非饱和激活函数

ReLU(修正线性单元)

​ sigmoid是早期神经网络的激活函数,最近主要使用ReLU(Rectified Linear Unit)函数。

​ 使用 ReLU,那么一定要小心设置 learning rate,而且要注意不要让网络出现很多 “dead” 神经元,如果这个问题不好解决,那么可以试试Leaky ReLUPReLU 或者Maxout.

numpy.maximunm(0,x),将所有的负值置为0,而其余的值不变。

h ( x ) = { x , x > 0 0 , x ≤ 0 h(x)= \begin{cases} x,&x>0 \\ 0,&x\leq0 \end{cases} h(x)={x,0,x>0x0
DL for Scratch 读书笔记_第13张图片

ReLU激活函数的缺点:

  • 坏死: ReLU 强制的稀疏处理会减少模型的有效容量(即特征屏蔽太多,导致模型无法学习到有效特征)。由于ReLU在 x < 0 x < 0 x<0时梯度为0,这样就导致负的梯度在这个ReLU被置零,而且这个神经元有可能再也不会被任何数据激活,称为神经元“坏死”。
  • 无负值: ReLU和sigmoid的一个相同点是结果是正值,没有负值。

SiLU

SiLU(Sigmoid Weighted Liner Unit)激活函数是Sigmoid 加权线性组合,被提议作为强化学习中神经网络函数逼近的激活函数,但在目标检测领域也多有应用。
f ( x ) = x ∗ σ ( x ) σ 为 s i g m o i d f(x)=x*\sigma(x)\qquad \sigma为sigmoid f(x)=xσ(x)σsigmoid
DL for Scratch 读书笔记_第14张图片
对于较大的 x x x值,SiLU 的激活大约等于ReLU的激活。与 ReLU(以及其他常用的激活单元,如 sigmoid 和 tanh 单元)不同,SiLU 不是单调递增的

相反,对于 x ≈ − 1.28 x ≈ -1.28 x1.28,它的全局最小值约为 − 0.28 -0.28 0.28。SiLU 的一个吸引人的特点是它具有自稳定特性
导数为零的全局最小值在权重上起到“软地板”的作用,作为隐式正则化器,抑制了大数量权重的学习。

导函数为 f ′ ( x ) = f ( x ) + σ ( x ) ( 1 − f ( x ) ) f^{'}(x)=f(x)+\sigma(x)(1-f(x)) f(x)=f(x)+σ(x)(1f(x))
DL for Scratch 读书笔记_第15张图片
它被提议作为sigmoid 函数的竞争替代方案。对于 x ≈ ± 2.4 x ≈ ±2.4 x±2.4, SiLU导函数的最大值约为 1.1 1.1 1.1,最小值约为 − 0.1 -0.1 0.1,即方程 x = − l o g ( ( x − 2 ) / ( x + 2 ) ) x =-log((x -2)/(x +2)) x=log((x2)/(x+2))的解。

ReLU6

f ( x ) = m i n ( 6 , m a x ( 0 , x ) ) f(x)=min(6,max(0,x)) f(x)=min(6,max(0,x))

Leaky-ReLU

Leaky ReLU和ReLU不同的是,ReLU是将所有的负值设为零,而Leaky ReLU是给所有负值赋予一个非零斜率,即用一个负值部分除以大于1的数。(公式中a是大于1的一个常数)
f ( x ) = { x i f    x > 0 x a i f    x < 0 f(x)=\begin{cases} x&if\;x>0\\ \frac{x}{a}&if\;x<0 \end{cases} f(x)={xaxifx>0ifx<0

P-Relu(参数化修正线性单元)

可以看作是Leaky ReLU的一个变体,不同的是,P-ReLU中的负值部分的斜率是根据数据来定的,即a的值并不是一个常数

R-Relu(随机纠正线性单元)

R-ReLU也是Leaky ReLU的一个变体,只不过在这里负值部分的斜率在训练的时候是随机的,即在一个范围内随机抽取a的值,不过这个值在测试环节会固定下来

ELU(线性指数单元)

其将激活函数的平均值接近零,从而加快学习的速度。同时,还可以通过正值的标识来避免梯度消失的问题。根据一些研究,ELU的分类精确度要高于ReLU。

  • 融合了Sigmoid和ReLU,左侧具有软饱和性,右侧无饱和性。
  • 右侧线性部分使得ELU能够缓解梯度消失,而左侧软饱能够让ELU对输入变化或噪声更鲁棒。
  • ELU的输出均值接近于零,所以收敛速度更快。

在 ImageNet上,不加 Batch Normalization 30 层以上的 ReLU
网络会无法收敛,PReLU网络在MSRA的Fan-in (caffe )初始化下会发散,而 ELU网络在Fan-in/Fan-out下都能收敛。

f ( x ) = { x , i f    x > 0 α ( e x p ( x ) − 1 ) , i f    x ≤ 0 f ′ ( x ) = { 1 , i f    x > 0 f ( x ) + α , i f    x ≤ 0 f(x)=\begin{cases} x,&if\;x>0\\ \alpha(exp(x)-1),&if\;x\leq0 \end{cases}\\ f^{'}(x)=\begin{cases} 1,&if\;x>0\\ f(x)+\alpha,&if\;x\leq0 \end{cases} f(x)={x,α(exp(x)1),ifx>0ifx0f(x)={1,f(x)+α,ifx>0ifx0

SELU

出自paper:Self-Normalizing Neural Networks,作者提到,SELU可以使得输入在经过一定层数之后变为固定的分布。

给ELU乘上一个系数 λ \lambda λ,该系数大于1。

以前的ReLU、P-ReLU、ELU等激活函数都是在负半轴坡度平缓,这样在激活的方差过大时可以让梯度减小,防止了梯度爆炸,但是在正半轴其梯度简单地设置为了1。

SELU的正半轴斜率大于1,在方差过小的时候可以让它增大,但是同时防止了梯度消失。这样激活函数就有了一个不动点,网络深了之后每一层的输出都是均值为0,方差为1。
f ( x ) = λ { x , i f    x > 0 α ( e x p ( x ) − 1 ) , i f    x ≤ 0 f ′ ( x ) = λ { 1 , i f    x > 0 f ( x ) + α , i f    x ≤ 0 f(x)=\lambda\begin{cases} x,&if\;x>0\\ \alpha(exp(x)-1),&if\;x\leq0 \end{cases}\\ f^{'}(x)=\lambda\begin{cases} 1,&if\;x>0\\ f(x)+\alpha,&if\;x\leq0 \end{cases} f(x)=λ{x,α(exp(x)1),ifx>0ifx0f(x)=λ{1,f(x)+α,ifx>0ifx0

Swish

出自paper:Searching for Activation Functions

β \beta β是个常数或者可以训练的参数。其具有无上界有下界、平滑、非单调的特性。其在模型效果上优于ReLU。

f ( x ) = x ∗ s i g m o i d ( β x ) f ′ = s i g m o i d ( β x ) + x ( β ∗ s i g m o i d ( β x ) ) f(x)=x*sigmoid(\beta{x})\\ f^{'}=sigmoid(\beta{x})+x(\beta*sigmoid(\beta{x})) f(x)=xsigmoid(βx)f=sigmoid(βx)+x(βsigmoid(βx))

DL for Scratch 读书笔记_第16张图片 DL for Scratch 读书笔记_第17张图片
  • 但是swish激活函数的计算、求导复杂,对量化过程很不友好,haed-Swish函数是对它的改进

hard-Swish

出自paper:Searching for MobileNetV3

h − s w i s h [ x ] = x R e L U 6 ( x + 3 ) 6 h-swish[x]=x\frac{ReLU6(x+3)}{6} hswish[x]=x6ReLU6(x+3)

在论文中,作者提到,虽然Swish非线性提高了精度,但是在嵌入式环境中,他的成本是非零的,因为在移动设备上计算sigmoid函数代价要大得多。

因此作者使用hard-Swishhard-Sigmoid替换了ReLU6SE-block中的Sigmoid层,但是只是在网络的后半段才将ReLU6替换为h-Swish,因为作者发现Swish函数只有在更深的网络层使用才能体现其优势

DL for Scratch 读书笔记_第18张图片 DL for Scratch 读书笔记_第19张图片

Mish

出自论文:Mish: A Self Regularized Non-Monotonic Neural Activation Function

相比Swish有0.494%的提升,相比ReLU有1.671%的提升。

y = x ∗ t a n h ( ln ⁡ ( 1 + e x p ( x ) ) ) y=x*tanh(\ln(1+exp(x))) y=xtanh(ln(1+exp(x)))
DL for Scratch 读书笔记_第20张图片

为什么Mish表现的更好:

上无边界(即正值可以达到任何高度)避免了由于封顶而导致的饱和。理论上对负值的轻微允许允许更好的梯度流,而不是像ReLU中那样的硬零边界。

最后,可能也是最重要的,平滑的激活函数允许更好的信息深入神经网络,从而得到更好的准确性和泛化。

神经网络的学习

​ 这里的“学习”是指可以由数据自动决定权重参数的值。一般将数据分为训练数据(training data)和测试数据(testing data)——数据拆分的原因:追求模型的泛化能力。

获得泛化能力是机器学习的最终目标——比如对手写数字识别(Mnist数据集),泛化能力可能用于自动读取明信片的邮政编码系统上,此时手写数字识别就必须具备较高地识别“某个人”写出的数字的能力。系统能正确识别已有的训练数据不是我们追求的目标,那只是学习到了训练数据中的个人习惯写法。

​ 因此,只用一个数据集取学习和评价参数无法正确评价,这样会导致可以顺利处理某个数据集,但无法处理其他数据集的情况。

损失函数

​ 神经网络以某个指标为线索寻找最优权重参数,该指标称为损失函数(loss function)。损失函数是表示神经网络性能的“恶劣程度”的指标,即当前的神经网络对监督数据在多大程度上不拟合。

均方误差(Mean Square Error)

E = 1 2 ∑ k ( y k ′ − y k ) 2 其中 y k ′ 表示神经网络的输出, y k 表示训练数据, k 为数据的维度 E = \frac{1}{2}\sum_k(y^{'}_k-y_k)^2 \qquad 其中y^{'}_k表示神经网络的输出,y_k表示训练数据,k为数据的维度 E=21k(ykyk)2其中yk表示神经网络的输出,yk表示训练数据,k为数据的维度

参考博文:https://blog.csdn.net/Xiaobai_rabbit0/article/details/111032136

均方误差(Mean Square Error,MSE)是回归损失函数中最常用的误差,它是预测值f(x)与目标值y之间差值平方和的均值,其公式如下所示:
M S E = ∑ i = 1 n ( f ( x ) − y ) 2 n MSE=\frac{\sum_{i=1}^n(f(x)-y)^2}{n} MSE=ni=1n(f(x)y)2
下图是均方误差值的曲线分布,其中最小值为预测值为目标值的位置。我们可以看到随着误差的增加损失函数增加的更为迅猛。

  • 优点:MSE的函数曲线光滑、连续,处处可导,便于使用梯度下降算法,是一种常用的损失函数。 而且,随着误差的减小,梯度也在减小,这有利于收敛,即使使用固定的学习速率,也能较快的收敛到最小值。
  • 缺点:当真实值y和预测值f(x)的差值大于1时,会放大误差;而当差值小于1时,则会缩小误差,这是平方运算决定的。MSE对于较大的误差(>1)给予较大的惩罚,较小的误差(<1)给予较小的惩罚。也就是说,对离群点比较敏感,受其影响较大

如果样本中存在离群点,MSE会给离群点更高的权重,这就会牺牲其他正常点数据的预测效果,最终降低整体的模型性能。 如下图(虽然样本中只有 5 个离群点,但是拟合的直线还是比较偏向于离群点):

平均绝对误差(MAE)是另一种常用的回归损失函数,它是目标值与预测值之差绝对值和的均值,表示了预测值的平均误差幅度,而不需要考虑误差的方向(平均偏差误差MBE则是考虑的方向的误差,是残差的和),范围是0到∞,其公式如下所示:
M A E = ∑ i = 1 n ∣ f ( x ) − y ∣ n MAE=\frac{\sum_{i=1}^n|f(x)-y|}{n} MAE=ni=1nf(x)y

  • 优点:相比于MSE,MAE对于离群点不那么敏感。因为MAE计算的是误差(y-f(x))的绝对值,对于任意大小的差值,其惩罚都是固定的。无论对于什么样的输入值,都有着稳定的梯度,不会导致梯度爆炸问题,具有较为稳健性的解。
  • 缺点:MAE曲线连续,但是在(y-f(x)=0)处不可导。而且 MAE 大部分情况下梯度都是相等的,这意味着即使对于小的损失值,其梯度也是大的。这不利于函数的收敛和模型的学习

如下图,使用 MAE 损失函数,受离群点的影响较小,拟合直线能够较好地表征正常数据的分布情况:

L1_Loss和L2_Loss

  • L1范数损失函数,也被称为最小绝对值偏差(LAD),最小绝对值误差(LAE)。总的说来,它是把目标值y与估计值f(xi)的绝对差值的总和S最小化: S = ∑ i = 1 n ∣ f ( x ) − y ∣ S=\sum_{i=1}^n|f(x)-y| S=i=1nf(x)y
  • L2范数损失函数,也被称为最小平方误差(LSE)。总的来说,它是把目标值y与估计值f(xi)的差值的平方和S最小化: S = ∑ i = 1 n ( f ( x ) − y ) 2 S=\sum_{i=1}^n(f(x)-y)^2 S=i=1n(f(x)y)2

Smooth L1损失函数(也被称为 Huber 损失函数)

在Faster R-CNN以及SSD中对边框的回归使用的损失函数都是Smooth L_1作为损失函数。L1损失的缺点就是有折点,不光滑,那如何让其变得光滑呢?
S m o o t h    L 1 : l o s s ( x , y ) = 1 n ∑ i z i 其中, z i = { 0.5 ( x i − y i ) 2 , i f    ∣ x i − y i ∣ < 1 ∣ x i − y i ∣ − 0.5 , o t h e r w i s e Smooth\;L1:loss(x,y)=\frac{1}{n}\sum_{i}z_i\\ 其中,z_i=\begin{cases} 0.5(x_i-y_i)^2,if\;|x_i-y_i|<1\\ |x_i-y_i|-0.5,otherwise \end{cases} SmoothL1:loss(x,y)=n1izi其中,zi={0.5(xiyi)2,ifxiyi<1xiyi0.5,otherwise
Smooth L1能从两个方面限制梯度:

  1. 当预测框与 ground truth 差别过大时,梯度值不至于过大;
  2. 当预测框与 ground truth 差别很小时,梯度值足够小。
DL for Scratch 读书笔记_第21张图片

该函数实际上就是一个分段函数,在[-1,1]之间实际上就是L2损失,这样解决了L1的不光滑问题,在[-1,1]区间外,实际上就是L1损失,这样就解决了离群点梯度爆炸的问题。

softmax函数

​ 分类问题中使用。softmax函数的输出通过箭头与所有的输入信号相连,将输出值映射到0—1之间,等价于概率且 ∑ y i = 1 \sum{y_i}=1 yi=1

y i = e y i ∑ j = 1 n e y j y_i = \quad{e^{y_i} \over \sum_{j=1}^{n}e^{y_j}} yi=j=1neyjeyi

​ 计算机实现softmax函数需要注意“溢出问题”,因为softmax函数需要进行指数函数运算,指数值容易变得很大。如 e 1000 e^{1000} e1000结果会返回一个表示无穷大的inf

改进的softmax函数
y i = C e y i C ∑ j = 1 n e y j = e y i + l o g C ∑ j = 1 n e y j + l o g C = e y i + C ′ ∑ j = 1 n e y j + C ′ \begin{aligned} y_i &= \quad{C e^{y_i} \over C\sum_{j=1}^{n}e^{y_j}} \\ &= \quad{e^{y_i + logC} \over \sum_{j=1}^{n} e^{y_j + logC}} \\ &= \quad{e^{y_i + C^{'}} \over \sum_{j=1}^{n} e^{y_j + C^{'}}} \end{aligned} yi=Cj=1neyjCeyi=j=1neyj+logCeyi+logC=j=1neyj+Ceyi+C

交叉熵(Cross Entropy)

E = − ∑ k y k ln ⁡ y k ′ y k ′ 是神经网络的输出(经过 s o f t m a x 的输出), y k 是正确解标签 E = - \sum_k{y_k \ln{y^{'}_k}} \qquad y^{'}_k是神经网络的输出(经过softmax的输出),{y_k}是正确解标签 E=kyklnykyk是神经网络的输出(经过softmax的输出),yk是正确解标签

代码实现

def cross_entropy(y,t):
    delta = 1e - 7  # 加上一个微小值delta,这是因为当出现np.log(0)时--无穷小-inf,导致无法正常计算
    return -numpy.sum(t * numpy.log(y + delta))

y k y_k yk为正确解标签,用one-hot vector表示时,只有正确解标签为1,其他均为0;此时上式只计算对应正确解标签的输出的自然对数。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5dRTklKM-1662084884182)(DL for Scratch images\cross_entropy_01.png)]

​ 上图为 ln ⁡ x \ln{x} lnx 0 → 1 0\rightarrow1 01区间上的图像。正确解标签对应的输出(经过softmax函数)越大,交叉熵越接近0;反之交叉熵越大。

批处理(mini-batch)
E = − 1 N ∑ n ∑ k y n k ln ⁡ y n k ′ N 个数据: b a t c h s i z e 1 N 是为了平均化,得到单个数据的平均损失函数值 E = - \frac{1}{N}\sum_n\sum_k{y_{nk} \ln{y^{'}_{nk}}} \qquad N个数据:batchsize \\ \frac{1}{N}是为了平均化,得到单个数据的平均损失函数值 E=N1nkynklnynkN个数据:batchsizeN1是为了平均化,得到单个数据的平均损失函数值

​ 如对Mnist数据集,training data有60000张图片,若以全部数据为对象求loss,则计算过程十分费时。因此从全部训练数据中“随机选择“(100)笔——mini-batch进行学习。

mini-batch的损失函数是利用一部分样本数据近似地计算整体

epoch

epoch是一个单位。一个 epoch表示学习中所有训练数据均被使用过一次时的更新次数。

​ 比如,对于 10000笔训练数据,用大小为 100笔数据的mini-batch进行学习时,重复随机梯度下降法 100次,所有的训练数据就都被“看过”了。此时,100次就是一个 epoch。

​ 一般做法是事先将所有训练数据随机打乱,然后按指定的批次大小、按序生成mini-batch。这样每个mini-batch都有一个索引号,然后使用索引号可以遍历所有的mini-batch。

正确解标签非one-hot表示,而是像“2”、“7”这样的标签时,批处理交叉熵代码实现

def batch_cross_entropy(y,t):
    if y.ndim == 1:  # 若神经网络输出y为维度为1,一维数组要把第一维放到第二维,表示只有一条数据
        t = t.reshape(1, t.size)
        y = y.reshape(1, y.size)
    batch_size = y.shape[0]  # 记下第一维的数值,表示有多少条数据
    # 若t中标签是以[2, 7, 0, 9, 4]的形式存储
    # y[np.arange(batch_size), t]会生成 NumPy数组[y[0,2], y[1,7], y[2,0], y[3,9], y[4,4]]
    return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7))/ batch_size 

import numpy as np
y=np.array([[0,1,0.1,0,0,0,0,0,0,0],[0,0,0.2,0.8,0,0,0,0,0,0]])
t_onehot=np.array([[0,1,0,0,0,0,0,0,0,0],[0,0,0,1,0,0,0,0,0,0]])#one-hot
t = t_onehot.argmax(axis=1)#非one-hot
print(t)#[1 3]
batch_size = y.shape[0]
print(batch_size)#2
k=y[np.arange(batch_size), t] # [y[0,1] y[1,3]]
print(k)#[1.  0.8]  索引二维数组y得到的元素
# 相当于求 -(log(1+1e-7)+log(0.8+1e-7))/2
r=-np.sum(np.log(y[np.arange(batch_size), t] + 1e-7))/ batch_size
print(r)#0.11157166315711126

为什么设定损失函数?

​ 在神经网络的学习中,寻找最优参数(weight和bias)时,要寻找尽可能使loss function值最小的参数,为了达到这个目的,需要计算参数的导数值(梯度 gradient2),然后以这个导数为指标逐步更新参数。

​ 对该权重参数的损失函数求导,表示的是**“如果稍微改变这个权重参数的值,损失函数的值会如何变化”**。

  • 如果导数的值为负,通过使该权重参数向正方向改变,可以减小损失函数的值;
  • 如果导数的值为正,则通过使该权重参数向负方向改变,可以减小损失函数的值;
  • 当导数的值为0时,无论权重参数向哪个方向变化,损失函数的值都不会改变,此时该权重参数的更新会停在此处。

之所以不能用accuracy作为指标,是由于这样一来大多数地方的导数都会变成 0 0 0,导致参数无法更新。 识别精度对微小的参数变化基本上没有什么反应,即便有反应,它的值也是不连续地、突然地变化。

随机梯度下降法(SGD)

​ 梯度下降算法每次更新参数都需要使用所有的样本。若对所有样本都计算一次,当样本总量特别大时,对算法的性能影响很大。SGD是对梯度下降算法的改进,每次都只随机取一部分样本进行优化(取值范围为32~256)以保证计算精度的同时提升速度。

W ← W − η ∂ L ∂ W \pmb{W} \leftarrow \pmb{W} - \eta \frac{\partial L}{\partial \pmb W} WWWWηWWL

参数更新方向为负梯度方向

方向导数 = cos ⁡ ( α ) × g r a d i e n t 方向导数=\cos(\alpha)\times gradient 方向导数=cos(α)×gradient,其中 α \alpha α是方向导数的方向与梯度方向的夹角。

由柯西 − 施瓦兹不等式: − ∣ a ∣ ∣ b ∣ ≤ a b ≤ ∣ a ∣ ∣ b ∣ 根据余弦性质对任意的 α 有 − 1 ≤ cos ⁡ α ≤ 1 , 则 − ∣ a ∣ ∣ b ∣ ≤ ∣ a ∣ ∣ b ∣ cos ⁡ α ≤ ∣ a ∣ ∣ b ∣ 由柯西-施瓦兹不等式:-|\pmb{a}||\pmb b| \leq \pmb a \pmb b \leq|\pmb a||\pmb b| \\ 根据余弦性质对任意的\alpha 有-1 \leq \cos\alpha \leq 1,则-|\pmb{a}||\pmb b| \leq |\pmb{a}||\pmb b| \cos\alpha \leq|\pmb a||\pmb b| \\ 由柯西施瓦兹不等式:aa∣∣bbaabbaa∣∣bb根据余弦性质对任意的α1cosα1,aa∣∣bbaa∣∣bbcosαaa∣∣bb
DL for Scratch 读书笔记_第22张图片

  1. 当两个向量方向相反时,内积取得最小值;(gradient descent的基本原理)
  2. 当两个向量不平行时,内积取平行时的中间值;
  3. 当两个向量方向相同时,内积取得最大值。

一些特殊点

局部最小值点(local minimum point)、鞍点(saddle point)。梯度下降法要寻找梯度为0的点,但那个地方不一定就是最小值点,也可能是局部最小值点或者鞍点。在高维度上,鞍点比局部最小值更有可能出现,鞍点处的梯度可能非常小,梯度下降在该点不会去更新网络,训练将停止。

​ ——解决鞍点问题的一个办法:Hessian矩阵

​ 此外,当函数很复杂且呈扁平状,学习可能进入一个(几乎)平坦的地区,陷入“学习高原”的无法前进的停滞期。复杂函数可联想浴缸(最小值点为浴缸漏孔),浴缸底部是相对平坦的。

梯度法

​ 梯度表示的是各点处的函数值减小最多的方向。因此,无法保证梯度所指的方向就是函数的最小值或者真正应该前进的方向。实际上,在复杂的函数中,梯度指示的方向基本上都不是函数值最小处。虽然梯度的方向并不一定指向最小值,但沿着它的方向能够最大限度地减小函数的值。

​ 在梯度法中,函数的取值从当前位置沿着梯度方向前进一定距离,然后在新的地方重新求梯度,再沿着新梯度方向前进,如此反复,不断地沿梯度方向前进

w 1 = w 0 − η ∂ L ∂ w ∣ w = w 0 L 为损失函数, η 为学习率—决定在一次学习中在多大程度上更新参数 w^1 = w^0 - \eta\frac{\partial L}{\partial w}|_{w=w_0} \quad L为损失函数,\eta为学习率—决定在一次学习中在多大程度上更新参数 w1=w0ηwLw=w0L为损失函数,η为学习率决定在一次学习中在多大程度上更新参数

​ learning rate也叫神经网络的超参数(hyper parameter),由人工设定。

  • 设置得过大,梯度下降过程中更新参数会发散成一个很大的值,造成梯度无法收敛;
  • 设置得过小,更新的速度很慢,需要设置很大的迭代次数或者 epoch_num才能达到训练效果,否则参数还没怎么更新就结束了。

(有一个形状为 2 × 3 2\times3 2×3的权重W的神经网络,损失函数用 L L L表示)
DL for Scratch 读书笔记_第23张图片

过拟合(over fitting)

​ 过拟合是指为了得到一致假设而使假设变得过度严格,只对某个数据集过度拟合。

​ 随着训练过程的进行,模型复杂度变高,在training data上的error渐渐减小。可是在测试集上的error却反而渐渐增大——由于训练出来的网络参数过拟合了训练集,对训练集以外的数据却不起作用。
DL for Scratch 读书笔记_第24张图片

出现过拟合的原因

(1)建模样本选取有误,如样本数量太少,选样方法错误,样本标签错误等,导致选取的样本数据不足以代表预定的分类规则;

(2)样本噪音干扰过大,使得机器将部分噪音认为是特征从而扰乱了预设的分类规则;

(3)假设的模型无法合理存在,或者说是假设成立的条件实际并不成立;

(4)参数太多,模型复杂度过高;

(5)对于决策树模型,如果我们对于其生长没有合理的限制,其自由生长有可能使节点只包含单纯的事件数据(event)或非事件数据(no event),使其虽然可以完美匹配(拟合)训练数据,但是无法适应其他数据集。

(6)对于神经网络模型:

  • 对样本数据可能存在分类决策面不唯一,随着学习的进行,BP算法使权值可能收敛过于复杂的决策面;
  • 权值学习迭代次数足够多(Over training),拟合了训练数据中的噪声和训练样例中没有代表性的特征。

解决方法

(1)在神经网络模型中,可使用权值衰减的方法,即每次迭代过程中以某个小因子降低每个权值。

(2)(Early stopping)选取合适的停止训练标准,使对机器的训练在合适的程度;

具体做法:

​ 在每一个Epoch结束时(一个Epoch集为对所有的训练数据的一轮遍历)计算validation data的accuracy,当accuracy不再提高时,就停止训练。

​ 一般的做法是,在训练的过程中,记录到目前为止最好的validation accuracy,当连续10次Epoch(或者更多次)没达到最佳accuracy时,则可以认为accuracy不再提高了,此时便可以停止迭代了。

(3)保留验证数据集(validation data)——拆分原始数据为training data、validation data和testing data,对训练成果进行验证;

​ 通常使用它确定一些超参数(如依据validation data上的Accuracy确定early stopping的epoch大小、依据validation data确定learning rate等)

为什么不在testing data上做这些操作?

  • 若在testing data上做这些,那么随着训练的进行,神经网络实际上是在一点点地overfit着testing data,导致最后得到的testing accuracy无实际参考意义。

(4)获取额外数据进行交叉验证(如使用数据增强(Data augmentation),即增加训练数据样本);

​ 能够在原始数据上做些改动,就能得到很多其它的数据。以图片数据集举例,能够做各种变换:

  • 将原始图片旋转一个小角度

  • 加入随机噪声

  • 一些有弹性的畸变(elastic distortions),论文《Best practices for convolutional neural networks applied to visual document analysis》对MNIST做了各种变种扩增。

  • 截取(crop)原始图片的一部分,比方DeepID中,从一副人脸图中,截取出了100个小patch作为训练数据,极大地添加了数据集。

(5)Dropout——在网络很复杂时,只使用权值衰减很难应付,此时改动神经网络,随机“丢”掉一部分隐藏单元
DL for Scratch 读书笔记_第25张图片

​ 运用了dropout的训练过程,相当于训练了非常多个仅仅有部分隐层单元的神经网络,每个这种半数网络,都能够给出一个分类结果,这些结果有的是正确的,有的是错误的。随着训练的进行,大部分半数网络都能够给出正确的分类结果。那么少数的错误分类结果就不会对终于结果造成大的影响。
DL for Scratch 读书笔记_第26张图片
(6)正则化,即在进行目标函数或代价函数优化时,在目标函数或代价函数后面加上一个正则项,一般有L1正则与L2正则等。

  • L1 Regularization:在原始的代价函数后面加上一个L1正则化项,即全部权重w的绝对值的和,再乘以 λ / n \lambda / n λ/n

    • w w w为正时, s g n ( w ) > 0 sgn(w)>0 sgn(w)>0,则更新后的 w w w变小;
    • w w w为负时, s g n ( w ) < 0 sgn(w)<0 sgn(w)<0,则更新后的 w w w变大;
    • w = 0 w=0 w=0时, ∣ w ∣ |w| w不可导,这时变成原始的更新规则——不带正则化项(编程时规定 s g n ( w ) = 0 sgn(w)=0 sgn(w)=0

    因为它的效果就是让 w w w往0靠,使网络中的权重尽可能为0,也就相当于减小了网络复杂度,防止overfitting。

C = C 0 + λ n ∑ ∣ w ∣ 先计算导数: ∂ C ∂ w = ∂ C 0 ∂ w + λ n s g n ( w ) 其中 s g n ( w ) 表示 w 的符号 权重 w 更新的规则为: w ⟶ w ′ = η λ n s g n ( w ) − η ∂ C 0 ∂ w 比原始的更新规则多出了 η λ n s g n ( w ) C = C_0 + {\lambda \over n} \sum {|w|} \\ 先计算导数:{\partial C \over \partial w} = {\partial C_0 \over \partial w} + {\lambda \over n }sgn(w) \qquad 其中sgn(w)表示w的符号 \\ 权重w更新的规则为:w \longrightarrow w^{'}= {\eta \lambda \over n}sgn(w) - \eta {\partial{C_0} \over \partial{w}} \qquad 比原始的更新规则多出了{\eta \lambda \over n}sgn(w) C=C0+nλw先计算导数:wC=wC0+nλsgn(w)其中sgn(w)表示w的符号权重w更新的规则为:ww=nηλsgn(w)ηwC0比原始的更新规则多出了nηλsgn(w)

  • L2 Regularization:权重衰减,正则化项即全部參数 w 的平方和,除以训练集的样本大小n。

在代价函数后面再加上一个正则化项: C = C 0 + λ 2 n ∑ w 2 λ 为正则项系数,权衡正则项与 C 0 项的比重 L 2 正则项如何避免 o v e r f i t t i n g ? ∂ C ∂ w = ∂ C 0 ∂ w + λ n w ∂ C ∂ b = ∂ C 0 ∂ b L 2 正则化项对 b 无影响,对 w 的更新有影响 w ⟶ w − η ∂ C 0 ∂ w − η λ n w = ( 1 − η λ n ) w − η ∂ C 0 ∂ w 在代价函数后面再加上一个正则化项:C = C_0 + {\lambda \over 2n} \sum w^2 \quad \lambda为正则项系数,权衡正则项与C_0项的比重 \\ L2正则项如何避免overfitting? \begin{aligned} {\partial C \over \partial w} &= {\partial C_0 \over \partial w} + {\lambda \over n}w \\ {\partial C \over \partial b} &= {\partial C_0 \over \partial b} \end{aligned} \qquad L2正则化项对b无影响,对w的更新有影响 \\ w \longrightarrow w - \eta {\partial C_0 \over \partial w} - {\eta \lambda \over n}w = (1 - {\eta \lambda \over n})w - \eta{\partial C_0 \over \partial w} 在代价函数后面再加上一个正则化项:C=C0+2nλw2λ为正则项系数,权衡正则项与C0项的比重L2正则项如何避免overfittingwCbC=wC0+nλw=bC0L2正则化项对b无影响,对w的更新有影响wwηwC0nηλw=(1nηλ)wηwC0

​ 在不使用L2正则化时,求导结果中 w w w的前系数为1,经变化后 w w w前系数为 1 − η λ n 1-\frac{\eta\lambda}{n} 1nηλ。由于 η \eta η λ \lambda λ、n都是正的,所以 1 − η λ n 1-\frac{\eta\lambda}{n} 1nηλ小于1,它的效果是减小 w w w,这就是所谓的权重衰减。

:基于mini-batch的随机梯度下降, w w w b b b更新的公式稍有不同:

DL for Scratch 读书笔记_第27张图片

为什么 w w w变小能够防止overfitting?

更小的权值 w w w,从某种意义上来说,表示网络的复杂度更低,对数据的拟合刚刚好(这个法则也叫做奥卡姆剃刀)。实际应用中,L2正则化的效果往往好于未经正则化的效果。

(怎么解决过拟合与欠拟合-引自CSDN)

过拟合的时候,拟合函数的系数往往非常大,为什么?例如以下图所看到的,过拟合。就是拟合函数须要顾忌每个点。终于形成的拟合函数波动非常大。在某些非常小的区间里,函数值的变化非常剧烈。

这就意味着函数在某些小区间里的导数值(绝对值)非常大,由于自变量值可大可小,所以仅仅有系数足够大,才干保证导数值非常大。而L2正则化是通过约束參数的范数使其不要太大,所以能够在一定程度上降低过拟合情况。

DL for Scratch 读书笔记_第28张图片

在什么情况下使用L1,什么情况下使用L2?

将权值参数以L1或者L2的方式放到代价函数里面去,然后模型就会尝试去最小化这些权值参数。而这个最小化就像一个下坡的过程,L1和L2的差别就在于这个“坡”不同

如下图:L1就是按绝对值函数的“坡”下降的,而L2是按二次函数的“坡”下降。所以实际上在0附近,L1的下降速度比L2的下降速度要快,所以会非常快得降到0。

DL for Scratch 读书笔记_第29张图片

总结就是:L1会趋向于产生少量的特征,而其他的特征都是0,而L2会选择更多的特征,这些特征都会接近于0

误差反向传播

​ 使用数值微分计算神经网络的权重参数的梯度虽易实现,但缺点是计算费时。代码参见ch04/two_layer_net.py

在确认误差反向传播(Backward Propagation)的实现是否正确时需要用到数值微分验证,因为误差反向传播实现复杂容易出错,所以需要这一步梯度确认

计算图

计算图的优点:

  • 无论全局是多么复杂的计算,都可以通过局部计算使各个节点致力于简单的计算,从而简化问题;
  • 利用计算图可以将中间的计算结果全部保存起来;
  • 可以通过反向传播高效计算导数。

前向传播(Forward propagation)

Q:太郎在超市买了2个苹果、3个橘子。其中,苹果每个100日元,橘子每个150日元。消费税是10%,请计算支付金额。
DL for Scratch 读书笔记_第30张图片

正向传播是从计算图的出发点到结束点的传播,即“从左到右计算”。

局部计算

​ 计算图的特征是可以通过传递“局部计算”获得最终结果,这里的“局部”是“与自己相关的某个小范围”。局部计算是指,无论全局发生了什么,都能只根据与自己相关的信息输出接下来的结果。
DL for Scratch 读书笔记_第31张图片

各个节点处的计算都是局部计算

​ 这意味着,苹果和其他很多东西的求和运算(4000 + 200 4200)并不关心4000这个数字是如何计算而来的,只要把两个数字相加就可以了。

​ 换言之,各个节点处只需进行与自己有关的计算(例子中是对输入的两个数字进行加法运算),不用考虑全局。

综上,计算图可以集中精力于局部计算,无论全局计算多么复杂,各个步骤要做的就是对象节点的局部计算。虽然局部计算十分简单,但通过传递它的计算结果,可以获得全局的复杂计算的结果。


反向传播前瞻

​ 假设我们想知道苹果价格的上涨会在多大程度上影响最终的支付金额,即求“支付金额关于苹果的价格的导数”。设苹果的价格为 x x x,支付金额为 L L L,则相当于求 ∂ L ∂ x \frac{\partial L}{\partial x} xL

​ 这个导数的值表示当苹果的价格稍微上涨时,支付金额会增加多少。
DL for Scratch 读书笔记_第32张图片

反向传播传递“局部导数”,将导数的值写在箭头的下方。计算中途求得的导数的结果(中间传递的导数)可以被共享,从而可以高效地计算多个导数。

​ 这意味着,如果苹果的价格上涨1日元,最终的支付金额会增加2*.2日元(严格地讲,如果苹果的价格增加某个微小值,则最终的支付金额将增加那个微小值的2.*2倍)。

链式法则(Chain Rule)

反向传播局部导数的原理基于求导的链式法则
假设对于函数 y = f ( x ) = x 2 局部导数 y ′ = f ′ ( x ) = 2 x 与上游传来的值 E 相乘传递给前面的节点 假设对于函数 \qquad y=f(x)=x^2 \\ 局部导数 \qquad y^{'}=f^{'}(x)=2x \\ 与上游传来的值 E相乘 传递给前面的节点 假设对于函数y=f(x)=x2局部导数y=f(x)=2x与上游传来的值E相乘传递给前面的节点
backward_pass_2

例:复合函数 z = ( x + y ) 2 z=(x+y)^2 z=(x+y)2 z = t 2 z=t^2 z=t2 t = x + y t=x+y t=x+y两个式子构成

∂ z ∂ x = ∂ z ∂ t ∂ t ∂ x 其中 ∂ z ∂ t = 2 t , ∂ t ∂ x = 1 ∂ z ∂ x = 2 t × 1 = 2 ( x + y ) \frac {\partial z}{\partial x} = \frac {\partial z}{\partial t} \frac {\partial t}{\partial x} \qquad 其中\frac {\partial z}{\partial t} = 2t,\frac {\partial t}{\partial x}=1 \\ \frac {\partial z}{\partial x} = 2t \times 1 = 2(x+y) xz=tzxt其中tz=2t,xt=1xz=2t×1=2(x+y)

沿着正向传播的反方向,乘上局部导数后传递

实例代码DL_learn/demo_07.py

DL for Scratch 读书笔记_第33张图片

反向传播

加法节点的反向传播

z = x + y z=x+y z=x+y为例: ∂ z ∂ x = 1 , ∂ z ∂ y = 1 \frac{\partial z}{\partial x}=1,\frac{\partial z}{\partial y}=1 xz=1yz=1

加法节点的反向传播会将上游的值原封不动地输出到下游

DL for Scratch 读书笔记_第34张图片

乘法节点的反向传播

z = x y z=xy z=xy为例: ∂ z ∂ x = y \frac{\partial z}{\partial x}=y xz=y ∂ z ∂ y = x \frac{\partial z}{\partial y}=x yz=x

乘法的反向传播会将上游的值乘以正向传播时的输入信号的“翻转值”后传递给下游。

DL for Scratch 读书笔记_第35张图片

神经网络的正向传播中进行的矩阵的乘积运算在几何学领域被称为“仿射变换”。因此,这里将进行仿射变换的处理实现为“Affine层”。

​ Affine层的正向/反向传播

​ 其中矩阵的乘积(“dot”点积)的反向传播可以通过组建使矩阵对应维度的元素个数一致的乘积运算而推导出来。

DL for Scratch 读书笔记_第36张图片

批版本的Affine层

DL for Scratch 读书笔记_第37张图片

Softmax-with-Loss层计算图

神经网络中进行的处理有推理(inference)和学习两个阶段,推理通常不使用softmax层。

如对手写数字识别,可以通过**“Affine层+ReLU函数激活”**这样多层叠加,将最后一个Affine层的输出做为识别结果送入softmax层进行概率转换。

DL for Scratch 读书笔记_第38张图片

神经网络的优化(Optimizer)

参数的更新

神经网络的学习目的是找到是损失函数(loss function)的值(loss)尽可能小的参数,解决这个问题的过程称为最优化(Optimization)。因为参数空间非常复杂,无法轻易找到最优解,优化问题十分复杂。

​ 当谈论神经网络的优化时,其实是在讨论非凸优化(non-convex optimization)问题,涉及多个最优值的函数,其中只有一个是全局最优值,根据损失曲面(loss surface),很难找到全局最优值;与之相对的是凸优化(convex optimization),函数只有一个最优值(全局最小/大值)。

​ 针对神经网络的训练,有许多相关的问题需要考虑:

  1. 合理的学习率是多少?太小的学习率将花费太久的时间才能收敛,而太大的学习率将意味着网络很容易发散。
  2. 如何避免陷入局部最优?一个局部最优可能被特别*“陡峭的”损失函数所包围——山谷与峭壁,因此可能难以“逃脱”*局部最优。
  3. 如果损失曲面形态发生变化该怎么办?即使我们可以找到全局最小值,也无法保证它将永远保持全局最小值。栗子:在不代表实际数据分布的数据集上进行训练时,将其应用于新数据时,损失曲面将有所不同。这就是为什么使训练和测试数据集要能够代表总数据分布的重要原因之一。

随机梯度下降(SGD)存在的问题

引子:探险家的故事

​ 有一个性情古怪的探险家。他在广袤的干旱地带旅行,坚持寻找幽深的山谷。他的目标是要到达最深的谷底(他称之为“至深之地”)。并且,他给自己制定了两个严格的“规定”:一个是不看地图;另一个是把眼睛蒙上。因此,他并不知道最深的谷底在这个广袤的大地的何处,而且什么也看不见。在这么严苛的条件下,这位探险家如何前往“至深之地”呢?他要如何迈步,才能迅速找到“至深之地”呢?

​ 在这么困难的状况下,地面的坡度显得尤为重要。探险家虽然看不到周围的情况,但是能够知道当前所在位置的坡度(通过脚底感受地面的倾斜状况)。朝着当前所在位置的坡度最大的方向前进,就是SGD的策略。

Q:求函数 f ( x , y ) = 1 20 x 2 + y 2 f(x,y)=\frac{1}{20}x^2 + y^2 f(x,y)=201x2+y2的最小值

DL for Scratch 读书笔记_第39张图片
该函数式向 x x x轴方向延伸的”碗“状函数,其等高线呈祥 x x x轴方向延伸的椭圆状。

DL for Scratch 读书笔记_第40张图片
其梯度特征是: y y y轴方向上大, x x x轴方向上小。虽然函数最小值在 ( 0 , 0 ) (0,0) (0,0)处取得,但是图中的梯度在很多地方没有指向 ( 0 , 0 ) (0,0) (0,0)

DL for Scratch 读书笔记_第41张图片
对该函数应用SGD算法更新参数,从 ( x , y ) = ( − 7.0 , 2.0 ) (x,y)=(-7.0,2.0) (x,y)=(7.0,2.0)处开始搜索,可以看出移动路径十分低效。

动量(Momentum)

又称线性动量(Linear Momentum)。在经典力学中,动量(国际单位制中的单位为 k g ⋅ m / s kg · m/s kgm/s)表示为物体的质量和速度的乘积( p = m v p=mv p=mv),是与物体的质量和速度相关的物理量,指的是运动物体的作用效果。动量是矢量,与速度方向一致。一般而言,一个物体的动量指的是这个物体在它运动方向上保持运动的趋势
SGD存在振荡的问题,原因在于没有充分利用曲率信息的更新引起。当曲率高时会导致SGD变慢。

动量是目标函数中的一个附加项,值介于0到1之间如果动量项较大,则学习率应保持较小。动量的大值也意味着收敛将很快发生。但是,如果动量和学习率都保持较大值,那么可能会大步跳过最小值。较小的动量值不能可靠地避免局部最小值,并且还可能减慢系统的训练速度。如果梯度保持方向变化,则动量还有助于平滑变化。正确的动量值可以通过随机设置和尝试或通过交叉验证(cross validation)来确定。

以下参考李宏毅的深度学习PPT内容:
m i 可理解为之前的梯度 g 0 , g 1 , . . . , g i − 1 加权和 λ 为超参数 假设参数 θ s t a r t i n g a t : θ 0 动量 m 0 = 0 计算梯度值 g 0 动量 m 1 = λ m 0 − η g 0 θ 1 = θ 0 + m 1 计算梯度值 g 1 动量 m 2 = λ m 1 − η g 1 θ 2 = θ 1 + m 2 ⋯ m^{i}可理解为之前的梯度g^{0},g^{1},...,g^{i-1}加权和 \qquad \lambda为超参数 \\ 假设参数\theta \quad starting \quad at: \quad \theta^{0} \quad 动量m^{0}=0 \\ 计算梯度值g^{0} \quad 动量m^{1}=\lambda m^{0}-\eta g^{0} \quad \theta^{1}=\theta^{0}+m^{1} \\ 计算梯度值g^{1} \quad 动量m^{2}=\lambda m^{1}-\eta g^{1} \quad \theta^{2}=\theta^{1}+m^{2} \\ \cdots mi可理解为之前的梯度g0,g1,...,gi1加权和λ为超参数假设参数θstartingat:θ0动量m0=0计算梯度值g0动量m1=λm0ηg0θ1=θ0+m1计算梯度值g1动量m2=λm1ηg1θ2=θ1+m2

DL for Scratch 读书笔记_第42张图片
可以看出,和SGD时的情形相比,可以更快地朝 x x x轴方向靠近,减弱“之”字形的变动程度。

扩展:Nesterov动量

自适应性学习率(AdaGrad)

学习率衰减(learning rate decay),即随着学习的进行,使学习率逐渐减小;逐渐减少学习率,相当于将“全体”参数的学习率值一起降低

​ AdaGrad会为参数的每个元素适当地调整学习率,针对“一个一个”的参数,赋予其“定制”的值,以根据其重要性执行或大或或小的更新。根据当前和之前所有的梯度值 g g g决定‘步伐’大小。

其中, θ i t 为待更新参数, g i t 为梯度,上标 t 代表第 t 个 , σ i t 为均方根 ( R o o t    M e a n    S q u r e ) θ i 1 ← θ i 0 − η σ i 0 g i 0 σ i 0 = ( g i 0 ) 2 θ i 2 ← θ i 1 − η σ i 1 g i 1 σ i 1 = 1 2 [ ( g i 0 ) 2 + ( g i 1 ) 2 ] θ i 3 ← θ i 2 − η σ i 2 g i 2 σ i 2 = 1 3 [ ( g i 0 ) 2 + ( g i 1 ) 2 + ( g i 2 ) 2 ] ⋯ θ i t + 1 ← θ i t − η σ i t g i t σ i t = 1 t + 1 ∑ i = 0 t ( g i t ) 2 这意味着,参数的元素中变动较大(被大幅更新)的元素的学习率将变小。 其中,\theta_{i}^{t}为待更新参数,g_{i}^{t}为梯度,上标t代表第t个,\sigma_{i}^{t}为均方根(Root\;Mean\;Squre) \\ \theta_{i}^{1} \leftarrow \theta_{i}^{0}-\frac{\eta}{\sigma_{i}^{0}}g_{i}^{0} \qquad \sigma_{i}^{0}= \sqrt{(g_i^{0})^{2}} \\ \theta_{i}^{2} \leftarrow \theta_{i}^{1}-\frac{\eta}{\sigma_{i}^{1}}g_{i}^{1} \qquad \sigma_{i}^{1}= \sqrt{\frac{1}{2}[(g_i^{0})^{2}+(g_i^{1})^{2}]} \\ \theta_{i}^{3} \leftarrow \theta_{i}^{2}-\frac{\eta}{\sigma_{i}^{2}}g_{i}^{2} \qquad \sigma_{i}^{2}= \sqrt{\frac{1}{3}[(g_i^{0})^{2}+(g_i^{1})^{2}+(g_i^{2})^{2}]} \\ \cdots \\ \theta_{i}^{t+1} \leftarrow \theta_{i}^{t}-\frac{\eta}{\sigma_{i}^{t}}g_{i}^{t} \qquad \sigma_{i}^{t}= \sqrt{\frac{1}{t+1}\sum_{i=0}^{t}(g_i^{t})^{2}} \\ 这意味着,参数的元素中变动较大(被大幅更新)的元素的学习率将变小。 其中,θit为待更新参数,git为梯度,上标t代表第t,σit为均方根(RootMeanSqure)θi1θi0σi0ηgi0σi0=(gi0)2 θi2θi1σi1ηgi1σi1=21[(gi0)2+(gi1)2] θi3θi2σi2ηgi2σi2=31[(gi0)2+(gi1)2+(gi2)2] θit+1θitσitηgitσit=t+11i=0t(git)2 这意味着,参数的元素中变动较大(被大幅更新)的元素的学习率将变小。

DL for Scratch 读书笔记_第43张图片
从上图可以看出,函数的取值高效地向着最小值移动。由于 y y y轴方向上的梯度较大,因此刚开始变动较大,但后面会根据这个较大的变动按比例进行调整,减小更新的步伐。

​ AdaGrad存在的缺点:AdaGrad会记录过去所有梯度的平方和。因此,**学习越深入,更新的幅度就越小。**实际上,如果无止境地学习,更新量就会变为 0,完全不再更新。

均方误差传播(RMSProp)

​ 对于非凸(non-convex)问题,AdaGrad可能会过早地降低学习率。因此使用指数加权平均值进行梯度累积。RMSProp方法并不是将过去所有的梯度一视同仁地相加,而是逐渐地遗忘过去的梯度,在做加法运算时将新梯度的信息更多地反映出来。

α 为超参数 该方法可以改善 A d a G r a d 随学习深入,更新幅度变小的问题 θ i 1 ← θ i 0 − η σ i 0 g i 0 σ i 0 = ( g i 0 ) 2 θ i 2 ← θ i 1 − η σ i 1 g i 1 σ i 1 = α ( σ i 0 ) 2 + ( 1 − α ) ( g i 1 ) 2 θ i 3 ← θ i 2 − η σ i 2 g i 2 σ i 2 = α ( σ i 1 ) 2 + ( 1 − α ) ( g i 2 ) 2 ⋯ θ i t + 1 ← θ i t − η σ i t g i t σ i t = α ( σ i t − 1 ) 2 + ( 1 − α ) ( g i t ) 2 \alpha为超参数 \qquad 该方法可以改善AdaGrad随学习深入,更新幅度变小的问题 \\ \theta_{i}^{1} \leftarrow \theta_{i}^{0}-\frac{\eta}{\sigma_{i}^{0}}g_{i}^{0} \qquad \sigma_{i}^{0}= \sqrt{(g_i^{0})^{2}} \\ \theta_{i}^{2} \leftarrow \theta_{i}^{1}-\frac{\eta}{\sigma_{i}^{1}}g_{i}^{1} \qquad \sigma_{i}^{1}= \sqrt{\alpha(\sigma_i^{0})^{2}+(1-\alpha)(g_i^{1})^{2}} \\ \theta_{i}^{3} \leftarrow \theta_{i}^{2}-\frac{\eta}{\sigma_{i}^{2}}g_{i}^{2} \qquad \sigma_{i}^{2}= \sqrt{\alpha(\sigma_i^{1})^{2}+(1-\alpha)(g_i^{2})^{2}} \\ \cdots \\ \theta_{i}^{t+1} \leftarrow \theta_{i}^{t}-\frac{\eta}{\sigma_{i}^{t}}g_{i}^{t} \qquad \sigma_{i}^{t}= \sqrt{\alpha(\sigma_i^{t-1})^{2}+(1-\alpha)(g_i^{t})^{2}} \\ α为超参数该方法可以改善AdaGrad随学习深入,更新幅度变小的问题θi1θi0σi0ηgi0σi0=(gi0)2 θi2θi1σi1ηgi1σi1=α(σi0)2+(1α)(gi1)2 θi3θi2σi2ηgi2σi2=α(σi1)2+(1α)(gi2)2 θit+1θitσitηgitσit=α(σit1)2+(1α)(git)2

Adam(融合Momentum和AdaGrad)

自适应矩估计组合Momentum和AdaGrad的优点,实现参数空间的高效搜索;此外,进行**超参数的“偏置校正”**也是Adam的特征。

​ Adam计算每个参数的自适应学习率。除了存储像Adadelta和RMSprop这样的历史平方梯度的指数衰减平均值之外,Adam还保留了历史梯度的指数衰减平均值,类似于动量。

Adam论文

DL for Scratch 读书笔记_第44张图片

参数的初始值

初始的权重值很重要,关系到神经网络能否成功学习。

以我们初始化为所有零值的网络为例。在这种情况下会发生什么?网络实际上根本不会学习任何东西。即使在进行梯度更新之后,由于我们计算梯度更新的固有方式,所有权重仍将为零。
若实施了该解决方案并解决了这个问题,然后决定将网络初始化都设置为相同的值0.5。现在会发生什么?网络实际上会学到一些东西,但是我们过早地规定了神经单元之间某种形式的对称性,在误差反向传播法中,所有的权重值都会进行相同的更新,权重被更新为相同的值,并拥有了对称的值(重复的值)。这使得神经网络拥有许多不同的权重的意义丧失了。为了防止“权重均一化”(严格地讲,是为了瓦解权重的对称结构),必须随机生成初始值。

​ 通常,通过根据 正态分布随机分配权重 来避免以任何形式的神经结构为前提是一个好主意。这通常在Keras中通过指定随机状态来完成(该状态具有随机性,但可确保测量结果可重复)。

numpy.random.randn(2,4) * 3 + 2.5 # μ=2.5,σ=3为的正态分布矩阵——2行4列

​ 初始化的数值范围应该是多少?如果我们为权重选择较大的值,则可能导致梯度爆炸。另一方面,较小的权重值可能会导致梯度消失。有一个最佳点可以提供这两者之间的最佳折衷,但是不能先验地知道,必须通过反复试验来推断。

注意:各层的激活值的分布都要求有适当的广度。Why?

因为通过在各层间传递多样性的数据,神经网络可以进行高效的学习。反过来,如果传递的是有所偏向的数据,就会出现梯度消失或者“表现力受限”的问题,导致学习可能无法顺利进行。

  • 激活值分布集中在0或1,若使用sigmoid激活函数就会出现梯度消失的问题;
  • 激活值分布集中在某个数附近,多个神经元都输出几乎相同的值,那么多neuron就没有意义了。

Xavier初始化

分配网络权重的一种简单启发式方法。对于每个经过的层,我们希望方差保持相同。这有助于防止信号爆炸到高值或消失为零。换句话说,我们需要以使输入和输出的方差保持相同的方式来初始化权重。
​各层的激活值呈现出具有相同广度的分布,推导结论:如果前一层的节点数为 n n n,则初始值使用标准差为 1 n \sqrt \frac{1}{{n}} n1 的分布。使用Xavier初始值后,前一层的节点数越多,要设定为目标节点的初始值的权重尺度就越小。

注意​:Xavier初始值是以激活函数是线性函数为前提而推导出来的。因为sigmoid函数和tanh函数左右对称,且中央附近可以视作线性函数,所以适合使用Xavier初始值。

当激活函数使用ReLU时,一般推荐使用ReLU专用的初始值——Kaiming He等人推荐的“He初始值”

当前一层的节点数为 n n n时,He初始值使用标准差为 2 n \sqrt \frac{2}{{n}} n2 的高斯分布

(直观上解释)因为ReLU的负值区域的值为0,为了使它更有广度,所以需要2倍的系数。

DL for Scratch 读书笔记_第45张图片
观察实验结果可知,当“std = 0.01”时,各层的激活值非常小 。神经网络上传递的是非常小的值,说明逆向传播时权重的梯度也同样很小。这是很严重的问题,实际上学习基本上没有进展。

初始值为Xavier初始值时,随着层的加深,偏向一点点变大。实际上随着层加深后,激活值的偏向变大,学习时会出现梯度消失的问题。

当初始值为He初始值时,各层中分布的广度相同。由于即便层加深,数据的广度也能保持不变,因此逆向传播时,也会传递合适的值。

总结:

  • 当激活函数使用ReLU时,权重初始值使用He初始值;
  • 当激活函数为sigmoid或tanh等S型曲线函数时,初始值使用Xavier初始值。

DL for Scratch 读书笔记_第46张图片

Bias初始化

Bias 初始化是指应如何初始化神经元的Bias。我们已经描述了权重应该以某种形式的正态分布随机初始化(以打破对称性),但是我们应该如何应对Bias呢?

简而言之,初始化Bias 的最简单且常用的方法是将其设置为零:因为不对称破坏是由权重中的较小随机数提供的。对于ReLU非线性,有些人喜欢对所有Bias使用较小的常量值(例如0.01),因为这样可以确保所有ReLU单元在开始时均会触发,从而获得并传播一定的梯度。但是,尚不清楚这是否能提供一致的改进,更常见的是将Bias设置为零。

​ Bias初始化的一个主要问题是避免隐藏单元内的初始化饱和——例如在ReLU中,可以通过将偏置初始化为0.1而不是零来实现。

批标准化(Batch Normalization)

如果设定了合适的权重初始值,则各层的激活值分布会有适当的广度,从而可以顺利地进行学习。那么,为了使各层拥有适当的广度,“强制性”地调整激活值的分布会怎样呢?

Batch Normalization方法就是基于这个想法而产生的。它具有如下优点

  • 可以使学习快速进行(可以增大学习率);
  • 不那么依赖初始值(对于初始值不用那么神经质);
  • 抑制过拟合(降低Dropout等的必要性)。

BN算法实现方法:使数据(mini-batch)分布的均值为0、方差为1的正规化,以减小数据分布的偏向。接着,Batch Norm层会对正规化后的数据进行缩放和平移的变换,用数学式表示: y i = γ x i ^ + β y_i = \gamma\hat{x_i}+\beta yi=γxi^+β,一开始参数 γ = 1 , β = 0 \gamma=1,\beta=0 γ=1,β=0,之后再通过学习调整到合适的值。

μ B ← 1 m ∑ i = 1 m x i , 其中 m 为 b a t c h _ s i z e σ B 2 ← 1 m ∑ i = 1 m ( x i − μ B ) 2 x i ^ ← x i − μ B σ B 2 + ε \mu_{B} \leftarrow \frac{1}{m}\sum_{i=1}^{m}x_i,\qquad 其中m为batch\_size \\ \sigma_{B}^{2} \leftarrow \frac{1}{m}\sum_{i=1}^{m}(x_i - \mu_{B})^2 \\ \hat{x_{i}} \leftarrow \frac{x_i - \mu_{B}}{\sqrt{\sigma_{B}^{2} + \varepsilon}} μBm1i=1mxi,其中mbatch_sizeσB2m1i=1m(xiμB)2xi^σB2+ε xiμB

DL for Scratch 读书笔记_第47张图片

上图表示:Batch Normalization可以改变error surface

其他的Normalization的方法:

  • min-max normalization,缩放到[0,1]或[-1,1]范围。这是通过将每个值减去最小值,然后根据数据集中存在的值的范围对其进行缩放来完成的。如果数据分布高度不均匀,则可能导致许多值聚集在一个位置。如果发生这种情况,有时可以通过采用特征变量的对数来缓解(因为这有使异常值柔和的趋势,因此它们对分布的影响较小)。
  • mean normalization,它与min-max normalization基本相同,只是从每个值中减去平均值。这是比较不常见的一个。

DL for Scratch 读书笔记_第48张图片

使用MNIST数据集,使用Batch Norm层和不使用Batch Norm层时学习的过程变化结果如上图。

几乎所有的情况下都是使用Batch Norm时学习进行得更快。实际上,在不使用Batch Norm的情况下,如果不赋予一个尺度好的初始值,学习将完全无法进行。

内部协方差平移(Internal Covariance Shift)

该想法来自论文Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift,公认的是,输入已被白化(即零均值,单位方差)并且不相关,并且内部协变量平移导致相反的结果,则网络收敛速度会更快。

作者给出的定义:

We define Internal Covariate Shift as the change in the distribution of network activations due to the change in network parameters during training.

批归一化(BN)是一种旨在减轻神经网络内部协变量平移的方法

卷积神经网络(CNN)

卷积神经网络是一种前馈神经网络,专门用来处理具有类似网格结构的数据的神经网络。例如,时间序列数据和图像数据。该神经网络使用了卷积数学运算,是一种特殊的线性运算。

​ 卷积神经网络(Convolutional Neural Network)被用于图像识别、语音识别等各种场合,在图像识别的比赛中,基于深度学习的方法几乎都以CNN为基础。

DL for Scratch 读书笔记_第49张图片
​ CNN 的层的连接顺序是Convolution - ReLU -(Pooling)(Pooling层有时会被省略)

卷积层(Convolution Layer)

卷积层的功能是对输入数据进行特征提取,其内部包含多个卷积核,组成卷积核的每个元素都对应一个权重系数和一个偏差量(bias vector),类似于一个前馈神经网络的神经元。

稀疏交互

卷积层内每个神经元都与前一层中位置接近的区域的多个神经元相连,区域的大小取决于卷积核的大小,在文献中被称为“感受野(receptive field)”,其含义可类比视觉皮层细胞的感受野 。

DL for Scratch 读书笔记_第50张图片

参数共享

在卷积神经网络中,核的每一个元素都作用在输入的每一个位置上,卷积运算中的参数共享保证了我们只需要学习一个参数集合,而不是对于每一位置都需要学习一个单独的参数集合。改方法没有改变代码的运行的时间,确降低了模型的存储空间,并且当网络非常大时,共享参数所占的空间会小很多。

等变表示

对于卷积,参数共享的特殊形式使得神经网络层具有对平移等变的性质。如果一个函数满足输入变化,输出也以同样的方式改变这一性质,我们就说它是等变的。

卷积核在工作时,会有规律地扫过输入特征,在感受野内对输入特征做矩阵元素乘法求和并叠加偏差量。 通过设计的卷积核分别能够提取不同的特征。

全连接层存在的问题:

  • 数据的形状被“忽视”

    ​ 比如,输入数据是图像时,图像通常是(高、宽、通道)方向上的3维形状。但是,向全连接层输入时,需要将3维数据拉平为1维数据(扁平化,Flatten)。

    ​ 使用MNIST数据集的例子中,输入图像就是1通道、高28像素、长28像素的(1*,* 28*,* 28)形状,但却被排成1列,以784个数据的形式输入到最开始的Affine层。

    图像是3维形状,这个形状中应该含有重要的空间信息。

    ​ 比如,***空间上邻近的像素为相似的值、RBG的各个通道之间分别有密切的关联性、相距较远的像素之间没有什么关联等,3维形状中可能隐藏有值得提取的本质模式。***但是,因为全连接层会忽视形状,将全部的输入数据作为相同的神经元(同一维度的神经元)处理,所以无法利用与形状相关的信息。

  • 卷积核的channel与输入特征图的channel相同

  • 输出的特征矩阵channel与卷积核的个数相同

卷积运算(Convolution)

卷积运算相当于图像处理中的“滤波器运算”——卷积核(Kernel)运算。
卷积核(滤波器)的大小也叫感受野(Receptive field)。

为什么要多层卷积而不是扩大卷积核的size?

​ 叠加小型滤波器来加深网络的好处是可以减少参数的数量,扩大感受野(receptive field,给神经元施加变化的某个局部空间区域)。并且,通过叠加层,将 ReLU等激活函数夹在卷积层的中间,进一步提高了网络的表现力。这是因为向网络添加了基于激活函数的“非线性”表现力,通过非线性函数的叠加,可以表现更加复杂的东西。

计算过程:将各个位置上Kernel的元素和输入的对应元素相乘,然后再求和(有时将这个计算称为乘积累加运算 ∑ A i j K i j \sum A_{ij}K_{ij} AijKij)。然后,将这个结果保存到输出的对应位置。将这个过程在所有位置都进行一遍,就可以得到卷积运算的输出。

DL for Scratch 读书笔记_第51张图片
三维数据卷积运算会将多个通道上的特征图的结果相加——输出的特征图通道数为1;

要在通道方向上也拥有多个卷积运算的输出,需要多个滤波器(Filter)【或叫卷积核(Kernel)】——n个Kernel对应输出的特征图通道数为n。

卷积运算的批处理:按(batch_size, channel, height,width)的顺序保存数据。
DL for Scratch 读书笔记_第52张图片

感受野另外一个角度的理解

卷积的过程就是对特征进行浓缩,浓缩后的一个点代表原始输入图片的多大区域 就相当于感受野。

DL for Scratch 读书笔记_第53张图片

❔ 如果堆叠3个 3 ∗ 3 3*3 33的卷积层,并且保持滑动窗口步长为1,其感受野就是 7 ∗ 7 7*7 77的了。这跟一个使用 7 ∗ 7 7*7 77卷积核的结果是一样的,那为什么非要堆叠3个小卷积呢?

假设输入大小都是 h ∗ w ∗ c h∗w∗c hwc,并且都使用c个卷积核(得到c个特征图),可以来计算
一下其各自所需参数:一个 7 ∗ 7 7*7 77卷积核所需参数 C × ( 7 × 7 × C ) = 49 C 2 C\times(7\times7\times{C})=49C^2 C×(7×7×C)=49C2;三个 3 ∗ 3 3*3 33卷积核所需参数 3 × C × ( 3 × 3 × C ) = 27 C 2 3\times{C}\times(3\times3\times{C})=27C^2 3×C×(3×3×C)=27C2.

很明显,堆叠小的卷积核所需的参数更少一些,并且卷积过程越多,特征提取也会越细致,加入的非线性变换也随着增多,还不会增大权重参数个数,这就是VGG网络的基本出发点,用小的卷积核来完成体特征提取操作

特殊地,当卷积核是大小 f = 1 f=1 f=1 ,步长为1 且不包含填充的单位卷积核时,卷积层内的交叉相关计算等价于矩阵乘法,并由此在卷积层间构建了全连接网络。

由单位卷积核组成的卷积层也被称为网中网(Network-In-Network, NIN)或多层感知器卷积层(multilayer perceptron convolution layer, mlpconv)。单位卷积核可以在保持特征图尺寸的同时减少图的通道数从而降低卷积层的计算量。完全由单位卷积核构建的卷积神经网络是一个包含参数共享的多层感知器(Muti-Layer Perceptron, MLP) 。

填充(Padding)

​ 在进行卷积层的处理之前,有时要向输入数据的周围填入固定的数据(比如0等),这称为填充(padding),是卷积运算中经常会用到的处理。

​ 每次进行卷积运算都会缩小空间,那么在某个时刻输出大小就有可能变为 1,导致无法再应用卷积运算。为了避免出现这样的情况,就要使用填充。

DL for Scratch 读书笔记_第54张图片

​ 上图中,将填充的幅度设为 1,那么相对于输入大小(4, 4),输出大小也保持为原来的(4, 4)。因此,卷积运算就可以在保持空间大小不变的情况下将数据传给下一层

填充是在特征图通过卷积核之前人为增大其尺寸以抵消计算中尺寸收缩影响的方法。常见的填充方法为按0填充和重复边界值填充(replication padding)。填充依据其层数和目的可分为四类:

  • 有效填充(valid padding):即完全不使用填充,卷积核只允许访问特征图中包含完整感受野的位置。输出的所有像素都是输入中相同数量像素的函数。使用有效填充的卷积被称为“窄卷积(narrow convolution)”,窄卷积输出的特征图尺寸为 ( L − f ) / s + 1 (L-f)/s+1 (Lf)/s+1
  • 相同填充/半填充(same/half padding):只进行足够的填充来保持输出和输入的特征图尺寸相同。相同填充下特征图的尺寸不会缩减但输入像素中靠近边界的部分相比于中间部分对于特征图的影响更小,即存在边界像素的欠表达。使用相同填充的卷积被称为“等长卷积(equal-width convolution)”。
  • 全填充(full padding):进行足够多的填充使得每个像素在每个方向上被访问的次数相同。步长为1时,全填充输出的特征图尺寸为 L + f − 1 L+f-1 L+f1,大于输入值。使用全填充的卷积被称为“宽卷积(wide convolution)”
  • 任意填充(arbitrary padding):介于有效填充和全填充之间,人为设定的填充,较少使用。
nn.Conv2d(in_channel,out_channel,kernel_size,stride,padding=tuple or int)

padding=1代表上下左右各填充一行/一列0;

padding=(1,2)代表上下补一行0,左右补两列0。

nn.ZeroPad2d((1,1,2,0))——(1,1)代表左补一列,右补一列;(2,0)代表上补两行,下补0行。

步幅(Stride)

应用滤波器(卷积核Kernel)的位置间隔称为步幅(stride)

DL for Scratch 读书笔记_第55张图片

​ 上图中,对输入大小为(7*,* 7)的数据,以步幅2应用了滤波器。通过将步幅设为2,输出大小变为(3*,* 3)。

​ 综上,增大步幅后,输出大小会变小。而增大填充后,输出大小会变大。

假设输入大小为 ( H , W ) , K e r n e l 大小为 ( K H , K W ) , 输出大小为 ( O H , O W ) , p a d d i n g 幅度为 P , s t r i d e 为 S O H = H + 2 P − K H S + 1 O W = W + 2 P − K W S + 1 P . S : 所设定的值必须使上两式分别可以除尽 假设输入大小为(H,W),Kernel大小为(KH,KW),输出大小为(OH,OW),padding幅度为P,stride为S \\ OH = \frac{H + 2P - KH}{ S } + 1 \\ OW = \frac{W + 2P - KW}{ S } + 1 \\ P.S:所设定的值必须使上两式分别可以除尽 假设输入大小为(H,W)Kernel大小为(KH,KW),输出大小为(OH,OW),padding幅度为PstrideSOH=SH+2PKH+1OW=SW+2PKW+1P.S:所设定的值必须使上两式分别可以除尽

卷积层的实现

将图片展开为二维数据,书上的提供common/util.py中的im2col函数实现该功能

DL for Scratch 读书笔记_第56张图片
​上图为了便于观察,故意将stride设置得很大以使Filter的应用区域不重叠。而在实际的卷积运算中,Filter的应用区域几乎都是重叠的。在Filter的应用区域重叠的情况下,使用im2col展开后,展开后的元素个数会多于原方块的元素个数。
DL for Scratch 读书笔记_第57张图片

class Convolution:
    def __init__(self, W, b, stride=1, pad=0):
        self.W = W
        self.b = b
        self.stride = stride
        self.pad = pad
        
    def forward(self, x):
        FN, C, FH, FW = self.W.shape # 卷积核/滤波器 的shape
        N, C, H, W = x.shape
        out_h = int(1 + (H + 2*self.pad - FH) / self.stride)
        out_w = int(1 + (W + 2*self.pad - FW) / self.stride)
        
        col = im2col(x, FH, FW, self.stride, self.pad)
        col_W = self.W.reshape(FN, -1).T # 将各个滤波器的方块纵向展开为1列
        out = np.dot(col, col_W) + self.b
        
        out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)
        return out

​ 在reshape时指定为-1,reshape函数会自动计算-1维度上的元素个数,以使多维数组的元素个数前后一致。

​ 比如,(10*,* 3*,* 5*,* 5)形状的数组的元素个数共有750个,指定reshape(10,-1)后,转换成(10*,* 75)形状的数组。

池化层(Pooling Layer)

池化是缩小高、长方向上的空间的运算。作用是:去除冗余信息、对特征进行压缩、简化网络复杂度、减小计算量、减小内存消耗等等;可以扩大感知野。同下采样(Subsampling),常用的有两种方式更多Pooling方式:

  • Max Pooling
    DL for Scratch 读书笔记_第58张图片
  • Mean(Average) Pooling

池化层的特征:

  • 没有要学习的参数

    池化层和卷积层不同,没有要学习的参数。池化只是从目标区域中取最大值(或者平均值),所以不存在要学习的参数。

  • 通道数不发生变化

    经过池化运算,输入数据和输出数据的通道数不会发生变化。计算是按通道独立进行的。

  • 对微小的位置变化具有鲁棒性(健壮)

    输入数据发生微小偏差时,池化仍会返回相同的结果。因此,池化对输入数据的微小偏差具有鲁棒性。如下图,池化会吸收输入数据的偏差(根据数据的不同,结果有可能不一致)。

    DL for Scratch 读书笔记_第59张图片

池化层的实现

池化在通道方向上是独立的,这一点和卷积层不同,即池化的应用区域按通道单独展开。
DL for Scratch 读书笔记_第60张图片

class Pooling:
    def __init__(self, pool_h, pool_w, stride=1, pad=0):
        self.pool_h = pool_h
        self.pool_w = pool_w
        self.stride = stride
        self.pad = pad
    def forward(self, x):
        N, C, H, W = x.shape
        out_h = int(1 + (H - self.pool_h) / self.stride)
        out_w = int(1 + (W - self.pool_w) / self.stride)
        # 展开(1)
        col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad)
        col = col.reshape(-1, self.pool_h*self.pool_w)
        # 最大值(2)
        out = np.max(col, axis=1)
        # 转换(3)
        out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)
        return out

扩展:SPP以及池化过程使用padding如何计算feature map的尺寸

class SPP(nn.Module):
    # Spatial Pyramid Pooling (SPP) layer https://arxiv.org/abs/1406.4729
    def __init__(self, c1, c2, k=(5, 9, 13)):
        super().__init__()
        c_ = c1 // 2  # hidden channels
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c_ * (len(k) + 1), c2, 1, 1)
        # 注意:这里的padding=x // 2
        self.m = nn.ModuleList([nn.MaxPool2d(kernel_size=x, stride=1, padding=x // 2) for x in k])

    def forward(self, x):
        x = self.cv1(x)
        with warnings.catch_warnings():
            warnings.simplefilter('ignore')  # suppress torch 1.9.0 max_pool2d() warning
            return self.cv2(torch.cat([x] + [m(x) for m in self.m], 1))

特征图size计算同卷积, W f e a t u r e − W p o o l + 2 ∗ p a d s t r i d e p o o l \frac{W_{feature}-W_{pool}+2*pad}{stride_{pool}} stridepoolWfeatureWpool+2pad H f e a t u r e − H p o o l + 2 ∗ p a d s t r i d e p o o l \frac{H_{feature}-H_{pool}+2*pad}{stride_{pool}} stridepoolHfeatureHpool+2pad

pooling池化的作用则体现在降采样:保留显著特征、降低特征维度,增大kernel的感受野。另外一点值得注意:pooling也可以提供一些旋转不变性。

池化层可对提取到的特征信息进行降维,一方面使特征图变小,简化网络计算复杂度并在一定程度上避免过拟合的出现;一方面进行特征压缩,提取主要特征。


CNN的可视化

第一层权重的可视化
DL for Scratch 读书笔记_第61张图片

​ (对应MNIST数据集)学习前的滤波器是随机进行初始化的,所以在黑白的浓淡上没有规律可循,但学习后的滤波器变成了有规律的图像。可以发现,通过学习,滤波器被更新成了有规律的滤波器,比如从白到黑渐变的滤波器、含有块状区域(称为blob)的滤波器等。

​ **有规律的滤波器在“观察”什么?**答案是:它在观察边缘(颜色变化的分界线)和斑块(局部的块状区域)等。

基于分层结构的信息提取

​ 第1层的卷积层中提取了边缘或斑块等“低级”信息,那么在堆叠了多层的CNN中,各层中又会提取什么样的信息呢?随着层次加深,提取的信息(正确地讲,是反映强烈的神经元)也越来越抽象。

DL for Scratch 读书笔记_第62张图片

代表性的CNN

LeNet

LeNet在1998年被提出,Yann LeCun第一次将LeNet卷积神经网络应用到图像分类上,在手写数字识别任务中取得了巨大成功。
DL for Scratch 读书笔记_第63张图片

原始LeNet的特点:

  • LeNet中使用sigmoid函数;

  • 原始的LeNet中使用子采样(subsampling)缩小中间数据的大小。

AlexNet

自从1998年LeNet问世以来,接下来十几年的时间里,神经网络并没有在计算机视觉领域取得很好的结果,反而一度被其它算法所超越。原因主要有两方面,一是神经网络的计算比较复杂,对当时计算机的算力来说,训练神经网络是件非常耗时的事情;另一方面,当时还没有专门针对神经网络做算法和训练技巧的优化,神经网络的收敛是件非常困难的事情。

​ Alex Krizhevsky等人提出的AlexNet以很大优势获得了2012年ImageNet比赛的冠军,这一成果是引发深度学习热潮的导火线,开创了使用深度神经网络解决图像问题的途径,随后也在这一领域涌现出越来越多的优秀成果。

DL for Scratch 读书笔记_第64张图片

​ AlexNet堆叠多个卷积层和池化层(包含5层卷积和3层全连接),最后经由全连接层输出结果。和LeNet有以下几点差异:

  • 激活函数使用ReLU;
  • 使用进行局部正规化的LRN(Local Response Normalization)层;
  • 使用Dropout
  • 数据增广:通过这种方式,扩大训练数据集。可以随机改变训练样本,避免模型过度依赖于某些属性,能从一定程度上抑制过拟合。

VGG

VGG是由卷积层和池化层构成的基础的CNN,2014年由Simonyan和Zisserman提出,其命名来源于论文作者所在的实验室Visual Geometry Group。它的特点在于将有权重的层(卷积层或者全连接层)叠加至16层(或者19层),具备了深度(根据层的深度,有时也称为“VGG16”或“VGG19”)。

VGG在 2014年的比赛中最终获得了第 2名的成绩(下一节介绍的GoogleNet是 2014年的第 1名)。虽然在性能上不及 GoogleNet,但因为VGG结构简单,应用性强,所以很多技术人员都喜欢使用基于VGG的网络。

​ VGG中需要注意的地方是,基于3×3的小型滤波器的卷积层的运算是连续进行的。

如下图所示,重复进行**“卷积层重叠2次到4次,再通过池化层将大小减半”**的处理,最后经由全连接层输出结果。
DL for Scratch 读书笔记_第65张图片

GooLeNet

GoogLeNet是2014年ImageNet比赛的冠军,它的主要特点是网络不仅有深度,还在横向上具有“宽度”。

由于图像信息在空间尺寸上的巨大差异,如何选择合适的卷积核来提取特征就显得比较困难了。空间分布范围更广的图像信息适合用较大的卷积核来提取其特征;而空间分布范围较小的图像信息则适合用较小的卷积核来提取其特征。为了解决这个问题,GoogLeNet提出了一种被称为Inception模块的方案。

​ Inception结构使用了多个大小不同的滤波器(和池化),最后再合并它们的结果。GoogLeNet的特征就是将这个Inception结构用作一个构件(构成元素)。

DL for Scratch 读书笔记_第66张图片
Inception模块结构示意图

图(a)是Inception模块的设计思想,使用3个不同大小的卷积核对输入图片进行卷积操作,并附加最大池化,将这4个操作的输出沿着通道这一维度进行拼接,构成的输出特征图将会包含经过不同大小的卷积核提取出来的特征,从而达到捕捉不同尺度信息的效果。

Inception模块采用多通路(multi-path)的设计形式,每个支路使用不同大小的卷积核,最终输出特征图的通道数是每个支路输出通道数的总和,这将会导致输出通道数变得很大,尤其是使用多个Inception模块串联操作的时候,模型参数量会变得非常大。

为了减小参数量,Inception模块使用了图(b)中的设计方式,在每个3x3和5x5的卷积层之前,增加1x1的卷积层来控制输出通道数;在最大池化层后面增加1x1卷积层减小输出通道数。基于这一设计思想,形成了上图(b)中所示的结构。


GoogLeNet模型网络结构示意图
DL for Scratch 读书笔记_第67张图片

​ GoogLeNet的架构如 图 所示,在主体卷积部分中使用5个模块(block),每个模块之间使用步幅为2的3 ×3最大池化层来减小输出高宽。

  • 第一模块使用一个64通道的7 × 7卷积层。
  • 第二模块使用2个卷积层:首先是64通道的1 × 1卷积层,然后是将通道增大3倍的3 × 3卷积层。
  • 第三模块串联2个完整的Inception块。
  • 第四模块串联了5个Inception块。
  • 第五模块串联了2 个Inception块。
  • 第五模块的后面紧跟输出层,使用全局平均池化层来将每个通道的高和宽变成1,最后接上一个输出个数为标签类别数的全连接层。

说明:
在原作者的论文中添加了图中所示的softmax1和softmax2两个辅助分类器,训练时将三个分类器的损失函数进行加权求和,以缓解梯度消失现象。

ResNet

ResNet是2015年ImageNet比赛的冠军,将识别错误率降低到了3.6%,这个结果甚至超出了正常人眼识别的精度。

​ 加深层对于提升性能很重要。但是,在深度学习中,过度加深层的话,很多情况下学习将不能顺利进行,导致最终性能不佳。——深度网络出现了退化问题(Degradation problem):网络深度增加时,网络准确度出现饱和,甚至出现下降。

DL for Scratch 读书笔记_第68张图片

20层与56层网络在CIFAR-10上的误差

​ ResNet中,为了解决这类问题,导入了残差块(Residual block),之后就可以随着层的加深而不断提高性能了(当然,层的加深也是有限度的)。

DL for Scratch 读书笔记_第69张图片

​ Kaiming He提出了残差学习来解决退化问题:对于一个堆积层结构(几层堆积而成)当输入为 x x x 时其学习到的特征记为 H ( x ) H(x) H(x) ,希望其可以学习到残差 F ( x ) = H ( x ) − x F(x)=H(x)-x F(x)=H(x)x,这样其实原始的学习特征是 F ( x ) + x F(x)+x F(x)+x

​ 之所以这样是因为残差学习相比原始特征直接学习更容易。当残差为0时,此时堆积层仅仅做了恒等映射,至少网络性能不会下降,实际上残差不会为0,这也会使得堆积层在输入特征基础上学习到新的特征,从而拥有更好的性能。残差学习的结构有点类似与电路中的“短路”,所以是一种短路连接(shortcut connection)。

​ 因为残差块只是原封不动地传递输入数据,所以反向传播时会将来自上游的梯度原封不动地传向下游。因此,不用担心梯度会变小(或变大),能够向前一层传递“有意义的梯度”。

​ 通过这个残差块结构,之前因为加深层而导致的梯度变小的梯度消失问题就有望得到缓解。

DL for Scratch 读书笔记_第70张图片

迁移学习:将学习完的权重(的一部分)复制到其他神经网络,进行再学习(fine tuning)。

比如,准备一个和 VGG相同结构的网络,把学习完的权重作为初始值,以新数据集为对象,进行再学习。迁移学习在手头数据集较少时非常有效。

平铺卷积的卷积核只扫过特征图的一部份,剩余部分由同层的其它卷积核处理,因此卷积层间的参数仅被部分共享,有利于神经网络捕捉输入图像的旋转不变(shift-invariant)特征。
反卷积或转置卷积(transposed convolution)将单个的输入激励与多个输出激励相连接,对输入图像进行放大。
扩张卷积在线性卷积的基础上引入扩张率以提高卷积核的感受野,从而获得特征图的更多信息。

转置卷积(transposed convolution)

转置卷积也被称作: "分数步长卷积(Fractionally-strided convolution)“和"反卷积(Deconvolution)”。卷积的逆向操作,考虑一下如换一个计算方向。 也就是说,要建立在一个矩阵中的1个值和另外一个矩阵中的9个值的关系

注意:转置卷积不是卷积的逆运算!!!只能恢复尺寸,不能恢复数值

​ 一个用于分类任务的深度神经网络通过卷积来不断抽象学习,实现分辨率的降低,最后得到一个较小的Featureb Map。而图像分割任务需要恢复与原尺寸大小一样的图片,从这个较小尺度的特征图恢复原始图片尺寸,这是一个上采样过程。由于这个过程与卷积正好是对应的逆操作,所以通常称为反卷积。反卷积通常有几种实现方式,一种是双线性插值为代表的插值法,一种是转置卷积。

​ 上采样有诸多方法,举例如下:

  • 最近邻插值(Nearest neighbor interpolation)
  • 双线性插值(Bi-linear interpolation)
  • 双立方插值(Bi-cubic interpolation)

​ 但是, 上述的方法都需要插值操作,没有网络进行学习的余地。如果我们想要网络去学出一种最优的上采样方法,我们可以使用转置卷积,它与基于插值的方法不同,它有可以学习的参数。

转置卷积的运算步骤可以归为以下几步:

  • 在输入特征图元素间填充 s − 1 s-1 s1行、列的0值(其中s表示转置卷积的步距)
  • 在输入特征图四周填充 k − p − 1 k-p-1 kp1行、列的0值(其中k表示转置卷积的kernel_size大小,p为转置卷积的padding,注意这里的padding和卷积操作中有些不同)
  • 将卷积核参数上下、左右翻转
  • 做正常卷积运算(填充0,步距1)

下面假设输入的特征图大小为2x2(假设输入输出都为单通道),通过转置卷积后得到4x4大小的特征图。这里使用的转置卷积核大小为k=3,stride=1,padding=0的情况(忽略偏执bias)。

  • 首先在元素间填充 s − 1 = 0 s-1=0 s1=0行、列0(等于0不用填充)

  • 然后在特征图四周填充 k − p − 1 = 2 k-p-1=2 kp1=2行、列0

  • 接着对卷积核参数进行上下、左右翻转

  • 最后做正常卷积(填充0,步距1)

s=1, p=0, k=3 s=2, p=0, k=3 s=2, p=1, k=3

转置卷积操作后特征图的大小可以通过如下公式计算:
H o u t = ( H i n − 1 ) × s t r i d e [ 0 ] − 2 × p a d d i n g [ 0 ] + k e r n e l _ s i z e [ 0 ] W o u t = ( W i n − 1 ) × s t r i d e [ 1 ] − 2 × p a d d i n g [ 1 ] + k e r n e l _ s i z e [ 1 ] 其中,索引 0 表示高度上的,索引 1 表示宽度上的 H_{out}=(H_{in}-1)×stride[0]−2×padding[0]+kernel\_size[0] \\ W_{out}=(W_{in}−1)×stride[1]−2×padding[1]+kernel\_size[1] \\ 其中,索引0表示高度上的,索引1表示宽度上的 Hout=(Hin1)×stride[0]2×padding[0]+kernel_size[0]Wout=(Win1)×stride[1]2×padding[1]+kernel_size[1]其中,索引0表示高度上的,索引1表示宽度上的

一些详细的矩阵方面推导参考:https://zhuanlan.zhihu.com/p/48501100/

空洞卷积(dilated convolution)

参考文章:详解空洞卷积

空洞卷积也叫扩张卷积或者膨胀卷积,简单来说就是在卷积核元素之间加入一些空格(零)来扩大卷积核的过程。

设以一个变量 a a a来衡量空洞卷积的扩张系数,则加入空洞之后的实际卷积核尺寸与原始卷积核尺寸之间的关系: K = K + ( k − 1 ) ( a − 1 ) K = K + (k-1)(a-1) K=K+(k1)(a1)或者 K = a ∗ ( k − 1 ) + 1 K=a*(k-1)+1 K=a(k1)+1.【 o u t p u t = i n p u t + 2 ∗ p a d − K s t r i d e + 1 output=\frac{input+2*pad-K}{stride}+1 output=strideinput+2padK+1

其中 k k k为原始卷积核大小, a a a为卷积扩张率(dilation rate), K K K为经过扩展后实际卷积核大小。除此之外,空洞卷积的卷积方式跟常规卷积一样。我们用一个扩展率 a a a来表示卷积核扩张的程度。比如说 a = 1 , 2 , 4 a=1,2,4 a=1,2,4的时候卷积核核感受野如下图所示:

DL for Scratch 读书笔记_第71张图片

在这张图像中,3×3 的红点表示经过卷积后,输出图像是 3×3 像素。尽管所有这三个扩张卷积的输出都是同一尺寸,但模型观察到的感受野有很大的不同。

  • 当a=1,原始卷积核size为 3 ∗ 3 3*3 33,就是常规卷积。
  • a=2时,加入空洞之后的卷积核: s i z e = 3 + ( 3 − 1 ) ∗ ( 2 − 1 ) = 5 size=3+(3-1) * (2-1)=5 size=3+(31)(21)=5,对应的感受野可计算为: 2 ( a + 2 ) − 1 = 7 2 ^{(a+2)}-1=7 2(a+2)1=7
  • a=3时,卷积核size可以变化到 3 + ( 3 − 1 ) ( 4 − 1 ) = 9 3+(3-1)(4-1)=9 3+(31)(41)=9,感受野则增长到 2 ( a + 2 ) − 1 = 15 2 ^{(a+2)}-1=15 2(a+2)1=15。有趣的是,与这些操作相关的参数的数量是相等的。我们「观察」更大的感受野不会有额外的成本。因此,扩张卷积可用于廉价地增大输出单元的感受野,而不会增大其核大小,这在多个扩张卷积彼此堆叠时尤其有效。

CNN感受野的计算

感受野,是指输出特征图上某个像素对应到输入空间中的区域范围。所以感受野可以理解为特征图像素到输入区域的映射。

  • ”正向“运算

    根据input_size、kernel_size等信息计算输出特征图

输出 s i z e : n o u t = n i n + 2 p − f s + 1 n i n 为输入 s i z e ; p 为 p a d d i n g 大小; f 为卷积核 s i z e ; s 为卷积步长 输出size:n_{out}=\frac{n_{in}+2p-f}{s}+1\\ n_{in}为输入size;p为padding大小;f为卷积核size;s为卷积步长 输出sizenout=snin+2pf+1nin为输入sizeppadding大小;f为卷积核sizes为卷积步长

假设输入大小为 5 ∗ 5 , f = 3 ∗ 3 5 * 5,f=3 * 3 55f=33,padding为 1 ∗ 1 1 * 1 11,卷积步长 s = 2 s=2 s=2,那么输出特征图size根据公式可计算为 3 ∗ 3 3 * 3 33。如下图所示:

DL for Scratch 读书笔记_第72张图片

然后我们继续对 3 ∗ 3 3 * 3 33的特征图执行卷积,卷积参数同第一次卷积一样,可得输出特征图size为 2 ∗ 2 2 * 2 22。我们把输入、两次卷积过程和对应特征图放到一起看一下:

DL for Scratch 读书笔记_第73张图片

可以看到两次卷积的特征图分别对应到输入空间的感受野大小,第一次卷积对应关系如图中绿色线条所示,感受野大小为 3 ∗ 3 3 * 3 33,第二次卷积对应关系如图中黄色线条所示,感受野大小为 7 ∗ 7 7 * 7 77

  • “逆向”计算

感受野计算公式:
F k = F k − 1 + [ ( f k − 1 ) ∗ ∏ i = 1 k − 1 s i ] o r    F ( i ) = ( F ( i + 1 ) − 1 ) × s i + f k F_k=F_{k-1}+[(f_k-1)*\prod_{i=1}^{k-1}s_i]\\ or\;F(i)=(F(i+1)-1)\times{s_i}+f_k Fk=Fk1+[(fk1)i=1k1si]orF(i)=(F(i+1)1)×si+fk
其中 F k F_k Fk为第 k k k层感受野, s i s_i si为第 i i i层步距, f k f_k fk为卷积核尺寸
输入的特征图经过 3 个 3 ∗ 3 、 s t r i d e = 1 的卷积后: 最后输出的 f e a t u r e    m a p : F = 1 3 t h : F = 1 + ( 3 − 1 ) ∗ 1 = 3 2 t h : F = 3 + ( 3 − 1 ) ∗ 1 ∗ 1 = 5 1 t h : F = 5 + ( 3 − 1 ) ∗ 1 ∗ 1 ∗ 1 = 7 输入的特征图经过3个3*3、stride=1的卷积后:\\最后输出的feature\;map:F=1\\ 3th:F=1+(3-1)*1=3\\ 2th:F=3+(3-1)*1*1=5\\ 1th:F=5+(3-1)*1*1*1=7 输入的特征图经过333stride=1的卷积后:最后输出的featuremap:F=13th:F=1+(31)1=32th:F=3+(31)11=51th:F=5+(31)111=7
连续使用3个 3 ∗ 3 、 s t r i d e = 1 3*3、stride=1 33stride=1的卷积核相当于使用使用一个 7 ∗ 7 7*7 77的卷积核,大大降低了参数。

空洞卷积主要有三个作用:

  1. 扩大感受野。但需要明确一点,池化也可以扩大感受野,但空间分辨率降低了,相比之下,空洞卷积可以在扩大感受野的同时不丢失分辨率,且保持像素的相对空间位置不变。简单而言,空洞卷积可以同时控制感受野和分辨率。
  2. 获取多尺度上下文信息。当多个带有不同dilation rate的空洞卷积核叠加时,不同的感受野会带来多尺度信息,这对于分割任务是非常重要的。
  3. 可以降低计算量,不需要引入额外的参数,如上图空洞卷积示意图所示,实际卷积时只有带有红点的元素真正进行计算。

CNN感受野的计算

感受野,是指输出特征图上某个像素对应到输入空间中的区域范围。所以感受野可以理解为特征图像素到输入区域的映射。

  • ”正向“运算

    根据input_size、kernel_size等信息计算输出特征图

输出 s i z e : n o u t = n i n + 2 p − f s + 1 n i n 为输入 s i z e ; p 为 p a d d i n g 大小; f 为卷积核 s i z e ; s 为卷积步长 输出size:n_{out}=\frac{n_{in}+2p-f}{s}+1\\ n_{in}为输入size;p为padding大小;f为卷积核size;s为卷积步长 输出sizenout=snin+2pf+1nin为输入sizeppadding大小;f为卷积核sizes为卷积步长

假设输入大小为 5 ∗ 5 , f = 3 ∗ 3 5 * 5,f=3 * 3 55f=33,padding为 1 ∗ 1 1 * 1 11,卷积步长 s = 2 s=2 s=2,那么输出特征图size根据公式可计算为 3 ∗ 3 3 * 3 33。如下图所示:

DL for Scratch 读书笔记_第74张图片

然后我们继续对 3 ∗ 3 3 * 3 33的特征图执行卷积,卷积参数同第一次卷积一样,可得输出特征图size为 2 ∗ 2 2 * 2 22。我们把输入、两次卷积过程和对应特征图放到一起看一下:

DL for Scratch 读书笔记_第75张图片

可以看到两次卷积的特征图分别对应到输入空间的感受野大小,第一次卷积对应关系如图中绿色线条所示,感受野大小为 3 ∗ 3 3 * 3 33,第二次卷积对应关系如图中黄色线条所示,感受野大小为 7 ∗ 7 7 * 7 77

  • “逆向”计算

感受野计算公式:
F k = F k − 1 + [ ( f k − 1 ) ∗ ∏ i = 1 k − 1 s i ] o r    F ( i ) = ( F ( i + 1 ) − 1 ) × s i + f k F_k=F_{k-1}+[(f_k-1)*\prod_{i=1}^{k-1}s_i]\\ or\;F(i)=(F(i+1)-1)\times{s_i}+f_k Fk=Fk1+[(fk1)i=1k1si]orF(i)=(F(i+1)1)×si+fk
其中 F k F_k Fk为第 k k k层感受野, s i s_i si为第 i i i层步距, f k f_k fk为卷积核尺寸
输入的特征图经过 3 个 3 ∗ 3 、 s t r i d e = 1 的卷积后: 最后输出的 f e a t u r e    m a p : F = 1 3 t h : F = 1 + ( 3 − 1 ) ∗ 1 = 3 2 t h : F = 3 + ( 3 − 1 ) ∗ 1 ∗ 1 = 5 1 t h : F = 5 + ( 3 − 1 ) ∗ 1 ∗ 1 ∗ 1 = 7 输入的特征图经过3个3*3、stride=1的卷积后:\\最后输出的feature\;map:F=1\\ 3th:F=1+(3-1)*1=3\\ 2th:F=3+(3-1)*1*1=5\\ 1th:F=5+(3-1)*1*1*1=7 输入的特征图经过333stride=1的卷积后:最后输出的featuremap:F=13th:F=1+(31)1=32th:F=3+(31)11=51th:F=5+(31)111=7
连续使用3个 3 ∗ 3 、 s t r i d e = 1 3*3、stride=1 33stride=1的卷积核相当于使用使用一个 7 ∗ 7 7*7 77的卷积核,大大降低了参数。

为什么偶数size的卷积核很少选用?

最近突然发现了一个问题,为什么深度学习中的模型基本用3x3和5x5的卷积(奇数),而不是2x2和4x4的卷积(偶数)呢?

知乎一位大哥解释让我豁然开朗!——知乎回答链接

在 stride 为 1 的情况下,连续两层 3 × 3 3\times3 3×3的卷积与一层 5 × 5 5\times5 5×5的卷积具有相同的感受野,连续两层 2 × 2 2\times2 2×2 的卷积与一层 3 × 3 3\times3 3×3的卷积具有相同感受野,一层 2 × 2 2\times2 2×2的卷积加上一层 3 × 3 3\times3 3×3的卷积与一层 4 × 4 4\times4 4×4的卷积具有相同的感受野。

使用小的卷积核代替大卷积核的目的有两个:

  • 第一,降低参数数量
  • 第二,顺带的效果是增加了网络深度。

两层 3 × 3 3\times3 3×3的卷积的参数数量为 3 × 3 × 2 = 18 3\times3\times2=18 3×3×2=18,一层 5 × 5 5\times5 5×5的卷积的参数数量为 5 × 5 = 25 5\times5=25 5×5=25。使用两层 3 × 3 3\times3 3×3的卷积代替一层 5 × 5 5\times5 5×5的卷积参数数量减少了 7。
两层 2 × 2 2\times2 2×2的卷积的参数数量为 2 × 2 × 2 = 8 2\times2\times2=8 2×2×2=8,一层 3 × 3 3\times3 3×3的卷积的参数数量为 3 × 3 = 9 3\times3=9 3×3=9。使用两层 2 × 2 2\times2 2×2的卷积替代一层 3 × 3 3\times3 3×3的卷积参数数量只减少了 1。
一层 2 × 2 2\times2 2×2的卷积加上一层 3 × 3 3\times3 3×3的卷积的参数数量为 2 × 2 + 3 × 3 = 13 2\times2+3\times3=13 2×2+3×3=13,一层 4 × 4 4\times4 4×4的卷积的参数数量为 4 × 4 = 16 4\times4=16 4×4=16。使用一层 2 × 2 2\times2 2×2的卷积加上一层 3 × 3 3\times3 3×3替代一层 4 × 4 4\times4 4×4的卷积参数数量减少了 3。


从减少参数数量的角度来讲,第一种方案优于后面两种方案。另外,使用 2 × 2 2\times2 2×2的卷积想要获得较大的感受野,需要更多的网络层次,比如想要获得 5 × 5 5\times5 5×5的感受野,可以使用两层 3 × 3 3\times3 3×3的卷积,如果使用 2 × 2 2\times2 2×2的卷积则需要四层。越深的网络越容易梯度消失,越难训练,使用 2 × 2 2\times2 2×2的卷积会在一定程度上增加网络训练的难度。


  1. 感知机又称“人工神经元”或者“朴素感知机”。 ↩︎

  2. ( ∂ L ∂ w i , ∂ L ∂ b ) (\frac{\partial L}{\partial w_{i}},\frac{\partial L}{\partial b}) (wiL,bL)​​这样由全部变量的偏导数汇总而成的向量 ↩︎

你可能感兴趣的:(深度学习,神经网络,人工智能,深度学习,pytorch)