01月28, 2020

深度学习总结(一)RNN中使用Dropout防止过拟合

为什么使用Dropout

Dropout的本质是每个batch训练一个完整网络的子网,然后测试的时候使用这些网络的集成,所以必须去掉

Dropout会产生收缩权重平方范数的效果,即压缩权重。并完成一些预防过拟合的外层正则化。Dropout被正式作为一种正则化的替代形式,L2对不同权重的衰减是不同的,它取决于倍增的激活函数的大小。Dropout的功能类似于L2正则化,与L2正则化不同的是,被应用的方式不同dropout也会有所不同,甚至更适用于不同的输入范围。

不同层的keep-prob可以不一样,参数多的层,keep-prob可以设置小一些,少的可以设置大一些,或者为1。

如果你觉得某一层比其它层更容易过拟合,你可以把该层的keep-drop设得比其它层低一些,但是你需要使用交叉验证来搜索更多的超参数。你也可以有的层用dropout,有的不用,这样在应用dropout的层只含有一个超级参数。

Dropout主要用在计算机视觉领域,因为数据量不够,容易过拟合,需要dropout。

怎样使用dropout

tensorflow

def add_layer(inputs, in_size, out_size, layer_name, activation_function=None, ):
    # add one more layer and return the output of this layer
    Weights = tf.Variable(tf.random_normal([in_size, out_size]))
    biases = tf.Variable(tf.zeros([1, out_size]) + 0.1, )
    Wx_plus_b = tf.matmul(inputs, Weights) + biases
    # here to dropout
    Wx_plus_b = tf.nn.dropout(Wx_plus_b, keep_prob)
    if activation_function is None:
        outputs = Wx_plus_b
    else:
        outputs = activation_function(Wx_plus_b, )
    tf.histogram_summary(layer_name + '/outputs', outputs)
    return outputs

pytorch

net_dropout = torch.nn.Sequential(
    torch.nn.Linear(1, N_HIDDEN),
    torch.nn.Dropout(0.5),           # drop 50% neurons
    torch.nn.ReLU(),
    torch.nn.Linear(N_HIDDEN, N_HIDDEN),
    torch.nn.Dropout(0.5),
    torch.nn.ReLU(),
    torch.nn.Linear(N_HIDDEN, 1)
)

怎样在测试时去掉dropout

tensorflow

(1) 通过命令行参数的训练状态来控制
is_training = (mode == tf.estimator.ModeKeys.TRAIN)

with tf.variable_scope("loss"):
    if is_training:
      # I.e., 0.1 dropout
      output_layer = tf.nn.dropout(output_layer, keep_prob=0.9)

(2) 通过feed_dict来控制
prob = tf.placeholder_with_default(1.0, shape=())
layer = tf.nn.dropout(layer, prob)
---------------------------
sess.run(train_step, feed_dict={prob: 1})

pytorch

import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
import torchvision
import torchvision.datasets as dsets
from torchvision.transforms import transforms
import cv2


class Net(nn.Module):

    def __init__(self):

        super(Net, self).__init__()
        self.fc = nn.Linear(5, 5)
        self.dp = nn.Dropout(0.5)

    def forward(self, x):

        x = self.fc(x)
        x = self.dp(x)

        return x


if __name__ == '__main__':

    x = torch.FloatTensor([1]*5)
    z = torch.FloatTensor([1]*5)
    print(x)
    net = Net()
    criterion = nn.MSELoss()
    optim = torch.optim.SGD(net.parameters(), 0.1)

    optim.zero_grad()
    net.train()
    y = net(x)
    loss = criterion(y, z)
    loss.backward()
    optim.step()
    print(y)
    print(net(x))  
    # net(x) is not the same as y -> dropout() changes result every time 

    net.eval()  
    optim.zero_grad()
    y = net(x)  
    loss = criterion(y, z)
    loss.backward()
    optim.step()
    print(y)
    print(net(x))
    # eval() or mode(train=False) only changes the state of some modules, e.g., dropout, but do not disable loss back-propogation. 
    # By setting train=False, dropout() does not work and is temporarily removed from the chain of update.

    with torch.no_grad():
        optim.zero_grad()
        y = net(x)
        loss = criterion(y, z)
        # loss.backward() -> torch.no_grad sets torch.parameters() to be an empty set, and conducting loss.backward() will raise error.
        optim.step()
        print(y)
        print(net(x))
        # net(x) == y -> with no loss.backward(), params of network are fixed.

使用 tensorflow 中的 DropoutWrapper给RNN的每个时间步增加dropout

class DropoutWrapper(RNNCell):
  """Operator adding dropout to inputs and outputs of the given cell."""

  def __init__(self, cell, input_keep_prob=1.0, output_keep_prob=1.0,
               state_keep_prob=1.0, variational_recurrent=False,
               input_size=None, dtype=None, seed=None,
               dropout_state_filter_visitor=None):

其中三个 keep_prob 分别决定 RNN 的输入、输出、状态向量的 dropout 概率

12224193-96b7142665903055.jpg

RNN dropout 方式是对于一个序列的所有时间步,输入向量、输出向量、状态向量分别采用相同的 dropout mask,如图所示,相同颜色表示相同的 dropout mask

将GRUCell与DropoutWraper结合时产生梯度爆炸问题

下图是 GRU 的计算结构:12224193-aca58b6e5830f6b8.jpg 在对 GRU 的状态向量引入 dropout 后结构如下图,图中 h* 表示 dropout 后的状态向量。我们知道,dropout 根据 1-keep_rate 的概率生成一个 mask 向量,根据 mask 对状态向量中某些值置0,然后为了保证向量在后续与权值矩阵 W 相乘后得到的结果向量的 scale 与不 dropout 时一致,会对向量 h 中的对应 mask 非 0 的值除以 keep_rate。在我的实验中 dropout 对h中每个对应 mask 非0值除以0.8即乘以1.25。如前所述,由于变分 RNN dropout 中所有时间步的 dropout mask 都是相同的,所以对于一个长 n 的序列,状态向量 h* 中有些值在这个序列中永远是0,而另外一些值每经过一个时间步就要乘以 1/keep_rate(我的实验中是1.25),一个序列计算完后状态向量的值要乘以 (1/keep_rate)n,这些值在长序列情况下会变得非常大甚至溢出。这样就解释了为什么将 GRU 和 variational RNN dropout 结合使用的时候 loss 会变成 nan。

然后看看 LSTM,LSTM 的结构如下图所示。由图中结构可以看到,LSTM 的状态向量 h 每次都与矩阵相乘后再使用,这样可以保证即使每个时间步 h 的某些值会乘以 1/keep_rate,在与矩阵相乘后不会造成像 GRU 那样 h 的值呈指数上升的情况。

12224193-5eebcfcdf7e90522.gif

总结

  • 由于 GRU 与 LSTM 结构差异,variantional RNN dropout 可以很好地在 LSTM 中使用,但是不能在 GRU 中使用,variantional RNN dropout 有这样的局限性。

本文链接:http://57km.cc/post/use dropout in rnn to prevent from overfitting.html

-- EOF --

Comments