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. ** ** **
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
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.
Mal sehen, was passiert, wenn wir jedes Framework auf einfachen PyTorch-Lerncode anwenden.
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.
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
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.
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)
$ 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')}
Keiner
2.3.1 Catalyst
$ 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]
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
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
--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
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]
--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
│ ├── ...
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
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)
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
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)!