- Lan truyền ngược (backpropagation) là cốt lõi của việc huấn luyện mạng nơ-ron, nhưng nếu không hiểu cơ chế hoạt động bên trong, có thể phát sinh những lỗi ngoài dự đoán do cấu trúc “trừu tượng hóa bị rò rỉ (leaky abstraction)”
- Các hàm kích hoạt sigmoid hoặc tanh có thể khiến việc huấn luyện dừng lại do tiêu biến gradient (vanishing gradient) nếu khởi tạo trọng số sai
- ReLU có thể gây ra hiện tượng ReLU chết (dead ReLU), trong đó nơ-ron bị vô hiệu hóa vĩnh viễn khi đầu vào nhỏ hơn hoặc bằng 0
- Trong RNN, việc lặp lại phép nhân ma trận có thể gây ra bùng nổ gradient (exploding gradient); để tránh điều này cần dùng gradient clipping hoặc LSTM
- Nếu không hiểu cách lan truyền ngược hoạt động, thì dù framework có tự động xử lý, khả năng debug và cải thiện mô hình vẫn sẽ suy giảm đáng kể
Sự cần thiết phải hiểu lan truyền ngược
- Trong khóa học CS231n của Stanford, bài tập được thiết kế để sinh viên tự triển khai lan truyền thuận và lan truyền ngược
- Một số sinh viên phàn nàn rằng điều này không cần thiết vì các framework như TensorFlow đã tự động tính lan truyền ngược
- Tuy nhiên, lan truyền ngược không phải là một sự trừu tượng hóa hoàn chỉnh mà là một sự trừu tượng hóa bị rò rỉ; nếu không hiểu hoạt động bên trong, sẽ rất khó xác định nguyên nhân khiến việc huấn luyện thất bại
- Thái độ đơn giản cho rằng “framework sẽ tự lo hết” dẫn đến suy giảm năng lực thiết kế mô hình và debug
Tiêu biến gradient trong sigmoid
- Các hàm phi tuyến sigmoid hoặc tanh sẽ rơi vào trạng thái bão hòa (saturation) khi giá trị đầu vào lớn, khiến đầu ra tiến gần 0 hoặc 1
- Lúc này gradient cục bộ z(1-z)* trở thành 0, khiến sự lan truyền gradient bị chặn trong quá trình lan truyền ngược
- Gradient lớn nhất của sigmoid là 0.25, nên sau mỗi lần đi qua, tín hiệu giảm xuống còn 1/4 hoặc ít hơn
- Kết quả là tốc độ học của các tầng thấp hơn chậm hơn rõ rệt so với các tầng phía trên
- Vì vậy, khi dùng các tầng sigmoid, cần đặc biệt chú ý đến khởi tạo trọng số và tiền xử lý dữ liệu
Vấn đề ReLU chết
- ReLU là hàm đưa đầu ra về 0 khi đầu vào nhỏ hơn hoặc bằng 0
- Nếu đầu ra của nơ-ron bằng 0 ở bước lan truyền thuận, thì khi lan truyền ngược gradient cũng bằng 0, khiến nơ-ron đó bị vô hiệu hóa vĩnh viễn
- Trong quá trình huấn luyện, cập nhật trọng số quá lớn hoặc learning rate cao có thể khiến nơ-ron bị kẹt ở “trạng thái chết”
- Cũng có trường hợp sau khi huấn luyện, một phần đáng kể nơ-ron trong toàn bộ mạng chỉ xuất ra 0
- Vì vậy khi dùng ReLU, điều chỉnh learning rate và chiến lược khởi tạo là rất quan trọng
Bùng nổ gradient trong RNN
- Trong RNN đơn giản, cùng một ma trận trạng thái ẩn (Whh) được nhân lặp đi lặp lại ở mỗi bước thời gian
- Khi lan truyền ngược, gradient sẽ hội tụ về 0 hoặc tăng vô hạn tùy theo độ lớn của eigenvalue của ma trận này
- |b| < 1 thì xảy ra tiêu biến gradient, còn |b| > 1 thì xảy ra bùng nổ gradient
- Để ngăn điều này, người ta thường áp dụng gradient clipping hoặc dùng kiến trúc LSTM
Ví dụ clipping sai trong mã DQN
- Trong một bản triển khai DQN dựa trên TensorFlow, có đoạn mã dùng
tf.clip_by_value để clip trực tiếp delta (sai số Q)
- Cách này khiến khi delta vượt ra ngoài phạm vi, gradient trở thành 0 và việc huấn luyện dừng lại
- Mục tiêu thực sự là gradient clipping, nên thay vào đó cần dùng Huber loss
- Trong mã ví dụ, Huber loss được triển khai bằng cách kết hợp có điều kiện giữa
tf.square và tf.abs
- Vấn đề này đã được báo cáo qua GitHub issue và được sửa ngay lập tức
Kết luận
- Lan truyền ngược không chỉ là một công cụ tự động hóa đơn thuần mà là một cơ chế gán tín dụng (credit assignment), có thể dẫn tới những hệ quả phức tạp
- Nếu không hiểu cơ chế bên trong, bạn sẽ đối mặt với sự bất ổn của mô hình, thất bại trong huấn luyện và giới hạn khi debug
- Lan truyền ngược không khó về mặt toán học, và có thể học một cách trực quan qua khóa học và bài tập CS231n
- Hiểu lan truyền ngược sẽ cải thiện đáng kể khả năng thiết kế mạng nơ-ron và giải quyết vấn đề
- Thay vì phụ thuộc hoàn toàn vào tính năng autodiff của framework, điều quan trọng là nắm được luồng thực tế của lan truyền ngược
1 bình luận
Ý kiến Hacker News
Có vẻ backpropagation đang bị mang tiếng xấu một cách không công bằng ở đây
Thực ra, cuộc thảo luận này nói về việc gradient và các biến thể gradient descent là những lớp trừu tượng không hoàn hảo đến mức nào trong quá trình tối ưu hóa hơn là về bản thân backprop
Backprop chỉ là một thuật toán để tính đạo hàm của hàm hợp, còn những vấn đề như gradient biến mất khi chồng nhiều sigmoid lên nhau không phải lỗi của backprop mà là đặc tính của chính hàm đó
Lý do người ta bắt mọi người tự cài đặt backward pass là để họ trực tiếp tính đạo hàm và cảm nhận được các số hạng mũ tác động như thế nào
Điểm mấu chốt là trong một số tình huống, không thể trừu tượng hóa các chi tiết của backprop (bao gồm cả việc tính gradient)
Đặc biệt là khi dùng gradient descent, còn với những thuật toán tối ưu toàn cục khác thì có thể vấn đề sẽ nhẹ hơn
Vì trong thực tế hiện nay backprop là cách duy nhất để tính gradient trong deep learning, nên sự rò rỉ của lớp trừu tượng này là điều có thật
Tôi tôn trọng đóng góp của Karpathy, nhưng các bài viết và bài giảng của anh ấy thường làm mờ ranh giới khái niệm, dễ gây hiểu lầm
Với trình độ của anh ấy, người ta có quyền kỳ vọng độ chính xác cao hơn
Đóng góp của Karpathy cho giáo dục deep learning thật sự rất lớn
Từ những bài viết ngắn đến bài viết kinh điển về RNN, rồi các bài giảng trên YouTube và các dự án GitHub đều rất xuất sắc
nanochat được công bố gần đây cũng là một ví dụ tuyệt vời; những ví dụ hoàn chỉnh, nhỏ gọn và rõ ràng như vậy giúp người học rất nhiều
Sau này tôi nhận ra họ quan tâm đến việc tin tưởng và tận dụng LLM hơn là hiểu nó
Trên thực tế, họ hứng thú với những bàn luận mang tính viễn tưởng kiểu “xã hội nơi máy móc thông minh làm mọi việc” hơn là nguyên lý vận hành của LLM
Thay vì chỉ đơn giản “hỏi ChatGPT”, anh ấy tự tìm kiếm, đọc code và phát hiện bug
Cách tiếp cận mang tính khám phá như vậy mới là học thật sự
Trong chương trình thạc sĩ, tôi từng làm bài tập tự cài đặt backprop dựa trên một bài báo
Đó là việc viết forward và backward pass chỉ bằng các phép toán toán học, và là trải nghiệm học tập tốt nhất của tôi năm đó
Đây là kiểu bài tập mà tự mình thường sẽ không làm, nhưng khi bị buộc phải làm thì nó giúp ích cực kỳ nhiều
Tôi còn làm cả UI để trực quan hóa việc trọng số và bias thay đổi như thế nào trong quá trình học
Tôi từng thắc mắc về mối quan hệ giữa backprop và optimizer
SGD chỉ đơn giản di chuyển theo hướng gradient, nhưng các optimizer cao cấp như Adam không dùng trực tiếp gradient mà áp dụng chuẩn hóa, momentum, clipping, v.v.
Vậy thì việc tính gradient chính xác có thực sự cần thiết không, hay chỉ cần biết đại khái hướng đi là đủ?
Có các nghiên cứu liên quan như các bài báo về nhiễu trong SGD, nghiên cứu trực quan hóa
Nhưng chỉ xác định hướng một cách đại khái là rất nguy hiểm — vì độ cong của hàm loss (Hessian) thay đổi rất mạnh
Thực tế, stochastic gradient descent ra đời từ chính ý tưởng này, và xa hơn nữa còn có những hướng tiếp cận như Direct Feedback Alignment
Bài tổng hợp của Ben Recht về mối quan hệ giữa tối ưu hóa và reinforcement learning cũng rất đáng xem
Điều quan trọng không phải giá trị của hàm loss mà là hình dạng của gradient và độ cong
Optimizer như Adam dùng xấp xỉ bậc một để ước lượng độ nhạy theo tỷ lệ của từng tham số rồi điều chỉnh gradient
Tối ưu hóa bậc cao hơn là bất khả thi với các hàm phi tuyến như ReLU
Đó là biện pháp cần thiết để giải quyết vấn đề vanishing gradient
Trong không gian nhiều chiều, sai số nhỏ cũng có thể gây ảnh hưởng lớn, nên việc tính gradient chính xác là cực kỳ quan trọng
Vấn đề là phải tính “hướng đại khái” đó bằng cách nào
Ngay cả các optimizer nâng cao như AdamW cũng vẫn lấy gradient làm cốt lõi
Vào khoảng năm 2016, có vẻ người ta dùng các mẹo như gradient clipping thường xuyên hơn nhiều
Ví dụ, trong bài báo năm 2013 của Alex Graves Sequence Generation with RNNs, ông cũng nói đã dùng clipping để ngăn gradient bùng nổ trong LSTM
Tôi cũng cảm nhận được tầm quan trọng của backprop đến mức từng học một khóa chuyên sâu chỉ về PyTorch autograd
Giờ đây framework đã hỗ trợ clipping, và mức độ hiểu biết về các vấn đề huấn luyện cũng cao hơn
Nhưng bản thân vấn đề không hề biến mất — ReLU hay GELU vẫn là hàm kích hoạt mặc định, và việc huấn luyện LLM vẫn còn khá giống “nghệ thuật đen”
Smol Training Playbook của Hugging Face là bằng chứng cho điều đó
Về lâu dài, tôi nghĩ có thể sẽ có lợi nếu huấn luyện một mô hình bền vững trước sự đa dạng của hàm kích hoạt
Ví dụ, nếu thay ngẫu nhiên giữa ReLU, Swish, GELU khi huấn luyện thì có thể thu được một dạng hiệu ứng regularization giống như dropout
Làm như vậy thì khi suy luận, ta thậm chí có thể đổi sang hàm có chi phí tính toán rẻ nhất
Tiêu đề gốc “Yes you should understand backprop” rõ ràng và hay hơn nhiều
Nếu để code làm thay quá nhiều, rất dễ rơi vào ảo tưởng rằng nó hoạt động như phép màu
Trải nghiệm tự tay tính convolution và backprop hồi cao học đã giúp tôi rất nhiều
Câu hỏi “framework đã tự tính backward pass rồi thì tại sao còn phải tự viết?”
có logic đáng lo ngại kiểu như “đã có máy tính cầm tay thì tại sao còn phải học phép cộng?”
Cũng như hiểu compiler, thuật toán sắp xếp hay cách transistor hoạt động đều hữu ích, việc học backprop cũng có giá trị
Tuy nhiên thời gian học là hữu hạn, nên điểm mấu chốt là học thứ này có lợi hơn những chủ đề khác hay không
Backprop đáng để học vì nếu không hiểu cách nó vận hành bên trong thì rất khó nhận ra các chế độ lỗi tiềm ẩn
Tôi cũng đã tự cài đặt nó nhiều lần, và nó có độ phức tạp vừa đủ để đánh giá một ngôn ngữ mới
Khi mới học deep learning, backprop từng cho tôi cảm giác như phép màu
Nhưng sau khi tự cài đặt, tôi nhận ra nó chỉ là một chuỗi phép tính đơn giản, và nhờ vậy tôi tự tin hơn nhiều khi debug hay tìm nguyên nhân loss bị đình trệ
Nếu ai đang học deep learning thì tôi khuyên ít nhất một lần hãy tự hiện thực bằng tay
Cũng có quan điểm ngược lại
Tôi không nghĩ sinh viên nhất thiết phải cài đặt backprop bằng NumPy
Vấn đề rò rỉ của BackProp nên để các nhà nghiên cứu giải quyết bằng optimizer mới, còn developer chỉ cần tìm hyperparameter tốt là được
Một phần vấn đề phát sinh từ thiết kế mô hình hoặc vòng lặp huấn luyện
Ví dụ, gradient clipping không phải mặc định trong hầu hết framework
Bài viết này hướng đến độc giả là nhà nghiên cứu hoặc quan tâm theo góc nhìn học thuật
Nếu không hiểu gradient của các hàm như Sigmoid hay ReLU hoạt động ra sao, bạn sẽ không thể xử lý vấn đề bùng nổ hoặc tiêu biến
Muốn tạo ra kiến trúc mô hình mới thì phải hiểu backprop hoạt động thế nào, nếu không việc huấn luyện sẽ thất bại hoặc hiệu năng suy giảm
Chỉ khi xuyên qua lớp trừu tượng, bạn mới phát hiện ra những vùng mù thật sự (unknown unknowns)