Compréhension intuitive des convolutions 1D, 2D et 3D dans les réseaux de neurones convolutifs

Réponses:

415

Je veux expliquer avec une image de C3D .

En un mot, la direction convolutionnelle et la forme de sortie sont importantes!

entrez la description de l'image ici

↑↑↑↑↑ Convolutions 1D - Basique ↑↑↑↑↑

  • juste 1 -direction (axe du temps) pour calculer conv
  • entrée = [W], filtre = [k], sortie = [W]
  • ex) entrée = [1,1,1,1,1], filtre = [0,25,0,5,0,25], sortie = [1,1,1,1,1]
  • output-shape est un tableau 1D
  • exemple) lissage de graphe

Exemple de jouet de code tf.nn.conv1d

import tensorflow as tf
import numpy as np

sess = tf.Session()

ones_1d = np.ones(5)
weight_1d = np.ones(3)
strides_1d = 1

in_1d = tf.constant(ones_1d, dtype=tf.float32)
filter_1d = tf.constant(weight_1d, dtype=tf.float32)

in_width = int(in_1d.shape[0])
filter_width = int(filter_1d.shape[0])

input_1d   = tf.reshape(in_1d, [1, in_width, 1])
kernel_1d = tf.reshape(filter_1d, [filter_width, 1, 1])
output_1d = tf.squeeze(tf.nn.conv1d(input_1d, kernel_1d, strides_1d, padding='SAME'))
print sess.run(output_1d)

entrez la description de l'image ici

↑↑↑↑↑ Convolutions 2D - Basique ↑↑↑↑↑

  • 2 -direction (x, y) pour calculer conv
  • la forme de sortie est une matrice 2D
  • entrée = [W, H], filtre = [k, k] sortie = [W, H]
  • exemple) Sobel Egde Fllter

tf.nn.conv2d - Exemple de jouet

ones_2d = np.ones((5,5))
weight_2d = np.ones((3,3))
strides_2d = [1, 1, 1, 1]

in_2d = tf.constant(ones_2d, dtype=tf.float32)
filter_2d = tf.constant(weight_2d, dtype=tf.float32)

in_width = int(in_2d.shape[0])
in_height = int(in_2d.shape[1])

filter_width = int(filter_2d.shape[0])
filter_height = int(filter_2d.shape[1])

input_2d   = tf.reshape(in_2d, [1, in_height, in_width, 1])
kernel_2d = tf.reshape(filter_2d, [filter_height, filter_width, 1, 1])

output_2d = tf.squeeze(tf.nn.conv2d(input_2d, kernel_2d, strides=strides_2d, padding='SAME'))
print sess.run(output_2d)

entrez la description de l'image ici

↑↑↑↑↑ Convolutions 3D - Basique ↑↑↑↑↑

  • 3 -direction (x, y, z) pour calculer conv
  • la forme de sortie est le volume 3D
  • entrée = [W, H, L ], filtre = [k, k, d ] sortie = [W, H, M]
  • d <L est important! pour faire une sortie de volume
  • exemple) C3D

tf.nn.conv3d - Exemple de jouet

ones_3d = np.ones((5,5,5))
weight_3d = np.ones((3,3,3))
strides_3d = [1, 1, 1, 1, 1]

in_3d = tf.constant(ones_3d, dtype=tf.float32)
filter_3d = tf.constant(weight_3d, dtype=tf.float32)

in_width = int(in_3d.shape[0])
in_height = int(in_3d.shape[1])
in_depth = int(in_3d.shape[2])

filter_width = int(filter_3d.shape[0])
filter_height = int(filter_3d.shape[1])
filter_depth = int(filter_3d.shape[2])

input_3d   = tf.reshape(in_3d, [1, in_depth, in_height, in_width, 1])
kernel_3d = tf.reshape(filter_3d, [filter_depth, filter_height, filter_width, 1, 1])

output_3d = tf.squeeze(tf.nn.conv3d(input_3d, kernel_3d, strides=strides_3d, padding='SAME'))
print sess.run(output_3d)

entrez la description de l'image ici

