Lần trước khi tôi cố gắng hiểu C4.5, tôi đã thất bại, nhưng tôi đã triển khai một biến thể của ID3 - ban đầu vì tò mò, nhưng cuối cùng nó đã được sử dụng như một phần của trình tạo mã nhiều công văn. Điều này không bao giờ xử lý các tập dữ liệu lớn, và đó là một công việc tốt. Bạn sẽ không làm tốt để bắt chước hầu hết những gì tôi đã làm, nhưng có thể với một vài ngoại lệ, và tất nhiên tôi đã học được một chút từ những sai lầm.
Tôi có xu hướng nghĩ về việc xây dựng cây quyết định cho một hệ thống chuyên gia, vì vậy tôi có xu hướng sử dụng các thuật ngữ sau - xin lỗi nếu điều đó gây nhầm lẫn ...
Column = Question ..... A question the expert system might ask
Row = Conclusion ... A possible conclusion the expert system might reach
Cell = Answer ....... For the question and conclusion, what answer should
the user be expected to give
Trên thực tế, trong trường hợp của tôi, tôi đã đưa ra kết luận vào một cột khác - tương tự như bảng chân lý cho cổng logic. Do đó, số hàng chỉ là số hàng. Điều này cho phép tôi xử lý các sự cố kiểu XOR thậm chí không thể được biểu diễn nếu cùng một kết luận không thể xuất hiện trên một số hàng. Tôi không chắc điều này có liên quan đến bạn hay không. Trong mọi trường hợp, tôi bỏ qua điều này dưới đây - nó thực sự không tạo ra nhiều sự khác biệt trừ khi bạn nhìn vào chi tiết của việc chọn câu hỏi nào để hỏi tiếp theo. Để khai thác dữ liệu, có lẽ bạn không có một thông tin cụ thể nào để coi là kết luận mục tiêu dù sao đi nữa - "kết luận" chỉ là bất cứ điều gì còn lại khi bạn quyết định ngừng đặt câu hỏi.
Vì vậy - đối với mỗi nút cây quyết định xuất phát từ trước đến nay, bạn có một bộ câu hỏi nổi bật (cột) và một tập hợp các kết luận chưa được loại bỏ (hàng). Đó là những gì tôi đã làm. Điểm duy nhất đáng thêm là tôi đã sử dụng các vectơ bit.
IIRC, C ++ std::vector<bool>
và std::array<bool>
có thể được triển khai dưới dạng vectơ bit, nhưng bạn vẫn phụ thuộc vào thuật toán STL cho các hoạt động được thiết lập, mỗi lần vận hành một mục. Tôi đã sử dụng lớp vectơ bit của riêng mình, nó đã dần dần được xây dựng trong một khoảng thời gian và sử dụng các toán tử bitwise ở bên dưới std::vector<CHUNK>
(trong đó CHUNK
là một kiểu int không dấu, thường rộng 32 bit).
Có thể có một tùy chọn vectơ bit tốt hơn trong C ++ 11 hoặc trong Boost, và phải có một số thư viện tốt ở đó - có rất nhiều loại chương trình mà bạn kết thúc làm việc với các bộ số nguyên không dấu. Tôi chỉ không biết nhiều về họ vì tôi đã quá lười để chuyển từ sử dụng của riêng mình.
Tuy nhiên, vectơ bit ở đó tốt nhất khi các tập hợp chủ yếu là dày đặc. Trong trường hợp này, tập hợp các hàng là vấn đề rõ ràng. Chỉ nút gốc của cây quyết định sẽ có một tập hợp hàng dày đặc hoàn hảo. Khi bạn nhận được nhiều hơn từ gốc, các bộ hàng nhận được sperer và sparser, với mỗi câu hỏi được trả lời dẫn đến tập hợp các hàng được phân phối giữa hai hoặc nhiều bộ hàng nút tiếp theo khác nhau.
Vì vậy, một dãy số được sắp xếp đơn giản có thể là đại diện tốt nhất cho các bộ này. Tuy nhiên, cũng có thể một "vectơ bit thưa thớt" có thể đáng giá. Một triển khai có thể là một mảng các cặp được sắp xếp trong đó đầu tiên của mỗi cặp là ID hàng đầu tiên của một khối và thứ hai là một bitvector có kích thước cố định cho khối đó. Ví dụ, hàng số 35 có thể được lưu trữ trong khối 32 ( 35 & ~(32 - 1)
) ở vị trí bit 3 ( 35 & (32 - 1)
). Nếu bạn chỉ lưu các cặp trong đó bitvector khác không, thì điều này mang lại một cái gì đó giữa một mảng ID được sắp xếp và một bitvector đơn giản - nó xử lý các mảng thưa thớt một cách hợp lý, đặc biệt là khi các ID có xu hướng tụ lại gần nhau theo bộ.
Ngoài ra, có thể đáng giá khi sử dụng một lớp có thể chuyển từ bitvector sang biểu diễn mảng được sắp xếp khi kích thước đủ nhỏ. Các biến chứng thêm, hoàn toàn có lợi cho một vài nút gần gốc, có lẽ là vô nghĩa.
Dù sao, tuy nhiên, các bộ này được trình bày, khi chúng quay trở lại một "cơ sở dữ liệu" không đổi duy nhất, điều này giúp tiết kiệm rất nhiều trong việc sao chép dữ liệu và lãng phí không gian khi thuật toán chạy. Nhưng nó vẫn đáng để xem xét "cơ sở dữ liệu" đó.
Tôi đã sử dụng cấu trúc dữ liệu liên kết, cho phép tôi tra cứu bằng cách sử dụng một bộ câu hỏi-ID và ID kết luận để có được câu trả lời-ID. Điều đó có nghĩa là tôi đã có một chi phí cho mỗi mục cho khóa (ID câu hỏi và ID kết luận) và trong trường hợp này cũng là chi phí trên cây kiểu B +. Lý do - về cơ bản là thói quen. Tôi có các thùng chứa rất linh hoạt và tôi có xu hướng sử dụng chúng rất nhiều vì nó giúp tiết kiệm dự đoán những khả năng tôi thực sự cần sau này. Có một cái giá cho điều đó, nhưng đó chỉ là điều tối ưu hóa sớm.
Trong trường hợp của bạn, bạn đang sử dụng ma trận - Tôi giả sử một mảng hai chiều được lập chỉ mục bởi ID câu hỏi và ID câu trả lời.
Cách duy nhất tôi có thể tưởng tượng phiên bản của mình hiệu quả hơn phiên bản của bạn là nếu hầu hết các câu trả lời không được biết đến. Trong một ma trận, bạn cần một ID câu trả lời không xác định đặc biệt cho điều đó, lấy cùng một không gian như một ID câu trả lời đã biết. Trong một thùng chứa kết hợp, bạn loại trừ các hàng đó.
Mặc dù vậy, một mảng được sắp xếp sẽ hiệu quả hơn giải pháp dựa trên cây B + của tôi. Bạn không cần phải cho phép chèn hiệu quả, do đó, chi phí cần thiết duy nhất là cho các phím.
Nếu bạn sử dụng hai trường chính (câu hỏi và kết luận, hàng và cột) có thể là một vấn đề (tôi thực sự không nhớ) - bạn có thể không thể giữ một bản sao của bảng theo một thứ tự được sắp xếp. Nhưng nếu bạn sử dụng một khóa được tính toán duy nhất dọc theo dòng (row * num_columns) + column
, thì về cơ bản bạn vẫn đang thực hiện một mảng thưa hai chiều.
Đối với tôi, sự hiện diện của các câu trả lời không xác định / không xác định cho một câu hỏi cụ thể có nghĩa là tôi chưa được phép hỏi câu hỏi đó - và thậm chí đó chỉ là lý thuyết tôi sử dụng khi tôi thực hiện thuật toán lần đầu tiên. Tôi không bao giờ thực sự đưa nó để sử dụng. Tôi có thể sử dụng nó, nhưng tôi không bao giờ sử dụng nó. Đối với bản ghi, trong trình tạo mã nhiều công văn đó, một ý tưởng là gửi dựa trên các trường trong loại. Vì bản thân loại này là đa hình, nên các trường đó thậm chí không có ở đó, vì vậy chỉ có giá trị để xem xét chúng một khi bạn đã xác nhận rằng chúng phải có mặt.
Nếu bạn không có ứng dụng cho câu trả lời không xác định / không xác định, ma trận hiện tại của bạn có lẽ là giải pháp tốt nhất.
Về cơ bản, đó là nó - tôi thực sự không thể đưa ra bất kỳ lựa chọn rõ ràng nào tốt hơn, và những gì bạn đang làm có lẽ đã tốt hơn những gì tôi đã làm. Tuy nhiên, có một số khả năng đánh đổi mà bạn có thể xem xét - giả sử rằng đó không phải là tối ưu hóa sớm (và có thể sai), tất nhiên.
Vấn đề đánh đổi chính liên quan đến hiệu quả của việc biểu diễn các bộ giá trị thưa thớt so với mật độ dày đặc, do đó, nó không thực sự cụ thể đối với C4.5 hoặc xây dựng cây quyết định. Và một cách tiếp cận "tinh vi" hơn thường kém hiệu quả hơn một cách đơn giản được lựa chọn cẩn thận.