pytorch lightning 是对原生 pytorch 的通用模型开发过程进行封装的一个工具库。本文不会介绍它的高级功能,而是通过几个最简单的例子来帮助读者快速理解、上手基本的使用方式。在掌握基础 API 和使用方式之后,读者可自行到 pytorch lightning 的官方文档,了解进阶 API。本文假设读者对原生 pytorch 训练脚本的搭建方法已经比较熟悉。
pytorch lighning 的安装非常简单,直接使用 pip 安装即可:
pip install pytorch-lightning
pytorch lightning 有两个最核心的 API:LigtningModule 和 Trainer 。
其中 LightningModule 是我们熟悉的 torch.nn.Module 的子类,可以通过
print(isinstance(pl.LightningModule(), torch.nn.Module))
来验证。这意味着该类同样需要实现 forward 方法,并可直接通过实例调用。
Trainer 则是开始执行模型训练、测试过程的类,传入一个 LightningModule 和对应控制参数来实例化即可开始训练。
我们从一个最简单的例子——MNIST 手写数字识别开始:
导入 pytorch_lightning 和 pytorch 常用的库。
import os
import torch
from torch.nn import functional as F
from torch.utils.data import DataLoader
from torchvision.datasets import MNIST
from torchvision import transforms
import pytorch_lightning as pl
我们先实现一个最简的 LightningModule。
__init__
构造函数中,像常见的 torch.nn.Module 一样,我们定义好模型的层。由于是最简实例,这里只有一层线性层,将手写数字图像映射为输出 logits。
forward
由于是继承自 torch.nn.Module,因此实现 forward 方法是必须的。forward 方法要完成模型的前向过程,这里直接调用 __init__ 中定义好的线性层,完成模型前向过程。
train_dataloader
train_dataloader 方法也是最简实现中必须的,它的功能是获取训练集的 DataLoader。这里我们返回 MNIST 数据集的 DataLoader。dataloader 的获取也可以不在类内实现,而是在 fit 时传入,后面会介绍。
training_step
training_step 是是 LigtningModule 的核心方法,它定义了一个训练步中需要做的事情。在深度学习的训练步中,最核心的事情就是模型前向,得到结果,计算损失,反向传播,更新参数,这几步在 pytorch 中都有对应的方法供调用。但是在 pytorch lightning 中,我们只需要进行模型前向,并返回必要的信息即可。在最简实现中,我们只需返回损失。
configure_optimizer
在 training_step 中,我们只需返回损失,这意味着模型的反向传播和参数更新过程由 pytorch lightning 帮我们完成了。虽然这个过程可以有框架自己完成,但是我们还是要指定参数更新所用的优化器,在很多模型中,优化器、学习率等超参数设置对结果影响很大。在最简实现中,我们设置好学习率,并返回一个 Adam 优化器。
class MNISTModel(pl.LightningModule):
def __init__(self):
super(MNISTModel, self).__init__()
self.l1 = torch.nn.Linear(28 * 28, 10)
def forward(self, x):
return torch.relu(self.l1(x.view(x.size(0), -1)))
def train_dataloader(self):
return DataLoader(MNIST(os.getcwd(), train=True, download=True, transform=transforms.ToTensor()), batch_size=32)
def training_step(self, batch, batch_nb):
x, y = batch
loss = F.cross_entropy(self(x), y)
return loss
def configure_optimizers(self):
return torch.optim.Adam(self.parameters(), lr=0.02)
以上我们实现 training_step,train_dataloader, configure_optimizer,已经是最简单的 LightningModule 的实现了。如果连这三个方法都没有实现的话,将会报错:
No `xxx` method defined. Lightning `Trainer` expects as minimum a `training_step()`, `train_dataloader()` and `configure_optimizers()` to be defined
在实现好 LightningModule 之后,就可以开始训练了。
启动训练的最简实现非常简单,只需三行:实例化模型、实例化训练器、开始训练!
model = MNISTModel()
trainer = pl.Trainer(gpus=1, max_epochs=2)
trainer.fit(model)
开始训练后,pytorch lightning 会打印出可用设备、模型参数等丰富的信息。
GPU available: True (cuda), used: True
TPU available: False, using: 0 TPU cores
IPU available: False, using: 0 IPUs
HPU available: False, using: 0 HPUs
LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [0,1,2]
| Name | Type | Params
--------------------------------
0 | l1 | Linear | 7.9 K
--------------------------------
7.9 K Trainable params
0 Non-trainable params
7.9 K Total params
0.031 Total estimated model params size (MB)
Epoch 1: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1875/1875 [00:07<00:00, 261.53it/s, loss=1.3, v_num=10]
以上我们用 30 行左右代码,实现了一个最简的 pytorch lightning 训练过程。这足以体现出 pytorch lightning 的简洁、易用。但是,显然这个最简实现缺少了很多东西,比如验证、测试、日志打印、模型保存等。接下来,我们将实现相对完整但依旧简洁的 pytorch lightning 模型开发过程。
本节将介绍相对更完整的 pytorch lightning 模型开发过程。
在一个相对完整的 LightnintModule 中,用户应当实现以下方法:
1 模型定义 (__init__)
通常定义模型的各个层,在 forward 调用这些层,完成模型前向。与原生 pytorch 类似。
2 前向计算 (forward)
与 torch.nn.Module 的 forward 中做的事情一样,调用 _init_ 中定义的层。完成模型前向。与原生 pytorch 类似。
3 训练/验证/测试步 (training_step/validation_step/test_step)
定义训练/测试/训练每一步中要做的事情,一般是计算损失、指标并返回。
def training_step(self, batch, batch_idx):
# ....
return xxx # 如果是training_step, 则必须包含损失
通常有两个入参 batch 和 batch_idx。是 batch 是 dataloader 给出的输入数据和标签,batch_idx 是当前 batch 的索引。
注意训练步的返回值必须是损失值,或者是包含 ‘loss’ 字段的字典。验证/测试步的返回值不必包括损失,可以是任意结果。
4 训练/验证/测试步结束后 (training_step_end/validation_step_end/test_step_end)
只在使用多个node进行训练且结果涉及如softmax之类需要全部输出联合运算的步骤时使用该函数。
5 训练/验证/测试轮结束后 (training_epoch_end/validation_epoch_end/test_epoch_end)
以 training_epoch_end 为例,其他类似。
如果需要对整一轮的结果进行处理,比如计算一些平均指标等,可以通过 training_epoch_end 来实现。
def training_epoch_end(self, outputs):
# ....
return xxx
其中入参 outputs 是一个列表,包含了每一步 training_step 返回的内容。我们可以在每一轮结束后,对每一步的结果进行处理。
4 选用优化器 (configure_optimizers)
设置模型参数更新所用的优化器。值得一提的是如果需要多个优化器(比如在训练 GAN 时),可以返回优化器列表。也可以在优化器的基础上返回学习率调整器,那就要返回两个列表。
5 数据加载器 (train_dataloader, val_dataloader, test_dataloader)
返回 dataloader。
各个 dataloader 也可以在运行 fit/validation/test 时传入,如:
train_loader = DataLoader(MNIST(os.getcwd(), train=True, download=True, transform=transforms.ToTensor()), batch_size=32)
model = MNISTModel() # 不需要实现get_dataloader方法
trainer.fit(model, train_loader)
LightningModule 中提供了一些常用工具供用户直接使用:
log
Tensorboard 损失/指标日志保存和查看,不要自己定义,直接用即可。用法非常简单,将要记录的值传入:
self.log('train loss', loss)
当然一个功能完整的日志保存接口肯定提供了很多参数来控制,比如是按照 epoch 记录还是按照 step 记录、多卡训练时如何同步、指标是否要展示在进度条上、指标是否要保存在日志文件中等等。pytorch lightning 为这些选项都提供了控制参数,读者可以参考官方文档中 log 相关部分。
python 自带的 print 函数在进行多进程训练时会在每个进程都打印内容,这是原生 pytorch 进行分布式训练时一个很小但是很头疼的问题。LightningModule 提供的 print 只打印一次。
freeze
冻结所有权重以供预测时候使用。仅当已经训练完成且后面只测试时使用。
在实例化 Trainer 时,pytorch lightning 也提供了很多控制参数,这里介绍常用的几个,完整参数及含义请参考官方文档中 Trainer 相关部分。
lightning_logs/version_x/ 。trainer.tune(model) 代码时工作。Callback 是一个自包含的程序,可以与训练流程交织在一起,而不会污染主要的研究逻辑。Callback 并不一定只能在 epoch 结尾调用。pytorch-lightning 提供了数十个hook(接口,调用位置)可供选择,也可以自定义callback,实现任何想实现的模块。
推荐使用方式是,随问题和项目变化的操作,实现到 lightning module里面。而独立的、可复用的内容则可以定义单独的模块,方便多个模型调用。
常见的内建 callback 如:EarlyStopping,根据某个值,在数个epoch没有提升的情况下提前停止训练。。PrintTableMetricsCallback,在每个epoch结束后打印一份结果整理表格等。更多内建 callbacks 可参考相关文档。
模型保存
ModelCheckpoint 是一个自动储存的 callback 模块。默认情况下训练过程中只会自动储存最新的模型与相关参数,而用户可以通过这个 module 自定义。如观测一个 val_loss 的值,并储存 top 3 好的模型,且同时储存最后一个 epoch 的模型,等等。例:
from pytorch_lightning.callbacks import ModelCheckpoint
# saves a file like: my/path/sample-mnist-epoch=02-val_loss=0.32.ckpt
checkpoint_callback = ModelCheckpoint(
monitor='val_loss',
filename='sample-mnist-{epoch:02d}-{val_loss:.2f}',
save_top_k=3,
mode='min',
save_last=True
)
trainer = pl.Trainer(gpus=1, max_epochs=3, callbacks=[checkpoint_callback])
ModelCheckpoint Callback中,如果 save_weights_only=True,那么将会只储存模型的权重,相当于 model.save_weights(filepath),反之会储存整个模型(包括模型结构),相当于model.save(filepath))。
另外,也可以手动存储checkpoint: trainer.save_checkpoint("example.ckpt")
模型加载
加载一个模型,包括它的模型权重和超参数:
model = MyLightingModule.load_from_checkpoint(PATH)
print(model.learning_rate)
# 打印出超参数
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)
# 如果在训练和保存模型时,超参数设置如下,在加载后可以替换这些超参数。
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_dim=10)
完整加载训练状态,包括模型的一切,以及和训练相关的一切参数,如 model, epoch, step, LR schedulers, apex 等。
model = LitModel()
trainer = Trainer(resume_from_checkpoint='some/path/to/my_checkpoint.ckpt')
# 自动恢复 model, epoch, step, LR schedulers, apex, etc...
trainer.fit(model)
基于第三节介绍的更多功能,我们扩展第二节 MNIST 训练程序。代码如下。
import os
import torch
import torch.nn as nn
from torch.nn import functional as F
from torch.utils.data import DataLoader
from torchvision.datasets import MNIST
from torchvision import transforms
import pytorch_lightning as pl
import numpy as np
class MNISTModel(pl.LightningModule):
def __init__(self):
super().__init__()
self.fc = nn.Linear(28 * 28, 10)
def forward(self, x):
return torch.relu(self.fc(x.view(-1, 28 * 28)))
def training_step(self, batch, batch_nb):
# REQUIRED
x, y = batch
y_hat = self(x)
loss = F.cross_entropy(y_hat, y)
self.log('train_loss', loss, on_step=False, on_epoch=True)
return {'loss': loss}
def validation_step(self, batch, batch_nb):
# OPTIONAL
x, y = batch
y_hat = self(x)
loss = F.cross_entropy(y_hat, y)
pred = y_hat.argmax(dim=1, keepdim=True)
correct = pred.eq(y.view_as(pred)).sum().item()
acc = correct / x.shape[0]
self.log('val_acc', acc, on_step=False, on_epoch=True)
self.log('val_loss', loss, on_step=False, on_epoch=True)
return {'val_loss': loss, 'val_acc': acc}
def validation_epoch_end(self, outputs):
# OPTIONAL
avg_loss = torch.stack([x['val_loss'] for x in outputs]).mean()
avg_acc = np.mean([x['val_acc'] for x in outputs])
return {'val_loss': avg_loss, 'val_acc': avg_acc}
def test_step(self, batch, batch_nb):
# OPTIONAL
x, y = batch
y_hat = self(x)
loss = F.cross_entropy(y_hat, y)
return {'test_loss': loss}
def test_epoch_end(self, outputs):
# OPTIONAL
avg_loss = torch.stack([x['test_loss'] for x in outputs]).mean()
return {'test_loss': avg_loss}
def configure_optimizers(self):
# REQUIRED
return torch.optim.Adam(self.parameters(), lr=0.02)
def train_dataloader(self):
# REQUIRED
return DataLoader(MNIST(os.getcwd(), train=True, download=True, transform=transforms.ToTensor()), batch_size=32)
def val_dataloader(self):
# OPTIONAL
return DataLoader(MNIST(os.getcwd(), train=False, download=True, transform=transforms.ToTensor()), batch_size=32)
def test_dataloader(self):
# OPTIONAL
return DataLoader(MNIST(os.getcwd(), train=False, download=True, transform=transforms.ToTensor()), batch_size=32)
model = MNISTModel()
trainer = pl.Trainer(
gpus=1,
max_epochs=10,
callbacks=[
pl.callbacks.EarlyStopping( monitor="val_loss", patience=3),
]
)
trainer.fit(model)
trainer.test()
我的工作要求我为某些测试自动生成电子邮件。我一直在四处寻找,但未能找到可以快速实现的合理解决方案。它需要在outlook而不是其他邮件服务器中,因为我们有一些奇怪的身份验证规则,我们需要保存草稿而不是仅仅发送邮件的选项。显然win32ole可以做到这一点,但我找不到任何相当简单的例子。 最佳答案 假设存储了Outlook凭据并且您设置为自动登录到Outlook,WIN32OLE可以很好地完成此操作:require'win32ole'outlook=WIN32OLE.new('Outlook.Application')message=
我需要做的就是从CSV文件中获取header。file.csv是:"A","B","C""1","2","3"我的代码是:table=CSV.open("file.csv",:headers=>true)putstable.headerstable.eachdo|row|putsrowend这给了我:true"1","2","3"我已经查看RubyCSV文档几个小时了,这让我发疯。我确信必须有一个简单的单行代码可以将header返回给我。有什么想法吗? 最佳答案 看起来CSV.read会让您访问headers方法:headers=C
我正在使用一些旧代码并使用ActiveResource进行非常基本的Twitter集成。我想尽可能少地接触应用程序代码,并在仍然使用ActiveResource的同时引入OAuth。不幸的是,我找不到简单的方法来做到这一点。我确实遇到了oauth-active-resourcegem,但它并没有完全记录下来,而且它似乎是为创建完整的API包装器库而设计的。您可以想象,我想避免为这一遗留更改创建整个TwitterActiveResourceAPI包装器。有什么成功案例吗?在我的例子中,离开ActiveResource可能比让它工作更快。我很高兴被证明是错误的!
跳过联网激活:OOBE界面直接按Ctrl+Shift+F3进入审核模式。这样就可以直接进入系统进行一些硬件测试等,而不用联网激活导致新机无法退货。需要注意的是,在审核模式下进行的一些操作都会保留,并不会在退出后自动还原!安装的软件在正常开机进系统后还会看见!如果电脑确实没连互联网又不想强行跳过OOBE(网上很多教程会叫你直接结束OOBE进程,但这是不推荐的,因为一些厂商自带优化程序和系统初始化设置在后面都会应用,对于笔记本跳过的话你会发现驱动和内置应用都没有装上。其实这部分脚本就在系统盘的Recovery隐藏文件夹下),可以参考以下方式:https://www.landiannews.com/
在Ruby中读取zip文件中的文本文件的最简单方法是什么?类似于PHP的file_get_contents("zip://archive.zip#article.txt") 最佳答案 require'zip/zip'Zip::ZipFile.new("archive.zip").read("article.txt") 关于Ruby-读取zip文件中的文本文件的最简单方法,我们在StackOverflow上找到一个类似的问题: https://stackover
我通常会做类似的事情array.sort{|a,b|a.somethingb.something}我应该如何干燥它? 最佳答案 使用排序方式array.sort_by{|e|e.something或sort_lambda=lambda{|e|e.something}array.sort_by(&sort_lambda)使用后者,您可以在其他sort_by语句中重用sort_lambda 关于ruby-在ruby中对数组进行排序的最简单代码?,我们在StackOverflow上找到一个
使用RubyonRails发送邮件的最简单方法是什么?有没有办法像php的mail()函数一样直接通过ruby发送邮件并跳过所有的rails模型和复杂性?感谢您的帮助。 最佳答案 普通老式ruby中最简单的方法是使用net/smtp。然而,rails有它自己的内置邮件设施,因为发送邮件是很常见的事情。在Rails中做到这一点的最佳方法是使用Mailermodel 关于ruby-on-rails-使用RubyonRails发送邮件的最简单方法,我们在StackOverflow上找到
假设我fork了一堆线程,并希望将每个线程的进度输出打印到STDERR。我怎样才能确保输出保持行原子性,即不会在同一输出行中混淆来自不同线程的输出?#runthisafewtimesandyou'llseetheproblemthreads=[]10.timesdothreads 最佳答案 puts有一个竞争条件,因为它可能将换行符与行分开写。您可能会在多线程应用程序中使用puts看到这种噪音:thread0thread1thread0thread2thread1thread0thread3thread2thread1相反,使用pr
我将围绕服务实现一个简单的RESTAPI包装器,并希望将Ruby与Sinatra一起用于此任务。想法是让用户使用两条腿的OAuth(2.0)验证/签署请求。我应该尝试使用像warden这样的身份验证框架和附加的oauth2.0插件(例如warden-oauth2)还是应该使用像rack-oauth2-server这样的基于机架的解决方案?.基于机架的方法似乎依赖于MongoDB,这没问题,但我宁愿最小化依赖性。干杯,马克 最佳答案 结帐oauth2-provider.另一个受欢迎的选项是doorkeeper(仅限导轨)。
所以我在ruby中知道x.nil?将测试x是否为空。测试x是否等于''、''(两个空格)或''(三个空格)等的最简单方法是什么?基本上,我想知道测试变量是否全是空格的最佳方法是什么? 最佳答案 如果您使用的是Rails,您可以简单地使用:x.blank?当x为nil时调用是安全的,如果x为nil或全为空白则返回true。如果您不使用Rails,您可以从activesupportgem获得它。使用geminstallactivesupport安装。在您的文件中,要么require'active_support/core_ext获取