【实验八】前馈神经网络(4)优化问题
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_sizeself.fc1 = nn.Linear(input_size, hidden_size)'''weight为权重参数属性,bias为偏置参数属性,这里使用'torch.nn.init.constant_'进行常量初始化'''# 初始化第一个线性层的权重和偏置为 0constant_(self.fc1.weight, 0.0)constant_(self.fc1.bias, 0.0)# 定义第二个线性层,输入特征数为 hidden_size,输出特征数为 output_sizeself.fc2 = nn.Linear(hidden_size, output_size)# 初始化第二个线性层的权重和偏置为 0constant_(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 和参数的内容 itemfor 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 pltclass 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_sizeself.fc1 = nn.Linear(input_size, hidden_size)'''weight为权重参数属性,bias为偏置参数属性,这里使用'torch.nn.init.constant_'进行常量初始化'''# 初始化第一个线性层的权重和偏置为 0constant_(self.fc1.weight, 0.0)constant_(self.fc1.bias, 0.0)# 定义第二个线性层,输入特征数为 hidden_size,输出特征数为 output_sizeself.fc2 = nn.Linear(hidden_size, output_size)# 初始化第二个线性层的权重和偏置为 0constant_(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 a2def print_weight(runner):print('The weights of the Layers:')# 通过 enumerate() 可以同时获取参数的索引 i 和参数的内容 itemfor 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.sigmoidelif act == 'relu':self.act = F.reluelif act == 'lrelu':self.act = F.leaky_reluelse: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_2class 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.sigmoidelif act == 'relu':self.act = F.reluelif act == 'lrelu':self.act = F.leaky_reluelse: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 outputsdef 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.sigmoidelif act == 'relu':self.act = F.reluelif act == 'lrelu':self.act = F.leaky_reluelse: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 matplotlibmatplotlib.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.sigmoidelif act == 'relu':self.act = F.reluelif act == 'lrelu':self.act = F.leaky_reluelse: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 outputsdef 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)
相关文章:

【实验八】前馈神经网络(4)优化问题
1 参数初始化 模型构建 模型训练 优化 完整代码 2 梯度消失问题 模型构建 模型训练 完整代码 3 死亡Relu问题 模型构建 模型训练 优化 完整代码 1 参数初始化 实现一个神经网络前,需要先初始化模型参数。如果对每一层的权重和偏置都用0初始化࿰…...

【深度学习】论文笔记:空间变换网络(Spatial Transformer Networks)
博主简介:努力学习的22级计算机科学与技术本科生一枚🌸博主主页: Yaoyao2024往期回顾: 【机器学习】有监督学习由浅入深讲解分类算法Fisher算法讲解每日一言🌼: 今天不想跑,所以才去跑,这才是长…...

Charles抓包_Android
1.下载地址 2.破解方法 3.安卓调试办法 查看官方文档,Android N之后抓包要声明App可用User目录下的CA证书 3.1.在Proxy下进行以下设置(路径Proxy->Proxy Settings) 3.1.1.不抓包Windows,即不勾选此项,免得打输出不…...

【MATLAB源码-第204期】基于matlab的语音降噪算法对比仿真,谱减法、维纳滤波法、自适应滤波法;参数可调。
操作环境: MATLAB 2022a 1、算法描述 语音降噪技术的目的是改善语音信号的质量,通过减少或消除背景噪声,使得语音更清晰,便于听者理解或进一步的语音处理任务,如语音识别和语音通讯。在许多实际应用中,如…...

Scala的包及其导入
//1.单个导入 //import com.sala02.A //import com.sala02.B //2.导入多个类 //import com.sala02.{A,B} //3.导入一个包下的所有类:包名._ //import com.sala02._ //4.导入一个包中的类,给他改个名字 //格式:import 包名.{原来的名字 > 新…...

deepfm模型实现招聘职位推荐算法
项目源码获取方式见文章末尾! 600多个深度学习项目资料,快来加入社群一起学习吧。 《------往期经典推荐------》 项目名称 1.【基于CNN-RNN的影像报告生成】 2.【卫星图像道路检测DeepLabV3Plus模型】 3.【GAN模型实现二次元头像生成】 4.【CNN模型实现…...

