AMS机器学习课程:Keras深度学习 - 卷积神经网络正则化

目录

本文翻译自 AMS 机器学习 Python 教程,并有部分修改。

David John Gagne, 2019: “Deep Learning with Keras”.

https://github.com/djgagne/ams-ml-python-course/blob/master/module_3/ML_Short_Course_Module_3_Deep_Learning.ipynb

David John Gagne, National Center for Atmospheric Research

本文接上一篇文章《AMS机器学习课程:Keras深度学习 - 卷积神经网络

由于要拟合大量的权重,并且要处理的数据量有限,如果神经网络受到不适当的约束,它们很容易过度拟合数据集中的噪声。 尽管使用卷积和池化确实可以充当正则化器,但对于具有许多权重的网络,可能需要其他正则化技术。

我们将讨论两种常见的正则化技术:权重衰减 (Weight decay) 和丢弃法 (Dropout)。

权重衰减

权重衰减 (Weight decay) 是一种通过在损失函数中包含惩罚项来约束回归模型权重的方法,该函数将权重的总大小最小化。 惩罚项的影响由参数 alpha 控制。

权重衰减有两种常见形式:

  • Ridge (或 L2 norm,Tikhonov)
  • LASSO (或 L1 norm) 正则化

Ridge 正则化将所有权重的大小减小一个恒定因子,但更相关的权重受到的影响较小,相关性较小的权重被最小化。

LASSO 正则化将不太重要的权重减小为 0,从而导致权重稀疏。 LASSO 可以用作特征选择 (feature selection) 的一种形式。

我们现在将使用权重的 Ridge 正则化训练 CNN。 这是通过在 Keras 中向每个 Conv2D 和 Dense 层添加 kernel_regularizer=l2(l2_param) 参数来完成的。

构建模型

模型参数

num_conv_filters = 8
filter_width = 5
conv_activation = "relu"
learning_rate = 0.001
l2_param = 0.001

输入数据

形状:(instance, y, x, variable)

conv_net_in_l2 = Input(shape=train_norm_2d.shape[1:])
train_norm_2d.shape[1:]
(32, 32, 3)

第一组二维卷积层

from tensorflow.compat.v1.keras.regularizers import l2

conv_net_l2 = Conv2D(
    num_conv_filters, 
    (filter_width, filter_width), 
    padding="same",
    kernel_regularizer=l2(l2_param)
)(conv_net_in_l2)
conv_net_l2 = Activation(conv_activation)(conv_net_l2)

平均池化采用 2x2 邻域中的平均值以减小图像大小

conv_net_l2 = AveragePooling2D()(conv_net_l2)

第二组卷积层和池化层

conv_net_l2 = Conv2D(
    num_conv_filters * 2, 
    (filter_width, filter_width), 
    padding="same",
    kernel_regularizer=l2(l2_param)
)(conv_net_l2)
conv_net_l2 = Activation(conv_activation)(conv_net_l2)
conv_net_l2 = AveragePooling2D()(conv_net_l2)

第三组卷积层和池化层

conv_net_l2 = Conv2D(
    num_conv_filters * 4, 
    (filter_width, filter_width), 
    padding="same",
    kernel_regularizer=l2(l2_param)
)(conv_net_l2)
conv_net_l2 = Activation(conv_activation)(conv_net_l2)
conv_net_l2 = AveragePooling2D()(conv_net_l2)

将最后一个卷积层展平为长特征向量

conv_net_l2 = Flatten()(conv_net_l2)

全连接输出层,相当于在最后一层做逻辑回归

conv_net_l2 = Dense(
    1, 
    kernel_regularizer=l2(l2_param)
)(conv_net_l2)
conv_net_l2 = Activation("sigmoid")(conv_net_l2)

构建模型

conv_model_l2 = Model(
    conv_net_in_l2, 
    conv_net_l2
)

使用默认的 Adam 优化器

opt = Adam(lr=learning_rate)

编译模型

conv_model_l2.compile(
    opt, 
    "binary_crossentropy"
)

查看模型结构

conv_model_l2.summary()
Model: "functional_3"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_2 (InputLayer)         [(None, 32, 32, 3)]       0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 32, 32, 8)         608       
_________________________________________________________________
activation_4 (Activation)    (None, 32, 32, 8)         0         
_________________________________________________________________
average_pooling2d_3 (Average (None, 16, 16, 8)         0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 16, 16, 16)        3216      
_________________________________________________________________
activation_5 (Activation)    (None, 16, 16, 16)        0         
_________________________________________________________________
average_pooling2d_4 (Average (None, 8, 8, 16)          0         
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 8, 8, 32)          12832     
_________________________________________________________________
activation_6 (Activation)    (None, 8, 8, 32)          0         
_________________________________________________________________
average_pooling2d_5 (Average (None, 4, 4, 32)          0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 512)               0         
_________________________________________________________________
dense_1 (Dense)              (None, 1)                 513       
_________________________________________________________________
activation_7 (Activation)    (None, 1)                 0         
=================================================================
Total params: 17,169
Trainable params: 17,169
Non-trainable params: 0
_________________________________________________________________

训练模型

conv_model_l2.fit(
    train_norm_2d, 
    train_out, 
    batch_size=512, 
    epochs=10
)
Train on 76377 samples
Epoch 1/10
76377/76377 [==============================] - 23s 305us/sample - loss: 0.2160
Epoch 2/10
76377/76377 [==============================] - 23s 307us/sample - loss: 0.1574
Epoch 3/10
76377/76377 [==============================] - 24s 310us/sample - loss: 0.1413
Epoch 4/10
76377/76377 [==============================] - 23s 303us/sample - loss: 0.1306
Epoch 5/10
76377/76377 [==============================] - 23s 305us/sample - loss: 0.1234
Epoch 6/10
76377/76377 [==============================] - 23s 301us/sample - loss: 0.1196
Epoch 7/10
76377/76377 [==============================] - 23s 303us/sample - loss: 0.1167
Epoch 8/10
76377/76377 [==============================] - 25s 330us/sample - loss: 0.1137
Epoch 9/10
76377/76377 [==============================] - 24s 311us/sample - loss: 0.1132
Epoch 10/10
76377/76377 [==============================] - 23s 306us/sample - loss: 0.1102

检验

calc_verification_scores(
    conv_model_l2, 
    test_norm_2d, 
    test_out
)
AUC: 0.945
Brier Score: 0.031
Brier Score (Climatology): 0.050
Brier Skill Score: 0.384

丢弃法

丢弃法 (dropout) 是一种正则化技术,使人们可以在仅存储一个模型的情况下训练小型模型的任意集合。

丢弃法从原理上来讲很简单。 为 Dropout 层分配了丢弃的概率。 每次调用该层时,该层的每个输入都会根据丢弃概率随机设置为 0。 对于 0.5 的辍学率,给定神经元将被设置为 0 的可能性为50%。 对于给定的调用,大约 50% 的神经元将被设置为零,但是会有一些变化。 为了保持与完整模型相同的均值,将剩余的输入除以丢弃率。

由于某些神经元在训练过程中并不总是可用,因此丢弃法会鼓励训练过程中神经元之间的独立性。

说明

默认的丢弃过程是随机丢弃单个神经元。 对于具有空间相关数据的卷积神经网络,标准丢弃可能对结果影响不大,因此可以使用称为 SpatialDropout2D 的变体。 它不会丢弃单个神经元,而是随机丢弃整个通道。

在推断期间也可以使用丢弃来产生对预测不确定性的估计。 如果在丢弃激活的情况下多次通过网络发送同一示例,则将生成预测分布。 有证据表明此分布已得到很好的校准,但是可能需要调整丢弃率才能产生最可靠的预测间隔。

下面是一个字段中的 dropout 和 spatial dropout 的示例。

from tensorflow.compat.v1.keras.layers import (
    Dropout, 
    SpatialDropout2D,
)

drop_in = K.placeholder((1, 10, 10, 3))
dropout_rate = 0.5

drop = Dropout(dropout_rate)(drop_in)
spatial_dropout = SpatialDropout2D(dropout_rate)(drop_in)

drop_func = K.function(
    [drop_in, K.learning_phase()], 
    [drop, spatial_dropout]
)

多次运行下面的单元格,以查看丢弃结果如何变化。

input_field = np.ones(
    (1, 10, 10, 3)
)
drop_output, space_drop_output = drop_func([input_field, 1])

fig, axes = plt.subplots(
    2, 3, 
    figsize=(12, 6)
)

