diff --git a/autogl/module/model/dgl/gat.py b/autogl/module/model/dgl/gat.py index 1aec580..3151708 100644 --- a/autogl/module/model/dgl/gat.py +++ b/autogl/module/model/dgl/gat.py @@ -5,6 +5,7 @@ from . import register_model from .base import BaseModel, activate_func from ....utils import get_logger + LOGGER = get_logger("GATModel") @@ -41,11 +42,14 @@ class GAT(torch.nn.Module): 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"], + feat_drop=self.args.get("feat_drop", self.args["dropout"]), attn_drop=self.args["dropout"], ) ) @@ -56,6 +60,7 @@ class GAT(torch.nn.Module): last_dim, self.args["hidden"][i + 1], num_heads=self.args["heads"], + feat_drop=self.args.get("feat_drop", self.args["dropout"]), attn_drop=self.args["dropout"], ) ) @@ -65,6 +70,7 @@ class GAT(torch.nn.Module): last_dim, self.args["num_class"], num_heads=1, + feat_drop=self.args.get("feat_drop", self.args["dropout"]), attn_drop=self.args["dropout"], ) ) @@ -77,7 +83,6 @@ class GAT(torch.nn.Module): 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"]) @@ -87,10 +92,10 @@ class GAT(torch.nn.Module): def lp_encode(self, data): x = data.ndata['feat'] for i in range(self.num_layer - 1): - x = self.convs[i](x, data.train_pos_edge_index).flatten(1) + x = self.convs[i](data).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): diff --git a/autogl/module/model/dgl/gcn.py b/autogl/module/model/dgl/gcn.py index bf6b070..54bb11b 100644 --- a/autogl/module/model/dgl/gcn.py +++ b/autogl/module/model/dgl/gcn.py @@ -14,246 +14,77 @@ 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: Optional[str] = None, - dropout_probability: Optional[Real] = None, - ): - super().__init__() - self._convolution: GraphConv = GraphConv( - input_channels, - output_channels, - norm='both' if normalize else 'none', +class GCN(torch.nn.Module): + def __init__(self, args): + super(GCN, self).__init__() + self.args = args + self.num_layer = int(self.args["num_layers"]) + + missing_keys = list( + set( + [ + "features_num", + "num_class", + "num_layers", + "hidden", + "dropout", + "act", + ] ) - self.add_self_loops = bool(add_self_loops), - if isinstance(activation_name, str): - self._activation_name = activation_name - else: - self._activation_name = None - if isinstance(dropout_probability, Real): - if dropout_probability < 0: - dropout_probability = 0 - if dropout_probability > 1: - dropout_probability = 1 - self._dropout = torch.nn.Dropout(dropout_probability) - else: - self._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 + - set(self.args.keys()) + ) + if len(missing_keys) > 0: + raise Exception("Missing keys: %s." % ",".join(missing_keys)) - def __init__( - self, - num_features: int, - num_classes: int, - hidden_features: Sequence[int], - activation_name: str, - dropout: Union[Real, Sequence[Optional[Real]], None] = None, - add_self_loops: bool = True, - normalize: bool = True, - ): - if isinstance(dropout, 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 not isinstance(_dropout, Real): - raise TypeError( - "When the dropout argument is a sequence, " - "every item in the sequence must be float or None" - ) - dropout_list: Sequence[Optional[Real]] = dropout - elif isinstance(dropout, Real): - if dropout < 0: - dropout = 0 - if dropout > 1: - dropout = 1 - dropout_list: Sequence[Real] = [ - dropout for _ in range(len(hidden_features)) - ] + [None] - elif dropout is None: - dropout_list: Sequence[None] = [ - 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], - ), - ) - ) + 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( + GraphConv( + self.args["features_num"], + self.args["hidden"][0] ) - else: - self.__sequential_encoding_layers = 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 i in range(self.num_layer - 2): + self.convs.append( + GraphConv( + self.args["hidden"][0], + self.args["hidden"][i + 1] ) ) - - 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 - ) -> Union[ - Sequence[Tuple[torch.LongTensor, Optional[torch.Tensor]]], - Tuple[torch.LongTensor, Optional[torch.Tensor]], - ]: - def __compose_edge_index_and_weight( - _edge_index: torch.LongTensor, - _edge_weight: Optional[torch.Tensor] = None, - ) -> Tuple[torch.LongTensor, 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: Optional[torch.Tensor] = None - return _edge_index, _edge_weight - - if not ( - hasattr(data, "edge_indexes") - and isinstance(getattr(data, "edge_indexes"), 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'] + self.convs.append( + GraphConv( + self.args["hidden"][-1], + self.args["num_class"] ) - # 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'], 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['feat'] - for gcn in self.__sequential_encoding_layers: - x = gcn(data,x) - return F.log_softmax(x, dim=-1) + for i in range(len(self.convs)): + if i!=0: + x = F.dropout(x, p=self.args["dropout"], training=self.training) + x = self.convs[i](data, x) + + if i != self.num_layer - 1: + x = activate_func(x, self.args["act"]) + + return F.log_softmax(x, dim=1) + def cls_encode(self, data) -> torch.Tensor: return self(data) - - edge_indexes_and_weights: Union[ - Sequence[Tuple[torch.LongTensor, Optional[torch.Tensor]]], - Tuple[torch.LongTensor, 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['feat'] - 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['feat'] - 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['feat'] - for i in range(len(self.__sequential_encoding_layers) - 2): - x = self.__sequential_encoding_layers[i]( + for i in range(len(self.convs) - 2): + x = self.convs[i]( autogl.data.Data(x, data.edges()) ) x = self.__sequential_encoding_layers[-2]( diff --git a/autogl/module/model/dgl/graphsage.py b/autogl/module/model/dgl/graphsage.py index 2718bf1..4fed9da 100644 --- a/autogl/module/model/dgl/graphsage.py +++ b/autogl/module/model/dgl/graphsage.py @@ -4,7 +4,7 @@ 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 @@ -12,183 +12,67 @@ 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 +class GraphSAGE(torch.nn.Module): + + def __init__(self, args): + super(GraphSAGE).__init__() + self.args = args + self.num_layer = int(self.args["num_layers"]) + + missing_keys = list( + set( + [ + "features_num", + "num_class", + "num_layers", + "hidden", + "dropout", + "act", + "agg" + ] ) - 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['feat'] - 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], - ) - ] - ) + - 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") + + if self.args["agg"] not in ("add", "max", "mean"): + self.args["agg"] = "mean" + + self.convs = torch.nn.ModuleList() + self.convs.append( + SAGEConv( + self.args["features_num"], + self.args["hidden"][0], + aggregator_type=self.args["agg"] ) - 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(self.num_layer - 2): + self.convs.append( + SAGEConv( + self.args["hidden"][i] , + self.args["hidden"][i + 1], + aggregator_type=self.args["agg"] ) ) - 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: - return self(data) - - # 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['feat'] - for i in range(len(self.__sequential_encoding_layers)): - x = self.__sequential_encoding_layers[i]( - autogl.data.Data(x, data.edges()) + + self.convs.append( + SAGEConv( + self.args["hidden"][-1], + self.args["num_class"], + aggregator_type=self.args["agg"] ) - 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['feat'] - 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 - ) + for i in range(len(self.convs) - 2): + x = self.convs[i](data) + x = activate_func(x, self.args["act"]) + x = self.convs[-2](data) return x def lp_decode(self, z, pos_edge_index, neg_edge_index): @@ -201,10 +85,17 @@ class GraphSAGE(ClassificationSupportedSequentialModel): return (prob_adj > 0).nonzero(as_tuple=False).t() def forward(self, data): - # only for test - x = data.ndata['feat'] - for i in range(len(self.__sequential_encoding_layers)): - x = self.__sequential_encoding_layers[i](data,x) + try: + x = data.ndata['feat'] + except: + print("no x") + pass + + for i in range(self.num_layer): + x = self.convs[i](data, x) + if i != self.num_layer - 1: + x = activate_func(x, self.args["act"]) + x = F.dropout(x, p=self.args["dropout"], training=self.training) return F.log_softmax(x, dim=1) diff --git a/test/performance/node_classification/dgl/model.py b/test/performance/node_classification/dgl/model.py index f45266c..f4256d2 100644 --- a/test/performance/node_classification/dgl/model.py +++ b/test/performance/node_classification/dgl/model.py @@ -89,6 +89,7 @@ if __name__ == '__main__': "num_layers": 2, "hidden": [8], "heads": 8, + "feat_drop": 0.6, "dropout": 0.6, "act": "elu", }).model