[PYTHON] Implémentation CNN avec juste numpy

introduction

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.

Couche pliante

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 **.

conv_layer.png

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.

Couche de regroupement

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 $.

pool.png

Apprentissage

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.

Mise à jour du poids

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}}

Erreur de propagation de retour

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.

Implémentation en python

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.

Implémentation de la couche de convolution

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. (12 - \lfloor 5 / 2 \rfloor \times 2) \times (12 - \lfloor 5 / 2 \rfloor \times 2) = 64

im2patch.png

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

Implémentation de la couche de pooling

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)

Expérimentez avec l'ensemble de données MNIST

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.

Apprentissage

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

résultat

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} $.

loss.png

en conclusion

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

Implémentation CNN avec juste numpy
Moyenne mobile avec numpy
Premiers pas avec Numpy
Apprenez avec Chemo Informatics NumPy
Concaténation de matrices avec Numpy
Code de bourdonnement avec numpy
Effectuer une analyse de régression avec NumPy
Étendre NumPy avec Rust
J'ai écrit GP avec numpy
Génération artificielle de données avec numpy
[Python] Méthode de calcul avec numpy
Essayez l'opération matricielle avec NumPy
Animation de l'équation de diffusion avec NumPy
Simulation de remboursement de dette avec numpy
Essayez d'exécuter CNN avec ChainerRL
Implémentation de SMO avec Python + NumPy
Coller les chaînes avec Numpy
Créez facilement des CNN avec Keras
Utilisez Maxout + CNN avec Pylearn2
Gérez les tableaux numpy avec f2py
Utilisez OpenBLAS avec numpy, scipy
Survivez à Noël avec CNN au niveau du personnage
Implémentation de réseau neuronal (NumPy uniquement)
Implémentation de la régression logistique avec NumPy
Décrypter le code QR avec CNN
Dessinez un beau cercle avec numpy
Jouer avec l'implémentation de l'interface utilisateur Pythonista [Action implementation]
Implémenter Keras LSTM feed forward avec numpy
Vérifiez simplement la communication série avec tk
Implémentation Score-CAM avec keras. Comparaison avec Grad-CAM
Implémentation de Light CNN (Python Keras)
Implémentation de la méthode Dyxtra par python
Extraire plusieurs éléments avec le tableau Numpy
C'est pourquoi j'ai quitté pandas [Trois façons de groupby.mean () avec juste NumPy]