动手学深度学习(Pytorch版)代码实践 -循环神经网络-54~55循环神经网络的从零开始实现和简洁实现

news2024/11/20 0:34:24

54循环神经网络的从零开始实现

在这里插入图片描述

import math
import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l
import matplotlib.pyplot as plt
import liliPytorch as lp

# 读取H.G.Wells的时光机器数据集
batch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)

# 查看数据集
# for X, Y in train_iter:
#     print('X:', X.shape)
#     print('Y:', Y.shape)
# print(vocab.token_freqs)
# print(vocab.idx_to_token)
# print(vocab.token_to_idx)

# 独热编码
# 将每个索引映射为相互不同的单位向量: 假设词表中不同词元的数目为N(即len(vocab)), 词元索引的范围为0
# 到N-1。 如果词元的索引是整数i, 那么我们将创建一个长度为N的全0向量, 并将第i处的元素设置为1。 
# 此向量是原始词元的一个独热向量。
# print(F.one_hot(torch.tensor([0,3,6]), len(vocab)))
"""
tensor([[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
         0, 0, 0, 0],
        [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,        
         0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,        
         0, 0, 0, 0]])
"""

# 每次采样的小批量数据形状是二维张量: (批量大小,时间步数)。 
# one_hot函数将这样一个小批量数据转换成三维张量, 张量的最后一个维度等于词表大小(len(vocab))。
# 我们经常转换输入的维度,以便获得形状为 (时间步数,批量大小,词表大小)的输出。 
# 这将使我们能够更方便地通过最外层的维度, 一步一步地更新小批量数据的隐状态。

# X = torch.arange(10).reshape((2, 5))
# print(X)
# tensor([[0, 1, 2, 3, 4],
#         [5, 6, 7, 8, 9]])
# print(X.T)
# tensor([[0, 5],
#         [1, 6],
#         [2, 7],
#         [3, 8],
#         [4, 9]])
# print(F.one_hot(X.T, 28).shape) # torch.Size([5, 2, 28])
# print(F.one_hot(X.T, 28))
"""
tensor([[[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
          0, 0, 0, 0, 0],
         [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
          0, 0, 0, 0, 0]],

        [[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
          0, 0, 0, 0, 0],
         [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
          0, 0, 0, 0, 0]],

        [[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
          0, 0, 0, 0, 0],
         [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
          0, 0, 0, 0, 0]],

        [[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
          0, 0, 0, 0, 0],
         [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
          0, 0, 0, 0, 0]],

        [[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
          0, 0, 0, 0, 0],
         [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
          0, 0, 0, 0, 0]]])
"""

# 初始化模型参数
def get_params(vocab_size, num_hiddens, device):
    # 设置输入和输出的数量为词汇表的大小
    num_inputs = num_outputs = vocab_size

    # 定义一个函数,用于以正态分布初始化权重
    def normal(shape):
        return torch.randn(size=shape, device=device) * 0.01

    # 初始化隐藏层参数
    W_xh = normal((num_inputs, num_hiddens))  # 输入到隐藏层的权重
    W_hh = normal((num_hiddens, num_hiddens))  # 隐藏层到隐藏层的权重(循环权重)
    b_h = torch.zeros(num_hiddens, device=device)  # 隐藏层的偏置

    # 初始化输出层参数
    W_hq = normal((num_hiddens, num_outputs))  # 隐藏层到输出层的权重
    b_q = torch.zeros(num_outputs, device=device)  # 输出层的偏置

    # 将所有参数收集到一个列表中
    params = [W_xh, W_hh, b_h, W_hq, b_q]

    # 设置每个参数的requires_grad属性为True,以便在反向传播期间计算梯度
    for param in params:
        param.requires_grad_(True)

    return params  # 返回参数列表

# 循环神经网络模型
# 初始化时返回隐状态
def init_rnn_state(batch_size, num_hiddens, device):
    # batch_size:批量的大小,即每次输入到RNN的序列数量。
    # num_hiddens:隐藏层单元的数量,即隐藏状态的维度。
    return (torch.zeros((batch_size, num_hiddens), device=device), ) # 返回一个包含一个张量的元组


