13.10. Tích chập Chuyển vị

Các tầng trong mạng nơ-ron tích chập, bao gồm tầng tích chập (Section 6.2) và tầng gộp (Section 6.5), thường giảm chiều rộng và chiều cao của đầu vào, hoặc giữ nguyên chúng. Tuy nhiên, các ứng dụng như phân vùng theo ngữ nghĩa (Section 13.9) và mạng đối sinh (GAN - Section 17.2), yêu cầu phải dự đoán các giá trị cho mỗi pixel vì thế cần tăng chiều rộng và chiều cao của đầu vào. Tích chập chuyển vị, cũng có tên là tích chập sải bước phân số (fractionally-strided convolution) [Dumoulin & Visin, 2016] hay phân tách tích chập (deconvolution) [Long et al., 2015], phục vụ cho mục đích này.

from mxnet import np, npx, init
from mxnet.gluon import nn
from d2l import mxnet as d2l

npx.set_np()

13.10.1. Tích chập Chuyển vị 2D Cơ bản

Xét một trường hợp cơ bản với số kênh đầu vào và đầu ra là 1, với đệm 0 và sải bước 1. Fig. 13.10.1 mô tả cách tích chập chuyển vị với một hạt nhân \(2\times 2\) được tính toán trên một ma trận đầu vào kích thước \(2\times 2\).

../_images/trans_conv.svg

Fig. 13.10.1 Tầng tích chập chuyển vị với hạt nhân \(2\times 2\).

Ta có thể lập trình phép tính này với ma trận hạt nhân \(K\) và ma trận đầu vào \(X\).

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

Nhớ lại rằng kết quả tính tích chập là Y[i, j] = (X[i: i + h, j: j + w] * K).sum() (đọc lại corr2d trong Section 6.2), tức tổng hợp các giá trị đầu vào thông qua hạt nhân. Trong khi tích chập chuyển vị lan truyền từng giá trị đầu vào khắp hạt nhân, tạo thành đầu ra có kích thước lớn hơn.

Kiểm chứng các kết quả trong Fig. 13.10.1.

X = np.array([[0, 1], [2, 3]])
K = np.array([[0, 1], [2, 3]])
trans_conv(X, K)
array([[ 0.,  0.,  1.],
       [ 0.,  4.,  6.],
       [ 4., 12.,  9.]])

Hoặc ta có thể sử dụng nn.Conv2DTranspose để thu được kết quả tương tự. Vì đang sử dụng nn.Conv2D, cả đầu vào và hạt nhân phải là tensor 4 chiều.

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)
array([[[[ 0.,  0.,  1.],
         [ 0.,  4.,  6.],
         [ 4., 12.,  9.]]]])

13.10.2. Đệm, Sải bước và Kênh

Khi tính tích chập ta áp dụng đệm lên đầu vào, nhưng với tích chập chuyển vị, chúng được áp dụng vào đầu ra. Ví dụ, với đệm \(1\times 1\), đầu tiên ta tính toán đầu ra như bình thường, sau đó bỏ đi dòng và cột đầu tiên / cuối cùng.

tconv = nn.Conv2DTranspose(1, kernel_size=2, padding=1)
tconv.initialize(init.Constant(K))
tconv(X)
array([[[[4.]]]])

Tương tự, sải bước cũng được áp dụng vào các đầu ra.

tconv = nn.Conv2DTranspose(1, kernel_size=2, strides=2)
tconv.initialize(init.Constant(K))
tconv(X)
array([[[[0., 0., 0., 1.],
         [0., 0., 2., 3.],
         [0., 2., 0., 3.],
         [4., 6., 6., 9.]]]])

Phần mở rộng đa kênh của tích chập chuyển vị cũng giống như tích chập. Khi đầu vào có \(c_i\) kênh, tích chập chuyển vị gán một ma trận hạt nhân có kích thước \(k_h\times k_w\) vào mỗi kênh đầu vào. Nếu số kênh đầu ra là \(c_o\), thì hạt nhân có kích thước \(c_i\times k_h\times k_w\) cho mỗi kênh đầu ra.

