From e6693df39be99638093c167586f1e816f01fb7bf Mon Sep 17 00:00:00 2001 From: Krzysztof Rudnicki Date: Sat, 13 May 2023 15:41:16 +0200 Subject: [PATCH] feat: make code pep8 compliant --- .gitignore | 3 + .vscode/extensions.json | 3 +- .vscode/settings.json | 5 +- lab5/code/.pylintrc | 9 ++ lab5/code/main.py | 235 ++++++++++++++++++++++++++++------------ 5 files changed, 186 insertions(+), 69 deletions(-) create mode 100644 lab5/code/.pylintrc diff --git a/.gitignore b/.gitignore index 974ab09c..a7a5ccc1 100644 --- a/.gitignore +++ b/.gitignore @@ -462,3 +462,6 @@ TSWLatexianTemp* # option is specified. Footnotes are the stored in a file with suffix Notes.bib. # Uncomment the next line to have this generated file ignored. #*Notes.bib + +# MNSIT data +data/MNIST/raw \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 2259694d..78857112 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -5,6 +5,7 @@ "mikoz.black-py", "james-yu.latex-workshop", "kisstkondoros.vscode-gutter-preview", - "streetsidesoftware.code-spell-checker" + "streetsidesoftware.code-spell-checker", + "wesbos.theme-cobalt2" ] } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 88071b01..577d9e4c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,8 +5,11 @@ "cSpell.words": [ "dtype", "loadtxt", + "MNIST", "optim", "Xbatch", "ybatch" - ] + ], + "python.linting.pylintEnabled": true, + "python.linting.enabled": true } \ No newline at end of file diff --git a/lab5/code/.pylintrc b/lab5/code/.pylintrc new file mode 100644 index 00000000..e0fd784e --- /dev/null +++ b/lab5/code/.pylintrc @@ -0,0 +1,9 @@ +[TYPECHECK] + +# List of members which are set dynamically and missed by Pylint inference +# system, and so shouldn't trigger E1101 when accessed. (Module 'torch' has no 'max' member) +generated-members=numpy.*, torch.* + +[DESIGN] +# Maximum number of statements in function / method body +max-statements=16 diff --git a/lab5/code/main.py b/lab5/code/main.py index ca9e3da6..c55a724e 100644 --- a/lab5/code/main.py +++ b/lab5/code/main.py @@ -1,92 +1,193 @@ +""" Implementation of a network analyzing MNIST dataset """ import torch -import torch.nn as nn -import torch.optim as optim +from torch import nn +from torch import optim from torchvision import datasets, transforms -# Set random seed for reproducibility -torch.manual_seed(42) -# Define hyperparameters -learning_rate = 0.001 -batch_size = 64 -num_epochs = 10 -input_size = 28 * 28 # MNIST images are 28x28 pixels -hidden_size = 128 -num_classes = 10 +def set_hyperparameters(): + """ sets hyperparameters used throughout the network """ + return { + "learning_rate": 0.001, + "batch_size": 64, + "num_epochs": 2, + "input_size": 28 * 28, # MNIST images are 28x28 pixels + "hidden_size": 128, + "num_classes": 10, + } -# Load MNIST dataset and apply transformations -train_dataset = datasets.MNIST( - root='./data', train=True, transform=transforms.ToTensor(), download=True -) -test_dataset = datasets.MNIST( - root='./data', train=False, transform=transforms.ToTensor(), download=True -) -# Create data loaders -train_loader = torch.utils.data.DataLoader( - dataset=train_dataset, batch_size=batch_size, shuffle=True -) -test_loader = torch.utils.data.DataLoader( - dataset=test_dataset, batch_size=batch_size, shuffle=False -) +def load_datasets(): + """ Loads train and test dataset from MNIST """ + train_dataset = datasets.MNIST( + root="./data", train=True, transform=transforms.ToTensor(), download=True + ) + test_dataset = datasets.MNIST( + root="./data", train=False, transform=transforms.ToTensor(), download=True + ) + return train_dataset, test_dataset -# Define the multilayer perceptron model -model = nn.Sequential( - nn.Linear(input_size, hidden_size), - nn.ReLU(), - nn.Linear(hidden_size, num_classes) -) -# Loss function -criterion = nn.CrossEntropyLoss() +def create_data_loaders(train_dataset, test_dataset, hyperparameters): + """ Create train and test data loaders """ + train_loader = torch.utils.data.DataLoader( + dataset=train_dataset, batch_size=hyperparameters["batch_size"], shuffle=True + ) + test_loader = torch.utils.data.DataLoader( + dataset=test_dataset, batch_size=hyperparameters["batch_size"], shuffle=False + ) + return train_loader, test_loader -# Optimizer -optimizer = optim.Adam(model.parameters(), lr=learning_rate) -# Training loop -for epoch in range(num_epochs): - for batch_idx, (data, targets) in enumerate(train_loader): - # Reshape the input data - data = data.view(data.size(0), -1) - - # Forward pass - outputs = model(data) - loss = criterion(outputs, targets) - - # Backward pass and optimization - optimizer.zero_grad() - loss.backward() - optimizer.step() - - # Print loss value for every learning step - if (batch_idx+1) % 100 == 0: - print(f'Epoch [{epoch+1}/{num_epochs}], Step [{batch_idx+1}/{len(train_loader)}], Loss: {loss.item():.4f}') - - # Calculate accuracy on train set after each epoch +def define_model(hyperparameters): + """ Define the multilayer perceptron training_parameters['model'] """ + model = nn.Sequential( + nn.Linear(hyperparameters["input_size"], + hyperparameters["hidden_size"]), + nn.ReLU(), + nn.Linear(hyperparameters["hidden_size"], + hyperparameters["num_classes"]), + ) + return model + + +def initial_configuration(): + """ + Perform all operations needed for training network + """ + # Set random seed for reproducibility + torch.manual_seed(42) + hyperparameters = set_hyperparameters() + # Load MNIST dataset and apply transformations + train_dataset, test_dataset = load_datasets() + train_loader, test_loader = create_data_loaders( + train_dataset, test_dataset, hyperparameters + ) + model = define_model(hyperparameters) + # Loss function + criterion = nn.CrossEntropyLoss() + # training_parameters['optimizer'] + optimizer = optim.Adam( + model.parameters(), lr=hyperparameters["learning_rate"]) + return hyperparameters, train_loader, test_loader, model, criterion, optimizer + + +def single_train_iteration( + data, training_parameters, targets, batch_idx, epoch +): + """ + Train network for single batch + """ + # Reshape the input data + data = data.view(data.size(0), -1) + # Forward pass + outputs = training_parameters['model'](data) + loss = training_parameters['criterion'](outputs, targets) + # Backward pass and optimization + training_parameters['optimizer'].zero_grad() + loss.backward() + training_parameters['optimizer'].step() + # Print loss value for every learning step + if (batch_idx + 1) % 100 == 0: + print( + f''' + Epoch [{epoch+1}/{training_parameters['hyperparameters']["num_epochs"]}], + Step [{batch_idx+1}/ \ + {len(training_parameters['loaders']['train_loader'])}], + Loss: {loss.item():.4f} + ''' + ) + return data, training_parameters['optimizer'] + + +def set_loaders(train_loader, test_loader): + """ + Put train and test loaders into one object + """ + return { + 'train_loader': train_loader, + 'test_loader': test_loader + } + + +def set_training_parameters(hyperparameters, loaders, model, criterion, optimizer): + """ + Put all training parameters into one object + """ + return { + 'hyperparameters': hyperparameters, + 'loaders': { + 'train_loader': loaders['train_loader'], + 'test_loader': loaders['test_loader'] + }, + 'model': model, + 'criterion': criterion, + 'optimizer': optimizer, + } + + +def training_loop(training_parameters): + """ + Train network for all epochs + """ + epochs_num = training_parameters["hyperparameters"]["num_epochs"] + # Training loop + for epoch in range(epochs_num): + for batch_idx, (data, targets) in enumerate(training_parameters['loaders']['train_loader']): + data, training_parameters['optimizer'] = single_train_iteration( + data, training_parameters, targets, batch_idx, epoch + ) + calculate_accuracy_epoch( + training_parameters, epoch) + calculate_validation_set_accuracy( + training_parameters, epoch) + return epoch, training_parameters['loaders']['train_loader'] + + +def calculate_accuracy_epoch(training_parameters, epoch): + """ Calculate accuracy on train set after each epoch """ correct = 0 total = 0 - for data, targets in train_loader: + for data, targets in training_parameters['loaders']['train_loader']: data = data.view(data.size(0), -1) - outputs = model(data) + outputs = training_parameters['model'](data) _, predicted = torch.max(outputs.data, 1) total += targets.size(0) correct += (predicted == targets).sum().item() - train_accuracy = 100 * correct / total - print(f'Accuracy on Train Set after Epoch {epoch+1}: {train_accuracy:.2f}%') + print( + f"Accuracy on Train Set after Epoch {epoch+1}: {train_accuracy:.2f}%") - # Calculate accuracy on validation set after each epoch + +def calculate_validation_set_accuracy(training_parameters, epoch): + """ Calculate accuracy on validation set after each epoch """ correct = 0 total = 0 - for data, targets in test_loader: + for data, targets in training_parameters['loaders']['test_loader']: data = data.view(data.size(0), -1) - outputs = model(data) + outputs = training_parameters['model'](data) _, predicted = torch.max(outputs.data, 1) total += targets.size(0) correct += (predicted == targets).sum().item() - - validation_accuracy = 100 * correct / total - print(f'Accuracy on Validation Set after Epoch {epoch+1}: {validation_accuracy:.2f}%') - print('---') -# Conclusions and observations can be included in the report \ No newline at end of file + validation_accuracy = 100 * correct / total + print( + f"Accuracy on Validation Set after Epoch {epoch+1}: {validation_accuracy:.2f}%" + ) + print("---") + + +if __name__ == "__main__": + ( + HYPERPARAMETERS, + TRAIN_LOADER, + TEST_LOADER, + MODEL, + CRITERION, + OPTIMIZER, + ) = initial_configuration() + LOADERS = set_loaders( + TRAIN_LOADER, TEST_LOADER) + TRAINING_PARAMETERS = set_training_parameters( + HYPERPARAMETERS, LOADERS, MODEL, CRITERION, OPTIMIZER) + training_loop(TRAINING_PARAMETERS)