13.2. Tinh Chỉnh

Trong các chương trước, chúng ta đã thảo luận cách huấn luyện mô hình trên tập dữ liệu Fashion-MNIST với chỉ 60,000 ảnh. Ta cũng đã nói về ImageNet, tập dữ liệu ảnh quy mô lớn được sử dụng phổ biến trong giới học thuật, với hơn 10 triệu tấm ảnh vật thể thuộc hơn 1000 hạng mục. Tuy nhiên, những tập dữ liệu ta thường gặp chỉ có kích thước đâu đó giữa hai tập này, lớn hơn MNIST nhưng nhỏ hơn ImageNet.

Giả sử ta muốn nhận diện các loại ghế khác nhau trong ảnh rồi gửi đường dẫn mua hàng đến người dùng. Một cách khả dĩ là: đầu tiên ta tìm khoảng một trăm loại ghế phổ biến, chụp một nghìn bức ảnh từ các góc máy khác nhau với mỗi loại, rồi huấn luyện mô hình phân loại trên tập dữ liệu ảnh này. Dù tập dữ liệu này lớn hơn Fashion-MNIST, thì số lượng ảnh vẫn không bằng được một phần mười của ImageNet. Điều này dẫn tới việc các mô hình phức tạp bị quá khớp khi huấn luyện trên tập dữ liệu này, dù chúng hoạt động tốt với Imagenet. Đồng thời, vì lượng dữ liệu khá hạn chế, độ chính xác của mô hình sau khi huấn luyện xong có thể không thoả mãn được mức yêu cầu trong thực tiễn.

Để giải quyết vấn đề này, một giải pháp dễ thấy là đi thu thập thêm dữ liệu. Tuy nhiên, việc thu thập và gán nhãn dữ liệu có thể tốn rất nhiều tiền và thời gian. Ví dụ, để xây dựng được tập ImageNet, hàng triệu đô la đã được sử dụng từ nguồn tài trợ nghiên cứu. Dù vậy, gần đây chi phí thu thập dữ liệu đã giảm mạnh, nhưng điều này vẫn rất đáng lưu ý.

Một giải pháp khác là áp dụng kỹ thuật học truyền tải (transfer learning), mang kiến thức đã học được từ tập dữ liệu gốc áp dụng sang tập dữ liệu mục tiêu. Ví dụ, đa phần ảnh trong ImageNet không chụp ghế, nhưng những mô hình đã được huấn luyện trên ImageNet có khả năng trích xuất các đặc trưng chung của ảnh, rồi từ đó giúp nhận diện ra góc cạnh, bề mặt, hình dáng, và các kết cấu của vật thể. Các đặc trưng tương đồng này cũng sẽ có ích trong bài toán nhận diện ghế.

Trong phần này, chúng tôi giới thiệu một kỹ thuật thông dụng trong việc học truyền tải, đó là tinh chỉnh (fine tuning). Như minh họa trong hình Fig. 13.2.1, việc tinh chỉnh được tiến hành theo bốn bước sau đây:

  1. Tiền huấn luyện một mô hình mạng nơ-ron, tức là là mô hình gốc, trên tập dữ liệu gốc (chẳng hạn tập dữ liệu ImageNet).
  2. Tạo mô hình mạng nơ-ron mới gọi là mô hình mục tiêu. Mô hình này sao chép tất cả các thiết kế cũng như các tham số của mô hình gốc, ngoại trừ tầng đầu ra. Ta giả định rằng các tham số mô hình chứa tri thức đã học từ tập dữ liệu gốc và tri thức này sẽ áp dụng tương tự đối với tập dữ liệu mục tiêu. Ta cũng giả định là tầng đầu ra của mô hình gốc có liên hệ mật thiết với các nhãn của tập dữ liệu gốc và do đó không được sử dụng trong mô hình mục tiêu.
  3. Thêm vào một tầng đầu ra cho mô hình mục tiêu mà kích thước của nó là số lớp của dữ liệu mục tiêu, và khởi tạo ngẫu nhiên các tham số mô hình của tầng này.
  4. Huấn luyện mô hình mục tiêu trên tập dữ liệu mục tiêu, chẳng hạn như tập dữ liệu ghế. Chúng ta sẽ huấn luyện tầng đầu ra từ đầu, trong khi các tham số của tất cả các tầng còn lại được tinh chỉnh từ các tham số của mô hình gốc.
