【python量化】将Transformer模型用于股票价格预测

2023-05-16

49c26e20e2f510050d2b9a01ca1c869d.png

写在前面

下面的这篇文章主要教大家如何搭建一个基于Transformer的简单预测模型,并将其用于股票价格预测当中。原代码在文末进行获取。

1

Transformer模型

Transformer 是 Google 的团队在 2017 年提出的一种 NLP 经典模型,现在比较火热的 Bert 也是基于 Transformer。Transformer 模型使用了 Self-Attention 机制,不采用 RNN 的顺序结构,使得模型可以并行化训练,而且能够拥有全局信息。这篇文章的目的主要是将带大家通过Pytorch框架搭建一个基于Transformer的简单股票价格预测模型。

a73a39ff17ffb20a0fb96a59af3102a7.png

Transformer的基本架构

具体地,我们用到了上证指数的收盘价数据为例,进行预测t+1时刻的收盘价。需要注意的是,本文只是通过这样一个简单的基本模型,带大家梳理一下数据预处理,模型构建以及模型评估的流程。模型还有很多可以改进的地方,例如选择更有意义的特征,如何进行有效的多步预测等。

2

环境准备

本地环境:

Python 3.7
IDE:Pycharm

库版本:

numpy 1.18.1
pandas 1.0.3 
sklearn 0.22.2
matplotlib 3.2.1
torch 1.10.1

3

代码实现

1. 导入库以及定义超参

首先,需要导入用到库,以及模型的一些超参数的设置。其中,input_window和output_window分别用于设置输入数据的长度以及输出数据的长度。当然,这些参数大家也可以根据实际应用场景进行修改。

import torch
import torch.nn as nn
import numpy as np
import time
import math
import matplotlib.pyplot as plt
from sklearn.preprocessing import MinMaxScaler
import pandas as pd


torch.manual_seed(0)
np.random.seed(0)


input_window = 20
output_window = 1
batch_size = 64
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)

2. 模型构建

Transformer中很重要的一个组件是提出了一种新的位置编码的方式。我们知道,循环神经网络本身就是一种顺序结构,天生就包含了词在序列中的位置信息。当抛弃循环神经网络结构,完全采用Attention取而代之,这些词序信息就会丢失,模型就没有办法知道每个词在句子中的相对和绝对的位置信息。因此,有必要把词序信号加到词向量上帮助模型学习这些信息,位置编码(Positional Encoding)就是用来解决这种问题的方法。它的原理是将生成的不同频率的正弦和余弦数据作为位置编码添加到输入序列中,从而使得模型可以捕捉输入变量的相对位置关系。

class PositionalEncoding(nn.Module):


    def __init__(self, d_model, max_len=5000):
        super(PositionalEncoding, self).__init__()
        pe = torch.zeros(max_len, d_model)
        position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
        div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)
        pe = pe.unsqueeze(0).transpose(0, 1)
        self.register_buffer('pe', pe)


    def forward(self, x):
        return x + self.pe[:x.size(0), :]

之后,搭建Transformer的基本结构,在Pytorch中有已经实现的封装好的Transformer组件,可以很方便地进行调用和修改。其中需要注意的是,文中并没有采用原论文中的Encoder-Decoder的架构,而是将Decoder用了一个全连接层进行代替,用于输出预测值。另外,其中的create_mask将输入进行mask,从而避免引入未来信息。

class TransAm(nn.Module):
    def __init__(self, feature_size=250, num_layers=1, dropout=0.1):
        super(TransAm, self).__init__()
        self.model_type = 'Transformer'
        self.src_mask = None
        self.pos_encoder = PositionalEncoding(feature_size)
        self.encoder_layer = nn.TransformerEncoderLayer(d_model=feature_size, nhead=10, dropout=dropout)
        self.transformer_encoder = nn.TransformerEncoder(self.encoder_layer, num_layers=num_layers)
        self.decoder = nn.Linear(feature_size, 1)
        self.init_weights()


    def init_weights(self):
        initrange = 0.1
        self.decoder.bias.data.zero_()
        self.decoder.weight.data.uniform_(-initrange, initrange)


    def forward(self, src):
        if self.src_mask is None or self.src_mask.size(0) != len(src):
            device = src.device
            mask = self._generate_square_subsequent_mask(len(src)).to(device)
            self.src_mask = mask


        src = self.pos_encoder(src)
        output = self.transformer_encoder(src, self.src_mask)
        output = self.decoder(output)
        return output
        
     def _generate_square_subsequent_mask(self, sz):
        mask = (torch.triu(torch.ones(sz, sz)) == 1).transpose(0, 1)
        mask = mask.float().masked_fill(mask == 0, float('-inf')).masked_fill(mask == 1, float(0.0))
        return mask

