教程|PyTorch经验指南:技巧与陷阱

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)可有效地使用手动化设备。

对于可重复实验,有必要为使用随机数生成的任何数据设置随机种子(如果也使用随机数,则包括随机或 numpy)。要注意,cuDNN 用的是非确定算法,可以通过语句 torch.backends.cudnn.enabled = False 将其禁用。

数据

train_data = datasets.MNIST('data', train=True, download=True,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))]))
test_data = datasets.MNIST('data', 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.transforms 对于单张图像有非常多便利的转换工具,例如裁剪和归一化等。

DataLoader 包含非常多的参数,除了 batch_size 和 shuffle,num_workers 和 pin_memory 对于高效加载数据同样非常重要。例如配置 num_workers > 0 将使用子进程异步加载数据,而不是使用一个主进程块加载数据。参数 pin_memory 使用固定 RAM 以加速 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),或者完全由 layers 函数构成的网络(如使用 nn.ReLU 而不是 F.relu)。

在将 device 设置为 GPU 时,.to(device) 是一种将设备参数(和缓存器)发送到 GPU 的便捷方式,且在将 device 设置为 CPU 时不会做任何处理。在将网络参数传递给优化器之前,把它们传递给适当的设备非常重要,不然的话优化器不能正确地追踪参数。

神经网络(nn.Module)和优化器(optim.Optimizer)都能保存和加载它们的内部状态,而.load_state_dict(state_dict) 是完成这一操作的推荐方法,我们可以从以前保存的状态字典中加载两者的状态并恢复训练。此外,保存整个对象可能会出错。

这里没讨论的一些注意事项即前向传播可以使用控制流,例如一个成员变量或数据本身能决定 if 语句的执行。此外,在前向传播的过程中打印张量也是可行的,这令 debug 更加简单。最后,前向传播可以使用多个参数。以下使用间断的代码块展示这一点:

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, target = data.to(device), target.to(device)
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')

网络模块默认设置为训练模式,这影响了某些模块的工作方式,最明显的是 dropout 和批归一化。最好用.train 对其进行手动设置,这样可以把训练标记向下传播到所有子模块。

在使用 loss.backward 收集一系列新的梯度以及用 optimiser.step 做反向传播之前,有必要手动地将由 optimiser.zero_grad 优化的参数梯度归零。默认情况下,PyTorch 会累加梯度,在单次迭代中没有足够资源来计算所有需要的梯度时,这种做法非常便利。

PyTorch 使用一种基于 tape 的自动化梯度(autograd)系统,它收集按顺序在张量上执行的运算,然后反向重放它们来执行反向模式微分。这正是为什么 PyTorch 如此灵活并允许执行任意计算图的原因。如果没有张量需要做梯度更新(当你需要为该过程构建一个张量时,你必须设置 requires_grad=True),则不需要保存任何图。然而,网络倾向于包含需要梯度更新的参数,因此任何网络输出过程中执行的计算都将保存在图中。因此如果想保存在该过程中得到的数据,你将需要手动禁止梯度更新,或者,更常见的做法是将其保存为一个 Python 数(通过一个 Python 标量上的.item())或者 NumPy 数组。更多关于 autograd 的细节详见官网文件。

截取计算图的一种方式是使用.detach,当通过沿时间的截断反向传播训练 RNN 时,数据流传递到一个隐藏状态可能会应用这个函数。当对损失函数求微分(其中一个成分是另一个网络的输出)时,也会很方便。但另一个网络不应该用「loss - examples」的模式进行优化,包括在 GAN 训练中从生成器的输出训练判别器,或使用价值函数作为基线(例如 A2C)训练 actor-critic 算法的策略。另一种在 GAN 训练(从判别器训练生成器)中能高效阻止梯度计算的方法是在整个网络参数上建立循环,并设置 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, target = data.to(device), target.to(device)
output = model(data)
test_loss += F.nll_loss(output, target, size_average=False).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 将网络明确地设置为评估模式。

正如前文所述,计算图通常会在使用网络时生成。通过 with torch.no_grad 使用 no_grad 上下文管理器,可以防止这种情况发生。

其它

内存有问题?可以查看官网文件获取帮助。

CUDA 出错?它们很难调试,而且通常是一个逻辑问题,会在 CPU 上产生更易理解的错误信息。如果你计划使用 GPU,那最好能够在 CPU 和 GPU 之间轻松切换。更普遍的开发技巧是设置代码,以便在启动合适的项目(例如准备一个较小/合成的数据集、运行一个 train + test epoch 等)之前快速运行所有逻辑来检查它。如果这是一个 CUDA 错误,或者你没法切换到 CPU,设置 CUDA_LAUNCH_BLOCKING=1 将使 CUDA 内核同步启动,从而提供更详细的错误信息。

torch.multiprocessing,甚至只是一次运行多个 PyTorch 脚本的注意事项。因为 PyTorch 使用多线程 BLAS 库来加速 CPU 上的线性代数计算,所以它通常需要使用多个内核。如果你想一次运行多个任务,在具有多进程或多个脚本的情况下,通过将环境变量 OMP_NUM_THREADS 设置为 1 或另一个较小的数字来手动减少线程,这样做减少了 CPU thrashing 的可能性。官网文件还有一些其它注意事项,尤其是关于多进程。教程|PyTorch经验指南:技巧与陷阱

✄------------------------------------------------

加入机器之心(全职记者 / 实习生):[email protected]

投稿或寻求报道:content@jiqizhixin.com

广告 & 商务合作:[email protected]


分享到:


相關文章: