深度学习之Xarier方法与kaiming方法

945 阅读11分钟

这是我参与8月更文挑战的第28天,活动详情查看:8月更文挑战

一 Xavier方法

1.1 Xavier初始化参数方法基础理论

对于Glorot条件,正向传播时候数据流经每一层前后的方差一致,并且反向传播时候数据流经每一层,该层梯度前后方差一致。前者称为向前传播条件,后者称为反向传播条件。 数据流至某一层时,该层神经元接收到的数据可以通过如下方法算得:

image.png 其中𝑧𝑗表示当前某层神经元接收到的数据,𝑥𝑖表示上一层某神经元传出的数据,𝑤𝑖代表连接对应两个神经元之间的权重,当然,如果我们将𝑧𝑗、𝑥𝑖、𝑤𝑖看成是随机变量,则上式就能够表示计算过程的一般情况。并且𝑤𝑖和𝑥𝑖的方差计算过程如下:

image.png 其中Var()表示方差计算,E()表示均值计算。由于我们假设参数是以0为均值的均匀分布或者0为均值的正态分布,因此𝐸(𝑤𝑖)=0,而此前我们介绍,假设输入数据是Zero-Centered的,因此𝐸(𝑥𝑖)=0,所以上式可进一步简化为:

image.png 我们可以认为x和w是独立同分布的(一个是采集处理后的数据,一个是随机生成的参数),因此每个𝑉𝑎𝑟(𝑤𝑖)𝑉𝑎𝑟(𝑥𝑖)也是独立同分布的,因此所有的𝑤𝑖都可以用一个随机变量𝑤表示,所有的𝑥𝑖也可以用一个随机变量𝑥表示

image.png 其中,n是上一层神经元的个数。需要注意的是,上式只考虑了正向传播的情况,而实际在进行反向传播时候,上述过程正好相反。反向传播时z代表上一层神经元接收到的数据,而x则代表当前层传出的数据,虽然计算公式不变,但n的含义却发生了变化。为了进行区分,我们将正向传播时的n、也就是代表上一层神经元个数的变量,命名为𝑛𝑖𝑛,而在进行反向传播时的n、也就是代表当前层神经元个数的变量,命名为𝑛𝑜𝑢𝑡。为了同时兼顾向前传播和反向传播这两种情况,我们将w的最终方差取值为:

image.png 一种更加严谨的、指代某一次传播过程上一层神经元数量和下一层神经元数量的叫法是扇入(fan in)和扇出(fan out),由此Xavier方法中初始参数的方差也可写为 𝑉𝑎𝑟(𝑤)=2/(𝑓𝑎𝑛𝑖𝑛+𝑓𝑎𝑛𝑜𝑢𝑡),另外,Xavier在论文中所指出的,应该保持各层的激活值和梯度的方差在传播过程中保持一致,也被称为Glorot条件。

二 Sigmoid激活函数建模过程使用Xavier初始化

# 随机模块
import random

# 绘图模块
import matplotlib as mpl
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import seaborn as sns

# numpy
import numpy as np

# pytorch
import torch
from torch import nn,optim
import torch.nn.functional as F
from torch.utils.data import Dataset,TensorDataset,DataLoader
from torch.utils.data import random_split
from torch.utils.tensorboard import SummaryWriter

本文涉及到的自建函数

#回归类数据集创建函数
def tensorGenReg(num_examples=1000,w=[2,-1,1],bias=True,delta=0.01,deg=1):
    
    """
    回归类数据集创建函数
    param num_examples:创建数据集的张量
    param w:包括截距(如果存在)特征系数张量
    param bias:是否需要截距
    param delta:扰动项取值
    param deg :方程次数
    return :生成的特征张量和标签张量
    """
    if bias==True:
        
        num_inputs=len(w)-1   #特征张量
        features_true=torch.randn(num_examples,num_inputs)   #不包含全是1的列的特征张量
        w_true=torch.tensor(w[:-1]).reshape(-1,1).float()   #自变量系数
        b_true=torch.tensor(w[-1]).float()
        if num_inputs==1:
            labels_true=torch.pow(features_true,deg)*w_true+b_true
        else:
            labels_true=torch.mm(torch.pow(features_true,deg),w_true)+b_true
        features=torch.cat((features_true,torch.ones(len(features_true)),1),1)
        labels=labels_true+torch.randn(size=labels_true.shape)*delta
    else:
        num_inputs=len(w)
        features=torch.randn(num_examples,num_inputs)
        w_true=torch.tensor(w).reshape(-1,1).float()
        if num_inputs==1:
            labels_true=torch.pow(features,deg)*w_true
        else:
            labels_true=torch.mm(torch.pow(features,deg),w_true)
        labels=labels_true+torch.randn(size=labels_true.shape)*delta
    return features,labels
