时间序列神器之争:prophet VS lstm

一、需求背景

我们福禄网络致力于为广大用户提供智能化充值服务,包括各类通信充值卡(比如移动、联通、电信的话费及流量充值)、游戏类充值卡(比如王者荣耀、吃鸡类点券、AppleStore充值、Q币、斗鱼币等)、生活服务类(比如肯德基、小鹿茶等),网娱类(比如QQ各类钻等),作为一个服务提供商,商品质量的稳定、持续及充值过程的便捷一直是我们在业内的口碑。
在整个商品流通过程中,如何做好库存的管理,以充分提高库存运转周期和资金使用效率,一直是个难题。基于此,我们提出了智能化的库存管理服务,根据订单数据及商品数据,来预测不同商品随着时间推移的日常消耗情况。

二、算法选择

目前成熟的时间序列预测算法很多,但商业领域性能优越的却不多,经过多种尝试,给大家推荐2种时间序列算法:facebook开源的Prophet算法和LSTM深度学习算法。
现将个人理解的2种算法特性予以简要说明:

  • (1)、在训练时间上,prophet几十秒就能出结果,而lstm往往需要1个半小时,更是随着网络层数和特征数量的增加而增加。

  • (2)、Prophet是一个为商业预测而生的时间序列预测模型,因此在很多方便都有针对性的优化,而lstm的初衷是nlp。

  • (3)、Prophet无需特征处理即可使用,参数调优也明确简单。而lstm则需要先进行必要的特征处理,其次要进行正确的网络结构设计,因此lstm相对prophet更为复杂。

  • (4)、Lstm需要更多的数据进行学习,否则无法消除欠拟合的情形。而prophet不同,prophet基于统计学,有完整的数学理论支撑,因此更容易从少量的数据中完成学习。

  • (5)、传统的时间序列预测算法只支持单纬度,但LSTM能支持多纬度,也就是说LSTM能考虑促销活动,目标用户特性,产品特性等

三、数据来源

  • (1)、订单数据

  • (2)、产品分类数据

四、数据形式

time,product,cnt2019-10-01 00,**充值,62019-10-01 00,***游戏,3682019-10-01 00,***,12019-10-01 00,***,112019-10-01 00,***游戏,172019-10-01 00,三网***,392019-10-01 00,**网,62019-10-01 00,***,2

字段说明:

  • Time:小时级时间

  • Product:产品名称或产品的分类名称,目前使用的是产品2级分类,名称

  • Cnt:成功订单数量
    目前的时间序列是由以上time和cnt组成,product是用于区分不同时间序列的字段。

五、特征处理

时间序列一般不进行特征处理,当然可以根据具体情况进行归一化处理或是取对数处理等。

六、算法选择

目前待选的算法主要有2种:

  • (1)、Prophet
    Facebook开源的时间序列预测算法,考虑了节假日因素。

  • (2)、LSTM
    优化后的RNN深度学习算法。

七、算法说明

7.1 prophet

7.1.1Prophet的核心是调参,步骤如下:
  • 1、首先我们去除数据中的异常点(outlier),直接赋值为none就可以,因为Prophet的设计中可以通过插值处理缺失值,但是对异常值比较敏感。

  • 2、选择趋势模型,默认使用分段线性的趋势,但是如果认为模型的趋势是按照log函数方式增长的,可设置growth=’logistic’从而使用分段log的增长方式

  • 3、 设置趋势转折点(changepoint),如果我们知道时间序列的趋势会在某些位置发现转变,可以进行人工设置,比如某一天有新产品上线会影响我们的走势,我们可以将这个时刻设置为转折点。如果自己不设置,算法会自己总结changepoint。

  • 4、 设置周期性,模型默认是带有年和星期以及天的周期性,其他月、小时的周期性需要自己根据数据的特征进行设置,或者设置将年和星期等周期关闭。
    设置节假日特征,如果我们的数据存在节假日的突增或者突降,我们可以设置holiday参数来进行调节,可以设置不同的holiday,例如五一一种,国庆一种,影响大小不一样,时间段也不一样。

  • 5、 此时可以简单的进行作图观察,然后可以根据经验继续调节上述模型参数,同时根据模型是否过拟合以及对什么成分过拟合,我们可以对应调节seasonality_prior_scale、holidays_prior_scale、changepoint_prior_scale参数。

    以上是理论上的调参步骤,但我们在实际情况下在建议使用grid_search(网格寻参)方式,直接简单效果好。当机器性能不佳时网格调参配合理论调参方法可以加快调参速度。建议初学者使用手动调参方式以理解每个参数对模型效果的影响。

holiday.csv

import pandas as pdimport numpy as npimport matplotlib.pyplot as pltfrom fbprophet import Prophetdata = pd.read_csv('../data/data2.csv', parse_dates=['time'], index_col='time')def get_product_data(name, rule=None):product = data[data['product'] == name][['cnt']]product.plot()if rule is not None:product = product.resample(rule).sum()product.reset_index(inplace=True)product.columns = ['ds', 'y']return productholidays = pd.read_csv('holiday.csv', parse_dates=['ds'])holidays['lower_window'] = -1holidays = holidays.append(pd.DataFrame({'holiday': '双11','ds': pd.to_datetime(['2019-11-11', '2020-11-11']),'lower_window': -1,'upper_window': 1,})).append(pd.DataFrame({'holiday': '双12','ds': pd.to_datetime(['2019-12-12', '2020-12-12']),'lower_window': -1,'upper_window': 1,}))
def predict(name, rule='1d', freq='d', periods=1, show=False):ds = get_product_data(name, rule=rule)if ds.shape[0] < 7:return Nonem = Prophet(holidays=holidays)m.fit(ds)future = m.make_future_dataframe(freq=freq, periods=periods)  # 建立数据预测框架,数据粒度为天,预测步长为一年forecast = m.predict(future)if show:m.plot(forecast).show()  # 绘制预测效果图m.plot_components(forecast).show()  # 绘制成分趋势图mse = forecast['yhat'].iloc[ds.shape[0]] - ds['y'].valuesmse = np.abs(mse) / (ds['y'].values + 1)return [name, mse.mean(), mse.max(), mse.min(), np.quantile(mse, 0.9), np.quantile(mse, 0.8), mse[-7:].mean(),ds['y'].iloc[-7:].mean()]if __name__ == '__main__':products = set(data['product'])p = []for i in products:y = predict(i)if y is not None:p.append(y)df = pd.DataFrame(p, columns=['product', 'total_mean', 'total_max', 'total_min', '0.9', '0.8', '7_mean','7_real_value_mean'])df.set_index('product', inplace=True)product_sum: pd.DataFrame = data.groupby('product').sum()df = df.join(product_sum)df.sort_values('cnt', ascending=False, inplace=True)df.to_csv('result.csv', index=False)

结果如下:由于行数较多这里只展示前1行
||product||total_mean||total_max||total_min||0.9||0.8||7_mean||7_real_value_mean||
||*||0.361079179||0.777714004||0.333142011||0.333715979||0.333715979||0.396816286||1||
根据结果,对比原生数据,可以得出如下结论:
就算法与产品的匹配性可分为3个类型:

  • (1)与算法较为匹配,算法的历史误差8分为数<=0.2的

  • (2)与算法不太匹配的,算法的历史误差8分为数>0.2的

  • (3)数据过少的,无法正常预测的。目前仅top10就能占到整体订单数的90%以上。

7.1.2 部分成果展示

A. 因素分解图

