《动手学深度学习 Pytorch版》 8.5 循环神经网络的从零开始实现

%matplotlib inline
import math
import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l
batch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)  # 仍然使用时间机器数据集

8.5.1 独热编码

采样的小批量数据形状是二维张量:(批量大小,时间步数)。one_hot 函数将这样一个小批量数据转换成形状为(时间步数,批量大小,词表大小)的输出。这将使我们能够更方便地通过最外层的维度, 一步一步地更新小批量数据的隐状态。

F.one_hot(torch.tensor([0, 2]), 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, 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]])
X = torch.arange(10).reshape((2, 5))
F.one_hot(X.T, 28).shape  # 转置一下把时间放前面
torch.Size([5, 2, 28])

8.5.2 初始化模型参数

def get_params(vocab_size, num_hiddens, device):num_inputs = num_outputs = vocab_size  # 输入是独热编码所以长度是vocab_size;输出是在vocab里面预测所以长度也是vocab_sizedef 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]for param in params:param.requires_grad_(True)return params

8.5.3 循环神经网络

init_rnn_state 函数在初始化时返回隐状态。这个函数返回的是一个全用 0 填充的张量,张量形状为(批量大小,隐藏单元数)。

def init_rnn_state(batch_size, num_hiddens, device):return (torch.zeros((batch_size, num_hiddens), device=device), )

rnn 函数在一个时间步内计算隐状态和输出。这里使用 tanh 函数作为激活函数。如前节所述,当元素在实数上满足均匀分布时,tanh 函数的平均值为0。

def rnn(inputs, state, params):  # inputs的形状:(时间步数量,批量大小,词表大小)W_xh, W_hh, b_h, W_hq, b_q = paramsH, = stateoutputs = []for X in inputs:  # X的形状:(批量大小,词表大小) 前面转置是为了这里遍历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_hiddensself.params = get_params(vocab_size, num_hiddens, device)self.init_state, self.forward_fn = init_state, forward_fndef __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)

测试输出维度是否不变。

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())
Y, new_state = net(X.to(d2l.try_gpu()), state)
Y.shape, len(new_state), new_state[0].shape
(torch.Size([10, 28]), 1, torch.Size([2, 512]))

8.5.4 预测

首先定义预测函数来生成 prefix (用户提供的多字符的字符串)之后的新字符。

在循环遍历 prefix 中的开始字符时,不断地将隐状态传递到下一个时间步,但是不生成任何输出。这被称为 预热期(warm-up),因为在此期间模型会自我更新(例如,更新隐状态),但不会进行预测。预热期结束后,隐状态的值通常比刚开始的初始值更适合预测,从而预测字符并输出它们。

def predict_ch8(prefix, num_preds, net, vocab, device):  #@save"""在prefix后面生成新字符"""state = net.begin_state(batch_size=1, device=device)outputs = [vocab[prefix[0]]]  # 调用 vocab 类的 __getitem__ 方法get_input = lambda: torch.tensor([outputs[-1]], device=device).reshape((1, 1))  # 把预测结果(结果的最后一个)作为下一个的输入for y in prefix[1:]:  # 预热期 把前缀先载进模型_, state = net(get_input(), state)outputs.append(vocab[y])for _ in range(num_preds):  # 预测 num_preds 步y, state = net(get_input(), state)outputs.append(int(y.argmax(dim=1).reshape(1)))  # 优雅return ''.join([vocab.idx_to_token[i] for i in outputs])
predict_ch8('time traveller ', 10, net, vocab, d2l.try_gpu())  # 没有训练过,会生成荒谬的结果
'time traveller gnwvr gnwv'

8.5.5 梯度裁剪

对于长度为 T T T 的序列,我们在迭代中计算 T T T 这个时间步上的梯度,将会在反向传播过程中产生长度为 O ( T ) O(T) O(T) 的矩阵乘法链。当 T T T 较大时,数值不稳定可能导致梯度爆炸或梯度消失。

一个流行的替代方案是通过将梯度 g g g 投影回给定半径 θ \theta θ 的球来裁剪梯度:

g ← min ⁡ ( 1 , θ ∣ ∣ g ∣ ∣ ) g g\gets\min\left(1,\frac{\theta}{||g||}\right)g gmin(1,∣∣g∣∣θ)g

def grad_clipping(net, theta):  #@save"""裁剪梯度"""if isinstance(net, nn.Module):params = [p for p in net.parameters() if p.requires_grad]else:params = net.paramsnorm = torch.sqrt(sum(torch.sum((p.grad ** 2)) for p in params))  # 计算范数if norm > theta:  # 限制梯度范围for param in params:param.grad[:] *= theta / norm

8.5.6 训练

与之前训练模型的方式有三个不同之处:

  • 序列数据的不同采样方法将导致隐状态初始化的差异。

    • 当使用顺序分区时, 我们只在每个迭代周期的开始位置初始化隐状态。

      由于下一个小批量数据中的序列样本 与当前子序列样本相邻,因此当前小批量数据最后一个样本的隐状态将用于初始化下一个小批量数据第一个样本的隐状态。

    • 当使用随机抽样时,因为每个样本都是在一个随机位置抽样的,因此需要为每个迭代周期重新初始化隐状态。

  • 在更新模型参数之前裁剪梯度。即使训练过程中某个点上发生了梯度爆炸,也能保证模型不会发散。

    • 在任何一点隐状态的计算,都依赖于同一迭代周期中前面所有的小批量数据,这使得梯度计算变得复杂。为了降低计算量,在处理任何一个小批量数据之前,我们先分离梯度,使得隐状态的梯度计算总是限制在一个小批量数据的时间步内。
  • 我们用困惑度来评价模型,确保了不同长度的序列具有可比性。

#@save
def train_epoch_ch8(net, train_iter, loss, updater, device, use_random_iter):"""训练网络一个迭代周期(定义见第8章)"""state, timer = None, d2l.Timer()metric = d2l.Accumulator(2)  # 训练损失之和,词元数量for X, Y in train_iter:if state is None or use_random_iter:# 在第一次迭代或使用随机抽样时初始化statestate = 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 = d2l.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  # 因为只使用了10000个词元,所以模型需要更多的迭代周期来更好地收敛
train_ch8(net, train_iter, vocab, lr, num_epochs, d2l.try_gpu())  # 先来一波默认的顺序分区
困惑度 1.0, 84837.9 词元/秒 cuda:0
time traveller for so it will be convenient to speak of himwas e
travelleryou can show black is white by argument said filby

在这里插入图片描述

困惑度 1.0,属于是把书背下来了。

net1 = 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)  # 使用随机抽样方法
困惑度 1.5, 79291.4 词元/秒 cuda:0
time traveller held in his hand was a glitteringmetallic framewo
travellerit would be remarkably convenient for the historia

在这里插入图片描述

练习

(1)尝试说明独热编码等价于为每个对象选择不同的嵌入表示。

不会,略


(2)通过调整超参数(如轮数、隐藏单元数、小批量数据的时间步数、学习率等)来改善困惑度。

a. 困惑度可以降到多少?

b. 用可学习的嵌入表示替换独热编码,是否会带来更好的表现?

c. 如果用H.G.Wells的其他书作为数据集时效果如何, 例如世界大战?

能降,b c太麻烦了,略。

batch_size, num_steps2 = 32, 64
train_iter2, vocab = d2l.load_data_time_machine(batch_size, num_steps2)num_hiddens2 = 1024
net2 = RNNModelScratch(len(vocab), num_hiddens2, d2l.try_gpu(), get_params,init_rnn_state, rnn)num_epochs2, lr2 = 1000, 0.5
train_ch8(net2, train_iter2, vocab, lr2, num_epochs2, d2l.try_gpu(),use_random_iter=True)  # 使用随机抽样方法,顺序分区已经降无可降了
困惑度 1.2, 58441.2 词元/秒 cuda:0
time traveller for so it will be convenient to speak of himwas e
traveller with a slight accession ofcheerfulness really thi

在这里插入图片描述


(3)修改预测函数,例如使用抽样,而不是选择最有可能的下一个字符。

a. 会发生什么?

b. 调整模型使之偏向更可能的输出,例如,当 α > 1 \alpha >1 α>1,从 q ( x t ∣ x t − 1 , … , x 1 ) ∝ P ( x t ∣ x t − 1 , … , x 1 ) α q(x_t|x_{t-1},\dots,x_1)\propto P(x_t|x_{t-1},\dots,x_1)^\alpha q(xtxt1,,x1)P(xtxt1,,x1)α 中采样。

不会,略。


(4)在不裁剪梯度的情况下运行本节中的代码会发生什么?

略。

def train_epoch_ch8_4(net, train_iter, loss, updater, device, use_random_iter):state, timer = None, d2l.Timer()metric = d2l.Accumulator(2)for X, Y in train_iter:if state is None or use_random_iter:state = net.begin_state(batch_size=X.shape[0], device=device)else:if isinstance(net, nn.Module) and not isinstance(state, tuple):state.detach_()else: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)  # 去掉梯度裁剪updater(batch_size=1)metric.add(l * y.numel(), y.numel())return math.exp(metric[0] / metric[1]), metric[1] / timer.stop()def train_ch8_4(net, train_iter, vocab, lr, num_epochs, device,use_random_iter=False):loss = nn.CrossEntropyLoss()animator = d2l.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_4(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'))
net3 = RNNModelScratch(len(vocab), num_hiddens, d2l.try_gpu(), get_params,init_rnn_state, rnn)
num_epochs, lr = 500, 1
train_ch8_4(net3, train_iter, vocab, lr, num_epochs, d2l.try_gpu())  # 去掉梯度裁剪直接爆炸
困惑度 195745164855533848843693558201405885920387170281738976926429980299285757160792307487577948624519168.0, 92924.4 词元/秒 cuda:0
time travellertttttttttttttttttttttttttttttttttttttttttttttttttt
travellertttttttttttttttttttttttttttttttttttttttttttttttttt