编程之路:蓝桥杯备赛指南
文章目录 一、蓝桥杯的起源与发展二、比赛的目的与意义三、比赛内容与形式四、比赛前的准备五、获奖与激励六、蓝桥杯的影响力七、蓝桥杯比赛注意事项详解使用Dev-C的注意事项 一、蓝桥杯的起源与发展 蓝桥杯全国软件和信息技术专业人才大赛,简称蓝桥杯,…...

Android 15 在状态栏时间中显示秒数
这是更新后的博客草稿,关于在Android 15状态栏中显示秒数的实现: 在Android 15状态栏中显示秒数 在Android 15中,您可以通过两种方式在状态栏中显示秒数:使用ADB命令或修改系统源代码。下面详细介绍这两种方法。 方法一:通过ADB实现 您可以使用ADB(Android调试桥)命令…...

Flutter 鸿蒙next版本:自定义对话框与表单验证的动态反馈与错误处理
在现代移动应用开发中,用户体验是至关重要的一环。Flutter和鸿蒙操作系统(HarmonyOS)的结合,为开发者提供了一个强大的平台,以创建跨平台、高性能的应用程序。本文将探讨如何在Flutter与鸿蒙next版本中创建自定义对话框…...

Unreal Engine5中使用 Lyra框架
UE5系列文章目录 文章目录 UE5系列文章目录前言一、Lyra和AIS框架的区别二、下载官方Lyra游戏示例三、Lyra在动画蓝图中的使用 前言 Unreal Engine 5(UE5)提供了多种用于游戏开发的模板和框架,其中Lyra和AlS是两个不同的示例项目,…...

Spring Security-02-Spring Security认证方式-HTTP基本认证、Form表单认证、HTTP摘要认证、前后端分离安全处理方案
Lison <dreamlison163.com>, v1.0.0, 2024.06.01 Spring Security-02-Spring Security认证方式-HTTP基本认证、Form表单认证、HTTP摘要认证、前后端分离安全处理方案 文章目录 Spring Security-02-Spring Security认证方式-HTTP基本认证、Form表单认证、HTTP摘要认证、…...

【scikit-learn 1.2版本后】sklearn.datasets中load_boston报错 使用 fetch_openml 函数来加载波士顿房价
ImportError: load_boston has been removed from scikit-learn since version 1.2. 由于 load_boston 已经在 scikit-learn 1.2 版本中被移除,需要使用 fetch_openml 函数来加载波士顿房价数据集。 # 导入sklearn数据集模块 from sklearn import datasets # 导入波…...

vxe-table v4.8+ 与 v3.10+ 导出 xlsx、支持导出合并、设置样式、宽高、边框、字体、背景、超链接、图片的详细介绍,一篇就够了
Vxe UI vue vxe-table v4.8 与 v3.10 导出 xlsx、支持导出合并、设置样式、宽高、边框、字体、背景、超链接、图片等、所有常用的 Excel 格式都能自定义,使用非常简单,纯前端实现复杂的导出。 安装插件 npm install vxe-pc-ui4.2.39 vxe-table4.8.0 vx…...

江协科技STM32学习- P36 SPI通信外设
🚀write in front🚀 🔎大家好,我是黄桃罐头,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流 🎁欢迎各位→点赞👍 收藏⭐️ 留言📝…...

【大数据】ClickHouse常见的表引擎及建表语法
ClickHouse 中最强大的表引擎当属 MergeTree (合并树)引擎及该系列(*MergeTree)中的其他引擎。接下来我们就仔细了解下MergeTree 及该系列的其他引擎的使用场景及建表语法。 MergeTree MergeTree 系列的引擎被设计用于插入极大量…...

explain执行计划分析 ref_
这里写目录标题 什么是ExplainExplain命令扩展explain extendedexplain partitions 两点重要提示本文示例使用的数据库表Explain命令(关键字)explain简单示例explain结果列说明【id列】【select_type列】【table列】【type列】 【possible_keys列】【key列】【key_len列】【ref…...

网络学习/复习4传输层
1,0...

Notepad++ 更改字体大小和颜色
前言 在长时间编程或文本编辑过程中,合适的字体大小和颜色可以显著提高工作效率和减少眼睛疲劳。Notepad 提供了丰富的自定义选项,让你可以根据个人喜好调整编辑器的外观。 步骤详解 1. 更改字体大小 打开 Notepad 启动 Notepad 编辑器。 进入设置菜…...

