机器学习算法那些事 | 使用Transformer模型进行时间序列预测实战

本文来源公众号“机器学习算法那些事,仅用于学术分享,侵权删,干货满满。

原文链接:使用Transformer模型进行时间序列预测实战

时间序列预测是一个经久不衰的主题,受自然语言处理领域的成功启发,transformer模型也在时间序列预测有了很大的发展。本文可以作为学习使用Transformer 模型的时间序列预测的一个起点。

1 数据集

这里我们直接使用kaggle中的 Store Sales — Time Series Forecasting作为数据。这个比赛需要预测54家商店中各种产品系列未来16天的销售情况,总共创建1782个时间序列。数据从2013年1月1日至2017年8月15日,目标是预测接下来16天的销售情况。虽然为了简洁起见,我们做了简化处理,作为模型的输入包含20列中的3,029,400条数据,。每行的关键列为' store_nbr '、' family '和' date '。数据分为三类变量:

1、截止到最后一次训练数据日期(2017年8月15日)之前已知的与时间相关的变量。这些变量包括数字变量,如“销售额”,表示某一产品系列在某家商店的销售额;“transactions”,一家商店的交易总数;' store_sales ',该商店的总销售额;' family_sales '表示该产品系列的总销售额。

2、训练截止日期(2017年8月31日)之前已知,包括“onpromotion”(产品系列中促销产品的数量)和“dcoilwtico”等变量。这些数字列由' holiday '列补充,它表示假日或事件的存在,并被分类编码为整数。此外,' time_idx '、' week_day '、' month_day '、' month '和' year '列提供时间上下文,也编码为整数。虽然我们的模型是只有编码器的,但已经添加了16天移动值“onpromotion”和“dcoilwtico”,以便在没有解码器的情况下包含未来的信息。

3、静态协变量随着时间的推移保持不变,包括诸如“store_nbr”、“family”等标识符,以及“city”、“state”、“type”和“cluster”等分类变量(详细说明了商店的特征),所有这些变量都是整数编码的。

我们最后生成的df名为' data_all ',结构如下:

 categorical_covariates = ['time_idx','week_day','month_day','month','year','holiday']categorical_covariates_num_embeddings = []for col in categorical_covariates:data_all[col] = data_all[col].astype('category').cat.codescategorical_covariates_num_embeddings.append(data_all[col].nunique())categorical_static = ['store_nbr','city','state','type','cluster','family_int']categorical_static_num_embeddings = []for col in categorical_static:data_all[col] = data_all[col].astype('category').cat.codescategorical_static_num_embeddings.append(data_all[col].nunique())numeric_covariates = ['sales','dcoilwtico','dcoilwtico_future','onpromotion','onpromotion_future','store_sales','transactions','family_sales']target_idx = np.where(np.array(numeric_covariates)=='sales')[0][0]

在将数据转换为适合我的PyTorch模型的张量之前,需要将其分为训练集和验证集。窗口大小是一个重要的超参数,表示每个训练样本的序列长度。此外,' num_val '表示使用的验证折数,在此上下文中设置为2。将2013年1月1日至2017年6月28日的观测数据指定为训练数据集,以2017年6月29日至2017年7月14日和2017年7月15日至2017年7月30日作为验证区间。

