Pytorch:十二、RNN(基础)


实际上就是对线性层的复用?

卷积层运算复杂,但是权重不多(共享权重);而FC层则是反一下,如果同一层的个数太多,那么其权重的数量可能会多到难以处理。因此,RNN就是专门用来处理带有序列模式的数据,同时也要结合权重共享的思想来减少权重数量;

  • 使用思路:以天气数据为例,不仅要考虑x1,x2之间的连接关系,还要考虑其时间上的先后关系(即x1/2的数据依赖于x2/3)——RNN主要处理带有序列连接的数据;

除了天气等一系列经典的时间顺序的数据以外,自然语言也是一种有着序列关系的数据;

  • RNN Cell:xt是指在序列中t时刻相应的数据,它可以把3维向量变为5维的向量。不难看出其本质为一个线性层

和普通的线性层相比,这里的RNN Cell是可以共享的。将上面的图像展开后就是下面这样:

和线性层的区别在于:由于每一项的数据都和前一项相关,所以送进RNN Cell的数据不止其本身的数据,还要有上一项的数据(也就是图中的红色箭头。h0可以通过CNN+Fc来生成,如果没有的话就可以把他设成一个对应维度的0向量)。注意,这里所有的RNN Cell是同一个东西

上面的流程图用公式表达出来就是:

  • 具体计算过程

先将输入做一次线性变化,将其维度(input_size)转为隐藏层的维度(hidden_size)——W是一个hidden_size×input_size的矩阵;而上一个隐藏层的数据也作一次类型操作(这里权重矩阵就是hidden_size×hidden_size了);然后将他们俩相加就完成了融合,接着要做一次激活,得到的结果就是ht了;

#要设定的参数主要就是两个:输入维度和隐层的维度
cell = torch.nn.RNNCell(input_size=input_size, hidden_size=hidden_size)
#调用方法
hidden = cell(input, hidden)

先假设我们的参数是下面这样的:

  • 𝑏𝑎𝑡𝑐ℎ𝑆𝑖𝑧𝑒 = 1 (N)
  • 𝑠𝑒𝑞𝐿𝑒𝑛 = 3 (有多少个x)
  • 𝑖𝑛𝑝𝑢𝑡𝑆𝑖𝑧𝑒 = 4 (每个x的维度)
  • ℎ𝑖𝑑𝑑𝑒𝑛𝑆𝑖𝑧𝑒 = 2
    那么RNNCell的输入以及输出就是:
  • input.shape = (batchSize,inputSize)
  • output.shape = (batchSize,outputSize)
    整个序列的维度就是:
  • dataset.shape = (seqLen,batchSize,inputSize)

注意这里seqLen放第一位,这是为了在循环的时候,每次拿出当前循环时刻t的张量,也就是后两项参数,显得自然一些;

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, faetures)
dataset = torch.randn(seq_len, batch_size, input_size)
#构造h0
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('outputs size:',hidden.shape)
    print(hidden)
运行结果

也可以直接用RNN来进行编写

#就是多了个指明层数的参数num_layers
cell = torch.nn.RNN(input_size=input_size, hidden_size=hidden_size, num_layers = num_layers)
#输出的out是指h1,...,hN,而hidden则是特指最后一个hN
out, hidden = cell(inputs, hidden)
#这样一来,就不用自己写循环了

要确认的数多了一个numlayer:
• 𝑏𝑎𝑡𝑐ℎ𝑆𝑖𝑧𝑒
• 𝑠𝑒𝑞𝐿𝑒𝑛
• 𝑖𝑛𝑝𝑢𝑡𝑆𝑖𝑧𝑒, ℎ𝑖𝑑𝑑𝑒𝑛𝑆𝑖𝑧𝑒,
• 𝑛𝑢𝑚𝐿𝑎𝑦𝑒𝑟s
输入以及h_0的维度要求:

  • input.shape = (seqLen, batchSize, inputSize)
  • h_0.shape = (numLayers, batchSize, hiddenSize)
    输出以及h_n的维度要求:
  • output.shape = (seqLen, batchSize, hiddenSize)
  • h_n.shape = (numLayers, batchSize, hiddenSize)

numLayers的结构:

每个xi结合多个Hij后得到输出的hi;这里同样颜色的RNN Cell全都是一样的;

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)

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)
输出结果

RNN的其他参数:

  1. batch_first:若设为True,那么batchSize和seqLen两个参数的位置就会互换,输入时要把batch_size作为第一个参数;
  • 例子:把模型训练成一种序列到序列的转换模型,比如将"hello"转化为"ohlol”。对应的流程图如下所示:

第一步,将输入的字符向量化,因为RNN Cell的输入得是向量:

一般都是根据字符来构造词典,并分配索引,再通过独热向量来进行查询字符。宽度就是词的种类数量;

此时,InputSize就是4(矩阵的宽)

这样一来,就可以化为一个多分类问题,也就是把输入经过RNN来分类到对应的输出上去。再把这个变成一个分布,就可以拿去训练了;

后边接的部分就是一个交叉熵损失

用RNN Cell就是这样的代码:


import torch
input_size = 4
hidden_size = 4
batch_size = 1

