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