PyTorch Ignite使用教程

PyTorch Ignite使用教程

介绍

PyTorch是一个用于训练深度神经网络模型的人工智能框架。目前使用PyTorch训练模型时通常包含以下几个步骤:

  1. 定义数据集和模型
  2. 划分数据集,并转换为DataLoader
  3. 确定需要收集的数据,如准确率、损失函数、训练时间等
  4. 确定训练模型的超参数,如学习率、Optimizer等
  5. 多次迭代DataLoader,训练模型,收集参数

为了提高训练模型的效率,PyTorch官方推出了一个名为Ignite的框架,以便更灵活高效地训练模型、收集数据。同时,Ignite还提供了一些模板,用以进一步简化开发的流程。

安装

pip install pytorch-ignite  # pip
conda install pytorch-ignite -c pytorch  # conda

其他安装选项可以在****How to install PyTorch-Ignite中进行查看。****

入门(以MNIST数据集为例)

  1. 定义模型:

    import torch.nn as nn
    
    class Model(nn.Net):
      def __init__(self):
        super(Model, self).__init__()
    
        self.conv1 = nn.Sequence(nn.Conv2d(1, 20, kernel_size=3), nn.ReLU())
        self.conv2 = nn.Sequence(nn.Conv2d(20, 10, kernel_size=3), nn.ReLU())
        
        self.fc1 = nn.Sequence(nn.Linear(7840, 50), nn.ReLU(), nn.Dropout())
        self.fc2 = nn.Sequence(nn.Linear(50, 10), nn.ReLU())
    
      def forward(self, x):
        x = self.conv2(self.conv1(x))
        x = x.view(-1)
        x = self.fc2(self.fc1(x))
        return x
    
  2. 确定学习率,优化器和训练设备等:

    import torch
    
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    model = Model().to(device)
    
    optimizer = torch.optim.Adam(model.parameters(), lr=0.005)
    criterion = nn.CrossEntropyLoss()
    
  3. 定义数据集和DataLoader:

    from torchvision.transforms import Compose, Normalize, ToTensor
    from torchvision.datasets import MNIST
    
    data_transform = Compose([ToTensor(), Normalize((0.1307,), (0.3081,))])
    train_loader = DataLoader(
        MNIST(download=True, root=".", transform=data_transform, train=True), batch_size=128, shuffle=True
    )
    
    val_loader = DataLoader(
        MNIST(download=True, root=".", transform=data_transform, train=False), batch_size=256, shuffle=False
    )
    
  4. 定义训练步骤以及Metrics(涉及到Ignite):

    from ignite.engine import Engine, Events, create_supervised_trainer, create_supervised_evaluator
    from ignite.metrics import Accuracy, Loss
    from ignite.contrib.handlers import TensorboardLogger, global_step_from_engine
    
    trainer = create_supervised_trainer(model, optimizer, criterion, device)
    
    val_metrics = {
        "accuracy": Accuracy(),
        "loss": Loss(criterion)
    }
    
    train_evaluator = create_supervised_evaluator(model, metrics=val_metrics, device=device)
    val_evaluator = create_supervised_evaluator(model, metrics=val_metrics, device=device)
    
  5. 输出日志,方便查看训练过程:

    log_interval = 100
    
    # 每迭代数据集100次之后输出日志
    @trainer.on(Events.ITERATION_COMPLETED(every=log_interval))
    def log_training_loss(engine):
        print(f"Epoch[{engine.state.epoch}], Iter[{engine.state.iteration}] Loss: {engine.state.output:.2f}")
    

    其中engine.state中储存了训练过程中必要的信息,比如输出、各种Metric、epoch、迭代次数等,用户也可以在其中添加自己所需要的内容。

  6. 在每个训练epoch结束以及模型训练结束后进行模型验证:

    @trainer.on(Events.EPOCH_COMPLETED)
    def log_training_results(trainer):
        train_evaluator.run(train_loader)
        metrics = train_evaluator.state.metrics
        print(f"Training Results - Epoch[{trainer.state.epoch}] Avg accuracy: {metrics['accuracy']:.2f} Avg loss: {metrics['loss']:.2f}")
    
    @trainer.on(Events.EPOCH_COMPLETED)
    def log_validation_results(trainer):
        val_evaluator.run(val_loader)
        metrics = val_evaluator.state.metrics
        print(f"Validation Results - Epoch[{trainer.state.epoch}] Avg accuracy: {metrics['accuracy']:.2f} Avg loss: {metrics['loss']:.2f}")
    
  7. 为模型添加Tensorboard logger:

    from ignite.handlers import ModelCheckpoint
    from ignite.contrib.handlers import TensorboardLogger, global_step_from_engine
    
    tb_logger = TensorboardLogger(log_dir="tb-logger")
    
    # 训练时每100次迭代后保存训练结果
    tb_logger.attach_output_handler(
        trainer,
        event_name=Events.ITERATION_COMPLETED(every=100),
        tag="training",
        output_transform=lambda loss: {"batch_loss": loss},
    )
    
    # 验证时每个epoch结束后保存训练结果
    for tag, evaluator in [("training", train_evaluator), ("validation", val_evaluator)]:
        tb_logger.attach_output_handler(
            evaluator,
            event_name=Events.EPOCH_COMPLETED,
            tag=tag,
            metric_names="all",
            global_step_transform=global_step_from_engine(trainer),
        )
    
  8. 为模型添加Checkpoint,保存训练结果较好的模型:

    # 保存Checkpoint时的验证指标
    def score_function(engine):
        return engine.state.metrics["accuracy"]
    
    model_checkpoint = ModelCheckpoint(
        "checkpoint",
        n_saved=2,
        filename_prefix="best",
        score_function=score_function,
        score_name="accuracy",
        global_step_transform=global_step_from_engine(trainer)
    )
    
    val_evaluator.add_event_handler(Events.COMPLETED, model_checkpoint, {"model": model})
    
  9. 开始模型的训练:

    trainer.run(trainer_loader, max_epochs=10)
    

