双向RNN和双向LSTM

双向RNN和双向LSTM

一、双向循环神经网络BiRNN

1、为什么要用BiRNN

双向RNN,即可以从过去的时间点获取记忆,又可以从未来的时间点获取信息,也就是说具有以下两个特点:

捕捉前后文信息:传统的单向 RNN 只能利用先前的上下文信息,而 BiRNN 同时利用了输入序列的前后文信息。在很多任务中,如自然语言处理中的命名实体识别、机器翻译等,理解一个词的前后文语境至关重要。

例如:img

判断句子中Teddy是否是人名,如果只从前面两个词是无法得知Teddy是否是人名,如果能有后面的信息就很好判断了,这就需要用的双向循环神经网络。

提高精度:在处理某些序列数据时,单向 RNN 可能无法充分捕捉整个序列中的重要信息,导致性能欠佳。BiRNN 能够通过双向处理,提高模型的表达能力和准确度。

2、BiRNN的架构

双向循环神经网络(BRNN)的基本思想是提出每一个训练序列向前和向后分别是两个循环神经网络(RNN),而且这两个都连接着一个输出层。这个结构提供给输出层输入序列中每一个点的完整的过去和未来的上下文信息。下图展示的是一个沿着时间展开的双向循环神经网络。六个独特的权值在每一个时步被重复的利用,六个权值分别对应:输入到向前和向后隐含层(w1, w3),隐含层到隐含层自己(w2, w5),向前和向后隐含层到输出层(w4, w6)。值得注意的是:向前和向后隐含层之间没有信息流,这保证了展开图是非循环的。每一个输出都是综合考虑两个方向获得的结果再输出,如下图所示:

在这里插入图片描述

H → t = ϕ ( X t W x h ( f ) + H → t − 1 W h h ( f ) + b h ( f ) ) , H ← t = ϕ ( X t W x h ( b ) + H ← t + 1 W h h ( b ) + b h ( b ) ) , \begin{array}{l} \overrightarrow{\mathbf{H}}_{t}=\phi\left(\mathbf{X}_{t} \mathbf{W}_{x h}^{(f)}+\overrightarrow{\mathbf{H}}_{t-1} \mathbf{W}_{h h}^{(f)}+\mathbf{b}_{h}^{(f)}\right), \\ \overleftarrow{\mathbf{H}}_{t}=\phi\left(\mathbf{X}_{t} \mathbf{W}_{x h}^{(b)}+\overleftarrow{\mathbf{H}}_{t+1} \mathbf{W}_{h h}^{(b)}+\mathbf{b}_{h}^{(b)}\right), \end{array} H t=ϕ(XtWxh(f)+H t1Whh(f)+bh(f)),H t=ϕ(XtWxh(b)+H t+1Whh(b)+bh(b)),
拼接得到结果:
H t = [ H → t H ← t ] \mathbf{H}_{t}=\left[\overrightarrow{\mathbf{H}}_{t} \overleftarrow{\mathbf{H}}_{t}\right] Ht=[H tH t]
至于网络单元到底是标准的RNN还是GRU或者是LSTM是没有关系的

(GRU:把遗忘门和输入门合并成一个更新门(Update Gate),并且把Cell State和Hidden State也合并成一个Hidden State,它的计算如下图所示)

在这里插入图片描述

对于整个双向循环神经网络(BRNN)的计算过程如下:

向前推算(Forward pass):

对于双向循环神经网络(BRNN)的隐含层,向前推算跟单向的循环神经网络(RNN)一样,除了输入序列对于两个隐含层是相反方向的,输出层直到两个隐含层处理完所有的全部输入序列才更新:

img

向后推算(Backward pass):

双向循环神经网络(BRNN)的向后推算与标准的循环神经网络(RNN)通过时间反向传播相似,除了所有的输出层δ项首先被计算,然后返回给两个不同方向的隐含层:

img

3、代码(用IMDB进行情感分析)

