24/10/14 算法笔记 循环神经网络RNN

embedded/2024/10/18 8:24:47/

RNN:

        一种专门用于处理序列数据的神经网络,它能够捕捉时间序列中的动态特征。RNN的核心特点是其循环连接,这允许网络在不同时间步之间传递信息,从而实现对序列数据的记忆和处理能力。

应用的场景:

自然语言处理(NLP)语音识别,时间序列预测(天气预报,股票),机器人控制(预测动作),图像处理

在前向传播过程中,RNN利用前一时间步的隐藏状态和当前时间步的输入来计算当前时间步的隐藏状态和输出,这种循环连接允许RNN在处理序列数据时考虑时间依赖性,从而在多种应用中表现出色。

然而,RNN在训练过程中可能会遇到梯度消失和梯度爆炸的问题,尤其是在处理长序列时。为了解决这些问题,研究者们开发了LSTM(长短期记忆网络)和GRU(门控循环单元)等变体,它们通过引入门控机制来控制信息的流动,从而有效地解决了梯度问题,并提高了RNN在长序列数据上的性能。

隐状态:

它代表了网络在处理序列数据时的内部记忆或信息状态。隐状态是网络在每个时间步的内部表示,它携带了序列中之前信息的累积效应,并用于预测当前时间步的输出。

  1. 信息的累积: 隐状态捕捉了序列中之前所有时间步的信息。在处理序列数据时,如文本、语音或时间序列数据,隐状态帮助网络记住重要的上下文信息。

  2. 序列依赖性: 由于隐状态的存在,RNN能够处理输入数据之间的序列依赖性。这意味着网络的输出不仅依赖于当前的输入,还依赖于之前的输入。

困惑度:困惑度(Perplexity)是一个衡量语言模型性能的指标,尤其在自然语言处理(NLP)领域中。它主要用于评估语言模型对文本序列的预测能力。困惑度越低,表示模型对数据的预测越准确。

困惑度可以被解释为模型在预测下一个词时的平均分支数。例如,如果困惑度为 100,这意味着对于每个词,模型平均需要从 100 个可能的词中选择下一个词。因此,困惑度越低,表示模型的预测越准确。困惑度常用于比较不同的语言模型或评估模型在不同数据集上的性能。它也用于监控模型训练过程中的性能变化,帮助确定何时停止训练以避免过拟合。

循环神经网络的实现

初始化返回隐状态的函数

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

在一个时间步内计算隐状态和输出

RNN的前向传播

def rnn(inputs,state,params):W_xh,W_hh,b_h,W_hq,b_q = paramsH, = state #这里的逗号 , 是必需的,它告诉 Python 解释器 H 是一个单独的元素,而不是一个元组。如果没有逗号,H = state 将会尝试将整个 state 赋值给变量 Houtputs = []for X in inputs:H = torch.tanh(torch.mm(X,W_xh)+torch.mm(H,W_hh)+b_h)Y = torch.mm(H, W_hq) + b_q   #torch.mm矩阵乘法outputs.append(Y)return torch.cat(outputs, dim=0), (H,)

包装

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 = F.one_hot(X.T, self.vocab_size).type(torch.float32)return self.forward_fn(X, state, self.params)def begin_state(self, batch_size, device):return self.init_state(batch_size, self.num_hiddens, device)

检查输出是否具有正确的形状

num_hiddens = 512
net = RNNModelScratch(len(vocab), num_hiddens, d2l.try_gpu(), get_params,init_rnn_state, rnn)
state = net.begin_state(X.shape[0], d2l.try_gpu())
Y, new_state = net(X.to(d2l.try_gpu()), state)
Y.shape, len(new_state), new_state[0].shape

 预测

其中的prefix是一个用户提供的包含多个字符的字符串。 在循环遍历prefix中的开始字符时, 我们不断地将隐状态传递到下一个时间步,但是不生成任何输出。 这被称为预热(warm-up)期, 因为在此期间模型会自我更新(例如,更新隐状态), 但不会进行预测。 预热期结束后,隐状态的值通常比刚开始的初始值更适合预测, 从而预测字符并输出它们。
def predict_ch8(prefix, num_preds, net, vocab, device):  #@save"""在prefix后面生成新字符"""state = net.begin_state(batch_size=1, device=device)outputs = [vocab[prefix[0]]]get_input = lambda: torch.tensor([outputs[-1]], device=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(dim=1).reshape(1)))return ''.join([vocab.idx_to_token[i] for i in outputs])

梯度裁剪

def grad_clipping(net, theta):  #@save"""裁剪梯度"""if isinstance(net, nn.Module):params = [p for p in net.parameters() if p.requires_grad]else:params = net.paramsnorm = torch.sqrt(sum(torch.sum((p.grad ** 2)) for p in params))if norm > theta:for param in params:param.grad[:] *= theta / norm

训练

def train_epoch_ch8(net, train_iter, loss, updater, device, use_random_iter):"""训练网络一个迭代周期(定义见第8章)"""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], device=device)else: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 * y.numel(), y.numel())return math.exp(metric[0] / metric[1]), metric[1] / timer.stop()

用高级API实现

