实际上就是对线性层的复用?
卷积层运算复杂,但是权重不多(共享权重);而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的其他参数:
- 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)的参数,从而使其少了一部分
简单来说就是运用上一层的数据来构建下一层的,有点像迭代算法。但是这种方法可以减小梯度消失的情况;
σ函数就是,这样一来就多了一条ct+1的通路,方便反向传播?
一般来说,LSTM的效果要比RNN好得多,但是其运算较为复杂。所以时间复杂度也就高,那么就采用折中的方法:GRU,就是下面这堆公式
W就是对应的权重。在构造cell时只需要拿来做上面相应的运算就可以了;
作业2:用下GRU来训练模型;
基本流程:
- 了解序列数据的维度;
- 循环过程所用的权重共享机制;
——>一般就是自己写个循环,权重层重复用就行了;