上图中主要分为3个部分,分别对应prophet 3大要素,趋势、节假日或特殊日期、周期性(包括年周期、月周期、week周期、天周期以及用户自定义的周期)
下面依照上面因素分解图的顺序依次对图进行说明:

  • (1)、Trend:
    即趋势因素图。描述时间序列的趋势。Prophet支持线性趋势和logist趋势。通过growth参数设置,当然模型能自己根据时间序列的走势判断growth类型。这也是prophet实现的比较智能的一点。

  • (2)、Holidays
    即节假日及特殊日期因素图。描述了节假日及用户自定义的特殊日期对时间序列的影响。正值为正影响,负值为负影响。从图中可以看出这个商品对节假日比较敏感。节假日是根据holidays参数设置的。

  • (3)、weekly
    星期周期性因素图。正常情况下,如果是小时级别数据将会有天周期图。有1年以上完整数据并且时间序列有典型的年周期性会有年周期图。如果你觉得这个有年周期,但模型并不这么认为,你可以通过设置yearly_seasonality设置一个具体的数值。这个数值默认情况下为10(weekly_seasonality默认为3),这个值代表的是傅里叶级数的项数,越大模型越容易过拟合,过小则会导致欠拟合,一般配合seasonality_prior_scale使用。
    B.预测曲线与实际值对比

7.2 lstm

LSTM(长短记忆网络)主要用于有先后顺序的序列类型的数据的深度学习网络。是RNN的优化版本。一般用于自然语言处理,也可用于时间序列的预测。

简单来说就是,LSTM一共有三个门,输入门,遗忘门,输出门, i 、o、 f 分别为三个门的程度参数, g 是对输入的常规RNN操作。公式里可以看到LSTM的输出有两个,细胞状态c 和隐状态 h,c是经输入、遗忘门的产物,也就是当前cell本身的内容,经过输出门得到h,就是想输出什么内容给下一单元。

import numpy as npimport pandas as pdimport matplotlib.pyplot as pltimport torchfrom torch import nnfrom sklearn.preprocessing import MinMaxScalerts_data = pd.read_csv('../data/data2.csv', parse_dates=['time'], index_col='time')def series_to_supervised(data, n_in=1, n_out=1, dropnan=True):n_vars = 1 if type(data) is list else data.shape[1]df = pd.DataFrame(data)cols, names = list(), list()# input sequence (t-n, ... t-1)for i in range(n_in, 0, -1):cols.append(df.shift(i))names += [('var%d(t-%d)' % (j + 1, i)) for j in range(n_vars)]# forecast sequence (t, t+1, ... t+n)for i in range(0, n_out):cols.append(df.shift(-i))if i == 0:names += [('var%d(t)' % (j + 1)) for j in range(n_vars)]else:names += [('var%d(t+%d)' % (j + 1, i)) for j in range(n_vars)]# put it all togetheragg = pd.concat(cols, axis=1)agg.columns = names# drop rows with NaN valuesif dropnan:agg.dropna(inplace=True)return aggdef transform_data(feature_cnt=2):yd = ts_data[ts_data['product'] == '移动话费'][['cnt']]scaler = MinMaxScaler(feature_range=(0, 1))yd_scaled = scaler.fit_transform(yd.values)yd_renamed = series_to_supervised(yd_scaled, n_in=feature_cnt).values.astype('float32')n_row = yd_renamed.shape[0]n_train = int(n_row * 0.7)train_X, train_y = yd_renamed[:n_train, :-1], yd_renamed[:n_train, -1]test_X, test_y = yd_renamed[n_train:, :-1], yd_renamed[n_train:, -1]# 最后,我们需要将数据改变一下形状,因为 RNN 读入的数据维度是 (seq, batch, feature),所以要重新改变一下数据的维度,这里只有一个序列,所以 batch 是 1,而输入的 feature 就是我们希望依据的几天,这里我们定的是两个天,所以 feature 就是 2.train_X = train_X.reshape((-1, 1, feature_cnt))test_X = test_X.reshape((-1, 1, feature_cnt))print(train_X.shape, train_y.shape, test_X.shape, test_y.shape)# 转化成torch 的张量train_x = torch.from_numpy(train_X)train_y = torch.from_numpy(train_y)test_x = torch.from_numpy(test_X)test_y = torch.from_numpy(test_y)return scaler, train_x, train_y, test_x, test_yscaler, train_x, train_y, test_x, test_y = transform_data(24)# lstm 网络class lstm_reg(nn.Module):  # 括号中的是python的类继承语法,父类是nn.Module类 不是参数的意思def __init__(self, input_size, hidden_size, output_size=1, num_layers=2):  # 构造函数# inpu_size 是输入的样本的特征维度, hidden_size 是LSTM层的神经元个数,# output_size是输出的特征维度super(lstm_reg, self).__init__()  # super用于多层继承使用,必须要有的操作self.rnn = nn.LSTM(input_size, hidden_size, num_layers)  # 两层LSTM网络,self.reg = nn.Linear(hidden_size, output_size)  # 把上一层总共hidden_size个的神经元的输出向量作为输入向量,然后回归到output_size维度的输出向量中def forward(self, x):  # x是输入的数据x, _ = self.rnn(x)  # 单个下划线表示不在意的变量,这里是LSTM网络输出的两个隐藏层状态s, b, h = x.shapex = x.view(s * b, h)x = self.reg(x)x = x.view(s, b, -1)  # 使用-1表示第三个维度自动根据原来的shape 和已经定了的s,b来确定return xdef train(feature_cnt, hidden_size, round, save_path='model.pkl'):# 我使用了GPU加速,如果不用的话需要把.cuda()给注释掉net = lstm_reg(feature_cnt, hidden_size)criterion = nn.MSELoss()optimizer = torch.optim.Adam(net.parameters(), lr=1e-2)for e in range(round):# 新版本中可以不使用Variable了#     var_x = Variable(train_x).cuda()#     var_y = Variable(train_y).cuda()# 将tensor放在GPU上面进行运算var_x = train_xvar_y = train_yout = net(var_x)loss = criterion(out, var_y)optimizer.zero_grad()loss.backward()optimizer.step()if (e + 1) % 100 == 0:print('Epoch: {}, Loss:{:.5f}'.format(e + 1, loss.item()))# 存储训练好的模型参数torch.save(net.state_dict(), save_path)return netif __name__ == '__main__':net = train(24, 8, 5000)# criterion = nn.MSELoss()# optimizer = torch.optim.Adam(net.parameters(), lr=1e-2)pred_test = net(test_x)  # 测试集的预测结果pred_test = pred_test.view(-1).data.numpy()  # 先转移到cpu上才能转换为numpy# 乘以原来归一化的刻度放缩回到原来的值域origin_test_Y = scaler.inverse_transform(test_y.reshape((-1,1)))origin_pred_test = scaler.inverse_transform(pred_test.reshape((-1,1)))# 画图plt.plot(origin_pred_test, 'r', label='prediction')plt.plot(origin_test_Y, 'b', label='real')plt.legend(loc='best')plt.show()# 计算MSE# loss = criterion(out, var_y)?true_data = origin_test_Ytrue_data = np.array(true_data)true_data = np.squeeze(true_data)  # 从二维变成一维MSE = true_data - origin_pred_testMSE = MSE * MSEMSE_loss = sum(MSE) / len(MSE)print(MSE_loss)

八、两种算法的比较

  • (1)在训练时间上,prophet几十秒就能出结果,而lstm往往需要1个半小时,更是随着网络层数和特征数量的增加而增加。

  • (2)Prophet是一个为商业预测而生的时间序列预测模型,因此在很多方便都有针对性的优化,而lstm的初衷是nlp。

  • (3)Prophet无需特征处理即可使用,参数调优也明确简单。而lstm则需要先进行必要的特征处理,其次要进行正确的网络结构设计,因此lstm相对prophet更为复杂。

  • (4)Lstm需要更多的数据进行学习,否则无法消除欠拟合的情形。而prophet不同,prophet基于统计学,有完整的数学理论支撑,因此更容易从少量的数据中完成学习。
    参考文献:
    【1】Prophet官方文档:https://facebook.github.io/prophet/
    【2】Prophet论文:https://peerj.com/preprints/3190/
    【3】Prophet-github:https://github.com/facebook/prophet
    【4】LSTM http://colah.github.io/posts/2015-08-Understanding-LSTMs/
    【5】基于LSTM的关联时间序列预测方法研究 尹康 《北京交通大学》 2019年 cnki地址:http://cdmd.cnki.com.cn/Article/CDMD-10004-1019209125.htm

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

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

