From b0c7c5c49fe30c4792f7bc9847831cf2b6f6d74c Mon Sep 17 00:00:00 2001 From: Beini Date: Tue, 26 Oct 2021 07:14:28 +0000 Subject: [PATCH] auto --- autogl/module/model/dgl/__init__.py | 12 +- autogl/module/model/dgl/gat.py | 35 +- autogl/module/model/dgl/gat_dgl.py | 212 ------------ autogl/module/model/dgl/gcn.py | 67 ++-- autogl/module/model/dgl/gcn_dgl.py | 409 ----------------------- autogl/module/model/dgl/gin.py | 232 ------------- autogl/module/model/dgl/gin_dgl.py | 171 ---------- autogl/module/model/dgl/ginparser.py | 81 ----- autogl/module/model/dgl/graphsage.py | 80 +++-- autogl/module/model/dgl/graphsage_dgl.py | 312 ----------------- 10 files changed, 96 insertions(+), 1515 deletions(-) delete mode 100644 autogl/module/model/dgl/gat_dgl.py delete mode 100644 autogl/module/model/dgl/gcn_dgl.py delete mode 100644 autogl/module/model/dgl/gin.py delete mode 100644 autogl/module/model/dgl/gin_dgl.py delete mode 100644 autogl/module/model/dgl/ginparser.py delete mode 100644 autogl/module/model/dgl/graphsage_dgl.py diff --git a/autogl/module/model/dgl/__init__.py b/autogl/module/model/dgl/__init__.py index 6a245eb..a007c64 100644 --- a/autogl/module/model/dgl/__init__.py +++ b/autogl/module/model/dgl/__init__.py @@ -2,11 +2,11 @@ from ._model_registry import MODEL_DICT, ModelUniversalRegistry, register_model from .base import BaseModel from .topkpool import AutoTopkpool -# from .graph_sage import AutoSAGE + from .graph_saint import GraphSAINTAggregationModel -from .gcn_dgl import GCN,AutoGCN -from .graphsage_dgl import GraphSAGE -from .gat_dgl import GAT +from .gcn import GCN, AutoGCN +from .graphsage import GraphSAGE, AutoSAGE +from .gat import GAT,AutoGAT __all__ = [ "ModelUniversalRegistry", @@ -17,5 +17,7 @@ __all__ = [ "GCN", "AutoGCN", "GraphSAGE", - "GAT" + "AutoSAGE", + "GAT", + "AutoGAT" ] diff --git a/autogl/module/model/dgl/gat.py b/autogl/module/model/dgl/gat.py index d153685..e7ecfc3 100644 --- a/autogl/module/model/dgl/gat.py +++ b/autogl/module/model/dgl/gat.py @@ -1,6 +1,6 @@ import torch import torch.nn.functional as F -from torch_geometric.nn import GATConv +from dgl.nn.pytorch.conv import GATConv from . import register_model from .base import BaseModel, activate_func from ....utils import get_logger @@ -45,8 +45,8 @@ class GAT(torch.nn.Module): GATConv( self.args["features_num"], self.args["hidden"][0], - heads=self.args["heads"], - dropout=self.args["dropout"], + num_heads =self.args["heads"], + attn_drop=self.args["dropout"], ) ) last_dim = self.args["hidden"][0] * self.args["heads"] @@ -55,8 +55,8 @@ class GAT(torch.nn.Module): GATConv( last_dim, self.args["hidden"][i + 1], - heads=self.args["heads"], - dropout=self.args["dropout"], + num_heads=self.args["heads"], + attn_drop=self.args["dropout"], ) ) last_dim = self.args["hidden"][i + 1] * self.args["heads"] @@ -64,41 +64,30 @@ class GAT(torch.nn.Module): GATConv( last_dim, self.args["num_class"], - heads=1, - concat=False, - dropout=self.args["dropout"], + num_heads=1, + attn_drop=self.args["dropout"], ) ) def forward(self, data): try: - x = data.x + x = data.ndata['x'] except: print("no x") pass - try: - edge_index = data.edge_index - except: - print("no index") - pass - try: - edge_weight = data.edge_weight - except: - edge_weight = None - pass - + for i in range(self.num_layer): x = F.dropout(x, p=self.args["dropout"], training=self.training) - x = self.convs[i](x, edge_index, edge_weight) + x = self.convs[i](data, x).flatten(1) if i != self.num_layer - 1: x = activate_func(x, self.args["act"]) return F.log_softmax(x, dim=1) def lp_encode(self, data): - x = data.x + x = data.ndata['x'] for i in range(self.num_layer - 1): - x = self.convs[i](x, data.train_pos_edge_index) + x = self.convs[i](x, data.train_pos_edge_index).flatten(1) if i != self.num_layer - 2: x = activate_func(x, self.args["act"]) # x = F.dropout(x, p=self.args["dropout"], training=self.training) diff --git a/autogl/module/model/dgl/gat_dgl.py b/autogl/module/model/dgl/gat_dgl.py deleted file mode 100644 index e7ecfc3..0000000 --- a/autogl/module/model/dgl/gat_dgl.py +++ /dev/null @@ -1,212 +0,0 @@ -import torch -import torch.nn.functional as F -from dgl.nn.pytorch.conv import GATConv -from . import register_model -from .base import BaseModel, activate_func -from ....utils import get_logger - -LOGGER = get_logger("GATModel") - - -def set_default(args, d): - for k, v in d.items(): - if k not in args: - args[k] = v - return args - - -class GAT(torch.nn.Module): - def __init__(self, args): - super(GAT, self).__init__() - self.args = args - self.num_layer = int(self.args["num_layers"]) - - missing_keys = list( - set( - [ - "features_num", - "num_class", - "num_layers", - "hidden", - "heads", - "dropout", - "act", - ] - ) - - set(self.args.keys()) - ) - if len(missing_keys) > 0: - raise Exception("Missing keys: %s." % ",".join(missing_keys)) - - if not self.num_layer == len(self.args["hidden"]) + 1: - LOGGER.warn("Warning: layer size does not match the length of hidden units") - self.convs = torch.nn.ModuleList() - self.convs.append( - GATConv( - self.args["features_num"], - self.args["hidden"][0], - num_heads =self.args["heads"], - attn_drop=self.args["dropout"], - ) - ) - last_dim = self.args["hidden"][0] * self.args["heads"] - for i in range(self.num_layer - 2): - self.convs.append( - GATConv( - last_dim, - self.args["hidden"][i + 1], - num_heads=self.args["heads"], - attn_drop=self.args["dropout"], - ) - ) - last_dim = self.args["hidden"][i + 1] * self.args["heads"] - self.convs.append( - GATConv( - last_dim, - self.args["num_class"], - num_heads=1, - attn_drop=self.args["dropout"], - ) - ) - - def forward(self, data): - try: - x = data.ndata['x'] - except: - print("no x") - pass - - for i in range(self.num_layer): - x = F.dropout(x, p=self.args["dropout"], training=self.training) - x = self.convs[i](data, x).flatten(1) - if i != self.num_layer - 1: - x = activate_func(x, self.args["act"]) - - return F.log_softmax(x, dim=1) - - def lp_encode(self, data): - x = data.ndata['x'] - for i in range(self.num_layer - 1): - x = self.convs[i](x, data.train_pos_edge_index).flatten(1) - if i != self.num_layer - 2: - x = activate_func(x, self.args["act"]) - # x = F.dropout(x, p=self.args["dropout"], training=self.training) - return x - - def lp_decode(self, z, pos_edge_index, neg_edge_index): - edge_index = torch.cat([pos_edge_index, neg_edge_index], dim=-1) - logits = (z[edge_index[0]] * z[edge_index[1]]).sum(dim=-1) - return logits - - def lp_decode_all(self, z): - prob_adj = z @ z.t() - return (prob_adj > 0).nonzero(as_tuple=False).t() - - -@register_model("gat") -class AutoGAT(BaseModel): - r""" - AutoGAT. The model used in this automodel is GAT, i.e., the graph attentional network from the `"Graph Attention Networks" - `_ paper. The layer is - - .. math:: - \mathbf{x}^{\prime}_i = \alpha_{i,i}\mathbf{\Theta}\mathbf{x}_{i} + - \sum_{j \in \mathcal{N}(i)} \alpha_{i,j}\mathbf{\Theta}\mathbf{x}_{j} - - where the attention coefficients :math:`\alpha_{i,j}` are computed as - - .. math:: - \alpha_{i,j} = - \frac{ - \exp\left(\mathrm{LeakyReLU}\left(\mathbf{a}^{\top} - [\mathbf{\Theta}\mathbf{x}_i \, \Vert \, \mathbf{\Theta}\mathbf{x}_j] - \right)\right)} - {\sum_{k \in \mathcal{N}(i) \cup \{ i \}} - \exp\left(\mathrm{LeakyReLU}\left(\mathbf{a}^{\top} - [\mathbf{\Theta}\mathbf{x}_i \, \Vert \, \mathbf{\Theta}\mathbf{x}_k] - \right)\right)}. - - Parameters - ---------- - num_features: `int`. - The dimension of features. - - num_classes: `int`. - The number of classes. - - device: `torch.device` or `str` - The device where model will be running on. - - init: `bool`. - If True(False), the model will (not) be initialized. - - args: Other parameters. - """ - - def __init__( - self, num_features=None, num_classes=None, device=None, init=False, **args - ): - super(AutoGAT, self).__init__() - self.num_features = num_features if num_features is not None else 0 - self.num_classes = int(num_classes) if num_classes is not None else 0 - self.device = device if device is not None else "cpu" - self.init = True - - self.params = { - "features_num": self.num_features, - "num_class": self.num_classes, - } - self.space = [ - { - "parameterName": "num_layers", - "type": "DISCRETE", - "feasiblePoints": "2,3,4", - }, - { - "parameterName": "hidden", - "type": "NUMERICAL_LIST", - "numericalType": "INTEGER", - "length": 3, - "minValue": [8, 8, 8], - "maxValue": [64, 64, 64], - "scalingType": "LOG", - "cutPara": ("num_layers",), - "cutFunc": lambda x: x[0] - 1, - }, - { - "parameterName": "dropout", - "type": "DOUBLE", - "maxValue": 0.8, - "minValue": 0.2, - "scalingType": "LINEAR", - }, - { - "parameterName": "heads", - "type": "DISCRETE", - "feasiblePoints": "2,4,8,16", - }, - { - "parameterName": "act", - "type": "CATEGORICAL", - "feasiblePoints": ["leaky_relu", "relu", "elu", "tanh"], - }, - ] - - self.hyperparams = { - "num_layers": 2, - "hidden": [32], - "heads": 4, - "dropout": 0.2, - "act": "leaky_relu", - } - - self.initialized = False - if init is True: - self.initialize() - - def initialize(self): - # """Initialize model.""" - if self.initialized: - return - self.initialized = True - self.model = GAT({**self.params, **self.hyperparams}).to(self.device) diff --git a/autogl/module/model/dgl/gcn.py b/autogl/module/model/dgl/gcn.py index 30bc1a7..2ff57d0 100644 --- a/autogl/module/model/dgl/gcn.py +++ b/autogl/module/model/dgl/gcn.py @@ -2,12 +2,14 @@ import torch import torch.nn.functional import typing as _typing -from torch_geometric.nn.conv import GCNConv +from dgl.nn.pytorch.conv import GraphConv +from dgl import remove_self_loop, add_self_loop import autogl.data from . import register_model from .base import BaseModel, activate_func, ClassificationSupportedSequentialModel from ....utils import get_logger + LOGGER = get_logger("GCNModel") @@ -23,12 +25,12 @@ class GCN(ClassificationSupportedSequentialModel): dropout_probability: _typing.Optional[float] = ..., ): super().__init__() - self._convolution: GCNConv = GCNConv( + self._convolution: GraphConv = GraphConv( input_channels, output_channels, - add_self_loops=bool(add_self_loops), - normalize=bool(normalize), + norm='both' if normalize else 'none', ) + self.add_self_loops = bool(add_self_loops), if ( activation_name is not Ellipsis and activation_name is not None @@ -52,22 +54,13 @@ class GCN(ClassificationSupportedSequentialModel): else: self._dropout: _typing.Optional[torch.nn.Dropout] = None - def forward(self, data, enable_activation: bool = True) -> torch.Tensor: - x: torch.Tensor = getattr(data, "x") - edge_index: torch.LongTensor = getattr(data, "edge_index") - edge_weight: _typing.Optional[torch.Tensor] = getattr( - data, "edge_weight", None - ) - """ Validate the arguments """ - if not type(x) == type(edge_index) == torch.Tensor: - raise TypeError - if edge_weight is not None and ( - type(edge_weight) != torch.Tensor - or edge_index.size() != (2, edge_weight.size(0)) - ): - edge_weight: _typing.Optional[torch.Tensor] = None + def forward(self, data, x, enable_activation: bool = True) -> torch.Tensor: + + if self.add_self_loops: + data = remove_self_loop(data) + data = add_self_loop(data) - x: torch.Tensor = self._convolution.forward(x, edge_index, edge_weight) + x: torch.Tensor = self._convolution.forward(data, x) if self._activation_name is not None and enable_activation: x: torch.Tensor = activate_func(x, self._activation_name) if self._dropout is not None: @@ -199,19 +192,21 @@ class GCN(ClassificationSupportedSequentialModel): and len(getattr(data, "edge_indexes")) == len(self.__sequential_encoding_layers) ): + if not data.edata.has_key('edge_weights'): + data.edata['edge_weights']=None return __compose_edge_index_and_weight( - getattr(data, "edge_index"), getattr(data, "edge_weight", None) + data.edges(), data.edata['edge_weights'] ) - for __edge_index in getattr(data, "edge_indexes"): - if type(__edge_index) != torch.Tensor or __edge_index.dtype != torch.int64: - return __compose_edge_index_and_weight( - getattr(data, "edge_index"), getattr(data, "edge_weight", None) - ) + # for __edge_index in getattr(data, "edge_indexes"): + # if type(__edge_index) != torch.Tensor or __edge_index.dtype != torch.int64: + # return __compose_edge_index_and_weight( + # data.edges(), getattr(data, "edge_weight", None) + # ) if ( - hasattr(data, "edge_weights") - and isinstance(getattr(data, "edge_weights"), _typing.Sequence) - and len(getattr(data, "edge_weights")) + data.edata.has_key('edge_weights') + and isinstance(data.edata['edge_weights'], _typing.Sequence) + and len(data.edata.has_key('edge_weights')) == len(self.__sequential_encoding_layers) ): return [ @@ -226,6 +221,12 @@ class GCN(ClassificationSupportedSequentialModel): for __edge_index in getattr(data, "edge_indexes") ] + def forward(self, data): + x = data.ndata['x'] + for gcn in self.__sequential_encoding_layers: + x = gcn(data,x) + return x + def cls_encode(self, data) -> torch.Tensor: edge_indexes_and_weights: _typing.Union[ _typing.Sequence[ @@ -241,7 +242,7 @@ class GCN(ClassificationSupportedSequentialModel): assert len(edge_indexes_and_weights) == len( self.__sequential_encoding_layers ) - x: torch.Tensor = getattr(data, "x") + x: torch.Tensor = data.ndata['x'] for _edge_index_and_weight, gcn in zip( edge_indexes_and_weights, self.__sequential_encoding_layers ): @@ -251,7 +252,7 @@ class GCN(ClassificationSupportedSequentialModel): return x else: """ edge_indexes_and_weights is (edge_index, edge_weight) """ - x = getattr(data, "x") + x = data.ndata['x'] for gcn in self.__sequential_encoding_layers: _temp_data = autogl.data.Data( x=x, edge_index=edge_indexes_and_weights[0] @@ -264,13 +265,13 @@ class GCN(ClassificationSupportedSequentialModel): return torch.nn.functional.log_softmax(x, dim=1) def lp_encode(self, data): - x: torch.Tensor = getattr(data, "x") + x: torch.Tensor = data.ndata['x'] for i in range(len(self.__sequential_encoding_layers) - 2): x = self.__sequential_encoding_layers[i]( - autogl.data.Data(x, getattr(data, "edge_index")) + autogl.data.Data(x, data.edges()) ) x = self.__sequential_encoding_layers[-2]( - autogl.data.Data(x, getattr(data, "edge_index")), enable_activation=False + autogl.data.Data(x, data.edges()), enable_activation=False ) return x diff --git a/autogl/module/model/dgl/gcn_dgl.py b/autogl/module/model/dgl/gcn_dgl.py deleted file mode 100644 index 2ff57d0..0000000 --- a/autogl/module/model/dgl/gcn_dgl.py +++ /dev/null @@ -1,409 +0,0 @@ -import torch -import torch.nn.functional -import typing as _typing - -from dgl.nn.pytorch.conv import GraphConv -from dgl import remove_self_loop, add_self_loop -import autogl.data -from . import register_model -from .base import BaseModel, activate_func, ClassificationSupportedSequentialModel -from ....utils import get_logger - - -LOGGER = get_logger("GCNModel") - - -class GCN(ClassificationSupportedSequentialModel): - class _GCNLayer(torch.nn.Module): - def __init__( - self, - input_channels: int, - output_channels: int, - add_self_loops: bool = True, - normalize: bool = True, - activation_name: _typing.Optional[str] = ..., - dropout_probability: _typing.Optional[float] = ..., - ): - super().__init__() - self._convolution: GraphConv = GraphConv( - input_channels, - output_channels, - norm='both' if normalize else 'none', - ) - self.add_self_loops = bool(add_self_loops), - if ( - activation_name is not Ellipsis - and activation_name is not None - and type(activation_name) == str - ): - self._activation_name: _typing.Optional[str] = activation_name - else: - self._activation_name: _typing.Optional[str] = None - if ( - dropout_probability is not Ellipsis - and dropout_probability is not None - and type(dropout_probability) == float - ): - if dropout_probability < 0: - dropout_probability = 0 - if dropout_probability > 1: - dropout_probability = 1 - self._dropout: _typing.Optional[torch.nn.Dropout] = torch.nn.Dropout( - dropout_probability - ) - else: - self._dropout: _typing.Optional[torch.nn.Dropout] = None - - def forward(self, data, x, enable_activation: bool = True) -> torch.Tensor: - - if self.add_self_loops: - data = remove_self_loop(data) - data = add_self_loop(data) - - x: torch.Tensor = self._convolution.forward(data, x) - if self._activation_name is not None and enable_activation: - x: torch.Tensor = activate_func(x, self._activation_name) - if self._dropout is not None: - x: torch.Tensor = self._dropout.forward(x) - return x - - def __init__( - self, - num_features: int, - num_classes: int, - hidden_features: _typing.Sequence[int], - activation_name: str, - dropout: _typing.Union[ - _typing.Optional[float], _typing.Sequence[_typing.Optional[float]] - ] = None, - add_self_loops: bool = True, - normalize: bool = True, - ): - if isinstance(dropout, _typing.Sequence): - if len(dropout) != len(hidden_features) + 1: - raise TypeError( - "When the dropout argument is a sequence, " - "The sequence length must equal to the number of layers to construct." - ) - for _dropout in dropout: - if _dropout is not None and type(_dropout) != float: - raise TypeError( - "When the dropout argument is a sequence, " - "every item in the sequence must be float or None" - ) - dropout_list: _typing.Sequence[_typing.Optional[float]] = dropout - elif type(dropout) == float: - if dropout < 0: - dropout = 0 - if dropout > 1: - dropout = 1 - dropout_list: _typing.Sequence[_typing.Optional[float]] = [ - dropout for _ in range(len(hidden_features)) - ] + [None] - elif dropout in (None, Ellipsis, ...): - dropout_list: _typing.Sequence[_typing.Optional[float]] = [ - None for _ in range(len(hidden_features) + 1) - ] - else: - raise TypeError( - "The provided dropout argument must be a float number or None or " - "a sequence in which each item is either a float Number or None." - ) - super().__init__() - if len(hidden_features) == 0: - self.__sequential_encoding_layers: torch.nn.ModuleList = ( - torch.nn.ModuleList( - ( - self._GCNLayer( - num_features, - num_classes, - add_self_loops, - normalize, - dropout_probability=dropout_list[0], - ), - ) - ) - ) - else: - self.__sequential_encoding_layers: torch.nn.ModuleList = ( - torch.nn.ModuleList() - ) - self.__sequential_encoding_layers.append( - self._GCNLayer( - num_features, - hidden_features[0], - add_self_loops, - normalize, - activation_name, - dropout_list[0], - ) - ) - for hidden_feature_index in range(len(hidden_features)): - if hidden_feature_index + 1 < len(hidden_features): - self.__sequential_encoding_layers.append( - self._GCNLayer( - hidden_features[hidden_feature_index], - hidden_features[hidden_feature_index + 1], - add_self_loops, - normalize, - activation_name, - dropout_list[hidden_feature_index + 1], - ) - ) - else: - self.__sequential_encoding_layers.append( - self._GCNLayer( - hidden_features[hidden_feature_index], - num_classes, - add_self_loops, - normalize, - dropout_list[-1], - ) - ) - - @property - def sequential_encoding_layers(self) -> torch.nn.ModuleList: - return self.__sequential_encoding_layers - - def __extract_edge_indexes_and_weights( - self, data - ) -> _typing.Union[ - _typing.Sequence[ - _typing.Tuple[torch.LongTensor, _typing.Optional[torch.Tensor]] - ], - _typing.Tuple[torch.LongTensor, _typing.Optional[torch.Tensor]], - ]: - def __compose_edge_index_and_weight( - _edge_index: torch.LongTensor, - _edge_weight: _typing.Optional[torch.Tensor] = None, - ) -> _typing.Tuple[torch.LongTensor, _typing.Optional[torch.Tensor]]: - if type(_edge_index) != torch.Tensor or _edge_index.dtype != torch.int64: - raise TypeError - if _edge_weight is not None and ( - type(_edge_weight) != torch.Tensor - or _edge_index.size() != (2, _edge_weight.size(0)) - ): - _edge_weight: _typing.Optional[torch.Tensor] = None - return _edge_index, _edge_weight - - if not ( - hasattr(data, "edge_indexes") - and isinstance(getattr(data, "edge_indexes"), _typing.Sequence) - and len(getattr(data, "edge_indexes")) - == len(self.__sequential_encoding_layers) - ): - if not data.edata.has_key('edge_weights'): - data.edata['edge_weights']=None - return __compose_edge_index_and_weight( - data.edges(), data.edata['edge_weights'] - ) - # for __edge_index in getattr(data, "edge_indexes"): - # if type(__edge_index) != torch.Tensor or __edge_index.dtype != torch.int64: - # return __compose_edge_index_and_weight( - # data.edges(), getattr(data, "edge_weight", None) - # ) - - if ( - data.edata.has_key('edge_weights') - and isinstance(data.edata['edge_weights'], _typing.Sequence) - and len(data.edata.has_key('edge_weights')) - == len(self.__sequential_encoding_layers) - ): - return [ - __compose_edge_index_and_weight(_edge_index, _edge_weight) - for _edge_index, _edge_weight in zip( - getattr(data, "edge_indexes"), getattr(data, "edge_weights") - ) - ] - else: - return [ - __compose_edge_index_and_weight(__edge_index) - for __edge_index in getattr(data, "edge_indexes") - ] - - def forward(self, data): - x = data.ndata['x'] - for gcn in self.__sequential_encoding_layers: - x = gcn(data,x) - return x - - def cls_encode(self, data) -> torch.Tensor: - edge_indexes_and_weights: _typing.Union[ - _typing.Sequence[ - _typing.Tuple[torch.LongTensor, _typing.Optional[torch.Tensor]] - ], - _typing.Tuple[torch.LongTensor, _typing.Optional[torch.Tensor]], - ] = self.__extract_edge_indexes_and_weights(data) - - if (not isinstance(edge_indexes_and_weights, tuple)) and isinstance( - edge_indexes_and_weights[0], tuple - ): - """ edge_indexes_and_weights is sequence of (edge_index, edge_weight) """ - assert len(edge_indexes_and_weights) == len( - self.__sequential_encoding_layers - ) - x: torch.Tensor = data.ndata['x'] - for _edge_index_and_weight, gcn in zip( - edge_indexes_and_weights, self.__sequential_encoding_layers - ): - _temp_data = autogl.data.Data(x=x, edge_index=_edge_index_and_weight[0]) - _temp_data.edge_weight = _edge_index_and_weight[1] - x = gcn(_temp_data) - return x - else: - """ edge_indexes_and_weights is (edge_index, edge_weight) """ - x = data.ndata['x'] - for gcn in self.__sequential_encoding_layers: - _temp_data = autogl.data.Data( - x=x, edge_index=edge_indexes_and_weights[0] - ) - _temp_data.edge_weight = edge_indexes_and_weights[1] - x = gcn(_temp_data) - return x - - def cls_decode(self, x: torch.Tensor) -> torch.Tensor: - return torch.nn.functional.log_softmax(x, dim=1) - - def lp_encode(self, data): - x: torch.Tensor = data.ndata['x'] - for i in range(len(self.__sequential_encoding_layers) - 2): - x = self.__sequential_encoding_layers[i]( - autogl.data.Data(x, data.edges()) - ) - x = self.__sequential_encoding_layers[-2]( - autogl.data.Data(x, data.edges()), enable_activation=False - ) - return x - - def lp_decode(self, z, pos_edge_index, neg_edge_index): - edge_index = torch.cat([pos_edge_index, neg_edge_index], dim=-1) - logits = (z[edge_index[0]] * z[edge_index[1]]).sum(dim=-1) - return logits - - def lp_decode_all(self, z): - prob_adj = z @ z.t() - return (prob_adj > 0).nonzero(as_tuple=False).t() - - -@register_model("gcn") -class AutoGCN(BaseModel): - r""" - AutoGCN. - The model used in this automodel is GCN, i.e., the graph convolutional network from the - `"Semi-supervised Classification with Graph Convolutional - Networks" `_ paper. The layer is - - .. math:: - - \mathbf{X}^{\prime} = \mathbf{\hat{D}}^{-1/2} \mathbf{\hat{A}} - \mathbf{\hat{D}}^{-1/2} \mathbf{X} \mathbf{\Theta}, - - where :math:`\mathbf{\hat{A}} = \mathbf{A} + \mathbf{I}` denotes the - adjacency matrix with inserted self-loops and - :math:`\hat{D}_{ii} = \sum_{j=0} \hat{A}_{ij}` its diagonal degree matrix. - - Parameters - ---------- - num_features: ``int`` - The dimension of features. - - num_classes: ``int`` - The number of classes. - - device: ``torch.device`` or ``str`` - The device where model will be running on. - - init: `bool`. - If True(False), the model will (not) be initialized. - """ - - def __init__( - self, - num_features: int = ..., - num_classes: int = ..., - device: _typing.Union[str, torch.device] = ..., - init: bool = False, - **kwargs - ) -> None: - super().__init__() - self.num_features = num_features - self.num_classes = num_classes - self.device = device - - self.params = { - "features_num": self.num_features, - "num_class": self.num_classes, - } - self.space = [ - { - "parameterName": "add_self_loops", - "type": "CATEGORICAL", - "feasiblePoints": [1], - }, - { - "parameterName": "normalize", - "type": "CATEGORICAL", - "feasiblePoints": [1], - }, - { - "parameterName": "num_layers", - "type": "DISCRETE", - "feasiblePoints": "2,3,4", - }, - { - "parameterName": "hidden", - "type": "NUMERICAL_LIST", - "numericalType": "INTEGER", - "length": 3, - "minValue": [8, 8, 8], - "maxValue": [128, 128, 128], - "scalingType": "LOG", - "cutPara": ("num_layers",), - "cutFunc": lambda x: x[0] - 1, - }, - { - "parameterName": "dropout", - "type": "DOUBLE", - "maxValue": 0.8, - "minValue": 0.2, - "scalingType": "LINEAR", - }, - { - "parameterName": "act", - "type": "CATEGORICAL", - "feasiblePoints": ["leaky_relu", "relu", "elu", "tanh"], - }, - ] - - # initial point of hp search - # self.hyperparams = { - # "num_layers": 2, - # "hidden": [16], - # "dropout": 0.2, - # "act": "leaky_relu", - # } - - self.hyperparams = { - "num_layers": 3, - "hidden": [128, 64], - "dropout": 0, - "act": "relu", - } - - self.initialized = False - if init is True: - self.initialize() - - def initialize(self): - if self.initialized: - return - self.initialized = True - self.model = GCN( - self.num_features, - self.num_classes, - self.hyperparams.get("hidden"), - self.hyperparams.get("act"), - self.hyperparams.get("dropout", None), - bool(self.hyperparams.get("add_self_loops", True)), - bool(self.hyperparams.get("normalize", True)), - ).to(self.device) diff --git a/autogl/module/model/dgl/gin.py b/autogl/module/model/dgl/gin.py deleted file mode 100644 index 52a495a..0000000 --- a/autogl/module/model/dgl/gin.py +++ /dev/null @@ -1,232 +0,0 @@ -import torch -import torch.nn.functional as F -from torch.nn import Linear, ReLU, Sequential, LeakyReLU, Tanh, ELU -from torch_geometric.nn import GINConv, global_add_pool -from torch.nn import BatchNorm1d -from . import register_model -from .base import BaseModel, activate_func -from copy import deepcopy -from ....utils import get_logger - -LOGGER = get_logger("GINModel") - - -def set_default(args, d): - for k, v in d.items(): - if k not in args: - args[k] = v - return args - - -class GIN(torch.nn.Module): - def __init__(self, args): - super(GIN, self).__init__() - self.args = args - self.num_layer = int(self.args["num_layers"]) - assert self.num_layer > 2, "Number of layers in GIN should not less than 3" - - missing_keys = list( - set( - [ - "features_num", - "num_class", - "num_graph_features", - "num_layers", - "hidden", - "dropout", - "act", - "mlp_layers", - "eps", - ] - ) - - set(self.args.keys()) - ) - if len(missing_keys) > 0: - raise Exception("Missing keys: %s." % ",".join(missing_keys)) - if not self.num_layer == len(self.args["hidden"]) + 1: - LOGGER.warn("Warning: layer size does not match the length of hidden units") - self.num_graph_features = self.args["num_graph_features"] - - if self.args["act"] == "leaky_relu": - act = LeakyReLU() - elif self.args["act"] == "relu": - act = ReLU() - elif self.args["act"] == "elu": - act = ELU() - elif self.args["act"] == "tanh": - act = Tanh() - else: - act = ReLU() - - train_eps = True if self.args["eps"] == "True" else False - - self.convs = torch.nn.ModuleList() - self.bns = torch.nn.ModuleList() - - nn = [Linear(self.args["features_num"], self.args["hidden"][0])] - for _ in range(self.args["mlp_layers"] - 1): - nn.append(act) - nn.append(Linear(self.args["hidden"][0], self.args["hidden"][0])) - # nn.append(BatchNorm1d(self.args['hidden'][0])) - self.convs.append(GINConv(Sequential(*nn), train_eps=train_eps)) - self.bns.append(BatchNorm1d(self.args["hidden"][0])) - - for i in range(self.num_layer - 3): - nn = [Linear(self.args["hidden"][i], self.args["hidden"][i + 1])] - for _ in range(self.args["mlp_layers"] - 1): - nn.append(act) - nn.append( - Linear(self.args["hidden"][i + 1], self.args["hidden"][i + 1]) - ) - # nn.append(BatchNorm1d(self.args['hidden'][i+1])) - self.convs.append(GINConv(Sequential(*nn), train_eps=train_eps)) - self.bns.append(BatchNorm1d(self.args["hidden"][i + 1])) - - self.fc1 = Linear( - self.args["hidden"][self.num_layer - 3] + self.num_graph_features, - self.args["hidden"][self.num_layer - 2], - ) - self.fc2 = Linear( - self.args["hidden"][self.num_layer - 2], self.args["num_class"] - ) - - def forward(self, data): - x, edge_index, batch = data.x, data.edge_index, data.batch - - if self.num_graph_features > 0: - graph_feature = data.gf - - for i in range(self.num_layer - 2): - x = self.convs[i](x, edge_index) - x = activate_func(x, self.args["act"]) - x = self.bns[i](x) - - x = global_add_pool(x, batch) - if self.num_graph_features > 0: - x = torch.cat([x, graph_feature], dim=-1) - x = self.fc1(x) - x = activate_func(x, self.args["act"]) - x = F.dropout(x, p=self.args["dropout"], training=self.training) - - x = self.fc2(x) - - return F.log_softmax(x, dim=1) - - -@register_model("gin") -class AutoGIN(BaseModel): - r""" - AutoGIN. The model used in this automodel is GIN, i.e., the graph isomorphism network from the `"How Powerful are - Graph Neural Networks?" `_ paper. The layer is - - .. math:: - \mathbf{x}^{\prime}_i = h_{\mathbf{\Theta}} \left( (1 + \epsilon) \cdot - \mathbf{x}_i + \sum_{j \in \mathcal{N}(i)} \mathbf{x}_j \right) - - or - - .. math:: - \mathbf{X}^{\prime} = h_{\mathbf{\Theta}} \left( \left( \mathbf{A} + - (1 + \epsilon) \cdot \mathbf{I} \right) \cdot \mathbf{X} \right), - - here :math:`h_{\mathbf{\Theta}}` denotes a neural network, *.i.e.* an MLP. - - Parameters - ---------- - num_features: `int`. - The dimension of features. - - num_classes: `int`. - The number of classes. - - device: `torch.device` or `str` - The device where model will be running on. - - init: `bool`. - If True(False), the model will (not) be initialized. - """ - - def __init__( - self, - num_features=None, - num_classes=None, - device=None, - init=False, - num_graph_features=None, - **args - ): - - super(AutoGIN, self).__init__() - self.num_features = num_features if num_features is not None else 0 - self.num_classes = int(num_classes) if num_classes is not None else 0 - self.num_graph_features = ( - int(num_graph_features) if num_graph_features is not None else 0 - ) - self.device = device if device is not None else "cpu" - self.init = True - - self.params = { - "features_num": self.num_features, - "num_class": self.num_classes, - "num_graph_features": self.num_graph_features, - } - self.space = [ - { - "parameterName": "num_layers", - "type": "DISCRETE", - "feasiblePoints": "4,5,6", - }, - { - "parameterName": "hidden", - "type": "NUMERICAL_LIST", - "numericalType": "INTEGER", - "length": 5, - "minValue": [8, 8, 8, 8, 8], - "maxValue": [64, 64, 64, 64, 64], - "scalingType": "LOG", - "cutPara": ("num_layers",), - "cutFunc": lambda x: x[0] - 1, - }, - { - "parameterName": "dropout", - "type": "DOUBLE", - "maxValue": 0.9, - "minValue": 0.1, - "scalingType": "LINEAR", - }, - { - "parameterName": "act", - "type": "CATEGORICAL", - "feasiblePoints": ["leaky_relu", "relu", "elu", "tanh"], - }, - { - "parameterName": "eps", - "type": "CATEGORICAL", - "feasiblePoints": ["True", "False"], - }, - { - "parameterName": "mlp_layers", - "type": "DISCRETE", - "feasiblePoints": "2,3,4", - }, - ] - - self.hyperparams = { - "num_layers": 3, - "hidden": [64, 32], - "dropout": 0.5, - "act": "relu", - "eps": "True", - "mlp_layers": 2, - } - - self.initialized = False - if init is True: - self.initialize() - - def initialize(self): - # """Initialize model.""" - if self.initialized: - return - self.initialized = True - self.model = GIN({**self.params, **self.hyperparams}).to(self.device) diff --git a/autogl/module/model/dgl/gin_dgl.py b/autogl/module/model/dgl/gin_dgl.py deleted file mode 100644 index 8a5408e..0000000 --- a/autogl/module/model/dgl/gin_dgl.py +++ /dev/null @@ -1,171 +0,0 @@ -""" -How Powerful are Graph Neural Networks -https://arxiv.org/abs/1810.00826 -https://openreview.net/forum?id=ryGs6iA5Km -Author's implementation: https://github.com/weihua916/powerful-gnns -""" - - -import torch -import torch.nn as nn -import torch.nn.functional as F -from dgl.nn.pytorch.conv import GINConv -from dgl.nn.pytorch.glob import SumPooling, AvgPooling, MaxPooling - - -class ApplyNodeFunc(nn.Module): - """Update the node feature hv with MLP, BN and ReLU.""" - def __init__(self, mlp): - super(ApplyNodeFunc, self).__init__() - self.mlp = mlp - self.bn = nn.BatchNorm1d(self.mlp.output_dim) - - def forward(self, h): - h = self.mlp(h) - h = self.bn(h) - h = F.relu(h) - return h - - -class MLP(nn.Module): - """MLP with linear output""" - def __init__(self, num_layers, input_dim, hidden_dim, output_dim): - """MLP layers construction - - Paramters - --------- - num_layers: int - The number of linear layers - input_dim: int - The dimensionality of input features - hidden_dim: int - The dimensionality of hidden units at ALL layers - output_dim: int - The number of classes for prediction - - """ - super(MLP, self).__init__() - self.linear_or_not = True # default is linear model - self.num_layers = num_layers - self.output_dim = output_dim - - if num_layers < 1: - raise ValueError("number of layers should be positive!") - elif num_layers == 1: - # Linear model - self.linear = nn.Linear(input_dim, output_dim) - else: - # Multi-layer model - self.linear_or_not = False - self.linears = torch.nn.ModuleList() - self.batch_norms = torch.nn.ModuleList() - - self.linears.append(nn.Linear(input_dim, hidden_dim)) - for layer in range(num_layers - 2): - self.linears.append(nn.Linear(hidden_dim, hidden_dim)) - self.linears.append(nn.Linear(hidden_dim, output_dim)) - - for layer in range(num_layers - 1): - self.batch_norms.append(nn.BatchNorm1d((hidden_dim))) - - def forward(self, x): - if self.linear_or_not: - # If linear model - return self.linear(x) - else: - # If MLP - h = x - for i in range(self.num_layers - 1): - h = F.relu(self.batch_norms[i](self.linears[i](h))) - return self.linears[-1](h) - - -class GIN(nn.Module): - """GIN model""" - def __init__(self, num_layers, num_mlp_layers, input_dim, hidden_dim, - output_dim, final_dropout, learn_eps, graph_pooling_type, - neighbor_pooling_type): - """model parameters setting - - Paramters - --------- - num_layers: int - The number of linear layers in the neural network - num_mlp_layers: int - The number of linear layers in mlps - input_dim: int - The dimensionality of input features - hidden_dim: int - The dimensionality of hidden units at ALL layers - output_dim: int - The number of classes for prediction - final_dropout: float - dropout ratio on the final linear layer - learn_eps: boolean - If True, learn epsilon to distinguish center nodes from neighbors - If False, aggregate neighbors and center nodes altogether. - neighbor_pooling_type: str - how to aggregate neighbors (sum, mean, or max) - graph_pooling_type: str - how to aggregate entire nodes in a graph (sum, mean or max) - - """ - super(GIN, self).__init__() - self.num_layers = num_layers - self.learn_eps = learn_eps - - # List of MLPs - self.ginlayers = torch.nn.ModuleList() - self.batch_norms = torch.nn.ModuleList() - - for layer in range(self.num_layers - 1): - if layer == 0: - mlp = MLP(num_mlp_layers, input_dim, hidden_dim, hidden_dim) - else: - mlp = MLP(num_mlp_layers, hidden_dim, hidden_dim, hidden_dim) - - self.ginlayers.append( - GINConv(ApplyNodeFunc(mlp), neighbor_pooling_type, 0, self.learn_eps)) - self.batch_norms.append(nn.BatchNorm1d(hidden_dim)) - - # Linear function for graph poolings of output of each layer - # which maps the output of different layers into a prediction score - self.linears_prediction = torch.nn.ModuleList() - - for layer in range(num_layers): - if layer == 0: - self.linears_prediction.append( - nn.Linear(input_dim, output_dim)) - else: - self.linears_prediction.append( - nn.Linear(hidden_dim, output_dim)) - - self.drop = nn.Dropout(final_dropout) - - if graph_pooling_type == 'sum': - self.pool = SumPooling() - elif graph_pooling_type == 'mean': - self.pool = AvgPooling() - elif graph_pooling_type == 'max': - self.pool = MaxPooling() - else: - raise NotImplementedError - - def forward(self, g, h): - # list of hidden representation at each layer (including input) - hidden_rep = [h] - - for i in range(self.num_layers - 1): - h = self.ginlayers[i](g, h) - h = self.batch_norms[i](h) - h = F.relu(h) - hidden_rep.append(h) - - score_over_layer = 0 - - # perform pooling over all nodes in each graph in every layer - for i, h in enumerate(hidden_rep): - pooled_h = self.pool(g, h) - score_over_layer += self.drop(self.linears_prediction[i](pooled_h)) - - return score_over_layer diff --git a/autogl/module/model/dgl/ginparser.py b/autogl/module/model/dgl/ginparser.py deleted file mode 100644 index 280aa12..0000000 --- a/autogl/module/model/dgl/ginparser.py +++ /dev/null @@ -1,81 +0,0 @@ -"""Parser for arguments - -Put all arguments in one file and group similar arguments -""" -import argparse - - -class Parser(): - - def __init__(self, description): - ''' - arguments parser - ''' - self.parser = argparse.ArgumentParser(description=description) - self.args = None - self._parse() - - def _parse(self): - # dataset - self.parser.add_argument( - '--dataset', type=str, default="MUTAG", - choices=['MUTAG', 'COLLAB', 'IMDBBINARY', 'IMDBMULTI'], - help='name of dataset (default: MUTAG)') - self.parser.add_argument( - '--batch_size', type=int, default=32, - help='batch size for training and validation (default: 32)') - self.parser.add_argument( - '--fold_idx', type=int, default=0, - help='the index(<10) of fold in 10-fold validation.') - self.parser.add_argument( - '--filename', type=str, default="", - help='output file') - - # device - self.parser.add_argument( - '--disable-cuda', action='store_true', - help='Disable CUDA') - self.parser.add_argument( - '--device', type=int, default=0, - help='which gpu device to use (default: 0)') - - # net - self.parser.add_argument( - '--num_layers', type=int, default=5, - help='number of layers (default: 5)') - self.parser.add_argument( - '--num_mlp_layers', type=int, default=2, - help='number of MLP layers(default: 2). 1 means linear model.') - self.parser.add_argument( - '--hidden_dim', type=int, default=64, - help='number of hidden units (default: 64)') - - # graph - self.parser.add_argument( - '--graph_pooling_type', type=str, - default="sum", choices=["sum", "mean", "max"], - help='type of graph pooling: sum, mean or max') - self.parser.add_argument( - '--neighbor_pooling_type', type=str, - default="sum", choices=["sum", "mean", "max"], - help='type of neighboring pooling: sum, mean or max') - self.parser.add_argument( - '--learn_eps', action="store_true", - help='learn the epsilon weighting') - - # learning - self.parser.add_argument( - '--seed', type=int, default=0, - help='random seed (default: 0)') - self.parser.add_argument( - '--epochs', type=int, default=350, - help='number of epochs to train (default: 350)') - self.parser.add_argument( - '--lr', type=float, default=0.01, - help='learning rate (default: 0.01)') - self.parser.add_argument( - '--final_dropout', type=float, default=0.5, - help='final layer dropout (default: 0.5)') - - # done - self.args = self.parser.parse_args() diff --git a/autogl/module/model/dgl/graphsage.py b/autogl/module/model/dgl/graphsage.py index 6cf5a17..433e0d7 100644 --- a/autogl/module/model/dgl/graphsage.py +++ b/autogl/module/model/dgl/graphsage.py @@ -1,7 +1,8 @@ import torch import typing as _typing -from torch_geometric.nn.conv import SAGEConv +import torch.nn.functional as F +from dgl.nn.pytorch.conv import SAGEConv import torch.nn.functional import autogl.data from . import register_model @@ -23,7 +24,7 @@ class GraphSAGE(ClassificationSupportedSequentialModel): ): super().__init__() self._convolution: SAGEConv = SAGEConv( - input_channels, output_channels, aggr=aggr + input_channels, output_channels, aggregator_type=aggr ) if ( activation_name is not Ellipsis @@ -48,14 +49,10 @@ class GraphSAGE(ClassificationSupportedSequentialModel): else: self._dropout: _typing.Optional[torch.nn.Dropout] = None - def forward(self, data, enable_activation: bool = True) -> torch.Tensor: - x: torch.Tensor = getattr(data, "x") - edge_index: torch.Tensor = getattr(data, "edge_index") - if type(x) != torch.Tensor or type(edge_index) != torch.Tensor: - raise TypeError - - x: torch.Tensor = self._convolution.forward(x, edge_index) - if self._activation_name is not None and enable_activation: + def forward(self, data, x, enable_activation: bool = True) -> torch.Tensor: + # x = data.ndata['x'] + x: torch.Tensor = self._convolution.forward(data, x) + if (self._activation_name is not None) and enable_activation: x: torch.Tensor = activate_func(x, self._activation_name) if self._dropout is not None: x: torch.Tensor = self._dropout.forward(x) @@ -145,7 +142,7 @@ class GraphSAGE(ClassificationSupportedSequentialModel): hidden_features[i], num_classes, aggr, - _layers_dropout[i + 1], + dropout_probability=_layers_dropout[i + 1], ) ) @@ -154,41 +151,41 @@ class GraphSAGE(ClassificationSupportedSequentialModel): return self.__sequential_encoding_layers def cls_encode(self, data) -> torch.Tensor: - if ( - hasattr(data, "edge_indexes") - and isinstance(getattr(data, "edge_indexes"), _typing.Sequence) - and len(getattr(data, "edge_indexes")) - == len(self.__sequential_encoding_layers) - ): - for __edge_index in getattr(data, "edge_indexes"): - if type(__edge_index) != torch.Tensor: - raise TypeError - """ Layer-wise encode """ - x: torch.Tensor = getattr(data, "x") - for i, __edge_index in enumerate(getattr(data, "edge_indexes")): - x: torch.Tensor = self.__sequential_encoding_layers[i]( - autogl.data.Data(x=x, edge_index=__edge_index) - ) - return x - else: - x: torch.Tensor = getattr(data, "x") - for i in range(len(self.__sequential_encoding_layers)): - x = self.__sequential_encoding_layers[i]( - autogl.data.Data(x, getattr(data, "edge_index")) - ) - return x + # if ( + # hasattr(data, "edge_indexes") + # and isinstance(getattr(data, "edge_indexes"), _typing.Sequence) + # and len(getattr(data, "edge_indexes")) + # == len(self.__sequential_encoding_layers) + # ): + # for __edge_index in getattr(data, "edge_indexes"): + # if type(__edge_index) != torch.Tensor: + # raise TypeError + # """ Layer-wise encode """ + # x: torch.Tensor = getattr(data, "x") + # for i, __edge_index in enumerate(getattr(data, "edge_indexes")): + # x: torch.Tensor = self.__sequential_encoding_layers[i]( + # autogl.data.Data(x=x, edge_index=__edge_index) + # ) + # return x + # else: + x: torch.Tensor = data.ndata['x'] + for i in range(len(self.__sequential_encoding_layers)): + x = self.__sequential_encoding_layers[i]( + autogl.data.Data(x, data.edges()) + ) + return x def cls_decode(self, x: torch.Tensor) -> torch.Tensor: return torch.nn.functional.log_softmax(x, dim=1) def lp_encode(self, data): - x: torch.Tensor = getattr(data, "x") + x: torch.Tensor = data.ndata['x'] for i in range(len(self.__sequential_encoding_layers) - 2): x = self.__sequential_encoding_layers[i]( - autogl.data.Data(x, getattr(data, "edge_index")) + autogl.data.Data(x, data.edges()) ) x = self.__sequential_encoding_layers[-2]( - autogl.data.Data(x, getattr(data, "edge_index")), enable_activation=False + autogl.data.Data(x, data.edges()), enable_activation=False ) return x @@ -200,6 +197,15 @@ class GraphSAGE(ClassificationSupportedSequentialModel): def lp_decode_all(self, z): prob_adj = z @ z.t() return (prob_adj > 0).nonzero(as_tuple=False).t() + + def forward(self, data): + # only for test + x = data.ndata['x'] + for i in range(len(self.__sequential_encoding_layers)): + x = self.__sequential_encoding_layers[i](data,x) + + return F.log_softmax(x, dim=1) + @register_model("sage") diff --git a/autogl/module/model/dgl/graphsage_dgl.py b/autogl/module/model/dgl/graphsage_dgl.py deleted file mode 100644 index 433e0d7..0000000 --- a/autogl/module/model/dgl/graphsage_dgl.py +++ /dev/null @@ -1,312 +0,0 @@ -import torch -import typing as _typing - -import torch.nn.functional as F -from dgl.nn.pytorch.conv import SAGEConv -import torch.nn.functional -import autogl.data -from . import register_model -from .base import BaseModel, activate_func, ClassificationSupportedSequentialModel -from ....utils import get_logger - -LOGGER = get_logger("SAGEModel") - - -class GraphSAGE(ClassificationSupportedSequentialModel): - class _SAGELayer(torch.nn.Module): - def __init__( - self, - input_channels: int, - output_channels: int, - aggr: str, - activation_name: _typing.Optional[str] = ..., - dropout_probability: _typing.Optional[float] = ..., - ): - super().__init__() - self._convolution: SAGEConv = SAGEConv( - input_channels, output_channels, aggregator_type=aggr - ) - if ( - activation_name is not Ellipsis - and activation_name is not None - and type(activation_name) == str - ): - self._activation_name: _typing.Optional[str] = activation_name - else: - self._activation_name: _typing.Optional[str] = None - if ( - dropout_probability is not Ellipsis - and dropout_probability is not None - and type(dropout_probability) == float - ): - if dropout_probability < 0: - dropout_probability = 0 - if dropout_probability > 1: - dropout_probability = 1 - self._dropout: _typing.Optional[torch.nn.Dropout] = torch.nn.Dropout( - dropout_probability - ) - else: - self._dropout: _typing.Optional[torch.nn.Dropout] = None - - def forward(self, data, x, enable_activation: bool = True) -> torch.Tensor: - # x = data.ndata['x'] - x: torch.Tensor = self._convolution.forward(data, x) - if (self._activation_name is not None) and enable_activation: - x: torch.Tensor = activate_func(x, self._activation_name) - if self._dropout is not None: - x: torch.Tensor = self._dropout.forward(x) - return x - - def __init__( - self, - num_features: int, - num_classes: int, - hidden_features: _typing.Sequence[int], - activation_name: str, - layers_dropout: _typing.Union[ - _typing.Optional[float], _typing.Sequence[_typing.Optional[float]] - ] = None, - aggr: str = "mean", - ): - super().__init__() - if not type(num_features) == type(num_classes) == int: - raise TypeError - if not isinstance(hidden_features, _typing.Sequence): - raise TypeError - for hidden_feature in hidden_features: - if type(hidden_feature) != int: - raise TypeError - elif hidden_feature <= 0: - raise ValueError - if isinstance(layers_dropout, _typing.Sequence): - if len(layers_dropout) != (len(hidden_features) + 1): - raise TypeError - for d in layers_dropout: - if d is not None and type(d) != float: - raise TypeError - _layers_dropout: _typing.Sequence[_typing.Optional[float]] = layers_dropout - elif layers_dropout is None or type(layers_dropout) == float: - _layers_dropout: _typing.Sequence[_typing.Optional[float]] = [ - layers_dropout for _ in range(len(hidden_features)) - ] + [None] - else: - raise TypeError - if not type(activation_name) == type(aggr) == str: - raise TypeError - if aggr not in ("add", "max", "mean"): - aggr = "mean" - - if len(hidden_features) == 0: - self.__sequential_encoding_layers: torch.nn.ModuleList = ( - torch.nn.ModuleList( - [ - self._SAGELayer( - num_features, - num_classes, - aggr, - activation_name, - _layers_dropout[0], - ) - ] - ) - ) - else: - self.__sequential_encoding_layers: torch.nn.ModuleList = ( - torch.nn.ModuleList( - [ - self._SAGELayer( - num_features, - hidden_features[0], - aggr, - activation_name, - _layers_dropout[0], - ) - ] - ) - ) - for i in range(len(hidden_features)): - if i + 1 < len(hidden_features): - self.__sequential_encoding_layers.append( - self._SAGELayer( - hidden_features[i], - hidden_features[i + 1], - aggr, - activation_name, - _layers_dropout[i + 1], - ) - ) - else: - self.__sequential_encoding_layers.append( - self._SAGELayer( - hidden_features[i], - num_classes, - aggr, - dropout_probability=_layers_dropout[i + 1], - ) - ) - - @property - def sequential_encoding_layers(self) -> torch.nn.ModuleList: - return self.__sequential_encoding_layers - - def cls_encode(self, data) -> torch.Tensor: - # if ( - # hasattr(data, "edge_indexes") - # and isinstance(getattr(data, "edge_indexes"), _typing.Sequence) - # and len(getattr(data, "edge_indexes")) - # == len(self.__sequential_encoding_layers) - # ): - # for __edge_index in getattr(data, "edge_indexes"): - # if type(__edge_index) != torch.Tensor: - # raise TypeError - # """ Layer-wise encode """ - # x: torch.Tensor = getattr(data, "x") - # for i, __edge_index in enumerate(getattr(data, "edge_indexes")): - # x: torch.Tensor = self.__sequential_encoding_layers[i]( - # autogl.data.Data(x=x, edge_index=__edge_index) - # ) - # return x - # else: - x: torch.Tensor = data.ndata['x'] - for i in range(len(self.__sequential_encoding_layers)): - x = self.__sequential_encoding_layers[i]( - autogl.data.Data(x, data.edges()) - ) - return x - - def cls_decode(self, x: torch.Tensor) -> torch.Tensor: - return torch.nn.functional.log_softmax(x, dim=1) - - def lp_encode(self, data): - x: torch.Tensor = data.ndata['x'] - for i in range(len(self.__sequential_encoding_layers) - 2): - x = self.__sequential_encoding_layers[i]( - autogl.data.Data(x, data.edges()) - ) - x = self.__sequential_encoding_layers[-2]( - autogl.data.Data(x, data.edges()), enable_activation=False - ) - return x - - def lp_decode(self, z, pos_edge_index, neg_edge_index): - edge_index = torch.cat([pos_edge_index, neg_edge_index], dim=-1) - logits = (z[edge_index[0]] * z[edge_index[1]]).sum(dim=-1) - return logits - - def lp_decode_all(self, z): - prob_adj = z @ z.t() - return (prob_adj > 0).nonzero(as_tuple=False).t() - - def forward(self, data): - # only for test - x = data.ndata['x'] - for i in range(len(self.__sequential_encoding_layers)): - x = self.__sequential_encoding_layers[i](data,x) - - return F.log_softmax(x, dim=1) - - - -@register_model("sage") -class AutoSAGE(BaseModel): - r""" - AutoSAGE. The model used in this automodel is GraphSAGE, i.e., the GraphSAGE from the `"Inductive Representation Learning on - Large Graphs" `_ paper. The layer is - - .. math:: - - \mathbf{x}^{\prime}_i = \mathbf{W}_1 \mathbf{x}_i + \mathbf{W_2} \cdot - \mathrm{mean}_{j \in \mathcal{N(i)}} \mathbf{x}_j - - Parameters - ---------- - num_features: `int`. - The dimension of features. - - num_classes: `int`. - The number of classes. - - device: `torch.device` or `str` - The device where model will be running on. - - init: `bool`. - If True(False), the model will (not) be initialized. - - """ - - def __init__( - self, num_features=None, num_classes=None, device=None, init=False, **args - ): - - super(AutoSAGE, self).__init__() - - self.num_features = num_features if num_features is not None else 0 - self.num_classes = int(num_classes) if num_classes is not None else 0 - self.device = device if device is not None else "cpu" - self.init = True - - self.params = { - "features_num": self.num_features, - "num_class": self.num_classes, - } - self.space = [ - { - "parameterName": "num_layers", - "type": "DISCRETE", - "feasiblePoints": "2,3,4", - }, - { - "parameterName": "hidden", - "type": "NUMERICAL_LIST", - "numericalType": "INTEGER", - "length": 3, - "minValue": [8, 8, 8], - "maxValue": [128, 128, 128], - "scalingType": "LOG", - "cutPara": ("num_layers",), - "cutFunc": lambda x: x[0] - 1, - }, - { - "parameterName": "dropout", - "type": "DOUBLE", - "maxValue": 0.8, - "minValue": 0.2, - "scalingType": "LINEAR", - }, - { - "parameterName": "act", - "type": "CATEGORICAL", - "feasiblePoints": ["leaky_relu", "relu", "elu", "tanh"], - }, - { - "parameterName": "agg", - "type": "CATEGORICAL", - "feasiblePoints": ["mean", "add", "max"], - }, - ] - - self.hyperparams = { - "num_layers": 3, - "hidden": [64, 32], - "dropout": 0.5, - "act": "relu", - "agg": "mean", - } - - self.initialized = False - if init is True: - self.initialize() - - def initialize(self): - if self.initialized: - return - self.initialized = True - self.model = GraphSAGE( - self.num_features, - self.num_classes, - self.hyperparams.get("hidden"), - self.hyperparams.get("act", "relu"), - self.hyperparams.get("dropout", None), - self.hyperparams.get("agg", "mean"), - ).to(self.device)