赞
踩
目录
学习LSTM代码之前要先了解LSTM模型解决什么问题,简而言之,LSTM是RNN的升级版,擅长挖掘时序数据中的信息。本模型对ZHW_AI课题组.2021的LSTM(股票预测)代码进行讲解。本文对代码的难以理解的、重点的部分进行了注释,并对需要注意的点进行了文字提醒。同时,本文最大的特点,是结合本人自制的数据集制作图、LSTM网络结构图,深入浅出的对由数据-->网络的具体过程进行了讲解演示。
基本逻辑:导入相关资源包-->定义模型结构-->制作数据集-->模型训练-->测试与保存结果
这里除了安装深度学习框架pytorch之外,还需要安装matplotlib、numpy、pandas、tushare等库,安装步骤都很简单,只需输入pip3 install xxx(库名)即可。
- import matplotlib.pyplot as plt
- import numpy as np
- import tushare as ts
- import torch
- from torch import nn
- import datetime
- import time
- class LSTM_Regression(nn.Module): #调用nn.Module的初始化函数,初始化LSTMRegression类。
- """
- 使用LSTM进行回归
-
- 参数:
- - input_size: feature size
- - hidden_size: number of hidden units
- - output_size: number of output
- - num_layers: layers of LSTM to stack
- """
- def __init__(self, input_size, hidden_size, output_size=1, num_layers=2):
- super().__init__()
-
- self.lstm = nn.LSTM(input_size, hidden_size, num_layers) #定义一个LSTM层,其中输入的大小为inputsize,输出的大小为hiddensize,LSTM的层数为numlayers。
- self.fc = nn.Linear(hidden_size, output_size) #定义一个全连接层,其中输入的大小为hiddensize,输出的大小为outputsize
-
- def forward(self, _x):
- x, _ = self.lstm(_x) # _x is input, size (seq_len, batch, input_size) 这行代码是在LSTMRegression类的forward函数中,将输入_x送入LSTM层中进行计算,并将输出赋值给变量x。其中x的形状为(seqlen, batch, inputsize),表示输入的序列长度为seqlen,batchsize为batch,每个时间步的特征维度为inputsize。在这里,由于不需要使用LSTM层的输出,所以将其赋值给了一个下划线变量。
- s, b, h = x.shape # x is output, size (seq_len, batch, hidden_size)
- x = x.view(s*b, h) #将x的形状改为(sb, h)。 在深度学习中,全连接层通常接受的输入是二维张量,即(batchsize, inputsize),其中batchsize表示批次大小,inputsize表示每个样本的特征维度。这个操作是为了将LSTM的输出送入全连接层中。
- x = self.fc(x) #将x送入全连接层中,得到输出x。
- x = x.view(s, b, -1) # 把形状改回来
- return x
参照LSTM网络模型图可知,self.lstm 包含了input_layers的定义,hidden_layers的定义
self.fc包含了对output_layers的定义,此二者即完成了整个LSTM网络模型的搭建。
注意:在深度学习中,全连接层通常接受的输入是二维张量,即(batchsize, inputsize),其中batchsize表示批次大小,inputsize表示每个样本的特征维度。
- def create_dataset(data, days_for_train=5) -> (np.array, np.array):
- """
- 根据给定的序列data,生成数据集
- 数据集分为输入和输出,每一个输入的长度为days_for_train,每一个输出的长度为1。
- 也就是说用days_for_train天的数据,对应下一天的数据。
- 若给定序列的长度为d,将输出长度为(d-days_for_train+1)个输入/输出对
- """
- dataset_x, dataset_y= [], []
- for i in range(len(data)-days_for_train):
- _x = data[i:(i+days_for_train)] #具体来说,i:(i+days_for_train)] 是一个左闭右开的区间
- dataset_x.append(_x)
- dataset_y.append(data[i+days_for_train])
- return (np.array(dataset_x), np.array(dataset_y)) # 数据类型变换:dataset_x和data_set_y数据类型(type)由列表(list)->数组(numpy.nparray)
- """
为什么要将数据集由序列变为数组?序列和数据的联系和区别
为了更高效地进行数值计算,通常会使用数组(numpy.nparray)代替列表(list)。相比于列表,数组具有以下优势:
1. 数组支持向量化操作,即对整个数组进行操作,而不需要使用循环。这样可以大大提高计算速度;
2. 数组支持broadcasting操作,即不同形状的数组之间的算法,这样可以简化代码,提高可读性。
原始数据和数据集的区别:
原始数据指的是要研究的时序数据,数据集并不是把整个原始时序数据给作为一个数据,而是要结合研究目的对原始时序数据进行采样从而制作。
案例演示:input数据集的采集:原始时序数据为(n,1)的序列,每k个作为一个输入数据,共能采集(n-k)个输入数据,数据集尺寸为(n-k,k,1),label数据的采集是同样的道理。
- if __name__ == '__main__':
- t0 = time.time() #这段代码的作用是记录当前时间并将其赋值给变量t0。通常情况下,可以通过将代码结束时的当前时间减去t0的值来测量代码的执行时间。
-
- '获取原始数据'
- data_close = ts.get_k_data('000001', start='2019-01-01', index=True)['close'] # 取上证指数的收盘价 ,'index=True'表示获取的是指数数据,而不是股票数据。['close']表示只获取收盘价数据。
- data_close.to_csv('000001.csv', index=False) #将下载的数据转存为.csv格式保存
- data_close = pd.read_csv('000001.csv') #读取.csv文件
- # df_sh = ts.get_k_data('sh', start='2019-01-01', end=datetime.datetime.now().strftime('%Y-%m-%d'))
- # print(df_sh.shape)
-
- # '绘制原始数据折线图'
- #data_close = data_close.astype('float32').values # 转换数据类型:astype('float32')将数据类型转换为float32,values将其转换为numpy数组
- #plt.plot(data_close) #将数据绘制成折线图
- #plt.savefig('data.png', format='png', dpi=200) #将图像保存,名字”data.png“,格式为png,分辨率200
- #plt.close() #关闭图像
-
- '数据预处理:将价格标准化到0~1'
- max_value = np.max(data_close)
- min_value = np.min(data_close)
- data_close = (data_close - min_value) / (max_value - min_value)
-
- '制作数据集'
- dataset_x, dataset_y = create_dataset(data_close, DAYS_FOR_TRAIN)
-
- '划分训练集和测试集,70%作为训练集'
- train_size = int(len(dataset_x) * 0.7)
- train_x = dataset_x[:train_size]
- train_y = dataset_y[:train_size]
-
- '将数据改变形状,RNN 读入的数据维度是 (seq_size, batch_size, feature_size)'
- train_x = train_x.reshape(-1, 1, DAYS_FOR_TRAIN) #这行代码的作用是将trainx数组的形状改变为(-1, 1, DAYSFORTRAIN)。其中,-1表示该维度的大小由程序自动计算得出,1表示batchsize为1,DAYSFORTRAIN表示featuresize为DAYSFORTRAIN。这是为了将数据转换为RNN读入的格式。
- train_y = train_y.reshape(-1, 1, 1)
-
- '转为pytorch的tensor对象'
- train_x = torch.from_numpy(train_x) #这行代码的作用是将numpy数组trainx转换为PyTorch的tensor对象。这是因为在PyTorch中,神经网络的输入和输出必须是tensor对象。通过将trainx转换为tensor对象,可以将其作为神经网络的输入。
- train_y = torch.from_numpy(train_y)
-
- '模型训练'
- model = LSTM_Regression(DAYS_FOR_TRAIN, 8, output_size=1, num_layers=2) # 导入模型并设置模型的参数输入输出层、隐藏层等
- model_total = sum([param.nelement() for param in model.parameters()]) # 计算模型参数: 具体来说,它使用了PyTorch中的sum函数和nelement函数,对模型的所有参数进行求和并返回总参数数量。这个值可以用来评估模型的大小和复杂度。
- print("Number of model_total parameter: %.8fM" % (model_total/1e6)) # 这行代码的作用是打印模型的总参数数量。我们使用了Python的百分号格式化字符串,其中%.8f表示将一个浮点数格式化为8位小数的字符串。我们将模型的总参数数量除以1e6,以将其从字节转换为兆字节。最后,我们将结果插入到字符串中,使用%f占位符。
-
- train_loss = []
- loss_function = nn.MSELoss() #损失函数
- optimizer = torch.optim.Adam(model.parameters(), lr=1e-2, betas=(0.9, 0.999), eps=1e-08, weight_decay=0) #优化器
- for i in range(200): #迭代200次
- out = model(train_x)
- loss = loss_function(out, train_y)
- loss.backward()
- optimizer.step()
- optimizer.zero_grad()
- train_loss.append(loss.item()) #为了在训练过程中访问损失函数的值,您可以使用loss.item()方法。该方法将损失函数的标量值作为Python浮点数返回。
-
- # 将训练过程的损失值写入文档保存,并在终端打印出来
- with open('log.txt', 'a+') as f: #语句打开一个名为log.txt的文档,如果该文档不存在则创建一个新的文档
- f.write('{} - {}\n'.format(i+1, loss.item())) #将当前的迭代次数和损失值写入到文档中
- if (i+1) % 1 == 0:
- print('Epoch: {}, Loss:{:.5f}'.format(i+1, loss.item())) #在终端打印出当前的迭代次数和损失值。其中,{:.5f}表示将一个浮点数格式化为5位小数的字符串。
模型训练的准备工作:
(1)制作并处理数据集
1. 获取原始数据
2. 原始数据的预处理:标准化
3. 制作数据集:调用create_dataset( )函数制作数据集
4. 划分训练集和测试集
(2)网络输入维度和数据类型转换要求:
1. RNN 读入的数据维度是 (seq_size, batch_size, feature_size)
2.在PyTorch中,神经网络的输入和输出必须是tensor对象
模型训练完毕之后,想要对损失函数进行可视化操作、保存模型参数或计算训练时间的可进行以下操作。
- # 画loss曲线 matplotlib.pyplot
- plt.figure() # 先准备一个图板
- plt.plot(train_loss, 'b', label='loss') # train_loss是要绘制的列表文件,b-颜色
- plt.title("Train_Loss_Curve") # 标题
- plt.ylabel('train_loss') # y轴名称
- plt.xlabel('epoch_num') # x轴名称
- plt.savefig('loss.png', format='png', dpi=200) #将图保存为图片:图片名,格式,分辨率
- plt.close() #关闭图版
-
- # torch.save(model.state_dict(), 'model_params.pkl') # 可以保存模型的参数供未来使用
- t1 = time.time()
- T = t1 - t0
- print('The training time took %.2f' % (T / 60) + ' mins.')
-
- tt0 = time.asctime(time.localtime(t0))
- tt1 = time.asctime(time.localtime(t1))
- print('The starting time was ', tt0)
- print('The finishing time was ', tt1)
- # for test
- model = model.eval() # 转换成测试模式
- # model.load_state_dict(torch.load('model_params.pkl')) # 读取参数
-
- # 注意这里用的是全集 模型的输出长度会比原数据少DAYS_FOR_TRAIN 填充使长度相等再作图
- dataset_x = dataset_x.reshape(-1, 1, DAYS_FOR_TRAIN) # (seq_size, batch_size, feature_size)
- dataset_x = torch.from_numpy(dataset_x)
-
- pred_test = model(dataset_x) # 全量训练集的模型输出 (seq_size, batch_size, output_size)
- pred_test = pred_test.view(-1).data.numpy()
- pred_test = np.concatenate((np.zeros(DAYS_FOR_TRAIN), pred_test)) # 填充0 使长度相同
- assert len(pred_test) == len(data_close)
-
- plt.plot(pred_test, 'r', label='prediction')
- plt.plot(data_close, 'b', label='real')
- plt.plot((train_size, train_size), (0, 1), 'g--') # 分割线 左边是训练数据 右边是测试数据的输出
- plt.legend(loc='best')
- plt.savefig('result.png', format='png', dpi=200)
- plt.close()
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。