|
|
import os
|
|
|
import math
|
|
|
import numpy as np
|
|
|
import datetime as dt
|
|
|
from numpy import newaxis
|
|
|
from LSTMPredictStock.core.utils import Timer
|
|
|
from keras.layers import Dense, Activation, Dropout, LSTM
|
|
|
from keras.models import Sequential, load_model
|
|
|
from keras.callbacks import EarlyStopping, ModelCheckpoint
|
|
|
|
|
|
class Model():
|
|
|
"""A class for an building and inferencing an lstm model"""
|
|
|
|
|
|
def __init__(self):
|
|
|
self.model = Sequential()
|
|
|
|
|
|
def load_model(self, filepath):
|
|
|
'''
|
|
|
从本地保存的模型参数来加载模型
|
|
|
filepath: .h5 格式文件
|
|
|
'''
|
|
|
print('[Model] Loading model from file %s' % filepath)
|
|
|
self.model = load_model(filepath)
|
|
|
|
|
|
def build_model(self, configs):
|
|
|
"""
|
|
|
新建一个模型
|
|
|
configs:配置文件
|
|
|
"""
|
|
|
timer = Timer()
|
|
|
timer.start()
|
|
|
|
|
|
for layer in configs['model']['layers']:
|
|
|
neurons = layer['neurons'] if 'neurons' in layer else None
|
|
|
dropout_rate = layer['rate'] if 'rate' in layer else None
|
|
|
activation = layer['activation'] if 'activation' in layer else None
|
|
|
return_seq = layer['return_seq'] if 'return_seq' in layer else None
|
|
|
input_timesteps = layer['input_timesteps'] if 'input_timesteps' in layer else None
|
|
|
input_dim = layer['input_dim'] if 'input_dim' in layer else None
|
|
|
|
|
|
if layer['type'] == 'dense':
|
|
|
self.model.add(Dense(neurons, activation=activation))
|
|
|
if layer['type'] == 'lstm':
|
|
|
self.model.add(LSTM(neurons, input_shape=(input_timesteps, input_dim), return_sequences=return_seq))
|
|
|
if layer['type'] == 'dropout':
|
|
|
self.model.add(Dropout(dropout_rate))
|
|
|
|
|
|
self.model.compile(loss=configs['model']['loss'], optimizer=configs['model']['optimizer'])
|
|
|
|
|
|
print('[Model] Model Compiled')
|
|
|
timer.stop() #输出构建一个模型耗时
|
|
|
|
|
|
def train(self, x, y, epochs, batch_size, save_dir):
|
|
|
timer = Timer()
|
|
|
timer.start()
|
|
|
print('[Model] Training Started')
|
|
|
print('[Model] %s epochs, %s batch size' % (epochs, batch_size))
|
|
|
|
|
|
save_fname = os.path.join(save_dir, '%s-e%s.h5' % (dt.datetime.now().strftime('%d%m%Y-%H%M%S'), str(epochs)))
|
|
|
callbacks = [
|
|
|
EarlyStopping(monitor='val_loss', patience=2),
|
|
|
ModelCheckpoint(filepath=save_fname, monitor='val_loss', save_best_only=True)
|
|
|
]
|
|
|
self.model.fit(
|
|
|
x,
|
|
|
y,
|
|
|
epochs=epochs,
|
|
|
batch_size=batch_size,
|
|
|
callbacks=callbacks
|
|
|
)
|
|
|
self.model.save(save_fname) #保存训练好的模型
|
|
|
|
|
|
print('[Model] Training Completed. Model saved as %s' % save_fname)
|
|
|
timer.stop() #输出训练耗时
|
|
|
|
|
|
def train_generator(self, data_gen, epochs, batch_size, steps_per_epoch, save_dir,save_name):
|
|
|
'''
|
|
|
由data_gen数据产生器来,逐步产生训练数据,而不是一次性将数据读入到内存
|
|
|
'''
|
|
|
timer = Timer()
|
|
|
timer.start()
|
|
|
print('[Model] Training Started')
|
|
|
print('[Model] %s epochs, %s batch size, %s batches per epoch' % (epochs, batch_size, steps_per_epoch))
|
|
|
|
|
|
# save_fname = os.path.join(save_dir, '%s-e%s.h5' % (dt.datetime.now().strftime('%d%m%Y-%H%M%S'), str(epochs)))
|
|
|
save_fname = os.path.join(save_dir, save_name+'.h5')
|
|
|
callbacks = [
|
|
|
ModelCheckpoint(filepath=save_fname, monitor='loss', save_best_only=True)
|
|
|
]
|
|
|
self.model.fit_generator(
|
|
|
data_gen,
|
|
|
steps_per_epoch=steps_per_epoch,
|
|
|
epochs=epochs,
|
|
|
callbacks=callbacks,
|
|
|
workers=1
|
|
|
)
|
|
|
|
|
|
print('[Model] Training Completed. Model saved as %s' % save_fname)
|
|
|
timer.stop()
|
|
|
|
|
|
# data必须是三维数据,即shape:(a,b,c)
|
|
|
def predict_point_by_point(self, data):
|
|
|
#Predict each timestep given the last sequence of true data, in effect only predicting 1 step ahead each time
|
|
|
predicted = self.model.predict(data) # data有多少行就输出多少个预测值,返回的预测值是一个2维数组:(a,1) a:为data的行数
|
|
|
predicted = np.reshape(predicted, (predicted.size,)) # 这里将二维数组,变成一维数组
|
|
|
return predicted # 返回一维数组,元素个数与data的a相同
|
|
|
|
|
|
# 对data进行多段预测,每段预测基于一个窗口大小(window_size)的数据,然后输出prediction_len长的预测值(一维数组)
|
|
|
# 再从上一个窗口移动prediction_len的长度,得到下一个窗口的数据,并基于该数据再预测prediction_len长的预测值
|
|
|
# 所以prediction_len决定了窗口的移位步数,每次的窗口大小是一样的,所以最后预测的段数 = 窗口个数/预测长度
|
|
|
# 相当于多次调用predict_1_win_sequence方法
|
|
|
def predict_sequences_multiple(self, data, window_size, prediction_len):
|
|
|
#Predict sequence of 50 steps before shifting prediction run forward by 50 steps
|
|
|
prediction_seqs = []
|
|
|
for i in range(int(len(data)/prediction_len)):
|
|
|
curr_frame = data[i*prediction_len]
|
|
|
predicted = []
|
|
|
for j in range(prediction_len):
|
|
|
predicted.append(self.model.predict(curr_frame[newaxis,:,:])[0,0]) # newaxis:增加新轴,使得curr_frame变成(1,x,x)三维数据
|
|
|
curr_frame = curr_frame[1:]
|
|
|
curr_frame = np.insert(curr_frame, [window_size-2], predicted[-1], axis=0)
|
|
|
prediction_seqs.append(predicted)
|
|
|
return prediction_seqs
|
|
|
|
|
|
# 输入一个窗口的数据,指定预测的长度,data:依旧是三维数组(1,win_len,fea_len)
|
|
|
# 返回预测数组
|
|
|
def predict_sequence_full(self, data, window_size): # window_size:为输入数据的长度
|
|
|
#Shift the window by 1 new prediction each time, re-run predictions on new window
|
|
|
curr_frame = data[0] # 基于data[0]一个窗口的数据,来预测len(data)个输出
|
|
|
predicted = []
|
|
|
for i in range(len(data)):
|
|
|
predicted.append(self.model.predict(curr_frame[newaxis,:,:])[0,0]) # append了一个预测值(标量)
|
|
|
curr_frame = curr_frame[1:]
|
|
|
curr_frame = np.insert(curr_frame, [window_size-2], predicted[-1], axis=0) # 插入位置[window_size-2]:curr_frame的末尾,predicted[-1]:插入值
|
|
|
return predicted
|
|
|
|
|
|
# 输入一个窗口的数据,指定预测的长度,data:依旧是三维数组(1,win_len,fea_len)
|
|
|
# 返回预测数组
|
|
|
def predict_1_win_sequence(self, data, window_size,predict_length): # window_size:data的窗口大小
|
|
|
#Shift the window by 1 new prediction each time, re-run predictions on new window
|
|
|
curr_frame = data[0]
|
|
|
predicted = []
|
|
|
for i in range(predict_length): # range(len(data))
|
|
|
predicted.append(self.model.predict(curr_frame[newaxis,:,:])[0,0]) # append了一个预测值(标量)
|
|
|
curr_frame = curr_frame[1:]
|
|
|
curr_frame = np.insert(curr_frame, [window_size-2], predicted[-1], axis=0) # 插入位置[window_size-2]:curr_frame的末尾,predicted[-1]:插入值
|
|
|
return predicted |