您的当前位置:首页一文搞懂深度网络初始化(Xavier and Kaiming i

一文搞懂深度网络初始化(Xavier and Kaiming i

2024-12-14 来源:哗拓教育

最近时不时就有网友发私信让我帮忙debug程序,对于这些来信,我的回复通常都是一句话:“先跑通论文作者的开源代码,在此基础上再逐步修改数据集和模型。”

你或许会觉得我在摆谱,净说些高大上的政治正确的空话,但这还真不是。

和其他的软件程序不同,神经网络是个系统工程,数据、参数、模型内部结构、训练策略、学习率等等,这些因素不管哪一部分出错,它都不会报错,只是会输出一些不是你想要的结果而已。

参数初始化就是这么一个容易被忽视的重要因素,因为不仅使用者对其重要性缺乏概念,而且这些操作都被TF、pytorch这些框架封装了,你可能不知道的是,糟糕的参数初始化是会阻碍复杂非线性系统的训练的。

Xavier Initialization

早期的参数初始化方法普遍是将数据和参数normalize为高斯分布(均值0方差1),但随着神经网络深度的增加,这方法并不能解决梯度消失问题。

Figure 1: XavierInitialisation.pdf Figure 2: xavier initialization

因此,他提出了Xavier初始化:bias初始化为0,为Normalize后的参数乘以一个rescale系数:1/,n是输入参数的个数。

公式的推导过程大致如下:

  • 因为E(期望)等于均值,而输入数据(x)和参数(W)的均值都是0,因此,
  • 又因为x和W恒等分布(方差都是1),因此,
  • 我们的目标是,因此,

如果上述这段公式你看晕了,也没关系,只要记住结果就好。

接下来,我们要做实验来验证Xavier的洞见。

def linear(x, w, b): return x @ w + b

def relu(x): return x.clamp_min(0.)

nh = 50
W1 = torch.randn(784, nh)
b1 = torch.zeros(nh)
W2 = torch.randn(nh, 1)
b2 = torch.zeros(1)

z1 = linear(x_train, W1, b1)
print(z1.mean(), z1.std())

tensor(-0.8809) tensor(26.9281)

这是个简单的线性回归模型:,(W1, b1)和(W2, b2)分别是隐层和输出层的参数,W1/W2初始化为高斯分布,b1/b2初始为0。果然,第一个linear层的输出值(z1)的均值和标准差就已经发生了很大的变化。如果后续使用sigmoid作为激活函数,那梯度消失就会很明显。

现在我们按照Xavier的方法来初始化参数:

W1 = torch.randn(784, nh) * math.sqrt(1 / 784)
b1 = torch.zeros(nh)
W2 = torch.randn(nh, 1) * math.sqrt(1 / nh)
b2 = torch.zeros(1)

z1 = linear(x_train, W1, b1)
print(z1.mean(), z1.std())

tensor(0.1031) tensor(0.9458)

a1 = relu(z1)
a1.mean(), a1.std()

(tensor(0.4272), tensor(0.5915))

参数经过Xavier初始化后,linear层的输出值的分布没有大的变化(),依旧接近高斯分布,但是好景不长,relu的激活值分布就开始跑偏了()。

Kaiming Initialization

Xavier初始化的问题在于,它只适用于线性激活函数,但实际上,对于深层神经网络来说,线性激活函数是没有价值,神经网络需要非线性激活函数来构建复杂的非线性系统。今天的神经网络普遍使用relu激活函数。

W1 = torch.randn(784, nh) * math.sqrt(2 / 784)
b1 = torch.zeros(nh)
W2 = torch.randn(nh, 1) * math.sqrt(2 / nh)
b2 = torch.zeros(1)

z1 = linear(x_train, W1, b1)
a1 = relu(z1)
a1.mean(), a1.std()

(tensor(0.4553), tensor(0.7339))

可以看到,Kaiming初始化的表现要优于Xavier初始化,relu之后的输出值标准差还有0.7339(浮动可以达到0.8+)。

实际上,Kaiming初始化已经被Pytorch用作默认的参数初始化函数。

import torch.nn.init as init

W1 = torch.zeros(784, nh)
b1 = torch.zeros(nh)
W2 = torch.zeros(nh, 1)
b2 = torch.zeros(1)

init.kaiming_normal_(W1, mode='fan_out', nonlinearity='relu')
init.kaiming_normal_(W2, mode='fan_out')
z1 = linear(x_train, W1, b1)
a1 = relu(z1)
print("layer1: ", a1.mean(), a1.std())
z2 = linear(a1, W2, b2)

layer1:  tensor(0.5583) tensor(0.8157)
tensor(1.1784) tensor(1.3209)

现在,方差的问题已经解决了,接下来就是均值不为0的问题。因为在x轴上平移data并不会影响data的方差,因此,如果把relu的激活值左移5,结果会如何?

def linear(x, w, b):
  return x @ w + b

def relu(x):
  return x.clamp_min(0.) - 0.5

def model(x):
  x = relu(linear(x, W1, b1))
  print("layer1: ", x.mean(), x.std())
  x = relu(linear(x, W2, b2))
  print("layer2: ", x.mean(), x.std())
  x = linear(x, W3, b3)
  print("layer3: ", x.mean(), x.std())
  return x

nh = [100, 50]
W1 = torch.zeros(784, nh[0])
b1 = torch.zeros(nh[0])
W2 = torch.zeros(nh[0], nh[1])
b2 = torch.zeros(nh[1])
W3 = torch.zeros(nh[1], 1)
b3 = torch.zeros(1)

init.kaiming_normal_(W1, mode='fan_out')
init.kaiming_normal_(W2, mode='fan_out')
init.kaiming_normal_(W3, mode='fan_out')
_ = model(x_train)

layer1:  tensor(0.0383) tensor(0.7993)
layer2:  tensor(0.0075) tensor(0.7048)
layer3:  tensor(-0.2149) tensor(0.4493)

结果出乎意料的好,这个三层的模型在没有添加batchnorm的情况下,每层的输入值和输出值都接近高斯分布,虽然数据方差是会逐层递减,但相比normalize初始化和Xavier初始化要好很多。

最后,因为Kaiming初始化是pytorch的默认初始化函数,因此我又用pytorch提供的nn.Linear()和nn.Relu()来构建相同的模型对比测试,结果是大跌眼镜。

class Model(nn.Module):
  def __init__(self):
    super().__init__()
    self.lin1 = nn.Linear(784, nh[0])
    self.lin2 = nn.Linear(nh[0], nh[1])
    self.lin3 = nn.Linear(nh[1], 1)
    self.relu = nn.ReLU()
  
  def forward(self, x):
    x = self.relu(self.lin1(x))
    print("layer 1: ", x.mean().item(), x.std().item())
    x = self.relu(self.lin2(x))
    print("layer 2: ", x.mean().item(), x.std().item())
    x = self.relu(self.lin3(x))
    print("layer 3: ", x.mean().item(), x.std().item())
    return x

m = Model()
_ = m(x_train)

layer 1:  0.2270725518465042 0.32707411050796
layer 2:  0.033514849841594696 0.23475737869739532
layer 3:  0.013271240517497063 0.09185370802879333

可以看到,第三层的输出已经均值为0、方差为0。去看nn.Linear()类的代码时会看到,它在做初始化时会传入参数a=math.sqrt(5)。我们知道,当输入为负数时,leaky relu的梯度为,,参数a就是这个。虽然kaiming_uniform_()的默认网络要使用的激活函数是leaky relu,但a默认值为0,此时leaky relu就等于relu。但现在数据存在负数,因此,mean相比relu模型更接近于0,甚至E(x) > 0的假设都不成立了,因此,rescale系数就不准确了,nn.Linear()才会有这样的表现。

    def reset_parameters(self):
        init.kaiming_uniform_(self.weight, a=math.sqrt(5))

END

本文通过Xavier和Kaiming初始化来展现了参数初始化的重要性,因为糟糕的初始化容易让神经网络陷入梯度消失的陷阱中。

References

哗拓教育还为您提供以下相关内容希望对您有帮助:

深度学习:Xavier and Kaiming Initialization

深度学习领域存在多种模型初始化策略,本文将简要介绍常量初始化和随机初始化,着重探讨Xavier和Kaiming初始化方法。常量初始化将神经网络中的模型全部初始化为固定常数,导致计算单元之间存在对称关系或完全相同,降低了神经网络的灵活性。随机初始化将每个计算单元初始化为不同状态,但无法有效选择概率模型中的...

深度学习:Xavier and Kaiming Initialization

在深度学习模型训练中,初始化策略扮演着关键角色。其中,Xavier和Kaiming初始化方法尤为突出,它们旨在解决Random Initialization中参数选择的难题,保证信号强度在前向传播和反向传播过程中的稳定性。首先,Constant Initialization过于简单,可能导致神经网络灵活性下降。而Random Initialization虽然引入了多样性,但...

深度学习参数初始化详细推导:Xavier方法和kaiming方法【一】

Xavier初始化旨在保持前向传播中激活值方差稳定,后向传播中梯度方差不变,确保输入和输出在数值上相对稳定。激活值导数在0附近较大,以避免过饱和导致的梯度消失。激活函数导数形式如下:1. sigmoid函数:导数为sigmoid(x) * (1 - sigmoid(x))2. tanh函数:导数为1 - (tanh(x))^2 3. softsign...

深度学习参数初始化详细推导:Xavier方法和kaiming方法【二】_百度知...

kaiming初始化方法专为ReLU函数及其变种设计,旨在改善Xavier初始化方法在ReLU函数应用时的局限性。2015年,kaiming方法在论文中提出,该论文不仅针对卷积神经网络(CNN)结构的网络,而且提出ReLU的变种PReLU,以及专门针对ReLU函数的kaiming初始化方法。kaiming方法考虑了网络层数、输入和输出的维度以及激活函数的...

深度学习:零散知识——xavier初始化

Xavier 初始化解决权重矩阵的初始设置问题。若每层神经网络权重参数 W 随机正态分布,搭建的网络可能在梯度更新时出现问题,随着层数增加,输出均值不变,方差急剧减小,直方图在0点处高度集中,导致梯度更新失效。Xavier 初始化通过考虑激活函数特性,给权重值分布提供两种模式。结合不同激活函数,初始化权重...

神经网络参数的初始化

一、Xavier初始化 我们从最简单的神经网络开始,即没有激活函数,只有线性变换的神经网络。在[公式] 中,定义一个全连接层:这实际上就是矩阵乘法的操作:这其实就是一个线性的变换。我们知道,在机器学习中,我们假设数据分布满足标准正态分布(均值为0,方差为1的分布),我们需要它经过线性变换之后也...

Xavier参数初始化方法

Xavier方法是用于神经网络参数初始化的技术,旨在确保梯度在正向传播和反向传播过程中的平稳性,从而促进训练过程的稳定性和效率。该方法遵循Glorot条件,即在数据经过每一层前后方差保持一致,并且在数据反向传播时,每层的梯度方差也保持一致。Xavier方法的提出,主要是针对Sigmoid和tanh激活函数,它们在使用...

神经网络训练:模型参数初始化方法详解,附Pytorch实现

He初始化针对ReLU激活函数优化,确保输入和输出信号方差一致,适用于深度网络的ReLU层。通过`nn.init.kaiming_uniform_`函数,结合`mode='fan_in'`和`nonlinearity='relu'`,实现He初始化。在实际应用中,根据网络结构和激活函数选择合适的初始化方法是关键,可能需要混合使用不同的策略。未来,对参数初始...

神经网络的各种初始化方法

He初始化 (Kaiming initialization): 针对ReLU网络,假设一半神经元激活,调整Xavier的公式除以2。 正交初始化 (orthogonal): 使用正交矩阵,保持输入张量的特性。对于不同类型的层,如卷积层(ConvTranspose2d)和归一化层(BatchNorm2d),需要根据其特性进行定制化初始化。例如,netG.apply(weights_init)会...

在训练神经网络模型时,如何初始化模型参数?

以卷积神经网络为例,常用的初始化方法包括Xavier/Glorot初始化和Kaiming初始化。Xavier初始化通常适用于全连接层,它通过调整权重的尺度,使每个神经元的输入均值接近于零,标准差为$\sqrt{\frac{2}{n_{in} + n_{out}}}$,其中$n_{in}$和$n_{out}$分别为输入维度和输出维度。这样可以确保每个...

显示全文

猜你还关注