目录

  • 递归神经网络
  • 基础概念
  • 长时依赖问题
  • LSTM神经网络
  • RNN具体结构
  • LSTM结构
  • LSTM解析
  • 利用LSTM进行时间序列分析预测
  • 时间序列
  • 实战


递归神经网络

基础概念

不只是关注当前数据,还会根据过去的数据做推测,具有记忆功能。

传统神经网络没有记忆功能,RNN递归神经网络有记忆功能。

递归神经网络的结果与传统神经网络有一些不同,它带有一个指向自身的环,用来表示它可以传递当前时刻处理的信息给下一时刻使用,结构如下:

单层的GRU循环神经网络 循环神经网络lstm_递归


为了更容易地说明递归神经网络,我们把上图展开,得到:

单层的GRU循环神经网络 循环神经网络lstm_神经网络_02


这样的一条链状神经网络代表了一个递归神经网络,可以认为它是对相同神经网络的多重复制,每一时刻的神经网络会传递信息给下一时刻。如何理解它呢?假设有这样一个语言模型,我们要根据句子中已出现的词预测当前词是什么,递归神经网络的工作原理如下:

单层的GRU循环神经网络 循环神经网络lstm_LSTM_03


递归神经网络因为具有一定的记忆功能,可以被用来解决很多问题,例如:语音识别、语言模型、机器翻译等,但是它并不能很好地处理长时依赖问题。

长时依赖问题

长时依赖是这样的一个问题,当预测点与依赖的相关信息距离比较远的时候,就难以学到该相关信息。例如在句子”我出生在法国,……,我会说法语“中,若要预测末尾”法语“,我们需要用到上下文”法国“。理论上,递归神经网络是可以处理这样的问题的,但是实际上,常规的递归神经网络并不能很好地解决长时依赖,好的是LSTM可以很好地解决这个问题。

LSTM神经网络

RNN具体结构

Long Short Term Mermory network(LSTM)是一种特殊的RNN,可以很好地解决长时依赖问题。那么它与常规神经网络有什么不同?

首先我们来看RNNs具体一点的结构:

单层的GRU循环神经网络 循环神经网络lstm_机器学习_04


所有的递归神经网络都是由重复神经网络模块构成的一条链,可以看到它的处理层非常简单,通常是一个单tanh层,通过当前输入及上一时刻的输出来得到当前输出。与神经网络相比,经过简单地改造,它已经可以利用上一时刻学习到的信息进行当前时刻的学习了。

LSTM结构

LSTM的结构与上面相似,不同的是它的重复模块会比较复杂一点,它有四层结构:

单层的GRU循环神经网络 循环神经网络lstm_机器学习_05


其中,处理层出现的符号及表示意思如下:

单层的GRU循环神经网络 循环神经网络lstm_递归_06


LSTM的核心思想

理解LSTMs的关键就是下面的矩形方框,被称为memory block(记忆块),主要包含了三个门(forget gate、input gate、output gate)与一个记忆单元(cell)。方框内上方的那条水平线,被称为cell state(单元状态),它就像一个传送带,可以控制信息传递给下一时刻。

LSTM可以通过门控单元可以对cell添加和删除信息。通过门可以有选择地决定信息是否通过,它有一个sigmoid神经网络层和一个成对乘法操作组成,如下:

单层的GRU循环神经网络 循环神经网络lstm_神经网络_07


该层的输出是一个介于0到1的数,表示允许信息通过的多少,0 表示完全不允许通过,1表示允许完全通过。

LSTM解析

LSTM第一步是用来决定什么信息可以通过cell state。这个决定由“forget gate”层通过sigmoid来控制,它会根据上一时刻的输出单层的GRU循环神经网络 循环神经网络lstm_机器学习_08和当前输入单层的GRU循环神经网络 循环神经网络lstm_递归_09来产生一个0到1 的单层的GRU循环神经网络 循环神经网络lstm_机器学习_10值,来决定是否让上一时刻学到的信息单层的GRU循环神经网络 循环神经网络lstm_机器学习_11通过或部分通过。如下:

单层的GRU循环神经网络 循环神经网络lstm_单层的GRU循环神经网络_12


