Skip to content
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
0a0a4c6
Change readme to trigger test
Mar 15, 2022
002683f
add dependencies for AG
Mar 15, 2022
60a847c
add user permission to test_notebook_example L81
Mar 15, 2022
60a9e27
add mlflow dependency to setup
Mar 16, 2022
bc7f38d
add textpredictor estimator and test
Mar 16, 2022
f9ca56b
new estimator, no test file
Mar 16, 2022
fe0ecbb
Update automl.py
Qiaochu-Song Mar 16, 2022
4a52ac7
Update automl.py
Qiaochu-Song Mar 16, 2022
30cc834
add test with gc, narrow down mxnet version
Mar 16, 2022
14e6720
Merge branch 'test_main' of github.com:Qiaochu-Song/FLAML into test_main
Mar 16, 2022
6b75a73
skip test for py3.6 and win+py3.8, loose mxnet ver
Mar 16, 2022
d10945e
no ag on windows, remove mlflow dependency
Mar 16, 2022
06f64b2
no ag on windows, remove mlflow dependency
Mar 16, 2022
c9ff3d4
test with direct return
Mar 17, 2022
e7b6f6d
debug without new test
Mar 17, 2022
2307b37
w/o os.environ setting in new test, direct return
Mar 17, 2022
bf3203b
debug, import only in new test
Mar 17, 2022
10c93b2
move new test to automl
Mar 17, 2022
53b5f09
move new test to test/nlp/
Mar 17, 2022
ee3cacb
pass data with X_train
Mar 21, 2022
8096a89
pr fixes, debugging
Mar 24, 2022
fed989b
update with upstream
Mar 24, 2022
c40af7d
Rename to MultimodalEstimator, pr fix
Mar 24, 2022
d0b3b11
remove comment
Mar 24, 2022
30e9f60
Update data.py
Qiaochu-Song Mar 25, 2022
d15dd60
fix bug
Mar 25, 2022
6c42839
Merge branch 'new-test2' of github.com:Qiaochu-Song/FLAML into new-test2
Mar 25, 2022
301eb16
remove useless import
Mar 25, 2022
c59a3b2
remove useless import
Mar 25, 2022
f04b69e
Merge branch 'new-test2' of github.com:Qiaochu-Song/FLAML into new-test2
Mar 25, 2022
2f07223
resolve conflict
Mar 28, 2022
ea515d2
remove task mapping for AG
Mar 28, 2022
6cc2f9e
use 0.5 threshold for text/cat inference
Apr 13, 2022
4cc2b4e
add MM_TASKS; no preprocess on X; pass val_data for early stopping
Apr 14, 2022
4fa136d
adjust testing data and raise budget
Apr 14, 2022
c5d9914
Merge remote-tracking branch 'upstream/main' into new-test2
Apr 14, 2022
25c1baf
shrink test toy data and budget
Apr 14, 2022
f9d3b22
change to regression test
Apr 14, 2022
c1568b4
add metric to kwargs for mm in train_estimator, raise test budget
Apr 14, 2022
1e4201d
use valid data if any for early stopping, raise test budget
Apr 15, 2022
9692d4e
return to the original budget
Apr 15, 2022
1b2cb28
fix valid DF checking
Apr 16, 2022
05941bc
simplify isinstance in ml.py
Apr 18, 2022
984d000
Merge remote-tracking branch 'upstream/main' into new-test2
Apr 18, 2022
74f27b5
reduce text column and budget
Apr 19, 2022
c8848c7
use only 4-row toy test data
Apr 19, 2022
7be2c5c
test 10s budget
Apr 19, 2022
1c7f7ad
minimize test toy dataset
Apr 19, 2022
be60fa6
shorter test sentence
Apr 19, 2022
3a29c5b
give enough test budget
Apr 20, 2022
543b660
give enough test budget
Apr 20, 2022
4296129
solve conflict
May 4, 2022
ca30eab
Merge branch 'mxtextpredictor' of github.com:Qiaochu-Song/FLAML into …
May 6, 2022
5bd061f
add pytorch backend support
May 12, 2022
2b150e7
set pytorch backend to default
May 19, 2022
505c894
pytorch backend support only
May 19, 2022
cd98daf
solve merge conflict
May 19, 2022
98ee138
test remove os and python ver constraints
May 19, 2022
ff8c078
no support for python 3.6
May 19, 2022
24a5333
no support for python 3.6 or windows
May 19, 2022
2aeb563
Merge branch 'main' into mxtextpredictor
Qiaochu-Song May 20, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ jobs:
run: |
pip install -e .[ray,forecast]
pip install 'tensorboardX<=2.2'
- name: If python version > 3.6 and not on windows, install autogluon
if: matrix.python-version >= '3.7' && (matrix.os == 'macOS-latest' || matrix.os == 'ubuntu-latest')
run: |
pip install -e .[autogluon]
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
Expand Down
3 changes: 3 additions & 0 deletions flaml/ml.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
ARIMA,
SARIMAX,
TransformersEstimator,
AGTextPredictorEstimator,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
AGTextPredictorEstimator,
MultiModalEstimator,

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please update all occurrences

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Copy link
Contributor

