We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Classification metrics can't handle a mix of binary and continuous targets
### 🐛 Describe the bug

I was trying to run the [pytorch_geometric](https://github.com/pyg-team/pytorch_geometric)/[examples](https://github.com/pyg-team/pytorch_geometric/tree/master/examples)/[hetero](https://github.com/pyg-team/pytorch_geometric/tree/master/examples/hetero)/bipartite_sage_unsup.py . I received the following error in the training loop. The error happened after the test() function was called

## Error
```
ValueError                                Traceback (most recent call last)
[<ipython-input-7-449d394a8262>](https://localhost:8080/#) in <module>
     54 for epoch in range(1, 21):
     55     loss = train()
---> 56     val_acc, val_prec, val_rec, val_f1 = test(val_loader)
     57     test_acc, test_prec, test_rec, test_f1 = test(test_loader)
     58 

3 frames
[/usr/local/lib/python3.8/dist-packages/sklearn/metrics/_classification.py](https://localhost:8080/#) in _check_targets(y_true, y_pred)
     91 
     92     if len(y_type) > 1:
---> 93         raise ValueError(
     94             "Classification metrics can't handle a mix of {0} and {1} targets".format(
     95                 type_true, type_pred

ValueError: Classification metrics can't handle a mix of binary and continuous targets
```
## Code modifications
```
@torch.no_grad()
def test(loader):
    model.eval()

    preds, targets = [], []
    for batch in tqdm.tqdm(loader):
        batch = batch.to(device)

        pred = model(
            batch.x_dict,
            batch.edge_index_dict,
            batch['user', 'item'].edge_label_index,
        ).sigmoid().view(-1).cpu()
        target = batch['user', 'item'].edge_label.long().cpu()

        preds.append(pred)
        targets.append(target) #I assumed it was a typo in the original example

    pred = torch.cat(preds, dim=0).numpy()
    target = torch.cat(targets, dim=0).numpy() #I assumed it was a typo in the original example

    acc = accuracy_score(target, pred)
    prec = precision_score(target, pred)
    rec = recall_score(target, pred)
    f1 = f1_score(target, pred)

    return acc, prec, rec, f1
```

### Environment

* PyG version: 
* PyTorch version: 1.13.0
* OS: Linux(Colab)
* Python version: 3.8.10
* CUDA/cuDNN version: 11.6
* How you installed PyTorch and PyG (`conda`, `pip`, source): pip
* Any other relevant information (*e.g.*, version of `torch-scatter`): 


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `examples/hetero/bipartite_sage_unsup.py`
Content:
```
1 # An implementation of unsupervised bipartite GraphSAGE using the Alibaba
2 # Taobao dataset.
3 import os.path as osp
4 
5 import torch
6 import torch.nn.functional as F
7 import tqdm
8 from sklearn.metrics import (
9     accuracy_score,
10     f1_score,
11     precision_score,
12     recall_score,
13 )
14 from torch.nn import Embedding, Linear
15 
16 import torch_geometric.transforms as T
17 from torch_geometric.datasets import Taobao
18 from torch_geometric.loader import LinkNeighborLoader
19 from torch_geometric.nn import SAGEConv
20 from torch_geometric.utils.convert import to_scipy_sparse_matrix
21 
22 device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
23 path = osp.join(osp.dirname(osp.realpath(__file__)), '../../data/Taobao')
24 
25 dataset = Taobao(path)
26 data = dataset[0]
27 
28 data['user'].x = torch.arange(0, data['user'].num_nodes)
29 data['item'].x = torch.arange(0, data['item'].num_nodes)
30 
31 # Only consider user<>item relationships for simplicity:
32 del data['category']
33 del data['item', 'category']
34 del data['user', 'item'].time
35 del data['user', 'item'].behavior
36 
37 # Add a reverse ('item', 'rev_to', 'user') relation for message passing:
38 data = T.ToUndirected()(data)
39 
40 # Perform a link-level split into training, validation, and test edges:
41 print('Computing data splits...')
42 train_data, val_data, test_data = T.RandomLinkSplit(
43     num_val=0.1,
44     num_test=0.1,
45     neg_sampling_ratio=1.0,
46     add_negative_train_samples=False,
47     edge_types=[('user', 'to', 'item')],
48     rev_edge_types=[('item', 'rev_to', 'user')],
49 )(data)
50 print('Done!')
51 
52 # Compute sparsified item<>item relationships through users:
53 print('Computing item<>item relationships...')
54 mat = to_scipy_sparse_matrix(data['user', 'item'].edge_index).tocsr()
55 mat = mat[:data['user'].num_nodes, :data['item'].num_nodes]
56 comat = mat.T @ mat
57 comat.setdiag(0)
58 comat = comat >= 3.
59 comat = comat.tocoo()
60 row = torch.from_numpy(comat.row).to(torch.long)
61 col = torch.from_numpy(comat.col).to(torch.long)
62 item_to_item_edge_index = torch.stack([row, col], dim=0)
63 
64 # Add the generated item<>item relationships for high-order information:
65 train_data['item', 'item'].edge_index = item_to_item_edge_index
66 val_data['item', 'item'].edge_index = item_to_item_edge_index
67 test_data['item', 'item'].edge_index = item_to_item_edge_index
68 print('Done!')
69 
70 train_loader = LinkNeighborLoader(
71     data=train_data,
72     num_neighbors=[8, 4],
73     edge_label_index=('user', 'to', 'item'),
74     neg_sampling='binary',
75     batch_size=2048,
76     shuffle=True,
77     num_workers=16,
78     drop_last=True,
79 )
80 
81 val_loader = LinkNeighborLoader(
82     data=val_data,
83     num_neighbors=[8, 4],
84     edge_label_index=(
85         ('user', 'to', 'item'),
86         val_data[('user', 'to', 'item')].edge_label_index,
87     ),
88     edge_label=val_data[('user', 'to', 'item')].edge_label,
89     batch_size=2048,
90     shuffle=False,
91     num_workers=16,
92 )
93 
94 test_loader = LinkNeighborLoader(
95     data=test_data,
96     num_neighbors=[8, 4],
97     edge_label_index=(
98         ('user', 'to', 'item'),
99         test_data[('user', 'to', 'item')].edge_label_index,
100     ),
101     edge_label=test_data[('user', 'to', 'item')].edge_label,
102     batch_size=2048,
103     shuffle=False,
104     num_workers=16,
105 )
106 
107 
108 class ItemGNNEncoder(torch.nn.Module):
109     def __init__(self, hidden_channels, out_channels):
110         super().__init__()
111         self.conv1 = SAGEConv(-1, hidden_channels)
112         self.conv2 = SAGEConv(hidden_channels, hidden_channels)
113         self.lin = Linear(hidden_channels, out_channels)
114 
115     def forward(self, x, edge_index):
116         x = self.conv1(x, edge_index).relu()
117         x = self.conv2(x, edge_index).relu()
118         return self.lin(x)
119 
120 
121 class UserGNNEncoder(torch.nn.Module):
122     def __init__(self, hidden_channels, out_channels):
123         super().__init__()
124         self.conv1 = SAGEConv((-1, -1), hidden_channels)
125         self.conv2 = SAGEConv((-1, -1), hidden_channels)
126         self.conv3 = SAGEConv((-1, -1), hidden_channels)
127         self.lin = Linear(hidden_channels, out_channels)
128 
129     def forward(self, x_dict, edge_index_dict):
130         item_x = self.conv1(
131             x_dict['item'],
132             edge_index_dict[('item', 'to', 'item')],
133         ).relu()
134 
135         user_x = self.conv2(
136             (x_dict['item'], x_dict['user']),
137             edge_index_dict[('item', 'rev_to', 'user')],
138         ).relu()
139 
140         user_x = self.conv3(
141             (item_x, user_x),
142             edge_index_dict[('item', 'to', 'user')],
143         ).relu()
144 
145         return self.lin(user_x)
146 
147 
148 class EdgeDecoder(torch.nn.Module):
149     def __init__(self, hidden_channels):
150         super().__init__()
151         self.lin1 = Linear(2 * hidden_channels, hidden_channels)
152         self.lin2 = Linear(hidden_channels, 1)
153 
154     def forward(self, z_src, z_dst, edge_label_index):
155         row, col = edge_label_index
156         z = torch.cat([z_src[row], z_dst[col]], dim=-1)
157 
158         z = self.lin1(z).relu()
159         z = self.lin2(z)
160         return z.view(-1)
161 
162 
163 class Model(torch.nn.Module):
164     def __init__(self, num_users, num_items, hidden_channels, out_channels):
165         super().__init__()
166         self.user_emb = Embedding(num_users, hidden_channels, device=device)
167         self.item_emb = Embedding(num_items, hidden_channels, device=device)
168         self.item_encoder = ItemGNNEncoder(hidden_channels, out_channels)
169         self.user_encoder = UserGNNEncoder(hidden_channels, out_channels)
170         self.decoder = EdgeDecoder(out_channels)
171 
172     def forward(self, x_dict, edge_index_dict, edge_label_index):
173         z_dict = {}
174         x_dict['user'] = self.user_emb(x_dict['user'])
175         x_dict['item'] = self.item_emb(x_dict['item'])
176         z_dict['item'] = self.item_encoder(
177             x_dict['item'],
178             edge_index_dict[('item', 'to', 'item')],
179         )
180         z_dict['user'] = self.user_encoder(x_dict, edge_index_dict)
181 
182         return self.decoder(z_dict['user'], z_dict['item'], edge_label_index)
183 
184 
185 model = Model(
186     num_users=data['user'].num_nodes,
187     num_items=data['item'].num_nodes,
188     hidden_channels=64,
189     out_channels=64,
190 ).to(device)
191 optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
192 
193 
194 def train():
195     model.train()
196 
197     total_loss = total_examples = 0
198     for batch in tqdm.tqdm(train_loader):
199         batch = batch.to(device)
200         optimizer.zero_grad()
201 
202         pred = model(
203             batch.x_dict,
204             batch.edge_index_dict,
205             batch['user', 'item'].edge_label_index,
206         )
207         loss = F.binary_cross_entropy_with_logits(
208             pred, batch['user', 'item'].edge_label)
209 
210         loss.backward()
211         optimizer.step()
212         total_loss += float(loss)
213         total_examples += pred.numel()
214 
215     return total_loss / total_examples
216 
217 
218 @torch.no_grad()
219 def test(loader):
220     model.eval()
221 
222     preds, targets = [], []
223     for batch in tqdm.tqdm(loader):
224         batch = batch.to(device)
225 
226         pred = model(
227             batch.x_dict,
228             batch.edge_index_dict,
229             batch['user', 'item'].edge_label_index,
230         ).sigmoid().view(-1).cpu()
231         target = batch['user', 'item'].edge_label.long().cpu()
232 
233         preds.append(pred)
234         targets.append(pred)
235 
236     pred = torch.cat(preds, dim=0).numpy()
237     target = torch.cat(target, dim=0).numpy()
238 
239     acc = accuracy_score(target, pred)
240     prec = precision_score(target, pred)
241     rec = recall_score(target, pred)
242     f1 = f1_score(target, pred)
243 
244     return acc, prec, rec, f1
245 
246 
247 for epoch in range(1, 21):
248     loss = train()
249     val_acc, val_prec, val_rec, val_f1 = test(val_loader)
250     test_acc, test_prec, test_rec, test_f1 = test(test_loader)
251 
252     print(f'Epoch: {epoch:03d}, Loss: {loss:4f}')
253     print(f'Val Acc: {val_acc:.4f}, Val Precision {val_prec:.4f}, '
254           f'Val Recall {val_rec:.4f}, Val F1 {val_f1:.4f}')
255     print(f'Test Acc: {test_acc:.4f}, Test Precision {test_prec:.4f}, '
256           f'Test Recall {test_rec:.4f}, Test F1 {test_f1:.4f}')
257
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/examples/hetero/bipartite_sage_unsup.py b/examples/hetero/bipartite_sage_unsup.py
--- a/examples/hetero/bipartite_sage_unsup.py
+++ b/examples/hetero/bipartite_sage_unsup.py
@@ -139,7 +139,7 @@
 
         user_x = self.conv3(
             (item_x, user_x),
-            edge_index_dict[('item', 'to', 'user')],
+            edge_index_dict[('item', 'rev_to', 'user')],
         ).relu()
 
         return self.lin(user_x)
@@ -231,11 +231,12 @@
         target = batch['user', 'item'].edge_label.long().cpu()
 
         preds.append(pred)
-        targets.append(pred)
+        targets.append(target)
 
     pred = torch.cat(preds, dim=0).numpy()
-    target = torch.cat(target, dim=0).numpy()
+    target = torch.cat(targets, dim=0).numpy()
 
+    pred = pred > 0.5
     acc = accuracy_score(target, pred)
     prec = precision_score(target, pred)
     rec = recall_score(target, pred)

{"golden_diff": "diff --git a/examples/hetero/bipartite_sage_unsup.py b/examples/hetero/bipartite_sage_unsup.py\n--- a/examples/hetero/bipartite_sage_unsup.py\n+++ b/examples/hetero/bipartite_sage_unsup.py\n@@ -139,7 +139,7 @@\n \n         user_x = self.conv3(\n             (item_x, user_x),\n-            edge_index_dict[('item', 'to', 'user')],\n+            edge_index_dict[('item', 'rev_to', 'user')],\n         ).relu()\n \n         return self.lin(user_x)\n@@ -231,11 +231,12 @@\n         target = batch['user', 'item'].edge_label.long().cpu()\n \n         preds.append(pred)\n-        targets.append(pred)\n+        targets.append(target)\n \n     pred = torch.cat(preds, dim=0).numpy()\n-    target = torch.cat(target, dim=0).numpy()\n+    target = torch.cat(targets, dim=0).numpy()\n \n+    pred = pred > 0.5\n     acc = accuracy_score(target, pred)\n     prec = precision_score(target, pred)\n     rec = recall_score(target, pred)\n", "issue": "Classification metrics can't handle a mix of binary and continuous targets\n### \ud83d\udc1b Describe the bug\n\nI was trying to run the [pytorch_geometric](https://github.com/pyg-team/pytorch_geometric)/[examples](https://github.com/pyg-team/pytorch_geometric/tree/master/examples)/[hetero](https://github.com/pyg-team/pytorch_geometric/tree/master/examples/hetero)/bipartite_sage_unsup.py . I received the following error in the training loop. The error happened after the test() function was called\r\n\r\n## Error\r\n```\r\nValueError                                Traceback (most recent call last)\r\n[<ipython-input-7-449d394a8262>](https://localhost:8080/#) in <module>\r\n     54 for epoch in range(1, 21):\r\n     55     loss = train()\r\n---> 56     val_acc, val_prec, val_rec, val_f1 = test(val_loader)\r\n     57     test_acc, test_prec, test_rec, test_f1 = test(test_loader)\r\n     58 \r\n\r\n3 frames\r\n[/usr/local/lib/python3.8/dist-packages/sklearn/metrics/_classification.py](https://localhost:8080/#) in _check_targets(y_true, y_pred)\r\n     91 \r\n     92     if len(y_type) > 1:\r\n---> 93         raise ValueError(\r\n     94             \"Classification metrics can't handle a mix of {0} and {1} targets\".format(\r\n     95                 type_true, type_pred\r\n\r\nValueError: Classification metrics can't handle a mix of binary and continuous targets\r\n```\r\n## Code modifications\r\n```\r\n@torch.no_grad()\r\ndef test(loader):\r\n    model.eval()\r\n\r\n    preds, targets = [], []\r\n    for batch in tqdm.tqdm(loader):\r\n        batch = batch.to(device)\r\n\r\n        pred = model(\r\n            batch.x_dict,\r\n            batch.edge_index_dict,\r\n            batch['user', 'item'].edge_label_index,\r\n        ).sigmoid().view(-1).cpu()\r\n        target = batch['user', 'item'].edge_label.long().cpu()\r\n\r\n        preds.append(pred)\r\n        targets.append(target) #I assumed it was a typo in the original example\r\n\r\n    pred = torch.cat(preds, dim=0).numpy()\r\n    target = torch.cat(targets, dim=0).numpy() #I assumed it was a typo in the original example\r\n\r\n    acc = accuracy_score(target, pred)\r\n    prec = precision_score(target, pred)\r\n    rec = recall_score(target, pred)\r\n    f1 = f1_score(target, pred)\r\n\r\n    return acc, prec, rec, f1\r\n```\n\n### Environment\n\n* PyG version: \r\n* PyTorch version: 1.13.0\r\n* OS: Linux(Colab)\r\n* Python version: 3.8.10\r\n* CUDA/cuDNN version: 11.6\r\n* How you installed PyTorch and PyG (`conda`, `pip`, source): pip\r\n* Any other relevant information (*e.g.*, version of `torch-scatter`): \r\n\n", "before_files": [{"content": "# An implementation of unsupervised bipartite GraphSAGE using the Alibaba\n# Taobao dataset.\nimport os.path as osp\n\nimport torch\nimport torch.nn.functional as F\nimport tqdm\nfrom sklearn.metrics import (\n    accuracy_score,\n    f1_score,\n    precision_score,\n    recall_score,\n)\nfrom torch.nn import Embedding, Linear\n\nimport torch_geometric.transforms as T\nfrom torch_geometric.datasets import Taobao\nfrom torch_geometric.loader import LinkNeighborLoader\nfrom torch_geometric.nn import SAGEConv\nfrom torch_geometric.utils.convert import to_scipy_sparse_matrix\n\ndevice = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')\npath = osp.join(osp.dirname(osp.realpath(__file__)), '../../data/Taobao')\n\ndataset = Taobao(path)\ndata = dataset[0]\n\ndata['user'].x = torch.arange(0, data['user'].num_nodes)\ndata['item'].x = torch.arange(0, data['item'].num_nodes)\n\n# Only consider user<>item relationships for simplicity:\ndel data['category']\ndel data['item', 'category']\ndel data['user', 'item'].time\ndel data['user', 'item'].behavior\n\n# Add a reverse ('item', 'rev_to', 'user') relation for message passing:\ndata = T.ToUndirected()(data)\n\n# Perform a link-level split into training, validation, and test edges:\nprint('Computing data splits...')\ntrain_data, val_data, test_data = T.RandomLinkSplit(\n    num_val=0.1,\n    num_test=0.1,\n    neg_sampling_ratio=1.0,\n    add_negative_train_samples=False,\n    edge_types=[('user', 'to', 'item')],\n    rev_edge_types=[('item', 'rev_to', 'user')],\n)(data)\nprint('Done!')\n\n# Compute sparsified item<>item relationships through users:\nprint('Computing item<>item relationships...')\nmat = to_scipy_sparse_matrix(data['user', 'item'].edge_index).tocsr()\nmat = mat[:data['user'].num_nodes, :data['item'].num_nodes]\ncomat = mat.T @ mat\ncomat.setdiag(0)\ncomat = comat >= 3.\ncomat = comat.tocoo()\nrow = torch.from_numpy(comat.row).to(torch.long)\ncol = torch.from_numpy(comat.col).to(torch.long)\nitem_to_item_edge_index = torch.stack([row, col], dim=0)\n\n# Add the generated item<>item relationships for high-order information:\ntrain_data['item', 'item'].edge_index = item_to_item_edge_index\nval_data['item', 'item'].edge_index = item_to_item_edge_index\ntest_data['item', 'item'].edge_index = item_to_item_edge_index\nprint('Done!')\n\ntrain_loader = LinkNeighborLoader(\n    data=train_data,\n    num_neighbors=[8, 4],\n    edge_label_index=('user', 'to', 'item'),\n    neg_sampling='binary',\n    batch_size=2048,\n    shuffle=True,\n    num_workers=16,\n    drop_last=True,\n)\n\nval_loader = LinkNeighborLoader(\n    data=val_data,\n    num_neighbors=[8, 4],\n    edge_label_index=(\n        ('user', 'to', 'item'),\n        val_data[('user', 'to', 'item')].edge_label_index,\n    ),\n    edge_label=val_data[('user', 'to', 'item')].edge_label,\n    batch_size=2048,\n    shuffle=False,\n    num_workers=16,\n)\n\ntest_loader = LinkNeighborLoader(\n    data=test_data,\n    num_neighbors=[8, 4],\n    edge_label_index=(\n        ('user', 'to', 'item'),\n        test_data[('user', 'to', 'item')].edge_label_index,\n    ),\n    edge_label=test_data[('user', 'to', 'item')].edge_label,\n    batch_size=2048,\n    shuffle=False,\n    num_workers=16,\n)\n\n\nclass ItemGNNEncoder(torch.nn.Module):\n    def __init__(self, hidden_channels, out_channels):\n        super().__init__()\n        self.conv1 = SAGEConv(-1, hidden_channels)\n        self.conv2 = SAGEConv(hidden_channels, hidden_channels)\n        self.lin = Linear(hidden_channels, out_channels)\n\n    def forward(self, x, edge_index):\n        x = self.conv1(x, edge_index).relu()\n        x = self.conv2(x, edge_index).relu()\n        return self.lin(x)\n\n\nclass UserGNNEncoder(torch.nn.Module):\n    def __init__(self, hidden_channels, out_channels):\n        super().__init__()\n        self.conv1 = SAGEConv((-1, -1), hidden_channels)\n        self.conv2 = SAGEConv((-1, -1), hidden_channels)\n        self.conv3 = SAGEConv((-1, -1), hidden_channels)\n        self.lin = Linear(hidden_channels, out_channels)\n\n    def forward(self, x_dict, edge_index_dict):\n        item_x = self.conv1(\n            x_dict['item'],\n            edge_index_dict[('item', 'to', 'item')],\n        ).relu()\n\n        user_x = self.conv2(\n            (x_dict['item'], x_dict['user']),\n            edge_index_dict[('item', 'rev_to', 'user')],\n        ).relu()\n\n        user_x = self.conv3(\n            (item_x, user_x),\n            edge_index_dict[('item', 'to', 'user')],\n        ).relu()\n\n        return self.lin(user_x)\n\n\nclass EdgeDecoder(torch.nn.Module):\n    def __init__(self, hidden_channels):\n        super().__init__()\n        self.lin1 = Linear(2 * hidden_channels, hidden_channels)\n        self.lin2 = Linear(hidden_channels, 1)\n\n    def forward(self, z_src, z_dst, edge_label_index):\n        row, col = edge_label_index\n        z = torch.cat([z_src[row], z_dst[col]], dim=-1)\n\n        z = self.lin1(z).relu()\n        z = self.lin2(z)\n        return z.view(-1)\n\n\nclass Model(torch.nn.Module):\n    def __init__(self, num_users, num_items, hidden_channels, out_channels):\n        super().__init__()\n        self.user_emb = Embedding(num_users, hidden_channels, device=device)\n        self.item_emb = Embedding(num_items, hidden_channels, device=device)\n        self.item_encoder = ItemGNNEncoder(hidden_channels, out_channels)\n        self.user_encoder = UserGNNEncoder(hidden_channels, out_channels)\n        self.decoder = EdgeDecoder(out_channels)\n\n    def forward(self, x_dict, edge_index_dict, edge_label_index):\n        z_dict = {}\n        x_dict['user'] = self.user_emb(x_dict['user'])\n        x_dict['item'] = self.item_emb(x_dict['item'])\n        z_dict['item'] = self.item_encoder(\n            x_dict['item'],\n            edge_index_dict[('item', 'to', 'item')],\n        )\n        z_dict['user'] = self.user_encoder(x_dict, edge_index_dict)\n\n        return self.decoder(z_dict['user'], z_dict['item'], edge_label_index)\n\n\nmodel = Model(\n    num_users=data['user'].num_nodes,\n    num_items=data['item'].num_nodes,\n    hidden_channels=64,\n    out_channels=64,\n).to(device)\noptimizer = torch.optim.Adam(model.parameters(), lr=0.001)\n\n\ndef train():\n    model.train()\n\n    total_loss = total_examples = 0\n    for batch in tqdm.tqdm(train_loader):\n        batch = batch.to(device)\n        optimizer.zero_grad()\n\n        pred = model(\n            batch.x_dict,\n            batch.edge_index_dict,\n            batch['user', 'item'].edge_label_index,\n        )\n        loss = F.binary_cross_entropy_with_logits(\n            pred, batch['user', 'item'].edge_label)\n\n        loss.backward()\n        optimizer.step()\n        total_loss += float(loss)\n        total_examples += pred.numel()\n\n    return total_loss / total_examples\n\n\n@torch.no_grad()\ndef test(loader):\n    model.eval()\n\n    preds, targets = [], []\n    for batch in tqdm.tqdm(loader):\n        batch = batch.to(device)\n\n        pred = model(\n            batch.x_dict,\n            batch.edge_index_dict,\n            batch['user', 'item'].edge_label_index,\n        ).sigmoid().view(-1).cpu()\n        target = batch['user', 'item'].edge_label.long().cpu()\n\n        preds.append(pred)\n        targets.append(pred)\n\n    pred = torch.cat(preds, dim=0).numpy()\n    target = torch.cat(target, dim=0).numpy()\n\n    acc = accuracy_score(target, pred)\n    prec = precision_score(target, pred)\n    rec = recall_score(target, pred)\n    f1 = f1_score(target, pred)\n\n    return acc, prec, rec, f1\n\n\nfor epoch in range(1, 21):\n    loss = train()\n    val_acc, val_prec, val_rec, val_f1 = test(val_loader)\n    test_acc, test_prec, test_rec, test_f1 = test(test_loader)\n\n    print(f'Epoch: {epoch:03d}, Loss: {loss:4f}')\n    print(f'Val Acc: {val_acc:.4f}, Val Precision {val_prec:.4f}, '\n          f'Val Recall {val_rec:.4f}, Val F1 {val_f1:.4f}')\n    print(f'Test Acc: {test_acc:.4f}, Test Precision {test_prec:.4f}, '\n          f'Test Recall {test_rec:.4f}, Test F1 {test_f1:.4f}')\n", "path": "examples/hetero/bipartite_sage_unsup.py"}], "after_files": [{"content": "# An implementation of unsupervised bipartite GraphSAGE using the Alibaba\n# Taobao dataset.\nimport os.path as osp\n\nimport torch\nimport torch.nn.functional as F\nimport tqdm\nfrom sklearn.metrics import (\n    accuracy_score,\n    f1_score,\n    precision_score,\n    recall_score,\n)\nfrom torch.nn import Embedding, Linear\n\nimport torch_geometric.transforms as T\nfrom torch_geometric.datasets import Taobao\nfrom torch_geometric.loader import LinkNeighborLoader\nfrom torch_geometric.nn import SAGEConv\nfrom torch_geometric.utils.convert import to_scipy_sparse_matrix\n\ndevice = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')\npath = osp.join(osp.dirname(osp.realpath(__file__)), '../../data/Taobao')\n\ndataset = Taobao(path)\ndata = dataset[0]\n\ndata['user'].x = torch.arange(0, data['user'].num_nodes)\ndata['item'].x = torch.arange(0, data['item'].num_nodes)\n\n# Only consider user<>item relationships for simplicity:\ndel data['category']\ndel data['item', 'category']\ndel data['user', 'item'].time\ndel data['user', 'item'].behavior\n\n# Add a reverse ('item', 'rev_to', 'user') relation for message passing:\ndata = T.ToUndirected()(data)\n\n# Perform a link-level split into training, validation, and test edges:\nprint('Computing data splits...')\ntrain_data, val_data, test_data = T.RandomLinkSplit(\n    num_val=0.1,\n    num_test=0.1,\n    neg_sampling_ratio=1.0,\n    add_negative_train_samples=False,\n    edge_types=[('user', 'to', 'item')],\n    rev_edge_types=[('item', 'rev_to', 'user')],\n)(data)\nprint('Done!')\n\n# Compute sparsified item<>item relationships through users:\nprint('Computing item<>item relationships...')\nmat = to_scipy_sparse_matrix(data['user', 'item'].edge_index).tocsr()\nmat = mat[:data['user'].num_nodes, :data['item'].num_nodes]\ncomat = mat.T @ mat\ncomat.setdiag(0)\ncomat = comat >= 3.\ncomat = comat.tocoo()\nrow = torch.from_numpy(comat.row).to(torch.long)\ncol = torch.from_numpy(comat.col).to(torch.long)\nitem_to_item_edge_index = torch.stack([row, col], dim=0)\n\n# Add the generated item<>item relationships for high-order information:\ntrain_data['item', 'item'].edge_index = item_to_item_edge_index\nval_data['item', 'item'].edge_index = item_to_item_edge_index\ntest_data['item', 'item'].edge_index = item_to_item_edge_index\nprint('Done!')\n\ntrain_loader = LinkNeighborLoader(\n    data=train_data,\n    num_neighbors=[8, 4],\n    edge_label_index=('user', 'to', 'item'),\n    neg_sampling='binary',\n    batch_size=2048,\n    shuffle=True,\n    num_workers=16,\n    drop_last=True,\n)\n\nval_loader = LinkNeighborLoader(\n    data=val_data,\n    num_neighbors=[8, 4],\n    edge_label_index=(\n        ('user', 'to', 'item'),\n        val_data[('user', 'to', 'item')].edge_label_index,\n    ),\n    edge_label=val_data[('user', 'to', 'item')].edge_label,\n    batch_size=2048,\n    shuffle=False,\n    num_workers=16,\n)\n\ntest_loader = LinkNeighborLoader(\n    data=test_data,\n    num_neighbors=[8, 4],\n    edge_label_index=(\n        ('user', 'to', 'item'),\n        test_data[('user', 'to', 'item')].edge_label_index,\n    ),\n    edge_label=test_data[('user', 'to', 'item')].edge_label,\n    batch_size=2048,\n    shuffle=False,\n    num_workers=16,\n)\n\n\nclass ItemGNNEncoder(torch.nn.Module):\n    def __init__(self, hidden_channels, out_channels):\n        super().__init__()\n        self.conv1 = SAGEConv(-1, hidden_channels)\n        self.conv2 = SAGEConv(hidden_channels, hidden_channels)\n        self.lin = Linear(hidden_channels, out_channels)\n\n    def forward(self, x, edge_index):\n        x = self.conv1(x, edge_index).relu()\n        x = self.conv2(x, edge_index).relu()\n        return self.lin(x)\n\n\nclass UserGNNEncoder(torch.nn.Module):\n    def __init__(self, hidden_channels, out_channels):\n        super().__init__()\n        self.conv1 = SAGEConv((-1, -1), hidden_channels)\n        self.conv2 = SAGEConv((-1, -1), hidden_channels)\n        self.conv3 = SAGEConv((-1, -1), hidden_channels)\n        self.lin = Linear(hidden_channels, out_channels)\n\n    def forward(self, x_dict, edge_index_dict):\n        item_x = self.conv1(\n            x_dict['item'],\n            edge_index_dict[('item', 'to', 'item')],\n        ).relu()\n\n        user_x = self.conv2(\n            (x_dict['item'], x_dict['user']),\n            edge_index_dict[('item', 'rev_to', 'user')],\n        ).relu()\n\n        user_x = self.conv3(\n            (item_x, user_x),\n            edge_index_dict[('item', 'rev_to', 'user')],\n        ).relu()\n\n        return self.lin(user_x)\n\n\nclass EdgeDecoder(torch.nn.Module):\n    def __init__(self, hidden_channels):\n        super().__init__()\n        self.lin1 = Linear(2 * hidden_channels, hidden_channels)\n        self.lin2 = Linear(hidden_channels, 1)\n\n    def forward(self, z_src, z_dst, edge_label_index):\n        row, col = edge_label_index\n        z = torch.cat([z_src[row], z_dst[col]], dim=-1)\n\n        z = self.lin1(z).relu()\n        z = self.lin2(z)\n        return z.view(-1)\n\n\nclass Model(torch.nn.Module):\n    def __init__(self, num_users, num_items, hidden_channels, out_channels):\n        super().__init__()\n        self.user_emb = Embedding(num_users, hidden_channels, device=device)\n        self.item_emb = Embedding(num_items, hidden_channels, device=device)\n        self.item_encoder = ItemGNNEncoder(hidden_channels, out_channels)\n        self.user_encoder = UserGNNEncoder(hidden_channels, out_channels)\n        self.decoder = EdgeDecoder(out_channels)\n\n    def forward(self, x_dict, edge_index_dict, edge_label_index):\n        z_dict = {}\n        x_dict['user'] = self.user_emb(x_dict['user'])\n        x_dict['item'] = self.item_emb(x_dict['item'])\n        z_dict['item'] = self.item_encoder(\n            x_dict['item'],\n            edge_index_dict[('item', 'to', 'item')],\n        )\n        z_dict['user'] = self.user_encoder(x_dict, edge_index_dict)\n\n        return self.decoder(z_dict['user'], z_dict['item'], edge_label_index)\n\n\nmodel = Model(\n    num_users=data['user'].num_nodes,\n    num_items=data['item'].num_nodes,\n    hidden_channels=64,\n    out_channels=64,\n).to(device)\noptimizer = torch.optim.Adam(model.parameters(), lr=0.001)\n\n\ndef train():\n    model.train()\n\n    total_loss = total_examples = 0\n    for batch in tqdm.tqdm(train_loader):\n        batch = batch.to(device)\n        optimizer.zero_grad()\n\n        pred = model(\n            batch.x_dict,\n            batch.edge_index_dict,\n            batch['user', 'item'].edge_label_index,\n        )\n        loss = F.binary_cross_entropy_with_logits(\n            pred, batch['user', 'item'].edge_label)\n\n        loss.backward()\n        optimizer.step()\n        total_loss += float(loss)\n        total_examples += pred.numel()\n\n    return total_loss / total_examples\n\n\n@torch.no_grad()\ndef test(loader):\n    model.eval()\n\n    preds, targets = [], []\n    for batch in tqdm.tqdm(loader):\n        batch = batch.to(device)\n\n        pred = model(\n            batch.x_dict,\n            batch.edge_index_dict,\n            batch['user', 'item'].edge_label_index,\n        ).sigmoid().view(-1).cpu()\n        target = batch['user', 'item'].edge_label.long().cpu()\n\n        preds.append(pred)\n        targets.append(target)\n\n    pred = torch.cat(preds, dim=0).numpy()\n    target = torch.cat(targets, dim=0).numpy()\n\n    pred = pred > 0.5\n    acc = accuracy_score(target, pred)\n    prec = precision_score(target, pred)\n    rec = recall_score(target, pred)\n    f1 = f1_score(target, pred)\n\n    return acc, prec, rec, f1\n\n\nfor epoch in range(1, 21):\n    loss = train()\n    val_acc, val_prec, val_rec, val_f1 = test(val_loader)\n    test_acc, test_prec, test_rec, test_f1 = test(test_loader)\n\n    print(f'Epoch: {epoch:03d}, Loss: {loss:4f}')\n    print(f'Val Acc: {val_acc:.4f}, Val Precision {val_prec:.4f}, '\n          f'Val Recall {val_rec:.4f}, Val F1 {val_f1:.4f}')\n    print(f'Test Acc: {test_acc:.4f}, Test Precision {test_prec:.4f}, '\n          f'Test Recall {test_rec:.4f}, Test F1 {test_f1:.4f}')\n", "path": "examples/hetero/bipartite_sage_unsup.py"}]}