def rnn(inputs, state, params):
    # inputs的形状:(时间步数量,批量大小,词表大小)
    # state:初始隐藏状态,通常是一个元组,包含隐藏层的状态。
    # params:RNN的参数,包含权重和偏置。
    W_xh, W_hh, b_h, W_hq, b_q = params
    H, = state # 当前的隐藏状态。
    outputs = []
    # X的形状:(批量大小,词表大小)
    for X in inputs:
        H = torch.tanh(torch.mm(X, W_xh) + torch.mm(H, W_hh) + b_h)
        Y = torch.mm(H, W_hq) + b_q
        outputs.append(Y)
    return torch.cat(outputs, dim=0), (H,)

# 存储从零开始实现的循环神经网络模型的参数
class RNNModelScratch: #@save
    """从零开始实现的循环神经网络模型"""
    def __init__(self, vocab_size, num_hiddens, device,
                 get_params, init_state, forward_fn):
        self.vocab_size, self.num_hiddens = vocab_size, num_hiddens
        self.params = get_params(vocab_size, num_hiddens, device)
        self.init_state, self.forward_fn = init_state, forward_fn

    def __call__(self, X, state): # 前向传播方法
        X = F.one_hot(X.T, self.vocab_size).type(torch.float32)
        return self.forward_fn(X, state, self.params)

    def begin_state(self, batch_size, device): # 初始化隐藏状态
        return self.init_state(batch_size, self.num_hiddens, device)


# X = torch.arange(10).reshape((2, 5))
num_hiddens = 512
# net = RNNModelScratch(len(vocab), num_hiddens, d2l.try_gpu(), get_params,
#                       init_rnn_state, rnn)
# state = net.begin_state(X.shape[0], d2l.try_gpu()) # 初始化隐藏状态
# 调用模型实例的 __call__ 方法执行前向传播。
# Y, new_state = net(X.to(d2l.try_gpu()), state)
# Y:模型输出。
# new_state:更新后的隐藏状态。

# print(Y.shape, len(new_state), new_state[0].shape)
# torch.Size([10, 28]) 1 torch.Size([2, 512])
# 输出形状是(时间步数 X 批量大小,词表大小), 而隐状态形状保持不变,即(批量大小,隐藏单元数)


def predict_ch8(prefix, num_preds, net, vocab, device):  #@save
    """在prefix后面生成新字符
        prefix:生成文本的前缀,即初始输入字符序列。
        num_preds:要预测的字符数。
        net:训练好的循环神经网络模型。
        vocab:词汇表,包含字符到索引和索引到字符的映射。
    """
    state = net.begin_state(batch_size=1, device=device)
    outputs = [vocab[prefix[0]]] # outputs:用于存储生成字符的索引列表。
    get_input = lambda: torch.tensor([outputs[-1]], device=device).reshape((1, 1))
    for y in prefix[1:]:  # 预热期,遍历前缀中的剩余字符(从第二个字符开始)。
        _, state = net(get_input(), state) # 调用 net 进行前向传播,更新隐藏状态 state。
        outputs.append(vocab[y]) # 将当前字符的索引添加到 outputs 中。
        
    for _ in range(num_preds):  # 预测num_preds步
        # 调用 net 进行前向传播,获取预测结果 y 和更新后的隐藏状态 state。
        y, state = net(get_input(), state)
        # 使用 y.argmax(dim=1) 获取预测的字符索引,并将其添加到 outputs 中。
        outputs.append(int(y.argmax(dim=1).reshape(1)))
    return ''.join([vocab.idx_to_token[i] for i in outputs])

# print(predict_ch8('time traveller ', 10, net, vocab, d2l.try_gpu()))
# time traveller cfjwsthaqc

# 梯度裁剪
"""
在训练深层神经网络(特别是循环神经网络)时,梯度爆炸(gradients exploding)问题会导致梯度值变得非常大,
从而导致模型不稳定甚至训练失败。为了防止梯度爆炸,可以对梯度进行裁剪,使得梯度的范数不超过某个预设的阈值。
"""
def grad_clipping(net, theta):  #@save
    """裁剪梯度
        net:神经网络模型。
        theta:梯度裁剪的阈值。
    """
    if isinstance(net, nn.Module):
        params = [p for p in net.parameters() if p.requires_grad]
    else:
        params = net.params
    # 计算梯度范数, L2 范数
    norm = torch.sqrt(sum(torch.sum((p.grad ** 2)) for p in params))
    if norm > theta:
        for param in params:
            param.grad[:] *= theta / norm
            # 将每个参数的梯度按比例缩放,使得新的梯度范数等于 theta。

