Grokay PyTorch

哈Ha!

我们已经预购了一本期待已久的有关PyTorch库的书。



由于您将从本书中学习有关PyTorch的所有必要基本材料,因此,我们提醒您想学习的主题称为“摸索”或“深入理解”的好处 。 在今天的帖子中,我们将告诉您Kai Arulkumaran如何抨击PyTorch(无图片)。 欢迎来到猫。

PyTorch是一个灵活的深度学习框架,它使用动态神经网络(即使用动态流控制的网络,例如ifwhile循环)自动区分对象。 PyTorch支持GPU加速, 分布式培训 ,各种类型的优化以及许多其他不错的功能。 在这里,我对如何使用PyTorch提出了一些想法。 库的所有方面和建议的实践都不在此处,但我希望本文对您有所帮助。

神经网络是计算图的子类。 计算图接收数据作为输入,然后在处理它们的节点上将这些数据路由(并可以转换)。 在深度学习中,神经元(节点)通常通过向其应用参数和可微函数来转换数据,以便可以通过梯度下降法对参数进行优化以使损失最小化。 从广义上讲,我注意到函数可以是随机的,而图可以是动态的。 因此,虽然神经网络非常适合数据流编程范例,但PyTorch API专注于命令式编程范例,这种解释创建的程序的方式更加熟悉。 这就是为什么PyTorch代码更易于阅读,更容易判断复杂程序的设计的原因,然而,这并不需要严重影响性能:实际上,PyTorch足够快,并且提供了许多优化,作为最终用户,您完全不必担心(但是,如果您真的对它们感兴趣,则可以更深入地了解它们)。

本文的其余部分是对MNIST数据集上官方示例的分析。 在这里我们 PyTorch,因此,我建议您仅在熟悉正式的初学者手册后才了解本文。 为了方便起见,该代码以带有注释的小片段的形式呈现,也就是说,它没有分发到您习惯于在纯模块化代码中看到的单独的函数/文件中。

进口货


 import argparse import os import torch from torch import nn, optim from torch.nn import functional as F from torch.utils.data import DataLoader from torchvision import datasets, transforms 

torchvision模块外,所有这些都是非常标准的导入,特别是在解决与计算机视觉有关的任务时, torchvision模块除外。

客制化


 parser = argparse.ArgumentParser(description='PyTorch MNIST Example') parser.add_argument('--batch-size', type=int, default=64, metavar='N', help='input batch size for training (default: 64)') parser.add_argument('--epochs', type=int, default=10, metavar='N', help='number of epochs to train (default: 10)') parser.add_argument('--lr', type=float, default=0.01, metavar='LR', help='learning rate (default: 0.01)') parser.add_argument('--momentum', type=float, default=0.5, metavar='M', help='SGD momentum (default: 0.5)') parser.add_argument('--no-cuda', action='store_true', default=False, help='disables CUDA training') parser.add_argument('--seed', type=int, default=1, metavar='S', help='random seed (default: 1)') parser.add_argument('--save-interval', type=int, default=10, metavar='N', help='how many batches to wait before checkpointing') parser.add_argument('--resume', action='store_true', default=False, help='resume training from checkpoint') args = parser.parse_args() use_cuda = torch.cuda.is_available() and not args.no_cuda device = torch.device('cuda' if use_cuda else 'cpu') torch.manual_seed(args.seed) if use_cuda: torch.cuda.manual_seed(args.seed) 

argparse是在Python中处理命令行参数的标准方法。

如果您需要编写旨在在不同设备上工作的代码(使用GPU加速,如果可用,但如果没有回滚到CPU上的计算),则选择并保存适当的torch.device ,您可以使用该文件确定应该在哪里张量被存储。 有关创建此类代码的更多信息,请参见官方文档 。 PyTorch的方法是将设备的选择带给用户控制,这在简单的示例中似乎是不可取的。 但是,这种方法极大地简化了必须处理张量的工作,其中a)调试方便b)允许您有效地手动使用设备。

为了提高实验的可重复性,您需要为所有使用随机数生成的组件(包括randomnumpy ,如果也使用)设置随机初始值。 请注意:cuDNN使用非确定性算法,并且可以选择使用torch.backends.cudnn.enabled = False禁用。

