[PYTHON] Liste des méthodes de descente de gradient (2020)

Personne cible

J'ai résumé la méthode de descente de gradient stochastique, qui est une méthode d'optimisation pour l'apprentissage en profondeur. Cependant, sur la base des formules et des articles résumés dans ici, Pytorch détermine les valeurs initiales et les constantes qui n'étaient pas incluses dans les articles. , Keras, Chainer, etc. sont juste résumés. Les formules, etc. sont résumées de manière similaire à une implémentation, veuillez donc jeter un coup d'œil si vous souhaitez l'implémenter vous-même.

N'hésitez pas à signaler toute erreur. De plus, si vous avez des informations sur les nouvelles méthodes d'optimisation, veuillez nous faire part des méthodes d'optimisation que vous créez!

table des matières

introduction

Tout d'abord, à titre de connaissance préalable, je vais expliquer la différence entre la méthode de descente la plus raide et la méthode de descente de gradient stochastique. Ce n'est pas grave.

Quelle est la méthode de descente la plus raide?

La méthode de descente la plus raide est une méthode de descente dans la direction qui réduit le plus la fonction de perte (la direction dans laquelle la pente est maximale) **. Elle est également appelée méthode de descente de gradient. En tant que procédure,

  1. Entrez toutes les données d'entrée dans le réseau neuronal et laissez-le calculer
  2. Calculez l'erreur à l'aide de la ** fonction de perte ** telle que ** erreur carrée ** ou ** erreur d'entropie croisée **
  3. Calculez le différentiel partiel de la fonction de perte pour chaque paramètre en utilisant la ** loi des chaînes **
  4. ** Mettre à jour les paramètres selon certaines règles **
  5. Répétez les étapes 1 à 4 jusqu'à ce que le ** changement ** de la valeur de la fonction de perte soit suffisamment petit.

La règle de chaîne est brièvement mentionnée dans ici.

** Une certaine règle ** est l'essence même de la méthode de descente de gradient. Les performances sont déterminées par cette partie. Malheureusement, il n'y a pas de meilleure méthode de descente de gradient pour tous les problèmes (du moins pour le moment). La formule de mise à jour des paramètres est pour $ t $ à un certain moment.

w_{t+1} = w_t - \eta \nabla_{w_t} \mathcal{L}(w_t)

Peut être exprimé comme Lorsqu'elle est exprimée pour chaque élément de paramètre

w_{t+1}^{(i)} = w_t^{(i)} - \eta \nabla_{w_{t}^{(i)}} \mathcal{L}(w_t)

Et ainsi de suite (bien sûr, les dimensions des paramètres sont généralement plus multidimensionnelles). Comme explication du symbole

est. La mise à jour des paramètres selon cette formule de mise à jour est répétée jusqu'à ce que la fonction de perte change à peine. L'apprentissage est considéré comme terminé lorsqu'il y a peu de changement. Notez que ce n'est pas "tant que la valeur de la fonction de perte n'est pas suffisamment petite" **. Par exemple, il est possible que l'apprentissage ne converge pas en raison de facteurs tels que des paramètres insuffisants, donc ** "jusqu'à ce que la quantité de changement soit suffisamment faible" ** l'apprentissage est effectué.

Quelle est la méthode de descente de gradient stochastique?

Avant de discuter de la méthode de descente de gradient stochastique, introduisons d'abord les inconvénients de la méthode de descente la plus raide.

L'inconvénient de la méthode de descente la plus raide est qu'elle ** met à jour les paramètres en utilisant toutes les données d'entrée **. C'est parce que cette méthode ** vous ne pouvez pas en sortir lorsque vous atteignez la valeur minimale **. Les détails sont disponibles sur ici, donc je vais l'omettre, mais en bref, si vous apprenez à utiliser toutes les données à la fois, vous tomberez facilement dans une telle situation. À propos, lorsque la dimension du paramètre devient 2 ou plus, un point plus gênant appelé ** point de selle ** apparaît. Le point de selle est "un point qui est une valeur maximale pour un paramètre mais une valeur minimale pour un autre paramètre", ce qui est très gênant. En effet, on sait que le point de selle augmente de façon exponentielle lorsque la dimension du paramètre devient multidimensionnelle. Ceci est appelé ** Malédiction dimensionnelle **, mais comme ce point de selle a un gradient de $ 0 $, l'apprentissage ne se poursuit pas. De plus, à mesure que la dimension du paramètre augmente, elle est beaucoup plus susceptible d'apparaître que les points minimum et maximum, donc elle n'est pas différente de la situation où il y a des «pièges» partout. De plus, la dimension du paramètre est essentiellement le nombre total de poids $ w $ et de biais $ b $, donc cela devient rapidement un nombre ridicule. </ font>

Conversation silencieuse ...

Dans tous les cas, les règles d'apprentissage sont des problèmes très délicats et très compliqués. Revenons au sujet. L'inconvénient de la méthode de descente la plus raide est qu'elle utilise toutes les données d'entrée, il semble donc qu'elle puisse être résolue en procédant une par une. Par conséquent, la méthode de mise à jour des paramètres en utilisant les données d'entrée une par une est appelée ** méthode de descente de gradient stochastique **. De plus, «une méthode d'apprentissage qui met à jour séquentiellement les paramètres de chaque donnée d'entrée» de cette manière est appelée ** apprentissage en ligne **. Puisque les paramètres sont mis à jour séquentiellement pour chaque donnée d'entrée, il semble qu'un modèle plus précis puisse être créé. En passant, ce qui est probabiliste, c'est que des nombres aléatoires sont utilisés pour extraire les données une par une de toutes les données d'entrée.

Bien qu'il s'agisse d'une méthode de descente de gradient probabiliste, elle présente l'inconvénient de ne pas pouvoir être parallélisée **. C'est un gaspillage de ne pas pouvoir bénéficier des progrès des traitements de parallélisation tels que les GPU dans les temps modernes. À la suite de , une méthode appelée ** apprentissage par mini-lots ** a été conçue. L'idée est simple: mettre à jour les paramètres en utilisant un certain nombre de données d'entrée à la fois (probablement 16 $ ou 32 $). Si tel est le cas, il peut être parallélisé. </ font>

Cependant, l'utilisation de l'apprentissage par mini-lots présente l'inconvénient de ** "vibrer dans les vallées acérées" **. Il semble que la seule façon de surmonter cela est de s'en remettre aux règles d'apprentissage elles-mêmes.

SGD La première est la méthode de descente de gradient la plus élémentaire. SGD: Stochastic Gradient Descent est traduit par une méthode probabiliste de descente de gradient en japonais. En d'autres termes, c'est la règle d'apprentissage que j'ai mentionnée plus tôt. En tant qu'expression mathématique, c'est la même chose que la méthode de descente la plus raide.

\begin{align}
  g_t &= \nabla_{w_t}\mathcal{L}(w_t) \\
  \Delta w_t &= - \eta g_t \\
  w_{t+1} &= w_t + \Delta w_t
\end{align}

Dans l'implémentation , $ g_t $ est calculé par la loi des chaînes comme l'erreur de flux (gradient) (TensorFlow doit être fait par différenciation automatique?). Vous n'avez donc pas à vous soucier de $ g_t $. $ \ Delta w_t $ est le nombre de mises à jour dans ce pas de temps. Multipliez simplement le gradient $ g_t $ par le taux d'apprentissage $ \ eta $. La raison pour laquelle il est négatif comme $ \ Delta w_t = - \ eta g_t $ est de l'ajouter dans la dernière formule. Il n'y a pas d'autre signification, donc si vous définissez $ w_ {t + 1} = w_t- \ Delta w_t $, vous pouvez utiliser $ \ Delta w_t = \ eta g_t $. </ font>

La valeur de l'hyper paramètre est

symbole Notation dans le code valeur
\eta eta 1e-2=10^{-2}

est.

Exemple d'implémentation

optimizers.py


class SGD(Optimizer):
    def __init__(self, eta=1e-2, *args, **kwds):
        super().__init__(*args, **kwds)
        
        self.eta = eta
    
    
    def update(self, grad_w, grad_b, *args, **kwds):
        dw = -self.eta*grad_w
        db = -self.eta*grad_b
        return dw, db

MomentumSGD Simple SGD a une convergence lente et est sujet à des problèmes tels que les vibrations et les points de selle, il doit donc être amélioré. Une façon de faire est de considérer ** Momentum **. Le moment est, en bref, l'inertie. Les vibrations sont supprimées en utilisant les informations du gradient précédent.

\begin{align}
  g_t &= \nabla_{w_t}\mathcal{L}(w_t) \\
  \Delta w_t &= \mu \Delta w_{t-1} - (1 - \mu) \eta g_t \\
  w_{t+1} &= w_t + \Delta w_t
\end{align}

Il y a pas mal de fluctuations dans la notation selon l'endroit où $ \ eta $ est multiplié et si $ 1- \ mu $ est multiplié par le deuxième terme sur le côté droit de la deuxième équation. Juste un coup d'oeil

\begin{align}
  g_t &= \nabla_{w_t}\mathcal{L}(w_t) \\
  \Delta w_t &= \mu \Delta w_{t-1} - \eta g_t \\
  w_{t+1} &= w_t + \Delta w_t
\end{align}

Ou

\begin{align}
  g_t &= \nabla_{w_t}\mathcal{L}(w_t) \\
  \Delta w_t &= \mu \Delta w_{t-1} - g_t \\
  w_{t+1} &= w_t + \eta \Delta w_t
\end{align}

Il y avait aussi. Dans cet article, il est implémenté par la première formule. La fluctuation de la notation ici peut être transformée en une expression équivalente en modifiant de manière appropriée les hyperparamètres $ \ mu $ et $ \ eta $, alors ne vous inquiétez pas. </ font>

Hyper paramètres

symbole Notation dans le code valeur
\eta eta 1e-2=10^{-2}
\mu mu 0.9
\Delta w_0 dw, db 0

Il est devenu.

Exemple d'implémentation

optimizers.py


class MSGD(Optimizer):
    def __init__(self, eta=1e-2, mu=0.9, *args, **kwds):
        super().__init__(*args, **kwds)
        
        self.eta = eta
        self.mu = mu

        #Maintenez la valeur de l'étape précédente
        self.dw = 0
        self.db = 0
    
    
    def update(self, grad_w, grad_b, *args, **kwds):
        dw = self.mu*self.dw - (1-self.mu)*self.eta*grad_w
        db = self.mu*self.db - (1-self.mu)*self.eta*grad_b
        
        #L'affectation dans la vue au lieu de la copie est due au fait que ces valeurs peuvent être utilisées
        #C'est parce qu'il ne sera pas changé.
        self.dw = dw
        self.db = db
        
        return dw, db

NAG NAG: La méthode Accelerated Gradient de Nesterov est une méthode qui modifie MSGD pour accélérer l'accélération vers la convergence. En effectuant le calcul du gradient en utilisant le ** montant de mise à jour ** précédent, il est possible d'estimer approximativement la destination par un pas de temps.

