读书笔记-深度学习入门
误差的反向传播
误差的反向传播是正向传播的逆向推导,将误差的结果分摊到每个参数,作为其修改的依据。
计算图
计算图的优点:
- 局部计算:无论全局设计多么复杂,每个步骤所要做的都是对象节点的局部计算
- 计算图可以保存每个节点的计算结果
- 计算图可以方向传播高效计算导数
每个节点的运算看作一个函数,最后结果是所有函数运算的复合结果
链式法则
如果某个函数由复合函数表示,则该复合函数的导数可以用复合函数的各个函数的导数的乘积表示
加法的反向传播
#加法层的实现
class AddLayer:
def __init__(self):
pass
def forward(self, x, y):
out = x + y
return out
def backward(self, dout):
dx = dout * 1
dy = dout * 1
return dx, dy
乘法的反向传播
#乘法层的实现
class MulLayer:
def __init__(self):
self.x = None
self.y = None
def forward(self, x, y):
self.x = x
self.y = y
out = x * y
return out
def backward(self, dout):
dx = dout * self.y
dy = dout * self.x
return dx, dy
加法的反向传播与乘法的反向传播的不同之处
加法的反向传播只是将上游的值传给下游,并不需要正向传播输入信号。但是乘法的反向传播需要正向传播时的输入信号。所以实现乘法节点的反向传播时,需要保存正向传播的输入信号。
ReLU层函数的实现
def Relu:
def __init__(self):
self.mask = None
def forward(self, x):
self.mask = (x <= 0) #存的是bool值 x<=0为true,否则为false
out = x.copy()
out[self.mask] = 0 #将小于0的置为0
return out
def backward(self, y):
dout[self.mask] = 0
dx = dout
return dx
sigmoid层函数的实现
下面的推导过程来源于百度
def Sigmoid:
def __init__(self):
self.out = None
def forward(self, x):
out = 1 / (1 + np.sxp(-x))
self.out = out
return out
def backward(self, dout):
dx = dout * (1.0 - self.out) * self.out
return dx
Affine层
Affine层:计算加权信号的总和加偏置,计算仿射变换(包括一次线性变换和一次平移,对应神经网络的加权和偏置运算)的层
推导式
Affine层计算图
批处理版本的Affine层
输入的x为多维
#Affine层的实现
def Affine:
def __init__(self, w, b):
self.w = w
self.b = b
self.x = None
self.dw = None
self.db = None
def forward(self, x):
self.x = x
out = np.dot(x, self.w) + self.b
return out
def backward(self, dout):
dx = np.dot(dout, self.w.T)
self.dw = np.dot(self.x.T, dout)
self.db = np.sum(dout, axis = 0) #对第0轴方向上的数据求和
return dx
Softmax-with-Loss层
Softmax函数和交叉熵误差层
神经网络的处理过程有两种:学习和推理,推理层不需要softmax层,因为softmax是对Affine层结果的处理,推理在只有一个输出的情况下只需要知道所有输出结果中的最大值,Affine层已经足够,而学习阶段则需要softmax层。
图中最后反向传播的结果是{y1-t1, y2- t2, y3-t3},这个结果直接显示出了神经网络的输出和监督标签的误差,能得到这样的结果并不是偶然,而是为了得到这样的结果而设计的交叉熵误差函数。
class SoftmaxWithLoss:
def __init(self):
self.loss = None
self.y = None
self.t = None
def forward(self, x, t):
self.t = t
self.y = softmax(x) #上一篇博文已经实现
self.loss = cross_entropy_error(self.y, self.t)
return self.loss
def backward(self, dout = 1):
batch_size = self.t.shape(0)
dx = (self.y - self.t)/batch_size
return dx
与学习有关的技巧
参数的更新
SGD的缺点
如果输入的函数的形状非均向,搜索路径会变得非常低效,因为梯度没有指向最小值的方向
Momentum
class Momentum:
def __init__(self, lr = 0.01, momentum = 0.9):
self.lr = lr
self.momentum = momentum #相当于物理中的阻力,承担减小速度的任务
self.v = None
def update(self, params, grads):
if self.v is None:
self.v = {}
for key, val in params.items():
self.v[key] = np.zeros_like(val)
for key in params.keys():
self.v[key] = self.momentum * self.v[key] - self.lr * grads[key]
params[key] += self.v[key]
x轴上的受力非常的小,但是一直在同一个方向上受力,反过来y虽然受的力很大,但是方向却会发生改变,它们会相互抵消,所以y轴上的速度不稳定。这样可以减弱方向不均匀带来的影响。
AdaGrad
基于学习率衰减的思想,参数的元素中变动较大的元素的学习率将变小,也就是说,可以调整学习的幅度。
class AdaGrad:
def __init__(self, lr = 0.01):
self.lr = lr
self.h = None
def update(self, params, grads):
if self.h is None:
self.h = {}
for key, val in params.items():
self.h[key] = np.zeros_like(val)
for key in params.keys():
self.h[key] += grads[key] * grads[key]
params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)
Adam
- 组合了前俩个方法的优点
- 偏置校正
Adam的3个超参数:
- 学习率α
- 一次momentum系数 β1,标准设定值为0.9
- 二次momentum系数 β2,标准设定值为0.999
比较
以上3个方法各有特点,但是没有可以在所有问题中都表表现良好的方法,因此,应该根据具体的场景和实验结果选择合适的方法。
权重的初始化
权重的初始值非常重要。
随着层次加深,梯度消失的问题可能会更加严重。
- 对于sigmoid和tanh等S型函数,初始值使用Xavier初始值。Xavier是标准差为
( 1 / n ) \sqrt (1/n) (1/n)
的高斯分布。 - 对于Relu,权重初始值使用He初始值。He是
-
(
2
/
n
)
\sqrt (2/n)
(2/n)
的高斯分布。
权重的初始值可以设置为0吗
答案是不可以,因为如果将权重的初始值设置为0,那么在误差的反向传播法中,所有的权重值都会进行相同的更新。因为输入层的权重为0,所以第2层的神经元中全部输入相同的值,这意味着反向传播的过程中,第二层的权重全部会进行相同的更新,因此,权重会被更新为相同的值,并拥有的对称的值,这使得神经网络拥有许多不同权重的意义丧失了
Batch Normalization
为了使各层拥有适当的广度,“强制性地”调整激活值地分布,具体而言,就是使数据地均值为0,方差为1 地正规化。
优点:
- 可以使学习快速进行
- 不那么依赖初始值
- 抑制过拟合
batch norm的计算图
正则化
过拟合
只能拟合训练数据,不能很好地拟合不包含在训练数据中的其他数据。
产生原因:
- 模型拥有大量参数,表现力强
- 训练数据少
权值衰减
对大的权重进行惩罚.
例如为损失函数加上权重的平方函数(L2范数)。
L2范数相当于各个元素的平方和。L1是各个元素的绝对值之和,L∞相当于各个元素中绝对值最大的那个。
Dropout
随机删除神经元,Dropout将集成学习的效果(模拟地)通过一个网络实现了。
class Dropout:
def __init__(self, dropout_ratio = 0.5):
self.dropout_ratio = dropout_ratio
self.mask = None
def forward(self, x, train_flg = True):
if train_flg:
self.mask = np.random.rand(*x.shape) > self.dropout_ratio
return x * self.mask
else:
return x * (1.0 - self.dropout_ratio)
def backward(self, dout):
return dout * self.mask
超参数的验证
比如各层的神经元的数量,batch的大小,参数更新时的学习率和权值衰减。如果这些超参数没有设置合适的值,模型的性能就会很差。
验证数据
用于调整超参数的数据。
超参数的最优化
逐渐缩小超参数的“好值”的存在范围。
步骤:
- 设置超参数的范围
- 从设定的超参数范围中随机取样
- 使用步骤1中采样到的超参数的值进行学习,通过验证数据评估识别精度(但是要将epoch设置得很小)
- 重复步骤1和步骤2,根据它们识别精度的结果,缩小超参数的范围
除上述方法之外,还可以使用贝叶斯最优化