import pandas as pd
import torch
import torch.nn as nn
import torch.optim as optim
from sklearn.model_selection import train_test_split
from torch.utils.data import Dataset, DataLoader
import spacy
from torchtext.vocab import GloVe# 加载数据集
base_csv = r'E:\kaggle\情感分析\archive (1)\IMDB Dataset.csv'
df = pd.read_csv(base_csv)# 分割数据集
train_data, test_data = train_test_split(df, test_size=0.2, random_state=42)# 保存为CSV文件
train_data.to_csv('train.csv', index=False)
test_data.to_csv('test.csv', index=False)# 加载Spacy分词器
spacy_en = spacy.load('en_core_web_sm')
tokenizer = spacy_en.tokenizerclass TextDataset(Dataset):def __init__(self, dataframe, text_field, label_field, vocab):self.dataframe = dataframeself.text_field = text_fieldself.label_field = label_fieldself.vocab = vocabdef __len__(self):return len(self.dataframe)def __getitem__(self, idx):text = self.dataframe.iloc[idx][self.text_field]label = self.dataframe.iloc[idx][self.label_field]tokens = tokenizer(text)token_ids = [self.vocab.get(token.text, self.vocab['<unk>']) for token in tokens]label = 1 if label == "positive" else 0return torch.tensor(token_ids, dtype=torch.long), torch.tensor(label, dtype=torch.long)# 加载预训练词向量
glove_embeddings = GloVe(name='6B', dim=100)
vocab = glove_embeddings.stoi
vocab['<unk>'] = len(vocab)  # 添加 <unk> 标记
glove_embeddings.vectors = torch.cat((glove_embeddings.vectors, torch.zeros(1, glove_embeddings.dim)), 0)# 创建数据集
train_dataset = TextDataset(train_data, 'review', 'sentiment', vocab)
test_dataset = TextDataset(test_data, 'review', 'sentiment', vocab)# 创建数据加载器
batch_size = 32def collate_fn(batch):texts, labels = zip(*batch)text_lengths = [len(text) for text in texts]padded_texts = nn.utils.rnn.pad_sequence(texts, batch_first=True, padding_value=0)return padded_texts, torch.tensor(labels, dtype=torch.long)train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, collate_fn=collate_fn)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False, collate_fn=collate_fn)# 定义模型
class BiRNN(nn.Module):def __init__(self, vocab_size, embed_size, hidden_size, num_layers, num_classes, pad_idx):super(BiRNN, self).__init__()self.hidden_size = hidden_sizeself.num_layers = num_layersself.num_directions = 2self.embedding = nn.Embedding(vocab_size, embed_size, padding_idx=pad_idx)self.rnn = nn.GRU(embed_size, hidden_size, num_layers, batch_first=True, bidirectional=True)self.fc = nn.Linear(hidden_size * self.num_directions, num_classes)def forward(self, x):h0 = torch.zeros(self.num_layers * self.num_directions, x.size(0), self.hidden_size).to(x.device)x = self.embedding(x)out, _ = self.rnn(x, h0)out = self.fc(out[:, -1, :])return out# 设置参数
vocab_size = len(vocab)
embed_size = 100
hidden_size = 256
num_layers = 2
num_classes = 2
pad_idx = 0model = BiRNN(vocab_size, embed_size, hidden_size, num_layers, num_classes, pad_idx)
model.embedding.weight.data.copy_(glove_embeddings.vectors)
model.embedding.weight.data[pad_idx] = torch.zeros(embed_size)# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = model.to(device)
criterion = criterion.to(device)# 训练模型
num_epochs = 5for epoch in range(num_epochs):model.train()epoch_loss = 0for texts, labels in train_loader:texts = texts.to(device)labels = labels.to(device)optimizer.zero_grad()outputs = model(texts)loss = criterion(outputs, labels)loss.backward()optimizer.step()epoch_loss += loss.item()print(f'Epoch {epoch + 1}/{num_epochs}, Loss: {epoch_loss / len(train_loader):.4f}')# 评估模型
model.eval()
with torch.no_grad():correct = 0total = 0for texts, labels in test_loader:texts = texts.to(device)labels = labels.to(device)outputs = model(texts)_, predicted = torch.max(outputs.data, 1)total += labels.size(0)correct += (predicted == labels).sum().item()accuracy = correct / totalprint(f'Accuracy: {accuracy * 100:.2f}%')# 保存模型
torch.save(model.state_dict(), 'model.ckpt')# 预测函数
def predict(text, model, vocab, device):model.eval()tokens = tokenizer(text)token_ids = [vocab.get(token.text, vocab['<unk>']) for token in tokens]token_tensor = torch.tensor(token_ids, dtype=torch.long).unsqueeze(0).to(device)with torch.no_grad():output = model(token_tensor)_, predicted = torch.max(output.data, 1)return 'positive' if predicted.item() == 1 else 'negative'# 示例使用
sample_text = "This movie was fantastic! I really enjoyed it."
prediction = predict(sample_text, model, vocab, device)
print(f'Prediction: {prediction}')

