RNN教程之-2 LSTM实战

作者: LucasJin | 来源:发表于2016-10-25 10:15 被阅读12255次

    本文由清华大学硕士大神金天撰写,欢迎大家转载,不过请保留这段版权信息,对本文内容有疑问欢迎联系作者微信:jintianiloveu探讨,多谢合作~

    UPDATE 2017-4-11

    这篇是之前写的文章,关于时间序列的更新版本在这里, 稍后会开源所有代码。

    前言

    说出来你们不敢相信,刚才码了半天的字,一个侧滑妈的全没了,都怪这Mac的触摸板太敏感沃日。好吧,不浪费时间了,前言一般都是废话,这个教程要解决的是一个LSTM的实战问题,很多人问我RNN是啥,有什么卵用,你可以看看我之前写的博客可以入门,但是如果你想实际操作代码,那么慢慢看这篇文章。本文章所有代码和数据集在我的Github Repository下载。

    问题

    给你一个数据集,只有一列数据,这是一个关于时间序列的数据,从这个时间序列中预测未来一年某航空公司的客运流量。

    首先我们数据预览一下,用pandas读取数据,这里我们只需要使用后一列真实数据,如果你下载了数据,数据大概长这样:

          time       passengers
    0    1949-01         112
    1    1949-02         118
    2    1949-03         132
    3    1949-04         129
    4    1949-05         121
    5    1949-06         135
    6    1949-07         148
    7    1949-08         148
    8    1949-09         136
    9    1949-10         119
    ...    ...          ....
    

    第一列是时间,第二列是客流量,为了看出这个我们要预测的客流量随时间的变化趋势,本大神教大家如何把趋势图画出来,接下来就非常牛逼了。用下面的代码来画图:

    import pandas as pd
    import numpy as np
    import matplotlib.pyplot as plt
    
    df = pd.read_csv('international-airline-passengers.csv', sep=',')
    df = df.set_index('time')
    df['passengers'].plot()
    plt.show()
    

    这时候我们可以看到如下的趋势图:

    figure_1.png

    可以看出,我们的数据存在一定的周期性,这个周期性并不是一个重复出现某个值,而是趋势的增长过程有一定的规律性,这个我们人肉眼就能看得出来,但是实际上计算机要识别这种规律就有一定的难度了,这时候就需要使用我们的LSTM大法。
    好的,数据已经预览完了,接下来我们得思考一下怎么预测,怎么把数据处理为LSTM网络需要的格式。

    LSTM数据预处理

    这个过程非常重要,这也是很多水平不高的博客或者文章中没有具体阐述而导致普通读者不知道毛意思的过程,其实我可以这样简单的叙述,LSTM你不要以为各种时间序列搞的晕头转向,其实本质它还是神经网络,与普通的神经网络没有任何区别。我们接下来就用几行小代码把数据处理为我们需要的类似于神经网络输入的二维数据。
    首先我们确确实实需要的只是一列数据:

    df = pd.read_csv(file_name, sep=',', usecols=[1])
    data_all = np.array(df).astype(float)
    print(data_all)
    

    输出是:

    [[ 112.]
     [ 118.]
     [ 132.]
     [ 129.]
     [ 121.]
     [ 135.]
     [ 148.]
     [ 148.]
     [ 136.]
     [ 119.]
     [ 104.]
     [ 118.]
     [ 115.]
    ....
    ]
    

    非常好,现在我们已经把我们需要的数据抠出来了,继续上面处理:

    data = []
    for i in range(len(data_all) - sequence_length - 1):
        data.append(data_all[i: i + sequence_length + 1])
    reshaped_data = np.array(data).astype('float64')
    print(reshaped_data)
    

    这时候你会发现好像结果看不懂,不知道是什么数据,如果你data_all处理时加ravel()(用来把数据最里面的中括号去掉),即:

    df = pd.read_csv(file_name, sep=',', usecols=[1])
    data_all = np.array(df).ravel().astype(float)
    print(data_all)
    

    那么数据输出一目了然:

    [[ 112.  118.  132. ...,  136.  119.  104.]
     [ 118.  132.  129. ...,  119.  104.  118.]
     [ 132.  129.  121. ...,  104.  118.  115.]
     ..., 
     [ 362.  405.  417. ...,  622.  606.  508.]
     [ 405.  417.  391. ...,  606.  508.  461.]
     [ 417.  391.  419. ...,  508.  461.  390.]]
    

    是的,没有错!一列数据经过我们这样不处理就可以作为LSTM网络的输入数据了,而且和神经网络没有什么两样!!牛逼吧?牛逼快去哥的Github Repo给个star,喊你们寝室的菜市场的大爷大妈都来赞!越多越好,快,哥的大牛之路就靠你们了!
    然而这还是只是开始。。接下来要做的就是把数据切分为训练集和测试集:

    split = 0.8
    np.random.shuffle(reshaped_data)
    x = reshaped_data[:, :-1]
    y = reshaped_data[:, -1]
    split_boundary = int(reshaped_data.shape[0] * split)
    train_x = x[: split_boundary]
    test_x = x[split_boundary:]
    
    train_y = y[: split_boundary]
    test_y = y[split_boundary:]
    

    这些步骤相信聪明的你一点看得懂,我就不多废话了,我要说明的几点是,你运行时直接运行Github上的脚本代码,如果报错请私信我微信jintianiloveu,我在代码中把过程包装成了函数所以文章中的代码可能不太一样。在实际代码中数据是需要归一化的,这个你应该知道,如何归一化代码中也有。

    搭建LSTM模型

    好,接下来是最牛逼的部分,也是本文章的核心内容(但实际内容并不多),数据有了,我们就得研究研究LSTM这个东东,不管理论上吹得多么牛逼,我只看它能不能解决问题,不管黑猫白猫,能抓到老鼠的就是好猫,像我们这样不搞伪学术注重经济效益的商人来说,这点尤为重要。搭建LSTM模型,我比较推荐使用keras,快速简单高效,分分钟,但是牺牲的是灵活性,不过话又说回来,真正的灵活性也是可以发挥的,只是要修改底层的东西那就有点麻烦了,我们反正是用它来解决问题的,更基础的部分我们就不研究了,以后有时间再慢慢深入。
    在keras 的官方文档中,说了LSTM是整个Recurrent层实现的一个具体类,它需要的输入数据维度是:

    形如(samples,timesteps,input_dim)的3D张量

    发现没有,我们上面处理完了数据的格式就是(samples,timesteps)这个time_step是我们采用的时间窗口,把一个时间序列当成一条长链,我们固定一个一定长度的窗口对这个长链进行采用,最终就得到上面的那个二维数据,那么我们缺少的是input_dim这个维度,实际上这个input_dim就是我们的那一列数据的数据,我们现在处理的是一列也有可能是很多列,一系列与时间有关的数据需要我们去预测,或者文本处理中会遇到。我们先不管那么多,先把数据处理为LSTM需要的格式:

    train_x = np.reshape(train_x, (train_x.shape[0], train_x.shape[1], 1))
    test_x = np.reshape(test_x, (test_x.shape[0], test_x.shape[1], 1))
    

    好的,这时候数据就是我们需要的啦。接下来搭建模型:

    # input_dim是输入的train_x的最后一个维度,train_x的维度为(n_samples, time_steps, input_dim)
    model = Sequential()
    model.add(LSTM(input_dim=1, output_dim=50, return_sequences=True))
    model.add(LSTM(100, return_sequences=False))
    model.add(Dense(output_dim=1))
    model.add(Activation('linear'))
    model.compile(loss='mse', optimizer='rmsprop')
    

    看到没,这个LSTM非常简单!!甚至跟输入的数据格式没有任何关系,只要输入数据的维度是1,就不需要修改模型的任何参数就可以把数据输入进去进行训练!
    我们这里使用了两个LSTM进行叠加,第二个LSTM第一个参数指的是输入的维度,这和第一个LSTM的输出维度并不一样,这也是LSTM比较“随意”的地方。最后一层采用了线性层。

    结果

    预测的结果如下图所示:


    result.png

    这个结果还是非常牛逼啊,要知道我们的数据是打乱过得噢,也就是说泛化能力非常不错,厉害了word LSTM!
    筒子们,本系列教程到此结束,欢迎再次登录老司机的飞船。。。。如果有不懂的私信我,想引起我的注意快去Github上给我star!!!

    系列文章结尾安利:Python深度学习基地群 216912253一个即谈理想又谈技术的技术人聚集地,欢迎加入。

    相关文章

      网友评论

      • 村雨Murasame:最为新入门的菜鸟,我觉得这篇文章很好,最大的意义是让我学会了代码,结果虽然经不起推敲但起码调动了兴趣。看了热评基本上都是在说train和test要严格区分,说明不够严谨。
        综上,代码要看才会用,同时原理也必须扎扎实实理解
      • LJC_ARROW:第二个LSTM第一个参数(100)指的是输出的维度,这里的输入为上一层的50
      • 韩韩小橙子:作者我想问一下,假如你的timesteps是7的话,按理来说每一个样本点应该有7个输出吧。也就是train_y,因为每一个时间步不应该有一个训练目标吗?
      • ArthurMLQ:同时作者还犯了第二个错误,就是在normalize data的时候,直接拿所有的data, data_all 去normalize, 但实际上正确的approach 应该是normalize train, 然后用train的parameter 去normalize test(如train的min, max, avg, std等取决于你的normalize 的方法,if train和test来自same underlying distribution)。因为用整个data set 去normalize 的话相当于提前获取了未来的信息(你的normalize 不光是取决于过去/train的data, 还和未来/test有关)
        韩韩小橙子:作者我想问一下,假如你的timesteps是7的话,按理来说每一个样本点应该有7个输出吧。也就是train_y,因为每一个时间步不应该有一个训练目标吗?
        ArthurMLQ:@Nicholas_Jela 在shuffle的情况下确实是的,但是shuffle本身就不合理,这样你部分train的数据就来自于未来了
        LucasJin:你的train和test本来就是从shuffle的data里面拿出来的,本质来说用train的方差和均值去归一化或者用整体的数据去归一化都差不多,只要你数量合理,但是同时你要也要考虑到为什么要归一化,就是为了充分考虑数据的统计特性,从这个角度来讲,数据越多的情况下计算的方差和均值越合理。
      • ArthurMLQ:首先感谢作者的分享,挺好的入门教程
        但是,针对大家都在问的randomize的问题, 我个人做了一个实验:先按时间顺序做train test split,然后再randomize train(逻辑上应该是一样的),去看看test上fit的结果。结果非常糟糕(至少在我的time series数据上)。
        实际上,我一开始看到这篇文章的时候就对randomize 产生了怀疑, 因为LSTM相比RNN, 最大的进步就在于它的gate 对于long term memory 和 working memory 中信息的保存和使用,但是randomize 以后完全就使这个部分无效化了。 但是作者在评论区里的解释并不能让我信服。所以我自己去做了这个尝试,对于结果,我给的一个猜测是: train的时候因为使用的是randomize的数据,所以数据是散布在各个时间段的。 好比说我的数据是15,16,17三年的,那么train和test里就都有这三年每个交易日的数据。 因此,当我们预测test的时候, 很有可能我们预测的时间接下来的时间(好比说我们去预测2017.7.16, 但是2017.7.17-7.20的数据在randomize 后都分配到train里面了)已经被Model给train过了,因此在fit test的时候,它的结果就会更好。事实上,如果先按时间顺序做train test split,不randomize 反而还比randomize 结果要更好(但如果只用time series数据,你很有可能得到的就是滞后一天的趋势)
        最后的最后,还是想分享一句我个人信奉的真理:丢进去的数据是shit,再强的模型,出来的结果还是shit
        希望这条评论能够对所有以后看到这篇文章的人有所帮助
        张桓铭:作者的打乱数据,本质造成了历史和未来的混淆,实际是用到了未来的数据预测趋势,overfitting
        ArthurMLQ:@zeekzhen 因为你丢进去的只有时间序列,好比说股价的时间序列, 影响每一天价格的因素除了t-1的价格之外,还有太多的东西了,好比说公司的财务指标,市场环境等等,可是这些在你的模型中都并没有体现
        zeekzhen:你说 :(但如果只用time series数据,你很有可能得到的就是滞后一天的趋势)

        是的,我也遇到这个奇怪的现象,我构建了一个LSTM网络, 使用单序列输入预测单序列的下一个数的时候,经过几千步训练之后,输出几乎等于输入。 而不是下一天的数据,请问该问题怎么解决。 查了好久都无解
      • 鱼肠小问:LSTM做点预测确实还行,但现实中我们通常都是预测未来一段时间的数据,一般来说就是先基于历史预测,然后根据预测继续预测。这时候LSTM好像会超级差?我做出来的结果是Arima之类的传统方法要远远优于LSTM,难道是数据量不够?希望可以交流一下看法
      • 快乐女孩_da87:大神 我想问下time steps什么意思?你这个序列是预测未来一个月的 那如果我想预测未来5个月的值怎么改
      • b828f054977e:那个,表示看了一遍源代码,表示这是按点预测吧?每一个点的预测都有10个真实历史数据支撑对吧?这样是不是没有什么实际价值呀?
        LucasJin:@喵_a65a 可以一个数据一个数据生成
      • 9327483b7137:根据源代码自己跑了一下,结果却是还不错,但是在打乱之后和没打乱的结果确实差距很大,还有一个小问题就是,如果打乱了数据集,那么如何体现方法的泛化能力呢,对于我想预测的未来值并没有进行预测,而是在数据集中随机拿到一部分数据进行了预测,希望作者能够帮我理解下这个问题。
      • 不会停的蜗牛:文风好幽默呀,而且敲自信,怎么做到的,学习学习:blush:
      • b82f2414fdcb:你好,方便加一下微信请教你一下吗
      • 9f0864618eac:大神,最近研究lstm遇到问题,请问,如何在deeplearning官方的lstm代码中,使用自己的数据集或者stanford Sentiment Treebank的数据集呢
        LucasJin:你在说啥?风太大听不清....
      • 吃饭睡觉和豆豆:大神,我这里不明白:
        for i in range(len(data_all) - sequence_length - 1):
        这里的sequence_length是什么呢?
        吃饭睡觉和豆豆:看到源代码了
      • 95bc2039fc41:非常简单的教程,git已经stars了,
        我有个小问题,一直没想明白,是关于
        np.random.shuffle(reshaped_data)
        数据打乱。
        如果注释了这行, 结果会差很多。为什么要打乱数据,才可以训练到好的模型呢。
        95bc2039fc41:@Nicholas_Jela 我也思考了一下,我的理解是为了让梯度下降算法的效率更高
        LucasJin:感谢认真阅读,打乱顺序你自习思考下,其实时间序列变成二维数据之后,再打乱是很合乎情理的,打乱并不会改变原始时间序列的序列关系,我们可以理解为把原始时间序列切割成了一个个小段,每个小段相互独立,我们打乱之能够充分学习到素有小段间的关系
      • FengYabing:大神,我想请问下,如果我的输入数据是传统的二维数据(行代表样本个数,列代表特征个数),运行lstm之前,需要怎样做处理才能够做成lstm能够识别的输入?
        95bc2039fc41:model.add(
        LSTM(
        input_dim=4, # 数据的维度, 统计学中的自变量, 数据库中的列
        input_length=1, # 记录数, 数据库中的行, 每几行数据描述一个因变量
        output_dim=hidden_unit, # 这个层包含隐含单元的个数
        activation=fun_act.tanh, # 激励函数
        return_sequences=False, # True 返回一个序列给下一层, False 返回最后一个值
        # inner_activation='hard_sigmoid',
        )
        )
        FengYabing:@Nicholas_Jela 我的理解是不是(samples*features)格式的数据,用lstm训练时,如果sequence_length设置为10的话,是不是将原始数据转化为 x=(samples*sequence_length*features) y=(samples*label)的形式就可以使用lstm训练哈。只是你这篇文章里面features就是1
        LucasJin:稍微仔细看一下这个文章你就能明白
      • 板儿玉Ben笨鱼:请问,时序预测为什么要打乱输入顺序呢?
        np.random.shuffle(reshaped_data)
        假设一个平稳的时间序列,如果输入是乱序的,那LSTM根据什么能做出准确的预测呢?
        多谢
        LucasJin:打乱lstm输入训练数据并没有改变时序的关系,你把lstm想象成一个结巴,它说话总是要重复前面一句话的5个字符,这样它说的每一句话实际上就包含了这个时序关系
      • cb71595ab85f:你好,你的model中没有加入Dropout层,会不会出现过拟合?还有那个LSTM的input_dim是怎么确定的?怎么确定model中输入层的个数、隐藏层的个数,输出层的个数,是不是input_dim的大小就代表各层的个数? :smiley:
        慧慧慧慧1107:请问下如果不止一列数据的话要怎么设计输入输出
        cb71595ab85f:@Nicholas_Jela 好的,谢谢大神。
        LucasJin:@再没 这只是个简易教程,实际做的话肯定要考虑这些东西的,input_dim?我好像忘记了时间太过久远

      本文标题:RNN教程之-2 LSTM实战

      本文链接:https://www.haomeiwen.com/subject/eppluttx.html