Browse Source

node model

tags/v0.3.1
Beini 4 years ago
parent
commit
bc1e016df1
4 changed files with 127 additions and 399 deletions
  1. +8
    -3
      autogl/module/model/dgl/gat.py
  2. +53
    -222
      autogl/module/model/dgl/gcn.py
  3. +65
    -174
      autogl/module/model/dgl/graphsage.py
  4. +1
    -0
      test/performance/node_classification/dgl/model.py

+ 8
- 3
autogl/module/model/dgl/gat.py View File

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


+ 53
- 222
autogl/module/model/dgl/gcn.py View File

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


+ 65
- 174
autogl/module/model/dgl/graphsage.py View File

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



+ 1
- 0
test/performance/node_classification/dgl/model.py View File

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


Loading…
Cancel
Save