def train_ch8(net, train_iter, vocab, lr, num_epochs, device,use_random_iter=False):"""训练模型(定义见第8章)"""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)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:print(predict('time traveller'))animator.add(epoch + 1, [ppl])print(f'困惑度 {ppl:.1f}, {speed:.1f} 词元/秒 {str(device)}')print(predict('time traveller'))print(predict('traveller'))
num_epochs, lr = 500, 1
train_ch8(net, train_iter, vocab, lr, num_epochs, d2l.try_gpu())

检查一下使用随机抽样方法的结果

net = RNNModelScratch(len(vocab), num_hiddens, d2l.try_gpu(), get_params,init_rnn_state, rnn)
train_ch8(net, train_iter, vocab, lr, num_epochs, d2l.try_gpu(),use_random_iter=True)

循环神经网络的简洁实现

定义模型

num_hiddens = 256
rnn_layer = nn.RNN(len(vocab), num_hiddens)

使用张量来初始化隐状态

state = torch.zeros((1, batch_size, num_hiddens))

通过一个隐状态和一个输入,我们就可以用更新后的隐状态计算输出。

X = torch.rand(size=(num_steps, batch_size, len(vocab)))
Y, state_new = rnn_layer(X, state)
Y.shape, state_new.shape

为一个完整的循环神经网络模型定义一个RNNModel

class RNNModel(nn.Module):"""循环神经网络模型"""def __init__(self, rnn_layer, vocab_size, **kwargs):super(RNNModel, self).__init__(**kwargs)self.rnn = rnn_layerself.vocab_size = vocab_sizeself.num_hiddens = self.rnn.hidden_size# 如果RNN是双向的(之后将介绍),num_directions应该是2,否则应该是1if not self.rnn.bidirectional:self.num_directions = 1self.linear = nn.Linear(self.num_hiddens, self.vocab_size)else:self.num_directions = 2self.linear = nn.Linear(self.num_hiddens * 2, 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, statedef 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))

训练与预测

device = d2l.try_gpu()
net = RNNModel(rnn_layer, vocab_size=len(vocab))
net = net.to(device)
d2l.predict_ch8('time traveller', 10, net, vocab, device)

由于深度学习框架的高级API对代码进行了更多的优化, 该模型在较短的时间内达到了较低的困惑度。

  • 相比从零开始实现的循环神经网络,使用高级API实现可以加速训练。


http://www.ppmy.cn/embedded/127793.html

相关文章

ASP.NET Core8.0学习笔记(二十)——EFCore导航属性与外键

一、什么是实体间关系 数据库表(实体)之间的关系:一对一(学生-成绩)、一对多(学生-科目)、多对多(教师-班级)。数据库中,每一个实体可以由主键唯一标识&…

如何在Android中存储数据?

在Android中存储数据是开发过程中至关重要的一环,根据数据的类型、大小、访问频率及安全性需求,开发者可以选择多种存储方式。以下是Android中存储数据的几种主要方式,每种方式都有其特定的应用场景和优缺点。 一、SharedPreferences Share…

开发实时美颜系统:视频美颜SDK与直播平台的集成方案详解

本文将详细介绍如何开发一个实时美颜系统,并探讨视频美颜SDK与直播平台的集成方案,帮助开发者实现流畅的美颜功能。 一、视频美颜SDK的核心功能 视频美颜SDK主要提供了一系列实时处理视频图像的算法,常见的功能包括: 1.美白、磨…

PolarCTF靶场[web]file、ezphp WP

[WEB]file 知识点:文件上传漏洞 工具:Burp Suite、dirsearch 方法一: 根据页面提示,先用dirsearch工具扫一扫 访问/upload.php,发现一个上传区 在访问/uploaded/,再点击Parent Directory,发现链接到首页…

二叉搜索树(超详细+通俗易懂)

二叉搜索树定义: 二叉搜索树又被称为二叉排序树/二叉搜索树,为什么会被起这样的名字呢?我们先来看一张二叉搜索树的图片 这张图片里面的树就是二叉搜素树,那么二叉树有什么性质呢?我们从图中可以发现,每一个子树都是…

JavaSE——集合2:List(Iterator迭代器、增强for、普通for循环遍历集合)

目录 一、List (一)List接口基本介绍 二、List接口的常用方法 三、List集合的三种遍历方式 四、小练习——使用冒泡排序遍历集合 一、List (一)List接口基本介绍 List接口是Collection接口的子接口 public interface List<E> extends Collection<E> List集…

CMake函数:get_filename_component——从文件路径中提取特定组件

get_filename_component是CMake中的一个命令&#xff0c;用于从文件路径中提取特定组件&#xff08;例如目录、文件名、扩展名等&#xff09;。它的语法如下&#xff1a; get_filename_component(<VAR> <FileName> <COMP> [CACHE])其中&#xff1a; <VA…

Animatediff 工作流之神 Jerry Davos 新作! 使用Differential Diffusion使视频转绘生成稳定的背景。

今天给大家介绍一个新的ComfyUI工作流程&#xff0c;是Animatediff 工作流之神 Jerry Davos 新作。利用 Differential Diffusion 确保视频转绘的时候生成稳定的背景。 它可以使用蒙版对主体和背景进行不同的降噪值降噪&#xff0c;也可以设置它们的控制网为不同的强度。这样&a…