13.1. Tăng cường Ảnh

Trong Section 7.1 chúng ta có đề cập đến việc các bộ dữ liệu lớn là điều kiện tiên quyết cho sự thành công của các mạng nơ-ron sâu. Kỹ thuật tăng cường ảnh giúp mở rộng kích thước của tập dữ liệu huấn luyện thông qua việc áp dụng một loạt thay đổi ngẫu nhiên trên các mẫu ảnh, từ đó tạo ra các mẫu huấn luyện tuy tương tự nhưng vẫn có sự khác biệt. Cũng có thể giải thích tác dụng của tăng cường ảnh là việc thay đổi ngẫu nhiên các mẫu dùng cho huấn luyện, làm giảm sự phụ thuộc của mô hình vào một số thuộc tính nhất định. Do đó giúp cải thiện năng lực khái quát hóa của mô hình.

Chẳng hạn, ta có thể cắt tập ảnh theo các cách khác nhau, để các đối tượng ta quan tâm xuất hiện ở các vị trí khác nhau, vì vậy giảm sự phụ thuộc của mô hình vào vị trí xuất hiện của đối tượng. Ta cũng có thể điều chỉnh độ sáng, màu sắc, và các yếu tố khác để giảm độ nhạy màu sắc của mô hình. Có thể khẳng định rằng kỹ thuật tăng cường ảnh đóng góp rất lớn cho sự thành công của mạng AlexNet. Tới đây, chúng ta sẽ thảo luận về kỹ thuật mà được sử dụng rộng rãi trong lĩnh vực thị giác máy tính này.

Trước tiên, thực hiện nhập các gói và mô-đun cần thiết.

%matplotlib inline
from d2l import mxnet as d2l
from mxnet import autograd, gluon, image, init, np, npx
from mxnet.gluon import nn

npx.set_np()

13.1.1. Phương pháp Tăng cường Ảnh Thông dụng

Trong phần thử nghiệm này, ta sẽ dùng một ảnh có kích thước \(400\times 500\) làm ví dụ.

d2l.set_figsize()
img = image.imread('../img/cat1.jpg')
d2l.plt.imshow(img.asnumpy());
../_images/output_image-augmentation_vn_3e478f_3_0.svg

Hầu hết các phương pháp tăng cường ảnh có một độ ngẫu nhiên nhất định. Để giúp việc quan sát tính hiệu quả của nó dễ hơn, ta sẽ định nghĩa hàm bổ trợ apply. Hàm này thực hiện phương thức tăng cường ảnh aug nhiều lần từ ảnh đầu vào img và hiển thị tất cả kết quả.

def apply(img, aug, num_rows=2, num_cols=4, scale=1.5):
    Y = [aug(img) for _ in range(num_rows * num_cols)]
    d2l.show_images(Y, num_rows, num_cols, scale=scale)

13.1.1.1. Lật và Cắt ảnh

Lật hình ảnh sang trái và phải thường không thay đổi thể loại đối tượng. Đây là một trong những phương pháp tăng cường ảnh được sử dụng sớm nhất và rộng rãi nhất. Tiếp theo, chúng ta sử dụng mô-đun transforms để tạo thực thể RandomFlipLeftRight, ngẫu nhiên lật hình ảnh sang trái hoặc phải với xác suất 50%.

apply(img, gluon.data.vision.transforms.RandomFlipLeftRight())
../_images/output_image-augmentation_vn_3e478f_7_0.svg

Lật lên và xuống không được sử dụng phổ biến như lật trái và phải. Tuy nhiên, ít nhất là đối với hình ảnh ví dụ này, lật lên xuống không gây trở ngại cho việc nhận dạng. Tiếp theo, chúng tôi tạo thực thể RandomFlipTopBottom để lật hình ảnh lên và xuống với xác suất 50%.

apply(img, gluon.data.vision.transforms.RandomFlipTopBottom())
../_images/output_image-augmentation_vn_3e478f_9_0.svg

