前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >时间序列数据建模流程范例

时间序列数据建模流程范例

作者头像
EmoryHuang
发布于 2022-10-31 07:51:01
发布于 2022-10-31 07:51:01
1.2K00
代码可运行
举报
文章被收录于专栏:EmoryHuang's BlogEmoryHuang's Blog
运行总次数:0
代码可运行

时间序列数据建模流程范例

前言

最开始在学习神经网络,PyTorch 的时候,懂的都还不多,虽然也知道 RNN, CNN 这些网络的原理,但真正自己实现起来又是另一回事,代码往往也都是从网上 copy 过来然后再自己魔改的,这也就导致了一系列的问题,代码格式不统一,没弄懂具体实现细节等等。当然,凭这些 copy 过来的代码让模型运行起来还是不难的,你只需要知晓一定的原理。显而易见,这些时间往往最后都是要“还”的。

写这篇文章主要还是记录一下整体的思路,并对网络训练的整个过程进行标准化。当然,这只是我自己在写网络时的总结而已,未必适合每一个人的风格,希望能对你有所启发。

还是从一个例子开始,问题的背景很简单,一维时序数据的预测问题

假如你对 RNN、LSTM 的原理并不了解同样不影响阅读,说白了,这里探讨的并不是怎么建立网络,重要的是整体的流程。

你也可以 点击这里 了解 RNN、LSTM 的工作原理

准备数据

首先就是准备数据,这部分往往是最花费时间,最会发生问题的地方。这里说的准备数据并不只是丢出来一个数据库或是 csv 文件,它涉及到数据获取,数据清洗,数据标准化,创建数据集等过程,让我们一个一个来讨论。

数据获取

数据获取部分没什么好讲的,根据你的数据来源,可能是格式化的,也可能的非格式化的。

你可以 点击这里 获取本文所使用的数据。

这里我使用的数据是从 2020/08/01 到 2020/08/31 的小时数据,如下图所示。

数据清洗

视你的需求以及原始数据来说,数据清洗可以很简单,也可以很复杂。简单来说,去除空值,去除重复值,去除连续常值,正态分布的 3σ 去除异常值等等,根据你想要的目标,选择不同的数据清洗方式。

下面是一个简单的标准化函数,使用 MinMaxScaler 将数据归一化为 0 - 1。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
def data_normalized(data):
    '''标准化数据

    Args:
        data(pd.DataFrame): 待标准化数据

    Returns:
        norm_data(tensor): 标准化后的数据
        scaler(MinMaxScaler): 标准化器
    '''
    __data = np.array(data)
    # 将小于 0 的值置为 0
    __data[__data < 0] = 0
    # 标准化数据
    scaler = MinMaxScaler()
    norm_data = scaler.fit_transform(__data.reshape(-1, 1))
    norm_data = torch.tensor(norm_data, dtype=torch.float32)
    return norm_data, scaler

为了简便起见,这里我给出的数据是已经经过了差分,重采样等步骤之后的数据。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
data = pd.read_csv('TIME_SEQ_DATA.csv')
data['CreateDate'] = pd.to_datetime(data['CreateDate'])

data.dropna(inplace=True)
data.drop_duplicates(inplace=True)
data.sort_values(by=['CreateDate'], inplace=True)
data.reset_index(drop=True, inplace=True)

norm_data, scaler = data_normalized(data['Value'])

上面的处理都是常规操作,还是那句话,根据你的实际需求。

至此,我们完成了简单的数据清洗,获得了标准化的数据。

创建数据集

创建数据集同样也有很多方法,手动对数据划分,或是利用 PyTorch 定义好的 Dataset 进行重写。

网上有许多手动划分的例子,大多数都是类似下面这样的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
def create_dataset(data, look_back):
    dataset_x, dataset_y = [], []
    for i in range(len(data) - look_back):
        dataset_x.append(data[i:(i + look_back)])
        dataset_y.append(data[i + look_back])
    return np.array(dataset_x), np.array(dataset_y)

...
# 划分训练集和测试集
train_size = int(len(dataset_x) * 0.7)
...

这里我使用 DatasetDataLoader 这两个工具类来构建数据

  • Dataset 定义了数据集的内容,它相当于一个类似列表的数据结构,具有确定的长度,能够用索引获取数据集中的元素。
  • DataLoader 定义了按 batch 加载数据集的方法,能够控制 batch 的大小,batch 中元素的采样方法,以及将 batch 结果整理成模型所需输入形式的方法,并且能够使用多进程读取数据。
根据 Tensor 创建数据集

现在让我们暂时抛开背景问题,下面这个例子很好的说明了创建鸢尾花数据集的过程:

  1. 使用 TensorDataset,将 data 和 target,也就是 x 和 y 分别传入,得到了 TensorDataset 类型的数据,你可以使用 for 循环查看里面的具体形式。
  2. 使用 random_split,将整个数据集划分为训练集和预测集,得到 Subset,你可以加上 torch.manual_seed(0) 来指定随机种子。
  3. 使用 DataLoader 加载数据集。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
