【深度学习实验】循环神经网络(五):基于GRU的语言模型训练(包括自定义门控循环单元GRU)

文章目录

  • 一、实验介绍
  • 二、实验环境
    • 1. 配置虚拟环境
    • 2. 库版本介绍
  • 三、实验内容
    • (一)自定义门控循环单元(GRU,Gated Recurrent Unit)
      • 1. get_params
      • 2. init_gru_state
      • 3. gru
    • (二)创建模型
      • 0. 超参数
      • 1. 使用上述手动实现的GRU函数
      • 2. 调用Pytorch库的GRU类
    • (三)基于GRU的语言模型训练
      • 1. RNNModel类
      • 2. 训练、测试及其余辅助函数
      • 3. 主函数
        • a. 训练
        • b. 测试结果
      • 4. 代码整合

  经验是智慧之父,记忆是智慧之母。
——谚语

一、实验介绍

  • 基于门控的循环神经网络(Gated RNN)
    • 门控循环单元(GRU)
      • 门控循环单元(GRU)具有比传统循环神经网络更少的门控单元,因此参数更少,计算效率更高。GRU通过重置门更新门来控制信息的流动,从而改善了传统循环神经网络中的长期依赖问题。
    • 长短期记忆网络(LSTM)
      • 长短期记忆网络(LSTM)是另一种常用的门控循环神经网络结构。LSTM引入了记忆单元输入门输出门以及遗忘门等门控机制,通过这些门控机制可以选择性地记忆、遗忘和输出信息,有效地处理长期依赖和梯度问题。
  • GRU示意图:
    GRU 模块图示

二、实验环境

  本系列实验使用了PyTorch深度学习框架,相关操作如下:

1. 配置虚拟环境

conda create -n DL python=3.7 
conda activate DL
pip install torch==1.8.1+cu102 torchvision==0.9.1+cu102 torchaudio==0.8.1 -f https://download.pytorch.org/whl/torch_stable.html
conda install matplotlib
 conda install scikit-learn

2. 库版本介绍

软件包本实验版本目前最新版
matplotlib3.5.33.8.0
numpy1.21.61.26.0
python3.7.16
scikit-learn0.22.11.3.0
torch1.8.1+cu1022.0.1
torchaudio0.8.12.0.2
torchvision0.9.1+cu1020.15.2

三、实验内容

(一)自定义门控循环单元(GRU,Gated Recurrent Unit)

1. get_params

def get_params(vocab_size, num_hiddens, device):
    num_inputs = num_outputs = vocab_size

    def normal(inputs, hiddens):
        ctx = device
        param = torch.rand((inputs, hiddens))
        param.to(ctx)
        return param
    
    def three():
        return (normal(num_inputs, num_hiddens),
                normal(num_hiddens, num_hiddens),
                torch.zeros(num_hiddens, device=device))

    W_xz, W_hz, b_z = three()  # 更新门参数
    W_xr, W_hr, b_r = three()  # 重置门参数
    W_xh, W_hh, b_h = three()  # 候选隐状态参数
    # 输出层参数
    W_hq = normal(num_hiddens, num_outputs)
    b_q = torch.zeros(num_outputs, device=device)
    # 附加梯度
    params = [W_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hq, b_q]
    for param in params:
        param.requires_grad_(True)
    return params

  get_params 函数用于初始化模型的参数。它接受三个参数:vocab_size 表示词汇表的大小,num_hiddens 表示隐藏单元的数量,device 表示模型所在的设备(如 CPU 或 GPU)。

  • 首先,根据 vocab_size 初始化 num_inputsnum_outputs,它们的值都等于 vocab_size
  • 然后,定义了一个内部函数 normal,该函数用于生成一个服从均匀分布的随机参数矩阵。这个函数返回一个形状为 (inputs, hiddens) 的随机参数矩阵,并将其移动到指定的设备上。
  • 接下来,定义了另一个内部函数 three,该函数用于生成三个参数组成的元组。这三个参数分别表示更新门参数、重置门参数和候选隐状态参数。
  • 使用 three 函数分别初始化了更新门参数 W_xz, W_hz, b_z,重置门参数 W_xr, W_hr, b_r,候选隐状态参数 W_xh, W_hh, b_h
  • 然后,通过调用 normal 函数初始化了输出层参数 W_hqb_q
  • 最后,将所有的参数放入一个列表 params 中,并设置它们的 requires_grad 属性为 True,表示这些参数需要计算梯度。
  • 返回参数列表 params

