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

本节我们通过使用MXnet,来从零开始的实现一个含有隐藏状态的循环神经网络。

前序工作

  1. 数据集预处理
  2. 进行采样

实现循环神经网络

完成前序工作后,即可开始实现循环神经网络。本文首先构建一个具有隐状态的循环神经网络。其结构如图所示:

接下来,我们一边讲解循环神经网络的结构,一边构建循环神经网络。

首先需要引入使用到的库,并读取数据集,设置批量大小为32,时间步为35,通过前文(前序工作中的第二项)的加载数据集的函数对数据集进行读取,并完成采样:

%matplotlib inline
import math
from mxnet import autograd, gluon, np, npx
from d2l import mxnet as d2lnpx.set_np()batch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)

独热编码

将数字索引直接进行训练会使模型训练十分困难,因此我们使用独热编码(one-hot encoding)将训练集的数据进行转换——假设词元表中不同的词元共有N个,则生成一个长度为N的数组,将数组的对应位置设为1,其他位置均为零,这样做可以更好的展现数据的特征。

每次采样的数据形状是(批量大小,时间步数),通过独热编码,我们希望我们构建的训练数据的形状为(时间步数,批量大小,词表大小),即num_steps个,批量大小x词表大小的二维数组

所以我们需要先对输入X进行转置操作,随后进行独热编码,具体操作如下:

npx.one_hot(X.T, len(vocab))

初始化模型参数

我们直接使用如下的get_params函数来初始化模型参数,其中vocab_size表示词表大小,num_hiddens为超参数,表示隐藏层的大小,device为规定使用cpu运算还是gpu运算。

注:经过作者检验,此处RNN的训练是否使用GPU运算不太重要,都很快!

def get_params(vocab_size, num_hiddens, device):num_inputs = num_outputs = vocab_sizedef normal(shape):return np.random.normal(scale=0.01, size=shape, ctx=device)# 隐藏层参数W_xh = normal((num_inputs, num_hiddens))W_hh = normal((num_hiddens, num_hiddens))b_h = np.zeros(num_hiddens, ctx=device)# 输出层参数W_hq = normal((num_hiddens, num_outputs))b_q = np.zeros(num_outputs, ctx=device)# 附加梯度params = [W_xh, W_hh, b_h, W_hq, b_q]for param in params:param.attach_grad()return params

 内部函数normal对所有参数进行随机的初始化,在隐蔽层参数中,W_{xh},W_{hh},b_{hh}分别为隐藏层的权重和偏差,W_{hq},b_{q}表示输出层的权重和偏差,将这些参数全部附上梯度。

循环神经网络模型

在定义循环神经网络模型之前,我们还需要定义一个函数来在初始化时返回隐状态。这里使用语法使得返回的隐状态是一个元组,隐状态的大小为(批量大小,隐藏单元个数)。

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

现在,我们来开始定义循环神经网络。

我们可以将循环神经网络看成是一个类似于单隐层多层感知机的结构,隐藏状态类似于多层感知机的隐层。先将输入X进行处理后形成一个新的隐藏状态,然后将输入的旧的隐藏状态进行处理产生另一个新的隐藏状态,将两个新的隐藏状态相加,得到当前时间步的隐藏状态。之后通过矩阵乘法处理隐藏状态进行输出。最后,将输出与状态进行连结。

def rnn(inputs, state, params):# inputs的形状:(时间步数量,批量大小,词表大小)W_xh, W_hh, b_h, W_hq, b_q = paramsH, = stateoutputs = []# X的形状:(批量大小,词表大小)for X in inputs:H = np.tanh(np.dot(X, W_xh) + np.dot(H, W_hh) + b_h)Y = np.dot(H, W_hq) + b_qoutputs.append(Y)return np.concatenate(outputs, axis=0), (H,)

注:在对这个代码块进行学习时,我曾产生一个疑惑,通过concatenate来连接隐状态和输出,岂不是隐状态的大小越来越大了吗?但是事实上我发现,H在for-each循环的第一步进行了一下更新,使得H相当于重新进行了初始化,这样做,每个时间步新输出的H将与输入的H具有同样大小。

