NNDL 循环神经网络-梯度爆炸实验 [HBU]

目录

6.2.1 梯度打印函数

6.2.2 复现梯度爆炸现象

6.2.3 使用梯度截断解决梯度爆炸问题

【思考题】梯度截断解决梯度爆炸问题的原理是什么? 

总结


前言:

造成简单循环网络较难建模长程依赖问题的原因有两个:梯度爆炸和梯度消失

循环网络的梯度爆炸问题比较容易解决,一般通过权重衰减或梯度截断可以较好地来避免;

梯度消失问题,更加有效的方式是改变模型,比如通过长短期记忆网络LSTM来进行缓解。

本节将首先进行复现简单循环网络中的梯度爆炸问题,然后尝试使用梯度截断的方式进行解决。这里采用长度为20的数据集进行实验,训练过程中将进行输出W,U,b的梯度向量的范数,以此来衡量梯度的变化情况。


6.2.1 梯度打印函数

在训练过程中打印梯度

分别定义W_list, U_list和b_list,用于分别存储训练过程中参数W,U和b的梯度范数

流程图和代码为:

W_list = []
U_list = []
b_list = []
# 计算梯度范数
def custom_print_log(runner):model = runner.modelW_grad_l2, U_grad_l2, b_grad_l2 = 0, 0, 0for name, param in model.named_parameters():if name == "rnn_model.W":W_grad_l2 = torch.norm(param.grad, p=2).numpy()if name == "rnn_model.U":U_grad_l2 = torch.norm(param.grad, p=2).numpy()if name == "rnn_model.b":b_grad_l2 = torch.norm(param.grad, p=2).numpy()print(f"[Training] W_grad_l2: {W_grad_l2:.5f}, U_grad_l2: {U_grad_l2:.5f}, b_grad_l2: {b_grad_l2:.5f} ")W_list.append(W_grad_l2)U_list.append(U_grad_l2)b_list.append(b_grad_l2)

6.2.2 复现梯度爆炸现象

为了更好地复现梯度爆炸问题,使用SGD优化器将批大小和学习率调大,学习率为0.2,同时在计算交叉熵损失时,将reduction设置为sum,表示将损失进行累加。 流程及代码实现如下:

import os
import random
import torch
import numpy as npnp.random.seed(0)
random.seed(0)
torch.seed()# 训练轮次
num_epochs = 50
# 学习率
lr = 0.2
# 输入数字的类别数
num_digits = 10
# 将数字映射为向量的维度
input_size = 32
# 隐状态向量的维度
hidden_size = 32
# 预测数字的类别数
num_classes = 19
# 批大小 
batch_size = 64
# 模型保存目录
save_dir = "./checkpoints"# 可以设置不同的length进行不同长度数据的预测实验
length = 20
print(f"\n====> Training SRN with data of length {length}.")# 加载长度为length的数据
data_path = f"./datasets/{length}"
train_examples, dev_examples, test_examples = load_data(data_path)
train_set, dev_set, test_set = DigitSumDataset(train_examples), DigitSumDataset(dev_examples),DigitSumDataset(test_examples)
train_loader = DataLoader(train_set, batch_size=batch_size)
dev_loader = DataLoader(dev_set, batch_size=batch_size)
test_loader = DataLoader(test_set, batch_size=batch_size)
# 实例化模型
base_model = SRN(input_size, hidden_size)
model = Model_RNN4SeqClass(base_model, num_digits, input_size, hidden_size, num_classes) 
# 指定优化器
optimizer = torch.optim.SGD(lr=lr, params=model.parameters())
# 定义评价指标
metric = Accuracy()
# 定义损失函数
loss_fn = nn.CrossEntropyLoss(reduction="sum")# 基于以上组件,实例化Runner
runner = RunnerV3(model, optimizer, loss_fn, metric)# 进行模型训练
model_save_path = os.path.join(save_dir, f"srn_explosion_model_{length}.pdparams")
runner.train(train_loader, dev_loader, num_epochs=num_epochs, eval_steps=100, log_steps=1, save_path=model_save_path, custom_print_log=custom_print_log)

这段代码里还有很多上一个实验中用到的类和函数,这里进行了代码复用,完整代码如下:

#完整代码
#NNDL 梯度爆炸
W_list = []
U_list = []
b_list = []
# 计算梯度范数
def custom_print_log(runner):model = runner.modelW_grad_l2, U_grad_l2, b_grad_l2 = 0, 0, 0for name, param in model.named_parameters():if name == "rnn_model.W":W_grad_l2 = torch.norm(param.grad, p=2).numpy()if name == "rnn_model.U":U_grad_l2 = torch.norm(param.grad, p=2).numpy()if name == "rnn_model.b":b_grad_l2 = torch.norm(param.grad, p=2).numpy()print(f"[Training] W_grad_l2: {W_grad_l2:.5f}, U_grad_l2: {U_grad_l2:.5f}, b_grad_l2: {b_grad_l2:.5f} ")W_list.append(W_grad_l2)U_list.append(U_grad_l2)b_list.append(b_grad_l2)import torch
#RUnnerV3
class RunnerV3(object):def __init__(self, model, optimizer, loss_fn, metric, **kwargs):self.model = modelself.optimizer = optimizerself.loss_fn = loss_fnself.metric = metric  # 只用于计算评价指标# 记录训练过程中的评价指标变化情况self.dev_scores = []# 记录训练过程中的损失函数变化情况self.train_epoch_losses = []  # 一个epoch记录一次lossself.train_step_losses = []  # 一个step记录一次lossself.dev_losses = []# 记录全局最优指标self.best_score = 0def train(self, train_loader, dev_loader=None, **kwargs):# 将模型切换为训练模式self.model.train()# 传入训练轮数,如果没有传入值则默认为0num_epochs = kwargs.get("num_epochs", 0)# 传入log打印频率,如果没有传入值则默认为100log_steps = kwargs.get("log_steps", 100)# 评价频率eval_steps = kwargs.get("eval_steps", 0)# 传入模型保存路径,如果没有传入值则默认为"best_model.pdparams"save_path = kwargs.get("save_path", "best_model.pdparams")custom_print_log = kwargs.get("custom_print_log", None)# 训练总的步数num_training_steps = num_epochs * len(train_loader)if eval_steps:if self.metric is None:raise RuntimeError('Error: Metric can not be None!')if dev_loader is None:raise RuntimeError('Error: dev_loader can not be None!')# 运行的step数目global_step = 0# 进行num_epochs轮训练for epoch in range(num_epochs):# 用于统计训练集的损失total_loss = 0for step, data in enumerate(train_loader):X, y = data# 获取模型预测logits = self.model(X)loss = self.loss_fn(logits, y.long())  # 默认求meantotal_loss += loss# 训练过程中,每个step的loss进行保存self.train_step_losses.append((global_step, loss.item()))if log_steps and global_step % log_steps == 0:print(f"[Train] epoch: {epoch}/{num_epochs}, step: {global_step}/{num_training_steps}, loss: {loss.item():.5f}")# 梯度反向传播,计算每个参数的梯度值loss.backward()if custom_print_log:custom_print_log(self)# 小批量梯度下降进行参数更新self.optimizer.step()# 梯度归零self.optimizer.zero_grad()# 判断是否需要评价if eval_steps > 0 and global_step > 0 and \(global_step % eval_steps == 0 or global_step == (num_training_steps - 1)):dev_score, dev_loss = self.evaluate(dev_loader, global_step=global_step)print(f"[Evaluate]  dev score: {dev_score:.5f}, dev loss: {dev_loss:.5f}")# 将模型切换为训练模式self.model.train()# 如果当前指标为最优指标,保存该模型if dev_score > self.best_score:self.save_model(save_path)print(f"[Evaluate] best accuracy performence has been updated: {self.best_score:.5f} --> {dev_score:.5f}")self.best_score = dev_scoreglobal_step += 1# 当前epoch 训练loss累计值trn_loss = (total_loss / len(train_loader)).item()# epoch粒度的训练loss保存self.train_epoch_losses.append(trn_loss)print("[Train] Training done!")# 模型评估阶段,使用'torch.no_grad()'控制不计算和存储梯度@torch.no_grad()def evaluate(self, dev_loader, **kwargs):assert self.metric is not None# 将模型设置为评估模式self.model.eval()global_step = kwargs.get("global_step", -1)# 用于统计训练集的损失total_loss = 0# 重置评价self.metric.reset()# 遍历验证集每个批次for batch_id, data in enumerate(dev_loader):X, y = data# 计算模型输出logits = self.model(X)# 计算损失函数loss = self.loss_fn(logits, y.long()).item()# 累积损失total_loss += loss# 累积评价self.metric.update(logits, y)dev_loss = (total_loss / len(dev_loader))dev_score = self.metric.accumulate()# 记录验证集lossif global_step != -1:self.dev_losses.append((global_step, dev_loss))self.dev_scores.append(dev_score)return dev_score, dev_loss# 模型评估阶段,使用'torch.no_grad()'控制不计算和存储梯度@torch.no_grad()def predict(self, x, **kwargs):# 将模型设置为评估模式self.model.eval()# 运行模型前向计算,得到预测值logits = self.model(x)return logitsdef save_model(self, save_path):torch.save(self.model.state_dict(), save_path)def load_model(self, model_path):state_dict = torch.load(model_path)self.model.load_state_dict(state_dict)#Accuracy
class Accuracy():def __init__(self, is_logist=True):# 用于统计正确的样本个数self.num_correct = 0# 用于统计样本的总数self.num_count = 0self.is_logist = is_logistdef update(self, outputs, labels):# 判断是二分类任务还是多分类任务,shape[1]=1时为二分类任务,shape[1]>1时为多分类任务if outputs.shape[1] == 1:  # 二分类outputs = torch.squeeze(outputs, dim=-1)if self.is_logist:# logist判断是否大于0preds = torch.tensor((outputs >= 0), dtype=torch.float32)else:# 如果不是logist,判断每个概率值是否大于0.5,当大于0.5时,类别为1,否则类别为0preds = torch.tensor((outputs >= 0.5), dtype=torch.float32)else:# 多分类时,使用'torch.argmax'计算最大元素索引作为类别preds = torch.argmax(outputs, dim=1)# 获取本批数据中预测正确的样本个数labels = torch.squeeze(labels, dim=-1)batch_correct = torch.sum(torch.tensor(preds == labels, dtype=torch.float32)).cpu().numpy()batch_count = len(labels)# 更新num_correct 和 num_countself.num_correct += batch_correctself.num_count += batch_countdef accumulate(self):# 使用累计的数据,计算总的指标if self.num_count == 0:return 0return self.num_correct / self.num_countdef reset(self):# 重置正确的数目和总数self.num_correct = 0self.num_count = 0def name(self):return "Accuracy"#加载数据集并划分
import os
import torch.nn as nn# 加载数据
def load_data(data_path):# 加载训练集train_examples = []train_path = os.path.join(data_path, "train.txt")with open(train_path, "r", encoding="utf-8") as f:for line in f.readlines():# 解析一行数据,将其处理为数字序列seq和标签labelitems = line.strip().split("\t")seq = [int(i) for i in items[0].split(" ")]label = int(items[1])train_examples.append((seq, label))# 加载验证集dev_examples = []dev_path = os.path.join(data_path, "dev.txt")with open(dev_path, "r", encoding="utf-8") as f:for line in f.readlines():# 解析一行数据,将其处理为数字序列seq和标签labelitems = line.strip().split("\t")seq = [int(i) for i in items[0].split(" ")]label = int(items[1])dev_examples.append((seq, label))# 加载测试集test_examples = []test_path = os.path.join(data_path, "test.txt")with open(test_path, "r", encoding="utf-8") as f:for line in f.readlines():# 解析一行数据,将其处理为数字序列seq和标签labelitems = line.strip().split("\t")seq = [int(i) for i in items[0].split(" ")]label = int(items[1])test_examples.append((seq, label))return train_examples, dev_examples, test_examples# 设定加载的数据集的长度
length = 5
# 该长度的数据集的存放目录
data_path = f"./datasets/{length}"
# 加载该数据集
train_examples, dev_examples, test_examples = load_data(data_path)
print("dev example:", dev_examples[:2])
print("训练集数量:", len(train_examples))
print("验证集数量:", len(dev_examples))
print("测试集数量:", len(test_examples))from torch.utils.data import Dataset, DataLoader
import torchclass DigitSumDataset(Dataset):def __init__(self, data):self.data = datadef __getitem__(self, idx):example = self.data[idx]seq = torch.tensor(example[0], dtype=torch.int64)label = torch.tensor(example[1], dtype=torch.int64)return seq, labeldef __len__(self):return len(self.data)class Embedding(nn.Module):def __init__(self, num_embeddings, embedding_dim):super(Embedding, self).__init__()self.W = nn.init.xavier_uniform_(torch.empty(num_embeddings, embedding_dim), gain=1.0)def forward(self, inputs):# 根据索引获取对应词向量embs = self.W[inputs]return embsemb_layer = Embedding(10, 5)
inputs = torch.tensor([0, 1, 2, 3])
emb_layer(inputs)#SRN
import torch.nn.functional as Ftorch.manual_seed(0)# SRN模型
class SRN(nn.Module):def __init__(self, input_size, hidden_size, W_attr=None, U_attr=None, b_attr=None):super(SRN, self).__init__()# 嵌入向量的维度self.input_size = input_size# 隐状态的维度self.hidden_size = hidden_size# 定义模型参数W,其shape为 input_size x hidden_sizeif W_attr == None:W = torch.zeros(size=[input_size, hidden_size], dtype=torch.float32)else:W = torch.tensor(W_attr, dtype=torch.float32)self.W = torch.nn.Parameter(W)# 定义模型参数U,其shape为hidden_size x hidden_sizeif U_attr == None:U = torch.zeros(size=[hidden_size, hidden_size], dtype=torch.float32)else:U = torch.tensor(U_attr, dtype=torch.float32)self.U = torch.nn.Parameter(U)# 定义模型参数b,其shape为 1 x hidden_sizeif b_attr == None:b = torch.zeros(size=[1, hidden_size], dtype=torch.float32)else:b = torch.tensor(b_attr, dtype=torch.float32)self.b = torch.nn.Parameter(b)# 初始化向量def init_state(self, batch_size):hidden_state = torch.zeros(size=[batch_size, self.hidden_size], dtype=torch.float32)return hidden_state# 定义前向计算def forward(self, inputs, hidden_state=None):# inputs: 输入数据, 其shape为batch_size x seq_len x input_sizebatch_size, seq_len, input_size = inputs.shape# 初始化起始状态的隐向量, 其shape为 batch_size x hidden_sizeif hidden_state is None:hidden_state = self.init_state(batch_size)# 循环执行RNN计算for step in range(seq_len):# 获取当前时刻的输入数据step_input, 其shape为 batch_size x input_sizestep_input = inputs[:, step, :]# 获取当前时刻的隐状态向量hidden_state, 其shape为 batch_size x hidden_sizehidden_state = F.tanh(torch.matmul(step_input, self.W) + torch.matmul(hidden_state, self.U) + self.b)return hidden_state# 基于RNN实现数字预测的模型
class Model_RNN4SeqClass(nn.Module):def __init__(self, model, num_digits, input_size, hidden_size, num_classes):super(Model_RNN4SeqClass, self).__init__()# 传入实例化的RNN层,例如SRNself.rnn_model = model# 词典大小self.num_digits = num_digits# 嵌入向量的维度self.input_size = input_size# 定义Embedding层self.embedding = Embedding(num_digits, input_size)# 定义线性层self.linear = nn.Linear(hidden_size, num_classes)def forward(self, inputs):# 将数字序列映射为相应向量inputs_emb = self.embedding(inputs)# 调用RNN模型hidden_state = self.rnn_model(inputs_emb)# 使用最后一个时刻的状态进行数字预测logits = self.linear(hidden_state)return logits# 实例化一个input_size为4, hidden_size为5的SRN
srn = SRN(4, 5)
# 基于srn实例化一个数字预测模型实例
model = Model_RNN4SeqClass(srn, 10, 4, 5, 19)
# 生成一个shape为 2 x 3 的批次数据
inputs = torch.tensor([[1, 2, 3], [2, 3, 4]])
# 进行模型前向预测
logits = model(inputs)
print(logits)#复现梯度爆炸
import os
import random
import torch
import numpy as npnp.random.seed(0)
random.seed(0)
torch.seed()# 训练轮次
num_epochs = 50
# 学习率
lr = 0.2
# 输入数字的类别数
num_digits = 10
# 将数字映射为向量的维度
input_size = 32
# 隐状态向量的维度
hidden_size = 32
# 预测数字的类别数
num_classes = 19
# 批大小
batch_size = 64
# 模型保存目录
save_dir = "./checkpoints"# 可以设置不同的length进行不同长度数据的预测实验
length = 20
print(f"\n====> Training SRN with data of length {length}.")# 加载长度为length的数据
data_path = f"./datasets/{length}"
train_examples, dev_examples, test_examples = load_data(data_path)
train_set, dev_set, test_set = DigitSumDataset(train_examples), DigitSumDataset(dev_examples),DigitSumDataset(test_examples)
train_loader = DataLoader(train_set, batch_size=batch_size)
dev_loader = DataLoader(dev_set, batch_size=batch_size)
test_loader = DataLoader(test_set, batch_size=batch_size)
# 实例化模型
base_model = SRN(input_size, hidden_size)
model = Model_RNN4SeqClass(base_model, num_digits, input_size, hidden_size, num_classes)
# 指定优化器
optimizer = torch.optim.SGD(lr=lr, params=model.parameters())
# 定义评价指标
metric = Accuracy()
# 定义损失函数
loss_fn = nn.CrossEntropyLoss(reduction="sum")# 基于以上组件,实例化Runner
runner = RunnerV3(model, optimizer, loss_fn, metric)# 进行模型训练
model_save_path = os.path.join(save_dir, f"srn_explosion_model_{length}.pdparams")
runner.train(train_loader, dev_loader, num_epochs=num_epochs, eval_steps=100, log_steps=1,save_path=model_save_path, custom_print_log=custom_print_log)

运行结果(截取一段):
[Training] W_grad_l2: 0.00003, U_grad_l2: 0.00013, b_grad_l2: 0.00002 
[Train] epoch: 47/50, step: 238/250, loss: 14879.33398
[Training] W_grad_l2: 0.00000, U_grad_l2: 0.00000, b_grad_l2: 0.00000 
[Train] epoch: 47/50, step: 239/250, loss: 7440.75293
[Training] W_grad_l2: 0.00001, U_grad_l2: 0.00006, b_grad_l2: 0.00001 
[Train] epoch: 48/50, step: 240/250, loss: 14501.10547
[Training] W_grad_l2: 0.00253, U_grad_l2: 0.01115, b_grad_l2: 0.00197 
[Train] epoch: 48/50, step: 241/250, loss: 10598.82715
[Training] W_grad_l2: 0.00000, U_grad_l2: 0.00000, b_grad_l2: 0.00000 
[Train] epoch: 48/50, step: 242/250, loss: 12267.08594
[Training] W_grad_l2: 0.00002, U_grad_l2: 0.00010, b_grad_l2: 0.00002 
[Train] epoch: 48/50, step: 243/250, loss: 11133.79102
[Training] W_grad_l2: 0.00000, U_grad_l2: 0.00000, b_grad_l2: 0.00000 
[Train] epoch: 48/50, step: 244/250, loss: 5316.20361
[Training] W_grad_l2: 0.00002, U_grad_l2: 0.00010, b_grad_l2: 0.00002 
[Train] epoch: 49/50, step: 245/250, loss: 13425.17871
[Training] W_grad_l2: 0.00412, U_grad_l2: 0.01818, b_grad_l2: 0.00321 
[Train] epoch: 49/50, step: 246/250, loss: 10136.34961
[Training] W_grad_l2: 0.00000, U_grad_l2: 0.00000, b_grad_l2: 0.00000 
[Train] epoch: 49/50, step: 247/250, loss: 14019.18262
[Training] W_grad_l2: 0.00001, U_grad_l2: 0.00002, b_grad_l2: 0.00000 
[Train] epoch: 49/50, step: 248/250, loss: 10505.15234
[Training] W_grad_l2: 0.00000, U_grad_l2: 0.00000, b_grad_l2: 0.00000 
[Train] epoch: 49/50, step: 249/250, loss: 6560.39893
[Training] W_grad_l2: 0.00002, U_grad_l2: 0.00008, b_grad_l2: 0.00001 
[Evaluate]  dev score: 0.02000, dev loss: 9217.20581
[Train] Training done!

