其他
【他山之石】pytorch_lightning 全程笔记
“他山之石,可以攻玉”,站在巨人的肩膀才能看得更高,走得更远。在科研的道路上,更需借助东风才能更快前行。为此,我们特别搜集整理了一些实用的代码链接,数据集,软件,编程技巧等,开辟“他山之石”专栏,助你乘风破浪,一路奋勇向前,敬请关注。
地址:https://www.zhihu.com/people/yongqian_xiao
前言
环境
OS (e.g., Linux): Ubuntu 18.04
pytorch: 1.7
pytorch_lightning: 1.0.7
cudatoolkit version: 10.0.130
GPU models and configuration: 2080Ti 10GB x 4
pl 的流程
PL的流程很简单,生产流水线,有一个固定的顺序:
*_step_end -- 即每一个 * 步完成后调用
*_epoch_end -- 即每一个 * 的epoch 完成之后会自动调用
def training_step(self, batch, batch_idx):
x, y = batch
y_hat = self.model(x)
loss = F.cross_entropy(y_hat, y)
pred = ...
return {'loss': loss, 'pred': pred}
def training_step_end(self, batch_parts):
'''
当gpus=0 or 1时,这里的batch_parts即为traing_step的返回值(已验证)
当gpus>1时,这里的batch_parts为list,list中每个为training_step返回值,list[i]为i号gpu的返回值(这里未验证)
'''
gpu_0_prediction = batch_parts[0]['pred']
gpu_1_prediction = batch_parts[1]['pred']
# do something with both outputs
return (batch_parts[0]['loss'] + batch_parts[1]['loss']) / 2
def training_epoch_end(self, training_step_outputs):
'''
当gpu=0 or 1时,training_step_outputs为list,长度为steps的数量(不包括validation的步数,当你训练时,你会发现返回list<训练时的steps数,这是因为训练时显示的steps数据还包括了validation的,若将limit_val_batches=0.,即关闭validation,则显示的steps会与training_step_outputs的长度相同)。list中的每个值为字典类型,字典中会存有`training_step_end()`返回的键值,键名为`training_step()`函数返回的变量名,另外还有该值是在哪台设备上(哪张GPU上),例如{device='cuda:0'}
'''
for out in training_step_outputs:
# do something with preds
Train
训练主要是重写def training_setp(self, batch, batch_idx)函数,并返回要反向传播的loss即可,其中batch 即为从 train_dataloader 采样的一个batch的数据,batch_idx即为目前batch的索引。
def training_setp(self, batch, batch_idx):
image, label = batch
pred = self.forward(iamge)
loss = ...
# 一定要返回loss
return loss
Validation
设置校验的频率
每训练n个epochs 校验一次
trainer = Trainer(check_val_every_n_epoch=1)
单个epoch内校验频率
# 每训练单个epoch的 25% 调用校验函数一次,注意:要传入float型数
trainer = Trainer(val_check_interval=0.25)
# 当然也可以是单个epoch训练完多少个batch后调用一次校验函数,但是一定是传入int型
trainer = Trainer(val_check_interval=100) # 每训练100个batch校验一次
def validation_step(self, batch, batch_idx):
image, label = batch
pred = self.forward(iamge)
loss = ...
# 标记该loss,用于保存模型时监控该量
self.log('val_loss', loss)
在pytoch_lightning框架中,test 在训练过程中是不调用的,也就是说是不相关,在训练过程中只进行training和validation,因此如果需要在训练过中保存validation的一些信息,就要放到validation中。
# 获取恢复了权重和超参数等的模型
model = MODEL.load_from_checkpoint(checkpoint_path='my_model_path/heiheihei.ckpt')
# 修改测试时需要的参数,例如预测的步数等
model.pred_step = 1000
# 定义trainer, 其中limit_test_batches表示取测试集中的0.05的数据来做测试
trainer = pl.Trainer(gpus=1, precision=16, limit_test_batches=0.05)
# 测试,自动调用test_step(), 其中dm为数据集,放在下面讲
trainer.test(model=dck, datamodule=dm)
直接实现
class ExampleModel(pl.LightningModule):
def __init__(self, args):
super().__init__()
self.train_dataset = ...
self.val_dataset = ...
self.test_dataset = ...
...
def train_dataloader(self):
return DataLoader(self.train_dataset, batch_size=self.batch_size, shuffle=False, num_workers=0)
def val_dataloader(self):
return DataLoader(self.val_dataset, batch_size=self.batch_size, shuffle=False)
def test_dataloader(self):
return DataLoader(self.test_dataset, batch_size=1, shuffle=True)
自定义DataModule
class MyDataModule(pl.LightningDataModule):
def __init__(self):
super().__init__()
...blablabla...
def setup(self, stage):
# 实现数据集的定义,每张GPU都会执行该函数, stage 用于标记是用于什么阶段
if stage == 'fit' or stage is None:
self.train_dataset = DCKDataset(self.train_file_path, self.train_file_num, transform=None)
self.val_dataset = DCKDataset(self.val_file_path, self.val_file_num, transform=None)
if stage == 'test' or stage is None:
self.test_dataset = DCKDataset(self.test_file_path, self.test_file_num, transform=None)
def prepare_data(self):
# 在该函数里一般实现数据集的下载等,只有cuda:0 会执行该函数
pass
def train_dataloader(self):
return DataLoader(self.train_dataset, batch_size=self.batch_size, shuffle=False, num_workers=0)
def val_dataloader(self):
return DataLoader(self.val_dataset, batch_size=self.batch_size, shuffle=False)
def test_dataloader(self):
return DataLoader(self.test_dataset, batch_size=1, shuffle=True)
使用
dm = MyDataModule(args)
if not is_predict:# 训练
# 定义保存模型的callback,仔细查看后文
checkpoint_callback = ModelCheckpoint(monitor='val_loss')
# 定义模型
model = MyModel()
# 定义logger
logger = TensorBoardLogger('log_dir', name='test_PL')
# 定义数据集为训练校验阶段
dm.setup('fit')
# 定义trainer
trainer = pl.Trainer(gpus=gpu, logger=logger, callbacks=[checkpoint_callback]);
# 开始训练
trainer.fit(dck, datamodule=dm)
else:
# 测试阶段
dm.setup('test')
# 恢复模型
model = MyModel.load_from_checkpoint(checkpoint_path='trained_model.ckpt')
# 定义trainer并测试
trainer = pl.Trainer(gpus=1, precision=16, limit_test_batches=0.05)
trainer.test(model=model, datamodule=dm)
模型保存与恢复
模型保存
自动保存
trainer = Trainer(default_root_dir='/your/path/to/save/checkpoints')
trainer = Trainer(checkpoint_callback=False)
ModelCheckpoint (callbacks)
计算需要监控的量,例如校验误差:loss 使用log()函数标记该要监控的量 初始化ModelCheckpoint回调,并设置要监控的量,下面有详细的描述 将其传回到Trainer中
from pytorch_lightning.callbacks import ModelCheckpoint
class LitAutoEncoder(pl.LightningModule):
def validation_step(self, batch, batch_idx):
x, y = batch
y_hat = self.backbone(x)
# 1. 计算需要监控的量
loss = F.cross_entropy(y_hat, y)
# 2. 使用log()函数标记该要监控的量,名字叫'val_loss'
self.log('val_loss', loss)
# 3. 初始化`ModelCheckpoint`回调,并设置要监控的量
checkpoint_callback = ModelCheckpoint(monitor='val_loss')
# 4. 将该callback 放到其他的callback 的list中
trainer = Trainer(callbacks=[checkpoint_callback])
CLASS pytorch_lightning.callbacks.model_checkpoint.ModelCheckpoint(filepath=None, monitor=None, verbose=False, save_last=None, save_top_k=None, save_weights_only=False, mode='auto', period=1, prefix='', dirpath=None, filename=None)
模型名称: my/path/epoch=2-val_loss=0.02-other_metric=0.03.ckpt
from pytorch_lightning import Trainer
from pytorch_lightning.callbacks import ModelCheckpoint
# saves checkpoints to 'my/path/' at every epoch
checkpoint_callback = ModelCheckpoint(dirpath='my/path/')
trainer = Trainer(callbacks=[checkpoint_callback])
# save epoch and val_loss in name
# saves a file like: my/path/sample-mnist-epoch=02-val_loss=0.32.ckpt
checkpoint_callback = ModelCheckpoint(monitor='val_loss', dirpath='my/path/', filename='sample-mnist-{epoch:02d}-{val_loss:.2f}')
获取最好的模型
from pytorch_lightning import Trainer
from pytorch_lightning.callbacks import ModelCheckpoint
checkpoint_callback = ModelCheckpoint(dirpath='my/path/')
trainer = Trainer(callbacks=[checkpoint_callback])
model = ...
trainer.fit(model)
# 训练完成之后,保存了多个模型,下面是获得最好的模型,也就是将原来保存的模型中最好的模型权重apply到当前的网络上
checkpoint_callback.best_model_path
手动保存模型
from collections import deque
import os
# 维护一个队列
self.save_models = deque(maxlen=3)
# 这里的self 是指这个函数放到继承了pl.LightningModule的类里,跟training_step()是同级的
def manual_save_model(self):
model_path = 'your_model_save_path_%s' % (your_loss)
if len(self.save_models) >= 3:
# 当队列满了,取出最老的模型的路径,然后删除掉
old_model = self.save_models.popleft()
if os.path.exists(old_model):
os.remove(old_model)
# 手动保存
self.trainer.save_checkpoint(model_path)
# 将保存的模型路径加入到队列中
self.save_models.append(model_path)
# 保存最新的路径
def save_latest_model(self):
checkpoint_callbacks = [c for c in self.trainer.callbacks if isinstance(c, ModelCheckpoint)]
print("Saving latest checkpoint...")
model = self.trainer.get_model()
[c.on_validation_end(self.trainer, model) for c in checkpoint_callbacks]
一些其他的函数
format_checkpoint_name(epoch, step, metrics, ver=None)
在上面的filename参数中,定义了模型文件的保存格式,这个函数就是给其中的变量赋值的,返回string类型,文件名
>>> tmpdir = os.path.dirname(__file__)
>>> ckpt = ModelCheckpoint(dirpath=tmpdir, filename='{epoch}')
>>> os.path.basename(ckpt.format_checkpoint_name(0, 1, metrics={}))
'epoch=0.ckpt'
>>> ckpt = ModelCheckpoint(dirpath=tmpdir, filename='{epoch:03d}')
>>> os.path.basename(ckpt.format_checkpoint_name(5, 2, metrics={}))
'epoch=005.ckpt'
>>> ckpt = ModelCheckpoint(dirpath=tmpdir, filename='{epoch}-{val_loss:.2f}')
>>> os.path.basename(ckpt.format_checkpoint_name(2, 3, metrics=dict(val_loss=0.123456)))
'epoch=2-val_loss=0.12.ckpt'
>>> ckpt = ModelCheckpoint(dirpath=tmpdir, filename='{missing:d}')
>>> os.path.basename(ckpt.format_checkpoint_name(0, 4, metrics={}))
'missing=0.ckpt'
>>> ckpt = ModelCheckpoint(filename='{step}')
>>> os.path.basename(ckpt.format_checkpoint_name(0, 0, {}))
'step=0.ckpt'
手动保存
model = MyLightningModule(hparams)
trainer.fit(model)
trainer.save_checkpoint("example.ckpt")
new_model = MyModel.load_from_checkpoint(checkpoint_path="example.ckpt")
加载Checkpoint
model = MyLightingModule.load_from_checkpoint(PATH)
print(model.learning_rate)
# prints the learning_rate you used in this checkpoint
model.eval()
y_hat = model(x)
class LitModel(LightningModule):
def __init__(self, in_dim, out_dim):
super().__init__()
self.save_hyperparameters()
# 在这里使用新的超参数,而不是从模型中加载的超参数
self.l1 = nn.Linear(self.hparams.in_dim, self.hparams.out_dim)
# 例如训练的时候初始化in_dim=32, out_dim=10
LitModel(in_dim=32, out_dim=10)
# 下面的方式恢复模型,使用in_dim=32和out_dim=10为保存的参数
model = LitModel.load_from_checkpoint(PATH)
# 当然也可以覆盖这些参数,例如改成in_dim=128, out_dim=10
model = LitModel.load_from_checkpoint(PATH, in_dim=128, out_dim10)
load_from_checkpoint 方法
LightningModule.load_from_checkpoint(checkpoint_path, map_location=None, hparams_file=None, strict=True, **kwargs)
该方法是从checkpoint 加载模型的主要方法,
恢复模型和Trainer
model = LitModel()
trainer = Trainer(resume_from_checkpoint='some/path/to/my_checkpoint.ckpt')
# 自动恢复模型、epoch、step、学习率信息(包括LR schedulers),精度等
# automatically restores model, epoch, step, LR schedulers, apex, etc...
trainer.fit(model)
训练辅助
Early Stopping
pytorch_lightning.callbacks.early_stopping.EarlyStopping(monitor='early_stop_on', min_delta=0.0, patience=3, verbose=False, mode='auto', strict=True)
Logging
# 在定义Trainer对象的时候,传入tensorboardlogger
logger = TensorBoardLogger(args['log_dir'], name='DCK_PL')
trainer = pl.Trainer(logger=logger)
# 获取tensorboard Logger, 以在validation_step()函数为例
def validation_step():
tensorboard = self.logger.experiment
# 例如求得validation loss为:
loss = ...
# 直接log
self.log('val_loss', loss, on_step=True, on_epoch=True, prog_bar=True, logger=True)
# 如果是图像等,就需要用到tensorboard的API
tensorboard.add_image()
# 同时log多个
other_loss = ...
loss_dict = {'val_loss': loss, 'loss': other_loss}
tensorboard.add_scalars(loss_dict)
# log 权重等
tensorboard.add_histogram(...)
# 查看的方法跟tensorboard是一样的,在终端下
tensorboard --logdir=my/log_path
optimizer 和 lr_scheduler
# 重写configure_optimizers()函数即可
# 设置优化器
def configure_optimizers(self):
weight_decay = 1e-6 # l2正则化系数
# 假如有两个网络,一个encoder一个decoder
optimizer = optim.Adam([{'encoder_params': self.encoder.parameters()}, {'decoder_params': self.decoder.parameters()}], lr=learning_rate, weight_decay=weight_decay)
# 同样,如果只有一个网络结构,就可以更直接了
optimizer = optim.Adam(my_model.parameters(), lr=learning_rate, weight_decay=weight_decay)
# 我这里设置2000个epoch后学习率变为原来的0.5,之后不再改变
StepLR = torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones=[2000], gamma=0.5)
optim_dict = {'optimizer': optimizer, 'lr_scheduler': StepLR}
return optim_dict
多优化器用于多模型等网络结构
# multiple optimizer case (e.g.: GAN)
def configure_optimizers(self):
opt_d = Adam(self.model_d.parameters(), lr=0.01)
opt_g = Adam(self.model_g.parameters(), lr=0.02)
return opt_d, opt_g
# 在new Trainer对象的时候,把自动优化关掉
trainer = Trainer(automatic_optimization=False)
def training_step(self, batch, batch_idx, opt_idx):
# 获取在configure_optimizers()中返回的优化器
(opt_d, opt_g) = self.optimizers()
loss_g = self.acquire_loss_g()
# 注意:不再使用loss.backward(). 另外以GAN为例,因为生成器的动态图还要保持给判别器用于更新,因此retain_graph=True.
self.manual_backward(loss_g, opt_g, retain_graph=True)
# 销毁动态图
self.manual_backward(loss_g, opt_g)
opt_g.step()
# 在更新判别器的时候,保存生成器是0梯度的
opt_g.zero_grad()
# 更新判别器
loss_d = self.acquire_loss_d()
self.manual_backward(loss_d, opt_d)
其他
多GPU训练
trainer = pl.Trainer(gpus=0)
trainer = pl.Trainer(gpus=4)
import os
os.environ['CUDA_VISIBLE_DEVICES'] = '0, 2'
trainer = pl.Trainer(gpus=2)
半精度训练
trainer = pl.Trainer(precision=16)
累积梯度
# 默认情况下不开启累积梯度
trainer = Trainer(accumulate_grad_batches=1)
自动缩放batch_size
# 默认不开启
trainer = Trainer(auto_scale_batch_size=None)
# 自动找满足内存的 batch size
trainer = Trainer(auto_scale_batch_size=None|'power'|'binsearch')
# 加载到模型
trainer.tune(model)
保存所有超参数到模型中
# 例如你传入的超参数字典为params_dict
self.hparams.update(params_dict) # 直接将你的超参数更新到pl模型的超参数字典中
# 这样,在保存的时候就会保存超参数了
self.save_hyperparameters()
当然,对于无们训练的不同的模型,我们
还是需要查看其超参数,可以通过将超参数字典保存到本地txt的方法,来以便后期查看
def save_dict_as_txt(list_dict, save_dir):
with open(save_dir, 'w') as fw:
if isinstance(list_dict, list):
for dict in list_dict:
for key in dict.keys():
fw.writelines(key + ': ' + str(dict.get(key)) + '\n')
else:
for key in list_dict.keys():
fw.writelines(key + ': ' + str(list_dict.get(key)) + '\n')
fw.close()
# 保存超参数字典到txt
save_dict_as_txt(self.hparams, save_dir)
梯度剪裁
# 默认不剪裁
trainer = Trainer(gradient_clip_val=0)
# 梯度范数的上限为0.5
trainer = Trainer(gradient_clip_val=0.5)
设置训练的最小和最大epochs
trainer = Trainer(min_epochs=1, max_epochs=1000)
小数据集
# 参训练集、校验集和测试集分别只加载 10%, 20%, 30%,或者使用int 型表示batch
trainer = Trainer(
limit_train_batches=0.1,
limit_val_batches=0.2,
limit_test_batches=0.3
)
# 默认为2个batch的validation
trainer = Trainer(num_sanity_val_steps=2)
# 关闭开始训练前的validaion,直接开始训练
trainer = Trainer(num_sanity_val_steps=0)
# 把校验集都运行一遍(可能会浪费很多时间)
trainer = Trainer(num_sanity_val_steps=-1)
异常处理
多GPU CUDA设备不同步问题
DataLoader的问题
RuntimeError: DataLoader worker (pid(s) 6700, 10620) exited unexpectedly
trainer = pl.Trainer(distributed_backend='ddp')
# or
trainer = pl.Trainer(distributed_backend='dp')
本文目的在于学术交流,并不代表本公众号赞同其观点或对其内容真实性负责,版权归原作者所有,如有侵权请告知删除。
“他山之石”历史文章
深度学习中的那些Trade-off
PyTorch 手把手搭建神经网络 (MNIST)
autograd源码剖析
怎样才能让你的模型更加高效运行?
来自日本程序员的纯C++深度学习库tiny-dnn
MMTracking: OpenMMLab 一体化视频目标感知平台
深度学习和机器视觉top组都在研究什么
pytorch常见的坑汇总
pytorch 中张量基本操作
pytorch计算模型FLOPs和Params
保姆级教程:个人深度学习工作站配置指南
整理 Deep Learning 调参 tricks
Tensorflow模型保存方式大汇总
利用Tensorflow构建CNN图像多分类模型及图像参数、数据维度变化情况实例分析
更多他山之石专栏文章,
请点击文章底部“阅读原文”查看
分享、点赞、在看,给个三连击呗!