Ignite中的Engine

训练模型的步骤

在深度学习中,模型的训练可以用以下代码表示:

for i in range(max_epochs):
  data_iter = iter(data_loader)
  for data in data_iter:
    output = train_step(data)

其中process_function中包含了较多的步骤,如切换模型的训练/验证模式,计算损失函数等,以有监督学习为例,代码如下:

def train_step(_engine, data):
  model.train()
  optimizer.zero_grad()
  x, y = prepare_data(data)
  y_pred = model(x)
  loss = loss_fn(y_pred, y)
  loss.backward()
  optimizer.step()
  return loss.item()

因此一个trainer就是在训练时不断地迭代数据集,同时更新模型参数。针对上述代码,使用Ignite定义trainer时的代码如下所示:

from ignite.engine import Engine
trainer = Engine(train_step)

Ignite提供了create_supervised_trainercreate_supervised_evaluator两个方法,用于自定义Engine,二者的定义和说明如下:

def create_supervised_trainer(model, optimizer, loss_fn, device=None, non_blocking=False,
                              prepare_batch=_prepare_batch, output_transform=_output_transform,
                              deterministic=False, amp_mode=False, scaler=False, gradient_accumulation_step=1):
  ...

def create_supervised_evaluator(model, metrics=None, device=None, non_blocking=False,
                                prepare_batch=_prepare_batch, output_transform=_output_transform, amp_mode=None):
  ...
  • model、optimizer、loss_fn、device:深度学习中的模型、优化器、损失函数和训练设备。
  • non_blocking:如果为True,并且存在CPU和GPU之间的数据转移,那么转移的过程将使用多线程。
  • prepare_batch:一个函数,参数为batch、device和non_blocking,返回元组(batch_x,batch_y),参见训练过程中的输入输出
  • output_transform:一个函数,接收模型的输出并转换成损失函数需要的形式,参见训练过程中的输入输出
  • deterministic:如果为True,则返回Engine的子类型DeterministicEngine,参见让训练结果可复现
  • amp_mode:加速模型训练的模式,可选ampapex。如果指定为amp,则使用AMP,如果为apex,则使用NVIDIA APEX
  • scaler:GradScaler实例/bool,在PyTorch版本≥1.6.0且amp_mode为amp时有效。如果指定为True,则自动创建一个默认实例。
  • gradient_accumulation_step:每n次迭代数据后进行反向传播,默认为1(这个操作可以减轻显存的使用情况)。
  • metrics:一个由名称-Metric为键值对的字典,参见Metrics

