文章目录
前言
经过了一天的学习有了自己的一点理解, 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
hidden∗input,
h
t
−
1
h_{t-1}
ht−1对应的是
h
i
d
d
e
n
∗
h
i
d
d
e
n
hidden*hidden
hidden∗hidden
然后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的维度是这样的:
- input_size of shape(batch, input_size)
- hiddden0_size (batch, hidden_size)
- 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 4∗5)的矩阵, 然后做查询, 比如索引是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
seq∗batch作为一个维度, hidden是一个维度, 那么对应的label就是
s
e
q
∗
b
a
t
c
h
seq*batch
seq∗batch, 没有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()))