pytorch-13_3 模型参数初始化方法:Xavier和Kaiming(HE)

2024-05-27 11:28

本文主要是介绍pytorch-13_3 模型参数初始化方法:Xavier和Kaiming(HE),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、激活函数使用过程中的优化

不同激活函数在深层次神经网络运行时存在的不同问题

  • ReLU激活函数叠加后出现的模型失效问题,也就是Dead ReLU Problem(神经元活性失效问题)。
  • Sigmoid激活函数堆叠后出现的问题,本质上就是梯度消失所导致的问题。
  • tanh激活函数堆叠所导致的迭代过程剧烈波动的问题,也被称为迭代不平稳,需要优化迭代过程来解决。

  从本节开始,将正式进入到优化方法的具体方法部分内容。首先是关于激活函数使用过程的优化。激活函数的使用能够有效提升神经网络模型效果,但激活函数的简单叠加却会让模型出现很多问题。本节将从堆叠激活函数遇到的问题入手,讨论造成这些问题的根源,以及如何通过一些优化方法解决这些问题。

1、梯度消失与梯度爆炸

1.1 梯度消失与梯度爆炸的概念

  对于神经网络这个复杂系统来说,在模型训练过程中,一个最基础、同时也最常见的问题,就是梯度消失和梯度爆炸。

  总结一下,不同层参数的梯度在计算过程中都有很大的差异,并且这种差异是一种累乘效应,我们也可以简单理解为是一种伴随着层数增加指数级变化的差异。而这种累乘效应会导致线性层参数的一部分梯度过大而另一部分过小,从而影响模型平稳训练。而从具体原因来说,每一层参数的梯度主要和两个因素相关,其一是线性层输入数据,如 X X X F ( X ∗ W ) F(X*W) F(XW),其二则是激活函数导函数计算结果 f ( X ∗ w 1 ) f(X*w_1) f(Xw1)

1.2 sigmoid激活函数的梯度消失问题探讨

1.3 tanh激活函数的梯度爆炸问题探讨

1.4 模型参数及梯度提取方法

实操代码演示

2、梯度不平稳的解决方案

  通过对Sigmoid和tanh激活函数叠加后的模型梯度变化情况分析,我们不难发现,梯度不平稳是影响模型建模效果的非常核心的因素。

  整体来看,针对梯度不平稳的解决方案(优化方法)总共分为五类,分别是参数初始化方法、输入数据的归一化方法、衍生激活函数使用方法、学习率调度方法以及梯度下降优化方法。接下来,先介绍所有上述优化算法的一个基本理论,由Xavier Glorot在2010提出的Glorot条件。

值得注意的是,虽然不同优化算法有不同的出发点和不同的论证方式,但基本都可以从Glorot条件出发进行思考。

2.1 Zero-centered 数据的零对称属性

  • 为了确保各层网络梯度的平稳性和学习的平稳性,通常将梯度及输入数据转换为Zero-centered data。

  • 但是转换为Zero-centered data的过程中,不能将参数的初始值全部设为0,需要借助统计工具生成均值是0的随机数,也就是0均值的均匀分布或者是0均值的高斯分布。

2.2 Glorot条件:初始化参数方差满足的条件

  • 上述随机数的方差,需要满足Glorot条件(正向传播时数据方差保持一致、反向传播时参数梯度方差保持一致的条件)。满足该条件的模型能够进行有效平稳的训练。

2.3 Xavier方法:模型初始化参数设计方法

  • 模型初始化参数设计方法:Xavier方法。该方法通过满足Glorot条件创建Zero-Centered的初始化参数时参数的方差。
  • Xavier初始化能够在Sigmoid和tanh激活函数叠加的神经网络中起到一定的效果。解决Sigmoid和tanh激活函数使用过程中可能存在的梯度消失或梯度爆炸问题。

2.4 模型初始化参数的取值影响

模型初始化参数不同,会得到不同的建模结果。换句话说,模型初始化时得到的不同参数,本质上等价于在损失函数上找到了不同的初始点。而同一损失函数,初始点选取的不同会影响最终迭代结果。

  • 接下来我们通过一个实验来说明初始值的更换对模型结果的影响。在模型实例化过程中,采用不同随机数种子,就相当于选取了不同的模型初始参数。
