pyg-team / pytorch_geometric

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

HeteroGraphs- The dictionary storing edge indexes and its types remains empty even after proper initialization #7696

Open SHamda opened 1 year ago

SHamda commented 1 year ago

šŸ› Describe the bug

Below, you'll find the code used for the creation of the heterodata obejct.

import pandas as pd
import networkx as nx
import torch
from torch_geometric.data import Data, HeteroData

# Build graph
G = nx.DiGraph()
G.add_nodes_from(df_tweets['id'], bipartite=0)
G.add_nodes_from(df_users['userId'], bipartite=1)

for index, row in df_tweets.iterrows():
    G.add_edge(row['userId'], row['id'])

# Create mapping from node ids to their indices
user_node_indices = {node_id: index for index, node_id in enumerate(df_users['userId'])}
tweet_node_indices = {node_id: index + len(df_users) for index, node_id in enumerate(df_tweets['id'])}

# Create node features
user_features = torch.tensor(df_users[['protected', 'followers_count', 'friends_count', 'listed_count', 'favourites_count', 'geo_enabled', 'verified', 'statuses_count']].values, dtype=torch.long)
tweet_features = torch.tensor(df_tweets[['truncated', 'is_quote_status', 'retweet_count', 'favorite_count', 'userId']].values, dtype=torch.long)

# Create edge indices
edge_index = []
for index, row in df_tweets.iterrows():
    edge_index.append((user_node_indices[row['userId']], tweet_node_indices[row['id']]))
edge_index = torch.tensor(edge_index, dtype=torch.long).t().contiguous()

# Create node type mapping
user_node_types = torch.tensor([0] * len(df_users), dtype=torch.long)
tweet_node_types = torch.tensor([1] * len(df_tweets), dtype=torch.long)
node_type_mapping = {'user': user_node_types, 'tweet': tweet_node_types}
data = HeteroData()
data['user'].x = user_features.float()
data['tweet'].x = tweet_features.float()
data['user', 'writes', 'tweet'].edgeindex = edge_index
data['user'].y = None
data['tweet'].y = torch.tensor(df_tweets['label'], dtype=torch.float32)
print(data.metadata())

When I check the content through the line data.metadata() I get the following output (['user', 'tweet'], [('user', 'writes', 'tweet')]) . So, it's safe to assume that my graph object was created properly. Nevertheless, when I try to train the graph on a binary classification task through this NN architecture.

import torch_geometric.transforms as T
from torch_geometric.datasets import OGB_MAG
from torch_geometric.nn import HGTConv, Linear

class HGT(torch.nn.Module):
    def __init__(self, hidden_channels, out_channels, num_heads, num_layers):
        super().__init__()

        self.lin_dict = torch.nn.ModuleDict()
        for node_type in data.node_types:
            self.lin_dict[node_type] = Linear(-1, hidden_channels)

        self.convs = torch.nn.ModuleList()
        for _ in range(num_layers):
            conv = HGTConv(hidden_channels, hidden_channels, data.metadata(),
                           num_heads, group='sum')
            self.convs.append(conv)

        self.lin = Linear(hidden_channels, out_channels)

    def forward(self, x_dict, edge_index_dict):
        for node_type, x in x_dict.items():
            x_dict[node_type] = self.lin_dict[node_type](x).relu_()

        for conv in self.convs:
            x_dict = conv(x_dict, edge_index_dict)

        return self.lin(x_dict['tweet'])

model = HGT(hidden_channels=64, out_channels=2,
            num_heads=2, num_layers=1)
with torch.no_grad():  # Initialize lazy modules.
    out = model(data.x_dict, data.edge_index_dict)

optimizer = torch.optim.Adam(model.parameters(), lr=0.005, weight_decay=0.001)

def train():
    model.train()
    optimizer.zero_grad()
    out = model(data.x_dict, data.edge_index_dict)
    mask = data['tweet'].train_mask
    loss = F.cross_entropy(out[mask], data['tweet'].y[mask])
    loss.backward()
    optimizer.step()
    return float(loss)

I get the following error:

TypeError                                 Traceback (most recent call last)

[<ipython-input-35-78ae7de7c176>](https://localhost:8080/#) in <cell line: 1>()
      1 with torch.no_grad():  # Initialize lazy modules.
----> 2     out = model(data.x_dict, data.edge_index_dict)
      3 
      4 optimizer = torch.optim.Adam(model.parameters(), lr=0.005, weight_decay=0.001)
      5 

3 frames

[/usr/local/lib/python3.10/dist-packages/torch/nn/modules/module.py](https://localhost:8080/#) in _call_impl(self, *args, **kwargs)
   1499                 or _global_backward_pre_hooks or _global_backward_hooks
   1500                 or _global_forward_hooks or _global_forward_pre_hooks):
-> 1501             return forward_call(*args, **kwargs)
   1502         # Do not call functions when jit is used
   1503         full_backward_hooks, non_full_backward_hooks = [], []

[<ipython-input-24-5599072ddba1>](https://localhost:8080/#) in forward(self, x_dict, edge_index_dict)
     26             x_dict = conv(x_dict, edge_index_dict)
     27 
---> 28         return self.lin(x_dict['tweet'])
     29 
     30 model = HGT(hidden_channels=64, out_channels=2,

[/usr/local/lib/python3.10/dist-packages/torch/nn/modules/module.py](https://localhost:8080/#) in _call_impl(self, *args, **kwargs)
   1499                 or _global_backward_pre_hooks or _global_backward_hooks
   1500                 or _global_forward_hooks or _global_forward_pre_hooks):
-> 1501             return forward_call(*args, **kwargs)
   1502         # Do not call functions when jit is used
   1503         full_backward_hooks, non_full_backward_hooks = [], []

[/usr/local/lib/python3.10/dist-packages/torch_geometric/nn/dense/linear.py](https://localhost:8080/#) in forward(self, x)
    130             x (torch.Tensor): The input features.
    131         """
--> 132         return F.linear(x, self.weight, self.bias)
    133 
    134     @torch.no_grad()

TypeError: linear(): argument 'input' (position 1) must be Tensor, not NoneType

When I check the content of edge_index_dict I find it empty why is that, and in no other code example was I able to find a case where edge_index_dict is initialized explicitly, so I assume it's infered from edge_index. Any help would be much appreciated thanks.

Environment

SimonPop commented 1 year ago

Hi!

I have not run the code myself, but according to your last comment about edge_index_dict being empty, I can spot one odd thing in your code:

At some point you write:

data['user', 'writes', 'tweet'].edgeindex = edge_index

I think it should be data['user', 'writes', 'tweet'].edge_index instead.

Hope that helped :)

rusty1s commented 1 year ago

I added https://github.com/pyg-team/pytorch_geometric/pull/7714 to prevent this mistake in future releases :)