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

news2024/11/13 12:48:58

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_size
        self.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.shape
        hidden_state, cell_state = states
        hiddens = []
        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_tilda
            hidden_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_size
        self.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.shape
        hidden_state, = states
        hiddens = []
        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_tilda
            hiddens.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_size
        self.hidden_size = hidden_size
        self.num_layers = num_layers
        self._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_size
        self._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] = value
        super().__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.shape
        layer_hidden_states, = states
        layer_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_size
        self.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.shape
        hidden_state, reverse_hidden_state = states
        hiddens = []
        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_h
            hidden_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_reverse
            reverse_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.coloradmin.cn/o/2080531.html

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

相关文章

Datawhale X 李宏毅苹果书 AI夏令营 入门 Task1-机器学习

目录 机器学习基础案例分析-视频的点击次数预测机器学习流程相关公式 机器学习基础 机器学习&#xff1a;机器具备有学习的能力/让机器具备找一个函数的能力。比如语音识别、图像识别、 机器学习有不同的类别。 1&#xff09;回归&#xff1a;假设要找的函数的输出是一个数值…

密码学(二)---DES、SM、RSA

在使用本博客提供的学习笔记及相关内容时&#xff0c;请注意以下免责声明&#xff1a;信息准确性&#xff1a;本博客的内容是基于作者的个人理解和经验&#xff0c;尽力确保信息的准确性和时效性&#xff0c;但不保证所有信息都完全正确或最新。非专业建议&#xff1a;博客中的…

【网络安全】服务基础第一阶段——第四节:Windows系统管理基础---- NTFS安全权限与SMB文件共享服务器

目录 一、NTFS安全权限 1.1 文件系统 1.2 格式化磁盘中的文件系统 1.FAT32 2.NTFS 3.EXT 4.XFS 应用场景&#xff1a; 1.3 文件操作权限 1.4 权限管理系统 1.5 特殊权限 1.6 NTFS权限类型 二、权限管理实践 三、SMB文件共享服务器 3.1 文件共享服务器 3.2 常用的…

excel规划求解结合vba宏笔记

目录 概念与配置 规划求解定义 excel设置规划求解 宏的基本操作 excel批量进行规划求解案例 加载规划求解模块 宏的设置 宏录制vba 其他案例 概念与配置 规划求解定义 运用“规划求解”定义并求解问题 - Microsoft 支持 excel设置规划求解 EXCEL规划求解的简明教程…

OpenAI的GPT-4模型详细介绍:研发能力、应用场景、开发的合作、持续投入

Open AI GPT-4的详细介绍 OpenAI的GPT-4模型展现出了强大的研发能力&#xff1a; 这主要体现在以下几个方面&#xff1a; 1. 庞大的模型规模和参数数量 GPT-4拥有超过1万亿个参数&#xff0c;这是其前代模型GPT-3的显著扩展。如此庞大的模型规模使得GPT-4能够处理更为复杂…

如何从人机环境系统中捕捉语义

从人机环境系统中捕捉语义主要涉及将系统中的数据和信息转化为具有实际意义的内容&#xff0c;以便更好地理解和响应用户的需求。以下是几种常见的方法来捕捉语义&#xff1a; 1. 自然语言处理 (NLP) 方法&#xff1a;使用自然语言处理技术来分析和理解用户输入的文本或语音。N…

8.27-dockerfile的应用+私有仓库的创建

一、dockerfile应用 通过dockerfile创建⼀个在启动容器时&#xff0c;就可以启动httpd服务的镜像 1.步骤 : 1.创建⼀个⽬录&#xff0c;⽤于存储Docker file所使⽤的⽂件2.在此⽬录中创建Docker file⽂件&#xff0c;以及镜像制作所使⽤的⽂件3.使⽤docker build创建镜像4.使…

MySQL集群技术3——MySQL高可用之组复制

MySQL高可用之组复制 MySQL Group Replication(简称 MGR )是 MySQL 官方于 2016 年 12 月推出的一个全新的高可用与高扩 展的解决方案 组复制是 MySQL 5.7.17 版本出现的新特性&#xff0c;它提供了高可用、高扩展、高可靠的 MySQL 集群服务 MySQL 组复制分单主模式和多主模式…

大数据-104 Spark Streaming Kafka Offset Scala实现Redis管理Offset并更新

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

IngsollRang拧紧Insight IC-D控制器维修 系统参数设置

