深度学习实验十四 循环神经网络(1)——测试简单循环网络的记忆能力和梯度爆炸实验
目录
一、数据集构建
1.1数据集的构建函数
1.2加载数据集并划分
1.3 构建Dataset类
二、模型构建
2.1嵌入层
2.2SRN层
2.3模型汇总
三、模型训练
3.1 训练指定长度的数字预测模型
3.2 损失曲线展示
四、模型评价
五、修改
附完整可运行代码
实验大体步骤:
简单循环网络在参数学习时存在长程依赖问题,很难建模长时间间隔(Long Range)的状态之间的依赖关系。为了测试简单循环网络的记忆能力,本节构建一个数字求和任务进行实验。
数字求和任务的输入是一串数字,前两个位置的数字为0-9,其余数字随机生成(主要为0),预测目标是输入序列中前两个数字的加和。下图展示了长度为10的数字序列.
如果序列长度越长,准确率越高,则说明网络的记忆能力越好.因此,我们可以构建不同长度的数据集,通过验证简单循环网络在不同长度的数据集上的表现,从而测试简单循环网络的长程依赖能力.
一、数据集构建
我们首先构建不同长度的数字预测数据集DigitSum.
由于在本任务中,输入序列的前两位数字为 0 − 9,其组合数是固定的,所以可以穷举所有的前两位数字组合,并在后面默认用0填充到固定长度. 但考虑到数据的多样性,这里对生成的数字序列中的零位置进行随机采样,并将其随机替换成0-9的数字以增加样本的数量.
我们可以通过设置kk的数值来指定一条样本随机生成的数字序列数量.当生成某个指定长度的数据集时,会同时生成训练集、验证集和测试集。当k=3时,生成训练集。当kk=1时,生成验证集和测试集.
1.1数据集的构建函数
代码如下:
import os
import random
import numpy as np
from torch import nn# 固定随机种子
random.seed(0)
np.random.seed(0)# ========数据集构建=============================================
def generate_data(length, k, save_path):if length < 3:raise ValueError("The length of data should be greater than 2.")if k == 0:raise ValueError("k should be greater than 0.")# 创建目录(如果不存在)directory = os.path.dirname(save_path)if not os.path.exists(directory):os.makedirs(directory)# 生成100条长度为length的数字序列,除前两个字符外,序列其余数字暂用0填充base_examples = []for n1 in range(0, 10):for n2 in range(0, 10):seq = [n1, n2] + [0] * (length - 2)label = n1 + n2base_examples.append((seq, label))examples = []# 数据增强:对base_examples中的每条数据,默认生成k条数据,放入examplesfor base_example in base_examples:for _ in range(k):# 随机生成替换的元素位置和元素idx = np.random.randint(2, length)val = np.random.randint(0, 10)# 对序列中的对应零元素进行替换seq = base_example[0].copy()label = base_example[1]seq[idx] = valexamples.append((seq, label))# 保存增强后的数据with open(save_path, "w", encoding="utf-8") as f:for example in examples:# 将数据转为字符串类型,方便保存seq = [str(e) for e in example[0]]label = str(example[1])line = " ".join(seq) + "\t" + label + "\n"f.write(line)print(f"generate data to: {save_path}.")# 定义生成的数字序列长度
lengths = [5, 10, 15, 20, 25, 30, 35]
for length in lengths:# 生成长度为length的训练数据save_path = f"./datasets/{length}/train.txt"k = 3generate_data(length, k, save_path)# 生成长度为length的验证数据save_path = f"./datasets/{length}/dev.txt"k = 1generate_data(length, k, save_path)# 生成长度为length的测试数据save_path = f"./datasets/{length}/test.txt"k = 1generate_data(length, k, save_path)
1.2加载数据集并划分
本实验提前生成了长度分别为5、10、 15、20、25、30和35的7份数据,存放于“./datasets”目录下
代码如下:
# ===加载数据并进行数据划分=================================================
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))
运行结果:
1.3 构建Dataset类
代码如下:
# =====构造Dataset类=====================================================
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)
二、模型构建
整个模型由以下几个部分组成:
(1) 嵌入层:将输入的数字序列进行向量化,即将每个数字映射为向量;
(2) SRN 层:接收向量序列,更新循环单元,将最后时刻的隐状态作为整个序列的表示;
(3) 输出层:一个线性层,输出分类的结果.
2.1嵌入层
本任务输入的样本是数字序列,为了更好地表示数字,需要将数字映射为一个嵌入(Embedding)向量。嵌入向量中的每个维度均能用来刻画该数字本身的某种特性。由于向量能够表达该数字更多的信息,利用向量进行数字求和任务,可以使得模型具有更强的拟合能力。
代码如下:
# ==========嵌入层=====================================================
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层输入有什么问题?
①神经网络尤其是递归神经网络(RNN)通常期望接收连续的输入数据。数字(例如:0, 1, 2, 3, 等)是离散的,它们与模型期望的输入方式不太匹配。如果直接将数字作为输入,模型可能难以理解数字之间的相对关系。模型可能会将数字 1 和 2 看作两个独立的、无关的输入,无法理解它们之间的顺序和数值差异。
②数字 100 可能和数字 1 在输入层上的表示差异非常大,而这种差异不一定反映了数字之间的实际关系。在神经网络中,输入特征的尺度差异可能导致模型训练不稳定,且难以收敛。
2.2SRN层
代码如下:
# ==========SRN层============================================================================
import torch
import torch.nn as nn
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_size# hidden_state = F.tanh(torch.matmul(step_input, self.W) + torch.matmul(hidden_state, self.U) + self.b)hidden_state = hidden_state + F.tanh(torch.matmul(step_input, self.W) + torch.matmul(hidden_state, self.U) + self.b)return hidden_state# 初始化参数并运行
U_attr = [[0.0, 0.1], [0.1, 0.0]]
b_attr = [[0.1, 0.1]]
W_attr = [[0.1, 0.2], [0.1, 0.2]]srn = SRN(2, 2, W_attr=W_attr, U_attr=U_attr, b_attr=b_attr)inputs = torch.tensor([[[1, 0], [0, 2]]], dtype=torch.float32)
hidden_state = srn(inputs)
print("hidden_state", hidden_state)
运行结果如下:
PyTorch框架内置了SRN的API torch.nn.RNN
代码如下:
# ====PyTorch框架内置了SRN的API torch.nn.RNN============================================
# 初始化参数并运行
U_attr = [[0.0, 0.1], [0.1, 0.0]]
b_attr = [[0.1, 0.1]]
W_attr = [[0.1, 0.2], [0.1, 0.2]]srn = SRN(2, 2, W_attr=W_attr, U_attr=U_attr, b_attr=b_attr)inputs = torch.tensor([[[1, 0], [0, 2]]], dtype=torch.float32)
hidden_state = srn(inputs)
print("hidden_state", hidden_state)# =====将自己实现的SRN和PyTorch框架内置的RNN返回的结果进行打印展示================================
# 这里创建一个随机数组作为测试数据,数据shape为batch_size x seq_len x input_size
batch_size, seq_len, input_size = 8, 20, 32
inputs = torch.randn([batch_size, seq_len, input_size])# 设置模型的hidden_size
hidden_size = 32
torch_srn = nn.RNN(input_size, hidden_size)
self_srn = SRN(input_size, hidden_size)self_hidden_state = self_srn(inputs)
torch_outputs, torch_hidden_state = torch_srn(inputs)print("self_srn hidden_state: ", self_hidden_state.shape)
print("torch_srn outpus:", torch_outputs.shape)
print("torch_srn hidden_state:", torch_hidden_state.shape)
运行结果:
将自己实现的SRN和PyTorch框架内置的RNN返回的结果进行打印展示:
# 这里创建一个随机数组作为测试数据,数据shape为batch_size x seq_len x input_size
batch_size, seq_len, input_size, hidden_size = 2, 5, 10, 10
inputs = torch.randn([batch_size, seq_len, input_size])# 设置模型的hidden_sizetorch_srn = nn.RNN(input_size, hidden_size, bias=False)# 获取torch_srn中的参数,并设置相应的paramAttr,用于初始化SRN
W_attr = torch_srn.weight_ih_l0.T
U_attr = torch_srn.weight_hh_l0.T
self_srn = SRN(input_size, hidden_size, W_attr=W_attr, U_attr=U_attr)# 进行前向计算,获取隐状态向量,并打印展示
self_hidden_state = self_srn(inputs)
torch_outputs, torch_hidden_state = torch_srn(inputs)
print("torch SRN:\n", torch_hidden_state.detach().numpy().squeeze(0))
print("self SRN:\n", self_hidden_state.detach().numpy())
运行结果如下:
对比一下运行时间:
# ======两者时间差异===========================================================
import time# 这里创建一个随机数组作为测试数据,数据shape为batch_size x seq_len x input_size
batch_size, seq_len, input_size, hidden_size = 2, 5, 10, 10
inputs = torch.randn([batch_size, seq_len, input_size])# 实例化模型
self_srn = SRN(input_size, hidden_size)
torch_srn = nn.RNN(input_size, hidden_size)# 计算自己实现的SRN运算速度
model_time = 0
for i in range(100):strat_time = time.time()out = self_srn(inputs)if i < 10:continueend_time = time.time()model_time += (end_time - strat_time)
avg_model_time = model_time / 90
print('self_srn speed:', avg_model_time, 's')# 计算torch内置的SRN运算速度
model_time = 0
for i in range(100):strat_time = time.time()out = torch_srn(inputs)# 预热10次运算,不计入最终速度统计if i < 10:continueend_time = time.time()model_time += (end_time - strat_time)
avg_model_time = model_time / 90
print('torch_srn speed:', avg_model_time, 's')
运行结果:
我看着感觉是两者并没有差太多。
2.3模型汇总
在定义了每一层的算子之后,我们定义一个数字求和模型Model_RNN4SeqClass,该模型会将嵌入层、SRN层和线性层进行组合,以实现数字求和的功能.
具体来讲,Model_RNN4SeqClass会接收一个SRN层实例,用于处理数字序列数据,同时在__init__
函数中定义一个Embedding
嵌入层,其会将输入的数字作为索引,输出对应的向量,最后会使用paddle.nn.Linear
定义一个线性层。
代码如下:
# ======模型汇总======================================================
# 基于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)
运行结果:
三、模型训练
3.1 训练指定长度的数字预测模型
代码如下:
# =========模型训练================================================
import os
import random
import torch
import numpy as np
from nndl_3 import Accuracy, RunnerV3# 训练轮次
num_epochs = 500
# 学习率
lr = 0.0001
# 输入数字的类别数
num_digits = 10
# 将数字映射为向量的维度
input_size = 32
# 隐状态向量的维度
hidden_size = 32
# 预测数字的类别数
num_classes = 19
# 批大小
batch_size = 8
# 模型保存目录
save_dir = "./checkpoints"# 通过指定length进行不同长度数据的实验
def train(length):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.Adam(lr=lr, params=model.parameters())# 定义评价指标metric = Accuracy()# 定义损失函数loss_fn = nn.CrossEntropyLoss()# 基于以上组件,实例化Runnerrunner = RunnerV3(model, optimizer, loss_fn, metric)# 进行模型训练model_save_path = os.path.join(save_dir, f"best_srn_model_{length}.pdparams")runner.train(train_loader, dev_loader, num_epochs=num_epochs, eval_steps=100, log_steps=100,save_path=model_save_path)return runnersrn_runners = {}# 多组训练
lengths = [10, 15, 20, 25, 30, 35]
for length in lengths:runner = train(length)srn_runners[length] = runner
3.2 损失曲线展示
代码如下:
# =======损失函数绘制===================================================
import matplotlib.pyplot as plt
def plot_training_loss(runner, fig_name, sample_step):plt.figure()train_items = runner.train_step_losses[::sample_step]train_steps = [x[0] for x in train_items]train_losses = [x[1] for x in train_items]plt.plot(train_steps, train_losses, color='#e4007f', label="Train loss")dev_steps = [x[0] for x in runner.dev_losses]dev_losses = [x[1] for x in runner.dev_losses]plt.plot(dev_steps, dev_losses, color='#f19ec2', linestyle='--', label="Dev loss")# 绘制坐标轴和图例plt.ylabel("loss", fontsize='large')plt.xlabel("step", fontsize='large')plt.legend(loc='upper right', fontsize='x-large')plt.savefig(fig_name)plt.show()# 画出训练过程中的损失图
for length in lengths:runner = srn_runners[length]fig_name = f"./images/6.6_{length}.pdf"plot_training_loss(runner, fig_name, sample_step=100)
运行结果:(分别是长度为10、15、20、25、30、35,lr=0.001的情况)
可以发现是有点过拟合的,Train loss不断减小,但是dev loss却在上升。
这表明当序列变长时,SRN模型保持序列长期依赖能力在逐渐变弱,越来越无法学习到有用的知识。SRN模型过拟合到序列结尾的信息,而遗忘了序列开始位置的信息。说明,SRN模型在建模长程依赖方面的能力比较弱。
四、模型评价
代码如下:
# ======模型评价===========================================
srn_dev_scores = []
srn_test_scores = []
for length in lengths:print(f"Evaluate SRN with data length {length}.")runner = srn_runners[length]# 加载训练过程中效果最好的模型model_path = os.path.join(save_dir, f"best_srn_model_{length}.pdparams")runner.load_model(model_path)# 加载长度为length的数据data_path = f"./datasets/{length}"train_examples, dev_examples, test_examples = load_data(data_path)test_set = DigitSumDataset(test_examples)test_loader = DataLoader(test_set, batch_size=batch_size)# 使用测试集评价模型,获取测试集上的预测准确率score, _ = runner.evaluate(test_loader)srn_test_scores.append(score)srn_dev_scores.append(max(runner.dev_scores))for length, dev_score, test_score in zip(lengths, srn_dev_scores, srn_test_scores):print(f"[SRN] length:{length}, dev_score: {dev_score}, test_score: {test_score: .5f}")# ========将SRN在不同长度的验证集和测试集数据上的表现,绘制成图片进行观察=================================
import matplotlib.pyplot as pltplt.plot(lengths, srn_dev_scores, '-o', color='#e4007f', label="Dev Accuracy")
plt.plot(lengths, srn_test_scores, '-o', color='#f19ec2', label="Test Accuracy")# 绘制坐标轴和图例
plt.ylabel("accuracy", fontsize='large')
plt.xlabel("sequence length", fontsize='large')
plt.legend(loc='upper right', fontsize='x-large')fig_name = "./images/6.7.pdf"
plt.savefig(fig_name)
plt.show()
运行结果:
可以看出实验结果并不是很好。
五、修改
于是参看书上的动手练习6.1:
参考《神经网络与深度学习》中的公式(6.50),改进SRN的循环单元,加入隐状态之间的残差连接,并重复数字求和实验。观察是否可以缓解长程依赖问题。
这样和之间为既有线性关系,也有非线性关系,并且可以缓解梯度消失问题.于是尝试加入残差连接,并重新进行实验。根据这个公式可知,只需要将原来的hidden_state加上上一个时刻的hidden_state即可。
原始代码:
hidden_state = F.tanh(torch.matmul(step_input, self.W) + torch.matmul(hidden_state, self.U) + self.b)
修改为:
hidden_state =hidden_state + F.tanh(torch.matmul(step_input, self.W) + torch.matmul(hidden_state, self.U) + self.b)
运行结果:
从以上的loss图可以看出,train的loss基本和dev的loss重合,减轻了过拟合。
但是这种改进方法还存在着两个问题:
于是可以通过引入门控机制来进一步改进模型,也就是LSTM。
造成简单循环网络较难建模长程依赖问题的原因有两个:梯度爆炸和梯度消失。
一般来讲,循环网络的梯度爆炸问题比较容易解决,一般通过权重衰减或梯度截断可以较好地来避免;对于梯度消失问题,更加有效的方式是改变模型,比如通过长短期记忆网络LSTM来进行缓解。
各个长度数据集的准确率可以看出,并不是长度越长,准确率越低,往往在25或者30处存在一个拐点(中点低两边高或者是中间高两边低),那这种情况是为什么呢?
查阅了一下资料做出的一些解释(可能有点不太准确):
①网络的训练时间对准确率也有影响。较长的序列需要更多的训练时间来优化网络参数,且较长序列可能更容易陷入局部最优解。30长度的序列可能正好处于训练过程中较难收敛的区间,而25和35的序列可能更容易收敛到较好的解。
②在较短的序列的情况下,网络可能会过拟合或过于依赖序列中的少量信息,网络可能会过度“记住”某些样本的特征,而不是学习到更通用的规律。随着数据集长度增加,网络获得的信息变得更加冗余,可以帮助它做出更泛化的预测。过短的序列会使模型过度依赖训练集中的特定样本,而较长的序列可以让网络更好地学习到前两个数字的关系,避免过拟合。从图xlen=25的loss也可以看出,他的train loss和dev loss相差最多,也说明了为什么len=25是准确率最低的情况。
调了调学习率
lr=0.01
lr=0.1
(这个也太不好了吧)
lr=0.0001
这个就感觉有点欠拟合的意思了。
我发现SRN的准确率不宜调的过大,当lr=0.001时所有长度数据集准确率基本都在0.8以上,但是lr=0.01和0.1时准确率就不是很好看了。所以较长的序列可能需要更低的学习率或更高的隐藏层单元数。如果超参数设置不合理,可能会导致模型在某些长度下的性能波动。
但是从lr=0.0001的图中可以看出过小的准确率也不是很好,train和dev的loss都在逐渐减小,但是可能还没有训练好,产生了欠拟合,所以出现了len=35比任何长度的准确率都高的情况。
思考题:如果不使用嵌入层,直接将数字作为SRN层输入有什么问题?
①神经网络尤其是递归神经网络(RNN)通常期望接收连续的输入数据。数字(例如:0, 1, 2, 3, 等)是离散的,它们与模型期望的输入方式不太匹配。如果直接将数字作为输入,模型可能难以理解数字之间的相对关系。模型可能会将数字 1 和 2 看作两个独立的、无关的输入,无法理解它们之间的顺序和数值差异。
②数字 100 可能和数字 1 在输入层上的表示差异非常大,而这种差异不一定反映了数字之间的实际关系。在神经网络中,输入特征的尺度差异可能导致模型训练不稳定,且难以收敛。
什么是词向量呢?
之前讲过的word2vec是词向量的一种方式。词向量是一种将词汇映射为固定长度连续向量的技术,每个词被映射成一个固定维度的向量,通常是几十到几百维。在一个好的词向量空间中,语义相似的词会有相似的向量表示。例如,“猫”和“狗”的词向量应该比“猫”和“汽车”的词向量更相似。同时,通过词向量还可以捕捉词汇之间的语义关系(如“国王” - “男人” + “女人” ≈ “女王”)
附完整可运行代码
主程序:
import os
import random
import numpy as np
from torch import nn# 固定随机种子
random.seed(0)
np.random.seed(0)# ========数据集构建=============================================
def generate_data(length, k, save_path):if length < 3:raise ValueError("The length of data should be greater than 2.")if k == 0:raise ValueError("k should be greater than 0.")# 创建目录(如果不存在)directory = os.path.dirname(save_path)if not os.path.exists(directory):os.makedirs(directory)# 生成100条长度为length的数字序列,除前两个字符外,序列其余数字暂用0填充base_examples = []for n1 in range(0, 10):for n2 in range(0, 10):seq = [n1, n2] + [0] * (length - 2)label = n1 + n2base_examples.append((seq, label))examples = []# 数据增强:对base_examples中的每条数据,默认生成k条数据,放入examplesfor base_example in base_examples:for _ in range(k):# 随机生成替换的元素位置和元素idx = np.random.randint(2, length)val = np.random.randint(0, 10)# 对序列中的对应零元素进行替换seq = base_example[0].copy()label = base_example[1]seq[idx] = valexamples.append((seq, label))# 保存增强后的数据with open(save_path, "w", encoding="utf-8") as f:for example in examples:# 将数据转为字符串类型,方便保存seq = [str(e) for e in example[0]]label = str(example[1])line = " ".join(seq) + "\t" + label + "\n"f.write(line)print(f"generate data to: {save_path}.")# 定义生成的数字序列长度
lengths = [5, 10, 15, 20, 25, 30, 35]
for length in lengths:# 生成长度为length的训练数据save_path = f"./datasets/{length}/train.txt"k = 3generate_data(length, k, save_path)# 生成长度为length的验证数据save_path = f"./datasets/{length}/dev.txt"k = 1generate_data(length, k, save_path)# 生成长度为length的测试数据save_path = f"./datasets/{length}/test.txt"k = 1generate_data(length, k, save_path)# ===加载数据并进行数据划分=================================================
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))# =====构造Dataset类=====================================================
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
import torch.nn as nn
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_size# hidden_state = F.tanh(torch.matmul(step_input, self.W) + torch.matmul(hidden_state, self.U) + self.b)hidden_state = hidden_state + F.tanh(torch.matmul(step_input, self.W) + torch.matmul(hidden_state, self.U) + self.b)return hidden_state# 初始化参数并运行
U_attr = [[0.0, 0.1], [0.1, 0.0]]
b_attr = [[0.1, 0.1]]
W_attr = [[0.1, 0.2], [0.1, 0.2]]srn = SRN(2, 2, W_attr=W_attr, U_attr=U_attr, b_attr=b_attr)inputs = torch.tensor([[[1, 0], [0, 2]]], dtype=torch.float32)
hidden_state = srn(inputs)
print("hidden_state", hidden_state)# ====PyTorch框架内置了SRN的API torch.nn.RNN============================================
# 初始化参数并运行
U_attr = [[0.0, 0.1], [0.1, 0.0]]
b_attr = [[0.1, 0.1]]
W_attr = [[0.1, 0.2], [0.1, 0.2]]srn = SRN(2, 2, W_attr=W_attr, U_attr=U_attr, b_attr=b_attr)inputs = torch.tensor([[[1, 0], [0, 2]]], dtype=torch.float32)
hidden_state = srn(inputs)
print("hidden_state", hidden_state)# =====将自己实现的SRN和PyTorch框架内置的RNN返回的结果进行打印展示================================
# 这里创建一个随机数组作为测试数据,数据shape为batch_size x seq_len x input_size
batch_size, seq_len, input_size = 8, 20, 32
inputs = torch.randn([batch_size, seq_len, input_size])# 设置模型的hidden_size
hidden_size = 32
torch_srn = nn.RNN(input_size, hidden_size)
self_srn = SRN(input_size, hidden_size)self_hidden_state = self_srn(inputs)
torch_outputs, torch_hidden_state = torch_srn(inputs)print("self_srn hidden_state: ", self_hidden_state.shape)
print("torch_srn outpus:", torch_outputs.shape)
print("torch_srn hidden_state:", torch_hidden_state.shape)# 这里创建一个随机数组作为测试数据,数据shape为batch_size x seq_len x input_size
batch_size, seq_len, input_size, hidden_size = 2, 5, 10, 10
inputs = torch.randn([batch_size, seq_len, input_size])# 设置模型的hidden_sizetorch_srn = nn.RNN(input_size, hidden_size, bias=False)# 获取torch_srn中的参数,并设置相应的paramAttr,用于初始化SRN
W_attr = torch_srn.weight_ih_l0.T
U_attr = torch_srn.weight_hh_l0.T
self_srn = SRN(input_size, hidden_size, W_attr=W_attr, U_attr=U_attr)# 进行前向计算,获取隐状态向量,并打印展示
self_hidden_state = self_srn(inputs)
torch_outputs, torch_hidden_state = torch_srn(inputs)
print("torch SRN:\n", torch_hidden_state.detach().numpy().squeeze(0))
print("self SRN:\n", self_hidden_state.detach().numpy())# ======两者时间差异===========================================================
import time# 这里创建一个随机数组作为测试数据,数据shape为batch_size x seq_len x input_size
batch_size, seq_len, input_size, hidden_size = 2, 5, 10, 10
inputs = torch.randn([batch_size, seq_len, input_size])# 实例化模型
self_srn = SRN(input_size, hidden_size)
torch_srn = nn.RNN(input_size, hidden_size)# 计算自己实现的SRN运算速度
model_time = 0
for i in range(100):strat_time = time.time()out = self_srn(inputs)if i < 10:continueend_time = time.time()model_time += (end_time - strat_time)
avg_model_time = model_time / 90
print('self_srn speed:', avg_model_time, 's')# 计算torch内置的SRN运算速度
model_time = 0
for i in range(100):strat_time = time.time()out = torch_srn(inputs)# 预热10次运算,不计入最终速度统计if i < 10:continueend_time = time.time()model_time += (end_time - strat_time)
avg_model_time = model_time / 90
print('torch_srn speed:', avg_model_time, 's')# ======模型汇总======================================================
# 基于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 np
from nndl_3 import Accuracy, RunnerV3# 训练轮次
num_epochs = 500
# 学习率
lr = 0.0001
# 输入数字的类别数
num_digits = 10
# 将数字映射为向量的维度
input_size = 32
# 隐状态向量的维度
hidden_size = 32
# 预测数字的类别数
num_classes = 19
# 批大小
batch_size = 8
# 模型保存目录
save_dir = "./checkpoints"# 通过指定length进行不同长度数据的实验
def train(length):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.Adam(lr=lr, params=model.parameters())# 定义评价指标metric = Accuracy()# 定义损失函数loss_fn = nn.CrossEntropyLoss()# 基于以上组件,实例化Runnerrunner = RunnerV3(model, optimizer, loss_fn, metric)# 进行模型训练model_save_path = os.path.join(save_dir, f"best_srn_model_{length}.pdparams")runner.train(train_loader, dev_loader, num_epochs=num_epochs, eval_steps=100, log_steps=100,save_path=model_save_path)return runnersrn_runners = {}# 多组训练
lengths = [10, 15, 20, 25, 30, 35]
for length in lengths:runner = train(length)srn_runners[length] = runner# =======损失函数绘制===================================================
import matplotlib.pyplot as plt
def plot_training_loss(runner, fig_name, sample_step):plt.figure()train_items = runner.train_step_losses[::sample_step]train_steps = [x[0] for x in train_items]train_losses = [x[1] for x in train_items]plt.plot(train_steps, train_losses, color='#e4007f', label="Train loss")dev_steps = [x[0] for x in runner.dev_losses]dev_losses = [x[1] for x in runner.dev_losses]plt.plot(dev_steps, dev_losses, color='#f19ec2', linestyle='--', label="Dev loss")# 绘制坐标轴和图例plt.ylabel("loss", fontsize='large')plt.xlabel("step", fontsize='large')plt.legend(loc='upper right', fontsize='x-large')plt.savefig(fig_name)plt.show()# 画出训练过程中的损失图
for length in lengths:runner = srn_runners[length]fig_name = f"./images/6.6_{length}.pdf"plot_training_loss(runner, fig_name, sample_step=100)# ======模型评价===========================================
srn_dev_scores = []
srn_test_scores = []
for length in lengths:print(f"Evaluate SRN with data length {length}.")runner = srn_runners[length]# 加载训练过程中效果最好的模型model_path = os.path.join(save_dir, f"best_srn_model_{length}.pdparams")runner.load_model(model_path)# 加载长度为length的数据data_path = f"./datasets/{length}"train_examples, dev_examples, test_examples = load_data(data_path)test_set = DigitSumDataset(test_examples)test_loader = DataLoader(test_set, batch_size=batch_size)# 使用测试集评价模型,获取测试集上的预测准确率score, _ = runner.evaluate(test_loader)srn_test_scores.append(score)srn_dev_scores.append(max(runner.dev_scores))for length, dev_score, test_score in zip(lengths, srn_dev_scores, srn_test_scores):print(f"[SRN] length:{length}, dev_score: {dev_score}, test_score: {test_score: .5f}")# ========将SRN在不同长度的验证集和测试集数据上的表现,绘制成图片进行观察=================================
import matplotlib.pyplot as pltplt.plot(lengths, srn_dev_scores, '-o', color='#e4007f', label="Dev Accuracy")
plt.plot(lengths, srn_test_scores, '-o', color='#f19ec2', label="Test Accuracy")# 绘制坐标轴和图例
plt.ylabel("accuracy", fontsize='large')
plt.xlabel("sequence length", fontsize='large')
plt.legend(loc='upper right', fontsize='x-large')fig_name = "./images/6.7.pdf"
plt.savefig(fig_name)
plt.show()
nndl_3:
import torch
from matplotlib import pyplot as plt
from torch import nnclass Op(object):def __init__(self):passdef __call__(self, inputs):return self.forward(inputs)def forward(self, inputs):raise NotImplementedErrordef backward(self, inputs):raise NotImplementedError# 实现一个两层前馈神经网络
class Model_MLP_L2_V3(torch.nn.Module):def __init__(self, input_size, hidden_size, output_size):super(Model_MLP_L2_V3, self).__init__()self.fc1 = torch.nn.Linear(input_size, hidden_size)w_ = torch.normal(0, 0.01, size=(hidden_size, input_size), requires_grad=True)self.fc1.weight = torch.nn.Parameter(w_)self.fc1.bias = torch.nn.init.constant_(self.fc1.bias, val=1.0)self.fc2 = torch.nn.Linear(hidden_size, output_size)w2 = torch.normal(0, 0.01, size=(output_size, hidden_size), requires_grad=True)self.fc2.weight = nn.Parameter(w2)self.fc2.bias = torch.nn.init.constant_(self.fc2.bias, val=1.0)self.act = torch.sigmoiddef forward(self, inputs):outputs = self.fc1(inputs)outputs = self.act(outputs)outputs = self.fc2(outputs)return outputsclass 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).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):model_state_dict = torch.load(model_path)self.model.load_state_dict(model_state_dict)class Accuracy():def __init__(self, is_logist=True):# 用于统计正确的样本个数self.num_correct = 0# 用于统计样本的总数self.num_count = 0self.is_logist = is_logistdef update(self, outputs, labels):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"# 可视化
def plot(runner, fig_name):plt.figure(figsize=(10, 5))plt.subplot(1, 2, 1)train_items = runner.train_step_losses[::30]train_steps = [x[0] for x in train_items]train_losses = [x[1] for x in train_items]plt.plot(train_steps, train_losses, color='#8E004D', label="Train loss")if runner.dev_losses[0][0] != -1:dev_steps = [x[0] for x in runner.dev_losses]dev_losses = [x[1] for x in runner.dev_losses]plt.plot(dev_steps, dev_losses, color='#E20079', linestyle='--', label="Dev loss")# 绘制坐标轴和图例plt.ylabel("loss", fontsize='x-large')plt.xlabel("step", fontsize='x-large')plt.legend(loc='upper right', fontsize='x-large')plt.subplot(1, 2, 2)# 绘制评价准确率变化曲线if runner.dev_losses[0][0] != -1:plt.plot(dev_steps, runner.dev_scores,color='#E20079', linestyle="--", label="Dev accuracy")else:plt.plot(list(range(len(runner.dev_scores))), runner.dev_scores,color='#E20079', linestyle="--", label="Dev accuracy")# 绘制坐标轴和图例plt.ylabel("score", fontsize='x-large')plt.xlabel("step", fontsize='x-large')plt.legend(loc='lower right', fontsize='x-large')plt.savefig(fig_name)plt.show()
补充梯度截断
(写到下一个实验才发现这里忘记写了)
1.梯度打印函数
代码如下:
import torch
from torch import nn# ====梯度打印函数======================================
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)
2.复现梯度爆炸现象
为了更好地复现梯度爆炸问题,使用SGD优化器将批大小和学习率调大,学习率为0.2,同时在计算交叉熵损失时,将reduction设置为sum,表示将损失进行累加。
代码如下:
# ======复现梯度爆炸现象===================================================
import os
import random
import torch
import numpy as np
from nndl_3 import Accuracy, RunnerV3
from RNNandLSTM import DigitSumDataset, Model_RNN4SeqClass, SRN, load_data
from torch.utils.data import Dataset, DataLoadernp.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)
3.把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], "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.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)# 加载训练过程中效果最好的模型
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}")
运行结果如下:
4.使用梯度截断解决
只需要在RunnerV3的优化器参数更新之前加一行即可:
nn.utils.clip_grad_norm_(parameters=model.parameters(), max_norm=20, norm_type=2)
其余代码不变
运行结果:
这次的分享就到这里了,下次再见~
相关文章:
深度学习实验十四 循环神经网络(1)——测试简单循环网络的记忆能力和梯度爆炸实验
目录 一、数据集构建 1.1数据集的构建函数 1.2加载数据集并划分 1.3 构建Dataset类 二、模型构建 2.1嵌入层 2.2SRN层 2.3模型汇总 三、模型训练 3.1 训练指定长度的数字预测模型 3.2 损失曲线展示 四、模型评价 五、修改 附完整可运行代码 实验大体步骤&#x…...
AWS re:Invent 发布新的数据库产品 Aurora DSQL; NineData SQL编程大赛开始; 腾讯云支持PostgreSQL 17
重要更新 1. AWS re:Invent 发布新的数据库产品 Aurora DSQL ,提供了跨区域、强一致、多区域读写的能力,同时具备99.999%(多区域部署)的可用性,兼容PostgreSQL;同时发布的还有 DynamoDB 也提供类似的跨区域…...
STM32 OLED屏幕驱动详解
一、介绍 OLED是有机发光二极管,又称为有机电激光显示(Organic Electroluminescence Display, OLED)。OLED由于同时具备自发光,不需背光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广…...
Python字符串常用操作
Python字符串常用操作 一、字符串的切片 1.1、通过下标及下标范围取值 my_str myNameIsTaichi value1 my_str[2] # 正向 N value2 my_str[-5] # 反向 从 -1 开始 a字符串分割,语法:string[end: step] start:头下标,以0开…...
Redis 生产问题(重要)
缓存穿透 什么是缓存穿透? 缓存穿透说简单点就是大量请求的 key 是不合理的,根本不存在于缓存中,也不存在于数据库中 。这就导致这些请求直接到了数据库上,根本没有经过缓存这一层,对数据库造成了巨大的压力…...
前端 —— Git
Git安装 下载安装包 【免费】前端前置-Git安装包资源-CSDN文库 安装 ‘git‘不是内部或外部命令及Git 的保姆级安装教程(保姆级教程)_git不是内部或外部命令-CSDN博客 vscode添加gitbash终端 setting.json "terminal.integrated.profiles.win…...
【GL006】Linux 之 shell
目录 一、shell 指令 1.1 体验shell指令 1.2 命令格式 1.3 shell中的通配符 1.4 输入输出重定向 1.5 命令置换 1.6 基本系统维护命令 1.7 Linux的进程管理命令 1.8 文件系统相关命令 1.9 Linux网络配置管理 二、shell 编程 2.1 shell 脚本的基础知识 2.2 shell 变…...
JS听到了强运的回响
正则表达式 介绍 正则表达式是用于匹配字符串中字符组合的模式,在JS中,正则表达式也是对象 通常用来查找,替换那些符合正则表达式的文本 就是筛选出符合条件的一类人 比如说 有人喜欢玩艾斯爱慕,那他喜欢的就是这一类人&…...
Linux下MySQL的简单使用
Linux下MySQL的简单使用 导语MySQL安装与配置 MySQL安装密码设置 MySQL管理 命令 myisamchkmysql其他 常见操作 C语言访问MYSQL 连接例程错误处理使用SQL 总结参考文献 导语 这一章是MySQL的使用,一些常用的MySQL语句属于本科阶段内容,然后是C语言和M…...
.net core使用AutoMapper
AutoMapper 是一个用于 .NET 平台的对象映射工具,它简化了不同对象类型之间的转换过程。在软件开发中,尤其是在分层架构的应用程序里,常常需要在不同的对象模型之间进行数据传递,例如从数据库实体到视图模型、DTO(数据…...
nmap详解
Nmap(Network Mapper)是一个开放源代码的网络探测和安全审核的工具。由于它的功能强大,被广泛应用于网络安全领域。以下是Nmap的一些主要功能及其在实战中的应用举例。 Nmap的主要功能: 端口扫描:检测目标主机上开放…...
CentOS7环境安装php
直接安装 yum -y install php CentOS7默认安装是php5,现在php已有8.3版本 先查看php -v 版本 如果是低版本,可以删除 yum remove php yum remove php-fpm yum remove php-common 一、添加REMI存储库 yum install epel-release yum install -y …...
基于深度学习的猫狗识别系统【深度学习课设】
🏆 作者简介:席万里 ⚡ 个人网站:https://dahua.bloggo.chat/ ✍️ 一名后端开发小趴菜,同时略懂Vue与React前端技术,也了解一点微信小程序开发。 🍻 对计算机充满兴趣,愿意并且希望学习更多的技…...
字体子集化实践探索
最近项目rust生成PDF组件printpdf需要内嵌完整字体导致生成的PDF很大,需要做压缩,但是rust的类库allsorts::subset::subset不支持windows,所以做了一些windows下字体子集化的尝试 方案一:node.js做子集化 fontmin 缺点是也需要集…...
A1017 基于Java+JSP+SQL Server+servlet的二手购物平台的设计与实现
二手购物平台 1.摘要2.开发目的和意义3.系统功能设计4.系统界面截图5.源码获取 1.摘要 摘 要 计算机以及网络技术的飞速发展,网络的应用在全国乃至全球日益普及,随着人们的思想水平和生活水平的提高,网络已经是人们必不可少的一部分。人们的…...
Simdroid-EC:液冷仿真新星,助力新能源汽车电机控制器高效散热
近年来,新能源电动车的销量呈现出快速增长的态势。据统计,2024 年1-10月中国新能源汽车销量达728万辆,同比增长37.8%。 电机控制器在新能源汽车中对于保障动力和安全性能扮演着至关重要的角色,其核心部件IGBT(绝缘栅双…...
C语言——实现并求出两个数的最大公约数
问题描述:求出两个数的最大公约数 //求两个数的最大公约数 #include<stdio.h> #include<stdlib.h> #include<string.h> #include<math.h> #include<time.h>int main() {int a,b;printf("请您输入两个数 a 和 b\n");scanf…...
今天你学C++了吗?——C++中的类与对象(日期类的实现)——实践与知识的碰撞❤
♥♥♥~~~~~~欢迎光临知星小度博客空间~~~~~~♥♥♥ ♥♥♥零星地变得优秀~也能拼凑出星河~♥♥♥ ♥♥♥我们一起努力成为更好的自己~♥♥♥ ♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥ ♥♥♥如果有什么问题可以评论区留言或者私信我哦~♥♥♥ ✨✨✨✨✨✨ 个…...
享元模式的理解和实践
在软件开发中,性能优化是一个永恒的话题。在追求高性能的过程中,减少内存的使用是一项重要的任务。享元模式(Flyweight Pattern)就是一种用于减少内存使用量的设计模式,它特别适用于存在大量重复对象的场景。本文将详细…...
Unreal Engine 中的UI界面开发
推荐的使用方式 轻量级 HUD:使用 Canvas 绘制简单的文本、调试信息或基础 UI(如准星、血量条等)。 复杂 UI:使用 UMG(Unreal Motion Graphics)和 Slate 进行布局和交互,避免手动管理 Canvas 绘制。 避免遮挡场景:仅绘制必要的内容,并利用透明度(如 FLinearColor(1, 1…...
Docker在Ubuntu和CentOS系统下的安装
目录 1. 各版本平台支持情况2. 在Ubuntu系统下安装docker3. 常见报错4. Docker的镜像源修改5. Docker目录修改6. 在CentOS系统下安装docker 1. 各版本平台支持情况 (1)平台支持情况如下: Server 版本 桌面版本 2. 在Ubuntu系统下安装docker…...
EXCEL 关于plot 折线图--频度折线图的一些细节
目录 0 折线图有很多 1 频度折线图 1.1 直接用原始数据做的频度折线图 2 将原始数据生成数据透视表 3 这样可以做出了,频度plot 4 做按某字段汇总,成为累计plot分布 5 修改上面显示效果,做成百分比累计plot频度分布 0 折线图有很多 这…...
Hive操作案例
目录 idea/dg远程连接导入数据建表数据导入 idea/dg远程连接 hive的详细安装不多展示,自行搜索即可。 依次启动zookeeper,hadoop 在zookeeper的节点上启动如下指令(我的是1个主节点和2个备用节点) 启动Hive的metastore࿰…...
C++ 内存管理和模板与STL
此篇目是之后各种C库的基础 目录 内存管理 内存分布 内存管理方式 new和delete operator new 与 operator delete函数 实现原理 定位new表达式(placement-new) 模板基础 泛型编程 模板 函数模板 类模板 STL 组成部分 内存管理 内存分布 int globalVar 1; //全局变量 静…...
JDK8新特性:Stream
JDK8最大的改变: 1. lambda表达式 2. Stream 1. Steam流的入门 什么是Stream? 也叫Stream流,是jdk8开始的一套API,用于操作集合或者数组中的数据 优点: Stream流大量结合了Lambda的语法风格来创建,提…...
前端传入Grule,后端保存到 .grl 文件中
前端传入Grule,后端保存到 .grl 文件中 通过简单的输入框,将Grule的部分拆解成 规则名称 规则描述 规则优先级 规则条件 规则逻辑Grule关键字 when Then 模拟了 if 判断的条件和逻辑部分 类似于 shell 和 ruby 之类的脚本语言,有 then 关键字…...
探索《Crypto Rumble》 游戏:经济模型篇
《Crypto Rumble》是一款基于 Zypher Network 游戏引擎打造的卡牌 RPG三消品类的 Web3 游戏,通过引人入胜的游戏设计以及轻量化的游戏玩法,《Crypto Rumble》不仅能够为玩家带来引人入胜的沉浸式游戏体验,同时基于 AI Bot 的游戏编辑器&#…...
【CSS in Depth 2 精译_072】第 12 章 CSS 排版与间距概述 + 12.1 间距设置(上):究竟该用 em 还是 px
当前内容所在位置(可进入专栏查看其他译好的章节内容) 第四部分 视觉增强技术 ✔️【第 12 章 CSS 排版与间距】 ✔️ 12.1 间距设置 ✔️ 12.1.1 使用 em 还是 px ✔️12.1.2 对行高的深入思考12.1.3 行内元素的间距设置 文章目录 第 12 章 排版与间距…...
Elasticsearch对象映射
Spring Data Elasticsearch对象映射是将Java对象(域实体)映射到存储在Elasticsearchs中的JSON表示形式并返回的过程。内部用于此映射的类是MappingElasticsearchConverter。 元模型对象映射 基于元模型的方法使用域类型信息对Elasticsearch进行读写操作…...
Oracle 19c rac 补丁升级,从19.7 to19.22-集群
1. 补丁包概述 数据库环境 角色 数据库 IP地址 数据库版本 主机名 数据库名称 源端 RAC 172.30.21.166/167 19.7 hfcwdb66/hfcwdb67 hfdb 将以下补丁包上传到/soft下 上传到两个节点的soft目录下:p6880880_190000_Linux-x86-64.zip (更新o…...
免费做图片的网站有哪些/企业官网定制设计
我同意坏孩子的的提法,不管男或女,除了风度也而要讲气质。风度这东西,就像我咱们国产的名牌西装"风度",只要你有钱,你也可以买一件穿上。可是,你穿上後是否潇洒呢?这就看你有没有气质…...
郑州网站seo服务/网站排行
网络管理员在维护系统过程中,经常遇到计算机或服务器意外重新启动,这个时候运行在服务器上的程序会因为重新启动导致程序无法运行,比如摄像头监控程序,等发现服务器等待你输入用户名和密码的时候,监控程序已经好长时间…...
微信公众号做电影网站要域名吗/宁德网站建设制作
背景:前段时间,业务需要,为了快速让解析的Excel入库,所以把不是很确定的字段全部设置成了TEXT。今天需要进行表结构优化,把字段长度控制在合适的范围,并尽量不使用TEXT类型。-- 计算长度select LENGTH(CAST…...
网页传奇排名/seo在线培训机构
从现在起,珍惜时间,好好拼搏。 从现在起,好好对待身边的每一个人,建立好的亲情,友情或是爱情。 从现在起,喂马劈柴,周游世界,学会欣赏,学会做人。 从现在起,学…...
网站文章列表如何排版/全媒体广告代理加盟靠谱吗
【我的Go语言初体验” | 征文活动进行中…】 以写促学,接下来,我将带大家使用 GO 逐步 实现常见的数据结构 欢迎关注【我的Go语言初体验——实现数据结构】系列,持续更新中… 往期文章 我的Go语言初体验——实现数据结构之【数组 切片 Map】(…...
网软志成个人商城网站/济南竞价托管
Dreamweaver CC2018怎么写div自适应页面布局?一般网页都是固定的宽度,所以不能自适应网页窗口大小。我们可以把宽度设置为百分比,这样就可以啦。只不过需要自己算好百分比的数值,接下来小编就给大家详细的介绍一下Dreamweaver CC2…...