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).
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.
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). (La figure est tirée du papier. Il en va de même ci-dessous)
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». 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.
Lorsque la sortie du bloc résiduel est $ y
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. 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.
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.
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
Tout dans le journal le suit.
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.
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 | 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.)
Recommended Posts