.. raw:: html
.. raw:: html
.. raw:: html
.. _sec_mlp_scratch:
Lập trình Perceptron Đa tầng từ đầu
===================================
.. raw:: html
Chúng ta đã mô tả perceptron đa tầng (MLPs) ở dạng toán học, giờ hãy
cùng thử tự lập trình một mạng như vậy xem sao.
.. code:: python
from d2l import mxnet as d2l
from mxnet import gluon, np, npx
npx.set_np()
.. raw:: html
Để so sánh với kết quả đã đạt được trước đó bằng hồi quy (tuyến tính)
softmax (:numref:`sec_softmax_scratch`), chúng ta sẽ tiếp tục sử dụng
tập dữ liệu phân loại ảnh Fashion-MNIST.
.. code:: python
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
.. raw:: html
Khởi tạo Tham số Mô hình
------------------------
.. raw:: html
Nhắc lại rằng Fashion-MNIST gồm có :math:`10` lớp, mỗi ảnh là một lưới
có :math:`28 \times 28 = 784` điểm ảnh (đen và trắng). Chúng ta sẽ lại
(tạm thời) bỏ qua mối liên hệ về mặt không gian giữa các điểm ảnh, khi
đó ta có thể coi nó đơn giản như một tập dữ liệu phân loại với
:math:`784` đặc trưng đầu vào và :math:`10` lớp. Để bắt đầu, chúng ta sẽ
lập trình một mạng MLP chỉ có một tầng ẩn với :math:`256` nút ẩn. Lưu ý
rằng ta có thể coi cả hai đại lượng này là các *siêu tham số* và ta nên
thiết lập giá trị cho chúng dựa vào chất lượng trên tập kiểm định. Thông
thường, chúng ta sẽ chọn độ rộng của các tầng là các lũy thừa bậc
:math:`2` để giúp việc tính toán hiệu quả hơn do cách mà bộ nhớ được cấp
phát và địa chỉ hóa ở phần cứng.
.. raw:: html
Chúng ta sẽ lại biểu diễn các tham số bằng một vài ``ndarray``. Lưu ý
rằng *với mỗi tầng*, ta luôn phải giữ một ma trận trọng số và một vector
chứa hệ số điều chỉnh. Và như mọi khi, ta gọi hàm ``attach_grad`` để cấp
phát bộ nhớ cho gradient (của hàm mất mát) theo các tham số này.
.. code:: python
num_inputs, num_outputs, num_hiddens = 784, 10, 256
W1 = np.random.normal(scale=0.01, size=(num_inputs, num_hiddens))
b1 = np.zeros(num_hiddens)
W2 = np.random.normal(scale=0.01, size=(num_hiddens, num_outputs))
b2 = np.zeros(num_outputs)
params = [W1, b1, W2, b2]
for param in params:
param.attach_grad()
.. raw:: html
.. raw:: html
.. raw:: html
Hàm Kích hoạt
-------------
.. raw:: html
Để đảm bảo rằng ta biết mọi thứ hoạt động như thế nào, chúng ta sẽ tự
lập trình hàm kích hoạt ReLU bằng cách sử dụng hàm ``maximum`` thay vì
gọi trực tiếp hàm ``npx.relu``.
.. code:: python
def relu(X):
return np.maximum(X, 0)
.. raw:: html
Mô hình
-------
.. raw:: html
Vì ta đang bỏ qua mối liên hệ về mặt không gian giữa các điểm ảnh, ta
``reshape`` mỗi bức ảnh 2D thành một vector phẳng có độ dài
``num_inputs``. Cuối cùng, ta có được mô hình chỉ với một vài dòng mã
nguồn.
.. code:: python
def net(X):
X = X.reshape(-1, num_inputs)
H = relu(np.dot(X, W1) + b1)
return np.dot(H, W2) + b2
.. raw:: html
.. raw:: html
.. raw:: html
Hàm mất mát
-----------
.. raw:: html
Để đảm bảo tính ổn định số học (và cũng bởi ta đã lập trình hàm softmax
từ đầu ở :numref:`sec_softmax_scratch`), ta sẽ tận dụng luôn các hàm
số đã tích hợp sẵn của Gluon để tính softmax và mất mát entropy chéo.
Nhắc lại phần thảo luận của chúng ta trước đó về vấn đề rắc rối này
(:numref:`sec_mlp`). Chúng tôi khuyến khích bạn đọc quan tâm hãy thử
kiểm tra mã nguồn trong ``mxnet.gluon.loss.SoftmaxCrossEntropyLoss`` để
hiểu thêm về cách lập trình chi tiết.
.. code:: python
loss = gluon.loss.SoftmaxCrossEntropyLoss()
.. raw:: html
.. raw:: html
.. raw:: html
Huấn luyện
----------
.. raw:: html
Thật may, vòng lặp huấn luyện của MLP giống hệt với vòng lặp của hồi quy
softmax. Tận dụng gói ``d2l``, ta gọi hàm ``train_ch3`` (xem
:numref:`sec_softmax_scratch`), đặt số epoch bằng :math:`10` và tốc độ
học bằng :math:`0.5`
.. code:: python
num_epochs, lr = 10, 0.5
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs,
lambda batch_size: d2l.sgd(params, lr, batch_size))
.. figure:: output_mlp-scratch_vn_532965_13_0.svg
.. raw:: html
Để đánh giá mô hình sau khi học xong, chúng ta sẽ áp dụng nó vào dữ liệu
kiểm tra.
.. code:: python
d2l.predict_ch3(net, test_iter)
.. figure:: output_mlp-scratch_vn_532965_15_0.svg
.. raw:: html
Kết quả này tốt hơn một chút so với kết quả trước đây của các mô hình
tuyến tính và điều này cho thấy chúng ta đang đi đúng hướng.
.. raw:: html
Tóm tắt
-------
.. raw:: html
Chúng ta đã thấy việc lập trình một MLP đơn giản khá là dễ dàng, ngay cả
khi phải làm thủ công. Tuy vậy, với một số lượng tầng lớn, việc này có
thể sẽ trở nên rắc rối (ví dụ như đặt tên và theo dõi các tham số của mô
hình, v.v.).
.. raw:: html
Bài tập
-------
.. raw:: html
1. Thay đổi giá trị của siêu tham số ``num_hiddens`` và quan sát xem nó
ảnh hưởng như thế nào tới kết quả. Giữ nguyên các siêu tham số khác,
xác định giá trị tốt nhất của siêu tham số này.
2. Thử thêm vào một tầng ẩn và quan sát xem nó ảnh hưởng như thế nào tới
kết quả.
3. Việc thay đổi tốc độ học ảnh hưởng như thế nào tới kết quả? Giữ
nguyên kiến trúc mô hình và các siêu tham số khác (bao gồm cả số
lượng epoch), tốc độ học nào cho kết quả tốt nhất?
4. Kết quả tốt nhất mà bạn đạt được khi tối ưu hóa tất cả các tham số,
gồm tốc độ học, số lượng vòng lặp, số lượng tầng ẩn, số lượng các nút
ẩn của mỗi tầng là bao nhiêu?
5. Giải thích tại sao việc phải xử lý nhiều siêu tham số lại gây ra
nhiều khó khăn hơn.
6. Đâu là chiến lược thông minh nhất bạn có thể nghĩ ra để tìm kiếm giá
trị cho nhiều siêu tham số?
.. raw:: html
.. 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
- Nguyễn Văn Tâm
- Phạm Hồng Vinh
- Vũ Hữu Tiệp
- Nguyễn Duy Du
- Phạm Minh Đức