.. _sec_fcn: Mạng kết nối hoàn toàn ====================== Như đã thảo luận trong :numref:`sec_semantic_segmentation`, phân đoạn ngữ nghĩa phân loại hình ảnh ở mức pixel. Một mạng phức tạp hoàn toàn (FCN) sử dụng mạng thần kinh phức tạp để chuyển đổi pixel hình ảnh thành các lớp pixel :cite:`Long.Shelhamer.Darrell.2015`. Không giống như các CNN mà chúng ta gặp phải trước đó để phân loại hình ảnh hoặc phát hiện đối tượng, một mạng phức tạp hoàn toàn biến đổi chiều cao và chiều rộng của bản đồ tính năng trung gian trở lại hình ảnh đầu vào: điều này đạt được bằng lớp biến đổi được giới thiệu trong :numref:`sec_transposed_conv`. Do đó, đầu ra phân loại và hình ảnh đầu vào có sự tương ứng một-một ở mức pixel: kích thước kênh ở bất kỳ điểm ảnh đầu ra nào giữ kết quả phân loại cho pixel đầu vào ở cùng một vị trí không gian. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python %matplotlib inline from mxnet import gluon, image, init, np, npx from mxnet.gluon import nn from d2l import mxnet as d2l npx.set_np() .. raw:: html
.. raw:: html
.. code:: python %matplotlib inline import torch import torchvision from torch import nn from torch.nn import functional as F from d2l import torch as d2l .. raw:: html
.. raw:: html
Mô hình ------- Ở đây chúng tôi mô tả thiết kế cơ bản của mô hình mạng phức tạp hoàn toàn. Như thể hiện trong :numref:`fig_fcn`, mô hình này đầu tiên sử dụng CNN để trích xuất các tính năng hình ảnh, sau đó chuyển đổi số lượng kênh thành số lượng lớp thông qua lớp ghép :math:`1\times 1` và cuối cùng biến đổi chiều cao và chiều rộng của bản đồ tính năng sang hình ảnh đầu vào thông qua sự phức tạp chuyển tiếp được giới thiệu trong :numref:`sec_transposed_conv`. Do đó, đầu ra mô hình có cùng chiều cao và chiều rộng với hình ảnh đầu vào, trong đó kênh đầu ra chứa các lớp dự đoán cho pixel đầu vào ở cùng một vị trí không gian. .. _fig_fcn: .. figure:: ../img/fcn.svg Fully convolutional network. Dưới đây, chúng ta sử dụng mô hình ResNet-18 được đào tạo trước trên bộ dữ liệu ImageNet để trích xuất các tính năng hình ảnh và biểu thị phiên bản model là ``pretrained_net``. Vài lớp cuối cùng của mô hình này bao gồm một lớp tổng hợp trung bình toàn cầu và một lớp kết nối hoàn toàn: chúng không cần thiết trong mạng phức tạp hoàn toàn. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python pretrained_net = gluon.model_zoo.vision.resnet18_v2(pretrained=True) pretrained_net.features[-3:], pretrained_net.output .. parsed-literal:: :class: output (HybridSequential( (0): Activation(relu) (1): GlobalAvgPool2D(size=(1, 1), stride=(1, 1), padding=(0, 0), ceil_mode=True, global_pool=True, pool_type=avg, layout=NCHW) (2): Flatten ), Dense(512 -> 1000, linear)) .. raw:: html
.. raw:: html
.. code:: python pretrained_net = torchvision.models.resnet18(pretrained=True) list(pretrained_net.children())[-3:] .. parsed-literal:: :class: output [Sequential( (0): BasicBlock( (conv1): Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False) (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (relu): ReLU(inplace=True) (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (downsample): Sequential( (0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False) (1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) ) ) (1): BasicBlock( (conv1): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (relu): ReLU(inplace=True) (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) ) ), AdaptiveAvgPool2d(output_size=(1, 1)), Linear(in_features=512, out_features=1000, bias=True)] .. raw:: html
.. raw:: html
Tiếp theo, chúng ta tạo phiên bản mạng hoàn toàn phức tạp ``net``. Nó sao chép tất cả các lớp được đào tạo trước trong ResNet-18 ngoại trừ lớp tổng hợp trung bình toàn cầu cuối cùng và lớp kết nối hoàn toàn gần đầu ra nhất. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python net = nn.HybridSequential() for layer in pretrained_net.features[:-2]: net.add(layer) .. raw:: html
.. raw:: html
.. code:: python net = nn.Sequential(*list(pretrained_net.children())[:-2]) .. raw:: html
.. raw:: html
Với một đầu vào với chiều cao và chiều rộng 320 và 480 tương ứng, sự lan truyền về phía trước của ``net`` làm giảm chiều cao và chiều rộng đầu vào xuống 1/32 của bản gốc, cụ thể là 10 và 15. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python X = np.random.uniform(size=(1, 3, 320, 480)) net(X).shape .. parsed-literal:: :class: output (1, 512, 10, 15) .. raw:: html
.. raw:: html
.. code:: python X = torch.rand(size=(1, 3, 320, 480)) net(X).shape .. parsed-literal:: :class: output torch.Size([1, 512, 10, 15]) .. raw:: html
.. raw:: html
Tiếp theo, chúng ta sử dụng một lớp ghép :math:`1\times 1` để chuyển đổi số kênh đầu ra thành số lớp (21) của tập dữ liệu Pascal VOC2012. Cuối cùng, chúng ta cần tăng chiều cao và chiều rộng của bản đồ tính năng lên 32 lần để thay đổi chúng trở lại chiều cao và chiều rộng của hình ảnh đầu vào. Nhớ lại cách tính hình dạng đầu ra của một lớp phức tạp trong :numref:`sec_padding`. Kể từ :math:`(320-64+16\times2+32)/32=10` và :math:`(480-64+16\times2+32)/32=15`, chúng tôi xây dựng một lớp ghép chuyển đổi với sải chân :math:`32`, thiết lập chiều cao và chiều rộng của hạt nhân là :math:`64`, đệm là :math:`16`. Nói chung, chúng ta có thể thấy rằng đối với sải chân :math:`s`, đệm :math:`s/2` (giả sử :math:`s/2` là một số nguyên) và chiều cao và chiều rộng của hạt nhân :math:`2s`, sự phức tạp chuyển tiếp sẽ làm tăng chiều cao và chiều rộng của đầu vào lên :math:`s` lần. .. raw:: html
mxnetpytorch
.. raw:: html
.. 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
.. raw:: html
.. code:: python num_classes = 21 net.add_module('final_conv', nn.Conv2d(512, num_classes, kernel_size=1)) net.add_module('transpose_conv', nn.ConvTranspose2d(num_classes, num_classes, kernel_size=64, padding=16, stride=32)) .. raw:: html
.. raw:: html
Initializing Transposed Convolutional Layers -------------------------------------------- Chúng ta đã biết rằng các lớp phức tạp chuyển tiếp có thể làm tăng chiều cao và chiều rộng của bản đồ tính năng. Trong xử lý hình ảnh, chúng ta có thể cần phải mở rộng một hình ảnh, tức là, \* upsampling\ *. *\ Nội suy song sinh\* là một trong những kỹ thuật lấy mẫu thường được sử dụng. Nó cũng thường được sử dụng để khởi tạo các lớp phức tạp chuyển tiếp. Để giải thích nội suy song tuyến, hãy nói rằng cho một hình ảnh đầu vào, chúng tôi muốn tính toán từng pixel của hình ảnh đầu ra được lấy mẫu lên. Để tính điểm ảnh của hình ảnh đầu ra ở tọa độ :math:`(x, y)`, bản đồ đầu tiên :math:`(x, y)` phối hợp :math:`(x', y')` trên hình ảnh đầu vào, ví dụ, theo tỷ lệ kích thước đầu vào với kích thước đầu ra. Lưu ý rằng :math:`x′` and :math:`y′` ánh xạ là số thực. Sau đó, tìm bốn pixel gần nhất với tọa độ :math:`(x', y')` trên hình ảnh đầu vào. Cuối cùng, pixel của hình ảnh đầu ra ở tọa độ :math:`(x, y)` được tính dựa trên bốn pixel gần nhất trên ảnh đầu vào và khoảng cách tương đối của chúng từ :math:`(x', y')`. Upsampling của nội suy bilinear có thể được thực hiện bởi lớp ghép chuyển đổi với hạt nhân được xây dựng bởi hàm ``bilinear_kernel`` sau đây. Do hạn chế về không gian, chúng tôi chỉ cung cấp việc triển khai hàm ``bilinear_kernel`` bên dưới mà không cần thảo luận về thiết kế thuật toán của nó. .. raw:: html
mxnetpytorch
.. raw:: html
.. 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
.. raw:: html
.. 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 = (torch.arange(kernel_size).reshape(-1, 1), torch.arange(kernel_size).reshape(1, -1)) filt = (1 - torch.abs(og[0] - center) / factor) * \ (1 - torch.abs(og[1] - center) / factor) weight = torch.zeros((in_channels, out_channels, kernel_size, kernel_size)) weight[range(in_channels), range(out_channels), :, :] = filt return weight .. raw:: html
.. raw:: html
Hãy để chúng tôi thử nghiệm với upsampling of bilinear interpolation được thực hiện bởi một lớp ghép chuyển tiếp. Chúng tôi xây dựng một lớp tích hợp chuyển tiếp tăng gấp đôi chiều cao và trọng lượng, và khởi tạo hạt nhân của nó với hàm ``bilinear_kernel``. .. raw:: html
mxnetpytorch
.. raw:: html
.. 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
.. raw:: html
.. code:: python conv_trans = nn.ConvTranspose2d(3, 3, kernel_size=4, padding=1, stride=2, bias=False) conv_trans.weight.data.copy_(bilinear_kernel(3, 3, 4)); .. raw:: html
.. raw:: html
Đọc hình ảnh ``X`` và gán đầu ra lấy mẫu lên ``Y``. Để in hình ảnh, chúng ta cần điều chỉnh vị trí của kích thước kênh. .. raw:: html
mxnetpytorch
.. raw:: html
.. 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
.. raw:: html
.. code:: python img = torchvision.transforms.ToTensor()(d2l.Image.open('../img/catdog.jpg')) X = img.unsqueeze(0) Y = conv_trans(X) out_img = Y[0].permute(1, 2, 0).detach() .. raw:: html
.. raw:: html
Như chúng ta có thể thấy, lớp biến thể chuyển đổi làm tăng cả chiều cao và chiều rộng của hình ảnh bằng một yếu tố là hai. Ngoại trừ các thang đo khác nhau về tọa độ, hình ảnh được thu nhỏ bằng nội suy song tuyến và hình ảnh gốc được in trong :numref:`sec_bbox` trông giống nhau. .. raw:: html
mxnetpytorch
.. raw:: html
.. 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_ce3435_75_1.svg .. raw:: html
.. raw:: html
.. code:: python d2l.set_figsize() print('input image shape:', img.permute(1, 2, 0).shape) d2l.plt.imshow(img.permute(1, 2, 0)); print('output image shape:', out_img.shape) d2l.plt.imshow(out_img); .. parsed-literal:: :class: output input image shape: torch.Size([561, 728, 3]) output image shape: torch.Size([1122, 1456, 3]) .. figure:: output_fcn_ce3435_78_1.svg .. raw:: html
.. raw:: html
Trong một mạng phức tạp hoàn toàn, chúng ta khởi tạo lớp ghép chuyển tiếp với upsampling của nội suy song sinh. Đối với lớp ghép :math:`1\times 1`, chúng tôi sử dụng khởi tạo Xavier. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python W = bilinear_kernel(num_classes, num_classes, 64) net[-1].initialize(init.Constant(W)) net[-2].initialize(init=init.Xavier()) .. raw:: html
.. raw:: html
.. code:: python W = bilinear_kernel(num_classes, num_classes, 64) net.transpose_conv.weight.data.copy_(W); .. raw:: html
.. raw:: html
Đọc dữ liệu ----------- Chúng tôi đọc tập dữ liệu phân đoạn ngữ nghĩa như được giới thiệu trong :numref:`sec_semantic_segmentation`. Hình dạng hình ảnh đầu ra của cắt xén ngẫu nhiên được chỉ định là :math:`320\times 480`: cả chiều cao và chiều rộng đều chia hết cho :math:`32`. .. raw:: html
mxnetpytorch
.. raw:: html
.. 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
.. raw:: html
.. 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 read 1114 examples read 1078 examples .. raw:: html
.. raw:: html
Đào tạo ------- Bây giờ chúng ta có thể đào tạo mạng lưới phức tạp được xây dựng hoàn toàn của chúng tôi. Chức năng mất mát và tính chính xác ở đây về cơ bản không khác với các chức năng trong phân loại hình ảnh của các chương trước đó. Bởi vì chúng ta sử dụng kênh đầu ra của lớp tích hợp chuyển tiếp để dự đoán lớp cho mỗi pixel, kích thước kênh được chỉ định trong phép tính tổn thất. Ngoài ra, độ chính xác được tính dựa trên tính chính xác của lớp dự đoán cho tất cả các pixel. .. raw:: html
mxnetpytorch
.. raw:: html
.. 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.324, train acc 0.893, test acc 0.858 162.9 examples/sec on [gpu(0), gpu(1)] .. figure:: output_fcn_ce3435_102_1.svg .. raw:: html
.. raw:: html
.. code:: python def loss(inputs, targets): return F.cross_entropy(inputs, targets, reduction='none').mean(1).mean(1) num_epochs, lr, wd, devices = 5, 0.001, 1e-3, d2l.try_all_gpus() trainer = torch.optim.SGD(net.parameters(), lr=lr, weight_decay=wd) d2l.train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs, devices) .. parsed-literal:: :class: output loss 0.446, train acc 0.863, test acc 0.854 252.5 examples/sec on [device(type='cuda', index=0), device(type='cuda', index=1)] .. figure:: output_fcn_ce3435_105_1.svg .. raw:: html
.. raw:: html
Prediction ---------- Khi dự đoán, chúng ta cần chuẩn hóa hình ảnh đầu vào trong mỗi kênh và chuyển đổi hình ảnh thành định dạng đầu vào bốn chiều theo yêu cầu của CNN. .. raw:: html
mxnetpytorch
.. raw:: html
.. 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
.. raw:: html
.. code:: python def predict(img): X = test_iter.dataset.normalize_image(img).unsqueeze(0) pred = net(X.to(devices[0])).argmax(dim=1) return pred.reshape(pred.shape[1], pred.shape[2]) .. raw:: html
.. raw:: html
Để visualize the predicted class của mỗi pixel, chúng ta ánh xạ lớp dự đoán trở lại màu nhãn của nó trong tập dữ liệu. .. raw:: html
mxnetpytorch
.. raw:: html
.. 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
.. raw:: html
.. code:: python def label2image(pred): colormap = torch.tensor(d2l.VOC_COLORMAP, device=devices[0]) X = pred.long() return colormap[X, :] .. raw:: html
.. raw:: html
Hình ảnh trong tập dữ liệu thử nghiệm khác nhau về kích thước và hình dạng. Kể từ khi mô hình sử dụng một lớp ghép chuyển tiếp với sải chân là 32, khi chiều cao hoặc chiều rộng của một hình ảnh đầu vào là không thể chia cắt bởi 32, chiều cao đầu ra hoặc chiều rộng của lớp ghép chuyển tiếp sẽ lệch khỏi hình dạng của hình ảnh đầu vào. Để giải quyết vấn đề này, chúng ta có thể cắt nhiều vùng hình chữ nhật với chiều cao và chiều rộng là bội số nguyên là 32 trong hình ảnh và thực hiện lan truyền về phía trước trên các pixel trong các khu vực này một cách riêng biệt. Lưu ý rằng sự kết hợp của các khu vực hình chữ nhật này cần phải bao phủ hoàn toàn hình ảnh đầu vào. Khi một pixel được bao phủ bởi nhiều khu vực hình chữ nhật, trung bình của các đầu ra tích hợp chuyển đổi trong các khu vực riêng biệt cho cùng một điểm ảnh này có thể được nhập vào hoạt động softmax để dự đoán lớp. Để đơn giản, chúng tôi chỉ đọc một vài hình ảnh thử nghiệm lớn hơn và cắt một khu vực :math:`320\times480` để dự đoán bắt đầu từ góc trên bên trái của một hình ảnh. Đối với những hình ảnh thử nghiệm này, chúng tôi in các khu vực cắt, kết quả dự đoán và sự thật mặt đất từng hàng. .. raw:: html
mxnetpytorch
.. raw:: html
.. 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_ce3435_129_0.svg .. raw:: html
.. raw:: html
.. 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, 320, 480) X = torchvision.transforms.functional.crop(test_images[i], *crop_rect) pred = label2image(predict(X)) imgs += [X.permute(1,2,0), pred.cpu(), torchvision.transforms.functional.crop( test_labels[i], *crop_rect).permute(1,2,0)] d2l.show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n, scale=2); .. figure:: output_fcn_ce3435_132_0.svg .. raw:: html
.. raw:: html
Tóm tắt ------- - Mạng phức tạp hoàn toàn đầu tiên sử dụng CNN để trích xuất các tính năng hình ảnh, sau đó chuyển đổi số lượng kênh thành số lượng lớp thông qua lớp ghép :math:`1\times 1` và cuối cùng biến đổi chiều cao và chiều rộng của bản đồ tính năng thành các hình ảnh đầu vào thông qua sự chuyển đổi. - Trong một mạng phức tạp hoàn toàn, chúng ta có thể sử dụng upsampling của nội suy song tuyến để khởi tạo lớp chuyển đổi. Bài tập ------- 1. Nếu chúng ta sử dụng khởi tạo Xavier cho lớp tích hợp chuyển tiếp trong thí nghiệm, kết quả thay đổi như thế nào? 2. Bạn có thể cải thiện hơn nữa độ chính xác của mô hình bằng cách điều chỉnh các siêu tham số? 3. Dự đoán các lớp của tất cả các pixel trong hình ảnh thử nghiệm. 4. Giấy mạng phức tạp hoàn toàn ban đầu cũng sử dụng đầu ra của một số lớp CNN trung gian :cite:`Long.Shelhamer.Darrell.2015`. Cố gắng thực hiện ý tưởng này. .. raw:: html
mxnetpytorch
.. raw:: html
`Discussions `__ .. raw:: html
.. raw:: html
`Discussions `__ .. raw:: html
.. raw:: html