Odpowiedzi:
Chcę to wyjaśnić obrazem z C3D .
W skrócie, kierunek splotu i kształt wyjścia są ważne!
↑↑↑↑↑ 1D zwojów - Podstawowe ↑↑↑↑↑
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)
& Uarr ; & uarr; & gt; & gt; & gt; & gt; & gt; & gt; & gt; &
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)
& Uarr ; & uarr; &; & gt; & gt; & gt; & gt; & gt; & gt; & rdquo; & rdquo; & d \ u003; d \ u003d
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)
↑↑↑↑↑ 2D zwojów z wejściem 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)
↑↑↑↑↑ Bonus conv 1x1 w 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)
- Oryginalny link: LINK
- Autor: Martin Görner
- Twitter: @martin_gorner
- Google +: plus.google.com/+MartinGorne
↑↑↑↑↑ 1D zwojów z wejściem 1D ↑↑↑↑↑
↑↑↑↑↑ 1D zwojów z wejściem 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
, a następnie → dla wiersza 1+stride
. Samo splot jest niezmienny, więc dlaczego kierunek splotu ma znaczenie?
Podążając za odpowiedzią @runhani, dodaję kilka dodatkowych szczegółów, aby wyjaśnienie było nieco bardziej przejrzyste i spróbuję to wyjaśnić nieco dokładniej (i oczywiście z przykładami z TF1 i TF2).
Jednym z głównych dodatkowych elementów, które dołączam, są:
tf.Variable
Oto jak możesz wykonać splot 1D używając TF 1 i TF 2.
Mówiąc konkretnie, moje dane mają następujące kształty,
[batch size, width, in channels]
(np. 1, 5, 1
)[width, in channels, out channels]
(np. 5, 1, 4
)[batch size, width, out_channels]
(np. 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)
To znacznie mniej pracy z TF2, ponieważ TF2 nie potrzebuje Session
i variable_initializer
na przykład.
Zrozummy więc, co to robi, na przykładzie wygładzania sygnału. Po lewej stronie masz oryginał, a po prawej wyjście Convolution 1D z 3 kanałami wyjściowymi.
Wiele kanałów to w zasadzie wiele reprezentacji funkcji wejścia. W tym przykładzie masz trzy reprezentacje uzyskane przez trzy różne filtry. Pierwszy kanał to jednakowo ważony filtr wygładzający. Drugi to filtr, który waży środek filtru bardziej niż granice. Ostatni filtr działa odwrotnie niż drugi. Możesz więc zobaczyć, jak te różne filtry wywołują różne efekty.
Zwój 1D został pomyślnie użyty do zadania klasyfikacji zdań .
Wyłącz do splotu 2D. Jeśli jesteś osobą uczącą się głęboko, prawdopodobieństwo, że nie spotkałeś się z konwolucją 2D, wynosi… cóż, zero. Jest używany w CNN do klasyfikacji obrazów, wykrywania obiektów itp., A także w problemach NLP, które obejmują obrazy (np. Generowanie podpisów do obrazów).
Spróbujmy na przykładzie, mam jądro konwolucji z następującymi filtrami tutaj,
Mówiąc konkretnie, moje dane mają następujące kształty,
[batch_size, height, width, 1]
(np. 1, 340, 371, 1
)[height, width, in channels, out channels]
(np. 3, 3, 1, 3
)[batch_size, height, width, out_channels]
(np. 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')
Tutaj możesz zobaczyć dane wyjściowe wygenerowane przez powyższy kod. Pierwszy obraz jest oryginalny i zgodnie z ruchem wskazówek zegara masz wyjścia pierwszego filtra, drugiego filtra i trzeciego filtra.
W kontekście splotu 2D dużo łatwiej jest zrozumieć, co oznaczają te wielokrotne kanały. Powiedz, że robisz rozpoznawanie twarzy. Możesz sobie wyobrazić (jest to bardzo nierealistyczne uproszczenie, ale ma sens) każdy filtr reprezentuje oko, usta, nos itp. Tak więc każda mapa cech byłaby binarną reprezentacją tego, czy ta cecha znajduje się na dostarczonym obrazie . Myślę, że nie muszę podkreślać, że dla modelu rozpoznawania twarzy są to bardzo cenne cechy. Więcej informacji w tym artykule .
To jest ilustracja tego, co próbuję wyrazić.
Konwolucja 2D jest bardzo rozpowszechniona w dziedzinie uczenia głębokiego.
CNN (Convolution Neural Networks) używają operacji splotu 2D do prawie wszystkich zadań widzenia komputerowego (np. Klasyfikacja obrazu, wykrywanie obiektów, klasyfikacja wideo).
Teraz coraz trudniej jest zilustrować, co się dzieje wraz ze wzrostem liczby wymiarów. Ale mając dobre zrozumienie, jak działa splot 1D i 2D, bardzo łatwo jest uogólnić to zrozumienie na splot 3D. Więc oto idzie.
Mówiąc konkretnie, moje dane mają następujące kształty,
[batch size, height, width, depth, in channels]
(np. 1, 200, 200, 200, 1
)[height, width, depth, in channels, out channels]
(np. 5, 5, 5, 1, 3
)[batch size, width, height, width, depth, out_channels]
(np. 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')
Konwolucja 3D została wykorzystana podczas tworzenia aplikacji uczenia maszynowego wykorzystujących dane LIDAR (Light Detection and Ranging), które mają charakter trójwymiarowy.
W porządku, już prawie jesteś. Więc trzymaj się. Zobaczmy, czym jest krok i wyściółka. Są dość intuicyjne, jeśli się nad nimi zastanowić.
Jeśli przejdziesz przez korytarz, dotrzesz tam szybciej, wykonując mniejszą liczbę kroków. Ale oznacza to również, że obserwowałeś mniejsze otoczenie, niż gdybyś przeszedł przez pokój. Wzmocnijmy teraz nasze zrozumienie ładnym obrazkiem! Rozumiemy to poprzez splot 2D.
Kiedy używasz tf.nn.conv2d
na przykład, musisz ustawić go jako wektor 4 elementów. Nie ma powodu, aby się tym onieśmielać. Po prostu zawiera kroki w następującej kolejności.
Konwolucja 2D - [batch stride, height stride, width stride, channel stride]
. Tutaj, batch stride i channel stride, ustawiłeś tylko jeden (wdrażam modele głębokiego uczenia się od 5 lat i nigdy nie musiałem ustawiać ich na nic poza jednym). Masz więc tylko 2 kroki do ustawienia.
Konwolucja 3D - [batch stride, height stride, width stride, depth stride, channel stride]
. Tutaj martwisz się tylko krokami wysokości / szerokości / głębokości.
Teraz zauważysz, że bez względu na to, jak mały jest twój krok (tj. 1), podczas splotu następuje nieuniknione zmniejszenie wymiarów (np. Szerokość wynosi 3 po skręceniu obrazu o szerokości 4 jednostek). Jest to niepożądane zwłaszcza przy budowaniu głębokich konwolucyjnych sieci neuronowych. Tutaj na ratunek przychodzi wyściółka. Istnieją dwa najczęściej używane typy wypełnienia.
SAME
i VALID
Poniżej możesz zobaczyć różnicę.
Ostatnie słowo : jeśli jesteś bardzo ciekawy, możesz się zastanawiać. Właśnie zrzuciliśmy bombę na całą automatyczną redukcję wymiarów, a teraz rozmawiamy o różnych krokach. Ale najlepszą rzeczą w kroku jest to, że kontrolujesz, gdzie i jak zmniejszają się wymiary.
Podsumowując, w 1D CNN jądro porusza się w jednym kierunku. Dane wejściowe i wyjściowe 1D CNN są dwuwymiarowe. Używany głównie w przypadku danych szeregów czasowych.
W 2D CNN jądro porusza się w 2 kierunkach. Dane wejściowe i wyjściowe 2D CNN są trójwymiarowe. Używany głównie w przypadku danych obrazu.
W 3D CNN jądro porusza się w 3 kierunkach. Dane wejściowe i wyjściowe 3D CNN są 4-wymiarowe. Stosowany głównie w przypadku danych obrazu 3D (skany MRI, CT).
Więcej szczegółów znajdziesz tutaj: https://medium.com/@xzz201920/conv1d-conv2d-and-conv3d-8a59182c4d6