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. ** **
--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
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
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.
Voyons ce qui se passe lorsque nous appliquons chaque framework au code d'apprentissage PyTorch.
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.
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
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.
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)
$ 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')}
Aucun
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]
--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
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
--Aucun -Il semble que vous deviez écrire la partie pour vous sauver ou utiliser la classe dans Ignite
2.3.3 Lightning
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]
--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
_ckpt_epoch_X.ckpt
est créé pour chaque époque, et il semble que vous supprimez le ckpt de l'ancienne époque.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
│ ├── ...
Les deux prennent en charge l'arrêt anticipé, etc.
2.4.1 Catalyst
catalyseur.utils.set_global_seed ()
sont également fournies.create_dataset, create_dataframe, prepare_dataset_labeling, split_dataframe
catalyst.utils.pandas
--Multi GPU et FP16 sont également pris en charge2.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
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)
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.
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)!