| @@ -1,12 +1,13 @@ | |||
| import importlib | |||
| import sys | |||
| from ..backend import DependentBackend | |||
| """ | |||
| Auto solver for various graph tasks | |||
| """ | |||
| # load corresponding backend of subclass | |||
| def _load_subclass_backend(backend): | |||
| sub_module = importlib.import_module(f'.{backend.get_backend_name()}', __name__) | |||
| this = sys.modules[__name__] | |||
| for api, obj in sub_module.__dict__.items(): | |||
| setattr(this, api, obj) | |||
| from .classifier import AutoGraphClassifier, AutoNodeClassifier, AutoLinkPredictor | |||
| from ..utils import LeaderBoard | |||
| _load_subclass_backend(DependentBackend) | |||
| __all__ = [ | |||
| "AutoNodeClassifier", | |||
| "AutoGraphClassifier", | |||
| "AutoLinkPredictor", | |||
| "LeaderBoard", | |||
| ] | |||
| @@ -4,20 +4,20 @@ Solver base class | |||
| Provide some standard solver interface. | |||
| """ | |||
| from typing import Any, Tuple | |||
| from typing import Any, Iterable, Tuple | |||
| from copy import deepcopy | |||
| import torch | |||
| from ...module.feature import FEATURE_DICT | |||
| from ...module.hpo import HPO_DICT | |||
| from ...module.model import MODEL_DICT | |||
| from ...module.nas.algorithm import NAS_ALGO_DICT | |||
| from ...module.nas.estimator import NAS_ESTIMATOR_DICT | |||
| from ...module.nas.space import NAS_SPACE_DICT | |||
| from ...module import BaseFeature, BaseHPOptimizer, BaseTrainer | |||
| from ..utils import LeaderBoard | |||
| from ...utils import get_logger | |||
| from ..module.feature import FEATURE_DICT | |||
| from ..module.hpo import HPO_DICT | |||
| from ..module.model import MODEL_DICT | |||
| from ..module.nas.algorithm import NAS_ALGO_DICT | |||
| from ..module.nas.estimator import NAS_ESTIMATOR_DICT | |||
| from ..module.nas.space import NAS_SPACE_DICT | |||
| from ..module import BaseFeature, BaseHPOptimizer, BaseTrainer | |||
| from .utils import LeaderBoard | |||
| from ..utils import get_logger | |||
| LOGGER = get_logger("BaseSolver") | |||
| @@ -175,7 +175,7 @@ class BaseSolver: | |||
| self.feature_module = None | |||
| elif isinstance(feature_module, (BaseFeature, str)): | |||
| self.feature_module = get_feature(feature_module) | |||
| elif isinstance(feature_module, list): | |||
| elif isinstance(feature_module, Iterable): | |||
| self.feature_module = get_feature(feature_module[0]) | |||
| for feature_engineer in feature_module[1:]: | |||
| self.feature_module &= get_feature(feature_engineer) | |||
| @@ -306,15 +306,15 @@ class BaseSolver: | |||
| nas_algorithms = ( | |||
| nas_algorithms | |||
| if isinstance(nas_algorithms, (list, tuple)) | |||
| if isinstance(nas_algorithms, Iterable) | |||
| else [nas_algorithms] | |||
| ) | |||
| nas_spaces = ( | |||
| nas_spaces if isinstance(nas_spaces, (list, tuple)) else [nas_spaces] | |||
| nas_spaces if isinstance(nas_spaces, Iterable) else [nas_spaces] | |||
| ) | |||
| nas_estimators = ( | |||
| nas_estimators | |||
| if isinstance(nas_estimators, (list, tuple)) | |||
| if isinstance(nas_estimators, Iterable) | |||
| else [nas_estimators] | |||
| ) | |||
| @@ -2,7 +2,7 @@ | |||
| Auto classifier for classification problems. | |||
| """ | |||
| from .base import BaseClassifier | |||
| from ..base import BaseClassifier | |||
| from .graph_classifier import AutoGraphClassifier | |||
| from .node_classifier import AutoNodeClassifier | |||
| from .link_predictor import AutoLinkPredictor | |||
| @@ -4,8 +4,8 @@ Base solver for classification problems | |||
| from typing import Any | |||
| from ..base import BaseSolver | |||
| from ....module.ensemble import ENSEMBLE_DICT | |||
| from ....module import BaseEnsembler | |||
| from ...module.ensemble import ENSEMBLE_DICT | |||
| from ...module import BaseEnsembler | |||
| class BaseClassifier(BaseSolver): | |||
| @@ -11,16 +11,17 @@ import numpy as np | |||
| import yaml | |||
| from .base import BaseClassifier | |||
| from ....module.feature import FEATURE_DICT | |||
| from ....module.model import BaseModel, MODEL_DICT | |||
| from ....module.train import TRAINER_DICT, get_feval, BaseGraphClassificationTrainer | |||
| from ...module.feature import FEATURE_DICT | |||
| from ...module.model import BaseModel, MODEL_DICT | |||
| from ...module.train import TRAINER_DICT, get_feval, BaseGraphClassificationTrainer | |||
| from ..base import _initialize_single_model, _parse_hp_space | |||
| from ...utils import LeaderBoard, set_seed | |||
| from ....datasets import utils | |||
| from ...utils import get_logger | |||
| from ..utils import LeaderBoard, set_seed | |||
| from ...datasets import utils | |||
| from ..utils import get_logger | |||
| from ...backend import DependentBackend | |||
| LOGGER = get_logger("GraphClassifier") | |||
| __backend = DependentBackend.get_backend_name() | |||
| class AutoGraphClassifier(BaseClassifier): | |||
| """ | |||
| @@ -239,7 +240,7 @@ class AutoGraphClassifier(BaseClassifier): | |||
| Parameters | |||
| ---------- | |||
| dataset: torch_geometric.data.dataset.Dataset | |||
| dataset: autogl.data.dataset | |||
| The multi-graph dataset needed to fit on. | |||
| time_limit: int | |||
| @@ -300,10 +301,17 @@ class AutoGraphClassifier(BaseClassifier): | |||
| # set up the dataset | |||
| if train_split is None and val_split is None: | |||
| assert hasattr(dataset, "train_split") and hasattr(dataset, "val_split"), ( | |||
| "The dataset has no default train/val split! " | |||
| "Please manually pass train and val ratio." | |||
| ) | |||
| # Currently, there are no much implementation difference between pyg and dgl on solver | |||
| # We can use way of hotfix to judge | |||
| if __backend == 'pyg': | |||
| assert hasattr(dataset, "train_split") and hasattr(dataset, "val_split"), ( | |||
| "The dataset has no default train/val split! " | |||
| "Please manually pass train and val ratio." | |||
| ) | |||
| elif __backend == 'dgl': | |||
| # no available solutions here. | |||
| # TODO: we cannot judge whether the graph dataset has train/val/test split on dgl. | |||
| pass | |||
| LOGGER.info("Use the default train/val/test ratio in given dataset") | |||
| # if hasattr(dataset.train_split, "n_splits"): | |||
| # cross_validation = True | |||
| @@ -327,17 +335,29 @@ class AutoGraphClassifier(BaseClassifier): | |||
| dataset = self.feature_module.transform(dataset, inplace=inplace) | |||
| self.dataset = dataset | |||
| assert dataset[0].x is not None, ( | |||
| "Does not support fit on non node-feature dataset!" | |||
| " Please add node features to dataset or specify feature engineers that generate" | |||
| " node features." | |||
| ) | |||
| # check whether the dataset has features. | |||
| # currently we only support graph classification with features. | |||
| if __backend == 'pyg': | |||
| assert dataset[0].x is not None, ( | |||
| "Does not support fit on non node-feature dataset!" | |||
| " Please add node features to dataset or specify feature engineers that generate" | |||
| " node features." | |||
| ) | |||
| elif __backend == 'dgl': | |||
| assert 'feat' in dataset[0].ndata['feat'], ( | |||
| "Does not support fit on non node-feature dataset!" | |||
| " Please add node features to dataset or specify feature engineers that generate" | |||
| " node features." | |||
| ) | |||
| # initialize graph networks | |||
| self._init_graph_module( | |||
| self.gml, | |||
| num_features=dataset.num_node_features, | |||
| num_classes=dataset.num_classes, | |||
| # TODO: what should we use to get feature dimension? | |||
| num_features=dataset.num_node_features if __backend == 'pyg' else dataset[0].ndata['feat'].size(-1), | |||
| num_classes=dataset.num_classes if __backend == 'pyg' else dataset.nclasses, | |||
| feval=evaluator_list, | |||
| device=self.runtime_device, | |||
| loss="cross_entropy" if not hasattr(dataset, "loss") else dataset.loss, | |||
| @@ -410,7 +430,10 @@ class AutoGraphClassifier(BaseClassifier): | |||
| if self.ensemble_module is not None: | |||
| performance = self.ensemble_module.fit( | |||
| result_valid, | |||
| dataset.data.y[dataset.val_index].cpu().detach().numpy(), | |||
| # TODO: get validation set of graphs | |||
| dataset.data.y[dataset.val_index].cpu().detach().numpy() | |||
| if __backend == 'pyg' else | |||
| dataset.labels[dataset.val_index].cpu().detach().numpy(), | |||
| names, | |||
| evaluator_list, | |||
| n_classes=dataset.num_classes, | |||
| @@ -519,7 +542,7 @@ class AutoGraphClassifier(BaseClassifier): | |||
| Parameters | |||
| ---------- | |||
| dataset: torch_geometric.data.dataset.Dataset or None | |||
| dataset: autogl.data.Dataset or None | |||
| The dataset needed to predict. If ``None``, will use the processed dataset | |||
| passed to ``fit()`` instead. Default ``None``. | |||
| @@ -629,7 +652,7 @@ class AutoGraphClassifier(BaseClassifier): | |||
| Parameters | |||
| ---------- | |||
| dataset: torch_geometric.data.dataset.Dataset or None | |||
| dataset: autogl.data.Dataset or None | |||
| The dataset needed to predict. If ``None``, will use the processed dataset passed | |||
| to ``fit()`` instead. Default ``None``. | |||
| @@ -13,21 +13,20 @@ import yaml | |||
| from .base import BaseClassifier | |||
| from ..base import _parse_hp_space, _initialize_single_model | |||
| from ....module.feature import FEATURE_DICT | |||
| from ....module.model import MODEL_DICT, BaseModel | |||
| from ....module.train import TRAINER_DICT, BaseNodeClassificationTrainer | |||
| from ....module.train import get_feval | |||
| from ....module.nas.space import NAS_SPACE_DICT | |||
| from ....module.nas.algorithm import NAS_ALGO_DICT | |||
| from ....module.nas.estimator import NAS_ESTIMATOR_DICT, BaseEstimator | |||
| from ...utils import LeaderBoard, set_seed | |||
| from ....datasets import utils | |||
| from ....utils import get_logger | |||
| from torch_geometric.nn import GATConv, GCNConv | |||
| from ...module.feature import FEATURE_DICT | |||
| from ...module.model import MODEL_DICT, BaseModel | |||
| from ...module.train import TRAINER_DICT, BaseNodeClassificationTrainer | |||
| from ...module.train import get_feval | |||
| from ...module.nas.space import NAS_SPACE_DICT | |||
| from ...module.nas.algorithm import NAS_ALGO_DICT | |||
| from ...module.nas.estimator import NAS_ESTIMATOR_DICT, BaseEstimator | |||
| from ..utils import LeaderBoard, set_seed | |||
| from ...datasets import utils | |||
| from ...utils import get_logger | |||
| from ...backend import DependentBackend | |||
| LOGGER = get_logger("NodeClassifier") | |||
| __backend = DependentBackend.get_backend_name() | |||
| class AutoNodeClassifier(BaseClassifier): | |||
| """ | |||
| @@ -241,7 +240,7 @@ class AutoNodeClassifier(BaseClassifier): | |||
| Parameters | |||
| ---------- | |||
| dataset: torch_geometric.data.dataset.Dataset | |||
| dataset: autogl.data.Dataset | |||
| The dataset needed to fit on. This dataset must have only one graph. | |||
| time_limit: int | |||
| @@ -306,7 +305,10 @@ class AutoNodeClassifier(BaseClassifier): | |||
| # set up the dataset | |||
| if train_split is not None and val_split is not None: | |||
| size = dataset.data.x.shape[0] | |||
| if __backend == 'pyg': | |||
| size = dataset.data.x.shape[0] | |||
| else: | |||
| size = dataset.graphs[0].num_nodes() | |||
| if balanced: | |||
| train_split = ( | |||
| train_split if train_split > 1 else int(train_split * size) | |||
| @@ -325,12 +327,18 @@ class AutoNodeClassifier(BaseClassifier): | |||
| dataset, train_ratio=train_split, val_ratio=val_split | |||
| ) | |||
| else: | |||
| assert hasattr(dataset.data, "train_mask") and hasattr( | |||
| dataset.data, "val_mask" | |||
| ), ( | |||
| "The dataset has no default train/val split! Please manually pass " | |||
| "train and val ratio." | |||
| ) | |||
| if __backend == 'pyg': | |||
| assert hasattr(dataset.data, "train_mask") and hasattr( | |||
| dataset.data, "val_mask" | |||
| ), ( | |||
| "The dataset has no default train/val split! Please manually pass " | |||
| "train and val ratio." | |||
| ) | |||
| elif __backend == 'dgl': | |||
| assert "train_mask" in dataset[0].ndata and "val_mask" in dataset[0].ndata, ( | |||
| "The dataset has no default train/val split! Please manually pass " | |||
| "train and val ratio." | |||
| ) | |||
| LOGGER.info("Use the default train/val/test ratio in given dataset") | |||
| # feature engineering | |||
| @@ -338,26 +346,40 @@ class AutoNodeClassifier(BaseClassifier): | |||
| dataset = self.feature_module.fit_transform(dataset, inplace=inplace) | |||
| self.dataset = dataset | |||
| assert self.dataset[0].x is not None, ( | |||
| "Does not support fit on non node-feature dataset!" | |||
| " Please add node features to dataset or specify feature engineers that generate" | |||
| " node features." | |||
| ) | |||
| # check whether the dataset has features. | |||
| # currently we only support graph classification with features. | |||
| if __backend == 'pyg': | |||
| assert dataset[0].x is not None, ( | |||
| "Does not support fit on non node-feature dataset!" | |||
| " Please add node features to dataset or specify feature engineers that generate" | |||
| " node features." | |||
| ) | |||
| elif __backend == 'dgl': | |||
| # TODO: how can we get features? | |||
| assert 'feat' in dataset[0].ndata['feat'], ( | |||
| "Does not support fit on non node-feature dataset!" | |||
| " Please add node features to dataset or specify feature engineers that generate" | |||
| " node features." | |||
| ) | |||
| # initialize graph networks | |||
| self._init_graph_module( | |||
| self.gml, | |||
| num_features=self.dataset[0].x.shape[1], | |||
| num_classes=dataset.num_classes, | |||
| # TODO: how can we get num_features? | |||
| num_features=self.dataset[0].x.shape[1] if __backend == 'pyg' else self.dataset[0].ndata['feat'].size(-1), | |||
| num_classes=self.dataset.num_classes, | |||
| feval=evaluator_list, | |||
| device=self.runtime_device, | |||
| loss="nll_loss" if not hasattr(dataset, "loss") else dataset.loss, | |||
| loss="nll_loss" if not hasattr(dataset, "loss") else self.dataset.loss, | |||
| ) | |||
| if self.nas_algorithms is not None: | |||
| # perform neural architecture search | |||
| self._init_nas_module( | |||
| num_features=self.dataset[0].x.shape[1], | |||
| # TODO: how can we get num_features? | |||
| num_features=self.dataset[0].x.shape[1] if __backend == 'pyg' else self.dataset[0].ndata['feat'].size(-1), | |||
| num_classes=self.dataset.num_classes, | |||
| feval=evaluator_list, | |||
| device=self.runtime_device, | |||
| @@ -385,7 +407,8 @@ class AutoNodeClassifier(BaseClassifier): | |||
| if isinstance(train_name, str): | |||
| trainer = TRAINER_DICT[train_name]( | |||
| model=model, | |||
| num_features=self.dataset[0].x.shape[1], | |||
| # TODO: how can we get num_features? | |||
| num_features=self.dataset[0].x.shape[1] if __backend == 'pyg' else self.dataset[0].ndata['feat'].size(-1), | |||
| num_classes=self.dataset.num_classes, | |||
| loss="nll_loss" | |||
| if not hasattr(dataset, "loss") | |||
| @@ -398,8 +421,9 @@ class AutoNodeClassifier(BaseClassifier): | |||
| trainer = train_name | |||
| trainer.model = model | |||
| trainer.update_parameters( | |||
| # TODO: how can we get num_features? | |||
| num_features=self.dataset[0].x.shape[1] if __backend == 'pyg' else self.dataset[0].ndata['feat'].size(-1), | |||
| num_classes=self.dataset.num_classes, | |||
| num_features=self.dataset[0].x.shape[1], | |||
| loss="nll_loss" | |||
| if not hasattr(dataset, "loss") | |||
| else dataset.loss, | |||
| @@ -444,7 +468,10 @@ class AutoNodeClassifier(BaseClassifier): | |||
| if self.ensemble_module is not None: | |||
| performance = self.ensemble_module.fit( | |||
| result_valid, | |||
| self.dataset[0].y[self.dataset[0].val_mask].cpu().numpy(), | |||
| # | |||
| self.dataset[0].y[self.dataset[0].val_mask].cpu().numpy() | |||
| if __backend == 'pyg' else | |||
| self.dataset[0].ndata['label'][self.dataset[0].ndata['val_mask']].cpu().numpy(), | |||
| names, | |||
| evaluator_list, | |||
| n_classes=dataset.num_classes, | |||
| @@ -1,13 +0,0 @@ | |||
| """ | |||
| Auto solver for various graph tasks | |||
| """ | |||
| from .classifier import AutoGraphClassifier, AutoNodeClassifier, AutoLinkPredictor | |||
| from ..utils import LeaderBoard | |||
| __all__ = [ | |||
| "AutoNodeClassifier", | |||
| "AutoGraphClassifier", | |||
| "AutoLinkPredictor", | |||
| "LeaderBoard", | |||
| ] | |||