[PYTHON] Lire et mettre en œuvre l'apprentissage résiduel profond pour la reconnaissance d'image

Dans cet article, nous présenterons le papier "Deep Residual Learning for Image Recognition" (CVPR 2016) \ [1] proposé par le célèbre ResNet. De plus, j'ai implémenté le code de la classification CIFAR-10 qui est en cours de travail dans l'article (GitHub) et en fait réalisé une expérience de reproduction. ResNet proposé dans cet article a non seulement remporté le concours ILSVRC pour la précision de classification d'ImageNet, mais est également utilisé dans une grande variété de tâches en raison de l'efficacité et de la polyvalence de la méthode de proposition qui gère les réseaux profonds. (Le nombre de citations dépasse 52000 en août 2020).

Commentaire papier

Aperçu

On a traditionnellement dit que les réseaux de neurones profonds sont difficiles à apprendre. Dans cet article, au lieu d'apprendre directement la vraie valeur, nous utilisons une structure dans laquelle de nombreux blocs du réseau neuronal qui apprennent le «résidu» (différence par rapport à la vraie valeur) sont connectés pour faciliter l'apprentissage des réseaux profonds. Performances grandement améliorées pour diverses tâches de reconnaissance d'image.

Contexte

Dans la reconnaissance d'images, on sait que plus la hiérarchie du réseau est profonde, plus les caractéristiques sémantiques peuvent être extraites. Cependant, le simple empilement des couches du réseau a causé des problèmes tels que la disparition du gradient / l'explosion du gradient, et les paramètres du réseau n'ont pas convergé et l'apprentissage n'a pas pu être effectué correctement. Ce problème de difficulté de convergence a été résolu par la valeur initiale du réseau et la méthode de normalisation, mais même si elle converge, il y avait un problème que la précision diminue à mesure que la couche s'approfondit (ceci). N'est pas surentraîné, et l'erreur d'entraînement est pire comme le montre le graphique ci-dessous). image.png (La figure est tirée du papier. Il en va de même ci-dessous)

Méthode proposée

La méthode proposée dans cet article consiste à réaliser un réseau profond en connectant un grand nombre de petits "blocs résiduels". Chaque bloc résiduel se compose de plusieurs couches de poids et d'une cartographie d'identité, comme illustré dans la figure ci-dessous. En supposant que la fonction à exprimer dans le bloc entier est $ H (x) $, $ F (x) = H (x) --x $ sera appris dans la partie où les couches de poids sont combinées. C'est pourquoi il est appelé «résiduel». image.png Cette méthode résout le problème de "plus la couche est profonde, plus la précision est faible" mentionnée à la fin du fond. En fait, il est difficile d'apprendre le mappage d'identité ($ H (x) = x $) pour plusieurs couches non linéaires, et ce mappage ne peut pas être bien appris dans les blocs où le mappage d'identité est la solution optimale lorsque le nombre de couches est augmenté. On dit que la précision diminuera. Cependant, si vous utilisez le bloc résiduel proposé, vous pouvez facilement apprendre le mappage d'identité en définissant simplement le poids de la couche de pondération sur 0. En pratique, il s'agit rarement d'une véritable identité, mais cela facilite l'apprentissage dans les cas où $ x $ et $ H (x) $ sont très petits.

Détails du bloc résiduel

Lorsque la sortie du bloc résiduel est $ y , le bloc peut être exprimé par une telle opération. $y = F(x) + x$$ Ce $ F (x) $ est réalisé en combinant deux ou plusieurs couches. Par exemple, dans la figure ci-dessus, il y a deux couches, donc $ \ sigma $ est utilisé comme fonction ReLU. $F(x) = W_2\sigma(W_1x)$ Il peut être exprimé par (le terme de biais est omis ici). L'opération non linéaire par ReLU est en fait effectuée après l'ajout final, donc c'est en fait $y = \sigma(W_2\sigma(W_1x) + x)$ On dirait. Si le nombre de canaux de $ x $ et $ F (x) $ ne correspond pas, le nombre de canaux est ajusté en complétant avec 0 ou en effectuant une convolution 1x1.

Structure globale du réseau

image.png Ce schéma de réseau est un réseau pour résoudre les problèmes de classification ImageNet. La figure à l'extrême droite est la méthode proposée. La structure du réseau est conçue sur la base du principe que si la taille de la carte d'entités est la même, le nombre de canaux est le même, et si la taille de la carte d'entités est divisée par deux, le nombre de canaux est doublé. L'opération de réduction de la taille de la carte des caractéristiques consiste essentiellement à définir la foulée de la convolution de la première couche sur 2, et la mise en commun n'est pas effectuée (autre que la première et la dernière). (À propos, le regroupement avant d'entrer dans la dernière couche FC est le regroupement moyen global.) La normalisation par lots s'exécute immédiatement après chaque convolution.

Différences d'implémentation pour chaque ensemble de données

CIFAR-10 La classification d'image de CIFAR-10 a une structure de réseau légèrement différente car la taille d'image d'entrée est beaucoup plus petite qu'ImageNet. La première couche est une simple convolution 3x3, suivie de blocs résiduels. La couche de convolution utilise 6n $ + 1 $ couches, et la répartition est comme indiqué dans le tableau ci-dessous. Le résultat est $ 3n $ blocs résiduels. image.png La mise en commun moyenne globale est effectuée après cette couche de 6n $ + 1 $ et la classification est effectuée à l'aide de 10 couches FC. Les expériences seront menées à $ n = \ {3, 5, 7, 9 \} $, ce qui donnera un réseau de couches de 20, 32, 44, 56 $, respectivement. Lorsque le nombre de canaux est différent, l'ajout du mappage d'identité se fait en remplissant la partie manquante avec 0.

Mise en œuvre de la reproduction

Parmi les ensembles de données présentés dans cet article, nous avons implémenté tout le code qui résout le problème de classification CIFAR-10 à l'aide de PyTorch. L'ensemble du code source a été publié sur GitHub.

code

Seules les parties de définition de modèle les plus importantes sont également affichées ici. Le bloc résiduel est implémenté comme une classe ResNetCifarBlock, et la fonction génériquemake_resblock_group qui crée des groupes avec le même nombre de canaux est implémentée pour rendre le code concis et extensible. Là où la taille de la carte des caractéristiques et le nombre de canaux changent, nous réduisons d'abord les pixels, puis remplissons des zéros.

nets.py


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


class ResNetCifarBlock(nn.Module):
    def __init__(self, input_nc, output_nc):
        super().__init__()
        stride = 1
        self.expand = False
        if input_nc != output_nc:
            assert input_nc * 2 == output_nc, 'output_nc must be input_nc * 2'
            stride = 2
            self.expand = True

        self.conv1 = nn.Conv2d(input_nc, output_nc, kernel_size=3, stride=stride, padding=1)
        self.bn1 = nn.BatchNorm2d(output_nc)
        self.conv2 = nn.Conv2d(output_nc, output_nc, kernel_size=3, stride=1, padding=1)
        self.bn2 = nn.BatchNorm2d(output_nc)

    def forward(self, x):
        xx = F.relu(self.bn1(self.conv1(x)), inplace=True)
        y = self.bn2(self.conv2(xx))
        if self.expand:
            x = F.interpolate(x, scale_factor=0.5, mode='nearest')  # subsampling
            zero = torch.zeros_like(x)
            x = torch.cat([x, zero], dim=1)  # option A in the original paper
        h = F.relu(y + x, inplace=True)
        return h


def make_resblock_group(cls, input_nc, output_nc, n):
    blocks = []
    blocks.append(cls(input_nc, output_nc))
    for _ in range(1, n):
        blocks.append(cls(output_nc, output_nc))
    return nn.Sequential(*blocks)


