Pytorch geometric (temporal) with lightning

geoHeil: <p>Hi, I am pretty new to deep learning let alone geometric deep learning. Nonetheless, I would prefer to start with some best practices from the beginning - such as using lightning with PyTorch. However, I have some trouble converting the temporal graph-specific structure of the training loop to lightning. So far, it is really unclear for me how to manually iterate the snapshots.</p>
<aside class="onebox githubissue">
  <header class="source">
      <a href="https://github.com/benedekrozemberczki/pytorch_geometric_temporal/issues/62" target="_blank" rel="noopener nofollow ugc">github.com/benedekrozemberczki/pytorch_geometric_temporal</a>
  </header>
  <article class="onebox-body">
    <div class="github-row">
  <div class="github-icon-container" title="Issue">
	  <svg width="60" height="60" class="github-icon" viewbox="0 0 14 16" aria-hidden="true"><path d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg>
  </div>

  <div class="github-info-container">
    <h4>
      <a href="https://github.com/benedekrozemberczki/pytorch_geometric_temporal/issues/62" target="_blank" rel="noopener nofollow ugc">[question] link prediction - how to formulate the dataloader in a non-regression way</a>
    </h4>

    <div class="github-info">
      <div class="date">
        opened <span class="discourse-local-date" data-format="ll" data-date="2021-05-15" data-time="07:34:41" data-timezone="UTC">07:34AM - 15 May 21 UTC</span>
      </div>


      <div class="user">
        <a href="https://github.com/geoHeil" target="_blank" rel="noopener nofollow ugc">
          <img alt="geoHeil" src="https://avatars.githubusercontent.com/u/1694964?v=4" class="onebox-avatar-inline" width="20" height="20">
          geoHeil
        </a>
      </div>
    </div>

    <div class="labels">
    </div>
  </div>
</div>

  <div class="github-row">
    <p class="github-body-container">How can I perform spatio-temporal link prediction on an unweighted graph with fe<span class="show-more-container"><a href="" rel="noopener" class="show-more">…</a></span><span class="excerpt hidden">atures on the edges?

My graph is a `DynamicGraphTemporalSignal`, but I do not want to regress on some signal, rather perform link prediction as also outlined in https://pytorch-geometric-temporal.readthedocs.io/en/latest/_modules/torch_geometric_temporal/nn/recurrent/gc_lstm.html?highlight=link

But from looking at the example code of: https://github.com/benedekrozemberczki/pytorch_geometric_temporal/blob/4fb610326811906eb4f95152a80f5df3f62fb459/test/recurrent_test.py#L305 it is still unclear to me how to create a suitable data loader.

So far I have looked into all the available ones from https://github.com/benedekrozemberczki/pytorch_geometric_temporal/tree/master/torch_geometric_temporal/dataset and think that the  Twitter tennis data loader https://github.com/benedekrozemberczki/pytorch_geometric_temporal/blob/master/torch_geometric_temporal/dataset/twitter_tennis.py is the most suitable one.

However, even this one seems to be stuck in formulating a regression problem.

&gt; NOTICE: even though the edge has features such as location, time, and hobby when making a prediction I am happy when only predicting a new suitable link from a topological perspective even if the features such as time, location, or hobby would not be required to match the ground truth data when evaluating a match on the test dataset.

Please find the code to produce the dummy data below.
It also contains the transformations I have come up with so far from looking at existing data loaders.

But for me, it remains unclear how to structure the data loader for a link prediction problem and not for a regression.

&lt;img width="618" alt="JupyterLab" src="https://user-images.githubusercontent.com/1694964/118352217-7c30e880-b560-11eb-8a91-e8d1879b7a23.png"&gt;

```
  import pandas as pd
  import numpy as np
  
  n_people = 20
  links = 80
  seed = 47
  time_steps = 10
  hobby_categories = 3
  
  np.random.seed(47)
  
  df_dummy = pd.DataFrame({'person_1':np.random.randint(0, n_people-1, links), 
                           'person_2':np.random.randint(0, n_people-1, links), 
                           'time':np.random.randint(0, time_steps-1, links),
                           'lat': np.random.uniform(10,19, links), 
                           'lng':np.random.uniform(45,49, links), 
                           'hobby':np.random.randint(0, hobby_categories-1, links)})
  display(df_dummy.head())
  
  df_dummy['edge_index'] = df_dummy[['person_1', 'person_2']].values.tolist()
  df_dummy['edge_features'] = df_dummy[['lat', 'lng', 'hobby']].values.tolist()
  df_dummy = df_dummy[['time', 'edge_index', 'edge_features']]
  display(df_dummy.head())
  
  df_dummy = df_dummy.groupby(['time']).agg(lambda x: list(x))
  display(df_dummy.head())
```

## edit

I guess somehow the target must be the lagged edge-index of a future time index? Perhaps even of any future time index? depending on the forecast horizon.

Perhaps something along these lines would work? Here in this example a prediction horizon with an unlimited boundary is assumend.

```
current_timestep = 3
# get the n+1
future_edges_target = pd.DataFrame(df_dummy.loc[current_timestep +1:].edge_index).explode('edge_index').edge_index#.unique()

# as the graph is undirected, sorting the edges is fine.
future_edges_target = future_edges_target.apply(sorted).transform(tuple).unique()
future_edges_target

array([(8, 8), (1, 7), (7, 11), (2, 16), (5, 18), (11, 18), (6, 11),
       (2, 5), (14, 16), (6, 6), (16, 18), (9, 17), (1, 2), (0, 6),
       (0, 5), (0, 15), (9, 18), (9, 12), (1, 17), (2, 14), (8, 13),
       (1, 18), (1, 8), (14, 17), (5, 6), (3, 6), (11, 14), (10, 17),
       (4, 14), (7, 12), (0, 18), (13, 15), (9, 15)], dtype=object)
```</span></p>
  </div>

  </article>
  <div class="onebox-metadata">
    
    
  </div>
  <div style="clear: both"></div>
</aside>

<p>Furthermore, PyTorch geometric temporal seems to utilize a concept of temporal snapshots (!= batch size) where they assume every snapshot fully fits into memory.</p>
<pre><code class="lang-auto">from tqdm import tqdm
model = RecurrentGCN(node_features = 4) # chickenpox
model
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
model.train()
for epoch in tqdm(range(200)):
    cost = 0
    for time, snapshot in enumerate(train_dataset):
        y_hat = model(snapshot.x, snapshot.edge_index, snapshot.edge_attr)
        cost = cost + torch.mean((y_hat-snapshot.y)**2)
    cost = cost / (time+1)
    cost.backward()
    optimizer.step()
    optimizer.zero_grad() 
</code></pre>

geoHeil: <p><a href="https://github.com/benedekrozemberczki/pytorch_geometric_temporal/blob/a13ea7876525ed9ba7c48ec69408024346eaded3/torch_geometric_temporal/nn/recurrent/dcrnn.py#L92" class="inline-onebox" rel="noopener nofollow ugc">pytorch_geometric_temporal/dcrnn.py at a13ea7876525ed9ba7c48ec69408024346eaded3 · benedekrozemberczki/pytorch_geometric_temporal · GitHub</a> seems to already hold the answer</p>