【进阶实战】用PaddlePaddle实现LSTM股票预测

Paddlepaddle实现LSTM - 股票预测

欢迎大家来到这个实验,本实验实现的是利用LSTM (长短期记忆神经网络) 进行股票预测,通过本次实验,你将了解股票预测的方法、股票预测的数据集处理技巧、LSTM模型搭建以及训练过程等等,除此之外,你还将看到Paddlepaddle框架实现深度学习的一个十分清晰的结构流程,加深你对Paddlepaddle的了解。

在开始实验之前,我们有必要先来了解一下股票预测的一些知识,以便对股票预测实验有一个整体把握。

背景知识

我们之前做过关于房价预测的实验,房价预测就是根据影响房价的一些因素(面积,地理位置等等)来构建一个全连接层神经网络并对其函数参数进行训练,最后使得损失函数收敛,这样一来我们就可以得到确切的函数关系式,每给一个输入,便得到一个相应的房价输出,每次的房价输出仅仅与本次的输入有关,而与之前的输入输出没有关系,即房价的数据不构成时间序列。

股票预测虽然同样是预测类的实验,但却与房价预测有很大的不同,因为我们无法只根据一天的股票数据就能对后面的股票进行预测,换句话说,股票价格不仅仅与前面一天的数据有关系,还跟以前的股票价格有关系,这样,股票的数据就构成了一种时间序列,而每次决定股票预测结果的也不是仅仅是本次的输入,而是过去一段时间的数据序列,所以我们不能像预测房价那样使用全连接层神经网络,而是使用LSTM模型。

下面是LSTM模型的一个原理简图: 

如上图所示,右图是左图的展开图,可以看出,此模型某一个时刻的输出ht不仅仅取决于当前的输入Xt,还跟上一个隐藏层的值有关系,这就意味着此模型具有一定的 “记忆力” ,而这正好符合股票预测的要求,所以在本次实验中,我们选择的是此模型,关于LSTM模型更详细的介绍,可以点此博客http://blog.csdn.net/fengkuangsake/article/details/51658489

简单了解了股票预测的原理后,我们就可以正式开始实验了。

本次实验分流程如下:

  1. 引用库的导入
  2. 数据预处理
  3. 构造数据读取的Reader
  4. 搭建模型
  5. 训练模型
  6. 预测及可视化
  7. 总结

1 - 引用库

首先,我们需要加载实验需要用到的一些库,他们分别是:

  • numpy:一个python的基本库,用于科学计算
  • matplotlib.pyplot:用于生成比特币预测效果图
  • paddle.fluid :PaddlePaddle深度学习框架
  • from future import print_function:在开头加上from future import print_function这句之后,即使在python2.X,使用print就得像 python3.X那样加括号使用.

In[7]

import numpy as np
import math
import matplotlib.pyplot as plt
import paddle
import paddle.fluid as fluid 
from __future__ import print_function

2 - 数据预处理

本次实验采用的是上证指数的股票,数据集包含10列 (股票来源、日期、开盘价、收盘价、最低价、最高价、交易量、交易额、跌涨幅、后一天最高价) ,共有6109天的股票数据

在本实验中,我们利用历史数据中的开盘价、收盘价、最低价、最高价、交易量、交易额、跌涨幅来对下一日的最高价进行预测,第一列的数据在股票来源确定以后默认预测的是此支股票,第一行的数据完全一样,所以第一列数据在本实验中不参与训练。此外,由于我们处理股票数据时就是按照时间序列来进行处理的,所以第二列的日期数据也不会参与训练。

数据处理第一步,我们首先需要对文件中我们不需要的数据进行删除,在本实验中,我们可以直接在原数据集文件中删除前两列和第一行,然后保存文件为新的数据集stock_dataset.txt。然后我们需要读取剩下的数据:

In[4]

# 解压数据集
!unzip -qo -d data data/data3580/stock_LSTM_fluid.zip

In[5]

SAVE_DIRNAME = 'model'
f = open('data/stock_LSTM_fluid/datasets/stock_dataset.txt') 
df = f.readlines()    
f.close()

