栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Python

机器学习实战笔记——第十一章

Python 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

机器学习实战笔记——第十一章

        在实际模型训练中,可能遇到以下问题:

  • 梯度消失:随着算法向下传播到较低层,梯度通常会越来越小,低层的连接权重几乎不变导致模型的无法收敛到一个很好的解
  • 梯度爆炸:某些情况下(可能出现在递归神经网络中)梯度会越来越大,各层会产生较大的更新权重,直到算法发散
  • 没有足够的训练数据
  • 训练缓慢
  • 参数过大造成过拟合
一、梯度消失与梯度爆炸 1.1 Glorot 和 He 初始化

        针对梯度不稳定问题,Glorot 提出希望信号流动过程既不消失也不饱和,使得每层的输出方差等于其输入的方差,除非每层的输入和输出神经元数量相等,否则无法很好的保证。对此提出了 Xavier 初始化或者 Glorot 初始化来初始化每层的连接权重。

    Glorot 初始化(激活函数采用逻辑函数)

正态分布,均值为0,方差 

或 -r 和 +r 之间的均匀分布,其中 

其中 

        使用 Glorot 初始化可以大大加快训练速度。用  代替  就成为 LeCun 初始化。各种激活函数的初始化参数见下表:

初始化激活函数
GlorotNone、tanh、逻辑、softmax
HeReLU 和变体
LeCunSELU

        默认情况下,Keras 使用具有均匀分布的 Glorot 初始化,但可以在创建层时可以通过设置 kernel_initializer 参数来更改初始化方式。

import tensorflow as tf

tf.keras.layers.Dense(10, activation='relu', kernel_initilizer='he_noraml')

         如果想使用均匀分布但是基于  的 He 初始化,则可以通过 Variance Scaling 初始化。

he_avg_init = tf.keras.initializers.VarianceScalling(scale=2, mode='fan_avg', distribution='uniform')
tf.keras.layers.Dense(10, activation='sigmoid', kernel_initilizer=he_avg_init)
1.1.1 tf.keras.initializers.VarianceScaling
tf.keras.initializers.VarianceScaling(
    scale=1.0, mode='fan_in', distribution='truncated_normal',
    seed=None
)
参数注释
scale缩放因子
modefan_in、fan_out、fan_avg 之一
distribution要使用的随机分布。“truncated_normal”、“untruncated_normal”和“uniform”之一。
其它见 tf.keras.initializers.VarianceScaling  |  TensorFlow Core v2.6.0
1.2 非饱和激活函数

        梯度不稳定的一个原因就是由于激活函数选择不当;ReLU 激活函数由于对正值不饱和并且计算速度很快,所以表现要比逻辑函数好很多。但它存在神经元”死亡“问题,特别是在较大的学习率下,神经元权重调整时,输入的加权为负数,此时神经元就会死亡,只会继续输出0,因为 ReLU 激活函数的输入为负时梯度为 0。

        通过对 ReLU 函数进行一些改动得到 LeakyRelU 函数,公式见下:

        超参数 α 定义函数”泄露“程度,它时 z < 0 时函数的斜率,一般设置为 0.01,这就确保了神经元不会死亡但会陷入长时间的昏迷。同时也有观点认为将 α 设置为 0.2 (大泄露)会有更好的性能。也可以在训练中随机选择 α ,在测试过程将其固定为平均值,会有不错的表现。

        也可以将 α 参数化得到 PReLU ,即在反向传播中修改,PReLU 在大型图像数据集的性能较优,但在小规模数据集上存在过拟合的风险。

        LeakyReLU 的变体 ELU(指数线性单位)具有更好的性能:更短的训练时时间、更好的测试结果,其定义如下:

         ELU 激活函数在 z 取负值时单元的平均输出接近 0 从而缓解梯度消失问题,超参数 α 定义了当 z 为较大负数时 ELU 逼近的值,一般设置为 1 ,并且 z 为负数时梯度不为 0 , 避免了神经元死亡问题;如果 α = 1 ,则函数在所有位置均平滑,有助于加速梯度下降。ELU 的计算速度较慢,但是会在训练过程具有更快的收敛速度。

        SELU (可扩展的 ELU)激活函数是 ELU 的变体,如果构建一个仅由密集层堆叠组成的神经网络,并且所有隐藏层均使用 SELU 激活函数,则该网络是自归一化的,即每层的输出倾向于在训练过程中保留平均值 0 和标准差 1 ,从而解决了梯度不稳定的问题;就结果而言 SELU 大大优于其它激活函数。神经网络自归一化的条件如下:

  • 输入特征必须标准化 (平均值是0,标准差是1)
  • 隐藏层参数必须使用 LeCun 正态初始化
  • 网络架构必须顺序
  • 所有层都是密集层

       激活函数的选择建议如下:

  • 通常 SELU > ELU > leakyReLU (及变体) > ReLU > tanh > logistic
  • 如果架构不能自归一化,则 ELU 性能可能优于 SELU
  • 如果关注运行时的延迟, leaky RELU 可能会是更好的选择
  • 如果不想调整其它的参数就可以使用 Keras 的默认参数值
  • 如果有空闲时间和计算能力则可以使用交叉验证来评估其他激活函数(比如过拟合则可以使用 RReLU,训练集很大则可以使用 PReLU)
  • 如果将速度放在首位,ReLU 仍可能是最好的选择

        使用 LeakyReLu 、PReLU激活函数的代码参考如下:

model = tf.keras.models.Sequential([
    # 其他层
    tf.keras.layers.LeakyReLU(alpha=0.2)    # leaky ReLU
    tf.keras.layers.PReLU()                 # PReLU
    # 其它层
])

        使用 SELU 时,在创建层时设置 activation="selu" 和  kernel_initializer="lecun_normal":

tf.keras.layers.Dense(10, activation="selu", kernel_initializer="lecun_normal")
1.2.1 tf.keras.layers.LeakyReLU
tf.keras.layers.LeakyReLU(
    alpha=0.3, **kwargs
)
参数注释
alpha负斜率系数
 1.2.2 tf.keras.layers.PReLU
tf.keras.layers.PReLU(
    alpha_initializer='zeros', alpha_regularizer=None,
    alpha_constraint=None, shared_axes=None, **kwargs
)
参数注释
alpha_initializer权重的初始化函数
alpha_regularizer权重的正则化器
alpha_constraint权重的约束
其它见 tf.keras.layers.PReLU  |  TensorFlow Core v2.6.0
1.3 批量归一化

        利用 Keras 实现批量归一化非常简单,只需要在每个隐藏层的激活函数之前或hi周添加一个 BatchNormalization 层,然后可选地在模型的第一层加一个 BN 层。

model = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape=[28, 28]), 
    tf.keras.layers.BatchNormalization(), 
    tf.keras.layers.Dense(300, activation='elu', kernel_initializer='he_normal'), 
    tf.keras.layers.BatchNormalization(), 
    tf.keras.layers.Dense(100, activation='elu', kernel_initializer='he_normal'), 
    tf.keras.layers.BatchNormalization(), 
    tf.keras.layers.Dense(10, activation='softmax')])

        在层次较少的网络中批量归一化不会有比较明显的结果,但在深层次的网络中影响巨大。 

        模型摘要如下:

         每个 BN 层为每个输入添加了 4 个参数,其中两个参数均值和标准差不受反向传播的影响,是不可训练参数。可以计算出 BN 添加的总共参数个数为:784*4 + 300*4 + 100*4 = 4736,而不可训练的参数为 4736 / 2 = 2368 个。

[(var.name, var.trainable) for var in model.layers[1].variables]

# 输出
[('batch_normalization_1/gamma:0', True), 
('batch_normalization_1/beta:0', True), 
('batch_normalization_1/moving_mean:0', False), 
('batch_normalization_1/moving_variance:0', False)]

        上述代码是在激活函数后添加的 BN 操作,若在激活函数前添加 BN ,则隐藏层中删除激活函数并将其作为单独层添加到 BN 之后,并且由于批量归一化的输入包含偏移参数,因此可以删除上一层的偏置项。

model = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape=[28, 28]),
    tf.keras.layers.BatchNormalization(),
    tf.keras.layers.Dense(300, kernel_initializer='he_normal', use_bias=False),
    tf.keras.layers.BatchNormalization(),
    tf.keras.layers.Activation('elu'),
    tf.keras.layers.Dense(100, kernel_initializer='he_normal', use_bias=False),
    tf.keras.layers.BatchNormalization(),
    tf.keras.layers.Activation('elu'),
    tf.keras.layers.Dense(10, activation='softmax')
])
 1.4 梯度裁剪

        梯度裁剪即在反向传播中裁剪梯度,使其不会超过某个阈值。Keras 实现代码如下:

