batch norm、relu、dropout 等的相对顺序和BN、dropout的几个问题和思考

总结:BN和dropout一般不同时使用,如果一定要同时使用,可以将dropout放置于BN后面。

 1.batch norm、relu、dropout 等的相对顺序

Ordering of batch normalization and dropout in TensorFlow?

在 Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift 一文中,作者指出,“we would like to ensure that for any parameter values, the network always produces activations with the desired distribution”(produces activations with the desired distribution,为激活层提供期望的分布,即先让分布标准化,然后再激活)。

因此 Batch Normalization 层恰恰插入在 Conv 层或全连接层之后,而在 ReLU等激活层之前。而对于 dropout 则应当置于 activation layer 之后。

-> CONV/FC -> BatchNorm -> ReLu(or other activation) -> Dropout -> CONV/FC ->;

 

1、BN的scale初始化

scale一般初始化为1.0。

联想到权重初始化时,使用relu激活函数时若采用随机正太分布初始化权重的公式是sqrt(2.0/Nin),其中Nin是输入节点数。即比一般的方法大了2的平方根(原因是relu之后一半的数据变成了0,所以应乘以根号2)。

那么relu前的BN,是否将scale初始化为根号2也会加速训练?

这里主要有个疑点:BN的其中一个目的是统一各层的方差,以适用一个统一的学习率。那么若同时存在sigmoid、relu等多种网络,以上方法会不会使得统一方差以适应不同学习率的效果打了折扣?

没来得及试验效果,如果有试过的朋友请告知下效果。

2、dropout后的标准差改变问题

实践发现droput之后改变了数据的标准差(令标准差变大,若数据均值非0时,甚至均值也会产生改变)。

如果同时又使用了BN归一化,由于BN在训练时保存了训练集的均值与标准差。dropout影响了所保存的均值与标准差的准确性(不能适应未来预测数据的需要),那么将影响网络的准确性。

若输入数据为正太分布,只需要在dropout后乘以sqrt(0.5)即可恢复原来的标准差。但是对于非0的均值改变、以及非正太分布的数据数据,又有什么好的办法解决呢?

3、稀疏自编码的稀疏系数

稀疏自编码使用一个接近0的额外惩罚因子来使得隐层大部分节点大多数时候是抑制的,本质上使隐层输出均值为负数(激活前),例如惩罚因子为0.05,对应sigmoid的输入为-3.5,即要求隐层激活前的输出中间值为-3.5,那么,是不是可以在激活前加一层BN,beta设为-3.5,这样学起来比较快?

经过测试,的确将BN的beta设为负数可加快训练速度。因为网络初始化时就是稀疏的。

但是是不是有什么副作用,没有理论上的研究。

4、max pooling是非线性的,avg pooling是线性的

5.Dropout VS Batch Normalization使用效果对比

结论:无论是理论上的分析,还是现代深度模型的演变,或者是实验的结果,BN技术已显示出其优于Dropout的正则化效果。

 Dropout是过去几年非常流行的正则化技术,可有效防止过拟合的发生。但从深度学习的发展趋势看,Batch Normalizaton(简称BN)正在逐步取代Dropout技术,特别是在卷积层。本文将首先引入Dropout的原理和实现,然后观察现代深度模型Dropout的使用情况,并与BN进行实验比对,从原理和实测上来说明Dropout已是过去式,大家应尽可能使用BN技术。

 5.1、Dropout原理

  根据wikipedia定义,dropout是指在神经网络中丢弃掉一些隐藏或可见单元。通常来说,是在神经网络的训练阶段,每一次迭代时,都会随机选择一批单元,让其被暂时忽略掉,所谓的忽略是不让这些单元参与前向推理和后向传播。

   

  上图是标准的神经网络,经过dropout后,则变成如下图:

  

  一般来说,我们在可能发生过拟合的情况下才会使用dropout等正则化技术。那什么时候可能会发生呢?比如神经网络过深,或训练时间过长,或没有足够多的数据时。那为什么dropout能有效防止过拟合呢?可以理解为,我们每次训练迭代时,随机选择一批单元不参与训练,这使得每个单元不会依赖于特定的前缀单元,因此具有一定的独立性;同样可以看成我们拿同样的数据在训练不同的网络,每个网络都有可能过拟合,但迭代多次后,这种过拟合会被抵消掉。

  要注意的是,dropout是体现在训练环节,训练完成后,我们认为所有的单元都被训练好了,在验证或测试阶段,我们是拿完整的神经网络去验证或测试。

    5.2、Dropout具体实现

  以keras为例,其代码为:keras.backend.dropout(x, level, noise_shape=None, seed=None),其中x指的是输入参数,level则是keep-prob,也就是这个单元有多少概率会被设置为0。

