# GNN for node classification in cora dataset

# -*- coding: utf-8 -*-
"""GNN-1.ipynb

Automatically generated by Colaboratory.

Original file is located at
    https://colab.research.google.com/drive/1P71T37OLu-Z8_-q01tk-qLlCxOp6uNnj
"""

!pip3 install torch_geometric

import torch
import torch.nn.functional as F
from torch_geometric.datasets import Planetoid
import torch_geometric.transforms as T
import numpy as np
import torch.nn as nn
import torch.optim as optim
from torch_geometric.transforms import NormalizeFeatures
import torch.nn.init as init
import os
import collections
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import scipy.sparse as sp
import torch
from torch import Tensor
import torch_geometric
from torch_geometric.utils import to_networkx
from torch_geometric.datasets import Planetoid
import networkx as nx
from networkx.algorithms import community
import torch.nn.functional as F
import torch.optim.lr_scheduler as lr_scheduler
from torch_geometric.nn import GCNConv

# 加载Cora数据集
dataset = Planetoid(root='data/Cora', name='Cora', transform=T.NormalizeFeatures())
data = dataset[0]

# laplacian = torch.diag(degree) - torch.sparse.FloatTensor(adj, torch.ones(adj.shape[1]), (num_nodes, num_nodes))
# laplacian = laplacian.to_dense()
# laplacian = laplacian.float()

# 计算拉普拉斯矩阵的特征值和特征向量
# eigenvalues, eigenvectors = np.linalg.eigh(laplacian.numpy())
# inverse_transformed = np.dot(eigenvectors, np.diag(eigenvalues)).dot(np.transpose(eigenvectors))
# mean = 0  # 均值
# std_deviation = 0.001  # 标准差,控制噪声的强度
# noise = np.random.normal(mean, std_deviation, eigenvectors.shape)
# noisy_eigenvectors = eigenvectors + noise
# inverse_transformed_noisy = np.dot(noisy_eigenvectors, np.diag(eigenvalues)).dot(np.transpose(noisy_eigenvectors))
# inverse_transformed = np.dot(eigenvectors, np.diag(eigenvalues)).dot(np.transpose(eigenvectors))

# 训练模型
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
num_features = dataset.num_features
num_classes = dataset.num_classes

# 构建图的拉普拉斯矩阵(这里使用对称归一化拉普拉斯)
adj = data.edge_index
num_nodes = data.num_nodes
degree = torch.zeros(num_nodes)
ones = torch.ones(num_nodes)
A = torch.diag(ones)
for i in range(len(adj[0])):
  degree[adj[0][i]] += 1
  degree[adj[1][i]] += 1
  A[adj[0][i]][adj[1][i]] = 1
  A[adj[1][i]][adj[0][i]] = 1

d_v = torch.pow(degree, -0.5)
D_1 = torch.diag(d_v)

M = torch.mm(torch.mm(D_1, A), D_1)

class GCNLayer(nn.Module):
    def __init__(self, input_dim, output_dim, M):
        super(GCNLayer, self).__init__()
        self.fc1 = nn.Linear(input_dim, output_dim)
        self.laplacian = M.to(device)

    def forward(self, x):
        x = x.to(device)
        x = torch.spmm(self.laplacian, x)
        x = self.fc1(x)
        activation = nn.LeakyReLU(negative_slope=0.01)
        # activation = torch.sigmoid
        x = activation(x)
        x = F.dropout(x, p=0.1, training=self.training)
        return x

class GCN(nn.Module):
    def __init__(self, input_dim, output_dim, M):
        super(GCN, self).__init__()
        self.conv1 = GCNLayer(input_dim, 128, M)
        self.conv2 = GCNLayer(128, output_dim, M)

    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        return F.log_softmax(x, dim=1)

def train():
    model.train()
    optimizer.zero_grad()
    out = model(data.x)  # 使用自定义的拉普拉斯矩阵
    target = data.y.to(device)  # 将目标张量移动到相同的设备上
    loss = criterion(out[data.train_mask], target[data.train_mask])
    loss.backward()
    optimizer.step()
    return loss.item()

def test(mask):
    model.eval()
    with torch.no_grad():
      logits = model(data.x)
      pred = logits[mask].max(1)[1]
      acc = pred.eq(data.y[mask]).sum().item() / mask.sum().item()
      loss = criterion(logits[mask], data.y[mask]).item()
    return acc, loss

model = GCN(num_features, num_classes, M).to(device)
optimizer = torch.optim.Adam(model.parameters(), lr=0.05, weight_decay=5e-4)  # Increased weight decay and reduced learning rate
# scheduler = lr_scheduler.StepLR(optimizer, step_size=20, gamma=0.5)

# Define the loss function (criterion)
criterion = torch.nn.CrossEntropyLoss()

# Early stopping parameters
patience = 10
best_val_loss = None
epochs_no_improve = 0
data = data.to(device)  # Add this line

for epoch in range(1000):
    train_loss = train()
    train_acc, _ = test(data.train_mask.to(device))
    val_acc, val_loss = test(data.val_mask.to(device))
    test_acc, test_loss = test(data.test_mask.to(device))
    if epoch % 50 == 0:
      print(f'Epoch: {epoch+1:03d}')
      print(f'Train Acc: {train_acc:.4f}, Train Loss: {train_loss:.4f}')
      print(f'Val Acc: {val_acc:.4f}, Val Loss: {val_loss:.4f}')
      print(f'Test Acc: {test_acc:.4f}, Test Loss: {test_loss:.4f}')

    # scheduler.step()
Last update in: 9/16/2024, 3:20:49 AM