class GenData(Dataset):
    def __init__(self,features,labels):
        self.features=features
        self.labels=labels
        self.lens=len(features)
    def __getitem__(self,index):
        return self.features[index,:],  self.labels[index]
    
    def __len__(self):
        return self.lens
    
def split_loader(features,labels,batch_size=10,rate=0.7):
    """
    数据封装,切分和加载函数
    param features:输入的特征
    param labels:数据集标签张量
    param batch_size:数据加载时每一个小批数据量
    param rate:训练集数据占比
    return:加载好的训练集合测试集
    """
    data=GenData(features,labels)
    num_train=int(data.lens*0.7)
    num_test=data.lens-num_train
    data_train,data_test=random_split(data,[num_train,num_test])
    train_loader=DataLoader(data_train,batch_size=batch_size,shuffle=True)
    test_loader=DataLoader(data_test,batch_size=batch_size,shuffle=False)
    return (train_loader,test_loader)

class Sigmoid_class3(nn.Module):
    def __init__(self,in_features=2,n_hidden1=4,n_hidden2=4,n_hidden3=4,out_features=1,BN_model=None):
        super(Sigmoid_class3,self).__init__()
        self.linear1=nn.Linear(in_features,n_hidden1)
        self.normalize1=nn.BatchNorm1d(n_hidden1)
        self.linear2=nn.Linear(n_hidden1,n_hidden2)
        self.normalize2=nn.BatchNorm1d(n_hidden2)
        self.linear3=nn.Linear(n_hidden2,n_hidden3)
        self.normalize3=nn.BatchNorm1d(n_hidden3)
        self.linear4=nn.Linear(n_hidden3,out_features)
        self.BN_model=BN_model
        
    def forward(self,x):
        if self.BN_model==None:
            z1=self.linear1(x)
            p1=torch.sigmoid(z1)
            z2=self.linear2(p1)
            p2=torch.sigmoid(z2)
            z3=self.linear3(p2)
            p3=torch.sigmoid(z3)
            out=self.linear4(p3)
        elif self.BN_model=='pre':
            z1=self.normalize1(self.linear1(x))
            p1=torch.sigmoid(z1)
            z2=self.normalize2(self.linear2(p1))
            p2=torch.sigmoid(z2)
            z3=self.normalize3(self.linear3(p2))
            p3=torch.sigmoid(z3)
            out=self.linear4(self.normalize3(p3))
        elif self.BN_model=='post':
            z1=self.linear1(x)
            p1=torch.sigmoid(z1)
            z2=self.linear2(self.normalize1(p1))
            p2=torch.sigmoid(z2)
            z3=self.linear3(self.normalize2(p2))
            p3=torch.sigmoid(z3)
            out=self.linear4(self.normalize3(p3))
        
        return out

