基于门控的循环神经网络:GRU

门控循环单元(GatedRecurrentUnit,GRU)网络,也是一种基于门控的循环神经网络,但是名气不如LSTM大,GRU是对LSTM的一种改版,可以理解为是LSTM的简化版。LSTM有三个门,输入门,遗忘门,输出门,而GRU仅有两个门,分别为更新门和重置门。GRU的目的和LSTM是一样的,也是为了解决循环神经网络的长期依赖问题,即随时间反向传播算法当序列长度太长后产生的梯度爆炸和梯度消失问题。GRU将LSTM中的输入门和遗忘门合并成了一个更新门,同时,GRU也不引入额外的记忆单元(LSTM中引入了额外的记忆单元C),直接在当前状态ht和历史状态ht-1之间引入线性依赖关系。

GRU网络的隐状态ht的更新方式为:

其中zt在[0,1]之间,称为更新门,用来控制当前状态需要从历史状态中保留多少信息,以及需要从候选状态中接受多少新信息。

这里的W,U,b都是可以学习的权重参数。

其中ht~称为当前时刻的候选状态,计算方法如下:

rt就是GRU的另一个门,重置门,用来控制候选状态ht~的计算是否依赖上一时刻的状态ht-1。

当rt=0时,候选状态就只和当前输入xt有关,而与历史状态ht-1无关了,当rt=1时,候选状态就又和当前输入xt有关,也和历史状态ht-1有关,就和简单循环神经网络RNN一样了。

当更新门zt=0时,那么ht就等于ht~,也就是说ht和ht-1就是一个非线性函数;如果zt=0且rt=1,GRU就是简单循环神经网络RNN;如果zt=0且rt=0,那么zt=tanh(Wh*xt+bh),ht就只和当前输入xt有关了;当zt=1时,ht=ht-1,也就是说ht直接是上一个时刻的隐状态ht-1,而与候选状态无关,也就是和当前输入xt无关了。当然,大多数时候,GRU的隐状态既和上一个时刻的隐状态有关,又和当前输入xt有关。可以这么说,重置门有助于捕获序列中的短期依赖关系,更新门有助于捕获序列中的长期依赖关系。GRU网络结构如下图:

下面我们来实现一下GRU网络。

class GRU(nn.Module):def __init__(self, input_size, num_hiddens, batch_first=True):super(GRU,self).__init__()def normal(shape):return torch.randn(size = shape)*0.01def three():wx = normal((input_size, num_hiddens)) wh = normal((num_hiddens, num_hiddens))b = torch.zeros(num_hiddens)return (wx,wh,b)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_hiddens))b_q = torch.zeros(num_hiddens)self.params = [W_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hq, b_q]self.batch_first = batch_firstself.num_hiddens = num_hiddensdef forward(self,inputs):[W_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hq, b_q] = self.paramsif self.batch_first:batch_size = inputs.shape[0]seq_len = inputs.shape[1] # 序列长度else:batch_size = inputs.shape[1]seq_len = inputs.shape[0] # 序列长度self.h = torch.zeros((seq_len, self.num_hiddens))H = self.houtputs = []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_tildaY = H@W_hq + b_qoutputs.append(Y)out = torch.cat(outputs, dim=0)out = out.reshape(batch_size,seq_len,self.num_hiddens)return out,(H)

可以看到,我们首先初始化了更新门,重置门,候选隐藏状态,以及输出层的参数。输入的第一个维度默认是batch_size,第二个维度是序列长度seq_len,由于我们这里只是定义了一个GRU模块,所以输出的维度和隐藏层一致。实际应用中,需要在GRU的输出结果后,加上全连接层,得到最终的输出。这里X遍历了整个inputs,最后按第0维(也就是第一个维度)拼接,得到GRU输出结果,其实就是把一个batch拆分开计算,再将得到结果,在batch_size维度上进行合并。我们可以看一下输出大小示例:

net = GRU(input_size=1, num_hiddens=20, batch_first=True)
data = torch.zeros(8,10,1)
out,ht = net(data)
print(out.shape)
# 输出:
torch.Size([8, 10, 20])

可以看到,输入batch_size=8,序列长度10,输入大小是1维的数字,输出第一个维度是8,也就是batch_size,第二个维度10,是序列长度,第三个维度20,是隐藏层的大小。我们可以看一下pytorch官方提供的GRU函数,得到的结果大小。