Ignite还提供了其他的方法用于定义trainer和evaluator,参考链接如下:Helper methods to define supervised trainer and evaluator.

训练过程中的输入输出

  1. 在Ignite中,创建Engine需要传递一个process_function,其类型为Callable[[Engine, Any], Any],函数接受的参数为Engine实例和训练中每个batch的数据,同时这也是前面提到的prepare_batch的参数之一。
  2. Ignite对process_function的输出格式没有明确的规定,但是process_function的输出一般是作为存放在engine.state.engine中,并在使用Metric时作为output_transform的参数,因此需要注意二者之间格式的统一
  3. 使用create_supervised_trainer和create_supervised_evaluator创建Engine时,process_function的输出一般为loss.item()和元组(y_pred,y),但是可以通过指定output_transform的方法来修改器输出,参见process_function of trainerprocess_function of evaluator

Ignite中数据的流动过程如下图所示:
Ignite中的数据流动方向

Ignite中的数据流动方向

Engine中的State

Engine对象中的state存放了一些上下文变量,用于在不同的handler之间进行调用,可以通过**engine.state.{name}**的方式访问。这些变量包括:

变量名说明
iteration数据集迭代次数,从1记起
epoch训练epoch,从1记起
seed每个epoch的随机数种子
dataloader传递给engine的数据
max_epochsepoch数,相当于深度学习中常见的num_epochs
batch本次数据集迭代过程中传递给process_function的数据
outputprocess_function单次迭代后的输出,前文有所涉及
metrics深度学习的Metrics,参见后文“Metrics和Checkpoint”
times训练中每个epoch花费的时间和训练全程花费的时间,参见后文”时间性能分析”

Ignite中的重要概念

模型训练的Event和Handler

基本使用

为了扩展Engine的功能,Ignite提供了一套贯穿整个训练过程的事件(Events),用户可以在这些事件上加入自己的Handler,从而实现自定义的功能。Ignite提供了两种添加Handler的方法,一种是使用Engine的add_event_handler方法,另一种是使用Handler的attach方法,这里以add_event_handler方法为主。首先是定义一个最简单的Handler:

engine = Engine(process_function)

engine.add_event_handler(Events.COMPLETED, lambda _: print("Training completed."))

这个事件会在Events.COMPLETED事件被触发后执行,打印一行“Training completed.”的字符串。其中,Events.COMPLETED事件在训练结束后会被触发。

除了使用这种方法,还可以使用装饰器来注册Handler:

@engine.on(Events.COMPLETED)
def print_completed():
  print("Training completed.")

# 或者可以捕获engine作为参数,获取engine中的内容
@engine.on(Events.COMPLETED)
def print_completed(engine):
  print(f"Training completed. {engine.state.max_epochs}")

如果需要向Handler中传递额外的参数,则可以使用engine.add_event_handler方法:

def handler_with_parameters(str_to_print):
  print(str_to_print)

engine.add_event_handler(Events.COMPLETED, handler_with_parameters, "Training completed.")

事件过滤

有时候我们需要某个事件只触发一次,或者间隔一定次数再触发,这时可以通过向事件传递参数的方式实现:

@engine.on(Events.ITERATION_STARTED(every=10))
def some_func():
  # 每间隔10次迭代被调用
  pass

@engine.on(Events.ITERATION_STARTED(once=50))
def some_func():
  # 只在第50次迭代时被调用
  pass

@engine.on(Events.ITERATION_STARTED(before=30, after=60, every=5))
def some_func():
  # 只在第30次迭代之前和第60次迭代之后,每5间隔次迭代被调用
  # before和after可以各自单独使用,也可以像这样配合every使用
  pass

def event_filter(engine, event):
  if event in [10, 30, 60, 120]:
    return True
  return False