optimizer = tf.keras.optimizers.SGD(clipvalue=1.0)
model.compile(loss='mse', optimizer=optimizer)

        该优化器会将梯度向量的每个分量都限制在 -1.0 到 1.0 之间,但可能会改变梯度向量的方向,如会将 [0.9, 100.0] 裁剪成 [0.9, 1.0];若不想改变梯度向量的方向,应使用 clipnorm 参数代替 clipvalue。

1.4.1 tf.keras.optimizers.SGD
tf.keras.optimizers.SGD(
    learning_rate=0.01, momentum=0.0, nesterov=False, name='SGD', **kwargs
)

        详情见 tf.keras.optimizers.SGD  |  TensorFlow Core v2.6.0 

二、重用预训练层——解决训练数据不同

        如果现有任务与曾经训练好的模型的功能相似,则可以通过重用已训练好模型的较低层来提高训练速度,减少训练数据,这就是迁移学习。

        往往需要添加预处理步骤将其调整为原始模型所需的大小,当输入具有相似的低级特征时迁移学习最有效。任务越相似,可重用的层数越多。

        首先冻结所有可重复使用的层,然后训练模型并查看表现;随后尝试解冻上部隐藏层的一两层并对其训练查看性能是否改善;训练数据越多解冻层数可以越多;解冻重用层也可以降低其学习率。

2.1 Keras 迁移学习

        通过给 Sequential() 函数传入已训练好模型的重用层来创建第二个模型,注意两个模型共享低层,所以需要对已训练好的模型克隆并传递连接权重从而做好备份。

from tensorflow import keras

model_A = keras.models.load_model('../chapter_10/model1.h5')
model_A.summary()

model_B = keras.Sequential(model_A.layers[:-1])
model_B.add(keras.layers.Dense(1, activation='sigmoid'))
# 此时模型 A,B 共享隐藏层,所以训练模型 B 也会改变模型 A
# 对模型 A 克隆
model_clone = keras.models.clone_model(model_A)
model_clone.set_weights(model_A.get_weights())

# 训练的前几个轮次冻结重用层
for layer in model_B.layers[:-1]:
    layer.trainable = False

model_B.compile(loss='binary_crossentropy', optimizer='sgd', metrics=['accuracy'])

# 后面开始训练模型

        由于新模型 B 的输出层是随机初始化,因而会产生较大的错误,并导致较大的错误梯度,从而破坏重用权重;为此可以在前几个训练模型冻结重用的层。通过将各层的 trainable 属性设置为 False 来冻结该层。然后在训练几个轮次后再解冻重用层。

        需要注意迁移学习在小型密集网络中不能很好地工作,迁移学习最适合使用深度卷积神经网络,更倾向于学习较为通用的特征检测器。

2.1.1 tf.keras.models.clone_model
tf.keras.models.clone_model(
    model, input_tensors=None, clone_function=None
)
参数注释
model模型实例
其它tf.keras.models.clone_model  |  TensorFlow Core v2.6.0
2.2 无监督预训练

        在训练数据较少,没有相似的训练模型,且收集带有标签的训练数据代价较大时,可以执行无监督预训练(如自动编码器或 GAN),然后可以重用自动编码器的较低层或 GAN 编码器的较低层,并在顶部添加输出层,然后使用有监督学习来微调最终网络。

2.3 辅助任务的预训练

        可以在辅助任务上训练第一个神经网络并轻松获得或生成标记的训练数据,然后对实际任务重用该网络较低层。

        第一个神经网络的较低层将学习特征检测器,第二个神经网络将重用这些特征检测。

三、优化器优化 3.1 动量优化

        传统的梯度下降通过直接减去权重的成本函数  的梯度乘以学习率  来更新权重 θ。公式为:

        而动量优化则关心先前的梯度:每次迭代中,从动量向量 m 中减去局部梯度,并通过添加该动量向量来更新权重。

        动量优化引入了一个超参数 β ,其值为 0 (高摩擦) 到 1(低摩擦)之间,一般取 0.9。但是在实践中会有很好的效果,一般比常规的梯度下降要快。

        实现代码如下:

optimizer = keras.optimizers.SGD(learning_rate=0.001, momentum=0.9)
3.2 Nesterov 加速梯度

        动量优化的一个变体就是 NAG 方法,它不是在局部位置 θ 处更新动量,而是在 θ+βm 处测量成本函数梯度。因为通常动量会指向正确的方向,因此使用在该方向上测得更远的梯度而不是原始位置上的梯度会更准确一些。NAG 通常比常规动量优化更快,使用方式如下:

optimizer = keras.optimizers.SGD(learning_rate=0.001, momentum=0.9, nesterov=True)
3.3 AdaGrad

        下降太快,容易无法收敛到全局最优解,不适合训练深度神经网络

3.4 RMSProp
optimizer = keras.optimizers.RMSprop(learning_rate=0.001, rho=0.9)
 3.5 Adam

        Adam 代表自适应矩估计,结合了动量优化和 RMSProp 的思想;它是一种自适应学习率算法,因此对超参数学习率只需要较少的调整。

optimizer = keras.optimizers.Adam(learning_rate=0.001, beta_1=0.9, beta_2=0.999)
3.6 学习率调度 3.6.1 幂调度

        将学习率设置为迭代次数 t 的函数: 。初始学习率、幂 c (一般是1)和步骤 s 是超参数,学习率在每一步中会下降,在 s 个步骤后下降到一半... 此调度下降速度由快到慢。

        在 keras 中实现幂调度只需要设置 SGD 方法的超参数 decay,decay 即 s 的倒数,Keras 默认 c 的值是 1 。

optimizer = keras.optimizers.SGD(learning_rate=0.01, decay=1e-4)
3.6.2 指数调度

        将学习速率设置为  ,学习率每 s 步下降十倍。

from tensorflow import keras

# 指数调度
def exp_decay_fn(epoch):
    return 0.01*0.1**(epoch/20)

# 如果不想对初始学习率和 s 进行硬编码,可以创建如下函数:
def exp_decay(lr0, s):
    def exp_decay_fn(epoch):
        return lr0 * 0.1 ** (epoch / 20)
    return exp_decay_fn

# 然后创建一个 LearningRateScheduler 回调函数,并将该回调函数传递给 fit 方法的 callbacks 参数
lr_scheduler = keras.callbacks.LearningRateScheduler(exp_decay)
 tf.keras.callbacks.LearningRateScheduler
tf.keras.callbacks.LearningRateScheduler(
    schedule, verbose=0
)
参数注释
schedule一个函数,它以轮次索引(整数,从 0 开始索引)和当前学习率(浮点数)作为输入,并返回一个新的学习率作为输出(浮点数)
其它tf.keras.callbacks.LearningRateScheduler  |  TensorFlow Core v2.6.0

        保存模型会同时保存优化率和学习器,所以加载训练后的模型也会加载该调度函数,但如果调度函数使用了 epoch 参数,epoch 参数不会被保存;对此需要在 fit 方法设置 initial_epoch 参数确定正确的 epoch 值。 

3.6.3 分段恒定调度

        对一些轮次使用恒定的学习率,对另外一些轮次使用较小的学习率等。

# 分段恒定调度
def piecewise_constant_fn(epoch):
    if epoch < 5:
        return 0.01
    elif epoch < 15:
        return 0.005
    else:
        return 0.001
# 后续同指数调度
3.6.4 性能调度

        每 N 步测量一次验证误差,并且当误差停止下降时,将学习率降低 λ 倍。

lr_scheduler = keras.callbacks.ReduceLRonPlateau(factor=0.5, patience=5)

        上述代码意思是每当连续 5 个轮次的最好验证损失没有改善时,将学习率乘以 0.5 。

tf.keras.callbacks.ReduceLRonPlateau
tf.keras.callbacks.ReduceLRonPlateau(
    monitor='val_loss', factor=0.1, patience=10, verbose=0,
    mode='auto', min_delta=0.0001, cooldown=0, min_lr=0, **kwargs
)
参数注释
factor学习率降低的倍数
patience没有改善的轮次数
其它tf.keras.callbacks.ReduceLRonPlateau  |  TensorFlow Core v2.6.0
3.6.5 1 周期调度 

        该调度算法从提高初始学习率开始,中途线性增长到一个值,然后在训练的后半部分再次线性降低到初始学习率,最后的几个轮次再次将学习线性降低几个数量级。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/313648.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号