第二步是产生我们需要更新的新信息。这一步包含两部分,第一个是一个“input gate”层通过sigmoid来决定哪些值用来更新,第二个是一个tanh层用来生成新的候选值Ct,它作为当前层产生的候选值可能会添加到cell state中。我们会把这两部分产生的值结合来进行更新。

单层的GRU循环神经网络 循环神经网络lstm_单层的GRU循环神经网络_13


一二步结合起来就是丢掉不需要的信息,添加新信息的过程:

举个例子就是,在前面的句子中我们保存的是张三的信息,现在有了新的李四信息,我们需要把张三的信息丢弃掉,然后把李四的信息保存下来

单层的GRU循环神经网络 循环神经网络lstm_机器学习_14


最后一步是决定模型的输出,首先是通过sigmoid层来得到一个初始输出,然后使用tanh将 Ct值缩放到-1到1间,再与sigmoid得到的输出逐对相乘,从而得到模型的输出。

单层的GRU循环神经网络 循环神经网络lstm_神经网络_15


这显然可以理解,首先sigmoid函数的输出是不考虑先前时刻学到的信息的输出,tanh函数是对先前学到信息的压缩处理,起到稳定数值的作用,两者的结合学习就是递归神经网络的学习思想。至于模型是如何学习的,那就是后向传播误差学习权重的一个过程了。

利用LSTM进行时间序列分析预测

时间序列

时间序列构成要素:长期趋势,季节变动,循环变动,不规则变动

  • 长期趋势( T )现象在较长时期内受某种根本性因素作用而形成的总的变动趋势
  • 季节变动( S )现象在一年内随着季节的变化而发生的有规律的周期性变动
  • 循环变动( C )现象以若干年为周期所呈现出的波浪起伏形态的有规律的变动
  • 不规则变动(I )是一种无规律可循的变动,包括严格的随机变动和不规则的突发性影响很大的变动两种类型

实战

  1. 原始时间序列数据(部分)
    1455.219971
    1399.420044
    1402.109985
    1403.449951
    1441.469971
    1457.599976
    1438.560059
    1432.25
    1449.680054
    1465.150024
    1455.140015
    1455.900024
    1445.569946
    1441.359985
    1401.530029
    1410.030029
    1404.089966
    1398.560059
  2. 处理数据
def load_data(filename, seq_len):
    f = open(filename, 'rb').read()
    data = f.split('\n')

    print('data len:',len(data))       #4172
    print('sequence len:',seq_len)     #50

    sequence_length = seq_len + 1
    result = []
    for index in range(len(data) - sequence_length):
        result.append(data[index: index + sequence_length])  #得到长度为seq_len+1的向量,最后一个作为label

    print('result len:',len(result))   #4121
    print('result shape:',np.array(result).shape)  #(4121,51)

    result = np.array(result)

    #划分train、test
    row = round(0.9 * result.shape[0])
    train = result[:row, :]
    np.random.shuffle(train)
    x_train = train[:, :-1]
    y_train = train[:, -1]
    x_test = result[row:, :-1]
    y_test = result[row:, -1]

    x_train = np.reshape(x_train, (x_train.shape[0], x_train.shape[1], 1))
    x_test = np.reshape(x_test, (x_test.shape[0], x_test.shape[1], 1))  

    print('X_train shape:',X_train.shape)  #(3709L, 50L, 1L)
    print('y_train shape:',y_train.shape)  #(3709L,)
    print('X_test shape:',X_test.shape)    #(412L, 50L, 1L)
    print('y_test shape:',y_test.shape)    #(412L,)

    return [x_train, y_train, x_test, y_test]
  1. LSTM模型(基于keras)
def build_model(layers):  #layers [1,50,100,1]
    model = Sequential()

    #Stack LSTM
    model.add(LSTM(input_dim=layers[0],output_dim=layers[1],return_sequences=True))
    model.add(Dropout(0.2))

    model.add(LSTM(layers[2],return_sequences=False))
    model.add(Dropout(0.2))

    model.add(Dense(output_dim=layers[3]))
    model.add(Activation("linear"))

    start = time.time()
    model.compile(loss="mse", optimizer="rmsprop")
    print("Compilation Time : ", time.time() - start)
    return model
  1. LSTM训练预测
    (1)直接预测
