DeepFin / models /custom_policies.py
Amós e Souza Fernandes
Upload 120 files
5f10e37 verified
# rnn/agents/custom_policies.py (NOVO ARQUIVO, ou adicione ao deep_portfolio.py)
import gymnasium as gym # Usar gymnasium
import tensorflow as tf
from stable_baselines3.common.torch_layers import BaseFeaturesExtractor as PyTorchBaseFeaturesExtractor
# Para TensorFlow, precisamos de um extrator de features compatível ou construir a política de forma diferente.
# Stable Baselines3 tem melhor suporte nativo para PyTorch. Para TF, é um pouco mais manual.
# VAMOS USAR A ABORDAGEM DE POLÍTICA CUSTOMIZADA COM TF DIRETAMENTE.
from stable_baselines3.common.policies import ActorCriticPolicy
from typing import List, Dict, Any, Union, Type
# Importar sua rede e configs
from .deep_portfolio import DeepPortfolioAgentNetwork
# from ..config import (NUM_ASSETS, WINDOW_SIZE, NUM_FEATURES_PER_ASSET, ...) # Importe do seu config real
# VALORES DE EXEMPLO (PEGUE DO SEU CONFIG.PY REAL)
NUM_ASSETS_POLICY = 4
WINDOW_SIZE_POLICY = 60
NUM_FEATURES_PER_ASSET_POLICY = 26
# Hiperparâmetros para DeepPortfolioAgentNetwork quando usada como extrator
ASSET_CNN_FILTERS1_POLICY = 32
ASSET_CNN_FILTERS2_POLICY = 64
ASSET_LSTM_UNITS1_POLICY = 64
ASSET_LSTM_UNITS2_POLICY = 32 # Esta será a dimensão das features latentes para ator/crítico
ASSET_DROPOUT_POLICY = 0.2
MHA_NUM_HEADS_POLICY = 4
MHA_KEY_DIM_DIVISOR_POLICY = 2 # Para key_dim = 32 // 2 = 16
FINAL_DENSE_UNITS1_POLICY = 128
FINAL_DENSE_UNITS2_POLICY = ASSET_LSTM_UNITS2_POLICY # A saída da dense2 SÃO as features latentes
FINAL_DROPOUT_POLICY = 0.3
class TFPortfolioFeaturesExtractor(tf.keras.layers.Layer): # Herda de tf.keras.layers.Layer
"""
Extrator de features customizado para SB3 que usa DeepPortfolioAgentNetwork.
A observação do ambiente é (batch, window, num_assets * num_features_per_asset).
A saída são as features latentes (batch, latent_dim).
"""
def __init__(self, observation_space: gym.spaces.Box, features_dim: int = ASSET_LSTM_UNITS2_POLICY):
super(TFPortfolioFeaturesExtractor, self).__init__()
self.features_dim = features_dim # SB3 usa isso para saber o tamanho da saída
# Instanciar a rede base para extrair features
# Ela deve retornar as ativações ANTES da camada softmax de alocação.
self.network = DeepPortfolioAgentNetwork(
num_assets=NUM_ASSETS_POLICY,
sequence_length=WINDOW_SIZE_POLICY,
num_features_per_asset=NUM_FEATURES_PER_ASSET_POLICY,
asset_cnn_filters1=ASSET_CNN_FILTERS1_POLICY,
asset_cnn_filters2=ASSET_CNN_FILTERS2_POLICY,
asset_lstm_units1=ASSET_LSTM_UNITS1_POLICY,
asset_lstm_units2=ASSET_LSTM_UNITS2_POLICY, # Define a saída do asset_processor
asset_dropout=ASSET_DROPOUT_POLICY,
mha_num_heads=MHA_NUM_HEADS_POLICY,
mha_key_dim_divisor=MHA_KEY_DIM_DIVISOR_POLICY,
final_dense_units1=FINAL_DENSE_UNITS1_POLICY,
final_dense_units2=self.features_dim, # A saída da dense2 é a nossa feature latente
final_dropout=FINAL_DROPOUT_POLICY,
output_latent_features=True # MUITO IMPORTANTE!
)
print("TFPortfolioFeaturesExtractor inicializado e usando DeepPortfolioAgentNetwork (output_latent_features=True).")
def call(self, observations: tf.Tensor, training: bool = False) -> tf.Tensor:
# A DeepPortfolioAgentNetwork já lida com o fatiamento e processamento.
# Ela foi configurada para retornar features latentes.
return self.network(observations, training=training)
class CustomPortfolioPolicySB3(ActorCriticPolicy):
def __init__(
self,
observation_space: gym.spaces.Space,
action_space: gym.spaces.Space,
lr_schedule, # Função que retorna a taxa de aprendizado
net_arch: Optional[List[Union[int, Dict[str, List[int]]]]] = None, # Arquitetura para MLPs pós-extrator
activation_fn: Type[tf.Module] = tf.nn.relu, # Usar tf.nn.relu para TF
# Adicionar quaisquer outros parâmetros específicos que o extrator precise
features_extractor_kwargs: Optional[Dict[str, Any]] = None,
**kwargs,
):
if features_extractor_kwargs is None:
features_extractor_kwargs = {}
# A dimensão das features que o nosso extrator PortfolioFeatureExtractor vai cuspir.
# Deve ser igual a ASSET_LSTM_UNITS2_POLICY (ou final_dense_units2 do extrator)
# Se não for passado, o construtor do ActorCriticPolicy pode tentar inferir.
# Vamos passar explicitamente para garantir.
features_extractor_kwargs.setdefault("features_dim", ASSET_LSTM_UNITS2_POLICY) # Ou o valor que você definiu
super().__init__(
observation_space,
action_space,
lr_schedule,
net_arch=net_arch, # Para camadas Dense APÓS o extrator de features
activation_fn=activation_fn,
features_extractor_class=TFPortfolioFeaturesExtractor,
features_extractor_kwargs=features_extractor_kwargs,
**kwargs,
)
# Otimizador é criado na classe base.
# As redes de ator e crítico são construídas no método _build da classe base,
# usando o self.features_extractor e depois o self.mlp_extractor (que é
# construído com base no net_arch).
# Não precisamos sobrescrever _build_mlp_extractor se o features_extractor
# já fizer o trabalho pesado e o net_arch padrão para as cabeças for suficiente.
# Se quisermos MLPs customizados para ator e crítico APÓS o extrator:
# def _build_mlp_extractor(self) -> None:
# # self.mlp_extractor é uma instância de MlpExtractor (ou similar)
# # A entrada para ele é self.features_extractor.features_dim
# # Aqui, net_arch definiria a estrutura do mlp_extractor
# self.mlp_extractor = MlpExtractor(
# feature_dim=self.features_extractor.features_dim,
# net_arch=self.net_arch, # net_arch é uma lista de ints para camadas da política e valor
# activation_fn=self.activation_fn,
# device=self.device,
# )
# As redes de ação e valor (action_net, value_net) são então criadas
# no _build da classe ActorCriticPolicy, no topo do mlp_extractor.