一、Dropout的概念
二、Dropout的使用
随机失活。随机是指:有一定的概率让神经元失去活性。失活:权重等于0.
看如下示意图:
左边是正常的全连接神经网络。右边是加了dropout,打×的是失活了。
这就可以让神经元学习到更鲁棒的特征,减轻过度的依赖性,从而缓解了过拟合,降低方差,达到正则化的效果。
这个操作也会使得我们的模型更加多样化,因为每一次进行前向传播,神经元都是随机失活,所训练的模型都是不一样的。
为什么可以得到正则化的效果呢?可以从两个角度进行思考。
1. 从依赖性角度。一个神经元可以接受上层的输出,如果特别它特别依赖与某个特征,它可能会给这个的权值很大,而其他的权重很小。加了dropout之后,就不知道上一层哪些神经元会出现,所以就不会对某些神经元过度依赖。这时候就需要把经历平均地放到前面的神经元上。
2. 从数值的角度考虑。所有的权重在数值上是差不多大小的。这样就实现了权重的约束。
使用过程中,需要注意:
在测试的时候,需要乘以一个系数。
我的理解是,比如我跟张三一起做一个项目。今天我来了,张三不来,我干的很好。明天,我不来,张三来了,他也干的很好。干了很多天之后,最后结果就是我和张三都能单独的做项目。突然有一天我俩一起来了。。。我俩一起干半天估计就能把一天的活干完。
我们来看一下pytorch提供的dropout层:
例1:dropout的使用
#dropout_regularization.py
# -*- coding:utf-8 -*- """ @file name : dropout_regularization.py @brief : dropout 使用实验 """ import torch import torch.nn as nn import matplotlib.pyplot as plt import sys, os hello_pytorch_DIR = os.path.abspath(os.path.dirname(__file__)+os.path.sep+".."+os.path.sep+"..") sys.path.append(hello_pytorch_DIR) from tools.common_tools import set_seed from torch.utils.tensorboard import SummaryWriter set_seed(1) # 设置随机种子 n_hidden = 200 max_iter = 2000 disp_interval = 400 lr_init = 0.01 # ============================ step 1/5 数据 ============================ def gen_data(num_data=10, x_range=(-1, 1)): w = 1.5 train_x = torch.linspace(*x_range, num_data).unsqueeze_(1) train_y = w*train_x + torch.normal(0, 0.5, size=train_x.size()) test_x = torch.linspace(*x_range, num_data).unsqueeze_(1) test_y = w*test_x + torch.normal(0, 0.3, size=test_x.size()) return train_x, train_y, test_x, test_y train_x, train_y, test_x, test_y = gen_data(x_range=(-1, 1)) # ============================ step 2/5 模型 ============================ class MLP(nn.Module): def __init__(self, neural_num, d_prob=0.5): super(MLP, self).__init__() self.linears = nn.Sequential( nn.Linear(1, neural_num), nn.ReLU(inplace=True), nn.Dropout(d_prob), #dropout需要放在需要dropout的网络层的前面。也就是对下面这个Linear()进行dropout。(这个地方不确定,存疑) nn.Linear(neural_num, neural_num), nn.ReLU(inplace=True), nn.Dropout(d_prob), nn.Linear(neural_num, neural_num), nn.ReLU(inplace=True), nn.Dropout(d_prob), #通常在输出层,是不加dropout的。但是由于这个任务比较简单,所以加了dropout。 nn.Linear(neural_num, 1), ) def forward(self, x): return self.linears(x) #初始化两个网络进行对比,一个采用dropout,一个不采用dropout net_prob_0 = MLP(neural_num=n_hidden, d_prob=0.) #被舍弃的概率是0,也就是不采用dropout net_prob_05 = MLP(neural_num=n_hidden, d_prob=0.5) #被舍弃的概率是0.5 # ============================ step 3/5 优化器 ============================ optim_normal = torch.optim.SGD(net_prob_0.parameters(), lr=lr_init, momentum=0.9) optim_reglar = torch.optim.SGD(net_prob_05.parameters(), lr=lr_init, momentum=0.9) # ============================ step 4/5 损失函数 ============================ loss_func = torch.nn.MSELoss() # ============================ step 5/5 迭代训练 ============================ writer = SummaryWriter(comment='_test_tensorboard', filename_suffix="12345678") for epoch in range(max_iter): pred_normal, pred_wdecay = net_prob_0(train_x), net_prob_05(train_x) loss_normal, loss_wdecay = loss_func(pred_normal, train_y), loss_func(pred_wdecay, train_y) optim_normal.zero_grad() optim_reglar.zero_grad() loss_normal.backward() loss_wdecay.backward() optim_normal.step() optim_reglar.step() if (epoch+1) % disp_interval == 0: # 因为dropout在训练阶段和测试阶段,操作功能是不同的。训练阶段要失活,测试阶段都要保留。所以要设置一下,表示网络要采用测试状态。 net_prob_0.eval() net_prob_05.eval() # 可视化 for name, layer in net_prob_0.named_parameters(): writer.add_histogram(name + '_grad_normal', layer.grad, epoch) writer.add_histogram(name + '_data_normal', layer, epoch) for name, layer in net_prob_05.named_parameters(): writer.add_histogram(name + '_grad_regularization', layer.grad, epoch) writer.add_histogram(name + '_data_regularization', layer, epoch) test_pred_prob_0, test_pred_prob_05 = net_prob_0(test_x), net_prob_05(test_x) # 绘图 plt.clf() plt.scatter(train_x.data.numpy(), train_y.data.numpy(), c='blue', s=50, alpha=0.3, label='train') plt.scatter(test_x.data.numpy(), test_y.data.numpy(), c='red', s=50, alpha=0.3, label='test') plt.plot(test_x.data.numpy(), test_pred_prob_0.data.numpy(), 'r-', lw=3, label='d_prob_0') plt.plot(test_x.data.numpy(), test_pred_prob_05.data.numpy(), 'b--', lw=3, label='d_prob_05') plt.text(-0.25, -1.5, 'd_prob_0 loss={:.8f}'.format(loss_normal.item()), fontdict={'size': 15, 'color': 'red'}) plt.text(-0.25, -2, 'd_prob_05 loss={:.6f}'.format(loss_wdecay.item()), fontdict={'size': 15, 'color': 'red'}) plt.ylim((-2.5, 2.5)) plt.legend(loc='upper left') plt.title("Epoch: {}".format(epoch+1)) plt.show() plt.close() #使用完测试状态以后,要把网络设置回训练状态。 net_prob_0.train() net_prob_05.train()
结果:
可以看到不带dropout的红线过拟合了。加了0.5概率的dropout的蓝色线,没有过拟合。
我们再来观察一下方差的变化:
可以看到不带dropout的时候,方差没有变化。
加了dropout之后,收缩了一些。
说明dropout实现了类似L2的功能,收缩权重。
其实在pytorch中dropout中,有一个实现的小细节。
pytorch在实现dropout的时候,会把权重除以(1-p)。p是失活的概率。
这样做的话,在测试的时候就不需要再对权重进行缩放了。这样操作更方便。
比如p=0.3。
假设原来训练出来的权重为x,测试的时候需要的权重应该为y = x*0.7。怎么才能让y = w呢?当然是x = w/0.7了。
下面通过代码,观察这个小细节:
(先略吧,我觉得有问题。)
