Respostas:
Eu quero explicar com imagem de C3D .
Em poucas palavras, a direção convolucional e o formato da saída são importantes!
↑↑↑↑↑ 1D Convolutions - Basic ↑↑↑↑↑
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)
↑↑↑↑↑ Convoluções 2D - Básica ↑↑↑↑↑
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)
↑↑↑↑↑ 3D Convolutions - Basic ↑↑↑↑↑
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)
↑↑↑↑↑ Convoluções 2D com entrada 3D - LeNet, VGG, ..., ↑↑↑↑↑
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)
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)
↑↑↑↑↑ Bônus 1x1 conv na CNN - GoogLeNet, ..., ↑↑↑↑↑
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)
- Link original: LINK
- O autor: Martin Görner
- Twitter: @martin_gorner
- Google +: plus.google.com/+MartinGorne
↑↑↑↑↑ Convoluções 1D com entrada 1D ↑↑↑↑↑
↑↑↑↑↑ Convoluções 1D com entrada 2D ↑↑↑↑↑
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()
1
e depois → para linha 1+stride
. A convolução em si é invariável, então por que a direção da convolução é importante?
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:
tf.Variable
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,
[batch size, width, in channels]
(por exemplo 1, 5, 1
)[width, in channels, out channels]
(por exemplo 5, 1, 4
)[batch size, width, out_channels]
(por exemplo 1, 5, 4
)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]]])}))
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 Session
e, variable_initializer
por exemplo.
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.
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.
A convolução 1D foi usada com êxito na tarefa de classificação de sentenças .
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,
E, para ser específico, meus dados têm as seguintes formas,
[batch_size, height, width, 1]
(por exemplo 1, 340, 371, 1
)[height, width, in channels, out channels]
(por exemplo 3, 3, 1, 3
)[batch_size, height, width, out_channels]
(por exemplo 1, 340, 371, 3
)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)})
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')
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.
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.
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).
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,
[batch size, height, width, depth, in channels]
(por exemplo 1, 200, 200, 200, 1
)[height, width, depth, in channels, out channels]
(por exemplo 5, 5, 5, 1, 3
)[batch size, width, height, width, depth, out_channels]
(por exemplo 1, 200, 200, 2000, 3
)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))})
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')
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.
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.
Quando você usa, tf.nn.conv2d
por 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.
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.
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.
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