class ResNetCifar(nn.Module):
    def __init__(self, n):
        super().__init__()

        self.conv = nn.Conv2d(3, 16, kernel_size=3, stride=1, padding=1)
        self.bn = nn.BatchNorm2d(16)
        self.block1 = make_resblock_group(ResNetCifarBlock, 16, 16, n)
        self.block2 = make_resblock_group(ResNetCifarBlock, 16, 32, n)
        self.block3 = make_resblock_group(ResNetCifarBlock, 32, 64, n)
        self.pool = nn.AdaptiveAvgPool2d(output_size=(1, 1))  # global average pooling
        self.fc = nn.Linear(64, 10)

    def forward(self, x):
        x = F.relu(self.bn(self.conv(x)), inplace=True)
        x = self.block1(x)
        x = self.block2(x)
        x = self.block3(x)
        x = self.pool(x)
        x = x.view(x.shape[0], -1)
        x = self.fc(x)
        return x

Jeu de données utilisé et divers paramètres

Tout dans le journal le suit.

base de données

CIFAR-10 est un ensemble de données contenant 60 000 images de 10 classes, dont 50 000 ont été utilisées pour la formation et 10 000 pour l'évaluation. La taille de l'image est de 32x32.

Paramètres

résultat

Les résultats de l'apprentissage et de l'évaluation avec les paramètres ci-dessus sont les suivants. Le taux d'erreur Top-1 est utilisé comme indice d'évaluation. Il représente la moyenne ± écart type en 5 essais.

Méthode n Top-1 error rate (%) Reported error rate (%)
ResNet-20 3 8.586 ± 0.120 8.75
ResNet-32 5 7.728 ± 0.318 7.51
ResNet-44 7 7.540 ± 0.475 7.17
ResNet-56 9 7.884 ± 0.523 6.97

Plus la couche est profonde, plus la variation du taux d'erreur est grande, et bien que la valeur moyenne soit différente de la valeur rapportée dans le papier, on peut dire que la valeur est généralement proche de la valeur du papier. (Bien que cela n'ait pas été écrit, je pense que c'est une valeur raisonnable si la valeur papier est exécutée plusieurs fois et utilise le mieux.)

Les références

Recommended Posts

Lire et mettre en œuvre l'apprentissage résiduel profond pour la reconnaissance d'image
Implémentation du modèle Deep Learning pour la reconnaissance d'images
Implémentation du modèle de reconnaissance d'images d'apprentissage en profondeur 2
[AI] Apprentissage en profondeur pour le débruitage d'image
Modèle de reconnaissance d'image utilisant l'apprentissage profond en 2016
Reconnaissance d'image en apprentissage profond 3 après la création du modèle
Apprentissage profond pour la formation composée?
Implémenter le deep learning / VAE (Variational Autoencoder)
Deep learning 2 appris par l'implémentation (classification d'images)
Créez votre propre PC pour un apprentissage en profondeur
Alignement d'image: du SIFT au deep learning
[Apprentissage en profondeur] Détection de visage Nogisaka ~ Pour les débutants ~
Reconnaissance d'image
L'apprentissage en profondeur
À propos du traitement d'expansion des données pour l'apprentissage en profondeur
Ordre d'étude recommandé pour les débutants en apprentissage automatique / apprentissage en profondeur
Principes de base de la technologie de reconnaissance d'image (pour les débutants)
[Implémentation pour l'apprentissage] Implémentation de l'échantillonnage stratifié en Python (1)
J'ai installé le framework Deep Learning Chainer
Mémorandum d'apprentissage profond
Commencer l'apprentissage en profondeur
Image d'apprentissage gonflée
Apprentissage en profondeur Python
Apprentissage profond × Python
Script Python de collection d'images pour créer des ensembles de données pour l'apprentissage automatique
Analyse d'images par apprentissage profond à partir de Kaggle et Keras
[Détection d'anomalies] Détecter la distorsion de l'image par apprentissage à distance
Techniques pour comprendre la base des décisions d'apprentissage en profondeur
Apprendre en profondeur à l'expérience avec Python Chapitre 2 (Matériel pour une conférence ronde)
Intelligence artificielle, machine learning, deep learning pour mettre en œuvre et comprendre
Une scène où le GPU est utile pour le deep learning?
Introduction au Deep Learning pour la première fois (Chainer) Reconnaissance des caractères japonais Chapitre 1 [Construction de l'environnement]