深度学习系列笔记09循环卷积神经网络RNN

1. 序列数据与模型

1.1 序列数据

  • 现实生活中很多数据都是有时序结构的,比如电影的评分(既不是固定的也不是随机的,会随着时间的变化而变化)
  • 在统计学中,对超出已知观测范围进行预测称为外推法(extrapolation),在现有的观测值之间进行估计称为内插法(interpolation)

在这里插入图片描述

处理序列数据应选用新的深度神经网络架构RNN。
这里我们所使用的数据发生了变化,包含了一个时间维度。
并且不同于之前的图像分类与识别,这里我们的数据之间是具有相关性的。

1.2 序列模型——自回归模型和隐变量自回归模型

在这里插入图片描述

1、自回归模型(autoregressive models)

  • 假设在现实情况下相当长的序列 x(t-1) , … , x1 可能是不必要的,因此只需要考虑满足某个长度为 τ 的时间跨度即可,即使用观测序列 x(t-1) , … , x(t-τ)
  • 这样做的好处是保证了在 t > τ 时,参数的数量是固定的
  • 之所以叫做自回归模型是因为该模型是对自己执行回归

2、隐变量自回归模型(latent autoregressive models)
在这里插入图片描述

  • 隐变量自回归模型中保留了对过去观测的总结 ht ,并且同时更新预测 xt_hat 和总结 ht
  • xt_hat = P( xt_hat | ht ) ; ht = g( h(t - 1) , x(t - 1) )
  • 因为 ht 从未被观测到,所以这类模型也被称为隐变量自回归模型

在这里插入图片描述

1.3 序列模型——隐马尔可夫模型

自回归模型的近似法中,使用 x(t-1) , … , x(t-τ),而不是 x(t-1) , … , x1 来估计 xt ,如果这种假设是成立的,也就是近似是精确的,可以称为满足马尔科夫条件( Markov condition )

如果 t=1,得到一个 一阶马尔可夫模型(first-order Markov model)

在这里插入图片描述
在这里插入图片描述

  • 假设当前数据只跟 τ 个过去数据点相关(每一次预测一个新的数据,只需要看过去 τ 个数据就可以了,τ 可以自由选择,τ 越小模型越简单,τ 越大模型越复杂),这样假设的好处是 τ 的值是固定的,不会随着时间的增大而增大(这样做也比较符合现实的逻辑,过去事件距离预测事件的时间越长,他们之间的关联程度就越小)

1.4 因果关系

在这里插入图片描述

1.5 小结

在这里插入图片描述

2. 文本预处理

文本预处理的核心思想是如何将文本中的词转化成能够训练的样本

本节中,我们将解析文本的常见预处理步骤。 这些步骤通常包括:

  • 1.将文本作为字符串加载到内存中
  • 2.将字符串拆分为词元(如单词和字符)
  • 3.建立一个词表,将拆分的词元映射到数字索引。
  • 4.将文本转换为数字索引序列,方便模型操作

2.1 读取数据集

import collections
import re
from d2l import torch as d2l
d2l.DATA_HUB['time_machine'] = (d2l.DATA_URL + 'timemachine.txt', '090b5e7e70c295757f55df93cb0a180b9691891a')

def read_time_machine(): 
    """将数据集加载到文本行的列表中"""
    with open(d2l.download('time_machine'), 'r') as f:
        lines = f.readlines()
    return [re.sub('[^A-Za-z]+', ' ', line).strip().lower() for line in lines]
    """去掉回车 将所有字母全部变成小写"""

lines = read_time_machine()

2.2 词元化

每个文本序列又被拆分成一个词元列表,词元(token)是文本的基本单位。 最后,返回一个由词元列表组成的列表,其中的每个词元都是一个字符串(string)。

tokenize 是 NLP 中一个比较常见的操作:将一个句子或者是一段文字转化成 token(字符串、字符或者是词)

def tokenize(lines, token='word'):  #将文本行列表(lines)作为输入
    """将文本行拆分为单词或字符词元"""
    if token == 'word':
        return [line.split() for line in lines]
    elif token == 'char':
        return [list(line) for line in lines]
    else:
        print('错误:未知词元类型:' + token)

tokens = tokenize(lines)

2.3 建立词表

词元的类型是字符串,而模型需要的输入是数字,因此这种类型不方便模型使用。 现在,让我们构建一个字典,通常也叫做词表(vocabulary), 用来将字符串类型的词元映射到从开始的数字索引中。
将拆分的词元映射到数字索引:将文本转换为数字索引序列,方便模型操作。

