pytorch dataset读取数据流程_高效 PyTorch :如何消除训练瓶颈

加入极市专业CV交流群,与 10000+来自港科大、北大、清华、中科院、CMU、腾讯、百度 等名校名企视觉开发者互动交流!

同时提供每月大咖直播分享、真实项目需求对接、干货资讯汇总,行业技术交流。关注 极市平台 公众号 ,回复 加群,立刻申请入群~

编译|McGL,https://zhuanlan.zhihu.com/p/147723652来源|https://towardsdatascience.com/efficient-pytorch-part-1-fe40ed5db76c

高效的 PyTorch 训练pipeline是怎样的呢?是产生准确率最高模型?还是跑得最快?或是容易理解和扩展?还是很容易并行计算?嗯,以上都是!

在研究和生产领域, PyTorch都是一个很好用的工具。从斯坦福大学、 Udacity、 SalelsForce和Tesla等都采用这个深度学习框架清楚的表明了这一点。然而,每个工具都需要投入时间来最高效地使用和掌握它。在使用 PyTorch 两年多之后,我决定总结一下我使用这个深度学习库的经验。

高效 ——(系统或机器)达到最大生产力,而浪费的精力或费用却最少。(牛津辞典)

高效 PyTorch 系列的这一部分展示了识别和消除 I/O 和 CPU 、GPU瓶颈的一般技巧。第二部分将揭露一些高效张量运算的技巧。第三部分——高效的模型调试技术。

声明: 本文假设你至少了解了 PyTorch 的基本知识和概念。

从最明显的一个开始:

建议0: 了解代码中的瓶颈在哪里

nvidia-smi, htop, iotop, nvtop, py-spy, strace 等命令行工具应该成为你最好的朋友。你的训练pipeline是CPU-bound? IO-bound 还是GPU-bound? 这些工具将帮助你找到答案。

你可能甚至没有听说过它们,或者听说过但没有使用过。没关系。如果你不马上开始使用它们也没关系。只要记住,别人可能用它们来训练模型,比你快5%-10%-15%...这最终可能会决定是否赢得或失去市场,得到或失去理想工作岗位的offer。

数据预处理

几乎所有的训练pipeline都是从 Dataset 类开始的。它负责提供数据样本。任何必要的数据转换和增强都可能在这里发生。简而言之,Dataset 是一个抽象,它报告数据的大小,并通过给定的索引返回数据样本。

如果使用类图像的数据(2D、3D 扫描) ,磁盘I/O可能会成为瓶颈。要获得原始像素数据,代码需要从磁盘读取数据并将图像解码到内存中。每个任务都很快,但当你需要尽快处理成千上万个任务时,这可能会成为一个挑战。像 NVidia Dali 这样的库提供 GPU加速的 JPEG 解码。如果在数据处理pipeline中遇到 IO 瓶颈,这绝对值得一试。

还有一个选择。SSD 磁盘的存取时间约为0.08-0.16毫秒。RAM 的访问时间为纳秒。我们可以把我们的数据直接放到内存中!

建议1: 如果可能的话,将所有或部分数据移动到 RAM。

如果你有足够的内存来加载和保存你所有的训练数据,这是从pipeline中消除最慢的数据读取步骤的最简单的方法。

这个建议对于云实例特别有用,比如 Amazon 的 p3.8 xlarge。此实例有 EBS 磁盘,其默认设置的性能非常有限。然而,这个实例配备了惊人的248Gb 内存。这足够将所有 ImageNet 数据集保存在内存中了!以下是实现这个目标的方法:

class RAMDataset(Dataset):  def __init__(image_fnames, targets):    self.targets = targets    self.images = []    for fname in tqdm(image_fnames, desc="Loading files in RAM"):      with open(fname, "rb") as f:        self.images.append(f.read())  def __len__(self):    return len(self.targets)  def __getitem__(self, index):    target = self.targets[index]    image, retval = cv2.imdecode(self.images[index], cv2.IMREAD_COLOR)    return image, target

我个人就遇到过这个瓶颈问题。我有一台配了4x1080Ti GPUs 的家用电脑。有一次我用一个 p3.8xlarge 实例,它有四个 NVidia Tesla V100,我把我的训练代码移到了那里。考虑到 V100比我的老款1080Ti 更新更快,我希望看到快15-30% 的训练速度。令我惊讶的是,每个epoch的训练时间都在增加!这是我学到的教训,要注意基础设施和环境的细微差别,而不仅仅是 CPU 和 GPU 的速度。

