HBU深度学习实验14.5-循环神经网络(1.5)

梯度爆炸实验

造成简单循环网络较难建模长程依赖问题的原因有两个:梯度爆炸和梯度消失。一般来讲,循环网络的梯度爆炸问题比较容易解决,一般通过权重衰减或梯度截断可以较好地来避免;对于梯度消失问题,更加有效的方式是改变模型,比如通过长短期记忆网络LSTM来进行缓解。

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

梯度打印函数

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

import torchW_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":# 使用torch的norm方法计算L2范数,并转换为Python标量值(原先是paddle的写法)W_grad_l2 = param.grad.norm(p=2).item()if name == "rnn_model.U":U_grad_l2 = param.grad.norm(p=2).item()if name == "rnn_model.b":b_grad_l2 = param.grad.norm(p=2).item()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)

复现梯度爆炸现象

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

import os
import random
import torch
import numpy as npnp.random.seed(0)
random.seed(0)
torch.manual_seed(0)  # torch中设置随机种子的方式,用于复现实验结果# 训练轮次
num_epochs = 50
# 学习率
lr = 0.2
# 输入数字的类别数
num_digits = 10
# 将数字映射为向量的维度
input_size = 32
# 隐状态向量的维度
hidden_size = 32
# 预测数字的类别数
num_classes = 19
# 批大小
batch_size = 64
# 模型保存目录
save_dir = "./checkpoints1"# 可以设置不同的length进行不同长度数据的预测实验
length = 20
print(f"\n====> Training SRN with data of length {length}.")# 加载长度为length的数据,这里假设load_data函数在torch环境下有对应的实现,能正确返回数据
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)
# 使用torch的DataLoader,注意一些参数的设置方式可能稍有不同
train_loader = torch.utils.data.DataLoader(train_set, batch_size=batch_size, shuffle=True)  # 一般需要设置shuffle为True打乱数据
dev_loader = torch.utils.data.DataLoader(dev_set, batch_size=batch_size)
test_loader = torch.utils.data.DataLoader(test_set, batch_size=batch_size)# 实例化模型,这里假设SRN和Model_RNN4SeqClass类已经正确按照torch的规范进行了定义(比如继承自torch.nn.Module等)
base_model = SRN(input_size, hidden_size)
model = Model_RNN4SeqClass(base_model, num_digits, input_size, hidden_size, num_classes)
# 指定优化器,使用torch的SGD优化器,参数传入方式等有不同写法
optimizer = torch.optim.SGD(model.parameters(), lr=lr)
# 定义评价指标,这里假设Accuracy类已经有对应的torch实现
metric = Accuracy()
# 定义损失函数,使用torch的CrossEntropyLoss,注意参数使用上的区别
loss_fn = torch.nn.CrossEntropyLoss(reduction='sum')# 基于以上组件,实例化Runner,这里假设RunnerV3类已经按照torch规范进行了适配
runner = RunnerV3(model, optimizer, loss_fn, metric)# 进行模型训练,假设train方法内部已经适配了torch的相关操作(比如梯度更新等机制)
model_save_path = os.path.join(save_dir, f"srn_explosion_model_{length}.pt")  # torch模型保存的常见后缀是.pt
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范数,并将其绘制为图片以便展示,相应代码如下:

import matplotlib.pyplot as pltdef 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="#8E004D", label="W_grad_l2")plt.plot(steps, U_list[:keep_steps], "-.", color="#E20079", label="U_grad_l2")plt.plot(steps, b_list[:keep_steps], "--", color="#3D3D3F", 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)

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

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

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}")

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

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

按模截断是按照梯度向量g的模进行截断,保证梯度向量的模值不大于阈值bb,裁剪后的梯度为:

\left.g=\left\{ \begin{array} {cc}g, & ||\boldsymbol{g}||\leq b \\ \frac{b}{||\boldsymbol{g}||}*\boldsymbol{g}, & ||\boldsymbol{g}||>b \end{array}\right.\right.

当梯度向量g的模不大于阈值b时,g数值不变,否则对g进行数值缩放。 

在飞桨中,可以使用paddle.nn.ClipGradByNorm进行按模截断. 在代码实现时,将ClipGradByNorm传入优化器,优化器在反向迭代过程中,每次梯度更新时默认可以对所有梯度裁剪。 

  • 在飞桨中使用nn.ClipGradByNorm进行梯度裁剪并传入优化器,而在torch中并没有完全对应的类来这样直接传入优化器实现裁剪。通常的做法是使用torch.nn.utils.clip_grad_norm_函数,先正常实例化优化器(如示例中使用torch.optim.SGD),然后在训练循环里每次反向传播计算出梯度后,调用torch.nn.utils.clip_grad_norm_函数对模型参数的梯度进行裁剪(传入模型的参数和设定的最大范数阈值,这里阈值为5.0对应原来飞桨代码中的clip_norm=5.0)。
  • 优化器实例化部分,torch.optim.SGD的参数传入方式和飞桨稍有不同,在torch里直接按顺序传入模型参数model.parameters()和学习率lr即可,不需要像飞桨那样通过关键字参数指定grad_clip等额外的与梯度裁剪相关的配置(因为梯度裁剪在torch里是在优化器外部单独控制的方式)。

在引入梯度截断之后,将重新观察模型的训练情况。这里我们重新实例化一下:模型和优化器,然后组装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}.pt")
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范数,并将其绘制为图片以便展示,相应代码如下: 

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('.', 'checkpoints2', 'srn_fix_explosion_model_20.pt')
runner.load_model(model_path)# 使用测试集评价模型,获取测试集上的预测准确率
score, _ = runner.evaluate(test_loader)
print(f"[SRN] length:{length}, Score: {score: .5f}")

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

总结:

RNN神经网络产生梯度消失和梯度爆炸的原因及解决方案 - 早起的小虫子 - 博客园 (cnblogs.com)

本次实验主要是为了解决梯度爆炸问题

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

产生梯度爆炸的原因:

首先来看一下RNN的模型结构图:

RNN的前向传播公式:


\begin{aligned} & s_{t}=\phi(Ux_{t}+Ws_{t-1})\ \\ & o_{t}=f(Vs_{t})\ \end{aligned} 

反向传播算法:

BPTT(back-propagation through time)算法是针对循层的训练算法,它的基本原理和BP算法一样。其算法本质还是梯度下降法,那么该算法的关键就是计算各个参数的梯度,对于RNN来说参数有 U、W、V

现对t=3时刻的U、W、V求偏导,由链式法则得到:

\begin{aligned} & \frac{\partial L_3}{\partial V}=\frac{\partial L_3}{\partial o_3}\frac{\partial o_3}{\partial V}\\\ & \frac{\partial L_3}{\partial W}=\frac{\partial L_3}{\partial o_3}\frac{\partial o_3}{\partial s_3}\frac{\partial s_3}{\partial W}+\frac{\partial L_3}{\partial o_3}\frac{\partial o_3}{\partial s_2}\frac{\partial s_2}{\partial W}+\frac{\partial L_3}{\partial o_3}\frac{\partial o_3}{\partial s_3}\frac{\partial s_3}{\partial s_2}\frac{\partial s_2}{\partial s_1}\frac{\partial s_1}{\partial W}\mathrm{} \\ & \frac{\partial L_3}{\partial U}=\frac{\partial L_3}{\partial o_3}\frac{\partial o_3}{\partial s_3}\frac{\partial s_3}{\partial U}+\frac{\partial L_3}{\partial o_3}\frac{\partial o_3}{\partial s_2}\frac{\partial s_2}{\partial U}+\frac{\partial L_3}{\partial o_3}\frac{\partial o_3}{\partial s_3}\frac{\partial s_3}{\partial s_2}\frac{\partial s_2}{\partial s_1}\frac{\partial s_1}{\partial U}\mathrm{} \end{aligned}  

可以简写成:

\frac{\partial L_3}{\partial U}=\sum_{k=0}^3\frac{\partial L_3}{\partial o_3}\frac{\partial o_3}{\partial s_3}\frac{\partial s_3}{\partial s_k}\frac{\partial s_k}{\partial U}=\sum_{k=0}^3\frac{\partial L_3}{\partial o_3}\frac{\partial o_3}{\partial s_3}(\prod_{j-k-1}^3\frac{\partial s_j}{\partial s_{j-1}})\frac{\partial s_k}{\partial U} 

取其中累乘的部分出来,其中激活函数 Φ 通常是tanh函数 ,则

\prod_{j=k-1}^3\frac{\partial s_j}{\partial s_{j-1}}=\prod_{j=k-1}^3tanh^{^{\prime}}W 

对于 V 求偏导不存在依赖问题;但是对于 W、U 求偏导的时候,由于时间序列长度,存在长期依赖的情况。主要原因可由 t=1、2、3 的情况观察得 , St会随着时间序列向前传播,同时St是 U、W 的函数。 

激活函数tanh和它的导数图像如下:

tanh函数的导数最大值为1,又不可能一直都取1这种情况,实际上这种情况很少出现,那么也就是说,大部分都是小于1的数在做累乘。 

如果参数 W 中的值太大,随着序列长度存在长期依赖的情况,大于1的数连乘好多次,就会出现梯度爆炸。反之,梯度消失。

