J'ai implémenté CNN en python. Je l'ai implémenté uniquement avec numpy sans utiliser la bibliothèque d'apprentissage en profondeur. J'ai utilisé ["Deep Learning"](http://www.amazon.co.jp/ Deep Learning-Machine Learning Professional Series-Okaya-Takayuki / dp / 4061529021) comme manuel.
Structure de cet article
CNN
CNN est un réseau de propagation directe qui utilise des opérations de convolution et est principalement appliqué à la reconnaissance d'images. Un réseau neuronal typique est une unité entièrement connectée de couches adjacentes, Les CNN ont une couche spéciale dans laquelle seules des unités spécifiques entre des couches adjacentes sont combinées. Ces couches spéciales effectuent les opérations ** convolution ** et ** pooling **. Ce qui suit décrit la convolution et la mise en commun.
La convolution est une opération qui prend le produit des pixels correspondants du filtre sur l'image et en prend la somme. Il a pour fonction de détecter un motif de nuance similaire au motif de nuance du filtre. La taille de l'image est représentée par $ W \ fois W $, l'index est représenté par $ (i, j) $ et la valeur du pixel est représentée par $ x_ {ij} $. La taille du filtre est représentée par $ H \ fois H $, l'index est représenté par $ (p, q) $ et la valeur du pixel du filtre est représentée par $ h_ {pq} $. La convolution est exprimée par la formule suivante.
u_{ij} = \sum^{H-1}_{p=0} \sum^{H-1}_{q=0} x_{i+p, j+q} \, h_{pq}
Lorsque le filtre est déplacé dans la plage qui correspond à l'image, la taille d'image du résultat de la convolution est la suivante. Cependant, $ \ lfloor \ cdot \ rfloor $ est un opérateur qui tronque après le point décimal et le convertit en entier.
(W - 2 \lfloor H / 2 \rfloor) \times (W - 2 \lfloor H / 2 \rfloor)
Dans la couche de convolution, l'opération de convolution est effectuée comme le montre la figure ci-dessous. Soit la taille de l'image d'entrée $ W \ fois W \ fois K $ et la taille du filtre de convolution soit $ H \ fois H \ fois K \ fois M $. $ K $ représente le nombre de canaux d'image et $ M $ représente le nombre de types de filtres. Le résultat de la convolution de l'image du canal $ k $ de la couche $ l --1 $ avec le filtre $ m $ th est le suivant. Cependant, $ b_ {ijm} $ représente le biais et $ f $ représente la fonction d'activation.
\begin{align}
u_{ijm} &= \sum^{K-1}_{k=0} \sum^{H-1}_{p=0} \sum^{H-1}_{q=0} z^{(l-1)}_{i+p, j+q, k} \, h_{pqkm} + b_{ijm} \\
\\
z_{ijm} &= f(u_{ijm})
\end{align}
L'ordre de $ z_ {ijm} $ est considéré comme l'image du canal $ M $, et l'entrée de la couche suivante est $ z ^ {(l)} _ {ijm} $. En outre, les mêmes poids sont utilisés à plusieurs reprises car les filtres sont appliqués lors du décalage. C'est ce qu'on appelle le ** partage du poids **.
Dans la figure ci-dessus, le nombre de canaux de l'image d'entrée est $ K = 3 $, le type de filtre est $ M = 2 $ et l'image du canal $ 2 $ est sortie.
Le regroupement est une opération qui combine les zones locales d'une image en une seule valeur. Cela réduit la sensibilité de position des entités extraites dans la couche convolutionnelle de sorte que la sortie de la couche de regroupement ne change pas avec un certain désalignement. $ H \ fois H $ surface carrée centrée sur le pixel $ (i, j) $ sur une image de taille $ W \ fois W \ fois K $, et l'ensemble des pixels de la zone est $ P_ {ij} $ Il est représenté par. La valeur $ u_ {ijk} $ obtenue par mise en commun peut être exprimée comme suit.
u_{ijk} = \biggl(\frac{1}{H^2} \sum_{(p, q) \in P_{ij}} z^{P}_{pqk} \biggr)^{\frac{1}{P}}
Lorsque $ P = 1 $, on l'appelle ** pooling moyen ** car il fait la moyenne des pixels de la zone. Lorsque $ P = \ infty $, il est appelé ** pooling maximum ** car il prend la valeur maximum des pixels dans la zone. La figure ci-dessous montre la mise en commun maximale. L'image d'entrée de $ 4 \ times 4 $ est mise en commun avec la taille de la zone $ 2 \ times 2 $ et la foulée $ 2 $.
Je vais vous expliquer l'apprentissage de CNN. Le chapitre sur la propagation des erreurs de retour dans "Implémentation d'un réseau neuronal avec python" sera utile.
Envisagez de minimiser la fonction d'erreur $ E $ afin de rapprocher la valeur de sortie calculée à partir des données d'apprentissage de l'étiquette de l'enseignant. La fonction d'erreur $ E $ est partiellement différenciée par le poids $ w $, et le poids est mis à jour pour qu'il approche $ 0 $.
w_{new} = w_{old} - \varepsilon \frac{\partial E}{\partial w_{old}}
La méthode de calcul de la propagation en retour d'erreur est la même que celle d'un réseau neuronal général. $ l + 1 $ Erreur de couche $ \ delta ^ {(l + 1)} $ et poids $ w ^ {(l + 1)} $ et $ l $ Le produit des valeurs différentielles des entrées de la couche, $ Vous pouvez trouver l'erreur dans la couche l $. Cependant, les deux points suivants sont différents de CNN et des réseaux de neurones entièrement connectés.
Cette fois, nous avons implémenté le réseau utilisé dans Implementation of convolutional neural network by Chainer. Le code implémenté est répertorié ici [https://github.com/shota-takayama/cnn). Nous présenterons les points de montage séparément pour la couche de pliage et la couche de mise en commun.
Dans la couche de convolution, la zone locale de l'image est d'abord découpée, et celles disposées dans l'ordre sont propagées vers l'avant en entrée.
Par exemple, si vous souhaitez plier l'image d'entrée de 20 $ \ fois 12 \ fois 12 $ avec le filtre de 50 $ \ fois 20 \ fois 5 \ fois 5 $, l'image d'entrée est formée comme indiqué dans la figure ci-dessous.
La taille de l'entrée moulée sera de 64 $ \ fois 20 \ fois 5 \ fois 5 $.
Le nombre $ 64 $ est calculé comme suit.
Calculez la convolution de l'image d'entrée moulée et du filtre. Encore une fois, les tailles d'entrée et de filtre sont respectivement de 64 $ \ fois 20 \ fois 5 \ fois 5 $ et 50 $ \ fois 20 \ fois 5 \ fois 5 $.
np.tensordot(X, weight, ((1, 2, 3), (1, 2, 3)))Par$64 \times 50$La sortie de est obtenue.
<img width="1000" alt="tensordot.png " src="https://qiita-image-store.s3.amazonaws.com/0/82527/1f8aaf87-bc78-a0f2-e798-888dec58990b.png ">
Le code suivant est la partie clé de la propagation directe dans la couche de convolution.
Comme le nombre de dimensions d'entrée a été augmenté de un pour pouvoir être appris dans un mini-lot, l'argument de `` `` tensordot``` est décalé de un et ```axes = ((2, 3, 4), (1, 2, 3)) C'est ``.
```py
def __forward(self, X):
s_batch, k, xh, xw = X.shape
m = self.weight.shape[0]
oh, ow = xh - self.kh / 2 * 2, xw - self.kw / 2 * 2
self.__patch = self.__im2patch(X, s_batch, k, oh, ow)
return np.tensordot(self.__patch, self.weight, ((2, 3, 4), (1, 2, 3))).swapaxes(1, 2).reshape(s_batch, m, oh, ow)
def __im2patch(self, X, s_batch, k, oh, ow):
patch = np.zeros((s_batch, oh * ow, k, self.kh, self.kw))
for j in range(oh):
for i in range(ow):
patch[:, j * ow + i, :, :, :] = X[:, :, j:j+self.kh, i:i+self.kw]
return patch
En rétropropagation dans la couche convolutive, le produit de $ \ delta $ dans la couche précédente et le coefficient du filtre est l'erreur de rétropropagation. Je l'ai implémenté comme suit. L'erreur obtenue pour chaque zone locale est remodelée dans la forme de l'image d'entrée. C'est l'inverse du processus de découpage de l'image d'entrée dans la zone locale pendant la propagation vers l'avant.
def backward(self, delta, shape):
s_batch, k, h, w = delta.shape
delta_patch = np.tensordot(delta.reshape(s_batch, k, h * w), self.weight, (1, 0))
return self.__patch2im(delta_patch, h, w, shape)
def __patch2im(self, patch, h, w, shape):
im = np.zeros(shape)
for j in range(h):
for i in range(w):
im[:, :, j:j+self.kh, i:i+self.kw] += patch[:, j * w + i]
return im
La propagation vers l'avant dans la couche de regroupement façonne également l'image d'entrée et organise les régions locales dans l'ordre. La différence avec la couche de convolution est qu'elle stocke l'index du pixel qui a donné la valeur maximale. En effet, l'erreur est rétro-propagée à l'aide des informations à partir de quel pixel la valeur a été propagée. Le code suivant est la partie clé de la propagation directe dans la couche de regroupement.
def forward(self, X):
s_batch, k, h, w = X.shape
oh, ow = (h - self.kh) / self.s + 1, (w - self.kw) / self.s + 1
val, self.__ind = self.__max(X, s_batch, k, oh, ow)
return val
def __max(self, X, s_batch, k, oh, ow):
patch = self.__im2patch(X, s_batch, k, oh, ow)
return map(lambda _f: _f(patch, axis = 3).reshape(s_batch, k, oh, ow), [np.max, np.argmax])
def __im2patch(self, X, s_batch, k, oh, ow):
patch = np.zeros((s_batch, oh * ow, k, self.kh, self.kw))
for j in range(oh):
for i in range(ow):
_j, _i = j * self.s, i * self.s
patch[:, j * ow + i, :, :, :] = X[:, :, _j:_j+self.kh, _i:_i+self.kw]
return patch.swapaxes(1, 2).reshape(s_batch, k, oh * ow, -1)
Comme mentionné ci-dessus, la rétro-propagation dans la couche de regroupement propage l'erreur telle quelle au pixel donnant la valeur maximale. Je l'ai implémenté comme suit.
def backward(self, X, delta, act):
s_batch, k, h, w = X.shape
oh, ow = delta.shape[2:]
rh, rw = h / oh, w / ow
ind = np.arange(s_batch * k * oh * ow) * rh * rw + self.__ind.flatten()
return self.__backward(delta, ind, s_batch, k, h, w, oh, ow) * act.derivate(X)
def __backward(self, delta, ind, s_batch, k, h, w, oh, ow):
_delta = np.zeros(s_batch * k * h * w)
_delta[ind] = delta.flatten()
return _delta.reshape(s_batch, k, oh, ow, self.kh, self.kw).swapaxes(3, 4).reshape(s_batch, k, h, w)
J'ai appris les nombres manuscrits en utilisant l'ensemble de données MNIST. La structure de la couche est la même que Implémentation du réseau de neurones convolutifs par Chainer.
Les divers paramètres sont les suivants. Données d'entrée: 28 $ \ fois 28 $ image en échelle de gris 10000 $ feuilles Taux d'apprentissage: $ \ varepsilon = 0,005 $ Coefficient du terme de régularisation: $ \ lambda = 0,0001 $ Coefficient de décroissance du taux d'apprentissage: $ \ gamma = 0,9 $ Taille du lot: 5 $ $ Époque: 50 $ $ Données de test: 100 $ image en niveaux de gris de la même taille que les données d'entrée
Vous trouverez ci-dessous un graphique illustrant la perte à chaque époque. Finalement, la perte est tombée à 0,104299490259 $. De plus, la précision d'identification des images de test à 100 $ était de $ \ boldsymbol {0,96} $.
J'ai pu mettre en œuvre CNN. Je n'ai pas implémenté de remplissage ou de normalisation par lots cette fois, mais je suis fatigué alors je vais le terminer. J'ai senti que les gens qui ont créé la bibliothèque étaient extraordinaires.
Recommended Posts