——设置菜单 Setup&#xff08;设置&#xff09;菜单及其子菜单用于编写拧紧策略并设置许多重要的系统参数。 在Setup&#xff08;设置&#xff09;菜单中&#xff0c;创建基本拧紧策略。 除策略外&#xff0c;您可以使用Setup&#xff08;设置&#xff09;菜单来设置时间、显…

堆和栈的概念和区别

文章目录 堆和栈的概念和区别栈 (Stack)堆 (Heap)详细描述补充说明逃逸分析 (Escape Analysis)栈上分配 (Stack Allocation)堆碎片化 (Heap Fragmentation) 堆和栈的概念和区别 堆和栈的概念和区别【改编自博客】 在说堆和栈之前&#xff0c;我们先说一下JVM&#xff08;虚拟…

家里两个路由器IP地址一样吗?‌IP地址冲突怎么办?‌

在家庭网络环境中&#xff0c;‌随着智能设备的不断增多和网络需求的日益提升&#xff0c;‌很多家庭选择使用两个或更多的路由器来扩展网络覆盖、‌提高网络性能。‌然而&#xff0c;‌在设置和使用多个路由器的过程中&#xff0c;‌一个常见且令人困惑的问题是&#xff1a;‌…

C++常见面试题(面试中总结)

文章目录 原文章链接1、回调函数的了解&#xff1f;2、递归算法解释&#xff1f;3、内存对齐解释&#xff1f;4、一种排序算法解释&#xff08;快速排序&#xff09;5、什么是多态&#xff1f;6、基类为什么需要虚析构函数&#xff1f;7、new和malloc的区别&#xff1f;8、指针…

ubuntu中安装Mysql以及使用Navicat远程连接的详细步骤【图文教程】

安装步骤 注意&#xff1a;建议大家都安装Ubuntu22.04的版本&#xff0c;在该版本下再安装MySQL8.0版本的数据库。 1查看当前是否安装了MySQL程序 $ dpkg -l |grep mysql 执行以上命令&#xff0c;如果执行后什么都没有&#xff0c;则进入到MySQL的安装步骤 2如果执行以上…

MATLAB进阶:应用微积分

今天我们继续学习matlab中的应用微积分 求导&#xff08;微分&#xff09; 1、数值微分 n维向量x(xi&#xff0c;x,… x)的差分定义为n-1维向量△x(X2-X1&#xff0c;X3-X2&#xff0c;…&#xff0c;Xn- Xn-1)。 diff(x) 如果x是向量&#xff0c;返回向量x的差分如果x是矩…

初识Linux · 有关gcc/g++

目录 前言&#xff1a; 1 gcc和g 2 翻译过程 2.1 预处理 2.2 编译 2.3 汇编 2.4 链接 前言&#xff1a; 继上文介绍了vim 和 yum&#xff0c;相当于介绍了 文本编译器&#xff0c;我们可以利用vim写代码&#xff0c;那么写代码的我们了解了&#xff0c;现在应该了解编译…

R语言统计分析——如何选择最佳回归模型

参考资料&#xff1a;R语言实战【第2版】 尝试获取一个回归方程时&#xff0c;实际上你就面对着从众多可能的模型中做选择的问题。是不是所有的变量都要包括&#xff1f;还是去掉那个对预测贡献不显著的变量&#xff1f;是否需要添加多项式项和/或交互项来提高拟合度&#xff1…

.NET WPF 抖动动画

.NET WPF 抖动动画 Demo Code <!-- 水平抖动 --> <Button Content"Hello World"><Button.RenderTransform><TranslateTransform x:Name"translateTransform" /></Button.RenderTransform><Button.Triggers><Even…

SP: eric

靶机搭建 靶机下载地址 在Virtualbox中打开下载好的靶机&#xff0c;网络配置修改为桥接模式&#xff0c;启动靶机即可。 信息收集 主机发现 nmap 192.168.31.0/24 -Pn -T4 靶机IP&#xff1a;192.168.31.244 端口扫描 nmap 192.168.31.244 -A -p- -T4 根据端口扫描结果…

Linux驱动学习之内核poll阻塞

在linux系统编程课程中学习过多路IO复用&#xff0c;简单来说就三个函数select&#xff0c;poll&#xff0c;epoll。 对于select 此函数是跨平台的&#xff0c;可以在windows&#xff0c;Linux中使用。 对于poll与epoll 只能在linux平台下使用&#xff0c; epoll底层实现是一个…