目录
一、简介
二、安装 PyTorch Lightning
三、定义 LightningModule
3.1 SYSTEM VS MODEL
3.2 FORWARD vs TRAINING_STEP
三、配置 Lightning Trainer
四、基本特性
4.1 Manual vs automatic optimization
4.1.1 自动优化 (Automatic optimization)
4.1.1 手动优化 (Manual optimization)
4.2 Predict or Deploy
4.2.1 选项一 —— 子模型 (Sub-models)
4.2.2 选项二 —— 前馈 (Forward)
4.2.2 选项三 —— 生产 (Production)
4.3 Using CPUs/GPUs/TPUs
4.4 Checkpoints
4.5 Data flow
4.6 Logging
4.7 Optional extensions
4.7.1 回调 (Callbacks)
4.7.2 LightningDataModules
4.8 Debugging
五、其他炫酷特性
相关文章
【PyTorch Lightning】简介
【PyTorch Ligntning】如何将 PyTorch 组织为 Lightning
【PyTorch Lightning】1.0 正式发布:从 0 到 1
项目地址:https://github.com/PyTorchLightning/pytorch-lightning
本指南将展示如何分两步将 PyTorch 代码组织成 Lightning。
使用 PyTorch Lightning 组织代码,可以使代码:
pip 安装:
pip install pytorch-lightning
或 conda 安装:
conda install pytorch-lightning -c conda-forge
或在 conda 虚拟环境下安装:
conda activate my_env
pip install pytorch-lightning
在新源文件中导入以下将用到的依赖:
import os
import torch
from torch import nn
import torch.nn.functional as F
from torchvision.datasets import MNIST
from torchvision import transforms
from torch.utils.data import DataLoader
import pytorch_lightning as pl
from torch.utils.data import random_split
class LitAutoEncoder(pl.LightningModule):
def __init__(self):
super().__init__()
self.encoder = nn.Sequential(
nn.Linear(28*28, 64),
nn.ReLU(),
nn.Linear(64, 3)
)
self.decoder = nn.Sequential(
nn.Linear(3, 64),
nn.ReLU(),
nn.Linear(64, 28*28)
)
def forward(self, x):
# in lightning, forward defines the prediction/inference actions
embedding = self.encoder(x)
return embedding
def training_step(self, batch, batch_idx):
# training_step defined the train loop.
# It is independent of forward
x, y = batch
x = x.view(x.size(0), -1)
z = self.encoder(x)
x_hat = self.decoder(z)
loss = F.mse_loss(x_hat, x)
# Logging to TensorBoard by default
self.log('train_loss', loss)
return loss
def configure_optimizers(self):
optimizer = torch.optim.Adam(self.parameters(), lr=1e-3)
return optimizer
注意,LightningModule 定义了一个系统 (system) 而不仅仅是一个模型 (model):
关于系统 (system) 的例子还有:
在内部,LightningModule 仍只是一个 torch.nn.Module,它将所有研究代码分组到一个文件中以使其自成一体:
可以通过覆盖 Available Callback hooks 中找到的 20+ 个 hooks 中的任意一个,来自定义任何训练部分 (如反向传播):
class LitAutoEncoder(pl.LightningModule):
def backward(self, loss, optimizer, optimizer_idx):
loss.backward()
在 Lightning 中,我们将训练与推理分开。training_step 定义了完整的训练循环。鼓励用户用 forward 定义推理行为。
例如,在这种情况下,可以定义自动编码器以充当嵌入提取器 (embedding extractor):
def forward(self, x):
embeddings = self.encoder(x)
return embeddings
当然,没有什么可以阻止你在 training_step 中使用 forward:
def training_step(self, batch, batch_idx):
...
z = self(x)
这确实取决于个人的应用程序,但仍建议将两个意图分开。
更多细节在 LightningModule 文档中。
首先,定义所需的数据。Lightning 只需一个 DataLoader 用于训练/验证/测试数据分片:
dataset = MNIST(os.getcwd(), download=True, transform=transforms.ToTensor())
train_loader = DataLoader(dataset)
接着,初始化 LightningModule 和 PyTorch Lightning Trainer,然后对数据和模型进行拟合。
# init model
autoencoder = LitAutoEncoder()
# most basic trainer, uses good defaults (auto-tensorboard, checkpoints, logs, and more)
# trainer = pl.Trainer(gpus=8) (if you have GPUs)
trainer = pl.Trainer()
trainer.fit(autoencoder, train_loader)
Trainer 自动化以下内容:
小贴士
若希望手动管理优化器,则可使用 “手动优化” 模式 (Manual optimization mode)(即:RL, GANs 等)。
以上便是在 Lightning 中需要了解的 2 个主要概念。Lightning 的所有其他特性都是 Trainer 或 LightningModule 的特性。
使用 Lightning,无需担心何时启用/禁用梯度 (grads),进行反向传播或更新优化器,只要在 training_step 中附带图 (attached graph) 返回损失,Lightning 就会自动执行优化。
def training_step(self, batch, batch_idx):
loss = self.encoder(batch[0])
return loss
然而,对于某些研究,如 GAN、强化学习或带有多个优化器或内部循环的某些研究,可以关闭自动优化并完全自主控制训练循环。
首先,关闭自动优化:
trainer = Trainer(automatic_optimization=False)
然后,构造自己的训练循环:
def training_step(self, batch, batch_idx, opt_idx):
(opt_a, opt_b, opt_c) = self.optimizers()
loss_a = self.generator(batch[0])
# use this instead of loss.backward so we can automate half precision, etc...
self.manual_backward(loss_a, opt_a, retain_graph=True)
self.manual_backward(loss_a, opt_a)
opt_a.step()
opt_a.zero_grad()
loss_b = self.discriminator(batch[0])
self.manual_backward(loss_b, opt_b)
...
完成训练后,可以使用 3 个选项将 LightningModule 用于预测。
取出系统 (system) 内部的任何模型进行预测。
# ----------------------------------
# to use as embedding extractor
# ----------------------------------
autoencoder = LitAutoEncoder.load_from_checkpoint('path/to/checkpoint_file.ckpt')
encoder_model = autoencoder.encoder
encoder_model.eval()
# ----------------------------------
# to use as image generator
# ----------------------------------
decoder_model = autoencoder.decoder
decoder_model.eval()
如果需要,也可以加入一个 forward 方法进行预测:
# ----------------------------------
# using the AE to extract embeddings
# ----------------------------------
class LitAutoEncoder(pl.LightningModule):
def forward(self, x):
embedding = self.encoder(x)
return embedding
autoencoder = LitAutoencoder()
autoencoder = autoencoder(torch.rand(1, 28 * 28))
# ----------------------------------
# or using the AE to generate images
# ----------------------------------
class LitAutoEncoder(pl.LightningModule):
def forward(self):
z = torch.rand(1, 3)
image = self.decoder(z)
image = image.view(1, 1, 28, 28)
return image
autoencoder = LitAutoencoder()
image_sample = autoencoder(()
对于生产系统 (production systems),onnx 或 torchscript 的速度要快得多。确保已添加 forward 方法或仅跟踪所需的子模型 (sub-models)。
# ----------------------------------
# torchscript
# ----------------------------------
autoencoder = LitAutoEncoder()
torch.jit.save(autoencoder.to_torchscript(), "model.pt")
os.path.isfile("model.pt")
# ----------------------------------
# onnx
# ----------------------------------
with tempfile.NamedTemporaryFile(suffix='.onnx', delete=False) as tmpfile:
autoencoder = LitAutoEncoder()
input_sample = torch.randn((1, 28 * 28))
autoencoder.to_onnx(tmpfile.name, input_sample, export_params=True)
os.path.isfile(tmpfile.name)
在 Lightning 中使用 CPU、GPU 或 TPU 很简单。无需更改代码,只需更改 Trainer 选项 即可。
# train on CPU
trainer = pl.Trainer()
# train on 8 CPUs
trainer = pl.Trainer(num_processes=8)
# train on 1024 CPUs across 128 machines
trainer = pl.Trainer(
num_processes=8,
num_nodes=128
)
# train on 1 GPU
trainer = pl.Trainer(gpus=1)
# train on multiple GPUs across nodes (32 gpus here)
trainer = pl.Trainer(
gpus=4,
num_nodes=8
)
# train on gpu 1, 3, 5 (3 gpus total)
trainer = pl.Trainer(gpus=[1, 3, 5])
# Multi GPU with mixed precision
trainer = pl.Trainer(gpus=2, precision=16)
# Train on TPUs
trainer = pl.Trainer(tpu_cores=8)
现在,无需更改自己代码,就可以用上述代码执行以下操作:
# train on TPUs using 16 bit precision
# using only half the training data and checking validation every quarter of a training epoch
trainer = pl.Trainer(
tpu_cores=8,
precision=16,
limit_train_batches=0.5,
val_check_interval=0.25
)
Lightning 会自动地保存模型。一旦进行过训练,即可按如下方式加载检查点 (checkpoints):
model = LitModel.load_from_checkpoint(path)
上面的检查点 (checkpoints) 包含 初始化模型 和 设置状态字典 (state dict) 所需的所有参数。如果希望手动进行操作,则可以使用以下等价方式:
# load the ckpt
ckpt = torch.load('path/to/checkpoint.ckpt')
# equivalent to the above
model = LitModel()
model.load_state_dict(ckpt['state_dict'])
每个循环 (训练,验证,测试) 都有三个钩子 (hooks) 函数可以执行:
为阐明数据如何流动,此处使用训练循环说明 (即 x=training):
outs = []
for batch in data:
out = training_step(batch)
outs.append(out)
training_epoch_end(outs)
这在 Lightning 中等价为:
def training_step(self, batch, batch_idx):
prediction = ...
return prediction
def training_epoch_end(self, training_step_outputs):
for prediction in predictions:
# do something with these
若使用 DP 或 DDP2 分布式模式 (即:拆分一个 batch 到各 GPUs),请使用 x_step_end 手动进行聚合 (aggregate) (或不实施以让 Lightning 自动进行聚合)。
for batch in data:
model_copies = copy_model_per_gpu(model, num_gpus)
batch_split = split_batch_per_gpu(batch, num_gpus)
gpu_outs = []
for model, batch_part in zip(model_copies, batch_split):
# LightningModule hook
gpu_out = model.training_step(batch_part)
gpu_outs.append(gpu_out)
# LightningModule hook
out = training_step_end(gpu_outs)
这在 Lightning 中等价为:
def training_step(self, batch, batch_idx):
loss = ...
return loss
def training_step_end(self, losses):
gpu_0_loss = losses[0]
gpu_1_loss = losses[1]
return (gpu_0_loss + gpu_1_loss) * 1/2
小贴士
验证和测试循环具有同样的结构。
为记录日志 (log) 到 Tensorboard、最喜欢的记录器 (logger) 和/或 进度条,请使用 log() 方法,该方法可以从LightningModule 中的任何方法调用:
def training_step(self, batch, batch_idx):
self.log('my_metric', x)
log() 方法有以下几个选项:
取决于从何处调用日志 (log),Lightning 会自动确定正确的模式。但当然,亦可通过手动设置标志来覆盖默认行为。
注意
设置 on_epoch = True 将在整个训练 epoch 内累积记录值。
def training_step(self, batch, batch_idx): self.log('my_loss', loss, on_step=True, on_epoch=True, prog_bar=True, logger=True)
注意
进度条中显示的损失值在最后一个值上进行了平滑 (平均),因此它不同于训练/验证步骤中返回的实际损失。
你也可以直接使用你的记录器 (loggers) 的任何方法:
def training_step(self, batch, batch_idx):
tensorboard = self.logger.experiment
tensorboard.any_summary_writer_method_you_want())
一旦训练开始后,就可以使用喜欢的记录器 (logger) 或启动 Tensorboard logs 来查看日志:
tensorboard --logdir ./lightning_logs
注意
Lightning 会自动在进度栏中显示从 training_step 返回的损失值。因此,无需显式记录此类日志,例如像这样 self.log('loss', loss, prog_bar = True)。
阅读更多关于记录器 (Loggers) 的内容。
回调 (Callbacks) 是一个任意的独立程序,可以在训练循环的任意部分执行。
以下是添加不太理想的学习率衰减规则的示例:
class DecayLearningRate(pl.Callback)
def __init__(self):
self.old_lrs = []
def on_train_start(self, trainer, pl_module):
# track the initial learning rates
for opt_idx in optimizer in enumerate(trainer.optimizers):
group = []
for param_group in optimizer.param_groups:
group.append(param_group['lr'])
self.old_lrs.append(group)
def on_train_epoch_end(self, trainer, pl_module, outputs):
for opt_idx in optimizer in enumerate(trainer.optimizers):
old_lr_group = self.old_lrs[opt_idx]
new_lr_group = []
for p_idx, param_group in enumerate(optimizer.param_groups):
old_lr = old_lr_group[p_idx]
new_lr = old_lr * 0.98
new_lr_group.append(new_lr)
param_group['lr'] = new_lr
self.old_lrs[opt_idx] = new_lr_group
可以通过回调执行的操作:
了解更多关于自定义回调 (custom callbacks) 的内容。
DataLoader 和数据处理代码趋向于四处分散。通过将其组织到 LightningDataModule 中,使数据代码可重用。
class MNISTDataModule(pl.LightningDataModule):
def __init__(self, batch_size=32):
super().__init__()
self.batch_size = batch_size
# When doing distributed training, Datamodules have two optional arguments for
# granular control over download/prepare/splitting data:
# OPTIONAL, called only on 1 GPU/machine
def prepare_data(self):
MNIST(os.getcwd(), train=True, download=True)
MNIST(os.getcwd(), train=False, download=True)
# OPTIONAL, called for every GPU/machine (assigning state is OK)
def setup(self, stage):
# transforms
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
# split dataset
if stage == 'fit':
mnist_train = MNIST(os.getcwd(), train=True, transform=transform)
self.mnist_train, self.mnist_val = random_split(mnist_train, [55000, 5000])
if stage == 'test':
self.mnist_test = MNIST(os.getcwd(), train=False, transform=transform)
# return the dataloader for each split
def train_dataloader(self):
mnist_train = DataLoader(self.mnist_train, batch_size=self.batch_size)
return mnist_train
def val_dataloader(self):
mnist_val = DataLoader(self.mnist_val, batch_size=self.batch_size)
return mnist_val
def test_dataloader(self):
mnist_test = DataLoader(self.mnist_test, batch_size=self.batch_size)
return mnist_test
LightningDataModule 旨在支持在不同项目之间共享和重用数据分片和转换 (splits and transforms)。它封装了处理数据所需的所有步骤:下载、标记化、处理 (downloading, tokenizing, processing) 等。
现在,只需将 LightningDataModule 传递给 Trainer:
# init model
model = LitModel()
# init data
dm = MNISTDataModule()
# train
trainer = pl.Trainer()
trainer.fit(model, dm)
# test
trainer.test(datamodule=dm)
DataModules 对于基于数据构建模型特别有用。阅读更多关于 LightningDataModule 的内容。
Lightning 有许多调试工具。这是其中一些示例:
# use only 10 train batches and 3 val batches
trainer = pl.Trainer(limit_train_batches=10, limit_val_batches=3)
# Automatically overfit the sane batch of your model for a sanity test
trainer = pl.Trainer(overfit_batches=1)
# unit test all the code- hits every line of your code once to see if you have bugs,
# instead of waiting hours to crash on validation
trainer = pl.Trainer(fast_dev_run=True)
# train only 20% of an epoch
trainer = pl. Trainer(limit_train_batches=0.2)
# run validation every 25% of a training epoch
trainer = pl.Trainer(val_check_interval=0.25)
# Profile your code to find speed/memory bottlenecks
Trainer(profiler=True)
定义并训练了第一个 Lightning 模型后,你可能想尝试其他出色的功能,例如:
或者阅读 Step-by-step walk-through 以了解更多。
参考文献
https://pytorch-lightning.readthedocs.io/en/stable/new-project.html#