实验14 RNN的记忆能力和梯度爆炸实验
一 循环神经网络的记忆能力
1.数据集构建
创建了一个DigitSumDataset 类,包括初始化函数init、数据生成函数 generate_data、数据加载函数 load_data、__len__ 方法、__getitem__ 方法。
init函数:接受的参数是data_path( 存放数据集的目录,用于加载数据)、length(数据序列的长度)、k( 数据增强的数量)、mode(决定生成训练集、验证集还是测试集),同时在init函数中初始化这些变量,然后是一个选择函数,如果传入了data_path,就调用load_data从文件中加载数据,否则调用generate_data生成数据集。
generate_data:通过两层循环生成基础样本,然后通过数据增强增加样本多样性
load_data:从文件中加载数据
代码:
import os
import random
import numpy as np
import torch
from matplotlib import pyplot as plt
from torch import optim
from torch.utils.data import Dataset, DataLoader#数据集构建
# 固定随机种子
random.seed(0)
np.random.seed(0)
class DigitSumDataset(Dataset):def __init__(self, data_path=None, length=None, k=None, mode='train'):"""初始化数据集如果传入了data_path,则从文件加载数据;如果传入了length和k,则生成数据集。参数:data_path: 存放数据集的目录,用于加载数据length: 数据序列的长度(仅用于生成数据集时)k: 数据增强的数量(仅用于生成数据集时)mode: 'train'/'dev'/'test',决定生成训练集、验证集还是测试集(仅用于生成数据集时)"""self.data_path = data_pathself.length = lengthself.k = kself.mode = modeif data_path: # 从文件加载数据self.examples = self.load_data(data_path)else: # 生成数据if length < 3 or k <= 0:raise ValueError("The length of data should be greater than 2 and k should be greater than 0.")self.examples = self.generate_data()def generate_data(self):"""生成数据:生成指定长度的数字序列,并进行数据增强"""base_examples = []for n1 in range(0, 10):for n2 in range(0, 10):seq = [n1, n2] + [0] * (self.length - 2)label = n1 + n2base_examples.append((seq, label))examples = []for base_example in base_examples:for _ in range(self.k):idx = np.random.randint(2, self.length)val = np.random.randint(0, 10)seq = base_example[0].copy()label = base_example[1]seq[idx] = valexamples.append((seq, label))return examplesdef load_data(self, data_path):"""从文件加载数据"""def _load_file(file_path):examples = []with open(file_path, "r", encoding="utf-8") as f:for line in f.readlines():items = line.strip().split("\t")seq = [int(i) for i in items[0].split(" ")]label = int(items[1])examples.append((seq, label))return examples# 加载训练集、验证集、测试集train_examples = _load_file(os.path.join(data_path, "train.txt"))dev_examples = _load_file(os.path.join(data_path, "dev.txt"))test_examples = _load_file(os.path.join(data_path, "test.txt"))return train_examples if self.mode == 'train' else dev_examples if self.mode == 'dev' else test_examplesdef __len__(self):return len(self.examples)def __getitem__(self, idx):seq, label = self.examples[idx]seq_tensor = torch.tensor(seq, dtype=torch.long)label_tensor = torch.tensor(label, dtype=torch.long)return seq_tensor, label_tensor
2.模型构建
定义了一个嵌入层(将输入的数字序列进行向量化,即将每个数字映射为向量)和SRN层,然后定义了定义了一个实现数字预测的模型 Model_RNN4SeqClass,里面用到了嵌入层和SRN层。
①Embedding:函数会接收两个参数,分别是数据数据的总数量和嵌入向量的维度。实现了一个简单的嵌入层,用于将输入的数字索引映射为对应的嵌入向量。
②SRN层:里面有三个函数,分别是init初始化函数,init_state初始化隐状态函数,forward函数,循环执行RNN,最终返回一个最后的隐向量。接下来详细解释一下内部实现:
init函数:接受输入的维度和隐状态的维度,并且输入的维度和隐状态的维度,这两个维度关乎到模型参数W,U,b的大小。因为输入权重W的大小是input_size*hidden*size,隐层权重U的大小是hidden_size*hidden_size,偏置b的大小是1*hidden_size。所以直接定义这样大小的全零矩阵就可以。
init_state函数:初始化隐状态向量,定义一个batch_size*hidden_size大小的全零矩阵。
forward函数:首选利用shape获取输入的形状为batch_size*seq_len*input_size。然后判断一下初始的隐状态是不是没有提供,如果没有提供,就利用init_state函数初始化一个隐状态。然后就是循环操作,循环的是每个序列,例如对于长度是5的数据来说,seq_len就是5,我们要遍历seq_len,依次执行各个序列。循环中,每个当前时刻的输入step_input数据的形状为batch_size*input*size,利用输入乘以权重W,隐层乘以权重U加上偏置b,最后把结果利用tanh函数激活,得到当前时刻的隐状态。
③Model_RNN4SeqClass:包括一个init函数和forward函数
init函数接受的初始参数分别是模型model、词典大小num_digits(嵌入函数需要用到)、输入维度input_size,隐层维度hidden_size,类别数num_classes。
然后开始定义变量:定义模型、词典大小、嵌入向量维度,定义embedding层,定义线性层(输入维度是隐层维度,输出维度是分类数),输出分类的结果。
forward函数:首先利用嵌入函数将数字序列映射为向量,然后调用RNN模型得到最后的隐层状态,最后利用linear函数,得到在每个类别上的logits
代码:
# 嵌入层
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 embs# 简单循环网络(SRN)
class SRN(nn.Module):def __init__(self, input_size, hidden_size):super(SRN, self).__init__()# 嵌入向量的维度self.input_size = input_size# 隐状态的维度self.hidden_size = hidden_size# 定义模型参数W,其shape为 input_size x hidden_sizeself.W = nn.Parameter(torch.zeros(input_size, hidden_size,dtype=torch.float32))# 定义模型参数U,其shape为 hidden_size x hidden_sizeself.U = nn.Parameter(torch.zeros(hidden_size, hidden_size,dtype=torch.float32))# 定义模型参数b,其shape为 1 x hidden_sizeself.b = nn.Parameter(torch.zeros(1, hidden_size,dtype=torch.float32))def init_state(self, batch_size):hidden = torch.zeros(batch_size, self.hidden_size,dtype=torch.float32)# 初始化隐状态向量return hiddendef forward(self, inputs, hidden_state=None):"""inputs: 输入数据,形状为 (batch_size, seq_len, input_size)hidden_state: 初始化隐状态,形状为 (batch_size, hidden_size)"""batch_size, seq_len, input_size = inputs.shape# 初始化起始状态的隐向量,如果没有提供if hidden_state is None:hidden_state = self.init_state(batch_size)# 循环执行RNN计算for step in range(seq_len):# 获取当前时刻的输入数据step_input,形状为 (batch_size, input_size)step_input = inputs[:, step, :]# 计算当前时刻的隐状态,使用tanh激活函数hidden_state = torch.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
3.模型训练
首先创建runner类,然后遍历不同的序列长度,生成数据集,定义模型、损失函数、优化器、实例化runner类,调用train函数开始训练,观察模型在训练集和验证集上面的损失变化。
Runner类还是使用之前的runner类,里面包括train函数,负责遍历训练集得到损失并反向优化参数;evaluate函数用于计算模型在验证集上的损失,并根据在验证集上的损失保存最优模型,不需要优化参数;test函数用于计算测试集在最优模型上的准确率。
定义lengths = [5, 10, 15, 20, 25, 30, 35],遍历这个序列,对于每个length
利用DigitSumDataset函数分别生成训练集、验证集、测试集,然后定义实例化模型需要用到的输入维度、隐层维度、输入数字的类别数和预测数字的类别数。实例化SRN模型得到base_model,然后实例化模型Model_RNN4SeqClass,定义损失函数为交叉熵损失、优化器为Adam,然后实例化runner类,调用train函数开始训练。
代码:
#模型训练
class Runner:def __init__(self, model, train_loader, val_loader, test_loader, criterion, optimizer):self.model = modelself.train_loader = train_loaderself.val_loader = val_loaderself.test_loader = test_loaderself.criterion = criterionself.optimizer = optimizerself.best_model = Noneself.best_val_loss = float('inf')self.train_losses = [] # 存储训练损失self.val_losses = [] # 存储验证损失def train(self, epochs):for epoch in range(epochs):self.model.train()running_loss = 0.0for inputs, labels in self.train_loader:self.optimizer.zero_grad()outputs = self.model(inputs)loss = self.criterion(outputs, labels)loss.backward()self.optimizer.step()running_loss += loss.item()# 计算平均训练损失train_loss = running_loss / len(self.train_loader)self.train_losses.append(train_loss)# 计算验证集上的损失val_loss = self.evaluate()self.val_losses.append(val_loss)print(f'Epoch [{epoch + 1}/{epochs}], Train Loss: {train_loss:.4f}, Val Loss: {val_loss:.4f}')# 如果验证集上的损失最小,保存模型if val_loss < self.best_val_loss:self.best_val_loss = val_lossself.best_model = self.model.state_dict()plt.figure(figsize=(10, 6))plt.plot(self.train_losses, label='Train Loss')plt.plot(self.val_losses, label='Validation Loss')plt.xlabel('Epoch')plt.ylabel('Loss')plt.title('Loss Curve')plt.legend()plt.grid()plt.show()def evaluate(self):self.model.eval()val_loss = 0.0with torch.no_grad():for inputs, labels in self.val_loader:outputs = self.model(inputs)loss = self.criterion(outputs, labels)val_loss += loss.item()return val_loss / len(self.val_loader)def test(self):self.model.load_state_dict(self.best_model)self.model.eval()correct = 0total = 0with torch.no_grad():for inputs, labels in self.test_loader:outputs = self.model(inputs)_, predicted = torch.max(outputs, 1)total += labels.size(0)correct += (predicted == labels).sum().item()test_accuracy = correct / totalprint(f'Test Accuracy: {test_accuracy:.4f}')def predict(self, image):self.model.eval()with torch.no_grad():output = self.model(image)_, predicted = torch.max(output, 1)return predicted.item()lengths = [5, 10, 15, 20, 25, 30, 35]
# lengths = [5]
k_train = 3 # 训练集数据增强的数量
k_test_val = 1 # 验证集和测试集数据增强的数量# 循环不同长度的序列
for length in lengths:# 生成训练集train_dataset = DigitSumDataset(length=length, k=k_train, mode='train')train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)# 生成验证集dev_dataset = DigitSumDataset(length=length, k=k_test_val, mode='dev')dev_loader = DataLoader(dev_dataset, batch_size=64, shuffle=False)# 生成测试集test_dataset = DigitSumDataset(length=length, k=k_test_val, mode='test')test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)# 输入数字的类别数num_digits = 10# 将数字映射为向量的维度input_size = 32# 隐状态向量的维度shidden_size = 32# 预测数字的类别数num_classes = 19base_model = SRN(input_size, hidden_size)model = Model_RNN4SeqClass(base_model, num_digits, input_size, hidden_size, num_classes)# 定义损失函数和优化器criterion = nn.CrossEntropyLoss()optimizer = optim.Adam(model.parameters(), lr=0.001)# 创建Runner实例,并进行训练runner = Runner(model, train_loader, dev_loader, test_loader, criterion, optimizer)print(f"Training model for sequence length {length}...")# 训练并测试模型runner.train(epochs=600) # 训练模型
4.模型评价
调用test函数进行模型测试
代码:
runner.test() # 测试模型
5实验结果
1.序列长度分别是5、10、15 时的loss变化和准确率
从输出结果看,随着数据序列长度的增加,虽然训练集损失逐渐逼近于0,但是验证集损失整体趋向越来越大,这表明当序列变长时,SRN模型保持序列长期依赖能力在逐渐变弱,越来越无法学习到有用的知识.并且,随着序列长度的增加,测试集的准确度整体趋势是降低的,这同样说明SRN模型保持长期依赖的能力在不断降低.
二 梯度爆炸实验
1.梯度打印函数
定义梯度打印函数custom_print_log,接受的参数是runner。
分别初始化W、U、b的梯度L2范数 W_grad_l2、U_grad_l2 和 b_grad_l2 为 0。使用 model.named_parameters() 遍历模型的所有参数,它会返回每个参数的名字(name)和对应的张量(param)。如果变量的名称为rnn_model.W,就获取这个参数的L2范数赋值给W_grad_l2。在函数体外创建了三个列表W_list = []、U_list = []、b_list = [],把每个epoch得到的梯度分别存储到列表中,训练结束后可视化梯度变化。
代码:
W_list = []
U_list = []
b_list = []# 计算梯度范数
def custom_print_log(runner):model = runner.modelW_grad_l2, U_grad_l2, b_grad_l2 = 0, 0, 0# 遍历模型的所有参数for name, param in model.named_parameters():if name == "rnn_model.W": # 针对W参数计算L2范数W_grad_l2 = torch.norm(param.grad, p=2).item() # 获取L2范数elif name == "rnn_model.U": # 针对U参数计算L2范数U_grad_l2 = torch.norm(param.grad, p=2).item() # 获取L2范数elif name == "rnn_model.b": # 针对b参数计算L2范数b_grad_l2 = torch.norm(param.grad, p=2).item() # 获取L2范数# 打印梯度L2范数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,调用train函数训练,观察W、U、b梯度变化。
代码:
from random import randomimport numpy as np
import torch
from matplotlib import pyplot as plt
from torch import nn, optim
from torch.utils.data import DataLoader
import os
import random
import numpy as np
import torch
from matplotlib import pyplot as plt
from torch import optim
from torch.utils.data import Dataset, DataLoader#数据集构建
# 固定随机种子
random.seed(0)
np.random.seed(0)
class DigitSumDataset(Dataset):def __init__(self, data_path=None, length=None, k=None, mode='train'):"""初始化数据集如果传入了data_path,则从文件加载数据;如果传入了length和k,则生成数据集。参数:data_path: 存放数据集的目录,用于加载数据length: 数据序列的长度(仅用于生成数据集时)k: 数据增强的数量(仅用于生成数据集时)mode: 'train'/'dev'/'test',决定生成训练集、验证集还是测试集(仅用于生成数据集时)"""self.data_path = data_pathself.length = lengthself.k = kself.mode = modeif data_path: # 从文件加载数据self.examples = self.load_data(data_path)else: # 生成数据if length < 3 or k <= 0:raise ValueError("The length of data should be greater than 2 and k should be greater than 0.")self.examples = self.generate_data()def generate_data(self):"""生成数据:生成指定长度的数字序列,并进行数据增强"""base_examples = []for n1 in range(0, 10):for n2 in range(0, 10):seq = [n1, n2] + [0] * (self.length - 2)label = n1 + n2base_examples.append((seq, label))examples = []for base_example in base_examples:for _ in range(self.k):idx = np.random.randint(2, self.length)val = np.random.randint(0, 10)seq = base_example[0].copy()label = base_example[1]seq[idx] = valexamples.append((seq, label))return examplesdef load_data(self, data_path):"""从文件加载数据"""def _load_file(file_path):examples = []with open(file_path, "r", encoding="utf-8") as f:for line in f.readlines():items = line.strip().split("\t")seq = [int(i) for i in items[0].split(" ")]label = int(items[1])examples.append((seq, label))return examples# 加载训练集、验证集、测试集train_examples = _load_file(os.path.join(data_path, "train.txt"))dev_examples = _load_file(os.path.join(data_path, "dev.txt"))test_examples = _load_file(os.path.join(data_path, "test.txt"))return train_examples if self.mode == 'train' else dev_examples if self.mode == 'dev' else test_examplesdef __len__(self):return len(self.examples)def __getitem__(self, idx):seq, label = self.examples[idx]seq_tensor = torch.tensor(seq, dtype=torch.long)label_tensor = torch.tensor(label, dtype=torch.long)return seq_tensor, label_tensor# 设定数据集的路径和生成参数
# lengths = [5, 10, 15, 20, 25, 30, 35]
lengths = [5]
k_train = 3 # 训练集数据增强的数量
k_test_val = 1 # 验证集和测试集数据增强的数量#总的模型
import torch
import torch.nn as nn
import torch.nn.functional as F# 嵌入层
class Embedding(nn.Module):def __init__(self, num_embeddings, embedding_dim):super(Embedding, self).__init__()# 定义嵌入层self.embedding = nn.Embedding(num_embeddings, embedding_dim)def forward(self, inputs):# 根据索引获取对应的嵌入向量return self.embedding(inputs)# 简单循环网络(SRN)
class SRN(nn.Module):def __init__(self, input_size, hidden_size):super(SRN, self).__init__()# 嵌入向量的维度self.input_size = input_size# 隐状态的维度self.hidden_size = hidden_size# 定义模型参数W,其shape为 input_size x hidden_sizeself.W = nn.Parameter(torch.randn(input_size, hidden_size))# 定义模型参数U,其shape为 hidden_size x hidden_sizeself.U = nn.Parameter(torch.randn(hidden_size, hidden_size))# 定义模型参数b,其shape为 1 x hidden_sizeself.b = nn.Parameter(torch.randn(1, hidden_size))def init_state(self, batch_size):# 初始化隐状态向量return torch.zeros(batch_size, self.hidden_size)def forward(self, inputs, hidden_state=None):"""inputs: 输入数据,形状为 (batch_size, seq_len, input_size)hidden_state: 初始化隐状态,形状为 (batch_size, hidden_size)"""batch_size, seq_len, input_size = inputs.shape# 初始化起始状态的隐向量,如果没有提供if hidden_state is None:hidden_state = self.init_state(batch_size)# 循环执行RNN计算for step in range(seq_len):# 获取当前时刻的输入数据step_input,形状为 (batch_size, input_size)step_input = inputs[:, step, :]# 计算当前时刻的隐状态,使用tanh激活函数hidden_state = torch.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 logitsW_list = []
U_list = []
b_list = []# 计算梯度范数
def custom_print_log(runner):model = runner.modelW_grad_l2, U_grad_l2, b_grad_l2 = 0, 0, 0# 遍历模型的所有参数for name, param in model.named_parameters():if name == "rnn_model.W": # 针对W参数计算L2范数W_grad_l2 = torch.norm(param.grad, p=2).item() # 获取L2范数elif name == "rnn_model.U": # 针对U参数计算L2范数U_grad_l2 = torch.norm(param.grad, p=2).item() # 获取L2范数elif name == "rnn_model.b": # 针对b参数计算L2范数b_grad_l2 = torch.norm(param.grad, p=2).item() # 获取L2范数# 打印梯度L2范数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)
#模型训练
class Runner:def __init__(self, model, train_loader, val_loader, test_loader, criterion, optimizer):self.model = modelself.train_loader = train_loaderself.val_loader = val_loaderself.test_loader = test_loaderself.criterion = criterionself.optimizer = optimizerself.best_model = Noneself.best_val_loss = float('inf')self.train_losses = [] # 存储训练损失self.val_losses = [] # 存储验证损失def train(self, epochs):for epoch in range(epochs):self.model.train()running_loss = 0.0for inputs, labels in self.train_loader:self.optimizer.zero_grad()outputs = self.model(inputs)loss = self.criterion(outputs, labels)loss.backward()self.optimizer.step()running_loss += loss.item()# 计算平均训练损失train_loss = running_loss / len(self.train_loader)self.train_losses.append(train_loss)# 计算验证集上的损失val_loss = self.evaluate()self.val_losses.append(val_loss)print(f'Epoch [{epoch + 1}/{epochs}], Train Loss: {train_loss:.4f}, Val Loss: {val_loss:.4f}')# 调用自定义函数来打印和记录梯度范数custom_print_log(self)# 如果验证集上的损失最小,保存模型if val_loss < self.best_val_loss:self.best_val_loss = val_lossself.best_model = self.model.state_dict()plt.figure(figsize=(10, 6))plt.plot(self.train_losses, label='Train Loss')plt.plot(self.val_losses, label='Validation Loss')plt.xlabel('Epoch')plt.ylabel('Loss')plt.title('Loss Curve')plt.legend()plt.grid()plt.show()# 可视化梯度L2范数plt.figure(figsize=(10, 6))plt.plot(W_list, label='W Gradient L2 Norm')plt.plot(U_list, label='U Gradient L2 Norm')plt.plot(b_list, label='b Gradient L2 Norm')plt.xlabel('Epoch')plt.ylabel('L2 Norm')plt.title('Gradient L2 Norms Across Epochs')plt.legend()plt.grid()plt.show()def evaluate(self):self.model.eval()val_loss = 0.0with torch.no_grad():for inputs, labels in self.val_loader:outputs = self.model(inputs)loss = self.criterion(outputs, labels)val_loss += loss.item()return val_loss / len(self.val_loader)def test(self):self.model.load_state_dict(self.best_model)self.model.eval()correct = 0total = 0with torch.no_grad():for inputs, labels in self.test_loader:outputs = self.model(inputs)_, predicted = torch.max(outputs, 1)total += labels.size(0)correct += (predicted == labels).sum().item()test_accuracy = correct / totalprint(f'Test Accuracy: {test_accuracy:.4f}')def predict(self, image):self.model.eval()with torch.no_grad():output = self.model(image)_, predicted = torch.max(output, 1)return predicted.item()length=5
# 生成训练集
train_dataset = DigitSumDataset(length=length, k=k_train, mode='train')
train_loader = DataLoader(train_dataset, batch_size=8, shuffle=True)
# 生成验证集
dev_dataset = DigitSumDataset(length=length, k=k_test_val, mode='dev')
dev_loader = DataLoader(dev_dataset, batch_size=8, shuffle=False)
# 生成测试集
test_dataset = DigitSumDataset(length=length, k=k_test_val, mode='test')
test_loader = DataLoader(test_dataset, batch_size=8, shuffle=False)
# 输入数字的类别数
num_digits = 10
# 将数字映射为向量的维度
input_size = 32
# 隐状态向量的维度s
hidden_size = 32
# 预测数字的类别数
num_classes = 19
base_model = SRN(input_size, hidden_size)
model = Model_RNN4SeqClass(base_model, num_digits, input_size, hidden_size, num_classes)
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.2, momentum=0.9)
# 创建Runner实例,并进行训练
runner = Runner(model, train_loader, dev_loader, test_loader, criterion, optimizer)
print(f"Training model for sequence length {length}...")# 训练并测试模型
runner.train(epochs=600) # 训练模型
runner.test() # 测试模型print(f"Finished training for length {length}.\n")
3.使用梯度截断解决梯度爆炸问题
在参数更新前面加上一行代码:
nn.utils.clip_grad_norm_(parameters=self.model.parameters(),max_norm=5, norm_type=1),用于控制梯度范数,防止梯度爆炸。
4.实验结果
1.梯度爆炸现象
通过观察发现,梯度范数急剧变大,而后梯度范数几乎为0. 这是因为Tanh为Sigmoid型函数,其饱和区的导数接近于0,由于梯度的急剧变化,参数数值变的较大或较小,容易落入梯度饱和区,导致梯度为0,模型很难继续训练.
在最优模型上计算准确率发现只有0.01
2.使用梯度截断法解决梯度爆炸
引入按模截断的策略之后,模型训练时参数梯度的变化情况。可以看到,随着迭代步骤的进行,梯度始终保持在一个有值的状态,表明按模截断能够很好地解决梯度爆炸的问题.
由于为复现梯度爆炸现象,改变了学习率,优化器等,因此准确率相对比较低。但由于采用梯度截断策略后,在后续训练过程中,模型参数能够被更新优化,因此准确率有一定的提升。
三总结和心得体会
解释词向量、词嵌入
词向量是用固定维度的 实数向量 来表示一个词语。它通过对语言中词语之间的语义相似度进行建模来生成这些向量。词向量通常是一个低维稠密的向量,相较于传统的稀疏表示(如独热编码),它能更有效地捕捉词语的语义信息。例如,假设有一个词语“dog”,其对应的词向量可能是:[0.12, -0.34, 0.65, -0.22, ...]。词向量的生成方式通常是基于上下文信息,例如通过共现统计(词与词之间的共现频率)或者神经网络模型(如Word2Vec, GloVe等)来训练这些词向量。
词向量的特点:
①低维稠密表示:相比传统的稀疏表示(如独热编码),词向量可以有效地表示词语之间的相似性。
②向量之间的运算:词向量可以进行加法、减法等数学操作,这种方式使得词向量能够在向量空间中进行运算。例如,“king” - “man” + “woman” ≈ “queen”。
词嵌入是通过 神经网络模型(如 Word2Vec、GloVe、FastText 等)将词语映射到一个 连续的向量空间 中。这些词嵌入不仅可以通过共现频率学习,也能在不同的上下文中不断更新,学习到更多语义信息。换句话说,词嵌入是通过一种方法或算法将词语映射到一个低维的稠密向量空间,这些词嵌入的向量不仅包含了每个词的单一表示,还能捕捉到词与词之间的关系。
常见的词嵌入方法有:
①Word2Vec:通过 Skip-gram 或 CBOW 模型训练词向量,能够通过上下文信息捕捉词语之间的关系。
②GloVe(Global Vectors for Word Representation):基于词语共现矩阵的全局统计信息,使用矩阵分解技术来训练词向量。
③FastText:比 Word2Vec 更进一步,能够处理词内子词信息,尤其适用于处理罕见词。
在RNN的记忆能力的实验中,新东西就是创建基于RNN实现数字预测的模型,在这个模型中先把输入进行嵌入,然后调用srn模型得到最后一个状态的隐向量,最后利用linear函数将其映射为在每个类别上的数值。通过在不同长度的序列上观察模型的准确率,发现序列越长,准确率越低,就相当于隔得时间越长,人的记忆就会越来越模糊。
在梯度爆炸实验中,学习率设置一个比较大的数,使用SGD优化器(SGD是一种非常基础的优化算法,它的更新步骤没有任何的约束或调整机制。在每次更新时,学习率是固定的,且没有任何机制来控制梯度的大小。如果学习率过大,SGD很容易因为参数更新过大而导致梯度爆炸。)编写了一个记录每个epoch上W、U、b梯度的函数,在每个epoch中收集参数的梯度,发现训练了几次,参数的梯度都变成了0,发生了梯度爆炸现象。
相关文章:
实验14 RNN的记忆能力和梯度爆炸实验
一 循环神经网络的记忆能力 1.数据集构建 创建了一个DigitSumDataset 类,包括初始化函数init、数据生成函数 generate_data、数据加载函数 load_data、__len__ 方法、__getitem__ 方法。 init函数:接受的参数是data_path( 存放数据集的目录…...
【电子通识】电流倒灌为什么需要注意?
电流倒灌是一个很常见的问题,以“IO电流倒灌”为关键词在百度上进行搜索,可以找到很多相关案例。 电流倒灌问题在5V电平的单片机时代几乎不会发生,主要是因为5V单片的IO耐压值高,单片机内部结构对IO保护设计很好。 到了3.3V单片机时代,这类问题有一定的偶发性,但…...
Elasticsearch 集群部署
Elasticsearch 是一个分布式的搜索和分析引擎,广泛应用于日志分析、全文搜索、实时数据分析等场景。它以其高性能、高可用性和易用性而著称。本文档将引导您完成一个基本的 Elasticsearch 集群配置,包括节点间的通信、客户端访问、安全设置等关键步骤。我…...
Windows系统VSCode 搭建ESP-IDF环境
VS Code,安装ESP-IDF插件 快捷键CTRLSHIFTP,弹出显示所有命令的窗口,选择ESP-IDF的欢迎 使用第一个选项,要选择一个ESP-IDF版本,选最新的就行 点击Install,等待下载 提示安装成功,如果过程中出现python已存…...
在centos 7.9上面安装mingw交叉编译工具
1.说明 为了在centos上面编译windows的程序,需要安装mingw工具,mingw工具是可以编译windows程序的一些工具链,使用方式和linux一致 2.下载脚本 使用脚本方式编译,github的脚本位置:https://github.com/Zeranoe/ming…...
【Java笔记】LinkedList 底层结构
一、LinkedList 的全面说明 LinkedList底层实现了双向链表和双端队列特点可以添加任意元素(元素可以重复),包括null线程不安全,没有实现同步 二、LinkedList 的底层操作机制 三、LinkedList的增删改查案例 public class LinkedListCRUD { public stati…...
【深入理解java中的设计模式】
深入理解java中的设计模式 设计模式是软件工程中的最佳实践,它们提供了解决特定问题的模板或蓝图。在Java中,设计模式通常被分为三大类:创建型模式、结构型模式和行为型模式。 创建型模式 单例模式 (Singleton Pattern) 描述: 保证一个类…...
详解下c语言下的多维数组和指针数组
在实际c语言编程中,三维及以上数组我们使用的很少,二维数组我们使用得较多。说到数组,又不得关联到指针,因为他们两者的联系太紧密了。今天我们就详细介绍下c语言下的多维数组(主要是介绍二维数组)和指针。 一、二维数组 1.1&am…...
如何使用 Python 发送 HTTP 请求?
在Python中发送HTTP请求最常用的库是requests,它提供了简单易用的API来发送各种类型的HTTP请求。 除此之外,还有标准库中的http.client(以前叫做httplib)和urllib,但它们相对更底层,代码量较大,…...
活动预告 |【Part2】Microsoft Azure 在线技术公开课:基础知识
课程介绍 参加“Azure 在线技术公开课:基础知识”活动,培养有助于创造新的技术可能性的技能并探索基础云概念。参加我们举办的本次免费培训活动,扩充自身的云模型和云服务类型知识。你还可以查看以计算、网络和存储为核心的 Azure 服务。 课…...
神经网络基础-激活函数
文章目录 1. 什么是激活函数2. sigmoid 激活函数3. tanh 激活函数4. ReLU 激活函数5. SoftMax 激活函数6. 其他常见的激活函数7. 激活函数的选择方法 1. 什么是激活函数 激活函数用于对每层的输出数据进行变换, 进而为整个网络注入了非线性因素。此时, 神经网络就可以拟合各种…...
Debedium如何忽略Oracle的purge命令
报错 截至目前3.0版本,Debezium的Oracle Connector并不支持purge table这个指令。 所以,在使用Debezium解析Oracle变更的时候,如果在源端执行了类似 purge table "$BIN… 的语句,就会导致Debezium罢工,日志里显…...
基于 webRTC Vue 的局域网 文件传输工具
文件传输工具,匿名加密,只需访问网页,即可连接到其他设备,基于 webRTC 和 Vue.js coturn TURN 服务器 docker pull coturn/coturn docker run -d --networkhost \-v $(pwd)/my.conf:/etc/coturn/turnserver.conf \coturn/coturn…...
opencv Canny边缘检测
canny阈值越高,检测到的边缘数量越少 # 导入OpenCV库,用于图像处理 import cv2 import numpy as np # 从matplotlib库中导入pyplot模块,用于绘制图像 from matplotlib import pyplot as plt # 创建一个名为window的窗口,窗口大小自…...
数仓高频面试 | 数仓为什么要分层
大家好,我是大D呀。 关于数仓分层,在面试过程中几乎是必问的。不过,面试官一般也不会直接考你数仓为什么要分层,而是在你介绍项目时,可能会换一种形式来穿插着问,比如数据链路为什么要这样设计,…...
Coconut:探索大语言模型的连续思维链推理能力
目录 简介: 什么是Coconut? 为什么我们需要Coconut? Coconut如何工作? 实验结果怎么样? Coconut的优势: 结论: 简介: 你有没有想过,计算机是如何像人类一样思考问…...
2024年安徽省职业院校技能大赛水利工程BIM建模与应用
2024年安徽省职业院校技能大赛 赛 项 规 程 赛项名称: 水利工程BIM建模与应用 赛项组别: 高职组 承办单位: 安徽水利水电职业技术学院 目录 一、赛项名称… 1 二、竞赛目标… 1 三、竞赛内容… 2 四、竞赛方式… 3 五、竞赛流程… 4 七、技术…...
vue常用命令汇总
nvm 一个nodejs版本管理工具,解决node.js各种版本存在不兼容现象可以通过它可以安装和切换不同版本的node.js。 npm 可以管理 nodejs 的第三方插件。 vue-cli 是Vue提供的一个官方cli,专门为单页面应用快速搭建繁杂的脚手架。 nginx 是一个高性能的HTTP和反向代理we…...
ios上架构建版本没苹果电脑怎么上传
在app store上架的时候,遇到下图的问题: 点击蓝色加号的时候,并没有构建版本可以选择 从图中可以看出,它给我们推荐了很多上传工具,比如xcode、transporter或命令行工具之类的,但是这些工具都是只能在苹果…...
某名校考研自命题C++程序设计——近10年真题汇总(上)
本帖更新一些某校的编程真题,总体来说不难,考察的都是基本功,92高校大一期末的难度,不过有些细节颇为繁琐,各位还是需要一定程度上注意的~ 目录 一.分数求和 二.大小写字母转换 三.判断当年天序 四.交替合并字符串…...
【ChatGPT】解锁AI思维链:如何让机器像人类一样思考?
在人工智能领域,我们一直在追求让机器像人类一样思考。然而,即使是最先进的AI,也常常被诟病缺乏“常识”,难以理解复杂问题,更不用说像人类一样进行逻辑推理和解决问题了。最经常的表现就是遇到不会的地方,…...
️️️ 避坑指南:如何修复国密gmssl 库填充问题并提炼优秀加密实践20241212
🛡️ 避坑指南:如何修复国密gmssl 库填充问题并提炼优秀加密实践 ✨ 引言 在当下的数据安全环境中,SM4作为中国国家密码算法的代表性选择,被广泛应用于金融、通信和政府领域。然而,在实际开发中,即便是开…...
鸿蒙ArkTS如何实现数据可视化:饼状图/柱状图/折线图
鸿蒙数据可视化系列 在我们鸿蒙APP开发中,经常需要使用到一些图表的开发,本文主要介绍使用 McCharts 框架绘制柱状图的方法 配套指导视频地址:鸿蒙数据可视化系列-McCharts使用介绍_哔哩哔哩_bilibili 1. 第三方库介绍 1.1. McCharts Mc…...
Win10环境vscode+latex+中文快速配置
安装vscodelatex workshop 配置: {"liveServer.settings.donotVerifyTags": true,"liveServer.settings.donotShowInfoMsg": true,"explorer.confirmDelete": false,"files.autoSave": "afterDelay","exp…...
Android14 AOSP支持短按关机
修改frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java diff --git a/base/services/core/java/com/android/server/policy/PhoneWindowManager.java b/base/services/core/java/com/android/server/policy/PhoneWindowManager.java in…...
康耐视智能相机(Insight)通过ModbusTCP发送字符串到倍福(BECKHOFF)PLC中
文章目录 1.背景2.分析3.实现3.1.PLC的ModbusTCP_Server3.1.1.安装TF6250-Modbus-TCP3.1.2.PLC设置 3.2.智能相机的ModbusTCP_Client3.2.1.了解ModbusTCP的协议3.2.2.根据协议写代码3.2.2.1.纯函数代码3.2.2.2.脚本代码 3.2.3.非脚本处理时的代码逻辑图3.2.4.关于代码的问题及解…...
OpenCV 图像变换与处理实战
OpenCV快速通关 第一章:OpenCV 简介与环境搭建 第二章:OpenCV 图像基本操作 第三章:OpenCV 图像变换与处理实战 OpenCV 图像变换与处理实战 OpenCV快速通关OpenCV 图像变换与处理实战一、OpenCV 基础与图像处理概览二、图像变换理论精析三、…...
meta llama 大模型一个基础语言模型的集合
LLaMA 是一个基础语言模型的集合,参数范围从 7B 到 65B。我们在数万亿个 Token 上训练我们的模型,并表明可以专门使用公开可用的数据集来训练最先进的模型,而无需诉诸专有的和无法访问的数据集。特别是,LLaMA-13B 在大多数基准测试…...
【MySQL — 数据库基础】深入理解数据库服务与数据库关系、MySQL连接创建、客户端工具及架构解析
目录 1. 数据库服务&数据库&表之间的关系 1.1 复习 my.ini 1.2 MYSQL服务基于mysqld启动而启动 1.3 数据库服务的具体含义 1.4 数据库服务&数据库&表之间的关系 2. 客户端工具 2.1 客户端连接MySQL服务器 2.2 客…...
详解多租户架构下的资源隔离模式
文章目录 0.简介1.多租户概念1.1 基本概念1.2 单租户 vs 多租户 2.实现方案2.1 独立数据库方案2.1.1 优点2.1.2 缺点2.1.3 应用场景 2.2 共享数据库,独立 Schema2.2.1 优点2.2.2 缺点2.2.3 应用场景 2.3 共享数据库、共享Schema、共享表2.3.1 优点2.3.2 缺点2.3.3 应…...
Bananna Pi开源社区联合矽昌通信打造开源的低成本Wifi5路由器
香蕉派 BPI-Wifi5 路由器采用矽昌SF19A2890S2芯片方案设计。它是一款高性能无线路由器,适用于小微企业、家庭和其他网络环境。Banana Pi开源社区提供整体解决方案。所有代码开源,用户可以在上面自由开发自己的应用。 Banana Pi wifi5 路由器github代码: …...
根据契约进行分析--录像店案例研究01
Richard Mitchell 著,zhen_lei 译 本文包括录像店案例研究的一些片段,用来说明根据契约进行分析的原理。本文假定读者已经从其它渠道学习了一些关于根据契约进行分析的方法。 完整的一套模型可以写成一本书。这些选择的片段用来说明开发的某些方面&…...
Linux系统操作03|chmod、vim
上文: Linux系统操作02|基本命令-CSDN博客 目录 六、chmod:给文件设置权限 1、字母法 2、数字法(用的最多) 七、vim:代码编写和文本编辑 1、启动和退出 1️⃣启动 2️⃣退出 2、vim基本操作 六、chmod&#x…...
MyBatis 核心知识与实践
一、MyBatis 概述 1. 框架简介 MyBatis 是一款支持自定义 SQL、存储过程以及高级映射的持久层框架。它避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集的操作,使开发人员能够更专注于 SQL 语句的编写和业务逻辑的处理。 2. 核心组件 SqlSessionFactoryB…...
负载均衡oj项目:介绍
目录 项目介绍 项目演示 项目介绍 负载均衡oj是一个基于bs模式的项目。 用户使用浏览器向oj模块提交代码,oj模块会在所有在线的后端主机中选择一个负载情况最低的主机,将用户的代码提交给该主机,该主机进行编译运行,将结果返回…...
Oracle最佳实践-优化硬解析
前段时间参加oracle CAB,oracle高级服务部门做了一个数据库最佳实践的报告,其中就有一项就是解决未使用绑定变量但执行次数很多的SQL; 对于一个数据库来说如果不知道该如何优化,那么最简单最有效的优化就是减少硬解析,…...
Java 实现给pdf文件指定位置盖章功能
Java 实现给pdf文件指定位置盖章功能 开发中遇到一个需求, 需要给用户上传的的pdf文件, 指定位置上盖公章的功能, 经过调研和对比, 最终确定实现思路. 这里是使用pdf文件中的关键字进行章子的定位, 之所以这样考虑是因为如果直接写死坐标的话, 可能会出现因pdf大小, 缩放, 盖章…...
前端通过 jspdf 和 html2canvas 工具将网页生成 pdf
由于 html2canvas 的性能问题,该方案对于页数比较多的场景生成的非常慢,可以试着使用 modern-screenshot 工具看是否性能会得到提升。 import html2canvas from html2canvas import { jsPDF } from jspdfasync function exportPdf(){const pages docume…...
网络安全—部署CA证书服务器
网络拓扑 两台服务器在同一网段即可,即能够互相ping通。 安装步骤 安装证书系统 首先我们对计算机名进行确认,安装了证书系统后我们是不能随意更改计算机名字的,因为以后颁发的证书都是和计算机也就是这一台的服务器名字有关。 修改完成后开…...
用Keytool和OpenSSL生成和签发数字证书
一)keytool生成私钥文件(.key)和签名请求文件(.csr),openssl签发数字证书 J2SDK在目录%JAVA_HOME%/bin提供了密钥库管理工具Keytool,用于管理密钥、证书和证书链。Keytool工具的命令在JavaSE6中已经改变,不过以前的命令仍然支持。Keytool也可以用来管理对称加密算法中…...
《CSS 知识点》大屏卡片布局思路:弹性布局 flex-grow
思路 大屏左右两侧高宽一致,内部卡片可按比例设置! 使用弹性布局和属性 flex-grow 设置比例;间隔使用 margin-bottom 设置,最后一个卡片不设置; 效果如图 代码说明 CSS代码 26 - 30,左右两侧设置弹性布…...
ceph单节点部署方式
准备一台单节点虚拟机,2C4G 三个50G的SCSi类型的磁盘 0. 关闭防火墙/关闭SELinux/下载podman/配置时间同步 systemctl disable --now firewalld setenforce 0 vim /etc/sysconfig/selinux SELINUXenforcing 修改为 SELINUXdisabled yum install podman -y sed -…...
【人工智能】OpenAI O1模型:超越GPT-4的长上下文RAG性能详解与优化指南
在人工智能(AI)领域,长上下文生成与检索(RAG) 已成为提升自然语言处理(NLP)模型性能的关键技术之一。随着数据规模与应用场景的不断扩展,如何高效地处理海量上下文信息,成…...
Qt如何将字串显示成二维码以供扫码识别
在Qt中可以使用QRcode来生成二维码,本质上说,其实是把一串信息生成二维码形式的图片。 首先介绍下QRcode,QRCode原本是一个用于生成二维码的 JavaScript 库,在Qt中大佬们将其改成C语言实现。现直接放出来,使用的时候直…...
【Golang】如何读取并解析SQL文件
一、背景 在数据库开发与维护过程中,我们经常需要执行大量的SQL语句。有时,这些SQL语句会被保存在一个文件中,以便于批量执行。为了方便地在Go语言中处理这些SQL文件,我们可以编写一个函数来读取并解析SQL文件中的语句。 二、实…...
图形学笔记 - 5. 光线追踪 - RayTracing
Whitted-Style Ray tracing 为什么要光线追踪 光栅化不能很好地处理全局效果 软阴影尤其是当光线反射不止一次的时候 栅格化速度很快,但质量相对较低 光线追踪是准确的,但速度很慢 光栅化:实时,光线追踪:离线~10K …...
WHY - 为什么选择 Rsbuild
目录 一、介绍二、工具对比三、性能 https://rsbuild.dev/zh/guide/start/index 一、介绍 Rsbuild 是由 Rspack 驱动的高性能构建工具,它默认包含了一套精心设计的构建配置,提供开箱即用的开发体验,并能够充分发挥出 Rspack 的性能优势。 二…...
前端(模块化)
未使用模块化 定义两个js文件simple1.js和simple2.js let a11; let a11; 两个js文件变量重名 在html测试 传统引入js文件 <script src"./simple1.js"></script> <script src"./simple2.js"></script> 浏览器报错 使用模块…...
Elasticsearch:Mapping-映射
一、创建索引 自动生成索引字段数据类型即自动映射 创建之前,先删除索引防止重复创建 删除索引: DELETE product_mapping创建索引 product_mapping并且赋值 PUT /product_mapping/_doc/1 {"name": "xiaomi phone","desc": "s…...
掘金电影市场的新机遇:开发特惠电影票小程序api文档
随着电影市场的不断扩大,特惠电影票小程序成为创业者和企业争相布局的新蓝海。本文将带你深入了解特惠电影票小程序的开发要点,以及如何通过这个项目实现盈利。 项目背景及市场分析 电影市场规模的不断扩大为特惠电影票小程序提供了广阔的市场空间。 根…...