Do đó, nếu ta đưa \(X\) qua một tầng tích chập \(f\) để tính \(Y=f(X)\) và tạo một tầng tích chập chuyển vị \(g\) với cùng một siêu tham số như \(f\) ngoại trừ kênh đầu ra được đặt thành kích thước kênh \(X\), thì \(g(Y)\) sẽ có cùng kích thước với \(X\). Ta hãy xác minh phát biểu này.

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
True

13.10.3. Sự Tương đồng với Chuyển vị Ma trận

Tên của tích chập chuyển vị xuất phát từ phép chuyển vị ma trận. Thật vậy, phép tích chập có thể tính thông qua phép nhân ma trận. Trong ví dụ dưới đây, ta định nghĩa một biến đầu vào \(X\) \(3\times 3\) với một hạt nhân \(K\) \(2\times 2\), rồi dùng corr2d để tính tích chập.

X = np.arange(9).reshape(3, 3)
K = np.array([[0, 1], [2, 3]])
Y = d2l.corr2d(X, K)
Y
array([[19., 25.],
       [37., 43.]])

Kế tiếp, ta viết lại hạt nhân chập \(K\) dưới dạng ma trận \(W\). Kích thước của nó sẽ là \((4, 9)\), hàng thứ \(i\) biểu diễn việc sử dụng hạt nhân lên đầu vào để sinh ra phần tử đầu ra thứ \(i\).

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
array([[0., 1., 0., 2., 3., 0., 0., 0., 0.],
       [0., 0., 1., 0., 2., 3., 0., 0., 0.],
       [0., 0., 0., 0., 1., 0., 2., 3., 0.],
       [0., 0., 0., 0., 0., 1., 0., 2., 3.]])

Rồi toán tử chập có thể được thực hiện nhờ phép nhân ma trận với việc chỉnh lại kích thước phù hợp.

Y == np.dot(W, X.reshape(-1)).reshape(2, 2)
array([[ True,  True],
       [ True,  True]])

Ta có thể thực hiện phép chập chuyển vị giống như phép nhân ma trận bằng cách sử dụng lại kernel2matrix. Để sử dụng lại ma trận \(W\) đã tạo ra, ta xây dựng một đầu vào \(2\times 2\), nên ma trận trọng số \(W^\top\) tương ứng sẽ có kích thước \((9, 4)\). Ta hãy cùng kiểm tra lại kết quả.

X = np.array([[0, 1], [2, 3]])
Y = trans_conv(X, K)
Y == np.dot(W.T, X.reshape(-1)).reshape(3, 3)
array([[ True,  True,  True],
       [ True,  True,  True],
       [ True,  True,  True]])

13.10.4. Tóm tắt

  • So với phương pháp tích chập nén đầu vào thông qua hạt nhân, phép tích chập chuyển vị làm tăng số chiều của đầu vào.
  • Nếu một tầng tích chập nén chiều rộng và chiều cao của đầu vào lần lượt đi \(n_w\)\(n_h\) lần, thì một tầng tích chập chuyển vị có cùng kích thước hạt nhân, đệm và sải bước sẽ tăng chiều dài và chiều cao của đầu vào lần lượt lên \(n_w\)\(n_h\) lần.
  • Ta có thể lập trình thao tác tích chập bằng phép nhân ma trận, và phép tích chập chuyển vị tương ứng cũng có thể thực hiện bằng phép nhân ma trận chuyển vị.

13.10.5. Bài tập

Việc sử dụng phép nhân ma trận để lập trình cho thao tác tích chập liệu có thực sự hiệu quả? Tại sao?

13.10.6. Thảo luận

13.10.7. Những người thực hiện

Bản dịch trong trang này được thực hiện bởi:

  • Đoàn Võ Duy Thanh
  • Trần Yến Thy
  • Nguyễn Văn Cường
  • Nguyễn Mai Hoàng Long
  • Đỗ Trường Giang
  • Lê Khắc Hồng Phúc
  • Phạm Hồng Vinh