# 训练
def train_epoch_ch8(net, train_iter, loss, updater, device, use_random_iter):
    """训练网络一个迭代周期(定义见第8章)"""
    state, timer = None, d2l.Timer()
    metric = lp.Accumulator(2)  # 训练损失之和,词元数量
    for X, Y in train_iter:
        if state is None or use_random_iter:
            # 在第一次迭代或使用随机抽样时初始化state
            state = net.begin_state(batch_size=X.shape[0], device=device)
        else:
            if isinstance(net, nn.Module) and not isinstance(state, tuple):
                # state对于nn.GRU是个张量
                state.detach_()
            else:
                # state对于nn.LSTM或对于我们从零开始实现的模型是个张量
                for s in state:
                    s.detach_()
        y = Y.T.reshape(-1)
        X, y = X.to(device), y.to(device)
        y_hat, state = net(X, state)
        l = loss(y_hat, y.long()).mean()
        if isinstance(updater, torch.optim.Optimizer):
            updater.zero_grad()
            l.backward()
            grad_clipping(net, 1)
            updater.step()
        else:
            l.backward()
            grad_clipping(net, 1)
            # 因为已经调用了mean函数
            updater(batch_size=1)
        metric.add(l * y.numel(), y.numel())
    return math.exp(metric[0] / metric[1]), metric[1] / timer.stop()

#@save
def train_ch8(net, train_iter, vocab, lr, num_epochs, device,
              use_random_iter=False):
    """训练模型(定义见第8章)"""
    loss = nn.CrossEntropyLoss()
    animator = lp.Animator(xlabel='epoch', ylabel='perplexity',
                            legend=['train'], xlim=[10, num_epochs])
    # 初始化
    if isinstance(net, nn.Module):
        updater = torch.optim.SGD(net.parameters(), lr)
    else:
        updater = lambda batch_size: d2l.sgd(net.params, lr, batch_size)

    predict = lambda prefix: predict_ch8(prefix, 50, net, vocab, device)
    # 训练和预测
    for epoch in range(num_epochs):
        ppl, speed = train_epoch_ch8(
            net, train_iter, loss, updater, device, use_random_iter)
        if (epoch + 1) % 10 == 0:
            print(predict('time traveller'))
            animator.add(epoch + 1, [ppl])
    print(f'困惑度 {ppl:.1f}, {speed:.1f} 词元/秒 {str(device)}')
    print(predict('time traveller '))
    print(predict('traveller '))

# 顺序抽样方法
num_epochs, lr = 500, 1
# train_ch8(net, train_iter, vocab, lr, num_epochs, d2l.try_gpu())
# plt.show()
"""
困惑度 1.0, 95138.3 词元/秒 cuda:0
time traveller you can show black is white by argument said filby
traveller you can show black is white by argument said filby
"""

# 随机抽样方法
net = RNNModelScratch(len(vocab), num_hiddens, d2l.try_gpu(), get_params,
                      init_rnn_state, rnn)
train_ch8(net, train_iter, vocab, lr, num_epochs, d2l.try_gpu(),
          use_random_iter=True)
plt.show()
"""
困惑度 1.3, 109268.9 词元/秒 cuda:0
time traveller held in his hand was a glitteringmetallic framewor
traveller held in his hand was a glitteringmetallic framewor
"""

顺序抽样:
在这里插入图片描述

随机抽样:
在这里插入图片描述

55循环神经网络的简洁实现

import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l
import matplotlib.pyplot as plt

# 加载时光机器数据集并设置批量大小和序列长度
batch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)

# 定义RNN模型
num_hiddens = 256
rnn_layer = nn.RNN(len(vocab), num_hiddens)

# 用零张量初始化隐藏状态
state = torch.zeros((1, batch_size, num_hiddens))
# print(state.shape) # torch.Size([1, 32, 256])

# X = torch.rand(size=(num_steps, batch_size, len(vocab)))
# Y, state_new = rnn_layer(X, state)
# print(Y.shape, state_new.shape, X.shape)
# torch.Size([35, 32, 256]) torch.Size([1, 32, 256]) torch.Size([35, 32, 28])

