.. raw:: html .. raw:: html .. raw:: html .. _sec_backprop: Lan truyền xuôi, Lan truyền ngược và Đồ thị tính toán ===================================================== .. raw:: html Cho đến lúc này, ta đã huấn luyện các mô hình với giải thuật hạ gradient ngẫu nhiên theo minibatch. Tuy nhiên, khi lập trình thuật toán, ta mới chỉ bận tâm đến các phép tính trong quá trình *lan truyền xuôi* qua mô hình. Khi cần tính gradient, ta mới chỉ đơn giản gọi hàm ``backward`` và mô-đun ``autograd`` sẽ lo các chi tiết tính toán. .. raw:: html Việc tính toán gradient tự động sẽ giúp công việc lập trình các thuật toán học sâu được đơn giản hóa đi rất nhiều. Trước đây, khi chưa có công cụ tính vi phân tự động, ngay cả khi ta chỉ thay đổi một chút các mô hình phức tạp, các đạo hàm rắc rối cũng cần phải được tính lại một cách thủ công. Điều đáng ngạc nhiên là các bài báo học thuật thường có các công thức cập nhật mô hình dài hàng trang giấy. Vậy nên dù vẫn phải tiếp tục dựa vào ``autograd`` để có thể tập trung vào những phần thú vị của học sâu, bạn vẫn nên *nắm* rõ thay vì chỉ hiểu một cách hời hợt cách tính gradient nếu bạn muốn tiến xa hơn. .. raw:: html Trong mục này, ta sẽ đi sâu vào chi tiết của lan truyền ngược (thường được gọi là *backpropagation* hoặc *backprop*). Ta sẽ sử dụng một vài công thức toán học cơ bản và đồ thị tính toán để giải thích một cách chi tiết cách thức hoạt động cũng như cách lập trình các kỹ thuật này. Và để bắt đầu, ta sẽ tập trung giải trình một perceptron đa tầng gồm ba tầng (một tầng ẩn) đi kèm với suy giảm trọng số (điều chuẩn :math:`\ell_2`). .. raw:: html .. raw:: html .. raw:: html Lan truyền Xuôi --------------- .. raw:: html Lan truyền xuôi là quá trình tính toán cũng như lưu trữ các biến trung gian (bao gồm cả đầu ra) của mạng nơ-ron theo thứ tự từ tầng đầu vào đến tầng đầu ra. Bây giờ ta sẽ thực hiện qua từng bước trong cơ chế vận hành của mạng nơ-ron sâu có một tầng ẩn. Điều này nghe có vẻ tẻ nhạt nhưng theo như cách nói dân giã, bạn phải “tập đi trước khi tập chạy”. .. raw:: html Để đơn giản hóa vấn đề, ta giả sử mẫu đầu vào là :math:`\mathbf{x}\in \mathbb{R}^d` và tầng ẩn của ta không có hệ số điều chỉnh. Ở đây biến trung gian là: .. math:: \mathbf{z}= \mathbf{W}^{(1)} \mathbf{x}, .. raw:: html trong đó :math:`\mathbf{W}^{(1)} \in \mathbb{R}^{h \times d}` là tham số trọng số của tầng ẩn. Sau khi đưa biến trung gian :math:`\mathbf{z}\in \mathbb{R}^h` qua hàm kích hoạt :math:`\phi`, ta thu được vector kích hoạt ẩn với :math:`h` phần tử, .. math:: \mathbf{h}= \phi (\mathbf{z}). .. raw:: html Biến ẩn :math:`\mathbf{h}` cũng là một biến trung gian. Giả sử tham số của tầng đầu ra chỉ gồm trọng số :math:`\mathbf{W}^{(2)} \in \mathbb{R}^{q \times h}`, ta sẽ thu được một vector với :math:`q` phần tử ở tầng đầu ra: .. math:: \mathbf{o}= \mathbf{W}^{(2)} \mathbf{h}. .. raw:: html Giả sử hàm mất mát là :math:`l` và nhãn của mẫu là :math:`y`, ta có thể tính được lượng mất mát cho một mẫu dữ liệu duy nhất, .. math:: L = l(\mathbf{o}, y). .. raw:: html Theo định nghĩa của điều chuẩn :math:`\ell_2` với siêu tham số :math:`\lambda`, lượng điều chuẩn là: .. math:: s = \frac{\lambda}{2} \left(\|\mathbf{W}^{(1)}\|_F^2 + \|\mathbf{W}^{(2)}\|_F^2\right), .. raw:: html trong đó chuẩn Frobenius của ma trận chỉ đơn giản là chuẩn :math:`L_2` của vector thu được sau khi trải phẳng ma trận. Cuối cùng, hàm mất mát được điều chuẩn của mô hình trên một mẫu dữ liệu cho trước là: .. math:: J = L + s. .. raw:: html Ta sẽ bàn thêm về *hàm mục tiêu* :math:`J` ở phía dưới. .. raw:: html .. raw:: html .. raw:: html .. raw:: html .. raw:: html Đồ thị Tính toán của Lan truyền Xuôi ------------------------------------ .. raw:: html Vẽ đồ thị tính toán giúp chúng ta hình dung được sự phụ thuộc giữa các toán tử và các biến trong quá trình tính toán. :numref:`fig_forward` thể hiện đồ thị tương ứng với mạng nơ-ron đã miêu tả ở trên. Góc trái dưới biểu diễn đầu vào trong khi góc phải trên biểu diễn đầu ra. Lưu ý rằng hướng của các mũi tên (thể hiện luồng dữ liệu) chủ yếu là đi qua phải và hướng lên trên. .. raw:: html .. _fig_forward: .. figure:: ../img/forward.svg Đồ thị tính toán .. raw:: html Lan truyền Ngược ---------------- .. raw:: html | Lan truyền ngược là phương pháp tính gradient của các tham số mạng nơ-ron. Nói một cách đơn giản, phương thức này duyệt qua mạng nơ-ron theo chiều ngược lại, từ đầu ra đến đầu vào, tuân theo quy tắc dây chuyền trong giải tích. | Thuật toán lan truyền ngược lưu trữ các biến trung gian (là các đạo hàm riêng) cần thiết trong quá trình tính toán gradient theo các tham số. Giả sử chúng ta có hàm :math:`\mathsf{Y}=f(\mathsf{X})` và :math:`\mathsf{Z}=g(\mathsf{Y}) = g \circ f(\mathsf{X})`, trong đó đầu vào và đầu ra :math:`\mathsf{X}, \mathsf{Y}, \mathsf{Z}` là các tensor có kích thước bất kỳ. Bằng cách sử dụng quy tắc dây chuyền, chúng ta có thể tính đạo hàm của :math:`\mathsf{Z}` theo :math:`\mathsf{X}` như sau: .. math:: \frac{\partial \mathsf{Z}}{\partial \mathsf{X}} = \text{prod}\left(\frac{\partial \mathsf{Z}}{\partial \mathsf{Y}}, \frac{\partial \mathsf{Y}}{\partial \mathsf{X}}\right). .. raw:: html Ở đây, chúng ta sử dụng toán tử :math:`\text{prod}` để nhân các đối số sau khi các phép tính cần thiết như là chuyển vị và hoán đổi đã được thực hiện. Với vector, điều này khá đơn giản: nó chỉ đơn thuần là phép nhân ma trận. Với các tensor nhiều chiều thì sẽ có các phương án tương ứng phù hợp. Toán tử :math:`\text{prod}` sẽ đơn giản hoá việc ký hiệu. .. raw:: html Các tham số của mạng nơ-ron đơn giản với một tầng ẩn là :math:`\mathbf{W}^{(1)}` và :math:`\mathbf{W}^{(2)}`. Mục đích của lan truyền ngược là để tính gradient :math:`\partial J/\partial \mathbf{W}^{(1)}` và :math:`\partial J/\partial \mathbf{W}^{(2)}`. Để làm được điều này, ta áp dụng quy tắc dây chuyền và lần lượt tính gradient của các biến trung gian và tham số. Các phép tính trong lan truyền ngược có thứ tự ngược lại so với các phép tính trong lan truyền xuôi, bởi ta muốn bắt đầu từ kết quả của đồ thị tính toán rồi dần đi tới các tham số. Bước đầu tiên đó là tính gradient của hàm mục tiêu :math:`J=L+s` theo mất mát :math:`L` và điều chuẩn :math:`s`. .. math:: \frac{\partial J}{\partial L} = 1 \; \text{và} \; \frac{\partial J}{\partial s} = 1. .. raw:: html Tiếp theo, ta tính gradient của hàm mục tiêu theo các biến của lớp đầu ra :math:`\mathbf{o}`, sử dụng quy tắc dây chuyền. .. math:: \frac{\partial J}{\partial \mathbf{o}} = \text{prod}\left(\frac{\partial J}{\partial L}, \frac{\partial L}{\partial \mathbf{o}}\right) = \frac{\partial L}{\partial \mathbf{o}} \in \mathbb{R}^q. .. raw:: html Kế tiếp, ta tính gradient của điều chuẩn theo cả hai tham số. .. math:: \frac{\partial s}{\partial \mathbf{W}^{(1)}} = \lambda \mathbf{W}^{(1)} \; \text{và} \; \frac{\partial s}{\partial \mathbf{W}^{(2)}} = \lambda \mathbf{W}^{(2)}. .. raw:: html .. raw:: html .. raw:: html Bây giờ chúng ta có thể tính gradient :math:`\partial J/\partial \mathbf{W}^{(2)} \in \mathbb{R}^{q \times h}` của các tham số mô hình gần nhất với tầng đầu ra. Áp dụng quy tắc dây chuyền, ta có: .. math:: \frac{\partial J}{\partial \mathbf{W}^{(2)}} = \text{prod}\left(\frac{\partial J}{\partial \mathbf{o}}, \frac{\partial \mathbf{o}}{\partial \mathbf{W}^{(2)}}\right) + \text{prod}\left(\frac{\partial J}{\partial s}, \frac{\partial s}{\partial \mathbf{W}^{(2)}}\right) = \frac{\partial J}{\partial \mathbf{o}} \mathbf{h}^\top + \lambda \mathbf{W}^{(2)}. .. raw:: html Để tính được gradient theo :math:`\mathbf{W}^{(1)}` ta cần tiếp tục lan truyền ngược từ tầng đầu ra đến các tầng ẩn. Gradient theo các đầu ra của tầng ẩn :math:`\partial J/\partial \mathbf{h} \in \mathbb{R}^h` được tính như sau: .. math:: \frac{\partial J}{\partial \mathbf{h}} = \text{prod}\left(\frac{\partial J}{\partial \mathbf{o}}, \frac{\partial \mathbf{o}}{\partial \mathbf{h}}\right) = {\mathbf{W}^{(2)}}^\top \frac{\partial J}{\partial \mathbf{o}}. .. raw:: html Vì hàm kích hoạt :math:`\phi` áp dụng cho từng phần tử, việc tính gradient :math:`\partial J/\partial \mathbf{z} \in \mathbb{R}^h` của biến trung gian :math:`\mathbf{z}` cũng yêu cầu sử dụng phép nhân theo từng phần tử, kí hiệu bởi :math:`\odot`. .. math:: \frac{\partial J}{\partial \mathbf{z}} = \text{prod}\left(\frac{\partial J}{\partial \mathbf{h}}, \frac{\partial \mathbf{h}}{\partial \mathbf{z}}\right) = \frac{\partial J}{\partial \mathbf{h}} \odot \phi'\left(\mathbf{z}\right). .. raw:: html Cuối cùng, ta có thể tính gradient :math:`\partial J/\partial \mathbf{W}^{(1)} \in \mathbb{R}^{h \times d}` của các tham số mô hình gần nhất với tầng đầu vào. Theo quy tắc dây chuyền, ta có .. math:: \frac{\partial J}{\partial \mathbf{W}^{(1)}} = \text{prod}\left(\frac{\partial J}{\partial \mathbf{z}}, \frac{\partial \mathbf{z}}{\partial \mathbf{W}^{(1)}}\right) + \text{prod}\left(\frac{\partial J}{\partial s}, \frac{\partial s}{\partial \mathbf{W}^{(1)}}\right) = \frac{\partial J}{\partial \mathbf{z}} \mathbf{x}^\top + \lambda \mathbf{W}^{(1)}. .. raw:: html .. raw:: html .. raw:: html .. raw:: html .. raw:: html Huấn luyện một Mô hình ---------------------- .. raw:: html | Khi huấn luyện các mạng nơ-ron, lan truyền xuôi và lan truyền ngược phụ thuộc lẫn nhau. Cụ thể với lan truyền xuôi, ta duyệt đồ thị tính toán theo hướng của các quan hệ phụ thuộc và tính tất cả các biến trên đường đi. Những biến này sau đó được sử dụng trong lan truyền ngược khi thứ tự tính toán trên đồ thị bị đảo ngược lại. Hệ quả là ta cần lưu trữ các giá trị trung gian cho đến khi lan truyền ngược hoàn tất. Đây cũng chính là một trong những lý do khiến lan truyền ngược yêu cầu nhiều bộ nhớ hơn đáng kể so với khi chỉ cần đưa ra dự đoán. | Ta tính các tensor gradient và giữ các biến trung gian lại để sử dụng trong quy tắc dây chuyền. Việc huấn luyện trên các minibatch chứa nhiều mẫu, do đó cần lưu trữ nhiều giá trị kích hoạt trung gian hơn cũng là một lý do khác. .. raw:: html Tóm tắt ------- .. raw:: html - Lan truyền xuôi lần lượt tính và lưu trữ các biến trung gian từ tầng đầu vào đến tầng đầu ra trong đồ thị tính toán được định nghĩa bởi mạng nơ-ron. - Lan truyền ngược lần lượt tính và lưu trữ các gradient của biến trung gian và tham số mạng nơ-ron theo chiều ngược lại. - Khi huấn luyện các mô hình học sâu, lan truyền xuôi và lan truyền ngược phụ thuộc lẫn nhau. - Việc huấn luyện cần nhiều bộ nhớ lưu trữ hơn đáng kể so với việc dự đoán. .. raw:: html Bài tập ------- .. raw:: html 1. Giả sử đầu vào :math:`\mathbf{x}` của hàm số vô hướng :math:`f` là ma trận :math:`n \times m`. Gradient của :math:`f` theo :math:`\mathbf{x}` có chiều là bao nhiêu? 2. Thêm một hệ số điều chỉnh vào tầng ẩn của mô hình được mô tả ở trên. - Vẽ đồ thị tính toán tương ứng. - Tìm các phương trình cho quá trình lan truyền xuôi và lan truyền ngược. 3. Tính lượng bộ nhớ mà mô hình được mô tả ở chương này sử dụng lúc huấn luyện và lúc dự đoán. 4. Giả sử bạn muốn tính đạo hàm *bậc hai*. Điều gì sẽ xảy ra với đồ thị tính toán? Hãy ước tính thời gian hoàn thành quá trình này? 5. Giả sử rằng đồ thị tính toán trên là quá sức với GPU của bạn. - Bạn có thể phân vùng nó trên nhiều GPU không? - Ưu điểm và nhược điểm của việc huấn luyện với một minibatch nhỏ hơn là gì? .. 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 Duy Du - Lý Phi Long - Lê Khắc Hồng Phúc - Phạm Minh Đức - Nguyễn Lê Quang Nhật - Phạm Ngọc Bảo Anh