-
Notifications
You must be signed in to change notification settings - Fork 560
TextPredictor #486
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
TextPredictor #486
Changes from 20 commits
0a0a4c6
002683f
60a847c
60a9e27
bc7f38d
f9ca56b
fe0ecbb
4a52ac7
30cc834
14e6720
6b75a73
d10945e
06f64b2
c9ff3d4
e7b6f6d
2307b37
bf3203b
10c93b2
53b5f09
ee3cacb
8096a89
fed989b
c40af7d
d0b3b11
30e9f60
d15dd60
6c42839
301eb16
c59a3b2
f04b69e
2f07223
ea515d2
6cc2f9e
4cc2b4e
4fa136d
c5d9914
25c1baf
f9d3b22
c1568b4
1e4201d
9692d4e
1b2cb28
05941bc
984d000
74f27b5
c8848c7
7be2c5c
1c7f7ad
be60fa6
3a29c5b
543b660
4296129
ca30eab
5bd061f
2b150e7
505c894
cd98daf
98ee138
ff8c078
24a5333
2aeb563
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,9 +23,11 @@ | |
| SEQCLASSIFICATION, | ||
| MULTICHOICECLASSIFICATION, | ||
| TOKENCLASSIFICATION, | ||
| "mm_multi", | ||
| "mm_binary", | ||
| ) | ||
| SEQREGRESSION = "seq-regression" | ||
| REGRESSION = ("regression", SEQREGRESSION) | ||
| REGRESSION = ("regression", "mm_regression", SEQREGRESSION) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rename "mm_regression" -> "multimodal-regression", define a static variable for it |
||
| TS_FORECASTREGRESSION = ( | ||
| "forecast", | ||
| "ts_forecast", | ||
|
|
@@ -47,6 +49,14 @@ | |
| TOKENCLASSIFICATION, | ||
| ) | ||
|
|
||
| MM_TASKS = ("mm_binary", "mm_multi", "mm_regression") | ||
|
|
||
|
|
||
| ## ***** ADDED FOR MULTIMODAL ***** | ||
| def _is_mm_task(task): | ||
| return True if task in MM_TASKS else False | ||
| ## ***** END ADDED FOR MULTIMODAL ***** | ||
|
|
||
|
|
||
| def _is_nlp_task(task): | ||
| if task in NLU_TASKS or task in NLG_TASKS: | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -37,6 +37,7 @@ | |||||||
| ARIMA, | ||||||||
| SARIMAX, | ||||||||
| TransformersEstimator, | ||||||||
| AGTextPredictorEstimator, | ||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please update all occurrences
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please update the commit. |
||||||||
| ) | ||||||||
| from .data import CLASSIFICATION, group_counts, TS_FORECAST, TS_VALUE_COL | ||||||||
| import logging | ||||||||
|
|
@@ -121,6 +122,8 @@ def get_estimator_class(task, estimator_name): | |||||||
| estimator_class = SARIMAX | ||||||||
| elif estimator_name == "transformer": | ||||||||
| estimator_class = TransformersEstimator | ||||||||
| elif estimator_name == "agtextpredictor": | ||||||||
| estimator_class = AGTextPredictorEstimator | ||||||||
| else: | ||||||||
| raise ValueError( | ||||||||
| estimator_name + " is not a built-in learner. " | ||||||||
|
|
||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1965,6 +1965,189 @@ class XGBoostLimitDepth_TS(TS_SKLearn): | |
| base_class = XGBoostLimitDepthEstimator | ||
|
|
||
|
|
||
| class AGTextPredictorEstimator(BaseEstimator): | ||
| """ | ||
| The class for tuning AutoGluon TextPredictor | ||
| """ | ||
| def __init__(self, task="binary", **params,): | ||
| from autogluon.text import TextPredictor | ||
|
|
||
| super().__init__(task, **params) | ||
| self.estimator_class = TextPredictor | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can remove this and initialize the model with TextPredictor instead. Is that better?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes |
||
|
|
||
| @classmethod | ||
| def search_space(cls, **params): | ||
| """ | ||
| Add the possible search space configs here, e.g. 'optimization.lr' | ||
| reference: | ||
| https://auto.gluon.ai/stable/tutorials/text_prediction/customization.html#custom-hyperparameter-values | ||
| """ | ||
| search_space_dict = { | ||
| "model.network.agg_net.mid_units": { | ||
| "domain": tune.choice(list(range(32, 129))), | ||
| "init_value": 128, | ||
| }, | ||
| "optimization.lr": { | ||
| "domain": tune.loguniform(lower=1E-5, upper=1E-4), | ||
| "init_value": 1E-4, | ||
| }, | ||
| "optimization.wd": { | ||
| "domain": tune.choice([1E-4, 1E-3, 1E-2]), | ||
| "init_value":1E-4, | ||
| }, | ||
| "optimization.warmup_portion": { | ||
| "domain": tune.choice([0.1, 0.2]), | ||
| "init_value":0.1, | ||
| }, | ||
| } | ||
| return search_space_dict | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There were only 4 hyperparameters and now there are 9. Which one was the search space used in your original experiment for autogluon?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The original four are "model.network.agg_net.mid_units", "optimization.warmup_portion", "optimization.lr", "optimization.wd". |
||
|
|
||
| def _init_fix_args(self, automl_fit_kwargs: dict=None): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need this function? Can we simply remove it?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we have
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, you can implement this, and define a similar init_hf_args if you need to check user input validity. |
||
| """ | ||
| Save the customed fix args here | ||
| this includes: | ||
| "output_dir", | ||
| "text_backbone": "electra_base" | ||
| "multimodal_fusion_strategy":"fuse_late", | ||
| """ | ||
| fix_args = {} | ||
| FIX_ARGS_LIST = ["output_dir", "dataset_name", "label_column", "per_device_batch_size", "backend", | ||
| "text_backbone", "multimodal_fusion_strategy", "num_train_epochs", "batch_size",] | ||
| for key, value in automl_fit_kwargs["custom_fix_args"].items(): | ||
| assert ( | ||
| key in FIX_ARGS_LIST | ||
| ), "The specified key {} is not in the argument list: output_dir, backend, label_column, dataset_name, text_backbone,\ | ||
| multimodal_fusion_strategy, num_train_epochs, batch_size, per_device_batch_size".format(key) | ||
|
|
||
| fix_args[key] = value | ||
|
|
||
| self.fix_args = fix_args | ||
|
|
||
| def _init_hp_config(self, text_backbone: str, multimodal_fusion_strategy: str): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please define cfg by defining a function inside of flaml/nlp/utils.py:class AGArgs, the remove this function.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Move this function to a function inside of AGArgs because AGArgs is for managing the config for AG. |
||
| """" | ||
| Ref: | ||
| https://auto.gluon.ai/stable/tutorials/text_prediction/customization.html#custom-hyperparameter-values | ||
| """ | ||
| if self.fix_args.get("backend", "pytorch") == "mxnet": | ||
| from autogluon.text.text_prediction.legacy_presets import ag_text_presets | ||
|
|
||
| base_key = f'{text_backbone}_{multimodal_fusion_strategy}' | ||
| cfg = ag_text_presets.create(base_key) | ||
| # NOTE: if the search_space() is modified, add new items or delete here too. | ||
| TUNABLE_HP = set(["model.network.agg_net.mid_units", | ||
| "optimization.batch_size", | ||
| "optimization.layerwise_lr_decay", | ||
| "optimization.lr", | ||
| "optimization.nbest", | ||
| "optimization.num_train_epochs", | ||
| "optimization.per_device_batch_size", | ||
| "optimization.wd", | ||
| "optimization.warmup_portion", | ||
| ]) | ||
| search_space = cfg["models"]["MultimodalTextModel"]["search_space"] | ||
| search_space["optimization.per_device_batch_size"] = self.fix_args.get("per_device_batch_size", 4) | ||
| search_space["optimization.num_train_epochs"] = self.fix_args.get("num_train_epochs", 10) | ||
| search_space["optimization.batch_size"] = self.fix_args.get("batch_size", 128) | ||
| for key, value in self.params.items(): | ||
| if key in TUNABLE_HP: | ||
| # NOTE: FLAML uses np.float64 but AG uses float, need to transform | ||
| if isinstance(value, np.float64): | ||
| search_space[key] = value.item() | ||
| else: | ||
| search_space[key] = value | ||
| return cfg | ||
|
|
||
| else: | ||
| raise ValueError("the pytorch automm model is not supported. ") | ||
| # from autogluon.text.text_prediction.presets import get_text_preset | ||
|
|
||
| # cfg, overrides = get_text_preset("default") # get preset for text+num+cat+fusion | ||
| # # TODO: set the search space for the auto_mm in AG 0.4.0 | ||
| # cfg.hf_text.checkpoint_name = self.fix_args["hf_text.checkpoint_name"] | ||
| # # get search configs from self.params and set here | ||
| # TUNABLE_HP = [] | ||
| # for key, value in self.params.items(): | ||
| # if key in TUNABLE_HP: | ||
| # # NOTE: FLAML uses np.float64 but AG uses float, might need to transform | ||
| # if isinstance(value, np.float64): | ||
| # search_space[key] = value.item() | ||
| # else: | ||
| # search_space[key] = value | ||
| return cfg | ||
|
|
||
|
|
||
| def _set_seed(self, seed): | ||
| import random | ||
| import mxnet as mx | ||
| import torch as th | ||
| th.manual_seed(seed) | ||
| mx.random.seed(seed) | ||
| np.random.seed(seed) | ||
| random.seed(seed) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please try if you can reproduce the result |
||
|
|
||
| def fit(self, X_train=None, y_train=None, budget=None, **kwargs): | ||
| self._kwargs = kwargs | ||
| self._init_fix_args(kwargs) | ||
| # the seed set in the bash script for ag experiment is 123 | ||
| seed = self.params.get("seed", 123) | ||
| self._set_seed(seed) | ||
|
|
||
| # get backbone and fusion strategy | ||
| text_backbone = self.fix_args["text_backbone"] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please remove these local variables by, e.g., simply use self.ag_args.text_backbone
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will fix and move to AGArgs |
||
| multimodal_fusion_strategy = self.fix_args["multimodal_fusion_strategy"] | ||
|
|
||
| # get & set the save dir, get the dataset info | ||
| save_dir = self.fix_args["output_dir"] | ||
| label_column = self.fix_args["label_column"] | ||
| dataset_name = self.fix_args["dataset_name"] | ||
| ag_model_save_dir = os.path.join(save_dir, f"{dataset_name}_ag_text_multimodal_{text_backbone}\ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we save the model after the HPO and after automl.fit in the test file instead of in here?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm afraid we cannot. When initializing the predictor, this path is either defined by a user or created by ag, to save the model. AG will automatically save the model to this directory.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok. Can you use the original directory save_dir instead of the modified directory ag_model_save_dir so users know where to find the saved model? |
||
| _{multimodal_fusion_strategy}_no_ensemble") | ||
|
|
||
| # set the hyperparameters | ||
| self.hyperparameters = self._init_hp_config(text_backbone, multimodal_fusion_strategy) | ||
| PROBLEM_TYPE_MAPPING = {"mm_binary": "binary", "mm_multi": "multiclass", "mm_regression": "regression"} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please implement the problem type mapping in flaml/nlp/utils.py:load_default_huggingface_metric_for_task |
||
| TASK_METRIC_MAPPING = {"mm_multi": "acc", "mm_binary": "roc_auc", "mm_regression": "r2"} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please implement the metric mapping in flaml/nlp/utils.py:load_default_huggingface_metric_for_task |
||
|
|
||
| # train the model | ||
| start_time = time.time() | ||
|
|
||
| self._model = self.estimator_class(path=ag_model_save_dir, | ||
|
Qiaochu-Song marked this conversation as resolved.
Outdated
|
||
| label=label_column, | ||
| problem_type=PROBLEM_TYPE_MAPPING[self._task], | ||
| eval_metric=TASK_METRIC_MAPPING[self._task], | ||
| backend=self.fix_args.get("backend", "pytorch")) | ||
|
|
||
| # train_data = self._kwargs["train_data"] | ||
| import pandas as pd | ||
| train_data = pd.concat([X_train, y_train], axis=1) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please use estimator._join method. See TransformersEstimator._join
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed |
||
| tuning_data = pd.concat([X_train, y_train], axis=1) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You mean X_val, y_val?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will remove this line since the tuning data is not necessary anymore. |
||
|
|
||
| self._model.fit(train_data=train_data, | ||
| tuning_data=kwargs.get("tuning_data", None), | ||
| hyperparameters=self.hyperparameters, | ||
| time_limit=budget, | ||
| seed=seed) | ||
|
|
||
| training_time = time.time() - start_time | ||
| return training_time | ||
|
|
||
| def predict(self, X): | ||
| output = self._model.predict(X, as_pandas=False) | ||
| return output | ||
|
|
||
| def predict_proba(self, X, as_multiclass=True): | ||
| # only works for classification tasks | ||
| assert ( | ||
| self._task in CLASSIFICATION | ||
| ), "predict_proba() only for classification tasks." | ||
|
|
||
| output = self._model.predict_proba(X, as_pandas=False) | ||
| if not as_multiclass: | ||
| if self._task == "mm_binary": | ||
| output = output[:, 1] | ||
| return output | ||
|
|
||
|
|
||
| class suppress_stdout_stderr(object): | ||
| def __init__(self): | ||
| # Open a pair of null files | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you automatically detect "mm_multi" and "mm_binary" so we don't need these two values anymore?