[PYTHON] PyTorch Sangokushi (Zünden / Katalysator / Blitz)

0. Einleitung

Ich denke, dass alle Deep-Learning-Frameworks Bereiche sind, in denen die Entwicklung sehr schnell und aufregend ist. Während es TensorFlow und Jax gibt, gibt es neulich auch PFN News, und PyTorch wird wahrscheinlich solider. Vielleicht werden weitere PyTorch-Benutzer fortfahren (ich werde den ** offiziellen Trainer **, der sich ebenfalls in Chainer befand, nicht erwähnen, da dies die Existenz dieses Artikels in PyTorch gefährden würde).

Während PyTorch einen hohen Freiheitsgrad aufweist, bleibt der Code zum Lernen (z. B. um die Schleife jeder Epoche) dem Einzelnen überlassen und ist in der Regel ein sehr eindeutiger Code.

Das Schreiben dieser Codes selbst ist eine Menge Lernen und ich denke, Sie sollten ** sicher gehen **, wenn Sie PyTorch starten. Wenn die Individualität jedoch zu stark ist, kann es schwierig sein, sie mit anderen Personen zu teilen oder zwischen Wettbewerben wiederzuverwenden (z. B. Oreore Trainer, häufig in Winner Solutions zu sehen).

Im Fall von PyTorch gibt es kein Framework an sich, um den Code zum Lernen zu vereinfachen (es gab zuvor Trainer, aber es wurde abgeschafft), sondern [Ecosystem \ | PyTorch](https: // pytorch) Die folgenden Frameworks für PyTorch werden in .org / ökosystem /) eingeführt.

Es gibt viele. Die Wahrheit ist, probieren Sie sie alle aus und finden Sie die, die zu Ihnen passt. Sie sind jedoch nicht einfach. Daher möchte ich in diesem Artikel jedes Framework vorstellen und einen kurzen Vergleich anstellen, damit Sie es als Leitfaden verwenden können.

Beachten Sie, dass ** fastai ** einen hohen Abstraktionsgrad aufweist (der Code ist in der Regel kurz), aber ich hatte das Gefühl, dass die Lernkosten für das Hinzufügen detaillierter Operationen selbst hoch waren, daher ** Vergleich in diesem Artikel Dann habe ich es vorher weggelassen **.

Daher konzentrieren wir uns in diesem Artikel auf ** Catalyst **, ** Ignite ** und ** Lightning ** und führen Vergleiche durch, vorausgesetzt, Sie nehmen am ** Kaggle-Wettbewerb ** teil.

Übrigens habe ich im Voraus bestätigt, dass diese drei Frameworks bis zu einem gewissen Grad funktionieren. Wenn Sie diesen Artikel gelesen haben und noch etwas weiter gehen möchten, lesen Sie ihn bitte.

--Code in diesem Artikel - https://github.com/yukkyo/Compare-PyTorch-Catalyst-Ignite-Lightning

Wie bereits erwähnt, ** haben alle das Potenzial, am Wettbewerb teilzunehmen. ** ** **

1. Ziel dieses Artikels (oder nicht)

2. Vergleich jedes Frameworks (Catalyst, Ignite, Lightning)

Ich habe die neueste Version von Pip am 13. Dezember 2019 verwendet. Die Python-Version ist 3.7.5. ~~ Es wird auch davon ausgegangen, dass NVIDIA / apex bereits installiert ist. ~~ Sie benötigen keinen Apex, um diesen Code auszuführen.

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

2.1 Übergang der Sternzahl (Stand 10. Dezember 2019)

Während Catalyst und Ignite stetig wachsen, ist Lightning seit April dieses Jahres enorm gewachsen. Auf der anderen Seite ist Lightning weniger als ein Jahr alt, daher befinden sich viele Funktionen in der Entwicklung, und es sollte beachtet werden, dass es immer noch instabil ist (beim Upgrade nicht abwärtskompatibel).

Es ist auch möglich, dass Catalyst Ignite überholt, da Catalyst bei modernen Kaggle-Notebooks so häufig vorkommt.

2.2 Wie schreibe ich?

Mal sehen, was passiert, wenn wir jedes Framework auf einfachen PyTorch-Lerncode anwenden.

2.2.1 Gemeinsamer Teil

Dieses Mal werde ich versuchen, mit Resnet18 für den cifer10-Datensatz zu lernen. Wie im folgenden Code gezeigt, werden die Modell- und Dataloader-Definitionen im Voraus in Funktionen umgewandelt.

Allgemeiner Teilecode (ich habe ihn gefaltet, weil er lang ist)

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():
    """Eine Funktion, die Loss gut zurückgibt"""
    return nn.CrossEntropyLoss()

def get_loaders(batch_size: int = 16, num_workers: int = 4):
    """Eine Funktion, die jeden Dataloader gut zurückgeben kann"""
    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):
    """Eine Funktion, die das Modell gut zurückgibt"""
    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 Basiscode (einfacher Lerncode)

