【实验八】前馈神经网络(4)优化问题

news2024/11/6 19:38:09

1 参数初始化

 模型构建

模型训练 

优化

完整代码

2 梯度消失问题

模型构建

模型训练

完整代码

3 死亡Relu问题

模型构建

模型训练

 优化

完整代码

1 参数初始化

        实现一个神经网络前,需要先初始化模型参数。如果对每一层的权重和偏置都用0初始化,那么通过第一遍前向计算,所有隐藏层神经元的激活值都相同;在反向传播时,所有权重的更新也都相同,这样会导致隐藏层神经元没有差异性,出现对称权重现象

导入需要的库:

import torch.nn as nn
import torch.nn.functional as F
from torch.nn.init import constant_, normal_, uniform_
import torch
from data import make_moons
from nndl import accuracy
from Runner2_2 import RunnerV2_2
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt

        这次实验又认识到一个pytorch新的模块torch.nn.init ,是 PyTorch 中一个用于初始化神经网络模型参数的模块。 

pytorch 笔记:torch.nn.init


总结常用的几个有:

常数初始化:将权重或偏置初始化为固定值torch.nn.init.constant_(tensor, value)

正态分布初始化:从正态分布中随机生成权重torch.nn.init.normal_(tensor, mean, std)

均匀分布初始化:从均匀分布中随机生成权重。torch.nn.init.uniform_(tensor, a, b)

零初始化:将权重或偏置初始化为零torch.nn.init.zeros_(tensor)

 模型构建

将模型参数全都初始化为0