权重衰减weight_decay参数从入门到精通_weight decay-CSDN博客

权重衰减

L=L_0+\frac{\lambda}{2}||W||^2

上面的公式是L2正则

L_{\mathrm{}}=L_{\mathrm{0}}+\lambda\sum_{i=1}^n|w_i|

上面就是L1正则(L是损失函数)

梯度截断 

在PyTorch中,梯度裁剪是一种常用的技术,用于防止梯度爆炸问题,这在训练深度神经网络时尤为重要。梯度裁剪通过限制梯度的大小,确保在反向传播过程中梯度不会变得过大,从而避免数值不稳定和过拟合问题。

梯度裁剪的方法

PyTorch提供了两种梯度裁剪的方法:torch.nn.utils.clip_grad_norm_ 和 torch.nn.utils.clip_grad_value_。这两种方法都应在计算完梯度后、执行优化器的 step() 方法之前使用。

固定阈值裁剪

clip_grad_value_ 函数通过将所有梯度限制在指定的阈值内来实现梯度裁剪。如果梯度的绝对值超过了阈值,它会被设置为阈值的符号乘以阈值。例如,如果阈值设置为0.5,那么所有梯度的值都会被限制在 [-0.5, 0.5] 范围内。

import torch
import torch.nn as nn# 创建模型和优化器
model = nn.Linear(2, 1)
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)# 计算梯度
loss.backward()# 对梯度进行裁剪
torch.nn.utils.clip_grad_value_(model.parameters(), clip_value=0.5)# 更新模型参数
optimizer.step()
 范数裁剪

clip_grad_norm_ 函数根据所有梯度的范数来进行裁剪。如果梯度的范数超过了最大范数(max_norm),则会按比例缩小梯度,使其范数等于 max_norm。这种方法考虑了所有参数的梯度,并将它们视为一个整体来进行裁剪。 

import torch
import torch.nn as nn
import torch.optim as optim# 创建模型和优化器
model = nn.Linear(10, 1)
optimizer = optim.SGD(model.parameters(), lr=0.01)# 计算梯度
loss.backward()# 对梯度进行裁剪
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=20, norm_type=2)# 更新模型参数
optimizer.step()

某些优化器,如Adam和RMSProp,已经包含了防止梯度爆炸的机制,使用梯度裁剪可能会干扰其内部工作机制。 

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

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

相关文章

Leetcode经典题4--查找数组中的多数元素+Boyer-Moore 投票算法

题目描述: 给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。 你可以假设数组是非空的,并且给定的数组总是存在多数元素。 输入输出示例 输入:nums [2,2,1,1,1,2,2] 输出…

android studio 读写文件操作(应用场景二)

android studio版本:2023.3.1 patch2 例程:readtextviewIDsaveandread 本例程是个过渡例程,如果单是实现下图的目的有更简单的方法,但这个方法是下一步工作的基础,所以一定要做。 例程功能:将两个textvi…

【NLP 9、实践 ① 五维随机向量交叉熵多分类】

目录 五维向量交叉熵多分类 规律: 实现: 1.设计模型 2.生成数据集 3.模型测试 4.模型训练 5.对训练的模型进行验证 调用模型 你的平静,是你最强的力量 —— 24.12.6 五维向量交叉熵多分类 规律: x是一个五维(索引)向量&#xff…

windows文件下换行, linux上不换行 解决CR换行符替换为LF notepad++

html文件是用回车换行的,在windows电脑上,显示正常。 文件上传到linux服务器后,文件不换行了。只有一行。而且相关js插件也没法正常运行。 用notepad查看,显示尾部换行符,是CR,这就是原因。CR是不被识别的。…

Unity 模拟百度地图,使用鼠标控制图片在固定区域内放大、缩小、鼠标左键拖拽移动图片

效果展示: 步骤流程: 1.使用的是UGUI,将下面的脚本拖拽到图片上即可。 using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems;public class CheckImage : MonoBehaviour, IDragHandler, IBeginDragHandler, IEndDragH…

游戏引擎学习第30天

仓库: https://gitee.com/mrxiao_com/2d_game 回顾 在这段讨论中,重点是对开发过程中出现的游戏代码进行梳理和进一步优化的过程。 工作回顾:在第30天,回顾了前一天的工作,并提到今天的任务是继续从第29天的代码开始&#xff0c…

基于MFC绘制门电路

MFC绘制门电路 1. 设计内容、方法与难点 本课题设计的内容包括了基本门电路中与门和非门的绘制、选中以及它们之间的连接。具体采用的方法是在OnDraw函数里面进行绘制,并设计元器件基类,派生出与门和非门,并组合了一个引脚类,在…