同时还进行了数据的缩放,完整代码如下:

 def dataframe_to_tensor(series,numeric_covariates,categorical_covariates,categorical_static,target_idx):numeric_cov_arr = np.array(series[numeric_covariates].values.tolist())category_cov_arr = np.array(series[categorical_covariates].values.tolist())static_cov_arr = np.array(series[categorical_static].values.tolist())x_numeric = torch.tensor(numeric_cov_arr,dtype=torch.float32).transpose(2,1)x_numeric = torch.log(x_numeric+1e-5)x_category = torch.tensor(category_cov_arr,dtype=torch.long).transpose(2,1)x_static = torch.tensor(static_cov_arr,dtype=torch.long)y = torch.tensor(numeric_cov_arr[:,target_idx,:],dtype=torch.float32)return x_numeric, x_category, x_static, ywindow_size = 16forecast_length = 16num_val = 2val_max_date = '2017-08-15'train_max_date = str((pd.to_datetime(val_max_date) - pd.Timedelta(days=window_size*num_val+forecast_length)).date())train_final = data_all[data_all['date']<=train_max_date]val_final = data_all[(data_all['date']>train_max_date)&(data_all['date']<=val_max_date)]train_series = train_final.groupby(categorical_static+['family']).agg(list).reset_index()val_series = val_final.groupby(categorical_static+['family']).agg(list).reset_index()x_numeric_train_tensor, x_category_train_tensor, x_static_train_tensor, y_train_tensor = dataframe_to_tensor(train_series,numeric_covariates,categorical_covariates,categorical_static,target_idx)x_numeric_val_tensor, x_category_val_tensor, x_static_val_tensor, y_val_tensor = dataframe_to_tensor(val_series,numeric_covariates,categorical_covariates,categorical_static,target_idx)

2 数据加载器

在数据加载时,需要将每个时间序列从窗口范围内的随机索引开始划分为时间块,以确保模型暴露于不同的序列段。

为了减少偏差还引入了一个额外的超参数设置,它不是随机打乱数据,而是根据块的开始时间对数据集进行排序。然后数据被分成五部分——反映了我们五年的数据集——每一部分都是内部打乱的,这样最后一批数据将包括去年的观察结果,但还是随机的。模型的最终梯度更新受到最近一年的影响,理论上可以改善最近时期的预测。

 def divide_shuffle(df,div_num):space = df.shape[0]//div_numdivision = np.arange(0,df.shape[0],space)return pd.concat([df.iloc[division[i]:division[i]+space,:].sample(frac=1) for i in range(len(division))])def create_time_blocks(time_length,window_size):start_idx = np.random.randint(0,window_size-1)end_idx = time_length-window_size-16-1time_indices = np.arange(start_idx,end_idx+1,window_size)[:-1]time_indices = np.append(time_indices,end_idx)return time_indicesdef data_loader(x_numeric_tensor, x_category_tensor, x_static_tensor, y_tensor, batch_size, time_shuffle):num_series = x_numeric_tensor.shape[0]time_length = x_numeric_tensor.shape[1]index_pd = pd.DataFrame({'serie_idx':range(num_series)})index_pd['time_idx'] = [create_time_blocks(time_length,window_size) for n in range(index_pd.shape[0])]if time_shuffle:index_pd = index_pd.explode('time_idx')index_pd = index_pd.sample(frac=1)else:index_pd = index_pd.explode('time_idx').sort_values('time_idx')index_pd = divide_shuffle(index_pd,5)indices = np.array(index_pd).astype(int)for batch_idx in np.arange(0,indices.shape[0],batch_size):cur_indices = indices[batch_idx:batch_idx+batch_size,:]x_numeric = torch.stack([x_numeric_tensor[n[0],n[1]:n[1]+window_size,:] for n in cur_indices])x_category = torch.stack([x_category_tensor[n[0],n[1]:n[1]+window_size,:] for n in cur_indices])x_static = torch.stack([x_static_tensor[n[0],:] for n in cur_indices])y = torch.stack([y_tensor[n[0],n[1]+window_size:n[1]+window_size+forecast_length] for n in cur_indices])yield x_numeric.to(device), x_category.to(device), x_static.to(device), y.to(device)def val_loader(x_numeric_tensor, x_category_tensor, x_static_tensor, y_tensor, batch_size, num_val):num_time_series = x_numeric_tensor.shape[0]for i in range(num_val):for batch_idx in np.arange(0,num_time_series,batch_size):x_numeric = x_numeric_tensor[batch_idx:batch_idx+batch_size,window_size*i:window_size*(i+1),:]x_category = x_category_tensor[batch_idx:batch_idx+batch_size,window_size*i:window_size*(i+1),:]x_static = x_static_tensor[batch_idx:batch_idx+batch_size]y_val = y_tensor[batch_idx:batch_idx+batch_size,window_size*(i+1):window_size*(i+1)+forecast_length]yield x_numeric.to(device), x_category.to(device), x_static.to(device), y_val.to(device)

