Training Neural Networks
Actiation Functions
几种常见的激活函数
Sigmoid 函数
σ ( x ) = 1 1 + e − x \sigma(x) = \frac{1}{1 + e^{-x}} σ(x)=1+e−x1
- 将输入值挤压到 0到1的范围之间
- 在历史中常用,对神经元的“firing rate” 有良好的解释
缺点:
- sigmoid 饱和的时候,梯度消失
当输入的x过大,或者过小,local gredian 趋近于0,local 梯度与上游传来的梯度相乘,趋近于0,参数几乎无法更新
为了防止饱和,对于权重矩阵的初始化必须特别留意,如果权重过大,那么大多数神经元将饱和,网络几乎不再更新。
- sigmoid 函数不是零中心的
这一情况将影响梯度下降的速度,因为如果输入神经元的数据X总是正数,那么 d σ d W = X T ∗ σ ′ \frac{d\sigma}{dW} = X^T * \sigma' dWdσ=XT∗σ′ 总是非负的,因此 w的梯度同号, 这将会导致梯度下降权重更新时出现 公式 字型的下降。该问题相对于上面的神经元饱和问题来说只是个小麻烦,没有那么严重。
- exp 计算耗费比较大
tanh 函数
t a n h ( x ) = 2 σ ( 2 x ) − 1 tanh(x) = 2\sigma(2x)-1 tanh(x)=2σ(2x)−1
- 将神经原压缩到[-1,1]之间
- 是零中心的
ReLu 函数
f ( x ) = m a x ( 0 , x ) f(x) = max(0,x) f(x)=max(0,x)
- 收敛更快
- 只有负半轴会饱和
- 非零中心
- 训练时ReLu的神经元容易死掉,比较脆弱
当一个很大的梯度流经过ReLU的神经原的时候,由于提夫下降,可能会导致权重 w都小于0,这是神经元无法被任何数据再次激活,自此经过这个神经元的梯度都将成为0,这个ReLu单元在训练中将死亡
如果学习率设的比较大,本来大多数大于 0 的 w 更新后都小于 0 了,那么网络中很多神经元就死亡了
Leaky ReLu
- 负区增加了一个斜率,避免死掉的问题
指数线性单元 ELU
介于 ReLU 和 Leaky ReLU 之间,有负饱和的问题,但是对噪声有较强的鲁棒性。
Maxout
- 有更多的参数
数据预处理
Mean Subtraction
如之前所属如果输入的数据X都是正,会导致 sigmoid 函数更新慢
因此可以对数据进行中心化处理。减均值法是数据预处理最常用的形式。它对数据中每个独立特征减去平均值,在每个维度上都将数据的中心都迁移到原点。
虽然经过这种操作,数据变成零中心的,但是仍然只能第一层解决 Sigmoid 非零均值的问题,后面会有更严重的问题。
Normalization
归一化,面都不同维度数值差异较大的问题,使用归一化,使得不同维度的数值范围近似相等
常用的方式有两种:
- 第一种是先对数据做零中心化(zero-centered)处理,然后每个维度都除以其标准差,实现代码为
X /= np.std(X, axis=0)
- 第二种是对每个维度都做归一化,使得每个维度的最大和最小值是 1 和 -1
在图像处理中,由于像素的数值范围几乎是一致的(都在0-255之间),所以进行这个额外的预处理步骤并不是很必要
PCA 可以进行降维处理 和白化
实际上在卷积神经网络中并不会采用PCA和白化
一个常见的错误做法是先计算整个数据集图像的平均值然后每张图片都减去平均值,最后将整个数据集分成训练/验证/测试集。正确的做法是先分成训练/验证/测试集,只是从训练集中求图片平均值,然后各个集(训练/验证/测试集)中的图像再减去这个平均值。
权重初始化
全零初始化
这种做法是错误的,会导致每个神经元有相同的输出,这样方向传播求梯度时,就计算出同样的梯度。
小随机数的初始化
将权重初始化位很小的数值,以此来打破对称性。
其思路是:如果神经元刚开始的时候是随机且不相等的,那么它们将计算出不同的更新,并将自身变成整个网络的不同部分。
W = 0.01 * np.random.randn(D,H)
randn 函数是基于零均值和标准差的一个高斯分布来生成随机数的
使用小随机数的初始化,在简单的网络中效果比较好,但是网络结构比较深的情况下,不一定能得到比较好的结果。
下面一个例子,使用10层,每层500个神经元。
import numpy as np
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
# 假设一些高斯分布单元
D = np.random.randn(1000, 500)
hidden_layer_sizes = [500]*10 # 隐藏层尺寸都是500,10层
nonlinearities = ['tanh']*len(hidden_layer_sizes) # 非线性函数都是用tanh函数
act = {'relu': lambda x: np.maximum(0, x), 'tanh': lambda x: np.tanh(x)}
Hs = {}
for i in range(len(hidden_layer_sizes)):
X = D if i == 0 else Hs[i-1] # 当前隐藏层的输入
fan_in = X.shape[1]
fan_out = hidden_layer_sizes[i]
W = np.random.randn(fan_in, fan_out) * 0.01 # 权重初始化
H = np.dot(X, W) # 得到当前层输出
H = act[nonlinearities[i]](H) # 激活函数
Hs[i] = H # 保存当前层的结果并作为下层的输入
# 观察每一层的分布
print('输入层的均值:%f 方差:%f'% (np.mean(D), np.std(D)))
layer_means = [np.mean(H) for i,H in Hs.items()]
layer_stds = [np.std(H) for i,H in Hs.items()]
for i,H in Hs.items():
print('隐藏层%d的均值:%f 方差:%f' % (i+1, layer_means[i], layer_stds[i]))
# 画图
plt.figure()
plt.subplot(121)
plt.plot(list(Hs.keys()), layer_means, 'ob-')
plt.title('layer mean')
plt.subplot(122)
plt.plot(Hs.keys(), layer_stds, 'or-')
plt.title('layer std')
# 绘制分布图
plt.figure()
for i,H in Hs.items():
plt.subplot(1, len(Hs), i+1)
plt.hist(H.ravel(), 30, range=(-1,1))
plt.show()
只有第一层的均值方差比较好,,输出接近高斯分布,后面几层的均值方差基本为 0 ,反向传播时就会计算出非常小的梯度(因权重的梯度就是层的输入,输入接近0,梯度接近0 ),参数基本不会更新。
如果不用较小的权重,使用 W = np.random.randn(fan_in, fan_out) * 1
tanh 函数的神经原容易饱和,输出为 -1 或 +1 ,梯度为 0
方差较大,均值接近于 0
Xavier/HE初始化(校准方差)
W = np.random.randn(fan_in, fan_out) / np.sqrt(fan_in)
后几层的输入输出接近高斯分布
但是使用ReLu 函数这种关系会被罚破,ReLu函数每层会消除一半的神经元,会有越来越多的神经原失活。