# 准备数据
idx2char = ['e', 'h', 'l', 'o']
x_data = [1, 0, 2, 2, 3]
y_data = [3, 1, 2, 3, 2]
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]
inputs = torch.Tensor(x_one_hot).view(-1, batch_size, input_size)
labels = torch.LongTensor(y_data).view(-1, 1)

# 做模型,用RNN Cell
class Model(torch.nn.Module):
    def __init__(self, input_size, hidden_size, batch_size):
        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.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

    # 初始化h0,只有这里需要batch_size
    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)

# 训练,用RNN cell
for epoch in range(15):
    #先进行归0
    loss = 0
    optimizer.zero_grad()
    hidden = net.init_hidden()
    print('Predicted string:', end='')
    #inputs是seq * batchsize * input_size 而 input则是batchsize * inputSize
    for input, label in zip(inputs, labels): #inputs = seq * batchsize * input_size\
        hidden = net(input, hidden)
        loss += criterion(hidden,label) #用的都是同一个cell,所以损失要叠加
        _, idx = hidden.max(dim=1)
        print(idx2char[idx.item()], end='')
    loss.backward()
    optimizer.step()
    print(', Epoch[%d/15] loss=%.4f' % (epoch+1, loss.item()))

如果用RNN就是下面的代码:

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

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 #用于构造隐层的h0,如果放外面构造的话这一句不用也可以,但是一般都是放里面构造的
        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)
        return out.view(-1, self.hidden_size)

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

#数据
idx2char = ['e', 'h', 'l', 'o']
x_data = [1, 0, 2, 2, 3]
y_data = [3, 1, 2, 3, 2]
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]
inputs = torch.Tensor(x_one_hot).view(seq_len, batch_size, input_size)
labels = torch.LongTensor(y_data)

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

#训练,用RNN
for epoch in range(15):
    optimizer.zero_grad()
    outputs = net(inputs)
    loss = criterion(outputs, labels)
    loss.backward()
    optimizer.step()

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

独热向量的缺点:

  • 维度太高;
  • 向量过于稀疏;
  • 是硬编码的,即每个词对应的向量是早就编码好的;

目标就是要变成:

  • 低维;
  • 稠密;
  • 学习的;
    ——>常用的方式叫嵌入层Embeding:把高维,稀疏的样本映射到低维,稠密的空间当中去(即降维,其实可以高低互转,看情况);
降维示意图

通过嵌入层,就可以直接学习一个单词,而不是一个个字母;

这样一来,把网络变成下面这样的结构:

经过Embed把独热向量变成稠密表示,最上面的线性层用于调整维度

  • Embedding类,但是常用的就前两个参数

class torch.nn.Embedding(num_embeddings, embedding_dim, padding_idx=None, max_norm=None, norm_type=2.0, scale_grad_by_freq=False, sparse=False, _weight=None, device=None, dtype=None)

框起来的第一个参数代表input的维度;第二个参数代表了矩阵的宽和高

输入/出的对比

*就是inputsize,就是seq×batch

线性层的参数和输入输出

其实就是把维度相同的input和output进行转换;

交叉熵的参数和输入输出

这里的输入可以加入维度di,反正最终的结果就是每个维度上的交叉熵求和;

import torch

# embedding层
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) #这样就可以变成稠密的向量
        x, _ = self.rnn(x, hidden)
        x = self.fc(x)
        return x.view(-1,num_class)

#一堆参数
num_class = 4
input_size = 4
hidden_size = 8
embedding_size = 10
num_layers = 2
batch_size = 1
seq_len = 5

#准备数据
idx2char = ['e', 'h', 'l', 'o']
x_data = [[1, 0, 2, 2, 3]] # (batch, seq_len)
y_data = [3, 1, 2, 3, 2] # (batch * seq_len)

inputs = torch.LongTensor(x_data).view(batch_size, seq_len)
labels = torch.LongTensor(y_data)

#实例化模型并构造损失函数和优化器
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[x] for x in idx]), end='')
    print(', Epoch[%d/15] loss=%.4f' % (epoch+1, loss.item()))

可以看到:能够更早的获得ohlol,这是因为模型的学习能力比之前要强

  • 作业1:用下LSTM

遗忘门,前面来的数据乘上(0,1)的参数,从而使其少了一部分

简单来说就是运用上一层的数据来构建下一层的,有点像迭代算法。但是这种方法可以减小梯度消失的情况;

σ函数就是\frac{1}{1 +e^{-z}},这样一来就多了一条ct+1的通路,方便反向传播?

一般来说,LSTM的效果要比RNN好得多,但是其运算较为复杂。所以时间复杂度也就高,那么就采用折中的方法:GRU,就是下面这堆公式

W就是对应的权重。在构造cell时只需要拿来做上面相应的运算就可以了;

  • 作业2:用下GRU来训练模型;

  • 基本流程:

  1. 了解序列数据的维度;
  2. 循环过程所用的权重共享机制
    ——>一般就是自己写个循环,权重层重复用就行了;
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,176评论 5 469
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,190评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,232评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,953评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,879评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,177评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,626评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,295评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,436评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,365评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,414评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,096评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,685评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,771评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,987评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,438评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,032评论 2 341

推荐阅读更多精彩内容