获取训练过程中关于W,U和b参数梯度的L2范数,并将其绘制为图片以便展示:

代码:

#可视化
import matplotlib.pyplot as plt
def plot_grad(W_list, U_list, b_list, save_path, keep_steps=40):# 开始绘制图片plt.figure()# 默认保留前40步的结果steps = list(range(keep_steps))plt.plot(steps, W_list[:keep_steps],'*', color="b", label="W_grad_l2")plt.plot(steps, U_list[:keep_steps], "-.", color="pink", label="U_grad_l2")plt.plot(steps, b_list[:keep_steps], "--", color="y", label="b_grad_l2")plt.xlabel("step")plt.ylabel("L2 Norm")plt.legend(loc="upper right")plt.show()plt.savefig(save_path)print("image has been saved to: ", save_path)save_path = f"./images/6.8.pdf"
plot_grad(W_list, U_list, b_list, save_path)

结果为:

U和b参数梯度的L2范数,可以看到经过学习率等方式的调整,梯度范数急剧变大,而后梯度范数几乎为0.
这是因为Tanh为Sigmoid型函数,其饱和区的导数接近于0,由于梯度的急剧变化,参数数值变的较大或较小,容易落入梯度饱和区,导致梯度为0,模型很难继续训练.

 接下来,使用该模型在测试集上进行测试:

准确率只有9%,这个性能太差了,应该是模型随机蒙出来的效果。


6.2.3 使用梯度截断解决梯度爆炸问题

梯度截断是一种可以有效解决梯度爆炸问题的启发式方法,当梯度的模大于一定阈值时,就将它截断成为一个较小的数。一般有两种截断方式:按值截断和按模截断.本实验使用按模截断的方式解决梯度爆炸问题。

在RunnerV3中加一行代码进行按模截断

nn.utils.clip_grad_norm_(parameters=self.model.parameters(), max_norm=20, norm_type=2)

在引入梯度截断之后,将重新观察模型的训练情况。这里我们重新实例化一下:模型和优化器,然后组装runner,进行训练。代码实现如下:

# 清空梯度列表
W_list.clear()
U_list.clear()
b_list.clear()
# 实例化模型
base_model = SRN(input_size, hidden_size)
model = Model_RNN4SeqClass(base_model, num_digits, input_size, hidden_size, num_classes)# 定义clip,并实例化优化器optimizer = torch.optim.SGD(lr=lr, params=model.parameters())
# 定义评价指标
metric = Accuracy()
# 定义损失函数
loss_fn = nn.CrossEntropyLoss(reduction="sum")# 实例化Runner
runner = RunnerV3(model, optimizer, loss_fn, metric)# 训练模型
model_save_path = os.path.join(save_dir, f"srn_fix_explosion_model_{length}.pdparams")
runner.train(train_loader, dev_loader, num_epochs=num_epochs, eval_steps=100, log_steps=1, save_path=model_save_path, custom_print_log=custom_print_log)

可视化结果:

引入按模截断的策略之后,模型训练时参数梯度的变化情况。可以看到,随着迭代步骤的进行,梯度始终保持在一个有值的状态,表明按模截断能够很好地解决梯度爆炸的问题.

接下来,使用梯度截断策略的模型在测试集上进行测试:

print(f"Evaluate SRN with data length {length}.")# 加载训练过程中效果最好的模型
model_path = os.path.join(save_dir, "srn_explosion_model_20.pdparams")
runner.load_model(model_path)# 使用测试集评价模型,获取测试集上的预测准确率
score, _ = runner.evaluate(test_loader)
print(f"[SRN] length:{length}, Score: {score: .5f}")

结果为:

由于为复现梯度爆炸现象,改变了学习率,优化器等,因此准确率相对比较低。但由于采用梯度截断策略后,在后续训练过程中,模型参数能够被更新优化,因此准确率有一定的提升。


【思考题】梯度截断解决梯度爆炸问题的原理是什么? 

参考文章:解决 “梯度爆炸” 的方法 - 梯度裁剪_loss 梯度裁剪-CSDN博客

【调参19】如何使用梯度裁剪(Gradient Clipping)避免梯度爆炸-CSDN博客

在给定的神经网络(例如卷积神经网络或多层感知器)中,可能由于配置选择不当而发生梯度爆炸:

学习率选择不当会导致较大的权重更新。
准备的数据有很多噪声,导致目标变量差异很大。
损失函数选择不当,导致计算出较大的误差值。
在递归神经网络(例如长短期记忆网络)中容易出现梯度爆炸。通常,可以通过精心配置网络模型来避免爆炸梯度,例如,选择较小的学习速率,按比例缩放目标变量和标准损失函数。尽管如此,对于具有大量输入时间步长的递归网络,梯度爆炸仍然是一个需要着重考虑的问题。

梯度爆炸的一种常见解决方法是先更改误差导数,然后通过网络反向传播误差导数,然后使用它来更新权重。通过重新缩放误差导数,权重的更新也将被重新缩放,从而大大降低了上溢或下溢的可能性。更新误差导数的主要方法有两种:

>梯度缩放(Gradient Scaling)
>梯度裁剪(Gradient Clipping)

梯度截断解决梯度爆炸问题的原理是在计算梯度的过程中,当梯度超过一定阈值或范围时,梯度截断会将超过部分的梯度值重置为设定范围内的值。例如,设定阈值为100,当某个梯度超过100时,就将其值重置为1到10之间的一个数。这种方式能有效防止梯度变得过大,从而使模型保持稳定。

        在模型训练过程中,如果梯度变得过大,可能会导致模型不稳定,阻止神经网络参数的更新,进而无法从训练数据中得到稳定的模型。此时,梯度截断通过设置一个梯度剪切阈值,在更新梯度时,将超出阈值的梯度强制限制在设定范围内。这种方式有助于让走向“邪路”的梯度回归正轨,避免因梯度爆炸引起的网络不稳定问题。

