| @@ -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" | |||
| ] | |||
| @@ -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) | |||
| @@ -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" | |||
| <https://arxiv.org/abs/1710.10903>`_ 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) | |||
| @@ -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 | |||
| @@ -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" <https://arxiv.org/abs/1609.02907>`_ 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) | |||
| @@ -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?" <https://arxiv.org/abs/1810.00826>`_ 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) | |||
| @@ -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 | |||
| @@ -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() | |||
| @@ -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") | |||
| @@ -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" <https://arxiv.org/abs/1706.02216>`_ 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) | |||