老饼讲解:一步一步上手深度学习
好了,既然我们学了RNN循环神经网络,那这里我们就简单的来玩一下它吧。
这里我们简单点来,就用它来实现sin函数的预测好了。
我们以sin函数序列数据的预测为例,讲述如何使用RNN来解决序列预测问题。
以下是一个sin函数的曲线与序列数据,我们希望通过前5个数据预测之后10个时刻数据。

这是一种常见的场景,利用前部分的数据,预测后续的数据。
事实上,我们需要先将数据处理成如下的形式,才能适用于RNN:

其中,是t时刻之前的5个数据,是之后10个时刻的数据。
我们使用 来预测,那么用RNN模型时,可以这样干:

如图所示,别看有5个变量,实际它都被我们当作t1时刻的输入,然后用它来预测y1,之后的时刻呢,就没有信息输入啦,所以输入被我们设为0,也就是只靠着隐层一路预测过去。
好了,根据上面的模型与数据,我们来训练一个RNN。具体代码实现如下:
# 本代码用于训练一个RNN模型来预测sin函数
# 本代码来自《老饼讲解-深度学习》www.bbblearn.com
import torch
import random
import torch.nn as nn
# ------------------数据生成-----------------
data = torch.sin(torch.arange(-10, 10,0.1)) # 生成sin序列数据
xLen = 5 # 利用前xLen个时刻的数据作为输入
yLen = 10 # 预测之后yLen个时刻的数据
sample_n = len(data)-1-xLen-yLen-1 # 样本个数
x = torch.zeros(1,sample_n,xLen) # 初始化x
y = torch.zeros(yLen,sample_n,1) # 初始化y
for i in range(sample_n): # 从序列数据中获取x与y
x[:,i,:] = data[i:i+xLen].unsqueeze(0) # 将前xLen个数据作为x
y[:,i,:] = data[i+xLen:i+xLen+yLen].unsqueeze(1) # 将后yLen个数据作为y
x=torch.cat([x,torch.zeros(yLen-1,sample_n,xLen)],dim=0) # 补充其余时刻的输入为0
valid_sample_n = round(sample_n*0.2) # 抽取20%的样本作为验证样本
idx = range(sample_n) # 生成一个序列,用于抽样
valid_idx = random.sample(idx, valid_sample_n) # 验证数据的序号
train_idx = [i for i in idx if i not in valid_idx] # 训练数据的序号
train_x = x[:,train_idx,:] # 抽取训练数据的x
train_y = y[:,train_idx,:] # 抽取训练数据的y
valid_x = x[:,valid_idx,:] # 抽取验证数据的x
valid_y = y[:,valid_idx,:] # 抽取验证数据的y
#--------------------模型结构-----------------
class RnnNet(nn.Module):
def __init__(self,input_size,out_size,hiden_size): # 模型初始化
super(RnnNet, self).__init__() # 先调用上级初始化
self.rnn = nn.RNN(input_size, hiden_size) # RNN隐层
self.fc = nn.Linear(hiden_size, out_size) # 线性层
self.hiden_size = hiden_size # 隐神经元个数
def forward(self, x,h0=None): # 定义模型的输出
if(h0==None): # 如果隐层初始值为None
h0 = torch.zeros(1,x.shape[1],self.hiden_size) # 将隐层初始化为0
h,_ = self.rnn(x,h0) # 计算各时刻的隐层
y = self.fc(h) # 计算各时刻的输出
return y,h # 返回隐层值与输出值
#--------------------模型训练------------------
# 模型设置
goal = 0.00001 # 训练目标
epochs = 100000 # 训练频数
model = RnnNet(xLen,1,10) # 初始化模型,1输入,1输出,10隐节点
lossFun = torch.nn.MSELoss() # 定义损失函数为MSE损失函数
optimizer = torch.optim.SGD(model.parameters(), lr=0.01) # 初始化优化器
# 模型训练
for epoch in range(epochs):
optimizer.zero_grad() # 将优化器里的参数梯度清空
train_py,_ = model(train_x) # 计算模型训练数据的预测值
train_loss = lossFun(train_py, train_y) # 计算训练数据的损失函数值
valid_py,_ = model(valid_x) # 计算验证数据的模型的预测值
valid_loss = lossFun(valid_py, valid_y) # 计算验证数据的损失函数值
if(epoch%1000==0):
print('------当前epoch:',str(epoch),'----------') # 打印当前步数
print('train_loss:',train_loss.data) # 打印训练损失值
print('valid_loss:',valid_loss.data) # 打印验证损失值
if(train_loss<goal): # 如果训练已经达到目标
break # 则退出训练
train_loss.backward() # 更新参数的梯度
optimizer.step() # 更新参数
# ------------------展示结果--------------------
py,h = model(x) # 模型预测
print('loss:',lossFun(py, y).data) # 打印损失值
idx = 0 # 抽取一条样本
print('---smaple'+str(idx)+':trueValue and PredictValue:---') # 打印样本真实与预测结果
print(torch.cat([y[:,idx,:],py[:,idx,:]],dim=1).data) # 打印真实值与预测值进行对比运行结果如下:

可以看到,训练了99000步后,模型的loss(即mse)已经达到极小0.000047。此时,抽了一条样本来查看预测结果,可以看到,真实值与预测值基本一致。当然,还是小有误差的,毕竟,它靠着前5条数据,一直推出后10个时刻的数据,已经算很不错了。
好了,下面我们来详细讲讲代码吧~

这部分数据生成的代码没什么好说的,代码读起来烦琐,我们直接说它最终所生成的x,y长什么样。
先回顾我们的原始数据为:

由于在pytorch中约定,时序数据的第0维代表时刻,第1维代表样本,第2维代表数据。
我们要预测的共有10个时刻,其中时刻0各个样本的输入是x1-x5,其余时刻各个样本的输入全为0。而y的处理后的数据格式如下:
x[0]代表时刻0的输入数据,它里面存放了各个样本在时刻0的输入,它具体的样本子如下:
![x[0]的数据](/img/cnt/text/th/202510/614e3379-3adf-4cd6-a335-831f998c2e21.png)
如上,x[0]存放了各个样本在时刻0的输入,每个样本都有5个变量。x[1]与x[0]的格式是一致的,只是x[1]的数据全为0,因为我们每个样本在时刻1的输入都全为0。
对于y也是类似的,y[0]代表时刻0的输出数据,它里面存放了各个样本在时刻0的输出,具体样子如下:
![y[0]的数据](/img/cnt/text/th/202510/9df13166-0d9a-4461-9770-7519f69025f3.png)
谨记,在pytorch中,时序数据的第0维代表时刻,第1维代表样本,第2维代表数据。
通过下述代码,我们把样本随机抽取20%作为验证数据,其余作为训练样本。

好了,下面说下RNN的模型结构

第29-33行,模型的初始化部分,详细如下:
第29行,设置模型初始化时共有三个参数:输入个数、输出个数与隐层个数。
第30行,调用上级的初始化函数进行预初始化。
第31行,初始化一个RNN层,用来作为我们的隐层。pytorch的RNN层我们下面使用时再具体讲解。
第32行,初始化一个线性层,用来作为RNN我们的输出层。
第33行,记录隐层的节点个数。
好了,到了forward部分了,详细如下:
第35行,这里我们设置模型必须传入一个x,与一个可选变量:隐层初始值。
第36、37行,如果没有输入隐层初始值,那么将隐层各个样本的初始值都设为0。
第38行,将x与h0用RNN层进行预测,就得到了各个时刻隐层的输出h了。
第39行,进一步,将隐层输出投放到全连接层,就得到最终的输出了。
如果对pytorch的RNN层有不理解的,可以参考文章:《详细说说pytorch-RNN层》。
后面都是模型训练的代码,无非就是以MSE为损失,逐步让优化器训练模型的参数,都是前面的知识,耐心看一下就可以了,这里我们就不再解说了。
好了,这里我们就简单的玩了一下RNN模型,对于RNN,作为入门我们这里就这样简单地小试牛刀地玩一下就好了,到了正式学RNN时,我们再来认真、全面地学习。
评论