LSTM的记忆能力实验 [HBU]

news2025/2/4 7:56:24

目录

模型构建

LSTM层

模型训练

多组训练

模型评价

模型在不同长度的数据集上的准确率变化图

模型汇总

总结


        长短期记忆网络(Long Short-Term Memory Network,LSTM)是一种可以有效缓解长程依赖问题的循环神经网络.LSTM 的特点是引入了一个新的内部状态(Internal State)C\epsilon \in {R}^{D}和门控机制(Gating Mechanism).不同时刻的内部状态以近似线性的方式进行传递,从而缓解梯度消失或梯度爆炸问题.同时门控机制进行信息筛选,可以有效地增加记忆能力.例如,输入门可以让网络忽略无关紧要的输入信息,遗忘门可以使得网络保留有用的历史信息.在上一节的数字求和任务中,如果模型能够记住前两个非零数字,同时忽略掉一些不重要的干扰信息,那么即时序列很长,模型也有效地进行预测.

LSTM 模型在第t步时,循环单元的内部结构如下图所示:

提醒:为了和代码的实现保存一致性,这里使用形状为 (样本数量 × 序列长度 × 特征维度) 的张量来表示一组样本.


模型构建

使用第6.1.2.4节中定义Model_RNN4SeqClass模型,并构建 LSTM 算子.

只需要实例化 LSTM ,并传入Model_RNN4SeqClass模型,就可以用 LSTM 进行数字求和实验。

LSTM层

  • 自定义LSTM算子

代码为:

#NNDL实验
import torch.nn.functional as F
import torch
import torch.nn as nn

# 声明LSTM和相关参数
class LSTM(nn.Module):
    def __init__(self, input_size, hidden_size, Wi_attr=None, Wf_attr=None, Wo_attr=None, Wc_attr=None,
                 Ui_attr=None, Uf_attr=None, Uo_attr=None, Uc_attr=None, bi_attr=None, bf_attr=None,
                 bo_attr=None, bc_attr=None):
        super(LSTM, self).__init__()

        #定义LSTM网络层需要的参数
        self.input_size = input_size #输入大小
        self.hidden_size = hidden_size #隐藏层大小

        # 初始化模型参数
        if Wi_attr==None:
             Wi= torch.zeros(size=[input_size, hidden_size], dtype=torch.float32)
        else:
             Wi = torch.tensor(Wi_attr, dtype=torch.float32)
        self.W_i = torch.nn.Parameter(Wi)

        if Wf_attr==None:
             Wf=torch.zeros(size=[input_size, hidden_size], dtype=torch.float32)
        else:
             Wf = torch.tensor(Wf_attr, dtype=torch.float32)
        self.W_f = torch.nn.Parameter(Wf)

        if Wo_attr==None:
             Wo=torch.zeros(size=[input_size, hidden_size], dtype=torch.float32)
        else:
             Wo = torch.tensor(Wo_attr, dtype=torch.float32)
        self.W_o =torch.nn.Parameter(Wo)

        if Wc_attr==None:
            Wc=torch.zeros(size=[input_size, hidden_size], dtype=torch.float32)
        else:
            Wc = torch.tensor(Wc_attr, dtype=torch.float32)
        self.W_c = torch.nn.Parameter(Wc)

        if Ui_attr==None:
            Ui = torch.zeros(size=[hidden_size, hidden_size], dtype=torch.float32)
        else:
            Ui = torch.tensor(Ui_attr, dtype=torch.float32)
        self.U_i = torch.nn.Parameter(Ui)
        if Uf_attr == None:
            Uf = torch.zeros(size=[hidden_size, hidden_size], dtype=torch.float32)
        else:
            Uf = torch.tensor(Uf_attr, dtype=torch.float32)
        self.U_f = torch.nn.Parameter(Uf)

        if Uo_attr == None:
            Uo = torch.zeros(size=[hidden_size, hidden_size], dtype=torch.float32)
        else:
            Uo = torch.tensor(Uo_attr, dtype=torch.float32)
        self.U_o = torch.nn.Parameter(Uo)

        if Uc_attr == None:
            Uc = torch.zeros(size=[hidden_size, hidden_size], dtype=torch.float32)
        else:
            Uc = torch.tensor(Uc_attr, dtype=torch.float32)
        self.U_c = torch.nn.Parameter(Uc)

        if bi_attr == None:
            bi = torch.zeros(size=[1,hidden_size], dtype=torch.float32)
        else:
            bi = torch.tensor(bi_attr, dtype=torch.float32)
        self.b_i = torch.nn.Parameter(bi)
        if bf_attr == None:
            bf = torch.zeros(size=[1,hidden_size], dtype=torch.float32)
        else:
            bf = torch.tensor(bf_attr, dtype=torch.float32)
        self.b_f = torch.nn.Parameter(bf)

        if bo_attr == None:
            bo = torch.zeros(size=[1,hidden_size], dtype=torch.float32)
        else:
            bo = torch.tensor(bo_attr, dtype=torch.float32)
        self.b_o = torch.nn.Parameter(bo)
        if bc_attr == None:
            bc = torch.zeros(size=[1,hidden_size], dtype=torch.float32)
        else:
            bc = torch.tensor(bc_attr, dtype=torch.float32)
        self.b_c = torch.nn.Parameter(bc)

    # 初始化状态向量和隐状态向量
    def init_state(self, batch_size):
        #hidden_state cell_state 都被初始化为大小为batch_size x hidden_size的零张量
        hidden_state = torch.zeros(size=[batch_size, self.hidden_size], dtype=torch.float32)
        cell_state = torch.zeros(size=[batch_size, self.hidden_size], dtype=torch.float32)
        return hidden_state, cell_state

    # 前向计算
    def forward(self, inputs, states=None):
        #获取输入数据的形状
        # inputs: 输入数据,其shape为batch_size * seq_len * input_size
        batch_size, seq_len, input_size = inputs.shape

        # 初始化起始的单元状态和隐状态向量,其shape为batch_size x hidden_size
        if states is None:
            states = self.init_state(batch_size)
        hidden_state, cell_state = states

        # 执行LSTM计算,包括:输入门、遗忘门和输出门、候选内部状态、内部状态和隐状态向量
        for step in range(seq_len):
            # 获取当前时刻的输入数据step_input: 其shape为batch_size x input_size
            step_input = inputs[:, step, :]
            # 计算输入门, 遗忘门和输出门, 其shape为:batch_size * hidden_size
            I_gate = F.sigmoid(torch.matmul(step_input, self.W_i) + torch.matmul(hidden_state, self.U_i) + self.b_i)
            F_gate = F.sigmoid(torch.matmul(step_input, self.W_f) + torch.matmul(hidden_state, self.U_f) + self.b_f)
            O_gate = F.sigmoid(torch.matmul(step_input, self.W_o) + torch.matmul(hidden_state, self.U_o) + self.b_o)

            # 计算候选状态向量, 其shape为:batch_size * hidden_size
            C_tilde = F.tanh(torch.matmul(step_input, self.W_c) + torch.matmul(hidden_state, self.U_c) + self.b_c)
            # 计算单元状态向量, 其shape为:batch_size * hidden_size
            cell_state = F_gate * cell_state + I_gate * C_tilde
            # 计算隐状态向量,其shape为:batch_size * hidden_size
            hidden_state = O_gate * F.tanh(cell_state)

        return hidden_state

Wi_attr = [[0.1, 0.2], [0.1, 0.2]]
Wf_attr = [[0.1, 0.2], [0.1, 0.2]]
Wo_attr = [[0.1, 0.2], [0.1, 0.2]]
Wc_attr = [[0.1, 0.2], [0.1, 0.2]]
Ui_attr = [[0.0, 0.1], [0.1, 0.0]]
Uf_attr = [[0.0, 0.1], [0.1, 0.0]]
Uo_attr = [[0.0, 0.1], [0.1, 0.0]]
Uc_attr = [[0.0, 0.1], [0.1, 0.0]]
bi_attr = [[0.1, 0.1]]
bf_attr = [[0.1, 0.1]]
bo_attr = [[0.1, 0.1]]
bc_attr = [[0.1, 0.1]]

lstm = LSTM(2, 2, Wi_attr=Wi_attr, Wf_attr=Wf_attr, Wo_attr=Wo_attr, Wc_attr=Wc_attr,
                 Ui_attr=Ui_attr, Uf_attr=Uf_attr, Uo_attr=Uo_attr, Uc_attr=Uc_attr,
                 bi_attr=bi_attr, bf_attr=bf_attr, bo_attr=bo_attr, bc_attr=bc_attr)

inputs = torch.as_tensor([[[1, 0]]], dtype=torch.float32)
hidden_state = lstm(inputs)
print(hidden_state)

运行结果,打印隐藏状态:

  • nn.LSTM

代码为:
 

# 创建一个随机数组作为测试数据,数据shape为batch_size * seq_len * input_size
batch_size, seq_len, input_size = 8, 20, 32 #批次大小 序列长度 输入大小
inputs = torch.randn(size=[batch_size, seq_len, input_size]) #创建随机张量 作为输入数据

# 设置模型的hidden_size
hidden_size = 32

#创建LSTM模型
torch_lstm = nn.LSTM(input_size, hidden_size)
self_lstm = LSTM(input_size, hidden_size)

#使用自定义的self_lstm对输入数据进行前向传播,得到隐藏状态
self_hidden_state = self_lstm(inputs)
#返回两个输出:输出张量 torch_outputs 和隐藏状态
torch_outputs, (torch_hidden_state, torch_cell_state) = torch_lstm(inputs)

print("self_lstm hidden_state: ", self_hidden_state.shape) #隐藏状态大小
print("torch_lstm outpus:", torch_outputs.shape) #输出大小
print("torch_lstm hidden_state:", torch_hidden_state.shape) #隐藏状态大小
print("torch_lstm cell_state:", torch_cell_state.shape) #单元状态大小

结果打印了初始隐藏状态大小,输出大小,隐藏状态大小以及单元状态大小:

可以看到,自己实现的LSTM由于没有考虑多层因素,因此没有层次这个维度,因此其输出shape为[8, 32]。同时由于在以上代码使用Paddle内置API实例化LSTM时,默认定义的是1层的单向SRN,因此其shape为[1, 8, 32],同时隐状态向量为[8,20, 32]。

  • 将自定义LSTM与pytorch内置的LSTM进行对比

代码:


import torch
torch.seed()

# 这里创建一个随机数组作为测试数据,数据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_size
torch_lstm = nn.LSTM(input_size, hidden_size, bias=True)

# 获取torch_lstm中的参数,并设置相应的paramAttr,用于初始化lstm
print(torch_lstm.weight_ih_l0.T.shape)
chunked_W = torch.split(torch_lstm.weight_ih_l0.T, split_size_or_sections=10, dim=-1)
chunked_U = torch.split(torch_lstm.weight_hh_l0.T, split_size_or_sections=10, dim=-1)
chunked_b = torch.split(torch_lstm.bias_hh_l0.T, split_size_or_sections=10, dim=-1)

Wi_attr = chunked_W[0]
Wf_attr = chunked_W[1]
Wc_attr = chunked_W[2]
Wo_attr = chunked_W[3]
Ui_attr = chunked_U[0]
Uf_attr = chunked_U[1]
Uc_attr = chunked_U[2]
Uo_attr = chunked_U[3]
bi_attr = chunked_b[0]
bf_attr = chunked_b[1]
bc_attr = chunked_b[2]
bo_attr = chunked_b[3]
self_lstm = LSTM(input_size, hidden_size, Wi_attr=Wi_attr, Wf_attr=Wf_attr, Wo_attr=Wo_attr, Wc_attr=Wc_attr,
                 Ui_attr=Ui_attr, Uf_attr=Uf_attr, Uo_attr=Uo_attr, Uc_attr=Uc_attr,
                 bi_attr=bi_attr, bf_attr=bf_attr, bo_attr=bo_attr, bc_attr=bc_attr)

# 进行前向计算,获取隐状态向量,并打印展示
self_hidden_state = self_lstm(inputs)
torch_outputs, (torch_hidden_state, _) = torch_lstm(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 = 8, 20, 32
inputs = torch.randn([batch_size, seq_len, input_size])

# 设置模型的hidden_size
hidden_size = 32
self_lstm = LSTM(input_size, hidden_size)
torch_lstm = nn.LSTM(input_size, hidden_size)

# 计算自己实现的SRN运算速度
model_time = 0
for i in range(100):
    strat_time = time.time()
    hidden_state = self_lstm(inputs)
    # 预热10次运算,不计入最终速度统计
    if i < 10:
        continue
    end_time = time.time()
    model_time += (end_time - strat_time)
avg_model_time = model_time / 90
print('self_lstm speed:', avg_model_time, 's')

# 计算torch内置的SRN运算速度
model_time = 0
for i in range(100):
    strat_time = time.time()
    outputs, (hidden_state, cell_state) = torch_lstm(inputs)
    # 预热10次运算,不计入最终速度统计
    if i < 10:
        continue
    end_time = time.time()
    model_time += (end_time - strat_time)
avg_model_time = model_time / 90
print('torch_lstm speed:', avg_model_time, 's')

结果:

由于PyTorch底层采用了C++实现并进行优化,Pytorch框架内置的LSTM运行效率远远高于自己实现的LSTM。


模型训练

训练指定长度的数字预测模型。

流程与代码为:

# 训练轮次
num_epochs = 500
# 学习率
lr = 0.001
# 输入数字的类别数
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 LSTM with data of length {length}.")
    np.random.seed(0)
    random.seed(0)

    # 加载长度为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 = LSTM(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 = torch.nn.CrossEntropyLoss()
    # 基于以上组件,实例化Runner
    runner = RunnerV3(model, optimizer, loss_fn, metric)

    # 进行模型训练
    model_save_path = os.path.join(save_dir, f"best_lstm_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 runner

涉及到的函数:
>DigitSumDataset()

from torch.utils.data import Dataset,DataLoader
import torch
class DigitSumDataset(Dataset):
    def __init__(self, data):
        self.data = data

    def __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, label

    def __len__(self):
        return len(self.data)

load_data()

import os
# 加载数据
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和标签label
            items = 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和标签label
            items = 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和标签label
            items = 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

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 embs


emb_layer = Embedding(10, 5)
inputs = torch.tensor([0, 1, 2, 3])
emb_layer(inputs)

Model_RNN4SeqClass()


# 基于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

RunnerV3()

class RunnerV3(object):
    def __init__(self, model, optimizer, loss_fn, metric, **kwargs):
        self.model = model
        self.optimizer = optimizer
        self.loss_fn = loss_fn
        self.metric = metric  # 只用于计算评价指标

        # 记录训练过程中的评价指标变化情况
        self.dev_scores = []

        # 记录训练过程中的损失函数变化情况
        self.train_epoch_losses = []  # 一个epoch记录一次loss
        self.train_step_losses = []  # 一个step记录一次loss
        self.dev_losses = []

        # 记录全局最优指标
        self.best_score = 0

    def train(self, train_loader, dev_loader=None, **kwargs):
        # 将模型切换为训练模式
        self.model.train()

        # 传入训练轮数,如果没有传入值则默认为0
        num_epochs = kwargs.get("num_epochs", 0)
        # 传入log打印频率,如果没有传入值则默认为100
        log_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 = 0
            for step, data in enumerate(train_loader):
                X, y = data
                # 获取模型预测
                logits = self.model(X)
                loss = self.loss_fn(logits, y.long())  # 默认求mean
                total_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_score

                global_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.long()).item()
            # 累积损失
            total_loss += loss

            # 累积评价
            self.metric.update(logits, y)

        dev_loss = (total_loss / len(dev_loader))
        dev_score = self.metric.accumulate()

        # 记录验证集loss
        if 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 logits

    def save_model(self, save_path):
        torch.save(self.model.state_dict(), save_path)

    def load_model(self, model_path):
        state_dict = torch.load(model_path)
        self.model.load_state_dict(state_dict)

Accuracy()

class Accuracy():
    def __init__(self, is_logist=True):
        # 用于统计正确的样本个数
        self.num_correct = 0
        # 用于统计样本的总数
        self.num_count = 0

        self.is_logist = is_logist

    def update(self, outputs, labels):

        # 判断是二分类任务还是多分类任务,shape[1]=1时为二分类任务,shape[1]>1时为多分类任务
        if outputs.shape[1] == 1:  # 二分类
            outputs = torch.squeeze(outputs, dim=-1)
            if self.is_logist:
                # logist判断是否大于0
                preds = torch.tensor((outputs >= 0), dtype=torch.float32)
            else:
                # 如果不是logist,判断每个概率值是否大于0.5,当大于0.5时,类别为1,否则类别为0
                preds = 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_count
        self.num_correct += batch_correct
        self.num_count += batch_count

    def accumulate(self):
        # 使用累计的数据,计算总的指标
        if self.num_count == 0:
            return 0
        return self.num_correct / self.num_count

    def reset(self):
        # 重置正确的数目和总数
        self.num_correct = 0
        self.num_count = 0

    def name(self):
        return "Accuracy"

多组训练

lstm_runners = {}

lengths = [10, 15, 20, 25, 30, 35]
for length in lengths:
    runner = train(length)
    lstm_runners[length] = runner

运行结果:(从左到右,从上到下依次为L=10,15,20,25,30,35)                          

LSTM模型在不同长度数据集上进行训练后的损失变化,同SRN模型一样,随着序列长度的增加,训练集上的损失逐渐不稳定,验证集上的损失整体趋向于变大,这说明当序列长度增加时,保持长期依赖的能力同样在逐渐变弱. 但是同上节实验运行结果(下图)相比,LSTM模型在序列长度增加时,收敛情况比SRN模型更好。


模型评价

代码:

lstm_dev_scores = []
lstm_test_scores = []
for length in lengths:
    print(f"Evaluate LSTM with data length {length}.")
    runner = lstm_runners[length]
    # 加载训练过程中效果最好的模型
    model_path = os.path.join(save_dir, f"best_lstm_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)
    lstm_test_scores.append(score)
    lstm_dev_scores.append(max(runner.dev_scores))

for length, dev_score, test_score in zip(lengths, lstm_dev_scores, lstm_test_scores):
    print(f"[LSTM] length:{length}, dev_score: {dev_score}, test_score: {test_score: .5f}")

结果为:

Evaluate LSTM with data length 15.
Evaluate LSTM with data length 20.
Evaluate LSTM with data length 25.
Evaluate LSTM with data length 30.
Evaluate LSTM with data length 35.
[LSTM] length:10, dev_score: 0.95, test_score:  0.93000
[LSTM] length:15, dev_score: 0.9, test_score:  0.92000
[LSTM] length:20, dev_score: 0.77, test_score:  0.74000
[LSTM] length:25, dev_score: 0.81, test_score:  0.78000
[LSTM] length:30, dev_score: 0.75, test_score:  0.67000
[LSTM] length:35, dev_score: 0.26, test_score:  0.19000

Process finished with exit code 0

模型在不同长度的数据集上的准确率变化图

代码:

#模型在不同长度的数据集上的准确率变化图
import matplotlib.pyplot as plt
plt.plot(lengths, lstm_dev_scores, '-o', color='#40E0D0',  label="LSTM-Dev Accuracy")
plt.plot(lengths, lstm_test_scores,'-o', color='#BA55D3', label="LSTM-Test Accuracy")

#绘制坐标轴和图例
plt.ylabel("accuracy", fontsize='large')
plt.xlabel("sequence length", fontsize='large')
plt.legend(loc='lower left', fontsize='x-large')

fig_name = "./images/6.12.pdf"
plt.savefig(fig_name)
plt.show()

可视化图像:

可见 ,随着数据集长度的增加,LSTM模型在验证集和测试集上的准确率整体均趋向于降低;
 


模型汇总

总结RNN

一文搞懂RNN(循环神经网络)基础篇 - 知乎 (zhihu.com)

>RNN结构:

也可以表示成这样:

循环神经网络(Recurrent Neural Network,RNN)是一类具有短期记忆能力的神经网络.在循环神经网络中,神经元不但可以接受其他神经元的信息,也可以接受自身的信息,形成具有环路的网络结构.和前馈神经网络相比,循环神经网络更加符合生物神经网络的结构.目前,循环神经网络已经被广泛应用在语音识别、语言模型以及自然语言生成等任务上.
 

循环神经网络的参数可以通过梯度下降法来学习。和前馈神经网络类似,我们可以使用随时间反向传播(BackPropagation Through Time,BPTT)算法高效地手工计算梯度,也可以使用自动微分的方法,通过计算图自动计算梯度。

循环神经网络被认为是图灵完备的,一个完全连接的循环神经网络可以近似解决所有的可计算问题。然而,虽然理论上循环神经网络可以建立长时间间隔的状态之间的依赖关系,但是由于具体的实现方式和参数学习方式会导致梯度爆炸或梯度消失问题,实际上,通常循环神经网络只能学习到短期的依赖关系,很难建模这种长距离的依赖关系,称为长程依赖问题(Long-Term Dependencies Problem)。

>LSTM模型:

这是我之前的博客链接:NNDL 作业11 LSTM [HBU ]-CSDN博客

我在这一节里,详细的推导了LSTM的前向传播、反向传播、梯度消失的原因以及如何解决梯度消失问题。

>RNN章节知识框架:


总结

>在进行自定义LSTM和Pytorch内置LSTM的对比时,我按照教材上的代码去运行,发生了一连串的报错:

其中的一个报错信息为:
UserWarning: The use of `x.T` on tensors of dimension other than 2 to reverse their shape is deprecated and it will throw an error in a future release. Consider `x.mT` to transpose batches of matrices or `x.permute(*torch.arange(x.ndim - 1, -1, -1))` to reverse the dimensions of a tensor. (Triggered internally at C:\actions-runner\_work\pytorch\pytorch\builder\windows\pytorch\aten\src\ATen\native\TensorShape.cpp:3618.)
  chunked_b = torch.split(torch_lstm.bias_hh_l0.T, split_size_or_sections=10, dim=-1)

此警告的内容是关于x.T在非2维张量上的使用。在PyTorch中,x.T通常用于2维张量,用于转置矩阵。当在非2维张量上使用时,它可能不会按照预期工作。

警告建议了两种替代方法:

  1. 使用x.mT来转置批量的矩阵。
  2. 使用x.permute(*torch.arange(x.ndim - 1, -1, -1))来反转张量的维度。

尝试修改代码,不反转张量的维度:

这样修改的确将错误信息消除了。但是这也只能证明了在python语法上没有错误,而这个修改对于LSTM模型架构可能会有影响。

          期末考试临近,关于RNN网络的知识整理,更详细版的我会发布到新的期末总结博客中,目的有两个:(1)应对期末考试  (2)整理当前最流行、最经典的网络模型架构,作为个人学习记录总结,为以后做项目而留下自己的学习记录整理,方便自己查询。

> 另外,还有一些小小的碎碎念和感触:

今天和家里的一位长辈聊天,长辈正是做AI专业的,开了一家公司(他也是从河北大学计算机学院毕业的,也算是我的老学长。)  目前我是在准备考研的,正在努力地刷初试分。长辈想测试我地python编程实力,过年后带我做他们公司的项目,这是机遇 也是挑战。

        和长辈学技术、做公司项目 十分十分十分重要,我其实挺想接受的,做实战项目可以极大的提升编程硬实力,但同时也是要付出很多很多时间和精力的。        我目前的水平,也处在新手期,跟着长辈做项目的话,担心托他们后腿,而且我更想好好准备考研,所以考虑了一上午,还是推脱掉了。

只能感叹一句,鱼和熊掌不可兼得,我的编程能力欠缺练习,我很想提升编程硬实力;但是还有一年就要考研初试了,我也想努力的提升自己的学历,考到一个更好的学校。

希望各位业界大佬或者和我一样专业(计算机-AI)的伙伴可以给出一些意见,对于计算机行业的人来说,研究生学历 和 编程硬实力 哪一个是本科阶段最需要追求的呢??

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

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

相关文章

Text-to-SQL小白入门(十)RLHF在Text2SQL领域的探索实践

本文内容主要基于以下开源项目探索实践&#xff0c; Awesome-Text2SQL:GitHub - eosphoros-ai/Awesome-Text2SQL: Curated tutorials and resources for Large Language Models, Text2SQL, Text2DSL、Text2API、Text2Vis and more.DB-GPT-Hub&#xff1a;GitHub - eosphoros-ai…

【QML-按钮】

QML编程指南 VX&#xff1a;hao541022348 ■ 按钮■ AbstractButton■ Button■ CheckBox■ DelayButton■ RadioButton■ RoundButton&#x1f31f;■ Switch&#x1f31f;■ ToolButton&#x1f31f; ■ 按钮 ■ AbstractButton 所有的按钮控件的父类都是 AbstractButton 。…

Electron自定义通知Notification

Notification是什么&#xff1f; 对于渲染进程&#xff0c;Electron 允许开发者使用通知中API&#xff0c;来运行系统的原生通知进行显示。 如何实现系统Notification&#xff1f; const { Notification } require(electron);const isAllowed Notification.isSupported();…

市场复盘总结 20231226

仅用于记录当天的市场情况&#xff0c;用于统计交易策略的适用情况&#xff0c;以便程序回测 短线核心&#xff1a;不参与任何级别的调整 昨日回顾&#xff1a; SELECT CODE,成交额排名,净流入排名,代码,名称,DDE大单金额,涨幅,主力净额,DDE大单净量,CONVERT(DATETIME, 最后封…

概率论1:下象棋问题(3.5)

每日小语 时刻望着他人的眼色行事&#xff0c;是腾飞不了的。自己怎么想就积极地去做&#xff0c;这是需要胆量的。——广中平佑 题目 甲、乙二人下象棋&#xff0c; 每局甲胜的概率为a,乙胜的概率为b. 为简化问题&#xff0c;设没有和局的情况&#xff0c;这意味着a b1. 设想…

Flappy Bird游戏python完整源码

通过pygame实现当年风靡一时的flappy bird小游戏。 当前只设定了同样长度的管道&#xff0c;图片和声音文件自行导入。 效果如下&#xff1a; # -*- coding:utf-8 -*- """ 通过pygame实现曾风靡一时的flappybird游戏。 小鸟x坐标不变&#xff0c;画布左移实现…

分享5款对工作有帮助的小工具

​ 在职场中&#xff0c;简单而高效的工具是我们事业成功的一部分。这五款工具可能会成为你职业生涯中的得力助手。 1.图片标注——iPhotoDraw ​ iPhotoDraw是一款图片标注软件&#xff0c;可以在图片上添加文字、符号、尺寸、放大细节等注释对象&#xff0c;增加图片的信息…

〖Python网络爬虫实战㊹〗- JavaScript Hook 的用法

订阅&#xff1a;新手可以订阅我的其他专栏。免费阶段订阅量1000 python项目实战 Python编程基础教程系列&#xff08;零基础小白搬砖逆袭) 说明&#xff1a;本专栏持续更新中&#xff0c;订阅本专栏前必读关于专栏〖Python网络爬虫实战〗转为付费专栏的订阅说明作者&#xff1…

SQL server 数据库练习题及答案(练习2)

使用你的名字创建一个数据库 创建表&#xff1a; 数据库中有三张表&#xff0c;分别为student,course,SC&#xff08;即学生表&#xff0c;课程表&#xff0c;选课表&#xff09; 问题&#xff1a; --1.分别查询学生表和学生修课表中的全部数据。--2.查询成绩在70到80分之间…

文件操作(c)

1.文件分类&#xff1a;分为程序文件和数据文件 1.程序文件&#xff1a;源程序文件&#xff08;后缀.c&#xff09;,目标文件&#xff08;windows环境后缀.obj&#xff09;,可执行程序&#xff08;windows环境后缀.exe&#xff09; 2.数据文件&#xff1a;程序运行时读写的数据…

【MySQL】复合查询 | 内外连接

文章目录 一、MySQL复合查询基本查询回顾多表查询自连接子查询单行子查询多行子查询多列子查询在from子句中使用子查询合并查询 二、MySQL内外连接内连接外连接左外连接右外连接 一、MySQL复合查询 基本查询回顾 准备测试表 下面给出三张表&#xff0c;员工表&#xff08;emp&…

多重断言插件之pytest-assume的简单使用

背景&#xff1a; pytest-assume是Pytest框架的一个扩展&#xff0c;它允许在单个测试用例中多次断言。通常情况下&#xff0c;当一个断言失败时&#xff0c;测试会立即停止执行&#xff0c;而pytest-assume允许我 们继续执行剩余的断言&#xff0c;以便查看更多的失败信息。…

k8s集群通过helm部署skywalking

1、安装helm 下载脚本安装 ~# curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 ~# chmod 700 get_helm.sh ~# ./get_helm.sh或者下载包进行安装 ~# wget https://get.helm.sh/helm-canary-linux-amd64.tar.gz ~# mv helm …

使用 Jekyll 构建你的网站 - 初入门

文章目录 一、Jekyll介绍二、Jekyll安装和启动2.1 配置Ruby环境1&#xff09;Windows2&#xff09;macOS 2.2 安装 Jekyll2.3 构建Jekyll项目2.4 启动 Jekyll 服务 三、Jekyll常用命令四、目录结构4.1 主要目录4.2 其他的约定目录 五、使用GitLink构建Jekyll博客5.1 生成Jekyll…

JAVA:JDK17新特性迈向更强大的编程体验

1、简述 随着技术的不断发展&#xff0c;Java语言也在不断演进&#xff0c;为开发者提供更强大、高效的编程体验。在JDK 17中&#xff0c;我们迎来了一系列引人注目的新特性&#xff0c;这些特性不仅使得Java更现代化&#xff0c;同时也进一步提升了开发效率和代码质量。本文将…

CleanMyMac X2024免费许可证及功能详细讲解

一些用户反映自己的CleanMyMac卸载不干净&#xff1f;你的卸载方式正确码&#xff1f;当你在Mac上安装使用CleanMyMac后&#xff0c;需要将软件卸载&#xff0c;你会使用怎样方法完成操作呢&#xff1f;小编今天主要讲解如何卸载CleanMyMac以及卸载这款软件时应该注意的事项。一…

OpenGL :LearnOpenGL笔记

glfw https://github.com/JoeyDeVries/LearnOpenGL/blob/master/src/1.getting_started/1.1.hello_window/hello_window.cpp #include <glad/glad.h>// 注: GLAD的include文件包含所需的OpenGL头文件(如GL/GL.h) &#xff0c;因此确保在其他需要OpenGL的头文件 (如GLFW…

【SQL经典题目】连续日期判断、同时在线人数、会话划分、间隔日期连续、日期交叉

【1.查询至少连续3天下单的用户】 思路1&#xff08;使用lead&#xff09;&#xff1a; distinct user_id,create_date去重&#xff0c;确保每个用户每天只有一条访问记录lead(create_date,2,‘9999-12-31’) over(partition by user_id order by create_date)根据用户分区&am…

【Unity游戏制作】游戏模型导入之前需要注意的三个基本点

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…

使用Dependency Walker和Process Explorer排查瑞芯微工具软件RKPQTool.exe启动报错问题

目录 1、问题说明 2、使用Dependency Walker查看工具程序的库依赖关系 3、在可以运行的电脑上使用Process Explorer查看依赖的msvcr120.dll和msvcp120.dll库的路径 4、C/C运行时库介绍 5、可以下载安装VC_redist.x86.exe或VC_redist.x64.exe解决系统库缺失问题 C软件异常排…