Wenn Sie es ehrlich schreiben, ohne zu viel nachzudenken, wird es wie folgt sein. .to (device), loss.backward () und optimizer.step () müssen geschrieben werden, daher sind sie in der Regel lang. Auch "mit torch.no_grad ()" kann mit "torch.set_grad_enabled (bool)" sowohl mit Train als auch mit Eval kompatibel gemacht werden, aber es gibt viele verschiedene Prozesse zwischen Train und Eval (z. Wenn Sie eine Funktion erstellen, die sowohl optimizer.step () als auch Metriken usw. unterstützt, sind die Aussichten tendenziell schlechter.

Basiscode (gefaltet, weil er lang ist)
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()

    #Modell Multi-GPU oder Unterstützung 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()

        #Verarbeitung um Logger
        #Ein chaotischer Prozess zum Drucken
        print(f'epoch: {epoch} ', metrics_train, metrics_eval)

        #Schreiben Sie hier einen Prozess, der noch verwirrender wäre, wenn Sie tqdm verwenden würden
        #Prozess zum Speichern des Modells
        # Multi-Schreiben Sie genauer für GPUs

2.2.2 Catalyst

Übergeben Sie für Catalyst einfach das, was Sie benötigen, an "SupervisedRunner" in der Bibliothek, und fertig. Es ist wirklich klug! Außerdem sind wichtige Metriken wie Genauigkeit und Würfel in Catalyst enthalten, sodass Sie sie selten selbst schreiben (die Einführung eigener Metriken schien relativ einfach zu sein). Es scheint normalerweise mühsam zu sein, die Standardeinstellung beizubehalten. Wenn Sie jedoch selbst eine detaillierte Verarbeitung hinzufügen möchten, müssen Sie möglicherweise ein wenig nachforschen.

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 hat eine etwas andere Haarfarbe als Catalyst und Lightning, die später beschrieben werden. Wie unten gezeigt, handelt es sich um ein Bild des Einfügens der Verarbeitung, die Sie mit @ Trainer.on (Events.EPOCH_COMPLETED) usw. für jedes Timing einfügen möchten. Darüber hinaus wird Ignite auch offiziell mit Genauigkeit usw. erstellt. Wenn es sich also um einen wichtigen Bewertungsindex handelt, müssen Sie ihn anscheinend nicht selbst definieren.

Auf der anderen Seite scheint es, dass Sie sich daran gewöhnen müssen, und es gibt ein hohes Maß an Freiheit beim Sandwiching des Ereignisses (Sie können es auch wie trainder.append hinzufügen). Wenn Sie also einen Fehler machen, können sich die allgemeinen Aussichten verschlechtern.

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

Für Lightning müssen Sie eine Klasse definieren, die von "LightningModule" erbt (wie eine Trainer-Klasse).

Der Name jedes Schritts (z. B. "Trainingsschritt") ist festgelegt, und Sie geben jeden Schritt selbst aus. Das Training selbst wird auch von der Klasse "pytorch_lightning.Trainer" durchgeführt, und Einstellungen wie GPU, MixedPrecision und Gradientenakkumulation werden in dieser Klasse festgelegt. Außerdem sind Metriken in Lightning nicht verfügbar, sodass Sie sie selbst schreiben müssen.

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 Konsolenbildschirm und Ausgabe bei Ausführung in jedem Framework

2.3.1 Standard

Konsolenbildschirm

$ 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')}

Ausgabe

Keiner

2.3.1 Catalyst

Konsolenbildschirm

$ 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]

Ausgabe

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

Konsolenbildschirm

Der Bildschirm ist etwas sauberer als 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

Ausgabe

--Keiner -Es scheint, dass Sie das Teil schreiben müssen, um sich selbst zu speichern oder die Klasse in Ignite zu verwenden

2.3.3 Lightning

Konsolenbildschirm

Standardmäßig scheint Lightning alles in der Leiste in tqdm anzuzeigen.

$ 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]

Ausgabe

--Lightning erstellt und speichert die folgenden Verzeichnisse wie version_x, wenn doppelte Verzeichnisse vorhanden sind. (Obwohl es ärgerlich sein kann und Sie Ihren eigenen Kontrollpunkt definieren können)

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 Andere bemerkenswerte Orte

Beide unterstützen Early Stopping usw.

2.4.1 Catalyst

--Wiederherstellbarkeitsbezogene Funktionen wie "catalog.utils.set_global_seed ()" werden ebenfalls bereitgestellt. --Funktionen, mit denen Sie Dataset einfacher schreiben können, werden ebenfalls unterstützt. - create_dataset, create_dataframe, prepare_dataset_labeling, split_dataframe - catalyst.utils.pandas --Multi GPU und FP16 werden ebenfalls unterstützt

2.4.2 Ignite

2.4.3 Lightning

--Multi GPU und FP16 werden ebenfalls unterstützt

2.5 Wenn Sie empfehlen

Beide haben Potenzial und sind nicht obligatorisch. Unten sind meine persönlichen Eindrücke.

――Es ist mein erstes Mal in einem Bildwettbewerb und ich bin mir nicht sicher, was ich tun soll - → PyTorch Catalyst ――Ich bin an den Bildwettbewerb gewöhnt und möchte mit mörderischer Absicht am Wettbewerb teilnehmen (starkes Gefühl, die Goldmedaille zu gewinnen)

3. Um sich frei zwischen Catalyst, Ignite und Lightning zu bewegen

Sie können die hier verwendeten Frameworks eingrenzen, aber ** Es sollte kein Problem sein, wenn Sie jedes Framework so schreiben, dass Sie problemlos zwischen ihnen wechseln können. ** Hier sind einige Dinge, die Sie beim Schreiben von PyTorch-Code beachten sollten.

--Entfernen Sie den Inhalt der Schleife so weit wie möglich

4. Schließlich

In diesem Artikel haben wir PyTorch Catalyst, Ignite und Lightning verglichen. In jedem Fall ist der Teil des Wunsches, die feste Phrase zu eliminieren, der gleiche, aber das Ergebnis ist, dass jede ihre eigene Individualität hat. Jedes Framework hat Potenzial. Wenn Sie also der Meinung sind, dass es zu Ihnen passt, sollten Sie zum Wettbewerb gehen und es nutzen.

Hab ein gutes Leben mit Kaggle (mit PyTorch)!

Recommended Posts

PyTorch Sangokushi (Zünden / Katalysator / Blitz)
Einführung in Lightning Pytorch