pyg-team / pytorch_geometric

Graph Neural Network Library for PyTorch
https://pyg.org
MIT License
21.09k stars 3.63k forks source link

how to build graph model base edge_attr #3416

Open scalaboy opened 2 years ago

scalaboy commented 2 years ago

hi,all. I have a graph dataset which edge_attr is importance. where I can't get a example about how to train a graph base edge_attr .like mnist_nn_conv.py is not solver my problem

rusty1s commented 2 years ago

In general, you can utilize any GNN layer that can handle edge_attr as input, see here, e.g., the GINEConv. You can simply call your layer via

x = conv(x, edge_index, edge_attr)

Does that resolve your concerns?

for-just-we commented 2 years ago

In general, you can utilize any GNN layer that can handle edge_attr as input, see here, e.g., the GINEConv. You can simply call your layer via

x = conv(x, edge_index, edge_attr)

Does that resolve your concerns?

hello, would other conv layers API like GatedGraphConv, GCNConv support this edge_attr parameter? It seems that in DGL library, GatedGraphConv support edge_attr.

rusty1s commented 2 years ago

I just looked into the DGL version of GatedGraphConv and it does not look like they support edge features either.

Nonetheless, I'm more than happy to let more GNN layers support edge features. How would you like to add edge feature support to GatedGraphConv?

for-just-we commented 2 years ago

I just looked into the DGL version of GatedGraphConv and it does not look like they support edge features either.

Nonetheless, I'm more than happy to let more GNN layers support edge features. How would you like to add edge feature support to GatedGraphConv?

I learn this from devign model, the code is for source code vulnerability detection task by graph network, which is also what I'm doing.

In the vulnerability detection task. There are different types of edge in a graph. For example, control flow edge, data dependence edge. i.e, I hope to use edge_attr to denote which type of edge it is. Maybe edge_attr is a simple one-hot vector. If there are 4 types of edge. CFG edge, CPG edge, DDG edge, AST edge. [1, 0, 0, 0] represents the edge is CFG edge and [0, 1, 0, 0] represents CPG edge and so on.

In the code, the author define a GatedGraphConv by self.ggnn = GatedGraphConv(in_feats=input_dim, out_feats=output_dim, n_steps=num_steps, n_etypes=max_edge_types) and use like outputs = self.ggnn(graph, features, edge_types). There is an additional parameter edge_types passed into ggnn layer.

I haven't used DGL yet. For now, I would like to support different types of edge by edge_attr because I haven't seen another way.

rusty1s commented 2 years ago

I see. Thanks for clarifying. You can either integrate it that by simply concatenating the edge features to x_j, e.g.:

def message(self, x_j, edge_attr):
    return self.lin(torch.cat([x_j, edge_attr], dim=-1))

or by using edge types (similar to how DGL is doing it), e.g., via our HeteroLinear layer:

self.hetero_lin = HeteroLinear(in_channels, out_channels, num_edge_types)

def message(self, x_j, edge_type):
    return self.hetero_lin(x_j, edge_type)
for-just-we commented 2 years ago

I see. Thanks for clarifying. You can either integrate it that by simply concatenating the edge features to x_j, e.g.:

def message(self, x_j, edge_attr):
    return self.lin(torch.cat([x_j, edge_attr], dim=-1))

or by using edge types (similar to how DGL is doing it), e.g., via our HeteroLinear layer:

self.hetero_lin = HeteroLinear(in_channels, out_channels, num_edge_types)

def message(self, x_j, edge_type):
    return self.hetero_lin(x_j, edge_type)

So, I should change the source code of GatedGraphConv in torch_geometric.nn.conv?

For now, the message method of GatedGraphConv is

def message(self, x_j: Tensor, edge_weight: OptTensor):
      return x_j if edge_weight is None else edge_weight.view(-1, 1) * x_j

And after changing, the source code of message method of GatedGraphConv is

def message(self, x_j: Tensor, edge_weight: OptTensor, edge_type):
      return self.hetero_lin(x_j, edge_type) if edge_weight is None else edge_weight.view(-1, 1) * self.hetero_lin(x_j, edge_type)

Am I right?

rusty1s commented 2 years ago

Yes, exactly :)

for-just-we commented 2 years ago

Yes, exactly :)

OK, thank you very much

scalaboy commented 2 years ago

I need an example,contains dataset,run fit code,train and test etc. not a api.....

rusty1s commented 2 years ago

Can you clarify? What about this one?

suice07 commented 1 year ago

I see. Thanks for clarifying. You can either integrate it that by simply concatenating the edge features to x_j, e.g.:

def message(self, x_j, edge_attr):
    return self.lin(torch.cat([x_j, edge_attr], dim=-1))

or by using edge types (similar to how DGL is doing it), e.g., via our HeteroLinear layer:

self.hetero_lin = HeteroLinear(in_channels, out_channels, num_edge_types)

def message(self, x_j, edge_type):
    return self.hetero_lin(x_j, edge_type)

Hello dear @rusty1s , I 'd like to implement my algorithm also by concate the aggregated node feature with the edge feature. Like you mentioned before, torch.cat([x_j, edge_attr] can be used, but how should I store the edge_attr? and I am still confusing that x_j should be node feature of node j right?