↑↑↑↑↑ Convolutions 2D avec entrée 3D - LeNet, VGG, ..., ↑↑↑↑↑

  • L'entrée Eventhough est 3D ex) 224x224x3, 112x112x32
  • output-forme ne soit pas 3D Volume, mais 2D Matrice
  • car la profondeur du filtre = L doit correspondre aux canaux d'entrée = L
  • 2 -direction (x, y) pour calculer conv! pas 3D
  • entrée = [W, H, L ], filtre = [k, k, L ] sortie = [W, H]
  • la forme de sortie est une matrice 2D
  • et si nous voulons former N filtres (N est le nombre de filtres)
  • alors la forme de sortie est (2D empilée) 3D = 2D x N matrice.

conv2d - LeNet, VGG, ... pour 1 filtre

in_channels = 32 # 3 for RGB, 32, 64, 128, ... 
ones_3d = np.ones((5,5,in_channels)) # input is 3d, in_channels = 32
# filter must have 3d-shpae with in_channels
weight_3d = np.ones((3,3,in_channels)) 
strides_2d = [1, 1, 1, 1]

in_3d = tf.constant(ones_3d, dtype=tf.float32)
filter_3d = tf.constant(weight_3d, dtype=tf.float32)

in_width = int(in_3d.shape[0])
in_height = int(in_3d.shape[1])

filter_width = int(filter_3d.shape[0])
filter_height = int(filter_3d.shape[1])

input_3d   = tf.reshape(in_3d, [1, in_height, in_width, in_channels])
kernel_3d = tf.reshape(filter_3d, [filter_height, filter_width, in_channels, 1])

output_2d = tf.squeeze(tf.nn.conv2d(input_3d, kernel_3d, strides=strides_2d, padding='SAME'))
print sess.run(output_2d)

conv2d - LeNet, VGG, ... pour N filtres

in_channels = 32 # 3 for RGB, 32, 64, 128, ... 
out_channels = 64 # 128, 256, ...
ones_3d = np.ones((5,5,in_channels)) # input is 3d, in_channels = 32
# filter must have 3d-shpae x number of filters = 4D
weight_4d = np.ones((3,3,in_channels, out_channels))
strides_2d = [1, 1, 1, 1]

in_3d = tf.constant(ones_3d, dtype=tf.float32)
filter_4d = tf.constant(weight_4d, dtype=tf.float32)

in_width = int(in_3d.shape[0])
in_height = int(in_3d.shape[1])

filter_width = int(filter_4d.shape[0])
filter_height = int(filter_4d.shape[1])

input_3d   = tf.reshape(in_3d, [1, in_height, in_width, in_channels])
kernel_4d = tf.reshape(filter_4d, [filter_height, filter_width, in_channels, out_channels])

#output stacked shape is 3D = 2D x N matrix
output_3d = tf.nn.conv2d(input_3d, kernel_4d, strides=strides_2d, padding='SAME')
print sess.run(output_3d)

entrez la description de l'image ici ↑↑↑↑↑ Bonus 1x1 conv dans CNN - GoogLeNet, ..., ↑↑↑↑↑

  • 1x1 conv est déroutant quand vous pensez qu'il s'agit d'un filtre d'image 2D comme Sobel
  • pour 1x1 conv dans CNN, l'entrée est de forme 3D comme l'image ci-dessus.
  • il calcule le filtrage en profondeur
  • entrée = [W, H, L], filtre = [1,1, L] sortie = [W, H]
  • la forme empilée en sortie est une matrice 3D = 2D x N.

tf.nn.conv2d - cas spécial 1x1 conv

in_channels = 32 # 3 for RGB, 32, 64, 128, ... 
out_channels = 64 # 128, 256, ...
ones_3d = np.ones((1,1,in_channels)) # input is 3d, in_channels = 32
# filter must have 3d-shpae x number of filters = 4D
weight_4d = np.ones((3,3,in_channels, out_channels))
strides_2d = [1, 1, 1, 1]

in_3d = tf.constant(ones_3d, dtype=tf.float32)
filter_4d = tf.constant(weight_4d, dtype=tf.float32)

in_width = int(in_3d.shape[0])
in_height = int(in_3d.shape[1])