def predict_point_by_point(model, data):
    predicted = model.predict(data)
    print('predicted shape:',np.array(predicted).shape)  #(412L,1L)
    predicted = np.reshape(predicted, (predicted.size,))
    return predicted

单层的GRU循环神经网络 循环神经网络lstm_单层的GRU循环神经网络_16


(2)滚动预测

def predict_sequence_full(model, data, window_size):  #data X_test
    curr_frame = data[0]  #(50L,1L)
    predicted = []
    for i in xrange(len(data)):
        #x = np.array([[[1],[2],[3]], [[4],[5],[6]]])  x.shape (2, 3, 1) x[0,0] = array([1])  x[:,np.newaxis,:,:].shape  (2, 1, 3, 1)
        predicted.append(model.predict(curr_frame[newaxis,:,:])[0,0])  #np.array(curr_frame[newaxis,:,:]).shape (1L,50L,1L)
        curr_frame = curr_frame[1:]
        curr_frame = np.insert(curr_frame, [window_size-1], predicted[-1], axis=0)   #numpy.insert(arr, obj, values, axis=None)
    return predicted

单层的GRU循环神经网络 循环神经网络lstm_机器学习_17


(3)滑动窗口+滚动预测

def predict_sequences_multiple(model, data, window_size, prediction_len):  #window_size = seq_len
    prediction_seqs = []
    for i in xrange(len(data)/prediction_len):
        curr_frame = data[i*prediction_len]
        predicted = []
        for j in xrange(prediction_len):
            predicted.append(model.predict(curr_frame[newaxis,:,:])[0,0])
            curr_frame = curr_frame[1:]
            curr_frame = np.insert(curr_frame, [window_size-1], predicted[-1], axis=0)
        prediction_seqs.append(predicted)
    return prediction_seqs

单层的GRU循环神经网络 循环神经网络lstm_神经网络_18


完整代码:

# -*- coding: utf-8 -*-

from __future__ import print_function

import time
import warnings
import numpy as np
import time
import matplotlib.pyplot as plt
from numpy import newaxis
from keras.layers.core import Dense, Activation, Dropout
from keras.layers.recurrent import LSTM
from keras.models import Sequential

warnings.filterwarnings("ignore")

def load_data(filename, seq_len, normalise_window):
    f = open(filename, 'rb').read().decode()
    data = f.split('\n')

    print('data len:',len(data))
    print('sequence len:',seq_len)

    sequence_length = seq_len + 1
    result = []
    for index in range(len(data) - sequence_length):
        result.append(data[index: index + sequence_length])  #得到长度为seq_len+1的向量,最后一个作为label

    print('result len:',len(result))
    print('result shape:',np.array(result).shape)
    print(result[:1])

    if normalise_window:
        result = normalise_windows(result)

    print(result[:1])
    print('normalise_windows result shape:',np.array(result).shape)

    result = np.array(result)

    #划分train、test
    row = round(0.9 * result.shape[0])
    train = result[:row, :]
    np.random.shuffle(train)
    x_train = train[:, :-1]
    y_train = train[:, -1]
    x_test = result[row:, :-1]
    y_test = result[row:, -1]

    x_train = np.reshape(x_train, (x_train.shape[0], x_train.shape[1], 1))
    x_test = np.reshape(x_test, (x_test.shape[0], x_test.shape[1], 1))

    return [x_train, y_train, x_test, y_test]

def normalise_windows(window_data):
    normalised_data = []
    for window in window_data:   #window shape (sequence_length L ,)  即(51L,)
        normalised_window = [((float(p) //float(window[0])) - 1) for p in window]
        normalised_data.append(normalised_window)
    return normalised_data

def build_model(layers):  #layers [1,50,100,1]
    model = Sequential()

    model.add(LSTM(input_dim=layers[0],output_dim=layers[1],return_sequences=True))
    model.add(Dropout(0.2))

    model.add(LSTM(layers[2],return_sequences=False))
    model.add(Dropout(0.2))

    model.add(Dense(output_dim=layers[3]))
    model.add(Activation("linear"))

    start = time.time()
    model.compile(loss="mse", optimizer="rmsprop")
    print("Compilation Time : ", time.time() - start)
    return model

#直接全部预测
def predict_point_by_point(model, data):
    predicted = model.predict(data)
    print('predicted shape:',np.array(predicted).shape)  #(412L,1L)
    predicted = np.reshape(predicted, (predicted.size,))
    return predicted

#滚动预测
def predict_sequence_full(model, data, window_size):  #data X_test
    curr_frame = data[0]  #(50L,1L)
    predicted = []
    for i in range(len(data)):
        #x = np.array([[[1],[2],[3]], [[4],[5],[6]]])  x.shape (2, 3, 1) x[0,0] = array([1])  x[:,np.newaxis,:,:].shape  (2, 1, 3, 1)
        predicted.append(model.predict(curr_frame[newaxis,:,:])[0,0])  #np.array(curr_frame[newaxis,:,:]).shape (1L,50L,1L)
        curr_frame = curr_frame[1:]
        curr_frame = np.insert(curr_frame, [window_size-1], predicted[-1], axis=0)   #numpy.insert(arr, obj, values, axis=None)
    return predicted

def predict_sequences_multiple(model, data, window_size, prediction_len):  #window_size = seq_len
    prediction_seqs = []
    for i in range(len(data)//prediction_len):
        curr_frame = data[i*prediction_len]
        predicted = []
        for j in range(prediction_len):
            predicted.append(model.predict(curr_frame[newaxis,:,:])[0,0])
            curr_frame = curr_frame[1:]
            curr_frame = np.insert(curr_frame, [window_size-1], predicted[-1], axis=0)
        prediction_seqs.append(predicted)
    return prediction_seqs

def plot_results(predicted_data, true_data, filename):
    fig = plt.figure(facecolor='white')
    ax = fig.add_subplot(111)
    ax.plot(true_data, label='True Data')
    plt.plot(predicted_data, label='Prediction')
    plt.legend()
    plt.show()
    plt.savefig(filename+'.png')

def plot_results_multiple(predicted_data, true_data, prediction_len):
    fig = plt.figure(facecolor='white')
    ax = fig.add_subplot(111)
    ax.plot(true_data, label='True Data')
    #Pad the list of predictions to shift it in the graph to it's correct start
    for i, data in enumerate(predicted_data):
        padding = [None for p in range(i * prediction_len)]
        plt.plot(padding + data, label='Prediction')
        plt.legend()
    plt.show()
    plt.savefig('plot_results_multiple.png')

if __name__=='__main__':
    global_start_time = time.time()
    epochs  = 1
    seq_len = 50

    print('> Loading data... ')

    X_train, y_train, X_test, y_test = load_data('sp500.csv', seq_len, True)

    print('X_train shape:',X_train.shape)  #(3709L, 50L, 1L)
    print('y_train shape:',y_train.shape)  #(3709L,)
    print('X_test shape:',X_test.shape)    #(412L, 50L, 1L)
    print('y_test shape:',y_test.shape)    #(412L,)

    print('> Data Loaded. Compiling...')

    model = build_model([1, 50, 100, 1])

    model.fit(X_train,y_train,batch_size=512,nb_epoch=epochs,validation_split=0.05)

    multiple_predictions = predict_sequences_multiple(model, X_test, seq_len, prediction_len=50)
    print('multiple_predictions shape:',np.array(multiple_predictions).shape)   #(8L,50L)

    full_predictions = predict_sequence_full(model, X_test, seq_len)
    print('full_predictions shape:',np.array(full_predictions).shape)    #(412L,)

    point_by_point_predictions = predict_point_by_point(model, X_test)
    print('point_by_point_predictions shape:',np.array(point_by_point_predictions).shape)  #(412L)

    print('Training duration (s) : ', time.time() - global_start_time)

    plot_results_multiple(multiple_predictions, y_test, 50)
    plot_results(full_predictions,y_test,'full_predictions')
    plot_results(point_by_point_predictions,y_test,'point_by_point_predictions')