From 79b74672c175156c0b7d86d590c816df96488944 Mon Sep 17 00:00:00 2001 From: AlexandrosChrtn Date: Mon, 31 Jul 2023 19:04:28 +0300 Subject: [PATCH 01/28] Add knowledge_distillation_tutorial --- .../img/knowledge_distillation/ce_only.png | Bin 0 -> 70620 bytes .../cosine_embedding_loss.png | Bin 0 -> 10142 bytes .../cosine_loss_distillation.png | Bin 0 -> 77952 bytes .../distillation_output_loss.png | Bin 0 -> 74755 bytes .../fitnets_knowledge_distill.png | Bin 0 -> 77589 bytes .../knowledge_distillation_pytorch_logo.png | Bin 0 -> 119896 bytes .../knowledge_distillation_tutorial.py | 654 ++++++++++++++++++ index.rst | 8 + 8 files changed, 662 insertions(+) create mode 100644 _static/img/knowledge_distillation/ce_only.png create mode 100644 _static/img/knowledge_distillation/cosine_embedding_loss.png create mode 100644 _static/img/knowledge_distillation/cosine_loss_distillation.png create mode 100644 _static/img/knowledge_distillation/distillation_output_loss.png create mode 100644 _static/img/knowledge_distillation/fitnets_knowledge_distill.png create mode 100644 _static/img/thumbnails/cropped/knowledge_distillation_pytorch_logo.png create mode 100644 beginner_source/knowledge_distillation_tutorial.py diff --git a/_static/img/knowledge_distillation/ce_only.png b/_static/img/knowledge_distillation/ce_only.png new file mode 100644 index 0000000000000000000000000000000000000000..a75037165759c4d20a69fc581e59687e0396833e GIT binary patch literal 70620 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 L(DCeC-^l*}_Q!Z+ literal 0 HcmV?d00001 diff --git a/_static/img/knowledge_distillation/distillation_output_loss.png b/_static/img/knowledge_distillation/distillation_output_loss.png new file mode 100644 index 0000000000000000000000000000000000000000..f86cbddbdfd889aa7d3921abfbaf51182a748557 GIT binary patch literal 74755 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 e_J2F|aNaCF(d^9{DdC4rE^d literal 0 HcmV?d00001 diff --git a/_static/img/thumbnails/cropped/knowledge_distillation_pytorch_logo.png b/_static/img/thumbnails/cropped/knowledge_distillation_pytorch_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..3ce4078154202661a04bf0fbfd6fdaf17fb43e19 GIT binary patch literal 119896 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 Y|Fay$bR$%~sQ>@~07*qoM6N<$f>DmDw*UYD literal 0 HcmV?d00001 diff --git a/beginner_source/knowledge_distillation_tutorial.py b/beginner_source/knowledge_distillation_tutorial.py new file mode 100644 index 00000000000..e44926d19d9 --- /dev/null +++ b/beginner_source/knowledge_distillation_tutorial.py @@ -0,0 +1,654 @@ +# -*- coding: utf-8 -*- +""" +Knowledge Distillation (KD) Tutorial +=============================== +**Author**: `Alexandros Chariton `_ +""" + +###################################################################### +# This tutorial focuses on the inclusion of additional loss function along with the traditional classification loss, Cross-Entropy. We will be using a popular image dataset, CIFAR-10 and the rest of the code should be easy to run with +# a simple GPU (4GB of memory should be more than enough). We will be running a number of experiments focused at improving the accuracy of a lightweight neural network, using a more powerful network as a teacher. +# The computational cost and the speed of the lightweight network will remain unaffected, our intervention only focuses on its weights, not on its forward pass. +# Applications of this technology can be found in devices such as drones or mobile phones. We do not need any external packages, we have everything we need in torch(vision). +# +# The script will download the dataset and save it in a directory called data. + +import torch +import torch.nn as nn +import torch.optim as optim +import torchvision.transforms as transforms +import torchvision.datasets as datasets + +# Ignore warnings +import warnings +warnings.filterwarnings("ignore") + +# Check if GPU is available, and if not, use the CPU +device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + +###################################################################### +# CIFAR-10 is a popular image dataset with 10 classes. Our objective is to predict one of the following classes for each input image. +# +# .. figure:: /../_static/img/cifar10.png +# :align: center +# +# Example of CIFAR-10 images +# +# The input images are RGB so 3 channels and ``32x32`` pixels. Basically each image is described by ``3 * 32 * 32 = 3072`` numbers from 0 to 255. +# A common practice in NNs is to normalize the input. This is done for multiple reasons like avoiding saturation in common activation functions or increased numerical stability. Our Normalization consists of subtracting the mean and dividing by the standard deviation, along each channel. +# The tensors ``mean=[0.485, 0.456, 0.406]`` and ``std=[0.229, 0.224, 0.225]`` were precomputed and they are the mean / std of each channel in the predefined subset of CIFAR-10 meant to be the train set. Notice how we use these numbers to the test set too and we do not compute the mean / std of the test set from scratch. +# This is because the network was trained on features produced by the subtraction and division of the numbers above and we want to be consistent. Furthermore, in real life we would not be able to compute the mean and std of the test set, because we are operating under the assumption that this data would not be accessible at this point. +# +# As a closing point, we often refer to this held out set as the validation set and we use a separate set, called test set after we optimize a model's performance on the validation set, to avoid selecting a model based on the greedy and biased optimization of a single metric. + +# Data preprocessing for CIFAR-10. We use an arbitrary batch size of 128. +transform_train = transforms.Compose([ + transforms.ToTensor(), + transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), +]) + +# Data preprocessing for CIFAR-10 +transform_test = transforms.Compose([ + transforms.ToTensor(), + transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), +]) +# Load CIFAR-10 dataset +train_dataset = datasets.CIFAR10(root='./data', train=True, download=True, transform=transform_train) +test_dataset = datasets.CIFAR10(root='./data', train=False, download=True, transform=transform_test) + +######################################################################## +# .. note:: This section is for CPU users only who are interested in quick results. Only use this if you're interested in a small scale experiment. Keep in mind the code should run fairly fast using any GPU. Select only the first ``num_images_to_keep`` images from the train / test dataset +# +# .. code-block:: python +# +# #from torch.utils.data import Subset +# #num_images_to_keep = 2000 +# #train_dataset = Subset(train_dataset, range(min(num_images_to_keep, 50_000))) +# #test_dataset = Subset(test_dataset, range(min(num_images_to_keep, 10_000))) + +#Dataloaders +train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=128, shuffle=True, num_workers=2) +test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=128, shuffle=False, num_workers=2) + +###################################################################### +# Moving forward we need to define our model classes. A number of user defined parameters needs to be set here. We use 2 different architectures, keeping the number of filters fixed across our experiments to ensure fair comparisons. +# Both architectures are Convolutional Neural Networks (CNNs) with a different number of convolutional layers that work as feature extractors followed by a classifier, with 10 classes. +# The number of filters and neurons is smaller for the students. + +# Deeper NN class to be used as teacher +class DeepNN(nn.Module): + def __init__(self, num_classes=10): + super(DeepNN, self).__init__() + self.features = nn.Sequential( + nn.Conv2d(3, 128, kernel_size=3, padding=1), + nn.ReLU(), + nn.Conv2d(128, 64, kernel_size=3, padding=1), + nn.ReLU(), + nn.MaxPool2d(kernel_size=2, stride=2), + nn.Conv2d(64, 64, kernel_size=3, padding=1), + nn.ReLU(), + nn.Conv2d(64, 32, kernel_size=3, padding=1), + nn.ReLU(), + nn.MaxPool2d(kernel_size=2, stride=2), + ) + self.classifier = nn.Sequential( + nn.Linear(2048, 512), + nn.ReLU(), + nn.Dropout(0.1), + nn.Linear(512, num_classes) + ) + + def forward(self, x): + x = self.features(x) + x = torch.flatten(x, 1) + x = self.classifier(x) + return x + +# Lightweight NN class to be used as student +class LightNN(nn.Module): + def __init__(self, num_classes=10): + super(LightNN, self).__init__() + self.features = nn.Sequential( + nn.Conv2d(3, 16, kernel_size=3, padding=1), + nn.ReLU(), + nn.MaxPool2d(kernel_size=2, stride=2), + nn.Conv2d(16, 16, kernel_size=3, padding=1), + nn.ReLU(), + nn.MaxPool2d(kernel_size=2, stride=2), + ) + self.classifier = nn.Sequential( + nn.Linear(1024, 256), + nn.ReLU(), + nn.Dropout(0.1), + nn.Linear(256, num_classes) + ) + + def forward(self, x): + x = self.features(x) + x = torch.flatten(x, 1) + x = self.classifier(x) + return x + +###################################################################### +# We employ 2 functions to help us produce and evaluate the results on our original classification task. +# One function called train with the following arguments: +# +# - ``model``: A model instance that is to be trained (update its weights) via this function +# - ``train_loader``: We defined our train_loader above, its job is to feed the data into the model +# - ``epochs``: How many times we loop over the dataset +# - ``learning_rate``: How large do we want the steps to be towards convergence. Too large / small steps can be bad +# - ``device``: CPU or cuda (GPU) +# +# Our test function is similar, but it will be invoked with our ``test_loader``, to load images from our test set. +# +# .. figure:: /../_static/img/knowledge_distillation/ce_only.png +# :align: center +# +# Train both networks with Cross-Entropy. The student will be used as a baseline +# + +def train(model, train_loader, epochs, learning_rate, device): + criterion = nn.CrossEntropyLoss() + optimizer = optim.Adam(model.parameters(), lr=learning_rate) + + model.to(device) + model.train() + + for epoch in range(epochs): + running_loss = 0.0 + for inputs, labels in train_loader: + # inputs: A collection of batch_size images + # labels: A vector of dimensionality batch_size with integers denoting class of each image + inputs, labels = inputs.to(device), labels.to(device) + + optimizer.zero_grad() + outputs = model(inputs) + + # outputs: Output of the network for the collection of images. A vector of dimensionality batch_size + # labels: The actual labels of the images. Vector of dimensionality batch_size + loss = criterion(outputs, labels) + loss.backward() + optimizer.step() + + running_loss += loss.item() + + print(f"Epoch {epoch+1}/{epochs}, Loss: {running_loss / len(train_loader)}") + +def test(model, test_loader, device): + model.to(device) + model.eval() + + correct = 0 + total = 0 + + with torch.no_grad(): + for inputs, labels in test_loader: + inputs, labels = inputs.to(device), labels.to(device) + + outputs = model(inputs) + _, predicted = torch.max(outputs.data, 1) + + total += labels.size(0) + correct += (predicted == labels).sum().item() + + accuracy = 100 * correct / total + print(f"Test Accuracy: {accuracy:.2f}%") + return accuracy + +###################################################################### +# For reproducibility we need to set the torch manual seed. We train networks with different methods, so to compare them, it makes sense we need to initialize the networks with the same weights to ensure fair comparisons. +# Start by training the teacher network using cross-entropy. + +torch.manual_seed(42) +nn_deep = DeepNN(num_classes=10) +train(nn_deep, train_loader, epochs=10, learning_rate=0.001, device=device) +test_accuracy_deep = test(nn_deep, test_loader, device) + +# Instantiate the lightweight nn model +torch.manual_seed(42) +nn_light = LightNN(num_classes=10) + +###################################################################### +# We instantiate one more lightweight NN model to compare their performances. +# Back propagation is sensitive to weight initialization, so we need to make sure these two networks have the exact same initialization. + +torch.manual_seed(42) +new_nn_light = LightNN(num_classes=10) + +###################################################################### +# To make sure we created a copy of the first network we inspect the norm of their first layer. If it matches then we are safe because the networks are indeed the same. + +# Print the norm of the first layer of the initial lightweight nn model +print("Norm of 1st layer of nn_light:", torch.norm(nn_light.features[0].weight).item()) +# Print the norm of the first layer of the new lightweight nn model +print("Norm of 1st layer of new_nn_light:", torch.norm(new_nn_light.features[0].weight).item()) + +###################################################################### +# Print the total number of parameters in each model. +total_params_deep = "{:,}".format(sum(p.numel() for p in nn_deep.parameters())) +print(f"DeepNN parameters: {total_params_deep}") +total_params_light = "{:,}".format(sum(p.numel() for p in nn_light.parameters())) +print(f"LightNN parameters: {total_params_light}") + +###################################################################### +# Train and test the lightweight network with cross entropy loss +train(nn_light, train_loader, epochs=10, learning_rate=0.001, device=device) +test_accuracy_light_ce = test(nn_light, test_loader, device) + +###################################################################### +# As we can see, based on test accuracy, we can now compare the deeper network that is to be used as a teacher with the lightweight network that is our supposed student. So far, our student did not intervene with the teacher, therefore this performance is achieved by the student itself. +# The metrics so far can be seen with the following lines: + +print(f"Teacher accuracy: {test_accuracy_deep:.2f}%") +print(f"Student accuracy: {test_accuracy_light_ce:.2f}%") + +###################################################################### +# Now let's try to improve the test accuracy of the student network by somehow including the teacher. Knowledge distillation is the most straightforward technique to that end and it is based on the fact that both of these networks output a probability distribution over our classes. Therefore, the two networks share the exact same number of output neurons. +# The method works by including an additional loss to the traditional cross entropy loss that is based on the softmax output of the teacher network. It is assumed that the output activations of the properly trained teacher carry additional information that can be exploited by a student network while the latter is training. +# The original work argues that making use of ratios of smaller probabilities in the soft targets can help with the underlying objective of deep neural networks, which is creating a similarity structure over the data, in which we would expect similar objects to be mapped closer together. As an example for CIFAR-10, a truck could be mistaken for an automobile or even an airplane with its wheels present, but it is less likely to be mistaken for a dog. +# Therefore it makes sense to assume that valuable information resides not only on the top prediction of a properly trained model, but on the entire output distribution. This information is not exploited by cross entropy sufficiently well because the activations for the non predicted classes tend to be so small that propagated gradients do not meaningfully change the weights to construct this desirable vector space. +# We do not dive deeper into KD as the purpose of this tutorial is to showcase how one would handle multiple loss functions. +# +# We continue by defining our first helper function that introduces a teacher-student dynamic. We need to include a few extra parameters: +# +# - ``T``: Temperature controls the smoothness of the output distributions. Larger T leads to smoother distributions, thus smaller probabilities get a larger boost. +# - ``soft_target_loss_weight``: A weight assigned to the extra objective we're about to include. +# - ``ce_loss_weight``: A weight assigned to cross-entropy. Tuning these weights pushes the network towards optimizing for either objective. +# +# .. figure:: /../_static/img/knowledge_distillation/distillation_output_loss.png +# :align: center +# +# Distillation loss is calcluated from the logits of the networks. It only returns gradients to the student +# + +def train_knowledge_distillation(teacher, student, train_loader, epochs, learning_rate, T, soft_target_loss_weight, ce_loss_weight, device): + ce_loss = nn.CrossEntropyLoss() + optimizer = optim.Adam(student.parameters(), lr=learning_rate) + + teacher.to(device) + student.to(device) + teacher.eval() # Teacher set to eval mode + student.train() # Student to train mode + + for epoch in range(epochs): + running_loss = 0.0 + for inputs, labels in train_loader: + inputs, labels = inputs.to(device), labels.to(device) + + optimizer.zero_grad() + + # Forward pass with the teacher model - do not save gradients here as we do not change the teacher's weights + with torch.no_grad(): + teacher_logits = teacher(inputs) + + # Forward pass with the student model + student_logits = student(inputs) + + #Soften the student logits by applying softmax first and log() second + soft_targets = nn.functional.softmax(teacher_logits / T, dim=-1) + soft_prob = nn.functional.log_softmax(student_logits / T, dim=-1) + + # Calculate the soft targets loss. Scaled by T**2 as suggested by the authors of KD + soft_targets_loss = -torch.sum(soft_targets * soft_prob) / soft_prob.size()[0] * (T**2) + + # Calculate the true label loss + label_loss = ce_loss(student_logits, labels) + + # Weighted sum of the two losses + loss = soft_target_loss_weight * soft_targets_loss + ce_loss_weight * label_loss + + loss.backward() + optimizer.step() + + running_loss += loss.item() + + print(f"Epoch {epoch+1}/{epochs}, Loss: {running_loss / len(train_loader)}") + +# Apply train_knowledge_distillation with a temperature of 2. Arbitrarily set the weights to 0.75 for CE and 0.25 for distillation loss. +train_knowledge_distillation(teacher=nn_deep, student=new_nn_light, train_loader=train_loader, epochs=10, learning_rate=0.001, T=2, soft_target_loss_weight=0.25, ce_loss_weight=0.75, device=device) +test_accuracy_light_ce_and_kd = test(new_nn_light, test_loader, device) + +# Compare the student test accuracy with and without the teacher, after introducing simple KD. +print(f"Teacher accuracy: {test_accuracy_deep:.2f}%") +print(f"Student accuracy without teacher: {test_accuracy_light_ce:.2f}%") +print(f"Student accuracy with CE + KD: {test_accuracy_light_ce_and_kd:.2f}%") + +###################################################################### +# Feel free to play around with the temperature parameter that controls the softness of the softmax function and the loss coefficients. +# It is common practice in NNs to include additional loss functions to the main classification objectives to achieve goals like better generalization. +# Let us try including an objective for the student, but now let us focus on their hidden states rather than their output layers. In the previous example, the teacher's representation after the convolutional layers has shape: +# +# ``(batch_size, num_filters_for_last_conv_layer, 8, 8)`` +# +# Same for the student, with the only exception being the number of filters, where here we have fewer filters. +# +# Our goal is to convey information from the teacher's representation to the student by including a naive loss, whose minimization implies that the flattened vectors that are subsequently passed to the classifiers have become more "similar" as the loss decreases. +# Of course the teacher does not update its weights, so the minimization depends only on the student's weights. The rationale behind this method is that we are operating under the assumption that the teacher model has a better internal representation that is +# unlikely to be achieved by the student without external intervention, therefore we artificially push the student to mimic the internal representation of the teacher. Whether or not this will end up helping the student is not straightforward though, because pushing the lightweight network +# to reach this point could be a good thing, assuming that we have found an internal representation that leads to better test accuracy, but it could also be harmful because the networks have different architectures and the student does not have the same learning capacity as the teacher. +# In other words, there is no reason for these two vectors, the student's and the teacher's to match per component. The student could reach an internal representation that is a permutation of the teacher's and it would be just as efficient. +# Nonetheless, we can still run a quick experiment to figure out the impact of this method. We will be using the CosineEmbeddingLoss which is given by the formula: +# +# .. figure:: /../_static/img/knowledge_distillation/cosine_embedding_loss.png +# :align: center +# :width: 450px +# +# Formula for CosineEmbeddingLoss +# +# Obviously, there is one thing that we need to resolve first. When we applied KD to the output layer we mentioned that both networks have the same number of neurons, equal to the number of classes. This is not the case for the layer following our convolutional layers. Here, the teacher has more neurons than the student +# after the flattening of the final convolutional layer. Our loss accepts 2 vectors of equal dimensionality as inputs, therefore we need to somehow match them. We will solve this by including an AvgPooling layer after the teacher's convolutional layer to reduce its dimensionality to match the one of the student. +# +# To proceed we will modify our model classes, or create new ones. +# Now the forward function returns not only the logits of the network but also the flattened hidden representation after the convolutional layer. We include the aforementioned pooling for the modified teacher. + +class ModifiedDeepNNCosine(nn.Module): + def __init__(self, num_classes=10): + super(ModifiedDeepNNCosine, self).__init__() + self.features = nn.Sequential( + nn.Conv2d(3, 128, kernel_size=3, padding=1), + nn.ReLU(), + nn.Conv2d(128, 64, kernel_size=3, padding=1), + nn.ReLU(), + nn.MaxPool2d(kernel_size=2, stride=2), + nn.Conv2d(64, 64, kernel_size=3, padding=1), + nn.ReLU(), + nn.Conv2d(64, 32, kernel_size=3, padding=1), + nn.ReLU(), + nn.MaxPool2d(kernel_size=2, stride=2), + ) + self.classifier = nn.Sequential( + nn.Linear(2048, 512), + nn.ReLU(), + nn.Dropout(0.1), + nn.Linear(512, num_classes) + ) + + def forward(self, x): + x = self.features(x) + flattened_conv_output = torch.flatten(x, 1) + x = self.classifier(flattened_conv_output) + flattened_conv_output_after_pooling = torch.nn.functional.avg_pool1d(flattened_conv_output, 2) + return x, flattened_conv_output_after_pooling + +# Create a similar student class where we return a tuple. We do not apply pooling after flattening. +class ModifiedLightNNCosine(nn.Module): + def __init__(self, num_classes=10): + super(ModifiedLightNNCosine, self).__init__() + self.features = nn.Sequential( + nn.Conv2d(3, 16, kernel_size=3, padding=1), + nn.ReLU(), + nn.MaxPool2d(kernel_size=2, stride=2), + nn.Conv2d(16, 16, kernel_size=3, padding=1), + nn.ReLU(), + nn.MaxPool2d(kernel_size=2, stride=2), + ) + self.classifier = nn.Sequential( + nn.Linear(1024, 256), + nn.ReLU(), + nn.Dropout(0.1), + nn.Linear(256, num_classes) + ) + + def forward(self, x): + x = self.features(x) + flattened_conv_output = torch.flatten(x, 1) + x = self.classifier(flattened_conv_output) + return x, flattened_conv_output + +# We do not have to train the modified DNN from scratch of course, we just load its weights from the trained instance +modified_nn_deep = ModifiedDeepNNCosine(num_classes=10) +modified_nn_deep.load_state_dict(nn_deep.state_dict()) + +# Once again ensure the norm of the first layer is the same for both networks +print("Norm of 1st layer for deep_nn:", torch.norm(nn_deep.features[0].weight).item()) +print("Norm of 1st layer for modified_deep_nn:", torch.norm(modified_nn_deep.features[0].weight).item()) + +# Initialize a ModifiedLightNN with the same seed as our other lightweight instances. This will be trained from scratch to examine the effectiveness of cosine loss minimization. +torch.manual_seed(42) +modified_light_nn = ModifiedLightNNCosine(num_classes=10) +print("Norm of 1st layer:", torch.norm(modified_light_nn.features[0].weight).item()) + +###################################################################### +# Naturally, we need to change the train loop because now the model returns a tuple ``(logits, hidden_representation)`` or to be more precise +# +# ``logits``: (``batch_size x total_classes``) +# +# ``hidden_representation``: (``batch_size x hidden_representation_size``) +# +# In our case ``hidden_representation_size`` is ``1024``. This is the flattened feature map of the final convolutional layer of the student and as you can see it is the input for its classifier. +# It is ``1024`` for the teacher too, because we made it so with ``avg_pool1d`` from ``2048``. The loss applied here only affects the weights of the student prior to the loss caclulation. In other words it does not affect the classifier of the student. +# The modified training loop is the following. +# +# .. figure:: /../_static/img/knowledge_distillation/cosine_loss_distillation.png +# :align: center +# +# In CosineLoss minimization we want to maximize the cosine similarity of the two representations by returning gradients to the student +# + +def train_cosine_loss(teacher, student, train_loader, epochs, learning_rate, hidden_rep_loss_weight, ce_loss_weight, device): + ce_loss = nn.CrossEntropyLoss() + cosine_loss = nn.CosineEmbeddingLoss() + optimizer = optim.Adam(student.parameters(), lr=learning_rate) + + teacher.to(device) + student.to(device) + teacher.eval() # Teacher set to eval mode + student.train() # Student to train mode + + for epoch in range(epochs): + running_loss = 0.0 + for inputs, labels in train_loader: + inputs, labels = inputs.to(device), labels.to(device) + + optimizer.zero_grad() + + # Forward pass with the teacher model and keep only the hidden representation + with torch.no_grad(): + _, teacher_hidden_representation = teacher(inputs) + + # Forward pass with the student model + student_logits, student_hidden_representation = student(inputs) + + # Calculate the cosine loss + # target is a vector of ones. From the CosineEmbeddingLoss formula above we can see that is the case where loss minimization leads to cosine similarity increase, which is desirable. + hidden_rep_loss = cosine_loss(student_hidden_representation, teacher_hidden_representation, target=torch.ones(inputs.size(0)).to(device)) + + # Calculate the true label loss + label_loss = ce_loss(student_logits, labels) + + # Weighted sum of the two losses + loss = hidden_rep_loss_weight * hidden_rep_loss + ce_loss_weight * label_loss + + loss.backward() + optimizer.step() + + running_loss += loss.item() + + print(f"Epoch {epoch+1}/{epochs}, Loss: {running_loss / len(train_loader)}") + +###################################################################### +#We need to modify our test function for the same reason. Here we ignore the hidden representation returned by the model. + +def test_multiple_outputs(model, test_loader, device): + model.to(device) + model.eval() + + correct = 0 + total = 0 + + with torch.no_grad(): + for inputs, labels in test_loader: + inputs, labels = inputs.to(device), labels.to(device) + + outputs, _ = model(inputs) # In this line we disregard the second tensor of the tuple + _, predicted = torch.max(outputs.data, 1) + + total += labels.size(0) + correct += (predicted == labels).sum().item() + + accuracy = 100 * correct / total + print(f"Test Accuracy: {accuracy:.2f}%") + return accuracy + +###################################################################### +# In this case we could easily include both KD and cosine loss minimization in the same function. It is common to combine methods to achieve better performance in teacher-student paradigms. +# For now we can run a simple train-test session. + +#Train and test the lightweight network with cross entropy loss +train_cosine_loss(teacher=modified_nn_deep, student=modified_light_nn, train_loader=train_loader, epochs=10, learning_rate=0.001, hidden_rep_loss_weight=0.25, ce_loss_weight=0.75, device=device) +test_accuracy_light_ce_and_cosine_loss = test_multiple_outputs(modified_light_nn, test_loader, device) + +###################################################################### +# Our naive minimization does not guarantee better results for a number of reasons, one being the dimensionality of the vectors. Cosine similarity in general works better than Euclidean distance for vectors of larger dimensionality, but we were dealing with vectors with ``1024`` components each, so it is a lot harder to extract meaningful similarities. +# Furthermore as we mentioned, pushing towards a match of the hidden representation of the teacher and the student is not supported by theory. There are no good reasons why we should be aiming for an 1-1 match of these vectors. +# We will provide a final example of training intervention, by including an extra network called regressor. Our intention is to extract the feature map of the teacher after a convolutional layer, extract a feature map of the student after a convolutional layer and try to match these maps, but this time include a regressor between the networks. +# The regressor will be trainable and ideally will do a better job than our naive cosine loss minimization scheme. Its main job is to match the dimensionality of these feature maps so that we can properly define a loss function between the teacher and the student. Defining such loss function provides a teaching "path", which is basically a flow to back-propagate gradients, that will change the student's weights. +# Focusing on the output of the conv layers right before each classifier we have shapes: +# +# ``torch.Size([batch_size, 32, 8, 8])`` +# +# ``torch.Size([batch_size, 16, 8, 8])`` +# +# so we have 32 filters for the teacher and 16 filters for the student. We will include a trainable layer that converts the feature map of the student to the shape of the feature map of the teacher. +# In practice, we modify the lightweight class to return the hidden state after an intermediate regressor that matches the sizes of the convolutional feature maps and the teacher class to return the output of the final convolutional layer without pooling or flattening. +# +# .. figure:: /../_static/img/knowledge_distillation/fitnets_knowledge_distill.png +# :align: center +# +# The trainable layer matches the shapes of the intermediate tensors and MSE is properly defined +# + +class ModifiedDeepNNRegressor(nn.Module): + def __init__(self, num_classes=10): + super(ModifiedDeepNNRegressor, self).__init__() + self.features = nn.Sequential( + nn.Conv2d(3, 128, kernel_size=3, padding=1), + nn.ReLU(), + nn.Conv2d(128, 64, kernel_size=3, padding=1), + nn.ReLU(), + nn.MaxPool2d(kernel_size=2, stride=2), + nn.Conv2d(64, 64, kernel_size=3, padding=1), + nn.ReLU(), + nn.Conv2d(64, 32, kernel_size=3, padding=1), + nn.ReLU(), + nn.MaxPool2d(kernel_size=2, stride=2), + ) + self.classifier = nn.Sequential( + nn.Linear(2048, 512), + nn.ReLU(), + nn.Dropout(0.1), + nn.Linear(512, num_classes) + ) + + def forward(self, x): + x = self.features(x) + conv_feature_map = x + x = torch.flatten(x, 1) + x = self.classifier(x) + return x, conv_feature_map + +class ModifiedLightNNRegressor(nn.Module): + def __init__(self, num_classes=10): + super(ModifiedLightNNRegressor, self).__init__() + self.features = nn.Sequential( + nn.Conv2d(3, 16, kernel_size=3, padding=1), + nn.ReLU(), + nn.MaxPool2d(kernel_size=2, stride=2), + nn.Conv2d(16, 16, kernel_size=3, padding=1), + nn.ReLU(), + nn.MaxPool2d(kernel_size=2, stride=2), + ) + # Include an extra NN regressor (linear projection) + self.regressor = nn.Sequential( + nn.Conv2d(16, 32, kernel_size=3, padding=1) + ) + self.classifier = nn.Sequential( + nn.Linear(1024, 256), + nn.ReLU(), + nn.Dropout(0.1), + nn.Linear(256, num_classes) + ) + + def forward(self, x): + x = self.features(x) + regressor_output = self.regressor(x) + x = torch.flatten(x, 1) + x = self.classifier(x) + return x, regressor_output + +###################################################################### +# We have to update our train loop again. This time, we extract the regressor output of the student, the feature map of the teacher, we calculate the MSE on these tensors (they have the exact same shape so it's properly defined) and we back propagate gradients based on that loss, in addition to the regular cross entropy loss of the classification task. + +def train_mse_loss(teacher, student, train_loader, epochs, learning_rate, feature_map_weight, ce_loss_weight, device): + ce_loss = nn.CrossEntropyLoss() + mse_loss = nn.MSELoss() + optimizer = optim.Adam(student.parameters(), lr=learning_rate) + + teacher.to(device) + student.to(device) + teacher.eval() # Teacher set to eval mode + student.train() # Student to train mode + + for epoch in range(epochs): + running_loss = 0.0 + for inputs, labels in train_loader: + inputs, labels = inputs.to(device), labels.to(device) + + optimizer.zero_grad() + + # Again ignore teacher logits + with torch.no_grad(): + _, teacher_feature_map = teacher(inputs) + + # Forward pass with the student model + student_logits, regressor_feature_map = student(inputs) + + # Calculate the MSE loss + hidden_rep_loss = mse_loss(regressor_feature_map, teacher_feature_map) + + # Calculate the true label loss + label_loss = ce_loss(student_logits, labels) + + # Weighted sum of the two losses + loss = feature_map_weight * hidden_rep_loss + ce_loss_weight * label_loss + + loss.backward() + optimizer.step() + + running_loss += loss.item() + + print(f"Epoch {epoch+1}/{epochs}, Loss: {running_loss / len(train_loader)}") + +# Notice how our test function remains the same here with the one we used in our previous case. We only care about the actual outputs because we measure accuracy. + +# Initialize a ModifiedLightNNRegressor +torch.manual_seed(42) +modified_light_nn_mse = ModifiedLightNNRegressor(num_classes=10) + +# We do not have to train the modified DNN from scratch of course, we just load its weights from the trained instance +modified_nn_deep_reg = ModifiedDeepNNRegressor(num_classes=10) +modified_nn_deep_reg.load_state_dict(nn_deep.state_dict()) + +# Train and test once again +train_mse_loss(teacher=modified_nn_deep_reg, student=modified_light_nn_mse, train_loader=train_loader, epochs=10, learning_rate=0.001, feature_map_weight=0.25, ce_loss_weight=0.75, device=device) +test_accuracy_light_ce_and_mse_loss = test_multiple_outputs(modified_light_nn_mse, test_loader, device) + +###################################################################### +# It is expected for the final method to work better than CosineLoss because now, we allowed a trainable layer between the teacher and the student, which gives the student some wiggle room when it comes to learning, rather than pushing the student to copy the teacher's representation. +# Including the extra network is the idea behind hint based distillation, proposed in the original FitNets paper. + +print(f"Teacher accuracy: {test_accuracy_deep:.2f}%") +print(f"Student accuracy without teacher: {test_accuracy_light_ce:.2f}%") +print(f"Student accuracy with CE + KD: {test_accuracy_light_ce_and_kd:.2f}%") +print(f"Student accuracy with CE + CosineLoss: {test_accuracy_light_ce_and_cosine_loss:.2f}%") +print(f"Student accuracy with CE + RegressorMSE: {test_accuracy_light_ce_and_mse_loss:.2f}%") + +###################################################################### +# None of the methods above increases the number of parameters for the network or inference time, so the performance increase comes at the little cost of calculating gradients during training. In ML applications we mostly care about inference time because training happens before the model deployment. +# If our lightweight model is still too heavy for deployment we can apply different ideas such as post training quantization. Additional losses can be applied in many tasks, not just classification and one can experiment with quantities like coefficients, temperature or number of neurons. +# Feel free to tune any numbers in the tutorial above, but keep in mind, if you change the number of neurons / filters chances are a shape mismatch might occur. +# +# References: +# `Hinton, G., Vinyals, O., Dean, J.: Distilling the knowledge in a neural network. In: Neural Information Processing System Deep Learning Workshop (2015) `_ +# +# `Romero, A., Ballas, N., Kahou, S.E., Chassang, A., Gatta, C., Bengio, Y.: Fitnets: Hints for thin deep nets. In: Proceedings of the International Conference on Learning Representations (2015) `_ diff --git a/index.rst b/index.rst index 654687ff51c..1d06d31bb4d 100644 --- a/index.rst +++ b/index.rst @@ -585,6 +585,13 @@ What's new in PyTorch tutorials? :image: _static/img/thumbnails/cropped/pytorch-logo.png :link: intermediate/scaled_dot_product_attention_tutorial.html :tags: Model-Optimization,Attention,Transformer + +.. customcarditem:: + :header: Knowledge Distillation in Convolutional Neural Networks + :card_description: Learn how to improve the accuracy of lightweight models using more powerful models as teachers. + :image: _static/img/thumbnails/cropped/knowledge_distillation_pytorch_logo.png + :link: beginner/knowledge_distillation_tutorial.html + :tags: Model-Optimization,Image/Video .. Parallel-and-Distributed-Training @@ -979,6 +986,7 @@ Additional Resources intermediate/torch_compile_tutorial intermediate/inductor_debug_cpu intermediate/scaled_dot_product_attention_tutorial + beginner/knowledge_distillation_tutorial .. toctree:: :maxdepth: 2 From 137b3130fa757061ca95eb0ff7aad009f4d3f586 Mon Sep 17 00:00:00 2001 From: AlexandrosChrtn Date: Tue, 8 Aug 2023 16:47:45 +0300 Subject: [PATCH 02/28] Include description above and fix pyspelling --- .../knowledge_distillation_tutorial.py | 70 ++++++++++--------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/beginner_source/knowledge_distillation_tutorial.py b/beginner_source/knowledge_distillation_tutorial.py index e44926d19d9..44428bbd63c 100644 --- a/beginner_source/knowledge_distillation_tutorial.py +++ b/beginner_source/knowledge_distillation_tutorial.py @@ -1,13 +1,17 @@ # -*- coding: utf-8 -*- """ -Knowledge Distillation (KD) Tutorial +Knowledge Distillation Tutorial =============================== **Author**: `Alexandros Chariton `_ """ ###################################################################### -# This tutorial focuses on the inclusion of additional loss function along with the traditional classification loss, Cross-Entropy. We will be using a popular image dataset, CIFAR-10 and the rest of the code should be easy to run with -# a simple GPU (4GB of memory should be more than enough). We will be running a number of experiments focused at improving the accuracy of a lightweight neural network, using a more powerful network as a teacher. +# From this tutorial one can learn: +# - How to modify model classes to extract hidden representations and use them for further calculations +# - How to modify regular train loops in pytorch to include additional losses on top of, for example, cross-entropy for classification +# - How to improve the performance of lightweight models, using more complex models as teachers +# +# We will be using a popular image dataset, CIFAR-10 and the rest of the code should be easy to run with a simple GPU (4GB of memory should be more than enough). We will be running a number of experiments focused at improving the accuracy of a lightweight neural network, using a more powerful network as a teacher. # The computational cost and the speed of the lightweight network will remain unaffected, our intervention only focuses on its weights, not on its forward pass. # Applications of this technology can be found in devices such as drones or mobile phones. We do not need any external packages, we have everything we need in torch(vision). # @@ -35,8 +39,8 @@ # Example of CIFAR-10 images # # The input images are RGB so 3 channels and ``32x32`` pixels. Basically each image is described by ``3 * 32 * 32 = 3072`` numbers from 0 to 255. -# A common practice in NNs is to normalize the input. This is done for multiple reasons like avoiding saturation in common activation functions or increased numerical stability. Our Normalization consists of subtracting the mean and dividing by the standard deviation, along each channel. -# The tensors ``mean=[0.485, 0.456, 0.406]`` and ``std=[0.229, 0.224, 0.225]`` were precomputed and they are the mean / std of each channel in the predefined subset of CIFAR-10 meant to be the train set. Notice how we use these numbers to the test set too and we do not compute the mean / std of the test set from scratch. +# A common practice in neural networks is to normalize the input. This is done for multiple reasons like avoiding saturation in common activation functions or increased numerical stability. Our Normalization consists of subtracting the mean and dividing by the standard deviation, along each channel. +# The tensors ``mean=[0.485, 0.456, 0.406]`` and ``std=[0.229, 0.224, 0.225]`` were already computed and they are the mean / std of each channel in the predefined subset of CIFAR-10 meant to be the train set. Notice how we use these numbers to the test set too and we do not compute the mean / std of the test set from scratch. # This is because the network was trained on features produced by the subtraction and division of the numbers above and we want to be consistent. Furthermore, in real life we would not be able to compute the mean and std of the test set, because we are operating under the assumption that this data would not be accessible at this point. # # As a closing point, we often refer to this held out set as the validation set and we use a separate set, called test set after we optimize a model's performance on the validation set, to avoid selecting a model based on the greedy and biased optimization of a single metric. @@ -75,7 +79,7 @@ # Both architectures are Convolutional Neural Networks (CNNs) with a different number of convolutional layers that work as feature extractors followed by a classifier, with 10 classes. # The number of filters and neurons is smaller for the students. -# Deeper NN class to be used as teacher +# Deeper neural network class to be used as teacher class DeepNN(nn.Module): def __init__(self, num_classes=10): super(DeepNN, self).__init__() @@ -104,7 +108,7 @@ def forward(self, x): x = self.classifier(x) return x -# Lightweight NN class to be used as student +# Lightweight neural network class to be used as student class LightNN(nn.Module): def __init__(self, num_classes=10): super(LightNN, self).__init__() @@ -137,7 +141,7 @@ def forward(self, x): # - ``train_loader``: We defined our train_loader above, its job is to feed the data into the model # - ``epochs``: How many times we loop over the dataset # - ``learning_rate``: How large do we want the steps to be towards convergence. Too large / small steps can be bad -# - ``device``: CPU or cuda (GPU) +# - ``device``: depends on CPU or GPU availability # # Our test function is similar, but it will be invoked with our ``test_loader``, to load images from our test set. # @@ -204,12 +208,12 @@ def test(model, test_loader, device): train(nn_deep, train_loader, epochs=10, learning_rate=0.001, device=device) test_accuracy_deep = test(nn_deep, test_loader, device) -# Instantiate the lightweight nn model +# Instantiate the lightweight network torch.manual_seed(42) nn_light = LightNN(num_classes=10) ###################################################################### -# We instantiate one more lightweight NN model to compare their performances. +# We instantiate one more lightweight network model to compare their performances. # Back propagation is sensitive to weight initialization, so we need to make sure these two networks have the exact same initialization. torch.manual_seed(42) @@ -218,9 +222,9 @@ def test(model, test_loader, device): ###################################################################### # To make sure we created a copy of the first network we inspect the norm of their first layer. If it matches then we are safe because the networks are indeed the same. -# Print the norm of the first layer of the initial lightweight nn model +# Print the norm of the first layer of the initial lightweight model print("Norm of 1st layer of nn_light:", torch.norm(nn_light.features[0].weight).item()) -# Print the norm of the first layer of the new lightweight nn model +# Print the norm of the first layer of the new lightweight model print("Norm of 1st layer of new_nn_light:", torch.norm(new_nn_light.features[0].weight).item()) ###################################################################### @@ -247,7 +251,6 @@ def test(model, test_loader, device): # The method works by including an additional loss to the traditional cross entropy loss that is based on the softmax output of the teacher network. It is assumed that the output activations of the properly trained teacher carry additional information that can be exploited by a student network while the latter is training. # The original work argues that making use of ratios of smaller probabilities in the soft targets can help with the underlying objective of deep neural networks, which is creating a similarity structure over the data, in which we would expect similar objects to be mapped closer together. As an example for CIFAR-10, a truck could be mistaken for an automobile or even an airplane with its wheels present, but it is less likely to be mistaken for a dog. # Therefore it makes sense to assume that valuable information resides not only on the top prediction of a properly trained model, but on the entire output distribution. This information is not exploited by cross entropy sufficiently well because the activations for the non predicted classes tend to be so small that propagated gradients do not meaningfully change the weights to construct this desirable vector space. -# We do not dive deeper into KD as the purpose of this tutorial is to showcase how one would handle multiple loss functions. # # We continue by defining our first helper function that introduces a teacher-student dynamic. We need to include a few extra parameters: # @@ -258,7 +261,7 @@ def test(model, test_loader, device): # .. figure:: /../_static/img/knowledge_distillation/distillation_output_loss.png # :align: center # -# Distillation loss is calcluated from the logits of the networks. It only returns gradients to the student +# Distillation loss is calculated from the logits of the networks. It only returns gradients to the student # def train_knowledge_distillation(teacher, student, train_loader, epochs, learning_rate, T, soft_target_loss_weight, ce_loss_weight, device): @@ -267,7 +270,7 @@ def train_knowledge_distillation(teacher, student, train_loader, epochs, learnin teacher.to(device) student.to(device) - teacher.eval() # Teacher set to eval mode + teacher.eval() # Teacher set to evaluation mode student.train() # Student to train mode for epoch in range(epochs): @@ -288,7 +291,7 @@ def train_knowledge_distillation(teacher, student, train_loader, epochs, learnin soft_targets = nn.functional.softmax(teacher_logits / T, dim=-1) soft_prob = nn.functional.log_softmax(student_logits / T, dim=-1) - # Calculate the soft targets loss. Scaled by T**2 as suggested by the authors of KD + # Calculate the soft targets loss. Scaled by T**2 as suggested by the authors of the paper soft_targets_loss = -torch.sum(soft_targets * soft_prob) / soft_prob.size()[0] * (T**2) # Calculate the true label loss @@ -308,14 +311,14 @@ def train_knowledge_distillation(teacher, student, train_loader, epochs, learnin train_knowledge_distillation(teacher=nn_deep, student=new_nn_light, train_loader=train_loader, epochs=10, learning_rate=0.001, T=2, soft_target_loss_weight=0.25, ce_loss_weight=0.75, device=device) test_accuracy_light_ce_and_kd = test(new_nn_light, test_loader, device) -# Compare the student test accuracy with and without the teacher, after introducing simple KD. +# Compare the student test accuracy with and without the teacher, after distillation print(f"Teacher accuracy: {test_accuracy_deep:.2f}%") print(f"Student accuracy without teacher: {test_accuracy_light_ce:.2f}%") print(f"Student accuracy with CE + KD: {test_accuracy_light_ce_and_kd:.2f}%") ###################################################################### # Feel free to play around with the temperature parameter that controls the softness of the softmax function and the loss coefficients. -# It is common practice in NNs to include additional loss functions to the main classification objectives to achieve goals like better generalization. +# In neural networks it is easy to include to include additional loss functions to the main objectives to achieve goals like better generalization. # Let us try including an objective for the student, but now let us focus on their hidden states rather than their output layers. In the previous example, the teacher's representation after the convolutional layers has shape: # # ``(batch_size, num_filters_for_last_conv_layer, 8, 8)`` @@ -327,7 +330,7 @@ def train_knowledge_distillation(teacher, student, train_loader, epochs, learnin # unlikely to be achieved by the student without external intervention, therefore we artificially push the student to mimic the internal representation of the teacher. Whether or not this will end up helping the student is not straightforward though, because pushing the lightweight network # to reach this point could be a good thing, assuming that we have found an internal representation that leads to better test accuracy, but it could also be harmful because the networks have different architectures and the student does not have the same learning capacity as the teacher. # In other words, there is no reason for these two vectors, the student's and the teacher's to match per component. The student could reach an internal representation that is a permutation of the teacher's and it would be just as efficient. -# Nonetheless, we can still run a quick experiment to figure out the impact of this method. We will be using the CosineEmbeddingLoss which is given by the formula: +# Nonetheless, we can still run a quick experiment to figure out the impact of this method. We will be using the ``CosineEmbeddingLoss`` which is given by the formula: # # .. figure:: /../_static/img/knowledge_distillation/cosine_embedding_loss.png # :align: center @@ -335,8 +338,8 @@ def train_knowledge_distillation(teacher, student, train_loader, epochs, learnin # # Formula for CosineEmbeddingLoss # -# Obviously, there is one thing that we need to resolve first. When we applied KD to the output layer we mentioned that both networks have the same number of neurons, equal to the number of classes. This is not the case for the layer following our convolutional layers. Here, the teacher has more neurons than the student -# after the flattening of the final convolutional layer. Our loss accepts 2 vectors of equal dimensionality as inputs, therefore we need to somehow match them. We will solve this by including an AvgPooling layer after the teacher's convolutional layer to reduce its dimensionality to match the one of the student. +# Obviously, there is one thing that we need to resolve first. When we applied distillation to the output layer we mentioned that both networks have the same number of neurons, equal to the number of classes. This is not the case for the layer following our convolutional layers. Here, the teacher has more neurons than the student +# after the flattening of the final convolutional layer. Our loss accepts 2 vectors of equal dimensionality as inputs, therefore we need to somehow match them. We will solve this by including an average pooling layer after the teacher's convolutional layer to reduce its dimensionality to match the one of the student. # # To proceed we will modify our model classes, or create new ones. # Now the forward function returns not only the logits of the network but also the flattened hidden representation after the convolutional layer. We include the aforementioned pooling for the modified teacher. @@ -395,7 +398,7 @@ def forward(self, x): x = self.classifier(flattened_conv_output) return x, flattened_conv_output -# We do not have to train the modified DNN from scratch of course, we just load its weights from the trained instance +# We do not have to train the modified deep network from scratch of course, we just load its weights from the trained instance modified_nn_deep = ModifiedDeepNNCosine(num_classes=10) modified_nn_deep.load_state_dict(nn_deep.state_dict()) @@ -403,7 +406,7 @@ def forward(self, x): print("Norm of 1st layer for deep_nn:", torch.norm(nn_deep.features[0].weight).item()) print("Norm of 1st layer for modified_deep_nn:", torch.norm(modified_nn_deep.features[0].weight).item()) -# Initialize a ModifiedLightNN with the same seed as our other lightweight instances. This will be trained from scratch to examine the effectiveness of cosine loss minimization. +# Initialize a modified lightweight network with the same seed as our other lightweight instances. This will be trained from scratch to examine the effectiveness of cosine loss minimization. torch.manual_seed(42) modified_light_nn = ModifiedLightNNCosine(num_classes=10) print("Norm of 1st layer:", torch.norm(modified_light_nn.features[0].weight).item()) @@ -416,7 +419,7 @@ def forward(self, x): # ``hidden_representation``: (``batch_size x hidden_representation_size``) # # In our case ``hidden_representation_size`` is ``1024``. This is the flattened feature map of the final convolutional layer of the student and as you can see it is the input for its classifier. -# It is ``1024`` for the teacher too, because we made it so with ``avg_pool1d`` from ``2048``. The loss applied here only affects the weights of the student prior to the loss caclulation. In other words it does not affect the classifier of the student. +# It is ``1024`` for the teacher too, because we made it so with ``avg_pool1d`` from ``2048``. The loss applied here only affects the weights of the student prior to the loss calculation. In other words it does not affect the classifier of the student. # The modified training loop is the following. # # .. figure:: /../_static/img/knowledge_distillation/cosine_loss_distillation.png @@ -432,7 +435,7 @@ def train_cosine_loss(teacher, student, train_loader, epochs, learning_rate, hid teacher.to(device) student.to(device) - teacher.eval() # Teacher set to eval mode + teacher.eval() # Teacher set to evaluation mode student.train() # Student to train mode for epoch in range(epochs): @@ -449,8 +452,7 @@ def train_cosine_loss(teacher, student, train_loader, epochs, learning_rate, hid # Forward pass with the student model student_logits, student_hidden_representation = student(inputs) - # Calculate the cosine loss - # target is a vector of ones. From the CosineEmbeddingLoss formula above we can see that is the case where loss minimization leads to cosine similarity increase, which is desirable. + # Calculate the cosine loss. Target is a vector of ones. From the loss formula above we can see that is the case where loss minimization leads to cosine similarity increase. hidden_rep_loss = cosine_loss(student_hidden_representation, teacher_hidden_representation, target=torch.ones(inputs.size(0)).to(device)) # Calculate the true label loss @@ -491,7 +493,7 @@ def test_multiple_outputs(model, test_loader, device): return accuracy ###################################################################### -# In this case we could easily include both KD and cosine loss minimization in the same function. It is common to combine methods to achieve better performance in teacher-student paradigms. +# In this case we could easily include both knowledge distillation and cosine loss minimization in the same function. It is common to combine methods to achieve better performance in teacher-student paradigms. # For now we can run a simple train-test session. #Train and test the lightweight network with cross entropy loss @@ -515,7 +517,7 @@ def test_multiple_outputs(model, test_loader, device): # .. figure:: /../_static/img/knowledge_distillation/fitnets_knowledge_distill.png # :align: center # -# The trainable layer matches the shapes of the intermediate tensors and MSE is properly defined +# The trainable layer matches the shapes of the intermediate tensors and Mean Squared Error ``(MSE)`` is properly defined # class ModifiedDeepNNRegressor(nn.Module): @@ -558,7 +560,7 @@ def __init__(self, num_classes=10): nn.ReLU(), nn.MaxPool2d(kernel_size=2, stride=2), ) - # Include an extra NN regressor (linear projection) + # Include an extra regressor (in our case linear) self.regressor = nn.Sequential( nn.Conv2d(16, 32, kernel_size=3, padding=1) ) @@ -577,7 +579,7 @@ def forward(self, x): return x, regressor_output ###################################################################### -# We have to update our train loop again. This time, we extract the regressor output of the student, the feature map of the teacher, we calculate the MSE on these tensors (they have the exact same shape so it's properly defined) and we back propagate gradients based on that loss, in addition to the regular cross entropy loss of the classification task. +# We have to update our train loop again. This time, we extract the regressor output of the student, the feature map of the teacher, we calculate the ``MSE`` on these tensors (they have the exact same shape so it's properly defined) and we back propagate gradients based on that loss, in addition to the regular cross entropy loss of the classification task. def train_mse_loss(teacher, student, train_loader, epochs, learning_rate, feature_map_weight, ce_loss_weight, device): ce_loss = nn.CrossEntropyLoss() @@ -586,7 +588,7 @@ def train_mse_loss(teacher, student, train_loader, epochs, learning_rate, featur teacher.to(device) student.to(device) - teacher.eval() # Teacher set to eval mode + teacher.eval() # Teacher set to evaluation mode student.train() # Student to train mode for epoch in range(epochs): @@ -625,7 +627,7 @@ def train_mse_loss(teacher, student, train_loader, epochs, learning_rate, featur torch.manual_seed(42) modified_light_nn_mse = ModifiedLightNNRegressor(num_classes=10) -# We do not have to train the modified DNN from scratch of course, we just load its weights from the trained instance +# We do not have to train the modified deep network from scratch of course, we just load its weights from the trained instance modified_nn_deep_reg = ModifiedDeepNNRegressor(num_classes=10) modified_nn_deep_reg.load_state_dict(nn_deep.state_dict()) @@ -635,7 +637,7 @@ def train_mse_loss(teacher, student, train_loader, epochs, learning_rate, featur ###################################################################### # It is expected for the final method to work better than CosineLoss because now, we allowed a trainable layer between the teacher and the student, which gives the student some wiggle room when it comes to learning, rather than pushing the student to copy the teacher's representation. -# Including the extra network is the idea behind hint based distillation, proposed in the original FitNets paper. +# Including the extra network is the idea behind hint based distillation. print(f"Teacher accuracy: {test_accuracy_deep:.2f}%") print(f"Student accuracy without teacher: {test_accuracy_light_ce:.2f}%") From a569ee94558a396ae96535cf5ef8a7d27aabf625 Mon Sep 17 00:00:00 2001 From: AlexandrosChrtn Date: Tue, 15 Aug 2023 18:13:11 +0300 Subject: [PATCH 03/28] review fixes + pyspelling --- .../knowledge_distillation_tutorial.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/beginner_source/knowledge_distillation_tutorial.py b/beginner_source/knowledge_distillation_tutorial.py index 44428bbd63c..972961c21aa 100644 --- a/beginner_source/knowledge_distillation_tutorial.py +++ b/beginner_source/knowledge_distillation_tutorial.py @@ -46,19 +46,14 @@ # As a closing point, we often refer to this held out set as the validation set and we use a separate set, called test set after we optimize a model's performance on the validation set, to avoid selecting a model based on the greedy and biased optimization of a single metric. # Data preprocessing for CIFAR-10. We use an arbitrary batch size of 128. -transform_train = transforms.Compose([ +transforms_cifar = transforms.Compose([ transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) -# Data preprocessing for CIFAR-10 -transform_test = transforms.Compose([ - transforms.ToTensor(), - transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), -]) # Load CIFAR-10 dataset -train_dataset = datasets.CIFAR10(root='./data', train=True, download=True, transform=transform_train) -test_dataset = datasets.CIFAR10(root='./data', train=False, download=True, transform=transform_test) +train_dataset = datasets.CIFAR10(root='./data', train=True, download=True, transform=transforms_cifar) +test_dataset = datasets.CIFAR10(root='./data', train=False, download=True, transform=transforms_cifar) ######################################################################## # .. note:: This section is for CPU users only who are interested in quick results. Only use this if you're interested in a small scale experiment. Keep in mind the code should run fairly fast using any GPU. Select only the first ``num_images_to_keep`` images from the train / test dataset @@ -291,7 +286,7 @@ def train_knowledge_distillation(teacher, student, train_loader, epochs, learnin soft_targets = nn.functional.softmax(teacher_logits / T, dim=-1) soft_prob = nn.functional.log_softmax(student_logits / T, dim=-1) - # Calculate the soft targets loss. Scaled by T**2 as suggested by the authors of the paper + # Calculate the soft targets loss. Scaled by T**2 as suggested by the authors of the paper "Distilling the knowledge in a neural network" soft_targets_loss = -torch.sum(soft_targets * soft_prob) / soft_prob.size()[0] * (T**2) # Calculate the true label loss @@ -425,7 +420,7 @@ def forward(self, x): # .. figure:: /../_static/img/knowledge_distillation/cosine_loss_distillation.png # :align: center # -# In CosineLoss minimization we want to maximize the cosine similarity of the two representations by returning gradients to the student +# In Cosine Loss minimization we want to maximize the cosine similarity of the two representations by returning gradients to the student # def train_cosine_loss(teacher, student, train_loader, epochs, learning_rate, hidden_rep_loss_weight, ce_loss_weight, device): @@ -605,7 +600,7 @@ def train_mse_loss(teacher, student, train_loader, epochs, learning_rate, featur # Forward pass with the student model student_logits, regressor_feature_map = student(inputs) - # Calculate the MSE loss + # Calculate the loss hidden_rep_loss = mse_loss(regressor_feature_map, teacher_feature_map) # Calculate the true label loss From a5d82213bb94cda873f403648077a0b4fa544c9a Mon Sep 17 00:00:00 2001 From: AlexandrosChrtn <56091961+AlexandrosChrtn@users.noreply.github.com> Date: Wed, 16 Aug 2023 15:32:01 +0300 Subject: [PATCH 04/28] Update beginner_source/knowledge_distillation_tutorial.py Co-authored-by: Svetlana Karslioglu --- beginner_source/knowledge_distillation_tutorial.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/beginner_source/knowledge_distillation_tutorial.py b/beginner_source/knowledge_distillation_tutorial.py index 972961c21aa..fcd4c6d6566 100644 --- a/beginner_source/knowledge_distillation_tutorial.py +++ b/beginner_source/knowledge_distillation_tutorial.py @@ -645,7 +645,7 @@ def train_mse_loss(teacher, student, train_loader, epochs, learning_rate, featur # If our lightweight model is still too heavy for deployment we can apply different ideas such as post training quantization. Additional losses can be applied in many tasks, not just classification and one can experiment with quantities like coefficients, temperature or number of neurons. # Feel free to tune any numbers in the tutorial above, but keep in mind, if you change the number of neurons / filters chances are a shape mismatch might occur. # -# References: -# `Hinton, G., Vinyals, O., Dean, J.: Distilling the knowledge in a neural network. In: Neural Information Processing System Deep Learning Workshop (2015) `_ +# For more information, see: +# * `Hinton, G., Vinyals, O., Dean, J.: Distilling the knowledge in a neural network. In: Neural Information Processing System Deep Learning Workshop (2015) `_ # -# `Romero, A., Ballas, N., Kahou, S.E., Chassang, A., Gatta, C., Bengio, Y.: Fitnets: Hints for thin deep nets. In: Proceedings of the International Conference on Learning Representations (2015) `_ +# * `Romero, A., Ballas, N., Kahou, S.E., Chassang, A., Gatta, C., Bengio, Y.: Fitnets: Hints for thin deep nets. In: Proceedings of the International Conference on Learning Representations (2015) `_ From b7185df1b7499b3ade4376b03bd2a670fbc81baf Mon Sep 17 00:00:00 2001 From: AlexandrosChrtn <56091961+AlexandrosChrtn@users.noreply.github.com> Date: Wed, 16 Aug 2023 15:32:38 +0300 Subject: [PATCH 05/28] Update beginner_source/knowledge_distillation_tutorial.py Co-authored-by: Svetlana Karslioglu --- beginner_source/knowledge_distillation_tutorial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beginner_source/knowledge_distillation_tutorial.py b/beginner_source/knowledge_distillation_tutorial.py index fcd4c6d6566..73d529177e5 100644 --- a/beginner_source/knowledge_distillation_tutorial.py +++ b/beginner_source/knowledge_distillation_tutorial.py @@ -642,7 +642,7 @@ def train_mse_loss(teacher, student, train_loader, epochs, learning_rate, featur ###################################################################### # None of the methods above increases the number of parameters for the network or inference time, so the performance increase comes at the little cost of calculating gradients during training. In ML applications we mostly care about inference time because training happens before the model deployment. -# If our lightweight model is still too heavy for deployment we can apply different ideas such as post training quantization. Additional losses can be applied in many tasks, not just classification and one can experiment with quantities like coefficients, temperature or number of neurons. +# If our lightweight model is still too heavy for deployment, we can apply different ideas, such as post-training quantization. Additional losses can be applied in many tasks, not just classification, and you can experiment with quantities like coefficients, temperature, or number of neurons. # Feel free to tune any numbers in the tutorial above, but keep in mind, if you change the number of neurons / filters chances are a shape mismatch might occur. # # For more information, see: From 86fa7a0a542017b75c642a64c5fb01a773d439e7 Mon Sep 17 00:00:00 2001 From: AlexandrosChrtn <56091961+AlexandrosChrtn@users.noreply.github.com> Date: Wed, 16 Aug 2023 22:54:38 +0300 Subject: [PATCH 06/28] Update beginner_source/knowledge_distillation_tutorial.py Co-authored-by: Svetlana Karslioglu --- .../knowledge_distillation_tutorial.py | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/beginner_source/knowledge_distillation_tutorial.py b/beginner_source/knowledge_distillation_tutorial.py index 73d529177e5..f78f47a9dc8 100644 --- a/beginner_source/knowledge_distillation_tutorial.py +++ b/beginner_source/knowledge_distillation_tutorial.py @@ -6,16 +6,30 @@ """ ###################################################################### -# From this tutorial one can learn: +# Knowledge is a technic that enables transfers knowledge from large, computationally expensive +# models to smaller ones without losing validity. This allows for deployment on less powerful +# hardware, making evaluation faster and more efficient. +# +# In this tutorial, we will run a number of experiments focused at improving the accuracy of a +# lightweight neural network, using a more powerful network as a teacher. +# The computational cost and the speed of the lightweight network will remain unaffected, +# our intervention only focuses on its weights, not on its forward pass. +# Applications of this technology can be found in devices such as drones or mobile phones. +# In this tutorial, we do not use any external packages as everything we need is available in ``torch`` and +# ``torchvision``. +# +# In this tutorial, you will learn: +# # - How to modify model classes to extract hidden representations and use them for further calculations -# - How to modify regular train loops in pytorch to include additional losses on top of, for example, cross-entropy for classification -# - How to improve the performance of lightweight models, using more complex models as teachers -# -# We will be using a popular image dataset, CIFAR-10 and the rest of the code should be easy to run with a simple GPU (4GB of memory should be more than enough). We will be running a number of experiments focused at improving the accuracy of a lightweight neural network, using a more powerful network as a teacher. -# The computational cost and the speed of the lightweight network will remain unaffected, our intervention only focuses on its weights, not on its forward pass. -# Applications of this technology can be found in devices such as drones or mobile phones. We do not need any external packages, we have everything we need in torch(vision). +# - How to modify regular train loops in PyTorch to include additional losses on top of, for example, cross-entropy for classification +# - How to improve the performance of lightweight models by using more complex models as teachers +# +# Prerequisites +# ~~~~~~~~~~~ # -# The script will download the dataset and save it in a directory called data. +# * 1 GPU, 4GB of memory +# * PyTorch v2.0 or later +# * CIFAR-10 dataset (downloaded by the script and saved it in a directory called ``/data``) import torch import torch.nn as nn From 4f3570a2bf14156db29fa1aedddfa6660dbcdeee Mon Sep 17 00:00:00 2001 From: AlexandrosChrtn <56091961+AlexandrosChrtn@users.noreply.github.com> Date: Wed, 16 Aug 2023 22:54:56 +0300 Subject: [PATCH 07/28] Update beginner_source/knowledge_distillation_tutorial.py Co-authored-by: Svetlana Karslioglu --- beginner_source/knowledge_distillation_tutorial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beginner_source/knowledge_distillation_tutorial.py b/beginner_source/knowledge_distillation_tutorial.py index f78f47a9dc8..c2856b23fec 100644 --- a/beginner_source/knowledge_distillation_tutorial.py +++ b/beginner_source/knowledge_distillation_tutorial.py @@ -45,7 +45,7 @@ device = torch.device("cuda" if torch.cuda.is_available() else "cpu") ###################################################################### -# CIFAR-10 is a popular image dataset with 10 classes. Our objective is to predict one of the following classes for each input image. +# CIFAR-10 is a popular image dataset with ten classes. Our objective is to predict one of the following classes for each input image. # # .. figure:: /../_static/img/cifar10.png # :align: center From 5881348b7e063e8375a0ead1ad78dedf8966475a Mon Sep 17 00:00:00 2001 From: AlexandrosChrtn <56091961+AlexandrosChrtn@users.noreply.github.com> Date: Wed, 16 Aug 2023 22:55:20 +0300 Subject: [PATCH 08/28] Update beginner_source/knowledge_distillation_tutorial.py Co-authored-by: Svetlana Karslioglu --- beginner_source/knowledge_distillation_tutorial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beginner_source/knowledge_distillation_tutorial.py b/beginner_source/knowledge_distillation_tutorial.py index c2856b23fec..f2c375d9adf 100644 --- a/beginner_source/knowledge_distillation_tutorial.py +++ b/beginner_source/knowledge_distillation_tutorial.py @@ -52,7 +52,7 @@ # # Example of CIFAR-10 images # -# The input images are RGB so 3 channels and ``32x32`` pixels. Basically each image is described by ``3 * 32 * 32 = 3072`` numbers from 0 to 255. +# The input images are RGB, so they have 3 channels and are 32x32 pixels. Basically, each image is described by 3 x 32 x 32 = 3072 numbers ranging from 0 to 255. # A common practice in neural networks is to normalize the input. This is done for multiple reasons like avoiding saturation in common activation functions or increased numerical stability. Our Normalization consists of subtracting the mean and dividing by the standard deviation, along each channel. # The tensors ``mean=[0.485, 0.456, 0.406]`` and ``std=[0.229, 0.224, 0.225]`` were already computed and they are the mean / std of each channel in the predefined subset of CIFAR-10 meant to be the train set. Notice how we use these numbers to the test set too and we do not compute the mean / std of the test set from scratch. # This is because the network was trained on features produced by the subtraction and division of the numbers above and we want to be consistent. Furthermore, in real life we would not be able to compute the mean and std of the test set, because we are operating under the assumption that this data would not be accessible at this point. From fbbb40dcca00da4d4252ce234ba994e2462cd805 Mon Sep 17 00:00:00 2001 From: AlexandrosChrtn <56091961+AlexandrosChrtn@users.noreply.github.com> Date: Wed, 16 Aug 2023 22:55:38 +0300 Subject: [PATCH 09/28] Update beginner_source/knowledge_distillation_tutorial.py Co-authored-by: Svetlana Karslioglu --- beginner_source/knowledge_distillation_tutorial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beginner_source/knowledge_distillation_tutorial.py b/beginner_source/knowledge_distillation_tutorial.py index f2c375d9adf..cc3f42c3265 100644 --- a/beginner_source/knowledge_distillation_tutorial.py +++ b/beginner_source/knowledge_distillation_tutorial.py @@ -655,7 +655,7 @@ def train_mse_loss(teacher, student, train_loader, epochs, learning_rate, featur print(f"Student accuracy with CE + RegressorMSE: {test_accuracy_light_ce_and_mse_loss:.2f}%") ###################################################################### -# None of the methods above increases the number of parameters for the network or inference time, so the performance increase comes at the little cost of calculating gradients during training. In ML applications we mostly care about inference time because training happens before the model deployment. +# None of the methods above increases the number of parameters for the network or inference time, so the performance increase comes at the little cost of calculating gradients during training. In ML applications, we mostly care about inference time because training happens before the model deployment. # If our lightweight model is still too heavy for deployment, we can apply different ideas, such as post-training quantization. Additional losses can be applied in many tasks, not just classification, and you can experiment with quantities like coefficients, temperature, or number of neurons. # Feel free to tune any numbers in the tutorial above, but keep in mind, if you change the number of neurons / filters chances are a shape mismatch might occur. # From 9078639e30a6fd000c08db659b9e79ebe35410db Mon Sep 17 00:00:00 2001 From: AlexandrosChrtn <56091961+AlexandrosChrtn@users.noreply.github.com> Date: Wed, 16 Aug 2023 22:55:56 +0300 Subject: [PATCH 10/28] Update beginner_source/knowledge_distillation_tutorial.py Co-authored-by: Svetlana Karslioglu --- beginner_source/knowledge_distillation_tutorial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beginner_source/knowledge_distillation_tutorial.py b/beginner_source/knowledge_distillation_tutorial.py index cc3f42c3265..883bfa53730 100644 --- a/beginner_source/knowledge_distillation_tutorial.py +++ b/beginner_source/knowledge_distillation_tutorial.py @@ -646,7 +646,7 @@ def train_mse_loss(teacher, student, train_loader, epochs, learning_rate, featur ###################################################################### # It is expected for the final method to work better than CosineLoss because now, we allowed a trainable layer between the teacher and the student, which gives the student some wiggle room when it comes to learning, rather than pushing the student to copy the teacher's representation. -# Including the extra network is the idea behind hint based distillation. +# Including the extra network is the idea behind hint-based distillation. print(f"Teacher accuracy: {test_accuracy_deep:.2f}%") print(f"Student accuracy without teacher: {test_accuracy_light_ce:.2f}%") From f66b0ee9d58a1e6632cc6e60b2f33965eea2c6ac Mon Sep 17 00:00:00 2001 From: AlexandrosChrtn <56091961+AlexandrosChrtn@users.noreply.github.com> Date: Wed, 16 Aug 2023 22:56:29 +0300 Subject: [PATCH 11/28] Update beginner_source/knowledge_distillation_tutorial.py Co-authored-by: Svetlana Karslioglu --- beginner_source/knowledge_distillation_tutorial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beginner_source/knowledge_distillation_tutorial.py b/beginner_source/knowledge_distillation_tutorial.py index 883bfa53730..4014ac9712b 100644 --- a/beginner_source/knowledge_distillation_tutorial.py +++ b/beginner_source/knowledge_distillation_tutorial.py @@ -53,7 +53,7 @@ # Example of CIFAR-10 images # # The input images are RGB, so they have 3 channels and are 32x32 pixels. Basically, each image is described by 3 x 32 x 32 = 3072 numbers ranging from 0 to 255. -# A common practice in neural networks is to normalize the input. This is done for multiple reasons like avoiding saturation in common activation functions or increased numerical stability. Our Normalization consists of subtracting the mean and dividing by the standard deviation, along each channel. +# A common practice in neural networks is to normalize the input, which is done for multiple reasons, including avoiding saturation in commonly used activation functions and increasing numerical stability. Our normalization process consists of subtracting the mean and dividing by the standard deviation along each channel. # The tensors ``mean=[0.485, 0.456, 0.406]`` and ``std=[0.229, 0.224, 0.225]`` were already computed and they are the mean / std of each channel in the predefined subset of CIFAR-10 meant to be the train set. Notice how we use these numbers to the test set too and we do not compute the mean / std of the test set from scratch. # This is because the network was trained on features produced by the subtraction and division of the numbers above and we want to be consistent. Furthermore, in real life we would not be able to compute the mean and std of the test set, because we are operating under the assumption that this data would not be accessible at this point. # From 035c541fa706ab2b1eca57e0625b5bbdca5e04cb Mon Sep 17 00:00:00 2001 From: AlexandrosChrtn <56091961+AlexandrosChrtn@users.noreply.github.com> Date: Wed, 16 Aug 2023 23:02:08 +0300 Subject: [PATCH 12/28] Update beginner_source/knowledge_distillation_tutorial.py Co-authored-by: Svetlana Karslioglu --- beginner_source/knowledge_distillation_tutorial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beginner_source/knowledge_distillation_tutorial.py b/beginner_source/knowledge_distillation_tutorial.py index 4014ac9712b..86f522af42f 100644 --- a/beginner_source/knowledge_distillation_tutorial.py +++ b/beginner_source/knowledge_distillation_tutorial.py @@ -54,7 +54,7 @@ # # The input images are RGB, so they have 3 channels and are 32x32 pixels. Basically, each image is described by 3 x 32 x 32 = 3072 numbers ranging from 0 to 255. # A common practice in neural networks is to normalize the input, which is done for multiple reasons, including avoiding saturation in commonly used activation functions and increasing numerical stability. Our normalization process consists of subtracting the mean and dividing by the standard deviation along each channel. -# The tensors ``mean=[0.485, 0.456, 0.406]`` and ``std=[0.229, 0.224, 0.225]`` were already computed and they are the mean / std of each channel in the predefined subset of CIFAR-10 meant to be the train set. Notice how we use these numbers to the test set too and we do not compute the mean / std of the test set from scratch. +# The tensors "mean=[0.485, 0.456, 0.406]" and "std=[0.229, 0.224, 0.225]" were already computed, and they represent the mean and standard deviation of each channel in the predefined subset of CIFAR-10 intended to be the training set. Notice how we use these values for the test set as well, without recomputing the mean and standard deviation from scratch. # This is because the network was trained on features produced by the subtraction and division of the numbers above and we want to be consistent. Furthermore, in real life we would not be able to compute the mean and std of the test set, because we are operating under the assumption that this data would not be accessible at this point. # # As a closing point, we often refer to this held out set as the validation set and we use a separate set, called test set after we optimize a model's performance on the validation set, to avoid selecting a model based on the greedy and biased optimization of a single metric. From fd8b9b43b77e2d36eeb78c9cc975515bc4c1b7a0 Mon Sep 17 00:00:00 2001 From: AlexandrosChrtn <56091961+AlexandrosChrtn@users.noreply.github.com> Date: Wed, 16 Aug 2023 23:02:40 +0300 Subject: [PATCH 13/28] Update beginner_source/knowledge_distillation_tutorial.py Co-authored-by: Svetlana Karslioglu --- beginner_source/knowledge_distillation_tutorial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beginner_source/knowledge_distillation_tutorial.py b/beginner_source/knowledge_distillation_tutorial.py index 86f522af42f..4d221d7f7e3 100644 --- a/beginner_source/knowledge_distillation_tutorial.py +++ b/beginner_source/knowledge_distillation_tutorial.py @@ -55,7 +55,7 @@ # The input images are RGB, so they have 3 channels and are 32x32 pixels. Basically, each image is described by 3 x 32 x 32 = 3072 numbers ranging from 0 to 255. # A common practice in neural networks is to normalize the input, which is done for multiple reasons, including avoiding saturation in commonly used activation functions and increasing numerical stability. Our normalization process consists of subtracting the mean and dividing by the standard deviation along each channel. # The tensors "mean=[0.485, 0.456, 0.406]" and "std=[0.229, 0.224, 0.225]" were already computed, and they represent the mean and standard deviation of each channel in the predefined subset of CIFAR-10 intended to be the training set. Notice how we use these values for the test set as well, without recomputing the mean and standard deviation from scratch. -# This is because the network was trained on features produced by the subtraction and division of the numbers above and we want to be consistent. Furthermore, in real life we would not be able to compute the mean and std of the test set, because we are operating under the assumption that this data would not be accessible at this point. +# This is because the network was trained on features produced by subtracting and dividing the numbers above, and we want to maintain consistency. Furthermore, in real life, we would not be able to compute the mean and standard deviation of the test set since, under our assumptions, this data would not be accessible at that point. # # As a closing point, we often refer to this held out set as the validation set and we use a separate set, called test set after we optimize a model's performance on the validation set, to avoid selecting a model based on the greedy and biased optimization of a single metric. From 788aedfa34c841714c217a675d20889d44ccfbea Mon Sep 17 00:00:00 2001 From: AlexandrosChrtn <56091961+AlexandrosChrtn@users.noreply.github.com> Date: Wed, 16 Aug 2023 23:03:07 +0300 Subject: [PATCH 14/28] Update beginner_source/knowledge_distillation_tutorial.py Co-authored-by: Svetlana Karslioglu --- beginner_source/knowledge_distillation_tutorial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beginner_source/knowledge_distillation_tutorial.py b/beginner_source/knowledge_distillation_tutorial.py index 4d221d7f7e3..d1b3290d31d 100644 --- a/beginner_source/knowledge_distillation_tutorial.py +++ b/beginner_source/knowledge_distillation_tutorial.py @@ -57,7 +57,7 @@ # The tensors "mean=[0.485, 0.456, 0.406]" and "std=[0.229, 0.224, 0.225]" were already computed, and they represent the mean and standard deviation of each channel in the predefined subset of CIFAR-10 intended to be the training set. Notice how we use these values for the test set as well, without recomputing the mean and standard deviation from scratch. # This is because the network was trained on features produced by subtracting and dividing the numbers above, and we want to maintain consistency. Furthermore, in real life, we would not be able to compute the mean and standard deviation of the test set since, under our assumptions, this data would not be accessible at that point. # -# As a closing point, we often refer to this held out set as the validation set and we use a separate set, called test set after we optimize a model's performance on the validation set, to avoid selecting a model based on the greedy and biased optimization of a single metric. +# As a closing point, we often refer to this held-out set as the validation set, and we use a separate set, called the test set, after optimizing a model's performance on the validation set. This is done to avoid selecting a model based on the greedy and biased optimization of a single metric. # Data preprocessing for CIFAR-10. We use an arbitrary batch size of 128. transforms_cifar = transforms.Compose([ From de2d4ef9adc776d129044c87ff17363b0e27a3b5 Mon Sep 17 00:00:00 2001 From: AlexandrosChrtn <56091961+AlexandrosChrtn@users.noreply.github.com> Date: Wed, 16 Aug 2023 23:03:36 +0300 Subject: [PATCH 15/28] Update beginner_source/knowledge_distillation_tutorial.py Co-authored-by: Svetlana Karslioglu --- beginner_source/knowledge_distillation_tutorial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beginner_source/knowledge_distillation_tutorial.py b/beginner_source/knowledge_distillation_tutorial.py index d1b3290d31d..4fae855d57f 100644 --- a/beginner_source/knowledge_distillation_tutorial.py +++ b/beginner_source/knowledge_distillation_tutorial.py @@ -65,7 +65,7 @@ transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) -# Load CIFAR-10 dataset +# Loading the CIFAR-10 dataset: train_dataset = datasets.CIFAR10(root='./data', train=True, download=True, transform=transforms_cifar) test_dataset = datasets.CIFAR10(root='./data', train=False, download=True, transform=transforms_cifar) From 56b70026b8319b519d9ece7bd2979342647259d9 Mon Sep 17 00:00:00 2001 From: AlexandrosChrtn <56091961+AlexandrosChrtn@users.noreply.github.com> Date: Wed, 16 Aug 2023 23:04:11 +0300 Subject: [PATCH 16/28] Update beginner_source/knowledge_distillation_tutorial.py Co-authored-by: Svetlana Karslioglu --- beginner_source/knowledge_distillation_tutorial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beginner_source/knowledge_distillation_tutorial.py b/beginner_source/knowledge_distillation_tutorial.py index 4fae855d57f..69b9239ae0c 100644 --- a/beginner_source/knowledge_distillation_tutorial.py +++ b/beginner_source/knowledge_distillation_tutorial.py @@ -59,7 +59,7 @@ # # As a closing point, we often refer to this held-out set as the validation set, and we use a separate set, called the test set, after optimizing a model's performance on the validation set. This is done to avoid selecting a model based on the greedy and biased optimization of a single metric. -# Data preprocessing for CIFAR-10. We use an arbitrary batch size of 128. +# Below we are preprocessing data for CIFAR-10. We use an arbitrary batch size of 128. transforms_cifar = transforms.Compose([ transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), From f44a32de984b5a984ab36e801809c0910fc4677b Mon Sep 17 00:00:00 2001 From: AlexandrosChrtn <56091961+AlexandrosChrtn@users.noreply.github.com> Date: Wed, 16 Aug 2023 23:04:50 +0300 Subject: [PATCH 17/28] Update beginner_source/knowledge_distillation_tutorial.py Co-authored-by: Svetlana Karslioglu --- beginner_source/knowledge_distillation_tutorial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beginner_source/knowledge_distillation_tutorial.py b/beginner_source/knowledge_distillation_tutorial.py index 69b9239ae0c..b82dfcb0124 100644 --- a/beginner_source/knowledge_distillation_tutorial.py +++ b/beginner_source/knowledge_distillation_tutorial.py @@ -70,7 +70,7 @@ test_dataset = datasets.CIFAR10(root='./data', train=False, download=True, transform=transforms_cifar) ######################################################################## -# .. note:: This section is for CPU users only who are interested in quick results. Only use this if you're interested in a small scale experiment. Keep in mind the code should run fairly fast using any GPU. Select only the first ``num_images_to_keep`` images from the train / test dataset +# .. note:: This section is for CPU users only who are interested in quick results. Use this option only if you're interested in a small scale experiment. Keep in mind the code should run fairly quickly using any GPU. Select only the first ``num_images_to_keep`` images from the train/test dataset # # .. code-block:: python # From 1ced800951b0b64934ec178fa8b738f62bb0a833 Mon Sep 17 00:00:00 2001 From: AlexandrosChrtn <56091961+AlexandrosChrtn@users.noreply.github.com> Date: Wed, 16 Aug 2023 23:06:58 +0300 Subject: [PATCH 18/28] Update beginner_source/knowledge_distillation_tutorial.py Co-authored-by: Svetlana Karslioglu --- beginner_source/knowledge_distillation_tutorial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beginner_source/knowledge_distillation_tutorial.py b/beginner_source/knowledge_distillation_tutorial.py index b82dfcb0124..385b5b56dd3 100644 --- a/beginner_source/knowledge_distillation_tutorial.py +++ b/beginner_source/knowledge_distillation_tutorial.py @@ -84,7 +84,7 @@ test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=128, shuffle=False, num_workers=2) ###################################################################### -# Moving forward we need to define our model classes. A number of user defined parameters needs to be set here. We use 2 different architectures, keeping the number of filters fixed across our experiments to ensure fair comparisons. +# Next, we need to define our model classes. Several user-defined parameters need to be set here. We use two different architectures, keeping the number of filters fixed across our experiments to ensure fair comparisons.. # Both architectures are Convolutional Neural Networks (CNNs) with a different number of convolutional layers that work as feature extractors followed by a classifier, with 10 classes. # The number of filters and neurons is smaller for the students. From 1113cf1130ef49092ee25ce4bd923a6038621d51 Mon Sep 17 00:00:00 2001 From: AlexandrosChrtn <56091961+AlexandrosChrtn@users.noreply.github.com> Date: Wed, 16 Aug 2023 23:08:05 +0300 Subject: [PATCH 19/28] Update beginner_source/knowledge_distillation_tutorial.py Co-authored-by: Svetlana Karslioglu --- beginner_source/knowledge_distillation_tutorial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beginner_source/knowledge_distillation_tutorial.py b/beginner_source/knowledge_distillation_tutorial.py index 385b5b56dd3..4699c8dc2bc 100644 --- a/beginner_source/knowledge_distillation_tutorial.py +++ b/beginner_source/knowledge_distillation_tutorial.py @@ -85,7 +85,7 @@ ###################################################################### # Next, we need to define our model classes. Several user-defined parameters need to be set here. We use two different architectures, keeping the number of filters fixed across our experiments to ensure fair comparisons.. -# Both architectures are Convolutional Neural Networks (CNNs) with a different number of convolutional layers that work as feature extractors followed by a classifier, with 10 classes. +# Both architectures are Convolutional Neural Networks (CNNs) with a different number of convolutional layers that serve as feature extractors, followed by a classifier with 10 classes. # The number of filters and neurons is smaller for the students. # Deeper neural network class to be used as teacher From d166fffddf604ff862b525f7c15f18ce15d43da3 Mon Sep 17 00:00:00 2001 From: AlexandrosChrtn <56091961+AlexandrosChrtn@users.noreply.github.com> Date: Wed, 16 Aug 2023 23:08:40 +0300 Subject: [PATCH 20/28] Update beginner_source/knowledge_distillation_tutorial.py Co-authored-by: Svetlana Karslioglu --- beginner_source/knowledge_distillation_tutorial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beginner_source/knowledge_distillation_tutorial.py b/beginner_source/knowledge_distillation_tutorial.py index 4699c8dc2bc..6e672f12346 100644 --- a/beginner_source/knowledge_distillation_tutorial.py +++ b/beginner_source/knowledge_distillation_tutorial.py @@ -88,7 +88,7 @@ # Both architectures are Convolutional Neural Networks (CNNs) with a different number of convolutional layers that serve as feature extractors, followed by a classifier with 10 classes. # The number of filters and neurons is smaller for the students. -# Deeper neural network class to be used as teacher +# Deeper neural network class to be used as teacher: class DeepNN(nn.Module): def __init__(self, num_classes=10): super(DeepNN, self).__init__() From 196637a7d9b2513c13823dde672995b501684721 Mon Sep 17 00:00:00 2001 From: AlexandrosChrtn <56091961+AlexandrosChrtn@users.noreply.github.com> Date: Wed, 16 Aug 2023 23:17:49 +0300 Subject: [PATCH 21/28] Update beginner_source/knowledge_distillation_tutorial.py Co-authored-by: Svetlana Karslioglu --- beginner_source/knowledge_distillation_tutorial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beginner_source/knowledge_distillation_tutorial.py b/beginner_source/knowledge_distillation_tutorial.py index 6e672f12346..13208996475 100644 --- a/beginner_source/knowledge_distillation_tutorial.py +++ b/beginner_source/knowledge_distillation_tutorial.py @@ -150,7 +150,7 @@ def forward(self, x): # - ``train_loader``: We defined our train_loader above, its job is to feed the data into the model # - ``epochs``: How many times we loop over the dataset # - ``learning_rate``: How large do we want the steps to be towards convergence. Too large / small steps can be bad -# - ``device``: depends on CPU or GPU availability +# - ``device``: Determines the device to run the workload on. Can be either CPU or GPU depending on availability. # # Our test function is similar, but it will be invoked with our ``test_loader``, to load images from our test set. # From 1d32f4ff8625880cce7f1399be449f128734532a Mon Sep 17 00:00:00 2001 From: AlexandrosChrtn <56091961+AlexandrosChrtn@users.noreply.github.com> Date: Wed, 16 Aug 2023 23:18:14 +0300 Subject: [PATCH 22/28] Update beginner_source/knowledge_distillation_tutorial.py Co-authored-by: Svetlana Karslioglu --- beginner_source/knowledge_distillation_tutorial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beginner_source/knowledge_distillation_tutorial.py b/beginner_source/knowledge_distillation_tutorial.py index 13208996475..1a1cce96c12 100644 --- a/beginner_source/knowledge_distillation_tutorial.py +++ b/beginner_source/knowledge_distillation_tutorial.py @@ -146,7 +146,7 @@ def forward(self, x): # We employ 2 functions to help us produce and evaluate the results on our original classification task. # One function called train with the following arguments: # -# - ``model``: A model instance that is to be trained (update its weights) via this function +# - ``model``: A model instance to train (update its weights) via this function. # - ``train_loader``: We defined our train_loader above, its job is to feed the data into the model # - ``epochs``: How many times we loop over the dataset # - ``learning_rate``: How large do we want the steps to be towards convergence. Too large / small steps can be bad From bccd00a33c09ff6d1e8787b67f839efd9c901024 Mon Sep 17 00:00:00 2001 From: AlexandrosChrtn <56091961+AlexandrosChrtn@users.noreply.github.com> Date: Thu, 17 Aug 2023 14:06:45 +0300 Subject: [PATCH 23/28] Apply suggestions from code review Co-authored-by: Svetlana Karslioglu --- .../knowledge_distillation_tutorial.py | 86 +++++++++---------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/beginner_source/knowledge_distillation_tutorial.py b/beginner_source/knowledge_distillation_tutorial.py index 1a1cce96c12..efa3a5c276c 100644 --- a/beginner_source/knowledge_distillation_tutorial.py +++ b/beginner_source/knowledge_distillation_tutorial.py @@ -6,7 +6,7 @@ """ ###################################################################### -# Knowledge is a technic that enables transfers knowledge from large, computationally expensive +# Knowledge distillation is a technique that enables knowledge transfer from large, computationally expensive # models to smaller ones without losing validity. This allows for deployment on less powerful # hardware, making evaluation faster and more efficient. # @@ -117,7 +117,7 @@ def forward(self, x): x = self.classifier(x) return x -# Lightweight neural network class to be used as student +# Lightweight neural network class to be used as student: class LightNN(nn.Module): def __init__(self, num_classes=10): super(LightNN, self).__init__() @@ -144,15 +144,15 @@ def forward(self, x): ###################################################################### # We employ 2 functions to help us produce and evaluate the results on our original classification task. -# One function called train with the following arguments: +# One function is called ``train`` and takes the following arguments: # # - ``model``: A model instance to train (update its weights) via this function. -# - ``train_loader``: We defined our train_loader above, its job is to feed the data into the model -# - ``epochs``: How many times we loop over the dataset -# - ``learning_rate``: How large do we want the steps to be towards convergence. Too large / small steps can be bad +# - ``train_loader``: we defined our ``train_loader`` above, and its job is to feed the data into the model. +# - ``epochs``: How many times we loop over the dataset. +# - ``learning_rate``: The learning rate determines how large our steps towards convergence should be. Too large or too small steps can be detrimental. # - ``device``: Determines the device to run the workload on. Can be either CPU or GPU depending on availability. # -# Our test function is similar, but it will be invoked with our ``test_loader``, to load images from our test set. +# Our test function is similar, but it will be invoked with ``test_loader`` to load images from the test set. # # .. figure:: /../_static/img/knowledge_distillation/ce_only.png # :align: center @@ -209,15 +209,15 @@ def test(model, test_loader, device): return accuracy ###################################################################### -# For reproducibility we need to set the torch manual seed. We train networks with different methods, so to compare them, it makes sense we need to initialize the networks with the same weights to ensure fair comparisons. -# Start by training the teacher network using cross-entropy. +# For reproducibility, we need to set the torch manual seed. We train networks using different methods, so to compare them fairly, it makes sense to initialize the networks with the same weights. +# Start by training the teacher network using cross-entropy: torch.manual_seed(42) nn_deep = DeepNN(num_classes=10) train(nn_deep, train_loader, epochs=10, learning_rate=0.001, device=device) test_accuracy_deep = test(nn_deep, test_loader, device) -# Instantiate the lightweight network +# Instantiate the lightweight network: torch.manual_seed(42) nn_light = LightNN(num_classes=10) @@ -229,7 +229,7 @@ def test(model, test_loader, device): new_nn_light = LightNN(num_classes=10) ###################################################################### -# To make sure we created a copy of the first network we inspect the norm of their first layer. If it matches then we are safe because the networks are indeed the same. +# To ensure we have created a copy of the first network, we inspect the norm of its first layer. If it matches, then we are safe to conclude that the networks are indeed the same. # Print the norm of the first layer of the initial lightweight model print("Norm of 1st layer of nn_light:", torch.norm(nn_light.features[0].weight).item()) @@ -249,21 +249,21 @@ def test(model, test_loader, device): test_accuracy_light_ce = test(nn_light, test_loader, device) ###################################################################### -# As we can see, based on test accuracy, we can now compare the deeper network that is to be used as a teacher with the lightweight network that is our supposed student. So far, our student did not intervene with the teacher, therefore this performance is achieved by the student itself. +# As we can see, based on test accuracy, we can now compare the deeper network that is to be used as a teacher with the lightweight network that is our supposed student. So far, our student has not intervened with the teacher, therefore this performance is achieved by the student itself. # The metrics so far can be seen with the following lines: print(f"Teacher accuracy: {test_accuracy_deep:.2f}%") print(f"Student accuracy: {test_accuracy_light_ce:.2f}%") ###################################################################### -# Now let's try to improve the test accuracy of the student network by somehow including the teacher. Knowledge distillation is the most straightforward technique to that end and it is based on the fact that both of these networks output a probability distribution over our classes. Therefore, the two networks share the exact same number of output neurons. -# The method works by including an additional loss to the traditional cross entropy loss that is based on the softmax output of the teacher network. It is assumed that the output activations of the properly trained teacher carry additional information that can be exploited by a student network while the latter is training. -# The original work argues that making use of ratios of smaller probabilities in the soft targets can help with the underlying objective of deep neural networks, which is creating a similarity structure over the data, in which we would expect similar objects to be mapped closer together. As an example for CIFAR-10, a truck could be mistaken for an automobile or even an airplane with its wheels present, but it is less likely to be mistaken for a dog. -# Therefore it makes sense to assume that valuable information resides not only on the top prediction of a properly trained model, but on the entire output distribution. This information is not exploited by cross entropy sufficiently well because the activations for the non predicted classes tend to be so small that propagated gradients do not meaningfully change the weights to construct this desirable vector space. +# Now let's try to improve the test accuracy of the student network by incorporating the teacher. Knowledge distillation is a straightforward technique to achieve this, based on the fact that both networks output a probability distribution over our classes. Therefore, the two networks share the same number of output neurons. +# The method works by incorporating an additional loss into the traditional cross entropy loss, which is based on the softmax output of the teacher network. The assumption is that the output activations of a properly trained teacher network carry additional information that can be leveraged by a student network during training.. +# The original work suggests that utilizing ratios of smaller probabilities in the soft targets can help achieve the underlying objective of deep neural networks, which is to create a similarity structure over the data where similar objects are mapped closer together. For example, in CIFAR-10, a truck could be mistaken for an automobile or airplane, if its wheels are present, but it is less likely to be mistaken for a dog. +# Therefore, it makes sense to assume that valuable information resides not only in the top prediction of a properly trained model but in the entire output distribution. However, cross entropy alone does not sufficiently exploit this information as the activations for non-predicted classes tend to be so small that propagated gradients do not meaningfully change the weights to construct this desirable vector space. # -# We continue by defining our first helper function that introduces a teacher-student dynamic. We need to include a few extra parameters: +# As we continue defining our first helper function that introduces a teacher-student dynamic, we need to include a few extra parameters: # -# - ``T``: Temperature controls the smoothness of the output distributions. Larger T leads to smoother distributions, thus smaller probabilities get a larger boost. +# - ``T``: Temperature controls the smoothness of the output distributions. Larger ``T`` leads to smoother distributions, thus smaller probabilities get a larger boost. # - ``soft_target_loss_weight``: A weight assigned to the extra objective we're about to include. # - ``ce_loss_weight``: A weight assigned to cross-entropy. Tuning these weights pushes the network towards optimizing for either objective. # @@ -316,7 +316,7 @@ def train_knowledge_distillation(teacher, student, train_loader, epochs, learnin print(f"Epoch {epoch+1}/{epochs}, Loss: {running_loss / len(train_loader)}") -# Apply train_knowledge_distillation with a temperature of 2. Arbitrarily set the weights to 0.75 for CE and 0.25 for distillation loss. +# Apply ``train_knowledge_distillation`` with a temperature of 2. Arbitrarily set the weights to 0.75 for CE and 0.25 for distillation loss. train_knowledge_distillation(teacher=nn_deep, student=new_nn_light, train_loader=train_loader, epochs=10, learning_rate=0.001, T=2, soft_target_loss_weight=0.25, ce_loss_weight=0.75, device=device) test_accuracy_light_ce_and_kd = test(new_nn_light, test_loader, device) @@ -327,19 +327,19 @@ def train_knowledge_distillation(teacher, student, train_loader, epochs, learnin ###################################################################### # Feel free to play around with the temperature parameter that controls the softness of the softmax function and the loss coefficients. -# In neural networks it is easy to include to include additional loss functions to the main objectives to achieve goals like better generalization. -# Let us try including an objective for the student, but now let us focus on their hidden states rather than their output layers. In the previous example, the teacher's representation after the convolutional layers has shape: +# In neural networks, it is easy to include to include additional loss functions to the main objectives to achieve goals like better generalization. +# Let's try including an objective for the student, but now let's focus on their hidden states rather than their output layers. In the previous example, the teacher's representation after the convolutional layers had the following shape: # # ``(batch_size, num_filters_for_last_conv_layer, 8, 8)`` # # Same for the student, with the only exception being the number of filters, where here we have fewer filters. # -# Our goal is to convey information from the teacher's representation to the student by including a naive loss, whose minimization implies that the flattened vectors that are subsequently passed to the classifiers have become more "similar" as the loss decreases. -# Of course the teacher does not update its weights, so the minimization depends only on the student's weights. The rationale behind this method is that we are operating under the assumption that the teacher model has a better internal representation that is -# unlikely to be achieved by the student without external intervention, therefore we artificially push the student to mimic the internal representation of the teacher. Whether or not this will end up helping the student is not straightforward though, because pushing the lightweight network +# Our goal is to convey information from the teacher's representation to the student by including a naive loss function, whose minimization implies that the flattened vectors that are subsequently passed to the classifiers have become more *similar* as the loss decreases.. +# Of course, the teacher does not update its weights, so the minimization depends only on the student's weights. The rationale behind this method is that we are operating under the assumption that the teacher model has a better internal representation that is +# unlikely to be achieved by the student without external intervention, therefore we artificially push the student to mimic the internal representation of the teacher. Whether or not this will end up helping the student is not straightforward, though, because pushing the lightweight network # to reach this point could be a good thing, assuming that we have found an internal representation that leads to better test accuracy, but it could also be harmful because the networks have different architectures and the student does not have the same learning capacity as the teacher. # In other words, there is no reason for these two vectors, the student's and the teacher's to match per component. The student could reach an internal representation that is a permutation of the teacher's and it would be just as efficient. -# Nonetheless, we can still run a quick experiment to figure out the impact of this method. We will be using the ``CosineEmbeddingLoss`` which is given by the formula: +# Nonetheless, we can still run a quick experiment to figure out the impact of this method. We will be using the ``CosineEmbeddingLoss`` which is given by the following formula: # # .. figure:: /../_static/img/knowledge_distillation/cosine_embedding_loss.png # :align: center @@ -347,11 +347,11 @@ def train_knowledge_distillation(teacher, student, train_loader, epochs, learnin # # Formula for CosineEmbeddingLoss # -# Obviously, there is one thing that we need to resolve first. When we applied distillation to the output layer we mentioned that both networks have the same number of neurons, equal to the number of classes. This is not the case for the layer following our convolutional layers. Here, the teacher has more neurons than the student -# after the flattening of the final convolutional layer. Our loss accepts 2 vectors of equal dimensionality as inputs, therefore we need to somehow match them. We will solve this by including an average pooling layer after the teacher's convolutional layer to reduce its dimensionality to match the one of the student. +# Obviously, there is one thing that we need to resolve first. When we applied distillation to the output layer we mentioned that both networks have the same number of neurons, equal to the number of classes. However, this is not the case for the layer following our convolutional layers. Here, the teacher has more neurons than the student +# after the flattening of the final convolutional layer. Our loss function accepts two vectors of equal dimensionality as inputs, therefore we need to somehow match them. We will solve this by including an average pooling layer after the teacher's convolutional layer to reduce its dimensionality to match that of the student.. # -# To proceed we will modify our model classes, or create new ones. -# Now the forward function returns not only the logits of the network but also the flattened hidden representation after the convolutional layer. We include the aforementioned pooling for the modified teacher. +# To proceed, we will modify our model classes, or create new ones. +# Now, the forward function returns not only the logits of the network but also the flattened hidden representation after the convolutional layer. We include the aforementioned pooling for the modified teacher. class ModifiedDeepNNCosine(nn.Module): def __init__(self, num_classes=10): @@ -421,15 +421,15 @@ def forward(self, x): print("Norm of 1st layer:", torch.norm(modified_light_nn.features[0].weight).item()) ###################################################################### -# Naturally, we need to change the train loop because now the model returns a tuple ``(logits, hidden_representation)`` or to be more precise +# Naturally, we need to change the train loop because now the model returns a tuple ``(logits, hidden_representation)`` or to be more precise: # # ``logits``: (``batch_size x total_classes``) # # ``hidden_representation``: (``batch_size x hidden_representation_size``) # -# In our case ``hidden_representation_size`` is ``1024``. This is the flattened feature map of the final convolutional layer of the student and as you can see it is the input for its classifier. -# It is ``1024`` for the teacher too, because we made it so with ``avg_pool1d`` from ``2048``. The loss applied here only affects the weights of the student prior to the loss calculation. In other words it does not affect the classifier of the student. -# The modified training loop is the following. +# In our case, ``hidden_representation_size`` is ``1024``. This is the flattened feature map of the final convolutional layer of the student, and as you can see, it is the input for its classifier. +# It is ``1024`` for the teacher too, because we made it so with ``avg_pool1d`` from ``2048``. The loss applied here only affects the weights of the student prior to the loss calculation. In other words, it does not affect the classifier of the student. +# The modified training loop is the following: # # .. figure:: /../_static/img/knowledge_distillation/cosine_loss_distillation.png # :align: center @@ -502,25 +502,25 @@ def test_multiple_outputs(model, test_loader, device): return accuracy ###################################################################### -# In this case we could easily include both knowledge distillation and cosine loss minimization in the same function. It is common to combine methods to achieve better performance in teacher-student paradigms. -# For now we can run a simple train-test session. +# In this case, we could easily include both knowledge distillation and cosine loss minimization in the same function. It is common to combine methods to achieve better performance in teacher-student paradigms. +# For now, we can run a simple train-test session. #Train and test the lightweight network with cross entropy loss train_cosine_loss(teacher=modified_nn_deep, student=modified_light_nn, train_loader=train_loader, epochs=10, learning_rate=0.001, hidden_rep_loss_weight=0.25, ce_loss_weight=0.75, device=device) test_accuracy_light_ce_and_cosine_loss = test_multiple_outputs(modified_light_nn, test_loader, device) ###################################################################### -# Our naive minimization does not guarantee better results for a number of reasons, one being the dimensionality of the vectors. Cosine similarity in general works better than Euclidean distance for vectors of larger dimensionality, but we were dealing with vectors with ``1024`` components each, so it is a lot harder to extract meaningful similarities. -# Furthermore as we mentioned, pushing towards a match of the hidden representation of the teacher and the student is not supported by theory. There are no good reasons why we should be aiming for an 1-1 match of these vectors. -# We will provide a final example of training intervention, by including an extra network called regressor. Our intention is to extract the feature map of the teacher after a convolutional layer, extract a feature map of the student after a convolutional layer and try to match these maps, but this time include a regressor between the networks. -# The regressor will be trainable and ideally will do a better job than our naive cosine loss minimization scheme. Its main job is to match the dimensionality of these feature maps so that we can properly define a loss function between the teacher and the student. Defining such loss function provides a teaching "path", which is basically a flow to back-propagate gradients, that will change the student's weights. -# Focusing on the output of the conv layers right before each classifier we have shapes: +# Our naive minimization does not guarantee better results for several reasons, one being the dimensionality of the vectors. Cosine similarity generally works better than Euclidean distance for vectors of higher dimensionality, but we were dealing with vectors with 1024 components each, so it is much harder to extract meaningful similarities. +# Furthermore, as we mentioned, pushing towards a match of the hidden representation of the teacher and the student is not supported by theory. There are no good reasons why we should be aiming for a 1:1 match of these vectors. +# We will provide a final example of training intervention by including an extra network called regressor. The objective is to first extract the feature map of the teacher after a convolutional layer, then extract a feature map of the student after a convolutional layer, and finally try to match these maps. However, this time, we will introduce a regressor between the networks to facilitate the matching process. +# The regressor will be trainable and ideally will do a better job than our naive cosine loss minimization scheme. Its main job is to match the dimensionality of these feature maps so that we can properly define a loss function between the teacher and the student. Defining such a loss function provides a teaching "path," which is basically a flow to back-propagate gradients that will change the student's weights. +# Focusing on the output of the convolutional layers right before each classifier, we have the following shapes: # # ``torch.Size([batch_size, 32, 8, 8])`` # # ``torch.Size([batch_size, 16, 8, 8])`` # -# so we have 32 filters for the teacher and 16 filters for the student. We will include a trainable layer that converts the feature map of the student to the shape of the feature map of the teacher. +# We have 32 filters for the teacher and 16 filters for the student. We will include a trainable layer that converts the feature map of the student to the shape of the feature map of the teacher. # In practice, we modify the lightweight class to return the hidden state after an intermediate regressor that matches the sizes of the convolutional feature maps and the teacher class to return the output of the final convolutional layer without pooling or flattening. # # .. figure:: /../_static/img/knowledge_distillation/fitnets_knowledge_distill.png @@ -588,7 +588,7 @@ def forward(self, x): return x, regressor_output ###################################################################### -# We have to update our train loop again. This time, we extract the regressor output of the student, the feature map of the teacher, we calculate the ``MSE`` on these tensors (they have the exact same shape so it's properly defined) and we back propagate gradients based on that loss, in addition to the regular cross entropy loss of the classification task. +# After that, we have to update our train loop again. This time, we extract the regressor output of the student, the feature map of the teacher, we calculate the ``MSE`` on these tensors (they have the exact same shape so it's properly defined) and we back propagate gradients based on that loss, in addition to the regular cross entropy loss of the classification task. def train_mse_loss(teacher, student, train_loader, epochs, learning_rate, feature_map_weight, ce_loss_weight, device): ce_loss = nn.CrossEntropyLoss() @@ -645,7 +645,7 @@ def train_mse_loss(teacher, student, train_loader, epochs, learning_rate, featur test_accuracy_light_ce_and_mse_loss = test_multiple_outputs(modified_light_nn_mse, test_loader, device) ###################################################################### -# It is expected for the final method to work better than CosineLoss because now, we allowed a trainable layer between the teacher and the student, which gives the student some wiggle room when it comes to learning, rather than pushing the student to copy the teacher's representation. +# It is expected that the final method will work better than ``CosineLoss`` because now we have allowed a trainable layer between the teacher and the student, which gives the student some wiggle room when it comes to learning, rather than pushing the student to copy the teacher's representation. # Including the extra network is the idea behind hint-based distillation. print(f"Teacher accuracy: {test_accuracy_deep:.2f}%") From abcdd60c01d4292753002272dc6142ec0b630930 Mon Sep 17 00:00:00 2001 From: AlexandrosChrtn Date: Fri, 18 Aug 2023 11:50:55 +0300 Subject: [PATCH 24/28] review fixes --- .../knowledge_distillation_tutorial.py | 213 ++++++++++++------ 1 file changed, 150 insertions(+), 63 deletions(-) diff --git a/beginner_source/knowledge_distillation_tutorial.py b/beginner_source/knowledge_distillation_tutorial.py index efa3a5c276c..1863546d9c1 100644 --- a/beginner_source/knowledge_distillation_tutorial.py +++ b/beginner_source/knowledge_distillation_tutorial.py @@ -37,13 +37,12 @@ import torchvision.transforms as transforms import torchvision.datasets as datasets -# Ignore warnings -import warnings -warnings.filterwarnings("ignore") - # Check if GPU is available, and if not, use the CPU device = torch.device("cuda" if torch.cuda.is_available() else "cpu") +# Loading CIFAR-10 +# ---------------- + ###################################################################### # CIFAR-10 is a popular image dataset with ten classes. Our objective is to predict one of the following classes for each input image. # @@ -53,11 +52,20 @@ # Example of CIFAR-10 images # # The input images are RGB, so they have 3 channels and are 32x32 pixels. Basically, each image is described by 3 x 32 x 32 = 3072 numbers ranging from 0 to 255. -# A common practice in neural networks is to normalize the input, which is done for multiple reasons, including avoiding saturation in commonly used activation functions and increasing numerical stability. Our normalization process consists of subtracting the mean and dividing by the standard deviation along each channel. -# The tensors "mean=[0.485, 0.456, 0.406]" and "std=[0.229, 0.224, 0.225]" were already computed, and they represent the mean and standard deviation of each channel in the predefined subset of CIFAR-10 intended to be the training set. Notice how we use these values for the test set as well, without recomputing the mean and standard deviation from scratch. -# This is because the network was trained on features produced by subtracting and dividing the numbers above, and we want to maintain consistency. Furthermore, in real life, we would not be able to compute the mean and standard deviation of the test set since, under our assumptions, this data would not be accessible at that point. +# A common practice in neural networks is to normalize the input, which is done for multiple reasons, +# including avoiding saturation in commonly used activation functions and increasing numerical stability. +# Our normalization process consists of subtracting the mean and dividing by the standard deviation along each channel. +# The tensors "mean=[0.485, 0.456, 0.406]" and "std=[0.229, 0.224, 0.225]" were already computed, +# and they represent the mean and standard deviation of each channel in the +# predefined subset of CIFAR-10 intended to be the training set. +# Notice how we use these values for the test set as well, without recomputing the mean and standard deviation from scratch. +# This is because the network was trained on features produced by subtracting and dividing the numbers above, and we want to maintain consistency. +# Furthermore, in real life, we would not be able to compute the mean and standard deviation of the test set since, +# under our assumptions, this data would not be accessible at that point. # -# As a closing point, we often refer to this held-out set as the validation set, and we use a separate set, called the test set, after optimizing a model's performance on the validation set. This is done to avoid selecting a model based on the greedy and biased optimization of a single metric. +# As a closing point, we often refer to this held-out set as the validation set, and we use a separate set, +# called the test set, after optimizing a model's performance on the validation set. +# This is done to avoid selecting a model based on the greedy and biased optimization of a single metric. # Below we are preprocessing data for CIFAR-10. We use an arbitrary batch size of 128. transforms_cifar = transforms.Compose([ @@ -70,7 +78,9 @@ test_dataset = datasets.CIFAR10(root='./data', train=False, download=True, transform=transforms_cifar) ######################################################################## -# .. note:: This section is for CPU users only who are interested in quick results. Use this option only if you're interested in a small scale experiment. Keep in mind the code should run fairly quickly using any GPU. Select only the first ``num_images_to_keep`` images from the train/test dataset +# .. note:: This section is for CPU users only who are interested in quick results. +# Use this option only if you're interested in a small scale experiment. +# Keep in mind the code should run fairly quickly using any GPU. Select only the first ``num_images_to_keep`` images from the train/test dataset # # .. code-block:: python # @@ -83,8 +93,11 @@ train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=128, shuffle=True, num_workers=2) test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=128, shuffle=False, num_workers=2) +# Defining model classes and utility functions +# -------------------------------------------- + ###################################################################### -# Next, we need to define our model classes. Several user-defined parameters need to be set here. We use two different architectures, keeping the number of filters fixed across our experiments to ensure fair comparisons.. +# Next, we need to define our model classes. Several user-defined parameters need to be set here. We use two different architectures, keeping the number of filters fixed across our experiments to ensure fair comparisons. # Both architectures are Convolutional Neural Networks (CNNs) with a different number of convolutional layers that serve as feature extractors, followed by a classifier with 10 classes. # The number of filters and neurons is smaller for the students. @@ -164,7 +177,6 @@ def train(model, train_loader, epochs, learning_rate, device): criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=learning_rate) - model.to(device) model.train() for epoch in range(epochs): @@ -208,28 +220,34 @@ def test(model, test_loader, device): print(f"Test Accuracy: {accuracy:.2f}%") return accuracy +# Cross-entropy runs +# ------------------ + ###################################################################### -# For reproducibility, we need to set the torch manual seed. We train networks using different methods, so to compare them fairly, it makes sense to initialize the networks with the same weights. +# For reproducibility, we need to set the torch manual seed. We train networks using different methods, so to compare them fairly, +# it makes sense to initialize the networks with the same weights. # Start by training the teacher network using cross-entropy: torch.manual_seed(42) -nn_deep = DeepNN(num_classes=10) +nn_deep = DeepNN(num_classes=10).to(device) train(nn_deep, train_loader, epochs=10, learning_rate=0.001, device=device) test_accuracy_deep = test(nn_deep, test_loader, device) # Instantiate the lightweight network: torch.manual_seed(42) -nn_light = LightNN(num_classes=10) +nn_light = LightNN(num_classes=10).to(device) ###################################################################### # We instantiate one more lightweight network model to compare their performances. -# Back propagation is sensitive to weight initialization, so we need to make sure these two networks have the exact same initialization. +# Back propagation is sensitive to weight initialization, +# so we need to make sure these two networks have the exact same initialization. torch.manual_seed(42) -new_nn_light = LightNN(num_classes=10) +new_nn_light = LightNN(num_classes=10).to(device) ###################################################################### -# To ensure we have created a copy of the first network, we inspect the norm of its first layer. If it matches, then we are safe to conclude that the networks are indeed the same. +# To ensure we have created a copy of the first network, we inspect the norm of its first layer. +# If it matches, then we are safe to conclude that the networks are indeed the same. # Print the norm of the first layer of the initial lightweight model print("Norm of 1st layer of nn_light:", torch.norm(nn_light.features[0].weight).item()) @@ -255,11 +273,24 @@ def test(model, test_loader, device): print(f"Teacher accuracy: {test_accuracy_deep:.2f}%") print(f"Student accuracy: {test_accuracy_light_ce:.2f}%") +# Knowledge distillation run +# -------------------------- + ###################################################################### -# Now let's try to improve the test accuracy of the student network by incorporating the teacher. Knowledge distillation is a straightforward technique to achieve this, based on the fact that both networks output a probability distribution over our classes. Therefore, the two networks share the same number of output neurons. -# The method works by incorporating an additional loss into the traditional cross entropy loss, which is based on the softmax output of the teacher network. The assumption is that the output activations of a properly trained teacher network carry additional information that can be leveraged by a student network during training.. -# The original work suggests that utilizing ratios of smaller probabilities in the soft targets can help achieve the underlying objective of deep neural networks, which is to create a similarity structure over the data where similar objects are mapped closer together. For example, in CIFAR-10, a truck could be mistaken for an automobile or airplane, if its wheels are present, but it is less likely to be mistaken for a dog. -# Therefore, it makes sense to assume that valuable information resides not only in the top prediction of a properly trained model but in the entire output distribution. However, cross entropy alone does not sufficiently exploit this information as the activations for non-predicted classes tend to be so small that propagated gradients do not meaningfully change the weights to construct this desirable vector space. +# Now let's try to improve the test accuracy of the student network by incorporating the teacher. +# Knowledge distillation is a straightforward technique to achieve this, +# based on the fact that both networks output a probability distribution over our classes. +# Therefore, the two networks share the same number of output neurons. +# The method works by incorporating an additional loss into the traditional cross entropy loss, +# which is based on the softmax output of the teacher network. +# The assumption is that the output activations of a properly trained teacher network carry additional information that can be leveraged by a student network during training. +# The original work suggests that utilizing ratios of smaller probabilities in the soft targets can help achieve the underlying objective of deep neural networks, +# which is to create a similarity structure over the data where similar objects are mapped closer together. +# For example, in CIFAR-10, a truck could be mistaken for an automobile or airplane, +# if its wheels are present, but it is less likely to be mistaken for a dog. +# Therefore, it makes sense to assume that valuable information resides not only in the top prediction of a properly trained model but in the entire output distribution. +# However, cross entropy alone does not sufficiently exploit this information as the activations for non-predicted classes +# tend to be so small that propagated gradients do not meaningfully change the weights to construct this desirable vector space. # # As we continue defining our first helper function that introduces a teacher-student dynamic, we need to include a few extra parameters: # @@ -277,8 +308,6 @@ def train_knowledge_distillation(teacher, student, train_loader, epochs, learnin ce_loss = nn.CrossEntropyLoss() optimizer = optim.Adam(student.parameters(), lr=learning_rate) - teacher.to(device) - student.to(device) teacher.eval() # Teacher set to evaluation mode student.train() # Student to train mode @@ -325,21 +354,31 @@ def train_knowledge_distillation(teacher, student, train_loader, epochs, learnin print(f"Student accuracy without teacher: {test_accuracy_light_ce:.2f}%") print(f"Student accuracy with CE + KD: {test_accuracy_light_ce_and_kd:.2f}%") +# Cosine loss minimization run +# ---------------------------- + ###################################################################### # Feel free to play around with the temperature parameter that controls the softness of the softmax function and the loss coefficients. # In neural networks, it is easy to include to include additional loss functions to the main objectives to achieve goals like better generalization. -# Let's try including an objective for the student, but now let's focus on their hidden states rather than their output layers. In the previous example, the teacher's representation after the convolutional layers had the following shape: +# Let's try including an objective for the student, but now let's focus on their hidden states rather than their output layers. In the previous example, +# the teacher's representation after the convolutional layers had the following shape: # # ``(batch_size, num_filters_for_last_conv_layer, 8, 8)`` # # Same for the student, with the only exception being the number of filters, where here we have fewer filters. # -# Our goal is to convey information from the teacher's representation to the student by including a naive loss function, whose minimization implies that the flattened vectors that are subsequently passed to the classifiers have become more *similar* as the loss decreases.. -# Of course, the teacher does not update its weights, so the minimization depends only on the student's weights. The rationale behind this method is that we are operating under the assumption that the teacher model has a better internal representation that is -# unlikely to be achieved by the student without external intervention, therefore we artificially push the student to mimic the internal representation of the teacher. Whether or not this will end up helping the student is not straightforward, though, because pushing the lightweight network -# to reach this point could be a good thing, assuming that we have found an internal representation that leads to better test accuracy, but it could also be harmful because the networks have different architectures and the student does not have the same learning capacity as the teacher. -# In other words, there is no reason for these two vectors, the student's and the teacher's to match per component. The student could reach an internal representation that is a permutation of the teacher's and it would be just as efficient. -# Nonetheless, we can still run a quick experiment to figure out the impact of this method. We will be using the ``CosineEmbeddingLoss`` which is given by the following formula: +# Our goal is to convey information from the teacher's representation to the student by including a naive loss function, +# whose minimization implies that the flattened vectors that are subsequently passed to the classifiers have become more *similar* as the loss decreases. +# Of course, the teacher does not update its weights, so the minimization depends only on the student's weights. +# The rationale behind this method is that we are operating under the assumption that the teacher model has a better internal representation that is +# unlikely to be achieved by the student without external intervention, therefore we artificially push the student to mimic the internal representation of the teacher. +# Whether or not this will end up helping the student is not straightforward, though, because pushing the lightweight network +# to reach this point could be a good thing, assuming that we have found an internal representation that leads to better test accuracy, +# but it could also be harmful because the networks have different architectures and the student does not have the same learning capacity as the teacher. +# In other words, there is no reason for these two vectors, the student's and the teacher's to match per component. +# The student could reach an internal representation that is a permutation of the teacher's and it would be just as efficient. +# Nonetheless, we can still run a quick experiment to figure out the impact of this method. +# We will be using the ``CosineEmbeddingLoss`` which is given by the following formula: # # .. figure:: /../_static/img/knowledge_distillation/cosine_embedding_loss.png # :align: center @@ -347,8 +386,11 @@ def train_knowledge_distillation(teacher, student, train_loader, epochs, learnin # # Formula for CosineEmbeddingLoss # -# Obviously, there is one thing that we need to resolve first. When we applied distillation to the output layer we mentioned that both networks have the same number of neurons, equal to the number of classes. However, this is not the case for the layer following our convolutional layers. Here, the teacher has more neurons than the student -# after the flattening of the final convolutional layer. Our loss function accepts two vectors of equal dimensionality as inputs, therefore we need to somehow match them. We will solve this by including an average pooling layer after the teacher's convolutional layer to reduce its dimensionality to match that of the student.. +# Obviously, there is one thing that we need to resolve first. +# When we applied distillation to the output layer we mentioned that both networks have the same number of neurons, equal to the number of classes. +# However, this is not the case for the layer following our convolutional layers. Here, the teacher has more neurons than the student +# after the flattening of the final convolutional layer. Our loss function accepts two vectors of equal dimensionality as inputs, +# therefore we need to somehow match them. We will solve this by including an average pooling layer after the teacher's convolutional layer to reduce its dimensionality to match that of the student. # # To proceed, we will modify our model classes, or create new ones. # Now, the forward function returns not only the logits of the network but also the flattened hidden representation after the convolutional layer. We include the aforementioned pooling for the modified teacher. @@ -408,7 +450,7 @@ def forward(self, x): return x, flattened_conv_output # We do not have to train the modified deep network from scratch of course, we just load its weights from the trained instance -modified_nn_deep = ModifiedDeepNNCosine(num_classes=10) +modified_nn_deep = ModifiedDeepNNCosine(num_classes=10).to(device) modified_nn_deep.load_state_dict(nn_deep.state_dict()) # Once again ensure the norm of the first layer is the same for both networks @@ -417,18 +459,34 @@ def forward(self, x): # Initialize a modified lightweight network with the same seed as our other lightweight instances. This will be trained from scratch to examine the effectiveness of cosine loss minimization. torch.manual_seed(42) -modified_light_nn = ModifiedLightNNCosine(num_classes=10) +modified_light_nn = ModifiedLightNNCosine(num_classes=10).to(device) print("Norm of 1st layer:", torch.norm(modified_light_nn.features[0].weight).item()) ###################################################################### -# Naturally, we need to change the train loop because now the model returns a tuple ``(logits, hidden_representation)`` or to be more precise: -# -# ``logits``: (``batch_size x total_classes``) -# -# ``hidden_representation``: (``batch_size x hidden_representation_size``) -# -# In our case, ``hidden_representation_size`` is ``1024``. This is the flattened feature map of the final convolutional layer of the student, and as you can see, it is the input for its classifier. -# It is ``1024`` for the teacher too, because we made it so with ``avg_pool1d`` from ``2048``. The loss applied here only affects the weights of the student prior to the loss calculation. In other words, it does not affect the classifier of the student. +# Naturally, we need to change the train loop because now the model returns a tuple ``(logits, hidden_representation)``. Using a sample input tensor +# we can print their shapes. + +# Create a sample input tensor +sample_input = torch.randn(128, 3, 32, 32).to(device) # Batch size: 128, Filters: 3, Image size: 32x32 + +# Pass the input through the student +logits, hidden_representation = modified_light_nn(sample_input) + +# Print the shapes of the tensors +print("Student logits shape:", logits.shape) # batch_size x total_classes +print("Student hidden representation shape:", hidden_representation.shape) # batch_size x hidden_representation_size + +# Pass the input through the teacher +logits, hidden_representation = modified_nn_deep(sample_input) + +# Print the shapes of the tensors +print("Teacher logits shape:", logits.shape) # batch_size x total_classes +print("Teacher hidden representation shape:", hidden_representation.shape) # batch_size x hidden_representation_size + +###################################################################### +# In our case, ``hidden_representation_size`` is ``1024``. This is the flattened feature map of the final convolutional layer of the student and as you can see, +# it is the input for its classifier. It is ``1024`` for the teacher too, because we made it so with ``avg_pool1d`` from ``2048``. +# The loss applied here only affects the weights of the student prior to the loss calculation. In other words, it does not affect the classifier of the student. # The modified training loop is the following: # # .. figure:: /../_static/img/knowledge_distillation/cosine_loss_distillation.png @@ -491,7 +549,7 @@ def test_multiple_outputs(model, test_loader, device): for inputs, labels in test_loader: inputs, labels = inputs.to(device), labels.to(device) - outputs, _ = model(inputs) # In this line we disregard the second tensor of the tuple + outputs, _ = model(inputs) # Disregard the second tensor of the tuple _, predicted = torch.max(outputs.data, 1) total += labels.size(0) @@ -505,23 +563,42 @@ def test_multiple_outputs(model, test_loader, device): # In this case, we could easily include both knowledge distillation and cosine loss minimization in the same function. It is common to combine methods to achieve better performance in teacher-student paradigms. # For now, we can run a simple train-test session. -#Train and test the lightweight network with cross entropy loss +# Train and test the lightweight network with cross entropy loss train_cosine_loss(teacher=modified_nn_deep, student=modified_light_nn, train_loader=train_loader, epochs=10, learning_rate=0.001, hidden_rep_loss_weight=0.25, ce_loss_weight=0.75, device=device) test_accuracy_light_ce_and_cosine_loss = test_multiple_outputs(modified_light_nn, test_loader, device) +# Intermediate regressor run +# -------------------------- + ###################################################################### -# Our naive minimization does not guarantee better results for several reasons, one being the dimensionality of the vectors. Cosine similarity generally works better than Euclidean distance for vectors of higher dimensionality, but we were dealing with vectors with 1024 components each, so it is much harder to extract meaningful similarities. -# Furthermore, as we mentioned, pushing towards a match of the hidden representation of the teacher and the student is not supported by theory. There are no good reasons why we should be aiming for a 1:1 match of these vectors. -# We will provide a final example of training intervention by including an extra network called regressor. The objective is to first extract the feature map of the teacher after a convolutional layer, then extract a feature map of the student after a convolutional layer, and finally try to match these maps. However, this time, we will introduce a regressor between the networks to facilitate the matching process. -# The regressor will be trainable and ideally will do a better job than our naive cosine loss minimization scheme. Its main job is to match the dimensionality of these feature maps so that we can properly define a loss function between the teacher and the student. Defining such a loss function provides a teaching "path," which is basically a flow to back-propagate gradients that will change the student's weights. -# Focusing on the output of the convolutional layers right before each classifier, we have the following shapes: -# -# ``torch.Size([batch_size, 32, 8, 8])`` -# -# ``torch.Size([batch_size, 16, 8, 8])`` -# -# We have 32 filters for the teacher and 16 filters for the student. We will include a trainable layer that converts the feature map of the student to the shape of the feature map of the teacher. -# In practice, we modify the lightweight class to return the hidden state after an intermediate regressor that matches the sizes of the convolutional feature maps and the teacher class to return the output of the final convolutional layer without pooling or flattening. +# Our naive minimization does not guarantee better results for several reasons, one being the dimensionality of the vectors. +# Cosine similarity generally works better than Euclidean distance for vectors of higher dimensionality, +# but we were dealing with vectors with 1024 components each, so it is much harder to extract meaningful similarities. +# Furthermore, as we mentioned, pushing towards a match of the hidden representation of the teacher and the student is not supported by theory. +# There are no good reasons why we should be aiming for a 1:1 match of these vectors. +# We will provide a final example of training intervention by including an extra network called regressor. +# The objective is to first extract the feature map of the teacher after a convolutional layer, +# then extract a feature map of the student after a convolutional layer, and finally try to match these maps. +# However, this time, we will introduce a regressor between the networks to facilitate the matching process. +# The regressor will be trainable and ideally will do a better job than our naive cosine loss minimization scheme. +# Its main job is to match the dimensionality of these feature maps so that we can properly define a loss function between the teacher and the student. +# Defining such a loss function provides a teaching "path," which is basically a flow to back-propagate gradients that will change the student's weights. +# Focusing on the output of the convolutional layers right before each classifier for our original networks, we have the following shapes: +# + +# Pass the sample input only from the convolutional feature extractor +convolutional_fe_output_student = nn_light.features(sample_input) +convolutional_fe_output_teacher = nn_deep.features(sample_input) + +# Print their shapes +print("Student's feature extractor output shape: ", convolutional_fe_output_student.shape) +print("Teacher's feature extractor output shape: ", convolutional_fe_output_teacher.shape) + +###################################################################### +# We have 32 filters for the teacher and 16 filters for the student. +# We will include a trainable layer that converts the feature map of the student to the shape of the feature map of the teacher. +# In practice, we modify the lightweight class to return the hidden state after an intermediate regressor that matches the sizes of the convolutional +# feature maps and the teacher class to return the output of the final convolutional layer without pooling or flattening. # # .. figure:: /../_static/img/knowledge_distillation/fitnets_knowledge_distill.png # :align: center @@ -588,7 +665,9 @@ def forward(self, x): return x, regressor_output ###################################################################### -# After that, we have to update our train loop again. This time, we extract the regressor output of the student, the feature map of the teacher, we calculate the ``MSE`` on these tensors (they have the exact same shape so it's properly defined) and we back propagate gradients based on that loss, in addition to the regular cross entropy loss of the classification task. +# After that, we have to update our train loop again. This time, we extract the regressor output of the student, the feature map of the teacher, +# we calculate the ``MSE`` on these tensors (they have the exact same shape so it's properly defined) and we back propagate gradients based on that loss, +# in addition to the regular cross entropy loss of the classification task. def train_mse_loss(teacher, student, train_loader, epochs, learning_rate, feature_map_weight, ce_loss_weight, device): ce_loss = nn.CrossEntropyLoss() @@ -634,10 +713,10 @@ def train_mse_loss(teacher, student, train_loader, epochs, learning_rate, featur # Initialize a ModifiedLightNNRegressor torch.manual_seed(42) -modified_light_nn_mse = ModifiedLightNNRegressor(num_classes=10) +modified_light_nn_mse = ModifiedLightNNRegressor(num_classes=10).to(device) # We do not have to train the modified deep network from scratch of course, we just load its weights from the trained instance -modified_nn_deep_reg = ModifiedDeepNNRegressor(num_classes=10) +modified_nn_deep_reg = ModifiedDeepNNRegressor(num_classes=10).to(device) modified_nn_deep_reg.load_state_dict(nn_deep.state_dict()) # Train and test once again @@ -645,7 +724,8 @@ def train_mse_loss(teacher, student, train_loader, epochs, learning_rate, featur test_accuracy_light_ce_and_mse_loss = test_multiple_outputs(modified_light_nn_mse, test_loader, device) ###################################################################### -# It is expected that the final method will work better than ``CosineLoss`` because now we have allowed a trainable layer between the teacher and the student, which gives the student some wiggle room when it comes to learning, rather than pushing the student to copy the teacher's representation. +# It is expected that the final method will work better than ``CosineLoss`` because now we have allowed a trainable layer between the teacher and the student, +# which gives the student some wiggle room when it comes to learning, rather than pushing the student to copy the teacher's representation. # Including the extra network is the idea behind hint-based distillation. print(f"Teacher accuracy: {test_accuracy_deep:.2f}%") @@ -654,10 +734,17 @@ def train_mse_loss(teacher, student, train_loader, epochs, learning_rate, featur print(f"Student accuracy with CE + CosineLoss: {test_accuracy_light_ce_and_cosine_loss:.2f}%") print(f"Student accuracy with CE + RegressorMSE: {test_accuracy_light_ce_and_mse_loss:.2f}%") +# Conclusions +# -------------------------------------------- + ###################################################################### -# None of the methods above increases the number of parameters for the network or inference time, so the performance increase comes at the little cost of calculating gradients during training. In ML applications, we mostly care about inference time because training happens before the model deployment. -# If our lightweight model is still too heavy for deployment, we can apply different ideas, such as post-training quantization. Additional losses can be applied in many tasks, not just classification, and you can experiment with quantities like coefficients, temperature, or number of neurons. -# Feel free to tune any numbers in the tutorial above, but keep in mind, if you change the number of neurons / filters chances are a shape mismatch might occur. +# None of the methods above increases the number of parameters for the network or inference time, +# so the performance increase comes at the little cost of calculating gradients during training. +# In ML applications, we mostly care about inference time because training happens before the model deployment. +# If our lightweight model is still too heavy for deployment, we can apply different ideas, such as post-training quantization. +# Additional losses can be applied in many tasks, not just classification, and you can experiment with quantities like coefficients, +# temperature, or number of neurons. # Feel free to tune any numbers in the tutorial above, +# but keep in mind, if you change the number of neurons / filters chances are a shape mismatch might occur. # # For more information, see: # * `Hinton, G., Vinyals, O., Dean, J.: Distilling the knowledge in a neural network. In: Neural Information Processing System Deep Learning Workshop (2015) `_ From bf798787d1cc5a0e1e009e534e79eee8b07a259f Mon Sep 17 00:00:00 2001 From: AlexandrosChrtn Date: Fri, 18 Aug 2023 12:25:24 +0300 Subject: [PATCH 25/28] review fixes --- .../knowledge_distillation_tutorial.py | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/beginner_source/knowledge_distillation_tutorial.py b/beginner_source/knowledge_distillation_tutorial.py index 1863546d9c1..8ec4dc40a20 100644 --- a/beginner_source/knowledge_distillation_tutorial.py +++ b/beginner_source/knowledge_distillation_tutorial.py @@ -40,10 +40,9 @@ # Check if GPU is available, and if not, use the CPU device = torch.device("cuda" if torch.cuda.is_available() else "cpu") +###################################################################### # Loading CIFAR-10 # ---------------- - -###################################################################### # CIFAR-10 is a popular image dataset with ten classes. Our objective is to predict one of the following classes for each input image. # # .. figure:: /../_static/img/cifar10.png @@ -78,9 +77,7 @@ test_dataset = datasets.CIFAR10(root='./data', train=False, download=True, transform=transforms_cifar) ######################################################################## -# .. note:: This section is for CPU users only who are interested in quick results. -# Use this option only if you're interested in a small scale experiment. -# Keep in mind the code should run fairly quickly using any GPU. Select only the first ``num_images_to_keep`` images from the train/test dataset +# .. note:: This section is for CPU users only who are interested in quick results. Use this option only if you're interested in a small scale experiment. Keep in mind the code should run fairly quickly using any GPU. Select only the first ``num_images_to_keep`` images from the train/test dataset # # .. code-block:: python # @@ -93,10 +90,9 @@ train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=128, shuffle=True, num_workers=2) test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=128, shuffle=False, num_workers=2) +###################################################################### # Defining model classes and utility functions # -------------------------------------------- - -###################################################################### # Next, we need to define our model classes. Several user-defined parameters need to be set here. We use two different architectures, keeping the number of filters fixed across our experiments to ensure fair comparisons. # Both architectures are Convolutional Neural Networks (CNNs) with a different number of convolutional layers that serve as feature extractors, followed by a classifier with 10 classes. # The number of filters and neurons is smaller for the students. @@ -189,7 +185,7 @@ def train(model, train_loader, epochs, learning_rate, device): optimizer.zero_grad() outputs = model(inputs) - # outputs: Output of the network for the collection of images. A vector of dimensionality batch_size + # outputs: Output of the network for the collection of images. A tensor of dimensionality batch_size x num_classes # labels: The actual labels of the images. Vector of dimensionality batch_size loss = criterion(outputs, labels) loss.backward() @@ -220,10 +216,9 @@ def test(model, test_loader, device): print(f"Test Accuracy: {accuracy:.2f}%") return accuracy +###################################################################### # Cross-entropy runs # ------------------ - -###################################################################### # For reproducibility, we need to set the torch manual seed. We train networks using different methods, so to compare them fairly, # it makes sense to initialize the networks with the same weights. # Start by training the teacher network using cross-entropy: @@ -273,10 +268,9 @@ def test(model, test_loader, device): print(f"Teacher accuracy: {test_accuracy_deep:.2f}%") print(f"Student accuracy: {test_accuracy_light_ce:.2f}%") +###################################################################### # Knowledge distillation run # -------------------------- - -###################################################################### # Now let's try to improve the test accuracy of the student network by incorporating the teacher. # Knowledge distillation is a straightforward technique to achieve this, # based on the fact that both networks output a probability distribution over our classes. @@ -354,10 +348,9 @@ def train_knowledge_distillation(teacher, student, train_loader, epochs, learnin print(f"Student accuracy without teacher: {test_accuracy_light_ce:.2f}%") print(f"Student accuracy with CE + KD: {test_accuracy_light_ce_and_kd:.2f}%") +###################################################################### # Cosine loss minimization run # ---------------------------- - -###################################################################### # Feel free to play around with the temperature parameter that controls the softness of the softmax function and the loss coefficients. # In neural networks, it is easy to include to include additional loss functions to the main objectives to achieve goals like better generalization. # Let's try including an objective for the student, but now let's focus on their hidden states rather than their output layers. In the previous example, @@ -567,10 +560,9 @@ def test_multiple_outputs(model, test_loader, device): train_cosine_loss(teacher=modified_nn_deep, student=modified_light_nn, train_loader=train_loader, epochs=10, learning_rate=0.001, hidden_rep_loss_weight=0.25, ce_loss_weight=0.75, device=device) test_accuracy_light_ce_and_cosine_loss = test_multiple_outputs(modified_light_nn, test_loader, device) +###################################################################### # Intermediate regressor run # -------------------------- - -###################################################################### # Our naive minimization does not guarantee better results for several reasons, one being the dimensionality of the vectors. # Cosine similarity generally works better than Euclidean distance for vectors of higher dimensionality, # but we were dealing with vectors with 1024 components each, so it is much harder to extract meaningful similarities. @@ -734,10 +726,9 @@ def train_mse_loss(teacher, student, train_loader, epochs, learning_rate, featur print(f"Student accuracy with CE + CosineLoss: {test_accuracy_light_ce_and_cosine_loss:.2f}%") print(f"Student accuracy with CE + RegressorMSE: {test_accuracy_light_ce_and_mse_loss:.2f}%") +###################################################################### # Conclusions # -------------------------------------------- - -###################################################################### # None of the methods above increases the number of parameters for the network or inference time, # so the performance increase comes at the little cost of calculating gradients during training. # In ML applications, we mostly care about inference time because training happens before the model deployment. From beec23ca7dfa9b8c5d124aa43d6cb754b1977786 Mon Sep 17 00:00:00 2001 From: AlexandrosChrtn Date: Fri, 18 Aug 2023 14:08:07 +0300 Subject: [PATCH 26/28] review fixes --- beginner_source/knowledge_distillation_tutorial.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/beginner_source/knowledge_distillation_tutorial.py b/beginner_source/knowledge_distillation_tutorial.py index 8ec4dc40a20..cf49bf2efa9 100644 --- a/beginner_source/knowledge_distillation_tutorial.py +++ b/beginner_source/knowledge_distillation_tutorial.py @@ -353,13 +353,7 @@ def train_knowledge_distillation(teacher, student, train_loader, epochs, learnin # ---------------------------- # Feel free to play around with the temperature parameter that controls the softness of the softmax function and the loss coefficients. # In neural networks, it is easy to include to include additional loss functions to the main objectives to achieve goals like better generalization. -# Let's try including an objective for the student, but now let's focus on their hidden states rather than their output layers. In the previous example, -# the teacher's representation after the convolutional layers had the following shape: -# -# ``(batch_size, num_filters_for_last_conv_layer, 8, 8)`` -# -# Same for the student, with the only exception being the number of filters, where here we have fewer filters. -# +# Let's try including an objective for the student, but now let's focus on their hidden states rather than their output layers. # Our goal is to convey information from the teacher's representation to the student by including a naive loss function, # whose minimization implies that the flattened vectors that are subsequently passed to the classifiers have become more *similar* as the loss decreases. # Of course, the teacher does not update its weights, so the minimization depends only on the student's weights. @@ -734,7 +728,7 @@ def train_mse_loss(teacher, student, train_loader, epochs, learning_rate, featur # In ML applications, we mostly care about inference time because training happens before the model deployment. # If our lightweight model is still too heavy for deployment, we can apply different ideas, such as post-training quantization. # Additional losses can be applied in many tasks, not just classification, and you can experiment with quantities like coefficients, -# temperature, or number of neurons. # Feel free to tune any numbers in the tutorial above, +# temperature, or number of neurons. Feel free to tune any numbers in the tutorial above, # but keep in mind, if you change the number of neurons / filters chances are a shape mismatch might occur. # # For more information, see: From c79d2986616a3171a204f45aff1a56c4115e690c Mon Sep 17 00:00:00 2001 From: AlexandrosChrtn Date: Fri, 18 Aug 2023 14:39:35 +0300 Subject: [PATCH 27/28] final review fixes --- .../knowledge_distillation_tutorial.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/beginner_source/knowledge_distillation_tutorial.py b/beginner_source/knowledge_distillation_tutorial.py index cf49bf2efa9..efd895ed05e 100644 --- a/beginner_source/knowledge_distillation_tutorial.py +++ b/beginner_source/knowledge_distillation_tutorial.py @@ -446,8 +446,8 @@ def forward(self, x): # Initialize a modified lightweight network with the same seed as our other lightweight instances. This will be trained from scratch to examine the effectiveness of cosine loss minimization. torch.manual_seed(42) -modified_light_nn = ModifiedLightNNCosine(num_classes=10).to(device) -print("Norm of 1st layer:", torch.norm(modified_light_nn.features[0].weight).item()) +modified_nn_light = ModifiedLightNNCosine(num_classes=10).to(device) +print("Norm of 1st layer:", torch.norm(modified_nn_light.features[0].weight).item()) ###################################################################### # Naturally, we need to change the train loop because now the model returns a tuple ``(logits, hidden_representation)``. Using a sample input tensor @@ -457,7 +457,7 @@ def forward(self, x): sample_input = torch.randn(128, 3, 32, 32).to(device) # Batch size: 128, Filters: 3, Image size: 32x32 # Pass the input through the student -logits, hidden_representation = modified_light_nn(sample_input) +logits, hidden_representation = modified_nn_light(sample_input) # Print the shapes of the tensors print("Student logits shape:", logits.shape) # batch_size x total_classes @@ -551,8 +551,8 @@ def test_multiple_outputs(model, test_loader, device): # For now, we can run a simple train-test session. # Train and test the lightweight network with cross entropy loss -train_cosine_loss(teacher=modified_nn_deep, student=modified_light_nn, train_loader=train_loader, epochs=10, learning_rate=0.001, hidden_rep_loss_weight=0.25, ce_loss_weight=0.75, device=device) -test_accuracy_light_ce_and_cosine_loss = test_multiple_outputs(modified_light_nn, test_loader, device) +train_cosine_loss(teacher=modified_nn_deep, student=modified_nn_light, train_loader=train_loader, epochs=10, learning_rate=0.001, hidden_rep_loss_weight=0.25, ce_loss_weight=0.75, device=device) +test_accuracy_light_ce_and_cosine_loss = test_multiple_outputs(modified_nn_light, test_loader, device) ###################################################################### # Intermediate regressor run @@ -699,15 +699,15 @@ def train_mse_loss(teacher, student, train_loader, epochs, learning_rate, featur # Initialize a ModifiedLightNNRegressor torch.manual_seed(42) -modified_light_nn_mse = ModifiedLightNNRegressor(num_classes=10).to(device) +modified_nn_light_reg = ModifiedLightNNRegressor(num_classes=10).to(device) # We do not have to train the modified deep network from scratch of course, we just load its weights from the trained instance modified_nn_deep_reg = ModifiedDeepNNRegressor(num_classes=10).to(device) modified_nn_deep_reg.load_state_dict(nn_deep.state_dict()) # Train and test once again -train_mse_loss(teacher=modified_nn_deep_reg, student=modified_light_nn_mse, train_loader=train_loader, epochs=10, learning_rate=0.001, feature_map_weight=0.25, ce_loss_weight=0.75, device=device) -test_accuracy_light_ce_and_mse_loss = test_multiple_outputs(modified_light_nn_mse, test_loader, device) +train_mse_loss(teacher=modified_nn_deep_reg, student=modified_nn_light_reg, train_loader=train_loader, epochs=10, learning_rate=0.001, feature_map_weight=0.25, ce_loss_weight=0.75, device=device) +test_accuracy_light_ce_and_mse_loss = test_multiple_outputs(modified_nn_light_reg, test_loader, device) ###################################################################### # It is expected that the final method will work better than ``CosineLoss`` because now we have allowed a trainable layer between the teacher and the student, From cafd91e03a9dc591ee839abff53fe5ba0e817056 Mon Sep 17 00:00:00 2001 From: Svetlana Karslioglu Date: Tue, 22 Aug 2023 08:25:39 -0700 Subject: [PATCH 28/28] Apply suggestions from code review Editorial clean up --- .../knowledge_distillation_tutorial.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/beginner_source/knowledge_distillation_tutorial.py b/beginner_source/knowledge_distillation_tutorial.py index efd895ed05e..304ac661d4e 100644 --- a/beginner_source/knowledge_distillation_tutorial.py +++ b/beginner_source/knowledge_distillation_tutorial.py @@ -29,7 +29,7 @@ # # * 1 GPU, 4GB of memory # * PyTorch v2.0 or later -# * CIFAR-10 dataset (downloaded by the script and saved it in a directory called ``/data``) +# * CIFAR-10 dataset (downloaded by the script and saved in a directory called ``/data``) import torch import torch.nn as nn @@ -156,7 +156,7 @@ def forward(self, x): # One function is called ``train`` and takes the following arguments: # # - ``model``: A model instance to train (update its weights) via this function. -# - ``train_loader``: we defined our ``train_loader`` above, and its job is to feed the data into the model. +# - ``train_loader``: We defined our ``train_loader`` above, and its job is to feed the data into the model. # - ``epochs``: How many times we loop over the dataset. # - ``learning_rate``: The learning rate determines how large our steps towards convergence should be. Too large or too small steps can be detrimental. # - ``device``: Determines the device to run the workload on. Can be either CPU or GPU depending on availability. @@ -166,7 +166,7 @@ def forward(self, x): # .. figure:: /../_static/img/knowledge_distillation/ce_only.png # :align: center # -# Train both networks with Cross-Entropy. The student will be used as a baseline +# Train both networks with Cross-Entropy. The student will be used as a baseline: # def train(model, train_loader, epochs, learning_rate, device): @@ -250,14 +250,14 @@ def test(model, test_loader, device): print("Norm of 1st layer of new_nn_light:", torch.norm(new_nn_light.features[0].weight).item()) ###################################################################### -# Print the total number of parameters in each model. +# Print the total number of parameters in each model: total_params_deep = "{:,}".format(sum(p.numel() for p in nn_deep.parameters())) print(f"DeepNN parameters: {total_params_deep}") total_params_light = "{:,}".format(sum(p.numel() for p in nn_light.parameters())) print(f"LightNN parameters: {total_params_light}") ###################################################################### -# Train and test the lightweight network with cross entropy loss +# Train and test the lightweight network with cross entropy loss: train(nn_light, train_loader, epochs=10, learning_rate=0.001, device=device) test_accuracy_light_ce = test(nn_light, test_loader, device) @@ -295,7 +295,7 @@ def test(model, test_loader, device): # .. figure:: /../_static/img/knowledge_distillation/distillation_output_loss.png # :align: center # -# Distillation loss is calculated from the logits of the networks. It only returns gradients to the student +# Distillation loss is calculated from the logits of the networks. It only returns gradients to the student: # def train_knowledge_distillation(teacher, student, train_loader, epochs, learning_rate, T, soft_target_loss_weight, ce_loss_weight, device): @@ -479,7 +479,7 @@ def forward(self, x): # .. figure:: /../_static/img/knowledge_distillation/cosine_loss_distillation.png # :align: center # -# In Cosine Loss minimization we want to maximize the cosine similarity of the two representations by returning gradients to the student +# In Cosine Loss minimization, we want to maximize the cosine similarity of the two representations by returning gradients to the student: # def train_cosine_loss(teacher, student, train_loader, epochs, learning_rate, hidden_rep_loss_weight, ce_loss_weight, device): @@ -589,7 +589,7 @@ def test_multiple_outputs(model, test_loader, device): # .. figure:: /../_static/img/knowledge_distillation/fitnets_knowledge_distill.png # :align: center # -# The trainable layer matches the shapes of the intermediate tensors and Mean Squared Error ``(MSE)`` is properly defined +# The trainable layer matches the shapes of the intermediate tensors and Mean Squared Error (MSE) is properly defined: # class ModifiedDeepNNRegressor(nn.Module): @@ -721,7 +721,7 @@ def train_mse_loss(teacher, student, train_loader, epochs, learning_rate, featur print(f"Student accuracy with CE + RegressorMSE: {test_accuracy_light_ce_and_mse_loss:.2f}%") ###################################################################### -# Conclusions +# Conclusion # -------------------------------------------- # None of the methods above increases the number of parameters for the network or inference time, # so the performance increase comes at the little cost of calculating gradients during training. @@ -732,6 +732,7 @@ def train_mse_loss(teacher, student, train_loader, epochs, learning_rate, featur # but keep in mind, if you change the number of neurons / filters chances are a shape mismatch might occur. # # For more information, see: +# # * `Hinton, G., Vinyals, O., Dean, J.: Distilling the knowledge in a neural network. In: Neural Information Processing System Deep Learning Workshop (2015) `_ # # * `Romero, A., Ballas, N., Kahou, S.E., Chassang, A., Gatta, C., Bengio, Y.: Fitnets: Hints for thin deep nets. In: Proceedings of the International Conference on Learning Representations (2015) `_