介绍RNN的两种实现方式和lstm的代码

前言

经过了一天的学习有了自己的一点理解, RNN其实没有什么神秘的,就可以简单的看成一个分类器,对每个时间t的输出进行分类,与线性层不同的一点就是输入是打断骨头连着筋的,不是独立的.

正文

简介

许多变量是有这种序列关系的, 比如语音有上下文关系,我们每天说的话都有这种时间上相互关联的特性。
先看这个东西,叫做RNN Cell。 本质上是个线性层,就像可以把3维的变为5维的

在这里插入图片描述
许多时候RNN可以画成这样:
在这里插入图片描述
更直观的比如下图:
在这里插入图片描述
这里要注意, 这些cell是权重共享的。然后hidden是依次传递的。 注意这个 h 0 h_0 h0, 它可以是初始化的0, 也可以接收之前网络的输入, 假如做图像转文本, 那么完全可以把图像经过CNN和Fc之后作为这里的 h 0 h_0 h0 , 还有seq2seq, 也可以接上一个LSTM的hidden输出作为 h 0 h_0 h0

我们再看一个输入到输出的过程, 如下图:
在这里插入图片描述

输入与hidden都对应着一个权重, x t x_t xt对应的是 h i d d e n ∗ i n p u t hidden*input hiddeninput, h t − 1 h_{t-1} ht1对应的是 h i d d e n ∗ h i d d e n hidden*hidden hiddenhidden
然后RNN一般习惯激活函数是 t a n tan tan. 当然实际算的时候权重是拼接在一起的, 就可以用向量乘法简化计算.
在这里插入图片描述

  • 构建RNN有两种方法, 第一种是构建单个RNN Cell , 另一种是直接构建.
第一种实现方式:
cell = torch.nn.RNNCell(input_size=input_size, hidden_size=hidden_size)

hidden = cell(input, hidden)
  • RNN Cell的维度是这样的:
  1. input_size of shape(batch, input_size)
  2. hiddden0_size (batch, hidden_size)
  3. hidden of shape(batch, hidden_size)

注意在构建数据集时seq放在第一位, 是 ( s e q , b a t c h , i n p u t ) (seq, batch, input) (seq,batch,input)

我们构建一个最简单的例程:

import torch
batch_size = 64
seq = 5
hidden_size = 10
input_size = 4

cell = torch.nn.RNNCell(input_size=input_size, hidden_size=hidden_size) # 这是构建一个cell, 只需要input与hidden

data = torch.randn(seq, batch_size, input_size) # 每次取一个seq, 也就是batch, input作为输入

hidden = torch.zeros(batch_size, hidden_size)
for index, data in enumerate(data):
    hidden = cell(data, hidden)
    print(hidden.shape)

第二种实现方式:

感觉用的最多的还是直接生成, 不用自己一层一层的构建。

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

out, hidden = cell(inputs, hidden)   

这里第一个张量out, 是所有seq的输出, hidden是最后的输出, 如下图:
在这里插入图片描述

注意这里的维度, 还有注意RNN整个是不需要seq这个参数的。

  • 输入维度是(seqSize, batch_size, input_size)
  • h0维度是(numLayers, batch_size, hidden_size), 因为只在x1起作用
  • Output维度(seq, batch, hidden)
  • hidden维度(numLayers, batch, hidden)

对于num_layer的理解:
在这里插入图片描述
可以看到RNN不仅向前,也向上传播。

写个很简单的代码:

import torch
batch_size = 64
seq = 5
hidden_size = 10
input_size = 4
num_layers = 1

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

data = torch.randn(seq, batch_size, input_size)

hidden = torch.zeros(num_layers, batch_size, hidden_size)
out, hidden = RNN(data, hidden)
RNN的batch_first

一般整个RNN的输入是( s e q , b a t c h , i n p u t seq, batch, input seq,batch,input), 如果batch_first设置True, 那么输入就变成( b a t c h , s e q , i n p u t batch, seq, input batch,seq,input), 同样out也会变为batch打头.

两个例程
第一种方法实现
import torch
idx2char = ['e', 'h', 'l', 'o']
x_data = [1, 0, 2, 2, 3] # 对应hello
y_data = [3, 1, 2, 3, 2]

batch_size = 1
input_size = 4
hidden_size = 4

x_one_hot = torch.zeros(5, 4).scatter_(1, torch.tensor(x_data).unsqueeze(1), 1)


