Je lis un chef-d'œuvre, ** "Deep Learning from Zero" **. Cette fois, c'est un mémo du chapitre 6. Pour exécuter le code, téléchargez le code complet depuis Github et utilisez le notebook jupyter dans ch06.
Afin d'essayer réellement la méthode d'optimisation, nous utiliserons ch06 / optimizer_compare_mnist.py
avec quelques modifications / ajouts. Le réseau est une couche 100x4 qui classe MNIST. Dans le code ci-dessous, ʻoptimizer key setting`, commentez uniquement l'optimiseur que vous n'utilisez pas et exécutez-le.
import os
import sys
sys.path.append(os.pardir) #Paramètres d'importation des fichiers dans le répertoire parent
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from common.util import smooth_curve # smooth_curve (Une fonction qui lisse la transition de la valeur de perte)importer
from common.multi_layer_net import MultiLayerNet #Importation MultiLayerNet
from common.optimizer import * #import de l'optimiseur
#Lire les données MNIST
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True)
#Réglage initial
train_size = x_train.shape[0]
batch_size = 128
max_iterations = 2001
#réglage des touches de l'optimiseur
optimizers = {}
optimizers['SGD'] = SGD()
optimizers['Momentum'] = Momentum()
optimizers['Nesterov'] = Nesterov()
optimizers['AdaGrad'] = AdaGrad()
optimizers['RMSprop'] = RMSprop()
optimizers['Adam'] = Adam()
#réseau et train_Définir la perte pour chaque clé d'optimisation
networks = {}
train_loss = {}
for key in optimizers.keys():
networks[key] = MultiLayerNet(
input_size=784, hidden_size_list=[100, 100, 100, 100],
output_size=10)
train_loss[key] = []
#Boucle d'apprentissage
for i in range(max_iterations):
#Extraire les données du mini-lot
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
#Mettez à jour le dégradé et enregistrez la perte pour chaque clé d'optimisation
for key in optimizers.keys():
grads = networks[key].gradient(x_batch, t_batch)
optimizers[key].update(networks[key].params, grads)
loss = networks[key].loss(x_batch, t_batch)
train_loss[key].append(loss)
#Affichage des pertes(Tous les 500 iter)
if i % 500 == 0:
print( "===========" + "iteration:" + str(i) + "===========")
for key in optimizers.keys():
loss = networks[key].loss(x_batch, t_batch)
print(key + ":" + str(loss))
#Dessiner un graphique
fig = plt.figure(figsize=(8,6)) #Spécification de la taille du graphique
markers = {"SGD": "o", "Momentum": "x", "Nesterov": "^", "AdaGrad": "s", "RMSprop":"*", "Adam": "D", }
x = np.arange(max_iterations)
for key in optimizers.keys():
plt.plot(x, smooth_curve(train_loss[key]), marker=markers[key], markevery=100, label=key)
plt.xlabel("iterations")
plt.ylabel("loss")
plt.ylim(0, 1)
plt.legend()
plt.show()
3.SGD
Le modèle de base de la méthode d'optimisation est «SGD», qui a été utilisé jusqu'au chapitre 5.
En regardant l'implémentation de SGD
dans common / optimizer.py
,
class SGD:
def __init__(self, lr=0.01):
self.lr = lr
def update(self, params, grads):
for key in params.keys():
params[key] -= self.lr * grads[key]
4.Momentum L'optimisation de SGD prend du temps, en particulier dans les premiers stades, car les mises à jour du gradient sont toujours constantes. C'est là qu'intervient «Momentum».
«Momentum» est une méthode pour augmenter progressivement le degré de mise à jour du gradient tandis que la direction du gradient ne change pas. C'est juste une image de la balle roulant en fonction de la pente du sol, et $ \ alpha = 0,9 $ peut être considéré comme le frottement et la résistance de l'air du sol.
Pour exprimer l'image un peu plus concrètement, par exemple, en supposant que les résultats des quatre calculs de gradient sont les mêmes pour $ \ frac {\ partial L} {\ partial W} $, v est
Vous pouvez voir que le degré de mise à jour du dégradé augmente progressivement jusqu'à -1,0, -1,9, -2,71, -3,439. En regardant l'implémentation de
Momentum
dans common / optimizer.py
,
class Momentum:
def __init__(self, lr=0.01, momentum=0.9):
self.lr = lr
self.momentum = momentum
self.v = None
def update(self, params, grads):
if self.v is None:
self.v = {}
for key, val in params.items():
self.v[key] = np.zeros_like(val)
for key in params.keys():
self.v[key] = self.momentum*self.v[key] - self.lr*grads[key]
params[key] += self.v[key]
5.Nesterov
L'élan est susceptible de dépasser lorsque la direction du gradient est inversée après avoir augmenté le degré de mise à jour du gradient. C'est là que le Nesterov
(également connu sous le nom de Momentum de Nestrov), qui est une modification partielle de l'élan, est introduit.
Nesterov
change la position où le gradient est calculé à la position après la mise à jour du gradient, une longueur d'avance, au lieu de la position actuelle. Bien sûr, je ne connais pas la position exacte après la mise à jour du dégradé, mais je la remplacerai en trouvant le v approximatif en utilisant le dégradé actuel. On peut s'attendre à ce que cela supprime le dépassement.
En regardant l'implémentation dans
common / optimizer.py
,
class Nesterov:
def __init__(self, lr=0.01, momentum=0.9):
self.lr = lr
self.momentum = momentum
self.v = None
def update(self, params, grads):
if self.v is None:
self.v = {}
for key, val in params.items():
self.v[key] = np.zeros_like(val)
for key in params.keys():
self.v[key] *= self.momentum
self.v[key] -= self.lr * grads[key]
params[key] += self.momentum * self.momentum * self.v[key]
params[key] -= (1 + self.momentum) * self.lr * grads[key]
Comparons maintenant SGD
, Momentum
, Nesterov
.
Par rapport à «SGD», «Momentum» et «Nesterov» sont extrêmement améliorés à la fois en termes de vitesse de réduction de perte initiale et de taux de perte final. «Nesterov» est un pas mieux que «Momentum», et il semble que la variation de la perte soit légèrement inférieure.
6.AdaGrad ʻAda Grad` introduit deux idées importantes.
Le premier est l'idée de «taux d'apprentissage adaptatif» (adaptatif), qui stipule qu'un grand nombre de paramètres doivent être optimisés en fonction des paramètres, plutôt que d'être optimisés en une seule fois.
La seconde est l'idée de "décomposer le coefficient d'apprentissage" pour augmenter le taux d'apprentissage au début de l'apprentissage et diminuer le taux d'apprentissage au fur et à mesure que l'apprentissage progresse pour promouvoir l'apprentissage efficacement.
Le taux d'apprentissage est corrigé en accumulant la somme des carrés du gradient sur h et en la multipliant par $ \ frac {1} {\ sqrt {h} + \ epsilon} $ lors de la mise à jour du gradient. En d'autres termes, le taux d'apprentissage des paramètres fortement mis à jour est progressivement réduit.
Au fait, $ \ epsilon $ est un très petit nombre (pour éviter la division par zéro). En regardant l'implémentation de common / optimizer.py
,
class AdaGrad:
def __init__(self, lr=0.01):
self.lr = lr
self.h = None
def update(self, params, grads):
if self.h is None:
self.h = {}
for key, val in params.items():
self.h[key] = np.zeros_like(val)
for key in params.keys():
self.h[key] += grads[key] * grads[key]
params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)
7.RMSprop
RMSprop
est une version améliorée de ʻAdaGrad` qui stocke la ** moyenne mobile exponentielle ** du carré du gradient en h (oubliez progressivement les résultats des calculs passés et intégrez de nouveaux résultats de calcul) et mettez à jour le gradient. Le taux d'apprentissage est corrigé en multipliant $ \ frac {1} {\ sqrt {h} + \ epsilon} $ lors de cette opération.
En regardant l'implémentation de common / optimizer.py
,
class RMSprop:
def __init__(self, lr=0.01, decay_rate = 0.99):
self.lr = lr
self.decay_rate = decay_rate
self.h = None
def update(self, params, grads):
if self.h is None:
self.h = {}
for key, val in params.items():
self.h[key] = np.zeros_like(val)
for key in params.keys():
self.h[key] *= self.decay_rate
self.h[key] += (1 - self.decay_rate) * grads[key] * grads[key]
params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)
8.Adam
ʻAdam est le résultat de l'idée de tirer le meilleur parti de
Momentum et de ʻAdaGrad
.
m
est comme la moyenne mobile exponentielle de Momentum
, et v
est ʻAdaGrad` lui-même. Après cela, "m" et "v" sont utilisés dans une large mesure tandis que l'itère est petit, et le degré d'utilisation est affaibli lorsque l'itère augmente. La mise en œuvre se fait en modifiant légèrement la formule comme indiqué ci-dessous.
En regardant l'implémentation de common / optimizer.py
,
class Adam:
def __init__(self, lr=0.001, beta1=0.9, beta2=0.999):
self.lr = lr
self.beta1 = beta1
self.beta2 = beta2
self.iter = 0
self.m = None
self.v = None
def update(self, params, grads):
if self.m is None:
self.m, self.v = {}, {}
for key, val in params.items():
self.m[key] = np.zeros_like(val)
self.v[key] = np.zeros_like(val)
self.iter += 1
lr_t = self.lr * np.sqrt(1.0 - self.beta2**self.iter) / (1.0 - self.beta1**self.iter)
for key in params.keys():
self.m[key] += (1 - self.beta1) * (grads[key] - self.m[key])
self.v[key] += (1 - self.beta2) * (grads[key]**2 - self.v[key])
params[key] -= lr_t * self.m[key] / (np.sqrt(self.v[key]) + 1e-7)
Comparons maintenant ʻAdaGrad,
RMSprop, ʻAdam
.
![Capture d'écran 2020-05-05 19.06.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209705/4ba770be-3cfe-15a1-16bb- 030899994296.png)
Il n'y a pas de méthode d'optimisation qui donne les meilleurs résultats pour n'importe quelle tâche. Cette fois, «Ada Grad» a donné les meilleurs résultats.
La correction du taux d'apprentissage par la moyenne mobile exponentielle du carré du gradient de «RMSprop» était excessive pour cette tâche, et l'amplitude de la perte est devenue très grande, et le taux de perte final est également devenu élevé.
«Adam» est censé toujours montrer des performances stables avec la méthode d'optimisation qui doit être utilisée en premier pour le moment. Cette tâche n'est pas la meilleure, mais elle montre une transition honorifique de réduction des pertes des étudiants.
Si la valeur initiale du poids est zéro, toutes les valeurs de poids seront mises à jour uniformément et l'expressivité sera perdue. Le type d'initialisation qui convient dépend du type de fonction d'activation.
Si la fonction sigmoïde, la fonction tanh, etc. sont symétriques et que la zone centrale peut être considérée comme une fonction linéaire, la distribution gaussienne avec $ \ sqrt {\ frac {1} {n}} $ comme écart type appelé «initialisation de Xavier» est dite optimale. Il est
Lors de l'utilisation de ReUL, on dit que la distribution gaussienne avec $ \ sqrt {\ frac {2} {n}} $ comme écart-type appelé «initialisation He» est optimale.
10.Dropout «Dropout» est une méthode pour supprimer le surapprentissage même dans un réseau hautement expressif en déconnectant des neurones sélectionnés au hasard pour chaque iter pendant l'apprentissage. En regardant le code d'implémentation,
class Dropout:
def __init__(self, dropout_ratio=0.5):
self.dropout_ratio = dropout_ratio
self.mask = None
#Propagation vers l'avant
def forward(self, x, train_flg=True):
#Au moment de l'apprentissage, créez un masque qui détermine s'il faut ou non se connecter et multipliez le signal de propagation vers l'avant par un masque.
if train_flg:
self.mask = np.random.rand(*x.shape) > self.dropout_ratio
return x * self.mask
#N'appliquez pas de masque lors de l'inférence et couvrez tout le signal(1 - dropout_ratio)Multiplier
else:
return x * (1.0 - self.dropout_ratio)
#Multipliez le signal de rétropropagation par masque
def backward(self, dout):
return dout * self.mask
A chaque session d'apprentissage, un nombre aléatoire uniforme et un seuil (dropout_ratio) sont utilisés pour créer un masque (True pour connectable, False pour non connectable) qui détermine s'il faut ou non se connecter. Masquez ensuite le signal de propagation vers l'avant (x * self.mask). De même, masquez le signal lors de la rétro-propagation.
Dans une image concrète, ça ressemble à ça,
Au moment de l'inférence, le signal entier est multiplié par (1 - dropout_ratio) sans masquage, et seule l'amplitude du signal entier est ajustée.
11.Batch Normalization
La normalisation par lots '' est une méthode annoncée en 2015, qui améliore la vitesse de convergence d'apprentissage, réduit le besoin de
suppression '' et pondère initialement en normalisant chaque mini-lot de sorte qu'il ait une moyenne de 0 et une distribution de 1. Vous pouvez obtenir des effets tels que la réduction du besoin de conversion (robuste pour l'initialisation du poids). Voici l'algorithme.
12.Weight decay La décroissance de poids est une méthode de suppression du surapprentissage en imposant une pénalité pour avoir un poids important dans le processus d'apprentissage.
Lorsque le poids est W, en ajoutant $ \ frac {1} {2} \ lambda W ^ 2 $ à la fonction de perte, il est possible de supprimer l'augmentation du poids W, et cette méthode est ** L2 régularisée ** Est appelé. Si vous augmentez $ \ lambda $ ici, vous pouvez augmenter la pénalité. ![Capture d'écran 2020-05-06 14.31.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209705/ddbf678e-4b99-e2f8-a182- 76a1329147c5.png)
Au fait, dans la fonction de perte
Recommended Posts