26 mai 2017

Aperçu de la librairie

MXNet est un engin permettant le développement, l'entraînement et le déploiement de modèle de Deep Learning.

Son architecture facilite l'exécution en parallèle sur de multiples GPU ou machines (Yarn - Hadoop)

Plusieurs langages sont supportés: R, Python, Scala, Julia, Perl, C++ en plus de permettre le déploiement de modèle sur mobile pour l'inférence.

Le projet a été reçu en 2017 dans l'Incubateur Apache et a été choisi comme engin de prédilection chez Amazon.

Le nom MXNet provient d'utilisation mixte de composantes de code impératif et déclaratif.

Composantes d'un modèle

  • Architecture: représentation symbolique des opérateurs qui composent le modèle et définissent les transformations applicables aux entrants.

  • Itérateur de données: fonction dont l'exécution fournit au modèle les entrants requis pour son exécution (ex: variables explicatives et réponses).

  • Optimiseur: module qui définit la façon dont les paramètres sont mis à jour à chaque itération.

  • Initialisation: spécifie la manière dont sont assignés les paramètres avant l'exécution de l'optimisation.

  • Exécuteur: instance qui exécute les calculs. Il peut y en avoir un seul (CPU) ou plusieurs (multi-GPU).

Architecture

La composition symbolique du modèle permet de définir: les entrants et les variables réponse, la structure et la fonction objective (RMSE, MAE, Softmax, …).

Aucun calcul n'est effectué à cette étape. Seule la structure du modèle est établie afin de déterminer les dépendances de calculs aux fins d'optimisation de l'exécution et la gestion de la mémoire.

data<- mx.symbol.Variable(name = "data")
final<- mx.symbol.FullyConnected(data=data, num_hidden=1, name = "final")
perte<- mx.symbol.LinearRegressionOutput(data=final, name = "perte_lineaire")

Exemples d'architectures

Multi-layer perceptron

Régression/classification

data<- mx.symbol.Variable(name = "data")
fc1<- mx.symbol.FullyConnected(data=data, num_hidden=5, name = "fc_1")
act1<- mx.symbol.Activation(data=fc1, act_type="relu", name = "act_1")
final<- mx.symbol.FullyConnected(data=act1, num_hidden=1, name = "final")
perte<- mx.symbol.LinearRegressionOutput(data=final, name = "perte_lineaire")

Convolutionnal Neural Network

Reconnaissance d'image - Microsoft ResNet

resnet<- Resnet_factory(num_classes = 2, loops=1)

Itérateur

La tâche de l'itérateur est de fournir aux exécuteurs les données nécessaires aux calculs pour chacune des itérations sur les mini-batch.

Les itérateurs pré-définis sont:

  • mx.io.arrayiter: itérateur prenant un objet array comme entrée
  • mx.io.CSVIter: lecture d'un CSV
  • mx.io.ImageRecordIter: lecture d'images en format binaire

Des itérateurs personnalisés peuvent être construits directement en R, leur performance dépend des fonctionnalités sous-jacentes.

À noter que l'utilisation d'itérateurs sur des CSV ou images permet d'éviter la contrainte de charger la totalité des données en mémoire.

MXNet fonctionne par défaut avec des données orientées par colonnes (chaque observation se trouve dans une colonne différente).

train_iter<- mx.io.arrayiter(data = t(train_x), label = train_y, batch.size = 64, shuffle = TRUE)
eval_iter<- mx.io.arrayiter(data = t(eval_x), label = eval_y, batch.size = 64, shuffle = FALSE)

Optimiseur

La technique de base pour la mise à jour des paramètres repose sur le Stochastic Gradient Descent (SGD). De nombreuses variantes développées pour rendre plus rapide ou robuste la convergence sont disponibles et des variantes personnalisées peuvent être implantées simplement.

Crédit: Alec Radford

optimizer_sgd<- mx.opt.create(name = "sgd", learning.rate=0.01, momentum=0.2, wd=0.001, clip_gradient = NULL)
optimizer_adadelta<- mx.opt.create(name = "adadelta", rho=0.9, epsilon=1e-5, wd=0.001, clip_gradient = NULL)

Initialisation

Afin que le modèle puisse apprendre, des valeurs aléatoires sont assignées aux paramètres du modèle avant l'exécution de l'optimisation. Différentes approches sont disponibles: Normal, Uniforme, Xavier.

initializer_uniform = mx.init.uniform(0.01)
initializer_normal = mx.init.normal(0.01)
initializer_Xavier = mx.init.Xavier(rnd_type = "gaussian", factor_type = "avg", magnitude = 2)
shapes<- perte$infer.shape(list(data=c(10,64)))
init_weight<- mx.init.create(initializer_Xavier, shape.array = shapes$arg.shapes, ctx = mx.cpu())

Paramètres initiaux de la première couche:

##             [,1]        [,2]        [,3]         [,4]        [,5]
##  [1,] -0.1468892 -0.31303713  0.11655252 -0.319210827  0.10671188
##  [2,] -0.9758506 -0.15900101  0.03780269  0.407831132 -0.10965590
##  [3,]  0.6533095 -0.44339389 -0.94432050  0.173175171  0.08963587
##  [4,]  0.4842907  0.07610406 -0.36179867  0.953701437  1.24566805
##  [5,] -0.5273501 -0.90586132 -0.10466730 -0.061899558  0.21827044
##  [6,] -0.5221599  0.56400305  0.14890528 -0.475205570  0.27220798
##  [7,] -0.7623045  0.77194858 -0.50170237 -0.473720253  0.17106315
##  [8,]  0.7719700  1.10728288 -0.18521838  0.687193453  0.14405634
##  [9,]  0.8050141 -0.13303186 -0.46044028 -0.001290238 -0.99506700
## [10,] -0.2301330  0.04370001 -0.07332364  0.020930536  0.77604187

Exécuteur

devices<- list(mx.gpu(0), mx.gpu(1))

Crédit: mxnet.io

Exemple - Régression avec MLP

model_reg<- mx.model.FeedForward.create(symbol = perte, 
                                        num.round = 5, 
                                        X = train_iter,
                                        eval.data = eval_iter,
                                        ctx = devices,
                                        optimizer = optimizer_adadelta,
                                        eval.metric = mx.metric.rmse, 
                                        initializer = initializer_uniform,
                                        epoch.end.callback = mx.callback.log.train.metric(1),
                                        verbose = TRUE)
## Start training with 1 devices
## [1] Train-rmse=24.8624257263575
## [1] Validation-rmse=21.4510698512757
## [2] Train-rmse=19.7278623338237
## [2] Validation-rmse=11.2220048750411
## [3] Train-rmse=12.0691206128286
## [3] Validation-rmse=9.96266543589868
## [4] Train-rmse=11.0969854126099
## [4] Validation-rmse=9.28710476080889
## [5] Train-rmse=10.4209175739393
## [5] Validation-rmse=8.74441345550095

Réutilisation de modèles pré-entraînés

Un modèle peut être représenté comme la combinaison de 2 composantes:

  • Structure symbolique: graph représentant l'enchaînement des différents opérateurs
  • Paramètres: les poids associés aux opérateurs

Un modèle performant bien à une tâche peut fournir une bonne base pour des tâches connexes.

Exemple: Chat vs Chien avec Resnet Microsoft 2015

  1. Charger le modèles de référence (Resnet)
  2. Adapter la structure au nouveau problème (changer le nombre de neuronnes de la dernière couche de 1000 à 2)
  3. Réutiliser les poids du modèle de référence pour tous les opérateurs saufs ceux qui ont été adaptés.
  4. Initialiser aléatoirement les paramètres qui ne sont pas transférés du modèle de référence.
  5. Run!

Une collection de modèles pré-entraînées est disponible dans le zoo

Analyse de langage avec CNN

Analyse de langage avec CNN

Un opérateur clé dans l'analyse de texte ou toute autre donnée à haute dimensionnalité est le embedding. Cet opérateur permet la vectorisation de l'information.

Pour une analyse des critiques de films sur IMDB, les mots ont été vectorisés en 2 dimensions:

embed <- mx.symbol.Embedding(data=data, input_dim=vocab_size, output_dim=2, name="embed")

Analyse de langage avec RNN

Les modès récurrents (RNN) forment une famille de modèles ayant mené à des avancées pas moins significatives que ce qu'ont permis les CNN, notamment pour la traduction machine (Google Translate).

Crédit: Andrej Karpathy

Analyse de langage avec RNN

L'analyse de sequences apporte des complications par rapport aux modèles conventionnels. Le nombre de mots (oude caractères) par exemple d'une phrase varie d'une observation à l'autre alors que la définition traditionelle d'un modèle symbolique implique une longueur fixeà la séquence.

Deux trucs viennent à la rescousse:
- Padding
- Bucketing

Generative Adversial Network (GAN)

Les modèle adversial recoupe une famille de modèles dont le trait commun est de mettre en compétition 2 sous-modèles:

  • Générateur: à partir de bruit, génère une réponse (image, texte, vecteur…)
  • Discriminateur: identifie si la valeur d'entrée provient d'un échantillon réel ou a plutôt été produit par le générateur

Le GAN conditionel est une variation permettant de générer un objet ayant des caractéristiques spécifiques. Ça peut être l'étiquette de la classe (ex: un chiffre pour MNIST) ou encore une expression plus élaborée (ex: un chat noir sur une chaise verte).

Crédit: Scott Reed

Generative Adversial Network (GAN)

L'implantation sur-mesure de nouvelles architectures nécessite d'aller au coeur des fonctionnalités de MXNet et permet de juger de la flexibilité de la plateforme pour le développement de nouveau modèles.

Exemple de code pour l'entraînement du générateur:

exec_D_back<- mxnet:::mx.symbol.bind(symbol = D_sym, ctx = devices,
                                     arg.arrays = exec_D$arg.arrays, 
                                     aux.arrays = exec_D$aux.arrays, 
                                     grad.reqs = rep("write", length(exec_D$arg.arrays)))

mx.exec.update.arg.arrays(exec_D_back, 
                          arg.arrays = list(data=D_data_fake, 
                                            digit=D_digit_fake, 
                                            label=mx.nd.array(rep(1, batch_size))), match.name=TRUE)

mx.exec.forward(exec_D_back, is.train=TRUE)
mx.exec.backward(exec_D_back)

D_grads<- exec_D_back$ref.grad.arrays$data
mx.exec.backward(exec_G, out_grads=D_grads)

update_args_G<- updater_G(weight = exec_G$ref.arg.arrays, grad = exec_G$ref.grad.arrays)
mx.exec.update.arg.arrays(exec_G, update_args_G, skip.null=TRUE)