首先推荐一个对LSTM一些类函数进行说明的博客: 函数说明
我的目标是用LSTM进行某种水果价格的预测,一开始我的做法是,将一种水果前n天的价格作为变量传入,即这样传入的DataFrame格式是有n+1列,结果训练出来的效果不尽人意,完全比不上之前我用ARIMA时间序列去拟合价格曲线.
之后继续浏览了很多博客,资料什么的,终于明白了一个参数:time_step的意义,LSTM,长短时训练网络,time_step这个参数才是体现其记忆的地方,比如说我要用前一百天的价格求当天的价格,time_step需要为100,且需要建立在数据是连续的基础上.而且其中还遇到了一个坑,就是传入数据的格式问题.代码过后来讲.
我的代码是基于 博客 这个博客的代码修改的,主要修改的地方就是格式的问题,这个问题在我一开始传入的X有很多列的时候是不会存在的,但在我单纯用价格来预测价格的时候,就出现问题了,如果直接按原来一样处理,会报错:
ValueError: Cannot feed value of shape (1,) for Tensor 'train/Placeholder:0', which has shape '(?, 100, 1)'
类似这样的错误,都是shape的错误引起的,一般来讲,tf.nn.dynamic_rnn()的inputs的输入格式大概是[batch_size,time_steps_size,input_size],time_steps_size便是要考虑的天数,input_size指输入的变量数batch_size是块大小,由数据量决定.在这份代码中,训练数据的获取没毛病,问题在于测试数据,比如说,我训练数据对应600条,测试数据200条.不过那是给[batch_size,time_steps_size,input_size],train_x可能是[590,10,10],train_y为[590,10,1],而test_x为[20,10,10],test_y为一个数组,主要是为了计算准确率,注意了,test_x的input_size大于1时代在处理时候比较简单,Dataframe的iloc().values获取后得到的numpy库的ndarray,比如
data[[1,2,3],[2,3,4],[3,4,5]] # 为numpy.ndarray()格式 x = data[i * time_step:(i + 1) * time_step, 1:101] # 获取多列的时候,返回多个数组 x = data[i * time_step:(i + 1) * time_step, 1] # 这个时候返回一个数组
具体的话可以print出来看看,最主要的shape问题就出现在这里,因此在数据的处理过程加入了对test_x的添加一个np.newaxis,对应成了[20,10,1],不然的话,得到的test_x shape则为[20,10]
2019-01-15更新,抽空把代码数据格式等重新整理了一遍,注释也写的比较清楚了一些。至于我文章上面的...没空去看看对不对了...以后有时间再看看
# -*- coding: utf-8 -*-
# @Time : 19-1-14
# @Author : lin
import pandas as pd
import numpy as np
import tensorflow as tf
import time
import os
from veg_final_predict.test_data_ok.solve_data_tools import SolveData
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
pd.set_option('max_colwidth', 5000)
pd.set_option('display.max_columns', 10)
pd.set_option('display.max_rows', 1000)
# 定义常量
rnn_unit = 50 # hidden layer units
input_size = 1 # 输入一个变量,即前一天的价格
output_size = 1 # 输出一个变量
def get_train_data(price_list, batch_size, time_step, train_begin, train_end):
"""
得到训练集
得到的train_x的格式为shape[len(data)-time_step,time_step,输入变量数]
得到的train_y的格式为shape[len(data)-time_step,time_step,输出变量数]
batch_size*time_step=数据量
:param price_list:价格的列表
:param batch_size:一次训练多少个批次
:param time_step: 以多少天的数据作为输入
:param train_begin: 开始位置
:param train_end: 结束位置
:return:
"""
batch_index = [] # 得到每两个批次间的下标
data_train = np.array(price_list[train_begin:train_end])
train_x, train_y = [], [] # 训练集
for i in range(len(data_train) - time_step):
if i % batch_size == 0: # 每隔一个批次添加一下下表
batch_index.append(i)
x = data_train[i:i + time_step, np.newaxis] # np.newaxis为array增加一维
y = data_train[i + 1:i + time_step + 1, np.newaxis]
train_x.append(x.tolist())
train_y.append(y.tolist())
batch_index.append((len(data_train) - time_step))
return batch_index, train_x, train_y
def get_test_data(price_list, time_step, test_begin, test_end, many_days=10):
"""
得到测试集
其中test_x的格式应与训练数据一致,y应为shape[test_x, 输出变量数]的长度
:param price_list: 价格列表
:param time_step: 同上
:param test_begin: 同上
:param test_end: 同上
:param many_days: 滚动预测的天数
:return:
"""
data_test = np.array(price_list[test_begin: test_end])
# mean = np.mean(data_test, axis=0)
# std = np.std(data_test, axis=0)
test_x, test_y = [], []
for i in range(len(data_test) - time_step):
# test_y应该至少多出many_days个长度,才可进行滚动预测
if len(test_x) < (len(data_test) - many_days - time_step):
x = data_test[i:i + time_step, np.newaxis]
test_x.append(x.tolist())
y = data_test[i + time_step]
test_y.append(y)
test_y = [[i] for i in test_y]
return test_x, test_y
# ——————————————————定义神经网络变量——————————————————
# 输入层、输出层权重、偏置
weights = {
'in': tf.Variable(tf.random_normal([input_size, rnn_unit])),
'out': tf.Variable(tf.random_normal([rnn_unit, 1]))
}
biases = {
'in': tf.Variable(tf.constant(0.1, shape=[rnn_unit, ])),
'out': tf.Variable(tf.constant(0.1, shape=[1, ]))
}
# ——————————————————定义神经网络变量——————————————————
def lstm(x):
"""
LSTM模型的建立
:param x: shape[batch_size, time_step, 输入变量个数]
:return:
"""
# tf.reset_default_graph()
batch_size = tf.shape(x)[0]
time_step = tf.shape(x)[1]
w_in = weights['in']
b_in = biases['in']
# -1表示第一层靠第二层来决定, 可以说其实第一层就会编程batch_size * time_step
input_value = tf.reshape(x, [-1, input_size]) # 需要将tensor转成2维进行计算,计算后的结果作为隐藏层的输入
input_rnn = tf.matmul(input_value, w_in) + b_in
# lstm的输入格式即为[batch_size, time_step, 输入变量数目]
input_rnn = tf.reshape(input_rnn, [-1, time_step, rnn_unit]) # 将tensor转成3维,作为lstm cell的输入
cell = tf.nn.rnn_cell.BasicLSTMCell(rnn_unit)
init_state = cell.zero_state(batch_size, dtype=tf.float32)
# output_rnn是记录lstm每个输出节点的结果,final_states是最后一个cell的结果
output_rnn, final_states = tf.nn.dynamic_rnn(cell,
input_rnn, initial_state=init_state,
dtype=tf.float32)
# -1表示根据实际情况分配,比如出来的数据为100个,rnn_unit为1,则-1的位置会变为100
output = tf.reshape(output_rnn, [-1, rnn_unit]) # 作为输出层的输入
w_out = weights['out']
b_out = biases['out']
predict_value = tf.matmul(output, w_out) + b_out
return predict_value, final_states
def train_lstm(name, acc_1, acc_5, acc_10, price_list, batch_size,
time_step, train_begin, train_end, test_begin,
test_end, many_days):
"""
进行模型的训练以及获取准确率
:param name: 为了将命名域区分开来,方可实现多个模型的定义,保证每种蔬菜开始预测用的都是初始化的模型
:param acc_1: 误差小于1%的准确率
:param acc_5: 误差小于5%的准确率
:param acc_10: 误差小于10%的准确率
:param price_list: 价格数组
:param batch_size: 同时传入的数据批次为多少
:param time_step: 以几日的数据进行预测
:param train_begin: 获取训练数据开始的下标
:param train_end: 获取训练数据结束的下标
:param test_begin: 获取测试数据开始的下标
:param test_end: 获取测试数据结束的下标
:param many_days: 滚动预测的天数
:return:
"""
input_x = tf.placeholder(tf.float32, shape=[None, time_step, input_size])
output_y = tf.placeholder(tf.float32, shape=[None, time_step, output_size])
batch_index, train_x, train_y = get_train_data(price_list, batch_size, time_step, train_begin, train_end)
test_x, test_y = get_test_data(price_list, time_step, test_begin, test_end, many_days)
# 命名域区分开来,方可实现多个模型的定义,否则会不知道是哪个
with tf.variable_scope(name):
predict_value, _ = lstm(input_x)
# 损失函数
loss = tf.reduce_mean(tf.square(tf.reshape(predict_value, [-1]) - tf.reshape(output_y, [-1])))
lr = tf.Variable(0.01, dtype=tf.float32) # 学习率定义
train_op = tf.train.AdamOptimizer(lr).minimize(loss)
with tf.Session() as sess:
# 初始化
sess.run(tf.global_variables_initializer())
# 训练1000次,每次以batch_size为一个批次
for i in range(1001):
# 对应训练过程中出现的loss值
loss_ = None
sess.run(tf.assign(lr, 0.01 * 0.95 ** i)) # 逐渐下降
for step in range(len(batch_index) - 1):
# 分别对应传入sess.run()的两个值
_, loss_ = sess.run([train_op, loss], feed_dict={
input_x: train_x[batch_index[step]:batch_index[step + 1]],
output_y: train_y[batch_index[step]:batch_index[step + 1]]})
# 每隔100次则测试一次准确率
if i % 100 == 0:
print(i, loss_)
test_predict = []
bool_list_1 = []
bool_list_5 = []
bool_list_10 = []
for step in range(len(test_x)):
x = test_x[step]
# prob代表投入数据预测得到的结果
prob = None
# 接下来是滚动预测了
for j in range(many_days):
# 输入要三维
prob = sess.run(predict_value, feed_dict={input_x: [x]})
# 先把输出可能多维的情况,转换成一维
predict = prob.reshape((-1))
# 最后一个便是预测对应y的那一天
predict_y = predict[-1]
origin_y = test_y[step + j]
# 当滚动预测到了最后一天了
if j == many_days - 1:
# 之所以后面需要加个[0]就是因为predict_y和origin_y都是数组,虽然只有一个数
bool_list_1.append((abs(predict_y - origin_y) / origin_y < 0.01)[0])
bool_list_5.append((abs(predict_y - origin_y) / origin_y < 0.05)[0])
bool_list_10.append((abs(predict_y - origin_y) / origin_y < 0.1)[0])
# 重置输入x,将预测得到的值加进去,并去掉原来的第一个数
x = np.append(x[1:], [predict_y]) # 将计算值添加进去
x = [[num] for num in x]
predict = prob.reshape((-1))
test_predict.extend(predict)
# 得到误差小于1%的准确率了,tf.cast将值转换为float格式
num_list = (tf.cast(bool_list_1, tf.float32))
# reduce_mean得到平均值,此时True已经为1,False已经为0,故平均值就是准确率
accuracy = tf.reduce_mean(num_list)
acc_one_1 = sess.run(accuracy)
acc_1.append(acc_one_1)
print(acc_one_1)
# 得到5%的
num_list = (tf.cast(bool_list_5, tf.float32))
accuracy = tf.reduce_mean(num_list)
acc_one_5 = sess.run(accuracy)
acc_5.append(acc_one_5)
print(acc_one_5)
# 得到10%的
num_list = (tf.cast(bool_list_10, tf.float32))
accuracy = tf.reduce_mean(num_list)
acc_one_10 = sess.run(accuracy)
acc_10.append(acc_one_10)
print(acc_one_10)
sess.close()
def training(num, root_path, file_name, predict_path):
"""
训练及预测准确率入口
:param num: 为了神经网络中初始化模型定义的一个每种蔬菜不会重复的值
:param root_path: 源数据存放的目录
:param file_name: 文件路径,指单个蔬菜的路径
:param predict_path: 预测得到的准确率存放的位置
:return:
"""
veg_data = pd.read_csv(open(root_path + file_name, 'r', encoding='UTF-8')) # 解决中文编码
# print(veg_data.price.tolist())
# get_train_data(veg_data.price.tolist(), 10, 100, 0, 800)
# get_test_data(veg_data.price.tolist(), 100, 700, 1000)
price_list = veg_data.price.tolist()
# 错误率小于1%、5%、10%的准确率
acc_1 = []
acc_5 = []
acc_10 = []
# 以下各值的意义,在train_lstm函数下有简介
batch_size = 50
time_step = 100
train_begin = 0
train_end = 800
test_begin = 700
test_end = 1060
many_days = 10
train_lstm('veg' + str(num), acc_1, acc_5, acc_10, price_list, batch_size, time_step,
train_begin, train_end, test_begin, test_end, many_days)
final_data = pd.DataFrame({'acc_1': acc_1, 'acc_5': acc_5, 'acc_10': acc_10})
final_data.to_csv(predict_path + file_name)
def run():
pwd = os.getcwd() # 当前的完整路径
father_path = os.path.abspath(os.path.dirname(pwd) + os.path.sep + ".") # 父目录
root_path = father_path + '/veg_data_solve_18_12/'
predict_path = father_path + '/veg_lstm_predict_accuracy/'
# 获取所有文件名
file_name_list = SolveData.get_file_name(root_path)
for i in range(len(file_name_list)):
start_time = time.time()
training(i, root_path, file_name_list[i], predict_path)
end_time = time.time()
print(file_name_list[i] + ' wastes time ', end_time-start_time, 's')
if __name__ == '__main__':
run()
其中用到的另一个文件的函数如下:
def get_file_name(file_dir):
file_name_list = []
for root, dirs, files_name in os.walk(file_dir):
# root为当前目录路径,dirs为当前路径下所有子目录,files_name为当前路径下所有非目录子文件
if root == file_dir:
file_name_list = files_name
return file_name_list