深度学习入门(七):正则化、超参数的验证、迁移学习、端到端的深度学习

本文为《深度学习入门 基于Python的理论与实现》的部分读书笔记,也参考吴恩达深度学习视频
代码以及图片均参考此书

正则化(regularization)

With regularization, training a bigger network almost never hurts.

过拟合(overfit)

发生过拟合(high variance)的原因,主要有以下两个:

  • 模型拥有大量参数、表现力强
  • 训练数据少

解决过拟合问题:

  • Get more data
  • Regularization
  • Find a more appropriate neural network architecture

模型诊断(diagnose high bias/variance)

  • 根据训练集与测试集上的预测误差进行判断
  • 先解决高偏差(high bias)问题(bigger network, train longer…),再解决过拟合问题

为了产生过拟合现象,特地只选择三百个样本作为训练数据,同时网络的隐藏层设为6层,在不进行正则化的情况下进行训练:
在这里插入图片描述
可以看到,网络产生了很明显的过拟合现象

权值衰减(weight decay)(L2 Regularization)

权值衰减通过在学习的过程中对大的权重进行惩罚,来抑制过拟合。很多过拟合原本就是因为权重参数取值过大才发生的。

神经网络的学习目的是减小损失函数的值。这时,为损失函数加上权重的平方范数(L2 范数),就可以抑制权重变大。也就是说,12λW2\frac {1}{2} \lambda W^2加到损失函数上。这里,λ\lambda是控制正则化强度的超参数。λ\lambda设置得越大,对大的权重施加的惩罚就越重。此外, 12λW2\frac {1}{2} \lambda W^2开头的12\frac {1}{2}是用于将12λW2\frac {1}{2} \lambda W^2的求导结果变成λW\lambda W的调整用常量。

  • 注意:使用权值衰减后,在误差反向传播时,得到的权重梯度要加上正则化项的导数λW\lambda W
  • 通常只对WW进行权值衰减,而不对bb进行权值衰减
  • L2范数相当于各个元素的平方和。用数学式表示的话,假设有权重W=(w1,w2,...,wn)W= (w1, w2, . . . , wn),则L2 范数可用w12+w22+..+wn2\sqrt {w_1^2 + w_2^2 + .. + w_n^2}计算出来

为什么权值衰减可以抑制过拟合?

  • 直观:λ\lambda 足够大时WW很小,基本消除了很多神经元的影响,会将high variance变为high bias,因此中间存在一个合适的λ\lambda 可以平衡 variance 和 bias
  • 稍加分析:λ\lambda 足够大时WW很小,因此输出WX+bWX + b也很小,若激活函数使用sigmoid,则激活值基本都在线性区内,整个网络就变成了一个线性网络,因此可以抑制过拟合

伪代码:

# forward
for each layer:
	loss += 0.5 * lambda * np.sum(w**2)
# backward
for each layer:
	dw += lambda * w

部分实现代码(用于理解权值衰减的流程,完整代码将在之后的博客中给出):

# forward
weight_decay = 0
for idx in range(1, self.hidden_layer_num):
    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
# backward
grads = {}
for idx in range(1, self.hidden_layer_num):
    grads['W' + str(idx)] = self.layers['Affine' + str(idx)].dW + self.weight_decay_lambda * self.params['W' + str(idx)]
    grads['b' + str(idx)] = self.layers['Affine' + str(idx)].db
  • 使用权值衰减之后过拟合现象得到了明显抑制
    在这里插入图片描述

Dropout

参考:https://arxiv.org/abs/1207.0580

如果网络的模型变得很复杂,只用权值衰减就难以应对过拟合了。在这种情况下,经常使用Dropout

当然,如果没有过拟合,就不要使用Dropout(会损失精度)

Dropout 是一种在学习的过程中随机删除神经元的方法,一般用于全连接层中。训练时,随机选出隐藏层的神经元,然后将其删除。被删除的神经元不再进行信号的传递。训练时,每传递一次数据,就会随机选择要删除的神经元。然后,测试时,虽然会传递所有的神经元信号,但是对于各个神经元的输出,要乘上训练时的删除比例后再输出(保持该层输出的期望值不变)。

在这里插入图片描述
为什么Dropout管用?

  • 使网络无法依赖于某一个神经元,而必须把权重分摊到各个神经元上,相当于减小了W2||W||^2
  • 可以看作是多模型的平均,减少了神经元间的依赖

This prevents complex co-adaptations in which a feature detector is only helpful in the context of several other specific feature detectors. Instead, each neuron learns to detect a feature that is generally helpful for producing the correct answer given the combinatorially large variety of internal contexts in which it must operate.

Dropout的缺点:

  • 使用Dropout后损失函数并不会在每次迭代中下降,失去了明确的意义。因此,在检查网络正确性时,需要先去掉Dropout层。

dropout_ratio的选择:

  • 一般选0.5
  • 也可以为不同的层设置不同的比例,例如权重矩阵更大的那一层可以考虑增大dropout_ratio(权重矩阵更大则更容易过拟合)

代码实现:

class Dropout:
    def __init__(self, dropout_ratio=0.5): # dropout_ratio一般选0.5
        self.dropout_ratio = dropout_ratio
        self.mask = None

    def forward(self, x, train_flg=True):
        if train_flg == True:
            retain_prob = 1 - self.dropout_ratio
            self.mask = np.random.binomial(1, p=retain_prob, size=x.shape)
            # self.mask = np.random.randn(*x.shape) > self.dropout_ratio         
            return x * self.mask
        else:
            return x * (1 - self.dropout_ratio)

    def backward(self, dout):
        return dout * self.mask
  • 使用Dropout之后过拟合现象也得到了抑制
    在这里插入图片描述

其他正则化方法

数据扩充(Data Augmentation)

将图片进行水平翻转(flip the images horizontally)(要确保不需要考虑图片的对称性)、随意裁剪(take random crops)、旋转、在垂直或水平方向上的移动、调整亮度、放大缩小…

Early stopping

在这里插入图片描述缺点:

  • 正常在训练网络的过程中,可以看作是分步进行两个操作:减小损失函数值、避免过拟合。这两个过程不是并行的,这种模式称为正交化(orthogonalization)。然而,early stopping却将这两个过程糅合在了一起

优点:

  • 少了一些超参数的选择,比如权值衰减时用到的λ\lambda

超参数的验证

划分数据集

之前我们使用的数据集分成了训练数据和测试数据,训练数据用于学习,测试数据用于评估泛化能力。由此,就可以评估是否只过度拟合了训练数据(是否发生了过拟合),以及泛化能力如何等。下面我们要对超参数设置各种各样的值以进行验证。这里要注意的是,不能使用测试数据评估超参数的性能。这一点非常重要,但也容易被忽视。为什么不能用测试数据评估超参数的性能呢?这是因为如果使用测试数据调整超参数,超参数的值会对测试数据发生过拟合。因此,调整超参数时,必须使用超参数专用的确认数据。用于调整超参数的数据,一般称为验证数据(validation data)

  • relatively small set \rightarrow train/dev/test: 60%/20%/20%
  • much larger set \rightarrow dev, test比例可降到更低,因为已足够对模型好坏进行评估

caution: dev, test, train set must come from the same distribution!

分割数据集之前,要先打乱数据与标签,因为数据集中的数据可能存在偏向(比如,数据从“0”到“10”按顺序排列等)

shuffle_data=True
(x_train, t_train), (x_test, t_test) = load_mnist(shuffle_data=True)
# 分割验证数据
validation_rate = 0.20
validation_num = int(x_train.shape[0] * validation_rate)
x_val = x_train[:validation_num]
t_val = t_train[:validation_num]
x_train = x_train[validation_num:]
t_train = t_train[validation_num:]

超参数的最优化

进行超参数的最优化时,逐渐缩小超参数的“好值”的存在范围非常重要。所谓逐渐缩小范围,是指一开始先大致设定一个范围,从这个范围中随机选出一个超参数(采样),用这个采样到的值进行识别精度的评估;然后,多次重复该操作,观察识别精度的结果,根据这个结果缩小超参数的“好值”的范围。通过重复这一操作,就可以逐渐确定超参数的合适范围。

有报告显示,在进行神经网络的超参数的最优化时,与网格搜索等有规律的搜索相比,随机采样的搜索方式效果更好。这是因为在多个超参数中,各个超参数对最终的识别精度的影响程度不同。

Hyperparameters:

  • Most important: α\alpha(learning rate)
  • Second important: β\beta(momentum), #hiddenunits, mini-batch size
  • Third important: #layers, λ\lambda(learning rate decay)

超参数的范围只要“大致地指定”就可以了。所谓“大致地指定”,是指像0.001(10−3)到1000(103)这样,以“10 的阶乘”的尺度指定范围(也表述为“用对数尺度(log scale)指定”)

下面举两个例子说明为什么不能使用线性尺度:

  • 如果采用线性尺度搜索α\alpha: 0.001 ~ 1,则90%的搜索都会集中在0.1 ~ 1,显然不合理。
  • 如果采用线性尺度搜索β\beta: 0.9 ~ 0.999。当在0.9000 ~ 0.9005范围内搜索时,11β10\frac {1}{1 - \beta} \approx 10,而在0.9990 ~ 0.9995范围内搜索时,11β1000\frac {1}{1 - \beta} \approx 1000 ~ 20002000,用线性范围搜索显然不合理,因为我们要选取的其实是合适的11β\frac {1}{1 - \beta}的值

在超参数的最优化中,要注意的是深度学习需要很长时间(比如,几天或几周)。因此,在超参数的搜索中,需要尽早放弃那些不符合逻辑的超参数。于是,在超参数的最优化中,减少学习的epoch,缩短一次评估所需的时间是一个不错的办法。
在这里插入图片描述

这里介绍的超参数的最优化方法是实践性的方法。在超参数的最优化中,如果需要更精炼的方法,可以使用贝叶斯最优化(Bayesian optimization)。贝叶斯最优化运用以贝叶斯定理为中心的数学理论,能够更加严密、高效地进行最优化。详细内容请参考论文“Practical Bayesian Optimization of Machine Learning Algorithms”等。

import sys
file_path = __file__.replace('\\', '/')
dir_path = file_path[: file_path.rfind('/')] # 当前文件夹的路径
pardir_path = dir_path[: dir_path.rfind('/')]
sys.path.append(pardir_path) # 添加上上级目录到python模块搜索路径

import numpy as np
from dataset.mnist import load_mnist
from layer.multi_layer_net import MultiLayerNet
from trainer.trainer import Trainer
import matplotlib.pyplot as plt 

(x_train, t_train),  (x_test, t_test) = load_mnist(normalize=True, flatten=False, one_hot_label=True, shuffle_data=True)

# 分割验证数据
validation_rate = 0.20
validation_num = int(x_train.shape[0] * validation_rate)
x_val = x_train[:validation_num]
t_val = t_train[:validation_num]
x_train = x_train[validation_num:]
t_train = t_train[validation_num:]

pkl_file_name = dir_path + '/hyperparameter_optimization.pkl'
fig_name = dir_path + '/hyperparameter_optimization.png'

def __train(lr, weight_decay, epocs=2):
    net = MultiLayerNet(784, [100, 100, 100, 100, 100, 100], 10,
                activation='relu', weight_init_std='relu', weight_decay_lambda=weight_decay, 
                use_dropout=False, dropout_ration=0.5, use_batchnorm=True, 
                pretrain_flag=False, pkl_file_name=pkl_file_name)
    trainer = Trainer(net, x_train, t_train, x_test, t_test,
                    epochs=epocs, mini_batch_size=100,
                    optimizer='SGD', optimizer_param={'lr':lr}, 
                    save_model_flag=True, pkl_file_name=pkl_file_name, plot_flag=False, fig_name=fig_name,
                    evaluate_sample_num_per_epoch=None, verbose=True)
    trainer.train()

    return trainer.test_acc_list, trainer.train_acc_list

# 之后进行超参数最优化的搜索
optimization_trial = 100
results_val = {}
results_train = {}
for _ in range(optimization_trial):
    # 指定搜索的超参数的范围===============
    weight_decay = 10 ** np.random.uniform(-8, -4)
    lr = 10 ** np.random.uniform(-6, -2)
    # ================================================

    val_acc_list, train_acc_list = __train(lr, weight_decay)
    print("val acc:" + str(val_acc_list[-1]) + " | lr:" + str(lr) + ", weight decay:" + str(weight_decay))
    key = "lr:" + str(lr) + ", weight decay:" + str(weight_decay)
    results_val[key] = val_acc_list
    results_train[key] = train_acc_list

# 绘制图形========================================================
print("=========== Hyper-Parameter Optimization Result ===========")
graph_draw_num = 20
col_num = 5
row_num = int(np.ceil(graph_draw_num / col_num))
i = 0