import tensorflow.keras.backend as K

input = K.random_uniform_variable(shape=(3, 3), low=0, high=1)

print("dropout with keep-prob 0.5:", K.eval(K.dropout(input, 0.5)))
print("dropout with keep-prob 0.2:", K.eval(K.dropout(input, 0.2)))
print("dropout with keep-prob 0.8:", K.eval(K.dropout(input, 0.8)))

 

 看看输出结果:

dropout with keep-prob 0.5: 
[[1.190095  0.        1.2999489]
 [0.        0.3164637 0.       ]
 [0.        0.        0.       ]]
dropout with keep-prob 0.2: 
[0.74380934 0.67237484 0.81246805]
 [0.8819132  0.19778982 1.2349881 ]
 [1.0369372  0.5945368  0.        ]]
dropout with keep-prob 0.8: 
[[0.        0.        0.       ]
 [0.        0.        4.9399524]
 [4.147749  2.3781471 0.       ]]

 

  可以看出,level值越大,每个单元成为0的概率也就越大。

  在具体的keras应用中,dropout通常放在激活函数后,比如:

model=keras.models.Sequential()
model.add(keras.layers.Dense(150, activation="relu"))
model.add(keras.layers.Dropout(0.5))

5.3、Dropout正在被抛弃

  随着深度学习的发展,Dropout在现代卷积架构中,已经逐步被BN(想要了解BN,大家可以参见我之前写的 深度学习基础系列(七)| Batch Normalization 一文,这里不再赘述)取代,BN也同样拥有不亚于Dropout的正则化效果。

  “We presented an algorithm for constructing, training, and performing inference with batch-normalized networks. The resulting networks can be trained with saturating nonlinearities, are more tolerant to increased training rates, and often do not require Dropout for regularization.” -Ioffe and Svegedy 2015

  至于为何Dropout不再受青睐,原因如下:

  • Dropout在卷积层的正则效果有限。相比较于全连接层,卷积层的训练参数较少,激活函数也能很好地完成特征的空间转换,因此正则化效果在卷积层不明显;
  • Dropout也过时了,能发挥其作用的地方在全连接层,可当代的深度网络中,全连接层也在慢慢被全局平均池化曾所取代,不但能减低模型尺寸,还可以提升性能。

  事实上,我们可以看看keras实现的现代经典模型,就可以窥之dropout目前的处境。打开keras的地址:https://github.com/keras-team/keras-applications 

  纵观无论是VGG、ResNet、Inception、MobileNetV2等模型,都不见了Dropout踪影。唯独在MobileNetV1模型里,还可以找到Dropout,但不是在卷积层;而且在MobileNetV2后,已经不再有全连接层,而是被全局平均池化层所取代。如下图所示:

  

  其他模型也类似,纷纷抛弃了Dropout和全连接层。

5.4、Dropout VS BatchNormalization

  我们需要做一个简单实验来验证上述理论的成立,实验分五种测试模型:

  • 没有使用Dropout,也没有使用BN;
  • 使用了Dropout,不使用BN,使训练单元为0的概率为0.2;
  • 使用了Dropout,不使用BN,使训练单元为0的概率为0.5;
  • 使用了Dropout,不使用BN,使训练单元为0的概率为0.8;
  • 使用了BN,不使用Dropout

  代码如下:

import keras
from keras.datasets import cifar10
from keras.preprocessing.image import ImageDataGenerator
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation, Flatten, BatchNormalization
from keras.layers import Conv2D, MaxPooling2D
from matplotlib import pyplot as plt
import numpy as np

# 为保证公平起见,使用相同的随机种子
np.random.seed(7)
batch_size = 32
num_classes = 10
epochs = 40
data_augmentation = True

# The data, split between train and test sets:
(x_train, y_train), (x_test, y_test) = cifar10.load_data()

# Convert class vectors to binary class matrices.
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)

x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255