filter_width = int(filter_4d.shape[0])
filter_height = int(filter_4d.shape[1])

input_3d   = tf.reshape(in_3d, [1, in_height, in_width, in_channels])
kernel_4d = tf.reshape(filter_4d, [filter_height, filter_width, in_channels, out_channels])

#output stacked shape is 3D = 2D x N matrix
output_3d = tf.nn.conv2d(input_3d, kernel_4d, strides=strides_2d, padding='SAME')
print sess.run(output_3d)

Animation (2D Conv avec entrées 3D)

entrez la description de l'image ici - Lien d'origine: LINK
- L'auteur: Martin Görner
- Twitter: @martin_gorner
- Google +: plus.google.com/+MartinGorne

Convolutions 1D bonus avec entrée 2D

entrez la description de l'image ici ↑↑↑↑↑ Convolutions 1D avec entrée 1D ↑↑↑↑↑

entrez la description de l'image ici ↑↑↑↑↑ Convolutions 1D avec entrée 2D ↑↑↑↑↑

  • Eventhough l'entrée est 2D ex) 20x14
  • la forme de sortie n'est pas 2D , mais matrice 1D
  • car la hauteur du filtre = L doit correspondre à la hauteur d'entrée = L
  • 1 -direction (x) pour calculer conv! pas 2D
  • entrée = [W, L ], filtre = [k, L ] sortie = [W]
  • la forme de sortie est une matrice 1D
  • et si nous voulons former N filtres (N est le nombre de filtres)
  • alors la forme de sortie est (empilée 1D) 2D = 1D x N matrice.

Bonus C3D

in_channels = 32 # 3, 32, 64, 128, ... 
out_channels = 64 # 3, 32, 64, 128, ... 
ones_4d = np.ones((5,5,5,in_channels))
weight_5d = np.ones((3,3,3,in_channels,out_channels))
strides_3d = [1, 1, 1, 1, 1]

in_4d = tf.constant(ones_4d, dtype=tf.float32)
filter_5d = tf.constant(weight_5d, dtype=tf.float32)

in_width = int(in_4d.shape[0])
in_height = int(in_4d.shape[1])
in_depth = int(in_4d.shape[2])

filter_width = int(filter_5d.shape[0])
filter_height = int(filter_5d.shape[1])
filter_depth = int(filter_5d.shape[2])

input_4d   = tf.reshape(in_4d, [1, in_depth, in_height, in_width, in_channels])
kernel_5d = tf.reshape(filter_5d, [filter_depth, filter_height, filter_width, in_channels, out_channels])

output_4d = tf.nn.conv3d(input_4d, kernel_5d, strides=strides_3d, padding='SAME')
print sess.run(output_4d)

sess.close()

Entrée et sortie dans Tensorflow

entrez la description de l'image ici

entrez la description de l'image ici

Résumé

entrez la description de l'image ici

runhani
la source
18
Compte tenu de votre travail et de la clarté des explications, les votes positifs de 8 sont trop moindres.
user3282777
3
Le 2d conv avec entrée 3d est une belle touche. Je suggérerais une modification pour inclure 1d conv avec une entrée 2d (par exemple un tableau multicanal) et comparer la différence avec une 2d conv avec une entrée 2d.
SumNeuron
2
Réponse incroyable!
Ben
Pourquoi la direction de convection est-elle en 2d ↲. J'ai vu des sources qui prétendent que la direction est pour la ligne 1, puis pour la ligne 1+stride. La convolution elle-même est invariante par décalage, alors pourquoi la direction de la convolution est-elle importante?
Minh Triet
Merci pour votre question. Oui! la convolution elle-même est invariante par décalage. Donc, pour le calcul, la direction de conv n'est pas importante. (Vous pouvez calculer 2d conv avec deux grandes multiplications matricielles. Le framework caffe l'a déjà fait) mais pour comprendre, il est préférable d'expliquer avec la direction de conv. parce que 2d conv avec entrée 3d est déroutant sans direction. ^^
runhani
6

