实验14 RNN的记忆能力和梯度爆炸实验

news2024/12/24 2:38:33

一 循环神经网络的记忆能力

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_path
        self.length = length
        self.k = k
        self.mode = mode

        if 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 + n2
                base_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] = val
                examples.append((seq, label))
        return examples

    def 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_examples

    def __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_size
        self.W = nn.Parameter(torch.zeros(input_size, hidden_size,dtype=torch.float32))
        # 定义模型参数U,其shape为 hidden_size x hidden_size
        self.U = nn.Parameter(torch.zeros(hidden_size, hidden_size,dtype=torch.float32))
        # 定义模型参数b,其shape为 1 x hidden_size
        self.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 hidden

    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层,例如SRN
        self.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 = model
        self.train_loader = train_loader
        self.val_loader = val_loader
        self.test_loader = test_loader
        self.criterion = criterion
        self.optimizer = optimizer
        self.best_model = None
        self.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.0
            for 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_loss
                self.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.0
        with 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 = 0
        total = 0
        with 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 / total
        print(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
    # 隐状态向量的维度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.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.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参数计算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 random

import 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_path
        self.length = length
        self.k = k
        self.mode = mode

        if 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 + n2
                base_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] = val
                examples.append((seq, label))

        return examples

    def 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_examples

    def __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_size
        self.W = nn.Parameter(torch.randn(input_size, hidden_size))
        # 定义模型参数U,其shape为 hidden_size x hidden_size
        self.U = nn.Parameter(torch.randn(hidden_size, hidden_size))
        # 定义模型参数b,其shape为 1 x hidden_size
        self.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层,例如SRN
        self.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

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参数计算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 = model
        self.train_loader = train_loader
        self.val_loader = val_loader
        self.test_loader = test_loader
        self.criterion = criterion
        self.optimizer = optimizer
        self.best_model = None
        self.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.0

            for 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_loss
                self.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.0
        with 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 = 0
        total = 0
        with 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 / total
        print(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. 这是因为TanhSigmoid型函数,其饱和区的导数接近于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,发生了梯度爆炸现象。

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

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

相关文章

一文说清flink从编码到部署上线

引言&#xff1a;目前flink的文章比较多&#xff0c;但一般都关注某一特定方面&#xff0c;很少有一个文章&#xff0c;从一个简单的例子入手&#xff0c;说清楚从编码、构建、部署全流程是怎么样的。所以编写本文&#xff0c;自己做个记录备查同时跟大家分享一下。本文以简单的…

IEEE T-RO 软体机器人手指状态估计实现两栖触觉传感

摘要&#xff1a;南方科技大学戴建生院士、林间院士、万芳老师、宋超阳老师团队近期在IEEE T-RO上发表了关于软体机器人手指在两栖环境中本体感知方法的论文。 近日&#xff0c;南方科技大学戴建生院士、林间院士、万芳老师、宋超阳老师团队在机器人顶刊IEEE T-RO上以《Propri…

java类静态初始化死锁问题

问题 前端时间帮同事分析了一个IO线程阻塞问题&#xff0c;该问题导致服务端无法处理任何请求&#xff0c;只能进行重启解决&#xff1b;事发时运维dump了下栈信息&#xff0c;堆栈信息如下图&#xff1a; 从上面可以看到io线程都阻塞于Object.wait()&#xff0c;具体是执行Cl…

厦门凯酷全科技有限公司怎么样?

随着短视频和直播带货的兴起&#xff0c;抖音电商平台迅速崛起&#xff0c;成为众多品牌和商家争夺的新战场。在这个竞争激烈的市场中&#xff0c;如何抓住机遇、实现销售增长&#xff0c;成为了每个企业面临的挑战。厦门凯酷全科技有限公司&#xff08;以下简称“凯酷全”&…

微信小程序uni-app+vue3实现局部上下拉刷新和scroll-view动态高度计算

微信小程序uni-appvue3实现局部上下拉刷新和scroll-view动态高度计算 前言 在uni-appvue3项目开发中,经常需要实现列表的局部上下拉刷新功能。由于网上相关教程较少且比较零散,本文将详细介绍如何使用scroll-view组件实现这一功能,包括动态高度计算、下拉刷新、上拉加载等完整…

在Windows和Ubuntu上安装SDKMAN

文章目录 1. SDKMAN概述2. 安装与使用SDKMAN2.1 在Windows上安装SDKMAN2.1.1 安装Git for Windows2.1.2 安装SDKMAN 2.2 利用SDKMAN管理Java2.2.1 查看所有可用的OpenJDK发行版2.2.2 安装Java2.2.3 查看Java版本2.2.4 shell指定使用某个Java版本 2.3 在Ubuntu上安装SDKMAN2.3.1…

1210 作业

思维导图 作业 使用read和write函数拷贝文件&#xff0c;一半拷进一个文件&#xff0c;另一半拷进另一个文件 #include <myhead.h> int main(int argc, const char *argv[]) {int fd1 open("./z1.txt",O_RDONLY);if(fd1-1){perror("open");return…

牛客网热门Java面试题及答案整理(2024最新版)

当今互联网行业中&#xff0c;Java 作为一种广泛应用的编程语言&#xff0c;对于求职者来说仍是一项受欢迎的技能。然而&#xff0c;随着市场上的开发人员数量越来越多&#xff0c;Java 面试的竞争也愈加激烈。 目前 Java 面试有着以下现状&#xff1a; 面试难度加大与过去相…

【SSH+X11】VsCode使用Remote-SSH在远程服务器的docker中打开Rviz

&#x1f680;今天来分享一下通过VsCode的Remote-SSH插件在远程服务器的docker中打开Rviz进行可视化的方法。 具体流程如下图所示&#xff0c;在操作开始前&#xff0c;请先重启设备&#xff0c;排除之前运行配置的影响&#xff1a; ⭐️ 我这里是使用主机连接服务器&#xff…

iPhone 17 Air基本确认,3个大动作

近段时间&#xff0c;果粉圈都在讨论一个尚未发布的新品&#xff1a;iPhone 17 Air&#xff0c;苹果又要来整新活了。 从供应链消息来看&#xff0c;iPhone 17 Air本质上是Plus的替代品&#xff0c;主要是在维持“大屏”这一卖点的同时&#xff0c;增加了“轻薄”属性&#xff…

浅析OCR技术与大模型的深度融合—中安未来OCR产品优势及前景探索

OCR&#xff08;光学字符识别&#xff09;技术作为一种文本识别工具&#xff0c;已在文档管理、自动化办公和图书数字化等领域发挥了重要作用。然而&#xff0c;随着深度学习和大语言模型&#xff08;LLM&#xff09;的迅猛发展&#xff0c;OCR技术迎来了新的机遇和挑战。如今&…

Android四大组件——Activity(二)

一、Activity之间传递消息 在&#xff08;一&#xff09;中&#xff0c;我们把数据作为独立的键值对进行传递&#xff0c;那么现在把多条数据打包成一个对象进行传递&#xff1a; 1.假设有一个User类的对象&#xff0c;我们先使用putExtra进行传递 activity_demo06.xml <…

STM32G4系列MCU双ADC多通道数据转换的应用

目录 概述 1 STM32Cube配置项目 1.1 基本参数配置 1.1.1 ADC1参数配置 1.1.2 ADC2参数配置 1.2 项目软件架构 2 功能实现 2.1 ADC转换初始化 2.2 ADC数据组包 3 测试函数 3.1 Vofa数据接口 3.2 输入数据 4 测试 4.1 ADC1 通道测试 4.2 ADC2 通道测试 概述 本文…

STM32串口接收与发送(关于为什么接收不需要中断而发生需要以及HAL_UART_Transmit和HAL_UART_Transmit_IT的区别)

一、HAL_UART_Transmit和HAL_UART_Transmit_IT的区别 1. HAL_UART_Transmit_IT&#xff08;非阻塞模式&#xff09;&#xff1a; HAL_UART_Transmit_IT 是非阻塞的传输函数&#xff0c;也就是说&#xff0c;当你调用 HAL_UART_Transmit_IT 时&#xff0c;它不会等到数据完全发…

constexpr、const和 #define 的比较

constexpr、const 和 #define 的比较 一、定义常量 constexpr 定义&#xff1a;constexpr用于定义在编译期可求值的常量表达式。示例&#xff1a;constexpr int x 5;这里&#xff0c;x的值在编译期就确定为5。 const 定义&#xff1a;const表示变量在运行期间不能被修改&…

BMS电池管理系统

一.项目简介 1.该项目是基于BQ7692003PWR STM32F103C8T6研发的一块锂电池控制板&#xff0c;本控制板可供五串18650锂电池&#xff08;目前软件仅支持三元锂类型&#xff0c;标称电压为4.2V&#xff09;串联使用&#xff0c;电芯均衡采用被动均衡方式 二.本项目功能 1.监控任…

Milvus向量数据库01-基础概念

Milvus向量数据库01-基础概念 Zilliz Cloud 集群由全托管 Milvus 实例及相关计算资源构成。您可以在 Zilliz Cloud 集群中创建 Collection&#xff0c;然后在 Collection 中插入 Entity。Zilliz Cloud 集群中的 Collection 类似于关系型数据库中的表。Collection 中的 Entity …

【OpenCV】模板匹配

理论 模板匹配是一种在较大图像中搜索和查找模板图像位置的方法。为此&#xff0c;OpenCV 带有一个函数 cv.matchTemplate&#xff08;&#xff09; 。它只是在输入图像上滑动模板图像&#xff08;如在 2D 卷积中&#xff09;&#xff0c;并比较模板图像下的模板和输入图像的补…

深入解析下oracle的number底层存储格式

oracle数据库中&#xff0c;number数据类型用来存储数值数据&#xff0c;它既可以存储负数数值&#xff0c;也可以存储正数数值。相对于其他类型数据&#xff0c;number格式的数据底层存储格式要复杂得多。今天我们就详细探究下oracle的number底层存储格式。 一、环境搭建 1.…

MySQL Binlog 日志监听与 Spring 集成实战

MySQL Binlog 日志监听与 Spring 集成实战 binlog的三种模式 MySQL 的二进制日志&#xff08;binlog&#xff09;有三种常见的格式&#xff1a;Statement 模式、Row 模式和Mixed 模式。每种模式的设计目标不同&#xff0c;适用于不同的场景&#xff0c;以下是它们的详细对比和…