.. 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