class Sigmoid_class4(nn.Module):
    def __init__(self,in_features=2,n_hidden1=4,n_hidden2=4,n_hidden3=4,n_hidden4=4,out_features=1,BN_model=None):
        super(Sigmoid_class4,self).__init__()
        self.linear1=nn.Linear(in_features,n_hidden1)
        self.normalize1=nn.BatchNorm1d(n_hidden1)
        self.linear2=nn.Linear(n_hidden1,n_hidden2)
        self.normalize2=nn.BatchNorm1d(n_hidden2)
        self.linear3=nn.Linear(n_hidden2,n_hidden3)
        self.normalize3=nn.BatchNorm1d(n_hidden3)
        self.linear4=nn.Linear(n_hidden3,n_hidden4)
        self.normalize4=nn.BatchNorm1d(n_hidden4)
        self.linear5=nn.Linear(n_hidden4,out_features)
        self.BN_model=BN_model
        
    def forward(self,x):
        if self.BN_model==None:
            z1=self.linear1(x)
            p1=torch.sigmoid(z1)
            z2=self.linear2(p1)
            p2=torch.sigmoid(z2)
            z3=self.linear3(p2)
            p3=torch.sigmoid(z3)
            z4=self.linear4(p3)
            p4=torch.sigmoid(z4)
            out=self.linear5(p4)
            
        elif self.BN_model=='pre':
            z1=self.normalize1(self.linear1(x))
            p1=torch.sigmoid(z1)
            z2=self.normalize2(self.linear2(p1))
            p2=torch.sigmoid(z2)
            z3=self.normalize3(self.linear3(p2))
            p3=torch.sigmoid(z3)
            z4=self.normalize4(self.linear4(p3))
            p4=torch.sigmoid(z4)
            out=self.linear5(p4)
            
        elif self.BN_model=='post':
            z1=self.linear1(x)
            p1=torch.sigmoid(z1)
            z2=self.linear2(self.normalize1(p1))
            p2=torch.sigmoid(z2)
            z3=self.linear3(self.normalize1(p2))
            p3=torch.sigmoid(z3)
            z4=self.linear4(self.normalize1(p3))
            p4=torch.sigmoid(z4)
            out=self.linear5(self.normalize1(p4))
        return out
            
            
            
def fit(net,criterion,optimizer,batchdata,epochs=3,cla=False):
    """
    模型训练函数
    param net:待训练的模型
    param criterion:损失函数
    param optimizer:优化算法
    param batchdata:训练数据集
    param cla:是否是分类问题
    param epochs:遍历数据次数
    """
    for epoch in range(epochs):
        for X,y in batchdata:
            if cla==True:
                y=y.flatten().long()
            yhat=net.forward(X)
            loss=criterion(yhat,y)
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
def mse_cal(data_loader,net):
    """
    mse计算函数
    param data_loader:加载好的数据
    param net:模型
    return:根据输入的数据,输出其mse计算结果
    """
    data=data_loader.dataset  #还原datasetlei
    X=data[:][0]
    y=data[:][1]
    yhat=net(X)
    return F.mse_loss(yhat,y)
    
def model_comparison(model_l
                    ,name_l
                     ,train_data
                     ,test_data
                     ,num_epochs=20
                     ,criterion=nn.MSELoss()
                     ,optimizer=optim.SGD
                     ,lr=0.03
                     ,cla=False
                     ,eva=mse_cal
                    ):
    
    
    """
    模型对比函数
    param  model_l:模型序列
    param name_l:模型名称序列
    param train_data:训练数据
    param test_data:测试数据
    param num_epochs:迭代轮数
    param criterion:损失函数
    param lr:学习率
    param cla:是否是分类模型
    param eva:模型评估指标
    return:评估指标张量矩阵    
    """
    train_l=torch.zeros(len(model_l),num_epochs)
    test_l=torch.zeros(len(model_l),num_epochs)
    #模型训练
    for epochs in range(num_epochs):
        for i ,model in enumerate(model_l):
            model.train()
            fit(net=model
               ,criterion=criterion
                ,optimizer=optimizer(model.parameters(),lr=lr)
                ,batchdata=train_data
                ,epochs=epochs
                ,cla=cla
               )
            model.eval()
            train_l[i][epochs]=eva(train_data,model).detach()
            test_l[i][epochs]=eva(test_data,model).detach()
    return train_l,test_l         

class tanh_class3(nn.Module):                                   
    def __init__(self, in_features=2, n_hidden1=4, n_hidden2=4, n_hidden3=4, out_features=1, BN_model=None):       
        super(tanh_class3, self).__init__()
        self.linear1 = nn.Linear(in_features, n_hidden1)
        self.normalize1 = nn.BatchNorm1d(n_hidden1)
        self.linear2 = nn.Linear(n_hidden1, n_hidden2)
        self.normalize2 = nn.BatchNorm1d(n_hidden2)
        self.linear3 = nn.Linear(n_hidden2, n_hidden3)
        self.normalize3 = nn.BatchNorm1d(n_hidden3)
        self.linear4 = nn.Linear(n_hidden3, out_features) 
        self.BN_model = BN_model
        
    def forward(self, x):
        if self.BN_model == None:
            z1 = self.linear1(x)
            p1 = torch.tanh(z1)
            z2 = self.linear2(p1)
            p2 = torch.tanh(z2)
            z3 = self.linear3(p2)
            p3 = torch.tanh(z3)
            out = self.linear4(p3)
        elif self.BN_model == 'pre':
            z1 = self.normalize1(self.linear1(x))
            p1 = torch.tanh(z1)
            z2 = self.normalize2(self.linear2(p1))
            p2 = torch.tanh(z2)
            z3 = self.normalize3(self.linear3(p2))
            p3 = torch.tanh(z3)
            out = self.linear4(p3)
        elif self.BN_model == 'post':
            z1 = self.linear1(x)
            p1 = torch.tanh(z1)
            z2 = self.linear2(self.normalize1(p1))
            p2 = torch.tanh(z2)
            z3 = self.linear3(self.normalize2(p2))
            p3 = torch.tanh(z3)
            out = self.linear4(self.normalize3(p3))
        return out