如下图所示:

①按值截断

按值截断是比较简单粗暴的方法,由于梯度太大会产生梯度爆炸的现象,太小会产生梯度消失的现象(参数不更新),所以为梯度提供一个范围[a,b],

  • 如果梯度大于b,就把它设置为b;
  • 如果梯度小于a,就把它设置为a;
  • 若在此区间,不做变化

②按模截断

为梯度g设置一个最大阈值threshold,用梯度的二范数与该阈值做比较;

  • 若大于阈值,则对其进行压缩,计算公式如图所示
  • 否则,不改变梯度

总结

这次的实验步骤仍然和以前的步骤一样,很多的代码都是可以复用的。对照着实验教材来做也是十分顺利的,训练速度很快(对比CIFAR图像实验的训练效率高很多很多),现在我有所疑惑的就是 L2范数 是什么?它的数学概念以及在深度学习领域中起到的作用?

一文搞懂深度学习正则化的L2范数-CSDN博客

欧几里得范数(L2范数)-CSDN博客

             

>L2范数(欧几里得范数)

首先明确一点,常用到的几个概念,含义相同。

欧几里得范数(Euclidean norm) == 欧式长度(距离) = L2 范数 == L2距离

十分简单的数学公式,L2范数的公式可以认为是两点之间的距离,当然不限于二维。

在深度学习里,L1、L2范数正则化常常用来解决模型过拟合问题。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网络基础(八):路由器的基本原理及配置

目录 1、路由概述 2、路由器 2.1路由器的工作原理 2.2路由器的转发原理 3、路由表 3.1路由表的概述 3.2路由表的形成 4、静态路由配置过程(使用eNSP软件配置) 4.1两个静态路由器配置过程 4.2三个静态路由器配置过程 5、默认路由配置过程 5.…

边缘计算系统设计与实践

随着科技的飞速发展,物联网和人工智能两大领域的不断突破,我们看到了一种新型的计算模型——边缘计算的崛起。这种计算模型在处理大规模数据、实现实时响应和降低延迟需求方面,展现出了巨大的潜力。本文将深入探讨边缘计算系统的设计原理和实…

黑马头条--day01.环境搭建

目录 一.前言 二.环境搭建 1.数据库 2.虚拟机搭建 3.1docker更换源 3.docker安装nacos 4.初始化工程 三.全局异常处理 四.登录加密 五.nacos公共配置数据源和mybatis-plus 六.user模块创建 1.配置文件bootstrap.yml 2.日志文件配置logback.xml 3.登录接口 七.统一结果处…

【INTEL(ALTERA)】 quartus SignalTap 逻辑分析器 – Nios® II 插件 无法检测 Nios® II/f 处理器内核

说明 使用 Nios II 插件将 Nios II/f 处理器内核节点添加到 SignalTap 逻辑分析器时,在 英特尔 Quartus Prime Pro Edition 软件 23.3 版中可能会出现此问题。 错误消息: 无法完成“添加带插件的节点”命令,因为在当前设计中找不到所选 IP。…

ubuntu 自动安装 MKL Intel fortran 编译器 ifort 及完美平替

首先据不完全观察,gfortran 与 openblas是 intel fortran 编译器 ifotr和mkl的非常优秀的平替,openblas连函数名都跟mkl一样,加了一个下划线。 1, 概况 https://www.intel.com/content/www/us/en/developer/tools/oneapi/base-too…

dockerfite创建镜像---INMP+wordpress

搭建dockerfile---lnmp 在192.168.10.201 使用 Docker 构建 LNMP 环境并运行 Wordpress 网站平台 [rootdocker1 opt]# mkdir nginx mysql php [rootdocker1 opt]# ls #分别拖入四个包: nginx-1.22.0.tar.gz mysql-boost-5.7.20.tar.gz php-7.1.10.tar.bz2 wor…

python:import 自定义包或者.py文件时出现:ModuleNotFoundError: no module named 的问题解决

问题: 在以下的示例中,wuHanMoviesSprider.py文件,想要import引用指定目录下的Items类时,出现无法识别module模块的问题(from 的引用处报错)。 原因分析: 正常情况下,被引用的包(或目录)中存在一个空文件…