axes[0, 0].set_ylabel(
    "Dropout", 
    fontsize=14
)
axes[1, 0].set_ylabel(
    "Spatial Dropout", 
    fontsize=14
)

for a in range(3):
    axes[0, a].pcolormesh(
        drop_output[0, :, :, a], 
        vmin=0, 
        vmax=2, 
        cmap="Reds"
    )
    axes[0, a].text(
        5, 5, 
        f"Max: {drop_output[0, :, :, a].max():0.2f}\nCount: {np.count_nonzero(drop_output[0, :, :, a]):d}", 
        ha='center', 
        va='center',
        fontsize=16, 
        color='white',
        bbox=dict(facecolor='k', alpha=0.5)
    )
    axes[1, a].pcolormesh(
        space_drop_output[0, :, :, a], 
        vmin=0, 
        vmax=2, 
        cmap="Reds"
    )
    axes[1, a].text(
        5, 5, 
        f"Max: {space_drop_output[0, :, :, a].max():0.2f}", 
        ha='center', 
        va='center', 
        fontsize=16, 
        color='white',
        bbox=dict(facecolor='k')
    )

第一行使用 Dropout,可以看到每个通道中的网格数据被随机丢弃

第二行使用 SpatialDropout2D,可以看到整个通道被随机丢弃

构建模型

现在,我们将构建一个具有空间缺失层 (spatial dropout layers) 的卷积神经网络 (convolutional neural network)。

设置参数

num_conv_filters = 16
filter_width = 5
conv_activation = "relu"
learning_rate = 0.001
dropout_rate = 0.2

输入数据,(instance, y, x, variable)

conv_net_in_d = Input(
    shape=train_norm_2d.shape[1:]
)
train_norm_2d.shape[1:]
(32, 32, 3)

第一组二维卷积层

conv_net_d = Conv2D(
    num_conv_filters, 
    (filter_width, filter_width), 
    padding="same"
)(conv_net_in_d)
conv_net_d = Activation(
    conv_activation
)(conv_net_d)
conv_net_d = SpatialDropout2D(
    dropout_rate
)(conv_net_d)

平均池化层选取 2x2 区域内的平均值,以减少图像尺寸

conv_net_d = AveragePooling2D()(conv_net_d)

第二组二维卷积层和池化层

conv_net_d = Conv2D(
    num_conv_filters * 2, 
    (filter_width, filter_width), 
    padding="same"
)(conv_net_d)
conv_net_d = Activation(
    conv_activation
)(conv_net_d)
conv_net_d = SpatialDropout2D(
    dropout_rate
)(conv_net_d)
conv_net_d = AveragePooling2D()(conv_net_d)

第三组二维卷积层和池化层

conv_net_d = Conv2D(
    num_conv_filters * 4, 
    (filter_width, filter_width), 
    padding="same"
)(conv_net_d)
conv_net_d = Activation(
    conv_activation
)(conv_net_d)
conv_net_d = SpatialDropout2D(
    dropout_rate
)(conv_net_d)
conv_net_d = AveragePooling2D()(conv_net_d)

将最后一个卷积层展平为长矢量

conv_net_d = Flatten()(conv_net_d)

全连接输出层,相当于在最后一层做逻辑回归

conv_net_d = Dense(1)(conv_net_d)
conv_net_d = Activation("sigmoid")(conv_net_d)
conv_model_d = Model(
    conv_net_in_d, 
    conv_net_d
)

使用默认参数的 Adam 优化器

opt = Adam(lr=learning_rate)

编译模型

conv_model_d.compile(
    opt, 
    "binary_crossentropy"
)

查看模型