3 模型

我们这里通过Pytorch来简单的实现《Attention is All You Need》(2017)²中描述的Transformer架构。因为是时间序列预测,所以注意力机制中不需要因果关系,也就是没有对注意块应用进行遮蔽。

从输入开始:分类特征通过嵌入层传递,以密集的形式表示它们,然后送到Transformer块。多层感知器(MLP)接受最终编码输入来产生预测。嵌入维数、每个Transformer块中的注意头数和dropout概率是模型的主要超参数。堆叠多个Transformer块由' num_blocks '超参数控制。

下面是单个Transformer块的实现和整体预测模型:

 class transformer_block(nn.Module):def __init__(self,embed_size,num_heads):super(transformer_block, self).__init__()self.attention = nn.MultiheadAttention(embed_size, num_heads, batch_first=True)self.fc = nn.Sequential(nn.Linear(embed_size, 4 * embed_size),nn.LeakyReLU(),nn.Linear(4 * embed_size, embed_size))self.dropout = nn.Dropout(drop_prob)self.ln1 = nn.LayerNorm(embed_size, eps=1e-6)self.ln2 = nn.LayerNorm(embed_size, eps=1e-6)def forward(self, x):attn_out, _ = self.attention(x, x, x, need_weights=False)x = x + self.dropout(attn_out)x = self.ln1(x)fc_out = self.fc(x)x = x + self.dropout(fc_out)x = self.ln2(x)return xclass transformer_forecaster(nn.Module):def __init__(self,embed_size,num_heads,num_blocks):super(transformer_forecaster, self).__init__()num_len = len(numeric_covariates)self.embedding_cov = nn.ModuleList([nn.Embedding(n,embed_size-num_len) for n in categorical_covariates_num_embeddings])self.embedding_static = nn.ModuleList([nn.Embedding(n,embed_size-num_len) for n in categorical_static_num_embeddings])self.blocks = nn.ModuleList([transformer_block(embed_size,num_heads) for n in range(num_blocks)])self.forecast_head = nn.Sequential(nn.Linear(embed_size, embed_size*2),nn.LeakyReLU(),nn.Dropout(drop_prob),nn.Linear(embed_size*2, embed_size*4),nn.LeakyReLU(),nn.Linear(embed_size*4, forecast_length),nn.ReLU())def forward(self, x_numeric, x_category, x_static):tmp_list = []for i,embed_layer in enumerate(self.embedding_static):tmp_list.append(embed_layer(x_static[:,i]))categroical_static_embeddings = torch.stack(tmp_list).mean(dim=0).unsqueeze(1)tmp_list = []for i,embed_layer in enumerate(self.embedding_cov):tmp_list.append(embed_layer(x_category[:,:,i]))categroical_covariates_embeddings = torch.stack(tmp_list).mean(dim=0)T = categroical_covariates_embeddings.shape[1]embed_out = (categroical_covariates_embeddings + categroical_static_embeddings.repeat(1,T,1))/2x = torch.concat((x_numeric,embed_out),dim=-1)for block in self.blocks:x = block(x)x = x.mean(dim=1)x = self.forecast_head(x)return x

我们修改后的transformer架构如下图所示:

模型接受三个独立的输入张量:数值特征、分类特征和静态特征。对分类和静态特征嵌入进行平均,并与数字特征组合形成具有形状(batch_size, window_size, embedding_size)的张量,为Transformer块做好准备。这个复合张量还包含嵌入的时间变量,提供必要的位置信息。

Transformer块提取顺序信息,然后将结果张量沿着时间维度聚合,将其传递到MLP中以生成最终预测(batch_size, forecast_length)。这个比赛采用均方根对数误差(RMSLE)作为评价指标,公式为:

鉴于预测经过对数转换,预测低于-1的负销售额(这会导致未定义的错误)需要进行处理,所以为了避免负的销售预测和由此产生的NaN损失值,在MLP层以后增加了一层ReLU激活确保非负预测。

 class RMSLELoss(nn.Module):def __init__(self):super().__init__()self.mse = nn.MSELoss()def forward(self, pred, actual):return torch.sqrt(self.mse(torch.log(pred + 1), torch.log(actual + 1)))

4 训练和验证

训练模型时需要设置几个超参数:窗口大小、是否打乱时间、嵌入大小、头部数量、块数量、dropout、批大小和学习率。以下配置是有效的,但不保证是最好的:

 num_epoch = 1000min_val_loss = 999num_blocks = 1embed_size = 500num_heads = 50batch_size = 128learning_rate = 3e-4time_shuffle = Falsedrop_prob = 0.1model = transformer_forecaster(embed_size,num_heads,num_blocks).to(device)criterion = RMSLELoss()optimizer = torch.optim.Adam(model.parameters(),lr=learning_rate)scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=50, gamma=0.5)

这里使用adam优化器和学习率调度,以便在训练期间逐步调整学习率。

 for epoch in range(num_epoch):batch_loader = data_loader(x_numeric_train_tensor, x_category_train_tensor, x_static_train_tensor, y_train_tensor, batch_size, time_shuffle)train_loss = 0counter = 0model.train()for x_numeric, x_category, x_static, y in batch_loader:optimizer.zero_grad()preds = model(x_numeric, x_category, x_static)loss = criterion(preds, y)train_loss += loss.item()counter += 1loss.backward()optimizer.step()train_loss = train_loss/counterprint(f'Epoch {epoch} training loss: {train_loss}')model.eval()val_batches = val_loader(x_numeric_val_tensor, x_category_val_tensor, x_static_val_tensor, y_val_tensor, batch_size, num_val)val_loss = 0counter = 0for x_numeric_val, x_category_val, x_static_val, y_val in val_batches:with torch.no_grad():preds = model(x_numeric_val,x_category_val,x_static_val)loss = criterion(preds,y_val).item()val_loss += losscounter += 1val_loss = val_loss/counterprint(f'Epoch {epoch} validation loss: {val_loss}')if val_loss<min_val_loss:print('saved...')torch.save(model,data_folder+'best.model')min_val_loss = val_lossscheduler.step()

5 结果

训练后,表现最好的模型的训练损失为0.387,验证损失为0.457。当应用于测试集时,该模型的RMSLE为0.416,比赛排名为第89位(前10%)。

更大的嵌入和更多的注意力头似乎可以提高性能,但最好的结果是用一个单独的Transformer 实现的,这表明在有限的数据下,简单是优点。当放弃整体打乱而选择局部打乱时,效果有所改善;引入轻微的时间偏差提高了预测的准确性。

6 引用

[1]: Alexis Cook, DanB, inversion, Ryan Holbrook. (2021). Store Sales — Time Series Forecasting. Kaggle. https://kaggle.com/competitions/store-sales-time-series-forecasting
[2]: Vaswani, A., Shazeer, N., Parmar, N., Uszkoreit, J., Jones, L., Gomez,  A. N., … & Polosukhin, I. (2017). Attention is all you need. Advances in neural information processing systems30.
[3]: Lim, B., Arık, S. Ö., Loeff, N., & Pfister, T. (2021). Temporal  fusion transformers for interpretable multi-horizon time series  forecasting. International Journal of Forecasting37(4), 1748–1764.

作者:Kaan Aslan
转自:Deephub IMBA

THE END!

文章结束,感谢阅读。您的点赞,收藏,评论是我继续更新的动力。大家有推荐的公众号可以评论区留言,共同学习,一起进步。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/767028.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WPS制作甘特图

“ 甘特图&#xff08;Gantt chart&#xff09;又称为横道图、条状图&#xff08;Bar chart&#xff09;&#xff0c;通过条状图来显示项目、进度和其他时间相关的系统进展的内在关系随着时间进展的情况。” 设置基础样式 设置行高 设置宽度 准备基础数据 计算持续时间 …