class Model_MLP_L2_V4(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super(Model_MLP_L2_V4, self).__init__()
        # 定义第一个线性层,输入特征数为 input_size,输出特征数为 hidden_size
        self.fc1 = nn.Linear(input_size, hidden_size)
        '''
         weight为权重参数属性,bias为偏置参数属性,这里使用'torch.nn.init.constant_'进行常量初始化
        '''
        # 初始化第一个线性层的权重和偏置为 0
        constant_(self.fc1.weight, 0.0)
        constant_(self.fc1.bias, 0.0)
        # 定义第二个线性层,输入特征数为 hidden_size,输出特征数为 output_size
        self.fc2 = nn.Linear(hidden_size, output_size)
        # 初始化第二个线性层的权重和偏置为 0
        constant_(self.fc2.weight, 0.0)
        constant_(self.fc2.bias, 0.0)
        self.act_fn = F.sigmoid

    # 前向计算
    def forward(self, inputs):
        z1 = self.fc1(inputs)
        a1 = self.act_fn(z1)
        z2 = self.fc2(a1)
        a2 = self.act_fn(z2)
        return a2

设置打印权重变化的函数: 

def print_weight(runner):
    print('The weights of the Layers:')
    # 通过 enumerate() 可以同时获取参数的索引 i 和参数的内容 item
    for i, item in enumerate(runner.model.named_parameters()):
        print(item)
        print('=========================')

模型训练 

利用runner类训练模型:

# ================================训练模型===========================
input_size = 2
hidden_size = 5
output_size = 1
model = Model_MLP_L2_V4(input_size=input_size, hidden_size=hidden_size, output_size=output_size)

# 设置损失函数
loss_fn = F.binary_cross_entropy

# 设置优化器
learning_rate = 0.2
optimizer = torch.optim.SGD(params=model.parameters(), lr=learning_rate)

# 设置评价指标
metric = accuracy

# 其他参数
epoch = 2000
saved_path = 'best_model.pdparams'

# 实例化RunnerV2_2类,并传入训练配置
runner = RunnerV2_2(model, optimizer, metric, loss_fn)

runner.train([X_train, y_train], [X_dev, y_dev], num_epochs=5, log_epochs=50, save_path="best_model.pdparams",
             custom_print_log=print_weight)

 输出结果:

The weights of the Layers:
('fc1.weight', Parameter containing:
tensor([[0., 0.],
        [0., 0.],
        [0., 0.],
        [0., 0.],
        [0., 0.]], requires_grad=True))
=========================
('fc1.bias', Parameter containing:
tensor([0., 0., 0., 0., 0.], requires_grad=True))
=========================
('fc2.weight', Parameter containing:
tensor([[0., 0., 0., 0., 0.]], requires_grad=True))
=========================
('fc2.bias', Parameter containing:
tensor([0.], requires_grad=True))
=========================
[Evaluate] best accuracy performence has been updated: 0.00000 --> 0.47500
[Train] epoch: 0/5, loss: 0.6931473016738892
The weights of the Layers:
('fc1.weight', Parameter containing:
tensor([[0., 0.],
        [0., 0.],
        [0., 0.],
        [0., 0.],
        [0., 0.]], requires_grad=True))
=========================
('fc1.bias', Parameter containing:
tensor([0., 0., 0., 0., 0.], requires_grad=True))
=========================
('fc2.weight', Parameter containing:
tensor([[0.0008, 0.0008, 0.0008, 0.0008, 0.0008]], requires_grad=True))
=========================
('fc2.bias', Parameter containing:
tensor([0.0016], requires_grad=True))
=========================
The weights of the Layers:
('fc1.weight', Parameter containing:
tensor([[ 9.3081e-06, -7.6568e-06],
        [ 9.3081e-06, -7.6568e-06],
        [ 9.3081e-06, -7.6568e-06],
        [ 9.3081e-06, -7.6568e-06],
        [ 9.3081e-06, -7.6568e-06]], requires_grad=True))
=========================
('fc1.bias', Parameter containing:
tensor([2.7084e-07, 2.7084e-07, 2.7084e-07, 2.7084e-07, 2.7084e-07],
       requires_grad=True))
=========================
('fc2.weight', Parameter containing:
tensor([[0.0015, 0.0015, 0.0015, 0.0015, 0.0015]], requires_grad=True))
=========================
('fc2.bias', Parameter containing:
tensor([0.0029], requires_grad=True))
=========================
The weights of the Layers:
('fc1.weight', Parameter containing:
tensor([[ 2.6847e-05, -2.2122e-05],
        [ 2.6847e-05, -2.2122e-05],
        [ 2.6847e-05, -2.2122e-05],
        [ 2.6847e-05, -2.2122e-05],
        [ 2.6847e-05, -2.2122e-05]], requires_grad=True))
=========================
('fc1.bias', Parameter containing:
tensor([7.2455e-07, 7.2455e-07, 7.2455e-07, 7.2455e-07, 7.2455e-07],
       requires_grad=True))
=========================
('fc2.weight', Parameter containing:
tensor([[0.0021, 0.0021, 0.0021, 0.0021, 0.0021]], requires_grad=True))
=========================
('fc2.bias', Parameter containing:
tensor([0.0042], requires_grad=True))
=========================
The weights of the Layers:
('fc1.weight', Parameter containing:
tensor([[ 5.1669e-05, -4.2643e-05],
        [ 5.1669e-05, -4.2643e-05],
        [ 5.1669e-05, -4.2643e-05],
        [ 5.1669e-05, -4.2643e-05],
        [ 5.1669e-05, -4.2643e-05]], requires_grad=True))
=========================
('fc1.bias', Parameter containing:
tensor([1.2953e-06, 1.2953e-06, 1.2953e-06, 1.2953e-06, 1.2953e-06],
       requires_grad=True))
=========================
('fc2.weight', Parameter containing:
tensor([[0.0026, 0.0026, 0.0026, 0.0026, 0.0026]], requires_grad=True))
=========================
('fc2.bias', Parameter containing:
tensor([0.0053], requires_grad=True))

所有权重的更新都相同,即出现了对称权重现象 

可视化权重变化:

# ===========可视化函数===============
def plot(runner, fig_name):
    plt.figure(figsize=(10, 5))
    epochs = [i for i in range(0, len(runner.train_scores))]
    plt.subplot(1, 2, 1)
    plt.plot(epochs, runner.train_loss, color='#e4007f', label="Train loss")
    plt.plot(epochs, runner.dev_loss, color='#f19ec2', linestyle='--', label="Dev loss")
    # 绘制坐标轴和图例
    plt.ylabel("loss", fontsize='large')
    plt.xlabel("epoch", fontsize='large')
    plt.legend(loc='upper right', fontsize='x-large')
    plt.subplot(1, 2, 2)
    plt.plot(epochs, runner.train_scores, color='#e4007f', label="Train accuracy")
    plt.plot(epochs, runner.dev_scores, color='#f19ec2', linestyle='--', label="Dev accuracy")
    # 绘制坐标轴和图例
    plt.ylabel("score", fontsize='large')
    plt.xlabel("epoch", fontsize='large')
    plt.legend(loc='lower right', fontsize='x-large')
    plt.savefig(fig_name)
    plt.show()
plot(runner, 'fw-acc.pdf')

       

从图像可以看出,二分类score为50%左右,说明模型没有学到任何内容。训练和验证的loss几乎没有怎么下降。

优化

为了避免对称权重现象,可以使用高斯分布或均匀分布初始化神经网络的参数。

高斯分布和均匀分布采样的实现和可视化代码如下:

import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
import torch

# 使用'torch.normal'实现高斯分布采样,其中'mean'为高斯分布的均值,'std'为高斯分布的标准差,'shape'为输出形状
gausian_weights = torch.normal(mean=0.0, std=1.0, size=[10000])
# 使用'torch.uniform'实现在[min,max)范围内的均匀分布采样,其中'shape'为输出形状
uniform_weights = torch.Tensor(10000)
uniform_weights.uniform_(-1,1)
gausian_weights=gausian_weights.numpy()
uniform_weights=uniform_weights.numpy()
print(uniform_weights)
# 绘制两种参数分布
plt.figure()
plt.subplot(1,2,1)
plt.title('Gausian Distribution')
plt.hist(gausian_weights, bins=200, density=True, color='#f19ec2')
plt.subplot(1,2,2)
plt.title('Uniform Distribution')
plt.hist(uniform_weights, bins=200, density=True, color='#e4007f')
plt.savefig('fw-gausian-uniform.pdf')
plt.show()

完整代码

'''
@author: lxy
@function: The Impact of Zero Weight Initialization
@date: 2024/10/31
'''
import torch.nn as nn
import torch.nn.functional as F
from torch.nn.init import constant_, normal_, uniform_
import torch
from data import make_moons
from nndl import accuracy
from Runner2_2 import RunnerV2_2
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt


class Model_MLP_L2_V4(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super(Model_MLP_L2_V4, self).__init__()
        # 定义第一个线性层,输入特征数为 input_size,输出特征数为 hidden_size
        self.fc1 = nn.Linear(input_size, hidden_size)
        '''
         weight为权重参数属性,bias为偏置参数属性,这里使用'torch.nn.init.constant_'进行常量初始化
        '''
        # 初始化第一个线性层的权重和偏置为 0
        constant_(self.fc1.weight, 0.0)
        constant_(self.fc1.bias, 0.0)
        # 定义第二个线性层,输入特征数为 hidden_size,输出特征数为 output_size
        self.fc2 = nn.Linear(hidden_size, output_size)
        # 初始化第二个线性层的权重和偏置为 0
        constant_(self.fc2.weight, 0.0)
        constant_(self.fc2.bias, 0.0)

        self.act_fn = F.sigmoid

    # 前向计算
    def forward(self, inputs):
        z1 = self.fc1(inputs)
        a1 = self.act_fn(z1)
        z2 = self.fc2(a1)
        a2 = self.act_fn(z2)
        return a2

def print_weight(runner):
    print('The weights of the Layers:')
    # 通过 enumerate() 可以同时获取参数的索引 i 和参数的内容 item
    for i, item in enumerate(runner.model.named_parameters()):
        print(item)
        print('=========================')

# =============================数据集=======================
# 数据集构建
n_samples = 1000
X, y = make_moons(n_samples=n_samples, shuffle=True, noise=0.2)
# 划分数据集
num_train = 640  # 训练集样本数量
num_dev = 160    # 验证集样本数量
num_test = 200   # 测试集样本数量
# 根据指定数量划分数据集
X_train, y_train = X[:num_train], y[:num_train]  # 训练集
X_dev, y_dev = X[num_train:num_train + num_dev], y[num_train:num_train + num_dev]  # 验证集
X_test, y_test = X[num_train + num_dev:], y[num_train + num_dev:]  # 测试集
# 调整标签的形状,将其转换为[N, 1]的格式
y_train = y_train.reshape([-1, 1])
y_dev = y_dev.reshape([-1, 1])
y_test = y_test.reshape([-1, 1])
# ================================训练模型===========================
input_size = 2
hidden_size = 5
output_size = 1
model = Model_MLP_L2_V4(input_size=input_size, hidden_size=hidden_size, output_size=output_size)

# 设置损失函数
loss_fn = F.binary_cross_entropy

# 设置优化器
learning_rate = 0.2
optimizer = torch.optim.SGD(params=model.parameters(), lr=learning_rate)

# 设置评价指标
metric = accuracy

# 其他参数
epoch = 2000
saved_path = 'best_model.pdparams'

# 实例化RunnerV2_2类,并传入训练配置
runner = RunnerV2_2(model, optimizer, metric, loss_fn)

runner.train([X_train, y_train], [X_dev, y_dev], num_epochs=5, log_epochs=50, save_path="best_model.pdparams",
             custom_print_log=print_weight)

# ===========可视化函数===============
def plot(runner, fig_name):
    plt.figure(figsize=(10, 5))
    epochs = [i for i in range(0, len(runner.train_scores))]
    plt.subplot(1, 2, 1)
    plt.plot(epochs, runner.train_loss, color='#e4007f', label="Train loss")
    plt.plot(epochs, runner.dev_loss, color='#f19ec2', linestyle='--', label="Dev loss")
    # 绘制坐标轴和图例
    plt.ylabel("loss", fontsize='large')
    plt.xlabel("epoch", fontsize='large')
    plt.legend(loc='upper right', fontsize='x-large')
    plt.subplot(1, 2, 2)
    plt.plot(epochs, runner.train_scores, color='#e4007f', label="Train accuracy")
    plt.plot(epochs, runner.dev_scores, color='#f19ec2', linestyle='--', label="Dev accuracy")
    # 绘制坐标轴和图例
    plt.ylabel("score", fontsize='large')
    plt.xlabel("epoch", fontsize='large')
    plt.legend(loc='lower right', fontsize='x-large')
    plt.savefig(fig_name)
    plt.show()
plot(runner, 'fw-acc.pdf')

2 梯度消失问题

由于Sigmoid型函数的饱和性,饱和区的导数更接近于0,误差经过每一层传递都会不断衰减。当网络层数很深时,梯度就会不停衰减,甚至消失,使得整个网络很难训练,这就是所谓的梯度消失问题。减轻梯度消失问题的方法有很多种,一种简单有效的方式就是使用导数比较大的激活函数,如:ReLU。

定义一个前馈神经网络,包含4个隐藏层和1个输出层,分别使用ReLU函数和sigmod函数作为激活函数,观察梯度变化。

模型构建

class Model_MLP_L5(nn.Module):
    def __init__(self, input_size, output_size, act='sigmoid', w_init=nn.init.normal_, b_init=nn.init.constant_):
        super(Model_MLP_L5, self).__init__()
        self.fc1 = nn.Linear(input_size, 3)
        self.fc2 = nn.Linear(3, 3)
        self.fc3 = nn.Linear(3, 3)
        self.fc4 = nn.Linear(3, 3)
        self.fc5 = nn.Linear(3, output_size)

        # 定义激活函数
        if act == 'sigmoid':
            self.act = F.sigmoid
        elif act == 'relu':
            self.act = F.relu
        elif act == 'lrelu':
            self.act = F.leaky_relu
        else:
            raise ValueError("Please enter sigmoid, relu or lrelu!")

        # 初始化权重和偏置
        self.init_weights(w_init, b_init)

    # 初始化线性层权重和偏置参数
    def init_weights(self, w_init, b_init):
        for m in self.children():
            if isinstance(m, nn.Linear):
                w_init(m.weight, mean=0.0, std=0.01)  # 对权重进行初始化
                b_init(m.bias, 1.0)  # 对偏置进行初始化

    def forward(self, inputs):
        outputs = self.fc1(inputs)
        outputs = self.act(outputs)
        outputs = self.fc2(outputs)
        outputs = self.act(outputs)
        outputs = self.fc3(outputs)
        outputs = self.act(outputs)
        outputs = self.fc4(outputs)
        outputs = self.act(outputs)
        outputs = self.fc5(outputs)
        outputs = F.sigmoid(outputs)
        return outputs

设置打印梯度的L 2范数的函数

def print_grads(runner, grad_norms):
    """ 打印模型每一层的梯度并计算其L2范数。 """
    print("The gradient of the Layers:")
    for name, param in runner.model.named_parameters():
        if param.requires_grad and param.grad is not None:
            grad_norm = param.grad.data.norm(2).item()  # 计算L2范数
            grad_norms[name].append(grad_norm)  # 记录L2范数
            print(f'Layer: {name}, Gradient Norm: {grad_norm}')

这里为什么要打印梯度范数? 

        当梯度过大时,它可能导致模型训练过程中的数值不稳定,进而影响模型的性能。

打印范数可以帮助我们了解梯度的幅度大小。范数可以衡量向量的大小,因此通过打印梯度的范数,我们可以直观地看到梯度的幅度是否过大或过小。简单说,就是范数可以反应梯度的大小,打印范数我们可以及时知道梯度的情况。


参考连接:

梯度爆炸实验

模型训练

分别使用sigmod函数和relu函数

# =====================使用sigmoid激活函数训练=====================
torch.manual_seed(111)
lr = 0.01
model = Model_MLP_L5(input_size=2, output_size=1, act='sigmoid')
optimizer = torch.optim.SGD(params=model.parameters(), lr=lr)
loss_fn = F.binary_cross_entropy
metric = accuracy

# 初始化L2范数记录字典
grad_norms_sigmoid = {name: [] for name, _ in model.named_parameters()}

# 实例化Runner类
runner = RunnerV2_2(model, optimizer, metric, loss_fn)
print("使用sigmoid函数为激活函数时:")
runner.train([X_train, y_train], [X_dev, y_dev],
             num_epochs=1, log_epochs=None,
             save_path="best_model.pdparams",
             custom_print_log=lambda runner: print_grads(runner, grad_norms_sigmoid))

# =====================使用ReLU激活函数训练=====================
torch.manual_seed(102)
model = Model_MLP_L5(input_size=2, output_size=1, act='relu')
optimizer = torch.optim.SGD(params=model.parameters(), lr=lr)
loss_fn = F.binary_cross_entropy

# 初始化L2范数记录字典
grad_norms_relu = {name: [] for name, _ in model.named_parameters()}

# 实例化Runner类
runner = RunnerV2_2(model, optimizer, metric, loss_fn)
print("使用ReLU函数为激活函数时:")
runner.train([X_train, y_train], [X_dev, y_dev],
             num_epochs=1, log_epochs=None,
             save_path="best_model.pdparams",
             custom_print_log=lambda runner: print_grads(runner, grad_norms_relu))

运行结果输出: 

使用sigmoid函数为激活函数时:
The gradient of the Layers:
Layer: fc1.weight, Gradient Norm: 2.4828878536498067e-11
Layer: fc1.bias, Gradient Norm: 1.8694254477757966e-11
Layer: fc2.weight, Gradient Norm: 1.2134693250231976e-08
Layer: fc2.bias, Gradient Norm: 9.58359702707412e-09
Layer: fc3.weight, Gradient Norm: 5.372268333303509e-06
Layer: fc3.bias, Gradient Norm: 4.236671884427778e-06
Layer: fc4.weight, Gradient Norm: 0.001065725926309824
Layer: fc4.bias, Gradient Norm: 0.0008412969764322042
Layer: fc5.weight, Gradient Norm: 0.27612796425819397
Layer: fc5.bias, Gradient Norm: 0.21845529973506927
[Evaluate] best accuracy performence has been updated: 0.00000 --> 0.54375
使用ReLU函数为激活函数时:
The gradient of the Layers:
Layer: fc1.weight, Gradient Norm: 1.736074395353171e-08
Layer: fc1.bias, Gradient Norm: 1.370427327174184e-08
Layer: fc2.weight, Gradient Norm: 1.4403226487047505e-06
Layer: fc2.bias, Gradient Norm: 8.300839340336097e-07
Layer: fc3.weight, Gradient Norm: 0.00011438350338721648
Layer: fc3.bias, Gradient Norm: 6.653369928244501e-05
Layer: fc4.weight, Gradient Norm: 0.009503044188022614
Layer: fc4.bias, Gradient Norm: 0.005468158517032862
Layer: fc5.weight, Gradient Norm: 0.3917791247367859
Layer: fc5.bias, Gradient Norm: 0.22893022000789642
[Evaluate] best accuracy performence has been updated: 0.00000 --> 0.54375

 可视化梯度范数的变化情况:

# 可视化梯度L2范数
def plot_grad_norms(grad_norms_sigmoid, grad_norms_relu):
    layers = list(grad_norms_sigmoid.keys())
    sigmoid_norms = [np.mean(grad_norms_sigmoid[layer]) for layer in layers]
    relu_norms = [np.mean(grad_norms_relu[layer]) for layer in layers]

    x = np.arange(len(layers))

    plt.figure(figsize=(10, 6))
    plt.plot(x, sigmoid_norms, marker='o', label='Sigmoid', color='b')
    plt.plot(x, relu_norms, marker='o', label='ReLU', color='r')

    plt.ylabel('Gradient L2 Norm')
    plt.title('Gradient L2 Norm by different Activation Function')
    plt.xticks(x, layers)
    plt.legend()

    # 设置 y 轴为对数坐标
    plt.yscale('log')

    # 设置 y 轴的范围
    plt.ylim(1e-8, 1)  # 设置下限为 1e-8,上限为 1

    # 设置 y 轴的刻度
    plt.yticks([1, 1e-1, 1e-2, 1e-3, 1e-4, 1e-5, 1e-6, 1e-7, 1e-8,1e-9,1e-10,1e-11])

    plt.grid()
    plt.tight_layout()
    plt.show()

      

        图中展示了使用不同激活函数时,网络每层梯度值的ℓ2​范数情况。从结果可以看到,5层的全连接前馈神经网络使用Sigmoid型函数作为激活函数时,梯度经过每一个神经层的传递都会不断衰减,最终传递到第一个神经层时,梯度几乎完全消失。改为ReLU激活函数后,梯度消失现象得到了缓解 

完整代码

'''
@author: lxy
@function: Exploration and Optimization of the Gradient Vanishing Problem
@date: 2024/10/31
'''
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.nn.init import constant_, normal_
import numpy as np
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
from data import make_moons
from nndl import accuracy
from Runner2_2 import RunnerV2_2

class Model_MLP_L5(nn.Module):
    def __init__(self, input_size, output_size, act='sigmoid', w_init=nn.init.normal_, b_init=nn.init.constant_):
        super(Model_MLP_L5, self).__init__()
        self.fc1 = nn.Linear(input_size, 3)
        self.fc2 = nn.Linear(3, 3)
        self.fc3 = nn.Linear(3, 3)
        self.fc4 = nn.Linear(3, 3)
        self.fc5 = nn.Linear(3, output_size)

        # 定义激活函数
        if act == 'sigmoid':
            self.act = F.sigmoid
        elif act == 'relu':
            self.act = F.relu
        elif act == 'lrelu':
            self.act = F.leaky_relu
        else:
            raise ValueError("Please enter sigmoid, relu or lrelu!")

        # 初始化权重和偏置
        self.init_weights(w_init, b_init)

    # 初始化线性层权重和偏置参数
    def init_weights(self, w_init, b_init):
        for m in self.children():
            if isinstance(m, nn.Linear):
                w_init(m.weight, mean=0.0, std=0.01)  # 对权重进行初始化
                b_init(m.bias, 1.0)  # 对偏置进行初始化

    def forward(self, inputs):
        outputs = self.fc1(inputs)
        outputs = self.act(outputs)
        outputs = self.fc2(outputs)
        outputs = self.act(outputs)
        outputs = self.fc3(outputs)
        outputs = self.act(outputs)
        outputs = self.fc4(outputs)
        outputs = self.act(outputs)
        outputs = self.fc5(outputs)
        outputs = F.sigmoid(outputs)
        return outputs


def print_grads(runner, grad_norms):
    """ 打印模型每一层的梯度并计算其L2范数。 """
    print("The gradient of the Layers:")
    for name, param in runner.model.named_parameters():
        if param.requires_grad and param.grad is not None:
            grad_norm = param.grad.data.norm(2).item()  # 计算L2范数
            grad_norms[name].append(grad_norm)  # 记录L2范数
            print(f'Layer: {name}, Gradient Norm: {grad_norm}')



# 可视化梯度L2范数
def plot_grad_norms(grad_norms_sigmoid, grad_norms_relu):
    layers = list(grad_norms_sigmoid.keys())
    sigmoid_norms = [np.mean(grad_norms_sigmoid[layer]) for layer in layers]
    relu_norms = [np.mean(grad_norms_relu[layer]) for layer in layers]

    x = np.arange(len(layers))

    plt.figure(figsize=(10, 6))
    plt.plot(x, sigmoid_norms, marker='o', label='Sigmoid', color='b')
    plt.plot(x, relu_norms, marker='o', label='ReLU', color='r')

    plt.ylabel('Gradient L2 Norm')
    plt.title('Gradient L2 Norm by different Activation Function')
    plt.xticks(x, layers)
    plt.legend()
    # 设置 y 轴为对数坐标
    plt.yscale('log')
    # 设置 y 轴的范围
    plt.ylim(1e-8, 1)  # 设置下限为 1e-8,上限为 1
    # 设置 y 轴的刻度
    plt.yticks([1, 1e-1, 1e-2, 1e-3, 1e-4, 1e-5, 1e-6, 1e-7, 1e-8,1e-9,1e-10,1e-11])

    plt.grid()
    plt.tight_layout()
    plt.show()


# =============================数据集=======================
# 数据集构建
n_samples = 1000
X, y = make_moons(n_samples=n_samples, shuffle=True, noise=0.2)
# 划分数据集
num_train = 640  # 训练集样本数量
num_dev = 160  # 验证集样本数量
num_test = 200  # 测试集样本数量
# 根据指定数量划分数据集
X_train, y_train = X[:num_train], y[:num_train]  # 训练集
X_dev, y_dev = X[num_train:num_train + num_dev], y[num_train:num_train + num_dev]  # 验证集
X_test, y_test = X[num_train + num_dev:], y[num_train + num_dev:]  # 测试集
# 调整标签的形状,将其转换为[N, 1]的格式
y_train = y_train.reshape([-1, 1])
y_dev = y_dev.reshape([-1, 1])
y_test = y_test.reshape([-1, 1])
# =====================使用sigmoid激活函数训练=====================
torch.manual_seed(111)
lr = 0.01
model = Model_MLP_L5(input_size=2, output_size=1, act='sigmoid')
optimizer = torch.optim.SGD(params=model.parameters(), lr=lr)
loss_fn = F.binary_cross_entropy
metric = accuracy

# 初始化L2范数记录字典
grad_norms_sigmoid = {name: [] for name, _ in model.named_parameters()}

# 实例化Runner类
runner = RunnerV2_2(model, optimizer, metric, loss_fn)
print("使用sigmoid函数为激活函数时:")
runner.train([X_train, y_train], [X_dev, y_dev],
             num_epochs=1, log_epochs=None,
             save_path="best_model.pdparams",
             custom_print_log=lambda runner: print_grads(runner, grad_norms_sigmoid))

# =====================使用ReLU激活函数训练=====================
torch.manual_seed(102)
model = Model_MLP_L5(input_size=2, output_size=1, act='relu')
optimizer = torch.optim.SGD(params=model.parameters(), lr=lr)
loss_fn = F.binary_cross_entropy

# 初始化L2范数记录字典
grad_norms_relu = {name: [] for name, _ in model.named_parameters()}

# 实例化Runner类
runner = RunnerV2_2(model, optimizer, metric, loss_fn)
print("使用ReLU函数为激活函数时:")
runner.train([X_train, y_train], [X_dev, y_dev],
             num_epochs=1, log_epochs=None,
             save_path="best_model.pdparams",
             custom_print_log=lambda runner: print_grads(runner, grad_norms_relu))

# 绘制梯度范数
plot_grad_norms(grad_norms_sigmoid, grad_norms_relu)

3 死亡Relu问题

        ReLU激活函数可以一定程度上改善梯度消失问题,但是ReLU函数在某些情况下容易出现死亡 ReLU问题,使得网络难以训练。

        这是由于激活前神经元通常也包含偏置项,如果偏置项是一个过小的负数当x<0时,ReLU函数的输出恒为0。在训练过程中,如果参数在一次不恰当的更新后,某个ReLU神经元在所有训练数据上都不能被激活(即输出为0),那么这个神经元自身参数的梯度永远都会是0,在以后的训练过程中永远都不能被激活。

模型构建

当神经层的偏置被初始化为一个相对于权重较大的负值时,可以想像,输入经过神经层的处理,最终的输出会为负值,从而导致死亡ReLU现象。这里我们初始化偏置为-8.0

class Model_MLP_L5(nn.Module):
    def __init__(self, input_size, output_size, act='sigmoid', w_init=nn.init.normal_, b_init=-8.0):
        super(Model_MLP_L5, self).__init__()
        self.fc1 = nn.Linear(input_size, 3)
        self.fc2 = nn.Linear(3, 3)
        self.fc3 = nn.Linear(3, 3)
        self.fc4 = nn.Linear(3, 3)
        self.fc5 = nn.Linear(3, output_size)

        # 定义激活函数
        if act == 'sigmoid':
            self.act = F.sigmoid
        elif act == 'relu':
            self.act = F.relu
        elif act == 'lrelu':
            self.act = F.leaky_relu
        else:
            raise ValueError("Please enter sigmoid, relu or lrelu!")

        # 初始化权重和偏置
        self.init_weights(w_init, b_init)

    # 初始化线性层权重和偏置参数
    def init_weights(self, w_init, b_init):
        for m in self.children():
            if isinstance(m, nn.Linear):
                w_init(m.weight, mean=0.0, std=0.01)  # 对权重进行初始化
                constant_(m.bias, b_init)

    def forward(self, inputs):
        outputs = self.fc1(inputs)
        outputs = self.act(outputs)
        outputs = self.fc2(outputs)
        outputs = self.act(outputs)
        outputs = self.fc3(outputs)
        outputs = self.act(outputs)
        outputs = self.fc4(outputs)
        outputs = self.act(outputs)
        outputs = self.fc5(outputs)
        outputs = F.sigmoid(outputs)
        return outputs

 设置打印梯度范数的函数:
 

def print_grads(runner, grad_norms):
    """ 打印模型每一层的梯度并计算其L2范数。 """
    print("The gradient of the Layers:")
    for name, param in runner.model.named_parameters():
        if param.requires_grad and param.grad is not None:
            grad_norm = param.grad.data.norm(2).item()  # 计算L2范数
            grad_norms[name].append(grad_norm)  # 记录L2范数
            print(f'Layer: {name}, Gradient Norm: {grad_norm}')

模型训练

使用relu函数-观察梯度变化

# 定义网络,并使用较大的负值来初始化偏置
model =  Model_MLP_L5(input_size=2, output_size=1, act='relu')
#model =  Model_MLP_L5(input_size=2, output_size=1, act='lrelu')
torch.manual_seed(111)
lr = 0.01
optimizer = torch.optim.SGD(params=model.parameters(), lr=lr)
loss_fn = F.binary_cross_entropy
metric = accuracy
# 初始化L2范数记录字典
grad_norms = {name: [] for name, _ in model.named_parameters()}
# 实例化Runner类
runner = RunnerV2_2(model, optimizer, metric, loss_fn)

# 启动训练
runner.train([X_train, y_train], [X_dev, y_dev],
             num_epochs=1, log_epochs=0,
             save_path="best_model.pdparams",
             custom_print_log=lambda runner: print_grads(runner, grad_norms))

运行结果:

The gradient of the Layers:
Layer: fc1.weight, Gradient Norm: 0.0
Layer: fc1.bias, Gradient Norm: 0.0
Layer: fc2.weight, Gradient Norm: 0.0
Layer: fc2.bias, Gradient Norm: 0.0
Layer: fc3.weight, Gradient Norm: 0.0
Layer: fc3.bias, Gradient Norm: 0.0
Layer: fc4.weight, Gradient Norm: 0.0
Layer: fc4.bias, Gradient Norm: 0.0
Layer: fc5.weight, Gradient Norm: 0.0
Layer: fc5.bias, Gradient Norm: 0.4887271523475647
[Evaluate] best accuracy performence has been updated: 0.00000 --> 0.50000

可以看出梯度反向传播时以及变为0了 ,出现了死亡relu问题

可视化梯度变化:

# 可视化梯度L2范数
def plot_grad_norms(grad_norms):
    layers = list(grad_norms.keys())
    norms = [np.mean(grad_norms[layer]) for layer in layers]
    x = np.arange(len(layers))  # x轴为层数
    plt.figure(figsize=(10, 6))
    plt.plot(x, norms, marker='o', label='ReLU', color='r')
    plt.ylabel('Gradient L2 Norm')
    plt.title('Gradient L2 Norm --Relu')
    plt.xticks(x, layers)
    plt.legend()
    # 设置 y 轴为对数坐标
    plt.yscale('log')
    # 设置 y 轴的范围
    plt.ylim(1e-8, 1)  # 设置下限为 1e-8,上限为 1
    # 设置 y 轴的刻度
    plt.yticks([1, 1e-1, 1e-2, 1e-3, 1e-4, 1e-5, 1e-6, 1e-7, 1e-8, 1e-9, 1e-12, 1e-17]
               )
    plt.grid()
    plt.tight_layout()
    plt.show()

从输出结果以及可视化的图像可以发现,使用 ReLU 作为激活函数,当满足条件时,会发生死亡ReLU问题,网络训练过程中 ReLU 神经元的梯度始终为0,参数无法更新。 

 优化

针对死亡ReLU问题,一种简单有效的优化方式就是将激活函数更换为Leaky ReLU、ELU等ReLU 的变种。接下来,观察将激活函数更换为 Leaky ReLU时的梯度情况。

model =  Model_MLP_L5(input_size=2, output_size=1, act='lrelu')
The gradient of the Layers:
Layer: fc1.weight, Gradient Norm: 1.6563501643453269e-16
Layer: fc1.bias, Gradient Norm: 1.6535552203171837e-16
Layer: fc2.weight, Gradient Norm: 1.4167183051694288e-13
Layer: fc2.bias, Gradient Norm: 1.0233488318897588e-12
Layer: fc3.weight, Gradient Norm: 6.822118980842617e-10
Layer: fc3.bias, Gradient Norm: 4.9233230825507235e-09
Layer: fc4.weight, Gradient Norm: 6.337210834317375e-06
Layer: fc4.bias, Gradient Norm: 4.57389687653631e-05
Layer: fc5.weight, Gradient Norm: 0.07076060771942139
Layer: fc5.bias, Gradient Norm: 0.510601818561554
[Evaluate] best accuracy performence has been updated: 0.00000 --> 0.55625

完整代码

'''
@author: lxy
@function: Exploration and Optimization of the Dead ReLU Problem
@date: 2024/10/31
'''
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.nn.init import constant_, normal_
import numpy as np
import matplotlib

matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
from data import make_moons
from nndl import accuracy
from Runner2_2 import RunnerV2_2


# 定义模型

class Model_MLP_L5(nn.Module):
    def __init__(self, input_size, output_size, act='sigmoid', w_init=nn.init.normal_, b_init=-8.0):
        super(Model_MLP_L5, self).__init__()
        self.fc1 = nn.Linear(input_size, 3)
        self.fc2 = nn.Linear(3, 3)
        self.fc3 = nn.Linear(3, 3)
        self.fc4 = nn.Linear(3, 3)
        self.fc5 = nn.Linear(3, output_size)

        # 定义激活函数
        if act == 'sigmoid':
            self.act = F.sigmoid
        elif act == 'relu':
            self.act = F.relu
        elif act == 'lrelu':
            self.act = F.leaky_relu
        else:
            raise ValueError("Please enter sigmoid, relu or lrelu!")

        # 初始化权重和偏置
        self.init_weights(w_init, b_init)

    # 初始化线性层权重和偏置参数
    def init_weights(self, w_init, b_init):
        for m in self.children():
            if isinstance(m, nn.Linear):
                w_init(m.weight, mean=0.0, std=0.01)  # 对权重进行初始化
                constant_(m.bias, b_init)

    def forward(self, inputs):
        outputs = self.fc1(inputs)
        outputs = self.act(outputs)
        outputs = self.fc2(outputs)
        outputs = self.act(outputs)
        outputs = self.fc3(outputs)
        outputs = self.act(outputs)
        outputs = self.fc4(outputs)
        outputs = self.act(outputs)
        outputs = self.fc5(outputs)
        outputs = F.sigmoid(outputs)
        return outputs



def print_grads(runner, grad_norms):
    """ 打印模型每一层的梯度并计算其L2范数。 """
    print("The gradient of the Layers:")
    for name, param in runner.model.named_parameters():
        if param.requires_grad and param.grad is not None:
            grad_norm = param.grad.data.norm(2).item()  # 计算L2范数
            grad_norms[name].append(grad_norm)  # 记录L2范数
            print(f'Layer: {name}, Gradient Norm: {grad_norm}')

# 可视化梯度L2范数
def plot_grad_norms(grad_norms):
    layers = list(grad_norms.keys())
    norms = [np.mean(grad_norms[layer]) for layer in layers]
    x = np.arange(len(layers))  # x轴为层数
    plt.figure(figsize=(10, 6))
    plt.plot(x, norms, marker='o', label='ReLU', color='r')
    plt.ylabel('Gradient L2 Norm')
    plt.title('Gradient L2 Norm --Relu')
    plt.xticks(x, layers)
    plt.legend()
    # 设置 y 轴为对数坐标
    plt.yscale('log')
    # 设置 y 轴的范围
    plt.ylim(1e-8, 1)  # 设置下限为 1e-8,上限为 1
    # 设置 y 轴的刻度
    plt.yticks([1, 1e-1, 1e-2, 1e-3, 1e-4, 1e-5, 1e-6, 1e-7, 1e-8, 1e-9, 1e-12, 1e-17]
               )
    plt.grid()
    plt.tight_layout()
    plt.show()
# =============================数据集=======================
# 数据集构建
n_samples = 1000
X, y = make_moons(n_samples=n_samples, shuffle=True, noise=0.2)
# 划分数据集
num_train = 640  # 训练集样本数量
num_dev = 160  # 验证集样本数量
num_test = 200  # 测试集样本数量
# 根据指定数量划分数据集
X_train, y_train = X[:num_train], y[:num_train]  # 训练集
X_dev, y_dev = X[num_train:num_train + num_dev], y[num_train:num_train + num_dev]  # 验证集
X_test, y_test = X[num_train + num_dev:], y[num_train + num_dev:]  # 测试集
# 调整标签的形状,将其转换为[N, 1]的格式
y_train = y_train.reshape([-1, 1])
y_dev = y_dev.reshape([-1, 1])
y_test = y_test.reshape([-1, 1])

# ===============================模型训练=================
model =  Model_MLP_L5(input_size=2, output_size=1, act='relu')
#model =  Model_MLP_L5(input_size=2, output_size=1, act='lrelu')
torch.manual_seed(111)
lr = 0.01
optimizer = torch.optim.SGD(params=model.parameters(), lr=lr)
loss_fn = F.binary_cross_entropy
metric = accuracy
# 初始化L2范数记录字典
grad_norms = {name: [] for name, _ in model.named_parameters()}
# 实例化Runner类
runner = RunnerV2_2(model, optimizer, metric, loss_fn)

# 启动训练
runner.train([X_train, y_train], [X_dev, y_dev],
             num_epochs=1, log_epochs=0,
             save_path="best_model.pdparams",
             custom_print_log=lambda runner: print_grads(runner, grad_norms))
# 绘制梯度范数
plot_grad_norms(grad_norms)

参考链接:

 点击查看实验内容

pytorch 笔记:torch.nn.init

 梯度爆炸实验

深度学习 --- 优化入门三(梯度消失和激活函数ReLU)

【AI知识点】梯度消失(Vanishing Gradient)和梯度爆炸(Exploding Gradient)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2229143.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vscode配置CC++编程环境的使用体验优化和补充说明

文章目录 快速编译运行&#x1f47a;code runner插件方案Code Runner Configuration 直接配置 相关指令和快捷键默认task配置和取消默认 配置文件补充介绍(可选 推荐阅读)&#x1f60a;使用vscode预置变量和环境变量环境变量的使用使用环境变量的好处环境变量可能引起的问题 检…

计算机网络:网络层 —— IPv4 地址与 MAC 地址 | ARP 协议

文章目录 IPv4地址与MAC地址的封装位置IPv4地址与MAC地址的关系地址解析协议ARP工作原理ARP高速缓存表 IPv4地址与MAC地址的封装位置 在数据传输过程中&#xff0c;每一层都会添加自己的头部信息&#xff0c;最终形成完整的数据包。具体来说&#xff1a; 应用层生成的应用程序…

【秋冬进补】灵芝玉叶膏冬令上选减补两不误

秋冬季不仅是进补季&#xff0c;也是肥胖增涨的季节&#xff0c;气温降低了&#xff0c;运动、户外活动量减少了&#xff0c;消耗吸收率减慢&#xff0c;饮食中肉类比例上升&#xff0c;绿蔬少了&#xff0c;油热量增加了&#xff0c;肥胖几率也一样增加了。因此&#xff0c;选…

HTML5 + CSS3 + JavaScript 编程语言学习教程

HTML5 CSS3 JavaScript 编程语言学习教程 欢迎来到这篇关于 HTML5、CSS3 和 JavaScript 的详细学习教程&#xff01;无论你是初学者还是有一定基础的开发者&#xff0c;这篇文章都将帮助你深入理解这三种技术的核心概念、语法和应用。 目录 HTML5 1.1 HTML5 简介1.2 HTML5 …

10月第4周AI资讯

阅读时间&#xff1a;3-4min 更新时间&#xff1a;2024.10.21-2024.10.25 目录 CoI-Agent&#xff1a;一键生成科研idea的AI研究助手 波兰电台正式启用 AI 主播 Claude可以像人类一样使用计算机 简单文本即可创建个性化语音 AI音乐制作工具新突破 CoI-Agent&#xff1a;…

[TypeError]: type ‘AbstractProvider‘ is not subscriptable

升级pdm到2.20.0后&#xff0c;执行pdm add --dev mypy时报错了&#xff1a; INFO: Adding group dev to lockfile Adding packages to dev dev-dependencies: pytest, pdm, ruff, click, mypy ⠋ 0:00:00 Resolving dependencies See /Users/mac10.12/Library/Logs/pdm/pdm-l…

(大开眼界)想要数据安全,企业需要选择正确的数据备份方案!七种常见数据备份策略方法及优缺点分析!

信息化时代&#xff0c;数据已成为企业的核心资产&#xff0c;其重要性不言而喻。 然而&#xff0c;随着数据价值的不断提升&#xff0c;其脆弱性也愈加明显。自然灾害、硬件故障、人为错误以及恶意攻击等因素&#xff0c;都可能导致数据的丢失或损坏。 因此&#xff0c;选择…

微信公众号(或微信浏览器)获取openId(网页授权)

下单支付需要openId 首先授权去拿到code --然后调用后太换取openId 1.去拿取code 下图中执行到window.location.href &#xff08; redirect_uri 传入当前路径-&#xff09;–执行后重新跳转到当前页面–但是路径上会带上code参数 //然后调用后台方法–将code传给后台得到 o…

如何找到优质的抖音视频素材

随着短视频的流行&#xff0c;越来越多的人开始拍摄和分享自己的作品。但很多创作者常常遇到一个问题&#xff1a;视频素材从哪里来&#xff1f;今天&#xff0c;我就为大家推荐一些优秀的网站&#xff0c;帮助你轻松找到优质的抖音视频素材。 蛙学网 首先推荐的是蛙学网。这个…

django快速基本配置(2)

知识星球 | 深度连接铁杆粉丝&#xff0c;运营高品质社群&#xff0c;知识变现的工具 目录 配置开发目录 配置MySQL数据库 配置Redis数据库 配置工程日志 用户注册 跨域CORS 注意 配置开发目录 libs 存放第三方的库文件 utils 存放项目自己定义的公共函数或类等 apps 存…

【SQL】SQL函数

&#x1f4e2; 前言 函数 是指一段可以直接被另一段程序调用的程序或代码。主要包括了以下4中类型的函数。 字符串函数数值函数日期函数流程函数 &#x1f384; 字符串函数 ⭐ 常用函数 函数 功能 CONCAT(S1,S2,...Sn) 字符串拼接&#xff0c;将S1&#xff0c;S2&#xff0…

Javaweb 实验4 xml

我发现了有些人喜欢静静看博客不聊天呐&#xff0c; 但是ta会点赞。 这样的人呢帅气低调有内涵&#xff0c; 美丽大方很优雅。 说的就是你&#xff0c; 不用再怀疑哦 实验四 XML 目的&#xff1a; 安装和使用XML的开发环境认识XML的不同类型掌握XML文档的基本语法了解D…

第二十章 Vue组件通信之父子通信

目录 一、引言 二、组件关系分类 三、组件通信的解决方案 3.1. 父子通信流程图 3.2. 父组件通过 props 将数据传递给子组件 3.2.1. 代码App.vue 3.2.2. 代码MySon.vue 3.3. 子组件利用 $emit 通知父组件修改更新 ​编辑3.3.1. 代码App.vue 3.3.2. 代码MySon.vue 3…

使用GDAL库的ogr2ogr将GeoJSON数据导入到PostgreSql中

数据下载 数据下载地址&#xff1a;https://datav.aliyun.com/portal/school/atlas/area_selector 我这里下载全国所有城市的数据进行导入 下载安装GDAL 以下是安装 ogr2ogr&#xff08;GDAL 工具集的一部分&#xff09;的步骤&#xff0c;适用于 Windows、macOS 和 Linux 系…

pycharm configurations中配置运行fastapi项目

环境 windows11 python3.11 fastapi0.115 使用virtualenv安装fastapi uvicorn pip install fastapi pip install uvicorn目的 在pycharm中可以一键运行&#xff0c;直接把命令行的运行参数配置到pycharm中, 即使用"uvicorn main:app --reload"运行main文件 配置 …

和小北一起Cozeplay | 扣子万圣节企划

前言&#xff1a; &#x1f383;&#x1f383;&#x1f383;在这个充满神秘与欢乐的万圣节季节&#xff0c;扣子携手小北&#xff0c;为大家带来了一场别开生面的Cozeplay活动&#xff01;无论你是想要体验一把惊悚刺激的万圣节氛围&#xff0c;还是想要展示自己的创意与才华&a…

工作转型与个人突破提升:如何在社会浪潮中激流勇进

文章目录 一、写在前面二、技术人的迷茫三、做好项目经理其实很难四、从纯技术者转型为管理者面临的事五、最重要的技能【重磅推荐&#xff01;免费简单内网穿透神器&#xff01;支持linuxwindows】 一、写在前面 近期工作变动&#xff0c;虽然说对于开发者而言&#xff0c;工…

为什么NMOS管比PMOS管更受欢迎?

NMOS在实际应用中为何比PMOS要更受欢迎。本文将从导电沟道、电子迁移率和器件速度等多个方面来展开讲解。 首先是在性能方面考虑&#xff1a; 与NMOS管驱动能力相同的一个PMOS管&#xff0c;其器件面积可能是NMOS管的2&#xff5e;3倍&#xff0c;然而器件面积会影响导通电阻…

Windows安装Git最新保姆级教程【附安装包】

一、Git下载: 链接&#xff1a;https://pan.baidu.com/s/1_uH-_-cdBb6GD58oLcxvAA 提取码&#xff1a;m366 二、安装Git 1.右键桌面【此电脑】-【属性】&#xff0c;查看操作系统是32位还是64位。 2.下载好对应64位操作系统版本的Git&#xff0c;解压并打开。 我电脑系统是64位…

网络通信与并发编程(七)GIL、协程

GIL、协程 文章目录 GIL、协程一、GIL二、协程 一、GIL GIL本质就是CPython解释器中的一把互斥锁。那既然是互斥锁&#xff0c;原理都一样&#xff0c;都是让多个并发线程同一时间只能有一个执行&#xff0c;以此确保共享数据的安全性。有了GIL的存在&#xff0c;同一进程内的…