那么接下来,我们创建一个类对上述函数进行包装。包装完成后,即可来定义一个该类的神经网络net,后面将利用net来完成前向计算。

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 = npx.one_hot(X.T, self.vocab_size)return self.forward_fn(X, state, self.params)def begin_state(self, batch_size, ctx):return self.init_state(batch_size, self.num_hiddens, ctx)num_hiddens = 512
net = RNNModelScratch(len(vocab), num_hiddens, d2l.try_gpu(), get_params,init_rnn_state, rnn)

预测

使用predict_ch8这一函数来生成prefix之后的新字符,prefix为用户输入的一个具有若干连续字符的字符串,通过这一阶段来对隐状态H进行一定程度的更新,因此,这一状态被称为预热期(warm-up)

def predict_ch8(prefix, num_preds, net, vocab, device):  #@save"""在prefix后面生成新字符"""state = net.begin_state(batch_size=1, ctx=device)outputs = [vocab[prefix[0]]]get_input = lambda: np.array([outputs[-1]], ctx=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(axis=1).reshape(1)))return ''.join([vocab.idx_to_token[i] for i in outputs])

梯度裁剪

使用梯度裁剪主要是为了缓解梯度爆炸或梯度消失,关于梯度裁剪的具体数学原理,这里暂时不做讨论,但使用梯度裁剪在RNN中是必不可少的。在更新模型参数之前裁剪梯度,即使训练过程中某个点上发生了梯度爆炸,也能保证模型不会发散。

def grad_clipping(net, theta):  #@saveif isinstance(net, gluon.Block):params = [p.data() for p in net.collect_params().values()]else:params = net.paramsnorm = math.sqrt(sum((p.grad ** 2).sum() for p in params))if norm > theta:for param in params:param.grad[:] *= theta / norm

训练

经过预热后,我们现在可以开始训练循环神经网络模型了。在代码之前,我们首先对使用到的各种信息进行说明,便于大家进行了解。

参数说明

  • train_iter:训练集的迭代器,返回每个训练样本。
  • vocab:词表,返回用到的词及其对应的编号id。
  • lr:学习率,一个超参数。
  • num_epochs:学习轮数,一个超参数。
  • device:使用的计算设备。

方法说明

  • SoftmaxCrossEntropyLoss:gluon自带的交叉熵损失函数。
  • Animator:使训练结果更加可视化的方法。
