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.
ʻ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).
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.
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.
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.
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.
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.
Le gif ci-dessus représente une image de filtrage.
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.
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. 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}
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.
À 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.
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.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.
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.
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))
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.
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.
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))
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))
La mémoire est sécurisée avec une structure de données en 4 dimensions comme celle-ci.
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éé.
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. Vous pensez que c'est bon ~
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. En tenant compte de cela, nous modifierons amélioré_early_im2col.py.
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.
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_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.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!
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.
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.
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 $.
Jetons un coup d'œil à chaque implémentation.
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 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. Après tout, c'est délicat ... C'est trop étonnant d'y penser.
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.
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.
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 $
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\}
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
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.
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)
--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
_, _, ...
) et obtenez la taille d'un filtre.
--Si stride
est un tuple
, on considère que les largeurs de foulée supérieure et inférieure et gauche et droite sont spécifiées individuellement, sinon la même valeur est utilisée.
--Si pad
est tuple
, on considère que les largeurs de remplissage haut et bas et gauche et droite sont spécifiées individuellement, sinon la même valeur est utilisée.
--Si pad ==" same "
est spécifié, la largeur de remplissage qui maintient la taille de l'image d'entrée est calculée avec ** float
** (pour le calcul ultérieur de la taille de sortie).Je fais le traitement comme ça.
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.
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.
h_lim
et w_lim
, et définissent les extrémités droite et inférieure du processus de filtrage.cols
Téléchargez les données MNIST de l'ensemble de données Keras et expérimentez.
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()
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.