../_images/finetune.svg

Fig. 13.2.1 Thực hiện tinh chỉnh.

13.2.1. Nhận dạng Xúc xích

Tiếp theo, ta sẽ dùng một ví dụ cụ thể để luyện tập đó là: nhận dạng xúc xích. Ta sẽ tinh chỉnh mô hình ResNet đã huấn luyện trên tập dữ liệu ImageNet dựa trên một tập dữ liệu nhỏ. Tập dữ liệu nhỏ này chứa hàng nghìn ảnh, trong đó sẽ có các ảnh chứa hình xúc xích. Ta sẽ sử dụng mô hình thu được từ việc tinh chỉnh để xác định một bức ảnh có chứa món ăn này hay không.

Trước tiên, ta thực hiện nhập các gói và mô-đun cần cho việc thử nghiệm. Gói model_zoo trong Gluon cung cấp một mô hình đã được huấn luyện sẵn phổ biến. Nếu bạn muốn lấy thêm các mô hình đã được tiền huấn luyện cho thị giác máy tính, bạn có thể tham khảo Bộ công cụ GluonCV.

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

npx.set_np()

13.2.1.1. Lấy dữ liệu

Tập dữ liệu xúc xích mà ta sử dụng được lấy từ internet, gồm \(1,400\) ảnh mẫu dương chứa xúc xích và \(1,400\) ảnh mẫu âm chứa các loại thức ăn khác. \(1,000\) ảnh thuộc nhiều lớp khác nhau được sử dụng để huấn luyện và phần còn lại được dùng để kiểm tra.

Đầu tiên ta tải tập dữ liệu nén và thu được 2 tập tin hotdog/trainhotdog/test. Cả hai đều có hai tập tin phụ hotdognot-hotdog chứa các ảnh với phân loại tương ứng.

#@save
d2l.DATA_HUB['hotdog'] = (d2l.DATA_URL+'hotdog.zip',
                         'fba480ffa8aa7e0febbb511d181409f899b9baa5')

data_dir = d2l.download_extract('hotdog')
Downloading ../data/hotdog.zip from http://d2l-data.s3-accelerate.amazonaws.com/hotdog.zip...

Ta tạo hai thực thể ImageFolderDataset để đọc toàn bộ các tệp ảnh trong tập huấn luyện và tập kiểm tra.

train_imgs = gluon.data.vision.ImageFolderDataset(
    os.path.join(data_dir, 'train'))
test_imgs = gluon.data.vision.ImageFolderDataset(
    os.path.join(data_dir, 'test'))

Dưới đây là 8 mẫu dương tính đầu tiên và 8 mẫu âm cuối cùng. Bạn có thể thấy những hình ảnh có nhiều kích thước và tỷ lệ khác nhau.

hotdogs = [train_imgs[i][0] for i in range(8)]
not_hotdogs = [train_imgs[-i - 1][0] for i in range(8)]
d2l.show_images(hotdogs + not_hotdogs, 2, 8, scale=1.4);
../_images/output_fine-tuning_vn_ca1146_7_0.png

Trong quá trình huấn luyện, chúng ta cắt ảnh với kích thước và tỷ lệ ngẫu nhiên sau đó biến đổi tỷ lệ để có chiều dài và chiều rộng 224 pixel. Khi kiểm tra, ta biến đổi tỷ lệ chiều dài và chiều rộng của ảnh về kích thước 256 pixel, sau đó cắt ở vùng trung tâm để thu được ảnh có chiều dài và rộng là 224 pixel để làm đầu vào cho mô hình. Thêm vào đó, chúng ta chuẩn hóa các giá trị của ba kênh màu RGB (red, green, blue). Tất cả giá trị trên ảnh sẽ được trừ đi giá trị trung bình trên kênh màu, sau đó được chia cho độ lệch chuẩn của chúng để thu được ảnh đã qua xử lý.