According to the doc Furthermore, tensors passed to [propagate()](https://pytorch-geometric.readthedocs.io/en/latest/modules/nn.html#torch_geometric.nn.conv.MessagePassing.propagate) can be mapped to the respective nodes and by appending _i or _j to the variable name, .e.g. x_i and x_j.

if I want to pass the message torch.cat[x_j,edge_attr],it should be return torch.cat([x_j, edge_attr_i], dim=-1)? But when i print the edge_attr_i,it turns to be the whole edge_attr set,without index

rusty1s commented 1 year ago

It should be return torch.cat([x_j, edge_attr], dim=-1). Since edge_attr is already an edge-level tensor, there is no need to appeng _i to it. Now both x_j and edge_attr will have shape [num_edges, *].

suice07 commented 1 year ago

Hello dear @rusty1s :

torch.cat([x_j, edge_attr], dim=-1)

I have the x in shape [25,15],25 is the node number, edge_attr in shape[56,6],56 is the number of edges, and using out = self.propagate(edge_index, x=node, norm=norm, edge_attr = edge_attr) to pass the message, in the message function, the shape of x_j is [81,15],but edge_attr is still[56,6],seems that it passed the whole edge_attr to message function instead of those of a specific edge. did I miss some steps?

rusty1s commented 1 year ago

It looks like your GNN layer adds self-loops to the graph, that's why you have x_j.size(0) = 25 + 56 = 81. Can you remove this logic?

suice07 commented 1 year ago

It looks like your GNN layer adds self-loops to the graph, that's why you have x_j.size(0) = 25 + 56 = 81. Can you remove this logic? Thanks dear @rusty1s :

I think that's the point, so x_j is the aggregated message from node j, then concatenate it with the edge_attr through torch.cat([x_j, edge_attr], dim=-1) Thanks so much! I think it is solved,One more question,why is the shape of x_j always be 56? I build a parallel network which consist of two exact the same architecture.one passes node feature as x,edge_attr=edge_feature, the other passes edge feature as x, and the edge_att is node feature, but when I print the shape of x_j, both of them are in shape[56,x], in node network it is [56,15],in edge network it is [56,6]. I am a little bit confused.

rusty1s commented 1 year ago

It is only 56 in case you have 56 edges. The first dimension can be seen as the batch dimension here, as we share model weights across all edges.

suice07 commented 1 year ago

It is only 56 in case you have 56 edges. The first dimension can be seen as the batch dimension here, as we share model weights across all edges. Thanks for reply, dear @rusty1s , In my case I have a parallel network,the first one takes node feature as x,which propagate like this:self.propagate(edge_index, x=node_feature, edge_attr = edge_feature),noted as node network,the other one switches node feature and edge feature,in my case takes nodes as edges,edges as nodes,which propagates like self.propagate(node_index, x=edge_feature, edge_attr = node_feature),noted as Edge-network,node feature has a dimension [25,m],edge feature has a dimension of [28,n],but when I print the x_j in the message function, in both network, the dimension of x_j is [56,t], it is originally 25 nodes and 28 edges, I think I my a mistake in build adjacency matrix for Edge-network, I build the adjacency matrix by setting the corresponding position to 1 when two edges connected to the same node. and use coo_matrix to convert it to the adjacency matrix which Pyg takes, but the resulting matrix is in shape[2,78], which is not [2,2N_nodes],can't be used to concatenate the edge feature. maybe it is not a good idea to switch it...

rusty1s commented 1 year ago

For an edge network, you can take a look at transforms.LineGraph and compare it to your implementation. It will treat edges as nodes and connects two nodes whenever there exists a node that connects the two edges in the original network. Let me know!

suice07 commented 1 year ago

For an edge network, you can take a look at transforms.LineGraph and compare it to your implementation. It will treat edges as nodes and connects two nodes whenever there exists a node that connects the two edges in the original network. Let me know!

the original data is like Data(edge_index=[2, 56], edge_attr=[56, 6], node_feature=[25, 27], smiles='N[C@@H](Cc1c[nH]c2ccccc12)C(=O)Nc1ccc2ccccc2c1') I called LineGraph data_edge = transform(data),then the new data is turned to: Data(edge_index=[2, 134], node_feature=[25, 27], smiles='N[C@@H](Cc1c[nH]c2ccccc12)C(=O)Nc1ccc2ccccc2c1', x=[56, 6], num_nodes=56) According to the adjacency matrix I caculated before ,I think the new edge_index is right. but I am confused to perform my algorithm throught this.

what I want to do is for a specific edge from node a to node b, the edge ab takes all aggregated edge features from the edges which connected to start node a, attached by its start node feature;

but for the transformed data,it takes edge as node,but according to the edge_index of it, the transformed 'edge' seems to have nothing to do with the real node

rusty1s commented 1 year ago

Isn't that what is already happening? If you also want to integrate node features into propagation, you can convert them to edge features first:

row, col = data.edge_index
data.edge_attr = torch.cat([data.x[row], data.x[col], data.edge_attr], dim=-1)
data = transform(data)
suice07 commented 1 year ago

Isn't that what is already happening? If you also want to integrate node features into propagation, you can convert them to edge features first:

row, col = data.edge_index
data.edge_attr = torch.cat([data.x[row], data.x[col], data.edge_attr], dim=-1)
data = transform(data)

I think I got the point, thanks for all the help!