根据你的场景,你可以在内存中保持每个文件的二进制内容不变,并“动态”解码它,或者保留未压缩的图像的原始像素。无论你选择哪种方式,这里有第二个建议:

建议2: 性能分析。测量。比较。每次你对pipeline进行任何改动时,都要仔细评估它对整体的影响。

这个建议仅仅关注训练速度,假设你不对模型、超参数、数据集等进行更改。你可以拥有一个魔术般的命令行参数(魔术开关) ,如果指定了,它将运行一些合理数量的数据样本的训练。有了这个功能,你可以快速的对你的pipeline进行性能分析:

# Profile CPU bottleneckspython -m cProfile training_script.py --profiling# Profile GPU bottlenecksnvprof --print-gpu-trace python train_mnist.py# Profile system calls bottlenecksstrace -fcT python training_script.py -e trace=open,close,read

建议3: 线下预处理所有数据

如果你正在训练512x512大小的图像,这些图像是由2048 × 2048的图片转换的,那么事先调整它们的大小。如果你使用灰度图像作为模型的输入,请离线进行颜色转换。如果你正在做 NLP ——事先做tokenization并保存到磁盘。没有必要在训练期间一遍又一遍地重复同样的操作。就渐进式学习而言,你可以保存多种分辨率的训练数据——这仍然比在线调整目标分辨率要快。

对于表格数据,请考虑在 Dataset 创建时将 pd.DataFrame 对象转换为 PyTorch 张量。

建议4: 调整 DataLoader 的workers数量

PyTorch 使用 DataLoader 类来简化为训练模型生成batches的过程。为了加快速度,它可以并行执行,使用 python 的multiprocessing。大多数情况下,直接用就很好了。以下是一些需要记住的事情:

每个进程生成一批数据,这些batches通过互斥同步(mutex synchronization)提供给主进程。如果你有 N 个workers,那么你的脚本将需要 N 倍的内存才能在系统内存中存储这些batches。你究竟需要多少RAM?让我们计算一下:

  1. 假设我们用batch size 32 来训练 Cityscapes 图像分割模型,RGB 图像大小为512x512x3 (高, 宽, 通道). 我们在 CPU 端进行图像标准化(稍后我将解释为什么它很重要)。在这种情况下,我们的最终图像张量512 * 512 * 3 * sizeof(float32) = 3,145,728 bytes. 乘以batch size得到的结果是100,663,296 bytes ,即大约100mb
  2. 除了图像,我们需要提供ground-truth masks。它们各自的大小为(默认情况下,掩码的类型为 long,为8字节)ー 512 * 512 * 1 * 8 * 32 = 67,108,864 , 即大约67mb
  3. 因此,一批数据所需的总内存为167 Mb。如果我们有8个workers,所需的总内存量将是167 Mb * 8 = 1,336 Mb.

听起来还不算太糟,对吧?当你的硬件配置能够处理超过8个workers所能提供的batches时,问题就出现了。你可以简单的设置64个workers,但这至少会消耗11gb 的内存。

如果你的数据是3D的,情况会变得更糟; 在这种情况下,即使是一个单通道512x512x512的样本也将占用134 Mb,而batch size为32将占用4.2 Gb,如果有8个workers,则需要32gb 的内存来保存中间数据。

这个问题有一个部分解决方案ーー你可以尽可能地减少输入数据的通道深度:

  1. 保持 RGB 图像在每个通道深度为8位。图像转换为float和标准化可以很容易地在 GPU 上完成
  2. 在数据集中使用 uint8或 uint16数据类型代替 long
class MySegmentationDataset(Dataset):  ...  def __getitem__(self, index):    image = cv2.imread(self.images[index])    target = cv2.imread(self.masks[index])    # No data normalization and type casting here    return torch.from_numpy(image).permute(2,0,1).contiguous(),           torch.from_numpy(target).permute(2,0,1).contiguous()class Normalize(nn.Module):    # https://github.com/BloodAxe/pytorch-toolbelt/blob/develop/pytorch_toolbelt/modules/normalize.py    def __init__(self, mean, std):        super().__init__()        self.register_buffer("mean", torch.tensor(mean).float().reshape(1, len(mean), 1, 1).contiguous())        self.register_buffer("std", torch.tensor(std).float().reshape(1, len(std), 1, 1).reciprocal().contiguous())    def forward(self, input: torch.Tensor) -> torch.Tensor:        return (input.to(self.mean.type) - self.mean) * self.stdclass MySegmentationModel(nn.Module):  def __init__(self):    self.normalize = Normalize([0.221 * 255], [0.242 * 255])    self.loss = nn.CrossEntropyLoss()  def forward(self, image, target):    image = self.normalize(image)    output = self.backbone(image)    if target is not None:      loss = self.loss(output, target.long())      return loss    return output

这样做,你可以大大降低内存需求。对于上面的示例,用于内存高效数据表示的内存使用量为每批33 Mb,而不是167 Mb。那是5倍的缩减!当然,这需要在模型本身中执行额外的步骤,将数据normalize/cast为适当的数据类型。然而,张量越小,CPU 到 GPU 的传输时间越快。

应该理性的选择 DataLoader 的workers数量。你应该检查你的 CPU 和 IO 系统有多快,有多少内存,GPU 处理这些数据有多快。

多GPU训练与推理

神经网络模型变得越来越大。今天的趋势是使用多个 GPU 来提速训练。由于更大的batch size,它还经常可以改善模型的性能。PyTorch 只需要几行代码就可以实现多GPU功能。然而,一些说明乍一看并不明显。

model = nn.DataParallel(model) # Runs model on all available GPUs

使用多 GPU 最简单的方法是将模型包在nn.DataParallel类中。在大多数情况下,它工作得不错,除非你训练一些图像分割模型(或任何其它模型,产生大型张量作为输出)。在前传结束的时候, nn.DataParallel从所有 GPU 收集输出回到主 GPU 上,通过输出后向传播并进行梯度更新。

有两个问题:

  • GPUs负载不平衡
  • 在主 GPU 上收集需要额外的内存

首先,只有主 GPU 在进行损失计算、后向传播和梯度更新,而其它 GPU 则在60C 凉快处等待下一组数据。

其次,主 GPU 收集所有输出所需的额外内存通常会迫使你减少batch size。问题是nn.DataParallel 将一批数据均匀的分给各个GPU。假设你有4个GPUs,总batch size为32。那么每个 GPU 将得到8个样本。但问题是,虽然所有非主 GPU 都可以轻松地将这些batch放入相应的 VRAM 中,但主 GPU 必须分配额外的空间,以保持batch size为32的其它卡的输出。

GPU使用率不均衡有两种解决方案:

  1. 继续使用nn.DataParallel并在训练前传中计算损失。在这种情况下,你不会将密集预测masks返回给主 GPU,而只返回单个标量损失
  2. 使用分布式训练,也就是nn.DistributedDataParallel. 在分布式训练的帮助下,你可以解决上面这两个问题,同时享受观看所有GPU的100% 负载的乐趣

建议5: 如果你有超过2个 GPU ——考虑使用分布式训练模式

节省多少时间很大程度上取决于你的场景,根据我的观察,在4x1080Ti 上训练图像分类pipeline时,时间减少了20% 。

另外值得一提的是,你也可以使用nn.DataParallel and nn.DistributedDataParallel来进行推理。

关于自定义损失函数

编写自定义损失函数是一个有趣和令人兴奋的练习。我建议每个人都不时地尝试一下。实现一个逻辑复杂的损失函数时,有一件事你必须记住: 它运行在 CUDA上,编写高效的 CUDA 代码是你的职责。CUDA 高效意味着“没有 python 控制流”。在 CPU 和 GPU 之间来回切换,访问 GPU 张量的值可以完成任务,但是性能会很差。

不久前,我实现了一个自定义的cosine embedding损失函数,用于实例分割,该函数来自论文“Segmenting and tracking cell instances with cosine embeddings and recurrent hourglass networks”。它的文本形式相当简单,但是实现起来有点复杂。

我编写的第一个简单的实现(除了 bugs)花了几分钟(!) 计算一个batch的损失值。为了分析 CUDA 的瓶颈,PyTorch 提供了一个非常方便的内置性能分析器。使用起来非常简单,并且给出了解决代码瓶颈的所有信息:

def test_loss_profiling():    loss = nn.BCEWithLogitsLoss()    with torch.autograd.profiler.profile(use_cuda=True) as prof:        input = torch.randn((8, 1, 128, 128)).cuda()        input.requires_grad = True        target = torch.randint(1, (8, 1, 128, 128)).cuda().float()        for i in range(10):            l = loss(input, target)            l.backward()    print(prof.key_averages().table(sort_by="self_cpu_time_total"))

