[PYTHON] Introduction au Deep Learning ~ Dropout Edition ~

Aperçu

L'article précédent était ici Cet article décrit l'abandon, qui est l'une des méthodes classiques de suppression du surapprentissage. Bien qu'il s'agisse d'une méthode simple, son effet peut être déduit du fait qu'elle a été utilisée depuis qu'elle a été proposée. À propos, il semble qu'il n'y ait aucune explication théorique qui puisse supprimer le surapprentissage. (Peut-être manque de recherche ...) Il existe de nombreuses raisons possibles, cependant ~

table des matières

Qu'est-ce qu'un abandon?

Dropout: Dropout a été proposé en 2012 comme un moyen de contrôler le surapprentissage et a également été adopté par le bien connu ** AlexNet **. Le contour est simplement "exclure la sortie de chaque couche de la couche entièrement connectée avec une certaine probabilité $ ratio $ pendant l'apprentissage". Je suis surpris que le surapprentissage puisse être supprimé avec juste cela. neural_net.png neural_net_dropout.png Je ne pense pas qu'il y ait une explication théorique sur la raison pour laquelle le surapprentissage est supprimé, mais il existe différentes théories. L'un d'eux est l'apprentissage d'ensemble.

Relation avec l'apprentissage d'ensemble

En premier lieu, l'apprentissage d'ensemble est une technologie qui réalise une grande précision en intégrant plusieurs apprenants faibles. Les abandons sont particulièrement préoccupants pour la technique d'ensachage. Pour plus d'informations [ici](https://qiita.com/kuroitu/items/57425380546f7b9ed91c#%E3%82%A2%E3%83%B3%E3%82%B5%E3%83%B3%E3%83%96 % E3% 83% AB% E5% AD% A6% E7% BF% 92), veuillez donc vous y référer. Dans tous les cas, les abandons entraînent plusieurs modèles en même temps, c'est donc une sorte d'ensachage. bagging_dropout.png Le fait que les neurones désactivants soient différents à chaque fois qu'ils sont entraînés signifie qu'ils apprennent différents modèles pour chaque motif, ce qui signifie qu'ils apprennent différents modèles.

À partir de là, on considère que l'apprentissage avec plusieurs apprenants = apprentissage d'ensemble est effectué de manière simulée. L'une des caractéristiques de l'ensachage est que le résultat de l'entraînement a un biais élevé et une faible variance, de sorte qu'il ne s'adapte pas parfaitement aux données d'entraînement même s'il est organisé dans une certaine mesure. Par conséquent, on pense que le surapprentissage est supprimé.

Voir l'implémentation de la théorie

Maintenant, jetons un bref regard sur la mise en œuvre de la théorie. Comme mentionné précédemment, la couche d'exclusion est facile à mettre en œuvre car elle "coupe uniquement la sortie de chaque couche de la couche entièrement connectée avec une certaine probabilité $ ratio $ pendant l'entraînement". Cependant, comme les passionnés l'ont peut-être remarqué, l'accent est mis sur "** lors de l'apprentissage **". Alors, que se passe-t-il lorsque vous avez fini d'apprendre et que vous commencez à raisonner?

Il n'abandonne pas pendant l'inférence, donc tous les neurones restent actifs. Comme vous pouvez l'imaginer, la sortie "** densité **" au moment de l'apprentissage et au moment de l'inférence sera différente. train_vs_predict.png Pour résoudre ce problème, il existe une méthode de multiplication de la sortie par $ (1 --ratio) $ au moment de l'inférence.

Jetons un coup d'œil à la formule. En supposant que la sortie avant l'application de la suppression est $ y $ et la sortie après l'application est $ \ hat {y} $, la valeur attendue de la sortie au moment de l'apprentissage est

\mathbb{E}[\hat{y}] = \underbrace{(1 - ratio) y}_{Valeur attendue des neurones actifs} + \underbrace{ratio \times 0}_{非Valeur attendue des neurones actifs} = (1 - ratio)y

On dirait. Par contre, au moment de l'inférence, le taux de troncature $ ratio $ est 0 et la valeur attendue de la sortie est

\mathbb{E}[\hat{y}] = \underbrace{(1 - 0) y}_{Valeur attendue des neurones actifs} + \underbrace{0 \times 0}_{非Valeur attendue des neurones actifs} = y

Et la sortie $ \ frac {1} {1 --ratio} $ times est "sombre". (Notez que $ ratio $ est $ 0 \ le ratio \ lt 1 $ ici) L'idée est d'éliminer ce décalage en multipliant la sortie au moment de l'inférence par $ (1 --ratio) $ afin d'ajuster cette "obscurité".

(1 - ratio) \mathbb{E}[\hat{y}] = (1 - ratio) \left\{ \underbrace{(1 - 0) y}_{Valeur attendue des neurones actifs} + \underbrace{0 \times 0}_{非Valeur attendue des neurones actifs} \right\}= (1 - ratio)y

Mais c'est une méthode simple et dangereuse. Bien sûr, vous pouvez étudier sans aucun problème tel quel, et vous pouvez faire des inférences sans aucun problème. Cependant, cette méthode comporte le risque de "modifier la sortie de l'inférence". Je ne pense pas que ce soit un problème dans de nombreux cas, mais la sortie de la phase d'inférence est utilisée pour évaluer la précision du modèle, il est donc préférable de la toucher.

Ensuite, il existe une méthode pour "aligner la sortie au moment de l'apprentissage avec celle au moment de l'inférence". En d'autres termes, la densité est "assombrie" en divisant la sortie pendant l'entraînement par $ (1 --ratio) $.

\cfrac{1}{1 - ratio}\mathbb{E}[\hat{y}] = \cfrac{1}{1 - ratio} \left\{ \underbrace{(1 - ratio) y}_{Valeur attendue des neurones actifs} + \underbrace{ratio \times 0}_{非Valeur attendue des neurones actifs} \right\} = y

En faisant cela, la valeur attendue de la sortie sera la même au moment de l'apprentissage et au moment de l'inférence, il n'est donc pas nécessaire de toucher la sortie au moment de l'inférence. Cette méthode de jeu avec la sortie pendant l'apprentissage est appelée la ** méthode d'abandon inverse ** par opposition à la méthode d'abandon normal.

Implémentation de la couche d'abandon

Maintenant, implémentons la couche d'abandon en utilisant la méthode d'abandon inverse.

dropout.py


class Dropout(BaseLayer):
    def __init__(self, *args,
                 mode="cpu", ratio=0.25,
                 prev=1, n=None, **kwds):
        if not n is None:
            raise KeyError("'n' must not be specified.")
        super().__init__(*args, mode=mode, **kwds)

        self.ratio = ratio
        self.mask = self.calculator.zeros(prev)
        self.prev = prev
        self.n = prev
    

    def forward(self, x, *args, train_flag=True, **kwds):
        if train_flag:
            self.mask = self.calculator.random.randn(self.prev)
            self.mask = self.calculator.where(self.mask >= self.ratio, 1, 0)
            return x*self.mask/(1- self.ratio)
        else:
            return x
    

    def backward(self, grad, *args, **kwds):
        return grad*self.mask/(1 - self.ratio)
    

    def update(self, *args, **kwds):
        pass

La mise en œuvre est simple, n'est-ce pas? Le nombre de neurones dans la sortie doit correspondre à la couche précédente, il est donc repoussé lors de la phase d'initialisation.

Pour la propagation directe, pendant l'apprentissage, une variable appelée «masque» est utilisée pour sélectionner au hasard les neurones qui abandonnent. De plus, le décrochage inversé est réalisé en divisant par $ (rapport 1) $ au moment de la production. Par conséquent, c'est une implémentation qui passe telle qu'elle est au moment de l'inférence.

Comme la rétro-propagation n'est utilisée que pendant l'apprentissage, il n'est pas nécessaire de séparer le traitement comme la propagation vers l'avant. Le même «masque» que dans la propagation vers l'avant est multiplié par le produit de l'élément de sorte que seuls les neurones actifs se propagent vers l'arrière, et il est également divisé par $ (1-ratio) $.

Il n'y a pas de paramètres à apprendre dans la couche d'exclusion, donc l'implémentation est réussie.

De plus, lors de l'ajout d'une couche d'exclusion, ajoutez une couche d'exclusion à la classe _TypeManager, et calculez l'erreur dans la fonction training dans l'implémentation de la classe Trainer et la fonction forward utilisée dans la fonction prédire. Ajoutons train_flag à.

type_manager.py et trainer.py

type_manager.py


class _TypeManager():
    """
Classe de gestionnaire pour les types de couches
    """
    N_TYPE = 5  #Nombre de types de couches

    BASE = -1
    MIDDLE = 0  #Numérotation des couches intermédiaires
    OUTPUT = 1  #Numérotation des couches de sortie
    DROPOUT = 2    #Numérotation des couches de suppression
    CONV = 3    #Numérotation des couches de pliage
    POOL = 4    #Numérotation de la couche de pooling
    
    REGULATED_DIC = {"Middle": MiddleLayer,
                     "Output": OutputLayer,
                     "Dropout": Dropout,
                     "Conv": ConvLayer,
                     "Pool": PoolingLayer,
                     "BaseLayer": None}
    
    
    @property
    def reg_keys(self):
        return list(self.REGULATED_DIC.keys())
    
    
    def name_rule(self, name):
        name = name.lower()
        if "middle" in name or name == "mid" or name == "m":
            name = self.reg_keys[self.MIDDLE]
        elif "output" in name or name == "out" or name == "o":
            name = self.reg_keys[self.OUTPUT]
        elif "dropout" in name or name == "drop" or name == "d":
            name = self.reg_keys[self.DROPOUT]
        elif "conv" in name or name == "c":
            name = self.reg_keys[self.CONV]
        elif "pool" in name or name == "p":
            name = self.reg_keys[self.POOL]
        else:
            raise UndefinedLayerError(name)
        
        return name

trainer.py


import time


import matplotlib.pyplot as plt
import matplotlib.animation as animation


softmax = type(get_act("softmax"))
sigmoid = type(get_act("sigmoid"))


class Trainer(Switch):
    def __init__(self, x, y, *args, mode="cpu", **kwds):
        #Si le GPU est disponible
        if not mode in ["cpu", "gpu"]:
            raise KeyError("'mode' must select in {}".format(["cpu", "gpu"])
                         + "but you specify '{}'.".format(mode))
        self.mode = mode.lower()

        super().__init__(*args, mode=self.mode, **kwds)

        self.x_train, self.x_test = x
        self.y_train, self.y_test = y
        self.x_train = self.calculator.asarray(self.x_train)
        self.x_test = self.calculator.asarray(self.x_test)
        self.y_train = self.calculator.asarray(self.y_train)
        self.y_test = self.calculator.asarray(self.y_test)
    
        self.make_anim = False
    

    def forward(self, x, train_flag=True, lim_memory=10):
        def propagate(x, train_flag=True):
            x_in = x
            n_batch = x.shape[0]
            switch = True
            for ll in self.layer_list:
                if switch and not self.is_CNN(ll.name):
                    x_in = x_in.reshape(n_batch, -1)
                    switch = False
                x_in = ll.forward(x_in, train_flag=train_flag)
        
        #Parce que la méthode de propagation directe est également utilisée pour le calcul d'erreur et la prédiction de données inconnues
        #La capacité de mémoire peut être importante
        if self.calculator.prod(
            self.calculator.asarray(x.shape))*8/2**20 >= lim_memory:
            #Nombre à virgule flottante double précision(8byte)À 10 Mo(=30*2**20)Plus que
            #Lorsque vous utilisez de la mémoire, divisez-la en 5 Mo ou moins et exécutez
            n_batch = int(5*2**20/(8*self.calculator.prod(
                                     self.calculator.asarray(x.shape[1:]))))
            if self.mode == "cpu":
                y = self.calculator.zeros((x.shape[0], lm[-1].n))
            elif self.mode == "gpu":
                y = self.calculator.zeros((x.shape[0], lm[-1].n))
            n_loop = int(self.calculator.ceil(x.shape[0]/n_batch))
            for i in range(n_loop):
                propagate(x[i*n_batch : (i+1)*n_batch], train_flag=train_flag)
                y[i*n_batch : (i+1)*n_batch] = lm[-1].y.copy()
            lm[-1].y = y
        else:
            #Sinon, exécutez normalement
            propagate(x, train_flag=train_flag)

・
・
・
    
    def training(self, epoch, n_batch=16, threshold=1e-8,
                 show_error=True, show_train_error=False, **kwds):
        if show_error:
            self.error_list = []
        if show_train_error:
            self.train_error_list = []
        if self.make_anim:
            self.images = []
        self.n_batch = n_batch
        
        n_train = self.x_train.shape[0]//n_batch
        n_test = self.x_test.shape[0]
        
        #Commencer à apprendre
        if self.mode == "gpu":
            cp.cuda.Stream.null.synchronize()
        start_time = time.time()
        lap_time = -1
        error = 0
        error_prev = 0
        rand_index = self.calculator.arange(self.x_train.shape[0])
        for t in range(1, epoch+1):
            #Création de scène
            if self.make_anim:
                self.make_scene(t, epoch)
            
            #Calcul des erreurs d'entraînement
            if show_train_error:
                self.forward(self.x_train[rand_index[:n_test]],
                             train_flag=False)
                error = lm[-1].get_error(self.y_train[rand_index[:n_test]])
                self.train_error_list.append(error)
            
            #Calcul d'erreur
            self.forward(self.x_test, train_flag=False)
            error = lm[-1].get_error(self.y_test)
            if show_error:
                self.error_list.append(error)

・
・
・
    
    def predict(self, x=None, y=None, threshold=0.5):
        if x is None:
            x = self.x_test
        if y is None:
            y = self.y_test
        
        self.forward(x, train_flag=False)
        self.y_pred = self.pred_func(self[-1].y, threshold=threshold)
        y = self.pred_func(y, threshold=threshold)
        print("correct:", y[:min(16, int(y.shape[0]*0.1))])
        print("predict:", self.y_pred[:min(16, int(y.shape[0]*0.1))])
        print("accuracy rate:",
              100*self.calculator.sum(self.y_pred == y, 
                                      dtype=int)/y.shape[0], "%",
              "({}/{})".format(self.calculator.sum(self.y_pred == y, dtype=int),
                               y.shape[0]))
        if self.mode == "cpu":
            return self.y_pred
        elif self.mode == "gpu":
            return self.y_pred.get()

Expérience

Expérimentons. Cependant, l'apprentissage avec l'ensemble de données MNIST ne provoque pas beaucoup de surentraînement, donc l'effet peut sembler faible. L'expérience est menée sur Google Colaboratory. J'exécute en mode GPU car j'utilise l'ensemble de données MNIST de Keras, mais cela prend encore environ 20 minutes pour 200 époques. Le code peut être exécuté tel quel en passant de github à Google Colaboratory.

test.py


%matplotlib inline
#Créer une couche de convolution et une couche de sortie
M, F_h, F_w = 10, 3, 3
lm = LayerManager((x_train, x_test), (t_train, t_test), mode="gpu")
lm.append(name="c", I_shape=(C, I_h, I_w), F_shape=(M, F_h, F_w), pad=1)
lm.append(name="p", I_shape=lm[-1].O_shape, pool=2)
lm.append(name="m", n=100, opt="eve")
lm.append(name="d", ratio=0.5)
lm.append(name="o", n=n_class, act="softmax", err_func="Cross")

#Apprendre
epoch = 200
threshold = 1e-8
n_batch = 128
lm.training(epoch, threshold=threshold, n_batch=n_batch, show_train_error=True)

#Prédire
print("training dataset")
_ = lm.predict(x=lm.x_train, y=lm.y_train)
print("test dataset")
y_pred = lm.predict()

dropout_normal_comparison.png Un peu de travail pénible est nécessaire pour illustrer les résultats expérimentaux. Tout d'abord, exécutez la cellule de code de test sans la couche de suppression, puis exécutez le code suivant préparé dans une autre cellule.

get_error.py


err_list = lm.error_list

Ensuite, exécutez la cellule de code de test avec la couche d'exclusion et exécutez le code suivant préparé dans une autre cellule.

get_drop_error.py


drop_error_list = lm.error_list

Après la configuration ci-dessus, préparez le code suivant dans une autre cellule et exécutez-le.

plot.py


fig, ax = plt.subplots(1)
fig.suptitle("error comparison")
ax.set_xlabel("epoch")
ax.set_ylabel("error")
ax.set_yscale("log")
ax.grid()
ax.plot(drop_error_list, label="dropout error")
ax.plot(err_list, label="normal error")
ax.legend(loc="best")

Vous pouvez maintenant le visualiser.

en conclusion

C'est un peu ennuyeux, alors pensons à une implémentation qui permet d'illustrer plus facilement ce genre de vérification comparative ...

Recommended Posts

Introduction au Deep Learning ~ Dropout Edition ~
Introduction au Deep Learning ~ Règles d'apprentissage ~
Apprentissage par renforcement profond 1 Introduction au renforcement de l'apprentissage
Introduction au Deep Learning ~ Rétropropagation ~
Introduction à l'apprentissage en profondeur ~ Approximation des fonctions ~
Introduction au Deep Learning ~ Propagation vers l'avant ~
Introduction à l'apprentissage profond ~ Expérience CNN ~
Introduction au Deep Learning ~ Pliage et mise en commun ~
Introduction à l'apprentissage automatique
Introduction à l'apprentissage profond ~ Fonction de localisation et de perte ~
[Mémorandum d'apprentissage] Introduction à vim
Une introduction à l'apprentissage automatique
Super introduction à l'apprentissage automatique
[Deep Learning from scratch] J'ai essayé d'expliquer le décrochage
Introduction à la rédaction de notes d'apprentissage automatique
Apprentissage profond pour démarrer sans GPU
Présentation de la bibliothèque d'apprentissage automatique SHOGUN
Deep Strengthening Learning 3 Édition pratique: Briser des blocs
L'apprentissage en profondeur
Introduction à Python Django (2) Édition Mac
Introduction au Deep Learning (2) - Essayez votre propre régression non linéaire avec Chainer-
Introduction à l'apprentissage automatique: fonctionnement du modèle
Apprentissage profond à partir de zéro (propagation vers l'avant)
Une introduction à OpenCV pour l'apprentissage automatique
Comment étudier le test Deep Learning G
Alignement d'image: du SIFT au deep learning
Une introduction à Python pour l'apprentissage automatique
Introduction à Machine Learning-Hard Margin SVM Edition-
Introduction à TensorFlow - Explication des termes et concepts d'apprentissage automatique
Introduction à MQTT (Introduction)
Introduction à Scrapy (1)
Introduction à Scrapy (3)
[Introduction] Renforcer l'apprentissage
Premiers pas avec Supervisor
Introduction à Tkinter 1: Introduction
Mémorandum d'apprentissage profond
Introduction à PyQt
Introduction à Scrapy (2)
Commencer l'apprentissage en profondeur
[Linux] Introduction à Linux
Apprentissage en profondeur Python
Introduction à Scrapy (4)
Apprentissage profond × Python
Introduction à discord.py (2)
[Python] Introduction facile à l'apprentissage automatique avec python (SVM)
[Super introduction à l'apprentissage automatique] Découvrez les didacticiels Pytorch
Un amateur a essayé le Deep Learning avec Caffe (Introduction)
Deep Learning from scratch ① Chapitre 6 "Techniques liées à l'apprentissage"
Une introduction à Cython sans aller plus loin
[Introduction à StyleGAN2] Apprentissage indépendant avec 10 visages d'anime ♬
[Mémo d'apprentissage] Apprentissage profond à partir de zéro ~ Mise en œuvre de l'abandon ~
[Super introduction à l'apprentissage automatique] Découvrez les didacticiels Pytorch
[Pour les débutants] Introduction à la vectorisation dans l'apprentissage automatique
Introduction à Cython sans approfondir -2-
Introduction au Deep Learning (1) --Chainer est expliqué d'une manière facile à comprendre pour les débutants-
J'ai essayé de rendre le deep learning évolutif avec Spark × Keras × Docker 2 Multi-host edition
Pourquoi quoi? Bibliothèque de calcul scientifique Deep Learning Édition Numpy
Python: pratique du Deep Learning
Fonctions d'apprentissage en profondeur / d'activation