基于SSM+小程序的宿舍管理系统(宿舍1)
👉文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 本宿舍管理系统小程序有管理员和学生两个角色。 1、管理员功能有个人中心,公告信息管理,班级管理,学生管理,宿舍信息管理,宿舍…...

【案例分享】TeeChart 如何为人类绩效解决方案提供数据洞察
“过去二十年来,我们一直在使用 Steema Software 产品,尤其是 TeeChart,这是我们软件开发的基础部分。看到 TeeChart 在这段时间里不断发展、改进和增加功能,真是太棒了,这极大地增强了我们的产品。Steema 的客户和技术…...

细谈 Linux 中的多路复用epoll
大家好,我是 V 哥。在 Linux 中,epoll 是一种多路复用机制,用于高效地处理大量文件描述符(file descriptor, FD)事件。与传统的select和poll相比,epoll具有更高的性能和可扩展性,特别是在大规模…...

51c自动驾驶~合集4
我自己的原文哦~ https://blog.51cto.com/whaosoft/12413878 #MCTrack 迈驰&旷视最新MCTrack:KITTI/nuScenes/Waymo三榜单SOTA paper:MCTrack: A Unified 3D Multi-Object Tracking Framework for Autonomous Driving code:https://gi…...

回归预测 | MATLAB实现BO-BiGRU贝叶斯优化双向门控循环单元多输入单输出回归预测
要在MATLAB中实现BO-BiGRU(贝叶斯优化双向门控循环单元)进行多输入单输出回归预测,您需要执行以下步骤: 数据准备:准备您的训练数据和测试数据。 模型构建:构建BO-BiGRU模型,可以使用MATLAB中的…...

2-ARM Linux驱动开发-设备树平台驱动
一、概述 设备树(Device Tree)是一种描述硬件的数据结构,用于将硬件设备的信息传递给操作系统内核。它的主要作用是使内核能够以一种统一、灵活的方式了解硬件平台的细节,包括设备的拓扑结构、资源分配(如内存地址、中断号等)等信…...

C语言函数与递归
函数 函数是指将一组能完成一个功能或多个功能的语句放在一起的代码结构。在C语言程序中,至少会包含一个函数,主函数main()。本章将详细讲解关于函数的相关内容。 1、库函数 ⭕️C语言库函数是指在C语言标准库中预先定义好的函数,这些函数包…...

Linux下的Debugfs
debugfs 1. 简介 类似sysfs、procfs,debugfs 也是一种内存文件系统。不过不同于sysfs一个kobject对应一个文件,procfs和进程相关的特性,debugfs的灵活度很大,可以根据需求对指定的变量进行导出并提供读写接口。debugfs又是一个Li…...

【FFmpeg】调整音频文件的音量
1、调整音量的命令 1)音量调整为当前音量的十倍 ffmpeg -i inputfile -vol 1000 outputfile 2)音量调整为当前音量的一半 ffmpeg -i input.wav -filter:a "volume=0.5" output.wav3)静音 ffmpeg -i input.wav -filter:a "volume=0" output.wav4)…...

mac 打开访达快捷键
一、使用快捷键组合 1. Command N 在当前桌面或应用程序窗口中,按下“Command N”组合键可以快速打开一个新的访达窗口。这就像在 Windows 系统中通过“Ctrl N”打开新的资源管理器窗口一样。 2. Command Tab 切换 如果访达已经打开,只是被其他应…...

Ubuntu学习笔记 - Day2
文章目录 学习目标:学习内容:学习笔记:Linux系统启动过程内核引导运行init运行级别系统初始化建立终端用户登录系统 Ubuntu关机关机流程相关命令 Linux系统目录结构查看目录目录结构 文件基本属性读写权限命令 下载文件的方法安装wget工具下载…...

c++基础12比较/逻辑运算符
比较/逻辑运算符 布尔比较运算符逻辑运算符位运算符(也用于逻辑运算)1<a<10怎么表达T140399判断是否为两位数代码 布尔 在C中,布尔类型是一种基本数据类型,用于表示逻辑值,即真(true)或假…...