建议6: 如果你设计自定义模块和损失函数——进行性能分析和测试

在分析了我的初始实现之后,我可以将实现的速度提高100倍。关于在 PyTorch 中编写高效张量表达式的更多内容将在高效PyTorch 的第二部分中解释。

时间 vs 金钱

最后但并非最不重要的一点是,有时值得投资功能更强大的硬件,而不是优化代码。软件优化总是一个具有不确定结果的高风险过程。升级 CPU,内存,GPU,或所有可能更有效。资金和工程时间都是资源,合理利用两者是成功的关键。

建议7: 有些瓶颈可以通过硬件升级更容易的解决

总结

最大限度的利用你的日常工具是熟练的关键。尽量不要走捷径,如果你不清楚某些事情,就要深入挖掘。总有机会获得新知识的。问问你自己或你的同事——“我的代码如何改进? ” 我相信这种追求完美的意识对于计算机工程师来说和其它技能一样重要。

推荐阅读

  • 给训练踩踩油门:编写高效的PyTorch代码技巧

  • Pytorch数据加载的分析

  • Pytorch有什么节省内存(显存)的小技巧?

ad37e8d74298c760182a2fcea054ab03.png添加极市小助手微信(ID : cv-mart),备注:研究方向-姓名-学校/公司-城市(如:目标检测-小极-北大-深圳),即可申请加入极市技术交流群,更有每月大咖直播分享、真实项目需求对接、求职内推、算法竞赛、干货资讯汇总、行业技术交流,一起来让思想之光照的更远吧~

1f8c020662fdceabd6d67f9582e414d1.png

△长按添加极市小助手

21cc8ab9995007461f84b7d66b2334d8.png

△长按关注极市平台,获取最新CV干货

觉得有用麻烦给个在看啦~  44e2f1a10f3efb04af8643ce8541920b.gif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/337561.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

APP 文档服务器,app服务器

app服务器 内容精选换一换华为云帮助中心,为用户提供产品简介、价格说明、购买指南、用户指南、API参考、最佳实践、常见问题、视频帮助等技术文档,帮助您快速上手使用华为云服务。调用接口出错后,将不会返回结果数据。调用方可根据每个接口对…

需求最高的8种编程语言

点击蓝字关注我们在过去的 8 个月时间里(从 2021 年 10 月到 2022 年 6 月),DevJobsScanner 分析了超过 700 万份开发者工作需求,得出了目前行业需求量最高的 8 种编程语言。需要注意的是,在这 700 万份工作需求中&…

java面包屑实现_在Java中实现过滤器和面包店锁

java面包屑实现为了了解锁的工作原理,实现自定义锁是一种好方法。 这篇文章将展示如何在Java上实现Filter和Bakery锁(自旋锁),并将它们的性能与Java的ReentrantLock进行比较。 过滤器锁和面包房锁满足互斥并且也是无饥饿算法&…

postman 怎么调试pos_SpringBoot|第十五章:基于Postman的RESTful接口测试

前言从上一章节开始,接下来的几个章节会讲解一些开发过程中配套工具的使用。俗话说的好,工欲善其事,必先利其器。对于开发人员而言,有个好用的工具,也是一件事半功倍的事,而且开发起来也很爽,效…

这几个C语言关键字你真的得懂(深度解剖)

点击蓝字关注我们1、什么是语句,表达式? 在C语言中 ,凡是以分号隔开的就是一条语句:printf("hello world\n");a 1 2; ; (空语句)什么是表达式呢?C语言中,用各种操作符把变量连起来,…

aws sqs_JMS和AWS SQS的更多高级内容

aws sqs如您所知, AWS中的SQS SQS代表“简单队列服务”。 最近,在使用它的同时,我发现了将其称为“简单”的原因之一。 在之前的两篇文章( 此处和此处 )中,我展示了结合Spring Framework将SQS用作JMS队列提…

python 直方图每个bin中的值_【Python数据分析】四级成绩分布 -matplotlib,xlrd 应用...

标签: 最近获得了一些四级成绩数据,大概500多个,于是突发奇想是否能够看看这些成绩数据是否满足所谓的正态分布呢?说干就干,于是有了这篇文章。 文章顺带介绍了xlrd模块的一些用法和matplotlib画自定义数据的条形图和随…

adf开发_了解ADF生命周期中的ADF绑定

adf开发在这篇文章中,我将重点介绍ADF绑定层,并探讨当最初从浏览器请求带有一些数据的ADF页面时它如何工作。 Oracle ADF提供了自己的JSF生命周期扩展版。 实际上,ADF扩展了标准的JSF生命周期实现类,并提供了ADF阶段侦听器&#…

C语言实现计算器

点击蓝字关注我们1、实现逻辑首先创建菜单(menu),把我们需要实现的功能打印到运行栏屏幕上。实现加法计算实现减法计算实现除法计算实现乘法计算退出计算器当然以上都是属于最基本的计算,你当然还可以实现一些其它计算。例如:位运算(按位与、…

python怎么创建txt文件啊_python根据txt文本批量创建文件夹

前言 前言:想写这个代码的原因是因为实习的时候需要根据表格名创建对应的文件夹,如果只是很少个数文件夹的话,ctrlshiftn还可以接受吧,可是一次就要创建几百个文件夹,这就有点方方了。所以我写了一些代码解决实际的问题…

十道题带你手撕二叉树

点击蓝字关注我们1、单值二叉树题目:思路一:(遍历的方法)将根节点的值与二叉树中的每一个节点存储的val值进行比较,如果不同就返回false,如果全部相同,就返回true。代码:bool _isUni…

C语言实现面向对象编程 : 封装、继承、多态

点击蓝字关注我们不知道有多少人去了解过语言的发展史,早期C语言的语法功能其实比较简单。随着应用需求和场景的变化,C语言的语法功能在不断升级变化。虽然我们的教材有这么一个结论:C语言是面向过程的语言,C是面向对象的编程语言…

图解python pdf_Python合并同一个文件夹下所有PDF文件的方法

一、需求说明 下载了网易云课堂的吴恩达免费的深度学习的pdf文档,但是每一节是一个pdf,我把这些PDF文档放在一个文件夹下,希望合并成一个PDF文件。于是写了一个python程序,很好的解决了这个问题。 二、数据形式三、合并效果四、py…

用C语言实现状态机设计模式

点击蓝字关注我们状态机模式是一种行为模式,在 《设计模式》 这本书中对其有详细的描述,通过多态实现不同状态的调转行为的确是一种很好的方法,只可惜在嵌入式环境下,有时只能写纯C代码,并且还需要考虑代码的重入和多任…

这几行代码,惊为天人!

点击蓝字关注我们事情是这么一回事:国外有个大佬在StackExchange上发起了一个叫做 Tweetable Mathematical Art 的比赛。参赛者需要用C编写代表三原色的RD、GR、BL三个函数,每个函数都不能超过 140 个字符。每个函数都会接到 i 和 j 两个整型参数&#x…

快速搞定C/C++ 的条件编译

点击蓝字关注我们1、条件编译的时机我们都知道vscode其实是一个编辑器,你要在上面跑C或者C你需要配置编译器,拿编译器是怎样吧一个文本文件变成一个可执行文件的呢?那必然是经历以下这四步预处理:宏替换,头文件的展开&…

python连接不上数据库_pycharm连接mysql数据库连接不上

代码其实很简单,只有一小段,是在pycharm上运行的,所用的python版本为2.7,mysql版本为5.7.21 # -*- coding: UTF-8 -*- import re import MySQLdb if __name__ __main__: #打开数据库 conn MySQLdb.connect(hostlocalhost,port33…

assertj_AssertJ的SoftAssertions –我们需要它们吗?

assertj编写好的单元测试的规则之一是,它应该由于一种原因而失败,因此,单元测试应该测试一种逻辑概念。 有时很难在每个测试中拥有一个断言。 为了遵循规则,我们可能在单个测试中每个对象具有多个断言。 但是,在一个测…

用C/C++语言代码实现一个虚拟机

点击蓝字关注我们本文将教你编写一个自己的虚拟机(VM),这个虚拟机能够运行汇编语言编写的程序, 例如我朋友编写的 2048 或者我自己的 Roguelike。如果你会编程,但希望 更深入地了解计算机的内部原理以及编程语言是如何…

C++—vector的使用

点击蓝字关注我们一、vector的介绍说的简单点:vector是可以动态增长的数组容器vector是表示可变大小数组的序列容器。就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。…