Compreensão intuitiva das convoluções 1D, 2D e 3D em redes neurais convolucionais

Respostas:

414

Eu quero explicar com imagem de C3D .

Em poucas palavras, a direção convolucional e o formato da saída são importantes!

insira a descrição da imagem aqui

↑↑↑↑↑ 1D Convolutions - Basic ↑↑↑↑↑

  • apenas 1 direção (eixo do tempo) para calcular conv
  • entrada = [W], filtro = [k], saída = [W]
  • ex) entrada = [1,1,1,1,1], filtro = [0,25,0,5,0,25], saída = [1,1,1,1,1]
  • formato de saída é matriz 1D
  • exemplo) suavização de gráficos

Código tf.nn.conv1d Exemplo de brinquedo

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)

insira a descrição da imagem aqui

↑↑↑↑↑ Convoluções 2D - Básica ↑↑↑↑↑

  • Direção 2 (x, y) para calcular conv
  • forma de saída é matriz 2D
  • entrada = [W, H], filtro = [k, k] saída = [W, H]
  • exemplo) Sobel Egde Fllter

tf.nn.conv2d - Exemplo de brinquedo

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)

insira a descrição da imagem aqui

↑↑↑↑↑ 3D Convolutions - Basic ↑↑↑↑↑

  • Direção 3 (x, y, z) para calcular a conv
  • a forma de saída é o volume 3D
  • entrada = [W, H, L ], filtro = [k, k, d ] saída = [W, H, M]
  • d <L é importante! para fazer volume de saída
  • exemplo) C3D

tf.nn.conv3d - Exemplo de brinquedo

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)

insira a descrição da imagem aqui

↑↑↑↑↑ Convoluções 2D com entrada 3D - LeNet, VGG, ..., ↑↑↑↑↑

  • Eventhough entrada é 3D ex) 224x224x3, 112x112x32
  • a forma de saída não é o volume 3D , mas a matriz 2D
  • porque a profundidade do filtro = L deve corresponder aos canais de entrada = L
  • Direção 2 (x, y) para calcular conv! não 3D
  • entrada = [W, H, L ], filtro = [k, k, L ] saída = [W, H]
  • forma de saída é matriz 2D
  • e se quisermos treinar N filtros (N é o número de filtros)
  • então a forma de saída é (2D empilhado) 3D = 2D x N matriz.

conv2d - LeNet, VGG, ... para 1 filtro

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, ... para filtros N

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)

insira a descrição da imagem aqui ↑↑↑↑↑ Bônus 1x1 conv na CNN - GoogLeNet, ..., ↑↑↑↑↑

  • 1x1 conv é confuso quando você pensa isso como filtro de imagem 2D como sobel
  • para 1x1 conv na CNN, a entrada é 3D, como na figura acima.
  • calcula a filtragem profunda
  • entrada = [W, H, L], filtro = [1,1, L] saída = [W, H]
  • A forma empilhada de saída é 3D = matriz 2D x N.

tf.nn.conv2d - caso especial 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)

Animação (2D Conv com entradas 3D)

insira a descrição da imagem aqui - Link original: LINK
- O autor: Martin Görner
- Twitter: @martin_gorner
- Google +: plus.google.com/+MartinGorne

Convoluções 1D bônus com entrada 2D

insira a descrição da imagem aqui ↑↑↑↑↑ Convoluções 1D com entrada 1D ↑↑↑↑↑

insira a descrição da imagem aqui ↑↑↑↑↑ Convoluções 1D com entrada 2D ↑↑↑↑↑

  • A entrada de Eventhough é 2D ex) 20x14
  • formato de saída não é 2D , mas matriz 1D
  • porque a altura do filtro = L deve corresponder à altura da entrada = L
  • 1- direção (x) para calcular a conv! não 2D
  • entrada = [W, L ], filtro = [k, L ] saída = [W]
  • forma de saída é 1D Matrix
  • e se quisermos treinar N filtros (N é o número de filtros)
  • então a forma de saída é (empilhada 1D) 2D = 1D x N matriz.

Bônus 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()

Entrada e Saída em Tensorflow

insira a descrição da imagem aqui

insira a descrição da imagem aqui

Resumo

insira a descrição da imagem aqui

runhani
fonte
18
Considerando seu trabalho e clareza nas explicações, os votos positivos de 8 são muito menores.
user3282777
3
O conv 2D com entrada 3D é um toque agradável. Eu sugeriria uma edição para incluir 1d conv com entrada 2d (por exemplo, uma matriz multicanal) e comparar sua diferença com uma conv 2d com entrada 2d.
SumNeuron
2
Resposta incrível!
Ben
Por que a direção conv está em 2d ↲. Vi fontes que afirmam que a direção é para linha 1e depois para linha 1+stride. A convolução em si é invariável, então por que a direção da convolução é importante?
Minh Triet 19/03/19
Obrigado por sua pergunta. Sim! a convolução em si é uma mudança invariável. portanto, para o cálculo da direção da conv. não é importante (você pode calcular 2d conv com duas grandes matrizes. caffe framework já o fez), mas para entender é melhor explicar com a direção da conv. porque conv 2D com entrada 3D é confuso sem direção. ^^
runhani 29/03
6

Seguindo a resposta de @runhani, estou adicionando mais alguns detalhes para tornar a explicação um pouco mais clara e tentarei explicar um pouco mais (e, é claro, com exemplos de TF1 e TF2).

Um dos principais bits adicionais que incluo são:

  • Ênfase em aplicações
  • Uso de tf.Variable
  • Explicação mais clara de entradas / núcleos / saídas 1D / 2D / 3D convolução
  • Os efeitos do passo / preenchimento

Convolução 1D

Veja como você pode fazer a convolução 1D usando TF 1 e TF 2.

E, para ser específico, meus dados têm as seguintes formas,

  • Vetor 1D - [batch size, width, in channels](por exemplo 1, 5, 1)
  • Kernel - [width, in channels, out channels](por exemplo 5, 1, 4)
  • Saída - [batch size, width, out_channels](por exemplo 1, 5, 4)

Exemplo 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]]])}))

Exemplo 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)

É muito menos trabalho com o TF2, pois o TF2 não precisa Sessione, variable_initializerpor exemplo.

Como isso pode ser na vida real?

Então, vamos entender o que isso está fazendo usando um exemplo de suavização de sinal. À esquerda você tem o original e à direita você tem a saída de um Convolution 1D que possui 3 canais de saída.

insira a descrição da imagem aqui

O que significam vários canais?

Múltiplos canais são basicamente múltiplas representações de recursos de uma entrada. Neste exemplo, você tem três representações obtidas por três filtros diferentes. O primeiro canal é o filtro de nivelamento igualmente ponderado. O segundo é um filtro que pesa mais no meio do filtro do que nos limites. O filtro final faz o oposto do segundo. Então você pode ver como esses filtros diferentes produzem efeitos diferentes.

Aplicações de aprendizagem profunda de convolução 1D

A convolução 1D foi usada com êxito na tarefa de classificação de sentenças .

Convolução 2D

Desligado para convolução 2D. Se você é uma pessoa que aprende profundamente, as chances de você não encontrar a convolução 2D são ... bem, quase zero. É usado em CNNs para classificação de imagens, detecção de objetos, etc., bem como em problemas de PNL que envolvem imagens (por exemplo, geração de legendas).

Vamos tentar um exemplo, eu tenho um kernel de convolução com os seguintes filtros aqui,

  • Kernel de detecção de borda (janela 3x3)
  • Desfocar o kernel (janela 3x3)
  • Afiar o kernel (janela 3x3)

E, para ser específico, meus dados têm as seguintes formas,

  • Imagem (preto e branco) - [batch_size, height, width, 1](por exemplo 1, 340, 371, 1)
  • Kernel (aka filtros) - [height, width, in channels, out channels](por exemplo 3, 3, 1, 3)
  • Saída (também conhecido como mapas de recursos) - [batch_size, height, width, out_channels](por exemplo 1, 340, 371, 3)

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

Exemplo 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')

Como isso pode ser na vida real?

Aqui você pode ver a saída produzida pelo código acima. A primeira imagem é a original e, no sentido horário, você tem saídas do 1º filtro, 2º filtro e 3 filtro. insira a descrição da imagem aqui

O que significam vários canais?

No contexto de convolução 2D, é muito mais fácil entender o que esses múltiplos canais significam. Digamos que você esteja reconhecendo o rosto. Você pode pensar (isso é uma simplificação muito irrealista, mas mostra o ponto): cada filtro representa um olho, boca, nariz etc. Para que cada mapa de recursos seja uma representação binária de se esse recurso está na imagem que você forneceu . Acho que não preciso enfatizar que, para um modelo de reconhecimento facial, esses são recursos muito valiosos. Mais informações neste artigo .

