Mã Python của tôi là một mạng nơ-ron
(blog.gabornyeki.com)- Các chương trình trích xuất thông tin mơ hồ dễ trở thành mã spaghetti khi các quy tắc ngoại lệ chồng chất, và loại logic dựa trên trạng thái này có thể được suy nghĩ lại dưới dạng mạng nơ-ron hồi quy (RNN)
- Ví dụ tìm tham chiếu đến mã C trong thông điệp review code được triển khai như một bộ phân loại viết tay, theo dõi các mẫu token như
identifier–open_paren–close_parendưới dạng trạng thái - Quy tắc này cho thấy độ chính xác 100% trong ví dụ, nhưng bỏ sót các trường hợp như
if (err) goto cleanup;, khiến recall chỉ dừng ở 50%; càng thêm quy tắc,Statevà các câu lệnh rẽ nhánh càng phức tạp - Cùng một máy trạng thái có thể được mã hóa bằng trạng thái ẩn và phép tính theo lớp của RNN; để khiến nó có thể học được, cần ReLU·sigmoid cùng trọng số·bias có thể học, thay vì các hàm chỉ thị nhị phân
- Các triển khai như Elman RNN, GRU, LSTM trong PyTorch và vấn đề tiêu biến gradient ở chuỗi token dài trở thành ràng buộc trong huấn luyện thực tế; bản thân quá trình xác định tập dữ liệu·nhãn·hàm mất mát cũng hữu ích cho việc thiết kế quy tắc thủ công
Quá trình trích xuất dữ liệu mơ hồ biến thành mã spaghetti
- Các chương trình nghiên cứu dùng để trích xuất thông tin từ dữ liệu thô nhanh chóng có quy tắc phức tạp khi dữ liệu không tuân theo đặc tả rõ ràng hoặc có hình thái đặc biệt
- Các tác vụ ví dụ gồm xác định doanh nghiệp và lãnh đạo trong bài báo tin tức, gán nhãn hợp đồng mua sắm công theo loại dịch vụ, và đánh giá liệu thông điệp của kỹ sư có chứa mã chương trình hay không
- Nếu muốn đầu ra hoàn hảo, có thể kiểm tra kỹ từng quan sát và viết unit test cho các trường hợp đại diện
- Cả R và Python đều cung cấp thư viện kiểm thử cho việc này
- Trong những tình huống cần quy tắc phán đoán phức tạp như biệt danh, từ đồng nghĩa, ranh giới giữa tiếng Anh và mã, các quy tắc thủ công rất dễ lung lay
- Thuật toán huấn luyện mạng nơ-ron chuyển vấn đề thành việc tìm các tổ hợp quy tắc này từ dữ liệu, thay vì con người phải liên tục chỉnh sửa bằng tay
Tìm tham chiếu đến mã trong thông điệp review code
- Mục tiêu là phát hiện liệu thông điệp được gửi trong quá trình review code có tham chiếu rõ ràng đến mã chương trình hay không
- Giả định codebase được quan sát được viết bằng C
- Các thông điệp tiêu biểu chứa những tham chiếu mã như sau
render_ipa_alloc()FTPSACKdebug_error()NULLIS_ERROR()aarch64,amd64if (err) goto cleanup;
- Các ứng viên quy tắc đơn giản thất bại theo những cách khác nhau
- Quy tắc xem một từ là mã nếu sau nó có dấu ngoặc bắt được các trường hợp như
render_ipa_alloc()nhưng bỏ sótif (err) goto cleanup; - Quy tắc xem các từ toàn chữ hoa là mã bắt được
FTPSACKvàIS_ERROR()nhưng nhận nhầm các viết tắt nhưAFAICT - Quy tắc xem các từ không phải tiếng Anh là mã có thể nhận nhầm cả thuật ngữ kỹ thuật hoặc tên kiến trúc là mã
- Quy tắc xem một từ là mã nếu sau nó có dấu ngoặc bắt được các trường hợp như
- Để cải thiện quy tắc 2 và quy tắc 3, cần danh sách các từ viết tắt·thuật ngữ chuyên môn như
AFAICT,LGTM,USD,COVID,aarch64,amd64
Bộ phân loại tạo bằng máy trạng thái viết tay
- Thuật toán đơn giản quyết định liệu thông điệp có chứa mã hay không qua hai bước
- Tiền xử lý: chuyển thông điệp thành chuỗi token phản ánh các yếu tố cú pháp của mã C
- Suy luận: kiểm tra xem chuỗi token có thỏa mãn quy tắc hay không
- Rule 1 xem mẫu
underscore_identifier–open_paren–close_parenlà tham chiếu mã - Triển khai Python lưu trạng thái token trước đó bằng data class
Stateprevious_was_identifierprevious_was_open_parenprevious_previous_was_identifierseen_code
contains_codeduyệt qua các token, gọiprocess, và cuối cùng trả vềstate.seen_codeprocessđặtseen_codethànhTruenếu token hiện tại làclose_paren, ngay trước đó làopen_paren, và trước nữa là định danh- Bộ phân loại này trong ví dụ không có dương tính giả và đạt độ chính xác 100%, nhưng bỏ sót nhiều trường hợp nên recall chỉ ở mức 50%
- Khi thêm Rule 2, các trường trong
Statevà các nhánhif/elif/elsetăng lên; càng tinh chỉnh quy tắc thì càng khó bảo trì
Chuyển máy trạng thái thành RNN
contains_codevàprocesslà máy trạng thái, và máy trạng thái có thể được mã hóa thành mạng nơ-ron hồi quy (RNN)- RNN xử lý chuỗi token từng token một và xấp xỉ xác suất có điều kiện rằng thông điệp chứa mã
- Giá trị tương ứng với
Statetrong Python được biểu diễn bằng trạng thái ẩn của RNNState_0là trạng thái ban đầu- Mỗi
State_tđược tính bằng cách đưa token hiện tại và trạng thái trước đó vào hàmf - Trạng thái cuối cùng đi qua tầng đầu ra
gđể trở thành kết quả phân loại
- RNN ví dụ sử dụng ba tầng ẩn
- Tầng thứ nhất lưu hoặc sao chép token hiện tại và trạng thái trước đó
- Tầng thứ hai kiểm tra mẫu tương ứng với Rule 1
- Tầng thứ ba ghi nhớ liệu đã từng thấy mẫu mã hay chưa
- Token được biểu diễn dưới dạng vector nhị phân one-hot
- Để mô phỏng nguyên xi thuật toán viết tay, có thể dùng hàm chỉ thị nhị phân
1{x > 0}- Có thể duy trì tầng ẩn dưới dạng giá trị nhị phân
- Nhưng đạo hàm bằng 0 ở hầu hết mọi nơi, nên không phù hợp cho huấn luyện
- Cũng có thể kiểm tra mẫu bằng tích của
identifier,open_paren,close_paren, nhưng với tầng ẩn nhị phân, có thể biểu diễn cùng phép kiểm tra bằng tổng - Giles et al. (1992) được liên hệ như một trường hợp sử dụng second-order RNN để phát hiện máy trạng thái
Biến thành mạng có thể học được
- Để huấn luyện, dùng ReLU thay cho hàm chỉ thị nhị phân
- Các hằng số số học được thay bằng trọng số và bias, và gradient descent ước lượng các tham số này
- Tầng đầu ra dùng hàm kích hoạt sigmoid để tính giá trị xác suất cuối cùng
- Dạng này có thể đưa vào PyTorch để huấn luyện, nhưng nếu huấn luyện nguyên như vậy thì hiệu năng không cao
- Một lý do khiến hiệu năng còn thiếu là kiến trúc không phổ biến, nên nhiều phần hơn của quy trình huấn luyện được chạy trong mã kết nối Python, ít tận dụng các triển khai thư viện C++ của PyTorch hơn
Triển khai PyTorch và ràng buộc của thông điệp dài
- torch.nn.RNN của PyTorch cung cấp triển khai dựa trên Elman RNN
- Kiến trúc ví dụ và Elman RNN khác nhau ở cách kết nối các tầng ẩn
- Trong kiến trúc ví dụ, tầng thứ nhất của token
tnhận tầng thứ ba của tokent-1làm đầu vào, và mỗi tầng chỉ nhận đầu vào từ tầng ngay trước nó - Trong Elman RNN, mỗi tầng ẩn cũng nhận trạng thái ở thời điểm trước đó của chính tầng đó làm đầu vào
- Tầng ẩn thứ nhất của Elman RNN không nhận tầng cuối cùng của thời điểm trước đó làm đầu vào
- Trong kiến trúc ví dụ, tầng thứ nhất của token
- Thông điệp review code thực tế có thể dài, và thông điệp dài dẫn đến chuỗi token dài
- Với chuỗi dài, dù gradient descent về lý thuyết vẫn hoạt động, vấn đề ổn định số có thể xuất hiện do tiêu biến gradient
- Elman RNN cũng có thể dễ gặp vấn đề này, và GRU hoặc LSTM có thể cho hiệu năng tốt hơn trong tác vụ phát hiện mã
Kỷ luật dựa trên dữ liệu
- RNN biến các tổ hợp quy tắc khó xử lý bằng tay thành mục tiêu học, đồng thời buộc ta định nghĩa vấn đề rõ ràng hơn
- Để huấn luyện mạng, cần có
- Lựa chọn tập dữ liệu huấn luyện và tập dữ liệu kiểm chứng
- Gán nhãn trước
- Hàm mất mát nêu rõ bộ phân loại cần đạt được gì và cần tránh gì
- Quá trình này làm lộ ra các vùng xám không lường trước và khiến tiêu chí phán đoán rõ ràng hơn
- Kỷ luật dựa trên dữ liệu như vậy cũng hữu ích cho những vấn đề được giải bằng thuật toán viết tay, chứ không chỉ bằng mạng nơ-ron
1 bình luận
Ý kiến trên Hacker News
Bài viết này không nói nhiều về kiểm thử hay việc thu thập dữ liệu huấn luyện, nhưng phần đó có vẻ là điểm cốt lõi
Đoạn mã mà ta cảm thấy mình hiểu được là vì, dù không chính thức, ta đã tự chứng minh rằng nó có một tính chất tổng quát hóa cho mọi đầu vào. Ví dụ, thuật toán sắp xếp không chỉ sắp xếp những danh sách đã kiểm thử, mà sắp xếp được bất kỳ danh sách nào
Điểm bất định ở mạng nơ-ron là ta không biết nó sẽ tổng quát hóa như thế nào. Với đầu vào chưa từng thấy, chỉ cần khác đi một chút là không có tính chất nào được bảo đảm; thậm chí ngay từ đầu đó có thể là một bài toán khó đặc tả tính chất mong muốn bằng toán học
Nếu có thể định nghĩa một tính chất đủ rõ để dùng làm kiểm thử dựa trên thuộc tính kiểu QuickCheck, ta có thể tạo ra lượng lớn dữ liệu kiểm thử hoặc dữ liệu huấn luyện bằng cách ngẫu nhiên hóa. Chỉ cần viết các kiểm thử bắt đầu từ một ví dụ mong muốn rồi sinh ra các biến thể khả dĩ của ví dụ dương/âm
Đó không phải là chứng minh, nhưng là một điểm khởi đầu. Ít nhất, nếu có thể chứng minh được thì ta biết mình cần chứng minh điều gì
Nếu có những thứ như vậy, việc dựa vào mã spaghetti và dựa vào mạng nơ-ron trông khá giống nhau. Nếu muốn thỏa mãn thêm tính chất khác, chỉ cần viết thêm một kiểm thử dựa trên thuộc tính. Với mạng nơ-ron, ta có thể huấn luyện thay vì sửa trực tiếp, nhưng việc sửa mã cũng có trợ lý AI hỗ trợ
Dù vậy có lẽ tôi vẫn tin mã nguồn hơn. Ít nhất thì còn có thể gỡ lỗi được
Đọc như một cách để tạo ra mạng nơ-ron thực hiện công việc thực tế thì bài viết rất thú vị. Nhưng nếu lần tới cần phân tích cú pháp đầu vào mà bạn định làm đúng theo cách này, thì thật lòng tôi không biết phải nói gì
Tác giả chọn một bài toán khó là phân tích cú pháp đầu vào tùy ý có các mẫu được định nghĩa lỏng lẻo, và nói đúng rằng việc này rất dễ tạo ra mã spaghetti khó đọc
Nhưng rồi phương án thay thế được đề xuất lại là một loại mã khó đọc đến mức đến nay ta vẫn còn đang nghiên cứu nguyên lý hoạt động của nó, tức mạng nơ-ron
Có thể hiểu được, nhưng không nên đánh giá quá cao thứ hoàn toàn không thể diễn giải so với thứ chỉ “xấu xí”. Với một số công việc, mô hình học máy có thể phù hợp, nhưng trong nhiều trường hợp, dù tốn công, phương án có thể đọc và kiểm chứng vì sao nó hoạt động như vậy vẫn tốt hơn phương án không thể làm được điều đó
Rốt cuộc học máy chỉ cung cấp thêm một lựa chọn. Nó có phù hợp hay không phụ thuộc vào kết quả đánh giá và mức độ quyết định tính, khả năng giải thích mà thuật toán đã chọn cần có
Điểm đáng chú ý là liệu RNN có phải lựa chọn đúng không. Nó cần huấn luyện và có thể cần nhiều ví dụ hơn rất nhiều so với số đang có. Tuy vậy, có vẻ vẫn có thể tạo dữ liệu tổng hợp cho các trường hợp dương/âm dựa trên các quy tắc đã biết
Một hệ thống mà ta có thể thấy vì sao nó hoạt động như vậy là tốt, nhưng nếu nó liên tục đưa ra câu trả lời sai thì không có ý nghĩa. Trong sử dụng thực tế, việc có được câu trả lời đúng thường quan trọng hơn việc biết nó đi đến câu trả lời bằng cách nào
Với mạng nơ-ron có định lý xấp xỉ hàm phổ quát. Nội dung là nó có thể biểu diễn hoặc mã hóa một hàm tùy ý đến mức độ chính xác mong muốn[0]
Nhưng không có định lý nào nói rằng ta có thể học được phép xấp xỉ đó, hay học bằng cách nào
[0] https://en.m.wikipedia.org/wiki/Universal_approximation_theo...
Điều này còn rất xa mới giải thích được các mạng nơ-ron thực tế và hữu ích như mạng nơ-ron tích chập, transformer, LSTM thật sự hoạt động ra sao
Thú vị là định lý này nói về mạng một tầng. Trong thực tế, dùng nhiều tầng hoạt động tốt hơn nhiều
Bài viết thật sự hay, và dù tôi chưa hoàn toàn hiểu các khái niệm toán học sâu hơn xoay quanh RNN, nó gợi ra nhiều suy nghĩ
Nó có cảm giác giống với thứ tôi đang tìm hiểu gần đây: cách xây dựng ứng dụng bằng cách gắn với thuật toán suy luận tiến. Tác giả dùng RNN, còn tôi đang đưa vào thuật toán Rete
Phần suy nghĩ theo kiểu tiêu hóa chuỗi đầu vào từng ký tự một cũng rất mạnh. Như vậy ta giao logic suy luận cho thuật toán, còn chúng ta chỉ viết logic nhập/xuất thật mỏng; phần còn lại thuật toán xử lý
Điểm hay của bài viết này là nó giải thích bằng trải nghiệm học tập rằng việc biến một hàm nào đó thành RNN thật sự có nghĩa là gì, và so sánh điều đó với RNN “đầy đủ pin kèm sẵn” có trong PyTorch
Câu hỏi là bài nói rằng để mô hình hóa trạng thái cần thêm ba tầng ẩn vào mạng, nhưng vì sao lại là ba? Tôi tò mò liệu đó là hệ quả của quy tắc cụ thể mà tác giả muốn triển khai, hay là số tầng thường dùng khi triển khai dạng quy tắc này bằng kiến trúc đó. Tôi cũng tò mò liệu với cấu trúc Elman thì có thể làm được bằng ít tầng hơn không
Tầng đầu tiên gom những gì có thể biết từ token hiện tại và những gì đã biết sau khi tính toán token trước đó. Tầng thứ hai kiểm tra xem có thỏa mãn quy tắc quyết định hay không để xác định token hiện tại có trông giống mã chương trình hay không. Tầng thứ ba so sánh quyết định đó với các quyết định về những token trước
Có lẽ cũng có thể nén việc này vào một tầng ẩn duy nhất. Vì ReLU đủ để nắm bắt tính phi tuyến nên có vẻ khả thi. Tôi chưa xem xét đủ kỹ sự tương ứng với cấu trúc Elman nên chưa biết câu trả lời
RNN đã bị transformer hấp thụ hoàn toàn rồi sao? Tôi tự hỏi liệu có thể quên cách xử lý RNN và chỉ tập trung vào transformer hay không
Ở đây, bài báo cho thấy một định nghĩa hẹp cụ thể của transformer, tức transformer có causal masking, là tương đương với RNN và chiều ngược lại cũng có thể
Tương tự, Mamba(https://arxiv.org/abs/2312.00752), một kiến trúc đang rất nóng hiện nay, cũng có đơn vị tương đương với RNN có gate. Theo tôi biết, vì lý do hiệu năng, khi huấn luyện nó dùng CNN tương đương, còn khi suy luận thì dùng RNN
Đây thật ra là lợi thế cốt lõi của transformer. Các liên hệ ở khoảng cách gần và xa không khó hơn hay dễ hơn nhau. Nhưng về mặt lý thuyết, RNN có thể ghi nhớ cả quá khứ xa vô hạn
Trong 7 năm qua tôi làm machine learning/LLM với vai trò research engineer và cũng từng làm ở phòng nghiên cứu của FAANG, nhưng tôi chỉ luôn nghĩ rằng mình nên học RNN, chứ thực ra chưa từng học và cũng chưa từng cần đến nó
Nếu thấy thú vị, bạn nên xem qua lập trình di truyền. Tôi xem đây là một cách tiếp cận đơn giản hơn cho cùng vấn đề, và không cần toán học
Nó tái tổ hợp chương trình dựa trên cây cú pháp trừu tượng, và nếu bạn đưa ra một heuristic nào đó, nó sẽ tối ưu hóa chương trình theo tiêu chí đó. Phép màu nằm ở hàm heuristic; bạn có thể chọn tối ưu hóa thứ mình muốn, như tốc độ, độ dài chương trình, giảm thiểu cấu trúc phức tạp hoặc lời gọi hàm, hiệu quả mạng, hoặc kết hợp các yếu tố đó
https://youtu.be/tTMpKrKkYXo
https://www.human-competitive.org/
Gần đây tôi đã viết một bài blog khám phá ý tưởng giao tiếp với LLM cục bộ cho những tác vụ mơ hồ kiểu này
Tôi nghĩ cách đó hợp lý hơn so với việc tự code mạng nơ-ron. Có vẻ thực tế hơn khi dùng thứ như llama.cpp để đánh giá xem một mô hình nhỏ có thể giải trực tiếp bài toán hay không; nếu không thì tinh chỉnh nó rồi tích hợp llama.cpp theo cách lập trình với wrapper mong muốn
Mạng nơ-ron hồi quy có thể được dùng cho tính toán tùy ý, và tính tương đương với máy Turing cũng đã được chứng minh. Nhưng với tác vụ đó thì hoàn toàn phi thực tế
Cách trong bài này trông giống một máy trạng thái đã được học bằng cách nào đó. Bài viết sẽ tốt hơn nếu có phần tóm tắt dài hơn, và “Python” thì có vẻ chẳng liên quan gì. Việc học ngữ nghĩa Python thật sự có lẽ khá khó do đặc tính của ngôn ngữ này. Nó không phải là ngôn ngữ có một chuẩn, mà là làm theo những gì CPython làm
Vậy thì có lý do gì nó không xử lý được ngôn ngữ tự nhiên mang tính trang trọng như bình luận code review?
Trong trường hợp đó, suy luận được chạy với đầu vào ngẫu nhiên để tạo ra “Shakespeare” ngẫu nhiên, nhưng cấu trúc và phong cách ngôn ngữ vẫn được RNN học. Có lẽ nó cũng có thể dùng cho phân loại
Trước hết cứ biên dịch Python thành mạng nơ-ron, rồi sau đó vặn nó để đặt lên một mạng nơ-ron dựa trên transformer là được
Khi đó Transformer Virtual Machine(TVM) có thể chạy chương trình tùy ý
Nếu dùng transfer learning, tức cách chồng các trọng số lên nhau, LLM có thể “sinh ra” với các thuật toán đã được mã hóa sâu bên trong