参数初始化方法总览图
一.常见的参数初始化方法
在人工智能算法中,参数初始化是深度学习模型训练过程中的一个关键步骤,它对模型的性能和训练速度有着重要的影响。以下是一些常见的参数初始化方法:
-
零初始化:即将所有参数初始化为0。这种方法虽然简单,但会导致所有神经元的输出相同,因为它们将具有相同的权重,从而失去学习不同特征的能力。
-
随机初始化:这是一种基本的方法,通过从某个分布(如均匀分布或正态分布)中随机选择权重值。这种方法可以避免对称性问题,但可能会导致梯度消失或爆炸的问题。
-
Xavier/Glorot 初始化:这种方法考虑了前一层和后一层的节点数量,通过设置权重的方差来保持每层激活函数的方差大致相同,从而避免梯度消失或爆炸的问题。
-
He 初始化:这是针对ReLU激活函数的变体,它调整了权重的初始化标准差,以保持方差与输入的激活数成比例,从而减少ReLU激活函数带来的梯度消失问题。
-
预训练初始化:在某些情况下,可以使用预训练模型的参数作为初始化值,这通常在迁移学习中使用,可以提高模型在新任务上的性能。
-
固定值初始化:对于一些特定的参数,如偏置(Bias),可以使用一些经验固定的值进行初始化。
-
PID深度学习算法:这是一种新型的参数初始化方法,通过对输入数据的特征进行分析,为模型提供更加合理的参数初始化值,从而加速模型的训练过程,提高模型的性能。
-
其他高级方法:随着研究的发展,还有许多其他高级的参数初始化方法,如使用神经网络自身来预测参数的最优初始值等。
总之,正确的参数初始化可以加速模型的收敛,提高模型的泛化能力,并减少对初始参数选择的敏感性。然而,对于选择哪种初始化方法通常取决于具体的应用场景、网络结构和激活函数类型。在实践中,我们可能需要尝试多种方法来找到最适合特定任务的初始化策略。
基于以上给出的一系列参数初始化方法,我们将挑出其中相对较为重要且运用广泛的几种参数初始化方法进行详细地讲解,希望对大家的深度学习模型处理过程有所帮助,当然,相信大家也都看到了以上方法中一直出现两个名词:梯度消失和梯度下降,所以在此之前让我们先把这两个问题搞清楚。
二.梯度消失和梯度爆炸
定义: 梯度消失和梯度爆炸是深度神经网络训练中的两个常见问题,它们都与反向传播算法中的梯度传播
有关。
什么?反向传播忘记了? 那可不行,因为这两个问题和反向传播的过程紧密相连,笔者推荐个反向传播算法视频。
梯度:
在深度学习中,梯度通常指的是损失函数(目标函数)关于模型参数的导数。模型参数主要包括权重(weights) <math xmlns="http://www.w3.org/1998/Math/MathML"> w w </math>w和偏置(biases) <math xmlns="http://www.w3.org/1998/Math/MathML"> b b </math>b。梯度是一个向量,其元素是损失函数对每个参数的偏导数。
梯度的数学定义:
对于一个给定的损失函数 <math xmlns="http://www.w3.org/1998/Math/MathML"> L L </math>L,权重 <math xmlns="http://www.w3.org/1998/Math/MathML"> w w </math>w 和偏置 <math xmlns="http://www.w3.org/1998/Math/MathML"> b b </math>b 的梯度可以分别定义为:
<math xmlns="http://www.w3.org/1998/Math/MathML"> ∇ w L = ∂ L ∂ w \nabla_w L = \frac{\partial L}{\partial w} </math>∇wL=∂w∂L
<math xmlns="http://www.w3.org/1998/Math/MathML"> ∇ b L = ∂ L ∂ b \nabla_b L = \frac{\partial L}{\partial b} </math>∇bL=∂b∂L
这里的 <math xmlns="http://www.w3.org/1998/Math/MathML"> ∇ \nabla </math>∇ 表示梯度算子, <math xmlns="http://www.w3.org/1998/Math/MathML"> ∂ L ∂ w \frac{\partial L}{\partial w} </math>∂w∂L 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> ∂ L ∂ b \frac{\partial L}{\partial b} </math>∂b∂L 分别表示损失函数 <math xmlns="http://www.w3.org/1998/Math/MathML"> L L </math>L 对权重 <math xmlns="http://www.w3.org/1998/Math/MathML"> w w </math>w 和偏置 <math xmlns="http://www.w3.org/1998/Math/MathML"> b b </math>b 的偏导数。
1.梯度消失问题
定义:当神经网络层数较多时,反向传播过程中的梯度会通过多层权重进行连乘,如果权重或激活函数的导数较小,梯度会迅速减小,导致深层网络权重更新非常缓慢,这种现象称为梯度消失。
原因:
- 使用了导数很小的激活函数,如Sigmoid或Tanh。
- 权重初始化不当,如初始化得太小。
实例解释 : 假设我们有一个深层网络,每层使用Sigmoid激活函数。Sigmoid函数的输出范围是(0, 1),其导数为sigmoid(x)*(1-sigmoid(x))
(这个式子为算法函数的求导公式,这里不做过多推导)。当sigmoid(x)
接近0或1时,导数接近0(当你的导数接近0时,相乘后的结果会越来越小,而当导数接近1时,因为(1-sigmoid(x)
,所以实际上相乘的结果还是接近于0)。如果一个深层网络的每层输出都接近0或1,那么反向传播时梯度将非常小。
解决方法:
- 使用ReLU或其变体作为激活函数,因为它们在正区间的导数是常数(简单点说就是扩大了导数的取值范围)。
- 使用Xavier或He初始化方法,这些方法考虑了激活函数的特性,有助于梯度在各层间传播(考虑了整体导数的平衡性问题)。
- 使用批量归一化(Batch Normalization),它可以减少内部协变量偏移,加速训练过程。
2.梯度爆炸问题
定义:与梯度消失相反,梯度爆炸发生在梯度在反向传播过程中通过多层权重连乘变得非常大,导致权重更新过大,甚至导致数值计算不稳定。
原因:
- 权重初始化过大。
- 学习率设置过高。
实例解释 : 假设一个网络的权重初始化为一个较大的值,如10(即某个神经元的 <math xmlns="http://www.w3.org/1998/Math/MathML"> w w </math>w值为10)。在反向传播时,如果损失函数对权重的梯度为1,那么更新后的权重将变为20(这里是根据梯度下降算法得出的结果)。如果这个情况在多层网络中连续发生,梯度将指数级增长,导致权重变得非常大。
解决方法:
- 使用批量归一化(Batch Normalization),它可以减少内部协变量偏移,加速训练过程,并有助于稳定梯度。
- 使用梯度剪切(Gradient Clipping),限制梯度的最大值。
- 调整学习率,避免过大的更新。
- 使用预训练的权重,这些权重已经过优化,更稳定。
- 使用残差连接(Residual Connections),如在ResNet中,它们可以帮助梯度直接流向前面的层。
3.梯度下降参数更新(便于大家进一步理解参数的变化过程)
在深度学习中,一旦我们计算出损失函数相对于模型参数(权重 <math xmlns="http://www.w3.org/1998/Math/MathML"> w w </math>w 和偏置 <math xmlns="http://www.w3.org/1998/Math/MathML"> b b </math>b)的梯度,就可以使用这些梯度来更新参数,这个过程通常通过梯度下降或其变体来实现。以下是参数更新的基本步骤:
-
计算梯度 :首先,通过反向传播算法计算损失函数 <math xmlns="http://www.w3.org/1998/Math/MathML"> L L </math>L 相对于每个参数的梯度。对于权重和偏置,梯度分别是 <math xmlns="http://www.w3.org/1998/Math/MathML"> ∇ w L \nabla_w L </math>∇wL 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> ∇ b L \nabla_b L </math>∇bL。
-
选择学习率 :确定一个学习率 <math xmlns="http://www.w3.org/1998/Math/MathML"> η \eta </math>η,这是一个超参数,用于控制每次更新步长的大小
-
参数更新:使用梯度和学习率来更新每个参数。更新规则通常如下:
<math xmlns="http://www.w3.org/1998/Math/MathML"> w ← w − η ⋅ ∇ w L w \leftarrow w - \eta \cdot \nabla_w L </math>w←w−η⋅∇wL
<math xmlns="http://www.w3.org/1998/Math/MathML"> b ← b − η ⋅ ∇ b L b \leftarrow b - \eta \cdot \nabla_b L </math>b←b−η⋅∇bL
这里, <math xmlns="http://www.w3.org/1998/Math/MathML"> ← \leftarrow </math>← 表示赋值操作,即用右侧的表达式来更新左侧的变量。
梯度实例:
假设我们有一个简单的线性模型 <math xmlns="http://www.w3.org/1998/Math/MathML"> y = w ⋅ x + b y = w \cdot x + b </math>y=w⋅x+b,损失函数是均方误差(MSE):
<math xmlns="http://www.w3.org/1998/Math/MathML"> L = 1 2 ( t a r g e t − y ) 2 L = \frac{1}{2}(target - y)^2 </math>L=21(target−y)2
梯度计算如下:
<math xmlns="http://www.w3.org/1998/Math/MathML"> ∇ w L = − ( t a r g e t − y ) ⋅ x \nabla_w L = -(target - y) \cdot x </math>∇wL=−(target−y)⋅x
<math xmlns="http://www.w3.org/1998/Math/MathML"> ∇ b L = − ( t a r g e t − y ) \nabla_b L = -(target - y) </math>∇bL=−(target−y)
如果我们有一个训练样本 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( x , t a r g e t ) (x, target) </math>(x,target),学习率为 <math xmlns="http://www.w3.org/1998/Math/MathML"> η \eta </math>η,那么参数更新将是:
<math xmlns="http://www.w3.org/1998/Math/MathML"> w ← w + η ⋅ ( t a r g e t − y ) ⋅ x w \leftarrow w + \eta \cdot (target - y) \cdot x </math>w←w+η⋅(target−y)⋅x
<math xmlns="http://www.w3.org/1998/Math/MathML"> b ← b + η ⋅ ( t a r g e t − y ) b \leftarrow b + \eta \cdot (target - y) </math>b←b+η⋅(target−y)
4.总例
考虑一个简单的多层感知机(MLP),有3个隐藏层,每层有10个神经元,使用Sigmoid激活函数。假设所有权重初始化为1,学习率为1。
前向传播时,每层的输出将通过Sigmoid函数,其输出值范围为(0, 1)。反向传播时,对于每个神经元,梯度将是前一层梯度乘以当前层权重的连乘积。由于Sigmoid函数的导数在输出接近0或1时非常小,经过几层后,梯度将变得非常小,导致权重更新非常缓慢。
如果权重初始化为一个非常大的值,比如100,学习率也为1,在反向传播时,梯度将非常大,导致权重更新过大,可能会出现数值不稳定的情况。
5.结论
梯度消失和梯度爆炸问题都与梯度在反向传播中的传播方式有关。通过选择合适的激活函数、初始化方法、学习率和正则化技术,可以有效地解决这些问题。在实际应用中,通常需要结合多种策略来确保网络的稳定训练。
什么?还是不懂梯度消失和爆炸? OK 上视频!
三.全0初始化
定义: 全0初始化是一种参数初始化方法,其中神经网络的所有权重 <math xmlns="http://www.w3.org/1998/Math/MathML"> w w </math>w 和偏置 <math xmlns="http://www.w3.org/1998/Math/MathML"> b b </math>b 都被初始化为0。这种初始化方法非常简单,但在实际应用中,它通常不是一个好的选择,原因如下:
-
对称性问题:如果所有的权重和偏置都被初始化为0,那么在多层网络中,每一层的输出在前向传播时将完全相同,因为它们都是前一层输出的线性变换。这会导致网络无法打破对称性,即每个神经元将学习到相同的特征,从而无法有效地学习数据中的不同特征。
-
梯度消失:由于所有参数都是0,前向传播的输出也将是0,这将导致反向传播时所有梯度也都是0。因此,权重不会得到更新,训练过程将无法进行。
-
学习效率低下:即使在某些情况下,网络能够开始学习,由于初始参数的值完全相同,网络的学习效率也会非常低,因为它需要从完全相同的起点开始区分不同的特征。
1.为什么全0初始化不是一个好主意:
- 网络无法学习:由于对称性问题,网络中的每个神经元将输出相同的值,导致网络无法学习输入数据的不同特征。
- 训练过程停滞:由于梯度消失,权重更新将非常缓慢或根本不更新,导致训练过程停滞不前。
2.既然全0初始化的结果都是0,为什么还会存在这个初始化方法?
因为全0初始化在某些特定情况下可能有意义或作为某些算法的起点。
四.随机初始化(较常用)
定义: 随机初始化是深度学习中常用的一种参数初始化方法。在这种方法中,模型的权重和偏置被随机分配一个值,通常是从一个均匀分布或正态分布中采样得到的。随机初始化的目的是打破权重的对称性,使得每个神经元可以学习到不同的特征,从而避免在训练开始时所有神经元的输出完全相同。
1.随机初始化的特点:
-
打破对称性:随机初始化可以防止神经元之间的对称性,确保每个神经元学习不同的特征。
-
简单易实现:随机初始化方法简单,易于实现,不需要额外的计算资源。
-
防止过拟合:随机初始化可以增加模型的泛化能力,因为它为模型提供了一个随机的起点。
-
梯度消失/爆炸问题:随机初始化可能会导致梯度消失或梯度爆炸的问题,特别是在深层网络中。这是因为如果权重过大或过小,梯度在反向传播过程中可能会变得非常大或非常小。
2.随机初始化的改进方法(本质上就是限制随机数取值区间):
-
小随机值:使用较小的随机值初始化权重,可以减少梯度消失或爆炸的风险。
-
均匀分布 :从 <math xmlns="http://www.w3.org/1998/Math/MathML"> [ − ε , ε ] [-ε, ε] </math>[−ε,ε]的均匀分布中采样权重,其中ε是一个很小的正数。
-
正态分布:从均值为0,标准差为小正数的正态分布中采样权重。
-
Xavier/Glorot 初始化:根据前一层和后一层的节点数来调整权重的初始方差,以保持激活函数的方差在各层之间相对稳定。
-
He 初始化:特别为ReLU激活函数设计的初始化方法,它根据前一层的节点数来调整权重的初始方差。
随机初始化是深度学习中一个"肥肠"重要的概念,它为模型的训练提供了一个良好的起点。然而,为了解决梯度消失或爆炸的问题,大神们提出了多种改进的初始化方法,这些方法在实践中被广泛使用。
3.随机初始化代码示例
随机初始化权重和偏置的代码实例通常依赖于使用的编程语言和深度学习框架。以下是我使用Python和一些流行的深度学习框架(如TensorFlow和PyTorch),结合网上的一些源码(下面相同)给出的随机初始化的示例代码:
PyTorch 示例
python
import torch
import torch.nn as nn
# 定义一个简单的神经网络层
class SimpleNet(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(SimpleNet, self).__init__()
self.layer1 = nn.Linear(input_size, hidden_size)
self.relu = nn.ReLU()
self.layer2 = nn.Linear(hidden_size, output_size)
def forward(self, x):
x = self.layer1(x)
x = self.relu(x)
x = self.layer2(x)
return x
# 初始化网络
input_size = 784 # 例如,MNIST数据集中的图像大小
hidden_size = 128
output_size = 10 # 例如,MNIST数据集中的类别数
# 实例化网络
net = SimpleNet(input_size, hidden_size, output_size)
# 打印网络参数,查看随机初始化的权重和偏置
for param in net.parameters():
print(param)
在PyTorch中,如果我们没有显式地指定权重和偏置的初始化方式,PyTorch会使用其默认的初始化方法。
在PyTorch中,如果nn.Linear
层没有指定初始化器,权重(weights)将被初始化为一个标准差为 <math xmlns="http://www.w3.org/1998/Math/MathML"> 2 fan-in + fan-out \sqrt{\frac{2}{\text{fan-in} + \text{fan-out}}} </math>fan-in+fan-out2 的正态分布,这是 <math xmlns="http://www.w3.org/1998/Math/MathML"> H e He </math>He初始化的一种变体,适用于 <math xmlns="http://www.w3.org/1998/Math/MathML"> R e L U ReLU </math>ReLU激活函数。偏置 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( b i a s e s ) (biases) </math>(biases)默认会被初始化为0。
以下是代码中与初始化相关的部分:
python
self.layer1 = nn.Linear(input_size, hidden_size)
self.layer2 = nn.Linear(hidden_size, output_size)
TensorFlow 示例
python
import tensorflow as tf
# 定义一个简单的神经网络层
class SimpleNet(tf.keras.Model):
def __init__(self):
super(SimpleNet, self).__init__()
self.layer1 = tf.keras.layers.Dense(128, activation='relu', kernel_initializer='random_normal')
self.layer2 = tf.keras.layers.Dense(10)
def call(self, inputs):
x = self.layer1(inputs)
return self.layer2(x)
# 实例化网络
net = SimpleNet()
# 创建一个输入张量
input_tensor = tf.random.normal([1, 784])
# 通过网络前向传播
output = net(input_tensor)
# 打印网络参数,查看随机初始化的权重和偏置
for layer in net.layers:
print(layer.get_weights())
在这段代码中,随机初始化是通过设置Dense
层的kernel_initializer
参数来实现的。以下就是与随机初始化相关的部分:
python
self.layer1 = tf.keras.layers.Dense(128, activation='relu',kernel_initializer='random_normal')
在这行代码中,self.layer1
是一个Dense
层,具有128个输出单元(神经元)和ReLU激活函数。kernel_initializer
参数被设置为'random_normal'
,这告诉TensorFlow使用正态分布的随机数来初始化该层的权重。
默认情况下,正态分布的均值为0,标准差为0.05。
TensorFlow中的'random_normal'
初始化器生成的权重是从具有以下参数的正态分布中抽取的:
- <math xmlns="http://www.w3.org/1998/Math/MathML"> 均值( m e a n ): 0 均值(mean):0 </math>均值(mean):0
- <math xmlns="http://www.w3.org/1998/Math/MathML"> 标准差( s t d d e v ): 0.05 ,除非另有指定 标准差(stddev):0.05,除非另有指定 </math>标准差(stddev):0.05,除非另有指定
这是权重的随机初始化。对于偏置,如果没有特别指定初始化器,TensorFlow将默认使用0值初始化,即没有偏置。在代码中并没有为self.layer2
指定初始化器,因此它会使用默认的权重初始化方法(如果存在的话)和0值初始化偏置。
手动实现随机初始化权重和偏置(不一定要这么写):
如果不使用深度学习框架,我们需要手动实现随机初始化权重和偏置的代码。以下是一个简单的示例,使用Python标准库中的random
模块来为一个全连接层(也称为线性层)随机初始化权重和偏置。
首先,我们定义一个全连接层的类,包括随机初始化权重和偏置的方法:
python
import numpy as np
class LinearLayer:
def __init__(self, input_size, output_size):
self.input_size = input_size
self.output_size = output_size
# 随机初始化权重
self.weights = np.random.randn(output_size, input_size) * 0.01
# 随机初始化偏置
self.biases = np.random.randn(output_size, 1)
def forward(self, x):
return np.dot(self.weights, x) + self.biases
# 定义输入层大小、隐藏层大小和输出层大小
input_size = 784 # 例如,MNIST数据集中的图像大小
hidden_size = 128
output_size = 10 # 例如,MNIST数据集中的类别数
# 实例化全连接层
layer = LinearLayer(input_size, hidden_size)
# 打印随机初始化的权重和偏置
print("Weights:")
print(layer.weights)
print("\nBiases:")
print(layer.biases)
在这个示例中,我们使用NumPy库来处理矩阵运算。LinearLayer
类包含权重和偏置的初始化以及前向传播的逻辑。
self.weights
是一个(output_size, input_size)
的矩阵,表示全连接层的权重。我们使用np.random.randn
函数从标准正态分布中随机采样权重,然后乘以一个小的缩放因子(例如0.01),以控制权重的初始大小。self.biases
是一个(output_size, 1)
的向量,表示全连接层的偏置。我们同样使用np.random.randn
函数来随机初始化偏置。
But请注意,在这个示例中的随机初始化方法非常简单,没有考虑激活函数的特性或梯度消失/爆炸问题!
4.总结:
在这两个示例中,我们定义了一个简单的神经网络,其中包含一个或两个全连接层(也称为线性层或Dense
层)。 在PyTorch中,我们直接打印出网络的参数来查看它们的初始值。而在TensorFlow中,我们使用call
方法来模拟前向传播,并打印每层的权重。
请注意,这些代码示例需要在安装了相应框架的环境中运行。所以所以,家人们在实际应用中,可能还需要设置其他参数,如激活函数、优化器等。
五.Xavier初始化
定义: <math xmlns="http://www.w3.org/1998/Math/MathML"> X a v i e r Xavier </math>Xavier初始化(也称为 <math xmlns="http://www.w3.org/1998/Math/MathML"> G l o r o t Glorot </math>Glorot初始化)是一种用于深度神经网络的权重初始化方法,由 <math xmlns="http://www.w3.org/1998/Math/MathML"> X a v i e r G l o r o t Xavier Glorot </math>XavierGlorot和 <math xmlns="http://www.w3.org/1998/Math/MathML"> Y o s h u a B e n g i o Yoshua Bengio </math>YoshuaBengio在2010年提出。这种方法特别适用于具有 <math xmlns="http://www.w3.org/1998/Math/MathML"> S i g m o i d Sigmoid </math>Sigmoid或 <math xmlns="http://www.w3.org/1998/Math/MathML"> T a n h Tanh </math>Tanh激活函数的网络,因为它有助于保持网络在训练初期的激活值和梯度的方差稳定。
1.Xavier初始化的原理:
Xavier初始化的目标是保持网络各层的输入和输出的方差大致相同,从而避免梯度消失或爆炸问题。Xavier初始化通过合理地选择权重的初始值范围,来保持每层激活值的方差大致相同。具体来说,它根据前一层(输入层)和后一层(输出层)的单元数量来调整权重的初始方差。这样做可以使得在前向传播和反向传播过程中,信号的方差不会随着网络深度的增加而显著减小或增大。
2.方差与保持方差
定义:
在神经网络中,"方差"通常指的是网络层激活值(输出)的离散程度,即激活值围绕其均值的分布范围。方差是衡量数据点分布的集中趋势或离散程度的一个统计量,计算为每个数据点与均值差值的平方的平均值(简单点理解即方差适中,则参数的波动适中)。
那么为何要保持方差:
- 网络深度 :在深层网络中,如果方差不适当,可能会导致深层的激活值和梯度变得非常小或非常大,影响网络的性能。
- 激活函数:某些激活函数(如Sigmoid或Tanh)可能会导致激活值的方差随着传播逐渐减小,因此需要通过初始化来调整。
- 训练动态:保持方差有助于维持训练过程中的动态平衡,使网络能够稳定地学习和适应数据。
3.初始化方法与数学公式
对于每一层,权重矩阵 <math xmlns="http://www.w3.org/1998/Math/MathML"> W W </math>W 的初始化方法如下:
-
对于均匀分布,权重 <math xmlns="http://www.w3.org/1998/Math/MathML"> W W </math>W 应该从以下范围内均匀采样:
<math xmlns="http://www.w3.org/1998/Math/MathML"> W ∼ U ( − 6 f i n + f o u t , 6 f i n + f o u t ) W \sim U\left(-\frac{\sqrt{6}}{\sqrt{f_{in} + f_{out}}}, \frac{\sqrt{6}}{\sqrt{f_{in} + f_{out}}}\right) </math>W∼U(−fin+fout 6 ,fin+fout 6 )
其中, <math xmlns="http://www.w3.org/1998/Math/MathML"> f i n f_{in} </math>fin是前一层的单元(或特征)数量, <math xmlns="http://www.w3.org/1998/Math/MathML"> f o u t f_{out} </math>fout 是当前层的单元数量。
-
对于正态分布,权重 <math xmlns="http://www.w3.org/1998/Math/MathML"> W W </math>W 应该从以下标准差的标准正态分布中采样:
<math xmlns="http://www.w3.org/1998/Math/MathML"> W ∼ N ( 0 , 2 f i n + f o u t ) W \sim \mathcal{N}\left(0, \frac{2}{f_{in} + f_{out}}\right) </math>W∼N(0,fin+fout2)
这里的 <math xmlns="http://www.w3.org/1998/Math/MathML"> f i n f_{in} </math>fin 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> f o u t f_{out} </math>fout 可以是输入单元的数量或输出单元的数量,具体取决于具体的实现和文献来源。
4.Xavier初始化的代码示例:
以下是使用PyTorch和TensorFlow实现Xavier初始化的示例代码。
PyTorch 示例
python
import torch
import torch.nn as nn
class XavierNet(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(XavierNet, self).__init__()
self.layer1 = nn.Linear(input_size, hidden_size)
self.layer2 = nn.Linear(hidden_size, output_size)
self.initialize_weights()
def forward(self, x):
x = torch.sigmoid(self.layer1(x)) # 假设使用Sigmoid激活函数
x = self.layer2(x)
return x
def initialize_weights(self):
for m in self.modules():
if isinstance(m, nn.Linear):
nn.init.xavier_uniform_(m.weight)
if m.bias is not None:
m.bias.data.fill_(0)
# 实例化网络
input_size = 10
hidden_size = 5
output_size = 2
net = XavierNet(input_size, hidden_size, output_size)
TensorFlow 示例
python
import tensorflow as tf
from tensorflow.keras.layers import Dense, Layer
from tensorflow.keras.models import Sequential
class XavierInitializer(tf.keras.initializers.Initializer):
def __call__(self, shape, dtype=None):
limit = tf.math.sqrt(6 / (shape[0] + shape[1]))
return tf.random.uniform(shape, -limit, limit, dtype)
class XavierModel(tf.keras.Model):
def __init__(self):
super(XavierModel, self).__init__()
self.dense1 = Dense(5, activation='sigmoid', kernel_initializer=XavierInitializer())
self.dense2 = Dense(2, kernel_initializer=XavierInitializer())
def call(self, inputs):
x = self.dense1(inputs)
x = self.dense2(x)
return x
# 实例化网络
model = XavierModel()
在PyTorch示例中,我们使用了nn.init.xavier_uniform_
函数来对权重进行Xavier均匀初始化。在TensorFlow示例中,我们首先定义了一个自定义的初始化器XavierInitializer
,然后将其用作Dense
层的kernel_initializer
。
手动实现Xavier初始化
如果不使用深度学习框架,我们也阔以手动实现Xavier初始化。以下是一个使用Python和NumPy库实现Xavier初始化的示例,包括一个简单的全连接神经网络层:
python
import numpy as np
# 定义一个全连接层的类
class DenseLayer:
def __init__(self, input_size, output_size):
self.input_size = input_size
self.output_size = output_size
# Xavier初始化权重
limit = np.sqrt(6 / (input_size + output_size))
self.weights = np.random.uniform(-limit, limit, (output_size, input_size))
# 偏置初始化为0
self.biases = np.zeros((output_size, 1))
def forward(self, x):
return np.dot(self.weights, x) + self.biases
# 示例:创建一个全连接层
input_size = 784 # 例如,MNIST数据集中的图像大小
output_size = 128 # 假设我们想要128个神经元
layer = DenseLayer(input_size, output_size)
# 打印随机初始化的权重和偏置
print("Weights (部分):")
print(layer.weights[:5, :5]) # 打印权重矩阵的前5行和前5列
print("\nBiases (部分):")
print(layer.biases[:5]) # 打印偏置向量的前5个值
在这个示例中,我们首先定义了一个DenseLayer
类,它接受输入大小input_size
和输出大小output_size
作为参数。在初始化方法__init__
中,我们使用Xavier初始化来设置权重,计算均匀分布的范围限制limit
,然后使用np.random.uniform
函数在[-limit, limit]
范围内生成均匀分布的随机数作为权重。偏置则初始化为0,使用np.zeros
函数实现。forward
方法实现了层的前向传播,即计算输入x
和权重的点积,然后加上偏置。
最后,我们创建了一个DenseLayer
的实例,并打印了初始化后的权重和偏置的样本,以验证初始化是否正确应用。
请注意,这个示例仅用于演示Xavier初始化的实现方式,并不包含完整的神经网络训练逻辑。在实际应用中,还需要实现其他层类型、激活函数、损失函数、反向传播和参数更新等。
5.总结:
虽然Xavier初始化对于Sigmoid和Tanh激活函数很有用,但对于ReLU及其变体,通常推荐使用He初始化。
六.He初始化
定义: <math xmlns="http://www.w3.org/1998/Math/MathML"> H e He </math>He初始化(也称为 <math xmlns="http://www.w3.org/1998/Math/MathML"> H e N o r m a l He Normal </math>HeNormal初始化)是一种针对具有 <math xmlns="http://www.w3.org/1998/Math/MathML"> R e L U ReLU </math>ReLU( <math xmlns="http://www.w3.org/1998/Math/MathML"> R e c t i f i e d L i n e a r U n i t Rectified Linear Unit </math>RectifiedLinearUnit)激活函数的神经网络层的权重初始化方法,由 <math xmlns="http://www.w3.org/1998/Math/MathML"> K a i m i n g H e Kaiming He </math>KaimingHe等人在2015年提出。 <math xmlns="http://www.w3.org/1998/Math/MathML"> H e He </math>He初始化特别适用于 <math xmlns="http://www.w3.org/1998/Math/MathML"> R e L U ReLU </math>ReLU激活函数 ,因为它考虑了 <math xmlns="http://www.w3.org/1998/Math/MathML"> R e L U ReLU </math>ReLU在正区间的恒等性质,有助于保持网络在训练初期的激活值和梯度的方差稳定。
1.RELU激活函数:
考虑到某些家人们可能忘记了ReLU激活函数或者还不知道这个激活函数,我在这里简单讲解一下:
函数图像
ReLU(Rectified Linear Unit,线性整流单元)激活函数是深度学习中非常流行和常用的一种激活函数,特别是在卷积神经网络(CNN)中。ReLU函数的定义非常简单:
<math xmlns="http://www.w3.org/1998/Math/MathML"> ReLU ( x ) = max ( 0 , x ) \text{ReLU}(x) = \max(0, x) </math>ReLU(x)=max(0,x)
这意味着,当输入 <math xmlns="http://www.w3.org/1998/Math/MathML"> x x </math>x 大于0时,ReLU函数的输出就是 <math xmlns="http://www.w3.org/1998/Math/MathML"> x x </math>x 本身;当输入 <math xmlns="http://www.w3.org/1998/Math/MathML"> x x </math>x小于或等于0时,输出就是0。
ReLU激活函数的特点:
-
非线性:ReLU引入了非线性,使得神经网络可以学习和模拟非线性关系。
-
计算效率:ReLU的计算非常高效,因为它只涉及阈值操作,没有复杂的数学运算。
-
稀疏激活:由于ReLU函数在输入小于0时输出为0,这导致网络在任何时候只有一部分神经元被激活,从而增加了稀疏性。
-
缓解梯度消失问题:与Sigmoid或Tanh激活函数相比,ReLU在正区间的导数是常数1,这有助于缓解梯度消失问题。
-
死亡ReLU问题 :如果输入为负,ReLU的导数为0,这可能导致一些神经元的权重在训练过程中不再更新,这种现象被称为"死亡ReLU"。(这里也就是俺们为什么要用 <math xmlns="http://www.w3.org/1998/Math/MathML"> H e He </math>He初始化的原因!)
ReLU的变体:
由于ReLU的一些局限性,研究者们提出了几种ReLU的变体来解决特定问题(了解即可):
-
Leaky ReLU :允许负输入有一个非零的梯度,通常是很小的正斜率(例如0.01)。 <math xmlns="http://www.w3.org/1998/Math/MathML"> Leaky ReLU ( x ) = max ( α x , x ) \text{Leaky ReLU}(x) = \max(\alpha x, x) </math>Leaky ReLU(x)=max(αx,x) 其中 <math xmlns="http://www.w3.org/1998/Math/MathML"> α \alpha </math>α 是一个很小的正数。
-
Parametric ReLU (PReLU) :Leaky ReLU的泛化形式,其斜率系数 <math xmlns="http://www.w3.org/1998/Math/MathML"> α \alpha </math>α 可以是学习的参数。
-
Exponential Linear Unit (ELU) :在负区间使用指数函数,允许负输入有一个小于1的梯度。 <math xmlns="http://www.w3.org/1998/Math/MathML"> ELU ( x ) = max ( α ( x + 1 ) , x ) \text{ELU}(x) = \max(\alpha(x + 1), x) </math>ELU(x)=max(α(x+1),x) 当 <math xmlns="http://www.w3.org/1998/Math/MathML"> x > 0 x > 0 </math>x>0 时,输出 <math xmlns="http://www.w3.org/1998/Math/MathML"> x x </math>x;当 <math xmlns="http://www.w3.org/1998/Math/MathML"> x ≤ 0 x \leq 0 </math>x≤0 时,输出 <math xmlns="http://www.w3.org/1998/Math/MathML"> α ( e x − 1 ) \alpha(e^x - 1) </math>α(ex−1)。
-
Scaled Exponential Linear Unit (SELU):自归一化激活函数,对于有自归一化属性的网络架构非常有用。
ReLU及其变体在许多深度学习架构中都有广泛的应用,因为它们简单、高效,并且在很多情况下能够提高网络的性能。然而,设计网络时也需要考虑到ReLU可能导致的"死亡ReLU"问题,并根据具体情况选择合适的激活函数。
2.He初始化的原理:
He初始化主要针对ReLU激活函数设计,因为ReLU在正区间的导数为1,而在负区间的导数为0。这意味着在训练过程中,使用ReLU激活的网络的正向传播和反向传播行为与使用Sigmoid或Tanh激活的网络不同。He初始化的目标是保持每层激活的方差在网络中向前传播时大致相同。
对于一个给定的层,权重矩阵 <math xmlns="http://www.w3.org/1998/Math/MathML"> W W </math>W 的He初始化方法如下:
-
对于正态分布,权重 <math xmlns="http://www.w3.org/1998/Math/MathML"> W W </math>W 应该从以下标准差的标准正态分布中采样:
<math xmlns="http://www.w3.org/1998/Math/MathML"> W ∼ N ( 0 , 2 f i n ) W \sim \mathcal{N}\left(0, \frac{2}{f_{in}}\right) </math>W∼N(0,fin2) 其中, <math xmlns="http://www.w3.org/1998/Math/MathML"> f i n f_{in} </math>fin 是前一层的单元(或特征)数量。
-
对于均匀分布,权重 <math xmlns="http://www.w3.org/1998/Math/MathML"> W W </math>W应该从以下范围内均匀采样:
<math xmlns="http://www.w3.org/1998/Math/MathML"> W ∼ U ( − 6 f i n , 6 f i n ) W \sim U\left(-\frac{\sqrt{6}}{\sqrt{f_{in}}}, \frac{\sqrt{6}}{\sqrt{f_{in}}}\right) </math>W∼U(−fin 6 ,fin 6 ) 这里, <math xmlns="http://www.w3.org/1998/Math/MathML"> f i n f_{in} </math>fin 是前一层的单元数量。
3.He初始化的代码示例:
以下是使用PyTorch和TensorFlow实现He初始化的示例代码。
PyTorch 示例
python
import torch
import torch.nn as nn
class HeNet(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(HeNet, self).__init__()
self.layer1 = nn.Linear(input_size, hidden_size)
self.layer2 = nn.Linear(hidden_size, output_size)
self.initialize_weights()
def forward(self, x):
x = torch.relu(self.layer1(x)) # 使用ReLU激活函数
x = self.layer2(x)
return x
def initialize_weights(self):
for m in self.modules():
if isinstance(m, nn.Linear):
nn.init.kaiming_normal_(m.weight, mode='fan_in', nonlinearity='relu')
if m.bias is not None:
m.bias.data.fill_(0)
# 实例化网络
input_size = 10
hidden_size = 5
output_size = 2
net = HeNet(input_size, hidden_size, output_size)
TensorFlow 示例
python
import tensorflow as tf
class HeModel(tf.keras.Model):
def __init__(self):
super(HeModel, self).__init__()
self.dense1 = tf.keras.layers.Dense(5, activation='relu', kernel_initializer='he_normal')
self.dense2 = tf.keras.layers.Dense(2, kernel_initializer='he_normal')
def call(self, inputs):
x = self.dense1(inputs)
x = self.dense2(x)
return x
# 实例化网络
model = HeModel()
在PyTorch示例中,我们使用了nn.init.kaiming_normal_
函数来对权重进行He正态初始化,并且指定了mode='fan_in'
和nonlinearity='relu'
,这适用于ReLU激活函数。在TensorFlow示例中,我们使用了kernel_initializer='he_normal'
作为Dense层的初始化器,这是TensorFlow中He初始化的默认设置。
He初始化由于其对ReLU激活函数的良好适应性,在现代深度学习中被广泛使用。然而,对于其他类型的激活函数,可能需要考虑其他初始化方法。
手动实现He初始化
如果不使用深度学习框架,我们可以通过基本的Python编程和NumPy库来实现He初始化(也称为 <math xmlns="http://www.w3.org/1998/Math/MathML"> K a i m i n g Kaiming </math>Kaiming初始化,特别是当用于ReLU激活函数时)。以下是手动实现He初始化的示例代码:
python
import numpy as np
def he_normal_initialization(input_size, output_size, dtype=np.float32):
"""
根据He初始化方法(正态分布)初始化权重。
参数:
- input_size: 权重矩阵的输入维度大小。
- output_size: 权重矩阵的输出维度大小。
- dtype: 输出数据类型。
返回:
- weights: He初始化后的权重矩阵。
"""
# 计算标准差
std_dev = np.sqrt(2.0 / (input_size + output_size))
# 根据正态分布初始化权重
weights = np.random.normal(0, std_dev, (output_size, input_size)).astype(dtype)
return weights
def he_uniform_initialization(input_size, output_size, dtype=np.float32):
"""
根据He初始化方法(均匀分布)初始化权重。
参数:
- input_size: 权重矩阵的输入维度大小。
- output_size: 权重矩阵的输出维度大小。
- dtype: 输出数据类型。
返回:
- weights: He初始化后的权重矩阵。
"""
# 计算界限
limit = np.sqrt(6.0 / (input_size + output_size))
# 根据均匀分布初始化权重
weights = np.random.uniform(-limit, limit, (output_size, input_size)).astype(dtype)
return weights
# 示例:创建一个具有He初始化的全连接层
class DenseLayer:
def __init__(self, input_size, output_size):
self.input_size = input_size
self.output_size = output_size
# 使用He正态分布初始化权重
self.weights = he_normal_initialization(input_size, output_size)
# 偏置初始化为0
self.biases = np.zeros((output_size, 1))
def forward(self, x):
return np.dot(self.weights, x) + self.biases
# 创建一个全连接层实例
input_size = 784 # 例如,MNIST数据集中的图像大小
output_size = 128 # 假设输出层大小为128
layer = DenseLayer(input_size, output_size)
# 打印He初始化的权重和偏置
print("Weights (部分):")
print(layer.weights[:5, :5]) # 打印权重矩阵的前5行和前5列
print("\nBiases (部分):")
print(layer.biases[:5]) # 打印偏置向量的前5个值
在这个示例中,我们首先定义了两个函数he_normal_initialization
和he_uniform_initialization
来分别实现He正态分布和均匀分布初始化。然后,我们定义了一个DenseLayer
类,它在初始化时使用he_normal_initialization
函数来初始化权重,并将偏置初始化为0。
最后,我们创建了一个DenseLayer
的实例,并打印了初始化后的权重和偏置的样本,以验证He初始化是否正确应用。请注意,这里使用的是He正态分布初始化,但根据需要,家人们也可以使用he_uniform_initialization
函数来应用He均匀分布初始化。
七.预训练初始化(不详细介绍)
定义: 预训练初始化通常指的是在迁移学习中使用预训练模型的参数作为新模型的初始化参数。以下是它的一些特点:
-
预训练模型的概念:预训练模型是指在一个大型且多样化的数据集(如ImageNet)上训练好的深度学习模型。这些模型已经学习到了丰富的特征表示,能够识别和处理各种视觉模式。
-
迁移学习的背景:迁移学习是一种机器学习技术,它允许我们将在一个任务上学到的知识迁移到另一个相关的任务上。这在数据量有限或计算资源受限的情况下特别有用。
-
预训练初始化的目的:
- 利用已有知识:预训练模型已经在大量数据上学习到了特征,预训练初始化可以利用这些知识。
- 加速收敛:使用预训练参数作为起点,可以加速新模型在新任务上的训练过程。
- 提高性能:预训练模型的特征提取能力可以提高新模型在新任务上的性能。
-
预训练初始化的方法:
- 直接使用预训练模型的权重作为新模型的初始化参数。
- 在新数据集上进行微调,即在保持预训练权重的基础上,对部分或全部层进行额外的训练,以适应新任务。
-
微调的策略:
- 冻结层:在某些情况下,可以选择冻结预训练模型中的一部分层,只训练模型的顶层或新添加的层。
- 部分微调:解冻预训练模型中的某些层,并与新层一起训练。
- 全微调:解冻整个预训练模型的所有层,并在新数据集上进行端到端的训练。
-
选择预训练模型:选择与新任务最相关的预训练模型。例如,如果新任务是医学图像分类,选择在类似图像上预训练的模型可能更合适。
-
调整模型架构:根据新任务的需求,可能需要对预训练模型的架构进行调整,如添加或删除层,修改网络的宽度或深度。
-
适应新数据集:预训练模型可能需要根据新数据集的特性进行调整,如进行数据归一化或标准化,以匹配预训练时使用的分布。
-
评估和迭代:在使用预训练初始化后,需要在新任务上评估模型的性能,并根据需要进行迭代优化。
预训练初始化是一种强大的技术,它允许我们利用在大规模数据集上训练得到的模型,为新任务提供一个有利的起点。通过微调和适当的架构调整,我们可以在新任务上实现更好的性能和更快的训练速度。
总结(简单粗暴):
借鸡生蛋,蛋破鸡出,鸡瘦训练!
八.Reference:
文章部分引用吸收下文图文,感谢其原作者
以上就是关于人工智能深度学习之参数初始化的方法,可能有些地方依旧有瑕疵,欢迎大家指正,点赞,收藏和交流,谢谢O(∩_∩)O