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

news2024/9/20 16:56:14

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

前序工作

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

实现循环神经网络

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

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

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

%matplotlib inline
import math
from mxnet import autograd, gluon, np, npx
from d2l import mxnet as d2l

npx.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_size

    def 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 = params
    H, = state
    outputs = []
    # 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_q
        outputs.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_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 = 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):  #@save
    if isinstance(net, gluon.Block):
        params = [p.data() for p in net.collect_params().values()]
    else:
        params = net.params
    norm = 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,  #@save
              use_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:
            # 在第一次迭代或使用随机抽样时初始化state
            state = 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_layer
        self.vocab_size = vocab_size
        self.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, state

    def 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.coloradmin.cn/o/2035073.html

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

相关文章

力扣面试经典算法150题:最后一个单词的长度

最后一个单词的长度 今天的题目是力扣面试经典150题中的数组的简单题: 最后一个单词的长度 题目链接:https://leetcode.cn/problems/length-of-last-word/description/?envTypestudy-plan-v2&envIdtop-interview-150 题目描述 给定一个仅包含大小写字母和空…

Broken靶机

查看靶机的mac地址 使用kail进行扫描ip 探测靶机主机,端口,服务 nmap -sS -sS -A -p- 192.168.154.137 进行目录扫描 dirsearch -u http://192.168.154.137 拼接后没什么发现 访问靶机ip 访问readme.md 发现是十六进制的值 将内容写入到readme.md中 使…

坐牢第二十五天20240813(网络通信)

一、TCP机械臂测试 通过w(红色臂角度增大)s(红色臂角度减小)d(蓝色臂角度增大)a(蓝色臂角度减小)按键控制机械臂 注意:关闭计算机的杀毒软件,电脑管家,防火墙 1&#x…

C语言问答进阶--5、基本表达式和基本语句

赋值表达式 表达式是什么&#xff1f;表达式是由运算符和操作数组成的式子。 如下的代码 #include "iostream.h" int main() { int a1,b2,sum; cout<<(sumab)<<endl; return 0; } 那么如下的呢&#xff1f; #include "iostream.h" int mai…

智能建筑系统,实现智慧城市的可持续发展

智能建筑系统是指通过现代技术和通信技术&#xff0c;对建筑系统进行全方位、智能化的管理和控制。该系统可以通过各种传感器、安全监控系统和计算机设备对工程建筑的内外环境进行认知和控制&#xff0c;进而监控和管理建筑工程设备和信息。 智能建筑系统可以调节室温、湿度等环…

基于Hadoop的汽车大数据分析系统设计与实现【爬虫、数据预处理、MapReduce、echarts、Flask】

文章目录 有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主项目介绍爬虫数据概览HIve表设计Cars Database Tables1. cars_data2. annual_sales_volume3. brand_sales_volume4. city_sales_volume5. sales_volume_by_year_and_brand6. sales_distribu…

Mysql的完整性约束

主键约束&#xff1a;一个表中只有一个主键&#xff0c;通过主键找到唯一的记录。主键不能为空不能重复。 CREATE TABLE s1&#xff08;id TINYINT PRIMARY KEY UNSIGNEDINT AUTO_INCREAMENT,name VARCHAR(20) NOT NULL UNIQUE ,age TINYINT DEFAULT 18&#xff09;;…

镜像仓库认证信息加密初始化脚本

文章目录 一、场景说明二、脚本职责三、参数说明四、操作示例五、注意事项 一、场景说明 本自动化脚本旨在为提高研发、测试、运维快速部署应用环境而编写。 脚本遵循拿来即用的原则快速完成 CentOS 系统各应用环境部署工作。 统一研发、测试、生产环境的部署模式、部署结构、…

Python爬虫——爬取bilibili中的视频

爬取bilibili中的视频 本次爬取&#xff0c;还是运用的是requests方法 首先进入bilibili官网中&#xff0c;选取你想要爬取的视频&#xff0c;进入视频播放页面&#xff0c;按F12&#xff0c;将网络中的名称栏向上拉找到第一个并点击&#xff0c;可以在标头中&#xff0c;找到…

【研发日记】嵌入式处理器技能解锁(三)——TI C2000 DSP的C28x内核

文章目录 前言 背景介绍 C28x内核 浮点单元(FPU) 快速整数除法单元(FINTDIV) 三角数学单元(TMU) VCRC单元 CPU总线 指令流水线 总结 参考资料 前言 见《【研发日记】嵌入式处理器技能解锁(一)——多任务异步执行调度的三种方法》 见《【研发日记】嵌入式处理器技能解…