4、多层RNN

将许多RNN层堆叠,构成一个多层RNN网络。RNN每读取一个新的 x t \mathrm{x}_{\mathrm{t}} xt输就会生成状态向量 h t \mathrm{h}_{\mathrm{t}} ht作为当前时刻的输出和下一时刻的输入状态。 将T 个输入 x 0 ∼ x T \mathrm{x}_{0} \sim \mathrm{x}_{\mathrm{T}} x0xT 依次输入RNN,相应地会产生个输出。第一层RNN输出的T TT个状态向量可以作为第二层RNN的输入,第二层RNN拥有独立的参数,依次读取T个来自第一层RNN的状态向量,产生T个新的输出。第二层RNN输出的T个状态向量可以作为第三层RNN的输入,依此类推,构成一个多层RNN网络。
在这里插入图片描述

RNN自带层数和是否双向,只要把层数设置成你想要的层数就可以

class RNN(nn.Module):def __init__(self,vocab_size, embedding_dim, hidden_size, num_classes, num_layers,bidirectional):super(RNN, self).__init__()self.vocab_size = vocab_sizeself.embedding_dim = embedding_dimself.hidden_size = hidden_sizeself.num_classes = num_classesself.num_layers = num_layersself.embedding = nn.Embedding(self.vocab_size, embedding_dim, padding_idx=word2idx['<PAD>'])# 这里用了torch的embeding函数self.rnn = nn.RNN(input_size=self.embedding_dim, hidden_size=self.hidden_size,batch_first=True,num_layers=self.num_layers)# input_size:表示输入 xt 的特征维度,从embeding层的size# hidden_size:表示输出的特征维度# num_layers:表示网络的层数# nonlinearity:表示选用的非线性激活函数,默认是 ‘tanh’# bias:表示是否使用偏置,默认使用# batch_first:表示输入数据的形式,默认是 False,就是这样形式,(seq, batch, feature),也就是将序列长度放在第一位,batch 放在第二位# dropout:表示是否在输出层应用(随机丢掉一些特征,重要的调参)# bidirectional:表示是否使用双向的 rnn,默认是 False。def forward(self, x):batch_size, seq_len = x.shapeh0 = torch.randn(self.num_layers, batch_size, self.hidden_size).to(device)#初始化一个h0,也即c0,在RNN中一个Cell输出的ht和Ct是相同的,而LSTM的一个cell输出的ht和Ct是不同的#维度[layers, batch, hidden_len]x = self.embedding(x)   # 这里用的是nn的embedingout,_ = self.rnn(x, h0)  # 输入x,h0是初始化的特征,output = self.fc(out[:,-1,:]).squeeze(0) #因为有max_seq_len个时态,所以取最后一个时态即-1层return output   

二、双向LSTM----Bi-LSTM

BiLSTM无非就是把cell变成了LSTM的基本单元,其他同理如上所示

需要修改的内容:

  1. 替换RNN层为LSTM层:将nn.RNN替换为nn.LSTM
  2. 设置双向LSTM:将LSTM层的bidirectional参数设置为True
  3. 修改隐藏状态的初始化:LSTM的隐藏状态包含两个张量(隐藏状态和细胞状态),因此需要初始化这两个张量。
  4. 处理双向LSTM的输出:双向LSTM的输出维度会加倍(因为有两个方向)

在这里插入图片描述