for key, val_acc_list in sorted(results_val.items(), key=lambda x:x[1][-1], reverse=True):
    print("Best-" + str(i+1) + "(val acc:" + str(val_acc_list[-1]) + ") | " + key)

    plt.subplot(row_num, col_num, i+1)
    plt.title("Best-" + str(i+1))
    plt.ylim(0.0, 1.0)
    if i % 5: plt.yticks([])
    plt.xticks([])
    x = np.arange(len(val_acc_list))
    plt.plot(x, val_acc_list)
    plt.plot(x, results_train[key], "--")
    i += 1

    if i >= graph_draw_num:
        break

plt.savefig(fig_name)
plt.show()

在这里插入图片描述

=========== Hyper-Parameter Optimization Result ===========
Best-1(val acc:0.9178) | lr:0.009005557486889807, weight decay:1.7477510887585676e-07
Best-2(val acc:0.9147) | lr:0.008230729959088037, weight decay:4.0741675278124096e-05
Best-3(val acc:0.9137) | lr:0.007172321239239096, weight decay:3.279148370122038e-05
Best-4(val acc:0.9106) | lr:0.007784860782344955, weight decay:2.6963471504299467e-05
Best-5(val acc:0.9103) | lr:0.00639415111314913, weight decay:2.5875411574553633e-07
Best-6(val acc:0.9096) | lr:0.0067713810843289994, weight decay:1.4721636892262977e-08
Best-7(val acc:0.9009) | lr:0.006278021397985206, weight decay:1.347044302843336e-08
Best-8(val acc:0.886) | lr:0.004757015189713183, weight decay:3.469706246467051e-05
Best-9(val acc:0.8856) | lr:0.003900241211695265, weight decay:3.3527006586550624e-08
Best-10(val acc:0.8786) | lr:0.004096508041494537, weight decay:7.797544480165376e-06
Best-11(val acc:0.8736) | lr:0.0035426486270627756, weight decay:2.6716950639718e-06
Best-12(val acc:0.843) | lr:0.0029865433537836217, weight decay:1.168246937591811e-07
Best-13(val acc:0.8405) | lr:0.002651462378045705, weight decay:9.754264464435194e-07
Best-14(val acc:0.8206) | lr:0.0022564421780149114, weight decay:1.7280502179151947e-06
Best-15(val acc:0.7975) | lr:0.002067204045559343, weight decay:6.48318010753941e-08
Best-16(val acc:0.7957) | lr:0.0019221274810548542, weight decay:1.2329400973635888e-06
Best-17(val acc:0.7923) | lr:0.0017003621475225061, weight decay:6.848766914357895e-07
Best-18(val acc:0.7912) | lr:0.002028435943575853, weight decay:5.611188782800319e-05
Best-19(val acc:0.7882) | lr:0.0017122199198167435, weight decay:3.1379461173155134e-06
Best-20(val acc:0.7762) | lr:0.002026173247734754, weight decay:4.745680808901564e-07

从这个结果可以看出,学习率在0.001 到0.01、权值衰减系数在10710^{−7}10610^{−6} 之间时,学习可以顺利进行。

迁移学习(Transfer Learning)

保留前面层的参数,只修改最后一层或几层的参数(数据越多,修改的层数越多),并构造新的输出层。这样做的话就相当于只训练一个浅层网路。(大部分框架都可以设置来不训练某些层的参数)

适用于:

  • 被迁移问题的数据较多,而迁移问题的数据较少
  • 两个问题都有相同的输入XX
  • 被迁移问题的低级特征(low level features)对迁移问题有帮助

例如对于图像分类问题,卷积网络的前两三层的作用是非常类似的,都是提取图像的边缘信息。因此为了保证模型训练中能够更加稳定,一般会固定与训练网络的前两三个卷积层不进行参数的学习

端到端的深度学习(End-to-end deep learning)

  • The tradition way – small data set
    Audio\rightarrowExtract features\rightarrowPhonemes\rightarrowWords\rightarrowTranscript
  • The hybrid way – medium data set
    Audio\rightarrowPhonemes\rightarrowWords\rightarrowTranscript
  • The end-to-end deep learning way - large data set
    Audio\rightarrowTranscript

没有大量数据支持端到端学习时可以拆分任务为多个小任务(每个小任务都有足够的数据)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章