golang反射(reflect)虽爽,但很贵

标准库 reflect 为 Go 语言提供了运行时动态获取对象的类型和值以及动态创建对象的能力。反射可以帮助抽象和简化代码,提高开发效率。 但是使用反射势必会多出大量的操作指令,导致性能下降 案例 字段赋值方式对比 type Student struct {Name string…

QML 自定义进度条组件开发

一、效果预览 二、介绍: 自定义的QML 屏幕亮度拖动进度条组件CusProgressBar 可跟鼠标移动 更改进度条样式 三、代码 import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Controls.Material 2.12/***author:Zwj*csdn:来份煎蛋吧*date:2023/12/16*…

爬虫工作量由小到大的思维转变---<第十一章 Scrapy之sqlalchemy模版和改造(番外)>

前言: 正常的pymysql当然问题不大,但是我个人还是建议:sqlalchemy! 因为他更能让我们把精力放在表单设计上,而不执着于代码本身了. (-----版权所有。未经作者书面同意,不得转载或用于任何商业用途!----) 正文: 先提供一个基础模版: 表图: 创建表的sql: CREA…

2023-12-14 二叉树的最大深度和二叉树的最小深度以及完全二叉树的节点个数

二叉树的最大深度和二叉树的最小深度以及完全二叉树的节点个数 104. 二叉树的最大深度 思想:可以使用迭代法或者递归!使用递归更好,帮助理解递归思路!明确递归三部曲–①确定参数以及返回参数 ②递归结束条件 ③单层逻辑是怎么样…

C#中的封装、继承和多态

1.引言 在面向对象的编程中,封装、继承和多态是三个重要的概念。它们是C#语言中的基本特性,用于设计和实现具有高内聚和低耦合的代码。本文将详细介绍C#中的封装、继承和多态的相关知识。 目录 1.引言2. 封装2.1 类2.2 访问修饰符 3. 继承4. 多态4.1 虚方…

中文星期几十二时辰

输入年月日输出中文星期败,输入时间字符串,输出十二时辰。 (笔记模板由python脚本于2023年12月16日 23:39:04创建,本篇笔记适合熟悉python字符串类型str,并可以熟练应用的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&…

毅速:3D打印随形水路 提高良品率和生产效率的新利器

随着科技的不断发展,3D打印技术已经成为模具制造领域的一种重要技术。其中,模具随形水路的设计和制造是提高注塑产品良品率和生产效率的关键环节。 模具随形水路是一种根据产品形状设计的水路,可以更靠近产品,并在模具内热点集中区…

数据库系列之简要对比下GaussDB和OpenGauss数据库

GaussDB作为一款企业级的数据库产品,和开源数据库OpenGauss之间又是什么样的关系,刚开始接触的时候是一头雾水,因此本文简要对比下二者的区别,以加深了解。 1、GaussDB和OpenGauss数据库简要对比 GaussDB是华为基于PostgreSQL数据…

vue3 element-plus 日期选择器 el-date-picker 汉化

vue3 项目中,element-plus 的日期选择器 el-date-picker 默认是英文版的,如下: 页面引入: //引入汉化语言包 import locale from "element-plus/lib/locale/lang/zh-cn" import { ElDatePicker, ElButton, ElConfigP…

WPF-UI HandyControl 控件简单实战

文章目录 前言UserControl简单使用新建项目直接新建项目初始化UserControlGeometry:矢量图形额外Icon导入最优解决方案 按钮Button切换按钮ToggleButton默认按钮图片可切换按钮加载按钮切换按钮 单选按钮和复选按钮没有太大特点,就不展开写了总结 DataGrid数据表格G…

浅谈MapReduce

MapReduce是一个抽象的分布式计算模型,主要对键值对进行运算处理。用户需要提供两个自定义函数: map:用于接受输入,并生成中间键值对。reduce:接受map输出的中间键值对集合,进行sorting后进行合并和数据规…

〖大前端 - 基础入门三大核心之JS篇(55)〗- 内置对象

说明:该文属于 大前端全栈架构白宝书专栏,目前阶段免费,如需要项目实战或者是体系化资源,文末名片加V!作者:哈哥撩编程,十余年工作经验, 从事过全栈研发、产品经理等工作,目前在公司…

什么是前端国际化(internationalization)和本地化(localization)?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 欢迎来到前端入门之旅!感兴趣的可以订阅本专栏哦!这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…