2. init_gru_state

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

  init_gru_state 函数用于初始化隐藏状态,作为时间步 t=0 时的输入。它接受三个参数:batch_size 表示批次大小,num_hiddens 表示隐藏单元的数量,device 表示模型所在的设备。

  • 使用 torch.zeros 函数创建并返回一个形状为 (batch_size, num_hiddens) 的全零张量,表示初始的隐藏状态。

3. gru

def gru(inputs, state, params):
    W_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hq, b_q = params
    H, = state
    outputs = []
    # @符号为矩阵乘法的运算符号
    for X in inputs:
        Z = torch.sigmoid((X @ W_xz) + (H @ W_hz) + b_z)
        R = torch.sigmoid((X @ W_xr) + (H @ W_hr) + b_r)
        H_tilda = torch.tanh((X @ W_xh) + ((R * H) @ W_hh) + b_h)
        H = Z * H + (1 - Z) * H_tilda
        Y = H @ W_hq + b_q
        outputs.append(Y)
    return torch.cat(outputs, dim=0), (H,)

   gru 函数是实现门控循环单元的关键部分,接受三个参数:inputs 表示输入序列,state 表示隐藏状态,params 表示模型的参数。

  • 首先,从 params 中解包出更新门参数、重置门参数、候选隐状态参数以及输出层参数。
  • 然后,通过一个循环遍历输入序列 inputs
  • 在每个时间步,根据输入 X 和当前的隐藏状态 H,计算更新门 Z、重置门 R 和候选隐状态 H_tilda
  • 然后,根据门控机制和候选隐状态,计算新的隐藏状态 H
  • 接着,使用隐藏状态 H 计算输出 Y
  • 将输出 Y 添加到输出列表 outputs 中。
  • 循环结束后,使用 torch.cat 函数将输出列表中的所有输出连接起来,得到一个形状为 (seq_length * batch_size, num_outputs) 的张量,表示模型在整个序列上的输出。
  • 最后,返回连接后的输出张量和最终的隐藏状态 (H,)

(二)创建模型

0. 超参数

batch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)
vocab_size, num_hiddens, num_epochs, lr= 28, 256, 200, 1
device = try_gpu()
  • 调用d2l.load_data_time_machine函数加载了训练数据,并设置了一些训练超参数。

1. 使用上述手动实现的GRU函数

model = d2l.RNNModelScratch(len(vocab), num_hiddens, device, get_params,
                            init_gru_state, gru)
d2l.train_ch8(model, train_iter, vocab, lr, num_epochs, device)
  • 使用先前定义的参数初始化函数、隐藏状态初始化函数和GRU函数创建自定义的RNN模型model
  • 调用d2l.train_ch8函数对该模型进行训练。

2. 调用Pytorch库的GRU类

gru_layer = nn.GRU(vocab_size, num_hiddens)
model_gru = RNNModel(gru_layer, vocab_size)
train(model_gru, train_iter, vocab, lr, num_epochs, device)
  • 创建了一个使用PyTorch库中的GRU类的model_gru,并对其进行训练。
  • 关于训练过程,请继续阅读

(三)基于GRU的语言模型训练

注:本实验使用Pytorch库的GRU类,不使用自定义的GRU函数

1. RNNModel类

  • 参考前文: 【深度学习实验】循环神经网络(三):门控制——自定义循环神经网络LSTM(长短期记忆网络)模型

2. 训练、测试及其余辅助函数

  • 参考前文: 【深度学习实验】循环神经网络(四):基于 LSTM 的语言模型训练

3. 主函数

a. 训练
batch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)
vocab_size, num_hiddens, num_epochs, lr= 28, 256, 200, 1
device = try_gpu()
gru_layer = nn.GRU(vocab_size, num_hiddens)
model_gru = RNNModel(gru_layer, vocab_size)
train(model_gru, train_iter, vocab, lr, num_epochs, device)

print(predict('time ', 10, model_gru, vocab, device))

  • 训练中每个小批次(batch)的大小和每个序列的时间步数(time step)的值分别为32,25
  • 加载的训练数据迭代器和词汇表
  • vocab_size 是词汇表的大小,num_hiddens 是GRU 隐藏层中的隐藏单元数量,num_epochs 是训练的迭代次数,lr 是学习率
  • 选择可用的 GPU 设备进行训练,如果没有可用的 GPU,则会使用 CPU
  • 训练模型
  • 模型测试
b. 测试结果

在这里插入图片描述

4. 代码整合