Esta é uma ilustração do que estou tentando articular.

insira a descrição da imagem aqui

Aplicações de aprendizagem profunda de convolução 2D

A convolução 2D é muito prevalente no domínio da aprendizagem profunda.

As CNNs (Redes Neurais de Convolução) usam operação de convolução 2D para quase todas as tarefas de visão computacional (por exemplo, classificação de imagens, detecção de objetos, classificação de vídeos).

Convolução 3D

Agora fica cada vez mais difícil ilustrar o que está acontecendo à medida que o número de dimensões aumenta. Mas, com um bom entendimento de como funciona a convolução 1D e 2D, é muito fácil generalizar esse entendimento para a convolução 3D. Então aqui vai.

E, para ser específico, meus dados têm as seguintes formas,

  • Dados 3D (LIDAR) - [batch size, height, width, depth, in channels](por exemplo 1, 200, 200, 200, 1)
  • Kernel - [height, width, depth, in channels, out channels](por exemplo 5, 5, 5, 1, 3)
  • Saída - [batch size, width, height, width, depth, out_channels](por exemplo 1, 200, 200, 2000, 3)

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

Exemplo 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') 

Aplicações de aprendizagem profunda de convolução 3D

A convolução 3D foi usada no desenvolvimento de aplicativos de aprendizado de máquina envolvendo dados LIDAR (Light Detection and Ranging), que são tridimensionais por natureza.

O que ... mais jargão ?: Passo e preenchimento

Tudo bem, você está quase lá. Então espere. Vamos ver o que é o passo e o preenchimento. Eles são bastante intuitivos se você pensar sobre eles.

Se você atravessar um corredor, chegará mais rápido em menos etapas. Mas isso também significa que você observou menos entorno do que se atravessasse a sala. Vamos agora reforçar nosso entendimento com uma imagem bonita também! Vamos entender isso por convolução 2D.

Compreensão do passo

Passo da convolução

Quando você usa, tf.nn.conv2dpor exemplo, você precisa configurá-lo como um vetor de 4 elementos. Não há razão para se intimidar com isso. Apenas contém os passos na seguinte ordem.

  • Convolução 2D - [batch stride, height stride, width stride, channel stride]. Aqui, passo em lote e passo do canal que você acabou de definir para um (estou implementando modelos de aprendizado profundo há 5 anos e nunca tive que defini-los para nada, exceto um). Então, isso deixa você apenas com 2 passos para definir.

  • Convolução 3D - [batch stride, height stride, width stride, depth stride, channel stride]. Aqui você se preocupa apenas com os passos de altura / largura / profundidade.

Entendendo o preenchimento

Agora, você percebe que, por menor que seja o seu passo (ou seja, 1), ocorre uma redução inevitável da dimensão durante a convolução (por exemplo, a largura é 3 após a conversão de uma imagem de 4 unidades de largura). Isso é indesejável, especialmente ao criar redes neurais de convolução profunda. É aqui que o preenchimento vem em socorro. Existem dois tipos de preenchimento mais usados.

  • SAME e VALID

Abaixo você pode ver a diferença.

insira a descrição da imagem aqui

Palavra final : se você está muito curioso, pode estar se perguntando. Acabamos de lançar uma bomba sobre toda a redução automática de dimensão e agora falando sobre ter avanços diferentes. Mas a melhor coisa sobre a passada é que você controla quando, onde e como as dimensões são reduzidas.

thushv89
fonte
2
  1. CNN 1D, 2D ou 3D refere-se à direção da convolução, em vez da dimensão da entrada ou do filtro.

  2. Para entrada de 1 canal, CNN2D é igual a CNN1D é comprimento do kernel = comprimento da entrada. (1 direção conv)

Jerry Liu
fonte
2

Em resumo, no 1D CNN, o kernel se move em uma direção. Os dados de entrada e saída de 1D CNN são bidimensionais. Usado principalmente em dados de séries temporais.

Na CNN 2D, o kernel se move em duas direções. Os dados de entrada e saída da CNN 2D são tridimensionais. Usado principalmente em dados de imagem.

Na CNN 3D, o kernel se move em 3 direções. Os dados de entrada e saída da CNN 3D são tridimensionais. Usado principalmente em dados de imagem 3D (ressonância magnética, tomografia computadorizada).

Você pode encontrar mais detalhes aqui: https://medium.com/@xzz201920/conv1d-conv2d-and-conv3d-8a59182c4d6

zz x
fonte