# 完整的循环神经网络模型定义了一个RNNModel类
#@save
class RNNModel(nn.Module):
    """循环神经网络模型"""
    def __init__(self, rnn_layer, vocab_size, **kwargs):
        super(RNNModel, self).__init__(**kwargs)
        self.rnn = rnn_layer
        self.vocab_size = vocab_size
        self.num_hiddens = self.rnn.hidden_size
        # 如果RNN是双向的,num_directions应该是2,否则应该是1
        if not self.rnn.bidirectional:
            self.num_directions = 1
            self.linear = nn.Linear(self.num_hiddens, self.vocab_size)
        else:
            self.num_directions = 2
            self.linear = nn.Linear(self.num_hiddens * 2, self.vocab_size)

    def forward(self, inputs, state):
        X = F.one_hot(inputs.T.long(), self.vocab_size)
        X = X.to(torch.float32)
        Y, state = self.rnn(X, state)
        # 全连接层首先将Y的形状改为(时间步数*批量大小,隐藏单元数)
        # 它的输出形状是(时间步数*批量大小,词表大小)。
        output = self.linear(Y.reshape((-1, Y.shape[-1])))
        return output, state

    def begin_state(self, device, batch_size=1):
        if not isinstance(self.rnn, nn.LSTM):
            # nn.GRU以张量作为隐状态
            return  torch.zeros((self.num_directions * self.rnn.num_layers,
                                 batch_size, self.num_hiddens),
                                device=device)
        else:
            # nn.LSTM以元组作为隐状态
            return (torch.zeros((
                self.num_directions * self.rnn.num_layers,
                batch_size, self.num_hiddens), device=device),
                    torch.zeros((
                        self.num_directions * self.rnn.num_layers,
                        batch_size, self.num_hiddens), device=device))

# 训练与预测

device = d2l.try_gpu()
net = RNNModel(rnn_layer, vocab_size=len(vocab))
net = net.to(device)
num_epochs, lr = 500, 1
d2l.train_ch8(net, train_iter, vocab, lr, num_epochs, device)
"""
perplexity 1.3, 236379.1 tokens/sec on cuda:0
time traveller held in his hand was a glitteringmetallic framewo
traveller fith a slan but move anotle bothe thon st stagee 
"""
plt.show()
print(d2l.predict_ch8('time traveller', 10, net, vocab, device))
# time traveller held in h

在这里插入图片描述

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

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

相关文章

鸿蒙‘ohpm‘ 不是内部或外部命令,也不是可运行的程序-解决方案

🔥 博客主页: 小韩本韩! ❤️ 感谢大家点赞👍收藏⭐评论✍️ 在鸿蒙的DevEco Studio的终端下输入 onpm -v 或者 你需要下载第三方ohpm包的时候提示‘ohpm‘ 不是内部或外部命令,也不是可运行的程序- 主要是因为我们…

节省上千元的SSL多域名证书申请方法

在数字化时代的浪潮中,网络安全问题日益凸显其重要性。 作为网络安全的核心组成部分,SSL证书(安全套接层证书)在确保数据传输的机密性、完整性和真实性方面发挥着至关重要的作用。 申请便宜SSL证书步骤 1. 登录来此加密网站&am…

2024年特种设备(电梯作业)题库考试题库