【text2sql】低资源场景下Text2SQL方法

SFT使模型能够遵循输入指令并根据预定义模板进行思考和响应。如上图,、 和 是用于通知模型在推理过程中响应角色的角色标签。 后面的内容表示模型需要遵循的指令,而 后面的内容传达了当前用户对模型的需求。 后面的内容代表模型的预期输出,也…

学习threejs,实现配合使用WebWorker

👨‍⚕️ 主页: gis分享者 👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍⚕️ 收录于专栏:threejs gis工程师 文章目录 一、🍀前言1.1 ☘️WebWorker web端多线程 二、…

16-03、JVM系列之:内存与垃圾回收篇(三)

JVM系列之:内存与垃圾回收篇(三) ##本篇内容概述: 1、执行引擎 2、StringTable 3、垃圾回收一、执行引擎 ##一、执行引擎概述 如果想让一个java程序运行起来,执行引擎的任务就是将字节码指令解释/编译为对应平台上的本地机器指令才可以。 简…

小程序 - 美食列表

小程序交互练习 - 美食列表小程序开发笔记 目录 美食列表 功能描述 准备工作 创建项目 配置页面 配置导航栏 启动本地服务器 页面初始数据 设置获取美食数据 设置onload函数 设置项目配置 页面渲染 页面样式 处理电话格式 创建处理电话格式脚本 页面引入脚本 …

Qt6.8 QGraphicsView鼠标坐标点偏差

ui文件拖放QGraphicsView,src文件定义QGraphicsScene赋值给图形视图。 this->scene new QGraphicsScene();ui.graph->setScene(this->scene);对graphicview过滤事件,只能在其viewport之后安装,否则不响应。 ui.graph->viewport…

若依 ruoyi VUE el-select 直接获取 选择option 的 label和value

1、最新在研究若依这个项目,我使用的是前后端分离的方案,RuoYi-Vue-fast(后端) RuoYi-Vue-->ruoyi-ui(前端)。RuoYi-Vue-fast是单应用版本没有区分那么多的modules 自己开发起来很方便,这个项目运行起来很方便,但是需要自定义的…

springboot事务手动回滚报错

捕捉异常之后手动标记回滚事务 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 没有嵌套事务,还是报Transaction rolled back because it has been marked as rollback-only异常错误 查看错误堆栈,service调用的方法外层还套…

使用 LlamaFactory 结合开源大语言模型实现文本分类:从数据集构建到 LoRA 微调与推理评估

文章目录 背景介绍文本分类数据集Lora 微调模型部署与推理期待模型的输出结果 文本分类评估代码 背景介绍 本文将一步一步地,介绍如何使用llamafactory框架利用开源大语言模型完成文本分类的实验,以 LoRA微调 qwen/Qwen2.5-7B-Instruct 为例。 文本分类…

ARM内核与单片机

1.单片机硬件架构如下所示:各种硬件通过总线进行连接。 2.M4内核架构 3.单片机如何工作: 4.CPU是通过读写寄存器来控制GPIO的 5.GPIO的硬件框架:一共有8种模式 (1)推挽/推挽复用输出。下图先看图1,如果输入…

PHP 命令执行漏洞学习记录

PHP 命令执行 命令函数 作用 例子 system() 执行外部程序,并且显示输出 system(whoami) exec() 执行一个外部程序 echo exec(whoami); shell_exec() 通过shell环境执行命令,并且将完整的输出以字符串的形式返回 echo shell_exec(whoami); passthru() 执行外部程序…

VSCode GDB远程嵌入开发板调试

VSCode GDB远程嵌入式开发板调试 一、原理 嵌入式系统中一般在 PC端运行 gdb工具,源码也是在 PC端,源码对应的可执行文件放到开发板中运行。为此我们需要在开发板中运行 gdbserver,通过网络与 PC端的 gdb进行通信。因此要想在 PC上通过 gdb…

专业140+总分420+上海交通大学819考研经验上交电子信息与通信工程,真题,大纲,参考书。博睿泽信息通信考研论坛,信息通信考研Jenny

考研结束,专业819信号系统与信号处理140,总分420,终于梦圆交大,高考时敢都不敢想目标,现在已经成为现实,考研后劲很大,这一年的复习经历,还是历历在目,整理一下&#xff…

【NLP修炼系列之Bert】Bert多分类多标签文本分类实战(附源码下载)

引言 今天我们就要用Bert做项目实战,实现文本多分类任务和我在实际公司业务中的多标签文本分类任务。通过本篇文章,可以让想实际入手Bert的NLP学习者迅速上手Bert实战项目。 1 项目介绍 本文是Bert文本多分类和多标签文本分类实战,其中多分…