Trong ví dụ chúng ta sử dụng, con mèo nằm ở giữa ảnh, nhưng không phải tất cả các ảnh mèo khác đều sẽ như vậy. Section 6.5 có đề cập rằng tầng gộp có thể làm giảm độ nhạy của tầng tích chập với vị trí mục tiêu. Ngoài ra, chúng ta có thể làm cho các đối tượng xuất hiện ở các vị trí khác nhau trong ảnh theo tỷ lệ khác nhau bằng cách cắt ngẫu nhiên hình ảnh. Điều này cũng có thể làm giảm độ nhạy của mô hình với vị trí mục tiêu.

Trong đoạn mã sau, chúng tôi cắt ngẫu nhiên một vùng có diện tích từ 10% đến 100% diện tích ban đầu và tỷ lệ giữa chiều rộng và chiều cao của vùng được chọn ngẫu nhiên trong khoảng từ 0.5 đến 2. Sau đó, cả chiều rộng và chiều cao của vùng đều được biến đổi tỷ lệ thành 200 pixel. Trừ khi có quy định khác, giá trị ngẫu nhiên liên tục giữa \(a\)\(b\) thu được bằng cách lấy mẫu đồng nhất trong khoảng \([a, b]\).

shape_aug = gluon.data.vision.transforms.RandomResizedCrop(
    (200, 200), scale=(0.1, 1), ratio=(0.5, 2))
apply(img, shape_aug)
../_images/output_image-augmentation_vn_3e478f_11_0.svg

13.1.1.2. Đổi màu

Một phương pháp tăng cường khác là thay đổi màu sắc. Chúng ta có thể thay đổi bốn khía cạnh màu sắc của hình ảnh: độ sáng, độ tương phản, độ bão hòa và tông màu. Trong ví dụ dưới đây, chúng tôi thay đổi ngẫu nhiên độ sáng của hình ảnh với giá trị trong khoảng từ 50% (\(1-0.5\)) đến 150% (\(1+0.5\)) độ sáng của ảnh gốc.

apply(img, gluon.data.vision.transforms.RandomBrightness(0.5))
../_images/output_image-augmentation_vn_3e478f_13_0.svg

Tương tự vậy, ta có thể ngẫu nhiên thay đổi tông màu của ảnh.

apply(img, gluon.data.vision.transforms.RandomHue(0.5))
../_images/output_image-augmentation_vn_3e478f_15_0.svg

Ta cũng có thể tạo một thực thể RandomColorJitter và thiết lập để ngẫu nhiên thay đổi brightness (độ sáng), contrast (độ tương phản), saturation (độ bão hòa), và hue (tông màu) của ảnh cùng một lúc.

color_aug = gluon.data.vision.transforms.RandomColorJitter(
    brightness=0.5, contrast=0.5, saturation=0.5, hue=0.5)
apply(img, color_aug)
../_images/output_image-augmentation_vn_3e478f_17_0.svg

13.1.1.3. Kết hợp nhiều Phương pháp Tăng cường Ảnh

Trong thực tế, chúng ta sẽ kết hợp nhiều phương pháp tăng cường ảnh. Ta có thể kết hợp các phương pháp trên và áp dụng chúng cho từng hình ảnh bằng cách sử dụng thực thể Compose.

augs = gluon.data.vision.transforms.Compose([
    gluon.data.vision.transforms.RandomFlipLeftRight(), color_aug, shape_aug])
apply(img, augs)
../_images/output_image-augmentation_vn_3e478f_19_0.svg

13.1.2. Huấn luyện Mô hình dùng Tăng cường Ảnh

Tiếp theo, ta sẽ xem xét làm thế nào để áp dụng tăng cường hình ảnh trong huấn luyện thực tế. Ở đây, ta sử dụng bộ dữ liệu CIFAR-10, thay vì Fashion-MNIST trước đây. Điều này là do vị trí và kích thước của các đối tượng trong bộ dữ liệu Fashion-MNIST đã được chuẩn hóa và sự khác biệt về màu sắc và kích thước của các đối tượng trong bộ dữ liệu CIFAR-10 là đáng kể hơn. 32 hình ảnh huấn luyện đầu tiên trong bộ dữ liệu CIFAR-10 được hiển thị bên dưới.

d2l.show_images(gluon.data.vision.CIFAR10(
    train=True)[0:32][0], 4, 8, scale=0.8);
../_images/output_image-augmentation_vn_3e478f_21_0.svg

Để có được kết quả cuối cùng trong dự đoán, ta thường chỉ áp dụng tăng cường ảnh khi huấn luyện nhưng không sử dụng các biến đổi ngẫu nhiên trong dự đoán. Ở đây, chúng ta chỉ sử dụng phương pháp lật ngẫu nhiên trái phải đơn giản nhất. Ngoài ra, chúng ta sử dụng một thực thể ToTensor để chuyển đổi minibatch hình ảnh thành định dạng theo yêu cầu của MXNet, tức là, tensor số thực dấu phẩy động 32-bit có kích thước (kích thước batch, số kênh, chiều cao, chiều rộng) và phạm vi giá trị trong khoảng từ 0 đến 1.

train_augs = gluon.data.vision.transforms.Compose([
    gluon.data.vision.transforms.RandomFlipLeftRight(),
    gluon.data.vision.transforms.ToTensor()])

test_augs = gluon.data.vision.transforms.Compose([
    gluon.data.vision.transforms.ToTensor()])

Tiếp theo, ta định nghĩa một chức năng phụ trợ để giúp đọc hình ảnh và áp dụng tăng cường ảnh dễ dàng hơn. Hàm transform_first được cung cấp bởi Gluon giúp thực thi tăng cường ảnh cho phần tử đầu tiên của mỗi mẫu huấn luyện (hình ảnh và nhãn), tức là chỉ áp dụng lên phần ảnh. Để biết thêm chi tiết về DataLoader, hãy tham khảo Section 3.5.

def load_cifar10(is_train, augs, batch_size):
    return gluon.data.DataLoader(
        gluon.data.vision.CIFAR10(train=is_train).transform_first(augs),
        batch_size=batch_size, shuffle=is_train,
        num_workers=d2l.get_dataloader_workers())

13.1.2.1. Sử dụng Mô hình Huấn luyện Đa GPU

Ta huấn luyện mô hình ResNet-18 như mô tả ở Section 7.6 trên tập dữ liệu CIFAR-10. Cùng với đó ta áp dụng các phương pháp được mô tả trong sec_multi_gpu_concise và sử dụng mô hình huấn luyện đa GPU.

Tiếp theo, ta định nghĩa hàm huấn luyện để huấn luyện và đánh giá mô hình sử dụng nhiều GPU.

#@save
def train_batch_ch13(net, features, labels, loss, trainer, devices,
                     split_f=d2l.split_batch):
    X_shards, y_shards = split_f(features, labels, devices)
    with autograd.record():
        pred_shards = [net(X_shard) for X_shard in X_shards]
        ls = [loss(pred_shard, y_shard) for pred_shard, y_shard
              in zip(pred_shards, y_shards)]
    for l in ls:
        l.backward()
    # The True flag allows parameters with stale gradients, which is useful
    # later (e.g., in fine-tuning BERT)
    trainer.step(labels.shape[0], ignore_stale_grad=True)
    train_loss_sum = sum([float(l.sum()) for l in ls])
    train_acc_sum = sum(d2l.accuracy(pred_shard, y_shard)
                        for pred_shard, y_shard in zip(pred_shards, y_shards))
    return train_loss_sum, train_acc_sum
