[PYTHON] Compréhension approfondie d'Im2col

Personne cible

Pour ceux qui veulent en savoir plus sur la fonction ʻim2col` qui apparaît dans la reconnaissance d'image en utilisant CNN Nous expliquerons en détail de la mise en œuvre initiale à la version améliorée, la version compatible avec le canal de traitement par lots, la version compatible avec le rembourrage de foulée à l'aide de gifs et d'images.

table des matières

[Implémentation de la foulée et du rembourrage](# Implémentation de la foulée et du rembourrage) - [Implémentation de la foulée](# Implémentation de la foulée) - [Implémentation du padding](#Implementation of padding) - [Calcul de la dimension de sortie](#Calcul de la dimension de sortie) - [Version terminée ʻim2col`](#Version terminée im2col) - [Expérimenter avec MNIST](Expérimenter avec #mnist) - [Conclusion](#Conclusion)

Qu'est-ce que ʻim2col`?

ʻIm2col` est une fonction utilisée dans la reconnaissance d'image. L'opération consiste à convertir de manière réversible un tableau multidimensionnel en un tableau à deux dimensions. Le plus grand avantage de ceci est que vous pouvez ** maximiser les avantages de numpy pour des opérations matricielles rapides **. Il n'est pas exagéré de dire que la reconnaissance d'images d'aujourd'hui ne se serait pas développée sans elle (probablement).

Pourquoi tu en as besoin

Je pense que l'image a à l'origine une structure de données bidimensionnelle, non? Cela semble bidimensionnel, mais lors de l'apprentissage automatique, nous utilisons souvent des images décomposées en RVB (cela s'appelle ** canal **). En d'autres termes, l'image couleur a une structure de données tridimensionnelle. color_image.png De plus, bien que les images en noir et blanc aient un canal, plusieurs images sont envoyées en une seule propagation (c'est ce qu'on appelle ** batch **), donc il a une structure de données en trois dimensions. En pratique, il est inefficace de se soucier de mettre en œuvre uniquement une image en noir et blanc en 3D, donc l'image en noir et blanc a un total de structure de données 4D en l'alignant avec l'image couleur avec 1 canal. 4dim_image.png Si vous utilisez une double boucle, vous pouvez traiter chaque image une par une, mais cela efface l'avantage de numpy (numpy a la propriété d'être lent lorsqu'il est tourné dans une boucle for etc.). Par conséquent, nous avons besoin d'une fonction appelée ʻim2col` qui peut maximiser les avantages de numpy en créant des données 2D en 4 dimensions.

Qu'est-ce que CNN

CNN est une abréviation de Convolutional Neural Network, qui est utilisée pour les données étroitement liées à un certain point de coordonnées et aux points de coordonnées qui l'entourent. Un exemple simple est une image ou une vidéo. Avant l'avènement de CNN, lors de l'apprentissage d'une structure de données telle qu'une image à l'aide d'un réseau neuronal, les données bidimensionnelles étaient lissées et traitées comme des données unidimensionnelles, ignorant l'importante corrélation des données bidimensionnelles. J'ai fait. CNN a fait une percée dans la reconnaissance d'images en extrayant des caractéristiques tout en conservant la structure de données bidimensionnelle des images. Cette technologie s'inspire du traitement qui est effectué lors de la transmission d'informations de la rétine au nerf optique, permettant d'effectuer des traitements plus proches de la reconnaissance humaine.

filtration

Le contenu du traitement CNN est principalement un traitement appelé filtrage (couche de convolution) et pooling (couche de pooling). Le filtrage est le processus de détection de caractéristiques telles que les lignes verticales à partir de données d'image. Ceci est similaire à ce que font les cellules rétiniennes humaines (certaines cellules rétiniennes humaines répondent à des modèles spécifiques et émettent des signaux électriques pour transmettre des informations au nerf optique). Le regroupement est un processus pour extraire des caractéristiques plus caractéristiques des quantités de caractéristiques extraites par filtrage. Ceci est similaire à ce qui se fait dans le nerf optique humain (le nombre de cellules nerveuses diminue lorsque l'information est transmise du nerf optique au cerveau → l'information est compressée). Du point de vue de la réduction du volume de données, c'est un très bon processus, qui permet d'économiser de la mémoire et de réduire les calculs tout en préservant les fonctionnalités. ʻIm2col et col2im`, qui seront présentés dans un autre article, joueront également un rôle actif dans l'implémentation du pooling, mais cette fois nous porterons une attention particulière au filtrage. filter_image.gif Le gif ci-dessus représente une image de filtrage.

Comportement de ʻIm2col` et implémentation initiale

Pour comprendre l'implémentation de ʻim2col`, nous disséquerons complètement son comportement en utilisant des formules mathématiques, des images et des gifs.

Comportement de ʻIm2col`

Le gif précédent est mathématiquement

a = 1W + 2X + 5Y + 6Z \\
b = 2W + 3X + 6Y + 7Z \\
c = 3W + 4X + 7Y + 8Z \\
d = 5W + 6X + 9Y + 10Z \\
e = 6W + 7X + 10Y + 11Z \\
f = 7W + 8X + 11Y + 12Z \\
g = 9W + 10X + 13Y + 14Z \\
h = 10W + 11X + 14Y + 15Z \\
i = 11W + 12X + 15Y + 16Z

On dirait. ʻIm2col` transforme bien les données d'image pour y parvenir avec la production matricielle. im2col_image.gif Vérifiez également la formule.

\begin{align}
  \left(
    \begin{array}{c}
      a \\
      b \\
      c \\
      d \\
      e \\
      f \\
      g \\
      h \\
      i
    \end{array}
  \right)^{\top}
  &=
  \left(
    \begin{array}{cccc}
      W & X & Y & Z
    \end{array}
  \right)
  \left(
    \begin{array}{ccccccccc}
      1 & 2 & 3 & 5 & 6 & 7 & 9 & 10 & 11 \\
      2 & 3 & 4 & 6 & 7 & 8 & 10 & 11 & 12 \\
      5 & 6 & 7 & 9 & 10 & 11 & 13 & 14 & 15 \\
      6 & 7 & 8 & 10 & 11 & 12 & 14 & 15 & 16
    \end{array}
  \right) \\
  &=
  \left(
    \begin{array}{c}
      1W + 2X + 5Y + 6Z \\
      2W + 3X + 6Y + 7Z \\
      3W + 4X + 7Y + 8Z \\
      5W + 6X + 9Y + 10Z \\
      6W + 7X + 10Y + 11Z \\
      7W + 8X + 11Y + 12Z \\
      8W + 9X + 12Y + 13Z \\
      10W + 11X + 14Y + 15Z \\
      11W + 12X + 15Y + 16Z
    \end{array}
  \right)^{\top}
\end{align}

Implémentation précoce de ʻim2col`

Alors, implémentons cela honnêtement d'abord. Filtrer une matrice $ 4 \ times 4 $ avec un filtre $ 2 \ times 2 $ produira une matrice $ 3 \ times 3 $. Généralisons cela. Envisagez de filtrer la matrice $ I_h \ times I_w $ avec $ F_h \ times F_w $. À ce stade, l'index en haut à gauche du filtre lorsque le dernier filtre est appliqué correspond à la taille de la matrice de sortie. C'est parce que le nombre de filtres et la taille de la matrice de sortie correspondent. cal_output_size.png À partir de l'image, la taille de la matrice de sortie peut être calculée comme suit: $ (I_h --F_h + 1) \ times (I_w --F_w + 1) = O_h \ times O_w $. En d'autres termes, les éléments $ O_h O_w $ sont requis, donc le nombre de colonnes dans ʻim2col est $ O_h O_w $. D'autre part, puisque le nombre de lignes est proportionnel à la taille du filtre, il devient $ F_hF_w $, donc lors du filtrage de la matrice d'entrée de $ I_h \ fois I_w $ avec $ F_h \ times F_w $, la matrice de sortie de ʻim2col est Cela devient $ F_h F_w \ times O_h O_w $. Ce qui précède peut être intégré au programme comme suit.

Début im2col

early_im2col.py


import time
import numpy as np


def im2col(image, F_h, F_w):
    I_h, I_w = image.shape
    O_h = I_h - F_h + 1
    O_w = I_w - F_w + 1
    col = np.empty((F_h*F_w, O_h*O_w))
    for h in range(O_h):
        for w in range(O_w):
            col[:, w + h*O_w] = image[h : h+F_h, w : w+F_w].reshape(-1)
    return col

x = np.arange(1, 17).reshape(4, 4)
f = np.arange(-4, 0).reshape(2, 2)
print(im2col(x, 2, 2))
print(im2col(f, 2, 2).T)
print(im2col(f, 2, 2).T @ im2col(x, 2, 2))
early_im2col_ex.png Le calcul de la taille de la matrice est comme ci-dessus. Ci-dessous, nous allons regarder l'implémentation où elle est réellement transformée.

early_im2col.py


for h in range(O_h):
    for w in range(O_w):
        col[:, w + h*O_w] = image[h : h+F_h, w : w+F_w].reshape(-1)

L'emplacement d'écriture dans la matrice de sortie correspondant à chaque «h, w» est le suivant. early_image.png C'est l'emplacement d'écriture spécifié par col [:, w + h * O_w]. Ici, la partie pertinente de la matrice d'entrée ʻimage [h: h + F_h, w: w + F_w] est lissée avec .reshape (-1) `et remplacée. C'est toujours facile.

Problèmes avec les premiers ʻim2col`

Maintenant, early_im2col.py a un sérieux inconvénient. L'inconvénient est que, comme mentionné précédemment, numpy est lent lorsqu'il est accédé par un traitement en boucle tel que «for». En général, le tableau d'entrée x montré dans early_im2dol.py comme exemple d'opération est beaucoup plus grand (par exemple ** très petit jeu de données ** [MNIST](http: //yann.lecun). L'image numérique manuscrite de .com / exdb / mnist /) est une matrice de 28 $ \ fois 28 $). Mesurons le temps de traitement.

early_im2col.py


y = np.zeros((28, 28))
start = time.time()
for i in range(1000):
    im2col(y, 2, 2)
end = time.time()
print("time: {}".format(end - start))
early_im2col_time.png Il faut 1,5 seconde pour traiter une matrice d'au plus 28 $ \ fois 28 $ 1000 fois. La base de données MNIST étant une base de données de 60 000 numéros manuscrits, il faut ** 900 secondes ** pour filtrer toutes les images une fois par simple calcul. Ce n'est pas pratique car dans le vrai machine learning, plusieurs filtres sont appliqués plusieurs fois.

Version améliorée ʻim2col` (version initiale)

Si vous passez en revue le problème, vous constaterez que le problème est que la boucle for accède fréquemment au tableau numpy. Cela signifie que vous pouvez réduire le nombre d'accès. Dans early_im2col.py, le tableau numpy ʻimage est accédé $ O_h O_w $ fois, et la matrice d'entrée $ 28 \ times 28 $ est filtrée par $ 2 \ fois 2 $, et le nombre d'accès est en fait de 27 $ \ fois. 27 = 729 $ fois. À propos, les filtres sont généralement beaucoup plus petits que les matrices de sortie, ce qui peut être utilisé pour réduire considérablement le nombre d'accès à un tableau numpy avec un traitement équivalent. C'est la version améliorée ʻim2col (version initiale). Je fais quelque chose de délicat.

Version améliorée ʻim2col` (version initiale)

improved_early_im2col.py


import time
import numpy as np


def im2col(image, F_h, F_w):
    I_h, I_w = image.shape
    O_h = I_h - F_h + 1
    O_w = I_w - F_w + 1
    col = np.empty((F_h, F_w, O_h, O_w))
    for h in range(F_h):
        for w in range(F_w):
            col[h, w, :, :] = image[h : h+O_h, w : w+O_w]
    return col.reshape(F_h*F_w, O_h*O_w)

x = np.arange(1, 17).reshape(4, 4)
f = np.arange(-4, 0).reshape(2, 2)
print(im2col(x, 2, 2))
print(im2col(f, 2, 2).T)
print(im2col(f, 2, 2).T @ im2col(x, 2, 2))

y = np.zeros((28, 28))
start = time.time()
for i in range(1000):
    im2col(y, 2, 2)
end = time.time()
print("time: {}".format(end - start))
improved_early_im2col.png Comme vous pouvez le voir, le résultat est 150 fois plus rapide! Dans ce cas, même s'il est traité une fois toutes les 60 000 feuilles, cela ne prend que 6 secondes, ce qui le rend plus pratique (qu'avant). Voyons maintenant ce qui a changé et comment cela a changé.

Changer 1

Le premier changement est la partie d'allocation de mémoire de la matrice de sortie.

improved_early_im2col.py


col = np.empty((F_h, F_w, O_h, O_w))

improved_col.png La mémoire est sécurisée avec une structure de données en 4 dimensions comme celle-ci.

Changement 2

Le prochain changement est que le nombre de boucles a été changé de $ O_h O_w $ à $ F_h F_w $ pour réduire le nombre d'accès.

improved_early_im2col.py


for h in range(F_h):
    for w in range(F_w):
        col[h, w, :, :] = image[h : h+O_h, w : w+O_w]

Cela réduira le nombre d'accès aux tableaux numpy par image MNIST de 729 à 4! De plus, l'emplacement d'accès au tableau de sortie et l'emplacement d'accès au tableau d'entrée dans chaque boucle sont les suivants. Lorsqu'on y accède de cette manière, le tableau de sortie suivant est créé. improved_im2col_numbering.png

Changement 3

Enfin, donnez-lui la forme souhaitée au moment de la sortie.

improved_early_im2col.py


return col.reshape(F_h*F_w, O_h*O_w)

En termes de fonctionnement de numpy, les données unidimensionnelles de $ (F_h F_w O_h O_w,) $, qui sont lissées $ (F_h, F_w, O_h, O_w) $, sont transformées en données bidimensionnelles de $ (F_h F_w, O_h O_w) $. J'ai l'impression de le faire. Pour le dire plus en détail, cela ressemble à un lissage de chacune des données bidimensionnelles de la figure dans une dimension et à l'empiler en dessous. improved_im2col_reshape.png Vous pensez que c'est bon ~

Extension au tableau multidimensionnel

En passant, comme mentionné dans [Qu'est-ce que ʻim2col`](Qu'est-ce que # im2col), la matrice cible de cette fonction a à l'origine une structure de données à 4 dimensions. Le filtre a également une structure de données à 4 dimensions dans laquelle $ M $ de l'ensemble est préparé en plus de sécuriser le nombre de canaux de la matrice d'entrée. color_image_and_filter.png En tenant compte de cela, nous modifierons amélioré_early_im2col.py.

Chase avec une formule

Tout d'abord, réfléchissons au type de forme qui doit être transformé mathématiquement. La structure de l'image couleur est $ (B, C, I_h, I_w) $ lorsque le nombre de canaux est $ C $ et la taille du lot est $ B $. D'autre part, le filtre a une structure de $ (M, C, F_h, F_w) $. Dans Enhanced_early_im2col.py, lorsque la matrice $ (I_h, I_w) $ est filtrée par $ (F_h, F_w) $, la matrice de sortie est $ (F_h F_w, O_h O_w) $ et $ (1, F_h F_w) $. Tu l'as fait. En supposant que $ B = 1 $ et $ M = 1 $, pour que le filtrage soit calculé comme un produit matriciel, les lignes et colonnes des données d'entrée transformées par ʻim2col et la forme du filtre doivent correspondre. Parce qu'ils doivent être $ (C F_h F_w, O_h O_w) $ et $ (1, C F_h F_w) $. De plus, comme ils sont généralement $ B \ ne M $, ils doivent être combinés avec ceux qui n'ont rien à voir avec $ C F_h F_w $. En combinant ces faits, la forme du tableau qui devrait être sortie par ʻim2col est $ (C F_h F_w, B O_h O_w) $ et $ (M, C F_h F_w) $. Au fait, le résultat du calcul de filtrage est $ (M, C F_h F_w) \ times (C F_h F_w, B O_h O_w) = (M, B O_h O_w) $, qui est «remodelé» et les dimensions sont remplacées. (B, M, O_h, O_w): = (B, C ', I_h', I_w ') $ se propage comme entrée à la couche suivante.

Essayez de mettre en œuvre

La mise en œuvre est presque la même que améliorée_early_im2col.py. Je viens d'ajouter les dimensions des lots et des canaux en haut. BC_cols.png

Prise en charge du canal Batch ʻim2col`

BC_support_im2col.py


import time
import numpy as np


def im2col(images, F_h, F_w):
    B, C, I_h, I_w = images.shape
    O_h = I_h - F_h + 1
    O_w = I_w - F_w + 1
    cols = np.empty((B, C, F_h, F_w, O_h, O_w))
    for h in range(F_h):
        for w in range(F_w):
            cols[:, :, h, w, :, :] = images[:, :, h : h+O_h, w : w+O_w]
    return cols.transpose(1, 2, 3, 0, 4, 5).reshape(C*F_h*F_w, B*O_h*O_w)

x = np.arange(1, 3*3*4*4+1).reshape(3, 3, 4, 4)
f = np.arange(-3*3*2*2, 0).reshape(3, 3, 2, 2)
print(im2col(x, 2, 2))
print(im2col(f, 2, 2).T)
print(np.dot(im2col(f, 2, 2).T, im2col(x, 2, 2)))

y = np.zeros((100, 3, 28, 28))
start = time.time()
for i in range(10):
    im2col(y, 2, 2)
end = time.time()
print("time: {}".format(end - start))
BC_support_im2col_ex.png Le plus gros changement est la valeur de retour.

BC_support_im2col.py


return cols.transpose(1, 2, 3, 0, 4, 5).reshape(C*F_h*F_w, B*O_h*O_w)

Ici, l'ordre des dimensions est modifié à l'aide de la fonction transpose de numpy. Chacun correspond comme suit, et la sortie correcte est renvoyée en changeant l'ordre, puis en remodelant.

\begin{array}{ccccccc}
  (&0, &1, &2, &3, &4, &5) \\
  (&B, &C, &F_h, &F_w, &O_h, &O_w)
\end{array}
\xrightarrow[\textrm{transpose}]{Échanger}
\begin{array}{ccccccc}
  (&1, &2, &3, &0, &4, &5) \\
  (&C, &F_h, &F_w, &B, &O_h, &O_w)
\end{array}
\xrightarrow[\textrm{reshape}]{Déformation}
(C F_h F_w, B O_h O_w)

Ceci complète ʻim2col`, qui supporte également les canaux batch!

