From 08398163e1012b6966602132ca4fab6ff466aeca Mon Sep 17 00:00:00 2001 From: Jamie Date: Wed, 5 May 2021 11:04:28 -0400 Subject: [PATCH] update neural networks typing and add new preprocessing (add actual normalization and correctly rename nrormalization to mimax) --- .../config_neural_network_models.py | 6 +- .../prediction_techniques/README.md | 10 +- .../prediction_techniques/ets_view.py | 10 +- ...al_networks.py => neural_networks_view.py} | 106 ++++++++++++++---- .../prediction_techniques/pred_controller.py | 8 +- .../prediction_techniques/pred_helper.py | 12 +- tests/test_pred_neural_networks.py | 2 +- 7 files changed, 114 insertions(+), 40 deletions(-) rename gamestonk_terminal/prediction_techniques/{neural_networks.py => neural_networks_view.py} (92%) diff --git a/gamestonk_terminal/config_neural_network_models.py b/gamestonk_terminal/config_neural_network_models.py index 24358030438..9d788f9aa59 100644 --- a/gamestonk_terminal/config_neural_network_models.py +++ b/gamestonk_terminal/config_neural_network_models.py @@ -23,7 +23,7 @@ MultiLayer_Perceptron = [ {"Dense": {"units": 100, "activation": "relu"}}, {"Dense": {"units": 80, "activation": "relu"}}, {"Dense": {"units": 30, "activation": "relu"}}, - {"Dense": {"activation": "linear"}}, + {"Dense": {"activation": "relu"}}, ] Recurrent_Neural_Network = [ @@ -31,11 +31,11 @@ Recurrent_Neural_Network = [ {"SimpleRNN": {"units": 50, "activation": "linear", "return_sequences": True}}, {"Dropout": {"rate": 0.2}}, {"SimpleRNN": {"units": 21, "activation": "linear", "return_sequences": False}}, - {"Dense": {"activation": "linear"}}, + {"Dense": {"activation": "relu"}}, ] Long_Short_Term_Memory = [ {"LSTM": {"units": 25, "activation": "tanh", "return_sequences": True}}, {"LSTM": {"units": 15, "activation": "tanh", "return_sequences": False}}, - {"Dense": {"activation": "linear"}}, + {"Dense": {"activation": "relu"}}, ] diff --git a/gamestonk_terminal/prediction_techniques/README.md b/gamestonk_terminal/prediction_techniques/README.md index 0f137a5b06a..4646140a780 100644 --- a/gamestonk_terminal/prediction_techniques/README.md +++ b/gamestonk_terminal/prediction_techniques/README.md @@ -153,7 +153,7 @@ Facebook's Prophet: ## mlp ``` -usage: mlp [-d N_DAYS] [-i N_INPUTS] [-j N_JUMPS] [--epochs N_EPOCHS] [-p {normalization,standardization,none}] +usage: mlp [-d N_DAYS] [-i N_INPUTS] [-j N_JUMPS] [--epochs N_EPOCHS] [-p {minmax, normalization,standardization,none}] [-o {adam,adagrad,adadelta,adamax,ftrl,nadam,optimizer,rmsprop,sgd}] [-l {mae,mape,mse,msle}] [-e S_END_DATE] [--loops N_LOOPS] ``` MulitLayer Perceptron: @@ -161,7 +161,7 @@ MulitLayer Perceptron: * -i : number of days to use for prediction. Default 40. * -j : number of jumps in training data. Default 1. * --epochs : number of training epochs. Default 200. - * -p : pre-processing data. Default normalization. + * -p : pre-processing data. Default minmax. * -o : optimization technique. Default adam. * -l : loss function. Default mae. * -e : end date (format YYYY-MM-DD) of the stock - Backtesting. Default None. @@ -183,7 +183,7 @@ MultiLayer_Perceptron \ {'Dense': {'units':30, 'activation':'relu'} }, {'Dense': - {'activation':'linear'} }] + {'activation':'relu'} }] ``` ![mlp](https://user-images.githubusercontent.com/25267873/108604944-d2025400-73a8-11eb-9ab6-52972160cd2a.png) @@ -220,7 +220,7 @@ Recurrent_Neural_Network \ {'SimpleRNN': {'units':21, 'activation':'linear', 'return_sequences':False} }, {'Dense': - {'activation':'linear'} }] + {'activation':'relu'} }] ``` ![rnn](https://user-images.githubusercontent.com/25267873/108604940-d0d12700-73a8-11eb-837e-a5aa128942d9.png) @@ -260,7 +260,7 @@ Long_Short_Term_Memory \ {'LSTM': {'units':15, 'activation':'tanh', 'return_sequences':False} }, {'Dense': - {'activation':'linear'} }] + {'activation':'relu'} }] ``` ![lstm](https://user-images.githubusercontent.com/25267873/108604943-d2025400-73a8-11eb-83c5-edb4a2121cba.png) diff --git a/gamestonk_terminal/prediction_techniques/ets_view.py b/gamestonk_terminal/prediction_techniques/ets_view.py index 3e51ecef9bd..96238826ba9 100644 --- a/gamestonk_terminal/prediction_techniques/ets_view.py +++ b/gamestonk_terminal/prediction_techniques/ets_view.py @@ -2,7 +2,7 @@ __docformat__ = "numpy" import argparse -from typing import List +from typing import List, Union import datetime import matplotlib.pyplot as plt import numpy as np @@ -49,7 +49,7 @@ def check_valid_seasonal(seasonal: str) -> str: ) -def exponential_smoothing(other_args:List[str], s_ticker:str, df_stock:pd.DataFrame): +def exponential_smoothing(other_args: List[str], s_ticker: str, df_stock: pd.DataFrame): """ Perform exponential smoothing forecasting Parameters @@ -409,12 +409,14 @@ def exponential_smoothing(other_args:List[str], s_ticker:str, df_stock:pd.DataFr print("") -def get_exponential_smoothing_model(data:pd.Series, trend, seasonal, seasonal_periods): +def get_exponential_smoothing_model( + data: Union[pd.Series, np.ndarray], trend, seasonal, seasonal_periods +): """ Perform exponential smoothing Parameters ---------- - data: pd.Series + data: Union[pd.Series, np.ndarray] Series of closing values trend: str Trend component. One of [N, A, Ad] diff --git a/gamestonk_terminal/prediction_techniques/neural_networks.py b/gamestonk_terminal/prediction_techniques/neural_networks_view.py similarity index 92% rename from gamestonk_terminal/prediction_techniques/neural_networks.py rename to gamestonk_terminal/prediction_techniques/neural_networks_view.py index e3cabd309f9..498606321d5 100644 --- a/gamestonk_terminal/prediction_techniques/neural_networks.py +++ b/gamestonk_terminal/prediction_techniques/neural_networks_view.py @@ -1,4 +1,8 @@ +""" Neural Networks View""" +__docformat__ = "numpy" + import argparse +from typing import List, Any import datetime import os import traceback @@ -8,7 +12,7 @@ import matplotlib.pyplot as plt import pandas as pd from pandas.plotting import register_matplotlib_converters from TimeSeriesCrossValidation import splitTrain -from sklearn.preprocessing import MinMaxScaler, StandardScaler +from sklearn.preprocessing import MinMaxScaler, StandardScaler, Normalizer from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, SimpleRNN, Dense, Dropout @@ -44,7 +48,9 @@ ORIGINAL_TF_XLA_FLAGS = os.environ.get("TF_XLA_FLAGS") ORIGINAL_TF_FORCE_GPU_ALLOW_GROWTH = os.environ.get("TF_FORCE_GPU_ALLOW_GROWTH") -def build_neural_network_model(Recurrent_Neural_Network, n_inputs, n_days): +def build_neural_network_model( + Recurrent_Neural_Network: List[Any], n_inputs: int, n_days: int +): model = Sequential() for idx_layer, d_layer in enumerate(Recurrent_Neural_Network): @@ -91,9 +97,24 @@ def build_neural_network_model(Recurrent_Neural_Network, n_inputs, n_days): return model -def _parse_args(prog, description, l_args): - """Create an argparser and parse l_args. Will print help if user requests it. - :return: ns_parser""" +def _parse_args(prog: str, description: str, other_args: List[str]): + """ + Create an argparser and parse other_args. Will print help if user requests it. + Parameters + ---------- + prog: str + Program for argparser + description: str + Description for argparser + other_args + Argparse arguments to pass + Returns + ------- + ns_parser: argparse.Namespace + + Parsed argument parser + """ + parser = argparse.ArgumentParser( prog=prog, description=description, @@ -140,8 +161,8 @@ def _parse_args(prog, description, l_args): "--pp", action="store", dest="s_preprocessing", - default="normalization", - choices=["normalization", "standardization", "none"], + default="minmax", + choices=["normalization", "standardization", "minmax", "none"], help="pre-processing data.", ) parser.add_argument( @@ -233,7 +254,7 @@ def _parse_args(prog, description, l_args): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return None @@ -276,10 +297,27 @@ def _restore_env(): restore("TF_FORCE_GPU_ALLOW_GROWTH", ORIGINAL_TF_FORCE_GPU_ALLOW_GROWTH) -def _setup_backtesting(df_stock, ns_parser): - """Set up backtesting if enabled - :return: (df_stock, df_future), where df_future is None if s_end_date is not set. - :raises Exception: if configuration is invalid""" +def _setup_backtesting(df_stock: pd.DataFrame, ns_parser: argparse.Namespace): + """ + Setup backtesting when end data is specified + Parameters + ---------- + df_stock: pd.DataFrame + Stock data + ns_parser: argparse.Namespace + Parsed arguments + Returns + ------- + df_stock: pd.DataFrame + Data before end date + df_future: pd.DataFrame + Data after end date + Raises + ------- + Exception + If configuration is invalid + """ + df_future = None if ns_parser.s_end_date: if ns_parser.s_end_date < df_stock.index[0]: @@ -319,11 +357,16 @@ def _preprocess_split(df_stock, ns_parser): stock_train_data = scaler.fit_transform( np.array(df_stock["5. adjusted close"].values.reshape(-1, 1)) ) - elif ns_parser.s_preprocessing == "normalization": + elif ns_parser.s_preprocessing == "minmax": scaler = MinMaxScaler() stock_train_data = scaler.fit_transform( np.array(df_stock["5. adjusted close"].values.reshape(-1, 1)) ) + elif ns_parser.s_preprocessing == "normalization": + scaler = Normalizer() + stock_train_data = scaler.fit_transform( + np.array(df_stock["5. adjusted close"].values.reshape(-1, 1)) + ) else: # No pre-processing stock_train_data = np.array(df_stock["5. adjusted close"].values.reshape(-1, 1)) @@ -343,8 +386,10 @@ def _preprocess_split(df_stock, ns_parser): def _rescale_data(df_stock, ns_parser, scaler, yhat, idx_loop): """Re-scale the data back and return the prediction dataframe.""" - if (ns_parser.s_preprocessing == "standardization") or ( - ns_parser.s_preprocessing == "normalization" + if ( + (ns_parser.s_preprocessing == "standardization") + or (ns_parser.s_preprocessing == "normalization") + or (ns_parser.s_preprocessing == "minmax") ): y_pred_test_t = scaler.inverse_transform(yhat.tolist()) else: @@ -699,10 +744,25 @@ def _plot_and_print_results( print("") -def mlp(l_args, s_ticker, df_stock): +def mlp(other_args: List[str], s_ticker: str, df_stock: pd.DataFrame): + """ + Train a multi-layer perceptron model + Parameters + ---------- + other_args: List[str] + Argparse Arguments + s_ticker: str + Ticker + df_stock: pd.DataFrame + Loaded stock dataframe + + Returns + ------- + + """ try: ns_parser = _parse_args( - prog="mlp", description="""Multilayer Perceptron. """, l_args=l_args + prog="mlp", description="""Multilayer Perceptron. """, other_args=other_args ) if not ns_parser: return @@ -768,10 +828,12 @@ def mlp(l_args, s_ticker, df_stock): _restore_env() -def rnn(l_args, s_ticker, df_stock): +def rnn(other_args: List[str], s_ticker, df_stock): try: ns_parser = _parse_args( - prog="rnn", description="""Recurrent Neural Network. """, l_args=l_args + prog="rnn", + description="""Recurrent Neural Network. """, + other_args=other_args, ) if not ns_parser: return @@ -839,10 +901,12 @@ def rnn(l_args, s_ticker, df_stock): _restore_env() -def lstm(l_args, s_ticker, df_stock): +def lstm(other_args: List[str], s_ticker, df_stock): try: ns_parser = _parse_args( - prog="lstm", description="""Long-Short Term Memory. """, l_args=l_args + prog="lstm", + description="""Long-Short Term Memory. """, + other_args=other_args, ) if not ns_parser: return diff --git a/gamestonk_terminal/prediction_techniques/pred_controller.py b/gamestonk_terminal/prediction_techniques/pred_controller.py index 64600e41d53..3b92c7f058f 100644 --- a/gamestonk_terminal/prediction_techniques/pred_controller.py +++ b/gamestonk_terminal/prediction_techniques/pred_controller.py @@ -15,7 +15,7 @@ from gamestonk_terminal.prediction_techniques import ( arima_view, ets_view, knn, - neural_networks, + neural_networks_view, regression, sma, ) @@ -159,15 +159,15 @@ class PredictionTechniquesController: def call_mlp(self, other_args: List[str]): """Process mlp command""" - neural_networks.mlp(other_args, self.ticker, self.stock) + neural_networks_view.mlp(other_args, self.ticker, self.stock) def call_rnn(self, other_args: List[str]): """Process rnn command""" - neural_networks.rnn(other_args, self.ticker, self.stock) + neural_networks_view.rnn(other_args, self.ticker, self.stock) def call_lstm(self, other_args: List[str]): """Process lstm command""" - neural_networks.lstm(other_args, self.ticker, self.stock) + neural_networks_view.lstm(other_args, self.ticker, self.stock) if gtff.ENABLE_FBPROPHET: diff --git a/gamestonk_terminal/prediction_techniques/pred_helper.py b/gamestonk_terminal/prediction_techniques/pred_helper.py index c69ac5f0616..fd80ded4824 100644 --- a/gamestonk_terminal/prediction_techniques/pred_helper.py +++ b/gamestonk_terminal/prediction_techniques/pred_helper.py @@ -1,3 +1,6 @@ +""" Prediction helper functions """ +__docformat__ = "numpy" + import numpy as np import pandas as pd from colorama import Fore, Style @@ -11,6 +14,7 @@ from gamestonk_terminal import feature_flags as gtff def price_prediction_color(val: float, last_val: float) -> str: + """Set prediction to be a colored string""" if float(val) > last_val: color = Fore.GREEN else: @@ -19,6 +23,7 @@ def price_prediction_color(val: float, last_val: float) -> str: def print_pretty_prediction(df_pred: pd.DataFrame, last_price: float): + """Print predictions""" if gtff.USE_COLOR: print(f"Actual price: {Fore.YELLOW}{last_price:.2f} ${Style.RESET_ALL}\n") print("Prediction:") @@ -44,12 +49,14 @@ def print_pretty_prediction_nn(df_pred: pd.DataFrame, last_price: float): print(df_pred.to_string()) -def mean_absolute_percentage_error(y_true, y_pred): +def mean_absolute_percentage_error(y_true: np.ndarray, y_pred: np.ndarray) -> np.number: + """Calculate mean absolute percent error""" y_true, y_pred = np.array(y_true), np.array(y_pred) return np.mean(np.abs((y_true - y_pred) / y_true)) * 100 -def print_prediction_kpis(real, pred): +def print_prediction_kpis(real: np.ndarray, pred: np.ndarray): + """Print prediction statistics""" print("KPIs") print(f"MAPE: {mean_absolute_percentage_error(real, pred):.3f} %") print(f"R2: {r2_score(real, pred):.3f}") @@ -59,6 +66,7 @@ def print_prediction_kpis(real, pred): def price_prediction_backtesting_color(val: list) -> str: + """Add color to backtest data""" err_pct = 100 * (val[0] - val[1]) / val[1] if val[0] > val[1]: s_err_pct = f" {Fore.GREEN} +{err_pct:.2f} %" diff --git a/tests/test_pred_neural_networks.py b/tests/test_pred_neural_networks.py index cc369861296..303038f2a02 100644 --- a/tests/test_pred_neural_networks.py +++ b/tests/test_pred_neural_networks.py @@ -2,7 +2,7 @@ import unittest try: - from gamestonk_terminal.prediction_techniques.neural_networks import mlp + from gamestonk_terminal.prediction_techniques.neural_networks_view import mlp except ModuleNotFoundError as e: print("One of the optional packages seems to be missing") print(e)