From fa1d564b421b4c1c723471e80299d1fae49e1105 Mon Sep 17 00:00:00 2001 From: Patricio Cerda Mardini Date: Wed, 6 Sep 2023 18:13:45 -0700 Subject: [PATCH 1/8] remove sktime --- lightwood/api/json_ai.py | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/lightwood/api/json_ai.py b/lightwood/api/json_ai.py index fbc775d16..94cedd482 100644 --- a/lightwood/api/json_ai.py +++ b/lightwood/api/json_ai.py @@ -257,32 +257,7 @@ def generate_json_ai( ) elif tss.is_timeseries and tss.horizon > 1 and tss.use_previous_target and \ dtype_dict[target] in (dtype.integer, dtype.float, dtype.quantity): - - submodels.extend( - [ - { - "module": "SkTime", - "args": { - "stop_after": "$problem_definition.seconds_per_mixer", - "horizon": "$problem_definition.timeseries_settings.horizon", - }, - }, - { - "module": "ETSMixer", - "args": { - "stop_after": "$problem_definition.seconds_per_mixer", - "horizon": "$problem_definition.timeseries_settings.horizon", - }, - }, - { - "module": "ARIMAMixer", - "args": { - "stop_after": "$problem_definition.seconds_per_mixer", - "horizon": "$problem_definition.timeseries_settings.horizon", - }, - } - ] - ) + pass # TODO: XGBoostArrayMixer model = { "module": "BestOf", From 55126dcf5fe1ba028031b79b20ac3c628e23d231 Mon Sep 17 00:00:00 2001 From: Patricio Cerda Mardini Date: Fri, 8 Sep 2023 13:09:39 -0700 Subject: [PATCH 2/8] progress --- lightwood/api/json_ai.py | 4 ++ lightwood/ensemble/best_of.py | 2 +- lightwood/mixer/nhits.py | 45 +++++++++++++------ tests/integration/advanced/test_timeseries.py | 2 +- 4 files changed, 38 insertions(+), 15 deletions(-) diff --git a/lightwood/api/json_ai.py b/lightwood/api/json_ai.py index 94cedd482..81f20948b 100644 --- a/lightwood/api/json_ai.py +++ b/lightwood/api/json_ai.py @@ -226,6 +226,10 @@ def generate_json_ai( "stop_after": "$problem_definition.seconds_per_mixer", "search_hyperparameters": True, }, + }, + { + "module": "NHitsMixer", + "args": {}, } ] ) diff --git a/lightwood/ensemble/best_of.py b/lightwood/ensemble/best_of.py index e7fd3411b..50753d941 100644 --- a/lightwood/ensemble/best_of.py +++ b/lightwood/ensemble/best_of.py @@ -57,7 +57,7 @@ def __init__(self, target, mixers: List[BaseMixer], data: EncodedDs, accuracy_fu score_list.append(avg_score) - self.indexes_by_accuracy = list(reversed(np.array(score_list).argsort())) + self.indexes_by_accuracy = list(np.array(score_list).argsort()) self.supports_proba = self.mixers[self.indexes_by_accuracy[0]].supports_proba log.info(f'Picked best mixer: {type(self.mixers[self.indexes_by_accuracy[0]]).__name__}') self.prepared = True diff --git a/lightwood/mixer/nhits.py b/lightwood/mixer/nhits.py index b88528c16..7dee62a84 100644 --- a/lightwood/mixer/nhits.py +++ b/lightwood/mixer/nhits.py @@ -5,6 +5,7 @@ import pandas as pd from neuralforecast import NeuralForecast from neuralforecast.models.nhits import NHITS +from neuralforecast.models.nbeats import NBEATS from neuralforecast.losses.pytorch import MQLoss from lightwood.helpers.log import log @@ -20,6 +21,7 @@ class NHitsMixer(BaseMixer): model_path: str hyperparam_search: bool default_config: dict + SUPPORTED_MODELS = ('nhits', 'nbeats') def __init__( self, @@ -56,7 +58,14 @@ def __init__( self.grouped_by = ['__default'] if not ts_analysis['tss'].group_by else ts_analysis['tss'].group_by self.group_boundaries = {} # stores last observed timestamp per series self.train_args = train_args.get('trainer_args', {}) if train_args else {} - self.train_args['early_stop_patience_steps'] = self.train_args.get('early_stop_patience_steps', 10) + + # we set a fairly aggressive training schedule by default + self.train_args['early_stop_patience_steps'] = self.train_args.get('early_stop_patience_steps', 1) + self.train_args['val_check_steps'] = self.train_args.get('val_check_steps', 10) + self.train_args['learning_rate'] = self.train_args.get('learning_rate', 3e-3) + self.train_args['mlp_units'] = self.train_args.get('mlp_units', [[128, 128], [128, 128]]) + self.train_args['random_seed'] = self.train_args.get('random_seed', 1) + self.conf_level = self.train_args.pop('conf_level', [90]) for level in self.conf_level: assert 0 <= level <= 100, f'A provided level is not in the [0, 100] range (found: {level})' @@ -74,18 +83,24 @@ def __init__( 'T': 'hourly', # NOTE: use another pre-trained model once available 'S': 'hourly' # NOTE: use another pre-trained model once available } + self.model = None + self.model_class_str = self.train_args.get('model_class', 'nhits').lower() + assert self.model_class_str in NHitsMixer.SUPPORTED_MODELS, f'Provided model class ({self.model_class_str}) is not supported. Supported models are: {NHitsMixer.SUPPORTED_MODELS}' # noqa + self.model_class = NBEATS if self.model_class_str == 'nbeats' else NHITS + self.model_name = None self.model_names = { - 'hourly': 'nhits_m4_hourly.ckpt', # hourly (non-tiny) - 'daily': 'nhits_m4_daily.ckpt', # daily - 'monthly': 'nhits_m4_monthly.ckpt', # monthly - 'yearly': 'nhits_m4_yearly.ckpt', # yearly + 'nhits': { + 'hourly': 'nhits_m4_hourly.ckpt', # hourly (non-tiny) + 'daily': 'nhits_m4_daily.ckpt', # daily + 'monthly': 'nhits_m4_monthly.ckpt', # monthly + 'yearly': 'nhits_m4_yearly.ckpt', # yearly + }, + 'nbeats': {} # TODO: complete } - self.model_name = None - self.model = None def fit(self, train_data: EncodedDs, dev_data: EncodedDs) -> None: """ - Fits the N-HITS model. + Fits the NeuralForecast model. """ # noqa log.info('Started fitting N-HITS forecasting model') @@ -110,7 +125,7 @@ def fit(self, train_data: EncodedDs, dev_data: EncodedDs) -> None: None) self.model_name = self.model_names['hourly'] if self.model_name is None else self.model_name ckpt_url = self.base_url + self.model_name - self.model = NHITS.load_from_checkpoint(ckpt_url) + self.model = self.model_class.load_from_checkpoint(ckpt_url) if not self.window < self.model.hparams.n_time_in: log.info(f'NOTE: Provided window ({self.window}) is smaller than specified model input length ({self.model.hparams.n_time_in}). Will train a new model from scratch.') # noqa @@ -126,8 +141,8 @@ def fit(self, train_data: EncodedDs, dev_data: EncodedDs) -> None: new_window = max(1, n_time - self.horizon - 1) self.window = new_window log.info(f'Window {self.window} is too long for data provided (group: {df[gby].value_counts()[::-1].index[0]}), reducing window to {new_window}.') # noqa - model = NHITS(h=n_time_out, input_size=self.window, **self.train_args, loss=MQLoss(level=self.conf_level)) - self.model = NeuralForecast(models=[model], freq=self.ts_analysis['sample_freqs']['__default']) + model = self.model_class(h=n_time_out, input_size=self.window, **self.train_args, loss=MQLoss(level=self.conf_level)) # noqa + self.model = NeuralForecast(models=[model], freq=self.ts_analysis['sample_freqs']['__default'],) self.model.fit(df=Y_df, val_size=n_ts_val) log.info('Successfully trained N-HITS forecasting model.') @@ -156,7 +171,11 @@ def __call__(self, ds: Union[EncodedDs, ConcatedEncodedDs], level = max(self.conf_level) target_cols = ['prediction', 'lower', 'upper'] - pred_cols = ['NHITS-median', f'NHITS-lo-{level}', f'NHITS-hi-{level}'] + pred_cols = [ + f'{self.model_class_str.upper()}-median', + f'{self.model_class_str.upper()}-lo-{level}', + f'{self.model_class_str.upper()}-hi-{level}' + ] input_df, idxs = self._make_initial_df(deepcopy(ds.data_frame)) length = sum(ds.encoded_ds_lengths) if isinstance(ds, ConcatedEncodedDs) else len(ds) @@ -189,7 +208,7 @@ def __call__(self, ds: Union[EncodedDs, ConcatedEncodedDs], def _make_initial_df(self, df, mode='inference'): """ - Prepares a dataframe for the NHITS model according to what neuralforecast expects. + Prepares a dataframe for the model according to what neuralforecast expects. If a per-group boundary exists, this method additionally drops out all observations prior to the cutoff. """ # noqa diff --git a/tests/integration/advanced/test_timeseries.py b/tests/integration/advanced/test_timeseries.py index 412f36732..346dde6ec 100644 --- a/tests/integration/advanced/test_timeseries.py +++ b/tests/integration/advanced/test_timeseries.py @@ -475,7 +475,7 @@ def test_8_time_series_double_grouped_regression(self): target = 'MA' order_by = 'saledate' window = 8 - for horizon in [1, 4]: + for horizon in [4]: train, _, test = stratify(data, pct_train=0.8, pct_dev=0, pct_test=0.2, stratify_on=gby, seed=1, reshuffle=False) jai = json_ai_from_problem(train, From d650f22d0d2bc009edd76858957c591dae1ac39f Mon Sep 17 00:00:00 2001 From: Patricio Cerda Mardini Date: Fri, 8 Sep 2023 17:38:39 -0700 Subject: [PATCH 3/8] fix reqs --- requirements.txt | 1 + requirements_extra_ts.txt | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6b81917b2..b65c685f4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -32,3 +32,4 @@ tab-transformer-pytorch >= 0.2.1 typing-inspect six regex +neuralforecast ==1.5.0 diff --git a/requirements_extra_ts.txt b/requirements_extra_ts.txt index e37416201..07f37ada2 100644 --- a/requirements_extra_ts.txt +++ b/requirements_extra_ts.txt @@ -1,5 +1,4 @@ pystan==2.19.1.1 prophet==1.1 -neuralforecast ==1.5.0 mxnet >=1.6.0, <2.0.0 gluonts >= 0.13.2, <0.14.0 From ba8ad29dff5cfd5560192d617191c87a3fa29a9d Mon Sep 17 00:00:00 2001 From: Patricio Cerda Mardini Date: Fri, 8 Sep 2023 18:20:34 -0700 Subject: [PATCH 4/8] fix tests --- lightwood/ensemble/best_of.py | 2 +- tests/integration/basic/test_model_selection.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lightwood/ensemble/best_of.py b/lightwood/ensemble/best_of.py index 50753d941..e7fd3411b 100644 --- a/lightwood/ensemble/best_of.py +++ b/lightwood/ensemble/best_of.py @@ -57,7 +57,7 @@ def __init__(self, target, mixers: List[BaseMixer], data: EncodedDs, accuracy_fu score_list.append(avg_score) - self.indexes_by_accuracy = list(np.array(score_list).argsort()) + self.indexes_by_accuracy = list(reversed(np.array(score_list).argsort())) self.supports_proba = self.mixers[self.indexes_by_accuracy[0]].supports_proba log.info(f'Picked best mixer: {type(self.mixers[self.indexes_by_accuracy[0]]).__name__}') self.prepared = True diff --git a/tests/integration/basic/test_model_selection.py b/tests/integration/basic/test_model_selection.py index fb7a02363..8a1579868 100644 --- a/tests/integration/basic/test_model_selection.py +++ b/tests/integration/basic/test_model_selection.py @@ -53,7 +53,7 @@ def test_4_timeseries_t_plus_1(self): 'window': 5 } } - expected_mixers = ['NeuralTs', 'Regression', 'RandomForest', 'XGBoostMixer'] + expected_mixers = ['NeuralTs', 'Regression', 'RandomForest', 'XGBoostMixer', 'NHitsMixer'] mixers = self.get_mixers(df, target, prob_kwargs=prob_kwargs) self.assertEqual(set(mixers), set(expected_mixers)) @@ -69,6 +69,6 @@ def test_5_timeseries_t_plus_n(self): 'window': 5 } } - expected_mixers = ['NeuralTs', 'SkTime', 'ARIMAMixer', 'ETSMixer'] + expected_mixers = ['NeuralTs', 'NHitsMixer'] mixers = self.get_mixers(df, target, prob_kwargs=prob_kwargs) self.assertEqual(set(mixers), set(expected_mixers)) From c5a5c9f8a5d42740dd494304fab792cea0c571a7 Mon Sep 17 00:00:00 2001 From: Patricio Cerda Mardini Date: Thu, 14 Sep 2023 17:06:31 -0700 Subject: [PATCH 5/8] add xgboostarraymixer, remove nhits as default --- lightwood/api/json_ai.py | 21 ++++--- lightwood/mixer/__init__.py | 3 +- lightwood/mixer/xgboost.py | 4 +- lightwood/mixer/xgboost_array.py | 98 ++++++++++++++++++++++++++++++++ 4 files changed, 115 insertions(+), 11 deletions(-) create mode 100644 lightwood/mixer/xgboost_array.py diff --git a/lightwood/api/json_ai.py b/lightwood/api/json_ai.py index 81f20948b..0e697cb56 100644 --- a/lightwood/api/json_ai.py +++ b/lightwood/api/json_ai.py @@ -203,6 +203,8 @@ def generate_json_ai( ] ) else: + + # add neural model if not tss.is_timeseries: submodels.extend( [ @@ -227,13 +229,10 @@ def generate_json_ai( "search_hyperparameters": True, }, }, - { - "module": "NHitsMixer", - "args": {}, - } ] ) + # add other models if (not tss.is_timeseries or tss.horizon == 1) and dtype_dict[target] not in (dtype.num_array, dtype.cat_array): submodels.extend( [ @@ -259,9 +258,15 @@ def generate_json_ai( }, ] ) - elif tss.is_timeseries and tss.horizon > 1 and tss.use_previous_target and \ - dtype_dict[target] in (dtype.integer, dtype.float, dtype.quantity): - pass # TODO: XGBoostArrayMixer + + # special forecasting dispatch + elif tss.is_timeseries: + submodels.extend([ + { + "module": "XGBoostArrayMixer", + "args": {}, + }, + ]) model = { "module": "BestOf", @@ -550,7 +555,7 @@ def add_implicit_values(json_ai: JsonAI) -> JsonAI: "target_encoder", "$encoders[self.target]" ) - elif mixers[i]["module"] == "LightGBMArray": + elif mixers[i]["module"] in ("LightGBMArray", "XGBoostArrayMixer"): mixers[i]["args"]["input_cols"] = mixers[i]["args"].get( "input_cols", "$input_cols" ) diff --git a/lightwood/mixer/__init__.py b/lightwood/mixer/__init__.py index 3d7c1c2fa..d98806aa0 100644 --- a/lightwood/mixer/__init__.py +++ b/lightwood/mixer/__init__.py @@ -3,6 +3,7 @@ from lightwood.mixer.neural import Neural from lightwood.mixer.neural_ts import NeuralTs from lightwood.mixer.xgboost import XGBoostMixer +from lightwood.mixer.xgboost_array import XGBoostArrayMixer from lightwood.mixer.random_forest import RandomForest from lightwood.mixer.sktime import SkTime from lightwood.mixer.arima import ARIMAMixer @@ -43,4 +44,4 @@ __all__ = ['BaseMixer', 'Neural', 'NeuralTs', 'LightGBM', 'RandomForest', 'LightGBMArray', 'Unit', 'Regression', 'SkTime', 'QClassic', 'ProphetMixer', 'ETSMixer', 'ARIMAMixer', 'NHitsMixer', 'GluonTSMixer', 'XGBoostMixer', - 'TabTransformerMixer'] + 'TabTransformerMixer', 'XGBoostArrayMixer'] diff --git a/lightwood/mixer/xgboost.py b/lightwood/mixer/xgboost.py index d64470fad..c6b8c9cf9 100644 --- a/lightwood/mixer/xgboost.py +++ b/lightwood/mixer/xgboost.py @@ -83,8 +83,8 @@ def __init__( self.use_optuna = use_optuna self.params = {} self.fit_on_dev = fit_on_dev - self.cls_dtypes = [dtype.categorical, dtype.binary] # , dtype.cat_tsarray] # TODO - self.float_dtypes = [dtype.float, dtype.quantity] # , dtype.num_tsarray] # TODO + self.cls_dtypes = [dtype.categorical, dtype.binary, dtype.cat_tsarray] + self.float_dtypes = [dtype.float, dtype.quantity, dtype.num_tsarray] self.num_dtypes = [dtype.integer] + self.float_dtypes self.supports_proba = dtype_dict[target] in self.cls_dtypes self.stable = True diff --git a/lightwood/mixer/xgboost_array.py b/lightwood/mixer/xgboost_array.py new file mode 100644 index 000000000..dc7771554 --- /dev/null +++ b/lightwood/mixer/xgboost_array.py @@ -0,0 +1,98 @@ +from typing import Dict, List, Union, Optional +from copy import deepcopy + +import numpy as np +import pandas as pd + +from type_infer.dtype import dtype +from lightwood.helpers.log import log +from lightwood.encoder.base import BaseEncoder +from lightwood.mixer.base import BaseMixer +from lightwood.mixer.xgboost import XGBoostMixer, check_gpu_support +from lightwood.api.types import PredictionArguments, TimeseriesSettings +from lightwood.data.encoded_ds import EncodedDs, ConcatedEncodedDs + + +class XGBoostArrayMixer(BaseMixer): + """XGBoost-based model, intended for usage in forecasting tasks.""" + models: List[XGBoostMixer] + submodel_stop_after: float + target: str + supports_proba: bool + ts_analysis: Dict + tss: TimeseriesSettings + + def __init__( + self, + stop_after: float, + target: str, + dtype_dict: Dict[str, str], + input_cols: List[str], + fit_on_dev: bool, + target_encoder: BaseEncoder, + ts_analysis: Dict[str, object], + use_stl: bool, + tss: TimeseriesSettings + ): + super().__init__(stop_after) + self.tss = tss + self.horizon = tss.horizon + self.submodel_stop_after = stop_after / self.horizon + self.target = target + self.offset_pred_cols = [f'{self.target}_timestep_{i}' for i in range(1, self.horizon)] + if set(input_cols) != {self.tss.order_by}: + input_cols.remove(self.tss.order_by) + for col in self.offset_pred_cols: + dtype_dict[col] = dtype_dict[self.target] + self.models = [XGBoostMixer(self.submodel_stop_after, + target_col, + dtype_dict, + input_cols, + False, # fit_on_dev, + False, # use_optuna + target_encoder) + for _, target_col in zip(range(self.horizon), [target] + self.offset_pred_cols)] + self.ts_analysis = ts_analysis + self.supports_proba = False + self.use_stl = False + self.stable = True + + def _fit(self, train_data: EncodedDs, dev_data: EncodedDs, submodel_method='fit') -> None: + original_train = deepcopy(train_data.data_frame) + original_dev = deepcopy(dev_data.data_frame) + + for timestep in range(self.horizon): + getattr(self.models[timestep], submodel_method)(train_data, dev_data) + + # restore dfs + train_data.data_frame = original_train + dev_data.data_frame = original_dev + + def fit(self, train_data: EncodedDs, dev_data: EncodedDs) -> None: + log.info('Started fitting LGBM models for array prediction') + self._fit(train_data, dev_data, submodel_method='fit') + + def partial_fit(self, train_data: EncodedDs, dev_data: EncodedDs, args: Optional[dict] = None) -> None: + log.info('Updating array of LGBM models...') + self._fit(train_data, dev_data, submodel_method='partial_fit') + + def __call__(self, ds: Union[EncodedDs, ConcatedEncodedDs], + args: PredictionArguments = PredictionArguments()) -> pd.DataFrame: + if args.predict_proba: + log.warning('This model does not output probability estimates') + + original_df = deepcopy(ds.data_frame) + length = sum(ds.encoded_ds_lengths) if isinstance(ds, ConcatedEncodedDs) else len(ds) + ydf = pd.DataFrame(0, # zero-filled + index=np.arange(length), + columns=[f'prediction_{i}' for i in range(self.horizon)]) + + for timestep in range(self.horizon): + ydf[f'prediction_{timestep}'] = self.models[timestep](ds, args)['prediction'].values + + if self.models[0].positive_domain: + ydf = ydf.clip(0) + + ydf['prediction'] = ydf.values.tolist() + ds.data_frame = original_df + return ydf[['prediction']] From de79553e6dcac61a8dd76f481ca7aab4e0689139 Mon Sep 17 00:00:00 2001 From: Patricio Cerda Mardini Date: Thu, 14 Sep 2023 17:07:13 -0700 Subject: [PATCH 6/8] update reqs --- requirements.txt | 1 - requirements_extra_ts.txt | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b65c685f4..6b81917b2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -32,4 +32,3 @@ tab-transformer-pytorch >= 0.2.1 typing-inspect six regex -neuralforecast ==1.5.0 diff --git a/requirements_extra_ts.txt b/requirements_extra_ts.txt index 07f37ada2..e37416201 100644 --- a/requirements_extra_ts.txt +++ b/requirements_extra_ts.txt @@ -1,4 +1,5 @@ pystan==2.19.1.1 prophet==1.1 +neuralforecast ==1.5.0 mxnet >=1.6.0, <2.0.0 gluonts >= 0.13.2, <0.14.0 From 9a61d7c139e80020beaf58ed60b74958b28dab4b Mon Sep 17 00:00:00 2001 From: Patricio Cerda Mardini Date: Thu, 14 Sep 2023 17:11:17 -0700 Subject: [PATCH 7/8] fix tests --- lightwood/mixer/xgboost_array.py | 2 +- tests/integration/advanced/test_timeseries.py | 2 +- tests/integration/basic/test_model_selection.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lightwood/mixer/xgboost_array.py b/lightwood/mixer/xgboost_array.py index dc7771554..1a50aa001 100644 --- a/lightwood/mixer/xgboost_array.py +++ b/lightwood/mixer/xgboost_array.py @@ -55,7 +55,7 @@ def __init__( self.ts_analysis = ts_analysis self.supports_proba = False self.use_stl = False - self.stable = True + self.stable = False def _fit(self, train_data: EncodedDs, dev_data: EncodedDs, submodel_method='fit') -> None: original_train = deepcopy(train_data.data_frame) diff --git a/tests/integration/advanced/test_timeseries.py b/tests/integration/advanced/test_timeseries.py index 346dde6ec..412f36732 100644 --- a/tests/integration/advanced/test_timeseries.py +++ b/tests/integration/advanced/test_timeseries.py @@ -475,7 +475,7 @@ def test_8_time_series_double_grouped_regression(self): target = 'MA' order_by = 'saledate' window = 8 - for horizon in [4]: + for horizon in [1, 4]: train, _, test = stratify(data, pct_train=0.8, pct_dev=0, pct_test=0.2, stratify_on=gby, seed=1, reshuffle=False) jai = json_ai_from_problem(train, diff --git a/tests/integration/basic/test_model_selection.py b/tests/integration/basic/test_model_selection.py index 8a1579868..6207aad5f 100644 --- a/tests/integration/basic/test_model_selection.py +++ b/tests/integration/basic/test_model_selection.py @@ -53,7 +53,7 @@ def test_4_timeseries_t_plus_1(self): 'window': 5 } } - expected_mixers = ['NeuralTs', 'Regression', 'RandomForest', 'XGBoostMixer', 'NHitsMixer'] + expected_mixers = ['NeuralTs', 'Regression', 'RandomForest', 'XGBoostMixer'] mixers = self.get_mixers(df, target, prob_kwargs=prob_kwargs) self.assertEqual(set(mixers), set(expected_mixers)) @@ -69,6 +69,6 @@ def test_5_timeseries_t_plus_n(self): 'window': 5 } } - expected_mixers = ['NeuralTs', 'NHitsMixer'] + expected_mixers = ['NeuralTs', 'XGBoostArrayMixer'] mixers = self.get_mixers(df, target, prob_kwargs=prob_kwargs) self.assertEqual(set(mixers), set(expected_mixers)) From e6263272c40585158861438dd39e6eb148e2f8cb Mon Sep 17 00:00:00 2001 From: Patricio Cerda Mardini Date: Thu, 14 Sep 2023 17:11:40 -0700 Subject: [PATCH 8/8] lint: flake8 --- lightwood/mixer/xgboost_array.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lightwood/mixer/xgboost_array.py b/lightwood/mixer/xgboost_array.py index 1a50aa001..5b785abf7 100644 --- a/lightwood/mixer/xgboost_array.py +++ b/lightwood/mixer/xgboost_array.py @@ -4,11 +4,10 @@ import numpy as np import pandas as pd -from type_infer.dtype import dtype from lightwood.helpers.log import log from lightwood.encoder.base import BaseEncoder from lightwood.mixer.base import BaseMixer -from lightwood.mixer.xgboost import XGBoostMixer, check_gpu_support +from lightwood.mixer.xgboost import XGBoostMixer from lightwood.api.types import PredictionArguments, TimeseriesSettings from lightwood.data.encoded_ds import EncodedDs, ConcatedEncodedDs