过拟合指的是只能拟合训练数据,但不能很好地拟合不包含训练数据的其他数据。神经网络要求模型能够具有较高的泛化能力,也就是对不包含训练数据的未观测数据也能进行正确识别。所以在训练表现力强的模型同时,抑制过拟合的技巧也很重要。
防止过拟合的方法:(1)人为增加训练数据集;(2)正则化 regularization(权值衰减);(3)Dropout;(4)早停(early stopping)
发生过拟合的原因主要有:
所以通过增加训练数据可以抑制过拟合,提高模型的泛化能力。例如增加图像数据集可以对原始图像进行旋转、镜像。
权值衰减是一直以来经常被使用的一种抑制过拟合的方法。该方法通过在学习过程中对大的权重进行惩罚,来抑制过拟合。因为很多过拟合原本就是因为权重参数取值过大才发生的。
神经网络的习目的是减小损失函数的值。这时,例如为损失函数加上权值的平方范数(L2范数)。这样就可以抑制权重变大。用符号表示的话,如果将权重记为W,L2范数的权值衰减就是,然后将加到损失函数上。这里,是控制正则化强度的超参数。设置的越大,对大的权重施加惩罚就越重。此外,开头的是用于将的求导结果变成的调整用常量。
对于所有权重,权值衰减方法都会为损失函数加上。因此在求权重梯度的计算中,要为之前的误差反向传播算法的结果加上正则化项的导数。正则化项可以使用L1,L2,L∞,这里使用比较常用的L2。
所以用python搭建神经网络时,损失函数和计算梯度的代码要改为:
def loss(self, x, t):
# 前向传播
y = self.predict(x)
weight_decay = 0
# 计算所有权重的L2范数
for idx in range(1, self.hidden_layer_num + 2):
W = self.params['W' + str(idx)]
weight_decay += 0.5 * self.weight_decay_lambda * np.sum(W ** 2)
return self.last_layer.forward(y, t) + weight_decay
def gradient(self, x, t):
# 计算损失函数
self.loss(x, t)
# backward
dout = 1
dout = self.last_layer.backward(dout)
layers = list(self.layers.values())
layers.reverse()
for layer in layers:
dout = layer.backward(dout)
# 梯度
grads = {}
for idx in range(1, self.hidden_layer_num+2):
grads['W' + str(idx)] = self.layers['Affine' + str(idx)].dW + self.weight_decay_lambda * self.layers['Affine' + str(idx)].W
grads['b' + str(idx)] = self.layers['Affine' + str(idx)].db
return grads
上面介绍的损失函数加上权重的L2范数的权值衰减方法,在某种程度上可以抑制过拟合。但是,如果网络模型变得复杂,只有权值衰减就显得力不从心了。在这种情况下,往往使用Dropout方法。
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
def backward(self, dout):
return dout * self.mask
说明:每次正向传播时,self.mask中都会以False的形式保存要删除的神经元。self.mask会随机生成和x形状相同的数组,并将值比dropout_ratio大的元素设为True。传播行为和RuLU相同。
在搭建神经网络时可以这样使用Dropout:
def __init__(self, input_size, hidden_size_list, output_size,
activation='relu', weight_init_std='relu', weight_decay_lambda=0,
use_dropout = False, dropout_ration = 0.5):
self.input_size = input_size
self.output_size = output_size
self.hidden_size_list = hidden_size_list
self.hidden_layer_num = len(hidden_size_list)
self.use_dropout = use_dropout
self.weight_decay_lambda = weight_decay_lambda
self.params = {}
# 权重初始化方法
self.__init_weight(weight_init_std)
# 每层网络生成
activation_layer = {'sigmoid': Sigmoid, 'relu': Relu}
self.layers = OrderedDict()
for idx in range(1, self.hidden_layer_num+1):
self.layers['Affine' + str(idx)] = Affine(self.params['W' + str(idx)],
self.params['b' + str(idx)])
if self.use_dropout:
self.layers['Dropout' + str(idx)] = Dropout(dropout_ration)
idx = self.hidden_layer_num + 1
self.layers['Affine' + str(idx)] = Affine(self.params['W' + str(idx)], self.params['b' + str(idx)])
self.last_layer = SoftmaxWithLoss()
也就是一个全连接层后面使用Dropout。
4、早停(early stopping)
将数据分成训练集和验证集,训练集用来计算梯度、更新连接权和阈值;验证集用来估计误差。若训练集误差降低但验证集误差升高,则停止训练,同时返回具有最小验证集误差的连接权和阈值。
——引自周志华老师《机器学习》一书
机器学习中经常使用集成学习。所谓集成学习,就是让多个模型单独进行学习,推理时再取多个模型的输出的平均值。用神经网络的语境来说,比如,准备5个结构相同的网络,分别进行学习,测试时,以这5个网络的输出的平均值作为答案。通过实验可以发现,使用集成学习,神经网络的识别精度可以提高好几个百分点。
这个集成学习与Dropout有密切的关系。这是因为可以将Dropout理解为,通过在学习过程中随机删除神经元,从而每一次都让不同的模型进行学习。并且,推理时,通过对神经元的输出乘以删除比例(比如0.5),可以取模型的平均值。也就是说,可以理解成,Dropout将集成学习的效果通过一个网络实现了。