conv_model_d.summary()
Model: "functional_5"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_3 (InputLayer)         [(None, 32, 32, 3)]       0         
_________________________________________________________________
conv2d_6 (Conv2D)            (None, 32, 32, 16)        1216      
_________________________________________________________________
activation_8 (Activation)    (None, 32, 32, 16)        0         
_________________________________________________________________
spatial_dropout2d_1 (Spatial (None, 32, 32, 16)        0         
_________________________________________________________________
average_pooling2d_6 (Average (None, 16, 16, 16)        0         
_________________________________________________________________
conv2d_7 (Conv2D)            (None, 16, 16, 32)        12832     
_________________________________________________________________
activation_9 (Activation)    (None, 16, 16, 32)        0         
_________________________________________________________________
spatial_dropout2d_2 (Spatial (None, 16, 16, 32)        0         
_________________________________________________________________
average_pooling2d_7 (Average (None, 8, 8, 32)          0         
_________________________________________________________________
conv2d_8 (Conv2D)            (None, 8, 8, 64)          51264     
_________________________________________________________________
activation_10 (Activation)   (None, 8, 8, 64)          0         
_________________________________________________________________
spatial_dropout2d_3 (Spatial (None, 8, 8, 64)          0         
_________________________________________________________________
average_pooling2d_8 (Average (None, 4, 4, 64)          0         
_________________________________________________________________
flatten_2 (Flatten)          (None, 1024)              0         
_________________________________________________________________
dense_2 (Dense)              (None, 1)                 1025      
_________________________________________________________________
activation_11 (Activation)   (None, 1)                 0         
=================================================================
Total params: 66,337
Trainable params: 66,337
Non-trainable params: 0
_________________________________________________________________

训练模型

conv_model_d.fit(
    train_norm_2d, 
    train_out, 
    batch_size=512, 
    epochs=10
)
Train on 76377 samples
Epoch 1/10
76377/76377 [==============================] - 45s 592us/sample - loss: 0.1723
Epoch 2/10
76377/76377 [==============================] - 45s 594us/sample - loss: 0.1384
Epoch 3/10
76377/76377 [==============================] - 46s 599us/sample - loss: 0.1262
Epoch 4/10
76377/76377 [==============================] - 45s 592us/sample - loss: 0.1186
Epoch 5/10
76377/76377 [==============================] - 45s 589us/sample - loss: 0.1142
Epoch 6/10
76377/76377 [==============================] - 47s 614us/sample - loss: 0.1091
Epoch 7/10
76377/76377 [==============================] - 44s 573us/sample - loss: 0.1067
Epoch 8/10
76377/76377 [==============================] - 41s 530us/sample - loss: 0.1031
Epoch 9/10
76377/76377 [==============================] - 40s 522us/sample - loss: 0.1023
Epoch 10/10
76377/76377 [==============================] - 43s 562us/sample - loss: 0.1003

检验

calc_verification_scores(
    conv_model_d, 
    test_norm_2d, 
    test_out
)
AUC: 0.959
Brier Score: 0.029
Brier Score (Climatology): 0.050
Brier Skill Score: 0.410

不确定度量化

最后,我们将在推断模式下使用丢弃层来生成预测分布。 不同示例的不确定性如何变化?

dist_pred = K.function(
    [conv_model_d.input, K.learning_phase()], 
    [conv_model_d.output]
)
num_samples = 1000

index = 32237

pred_values = np.zeros(num_samples)

pred_det = conv_model_d.predict(
    test_norm_2d[index:index+1]
)[0, 0]

for i in range(num_samples):
    pred_values[i] = dist_pred(
        [test_norm_2d[index:index+1], 1]
    )[0][0,0]
    
freqs, bins, patches = plt.hist(
    pred_values, 
    bins=np.arange(0, 1.05, 0.05), 
    cumulative=False, 
    density=True
)

plt.plot(
    [pred_det, pred_det], 
    [0, freqs.max()], 
    'r-', 
    lw=3, 
    label="Deterministic"
)

plt.plot(
    [pred_values.mean(), pred_values.mean()], 
    [0, freqs.max()], 
    'k-', 
    lw=3, 
    label="Mean"
)

plt.plot(
    [np.median(pred_values), np.median(pred_values)], 
    [0, freqs.max()], 
    color='purple', 
    lw=3, 
    label="Median"
)

plt.legend()

plt.xlabel(
    "Probability of Strong Rotation", 
    fontsize=12
)
plt.ylabel(
    "Frequency", 
    fontsize=12
)

参考

https://github.com/djgagne/ams-ml-python-course

AMS 机器学习课程

数据预处理

数据分析与预处理

预测雷暴旋转的基础机器学习

数据处理

线性回归

线性回归

正则化

超参数试验

逻辑回归分类

训练

ROC 曲线

性能图

属性图

评估

系数与正则化

决策树分类

决策树

决策树超参数试验

决策树集成学习

Keras 深度学习

人工神经网络

卷积神经网络