3. 数据预处理

接下来需要对数据进行预处理,首先定义一个窗口划分的函数。它的作用是将输入按照延迟output_windw的方式来划分数据以及其标签,文中是进行单步预测,所以假设输入是1到20,则其标签就是2到21,以适应Transformer的seq2seq的形式的输出。

def create_inout_sequences(input_data, tw):
    inout_seq = []
    L = len(input_data)
    for i in range(L - tw):
        train_seq = input_data[i:i + tw]
        train_label = input_data[i + output_window:i + tw + output_window]
        inout_seq.append((train_seq, train_label))
    return torch.FloatTensor(inout_seq)

之后划分训练集和测试集,其中前70%条数据用于模型训练,后面的数据用于模型测试。具体地,我们用到了前input_window个收盘价来预测下一时刻的收盘价数据。

def get_data():


    series = pd.read_csv('./000001_Daily.csv', usecols=['Close'])
    # series = pd.read_csv('./daily-min-temperatures.csv', usecols=['Temp'])
    scaler = MinMaxScaler(feature_range=(-1, 1))
    series = scaler.fit_transform(series.values.reshape(-1, 1)).reshape(-1)


    train_samples = int(0.7 * len(series))
    train_data = series[:train_samples]
    test_data = series[train_samples:]


    train_sequence = create_inout_sequences(train_data, input_window)
    train_sequence = train_sequence[:-output_window]


    test_data = create_inout_sequences(test_data, input_window)
    test_data = test_data[:-output_window]


    return train_sequence.to(device), test_data.to(device)

接下来实现一个databatch generator,便于从数据中按照batch的形式进行读取数据。

def get_batch(source, i, batch_size):
    seq_len = min(batch_size, len(source) - 1 - i)
    data = source[i:i + seq_len]
    input = torch.stack(torch.stack([item[0] for item in data]).chunk(input_window, 1))  
    target = torch.stack(torch.stack([item[1] for item in data]).chunk(input_window, 1))
    return input, target

4. 模型训练以及评估

下面是模型训练的代码。具体地,就是通过遍历训练集,通过既定的loss,对参数进行反向传播,其中用到了梯度裁剪的技巧用于防止梯度爆炸,然后每间隔几个间隔打印一下loss。

def train(train_data):
    model.train()


    for batch_index, i in enumerate(range(0, len(train_data) - 1, batch_size)):
        start_time = time.time()
        total_loss = 0
        data, targets = get_batch(train_data, i, batch_size)
        optimizer.zero_grad()
        output = model(data)
        loss = criterion(output, targets)
        loss.backward()
        torch.nn.utils.clip_grad_norm_(model.parameters(), 0.7)
        optimizer.step()


        total_loss += loss.item()
        log_interval = int(len(train_data) / batch_size / 5)
        if batch_index % log_interval == 0 and batch_index > 0:
            cur_loss = total_loss / log_interval
            elapsed = time.time() - start_time
            print('| epoch {:3d} | {:5d}/{:5d} batches | lr {:02.6f} | {:5.2f} ms | loss {:5.5f} | ppl {:8.2f}'
                  .format(epoch, batch_index, len(train_data) // batch_size, scheduler.get_lr()[0], elapsed * 1000 / log_interval, cur_loss, math.exp(cur_loss)))

接下来是对模型进行评估的代码。

def evaluate(eval_model, data_source):
    eval_model.eval() 
    total_loss = 0
    eval_batch_size = 1000
    with torch.no_grad():
        for i in range(0, len(data_source) - 1, eval_batch_size):
            data, targets = get_batch(data_source, i, eval_batch_size)
            output = eval_model(data)
            total_loss += len(data[0]) * criterion(output, targets).cpu().item()
    return total_loss / len(data_source)

最后,是模型运行过程的可视化。

def plot_and_loss(eval_model, data_source, epoch):
    eval_model.eval()
    total_loss = 0.
    test_result = torch.Tensor(0)
    truth = torch.Tensor(0)
    with torch.no_grad():
        for i in range(0, len(data_source) - 1):
            data, target = get_batch(data_source, i, 1)
            output = eval_model(data)
            total_loss += criterion(output, target).item()
            test_result = torch.cat((test_result, output[-1].view(-1).cpu()), 0)
            truth = torch.cat((truth, target[-1].view(-1).cpu()), 0)


    plt.plot(test_result, color="red")
    plt.plot(truth, color="blue")
    plt.grid(True, which='both')
    plt.axhline(y=0, color='k')
    plt.savefig('graph/transformer-epoch%d.png' % epoch)
    plt.close()


    return total_loss / i

5. 模型运行

最后,对模型进行运行。其中用到了mse作为loss,adam作为优化器,以及设定学习率的调度器,最后运行200个epoch,每隔10个epoch在测试集上评估一下模型。

train_data, val_data = get_data()
model = TransAm().to(device)
criterion = nn.MSELoss()
lr = 0.005
optimizer = torch.optim.AdamW(model.parameters(), lr=lr)
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, 1, gamma=0.95)
epochs = 200


for epoch in range(1, epochs + 1):
    epoch_start_time = time.time()
    train(train_data)


    if (epoch % 10 is 0):
        val_loss = plot_and_loss(model, val_data, epoch)
    else:
        val_loss = evaluate(model, val_data)


    print('-' * 89)
    print('| end of epoch {:3d} | time: {:5.2f}s | valid loss {:5.5f} | valid ppl {:8.2f}'.format(epoch, (
                time.time() - epoch_start_time), val_loss, math.exp(val_loss)))
    print('-' * 89)
    scheduler.step()

下面是运行的结果,可以看到loss明显降低了。

cuda
| epoch   1 |     2/   10 batches | lr 0.005000 |  7.83 ms | loss 39.99368 | ppl 233902099994043520.00
| epoch   1 |     4/   10 batches | lr 0.005000 |  7.81 ms | loss 7.20889 | ppl  1351.39
| epoch   1 |     6/   10 batches | lr 0.005000 | 11.10 ms | loss 1.68758 | ppl     5.41
| epoch   1 |     8/   10 batches | lr 0.005000 |  9.35 ms | loss 0.00833 | ppl     1.01
| epoch   1 |    10/   10 batches | lr 0.005000 |  7.81 ms | loss 1.18041 | ppl     3.26
-----------------------------------------------------------------------------------------
| end of epoch   1 | time:  1.96s | valid loss 2.58557 | valid ppl    13.27


...


| end of epoch 198 | time:  0.30s | valid loss 0.00032 | valid ppl     1.00
-----------------------------------------------------------------------------------------
| epoch 199 |     2/   10 batches | lr 0.000000 | 15.62 ms | loss 0.00057 | ppl     1.00
| epoch 199 |     4/   10 batches | lr 0.000000 | 15.62 ms | loss 0.00184 | ppl     1.00
| epoch 199 |     6/   10 batches | lr 0.000000 | 15.62 ms | loss 0.00212 | ppl     1.00
| epoch 199 |     8/   10 batches | lr 0.000000 |  7.81 ms | loss 0.00073 | ppl     1.00
| epoch 199 |    10/   10 batches | lr 0.000000 |  7.81 ms | loss 0.00057 | ppl     1.00
-----------------------------------------------------------------------------------------
| end of epoch 199 | time:  0.30s | valid loss 0.00032 | valid ppl     1.00
-----------------------------------------------------------------------------------------
| epoch 200 |     2/   10 batches | lr 0.000000 | 15.62 ms | loss 0.00053 | ppl     1.00
| epoch 200 |     4/   10 batches | lr 0.000000 |  7.81 ms | loss 0.00177 | ppl     1.00
| epoch 200 |     6/   10 batches | lr 0.000000 |  7.81 ms | loss 0.00224 | ppl     1.00
| epoch 200 |     8/   10 batches | lr 0.000000 | 15.62 ms | loss 0.00069 | ppl     1.00
| epoch 200 |    10/   10 batches | lr 0.000000 |  7.81 ms | loss 0.00049 | ppl     1.00
-----------------------------------------------------------------------------------------
| end of epoch 200 | time:  0.62s | valid loss 0.00032 | valid ppl     1.00
-----------------------------------------------------------------------------------------

最后是模型的拟合效果,从实验结果中可以看出我们搭建的简单的Transformer模型可以实现相对不错的数据拟合效果。

fed731fd62b0a1bcf0e97b66535b3e67.png

4

总结

在这篇文章中,我们介绍了如何基于Pytorch框架搭建一个基于Transformer的股票预测模型,并通过真实股票数据对模型进行了实验,可以看出Transformer模型对股价预测具有一定的效果。另外,文中只是做了一个简单的demo,其中仍然有很多可以改进的地方,如采用更多有意义的输入数据,优化其中的一些组件等。除此之外,目前基于Transformer的模型层出不穷,其中也有很多值得我们去学习,大家也可以采用更先进的Transformer模型进行试验。

reference:

[1] Vaswani A, Shazeer N, Parmar N, et al. Attention is all you need[J]. arXiv preprint arXiv:1706.03762, 2017.

[2] https://github.com/oliverguhr/transformer-time-series-prediction

完整代码请在《人工智能量化实验室》公众号后台回复“101”关键字。本文内容仅仅是技术探讨和学习,并不构成任何投资建议。

8f13a6475dd46a492920b195815b08a9.png

b01feb70300b8a0402df4f1d99356864.png

了解更多人工智能与
量化金融知识

<-请扫码关注

让我知道你在看

4fefebfabbab4fe065df1a8303beaf89.gif

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【python量化】将Transformer模型用于股票价格预测 的相关文章

随机推荐

  • STM32 复位电路设计

    在此之前我是个只会抄写原理图的工程师 xff0c 每当遇到一个问题时 xff0c 确需要解决很久 xff0c 最根本的原因在于不明白其中的原理 xff0c 这次补充一下单片机复位电路设计 1 为什么要设计复位电路 xff1f 在做一件事情之
  • STM32核心板设计——电源设计

    1 STM32 数据手册电源部分研读 RTC电源管脚为V BAT 电源范围为1 8 3 6V xff0c 主要用于RTC时钟的供电 xff0c RTC在大部分场合用于保存一些重要的参数 xff0c 比如在电脑主板上用于保存boss的信息 x
  • stm32的复位电路问题

    现在比较流行的复位方式是这样的 xff1a 但我们都知道对于结构紧凑型硬件来说 xff0c 多一个电阻都是没必要的 在没有手动复位需求的场合 xff0c 能不能删掉按键与R24 xff0c 仅保留104电容 xff1f 通过阅读stm32
  • 外设驱动库开发笔记21:BME680环境传感器驱动

    环境传感器是一类我们很常用的传感器 它可以方便我们获取压力 温度 湿度以及空气质量等数据 在这一篇中 xff0c 我们将分析 BME680 环境传感器的功能 xff0c 并设计和实现 BME680 环境传感器的驱动 1 功能概述 BME68
  • 外设驱动库开发笔记45:MS4515DO压力传感器驱动

    很多时候我们需要检测流量和压力这些参数 xff0c 比如我们要检测大气压 xff0c 或者通过测量差压来获得输送流体的流量等 xff0c 都需要用到压力传感器 这一篇我们就来讨论MS4515DO压力传感器的数据获取 1 功能概述 MS451
  • 一个好看的CSS样式表格

    一个好看的CSS样式表格 自动换整行颜色的CSS样式表格 xff08 需要用到JS xff09 自动换整行颜色的CSS样式表格源代码 自动换整行颜色的CSS样式表格 xff08 需要用到JS xff09 这个CSS表格会自动切换每一行的颜色
  • docker删除镜像

    docker要删除镜像 xff0c 先要删除依赖它的容器 1 删除容器 docker ps 查看正在运行的容器 docker ps a 查看所有容器 docker rm container id 删除容器 2 删除镜像 docker ima
  • FreeRTOS如何结束和重新启动调度程序

    大多数主机或桌面系统 xff08 比如Linux xff0c Mac或Windows xff09 都有一个正常的用例 xff0c 你可以在早上启动操作系统 xff0c 然后在晚上关闭它 xff0c 然后你就离开机器 嵌入式系统是不同的 xf
  • [显存被占满,程序无法运行问题]ResourceExhaustedError (see above for traceback): OOM when allocating tensor

    最近在实验室的服务器上跑tensorflow程序 xff0c 一直都没有报错 xff0c 但是今天却突然报错 xff0c 而且出错提示显示的内容从未见到过 xff0c 错误提示如下 xff1a 错误提示资源耗尽 xff0c 无法分配tens
  • 解读神经网络十大误解,再也不会弄错它的工作原理(转载自机器之心)

    神经网络是机器学习算法中最流行和最强大的一类 在计量金融中 xff0c 神经网络常被用于时间序列预测 构建专用指标 算法交易 证券分类和信用风险建模 它们也被用于构建随机过程模型和价格衍生品 尽管神经网络有这些用处 xff0c 但它们却往往
  • 树莓派 Raspberry Pi VNC屏幕无法显示、软键盘、摄像头实时图传、固定IP等环境配置

    目录 1 VNC屏幕无法显示 2 树莓派软键盘安装 3 摄像头实时图传配置 xff0c 可用于图像监控系统 4 安装VIM与固定IP 1 VNC屏幕无法显示 在树莓派终端 xff0c 输入 sudo raspi config 选择接口配置
  • 在Jetson上配置RealSense相机驱动

    1 下载源码 https github com IntelRealSense librealsense span class token builtin class name cd span librealsense scripts set
  • aruco marker使用笔记

    在英伟达Jetson Xaiver开发板上配置 SDK环境 opencv 4 1 1 CUDA 10 2 1 git clone https github com pal robotics aruco ros 2 复制到catkin ws
  • catkin_make命令

    catkin make是在catkin工作区中构建代码的便捷工具 catkin make遵循catkin工作区的标准布局 xff0c 如REP 128中所述 用法 假设您的catkin工作区位于 catkin ws中 xff0c 则应始终在
  • docker容器中运行界面程序

    Docker比较常用的场景是 运行无界面的后台服务 或者 运行Web服务 不过有时出于个人的喜好或特定的需求 xff0c 我们会希望在Docker中运行带图形界面的应用程序 将容器中的图形界面展示到外部的一般性思路 xff1a 目前Unix
  • linux录屏

    Linux下好用的录屏软件是kazam录屏后视频处理软件kdenlive根据剪辑好的视频撰写解说词 xff0c 使用讯飞配音app将解说词文字转换为语音mp3将语音与视频通过kdenlive软件合成在一起 xff0c 完美的演示视频诞生了
  • 【python】conda和pip安装库之间的区别

    conda 首先 xff0c conda是一个通用的包管理器 xff0c 意思是什么语言的包都可以用其进行管理 xff0c 自然也就包括Python了 在安装Anaconda或者Miniconda时 xff0c 会对conda进行一同安装
  • OpenHarmony-Overview_zh

    OpenHarmony开源项目 项目介绍 OpenHarmony是开放原子开源基金会 xff08 OpenAtom Foundation xff09 旗下开源项目 xff0c 定位是一款面向全场景的开源分布式操作系统 OpenHarmony
  • 【python量化】用时间卷积神经网络(TCN)进行股价预测

    写在前面 下面这篇文章首先主要简单介绍了目前较为先进的时间序列预测方法 时间卷积神经网络 xff08 TCN xff09 的基本原理 xff0c 然后基于TCN的开源代码 xff0c 手把手教你如何通过时间卷积神经网络来进行股价预测 xff
  • 【python量化】将Transformer模型用于股票价格预测

    写在前面 下面的这篇文章主要教大家如何搭建一个基于Transformer的简单预测模型 xff0c 并将其用于股票价格预测当中 原代码在文末进行获取 1 Transformer模型 Transformer 是 Google 的团队在 201