单向RNN
这几天一直在看RNN方面的知识,其中最感到疑惑的是下面的两张图。下面两张图说出了单向循环神经网络的所有原理,但是这里面其实是有一点问题的。比如下面第一张图,整个RNN的构成其实是有三个矩阵的。首先输入向量通过输入矩阵U,与上一个的隐藏层状态乘以权重矩阵W后相乘得到了这一层的隐藏状态ht,ht通过权重矩阵V得到最后的输出。
下面这一张图更加简单,整个RNN共用一个矩阵。下图的第二张图讲的非常清楚。将上一层的隐藏层状态和当前的输入拼接送入权重矩阵A中得到本层的隐藏层的状态。
pytorch中 RNN的实现
RNN — PyTorch 1.13 documentation
上面是隐藏层的的计算公式。这也说明了RNN为什么叫做循环神经网络,因为它是不同时序的数据共用一个权重矩阵。
结合一下下面这张图解释一些RNN中参数的含义。
input_size:表示 输入特征的维度。也就是x0的维度
hidden_size:表示隐藏层的输出维度。也就是h1的维度
num_layers: 表示中间隐藏层的个数。上图表示只有一个隐藏层,也就是只有一个权重矩阵A
seq_lenth : 表示词向量的个数。就是上图中x0,x1,x2这些向量的个数。这里解释一下计算的过程,首先x0输入RNN网络中,得到隐藏层的状态,隐藏层的状态被保留了下来,然后输入下一个向量,这时添加上一个时刻的隐藏层的状态,可以得到之前的信息。下面的代码可以看出有个for循环,就是遍历每一个词向量。最后的输出包含两个变量,一个是所有时刻输出组成的向量,一个是最后一个时刻的输出。一般使用最后一个时刻的向量就可以,因为这个向量就已经包含了前面所有的信息。
bidirectional:表示的是是否使用双向的RNN。
import torch
import torch.nn as nn
batchsize=2
seq_lenth=3 ##时间长度
input_size=10
hidden_size=5
#随机初始化一个随机序列
input=torch.randn(batchsize,seq_lenth,input_size)
#随机一个初始状态
h0=torch.zeros(batchsize,hidden_size)
#setp1 调用pytorch中API
signal_rnn=nn.RNN(input_size=input_size,hidden_size=hidden_size,batch_first=True)
rnn_output,hn=signal_rnn(input,h0.unsqueeze(0))
# print('所有的隐藏层输出',rnn_output.shape)
# print('最后一个隐藏层的输出',hn.shape)
print('RNN的输出')
# print('所有的隐藏层输出',rnn_output)
print('最后一个隐藏层的输出',hn)
print('最后一个时刻RNN的输出',rnn_output[:,-1,])
#step2 自己手写实现简单的API
def rnn_forward(input,weight_ih,bias_ih,weight_hh,bias_hh,h0=None):
batchsize,seq_lenth,input_size=input.shape
hidden_size=weight_ih.shape[0]
#初始化一个输出矩阵
h_out=torch.zeros(batchsize,seq_lenth,hidden_size)
#计算最后的输出
for t in range(seq_lenth):
x=input[:,t,:].unsqueeze(2) #获取当前时刻的输入特征 batch_size*input_size*1
weight_ih_batch=weight_ih.unsqueeze(0).tile(batchsize,1,1)
weight_hh_batch=weight_hh.unsqueeze(0).tile(batchsize,1,1)
w_times_x=torch.bmm(weight_ih_batch,x).squeeze(-1)
w_times_h=torch.bmm(weight_hh_batch,h0.unsqueeze(2)).squeeze(-1) #batch_size*hidden_size
h0=torch.tanh(w_times_h+bias_ih+w_times_x+bias_hh)
h_out[:,t,:]=h0
return h_out,h0.unsqueeze(0)
#验证一下rnn_forward的正确性
# for pram,name in signal_rnn.named_parameters():
# print('参数',pram,'值',name)
custom_rnn_output,custom_state_final=rnn_forward(input,signal_rnn.weight_ih_l0,signal_rnn.bias_ih_l0,signal_rnn.weight_hh_l0,signal_rnn.bias_hh_l0,h0=h0)
print('单向RNN')
# print(custom_rnn_output)
print('单向RNN的最后输出',custom_state_final)
讲到这里差不多就明白了RNN的基本知识了。
RNN实战
import numpy as np
import matplotlib.pyplot as plt
# 生成一些数据,并且可视化
steps = np.linspace(0, np.pi*4, 100, dtype=np.float)
x_np = np.sin(steps)
y_np = np.cos(steps)
plt.plot(steps, y_np, 'r-', label='target(cos)')
plt.plot(steps, x_np, 'b-', label='input(sin)')
plt.legend(loc='best')
plt.show()
这里做一个预测问题,输入为sin值,输出对应的cos值。从上图中,可以看到x1和x2具有相同的cos值,但是对应的输入不一样,也就是说如果使用传统直连模型,这种预测问题是很不好做的。
import torch
from torch import nn
class Rnn(nn.Module):
def __init__(self, input_size):
super(Rnn, self).__init__()
self.rnn = nn.RNN(
input_size=input_size, ##输入特征的维度
hidden_size=16, ##隐藏层输出的维度
num_layers=1, ##隐藏层的个数
batch_first=True
)
self.out = nn.Sequential(
# nn.Linear(16,16),
# nn.ReLU(),
nn.Linear(160,1),
# nn.Sigmoid()
)
def forward(self, x, h_state=None):
r_out, h_state = self.rnn(x, h_state)
out=self.out(r_out.flatten())
return out, h_state
if __name__=='__main__':
from torchinfo import summary
model=Rnn(1)
#输入 batchsize,seq_lenth,input_size
input=torch.rand(1,10,1)
summary(model)
out,ht=model(input)
print('预测的输出',out.shape)
print('RNN最后一个时刻的输出',ht.shape)
模型的训练
import numpy as np
import torch
from torch import nn
from models import Rnn
seq_lenth = 10 # 词向量的维度
input_size=1 #输入的维度
# 模型的模型的创建
model = Rnn(input_size)
# 定义优化器和损失函数
loss_func = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.02)
h_state = None # 第一次的时候,暂存为0
for epoch in range(10):
epoch_loss=0
for step in range(300): ###生成3000个随机序列
# 生成训练数据
start, end = step , (step + 1)
steps = np.linspace(start, end, seq_lenth, dtype=np.float32)
x = torch.tensor(np.sin(steps))
x = x.view(1, seq_lenth, 1)
y = torch.tensor((np.cos(end)),dtype=torch.float32)
y = y.view(1,1,1)
prediction, h_state = model(x, h_state)
# h_state = h_state.data
h_state = h_state.detach() # 隐藏层的状态不参与梯度传播
loss = loss_func(prediction, y)
epoch_loss=epoch_loss+loss.data
optimizer.zero_grad()
loss.backward()
optimizer.step()
print('第{}个epoch损失值'.format(epoch),epoch_loss/300)
torch.save(model,'./model{}.pth'.format(epoch))
模型的测试
import numpy as np
import torch
import matplotlib.pyplot as plt
model=torch.load('model5.pth')
seg_lenth=10
h_state = None
x_true=[]
y_true=[]
y_predicted=[]
for step in range(300,350): ###生成300个随机序列
# 生成训练数据
start, end = step , (step + 1)
steps = np.linspace(start, end, seg_lenth, dtype=np.float32)
x = torch.tensor(np.sin(steps))
x_true.append((x[-1]))
x = x.view(1, seg_lenth, 1)
y = torch.tensor((np.cos(end)), dtype=torch.float32)
y = y.view(1, 1, 1)
y_true.append((y.data.numpy().flatten()))
prediction, h_state = model(x, h_state)
h_state = h_state.detach()
y_predicted.append(prediction.data.numpy().flatten())
print('预测值',y_predicted)
print('真实值',y_true)
plt.plot(np.array(x_true),np.array(y_predicted), 'r-',label='predict')
plt.plot(np.array(x_true),np.array(y_true), 'b-',label='true')
# plt.plot(np.array(y_predicted),'r-',label='predict')
# plt.plot(np.array(y_true),'b-',label='true')
plt.legend(loc='best')
plt.show()
由上面的效果图,可以看到模型拟合的很好。但是最开始的值差别非常大。