.. _sec_transposed_conv:
Devrik Evrişim
==============
Bugüne kadar gördüğümüz CNN katmanları, evrişimli katmanlar
(:numref:`sec_conv_layer`) ve ortaklama katmanları
(:numref:`sec_pooling`) gibi, genellikle girdinin uzamsal boyutlarını
(yükseklik ve genişlik) azaltır (örnek seyreltme) veya değişmeden tutar.
Piksel düzeyinde sınıflandırılan anlamsal bölümlemede, girdi ve çıktının
mekansal boyutları aynı ise uygun olacaktır. Örneğin, bir çıktı
pikselindeki kanal boyutu, girdi pikselinin sınıflandırma sonuçlarını
aynı uzamsal konumda tutabilir.
Bunu başarmak için, özellikle uzamsal boyutlar CNN katmanları tarafından
azaltıldıktan sonra, ara öznitelik haritalarının mekansal boyutlarını
artırabilecek (örnek sıklaştırma) yapabilen başka bir CNN katmanlarını
kullanabiliriz. Bu bölümde, evrişim tarafından örnek seyreltme
işlemlerini tersine çevirmek için *kesirli adımlı evrişim*
:cite:`Dumoulin.Visin.2016` olarak da adlandırılan *devrik evrişimi*
tanıtacağız.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
from d2l import mxnet as d2l
from mxnet import init, np, npx
from mxnet.gluon import nn
npx.set_np()
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
import torch
from torch import nn
from d2l import torch as d2l
.. raw:: html
.. raw:: html
Temel İşlem
-----------
Şimdilik kanalları görmezden gelerek, 1 adımlı ve dolgusuz temel devrik
evrişim işlemiyle başlayalım. Bir :math:`n_h \times n_w` girdi tensörü
ve bir :math:`k_h \times k_w` çekirdeği verildiğini varsayalım. Çekirdek
penceresini her satırda :math:`n_w` kez ve her sütundaki :math:`n_h` kez
1 adımıyla kaydırılması toplam :math:`n_h n_w` ara sonuç verir. Her ara
sonuç sıfır olarak ilklenen bir
:math:`(n_h + k_h - 1) \times (n_w + k_w - 1)` tensördür. Her ara
tensörün hesaplanması için, girdi tensöründe bulunan her eleman çekirdek
ile çarpılır, böylece sonuçta ortaya çıkan :math:`k_h \times k_w`
tensörü her ara tensörde bir kısmın yerini alır. Her ara tensördeki
değiştirilen kısmın konumunun, hesaplama için kullanılan girdi
tensöründe elemanın konumuna karşılık geldiğini unutmayın. Sonunda,
çıktı üretmek için tüm ara sonuçlar toplanır.
Örneğin, :numref:`fig_trans_conv`, :math:`2\times 2` girdi tensörü
için :math:`2\times 2` çekirdeği ile devrik evrişimin nasıl
hesaplandığını göstermektedir.
.. _fig_trans_conv:
.. figure:: ../img/trans_conv.svg
:math:`2\times 2` çekirdek ile devrik evrişim. Gölgeli kısımlar, bir
ara tensörün bir kısmı ile hesaplama için kullanılan girdi ve
çekirdek tensör elemanlarıdır.
Bir girdi matrisi ``X`` ve bir çekirdek matrisi ``K`` için bu temel
devrik evrişim işlemi ``trans_conv``'u uygulayabiliriz.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
def trans_conv(X, K):
h, w = K.shape
Y = np.zeros((X.shape[0] + h - 1, X.shape[1] + w - 1))
for i in range(X.shape[0]):
for j in range(X.shape[1]):
Y[i: i + h, j: j + w] += X[i, j] * K
return Y
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
def trans_conv(X, K):
h, w = K.shape
Y = torch.zeros((X.shape[0] + h - 1, X.shape[1] + w - 1))
for i in range(X.shape[0]):
for j in range(X.shape[1]):
Y[i: i + h, j: j + w] += X[i, j] * K
return Y
.. raw:: html
.. raw:: html
Çekirdek yoluyla girdi öğelerini *azaltan* olağan evrişimin
(:numref:`sec_conv_layer`) aksine, devrik evrişim, girdi öğelerini
çekirdek yoluyla *yayınlar*, böylece girdiden daha büyük bir çıktı
üretir. Temel iki boyutlu devrik evrişim işleminin yukarıdaki
uygulamanın çıktısını doğrulamak için :numref:`fig_trans_conv`
şeklindeki girdi tensörü ``X``'i ve çekirdek tensörü ``K``'yi
oluşturabiliriz.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
X = np.array([[0.0, 1.0], [2.0, 3.0]])
K = np.array([[0.0, 1.0], [2.0, 3.0]])
trans_conv(X, K)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
array([[ 0., 0., 1.],
[ 0., 4., 6.],
[ 4., 12., 9.]])
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
X = torch.tensor([[0.0, 1.0], [2.0, 3.0]])
K = torch.tensor([[0.0, 1.0], [2.0, 3.0]])
trans_conv(X, K)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
tensor([[ 0., 0., 1.],
[ 0., 4., 6.],
[ 4., 12., 9.]])
.. raw:: html
.. raw:: html
Alternatif olarak, girdi ``X`` ve çekirdek ``K`` dört boyutlu tensörler
olduğunda, aynı sonuçları elde etmek için üst seviye API'leri
kullanabiliriz.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
X, K = X.reshape(1, 1, 2, 2), K.reshape(1, 1, 2, 2)
tconv = nn.Conv2DTranspose(1, kernel_size=2)
tconv.initialize(init.Constant(K))
tconv(X)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
array([[[[ 0., 0., 1.],
[ 0., 4., 6.],
[ 4., 12., 9.]]]])
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
X, K = X.reshape(1, 1, 2, 2), K.reshape(1, 1, 2, 2)
tconv = nn.ConvTranspose2d(1, 1, kernel_size=2, bias=False)
tconv.weight.data = K
tconv(X)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
tensor([[[[ 0., 0., 1.],
[ 0., 4., 6.],
[ 4., 12., 9.]]]], grad_fn=)
.. raw:: html
.. raw:: html
Dolgu, Adım ve Çoklu Kanallar
-----------------------------
Dolgunun girdiye uygulandığı düzenli evrişimden farklı olarak, devrik
evrişim içindeki çıktıya uygulanır. Örneğin, yükseklik ve genişliğin her
iki tarafındaki dolgu sayısı 1 olarak belirtilirken, ilk ve son satırlar
ve sütunlar devrik evrişim çıktısından kaldırılır.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
tconv = nn.Conv2DTranspose(1, kernel_size=2, padding=1)
tconv.initialize(init.Constant(K))
tconv(X)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
array([[[[4.]]]])
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
tconv = nn.ConvTranspose2d(1, 1, kernel_size=2, padding=1, bias=False)
tconv.weight.data = K
tconv(X)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
tensor([[[[4.]]]], grad_fn=)
.. raw:: html
.. raw:: html
Devrik evrişimde, girdi için değil, ara sonuçlar (böylece çıktı) için
adımlar belirtilir. :numref:`fig_trans_conv` içinde bahsedilen aynı
girdi ve çekirdek tensörleri kullanılırken, adımın 1'den 2'ye
değiştirilmesi, ara tensörlerin, dolayısıyla
:numref:`fig_trans_conv_stride2` şeklinde gösterilen çıktı tensörünün,
hem yüksekliğini hem de ağırlığını artırır.
.. _fig_trans_conv_stride2:
.. figure:: ../img/trans_conv_stride2.svg
2 uzun adımlı :math:`2\times 2` çekirdekli devrik evrişim. Gölgeli
kısımlar, hesaplama için kullanılan girdi ve çekirdek tensör
elemanlarının yanı sıra bir ara tensörün bir kısmıdır.
Aşağıdaki kod parçacığı, :numref:`fig_trans_conv_stride2`'te 2 uzun
adım için devrik evrişim çıktısını doğrulayabilir.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
tconv = nn.Conv2DTranspose(1, kernel_size=2, strides=2)
tconv.initialize(init.Constant(K))
tconv(X)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
array([[[[0., 0., 0., 1.],
[0., 0., 2., 3.],
[0., 2., 0., 3.],
[4., 6., 6., 9.]]]])
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
tconv = nn.ConvTranspose2d(1, 1, kernel_size=2, stride=2, bias=False)
tconv.weight.data = K
tconv(X)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
tensor([[[[0., 0., 0., 1.],
[0., 0., 2., 3.],
[0., 2., 0., 3.],
[4., 6., 6., 9.]]]], grad_fn=)
.. raw:: html
.. raw:: html
Çoklu girdi ve çıktı kanalı için, devrik evrişim normal evrişim ile aynı
şekilde çalışır. Girdinin :math:`c_i` kanallara sahip olduğunu ve devrik
evrişimin her girdi kanalına bir :math:`k_h\times k_w` çekirdek tensörü
atadığını varsayalım. Birden fazla çıktı kanalı belirtildiğinde, her
çıktı kanalı için bir :math:`c_i\times k_h\times k_w` çekirdeğine sahip
olacağız.
Her durumda, :math:`\mathsf{X}`'i :math:`\mathsf{Y}=f(\mathsf{X})`
çıktısı almak için bir :math:`f` evrişim katmanına beslersek ve
:math:`\mathsf{X}` içindeki kanalların sayısı olan çıktı kanallarının
sayısı dışında :math:`f` ile aynı hiper parametrelere sahip bir
:math:`g` devrik evrimsel katman oluşturursak, o zaman :math:`g(Y)`
:math:`\mathsf{X}` ile aynı şekle sahip olacaktır. Bu, aşağıdaki örnekte
gösterilebilir.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
X = np.random.uniform(size=(1, 10, 16, 16))
conv = nn.Conv2D(20, kernel_size=5, padding=2, strides=3)
tconv = nn.Conv2DTranspose(10, kernel_size=5, padding=2, strides=3)
conv.initialize()
tconv.initialize()
tconv(conv(X)).shape == X.shape
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
True
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
X = torch.rand(size=(1, 10, 16, 16))
conv = nn.Conv2d(10, 20, kernel_size=5, padding=2, stride=3)
tconv = nn.ConvTranspose2d(20, 10, kernel_size=5, padding=2, stride=3)
tconv(conv(X)).shape == X.shape
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
True
.. raw:: html
.. raw:: html
.. _subsec-connection-to-mat-transposition:
Matris Devirme ile Bağlantı
---------------------------
Devrik evrişim, matris devirme sonrasından adlandırılmıştır. Açıklamak
için, önce matris çarpımlarını kullanarak evrişimlerin nasıl
uygulanacağını görelim. Aşağıdaki örnekte, :math:`3\times 3`'lük girdi
``X`` ve :math:`2\times 2`'lik evrişim çekirdeği ``K`` tanımlıyoruz ve
``Y`` evrişim çıktısını hesaplamak için ``corr2d`` işlevini
kullanıyoruz.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
X = np.arange(9.0).reshape(3, 3)
K = np.array([[1.0, 2.0], [3.0, 4.0]])
Y = d2l.corr2d(X, K)
Y
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
array([[27., 37.],
[57., 67.]])
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
X = torch.arange(9.0).reshape(3, 3)
K = torch.tensor([[1.0, 2.0], [3.0, 4.0]])
Y = d2l.corr2d(X, K)
Y
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
tensor([[27., 37.],
[57., 67.]])
.. raw:: html
.. raw:: html
Daha sonra, çok sayıda sıfır içeren seyrek ağırlık matrisi ``W`` olarak
evrişim çekirdeği ``K``'yi yeniden yazıyoruz. Ağırlık matrisinin şekli
(:math:`4`, :math:`9`) olup sıfır olmayan elemanlar evrişim çekirdeği
``K``'den gelmektedir.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
def kernel2matrix(K):
k, W = np.zeros(5), np.zeros((4, 9))
k[:2], k[3:5] = K[0, :], K[1, :]
W[0, :5], W[1, 1:6], W[2, 3:8], W[3, 4:] = k, k, k, k
return W
W = kernel2matrix(K)
W
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
array([[1., 2., 0., 3., 4., 0., 0., 0., 0.],
[0., 1., 2., 0., 3., 4., 0., 0., 0.],
[0., 0., 0., 1., 2., 0., 3., 4., 0.],
[0., 0., 0., 0., 1., 2., 0., 3., 4.]])
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
def kernel2matrix(K):
k, W = torch.zeros(5), torch.zeros((4, 9))
k[:2], k[3:5] = K[0, :], K[1, :]
W[0, :5], W[1, 1:6], W[2, 3:8], W[3, 4:] = k, k, k, k
return W
W = kernel2matrix(K)
W
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
tensor([[1., 2., 0., 3., 4., 0., 0., 0., 0.],
[0., 1., 2., 0., 3., 4., 0., 0., 0.],
[0., 0., 0., 1., 2., 0., 3., 4., 0.],
[0., 0., 0., 0., 1., 2., 0., 3., 4.]])
.. raw:: html
.. raw:: html
9 uzunluğunda bir vektör elde etmek için girdi ``X``'i satır satır
bitiştirin. Daha sonra ``W`` ve vektörleştirilmiş ``X``'in matris
çarpımı, 4 uzunluğunda bir vektör verir. Yeniden şekillendirdikten
sonra, yukarıdaki orijinal evrişim işlemindeki aynı ``Y`` sonucunu elde
edebiliriz: Matris çarpımlarını kullanarak evrişimleri uyguladık.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
Y == np.dot(W, X.reshape(-1)).reshape(2, 2)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
array([[ True, True],
[ True, True]])
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
Y == torch.matmul(W, X.reshape(-1)).reshape(2, 2)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
tensor([[True, True],
[True, True]])
.. raw:: html
.. raw:: html
Aynı şekilde, matris çarpımlarını kullanarak devrik evrişimleri de
uygulayabiliriz. Aşağıdaki örnekte, yukarıdaki olağan evrişimin
:math:`2 \times 2` çıktısı ``Y``'yi devrik evrişime girdi olarak
alıyoruz. Bu işlemi matrisleri çarparak uygulamak için, ``W`` ağırlık
matrisini yeni şekli :math:`(9, 4)` ile devirmemiz yeterlidir.
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
Z = trans_conv(Y, K)
Z == np.dot(W.T, Y.reshape(-1)).reshape(3, 3)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
array([[ True, True, True],
[ True, True, True],
[ True, True, True]])
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
Z = trans_conv(Y, K)
Z == torch.matmul(W.T, Y.reshape(-1)).reshape(3, 3)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
tensor([[True, True, True],
[True, True, True],
[True, True, True]])
.. raw:: html
.. raw:: html
Matrisleri çarparak evrişimi uygulamayı düşünün. Bir girdi vektörü
:math:`\mathbf{x}` ve bir ağırlık matrisi :math:`\mathbf{W}` göz önüne
alındığında, evrişimin ileri yayma fonksiyonu, girdi ağırlık matrisi ile
çarpımından bir :math:`\mathbf{y}=\mathbf{W}\mathbf{x}` vektör çıktısı
verilerek uygulanabilir. Geriye yayma zincir kuralını ve
:math:`\nabla_{\mathbf{x}}\mathbf{y}=\mathbf{W}^\top`'i izlediğinden,
evrişimin geriye yayma fonksiyonu, girdisini devrik ağırlık matrisi
:math:`\mathbf{W}^\top` ile çarparak uygulanabilir. Bu nedenle, devrik
evrişim katmanı sadece ileri yayma fonksiyonunu ve evrişim tabakasının
geri yayma fonksiyonunu değiştirebilir: İleri yayma ve geri yayma
fonksiyonları sırasıyla :math:`\mathbf{W}^\top` ve :math:`\mathbf{W}`
ile girdi vektörünü çarpar.
Özet
----
- Çekirdek üzerinden girdi elemanlarını azaltan olağan evrişimin
aksine, devrik evrişim çekirdek üzerinden girdi öğelerini yayınlar ve
böylece girdiden daha büyük bir çıktı üretir.
- :math:`\mathsf{X}`'i :math:`\mathsf{Y}=f(\mathsf{X})` çıktısı almak
için bir :math:`f` evrişim katmanına beslersek ve çıktı kanallarının
sayısının :math:`\mathsf{X}` içindeki kanalların sayısı olması
dışında :math:`f` ile aynı hiper parametrelere sahip bir devrik
evrişim katmanı :math:`g` oluşturursak, :math:`g(Y)`
:math:`\mathsf{X}` ile aynı şekle sahip olacaktır.
- Matris çarpımlarını kullanarak evrişimleri gerçekleştirebiliriz.
Devrik evrişimli katman sadece ileri yayma fonksiyonunu ve evrişimli
katmanın geri yayma işlevini değiştirebilir.
Alıştırmalar
------------
1. :numref:`subsec-connection-to-mat-transposition` içinde, evrişim
girdisi ``X`` ve devrik evrişim çıktısı ``Z`` aynı şekle sahiptir.
Aynı değere sahipler mi? Neden?
2. Evrişimleri uygulamak için matris çarpımlarını kullanmak verimli
midir? Neden?
.. raw:: html
.. raw:: html
`Tartışmalar `__
.. raw:: html
.. raw:: html
`Tartışmalar `__
.. raw:: html
.. raw:: html