Suite à la réponse de @runhani, j'ajoute quelques détails supplémentaires pour rendre l'explication un peu plus claire et j'essaierai de l'expliquer un peu plus (et bien sûr avec des exemples de TF1 et TF2).

L'un des principaux éléments supplémentaires que j'inclus est,

  • Accent sur les applications
  • L'utilisation de tf.Variable
  • Explication plus claire des entrées / noyaux / sorties Convolution 1D / 2D / 3D
  • Les effets de la foulée / du rembourrage

Convolution 1D

Voici comment faire une convolution 1D en utilisant TF 1 et TF 2.

Et pour être précis, mes données ont les formes suivantes,

  • Vecteur 1D - [batch size, width, in channels](par exemple 1, 5, 1)
  • Noyau - [width, in channels, out channels](par exemple 5, 1, 4)
  • Sortie - [batch size, width, out_channels](par exemple 1, 5, 4)

Exemple TF1

import tensorflow as tf
import numpy as np

inp = tf.placeholder(shape=[None, 5, 1], dtype=tf.float32)
kernel = tf.Variable(tf.initializers.glorot_uniform()([5, 1, 4]), dtype=tf.float32)
out = tf.nn.conv1d(inp, kernel, stride=1, padding='SAME')

with tf.Session() as sess:
  tf.global_variables_initializer().run()
  print(sess.run(out, feed_dict={inp: np.array([[[0],[1],[2],[3],[4]],[[5],[4],[3],[2],[1]]])}))

Exemple TF2

import tensorflow as tf
import numpy as np

inp = np.array([[[0],[1],[2],[3],[4]],[[5],[4],[3],[2],[1]]]).astype(np.float32)
kernel = tf.Variable(tf.initializers.glorot_uniform()([5, 1, 4]), dtype=tf.float32)
out = tf.nn.conv1d(inp, kernel, stride=1, padding='SAME')
print(out)

C'est beaucoup moins de travail avec TF2 que TF2 n'en a pas besoin Sessionet variable_initializerpar exemple.

À quoi cela pourrait-il ressembler dans la vraie vie?

Comprenons donc ce que cela fait en utilisant un exemple de lissage de signal. Sur la gauche, vous avez l'original et sur la droite, vous avez la sortie d'un Convolution 1D qui a 3 canaux de sortie.

entrez la description de l'image ici

Que signifient plusieurs canaux?

Les canaux multiples sont essentiellement des représentations d'entités multiples d'une entrée. Dans cet exemple, vous avez trois représentations obtenues par trois filtres différents. Le premier canal est le filtre de lissage à pondération égale. Le second est un filtre qui pondère le milieu du filtre plus que les limites. Le filtre final fait le contraire du second. Vous pouvez donc voir comment ces différents filtres produisent des effets différents.

Applications d'apprentissage profond de la convolution 1D

La convolution 1D a été utilisée avec succès pour la tâche de classification des phrases .

Convolution 2D

Arrêt à la convolution 2D. Si vous êtes une personne qui apprend en profondeur, les chances que vous n'ayez pas rencontré de convolution 2D sont… enfin nulles. Il est utilisé dans les CNN pour la classification d'images, la détection d'objets, etc. ainsi que dans les problèmes de PNL impliquant des images (par exemple, la génération de légendes d'images).

Essayons un exemple, j'ai un noyau de convolution avec les filtres suivants ici,

  • Noyau de détection des bords (fenêtre 3x3)
  • Noyau de flou (fenêtre 3x3)
  • Noyau de netteté (fenêtre 3x3)

Et pour être précis, mes données ont les formes suivantes,

  • Image (noir et blanc) - [batch_size, height, width, 1](par exemple 1, 340, 371, 1)
  • Kernel (aka filtres) - [height, width, in channels, out channels](par exemple 3, 3, 1, 3)
  • Sortie (aka cartes de caractéristiques) - [batch_size, height, width, out_channels](par exemple 1, 340, 371, 3)

Exemple TF1,

import tensorflow as tf
import numpy as np
from PIL import Image

im = np.array(Image.open(<some image>).convert('L'))#/255.0

kernel_init = np.array(
    [
     [[[-1, 1.0/9, 0]],[[-1, 1.0/9, -1]],[[-1, 1.0/9, 0]]],
     [[[-1, 1.0/9, -1]],[[8, 1.0/9,5]],[[-1, 1.0/9,-1]]],
     [[[-1, 1.0/9,0]],[[-1, 1.0/9,-1]],[[-1, 1.0/9, 0]]]
     ])

inp = tf.placeholder(shape=[None, image_height, image_width, 1], dtype=tf.float32)
kernel = tf.Variable(kernel_init, dtype=tf.float32)
out = tf.nn.conv2d(inp, kernel, strides=[1,1,1,1], padding='SAME')

with tf.Session() as sess:
  tf.global_variables_initializer().run()
  res = sess.run(out, feed_dict={inp: np.expand_dims(np.expand_dims(im,0),-1)})

Exemple TF2

import tensorflow as tf
import numpy as np
from PIL import Image

im = np.array(Image.open(<some image>).convert('L'))#/255.0
x = np.expand_dims(np.expand_dims(im,0),-1)

kernel_init = np.array(
    [
     [[[-1, 1.0/9, 0]],[[-1, 1.0/9, -1]],[[-1, 1.0/9, 0]]],
     [[[-1, 1.0/9, -1]],[[8, 1.0/9,5]],[[-1, 1.0/9,-1]]],
     [[[-1, 1.0/9,0]],[[-1, 1.0/9,-1]],[[-1, 1.0/9, 0]]]
     ])

kernel = tf.Variable(kernel_init, dtype=tf.float32)

out = tf.nn.conv2d(x, kernel, strides=[1,1,1,1], padding='SAME')

À quoi cela pourrait-il ressembler dans la vraie vie?

Ici vous pouvez voir la sortie produite par le code ci-dessus. La première image est l'original et dans le sens des aiguilles d'une montre, vous avez les sorties du 1er filtre, 2ème filtre et 3 filtres. entrez la description de l'image ici

Que signifient plusieurs canaux?

Dans le contexte de la convolution 2D, il est beaucoup plus facile de comprendre ce que signifient ces multiples canaux. Disons que vous faites de la reconnaissance faciale. Vous pouvez penser à (c'est une simplification très irréaliste mais fait passer le point) à chaque filtre représentant un œil, une bouche, un nez, etc. De sorte que chaque carte de caractéristiques soit une représentation binaire de la présence ou non de cette caractéristique dans l'image que vous avez fournie . Je ne pense pas avoir besoin de souligner que pour un modèle de reconnaissance faciale, ce sont des fonctionnalités très précieuses. Plus d'informations dans cet article .

C'est une illustration de ce que j'essaye d'articuler.

entrez la description de l'image ici

Applications d'apprentissage en profondeur de la convolution 2D

La convolution 2D est très répandue dans le domaine de l'apprentissage profond.

Les CNN (Convolution Neural Networks) utilisent l'opération de convolution 2D pour presque toutes les tâches de vision par ordinateur (par exemple, classification d'images, détection d'objets, classification vidéo).

Convolution 3D

Maintenant, il devient de plus en plus difficile d'illustrer ce qui se passe à mesure que le nombre de dimensions augmente. Mais avec une bonne compréhension du fonctionnement de la convolution 1D et 2D, il est très simple de généraliser cette compréhension à la convolution 3D. Alors voilà.

Et pour être précis, mes données ont les formes suivantes,

  • Données 3D (LIDAR) - [batch size, height, width, depth, in channels](par exemple 1, 200, 200, 200, 1)
  • Noyau - [height, width, depth, in channels, out channels](par exemple 5, 5, 5, 1, 3)
  • Sortie - [batch size, width, height, width, depth, out_channels](par exemple 1, 200, 200, 2000, 3)

Exemple TF1

import tensorflow as tf
import numpy as np

tf.reset_default_graph()

inp = tf.placeholder(shape=[None, 200, 200, 200, 1], dtype=tf.float32)
kernel = tf.Variable(tf.initializers.glorot_uniform()([5,5,5,1,3]), dtype=tf.float32)
out = tf.nn.conv3d(inp, kernel, strides=[1,1,1,1,1], padding='SAME')

with tf.Session() as sess:
  tf.global_variables_initializer().run()
  res = sess.run(out, feed_dict={inp: np.random.normal(size=(1,200,200,200,1))})

Exemple TF2

import tensorflow as tf
import numpy as np

x = np.random.normal(size=(1,200,200,200,1))
kernel = tf.Variable(tf.initializers.glorot_uniform()([5,5,5,1,3]), dtype=tf.float32)
out = tf.nn.conv3d(x, kernel, strides=[1,1,1,1,1], padding='SAME') 

Applications d'apprentissage profond de la convolution 3D

La convolution 3D a été utilisée lors du développement d'applications d'apprentissage automatique impliquant des données LIDAR (Light Detection and Ranging) qui sont de nature tridimensionnelle.

Quoi ... plus de jargon?: Stride et rembourrage

D'accord, vous y êtes presque. Alors attendez. Voyons ce qu'est la foulée et le rembourrage. Ils sont assez intuitifs si vous pensez à eux.

Si vous traversez un couloir, vous y arrivez plus rapidement en moins d'étapes. Mais cela signifie également que vous avez observé un environnement moindre que si vous traversiez la pièce. Renforcez maintenant notre compréhension avec une jolie image aussi! Comprenons-les via la convolution 2D.

Comprendre la foulée

Foulée de convolution

Lorsque vous utilisez tf.nn.conv2dpar exemple, vous devez le définir comme un vecteur de 4 éléments. Il n'y a aucune raison d'être intimidé par cela. Il contient simplement les foulées dans l'ordre suivant.

  • Convolution 2D - [batch stride, height stride, width stride, channel stride]. Ici, la foulée par lots et la foulée de canal que vous venez de définir sur un (j'implémente des modèles d'apprentissage en profondeur depuis 5 ans et je n'ai jamais eu à les définir sur autre chose). Cela ne vous laisse donc que 2 foulées à définir.

  • Convolution 3D - [batch stride, height stride, width stride, depth stride, channel stride]. Ici, vous ne vous souciez que des foulées de hauteur / largeur / profondeur.

Comprendre le rembourrage

Maintenant, vous remarquez que peu importe la taille de votre foulée (c'est-à-dire 1), il y a une réduction de dimension inévitable qui se produit pendant la convolution (par exemple, la largeur est de 3 après la convolution d'une image de 4 unités). Ceci n'est pas souhaitable, en particulier lors de la construction de réseaux de neurones à convolution profonde. C'est là que le rembourrage vient à la rescousse. Il existe deux types de rembourrage les plus couramment utilisés.

  • SAME et VALID

Ci-dessous, vous pouvez voir la différence.

entrez la description de l'image ici

Dernier mot : si vous êtes très curieux, vous vous demandez peut-être. Nous venons de lancer une bombe sur toute la réduction automatique des dimensions et parlons maintenant de progrès différents. Mais la meilleure chose à propos de la foulée est que vous contrôlez quand et comment les dimensions sont réduites.

thushv89
la source
2
  1. CNN 1D, 2D ou 3D fait référence à la direction de convolution, plutôt qu'à la dimension d'entrée ou de filtre.

  2. Pour une entrée de canal, CNN2D est égal à CNN1D est la longueur du noyau = longueur de l'entrée. (1 direction de conv.)

Jerry Liu
la source
2

En résumé, dans 1D CNN, le noyau se déplace dans une direction. Les données d'entrée et de sortie de 1D CNN sont à 2 dimensions. Principalement utilisé sur les données de séries chronologiques.

Dans CNN 2D, le noyau se déplace dans 2 directions. Les données d'entrée et de sortie de CNN 2D sont en 3 dimensions. Principalement utilisé sur les données d'image.

Dans 3D CNN, le noyau se déplace dans 3 directions. Les données d'entrée et de sortie du CNN 3D sont en 4 dimensions. Principalement utilisé sur les données d'image 3D (IRM, tomodensitométrie).

Vous pouvez trouver plus de détails ici: https://medium.com/@xzz201920/conv1d-conv2d-and-conv3d-8a59182c4d6

zz x
la source