#@save
def train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs,
               devices=d2l.try_all_gpus(), split_f=d2l.split_batch):
    num_batches, timer = len(train_iter), d2l.Timer()
    animator = d2l.Animator(xlabel='epoch', xlim=[0, num_epochs], ylim=[0, 1],
                            legend=['train loss', 'train acc', 'test acc'])
    for epoch in range(num_epochs):
        # Store training_loss, training_accuracy, num_examples, num_features
        metric = d2l.Accumulator(4)
        for i, (features, labels) in enumerate(train_iter):
            timer.start()
            l, acc = train_batch_ch13(
                net, features, labels, loss, trainer, devices, split_f)
            metric.add(l, acc, labels.shape[0], labels.size)
            timer.stop()
            if (i + 1) % (num_batches // 5) == 0:
                animator.add(epoch + i / num_batches,
                             (metric[0] / metric[2], metric[1] / metric[3],
                              None))
        test_acc = d2l.evaluate_accuracy_gpus(net, test_iter, split_f)
        animator.add(epoch + 1, (None, None, test_acc))
    print(f'loss {metric[0] / metric[2]:.3f}, train acc '
          f'{metric[1] / metric[3]:.3f}, test acc {test_acc:.3f}')
    print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec on '
          f'{str(devices)}')

Giờ ta có thể định nghĩa hàm train_with_data_aug để áp dụng tăng cường ảnh vào huấn luyện mô hình. Hàm này tìm tất cả các GPU có sẵn và sử dụng Adam làm thuật toán tối ưu cho quá trình huấn luyện. Sau đó nó áp dụng tăng cường ảnh vào tập huấn luyện, và cuối cùng gọi đến hàm train_ch13 được định nghĩa ở trên để huấn luyện và đánh giá mô hình.

batch_size, devices, net = 256, d2l.try_all_gpus(), d2l.resnet18(10)
net.initialize(init=init.Xavier(), ctx=devices)

def train_with_data_aug(train_augs, test_augs, net, lr=0.001):
    train_iter = load_cifar10(True, train_augs, batch_size)
    test_iter = load_cifar10(False, test_augs, batch_size)
    loss = gluon.loss.SoftmaxCrossEntropyLoss()
    trainer = gluon.Trainer(net.collect_params(), 'adam',
                            {'learning_rate': lr})
    train_ch13(net, train_iter, test_iter, loss, trainer, 10, devices)

Giờ ta huấn luyện mô hình áp dụng tăng cường ảnh qua phép lật ngẫu nhiên trái và phải.

train_with_data_aug(train_augs, test_augs, net)
loss 0.165, train acc 0.942, test acc 0.856
2276.0 examples/sec on [gpu(0)]
../_images/output_image-augmentation_vn_3e478f_32_1.svg

13.1.3. Tóm tắt

  • Tăng cường ảnh sản sinh ra những ảnh ngẫu nhiên dựa vào dữ liệu có sẵn trong tập huấn luyện để đối phó với hiện tượng quá khớp.
  • Để có thể thu được kết quả tin cậy trong quá trình dự đoán, thường thì ta chỉ áp dụng tăng cường ảnh lên mẫu huấn luyện, không áp dụng các biến đổi tăng cường ảnh ngẫu nhiên trong quá trình dự đoán.
  • Mô-đun transforms của Gluon có các lớp thực hiện tăng cường ảnh.

13.1.4. Bài tập

  1. Huấn luyện mô hình mà không áp dụng tăng cường ảnh: train_with_data_aug(no_aug, no_aug). So sánh độ chính xác trong huấn luyện và kiểm tra khi áp dụng và không áp dụng tăng cường ảnh. Liệu thí nghiệm so sánh này có thể hỗ trợ cho luận điểm rằng tăng cường ảnh có thể làm giảm hiện tượng quá khớp? Tại sao?
  2. Sử dụng thêm các phương thức tăng cường ảnh khác trên tập dữ liệu CIFAR-10 khi huấn luyện mô hình. Theo dõi kết quả.
  3. Tham khảo tài liệu của MXNet và cho biết mô-đun transforms của Gluon còn cung cấp các phương thức tăng cường ảnh nào khác?

13.1.5. Thảo luận

13.1.6. 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 Mai Hoàng Long
  • Trần Yến Thy
  • Lê Khắc Hồng Phúc
  • Nguyễn Văn Cường
  • Phạm Hồng Vinh
  • Đỗ Trường Giang
  • Nguyễn Lê Quang Nhật