Linux--应用层自定义协议与序列化(例子:网络计算器)

目录 0.上篇文章 1.应用层 再谈一谈协议 网络版计算器 序列化 和 反序列化 2.重新理解 read、 write、 recv、 send 和 tcp 为什么支持全双工 3.网络计算器&#xff08;代码实现) 3.1序列化&反序列化的接口 3.2 项目逻辑 3.3 代码 3.3.1辅助库 3.3.2 基于TCP的…

非线性RCD负载:电力系统的智能管理

随着科技的不断发展&#xff0c;电力系统的规模日益扩大&#xff0c;复杂性也越来越高。在这种背景下&#xff0c;非线性RCD负载&#xff08;Resistive-Capacitive-Inductive load&#xff09;的出现&#xff0c;对电力系统的智能管理提出了新的挑战。非线性RCD负载是指由电阻、…

【学习笔记】Day 10

一、进度概述 1、《地震勘探原理》第三章 二、详情 3.1 野外工作概述 主要介绍地上与海上两种情况下的测量方法&#xff0c;这里不做详解&#xff0c;需要就看书。 其中简况分为&#xff1a;试验工作&#xff0c;生产工作过程&#xff0c;干扰波调查&#xff0c;干扰…

thinkphp8反序列化分析

thinkphp8反序列化 前言 摆了一个暑假&#xff0c;正好看见周会有人分析了tp反序列化&#xff0c;想起这条链子的发现者就是我尊敬的nivia&#xff0c;这不得好好分析一下&#xff0c;而且师傅也是分析了这个&#xff0c;所以有了这个文章 链子一 __call触发 分析 相比于我…

SpringSecurity+前端项目+redis完成认证授权的代码

1. 前端准备工作--都在全局main.js页面中设置的 1.1. 创建Vue工程后&#xff0c;并导入element ui和axios&#xff0c;添加连接后端项目的路径&#xff0c;把axios挂载到Vue 1.2. 前置路由守卫&#xff08;所有路由跳转前核实一下身份&#xff09; //前置路由守卫--所有的路由…

C++密码管理器

先问一句 最近有几个关注我的原力等级为0或-1&#xff0c;文章全是转载&#xff0c;转载时间基本都在2021年&#xff0c;而且关注了很多人&#xff0c;这些是僵尸粉吗&#xff1f; 文末有投票&#xff0c;麻烦参与一下谢谢 实现功能列表 暂时还没做加密功能 打算用openssl/a…

C++ STL初阶(9):list 中关于reverse_iterator的实现

在完成vector和list的iterator相关部分的实践后来完成反向迭代器的实现 1. list的反向迭代器 书接上回&#xff0c;反向迭代器应当重新封装一个类。 反向迭代器和正向迭代器最大的区别就是&#xff0c;反向迭代器是倒着走的&#xff0c;所以最核心的逻辑就是将封装成-- 注意&am…

数字化转型-成就智慧智慧企业

数字化转型是企业迈向智慧化发展的关键路径&#xff0c;通过将先进的数字技术融入企业核心业务&#xff0c;构建智能化、数据驱动的运营模式&#xff0c;实现业务的全面升级与优化。智慧企业的实现依托于几个核心要素&#xff1a;首先是数字基础设施的建设&#xff0c;包括云计…

浅述TSINGSEE青犀EasyCVR视频汇聚平台与海康安防平台的区别对比

在我们的很多项目中都遇到过用户的咨询&#xff1a;TSINGSEE青犀EasyCVR视频汇聚平台与海康平台的区别在哪里&#xff1f;确实&#xff0c;在安防视频监控领域&#xff0c;EasyCVR视频汇聚平台与海康威视平台是两个备受关注的选择。它们各自具有独特的功能和优势&#xff0c;适…

RSS 源:在信息洪流中找回你的时间掌控权

简单介绍了 RSS 后&#xff0c;那么关键的一步就是建立好自己的 RSS 源了。 并不是所有平台都会提供 RSS 源&#xff0c;因此我们也没办法直接去订阅。 目前使用 RSS 的难题之一就是 RSS 源的匮乏&#xff0c;是无数人重新拥抱 RSS 的第一大障碍。 那么&#xff0c;如何去找…