From 4550447409a7895de8627718d03bd6bfd14577df Mon Sep 17 00:00:00 2001 From: robcaulk Date: Tue, 14 Mar 2023 21:13:30 +0100 Subject: [PATCH 01/12] cheat flake8 for now until we can refactor save into the model class --- freqtrade/freqai/data_drawer.py | 4 ++-- freqtrade/freqai/freqai_interface.py | 2 +- .../freqai/prediction_models/PyTorchClassifierMultiTarget.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/freqai/data_drawer.py b/freqtrade/freqai/data_drawer.py index aaf9a869c..c8dadb171 100644 --- a/freqtrade/freqai/data_drawer.py +++ b/freqtrade/freqai/data_drawer.py @@ -446,7 +446,7 @@ class FreqaiDataDrawer: dump(model, save_path / f"{dk.model_filename}_model.joblib") elif self.model_type == 'keras': model.save(save_path / f"{dk.model_filename}_model.h5") - elif self.model_type in ["stable_baselines", "sb3_contrib", "pytorch"]: + elif self.model_type in ["stable_baselines3", "sb3_contrib", "pytorch"]: model.save(save_path / f"{dk.model_filename}_model.zip") if dk.svm_model is not None: @@ -496,7 +496,7 @@ class FreqaiDataDrawer: dk.training_features_list = dk.data["training_features_list"] dk.label_list = dk.data["label_list"] - def load_data(self, coin: str, dk: FreqaiDataKitchen) -> Any: + def load_data(self, coin: str, dk: FreqaiDataKitchen) -> Any: # noqa: C901 """ loads all data required to make a prediction on a sub-train time range :returns: diff --git a/freqtrade/freqai/freqai_interface.py b/freqtrade/freqai/freqai_interface.py index 7c45d4642..8a1ac436b 100644 --- a/freqtrade/freqai/freqai_interface.py +++ b/freqtrade/freqai/freqai_interface.py @@ -563,7 +563,7 @@ class IFreqaiModel(ABC): file_type = ".joblib" elif self.dd.model_type == 'keras': file_type = ".h5" - elif self.dd.model_type in ["stable_baselines", "sb3_contrib", "pytorch"]: + elif self.dd.model_type in ["stable_baselines3", "sb3_contrib", "pytorch"]: file_type = ".zip" path_to_modelfile = Path(dk.data_path / f"{dk.model_filename}_model{file_type}") diff --git a/freqtrade/freqai/prediction_models/PyTorchClassifierMultiTarget.py b/freqtrade/freqai/prediction_models/PyTorchClassifierMultiTarget.py index edafb3b7a..967199c12 100644 --- a/freqtrade/freqai/prediction_models/PyTorchClassifierMultiTarget.py +++ b/freqtrade/freqai/prediction_models/PyTorchClassifierMultiTarget.py @@ -41,7 +41,7 @@ class PyTorchClassifierMultiTarget(BasePyTorchModel): self.max_n_eval_batches: Optional[int] = model_training_params.get( "max_n_eval_batches", None ) - self.model_kwargs: Dict = model_training_params.get("model_kwargs", {}) + self.model_kwargs: Dict[str, any] = model_training_params.get("model_kwargs", {}) self.class_name_to_index = None self.index_to_class_name = None From fab9ff129461141dfaa4a3b69d4dccb4e922e955 Mon Sep 17 00:00:00 2001 From: Yinon Polak Date: Sat, 18 Mar 2023 15:27:38 +0200 Subject: [PATCH 02/12] fix tests --- tests/freqai/test_freqai_interface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/freqai/test_freqai_interface.py b/tests/freqai/test_freqai_interface.py index f34659398..c35d1afb4 100644 --- a/tests/freqai/test_freqai_interface.py +++ b/tests/freqai/test_freqai_interface.py @@ -52,8 +52,7 @@ def can_run_model(model: str) -> None: ('ReinforcementLearner_multiproc', False, False, False, True, False, 0), ('ReinforcementLearner_test_3ac', False, False, False, False, False, 0), ('ReinforcementLearner_test_3ac', False, False, False, True, False, 0), - ('ReinforcementLearner_test_4ac', False, False, False, True, False, 0), - ('PyTorchClassifierMultiTarget', False, False, False, True, False, 0) + ('ReinforcementLearner_test_4ac', False, False, False, True, False, 0) ]) def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca, dbscan, float32, can_short, shuffle, buffer): @@ -183,6 +182,7 @@ def test_extract_data_and_train_model_MultiTargets(mocker, freqai_conf, model, s 'CatboostClassifier', 'XGBoostClassifier', 'XGBoostRFClassifier', + 'PyTorchClassifierMultiTarget', ]) def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model): if (is_arm() or is_py11()) and model == 'CatboostClassifier': From a49f62eecbb9838e211300964c61ba889214cb80 Mon Sep 17 00:00:00 2001 From: Yinon Polak Date: Sat, 18 Mar 2023 20:51:30 +0200 Subject: [PATCH 03/12] classifier test - set model file extension --- tests/freqai/test_freqai_interface.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/freqai/test_freqai_interface.py b/tests/freqai/test_freqai_interface.py index c35d1afb4..a5fe9b90b 100644 --- a/tests/freqai/test_freqai_interface.py +++ b/tests/freqai/test_freqai_interface.py @@ -210,8 +210,16 @@ def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model): freqai.extract_data_and_train_model(new_timerange, "ADA/BTC", strategy, freqai.dk, data_load_timerange) + if freqai.dd.model_type == 'joblib': + model_file_extension = ".joblib" + elif freqai.dd.model_type == "pytorch": + model_file_extension = ".zip" + else: + raise Exception(f"Unsupported model type: {freqai.dd.model_type}," + f" can't assign model_file_extension") - assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_model.joblib").exists() + assert Path(freqai.dk.data_path / + f"{freqai.dk.model_filename}_model{model_file_extension}").exists() assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_metadata.json").exists() assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_trained_df.pkl").exists() assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_svm_model.joblib").exists() From 366c148c10769e745e7748f02503f0ccecca994f Mon Sep 17 00:00:00 2001 From: Yinon Polak Date: Sun, 19 Mar 2023 14:38:49 +0200 Subject: [PATCH 04/12] create children class to PyTorchClassifier to implement the fit method where we initialize the trainer and model objects --- .../freqai/base_models/PyTorchModelTrainer.py | 19 ++- .../prediction_models/MLPPyTorchClassifier.py | 81 ++++++++++++ ...rget.py => PyTorchClassifierClassifier.py} | 125 ++++++++---------- tests/freqai/test_freqai_interface.py | 8 +- 4 files changed, 146 insertions(+), 87 deletions(-) create mode 100644 freqtrade/freqai/prediction_models/MLPPyTorchClassifier.py rename freqtrade/freqai/prediction_models/{PyTorchClassifierMultiTarget.py => PyTorchClassifierClassifier.py} (53%) diff --git a/freqtrade/freqai/base_models/PyTorchModelTrainer.py b/freqtrade/freqai/base_models/PyTorchModelTrainer.py index 90fb472e5..f91b44924 100644 --- a/freqtrade/freqai/base_models/PyTorchModelTrainer.py +++ b/freqtrade/freqai/base_models/PyTorchModelTrainer.py @@ -19,35 +19,32 @@ class PyTorchModelTrainer: optimizer: Optimizer, criterion: nn.Module, device: str, - batch_size: int, - max_iters: int, - max_n_eval_batches: int, init_model: Dict, 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 batch_size: The size of the batches to use during training. + :param init_model: A dictionary containing the initial model/optimizer + state_dict and model_meta_data saved by self.save() method. + :param model_meta_data: Additional metadata about the model (optional). :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. - :param init_model: A dictionary containing the initial model/optimizer - state_dict and model_meta_data saved by self.save() method. - :param model_meta_data: Additional metadata about the model (optional). """ self.model = model self.optimizer = optimizer self.criterion = criterion self.model_meta_data = model_meta_data self.device = device - self.max_iters = max_iters - self.batch_size = batch_size - self.max_n_eval_batches = max_n_eval_batches - + 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) if init_model: self.load_from_checkpoint(init_model) diff --git a/freqtrade/freqai/prediction_models/MLPPyTorchClassifier.py b/freqtrade/freqai/prediction_models/MLPPyTorchClassifier.py new file mode 100644 index 000000000..d6be8c1df --- /dev/null +++ b/freqtrade/freqai/prediction_models/MLPPyTorchClassifier.py @@ -0,0 +1,81 @@ +from typing import Any, Dict + +from freqtrade.freqai.base_models.PyTorchModelTrainer import PyTorchModelTrainer +from freqtrade.freqai.data_kitchen import FreqaiDataKitchen +from freqtrade.freqai.prediction_models.PyTorchClassifierClassifier import PyTorchClassifier +from freqtrade.freqai.prediction_models.PyTorchMLPModel import PyTorchMLPModel + +import torch + + +class MLPPyTorchClassifier(PyTorchClassifier): + """ + This class implements the fit method of IFreqaiModel. + int the fit method we initialize the model and trainer objects. + the only requirement from the model is to be aligned to PyTorchClassifier + predict method that expects the model to predict tensor of type long. + the trainer defines the training loop. + + parameters are passed via `model_training_parameters` under the freqai + section in the config file. e.g: + { + ... + "freqai": { + ... + "model_training_parameters" : { + "learning_rate": 3e-4, + "trainer_kwargs": { + "max_iters": 5000, + "batch_size": 64, + "max_n_eval_batches": None, + }, + "model_kwargs": { + "hidden_dim": 512, + "dropout_percent": 0.2, + "n_layer": 1, + }, + } + } + } + + + """ + + def __init__(self, **kwargs): + super().__init__(**kwargs) + model_training_params = self.freqai_info.get("model_training_parameters", {}) + self.learning_rate: float = model_training_params.get("learning_rate", 3e-4) + self.model_kwargs: Dict[str, any] = model_training_params.get("model_kwargs", {}) + self.trainer_kwargs: Dict[str, any] = model_training_params.get("trainer_kwargs", {}) + + def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any: + """ + User sets up the training and test data to fit their desired model here + :param data_dictionary: the dictionary constructed by DataHandler to hold + all the training and test data/labels. + :raises ValueError: If self.class_names is not defined in the parent class. + """ + + class_names = self.get_class_names() + self.convert_label_column_to_int(data_dictionary, dk, class_names) + n_features = data_dictionary["train_features"].shape[-1] + model = PyTorchMLPModel( + input_dim=n_features, + output_dim=len(class_names), + **self.model_kwargs + ) + model.to(self.device) + optimizer = torch.optim.AdamW(model.parameters(), lr=self.learning_rate) + criterion = torch.nn.CrossEntropyLoss() + init_model = self.get_init_model(dk.pair) + trainer = PyTorchModelTrainer( + model=model, + optimizer=optimizer, + criterion=criterion, + model_meta_data={"class_names": class_names}, + device=self.device, + init_model=init_model, + **self.trainer_kwargs, + ) + trainer.fit(data_dictionary) + return trainer diff --git a/freqtrade/freqai/prediction_models/PyTorchClassifierMultiTarget.py b/freqtrade/freqai/prediction_models/PyTorchClassifierClassifier.py similarity index 53% rename from freqtrade/freqai/prediction_models/PyTorchClassifierMultiTarget.py rename to freqtrade/freqai/prediction_models/PyTorchClassifierClassifier.py index 967199c12..0be10b31e 100644 --- a/freqtrade/freqai/prediction_models/PyTorchClassifierMultiTarget.py +++ b/freqtrade/freqai/prediction_models/PyTorchClassifierClassifier.py @@ -1,5 +1,5 @@ import logging -from typing import Any, Dict, List, Optional, Tuple +from typing import Dict, List, Tuple import numpy as np import numpy.typing as npt @@ -10,17 +10,16 @@ from torch.nn import functional as F from freqtrade.exceptions import OperationalException from freqtrade.freqai.base_models.BasePyTorchModel import BasePyTorchModel -from freqtrade.freqai.base_models.PyTorchModelTrainer import PyTorchModelTrainer from freqtrade.freqai.data_kitchen import FreqaiDataKitchen -from freqtrade.freqai.prediction_models.PyTorchMLPModel import PyTorchMLPModel logger = logging.getLogger(__name__) -class PyTorchClassifierMultiTarget(BasePyTorchModel): +class PyTorchClassifier(BasePyTorchModel): """ - A PyTorch implementation of a multi-target classifier. + A PyTorch implementation of a classifier. + User must implement fit method """ def __init__(self, **kwargs): """ @@ -34,59 +33,9 @@ class PyTorchClassifierMultiTarget(BasePyTorchModel): """ super().__init__(**kwargs) - model_training_params = self.freqai_info.get("model_training_parameters", {}) - self.max_iters: int = model_training_params.get("max_iters", 100) - self.batch_size: int = model_training_params.get("batch_size", 64) - self.learning_rate: float = model_training_params.get("learning_rate", 3e-4) - self.max_n_eval_batches: Optional[int] = model_training_params.get( - "max_n_eval_batches", None - ) - self.model_kwargs: Dict[str, any] = model_training_params.get("model_kwargs", {}) self.class_name_to_index = None self.index_to_class_name = None - def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any: - """ - User sets up the training and test data to fit their desired model here - :param data_dictionary: the dictionary constructed by DataHandler to hold - all the training and test data/labels. - :raises ValueError: If self.class_names is not defined in the parent class. - - """ - - if not hasattr(self, "class_names"): - raise ValueError( - "Missing attribute: self.class_names " - "set self.freqai.class_names = [\"class a\", \"class b\", \"class c\"] " - "inside IStrategy.set_freqai_targets method." - ) - - self.init_class_names_to_index_mapping(self.class_names) - self.encode_classes_name(data_dictionary, dk) - n_features = data_dictionary["train_features"].shape[-1] - model = PyTorchMLPModel( - input_dim=n_features, - output_dim=len(self.class_names), - **self.model_kwargs - ) - model.to(self.device) - optimizer = torch.optim.AdamW(model.parameters(), lr=self.learning_rate) - criterion = torch.nn.CrossEntropyLoss() - init_model = self.get_init_model(dk.pair) - trainer = PyTorchModelTrainer( - model=model, - optimizer=optimizer, - criterion=criterion, - model_meta_data={"class_names": self.class_names}, - device=self.device, - batch_size=self.batch_size, - max_iters=self.max_iters, - max_n_eval_batches=self.max_n_eval_batches, - init_model=init_model - ) - trainer.fit(data_dictionary) - return trainer - def predict( self, unfiltered_df: DataFrame, dk: FreqaiDataKitchen, **kwargs ) -> Tuple[DataFrame, npt.NDArray[np.int_]]: @@ -97,7 +46,7 @@ class PyTorchClassifierMultiTarget(BasePyTorchModel): :pred_df: dataframe containing the predictions :do_predict: np.array of 1s and 0s to indicate places where freqai needed to remove data (NaNs) or felt uncertain about data (PCA and DI index) - :raises ValueError: if 'class_name' doesn't exist in model meta_data. + :raises ValueError: if 'class_names' doesn't exist in model meta_data. """ class_names = self.model.model_meta_data.get("class_names", None) @@ -106,7 +55,9 @@ class PyTorchClassifierMultiTarget(BasePyTorchModel): "Missing class names. " "self.model.model_meta_data[\"class_names\"] is None." ) - self.init_class_names_to_index_mapping(class_names) + + if not self.class_name_to_index: + self.init_class_names_to_index_mapping(class_names) dk.find_features(unfiltered_df) filtered_df, _ = dk.filter_features( @@ -116,49 +67,77 @@ class PyTorchClassifierMultiTarget(BasePyTorchModel): dk.data_dictionary["prediction_features"] = filtered_df self.data_cleaning_predict(dk) - dk.data_dictionary["prediction_features"] = torch.tensor( - dk.data_dictionary["prediction_features"].values - ).float().to(self.device) + x = torch.from_numpy(dk.data_dictionary["prediction_features"].values)\ + .float()\ + .to(self.device) - logits = self.model.model(dk.data_dictionary["prediction_features"]) + logits = self.model.model(x) probs = F.softmax(logits, dim=-1) predicted_classes = torch.argmax(probs, dim=-1) - predicted_classes_str = self.decode_classes_name(predicted_classes) + predicted_classes_str = self.decode_class_names(predicted_classes) pred_df_prob = DataFrame(probs.detach().numpy(), columns=class_names) pred_df = DataFrame(predicted_classes_str, columns=[dk.label_list[0]]) pred_df = pd.concat([pred_df, pred_df_prob], axis=1) return (pred_df, dk.do_predict) - def encode_classes_name(self, data_dictionary: Dict[str, pd.DataFrame], dk: FreqaiDataKitchen): + def encode_class_names( + self, + data_dictionary: Dict[str, pd.DataFrame], + dk: FreqaiDataKitchen, + class_names: List[str], + ): """ - encode class name str -> int - assuming first column of *_labels data frame to contain class names + encode class name, str -> int + assuming first column of *_labels data frame to be the target column + containing the class names """ target_column_name = dk.label_list[0] for split in ["train", "test"]: label_df = data_dictionary[f"{split}_labels"] - self.assert_valid_class_names(label_df[target_column_name]) + self.assert_valid_class_names(label_df[target_column_name], class_names) label_df[target_column_name] = list( map(lambda x: self.class_name_to_index[x], label_df[target_column_name]) ) - def assert_valid_class_names(self, labels: pd.Series): - non_defined_labels = set(labels) - set(self.class_names) + @staticmethod + def assert_valid_class_names( + target_column: pd.Series, + class_names: List[str] + ): + non_defined_labels = set(target_column) - set(class_names) if len(non_defined_labels) != 0: raise OperationalException( f"Found non defined labels: {non_defined_labels}, ", - f"expecting labels: {self.class_names}" + f"expecting labels: {class_names}" ) - def decode_classes_name(self, classes: torch.Tensor) -> List[str]: + def decode_class_names(self, class_ints: torch.Tensor) -> List[str]: """ - decode class name int -> str + decode class name, int -> str """ - return list(map(lambda x: self.index_to_class_name[x.item()], classes)) + return list(map(lambda x: self.index_to_class_name[x.item()], class_ints)) def init_class_names_to_index_mapping(self, class_names): self.class_name_to_index = {s: i for i, s in enumerate(class_names)} self.index_to_class_name = {i: s for i, s in enumerate(class_names)} - logger.info(f"class_name_to_index: {self.class_name_to_index}") + logger.info(f"encoded class name to index: {self.class_name_to_index}") + + def convert_label_column_to_int( + self, + data_dictionary: Dict[str, pd.DataFrame], + dk: FreqaiDataKitchen, + class_names: List[str] + ): + self.init_class_names_to_index_mapping(class_names) + self.encode_class_names(data_dictionary, dk, class_names) + + def get_class_names(self) -> List[str]: + if not hasattr(self, "class_names"): + raise ValueError( + "Missing attribute: self.class_names " + "set self.freqai.class_names = [\"class a\", \"class b\", \"class c\"] " + "inside IStrategy.set_freqai_targets method." + ) + return self.class_names diff --git a/tests/freqai/test_freqai_interface.py b/tests/freqai/test_freqai_interface.py index a5fe9b90b..181c0539d 100644 --- a/tests/freqai/test_freqai_interface.py +++ b/tests/freqai/test_freqai_interface.py @@ -88,10 +88,12 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca, if 'PyTorchClassifierMultiTarget' in model: model_save_ext = 'zip' freqai_conf['freqai']['model_training_parameters'].update({ - "max_iters": 1, - "batch_size": 64, "learning_rate": 3e-4, - "max_n_eval_batches": 1, + "trainer_kwargs": { + "max_iters": 1, + "batch_size": 64, + "max_n_eval_batches": 1, + }, "model_kwargs": { "hidden_dim": 32, "dropout_percent": 0.2, From 61ac36c576e2dfafeba560ce5fe96a1fc5735d34 Mon Sep 17 00:00:00 2001 From: Yinon Polak Date: Sun, 19 Mar 2023 14:49:12 +0200 Subject: [PATCH 05/12] fix test --- tests/freqai/test_freqai_interface.py | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/tests/freqai/test_freqai_interface.py b/tests/freqai/test_freqai_interface.py index 181c0539d..33c18677b 100644 --- a/tests/freqai/test_freqai_interface.py +++ b/tests/freqai/test_freqai_interface.py @@ -85,22 +85,6 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca, if 'test_3ac' in model or 'test_4ac' in model: freqai_conf["freqaimodel_path"] = str(Path(__file__).parents[1] / "freqai" / "test_models") - if 'PyTorchClassifierMultiTarget' in model: - model_save_ext = 'zip' - freqai_conf['freqai']['model_training_parameters'].update({ - "learning_rate": 3e-4, - "trainer_kwargs": { - "max_iters": 1, - "batch_size": 64, - "max_n_eval_batches": 1, - }, - "model_kwargs": { - "hidden_dim": 32, - "dropout_percent": 0.2, - "n_layer": 1, - } - }) - strategy = get_patched_freqai_strategy(mocker, freqai_conf) exchange = get_patched_exchange(mocker, freqai_conf) strategy.dp = DataProvider(freqai_conf, exchange) @@ -184,7 +168,7 @@ def test_extract_data_and_train_model_MultiTargets(mocker, freqai_conf, model, s 'CatboostClassifier', 'XGBoostClassifier', 'XGBoostRFClassifier', - 'PyTorchClassifierMultiTarget', + 'PyTorchClassifier', ]) def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model): if (is_arm() or is_py11()) and model == 'CatboostClassifier': From 9f477aa3c9c7de59d44db45088ef30e1027b93e0 Mon Sep 17 00:00:00 2001 From: Yinon Polak Date: Sun, 19 Mar 2023 15:09:50 +0200 Subject: [PATCH 06/12] sort imports --- freqtrade/freqai/prediction_models/MLPPyTorchClassifier.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqai/prediction_models/MLPPyTorchClassifier.py b/freqtrade/freqai/prediction_models/MLPPyTorchClassifier.py index d6be8c1df..2f6705311 100644 --- a/freqtrade/freqai/prediction_models/MLPPyTorchClassifier.py +++ b/freqtrade/freqai/prediction_models/MLPPyTorchClassifier.py @@ -1,12 +1,12 @@ from typing import Any, Dict +import torch + from freqtrade.freqai.base_models.PyTorchModelTrainer import PyTorchModelTrainer from freqtrade.freqai.data_kitchen import FreqaiDataKitchen from freqtrade.freqai.prediction_models.PyTorchClassifierClassifier import PyTorchClassifier from freqtrade.freqai.prediction_models.PyTorchMLPModel import PyTorchMLPModel -import torch - class MLPPyTorchClassifier(PyTorchClassifier): """ From 719faab4b8301410f86956cf09d698529a2ee93b Mon Sep 17 00:00:00 2001 From: Yinon Polak Date: Sun, 19 Mar 2023 15:21:34 +0200 Subject: [PATCH 07/12] fix test --- tests/freqai/test_freqai_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/freqai/test_freqai_interface.py b/tests/freqai/test_freqai_interface.py index 33c18677b..d183501ea 100644 --- a/tests/freqai/test_freqai_interface.py +++ b/tests/freqai/test_freqai_interface.py @@ -168,7 +168,7 @@ def test_extract_data_and_train_model_MultiTargets(mocker, freqai_conf, model, s 'CatboostClassifier', 'XGBoostClassifier', 'XGBoostRFClassifier', - 'PyTorchClassifier', + 'MLPPyTorchClassifier', ]) def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model): if (is_arm() or is_py11()) and model == 'CatboostClassifier': From 8bee499328e9d242361fb11289b63bca6bf17d9d Mon Sep 17 00:00:00 2001 From: Yinon Polak Date: Sun, 19 Mar 2023 17:03:36 +0200 Subject: [PATCH 08/12] modify feedforward net, move layer norm to start of thr block --- freqtrade/freqai/prediction_models/PyTorchMLPModel.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/freqtrade/freqai/prediction_models/PyTorchMLPModel.py b/freqtrade/freqai/prediction_models/PyTorchMLPModel.py index 91e496c5d..482b3f889 100644 --- a/freqtrade/freqai/prediction_models/PyTorchMLPModel.py +++ b/freqtrade/freqai/prediction_models/PyTorchMLPModel.py @@ -22,7 +22,7 @@ class PyTorchMLPModel(nn.Module): def forward(self, x: Tensor) -> Tensor: x = self.relu(self.input_layer(x)) x = self.dropout(x) - x = self.relu(self.blocks(x)) + x = self.blocks(x) logits = self.output_layer(x) return logits @@ -35,8 +35,8 @@ class Block(nn.Module): self.ln = nn.LayerNorm(hidden_dim) def forward(self, x): - x = self.dropout(self.ff(x)) - x = self.ln(x) + x = self.ff(self.ln(x)) + x = self.dropout(x) return x @@ -46,7 +46,6 @@ class FeedForward(nn.Module): self.net = nn.Sequential( nn.Linear(hidden_dim, hidden_dim), nn.ReLU(), - nn.Linear(hidden_dim, hidden_dim), ) def forward(self, x): From 6f9a8a089cd5bd54ca91d0d5c57231d75519af2d Mon Sep 17 00:00:00 2001 From: Yinon Polak Date: Sun, 19 Mar 2023 17:45:30 +0200 Subject: [PATCH 09/12] add mlp documentation --- .../prediction_models/PyTorchMLPModel.py | 54 ++++++++++++++++--- 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/freqtrade/freqai/prediction_models/PyTorchMLPModel.py b/freqtrade/freqai/prediction_models/PyTorchMLPModel.py index 482b3f889..07056e930 100644 --- a/freqtrade/freqai/prediction_models/PyTorchMLPModel.py +++ b/freqtrade/freqai/prediction_models/PyTorchMLPModel.py @@ -1,16 +1,42 @@ import logging import torch.nn as nn -from torch import Tensor - +import torch logger = logging.getLogger(__name__) class PyTorchMLPModel(nn.Module): + """ + A multi-layer perceptron (MLP) model implemented using PyTorch. + + :param input_dim: The number of input features. + :param output_dim: The number of output classes. + :param hidden_dim: The number of hidden units in each layer. Default: 256 + :param dropout_percent: The dropout rate for regularization. Default: 0.2 + :param n_layer: The number of layers in the MLP. Default: 1 + + :returns: The output of the MLP, with shape (batch_size, output_dim) + + + A neural network typically consists of input, output, and hidden layers, where the + information flows from the input layer through the hidden layers to the output layer. + In a feedforward neural network, also known as a multilayer perceptron (MLP), the + information flows in one direction only. Each hidden layer contains multiple units + or nodes that take input from the previous layer and produce output that goes to the + next layer. + + The hidden_dim parameter in the FeedForward class refers to the number of units + (or nodes) in the hidden layer. This parameter controls the complexity of the neural + network and determines how many nonlinear relationships the network can represent. + A higher value of hidden_dim allows the network to represent more complex functions + but may also make the network more prone to overfitting, where the model memorizes + the training data instead of learning general patterns. + """ + def __init__(self, input_dim: int, output_dim: int, **kwargs): super(PyTorchMLPModel, self).__init__() - hidden_dim: int = kwargs.get("hidden_dim", 1024) + 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) @@ -19,7 +45,7 @@ class PyTorchMLPModel(nn.Module): self.relu = nn.ReLU() self.dropout = nn.Dropout(p=dropout_percent) - def forward(self, x: Tensor) -> Tensor: + def forward(self, x: torch.Tensor) -> torch.Tensor: x = self.relu(self.input_layer(x)) x = self.dropout(x) x = self.blocks(x) @@ -28,19 +54,35 @@ class PyTorchMLPModel(nn.Module): class Block(nn.Module): + """ + A building block for a multi-layer perceptron (MLP) implemented using PyTorch. + + :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(Block, self).__init__() self.ff = FeedForward(hidden_dim) self.dropout = nn.Dropout(p=dropout_percent) self.ln = nn.LayerNorm(hidden_dim) - def forward(self, x): + 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 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(FeedForward, self).__init__() self.net = nn.Sequential( @@ -48,5 +90,5 @@ class FeedForward(nn.Module): nn.ReLU(), ) - def forward(self, x): + def forward(self, x: torch.Tensor) -> torch.Tensor: return self.net(x) From 903a1dc3e52507457e46242348beb2e6ea54ad45 Mon Sep 17 00:00:00 2001 From: Yinon Polak Date: Sun, 19 Mar 2023 18:04:01 +0200 Subject: [PATCH 10/12] improve mlp documentation --- .../prediction_models/PyTorchMLPModel.py | 41 +++++++++---------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/freqtrade/freqai/prediction_models/PyTorchMLPModel.py b/freqtrade/freqai/prediction_models/PyTorchMLPModel.py index 07056e930..c2b6c1b93 100644 --- a/freqtrade/freqai/prediction_models/PyTorchMLPModel.py +++ b/freqtrade/freqai/prediction_models/PyTorchMLPModel.py @@ -10,28 +10,25 @@ class PyTorchMLPModel(nn.Module): """ A multi-layer perceptron (MLP) model implemented using PyTorch. - :param input_dim: The number of input features. - :param output_dim: The number of output classes. - :param hidden_dim: The number of hidden units in each layer. Default: 256 - :param dropout_percent: The dropout rate for regularization. Default: 0.2 - :param n_layer: The number of layers in the MLP. Default: 1 + :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) - - - A neural network typically consists of input, output, and hidden layers, where the - information flows from the input layer through the hidden layers to the output layer. - In a feedforward neural network, also known as a multilayer perceptron (MLP), the - information flows in one direction only. Each hidden layer contains multiple units - or nodes that take input from the previous layer and produce output that goes to the - next layer. - - The hidden_dim parameter in the FeedForward class refers to the number of units - (or nodes) in the hidden layer. This parameter controls the complexity of the neural - network and determines how many nonlinear relationships the network can represent. - A higher value of hidden_dim allows the network to represent more complex functions - but may also make the network more prone to overfitting, where the model memorizes - the training data instead of learning general patterns. """ def __init__(self, input_dim: int, output_dim: int, **kwargs): @@ -55,7 +52,7 @@ class PyTorchMLPModel(nn.Module): class Block(nn.Module): """ - A building block for a multi-layer perceptron (MLP) implemented using PyTorch. + 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. @@ -77,7 +74,7 @@ class Block(nn.Module): class FeedForward(nn.Module): """ - A fully-connected feedforward neural network block. + 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) From 1c11a5f0485d2bdca5295712f4607abc0fe6d6a9 Mon Sep 17 00:00:00 2001 From: Yinon Polak Date: Sun, 19 Mar 2023 18:10:57 +0200 Subject: [PATCH 11/12] improve mlp documentation --- freqtrade/freqai/prediction_models/PyTorchMLPModel.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/freqtrade/freqai/prediction_models/PyTorchMLPModel.py b/freqtrade/freqai/prediction_models/PyTorchMLPModel.py index c2b6c1b93..0e6b3c7bb 100644 --- a/freqtrade/freqai/prediction_models/PyTorchMLPModel.py +++ b/freqtrade/freqai/prediction_models/PyTorchMLPModel.py @@ -10,6 +10,9 @@ 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 From 2f386913ac0406371790b38d92f3b76c234552a8 Mon Sep 17 00:00:00 2001 From: Yinon Polak Date: Mon, 20 Mar 2023 11:54:17 +0200 Subject: [PATCH 12/12] refactor classifiers class names --- .../{PyTorchClassifierClassifier.py => PyTorchClassifier.py} | 0 .../{MLPPyTorchClassifier.py => PyTorchMLPClassifier.py} | 4 ++-- freqtrade/freqai/prediction_models/PyTorchMLPModel.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename freqtrade/freqai/prediction_models/{PyTorchClassifierClassifier.py => PyTorchClassifier.py} (100%) rename freqtrade/freqai/prediction_models/{MLPPyTorchClassifier.py => PyTorchMLPClassifier.py} (95%) diff --git a/freqtrade/freqai/prediction_models/PyTorchClassifierClassifier.py b/freqtrade/freqai/prediction_models/PyTorchClassifier.py similarity index 100% rename from freqtrade/freqai/prediction_models/PyTorchClassifierClassifier.py rename to freqtrade/freqai/prediction_models/PyTorchClassifier.py diff --git a/freqtrade/freqai/prediction_models/MLPPyTorchClassifier.py b/freqtrade/freqai/prediction_models/PyTorchMLPClassifier.py similarity index 95% rename from freqtrade/freqai/prediction_models/MLPPyTorchClassifier.py rename to freqtrade/freqai/prediction_models/PyTorchMLPClassifier.py index 2f6705311..453995ce8 100644 --- a/freqtrade/freqai/prediction_models/MLPPyTorchClassifier.py +++ b/freqtrade/freqai/prediction_models/PyTorchMLPClassifier.py @@ -4,11 +4,11 @@ import torch from freqtrade.freqai.base_models.PyTorchModelTrainer import PyTorchModelTrainer from freqtrade.freqai.data_kitchen import FreqaiDataKitchen -from freqtrade.freqai.prediction_models.PyTorchClassifierClassifier import PyTorchClassifier +from freqtrade.freqai.prediction_models.PyTorchClassifier import PyTorchClassifier from freqtrade.freqai.prediction_models.PyTorchMLPModel import PyTorchMLPModel -class MLPPyTorchClassifier(PyTorchClassifier): +class PyTorchMLPClassifier(PyTorchClassifier): """ This class implements the fit method of IFreqaiModel. int the fit method we initialize the model and trainer objects. diff --git a/freqtrade/freqai/prediction_models/PyTorchMLPModel.py b/freqtrade/freqai/prediction_models/PyTorchMLPModel.py index 0e6b3c7bb..f711a53a7 100644 --- a/freqtrade/freqai/prediction_models/PyTorchMLPModel.py +++ b/freqtrade/freqai/prediction_models/PyTorchMLPModel.py @@ -49,8 +49,8 @@ class PyTorchMLPModel(nn.Module): x = self.relu(self.input_layer(x)) x = self.dropout(x) x = self.blocks(x) - logits = self.output_layer(x) - return logits + x = self.output_layer(x) + return x class Block(nn.Module):