class Vocab:  
    """文本词表"""
    def __init__(self, tokens=None, min_freq=0, reserved_tokens=None):
        if tokens is None:
            tokens = []
        if reserved_tokens is None:
            reserved_tokens = []
        # 按出现频率排序
        counter = count_corpus(tokens)
        self._token_freqs = sorted(counter.items(), key=lambda x: x[1],
                                   reverse=True)
        # 未知词元的索引为0
        self.idx_to_token = ['<unk>'] + reserved_tokens
        self.token_to_idx = {token: idx
                             for idx, token in enumerate(self.idx_to_token)}
        for token, freq in self._token_freqs:
            if freq < min_freq:
                break
            if token not in self.token_to_idx:
                self.idx_to_token.append(token)
                self.token_to_idx[token] = len(self.idx_to_token) - 1

    def __len__(self):
        return len(self.idx_to_token)

    def __getitem__(self, tokens):
        if not isinstance(tokens, (list, tuple)):
            return self.token_to_idx.get(tokens, self.unk)
        return [self.__getitem__(token) for token in tokens]

    def to_tokens(self, indices):
        if not isinstance(indices, (list, tuple)):
            return self.idx_to_token[indices]
        return [self.idx_to_token[index] for index in indices]

    @property
    def unk(self):  # 未知词元的索引为0
        return 0

    @property
    def token_freqs(self):
        return self._token_freqs

def count_corpus(tokens):  #@save
    """统计词元的频率"""
    # 这里的tokens是1D列表或2D列表
    if len(tokens) == 0 or isinstance(tokens[0], list):
        # 将词元列表展平成一个列表
        tokens = [token for line in tokens for token in line]
    return collections.Counter(tokens)

2.4 小结

  • 文本是序列数据的一种最常见的形式之一。
  • 为了对文本进行预处理,我们通常将文本拆分为词元,构建词表,将词元字符串映射为数字索引,并将文本数据转换为词元索引以供模型操作。

3. 循环神经网络

什么是RNN模型?
RNN(Recurrent Neural Network)。中文称作循环神经网络,它一般以序列数据为输入,通过网络内部的结构设计有效捕捉序列之间的关系特征。一般也是以序列形式进行输出。

3.1 RNN简介

在这里插入图片描述

  • RNN的循环机制使模型隐层上一时间步产生的结果,能够作为当下时间步输入的一部分(当下时间步的输入除了正常的输入外还包括上一步的隐层输出)对当下时间步的输出产生影响。
  • RNN模型的作用:
    因为RNN结构能够很好利用序列之间的关系,因此针对自然界具有连续性的输入序列,如人类的语言,语音等进行很好的处理广泛应用于NLP领域的各项任务,如文本分类情感分析,意图识别,机器翻译等。

例如:给机器输入 What time is it?
经过4次循环,分析最终的输出O5来判断用户输入的信息。
在这里插入图片描述

3.2 RNN模型的分类

① 按照输入和输出结构进行分类

  • N vs N - RNN
  • N vs 1 - RNN
  • 1 vs N - RNN
  • N vs M - RNN

N vs N - RNN
它是RNN最基础的结构形式,最大的特点就是:输入和输出序列是等长的。由于这个限制的存在,使其适用范围比较小,可用于生成等长度的合辙诗句。在这里插入图片描述
N vs 1 - RNN
有时候我们要处理的问题输入是一个序列,而要求输出是一个单独的值而不是序列,我们只要在最后一个隐层输出h,上进行线性变换就可以了,大部分情况下,为了更好的明确结果,还要使用sigmold或者softmax进行处理。这种结构经常被应用在文本分类问题上。
在这里插入图片描述
1 vs N - RNN
如果输入不是序列而输出为序列的情况怎么处理呢?我们最常采用的一种方式就是使该输入作用于每次的输出之上。这种结构可用于将图片生成文字任务等。
在这里插入图片描述
N vs M - RNN
这是一种不限输入输出长度的RNN结构,它由编码器和解码器两部分组成。两者的内部结构都是某类RNN,它也被称为seq2seq架构。输入数据首先通过编码器,最终输出一个隐含变量c,之后最常用的做法是使用这个隐含变量c作用在解码器进行解码的每一步上,以保证输入信息被有效利用。
在这里插入图片描述

② 按照RNN内部结构进行分类

  • 传统RNN
  • LSTM
  • Bi - LSTM (双向 长短期记忆网络)
  • GRU
  • Bi - GRU