下面的代码把数据整理成我们需要的数组形式并保存在data中,形状为6109×8

In[8]

data = []
for line in df:
    data_raw = line.strip('\n').strip('\r').split('\t') #这里data_raw是列表形式,代表一行数据样本
    data.append(data_raw)#data为二维列表形式
data = np.array(data, dtype='float32')

在数据整理完成以后,我们可以查看一下数据信息,例如数据的类型、数据的个数、形状等等。

In[10]

print('数据类型:',type(data))
print('数据个数:', data.shape[0])
print('数据形状:', data.shape)
print('数据第一行:', data[0])
数据类型: <type 'numpy.ndarray'>
数据个数: 6109
数据形状: (6109, 8)
数据第一行: [1.0430000e+02 1.0439000e+02 9.9980003e+01 1.0439000e+02 1.9700000e+05
 8.5000000e+04 4.4108823e-02 1.0913000e+02]

期望输出:

数据类型: <type 'numpy.ndarray'>
数据个数: 6109
数据形状: (6109, 8)
数据第一行: [1.04300000e+02 1.04390000e+02 9.99800000e+01 1.04390000e+02
1.97000000e+05 8.50000000e+04 4.41088220e-02 1.09130000e+02]

接下来,我们需要将数据进行分割,由于数据量为几千,不是很大,通常按照7:3的比例来划分训练集和数据集,这里将前4276组数据作为训练集,后面1833组数据作为测试集。

完成以下训练集和数据集的分割代码

In[11]

ratio = 0.8
DATA_NUM = len(data)

train_len = int(DATA_NUM * ratio)
test_len = DATA_NUM - train_len

train_data = data[:train_len]
test_data = data[test_len:]

由于每条数据样本的6个输入特征的取值范围大小不一,因此这里实现特征的归一化操作

完成归一化

result_data = (data - avg) / (max_ - min_)

In[12]

# 归一化 
def normalization(data):
    avg = np.mean(data, axis=0)#axis=0表示按数组元素的列对numpy取相关操作值
    max_ = np.max(data, axis=0)
    min_ = np.min(data, axis=0)
    result_data = (data - avg) / (max_ - min_)
    return result_data
    

In[13]

train_data = normalization(train_data)
test_data = normalization(test_data)

3 - 构造Reader

首先来介绍一下paddlepaddle的reader机制:

  • 作用:构造train_reader()函数,来读取训练数据集train_set或者测试数据集test_set。
  • 用法:具体实现是在read_data()函数内部构造一个reader(),使用yield关键字来让reader()成为一个Generator(生成器),注意,yield关键字的作用和使用方法类似return关键字,不同之处在于yield关键字可以构造生成器(Generator)。
  • 优势:虽然我们可以直接创建一个包含所有数据的列表,但是由于内存限制,我们不可能创建一个无限大的或者巨大的列表,并且很多时候在创建了一个百万数量级别的列表之后,我们却只需要用到开头的几个或几十个数据,这样造成了极大的浪费,而生成器的工作方式是在每次循环时计算下一个值,不断推算出后续的元素,不会创建完整的数据集列表,从而节约了内存使用。

关于Reader机制更加详细的介绍,可以参看此博客:

In[14]

def my_train_reader():
    def reader():
        for temp in train_data:
            yield temp[:-1], temp[-1]
    return reader

def my_test_reader():
    def reader():
        for temp in test_data:
            yield temp[:-1], temp[-1]    
    return reader

由于比特币各个feature之间的数据差异比较大,所以在上面划分数据集的操作中使用了归一化操作,如此可以提高模型的精度以及加快模型的收敛速度。关于归一化的一些知识可以参看以下博客:
http://blog.csdn.net/lvweiyimi2b/article/details/54694351

至此,数据预处理完成,在这一部分,我们可以看到,数据预处理是程序的最基础部分,我们需要掌握一些数据处理的技巧来使接下来的模型训练减少很多不必要的问题甚至错误。

In[15]

# 定义batch
train_reader = paddle.batch(
    my_train_reader(),
    batch_size=10)

PaddleFluid 模型通过fluid.layers.data 来接收输入数据。图像分类网络以图片以及图片对应的类别标签作为网络的输入。

word = fluid.layers.data(

name= "current_word", shape=[ 1], dtype= "int64", lod_level= 1)

lbl = fluid.layers.data(

name= "next_word", shape=[ 1], dtype= "int64", lod_level= 1)

  1. 定义 data layer 的核心是指定输入 Tensor 的形状(shape )和类型。
  2. RNN LM 使用 one-hot 作为输入,一个词用一个和字典大小相同的向量表示,每一个位置对应了字典中的 一个词语。one-hot 向量仅有一个维度为 1, 其余全部为 0。因此为了节约存储空间,通常都直接用一个整型数表示给出词语在字典中的 id,而不是真的创建一个和词典同样大小的向量 ,因此在上面定义的 data layer 中word 和lbl 的形状都是 1,类型是int64 。
  3. 需要特别说明的是,实际上word 和lbl 是两个[batch_size x 1] 的向量,这里的batch size 是指一个 mini-batch 中序列中的总词数。对序列学习任务, mini-batch 中每个序列长度 总是在发生变化,因此实际的batch_size 只有在运行时才可以确定。batch size 总是一个输入 Tensor 的第 0 维,在 PaddleFluid 中指定 data layer 的 shape 时,不需要指定batch size 的大小,也不需要考虑占位。框架会自动补充占位符,并且在运行时 设置正确的维度信息。因此,上面的两个 data layer 的 shape 都只需要设置第二个维度,也就是 1。

https://www.sohu.com/a/239163488_500659

4 - 搭建模型

在完成了数据预处理与Reader的构造等基础工程之后,我们就可以正式进入LSTM模型的搭建流程了。

fluid.layers.data

  • 参数:
  • name (str) – The name/alias of the function
  • shape (list) – Tuple declaring the shape.数组声明形状
  • dtype (int|float) – The type of data : float32, float_16, int etc
  • lod_level (int) – The LoD Level. 0 means the input data is not a sequence.
  • 返回: The global variable that gives access to the data.
  • 返回类型: Variable
  • examples
    data = fluid.layers.data(name='x', shape=[784], dtype='float32')

fluid.layers.fc

  • input (Variable|list of Variable) – The input tensor(s) of this layer, and the dimension of the input tensor(s) is at least 2.
  • size (int) – The number of output units in this layer.
  • 返回: The transformation result.
  • 返回类型: Variable
  • examples
    data = fluid.layers.data(name="data", shape=[32, 32], dtype="float32")
    fc = fluid.layers.fc(input=data, size=1000, act="tanh")

fluid.layers.dynamic_lstm

  • input (Variable) – (LoDTensor) the first input is a LodTensor, which support variable-time length input sequence. The underlying tensor in this LoDTensor is a matrix with shape (T X 4D), where T is the total time steps in this mini-batch, D is the hidden size
  • size (int) – 4 * hidden size.
  • is_reverse (bool) – (bool, defalut: False) whether to compute reversed LSTM
  • examples
    hidden_dim = 512
    forward_proj = fluid.layers.fc(input=input_seq, size=hidden_dim * 4,act=None, bias_attr=None)
    forward, _ = fluid.layers.dynamic_lstm(input=forward_proj, size=hidden_dim * 4, use_peepholes=False)

