Dùng AI

Phân loại văn bản bằng mô hình ngôn ngữ thay vì tự huấn luyện

Muốn phân loại góp ý của học viên, email hỗ trợ hay bình luận thành vài nhóm, bạn có thể dùng mô hình ngôn ngữ lớn với một chỉ dẫn mô tả các nhãn, không cần gom hàng nghìn câu có nhãn để huấn luyện. Bài này nói khi nào cách này hợp lý theo hướng dẫn của Anthropic, cách viết nhãn và ép đầu ra vào danh sách nhãn, và cách đo bằng một bộ câu có nhãn, kèm đoạn Python có mốc so sánh bằng từ khóa.

Ý chính
  • Mô hình ngôn ngữ phân loại được văn bản chỉ với chỉ dẫn mô tả nhãn và vài ví dụ, hợp khi ít dữ liệu có nhãn, nhãn hay thay đổi, câu chữ phức tạp hoặc nhiều ngôn ngữ.
  • Ép đầu ra vào danh sách nhãn bằng JSON có enum để không nhận về nhãn lạ.
  • Luôn đo bằng một bộ câu có nhãn, so với một mốc đơn giản, và đo cả độ nhất quán khi chạy lại.
  • Khi lượng văn bản rất lớn và nhãn cố định, mô hình nhỏ tự huấn luyện có thể rẻ và nhanh hơn. Quyết định bằng số đo, không bằng cảm giác.

Bài này dựa trên hướng dẫn Ticket routing của Anthropic (đọc ngày 11/11/2026), bài báo của Wang, Pang và Lin (2023) về mô hình ngôn ngữ làm bộ phân loại không cần huấn luyện, và trang các chỉ số đánh giá của scikit-learn.

Khi nào dùng mô hình ngôn ngữ để phân loại

Cách truyền thống là gom dữ liệu, gán nhãn tay hàng nghìn câu, rồi huấn luyện một mô hình phân loại. Hướng dẫn của Anthropic liệt kê những dấu hiệu nên dùng mô hình ngôn ngữ thay vào đó: có ít dữ liệu có nhãn (hướng dẫn nói vài chục ví dụ có nhãn đã đủ để phân loại hiệu quả), các nhóm phân loại hay thay đổi, văn bản tự do khó trích đặc trưng, quy tắc phân loại dựa vào hiểu nghĩa, cần giải thích vì sao gán nhãn, cần xử lý câu mơ hồ, và cần nhiều ngôn ngữ mà không muốn giữ nhiều mô hình.

Về mặt nghiên cứu, Wang và cộng sự (2023) thử các mô hình GPT phân loại văn bản chỉ bằng prompt, không huấn luyện thêm, và thấy kết quả cạnh tranh được với các cách học máy, học sâu và các cách không huấn luyện khác trên ba trong bốn bộ dữ liệu. Đây là một nghiên cứu cụ thể trên dữ liệu tiếng Anh; với dữ liệu tiếng Việt của bạn, cần tự đo.

Ngược lại, nếu mỗi ngày có hàng triệu câu, nhãn đã cố định nhiều năm và bạn có sẵn dữ liệu có nhãn, một mô hình nhỏ tự huấn luyện thường rẻ hơn và nhanh hơn nhiều. Bài fine-tuning là gì bàn thêm về lựa chọn này.

Viết nhãn cho rõ

Mô hình chỉ biết nhãn qua mô tả của bạn. Nhãn mơ hồ cho kết quả mơ hồ. Mấy điều nên làm:

  • Mỗi nhãn một câu định nghĩa, nói rõ khác các nhãn khác ở đâu. Ví dụ "góp ý: đề xuất cách làm tốt hơn" khác "chê: không hài lòng".
  • Nói rõ cách xử lý câu có hai ý, như "rất hay nhưng nên chậm lại": theo ý chính, hay theo ý đề xuất.
  • Thêm vài ví dụ khó vào chỉ dẫn, nhất là những câu mà chính bạn phải nghĩ lâu mới gán được.
  • Có nhãn cho trường hợp không thuộc nhóm nào, để mô hình không bị ép chọn sai.

Hướng dẫn của Anthropic cũng gợi ý cho mô hình viết một lý do ngắn: dễ dò lỗi và giúp người xem tin kết quả hơn.

Từ khóa sai ở đâu Mốc từ khóa: đúng 8/12 câu mẫu Câu mỉa mai"Hay thật, vẫn treo"đoán khen, đúng là chê Câu phủ định"Không hay lắm"đoán khen, đúng là chê Câu hai ý"Rất hay nhưng nên..."đoán khen, là góp ý Mô hình ngôn ngữĐo cùng bộ câuchính xác, nhất quán Câu và nhãn là dữ liệu giả định
Từ khóa sai ở đâu Câu mỉa mai"Hay thật, vẫn treo"đoán khen, đúng là chê Câu phủ định"Không hay lắm"đoán khen, đúng là chê Câu hai ý"Rất hay nhưng nên..."đoán khen, là góp ý Mô hình ngôn ngữĐo cùng bộ câuchính xác, nhất quán Câu và nhãn là dữ liệu giả định
Hình 1. Ba loại câu làm phân loại bằng từ khóa sai trong bộ câu mẫu: câu mỉa mai ("Hay thật, vẫn treo", đoán khen nhưng là chê), câu phủ định ("Không hay lắm", đoán khen nhưng là chê), câu hai ý ("Rất hay nhưng nên...", đoán khen nhưng là góp ý). Mốc từ khóa đúng 8/12 câu. Mô hình ngôn ngữ được đo bằng cùng bộ câu, cả độ chính xác lẫn độ nhất quán. Câu và nhãn là dữ liệu giả định.

Ví dụ: phân loại góp ý của học viên

Chương trình dưới đây phân loại góp ý vào bốn nhãn: khen, chê, góp ý, hỏi. Hàm phan_loai gọi Claude với chỉ dẫn mô tả nhãn và schema JSON, trong đó trường nhãn chỉ được nhận một trong bốn giá trị (cách làm trong bài bắt AI trả về JSON). Nhãn trả về được đưa về chữ thường trước khi so, và trả về rỗng nếu mô hình từ chối hay bị cắt. Mô hình mặc định là Claude Haiku 5.5; hướng dẫn của Anthropic gọi đây là mô hình nhanh và rẻ nhất hiện tại, hợp với việc phân loại số lượng lớn.

"""Phân loại góp ý của học viên thành 4 nhãn bằng mô hình ngôn ngữ, và đo bằng một bộ câu có nhãn.

- phan_loai: gọi Claude với structured outputs, nhãn nằm trong enum nên không bị nhãn lạ.
- tu_khoa: cách làm cũ bằng từ khóa, dùng làm mốc so sánh.
- danh_gia: độ chính xác, recall từng nhãn, danh sách câu sai. Dùng cùng hàm này cho mọi cách phân loại.
Bộ câu DU_LIEU là dữ liệu giả định. Cài: python -m pip install anthropic ; đặt ANTHROPIC_API_KEY để gọi thật.
"""
import json
import os
import sys
from collections import Counter

NHAN = ["khen", "chê", "góp ý", "hỏi"]
SCHEMA = {
    "type": "object",
    "properties": {"nhan": {"type": "string", "enum": NHAN}, "ly_do": {"type": "string"}},
    "required": ["nhan", "ly_do"],
    "additionalProperties": False,
}
HE_THONG = ("Phân loại góp ý của học viên vào đúng một nhãn. khen: hài lòng; chê: không hài lòng; "
            "góp ý: đề xuất cách làm tốt hơn; hỏi: cần thông tin. Lý do viết một câu ngắn.")

DU_LIEU = [
    ("Thầy giảng dễ hiểu, em làm được bài ngay", "khen"),
    ("Bài tập tuần này hay quá", "khen"),
    ("Hay thật, nộp bài ba lần máy chấm vẫn treo", "chê"),
    ("Video bị rè, nghe không rõ", "chê"),
    ("Không hay lắm, ví dụ khó theo", "chê"),
    ("Nên thêm lời giải mẫu sau mỗi buổi", "góp ý"),
    ("Thầy có thể đăng slide trước buổi học không ạ", "góp ý"),
    ("Mong lớp có thêm buổi chữa bài", "góp ý"),
    ("Khi nào có điểm bài kiểm tra giữa kỳ?", "hỏi"),
    ("Em nộp bài muộn thì có bị trừ điểm không?", "hỏi"),
    ("Học phí khóa sau là bao nhiêu?", "hỏi"),
    ("Bài giảng rất hay nhưng nên chậm lại ở phần đệ quy", "góp ý"),
]


def tu_khoa(cau):
    c = cau.lower()
    if "?" in c:
        return "hỏi"
    if any(t in c for t in ("hay", "dễ hiểu", "tốt")):
        return "khen"
    if any(t in c for t in ("không", "rè", "treo", "lỗi", "khó")):
        return "chê"
    return "góp ý"


def phan_loai(client, cau, mo_hinh="claude-haiku-5-5"):
    tl = client.messages.create(
        # Haiku 5.5 bật suy nghĩ sẵn và token suy nghĩ tính vào max_tokens, nên 300 token thì phần suy nghĩ ăn
        # hết và lượt gọi dừng ở stop_reason "max_tokens", không ra nhãn nào. Chính hướng dẫn Ticket routing
        # của Anthropic dùng max_tokens=2048 kèm output_config effort thấp cho việc phân loại.
        model=mo_hinh, max_tokens=2048, system=HE_THONG,
        messages=[{"role": "user", "content": cau}],
        output_config={"effort": "low", "format": {"type": "json_schema", "schema": SCHEMA}},
    )
    if tl.stop_reason in ("refusal", "max_tokens"):
        return None
    nhan = json.loads(next(k.text for k in tl.content if k.type == "text"))["nhan"].lower()
    return nhan if nhan in NHAN else None


def danh_gia(du_doan, du_lieu):
    dung = [n for _, n in du_lieu]
    dem_dung = Counter(n for n, d in zip(dung, du_doan) if n == d)
    tong = Counter(dung)
    sai = [(c, n, d) for (c, n), d in zip(du_lieu, du_doan) if n != d]
    recall = {k: dem_dung[k] / tong[k] for k in NHAN if tong[k]}  # bỏ nhãn không có câu nào trong bộ đo
    return sum(dem_dung.values()) / len(dung), recall, sai


def ty_le_nhat_quan(cac_lan):
    """cac_lan: danh sách các lượt chạy, mỗi lượt là danh sách nhãn. Tỷ lệ câu cho cùng nhãn ở mọi lượt."""
    return sum(len(set(nhan)) == 1 for nhan in zip(*cac_lan)) / len(cac_lan[0])


def in_ket_qua(ten, du_doan):
    dd, recall, sai = danh_gia(du_doan, DU_LIEU)
    dong = [f"{ten}: đúng {dd:.0%} ({len(DU_LIEU) - len(sai)}/{len(DU_LIEU)})",
            "  recall: " + ", ".join(f"{k} {v:.0%}" for k, v in recall.items())]
    dong += [f"  sai: '{c}' -> đoán {d}, đúng là {n}" for c, n, d in sai]
    return dong


def chay_thu():
    dong = in_ket_qua("Từ khóa", [tu_khoa(c) for c, _ in DU_LIEU])
    print("\n".join(dong))
    return dong


def main():
    if not os.environ.get("ANTHROPIC_API_KEY"):
        sys.exit("Chưa có ANTHROPIC_API_KEY. Đặt biến môi trường trước khi chạy.")
    import anthropic
    client = anthropic.Anthropic()
    try:
        du_doan = [phan_loai(client, c) for c, _ in DU_LIEU]
    except anthropic.APIStatusError as e:
        sys.exit(f"API báo lỗi {e.status_code}: {e.message}")
    except anthropic.APIConnectionError:
        sys.exit("Không kết nối được tới API. Kiểm tra mạng rồi thử lại.")
    print("\n".join(in_ket_qua("Từ khóa", [tu_khoa(c) for c, _ in DU_LIEU]) + in_ket_qua("Claude", du_doan)))


if __name__ == "__main__":
    main() if "--goi-that" in sys.argv else chay_thu()

Chạy không có tham số, chương trình chỉ chạy mốc so sánh: phân loại bằng từ khóa (có dấu hỏi là hỏi, có chữ khen là khen, có chữ chê là chê, còn lại là góp ý) trên 12 câu mẫu. Kết quả đúng 8/12 câu, tức 67%. Bốn câu sai đều thuộc loại khó với từ khóa: câu mỉa mai "Hay thật, nộp bài ba lần máy chấm vẫn treo" bị đoán là khen vì có chữ "hay"; câu phủ định "Không hay lắm" cũng bị đoán là khen; câu hỏi lịch sự không có dấu hỏi bị đoán là chê vì có chữ "không"; câu vừa khen vừa đề xuất bị đoán là khen.

Chạy với tham số gọi thật (cần khóa API), chương trình phân loại cùng 12 câu bằng Claude và in kết quả của cả hai cách bằng cùng một hàm đánh giá. Bài không ghi sẵn con số cho mô hình: bạn tự chạy và xem trên dữ liệu của mình. Mười hai câu là quá ít để kết luận; bộ đo thật nên có ít nhất vài trăm câu, lấy từ dữ liệu thực tế, có đủ các loại câu khó.

Đo những gì

Độ chính xác và recall từng nhãn. Độ chính xác chung có thể che mất một nhãn bị làm kém. Xem recall từng nhãn: trong các câu thật sự là "chê", bao nhiêu câu được nhận ra. Trang chỉ số của scikit-learn có đủ các chỉ số như precision, recall, F1 và ma trận nhầm lẫn nếu bạn muốn đo kỹ hơn.

Độ nhất quán. Chạy cùng bộ câu vài lần, xem bao nhiêu câu luôn nhận cùng nhãn. Hàm ty_le_nhat_quan trong code tính tỷ lệ này. Hướng dẫn của Anthropic đặt mục tiêu ví dụ từ 95% trở lên cho việc phân loại phiếu hỗ trợ.

Chi phí và thời gian. Mỗi câu là một lần gọi API. Nhân số câu mỗi ngày với số token mỗi lần và giá để biết chi phí tháng. Chỉ dẫn dài với nhiều ví dụ làm mỗi lần gọi đắt hơn; với khối lượng lớn, xem thêm cách gửi theo lô và lưu đệm prompt.

Một cách ở giữa: embedding cộng mô hình nhỏ

Giữa hai đầu "gọi mô hình lớn cho từng câu" và "tự huấn luyện từ đầu" có một cách thường bị quên. Biến mỗi câu thành embedding một lần, rồi huấn luyện một mô hình phân loại rất nhỏ như hồi quy logistic trên các vector đó với vài trăm câu có nhãn. Cách này chạy nhanh, rẻ khi số câu lớn, và vẫn tận dụng được hiểu biết về nghĩa có sẵn trong mô hình embedding. Điểm yếu là mỗi lần đổi nhãn phải gán nhãn và huấn luyện lại. Một quy trình hợp lý: dùng mô hình ngôn ngữ để gán nhãn nháp cho vài trăm câu, người kiểm lại, rồi dùng chính bộ đó để huấn luyện mô hình nhỏ và làm bộ đo. Xem bài embedding là gì.

Khi kết quả chưa tốt

Đọc từng câu sai và hỏi: nhãn có định nghĩa rõ chưa, câu này người gán có đồng ý với nhau không, chỉ dẫn có ví dụ nào giống không. Thường sửa định nghĩa nhãn và thêm ví dụ khó giúp nhiều hơn đổi mô hình. Nếu hai người gán tay còn bất đồng ở một loại câu, đừng mong mô hình làm tốt hơn: hãy làm rõ quy tắc trước.

Theo dõi sau khi dùng thật. Văn bản đổi theo thời gian (khóa học mới, lỗi mới), nên định kỳ lấy mẫu câu mới, gán nhãn tay, đo lại. Bài đánh giá câu trả lời AI có thêm cách dựng bộ đo.

Cẩn thận
  • Không có bộ câu có nhãn để đo, chỉ nhìn vài câu thấy đúng là dùng.
  • Nhãn chồng chéo, không nói cách xử lý câu có hai ý.
  • Nhận đầu ra dạng chữ tự do rồi tự đoán nhãn từ chữ.
  • Chỉ xem độ chính xác chung, bỏ qua nhãn ít câu.
  • Dán dữ liệu cá nhân của người dùng vào công cụ AI mà không được phép.

Câu hỏi thường gặp

Cần bao nhiêu ví dụ trong chỉ dẫn?

Bắt đầu không có hoặc vài ví dụ, đo, rồi thêm ví dụ cho những loại câu hay sai. Nhiều ví dụ quá làm tốn token mỗi lần gọi.

Dùng mô hình lớn hay nhỏ?

Bắt đầu với mô hình nhỏ, nhanh. Chỉ chuyển lên mô hình lớn hơn nếu bộ đo cho thấy mô hình nhỏ không đạt.

Một câu có nhiều nhãn thì sao?

Dùng schema cho phép một mảng nhãn thay vì một nhãn, và đo theo từng nhãn.

Tiếng Việt không dấu có được không?

Thường vẫn hiểu, nhưng nên đưa câu không dấu vào bộ đo để biết chắc.

Học tiếp

Nếu bạn muốn học Python và AI bài bản cùng giảng viên, tham khảo khóa học AI của Big-O Coding.

Muốn học bài bản cùng giảng viên?

Khóa AI – Introduction to Machine Learning: nền tảng Máy học từ thuật toán cơ bản đến Neural Networks và LLM, học bằng Python. Học thử buổi 1 miễn phí.

Xem Khóa AI

Nguồn tham khảo

Chia sẻ: Facebook LinkedIn
Nhắn Zalo tư vấnZalo Gọi 0937 401 483