《动手学深度学习 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.mzph.cn/news/106129.shtml

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

相关文章

VSCode自定义代码块详解

第一步:点击文件-首选项-用户代码片段 第二步:选择代码块作用域的文件类型 类型一:全局作用域 这种类型的代码块是创建在vscode软件内部的文件。是跟随这当前安装的vscode这个软件的,不会随着项目的关闭而失效,会一直存…

Gpt-4多模态功能强势上线,景联文科技多模态数据采集标注服务等您来体验!

就在上个月,OpenAI 宣布对ChatGPT 进行重大更新,该模型不仅能够通过文字输入进行识别和分析,还能够通过语音、图像甚至视频等多种模态的输入来获取、识别、分析和输出信息。这一重要技术突破,将促进多模态自然语言处理的发展&…

Android位置服务和应用权限

Github:https://github.com/MADMAX110/Odometer 一、使用位置服务 之前的Odometer应用是显示一个随机数,现在要使用Android的位置服务返回走过的距离。 修改getDiatance方法使其返回走过的距离,为此要用Android的位置服务。这些服务允许你得到用户的当…

巧用正则表达式

文章目录 题目巧用正则表达式,题目将十进制转为16进制,可以采用Java的语法来表示 题目 巧用正则表达式,题目将十进制转为16进制,可以采用Java的语法来表示 String nInteger.toString(num,16); 那如何确定是否都是字母呢a-f呢&…

车载多源融合定位

终端硬件由两部分组成,组合导航处理板和地磁导航处理板。 组合导航处理板负责采集加速度计、陀螺、GNSS和轮速计等数据进行组合导航解算,差分数据通过6Q主板获取到后通过串口发送至组合导航处理板。地磁导航处理板负责地磁数据采集,保存至数…

Rxjava3 全新详解及常用操作符

简介 RxJava 是一个基于 Java 的响应式编程库,用于处理异步事件流和数据流。它是由 Netflix 开发并开源,现在广泛用于 Android 和 Java 后端开发。RxJava 提供了一种用于组合和处理异步数据的丰富工具集,它的核心思想是将数据流视为一系列事…

微信发红包(各种红包类型)-测试用例设计

微信发红包(各种红包类型)

总结10.15

项目进展 登陆注册,连接了数据库,找回密码写到了通过给邮箱发送验证码,然后重新输入密码 项目看法 之后俩天加紧把这个登陆注册这些搞完,注册用到的随机生成一个账号且不重复,且设置一个邮箱作为之后找回密码时候的…

CVPR 2023 | 数据驱动的解释对分布外数据具有鲁棒性吗?

论文链接: https://arxiv.org/abs/2303.16390 代码链接: https://github.com/tangli-udel/DRE 01. 研究背景:数据驱动的解释对分布外数据具有鲁棒性吗? 近年来,将黑盒机器学习(ML)模型用于高风…

CentOS 7 编译安装Boost

1、前提条件 linux平台/CentOS 7 下要编译安装Boost除gcc和gcc-c之外,还需要两个开发库:bzip2-devel 和python-devel ,因此在安装前应该先保证这两个库已经安装。 安装指令: yum install bzip2 bzip2-devel bzip2-libs python-devel Cent…

zookeeper源码学习笔记(一)

一、缘起 1、CP还是AP 作为一个在大数据行业工作了7~8年的老兵,在被问到zookeeper和CAP时,竟然有些恍惚,AP还是CP? 看了一些博文,答案几乎都是CP? zookeeper的实现中,P是一定的&…

低代码提速应用开发

低代码介绍 低代码平台是指一种能够帮助企业快速交付业务应用的平台。自2000年以来,低代码市场一直充斥着40大大小小的各种玩家,比如国外的Appian、K2、Pega Systems、Salesforce和Ultimus,国内的H3 BPM。 2015年以后,这个市场更是…

2023年厦门市高等职业院校技能竞赛软件测试竞赛规程

2023年厦门市高等职业院校技能竞赛 软件测试竞赛规程 一、赛项名称 赛项名称:软件测试 竞赛形式:团体赛 赛项专业大类:电子信息 二、竞赛目的 (一)检验教学成效 本赛项竞赛内容以《国家职业教育改革实施方案》为设计方…

Docker逃逸---procfs文件挂载

一、产生原因 将宿主机/proc目录挂载进了容器,而该目录内的/proc/sys/kernel/core_pattern文件是负责进程奔溃时内存数据转储的,当第一个字符是| 管道符时,后面的部分会以命令行的方式进行解析并运行,攻击者可以将恶意文件写入该…

【Python数据分析工具】

文章目录 概要整体架构流程技术名词解释 概要 数据分析是一种通过收集、处理、分析和解释大量数据,以发现有价值信息、洞察趋势、制定决策并解决问题的过程。在现代科技和互联网的推动下,数据分析变得日益重要。它不仅仅是对数字和图表的简单解释&#…

MacOS ventura跳过配置锁

Macbook pro 2021跳配置锁 1.什么是配置锁? 配置锁顾名思义就是美国一些企业和公司向苹果工公司定制采购的机器,这些机器一般供应内部员工使用,这种机器和正常机没有什么区别,也是无锁三网机器,功能和正常机器一摸一…

如何用精准测试来搞垮团队?

测试行业每年会冒出来一些新鲜词:混沌工程、精准测试、AI测试…… 这些新概念、新技术让我们感到很焦虑,逼着自己去学习和了解这些新玩意,担心哪一天被淘汰掉。 以至于给我这样的错觉,当「回归测试」、「精准测试」这两个词摆在一…

解决git在window11操作很慢,占用很大cpu的问题

【git在window11操作很慢,占用很大cpu,最后也执行失败】 在谷歌输入:git very slow in window 11。通过下面链接终于找到了解决方案: https://www.reddit.com/r/vscode/comments/sulebx/slow_git_in_wsl_after_updating_to_window…

怒刷LeetCode的第26天(Java版)

目录 第一题 题目来源 题目内容 解决方法 方法一:动态规划 第二题 题目来源 题目内容 解决方法 方法一:有限状态自动机 方法二:正则表达式 第三题 题目来源 题目内容 解决方法 方法一:从最后一位向前遍历 方法二…

测试除了点点点,还有哪些内容呢?

今天和一个网友讨论了一下关于互联网行业中测试的情况,希望能够了解现在的互联网行业主要的测试工作内容。小编根据以往的工作经历和经验情况,来做一个总结和整理。 1、岗位分类 现在的岗位划分主要是分为两大类:测试工程师 和 测试开发工程…