def model(bn=False, dropout=False, level=0.5):
    model = Sequential()
    model.add(Conv2D(32, (3, 3), padding='same', input_shape=x_train.shape[1:]))
    if bn:
        model.add(BatchNormalization())
    model.add(Activation('relu'))
    model.add(Conv2D(32, (3, 3)))
    if bn:
        model.add(BatchNormalization())
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    if dropout:
        model.add(Dropout(level))

    model.add(Conv2D(64, (3, 3), padding='same'))
    if bn:
        model.add(BatchNormalization())
    model.add(Activation('relu'))
    model.add(Conv2D(64, (3, 3)))
    if bn:
        model.add(BatchNormalization())
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    if dropout:
        model.add(Dropout(level))

    model.add(Flatten())
    model.add(Dense(512))
    if bn:
        model.add(BatchNormalization())
    model.add(Activation('relu'))
    if dropout:
        model.add(Dropout(level))
    model.add(Dense(num_classes))
    model.add(Activation('softmax'))
    if bn:
        opt = keras.optimizers.rmsprop(lr=0.001, decay=1e-6)
    else:
        opt = keras.optimizers.rmsprop(lr=0.0001, decay=1e-6)

    model.compile(loss='categorical_crossentropy',
                             optimizer=opt,
                             metrics=['accuracy'])

    # 使用数据增强获取更多的训练数据
    datagen = ImageDataGenerator(width_shift_range=0.1, height_shift_range=0.1, horizontal_flip=True)
    datagen.fit(x_train)
    history = model.fit_generator(datagen.flow(x_train, y_train, batch_size=batch_size), epochs=epochs,
                                  validation_data=(x_test, y_test), workers=4)
    return history


no_dropout_bn_history = model(False, False)
dropout_low_history = model(False, True, 0.2)
dropout_medium_history = model(False, True, 0.5)
dropout_high_history = model(False, True, 0.8)
bn_history = model(True, False)

# 比较多种模型的精确度
plt.plot(no_dropout_bn_history.history['val_acc'])
plt.plot(dropout_low_history.history['val_acc'])
plt.plot(dropout_medium_history.history['val_acc'])
plt.plot(dropout_high_history.history['val_acc'])
plt.plot(bn_history.history['val_acc'])
plt.title('Model accuracy')
plt.ylabel('Validation Accuracy')
plt.xlabel('Epoch')
plt.legend(['No bn and dropout', 'Dropout with 0.2', 'Dropout with 0.5', 'Dropout with 0.8', 'BN'], loc='lower right')
plt.grid(True)
plt.show()

# 比较多种模型的损失率
plt.plot(no_dropout_bn_history.history['val_loss'])
plt.plot(dropout_low_history.history['val_loss'])
plt.plot(dropout_medium_history.history['val_loss'])
plt.plot(dropout_high_history.history['val_loss'])
plt.plot(bn_history.history['val_loss'])
plt.title('Model loss')
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.legend(['No bn and dropout', 'Dropout with 0.2', 'Dropout with 0.5', 'Dropout with 0.8', 'BN'], loc='upper right')
plt.grid(True)
plt.show()

 

各模型的验证准确率如下图:

  各模型的验证损失率如下:

  

  由上图可知,Dropout在不同概率下,其表现差异较大,相对来说,Dropout with 0.2的表现接近于 No bn and dropout(可以理解为Dropout的keep-prob为1的版本)。总体来说,BN在准确率和损失率上表现要优于Dropout,比如准确率上BN能达到85%,而Dropout接近为79%。

5.5、结论

  无论是理论上的分析,还是现代深度模型的演变,或者是实验的结果,BN技术已显示出其优于Dropout的正则化效果,我们也是时候放弃Dropout,投入BN的怀抱了。  

 

参考:

https://blog.csdn.net/weixin_30819085/article/details/95617900

https://blog.csdn.net/lecturekeke/article/details/78963022