在这里插入图片描述


(5)更改顺序划分,使其不会从计算图中分离隐状态。运行时间会有变化吗?困惑度呢?

略。


(6)用 ReLU 替换本节中使用的激活函数,并重复本节中的实验。我们还需要梯度裁剪吗?为什么?

def rnn_6(inputs, state, params):W_xh, W_hh, b_h, W_hq, b_q = paramsH, = stateoutputs = []for X in inputs:H = torch.relu(torch.mm(X, W_xh) + torch.mm(H, W_hh) + b_h)Y = torch.mm(H, W_hq) + b_qoutputs.append(Y)return torch.cat(outputs, dim=0), (H,)
net4 = RNNModelScratch(len(vocab), num_hiddens, d2l.try_gpu(), get_params,init_rnn_state, rnn_6)
num_epochs, lr = 500, 1
train_ch8_4(net4, train_iter, vocab, lr, num_epochs, d2l.try_gpu())  # 用 Relu 好像不用裁剪也行哇,收敛更快了
困惑度 1.0, 83541.7 词元/秒 cuda:0
time traveller for so it will be convenient to speak of himwas e
traveller with a slight accession ofcheerfulness really thi

在这里插入图片描述

net5 = RNNModelScratch(len(vocab), num_hiddens, d2l.try_gpu(), get_params,init_rnn_state, rnn_6)
num_epochs, lr = 500, 1
train_ch8(net5, train_iter, vocab, lr, num_epochs, d2l.try_gpu())  # 换了 Relu 好像用不用裁剪没差
困惑度 1.0, 88798.8 词元/秒 cuda:0
time traveller with a slight accession ofcheerfulness really thi
traveller with a slight accession ofcheerfulness really thi

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微信小程序入门级

目录 一.什么是小程序? 二.小程序可以干什么? 三.入门使用 3.1. 注册 3.2. 安装 3.3.创建项目 3.4.项目结构 3.5.应用 好啦今天就到这里了,希望能帮到你哦!!! 一.什么是小程序? 微信小程…

PyTorch 深度学习之卷积神经网络(基础篇)Basic CNN(九)

0. Revision: Fully connected Neural Network 全连接 1. Convolution Neural Network 保留空间信息 1.1 Convolution Convolution-Single Input Channel 单通道 数乘 3 input Channels 3通道 N input Channels N input Channels and M output channel M 个卷积核 1.2 conv…

npm安装依赖报错npm ERR! code ENOTFOUND npm ERR! errno ENOTFOUND、npm run dev报错记录

npm安装依赖报错npm ERR! code ENOTFOUND npm ERR! errno ENOTFOUND_得我所得,爱我所爱的博客-CSDN博客npm安装依赖报错今天在学习webpack的时候,在使用npm install来安装一个局部的webpack时候,报出一下错误:npm ERR! code ENOTFOUNDnpm ERR…

Python批量测试IP端口GUI程序(Tkinter)

一、实现样式 批量IP与端口中间用“,”分割,点击Telnet进行测试,前提是你电脑安装了telnet客户端,Clear按钮用来清空文本框。 二、核心点 1、使用Tkinter来制作桌面GUI页面 2、使用telnetlib模块测试telnet端口 三、困难点 1、测试结果…

15.项目讲解之前端页面的实现

项目讲解之前端页面的实现 本项目前端使用HBuilerX软件编写HBuilderX下载安装配置一键直达, uniapp框架uniapp官网, 使用Element-ui组件Element-ui组件网址进行前端页面的完成。 前端项目下载地址 前端项目 前端项目展示 首页 首页展示 echarts实现…

前端页面布局之【响应式布局】

目录 🌟前言🌟优点🌟缺点🌟media兼容性🌟利用CSS3-Media Query实现响应式布局🌟常见的媒体类型🌟常见的操作符🌟属性值🌟设备检测🌟响应式阈值选取&#x1f3…

【使用教程】在Ubuntu下PMM60系列一体化伺服电机通过SDO跑循环同步位置模式详解

本教程将指导您在Ubuntu操作系统下使用SDO(Service Data Object)来配置和控制PMM60系列一体化伺服电机以实现循环同步位置模式。我们将介绍必要的步骤和命令,以确保您能够成功地配置和控制PMM系列一体化伺服电机。 01.准备工作 在正式介绍之…

位于同一子网下的ip在子网掩码配置错误的情况下如何进行通信(wireshrak抓包分析)

前言 最近看书发现个问题,正好想学习下wireshark的使用,于是抓包做了下实验。 问题是这样的,假设有服务器A和服务器B,正确配置下两者处于同一子网;此时B的网络配置正确,而A在配置子网掩码时出了错&#xff…

PromptScript:轻量级 DSL 脚本,加速多样化的 LLM 测试与验证

TL;DR 版本 PromptScript 是一个轻量级的 Prompt 调试用的 DSL (Yaml)脚本,以用于快速使用、构建 Prompt。 PromptScript 文档:https://framework.unitmesh.cc/prompt-script Why PromptScript ? 几个月前&…

Qtcreator console 中文 乱码

开发环境:windows11 x64 位;Qt Creator 11.0.3;Based on Qt 6.4.1 (MSVC 2019, x86_64) 报错内容如图所示: 解决方法如下:

unity2022版本 实现手机虚拟操作杆

简介 在许多移动游戏中,虚拟操纵杆是一个重要的用户界面元素,用于控制角色或物体的移动。本文将介绍如何在Unity中实现虚拟操纵杆,提供了一段用于移动控制的代码。我们将讨论不同类型的虚拟操纵杆,如固定和跟随,以及如…

关于EEGLAB安装时报错“未定义函数或者变量‘EEGLAB’”

按照其他博主写的,下载EEGLAB(最新版),然后解压,打开matlab(2019a)导入路径,在前述步骤都正确的情况下,命令行输入eeglab,matlab提示"未定义函数或者变量…

网络原理之初识

文章目录 前言计算机网络的发展史网络互连局域网局域网组建网络的方式 广域网网络通信基础IP 地址端口号网络协议五元组协议分层OSI 七层模型TCP/IP 五层协议网络设备所在分层 封装和分用 前言 在这个信息爆炸的时代,计算机网络已经渗透到我们生活的方方面面&#…

算法村开篇

大家好我是苏麟从今天开始我将带来算法的一些习题和心得体会等等...... 算法村介绍 我们一步步地学习算法本专栏会以闯关的方式来学习算法 循序渐进地系统的学习算法并掌握大部分面试知识 , 期待和大家一起进步 . 索大祝大家学有所成 , 前程似锦.

ESP32网络开发实例-从SD卡加载Web页面文件

从SD卡加载Web页面文件 文章目录 从SD卡加载Web页面文件1、应用介绍2、软件准备3、硬件准备4、Web页面代码实现5、Web服务器代码实现在文中,将展示如何构建一个 Web 服务器,为存储在SD卡中的 HTML 和 CSS 文件提供服务。 我们不必将 HTML 和 CSS 文本硬编码入代码中,而是创建…

多目标水母搜索算法(Multi-Objective Jellyfish Search algorithm,MOJS)求解微电网优化--提供MATLAB代码

一、微网系统运行优化模型 微电网优化模型介绍: 微电网多目标优化调度模型简介_IT猿手的博客-CSDN博客 参考文献: [1]李兴莘,张靖,何宇,等.基于改进粒子群算法的微电网多目标优化调度[J].电力科学与工程, 2021, 37(3):7 二、多目标水母搜索算法MOJS …

局域网上IP多播与IP单播关于MAC地址的区别

IP单播进行到局域网上的时候: 网际层使用IP地址进行寻址,各路由器收到IP数据报后,根据其首部中的目的IP地址的网络号部分,基于路由表进行查表转发。 查表转发的结果可指明IP数据报的下一跳路由器的IP地址,但无法指明…

数据库安全-H2 databaseElasticsearchCouchDBInfluxdb漏洞复现

目录 数据库安全-H2 database&Elasticsearch&CouchDB&Influxdb 复现influxdb-未授权访问-jwt 验证H2database-未授权访问-配置不当CouchDB-权限绕过配合 RCE-漏洞CouchDB 垂直权限绕过Couchdb 任意命令执行 RCE ElasticSearch-文件写入&RCE-漏洞Elasticsearch写…

School‘s Java test

欢迎来到Cefler的博客😁 🕌博客主页:那个传说中的man的主页 🏠个人专栏:题目解析 🌎推荐文章:题目大解析(3) 目录 👉🏻第四周素数和念整数 &#…

【PostgreSQL启动,停止命令(重启)】

找到 /usr/lib/systemd/system文件夹路径看是否包含 postgresql服务 关闭服务: systemctl stop postgresql-12.service启动服务 systemctl start postgresql-12.service重启服务 systemctl restart postgresql-12查看状态 systemctl status postgresql-12.servi…