From d0396c8bd250e9e5f2ceb10c43df40177375fa79 Mon Sep 17 00:00:00 2001 From: Frozenmad Date: Tue, 24 Aug 2021 18:54:39 +0800 Subject: [PATCH] change basic order --- autogl/solver/__init__.py | 21 +++-- autogl/solver/{pyg => }/base.py | 28 +++--- .../{pyg/classifier => classfier}/__init__.py | 2 +- .../{pyg/classifier => classfier}/base.py | 4 +- .../graph_classifier.py | 67 ++++++++----- .../link_predictor.py | 0 .../node_classifier.py | 93 ++++++++++++------- autogl/solver/dgl/__init__.py | 0 autogl/solver/pyg/__init__.py | 13 --- 9 files changed, 133 insertions(+), 95 deletions(-) rename autogl/solver/{pyg => }/base.py (95%) rename autogl/solver/{pyg/classifier => classfier}/__init__.py (90%) rename autogl/solver/{pyg/classifier => classfier}/base.py (95%) rename autogl/solver/{pyg/classifier => classfier}/graph_classifier.py (92%) rename autogl/solver/{pyg/classifier => classfier}/link_predictor.py (100%) rename autogl/solver/{pyg/classifier => classfier}/node_classifier.py (91%) delete mode 100644 autogl/solver/dgl/__init__.py delete mode 100644 autogl/solver/pyg/__init__.py diff --git a/autogl/solver/__init__.py b/autogl/solver/__init__.py index 83bf0c1..9fef37a 100644 --- a/autogl/solver/__init__.py +++ b/autogl/solver/__init__.py @@ -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", +] diff --git a/autogl/solver/pyg/base.py b/autogl/solver/base.py similarity index 95% rename from autogl/solver/pyg/base.py rename to autogl/solver/base.py index b0d69ca..4c88b21 100644 --- a/autogl/solver/pyg/base.py +++ b/autogl/solver/base.py @@ -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] ) diff --git a/autogl/solver/pyg/classifier/__init__.py b/autogl/solver/classfier/__init__.py similarity index 90% rename from autogl/solver/pyg/classifier/__init__.py rename to autogl/solver/classfier/__init__.py index e30c582..7cc0b07 100644 --- a/autogl/solver/pyg/classifier/__init__.py +++ b/autogl/solver/classfier/__init__.py @@ -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 diff --git a/autogl/solver/pyg/classifier/base.py b/autogl/solver/classfier/base.py similarity index 95% rename from autogl/solver/pyg/classifier/base.py rename to autogl/solver/classfier/base.py index 86df76c..96f84a3 100644 --- a/autogl/solver/pyg/classifier/base.py +++ b/autogl/solver/classfier/base.py @@ -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): diff --git a/autogl/solver/pyg/classifier/graph_classifier.py b/autogl/solver/classfier/graph_classifier.py similarity index 92% rename from autogl/solver/pyg/classifier/graph_classifier.py rename to autogl/solver/classfier/graph_classifier.py index fdcb581..adc318b 100644 --- a/autogl/solver/pyg/classifier/graph_classifier.py +++ b/autogl/solver/classfier/graph_classifier.py @@ -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``. diff --git a/autogl/solver/pyg/classifier/link_predictor.py b/autogl/solver/classfier/link_predictor.py similarity index 100% rename from autogl/solver/pyg/classifier/link_predictor.py rename to autogl/solver/classfier/link_predictor.py diff --git a/autogl/solver/pyg/classifier/node_classifier.py b/autogl/solver/classfier/node_classifier.py similarity index 91% rename from autogl/solver/pyg/classifier/node_classifier.py rename to autogl/solver/classfier/node_classifier.py index 9f5c085..5c8eb81 100644 --- a/autogl/solver/pyg/classifier/node_classifier.py +++ b/autogl/solver/classfier/node_classifier.py @@ -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, diff --git a/autogl/solver/dgl/__init__.py b/autogl/solver/dgl/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/autogl/solver/pyg/__init__.py b/autogl/solver/pyg/__init__.py deleted file mode 100644 index 9fef37a..0000000 --- a/autogl/solver/pyg/__init__.py +++ /dev/null @@ -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", -]