PyTorch Sangokushi (Ignite / Catalyst / Lightning)

0. Introduction

Je pense que tous les frameworks d'apprentissage profond sont des domaines où le développement est très rapide et passionnant. Bien qu'il y ait TensorFlow et jax, il y a aussi PFN News l'autre jour, et PyTorch est susceptible de devenir plus solide. Peut-être que d'autres utilisateurs de PyTorch continueront (je ne mentionnerai pas le ** formateur officiel ** qui était également dans Chainer, car cela mettrait en péril l'existence de cet article dans PyTorch).

Cependant, alors que PyTorch a un haut degré de liberté, le code autour de l'apprentissage (comme autour de la boucle de chaque époque) est laissé à l'individu, et il a tendance à être un code très unique.

L'écriture de ces codes vous-même est beaucoup d'apprentissage et je pense que vous devriez ** être sûr d'y aller ** lorsque vous démarrez PyTorch. Cependant, si l'individualité est trop forte, il peut être difficile de la partager avec d'autres personnes ou de la réutiliser entre les compétitions (ex. Oreore Trainer souvent vu dans Winner Solutions).

Dans le cas de PyTorch, il n'y a pas de framework en soi pour simplifier le code autour de l'apprentissage (il y avait Trainer avant, mais il a été aboli), mais [Ecosystem \ | PyTorch](https: // pytorch) Les frameworks suivants pour PyTorch sont introduits dans .org / écosystème /).

Il y a beaucoup de. La vérité est, essayez-les tous et trouvez celui qui vous convient. Cependant, ils ne sont pas faciles, donc dans cet article, je voudrais présenter chaque framework et faire une brève comparaison afin que vous puissiez l'utiliser comme guide.

Notez que ** fastai ** a un haut degré d'abstraction (le code a tendance à être court), mais j'ai senti que le coût d'apprentissage pour ajouter des opérations détaillées par moi-même était élevé, donc ** Comparaison dans cet article Ensuite, je l'ai omis d'avance **.

Par conséquent, dans cet article, nous nous concentrerons sur ** Catalyst **, ** Ignite ** et ** Lightning **, et ferons des comparaisons en supposant que vous participerez au ** concours Kaggle **.

D'ailleurs, j'ai confirmé à l'avance que ces trois cadres fonctionnent dans une certaine mesure. Si vous avez lu cet article et que vous souhaitez aller un peu plus loin, veuillez vous y référer.

--Code dans cet article - https://github.com/yukkyo/Compare-PyTorch-Catalyst-Ignite-Lightning

Comme je l'ai mentionné plus tôt, ** tous ont le potentiel de participer au concours. ** **

1. Cible de cet article (ou non)

--Je suis intéressé par le concours Kaggle --Je suis intéressé par le concours d'image --Je suis particulièrement intéressé par la classification, la segmentation et la détection. --PyTorch je l'ai touché ――Si vous ne l'avez pas touché, ce n'est pas le cas lorsque vous lisez cet article. ―― Commençons par les pages et livres suivants - Welcome to PyTorch Tutorials — PyTorch Tutorials 1.3.1 documentation - Apprenez en faisant! Développement Deep Learning par PyTorch|Yutaro Ogawa|ingénierie|Boutique Kindle| Amazon

2. Comparaison de chaque framework (Catalyst, Ignite, Lightning)

J'ai utilisé la dernière version sur pip en date du 13 décembre 2019. La version Python est la 3.7.5. ~~ On suppose aussi que NVIDIA / apex est déjà installé. ~~ Vous n'avez pas besoin d'apex pour exécuter ce code.

torch==1.3.1
torchvision==0.4.2
catalyst==19.12
pytorch-ignite==0.2.1
pytorch-lightning==0.5.3.2

2.1 Transition du numéro d'étoile (à compter du 10 décembre 2019)

Alors que Catalyst et Ignite se développent régulièrement, Lightning a énormément augmenté depuis avril de cette année. D'autre part, Lightning a moins d'un an, il y a donc de nombreuses fonctionnalités en cours de développement, et il convient de noter qu'il est toujours instable (pas de rétrocompatibilité lors de la mise à niveau).

Il est également possible que Catalyst dépasse Ignite, car Catalyst est si courant sur les ordinateurs portables Kaggle modernes.

2.2 Comment écrire

Voyons ce qui se passe lorsque nous appliquons chaque framework au code d'apprentissage PyTorch.

2.2.1 Partie commune

Cette fois, j'essaierai d'apprendre avec Resnet18 pour l'ensemble de données cifer10. Comme indiqué dans le code ci-dessous, les définitions du modèle et du chargeur de données sont transformées en fonctions à l'avance.

Code de pièce commun (je l'ai plié car il est long)

share_funcs.py


import torch
import torch.nn as nn
from torch import optim
from torch.utils.data import DataLoader
from torchvision import datasets, models, transforms

def get_criterion():
    """Une fonction qui renvoie bien Loss"""
    return nn.CrossEntropyLoss()

def get_loaders(batch_size: int = 16, num_workers: int = 4):
    """Une fonction qui peut bien renvoyer chaque Dataloader"""
    transform = transforms.Compose([transforms.ToTensor()])

    # Dataset
    args_dataset = dict(root='./data', download=True, transform=transform)
    trainset = datasets.CIFAR10(train=True, **args_dataset)
    testset = datasets.CIFAR10(train=False, **args_dataset)

    # Data Loader
    args_loader = dict(batch_size=batch_size, num_workers=num_workers)

    train_loader = DataLoader(trainset, shuffle=True, **args_loader)
    val_loader = DataLoader(testset, shuffle=False, **args_loader)
    return train_loader, val_loader

def get_model(num_class: int = 10):
    """Une fonction qui retourne bien le modèle"""
    model = models.resnet18(pretrained=True)
    num_features = model.fc.in_features
    model.fc = nn.Linear(num_features, num_class)
    return model

def get_optimizer(model: torch.nn.Module, init_lr: float = 1e-3, epoch: int = 10):
    optimizer = optim.SGD(model.parameters(), lr=init_lr, momentum=0.9)
    lr_scheduler = optim.lr_scheduler.MultiStepLR(
        optimizer,
        milestones=[int(epoch*0.8), int(epoch*0.9)],
        gamma=0.1
    )
    return optimizer, lr_scheduler

2.2.1 Code de base (code d'apprentissage clair)

Si vous l'écrivez honnêtement sans trop réfléchir, ce sera comme suit. .to (device), loss.backward () et ʻoptimizer.step () doivent être écrits, ils ont donc tendance à être longs. De plus, with torch.no_grad () peut être rendu compatible avec Train et Eval en utilisant torch.set_grad_enabled (bool) , mais il existe de nombreux processus différents entre Train et Eval (ex. ʻOptimizer.step () , métriques, etc.), si vous créez une fonction qui prend en charge les deux, les perspectives ont tendance à être pires.

Code de base (plié car il est long)
def train(model, data_loader, criterion, optimizer, device, grad_acc=1):
    model.train()

    # zero the parameter gradients
    optimizer.zero_grad()

    total_loss = 0.
    for i, (inputs, labels) in tqdm(enumerate(data_loader), total=len(data_loader)):
        inputs = inputs.to(device)
        labels = labels.to(device)

        outputs = model(inputs)

        loss = criterion(outputs, labels)
        loss.backward()

        # Gradient accumulation
        if (i % grad_acc) == 0:
            optimizer.step()
            optimizer.zero_grad()

        total_loss += loss.item()

    total_loss /= len(data_loader)
    metrics = {'train_loss': total_loss}
    return metrics


def eval(model, data_loader, criterion, device):
    model.eval()
    num_correct = 0.

    with torch.no_grad():
        total_loss = 0.
        for inputs, labels in tqdm(data_loader, total=len(data_loader)):
            inputs = inputs.to(device)
            labels = labels.to(device)

            outputs = model(inputs)
            _, preds = torch.max(outputs, 1)

            loss = criterion(outputs, labels)

            total_loss += loss.item()
            num_correct += torch.sum(preds == labels.data)

        total_loss /= len(data_loader)
        num_correct /= len(data_loader.dataset)
        metrics = {'valid_loss': total_loss, 'val_acc': num_correct}
    return metrics


def main():
    epochs = 10

    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    model = get_model()
    train_loader, val_loader = get_loaders()
    optimizer, lr_scheduler = get_optimizer(model=model)
    criterion = get_criterion()

    #Modèle multi-gpu ou supporte FP16
    model = model.to(device)

    print('Train start !')
    for epoch in range(epochs):
        print(f'epoch {epoch} start !')
        metrics_train = train(model, train_loader, criterion, optimizer, device)
        metrics_eval = eval(model, val_loader, criterion, device)

        lr_scheduler.step()

        #Traitement autour de l'enregistreur
        #Un processus d'impression désordonné
        print(f'epoch: {epoch} ', metrics_train, metrics_eval)

        #Écrivez ici un processus qui serait encore plus déroutant si vous utilisiez tqdm
        #Processus d'enregistrement du modèle
        # Multi-Écrivez plus attentivement pour les GPU

2.2.2 Catalyst

Pour Catalyst, transmettez simplement ce dont vous avez besoin à SupervisedRunner dans la bibliothèque et vous avez terminé. C'est vraiment intelligent! De plus, les principales métriques telles que la précision et les dés sont dans Catalyst, vous les écrivez donc rarement vous-même (l'introduction de vos propres métriques semblait relativement facile). Il semble qu'il soit généralement difficile de conserver la valeur par défaut, mais si vous souhaitez ajouter vous-même un traitement détaillé, il semble que vous deviez enquêter un peu.

import catalyst
from catalyst.dl import SupervisedRunner
from catalyst.dl.callbacks import AccuracyCallback
from share_funcs import get_model, get_loaders, get_criterion, get_optimizer

def main():
    epochs = 5
    num_class = 10
    output_path = './output/catalyst'

    model = get_model()
    train_loader, val_loader = get_loaders()
    loaders = {"train": train_loader, "valid": val_loader}

    optimizer, lr_scheduler = get_optimizer(model=model)
    criterion = get_criterion()

    runner = SupervisedRunner(device=catalyst.utils.get_device())
    runner.train(
        model=model,
        criterion=criterion,
        optimizer=optimizer,
        scheduler=lr_scheduler,
        loaders=loaders,
        logdir=output_path,
        callbacks=[AccuracyCallback(num_classes=num_class, accuracy_args=[1])],
        num_epochs=epochs,
        main_metric="accuracy01",
        minimize_metric=False,
        fp16=None,
        verbose=True
    )

2.2.3 Ignite

Ignite a une couleur de cheveux légèrement différente de celle de Catalyst et Lightning, qui sera décrite plus tard. Comme indiqué ci-dessous, il s'agit d'une image d'insertion du traitement que vous souhaitez prendre en sandwich avec @ trainer.on (Events.EPOCH_COMPLETED) etc. pour chaque minutage. De plus, Ignite est également officiellement préparé avec Accuracy etc., donc s'il s'agit d'un indice d'évaluation majeur, il semble que vous n'ayez pas à le définir vous-même.

D'un autre côté, il semble que vous deviez vous y habituer, et il y a un degré élevé de liberté dans la façon de prendre en sandwich l'événement (vous pouvez également l'ajouter comme trainder.append), donc si vous faites une erreur, les perspectives générales peuvent se détériorer.

import torch
from ignite.engine import Events, create_supervised_trainer, create_supervised_evaluator
from ignite.metrics import Accuracy, Loss, RunningAverage
from ignite.contrib.handlers import ProgressBar
from share_funcs import get_model, get_loaders, get_criterion, get_optimizer

def run(epochs, model, criterion, optimizer, scheduler,
        train_loader, val_loader, device):
    trainer = create_supervised_trainer(model, optimizer, criterion, device=device)
    evaluator = create_supervised_evaluator(
        model,
        metrics={'accuracy': Accuracy(), 'nll': Loss(criterion)},
        device=device
    )

    RunningAverage(output_transform=lambda x: x).attach(trainer, 'loss')

    pbar = ProgressBar(persist=True)
    pbar.attach(trainer, metric_names='all')

    @trainer.on(Events.EPOCH_COMPLETED)
    def log_training_results(engine):
        scheduler.step()
        evaluator.run(train_loader)
        metrics = evaluator.state.metrics
        avg_accuracy = metrics['accuracy']
        avg_nll = metrics['nll']
        pbar.log_message(
            "Training Results - Epoch: {}  Avg accuracy: {:.2f} Avg loss: {:.2f}"
            .format(engine.state.epoch, avg_accuracy, avg_nll)
        )

    @trainer.on(Events.EPOCH_COMPLETED)
    def log_validation_results(engine):
        evaluator.run(val_loader)
        metrics = evaluator.state.metrics
        avg_accuracy = metrics['accuracy']
        avg_nll = metrics['nll']
        pbar.log_message(
            "Validation Results - Epoch: {}  Avg accuracy: {:.2f} Avg loss: {:.2f}"
            .format(engine.state.epoch, avg_accuracy, avg_nll))

        pbar.n = pbar.last_print_n = 0

    trainer.run(train_loader, max_epochs=epochs)

def main():
    epochs = 10
    train_loader, val_loader = get_loaders()
    model = get_model()
    device = 'cuda' if torch.cuda.is_available() else 'cpu'
    optimizer, scheduler = get_optimizer(model)
    criterion = get_criterion()

    run(
        epochs=epochs,
        model=model,
        criterion=criterion,
        optimizer=optimizer,
        scheduler=scheduler,
        train_loader=train_loader,
        val_loader=val_loader,
        device=device
    )

2.2.4 Lightning

Pour Lightning, vous devez définir une classe qui hérite de LightningModule (comme une classe Trainer).

Le nom de chaque étape (ex. Training_step) est fixe, et vous remplissez chaque étape vous-même. En outre, la formation elle-même est effectuée par la classe pytorch_lightning.Trainer, et des paramètres tels que GPU, MixedPrecision et l'accumulation de dégradés sont définis dans cette classe. De plus, les métriques ne sont pas disponibles dans Lightning, vous devrez donc les écrire vous-même.

import torch
import pytorch_lightning as pl
from pytorch_lightning import Trainer
from share_funcs import get_model, get_loaders, get_criterion, get_optimizer

class MyLightninModule(pl.LightningModule):
    def __init__(self, num_class):
        super(MyLightninModule, self).__init__()
        self.model = get_model(num_class=num_class)
        self.criterion = get_criterion()

    def forward(self, x):
        return self.model(x)

    def training_step(self, batch, batch_idx):
        # REQUIRED
        x, y = batch
        y_hat = self.forward(x)
        loss = self.criterion(y_hat, y)
        logs = {'train_loss': loss}
        return {'loss': loss, 'log': logs, 'progress_bar': logs}

    def validation_step(self, batch, batch_idx):
        # OPTIONAL
        x, y = batch
        y_hat = self.forward(x)
        preds = torch.argmax(y_hat, dim=1)
        return {'val_loss': self.criterion(y_hat, y), 'correct': (preds == y).float()}

    def validation_end(self, outputs):
        # OPTIONAL
        avg_loss = torch.stack([x['val_loss'] for x in outputs]).mean()
        acc = torch.cat([x['correct'] for x in outputs]).mean()
        logs = {'val_loss': avg_loss, 'val_acc': acc}
        return {'avg_val_loss': avg_loss, 'log': logs}

    def configure_optimizers(self):
        # REQUIRED
        optimizer, scheduler = get_optimizer(model=self.model)
        return [optimizer], [scheduler]

    @pl.data_loader
    def train_dataloader(self):
        # REQUIRED
        return get_loaders()[0]

    @pl.data_loader
    def val_dataloader(self):
        # OPTIONAL
        return get_loaders()[1]


def main():
    epochs = 5
    num_class = 10
    output_path = './output/lightning'

    model = MyLightninModule(num_class=num_class)

    # most basic trainer, uses good defaults
    trainer = Trainer(
        max_nb_epochs=epochs,
        default_save_path=output_path,
        gpus=[0],
        # use_amp=False,
    )
    trainer.fit(model)

2.3 Écran de la console et sortie lors de l'exécution dans chaque framework

2.3.1 Par défaut

Écran de la console

$ python train_default.py
Files already downloaded and verified
Files already downloaded and verified
Train start !
epoch 0 start !
100%|_____| 196/196 [00:05<00:00, 33.44it/s]
100%|_____| 40/40 [00:00<00:00, 50.43it/s]
epoch: 0  {'train_loss': 1.3714478426441854} {'valid_loss': 0.992230711877346, 'val_acc': tensor(0, device='cuda:0')}

Production

Aucun

2.3.1 Catalyst

Écran de la console

$ python train_catalyst.py
1/5 * Epoch (train): 100% 196/196 [00:06<00:00, 30.09it/s, accuracy01=61.250, loss=1.058]
1/5 * Epoch (valid): 100% 40/40 [00:00<00:00, 49.75it/s, accuracy01=56.250, loss=1.053]
[2019-12-14 08:47:33,819]
1/5 * Epoch 1 (train): _base/lr=0.0010 | _base/momentum=0.9000 | _timers/_fps=58330.0450 | _timers/batch_time=0.0071 | _timers/data_time=0.0045 | _timers/model_time=0.0026 | accuracy01=52.0863 | loss=1.3634
1/5 * Epoch 1 (valid): _base/lr=0.0010 | _base/momentum=0.9000 | _timers/_fps=77983.3850 | _timers/batch_time=0.0146 | _timers/data_time=0.0126 | _timers/model_time=0.0019 | accuracy01=65.6250 | loss=0.9848
2/5 * Epoch (train): 100% 196/196 [00:06<00:00, 30.28it/s, accuracy01=63.750, loss=0.951]

Production

--Tensorboard etc. sont générés par défaut

catalyst
├── checkpoints
│   └── train.1.exception_KeyboardInterrupt.pth
├── code
│   ├── share_funcs.py
│   ├── train_catalyst.py
│   ├── train_default.py
│   └── train_lightning.py
├── log.txt
└── train_log
    └── events.out.tfevents.1576306176.FujimotoMac.local.41575.0

2.3.2 Ignite

Écran de la console

L'écran est un peu plus propre que Catalyst.

$ python train_ignite.py
Epoch [1/10]: [196/196] 100%|________________, loss=1.14 [00:05<00:00]
Training Results - Epoch: 1  Avg accuracy: 0.69 Avg loss: 0.88
Validation Results - Epoch: 1  Avg accuracy: 0.65 Avg loss: 0.98
Epoch [2/10]: [196/196] 100%|________________, loss=0.813 [00:05<00:00]
Training Results - Epoch: 2  Avg accuracy: 0.78 Avg loss: 0.65
Validation Results - Epoch: 2  Avg accuracy: 0.70 Avg loss: 0.83

Production

--Aucun -Il semble que vous deviez écrire la partie pour vous sauver ou utiliser la classe dans Ignite

2.3.3 Lightning

Écran de la console

Par défaut, Lightning semble afficher tout ce qui se trouve dans la barre à l'intérieur de tqdm.

$ python train_lightning.py
Epoch 1: 100%|_____________| 236/236 [00:07<00:00, 30.75batch/s, batch_nb=195, gpu=0, loss=1.101, train_loss=1.06, v_nb=5]
Epoch 4:  41%|_____________| 96/236 [00:03<00:04, 32.28batch/s, batch_nb=95, gpu=0, loss=0.535, train_loss=0.524, v_nb=5]

Production

--Lightning crée et enregistre les répertoires suivants comme version_x s'il existe des répertoires en double. (Bien que cela puisse être ennuyeux et que vous puissiez définir votre propre point de contrôle) --Pour Lightning, les paramètres transmis à LightningModule sont automatiquement enregistrés dans meta_tags.csv. --Log for Tensorboard est également créé par défaut

lightning
└── lightning_logs
    ├── version_0
    │   └── checkpoints
    │       └── _ckpt_epoch_4.ckpt
    │   ├── media
    │   ├── meta.experiment
    │   ├── meta_tags.csv
    │   ├── metrics.csv
    │   └── tf
    │       └── events.out.tfevents.1576305970
    ├── version_1
    │   └── checkpoints
    │       └── _ckpt_epoch_3.ckpt
    │   ├── ...

2.4 Autres lieux remarquables

Les deux prennent en charge l'arrêt anticipé, etc.

2.4.1 Catalyst

2.4.2 Ignite

--Tensorboard et Logger sont également dans Ignite et peuvent être appelés et utilisés ――Le degré de liberté semble être le plus élevé ――Il semble que vous deviez vous habituer à la façon de prendre en sandwich l'événement --Il est situé sous le référentiel officiel

2.4.3 Lightning

--Multi GPU et FP16 sont également pris en charge

2.5 Si vous recommandez

Les deux ont du potentiel et ne sont pas obligatoires. Voici mes impressions personnelles.

――C'est la première fois que je participe à un concours d'image et je ne sais pas trop quoi faire - → PyTorch Catalyst ――Je suis habitué au concours d'image et je veux participer au concours avec une intention meurtrière (fort sentiment d'obtenir une médaille d'or)

3. Pour se déplacer librement entre Catalyst, Ignite et Lightning

Vous pouvez affiner les frameworks utilisés ici, mais ** Cela ne devrait pas être un problème si vous écrivez chaque framework afin que vous puissiez facilement basculer entre eux. ** Voici donc quelques éléments à prendre en compte lors de l'écriture de code PyTorch.

--Supprimer le contenu de la boucle autant que possible --Il semble bon de savoir que le traitement de chaque étape (traitement pour chaque lot dans ex. Train) peut être extrait. ――Au moins une fois que vous commencez à écrire la triple boucle, il semble bon de savoir si vous pouvez extraire le contenu de la boucle.

4. Enfin

Dans cet article, nous avons comparé PyTorch Catalyst, Ignite et Lightning. Dans chaque cas, la partie de vouloir éliminer la phrase fixe est la même, mais le résultat est que chacun a sa propre individualité. Chaque framework a du potentiel, donc si vous pensez qu'il vous convient, vous devriez aller à la compétition et l'utiliser.

Bonne vie à Kaggle (avec PyTorch)!

Recommended Posts

PyTorch Sangokushi (Ignite / Catalyst / Lightning)
Introduction à Lightning Pytorch