Một tòa soạn điện tử nhận vài trăm tin mỗi ngày và cần gán từng tin vào đúng chuyên mục, một sàn thương mại điện tử cần xếp phản hồi của khách vào nhóm "giao hàng", "thanh toán" hay "bảo hành". Đó là bài toán phân loại văn bản (text classification): cho một đoạn chữ, dự đoán nhãn của nó trong một tập nhãn định sẵn. Trước khi nghĩ tới mô hình ngôn ngữ lớn, một pipeline phân loại văn bản gồm tách từ, TF-IDF và mô hình tuyến tính đã cho kết quả khá, chạy trong vài phần trăm giây và dễ giải thích. Bài này dựng pipeline phân loại văn bản đó cho tiếng Việt, đo từng quyết định bằng số thật và chỉ ra chỗ nào nên nghi ngờ con số.

Bài phân loại văn bản này nối tiếp Phát hiện đối tượng với YOLO: huấn luyện trên dữ liệu riêng từ thị giác máy tính sang ngôn ngữ, và dùng lại quy trình ở Machine learning với scikit-learn: Pipeline, cross-validation và tuning cùng các thước đo ở Đánh giá mô hình phân loại: precision, recall, ROC và dữ liệu lệch. Môi trường: Apple M4, 16 GB RAM, Python 3.12, scikit-learn 1.4, NumPy 1.26, pyvi 0.1.1. Toàn bộ mã nằm trong code/s7-25.

Bài toán phân loại văn bản: 10 chuyên mục tin tức tiếng Việt

Dữ liệu cho bài phân loại văn bản này là VNTC (Vietnamese News Text Classification Corpus), bộ ngữ liệu công khai của Vu Cong Duy Hoang, giấy phép MIT theo tệp LICENSE trong kho mã. Phiên bản 1.1 có 33.759 tin huấn luyện thuộc 10 chuyên mục (Chính trị - Xã hội, Đời sống, Khoa học, Kinh doanh, Pháp luật, Sức khỏe, Thế giới, Thể thao, Văn hóa, Vi tính), lấy từ bốn tờ báo (VnExpress, Tuổi Trẻ, Thanh Niên, Người Lao Động) theo tệp Stats.txt đi kèm.

Đây là tin từ những năm 2000 (một tin mẫu nhắc kỳ tuyển sinh đại học 2004), nên từ vựng thời sự đã cũ; điều đó không cản trở việc học quy trình, nhưng bạn không nên kỳ vọng mô hình này hiểu tin hôm nay. Bản quyền từng bài thuộc các tòa soạn gốc, nên bài phân loại văn bản này chỉ dùng phần mở đầu cho mục đích nghiên cứu. Với dữ liệu nội bộ như phản hồi khách hàng, hãy ẩn số điện thoại và địa chỉ trước khi huấn luyện.

Tệp Train_Full.rar nặng khoảng 49 MB, giải nén ra khoảng 207 MB tệp văn bản UTF-16 và không đi kèm trong code/s7-25; để tái lập, tải về từ kho VNTC rồi chạy python prepare_data.py Train_Full (thư mục data/ chỉ giữ sample.jsonl khoảng 2 MB). Số bài mỗi lớp lệch khá nhiều (Thể thao 5.298, Khoa học 1.820), nên prepare_data.py cắt mỗi tin còn 60 âm tiết đầu (tiêu đề cộng phần mở đầu), khử trùng rồi lấy mẫu phân tầng 500 tin mỗi chuyên mục với hạt giống cố định, tổng 5.000 tin. Khử trùng làm trên toàn bộ tập trước khi chia: khoá là 25 âm tiết đầu đã hạ chữ thường và bỏ dấu câu, khoá nào xuất hiện hai lần trở lên (kể cả khác nhãn) thì loại hết các bản sao. Output thật của bước này:

tổng số tệp: 33759, còn lại sau khi bỏ tin quá ngắn: 33759
khoá trùng (25 âm tiết đầu): 62, trong đó khác nhãn: 50; loại toàn bộ bản sao của các khoá này
số tin duy nhất mỗi nhãn (tệp gốc -> sau khử trùng):
  Chính trị - Xã hội   5219 ->   5202
  Đời sống             3159 ->   3144
  Khoa học             1820 ->   1786
  ...
đã lưu 5000 bài vào data/sample.jsonl

Chỉ 62 khoá trùng nên ảnh hưởng nhỏ, nhưng 50 trong số đó khác nhãn, tức cùng một tin xuất hiện ở hai chuyên mục; để lọt sang cả huấn luyện lẫn kiểm tra thì sẽ thổi phồng điểm.

Giới hạn còn lại: cách khử trùng này chỉ bắt tin giống nhau ở 25 âm tiết đầu, các bản tin gần trùng (cùng một sự kiện, sửa vài câu) vẫn có thể rơi vào cả hai phía và làm điểm cao hơn thực tế một chút. Sau đó chia 80/20 có phân tầng: 4.000 tin huấn luyện, 1.000 tin kiểm tra, mỗi lớp đúng 100 tin trong tập kiểm tra. Vì các lớp cân bằng, đoán ngẫu nhiên đúng khoảng 10% và accuracy đọc được trực tiếp.

Tách từ cho phân loại văn bản: vì sao khoảng trắng chưa đủ

Tiếng Việt viết cách nhau theo âm tiết chứ không theo từ. "Ngân hàng Nhà nước" gồm bốn âm tiết nhưng chỉ hai từ, và "lãi suất" mang nghĩa khác hẳn khi tách riêng "lãi" và "suất". Nếu chỉ split() theo khoảng trắng, bộ từ vựng chứa nhiều đơn vị mơ hồ như "nhà", "giá", "hàng". Bước tách từ (word segmentation) ghép các âm tiết thành từ, đánh dấu bằng dấu gạch dưới. Hai công cụ phổ biến là pyvi (thuần Python, giấy phép MIT, dùng mô hình CRF, cài bằng pip install pyvi) và RDRSegmenter trong VnCoreNLP (cần Java 1.8 trở lên, bài báo NAACL 2018).

Với pipeline tuyến tính, pyvi gọn vì không kéo thêm Java. Ví dụ thật:

from pyvi import ViTokenizer
demo = "Ngân hàng Nhà nước giữ nguyên lãi suất điều hành trong quý này"
print(ViTokenizer.tokenize(demo))
# Ngân_hàng Nhà_nước giữ nguyên lãi_suất điều_hành trong quý này

Tách từ 5.000 tin bằng pyvi mất 1,4 giây trên M4, nên chi phí này không đáng kể. Mô hình tách từ cũng có lỗi, nhất là với tên sản phẩm hoặc thuật ngữ mới. Nếu sau này bạn chuyển sang PhoBERT, README của VinAI PhoBERT yêu cầu văn bản đầu vào phải được tách từ sẵn bằng RDRSegmenter, nên làm quen với bước này từ bây giờ là không phí.

TF-IDF: biến câu chữ thành vector thưa

Để phân loại văn bản bằng mô hình tuyến tính, mỗi tin phải là một vector số. Cách đơn giản nhất là túi từ (bag-of-words): mỗi cột là một từ, mỗi ô là số lần từ đó xuất hiện. Vấn đề là các từ phổ biến như "của", "và", "trong" xuất hiện khắp nơi nên mang rất ít thông tin. TF-IDF nhân tần suất trong tin (tf) với độ hiếm trên toàn bộ tập (idf). Theo tài liệu Feature extraction của scikit-learn, với smooth_idf=True (mặc định) thì idf(t) = ln((1 + n) / (1 + df(t))) + 1, rồi mỗi vector được chia cho chuẩn L2 của nó. Công thức này khác một chút so với giáo trình (ln(n / df)), nên đáng kiểm chứng bằng tay trên ba câu:

docs = ["giá vàng tăng", "giá xăng giảm", "giá vàng giảm mạnh"]
v = TfidfVectorizer(); X = v.fit_transform(docs)
n = len(docs)
df = np.array([sum(t in d.split() for d in docs) for t in v.get_feature_names_out()])
idf_manual = np.log((1 + n) / (1 + df)) + 1
từ vựng: ['giá', 'giảm', 'mạnh', 'tăng', 'vàng', 'xăng']
idf    : [1.     1.2877 1.6931 1.6931 1.2877 1.6931]
idf tay: [1.     1.2877 1.6931 1.6931 1.2877 1.6931] | khớp: True
vector câu 1 (tay)     : [0.4254 0.     0.     0.7203 0.5478 0.    ]
vector câu 1 (sklearn) : [0.4254 0.     0.     0.7203 0.5478 0.    ]

Từ "giá" có mặt trong cả ba câu nên idf bằng đúng 1 (mức thấp nhất), còn "tăng" chỉ có ở một câu nên idf cao nhất. Trong vector của câu 1, "tăng" nhận trọng số lớn nhất (0,7203) dù mỗi từ chỉ xuất hiện một lần.

Hai tham số quan trọng khác. sublinear_tf=True thay tf bằng 1 + ln(tf) để một từ lặp mười lần không nặng gấp mười lần. ngram_range=(1, 2) thêm cặp từ liền kề, giữ lại một phần thứ tự từ. Với min_df=2 ta bỏ cặp từ chỉ có ở một tin.

Mô hình tuyến tính cho phân loại văn bản tiếng Việt

Mô hình tuyến tính hợp với phân loại văn bản vì số chiều rất lớn (hàng chục nghìn từ) trong khi số mẫu chỉ vài nghìn; ở không gian đó các lớp thường gần như tách tuyến tính. Theo tài liệu LinearSVC, lớp này dùng liblinear, mặc định loss='squared_hinge', penalty='l2', C=1.0 và huấn luyện kiểu one-vs-rest, mỗi chuyên mục một bộ phân loại (ở scikit-learn 1.4, nếu không đặt tham số dual thì có FutureWarning, nên ví dụ đặt dual="auto"), tương thích tốt với số mẫu lớn hơn SVC kernel. Ta đóng gói tách từ, TF-IDF và bộ phân loại trong một Pipeline để bước biến đổi chỉ học từ tập huấn luyện:

pipe = Pipeline([
    ("tfidf", TfidfVectorizer(ngram_range=(1, 2), min_df=2, sublinear_tf=True)),
    ("clf", LinearSVC(C=1.0, dual="auto")),
])
pipe.fit(Xtr_seg, ytr)           # Xtr_seg: các tin đã qua ViTokenizer.tokenize
pred = pipe.predict(Xte_seg)

Vì pyvi không học gì từ nhãn nên tách từ một lần cho toàn bộ dữ liệu không gây rò rỉ, nhưng TfidfVectorizer phải nằm trong pipeline, nếu không idf bị tính cả trên tập kiểm tra, đúng lỗi đã nói ở bài feature engineering cho dữ liệu bảng.

Kết quả phân loại văn bản: tách từ và n-gram đóng góp bao nhiêu

Với bài toán phân loại văn bản này, lần chạy đầu cố định mô hình LinearSVC(C=1) và đổi cách biểu diễn. Cột cuối là thời gian huấn luyện, chưa tính tách từ:

âm tiết, unigram                             acc=0.8010 macroF1=0.8005 đặc trưng=   8624 fit=0.15s
âm tiết, 1-2 gram                            acc=0.8230 macroF1=0.8225 đặc trưng=  31555 fit=0.36s
từ ghép pyvi, unigram                        acc=0.8240 macroF1=0.8235 đặc trưng=  16679 fit=0.17s
từ ghép pyvi, 1-2 gram                       acc=0.8230 macroF1=0.8222 đặc trưng=  26641 fit=0.25s
từ ghép pyvi, 1-2 gram, không sublinear      acc=0.8230 macroF1=0.8228 đặc trưng=  26641 fit=0.32s
bag-of-words đếm thô + LinearSVC             acc=0.8050 macroF1=0.8049 đặc trưng=  26641 fit=0.30s

Đọc lướt, bảng phân loại văn bản này cho thấy tách từ nâng macro-F1 từ 0,800 lên 0,824 ở mức unigram, bigram âm tiết cũng cho kết quả tương đương (0,8225), còn TF-IDF hơn đếm thô khoảng 1,7 điểm. Với phân loại văn bản, việc bỏ sublinear_tf gần như không đổi gì ở dữ liệu này (0,8228 so với 0,8222). Nhưng một lần chia 1.000 tin kiểm tra có sai số đáng kể (với accuracy 0,82, độ lệch chuẩn lý thuyết của phép đo khoảng 1,2 điểm), nên tôi chạy thêm 5-fold cross-validation phân tầng (cv_check.py, C=0.3) để xem hiệu ứng nào còn đứng vững:

âm tiết, unigram             macroF1 = 0.8120 ± 0.0061
âm tiết, 1-2 gram            macroF1 = 0.8304 ± 0.0069
từ ghép, unigram             macroF1 = 0.8285 ± 0.0045
từ ghép, 1-2 gram            macroF1 = 0.8251 ± 0.0064

Kết luận trung thực hơn cho phân loại văn bản: tách từ giúp khoảng 1,7 điểm khi chỉ dùng unigram (0,8285 so với 0,8120, lớn hơn độ lệch chuẩn giữa các fold là 0,005 đến 0,007), nhưng khi đã dùng bigram âm tiết thì lợi thế biến mất: bigram âm tiết đạt 0,8304, nhỉnh hơn bigram từ ghép (0,8251) một chút, chênh lệch nhỏ hơn độ lệch chuẩn.

Lý do dễ hiểu, cặp âm tiết liền kề như "lãi suất" tự nó đã bắt được phần lớn từ ghép. Vì vậy nếu hệ thống của bạn không muốn thêm phụ thuộc, bigram âm tiết là điểm khởi đầu rẻ và gần ngang. Trong phân loại văn bản tiếng Việt, tách từ vẫn có giá trị riêng: unigram từ ghép đạt gần cùng điểm với bigram âm tiết bằng 16.679 đặc trưng thay vì 31.555 (đếm trên tập huấn luyện của lần chia đơn), và các từ trọng số cao dễ đọc hơn khi giải thích cho người dùng.

So sánh mô hình tuyến tính trên cùng đặc trưng

LinearSVC C=1                                acc=0.8230 macroF1=0.8222 fit=0.34s
LinearSVC C=0.3                              acc=0.8270 macroF1=0.8270 fit=0.37s
LogisticRegression C=10                      acc=0.8270 macroF1=0.8278 fit=1.70s
ComplementNB                                 acc=0.8250 macroF1=0.8232 fit=0.31s

Với phân loại văn bản, bốn mô hình nằm trong khoảng dưới 0,6 điểm macro-F1 của nhau (0,8222 đến 0,8278) và dưới nửa điểm accuracy, nhỏ hơn nhiều so với sai số của một lần chia, nên không nên kết luận mô hình nào "thắng". Điều đáng nhớ trong phân loại văn bản là cả bốn đều huấn luyện dưới 2 giây. Hồi quy logistic chậm hơn khoảng năm lần nhưng cho xác suất có thể hiệu chỉnh, hữu ích khi bạn cần ngưỡng từ chối ("không chắc, chuyển cho người xem"). ComplementNB rẻ nhất và là baseline tốt cho dữ liệu lệch.

Đọc lỗi phân loại văn bản: mô hình nhầm ở đâu

Accuracy 82% của phân loại văn bản che giấu sự khác biệt giữa các lớp. Báo cáo classification_report cho mô hình C=1, từ ghép 1-2 gram (bỏ bớt 4 lớp ở giữa, đủ 10 lớp trong output.txt):

                    precision    recall  f1-score   support

Chính trị - Xã hội      0.753     0.700     0.725       100
          Khoa học      0.769     0.700     0.733       100
         Pháp luật      0.888     0.870     0.879       100
          Thể thao      0.933     0.980     0.956       100
           Văn hóa      0.779     0.810     0.794       100
          Đời sống      0.725     0.790     0.756       100

          accuracy                          0.823      1000

Trong phân loại văn bản này, Thể thao đạt F1 0,956 vì từ vựng riêng biệt (hlv, đội, trận, cầu_thủ). Chính trị - Xã hội (0,725), Khoa học (0,733) và Đời sống (0,756) thấp nhất. Các cặp nhầm nhiều nhất trong ma trận nhầm lẫn: Đời sống bị đoán thành Văn hóa 11 tin, Khoa học thành Đời sống 8 tin, Pháp luật thành Chính trị - Xã hội 7 tin, Khoa học thành Sức khỏe 7 tin.

Đây không hẳn là lỗi của mô hình phân loại văn bản, vì ranh giới chuyên mục của tòa soạn vốn mờ: một vụ án có thể xếp Pháp luật hoặc Chính trị - Xã hội, một bài về ẩm thực có thể xếp Đời sống hoặc Văn hóa. Khi độ chính xác dừng ở một ngưỡng, hãy đọc vài chục tin bị nhầm trước khi đổi thuật toán phân loại văn bản, vì có thể chính nhãn mới là chỗ cần sửa.

Mô hình học được gì: đặc trưng trọng số cao và cảnh báo rò rỉ nguồn

Ưu điểm lớn của phân loại văn bản bằng mô hình tuyến tính là ta đọc được hệ số. Lấy 8 đặc trưng có trọng số cao nhất của mỗi lớp (coef_ ghép với get_feature_names_out()), phần lớn hợp lý:

  Chính trị - Xã hội giáo_dục, trường, nlđ, du_lịch, dân, đô_thị, du_học, các trường
  Khoa học      nghiên_cứu, loài, khoa_học, vũ_trụ, trái_đất, con, các nhà, robot
  Pháp luật     công_an, án, bị_cáo, tand, vụ, nguyên, dũng, tội_phạm
  Thế giới      iraq, tổng_thống, thủ_tướng, hôm_qua, iran, tq, hạt_nhân, palestine
  Thể thao      hlv, đội, trận, cầu_thủ, bóng_đá, đội_tuyển, clb, vđv
  Vi tính       phần_mềm, internet, máy_tính, website, máy, microsoft, mạng, dữ_liệu
  Đời sống      món, chị, món ăn, lòng, anh, ăn, quán, nguyên_liệu

Nhìn kỹ có vài dấu hiệu đáng ngờ. Lớp Chính trị - Xã hội chứa "nlđ", tức viết tắt tên một tờ báo, nên mô hình đã học cả tên nguồn thay vì chỉ học chủ đề; lớp này cũng chứa "giáo_dục", "trường", "du_học" vì VNTC không có chuyên mục Giáo dục riêng, nên tin về trường học nằm ở đây (hệ quả sẽ thấy ở phần dự đoán bên dưới). "nguyên" và "dũng" ở lớp Pháp luật nhiều khả năng là tên riêng trong tin, không phải từ chỉ chủ đề. Với phân loại văn bản, tôi kiểm tra rò rỉ nguồn bằng leak_check.py: đếm số tin chứa tên nguồn báo (NLĐ, VnExpress, Tuổi Trẻ online...), xoá chúng rồi huấn luyện lại.

số tin chứa tên nguồn báo, theo chuyên mục: {'Chính trị - Xã hội': 82, 'Pháp luật': 71, 'Đời sống': 5, 'Thể thao': 25, 'Kinh doanh': 19, 'Vi tính': 3, 'Sức khỏe': 7, 'Văn hóa': 8}
macro-F1 giữ nguyên         : 0.8270
macro-F1 sau khi xoá tên nguồn: 0.8270

Trong tập phân loại văn bản này, 220 trên 5.000 tin mang tên nguồn (82 ở Chính trị - Xã hội, 71 ở Pháp luật, hầu hết là "NLĐ"), và xoá đi không làm macro-F1 thay đổi ở bốn chữ số thập phân. Vậy ở mẫu này rò rỉ có xảy ra nhưng không phải nguồn của con số 82%.

Lưu ý phép kiểm tra chỉ dùng danh sách tên báo tôi tự liệt kê; rò rỉ có thể nằm ở dấu hiệu khác (cách viết hoa, dấu câu riêng của từng tòa soạn) mà phép thử này không bắt được. Điều đáng học khi phân loại văn bản là thói quen: luôn kiểm tra đặc trưng nổi bật và thử loại bỏ những thứ không phải nội dung (tên báo, chữ ký email, số ticket) trước khi tin vào kết quả.

Lưu mô hình phân loại văn bản và dự đoán tin mới

Pipeline phân loại văn bản đóng gói được bằng joblib.dump, tệp kết quả khoảng 4,9 MB. Khi dự đoán, nhớ áp dụng đúng bước tách từ đã dùng lúc huấn luyện, vì mô hình chỉ biết các token dạng lãi_suất. Bốn tin mới tự đặt, output thật:

  Thể thao      <- Đội tuyển Việt Nam thắng đậm trong trận chung kết lượt đi
  Kinh doanh    <- Giá vàng SJC tăng mạnh, ngân hàng điều chỉnh lãi suất tiền gửi
  Chính trị - Xã hội <- Bộ Giáo dục công bố phương án thi tốt nghiệp trung học phổ thông
  Kinh doanh    <- Căn hộ chung cư tại Hà Nội tăng giá, nhà đầu tư quay lại thị trường

Kết quả dự đoán đáng đọc kỹ hơn là đúng hay sai. Câu về thể thao và giá vàng đúng như mong đợi. Câu về thi tốt nghiệp bị xếp Chính trị - Xã hội và câu về căn hộ bị xếp Kinh doanh, không phải vì mô hình hỏng mà vì bộ nhãn VNTC không có chuyên mục Giáo dục hay Bất động sản: mô hình chỉ chọn được trong 10 nhãn đã học và gán vào nhãn gần nhất.

Đây là bài học về phạm vi nhãn của phân loại văn bản, và bốn câu tự đặt không chứng minh chất lượng tổng thể; con số 82% trên tập kiểm tra mới là thước đo, kèm lưu ý rằng nó đo trên tin những năm 2000. Tệp joblib thực chất là pickle, chỉ nạp tệp do chính bạn tạo, và nên ghim phiên bản scikit-learn như bài về môi trường Python cho dự án dữ liệu đã hướng dẫn.

Khi nào không nên dùng cách phân loại văn bản này

  • Cần hiểu ngữ nghĩa: phân loại văn bản bằng TF-IDF chỉ thấy từ, không thấy nghĩa; "không hài lòng" và "hài lòng" chia sẻ gần hết từ vựng, mô hình TF-IDF khó phân biệt phủ định, mỉa mai. Phân tích cảm xúc thường cần mô hình ngữ cảnh như PhoBERT.
  • Từ ngoài từ vựng: tên sản phẩm mới, từ lóng, viết tắt, tiếng Việt không dấu sẽ không có trong từ vựng; thêm n-gram ký tự (analyzer="char_wb") giúp một phần.
  • Nhãn quá mờ hoặc quá ít dữ liệu: như Chính trị - Xã hội và Đời sống ở trên, kiến trúc mạnh hơn không cứu được nhãn mâu thuẫn.
  • Nhãn thay đổi theo thời gian: chủ đề của phân loại văn bản lệch (dịch bệnh, bầu cử) làm từ vựng cũ mất tác dụng; cần giám sát và huấn luyện lại định kỳ.

Ngược lại, với phân loại văn bản khi có vài nghìn mẫu có nhãn tốt, cần độ trễ mili giây trên CPU, cần giải thích cho người vận hành và cần baseline trước khi thử mô hình lớn, cách này là lựa chọn hợp lý và rẻ nhất.

Lỗi thường gặp khi phân loại văn bản

  1. Fit TF-IDF trên cả train lẫn test: idf thấm thông tin của tập kiểm tra. Luôn để vectorizer trong Pipeline.
  2. Danh sách từ dừng không cùng cách tách từ: mẫu token_pattern mặc định giữ nguyên lãi_suất vì \w tính gạch dưới là ký tự chữ, nhưng tài liệu scikit-learn cảnh báo từ dừng phải qua cùng bước tiền xử lý như vectorizer.
  3. Tin vào một lần chia train/test: chênh 1 điểm nằm trong nhiễu; dùng cross-validation và báo cáo độ lệch chuẩn.
  4. Không đọc đặc trưng nổi bật: bỏ lỡ rò rỉ nguồn báo, mã ticket, chữ ký.
  5. Khử trùng chỉ trong từng nhãn hoặc sau khi chia: tin trùng sang cả huấn luyện và kiểm tra làm điểm cao giả; khử trùng trên toàn bộ tập trước khi chia, và nhớ rằng tin gần trùng vẫn lọt qua khoá khớp chính xác.
  6. Quên tách từ khi dự đoán: mô hình đã học token dạng lãi_suất, đưa vào chuỗi gốc sẽ rơi hết vào từ lạ.

Checklist phân loại văn bản tiếng Việt

  • Với mọi dự án phân loại văn bản, cân bằng hoặc ít nhất phân tầng khi chia dữ liệu; ghi hạt giống.
  • Bắt đầu bằng TF-IDF với bigram âm tiết, so với tách từ bằng cross-validation, không bằng một lần chia.
  • Đặt vectorizer và bộ phân loại trong cùng một Pipeline của bài toán phân loại văn bản.
  • Xem báo cáo từng lớp, ma trận nhầm lẫn và các đặc trưng trọng số cao.
  • Thử loại bỏ tên nguồn và chữ ký trước khi tin con số phân loại văn bản.
  • Ghim phiên bản thư viện, chỉ nạp mô hình do mình tạo.

Mã nguồn phân loại văn bản gồm prepare_data.py, classify_demo.py, cv_check.py, leak_check.py kèm output.txt và requirements.txt; chạy trên Apple M4, 16 GB, Python 3.12 với tập mẫu 5.000 tin. Mọi con số trong bài là output thật của các lần chạy đó, và sẽ thay đổi nếu bạn lấy mẫu khác.

Bài tiếp theo

Word embedding: Word2Vec, FastText và biểu diễn nghĩa của từ. TF-IDF coi "ô tô" và "xe hơi" là hai cột không liên quan; bài sau huấn luyện embedding để từ cùng nghĩa nằm gần nhau và đo điều đó bằng số thật.