@engine.on(Events.ITERATION_STARTED(event_filter=event_filter))
def some_func():
  # 只在第10、30、60、120次迭代开始时被调用
  pass

删除Handler

在某些情况下,我们可能需要删除掉某个Handler,这时可以调用Engine实例的remove_event_handler方法,其定义如下:

def remove_event_handler(handler, event_name):
  ...

其中handler为需要删除的Handler函数,在需要删除某个事件的Handler时,使用event_name指定需要处理的事件。

同时,add_event_handler方法会返回一个RemovableEventHandler实例,也可以用来删除Handler,一下代码摘自Ignite官网:

def some_func(engine):
  print("blablabla")

@trainer.on(Events.EPOCH_COMPLETED)
def evaluate(trainer):
  # 这里使用with上下文调用返回的RemovableEventHandler实例
  with evaluator.add_event_handler(Events.COMPLETED, some_func):
    evaluator.run(train_loader)

trainer.run(train_loader, max_epochs=100)

Ignite定义的训练过程的生命周期如下图所示,提供的所有事件参见文档
Ignite支持的训练过程生命周期,摘自官网。

Ignite支持的训练过程生命周期,摘自官网。

Metrics

在深度学习中,有时需要对模型的性能进行检验,比如准确率、F-score等。Ignite提供了一系列开箱即用的Metrics,下面是一个使用示例:

from ignite.metrics import Accuracy

acc = Accuracy()
acc.attach(evaluator, "acc")

state = evaluator.run(eval_data)
print(f"Accuracy is: {state.metrics['acc']}")

Ignite提供的Metrics需要Engine的输出为元组(y_pred,y),如果不是这种格式,可以使用Metrics的output_transform参数进行调整:

def process_function(engine, batch):
  return { "x": 1, "y": 2, "y_pred": 2.1, "loss": 0.1}

engine = Engine(process_function)

def output_transform(output):
  return output["y_pred"], output["y"]

acc = Accuracy(output_transform=output_transform)
acc.attach(engine, "acc")

state = engine.run(data)
print(f"Accuracy is: {state.metrics['acc']}")

Ignite提供的全部Metrics以及一些高级用法可以在Ignite MetricsIgnite Contrib Metrics中查看。

保存和恢复训练进度

保存训练进度

训练模型时,有时需要保存当前的训练进度,或者提供一个或多个预训练模型。Ignite提供了一个名为Checkpoint的Handler,用于保存模型。

from ignite.handlers import Checkpoint, DiskSaver, global_step_from_engine

# 定义好的模型和optimizer
model = ...
optimizer = ...

trainer = Engine(process_function)
acc = Accuracy()
acc.attach(trainer, "acc")

to_save = { "model": model, "optimizer": optimizer, "trainer": trainer }
checkpoint = Checkopint(
  to_save,
  save_handle=DiskSaver(dirname="checkpoints", require_empty=False),  # 保存在"checkpoints"文件夹中,且文件夹不必为空
  n_saved=3,
  score_name="acc",
  # 有些情况要求Metric越小越好(比如Loss),则可以指定score_sign为-1.0,score_sign只能有1.0和-1.0两个可选值
  # score_function=Checkpoints.get_default_score_fn("acc", score_sign=-1.0),  
  global_step_transform=global_step_from_engine(engine),
)

engine.add_event_handler(Events.EPOCH_COMPLETED(every=1), checkpoint)

上面是定义一个Checkpoint的示例代码。可以看到,Checkpoint同样是一个Handler,下面是其参数的说明:

  • save_handle:可以使用DiskSaver,也可以使用字符串直接指定Checkpoint保存的路径。
  • n_saved:保存的Checkpoint数量,旧的Checkpoint会被丢弃。如果不指定数量,则所有的Checkpoint都会被保存。
  • score_function:Checkpoint排序的依据。在某些情况下,可能需要Metric越小越好,比如损失值,这时可以指定score_sign为-1.0。注意:score_sign只能在1.0和-1.0之间取值。
  • score_name:score_function被保存时的名称。
  • global_step_transform:使用之后,在记录日志时,会使用trainer的epoch,而不是evaluator的epoch,这样能更清楚地记录evaluator的训练进度。

Checkpoint的命名

这是一个Checkpoint的命名结构:{filename_prefix}{name}{suffix}.{ext}

  • filename_prefix:在创建Checkpoint时作为参数传递,默认为空字符串。

  • name:如果to_save只有一个对象需要保存,那么name就是to_save里唯一的键,否则name会被设置为”checkpoint”。

  • ext:Checkpoint的扩展名,一般是“pt”。

  • suffix:命名见下表:

    score_functionscore_nameglobal_step_transformsuffix
    {engine.state.iteration}
    {score}
    {global_step}_{score}
    {global_step}_{score_name}={score}
    {global_step}
    {score_name}={score}

恢复训练进度

device = torch.device("cuda" if torch.cuda_is_available() else "cpu")
to_save = ...  # 参见前面的定义
checkpoint = torch.load("checkpoint.pt", map_location=device)
Checkpoint.load_objects(to_load=to_save, checkpoint=checkpoint)

trainer.run(...)  # 训练过程被恢复

Ignite还提供了两个第三方的函数用来在evaluator运行时保存模型本身,分别是:gen_save_best_models_by_val_scoresave_best_model_by_val_score

让训练结果可复现

为了验证深度学习的算法优劣,或者对算法进行基准测试时,我们需要保证训练过程的可复现性。在PyTorch中,这可以通过1:设置随机数种子;2:保证cudnn的确定性(可选)来实现,这部分可以参考PyTorch官方文档。Ignite同样提供了一套API来简化这一过程,主要有以下几个:

  1. DeterministicEngine
  2. manual_seed
  3. ReproducibleBatchSampler

以下示例代码摘自Ignite官网:

import torch
from torch.utils.data import DataLoader
from ignite.engine import DeterministicEngine, Events
from ignite.utils import manual_seed

def random_train_data_loader(size):
    data = torch.arange(0, size)
    return DataLoader(data, batch_size=4, shuffle=True)

def print_train_data(engine, batch):
    i = engine.state.iteration
    e = engine.state.epoch
    print("train", e, i, batch.tolist())

trainer = DeterministicEngine(print_train_data)

print("Original Run")
manual_seed(56)
trainer.run(random_train_data_loader(40), max_epochs=2, epoch_length=5)

print("Resumed Run")
# Resume from 2nd epoch
trainer.load_state_dict({"epoch": 1, "epoch_length": 5, "max_epochs": 2, "rng_states": None})
manual_seed(56)
trainer.run(random_train_data_loader(40))

以上代码的主要实现步骤:

  1. manual_seed:指定随机数种子,相当于依次调用random.seed、torch.manual_seed、numpy.seed、xla_model.set_rng_state四个方法。
  2. DeterministicEngine:继承自Engine类,让DataLoader的加载过程确定化。
  3. ReproduciblaBatchSampler:在使用DeterministicEngine,且数据的类型为torch.utils.data.DataLoader时会自动使用,一般不需要用户手动设置

DeterministicEngine的实现原理

DeterministicEngine能够保证其训练过程确定性主要有两个方面:一是让cudnn的算法具有确定性,二是让数据集的加载过程具有确定性。第一条可以通过在运行时添加Handler实现:

class DeterministicEngine(Engine):
  def __init__(self, process_function: Callable[[Engine, Any], Any]):
    # 省略其余初始化过程
    self.add_event_handler(Events.STARTED, self._init_run)
    self.add_event_handler(Events.DATALOADER_STOP_ITERATION | Events.TERMINATE_SINGLE_EPOCH, self._setup_seed)

其中_setup_seed方法是在每次迭代数据集之后重置随机数种子,这个不多赘述,重点在于_init_run方法:

def _init_run(self) -> None:
  self.state.seed = int(torch.randint(0, int(1e9), (1,)).item())
  if torch.cuda.is_available():
    if hasattr(torch, "use_deterministic_algorithms"):
      torch.use_deterministic_algorithms(True, warn_only=True)
    else:
      torch.backends.cudnn.deterministic = True
    torch.backends.cudnn.benchmark = False

这个方法的所有和cuda、cudnn相关的内容都可以在前面的PyTorch文档链接中进行查看,主要功能是让cudnn的实现算法具有确定性。

之后我们来看DeterministicEngine是如何让数据集的迭代过程具有确定性。无论是Engine还是DeterministicEngine,他们在最终执行时都会调用实例的run方法,这个方法的内部实现如下所示:

def run(self, data: Optional[Iterable] = None, max_epochs: Optional[int] = None, seed: Optional[int] = None) -> State:
  # 省略参数校验过程
  ...

  if self.interrupt_resume_enabled:
    return self._internal_run()
  else:
    return self._internal_run_legacy()

这两个方法对应不同条件下的执行过程。其中_internal_run方法会调用内部的_internal_run_as_gen方法,这个方法和_internal_run_legacy方法都会去调用_setup_engine方法,代码如下:

def _internal_run(self) -> State:
  if self._internal_run_generator is None:
    self._internal_run_generator = self._internal_run_as_gen()  # 在这里调用_internal_run_as_gen方法
  try:
    return next(self._internal_run_generator)
  except StopIteration as out:
    self._internal_run_generator = None
    return out.value

def _internal_run_as_gen(self) -> Generator:
  # 省略无关代码和缩进,只作为演示
  if self._dataloader_iter is None:
    self._setup_engine()

def _internal_run_legacy(self) -> State:
  # 省略无关代码和缩进,只作为演示
  if self._dataloader_iter is None:
    self._setup_engine()

而DeterministicEngine重写了内部的_setup_engine方法,参见下面代码:

def _setup_engine(self) -> None:
  # 省略参数校验过程
  if isinstance(self.state.dataloader, DataLoader):
    # 省略部分无关代码及缩进
    batch_sampler = self.state.dataloader.batch_sampler
    if not (batch_sampler is None or isinstance(batch_sampler, ReproducibleBatchSampler)):
      # 如果batch_sampler存在且不是ReproducibleBatchSampler
      self.state.dataloader = update_dataloader(
        self.state.dataloader, ReproducibleBatchSampler(batch_sampler)
      )

这里的update_dataloader函数只有一个功能,就是替换DataLoader的BatchSampler。上述代码的功能是检验内部DataLoader的BatchSampler是不是ReproducibleBatchSampler,如果不是,那么替换为ReproducibleBatchSampler,其代码实现如下:

class ReproducibleBatchSampler(BatchSampler):
  def __init__(self, batch_sampler: BatchSampler, start_iteration: Optional[int] = None):
    # 省略校验过程及无关代码
    self.batch_indicies: List = []  # 这里存放原有BatchSampler的索引
  
  def __iter__(self) -> Generator:
    self.setup_batch_indicies()
    for batch in self.batch_indicies:
      yield batch

  def setup_batch_indicies(self) -> None:
    self.batch_indices = []
    for batch in self.batch_sampler:
      self.batch_indices.append(batch)
    # 省略后续代码

可以看到,ReproducibleBatchSampler在迭代之前首先在内部将原有的BatchSampler进行迭代,将迭代到的数据存储在batch_indicies中,然后迭代batch_indicies列表,将数据返回。这样就能够保证训练时每次获取到的数据都是确定的。

Ignite还提供了两个示例代码,分别在MNIST数据集和CIFAR10数据集上进行训练。截至目前(2023-09-23),Ignite官网给出的CIFAR10数据集代码的链接有误,虽然已在GitHub仓库上修改,但还未更新到官网中,因此这里手动给出链接:

杂项

添加日志

日志打印/保存是训练模型时的重要部分,可以帮助分析训练过程,同时也可以保存一些关键信息,比如模型的权重、梯度等。日志一般分为两个部分:运行时日志,比如使用logging和loguru库,以及可视化数据日志,比如使用TensorBoardX和ClearML库等两个大类。

运行日志

在Ignite中,默认使用Python自带的logging库,如果不加任何设置,则Logger会在Engine初始化时被自动创建,其代码为:

self.logger = logging.getLogger(__name__ + "." + self.__class__.__name__)

Ignite同时提供了setup_logger函数,用来对Logger做进一步的配置:

def setup_logger(
  name: Optional[str] = "ignite",
  level: int = logging.INFO,
  stream: Optional[TextIO] = None,
  format: str = "%(asctime)s %(name)s %(levelname)s: %(message)s",
  filepath: Optional[str] = None,
  distributed_rank: Optional[int] = None,
  reset: bool = False
) -> logging.Logger:
  ...

engine = Engine(process_function)
logger = setup_logger()
engine.logger = logger

只需要访问engine.logger成员即可使用Logger来打印日志:

@engine.on(Events.STARTED)
def log_something(engine):
  engine.logger.INFO("训练开始,打印日志")

Ignite并没有对Logger的类型做出规定,这意味着用户可以使用第三方的日志库,比如loguru等,使用时只需要向上面一样指定engine.logger成员即可。

可视化数据日志

在训练模型时,有时需要将训练数据记录下来,并进行可视化,这就用到了Tensorboard。Ignite同样提供了快速接入这些库的API:

from ignite.contrib.engines.common import setup_tb_logging

model = ...
optimizer = ...

trainer = Engine(process_function_trianing)
evaluator = Engine(process_function_evaluating)

tb_logger = setup_tb_logging(
              output_path="./tb_logging",
              trainer=trainer, evaluators=evaluator,
              optimizers=optimizer, log_every_iters=1
            )

setup_tb_logging的参数如下:

  • output_path:输出日志的路径
  • trainer:训练的Engine
  • optimizers:可选,可以是一个或多个Optimizer
  • evaluators:可选,可以使一个或多个验证的Engine
  • log_evety_iters:每多少次迭代数据后记录一次数据,默认为1

setup_tb_logging返回一个TensorboardLogger,可以通过这个logger额外添加一些需要追踪的数据:

from ignite.contrib.handlers.tensorboard_logger import WeightsScalarHandler, WeightsHistHandler, GradsScalarHandler, GradsHistHandler

# 记录Trainer的损失函数
tb_logger.attach_output_handler(
            trainer, event_name=Events.ITERATION_COMPLETED, tag="training", output_transform=lambda loss: {"loss": loss})

# 记录Evaluator的Metrics
tb_logger.attach_output_handler(
            evaluator, event_name=Events.EPOCH_COMPLETED, tag="training", metric_names=["accuracy", "fscore"]
            global_step_transform=global_step_from_engine(trainer))

# 记录Optimizer的参数
tb_logger.attach_output_handler(
            trainer, event_name=Events.ITERATION_STARTED, optimizer=optimizer, param_name="lr")  # 最后一个参数选填

# 记录模型的权重
tb_logger.attach(trainer, event_name=Events.ITERATION_COMPLETED, log_handler=WeightsScalarHandler(model))
# 以直方图的形式记录模型权重
tb_logger.attach(trainer, event_name=Events.ITERATION_COMPLETED, log_handler=WeightsHistHandler(model))
# 记录模型的梯度
tb_logger.attach(trainer, event_name=Events.ITERATION_COMPLETED, log_handler=GradsScalarHandler(model))
# 以直方图的形式记录模型梯度
tb_logger.attach(trainer, event_name=Events.ITERATION_COMPLETED, log_handler=GradsHistHandler(model))

最后,当模型训练完毕时,我们需要关闭logger:

tb_logger.close()

对于其他的日志库,比如ClearML等,Ignite同样提供了对应的使用方法,使用时需要参考setup_xxx_logging页面对应的logger页面

提前结束训练

有时候我们并不确定什么时候训练结束,或者需要在训练经过一段时间没有进展之后停止训练,这时就可以考虑使用EarlyStopping Handler或add_early_stopping_by_val_score函数。

首先是EarlyStopping的使用方法:

from ignite.handlers import EarlyStopping

def score_function(engine):
  return engine.state.metrics["accuracy"]

handler = EarlyStopping(patience=2, score_function=score_function, trainer=trainer)
# 注意,这个Handler需要附加在Evaluator上,一定要注意这一点
evaluator.add_event_handler(Events.COMPLETED, handler)

EarlyStopping需要被加载Evaluator上,当Evaluator的score_function,也就是准确率,连续2次没有进步时,停止Trainer的训练过程。

注意:当score_function获取的数据是比如损失值这样越小越好的指标时,可以考虑把score_function的返回值取反。

EarlyStopping的参数如下:

  • patience:当连续几次训练没有进步时停止训练过程
  • score_function:评价训练是否有进步的标志
  • trainer:需要停止训练的Engine
  • min_delta:浮点数,小于min_delta的进步将被忽略不计,默认为0.0
  • cumulative_delta:默认False。为False时,以相对于上一次评价时是否有进步为标准;为True时,min_delta取上一次patience被重置时的进步值。

有时候我们并不需要EarlyStopping这样精细的控制程度,同时为了简化编写代码的复杂度,我们可以使用add_early_stopping_by_val_score函数:

def add_early_stopping_by_val_score(
  patience: int, evaluetor: Engine, trainer: Engine, metric_name: str, score_sign: float = 1.0):
  ...

其中metric_name表示用作评价训练是否进步的metric名称;对于如损失值这样越小越好的指标,score_sign可以取-1.0,可选值为1.0或-1.0。

二者的不同主要在以下几个方面:

项目EarlyStoppingadd_early_stopping_by_val_score
设置score_sign
设置min_delta
设置cumulative_delta
编写复杂度

时间性能分析

使用Ignite进行时间分析主要有2个方法:BasicTimeProfiler和HandlersTimeProfiler。除此之外,用户还可以通过使用Timer来进行更底层的访问,但是这个不常用,因此本篇文章省略Timer相关的内容。

首先是BasicTimeProfiler的使用方法:

trainer = Engine(...)

basic_profiler = BasicTimeProfiler()
basic_profiler.attach(trainer)

trainer.run(train_loader, max_epochs=10)

basic_profiler.print_results(basic_profiler.get_results())

使用BasicTimeProfiler时无法对每个Handler占用的时间进行分析,可以考虑使用HandlersTimeProfiler:

trainer = Engine(...)

handlers_profiler = HandlersTimeProfiler()
handlers_profiler.attach(trainer)

trainer.run(train_loader, max_epochs=10)

handler_profilers.print_results(handler_profilers.get_results())

这二者都可以把获取到的运行时间相关信息保存在csv文件中:

# 保存结果
basic_profiler.write_results("./basic_profile.csv")
handlers_profiler.write_results("./handlers_profile.csv")

# 加载结果
basic_profile = pd.read_csv("./basic_profile.csv")
basic_profile.head()

handlers_profile = pd.read_csv("./handlers_profile.csv")
handlers_profile.head()

进度条

在训练模型时,我们可以使用进度条来直观地展现训练过程(部分代码摘自官网):

trainer = Engine(...)

pbar = ProgressBar()
pbar.attach(trainer)

# Epoch [2/50]: [64/128]  50%|█████      [06:17<12:34]

同时也可以在进度条上监控一些变量:

trainer = Engine(...)

pbar = ProcessBar()
pbar.attach(trainer, output_transform=lambda x: { "loss": x })

# Epoch [2/50]: [64/128]  50%|█████      , loss=0.123 [06:17<12:34]

# 或者使用以下代码监控更多变量
pbar.attach(trainer, metric_names=["acc", "nll"], state_attributes=["alpla", "beta"])

更多相关参数可以参考API文档

附录

本文之前的部分列出了Ignite的大多数使用方法,但是还有一些遗漏,读者可以参考以下链接:

  • 使用FastaiRLFinder找出最合适的学习率:教程API文档
  • 在显存有限的情况下逐步增加Batch Size:教程
  • 在训练时切换数据集:教程
  • Ignite教程:包含了多种场景下的Ignite使用教程,包括强化学习、分布式训练等。
  • keep_random_state:在某个函数运行时保持随机状态的装饰器,不常用。
  • TerminateOnNan:在Engine的输出中有NaN时停止训练。
  • TimeLimit:限制训练时间,超过时间限制后自动停止训练
  • Ignite Utils:训练中常用的工具函数,包括给Checkpoint进行哈希、Tensor的编码转换和设备迁移等。
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

OriginCoding

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值