https://www.cnblogs.com/hutao722/p/9946047.html

  • 62
    点赞
  • 182
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
好的,针对您的问题,我来分别介绍一下如何使用Paddle实现正交位置编码和相对位置编码。 首先,我们需要明确一下正交位置编码和相对位置编码的概念: 正交位置编码(Orthogonal Positional Encoding)是一种在Transformer模型中广泛使用的位置编码方式,它通过将位置信息嵌入到词向量中,使得模型能够区分不同位置的单词。 相对位置编码(Relative Positional Encoding)是一种在BERT等模型中使用的位置编码方式,它不仅考虑到单词之间的绝对位置,还考虑到它们之间的相对位置,从而更好地处理长文本序列。 接下来,我将分别介绍如何使用Paddle实现这两种位置编码方式。 1. 正交位置编码 首先,我们需要定义一个正交矩阵,用于将位置信息嵌入到词向量中。在Paddle中,我们可以使用`paddle.Tensor`来定义矩阵。 ``` python import paddle # 定义正交矩阵 d_model = 512 max_len = 1000 pos_enc = paddle.zeros([max_len, d_model]) for pos in range(max_len): for i in range(0, d_model, 2): pos_enc[pos, i] = math.sin(pos / (10000 ** ((2 * i) / d_model))) pos_enc[pos, i + 1] = math.cos(pos / (10000 ** ((2 * (i + 1)) / d_model))) pos_enc = paddle.to_tensor(pos_enc) ``` 上述代码中,我们定义了一个大小为`max_len x d_model`的全零矩阵,并对其进行了正交编码。其中,`pos_enc[pos, i]`和`pos_enc[pos, i+1]`分别表示位置为`pos`的向量的第`i`和第`i+1`个元素。 接下来,我们将这个正交矩阵加到词向量中,即可实现正交位置编码。 ``` python import paddle.nn as nn class EncoderLayer(nn.Layer): def __init__(self, d_model, nhead, dim_feedforward, dropout): super(EncoderLayer, self).__init__() self.self_attn = nn.MultiHeadAttention(d_model, nhead) self.linear1 = nn.Linear(d_model, dim_feedforward) self.dropout = nn.Dropout(dropout) self.linear2 = nn.Linear(dim_feedforward, d_model) self.norm1 = nn.LayerNorm(d_model) self.norm2 = nn.LayerNorm(d_model) self.dropout1 = nn.Dropout(dropout) self.dropout2 = nn.Dropout(dropout) self.pos_enc = paddle.zeros([max_len, d_model]) for pos in range(max_len): for i in range(0, d_model, 2): self.pos_enc[pos, i] = math.sin(pos / (10000 ** ((2 * i) / d_model))) self.pos_enc[pos, i + 1] = math.cos(pos / (10000 ** ((2 * (i + 1)) / d_model))) self.pos_enc = paddle.to_tensor(self.pos_enc) def forward(self, src): src = src + self.pos_enc[:src.shape[0], :] src2 = self.self_attn(src, src, src) src = src + self.dropout1(src2) src2 = self.norm1(src) src2 = self.linear2(self.dropout(nn.functional.relu(self.linear1(src2)))) src = src + self.dropout2(src2) src = self.norm2(src) return src ``` 上述代码中,我们在`EncoderLayer`中增加了一个`pos_enc`属性,用于存储正交矩阵。在`forward`方法中,我们将`pos_enc`矩阵加到输入向量`src`中,并进行后续的自注意力、馈等操作。 2. 相对位置编码 相对位置编码需要考虑到单词之间的相对位置,因此需要先计算出单词之间的相对位置向量,然后将其与位置编码矩阵相加。在Paddle中,我们可以使用`paddle.nn.functional.embedding`函数来实现这个过程。 ``` python import paddle.nn.functional as F class RelativePositionalEncoding(nn.Layer): def __init__(self, d_model, max_len): super(RelativePositionalEncoding, self).__init__() self.d_model = d_model self.max_len = max_len self.pos_enc = nn.Embedding(2 * max_len - 1, d_model) freq = torch.exp(torch.arange(0, d_model, 2) * (-math.log(10000.0) / d_model)) self.register_buffer("freq", freq) def forward(self, x): batch_size, seq_len = x.shape[:2] pos = paddle.arange(0, seq_len, dtype='float32').unsqueeze(1) rel_pos = paddle.arange(0, self.d_model, 2, dtype='float32') rel_pos = self.freq * rel_pos rel_pos = pos / rel_pos sin_pos = paddle.sin(rel_pos) cos_pos = paddle.cos(rel_pos) sin_pos = F.pad(sin_pos, [0, 0, 1, 0])[:, :-1] cos_pos = F.pad(cos_pos, [0, 0, 1, 0])[:, :-1] pos_emb = paddle.concat([sin_pos, cos_pos], axis=-1) pos_emb = self.pos_enc(pos_emb.long()) return pos_emb ``` 上述代码中,我们定义了一个`RelativePositionalEncoding`类,用于实现相对位置编码。在`__init__`方法中,我们定义了一个大小为`(2*max_len-1) x d_model`的位置编码矩阵,并计算了相对位置向量的值。在`forward`方法中,我们根据输入序列`x`的长度,计算出相对位置向量,并将其拆分成`sin`和`cos`两部分。最后,我们将`sin`和`cos`拼接起来,并使用`embedding`函数将其映射成位置编码矩阵。 通过上述代码,我们就可以实现相对位置编码。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值