class Sigmoid_class2(nn.Module):                                   
    def __init__(self, in_features=2, n_hidden1=4, n_hidden2=4, out_features=1, BN_model=None):       
        super(Sigmoid_class2, self).__init__()
        self.linear1 = nn.Linear(in_features, n_hidden1)
        self.normalize1 = nn.BatchNorm1d(n_hidden1)
        self.linear2 = nn.Linear(n_hidden1, n_hidden2)
        self.normalize2 = nn.BatchNorm1d(n_hidden2)
        self.linear3 = nn.Linear(n_hidden2, out_features) 
        self.BN_model = BN_model
        
    def forward(self, x):
        if self.BN_model == None:
            z1 = self.linear1(x)
            p1 = torch.sigmoid(z1)
            z2 = self.linear2(p1)
            p2 = torch.sigmoid(z2)
            out = self.linear3(p2)
        elif self.BN_model == 'pre':
            z1 = self.normalize1(self.linear1(x))
            p1 = torch.sigmoid(z1)
            z2 = self.normalize2(self.linear2(p1))
            p2 = torch.sigmoid(z2)
            out = self.linear3(p2)
        elif self.BN_model == 'post':
            z1 = self.linear1(x)
            p1 = torch.sigmoid(z1)
            z2 = self.linear2(self.normalize1(p1))
            p2 = torch.sigmoid(z2)
            out = self.linear3(self.normalize2(p2))
        return out
    
class ReLU_class3(nn.Module):                                   
    def __init__(self, in_features=2, n_hidden1=4, n_hidden2=4, n_hidden3=4, out_features=1, bias=True, BN_model=None):       
        super(ReLU_class3, self).__init__()
        self.linear1 = nn.Linear(in_features, n_hidden1, bias=bias)
        self.normalize1 = nn.BatchNorm1d(n_hidden1)
        self.linear2 = nn.Linear(n_hidden1, n_hidden2, bias=bias)
        self.normalize2 = nn.BatchNorm1d(n_hidden2)
        self.linear3 = nn.Linear(n_hidden2, n_hidden3, bias=bias)
        self.normalize3 = nn.BatchNorm1d(n_hidden3)
        self.linear4 = nn.Linear(n_hidden3, out_features, bias=bias)
        self.BN_model = BN_model
        
    def forward(self, x):  
        if self.BN_model == None:
            z1 = self.linear1(x)
            p1 = torch.relu(z1)
            z2 = self.linear2(p1)
            p2 = torch.relu(z2)
            z3 = self.linear3(p2)
            p3 = torch.relu(z3)
            out = self.linear4(p3)
        elif self.BN_model == 'pre':
            z1 = self.normalize1(self.linear1(x))
            p1 = torch.relu(z1)
            z2 = self.normalize2(self.linear2(p1))
            p2 = torch.relu(z2)
            z3 = self.normalize3(self.linear3(p2))
            p3 = torch.relu(z3)
            out = self.linear4(p3)
        elif self.BN_model == 'post':
            z1 = self.linear1(x)
            p1 = torch.relu(z1)
            z2 = self.linear2(self.normalize1(p1))
            p2 = torch.relu(z2)
            z3 = self.linear3(self.normalize2(p2))
            p3 = torch.relu(z3)
            out = self.linear4(self.normalize3(p3))
        return out

#设置随机种子
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
torch.manual_seed(420)