inputs = x_one_hot.view(-1, batch_size, input_size) # 变为seq, batch,input
# 这里的seq是5
labels = torch.LongTensor(y_data).view(-1, 1) # 变为seq, 1


class Model(torch.nn.Module):
    def __init__(self, input_size, hidden_size, batch_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=self.input_size, hidden_size=self.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)
net = Model(input_size, hidden_size, batch_size)

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

for epoch in range(15):
    loss = 0
    optimizer.zero_grad()
    hidden = net.init_hidden()
    print('Predicted string:', end='')
    pre = []
    for input, label in zip(inputs, labels):  # 这样的话label就是1维度, 因为labels是seq,1
        hidden = net(input, hidden)
        loss += criterion(hidden, label) # 是把每个cell输出加起来
        _, idx = hidden.max(dim=1) # idx是索引值
        pre.append(idx2char[idx])

    print(pre)
    loss.backward()
    optimizer.step()
    print('Epoch:', epoch+1, 'loss:', loss.item())
第二种方法实现
import torch
idx2char = ['e', 'h', 'l', 'o']

input_size = 4
hidden_size = 4
batch_size = 1
num_layers = 1
seq_len = 5

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

x_one_hot = torch.zeros(5, 4).scatter_(1, torch.tensor(y_data).unsqueeze(1), 1)
# 这里就是按x的索引构建的one-hot,只是我现在忘了具体的用法了······

inputs = x_one_hot.view(seq_len, batch_size, input_size)
labels = torch.LongTensor(y_data)  # 这个时候的维度是5, 也就是seq*batch,但是是单维度的,也就是单个5


# labels = torch.tensor(y_data).view(-1, 1) # 但是这样写就不行,不知道为什么
# 后来知道为什么了, 因为输入labels维度应该为1维,且精度不能是Double,必须换成long
# 维度类似这样(batch_size,) 而不是(batch_size, 1)

class Model(torch.nn.Module):
    def __init__(self, input_size, hidden_size, batch_size, num_layers=1):
        super().__init__()
        self.batch_size = batch_size # 这里的batch是为了构造h0

        self.hidden_size = hidden_size
        self.input_size = input_size
        self.num_layers = num_layers
        self.rnn = torch.nn.RNN(input_size=self.input_size,
                                hidden_size=self.hidden_size,
                                num_layers=num_layers)

    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)  # 这里的输出变为了seq*batch, hidden
    # 为什么要这样做呢,是为了交叉熵, 原来的label应该是seq,batch,1, 我们也将其变成seq*batch, 1
    # 因为这里是一次性的RNN,所以才会让out与label的第一维相等

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

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

for epoch in range(15):
    optimizer.zero_grad()
    outputs = net(inputs) # 这里的输入就是seq, batch, input
    print(outputs.shape, 'liiigasgfsgsagasdg')
    print(labels.shape)
    loss = criterion(outputs, labels)  # 对应的label是seq, batch, 1

    loss.backward()
    optimizer.step()

    _, idx = outputs.max(dim=1)
    idx = idx.data.numpy()
    print('Predicted:', ''.join([idx2char[x] for x in idx]))
embedding

之前的输入用的是one-hot, 但是假如有几万维那岂不是很可怕,而且每个之间关系稀疏, 关联小, embedding就是一种降维操作.
在这里插入图片描述

  • 我们现在用一下。 之前的时候是one-hot, 就是输入的索引先变为one-hot,然后再处理, 那么假如我们不用one-hot, 而是embedding。 当然embedding也可以升维, 假设我们从4维原来的 one-hot升到5维。
    在这里插入图片描述
    那么我们可以怎么做呢? 可以构建一个( 4 ∗ 5 4*5 45)的矩阵, 然后做查询, 比如索引是2, 那么就输出对应的序列. 如下图:

在这里插入图片描述

用了embedding之后的示意图如下:
在这里插入图片描述

对embedding, Linear, crossentropy参数的理解
1. torch.nn.Embedding

对于 embedding的参数我们只需要注意两个,一个是num_embeddings, 就是你input的one-hot向量是几维的, embedding_dim, 就是降到几维
在这里插入图片描述
这里要注意, 输入是长整型的值, 也就是前面加上torch.LongTensor, 这里要注意有时候会报错,可以先用.numpy()转为numpy然后再用LongTensor. 假如我们的输入是( s e q , b a t c h seq, batch seq,batch), 也就是batch里面是索引,(这是我自己猜的), 然后它会把输入加上embedding的维度, 也就是它会加维度
在这里插入图片描述

我用一个小例子说明一下:

import torch

x = torch.LongTensor(torch.rand(3, 4, 12).numpy()) # seq, batch, input
embedding = torch.nn.Embedding(12, 6)
x_ = embedding(x)
print(x_.shape)

>>
torch.Size([3, 4, 12, 6])
2. torch.nn.Linear

之前理解的线性层都是 b a t c h _ s i z e , i n p u t _ s i z e batch\_size, input\_size batch_size,input_size作为输入, 然后类似的输出, 但其实它的输入相当宽泛,只要开头第一维度一致, 它其实只是改变了结尾的维度, 假如我们的data是 b a t c h , s e q , i n p u t batch, seq, input batch,seq,input, 那么也可以使用Linear, 其作用只是变了最后的维度而已
在这里插入图片描述

再举个小例子:

import torch

x = torch.rand(3, 4, 8)
Linear = torch.nn.Linear(8, 12)
y = Linear(x)
print(y.shape)

>>
torch.Size([3, 4, 12])

这样就可以和RNN很好的配合了, 调整下batch_first参数

3. torch.nn.CrossEntropyLoss()

原来的时候交叉熵是这样的, 我们预测一个y, 其维度是 b a t c h , c l a s s batch, class batch,class, 类比Mnist分类, 线性层对应的是10, 一个道理, 然后这里的target就是label, 对应的就是一个数, Mnist的类别是通过找最大值索引得到的, 而label直接就是数, 比如4, 2 这种. 对于RNN同样输出是( s e q , b a t c h , h i d d e n seq, batch, hidden seq,batch,hidden), 为什么选这个? 因为RNN是对全体进行分类啊, 所以用全部的那个序列, 而不只是最后的hidden.
然后我们让 s e q ∗ b a t c h seq*batch seqbatch作为一个维度, hidden是一个维度, 那么对应的label就是 s e q ∗ b a t c h seq*batch seqbatch, 没有1
在这里插入图片描述

embedding的例程
import torch

idx2char = ['e', 'h', 'l', 'o']

x_data = [[1, 0, 2, 2, 3]] # batch, seq
y_data = [3, 1, 2, 3, 2] # batch*seq

inputs = torch.LongTensor(x_data)
labels = torch.LongTensor(y_data)

num_class = 4
input_size = 4 # 这是打算的转变为的one-hot维度
hidden_size = 8
embedding_size = 10
num_layers = 2
batch_size = 1
seq_len = 5


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) #  (num, batch, hidden)
        x = self.emb(x)  # (batch, seqLen, embeddingSize), 这里并没有转化为one-hot, 而是直接用索引变了embedding
        x, _ = self.rnn(x, hidden)
        x = self.fc(x)

        return x.view(-1, num_class) # 这里返回的就是seq*batch, 和开头的labels正好对应


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: ', ''.join([idx2char[x] for x in idx]), end='')

    print(', Epoch [%d/15] loss = %.3f' % (epoch + 1, loss.item()))
LSTM的应用

其实就是h增加了一个c, 然后注意输入是元组的形式.
在这里插入图片描述

import torch

idx2char = ['e', 'h', 'l', 'o']

x_data = [[1, 0, 2, 2, 3]] # batch, seq
y_data = [3, 1, 2, 3, 2] # batch*seq

inputs = torch.LongTensor(x_data)
labels = torch.LongTensor(y_data)

num_class = 4
input_size = 4 # 这是打算的转变为的one-hot维度
hidden_size = 8
embedding_size = 10
num_layers = 2
batch_size = 1
seq_len = 5


class Model(torch.nn.Module):
    def __init__(self):
        super(Model, self).__init__()
        self.emb = torch.nn.Embedding(input_size, embedding_size)
        self.lstm = torch.nn.LSTM(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) #  (num, batch, hidden)
        c = torch.zeros(num_layers, x.size(0), hidden_size)
        x = self.emb(x)  # (batch, seqLen, embeddingSize), 这里并没有转化为one-hot, 而是直接用索引变了embedding
        x, (_, _) = self.lstm(x, (hidden,c))
        x = self.fc(x)

        return x.view(-1, num_class) # 这里返回的就是seq*batch, 和开头的labels正好对应


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: ', ''.join([idx2char[x] for x in idx]), end='')

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


  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

live_for_myself

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值