使用 pytorch-lightning

在用 PyTorch 训练不同模型时,需要编写很多重复的逻辑。一个工程的代码无可避免地变得越来越长,难以管理。PyTorch Lightning 作为一个对 PyTorch 二次封装的框架,能让训练逻辑的编写像堆积木一样秩序井然。

虽然叫做 lightning,这个库的学习成本并不低。好在一但熟悉,就能享受到很多便利。

安装

使用类似下面的指令安装 PyTorch Lightning。

python -m pip install lightning

完成后就可以在 Python 脚本里 import pytorch_lightning 了。

快速开始

PyTorch Lightning 的宗旨是:all in one。

不使用任何框架,写出来的训练代码会像是这样:


writer = SummaryWriter()
model = ResNet18().to(device)
optimizer = torch.optim.Adam(model.params, lr=1e-3)

for epoch in range(num_epochs):
    for i, batch in enumerate(train_data):
        x, y = batch
        x = x.to(device)
        output = model(x)
        loss = criterion(output, y)
        writer.add_scalar("train_loss", loss, num_epochs * len(train_data) + i)

        loss.backward()
        optimizer.step()
        optimizer.zero_grad()

    with torch.no_grad():
        for batch in val_data:
            x, y = batch

                ···

若使用 PyTorch Lightning,可以把这一切封装到一个 class 内:

import pytorch_lightning as L

class LightningModel(L.LightningModule):
    def __init__(self):
        super().__init__()
        self.model = ResNet18()
    
    def forward(self, x):
        return model(x)

    def configure_optimizers(self):
        optimizer = torch.optim.Adam(self.model.parameters, lr=1e-3)
        return optimizer

    def training_step(self, train_batch, batch_idx):
        x, y = train_batch
        output = self.model(x)
        loss = criterion(output, y)
        self.log('train_loss', loss, on_step=True, on_epoch=False)
        return loss

    def validation_step(self, val_batch, batch_idx):
        x, y = val_batch
        output = criterion(output, y)
        loss = criterion(output, y)
        self.log('val_loss', loss)

可见 lightning 的特点以及省下的工作:

  • 将训练各步骤的代码分配到不同的函数,层次分明
  • 不需要 .to(device)。lightning 会自动转换
  • 不需要手动 loss.backward() optimizer.step() optimizer.zero_grad()
  • validation 阶段不需要手动 torch.no_grad()
  • 记录 log 时 lightning 会帮助填写当前 step 和 epoch
  • ……

接下来训练的代码就可以非常简明:

model = LightningModel()
trainer = L.Trainer(accelerator='gpu', max_epochs=15)
trainer.fit(model, train_data, val_data) # 正式开始训练

使用混合精度

若要在 lightning 的训练启用混合精度加速,需要先执行:

torch.set_float32_matmul_precision('high')

然后在实例化 trainer 时,传入参数 precision='bf16-mixed' 即可。参数可选的值随硬件条件改变而变化,请查阅文档。

学习率调度器(scheduler)

在自定义 configure_optimizers() 时,除了需要指定 optimizer,还可以设定一个学习率调度器 scheduler。

class LightningModel(L.LightningModule):
    ···
    def configure_optimizers(self):
        optimizer = torch.optim.Adam(self.model.parameters(), lr=0.001)
        scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=10)
        return [optimizer], [scheduler]

lightning 会自动执行 scheduler.step()

trainer.init_module() 节省初始化资源

利用 trainer.init_module(),可以避免额外的精度转换,且能直接将模型加载到 GPU(而不是从 CPU 转移)。

trainer = Trainer(accelerator="cuda", precision="16-true")

with trainer.init_module():
    # models created here will be on GPU and in float16
    model = MyLightningModule()

若是从 checkpoint 初始化模型,可以向 trainer 传入参数 empty_init=True,这样在读取 checkpoint 之前模型的权重不会占用内存空间,且速度更快。

with trainer.init_module(empty_init=True):
    model = MyLightningModule.load_from_checkpoint("my/checkpoint/path.ckpt")

trainer.fit(model)

要注意,此时必须保证模型的每个权重都从 checkpoint 加载(或是手动加载),否则模型不完整。

针对使用 FSDP 或 DeepSpeed 训练的大参数模型,就不应使用 trainer.init_module() 了。对应的,为了加快大参数模型加载速度、减少内存消耗,在编写 LightningModel 时要把模型参数写到 def configure_model(self) 方法中。

设置所有随机数种子

lightning 提供了一种设置全局随机数种子的方法,能把 numpy、python 和 torch 的随机数种子都固定住,很适合调参。

若 dataloader 设置了多 worker,这个方法也能照顾到。保证每次重置获取的 batch 都一样。

import pytorch_lightning as pl
pl.seed_everything(42, workers=True)
posted @ 2024-01-14 20:05  倒地  阅读(969)  评论(0)    收藏  举报