#实例化模型
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)
   
        
#创建模型容器
model_l=[sigmoid_model3,sigmoid_model3_init]
name_l=['sigmoid_model3','sigmoid_model3_init']
def weights_vp(model,att='grad'):
    
    """
    观察各层参数和梯度的小提琴绘图
    param model:观察对象
    param att:选择参数梯度(grad)还是参数取值(weights)
    return :对应att的小提琴图
    """
    vp=[]
    for i ,m in enumerate(model.modules()):
        if isinstance(m,nn.Linear):
            if att=='grad':
                vp_x=m.weight.grad.detach().reshape(-1,1).numpy()
            else:
                vp_x=m.weight.detach().reshape(-1,1).numpy()
            vp_y=np.full_like(vp_x,i)
            vp_a=np.concatenate((vp_x,vp_y),1)
            vp.append(vp_a)
    vp_r=np.concatenate((vp),0)
    ax=sns.violinplot(y=vp_r[:,0],x=vp_r[:,1])
    ax.set(xlabel='num_hidden',title=att)
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')

image.png

weights_vp(sigmoid_model3_init,att='grad')

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

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')

image.png

#测试误差
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')

image.png Xavier初始化的作用核心在于保证各层梯度取值的平稳分布,从而确保各层模型学习的有效性,最终在模型结果的表现上,经过Xavier初始化参数的模型学习效率更高、收敛速度更快。

2.1 极端情况下Xavier初始化效果

在一些极端情况下,Xavier初始化效果会更加明显。以四层sigmoid隐藏层的神经网络为例,观察Xavier初始化在规避梯度消失问题时的效果。

torch.manual_seed(420)
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')

image.png

#测试误差
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')

image.png sigmoid_model4在之前实验中出现了严重梯度消失的模型,由于前几层基本丧失学习能力,sigmoid_model4本身效果并不好。但加入Xavier初始化之后,init模型能够极大程度规避梯度消失问题,从而获得更好的效果。

三 tanh激活函数建模过程使用Xavier初始化

相比于sigmoid激活函数,Xavier初始化方法更适用于tanh激活函数,核心原因在于tanh激活函数本身能够生成Zero-centered Data,配合Xavier初始化生成的参数,能够更好的确保各层梯度平稳、确保各层平稳学习。

  • 以三层tanh激活函数隐藏层的神经网络为例,测试Xavier初始化效果。
#设置随机种子
torch.manual_seed(420)

#创建最高此项为2 的多项式回归数据集
features,labels=tensorGenReg(w=[2,-1],bias=False,deg=2)

#进行数据集切分与加载
train_loader,test_loader=split_loader(features,labels)
# 设置随机数种子
torch.manual_seed(420)  

# 实例化模型
tanh_model3 = tanh_class3()                   # 保留原参数
tanh_model3_init = tanh_class3()              # 使用Xavier初始化参数
# 设置随机数种子
torch.manual_seed(420)  

# 修改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 = 20
# 模型训练
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(tanh_model3, att="grad")

image.png

weights_vp(tanh_model3_init, att="grad")

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

# 设置随机数种子
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)

# 训练误差
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')

image.png

# 测试误差
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')

image.png 模型收敛速度更快,迭代多轮之后也变得更加稳定。

四 Kaiming方法(HE初始化)

4.1 HE初始化基础

  • 尽管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方法。

4.2 Kaiming方法相关参数解释

  • mode:参数表示选择带入扇入还是扇出的神经元个数进行计算,正如前文所说,理论上二者对建模没有明显影响,可任选其一,但实际由于模型个体差异,在实际使用过程中还是略有差异,我们可以根据实际效果进行选择;
  • a:为使用ReLU变种激活函数时的修正系数;
  • nonlinearity:表示所选用的变种ReLU激活函数类型,需要配合a参数使用
torch.manual_seed(420)
# 设置随机数种子
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.001
num_epochs = 20
# 设置随机数种子
torch.manual_seed(420)  

# 实例化模型
relu_model3 = ReLU_class3()                   # 保留原参数
relu_model3_init = ReLU_class3()              # 使用HE初始化参数

# 修改init模型初始参数
for m in relu_model3_init.modules():
    if isinstance(m, nn.Linear):
        nn.init.kaiming_uniform_(m.weight)
        
# 创建模型容器
model_l = [relu_model3, relu_model3_init]           
name_l = ['relu_model3', 'relu_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 = 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')

image.png

# 测试误差
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')

image.png 使用HE初始化之后模型收敛速度明显提升。