轻松引流几百精准粉丝,抖音自动爆粉秘籍揭秘

对于做互联网的朋友们来说&#xff0c;引流是一个必不可少的环节。 掌握一种优秀的引流方法至关重要&#xff0c;这也可以视为我们的生计之源。 今天&#xff0c;我将向大家介绍一款全自动的引流工具——抖音全自动引流脚本软件。 这款软件的效果非常显著&#xff0c;它可以替…

R-CNN笔记

目标检测之R-CNN论文精讲&#xff0c;RCNN_哔哩哔哩_bilibili 论文背景 在该论文提出之前&#xff0c;主流的目标检测思路是&#xff1a; 将一幅图片划分成很多个区域&#xff0c;单独提取出来 对于每个区域使用传统的特征提取方法提取 提取结束后可以使用以为特征向量表示 可以…

计算方法实验2:列主元消元法和Gauss-Seidel迭代法解线性方程组

Task 即已知 y 0 0 , y 100 1 y_00,y_{100}1 y0​0,y100​1&#xff0c;解线性方程组 A y b \mathbf{A}\mathbf{y} \mathbf{b} Ayb&#xff0c;其中 A 99 99 [ − ( 2 ϵ h ) ϵ h 0 ⋯ 0 ϵ − ( 2 ϵ h ) ϵ h ⋯ 0 0 ϵ − ( 2 ϵ h ) ⋯ 0 ⋮ ⋮ ⋱ ⋱ ⋮ 0 0 ⋯…

C++命名空间和内联函数

目录 命名空间 内联函数 概述 特性&#xff1a; 命名空间 在C/C中&#xff0c;变量&#xff0c;函数和和类这些名称都存在于全局作用域中&#xff0c;可能会导致很多冲突&#xff0c;使用命名空间的目的是对标识符的名称进行本地化&#xff0c;避免命名冲突或名字污染&…

C语言函数和数组

目录 一.数组 一.一维数组&#xff1a; 1.一维数组的创建: 2.一维数组的初始化&#xff1a; 3.一维数组的使用 4.一维数组在内存中的存储&#xff1a; 二.二维数组&#xff1a; 三.数组越界&#xff1a; 四.数组作为函数参数&#xff1a; 二.函数 一.函数是什么&…

vue3对openlayers使用(加高德,天地图图层)

OpenLayers认识 WebGIS四大框架&#xff1a; Leaflet、OpenLayers、Mapbox、Cesium OpenLayers 是一个强大的开源 JavaScript 地图库&#xff0c;专注于提供可嵌入网页的交互式地图体验。作为一款地理信息系统&#xff08;GIS&#xff09;的前端开发工具&#xff0c;OpenLaye…

关于php foreach函数和变量覆盖

foreach函数是PHP中用于遍历数组或对象的函数&#xff08;且仅用于数组的遍历&#xff09;。它允许循环遍历数组中的每个元素&#xff0c;并对每个元素执行相同的操作。foreach语句的基本语法如下&#xff1a; foreach ($array as $value) {//执行的操作 }在这个语法中&#x…

C++ Thread 源码 观后 自我感悟 整理

Thread的主要数据成员为_Thr 里面存储的是线程句柄和线程ID 先看看赋值运算符的移动构造 最开始判断线程的ID是否不为0 _STD就是使用std的域 如果线程ID不为0&#xff0c;那么就抛出异常 这里_New_val使用了完美转发&#xff0c;交换_Val和_New_val的值 _Thr _STD exchange(_…

回归预测 | Matlab基于SAO-LSTM雪消融算法优化长短期记忆神经网络的数据多输入单输出回归预测

回归预测 | Matlab基于SAO-LSTM雪消融算法优化长短期记忆神经网络的数据多输入单输出回归预测 目录 回归预测 | Matlab基于SAO-LSTM雪消融算法优化长短期记忆神经网络的数据多输入单输出回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab基于SAO-LSTM雪消融…

【Linux】进程的进一步认识

目录 进程的创建 fork函数初步认识 fork函数的返回值 写时拷贝 操作系统怎么知道什么时候要写时拷贝的呢&#xff1f; fork的常规用法 fork调用失败的原因 进程终止 进程的退出场景 进程常见退出方法 正常终止&#xff08;可以通过 echo $? 查看进程退出码&#xff…

Spring Boot从入门到实战

课程介绍 本课程从SpringBoot的最基础的安装、配置开始到SpringBoot的日志管理、Web业务开发、数据存储、数据缓存&#xff0c;安全控制及相关企业级应用&#xff0c;全程案例贯穿&#xff0c;案例每一步的都会讲解实现思路&#xff0c;全程手敲代码实现。让你不仅能够掌Sprin…

【Linux操作系统】:进程控制

目录 一、程序地址空间 1.C/C中的程序地址空间 2.进程地址空间 进程地址空间概念 什么是地址空间&#xff1f;什么是区域划分&#xff1f; 为啥要有地址空间&#xff1f; 地址空间的补充 二、进程创建 1.fork函数 2.写时拷贝 3.fork常规用法 4.fork调用失败的原因 …

Linux 常用命令 1

Tips&#xff1a;终端热键ctrl shift 放大终端窗口的字体 ctrl - 缩小终端窗口的字体 注意区分大小写 查阅命令帮助信息&#xff1a; 1&#xff09;--help command –help(两个减号) 显示command命令的帮助信息 2&#xff09;man man command 查阅command命令的使…

MyEclipse打开文件跳转到notepad打开问题

问题描述 windows系统打开README.md文件&#xff0c;每次都需要右键选择notepad打开&#xff0c;感觉很麻烦&#xff0c;然后就把README.md文件打开方式默认选择了notepad&#xff0c;这样每次双击就能打开&#xff0c;感觉很方便。 然后某天使用MyEclipse时&#xff0c;双击RE…

matlab实现神经网络检测手写数字

一、要求 1.计算sigmoid函数的梯度&#xff1b; 2&#xff0e;随机初始化网络权重&#xff1b; 3.编写网络的代价函数。 二、算法介绍 神经网络结构&#xff1a; 不正则化的神经网络的代价函数&#xff1a; 正则化&#xff1a; S型函数求导&#xff1a; 反向传播算法&…

【Linux】Linux工具学习之git

&#x1f525;博客主页&#xff1a; 小羊失眠啦. &#x1f3a5;系列专栏&#xff1a;《C语言》 《数据结构》 《C》 《Linux》 《Cpolar》 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 文章目录 前言一、账号注册1.1 GitHub与Gitee 二、构建仓库三、安装git 四、配置git五、克…

详解库和程序运行过程

我最近开了几个专栏&#xff0c;诚信互三&#xff01; > |||《算法专栏》&#xff1a;&#xff1a;刷题教程来自网站《代码随想录》。||| > |||《C专栏》&#xff1a;&#xff1a;记录我学习C的经历&#xff0c;看完你一定会有收获。||| > |||《Linux专栏》&#xff1…

lvgl 窗口 windows lv_port_win_visual_studio 版本 已解决

不知道的东西&#xff0c;不知道lvgl窗口。一切从未知开始 lv_port_win_visual_studio 主分支 对应的分支 v7版本更新git submodule update --init --recursive同步 lvgl代码随后打开 visualSudio 打开.sln 文件 编译 release模式 允许 一切正常代码部分

考研数学基础差,跟宋浩?

宋浩老师的课程我大一的时候听过&#xff0c;是我大一高数的救命恩人&#xff01; 不过&#xff0c;考研的针对性很强&#xff0c;基础差听宋浩老师的课程不如直接听汤家凤老师的课程&#xff0c;因为汤家凤老师的课程是专门为考研数学设计的&#xff0c;针对性很强。 汤家凤老…