[PYTHON] Un amateur a trébuché dans le Deep Learning à partir de zéro Note: Chapitre 7

introduction

J'ai soudainement commencé à étudier "Deep Learning from scratch - la théorie et l'implémentation du deep learning appris avec Python". C'est un mémo du voyage.

L'environnement d'exécution est macOS Mojave + Anaconda 2019.10, et la version Python est 3.7.4. Pour plus de détails, reportez-vous au Chapitre 1 de ce mémo.

(Vers d'autres chapitres de ce mémo: Chapitre 1 / Chapitre 2 / Chapitre 3 / Chapitre 4 / [Chapitre 5](https: // qiita. com / segavvy / items / 8707e4e65aa7fa357d8a) / Chapitre 6 / Chapitre 7 / Chapitre 8 / Résumé)

Chapitre 7 Réseau neuronal à convolution

Ce chapitre décrit le réseau neuronal à convolution (CNN).

7.1 Structure générale

En plus de la couche Affine, de la couche Softmax et de la couche ReLU existantes, la couche Convolution et la couche Pooling apparaîtront.

7.2 Couche pliante

L'explication de la couche de convolution est plus facile à lire si vous avez un peu de traitement d'image.

Il dit: "L'image est généralement une forme tridimensionnelle dans les directions verticale, horizontale et de canal." Cependant, puisque l'image est des données 2D verticales et horizontales, n'est-ce pas des données 3D avec de la profondeur ajoutée? Certaines personnes peuvent penser cela.

«Canal» se réfère ici aux informations pour chaque couleur comme RVB. Dans le cas des données d'échelle de gris (uniquement des nuances de noir et blanc) telles que MNIST, la densité d'un point peut être exprimée par une valeur, donc un canal est suffisant, mais dans une image couleur, un point est rouge, vert, bleu. Puisqu'elle est exprimée par la densité des trois valeurs de (RVB), trois canaux sont nécessaires. En plus de RVB, les canaux d'informations de couleur incluent CMJN, HSV et transparence alpha. Pour plus de détails, allez dans "RVB CMJN" etc. et vous y trouverez de nombreuses explications (bien qu'il y ait beaucoup d'histoires un peu plus proches de l'impression).

En outre, le mot "filtre" est également spécial et, dans le traitement d'image, il se réfère au traitement utilisé pour extraire uniquement les parties nécessaires (par exemple, les contours) d'une image ou pour supprimer les informations inutiles. Pour ceux qui ne le connaissent pas, il sera plus facile à comprendre si vous obtenez un aperçu du filtre de convolution dans le traitement d'image. @ t-tkd3a image de résultat du filtre de convolution 3x3 est recommandée car elle est facile à imaginer.

En passant, ce livre semble être une règle qui n'ajoute pas de longues notes pour katakana avec 3 notes ou plus comme "Layer". Cependant, puisque le "filtre" a une notation de note longue, il peut s'agir d'une omission unifiée. D'ailleurs, lorsque Microsoft a changé la méthode de notation de longues notes de Katakana en 2008 [^ 1], j'étais en charge du développement d'applications packagées pour Windows, et j'étais en charge de corriger le libellé des programmes et des manuels. C'était difficile. Avant cela, j'étais impliqué dans la suppression de kana demi-largeur de l'interface graphique de Windows 98 ... Dans cette industrie, vraiment japonais est gênant: transpirer:

Revenons à l'histoire et passons à autre chose.

7.3 Couche de regroupement

Quant à la couche de mise en commun, je n'avais pas d'obstacles particuliers.

7.4 Implémentation de la couche Convolution / Pooling

L'implémentation de la couche Convolution et de la couche Pooling est courte en code, mais compliquée car la forme des données cibles change rapidement avec ʻim2col, numpy.ndarray.reshape et numpy.ndarray.transpose`. C'était déroutant au début, mais je pouvais le comprendre en me référant à "Deep Learning from scratch" Convolution / Pooling layer implementation. ..

Le premier est l'implémentation de la couche Convolution. J'ai beaucoup de commentaires parce que je n'arrive pas à comprendre si je n'écris pas la forme.

convolution.py


# coding: utf-8
import os
import sys
import numpy as np
sys.path.append(os.pardir)  #Ajouter le répertoire parent au chemin
from common.util import im2col, col2im


class Convolution:
    def __init__(self, W, b, stride=1, pad=0):
        """Couche de convolution
        
        Args:
            W (numpy.ndarray):Filtre (poids), forme(FN, C, FH, FW)。
            b (numpy.ndarray):Biais, forme(FN)。
            stride (int, optional):Stride, la valeur par défaut est 1.
            pad (int, optional):Remplissage, la valeur par défaut est 0.
        """
        self.W = W
        self.b = b
        self.stride = stride
        self.pad = pad

        self.dW = None      #Valeur différentielle du poids
        self.db = None      #Valeur différentielle du biais

        self.x = None       #Entrée pendant la propagation directe requise pour la propagation arrière
        self.col_x = None   #Résultat de l'expansion des colonnes de l'entrée au moment de la propagation vers l'avant requise pour la propagation arrière
        self.col_W = None   #Résultat de l'expansion de colonne du filtre au moment de la propagation vers l'avant requise pour la propagation arrière

    def forward(self, x):
        """Propagation vers l'avant
        
        Args:
            x (numpy.ndarray):contribution. La forme est(N, C, H, W)。
            
        Returns:
            numpy.ndarray:production. La forme est(N, FN, OH, OW)。
        """
        FN, C, FH, FW = self.W.shape  # FN:Nombre de filtres, C:Nombre de canaux, FH:Hauteur du filtre, FW:largeur
        N, x_C, H, W = x.shape        # N:Taille du lot, x_C:Nombre de canaux, H: Hauteur des données d'entrée, W:largeur
        assert C == x_C, f'Inadéquation du nombre de canaux![C]{C}, [x_C]{x_C}'

        #Calcul de la taille de sortie
        assert (H + 2 * self.pad - FH) % self.stride == 0, 'OH n'est pas divisible!'
        assert (W + 2 * self.pad - FW) % self.stride == 0, 'OW est indivisible!'
        OH = int((H + 2 * self.pad - FH) / self.stride + 1)
        OW = int((W + 2 * self.pad - FW) / self.stride + 1)

        #Développer les données d'entrée
        # (N, C, H, W) → (N * OH * OW, C * FH * FW)
        col_x = im2col(x, FH, FW, self.stride, self.pad)

        #Développer le filtre
        # (FN, C, FH, FW) → (C * FH * FW, FN)
        col_W = self.W.reshape(FN, -1).T

        #Calculer la sortie (col_x, col_W,Le calcul de b est exactement le même que celui de la couche Affine)
        # (N * OH * OW, C * FH * FW)・(C * FH * FW, FN) → (N * OH * OW, FN)
        out = np.dot(col_x, col_W) + self.b

        #Formater le résultat
        # (N * OH * OW, FN) → (N, OH, OW, FN) → (N, FN, OH, OW)
        out = out.reshape(N, OH, OW, FN).transpose(0, 3, 1, 2)

        #Enregistrer pour la rétropropagation
        self.x = x
        self.col_x = col_x
        self.col_W = col_W

        return out

    def backward(self, dout):
        """Rétropropagation
        
        Args:
            dout (numpy.ndarray):La valeur différentielle et la forme transmises par la bonne couche(N, FN, OH, OW)。
        
        Returns:
            numpy.ndarray:Valeur de différenciation (gradient), forme(N, C, H, W)。
        """
        FN, C, FH, FW = self.W.shape  #La forme de la valeur différentielle est la même que W(FN, C, FH, FW)

        #Développez la valeur différentielle du bon calque
        # (N, FN, OH, OW) → (N, OH, OW, FN) → (N * OH * OW, FN)
        dout = dout.transpose(0, 2, 3, 1).reshape(-1, FN)

        #Calcul de la valeur de différenciation (col_x, col_W,Le calcul de b est exactement le même que celui de la couche Affine)
        dcol_x = np.dot(dout, self.col_W.T)     # → (N * OH * OW, C * FH * FW)
        self.dW = np.dot(self.col_x.T, dout)    # → (C * FH * FW, FN)
        self.db = np.sum(dout, axis=0)          # → (FN)

        #Formatage de la valeur différentielle du filtre (poids)
        # (C * FH * FW, FN) → (FN, C * FH * FW) → (FN, C, FH, FW)
        self.dW = self.dW.transpose(1, 0).reshape(FN, C, FH, FW)

        #Formatage du résultat (dégradé)
        # (N * OH * OW, C * FH * FW) → (N, C, H, W)
        dx = col2im(dcol_x, self.x.shape, FH, FW, self.stride, self.pad)
    
        return dx

Vient ensuite l'implémentation de la couche Pooling. C'est aussi plein de commentaires.

pooling.py


# coding: utf-8
import os
import sys
import numpy as np
sys.path.append(os.pardir)  #Ajouter le répertoire parent au chemin
from common.util import im2col, col2im


class Pooling:
    def __init__(self, pool_h, pool_w, stride=1, pad=0):
        """Couche de regroupement
        
        Args:
            pool_h (int):Hauteur de la zone de regroupement
            pool_w (int):Largeur de la zone de regroupement
            stride (int, optional):Stride, la valeur par défaut est 1.
            pad (int, optional):Remplissage, la valeur par défaut est 0.
        """
        self.pool_h = pool_h
        self.pool_w = pool_w
        self.stride = stride
        self.pad = pad

        self.x = None           #Entrée pendant la propagation directe requise pour la propagation arrière
        self.arg_max = None     #Le col utilisé pour la propagation avant, qui est requis pour la propagation arrière_x Position de chaque ligne

    def forward(self, x):
        """Propagation vers l'avant
        
        Args:
            x (numpy.ndarray):Entrée, forme(N, C, H, W)。
            
        Returns:
            numpy.ndarray:Sortie, forme(N, C, OH, OW)。
        """
        N, C, H, W = x.shape  # N:Nombre de données, C:Nombre de canaux, H:Hauteur, W:largeur

        #Calcul de la taille de sortie
        assert (H - self.pool_h) % self.stride == 0, 'OH n'est pas divisible!'
        assert (W - self.pool_w) % self.stride == 0, 'OW est indivisible!'
        OH = int((H - self.pool_h) / self.stride + 1)
        OW = int((W - self.pool_w) / self.stride + 1)

        #Développer et formater les données d'entrée
        # (N, C, H, W) → (N * OH * OW, C * PH * PW)
        col_x = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad)
        # (N * OH * OW, C * PH * PW) → (N * OH * OW * C, PH * PW)
        col_x = col_x.reshape(-1, self.pool_h * self.pool_w)

        #Calculer la sortie
        # (N * OH * OW * C, PH * PW) → (N * OH * OW * C)
        out = np.max(col_x, axis=1)

        #Formater le résultat
        # (N * OH * OW * C) → (N, OH, OW, C) → (N, C, OH, OW)
        out = out.reshape(N, OH, OW, C).transpose(0, 3, 1, 2)

        #Enregistrer pour la rétropropagation
        self.x = x
        self.arg_max = np.argmax(col_x, axis=1)  # col_x Position maximale (index) de chaque ligne

        return out

    def backward(self, dout):
        """Rétropropagation
        
        Args:
            dout (numpy.ndarray):La valeur différentielle et la forme transmises par la bonne couche(N, C, OH, OW)。
        
        Returns:
            numpy.ndarray:Valeur de différenciation (gradient), forme(N, C, H, W)。
        """
        #Façonner la valeur différentielle à partir du bon calque
        # (N, C, OH, OW) → (N, OH, OW, C)
        dout = dout.transpose(0, 2, 3, 1)

        #Initialiser col pour la valeur différentielle résultante avec 0
        # (N * OH * OW * C, PH * PW)
        pool_size = self.pool_h * self.pool_w
        dcol_x = np.zeros((dout.size, pool_size))

        #Définissez la valeur différentielle de dout (= dout manma) uniquement à la position adoptée comme valeur maximale pendant la propagation vers l'avant.
        #La position de la valeur qui n'a pas été adoptée lors de la propagation vers l'avant reste 0 à l'initialisation.
        #(Identique au traitement lorsque x est supérieur à 0 et x est inférieur à 0 dans ReLU)
        assert dout.size == self.arg_max.size, 'Col pendant la propagation vers l'avant_Ne correspond pas au nombre de lignes de x'
        dcol_x[np.arange(self.arg_max.size), self.arg_max.flatten()] = \
            dout.flatten()

        #Formatage de la valeur différentielle résultante 1
        # (N * OH * OW * C, PH * PW) → (N, OH, OW, C, PH * PW)
        dcol_x = dcol_x.reshape(dout.shape + (pool_size,))  #Dernier','Indique un taple à un élément

        #Formatage de la valeur différentielle résultante 2
        # (N, OH, OW, C, PH * PW) → (N * OH * OW, C * PH * PW)
        dcol_x = dcol_x.reshape(
            dcol_x.shape[0] * dcol_x.shape[1] * dcol_x.shape[2], -1
        )

        #Formatage de la valeur différentielle résultante 3
        # (N * OH * OW, C * PH * PW) → (N, C, H, W)
        dx = col2im(
            dcol_x, self.x.shape, self.pool_h, self.pool_w, self.stride, self.pad
        )

        return dx

7.5 Implémentation CNN

Implémentez CNN en combinant les implémentations précédentes.

(1) Implémentation de chaque couche

Commençons par organiser l'entrée et la sortie dans ce réseau.

couche Forme d'entrée / sortie Forme au moment du montage
$ (Taille du lot N,Nombre de canaux CH,Hauteur d'image H,Largeur W) $ $ (100, 1, 28, 28) $
:one: Convolution
$ (Taille du lot N,Nombre de filtres FN,Hauteur de sortie OH,Largeur OW) $ $ (100, 30, 24, 24) $
:two: ReLU
$ (Taille du lot N,Nombre de filtres FN,Hauteur de sortie OH,Largeur OW) $ $ (100, 30, 24, 24) $
:three: Pooling
$ (Taille du lot N,Nombre de filtres FN,Hauteur de sortie OH,Largeur OW) $ $ (100, 30, 12, 12) $
:four: Affine
$ (Taille du lot N,Taille du calque masqué) $ $ (100, 100) $
:five: ReLU
$ (Taille du lot N,Taille du calque masqué) $ $ (100, 100) $
:six: Affine
$ (Taille du lot N,Taille de sortie finale) $ $ (100, 10) $
:seven: Softmax
$ (Taille du lot N,Taille de sortie finale) $ $ (100, 10) $

La mise en œuvre de la couche Convlolution et de la couche Pooling est telle que décrite ci-dessus.

La couche Affine nécessite quelques modifications par rapport à l'implémentation précédente. Auparavant [5.6.2 Batch Affine Layer](https://qiita.com/segavvy/items/8707e4e65aa7fa357d8a#562-%E3%83%90%E3%83%83%E3%83%81%E7%89% Lorsqu'il est implémenté avec 88affine% E3% 83% AC% E3% 82% A4% E3% 83% A4), l'entrée était bidimensionnelle ($ taille du lot N $, taille de l'image), mais cette fois, la quatrième L'entrée de la couche Affine est de 4 dimensions (nombre de lots $ N $, nombre de filtres $ FN $, résultat de mise en commun $ OH $, $ OW $), il est donc nécessaire de s'en occuper. À la page 152 du livre, il y a une condition que «l'implémentation d'Affine dans common / layer.py est une implémentation qui considère le cas où les données d'entrée sont des tenseur (données 4D)». Je ne savais pas s'il était laissé sans surveillance, mais il était censé être utilisé cette fois.

Ce qui suit est une implémentation de la couche Affine qui prend également en charge la saisie de 3 dimensions ou plus.

affine.py


# coding: utf-8
import numpy as np


class Affine:

    def __init__(self, W, b):
        """Couche affine
        
        Args:
            W (numpy.ndarray):poids
            b (numpy.ndarray):biais
        """
        self.W = W                      #poids
        self.b = b                      #biais
        self.x = None                   #Entrée (après 2D)
        self.dW = None                  #Valeur différentielle du poids
        self.db = None                  #Valeur différentielle du biais
        self.original_x_shape = None    #Forme d'entrée d'origine (pour l'entrée de 3 dimensions ou plus)

    def forward(self, x):
        """Propagation vers l'avant
        
        Args:
            x (numpy.ndarray):contribution
            
        Returns:
            numpy.ndarray:production
        """
        #Entrée bidimensionnelle de trois dimensions ou plus (tenseur)
        self.original_x_shape = x.shape  #Parce qu'il est nécessaire de sauvegarder la forme et de la restaurer par propagation arrière
        x = x.reshape(x.shape[0], -1)
        self.x = x

        #Calculer la sortie
        out = np.dot(x, self.W) + self.b

        return out

    def backward(self, dout):
        """Rétropropagation
        
        Args:
            dout (numpy.ndarray):Valeur de différenciation transmise depuis la bonne couche

        Returns:
            numpy.ndarray:Valeur de différenciation
        """
        #Calcul de la valeur de différenciation
        dx = np.dot(dout, self.W.T)
        self.dW = np.dot(self.x.T, dout)
        self.db = np.sum(dout, axis=0)

        #Revenir à la forme d'origine
        dx = dx.reshape(*self.original_x_shape)
        return dx

Les couches ReLU et Softmax sont identiques à l'implémentation précédente, mais seront réimprimées.

relu.py


# coding: utf-8


class ReLU:
    def __init__(self):
        """Couche ReLU
        """
        self.mask = None

    def forward(self, x):
        """Propagation vers l'avant
        
        Args:
            x (numpy.ndarray):contribution
            
        Returns:
            numpy.ndarray:production
        """
        self.mask = (x <= 0)
        out = x.copy()
        out[self.mask] = 0

        return out

    def backward(self, dout):
        """Rétropropagation
        
        Args:
            dout (numpy.ndarray):Valeur de différenciation transmise depuis la bonne couche
        
        Returns:
            numpy.ndarray:Valeur de différenciation
        """
        dout[self.mask] = 0
        dx = dout

        return dx

softmax_with_loss.py


# coding: utf-8
from functions import softmax, cross_entropy_error


class SoftmaxWithLoss:
    def __init__(self):
        """Softmax-with-Couche de perte
        """
        self.loss = None    #perte
        self.y = None       #sortie de softmax
        self.t = None       #Données de l'enseignant (une-hot vector)

    def forward(self, x, t):
        """Propagation vers l'avant
        
        Args:
            x (numpy.ndarray):contribution
            t (numpy.ndarray):Données des enseignants

        Returns:
            float:Erreur d'entropie croisée
        """
        self.t = t
        self.y = softmax(x)
        self.loss = cross_entropy_error(self.y, self.t)

        return self.loss

    def backward(self, dout=1):
        """Rétropropagation
        
        Args:
            dout (float, optional):La valeur différentielle transmise depuis la couche droite. La valeur par défaut est 1.

        Returns:
            numpy.ndarray:Valeur de différenciation
        """
        batch_size = self.t.shape[0]    #Nombre de lots
        dx = (self.y - self.t) * (dout / batch_size)

        return dx

Les fonctions requises pour implémenter la couche softmax sont également réimprimées comme précédemment. De plus, les fonctions qui ne sont pas utilisées cette fois sont supprimées.

functions.py


# coding: utf-8
import numpy as np


def softmax(x):
    """Fonction Softmax
    
    Args:
        x (numpy.ndarray):contribution
    
    Returns:
        numpy.ndarray:production
    """
    #Pour le traitement par lots, x est(Nombre de lots, 10)Il devient un tableau bidimensionnel de.
    #Dans ce cas, il est nécessaire de bien calculer pour chaque image en utilisant la diffusion.
    #Ici, np afin qu'il puisse être partagé en 1 et 2 dimensions..max()Et np.sum()Est axe=-Calculé par 1
    #Keepdims pour qu'il puisse être diffusé tel quel=Vrai pour maintenir la dimension.
    c = np.max(x, axis=-1, keepdims=True)
    exp_a = np.exp(x - c)  #Mesures de débordement
    sum_exp_a = np.sum(exp_a, axis=-1, keepdims=True)
    y = exp_a / sum_exp_a
    return y


def cross_entropy_error(y, t):
    """Calcul de l'erreur d'entropie croisée
    
    Args:
        y (numpy.ndarray):Sortie de réseau neuronal
        t (numpy.ndarray):Étiquette de réponse correcte
    
    Returns:
        float:Erreur d'entropie croisée
    """

    #S'il y a une donnée, formez-la (faites une ligne de données)
    if y.ndim == 1:
        t = t.reshape(1, t.size)
        y = y.reshape(1, y.size)

    #Calculer l'erreur et normaliser par le nombre de lots
    batch_size = y.shape[0]
    return -np.sum(t * np.log(y + 1e-7)) / batch_size

(2) Implémentation de l'optimiseur

Pour l'optimiseur qui optimise les paramètres, voir 6.1 Paramètres de mise à jour (https://qiita.com/segavvy/items/ca4ac4c9ee1a126bff41#61-%E3%83%91%E3%83%A9%E3%83%] A1% E3% 83% BC% E3% 82% BF% E3% 81% AE% E6% 9B% B4% E6% 96% B0) J'ai sauté l'implémentation juste en lisant, j'ai donc décidé d'utiliser AdaGrad cette fois J'ai essayé de mettre en œuvre. C'est presque le même que le code du livre.

ada_grad.py


# coding: utf-8
import numpy as np


class AdaGrad:

    def __init__(self, lr=0.01):
        """Optimisation des paramètres avec AdaGrad
        
        Args:
            lr (float, optional):Coefficient d'apprentissage, la valeur par défaut est 0.01。
        """
        self.lr = lr
        self.h = None   #Somme des carrés du gradient jusqu'à présent

    def update(self, params, grads):
        """Mise à jour des paramètres
        
        Args:
            params (dict):Le dictionnaire des paramètres à mettre à jour, la clé est'W1'、'b1'Tel.
            grads (dict):Dictionnaire de dégradés correspondant aux paramètres
        """

        #initialisation de h
        if self.h is None:
            self.h = {}
            for key, val in params.items():
                self.h[key] = np.zeros_like(val)

        #mise à jour
        for key in params.keys():

            #mise à jour de h
            self.h[key] += grads[key] ** 2

            #Mise à jour des paramètres, dernière 1e-7 évite la division par 0
            params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)

(3) Mise en œuvre de CNN

CNN précédemment [5.7.2 Implémentation d'un réseau neuronal pour la rétropropagation](https://qiita.com/segavvy/items/8707e4e65aa7fa357d8a#572-%E8%AA%A4%E5%B7%AE% E9% 80% 86% E4% BC% 9D% E6% 92% AD% E6% B3% 95% E3% 81% AB% E5% AF% BE% E5% BF% 9C% E3% 81% 97% E3% 81% 9F% E3% 83% 8B% E3% 83% A5% E3% 83% BC% E3% 83% A9% E3% 83% AB% E3% 83% 8D% E3% 83% 83% E3% 83% Basé sur le TwoLayerNet fait avec 88% E3% 83% AF% E3% 83% BC% E3% 82% AF% E3% 81% AE% E5% AE% 9F% E8% A3% 85) Je l'ai implémenté selon les instructions.

Dans le code du livre, ʻOrderedDict est utilisé, mais comme la dernière fois, normal dictest utilisé ici. En effet, à partir de Python 3.7, l'ordre d'insertion des objetsdict` est enregistré [^ 2]. Aussi, je suis tombé sur l'implémentation de «l'exactitude», donc je l'expliquerai plus tard.

Voici la mise en œuvre de CNN.

simple_conv_net.py


# coding: utf-8
import numpy as np
from affine import Affine
from convolution import Convolution
from pooling import Pooling
from relu import ReLU
from softmax_with_loss import SoftmaxWithLoss


class SimpleConvNet:

    def __init__(
        self, input_dim=(1, 28, 28),
        conv_param={'filter_num': 30, 'filter_size': 5, 'pad': 0, 'stride': 1},
        hidden_size=100, output_size=10, weight_init_std=0.01
    ):
        """Réseau neuronal convolutif simple
        
        Args:
            input_dim (tuple, optional):Forme des données d'entrée, par défaut(1, 28, 28)。
            conv_param (dict, optional):Hyperparamètres de la couche de convolution,
La valeur par défaut est{'filter_num':30, 'filter_size':5, 'pad':0, 'stride':1}。
            hidden_size (int, optional):Le nombre de neurones dans la couche cachée, la valeur par défaut est 100.
            output_size (int, optional):Nombre de neurones dans la couche de sortie, par défaut 10.
            weight_init_std (float, optional):Paramètre de réglage de la valeur initiale du poids. La valeur par défaut est 0.01。
        """
        #Extraire les hyperparamètres de la couche de convolution
        filter_num = conv_param['filter_num']       #Nombre de filtres
        filter_size = conv_param['filter_size']     #Taille du filtre (même hauteur et largeur)
        filter_stride = conv_param['stride']        #foulée
        filter_pad = conv_param['pad']              #Rembourrage
        
        #Les hyper paramètres de la couche de pooling sont fixes
        pool_size = 2                               #Taille (même hauteur et largeur)
        pool_stride = 2                             #foulée
        pool_pad = 0                                #Rembourrage

        #Calcul de la taille des données d'entrée
        input_ch = input_dim[0]                     #Nombre de canaux de données d'entrée
        assert input_dim[1] == input_dim[2], 'Les données d'entrée sont supposées avoir la même hauteur et la même largeur!'
        input_size = input_dim[1]                   #Taille des données d'entrée
        
        #Calcul de la taille de sortie de la couche de convolution
        assert (input_size + 2 * filter_pad - filter_size) \
            % filter_stride == 0, 'La taille de sortie du calque de pliage n'est pas divisible!'
        conv_output_size = int(
            (input_size + 2 * filter_pad - filter_size) / filter_stride + 1
        )

        #Calcul de la taille de sortie de la couche de pooling
        assert (conv_output_size - pool_size) % pool_stride == 0, \
            'La taille de sortie de la couche de pooling n'est pas divisible!'
        pool_output_size_one = int(
            (conv_output_size - pool_size) / pool_stride + 1  #Taille hauteur / largeur
        )
        pool_output_size = filter_num * \
            pool_output_size_one * pool_output_size_one     #Taille totale de tous les filtres

        #Initialisation du poids
        self.params = {}
        #Couche pliante
        self.params['W1'] = weight_init_std * \
            np.random.randn(filter_num, input_ch, filter_size, filter_size)
        self.params['b1'] = np.zeros(filter_num)
        #Couche affine 1
        self.params['W2'] = weight_init_std * \
            np.random.randn(pool_output_size, hidden_size)
        self.params['b2'] = np.zeros(hidden_size)
        #Couche affine 2
        self.params['W3'] = weight_init_std * \
            np.random.randn(hidden_size, output_size)
        self.params['b3'] = np.zeros(output_size)
            
        #Génération de couches
        self.layers = {}    # Python 3.OrderedDict n'est pas nécessaire car l'ordre de stockage du dictionnaire est conservé à partir de 7
        #Couche pliante
        self.layers['Conv1'] = Convolution(
            self.params['W1'], self.params['b1'], filter_stride, filter_pad
        )
        self.layers['Relu1'] = ReLU()
        self.layers['Pool1'] = Pooling(
            pool_size, pool_size, pool_stride, pool_pad
        )
        #Couche affine 1
        self.layers['Affine1'] = \
            Affine(self.params['W2'], self.params['b2'])
        self.layers['Relu2'] = ReLU()
        #Couche affine 2
        self.layers['Affine2'] = \
            Affine(self.params['W3'], self.params['b3'])
    
        self.lastLayer = SoftmaxWithLoss()

    def predict(self, x):
        """Inférence par réseau neuronal
        
        Args:
            x (numpy.ndarray):Entrée dans le réseau neuronal
        
        Returns:
            numpy.ndarray:Sortie de réseau neuronal
        """
        #Propager les couches vers l'avant
        for layer in self.layers.values():
            x = layer.forward(x)

        return x

    def loss(self, x, t):
        """Calcul de la valeur de la fonction de perte
        
        Args:
            x (numpy.ndarray):Entrée dans le réseau neuronal
            t (numpy.ndarray):Étiquette de réponse correcte

        Returns:
            float:Valeur de la fonction de perte
        """
        #inférence
        y = self.predict(x)

        # Softmax-with-Calculé par propagation directe de la couche de perte
        loss = self.lastLayer.forward(y, t)

        return loss

    def accuracy(self, x, t, batch_size=100):
        """Calcul de la précision de la reconnaissance
        batch_size est la taille du lot au moment du calcul. Lorsque vous essayez de calculer une grande quantité de données à la fois
Parce que im2col consomme trop de mémoire et que des barres obliques se produisent et que cela ne fonctionne pas
Pour éviter ça.

        Args:
            x (numpy.ndarray):Entrée dans le réseau neuronal
            t (numpy.ndarray):Étiquette de réponse correcte (une-hot)
            batch_size (int), optional):Taille du lot au moment du calcul, la valeur par défaut est 100.
        
        Returns:
            float:Précision de reconnaissance
        """
        #Calcul du nombre de divisions
        batch_num = max(int(x.shape[0] / batch_size), 1)

        #Divisé
        x_list = np.array_split(x, batch_num, 0)
        t_list = np.array_split(t, batch_num, 0)

        #Processus en unités divisées
        correct_num = 0  #Nombre total de bonnes réponses
        for (sub_x, sub_t) in zip(x_list, t_list):
            assert sub_x.shape[0] == sub_t.shape[0], 'La frontière de division a-t-elle changé?'
            y = self.predict(sub_x)
            y = np.argmax(y, axis=1)
            t = np.argmax(sub_t, axis=1)
            correct_num += np.sum(y == t)
        
        #Calcul de la précision de la reconnaissance
        return correct_num / x.shape[0]

    def gradient(self, x, t):
        """Calculer le gradient pour le paramètre de poids par la méthode de propagation de l'erreur en retour
        
         Args:
            x (numpy.ndarray):Entrée dans le réseau neuronal
            t (numpy.ndarray):Étiquette de réponse correcte
        
        Returns:
            dictionary:Un dictionnaire qui stocke les dégradés
        """
        #Propagation vers l'avant
        self.loss(x, t)     #Propager vers l'avant pour calculer la valeur de la perte

        #Rétropropagation
        dout = self.lastLayer.backward()
        for layer in reversed(list(self.layers.values())):
            dout = layer.backward(dout)

        #Extraire la valeur différentielle de chaque couche
        grads = {}
        grads['W1'] = self.layers['Conv1'].dW
        grads['b1'] = self.layers['Conv1'].db
        grads['W2'] = self.layers['Affine1'].dW
        grads['b2'] = self.layers['Affine1'].db
        grads['W3'] = self.layers['Affine2'].dW
        grads['b3'] = self.layers['Affine2'].db

        return grads

La pierre d'achoppement dans cette implémentation est la «précision», qui est omise dans le livre.

Pendant l'apprentissage, la précision de la reconnaissance est calculée en unités d'une époque, mais dans le code écrit au chapitre 4, 60 000 éléments de données d'apprentissage ont été ajoutés en même temps pour obtenir la précision de la reconnaissance. Cependant, si je fais la même chose cette fois, il semble que l'expansion de ʻim2col` consomme beaucoup de mémoire, et ma VM de 4 Go de mémoire s'arrête à slashing [^ 3]: sueur:

Cependant, la source du livre consomme toujours moins de mémoire et fonctionne normalement dans mon environnement. C'est étrange, donc quand j'ai suivi la source, elle a été divisée et traitée en interne. C'est pourquoi je l'imite et la divise également en interne. De plus, essayez d'utiliser numpy.array_split pour implémenter le fractionnement. J'ai fait.

(4) Mise en œuvre de l'apprentissage

L'apprentissage est le précédent [5.7.4 Apprentissage utilisant la méthode de propagation des erreurs de retour](https://qiita.com/segavvy/items/8707e4e65aa7fa357d8a#574-%E8%AA%A4%E5%B7%AE%E9% 80% 86% E4% BC% 9D% E6% 92% AD% E6% B3% 95% E3% 82% 92% E4% BD% BF% E3% 81% A3% E3% 81% 9F% E5% AD% Mis en œuvre sur la base de A6% E7% BF% 92). Voici quelques points.

Voici la mise en œuvre de l'apprentissage.

mnist.py


# coding: utf-8
import os
import sys
import matplotlib.pylab as plt
import numpy as np
from ada_grad import AdaGrad
from simple_conv_net import SimpleConvNet
sys.path.append(os.pardir)  #Ajouter le répertoire parent au chemin
from dataset.mnist import load_mnist


#Lire les données d'entraînement MNIST et les données de test
(x_train, t_train), (x_test, t_test) = \
    load_mnist(normalize=True, flatten=False, one_hot_label=True)

#Paramètres des hyper paramètres
iters_num = 6000        #Nombre de mises à jour
batch_size = 100        #Taille du lot
learning_rate = 0.06    #En supposant le taux d'apprentissage, AdaGrad

train_size = x_train.shape[0]  #Taille des données d'entraînement
iter_per_epoch = max(int(train_size / batch_size), 1)    #Nombre d'itérations par époque

#Génération simple de réseau neuronal convolutif
network = SimpleConvNet(
    input_dim=(1, 28, 28),
    conv_param={'filter_num': 30, 'filter_size': 5, 'pad': 0, 'stride': 1},
    hidden_size=100, output_size=10, weight_init_std=0.01
)

#Génération d'optimiseur
optimizer = AdaGrad(learning_rate)   # AdaGrad

#Confirmation de la précision de la reconnaissance avant l'apprentissage
train_acc = network.accuracy(x_train, t_train)
test_acc = network.accuracy(x_test, t_test)
train_loss_list = []            #Emplacement de stockage de la transition de la valeur de la fonction de perte
train_acc_list = [train_acc]    #Emplacement de stockage des modifications de la précision de la reconnaissance des données d'entraînement
test_acc_list = [test_acc]      #Destination de stockage de la transition de la précision de reconnaissance pour les données de test
print(f'Avant d'apprendre[Reconnaissance de l'exactitude des données d'entraînement]{train_acc:.4f} [Précision de reconnaissance des données de test]{test_acc:.4f}')

#Commencer à apprendre
for i in range(iters_num):

    #Mini génération de lots
    batch_mask = np.random.choice(train_size, batch_size, replace=False)
    x_batch = x_train[batch_mask]
    t_batch = t_train[batch_mask]

    #Calcul du gradient
    grads = network.gradient(x_batch, t_batch)

    #Mise à jour des paramètres de poids
    optimizer.update(network.params, grads)
    
    #Calcul de la valeur de la fonction de perte
    loss = network.loss(x_batch, t_batch)
    train_loss_list.append(loss)

    #Calcul de la précision de la reconnaissance pour chaque époque
    if (i + 1) % iter_per_epoch == 0:
        train_acc = network.accuracy(x_train, t_train)
        test_acc = network.accuracy(x_test, t_test)
        train_acc_list.append(train_acc)
        test_acc_list.append(test_acc)

        #Affichage de la progression
        print(
            f'[époque]{(i + 1) // iter_per_epoch:>2} '
            f'[Nombre de mises à jour]{i + 1:>5} [Valeur de la fonction de perte]{loss:.4f} '
            f'[Reconnaissance de l'exactitude des données d'entraînement]{train_acc:.4f} [Précision de reconnaissance des données de test]{test_acc:.4f}'
        )

#Tracez la transition de la valeur de la fonction de perte
x = np.arange(len(train_loss_list))
plt.plot(x, train_loss_list, label='loss')
plt.xlabel('iteration')
plt.ylabel('loss')
plt.xlim(left=0)
plt.ylim(0, 2.5)
plt.show()

#Dessinez la transition de la précision de reconnaissance des données d'entraînement et des données de test
x2 = np.arange(len(train_acc_list))
plt.plot(x2, train_acc_list, label='train acc')
plt.plot(x2, test_acc_list, label='test acc', linestyle='--')
plt.xlabel('epochs')
plt.ylabel('accuracy')
plt.xlim(left=0)
plt.ylim(0, 1.0)
plt.legend(loc='lower right')
plt.show()

(5) Résultat d'exécution

Voici les résultats de l'exécution. Cela a pris environ une heure dans mon environnement.

Avant d'apprendre[Reconnaissance de l'exactitude des données d'entraînement]0.0909 [Précision de reconnaissance des données de test]0.0909
[époque] 1 [Nombre de mises à jour]  600 [Valeur de la fonction de perte]0.0699 [Reconnaissance de l'exactitude des données d'entraînement]0.9784 [Précision de reconnaissance des données de test]0.9780
[époque] 2 [Nombre de mises à jour] 1200 [Valeur de la fonction de perte]0.0400 [Reconnaissance de l'exactitude des données d'entraînement]0.9844 [Précision de reconnaissance des données de test]0.9810
[époque] 3 [Nombre de mises à jour] 1800 [Valeur de la fonction de perte]0.0362 [Reconnaissance de l'exactitude des données d'entraînement]0.9885 [Précision de reconnaissance des données de test]0.9853
[époque] 4 [Nombre de mises à jour] 2400 [Valeur de la fonction de perte]0.0088 [Reconnaissance de l'exactitude des données d'entraînement]0.9907 [Précision de reconnaissance des données de test]0.9844
[époque] 5 [Nombre de mises à jour] 3000 [Valeur de la fonction de perte]0.0052 [Reconnaissance de l'exactitude des données d'entraînement]0.9926 [Précision de reconnaissance des données de test]0.9851
[époque] 6 [Nombre de mises à jour] 3600 [Valeur de la fonction de perte]0.0089 [Reconnaissance de l'exactitude des données d'entraînement]0.9932 [Précision de reconnaissance des données de test]0.9850
[époque] 7 [Nombre de mises à jour] 4200 [Valeur de la fonction de perte]0.0029 [Reconnaissance de l'exactitude des données d'entraînement]0.9944 [Précision de reconnaissance des données de test]0.9865
[époque] 8 [Nombre de mises à jour] 4800 [Valeur de la fonction de perte]0.0023 [Reconnaissance de l'exactitude des données d'entraînement]0.9954 [Précision de reconnaissance des données de test]0.9873
[époque] 9 [Nombre de mises à jour] 5400 [Valeur de la fonction de perte]0.0051 [Reconnaissance de l'exactitude des données d'entraînement]0.9959 [Précision de reconnaissance des données de test]0.9860
[époque]10 [Nombre de mises à jour] 6000 [Valeur de la fonction de perte]0.0037 [Reconnaissance de l'exactitude des données d'entraînement]0.9972 [Précision de reconnaissance des données de test]0.9860

スクリーンショット 2020-02-11 17.03.04.png スクリーンショット 2020-02-11 17.03.20.png En conséquence, la précision de reconnaissance des données d'apprentissage était de 99,72% et la précision de reconnaissance des données de test était de 98,60%. Avec une époque, il a déjà dépassé la précision de reconnaissance précédente. Étant donné que la précision de reconnaissance des données de test n'a pas changé depuis environ 7 époques, il se peut que cela ait simplement été un apprentissage excessif après cela. Même ainsi, la précision de 98,60% avec un simple CNN est incroyable.

J'ai également essayé d'exécuter la source du livre, mais pour une raison quelconque, le calcul de la précision de reconnaissance pour chaque époque est très rapide. Mystérieusement, j'ai trouvé qu'il était possible d'échantillonner avec le paramètre ʻevaluate_sample_num_per_epoch de la classe Trainer`, et l'image de formation et l'image de test ont été calculées avec seulement les 1000 premières images. injuste! : non amusé:

7.6 Visualisation CNN

Il est étonnant que les filtres nécessaires tels que l'extraction des bords et des objets blob soient automatiquement créés. Il est très intéressant que le degré d'abstraction augmente au fur et à mesure que les couches se superposent.

7.7 CNN typique

On dit que le big data et le GPU apportent une grande contribution au développement du deep learning, mais je pense que la diffusion du cloud, qui permet d'utiliser d'énormes ressources machines à faible coût, est également un gros point.

C'est aussi un aparté complet, mais j'ai été profondément ému par le fait que la proposition du LeNet remonte à 1998, il y a 20 ans, et que 1998 était une impression plus récente. Je ne veux pas vieillir: transpirer:

7.8 Résumé

C'était un peu difficile à mettre en œuvre, mais grâce à cela, j'ai compris CNN. C'est tout pour ce chapitre. Si vous avez des erreurs, je vous serais reconnaissant de bien vouloir les signaler.

(Vers d'autres chapitres de ce mémo: Chapitre 1 / Chapitre 2 / Chapitre 3 / Chapitre 4 / [Chapitre 5](https: // qiita. com / segavvy / items / 8707e4e65aa7fa357d8a) / Chapitre 6 / Chapitre 7 / Chapitre 8 / Résumé)

[^ 1]: [Modifications de la longue note à la fin des mots étrangers et des termes katakana dans les produits et services Microsoft](https://web.archive.org/web/20130228002415/http://www.microsoft.com/japan/ presspass / detail.aspx? newsid = 3491) (* Puisqu'il n'y a plus de pages à ce moment-là, Wikipedia> Notes longues B3% E7% AC% A6) est également un lien vers la Wayback Machine de l'Internet Archive)

[^ 2]: Voir "Amélioration du modèle de données Python" dans Nouveautés de Python 3.7.

[^ 3]: La barre oblique est un phénomène qui se produit lorsque la mémoire est insuffisante, et elle est gênante car elle peut devenir inopérante pour chaque système d'exploitation. Si vous êtes intéressé par la gestion de la mémoire du système d'exploitation, veuillez consulter l '[Introduction à la gestion de la mémoire pour tous: 01] précédemment publiée (https://qiita.com/segavvy/items/9a9f8aa5cc4e6760307a)! : sourire:

Recommended Posts

Un amateur a trébuché dans le Deep Learning à partir de zéro Note: Chapitre 1
Un amateur a trébuché dans le Deep Learning à partir de zéro Note: Chapitre 3
Un amateur a trébuché dans le Deep Learning à partir de zéro Note: Chapitre 7
Un amateur a trébuché dans le Deep Learning à partir de zéro Note: Chapitre 5
Un amateur a trébuché dans le Deep Learning à partir de zéro.
Un amateur a trébuché dans le Deep Learning à partir de zéro Note: Chapitre 2
Un amateur a trébuché dans le Deep Learning ❷ fait à partir de zéro Note: Chapitre 5
Un amateur a trébuché dans le Deep Learning ❷ fait à partir de zéro Note: Chapitre 2
Un amateur a trébuché dans le Deep Learning ❷ fait de zéro Note: Chapitre 1
Un amateur a trébuché dans le Deep Learning ❷ fait à partir de zéro Note: Chapitre 4
[Mémo d'apprentissage] Le Deep Learning fait de zéro [Chapitre 7]
Deep learning / Deep learning made from scratch Chapitre 6 Mémo
[Mémo d'apprentissage] Le Deep Learning fait de zéro [Chapitre 6]
"Deep Learning from scratch" avec Haskell (inachevé)
Deep learning / Deep learning made from scratch Chapitre 7 Mémo
[Mémo d'apprentissage] Deep Learning fait de zéro [~ Chapitre 4]
Apprentissage profond à partir de zéro
Deep Learning from scratch ① Chapitre 6 "Techniques liées à l'apprentissage"
Deep Learning from scratch Chapter 2 Perceptron (lecture du mémo)
Apprentissage profond à partir de zéro 1 à 3 chapitres
Deep learning / Deep learning made from scratch Chapitre 3 Mémo
Deep Learning / Deep Learning à partir de Zero 2 Chapitre 5 Mémo
Créez un environnement pour "Deep Learning from scratch" avec Docker
Apprentissage profond à partir de zéro (calcul des coûts)
Deep Learning / Deep Learning à partir de Zero 2 Chapitre 7 Mémo
Deep Learning / Deep Learning à partir de Zero 2 Chapitre 8 Mémo
Deep learning / Deep learning made from scratch Chapitre 5 Mémo
Deep learning / Deep learning made from scratch Chapitre 4 Mémo
Deep learning / Deep learning from scratch 2 Chapitre 3 Mémo
Mémo d'apprentissage profond créé à partir de zéro
Deep Learning / Deep Learning à partir de Zero 2 Chapitre 6 Mémo
[Deep Learning from scratch] J'ai essayé d'expliquer la confirmation du gradient d'une manière facile à comprendre.
"Deep Learning from scratch" Mémo d'auto-apprentissage (n ° 14) Exécutez le programme du chapitre 4 sur Google Colaboratory
Mémo d'auto-apprentissage "Deep Learning from scratch" (partie 8) J'ai dessiné le graphique du chapitre 6 avec matplotlib
Pourquoi ModuleNotFoundError: Aucun module nommé'dataset.mnist 'n'apparaît dans "Deep Learning from scratch".
Écrivez vos impressions sur l'édition du framework Deep Learning 3 créée à partir de zéro
Apprentissage profond à partir de zéro (propagation vers l'avant)
Apprentissage profond / Apprentissage profond à partir de zéro 2-Essayez de déplacer GRU
[Windows 10] Construction de l'environnement "Deep Learning from scratch"
Enregistrement d'apprentissage de la lecture "Deep Learning from scratch"
[Deep Learning from scratch] À propos de l'optimisation des hyper paramètres
Mémo d'auto-apprentissage "Deep Learning from scratch" (partie 12) Deep learning
Python vs Ruby «Deep Learning from scratch» Chapitre 2 Circuit logique par Perceptron
Python vs Ruby "Deep Learning from scratch" Chapitre 4 Implémentation de la fonction de perte
Mémo d'auto-apprentissage "Deep Learning from scratch" (glossaire illisible)
"Deep Learning from scratch" Mémo d'auto-apprentissage (n ° 9) Classe MultiLayerNet
Un amateur a essayé le Deep Learning avec Caffe (Introduction)
Un amateur a essayé le Deep Learning en utilisant Caffe (Practice)
Un amateur a essayé le Deep Learning avec Caffe (Vue d'ensemble)
Résumé Python vs Ruby "Deep Learning from scratch"
Mémo d'auto-apprentissage «Deep Learning from scratch» (10) Classe MultiLayerNet
Mémo d'auto-apprentissage «Deep Learning from scratch» (n ° 11) CNN
Python vs Ruby "Deep Learning from scratch" Chapitre 3 Implémentation d'un réseau neuronal à 3 couches
[Python] [Traitement du langage naturel] J'ai essayé le Deep Learning ❷ fait de toutes pièces en japonais ①
Deep Learning from scratch La théorie et la mise en œuvre de l'apprentissage profond appris avec Python Chapitre 3
Version Lua Deep Learning from scratch Part 5.5 [Rendre les fichiers pkl disponibles dans Lua Torch]
[Pour les débutants] Après tout, qu'est-ce qui est écrit dans Deep Learning fait à partir de zéro?
[Deep Learning from scratch] J'ai implémenté la couche Affine
Mémo d'auto-apprentissage «Deep Learning from scratch» (n ° 19) Augmentation des données