input_size = 1
num_hiddens = 20gru=nn.GRU(input_size=input_size,    #输入特征维度,维度为1hidden_size=num_hiddens,  #隐藏层神经元个数,或者也叫输出的维度num_layers=1,batch_first=True)
data = torch.zeros(8, 10, 1)
out,ht = gru(data)
print(out.shape)
print(out[:,-1,:].shape)
# 输出:
torch.Size([8, 10, 20])
torch.Size([8, 20])

可以看到pytorch官方的GRU函数和我们自己实现的输出格式是一致的,都是[8,10,20]。下面,我们根据我们自己定义的GRU模块,来实现一个完整的GRU网络:

class model(nn.Module):  def __init__(self, input_size, hidden_size, output_size):  super(model, self).__init__()  self.hidden_size = hidden_size  self.gru = GRU(input_size, hidden_size, batch_first=True) # 自定义的GRU#self.gru = nn.GRU(input_size, hidden_size, batch_first=True) # Pytorch提供的GRUself.fc = nn.Linear(hidden_size, output_size)  def forward(self, x):  out, _ = self.gru(x)out = self.fc(out[:, -1, :])return out
net = model(1, 20, 1)
data = torch.zeros(8, 4, 1)
out = net(data)
print(out.shape)
# 输出:
torch.Size([8, 1])

完整的模型中,我在得到GRU的结果后又加上了全连接模块,得到了最终的输出。我这里输入了batch_size=8,序列长度为4,输入维度为1的序列数据,最终输出结果是batch_size=8,输出维度是1的数据,符合我们的要求。下面我们用这个模型,在之前用过的正弦数据上模拟一下。

import matplotlib.pyplot as plt# 画出sin函数作为序列函数
y = []
for i in range(1000):y.append(np.sin(0.01*i)+np.random.normal(0,0.2)) # 给sin函数增加一个微小的扰动
x = [i for i in range(1000)]plt.plot(x, y)
plt.show()

根据正弦函数并加上一个随机扰动生成序列数据,我们的目的是希望根据四个输入数据输出下一个数据。下面构造训练数据。

# 用前600个数字作为训练集,后400个作为测试集
class myDataset(Dataset):def __init__(self, tau=4, total=600, transform=None):data = [i for i in range(total)]y = []for i in range(total):y.append(np.sin(0.01*i)+np.random.normal(0,0.2)) # 给sin函数增加一个微小的扰动# tau代表用多少个数字来作为输入,默认为4self.features = np.zeros((total-tau, tau)) # 构建了996行4列的输入序列,代表了996个训练样本,每个样本有4个数字构成for i in range(tau):self.features[:,i] = y[i: total-tau+i] # 给特征向量赋值self.data = dataself.transform = transformself.labels = y[tau:]def __len__(self):return len(self.labels)def __getitem__(self, idx):return self.features[idx], self.labels[idx]
transform = transforms.Compose([transforms.ToTensor()])
trainDataset = myDataset(transform=transform)
train_loader = DataLoader(dataset=trainDataset, batch_size=32, shuffle=False) 

训练函数:

def train(epochs=10):net = model(1, 20, 1)criterion = nn.MSELoss()optimizer = torch.optim.Adam(net.parameters(), lr=0.01)for epoch in range(epochs):total_loss = 0.0for i, (x, y) in enumerate(train_loader):x = Variable(x)x = x.to(torch.float32)x = x.unsqueeze(2)y = Variable(y)y = y.to(torch.float32)y = y.unsqueeze(1)optimizer.zero_grad()outputs = net(x)loss = criterion(outputs, y)total_loss += loss.sum() # 因为标签值和输出都是一个张量,所以损失值要求和loss.sum().backward()optimizer.step()if (epoch+1)%50==0:print('Epoch {}, Loss: {:.4f}'.format(epoch+1, total_loss/len(trainDataset)))torch.save(net, 'gru.pt')
train(epochs=1000)
# 输出
Epoch 50, Loss: 0.0164
Epoch 100, Loss: 0.0149
Epoch 150, Loss: 0.0135
Epoch 200, Loss: 0.0122
Epoch 250, Loss: 0.0109
Epoch 300, Loss: 0.0098
Epoch 350, Loss: 0.0087
Epoch 400, Loss: 0.0077
Epoch 450, Loss: 0.0068
Epoch 500, Loss: 0.0060
Epoch 550, Loss: 0.0053
Epoch 600, Loss: 0.0046
Epoch 650, Loss: 0.0040
Epoch 700, Loss: 0.0035
Epoch 750, Loss: 0.0031
Epoch 800, Loss: 0.0027
Epoch 850, Loss: 0.0024
Epoch 900, Loss: 0.0022
Epoch 950, Loss: 0.0020
Epoch 1000, Loss: 0.0019

下面根据训练后的模型进行预测:

# 预测
net = torch.load('gru.pt')
features = torch.from_numpy(features)
features = features.float()
features = features.unsqueeze(2)
y_pred = net(features)# 画出sin函数作为序列函数
y = []
for i in range(996):# 给sin函数增加一个微小的扰动y.append(np.sin(0.01*i)+np.random.normal(0,0.2))
x = [i for i in range(996)]fig, ax = plt.subplots()
ax.plot(x, y)
ax.plot(x, y_pred.detach().numpy(), color="y")
plt.show()

可以看到,训练结果符合正弦函数的图形特征。下一篇文章,我们看一下如何利用循环神经网络预测股价。

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

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

相关文章

力扣刷题--448. 找到所有数组中消失的数字【简单】

题目描述 给你一个含 n 个整数的数组 nums ,其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字,并以数组的形式返回结果。 示例 1: 输入:nums [4,3,2,7,8,2,3,1] 输出:[5,6…

Python零基础-中【详细】

接上篇继续: Python零基础-上【详细】-CSDN博客 目录 十、函数式编程 1、匿名函数lambda表达式 (1)匿名函数理解 (2)lambda表达式的基本格式 (3)lambda表达式的使用场景 (4&…

内存函数详解,包含部分字符串函数

目录 一,memcpy内存函数的介绍 二memmove函数的介绍 三,memset的函数使用 四,memcmp的介绍 五,内存函数的模拟实现,以及一个字符串函数strstr的模拟实现 5.1memcpy函数的实现 5.2memmove的模拟实现 5.3memcmp的模拟…

Shell环境变量深入:自定义系统环境变量

Shell环境变量深入:自定义系统环境变量 目标 能够自定义系统级环境变量 全局配置文件/etc/profile应用场景 当前用户进入Shell环境初始化的时候会加载全局配置文件/etc/profile里面的环境变量, 供给所有Shell程序使用 以后只要是所有Shell程序或命令使用的变量…

H.机房【蓝桥杯】/数组链式前向星建图+堆优化版dijkstra

机房 数组链式前向星建图堆优化版dijkstra #include<iostream> #include<queue> #include<cstring> #include<vector> using namespace std; typedef pair<int,int> pii; //无向图开两倍 int e[200005],ne[200005],v[200005],h[200005],du[1000…

STL---unordered set和unordered multiset【无序集合】

1.1 定义及初始化&#x1f357; 下面列出常用的初始化方式 #include <unordered_set> #include <iostream> using namespace std; //输出s中的所有元素 template<typename T> void Show(const T& s) {for (auto& x : s) …

Python的pip配置、程序运行、生成exe文件

一、安装Python 通过官网下载对应的版本&#xff0c;安装即可。 下载地址&#xff1a;Download Python | Python.org Python标准库查看&#xff08;Python自带库&#xff09; Python 标准库文档 安装Python的时候&#xff0c;如果选第二个自定义安装要记得勾选安装pip 二、…

2024/05/25学习记录

1、面经复习&#xff1a;前端广度 2、代码随想录刷题&#xff1a;动态规划 3、rosebush 完成input组件基础

闲置商标转让出现这些状态时注意!

近日以前做转让的一个朋友的商标转让证明下来&#xff0c;正好是2个半月&#xff0c;普推知产老杨发现这个时间也太快&#xff0c;以前差不多四个月左右&#xff0c;有些朋友需要购买闲置商标&#xff0c;3个月内所有权就变成自己的。 在购买闲置商标时要注意有一些细节&#x…

MySQL的索引, 到底怎么创建?

目录 前言 MySQL的数据结构 索引是一把双刃剑 索引创建原则 如何给一个列挑选索引? 索引列的基数, 要尽量小 索引列的类型尽量小 索引长字符串的前缀 不要对索引列进行计算操作或者函数计算. 不要老想着查询, 想想插入该怎么办? 避免索引冗余和重复 前言 今天在…

TOTP 算法实现:双因素认证的基石(C/C++代码实现)

双因素认证&#xff08;Two-Factor Authentication, 2FA&#xff09;扮演着至关重要的角色。它像是一道额外的防线&#xff0c;确保即便密码被窃取&#xff0c;不法分子也难以轻易突破。在众多双因素认证技术中&#xff0c;基于时间的一次性密码&#xff08;Time-Based One-Tim…

【fastapi+mongodb】使用motor操作mongodb

上一篇文章&#xff0c;我们在电脑上安装了mongodb数据库。这篇文章&#xff0c;我们在fastapi后端使用motor操作mongodb 如果你还没看过上一篇文章&#xff0c;链接在这里&#xff1a;【MongoDB】安装与使用 安装 motor motor 是一个用于操作 mongodb 数据库的 python 库&a…

计算机网络 1

两台主机想通信&#xff0c;其实本质就是两个文件的资源交换&#xff0c;但是长距离的通信&#xff0c;面临的是很多的问题。这个时候需要通过一些方式来保证可靠性 什么是协议 这样一个例子&#xff0c;我是住在农村&#xff0c;我读高中了我需要去县里面读书。这个时候呢&…

Jupyter Lab 软件安装与使用

软件简介 Jupyter Lab 软件是一个基于web 的交互式开发环境&#xff0c;集成了代码编辑器、终端、文件管理器等功能&#xff0c;使得开发者可以在一个界面中完成各种任务。JupyterLab是Jupyter Notebook的全面升级&#xff0c;是一个集文本编辑器、终端以及各种个性化组件于一…

Java进阶学习笔记29——Math、System、Runtime

Math&#xff1a; 代表的是数学&#xff0c;是一个工具类&#xff0c;里面提供的都是对数据进行操作的一些静态方法。 示例代码&#xff1a; package cn.ensourced1_math;public class MathTest {public static void main(String[] args) {// 目标&#xff1a;了解Math类提供…

那智不二越机器人维修案例分享

那智不二越工业机器人在工业范围内广泛应用于各种生产领域。其示教器作为人机交互的重要设备&#xff0c;常常需要定期维护和Nachi不二越机械手示教盒修理。 【Nachi不二越机器人示教器维修步骤】 1. 关闭电源 在进行任何那智不二越机器人维修操作之前&#xff0c;务必确保机器…

<商务世界>《75 微课堂<茶叶(1)-质量分级>》

1 中国茶叶分级 中国的10级标准是按照茶叶的外观、香气、滋味、汤色、叶底五个方面进行评分&#xff0c;分别用10分制进行评分&#xff0c;总分为50分&#xff0c;得分越高&#xff0c;茶叶的品质就越高。具体的分数和等级如下表所示&#xff1a; 2 每级的特点 茶叶的质量等级…

OceanBase SQL 诊断和调优实践——【DBA从入门到实践】第七期

数据库作为绝大多数应用系统储存数据的核心系统&#xff0c;在用户系统需要访问数据时&#xff0c;有着至关重要的作用。在这些交互中&#xff0c;SQL 语言是应用与数据库系统之间“沟通”的桥梁&#xff0c;它负责将应用的指令传达给数据库。因此&#xff0c;SQL 的性能好坏直…

弱类型解析

php中 转化为相同类型后比较 先判断数据类型后比较数值 var_dump("asdf"0);#bool(true) var_dump("asdf"1);#bool(false) var_dump("0asdf"0);#bool(true) var_dump("1asdf"1);#bool(true)1、md5撞库 例&#xff1a; <?php incl…

【智能算法应用】模拟退火算法求解多车型车辆路径问题HFVRP

目录 1.算法原理2.多车型车辆路径HFVRP数学模型3.结果展示4.参考文献5.代码获取 1.算法原理 模拟退火算法&#xff08;Simulated Annealing, SA&#xff09;是一种通用概率算法&#xff0c;用于在给定一个大的搜索空间内寻找问题的近似最优解。这种算法受到物理中退火过程的启…