相关文章

docker registry 镜像同步

docker registry 镜像同步Intro之前我们的 docker 镜像是保存在 Azure 的 Container Registry 里的&#xff0c;最近我们自己搭建了一个 docker registry&#xff0c;我们想把之前保存的 Azure 的 Container Registry 的 docker 镜像同步到我们自己的 docker registry 里实现思…

[数据结构-严蔚敏版]P37定义一个带头结点的线性链表

大家如果发现代码有错误&#xff0c;麻烦评论告知一下!!! 代码我已经发现存在部分错误&#xff0c;等有时间我再进行修正。目前存在错误:mergeList_L函数有问题! 代码如下: #include <iostream> using namespace std;typedef struct LNode {int data;LNode *next; }*L…

.NET Core加解密实战系列之——RSA非对称加密算法

简介加解密现状&#xff0c;编写此项目的背景&#xff1a;需要考虑系统环境兼容性问题&#xff08;Linux、Windows&#xff09;语言互通问题&#xff08;如C#、Java&#xff09;网上资料版本不一、不全面.NET官方库密码算法提供不全面&#xff0c;很难针对其他语言&#xff08;…

[数据结构-严蔚敏版]P42多项式Polynomial的实现

大家如果发现代码有错误&#xff0c;麻烦评论告知一下!!! 下面的代码多项式相乘的算法实现并不是书上那种&#xff0c;书上那种我实在是看不懂&#xff0c;所以用了自己的方法写了相乘的算法&#xff0c;就是模拟手算过程&#xff0c;一个一个相乘。 代码如下: #include <…

number five

一、外单履行域业务熟悉 申请了各种权限 看了部分三号项目 熟悉测试流程&#xff0c;例如履行的一些工具&#xff0c;只是熟悉了平台&#xff0c;因为部分权限未申请&#xff0c;没有操作过测试。 对于三号项目概念还没成体系&#xff0c;但了解了相关测试任务。 中间件hsf…

.Net Core实战之基于角色的访问控制的设计

前言上个月&#xff0c;我写了两篇微服务的文章&#xff1a;《.Net微服务实战之技术架构分层篇》与《.Net微服务实战之技术选型篇》&#xff0c;微服务系列原有三篇&#xff0c;当我憋第三篇的内容时候一直没有灵感&#xff0c;因此先打算放一放。本篇文章与源码原本打算实在去…

[数据结构-严蔚敏版]P46栈的顺序存储表示

大家如果发现代码有错误&#xff0c;麻烦评论告知一下!!! 代码如下: #include <iostream> using namespace std;const int STACK_INIT_SIZE 100; const int STACKINCREMENT 10;typedef int ElemType;typedef struct {ElemType *base;ElemType *top;int stacksize; }S…

ProcessOn使用

文章目录1. 拥有一个账号;2.思维导图&#xff1a;快速添加主题&#xff1a;移动主题位置和排序用格式刷来复制主题样式善用右键菜单快捷键后续使用时再补充 ^ ^1. 拥有一个账号; 2.思维导图&#xff1a; 快速添加主题&#xff1a; 移动主题位置和排序 用格式刷来复制主题样式…

基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(六)

系列文章使用 abp cli 搭建项目给项目瘦身&#xff0c;让它跑起来完善与美化&#xff0c;Swagger登场数据访问和代码优先自定义仓储之增删改查统一规范API&#xff0c;包装返回模型再说Swagger&#xff0c;分组、描述、小绿锁接入GitHub&#xff0c;用JWT保护你的API异常处理和…

[数据结构-严蔚敏版]P48栈的链式表示

代码如下: #include <iostream> using namespace std;typedef int ElemType;typedef struct SNode {ElemType data;SNode *next; }SNode;typedef struct {SNode *top; }SqStack;bool initStack(SqStack &s) {s.top nullptr;return true; }bool destroyStack(SqStac…

还有多少人不会用K8s?.NET高级高薪岗,开始要求了!

