Unverified Commit 81bbeb60 authored by VoVAllen's avatar VoVAllen Committed by GitHub
Browse files

Merge branch 'capsule-tutorial' into master

parents a50bbe58 4fd359cb
"""
PyTorch implementation of CapsNet in Sabour, Hinton et al.'s paper
Dynamic Routing Between Capsules. NIPS 2017.
https://arxiv.org/abs/1710.09829
Usage:
python main.py
python main.py --epochs 30
python main.py --epochs 30 --num-routing 1
Author: Cedric Chee
"""
from __future__ import print_function
import argparse
import os
from timeit import default_timer as timer
import torch
import torch.optim as optim
import torchvision.utils as vutils
from torch.autograd import Variable
from torch.backends import cudnn
from tqdm import tqdm
import utils
from model import Net
from utils import writer, step
def train(model, data_loader, optimizer, epoch, writer):
"""
Train CapsuleNet model on training set
Args:
model: The CapsuleNet model.
data_loader: An interator over the dataset. It combines a dataset and a sampler.
optimizer: Optimization algorithm.
epoch: Current epoch.
"""
print('===> Training mode')
num_batches = len(data_loader) # iteration per epoch. e.g: 469
total_step = args.epochs * num_batches
epoch_tot_acc = 0
# Switch to train mode
model.train()
if args.cuda:
# When we wrap a Module in DataParallel for multi-GPUs
model = model.module
start_time = timer()
for batch_idx, (data, target) in enumerate(tqdm(data_loader, unit='batch')):
batch_size = data.size(0)
global_step = batch_idx + (epoch * num_batches) - num_batches
step['step'] = global_step
labels = target
target_one_hot = utils.one_hot_encode(target, length=args.num_classes)
assert target_one_hot.size() == torch.Size([batch_size, 10])
data, target = Variable(data), Variable(target_one_hot)
if args.cuda:
data = data.cuda()
target = target.cuda()
# Train step - forward, backward and optimize
optimizer.zero_grad()
output = model(data) # output from DigitCaps (out_digit_caps)
loss, margin_loss, recon_loss = model.loss(data, output, target)
loss.backward()
optimizer.step()
# Calculate accuracy for each step and average accuracy for each epoch
acc = utils.accuracy(output, labels, args.cuda)
epoch_tot_acc += acc
epoch_avg_acc = epoch_tot_acc / (batch_idx + 1)
# TensorBoard logging
# 1) Log the scalar values
writer.add_scalar('train/total_loss', loss.item(), global_step)
writer.add_scalar('train/margin_loss', margin_loss.item(), global_step)
if args.use_reconstruction_loss:
writer.add_scalar('train/reconstruction_loss', recon_loss.item(), global_step)
writer.add_scalar('train/batch_accuracy', acc, global_step)
writer.add_scalar('train/accuracy', epoch_avg_acc, global_step)
# 2) Log values and gradients of the parameters (histogram)
# for tag, value in model.named_parameters():
# tag = tag.replace('.', '/')
# writer.add_histogram(tag, utils.to_np(value), global_step)
# writer.add_histogram(tag + '/grad', utils.to_np(value.grad), global_step)
# Print losses
if batch_idx % args.log_interval == 0:
template = 'Epoch {}/{}, ' \
'Step {}/{}: ' \
'[Total loss: {:.6f},' \
'\tMargin loss: {:.6f},' \
'\tReconstruction loss: {:.6f},' \
'\tBatch accuracy: {:.6f},' \
'\tAccuracy: {:.6f}]'
tqdm.write(template.format(
epoch,
args.epochs,
global_step,
total_step,
loss.item(),
margin_loss.item(),
recon_loss.item() if args.use_reconstruction_loss else 0,
acc,
epoch_avg_acc))
# Print time elapsed for an epoch
end_time = timer()
print('Time elapsed for epoch {}: {:.0f}s.'.format(epoch, end_time - start_time))
def test(model, data_loader, num_train_batches, epoch, writer):
"""
Evaluate model on validation set
Args:
model: The CapsuleNet model.
data_loader: An interator over the dataset. It combines a dataset and a sampler.
"""
print('===> Evaluate mode')
# Switch to evaluate mode
model.eval()
if args.cuda:
# When we wrap a Module in DataParallel for multi-GPUs
model = model.module
loss = 0
margin_loss = 0
recon_loss = 0
correct = 0
num_batches = len(data_loader)
global_step = epoch * num_train_batches + num_train_batches
step['step'] = global_step
for data, target in data_loader:
batch_size = data.size(0)
target_indices = target
target_one_hot = utils.one_hot_encode(target_indices, length=args.num_classes)
assert target_one_hot.size() == torch.Size([batch_size, 10])
data, target = Variable(data, volatile=True), Variable(target_one_hot)
if args.cuda:
data = data.cuda()
target = target.cuda()
# Output predictions
output = model(data) # output from DigitCaps (out_digit_caps)
# Sum up batch loss
t_loss, m_loss, r_loss = model.loss(data, output, target, size_average=False)
loss += t_loss.data[0]
margin_loss += m_loss.data[0]
recon_loss += r_loss.data[0]
# Count number of correct predictions
# v_magnitude shape: [128, 10, 1, 1]
v_magnitude = torch.sqrt((output ** 2).sum(dim=2, keepdim=True))
# pred shape: [128, 1, 1, 1]
pred = v_magnitude.data.max(1, keepdim=True)[1].cpu()
correct += pred.eq(target_indices.view_as(pred)).sum()
# Get the reconstructed images of the last batch
if args.use_reconstruction_loss:
reconstruction = model.decoder(output, target)
# Input image size and number of channel.
# By default, for MNIST, the image width and height is 28x28 and 1 channel for black/white.
image_width = args.input_width
image_height = args.input_height
image_channel = args.num_conv_in_channel
recon_img = reconstruction.view(-1, image_channel, image_width, image_height)
assert recon_img.size() == torch.Size([batch_size, image_channel, image_width, image_height])
# Save the image into file system
utils.save_image(recon_img, 'results/recons_image_test_{}_{}.png'.format(epoch, global_step))
utils.save_image(data, 'results/original_image_test_{}_{}.png'.format(epoch, global_step))
# Add and visualize the image in TensorBoard
recon_img = vutils.make_grid(recon_img.data, normalize=True, scale_each=True)
original_img = vutils.make_grid(data.data, normalize=True, scale_each=True)
writer.add_image('test/recons-image-{}-{}'.format(epoch, global_step), recon_img, global_step)
writer.add_image('test/original-image-{}-{}'.format(epoch, global_step), original_img, global_step)
# Log test losses
loss /= num_batches
margin_loss /= num_batches
recon_loss /= num_batches
# Log test accuracies
num_test_data = len(data_loader.dataset)
accuracy = correct / num_test_data
accuracy_percentage = 100. * accuracy
# TensorBoard logging
# 1) Log the scalar values
writer.add_scalar('test/total_loss', loss, global_step)
writer.add_scalar('test/margin_loss', margin_loss, global_step)
if args.use_reconstruction_loss:
writer.add_scalar('test/reconstruction_loss', recon_loss, global_step)
writer.add_scalar('test/accuracy', accuracy, global_step)
# Print test losses and accuracy
print('Test: [Loss: {:.6f},' \
'\tMargin loss: {:.6f},' \
'\tReconstruction loss: {:.6f}]'.format(
loss,
margin_loss,
recon_loss if args.use_reconstruction_loss else 0))
print('Test Accuracy: {}/{} ({:.0f}%)\n'.format(
correct, num_test_data, accuracy_percentage))
def main():
"""The main function
Entry point.
"""
global args
# Setting the hyper parameters
parser = argparse.ArgumentParser(description='Example of Capsule Network')
parser.add_argument('--epochs', type=int, default=10,
help='number of training epochs. default=10')
parser.add_argument('--lr', type=float, default=0.01,
help='learning rate. default=0.01')
parser.add_argument('--batch-size', type=int, default=128,
help='training batch size. default=128')
parser.add_argument('--test-batch-size', type=int,
default=128, help='testing batch size. default=128')
parser.add_argument('--log-interval', type=int, default=10,
help='how many batches to wait before logging training status. default=10')
parser.add_argument('--no-cuda', action='store_true', default=False,
help='disables CUDA training. default=false')
parser.add_argument('--threads', type=int, default=4,
help='number of threads for data loader to use. default=4')
parser.add_argument('--seed', type=int, default=42,
help='random seed for training. default=42')
parser.add_argument('--num-conv-out-channel', type=int, default=256,
help='number of channels produced by the convolution. default=256')
parser.add_argument('--num-conv-in-channel', type=int, default=1,
help='number of input channels to the convolution. default=1')
parser.add_argument('--num-primary-unit', type=int, default=8,
help='number of primary unit. default=8')
parser.add_argument('--primary-unit-size', type=int,
default=1152, help='primary unit size is 32 * 6 * 6. default=1152')
parser.add_argument('--num-classes', type=int, default=10,
help='number of digit classes. 1 unit for one MNIST digit. default=10')
parser.add_argument('--output-unit-size', type=int,
default=16, help='output unit size. default=16')
parser.add_argument('--num-routing', type=int,
default=3, help='number of routing iteration. default=3')
parser.add_argument('--use-reconstruction-loss', type=utils.str2bool, nargs='?', default=True,
help='use an additional reconstruction loss. default=True')
parser.add_argument('--regularization-scale', type=float, default=0.0005,
help='regularization coefficient for reconstruction loss. default=0.0005')
parser.add_argument('--dataset', help='the name of dataset (mnist, cifar10)', default='mnist')
parser.add_argument('--input-width', type=int,
default=28, help='input image width to the convolution. default=28 for MNIST')
parser.add_argument('--input-height', type=int,
default=28, help='input image height to the convolution. default=28 for MNIST')
args = parser.parse_args()
print(args)
# Check GPU or CUDA is available
args.cuda = not args.no_cuda and torch.cuda.is_available()
# Get reproducible results by manually seed the random number generator
torch.manual_seed(args.seed)
if args.cuda:
torch.cuda.manual_seed(args.seed)
# Load data
train_loader, test_loader = utils.load_data(args)
# Build Capsule Network
print('===> Building model')
model = Net(num_conv_in_channel=args.num_conv_in_channel,
num_conv_out_channel=args.num_conv_out_channel,
num_primary_unit=args.num_primary_unit,
primary_unit_size=args.primary_unit_size,
num_classes=args.num_classes,
output_unit_size=args.output_unit_size,
num_routing=args.num_routing,
use_reconstruction_loss=args.use_reconstruction_loss,
regularization_scale=args.regularization_scale,
input_width=args.input_width,
input_height=args.input_height,
cuda_enabled=args.cuda)
if args.cuda:
print('Utilize GPUs for computation')
print('Number of GPU available', torch.cuda.device_count())
model.cuda()
cudnn.benchmark = True
model = torch.nn.DataParallel(model)
# Print the model architecture and parameters
print('Model architectures:\n{}\n'.format(model))
print('Parameters and size:')
for name, param in model.named_parameters():
print('{}: {}'.format(name, list(param.size())))
# CapsNet has:
# - 8.2M parameters and 6.8M parameters without the reconstruction subnet on MNIST.
# - 11.8M parameters and 8.0M parameters without the reconstruction subnet on CIFAR10.
num_params = sum([param.nelement() for param in model.parameters()])
# The coupling coefficients c_ij are not included in the parameter list,
# we need to add them manually, which is 1152 * 10 = 11520 (on MNIST) or 2048 * 10 (on CIFAR10)
print('\nTotal number of parameters: {}\n'.format(num_params + (11520 if args.dataset == 'mnist' else 20480)))
# Optimizer
optimizer = optim.Adam(model.parameters(), lr=args.lr)
# Make model checkpoint directory
if not os.path.exists('results/trained_model'):
os.makedirs('results/trained_model')
# Train and test
for epoch in range(1, args.epochs + 1):
train(model, train_loader, optimizer, epoch, writer)
test(model, test_loader, len(train_loader), epoch, writer)
# Save model checkpoint
utils.checkpoint({
'epoch': epoch + 1,
'state_dict': model.state_dict(),
'optimizer': optimizer.state_dict()
}, epoch)
writer.close()
if __name__ == "__main__":
main()
"""CapsNet Architecture
PyTorch implementation of CapsNet in Sabour, Hinton et al.'s paper
Dynamic Routing Between Capsules. NIPS 2017.
https://arxiv.org/abs/1710.09829
Author: Cedric Chee
"""
import torch
import torch.nn as nn
from torch.autograd import Variable
from capsule_layer import CapsuleLayer
from conv_layer import ConvLayer
from decoder import Decoder
from dgl_capsule_batch import DGLBatchCapsuleLayer
class Net(nn.Module):
"""
A simple CapsNet with 3 layers
"""
def __init__(self, num_conv_in_channel, num_conv_out_channel, num_primary_unit,
primary_unit_size, num_classes, output_unit_size, num_routing,
use_reconstruction_loss, regularization_scale, input_width, input_height,
cuda_enabled):
"""
In the constructor we instantiate one ConvLayer module and two CapsuleLayer modules
and assign them as member variables.
"""
super(Net, self).__init__()
self.cuda_enabled = cuda_enabled
# Configurations used for image reconstruction.
self.use_reconstruction_loss = use_reconstruction_loss
# Input image size and number of channel.
# By default, for MNIST, the image width and height is 28x28
# and 1 channel for black/white.
self.image_width = input_width
self.image_height = input_height
self.image_channel = num_conv_in_channel
# Also known as lambda reconstruction. Default value is 0.0005.
# We use sum of squared errors (SSE) similar to paper.
self.regularization_scale = regularization_scale
# Layer 1: Conventional Conv2d layer.
self.conv1 = ConvLayer(in_channel=num_conv_in_channel,
out_channel=num_conv_out_channel,
kernel_size=9)
# PrimaryCaps
# Layer 2: Conv2D layer with `squash` activation.
self.primary = CapsuleLayer(in_unit=0,
in_channel=num_conv_out_channel,
num_unit=num_primary_unit,
unit_size=primary_unit_size, # capsule outputs
use_routing=False,
num_routing=num_routing,
cuda_enabled=cuda_enabled)
# DigitCaps
# Final layer: Capsule layer where the routing algorithm is.
self.digits = CapsuleLayer(in_unit=num_primary_unit,
in_channel=primary_unit_size,
num_unit=num_classes,
unit_size=output_unit_size, # 16D capsule per digit class
use_routing=True,
num_routing=num_routing,
cuda_enabled=cuda_enabled)
# Reconstruction network
if use_reconstruction_loss:
self.decoder = Decoder(num_classes, output_unit_size, input_width,
input_height, num_conv_in_channel, cuda_enabled)
def forward(self, x):
"""
Defines the computation performed at every forward pass.
"""
# x shape: [128, 1, 28, 28]. 128 is for the batch size.
# out_conv1 shape: [128, 256, 20, 20]
out_conv1 = self.conv1(x)
# out_primary_caps shape: [128, 8, 1152].
# Total PrimaryCapsules has [32 × 6 × 6 = 1152] capsule outputs.
out_primary_caps = self.primary(out_conv1)
# out_digit_caps shape: [128, 10, 16, 1]
# batch size: 128, 10 digit class, 16D capsule per digit class.
out_digit_caps = self.digits(out_primary_caps)
return out_digit_caps
def loss(self, image, out_digit_caps, target, size_average=True):
"""Custom loss function
Args:
image: [batch_size, 1, 28, 28] MNIST samples.
out_digit_caps: [batch_size, 10, 16, 1] The output from `DigitCaps` layer.
target: [batch_size, 10] One-hot MNIST dataset labels.
size_average: A boolean to enable mean loss (average loss over batch size).
Returns:
total_loss: A scalar Variable of total loss.
m_loss: A scalar of margin loss.
recon_loss: A scalar of reconstruction loss.
"""
recon_loss = 0
m_loss = self.margin_loss(out_digit_caps, target)
if size_average:
m_loss = m_loss.mean()
total_loss = m_loss
if self.use_reconstruction_loss:
# Reconstruct the image from the Decoder network
reconstruction = self.decoder(out_digit_caps, target)
recon_loss = self.reconstruction_loss(reconstruction, image)
# Mean squared error
if size_average:
recon_loss = recon_loss.mean()
# In order to keep in line with the paper,
# they scale down the reconstruction loss by 0.0005
# so that it does not dominate the margin loss.
total_loss = m_loss + recon_loss * self.regularization_scale
return total_loss, m_loss, (recon_loss * self.regularization_scale)
def margin_loss(self, input, target):
"""
Class loss
Implement equation 4 in section 3 'Margin loss for digit existence' in the paper.
Args:
input: [batch_size, 10, 16, 1] The output from `DigitCaps` layer.
target: target: [batch_size, 10] One-hot MNIST labels.
Returns:
l_c: A scalar of class loss or also know as margin loss.
"""
batch_size = input.size(0)
# ||vc|| also known as norm.
v_c = torch.sqrt((input ** 2).sum(dim=2, keepdim=True))
# Calculate left and right max() terms.
zero = Variable(torch.zeros(1))
if self.cuda_enabled:
zero = zero.cuda()
m_plus = 0.9
m_minus = 0.1
loss_lambda = 0.5
max_left = torch.max(m_plus - v_c, zero).view(batch_size, -1) ** 2
max_right = torch.max(v_c - m_minus, zero).view(batch_size, -1) ** 2
t_c = target
# Lc is margin loss for each digit of class c
l_c = t_c * max_left + loss_lambda * (1.0 - t_c) * max_right
l_c = l_c.sum(dim=1)
return l_c
def reconstruction_loss(self, reconstruction, image):
"""
The reconstruction loss is the sum of squared differences between
the reconstructed image (outputs of the logistic units) and
the original image (input image).
Implement section 4.1 'Reconstruction as a regularization method' in the paper.
Based on naturomics's implementation.
Args:
reconstruction: [batch_size, 784] Decoder outputs of reconstructed image tensor.
image: [batch_size, 1, 28, 28] MNIST samples.
Returns:
recon_error: A scalar Variable of reconstruction loss.
"""
# Calculate reconstruction loss.
batch_size = image.size(0) # or another way recon_img.size(0)
# error = (recon_img - image).view(batch_size, -1)
image = image.view(batch_size, -1) # flatten 28x28 by reshaping to [batch_size, 784]
error = reconstruction - image
squared_error = error ** 2
# Scalar Variable
recon_error = torch.sum(squared_error, dim=1)
return recon_error
http://download.pytorch.org/whl/cu90/torch-0.3.0.post4-cp36-cp36m-linux_x86_64.whl ; sys_platform == "linux"
http://download.pytorch.org/whl/torch-0.3.0.post4-cp36-cp36m-macosx_10_7_x86_64.whl ; sys_platform == "darwin"
torchvision
tensorboardX
tensorflow
tqdm
"""Utilities
PyTorch implementation of CapsNet in Sabour, Hinton et al.'s paper
Dynamic Routing Between Capsules. NIPS 2017.
https://arxiv.org/abs/1710.09829
Author: Cedric Chee
"""
import argparse
import torch
import torch.nn.functional as F
import torchvision.utils as vutils
from tensorboardX import SummaryWriter
from torch.autograd import Variable
from torch.utils.data import DataLoader
from torchvision import transforms, datasets
# Set the logger
writer = SummaryWriter()
step = {'step': 0}
def one_hot_encode(target, length):
"""Converts batches of class indices to classes of one-hot vectors."""
batch_s = target.size(0)
one_hot_vec = torch.zeros(batch_s, length)
for i in range(batch_s):
one_hot_vec[i, target[i]] = 1.0
return one_hot_vec
def checkpoint(state, epoch):
"""Save checkpoint"""
model_out_path = 'results/trained_model/model_epoch_{}.pth'.format(epoch)
torch.save(state, model_out_path)
print('Checkpoint saved to {}'.format(model_out_path))
def load_mnist(args):
"""Load MNIST dataset.
The data is split and normalized between train and test sets.
"""
# Normalize MNIST dataset.
data_transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
kwargs = {'num_workers': args.threads,
'pin_memory': True} if args.cuda else {}
print('===> Loading MNIST training datasets')
# MNIST dataset
training_set = datasets.MNIST(
'./data', train=True, download=True, transform=data_transform)
# Input pipeline
training_data_loader = DataLoader(
training_set, batch_size=args.batch_size, shuffle=True, **kwargs)
print('===> Loading MNIST testing datasets')
testing_set = datasets.MNIST(
'./data', train=False, download=True, transform=data_transform)
testing_data_loader = DataLoader(
testing_set, batch_size=args.test_batch_size, shuffle=True, **kwargs)
return training_data_loader, testing_data_loader
def load_cifar10(args):
"""Load CIFAR10 dataset.
The data is split and normalized between train and test sets.
"""
# Normalize CIFAR10 dataset.
data_transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])
kwargs = {'num_workers': args.threads,
'pin_memory': True} if args.cuda else {}
print('===> Loading CIFAR10 training datasets')
# CIFAR10 dataset
training_set = datasets.CIFAR10(
'./data', train=True, download=True, transform=data_transform)
# Input pipeline
training_data_loader = DataLoader(
training_set, batch_size=args.batch_size, shuffle=True, **kwargs)
print('===> Loading CIFAR10 testing datasets')
testing_set = datasets.CIFAR10(
'./data', train=False, download=True, transform=data_transform)
testing_data_loader = DataLoader(
testing_set, batch_size=args.test_batch_size, shuffle=True, **kwargs)
return training_data_loader, testing_data_loader
def load_data(args):
"""
Load dataset.
"""
dst = args.dataset
if dst == 'mnist':
return load_mnist(args)
elif dst == 'cifar10':
return load_cifar10(args)
else:
raise Exception('Invalid dataset, please check the name of dataset:', dst)
def squash(sj, dim=2):
"""
The non-linear activation used in Capsule.
It drives the length of a large vector to near 1 and small vector to 0
This implement equation 1 from the paper.
"""
sj_mag_sq = torch.sum(sj ** 2, dim, keepdim=True)
# ||sj||
sj_mag = torch.sqrt(sj_mag_sq)
v_j = (sj_mag_sq / (1.0 + sj_mag_sq)) * (sj / sj_mag)
return v_j
def mask(out_digit_caps, cuda_enabled=True):
"""
In the paper, they mask out all but the activity vector of the correct digit capsule.
This means:
a) during training, mask all but the capsule (1x16 vector) which match the ground-truth.
b) during testing, mask all but the longest capsule (1x16 vector).
Args:
out_digit_caps: [batch_size, 10, 16] Tensor output of `DigitCaps` layer.
Returns:
masked: [batch_size, 10, 16, 1] The masked capsules tensors.
"""
# a) Get capsule outputs lengths, ||v_c||
v_length = torch.sqrt((out_digit_caps ** 2).sum(dim=2))
# b) Pick out the index of longest capsule output, v_length by
# masking the tensor by the max value in dim=1.
_, max_index = v_length.max(dim=1)
max_index = max_index.data
# Method 1: masking with y.
# c) In all batches, get the most active capsule
# It's not easy to understand the indexing process with max_index
# as we are 3D animal.
batch_size = out_digit_caps.size(0)
masked_v = [None] * batch_size # Python list
for batch_ix in range(batch_size):
# Batch sample
sample = out_digit_caps[batch_ix]
# Masks out the other capsules in this sample.
v = Variable(torch.zeros(sample.size()))
if cuda_enabled:
v = v.cuda()
# Get the maximum capsule index from this batch sample.
max_caps_index = max_index[batch_ix]
v[max_caps_index] = sample[max_caps_index]
masked_v[batch_ix] = v # append v to masked_v
# Concatenates sequence of masked capsules tensors along the batch dimension.
masked = torch.stack(masked_v, dim=0)
return masked
def save_image(image, file_name):
"""
Save a given image into an image file
"""
# Check number of channels in an image.
if image.size(1) == 2:
# 2-channel image
zeros = torch.zeros(image.size(0), 1, image.size(2), image.size(3))
image_tensor = torch.cat([zeros, image.data.cpu()], dim=1)
else:
# Grayscale or RGB image
image_tensor = image.data.cpu() # get Tensor from Variable
vutils.save_image(image_tensor, file_name)
def accuracy(output, target, cuda_enabled=True):
"""
Compute accuracy.
Args:
output: [batch_size, 10, 16, 1] The output from DigitCaps layer.
target: [batch_size] Labels for dataset.
Returns:
accuracy (float): The accuracy for a batch.
"""
batch_size = target.size(0)
v_length = torch.sqrt((output ** 2).sum(dim=2, keepdim=True))
softmax_v = F.softmax(v_length, dim=1)
assert softmax_v.size() == torch.Size([batch_size, 10, 1, 1])
_, max_index = softmax_v.max(dim=1)
assert max_index.size() == torch.Size([batch_size, 1, 1])
pred = max_index.squeeze() # max_index.view(batch_size)
assert pred.size() == torch.Size([batch_size])
if cuda_enabled:
target = target.cuda()
pred = pred.cuda()
correct_pred = torch.eq(target, pred.data) # tensor
# correct_pred_sum = correct_pred.sum() # scalar. e.g: 6 correct out of 128 images.
acc = correct_pred.float().mean() # e.g: 6 / 128 = 0.046875
return acc
def to_np(param):
"""
Convert values of the model parameters to numpy.array.
"""
return param.clone().cpu().data.numpy()
def str2bool(v):
"""
Parsing boolean values with argparse.
"""
if v.lower() in ('yes', 'true', 't', 'y', '1'):
return True
elif v.lower() in ('no', 'false', 'f', 'n', '0'):
return False
else:
raise argparse.ArgumentTypeError('Boolean value expected.')
{
"cells": [
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Populating the interactive namespace from numpy and matplotlib\n"
]
}
],
"source": [
"%pylab inline\n",
"import networkx as nx\n",
"import scipy as sp\n",
"import scipy"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"edges=np.loadtxt(\"edges.txt\",dtype=np.int32)\n",
"nodes=np.loadtxt(\"nodes.txt\",dtype=np.int32)\n",
"G=nx.Graph()\n",
"\n",
"for i in range(4):\n",
" G.add_nodes_from(nodes[nodes[:,1]==i][:,0],labels=i)\n",
"G.add_edges_from(edges)"
]
},
{
"cell_type": "code",
"execution_count": 43,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"values= [node[1]['labels'] for node in G.nodes(data=True)]\n",
"\n",
"nx.draw_spring(G, cmap=plt.get_cmap('Set2'), node_color=values)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"def normalized_A(G):\n",
" A = nx.to_scipy_sparse_matrix(G,format='csr')\n",
" I=scipy.sparse.eye(A.shape[0])\n",
" n,m = A.shape\n",
" diags = A.sum(axis=1).flatten()\n",
" D = scipy.sparse.spdiags(diags, [0], m, n, format='csr')\n",
" AH=A+I\n",
" with scipy.errstate(divide='ignore'):\n",
" diags_sqrt = 1.0/scipy.sqrt(diags)\n",
" diags_sqrt[scipy.isinf(diags_sqrt)] = 0\n",
" DH = scipy.sparse.spdiags(diags_sqrt, [0], m, n, format='csr')\n",
" normalized_A=DH.dot(AH.dot(DH))\n",
" return normalized_A"
]
},
{
"cell_type": "code",
"execution_count": 39,
"metadata": {},
"outputs": [],
"source": [
"def ReLU(x):\n",
" return np.maximum(x, 0)\n",
"\n",
"num_input_features=34\n",
"num_output_features=2\n",
"\n",
"hidden_dim=[num_input_features,34,24,num_output_features]\n",
"num_layers=len(hidden_dim)-1\n",
"\n",
"num_nodes=G.number_of_nodes()\n",
"H=[np.random.randn(num_nodes,num_input_features) for i in range(num_layers+1)]\n",
"W=[np.random.randn(hidden_dim[i],hidden_dim[i+1])*3 for i in range(num_layers)]\n"
]
},
{
"cell_type": "code",
"execution_count": 40,
"metadata": {},
"outputs": [],
"source": [
"NA=normalized_A(G)\n",
"H[0]=np.eye(num_input_features)\n",
"for i in range(num_layers):\n",
" H[i+1]=ReLU(NA@H[i]@W[i])"
]
},
{
"cell_type": "code",
"execution_count": 41,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([330.65527807, 328.33212954, 327.29190903, 334.35929148,\n",
" 254.98186216, 848.1842729 , 367.62767144, 680.85927011,\n",
" 336.21841481, 324.98512806, 257.44082247, 345.22589289,\n",
" 389.92371201, 251.94775242, 262.56207955, 300.10323616,\n",
" 304.4027439 , 169.89019483, 181.95920536, 205.9823762 ,\n",
" 257.33860518, 253.01664177, 216.53126085, 195.03677365,\n",
" 391.22075663, 346.15820503, 196.40311898, 222.7800006 ,\n",
" 180.89261242, 189.85892877, 177.72642253, 212.64308276,\n",
" 185.25827475, 155.83209081])"
]
},
"execution_count": 41,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"H[3][:,0]"
]
},
{
"cell_type": "code",
"execution_count": 42,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<matplotlib.collections.PathCollection at 0x7f71f55464a8>"
]
},
"execution_count": 42,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"plt.scatter(H[3][:,1],H[3][:,0],c=values,cmap='Set2')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.6",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.1"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
# Embedding-the-karate-club-network-
Embedding the karate club network
{
"cells": [
{
"cell_type": "code",
"execution_count": 52,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Populating the interactive namespace from numpy and matplotlib\n"
]
}
],
"source": [
"%pylab inline\n",
"import networkx as nx\n",
"import torch\n",
"\n",
"import torch.nn.functional as F\n",
"import torch_geometric.transforms as T\n",
"from torch_geometric.data import Data\n",
"from torch_geometric.nn import GCNConv"
]
},
{
"cell_type": "code",
"execution_count": 53,
"metadata": {},
"outputs": [],
"source": [
"edges=np.loadtxt(\"edges.txt\",dtype=np.int32)\n",
"nodes=np.loadtxt(\"nodes.txt\",dtype=np.int32)\n",
"G=nx.Graph()\n",
"\n",
"for i in range(4):\n",
" G.add_nodes_from(nodes[nodes[:,1]==i][:,0],labels=i)\n",
"G.add_edges_from(edges)"
]
},
{
"cell_type": "code",
"execution_count": 54,
"metadata": {},
"outputs": [],
"source": [
"nodes=G.nodes(data=True)\n",
"values=[]\n",
"for i in range(1,G.number_of_nodes()+1):\n",
" values.append(nodes[i]['labels'])\n",
"edge_index=torch.from_numpy(np.array(G.edges()))\n",
"y=torch.from_numpy(np.array(values))\n",
"x=torch.eye(G.number_of_nodes())"
]
},
{
"cell_type": "code",
"execution_count": 55,
"metadata": {},
"outputs": [],
"source": [
"data = Data(x=x, edge_index=edge_index,y=y)"
]
},
{
"cell_type": "code",
"execution_count": 56,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Data(edge_index=[77, 2], x=[34, 34], y=[34])"
]
},
"execution_count": 56,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"data"
]
},
{
"cell_type": "code",
"execution_count": 57,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"tensor([0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], dtype=torch.uint8)"
]
},
"execution_count": 57,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"data.y==0"
]
},
{
"cell_type": "code",
"execution_count": 60,
"metadata": {},
"outputs": [],
"source": [
"class Net(torch.nn.Module):\n",
" def __init__(self):\n",
" super(Net, self).__init__()\n",
" self.module=torch.nn.Sequential(\n",
" GCNConv(data.num_features, 16),\n",
" torch.nn.ReLU(),\n",
" GCNConv(16, data.num_classes),\n",
" torch.nn.LogSoftmax(dim=1)\n",
" )\n",
" def forward(self, data):\n",
" return self.module((data.x, data.edge_index)\n"
]
},
{
"cell_type": "code",
"execution_count": 41,
"metadata": {},
"outputs": [],
"source": [
"device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n"
]
},
{
"cell_type": "code",
"execution_count": 42,
"metadata": {},
"outputs": [
{
"ename": "RuntimeError",
"evalue": "cuda runtime error (59) : device-side assert triggered at /pytorch/aten/src/THC/generic/THCTensorCopy.cpp:20",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mRuntimeError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-42-b966576db2b6>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mdata\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mto\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdevice\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;32m~/.pyenv/versions/3.6.1/lib/python3.6/site-packages/torch_geometric/data/data.py\u001b[0m in \u001b[0;36mto\u001b[0;34m(self, device, *keys)\u001b[0m\n\u001b[1;32m 102\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 103\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mto\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdevice\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0mkeys\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 104\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mapply\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;32mlambda\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mto\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdevice\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0mkeys\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 105\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 106\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__repr__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m~/.pyenv/versions/3.6.1/lib/python3.6/site-packages/torch_geometric/data/data.py\u001b[0m in \u001b[0;36mapply\u001b[0;34m(self, func, *keys)\u001b[0m\n\u001b[1;32m 95\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mapply\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mfunc\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0mkeys\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 96\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mkey\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mitem\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0mkeys\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 97\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfunc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mitem\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 98\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 99\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m~/.pyenv/versions/3.6.1/lib/python3.6/site-packages/torch_geometric/data/data.py\u001b[0m in \u001b[0;36m<lambda>\u001b[0;34m(x)\u001b[0m\n\u001b[1;32m 102\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 103\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mto\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdevice\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0mkeys\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 104\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mapply\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;32mlambda\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mto\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdevice\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0mkeys\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 105\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 106\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__repr__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mRuntimeError\u001b[0m: cuda runtime error (59) : device-side assert triggered at /pytorch/aten/src/THC/generic/THCTensorCopy.cpp:20"
]
}
],
"source": [
"data.to(device)"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"ename": "RuntimeError",
"evalue": "cuda runtime error (59) : device-side assert triggered at /pytorch/aten/src/THC/generic/THCTensorCopy.cpp:20",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mRuntimeError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-22-57c0fbac8614>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mdevice\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtorch\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdevice\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'cuda'\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mtorch\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcuda\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mis_available\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32melse\u001b[0m \u001b[0;34m'cpu'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mmodel\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdata\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mNet\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mto\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdevice\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdata\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mto\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdevice\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;32m~/.pyenv/versions/3.6.1/lib/python3.6/site-packages/torch/nn/modules/module.py\u001b[0m in \u001b[0;36mto\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 377\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mt\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mto\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdevice\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdtype\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mt\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mis_floating_point\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32melse\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnon_blocking\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 378\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 379\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_apply\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mconvert\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 380\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 381\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mregister_backward_hook\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mhook\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m~/.pyenv/versions/3.6.1/lib/python3.6/site-packages/torch/nn/modules/module.py\u001b[0m in \u001b[0;36m_apply\u001b[0;34m(self, fn)\u001b[0m\n\u001b[1;32m 183\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_apply\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mfn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 184\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mmodule\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mchildren\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 185\u001b[0;31m \u001b[0mmodule\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_apply\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 186\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 187\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mparam\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_parameters\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mvalues\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m~/.pyenv/versions/3.6.1/lib/python3.6/site-packages/torch/nn/modules/module.py\u001b[0m in \u001b[0;36m_apply\u001b[0;34m(self, fn)\u001b[0m\n\u001b[1;32m 189\u001b[0m \u001b[0;31m# Tensors stored in modules are graph leaves, and we don't\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 190\u001b[0m \u001b[0;31m# want to create copy nodes, so we have to unpack the data.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 191\u001b[0;31m \u001b[0mparam\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdata\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfn\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mparam\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdata\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 192\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mparam\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_grad\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 193\u001b[0m \u001b[0mparam\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_grad\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdata\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfn\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mparam\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_grad\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdata\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m~/.pyenv/versions/3.6.1/lib/python3.6/site-packages/torch/nn/modules/module.py\u001b[0m in \u001b[0;36mconvert\u001b[0;34m(t)\u001b[0m\n\u001b[1;32m 375\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 376\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mconvert\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mt\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 377\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mt\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mto\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdevice\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdtype\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mt\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mis_floating_point\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32melse\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnon_blocking\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 378\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 379\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_apply\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mconvert\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mRuntimeError\u001b[0m: cuda runtime error (59) : device-side assert triggered at /pytorch/aten/src/THC/generic/THCTensorCopy.cpp:20"
]
}
],
"source": [
"model, data = Net().to(device), data.to(device)"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [],
"source": [
"optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4)"
]
},
{
"cell_type": "code",
"execution_count": 43,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"tensor([1, 1, 2, 1, 0, 0, 0, 1, 3, 2, 0, 1, 1, 1, 3, 3, 0, 1, 3, 1, 3, 1, 3, 3,\n",
" 2, 2, 3, 2, 2, 3, 3, 2, 3, 3])"
]
},
"execution_count": 43,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"y"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"ename": "RuntimeError",
"evalue": "Expected object of type torch.FloatTensor but found type torch.cuda.FloatTensor for argument #2 'mat2'",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mRuntimeError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-16-ac68ecf76d96>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0moutput\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mmodel\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;32m~/.pyenv/versions/3.6.1/lib/python3.6/site-packages/torch/nn/modules/module.py\u001b[0m in \u001b[0;36m__call__\u001b[0;34m(self, *input, **kwargs)\u001b[0m\n\u001b[1;32m 475\u001b[0m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_slow_forward\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0minput\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 476\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 477\u001b[0;31m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mforward\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0minput\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 478\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mhook\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_forward_hooks\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mvalues\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 479\u001b[0m \u001b[0mhook_result\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mhook\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minput\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mresult\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m<ipython-input-6-6046f23108a0>\u001b[0m in \u001b[0;36mforward\u001b[0;34m(self, data)\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 7\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mforward\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdata\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 8\u001b[0;31m \u001b[0mx\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mF\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrelu\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconv1\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdata\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0medge_index\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 9\u001b[0m \u001b[0mx\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconv2\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdata\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0medge_index\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mF\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlog_softmax\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdim\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m~/.pyenv/versions/3.6.1/lib/python3.6/site-packages/torch/nn/modules/module.py\u001b[0m in \u001b[0;36m__call__\u001b[0;34m(self, *input, **kwargs)\u001b[0m\n\u001b[1;32m 475\u001b[0m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_slow_forward\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0minput\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 476\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 477\u001b[0;31m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mforward\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0minput\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 478\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mhook\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_forward_hooks\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mvalues\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 479\u001b[0m \u001b[0mhook_result\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mhook\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minput\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mresult\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m~/.pyenv/versions/3.6.1/lib/python3.6/site-packages/torch_geometric/nn/conv/gcn_conv.py\u001b[0m in \u001b[0;36mforward\u001b[0;34m(self, x, edge_index, edge_attr)\u001b[0m\n\u001b[1;32m 41\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 42\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mforward\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0medge_index\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0medge_attr\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 43\u001b[0;31m \u001b[0mout\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtorch\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmm\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mweight\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 44\u001b[0m \u001b[0mout\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mprop\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mout\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0medge_index\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0medge_attr\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 45\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mRuntimeError\u001b[0m: Expected object of type torch.FloatTensor but found type torch.cuda.FloatTensor for argument #2 'mat2'"
]
}
],
"source": [
"output=model(data)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"for epoch in range(1, 101):\n",
" output=model(data)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.6",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.1"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
2692
2532
2050
1715
2362
2609
2622
1975
2081
1767
2263
1725
2588
2259
2357
1998
2574
2179
2291
2382
1812
1751
2422
1937
2631
2510
2378
2589
2345
1943
1850
2298
1825
2035
2507
2313
1906
1797
2023
2159
2495
1886
2122
2369
2461
1925
2565
1858
2234
2000
1846
2318
1723
2559
2258
1763
1991
1922
2003
2662
2250
2064
2529
1888
2499
2454
2320
2287
2203
2018
2002
2632
2554
2314
2537
1760
2088
2086
2218
2605
1953
2403
1920
2015
2335
2535
1837
2009
1905
2636
1942
2193
2576
2373
1873
2463
2509
1954
2656
2455
2494
2295
2114
2561
2176
2275
2635
2442
2704
2127
2085
2214
2487
1739
2543
1783
2485
2262
2472
2326
1738
2170
2100
2384
2152
2647
2693
2376
1775
1726
2476
2195
1773
1793
2194
2581
1854
2524
1945
1781
1987
2599
1744
2225
2300
1928
2042
2202
1958
1816
1916
2679
2190
1733
2034
2643
2177
1883
1917
1996
2491
2268
2231
2471
1919
1909
2012
2522
1865
2466
2469
2087
2584
2563
1924
2143
1736
1966
2533
2490
2630
1973
2568
1978
2664
2633
2312
2178
1754
2307
2480
1960
1742
1962
2160
2070
2553
2433
1768
2659
2379
2271
1776
2153
1877
2027
2028
2155
2196
2483
2026
2158
2407
1821
2131
2676
2277
2489
2424
1963
1808
1859
2597
2548
2368
1817
2405
2413
2603
2350
2118
2329
1969
2577
2475
2467
2425
1769
2092
2044
2586
2608
1983
2109
2649
1964
2144
1902
2411
2508
2360
1721
2005
2014
2308
2646
1949
1830
2212
2596
1832
1735
1866
2695
1941
2546
2498
2686
2665
1784
2613
1970
2021
2211
2516
2185
2479
2699
2150
1990
2063
2075
1979
2094
1787
2571
2690
1926
2341
2566
1957
1709
1955
2570
2387
1811
2025
2447
2696
2052
2366
1857
2273
2245
2672
2133
2421
1929
2125
2319
2641
2167
2418
1765
1761
1828
2188
1972
1997
2419
2289
2296
2587
2051
2440
2053
2191
1923
2164
1861
2339
2333
2523
2670
2121
1921
1724
2253
2374
1940
2545
2301
2244
2156
1849
2551
2011
2279
2572
1757
2400
2569
2072
2526
2173
2069
2036
1819
1734
1880
2137
2408
2226
2604
1771
2698
2187
2060
1756
2201
2066
2439
1844
1772
2383
2398
1708
1992
1959
1794
2426
2702
2444
1944
1829
2660
2497
2607
2343
1730
2624
1790
1935
1967
2401
2255
2355
2348
1931
2183
2161
2701
1948
2501
2192
2404
2209
2331
1810
2363
2334
1887
2393
2557
1719
1732
1986
2037
2056
1867
2126
1932
2117
1807
1801
1743
2041
1843
2388
2221
1833
2677
1778
2661
2306
2394
2106
2430
2371
2606
2353
2269
2317
2645
2372
2550
2043
1968
2165
2310
1985
2446
1982
2377
2207
1818
1913
1766
1722
1894
2020
1881
2621
2409
2261
2458
2096
1712
2594
2293
2048
2359
1839
2392
2254
1911
2101
2367
1889
1753
2555
2246
2264
2010
2336
2651
2017
2140
1842
2019
1890
2525
2134
2492
2652
2040
2145
2575
2166
1999
2434
1711
2276
2450
2389
2669
2595
1814
2039
2502
1896
2168
2344
2637
2031
1977
2380
1936
2047
2460
2102
1745
2650
2046
2514
1980
2352
2113
1713
2058
2558
1718
1864
1876
2338
1879
1891
2186
2451
2181
2638
2644
2103
2591
2266
2468
1869
2582
2674
2361
2462
1748
2215
2615
2236
2248
2493
2342
2449
2274
1824
1852
1870
2441
2356
1835
2694
2602
2685
1893
2544
2536
1994
1853
1838
1786
1930
2539
1892
2265
2618
2486
2583
2061
1796
1806
2084
1933
2095
2136
2078
1884
2438
2286
2138
1750
2184
1799
2278
2410
2642
2435
1956
2399
1774
2129
1898
1823
1938
2299
1862
2420
2673
1984
2204
1717
2074
2213
2436
2297
2592
2667
2703
2511
1779
1782
2625
2365
2315
2381
1788
1714
2302
1927
2325
2506
2169
2328
2629
2128
2655
2282
2073
2395
2247
2521
2260
1868
1988
2324
2705
2541
1731
2681
2707
2465
1785
2149
2045
2505
2611
2217
2180
1904
2453
2484
1871
2309
2349
2482
2004
1965
2406
2162
1805
2654
2007
1947
1981
2112
2141
1720
1758
2080
2330
2030
2432
2089
2547
1820
1815
2675
1840
2658
2370
2251
1908
2029
2068
2513
2549
2267
2580
2327
2351
2111
2022
2321
2614
2252
2104
1822
2552
2243
1798
2396
2663
2564
2148
2562
2684
2001
2151
2706
2240
2474
2303
2634
2680
2055
2090
2503
2347
2402
2238
1950
2054
2016
1872
2233
1710
2032
2540
2628
1795
2616
1903
2531
2567
1946
1897
2222
2227
2627
1856
2464
2241
2481
2130
2311
2083
2223
2284
2235
2097
1752
2515
2527
2385
2189
2283
2182
2079
2375
2174
2437
1993
2517
2443
2224
2648
2171
2290
2542
2038
1855
1831
1759
1848
2445
1827
2429
2205
2598
2657
1728
2065
1918
2427
2573
2620
2292
1777
2008
1875
2288
2256
2033
2470
2585
2610
2082
2230
1915
1847
2337
2512
2386
2006
2653
2346
1951
2110
2639
2520
1939
2683
2139
2220
1910
2237
1900
1836
2197
1716
1860
2077
2519
2538
2323
1914
1971
1845
2132
1802
1907
2640
2496
2281
2198
2416
2285
1755
2431
2071
2249
2123
1727
2459
2304
2199
1791
1809
1780
2210
2417
1874
1878
2116
1961
1863
2579
2477
2228
2332
2578
2457
2024
1934
2316
1841
1764
1737
2322
2239
2294
1729
2488
1974
2473
2098
2612
1834
2340
2423
2175
2280
2617
2208
2560
1741
2600
2059
1747
2242
2700
2232
2057
2147
2682
1792
1826
2120
1895
2364
2163
1851
2391
2414
2452
1803
1989
2623
2200
2528
2415
1804
2146
2619
2687
1762
2172
2270
2678
2593
2448
1882
2257
2500
1899
2478
2412
2107
1746
2428
2115
1800
1901
2397
2530
1912
2108
2206
2091
1740
2219
1976
2099
2142
2671
2668
2216
2272
2229
2666
2456
2534
2697
2688
2062
2691
2689
2154
2590
2626
2390
1813
2067
1952
2518
2358
1789
2076
2049
2119
2013
2124
2556
2105
2093
1885
2305
2354
2135
2601
1770
1995
2504
1749
2157
1 32
1 22
1 20
1 18
1 14
1 13
1 12
1 11
1 9
1 8
1 7
1 6
1 5
1 4
1 3
1 2
2 31
2 22
2 20
2 18
2 14
2 8
2 4
2 3
3 14
3 9
3 10
3 33
3 29
3 28
3 8
3 4
4 14
4 13
4 8
5 11
5 7
6 17
6 11
6 7
7 17
9 34
9 33
9 33
10 34
14 34
15 34
15 33
16 34
16 33
19 34
19 33
20 34
21 34
21 33
23 34
23 33
24 30
24 34
24 33
24 28
24 26
25 32
25 28
25 26
26 32
27 34
27 30
28 34
29 34
29 32
30 34
30 33
31 34
31 33
32 34
32 33
33 34
7 0
5 0
11 0
6 0
17 0
12 1
13 1
1 1
18 1
22 1
8 1
4 1
2 1
20 1
14 1
3 2
32 2
10 2
29 2
28 2
26 2
25 2
9 3
31 3
34 3
33 3
21 3
24 3
15 3
16 3
23 3
19 3
30 3
27 3
import numpy as np
from matplotlib import pyplot as plt
import networkx as nx
import torch
import torch.nn.functional as F
import torch_geometric.transforms as T
from torch_geometric.data import Data
from torch_geometric.nn import GCNConv
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
edges = np.loadtxt("../edges.txt", dtype=np.int32)
nodes = np.loadtxt("../nodes.txt", dtype=np.int32)
G = nx.Graph()
for i in range(4):
G.add_nodes_from(nodes[nodes[:, 1] == i][:, 0], labels=i)
G.add_edges_from(edges)
nodes = G.nodes(data=True)
values = []
for i in range(1, G.number_of_nodes() + 1):
values.append(nodes[i]['labels'])
edge_index = torch.from_numpy(np.array(G.edges())).long().t()
y = torch.from_numpy(np.array(values))
# x = torch.eye(G.number_of_nodes()).float()
x = torch.zeros((34, 2)).float()
train_mask = torch.zeros(G.number_of_nodes())
train_mask.data[0] = 1
train_mask.data[2] = 1
train_mask.data[8] = 1
train_mask.data[4] = 1
val_mask = 1 - train_mask
data = Data(x=x, edge_index=edge_index - 1, y=y)
data.train_mask = train_mask.to(torch.uint8).to(device)
data.val_mask = val_mask.to(torch.uint8).to(device)
class Net(torch.nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = GCNConv(data.num_features, 16)
self.conv2 = GCNConv(16, data.num_classes)
def forward(self, data):
x = F.relu(self.conv1(data.x, data.edge_index))
x = self.conv2(x, data.edge_index)
return F.log_softmax(x, dim=1)
model, data = Net().to(device), data.to(device).contiguous()
optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4)
EPOCH = 10
for i in range(EPOCH):
output = model(data)
optimizer.zero_grad()
loss = F.nll_loss(output[data.train_mask], data.y[data.train_mask])
loss.backward()
optimizer.step()
# Validation
logits = model()
mask = data.val_mask
pred = logits[mask].max(1)[1]
acc = pred.eq(data.y[mask]).sum().item() / mask.sum().item()
print(f"Validation Accuracy: {acc}")
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment