.. raw:: html
.. raw:: html
.. raw:: html
.. _sec_lenet:
Mạng Nơ-ron Tích chập (LeNet)
=============================
.. raw:: html
Bây giờ ta đã sẵn sàng kết hợp tất cả các công cụ lại với nhau để triển
khai mạng nơ-ron tích chập hoàn chỉnh đầu tiên. Lần đầu làm việc với dữ
liệu ảnh, ta đã áp dụng một perceptron đa tầng
(:numref:`sec_mlp_scratch`) cho ảnh quần áo trong bộ dữ liệu
Fashion-MNIST. Mỗi ảnh trong Fashion-MNIST là một ma trận hai chiều có
kích thước :math:`28 \times 28`. Để tương thích với đầu vào dạng vector
một chiều với độ dài cố định của các perceptron đa tầng, đầu tiên ta
trải phẳng từng hình ảnh và thu được các vector có chiều dài 784, trước
khi xử lý chúng với một chuỗi các tầng kết nối đầy đủ.
.. raw:: html
Bây giờ đã có các tầng tích chập, ta có thể giữ nguyên ảnh đầu vào ở
dạng không gian hai chiều như ảnh gốc và xử lý chúng với một chuỗi các
tầng tích chập liên tiếp. Hơn nữa, vì ta đang sử dụng các tầng tích
chập, số lượng tham số cần thiết sẽ giảm đi đáng kể.
.. raw:: html
Trong phần này, chúng tôi sẽ giới thiệu một trong những mạng nơ-ron tích
chập được công bố đầu tiên. Ưu điểm của mạng tích chập được minh hoạ lần
đầu bởi Yann Lecun (lúc đó đang nghiên cứu tại AT&T Bell Labs) với ứng
dụng nhận dạng các số viết tay trong
ảnh-\ `LeNet5 `__. Vào những năm 90,
các thí nghiệm của các nhà nghiên cứu với LeNet đã đưa ra bằng chứng
thuyết phục đầu tiên về tính khả thi của việc huấn luyện mạng nơ-ron
tích chập bằng lan truyền ngược. Mô hình của họ đã đạt được kết quả rất
tốt (chỉ có Máy Vector Hỗ trợ — SVM tại thời điểm đó là có thể sánh
bằng) và đã được đưa vào sử dụng để nhận diện các chữ số khi xử lý tiền
gửi trong máy ATM. Một số máy ATM vẫn chạy các đoạn mã mà Yann và đồng
nghiệp Leon Bottou đã viết vào những năm 1990!
.. raw:: html
.. raw:: html
.. raw:: html
LeNet
-----
.. raw:: html
Một cách đơn giản, ta có thể xem LeNet gồm hai phần: (i) một khối các
tầng tích chập; và (ii) một khối các tầng kết nối đầy đủ. Trước khi đi
vào các chi tiết cụ thể, hãy quan sát tổng thể mô hình trong
:numref:`img_lenet`.
.. raw:: html
.. _img_lenet:
.. figure:: ../img/lenet.svg
Dòng dữ liệu trong LeNet 5. Đầu vào là một chữ số viết tay, đầu ra là
một xác suất đối với 10 kết quả khả thi.
.. raw:: html
Các đơn vị cơ bản trong khối tích chập là một tầng tích chập và một lớp
gộp trung bình theo sau (lưu ý rằng gộp cực đại hoạt động tốt hơn, nhưng
nó chưa được phát minh vào những năm 90). Tầng tích chập được sử dụng để
nhận dạng các mẫu không gian trong ảnh, chẳng hạn như các đường cạnh và
các bộ phận của vật thể, lớp gộp trung bình phía sau được dùng để giảm
số chiều. Khối tầng tích chập tạo nên từ việc xếp chồng các khối nhỏ gồm
hai đơn vị cơ bản này. Mỗi tầng tích chập sử dụng hạt nhân có kích thước
:math:`5\times 5` và xử lý mỗi đầu ra với một hàm kích hoạt sigmoid
(nhấn mạnh rằng ReLU hiện được biết là hoạt động đáng tin cậy hơn, nhưng
chưa được phát minh vào thời điểm đó). Tầng tích chập đầu tiên có 6 kênh
đầu ra và tầng tích chập thứ hai tăng độ sâu kênh hơn nữa lên 16.
.. raw:: html
Tuy nhiên, cùng với sự gia tăng số lượng kênh này, chiều cao và chiều
rộng lại giảm đáng kể. Do đó, việc tăng số lượng kênh đầu ra làm cho
kích thước tham số của hai tầng tích chập tương tự nhau. Hai lớp gộp
trung bình có kích thước :math:`2\times 2` và sải bước bằng 2 (điều này
có nghĩa là chúng không chồng chéo). Nói cách khác, lớp gộp giảm kích
thước của các biểu diễn còn *một phần tư* kích thước trước khi gộp.
.. raw:: html
.. raw:: html
.. raw:: html
Đầu ra của khối tích chập có kích thước được xác định bằng (kích thước
batch, kênh, chiều cao, chiều rộng). Trước khi chuyển đầu ra của khối
tích chập sang khối kết nối đầy đủ, ta phải trải phẳng từng mẫu trong
minibatch. Nói cách khác, ta biến đổi đầu vào 4D thành đầu vào 2D tương
thích với các tầng kết nối đầy đủ: nhắc lại, chiều thứ nhất là chỉ số
các mẫu trong minibatch và chiều thứ hai là biểu diễn vector phẳng của
mỗi mẫu. Khối tầng kết nối đầy đủ của LeNet có ba tầng kết nối đầy đủ,
với số lượng đầu ra lần lượt là 120, 84 và 10. Bởi vì ta đang thực hiện
bài toán phân loại, tầng đầu ra 10 chiều tương ứng với số lượng các lớp
đầu ra khả thi (10 chữ số từ 0 đến 9).
.. raw:: html
Để thực sự hiểu những gì diễn ra bên trong LeNet có thể đòi hỏi một chút
nỗ lực, tuy nhiên bạn có thể thấy bên dưới đây việc lập trình Lenet bằng
thư viện học sâu hiện đại rất đơn giản. Một lần nữa, ta sẽ dựa vào lớp
Sequential.
.. code:: python
from d2l import mxnet as d2l
from mxnet import autograd, gluon, init, np, npx
from mxnet.gluon import nn
npx.set_np()
net = nn.Sequential()
net.add(nn.Conv2D(channels=6, kernel_size=5, padding=2, activation='sigmoid'),
nn.AvgPool2D(pool_size=2, strides=2),
nn.Conv2D(channels=16, kernel_size=5, activation='sigmoid'),
nn.AvgPool2D(pool_size=2, strides=2),
# Dense will transform the input of the shape (batch size, channel,
# height, width) into the input of the shape (batch size,
# channel * height * width) automatically by default
nn.Dense(120, activation='sigmoid'),
nn.Dense(84, activation='sigmoid'),
nn.Dense(10))
.. raw:: html
So với mạng ban đầu, ta đã thay thế kích hoạt Gauss ở tầng cuối cùng
bằng một tầng kết nối đầy đủ thông thường mà thường dễ huấn luyện hơn
đáng kể. Ngoại trừ điểm đó, mạng này giống với định nghĩa gốc của
LeNet5.
.. raw:: html
Tiếp theo, ta hãy xem một ví dụ dưới đây. Như trong
:numref:`img_lenet_vert`, ta đưa vào mạng một mẫu đơn kênh kích thước
:math:`28 \times 28` và thực hiện một lượt truyền xuôi qua các tầng và
in kích thước đầu ra ở mỗi tầng để hiểu rõ những gì đang xảy ra bên
trong.
.. code:: python
X = np.random.uniform(size=(1, 1, 28, 28))
net.initialize()
for layer in net:
X = layer(X)
print(layer.name, 'output shape:\t', X.shape)
.. parsed-literal::
:class: output
conv0 output shape: (1, 6, 28, 28)
pool0 output shape: (1, 6, 14, 14)
conv1 output shape: (1, 16, 10, 10)
pool1 output shape: (1, 16, 5, 5)
dense0 output shape: (1, 120)
dense1 output shape: (1, 84)
dense2 output shape: (1, 10)
.. raw:: html
.. raw:: html
.. raw:: html
Xin hãy chú ý rằng, chiều cao và chiều rộng của biểu diễn sau mỗi tầng
trong toàn bộ khối tích chập sẽ giảm theo chiều sâu của mạng(so với
chiều cao và chiều rộng của biểu diễn ở tầng trước). Tầng tích chập đầu
tiên sử dụng một hạt nhân với chiều cao và chiều rộng là :math:`5` rồi
đệm thêm :math:`2` đơn vị điểm ảnh để giữ nguyên kích thước đầu vào.
Trong khi đó, tầng tích chập thứ hai cũng dùng cùng một hạt nhân với
kích thước là :math:`5 \times 5` mà không có sử dụng giá trị đệm thêm
vào, dẫn đến việc chiều cao và chiều rộng giảm đi 4 đơn vị điểm ảnh.
Ngoài ra, mỗi tầng gộp sẽ làm giảm đi một nửa chiều cao và chiều rộng
của đặc trưng ánh xạ đầu vào. Tuy nhiên, khi chúng ta đi theo chiều sâu
của mạng, số kênh sẽ tăng lần lượt theo từng tầng. Từ 1 kênh của dữ liệu
đầu vào lên tới 6 kênh sau tầng tích chập thứ nhất và 16 kênh sau tầng
tích chập thứ hai. Sau đó,giảm số chiều lần lượt qua từng tầng kết nối
đầy đủ đến khi trả về một đầu ra có kích thước bằng số lượng lớp của
hình ảnh.
.. raw:: html
.. _img_lenet_vert:
.. figure:: ../img/lenet-vert.svg
Kí hiệu vắn tắt cho mô hình LeNet5
.. raw:: html
.. raw:: html
.. raw:: html
Thu thập Dữ liệu và Huấn luyện
------------------------------
.. raw:: html
Sau khi xây dựng xong mô hình, chúng ta sẽ thực hiện một số thử nghiệm
để xem chất lượng của mô hình LeNet. Tập dữ liệu Fashion-MNIST sẽ được
dùng trong ví dụ này. Việc phân loại tập Fashion-MNIST sẽ khó hơn so với
tập MNIST gốc mặc dù chúng đều chứa các ảnh có cùng kích thước
:math:`28\times28`.
.. code:: python
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)
.. raw:: html
Dù mạng tích chập có thể có số lượng tham số không lớn, chúng vẫn tiêu
tốn nhiều tài nguyên tính toán hơn so với perceptron sâu đa tầng. Vì
vậy, nếu có sẵn GPU, thì đây là thời điểm thích hợp để dùng nó nhằm tăng
tốc quá trình huấn luyện.
.. raw:: html
Để đánh giá mô hình, chúng ta cần điều chỉnh một chút hàm
``evaluate_accuracy`` đã mô tả ở phần :numref:`sec_softmax_scratch`.
Vì toàn bộ tập dữ liệu đang nằm trên CPU, ta cần sao chép nó lên GPU
trước khi thực hiện tính toán với mô hình. Việc này được thực hiện thông
qua việc gọi hàm ``as_in_ctx`` được mô tả ở phần
:numref:`sec_use_gpu`.
.. raw:: html
.. raw:: html
.. code:: python
# Saved in the d2l package for later use
def evaluate_accuracy_gpu(net, data_iter, ctx=None):
if not ctx: # Query the first device the first parameter is on
ctx = list(net.collect_params().values())[0].list_ctx()[0]
metric = d2l.Accumulator(2) # num_corrected_examples, num_examples
for X, y in data_iter:
X, y = X.as_in_ctx(ctx), y.as_in_ctx(ctx)
metric.add(d2l.accuracy(net(X), y), y.size)
return metric[0]/metric[1]
.. raw:: html
Chúng ta cũng cần phải cập nhật hàm huấn luyện để mô hình có thể chạy
được trên GPU. Không giống hàm ``train_epoch_ch3`` được định nghĩa ở
phần :numref:`sec_softmax_scratch`, giờ chúng ta cần chuyển từng batch
dữ liệu tới ngữ cảnh được chỉ định (hy vọng là GPU thay vì CPU) trước
khi thực hiện lượt truyền xuôi và lượt truyền ngược.
.. raw:: html
Hàm huấn luyện ``train_ch6`` khá giống với hàm huấn luyện ``train_ch3``
đã được định nghĩa tại :numref:`sec_softmax_scratch`. Để đơn giản khi
làm việc với mạng nơ-ron có tới hàng chục tầng, hàm ``train_ch6`` chỉ hỗ
trợ các mô hình được xây dựng bằng thư viện Gluon. Để khởi tạo bộ tham
số của mô hình trên thiết bị đã được chỉ định bởi ``ctx``, ta sẽ sử dụng
bộ khởi tạo Xavier. Ta vẫn sử dụng hàm mất mát entropy chéo và thuật
toán huấn luyện là phương pháp hạ gradient ngẫu nhiên theo minibatch.
Với mỗi epoch tốn khoảng hàng chục giây để chạy, ta sẽ vẽ đường biểu
diễn giá trị mất mát huấn luyện với nhiều giá trị chi tiết hơn.
.. code:: python
# Saved in the d2l package for later use
def train_ch6(net, train_iter, test_iter, num_epochs, lr, ctx=d2l.try_gpu()):
net.initialize(force_reinit=True, ctx=ctx, init=init.Xavier())
loss = gluon.loss.SoftmaxCrossEntropyLoss()
trainer = gluon.Trainer(net.collect_params(),
'sgd', {'learning_rate': lr})
animator = d2l.Animator(xlabel='epoch', xlim=[0, num_epochs],
legend=['train loss', 'train acc', 'test acc'])
timer = d2l.Timer()
for epoch in range(num_epochs):
metric = d2l.Accumulator(3) # train_loss, train_acc, num_examples
for i, (X, y) in enumerate(train_iter):
timer.start()
# Here is the only difference compared to train_epoch_ch3
X, y = X.as_in_ctx(ctx), y.as_in_ctx(ctx)
with autograd.record():
y_hat = net(X)
l = loss(y_hat, y)
l.backward()
trainer.step(X.shape[0])
metric.add(l.sum(), d2l.accuracy(y_hat, y), X.shape[0])
timer.stop()
train_loss, train_acc = metric[0]/metric[2], metric[1]/metric[2]
if (i+1) % 50 == 0:
animator.add(epoch + i/len(train_iter),
(train_loss, train_acc, None))
test_acc = evaluate_accuracy_gpu(net, test_iter)
animator.add(epoch+1, (None, None, test_acc))
print('loss %.3f, train acc %.3f, test acc %.3f' % (
train_loss, train_acc, test_acc))
print('%.1f examples/sec on %s' % (metric[2]*num_epochs/timer.sum(), ctx))
.. raw:: html
Bây giờ, chúng ta hãy bắt đầu huấn luyện mô hình.
.. code:: python
lr, num_epochs = 0.9, 10
train_ch6(net, train_iter, test_iter, num_epochs, lr)
.. parsed-literal::
:class: output
loss 0.473, train acc 0.822, test acc 0.799
18915.1 examples/sec on gpu(0)
.. figure:: output_lenet_vn_7e6b1f_11_1.svg
.. raw:: html
.. raw:: html
.. raw:: html
Tóm tắt
-------
.. raw:: html
- Mạng nơ-ron tích chập (gọi tắt là ConvNet) là một mạng sử dụng các
tầng tích chập.
- Trong ConvNet, ta xen kẽ các phép tích chập, các hàm phi tuyến và các
phép gộp.
- Độ phân giải được giảm xuống trước khi tạo một đầu ra thông qua một
(hoặc nhiều) tầng kết nối dày đặc.
- LeNet là mạng ConvNet đầu tiên được triển khai thành công.
.. raw:: html
Bài tập
-------
.. raw:: html
1. Điều gì sẽ xảy ra nếu ta thay thế phép gộp trung bình bằng phép gộp
cực đại?
2. Thử cải thiện độ chính xác dự đoán dựa trên LeNet bằng cách: \*
Điều chỉnh kích thước cửa sổ tích chập. \* Điều chỉnh số lượng
kênh đầu ra. \* Điều chỉnh hàm kích hoạt (ReLU?). \* Điều
chỉnh số lượng các tầng tích chập. \* Điều chỉnh số lượng các
tầng kết nối đầy đủ. \* Điều chỉnh tốc độ học và các chi tiết
huấn luyện khác (phương thức khởi tạo, số lượng epoch, v.v.)
3. Thử sử dụng mạng đã cải tiến ở phần 3 với tập dữ liệu MNIST ban đầu.
4. Hiển thị các giá trị kích hoạt của tầng thứ nhất và tầng thứ hai của
LeNet với các đầu vào khác nhau (ví dụ: áo len, áo khoác).
.. raw:: html
.. raw:: html
Thảo luận
---------
- `Tiếng Anh `__
- `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ần Yến Thy
- Nguyễn Văn Cường
- Phạm Minh Đức
- Lê Khắc Hồng Phúc
- Đinh Đắc
- Phạm Hồng Vinh
- Nguyễn Duy Du