class RNN(nn.Module):def __init__(self, vocab_size, embedding_dim, hidden_size, num_classes, num_layers, bidirectional):super(RNN, self).__init__()self.vocab_size = vocab_sizeself.embedding_dim = embedding_dimself.hidden_size = hidden_sizeself.num_classes = num_classesself.num_layers = num_layersself.bidirectional = bidirectionalself.num_directions = 2 if bidirectional else 1self.embedding = nn.Embedding(self.vocab_size, embedding_dim, padding_idx=word2idx['<PAD>'])self.lstm = nn.LSTM(input_size=self.embedding_dim, hidden_size=self.hidden_size, num_layers=self.num_layers, bidirectional=self.bidirectional, batch_first=True)#替换了原来的nn.RNN层,使其变为LSTM,并添加了双向参数# 全连接层的输入大小需要考虑双向LSTM的输出维度self.fc = nn.Linear(self.hidden_size * self.num_directions, self.num_classes)def forward(self, x):batch_size, seq_len = x.shape# 初始化隐藏状态和细胞状态,双向LSTM需要初始化2*num_layers的hidden statesh0 = torch.zeros(self.num_layers * self.num_directions, batch_size, self.hidden_size).to(device)c0 = torch.zeros(self.num_layers * self.num_directions, batch_size, self.hidden_size).to(device)x = self.embedding(x)  # Embedding layer# LSTM layerout, (hn, cn) = self.lstm(x, (h0, c0))# 取最后一个时间步的输出,双向LSTM会拼接两个方向的输出output = self.fc(out[:, -1, :])return output

三、两者异同

双向RNN(Bidirectional Recurrent Neural Network)和双向LSTM(Bidirectional Long Short-Term Memory)都是用于处理序列数据的神经网络模型。它们在处理时间序列、自然语言处理等任务中非常有用,因为它们能够考虑到序列中的前后信息。尽管它们有一些相似之处,但也有一些关键的不同点。

1、相同点

  1. 双向结构:无论是双向RNN还是双向LSTM,它们都有双向结构,这意味着它们有两个隐藏层,一个从前向后处理序列,另一个从后向前处理序列。这种结构允许模型在每个时间步上同时考虑前面和后面的信息,从而提高预测性能。

  2. 序列处理:两者都是为序列数据设计的,适用于自然语言处理、时间序列预测等任务。

  3. 隐层状态的结合:在每个时间步,它们都会结合来自两个方向的隐藏状态(通常是通过连接或求和)来产生输出。

2、不同点

  1. 基本单元

    • 双向RNN:基本单元是RNN,即简单的循环神经网络。它们依赖于隐藏状态将信息从一个时间步传播到下一个时间步。然而,RNN在处理长序列时容易遇到梯度消失和梯度爆炸问题,这限制了它们捕捉长距离依赖的能力。
    • 双向LSTM:基本单元是LSTM,即长短期记忆网络。LSTM通过引入遗忘门、输入门和输出门来控制信息的流动,从而更好地解决了梯度消失和梯度爆炸问题。因此,LSTM在处理长序列时比普通RNN更有效。
  2. 记忆能力

    • 双向RNN:由于其结构的限制,普通RNN在处理长期依赖关系时表现不佳。它们更适合短期依赖任务。
    • 双向LSTM:LSTM单元通过其门控机制能够有效地捕捉和保持长期依赖信息,因此在需要长时间记忆的任务中表现更优。
  3. 复杂性和计算成本

    • 双向RNN:结构相对简单,计算成本较低。但由于其局限性,可能需要更多层次的堆叠或者更复杂的架构来提升性能。
    • 双向LSTM:由于引入了门机制,LSTM单元比RNN单元复杂,计算成本也更高。但其增强的记忆能力通常能带来更好的性能。

参考https://zhuanlan.zhihu.com/p/519965073

https://fancyerii.github.io/books/rnn-intro/

https://www.cnblogs.com/Lee-yl/p/10066531.html

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

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

相关文章

Google Veo发布:AI生成视频的重大突破

在Google I/O 2024大会上&#xff0c;Google推出了Veo&#xff0c;这是一款能够根据文本提示生成1080p视频的AI模型。这次发布标志着Google在生成式AI领域的又一重大突破。 Veo的强大功能 Veo不仅能够生成各种视觉和电影风格的视频片段&#xff0c;包括风景镜头和延时摄影&am…

【计算机毕业设计】springboot房地产销售管理系统的设计与实现

