NLP从零开始------14.文本中阶序列处理之语言模型(2)

3.2 长短期记忆

        梯度消失问题的一个解决方案是使用循环神经网络的变体——长短期记忆( long short- term memory, LSTM)。

        长短期记忆的原理是, 在每一步t, 都保存一个隐状态h^{t}和一个单元状态( cell state) c^{t}, 通过单元状态来存储长距离信息, 长短期记忆模型使用3个门控( gate) 来控制单元状态的读写和擦除。这些门控同样以向量形式表示, 其中元素的取值为0或1,0表示门控关闭, 1表示门控打开。门控是动态变化的, 每一步都将重新计算门控。

        接下来展示长短期记忆模型每一步的具体计算过程。假设第t步的输入为x^{t},隐状态与单元状态分别为h^{t}c^{t}。我们依次计算如下向量, 所有向量的维度相同。

        遗忘门( forget gate),控制上一个单元状态中的哪些信息被保留, 哪些信息被遗忘:
                                        f^{(t)}= \sigma (W_{f}h^{(t-1)}+U_{f}x^{(t)}+b_{f})
        输入门( input gate), 控制哪些信息被写入单元状态:
                                        i⁽ᵗ⁾=σ(Wᵢh⁽ᵗ⁻¹⁾+Uᵢx⁽ᵗ⁾+bᵢ)
        输出门( output gate), 控制单元状态中的哪些信息被写入隐状态:
                                        o⁽ᵗ⁾=σ(Wₒh⁽ᵗ⁻¹⁾+Uₒx⁽ᵗ⁾+bₒ)
        新的单元内容, 即待写入单元的新信息:
                                        \tilde {c}^{(t)}= \tanh (W_{c}h^{(t-1)}+U_{c}x^{(t)}+b_{c})
        单元状态,通过擦除(遗忘)上一个单元状态中的部分信息并写入部分新的信息而获得:
        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        c^{(t)}=f^{(t)} \odot c^{(t-1)}+i^{(t)} \odot \tilde {c}^{(t)}
        隐状态, 其内容是从单元状态中输出的一部分信息:
                                                h⁽ᵗ⁾=o⁽ᵗ⁾\odottanhc⁽ᵗ⁾

        其中,\sigma (x)= \frac {1}{1+ \exp (-x)}为 sigmoid 激活函数,\tanh (x)= \frac { \exp (x)- \exp (-x)}{ \exp (x)+ \exp (-x)}为 tanh 激活函数,⊙运算为逐元素相乘( element- wise product)。

        长短期记忆的模型结构使得跨越多步保存信息变得更为简单直接: 如果某一维度的遗忘门打开、输入门关闭,那么单元状态中对应维度的信息就会被完全保存下来。通过这种方式可以跨越多步保留信息,从而更好地建模长距离依赖。而这种跨越多步的状态之间的依赖关系也意味着它们之间存在非零梯度,因而缓解了梯度消失问题。然而,长短期记忆并不能使所有门控会如我们所愿那样打开和关闭,因此不能保证完全没有梯度消失或梯度爆炸的问题,只是长短期记忆在大部分场景中缓解了这些问题。

        接下来仿照循环神经网络实现长短期记忆,由于采用同样的接口, 我们可以复用之前的训练代码。

        

