First we'll look at a Multi-Layer Perceptron (MLP). An MLP is a very basic neutral network, with a set of fully-connected layers connecting input (PMT information) to output (class prediction). We'll use the script scripts/mlp_training.py to do this. By running this script and following along here, we'll manually go through training through one batch of our input data.
You can run the script by running
python scripts/mlp_training.py
Firs time running, the script will pause, so you can follow along here (similar to previous). Add the option -s to just run training.
This script load the class SimpleMLP, which I encourage you to peruse. We load this class
model_MLP=SimpleMLP(num_classes=3)The code will then print out the names of parameters, and whether they require a gradient to be computed (for a backwards pass):
for name, param in model_MLP.named_parameters():
print("name of a parameter: {}, type: {}, parameter requires a gradient?: {}".
format(name, type(param),param.requires_grad))Can you link all of these parameters to those defined in the SimpleMLP class?
As we can see by default the parameters have requires_grad set - i.e. we will be able to obtain gradient of the loss function with respect to these parameters.
Let's quickly look at the source for the linear module
The parameters descend from the Tensor class. When Parameter object is instantiated as a member of a Module object class the parameter is added to Modules list of parameters automatically.
This list and values are captured in the 'state dictionary' of a module, which is essentially all the weights and biases of the model. This can just be accessed by:
model_MLP.state_dict()Did you notice that the values are not 0? This is actually by design - by default that initialization follows an accepted scheme - but many strategies are possible
Let's load a dataset using our WCH5Dataset class, and use a dataloader
dset=WCH5Dataset("/fast_scratch_1/TRISEP_data/NUPRISM.h5",reduced_dataset_size=100000,val_split=0.1,test_split=0.1)
# Let's make a dataloader and grab a first batch
train_dldr=DataLoader(dset,
batch_size=32,
shuffle=False,
sampler=SubsetRandomSampler(dset.train_indices))
train_iter=iter(train_dldr)Then we can grab the data and labels from the 1st batch of the iterator we just made:
#Set up iterator over training set
train_iter=iter(train_dldr)
#Get the first batch
batch0=next(train_iter)
#Get PMT data from batch
data=batch0[0]
#Get labels from batch
labels=batch0[1]
# Now compute the model output on the data
model_out=model_MLP(data)Now we have model's predictions and we above got 'true' labels from the dataset, so we can now compute the loss - CrossEntropyLoss is the apropropriate one to use here.
We will use CrossEntropyLoss from torch.nn - btw it is also a Module. First create it, then evaluate:
#Define the kind of loss
loss_module=CrossEntropyLoss()
# Now evaluate the loss.
loss_tensor=loss_module(model_out,labels)Now that we've defined a model, we can save a diagram of it to remember our architecture. This can also be used for different architectures later on.
Before we calculate the gradients - let's check what they are now:
#Print out each parameter's gradient
for name, param in model_MLP.named_parameters():
print("name of a parameter: {}, gradient: {}".
format(name, param.grad))This doesn't look right! Don't forget we have to actually do the backwards pass
#Compute gradients by doing backward pass
loss_tensor.backward()
#Print out each parameter's gradient
for name, param in model_MLP.named_parameters():
print("name of a parameter: {}, gradient: {}".
format(name, param.grad))All we have to do now is subtract the gradient of a given parameter from the parameter tensor itself and do it for all parameters of the model - that should decrease the loss. Normally the gradient is multiplied by a learning rate parameter
#Define learning rate
lr=0.0001
#Multiply each parameter by the learning rate times graduent
for param in model_MLP.parameters():
param.data.add_(-lr*param.grad.data)
# call to backward **accumulates** gradients - so we also need to zero the gradient tensors if we want to keep going
for param in model_MLP.parameters():
param.grad.data.zero_()We've now succesfully updated our model parameters by doing a backward pass!
There is a much simpler way of doing this - we can use the pytorch optim classes. i This allows us to easily use more advanced optimization options (like momentum or adaptive optimizers like Adam), no loops over parameters needed:
#Define Optimizer as SGD
optimizer = optim.SGD(model_MLP.parameters(), lr=0.0001)
# Lets get a new batch of events
batch1=next(train_iter)
data=batch1[0]
labels=batch1[1]
#Forward pass
model_out=model_MLP(data)
#Compute loss
loss_tensor=loss_module(model_out,labels)
#Backward pass
loss_tensor.backward()
#Update parameters
optimizer.step()We could just put the code above in a loop and be done with it, but the usual practice would be to wrap this functionality in a training object. Here we'll use the engine class. Let's examine it. These are the general steps the engine abstracts:
- Implementation of the training loop
- Evaluation on validation set and training and test modes.
- Turning evaluation of gradients on and off.
- Saving and retrieving the model and optimizer state.
To run an MLP training session, simply run scripts/mlp_training.py -s; the -s option skips past tutorial to a training with the engine. Especially note the configuration parameters. Some of these settings can be changed - feel free to play around with them! For the settings that affect the training, they are called hyperparameters, and finding optimal ones is called hyperparameter tuning. The dump_path config
config.dump_path = 'model_state_dumps'Will be where the model is saved - a directory with current date-time will be made for every new training.
Most of the configuration parameters should be related to something we've done in this tutorial already, so try to think of what they all mean!
We now put together all we learned, and use the engine with the MLP model class and use its train function to abstract the loop
#Initialize engine
engine=Engine(model_MLP,dset,config)
#Train!
engine.train(epochs=1,report_interval=100,valid_interval=200)Notice the arguments to the train function: epochs is how may times to go through the training data during training, report intervals when to report training performance, and valid interval when to run on an independent validation set to see a more valid performance of the network.
Next let's learn how to evaluate the model we just trained.