fluid.layers.sequence_pool

  • input (variable) – The input variable which is a LoDTensor.
  • pool_type (string) – The pooling type of sequence_pool. It supports average, sum, sqrt and max.
  • 返回: The sequence pooling variable which is a Tensor.
  • examples
    x is a 1-level LoDTensor:
    x.lod = [[2, 3, 2]]
    x.data = [1, 3, 2, 4, 6, 5, 1]
    x.dims = [7, 1]
    then output is a Tensor:
    out.dim = [3, 1]
    with condition len(x.lod[-1]) == out.dims[0]
    max : out.data = [3, 6, 5], where 3=max(1,3), 6=max(2,4,6), 5=max(5,1)'''
    直观理解就是把[1,3,2,4,6,5,1]按[2,3,2]分成了[1,3],[2,4,6],[5,1],再取max值

根据以上API完成LSTM模型搭建

In[16]

DIM = 1
hid_dim2 = 1

#lod_level=0表示0 means the input data is not a sequence
x = fluid.layers.data(name='x', shape=[DIM], dtype='float32', lod_level=1)  
label = fluid.layers.data(name='y', shape=[1], dtype='float32')

# Lstm layer
fc0 = fluid.layers.fc(input=x, size=DIM * 4)
lstm_h, c = fluid.layers.dynamic_lstm(
    input=fc0, size=DIM * 4, is_reverse=False)

# 最大池化
lstm_max = fluid.layers.sequence_pool(input=lstm_h, pool_type='max')
# 激活函数
lstm_max_tanh = fluid.layers.tanh(lstm_max)
# 全连接层
prediction = fluid.layers.fc(input=lstm_max_tanh, size=hid_dim2, act='tanh')
# 代价函数
cost = fluid.layers.square_error_cost(input=prediction, label=label)
avg_cost = fluid.layers.mean(x=cost)
# acc = fluid.layers.accuracy(input=prediction, label=label)

In[18]

from paddle.v2.plot import Ploter
train_title = "Train cost"
test_title = "Test cost"
plot_cost = Ploter(train_title, test_title)

fluid.default_startup_program()

Get default/global startup program.
The layer function in fluid.layers will create parameters, readers, NCCL handles as global variables.
The startup_program will initialize them by the operators in startup program.
The layer function will append these initialization operators into startup program.
This method will return the default or the current startup program.
Users can use fluid.program_guard to switch program.

  • 返回: startup program
  • 返回类型: Program

fluid.DataFeeder

  • 参数:
  • feed_list (list) – The Variables or Variables’name that will feed into model.
  • place (Place) – place indicates feed data into CPU or GPU, if you want to feed data into GPU, please using fluid.CUDAPlace(i) (i represents the GPU id), or if you want to feed data into CPU, please using fluid.CPUPlace().
  • program (Program) – The Program that will feed data into, if program is None, it will use default_main_program(). Default None.

fluid.io.save_inference_model

  • 参数
  • dirname (str) – The directory path to save the inference model.
  • feeded_var_names (list[str]) – Names of variables that need to be feeded data during inference.
  • target_vars (list[Variable]) – Variables from which we can get inference results.
  • executor (Executor) – The executor that saves the inference model.

In[19]

# 定义优化器
# Adam
adam_optimizer = fluid.optimizer.Adam(learning_rate=0.001)
adam_optimizer.minimize(avg_cost)

# 使用CPU
place = fluid.CPUPlace()
# place = fluid.CUDAPlace(1)

exe = fluid.Executor(place)#Python中的一个执行器,只支持单个GPU运行。
exe.run( fluid.default_startup_program() )#获取默认/全局启动程序,并由执行器执行

feeder = fluid.DataFeeder(place=place, feed_list=[x, label])
# 定义双层循环
def train_loop():
    step = 0 # 画图用
    PASS_NUM = 100
    for pass_id in range(PASS_NUM):
        total_loss_pass = 0#初始化每一个epoch的损失值初始值为0
        for data in train_reader(): #data表示batch大小的数据样本          
            avg_loss_value, = exe.run(
                fluid.default_main_program(), 
                feed= feeder.feed(data), 
                fetch_list=[avg_cost])
            total_loss_pass += avg_loss_value#计算每个epoch的总损失值
#         print("Pass %d, total avg cost = %f" % (pass_id, total_loss_pass))
        # 画图
        plot_cost.append(train_title, step, avg_loss_value)
        step += 1
        plot_cost.plot()
    fluid.io.save_inference_model(SAVE_DIRNAME, ['x'], [prediction], exe)
    #对给定的main_program进行修剪,以构建一个特别用于推理的新程序,
    #然后由执行器将其和所有相关参数保存到给定的dirname 
    #['x']在推断过程中需要被馈送数据的变量的名称。
    #[prediction]从中我们可以得到推断结果的变量
train_loop()

<Figure size 432x288 with 0 Axes>

fluid.create_lod_tensor

  • 参数:
  • data (numpy.ndarray|list|LoDTensor) – a numpy array or a LoDTensor or a list holding the data to be copied.
  • recursive_seq_lens (list) – a list of lists indicating the length-based level of detail info specified by the user.
  • place (Place) – CPU or GPU place indicating where the data in the new LoDTensor will be stored.
  • 返回: A fluid LoDTensor object with tensor data and recursive_seq_lens info
  • examples
    Suppose we want LoDTensor to hold data for sequences of word, where each word is represented by an integer.
    If we want to create a LoDTensor to represent two sentences, one of 2 words, and one of 3 words. Then data can be a numpy array of integers with shape (5, 1). recursive_seq_lens will be [[2, 3]], indicating the length(# of words) in each sentence. This length-based recursive_seq_lens [[2, 3]]

In[20]

def convert2LODTensor(temp_arr, len_list):
    temp_arr = np.array(temp_arr) 
    temp_arr = temp_arr.flatten().reshape((-1, 1))#把325个测试样本的array平坦化到一维数据[1950,1]的格式
    print(temp_arr.shape)
    return fluid.create_lod_tensor(
        data=temp_arr,#对测试样本来说这里表示325个样本的平坦化数据列表,维度为[1950,1]
        recursive_seq_lens =[len_list],#对于测试样本来说这里全是6,所以为325 个6的列表
        place=fluid.CPUPlace()
        )#返回:A fluid LoDTensor object with tensor data and recursive_seq_lens info
    

def get_tensor_label(mini_batch):  
    tensor = None
    labels = []
    
    temp_arr = []
    len_list = []
    for _ in mini_batch:   #mini_batch表示的大小为325个测试样本数据
        labels.append(_[1]) #收集 label----y----------1维
        temp_arr.append(_[0]) #收集序列本身--x---------6维
        len_list.append(len(_[0])) #收集每个序列x的长度,和上边x的维度对应,这里全为6
    tensor = convert2LODTensor(temp_arr, len_list)    
    return tensor, labels

my_tensor = None
labels = None

# 定义batch
test_reader = paddle.batch(
    my_test_reader(),
    batch_size=325)#一次性把样本取完


for mini_batch in test_reader():
    my_tensor,labels = get_tensor_label(mini_batch)#其实就是变成tensor格式的x和y
    break
   
(2275, 1)

fluid.io.load_inference_model

  • 参数:
  • dirname (str) – The directory path
  • executor (Executor) – The executor to run for loading inference model.
  • 返回: The return of this function is a tuple with three elements: (program, feed_target_names, fetch_targets). The program is a Program, it’s the program for inference. The feed_target_names is a list of str, it contains Names of variables that need to feed data in the inference program. The fetch_targets is a list of Variable. It contains variables from which we can get inference results.
  • 返回类型: tuple
    这个函数的返回是一个元组,有三个元素:(1)推断程序,(2)需要在推理程序中提供数据的变量的名称, (3)The fetch_targets is a list of Variable. It contains variables from which we can get inference results

In[22]

place = fluid.CPUPlace()
exe = fluid.Executor(place)
inference_scope = fluid.core.Scope()
with fluid.scope_guard(inference_scope):#更改全局/默认作用域实例。运行时的所有变量将分配给新的作用域。
    [inference_program, feed_target_names, fetch_targets] = (
        fluid.io.load_inference_model(SAVE_DIRNAME, exe))
    results = exe.run(inference_program,
                      feed= {'x': my_tensor}, #{feed_target_names[0]:my_tensor },和上面保存模型时统一
                      fetch_list=fetch_targets)
#     print("infer results: ", results[0])
#     print("ground truth: ", labels)
#load_inference_model解释件上面api解释

In[23]

result_print = results[0].flatten()
plt.figure()
plt.plot(list(range(len(labels))), labels, color='r')  #红线为真实值
plt.plot(list(range(len(result_print))), result_print, color='g')  #绿线为预测值
plt.show()

 

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值