.. raw:: html .. _sec_fine_tuning: Tinh Chỉnh ========== .. raw:: html 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. .. raw:: html 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. .. raw:: html Để 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 ý. .. raw:: html 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ế. .. raw:: html 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 :numref:`fig_finetune`, việc tinh chỉnh được tiến hành theo bốn bước sau đây: .. raw:: html 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. .. raw:: html .. _fig_finetune: .. figure:: ../img/finetune.svg Thực hiện tinh chỉnh. .. raw:: html Nhận dạng Xúc xích ------------------ .. raw:: html 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. .. raw:: html 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 `__. .. code:: python %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() .. raw:: html Lấy dữ liệu ~~~~~~~~~~~ .. raw:: html Tập dữ liệu xúc xích mà ta sử dụng được lấy từ internet, gồm :math:`1,400` ảnh mẫu dương chứa xúc xích và :math:`1,400` ảnh mẫu âm chứa các loại thức ăn khác. :math:`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. .. raw:: html Đầu tiên ta tải tập dữ liệu nén và thu được 2 tập tin ``hotdog/train`` và ``hotdog/test``. Cả hai đều có hai tập tin phụ ``hotdog`` và ``not-hotdog`` chứa các ảnh với phân loại tương ứng. .. code:: python #@save d2l.DATA_HUB['hotdog'] = (d2l.DATA_URL+'hotdog.zip', 'fba480ffa8aa7e0febbb511d181409f899b9baa5') data_dir = d2l.download_extract('hotdog') .. parsed-literal:: :class: output Downloading ../data/hotdog.zip from http://d2l-data.s3-accelerate.amazonaws.com/hotdog.zip... .. raw:: html 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. .. code:: python train_imgs = gluon.data.vision.ImageFolderDataset( os.path.join(data_dir, 'train')) test_imgs = gluon.data.vision.ImageFolderDataset( os.path.join(data_dir, 'test')) .. raw:: html 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. .. code:: python 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); .. figure:: output_fine-tuning_vn_ca1146_7_0.png .. raw:: html 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ý. .. code:: python # 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]) .. raw:: html Định nghĩa và Khởi tạo Mô hình ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. raw:: html 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. .. code:: python pretrained_net = gluon.model_zoo.vision.resnet18_v2(pretrained=True) .. raw:: html Mô hình gốc được huấn luyện sẵn bao gồm hai biến thành viên: ``features`` và ``output``. ``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. .. code:: python pretrained_net.output .. parsed-literal:: :class: output Dense(512 -> 1000, linear) .. raw:: html 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`` là :math:`\eta` thì ta sử dụng tốc độ học là :math:`10\eta` để cập nhật tham số mô hình trong biến thành viên ``output``. .. code:: python 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) .. raw:: html Tinh chỉnh Mô hình ~~~~~~~~~~~~~~~~~~ .. raw:: html Đầ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. .. code:: python 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) .. raw:: html 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. .. code:: python train_fine_tuning(finetune_net, 0.01) .. parsed-literal:: :class: output loss 0.223, train acc 0.926, test acc 0.934 587.8 examples/sec on [gpu(0)] .. figure:: output_fine-tuning_vn_ca1146_19_1.svg .. raw:: html Để 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. .. code:: python scratch_net = gluon.model_zoo.vision.resnet18_v2(classes=2) scratch_net.initialize(init=init.Xavier()) train_fine_tuning(scratch_net, 0.1) .. parsed-literal:: :class: output loss 0.431, train acc 0.807, test acc 0.809 649.9 examples/sec on [gpu(0)] .. figure:: output_fine-tuning_vn_ca1146_21_1.svg .. raw:: html 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. Tóm tắt ------- .. raw:: html - 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. Bài tập ------- .. raw:: html 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_net`` và ``scratch_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. .. code:: python finetune_net.features.collect_params().setattr('grad_req', 'null') .. raw:: html 4. 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? .. code:: python weight = pretrained_net.output.weight hotdog_w = np.split(weight.data(), 1000, axis=0)[713] hotdog_w.shape .. parsed-literal:: :class: output (1, 512) 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 - 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