发布时间:2024-07-16 10:01
在梯度下降中,随着算法反向反馈到前面几层,梯度会越来越小,最终,没有变化,这 时或许还没有收敛到比较好的解,这就是梯度消失问题,深度学习遭受不稳定的梯度,不同 层学习在不同的速度上
如果我们看sigmoid激活函数,当输入比较大,不管正负,将会饱和在 0 或 1,这样梯度就是 0,因此当反向传播开始,它几乎没有梯度传播回神经网络,所以就会导致只更改高的几 层,低的几层不会变化
选择 ReLU 更多因为对于正数时候不饱和,同时因为它计算速度快 但是不幸的是,ReLU 激活函数不完美,有个问题是 dying ReLU,在训练的时候一些神经元死了,当它们输出小于 0,经过ReLU就会变成0,不管以后反向传播的再多,最终改变w也是0
解决方案: Leaky ReLU
max ( α ∗ z , z ) , α = 0.01 \\max(\\alpha * z,z), \\alpha =0.01 max(α∗z,z),α=0.01
自己封装 Leaky ReLU
def leaky_relu(z,name=None):
return tf.maximun(0.01*z,z,name=name)
hidden1 = fully_connected(X,n_hidden1,activitation_fn=leaky_relu)
ELU 可以在tansorflow中直接调用
hidden1 = fully_connected(X,n_hidden1,activation_fn=tf.nn.elu)
深度学习模型训练的过程本质是对 weight(即参数 W)进行更新,这需要每个参数有相应的初始值。有人可能会说:“参数初始化有什么难点?直接将所有 weight 初始化为 0 或者初始化为随机数!” 对一些简单的机器学习模型,或当 optimization function 是 convex function 时,这些简单的方法确实有效。然而对于深度学习而言,非线性函数被疯狂叠加,non-convex function,如何选择参数初始值便成为一个值得探讨的问题 — 其本质是初始参数的选择应使得 objective function 便于被优化。事实上,在学术界这也是一个被 actively 研究的领域。
不想从零开始训练神经网络时,我们往往选择一个已经训练好的在任务 A 上的模型(称为 pre-trained model),将其放在任务 B 上做模型调整(称为 fine-tuning)
随机初始化是很多人目前经常使用的方法,然而这是有弊端的,一旦随机分布选择不当, 就会导致网络优化陷入困境。
以 tanh 神经网络为例,查看激活值和梯度的分布情况。
可以看出,激活值的方差逐层递减 各层反向传播的梯度(关于状态的梯度)的分布情况:
随着层数的增加,梯度就会越来越接近0,就会产生梯度消失
我们创建了一个 10 层的神经网络,非线性变换为 tanh,每一层的参数都是随机正态分布,均值为 0,标准差为 0.01。
import tensorflow as tf # 2.0+的tensorflow
import numpy as np
import matplotlib.pyplot as plt
data = tf.constant(np.random.randn(2000, 800))
layer_sizes = [800 - 50 * i for i in range(0,11)]
num_layers = len(layer_sizes)
fcs = [] # To store fully connected layers\' output
fig, axs = plt.subplots(2, 5, figsize=(15, 6), sharey=True)
for i in range(0, num_layers - 1):
X = data if i == 0 else fcs[i - 1]
node_in = layer_sizes[i]
node_out = layer_sizes[i + 1]
W = tf.Variable(np.random.randn(node_in, node_out)) * 0.01
fc = tf.matmul(X, W)
fc = tf.nn.tanh(fc)
fcs.append(fc)
axs[i//5,i%5].hist(fc.numpy()[:,1])
# axs[i//5,i%5].set_xlim([-1,1])
fig.show()
注意横坐标值,如果都固定为[-1,1]区间,那么最终就会看不见输出。随着层数的增加,我们看到输出值迅速向 0 靠拢,在后几层中,几乎所有的输出值都 很接近 0!回忆优化神经网络的 back propagation 算法,根据链式法则,gradient 等于当前函数的 gradient 乘以后一层的 gradient,这意味着输出值计算 gradient 中的乘法因子,直接导致 gradient 很小,使得参数难以被更新!
让我们将初始值调大一些: 均值为 0,标准差为 1。
几乎所有的值集中在-1 或 1 附近,神经元 saturated 了!注意到 tanh 在-1 和 1 附近 的 gradient 都接近 0,这同样导致了 gradient 太小,参数难以被更新。
Xavier 初始化的基本思想是保持输入和输出的方差一致,这样就避免了所有输出值都趋向于 0。注意,为了问题的简便,Xavier 初始化的推导过程是基于线性函数的,但是它在一些非线性神经元中也很有效
激活函数 | 采用均匀分布随机,区间[-r,r] | 采用正太分布随机,均值为0 |
---|---|---|
sigmoid | r = 6 n i n p u t s + n o u t p u t s r=\\sqrt{\\frac{6}{n_{inputs}+n_{outputs}}} r=ninputs+noutputs6 | σ = 2 n i n p u t s + n o u t p u t s \\sigma=\\sqrt{\\frac{2}{n_{inputs}+n_{outputs}}} σ=ninputs+noutputs2 |
tanh | r = 4 6 n i n p u t s + n o u t p u t s r=4\\sqrt{\\frac{6}{n_{inputs}+n_{outputs}}} r=4ninputs+noutputs6 | σ = 4 2 n i n p u t s + n o u t p u t s \\sigma=4\\sqrt{\\frac{2}{n_{inputs}+n_{outputs}}} σ=4ninputs+noutputs2 |
Relu | r = 2 6 n i n p u t s + n o u t p u t s r=\\sqrt{2}\\sqrt{\\frac{6}{n_{inputs}+n_{outputs}}} r=2 ninputs+noutputs6 | σ = 2 2 n i n p u t s + n o u t p u t s \\sigma=\\sqrt{2}\\sqrt{\\frac{2}{n_{inputs}+n_{outputs}}} σ=2 ninputs+noutputs2 |
import tensorflow as tf # 2.0+çš„tensorflow
import numpy as np
import matplotlib.pyplot as plt
data = tf.constant(np.random.randn(2000, 800))
layer_sizes = [800 - 50 * i for i in range(0,11)]
num_layers = len(layer_sizes)
fcs = [] # To store fully connected layers\' output
fig, axs = plt.subplots(2, 5, figsize=(15, 6), sharey=True)
for i in range(0, num_layers - 1):
X = data if i == 0 else fcs[i - 1]
node_in = layer_sizes[i]
node_out = layer_sizes[i + 1]
W = tf.Variable(np.random.randn(node_in, node_out)) * np.sqrt(2/(node_in+node_out))
fc = tf.matmul(X, W)
fc = tf.nn.tanh(fc)
fcs.append(fc)
axs[i//5,i%5].hist(fc.numpy()[:,1])
axs[i//5,i%5].set_xlim([-1,1])
fig.show()
输出值在很多层之后依然保持着良好的分布,这很有利于我们优化神经网络!之前谈到 Xavier initialization 是在线性函数上推导得出,这说明它对非线性函数并不具有普适性,所以这个例子仅仅说明它对 tanh 很有效,那么对于目前最常用的 ReLU 神经元呢?
W = tf.Variable(np.random.randn(node_in, node_out)) * np.sqrt(4/(node_in+node_out))
fc = tf.nn.relu(fc)
前面看起来还不错,后面的趋势却是越来越接近 0。
总的来说,无论是参数的初始化,还是激活函数的选取,都只能减轻梯度消失的症状,而不能解决梯度消失问题
在反向传播的过程中,很多梯度大于1,在链式求导法则下,累乘结果趋于无穷大,根据梯度下降的做法,在梯度爆炸下将会走很大一步,从而走出最优解域
常用的Normalization方法主要有: Batch Normalization(BN,2015 年)、Layer Normalization (LN , 2016 年)、 Instance Normalization ( IN , 2017 年 )、Group Normalization(GN,2018 年)。它们都是从激活函数的输入来考虑、做文章的,以不同的方式对激活函数的输入进行 Norm 的。
我们将输入的 feature map shape 记为[N, C, H, W],其中 N 表示 batch size,即 N
个样本;C 表示通道数;H、W 分别表示特征图的高度、宽度。这几个方法主要的区别就是在:
BN的操作是这样的: 取一个特定的Batch,把这一个Batch中的每个图片的某个通道,作为一个归一化的对象,举例来说 [ 10 , 3 , 28 , 28 ] [10,3,28,28] [10,3,28,28]的图片,当取第一个通道作为归一化的对象的时候,会将10幅图片的第一个通道都拿出来,将10*28*28的数据做归一化…
Batch Normalization 是一种巧妙而粗暴的方法来削弱 bad initialization 的影响,我们想要的是在非线性 activation 之前,输出值应该有比较好的分布(例如高斯分布),
以便于 back propagation 时计算 gradient,更新 weight。
主要思想: 针对每个神经元,使数据在进入激活函数之前,沿着通道计算每个 batch 的均值、方差,‘强迫’数据保持均值为 0,方差为 1 的正态分布,避免发生梯度消失。
全连接层或卷积操作之后,激活函数之前
Batch Normalization 将输出值强行做一次 Gaussian Normalization 和线性变换
Input:Values of x over a mini-batch: B = { x 1 , … , m } \\mathcal{B} = \\{x_{1,\\ldots,m}\\} B={x1,…,m}Parameters to be learned: γ , β \\gamma,\\beta γ,β
Output: { y i = N B γ , β ( x i ) } \\{y_i=NB_{\\gamma,\\beta}(x_i)\\} {yi=NBγ,β(xi)}
μ B ← 1 m ∑ i = 1 m x i σ B 2 ← 1 m ∑ i = 1 m ( x i − μ B ) 2 x ^ i ← x i − μ B σ B 2 + ϵ y i ← γ x ^ i + β ≡ B N γ , β ( x i ) \\mu_{\\mathcal{B}} \\leftarrow \\frac{1}{m}\\sum_{i=1}^mx_i \\\\ \\sigma_{\\mathcal{B}}^2 \\leftarrow \\frac{1}{m}\\sum_{i=1}^m(x_i-\\mu_{\\mathcal{B}})^2 \\\\ \\hat{x}_i \\leftarrow \\frac{x_i-\\mu_{\\mathcal{B}}}{\\sqrt{\\sigma_{\\mathcal{B}}^2 + \\epsilon}}\\\\ y_i \\leftarrow \\gamma\\hat{x}_i + \\beta \\equiv BN_{\\gamma,\\beta}(x_i) μB←m1i=1∑mxiσB2←m1i=1∑m(xi−μB)2x^i←σB2+ϵ xi−μByi←γx^i+β≡BNγ,β(xi)
Batch Normalization 中所有的操作都是平滑可导,这使得 back propagation 可以有效运行并学到相应的参数 γ , β \\gamma,\\beta γ,β。需要注意的一点是 Batch Normalization 在 training 和testing 时行为有所差别。Training 时 μ B \\mu_{\\mathcal{B}} μB 和 σ B \\sigma_{\\mathcal{B}} σB 由当前 batch 计算得出;在 Testing 时 μ B \\mu_{\\mathcal{B}} μB 和 σ B \\sigma_{\\mathcal{B}} σB 应使用 Training 时保存的均值或类似的经过处理的值,而不是由当前 batch 计算。
加入缩放和平移变量(算法的最后一步)的原因是:保证每一次数据经过归一化后还保留原有学习来的特征,同时又能完成归一化操作,加速训练。 这两个参数是用来学习的参数。
举例来说 [ 10 , 3 , 28 , 28 ] [10,3,28,28] [10,3,28,28]的图片,当取第一张图片作为归一化的对象的时候,会将该图片的全部都拿出来,将3*28*28的数据做归一化…
举例来说 [ 10 , 3 , 28 , 28 ] [10,3,28,28] [10,3,28,28]的图片,当取第一张图片的第一个通道作为归一化的对象的时候,会将该图片该通道全部都拿出来,将28*28的数据做归一化…
举例来说 [ 10 , 4 , 28 , 28 ] [10,4,28,28] [10,4,28,28]的图片,先将每一张图片的通道数划分为G份(假设为2),当取第一张图片的第一个份作为归一化的对象的时候,会将该图片该份全部都拿出来,将2*28*28的数据做归一化…