9.6 C
New York

Weblog Posit AI : Pleins feux sur la communauté : s’amuser avec torchopt


Dès le début, il a été passionnant de voir le nombre croissant de packages se développer dans le torch écosystème. Ce qui est étonnant, c’est la variété des choses que les gens font avec torch: étendre ses fonctionnalités ; intégrer et mettre à revenue dans un domaine spécifique son infrastructure de différenciation automatique de bas niveau ; architectures de réseaux de neurones portuaires… et final however not least, répondre à des questions scientifiques.

Ce billet de weblog présentera, sous une forme courte et plutôt subjective, l’un de ces packages : torchopt. Avant de commencer, une selected que nous devrions probablement dire beaucoup plus souvent : si vous souhaitez publier un article sur ce weblog, sur le package deal que vous développez ou sur la manière dont vous utilisez les frameworks d’apprentissage en profondeur du langage R, faites-le nous savoir. – vous êtes plus que bienvenus!

torchopt

torchopt est un package deal développé par Gilberto Camara et collègues de Institut nationwide de recherche spatiale, Brésil.

À première vue, la raison d’être de l’emballage est plutôt évidente. torch lui-même n’implémente pas – et ne devrait pas non plus – mettre en œuvre tous les algorithmes d’optimisation récemment publiés et potentiellement utiles pour vos besoins. Les algorithmes rassemblés ici sont donc probablement exactement ceux que les auteurs avaient le plus envie d’expérimenter dans leur propre travail. Au second d’écrire ces lignes, ils comprennent, entre autres, divers membres du mouvement populaire ADA* et *ADAM* des familles. Et nous pouvons supposer en toute sécurité que la liste s’allongera avec le temps.

Je vais présenter le package deal en mettant en évidence quelque selected qui, techniquement, est « simplement » une fonction utilitaire, mais qui peut être extrêmement utile pour l’utilisateur : la possibilité, pour un optimiseur arbitraire et une fonction de take a look at arbitraire, de tracer les étapes suivies en optimisation.

S’il est vrai que je n’ai aucune intention de comparer (et encore moins d’analyser) différentes stratégies, il y en a une qui, pour moi, se démarque dans la liste : ADAHESSIAN (Yao et al. 2020), un algorithme de second ordre conçu pour s’adapter aux grands réseaux de neurones. Je suis particulièrement curieux de voir remark il se comporte par rapport au L-BFGS, le « classique » de second ordre disponible à partir de la base torch nous avons eu un article de weblog dédié environ l’année dernière.

La façon dont ça marche

La fonction d’utilité en query est nommée test_optim(). Le seul argument requis concerne l’optimiseur à essayer (optim). Mais vous voudrez probablement en modifier trois autres également :

  • test_fn: Pour utiliser une fonction de take a look at différente de celle par défaut (beale). Vous pouvez choisir parmi les nombreux proposés dans torchopt, ou vous pouvez passer le vôtre. Dans ce dernier cas, vous devez également fournir des informations sur le domaine de recherche et les factors de départ. (Nous verrons cela dans un instantaneous.)
  • steps: Pour définir le nombre d’étapes d’optimisation.
  • opt_hparams: Pour modifier les hyperparamètres de l’optimiseur ; plus particulièrement, le taux d’apprentissage.

Ici, je vais utiliser le flower() fonction qui figurait déjà en bonne place dans le publish susmentionné sur L-BFGS. Il se rapproche de son minimal à mesure qu’il se rapproche de (0,0) (mais est indéfini à l’origine elle-même).

C’est ici:

flower <- operate(x, y) {
  a <- 1
  b <- 1
  c <- 4
  a * torch_sqrt(torch_square(x) + torch_square(y)) + b * torch_sin(c * torch_atan2(y, x))
}

Pour voir à quoi ça ressemble, faites défiler un peu vers le bas. L’intrigue peut être modifiée de multiples façons, mais je m’en tiendrai à la disposition par défaut, avec des couleurs de longueur d’onde plus courte mappées sur des valeurs de fonction inférieures.

Commençons nos explorations.

Pourquoi disent-ils toujours que le taux d’apprentissage est necessary ?

C’est vrai que c’est une query rhétorique. Mais encore, parfois, les visualisations constituent les preuves les plus mémorables.

Ici, nous utilisons un optimiseur de premier ordre populaire, AdamW (Loshchilov et Hutter 2017). Nous l’appelons avec son taux d’apprentissage par défaut, 0.01, et laissez la recherche s’exécuter sur deux cents étapes. Comme dans ce publish précédent, nous partons de loin – le level (20,20)bien en dehors de la région rectangulaire d’intérêt.

library(torchopt)
library(torch)

test_optim(
    # name with default studying price (0.01)
    optim = optim_adamw,
    # move in self-defined take a look at operate, plus a closure indicating beginning factors and search area
    test_fn = checklist(flower, operate() (c(x0 = 20, y0 = 20, xmax = 3, xmin = -3, ymax = 3, ymin = -3))),
    steps = 200
)
Minimiser la fonction fleur avec AdamW.  N° de configuration  1 : taux d'apprentissage par défaut, 200 étapes.

Oups, que s’est-il passé ? Y a-t-il une erreur dans le code de traçage ? – Pas du tout; c’est juste qu’après le nombre most d’étapes autorisées, nous ne sommes pas encore entrés dans la région d’intérêt.

Ensuite, nous augmentons le taux d’apprentissage d’un facteur dix.

test_optim(
    optim = optim_adamw,
    # scale default price by an element of 10
    opt_hparams = checklist(lr = 0.1),
    test_fn = checklist(flower, operate() (c(x0 = 20, y0 = 20, xmax = 3, xmin = -3, ymax = 3, ymin = -3))),
    steps = 200
)
Minimiser la fonction fleur avec AdamW.  N° de configuration  1 : taux d'apprentissage par défaut, 200 étapes.

Quel changement ! Avec un taux d’apprentissage multiplié par dix, le résultat est optimum. Cela signifie-t-il que le paramètre par défaut est mauvais ? Bien sûr que non; l’algorithme a été réglé pour bien fonctionner avec les réseaux de neurones, et non avec une fonction qui a été délibérément conçue pour présenter un défi spécifique.

Naturellement, nous devons également voir ce qui se passe pour un taux d’apprentissage encore plus élevé.

test_optim(
    optim = optim_adamw,
    # scale default price by an element of 70
    opt_hparams = checklist(lr = 0.7),
    test_fn = checklist(flower, operate() (c(x0 = 20, y0 = 20, xmax = 3, xmin = -3, ymax = 3, ymin = -3))),
    steps = 200
)
Minimiser la fonction fleur avec AdamW.  N° de configuration  3 : lr = 0,7, 200 pas.

Nous voyons le comportement dont nous avons toujours été avertis : l’optimisation sautille follement, avant de sembler s’éterniser. (Apparemment, parce que dans ce cas, ce n’est pas ce qui se passe. Au lieu de cela, la recherche sautera loin, et reviendra, en continu.)

Maintenant, cela pourrait rendre un curieux. Que se passe-t-il réellement si nous choisissons le « bon » taux d’apprentissage, mais ne nous arrêtons pas à l’optimisation à deux cents étapes ? Ici, nous essayons trois cents à la place :

test_optim(
    optim = optim_adamw,
    # scale default price by an element of 10
    opt_hparams = checklist(lr = 0.1),
    test_fn = checklist(flower, operate() (c(x0 = 20, y0 = 20, xmax = 3, xmin = -3, ymax = 3, ymin = -3))),
    # this time, proceed search till we attain step 300
    steps = 300
)
Minimiser la fonction fleur avec AdamW.  N° de configuration  3 : gd

Fait intéressant, nous voyons le même style de va-et-vient se produire ici qu’avec un taux d’apprentissage plus élevé – c’est juste retardé dans le temps.

Une autre query amusante qui vient à l’esprit est la suivante : pouvons-nous suivre la manière dont le processus d’optimisation « discover » les quatre pétales ? Avec quelques expérimentations rapides, je suis arrivé à ceci:

Minimisation de la fonction fleur avec AdamW, lr = 0,1 : « Exploration » successive des pétales.  Pas (dans le sens des aiguilles d'une montre) : 300, 700, 900, 1300.

Qui a dit qu’il fallait du chaos pour produire une belle intrigue ?

Un optimiseur de second ordre pour les réseaux de neurones : ADAHESSIAN

Passons à l’algorithme que j’aimerais vérifier spécifiquement. À la suite d’un peu d’expérimentation sur le taux d’apprentissage, j’ai pu arriver à un wonderful résultat après seulement trente-cinq étapes.

test_optim(
    optim = optim_adahessian,
    opt_hparams = checklist(lr = 0.3),
    test_fn = checklist(flower, operate() (c(x0 = 20, y0 = 20, xmax = 3, xmin = -3, ymax = 3, ymin = -3))),
    steps = 35
)
Minimiser la fonction fleur avec AdamW.  N° de configuration  3 : gd

Compte tenu de nos expériences récentes avec AdamW, c’est-à-dire qu’il « ne s’installe tout simplement pas » très proche du minimal, nous voudrons peut-être également effectuer un take a look at équivalent avec ADAHESSIAN. Que se passe-t-il si nous continuons à optimiser un peu plus longtemps – pour deux cents étapes, disons ?

test_optim(
    optim = optim_adahessian,
    opt_hparams = checklist(lr = 0.3),
    test_fn = checklist(flower, operate() (c(x0 = 20, y0 = 20, xmax = 3, xmin = -3, ymax = 3, ymin = -3))),
    steps = 200
)
Minimiser la fonction florale avec ADAHESSIAN.  N° de configuration  2 : lr = 0,3, 200 pas.

Comme AdamW, ADAHESSIAN va « explorer » les pétales, mais il ne s’éloigne pas pour autant du minimal.

Est-ce surprenant ? Je ne dirais pas que c’est le cas. L’argument est le même qu’avec AdamW, ci-dessus : son algorithme a été réglé pour bien fonctionner sur de grands réseaux de neurones, et non pour résoudre une tâche de minimisation classique et artisanale.

Maintenant que nous avons déjà entendu cet argument deux fois, il est temps de vérifier l’hypothèse explicite : qu’un algorithme classique du second ordre gère mieux cela. En d’autres termes, il est temps de revisiter L-BFGS.

Le meilleur des classiques : revisiter le L-BFGS

Utiliser test_optim() avec L-BFGS, il faut faire un petit détour. Si vous avez lu le message sur L-BFGS, vous vous souvenez peut-être qu’avec cet optimiseur, il est nécessaire d’envelopper à la fois l’appel à la fonction de take a look at et l’évaluation du gradient dans une fermeture. (La raison étant que les deux doivent être appelables plusieurs fois par itération.)

Maintenant, vu remark L-BFGS est un cas très spécial, et peu de gens sont susceptibles d’utiliser test_optim() avec elle à l’avenir, il ne semblerait pas utile de faire en sorte que cette fonction gère différents cas. Pour ce take a look at on-off, j’ai simplement copié et modifié le code selon les besoins. Le résultat, test_optim_lbfgs()se trouve dans le annexe.

En décidant du nombre d’étapes à essayer, nous tenons compte du fait que L-BFGS a un idea d’itérations différent de celui des autres optimiseurs ; c’est-à-dire qu’il peut affiner sa recherche plusieurs fois par étape. En effet, du publish précédent je sais que trois itérations suffisent :

test_optim_lbfgs(
    optim = optim_lbfgs,
    opt_hparams = checklist(line_search_fn = "strong_wolfe"),
    test_fn = checklist(flower, operate() (c(x0 = 20, y0 = 20, xmax = 3, xmin = -3, ymax = 3, ymin = -3))),
    steps = 3
)
Minimisation de la fonction florale avec L-BFGS.  N° de configuration  1 : 3 étapes.

À ce stade, bien sûr, je dois m’en tenir à ma règle de tester ce qui se passe avec « trop ​​d’étapes ». (Même si cette fois, j’ai de bonnes raisons de croire qu’il ne se passera rien.)

test_optim_lbfgs(
    optim = optim_lbfgs,
    opt_hparams = checklist(line_search_fn = "strong_wolfe"),
    test_fn = checklist(flower, operate() (c(x0 = 20, y0 = 20, xmax = 3, xmin = -3, ymax = 3, ymin = -3))),
    steps = 10
)
Minimisation de la fonction florale avec L-BFGS.  N° de configuration  2 : 10 étapes.

Hypothèse confirmée.

Et ici se termine mon introduction ludique et subjective à torchopt. J’espère que vous l’avez aimé; mais en tout cas, je pense que vous auriez dû avoir l’impression qu’il s’agit ici d’un package deal utile, extensible et inclined d’évoluer, à surveiller à l’avenir. Comme toujours, merci d’avoir lu!

annexe

test_optim_lbfgs <- operate(optim, ...,
                       opt_hparams = NULL,
                       test_fn = "beale",
                       steps = 200,
                       pt_start_color = "#5050FF7F",
                       pt_end_color = "#FF5050FF",
                       ln_color = "#FF0000FF",
                       ln_weight = 2,
                       bg_xy_breaks = 100,
                       bg_z_breaks = 32,
                       bg_palette = "viridis",
                       ct_levels = 10,
                       ct_labels = FALSE,
                       ct_color = "#FFFFFF7F",
                       plot_each_step = FALSE) {


    if (is.character(test_fn)) {
        # get beginning factors
        domain_fn <- get(paste0("domain_",test_fn),
                         envir = asNamespace("torchopt"),
                         inherits = FALSE)
        # get gradient operate
        test_fn <- get(test_fn,
                       envir = asNamespace("torchopt"),
                       inherits = FALSE)
    } else if (is.checklist(test_fn)) {
        domain_fn <- test_fn((2))
        test_fn <- test_fn((1))
    }

    # start line
    dom <- domain_fn()
    x0 <- dom(("x0"))
    y0 <- dom(("y0"))
    # create tensor
    x <- torch::torch_tensor(x0, requires_grad = TRUE)
    y <- torch::torch_tensor(y0, requires_grad = TRUE)

    # instantiate optimizer
    optim <- do.name(optim, c(checklist(params = checklist(x, y)), opt_hparams))

    # with L-BFGS, it's essential to wrap each operate name and gradient analysis in a closure,
    # for them to be callable a number of occasions per iteration.
    calc_loss <- operate() {
      optim$zero_grad()
      z <- test_fn(x, y)
      z$backward()
      z
    }

    # run optimizer
    x_steps <- numeric(steps)
    y_steps <- numeric(steps)
    for (i in seq_len(steps)) {
        x_steps(i) <- as.numeric(x)
        y_steps(i) <- as.numeric(y)
        optim$step(calc_loss)
    }

    # put together plot
    # get xy limits

    xmax <- dom(("xmax"))
    xmin <- dom(("xmin"))
    ymax <- dom(("ymax"))
    ymin <- dom(("ymin"))

    # put together knowledge for gradient plot
    x <- seq(xmin, xmax, size.out = bg_xy_breaks)
    y <- seq(xmin, xmax, size.out = bg_xy_breaks)
    z <- outer(X = x, Y = y, FUN = operate(x, y) as.numeric(test_fn(x, y)))

    plot_from_step <- steps
    if (plot_each_step) {
        plot_from_step <- 1
    }

    for (step in seq(plot_from_step, steps, 1)) {

        # plot background
        picture(
            x = x,
            y = y,
            z = z,
            col = hcl.colours(
                n = bg_z_breaks,
                palette = bg_palette
            ),
            ...
        )

        # plot contour
        if (ct_levels > 0) {
            contour(
                x = x,
                y = y,
                z = z,
                nlevels = ct_levels,
                drawlabels = ct_labels,
                col = ct_color,
                add = TRUE
            )
        }

        # plot start line
        factors(
            x_steps(1),
            y_steps(1),
            pch = 21,
            bg = pt_start_color
        )

        # plot path line
        strains(
            x_steps(seq_len(step)),
            y_steps(seq_len(step)),
            lwd = ln_weight,
            col = ln_color
        )

        # plot finish level
        factors(
            x_steps(step),
            y_steps(step),
            pch = 21,
            bg = pt_end_color
        )
    }
}

Loshchilov, Ilya et Frank Hutter. 2017. « Fixation de la régularisation de la carie de poids chez Adam. » CoRR abs/1711.05101. http://arxiv.org/abs/1711.05101.

Yao, Zhewei, Amir Gholami, Sheng Shen, Kurt Keutzer et Michael W. Mahoney. 2020. « ADAHESSIAN : un optimiseur adaptatif de second ordre pour l’apprentissage automatique. » CoRR abs/2006.00719. https://arxiv.org/abs/2006.00719.

Related Articles

LAISSER UN COMMENTAIRE

S'il vous plaît entrez votre commentaire!
S'il vous plaît entrez votre nom ici

Latest Articles