# We specify the mean and variance of the three RGB channels to normalize the
# image channel
normalize = gluon.data.vision.transforms.Normalize(
    [0.485, 0.456, 0.406], [0.229, 0.224, 0.225])

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

test_augs = gluon.data.vision.transforms.Compose([
    gluon.data.vision.transforms.Resize(256),
    gluon.data.vision.transforms.CenterCrop(224),
    gluon.data.vision.transforms.ToTensor(),
    normalize])

13.2.1.2. Định nghĩa và Khởi tạo Mô hình

Ta sử dụng ResNet-18 đã được tiền huấn luyện trên tập dữ liệu ImageNet làm mô hình gốc. Ở đây ta chỉ rõ pretrained=True để tự động tải xuống và nạp các tham số mô hình được tiền huấn luyện. Trong lần sử dụng đầu tiên, các tham số mô hình cần được tải xuống từ Internet.

pretrained_net = gluon.model_zoo.vision.resnet18_v2(pretrained=True)

Mô hình gốc được huấn luyện sẵn bao gồm hai biến thành viên: featuresoutput. features bao gồm tất cả các tầng của mô hình ngoại trừ tầng đầu ra, và output chính là tầng đầu ra của mô hình đó. Mục đích chính của việc phân chia này là để tạo điều kiện cho việc tinh chỉnh các tham số của tất cả các tầng của mô hình trừ tầng đầu ra. Biến thành viên output của mô hình gốc là một tầng kết nối đầy đủ. Nó biến đổi đầu ra của tầng gộp trung bình toàn cục thành 1000 lớp đầu ra trên tập dữ liệu ImageNet như dưới đây.

pretrained_net.output
Dense(512 -> 1000, linear)

Sau đó ta xây dựng một mạng nơ-ron mới làm mô hình mục tiêu. Mạng này được định nghĩa giống như mô hình tiền huấn luyện gốc, tuy nhiên số đầu ra cuối cùng bằng với số hạng mục trong tập dữ liệu mục tiêu. Ở đoạn mã phía dưới, các tham số mô hình trong biến thành viên features của mô hình mục tiêu finetune_net được khởi tạo giống như các tham số của các tầng tương ứng trong mô hình gốc. Các tham số mô hình trong features đã được huấn luyện trên tập dữ liệu ImageNet nên đã tương đối tốt. Vì vậy thường thì ta chỉ cần sử dụng tốc độ học nhỏ để “tinh chỉnh” các tham số trên. Ngược lại, các tham số mô hình trong biến thành viên output được khởi tạo ngẫu nhiên và thường yêu cầu tốc độ học lớn hơn nhiều để học lại từ đầu. Giả sử rằng tốc độ học trong đối tượng Trainer\(\eta\) thì ta sử dụng tốc độ học là \(10\eta\) để cập nhật tham số mô hình trong biến thành viên output.

finetune_net = gluon.model_zoo.vision.resnet18_v2(classes=2)
finetune_net.features = pretrained_net.features
finetune_net.output.initialize(init.Xavier())
# The model parameters in output will be updated using a learning rate ten
# times greater
finetune_net.output.collect_params().setattr('lr_mult', 10)

13.2.1.3. Tinh chỉnh Mô hình

Đầu tiên, ta định nghĩa hàm huấn luyện tinh chỉnh train_fine_tuning để có thể gọi nhiều lần.

def train_fine_tuning(net, learning_rate, batch_size=128, num_epochs=5):
    train_iter = gluon.data.DataLoader(
        train_imgs.transform_first(train_augs), batch_size, shuffle=True)
    test_iter = gluon.data.DataLoader(
        test_imgs.transform_first(test_augs), batch_size)
    devices = d2l.try_all_gpus()
    net.collect_params().reset_ctx(devices)
    net.hybridize()
    loss = gluon.loss.SoftmaxCrossEntropyLoss()
    trainer = gluon.Trainer(net.collect_params(), 'sgd', {
        'learning_rate': learning_rate, 'wd': 0.001})
    d2l.train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs,
                   devices)

