2 điểm bởi GN⁺ 2024-07-02 | 1 bình luận | Chia sẻ qua WhatsApp
  • 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ư identifieropen_parenclose_paren dướ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, State và 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()
    • FTPSACK
    • debug_error()
    • NULL
    • IS_ERROR()
    • aarch64, amd64
    • if (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ót if (err) goto cleanup;
    • Quy tắc xem các từ toàn chữ hoa là mã bắt được FTPSACKIS_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ã
  • Để 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_identifieropen_parenclose_paren là tham chiếu mã
  • Triển khai Python lưu trạng thái token trước đó bằng data class State
    • previous_was_identifier
    • previous_was_open_paren
    • previous_previous_was_identifier
    • seen_code
  • contains_code duyệt qua các token, gọi process, và cuối cùng trả về state.seen_code
  • process đặt seen_code thành True nế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 State và các nhánh if/elif/else tă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_codeprocessmá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 State trong Python được biểu diễn bằng trạng thái ẩn của RNN
    • State_0 là 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àm f
    • 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 t nhận tầng thứ ba của token t-1 là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
  • 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

 
GN⁺ 2024-07-02
Ý 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 đó

    • Tôi nghĩ việc tác giả lôi mã spaghetti ra gần như là đánh lạc hướng. Nếu đầu ra của thuật toán không được định nghĩa chính xác như một hàm của đầu vào, nhưng có các ví dụ để đưa ra, thì đó là lúc học máy hữu ích
      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
    • Cách làm bằng mã spaghetti về cơ bản là hệ chuyên gia. Có thể xem đó là AI thuật toán kiểu cũ. Bên ngoài các miền giới hạn, những hệ thống như vậy hiếm khi hoạt động tốt; thực tế quá lộn xộn
      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
    • Nghe giống như đề xuất giấu đoạn mã spaghetti xấu xí phía sau một ma trận số thực dấu phẩy động 1000x1000 gọn gàng
  • 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...

    • Chứng minh đó thường được viện dẫn, nhưng điều nó thực sự cho thấy chỉ là mạng nơ-ron tương đương với bảng tra cứu. Một bảng tra cứu có đủ bộ nhớ có thể xấp xỉ bất kỳ hàm nào
      Đ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
    • Nhân tiện, có nhiều thuật toán đã được chứng minh là bộ xấp xỉ hàm phổ quát từ rất lâu trước mạng nơ-ron. Mạng nơ-ron không phải là duy nhất, cũng không phải đầu tiên. Trong nhiều trường hợp còn có khá nhiều phương pháp phù hợp hơn mạng nơ-ron rất nhiều
    • Không phải hàm nào cũng được. Có ràng buộc đối với loại hàm mà định lý xấp xỉ phổ quát áp dụng
      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
    • Nó chỉ có thể mô hình hóa các hàm liên tục; chính xác hơn là có thể xấp xỉ một hàm liên tục bất kỳ trên một tập con compact của ℝⁿ với độ chính xác tùy ý khi có đủ nơ-ron
    • Vậy thì tôi tò mò “học” nghĩa là gì
  • 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

    • Về câu hỏi đầu tiên, dùng ba tầng ẩn làm cho việc mạng đang làm gì trở nên rõ ràng hơn một chút. Mỗi tầng thực hiện một bước tính toán
      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

    • Nếu muốn làm câu hỏi này phức tạp hơn, đáng xem bài báo “Transformers are RNNs: Fast Autoregressive Transformers with Linear Attention” - https://arxiv.org/pdf/2006.16236
      Ở đâ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
    • Transformer có ngữ cảnh hữu hạn, còn RNN thì không. Trong thực tế, tín hiệu gradient của RNN bị giới hạn và suy giảm do lan truyền ngược theo thời gian
      Đâ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
    • Nếu bạn muốn trở thành tiến sĩ hay nhà nghiên cứu machine learning thì không; còn lại thì đúng
      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

    • Tôi cũng muốn bổ sung Humies Awards, nơi cho thấy các kết quả cạnh tranh ở mức con người. Chỉ cần lướt qua các bài báo đã nộp cũng có thể học được rất nhiều về những gì có thể và không thể trong lĩnh vực này
      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

    • Bài viết năm 2015 của Karpathy về RNN[1] đã cho thấy một RNN được huấn luyện ở cấp ký tự trên tác phẩm của Shakespeare có thể tạo ra văn bản mang phong cách Shakespeare, dù không có tính nhất quán tự sự như LLM
      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
      1. https://karpathy.github.io/2015/05/21/rnn-effectiveness/
  • 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