神经网络与深度学习实验day14-循环神经网络(2)梯度爆炸实验


6.2 梯度爆炸实验

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

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

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


本节将首先进行复现简单循环网络中的梯度爆炸问题,然后尝试使用梯度截断的方式进行解决。

采用长度为20的数据集进行实验,

训练过程中将进行输出W,U,b的梯度向量的范数,以此来衡量梯度的变化情况。

6.2.1 定义梯度打印函数

使用custom_print_log实现了在训练过程中打印梯度的功能,custom_print_log需要接收runner的实例,并通过model.named_parameters()获取该模型中的参数名和参数值. 这里我们分别定义W_list, U_list和b_list,用于分别存储训练过程中参数W,U和b的梯度范数。
代码如下:

import torch
import os
import random
import torch
import numpy as np
from torch.utils.data import DataLoader
W_list = []
U_list = []
b_list = []
# 计算梯度范数
def custom_print_log(runner):
    model = runner.model
    W_grad_l2, U_grad_l2, b_grad_l2 = 0, 0, 0
    for 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,表示将损失进行累加。

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

因为Tanh为Sigmoid型函数,其饱和区的导数接近于0,

由于梯度的急剧变化,参数数值变的较大或较小,容易落入梯度饱和区,导致梯度为0,

模型很难继续训练.

np.random.seed(0)
random.seed(0)
torch.manual_seed(0)

# 训练轮次
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"D:/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(model.parameters(),lr)
# 定义评价指标
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)

训练结果:
在这里插入图片描述
接下来,可以获取训练过程中关于W,U和b参数梯度的L2范数,并将其绘制为图片以便展示,相应代码如下:

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], "r-", color="#e4007f", label="W_grad_l2")
    plt.plot(steps, U_list[:keep_steps], "-.", color="#f19ec2", label="U_grad_l2")
    plt.plot(steps, b_list[:keep_steps], "--", color="#000000", label="b_grad_l2")

    plt.xlabel("step")
    plt.ylabel("L2 Norm")
    plt.legend(loc="upper right")
    plt.savefig(save_path)
    print("image has been saved to: ", save_path)


save_path = f"D:/datasets/images/6.8.pdf"
plot_grad(W_list, U_list, b_list, save_path)

结果如下:
在这里插入图片描述
接下来,使用该模型在测试集上进行测试。模型的评价代码:

print(f"Evaluate SRN with data length {length}.")
# 加载训练过程中效果最好的模型
model_path = os.path.join(save_dir, f"srn_explosion_model_{length}.pdparams")
runner.load_model(model_path)

# 使用测试集评价模型,获取测试集上的预测准确率
score, _ = runner.evaluate(test_loader)
print(f"[SRN] length:{length}, Score: {score: .5f}")

结果如下:
在这里插入图片描述

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

梯度截断是一种可以有效解决梯度爆炸问题的启发式方法,

当梯度的模大于一定阈值时,就将它截断成为一个较小的数。

一般有两种截断方式:按值截断和按模截断.

本实验使用按模截断的方式解决梯度爆炸问题。

在飞桨中,可以使用paddle.nn.ClipGradByNorm进行按模截断.— pytorch中用什么?
答:我在torch中使用的是torch.nn.utils.clip_grad_norm_(parameters=model.parameters(), max_norm=6, norm_type=1).

引入梯度截断之后,将重新观察模型的训练情况。

# 清空梯度列表
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,并实例化优化器
clip = nn.ClipGradByNorm(clip_norm=5.0)
optimizer = paddle.optimizer.SGD(learning_rate=lr, parameters=model.parameters(), grad_clip=clip)
# 定义评价指标
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)

训练结果:
在这里插入图片描述

画图代码:

save_path =  f"./images/6.9.pdf"
plot_grad(W_list, U_list, b_list, save_path, keep_steps=100)

在这里插入图片描述
接下来,使用梯度截断策略的模型在测试集上进行测试。
测试代码:

print(f"Evaluate SRN with data length {length}.")

# 加载训练过程中效果最好的模型
model_path = os.path.join(save_dir, f"srn_fix_explosion_model_{length}.pdparams")
runner.load_model(model_path)

# 使用测试集评价模型,获取测试集上的预测准确率
score, _ = runner.evaluate(test_loader)
print(f"[SRN] length:{length}, Score: {score: .5f}")

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

思考题

【思考题】梯度截断解决梯度爆炸问题的原理是什么?
答:我感觉就是类似于归一化那一类的,就是比如说在梯度计算的过程中,梯度越来越大,呈指数爆炸增长,现在在计算过程中比如说一个小的梯度数:120,通过某种梯度截断(强制限制在某个特定范围内)我们可以将这个梯度变为1.1(举例)从而解决后续梯度计算越来越大的问题,原理用我比较的解释就是说将一个大的梯度替换成一个小的梯度,从而解决梯度爆炸的问题。


总结

通过对梯度爆炸的复现,在实验中由于梯度的急剧变化,参数数值变的较大或较小,容易落入梯度饱和区,导致梯度为0,模型很难继续训练。所以我们使用梯度裁剪的办法进行解决,在本例中,梯度爆炸的最高峰达到了8000,我也想试一下,当梯度大于1时,他的梯度爆炸能不能到inf??,我也没有实现,或者我们可以用激活函数在大于1 的范围内,相信绝对可以实现,因为使用的是tanh所以在0~1梯度爆炸的话也会归零。至于inf的情况,我估计换一个激活函数就能实现。以上就是对本次复现梯度爆炸的实现。

References:

Paddle SGD
torch.optim.SGD参数详解(除nesterov)
梯度剪裁: torch.nn.utils.clip_grad_norm_()
pytorch梯度剪裁的方法
NNDL 实验6(上) - HBU_DAVID - 博客园 (cnblogs.com)
老师的博客:
NNDL 实验七 循环神经网络(2)梯度爆炸实验

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
深度学习实验中的循环神经网络是一种用于处理连续性数据的神经网络模型。它在神经元层之间不仅建立了层与层之间的权连接,还在层之间的神经元之间建立了权连接。这使得循环神经网络能够对序列数据进行处理,并且具有记忆能力。循环神经网络的一种简单实现是简单循环网络(Simple Recurrent Network,SRN)。 关于深度学习实验中的循环神经网络的更详细的信息,可以参考邱锡鹏老师的博客以及动手学深度学习的官方文档。这些资源提供了关于循环神经网络的理论知识和实际实施的指导。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [神经网络与深度学习实验day13-循环神经网络:RNN记忆能力实验](https://blog.csdn.net/ABU366/article/details/127912426)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [深度学习循环神经网络](https://download.csdn.net/download/baidu_35560935/13086725)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小鬼缠身、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值