Foulée et rembourrage

Eh bien, je ne pense pas que ce soit la fin. Enfin, je voudrais vous présenter les processus appelés ** stride ** et ** padding **. Les deux sont des éléments essentiels pour une mise en œuvre plus efficace et efficiente de CNN.

foulée

Dans la mise en œuvre jusqu'à présent, les filtres ont été décalés d'un carré naturellement, non? Cette quantité d'écart est appelée ** foulée **, mais il n'y a pas de règle selon laquelle cela doit être un carré à la fois. Dans la plupart des cas, la foulée ne sera pas de 1 car l'image réelle est moins susceptible d'avoir un grand changement d'informations avec seulement un décalage de 1 pixel.

Rembourrage

Contrairement aux foulées, ** padding ** n'a jamais été mentionné dans les implémentations précédentes. Son rôle principal est de ** maintenir inchangée la taille de l'image de sortie en filtrant ** et ** d'obtenir toutes les informations vers les bords de l'image **. Plus précisément, la plage dans laquelle le filtre se déplace est étendue en remplissant la circonférence de l'image d'entrée avec $ 0 $. pading_image.png

Mise en œuvre de la foulée et du rembourrage

Jetons un coup d'œil à chaque implémentation.

Implémentation Stride

La mise en œuvre de Stride n'est pas si difficile. Il vous permet uniquement de modifier la largeur de mouvement de la foulée si loin de 1. jusqu'à maintenant

BC_support_im2col.py


cols[:, :, h, w, :, :] = images[:, :, h : h+O_h, w : w+O_w]

Je faisais ça

im2col.py


cols[:, :, h, w, :, :] = images[:, :, h : h + stride*O_h : stride, w : w + stride*O_w : stride]

Changez comme suit. Le mouvement de la version initiale est comme ça stride_image.gif Dans la formule

a = 1W + 2X + 5Y + 6Z \\
b = 3W + 4X + 7Y + 8Z \\
c = 9W + 10X + 13Y + 14Z \\
d = 11W + 12X + 15Y + 16Z \\
\Leftrightarrow \left(
  \begin{array}{c}
    a \\
    b \\
    c \\
    d
  \end{array}
\right)^{\top}
=
\left(
  \begin{array}{cccc}
    W & X & Y & Z
  \end{array}
\right)
\left(
  \begin{array}{cccc}
    1 & 3 & 9 & 11 \\
    2 & 4 & 10 & 12 \\
    5 & 7 & 13 & 15 \\
    6 & 8 & 14 & 16
  \end{array}
\right)

Cela ressemble à ceci, et la version améliorée ressemble à ceci. improved_stride_image.gif stride_col.png Après tout, c'est délicat ... C'est trop étonnant d'y penser.

Implémentation du rembourrage

