organize pytorch files
This commit is contained in:
95
freqtrade/freqai/torch/PyTorchMLPModel.py
Normal file
95
freqtrade/freqai/torch/PyTorchMLPModel.py
Normal file
@@ -0,0 +1,95 @@
|
||||
import logging
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PyTorchMLPModel(nn.Module):
|
||||
"""
|
||||
A multi-layer perceptron (MLP) model implemented using PyTorch.
|
||||
|
||||
This class mainly serves as a simple example for the integration of PyTorch model's
|
||||
to freqai. It is not optimized at all and should not be used for production purposes.
|
||||
|
||||
:param input_dim: The number of input features. This parameter specifies the number
|
||||
of features in the input data that the MLP will use to make predictions.
|
||||
:param output_dim: The number of output classes. This parameter specifies the number
|
||||
of classes that the MLP will predict.
|
||||
:param hidden_dim: The number of hidden units in each layer. This parameter controls
|
||||
the complexity of the MLP and determines how many nonlinear relationships the MLP
|
||||
can represent. Increasing the number of hidden units can increase the capacity of
|
||||
the MLP to model complex patterns, but it also increases the risk of overfitting
|
||||
the training data. Default: 256
|
||||
:param dropout_percent: The dropout rate for regularization. This parameter specifies
|
||||
the probability of dropping out a neuron during training to prevent overfitting.
|
||||
The dropout rate should be tuned carefully to balance between underfitting and
|
||||
overfitting. Default: 0.2
|
||||
:param n_layer: The number of layers in the MLP. This parameter specifies the number
|
||||
of layers in the MLP architecture. Adding more layers to the MLP can increase its
|
||||
capacity to model complex patterns, but it also increases the risk of overfitting
|
||||
the training data. Default: 1
|
||||
|
||||
:returns: The output of the MLP, with shape (batch_size, output_dim)
|
||||
"""
|
||||
|
||||
def __init__(self, input_dim: int, output_dim: int, **kwargs):
|
||||
super().__init__()
|
||||
hidden_dim: int = kwargs.get("hidden_dim", 256)
|
||||
dropout_percent: int = kwargs.get("dropout_percent", 0.2)
|
||||
n_layer: int = kwargs.get("n_layer", 1)
|
||||
self.input_layer = nn.Linear(input_dim, hidden_dim)
|
||||
self.blocks = nn.Sequential(*[Block(hidden_dim, dropout_percent) for _ in range(n_layer)])
|
||||
self.output_layer = nn.Linear(hidden_dim, output_dim)
|
||||
self.relu = nn.ReLU()
|
||||
self.dropout = nn.Dropout(p=dropout_percent)
|
||||
|
||||
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
||||
x = self.relu(self.input_layer(x))
|
||||
x = self.dropout(x)
|
||||
x = self.blocks(x)
|
||||
x = self.output_layer(x)
|
||||
return x
|
||||
|
||||
|
||||
class Block(nn.Module):
|
||||
"""
|
||||
A building block for a multi-layer perceptron (MLP).
|
||||
|
||||
:param hidden_dim: The number of hidden units in the feedforward network.
|
||||
:param dropout_percent: The dropout rate for regularization.
|
||||
|
||||
:returns: torch.Tensor. with shape (batch_size, hidden_dim)
|
||||
"""
|
||||
|
||||
def __init__(self, hidden_dim: int, dropout_percent: int):
|
||||
super().__init__()
|
||||
self.ff = FeedForward(hidden_dim)
|
||||
self.dropout = nn.Dropout(p=dropout_percent)
|
||||
self.ln = nn.LayerNorm(hidden_dim)
|
||||
|
||||
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
||||
x = self.ff(self.ln(x))
|
||||
x = self.dropout(x)
|
||||
return x
|
||||
|
||||
|
||||
class FeedForward(nn.Module):
|
||||
"""
|
||||
A simple fully-connected feedforward neural network block.
|
||||
|
||||
:param hidden_dim: The number of hidden units in the block.
|
||||
:return: torch.Tensor. with shape (batch_size, hidden_dim)
|
||||
"""
|
||||
|
||||
def __init__(self, hidden_dim: int):
|
||||
super().__init__()
|
||||
self.net = nn.Sequential(
|
||||
nn.Linear(hidden_dim, hidden_dim),
|
||||
nn.ReLU(),
|
||||
)
|
||||
|
||||
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
||||
return self.net(x)
|
193
freqtrade/freqai/torch/PyTorchModelTrainer.py
Normal file
193
freqtrade/freqai/torch/PyTorchModelTrainer.py
Normal file
@@ -0,0 +1,193 @@
|
||||
import logging
|
||||
import math
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
import pandas as pd
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from torch.optim import Optimizer
|
||||
from torch.utils.data import DataLoader, TensorDataset
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PyTorchModelTrainer:
|
||||
def __init__(
|
||||
self,
|
||||
model: nn.Module,
|
||||
optimizer: Optimizer,
|
||||
criterion: nn.Module,
|
||||
device: str,
|
||||
init_model: Dict,
|
||||
target_tensor_type: torch.dtype,
|
||||
squeeze_target_tensor: bool = False,
|
||||
model_meta_data: Dict[str, Any] = {},
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
:param model: The PyTorch model to be trained.
|
||||
:param optimizer: The optimizer to use for training.
|
||||
:param criterion: The loss function to use for training.
|
||||
:param device: The device to use for training (e.g. 'cpu', 'cuda').
|
||||
:param init_model: A dictionary containing the initial model/optimizer
|
||||
state_dict and model_meta_data saved by self.save() method.
|
||||
:param target_tensor_type: type of target tensor, for classification usually
|
||||
torch.long, for regressor usually torch.float.
|
||||
:param model_meta_data: Additional metadata about the model (optional).
|
||||
:param squeeze_target_tensor: controls the target shape, used for loss functions
|
||||
that requires 0D or 1D.
|
||||
:param max_iters: The number of training iterations to run.
|
||||
iteration here refers to the number of times we call
|
||||
self.optimizer.step(). used to calculate n_epochs.
|
||||
:param batch_size: The size of the batches to use during training.
|
||||
:param max_n_eval_batches: The maximum number batches to use for evaluation.
|
||||
|
||||
"""
|
||||
self.model = model
|
||||
self.optimizer = optimizer
|
||||
self.criterion = criterion
|
||||
self.model_meta_data = model_meta_data
|
||||
self.device = device
|
||||
self.target_tensor_type = target_tensor_type
|
||||
self.max_iters: int = kwargs.get("max_iters", 100)
|
||||
self.batch_size: int = kwargs.get("batch_size", 64)
|
||||
self.max_n_eval_batches: Optional[int] = kwargs.get("max_n_eval_batches", None)
|
||||
self.squeeze_target_tensor = squeeze_target_tensor
|
||||
if init_model:
|
||||
self.load_from_checkpoint(init_model)
|
||||
|
||||
def fit(self, data_dictionary: Dict[str, pd.DataFrame]):
|
||||
"""
|
||||
- Calculates the predicted output for the batch using the PyTorch model.
|
||||
- Calculates the loss between the predicted and actual output using a loss function.
|
||||
- Computes the gradients of the loss with respect to the model's parameters using
|
||||
backpropagation.
|
||||
- Updates the model's parameters using an optimizer.
|
||||
"""
|
||||
data_loaders_dictionary = self.create_data_loaders_dictionary(data_dictionary)
|
||||
epochs = self.calc_n_epochs(
|
||||
n_obs=len(data_dictionary["train_features"]),
|
||||
batch_size=self.batch_size,
|
||||
n_iters=self.max_iters
|
||||
)
|
||||
for epoch in range(epochs):
|
||||
# training
|
||||
losses = []
|
||||
for i, batch_data in enumerate(data_loaders_dictionary["train"]):
|
||||
xb, yb = batch_data
|
||||
xb = xb.to(self.device)
|
||||
yb = yb.to(self.device)
|
||||
yb_pred = self.model(xb)
|
||||
loss = self.criterion(yb_pred, yb)
|
||||
|
||||
self.optimizer.zero_grad(set_to_none=True)
|
||||
loss.backward()
|
||||
self.optimizer.step()
|
||||
losses.append(loss.item())
|
||||
train_loss = sum(losses) / len(losses)
|
||||
|
||||
# evaluation
|
||||
test_loss = self.estimate_loss(data_loaders_dictionary, self.max_n_eval_batches, "test")
|
||||
logger.info(
|
||||
f"epoch {epoch}/{epochs}:"
|
||||
f" train loss {train_loss:.4f} ; test loss {test_loss:.4f}"
|
||||
)
|
||||
|
||||
@torch.no_grad()
|
||||
def estimate_loss(
|
||||
self,
|
||||
data_loader_dictionary: Dict[str, DataLoader],
|
||||
max_n_eval_batches: Optional[int],
|
||||
split: str,
|
||||
) -> float:
|
||||
self.model.eval()
|
||||
n_batches = 0
|
||||
losses = []
|
||||
for i, batch in enumerate(data_loader_dictionary[split]):
|
||||
if max_n_eval_batches and i > max_n_eval_batches:
|
||||
n_batches += 1
|
||||
break
|
||||
|
||||
xb, yb = batch
|
||||
xb = xb.to(self.device)
|
||||
yb = yb.to(self.device)
|
||||
yb_pred = self.model(xb)
|
||||
loss = self.criterion(yb_pred, yb)
|
||||
losses.append(loss.item())
|
||||
|
||||
self.model.train()
|
||||
return sum(losses) / len(losses)
|
||||
|
||||
def create_data_loaders_dictionary(
|
||||
self,
|
||||
data_dictionary: Dict[str, pd.DataFrame]
|
||||
) -> Dict[str, DataLoader]:
|
||||
"""
|
||||
Converts the input data to PyTorch tensors using a data loader.
|
||||
"""
|
||||
data_loader_dictionary = {}
|
||||
for split in ["train", "test"]:
|
||||
x = torch.from_numpy(data_dictionary[f"{split}_features"].values).float()
|
||||
y = torch.from_numpy(data_dictionary[f"{split}_labels"].values)\
|
||||
.to(self.target_tensor_type)
|
||||
|
||||
if self.squeeze_target_tensor:
|
||||
y = y.squeeze()
|
||||
|
||||
dataset = TensorDataset(x, y)
|
||||
data_loader = DataLoader(
|
||||
dataset,
|
||||
batch_size=self.batch_size,
|
||||
shuffle=True,
|
||||
drop_last=True,
|
||||
num_workers=0,
|
||||
)
|
||||
data_loader_dictionary[split] = data_loader
|
||||
|
||||
return data_loader_dictionary
|
||||
|
||||
@staticmethod
|
||||
def calc_n_epochs(n_obs: int, batch_size: int, n_iters: int) -> int:
|
||||
"""
|
||||
Calculates the number of epochs required to reach the maximum number
|
||||
of iterations specified in the model training parameters.
|
||||
|
||||
the motivation here is that `max_iters` is easier to optimize and keep stable,
|
||||
across different n_obs - the number of data points.
|
||||
"""
|
||||
|
||||
n_batches = math.ceil(n_obs // batch_size)
|
||||
epochs = math.ceil(n_iters // n_batches)
|
||||
return epochs
|
||||
|
||||
def save(self, path: Path):
|
||||
"""
|
||||
- Saving any nn.Module state_dict
|
||||
- Saving model_meta_data, this dict should contain any additional data that the
|
||||
user needs to store. e.g class_names for classification models.
|
||||
"""
|
||||
|
||||
torch.save({
|
||||
"model_state_dict": self.model.state_dict(),
|
||||
"optimizer_state_dict": self.optimizer.state_dict(),
|
||||
"model_meta_data": self.model_meta_data,
|
||||
}, path)
|
||||
|
||||
def load_from_file(self, path: Path):
|
||||
checkpoint = torch.load(path)
|
||||
return self.load_from_checkpoint(checkpoint)
|
||||
|
||||
def load_from_checkpoint(self, checkpoint: Dict):
|
||||
"""
|
||||
when using continual_learning, DataDrawer will load the dictionary
|
||||
(containing state dicts and model_meta_data) by calling torch.load(path).
|
||||
you can access this dict from any class that inherits IFreqaiModel by calling
|
||||
get_init_model method.
|
||||
"""
|
||||
|
||||
self.model.load_state_dict(checkpoint["model_state_dict"])
|
||||
self.optimizer.load_state_dict(checkpoint["optimizer_state_dict"])
|
||||
self.model_meta_data = checkpoint["model_meta_data"]
|
||||
return self
|
0
freqtrade/freqai/torch/__init__.py
Normal file
0
freqtrade/freqai/torch/__init__.py
Normal file
Reference in New Issue
Block a user