def train_ch8(net, train_iter, vocab, lr, num_epochs, device,  #@saveuse_random_iter=False):loss = gluon.loss.SoftmaxCrossEntropyLoss()animator = d2l.Animator(xlabel='epoch', ylabel='perplexity',legend=['train'], xlim=[10, num_epochs])# 初始化if isinstance(net, gluon.Block):net.initialize(ctx=device, force_reinit=True,init=init.Normal(0.01))trainer = gluon.Trainer(net.collect_params(),'sgd', {'learning_rate': lr})updater = lambda batch_size: trainer.step(batch_size)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:animator.add(epoch + 1, [ppl])print(f'困惑度 {ppl:.1f}, {speed:.1f} 词元/秒 {str(device)}')print(predict('time traveller'))print(predict('traveller'))

其中的train_epoch_ch8()函数为每一轮的训练过程。先进行前向计算得到预测值,之后通过反向计算来更新权重、偏差,这些参数值。

def train_epoch_ch8(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:# 在第一次迭代或使用随机抽样时初始化statestate = net.begin_state(batch_size=X.shape[0], ctx=device)else:for s in state:s.detach()y = Y.T.reshape(-1)X, y = X.as_in_ctx(device), y.as_in_ctx(device)with autograd.record():y_hat, state = net(X, state)l = loss(y_hat, y).mean()l.backward()grad_clipping(net, 1)updater(batch_size=1)  # 因为已经调用了mean函数metric.add(l * d2l.size(y), d2l.size(y))return math.exp(metric[0] / metric[1]), metric[1] / timer.stop()

通过以下示例来观察训练的结果:

num_epochs, lr = 500, 1
train_ch8(net, train_iter, vocab, lr, num_epochs, d2l.try_gpu())
困惑度 1.0, 23960.0 词元/秒 gpu(0)
time traveller for so it will be convenient to speak of himwas e travelleryou can show black is white by argument said filby

简洁实现循环神经网络

这里,我们直接使用mxnet内的RNN类,用这些高级api完成RNN的计算。如果对RNN的原理不感兴趣,只是需要使用RNN的话,可以直接阅读和使用这一部分的代码。

num_hiddens = 256
rnn_layer = rnn.RNN(num_hiddens)
rnn_layer.initialize()
state = rnn_layer.begin_state(batch_size=batch_size)
class RNNModel(nn.Block):def __init__(self, rnn_layer, vocab_size, **kwargs):super(RNNModel, self).__init__(**kwargs)self.rnn = rnn_layerself.vocab_size = vocab_sizeself.dense = nn.Dense(vocab_size)def forward(self, inputs, state):X = npx.one_hot(inputs.T, self.vocab_size)Y, state = self.rnn(X, state)output = self.dense(Y.reshape(-1, Y.shape[-1]))return output, statedef begin_state(self, *args, **kwargs):return self.rnn.begin_state(*args, **kwargs)device = d2l.try_gpu()
net = RNNModel(rnn_layer, len(vocab))
net.initialize(force_reinit=True, ctx=device)
num_epochs, lr = 500, 1
d2l.train_ch8(net, train_iter, vocab, lr, num_epochs, device)

训练结果如下:

perplexity 1.2, 144941.9 tokens/sec on gpu(0)
time travellerit s against reason said filby of course a solid b
travelleryou can show black is whine be move the endled the

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

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

相关文章

Linux的常用操作-02

一:Linux的系统目录结构 /bin bin是ary的缩写,这个目录存放着最经常用的命令 /boot:这里存放的是启动Linux时使用的一些核心文件,包括一些连接文件以及镜像文件。 /dev:dev是Device(设备)的缩写,该目录下存放的是Lin…

Servlet——个人笔记

Servlet——个人笔记 文章目录 [toc]Servlet简介Servlet命名Servlet由来实现过程 Servlet 相对 CGI 的优势简要说说什么是CGI Servlet 在IDEA中开发流程Servlet注解方式配置WebServlet注解源码WebServlet注解使用 Servlet常见容器Servlet 生命周期简介测试 Servlet 方法init()…

MySQL(一)——初识数据库(概念、数据类型、基本表库操作)

文章目录 初识数据库数据库相关基本概念数据库的分类 MySQL数据库数据模型基本操作库操作查看数据库创建数据库删除数据库选中数据库 数据类型数值类型字符串类型日期类型 表操作查看所有表查看表结构创建表删除表 其他操作查看警告信息查看编码集 这是我们 MySQL 学习的第一程…

数据结构+图的基本应用

一、问题描述 求有向图的简单路径 编写一个程序,设计相关算法完成以下功能。 (1)输出如图所示的有向图 G 从顶点 5 到顶点 2 的所有简单路径。 (2)输出如图所示的有向图 G 从顶点 5 到顶点 2 的所有长度为 3 的简单路径。 (3)输出如图所示的有向图 G 从顶点 5 到顶点…

【nvidia-smi】Failed to initialize NVML: Driver/library version mismatch

服务器更新后,输入nvidia-smi出现如下报错: 解决方法参考: 已解决【nvidia-smi】Failed to initialize NVML: Driver/library version mismatch解决方法-腾讯云开发者社区-腾讯云 (tencent.com) 输入命令查看nvidia驱动的版本号&#xff1a…

python从入门到精通:判断语句

目录 前言 1、布尔类型和比较运算符 2、if语句的基本格式 3、if else语句 4、if elif else语句 5、判断语句的嵌套 6、实战演练 前言 逻辑判断是生活中常见的行为。同样,在程序中,进行逻辑判断也是最为基础的功能。 判断是程序最基础最核心的逻辑…

Hive3:识别内部表、外部表及相互转换

一、识别方法 查看内部表信息 desc formatted stu;查看外部表信息 desc formatted test_ext1;通过Table Type对应的值,我们可以区分外部表和内部表。 二、相互转换 内部表转外部表 alter table stu set tblproperties(EXTERNALTRUE);外部表转内部表 alter ta…

应急响应-主机安全之系统及进程排查相关命令(Linux操作系统-初级篇)

目录 概述lscpu-显示有关CPU架构的信息uname-查看系统信息lsmod-输出加载的所有模块lastb-输出最后登录失败的用户last-展示用户最近登录信息lastlog-展示所有用户最后的登录时间systemctl-系统服务,开机自启排查crontab-计划任务选项 history-查看历史命令选项常用…

正向代理 vs 反向代理:有什么区别?

在本文中,我们将讨论: 什么是常规代理(正向代理)?什么是反向代理?我应该为我的业务选择哪种代理?使用正向和反向代理的案例完成任务的代理替代方案 什么是常规代理(正向代理&#…

【Electron】npm安装Electron项目失败报错问题和解决办法

前言 闲来无事,便想着研究一下Electron,没想到安装直接就卡住了 问题 npm ERR! RequestError: Hostname/IP does not match certificates altnames: Host: npm.taobao.org. is not in the certs altnames: DNS:*.tbcdn.cn, DNS:*.taobao.com, DNS:*.al…

haproxy是什么?以及haproxy基础实验

目录 一、什么是负载均衡? 二、为什么要用haproxy? 三、haproxy的基本部署实验: 3.1 基本配置实验 环境准备: 详细步骤: 3.2 haproxy-多进程与多线程实验: 多进程: 多线程:…

【开源 Mac 工具推荐之 4】Awesome-macOS:全能的宝藏工具库

简介 Awesome-macOS 是一个开源项目,属于 GitHub 的热门项目“Awesome”的体系,旨在为 macOS 用户提供一个集合了各种优秀的 macOS 应用程序、插件、脚本和工具的精选列表。该项目由开源社区共同维护,通过不断收集和整理优秀的macOS资源&…

LeetCode - 209 - 长度最小的子数组

力扣209题 题目描述:长度最小的子数组 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其总和大于等于 target 的长度最小的 子数组 [numsl, numsl1, ..., numsr-1, numsr] ,并返回其长度**。**如果不存在符合条件的子数组&…

IO网络编程

思维导图 作业一&#xff1a;使用wasd控制机械臂 #include<myhead.h> #include <termios.h> #define SER_PORT 8888 //与服务器保持一致 #define SER_IP "192.168.0.103" //服务器ip地址 #define CLI_PORT 6666 //客户端…

【C++ 项目】负载均衡在线 OJ

文章目录 &#x1f308; 一、项目介绍&#x1f308; 二、项目源码&#x1f308; 三、项目演示⭐ 1. 前端界面展示⭐ 2. 后端界面展示 &#x1f308; 四、项目准备⭐ 1. 项目所用技术⭐ 2. 项目开发环境⭐ 3. 项目宏观结构 &#x1f308; 五、comm 公共模块⭐ 1. util.hpp 工具⭐…

【Android】安卓四大组件之Service用法

文章目录 使用Handler更新UIService基本特点启动方式非绑定式服务使用步骤 绑定式服务步骤 生命周期非绑定式启动阶段结束阶段 绑定式启动阶段结束阶段 前台Service使用步骤结束结束Service本身降级为普通Service降级为普通Service 使用Handler更新UI 主线程创建Handler对象&a…

房产中介小程序

本文来自&#xff1a;ThinkPHPFastAdmin房产中介小程序 - 源码1688 应用介绍 产中介小程序是一款基于ThinkPHPFastAdmin开发的原生微信小程序&#xff0c;为房地产中介提供房源管理、发布、报备客户、跟踪客户以及营销推广获客等服务的系统。 前端演示&#xff1a; 后台演示&am…

冷数据归档(历史库),成本与性能如何兼得?| OceanBase应用实践

随着数据量的迅猛增长&#xff0c;企业和组织在数据库管理方面遭遇的挑战愈发凸显。数据库性能逐渐下滑、存储成本节节攀升&#xff0c;以及数据运维复杂性的增加&#xff0c;这些挑战使得DBA和开发者在数据管理上面临更大的压力。 为了应对这些挑战&#xff0c;对数据生命周期…

奇异值分解(SVD)

1 奇异值分解(SVD)简介 Beltrami 和 Jordan 被认为是奇异值分解&#xff08;Singular Value Decomposition&#xff0c;SVD&#xff09;的共同开创者&#xff0c;二人于19世纪70年代相继提出了相关理论。奇异值分解主要解决的问题是数据降维。在高维度的数据中&#xff0c;数据…

Tied and Anchored Stereo Attention Network for Cloud Removal in Optical

论文名称 基于固定锚定立体注意力网络的光学遥感图像去云方法代码运行 论文代码 https://github.com/ningjin00/TASANet?tabreadme-ov-file 论文地址 1环境创建 模型环境给了这几个包&#xff0c;如果你自带环境 那就运行代码 提示缺哪个装哪个 python 3.12rasterio 1.3.10…