| @@ -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): | |||
| @@ -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]( | |||
| @@ -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) | |||
| @@ -89,6 +89,7 @@ if __name__ == '__main__': | |||
| "num_layers": 2, | |||
| "hidden": [8], | |||
| "heads": 8, | |||
| "feat_drop": 0.6, | |||
| "dropout": 0.6, | |||
| "act": "elu", | |||
| }).model | |||