Pytorch深度学习实践(b站刘二大人)P12讲 (RNN循环神经网络基础篇)

RNN循环神经网络:对线性层的复用

1.DNN

Dense网络:稠密网络,有很多线性层对输入数据进行空间上的变换,又叫DNN
输入x1,x2…xn是数据样本的不同特征
Dense连接就是指全连接

比如预测天天气,就需要知道之前几天的数据,每一天的数据都包含若个特征,需要若干天的数据作为输入

假设现在取前3天,每一天有3个特征

把x1,x2,x3拼成有9个维度的长向量,然后去训练最后一天是否有雨

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


为什么卷积神经网络的权重比较少呢?
因为使用了权重共享的概念,做卷积时,整个图像的卷积核是共享的,并不是图像上的每一个像素要和下一层的featureMap建立连接,权重数量就少,处理视频的时候,每一帧就少一张图像,我们需要把一组图像做成一个集合,如果用全连接网络的话,使用到的权重的数量就是一个天文数字,难以处理


RNN专门用来处理带有序列模式的数据,也使用权重共享减少需要训练的权重的数量
我们把x1,x2,x3,xn看成是一个序列,不仅考虑x1,x2之间的连接关系,还考虑x1,x2的时间上的先后顺序
x2依赖于x1,x3依赖于x2,下一天的天气状况部分依赖于前一天的天气状况,RNN主要处理这种具有序列连接的
天气,股市,金融,自然语言处理都是序列数据

2.RNN Cell


RNN Cell本质是一个线性层(linear),把一个维度映射到另一个维度(比如把输入的3维向量xt变成输出5维向量ht)

在这里插入图片描述

这个线性层与普通的线性层的区别是这个线性层是共享的 

在这里插入图片描述

展开就是下图(其中所有的RNN cell是同一个线性层,因为是展开的嘛),h0是先验值,没有就设置成0向量

RNN过程:h0和x1经过某种运算将他们拼接在一起,即:分别做线性变换,然后求和,生成h1。然后把h1,作为输出送到下一次RNN cell计算中,这次输入变成x2,x2和h1合在一起运算,生成h2… 

具体的计算过程:

输入xt先做线性变换,h t-1也是,xt的维度是input_size,h t-1的维度是hidden_size,输出ht的维度是hidden_size,我们需要先把xt的维度变成hidden_size,所以Wih应该是一个 hidden_size*input_size的矩阵,Wih * xt得到一个 hidden_size*1的矩阵(就是维度为hidden_size的向量),bih是偏置。输入权重矩阵Whh是一个hidden_size* hidden_size的矩阵。
whhHt-1+bhh和WihXt+bih都是维度为hidden_size的向量,两个向量相加,就把信息融合起来了,融合之后用tanh做激活,循环神经网络的激活函数用的是tanh,因为tanh的取值在-1到+1之间,算出结果得到隐藏层输出ht

 

把RNN Cell以循环的方式把序列(x1,x2,…)一个一个送进去,然后依次算出隐藏层(h1,h2…)的过程,每一次算出来的h会作为下一个RNN Cell的输入,这就叫循环神经网络 

3. PyTorch里面构造RNN的两种方式

①自己构建Cell
需要设定输入的值input_size,和隐层的值hidden_size,就能确定权重W的维度和偏置b的维度

cell = torch.nn.RNNCell(input_size=input_size, hidden_size=hidden_size)
#设定参数,输入维度和隐层维度
hidden = cell(input, hidden)
#实例化Cell之后,我们需要给定当前的输入input以及当前的hidden,所以需要用循环来处理

比如

h1=Cell(x1,h0)

 在这里插入图片描述

 batchSize表示批量
seqLen=3表示每一个样本都有x1,x2,x3这些特征
inputSize=4表示每一个特征都是4维的
hoddenSize=2表示每一个隐藏层是2维

 代码展示:

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)#初始化,构建RNNCell
dataset=torch.randn(seq_len,batch_size,input_size)#设置dataset的维度
hidden=torch.zeros(batch_size,hidden_size)#隐层的维度:batch_size*hidden_size,先把h0置为0向量
for idx,input in enumerate(dataset):
    print('='*20,idx,'='*20)
    print('Input size:',input.shape)
    hidden=Cell(input,hidden)
    print('Outputs size:',hidden.shape)
    print(hidden)

 注释:

1. torch.randn(*sizes, out=None) → Tensor      torch.randn()返回一个包含了从标准正态分布中抽取的一组随机数的张量   size:张量的形状,out:结果张量。

2.功能torch.zeros()返回一个由标量值0填充的张量,其形状由变量参数size定义。 

结果: 

==================== 0 ====================
Input size: torch.Size([1, 4])
Outputs size: torch.Size([1, 2])
tensor([[0.8677, 0.8320]], grad_fn=<TanhBackward>)
==================== 1 ====================
Input size: torch.Size([1, 4])
Outputs size: torch.Size([1, 2])
tensor([[-0.9137, -0.5884]], grad_fn=<TanhBackward>)
==================== 2 ====================
Input size: torch.Size([1, 4])
Outputs size: torch.Size([1, 2])
tensor([[0.9840, 0.9235]], grad_fn=<TanhBackward>)

②直接使用RNN

cell = torch.nn.RNN(input_size=input_size, hidden_size=hidden_size,
num_layers=num_layers)#num_layers:RNN的层数

#num_layers:RNN的层数,如果RNN有多层,每一层都会有输出

out, hidden = cell(inputs, hidden)

inputs:所有的x,x1,x2,x3,......xn
用RNN不用自己写循环,它自动循环,所以输入的时候要把所有的序列都送进去,然后给定h0,然后我们就会得到所有的隐层输出以及最后一层的输出 

在这里插入图片描述

当RNN有多层,同样颜色的RNN Cell是同一个,所以下图是有3个线性层(一个RNNCell是一个线性层)在这里插入图片描述 代码:

import torch
batch_size=1
seq_len=3
input_size=4
hidden_size=2
num_layers=1
cell=torch.nn.RNN(input_size=input_size,hidden_size=hidden_size,num_layers=num_layers)
#构造RNN时指明输入维度,隐层维度以及RNN的层数
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)

结果:

Output size: torch.Size([3, 1, 2])
Output: tensor([[[-0.9123,  0.9218]],

        [[ 0.9394, -0.2471]],

        [[-0.9064,  0.5193]]], grad_fn=<StackBackward>)
Hidden size: torch.Size([1, 1, 2])
Hidden tensor([[[-0.9064,  0.5193]]], grad_fn=<StackBackward>)

如果初始化RNN时,把batch_first设置成了TRUE,那么inputs的参数batch_size和seq_len需要调换一下位置在这里插入图片描述

4.利用RNN Cell训练hello转换到ohlol在这里插入图片描述

 把字符转成向量在这里插入图片描述

 inputsize=4,因为输入有4个字符(e h l o)
这相当于一个多分类问题,输出就是一个4维的向量,每一维代表是某一个字符的概率,接交叉熵就能输出概率了 代码:

#使用RNNCell
import torch
#参数
input_size = 4
hidden_size = 4
batch_size = 1
#准备数据
idx2char = ['e', 'h', 'l', 'o'] #为了后面可以根据索引把字母取出来
x_data = [1, 0, 2, 3, 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]]
x_one_hot = [one_hot_lookup[x] for x in x_data]  # (seqLen, inputSize)
#reshape the inputs to (seqlen,batchSize,inputSize)
inputs = torch.Tensor(x_one_hot).view(-1, batch_size, input_size)
#reshape the labels to (seqlen,1)
labels = torch.LongTensor(y_data).view(-1, 1)
# torch.Tensor默认是torch.FloatTensor是32位浮点类型数据,torch.LongTensor是64位整型
print(inputs.shape, labels.shape)

#设计模型
class Model(torch.nn.Module):
    def __init__(self, input_size, hidden_size, batch_size):
        super(Model, self).__init__()
        #对参数进行初始化
        self.batch_size = batch_size #仅构造h0时需要batch_size
        self.input_size = input_size
        self.hidden_size = hidden_size
        self.rnncell = torch.nn.RNNCell(input_size=self.input_size, hidden_size=self.hidden_size)

    def forward(self, inputs, hidden):
        hidden = self.rnncell(inputs, hidden)  # 输入和隐层转换为下一个隐层 ht = rnncell(xt,ht-1)
        # shape of inputs:(batchSize, inputSize),shape of hidden:(batchSize, hiddenSize),
        return hidden

    def init_hidden(self):
        return torch.zeros(self.batch_size, self.hidden_size)  # 提供初始的隐层,生成全0的h0


net = Model(input_size, hidden_size, batch_size)
#损失函数和优化器
criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(net.parameters(), lr=0.1)#使用Adam优化器,改进的随机梯度下降优化器进行优化

for epoch in range(15):
    loss = 0
    optimizer.zero_grad() #优化器梯度归0
    hidden = net.init_hidden() #每一轮的第一步先初始化hidden,即先计算h0
    print('Predicted string:', end='')
    #shape of inputs:(seqlen序列长度,batchSize,inputSize)  shape of input:(batchSize,inputSize)
    #shape of labeis:(seqsize序列长度,1)  shape of labei:(1)
    for input, label in zip(inputs, labels):
        hidden = net(input, hidden)
        # 注意交叉熵在计算loss的时候维度关系,这里的hidden是([1, 4]), label是 ([1])
        loss += criterion(hidden, label) #不用loss.item,所有的和才是最终的损失
        _, idx = hidden.max(dim=1)  #hidden.max()函数找出hidden里的最大值  _, idx最大值的下标
        print(idx2char[idx.item()], end='')

    loss.backward()
    optimizer.step()
    print(', Epoch [%d/15] loss=%.4f' % (epoch + 1, loss.item()))

运行结果: 

 5.利用RNN 训练hello转换到ohlol

代码;

#使用RNN
import torch

input_size=4
hidden_size=4
num_layers=1
batch_size=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]] #分别对应0,1,2,3项
x_one_hot=[one_hot_lookup[x] for x in x_data] # 组成序列张量
print('x_one_hot:',x_one_hot)

# 构造输入序列和标签
inputs=torch.Tensor(x_one_hot).view(seq_len,batch_size,input_size)
labels=torch.LongTensor(y_data)  #labels维度是: (seqLen * batch_size ,1)

# design model
class Model(torch.nn.Module):
    def __init__(self,input_size,hidden_size,batch_size,num_layers=1):
        super(Model, self).__init__()
        self.num_layers=num_layers
        self.batch_size=batch_size
        self.input_size=input_size
        self.hidden_size=hidden_size
        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=torch.zeros(self.num_layers,self.batch_size,self.hidden_size)
        out, _=self.rnn(input,hidden)
        # 为了能和labels做交叉熵,需要reshape一下:(seqlen*batchsize, hidden_size),即二维向量,变成一个矩阵
        return out.view(-1,self.hidden_size)

net=Model(input_size,hidden_size,batch_size,num_layers)

# loss and optimizer
criterion=torch.nn.CrossEntropyLoss()
optimizer=torch.optim.Adam(net.parameters(), lr=0.05)

# train cycle
for epoch in range(20):
    optimizer.zero_grad()
    #inputs维度是: (seqLen, batch_size, input_size) labels维度是: (seqLen * batch_size * 1)
    #outputs维度是: (seqLen, batch_size, hidden_size)
    outputs=net(inputs)
    loss=criterion(outputs,labels)
    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 [%d/20] loss=%.3f' % (epoch+1, loss.item()))

运行结果:

6、Embedding(嵌入层)编码方式

       独热编码向量维度过高;
       独热编码向量稀疏,每个向量是一个为1其余为0;
       独热编码是硬编码,编码情况与数据特征无关;
       采用一种低维度的、稠密的、可学习数据的编码方式:Embedding。

 Embedding把一个高维的稀疏的样本映射到一个稠密的低维的空间里面,也就是数据的降维。

输入4维,嵌入层5维(嵌入层即可以高维也可以低维)

4维转换为5维,构建这样一个矩阵

 在里边可以做一个查询

输入为2,就表示是第二个字符的索引(索引从0开始),找到第2行,把这个向量输出,这就叫embedding

反向传播时导数怎么求?

输入层必须是长整型张量,输出是(seqlen,4) 

 embedding初始化:

num_bedding: input 独热向量的维度

 num_bedding() 和 embedding_dim()构成矩阵的宽度和高度

输入层必须是长整型张量,输出是(input shape,embedding_shape)

 代码:

#Embedding编码方式
import torch

input_size = 4
num_class = 4
hidden_size = 8
embedding_size = 10
batch_size = 1
num_layers = 2
seq_len = 5

idx2char_1 = ['e', 'h', 'l', 'o']
idx2char_2 = ['h', 'l', 'o']

x_data = [[1, 0, 2, 2, 3]]
y_data = [3, 1, 2, 2, 3]

# inputs 维度为(batchsize,seqLen)
inputs = torch.LongTensor(x_data)
# labels 维度为(batchsize*seqLen)
labels = torch.LongTensor(y_data)


class Model(torch.nn.Module):
    def __init__(self):
        super(Model, self).__init__()
        #告诉input大小和 embedding大小 ,构成input_size * embedding_size 的矩阵
        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)
        # batch_first=True,input of RNN:(batchsize,seqlen,embeddingsize) output of RNN:(batchsize,seqlen,hiddensize)
        self.fc = torch.nn.Linear(hidden_size, num_class) #从hiddensize 到 类别数量的 变换

    def forward(self, x):
        hidden = torch.zeros(num_layers, x.size(0), hidden_size)
        x = self.emb(x)  # 进行embedding处理,把输入的长整型张量转变成嵌入层的稠密型张量
        x, _ = self.rnn(x, hidden)
        x = self.fc(x)
        return x.view(-1, num_class) #为了使用交叉熵,变成一个矩阵(batchsize * seqlen,numclass)


net = Model()

criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(net.parameters(), lr=0.05)

for epoch in range(15):
    optimizer.zero_grad()
    outputs = net(inputs)

    loss = criterion(outputs, labels)
    loss.backward()
    optimizer.step()

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

运行结果:

 

参考链接

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值