from sklearn import datasets

# 根据Tensor创建数据集
iris = datasets.load_iris()
ds_iris = TensorDataset(torch.tensor(iris.data), torch.tensor(iris.target))

# 分割成训练集和预测集
n_train = int(len(ds_iris) * 0.8)
n_valid = len(ds_iris) - n_train
ds_train, ds_valid = random_split(ds_iris, [n_train, n_valid])

print(type(ds_iris))
# <class 'torch.utils.data.dataset.TensorDataset'>
print(type(ds_train))
# <class 'torch.utils.data.dataset.Subset'>

dl_train = DataLoader(ds_train, batch_size = 8)
dl_valid = DataLoader(ds_valid, batch_size = 8)

for features, labels in dl_train:
    print(features, labels)
    break
创建自定义数据集

在上面的例子中,我们使用 TensorDataset 直接创建数据集。当你完成了对 x 和 y 的划分之后,对于划分简单的数据可以直接使用这样的方法。对于一些要求复杂的数据集,更优秀的方法是自定义。

我们只需实现 Dataset__len__ 方法和 __getitem__ 方法,就可以轻松构建自己的数据集。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 定义数据集
class myDataset(Dataset):
    def __init__(self, data, look_back) -> None:
        super().__init__()
        self.data = data
        self.look_back = look_back

    def __len__(self) -> int:
        return len(self.data) - self.look_back

    def __getitem__(self, index):
        feature = self.data[index:index + self.look_back]
        label = self.data[index + self.look_back]
        return feature, label

这里,我们通过 look_back 个数据点,预测下一个数据点。

具体来说,我们对 __len__ 方法和 __getitem__ 方法进行了重写,具体的代码并不复杂。接下来,我们就可以使用 myDataset 达到和上面提到的 create_dataset 同样的效果。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 生成数据集
ds_data = myDataset(norm_data.view(-1).to(DEVICE), look_back=LOOK_BACK)

# 将数据集分为训练集和测试集
n_train = int(len(ds_data) * 0.8)
n_test = len(ds_data) - n_train
ds_train, ds_test = random_split(ds_data, [n_train, n_test])

dl_tarin = DataLoader(ds_train, batch_size=BATCH_SIZE, shuffle=True, drop_last=True)
dl_test = DataLoader(ds_test, batch_size=BATCH_SIZE, shuffle=False, drop_last=False)

类似的,参考创建鸢尾花数据集的方法,同样将数据集分为训练集和测试集,并使用 DataLoader 加载。

使用 DataLoader 加载数据集

现在让我们回过头来看看 DataLoader 的具体使用。

DataLoader 能够控制 batch 的大小,batch 中元素的采样方法,以及将 batch 结果整理成模型所需输入形式的方法,并且能够使用多进程读取数据。

DataLoader 的函数签名如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
DataLoader(
    dataset,            # 数据集
    batch_size=1,       # 批次大小
    shuffle=False,      # 是否乱序
    num_workers=0,      # 使用多进程读取数据,设置的进程数。
    drop_last=False,    # 是否丢弃最后一个样本数量不足batch_size批次数据。
    ...
)

一般情况下,我们仅仅会配置 dataset, batch_size, shuffle, num_workers, drop_last 这五个参数,其他参数使用默认值即可。

关于 shape 的一些问题

准备数据的过程往往是复杂的,后面模型出了问题,或许就是数据处理上出了问题。上面我们着重将了如何创建数据集,但还有隐含在其中的另一个重要的点没有提及,也就是 size,或者说 shape。

最开始学习的时候,相信许多人都有疑问,为什么这里要 reshape(),为什么那里要 view(-1),为什么这里要 flatten(),为什么那里要 unsqueeze(0)

问题的根本原因就是,没有弄清楚经过某个处理之后你的数据的 shape 的变化,再或许就是没搞清上面这些函数的用法。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# LSTM input shape: (seq_len, batch_size, input_size)
output, hidden = self.lstm(input)
# output shape: (seq_len, batch_size, output_size)

另外就是 layer 往往需要特定的输入维度,以 LSTM 为例,它需要传入的是三维参数:(seq_len, batch_size, input_size),out 的输出维度 (seq_len, batch_size, output_size),在我看来,时刻注意 shape 是一个好的习惯,特别是当数据经过那些你不熟悉的函数后。

定义模型

好了,终于到了定义网络的时候了,或许这部分是最简单的。

一般来说,我们使用 nn.Sequential 按层顺序构建模型,或是继承 nn.Module 基类构建自定义模型。