资料


 data_path = os.path.join(os.path.expanduser('~'), '.torch', 'datasets', 'mnist') train_data = datasets.MNIST(data_path, train=True, download=True, transform=transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))])) test_data = datasets.MNIST(data_path, train=False, transform=transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))])) train_loader = DataLoader(train_data, batch_size=args.batch_size, shuffle=True, num_workers=4, pin_memory=True) test_loader = DataLoader(test_data, batch_size=args.batch_size, num_workers=4, pin_memory=True) 


由于torchvision模型存储在~/.torch/models/ ,因此我更喜欢将torchvision torchvision存储在~/.torch/datasets 。 这是我的版权协议,但是在基于MNIST,CIFAR-10等开发的项目中使用非常方便。 通常,如果要重复使用多个数据集,则应将数据集与代码分开存储。

torchvision.transforms包含许多方便的用于单个图像的转换选项,例如裁剪和归一化。

batch_size有很多选项,但是除了batch_sizeshuffle之外,您还应该记住num_workerspin_memory ,它们有助于提高效率。 num_workers > 0使用子进程进行异步数据加载,并且不会为此阻塞主进程。 一个典型的用例是从磁盘加载数据(例如图像),并可能对其进行转换。 所有这些以及网络数据处理可以并行完成。 可能需要调整处理程度,以便a)最小化工作人员的数量,并因此减少所使用的CPU和RAM的数量(每个工作人员加载一个单独的批次,而不是批次中包含的单个样本)b)最小化数据在网络上等待的时间长度。 pin_memory使用固定的内存 (而不是分页的)来加速从RAM到GPU的任何数据传输操作(对CPU特定的代码不执行任何操作)。

型号


 class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(1, 10, kernel_size=5) self.conv2 = nn.Conv2d(10, 20, kernel_size=5) self.conv2_drop = nn.Dropout2d() self.fc1 = nn.Linear(320, 50) self.fc2 = nn.Linear(50, 10) def forward(self, x): x = F.relu(F.max_pool2d(self.conv1(x), 2)) x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2)) x = x.view(-1, 320) x = F.relu(self.fc1(x)) x = self.fc2(x) return F.log_softmax(x, dim=1) model = Net().to(device) optimiser = optim.SGD(model.parameters(), lr=args.lr, momentum=args.momentum) if args.resume: model.load_state_dict(torch.load('model.pth')) optimiser.load_state_dict(torch.load('optimiser.pth')) 

网络初始化通常扩展到成员变量,包含学习参数以及可能包含单个学习参数和未经训练的缓冲区的层。 然后,通过直接传递,将它们与F中的纯函数且不包含参数的函数结合使用。 有些人喜欢使用纯功能网络(例如保留参数并使用F.conv2d而不是nn.Conv2d )或完全由层组成的网络(例如nn.ReLU而不是F.relu )。

如果将device设置为GPU, .to(device)是一种将设备参数(和缓冲区)发送到GPU的便捷方法,因为否则(如果将设备设置为CPU)则不会执行任何操作。 在将设备参数传递给优化器之前,将其传递到适当的设备很重要; 否则,优化器将无法正确跟踪参数!

神经网络( nn.Module )和优化器( optim.Optimizer )均可保存和加载其内部状态,建议使用.load_state_dict(state_dict)进行此.load_state_dict(state_dict) -必须重新加载两者的状态,以便基于先前保存的字典来恢复训练状态。 保存整个对象可能会充满错误 。 如果将张量保存在GPU上并想将它们加载到CPU或另一个GPU上,那么最简单的方法是使用map_location 选项将它们直接加载到CPU上,例如torch.load('model.pth'map_location='cpu' )。

