目录
老饼讲解:一步一步上手深度学习

【代码】训练一个RNN拟合sin曲线

作者 : 老饼 发表日期 : 2025-09-29 20:55:29 更新日期 : 2026-05-20 16:15:42
老饼讲解-简单易懂,干货满满,爽过嗦螺!


好了,既然我们学了RNN循环神经网络,那这里我们就简单的来玩一下它吧。

一、RNN-代码实现

这里我们简单点来,就用它来实现sin函数的预测好了。

1.1. 序列问题说明

我们以sin函数序列数据的预测为例,讲述如何使用RNN来解决序列预测问题。

以下是一个sin函数的曲线与序列数据,我们希望通过前5个数据预测之后10个时刻数据。

序列数据说明

这是一种常见的场景,利用前部分的数据,预测后续的数据。

1.2. 数据处理与模型设计

事实上,我们需要先将数据处理成如下的形式,才能适用于RNN:

预处理后的数据

其中,是t时刻之前的5个数据,是之后10个时刻的数据。

我们使用 来预测,那么用RNN模型时,可以这样干:

 RNN-模型设计

如图所示,别看有5个变量,实际它都被我们当作t1时刻的输入,然后用它来预测y1,之后的时刻呢,就没有信息输入啦,所以输入被我们设为0,也就是只靠着隐层一路预测过去。

1.3. 代码实现

好了,根据上面的模型与数据,我们来训练一个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)           # 打印真实值与预测值进行对比

运行结果如下:

RNN的训练结果

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

二、代码解说

好了,下面我们来详细讲讲代码吧~

2.1. 数据导入代码片段-数据导入

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

先回顾我们的原始数据为:

预处理后的数据

由于在pytorch中约定,时序数据的第0维代表时刻,第1维代表样本,第2维代表数据。

我们要预测的共有10个时刻,其中时刻0各个样本的输入是x1-x5,其余时刻各个样本的输入全为0。而y的处理后的数据格式如下:

x[0]代表时刻0的输入数据,它里面存放了各个样本在时刻0的输入,它具体的样本子如下:

x[0]的数据

如上,x[0]存放了各个样本在时刻0的输入,每个样本都有5个变量。x[1]与x[0]的格式是一致的,只是x[1]的数据全为0,因为我们每个样本在时刻1的输入都全为0。

对于y也是类似的,y[0]代表时刻0的输出数据,它里面存放了各个样本在时刻0的输出,具体样子如下:

y[0]的数据

谨记,在pytorch中,时序数据的第0维代表时刻,第1维代表样本,第2维代表数据。

2.2. 数据分割

通过下述代码,我们把样本随机抽取20%作为验证数据,其余作为训练样本。

代码片段-数据分割

2.3. 模型结构

好了,下面说下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层》。

2.4.模型训练 

后面都是模型训练的代码,无非就是以MSE为损失,逐步让优化器训练模型的参数,都是前面的知识,耐心看一下就可以了,这里我们就不再解说了。

结束语

好了,这里我们就简单的玩了一下RNN模型,对于RNN,作为入门我们这里就这样简单地小试牛刀地玩一下就好了,到了正式学RNN时,我们再来认真、全面地学习。



图标 评论
添加评论