Coletânea Python do ZERO às Redes Neurais Artificiais

Fernando Feltrin · Capítulo 280 de 287

Páginas do PDF

Coletânea Python do ZERO às Redes Neurais Artificiais

Redes Neurais Artificiais Intuitivas

REDES NEURAIS ARTIFICIAIS

INTUITIVAS

BOT Para mercado de Ações

 

Para darmos início a nossas implementações, começaremos com problemas de menor complexidade. O exemplo a seguir tem o objetivo de demonstrar como que uma inteligência artificial é capaz de aprender a realizar corretagem no mercado de ações. Para isso, criaremos um Bot que por sua vez aprenderá a buscar de forma autônoma os dados de ações de uma determinada carteira, aprender seus padrões, analisar em tempo real as operações que estão sendo realizadas assim como tomar iniciativa de comprar ou vender uma determinada ação.

Conceitualmente pode parecer algo complexo, mas seguindo nossa lógica gradual, por hora raciocine que o que teremos de fazer é um agente que buscará as informações do mercado financeiro a partir da internet, criará uma base de dados em memória com informações dos padrões de atividade em geral (como em uma série temporal) e dos últimos lançamentos realizados, com base nisso, finalmente o agente realizará uma série de tentativas aprendendo a operar o mercado buscando obviamente, sempre obter lucro.

 

Importante deixar bem claro aqui que análise financeira e corretagem de ações é algo bastante complexo, que depende de uma série de fatores e nosso agente estará simulando as atividades de corretagem em forma de demonstração. Caso você queira usar este tipo de rede neural em suas aplicações de dinheiro real, recomendo fortemente que a rede neural apenas sirva como um viés de confirmação, apontando melhores momentos para se realizar ações de compra ou venda.

 

De forma resumida, e com propósito de organizar nossa sequência lógica dos fatos, basicamente o que faremos é:

 

- Instalação das bibliotecas e módulos que serão utilizados em nosso código.

- Importação das bibliotecas e módulos específicos.- Construção da arquitetura de rede neural artificial intuitiva.- Criação da base de memória e pré-processamento dos dados.- Carregamento da memória de curto prazo.- Criação dos estados iniciais de nosso agente.- Treinamento da IA para seu devido propósito.- Definição final do modelo.

- Execução de nossa rede neural artificial intuitiva.- Interpretação dos dados obtidos em tempo real.

 

Partindo para a prática!!!

Para nossa rede neural artificial intuitiva, iremos criar um ambiente

isolado do ambiente de virtualização padrão do Windows, dessa forma, teremos nele apenas o núcleo da linguagem Python e as bibliotecas que farão parte desse projeto, facilitando assim a exportação do mesmo para outra máquina.

O processo de criar um ambiente virtual isolado pode ser feito de duas

formas, via Anaconda Navigator ou via Prompt de Comando.

No Anaconda Navigator, abrindo a aba a esquerda Environments, em seguida clicando no botão Create. Na janela que irá surgir, definiremos um nome para nosso ambiente (no meu caso AITRADE), assim como a versão do Python. Clicando em Create basta esperar alguns minutos que o ambiente estará devidamente criado e pronto para ser ativado.

 

Via Prompt Anaconda, podemos criar o ambiente virtual de acordo com o comando mostrado na imagem, conda create -n AITRADE python=3.7 anaconda.

Em seguida para que possamos instalar as bibliotecas complementares no ambiente isolado recém criado precisamos ativar o mesmo via termina, isso é feito pelo comando conda activate AITRADE.

 

Na sequência, por meio do comando pip install tensorflow==1.15 estamos instalando a referida versão em nosso ambiente isolado AITRADE do TensorFlow, biblioteca do Google que incorpora uma série de ferramentas otimizadas para criação de redes neurais artificiais.

Note que para este exemplo estamos usando a versão cpu 1.15, isto se dará porque a versão mais recente do TensorFlow (2.X) descontinuou algumas das funções que estaremos utilizando em nosso exemplo. Também foram realizados testes com a versão 2.0.0.alpha0 obtivendo apenas alguns avisos de funções a serem descontinuadas, tendo o código totalmente funcional.

Sinta-se à vontade para instalar a versão gpu, caso você tenha uma placa de vídeo dedicada e obviamente queira uma melhor performance no processamento da rede.

Uma vez feita a instalação corretamente do TensorFlow e suas dependências, iremos instalar uma outra biblioteca independente chamada Pandas DataReader, módulo independentemente da biblioteca Pandas que nos permitirá a leitura das bases de dados a partir da internet e não apenas a partir de um arquivo local.

 

O processo é exatamente o mesmo da instalação anterior, apenas alterando o comando para pip install pandas-datareader.

Finalizando esta etapa, realizamos as instalações das duas últimas bibliotecas necessárias, matplotlib, responsável por oferecer plotagem de gráficos sobre os resultados de nossos processamentos e tqdm que servirá para criar barras de progresso mais intuitivas quando estivermos visualizando a execução de nossa rede neural.

 

Partindo para o código!!!

 

Uma última verificação antes de partirmos efetivamente para o código deve ser realizada, abrindo o Spyder, na barra inferior da janela da IDE verifique se a mesma está trabalhando no ambiente virtualizado correto (nesse caso AITRADE), se sim, podemos finalmente partir para o código.

Como sempre, todo processo se inicia com as importações das bibliotecas, módulos e ferramentas que estaremos utilizando ao longo de nosso código. Para isso, via comando import inicialmente importamos math, biblioteca padrão para operações matemáticas simples; random que nos permitirá gerar números aleatórios para nossos estados; numpy que por sua vez é muito utilizada para operações matemáticas sobre matrizes numéricas (arrays numpy); pandas que nos fornecerá ferramentas e funções com suporte a dados de planilhas; tensorflow que será a biblioteca onde criaremos toda arquitetura de nossa rede neural artificial; matplotlib que oferece ferramentas para plotagem de gráficos e por fim pandas_datareader, módulo independentemente da biblioteca pandas que nos permitirá realizar o carregamento de dados para a memória a partir da internet.

 

Na sequência realizamos algumas importações pontuais, a fim de obter melhor performance, uma prática comum é quando possível, apenas carregar as ferramentas que de fato serão usadas de determinadas bibliotecas. Dessa forma, por meio dos comandos from import, da biblioteca tqdm, que por sua vez oferece meios de plotagem de linhas de progresso, tqdm_notebook e tqdm, para que possamos acompanhar de forma visual o progresso de cada tomada de decisão de nosso agente; De pandas.util.testing importamos todas funções, uma vez que uma das etapas a ser implementada é a de avaliação de performance de nossa rede neural artificial; Por fim, de collections importamos deque, função que permitirá a rápida manipulação da base de dados atualizando os últimos valores lidos e interpretados da mesma.

Em seguida criamos nossa primeira classe, chamada AI_Trader( ), sem atributos mesmo. Dentro de sua estrutura criamos o método construtor __init__( ) onde passamos como parâmetros self, state_size que receberá um valor como definição do número de estados a serem processados; action_space parametrizado com 3, uma vez que teremos tomadas de decisão baseadas em 3 estados (último estado, estado atual e próximo estado); model_name apenas dando um nome a nosso Bot.

Também são criados os referidos objetos dentro do método construtor, alocando assim variáveis para receber dados/valores específicos a serem repassados por meio de outras funções.

Da mesma forma é criado o objeto memory que recebe como atributo deque por sua vez parametrizado com maxlen = 2000, aqui estamos criando uma variável (literalmente alocando um espaço na memória) que manterá sempre carregado uma base de dados com os últimos 2000 valores lidos a partir da fonte, essa é a memória de longa duração de nosso agente, é por meio dela que serão lidos e aprendidos os padrões de sua função de corretor.

Finalizando este bloco, é criado o objeto model_name que foi definido com o nome padrão AITrader.

 

Dando sequência, ainda indentado em nosso método construtor temos alguns parâmetros definidos manualmente (que de preferência devem receber outros valores e serem submetidos a novos testes). O primeiro deles, gamma se equivale com a taxa de aprendizado de outros modelos de rede neural artificial, porém tome cuidado para não confundir, não somente a nomenclatura usual é diferente, mas a função de gama é parametrizar um ajuste de pesos em tempo real, diferente do learning rate que aprende “em gerações” de execução da rede neural.

Em seguida os objetos epsilon, epsilon_final e epsilon_decay terão o papel de manualmente definir parâmetros que combinados com a “taxa de aprendizado” simularão o processo de descida do gradiente, onde os ajustes dos pesos se dão de forma a rede neural buscar o melhor valor possível de taxa de acerto em suas tomadas de decisão, apenas não confundir também com a descida do gradiente de fato, que em um modelo de rede neural convencional possui seus valores atualizados a cada época de execução da rede neural, enquanto aqui, neste modelo em particular, esse processo é retroalimentado em ciclo ininterruptamente.

Por fim é criado o objeto model que por sua vez carrega a função model_builder( ), já que todos esses parâmetros iniciais são o gatilho inicial de execução da rede neural artificial intuitiva, porém a mesma a partir de certo momento se retroalimentará continuamente realizando processamento em tempo real.

 

Prosseguindo com nosso código, hora de criar a arquitetura da rede neural artificial em si. Aqui existem dois pontos importantes a se observar, primeiro deles que a criação dessa estrutura se dará por meio da biblioteca Keras, incorporada ao Tensorflow, existem outras bibliotecas que oferecem até ferramentas mais simples para se criar a estrutura da rede neural, porém aqui, para fins de uma rede neural artificial intuitiva, conseguimos uma ótima performance via Keras. Outro ponto é que construiremos essa estrutura situada dentro de uma classe já que iremos retroalimentar a mesma e a reprocessar constantemente como qualquer outra função

Sendo assim, inicialmente criamos a classe model_builder( ) apenas instanciando ela mesma, dentro de sua estrutura criamos um objeto de nome model que por sua vez recebe como atributo a função Sequential( ) que permitirá a criação de um modelo sequencial, em outras palavras, uma estrutura de rede neural multicamada onde as camadas estão interconectadas entre si, permitindo o processo de processamento de dados de forma subsequente. Desmembrando esse código, temos a função Sequential( ), parte do módulo models da biblioteca keras, parte integrante do TensorFlow que aqui estamos referenciando como tf.

Em seguida é criada a camada de entrada dessa rede neural por meio da função add( ), que por sua vez está parametrizada com a classe Dense( ), do módulo layers da biblioteca keras, parametrizada com units = 32, ou seja, 32 neurônios na camada de entrada; activation = “relu” (ReLU – Rectified Linear Unit), muito usada para problemas de classificação binária; input_dim = self.state_size, que de modo geral irá importar o tamanho da matriz conforme especificado pelo supervisor.

Da mesma forma são criadas mais duas camadas de neurônios, essas por sua vez, fazendo uso da função Dense( ) parametrizando a mesma com units = 64 e 128 respectivamente, ambas com activation = “relu”, ou seja, a primeira camada intermediária terá 64 neurônios em sua composição e a segunda 128, ambas usando da função de ativação ReLU, automaticamente realizando uma classificação de ativação ou não do neurônio da camada subsequente.

Nos mesmos moldes criamos uma terceira camada, que em modelos de redes neurais artificiais convencionais é a chamada camada de saída, uma vez que a mesma encerra o processamento da rede neural fornecendo dados de resultado. Aqui, units = self.action_space está pegando os valores de saída e transformando eles em um estado que retroalimentará a rede. *activation = “linear” apenas replica o resultado, não aplicando nenhuma função sobre ele.

Como de praxe criamos o compilador para essa rede, via função compile( ) parametrizado com loss = “mse”, definindo que a função de perda/custo será uma métrica chamada Mean Square Error, onde os valores obtidos são somados, elevados ao quadrado e comparado com os dados estimados, dando assim mais peso aos erros para que a rede os identifique e aprenda a não replicá-los.

Finalizando, optimizer recebe Adam como função, parametrizado com lr = 0.001, o que significa que aplica-se sobre os resultados obtidos do processamento uma função de otimização que possui uma taxa de aprendizado de 0.001, este parâmetro define como será internamente o processo de atualização dos pesos das amostras, para um ajuste fino dos mesmos em busca de padrões.

Os valores encontrados pela rede são retornados e atribuídos a model.

 

Dando continuidade criamos uma das funções que simulam a arbitrariedade de nosso Agente. Logo, criamos a classe trade que por sua vez deve receber um estado. Dentro de sua estrutura é criada uma estrutura condicional onde se os números gerador pela função random( ) forem menores ou iguais aos valores de self.epsilon, é definido via randrange( ) um parâmetro de tamanho/intervalo para self.action_space, dessa forma criamos uma dependência de um estado para uma tomada de ação, continuamente.

Também é criada uma variável actions que por sua vez é atribuída com a função model_predict( ) que recebe um estado, ou seja, existe uma leitura constante do estado para que se possa ter o gatilho de desempenhar uma função. Finalizando, é retornado via função np.argmax( ) o valor máximo encontrado no índice 0 de actions. O que em outras palavras, nesse caso é simplesmente o último valor encontrado.

Ainda criando o sistema de dependências de ações, que estipulam para nosso agente que ele sempre deve estar em busca da próxima ação a ser tomada, criamos um propósito a ser alcançado, nesse caso, uma recompensa.

Preste bastante atenção pois esta etapa pode parecer um tanto confusa,

pois haverá o cruzamento de uma série de dados agora finalmente interligados.

Então, inicialmente criamos a classe batch_train( ) que recebe um

batch_site, um tamanho que podemos entender como o tamanho da memória de curta duração de nosso Agente. Dentro de sua estrutura é criado um objeto de nome batch atribuído simplesmente com uma lista vazia e na sequência um laço de repetição onde é lido o tamanho de self.memory subtraído do valor de batch_size (valor ainda a ser repassado) somado de 1, também é lido novamente o valor de self.memory e a cada repetição do laço é adicionado o último valor encontrado em self.memory. Raciocine que esta primeira etapa está apenas construindo uma base de dados para a memória de nosso Agente.

Em seguida é criado um segundo laço de repetição, realizando a leitura

dos dados de state, action, reward, next_state, done do objeto batch, para cada repetição é imposta a condição de que, se não houver um valor para done, uma variável chamada reward receberá como valor ela mesma somada do valor de gamma, multiplicada pelo valor máximo obtido via predict( ) parametrizado com next_state em seu índice 0.

Vamos entender o que está acontecendo aqui, repare que é criada a variável done como um objetivo final (internamente inclusive o valor padrão dessa variável é booleano) e o que queremos por hora é que justamente esse estado não seja facilmente alcançado, para que o processo não termine, mas funcione em ciclo. Para que esse loop não seja eterno, é criado um sistema de recompensa, onde podemos especificar que quando um determinado valor for atingido em reward, aí sim o Agente pode parar.

Do mesmo modo é criado um objeto target que por sua vez recebe como atributo a função predict( ) parametrizada com o valor de state. Quando o valor no índice 0 de target e action tiverem o mesmo valor de reward, aí sim um estado está completo.

Para que isso funcione é necessário um gatilho inicial, onde por meio da função fit( ) alimentamos toda essa estrutura condicional com um estado, um alvo e uma época de processamento via suas referidas variáveis state, target e epochs. Encerrando esse bloco de código temos uma última estrutura condicional onde se o valor de epsilon for maior do que epsilon_final, o mesmo deve ser multiplicado por ele mesmo e por epsilon_decay, haja visto que esta estrutura de memória tem um limite de tamanho/extensão a não ser ultrapassado.

 

Dando sequência definimos manualmente nossa função sigmoid( ) parametrizado com uma variável temporária x. A função sigmoide como bem conhecemos é muito utilizada quando queremos realizar uma classificação binária, onde os dados estarão normalizados entre uma escala de 0 a 1.

 

Falando em normalização não podemos nos esquecer de criar uma pequena estrutura de código que transliterará os valores obtidos até então de uma forma visualmente melhorada. Para isso, criamos uma função stocks_price_format( ) que recebe uma variável temporária n, dentro de sua estrutura é criada uma estrutura condicional onde enquanto o valor de n for menor que 0, o valor será exibido como negativo, apresentando duas casas decimais, caso contrário, exibido como positivo.

Criada a primeira metade de nossa estrutura de código, podemos começar a visualizar alguns dados. Seguindo com nosso código criamos uma variável de nome dataset que por sua vez recebe como atributo a função stocks_price_format( ) parametrizada em 100, em outras palavras estamos criando uma pequena base com os últimos 100 registros encontrados.

Diferente de outros modelos de redes neurais que temos uma base de

dados fixa, aqui precisamos de algo que esteja sempre atualizado e em conformidade. Sendo assim, por meio da ferramenta DataReader( ) iremos realizar a importação desses dados a partir da internet. Uma vez que essa ferramenta já vem pré-configurada para esse propósito, basta passarmos dois parâmetros para que a mesma consiga realizar a leitura e importação dos dados. “AAPL” é a sigla oficial para ações da Apple em diferentes mercados, assim como para data_source passamos o parâmetro “yahoo” já que queremos que tais dados sejam importados do Yahoo Finanças.

 

Apenas por curiosidade, acessando o site Yahoo Finanças, consultando qualquer portfólio, em sua aba Dados Históricos é possível ver os últimos lançamentos assim como baixar uma base de dados em planilha Excel com um intervalo de tempo predefinido.

 

Assim como toda e qualquer base de dados gerada a partir de uma planilha Excel, podemos consultar os dados usando as funções básicas as quais temos familiaridade. Apenas como exemplo, dataset.head( ) mostrará os 5 primeiros registros, index[-1] mostrará o último registro, [‘Close’] exibirá apenas os valores de fechamento das ações, etc...

 

Em seguida, criando de fato a função que será acionada para a importação e atualização dos dados da base, criamos a função dataset_loader( ) parametrizada com os dados da variável stock_name. Dentro de sua estrutura é instanciada a variável dataset que como feito antes, por meio de DataReade( ) recebe os dados de stock_name e da fonte “yahoo”.

É definida manualmente também, atribuindo para start_date e end_date, respectivamente, os valores de início e fim do período especificado para esse dataset, assim como é criada a variável close que terá atribuídos para si apenas os valores de fechamento das ações. Note que toda essa estrutura justamente retorna os valores para close, é uma medida que tomamos porque o que nos interessará (na memória de curta duração de nosso Agente) será a atualização deste dado, na sua última atualização possível via site.

Uma vez criada a função responsável pela leitura dos dados de entrada podemos avançar mais um pouco criando agora a função criadora de estados. Logo, criamos a função state_creator( ) que receberá posteriormente dados para data, timestep e window_size.

Inicialmente é criada, indentada dentro do corpo da função, a variável starting_id que recebe como atributos timestep (um dado temporal, correspondente a uma data) que subtrai desse valor o tamanho de window_size (intervalo de tempo) acrescentando 1 ao final da operação, dessa forma a data inicial a ser lida na base de dados é atualizada.

Em seguida é criada uma estrutura condicional onde se os valores de starting_id forem iguais ou maiores que 0, é atribuído para windowed_data o valor de data em sua última posição indexada. Caso contrário, é atribuído o valor de starting_id multiplicado pelos dados do índice 0 de data somado ao valor da última posição indexada. Repare que assim reforçamos aquela estrutura que gera uma dependência, sempre haverá a necessidade de que estes dados sejam atualizados, isso pode ser trabalhado em ciclo, reforçando a operacionalidade de nosso agente processando dados em tempo real simulado.