相比于以前的传统手工管理方式&#xff0c;智能化的管理方式可以大幅降低房地产公司的运营人员成本&#xff0c;实现了房地产销售的 标准化、制度化、程序化的管理&#xff0c;有效地防止了房地产销售的随意管理&#xff0c;提高了信息的处理速度和精确度&#xff0c;能够及时、…

如何选择开箱机厂家,看这几点!

在现代化生产线上&#xff0c;开箱机作为自动化包装设备的重要组成部分&#xff0c;其选择对于提升生产效率、降低成本至关重要。然而&#xff0c;市场上开箱机厂家众多&#xff0c;如何挑选出合适的合作伙伴成为了许多企业面临的难题。与星派一起探讨如何选择开箱机厂家&#…

Docker 安装的MySQL迁移数据库

1. 导出数据库 docker ps :查看数据库对应的 CONTAINER ID docker exec -it id /bin/bash : 进入到mysql的docker实例中 cd /usr/bin : 进入到bin目录 mysqldump -u root -p123456 study > /root/study_backup0509.sql :使用mysqldump备份库&#xff0c;注意密码与-p之间…

MyBatis-Plus简介

一、简介 官网&#xff1a;https://baomidou.com/ MyBatis-Plus&#xff08;简称 MP&#xff09;是一个 MyBatis的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 二、特性 无侵入&#xff1a;只做增强不做改变&#xff0c;引…

Hadoop3:HDFS副本节点选择逻辑讲解

一、副本节点选择&#xff08;机架感知&#xff09; 说明 第一个副本&#xff0c;因为我们的client可能是web页&#xff0c;也可能是shell终端。 如果是web页&#xff0c;则随机选取一个节点&#xff0c;如果是shell终端&#xff0c;则选择当前shell终端所在的节点。 节点距离最…

优秀测试的核心能力!2招高效定位分析BUG!

之所以写这一篇文章&#xff0c;是突然想起来曾经在测试过程中被开发嘲讽过&#xff0c;事情是这样的&#xff0c;当时发现了一个疑似前端的Bug就草草提交到了禅道&#xff0c;结果刚来的女前端看到了就有点生气地问我为啥不查清到底是前后端问题就直接派给她前端了&#xff0c…

独家|暴雨推出基于国产X86芯片的四路服务器

伴随着智慧计算时代的到来和企业数字化转型的深入&#xff0c;人工智能、大数据、虚拟化等创新技术在应用普及的过程中&#xff0c;也在不断地细分和深化&#xff0c;使得企业的业务系统日趋复杂&#xff0c;数据量、数据类型更加庞大&#xff0c;对计算平台的性能要求“水涨船…

欢乐钓鱼大师攻略大全,新手钓鱼入坑必备攻略!

《欢乐钓鱼大师》是一款深受玩家喜爱的钓鱼手游&#xff0c;在游戏中&#xff0c;玩家可以通过升级和更换鱼竿来享受钓鱼的乐趣&#xff0c;并有机会钓到各种稀有鱼类。然而&#xff0c;很多玩家在闯关过程中遇到了不少困难。为了帮助大家更好地掌握游戏技巧&#xff0c;小编特…

最新版Ceph( Reef版本)块存储简单对接k8s(上集)

当前ceph 你的ceph集群上执行 1.创建名为k8s-rbd 的存储池 ceph osd pool create k8s-rbd 64 642.初始化 rbd pool init k8s-rbd3 创建k8s访问块设备的认证用户 ceph auth get-or-create client.kubernetes mon profile rbd osd profile rbd poolk8s-rbd部署 ceph-rbd-csi c…

Android系统不同版本存储权限

一、Android存储简介 Android系统分为内部存储和外部存储 从Android6.0开始不断在更新存储&#xff08;读写&#xff09;权限&#xff0c;除了在AndroidManifest.xml文件里声明&#xff0c;app运行时也要动态申请使用对应的权限 提醒&#xff1a;应用私有存储不需要动态申请权…

BERT for Joint Intent Classification and Slot Filling 论文阅读

BERT for Joint Intent Classification and Slot Filling 论文阅读 Abstract1 Introduction2 Related work3 Proposed Approach3.1 BERT3.2 Joint Intent Classification and Slot Filling3.3 Conditional Random Field 4 Experiments and Analysis4.1 Data4.2 Training Detail…