#!/usr/bin/env python3 import argparse import torch import torchmetrics import npfl138 npfl138.require_version("2526.1") from npfl138.datasets.mnist import MNIST parser = argparse.ArgumentParser() # These arguments will be set appropriately by ReCodEx, even if you change them. parser.add_argument("--activation", default="none", choices=["none", "relu", "tanh", "sigmoid"], help="Activation.") parser.add_argument("--batch_size", default=50, type=int, help="Batch size.") parser.add_argument("--epochs", default=10, type=int, help="Number of epochs.") parser.add_argument("--hidden_layer_size", default=100, type=int, help="Size of the hidden layer.") parser.add_argument("--hidden_layers", default=1, type=int, help="Number of layers.") parser.add_argument("--recodex", default=False, action="store_true", help="Evaluation in ReCodEx.") parser.add_argument("--seed", default=42, type=int, help="Random seed.") parser.add_argument("--threads", default=1, type=int, help="Maximum number of threads to use.") # If you add more arguments, ReCodEx will keep them with your default values. class Dataset(npfl138.TransformedDataset): def transform(self, example): image = example["image"] # a torch.Tensor with torch.uint8 values in [0, 255] range image = image.to(torch.float32) / 255 # image converted to float32 and rescaled to [0, 1] label = example["label"] # a torch.Tensor with a single integer representing the label return image, label # return an (input, target) pair def main(args: argparse.Namespace) -> dict[str, float]: # Set the random seed and the number of threads. npfl138.startup(args.seed, args.threads, args.recodex) npfl138.global_keras_initializers() # Load the data and create dataloaders. mnist = MNIST() train = torch.utils.data.DataLoader(Dataset(mnist.train), batch_size=args.batch_size, shuffle=True) dev = torch.utils.data.DataLoader(Dataset(mnist.dev), batch_size=args.batch_size) # Create the model. model = torch.nn.Sequential() # TODO: Finish the model. Namely: # - start by adding the `torch.nn.Flatten()` layer; # - then add `args.hidden_layers` number of fully connected hidden layers # `torch.nn.Linear()`, each with `args.hidden_layer_size` neurons and followed by # a specified `args.activation`, allowing "none", "relu", "tanh", "sigmoid"; # - finally, add an output fully connected layer with `MNIST.LABELS` units. ... # Create the TrainableModule and configure it for training. model = npfl138.TrainableModule(model) model.configure( optimizer=torch.optim.Adam(model.parameters()), loss=torch.nn.CrossEntropyLoss(), metrics={"accuracy": torchmetrics.Accuracy("multiclass", num_classes=MNIST.LABELS)}, logdir=npfl138.format_logdir("logs/{file-}{timestamp}{-config}", **vars(args)), ) # Train the model. logs = model.fit(train, dev=dev, epochs=args.epochs) # Return development metrics for ReCodEx to validate. return {metric: value for metric, value in logs.items() if metric.startswith("dev:")} if __name__ == "__main__": main_args = parser.parse_args([] if "__file__" not in globals() else None) main(main_args)