Je voulais utiliser un réseau de neurones profonds comme Residual Network. ImageNet a utilisé l'ensemble de données d'image CIFAR-10 pour classer les images car MNIST ne suffit pas et ImageNet semble difficile à collecter des données et prend du temps à apprendre.
L'ensemble de données d'image CIFAR-10 est un ensemble de données d'image couleur de petite taille https://www.cs.toronto.edu/~kriz/cifar.html
Je l'ai exécuté dans l'environnement suivant
Le code source utilisé est ci-dessous. Les commandes décrites ci-dessous supposent que vous avez cloné ce code et que vous êtes à la racine de l'arborescence source. https://github.com/dsanno/chainer-cifar
Il a les fonctions suivantes
Lors de la classification, le taux d'erreur a été mesuré comme suit.
Vous pouvez le télécharger à partir du lien «CIFAR-10 python version» à https://www.cs.toronto.edu/~kriz/cifar.html. Ou téléchargez le jeu de données avec la commande suivante. Le fichier du jeu de données fait 166 Mo et cela prend du temps si la ligne est fine.
$ python src/download.py
Décompressez l'ensemble de données téléchargé et vous verrez l'image suivante.
Le contenu de data_batch_1 etc. est dict, et les données brutes de l'image sont stockées dans "data" et les informations d'étiquette sont stockées dans "étiquettes". Voici un exemple qui enregistre les 100 premiers sous forme d'image tout en examinant data_batch_1.
$ python
>>> import cPickle as pickle
>>> f = open('dataset/cifar-10-batches-py/data_batch_1', 'rb')
>>> train_data = pickle.load(f)
>>> f.close()
>>> type(train_data['data'])
<type 'numpy.ndarray'>
>>> train_data['data'].shape #Obtenez la forme des données brutes
(10000L, 3072L)
>>> train_data['data'][:5] #Obtenez les 5 premières données brutes
array([[ 59, 43, 50, ..., 140, 84, 72],
[154, 126, 105, ..., 139, 142, 144],
[255, 253, 253, ..., 83, 83, 84],
[ 28, 37, 38, ..., 28, 37, 46],
[170, 168, 177, ..., 82, 78, 80]], dtype=uint8)
>>> type(train_data['labels'])
<type 'list'>
>>> train_data['labels'][:10] #Obtenez les 10 premières données d'étiquette
[6, 9, 9, 4, 1, 1, 2, 7, 8, 3]
>>> from PIL import Image
>>> sample_image = train_data['data'][:100].reshape((10, 10, 3, 32, 32)).transpose((0, 3, 1, 4, 2)).reshape((320, 320, 3)) #Trier les 100 premières pièces en tuiles
>>> Image.fromarray(sample_image).save('sample.png')
Vous pouvez obtenir les images suivantes
Exécutez la commande suivante pour générer un ensemble de données avec 3 types de prétraitement.
$ python src/dataset.py
Pour "Valeur moyenne de l'image", nous avons utilisé la valeur moyenne des valeurs RVB de l'ensemble des données d'entraînement indépendamment de RVB. La normalisation du contraste a rendu le contraste uniforme en soustrayant la valeur moyenne de chaque image de la valeur RVB, puis en la multipliant par une constante de sorte que l'écart type devienne 1. Je ne comprends pas vraiment le blanchiment ZCA, mais Toki no Mori Wiki Dit qu'une "transformation pour que la matrice de covariance des données devienne une matrice unitaire" est effectuée. Le calcul spécifique du blanchiment est décrit en détail dans Mambo Diary "CIFAR-10 and ZCA whitening".
L'image après normalisation du contraste + blanchiment ZCA est la suivante. Si elle est laissée telle quelle, la distribution des valeurs RVB est étroite, elle est donc normalisée afin que la distribution des valeurs RVB s'étale dans la plage de 0 à 255 pour chaque image.
Les augmentations suivantes sont effectuées dans tous les apprentissages. Au moment du test, l'augmentation n'est pas effectuée et les données de test prétraitées sont utilisées telles quelles.
Le code de la partie d'augmentation est le suivant.
import numpy as np
(Omission)
def __trans_image(self, x):
size = 32
n = x.shape[0]
images = np.zeros((n, 3, size, size), dtype=np.float32)
offset = np.random.randint(-4, 5, size=(n, 2))
mirror = np.random.randint(2, size=n)
for i in six.moves.range(n):
image = x[i]
top, left = offset[i]
left = max(0, left)
top = max(0, top)
right = min(size, left + size)
bottom = min(size, left + size)
if mirror[i] > 0:
images[i,:,size-bottom:size-top,size-right:size-left] = image[:,top:bottom, left:right][:,:,::-1]
else:
images[i,:,size-bottom:size-top,size-right:size-left] = image[:,top:bottom,left:right]
return images
Apprenons avec un réseau relativement peu profond comme celui ci-dessous. Utilisez une structure de type réseau utilisée dans le didacticiel Tensorflor (https://www.tensorflow.org/versions/r0.9/tutorials/deep_cnn/index.html). (Pas exactement la même chose, il existe des différences dans la composition des calques et les valeurs des paramètres initiaux) Après avoir empilé 3 couches de réseau neuronal à convolution (CNN) + ReLU + MaxPooling, il y a 2 couches de couche entièrement connectée. Après la couche entièrement connectée, une suppression est fournie pour supprimer le surapprentissage.
class CNN(chainer.Chain):
def __init__(self):
super(CNN, self).__init__(
conv1=L.Convolution2D(3, 64, 5, stride=1, pad=2),
conv2=L.Convolution2D(64, 64, 5, stride=1, pad=2),
conv3=L.Convolution2D(64, 128, 5, stride=1,
pad=2),
l1=L.Linear(4 * 4 * 128, 1000),
l2=L.Linear(1000, 10),
)
def __call__(self, x, train=True):
h1 = F.max_pooling_2d(F.relu(self.conv1(x)), 3, 2)
h2 = F.max_pooling_2d(F.relu(self.conv2(h1)), 3, 2)
h3 = F.max_pooling_2d(F.relu(self.conv3(h2)), 3, 2)
h4 = F.relu(self.l1(F.dropout(h3, train=train)))
return self.l2(F.dropout(h4, train=train))
Exécutez l'apprentissage avec la commande suivante. Il a fallu environ 40 minutes pour fonctionner.
$ python src/train.py -g 0 -m cnn -b 128 -p cnn --optimizer adam --iter 300 --lr_decay_iter 100
La signification des options est la suivante
La courbe d'erreur ressemble à ceci, avec un taux d'erreur de test de 18,94%. En regardant la courbe d'erreur, si vous réduisez le taux d'apprentissage après avoir appris pendant un certain temps, l'apprentissage progressera à nouveau rapidement. C'est une technique utilisée pour programmer le taux d'apprentissage pour diminuer après un certain nombre de fois de cette manière.
Cette fois, nous nous entraînerons à l'aide d'un ensemble de données qui a subi une normalisation du contraste + un blanchiment ZCA lors du prétraitement. Exécutez l'apprentissage avec la commande suivante. Il a fallu environ 40 minutes pour fonctionner.
$ python src/train.py -g 0 -m cnn -b 128 -p cnn_zca --optimizer adam --iter 300 --lr_decay_iter 100 -d dataset/image_norm_zca.pkl
"-d dataset / image_norm_zca.pkl" spécifie l'ensemble de données qui a subi la normalisation du contraste + le blanchiment ZCA.
La courbe d'erreur ressemble à ceci, avec un taux d'erreur de test de 18,76%. Le résultat est que même s'il vaut mieux que de simplement soustraire la valeur moyenne, elle est presque inchangée.
La normalisation par lots est une méthode pour normaliser la sortie d'une couche spécifique à 0 en moyenne et à 1 sur la distribution pour chaque mini-lot.
Le but est de faciliter l'apprentissage de la couche suivante en normalisant.
L'algorithme est décrit en détail dans cet article.
Vous pouvez utiliser chainer.links.BatchNormalization
pour effectuer la normalisation par lots avec Chainer.
Le code réseau utilisé cette fois est indiqué ci-dessous. La configuration du réseau est presque la même que celle que nous avons utilisée précédemment, la seule différence est la présence ou l'absence de normalisation par lots.
class BatchConv2D(chainer.Chain):
def __init__(self, ch_in, ch_out, ksize, stride=1, pad=0, activation=F.relu):
super(BatchConv2D, self).__init__(
conv=L.Convolution2D(ch_in, ch_out, ksize, stride, pad),
bn=L.BatchNormalization(ch_out),
)
self.activation=activation
def __call__(self, x, train):
h = self.bn(self.conv(x), test=not train)
if self.activation is None:
return h
return F.relu(h)
class CNNBN(chainer.Chain):
def __init__(self):
super(CNNBN, self).__init__(
bconv1=BatchConv2D(3, 64, 5, stride=1, pad=2),
bconv2=BatchConv2D(64, 64, 5, stride=1, pad=2),
bconv3=BatchConv2D(64, 128, 5, stride=1, pad=2),
l1=L.Linear(4 * 4 * 128, 1000),
l2=L.Linear(1000, 10),
)
def __call__(self, x, train=True):
h1 = F.max_pooling_2d(self.bconv1(x, train), 3, 2)
h2 = F.max_pooling_2d(self.bconv2(h1, train), 3, 2)
h3 = F.max_pooling_2d(self.bconv3(h2, train), 3, 2)
h4 = F.relu(self.l1(F.dropout(h3, train=train)))
return self.l2(F.dropout(h4, train=train))
Pour apprendre à utiliser la normalisation par lots, entrez la commande suivante: Il a fallu environ 50 minutes pour fonctionner.
$ python src/train.py -g 0 -m cnnbn -b 128 -p cnnbn --optimizer adam --iter 300 --lr_decay_iter 100
Utilisez le modèle avec normalisation par lots avec "-m cnnbn".
La courbe d'erreur ressemble à ceci, avec un taux d'erreur de 12,40%. Vous pouvez voir que le taux d'erreur est considérablement inférieur à celui sans normalisation par lots.
J'ai également essayé d'utiliser les données d'entraînement de Contrast Normalization + ZCA Whitening. La commande est la suivante.
$ python src/train.py -g 0 -m cnnbn -b 128 -p cnnbn --optimizer adam --iter 300 --lr_decay_iter 100 -d dataset/image_norm_zca.pkl
Le taux d'erreur était de 12,27%, ce qui était légèrement supérieur à la simple soustraction de la moyenne.
Utilisez un modèle similaire à la couche VGG 16 ou VGG 19. Le modèle VGG a une couche entièrement connectée après avoir répété plusieurs fois plusieurs CNN de taille de noyau 3 + Max Pooling. J'ai utilisé un réseau basé sur VGG le Jour "L'histoire de Kaggle CIFAR-10", j'ai donc utilisé un réseau similaire. Apprendre. Ce blog a obtenu un score élevé de 94,15% de taux de reconnaissance des données de test.
Les différences avec "L'histoire de Kaggle CIFAR-10" sont les suivantes.
Cette implémentation | Kaggle CIFAR-10 histoires | |
---|---|---|
Des données d'entrée | 32 x 32px | 24 x 24 px |
Augmentation(Pendant l'apprentissage) | Mouvement parallèle, inversion gauche-droite | Mouvement parallèle, inversion gauche-droite、拡大 |
Augmentation(Au moment du test) | Aucun | Mouvement parallèle, inversion gauche-droite, agrandissement |
Nombre de modèles | 1 pièce | 6(Utilisez la sortie moyenne de chaque modèle) |
Batch Normaliztion | Oui | Aucun |
Il se présente comme suit lorsqu'il est décrit à l'aide de Chainer.
class VGG(chainer.Chain):
def __init__(self):
super(VGG, self).__init__(
bconv1_1=BatchConv2D(3, 64, 3, stride=1, pad=1),
bconv1_2=BatchConv2D(64, 64, 3, stride=1, pad=1),
bconv2_1=BatchConv2D(64, 128, 3, stride=1, pad=1),
bconv2_2=BatchConv2D(128, 128, 3, stride=1, pad=1),
bconv3_1=BatchConv2D(128, 256, 3, stride=1, pad=1),
bconv3_2=BatchConv2D(256, 256, 3, stride=1, pad=1),
bconv3_3=BatchConv2D(256, 256, 3, stride=1, pad=1),
bconv3_4=BatchConv2D(256, 256, 3, stride=1, pad=1),
fc4=L.Linear(4 * 4 * 256, 1024),
fc5=L.Linear(1024, 1024),
fc6=L.Linear(1024, 10),
)
def __call__(self, x, train=True):
h = self.bconv1_1(x, train)
h = self.bconv1_2(h, train)
h = F.dropout(F.max_pooling_2d(h, 2), 0.25, train=train)
h = self.bconv2_1(h, train)
h = self.bconv2_2(h, train)
h = F.dropout(F.max_pooling_2d(h, 2), 0.25, train=train)
h = self.bconv3_1(h, train)
h = self.bconv3_2(h, train)
h = self.bconv3_3(h, train)
h = self.bconv3_4(h, train)
h = F.dropout(F.max_pooling_2d(h, 2), 0.25, train=train)
h = F.relu(self.fc4(F.dropout(h, train=train)))
h = F.relu(self.fc5(F.dropout(h, train=train)))
h = self.fc6(h)
return h
Exécutez-le avec la commande suivante. Un modèle de type VGG est spécifié avec "-m vgg". Il a fallu environ cinq heures et demie pour fonctionner.
$ python src/train.py -g 0 -m vgg -b 128 -p vgg_adam --optimizer adam --iter 300 --lr_decay_iter 100
Le taux d'erreur est de 7,65%, améliorant la précision de la reconnaissance.
Residual Network est un réseau qui combine une transformation uniforme et plusieurs couches de CNN afin que l'apprentissage puisse être bien effectué même si la hiérarchie est approfondie. Il est décrit en détail dans cet article.
Le réseau mis en œuvre cette fois a un total de 74 couches et a la configuration suivante. En ce qui concerne la façon de compter les couches, étant donné que le bloc résiduel utilise 2 couches de CNN, un bloc résiduel est compté comme 2 couches.
Je voulais vraiment en faire une couche 110, mais je ne pouvais pas l'exécuter en raison du manque de mémoire GPU. Nous avons confirmé que si l'image d'entrée est réduite à 24 x 24px, elle peut être exécutée avec 110 couches.
L'implémentation réseau dans Chainer ressemble à ceci:
class ResidualBlock(chainer.Chain):
def __init__(self, ch_in, ch_out, stride=1, swapout=False, skip_ratio=0, activation1=F.relu, activation2=F.relu):
w = math.sqrt(2)
super(ResidualBlock, self).__init__(
conv1=L.Convolution2D(ch_in, ch_out, 3, stride, 1, w),
bn1=L.BatchNormalization(ch_out),
conv2=L.Convolution2D(ch_out, ch_out, 3, 1, 1, w),
bn2=L.BatchNormalization(ch_out),
)
self.activation1 = activation1
self.activation2 = activation2
self.skip_ratio = skip_ratio
self.swapout = swapout
def __call__(self, x, train):
skip = False
if train and self.skip_ratio > 0 and np.random.rand() < self.skip_ratio:
skip = True
sh, sw = self.conv1.stride
c_out, c_in, kh, kw = self.conv1.W.data.shape
b, c, hh, ww = x.data.shape
if sh == 1 and sw == 1:
shape_out = (b, c_out, hh, ww)
else:
hh = (hh + 2 - kh) // sh + 1
ww = (ww + 2 - kw) // sw + 1
shape_out = (b, c_out, hh, ww)
h = x
if x.data.shape != shape_out:
xp = chainer.cuda.get_array_module(x.data)
n, c, hh, ww = x.data.shape
pad_c = shape_out[1] - c
p = xp.zeros((n, pad_c, hh, ww), dtype=xp.float32)
p = chainer.Variable(p, volatile=not train)
x = F.concat((p, x))
if x.data.shape[2:] != shape_out[2:]:
x = F.average_pooling_2d(x, 1, 2)
if skip:
return x
h = self.bn1(self.conv1(h), test=not train)
if self.activation1 is not None:
h = self.activation1(h)
h = self.bn2(self.conv2(h), test=not train)
if not train:
h = h * (1 - self.skip_ratio)
if self.swapout:
h = F.dropout(h, train=train) + F.dropout(x, train=train)
else:
h = h + x
if self.activation2 is not None:
return self.activation2(h)
else:
return h
class ResidualNet(chainer.Chain):
def __init__(self, depth=18, swapout=False, skip=True):
super(ResidualNet, self).__init__()
links = [('bconv1', BatchConv2D(3, 16, 3, 1, 1), True)]
skip_size = depth * 3 - 3
for i in six.moves.range(depth):
if skip:
skip_ratio = float(i) / skip_size * 0.5
else:
skip_ratio = 0
links.append(('res{}'.format(len(links)), ResidualBlock(16, 16, swapout=swapout, skip_ratio=skip_ratio, ), True))
links.append(('res{}'.format(len(links)), ResidualBlock(16, 32, stride=2, swapout=swapout), True))
for i in six.moves.range(depth - 1):
if skip:
skip_ratio = float(i + depth) / skip_size * 0.5
else:
skip_ratio = 0
links.append(('res{}'.format(len(links)), ResidualBlock(32, 32, swapout=swapout, skip_ratio=skip_ratio), True))
links.append(('res{}'.format(len(links)), ResidualBlock(32, 64, stride=2, swapout=swapout), True))
for i in six.moves.range(depth - 1):
if skip:
skip_ratio = float(i + depth * 2 - 1) / skip_size * 0.5
else:
skip_ratio = 0
links.append(('res{}'.format(len(links)), ResidualBlock(64, 64, swapout=swapout, skip_ratio=skip_ratio), True))
links.append(('_apool{}'.format(len(links)), F.AveragePooling2D(8, 1, 0, False, True), False))
links.append(('fc{}'.format(len(links)), L.Linear(64, 10), False))
for name, f, _with_train in links:
if not name.startswith('_'):
self.add_link(*(name, f))
self.layers = links
def __call__(self, x, train=True):
h = x
for name, f, with_train in self.layers:
if with_train:
h = f(h, train=train)
else:
h = f(h)
return h
Il existe un paramètre appelé swapout, mais c'est une implémentation expérimentale, alors ignorez-le pour le moment. Dans le bloc résiduel, lorsque les tailles d'entrée et de sortie sont différentes (la largeur et la hauteur sont plus petites en sortie, le nombre de canaux est le même ou la sortie est plus grande), la partie de conversion d'égalité est la suivante.
Exécutez-le avec la commande suivante.
python src/train.py -g 0 -m residual -b 128 -p residual --res_depth 12 --optimizer sgd --lr 0.1 --iter 300 --lr_decay_iter 100
«-m residuel» est la spécification du réseau résiduel.
--optimizer sgd
est une spécification qui utilise Momentum SGD, ce qui donne de meilleurs résultats que l'utilisation d'Adam.
La valeur initiale du taux d'apprentissage semble être de 0,1.
Dans la mise en œuvre de la classification d'image CIFAR-10 à l'aide du réseau résiduel répertorié ci-dessous, le taux d'apprentissage initial était de 0,1.
Il a fallu environ 10 heures pour fonctionner. Le taux d'erreur de test était de 8,06%, ce qui était pire que le modèle de type VGG.
La profondeur stochastique est une méthode permettant de sauter de manière probabiliste un bloc résiduel pendant l'apprentissage. L'explication est détaillée dans cet article.
Le code de réseau est publié dans Utilisation du réseau résiduel (en utilisant le # réseau-résiduel).
Donnez à ReisualBlock
une propriété appelée skip_ratio
, et empêchez la partie CNN de Residual Block d'être exécutée avec la probabilité spécifiée dans skip_ratio
pendant l'apprentissage.
Lors du test, utilisez la valeur obtenue en multipliant la partie CNN par (1 --skip_ratio)
.
Le skip_ratio
est incliné de sorte que plus le bloc résiduel est profond, plus le skip_ratio
est grand.
Cette fois, skip_ratio
vaut 0 pour le premier bloc résiduel, 0,5 pour le skip_ratio
le plus profond, et les blocs intermédiaires sont modifiés linéairement.
Exécutez-le avec la commande suivante.
python src/train.py -g 0 -m residual -b 128 -p residual_skip --skip_depth --res_depth 12 --optimizer sgd --lr 0.1 --iter 300 --lr_decay_iter 100
--skip_depth
est une option pour utiliser la profondeur stochastique.
Il a fallu environ 9 heures pour apprendre. Le taux d'erreur de test est maintenant de 7,42%, améliorant la précision.
Nous avons classé l'ensemble de données d'image CIFAR-10 à l'aide de divers modèles. Nous avons pu confirmer la différence de taux de reconnaissance due à la différence de prétraitement, à la présence ou à l'absence de normalisation des lots et à la différence des modèles.
Cette fois, je l'ai implémenté avec Chainer, mais par exemple, Implementation of Stochastic Depth by Torch7 est plus rapide et économise de la mémoire que l'implémentation utilisée cette fois, et le même environnement (https://github.com/yueatsprograms/Stochastic_Depth) J'ai pu former 56layer Residual Network sur Ubuntu). Si vous souhaitez exécuter un modèle plus profond, vous pouvez envisager un autre framework.
Recommended Posts