# 创建随机数种子
torch.manual_seed(420)  # 实例化模型
relu_model3 = ReLU_class3(bias=False)              # 核心参数
num_epochs = 20
lr = 0.03# 模型训练
train_l, test_l = model_train_test(relu_model3, train_loader,test_loader,num_epochs = num_epochs, criterion = nn.MSELoss(), optimizer = optim.SGD, lr = 0.03, cla = False, eva = mse_cal)# 绘制图像,查看MSE变化情况
plt.plot(list(range(num_epochs)), train_l, label='train_mse')
plt.plot(list(range(num_epochs)), test_l, label='test_mse')
plt.legend(loc = 4)

在这里插入图片描述

# 创建随机数种子
torch.manual_seed(29)  # 实例化模型
relu_model3 = ReLU_class3(bias=False)              # 核心参数
num_epochs = 20
lr = 0.03# 模型训练
train_l, test_l = model_train_test(relu_model3, train_loader,test_loader,num_epochs = num_epochs, criterion = nn.MSELoss(), optimizer = optim.SGD, lr = 0.03, cla = False, eva = mse_cal)# 绘制图像,查看MSE变化情况
plt.plot(list(range(num_epochs)), train_l, label='train_mse')
plt.plot(list(range(num_epochs)), test_l, label='test_mse')
plt.legend(loc = 4)

在这里插入图片描述

结果讨论:

  • 初始参数值的选取不仅会影响模型收敛速度,甚至在某些情况下还会影响模型的最终表现。
  • 造成此现象的根本原因还是在于神经网络模型在进行训练时,不确定性过多,而在一个拥有诸多不确定性的系统中再加上不确定的初始参数,初始参数的不确定性会被这个系统放大。
  • 值得一提的是,每一个epoch中的每一次迭代并不是在同一个损失函数上一步步下降的,当我们使用小批量梯度下降算法时,带入不同批的数据,实际创建的损失函数也会不同。

3、Dead ReLU Problem与学习率优化

3.1 Dead ReLU Problem直接表现

之前的ReLU叠加模型,在迭代多次后在MSE取值高位收敛的情况,其实就是出现了神经元活性失效所导致的问题。
在这里插入图片描述

3.2 Dead ReLU Problem成因分析

  • 神经元活性失效问题和ReLU激活函数本身特性有关。

  • 观察ReLU激活函数函数图像与导函数图像得知,对于ReLU激活函数来说,只要激活函数接收到的数据小于0,输出结果就全是0,由于ReLU的导函数是分段常数函数且接收数据为负时导数为0,因此如果ReLU输出结果为零,则反向传播结果、也就是各层的梯度,也都是零,此时参数将无法通过迭代更新。

  • 进一步的,如果在某种参数情况下,整个训练数据集输入模型之后输出结果都是0,则在小批量梯度下降的情况下,每次再挑选出一些数据继续进行迭代,仍然无法改变输出结果是0的情况,此时参数无法得到更新、进而下次输入的小批数据结果还是零、从而梯度为0、从而参数无法更新…至此陷入死循环,模型失效、激活函数失去活性,也就出现了Dead ReLU Problem。
    在这里插入图片描述

  • 出现Dead ReLU Problem问题的概率,伴随ReLU层的增加而增加的。在多层ReLU嵌套的情况下,输出结果 y ^ \hat y y^取值为0的概率就更大了,出现Dead ReLU Problem的概率也就更高了。而同时,根据各层参数的梯度计算公式,只要其中任意一层输出结果是0,则所有层参数的梯度均为0。

3.3 调整学习率:缓解Dead ReLU Problem

  在所有的解决Dead ReLU Problem的方法中,最简单的一种方法就是调整学习率。尽管我们知道,ReLU叠加越多层越容易出现神经元活性失效,但我们可以简单通过降低学习率的方法来缓解神经元活性失效的问题。甚至可以说这是一种通用且有效的方法。
  学习率作为模型重要的超参数,会在各方面影响模型效果,此前我们曾介绍学习率越小、收敛速度就越慢,而学习率过大、则又容易跳过最小值点造成模型结果震荡。对于ReLU激活函数来说,参数“稍有不慎”就容易落入输出值全为0的陷阱,因此训练过程需要更加保守,采用更小的学习率逐步迭代。当然学习率减少就必然需要增加迭代次数,但由于ReLU激活函数计算过程相对简单,增加迭代次数并不会显著增加计算量。

# 核心参数
lr = 0.030# 核心参数
lr = 0.001

在这里插入图片描述
在这里插入图片描述
通过调小学习率,模型更能够避开神经元活性失效陷阱。

4、nn.Sequential快速建模方法及nn.init模型参数自定义方法

# 设置随机数种子
torch.manual_seed(25)# 一、nn.Sequential快速构建模型
relu_test = nn.Sequential(nn.Linear(2, 2, bias=False), nn.ReLU(), nn.Linear(2, 1, bias=False))# 二、nn.init初始化模型参数
# (1)`.nn.init.uniform_`方法,新生成的参数服从均匀分布
nn.init.uniform_(list(relu_test.parameters())[0], 0, 1)         # 设置参数值为均匀分布在0,1区间内的随机数# `torch.nn.init.xavier_uniform_`
nn.init.xavier_uniform_(list(relu_test.parameters())[0])        # Xavier均匀分布的参数创建# `torch.nn.init.kaiming_uniform_`
nn.init.kaiming_uniform_(list(relu_test.parameters())[0])        # HE均匀分布的参数创建# (2)`.nn.init.normal_`方法,新生成的参数服从正态分布
nn.init.normal_(list(relu_test.parameters())[0], 0, 1)          # 服从均值为0、标准差为1的正态分布# `torch.nn.init.xavier_normal_`
nn.init.xavier_normal_(list(relu_test.parameters())[0])        # Xavier正态分布的参数创建# `torch.nn.init.kaiming_normal_`
nn.init.kaiming_normal_(list(relu_test.parameters())[0])        # HE正态分布的参数创建# (3)`.nn.init.constant_`方法,新生成的参数值为某一常数
nn.init.constant_(list(relu_test.parameters())[0], 1)           # 参数全为1

5、Xavier参数初始化方法

5.1 Xavier均匀分布的参数创建nn.init.xavier_uniform_

5.1.1 Sigmoid_class3
# 设置随机数种子
torch.manual_seed(420)  # 创建最高项为2的多项式回归数据集
features, labels = tensorGenReg(w=[2, -1], bias=False, deg=2)# 进行数据集切分与加载
train_loader, test_loader = split_loader(features, labels)# 初始核心参数
lr = 0.03
num_epochs = 20# 实例化模型
sigmoid_model3 = Sigmoid_class3()                   # 保留原参数
sigmoid_model3_init = Sigmoid_class3()              # 使用Xavier初始化参数# 修改init模型初始参数
for m in sigmoid_model3_init.modules():if isinstance(m, nn.Linear):nn.init.xavier_uniform_(m.weight)           # 使用Xavier初始化参数# 创建模型容器
model_l = [sigmoid_model3, sigmoid_model3_init]           
name_l = ['sigmoid_model3', 'sigmoid_model3_init']# 多个模型训练
train_l, test_l = model_comparison(model_l = model_l, name_l = name_l, train_data = train_loader,test_data = test_loader,num_epochs = 2, criterion = nn.MSELoss(), optimizer = optim.SGD, lr = lr, cla = False, eva = mse_cal)# 模型梯度
weights_vp(sigmoid_model3, att="grad")
weights_vp(sigmoid_model3_init, att="grad")

在这里插入图片描述
在这里插入图片描述

我们发现,在num_epochs取值为2的时候(只迭代了一轮),经过Xavier初始化的模型梯度整体更加稳定,并且没有出现梯度消失的情况,反观原始模型sigmoid_model2,第一层的梯度已经非常小了,已经出现了梯度消失的倾向。而我们知道,各层梯度的情况就代表着模型学习的状态,很明显经过初始化的模型各层都处于平稳学习状态,此时模型收敛速度较快。我们也可以通过MSE曲线进行验证。

# 训练误差 损失函数
for i, name in enumerate(name_l):plt.plot(list(range(num_epochs)), train_l[i], label=name)
plt.legend(loc = 1)
plt.title('mse_train')# 测试误差 损失函数
for i, name in enumerate(name_l):plt.plot(list(range(num_epochs)), test_l[i], label=name)
plt.legend(loc = 1)
plt.title('mse_test')

在这里插入图片描述
在这里插入图片描述
  由此我们可知,Xavier初始化的作用核心在于保证各层梯度取值的平稳分布,从而确保各层模型学习的有效性,最终在模型结果的表现上,经过Xavier初始化参数的模型学习效率更高、收敛速度更快。上述结果也验证了Xavier初始化有效性。

5.1.2 Sigmoid_class4
# 设置随机数种子
torch.manual_seed(24)  # 实例化模型
sigmoid_model4 = Sigmoid_class4()                   # 保留原参数
sigmoid_model4_init = Sigmoid_class4()              # 使用Xavier初始化参数# 修改init模型初始参数
for m in sigmoid_model4_init.modules():if isinstance(m, nn.Linear):nn.init.xavier_uniform_(m.weight)# 创建模型容器
model_l = [sigmoid_model4, sigmoid_model4_init]           
name_l = ['sigmoid_model4', 'sigmoid_model4_init']# 核心参数
lr = 0.03
num_epochs = 40# 模型训练
train_l, test_l = model_comparison(model_l = model_l, name_l = name_l, train_data = train_loader,test_data = test_loader,num_epochs = num_epochs, criterion = nn.MSELoss(), optimizer = optim.SGD, lr = lr, cla = False, eva = mse_cal)# 训练误差 损失函数
for i, name in enumerate(name_l):plt.plot(list(range(num_epochs)), train_l[i], label=name)
plt.legend(loc = 1)
plt.title('mse_train')# 测试误差 损失函数
for i, name in enumerate(name_l):plt.plot(list(range(num_epochs)), test_l[i], label=name)
plt.legend(loc = 1)
plt.title('mse_test')

在这里插入图片描述
在这里插入图片描述

  • sigmoid_model4是出现严重梯度消失的模型,由于前几层基本丧失学习能力,sigmoid_model4本身效果并不好。但加入Xavier初始化之后,我们发现,init模型能够极大程度规避梯度消失问题,从而获得更好的效果。
  • 不过正如此前所说,相比于sigmoid激活函数,Xavier初始化方法更适用于tanh激活函数,核心原因在于tanh激活函数本身能够生成Zero-centered
    Data,配合Xavier初始化生成的参数,能够更好的确保各层梯度平稳、确保各层平稳学习。
5.1.3 tanh_class3
# 设置随机数种子
torch.manual_seed(420)  # 实例化模型
tanh_model3 = tanh_class3()                   # 保留原参数
tanh_model3_init = tanh_class3()              # 使用Xavier初始化参数# 修改init模型初始参数
for m in tanh_model3_init.modules():if isinstance(m, nn.Linear):nn.init.xavier_uniform_(m.weight)# 创建模型容器
model_l = [tanh_model3, tanh_model3_init]           
name_l = ['tanh_model3', 'tanh_model3_init']# 核心参数
lr = 0.03
num_epochs = 40# 模型训练
train_l, test_l = model_comparison(model_l = model_l, name_l = name_l, train_data = train_loader,test_data = test_loader,num_epochs = num_epochs, criterion = nn.MSELoss(), optimizer = optim.SGD, lr = lr, cla = False, eva = mse_cal)# 查看模型梯度
weights_vp(tanh_model3, att="grad")
weights_vp(tanh_model3_init, att="grad")

在这里插入图片描述
在这里插入图片描述

同样,能够看出经过Xavier参数初始化后的模型梯度更加平稳,进而我们判断,经过初始化之后的模型初始迭代时收敛速度更快

# 训练误差
for i, name in enumerate(name_l):plt.plot(list(range(num_epochs)), train_l[i], label=name)
plt.legend(loc = 1)
plt.title('mse_train')# 测试误差
for i, name in enumerate(name_l):plt.plot(list(range(num_epochs)), test_l[i], label=name)
plt.legend(loc = 1)
plt.title('mse_test')

在这里插入图片描述
在这里插入图片描述

同样我们能够发现,模型收敛速度更快,迭代多轮之后也变得更加稳定。

5.2 Xavier高斯分布的参数创建nn.init.xavier_normal_

