Keras防止过拟合(一)Dropout层源码细节

在使用深度学习模型时,会遇到两种问题,过拟合和欠拟合。其中,解决欠拟合的方法有增大数据集,优化模型等等,根据具体问题具体对待。过拟合的问题,可以通过Dropout,添加L1,L2正规项等等很简单的方法解决,网上有许多文章介绍这些方法。但其代码具体如何实现,实现中的细节问题,却缺少描述。我就写写解决过拟合这方面的博客,刚好也是督促自己学习!
Dropout原理
有许多优质文章讲过Dropout操作的原理,大家可以去搜一搜。我在这里只简单提一下,重点讲解Keras是如何实现的。
在这里插入图片描述
上图是在神经网络中使用Dropout,其实就是在前向传播时抛弃一部分连接,让某些神经元不工作,这样可以提高泛化能力,防止过度依赖局部特征。
为了实现Dropout,可以考虑到:
1.神经元停止工作的方法是,将神经元的值变为0。
2.随机抛弃一部分。

源码细节

Dropout在keras中的源码位于keras\layers\core.py中,官方文档有如下介绍:
在这里插入图片描述
其部分代码如下:

def __init__(self, rate, noise_shape=None, seed=None, **kwargs):
    super(Dropout, self).__init__(**kwargs)
    self.rate = min(1., max(0., rate))
    self.noise_shape = noise_shape
    self.seed = seed
    self.supports_masking = True

def _get_noise_shape(self, inputs):
    if self.noise_shape is None:
        return self.noise_shape

    symbolic_shape = K.shape(inputs)
    noise_shape = [symbolic_shape[axis] if shape is None else shape
                    for axis, shape in enumerate(self.noise_shape)]
    return tuple(noise_shape)

def call(self, inputs, training=None):
    if 0. < self.rate < 1.:
        noise_shape = self._get_noise_shape(inputs)

        def dropped_inputs():
            return K.dropout(inputs, self.rate, noise_shape,
                                seed=self.seed)
        return K.in_train_phase(dropped_inputs, inputs,
                                training=training)
    return inputs

有三个参数,rate,noise_shape,seed. 官方文档中有介绍,其中,noise_shape有些难以理解,等等按照代码解释。
观察代码,其核心部分是:

def dropped_inputs():
    return K.dropout(inputs, self.rate, noise_shape,
                        seed=self.seed)
return K.in_train_phase(dropped_inputs, inputs,
        training=training)

先解释最后的返回部分,K.in_train_phase函数:
在这里插入图片描述
其作用是保证只在训练的时候执行dropout操作,不然的话,模型在测试时也执行了dropout,相当于将本来已经训练好了的模型又抛弃了一部分。
K.dropout函数,位于keras\backend\tensorflow_backend.py中(我用tensorflow作为后端),源码:

def dropout(x, level, noise_shape=None, seed=None):
    if seed is None:
        seed = np.random.randint(10e6)
    return tf.nn.dropout(x, rate=level, noise_shape=noise_shape, seed=seed)

好吧,还不是具体实现,那再去看看tf.nn.dropout源码,找了一圈发现,其位于tensorflow\python\ops\nn_ops.py中,实现功能的是def dropout_v2(x, rate, noise_shape=None, seed=None, name=None)函数。
对这个函数,tf源文件中有说明,其中比较重要的部分:

Computes dropout: randomly sets elements to zero to prevent overfitting.

Inputs elements are randomly set to zero (and the other elements are rescaled). This encourages each node to be independently useful, as it cannot rely on the output of other nodes.

More precisely: With probability rate elements of x are set to 0. The remaining elements are scaled up by 1.0 / (1 - rate), so that the expected value is preserved.

这三句话,表达的意思主要是:
1.随机设置元素为0。
2.随机设置以保证节点独立性,节点互不依赖。
3.没有被设置为0的节点,将扩大为1.0 / (1 - rate)倍,以保证数学期望一致。
前两条最开始就有分析到,第三条需要注意一下,因为我在看有些博主自写的dropout源码中并没有考虑到这点。

具体实现代码核心部分(终于到如何实现了…):

keep_prob = 1 - rate
scale = 1 / keep_prob 
scale = ops.convert_to_tensor(scale, dtype=x_dtype)
ret = gen_math_ops.mul(x, scale)

noise_shape = _get_noise_shape(x, noise_shape)

random_tensor = random_ops.random_uniform(
        noise_shape, seed=seed, dtype=x_dtype)
# NOTE: if (1.0 + rate) - 1 is equal to rate, then that float is selected,
# hence a >= comparison is used.
keep_mask = random_tensor >= rate
ret = gen_math_ops.mul(ret, gen_math_ops.cast(keep_mask, x_dtype))

return ret

通过观察,如果不使用noise_shape的话,先是使所有元素的值扩大1.0 / (1 - rate)倍。之后,tf是通过随机一个与x.shape相同的随机矩阵(值都在[0,1]之间),之后将其所有大于等于rate的值保留,其他抛弃,最后再将其与扩大倍数后的x(ret)相乘,即可完成随机rate比率元素为0.

最后,说明一下noise_shape的实现。关于noise_shape的作用,keras中文文档的介绍有点迷,tf源码中也有介绍,还有一个例子:

By default, each element is kept or dropped independently. If noise_shape is specified, it must be broadcastable to the shape of x, and only dimensions with noise_shape[i] ==shape(x)[i] will make independent decisions. This is useful for dropping whole channels from an image or sequence. For example:

tf.random.set_seed(0)
x = tf.ones([3,10])
tf.nn.dropout(x, rate = 2/3, noise_shape=[1,10], seed=1).numpy()
array([[0., 0., 0., 3., 3., 0., 3., 3., 3., 0.],
[0., 0., 0., 3., 3., 0., 3., 3., 3., 0.],
[0., 0., 0., 3., 3., 0., 3., 3., 3., 0.]], dtype=float32)

从描述和例子来看,noise_shape的作用是,在一个轴上,所有的随机为0的元素,位置都是一样的。
具体实现十分简单,上面已经讲过,tf是通过随机一个矩阵的方式来决定哪些位置为0.
若使用了noise_shape,则随机矩阵不按照x.shape,而是按照noise_shape。按照代码来看也是如此,不使用noise_shape时,noise_shape为x.shape.至于noise_shape = _get_noise_shape(x, noise_shape),研究了之后发现,可能是为了防止BUG设置的(不是很确定),但我自己测试这个模块的作用时,_get_noise_shape()的返回值就是noise_shape。

例子中x.shape为[3,10],noise_shape为[1,10].不使用noise_shape时,random_tensor为[3,10],使用时为[1,10]. [3,10]的矩阵与[3,10]的矩阵相乘,则所有元素相当于都按rate比率随机取0。而[1,10]矩阵与[3,10]矩阵相乘,则轴1上元素取0位置都相同。也就像keras中文文档所说,掩层在所有时间步都相同。

以上,就是dropout层源码的解析,在真正使用时。我们只需要在一层后面加上Dropout层就行了。例如,在全连接网络后加入丢弃率为0.3的dropout层。

nn = Dense(128)(model_input)
nn = Dropout(0.3)(nn)
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值