《PyTorch深度学习实践》学习笔记:循环神经网络(基础篇)


1. DNN

在这里插入图片描述
深度神经网络是稠密网络,Dense连接就是指全连接。有很多线性层对输入数据进行空间上的变换,又叫DNN。输入x1,x2,…,x8是数据样本的不同特征。

考虑这样一个场景:比如预测天天气,就需要知道之前几天的数据,每一天的数据都包含若个特征(温度、气压、雨天),如果你已知今天的温度、气压等特征去预测有没有雨,这是没有用的,需提前预测,需要之前若干天的数据作为输入。

假设现在取前3天,每一天有3个特征(温度、气压、雨天),如何预测第4天是否有雨?

第一种方法:把x1,x2,x3拼成有9个维度的长向量,然后去训练最后一天是否有雨。用全连接稠密网络进行预测,如果输入序列很长,而且每一个序列维度很高的话,对网络训练有很大挑战,因为稠密网络(全连接网络)实际上权重是最多的。
在这里插入图片描述
全连接网络的权重是最多的。
对比CNN网络和DNN网络的权重数量:
在这里插入图片描述
对于卷积层:比如输入通道是128个,输出通道是64个,如果用5x5的卷积,权重数就是 2564188=204800,卷积层的输入输出只与通道数和卷积核的大小有关,全连接层和变换之后的数据大小有关,比如3阶张量经过一系列的卷积变换还剩下4096个元素,4096我们很少直接降成1维或者10维,而是先降成1024维,全连接层的权重为4096*1024=4194304,所以相比起来,卷积层的权重并不多,而全连接层的权重较多。全连接层是在网络的全部参数中占大头的。

CNN在做卷积的时候,它的卷积核是共享的。所以它的权重数量比较少,它并不是图像上的每一个像素要和下一层的featureMap建立连接,权重数量就少。处理视频的时候,每一帧就少一张图像,我们需要把一组图像做成一个集合,如果用全连接网络的话,使用到的权重的数量就是一个天文数字,极大可能难以处理。

RNN是用来专门处理带有序列的数据,也会使用权重共享来减少权重的数量。它把x1,x2,x3看成是一个序列,不仅考虑x1,x2之间的连接关系,还考虑x1,x2的时间上的先后顺序(x2依赖于x1,x3依赖于x2),下一天的天气状况部分依赖于前一天的天气状况,RNN主要处理这种具有序列连接的数据。
在这里插入图片描述
数据是有先后顺序进行连接的,也就是当天数据会依赖之前数据。
哪些数据具有序列:股市、金融数据、自然语言(我爱北京天安门)
在这里插入图片描述

2. CNN

在这里插入图片描述

3. RNN

3.1 RNN的结构分析

RNN是循环神经网络。RNN其实是对线性层的复用。

RNN Cell本质是一个线性层(linear),把一个维度映射到另一个维度(比如把输入的3维向量xt变成输出5维向量ht)。 这个线性层与普通的线性层的区别是这个线性层是共享的。
在这里插入图片描述
右侧的结构是对左侧结构的展开。h0是一种先验知识,如果我们知道输出的维度,我们可以选择将其每一个维度都设置为0。也可以使用CNN+Fc的网络去生成先验知识,实现图像到文本的转换。

RNN相当于一个形成层,所以我们可以简单的用代码进行描述:

linear = Linear()
h = 0
for x in X:
    h = linear(x,h)
相当于
h1 = linear(x1,h0)
h2 = linear(x2,h1)
h3 = linear(x3,h2)
......

RNN的具体计算过程:
在这里插入图片描述
在这里插入图片描述
输入xt先做线性变换,ht-1也是,xt的维度是input_size,ht-1的维度是hidden_size,输出ht的维度是hidden_size。我们需要先把xt的维度变成hidden_size,所以Wih应该是一个 hidden_sizeinput_size的矩阵,Wihxt得到一个 hidden_size的矩阵(就是维度为hidden_size的向量),bih是偏置。输入权重矩阵Whh是一个hidden_size*hidden_size的矩阵。

Whhht-1+bhh和Wihxt+bih都是维度为hidden_size的向量,然后两个向量相加,就把信息融合起来了,融合之后用tanh做激活,循环神经网络的激活函数用的是tanh(为什么呢?因为tanh的取值在-1到+1之间),算出结果得到这一层的隐藏层输出ht。

补充:Tanh的诞生比Sigmoid晚一些,sigmoid函数我们提到过有一个缺点就是输出不以0为中心,使得收敛变慢的问题。而Tanh则就是解决了这个问题。Tanh就是双曲正切函数。等于双曲余弦除双曲正弦。函数表达式和图像见下图。这个函数是一个奇函数。
在这里插入图片描述
完整的定义RNN:将RNN cell以循环的形式一个一个送进去,然后依次算出隐藏层的过程,我们称之为循环神经网络。

3.2 RNN的使用分析

构造RNN的方式:

  1. 自己写处理序列的循环
  2. 直接使用RNN

3.2.1 RNN_cell的实现

在这里插入图片描述
主要是确定输入的维度和隐层的维度。
在这里插入图片描述
如上图,RNN本质上还是一个线性层,要弄清楚纬度。代码如下:

# pytorch实现
cell = torch.nn.RNNcell(input_size=input_size,hidden_size=hidden_size)
hidden = cell(input,hidden) # h1 = cell(x1,h0)

举例使用RNN Cell
在这里插入图片描述
输入有三个特征,每个特征是4维的。隐藏是一个2维的向量。
在这里插入图片描述

  • batchSize表示批量大小
  • seqLen=3表示每一个样本都有x1,x2,x3这些特征
  • inputSize=4表示每一个特征都是4维的
  • hiddenSize=2表示每一个隐藏层是2维的
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    可以发现,RNN相比之前的网络,多了一个序列的维度。
import torch

batch_size = 1
seq_len = 3
input_size = 4
hidden_size = 2

cell = torch.nn.RNNCell(input_size=input_size,hidden_size=hidden_size)

# (seq,batch,features)
dataset = torch.randn(seq_len,batch_size,input_size)
hidden = torch.zeros(batch_size,hidden_size)

for idx,input in enumerate(dataset):
    print("="*20,idx,"="*20)
    print("Input size:",input.shape)

    hidden = cell(input,hidden)
    print("Output size:",hidden.shape)
    print(hidden)

在这里插入图片描述

3.2.2 RNN的实现

使用RNN:
在这里插入图片描述
直接使用torch.nn.RNN()需要知道input_size、hidden_size和num_layers(RNN有多少层,默认为1)。cell(inputs,hidden)中 inputs指包含整个输入序列(x1,x2,x3,…xN),hidden指h0。
在这里插入图片描述
用RNN不用自己写循环,它自动循环,所以输入的时候要把所有的序列都送进去,然后给定h0,然后我们就会得到所有的隐层输出以及最后一层的输出。
在这里插入图片描述
在这里插入图片描述
当RNN有多层,同样颜色的RNNCell是同一个,所以上图是有3个线性层(一个RNNCell是一个线性层)。这样就能解释为什么隐藏层h的维度需要numLayers参数,因为每一层都需要。

输出:
在这里插入图片描述
输出与输入的区别就在于input_size变成了hidden_size
在这里插入图片描述

import torch

input_size = 4
hidden_size = 2
seq_len = 3
batch_size = 1
num_layers = 1

cell = torch.nn.RNN(hidden_size=hidden_size,input_size=input_size,num_layers=num_layers)

#(seq_len,batchsize,inputsize)
inputs = torch.randn(seq_len,batch_size,input_size)
hidden = torch.zeros(num_layers,batch_size,hidden_size)

out,hidden = cell(inputs,hidden)

print("output size:",out.shape)
print("output:",out)
print("Hidden size:",hidden.shape)
print("hidden:",hidden)

输出:
在这里插入图片描述
初次之外,还有一个batch_first参数可以设置:
在这里插入图片描述
在这里插入图片描述

4. Example

4.1 Use RNN_cell

举例:seq到seq。训练一个模型:输入hello,输出ohlol。
在这里插入图片描述
RNN Cell 的输入是向量,第一步先把字符转成向量。
在这里插入图片描述
在NLP中,先根据字符构造一个词典(Dictionary),然后根据indeces转换成相应的one-hot向量。这里inputsize=4,因为输入有4个字符(e h l o)这相当于一个多分类问题,输出就是一个4维的向量,每一维代表是某一个字符的概率,接交叉熵就能输出概率了。

输入和输出所需要的结构:
在这里插入图片描述
在这里插入图片描述

4.1.1 准备数据

# 准备数据
idx2char = ['e','h','l','o']
x_data = [1,0,2,2,3] # hello
y_data = [3,1,2,3,2] # ohlol

one_hot_lookup = [[1,0,0,0],
                  [0,1,0,0],
                  [0,0,1,0],
                  [0,0,0,1]]  # ont_hot查询

x_one_hot = [one_hot_lookup[x] for x in x_data] # seq * input_Size
print(x_one_hot)

# (seqlen,batchsize,inputsize)
inputs = torch.tensor(x_one_hot).view(-1,batch_size,input_size)
# label (seqlen,1)
labels = torch.LongTensor(y_data).view(-1,1)
print(labels.shape)
print(labels)

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

4.1.2 准备模型

在这里插入图片描述

# 构造模型
class Model(torch.nn.Module):
    def __init__(self,batch_size,input_size,hidden_size):
        super(Model, self).__init__()
        self.batch_size = batch_size
        self.input_size = input_size
        self.hidden_size = hidden_size
        self.rnncell = torch.nn.RNNCell(input_size=input_size,hidden_size=hidden_size)

    def forward(self,input,hidden):
        hidden = self.rnncell(input,hidden)
        return hidden

    def init_hidden(self):
        return torch.zeros(self.batch_size,self.hidden_size)

model = Model(input_size=input_size,hidden_size=hidden_size,batch_size=batch_size)

4.1.3 准备损失函数和优化器

# 损失函数和优化器
creation = torch.nn.CrossEntropyLoss(reduction="mean")
optimizer = torch.optim.SGD(model.parameters(),lr=0.1)

4.1.4 进行训练

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

# 训练
for epoch in range(15):
    loss = 0
    optimizer.zero_grad()
    hidden = model.init_hidden()  # 计算h0
    print("Predicted string:",end="")   #end=’ ‘意思是末尾不换行,加空格。
    for input,label in zip(inputs,labels): # 依次取x1,x2,x3,x4,x5
        # inputs : (seqlen,batchsize,inputsize)
        hidden = model(input,hidden)
        loss += creation(hidden,label) # loss这里不用item,因为序列的loss需要构造计算图
        _,idx = hidden.max(dim=1)
        print(idx2char[idx.item()],end="")
    loss.backward()
    optimizer.step()
    print(",Epoch {}/15 loss={:.4f}".format(epoch+1,loss.item()))

输出结果:
在这里插入图片描述
补充知识:torch.Tensor和torch.tensor的区别

4.2 Use RNN

4.2.1 训练的改变

在这里插入图片描述

# 训练
for epoch in range(15):

    optimizer.zero_grad()
    outputs = model(inputs) # inputs是(seq,Batchsize,Inputsize) outputs是(seq,Batchsize,Hiddensize)
    loss = creation(outputs,labels) # labels是(seq,batchsize,1)
    loss.backward()
    optimizer.step()

    _,idx = outputs.max(dim=1)
    idx = idx.data.numpy()
    print("Predicted:",''.join([idx2char[x] for x in idx]),end='')
    print(",Epoch {}/15 loss={:.3f}".format(epoch+1,loss.item()))

4.2.2 模型的改变

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

# 构造模型
class Model(torch.nn.Module):
    def __init__(self,batch_size,input_size,hidden_size,num_layers = 1):
        super(Model, self).__init__()
        self.batch_size = batch_size
        self.input_size = input_size
        self.hidden_size = hidden_size
        self.num_layers = num_layers
        # input维度(batchsize,inputsize)
        # hidden维度(batchsize,hiddensize)
        self.rnn = torch.nn.RNN(input_size=self.input_size,hidden_size=self.hidden_size,num_layers=self.num_layers)

    # def forward(self,input,hidden):
    #     hidden = self.rnncell(input,hidden)
    #     return hidden
    def forward(self,input):
        hidden = torch.zeros(self.num_layers,self.batch_size,self.hidden_size)

        out,_ = self.rnn(input,hidden)
        return out.view(-1,self.hidden_size) # 转变维2维矩阵,seq*batchsize*1 -》((seq*batchsize),1)

    # def init_hidden(self):
    #     return torch.zeros(self.batch_size,self.hidden_size)

model = Model(input_size=input_size,hidden_size=hidden_size,batch_size=batch_size,num_layers=1)

4.2.3 数据的改变

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

# 准备数据
idx2char = ['e','h','l','o'] # 字典
x_data = [1,0,2,2,3] # hello
y_data = [3,1,2,3,2] # ohlol

one_hot_lookup = [[1,0,0,0],
                  [0,1,0,0],
                  [0,0,1,0],
                  [0,0,0,1]]  # ont_hot查询

x_one_hot = [one_hot_lookup[x] for x in x_data] # seq * input_Size
# print(x_one_hot)

# (seqlen,batchsize,inputsize)
inputs = torch.Tensor(x_one_hot).view(seq_len,batch_size,input_size)
# label (seqlen,1)
labels = torch.LongTensor(y_data)
# print(labels.shape)
# print(labels)

4.2.4 完整代码

import torch

# 使用RNN
input_size = 4
hidden_size = 4
batch_size = 1
num_layers = 1
seq_len = 5

# 准备数据
idx2char = ['e','h','l','o'] # 字典
x_data = [1,0,2,2,3] # hello
y_data = [3,1,2,3,2] # ohlol

one_hot_lookup = [[1,0,0,0],
                  [0,1,0,0],
                  [0,0,1,0],
                  [0,0,0,1]]  # ont_hot查询

x_one_hot = [one_hot_lookup[x] for x in x_data] # seq * input_Size
# print(x_one_hot)

# (seqlen,batchsize,inputsize)
inputs = torch.Tensor(x_one_hot).view(seq_len,batch_size,input_size)
# label (seqlen,1)
labels = torch.LongTensor(y_data)
# print(labels.shape)
# print(labels)

# 构造模型
class Model(torch.nn.Module):
    def __init__(self,batch_size,input_size,hidden_size,num_layers = 1):
        super(Model, self).__init__()
        self.batch_size = batch_size
        self.input_size = input_size
        self.hidden_size = hidden_size
        self.num_layers = num_layers
        # input维度(batchsize,inputsize)
        # hidden维度(batchsize,hiddensize)
        self.rnn = torch.nn.RNN(input_size=self.input_size,hidden_size=self.hidden_size,num_layers=self.num_layers)

    # def forward(self,input,hidden):
    #     hidden = self.rnncell(input,hidden)
    #     return hidden
    def forward(self,input):
        hidden = torch.zeros(self.num_layers,self.batch_size,self.hidden_size)

        out,_ = self.rnn(input,hidden)
        return out.view(-1,self.hidden_size) # 转变维2维矩阵,seq*batchsize*1 -》((seq*batchsize),1)

    # def init_hidden(self):
    #     return torch.zeros(self.batch_size,self.hidden_size)

model = Model(input_size=input_size,hidden_size=hidden_size,batch_size=batch_size,num_layers=1)

# 损失函数和优化器
creation = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(),lr=0.05)  # lr = 0.01学习的太慢

# 训练
for epoch in range(15):

    optimizer.zero_grad()
    outputs = model(inputs) # inputs是(seq,Batchsize,Inputsize) outputs是(seq,Batchsize,Hiddensize)
    loss = creation(outputs,labels) # labels是(seq,batchsize,1)
    loss.backward()
    optimizer.step()

    _,idx = outputs.max(dim=1)
    idx = idx.data.numpy()
    print("Predicted:",''.join([idx2char[x] for x in idx]),end='')
    print(",Epoch {}/15 loss={:.3f}".format(epoch+1,loss.item()))

输出结果:
在这里插入图片描述

4.3 独热向量

4.3.1 one-hot缺点

缺点:

  1. 维度太高
  2. 向量稀疏
  3. 硬编码,不是学习出来的
    在这里插入图片描述
    在这里插入图片描述
    embedding将高维离散的数据映射到加低维稠密的空间,就是常说的数据降维。在输入层和RNN层之中。

4.3.2 改进

Embedding
在这里插入图片描述
在这里插入图片描述
输入是2,就去表里查找2的位置,输出向量。
取第2行的向量,我们可以通过矩阵乘法来获得。
在这里插入图片描述
将上述4x5行的矩阵进行转置变成5x4,然后乘以一个[0 0 1 0]就可以取出第2行的向量。

然后我们求反向传播导数的话,我们就可以通过矩阵乘法的导数来进行计算。

我们之前的RNN cell输出的隐藏是和分类的数量是一致的, 如果不一致的话,我们可以再接一个线性层。
在这里插入图片描述
torch.nn.Embedding
在这里插入图片描述
第一个参数:独热向量是几维的。
输入是(seq,batchsize)输出是(seq,batchsize,embedding_dim)
torch.nn.Linear
在这里插入图片描述
torch.nn.CrossEntropyLoss
在这里插入图片描述
d1,d2…代表交叉熵可以加维度的,可以满足RNN的计算。

4.3.3 网络结构

网络结构
在这里插入图片描述
在这里插入图片描述
添加嵌入层。
在这里插入图片描述
将输入的张量转变为嵌入曾稠密的张量表示。
在这里插入图片描述
batch_first设为true的话,那么batchsize维度在前,seqlen维度在后。
在这里插入图片描述
线性层,将hiddensize变成numclass。
在这里插入图片描述
最后返回成二维矩阵的形式,方便loss的计算。

4.3.4 完整代码

import torch

# 使用RNN 有嵌入层和线性层
num_class = 4     # 4个类别
input_size = 4    # 输入维度是4
hidden_size = 8   # 隐层是8个维度
embedding_size = 10 # 嵌入到10维空间
batch_size = 1
num_layers = 2    # 两层的RNN
seq_len = 5       # 序列长度是5

# 准备数据
idx2char = ['e','h','l','o'] # 字典
x_data = [[1,0,2,2,3]] # hello  维度(batch,seqlen)
y_data = [3,1,2,3,2] # ohlol    维度 (batch*seqlen)

# (batchsize,seqlen)
inputs = torch.LongTensor(x_data)
# label (batchsize*seqlen)
labels = torch.LongTensor(y_data)
# print(labels.shape)
# print(labels)

# 构造模型
class Model(torch.nn.Module):
    def __init__(self):
        super(Model, self).__init__()
        self.emb = torch.nn.Embedding(input_size,embedding_size)
        self.rnn = torch.nn.RNN(input_size=embedding_size,hidden_size=hidden_size,num_layers=num_layers,batch_first=True)
        self.fc = torch.nn.Linear(hidden_size,num_class)

    def forward(self,x):
        hidden = torch.zeros(num_layers,x.size(0),hidden_size)
        x = self.emb(x) # (batch,seqlen,embeddingsize)
        x,_ = self.rnn(x,hidden)
        x = self.fc(x)
        return x.view(-1,num_class) # 转变维2维矩阵,seq*batchsize*numclass -》((seq*batchsize),numclass)


model = Model()

# 损失函数和优化器
creation = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(),lr=0.05)  # lr = 0.01学习的太慢

# 训练
for epoch in range(15):

    optimizer.zero_grad()
    outputs = model(inputs) # inputs是(seq,Batchsize,Inputsize) outputs是(seq,Batchsize,Hiddensize)
    loss = creation(outputs,labels) # labels是(seq,batchsize,1)
    loss.backward()
    optimizer.step()

    _,idx = outputs.max(dim=1)
    idx = idx.data.numpy()
    print("Predicted:",''.join([idx2char[x] for x in idx]),end='')
    print(",Epoch {}/15 loss={:.3f}".format(epoch+1,loss.item()))

输出结果
在这里插入图片描述

5. Exercise

5.1 Use LSTM

在这里插入图片描述
torch.nn.LSTM()
在这里插入图片描述
在这里插入图片描述
多了上面的一条通道,实现了梯度传播的通道,能够避免梯度消失问题。

输入和输出
在这里插入图片描述
相比RNN,LSTM性能好,但是时间复杂度肯定变高。

5.2 Use GRU

RNN和LSTM的折中方案:GRU
在这里插入图片描述
在这里插入图片描述
理解序列data的维度概念,理解循环过程权重共享的机制。
课后练习
对比RNN
1.LSTM实现之前的模型
2.GRU实现之前的模型

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值