# 导入必要的库
import torch
from torch import nn
import torch.nn.functional as F
from d2l import torch as d2l
import math


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
        self.num_directions = 1
        self.linear = nn.Linear(self.num_hiddens, 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))


def train(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)

    for epoch in range(num_epochs):
        ppl, speed = train_epoch(
            net, train_iter, loss, updater, device, use_random_iter)
        if (epoch + 1) % 10 == 0:
            animator.add(epoch + 1, [ppl])
    print('Train Done!')
    torch.save(net.state_dict(), 'chapter6.pth')
    print(f'困惑度 {ppl:.1f}, {speed:.1f} 词元/秒 {str(device)}')


def train_epoch(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], device=device)
        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 * d2l.size(y), d2l.size(y))
    return math.exp(metric[0] / metric[1]), metric[1] / timer.stop()


def predict(prefix, num_preds, net, vocab, device):
    state = net.begin_state(batch_size=1, device=device)
    outputs = [vocab[prefix[0]]]
    get_input = lambda: torch.reshape(torch.tensor(
        [outputs[-1]], device=device), (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])


def grad_clipping(net, theta):
    if isinstance(net, nn.Module):
        params = [p for p in net.parameters() if p.requires_grad]
    else:
        params = net.params
    norm = torch.sqrt(sum(torch.sum((p.grad ** 2)) for p in params))
    if norm > theta:
        for param in params:
            param.grad[:] *= theta / norm


def try_gpu(i=0):
    # """如果存在,则返回gpu(i),否则返回cpu()"""
    # # if torch.cuda.device_count() >= i + 1:
    # #     return torch.device(f'cuda:{i}')
    return torch.device('cpu')


batch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)
vocab_size, num_hiddens, num_epochs, lr = 28, 256, 200, 1
device = try_gpu()

gru_layer = nn.GRU(vocab_size, num_hiddens)
model_gru = RNNModel(gru_layer, vocab_size)
train(model_gru, train_iter, vocab, lr, num_epochs, device)

print(predict('time ', 10, model_gru, vocab, device))

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

3. 博弈树 (15分)

下棋属于一种博弈游戏,博弈过程可以用树(博弈树)来表示。假设游戏由两个人( A 和 B )玩,开始由某个人从根结点开始走,两个人轮流走棋,每次只能走一步, 下一步棋只能选择当…

OTA语音芯片NV040C在智能电动牙刷的应用

以往我们对牙齿的清洁是使用的是手动方式进行,用柔软的牙刷刷毛去进行牙齿的清洁。但现在我们拥有了一种新颖的刷牙方式,靠电力去驱动、清洁我们的牙齿。电动牙刷的刷头通过快速旋转,产生高频振动,将牙膏迅速分解为细小的泡沫&…

支付宝支付接入流程

一、 接入准备 支付宝支付流程没有微信那么复杂,而且支付宝支持沙箱。登录支付宝开放平台控制台 点击开发工具中的沙箱 接口加密方式,我这里使用的是自定义密钥。生成密钥的方式 使用支付宝官方提供的密钥工具,唯一要注意的是支付宝密钥工具…

idea + Docker-Compose 实现自动化打包部署(仅限测试环境)

一、修改docker.service文件,添加监听端口 vi /usr/lib/systemd/system/docker.service ExecStart/usr/bin/dockerd -H fd:// --containerd/run/containerd/containerd.sock -H tcp://0.0.0.0:2375 -H unix://var/run/docker.sock重启docker服务 systemctl daemo…

线性代数3:矢量方程

一、前言 欢迎回到系列文章的第三篇文章,内容是线性代数的基础知识,线性代数是机器学习背后的基础数学。在我之前的文章中,我介绍了梯队矩阵形式。本文将介绍向量、跨度和线性组合,并将这些新想法与我们已经学到的内容联系起来。本…

基于 Redis + Lua 脚本实现分布式锁,确保操作的原子性

1.加锁的Lua脚本: lock.lua --- -1 failed --- 1 success--- getLock key local result redis.call(setnx , KEYS[1] , ARGV[1]) if result 1 then--PEXPIRE:以毫秒的形式指定过期时间redis.call(pexpire , KEYS[1] , 3600000) elseresult -1;-- 如果value相同&…

数据清洗(data clean)

整理了下数据清洗的基本流程,异常值部分整理在了EDA中。

什么是简单网络管理协议(SNMP)