** Peut-être [cette formule](https://qiita.com/ZoneTsuyoshi/items/8ef6fa1e154d176e25b8#%E3%83%8D%E3%82%B9%E3%83%86%E3%83%AD%E3 % 83% 95% E3% 81% AE% E5% 8A% A0% E9% 80% 9F% E6% B3% 95nag-1983) Faux. ** **

Probablement la formule suivante.

\begin{align}
  g_t &= \nabla_{w_t}\mathcal{L}(w_t+\mu \Delta w_{t-1}) \\
  \Delta w_t &= \mu \Delta w_{t-1} - (1-\mu)\eta g_t \\
  w_{t+1} &= w_t + \Delta w_t
\end{align}

Outre [ici](https://qiita.com/omiita/items/1735c1d048fe5f611f80#54-%E3%83%A2%E3%83%BC%E3%83%A1%E3%83%B3%E3% 82% BF% E3% 83% A0% E3% 81% AE% E6% 94% B9% E8% 89% AF% E7% 89% 88-nag)

\begin{align}
  g_t &= \nabla_{w_t}\mathcal{L}(w_t - \mu \Delta w_{t-1}) \\
  \Delta w_t &= \mu \Delta w_{t-1} + (1-\mu)g_t \\
  w_t &= w_{t-1} - \eta \Delta w_t
\end{align}

(La notation correspond à celle de cet article). Nous faisons la même chose, et nous avons en fait mis en œuvre et essayé les deux, mais ils ont fonctionné de la même manière, donc cela ne devrait pas être un problème.

Hyper paramètres

symbole Notation dans le code valeur
\eta eta 1e-2=10^{-2}
\mu mu 0.9
\Delta w_0 dw, db 0

C'est dit.

Exemple d'implémentation

optimizers.py


class NAG(Optimizer):
    def __init__(self, eta=1e-2, mu=0.9, *args, **kwds):
        super().__init__(*args, **kwds)
        
        self.eta = eta
        self.mu = mu
        
        #Contient la valeur de l'étape précédente
        self.dw = 0
        self.db = 0
    
    
    def update(self, grad_w, grad_b, w=0, b=0, df=None, *args, **kwds):
        grad_w = df(w + self.mu*self.dw)
        grad_b = df(b + self.mu*self.db)
        
        dw = self.mu*self.dw - (1-self.mu)*self.eta*grad_w
        db = self.mu*self.db - (1-self.mu)*self.eta*grad_b
        
        #L'affectation dans la vue au lieu de la copie est due au fait que ces valeurs peuvent être utilisées
        #C'est parce qu'il ne sera pas changé.
        self.dw = dw
        self.db = db
        
        return dw, db

Cela semble difficile à utiliser en deep learning car il faut redistribuer le gradient qui a coulé dans la règle de la chaîne ...

AdaGrad Puisque MSGD n'a pas utilisé d'informations sur la direction de convergence, il y a un problème que la convergence est lente dans la direction d'un gradient doux. AdaGrad est un pionnier dans la méthode d'ajustement du taux d'apprentissage pour chaque dimension pour résoudre ce problème.

\begin{align}
  g_t &= \nabla_{w_t} \mathcal{L}(w_t) \\
  \Delta w_t &= - \cfrac{\eta}{\displaystyle\sqrt{\sum_{\tau=1}^{t}{g_{\tau}^2}}} g_t \\
  w_{t+1} &= w_t + \Delta w_t
\end{align}

En termes de mise en œuvre, la somme des carrés de la deuxième équation

\begin{align}
  gw_t = gw_{t-1} + g_t^2
\end{align}

Il économise la mémoire et le calcul en le gardant comme ça.

Hyper paramètres

symbole Notation dans le code valeur
\eta eta 1e-3=10^{-3}
gw_0 gw, gb 0

Il est devenu.

Exemple d'implémentation

optimizers.py


class AdaGrad(Optimizer):
    def __init__(self, eta=1e-3, *args, **kwds):
        super().__init__(*args, **kwds)
        
        self.eta = eta

        #Maintenez la valeur de l'étape précédente
        self.gw = 0
        self.gb = 0
    
    
    def update(self, grad_w, grad_b, *args, **kwds):
        self.gw += grad_w*grad_w
        self.gb += grad_b*grad_b
        
        dw = -self.eta*grad_w/np.sqrt(self.gw)
        db = -self.eta*grad_b/np.sqrt(self.gb)

        return dw, db

RMSprop Dans AdaGrad, il y a une pente raide puis une pente douce, et même s'il y a une autre vallée après cela, elle ne descend pas beaucoup dans la vallée. La raison en est que le carré du gradient continue de s'accumuler et que le taux d'apprentissage continue de diminuer. RMSprop a amélioré cela. En "oubliant" l'influence du gradient avec le coefficient $ \ rho $, on résout le problème de la diminution constante du taux d'apprentissage.

\begin{align}
  g_t &= \nabla_{w_t}\mathcal{L}(w_t) \\
  v_t &= \rho v_{t-1} + (1-\rho)g_t^2 \\
  \Delta w_t &= - \cfrac{\eta}{\sqrt{v_t + \varepsilon}}g_t \\
  w_{t+1} &= w_t + \Delta w_t
\end{align}

Lors de la mise en œuvre, la deuxième formule

\begin{align}
  v_t &= \rho v_{t-1} + (1-\rho)g_t^2 \\
\Leftrightarrow
  v_t &= (v_{t-1} - v_{t-1}) + \rho v_{t-1} + (1-\rho)g_t^2 \\
\Leftrightarrow
  v_t &= v_{t-1} - (1-\rho)v_{t-1} + (1-\rho)g_t^2 \\
\Leftrightarrow
  v_t &= v_{t-1} + (1-\rho)(g_t^2 - v_{t-1})
\end{align}

La formule est transformée comme suit.

Hyper paramètres

symbole Notation dans le code valeur
\eta eta 1e-2=10^{-2}
\rho rho 0.99
\varepsilon eps 1e-8=10^{-8}
v_0 vw, vb 0

Il est devenu.

Exemple d'implémentation

optimizers.py


class RMSprop(Optimizer):
    def __init__(self, eta=1e-2, rho=0.99, eps=1e-8, *args, **kwds):
        super().__init__(*args, **kwds)
        
        self.eta = eta
        self.rho = rho
        self.eps = eps
        
        #Maintenez la valeur de l'étape précédente
        self.vw = 0
        self.vb = 0
    
    
    def update(self, grad_w, grad_b, *args, **kwds):
        self.vw += (1-self.rho)*(grad_w**2 - self.vw)
        self.vb += (1-self.rho)*(grad_b**2 - self.vb)
        
        dw = -self.eta*grad_w/np.sqrt(self.vw+self.eps)
        db = -self.eta*grad_b/np.sqrt(self.vb+self.eps)
        
        return dw, db

AdaDelta RMSprop est en fait physiquement faux. En termes simples, les dimensions des deux côtés ne correspondent pas. Je voudrais dire que cela n'a pas vraiment d'importance parce que je ne résous pas un problème physique ... mais il y a quelques problèmes avec cela. Après tout, l'hyperparamètre $ \ eta $ doit être ajusté pour chaque problème. Par exemple

\begin{align}
  f(x) &= x^2 \\
  g(x) &= 10x^2
\end{align}

Pensez à adapter chacune de ces deux fonctions. A ce moment, si les règles d'apprentissage dont les dimensions ne correspondent pas sont utilisées, il devient nécessaire d'ajuster le taux d'apprentissage pour chaque fonction. En d'autres termes, vous devez ajuster les hyper paramètres. C'est une tâche fastidieuse et difficile, et je ne peux pas imaginer combien de temps serait perdu si je faisais cela pour une étude à grande échelle qui prendrait plusieurs jours pour étudier une fois. Donc, l'inadéquation des dimensions est tout à fait un problème, et c'est cet AdaDelta qui le résout.

\begin{align}
  g_t &= \nabla_{w_t}\mathcal{L}(w_t) \\
  v_t &= \rho v_{t-1} + (1-\rho) g_t^2 \\
  \Delta w_t &= - \cfrac{\sqrt{u_{t-1} + \varepsilon}}{\sqrt{v_t + \varepsilon}} g_t \\
  u_t &= \rho u_{t-1} + (1-\rho) (\Delta w_t)^2 \\
  w_{t+1} &= w_t + \Delta w_t
\end{align}

Formules 2 et 4 pour le montage

\begin{align}
  v_t &= \rho v_{t-1} + (1-\rho)g_t^2 \\
\Leftrightarrow
  v_t &= v_{t-1} + (1-\rho)(g_t^2 - v_{t-1}) \\
  u_t &= \rho u_{t-1} + (1-\rho) (\Delta w_t)^2 \\
\Leftrightarrow
  u_t &= u_{t-1} + (1-\rho) \left\{ ( \Delta w_t)^2 - u_{t-1} \right\}
\end{align}

La formule est transformée comme suit.

Hyper paramètres

symbole Notation dans le code valeur
\rho rho 0.95
\varepsilon eps 1e-6=10^{-6}
v_0 vw, vb 0
u_0 uw, ub 0

Il est devenu.

Exemple d'implémentation

optimizers.py


class AdaDelta(Optimizer):
    def __init__(self, rho=0.95, eps=1e-6, *args, **kwds):
        super().__init__(*args, **kwds)
        
        self.rho = rho
        self.eps = eps
        
        #Maintenez la valeur de l'étape précédente
        self.vw = 0
        self.vb = 0
        self.uw = 0
        self.ub = 0
    
    
    def update(self, grad_w, grad_b, *args, **kwds):
        self.vw += (1-self.rho)*(grad_w**2 - self.vw)
        self.vb += (1-self.rho)*(grad_b**2 - self.vb)
        
        dw = -grad_w*np.sqrt(self.uw+self.eps)/np.sqrt(self.vw+self.eps)
        db = -grad_b*np.sqrt(self.ub+self.eps)/np.sqrt(self.vb+self.eps)
        
        self.uw += (1-self.rho)*(dw**2 - self.uw)
        self.ub += (1-self.rho)*(db**2 - self.ub)
        
        return dw, db

Adam Inutile de dire que c'est le fameux algorithme Adam. Depuis, diverses améliorations ont été apportées, mais il semble toujours actif. Après tout, sa force réside dans sa grande polyvalence. Cependant, il n'y a aucune différence dans la version améliorée de RMSprop, il y a donc aussi le problème que les dimensions ne correspondent pas réellement. Même ainsi, par rapport à d'autres règles d'apprentissage, on pense que le problème dimensionnel est atténué en raison de la caractéristique de ** l'oubli des informations de gradient **.

\begin{align}
  g_t &= \nabla_{w_t}\mathcal{L}(w_t) \\
  m_t &= \beta_1 m_{t-1} + (1- \beta_1) g_t \\
  v_t &= \beta_2 v_{t-1} + (1- \beta_2) g_t^2 \\
  \alpha_t &= \alpha_0 \cfrac{\sqrt{1-\beta_2^t}}{1- \beta_1^2} \\
  \Delta w_t &= - \alpha_t \cfrac{m_t}{\sqrt{v_t + \varepsilon}} \\
  w_{t+1} &= w_t + \Delta w_t
\end{align}

La formule ci-dessus utilise le code amélioré trouvé dans le texte de l 'article original. Si c'est une formule telle qu'elle est

\begin{align}
  g_t &= \nabla_{w_t}\mathcal{L}(w_t) \\
  m_t &= \beta_1 m_{t-1} + (1- \beta_1) g_t \\
  v_t &= \beta_2 v_{t-1} + (1- \beta_2) g_t^2 \\
  \hat{m}_t &= \cfrac{m_t}{1-\beta_1^t} \\
  \hat{v}_t &= \cfrac{v_t}{1-\beta_2^t} \\
  \Delta w_t &= - \alpha_0 \cfrac{\hat{m}_t}{\sqrt{\hat{v}_t + \varepsilon}} \\
  w_{t+1} &= w_t + \Delta w_t
\end{align}

n'est-ce pas.

Lors du montage, formule 2 et formule 3

\begin{align}
  m_t &= \beta_1 m_{t-1} + (1-\beta_1)g_t \\
\Leftrightarrow
  m_t &= m_{t-1} + (1-\beta_1)(g_t - m_{t-1}) \\
  v_t &= \beta_2 v_{t-1} + (1-\beta_2)g_t^2 \\
\Leftrightarrow
  v_t &= v_{t-1} + (1-\beta_2)(g_t^2 - v_{t-1})
\end{align}

La formule est transformée comme suit.

Hyper paramètres

symbole Notation dans le code valeur
\alpha_0 alpha 1e-3=10^{-3}
\beta_1 beta1 0.9
\beta_2 beta2 0.999
\varepsilon eps 1e-8=10^{-8}
m_0 mw, mb 0
v_0 vw, vb 0

Il est devenu.

Exemple d'implémentation

optimizers.py


class Adam(Optimizer):
    def __init__(self, alpha=1e-3, beta1=0.9, beta2=0.999, eps=1e-8,
                 *args, **kwds):
        super().__init__(*args, **kwds)
        
        self.alpha = alpha
        self.beta1 = beta1
        self.beta2 = beta2
        self.eps = eps
        
        #Maintenez la valeur de l'étape précédente
        self.mw = 0
        self.mb = 0
        self.vw = 0
        self.vb = 0
    
    
    def update(self, grad_w, grad_b, t=1, *args, **kwds):
        self.mw += (1-self.beta1)*(grad_w - self.mw)
        self.mb += (1-self.beta1)*(grad_b - self.mb)
        self.vw += (1-self.beta2)*(grad_w**2 - self.vw)
        self.vb += (1-self.beta2)*(grad_b**2 - self.vb)
        
        alpha_t = self.alpha*np.sqrt(1-self.beta2**t)/(1-self.beta1**t)
        
        dw = -alpha_t*self.mw/(np.sqrt(self.vw+self.eps))
        db = -alpha_t*self.mb/(np.sqrt(self.vb+self.eps))
        
        return dw, db

RSMpropGraves Il s'agit d'une version améliorée de RMSprop conçue par Graves. Il semble qu'il soit utilisé dans le domaine de la reconnaissance de caractères manuscrits.

\begin{align}
  g_t &= \nabla_{w_t} (w_t) \\
  m_t &= \rho m_{t-1} + (1-\rho) g_t \\
  v_t &= \rho v_{t-1} + (1-\rho) g_t^2 \\
  \Delta w_t &= - \eta \cfrac{g_t}{\sqrt{v_t - m_t^2 + \varepsilon}} \\
  w_{t+1} &= w_t + \Delta w_t
\end{align}

Lors du montage, formule 2 et formule 3

\begin{align}
  m_t &= \rho m_{t-1} + (1-\rho)g_t \\
\Leftrightarrow
  m_t &= m_{t-1} + (1-\rho)(g_t - m_{t-1}) \\
  v_t &= \rho v_{t-1} + (1-\rho)g_t^2 \\
\Leftrightarrow
  v_t &= v_{t-1} + (1-\rho)(g_t^2 - v_{t-1})
\end{align}

La formule est transformée comme suit.

Hyper paramètres

symbole Notation dans le code valeur
\eta eta 1e-4=10^{-4}
\rho rho 0.95
\varepsilon eps 1e-4=10^{-4}
m_0 mw, mb 0
v_0 vw, vb 0

Il est devenu.

Exemple d'implémentation

optimizers.py


class RMSpropGraves(Optimizer):
    def __init__(self, eta=1e-4, rho=0.95, eps=1e-4, *args, **kwds):
        super().__init__(*args, **kwds)
        
        self.eta = eta
        self.rho = rho
        self.eps = eps

        #Maintenez la valeur de l'étape précédente
        self.mw = 0
        self.mb = 0
        self.vw = 0
        self.vb = 0
    
    
    def update(self,grad_w, grad_b, *args, **kwds):
        self.mw += (1-self.rho)*(grad_w - self.mw)
        self.mb += (1-self.rho)*(grad_b - self.mb)
        self.vw += (1-self.rho)*(grad_w**2 - self.vw)
        self.vb += (1-self.rho)*(grad_b**2 - self.vb)
        
        dw = -self.eta*grad_w/np.sqrt(self.vw - self.mw**2 + self.eps)
        db = -self.eta*grad_b/np.sqrt(self.vb - self.mb**2 + self.eps)
        
        return dw, db

SMORMS3 C'est aussi une amélioration de RMSprop. Il semble que le problème de dimension ait été surmonté par la courbure et la méthode LeCun. Je ne sais pas ce qu'est la méthode LeCun, mais il semble que LeCun préconise une technique pour la méthode de rétropropagation et annonce un modèle appelé LeNet.

\begin{align}
  g_t &= \nabla_{w_t}(w_t) \\
  s_t &= 1 + (1-\zeta_{t-1} s_{t-1}) \\
  \rho_t &= \cfrac{1}{s_t + 1} \\
  m_t &= \rho_t m_{t-1} + (1-\rho_t) g_t \\
  v_t &= \rho_t v_{t-1} + (1-\rho_t) g_t^2 \\
  \zeta_t &= \cfrac{m_t^2}{v_t + \varepsilon} \\
  \Delta w_t &= \cfrac{\textrm{min}\{ \eta, \zeta_t \}}{\sqrt{v_t + \varepsilon}} g_t \\
  w_{t+1} &= w_t + \Delta w_t
\end{align}

Formules 4 et 5 pour le montage

\begin{align}
  m_t &= \rho_t m_{t-1} + (1-\rho_t)g_t \\
\Leftrightarrow
  m_t &= m_{t-1} + (1-\rho_t)(g_t - m_{t-1}) \\
  v_t &= \rho_t v_{t-1} + (1-\rho_t)g_t^2 \\
\Leftrightarrow
  v_t &= v_{t-1} + (1-\rho_t)(g_t^2 - v_{t-1})
\end{align}

La formule est transformée comme suit.

Hyper paramètres

symbole Notation dans le code valeur
\eta eta 1e-3=10^{-3}
\varepsilon eps 1e-8=10^{-8}
\zeta_0 zetaw, zetab 0
s_t sw, sb 0
m_0 mw, mb 0
v_0 vw, vb 0

Il est devenu.

Exemple d'implémentation

optimizers.py


class SMORMS3(Optimizer):
    def __init__(self, eta=1e-3, eps=1e-8, *args, **kwds):
        super().__init__(*args, **kwds)
        
        self.eta = eta
        self.eps = eps
        
        #Maintenez la valeur de l'étape précédente
        self.zetaw = 0
        self.zetab = 0
        self.sw = 0
        self.sb = 0
        self.mw = 0
        self.mb = 0
        self.vw = 0
        self.vb = 0
    
    
    def update(self, grad_w, grad_b, *args, **kwds):
        self.sw = 1 + (1 - self.zetaw*self.sw)
        self.sb = 1 + (1 - self.zetab*self.sb)
        rhow = 1/(1+self.sw)
        rhob = 1/(1+self.sb)
        
        self.mw += (1-rhow)*(grad_w - self.mw)
        self.mb += (1-rhob)*(grad_b - self.mb)
        self.vw += (1-rhow)*(grad_w**2 - self.vw)
        self.vb += (1-rhob)*(grad_b**2 - self.vb)
        
        self.zetaw = self.mw**2 / (self.vw + self.eps)
        self.zetaw = self.mb**2 / (self.vb + self.eps)
        
        dw = -grad_w*(np.minimum(self.eta, self.zetaw)
                      /np.sqrt(self.vw + self.eps))
        db = -grad_b*(np.minimum(self.eta, self.zetab)
                      /np.sqrt(self.vb + self.eps))
        
        return dw, db

AdaMax C'est une version sans dimension de Adam.

\begin{align}
  g_t &= \nabla_{w_t} \mathcal{L} (w_t) \\
  m_t &= \beta_1 m_{t-1} + (1-\beta_1) g_t \\
  u_t &= \textrm{max}\left\{ \beta_2 u_{t-1}, |g_t| \right\} \\
  \Delta w_t &= - \cfrac{\alpha}{1-\beta_1^t}\cfrac{m_t}{u_t} \\
  w_{t+1} &= w_t + \Delta w_t
\end{align}

La deuxième formule de montage

\begin{align}
  m_t &= \rho_t m_{t-1} + (1-\rho_t)g_t \\
\Leftrightarrow
  m_t &= m_{t-1} + (1-\rho_t)(g_t - m_{t-1}) \end{align}

La formule est transformée comme suit.

Hyper paramètres

symbole Notation dans le code valeur
\alpha alpha 2e-3=2 \times 10^{-3}
\beta_1 beta1 0.9
\beta_2 beta2 0.999
m_0 mw, mb 0
u_0 uw, ub 0

Il est devenu.

Exemple d'implémentation

optimizers.py


class AdaMax(Optimizer):
    def __init__(self, alpha=2e-3, beta1=0.9, beta2=0.999,
                 *args, **kwds):
        super().__init__(*args, **kwds)
        
        self.alpha = alpha
        self.beta1 = beta1
        self.beta2 = beta2
        
        #Maintenez la valeur de l'étape précédente
        self.mw = 0
        self.mb = 0
        self.uw = 0
        self.ub = 0
    
    
    def update(self, grad_w, grad_b, t=1, *args, **kwds):
        self.mw += (1-self.beta1)*(grad_w - self.mw)
        self.mb += (1-self.beta1)*(grad_b - self.mb)
        self.uw = np.maximum(self.beta2*self.uw, np.abs(grad_w))
        self.ub = np.maximum(self.beta2*self.ub, np.abs(grad_b))
        
        alpha_t = self.alpha/(1 - self.beta1**t)
        
        dw = -alpha_t*self.mw/self.uw
        db = -alpha_t*self.mb/self.ub
        
        return dw, db
Nadam C'est une combinaison de NAG dans Adam.

\begin{align}
  g_t &= \nabla_{w_t} \mathcal{L} (w_t) \\
  m_t &= \mu m_{t-1} + (1-\mu) g_t \\
  v_t &= \nu v_{t-1} + (1-\nu) g_t^2 \\
  \hat{m}_t &= \cfrac{\mu}{1 - \mu^{t+1}} m_t + \cfrac{1-\mu}{1-\mu^t} g_t \\
  \hat{v}_t &= \cfrac{\nu}{1-\nu^t} v_t \\
  \Delta w_t &= - \alpha \cfrac{\hat{m}_t}{\sqrt{\hat{v}_t + \varepsilon}} \\
  w_{t+1} &= w_t + \Delta w_t
\end{align}

En fait, $ \ alpha $ et $ \ mu $ sont des paramètres dépendant du temps, si vrai </ font>

\begin{align}
  g_t &= \nabla_{w_t} \mathcal{L} (w_t) \\
  m_t &= \mu_t m_{t-1} + (1-\mu_t) g_t \\
  v_t &= \nu v_{t-1} + (1-\nu) g_t^2 \\
  \hat{m}_t &= \cfrac{\mu_t}{1 - \displaystyle\prod_{\tau=1}^{t+1}{\mu_{\tau}}} m_t + \cfrac{1-\mu_t}{1-\displaystyle\prod_{\tau=1}^{t}{\mu_{\tau}}} g_t \\
  \hat{v}_t &= \cfrac{\nu}{1-\nu^t} v_t \\
  \Delta w_t &= - \alpha_t \cfrac{\hat{m}_t}{\sqrt{\hat{v}_t + \varepsilon}} \\
  w_{t+1} &= w_t + \Delta w_t
\end{align}

Il est exprimé par la formule. Cependant, il semble qu'il soit corrigé dans la plupart des cas, alors je l'ai corrigé pour le moment.

Lors du montage, formule 2 et formule 3

\begin{align}
  m_t &= \mu m_{t-1} + (1-\mu)g_t \\
\Leftrightarrow
  m_t &= m_{t-1} + (1-\mu)(g_t - m_{t-1}) \\
  v_t &= \nu v_{t-1} + (1-\nu)g_t^2 \\
\Leftrightarrow
  v_t &= v_{t-1} + (1-\nu)(g_t^2 - v_{t-1})
\end{align}

La formule est transformée comme suit.

Hyper paramètres

symbole Notation dans le code valeur
\alpha alpha 2e-3=2 \times 10^{-3}
\mu mu 0.975
\nu nu 0.999
\varepsilon eps 1e-8=10^{-8}
m_0 mw, mb 0
v_0 vw, vb 0

Il est devenu.

Exemple d'implémentation

optimizers.py


class Nadam(Optimizer):
    def __init__(self, alpha=2e-3, mu=0.975, nu=0.999, eps=1e-8,
                 *args, **kwds):
        super().__init__(*args, **kwds)
        
        self.alpha = alpha
        self.mu = mu
        self.nu = nu
        self.eps = eps
        
        #Maintenez la valeur de l'étape précédente
        self.mw = 0
        self.mb = 0
        self.vw = 0
        self.vb = 0
        
    
    def update(self, grad_w, grad_b, t=1, *args, **kwds):
        self.mw += (1-self.mu)*(grad_w - self.mw)
        self.mb += (1-self.mu)*(grad_b - self.mb)
        self.vw += (1-self.nu)*(grad_w**2 - self.vw)
        self.vb += (1-self.nu)*(grad_b**2 - self.vb)
        
        mhatw = (self.mu*self.mw/(1-self.mu**(t+1)) 
                 + (1-self.mu)*grad_w/(1-self.mu**t))
        mhatb = (self.mu*self.mb/(1-self.mu**(t+1)) 
                 + (1-self.mu)*grad_b/(1-self.mu**t))
        vhatw = self.nu*self.vw/(1-self.nu**t)
        vhatb = self.nu*self.vb/(1-self.nu**t)
        
        dw = -self.alpha*mhatw/np.sqrt(vhatw + self.eps)
        db = -self.alpha*mhatb/np.sqrt(vhatb + self.eps)
        
        return dw, db

Eve C'est une version améliorée de Adam. Il accélère l'apprentissage dans les zones plates (zones à gradient presque nul). Le nom est à la mode, n'est-ce pas? Adam et Eve

\begin{align}
  g_t &= \nabla_{w_t}\mathcal{L}(w_t) \\
  m_t &= \beta_1 m_{t-1} + (1-\beta_1) g_t \\
  v_t &= \beta_2 v_{t-1} + (1-\beta_2) g_t^2 \\
  \hat{m}_t &= \cfrac{m_t}{1-\beta_1^t} \\
  \hat{v}_t &= \cfrac{v_t}{1-\beta_2^t} \\
  \textrm{if} \quad &t \gt 1 \\
  & d_t = \cfrac{\left| f_t - f_{t-1} \right|}{\textrm{min} \left\{ f_t, f_{t-1} \right\} - f^{\star}} \\
  & \hat{d}_t = \textrm{clip}\left( d_t, \left[ \frac{1}{c}, c \right] \right) \\
  & \tilde{d}_t = \beta_3 \tilde{d}_{t-1} + (1-\beta_3)\hat{d}_t \\
  \textrm{else} \\
  & \tilde{d}_t = 1 \\
  \textrm{end} &\textrm{if} \\
  \Delta w_t &= - \cfrac{\alpha}{\tilde{d}_t} \cfrac{\hat{m}_t}{\sqrt{\hat{v}_t} + \varepsilon} \\
  w_{t+1} &= w_t + \Delta w_t
\end{align}

C'est trop difficile ... J'ajouterai quelques notes sur les symboles de la formule. $ f_t $ est la valeur de la fonction objectif $ f $ au pas de temps $ t $. $ f ^ {\ star} $ est la valeur minimale de la fonction objectif. Cependant, selon l'article original, si l'erreur quadratique ou l'erreur d'entropie croisée est utilisée pour la fonction de perte, $ 0 $ est suffisant. $ \ textrm {clip} $ est une fonction qui définit les valeurs minimum et maximum et arrondit les valeurs au-dessous / au-dessus à la valeur définie. </ font>

Lors du montage, formule 2 et formule 3

\begin{align}
  m_t &= \mu m_{t-1} + (1-\mu)g_t \\
\Leftrightarrow
  m_t &= m_{t-1} + (1-\mu)(g_t - m_{t-1}) \\
  v_t &= \nu v_{t-1} + (1-\nu)g_t^2 \\
\Leftrightarrow
  v_t &= v_{t-1} + (1-\nu)(g_t^2 - v_{t-1})
\end{align}

La formule est transformée comme suit.

Hyper paramètres

symbole Notation dans le code valeur
\alpha alpha 1e-3=10^{-3}
\beta_1 beta1 0.9
\beta_2 beta2 0.999
\beta_3 beta3 0.999
c c 10
\varepsilon eps 1e-8=10^{-8}
f^{\star} fstar 0
m_0 mw, mb 0
v_0 vw, vb 0
f_t fw, fb \infty
\tilde{d}_0 dtilde_w, dtilde_b 0

Il est devenu.

Exemple d'implémentation

optimizers.py


class Eve(Optimizer):
    def __init__(self, alpha=1e-3, beta1=0.9, beta2=0.999, beta3=0.999,
                 c=10, eps=1e-8, fstar=0, *args, **kwds):
        super().__init__(*args, **kwds)
        
        self.alpha = alpha
        self.beta1 = beta1
        self.beta2 = beta
        self.beta3 = beta3
        self.c = c
        self.eps = eps
        
        #Maintenez la valeur de l'étape précédente
        self.mw = 0
        self.mb = 0
        self.vw = 0
        self.vb = 0
        self.fw = np.inf
        self.fb = np.inf
        self.fstar = fstar
        self.dtilde_w = 0
        self.dtilde_b = 0
    
    
    def update(self, grad_w, grad_b, t=1, fw=1, fb=1, *args, **kwds):
        self.mw += (1-self.beta1)*(grad_w - self.mw)
        self.mb += (1-self.beta1)*(grad_b - self.mb)
        self.vw += (1-self.beta2)*(grad_w**2 - self.vw)
        self.vb += (1-self.beta2)*(grad_b**2 - self.vb)
        
        mhatw = self.mw/(1 - self.beta1**t)
        mhatb = self.mb/(1 - self.beta1**t)
        vhatw = self.vw/(1 - self.beta2**t)
        vhatb = self.vb/(1 - self.beta2**t)
        
        if t > 1:
            d_w = (np.abs(fw-self.fstar)
                    /(np.minimum(fw, self.fw) - self.fstar))
            d_b = (np.abs(fb-self.fstar)
                    /(np.minimum(fb, self.fb) - self.fstar))
            dhat_w = np.clip(d_w, 1/self.c, self.c)
            dhat_b = np.clip(d_b, 1/self.c, self.c)
            self.dtilde_w += (1 - self.beta3)*(dhat_w - self.dtilde_w)
            self.dtilde_b += (1 - self.beta3)*(dhat_b - self.dtilde_b)
        else:
            self.dtilde_w = 1
            self.dtilde_b = 1
        
        self.fw = fw
        self.fb = fb
        
        dw = -(self.alpha*mhatw
               /(self.dtilde_w*(np.sqrt(vhatw) + self.eps)))
        db = -(self.alpha*mhatb
               /(self.dtilde_b*(np.sqrt(vhatb) + self.eps)))
        
        return dw, db

Santa-E Il incorpore la méthode Markov Chain Monte Carlo (MCMC) dans Adam et RMSprop. Parmi eux, Santa-E semble utiliser le type Euler? J'ai lu l'article original lors de sa mise en œuvre, mais honnêtement, je ne comprends pas du tout ... lol Mais même si vous ne comprenez pas, vous pouvez écrire le code si vous concevez selon la formule! Alors j'ai fait de mon mieux. Ou plutôt, j'étais en difficulté parce que je n'ai pas donné les valeurs initiales du papier original ... j'ai jeté un coup d'œil aux implémentations de Pytorch, Keras, Chainer, etc. (toutes étaient décousues ...), et à ma propre implémentation Je n'utilise que les hyper paramètres que j'estime nécessaires.

\begin{align}
  g_t &= \nabla_{w_t} \mathcal{L}(w_t) \\
  v_t &= \sigma v_{t-1} + \cfrac{1-\sigma}{N^2} g_t^2 \\
  \mathcal{G}_t &= \cfrac{1}{\sqrt{\lambda + \sqrt{v_t}}} \\
  \textrm{if} \quad &t \lt burnin \\
  & \alpha_t = \alpha_{t-1} + (u_t^2 - \cfrac{\eta}{\beta_t}) \\
  & u_t = \cfrac{\eta}{\beta_t}\cfrac{1-\frac{\mathcal{G}_{t-1}}{\mathcal{G}_t}}{u_{t-1}} + \sqrt{\cfrac{2\eta}{\beta_t}\mathcal{G}_{t-1}} \otimes \zeta_t \\
  \textrm{else} \\
  & \alpha_t = \alpha_{t-1} \\
  & u_t = 0 \\
  \textrm{end} &\textrm{if} \\
  u_t &= u_t + (1-\alpha_t) \otimes u_{t-1} - \eta \mathcal{G}_t \otimes g_t \\
  \Delta w_t &= \mathcal{G}_t \otimes u_t \\
  w_{t+1} &= w_t + \Delta w_t
\end{align}

Il y a beaucoup de produits élémentaires $ \ otimes $, mais ne vous en faites pas. C'est le produit matriciel auquel vous devez faire attention lors de sa mise en œuvre normale.

Hyper paramètres

symbole Notation dans le code valeur
\eta eta 1e-2=10^{-2}
\sigma sigma 0.95
\lambda lambda_ 1e-8=10^{-8}
burnin burnin 100
C C 5
\alpha alpha_w, alpha_b \sqrt{\eta}C
\zeta_t Générer des nombres aléatoires directement sans stocker dans des variables \mathcal{N} (0, 1)
v_0 vw, vb 0
\mathcal{G}_t gw, gb 0
u_0 uw, ub \sqrt{\eta} \mathcal{N} (0, 1)

Il est devenu. $ \ mathcal {N} (0, 1) $ est un nombre aléatoire selon la distribution normale standard. De plus, $ \ beta $ est déterminé par ʻanne_func et ʻanne_rate dans le code. L'article original ne le spécifie pas, mais comme condition, $ t \ to \ infty $ doit être $ \ beta_t \ to \ infty $. Apparemment Keras, [Pytorch](https://github.com/ReyhaneAskari/santa/blob/master/santa. py), Chainer pour $ \ beta_t = \ sqrt {t} $ Vous calculez. Mais est-ce que tout va bien si les conditions sont remplies? </ font>

Exemple d'implémentation

optimizers.py


class SantaE(Optimizer):
    def __init__(self, eta=1e-2, sigma=0.95, lambda_=1e-8,
                 anne_func=lambda t, n: t**n, anne_rate=0.5,
                 burnin=100, C=5,
                 *args, **kwds):
        """
        Args:
            eta: Learning rate
            sigma: Maybe in other cases;
                    'rho' in RMSprop, AdaDelta, RMSpropGraves.
                    'rhow' or 'rhob' in SMORMS3.
                    'beta2' in Adam, Eve.
                    'nu' in Nadam.
                   To use calculation 'v'.
            lambda_: Named 'eps'(ε) in other cases.
            anne_func: Annealing function.
                       To use calculation 'beta' at each timestep.
                       Default is 'timestep'**'annealing rate'.
                       The calculated value should be towards infinity
                       as 't' increases.
            anne_rate: Annealing rate.
                       To use calculation 'beta' at each timestep.
                       The second Argument of 'anne_func'.
            burnin: Swith exploration and refinement.
                    This should be specified by users.
            C: To calculate first 'alpha'.
        """
        super().__init__(*args, **kwds)
        
        self.eta = eta
        self.sigma = sigma
        self.lambda_ = lambda_
        self.anne_func = anne_func
        self.anne_rate = anne_rate
        self.burnin = burnin
        
        # Keep one step before and Initialize.
        self.alpha_w = np.sqrt(eta)*C
        self.alpha_b = np.sqrt(eta)*C
        self.vw = 0
        self.vb = 0
        self.gw = 0
        self.gb = 0
    
    
    def update(self, grad_w, grad_b, t=1, N=200, *args, **kwds):
        try:
            shape_w = grad_w.shape
        except:
            shape_w = (1, )
        try:
            shape_b = grad_b.shape
        except:
            shape_b = (1, )
        
        if t == 1:
            # Initialize uw, ub.
            self.uw = np.sqrt(self.eta)*np.random.randn(*shape_w)
            self.ub = np.sqrt(self.eta)*np.random.randn(*shape_b)
        
        self.vw = (self.sigma*self.vw 
                   + grad_w*grad_w * (1 - self.sigma) / N**2)
        self.vb = (self.sigma*self.vb 
                   + grad_b*grad_b * (1 - self.sigma) / N**2)
        
        gw = 1/np.sqrt(self.lambda_ + np.sqrt(self.vw))
        gb = 1/np.sqrt(self.lambda_ + np.sqrt(self.vb))
        
        beta = self.anne_func(t, self.anne_rate)
        if t < self.burnin:
            # Exploration.
            self.alpha_w += self.uw*self.uw - self.eta/beta
            self.alpha_b += self.ub*self.ub - self.eta/beta
            
            uw = (self.eta/beta * (1 - self.gw/gw)/self.uw
                  + np.sqrt(2*self.eta/beta * self.gw) 
                  * np.random.randn(*shape_w))
            ub = (self.eta/beta * (1 - self.gb/gb)/self.ub
                  + np.sqrt(2*self.eta/beta * self.gb) 
                  * np.random.randn(*shape_b))
        else:
            # Refinement.
            uw = 0
            ub = 0
        
        uw += (1 - self.alpha_w)*self.uw - self.eta*gw*grad_w
        ub += (1 - self.alpha_b)*self.ub - self.eta*gb*grad_b
        
        # Update values.
        self.uw = uw
        self.ub = ub
        self.gw = gw
        self.gb = gb
        
        dw = gw*uw
        db = gb*ub
        
        return dw, db

Santa-SSS C'est également l'algorithme décrit dans le même article. Je ne comprends pas du tout, mais je peux l'implémenter. Qu'est-ce que la géométrie d'information de Lehman ...

\begin{align}
  g_t &= \nabla_{w_t} \mathcal{L}(w_t) \\
  v_t &= \sigma v_{t-1} + \cfrac{1-\sigma}{N^2} g_t^2 \\
  \mathcal{G}_t &= \cfrac{1}{\sqrt{\lambda + \sqrt{v_t}}} \\
  \Delta w_t &= \mathcal{G}_t \otimes u_{t-1} \times 0.5 \\
  \textrm{if} \quad &t \lt burnin \\
  & \alpha_t = \alpha_{t-1} + (u_t^2 - \cfrac{\eta}{\beta_t}) \times 0.5 \\
  & u_t = e^{- 0.5\alpha_t} \otimes u_{t-1} \\
  & u_t = u_t - \mathcal{G}_t \otimes g_t \eta + \sqrt{2\mathcal{G}_t \cfrac{\eta}{\beta_t}} \otimes \zeta_t + \cfrac{\eta}{\beta_t}\cfrac{ 
1 - \frac{\mathcal{G}_{t-1}}{\mathcal{G}_t}}{u_{t-1}} \\
  & u_t = e^{-0.5\alpha_t} \otimes u_t \\
  & \alpha_t = \alpha_t + \left( u_t^2 - \cfrac{\eta}{\beta_t} \right) \times 0.5 \\
  \textrm{else} \\
  & \alpha_t = \alpha_{t-1} \\
  & u_t = e^{-0.5\alpha_t} \otimes u_{t-1} \\
  & u_t = u_t - \mathcal{G}_t \otimes g_t \eta \\
  & u_t = e^{-0.5\alpha_t} \otimes u_t \\
  \textrm{end} &\textrm{if} \\
  \Delta w_t &= \Delta w_t + \mathcal{G}_t \otimes u_t \times 0.5 \\
  w_{t+1} &= w_t + \Delta w_t
\end{align}

Hyper paramètres

symbole Notation dans le code valeur
\eta eta 1e-2=10^{-2}
\sigma sigma 0.95
\lambda lambda_ 1e-8=10^{-8}
burnin burnin 100
C C 5
\alpha alpha_w, alpha_b \sqrt{\eta}C
\zeta_t Générer des nombres aléatoires directement sans stocker dans des variables \mathcal{N} (0, 1)
v_0 vw, vb 0
\mathcal{G}_t gw, gb 0
u_0 uw, ub \sqrt{\eta} \mathcal{N} (0, 1)

Il est devenu.

Exemple d'implémentation

optimizers.py


class SantaSSS(Optimizer):
    def __init__(self, eta=1e-2, sigma=0.95, lambda_=1e-8,
                 anne_func=lambda t, n: t**n, anne_rate=0.5,
                 burnin=100, C=5,
                 *args, **kwds):
        """
        Args:
            eta: Learning rate
            sigma: Maybe in other cases;
                    'rho' in RMSprop, AdaDelta, RMSpropGraves.
                    'rhow' or 'rhob' in SMORMS3.
                    'beta2' in Adam, Eve.
                    'nu' in Nadam.
                   To use calculation 'v'.
            lambda_: Named 'eps'(ε) in other cases.
            anne_func: Annealing function.
                       To use calculation 'beta' at each timestep.
                       Default is 'timestep'**'annealing rate'.
                       The calculated value should be towards infinity
                       as 't' increases.
            anne_rate: Annealing rate.
                       To use calculation 'beta' at each timestep.
                       The second Argument of 'anne_func'.
            burnin: Swith exploration and refinement.
                    This should be specified by users.
            C: To calculate first 'alpha'.
        """
        super().__init__(*args, **kwds)
        
        self.eta = eta
        self.sigma = sigma
        self.lambda_ = lambda_
        self.anne_func = anne_func
        self.anne_rate = anne_rate
        self.burnin = burnin
        
        # Keep one step before and Initialize.
        self.alpha_w = np.sqrt(eta)*C
        self.alpha_b = np.sqrt(eta)*C
        self.vw = 0
        self.vb = 0
        self.gw = 0
        self.gb = 0
    
    
    def update(self, grad_w, grad_b, t=1, N=200, *args, **kwds):
        try:
            shape_w = grad_w.shape
        except:
            shape_w = (1, )
        try:
            shape_b = grad_b.shape
        except:
            shape_b = (1, )
        
        if t == 1:
            # Initialize uw, ub.
            self.uw = np.sqrt(self.eta)*np.random.randn(*shape_w)
            self.ub = np.sqrt(self.eta)*np.random.randn(*shape_b)
        
        self.vw = (self.sigma*self.vw 
                   + grad_w*grad_w * (1 - self.sigma) / N**2)
        self.vb = (self.sigma*self.vb 
                   + grad_b*grad_b * (1 - self.sigma) / N**2)
        
        gw = 1/np.sqrt(self.lambda_ + np.sqrt(self.vw))
        gb = 1/np.sqrt(self.lambda_ + np.sqrt(self.vb))
        
        dw = 0.5*gw*self.uw
        db = 0.5*gb*self.ub
        
        beta = self.anne_func(t, self.anne_rate)
        if t < self.burnin:
            # Exploration.
            self.alpha_w += (self.uw*self.uw - self.eta/beta)*0.5
            self.alpha_b += (self.ub*self.ub - self.eta/beta)*0.5
            
            uw = np.exp(-0.5*self.alpha_w)*self.uw
            ub = np.exp(-0.5*self.alpha_b)*self.ub
            uw += (-gw*grad_w*self.eta 
                        + np.sqrt(2*self.gw*self.eta/beta)
                        * np.random.randn(*shape_w)
                        + self.eta/beta*(1-self.gw/gw)/self.uw)
            ub += (-gb*grad_b*self.eta 
                        + np.sqrt(2*self.gb*self.eta/beta)
                        * np.random.randn(*shape_b)
                        + self.eta/beta*(1-self.gb/gb)/self.ub)
            uw *= np.exp(-0.5*self.alpha_w)
            ub *= np.exp(-0.5*self.alpha_b)
            
            self.alpha_w += (uw*uw - self.eta/beta)*0.5
            self.alpha_b += (ub*ub - self.eta/beta)*0.5
        else:
            # Refinement.
            uw = np.exp(-0.5*self.alpha_w)*self.uw
            ub = np.exp(-0.5*self.alpha_b)*self.ub
            
            uw -= gw*grad_w*self.eta
            ub -= gb*grad_b*self.eta
            
            uw *= np.exp(-0.5*self.alpha_w)
            ub *= np.exp(-0.5*self.alpha_b)
        
        # Update values.
        self.uw = uw
        self.ub = ub
        self.gw = gw
        self.gb = gb
        
        dw = gw*uw*0.5
        db = gb*ub*0.5
        
        return dw, db

GD by GD Papier J'ai lu ... Je ne sais pas du tout ...

AdaSecant Article Lire ... Je ne comprends pas la relation de moyenne mobile ...

AMSGrad Dans RMSprop, AdaDelta, Adam et Nadam, la décroissance exponentielle a été effectuée par moyenne mobile exponentielle, mais c'est la raison. Même si des informations de gradient importantes circulent, il peut ne pas être possible de converger vers la solution optimale car elle est immédiatement oubliée. AMS Grad a résolu ce problème.

\begin{align}
  g_t &= \nabla_{w_t} \mathcal{L} (w_t) \\
  m_t &= \beta_1 m_{t-1} + (1-\beta_1) g_t \\
  v_t &= \beta_2 v_{t-1} + (1-\beta_2) g_t^2 \\
  \hat{v}_t &= \textrm{max}\{ \hat{v}_{t-1}, v_t \} \\
  \Delta w_t &= - \alpha_t \cfrac{m_t}{\sqrt{\hat{v}_t + \varepsilon}} \\
  w_{t+1} &= w_t + \Delta w_t
\end{align}

Lors du montage, formule 2 et formule 3

\begin{align}
  m_t &= \beta_1 m_{t-1} + (1-\beta_1)g_t \\
\Leftrightarrow
  m_t &= m_{t-1} + (1-\beta_1)(g_t - m_{t-1}) \\
  v_t &= \beta_2 v_{t-1} + (1-\beta_2)g_t^2 \\
\Leftrightarrow
  v_t &= v_{t-1} + (1-\beta_2)(g_t^2 - v_{t-1})
\end{align}

La formule est transformée comme suit. Aussi, vaut-il mieux calculer avec $ \ alpha_t = \ frac {\ alpha} {\ sqrt {t}} $? Il y avait une description comme celle-là dans le journal.

Hyper paramètres

symbole Notation dans le code valeur
\alpha_0 alpha 1e-3=10^{-3}
\beta_1 beta1 0.9
\beta_2 beta2 0.999
\varepsilon eps 1e-8=10^{-8}
m_0 mw, mb 0
v_0 vw, vb 0
\hat{v}_0 vhatw, vhatb 0

Il est devenu. Quant à la valeur initiale $ \ alpha_0 $, il n'y avait pas de valeur spécifique dans l'article, donc Chainer Je l'ai apporté.

Exemple d'implémentation

optimizers.py


class AMSGrad(Optimizer):
    def __init__(self, alpha=1e-3, beta1=0.9, beta2=0.999, eps=1e-8,
                 *args, **kwds):
        super().__init__(*args, **kwds)
        
        self.alpha = alpha
        self.beta1 = beta1
        self.beta2 = beta2
        self.eps = eps
        
        #Maintenez la valeur de l'étape précédente
        self.mw = 0
        self.mb = 0
        self.vw = 0
        self.vb = 0
        self.vhatw = 0
        self.vhatb = 0
    
    
    def update(self, grad_w, grad_b, t=1, *args, **kwds):
        self.mw += (1-self.beta1)*(grad_w - self.mw)
        self.mb += (1-self.beta1)*(grad_b - self.mb)
        
        self.vw += (1-self.beta2)*(grad_w**2 - self.vw)
        self.vb += (1-self.beta2)*(grad_b**2 - self.vb)
        
        self.vhatw = np.maximum(self.vhatw, self.vw)
        self.vhatb = np.maximum(self.vhatb, self.vb)
        
        alpha_t = self.alpha / np.sqrt(t)
        
        dw = - alpha_t * self.mw/np.sqrt(self.vhatw + self.eps)
        db = - alpha_t * self.mb/np.sqrt(self.vhatb + self.eps)
        
        return dw, db

AdaBound AMSGrad a supprimé le problème du taux d'apprentissage inutilement élevé, mais n'a pas résolu le problème inverse de l'inutilité de petite taille. AdaBound et AMSBound ont "l'apprentissage rapide de Adam mais ne peuvent pas supprimer correctement l'erreur de généralisation" et "l'erreur de généralisation finale de SGD peut être réduite, mais d'ici là" Combinant les points "lents", il a été proposé comme une méthode d'optimisation qui se comporte comme Adam au début et SGD à la fin. AdaBound est une version améliorée d'Adam.

\begin{align}
  g_t &= \nabla_{w_t} \mathcal{L} (w_t) \\
  m_t &= \beta_1 m_{t-1} + (1-\beta_1) g_t \\
  v_t &= \beta_2 v_{t-1} + (1-\beta_2) g_t^2 \\
  \hat{\eta}_t &= \textrm{clip}\left( \cfrac{\alpha}{\sqrt{v_t}}, \left[ \eta_l(t), \eta_h(t) \right] \right) \\
  \eta_t &= \cfrac{\hat{\eta}_t}{\sqrt{t}} \\
  \Delta w_t &= - \eta_t m_t \\
  w_{t+1} &= w_t + \Delta w_t
\end{align}

Lors du montage, formule 2 et formule 3

\begin{align}
  m_t &= \beta_1 m_{t-1} + (1-\beta_1)g_t \\
\Leftrightarrow
  m_t &= m_{t-1} + (1-\beta_1)(g_t - m_{t-1}) \\
  v_t &= \beta_2 v_{t-1} + (1-\beta_2)g_t^2 \\
\Leftrightarrow
  v_t &= v_{t-1} + (1-\beta_2)(g_t^2 - v_{t-1})
\end{align}

La formule est transformée comme suit. Aussi, si le taux d'apprentissage en SGD (taux d'apprentissage à la fin) est $ \ eta $,

\begin{align}
  \eta_l(t) &= \eta \left( 1 - \cfrac{1}{(1-\beta_2)t+1} \right) \\
  \eta_u(t) &= \eta \left( 1 - \cfrac{1}{(1-\beta_2)t} \right)
\end{align}

Il semble qu'il soit calculé dans le journal, donc (probablement) je l'adopterai. réellement,

  • $ \ eta_l $ est $ 0 $ à $ t = 0 $, $ \ eta _- $ à $ t \ to \ infty $ </ font>
  • $ \ eta_u $ est $ \ infty $ à $ t = 0 $, $ \ eta_ + $ </ font> à $ t \ à \ infty $ </ font>
  • $ \ eta_ {\ pm} $ est respectivement la limite droite et la limite gauche </ font>.

Il semble que ça devrait l'être.

Hyper paramètres

symbole Notation dans le code valeur
\alpha_0 alpha 1e-3=10^{-3}
\eta eta 1e-1=10^{-1}
\beta_1 beta1 0.9
\beta_2 beta2 0.999
\varepsilon eps 1e-8=10^{-8}
m_0 mw, mb 0
v_0 vw, vb 0

Il est devenu.

Exemple d'implémentation

optimizers.py


class AdaBound(Optimizer):
    def __init__(self, alpha=1e-3, eta=1e-1, beta1=0.9, beta2=0.999,
                 eps=1e-8, *args, **kwds):
        super().__init__(*args, **kwds)
        
        self.alpha = alpha
        self.eta = eta
        self.beta1 = beta1
        self.beta2 = beta2
        self.eps = eps
        
        #Maintenez la valeur de l'étape précédente
        self.mw = 0
        self.mb = 0
        self.vw = 0
        self.vb = 0
    
    
    def update(self, grad_w, grad_b, t=1, *args, **kwds):
        self.mw += (1-self.beta1)*(grad_w - self.mw)
        self.mb += (1-self.beta1)*(grad_b - self.mb)
        self.vw += (1-self.beta2)*(grad_w**2 - self.vw)
        self.vb += (1-self.beta2)*(grad_b**2 - self.vb)
        
        etal = self.eta*(1 - 1/((1-self.beta2)**(t+1)))
        etau = self.eta*(1 + 1/((1-self.beta2)*t))
        
        etahatw_t = np.clip(self.alpha/np.sqrt(self.vw), etal, etau)
        etahatb_t = np.clip(self.alpha/np.sqrt(self.vb), etal, etau)
        
        etaw_t = etahatw_t/np.sqrt(t)
        etab_t = etahatb_t/np.sqrt(t)
        
        dw = - etaw_t*self.mw
        db = - etab_t*self.mb
        
        return dw, db

AMSBound AMSGrad a supprimé le problème du taux d'apprentissage inutilement élevé, mais n'a pas résolu le problème inverse de l'inutilité de petite taille. AdaBound et AMSBound ont "l'apprentissage rapide de Adam mais ne peuvent pas supprimer correctement l'erreur de généralisation" et "l'erreur de généralisation finale de SGD peut être réduite, mais d'ici là" Combinant les points "lents", il a été proposé comme une méthode d'optimisation qui se comporte comme Adam au début et SGD à la fin. AdaBound est une version améliorée d'Adam.

\begin{align}
  g_t &= \nabla_{w_t} \mathcal{L} (w_t) \\
  m_t &= \beta_1 m_{t-1} + (1-\beta_1) g_t \\
  v_t &= \beta_2 v_{t-1} + (1-\beta_2) g_t^2 \\
  \hat{v}_t &= \textrm{max}(\hat{v}_{t-1}, v_t) \\
  \hat{\eta}_t &= \textrm{clip}\left( \cfrac{\alpha}{\sqrt{v_t}}, \left[ \eta_l(t), \eta_h(t) \right] \right) \\
  \eta_t &= \cfrac{\hat{\eta}_t}{\sqrt{t}} \\
  \Delta w_t &= - \eta_t m_t \\
  w_{t+1} &= w_t + \Delta w_t
\end{align}

Lors du montage, formule 2 et formule 3

\begin{align}
  m_t &= \beta_1 m_{t-1} + (1-\beta_1)g_t \\
\Leftrightarrow
  m_t &= m_{t-1} + (1-\beta_1)(g_t - m_{t-1}) \\
  v_t &= \beta_2 v_{t-1} + (1-\beta_2)g_t^2 \\
\Leftrightarrow
  v_t &= v_{t-1} + (1-\beta_2)(g_t^2 - v_{t-1})
\end{align}

La formule est transformée comme suit. $ \ eta_l $ et $ \ eta_u $ sont calculés de la même manière que AdaBound. </ font>

Hyper paramètres

symbole Notation dans le code valeur
\alpha_0 alpha 1e-3=10^{-3}
\eta eta 1e-1=10^{-1}
\beta_1 beta1 0.9
\beta_2 beta2 0.999
\varepsilon eps 1e-8=10^{-8}
m_0 mw, mb 0
v_0 vw, vb 0
\hat{v}_0 vhatw, vhatb 0

Il est devenu.

Exemple d'implémentation

optimizers.py


class AMSBound(Optimizer):
    def __init__(self, alpha=1e-3, eta=1e-1, beta1=0.9, beta2=0.999,
                 eps=1e-8, *args, **kwds):
        super().__init__(*args, **kwds)
        
        self.alpha = alpha
        self.eta = eta
        self.beta1 = beta1
        self.beta2 = beta2
        self.eps = eps
        
        #Maintenez la valeur de l'étape précédente
        self.mw = 0
        self.mb = 0
        self.vw = 0
        self.vb = 0
        self.vhatw = 0
        self.vhatb = 0
    
    
    def update(self, grad_w, grad_b, t=1, *args, **kwds):
        self.mw += (1-self.beta1)*(grad_w - self.mw)
        self.mb += (1-self.beta1)*(grad_b - self.mb)
        self.vw += (1-self.beta2)*(grad_w**2 - self.vw)
        self.vb += (1-self.beta2)*(grad_b**2 - self.vb)
        self.vhatw = np.maximum(self.vhatw, self.vw)
        self.vhatb = np.maximum(self.vhatb, self.vb)
        
        etal = self.eta*(1 - 1/((1-self.beta2)*t + 1))
        etau = self.eta*(1 + 1/((1-self.beta2)*t + self.eps))
        
        etahatw_t = np.clip(self.alpha/np.sqrt(self.vhatw), etal, etau)
        etahatb_t = np.clip(self.alpha/np.sqrt(self.vhatb), etal, etau)
        
        etaw_t = etahatw_t/np.sqrt(t)
        etab_t = etahatb_t/np.sqrt(t)
        
        dw = - etaw_t*self.mw
        db = - etab_t*self.mb
        
        return dw, db

Exemple de code d'implémentation

Voici une liste d'exemples de code. Je publierai également un résultat expérimental simple. En attendant, nous pouvons ajouter une comparaison au point de selle.

optimizers.py

optimizers.py


import numpy as np


class Optimizer():
    """
Une super classe héritée de la méthode d'optimisation.
    """
    def __init__(self, *args, **kwds):
        pass
    
    
    def update(self, *args, **kwds):
        pass


class SGD(Optimizer):
    def __init__(self, eta=1e-2, *args, **kwds):
        super().__init__(*args, **kwds)
        
        self.eta = eta
    
    
    def update(self, grad_w, grad_b, *args, **kwds):
        dw = -self.eta*grad_w
        db = -self.eta*grad_b
        return dw, db


class MSGD(Optimizer):
    def __init__(self, eta=1e-2, mu=0.9, *args, **kwds):
        super().__init__(*args, **kwds)
        
        self.eta = eta
        self.mu = mu
        
        #Maintenez la valeur de l'étape précédente
        self.dw = 0
        self.db = 0
    
    
    def update(self, grad_w, grad_b, *args, **kwds):
        dw = self.mu*self.dw - (1-self.mu)*self.eta*grad_w
        db = self.mu*self.db - (1-self.mu)*self.eta*grad_b
        
        #L'affectation dans la vue au lieu de la copie est due au fait que ces valeurs peuvent être utilisées
        #C'est parce qu'il ne sera pas changé.
        self.dw = dw
        self.db = db
        
        return dw, db


class NAG(Optimizer):
    def __init__(self, eta=1e-2, mu=0.9, *args, **kwds):
        super().__init__(*args, **kwds)
        
        self.eta = eta
        self.mu = mu
        
        #Contient la valeur de l'étape précédente
        self.dw = 0
        self.db = 0
    
    
    def update(self, grad_w, grad_b, w=0, b=0, df=None, *args, **kwds):
        grad_w = df(w + self.mu*self.dw)
        grad_b = df(b + self.mu*self.db)
        
        dw = self.mu*self.dw - (1-self.mu)*self.eta*grad_w
        db = self.mu*self.db - (1-self.mu)*self.eta*grad_b
        
        #L'affectation dans la vue au lieu de la copie est due au fait que ces valeurs peuvent être utilisées
        #C'est parce qu'il ne sera pas changé.
        self.dw = dw
        self.db = db
        
        return dw, db


class AdaGrad(Optimizer):
    def __init__(self, eta=1e-3, *args, **kwds):
        super().__init__(*args, **kwds)
        
        self.eta = eta
        
        #Maintenez la valeur de l'étape précédente
        self.gw = 0
        self.gb = 0
    
    
    def update(self, grad_w, grad_b, *args, **kwds):
        self.gw += grad_w*grad_w
        self.gb += grad_b*grad_b
        
        dw = -self.eta*grad_w/np.sqrt(self.gw)
        db = -self.eta*grad_b/np.sqrt(self.gb)

        return dw, db


class RMSprop(Optimizer):
    def __init__(self, eta=1e-2, rho=0.99, eps=1e-8, *args, **kwds):
        super().__init__(*args, **kwds)
        
        self.eta = eta
        self.rho = rho
        self.eps = eps
        
        #Maintenez la valeur de l'étape précédente
        self.vw = 0
        self.vb = 0
    
    
    def update(self, grad_w, grad_b, *args, **kwds):
        self.vw += (1-self.rho)*(grad_w**2 - self.vw)
        self.vb += (1-self.rho)*(grad_b**2 - self.vb)
        
        dw = -self.eta*grad_w/np.sqrt(self.vw+self.eps)
        db = -self.eta*grad_b/np.sqrt(self.vb+self.eps)
        
        return dw, db


class AdaDelta(Optimizer):
    def __init__(self, rho=0.95, eps=1e-6, *args, **kwds):
        super().__init__(*args, **kwds)
        
        self.rho = rho
        self.eps = eps
        
        #Maintenez la valeur de l'étape précédente
        self.vw = 0
        self.vb = 0
        self.uw = 0
        self.ub = 0
    
    
    def update(self, grad_w, grad_b, *args, **kwds):
        self.vw += (1-self.rho)*(grad_w**2 - self.vw)
        self.vb += (1-self.rho)*(grad_b**2 - self.vb)
        
        dw = -grad_w*np.sqrt(self.uw+self.eps)/np.sqrt(self.vw+self.eps)
        db = -grad_b*np.sqrt(self.ub+self.eps)/np.sqrt(self.vb+self.eps)
        
        self.uw += (1-self.rho)*(dw**2 - self.uw)
        self.ub += (1-self.rho)*(db**2 - self.ub)
        
        return dw, db


class Adam(Optimizer):
    def __init__(self, alpha=1e-3, beta1=0.9, beta2=0.999, eps=1e-8,
                 *args, **kwds):
        super().__init__(*args, **kwds)
        
        self.alpha = alpha
        self.beta1 = beta1
        self.beta2 = beta2
        self.eps = eps
        
        #Maintenez la valeur de l'étape précédente
        self.mw = 0
        self.mb = 0
        self.vw = 0
        self.vb = 0
    
    
    def update(self, grad_w, grad_b, t=1, *args, **kwds):
        self.mw += (1-self.beta1)*(grad_w - self.mw)
        self.mb += (1-self.beta1)*(grad_b - self.mb)
        self.vw += (1-self.beta2)*(grad_w**2 - self.vw)
        self.vb += (1-self.beta2)*(grad_b**2 - self.vb)
        
        alpha_t = self.alpha*np.sqrt(1-self.beta2**t)/(1-self.beta1**t)
        
        dw = -alpha_t*self.mw/(np.sqrt(self.vw+self.eps))
        db = -alpha_t*self.mb/(np.sqrt(self.vb+self.eps))
        
        return dw, db


class RMSpropGraves(Optimizer):
    def __init__(self, eta=1e-4, rho=0.95, eps=1e-4, *args, **kwds):
        super().__init__(*args, **kwds)
        
        self.eta = eta
        self.rho = rho
        self.eps = eps
        
        #Maintenez la valeur de l'étape précédente
        self.mw = 0
        self.mb = 0
        self.vw = 0
        self.vb = 0
    
    
    def update(self,grad_w, grad_b, *args, **kwds):
        self.mw += (1-self.rho)*(grad_w - self.mw)
        self.mb += (1-self.rho)*(grad_b - self.mb)
        self.vw += (1-self.rho)*(grad_w**2 - self.vw)
        self.vb += (1-self.rho)*(grad_b**2 - self.vb)
        
        dw = -self.eta*grad_w/np.sqrt(self.vw - self.mw**2 + self.eps)
        db = -self.eta*grad_b/np.sqrt(self.vb - self.mb**2 + self.eps)
        
        return dw, db


class SMORMS3(Optimizer):
    def __init__(self, eta=1e-3, eps=1e-8, *args, **kwds):
        super().__init__(*args, **kwds)
        
        self.eta = eta
        self.eps = eps
        
        #Maintenez la valeur de l'étape précédente
        self.zetaw = 0
        self.zetab = 0
        self.sw = 0
        self.sb = 0
        self.mw = 0
        self.mb = 0
        self.vw = 0
        self.vb = 0
    
    
    def update(self, grad_w, grad_b, *args, **kwds):
        self.sw = 1 + (1 - self.zetaw*self.sw)
        self.sb = 1 + (1 - self.zetab*self.sb)
        rhow = 1/(1+self.sw)
        rhob = 1/(1+self.sb)
        
        self.mw += (1-rhow)*(grad_w - self.mw)
        self.mb += (1-rhob)*(grad_b - self.mb)
        self.vw += (1-rhow)*(grad_w**2 - self.vw)
        self.vb += (1-rhob)*(grad_b**2 - self.vb)
        
        self.zetaw = self.mw**2 / (self.vw + self.eps)
        self.zetaw = self.mb**2 / (self.vb + self.eps)
        
        dw = -grad_w*(np.minimum(self.eta, self.zetaw)
                      /np.sqrt(self.vw + self.eps))
        db = -grad_b*(np.minimum(self.eta, self.zetab)
                      /np.sqrt(self.vb + self.eps))
        
        return dw, db


class AdaMax(Optimizer):
    def __init__(self, alpha=2e-3, beta1=0.9, beta2=0.999,
                 *args, **kwds):
        super().__init__(*args, **kwds)
        
        self.alpha = alpha
        self.beta1 = beta1
        self.beta2 = beta2
        
        #Maintenez la valeur de l'étape précédente
        self.mw = 0
        self.mb = 0
        self.uw = 0
        self.ub = 0
    
    
    def update(self, grad_w, grad_b, t=1, *args, **kwds):
        self.mw += (1-self.beta1)*(grad_w - self.mw)
        self.mb += (1-self.beta1)*(grad_b - self.mb)
        self.uw = np.maximum(self.beta2*self.uw, np.abs(grad_w))
        self.ub = np.maximum(self.beta2*self.ub, np.abs(grad_b))
        
        alpha_t = self.alpha/(1 - self.beta1**t)
        
        dw = -alpha_t*self.mw/self.uw
        db = -alpha_t*self.mb/self.ub
        
        return dw, db


class Nadam(Optimizer):
    def __init__(self, alpha=2e-3, mu=0.975, nu=0.999, eps=1e-8,
                 *args, **kwds):
        super().__init__(*args, **kwds)
        
        self.alpha = alpha
        self.mu = mu
        self.nu = nu
        self.eps = eps
        
        #Maintenez la valeur de l'étape précédente
        self.mw = 0
        self.mb = 0
        self.vw = 0
        self.vb = 0
        
    
    def update(self, grad_w, grad_b, t=1, *args, **kwds):
        self.mw += (1-self.mu)*(grad_w - self.mw)
        self.mb += (1-self.mu)*(grad_b - self.mb)
        self.vw += (1-self.nu)*(grad_w**2 - self.vw)
        self.vb += (1-self.nu)*(grad_b**2 - self.vb)
        
        mhatw = (self.mu*self.mw/(1-self.mu**(t+1)) 
                 + (1-self.mu)*grad_w/(1-self.mu**t))
        mhatb = (self.mu*self.mb/(1-self.mu**(t+1)) 
                 + (1-self.mu)*grad_b/(1-self.mu**t))
        vhatw = self.nu*self.vw/(1-self.nu**t)
        vhatb = self.nu*self.vb/(1-self.nu**t)
        
        dw = -self.alpha*mhatw/np.sqrt(vhatw + self.eps)
        db = -self.alpha*mhatb/np.sqrt(vhatb + self.eps)
        
        return dw, db


class Eve(Optimizer):
    def __init__(self, alpha=1e-3, beta1=0.9, beta2=0.999, beta3=0.999,
                 c=10, eps=1e-8, fstar=0, *args, **kwds):
        super().__init__(*args, **kwds)
        
        self.alpha = alpha
        self.beta1 = beta1
        self.beta2 = beta2
        self.beta3 = beta3
        self.c = c
        self.eps = eps
        
        #Maintenez la valeur de l'étape précédente
        self.mw = 0
        self.mb = 0
        self.vw = 0
        self.vb = 0
        self.fw = np.inf
        self.fb = np.inf
        self.fstar = fstar
        self.dtilde_w = 0
        self.dtilde_b = 0
    
    
    def update(self, grad_w, grad_b, t=1, fw=1, fb=1, *args, **kwds):
        self.mw += (1-self.beta1)*(grad_w - self.mw)
        self.mb += (1-self.beta1)*(grad_b - self.mb)
        self.vw += (1-self.beta2)*(grad_w**2 - self.vw)
        self.vb += (1-self.beta2)*(grad_b**2 - self.vb)
        
        mhatw = self.mw/(1 - self.beta1**t)
        mhatb = self.mb/(1 - self.beta1**t)
        vhatw = self.vw/(1 - self.beta2**t)
        vhatb = self.vb/(1 - self.beta2**t)
        
        if t > 1:
            d_w = (np.abs(fw-self.fstar)
                    /(np.minimum(fw, self.fw) - self.fstar))
            d_b = (np.abs(fb-self.fstar)
                    /(np.minimum(fb, self.fb) - self.fstar))
            dhat_w = np.clip(d_w, 1/self.c, self.c)
            dhat_b = np.clip(d_b, 1/self.c, self.c)
            self.dtilde_w += (1 - self.beta3)*(dhat_w - self.dtilde_w)
            self.dtilde_b += (1 - self.beta3)*(dhat_b - self.dtilde_b)
        else:
            self.dtilde_w = 1
            self.dtilde_b = 1
        
        self.fw = fw
        self.fb = fb
        
        dw = -(self.alpha*mhatw
               /(self.dtilde_w*(np.sqrt(vhatw) + self.eps)))
        db = -(self.alpha*mhatb
               /(self.dtilde_b*(np.sqrt(vhatb) + self.eps)))
        
        return dw, db


class SantaE(Optimizer):
    def __init__(self, eta=1e-2, sigma=0.95, lambda_=1e-8,
                 anne_func=lambda t, n: t**n, anne_rate=0.5,
                 burnin=100, C=5,
                 *args, **kwds):
        """
        Args:
            eta: Learning rate
            sigma: Maybe in other cases;
                    'rho' in RMSprop, AdaDelta, RMSpropGraves.
                    'rhow' or 'rhob' in SMORMS3.
                    'beta2' in Adam, Eve.
                    'nu' in Nadam.
                   To use calculation 'v'.
            lambda_: Named 'eps'(ε) in other cases.
            anne_func: Annealing function.
                       To use calculation 'beta' at each timestep.
                       Default is 'timestep'**'annealing rate'.
                       The calculated value should be towards infinity
                       as 't' increases.
            anne_rate: Annealing rate.
                       To use calculation 'beta' at each timestep.
                       The second Argument of 'anne_func'.
            burnin: Swith exploration and refinement.
                    This should be specified by users.
            C: To calculate first 'alpha'.
        """
        super().__init__(*args, **kwds)
        
        self.eta = eta
        self.sigma = sigma
        self.lambda_ = lambda_
        self.anne_func = anne_func
        self.anne_rate = anne_rate
        self.burnin = burnin
        
        # Keep one step before and Initialize.
        self.alpha_w = np.sqrt(eta)*C
        self.alpha_b = np.sqrt(eta)*C
        self.vw = 0
        self.vb = 0
        self.gw = 0
        self.gb = 0
    
    
    def update(self, grad_w, grad_b, t=1, N=200, *args, **kwds):
        try:
            shape_w = grad_w.shape
        except:
            shape_w = (1, )
        try:
            shape_b = grad_b.shape
        except:
            shape_b = (1, )
        
        if t == 1:
            # Initialize uw, ub.
            self.uw = np.sqrt(self.eta)*np.random.randn(*shape_w)
            self.ub = np.sqrt(self.eta)*np.random.randn(*shape_b)
        
        self.vw = (self.sigma*self.vw 
                   + grad_w*grad_w * (1 - self.sigma) / N**2)
        self.vb = (self.sigma*self.vb 
                   + grad_b*grad_b * (1 - self.sigma) / N**2)
        
        gw = 1/np.sqrt(self.lambda_ + np.sqrt(self.vw))
        gb = 1/np.sqrt(self.lambda_ + np.sqrt(self.vb))
        
        beta = self.anne_func(t, self.anne_rate)
        if t < self.burnin:
            # Exploration.
            self.alpha_w += self.uw*self.uw - self.eta/beta
            self.alpha_b += self.ub*self.ub - self.eta/beta
            
            uw = (self.eta/beta * (1 - self.gw/gw)/self.uw
                  + np.sqrt(2*self.eta/beta * self.gw) 
                  * np.random.randn(*shape_w))
            ub = (self.eta/beta * (1 - self.gb/gb)/self.ub
                  + np.sqrt(2*self.eta/beta * self.gb) 
                  * np.random.randn(*shape_b))
        else:
            # Refinement.
            uw = 0
            ub = 0
        
        uw += (1 - self.alpha_w)*self.uw - self.eta*gw*grad_w
        ub += (1 - self.alpha_b)*self.ub - self.eta*gb*grad_b
        
        # Update values.
        self.uw = uw
        self.ub = ub
        self.gw = gw
        self.gb = gb
        
        dw = gw*uw
        db = gb*ub
        
        return dw, db


class SantaSSS(Optimizer):
    def __init__(self, eta=1e-2, sigma=0.95, lambda_=1e-8,
                 anne_func=lambda t, n: t**n, anne_rate=0.5,
                 burnin=100, C=5,
                 *args, **kwds):
        """
        Args:
            eta: Learning rate
            sigma: Maybe in other cases;
                    'rho' in RMSprop, AdaDelta, RMSpropGraves.
                    'rhow' or 'rhob' in SMORMS3.
                    'beta2' in Adam, Eve.
                    'nu' in Nadam.
                   To use calculation 'v'.
            lambda_: Named 'eps'(ε) in other cases.
            anne_func: Annealing function.
                       To use calculation 'beta' at each timestep.
                       Default is 'timestep'**'annealing rate'.
                       The calculated value should be towards infinity
                       as 't' increases.
            anne_rate: Annealing rate.
                       To use calculation 'beta' at each timestep.
                       The second Argument of 'anne_func'.
            burnin: Swith exploration and refinement.
                    This should be specified by users.
            C: To calculate first 'alpha'.
        """
        super().__init__(*args, **kwds)
        
        self.eta = eta
        self.sigma = sigma
        self.lambda_ = lambda_
        self.anne_func = anne_func
        self.anne_rate = anne_rate
        self.burnin = burnin
        
        # Keep one step before and Initialize.
        self.alpha_w = np.sqrt(eta)*C
        self.alpha_b = np.sqrt(eta)*C
        self.vw = 0
        self.vb = 0
        self.gw = 0
        self.gb = 0
    
    
    def update(self, grad_w, grad_b, t=1, N=200, *args, **kwds):
        try:
            shape_w = grad_w.shape
        except:
            shape_w = (1, )
        try:
            shape_b = grad_b.shape
        except:
            shape_b = (1, )
        
        if t == 1:
            # Initialize uw, ub.
            self.uw = np.sqrt(self.eta)*np.random.randn(*shape_w)
            self.ub = np.sqrt(self.eta)*np.random.randn(*shape_b)
        
        self.vw = (self.sigma*self.vw 
                   + grad_w*grad_w * (1 - self.sigma) / N**2)
        self.vb = (self.sigma*self.vb 
                   + grad_b*grad_b * (1 - self.sigma) / N**2)
        
        gw = 1/np.sqrt(self.lambda_ + np.sqrt(self.vw))
        gb = 1/np.sqrt(self.lambda_ + np.sqrt(self.vb))
        
        dw = 0.5*gw*self.uw
        db = 0.5*gb*self.ub
        
        beta = self.anne_func(t, self.anne_rate)
        if t < self.burnin:
            # Exploration.
            self.alpha_w += (self.uw*self.uw - self.eta/beta)*0.5
            self.alpha_b += (self.ub*self.ub - self.eta/beta)*0.5
            
            uw = np.exp(-0.5*self.alpha_w)*self.uw
            ub = np.exp(-0.5*self.alpha_b)*self.ub
            uw += (-gw*grad_w*self.eta 
                        + np.sqrt(2*self.gw*self.eta/beta)
                        * np.random.randn(*shape_w)
                        + self.eta/beta*(1-self.gw/gw)/self.uw)
            ub += (-gb*grad_b*self.eta 
                        + np.sqrt(2*self.gb*self.eta/beta)
                        * np.random.randn(*shape_b)
                        + self.eta/beta*(1-self.gb/gb)/self.ub)
            uw *= np.exp(-0.5*self.alpha_w)
            ub *= np.exp(-0.5*self.alpha_b)
            
            self.alpha_w += (uw*uw - self.eta/beta)*0.5
            self.alpha_b += (ub*ub - self.eta/beta)*0.5
        else:
            # Refinement.
            uw = np.exp(-0.5*self.alpha_w)*self.uw
            ub = np.exp(-0.5*self.alpha_b)*self.ub
            
            uw -= gw*grad_w*self.eta
            ub -= gb*grad_b*self.eta
            
            uw *= np.exp(-0.5*self.alpha_w)
            ub *= np.exp(-0.5*self.alpha_b)
        
        # Update values.
        self.uw = uw
        self.ub = ub
        self.gw = gw
        self.gb = gb
        
        dw = gw*uw*0.5
        db = gb*ub*0.5
        
        return dw, db


class AMSGrad(Optimizer):
    def __init__(self, alpha=1e-3, beta1=0.9, beta2=0.999, eps=1e-8,
                 *args, **kwds):
        super().__init__(*args, **kwds)
        
        self.alpha = alpha
        self.beta1 = beta1
        self.beta2 = beta2
        self.eps = eps
        
        #Maintenez la valeur de l'étape précédente
        self.mw = 0
        self.mb = 0
        self.vw = 0
        self.vb = 0
        self.vhatw = 0
        self.vhatb = 0
    
    
    def update(self, grad_w, grad_b, t=1, *args, **kwds):
        self.mw += (1-self.beta1)*(grad_w - self.mw)
        self.mb += (1-self.beta1)*(grad_b - self.mb)
        
        self.vw += (1-self.beta2)*(grad_w**2 - self.vw)
        self.vb += (1-self.beta2)*(grad_b**2 - self.vb)
        
        self.vhatw = np.maximum(self.vhatw, self.vw)
        self.vhatb = np.maximum(self.vhatb, self.vb)
        
        alpha_t = self.alpha / np.sqrt(t)
        
        dw = - alpha_t * self.mw/np.sqrt(self.vhatw + self.eps)
        db = - alpha_t * self.mb/np.sqrt(self.vhatb + self.eps)
        
        return dw, db


class AdaBound(Optimizer):
    def __init__(self, alpha=1e-3, eta=1e-1, beta1=0.9, beta2=0.999,
                 eps=1e-8, *args, **kwds):
        super().__init__(*args, **kwds)
        
        self.alpha = alpha
        self.eta = eta
        self.beta1 = beta1
        self.beta2 = beta2
        self.eps = eps
        
        #Maintenez la valeur de l'étape précédente
        self.mw = 0
        self.mb = 0
        self.vw = 0
        self.vb = 0
    
    
    def update(self, grad_w, grad_b, t=1, *args, **kwds):
        self.mw += (1-self.beta1)*(grad_w - self.mw)
        self.mb += (1-self.beta1)*(grad_b - self.mb)
        self.vw += (1-self.beta2)*(grad_w**2 - self.vw)
        self.vb += (1-self.beta2)*(grad_b**2 - self.vb)
        
        etal = self.eta*(1 - 1/((1-self.beta2)*t + 1))
        etau = self.eta*(1 + 1/((1-self.beta2)*t + self.eps))
        
        etahatw_t = np.clip(self.alpha/np.sqrt(self.vw), etal, etau)
        etahatb_t = np.clip(self.alpha/np.sqrt(self.vb), etal, etau)
        
        etaw_t = etahatw_t/np.sqrt(t)
        etab_t = etahatb_t/np.sqrt(t)
        
        dw = - etaw_t*self.mw
        db = - etab_t*self.mb
        
        return dw, db


class AMSBound(Optimizer):
    def __init__(self, alpha=1e-3, eta=1e-1, beta1=0.9, beta2=0.999,
                 eps=1e-8, *args, **kwds):
        super().__init__(*args, **kwds)
        
        self.alpha = alpha
        self.eta = eta
        self.beta1 = beta1
        self.beta2 = beta2
        self.eps = eps
        
        #Maintenez la valeur de l'étape précédente
        self.mw = 0
        self.mb = 0
        self.vw = 0
        self.vb = 0
        self.vhatw = 0
        self.vhatb = 0
    
    
    def update(self, grad_w, grad_b, t=1, *args, **kwds):
        self.mw += (1-self.beta1)*(grad_w - self.mw)
        self.mb += (1-self.beta1)*(grad_b - self.mb)
        self.vw += (1-self.beta2)*(grad_w**2 - self.vw)
        self.vb += (1-self.beta2)*(grad_b**2 - self.vb)
        self.vhatw = np.maximum(self.vhatw, self.vw)
        self.vhatb = np.maximum(self.vhatb, self.vb)
        
        etal = self.eta*(1 - 1/((1-self.beta2)*t + 1))
        etau = self.eta*(1 + 1/((1-self.beta2)*t + self.eps))
        
        etahatw_t = np.clip(self.alpha/np.sqrt(self.vhatw), etal, etau)
        etahatb_t = np.clip(self.alpha/np.sqrt(self.vhatb), etal, etau)
        
        etaw_t = etahatw_t/np.sqrt(t)
        etab_t = etahatb_t/np.sqrt(t)
        
        dw = - etaw_t*self.mw
        db = - etab_t*self.mb
        
        return dw, db
Code expérimental

opt_example.py


%matplotlib nbagg
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation


def f(x):
    return ((1/4) * x**4 - (1/6) * x**3 - (17/8) * x**2 - (15/8) * x) * 4


def df(x):
    return (x**3 - (1/2) * x**2 - (17/4) * x - 15/8) * 4


x = np.arange(-3, 3, 1e-2)
start_x = -3
start_y = f(start_x)
exact = 2.5
epoch = 100
seed=32
np.random.seed(seed=seed)
# epoch=100 est une graine=32


opt_dict = {
    "SDG": SGD(),
    "MSGD": MSGD(),
    "NAG": NAG(mu=0.95),
    "AdaGrad": AdaGrad(eta=4e-1),
    "RMSprop": RMSprop(eta=4e-2),
    "AdaDelta": AdaDelta(eps=1e-2),
    "Adam": Adam(alpha=4e-1),
    "RMSpropGraves": RMSpropGraves(eta=4e-2),
    "SMORMS3": SMORMS3(eta=4e-2),
    "AdaMax": AdaMax(),
    "Nadam": Nadam(),
    "Eve": Eve(alpha=4e-1),
    "SantaE": SantaE(eta=4e-4, burnin=0.5*epoch),
    "SantaSSS": SantaSSS(eta=4e-4, burnin=0.5*epoch),
    "AMSGrad": AMSGrad(alpha=4e-1),
    "AdaBound": AdaBound(alpha=4e-1),
    "AMSBound": AMSBound(alpha=4e-1),
}
current_x = np.full(len(opt_dict), start_x, dtype=float)
current_y = np.full(len(opt_dict), start_y, dtype=float)
err_list = np.zeros((epoch, len(opt_dict)), dtype=float)

cmap = plt.get_cmap("rainbow")
coloring = [cmap(i) for i in np.linspace(0, 1, len(opt_dict))]

fig, ax = plt.subplots(1)
ax.set_position([0.1, 0.1, 0.6, 0.8])
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.grid()
images = []
fixed_im, = ax.plot(x, f(x))
for i in range(1, epoch+1):
    im_buf = []
    for j, opt in enumerate(opt_dict):
        im, = ax.plot(current_x[j], current_y[j], 
                      marker="o", color=coloring[j])
        err_list[i-1, j] = 0.5 * (current_x[j] - exact)**2
        
        dx, _ = opt_dict[opt].update(df(current_x[j]), 1,
                                     t=i, N=epoch, 
                                     w=current_x[j],
                                     df=df, fw=f(current_x[j]))
        current_x[j] += dx
        current_y[j] = f(current_x[j])
        im_buf.append(im)
    images.append([fixed_im] + im_buf)
for j, opt in enumerate(opt_dict):
    im, = ax.plot(current_x[j], current_y[j], 
                  marker="o", color=coloring[j],
                  label=opt)
    im_buf.append(im)
images.append([fixed_im] + im_buf)

fig.suptitle("Optimiser comparison")
fig.legend(bbox_to_anchor=(0.96, 0.9))
ani = animation.ArtistAnimation(fig, images, interval=100)

fig2, ax2 = plt.subplots(1)
fig2.suptitle("Optimiser comparison of errors")
ax2.set_position([0.1, 0.1, 0.6, 0.8])
ax2.set_xlabel("x")
ax2.set_ylabel("errors")
ax2.set_yscale("log")
ax2.grid()
for j, opt in enumerate(opt_dict):
    ax2.plot(err_list[:, j], color=coloring[j], label=opt)
fig2.legend(bbox_to_anchor=(0.96, 0.9))

opt_comparison.gif opt_comparison_of_error.png

en conclusion

Cela a pris beaucoup de temps ... Si vous trouvez des erreurs, faites le moi savoir.

référence

Série d'apprentissage en profondeur