随着机器学习模型变得越来越复杂和强大。提高大型复杂模型在小型数据集上性能的有效技术是知识蒸馏,它涉及训练更小、更高效的模型来模仿更大的“教师”模型的行为。在本文中,我们将探讨知识蒸馏的概念以及如何在PyTorch中实现它。我们将看到如何使用它来将大型、繁重的模型压缩成更小、更高效的模型,同时仍然保留原始模型的准确性和性能。我们首先定义知识蒸馏要解决的问题。我们训练了一个大型深度神经网络来执行复杂的任务,例如图像分类或机器翻译。这个模型可能有几千层和几百万个参数,这使得它很难部署在现实世界的应用程序、边缘设备等。而且这个超大的模型还需要大量的计算资源来运行,这使得它无法在一些资源受限的平台上工作。解决这个问题的一种方法是使用知识蒸馏将大型模型压缩成较小的模型。此过程涉及训练较小的模型以模仿较大模型针对给定任务的行为。我们将使用Kaggle的胸部X光数据集进行肺炎分类作为知识蒸馏的示例。我们使用的数据集分为3个文件夹(train、test、val),并包含每个图像类别(肺炎/正常)的子文件夹。有5,863张X射线图像(JPEG)和2个类别(肺炎/正常)。比较这两个类的图片:数据的加载和预处理与我们是否使用知识蒸馏或特定模型无关,代码片段可能如下所示:transforms_train=transforms.Compose([transforms.Resize((224,224)),transforms.RandomHorizo??ntalFlip(),transforms.ToTensor(),transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])])transforms_test=transforms.Compose([转换.Resize((224,224)),transforms.ToTensor(),transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])])train_data=ImageFolder(root=train_dir,transform=transforms_train)测试数据=ImageFolder(root=test_dir,transform=transforms_test)train_loader=DataLoader(train_data,batch_size=32,shuffle=True)test_loader=DataLoader(test_data,batch_size=32,shuffle=True)教师模型在这个背景下我们使用Resnet-18和这个数据在片场进行了微调。importtorchimporttorch.nnasnnimporttorchvisionclassTeacherNet(nn.Module):def__init__(self):super().__init__()self.model=torchvision.models.resnet18(pretrained=True)用于自身参数。model.parameters():params.requires_grad_=Falsen_filters=self.model.fc.in_featuresself.model.fc=nn.Linear(n_filters,2)defforward(self,x):x=self.model(x)returnx微调训练的代码如下deftrain(model,train_loader,test_loader,optimizer,criterion,device):dataloaders={'train':train_loader,'val':test_loader}forepochinrange(30):print('Epoch{}/{}'.format(epoch,num_epochs-1))print('-'*10)forphasein['train','val']:ifphase=='train':model.train()其他:model.eval()running_loss=0.0running_corrects=0对于输入,标签在tqdm.tqdm(dataloaders[phase]):inputs=inputs.to(device)labels=labels.to(device)optimizer.zero_grad()和torch.set_grad_enabled(phase=='train'):outputs=model(inputs)loss=criterion(outputs,labels)_,preds=torch.max(outputs,1)ifphase=='train':loss.backward()优化器.step()running_loss+=loss.item()*inputs.size(0)running_corrects+=torch.sum(preds==labels.data)epoch_loss=running_loss/len(dataloaders[phase].dataset)epoch_acc=running_corrects.double()/len(dataloaders[phase].dataset)print('{}损失:{:.4f}acc:{:.4f}'.format(phase,epoch_loss,epoch_acc))这是一个标准的微调训练步骤,训练后我们可以看到模型在测试集上达到了91%的准确率这也是我们没有选择更大型号的原因,因为测试91的精度足以作为基础模型。我们知道该模型有1170万个参数,因此它可能无法适应边缘设备或其他特定场景。.学生模型我们的学生是一个较浅的CNN,只有几层和大约100k参数。类StudentNet(nn.Module):def__init__(self):super().__init__()self.layer1=nn.Sequential(nn.Conv2d(3,4,kernel_size=3,padding=1),nn.BatchNorm2d(4),nn.ReLU(),nn.MaxPool2d(kernel_size=2,stride=2))self.fc=nn.Linear(4*112*112,2)defforward(self,x):out=self.layer1(x)out=out.view(out.size(0),-1)out=self.fc(out)returnout看代码很简单吧?如果我可以简单地训练这个较小的神经网络,我为什么要为知识蒸馏而烦恼呢?我们将在最后附上通过超参数调整从头开始训练该网络的结果以供比较。但现在我们继续我们的知识蒸馏步骤。知识蒸馏训练的基本步骤是一样的,区别在于如何计算最终的训练损失。我们将结合使用教师模型损失、学生模型损失和蒸馏损失来计算最终的损失。类DistillationLoss:def__init__(self):self.student_loss=nn.CrossEntropyLoss()self.distillation_loss=nn.KLDivLoss()self.temperature=1self.alpha=0.25def__call__(self,student_logits,student_target_loss,teacher_logits)=self.distillation_loss(F.log_softmax(student_logits/self.temperature,dim=1),F.softmax(teacher_logits/self.temperature,dim=1))loss=(1-self.alpha)*student_target_loss+self.alpha*distillation_lossreturnlossfunction是下面两个东西的加权和:classificationloss,叫做student_target_lossdistillationloss,studentlogarithm和teacherlogarithm之间的cross-entropyloss简单的说,我们的teachermodel需要教会学生如何“思考”,也就是指到它的不确定性;例如,如果教师模型的最终输出概率是[0.53,0.47],我们期望学生得到相同的结果,而这些预测之间的差异就是蒸馏损失。为了控制损失,有e两个主要参数:蒸馏损失的权重:0表示我们只考虑蒸馏损失,反之亦然。温度:衡量教师预测的不确定性。在上面的要点中,alpha和温度的值是基于我们尝试过的一些组合的最佳结果。结果比较以下是该实验的表格总结。我们可以清楚地看到使用更小(99.14%)、更浅的CNN的巨大好处:与没有蒸馏的训练相比,准确率提高了10个百分点,比Resnet-18快了11倍!也就是说,我们的小模型确实从大模型中学到了一些有用的东西。