5.2.1 Sigmoid_class3

5.2.2 Sigmoid_class4

5.2.3 tanh_class3

6、HE参数初始化:Kaiming方法

  • HE参数初始化:Kaiming方法,是目前通用的针对ReLU激活函数的初始化参数方法。
  • 尽管Xavier初始化能够在Sigmoid和tanh激活函数叠加的神经网络中起到一定的效果,但由于ReLU激活函数属于非饱和类激活函数,并不会出现类似Sigmoid和tanh激活函数使用过程中可能存在的梯度消失或梯度爆炸问题。

  • 因为ReLU激活函数的不饱和特性,ReLU激活函数的叠加极有可能出现神经元活性消失的问题,很明显,该类问题无法通过Xavier初始化解决。

  • 但是对参数的初始值进行合理设置,仍然是保证模型有效性的有效方法,同样也能一定程度上解决ReLU激活函数的神经元活性消失问题。

  • 目前通用的针对ReLU激活函数的初始化参数方法,是由何凯明在2015年的《Delving
    Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet
    Classification》一文中所提出的HE初始化方法,也被称为Kaiming方法。原文地址。

6.1 对ReLU_class3使用nn.init.kaiming_uniform_ 均匀分布

在这里插入图片描述
在这里插入图片描述
我们发现,使用HE初始化之后模型收敛速度明显提升。

6.2 对ReLU_class3使用nn.init.kaiming_normal_ 高斯分布

在这里插入图片描述
在这里插入图片描述
同样,经过HE初始化后的参数,模型收敛速度更快,也证明了HE初始化的有效性。

7、总结与讨论

7.1 激活函数问题及优化

  • sigmoid和tanh,可能出现梯度消失和梯度爆炸问题,通过Xavier参数初始化进行优化。
  • relu,可能出现神经元活性消失的问题,通过调节学习率或者HE参数初始化进行优化。

7.1 参数初始化的方法总结

# 设置随机数种子
torch.manual_seed(25)# 一、nn.Sequential快速构建模型
relu_test = nn.Sequential(nn.Linear(2, 2, bias=False), nn.ReLU(), nn.Linear(2, 1, bias=False))# 二、nn.init初始化模型参数
# (1)`.nn.init.uniform_`方法,新生成的参数服从均匀分布
nn.init.uniform_(list(relu_test.parameters())[0], 0, 1)         # 设置参数值为均匀分布在0,1区间内的随机数# `torch.nn.init.xavier_uniform_`
nn.init.xavier_uniform_(list(relu_test.parameters())[0])        # Xavier均匀分布的参数创建# `torch.nn.init.kaiming_uniform_`
nn.init.kaiming_uniform_(list(relu_test.parameters())[0])        # HE均匀分布的参数创建# (2)`.nn.init.normal_`方法,新生成的参数服从正态分布
nn.init.normal_(list(relu_test.parameters())[0], 0, 1)          # 服从均值为0、标准差为1的正态分布# `torch.nn.init.xavier_normal_`
nn.init.xavier_normal_(list(relu_test.parameters())[0])        # Xavier正态分布的参数创建# `torch.nn.init.kaiming_normal_`
nn.init.kaiming_normal_(list(relu_test.parameters())[0])        # HE正态分布的参数创建# (3)`.nn.init.constant_`方法,新生成的参数值为某一常数
nn.init.constant_(list(relu_test.parameters())[0], 1)           # 参数全为1

7.2 讨论:参数初始化的作用局限

  截止目前,我们讨论了围绕Glorot条件进行的参数初始化方法,当然,合理的设置初始参数值能够一定程度上使得模型各层都得到有效的学习,模型训练过程更加平稳、收敛速度也更快。但由于我们设置的是初始条件,伴随着模型不断训练,由于受到激活函数导函数本身特性影响,仍然有可能在迭代过程中出现梯度不均衡的现象。
  然而模型一旦开始训练,我们是不能手动参与修改模型参数的。那此时应该如何处理梯度不均衡的问题呢?我们知道,影响梯度计算的三个核心因素,分别是参数状态值、激活值和输入的数据,参数状态值由模型迭代的数学过程决定,激活值很大程度上由我们所选取的激活函数决定,如果从Glorot条件入手,我们就只剩下一个可以人工修改的选项:每一个线性层接收到的数据。

这篇关于pytorch-13_3 模型参数初始化方法:Xavier和Kaiming(HE)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1007339

相关文章

SpringBoot请求参数接收控制指南分享

《SpringBoot请求参数接收控制指南分享》:本文主要介绍SpringBoot请求参数接收控制指南,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Spring Boot 请求参数接收控制指南1. 概述2. 有注解时参数接收方式对比3. 无注解时接收参数默认位置

Windows 上如果忘记了 MySQL 密码 重置密码的两种方法

《Windows上如果忘记了MySQL密码重置密码的两种方法》:本文主要介绍Windows上如果忘记了MySQL密码重置密码的两种方法,本文通过两种方法结合实例代码给大家介绍的非常详细,感... 目录方法 1:以跳过权限验证模式启动 mysql 并重置密码方法 2:使用 my.ini 文件的临时配置在 Wi

MySQL重复数据处理的七种高效方法

《MySQL重复数据处理的七种高效方法》你是不是也曾遇到过这样的烦恼:明明系统测试时一切正常,上线后却频频出现重复数据,大批量导数据时,总有那么几条不听话的记录导致整个事务莫名回滚,今天,我就跟大家分... 目录1. 重复数据插入问题分析1.1 问题本质1.2 常见场景图2. 基础解决方案:使用异常捕获3.

最详细安装 PostgreSQL方法及常见问题解决

《最详细安装PostgreSQL方法及常见问题解决》:本文主要介绍最详细安装PostgreSQL方法及常见问题解决,介绍了在Windows系统上安装PostgreSQL及Linux系统上安装Po... 目录一、在 Windows 系统上安装 PostgreSQL1. 下载 PostgreSQL 安装包2.

Python使用getopt处理命令行参数示例解析(最佳实践)

《Python使用getopt处理命令行参数示例解析(最佳实践)》getopt模块是Python标准库中一个简单但强大的命令行参数处理工具,它特别适合那些需要快速实现基本命令行参数解析的场景,或者需要... 目录为什么需要处理命令行参数?getopt模块基础实际应用示例与其他参数处理方式的比较常见问http

SQL中redo log 刷⼊磁盘的常见方法

《SQL中redolog刷⼊磁盘的常见方法》本文主要介绍了SQL中redolog刷⼊磁盘的常见方法,将redolog刷入磁盘的方法确保了数据的持久性和一致性,下面就来具体介绍一下,感兴趣的可以了解... 目录Redo Log 刷入磁盘的方法Redo Log 刷入磁盘的过程代码示例(伪代码)在数据库系统中,r

Python实现图片分割的多种方法总结

《Python实现图片分割的多种方法总结》图片分割是图像处理中的一个重要任务,它的目标是将图像划分为多个区域或者对象,本文为大家整理了一些常用的分割方法,大家可以根据需求自行选择... 目录1. 基于传统图像处理的分割方法(1) 使用固定阈值分割图片(2) 自适应阈值分割(3) 使用图像边缘检测分割(4)

Java中Switch Case多个条件处理方法举例

《Java中SwitchCase多个条件处理方法举例》Java中switch语句用于根据变量值执行不同代码块,适用于多个条件的处理,:本文主要介绍Java中SwitchCase多个条件处理的相... 目录前言基本语法处理多个条件示例1:合并相同代码的多个case示例2:通过字符串合并多个case进阶用法使用

Python中__init__方法使用的深度解析

《Python中__init__方法使用的深度解析》在Python的面向对象编程(OOP)体系中,__init__方法如同建造房屋时的奠基仪式——它定义了对象诞生时的初始状态,下面我们就来深入了解下_... 目录一、__init__的基因图谱二、初始化过程的魔法时刻继承链中的初始化顺序self参数的奥秘默认

html5的响应式布局的方法示例详解

《html5的响应式布局的方法示例详解》:本文主要介绍了HTML5中使用媒体查询和Flexbox进行响应式布局的方法,简要介绍了CSSGrid布局的基础知识和如何实现自动换行的网格布局,详细内容请阅读本文,希望能对你有所帮助... 一 使用媒体查询响应式布局        使用的参数@media这是常用的