百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分类 > 正文

使用PyTorch进行知识蒸馏的代码示例

ztj100 2024-10-31 16:13 31 浏览 0 评论

随着机器学习模型的复杂性和能力不断增加。提高大型复杂模型在小数据集性能的一种有效技术是知识蒸馏,它包括训练一个更小、更有效的模型来模仿一个更大的“教师”模型的行为。

在本文中,我们将探索知识蒸馏的概念,以及如何在PyTorch中实现它。我们将看到如何使用它将一个庞大、笨重的模型压缩成一个更小、更高效的模型,并且仍然保留原始模型的准确性和性能。

我们首先定义知识蒸馏要解决的问题。

我们训练了一个大型深度神经网络来执行复杂的任务,比如图像分类或机器翻译。这个模型可能有数千层和数百万个参数,这使得它很难部署在现实应用程序、边缘设备等中。并且这个超大的模型还需要大量的计算资源来运行,这使得它在一些资源受限的平台上无法工作。

解决这个问题的一种方法是使用知识蒸馏将大模型压缩成较小的模型。这个过程包括训练一个较小的模型来模仿给定任务中大型模型的行为。

我们将使用来自Kaggle的胸部x光数据集进行肺炎分类来进行知识蒸馏的示例。我们使用的数据集被组织成3个文件夹(train, test, val),并包含每个图像类别的子文件夹(Pneumonia/Normal)。共有5,863张x射线图像(JPEG)和2个类别(肺炎/正常)。

比较一下这两个类的图片:

数据的加载和预处理与我们是否使用知识蒸馏或特定模型无关,代码片段可能如下所示:

transforms_train = transforms.Compose([
transforms.Resize((224, 224)),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406],
[0.229, 0.224, 0.225])])
transforms_test = transforms.Compose([
transforms.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)
test_data = 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并且在这个数据集上进行了微调。

import torch
import torch.nn as nn
import torchvision
class TeacherNet(nn.Module):
def __init__(self):
super().__init__()
self.model = torchvision.models.resnet18(pretrained=True)
for params in self.model.parameters():
params.requires_grad_ = False
n_filters = self.model.fc.in_features
self.model.fc = nn.Linear(n_filters, 2)
def forward(self, x):
x = self.model(x)
return x

微调训练的代码如下

def train(model, train_loader, test_loader, optimizer, criterion, device):
dataloaders = {'train': train_loader, 'val': test_loader}
for epoch in range(30):
print('Epoch {}/{}'.format(epoch, num_epochs - 1))
print('-' * 10)
for phase in ['train', 'val']:
if phase == 'train':
model.train()
else:
model.eval()
running_loss = 0.0
running_corrects = 0
for inputs, labels in tqdm.tqdm(dataloaders[phase]):
inputs = inputs.to(device)
labels = labels.to(device)
optimizer.zero_grad()
with torch.set_grad_enabled(phase == 'train'):
outputs = model(inputs)
loss = criterion(outputs, labels)
_, preds = torch.max(outputs, 1)
if phase == 'train':
loss.backward()
optimizer.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('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))

这是一个标准的微调训练步骤,训练后我们可以看到该模型在测试集上达到了91%的准确性,这也就是我们没有选择更大模型的原因,因为作为测试91的准确率已经足够作为基类模型来使用了。

我们知道模型有1170万个参数,因此不一定能够适应边缘设备或其他特定场景。

学生模型

我们的学生是一个更浅的CNN,只有几层和大约100k个参数。

class 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)
def forward(self, x):
out = self.layer1(x)
out = out.view(out.size(0), -1)
out = self.fc(out)
return out

看代码就非常的简单,对吧。

如果我可以简单地训练这个更小的神经网络,我为什么还要费心进行知识蒸馏呢?我们最后会附上我们通过超参数调整等手段从头训练这个网络的结果最为对比。

但是现在我们继续我们的知识蒸馏的步骤

知识蒸馏训练

训练的基本步骤是不变的,但是区别是如何计算最终的训练损失,我们将使用教师模型损失,学生模型的损失和蒸馏损失一起来计算最终的损失。

class DistillationLoss:
def __init__(self):
self.student_loss = nn.CrossEntropyLoss()
self.distillation_loss = nn.KLDivLoss()
self.temperature = 1
self.alpha = 0.25
def __call__(self, student_logits, student_target_loss, teacher_logits):
distillation_loss = 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_loss
return loss

损失函数是下面两个东西的加权和:

  • 分类损失,称为student_target_loss
  • 蒸馏损失,学生对数和教师对数之间的交叉熵损失

简单的讲,我们的教师模型需要教导学生如何“思考”的,这就是指的是它的不确定性;例如,如果教师模型的最终输出概率是[0.53,0.47],我们希望学生也得到同样类似结果,这些预测之间的差异就是蒸馏损失。

为了控制损失,还有有两个主要参数:

  • 蒸馏损失的权重:0意味着我们只考虑蒸馏损失,反之亦然。
  • 温度:衡量教师预测的不确定性。

在上面的要点中,alpha和temperature的值都是根据我们尝试过一些组合得到的最佳结果的值。

结果对比

这是这个实验的表格摘要。

我们可以清楚地看到使用更小(99.14%),更浅的CNN所获得的巨大好处:与无蒸馏训练相比,准确率提升了10点,并且比Resnet-18快11倍!也就是说,我们的小模型真的从大模型中学到了有用的东西。

作者:Alessandro Lamberti

相关推荐

离谱!写了5年Vue,还不会自动化测试?

前言大家好,我是倔强青铜三。是一名热情的软件工程师,我热衷于分享和传播IT技术,致力于通过我的知识和技能推动技术交流与创新,欢迎关注我,微信公众号:倔强青铜三。Playwright是一个功能强大的端到...

package.json 与 package-lock.json 的关系

模块化开发在前端越来越流行,使用node和npm可以很方便的下载管理项目所需的依赖模块。package.json用来描述项目及项目所依赖的模块信息。那package-lock.json和...

Github 标星35k 的 SpringBoot整合acvtiviti开源分享,看完献上膝盖

前言activiti是目前比较流行的工作流框架,但是activiti学起来还是费劲,还是有点难度的,如何整合在线编辑器,如何和业务表单绑定,如何和系统权限绑定,这些问题都是要考虑到的,不是说纯粹的把a...

Vue3 + TypeScript 前端研发模板仓库

我们把这个Vue3+TypeScript前端研发模板仓库的初始化脚本一次性补全到可直接运行的状态,包括:完整的目录结构所有配置文件研发规范文档示例功能模块(ExampleFeature)...

Vue 2迁移Vue 3:从响应式到性能优化

小伙伴们注意啦!Vue2已经在2023年底正式停止维护,再不升级就要面临安全漏洞没人管的风险啦!而且Vue3带来的性能提升可不是一点点——渲染速度快40%,内存占用少一半,更新速度直接翻倍!还在...

VUE学习笔记:声明式渲染详解,对比WEB与VUE

声明式渲染是指使用简洁的模板语法,声明式的方式将数据渲染进DOM系统。声明式是相对于编程式而言,声明式是面向对象的,告诉框架做什么,具体操作由框架完成。编程式是面向过程思想,需要手动编写代码完成具...

苏州web前端培训班, 苏州哪里有web前端工程师培训

前端+HTML5德学习内容:第一阶段:前端页面重构:PC端网站布局、HTML5+CSS3基础项目、WebAPP页面布局;第二阶段:高级程序设计:原生交互功能开发、面向对象开发与ES5/ES6、工具库...

跟我一起开发微信小程序——扩展组件的代码提示补全

用户自定义代码块步骤:1.HBuilderX中工具栏:工具-代码块设置-vue代码块2.通过“1”步骤打开设置文件...

JimuReport 积木报表 v1.9.3发布,免费可视化报表

项目介绍积木报表JimuReport,是一款免费的数据可视化报表,含报表、大屏和仪表盘,像搭建积木一样完全在线设计!功能涵盖:数据报表、打印设计、图表报表、门户设计、大屏设计等!...

软开企服开源的无忧企业文档(V2.1.3)产品说明书

目录1....

一款面向 AI 的下一代富文本编辑器,已开源

简介AiEditor是一个面向AI的下一代富文本编辑器。开箱即用、支持所有前端框架、支持Markdown书写模式什么是AiEditor?AiEditor是一个面向AI的下一代富文本编辑...

玩转Markdown(2)——抽象语法树的提取与操纵

上一篇玩转Markdown——数据的分离存储与组件的原生渲染发布,转眼已经鸽了大半年了。最近在操纵mdast生成md文件的时候,心血来潮,把玩转Markdown(2)给补上了。...

DeepseekR1+ollama+dify1.0.0搭建企业/个人知识库(入门避坑版)

找了网上的视频和相关文档看了之后,可能由于版本不对或文档格式不对,很容易走弯路,看完这一章,可以让你少踩三天的坑。步骤和注意事项我一一列出来:1,前提条件是在你的电脑上已配置好ollama,dify1...

升级JDK17的理由,核心是降低GC时间

升级前后对比升级方法...

一个vsCode格式化插件_vscode格式化插件缩进量

ESlint...

取消回复欢迎 发表评论: