torchvision.transforms GPU加速,提升预测阶段数据预处理速度

16 篇文章 1 订阅
1 篇文章 0 订阅

参考:Tensor transforms and JIT — Torchvision 0.11.0 documentation

Torchvision v0.8.0之前版本:

        Torchvision v0.8.0之前版本的transforms主要分为两类:

        1、一类处理的输入数据类型为Tensor

        2、另一类处理的数据类型为PILImage

        所以torchvision里面提供了两个转换函数ToTensor和ToPILImage:

        1、ToTensor:将PILImage(形状为 [H, W, C])转换为Tensor(形状为[C, H, W])

        2、ToPILImage:将Tensor(形状为[C, H, W])转换为PILImage(形状为 [H, W, C])

        这样转来转去的比较麻烦,并且这些转换操作基本上都在CPU上进行,处理速度比较慢。

 

Torchvision v0.8.0之后版本:

        Torchvision v0.8.0之后的版本,将transforms,分为了三类:

        1、有一部分只能处理Tensor(如Normalize)

        2、一部分只能处理PILImage

        3、还有一部分既能处理PILImage,也能处理Tensor

        这样的话,我们可以将Tensor的处理部分迁移到GPU上去处理,大大提升数据的预处理速度。关于torchvision.transform更多的内容请参考:torchvision.transforms — Torchvision 0.10.0 documentation

Torchvision v0.8.0之前版本:

        在模型预测阶段,我们通常按照模型的训练方式组织torchvision.transforms的pipeline,将pipeline的处理与训练阶段保持一致进行数据预处理,大致如下:

        图上的transform pipeline比较简单,将读取的图片数据转换为PILImage,然后送入Resize,之后将PILImage转换为Tensor(注意,对于图片数据,ToTensor会除以255将数值缩放到[0,1]之间),最后normalize进行归一化,这一连串的操作全都在CPU上进行,实测发现ToTensor这步的耗时很长(上百毫秒),并且CPU占用2000%。 

 

Torchvision v0.8.0之后版本:

        在torchvision v0.8.0版本中新增了from torchvision.io import read_image函数用来读取图片文件,并将图片文件转换为Tensor对象(注意:与原来的transforms.ToTensor()不同,这里转换为Tensor并没有对图像数据缩放到[0, 1]之间),同时torchvision中的一些原本只能处理PILImage的transform在这个版本可以同时处理PILImage和Tensor了,如上面图片中的Resize。所以,我们现在就可以将原本对于PILImage的一些处理,转换为处理Tensor,并且将这些对于Tensor的处理放到GPU上来做。

        对于上图中的old_transforms操作,转换为处理Tensor之后如下:

主要包括以下几步:

  1. 使用read_image读取文件,得到RGB图像数据的Tensor
  2. 将Tensor放到GPU上
  3. 在GPU上执行一系列能处理Tensor的transform

        其中,ZeroOneNormalize是自定义的transform,这个transform用来实现将Tensor除以255转换到[0,1]之间,保持跟torchvision.transforms.ToTensor的处理一致,并且只有这样才能在下一步使用ImageNet预训练模型的Normalize归一化参数mean和std。ZeroOneNormalize转换算子也可以替换为torchvision.transforms.ConvertImageDtype,前提是上一步的输出类型是uint8。这样的话,这一连串的转换操作全在GPU上完成,速度提升嗖嗖的。

发现问题:

        但是,实验验证发现,对于同一张图片,先转换为PILImage,然后进行Resize,与先转换为Tensor再进行Resize得到的结果之间存在一些差异,也就是得到的数值不同,这个在pytorch的官网上Resize函数的介绍部分也有说明。

        备注:antialias参数在Torchvision v0.10.0才有,之前的版本都没有。

        上图说,对于输入的PILImage和Tensor,Resize之后的结果会有轻微的不同,为了降低不同(并不能消除不同),提供了antialias参数,当输入为Tensor时,设置antialias=True,可以减缓这种不同程度。这种不同具体到什么程度,我测试了一张图片112x112大小,缩放到224x224之后,使用np.sum(img1 == img2)计算得到的相同的数值大概在11万,也就是说有大概4万个数值发生了变化,脆弱的模型感觉要崩。

        所以WARNING部分也说了,建议在模型的训练和测试时使用相同的输入类型,也就是说建议在模型训练时就是用Tensor的形式,这样在测试阶段也是用Tensor,就保持跟训练时一致了。下面的代码示例就是将训练的transform也转换成处理Tensor的形式:

         经过上述改造之后预测的速度能有多快呢?使用Resnet18训练输入大小为224x224的模型,预测的FPS达到280以上,就是这么快。改造之前只有不到10的FPS,而且CPU使用率2000%。

        除了以上介绍的优点以外,基于Tensor的transform还可以使用torchscript进行转换,将transform和模型推理forward代码融为一体,方便直接使用torchscript进行转化部署。

  • 10
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
好的,让我来为你讲解如何使用torchvision.models.squeezenet1_0来实现hotdog数据集的分类任务。首先,我们需要导入必要的库和模块: ```python import torch import torchvision import torchvision.transforms as transforms import torchvision.models as models import torch.optim as optim import torch.nn as nn ``` 接下来,我们需要定义数据集的路径,并使用torchvision中的transforms模块对数据进行预处理: ```python data_dir = "./hotdog" # 数据预处理 transform = transforms.Compose( [transforms.Resize(224), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) ``` 在这里,我们使用了transforms.Resize对图片进行缩放,并使用transforms.CenterCrop对图片进行中心裁剪,将其大小调整为224x224。接着,我们使用transforms.ToTensor将图片转化为张量,并使用transforms.Normalize对张量进行标准化。 现在,我们可以使用torchvision中的ImageFolder模块来加载数据集: ```python # 加载数据集 train_set = torchvision.datasets.ImageFolder(root=data_dir+"/train", transform=transform) test_set = torchvision.datasets.ImageFolder(root=data_dir+"/test", transform=transform) # 定义数据加载器 train_loader = torch.utils.data.DataLoader(train_set, batch_size=32, shuffle=True) test_loader = torch.utils.data.DataLoader(test_set, batch_size=32, shuffle=False) ``` 在这里,我们使用ImageFolder模块来加载数据集,并使用DataLoader模块定义数据加载器,将数据分为多个batch进行训练。 接下来,我们需要定义模型,并将模型转移到GPU上进行训练: ```python # 定义模型 model = models.squeezenet1_0(pretrained=True) num_ftrs = model.classifier[1].in_features model.classifier[1] = nn.Linear(num_ftrs, 2) # 定义损失函数和优化器 criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9) # 将模型转移到GPU上 device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") model.to(device) ``` 在这里,我们使用了预训练的SqueezeNet模型,并将其输出层修改为两个输出节点,用于hotdog数据集的二分类任务。接着,我们定义了损失函数和优化器,并将模型转移到GPU上进行训练。 最后,我们可以使用以下代码来进行训练和测试: ```python # 训练模型 for epoch in range(10): running_loss = 0.0 for i, data in enumerate(train_loader, 0): inputs, labels = data[0].to(device), data[1].to(device) optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() if i % 100 == 99: print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 100)) running_loss = 0.0 # 测试模型 correct = 0 total = 0 with torch.no_grad(): for data in test_loader: images, labels = data[0].to(device), data[1].to(device) outputs = model(images) _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum().item() print('Accuracy of the network on the test images: %d %%' % ( 100 * correct / total)) ``` 在这里,我们使用了10个epoch进行训练,并在测试集上进行测试。最后,我们输出了模型在测试集上的准确率。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值