D'autre part, la mise en œuvre du traitement de remplissage est très simple. Utilisation de la fonction pad dans numpy

im2col.py


images = np.pad(images, [(0, 0), (0, 0), (pad, pad), (pad, pad)], "constant")

Si c'est le cas, tout va bien. Le fonctionnement de la fonction pad est assez compliqué (je l'introduirai plus tard), donc je vais vous expliquer ce qui précède pour le moment. Le premier argument de pad est le tableau cible. Cela devrait être correct. Le problème est le deuxième argument.

im2col.py


[(0, 0), (0, 0), (pad, pad), (pad, pad)]

Si vous entrez ceci dans la fonction pad,

--La première dimension est (0, 0), c'est-à-dire pas de remplissage --La deuxième dimension est (0, 0), c'est-à-dire pas de remplissage ――La troisième dimension est (pad, pad), c'est-à-dire que la largeur d'augmentation supérieure et inférieure pad est remplie de 0 ( constant '') ―― La 4ème dimension est (pad, pad), c'est-à-dire que la largeur d'augmentation gauche et droite pad est remplie de 0 ( constant '')

Il y a quelques troisièmes arguments qui peuvent être spécifiés, mais cette fois je veux les compléter avec 0, donc je spécifie «" constant "». Consultez la documentation officielle (https://numpy.org/devdocs/reference/generated/numpy.pad.html) pour plus d'informations.

Calcul des dimensions de sortie

Eh bien, même si je fais les modifications ci-dessus et que je les exécute, une erreur apparaît toujours et cela ne fonctionne pas. Oui. La raison, comme vous pouvez vous y attendre, est que les dimensions de sortie changent avec la mise en œuvre de la foulée et du rembourrage. Réfléchissons à la manière dont cela va changer.

Impact de la foulée

Augmenter la largeur de la foulée diminuera le nombre de filtres en proportion inverse. Vous pouvez voir que le nombre de fois est divisé par deux selon que le filtre est appliqué toutes les 1 cellule ou toutes les 2 cellules. Exprimé dans une formule mathématique

O_h = \cfrac{I_h - F_h}{\textrm{stride}} + 1\\
O_w = \cfrac{I_w - F_w}{\textrm{stride}} + 1

Ce sera comme ça. Si $ I_h = 4, F_h = 2, \ textrm {foulée} = 1 $ O_h = \cfrac{4 - 2}{1} + 1 = 3 Et si $ I_h = 4, F_h = 2, \ textrm {stride} = 2 $ O_h = \cfrac{4 - 2}{2} + 1 = 2 Vous pouvez voir qu'il correspond à l'image précédente.

Impact du rembourrage

L'effet du rembourrage est très simple. Parce que la taille de chaque image d'entrée est de haut en bas $ + \ textrm {pad} \ _ {ud} $, gauche et droite $ + \ textrm {pad} \ _ {lr} $

I_h \leftarrow I_h + 2\textrm{pad}_{ud} \\
I_w \leftarrow I_w + 2\textrm{pad}_{lr}

Peut être remplacé par, c'est-à-dire

O_h = \cfrac{I_h - F_h + 2\textrm{pad}_{ud}}{\textrm{stride}} + 1 \\
O_w = \cfrac{I_w - F_w + 2\textrm{pad}_{lr}}{\textrm{stride}} + 1

Ce sera. Inversement, si vous souhaitez faire correspondre la taille de l'image de sortie à la taille de l'image d'entrée, $ O_h = I_h $ et $ O_w = I_w $.

\textrm{pad}_{ud} = \cfrac{1}{2}\left\{(I_h - 1) \textrm{stride} - I_h + F_h\right\} \\
\textrm{pad}_{lr} = \cfrac{1}{2}\left\{(I_w - 1) \textrm{stride} - I_w + F_w\right\}

Il peut être calculé comme suit. Au fait, augmentons le degré de liberté dans la foulée.

O_h = \cfrac{I_h - F_h + 2\textrm{pad}_{ud}}{\textrm{stride}_{ud}} + 1 \\
O_w = \cfrac{I_w - F_w + 2\textrm{pad}_{lr}}{\textrm{stride}_{lr}} + 1 \\
\textrm{pad}_{ud} = \cfrac{1}{2}\left\{(I_h - 1) \textrm{stride}_{ud} - I_h + F_h\right\} \\
\textrm{pad}_{lr} = \cfrac{1}{2}\left\{(I_w - 1) \textrm{stride}_{lr} - I_w + F_w\right\}

Version terminée ʻim2col`

Le ʻim2col` avec une liberté accrue en ajoutant de la foulée et du rembourrage est le suivant. Je vais également faire quelques personnalisations.

im2col.py

im2col.py


import numpy as np


def im2col(images, filters, stride=1, pad=0, get_out_size=True):
    if images.ndim == 2:
        images = images.reshape(1, 1, *images.shape)
    elif images.ndim == 3:
        B, I_h, I_w = images.shape
        images = images.reshape(B, 1, I_h, I_w)
    if filters.ndim == 2:
        filters = filters.reshape(1, 1, *filters.shape)
    elif images.ndim == 3:
        M, F_h, F_w = filters.shape
        filters = filters.reshape(M, 1, F_h, F_w)
    B, C, I_h, I_w = images.shape
    _, _, F_h, F_w = filters.shape
    
    if isinstance(stride, tuple):
        stride_ud, stride_lr = stride
    else:
        stride_ud = stride
        stride_lr = stride
    if isinstance(pad, tuple):
        pad_ud, pad_lr = pad
    elif isinstance(pad, int):
        pad_ud = pad
        pad_lr = pad
    elif pad == "same":
        pad_ud = 0.5*((I_h - 1)*stride_ud - I_h + F_h)
        pad_lr = 0.5*((I_w - 1)*stride_lr - I_w + F_w)
    pad_zero = (0, 0)
    
    O_h = int(np.ceil((I_h - F_h + 2*pad_ud)/stride_ud) + 1)
    O_w = int(np.ceil((I_w - F_w + 2*pad_lr)/stride_lr) + 1)
    
    pad_ud = int(np.ceil(pad_ud))
    pad_lr = int(np.ceil(pad_lr))
    pad_ud = (pad_ud, pad_ud)
    pad_lr = (pad_lr, pad_lr)
    images = np.pad(images, [pad_zero, pad_zero, pad_ud, pad_lr], \
                    "constant")
    
    cols = np.empty((B, C, F_h, F_w, O_h, O_w))
    for h in range(F_h):
        h_lim = h + stride_ud*O_h
        for w in range(F_w):
            w_lim = w + stride_lr*O_w
            cols[:, :, h, w, :, :] \
                = images[:, :, h:h_lim:stride_ud, w:w_lim:stride_lr]
    if get_out_size:
        return cols.transpose(1, 2, 3, 0, 4, 5).reshape(C*F_h*F_w, B*O_h*O_w), (O_h, O_w)
    else:
        return cols.transpose(1, 2, 3, 0, 4, 5).reshape(C*F_h*F_w, B*O_h*O_w)

Je vais vous expliquer brièvement.

Mise en forme, etc.

im2col.py


def im2col(images, filters, stride=1, pad=0, get_out_size=True):
    if images.ndim == 2:
        images = images.reshape(1, 1, *images.shape)
    elif images.ndim == 3:
        B, I_h, I_w = images.shape
        images = images.reshape(B, 1, I_h, I_w)
    if filters.ndim == 2:
        filters = filters.reshape(1, 1, *filters.shape)
    elif images.ndim == 3:
        M, F_h, F_w = filters.shape
        filters = filters.reshape(M, 1, F_h, F_w)
    B, C, I_h, I_w = images.shape
    _, _, F_h, F_w = filters.shape
    
    if isinstance(stride, tuple):
        stride_ud, stride_lr = stride
    else:
        stride_ud = stride
        stride_lr = stride
    if isinstance(pad, tuple):
        pad_ud, pad_lr = pad
    elif isinstance(pad, int):
        pad_ud = pad
        pad_lr = pad
    elif pad == "same":
        pad_ud = 0.5*((I_h - 1)*stride_ud - I_h + F_h)
        pad_lr = 0.5*((I_w - 1)*stride_lr - I_w + F_w)
    pad_zero = (0, 0)
Dans cette partie

--Changé pour prendre le filtre lui-même comme argument pour réduire le nombre d'arguments --Si l'image d'entrée n'est pas 4D, convertissez-la en 4D --Convertir en 4D si le filtre n'est pas 4D

Je fais le traitement comme ça.

Préparation

im2col.py


    O_h = int(np.ceil((I_h - F_h + 2*pad_ud)/stride_ud) + 1)
    O_w = int(np.ceil((I_w - F_w + 2*pad_lr)/stride_lr) + 1)
    
    pad_ud = int(np.ceil(pad_ud))
    pad_lr = int(np.ceil(pad_lr))
    pad_ud = (pad_ud, pad_ud)
    pad_lr = (pad_lr, pad_lr)
    images = np.pad(images, [pad_zero, pad_zero, pad_ud, pad_lr], \
                    "constant")
    
    cols = np.empty((B, C, F_h, F_w, O_h, O_w))

ici

--Calculer la taille de l'image de sortie

Je fais.

Corps de traitement et valeur de retour

im2col.py


    for h in range(F_h):
        h_lim = h + stride_ud*O_h
        for w in range(F_w):
            w_lim = w + stride_lr*O_w
            cols[:, :, h, w, :, :] \
                = images[:, :, h:h_lim:stride_ud, w:w_lim:stride_lr]
    if get_out_size:
        return cols.transpose(1, 2, 3, 0, 4, 5).reshape(C*F_h*F_w, B*O_h*O_w), (O_h, O_w)
    else:
        return cols.transpose(1, 2, 3, 0, 4, 5).reshape(C*F_h*F_w, B*O_h*O_w)

Enfin, à propos du corps de traitement et de la valeur de retour.

Expérimentez avec MNIST

Téléchargez les données MNIST de l'ensemble de données Keras et expérimentez.

mnist_test.py

mnist_test.py


#%pip install tensorflow
#%pip install keras
from keras.datasets import mnist
import matplotlib.pyplot as plt


#Spécifiez le nombre de feuilles à acquérir
B = 3

#Acquisition de jeux de données
(x_train, _), (_, _) = mnist.load_data()
x_train = x_train[:B]

#Essayez d'afficher
fig, ax = plt.subplots(1, B)
for i, x in enumerate(x_train):
    ax[i].imshow(x, cmap="gray")
fig.tight_layout()
plt.savefig("mnist_data.png ")
plt.show()

#Essayez de détecter les lignes verticales
M = 1
C = 1
F_h = 7
F_w = 7
_, I_h, I_w = x_train.shape
f = np.zeros((F_h, F_w))
f[:, int(F_w/2)] = 1
no_pad, (O_h, O_w) = im2col(x_train, f, stride=2, pad="same")
filters = im2col(f, f, get_out_size=False)
y = np.dot(filters.T, no_pad).reshape(M, B, O_h, O_w).transpose(1, 0, 2, 3).reshape(B, O_h, O_w)
fig2, ax2 = plt.subplots(1, B)
for i, x in enumerate(y):
    ax2[i].imshow(x[F_h : I_h-F_h, F_w : I_w-F_w], cmap="gray")
fig2.tight_layout()
plt.savefig("vertical_filtering.png ")
plt.show()

#Essayez de détecter les lignes horizontales
f = np.zeros((F_h, F_w))
f[int(F_h / 2), :] = 1
no_pad, (O_h, O_w) = im2col(x_train, f, stride=2, pad="same")
filters = im2col(f, f, get_out_size=False)
y = np.dot(filters.T, no_pad).reshape(M, B, O_h, O_w).transpose(1, 0, 2, 3).reshape(B, O_h, O_w)
fig3, ax3 = plt.subplots(1, B)
for i, x in enumerate(y):
    ax3[i].imshow(x[F_h : I_h-F_h, F_w : I_w-F_w], cmap="gray")
fig3.tight_layout()
plt.savefig("horizontal_filtering.png ")
plt.show()

#Essayez de détecter une pente descendante
f = np.zeros((F_h, F_w))
for i in range(F_h):
    f[i, i] = 1
no_pad, (O_h, O_w) = im2col(x_train, f, stride=2, pad="same")
filters = im2col(f, f, get_out_size=False)
y = np.dot(filters.T, no_pad).reshape(M, B, O_h, O_w).transpose(1, 0, 2, 3).reshape(B, O_h, O_w)
fig4, ax4 = plt.subplots(1, B)
for i, x in enumerate(y):
    ax4[i].imshow(x[F_h : I_h-F_h, F_w : I_w-F_w], cmap="gray")
fig4.tight_layout()
plt.savefig("right_down_filtering.png ")
plt.show()

#Essayez de détecter la montée vers la droite
f = np.zeros((F_h, F_w))
for i in range(F_h):
    f[F_h - i - 1, i] = 1
no_pad, (O_h, O_w) = im2col(x_train, f, stride=2, pad="same")
filters = im2col(f, f, get_out_size=False)
y = np.dot(filters.T, no_pad).reshape(M, B, O_h, O_w).transpose(1, 0, 2, 3).reshape(B, O_h, O_w)
fig4, ax4 = plt.subplots(1, B)
for i, x in enumerate(y):
    ax4[i].imshow(x[F_h : I_h-F_h, F_w : I_w-F_w], cmap="gray")
fig4.tight_layout()
plt.savefig("right_up_filtering.png ")
plt.show()
Résultat de sortie Données d'origine ![mnist_data.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/0b3333f9-7c32-cef3-697b-8a24bdf8f5e3.png) Résultat de détection de ligne verticale ![vertical_filtering.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/a7561d2c-0951-dd53-d718-1a3d61f02d69.png) Résultat de détection de ligne horizontale ![horizontal_filtering.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/db838924-ab55-d460-6026-467cfc6ef391.png) Résultat de détection vers le bas à droite ![right_down_filtering.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/aeddeae2-60cc-ffc4-312c-5c075bf760fc.png) Résultat de détection droit ![right_up_filtering.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/66964a57-9e17-6e4f-7c0f-7fca58832920.png)
Les deux premières lignes sont incluses car vous devez avoir installé `tensorflow` et` keras`. Si nécessaire, supprimez uniquement le commentaire «#» et exécutez. Une fois que vous l'avez exécuté, vous pouvez le commenter à nouveau. Comme vous pouvez le voir dans le résultat de sortie, à la suite de l'application de chaque filtre, seule la ligne cible reste sombre. C'est la détection des fonctionnalités.

en conclusion

Ceci est la fin de l'explication sur ʻim2col`. Si vous avez des bugs ou des styles d'écriture plus intelligents, je vous serais reconnaissant de bien vouloir me le faire savoir dans les commentaires.

référence

Série d'apprentissage en profondeur

Recommended Posts

Compréhension approfondie d'Im2col
compréhension approfondie de col2im
Comprendre VQ-VAE
Comprendre Concaténer