GCN model produces the same prediction for all batches in each epoch

sophie_gnn: <p>I am using this GCN model to classify a list of graphs into 5 families.</p>
<p>import torch<br>
from torch.nn import Linear<br>
from torch_geometric.nn import GCNConv, global_mean_pool<br>
import torch.nn.functional as F</p>
<p>class GCN(torch.nn.Module):<br>
def <strong>init</strong>(self, num_node_features, hidden_channels, num_classes):<br>
super(GCN, self).<strong>init</strong>()<br>
torch.manual_seed(12345)<br>
self.conv1 = GCNConv(num_node_features, hidden_channels)<br>
self.conv2 = GCNConv(hidden_channels, hidden_channels)<br>
self.conv3 = GCNConv(hidden_channels, hidden_channels)<br>
self.lin = Linear(hidden_channels, num_classes)</p>
<pre><code>def forward(self, x, edge_index, batch):
    # 1. Obtain node embeddings 
    x = self.conv1(x, edge_index)
    x = x.relu()
    x = self.conv2(x, edge_index)
    x = x.relu()
    x = self.conv3(x, edge_index)

    # 2. Readout layer
    x = global_mean_pool(x, batch)  # [batch_size, hidden_channels]

    # 3. Apply a final classifier
    x = F.dropout(x, p=0.5, training=self.training)
    x = self.lin(x)
    
    return x
</code></pre>
<p>model = GCN(num_node_features=1, hidden_channels=64, num_classes=5)<br>
from collections import Counter<br>
from random import choice</p>
<h1>
<a name="count-the-number-of-instances-in-each-class-1" class="anchor" href="#count-the-number-of-instances-in-each-class-1"></a>Count the number of instances in each class</h1>
<p>counter = Counter([data.y.item() for data in data_list])</p>
<h1>
<a name="get-the-maximum-number-of-instances-in-a-single-class-2" class="anchor" href="#get-the-maximum-number-of-instances-in-a-single-class-2"></a>Get the maximum number of instances in a single class</h1>
<p>max_count = max(counter.values())</p>
<h1>
<a name="create-a-dictionary-to-hold-instances-for-each-class-3" class="anchor" href="#create-a-dictionary-to-hold-instances-for-each-class-3"></a>Create a dictionary to hold instances for each class</h1>
<p>class_dict = {}<br>
for i in range(max(counter.keys()) + 1):<br>
class_dict[i] = [data for data in data_list if data.y.item() == i]</p>
<h1>
<a name="create-a-list-to-hold-the-oversampled-data-4" class="anchor" href="#create-a-list-to-hold-the-oversampled-data-4"></a>Create a list to hold the oversampled data</h1>
<p>oversampled_data_list = []</p>
<p>for class_key, data_instances in class_dict.items():<br>
oversample_count = max_count - len(data_instances)<br>
oversampled_data = data_instances + [choice(data_instances) for _ in range(oversample_count)]<br>
oversampled_data_list.extend(oversampled_data)</p>
<h1>
<a name="shuffle-the-oversampled-data-5" class="anchor" href="#shuffle-the-oversampled-data-5"></a>Shuffle the oversampled data</h1>
<p>random.shuffle(oversampled_data_list)</p>
<h1>
<a name="import-necessary-modules-6" class="anchor" href="#import-necessary-modules-6"></a>Import necessary modules</h1>
<p>from sklearn.model_selection import train_test_split<br>
from torch_geometric.data import DataLoader<br>
from torch.optim import Adam<br>
from torch.utils.data.sampler import WeightedRandomSampler<br>
import torch<br>
device = torch.device(‘cuda’ if torch.cuda.is_available() else ‘cpu’)<br>
<span class="hashtag">#model</span> = GAE(GATEncoder(in_channels, out_channels)).to(device)<br>
model=GCN(hidden_channels=3,out_channels=10)</p>
<h1>
<a name="create-a-dataloader-for-your-data_list-7" class="anchor" href="#create-a-dataloader-for-your-data_list-7"></a>Create a DataLoader for your data_list</h1>
<p>loader = DataLoader(data_list, batch_size=5, shuffle=True)<br>
import torch<br>
from torch.utils.data import random_split</p>
<p>torch.manual_seed(12345)<br>
total_length = len(oversampled_data_list)<br>
train_length = int(total_length*0.8)<br>
test_length = total_length - train_length</p>
<p>train_dataset, test_dataset = random_split(oversampled_data_list, [train_length, test_length])</p>
<p>train_loader = DataLoader(train_dataset, batch_size=10, shuffle=True)<br>
test_loader = DataLoader(test_dataset, batch_size=10, shuffle=False)</p>
<p>optimizer = torch.optim.Adam(model.parameters(), lr=0.5)<br>
criterion = torch.nn.CrossEntropyLoss()</p>
<p>def train():<br>
model.train()<br>
for data in train_loader:  # Iterate in batches over the training dataset.<br>
optimizer.zero_grad()  # Clear gradients.<br>
out = model(data.x, data.edge_index, data.batch)  # Perform a single forward pass.<br>
loss = criterion(out, data.y)  # Compute the loss.<br>
loss.backward()  # Derive gradients.<br>
optimizer.step()  # Update parameters based on gradients.</p>
<p>def test(loader):<br>
model.eval()<br>
correct = 0<br>
with torch.no_grad():<br>
for data in loader:  # Iterate in batches over the training/test dataset.<br>
out = model(data.x, data.edge_index, data.batch)<br>
pred = out.argmax(dim=1)  # Use the class with highest probability.<br>
print(pred)<br>
correct += int((pred == data.y).sum())  # Check against ground-truth labels.<br>
return correct / len(loader.dataset)  # Derive ratio of correct predictions.</p>
<p>for epoch in range(1, 171):<br>
train()<br>
train_acc = test(train_loader)<br>
test_acc = test(test_loader)<br>
print(f’Epoch: {epoch:03d}, Train Acc: {train_acc:.4f}, Test Acc: {test_acc:.4f}')</p>
<p>tensor([1, 1, 1, 1, 1, 1, 1, 1, 1, 1])<br>
tensor([1, 1, 1, 1, 1, 1, 1, 1, 1, 1])<br>
tensor([1, 1, 1, 1, 1, 1, 1, 1, 1, 1])<br>
tensor([1, 1, 1, 1, 1, 1, 1, 1, 1, 1])<br>
tensor([1, 1, 1, 1, 1, 1, 1, 1, 1, 1])<br>
tensor([1, 1, 1, 1, 1, 1, 1, 1, 1, 1])<br>
tensor([1, 1, 1, 1, 1, 1])<br>
Epoch: 001, Train Acc: 0.2054, Test Acc: 0.1786<br>
tensor([3, 3, 3, 3, 3, 3, 3, 3, 3, 3])<br>
tensor([3, 3, 3, 3, 3, 3, 3, 3, 3, 3])<br>
tensor([3, 3, 3, 3, 3, 3, 3, 3, 3, 3])<br>
tensor([3, 3, 3, 3, 3, 3, 3, 3, 3, 3])<br>
tensor([3, 3, 3, 3, 3, 3, 3, 3, 3, 3]</p>
<p>It does the same even after 30 epochs, just it changes which family.</p>
<p>How can I make it work? Thanks a lot!</p>