感觉就像这样,你只需要把它当做一个复合的层:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
self.my_seq = nn.Sequential(nn.Linear(input_size, 24),
                            nn.Dropout(0.5),
                            nn.ReLU(True),
                            nn.Linear(24, 10),
                            nn.Dropout(0.5),
                            nn.ReLU(True))

这里,我只是简单搭建了一个 LSTM 网络,就像所有其他网络一样,结构并不复杂。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 定义模型
class LSTM(nn.Module):
    '''
    Args:
        input_size: feature size
        hidden_size: number of hidden units
        output_size: number of output
        num_layers: layers of LSTM to stack
    '''
    def __init__(self, input_size, hidden_size, output_size=1, num_layers=3):
        super(LSTM, self).__init__()
        self.lstm = nn.LSTM(input_size, hidden_size, num_layers)
        self.linear = nn.Linear(hidden_size, output_size)

    def forward(self, input):
        # LSTM input shape: (seq_len, batch, input_size)
        output, hidden = self.lstm(input)
        output = self.linear(output[-1])
        return output

训练模型

如何训练网络因人而异,但大致都是类似的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
def train(dl_train):
    model = LSTM(LOOK_BACK, 64).to(DEVICE)
    criterion = nn.MSELoss()
    optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
    train_loss = []
    # 开始训练
    for e in range(EPOCH):
        __loss = 0
        for feature, label in dl_train:
            # feature: torch.Size([10, 24])     (BATCH_SIZE, LOOK_BACK)
            # label: torch.Size([10])           (BATCH_SIZE, )
            # out: torch.Size([10, 1])          (BATCH_SIZE, 1)
            out = model(feature.unsqueeze(0))
            loss = criterion(out, label.unsqueeze(1))
            # 反向传播
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            __loss += loss.item()
        train_loss.append(__loss)
        if (e + 1) % 10 == 0:  # 每 10 次输出结果
            print('Epoch: {}, Loss: {}'.format(e + 1, __loss / len(dl_train)))

    # 保存模型参数
    # torch.save(model.state_dict(), MODEL_DIR)
    return model, train_loss
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
model, train_loss = train(dl_tarin)

可视化损失函数在训练集上的迭代情况。

评估模型

在这里,我们直接使用之前创建的测试集进行训练,并计算根均方误差。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
model = model.eval()
pred, actual = [], []
for feature, label in dl_test:
    # feature: torch.Size([10, 24])     (BATCH_SIZE, LOOK_BACK)
    # label: torch.Size([10])           (BATCH_SIZE, )
    # out: torch.Size([10, 1])          (BATCH_SIZE, 1)
    out = model(feature.unsqueeze(0))
    pred += out.view(-1).data.cpu().tolist()
    actual += label.view(-1).data.cpu().tolist()
pred, actual = np.array(pred), np.array(actual)

rmse = np.sqrt(mean_squared_error(actual.reshape(-1), pred.reshape(-1)))
print("根均方误差(RMSE):" + str(rmse))

根均方误差(RMSE):0.12173503830068468

小结

感谢你阅读至此,本文只是简单介绍了一些自己的经验,梳理了一下建模的简单流程。总的来说,我希望我的代码是模块化,标准化的,相信你也如此,希望本文能对你有所帮助。

你可以 点击这里 得到完整代码。

参考资料

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-12-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
C/C++实现你的浪漫表白:浪漫流星雨表白程序
想要讨女朋友欢心也巩固自己所学的知识,各位小伙伴有自己的想法了吗?准备好想要怎样实施了吗?有什么美好的计划了吗?如果没有的话那么别慌,我知道,在座的各位肯定都是有自己的心仪的姑娘,那么今天就教大家一招,做一个表白程序去进行表白,别等了,赶紧打开你的IED,跟着代码敲起来,不然的话,喜欢的人都跟别人跑了!
用户6754675
2020/09/09
25.4K0
c语言黑白棋ai游戏源码
#include <graphics.h> // EasyX_2011惊蛰版 #include <strstream> #include <ctime> #pragma comment(lib, "W
C语言与CPP编程
2021/01/02
1.9K0
C语言/C++实战项目雷霆飞机(代码改进)
一枕眠秋雨
2024/03/11
1980
拼图
拼图这个游戏之前有分享过,但我觉得不是很完美,还有人吐槽背景图片太low,没办法,改点东西吧,还是老样子,先看梦凡玩一遍。
DeROy
2020/05/12
9070
拼图
使用C语言EasyX 创建动态爱心背景
在计算机图形学的世界中,有很多方法可以使程序的界面更加吸引人。在本篇博客中,我将向大家介绍如何使用 EasyX 图形库在 C++ 中创建一个动态的爱心背景。这不仅是一个简单的动画效果,它还包括背景的星星、旋转的心形以及一个美观的背景渐变。
命运之光
2024/03/20
2350
使用C语言EasyX 创建动态爱心背景
利用Python 绘制彩色动态流星雨
流星雨是可遇不可求的美景之一,除了在天空上看到,我们能不能用Python来绘画一场彩色的动态流星雨?
算法与编程之美
2024/03/27
3300
利用Python 绘制彩色动态流星雨
流星雨代码
一枕眠秋雨
2024/03/11
1850
流星雨代码
C语言+图形编程——自制象棋
用C语言做个象棋是不容易的,涉及到的知识点有很多方面,C语言里面的基本数据类型、运算符、顺序,分支,循环结构。还有运用到数组,函数,指针,以及我们的位运算等。
诸葛青云
2018/09/28
3.4K0
C语言+图形编程——自制象棋
C语言/C++雷霆战机代码(终极版)
一枕眠秋雨
2024/03/11
5800
C语言/C++雷霆战机代码(终极版)
C语言小游戏扫雷
声明:本文为原创,作者为 对弈,转载时请保留本声明及附带文章链接:http://www.duiyi.xyz/c%e5%ae%9e%e7%8e%b0%e9%9b%b7%e9%9c%86%e6%88%98%e6%9c%ba-50/
对弈
2019/09/04
5.2K0
c++ 字母降落小游戏
2、如果是常用颜色,直接输入英文大写即可;如果是不常见颜色,输入:RGB(13,240,60)即可自己调色
青衫哥
2023/03/31
6960
c++ 字母降落小游戏
C语言实现飞翔的小鸟小游戏
参考视频https://www.bilibili.com/video/BV1Xo4y1R7hs 缺陷:撞柱子功能暂未实现
半生瓜的blog
2023/05/12
3020
c语言对对碰游戏源码
//////////////////////////////////////////////////////////// //画素材的x和y都是反的,因为x表示行,但是画出来x表示列,y同 //////////////////////////////////////////////////////////// // 来自公众号:c语言与cpp编程 #include <graphics.h> #include <fstream> #include <strstream> #include <iomanip
C语言与CPP编程
2021/01/02
4100
c语言奔跑的火柴人游戏源码
#include <iostream> #include <fstream> #include <graphics.h> #include <conio.h> #include <time.h> using namespace std; // 来自公众号:c语言与cpp编程 /******全局变量******/ #define STEPDISTANCE 6 // 一步的距离。数值越大,移动速度越快 #define JUMPSPEED 10 // 数值越小,跳得越远 #define ROLLDIS
C语言与CPP编程
2021/01/02
2.7K0
【EasyX】飞机大战
本博客介绍利用EasyX加上图片、音乐素材实现一个鼠标控制的飞机大战小游戏。 本文源码可从github获取
程序员小涛
2023/07/10
3530
【EasyX】飞机大战
经典C语言/C++项目,雷霆战机
一枕眠秋雨
2024/03/11
2180
经典C语言/C++项目,雷霆战机
C语言教你写个‘浪漫烟花‘---特别漂亮
首先绘制菜单界面显示文字。烟花有上升阶段和爆炸阶段,定义烟花和烟花弹结构体。 烟花:坐标位置,爆炸的半径大小,最大半径,中心距左上角的距离,长宽,像素,时间等。 烟花弹:坐标位置,最高点,是否发射,时间,个数等。 初始化数据。加载资源贴图。随机发射数目随机,
用户6754675
2020/12/07
5.8K0
我用 140 行代码,带你看一场流星雨⭐
在一个夜深人静的晚上,程序员小丞坐在屋顶上,看着屏幕上满屏的error,心里拔凉拔凉的,泪水润湿了脸庞,无数个自己提桶跑路的身影充斥在脑海之中,猛然才发现自己还没有桶。此时星空中闪过了漫天的流星,小丞看到此景,心中的bug早已化去,留下的是还原此景的豪言壮举!(梦醒了,纯属瞎编)
小丞同学
2021/08/16
2K0
1611: [Usaco2008 Feb]Meteor Shower流星雨
1611: [Usaco2008 Feb]Meteor Shower流星雨 Time Limit: 5 Sec  Memory Limit: 64 MB Submit: 1010  Solved: 446 [Submit][Status][Discuss] Description 去年偶们湖南遭受N年不遇到冰冻灾害,现在芙蓉哥哥则听说另一个骇人听闻的消息: 一场流星雨即将袭击整个霸中,由于流星体积过大,它们无法在撞击到地面前燃烧殆尽, 届时将会对它撞到的一切东西造成毁灭性的打击。很自然地,芙蓉哥哥开始担心自
HansBug
2018/04/10
6120
C语言项目 微信小游戏《羊了个羊》
image:背景图bk.jpg,卡片图lingdang.png,shu.png,cao.png
CtrlX
2023/03/21
4160
C语言项目 微信小游戏《羊了个羊》
相关推荐
C/C++实现你的浪漫表白:浪漫流星雨表白程序
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验