这里有一些其他地方未在此处显示,但值得一提,您可以直接使用控制流(例如, if的执行可能取决于成员变量或数据本身。此外,在过程中间进行输出是完全可以接受的( print )张量,极大地简化了调试;最后,通过直接传递,可以使用很多参数。我将通过简短的清单来说明这一点,该清单与任何特定思想无关:

 def forward(self, x, hx, drop=False): hx2 = self.rnn(x, hx) print(hx.mean().item(), hx.var().item()) if hx.max.item() > 10 or self.can_drop and drop: return hx else: return hx2 

培训课程


 model.train() train_losses = [] for i, (data, target) in enumerate(train_loader): data = data.to(device=device, non_blocking=True) target = target.to(device=device, non_blocking=True) optimiser.zero_grad() output = model(data) loss = F.nll_loss(output, target) loss.backward() train_losses.append(loss.item()) optimiser.step() if i % 10 == 0: print(i, loss.item()) torch.save(model.state_dict(), 'model.pth') torch.save(optimiser.state_dict(), 'optimiser.pth') torch.save(train_losses, 'train_losses.pth') 

默认情况下,网络模块进入训练模式-在一定程度上影响模块的运行-最重要的是-精简和批量标准化。 一种或另一种方式,最好使用.train()手动设置此类设置,该设置会过滤所有子模块的“ training”标志。

在这里, .to()方法不仅接受设备,还设置non_blocking=True ,从而确保将数据从已提交的内存异步复制到GPU,从而使CPU在数据传输期间保持可操作状态。 否则, non_blocking=True根本不是一种选择。

在使用loss.backward()loss.backward()建立新的梯度集之前,必须先使用loss.backward()手动重置要优化的参数的梯度。 默认情况下,PyTorch会累积梯度,如果您没有足够的资源来一次计算所需的所有梯度,这将非常方便。

PyTorch使用自动渐变的“磁带”系统-它收集有关在张量上执行的操作和顺序的信息,然后以相反的方向播放它们以相反的顺序执行微分(反向模式微分)。 这就是为什么它是如此的灵活,并允许任意的计算图。 如果这些张量都不要求渐变(您必须将requires_grad=True设置为为此目的创建张量),则不会保存任何图形! 但是,网络通常具有需要梯度的参数,因此,基于网络输出执行的任何计算都将存储在图中。 因此,如果要保存此步骤生成的数据,则需要手动禁用渐变或(更常见的方法)将此信息另存为Python数字(在PyTorch标量中使用.item() )或numpy数组。 在官方文档中阅读有关autograd更多信息。

缩短计算图的一种方法是在学习带有截断时间反向传播版本的RNN时,通过隐藏状态时使用.detach() 。 当区分损失时,当组件之一是另一个网络的输出时,这也很方便,但是不应针对损失优化该另一个网络。 例如,我将讲授与GAN合作时产生的输出材料的区别部分,或使用目标函数作为基本函数(例如A2C)的行为者评论算法中的策略训练。 防止梯度计算的另一种技术有效地训练GAN(对区分材料进行生成部分的训练),并且在微调中通常采用的典型方法param.requires_grad = False参数param.requires_grad = False的网络参数的循环枚举。

重要的是,不仅要在控制台/日志文件中记录结果,而且还要以防万一,在模型参数(和优化器状态)中设置控制点。 您还可以使用torch.save()保存常规的Python对象,或使用另一个标准解决方案-内置pickle

测试中


 model.eval() test_loss, correct = 0, 0 with torch.no_grad(): for data, target in test_loader: data = data.to(device=device, non_blocking=True) target = target.to(device=device, non_blocking=True) output = model(data) test_loss += F.nll_loss(output, target, reduction='sum').item() pred = output.argmax(1, keepdim=True) correct += pred.eq(target.view_as(pred)).sum().item() test_loss /= len(test_data) acc = correct / len(test_data) print(acc, test_loss) 

响应.train()需要使用.eval()将网络明确地置于评估模式。

如上所述,当使用网络时,通常会编译计算图。 为了避免这种情况,请在no_grad上下文no_gradwith torch.no_grad()

还有一些


这是附加的部分,在其中我做了一些更有用的论述。
这是解释使用内存的官方文档

CUDA错误? 很难修复它们,通常它们之间存在逻辑上的不一致,据此,在CPU上比在GPU上显示的错误信息更多。 最重要的是,如果您打算使用GPU,则可以在CPU和GPU之间快速切换。 一个更通用的开发技巧是组织代码,以便在开始一项完整的任务之前可以对其进行快速检查。 例如,准备一个小型或综合数据集,运行一个时代训练+测试等。 如果问题是CUDA错误,或者您根本无法切换到CPU,请设置CUDA_LAUNCH_BLOCKING = 1。 这将使CUDA内核启动同步,并且您将收到更准确的错误消息。

关于torch.multiprocessing的说明,或仅同时运行多个PyTorch脚本。 由于PyTorch使用多线程BLAS库来加速CPU上的线性代数计算,因此通常涉及多个内核。 如果要使用多线程处理或多个脚本同时执行多个操作,建议通过将环境变量OMP_NUM_THREADS设置为1或另一个较低的值来手动减少它们的数量。 因此,减少了滑倒处理器的可能性。 官方文档中还有关于多线程处理的其他注释。

Source: https://habr.com/ru/post/zh-CN471228/


All Articles