.. raw:: html
Nhận diện Giống Chó (ImageNet Dogs) trên Kaggle
===============================================
.. raw:: html
Trong phần này, ta sẽ giải quyết thử thách nhận diện giống chó trong một
cuộc thi trên Kaggle. Cuộc thi có địa chỉ tại
https://www.kaggle.com/c/dog-breed-identification
.. raw:: html
Trong cuộc thi này, ta cần nhận diện 120 giống chó khác nhau. Tập dữ
liệu trong cuộc thi này thực chất là một tập con của tập dữ liệu
ImageNet nổi tiếng. Khác với ảnh trong tập dữ liệu CIFAR-10 được sử dụng
trong phần trước, các ảnh trong tập dữ liệu ImageNet có chiều dài và
chiều rộng lớn hơn, đồng thời kích thước của chúng không nhất quán.
.. raw:: html
:numref:`fig_kaggle_dog` mô tả thông tin trên trang web của cuộc thi.
Để có thể nộp kết quả, trước tiên vui lòng đăng kí tài khoảng trên
Kaggle.
.. raw:: html
.. _fig_kaggle_dog:
.. figure:: ../img/kaggle-dog.jpg
:width: 400px
Trang web cuộc thi nhận diện giống chó. Tập dữ liệu cho cuộc thi này
có thể được truy cập bằng cách nhấn vào thẻ “Data”.
.. raw:: html
Đầu tiên, ta nhập vào các gói thư viện hoặc các mô-đun cần cho cuộc thi.
.. code:: python
import collections
from d2l import mxnet as d2l
import math
from mxnet import autograd, gluon, init, npx
from mxnet.gluon import nn
import os
import time
npx.set_np()
.. raw:: html
Tải xuống và Tổ chức Tập dữ liệu
--------------------------------
.. raw:: html
Dữ liệu cuộc thi được chia thành tập huấn luyện và tập kiểm tra. Tập
huấn luyện bao gồm :math:`10,222` ảnh và tập kiểm tra bao gồm
:math:`10,357` ảnh. Tất cả các ảnh trong hai tập đều có định dạng JPEG.
Các ảnh này gồm có ba kênh (màu) RGB và có chiều cao và chiều rộng khác
nhau. Có tất cả 120 giống chó trong tập huấn luyện, gồm có Chó tha mồi
(*Labrador*), Chó săn vịt (*Poodle*), Chó Dachshund, Samoyed, Huskie,
Chihuahua, và Chó sục Yorkshire (*Yorkshire Terriers*).
.. raw:: html
Tải tập dữ liệu
~~~~~~~~~~~~~~~
.. raw:: html
Sau khi đăng nhập vào Kaggle, ta có thể chọn thẻ “Data” trong trang web
cuộc thi nhận diện giống chó như mô tả trong :numref:`fig_kaggle_dog`
và tải tập dữ liệu về bằng cách nhấn vào nút “Download All”. Sau khi
giải nén tệp đã tải về trong thư mục ``../data``, bạn có thể tìm thấy
toàn bộ tập dữ liệu theo các đường dẫn sau:
- ../data/dog-breed-identification/labels.csv
- ../data/dog-breed-identification/sample_submission.csv
- ../data/dog-breed-identification/train
- ../data/dog-breed-identification/test
.. raw:: html
Có thể bạn đã nhận ra rằng cấu trúc trên khá giống với cấu trúc thư mục
của cuộc thi CIFAR-10 trong :numref:`sec_kaggle_cifar10`, trong đó thư
mục ``train/`` và ``test/`` lần lượt chứa ảnh chó để huấn luyện và kiểm
tra, và ``labels.csv`` chứa nhãn cho các ảnh huấn luyện.
.. raw:: html
Tương tự, để đơn giản, chúng tôi cung cấp một tập mẫu nhỏ của tập dữ
liệu kể trên, “train_valid_test_tiny.zip”. Nếu bạn sử dụng tập dữ liệu
đầy đủ cho cuộc thi Kaggle, bạn cần thay đổi biến ``demo`` phía dưới
thành ``False``.
.. code:: python
#@save
d2l.DATA_HUB['dog_tiny'] = (d2l.DATA_URL + 'kaggle_dog_tiny.zip',
'75d1ec6b9b2616d2760f211f72a83f73f3b83763')
# If you use the full dataset downloaded for the Kaggle competition, change
# the variable below to False
demo = True
if demo:
data_dir = d2l.download_extract('dog_tiny')
else:
data_dir = os.path.join('..', 'data', 'dog-breed-identification')
.. parsed-literal::
:class: output
Downloading ../data/kaggle_dog_tiny.zip from http://d2l-data.s3-accelerate.amazonaws.com/kaggle_dog_tiny.zip...
.. raw:: html
Tổ chức Tập dữ liệu
~~~~~~~~~~~~~~~~~~~
.. raw:: html
Ta có thể tổ chức tập dữ liệu tương tự như cách ta đã làm trong
:numref:`sec_kaggle_cifar10`, tức là tách riêng một tập kiểm định từ
tập huấn luyện, sau đó đưa các ảnh vào từng thư mục con theo nhãn của
chúng.
.. raw:: html
Hàm ``reorg_dog_data`` dưới đây được sử dụng để đọc nhãn của dữ liệu
huấn luyện, tách riêng tập kiểm định và tổ chức tập huấn luyện.
.. code:: python
def reorg_dog_data(data_dir, valid_ratio):
labels = d2l.read_csv_labels(os.path.join(data_dir, 'labels.csv'))
d2l.reorg_train_valid(data_dir, labels, valid_ratio)
d2l.reorg_test(data_dir)
batch_size = 4 if demo else 128
valid_ratio = 0.1
reorg_dog_data(data_dir, valid_ratio)
.. raw:: html
Tăng cường Ảnh
--------------
.. raw:: html
Trong phần này, kích thước ảnh lớn hơn phần trước. Dưới đây là một số kỹ
thuật tăng cường ảnh có thể sẽ hữu dụng.
.. code:: python
transform_train = gluon.data.vision.transforms.Compose([
# Randomly crop the image to obtain an image with an area of 0.08 to 1 of
# the original area and height to width ratio between 3/4 and 4/3. Then,
# scale the image to create a new image with a height and width of 224
# pixels each
gluon.data.vision.transforms.RandomResizedCrop(224, scale=(0.08, 1.0),
ratio=(3.0/4.0, 4.0/3.0)),
gluon.data.vision.transforms.RandomFlipLeftRight(),
# Randomly change the brightness, contrast, and saturation
gluon.data.vision.transforms.RandomColorJitter(brightness=0.4,
contrast=0.4,
saturation=0.4),
# Add random noise
gluon.data.vision.transforms.RandomLighting(0.1),
gluon.data.vision.transforms.ToTensor(),
# Standardize each channel of the image
gluon.data.vision.transforms.Normalize([0.485, 0.456, 0.406],
[0.229, 0.224, 0.225])])
.. raw:: html
Trong quá trình kiểm tra, ta chỉ sử dụng một số bước tiền xử lý ảnh nhất
định.
.. code:: python
transform_test = gluon.data.vision.transforms.Compose([
gluon.data.vision.transforms.Resize(256),
# Crop a square of 224 by 224 from the center of the image
gluon.data.vision.transforms.CenterCrop(224),
gluon.data.vision.transforms.ToTensor(),
gluon.data.vision.transforms.Normalize([0.485, 0.456, 0.406],
[0.229, 0.224, 0.225])])
.. raw:: html
Đọc Dữ liệu
-----------
.. raw:: html
Như trong phần trước, ta có thể tạo thực thể ``ImageFolderDataset`` để
đọc dữ liệu chứa các tệp ảnh gốc.
.. code:: python
train_ds, valid_ds, train_valid_ds, test_ds = [
gluon.data.vision.ImageFolderDataset(
os.path.join(data_dir, 'train_valid_test', folder))
for folder in ('train', 'valid', 'train_valid', 'test')]
.. raw:: html
Ở đây, ta tạo các thực thể ``DataLoader`` giống như trong
:numref:`sec_kaggle_cifar10`.
.. code:: python
train_iter, train_valid_iter = [gluon.data.DataLoader(
dataset.transform_first(transform_train), batch_size, shuffle=True,
last_batch='discard') for dataset in (train_ds, train_valid_ds)]
valid_iter = gluon.data.DataLoader(
valid_ds.transform_first(transform_test), batch_size, shuffle=False,
last_batch='discard')
test_iter = gluon.data.DataLoader(
test_ds.transform_first(transform_test), batch_size, shuffle=False,
last_batch='keep')
.. raw:: html
Định nghĩa Mô hình
------------------
.. raw:: html
Dữ liệu cho cuộc thi này là một phần của tập dữ liệu ImageNet. Do đó, ta
có thể sử dụng cách tiếp cận được thảo luận trong
:numref:`sec_fine_tuning` để lựa chọn mô hình đã được tiền huấn luyện
trên toàn bộ dữ liệu ImageNet và sử dụng nó để trích xuất đặc trưng ảnh
làm đầu vào cho một mạng tùy biến cỡ nhỏ. Gluon cung cấp một số mô hình
đã được tiền huấn luyện. Ở đây, ta sử dụng mô hình ResNet-34 đã được
tiền huấn luyện. Do dữ liệu của cuộc thi là tập con của tập dữ liệu tiền
huấn luyện, ta đơn thuần sử dụng lại đầu vào của tầng đầu ra mô hình đã
được tiền huấn luyện làm đặc trưng được được trích xuất. Sau đó, ta có
thể thay thế tầng đầu ra gốc bằng một mạng đầu ra tùy biến cỡ nhỏ để
huấn luyện bao gồm hai tầng kết nối đầy đủ. Khác với thí nghiệm trong
:numref:`sec_fine_tuning`, ở đây ta không huấn luyện lại mô hình trích
xuất đặc trưng đã được tiền huấn luyện. Điều này giúp giảm thời gian
huấn luyện và bộ nhớ cần thiết để lưu trữ gradient của tham số mô hình.
.. raw:: html
Độc giả cần lưu ý, trong quá trình tăng cường ảnh, ta sử dụng giá trị
trung bình và độ lệch chuẩn của ba kênh RGB lấy từ toàn bộ dữ liệu
ImageNet để chuẩn hóa. Điều này giúp dữ liệu nhất quán với việc chuẩn
hóa của mô hình tiền huấn luyện.
.. code:: python
def get_net(devices):
finetune_net = gluon.model_zoo.vision.resnet34_v2(pretrained=True)
# Define a new output network
finetune_net.output_new = nn.HybridSequential(prefix='')
finetune_net.output_new.add(nn.Dense(256, activation='relu'))
# There are 120 output categories
finetune_net.output_new.add(nn.Dense(120))
# Initialize the output network
finetune_net.output_new.initialize(init.Xavier(), ctx=devices)
# Distribute the model parameters to the CPUs or GPUs used for computation
finetune_net.collect_params().reset_ctx(devices)
return finetune_net
.. raw:: html
Khi tính toán mất mát, đầu tiên ta sử dụng biến thành viên ``features``
để lấy đầu vào của tầng đầu ra trong mô hình được tiền huấn luyện làm
đặc trưng trích xuất. Sau đó, ta sử dụng đặc trưng này làm đầu vào cho
mạng đầu ra tùy biến cỡ nhỏ và tính toán đầu ra.
.. code:: python
loss = gluon.loss.SoftmaxCrossEntropyLoss()
def evaluate_loss(data_iter, net, devices):
l_sum, n = 0.0, 0
for features, labels in data_iter:
X_shards, y_shards = d2l.split_batch(features, labels, devices)
output_features = [net.features(X_shard) for X_shard in X_shards]
outputs = [net.output_new(feature) for feature in output_features]
ls = [loss(output, y_shard).sum() for output, y_shard
in zip(outputs, y_shards)]
l_sum += sum([float(l.sum()) for l in ls])
n += labels.size
return l_sum / n
.. raw:: html
Định nghĩa Hàm Huấn luyện
-------------------------
.. raw:: html
Ta sẽ lựa chọn mô hình và điều chỉnh siêu tham số dựa trên chất lượng mô
hình trên tập kiểm định. Hàm huấn luyện mô hình ``train`` chỉ huấn luyện
mạng đầu ra tùy biến cỡ nhỏ.
.. code:: python
def train(net, train_iter, valid_iter, num_epochs, lr, wd, devices, lr_period,
lr_decay):
# Only train the small custom output network
trainer = gluon.Trainer(net.output_new.collect_params(), 'sgd',
{'learning_rate': lr, 'momentum': 0.9, 'wd': wd})
num_batches, timer = len(train_iter), d2l.Timer()
animator = d2l.Animator(xlabel='epoch', xlim=[0, num_epochs],
legend=['train loss', 'valid loss'])
for epoch in range(num_epochs):
metric = d2l.Accumulator(2)
if epoch > 0 and epoch % lr_period == 0:
trainer.set_learning_rate(trainer.learning_rate * lr_decay)
for i, (features, labels) in enumerate(train_iter):
timer.start()
X_shards, y_shards = d2l.split_batch(features, labels, devices)
output_features = [net.features(X_shard) for X_shard in X_shards]
with autograd.record():
outputs = [net.output_new(feature) for feature in output_features]
ls = [loss(output, y_shard).sum() for output, y_shard
in zip(outputs, y_shards)]
for l in ls:
l.backward()
trainer.step(batch_size)
metric.add(sum([float(l.sum()) for l in ls]), labels.shape[0])
timer.stop()
if (i + 1) % (num_batches // 5) == 0:
animator.add(epoch + i / num_batches,
(metric[0] / metric[1], None))
if valid_iter is not None:
valid_loss = evaluate_loss(valid_iter, net, devices)
animator.add(epoch + 1, (None, valid_loss))
if valid_iter is not None:
print(f'train loss {metric[0] / metric[1]:.3f}, '
f'valid loss {valid_loss:.3f}')
else:
print(f'train loss {metric[0] / metric[1]:.3f}')
print(f'{metric[1] * num_epochs / timer.sum():.1f} examples/sec '
f'on {str(devices)}')
.. raw:: html
Huấn luyện và Kiểm định Mô hình
-------------------------------
.. raw:: html
Bây giờ, ta có thể huấn luyện và kiểm định mô hình. Các siêu tham số
dưới đây có thể được điều chỉnh: ``num_epochs``, ``lr_period`` và
``lr_decay``. Ví dụ, ta có thể tăng số lượng epoch. Do ``lr_period`` và
``lr_decay`` được thiết lập bằng 10 và 0.1, tốc độ học của thuật toán
tối ưu sẽ được nhân với 0.1 sau mỗi 10 epoch.
.. code:: python
devices, num_epochs, lr, wd = d2l.try_all_gpus(), 5, 0.01, 1e-4
lr_period, lr_decay, net = 10, 0.1, get_net(devices)
net.hybridize()
train(net, train_iter, valid_iter, num_epochs, lr, wd, devices, lr_period,
lr_decay)
.. parsed-literal::
:class: output
train loss 2.394, valid loss 2.422
389.2 examples/sec on [gpu(0)]
.. figure:: output_kaggle-dog_vn_094b6a_21_1.svg
.. raw:: html
Dự đoán trên tập Kiểm tra và Nộp Kết quả lên Kaggle
---------------------------------------------------
.. raw:: html
Sau khi thu được một thiết kế mô hình và các siêu tham số vừa ý, ta sử
dụng tất cả dữ liệu huấn luyện (bao gồm dữ liệu kiểm định) để huấn luyện
lại mô hình, sau đó thực hiện dự đoán trên tập kiểm tra. Chú ý rằng các
dự đoán được lấy từ mạng đầu ra mà ta đã huấn luyện.
.. code:: python
net = get_net(devices)
net.hybridize()
train(net, train_valid_iter, None, num_epochs, lr, wd, devices, lr_period,
lr_decay)
preds = []
for data, label in test_iter:
output_features = net.features(data.as_in_ctx(devices[0]))
output = npx.softmax(net.output_new(output_features))
preds.extend(output.asnumpy())
ids = sorted(os.listdir(
os.path.join(data_dir, 'train_valid_test', 'test', 'unknown')))
with open('submission.csv', 'w') as f:
f.write('id,' + ','.join(train_valid_ds.synsets) + '\n')
for i, output in zip(ids, preds):
f.write(i.split('.')[0] + ',' + ','.join(
[str(num) for num in output]) + '\n')
.. parsed-literal::
:class: output
train loss 2.332
386.6 examples/sec on [gpu(0)]
.. figure:: output_kaggle-dog_vn_094b6a_23_1.svg
.. raw:: html
Chạy đoạn mã trên sẽ sinh tệp “submission.csv”. Định dạng của tệp này
nhất quán với yêu cầu của cuộc thi Kaggle. Cách thức nộp kết quả tương
tự như trong :numref:`sec_kaggle_house`.
Tóm tắt
-------
.. raw:: html
- Ta có thể sử dụng mô hình đã được tiền huấn luyện trên tập dữ liệu
ImageNet để trích xuất đặc trưng và chỉ huấn luyện trên mạng đầu ra
tùy biến cỡ nhỏ.
- Điều này cho phép ta có thể thực hiện dự đoán trên tập con của tập dữ
liệu ImageNet với chi phí bộ nhớ và tính toán thấp hơn.
Bài tập
-------
.. raw:: html
1. Khi sử dụng toàn bộ dữ liệu Kaggle, bạn sẽ thu được kết quả như thế
nào khi tăng ``batch_size`` (kích thước batch) và ``num_epochs`` (số
lượng epoch)?
2. Bạn có đạt được kết quả tốt hơn nếu sử dụng mô hình đã được tiền huấn
luyện sâu hơn không?
3. Quét mã QR để tham gia thảo luận và trao đổi ý tưởng về các phương
pháp đã được sử dụng và kết quả thu được từ cộng đồng Kaggle. Bạn có
thể nghĩ ra một ý tưởng hay kỹ thuật tốt hơn không?
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
- Đỗ Trường Giang
- Nguyễn Văn Quang
- Phạm Hồng Vinh
- Nguyễn Văn Cường