Em seguida é criada a variável state que inicialmente atribuída para si tem apenas uma lista vazia. Também criamos um laço de repetição onde é lido o último registro de window_size e por meio da função append( ) adicionamos nesse campo o resultado da função sigmoid( ) entre o penúltimo e o último dado registrado na base de dados. Apenas relembrando que a função sigmoide por sua vez retornará um valor 0 ou 1, que nesse contexto pode representar a tomada ou não de uma ação com base nos valores lidos.

Seguindo com o código, agora que temos uma estrutura já funcional, podemos definir alguns parâmetros a serem usados. Lembrando que definimos esses parâmetros manualmente e por meio de variáveis para que possamos os modificar de forma mais fácil e dinâmica.

Seguindo essa lógica criamos a variável stock_name que recebe “AAPL”

como atributo, uma vez que neste exemplo em particular estamos trabalhando com ações da Apple; data que recebe como atribuição o retorno da função dataset_loader( ) que por sua vez recebe o dado de stock_name (Caso você queira testar outro portfólio de carteiras de ações de outras empresas basta alterar o nome repassado a stock_name; s que instancia a função state_creator( ) passando um valor para data, 0 e 5 respectivamente. Apenas relembrando que s é o estado de nosso agente de acordo com a lógica da equação de Bellman.

 

Dando sequência criamos a variável trader que por sua vez instancia a classe AI_Trader( ) passando como atributo de classe os dados contidos em window_size. A partir desse ponto é possível também visualizar (caso não haja nenhum erro de sintaxe ou de cruzamento dos dados) o sumário de nossa rede neural artificial intuitiva, por meio da função print( ) parametrizada com trader.model.summary( ).

Via console é possível visualizar o sumário que descreve a arquitetura de nossa rede neural artificial intuitiva. Como visto anteriormente trata-se de uma estrutura bastante básica, com poucos neurônios e camadas de processamento, ainda assim, note que, de acordo com os dados acima, temos a interconexão de 11,171 neurônios, cada um por sua vez com seus respectivos valores, pesos, funções de ativação, etc...

 

Concluída a estrutura de nosso agente em si, uma prática bastante comum é criar uma estrutura onde ao mesmo tempo em que os dados são lidos e processados, possamos acompanhar de forma visual o processamento dos mesmos.

Para tal feito, inicialmente criamos um laço de repetição que percorrerá

cada episódio (cada ciclo de processamento) de nosso agente, exibindo em terminal o andamento dessas etapas. Novamente é instanciada a variável state chamando a função state_creator( ). É criada a variável total_profit inicialmente com valor zerado, que será atualizada com os dados referentes ao lucro ou perda obtidos no processo de corretagem. Da mesma forma também é criada sobre trader um objeto de inventário que inicialmente recebe uma lista vazia atribuída a si, mas que armazenará os dados das transações.

Em seguida é criado um novo laço de repetição dentro do laço atual,

onde fazendo o uso da ferramenta tqdm( ) parametrizada com o tamanho de data_samples nos exibirá uma barra de progresso em nosso console, facilitando a visualização do progresso de processamento em si.

Também é criada a variável reward, inicialmente com valor 0 atribuído,

uma vez que no gatilho inicial de nosso agente ainda não há o feedback de uma ação e automaticamente ainda não há uma recompensa ou penalidade aplicada sobre seu estado atual.

 

Ainda dentro do laço de repetição inicial agora criamos uma estrutura

condicional a ser executada. Basicamente, estipulamos que se o valor de action for igual a 1 será realizada a ação de compra de uma ação. Para isso simplesmente adicionamos ao inventário o valor lido em data em seu último estado. Também exibimos a mensagem que nosso Agente comprou uma determinada ação. Ainda nessa estrutura, caso o valor de action seja igual a 2 e o tamanho do inventário seja maior que 0, de buy_price é removido o valor 0 de seu inventário. Se você reparar, esta é apenas outra maneira de representar a necessidade de nosso agente de buscar o últimos valor obtido para que se realize todo um processamento sobre o mesmo, em continuidade.

Em seguida reward agora é atualizada com o valor máximo do estado

atual de data subtraindo o valor de buy_price. Da mesma forma é possível também definir que total_profit quando atualizado recebe o valor dele mesmo somado do valor atual de data menos o valor de buy_price, em outras palavras, devemos considerar todo o processo para separar apenas o que diz respeito ao lucro total da operação.

Finalizando, é exibida em tela uma primeira mensagem de que nesse caso nosso Agente vendeu uma ação e uma segunda mensagem exibindo qual foi o lucro obtido na operação, já que, corretagem trata-se de comprar uma determinada ação em um preço e vender em outro tentando sempre lucrar com essa diferença de valor de compra/venda.

 

Finalizando esse bloco, ainda dentro do laço de repetição inicial, agora simplesmente criamos uma estrutura condicional que define que se o valor de t for igual ao valor de data_samples subtraído 1, done recebe o estado True, caso contrário, False e o ciclo se repete.

 

Apenas lembrando que “o ciclo se repete” significa uma atualização do

estado atual e uma nova tomada de ação por parte de nosso Agente. Sendo assim caso a estrutura condicional anterior retorne False, é adicionado a memória de nosso agente novamente os últimos valores obtidos e atribuídos para state, action, reward e next_state, além da leitura do estado de done nessa própria condicional. Por fim é atualizado o valor de state com o valor de next_state ciclicamente.

 

Finalizando nosso código, é criada uma estrutura condicional onde simplesmente quando done for validado, é impresso em tela a mensagem do lucro total estimado, exibindo o valor contido em total_profit.

Também é criada uma estrutura condicional para atualização de

batch_train, haja visto que nosso agente simultaneamente ao seu processamento intuitivo está realizando aprendizado de máquina e a cada ciclo de processamento os padrões encontrados são guardados como parâmetros de aprendizado. Uma última estrutura condicional é criada onde sempre que o valor do resto da divisão entre episode e 10 for 0, os parâmetros dos pesos da rede neural são salvos em um arquivo tipo .h5 reutilizável.

Essa é uma prática comum porque uma vez terminado todo um ciclo de processamento, podemos guardar esses valores e exportar nosso agente para outra máquina, por exemplo, onde lidos esses valores o mesmo não precisará realizar todo seu aprendizado novamente do zero.

 

Sendo assim, ao rodar o código podemos acompanhar via console alguns dados, o primeiro deles, os dados do intervalo de tempo lidos a partir do Yahoo Finanças.

A visualização dos processamentos ciclo a ciclo, inclusive com as primeiras tomadas de decisão por parte de nosso Agente.

 

Progresso das primeiras corretagens, mantendo inconsistência entre valores de compra e venda, realizando os primeiros passos de aprendizado de máquina.

Resultados melhorando a medida que o Agente evolui.

 

Resultado final, nesse cenário simulado onde nosso Agente a partir das cotações das ações da Apple obtidas via Yahoo Finanças, aprendeu os padrões certos para obtenção de lucro a partir da corretagem das mesmas.

Nesse contexto, realizando operações baseadas nos últimos registros, retroalimentando a rede e tomando novas decisões, nosso Agente inicialmente possuía uma margem de acertos sobre suas ações praticamente nula, porém, com algumas horas de processamento é possível notar que o objetivo principal de obter lucro com base na diferença entre os valores pagos por uma ação e os valores de venda da mesma não só mantiveram uma taxa de acertos alta como um lucro bastante significativo.

 

Código Completo:

 

import math

import random

import numpy as np

import pandas as pd

import tensorflow as tf

import matplotlib.pyplot as plt

import pandas_datareader as data_reader

 

from tqdm import tqdm_notebook, tqdm

from pandas.util.testing import *

from collections import deque

 

class AI_Trader():

 

def __init__(self, state_size, action_space=3, model_name="AITrader"

self.state_size = state_size

self.action_space = action_space

self.memory = deque(maxlen = 2000)

self.model_name = model_name

 

self.gamma = 0.95

self.epsilon = 1.0

self.epsilon_final = 0.01

self.epsilon_decay = 0.995

self.model = self.model_builder()

 

def model_builder(self):

model = tf.keras.models.Sequential()

model.add(tf.keras.layers.Dense(units = 32,

activation = "relu",

input_dim = self.state_size))

model.add(tf.keras.layers.Dense(units = 64,

activation = "relu"))

model.add(tf.keras.layers.Dense(units = 128,

activation = "relu"))

model.add(tf.keras.layers.Dense(units = self.action_space,

activation = "linear"))

model.compile(loss = "mse",

optimizer = tf.keras.optimizers.Adam(lr = 0.001))

return model

def trade(self, state):

 

if random.random() <= self.epsilon:

return random.randrange(self.action_space)

actions = self.model.predict(state)

return np.argmax(actions[0])

 

def batch_train(self, batch_size):

batch = []

for i in range(len(self.memory) - batch_size + 1, len(self.memory)):

batch.append(self.memory[i])

for state, action, reward, next_state, done in batch:

if not done:

reward = reward+self.gamma*np.amax(self.model.predict(next_state) [0])

target = self.model.predict(state)

target[0][action] = reward

self.model.fit(state, target, epochs=1, verbose=0)

if self.epsilon > self.epsilon_final:

self.epsilon *= self.epsilon_decay

 

def sigmoid(x):

return 1 / (1 + math.exp(-x))

def stocks_price_format(n):

 

if n < 0:

return "- R$ {0:2f}".format(abs(n))

else:

return "R$ {0:2f}".format(abs(n))

 

dataset = stocks_price_format(100)

dataset = data_reader.DataReader("AAPL", data_source = "yahoo")

 

print(dataset.head())

print(str(dataset.index[0]).split()[0])

print(dataset.index[-1])

print(dataset['Close'])

 

def dataset_loader(stock_name):

dataset = data_reader.DataReader(stock_name, data_source = "yahoo"

start_date = str(dataset.index[0]).split()[0]

end_date = str(dataset.index[-1]).split()[0]

close = dataset['Close']

return close

 

def state_creator(data, timestep, window_size): starting_id = timestep - window_size + 1

 

if starting_id >= 0:

windowed_data = data[starting_id:timestep + 1]

else:

windowed_data = - starting_id * [data[0]] + list(data[0:timestep +

 

state = []

for i in range(window_size - 1):

state.append(sigmoid(windowed_data[i + 1] - windowed_data[i]))

 

return np.array([state]), windowed_data

 

stock_name = "AAPL"

data = dataset_loader(stock_name)

s = state_creator(data, 0, 5)

window_size = 10

episodes = 1000

batch_size = 32

data_samples = len(data) - 1

 

trader = AI_Trader(window_size)

print(trader.model.summary())

 

for episode in range(1, episodes + 1):

print(f'Etapa: {episode} de {episodes}')

state = state_creator(data, 0, window_size + 1)

total_profit = 0

trader.inventory = []

for t in tqdm(range(data_samples)):

action = trader.trade(state)

next_state = state_creator(data, t + 1, window_size + 1)

reward = 0

 

if action == 1:

trader.inventory.append(data[t])

print("AI Trader comprou: ", stocks_price_format(data[t]))

elif action == 2 and len(trader.inventory) > 0:

buy_price = trader.inventory.pop(0)

 

reward = max(data[t] - buy_price, 0)

total_profit += data[t] - buy_price

print("AI Trader vendeu: ", stocks_price_format(data[t]),

"Lucro de: " + stocks_price_format(data[t] - buy_price))

if t == data_samples - 1:

done = True

else:

done = False

 

trader.memory.append((state, action, reward, next_state, done))

state = next_state

 

if done:

print("########################")

print(f'Lucro Total Estimado: {total_profit}')

print("########################")

 

if len(trader.memory) > batch_size:

trader.batch_train(batch_size)

 

if episode % 10 == 0:

trader.model.save("ai_trader_{}.h5".format(episode))