下面根据此分类方法一一介绍。

3.3 传统的RNN模型

3.3.1 内部结构分析:

在这里插入图片描述

在这里插入图片描述

3.3.2 数据流动过程:

在这里插入图片描述

3.3.3 公式:

在这里插入图片描述
激活函数tanh的作用:用于帮助调节流经网络的值,tanh函数将值压缩在-1和1之间.

3.3.4 实验代码:

#导入若干工具包
import torch
import torch.nn as nn
#实例化rnn对象
#第一个参数: input_size(输入张量x的维度)
#第二个参数: hidden_size(隐藏层的维度,隐藏层神经元数量)
#第三个参数: num_.layers(隐藏层的层数)
rnn = nn.RNN(5, 6, 1)
#设定输入的张量x
#第一个参数: sequence_.length(输入序列的长度)
#第二个参数: batch_size (批次的样本数)
#第三个参数: input_size(输入张量x的维度)
input1 = torch.randn(1, 3, 5)
#设定初始化的h0
#第一个参数: num_layers * num_ directions (层数*网络方向数)
#第二个参数: batch_size (批次的样本数)
#第三个参数: hidden_size(隐藏层的维度)
h0 = torch.randn(1, 3, 6)
#输入张量放入RNN 得到输出结果
output, hn = rnn(input1, h0)
print(output)
print(output.shape)
print(hn)
print(hn.shape)
-----------------------------------------
tensor([[[ 0.2020,  0.3738,  0.8060, -0.6857, -0.6111,  0.6123],
         [-0.9363,  0.3544, -0.2019,  0.8183, -0.1817, -0.6506],
         [-0.6587,  0.6482, -0.8166, -0.5486, -0.0163,  0.7191]]],
       grad_fn=<StackBackward0>)
torch.Size([1, 3, 6])
tensor([[[ 0.2020,  0.3738,  0.8060, -0.6857, -0.6111,  0.6123],
         [-0.9363,  0.3544, -0.2019,  0.8183, -0.1817, -0.6506],
         [-0.6587,  0.6482, -0.8166, -0.5486, -0.0163,  0.7191]]],
       grad_fn=<StackBackward0>)
torch.Size([1, 3, 6])

3.3.5 优缺点:

  • 传统RNN的优势:
    由于内部结构简单,对计算资源要求低,相比之后我们要学习的RNN变体:LSTM和GRU模型参数总量少了很多,在短序列任务上性能和效果都表现优异。
  • 传统RNN的缺点:
    传统RNN在解决长序列之间的关联时,通过实践,证明经典RNN表现很差,原因是在进行反向传播的时候,过长的序列导致梯度的计算异常,发生梯度消失或爆炸

3.4 LSTM 长短期记忆网络

3.4.1 内部结构分析:

LSTM (Long Short-Term Memory)也称长短期记忆结构,它是传统RNN的变体,与经典RNN相比能够有效捕捉长序列之间的语义关联缓解梯度消失或爆炸现象。同时LSTM的结构更复杂,它的核心结构可以分为四个部分去解析:遗忘门、输入门、细胞状态、输出门

在这里插入图片描述

  • 遗忘门:代表遗忘过去多少的信息
    在这里插入图片描述
    在这里插入图片描述

  • 输入门
    在这里插入图片描述
    在这里插入图片描述

  • 细胞状态更新图
    在这里插入图片描述
    在这里插入图片描述

  • 输出门
    在这里插入图片描述
    在这里插入图片描述

3.4.2 数据流动过程:

  • 遗忘门
    在这里插入图片描述
  • 输入门

在这里插入图片描述

  • 细胞状态更新图
    在这里插入图片描述
  • 输出门
    在这里插入图片描述

3.4.3 实验代码:

#导入若干工具包
import torch
import torch.nn as nn
#实例化LSTM对象
#第一个参数: input_size(输入张量x的维度)
#第二个参数: hidden_size(隐藏层的维度, 隐藏层的神经元数量)
#第三个参数: num_layers (隐藏层的层数)
lstm = nn.LSTM(5, 6, 2)
#初始化输入张量x
#第一个参数: sequence_length(输入序列的长度)
#第二个参数: batch_size(批次的样本数量)
#第三个参数: input_size(输入张量x的维度)
input1 = torch. randn(1, 3, 5)
#初始化隐藏层张量h0,和细胞状态c0
#第一个参数: num_layers * num_directions (隐藏层的层数*方向数.
#第二个参数: batch_size (批次的样本数量)
#第三个参数: hidden_size(隐藏层的维度)
h0 = torch. randn(2, 3, 6)
c0 = torch. randn(2, 3, 6)
#将inputI, h0, c0输入lstm中, 得到输出张量结果
output, (hn, cn) = lstm(input1, (h0, c0))
print (output)
print (output.shape)
print (hn)
print (hn.shape)
print(cn)
print (cn.shape)
---------------------------------------
tensor([[[-0.0356,  0.1013, -0.4488, -0.2720, -0.0605, -0.2809],
         [-0.0743,  0.3319,  0.1953,  0.3076, -0.4295,  0.0784],
         [-0.2240,  0.1658,  0.1031,  0.3426, -0.2790,  0.3442]]],
       grad_fn=<StackBackward0>)
torch.Size([1, 3, 6])
tensor([[[ 0.1035,  0.0796, -0.0350,  0.3091, -0.0084, -0.0795],
         [ 0.1013,  0.4979, -0.3049,  0.3802,  0.2845, -0.1771],
         [ 0.0804, -0.2093, -0.0581, -0.3859,  0.3678, -0.2731]],

        [[-0.0356,  0.1013, -0.4488, -0.2720, -0.0605, -0.2809],
         [-0.0743,  0.3319,  0.1953,  0.3076, -0.4295,  0.0784],
         [-0.2240,  0.1658,  0.1031,  0.3426, -0.2790,  0.3442]]],
       grad_fn=<StackBackward0>)
torch.Size([2, 3, 6])
tensor([[[ 0.1972,  0.1682, -0.0902,  0.9651, -0.0115, -0.1569],
         [ 0.1968,  1.4286, -0.5794,  0.9468,  0.7288, -0.3405],
         [ 0.2432, -1.5347, -0.1129, -1.4662,  0.5249, -0.6214]],

        [[-0.0889,  0.4005, -1.2702, -0.5516, -0.0938, -0.6681],
         [-0.1985,  0.6989,  0.4673,  1.0849, -0.7235,  0.2078],
         [-0.4790,  0.4915,  0.3270,  0.6981, -0.6362,  0.6638]]],
       grad_fn=<StackBackward0>)
torch.Size([2, 3, 6])

3.4.4 优缺点:

  • LSTM优势
    LSTM的结构能够有效减缓长序列问题中可能出现的梯度消失或爆炸,虽然并不能杜绝这种现象,但在更长的序列问题上表现优于传统RNN。
  • LSTM缺点:
    由于内部结构相对较复杂,因此训练效率在同等算力下较传统RNN低很多。

3.5 GRU 门控循环单元

GRU (Gated Recurrent Unit) 也称控循环单元结构。它也是传统RNN的变体,同LSTM一样能够有效捕捉长序列之间的语义关联,缓解梯度消失或爆炸现象。同时它的结构和计算要比LSTM更简单,它的核心结构可以分为两个部分去解析:更新门、重置门。

3.5.1 内部结构分析:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

3.5.3 实验代码:

#实例化GRU对象
#第一个参数: input_size(输入张量x的维度)
#第二个参数: hidden_size(隐藏层的维度, 隐藏层神经元的数量)
#第三个参数: num_layers (隐藏层的层数)
gru = nn. GRU(5, 6, 2)
#初始化输入张量input1
#第一个参数: sequence_.length(序列的长度)
#第二个参数: batch_size(批次的样本个数)
#第三个参数: input_size(输入张量x的维度)
input1 = torch.randn(1, 3, 5)
#初始化隐藏层的张量h0
#第一个参数: num_layers * num_ _di rections (隐藏层的层数*方向数)
#第二个参数: batch_size(批次的样本个数)
#第三个参数: hidden_size(隐藏层的维度)
h0 = torch. randn(2, 3, 6)
#将input1, h0输入GRU中, 得到输出张量结果
output, hn = gru(input1, h0)
print (output)
print (output. shape)
print (hn)
print (hn. shape )

3.5.4 优缺点:

  • GRU的优势:
    GRU和LSTM作用相同,在捕捉长序列语义关联时,能有效抑制梯度消失或爆炸,效果都优于传统RNN且计算复杂度相比LSTM要小。
  • GRU的缺点:
    GRU仍然不能完全解决梯度消失问题,同时其作用RNN的变体,有着RNN结构本身的一大弊
    端,即不可并行计算,这在数据量和模型体量逐步增大的未来,是RNN发展的关键瓶颈。
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值