Choose a reason for hiding this comment

The 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
Expand Down Expand Up @@ -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. "
Expand Down
159 changes: 159 additions & 0 deletions flaml/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -1965,6 +1965,165 @@ 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.text_prediction.mx_predictor import MXTextPredictor

super().__init__(task, **params)
self.estimator_class = MXTextPredictor

@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
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this function? Can we simply remove it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we have AGArgs dataclass in utils, and just use the default settings, we can remove this function, and just have self.ag_args=AGArgs() in MultimodalEstimator.fit(). Does it make sense?

Copy link
Contributor

Choose a reason for hiding this comment

The 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",
"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, label_column, dataset_name, text_backbone,\
multimodal_fusion_strategy".format(key)

fix_args[key] = value

self.fix_args = fix_args

def _init_hp_config(self, text_backbone: str, multimodal_fusion_strategy: str):
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This _init_hp_config is to use the AGArgs and the self.params to get the hyperparametersdiction for the TextPredictor. If removed, still need to assemble this diction inside the MultimodalEstimator.fit(). Do you think it is better without this function and have this part inside the .fit()?

Copy link
Contributor

Choose a reason for hiding this comment

The 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
"""
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

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)
Copy link
Contributor

Choose a reason for hiding this comment

The 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"]
Copy link
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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}\
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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.
Ref: https://github.com/awslabs/autogluon/blob/0.4.0/text/src/autogluon/text/text_prediction/predictor.py#L56

Copy link
Contributor

Choose a reason for hiding this comment

The 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 = {"binary": "binary", "multi": "multiclass", "regression": "regression"}
TASK_METRIC_MAPPING = {"multi": "acc", "binary": "roc_auc", "regression": "r2"}

# train the model
start_time = time.time()

self._model = self.estimator_class(path=ag_model_save_dir,
label=label_column,
problem_type=PROBLEM_TYPE_MAPPING[self._task],
eval_metric=TASK_METRIC_MAPPING[self._task])

train_data = self._kwargs["train_data"]

self._model.fit(train_data=train_data,
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(self._kwargs["valid_data"], 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(self._kwargs["valid_data"], as_pandas=False)
if not as_multiclass:
if self._task == "binary":
output = output[:, 1]
return output


class suppress_stdout_stderr(object):
def __init__(self):
# Open a pair of null files
Expand Down
5 changes: 5 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@
"hcrystalball==0.1.10",
"seqeval",
],
"autogluon": [
"mxnet<2.0.0",
"autogluon.text==0.4.0",
"autogluon.features==0.4.0",
],
"catboost": ["catboost>=0.26"],
"blendsearch": ["optuna==2.8.0"],
"ray": [
Expand Down
135 changes: 135 additions & 0 deletions test/nlp/test_agtextpredictor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
from flaml import AutoML
import pandas as pd
import requests
import gc
import numpy as np
import os
import sys
import platform
from sklearn.model_selection import train_test_split
os.environ["AUTOGLUON_TEXT_TRAIN_WITHOUT_GPU"] = "1"


def default_holdout_frac(num_train_rows, hyperparameter_tune=False):
"""
Returns default holdout_frac used in fit().
Between row count 5,000 and 25,000 keep 0.1 holdout_frac, as we want to grow validation set to a stable 2500 examples.
Ref: https://github.com/awslabs/autogluon/blob/master/core/src/autogluon/core/utils/utils.py#L243
"""
if num_train_rows < 5000:
holdout_frac = max(0.1, min(0.2, 500.0 / num_train_rows))
else:
holdout_frac = max(0.01, min(0.1, 2500.0 / num_train_rows))

if hyperparameter_tune:
holdout_frac = min(0.2, holdout_frac * 2) # to allocate more validation data for HPO to avoid overfitting

return holdout_frac


def test_ag_text_predictor():
# # DEBUG
# return
# # DEBUG
if sys.version < "3.7":
# do not test on python3.6
return
elif platform.system() == "Windows":
# do not test on windows with py3.8
return

seed = 123
metric = "roc_auc"
train_data = {
"sentence1": [
'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .',
"Yucaipa owned Dominick 's before selling the chain to Safeway in 1998 for $ 2.5 billion .",
"They had published an advertisement on the Internet on June 10 , offering the cargo for sale , he added .",
"Around 0335 GMT , Tab shares were up 19 cents , or 4.4 % , at A $ 4.56 , having earlier set a record high of A $ 4.57 .",
"The stock rose $ 2.11 , or about 11 percent , to close Friday at $ 21.51 on the New York Stock Exchange .",
"Revenue in the first quarter of the year dropped 15 percent from the same period a year earlier .",
"The Nasdaq had a weekly gain of 17.27 , or 1.2 percent , closing at 1,520.15 on Friday .",
"The DVD-CCA then appealed to the state Supreme Court .",
],
"sentence2": [
'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .',
"Yucaipa bought Dominick 's in 1995 for $ 693 million and sold it to Safeway for $ 1.8 billion in 1998 .",
"On June 10 , the ship 's owners had published an advertisement on the Internet , offering the explosives for sale .",
"Tab shares jumped 20 cents , or 4.6 % , to set a record closing high at A $ 4.57 .",
"PG & E Corp. shares jumped $ 1.63 or 8 percent to $ 21.03 on the New York Stock Exchange on Friday .",
"With the scandal hanging over Stewart 's company , revenue the first quarter of the year dropped 15 percent from the same period a year earlier .",
"The tech-laced Nasdaq Composite .IXIC rallied 30.46 points , or 2.04 percent , to 1,520.15 .",
"The DVD CCA appealed that decision to the U.S. Supreme Court .",
],
"numerical1": [1, 2, 3, 4, 5, 6, 7, 8],
"categorical1": ["a", "b", "a", "a", "a", "b", "a", "a"],
"label": [1, 0, 1, 0, 1, 1, 0, 1],
"idx": [0, 1, 2, 3, 4, 5, 6, 7],
}
train_dataset = pd.DataFrame(train_data)

test_data = {
"sentence1": [
"That compared with $ 35.18 million , or 24 cents per share , in the year-ago period .",
"Shares of Genentech , a much larger company with several products on the market , rose more than 2 percent .",
"Legislation making it harder for consumers to erase their debts in bankruptcy court won overwhelming House approval in March .",
"The Nasdaq composite index increased 10.73 , or 0.7 percent , to 1,514.77 .",
],
"sentence2": [
"Earnings were affected by a non-recurring $ 8 million tax benefit in the year-ago period .",
"Shares of Xoma fell 16 percent in early trade , while shares of Genentech , a much larger company with several products on the market , were up 2 percent .",
"Legislation making it harder for consumers to erase their debts in bankruptcy court won speedy , House approval in March and was endorsed by the White House .",
"The Nasdaq Composite index , full of technology stocks , was lately up around 18 points .",
],
"numerical1": [3, 4, 5, 6],
"categorical1": ["b", "a", "a", "b"],
"label": [0, 1, 1, 0],
"idx": [8, 10, 11, 12],
}
test_dataset = pd.DataFrame(test_data)

# FORCE THE SAME TRAIN-VALID SPLIT IN & OUT THE PREDICTOR
holdout_frac = default_holdout_frac(len(train_dataset), False)

_, valid_dataset = train_test_split(train_dataset,
test_size=holdout_frac,
random_state=np.random.RandomState(seed))

feature_columns = ["sentence1", "sentence2", "numerical1", "categorical1"]

automl = AutoML()
automl_settings = {
"gpu_per_trial": 0,
"max_iter": 2,
"time_budget": 50,
"task": "binary",
"metric": "roc_auc",
}

automl_settings["custom_fix_args"] = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please define a class to flaml/nlp/utils.py:class AGArgs to address the default value of AutoGluon, that the user does not need to see. Move any variable here to AGArgs so you don't need to set the value here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do. Do I need any function like _init_hpo_args() in the TransformersEstimator then?

"output_dir": "test/ag/output/",
"text_backbone": "electra_base",
"multimodal_fusion_strategy": "fuse_late",
"dataset_name": "test_ag",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this variable

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

"label_column": "label",
"per_device_batch_size": 4,
"num_train_epochs": 2,
"batch_size": 4,
}

automl.fit(
dataframe=train_dataset[feature_columns+["label"]],
label="label",
train_data=train_dataset[feature_columns+["label"]],
valid_data=valid_dataset[feature_columns+["label"]],
X_val=valid_dataset[feature_columns],
y_val=valid_dataset["label"],
estimator_list=["agtextpredictor"],
**automl_settings
)

print("Begin to run inference on test set")
score = automl.model.estimator.evaluate(test_dataset)
print(f"Inference on test set complete, {metric}: {score}")
del automl
gc.collect()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a breakline to the end

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed