[PYTHON] Tutoriel sur le réseau neuronal (CNN) de Pytorch 1.3.1.

Lecteur supposé

Je suis un débutant de Pytorch. Maintenant que vous comprenez le didacticiel 1.3.1 du réseau neuronal (CNN) de Pytorch, jetons un œil.

――J'ai une compréhension douce du mécanisme de CNN

C'est un article pour ceux qui disent. Par conséquent, je l'écris assez soigneusement. Veuillez lire uniquement là où vous en avez besoin. De plus, cette fois, nous nous concentrons sur la compréhension du tutoriel officiel, et nous n'expliquerons pas les arguments qui n'apparaissent pas dans le tutoriel. Cet article décrit le Tutoriel Pytorch 1.3.1est.

Que faire dans ce didacticiel

Dans ce didacticiel, «Pytorch place une image bidimensionnelle dans un réseau neuronal, la renvoie à la fonction objectif (propagation directe), puis met à jour chaque valeur de paramètre (méthode de propagation des erreurs: propagation arrière). Veux-tu le faire? "

image.png Source de l'image: [Tutoriel Pytorch 1.3.1] (https://pytorch.org/tutorials/beginner/blitz/neural_networks_tutorial.html#backprop )

Pour donner un aperçu plus détaillé, comme indiqué ci-dessus, l'image est la convolution d'image → regroupement → convolution → regroupement → conversion en un tableau unidimensionnel → amener à la couche de sortie (10 nœuds) avec un réseau entièrement connecté (après cela, chaque Je vais l'expliquer). Après cela, bien qu'elle ne soit pas écrite dans l'image ci-dessus, la valeur de la fonction objectif (dans ce cas, l'erreur quadratique moyenne) est calculée en comparant ce résultat de sortie avec la réponse que vous avez à l'avance, et la valeur du paramètre est mise à jour.

(En passant, lorsque CNN a été mentionné pour la première fois en 1998 comme étant idéal pour la reconnaissance d'objets simples tels que les caractères manuscrits, [Reconnaissance d'objets avec apprentissage basé sur des dégradés](http: //yann.lecun.). Il s'agit d'un LeNet à 5 couches introduit dans le document com / exdb / publis / pdf / lecun-99.pdf).

Maintenant, regardons plus de détails lors de l'écriture du code.

Faire un modèle

qiita.python


import torch
import torch.nn as nn
import torch.nn.functional as F

Tout d'abord, importez la torche. nn est un module qui contient des couches avec des paramètres, et F est un module qui contient des couches sans paramètres.

qiita.python


class Net(nn.Module):

    def __init__(self):
        super(Net, self).__init__()
        # 1 input image channel, 6 output channels, 3x3 square convolution
        # kernel
        self.conv1 = nn.Conv2d(1, 6, 3)
        self.conv2 = nn.Conv2d(6, 16, 3)
        # an affine operation: y = Wx + b
        self.fc1 = nn.Linear(16 * 6 * 6, 120)  # 6*6 from image dimension
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        # Max pooling over a (2, 2) window
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        # If the size is a square you can only specify a single number
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)
        x = x.view(-1, self.num_flat_features(x))
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

    def num_flat_features(self, x):
        size = x.size()[1:]  # all dimensions except the batch dimension
        num_features = 1
        for s in size:
            num_features *= s
        return num_features


net = Net()
print(net)

Maintenant, le réseau est défini ici. Pytorch crée une classe qui hérite de nn.Module (NET dans ce cas), et le réseau est défini dans cette classe. Je vais vous expliquer chacune des trois méthodes en divisant le code ci-dessus.

__Init__ avec des couches avec des paramètres

    def __init__(self):
        super(Net, self).__init__()

Le premier est de savoir comment traiter en couches avec des paramètres. Fondamentalement, la couche avec des paramètres est placée dans le constructeur __init __. Premièrement, super (Net, self) .__ init__ () hérite du constructeur de la classe parente. Si vous créez un constructeur dans une classe enfant, il sera écrasé, c'est donc comme prendre le contrôle du constructeur de la classe parent et ajouter les parties qui doivent être ajoutées cette fois. Au fait, super (Net, self) .__ init __ () peut être abrégé en super () .__ init __ ().

Couche pliante

        self.conv1 = nn.Conv2d(1, 6, 3)
        self.conv2 = nn.Conv2d(6, 16, 3)

conv2D est une classe utilisée pour la convolution bidimensionnelle. En d'autres termes, ici, l'image est telle que la hauteur et la largeur de l'image sont compressées. Les arguments sont (la profondeur de l'image d'entrée (dans le canal), la profondeur de l'image de sortie (hors canal) et la taille du filtre).

Que sont les canaux (profondeur, profondeur)?

Les images ont une profondeur (parfois traduite par profondeur) en plus de vertical et horizontal, et cette profondeur est appelée un canal. La profondeur correspond à la couleur dans le cas de l'image, le nombre de canaux est de 3 dans le cas du RVB, et de 1 dans le cas du monochrome. Nous y incorporons un filtre, qui est automatiquement défini comme ayant le même nombre de canaux que la couche d'entrée. Par exemple, si le nombre de canaux d'entrée est de 3, le nombre de canaux de filtre est automatiquement de 3.

Par exemple image.png Source de l'image: https://axa.biopapyrus.jp/deep-learning/cnn.html

De cette manière, si le nombre de canaux dans l'image d'entrée est de 3, le nombre de canaux dans le filtre sera également de 3. En d'autres termes, chaque canal du filtre est convolué en R, V et B, et une carte d'entités est créée comme somme.

Et le nombre de sorties changera en fonction du nombre de filtres que vous préparez. image.png Source de l'image: https://qiita.com/icoxfog417/items/5aa1b3f87bb294f84bac

En regardant le premier argument conv1, les données d'entrée sont 1 canal = monochrome, la sortie est 6 canaux et la taille du filtre est 3x3. En d'autres termes, en préparant 6 filtres avec la même profondeur de 1 que les données d'entrée et en les convoluant, 6 cartes de caractéristiques ont été produites. Le "conv2" suivant a 6 canaux de données d'entrée et 16 canaux de sortie, donc 16 filtres d'une profondeur de 6 sont alambiqués. Le nombre de canaux dans l'image au moment de la sortie est toujours le nombre de filtres.

Couche entièrement connectée

Ensuite, nn.Linear est une classe qui applique une transformation linéaire aux données d'entrée, et les arguments sont (nombre d'unités d'entrée, nombre d'unités de sortie). Un réseau entièrement connecté dans lequel toutes les unités (également appelées nœuds) sont connectées.

        self.fc1 = nn.Linear(16 * 6 * 6, 120)  # 6*6 from image dimension
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

À propos, le nombre 16 * 6 * 6 (= 576) apparaît soudainement ici, mais il s'agit d'une version unidimensionnelle des données en trois dimensions jusqu'à ce point.

Les données d'image étaient 16 canaux dans la couche de convolution avant d'arriver à cette couche entièrement connectée. Par conséquent, une donnée est (16, verticale, horizontale) des données 3D (nombre de canaux, vertical, horizontal) = (16, vertical, horizontal). Afin d'amener ces données à la couche entièrement connectée, il est nécessaire de rendre les données tridimensionnelles unidimensionnelles. Dans ce modèle, il est défini que les images d'une longueur et d'une largeur de 6 entrent ici, et 16 * 6 * 6 = 576 est le nombre de nœuds dans la première couche d'entrée de la couche entièrement connectée. Ainsi, si les données d'image à inclure dans ce modèle ne sont pas 6 * 6 verticalement et horizontalement avant la couche entièrement connectée, par exemple, une couche à changer en 6 * 6 est requise avant cette couche.

forward pour décrire la propagation vers l'avant

Vient ensuite la méthode appelée «forward». Ici, le réseau qui reçoit les données (x) en tant qu'argument et sort la valeur de la couche de sortie est décrit.

    def forward(self, x):
        # Max pooling over a (2, 2) window
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        # If the size is a square you can only specify a single number
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)
        x = x.view(-1, self.num_flat_features(x))
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

ici, input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d -> view -> linear -> relu -> linear -> relu -> linear Le flux de propagation vers l'avant est décrit. Nous avons déjà expliqué les couches avec des paramètres, nous allons donc expliquer les autres couches.

La fonction F.relu est une fonction de rampe, qui est l'une des fonctions d'activation qui effectue un traitement non linéaire sur les données alambiquées. ReLU (x) = max (x, 0), et si les données sont plus grandes que 0, leur valeur est sortie, et si elle est inférieure à 0, 0 est sortie.

La fonction max_pool2d effectue une mise en commun des valeurs maximales bidimensionnelles. Ici, nous utilisons une fenêtre 2 * 2. La figure est la suivante. image.png La valeur maximale dans la fenêtre est sortie dans l'ordre à partir du coin supérieur gauche. La mise en commun est calculée de sorte que la bordure ne couvre pas sans régler la foulée. Cette fois, c'est 2 * 2, donc les tailles verticale et horizontale sont divisées par deux ici.

Regardons de plus près la fonction view. view est une fonction qui renvoie une liste de nombres identiques aux données d'entrée sous la forme d'un nouveau tenseur de forme différente. Ceci est utilisé lors de la conversion des données d'image en données unidimensionnelles avant la couche entièrement connectée. Par exemple

>>>x = torch.randn(2,2)
>>>x
tensor([[-0.2834, -0.3660],
        [-0.1678, -0.3034]])
>>>x.view(4)
tensor([-0.2834, -0.3660, -0.1678, -0.3034])

Vous pouvez changer la forme des données comme vous l'avez dit. Cette fois, il y a un -1 au début de l'argument. Cela ajustera correctement la taille de la première dimension pour qu'elle corresponde aux autres arguments (deuxième dans ce cas). Par exemple

>>>x = torch.randn(4,3)
>>>x
tensor([[-1.2163,  1.6905,  0.1850],
        [-0.2123,  0.5995,  0.7282],
        [-0.5564, -0.1090, -0.8454],
        [-0.5643,  1.2565, -0.5475]])
>>>x.view(-1,6)
tensor([[-1.2163,  1.6905,  0.1850, -0.2123,  0.5995,  0.7282],
        [-0.5564, -0.1090, -0.8454, -0.5643,  1.2565, -0.5475]])

C'est comme ça. Si vous dites "changer 4 * 3 en x * 6", il passera automatiquement à l'optimum 2 * 6.

Considérez cette fois x = x.view (-1, self.num_flat_features (x)).

Si les données d'image d'origine sont (16,6,6), x.view (576) semble être bon, mais en fait, le tenseur d'entrée d'origine a quatre dimensions (nombre d'échantillons, nombre de canaux, longueur, largeur). C'est un tenseur de. Jusqu'à présent, je n'ai pas mentionné le nombre d'échantillons parce que je pensais que les données d'entrée étaient une image, mais en apprentissage automatique, essentiellement parce que les paramètres sont mis à jour après le traitement de plusieurs images dans un mini-lot (Pytorch's'torch ' .nn 'est fait en supposant qu'un mini-lot est utilisé), et les données d'entrée comprennent également des informations telles que le nombre d'échantillons. Par conséquent, ici, la forme de la sortie est définie sur (nombre d'échantillons, nombre de canaux x longueur x largeur), et la quantité de caractéristiques pour chaque échantillon est un tableau unidimensionnel, de sorte que la quantité de caractéristiques pour chaque échantillon est la couche entièrement connectée. Je veux que ce soit le nœud de départ.

x = x.view (-1, self.num_flat_features (x)) est cette fois x = x.view (-1, 576). Ce self.num_flat_features (x) est créé comme une méthode pour calculer le nombre de caractéristiques par échantillon, donc le résultat calculé par cette méthode est simplement remplacé ici. (Je parlerai de self.num_flat_features (x) plus tard.)

En créant un modèle de propagation vers l'avant avec cette méthode vers l'avant, la fonction vers l'arrière est également définie. La fonction arrière inverse uniquement le chemin qui provient de la propagation vers l'avant et trouve le gradient pour la fonction objectif, donc si un réseau de propagation vers l'avant est construit, cette formule sera également créée automatiquement.

Num_flat_features (x) en comptant le nombre de fonctionnalités

Ici, afin de rendre la quantité de caractéristiques autre que le nombre d'échantillons unidimensionnelle, seul le nombre de canaux x longueur x largeur est utilisé.

    def num_flat_features(self, x):
        size = x.size()[1:]  # all dimensions except the batch dimension
        num_features = 1
        for s in size:
            num_features *= s
        return num_features

Il multiplie simplement les dimensions autres que le premier nombre d'échantillons des données d'entrée. «[1:]» signifie extraire 16 ou plus (= index [1] ou plus) de (0,16,6, ...). num_features * = s =num_features = num_features * s.

En d'autres termes, ici, (nombre d'échantillons, 16,6,6), donc après le passage à (16,6,6), 16 * 6 * 6 par image est utilisé pour calculer le nombre de caractéristiques.

Le dessin de conception du modèle est maintenant terminé. Instancions-le.

>>>net = Net()
>>>print(net)
Net(
  (conv1): Conv2d(1, 6, kernel_size=(3, 3), stride=(1, 1))
  (conv2): Conv2d(6, 16, kernel_size=(3, 3), stride=(1, 1))
  (fc1): Linear(in_features=576, out_features=120, bias=True)
  (fc2): Linear(in_features=120, out_features=84, bias=True)
  (fc3): Linear(in_features=84, out_features=10, bias=True)
)

Dans le processus d'apprentissage automatique, l'apprentissage est effectué à l'aide d'un objet qui instancie une classe définie par soi-même (ici, la classe Net ()).

Organiser la taille de l'image

Pour être prudent, trions la taille d'image que ce modèle est censé être et les changements de taille d'image jusqu'à présent.

input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d -> view -> linear -> relu -> linear -> relu -> linear

Cette fois, "Les données d'entrée de LeNet sont supposées être 32 * 32" est écrit dans le tutoriel officiel de pytorch, mais je pense que 30 est l'optimum pour ce paramètre. Entrez les données 30 × 30 → La taille du filtre pliant est (3 * 3,1 foulée), donc 28 * 28 → La mise en commun (2 * 2) est 14 * 14 → Pliage (3 * 3,1 foulée), donc 12 * 12 → pooling (2 * 2) → 6 * 6 à mettre dans la couche entièrement connectée, n'est-ce pas? Dans le papier d'origine, la première couche de convolution utilise une taille de filtre de 5 * 5, donc 32 * 32 est le meilleur choix pour cela. (Le regroupement de Pytorch'nn.MaxPool2d 'est tronqué après la virgule décimale, il est donc possible de saisir 32 * 32 même avec ce paramètre.)

Vérifiez les paramètres

Maintenant, vérifions les paramètres. Les paramètres à apprendre peuvent être trouvés avec net.parameter ().

>>>params = list(net.parameters())
>>>print(len(params))
>>>print(params[0].size())  # conv1's .weight
>>>print(params[1].size())

10
torch.Size([6, 1, 3, 3])
torch.Size([6])

Cette fois, il y a 10 paramètres. La première couche de convolution a les paramètres [6, 1, 3, 3] et [6]. Le paramètre de la couche de convolution est la valeur du filtre. Puisque la valeur de ce filtre est un paramètre et est mise à jour par apprentissage, elle est de 6 (nombre de canaux de sortie) x 1 (nombre de canaux d'entrée) x 3 (vertical) x 3 (horizontal) + 6 (biais). La couche convolutive suivante est [16,6,3,3] et [16] avec le même raisonnement. Si vous comprenez jusqu'ici, vous pouvez comprendre les six autres couches entièrement connectées. Ce sont [120,576], [120], [84,120], [84], [10,84], [10].

Entrer des données

Maintenant, essayons de saisir un nombre approprié comme si nous avions entré les données d'image dans le modèle précédent.

>>>input = torch.randn(1, 1, 32, 32)
>>>out = net(input)
>>>print(out)
tensor([[-0.0843,  0.0283,  0.0677,  0.0639, -0.0076, -0.0293,  0.1049,  0.2183,
         -0.1275, -0.1151]], grad_fn=<AddmmBackward>)

10 pièces sont sorties fermement. À propos, la première des données 4D au moment de la saisie est le nombre d'images par lot.

Calcul des pertes

La fonction objectif prend une paire de (valeur de sortie, cible (réponse)) comme entrée et calcule la distance entre le résultat de sortie et la réponse que vous vouliez obtenir. Il y a plusieurs fonctions de perte dans le package nn, mais cette fois nous utiliserons nn.MSELoss pour calculer l'erreur quadratique moyenne entre le résultat de sortie et la cible.

>>>output = net(input)
>>>target = torch.randn(10)  # a dummy target, for example
>>>target = target.view(1, -1)  # make it the same shape as output
>>>criterion = nn.MSELoss()

>>>loss = criterion(output, target)
>>>print(loss)
tensor(0.6110, grad_fn=<MseLossBackward>)

Mettez le résultat de sortie du modèle en sortie, et mettez un nombre approprié dans la cible cette fois pour faire correspondre la forme avec le résultat de sortie du modèle (puisque le résultat de sortie du modèle comprend également le nombre de lots (1,10) )est). La fonction de perte est instanciée et utilisée.

Mise à jour des paramètres

Il existe différentes méthodes de mise à jour des paramètres dans le module appelé optim of pytorch, et vous pouvez facilement exécuter la méthode de propagation des erreurs pour mettre à jour les paramètres.

import torch.optim as optim

# create your optimizer
optimizer = optim.SGD(net.parameters(), lr=0.01)

# in your training loop:
optimizer.zero_grad()   # zero the gradient buffers
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step()    # Does the update

ʻOptim.SGD (net.parameters (), lr = 0.01) signifie mettre à jour la valeur du paramètre spécifié ( net.parameters () `) avec un taux d'apprentissage de 0,01 en utilisant la méthode probabiliste de descente de gradient. est.

optimizer.zero_grad() Cela signifie que le gradient de la fonction objectif doit être égal à 0. Cela peut être nécessaire pour ceux qui sont familiers avec les frameworks de définition et d'exécution tels que Transflow, mais Pytorch et chainer prescrivent à l'avance le traitement des calculs de rétropropagation. Ce n'est pas nécessaire et les paramètres peuvent être mis à jour de manière flexible en stockant tout l'historique de calcul nécessaire au traitement du gradient, mais inversement, lorsque le processus de calcul de ce gradient se termine? Non précisé. Par conséquent, si ce paramètre n'est pas défini sur 0 à l'emplacement requis, le gradient des nouvelles données sera accumulé par rapport au gradient calculé à partir des données d'entrée avant cela, et le gradient correct ne peut pas être calculé. En d'autres termes, le processus d'initialisation de ce gradient doit être effectué à chaque fois que l'erreur est rétropropagée (= chaque fois qu'un lot est créé).

Jusqu'à présent, nous avons expliqué le flux de création de modèle -> propagation directe -> calcul de la fonction de perte -> propagation de retour d'erreur pour mettre à jour les paramètres avec Pytorch. Ensuite, il y a un tutoriel sur la façon de déplacer le modèle en utilisant des données réelles, alors essayez-le.

en conclusion

Cette fois, j'ai fait référence à diverses URL pour ma compréhension, je voudrais donc les présenter. Tous sont recommandés.

Source de l'image

--Mise en œuvre d'un réseau neuronal convolutif
https://qiita.com/icoxfog417/items/5aa1b3f87bb294f84bac --Réseau de neurones convergents
https://axa.biopapyrus.jp/deep-learning/cnn.html

Vient ensuite l'URL de référence

--Pliage du réseau neuronal_CNN (Vol.16)
https://products.sint.co.jp/aisia/blog/vol1-16#toc-3 --Dernières tendances de recherche sur les réseaux de neurones convolutifs (~ 2017)
https://qiita.com/yu4u/items/7e93c454c9410c4b5427#fn3

Recommended Posts

Tutoriel sur le réseau neuronal (CNN) de Pytorch 1.3.1.
[Tutoriel PyTorch ③] RÉSEAUX NEURAUX
Tutoriel [PyTorch] (version japonaise) ③ ~ NEURAL NETWORKS (Neural Network) ~
Réseau neuronal paramétrique
[Tutoriel PyTorch ①] Qu'est-ce que PyTorch?
Implémenter un réseau neuronal convolutif
Expérience de réseau de neurones pliable
Entraînez les données MNIST avec PyTorch en utilisant un réseau neuronal
Bases de PyTorch (2) -Comment créer un réseau de neurones-
Implémenter un réseau neuronal à 3 couches
Classification d'images avec un réseau de neurones auto-fabriqué par Keras et PyTorch
Réseau de neurones avec Python (scikit-learn)
3. Distribution normale avec un réseau neuronal!
[Tutoriel PyTorch ④] FORMATION D'UN CLASSIFICATEUR
Tutoriel [PyTorch] (version japonaise) ② ~ AUTOGRAD ~
Réseau de neurones commençant par Chainer
Implémentation de réseau neuronal en python
[Tutoriel PyTorch ②] Autograd: différenciation automatique
4. Entourez les paramètres avec un réseau neuronal!
J'ai essayé un réseau de neurones convolutifs (CNN) avec un tutoriel TensorFlow sur Cloud9-Classification des images manuscrites-
Implémentation de réseau neuronal (NumPy uniquement)
Tutoriel TensorFlow - Réseau neuronal à convolution (traduction)
Série d'accélération CNN ~ FCNN: Introduction du réseau neuronal convolutif de Fourier ~
Implémentation simple d'un réseau neuronal à l'aide de Chainer
Réseau neuronal avec OpenCV 3 et Python 3
[Tutoriel PyTorch ⑤] Apprentissage de PyTorch avec des exemples (Partie 2)
Paquet réseau Basian ~ Exécution du tutoriel Pebl ~
Implémentation d'un réseau de neurones à deux couches 2
Modèle de classification simple avec réseau neuronal
Qu'est-ce que le réseau neuronal convolutif?
Tutoriel TensorFlow J'ai essayé CNN 4th
[TensorFlow] [Keras] Construction d'un réseau neuronal avec Keras
J'ai essayé de mettre en œuvre un réseau de neurones à deux couches
Théorie et implémentation simples des réseaux neuronaux
Touchez l'objet du réseau neuronal
[Traitement du langage 100 coups 2020] Chapitre 8: Réseau neuronal
[Tutoriel PyTorch ⑤] Apprentissage de PyTorch avec des exemples (Partie 1)
[Tutoriel PyTorch ⑥] Qu'est-ce que torch.nn?