毫无疑问&#xff0c;Kubernetes已经成为容器编排事实标准。除了已经拥抱Kubernetes的Google、BAT、京东、奇虎360等巨头大厂外&#xff0c;更多的企业也都在向Kubernetes迁移。容器技术大势所趋&#xff0c;是互联网企业目前急需的技术人才之一&#xff0c;已成为运维工程师、…

[数据结构-严蔚敏版]P61ADT Queue的表示与实现(单链队列-队列的链式存储结构)

代码如下: #include <iostream> using namespace std;typedef int ElemType;typedef struct QNode {ElemType data;QNode *next; }QNode ,*QueuePtr;typedef struct {QueuePtr front;QueuePtr rear; }LinkQueue;bool initQueue(LinkQueue &q) {q.front q.rear new…

基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(七)

系列文章使用 abp cli 搭建项目给项目瘦身&#xff0c;让它跑起来完善与美化&#xff0c;Swagger登场数据访问和代码优先自定义仓储之增删改查统一规范API&#xff0c;包装返回模型再说Swagger&#xff0c;分组、描述、小绿锁接入GitHub&#xff0c;用JWT保护你的API异常处理和…

[数据结构-严蔚敏版]P64循环队列-队列的顺序存储结构

代码如下: #include <iostream> using namespace std;const int MAXQSIZE 10;typedef int ElemType;typedef struct {ElemType *base;int front;int rear; }SqQueue;bool initQueue(SqQueue &q) {q.base new ElemType[MAXQSIZE];if (!q.base) return false;q.fron…

BeetleX.FastHttpApi之Vuejs扩展

非常喜欢用vuejs,但又不想花时间去搞nodejs和webpack之类的&#xff0c;所以才有了BeetleX.FastHttpApi.VueExtend这样一个组件&#xff1b;组件的主要功能就是可以实现在vs.net中编写*.vue并直接引用到服务中&#xff0c;这样对于我这个习惯在vs.net写服务应的带来极大的方便性…

C++的new、delete需要注意的一点:使用危险函数导致的越界CRT detected that the application wrote to memory after end of heap

new、delete需要注意的一个特性 正常情况new一个数组之后&#xff0c;用delete释放是没有问题的。但是当对new得到的堆区进行越界的写入操作&#xff08;读操作不会&#xff09;将会导致delete时出现段错误&#xff0c;无法进行删除。如下面的程序所示&#xff1a; 数组大小只…

数据结构与算法专题——第四题 字符串相似度

这篇我们看看 最长公共子序列 的另一个版本&#xff0c;求字符串相似度(编辑距离)&#xff0c;我也说过了&#xff0c;这是一个非常实用的算法&#xff0c;在DNA对比&#xff0c;网页聚类等方面都有用武之地。一&#xff1a;概念对于两个字符串 A 和 B&#xff0c;通过基本的增…

[数据结构-严蔚敏版]P71串的抽象数据类型的定义

代码如下: #include <iostream> #include <string> using namespace std;typedef struct {char *ch;int length; }String;bool initString(String &s) {s.ch nullptr;s.length 0;return true; }bool strAssign(String &s, const char *ch) {int len st…

三分钟学会.NET Core Jwt 策略授权认证

一.前言大家好我又回来了&#xff0c;前几天讲过一个关于Jwt的身份验证最简单的案例&#xff0c;但是功能还是不够强大&#xff0c;不适用于真正的项目&#xff0c;是的&#xff0c;在真正面对复杂而又苛刻的客户中&#xff0c;我们会不知所措&#xff0c;就现在需要将认证授权…

[数据结构-严蔚敏版]P65离散事件模拟(银行客户的离散事件驱动模拟程序)

写这个简单玩意&#xff0c;居然花费了我6小时&#xff0c;唉!!!&#xff0c;还是太菜了! 中间已经起了放弃的念头了&#xff0c;最后还是坚持下来了! 总结&#xff1a; (1)漏了p p->next (2)队列删除元素的时候&#xff0c;删除的是最后一个忘记特判。 (3)写的时候太急了…