.. raw:: html .. _sec_fcn: Mạng Tích chập Đầy đủ ===================== .. raw:: html Ở phần trước, chúng ta đã thảo luận về phân vùng theo ngữ nghĩa bằng cách dự đoán hạng mục trên từng điểm ảnh. Mạng tích chập đầy đủ (*fully convolutional network* - FCN) :cite:`Long.Shelhamer.Darrell.2015` sử dụng mạng nơ-ron tích chập để biến đổi các điểm ảnh thành các hạng mục tương ứng của điểm ảnh. Khác với các mạng nơ-ron tích chập được giới thiệu trước đây, mạng FCN biến đổi chiều cao và chiều rộng của ánh xạ đặc trưng ở tầng trung gian về kích thước ảnh đầu vào thông qua các tầng tích chập chuyển vị, sao cho các dự đoán có sự tương xứng một–một với ảnh đầu vào theo không gian (chiều cao và chiều rộng). Với một vị trí trên chiều không gian, đầu ra theo chiều kênh sẽ là hạng mục được dự đoán tương ứng với điểm ảnh tại vị trí đó. .. raw:: html Đầu tiên, ta sẽ nhập gói thư viện và mô-đun cần thiết cho thí nghiệm này, sau đó sẽ đi đến giải thích về tầng tích chập chuyển vị. .. code:: python %matplotlib inline from d2l import mxnet as d2l from mxnet import gluon, image, init, np, npx from mxnet.gluon import nn npx.set_np() .. raw:: html Xây dựng Mô hình ---------------- .. raw:: html Ở đây, chúng tôi sẽ trình bày một thiết kế cơ bản nhất của mô hình tích chập đầy đủ. Như mô tả trong hình :numref:`fig_fcn`, đầu tiên mạng tích chập đầy đủ sử dụng mạng nơ-ron tích chập để trích xuất đặc trưng ảnh, sau đó biến đổi số lượng kênh thành số lượng hạng mục thông qua tầng tích chập :math:`1\times 1`, và cuối cùng biến đổi chiều cao và chiều rộng của ánh xạ đặc trưng bằng với kích thước của ảnh đầu vào bằng cách sử dụng tầng tích chập chuyển vị :numref:`sec_transposed_conv`. Đầu ra của mạng có cùng chiều cao và chiều rộng với ảnh gốc, đồng thời cũng có sự tương xứng một-một theo vị trí không gian. Kênh đầu ra cuối cùng chứa hạng mục dự đoán của từng điểm ảnh ở vị trí không gian tương ứng. .. raw:: html .. _fig_fcn: .. figure:: ../img/fcn.svg Mạng tích chập đầy đủ. .. raw:: html Dưới đây, ta sử dụng mô hình ResNet-18 đã được tiền huấn luyện trên ImageNet để trích xuất đặc trưng và lưu thực thể mô hình là ``pretrained_net``. Như ta có thể thấy, hai tầng cuối của mô hình nằm trong biến thành viên ``features`` là tầng gộp cực đại toàn cục ``GlobalAvgPool2D`` và tầng trải phẳng ``Flatten``. Mô-đun ``output`` chứa tầng kết nối đầy đủ được sử dụng cho đầu ra. Các tầng này không bắt buộc phải có trong mạng tích chập đầy đủ. .. code:: python pretrained_net = gluon.model_zoo.vision.resnet18_v2(pretrained=True) pretrained_net.features[-4:], pretrained_net.output .. parsed-literal:: :class: output Downloading /home/tiepvu/.mxnet/models/resnet18_v2-a81db45f.zip586481c2-2c77-4268-b635-9630c6ed3616 from https://apache-mxnet.s3-accelerate.dualstack.amazonaws.com/gluon/models/resnet18_v2-a81db45f.zip... .. parsed-literal:: :class: output (HybridSequential( (0): BatchNorm(axis=1, eps=1e-05, momentum=0.9, fix_gamma=False, use_global_stats=False, in_channels=512) (1): Activation(relu) (2): GlobalAvgPool2D(size=(1, 1), stride=(1, 1), padding=(0, 0), ceil_mode=True, global_pool=True, pool_type=avg, layout=NCHW) (3): Flatten ), Dense(512 -> 1000, linear)) .. raw:: html Tiếp theo, ta khởi tạo một thực thể mạng tích chập đầy đủ ``net``. Thực thể này sao chép tất cả các tầng nơ-ron ngoại trừ hai tầng cuối cùng của biến thành viên ``features`` trong ``pretrained_net`` và các tham số mô hình thu được từ bước tiền huấn luyện. .. code:: python net = nn.HybridSequential() for layer in pretrained_net.features[:-2]: net.add(layer) .. raw:: html Với đầu vào có chiều cao và chiều rộng là 320 và 480, bước tính toán lượt truyền xuôi của ``net`` sẽ giảm chiều cao và chiều rộng của đầu vào thành :math:`1/32` kích thước ban đầu, tức 10 và 15. .. code:: python X = np.random.uniform(size=(1, 3, 320, 480)) net(X).shape .. parsed-literal:: :class: output (1, 512, 10, 15) .. raw:: html Tiếp đến, ta biến đổi số lượng kênh đầu ra bằng với số lượng hạng mục trong tập dữ liệu Pascal VOC2012 (21) thông qua tầng tích chập :math:`1\times 1`. Cuối cùng, ta cần phóng đại chiều cao và chiều rộng của ánh xạ đặc trưng lên gấp 32 lần để bằng với chiều cao và chiều rộng của ảnh đầu vào. Hãy xem lại phương pháp tính kích thước đầu ra của tầng tích chập được mô tả trong :numref:`sec_padding`. Vì :math:`(320-64+16\times2+32)/32=10` và :math:`(480-64+16\times2+32)/32=15`, ta sẽ xây dựng một tầng tích chập chuyển vị với sải bước 32, đặt chiều dài và chiều rộng của hạt nhân tích chập bằng 64 và kích thước đệm bằng 16. Không khó để nhận ra rằng nếu sải bước bằng :math:`s`, kích thước đệm là :math:`s/2` (giả sử :math:`s/2` là số nguyên) và hạt nhân tích chập có chiều dài và chiều rộng bằng :math:`2s`, thì hạt nhân tích chập chuyển vị sẽ phóng đại chiều cao và chiều rộng của đầu vào lên :math:`s` lần. .. code:: python num_classes = 21 net.add(nn.Conv2D(num_classes, kernel_size=1), nn.Conv2DTranspose( num_classes, kernel_size=64, padding=16, strides=32)) .. raw:: html Khởi tạo Tầng Tích chập Chuyển vị --------------------------------- .. raw:: html Chúng ta đã biết tầng tích chập chuyển vị có thể phóng đại ánh xạ đặc trưng. Trong xử lý ảnh, đôi khi ta cần phóng đại ảnh hay còn được biết đến là phép tăng mẫu. Có rất nhiều phương pháp tăng mẫu, và một phương pháp phổ biến là nội suy song tuyến tính (*bilinear interpolation*). Nói một cách đơn giản, để lấy điểm ảnh của ảnh đầu ra tại tọa độ :math:`(x, y)`, đầu tiên tọa độ này sẽ được ánh xạ tới tọa độ :math:`(x', y')` trong ảnh đầu vào. Điều này có thể được thực hiện dựa trên tỷ lệ kích thước của ba đầu vào tới kích thước của đầu ra. Giá trị được ánh xạ :math:`x'` và :math:`y'` thường là các số thực. Sau đó, ta tìm bốn điểm ảnh gần tọa độ :math:`(x', y')` nhất trên ảnh đầu vào. Cuối cùng, các điểm ảnh tại tọa độ :math:`(x, y)` của đầu ra sẽ được tính toán dựa trên bốn điểm ảnh của ảnh đầu vào và khoảng cách tương đối của chúng tới :math:`(x', y')`. Phép tăng mẫu bằng nội suy song tuyến tính có thể được lập trình bằng tầng tích chập chuyển vị với hạt nhân tích chập được xây dựng bằng hàm ``bilinear_kernel`` dưới đây. Do có nhiều giới hạn, chúng tôi sẽ chỉ cung cấp cách lập trình hàm ``bilinear_kernel`` và sẽ không thảo luận về nguyên tắc của thuật toán. .. code:: python def bilinear_kernel(in_channels, out_channels, kernel_size): factor = (kernel_size + 1) // 2 if kernel_size % 2 == 1: center = factor - 1 else: center = factor - 0.5 og = (np.arange(kernel_size).reshape(-1, 1), np.arange(kernel_size).reshape(1, -1)) filt = (1 - np.abs(og[0] - center) / factor) * \ (1 - np.abs(og[1] - center) / factor) weight = np.zeros((in_channels, out_channels, kernel_size, kernel_size)) weight[range(in_channels), range(out_channels), :, :] = filt return np.array(weight) .. raw:: html Bây giờ, ta sẽ thí nghiệm với phép tăng mẫu sử dụng phép nội suy song tuyến tính được thực hiện bằng tầng tích chập chuyển vị. Ta sẽ xây dựng tầng tích chập chuyển vị để phóng đại chiều cao và chiều rộng của đầu vào lên hai lần và khởi tạo hạt nhân tích chập với hàm ``bilinear_kernel``. .. code:: python conv_trans = nn.Conv2DTranspose(3, kernel_size=4, padding=1, strides=2) conv_trans.initialize(init.Constant(bilinear_kernel(3, 3, 4))) .. raw:: html Ta sẽ đọc ảnh ``X`` và lưu kết quả của phép tăng mẫu là ``Y``. Để in ảnh, ta cần điều chỉnh vị trí của chiều kênh. .. code:: python img = image.imread('../img/catdog.jpg') X = np.expand_dims(img.astype('float32').transpose(2, 0, 1), axis=0) / 255 Y = conv_trans(X) out_img = Y[0].transpose(1, 2, 0) .. raw:: html Như ta có thể thấy, tầng tích chập chuyển vị phóng đại cả chiều cao và chiều rộng của ảnh lên hai lần. Điều đáng nói là bên cạnh sự khác biệt trong tỷ lệ tọa độ, ảnh được phóng đại bởi phép nội suy song tuyến tính và ảnh ban đầu được in :numref:`sec_bbox` nhìn giống nhau. .. code:: python d2l.set_figsize() print('input image shape:', img.shape) d2l.plt.imshow(img.asnumpy()); print('output image shape:', out_img.shape) d2l.plt.imshow(out_img.asnumpy()); .. parsed-literal:: :class: output input image shape: (561, 728, 3) output image shape: (1122, 1456, 3) .. figure:: output_fcn_vn_e51ea4_17_1.svg .. raw:: html Trong mạng tích chập đầy đủ, ta khởi tạo tầng tích chập chuyển vị để thực hiện phép tăng mẫu nội suy song tuyến tính. Với tầng tích chập :math:`1\times 1`, ta sử dụng phương pháp khởi tạo ngẫu nhiên Xavier. .. code:: python W = bilinear_kernel(num_classes, num_classes, 64) net[-1].initialize(init.Constant(W)) net[-2].initialize(init=init.Xavier()) .. raw:: html Đọc Dữ liệu ----------- .. raw:: html Ta đọc dữ liệu bằng phương thức được mô tả ở phần trước. Ở đây, ta định rõ kích thước của ảnh đầu ra sau khi cắt ngẫu nhiên là :math:`320\times 480`, để cả chiều cao và chiều rộng chia hết cho 32. .. code:: python batch_size, crop_size = 32, (320, 480) train_iter, test_iter = d2l.load_data_voc(batch_size, crop_size) .. parsed-literal:: :class: output Downloading ../data/VOCtrainval_11-May-2012.tar from http://d2l-data.s3-accelerate.amazonaws.com/VOCtrainval_11-May-2012.tar... read 1114 examples read 1078 examples .. raw:: html Huấn luyện ---------- .. raw:: html Bây giờ ta có thể bắt đầu huấn luyện mô hình. Hàm mất mát và hàm tính độ chính xác được sử dụng ở đây không quá khác biệt so với các hàm được sử dụng trong bài toán phân loại ảnh. Vì ta sử dụng kênh của tầng tích chập chuyển vị để dự đoán hạng mục cho điểm ảnh, tham số ``axis=1`` (chiều kênh) được định rõ trong ``SoftmaxCrossEntropyLoss``. Thêm vào đó, dựa trên hạng mục của từng điểm ảnh có được dự đoán đúng hay không mà mô hình sẽ tính toán độ chính xác. .. code:: python num_epochs, lr, wd, devices = 5, 0.1, 1e-3, d2l.try_all_gpus() loss = gluon.loss.SoftmaxCrossEntropyLoss(axis=1) net.collect_params().reset_ctx(devices) trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': lr, 'wd': wd}) d2l.train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs, devices) .. parsed-literal:: :class: output loss 0.281, train acc 0.905, test acc 0.855 146.2 examples/sec on [gpu(0)] .. figure:: output_fcn_vn_e51ea4_23_1.svg .. raw:: html Dự đoán ------- .. raw:: html Trong quá trình dự đoán, ta cần chuẩn tắc hóa ảnh đầu vào theo từng kênh và chuyển đổi chúng thành tensor 4 chiều như yêu cầu của mạng nơ-ron tích chập. .. code:: python def predict(img): X = test_iter._dataset.normalize_image(img) X = np.expand_dims(X.transpose(2, 0, 1), axis=0) pred = net(X.as_in_ctx(devices[0])).argmax(axis=1) return pred.reshape(pred.shape[1], pred.shape[2]) .. raw:: html Để biểu diễn trực quan các hạng mục được dự đoán cho từng điểm ảnh, ta ánh xạ các hạng mục dự đoán về nhãn màu trong tập dữ liệu. .. code:: python def label2image(pred): colormap = np.array(d2l.VOC_COLORMAP, ctx=devices[0], dtype='uint8') X = pred.astype('int32') return colormap[X, :] .. raw:: html Kích thước và hình dạng của ảnh trong tập kiểm tra không cố định. Vì mô hình sử dụng tầng tích chập chuyển vị với sải bước bằng 32, nên khi chiều cao và chiều rộng của ảnh đầu vào không chia hết cho 32 thì chiều cao và chiều rộng của đầu ra tầng tích chập chuyển vị sẽ chênh lệch so với kích thước của ảnh đầu vào. Để giải quyết vấn đề này, ta có thể cắt nhiều vùng hình chữ nhật trong ảnh với chiều cao và chiều rộng chia hết cho 32, sau đó thực hiện lượt truyền xuôi trên các điểm ảnh của những vùng này. Khi kết hợp các kết quả lại, các vùng này sẽ khôi phục lại toàn bộ ảnh đầu vào. Khi một điểm ảnh nằm trong nhiều vùng khác nhau, trung bình đầu ra của tầng tích chập chuyển vị sau lan truyền xuôi của các vùng khác nhau có thể được sử dụng để làm đầu vào cho phép toán softmax nhằm dự đoán hạng mục. .. raw:: html Để đơn giản, ta chỉ đọc một vài ảnh kiểm tra có kích thước lớn và cắt các vùng với kích thước :math:`320\times480` từ góc trái trên cùng của ảnh, và chỉ sử dụng vùng này để dự đoán. Với ảnh đầu vào, đầu tiên ta in ra vùng được cắt, sau đó in ra kết quả dự đoán, và cuối cùng in ra hạng mục nhãn gốc. .. code:: python voc_dir = d2l.download_extract('voc2012', 'VOCdevkit/VOC2012') test_images, test_labels = d2l.read_voc_images(voc_dir, False) n, imgs = 4, [] for i in range(n): crop_rect = (0, 0, 480, 320) X = image.fixed_crop(test_images[i], *crop_rect) pred = label2image(predict(X)) imgs += [X, pred, image.fixed_crop(test_labels[i], *crop_rect)] d2l.show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n, scale=2); .. figure:: output_fcn_vn_e51ea4_29_0.svg Tóm tắt ------- .. raw:: html - Đầu tiên, mạng tích chập đầy đủ sử dụng một mạng nơ-ron tích chập để trích xuất đặc trưng ảnh, sau đó biến đổi số lượng kênh thành số lượng các hạng mục bằng tầng tích chập :math:`1\times 1`, và cuối cùng biến đổi chiều cao và chiều rộng của ánh xạ đặc trưng thành kích thước ban đầu của ảnh bằng cách sử dụng tầng tích chập chuyển vị để cho ra hạng mục của từng điểm ảnh. - Trong mạng tích chập đầy đủ, ta khởi tạo tầng tích chập chuyển vị cho phép tăng mẫu nội suy song tuyến tính. Bài tập ------- .. raw:: html 1. Nếu ta sử dụng Xavier để khởi tạo ngẫu nhiên tầng tích chập chuyển vị, kết quả thay đổi ra sao? 2. Bạn có thể cải thiện độ chính xác của mô hình bằng cách điều chỉnh các siêu tham số hay không? 3. Hãy dự đoán các hạng mục của tất cả các điểm ảnh trong ảnh kiểm tra. 4. Trong bài báo về mạng tích chập đầy đủ [1], đầu ra của một số tầng trung gian của mạng nơ-ron tích chập cũng được sử dụng. Hãy thử lập trình lại ý tưởng này. Thảo luận --------- - `Tiếng Anh - MXNet `__ - `Tiếng Việt `__ 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 - Nguyễn Văn Quang - Lê Khắc Hồng Phúc - Phạm Minh Đức - Phạm Hồng Vinh - Nguyễn Lê Quang Nhật - Nguyễn Văn Cường