简单网络管理协议(SNMP:Simple Network Management Protocol)是由互联网工程任务组(IETF:Internet Engineering Task Force )定义的一套网络管理协议。该协议基于简单网关监视协议(SGMP&#xf…

React中的Virtual DOM(看这一篇就够了)

文章目录 前言了解Virtual DOMreact创建虚拟dom的方式React Element虚拟dom的流程虚拟dom和真实dom的对比后言 前言 hello world欢迎来到前端的新世界 😜当前文章系列专栏:react合集 🐱‍👓博主在前端领域还有很多知识和技术需要掌…

DevOps持续集成-Jenkins(3)

文章目录 DevOpsDevOps概述Jenkins实战3:实战1和实战2的加强版(新增SonarQube和Harbor)⭐环境准备⭐项目架构图对比Jenkins实战1和实战2,新增内容有哪些?SonarQube教程采用Docker安装SonarQube (在Jenkins所…

生成树协议:监控 STP 端口和交换机

什么是生成树协议 生成树协议 (STP) 用于网络交换机,以防止循环和广播风暴。在局域网 (LAN) 中,两条或多条冗余路径可以连接到同一网段。当交换机或网桥从所有可用端口传输帧时,这些帧开始在网…

基于单片机设计的智能窗帘控制系统

一、前言 智能家居技术在近年来取得了巨大的发展,并逐渐成为人们日常生活中的一部分。智能家居系统带来了便利、舒适和高效的生活体验,拥有广泛的应用领域,其中之一就是智能窗帘控制系统。 传统窗帘需要手动操作,打开或关闭窗帘…

华硕天选1天选2天选3天选4天选air原厂预装出厂系统恢复安装教程方法

华硕天选1天选2天选3天选4天选air原厂预装出厂系统恢复安装教程方法 第一:自备原装swm/esd/wim/iso等格式系统文件,以上这几种格式文件安装恢复非常简单,使用PE工具即可完成恢复安装,还有一种安装方法就是华硕zip工厂恢复模式 1.首先需要自…

Adaptive AUTOSAR RTA-VRTE工具链介绍

ETAS Adaptive AUTOSAR RTA-VRTE是一种面向服务架构的中间件方案,提供了自适应AutoSAR平台,为应用层软件提供了运行环境. RTA-VRTE start kit的构建系统在主机VM内执行,可以创建AUTOSAR自适应应用程序并将其部署到一个或多个目标ECU虚拟机.

【VPX610】 青翼科技基于6U VPX总线架构的高性能实时信号处理平台

板卡概述 VPX610是一款基于6U VPX架构的高性能实时信号处理平台,该平台采用2片TI的KeyStone系列多核DSP TMS320C6678作为主处理单元,采用1片Xilinx的Virtex-7系列FPGA XC7VX690T作为协处理单元,具有2个FMC子卡接口,各个处理节点之…

linux-文件系统

目录 一、文件系统 1.分区 2.文件系统分类 3.文件系统创建工具 4.查看文件系统的属性 5.挂载 6.buffer和cache 一、文件系统 1.分区 1-4个主分区 第五个序号开始,是逻辑分区 2.文件系统分类 vfs文件系统 ------------- virtualenv file System&#xff0…

智慧社区燃气管网监测系统

燃气易燃易爆,一旦操作不当或疏忽大意,极易引发燃气安全事故,造成严重后果,2023年10月24日,在吉林某小区,发生了燃气使用不当产生的爆炸导致了1人死亡,1人重伤,15人轻伤,…

【嵌入式开源库】timeslice的使用,完全解耦的时间片轮询框架构

完全解耦的时间片轮询框架构 简介项目代码timeslice.htimeslice.clist.hlist.c 创建工程移植代码实验函数说明timeslice_task_inittimeslice_task_addtimeslice_tak_deltimeslice_get_task_num 结尾 简介 timeslice是一个时间片轮询框架,他是一个完全解耦的时间片轮…

电脑视频怎么转音频mp3

如果你在电脑上观看视频时喜欢上某个片段的背景音乐,且想将喜欢的背景音乐制作为手机铃声。我是建议你将此视频转换为 MP3 格式,因为 MP3 几乎与所有设备相兼容,让你可以在不同设备上不受限制地去聆听它。那该如何转换呢?无需担心…

LinkedList概念+MyLinkedList的实现

文章目录 LinkedList笔记一、 LinkedList1.概念2.LinkedList的构造方法3.LinkedList的遍历 二、MyLinkedList的实现1.定义内部类2.打印链表、求链表长度、判断是否包含关键字3. 头插法和尾插法4.在任意位置插入5.删除结点6.清空链表 LinkedList笔记 一、 LinkedList 1.概念 L…