Browse Source

auto

tags/v0.3.1
Beini Frozenmad 4 years ago
parent
commit
b0c7c5c49f
10 changed files with 96 additions and 1515 deletions
  1. +7
    -5
      autogl/module/model/dgl/__init__.py
  2. +12
    -23
      autogl/module/model/dgl/gat.py
  3. +0
    -212
      autogl/module/model/dgl/gat_dgl.py
  4. +34
    -33
      autogl/module/model/dgl/gcn.py
  5. +0
    -409
      autogl/module/model/dgl/gcn_dgl.py
  6. +0
    -232
      autogl/module/model/dgl/gin.py
  7. +0
    -171
      autogl/module/model/dgl/gin_dgl.py
  8. +0
    -81
      autogl/module/model/dgl/ginparser.py
  9. +43
    -37
      autogl/module/model/dgl/graphsage.py
  10. +0
    -312
      autogl/module/model/dgl/graphsage_dgl.py

+ 7
- 5
autogl/module/model/dgl/__init__.py View File

@@ -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"
]

+ 12
- 23
autogl/module/model/dgl/gat.py View File

@@ -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)


+ 0
- 212
autogl/module/model/dgl/gat_dgl.py View File

@@ -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)

+ 34
- 33
autogl/module/model/dgl/gcn.py View File

@@ -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



+ 0
- 409
autogl/module/model/dgl/gcn_dgl.py View File

@@ -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)

+ 0
- 232
autogl/module/model/dgl/gin.py View File

@@ -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)

+ 0
- 171
autogl/module/model/dgl/gin_dgl.py View File

@@ -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

+ 0
- 81
autogl/module/model/dgl/ginparser.py View File

@@ -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()

+ 43
- 37
autogl/module/model/dgl/graphsage.py View File

@@ -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")


+ 0
- 312
autogl/module/model/dgl/graphsage_dgl.py View File

@@ -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)

Loading…
Cancel
Save