1.直接作用式液压电梯轿厢与柱塞(缸筒)之间的连接应为()。 A.刚性连接 B.固定连接 C.法兰连接 D.挠性连接 答案:D 2.正常情况下,当电磁式继电器线圈得电时,其常开触点将(&…

【数据结构】08.堆及堆的应用

一、堆的概念及结构 堆(Heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵完全二叉树的数组对象。 堆是非线性数据结构,相当于一维数组,有两个直接后继。 如果有一个关键码的集合K { k₀,k₁,k₂ &#…

MySQL数据库树状结构查询

一、树状结构 MySQL数据库本身并不直接支持树状结构的存储,但它提供了足够的灵活性,允许我们通过不同的方法来模拟和实现树状数据结构。具体方法看下文。 数据库表结构: 实现效果 查询的结果像树一样 二、使用 以Catalog数据表&#xff0c…

ctfshow-web入门-文件包含(web82-web86)条件竞争实现session会话文件包含

目录 1、web82 2、web83 3、web84 4、web85 5、web86 1、web82 新增过滤点 . ,查看提示:利用 session 对话进行文件包含,通过条件竞争实现。 条件竞争这个知识点在文件上传、不死马利用与查杀这些里面也会涉及,如果大家不熟悉…

照片边框添加 | Python | 免费无广告

演示图 说明 照片边框添加 | Python | 免费无广告 🔅理论上Mac及Windos都可运行,只需要python环境即可~~~ 🔅目前提供了两种样式,白色边框以及透明边框:P2是原图,P3是白色边框的效果,P4是透明边框效果。 …

python: create Envircomnet in Visual Studio Code 创建虚拟环境

先配置python开发环境 1.在搜索栏输入“>" 或是用快捷组合键ctrlshiftP键 就会显示”>",再输入"python:" 选择已经安装好的python的版本,选定至当前项目中,都是按回车 就可以看到创建了一个虚拟环境的默认的文件夹名".venv" 2 …

动手学深度学习(Pytorch版)代码实践 -循环神经网络-53语言模型和数据集

53语言模型和数据集 1.自然语言统计 引入库和读取数据: import random import torch from d2l import torch as d2l import liliPytorch as lp import numpy as np import matplotlib.pyplot as plttokens lp.tokenize(lp.read_time_machine())一元语法&#xf…

FreeBSD@ThinkPad x250因电池耗尽关机后无法启动的问题存档

好几次碰到电池耗尽FreeBSD关机,再启动,网络通了之后到了该出Xwindows窗体的时候,屏幕灭掉,网络不通,只有风扇在响,启动失败。关键是长按开关键后再次开机,还是启动失败。 偶尔有时候重启到单人…

前端面试题16(跨域问题)

跨域问题源于浏览器的同源策略(Same-origin policy),这一策略限制了来自不同源的“写”操作(比如更新、删除数据等),同时也限制了读操作。当一个网页尝试请求与自身来源不同的资源时,浏览器会阻…

Redis基础教程(七):redis列表(List)

💝💝💝首先,欢迎各位来到我的博客,很高兴能够在这里和您见面!希望您在这里不仅可以有所收获,同时也能感受到一份轻松欢乐的氛围,祝你生活愉快! 💝&#x1f49…

Python酷库之旅-第三方库Pandas(005)

目录 一、用法精讲 7、pandas.read_clipboard函数 7-1、语法 7-2、参数 7-3、功能 7-4、返回值 7-5、说明 7-6、用法 7-6-1、代码示例 7-6-2、结果输出 8、pandas.DataFrame.to_clipboard函数 8-1、语法 8-2、参数 8-3、功能 8-4、返回值 8-5、说明 8-6、用法…

LivePortrait:一张照片生成生动视频,精准操控眼睛和嘴唇动作 本地一键整合包下载

LivePortrait,这个名字听起来就像是魔法,但它其实是现实世界中的黑科技。想象一下,你那尘封已久的相册里,那些定格在时间里的笑脸,突然间动了起来,眨眼、微笑、甚至说话,这不再是电影里的场景&a…

三相感应电机的建模仿真(2)基于ABC相坐标系S-Fun的仿真模型

1. 概述 2. 三相感应电动机状态方程式 3. 基于S-Function的仿真模型建立 4. 瞬态分析实例 5. 总结 6. 参考文献 1. 概述 前面建立的三相感应电机在ABC相坐标系下的数学模型是一组周期性变系数微分方程(其电感矩阵是转子位置角的函数,转子位置角随时…

DAY20-力扣刷题

1.填充每个节点的下一个右侧节点指针 116. 填充每个节点的下一个右侧节点指针 - 力扣(LeetCode) 方法一:层次遍历 class Solution {public Node connect(Node root) {if (root null) {return root;}// 初始化队列同时将第一层节点加入队列…

【网络管理工具】NETworkManager工具的基本使用教程

【网络管理工具】NETworkManager工具的基本使用教程 一、NETworkManager工具介绍1.1 NETworkManager简介1.2 NETworkManager特点1.3 NETworkManager使用场景 二、下载NETworkManager软件包2.1 下载地址2.2 下载软件 三、运行NETworkManager工具3.1 解压NETworkManager3.2 运行N…

搭建排查tomcat内存溢出问题的调试环境

上个月赶工上线的门户网站,由于种种原因导致部署到线上服务器后每隔一段时间后就会导致tomcat内存溢出,今天我就要来直面这个棘手的问题。 要解决的问题对我来说还是有点难度的,原因有二: 代码不是我写的;我对java并不…

【操作与配置】VSCode配置Python及Jupyter

Python环境配置 可以参见:【操作与配置】Python:CondaPycharm_pycharmconda-CSDN博客 官网下载Python:http://www.python.org/download/官网下载Conda:Miniconda — Anaconda documentation VSCode插件安装 插件安装后需重启V…

14-28 剑和诗人2 - 高性能编程Bend和Mojo

介绍: 在不断发展的计算世界中,软件和硬件之间的界限变得越来越模糊。随着我们不断突破技术可能性的界限,对能够利用现代硬件功能的高效、可扩展的编程语言的需求从未如此迫切。 Bend和 Mojo是编程语言领域的两种新秀,它们有望弥…