Ta gán giá trị tốc độ học nhỏ cho đối tượng Trainer, ví dụ như 0.01, để tinh chỉnh các tham số mô hình huấn luyện sẵn. Như đề cập ở trên, ta sẽ sử dụng tốc độ học gấp 10 lần để huấn luyện từ đầu các tham số của tầng đầu ra mô hình mục tiêu.

train_fine_tuning(finetune_net, 0.01)
loss 0.223, train acc 0.926, test acc 0.934
587.8 examples/sec on [gpu(0)]
../_images/output_fine-tuning_vn_ca1146_19_1.svg

Để so sánh, ta định nghĩa một mô hình y hệt, tuy nhiên tất cả các tham số mô hình của nó được khởi tạo một cách ngẫu nhiên. Do toàn bộ mô hình cần được huấn luyện từ đầu, ta có thể sử dụng tốc độ học lớn hơn.

scratch_net = gluon.model_zoo.vision.resnet18_v2(classes=2)
scratch_net.initialize(init=init.Xavier())
train_fine_tuning(scratch_net, 0.1)
loss 0.431, train acc 0.807, test acc 0.809
649.9 examples/sec on [gpu(0)]
../_images/output_fine-tuning_vn_ca1146_21_1.svg

Như bạn có thể thấy, với số epoch như nhau, mô hình tinh chỉnh có giá trị precision cao hơn. Lý do là vì các tham có giá trị khởi tạo ban đầu tốt hơn.

13.2.2. Tóm tắt

  • Học truyền tải chuyển kiến thức học được từ tập dữ liệu gốc sang tập dữ liệu mục tiêu. Tinh chỉnh là một kỹ thuật phổ biến trong học truyền tải.
  • Mô hình mục tiêu tái tạo toàn bộ thiết kế mô hình và các tham số của mô hình gốc, ngoại trừ tầng đầu ra, và tinh chỉnh các tham số này dựa vào tập dữ liệu mục tiêu. Ngược lại, tầng đầu ra của mô hình mục tiêu cần được huấn luyện lại từ đầu.
  • Thông thường việc tinh chỉnh các tham số sử dụng tốc độ học nhỏ, trong khi việc huấn luyện lại tầng đầu ra từ đầu có thể sử dụng tốc độ học lớn hơn.

13.2.3. Bài tập

  1. Liên tục tăng tốc độ học của finetune_net. Giá trị precision của mô hình thay đổi như thế nào?
  2. Tiếp tục điều chỉnh các siêu tham số của finetune_netscratch_net trong thí nghiệm so sánh ở trên. Liệu giá trị precision của chúng vẫn khác nhau hay không?
  3. Gán các tham số của finetune_net.features bằng các tham số của mô hình gốc và không cập nhật chúng suốt quá trình huấn luyện. Điều gì sẽ xảy ra? Bạn có thể sử dụng đoạn mã sau.
finetune_net.features.collect_params().setattr('grad_req', 'null')
  1. Thật ra, lớp “hotdog” có trong tập dữ liệu ImageNet. Các trọng số tương ứng của nó trong tầng đầu ra có thể thu được thông qua việc sử dụng đoạn mã sau. Ta có thể sử dụng các tham số này như thế nào?
weight = pretrained_net.output.weight
hotdog_w = np.split(weight.data(), 1000, axis=0)[713]
hotdog_w.shape
(1, 512)

13.2.4. Thảo luận

13.2.5. 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
  • Mai Sơn Hải
  • Phạm Minh Đức
  • Phạm Hồng Vinh
  • Nguyễn Thanh Hòa
  • Đỗ Trường Giang
  • Nguyễn Văn Cường
  • Lê Khắc Hồng Phúc
  • Nguyễn Lê Quang Nhật
  • Nguyễn Mai Hoàng Long