# 长短期记忆
def gate_params(input_size, hidden_size):return (nn.Parameter(normal((input_size, hidden_size))),nn.Parameter(normal((hidden_size, hidden_size))),nn.Parameter(torch.zeros(hidden_size)))class LSTM(nn.Module):def __init__(self, input_size, hidden_size):super(LSTM, self).__init__()self.input_size = input_sizeself.hidden_size = hidden_size# 输入门参数self.W_xi, self.W_hi, self.b_i = gate_params(input_size, hidden_size)# 遗忘门参数self.W_xf, self.W_hf, self.b_f = gate_params(input_size, hidden_size)# 输出门参数self.W_xo, self.W_ho, self.b_o = gate_params(input_size, hidden_size)# 候选记忆单元参数self.W_xc, self.W_hc, self.b_c = gate_params(input_size, hidden_size)def init_rnn_state(self, batch_size, hidden_size):return (torch.zeros((batch_size, hidden_size), dtype=torch.float),torch.zeros((batch_size, hidden_size), dtype=torch.float))def forward(self, inputs, states):seq_len, batch_size, _ = inputs.shapehidden_state, cell_state = stateshiddens = []for step in range(seq_len):I = torch.sigmoid(torch.mm(inputs[step], self.W_xi) \+ torch.mm(hidden_state, self.W_hi) + self.b_i)F = torch.sigmoid(torch.mm(inputs[step], self.W_xf) \+ torch.mm(hidden_state, self.W_hf) + self.b_f)O = torch.sigmoid(torch.mm(inputs[step], self.W_xo) \+ torch.mm(hidden_state, self.W_ho) + self.b_o)C_tilda = torch.tanh(torch.mm(inputs[step], self.W_xc) \+ torch.mm(hidden_state, self.W_hc) + self.b_c)cell_state = F * cell_state + I * C_tildahidden_state = O * torch.tanh(cell_state)hiddens.append(hidden_state)return torch.stack(hiddens, dim=0), (hidden_state, cell_state)data_loader = DataLoader(torch.tensor(sent_tokens, dtype=torch.long), batch_size=16, shuffle=True)lstm = LSTM(128, 128)
train_rnn_lm(data_loader, lstm, vocab_size, hidden_size=128, epochs=200, learning_rate=1e-3)

        代码结果:

epoch-199, loss=0.4065: 100%|█| 200/200 [29:26<00:00,  8.83s

        长短期记忆有很多变体,其中一个著名的简化变体是门控循环单元( gated recurrent unit,GRU)。门控循环单元不再包含单元状态,门控也从3个减少到两个。我们同样给出第t步的计算过程, 其中输入为x^{t}, 隐状态为h^{t}
        更新门( update gate), 控制隐状态中哪些信息被更新或者保留:
        ​​​​​​​        ​​​​​​​        u^{(t)}= \sigma (W_{u}h^{(t-1)}+U_{ \nu }x^{(t)}+b_{u})
        重置门( reset gate), 控制前一个隐状态中哪些部分被用来计算新的隐状态:
                                r⁽ᵗ⁾=σ(Wᵣh⁽ᵗ⁻¹⁾+Uᵣx⁽ᵗ⁾+bᵣ)
        新的隐状态内容, 根据重置门从前一个隐状态中选择部分信息和当前的输入来计算:
        ​​​​​​​        ​​​​​​​        \tilde h^{(r)}= \tanh (W_{h}(r^{(t)} \odot h^{(t-1)})+U_{h}x^{(t)}+b_{h})

        隐状态,由更新门控制哪些部分来源于前一步的隐状态、哪些部分使用新计算的内容:
                                h⁽ᵗ⁾=(1-u⁽ᵗ⁾)⊙h⁽ᵗ⁻¹⁾+u⁽ᵗ⁾⊙h⁽ᵗ⁾
        下面仿照长短期记忆实现门控循环单元。

# 门控循环单元
class GRU(nn.Module):def __init__(self, input_size, hidden_size):super(GRU, self).__init__()self.input_size = input_sizeself.hidden_size = hidden_size# 更新门参数self.W_xu, self.W_hu, self.b_u = gate_params(input_size, hidden_size)# 重置门参数self.W_xr, self.W_hr, self.b_r = gate_params(input_size, hidden_size)# 候选隐状态参数self.W_xh, self.W_hh, self.b_h = gate_params(input_size, hidden_size)def init_rnn_state(self, batch_size, hidden_size):return (torch.zeros((batch_size, hidden_size), dtype=torch.float),)def forward(self, inputs, states):seq_len, batch_size, _ = inputs.shapehidden_state, = stateshiddens = []for step in range(seq_len):U = torch.sigmoid(torch.mm(inputs[step], self.W_xu)\+ torch.mm(hidden_state, self.W_hu) + self.b_u)R = torch.sigmoid(torch.mm(inputs[step], self.W_xr)\+ torch.mm(hidden_state, self.W_hr) + self.b_r)H_tilda = torch.tanh(torch.mm(inputs[step], self.W_xh)\+ torch.mm(R * hidden_state, self.W_hh) + self.b_h)hidden_state = U * hidden_state + (1 - U) * H_tildahiddens.append(hidden_state)return torch.stack(hiddens, dim=0), (hidden_state,)data_loader = DataLoader(torch.tensor(sent_tokens, dtype=torch.long), batch_size=16, shuffle=True)gru = GRU(128, 128)
train_rnn_lm(data_loader, gru, vocab_size, hidden_size=128, epochs=200, learning_rate=1e-3)
#%% md
epoch-199, loss=0.2357: 100%|█| 200/200 [38:39<00:00, 11.60s<Figure size 640x480 with 1 Axes>

3.3 多层双向循环神经网络

        循环神经网络(包括像长短期记忆这样的变体) 可以很方便地扩展为多层和双向结构。
多层循环神经网络将多个循环神经网络堆叠起来,前一层的输出作为后一层的输入, 最后一层的输出作为整个模型最终的输出。通过这种方式可以增加整个模型的表达能力,以获得更好的效果, 如图所示。

        

        下面在循环神经网络的基础上实现多次循环神经网络。

# 多层循环神经网络
class DeepRNN(nn.Module):def __init__(self, input_size, hidden_size, num_layers, dropout=0.):super(DeepRNN, self).__init__()self.input_size = input_sizeself.hidden_size = hidden_sizeself.num_layers = num_layersself._flat_weight_names = []self._all_weights = []self.drop = nn.Dropout(p=dropout)# 定义每一层循环神经网络的参数,由于参数数量不固定,# 因此使用统一的命名方法更方便调用和管理for layer in range(num_layers):W_xh = nn.Parameter(normal((input_size, hidden_size)))W_hh = nn.Parameter(normal((hidden_size, hidden_size)))b_h = nn.Parameter(torch.zeros(hidden_size))layer_params = (W_xh, W_hh, b_h)params_names = [f'W_xh_l{layer}', f'W_hh_l{layer}', \f'b_h_l{layer}']# 将新的参数加入到成员列表中for name, param in zip(params_names, layer_params):setattr(self, name, param)self._flat_weight_names.extend(params_names)self._all_weights.append(params_names)input_size = hidden_sizeself._flat_weights = [getattr(self, wn) if hasattr(self, wn) \else None for wn in self._flat_weight_names]def __setattr__(self, attr, value):if hasattr(self, '_flat_weight_names') and \attr in self._flat_weight_names:idx = self._flat_weight_names.index(attr)self._flat_weights[idx] = valuesuper().__setattr__(attr, value)def init_rnn_state(self, batch_size, hidden_size):return (torch.zeros((self.num_layers, batch_size, hidden_size), dtype=torch.float),)def forward(self, inputs, states):seq_len, batch_size, _ = inputs.shapelayer_hidden_states, = stateslayer_h_t = []input_states = inputs# 需要保存每一层的输出作为下一层的输入for layer in range(self.num_layers):hiddens = []hidden_state = layer_hidden_states[layer]for step in range(seq_len):xh = torch.mm(input_states[step], getattr(self, f'W_xh_l{layer}'))hh = torch.mm(hidden_state, getattr(self, f'W_hh_l{layer}'))hidden_state = xh + hh + getattr(self, f'b_h_l{layer}')hidden_state = self.drop(torch.tanh(hidden_state))hiddens.append(hidden_state)input_states = torch.stack(hiddens, dim=0)layer_h_t.append(hidden_state)return input_states, torch.stack(layer_h_t, dim=0)data_loader = DataLoader(torch.tensor(sent_tokens, dtype=torch.long), batch_size=16, shuffle=True)
deep_rnn = DeepRNN(128, 128, 2)
train_rnn_lm(data_loader, deep_rnn, vocab_size, hidden_size=128, epochs=200, learning_rate=1e-3)

        

epoch-199, loss=0.3928: 100%|█| 200/200 [34:04<00:00, 10.22s<Figure size 640x480 with 1 Axes>

        双向循环神经网络的结构包含一个正向的循环神经网络和一个反向的循环神经网络(即从右到左读入文字序列),将这两个网络对应位置的输出拼接得到最终的输出,如下图所示。

        需要注意的是,双向循环神经网络在每个位置的输出同时包含来自左边和右边的信息,也就是整个输入序列的信息,因此双向循环神经网络不能用于语言模型,因为语言模型需要仅根据序列中每个词左边的信息来预测这个词。但是,在后续章节所讨论的很多其他任务中,双向循环神经网络因可以利用整个输入序列的信息而有着比单向循环神经网络更好的表现。
        下面的双向循环神经网络是一个简单的示例,要求一次只能输入一个序列。如果想在一个批次中并行处理不同长度的输入序列以获得更高的运行效率,可以通过填充将不同长度的输入序列对齐。单向循环神经网络的填充较为简单,只需在每个序列末尾添加字符。双向循环神经网络的填充更加复杂,正向和反向的循环神经网络的读取顺序相反, 难以保证两个方向的循环神经网络都在末尾填充,实现起来较为困难。有关解决方案可以参考PyTorch中的 pack _ padded _ sequence 和 pad _ packed _ sequence。双向循环神经网络不能用于训练语言模型,因此不再提供训练示例代码。        

# 双向循环神经网络
class BiRNN(nn.Module):def __init__(self, input_size, hidden_size):super(BiRNN, self).__init__()self.input_size = input_sizeself.hidden_size = hidden_size# 正向循环神经网络参数self.W_xh = nn.Parameter(normal((input_size, hidden_size)))self.W_hh = nn.Parameter(normal((hidden_size, hidden_size)))self.b_h = nn.Parameter(torch.zeros(hidden_size))# 反向循环神经网络参数self.W_xh_reverse = nn.Parameter(normal((input_size, hidden_size)))self.W_hh_reverse = nn.Parameter(normal((hidden_size, hidden_size)))self.b_h_reverse = nn.Parameter(torch.zeros(hidden_size))# 分别为正向和反向循环神经网络准备初始状态def init_rnn_state(self, batch_size, hidden_size):return (torch.zeros((batch_size, hidden_size), dtype=torch.float),torch.zeros((batch_size, hidden_size), dtype=torch.float))def forward(self, inputs, states):seq_len, batch_size, _ = inputs.shapehidden_state, reverse_hidden_state = stateshiddens = []for step in range(seq_len):xh = torch.mm(inputs[step], self.W_xh)hh = torch.mm(hidden_state, self.W_hh)hidden_state = xh + hh + self.b_hhidden_state = torch.tanh(hidden_state)hiddens.append(hidden_state)reverse_hiddens = []for step in range(seq_len-1, -1, -1):xh = torch.mm(inputs[step], self.W_xh_reverse)hh = torch.mm(reverse_hidden_state, self.W_hh_reverse)reverse_hidden_state = xh + hh + self.b_h_reversereverse_hidden_state = torch.tanh(reverse_hidden_state)reverse_hiddens.insert(0, reverse_hidden_state)# 将正向和反向循环神经网络输出的隐状态拼接在一起combined_hiddens = []for h1, h2 in zip(hiddens, reverse_hiddens):combined_hiddens.append(torch.cat([h1, h2], dim=-1))return torch.stack(combined_hiddens, dim=0), ()

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

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

相关文章

Redis内存淘汰

Redis内存淘汰 文章目录 Redis内存淘汰Redis可以存储多少数据淘汰策略选择哪种淘汰算法LRU什么是LRURedis的近似LRU为什么不直接使用LRU近似的LRU淘汰池的优化LRU优化后的对比 LFU什么是LFU为什么引入LFURedis的LFU策略访问频率衰减频率更新 Redis可以存储多少数据 maxmemory配…

Docker续4:harbar私有仓库管理

一、新建一台docker&#xff08;192.168.1.77&#xff09;&#xff08;用于上篇创建私有镜像后的测试&#xff08;拉取镜像&#xff09;&#xff09; 运行docker安装脚本 [rootlocalhost ~]# vim /etc/docker/daemon.json //编辑配置文件 { "…

uniapp-Vue项目如何实现国际化,实现多语言切换,拒绝多套开发,一步到位,看这篇就够

一 安装 找到自己的项目,输入cmd进入命令行,输入安装命令,点击回车进行下载: npm install vue-i18nnext 下载完将在项目的配置文件中看到: 二 使用 2.1 在项目中创建一个文件夹如:lang 用于存放不同语言的包。这些语言文件通常为JSON格式 2.2 在项目main.js文件中引入并初…

H264编码原理(一)压缩背后的秘密

一、引言 在当今的数字视频世界中&#xff0c;H.264编码技术无疑占据着至关重要的位置。虽然H.264编码原理可能听起来复杂又深奥&#xff0c;但只要深入了解视频的特性&#xff0c;就能明白为什么它需要如此设计。通过利用视频内容的冗余性和人眼的感知特性&#xff0c;H.264能…

后端面试真题整理

面试问题整理 本人主要记录2024年秋招、春招过程中的疑难八股真题&#xff0c;参考来源&#xff1a;牛客网、知乎等。 八股 深拷贝与浅拷贝 浅拷贝&#xff1a; 浅拷贝会在堆上创建一个新的对象&#xff08;区别于引用拷贝的一点&#xff09;&#xff0c;不过&#xff0c;如果…

黑马JavaWeb开发笔记07——Ajax、Axios请求、前后端分离开发介绍、Yapi详细配置步骤

文章目录 前言一、Ajax1. 概述2. 作用3. 同步异步4. 原生Ajax请求&#xff08;了解即可&#xff09;5. Axios&#xff08;重点&#xff09;5.1 基本使用5.2 Axios别名&#xff08;简化书写&#xff09; 二、前后端分离开发1. 介绍1.1 前后台混合开发1.2 前后台分离开发方式&…

linux下一切皆文件,如何理解?

linux下一切皆文件&#xff0c;不管你有没有学过linux&#xff0c;都应该听过这句话&#xff0c;就像java的一切皆对象一样。 今天就来看看它的真面目。 你记住了&#xff0c;只要一个竞争退出它的PCB要被释放文件名&#xff0c;客服表也要被释放。那么&#xff0c;指向这个文件…

使用代理和 Python 高效进行亚马逊数据抓取: 实用指南

文章目录 一、简介二、为什么要抓取亚马逊&#xff1f;三、了解代理3. 1. 搜索亚马逊的代理类型 四、为什么使用 Python&#xff1f;五、设置5. 1. 选择代理5. 2. 设置代理 六、抓取数据七、解析数据八、 结论 一、简介 在现代数字环境中&#xff0c;分析网络流量对于优化网站…

YOLOv10:实时端到端目标检测

摘要 https://arxiv.org/pdf/2405.14458 近年来&#xff0c;YOLO系列模型因其在计算成本与检测性能之间的有效平衡&#xff0c;在实时目标检测领域占据了主导地位。研究人员在YOLO的架构设计、优化目标、数据增强策略等方面进行了探索&#xff0c;并取得了显著进展。然而&…

RabbitMQ的基础概念介绍

MQ的三大特点&#xff1a;削峰、异步、解耦 1.RabblitMQ概念介绍 1.1概念 RabbitMQ是由erlang语言开发&#xff0c;基于AMQP&#xff08;Advanced Message Queue 高级消息队列协议&#xff09;协议实现的消息队列&#xff0c;它是一种应用程序之间的通信方法&#xff0c;消息…

【管理型文档】软件需求管理过程(原件)

软件需求管理规程应明确需求收集、分析、确认、变更控制及验证等流程&#xff0c;确保需求准确反映用户期望&#xff0c;支撑软件开发。该规程要求系统记录需求来源&#xff0c;通过评审确保需求完整、清晰、无歧义&#xff0c;实施变更控制以维护需求基线稳定&#xff0c;并持…

JVM类加载机制—类加载器和双亲委派机制详解

一、概述 上篇我们介绍了JVM类加载机制—JVM类加载过程&#xff0c;类加载过程是类加载机制第一阶段&#xff0c;这一阶段主要做将类的字节码&#xff08;class文件&#xff09;加载JVM内存模型中&#xff0c;并转换为JVM内部的数据结构&#xff08;如java.lang.Class实例&…

软件测试——自动化测试selenium常用函数

目录 元素的定位cssSelectorxpath函数 操作测试对象窗口切换窗口窗口设置大小窗口切换屏幕截图关闭窗口 等待强制等待隐式等待显示等待 浏览器导航弹窗警告弹窗确认弹窗提示弹窗 文件上传浏览器参数设置 元素的定位 web⾃动化测试的操作核⼼是能够找到⻚⾯对应的元素&#xff0…

【操作系统】14.I/O设备怎么分配和回收?

5.2 I/O设备怎么分配和回收&#xff1f; 5.2.1 I/O核心子系统 I/O调度 设备保护 假脱机技术&#xff08;SPOOLing技术&#xff09; ​ 输入井和输出井 ​ 输入进程和输出进程 ​ 输入缓冲区和输出缓冲区 设备分配与回收 ​ 设备分配应考虑的因素 ​ 静态分配与动态分配 ​ 设备…

上传文件(用户导入),第二次选择文件时没有反应(可用)

https://gitee.com/y_project/RuoYi-Cloud/issues/I582YB PS&#xff1a;恰好我使用的版本是 支持 handleRemove &#xff0c;所以很容易就解决了

企业如何选型人力资源管理系统?(附HR系统对比分析)

随着企业规模的扩大&#xff0c;人力资源管理系统成为了大中型企业不可或缺的工具。近年来&#xff0c;众多新技术产品层出不穷&#xff0c;这些技术和产品的实际功能和适用性并不明确&#xff0c;大量的新概念和新厂商通过各种渠道宣传&#xff0c;市场上信息过载使得企业难以…

美畅物联丨物联网平台的标准化之路:物模型设计的创新及应用

随着物联网&#xff08;IoT&#xff09;技术以前所未有的迅猛之势蓬勃发展&#xff0c;海量的物联网终端与应用纷纷接入&#xff0c;这不可避免地引发了数据与应用层面的异构化难题&#xff0c;进而形成了复杂且多变的碎片化问题。物联网感知数据因其具备多源异构的显著特性&am…

Linux中的常见命令——时间日期类命令

1、date显示当前时间 基本语法 写法功能描述date显示当前时间date %Y显示当前年份【四位数】date %m显示当前月份date %d显示当前是哪一天date "%Y-%m-%d %H:%M:%S" 显示年月日时分秒 【由于年月日和时分秒中间有空格所以需要用引号引起来】 实操案例 1、显示当…

【VUE入门级温故知新】一文向您详细介绍~组件属性Props(选项式API)

大家好&#xff0c;我是DX3906 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我静心耕耘大前端领域、真诚分享知识与智慧的小天地&#xff01;&#x1f387; 前面和大家分享了《如何从零开始创建一个 Vue 应用》 《VUE模板语法(超详细讲解)》 《一文向您详细介绍~Vu…

uniapp中H5网页怎么实现自动点击事件

<template><view><button ref"myButton" click"handleClick">点击我</button></view> </template><script> export default {mounted() {this.$nextTick(() > {const button this.$refs.myButton;console.l…