Luz est une API de haut niveau pour torch qui vise à encapsuler la
boucle d’entraînement dans un ensemble de blocs de code
réutilisables. Luz réduit le code de gestion requis pour former un
modèle avec torch et évite les erreurs fréquentes sur la séquence des
appels à zero_grad()
- backward()
-
step()
. Il simplifie également le processus de déplacement
des données et des modèles entre les CPUs et la GPU. Luz est conçu pour
être très flexible en fournissant une API en couches qui lui permet
d’être utile quel que soit le niveau de contrôle dont vous avez besoin
pour votre boucle d’entraînement.
Luz est fortement inspiré par d’autres framework de haut-niveau pour l’apprentissage profond, pour n’en citer que quelques-uns:
FastAI: nous avons été fortement inspirés par la bibliothèque FastAI, en particulier l’objet
Learner
et l’API callbacks.Keras: Nous sommes également fortement inspirés par Keras, en particulier pour les noms des callback. L’interface du module
lightning
est aussi similaire àcompile
.PyTorch Lightning: L’idée que le
luz_module
soit une sous-classe denn_module
s’inspire de l’objetLightningModule
dans la librairielightning
.HuggingFace Accelerate: L’API interne de placement sur les périphériques de calcul est fortement inspirée par Accelerate, mais est beaucoup plus modeste dans ses fonctionnalités.
Entraînement d’un nn_module
Luz tente, autant que possible, de réutiliser les structures existantes de torch. Aussi, un réseau de neurones est défini avec luz exactement de la même manière qu’avec torch brut. Par exemple, voici la définition d’un réseau convolutif (CNN feed-forward) qui peut être utilisé, par exemple, pour classer les images du jeu de données MNIST:
net <- nn_module(
"Net",
initialize = function(num_class) {
self$conv1 <- nn_conv2d(1, 32, 3, 1)
self$conv2 <- nn_conv2d(32, 64, 3, 1)
self$dropout1 <- nn_dropout2d(0.25)
self$dropout2 <- nn_dropout2d(0.5)
self$fc1 <- nn_linear(9216, 128)
self$fc2 <- nn_linear(128, num_class)
},
forward = function(x) {
x <- self$conv1(x)
x <- nnf_relu(x)
x <- self$conv2(x)
x <- nnf_relu(x)
x <- nnf_max_pool2d(x, 2)
x <- self$dropout1(x)
x <- torch_flatten(x, start_dim = 2)
x <- self$fc1(x)
x <- nnf_relu(x)
x <- self$dropout2(x)
x <- self$fc2(x)
x
}
)
Nous pouvons entraîner ce réseau en lui appliquant un jeu de données
via le chargeur de donnée train_dl
et l’évaluer grace au
torch::dataloaders()
nomé test_dl
avec:
fitted <- net %>%
setup(
loss = nn_cross_entropy_loss(),
optimizer = optim_adam,
metrics = list(
luz_metric_accuracy
)
) %>%
set_hparams(num_class = 10) %>%
set_opt_hparams(lr = 0.003) %>%
fit(train_dl, epochs = 10, valid_data = test_dl)
Voyons en détail ce qui se passe dans lee précédent bloc de code :
- La fonction
setup
vous permet de configurer la fonction de coût (objectif) et l’optimiseur que vous utiliserez pour entraîner votre modèle. En option, vous pouvez passer une liste de métriques qui sont suivies pendant la procédure d’apprentissage. Remarque : la fonction de coût peut être n’importe quelle fonction prenant en entréeinput
ettarget
et retournant une valeur de tenseur scalaire, et l’optimiseur peut être n’importe quel optimiseur de torch natif ou personnalisé, créé avec la fonctiontorch::optimizer()
. - La fonction
set_hparams()
permet de définir les hyper-paramètres qui doivent être passées à la méthodeinitialize()
du module. Par exemple, ici nous passonsnum_classes = 10
qui définit la taille de la dernière couche du réseau. - La fonction
set_opt_hparams()
vous permet de passer des hyper-paramètres utilisés par la fonction d’optimisation. Par exemple, l’optimiseur choisi icioptim_adam()
peut prendre le paramètrelr
spécifiant le taux d’apprentissage et nous le spécifions aveclr = 0.003
. - La méthode
fit
va prendre les spécifications du modèle fournies parsetup()
et exécuter la boucle d’apprentissage en utilisant les jeux de donnée d’entraînement et de validation spécifiés dans destorch::dataloaders()
pendant le nombre d’époques. Remarque : nous réutilisons les structures de données de base de torch pour les chargeurs de donnée, au lieu de recréer notre propre fonctionnalité de chargement de données. - L’objet retourné
fitted
contient le modèle entraîné ainsi que le registre des métriques et de la fonction de pertes produites au cours de l’apprentissage. Il pourra être utilisé pour faire de prédictions sur des données nouvelles, et pour l’évaluation du modèle entraîné sur d’autres jeux de données.
Au moment du lancement de la boucle de calcul, luz utilise l’accélérateur le plus rapide possible; si une carte graphique (GPU) doté de CUDA est disponible, elle sera utilisée, sinon le calcul sera affecté à la CPU. Il déplace également automatiquement les données, les optimisateurs et les modèles vers le périphérique sélectionné afin d’éviter de le programmer manuellement (qui constitue une grande source d’erreurs en général).
Pour créer des prédictions à partir du modèle entraîné, vous pouvez
utiliser la méthode predict
:
predictions <- predict(fitted, test_dl)
La boucle d’entraînement
Maintenant que vous avez une idée générale sur la façon d’utiliser la
fonction fit
, il est important d’avoir un aperçu de ce qui
se passe à l’intérieur. Voici le pseudocode de ce que fit
exécute. Sans vous montrer l’intégralité des opérations, cela devrait
vous aider à construire votre intuition:
# -> Initialiser les objets : modèle, optimiseurs.
# -> Sélectionner le périphérique d'apprentissage (GPU, ...).
# -> Déplacer les données, le modèle et les optimiseurs vers le périphérique sélectionné.
# -> Lancer l'apprentissage
for (epoch in 1:epochs) {
# -> Procédure d'apprentissage
for (batch in train_dl) {
# -> Calculer la méthode `forward` du modèle.
# -> Calculer la perte.
# -> Mettre à jour les poids.
# -> Enregistrer les métriques et suivre la perte.
}
# -> Procédure de validation
for (batch in valid_dl) {
# -> Calculer la méthode `forward` du modèle.
# -> Calculer la perte.
# -> Enregistrer les métriques et suivre la perte.
}
}
# -> Fin de l'apprentissage
Les métriques
L’un des paramêtres les plus importantes des projets d’apprentissage automatique est le choix de la métrique d’évaluation. Luz permet de suivre de nombreuses métriques différentes pendant l’apprentissage avec un effort de codage minime.
Pour suivre les métriques, il suffit de modifier le paramètre
metrics
dans la fonction setup
:
Luz fournit des implémentations de quelques-unes des métriques les
plus utilisées. Si une métrique n’est pas disponible, vous pouvez
toujours la créer à l’aide de la fonction luz_metric()
.
Pour implémenter une nouvelle métrique luz_metric
, il
faut définir 3 méthodes :
initialize
: définit l’état initial de la métrique. Cette fonction est appelée à chaque époque pour les boucles d’entraînement et de validation.update
: met à jour l’état interne de la métrique. Cette fonction est appelée après chaque lot d’entraînement et de validation en comparant les prédictions obtenues par le modèle et les valeurs cibles renvoyées par le chargeur de données.compute
: utilise l’état interne pour calculer les valeurs de la métrique. Cette fonction est appelée toutes les fois où il faut mettre à jour la métrique. Par exemple, elle est appelée après chaque lot d’entraînement pour afficher les informations de progression, mais seulement appelée une fois par époque pour enregistrer sa valeur lorsqu’il n’y a pas de barre de progression.
Vous pouvez définir un champ optionnel abbrev
qui donne
à la métrique une abréviation utilisée lors de l’affichage des
informations de métrique dans la console ou dans les enregistrements. Si
aucune valeur n’est fournie pour abbrev
, le nom de classe
de la métrique est utilisé.
Essayons maintenant de voir l’implémentation de
luz_metric_accuracy
pour mettre en œuvre une nouvelle
métrique :
luz_metric_accuracy <- luz_metric(
# Une abréviation à afficher dans les barres de progression ou
# lors de l'affichage des informations de progression
abbrev = "Acc",
# Initialisation pour la métrique. Les métriques sont initialisées
# à chaque époque, pour les entraînements et pour les validations.
initialize = function() {
self$correct <- 0
self$total <- 0
},
# Mise à jour à chaque lot d'entraînement ou de validation.
# La fonction update prend `preds`
# et `target` en paramètres et met à jour l'état interne `self`.
update = function(preds, target) {
pred <- torch::torch_argmax(preds, dim = 2)
self$correct <- self$correct + (pred == target)$
to(dtype = torch::torch_float())$
sum()$
item()
self$total <- self$total + pred$numel()
},
# Utilise l'état interne pour demander la valeur de la métrique.
compute = function() {
self$correct/self$total
}
)
Remarque: Il est préférable que la fonction
compute
retourne des valeurs R natives plutôt
qu’éventuellement des tenseurs torch. D’autres parties de luz
s’attendent à des valeurs R et s’y attacheront.
Évaluation
Une fois le modèle entraîné, vous voulez certainement évaluer sa
performance sur un autre jeu de données. Pour cela, luz fournit la
fonction ?evaluate
qui prend, en entrée, un modèle ajusté
et un jeu de données et calcule les métriques attachées au modèle.
La fonction evaluate()
retourne un objet de type
luz_module_evaluation
. Vous pouvez l’interroger pour les
métriques à l’aide de la fonction get_metrics()
ou
simplement imprimer pour voir les résultats.
Par exemple :
evaluation <- fitted %>% evaluate(data = valid_dl)
metrics <- get_metrics(evaluation)
print(evaluation)
#> A `luz_module_evaluation`
#> -- Results ---------------------------------------------------------------------
#> loss: 1.8892
#> mae: 1.0522
#> mse: 1.645
#> rmse: 1.2826
Personnalisation avec les points d’arrêt ou callbacks
Luz fournit différentes façons de personnaliser la boucle d’apprentissage, en fonction du niveau de contrôle dont vous avez besoin pendant qu’elle s’exécute. La façon la plus rapide et la plus « réutilisable » est via les rappels ou callbacks. Ils permettent de créer des modifications d’apprentissage qui peuvent être utiles dans diverses situations.
La boucle d’apprentissage de luz dispose de nombreux rappels qui peuvent, chacuns, appeler des fonctions R arbitraires. Cette fonctionnalité vous permet de personnaliser le processus d’apprentissage sans entrer dans la logique générale de l’apprentissage.
Luz implémente 3 rappels par défaut qui se produisent dans chaque procédure d’apprentissage :
callback train-eval: bascule le modèle en mode
train()
oueval()
selon si la procédure est en cours d’entraînement ou de validation.callback metrics : évalue les métriques au cours du processus d’entraînement et de validation.
callback progress : implémente une barre de progression et affiche des informations sur la progression des lots et des époques pendant l’apprentissage.
Vous pouvez également mettre en place des rappels personnalisés qui modifient ou agissent spécifiquement pour votre procédure d’apprentissage, c’est ce que nous allons montrer dans l’exemple suivant.
Implémentons un callback qui affiche ‘Itération n
’ (où
n
est le numéro d’itération) pour chaque lot du jeu de
données d’apprentissage et ‘Fini!’ lorsque la fin d’une époque est
atteinte.
Pour cela, nous utilisons la fonction luz_callback()
:
print_callback <- luz_callback(
name = "print_callback",
initialize = function(message) {
self$message <- message
},
on_train_batch_end = function() {
cat("Itération ", ctx$iter, "\n")
},
on_epoch_end = function() {
cat(self$message, "\n")
}
)
luz_callback()
prend des fonctions nommées en argument
...
, où le nom indique le moment auquel le callback doit
être appelé. Par exemple, on_train_batch_end()
est appelé
pour chaque fin de lot lors du processus d’apprentissage, et
on_epoch_end()
est appelé à la fin de chaque époque.
La valeur retournée par luz_callback()
est une fonction
qui s’intancie au moment du callback. Les callbacks peuvent avoir besoin
de paramètres d’initialisation, comme par exemple le nom d’un fichier où
vous souhaitez enregistrer les résultats. Dans ce cas, vous pouvez
passer par une méthode initialize
lors de la définition du
callback, et disposer ces paramètres dans l’objet self
.
Dans l’exemple ci-dessus, le callback a un paramètre
message
qui sera utilisé pour etre affiché à la fin de
chaque époque.
Une fois que le callback est défini, il peut être passé à la fonction
fit
via le paramètre callbacks
:
Les callbacks peuvent être déclenchés à de nombreuses positions de la boucle d’apprentissage, y compris en combinaison les uns avec les autres. Voici un aperçu des positions possibles pour insérer les callbacks :
Début de boucle d'ajustement
- on_fit_begin
Début de l'époque
- on_epoch_begin
Début de l'apprentissage
- on_train_begin
Début de la boucle sur le lot
- on_train_batch_begin
Début du lot d'apprentissage par défaut
- on_train_batch_after_pred
- on_train_batch_after_loss
- on_train_batch_before_backward
- on_train_batch_before_step
- on_train_batch_after_step
Fin du lot d'apprentissage par défaut :
- on_train_batch_end
Fin de la boucle de batch
- on_train_end
Fin de l'apprentissage
Début de la validation
- on_valid_begin
Début de la boucle sur le lot
- on_valid_batch_begin
Début du lot de validation par défaut
- on_valid_batch_after_pred
- on_valid_batch_after_loss
Fin du lot de validation par défaut
- on_valid_batch_end
Fin de la boucle sur le lot
- on_valid_end
Fin de la validation
- on_epoch_end
Fin de l'époque
- on_fit_end
Fin de l'ajustement
Chaque étape marquée avec on_*
est une position dans le
processus d’apprentissage qui est disponible pour déclencher les
callbacks.
L’autre partie importante des callbacks est l’objet contexte
ctx
. Voir help("ctx")
pour plus de
détails.
Par défaut, les callbacks sont déclenchés dans l’ordre dans lequel
ils ont été passés à fit
(ou predict
ou
evaluate
), mais vous pouvez fournir un attribut
weight
qui contrôlera l’ordre croissant dans lequel il sera
appelé. Par exemple, si un callback a un weight = 10
et un
autre a un weight = 1
, alors le premier est appelé
après le second. Les callbacks qui ne spécifient pas
d’attribut de poids sont considérés comme ayant un
weight = 0
. Certains callbacks intégrés dans luz
fournissent déjà une valeur de poids. Par exemple,
?luz_callback_early_stopping
a une valeur de poids de
Inf
, puisque en général on souhaite l’exécuter à la toute
fin dans la boucle.
Notez que les callbacks peuvent être combinés pour effectuer des opérations complexes sur le processus d’apprentissage.
La variable ctx
est un objet utilisé dans luz pour
partager des informations entre la boucle d’entraînement et les
callbacks, les méthodes du modèle et les métriques. La table suivante
décrit les informations disponibles par défaut dans ctx
.
D’autres rappels peuvent potentiellement modifier ces attributs ou en
ajouter de nouveaux.
Attribut | Description |
---|---|
verbose |
La valeur (TRUE ou FALSE ) attribuée à
l’argument verbose dans la fonction fit . |
accelerator |
Objet accélérateur utilisé pour interroger le bon périphérique de
calcul (cuda, …) sur lequel placer les modèles, les données, etc. Il
repose sur la valeur passée au paramètre accelerator dans
fit . |
model |
Objet nn_module initialisé, qui sera entraîné pendant
la procédure fit . |
optimizers |
Une liste nommée d’optimiseurs utilisés lors de l’entraînement. |
data |
Le chargeur de données actuellement en cours d’utilisation. Lors de
l’entraînement, c’est ctx$train_data , lors de la
validation, c’est ctx$valid_data . Il peut également s’agir
du jeu de données de prédiction lorsque dans predict . |
train_data |
Chargeur de données passé à l’argument data dans
fit . Modifié pour produire des données sur le périphérique
de calcul sélectionné. |
valid_data |
Chargeur de données passé à l’argument valid_data dans
fit . Modifié pour produire des données sur le périphérique
de calcul sélectionné. |
min_epochs |
Nombre minimum d’époques pendant lesquelles le modèle sera entraîné. |
max_epochs |
Nombre maximum d’époques pendant lesquelles le modèle sera entraîné. |
epoch |
Époque actuelle de l’entraînement. |
iter |
Itération actuelle de l’entraînement. Elle est réinitialisée à chaque époque et lorsque l’on passe de la phase d’apprentissage à la validation. |
training |
TRUE si le modèle est en mode entraînement,
FALSE sinon. Voir également
help("luz_callback_train_valid") . |
callbacks |
Liste des rappels qui seront déclenchés pendant la procédure
d’entraînement. C’est l’union de la liste passée à l’argument
callbacks et des rappels par défaut. |
step |
Closure qui sera utilisée pour faire une étape du modèle. Elle est
utilisée pour les deux phases : apprentissage et validation. Elle ne
prend pas d’arguments, mais peut accéder à la variable
ctx . |
call_callbacks |
Appeler les rappels par position. Par exemple
call_callbacks("on_train_begin") appellera tous les rappels
qui fournissent des méthodes pour cette position dans la boucle
d’apprentissage. |
batch |
Dernier lot obtenu par le chargeur de données. Un lot est une liste() avec 2 éléments, l’un servant d’entrée et l’autre de sortie. |
input |
Premier élément du dernier lot obtenu par le chargeur de données actuel. |
target |
Deuxième élément du dernier lot obtenu par le chargeur de données actuel. |
pred |
Dernières prédictions obtenues par ctx$model$forward .
Remarque : elles peuvent être potentiellement modifiées
par les rappels précédemment déclenchés. On notera également qu’elles ne
seront pas disponibles si vous avez utilisé une étape d’entraînement
personnalisée. |
loss_fn |
La fonction de coût active que l’on veut minimiser pendant l’apprentissage. |
loss |
Dernière valeur de coût obtenue. |
handlers |
Les gestionnaires d’erreur actuels, c’est-à-dire les fonctions qui sont appelées en cas d’erreur. |
epoch_handlers |
Liste des gestionnaires utilisés à l’intérieur de la boucle d’époque. Ils peuvent être utilisés pour gérer des conditions spécifiques aux époques, sans nécessairement mettre fin à l’apprentissage. |
Les attributs de ctx
peuvent être utilisés pour produire
le comportement souhaité des callbacks. Vous pouvez trouver de plus
amples informations sur l’objet de contexte à l’aide de
help("ctx")
. Dans notre exemple, nous utilisons l’attribut
ctx$iter
pour afficher le numéro d’itération pour chaque
lot d’apprentissage.
Étapes suivantes
Dans cet article, vous avez appris à entraîner votre premier modèle en utilisant luz et les bases de la personnalisation en utilisant à la fois des métriques personnalisées et des callbacks.
Luz permet également des modifications plus flexibles du processus
d’apprentissage décrites dans la
vignette("custom-loop")
.
Vous devriez maintenant être en mesure de suivre les exemples marqués avec la catégorie ‘basique’ dans la galerie d’exemples.