Bạn đang phát triển một màn hình quản lý tài liệu. Mục tiêu tưởng như đơn giản: đề xuất một danh mục cho từng tài liệu để việc sắp xếp dễ hơn. Bạn bắt đầu bằng vài điều kiện: tiêu đề có chứa « brouillon », một đoạn văn yêu cầu rà soát, một câu khác thông báo phê duyệt.
Sau đó xuất hiện tài liệu hư cấu V-033. Tiêu đề có « validé », nhưng nội dung lại ghi « pour relecture ». Nên tin quy tắc nào? Luôn ưu tiên tiêu đề không giải quyết được vấn đề: tiêu đề cũng có thể chứa phủ định hoặc một ghi chú cũ.
Đây là điểm khởi đầu của giám sát yếu: dùng những dấu hiệu chưa hoàn hảo để xây dựng dữ liệu huấn luyện mà không coi chúng là câu trả lời chắc chắn. Ở đây, chúng ta sẽ viết các quy tắc, học cách kết hợp ý kiến của chúng, rồi huấn luyện một bộ phân loại văn bản từ kết quả đó.
Chương trình đề xuất các lớp cho tài liệu hư cấu. Nó không thay đổi bất kỳ trạng thái hành chính nào và không suy ra một sự phê duyệt thực từ tiêu đề. Câu hỏi của chúng ta khiêm tốn hơn nhưng đo được: chuỗi này có học được điều hữu ích hay không, hay làm kém hơn một phương pháp đơn giản hơn?
Tải xuống dự án hoàn chỉnh của hướng dẫn
Những gì bạn sẽ xây dựng
Quy trình là tài liệu → phiếu bầu của quy tắc → xác suất các lớp → bộ phân loại văn bản → đề xuất.
Một lớp đơn giản là một danh mục cần dự đoán. Một quy tắc có thể đề xuất một lớp hoặc không đưa ra ý kiến. Mô hình đầu tiên nhận các phiếu bầu này và ước lượng một phân bố xác suất giữa ba lớp. Ví dụ, nó có thể dành xác suất cao hơn cho EN_REVUE trong khi vẫn còn phân vân với ANNONCE_APPROUVE.
Mô hình thứ hai, bộ phân loại văn bản, sau đó học từ văn bản tài liệu và các phân bố này. Vì vậy hai mô hình có đầu vào khác nhau: mô hình thứ nhất kết hợp ý kiến của các quy tắc; mô hình thứ hai học phân loại văn bản. Hướng dẫn này không dùng mô hình ngôn ngữ lớn để soạn câu trả lời.
Vì sao không dừng ở các quy tắc? Vì chúng ta muốn kiểm tra xem việc kết hợp chúng có thể cung cấp tín hiệu huấn luyện hữu ích cho bộ phân loại hay không. Đó là một khả năng cần đánh giá, không phải lợi ích đã có sẵn. Nguyên lý chung được trình bày trong phần giới thiệu về giám sát yếu của nhóm nghiên cứu Stanford. Dự án trong ZIP trình bày một cơ chế giới hạn mà chúng ta có thể theo dõi các phép tính.
Chúng ta sẽ so sánh bốn phương pháp: bỏ phiếu đa số, mô hình kết hợp phiếu bầu, bộ phân loại được huấn luyện trên một số chú thích đáng tin cậy và bộ phân loại dùng giám sát yếu. Đồng thời, chúng ta sẽ giải thích việc không bỏ phiếu, biến ẩn và thuật toán EM mà không giả định bạn đã biết chúng.
Một quy tắc là một dấu hiệu chưa hoàn hảo, không phải chú thích đáng tin cậy.
Chuẩn bị môi trường CPU có thể tái lập
Bạn cần biết đọc Python, dùng terminal và chạy Docker. Ở đây, một bảng số là tập hợp các số được tổ chức thành hàng và cột; chúng ta sẽ nói rõ các kích thước của nó biểu thị điều gì ở từng bước.
Dự án này được kiểm chứng trên CPU, tức bộ xử lý của máy. Không cần GPU NVIDIA. README khuyến nghị 12 GiB RAM và 6 GiB dung lượng làm việc, ngoài phần lưu trữ Docker. Đây là chỉ dẫn chuẩn bị, không phải mức tiêu thụ được đảm bảo.
Giải nén ZIP và mở terminal trong thư mục gốc của nó, nơi có README.md. Mọi lệnh sau đây đều chạy từ thư mục này. Hãy kiểm tra môi trường, chuẩn bị thư mục rồi xây dựng các dịch vụ.
sh scripts/preflight.sh
sh scripts/setup.sh
docker compose config --quiet
docker compose build acquisition tutorial learning inference
Script đầu tiên thực hiện các kiểm tra trước. Script thứ hai chuẩn bị các thư mục. docker compose config --quiet kiểm tra cấu hình Compose; build xây dựng các môi trường cần thiết cho quy trình. Hãy sửa một lỗi chặn trước khi sang bước tiếp theo.
Các dịch vụ tách vai trò: tutorial chuẩn bị dữ liệu và đánh giá; learning nhận đầu vào huấn luyện và các chú thích được phép; inference tạo dự đoán mà không xem đáp án mong đợi. assets/ lưu các trạng thái đã học và outputs/ lưu kết quả. Tệp Compose trong ZIP nêu chi tiết các mount và hạn chế.
Mô hình được khởi tạo cục bộ: chúng ta sẽ không tải trọng số của mô hình được huấn luyện trước. Ở đây bước acquisition kiểm tra một sổ đăng ký trống và không tải trọng số bên thứ ba nào. Nó vẫn có mặt trong quy trình, trước kiểm tra môi trường container hóa và các kiểm thử.
docker compose run --rm acquisition python scripts/acquire.py --config configs/minimum.yaml
docker compose run --rm tutorial python scripts/check_environment.py
docker compose run --rm tutorial python -m pytest -q
Quy trình tham chiếu báo cáo 24 kiểm thử thành công. Chúng kiểm tra hành vi của chương trình và việc từ chối các trường hợp không hợp lệ. Đừng nhầm chúng với phép đo chất lượng tài liệu: kiểm thử thành công không có nghĩa mọi đề xuất lớp đều đúng.
Đọc dữ liệu và chú thích
Ba lớp của dự án là BROUILLON, EN_REVUE và ANNONCE_APPROUVE. Lớp cuối chỉ một thông báo trong tài liệu hư cấu, không phải phê duyệt pháp lý. Văn bản có thể chứa phủ định, đề cập cũ hoặc tiêu đề gây hiểu lầm.
Mã biểu diễn các lớp bằng số nguyên. Đây là từ điển nhỏ cần ghi nhớ:
| Giá trị | Ý nghĩa |
|---|---|
0 |
BROUILLON |
1 |
EN_REVUE |
2 |
ANNONCE_APPROUVE |
-1 |
Không bỏ phiếu: không đề xuất lớp nào. |
-1 không phải là danh mục tài liệu thứ tư. Đó là thông tin về hành vi của một quy tắc hay phương pháp: nó không đưa ra ý kiến.
Một chú thích, còn gọi là label hay nhãn, cho biết lớp tham chiếu của một ví dụ. Corpus có các tập riêng cho huấn luyện, xác thực và kiểm thử. Huấn luyện dùng để điều chỉnh tham số; xác thực giúp xem xét hành vi trong quá trình phát triển; kiểm thử dùng để đo kết quả cuối trên các tài liệu tách biệt.
Tập kiểm thử có 360 tài liệu, với 120 tham chiếu cho mỗi lớp. Chúng ta cũng có 90 điểm neo: các ví dụ huấn luyện đã biết lớp. Từ « điểm neo » mô tả vai trò của chúng: chúng cho mô hình trung gian các mốc tin cậy. Chúng cũng dùng để huấn luyện một bộ phân loại nhỏ để so sánh, không có giám sát yếu.
Vì vậy đây không phải học tập hoàn toàn không có chú thích. Câu hỏi là các quy tắc mang lại gì ngoài những ví dụ đáng tin cậy này.
Hãy tạo dữ liệu.
docker compose run --rm tutorial python -m ged_supervision_faible_statuts.cli prepare --config configs/minimum.yaml
Hãy quan sát sự tách biệt giữa văn bản và chú thích. Chương trình có thể đọc tiêu đề, nội dung và siêu dữ liệu được phép của một tài liệu. Nó không được đọc đáp án kiểm thử để tạo dự đoán. 90 điểm neo là phần giám sát được cho phép rõ ràng; các tham chiếu kiểm thử chỉ dành cho đánh giá.
Xây dựng các quy tắc có thể không bỏ phiếu
Một quy tắc đưa ra ý kiến, không phải sự chắc chắn
Một hàm gán nhãn là hàm Python tìm một dấu hiệu và trả về một lớp, hoặc -1 khi không có ý kiến. Với lập trình viên web, hãy nghĩ đến khác biệt giữa giá trị vắng mặt và giá trị bằng không: ở đây, « tôi không biết » không bao giờ được mặc định thành BROUILLON.
Dự án có tám quy tắc. Một số kiểm tra tiêu đề, số khác kiểm tra nội dung; một quy tắc khác dùng ngày xuất. Quy tắc cuối cố ý mã hóa một tương quan hư cấu mong manh: xuất muộn không chứng minh được phê duyệt.
Đây là hàm trung tâm. Các đoạn trích trong bài cần được đọc cùng các hằng số và hàm phụ trợ của ZIP; các lệnh thực thi toàn bộ dự án.
def apply_label_functions(document):
title = normalize(document["title"])
body = normalize(document["text"])
date = document.get("export_date")
# Deliberately imperfect: title rule ignores negation, body approval excludes quoted and negated mentions.
tests = [
("valide" in title, 2, "validé dans le titre"),
("provisoire" in title or "brouillon" in title, 0, "titre provisoire"),
("relecture" in title, 1, "titre relecture"),
(
"pour relecture" in body and "pas pour relecture" not in body,
1,
"corps pour relecture",
),
("version de travail" in body, 0, "corps version de travail"),
(
"annonce une approbation obtenue" in body
and "aucune signature" not in body,
2,
"annonce positive et absence de négation détectée",
),
(
bool(date and date >= "2026-08-26"),
2,
"export tardif : corrélation fictive, pas approbation",
),
("commentaires attendus" in body, 1, "commentaires attendus"),
]
return [
{
"rule": name,
"vote": k if active else ABSTAIN,
"reason": reason if active else "aucun motif reconnu",
}
for name, (active, k, reason) in zip(RULES, tests)
]
normalize() thống nhất chữ hoa/thường và dấu trước khi tìm kiếm. Điều này giúp so sánh chuỗi, nhưng không trao cho quy tắc khả năng hiểu câu.
Mỗi phần tử của tests có ba thông tin: một điều kiện, số lớp được đề xuất nếu điều kiện đúng và lời giải thích bằng văn bản. zip(RULES, tests) ghép các phần tử này với tên quy tắc. Kết quả giữ lại rule, vote và reason, nhờ đó có thể lần theo nguồn gốc của một ý kiến.
Hãy nhìn điều kiện đầu tiên: nó chỉ tìm valide trong tiêu đề đã chuẩn hóa. Nó cố ý bỏ qua phủ định. Là phản ví dụ sư phạm, tiêu đề « Non validé » cũng chứa chuỗi này sau chuẩn hóa. Vì vậy quy tắc có thể bỏ phiếu sai hướng trong khi vẫn chạy đúng đoạn mã đã định.
Các điều kiện trong nội dung chọn lọc hơn một chút: một điều kiện loại trừ cách diễn đạt pas pour relecture, điều kiện khác kiểm tra sự vắng mặt của aucune signature. Đây là các kiểm tra chuỗi chính xác, không phải bộ phân tích tổng quát về phủ định hoặc trích dẫn. Bình luận về đoạn trích cần được hiểu trong phạm vi hẹp này.
Cuối cùng, ngày 2026-08-26 là ngưỡng đặt trong quy tắc xuất của corpus hư cấu. Đây không phải ngày cần thay bằng ngày hiện tại, cũng không phải quy tắc nghiệp vụ để áp dụng nguyên xi.
Với V-033, tiêu đề và nội dung vì thế cho chúng ta các dấu hiệu mâu thuẫn. Chúng ta sẽ giữ chúng thay vì che giấu mâu thuẫn bằng một thứ tự điều kiện tùy ý.
Bắt đầu bằng đa số đơn giản
Trước khi học cách kết hợp các quy tắc, hãy tính một mốc tham chiếu không cần huấn luyện, hay baseline: đếm phiếu bầu.
docker compose run --rm learning python -m ged_supervision_faible_statuts.cli baseline --config configs/minimum.yaml
outputs/rules.json mô tả mâu thuẫn và việc không bỏ phiếu. Phương pháp đa số trả về ý kiến trống nếu không quy tắc nào bỏ phiếu hoặc nhiều lớp đồng hạng dẫn đầu. Nó không chọn lớp đầu tiên chỉ để tạo ra câu trả lời.
Bảng votes có một hàng cho mỗi tài liệu và một cột cho mỗi quy tắc, tức tám cột trong quy trình chính. Hàm sau biến bảng này thành tỷ lệ phiếu bầu và quyết định.
def majority_vote(votes):
counts = np.stack([(votes == k).sum(1) for k in range(3)], axis=1)
total = counts.sum(1)
probabilities = np.divide(
counts,
total[:, None],
out=np.full(counts.shape, 1 / 3, dtype=np.float64),
where=total[:, None] > 0,
)
tied = (counts == counts.max(1, keepdims=True)).sum(1) > 1
predicted = np.where((total == 0) | tied, -1, counts.argmax(1))
return probabilities, predicted
Với mỗi lớp k, (votes == k).sum(1) đếm các ý kiến tương ứng trên từng tài liệu. np.stack(..., axis=1) tập hợp các số đếm này thành ba cột, theo thứ tự BROUILLON, EN_REVUE, ANNONCE_APPROUVE. total chỉ đếm phiếu đã nêu: -1 không được cộng vào lớp nào.
total[:, None] đưa tổng về dạng cột để chia ba số đếm của một hàng cho cùng một số. Tham số where tránh chia cho không. Khi không quy tắc nào lên tiếng, out cung cấp phân bố đều: một phần ba cho mỗi lớp.
Nhưng phân bố chưa phải là quyết định. tied phát hiện các trường hợp bằng nhau, và np.where áp đặt dự đoán -1 khi tổng bằng không hoặc nhiều lớp thắng.
Hãy lấy ba ví dụ sư phạm nhỏ, giới hạn ở ba quy tắc để dễ đọc các phép tính. Đây không phải đầu ra đầy đủ của V-033.
Với các phiếu [1, 1, 2], số đếm là [0, 2, 1]. Phân bố là [0 ; 2/3 ; 1/3] và quyết định là 1, tức EN_REVUE.
Với [2, 1, -1], số đếm là [0, 1, 1]. Phân bố là [0 ; 0,5 ; 0,5], nhưng quyết định vẫn là -1: có đồng hạng.
Với [-1, -1, -1], phân bố là [1/3 ; 1/3 ; 1/3] và quyết định cũng là -1. Lần này không ai bỏ phiếu.
Hai trường hợp cuối dẫn đến cùng một sự không bỏ phiếu, nhưng không vì cùng lý do. Giữ lại phiếu bầu và xác suất giúp hiểu sự khác biệt này.
Ước lượng một lớp ẩn bằng EM
Vấn đề: biết các lớp để đánh giá quy tắc… và ngược lại
Bỏ phiếu đa số cho mọi ý kiến cùng trọng số. Chúng ta muốn đi xa hơn: một quy tắc có thường sai với một lớp không? Nó có nhầm bản nháp với tài liệu đang rà soát không? Ý kiến của nó có đóng góp gì khi các quy tắc khác mâu thuẫn với nó không?
Khó khăn có tính vòng tròn. Để biết lỗi của các quy tắc, chúng ta cần biết lớp của tài liệu. Để ước lượng các lớp này, chính là chúng ta muốn dùng các quy tắc.
Một biến ẩn là thông tin không được quan sát trực tiếp. Ở đây, đó là lớp tham chiếu của một tài liệu khi nó không thuộc các điểm neo. Mô hình sẽ duy trì các giả thuyết xác suất về lớp này thay vì quyết định ngay nó là 0, 1 hay 2.
Ba bảng cần hiểu
pi chứa tỷ lệ lớp được ước lượng. Ví dụ, [0,2 ; 0,5 ; 0,3] nghĩa là nếu không có dấu hiệu mới, mô hình phân bố xác suất lần lượt 20 %, 50 % và 30 %. Những giá trị này thuộc ví dụ số được đưa ra sau; chúng không phải tỷ lệ học được của corpus được công bố trước.
theta mô tả hành vi của từng quy tắc. Các chiều của nó theo thứ tự quy tắc, lớp ẩn, phiếu quan sát. Với tám quy tắc và ba lớp, dạng của nó là (8, 3, 3). Một ô theta[j, k, c] biểu thị xác suất gán cho phiếu c của quy tắc j khi lớp giả định là k. Vì vậy, ô ngoài đường chéo là một khả năng nhầm lẫn, không nhất thiết là lỗi lập trình.
q chứa một phân bố cho mỗi tài liệu. Với N tài liệu, dạng của nó là (N, 3). Một hàng cho biết mỗi lớp hợp lý đến đâu theo tham số hiện tại. Trong từ vựng EM, các giá trị này cũng gọi là trách nhiệm: khi cập nhật, chúng khiến tài liệu đóng góp một phần cho nhiều lớp.
Ở đây, theta không có cột cho -1. Các lần không bỏ phiếu bị bỏ qua khi tính phiếu; tần suất của chúng không được mô hình hóa như đầu ra thứ tư.
Luân phiên ước lượng và cập nhật
EM là viết tắt của Expectation-Maximization. Thuật toán luân phiên hai thao tác: bước E ước lượng q bằng tham số hiện tại; bước M tính lại pi và theta bằng các phân bố này. Các điểm neo giữ nguyên lớp đã biết trong quá trình.
Nói cách khác: « Với những gì tôi biết về các quy tắc, lớp nào hợp lý? Rồi với các lớp hợp lý đó, tôi phải ước lượng lại hành vi của các quy tắc như thế nào? »
Mô hình đưa ra một đơn giản hóa quan trọng: nó giả định các phiếu độc lập khi biết lớp thực. Điều này cho phép nhân đóng góp của chúng. Nhưng hai quy tắc sao chép không phải là hai quan sát độc lập: chúng theo đúng cùng một dấu hiệu. Chúng ta sẽ kiểm tra giới hạn này trong bài tập.
Cộng các dấu hiệu mà không làm mất các xác suất nhỏ
Phép tính bắt đầu bằng logarit. Logarit biến tích thành tổng: thay vì nhân nhiều xác suất, chương trình cộng logarit của chúng.
def log_joint(votes, pi, theta):
result = np.broadcast_to(np.log(pi), (len(votes), 3)).copy()
for j in range(votes.shape[1]):
rows = np.flatnonzero(votes[:, j] >= 0)
for k in range(3):
result[rows, k] += np.log(theta[j, k, votes[rows, j]])
return result
np.broadcast_to(...).copy() chuẩn bị một hàng ba điểm số cho mỗi tài liệu, được khởi tạo bằng log(pi). Sau đó vòng lặp duyệt các quy tắc. np.flatnonzero(votes[:, j] >= 0) chỉ chọn các tài liệu mà quy tắc j đã nêu ý kiến.
Với mỗi lớp khả dĩ k, chương trình cộng đóng góp của phiếu thực sự quan sát được. Do đó nó không chỉ đếm « bao nhiêu quy tắc đã chọn lớp này? »; nó hỏi « các phiếu này có hợp lý nếu lớp này là đúng không? ».
Ở giai đoạn này, các điểm số chưa tạo thành phân bố đã chuẩn hóa. Đó là vai trò của bước E.
def e_step(votes, pi, theta, anchors):
validate_inputs(votes, pi, theta, anchors)
joint = log_joint(votes, pi, theta)
q = np.exp(joint - logsumexp(joint)[:, None])
for i, k in anchors.items():
q[i] = np.eye(3)[k]
return q
Dòng tạo q đưa ba điểm số của một tài liệu về dạng xác suất có tổng bằng một. logsumexp dùng để thực hiện chuẩn hóa này ổn định, kể cả với xác suất rất nhỏ.
Việc chuẩn hóa phải thực hiện theo từng tài liệu. Biểu thức logsumexp(joint)[:, None] ở đây cần một hệ số chuẩn hóa cho mỗi hàng để trừ khỏi ba điểm số tương ứng. Hãy giữ hàm phụ trợ của dự án: một hàm thư viện cùng tên có thể yêu cầu chỉ rõ trục. Tài liệu SciPy về logsumexp giải thích phép tính và vai trò của trục đó; đây không phải hướng dẫn thay thế mã.
Cuối cùng, np.eye(3)[k] tạo một mục tiêu chắc chắn cho một điểm neo. Nếu lớp đã biết là 1, hàng của nó thành [0, 1, 0]. Các quy tắc không thể thay thế giả thuyết của chính chúng vào đó.
Với tài liệu không có phiếu và không có điểm neo, không dấu hiệu mới nào được thêm: q vẫn bằng pi. Nếu đó là điểm neo, lớp đã biết của nó được ưu tiên, ngay cả khi không có phiếu.
Đếm các đóng góp từng phần bằng bước M
Bây giờ hãy đi từ các giả thuyết này đến các tham số mới.
def m_step_map(votes, q, alpha=1.1):
if (
alpha <= 1
or q.shape != (len(votes), 3)
or (q < 0).any()
or not np.isfinite(q).all()
or not np.allclose(q.sum(1), 1)
):
raise ValueError("Invalid responsibilities or Dirichlet prior")
pi = q.sum(0) + alpha - 1
pi /= pi.sum()
theta = np.full((votes.shape[1], 3, 3), alpha - 1, dtype=np.float64)
for j in range(votes.shape[1]):
for c in range(3):
theta[j, :, c] += q[votes[:, j] == c].sum(0)
theta /= theta.sum(2, keepdims=True)
return pi, theta
Các kiểm tra ban đầu yêu cầu một phân bố hợp lệ cho mỗi tài liệu: ba thành phần, không giá trị âm hoặc không hữu hạn, và tổng gần bằng một. Giá trị « không hữu hạn » bao gồm vô cùng hoặc NaN, kết quả số của một phép tính không xác định.
q.sum(0) cộng đóng góp của mọi tài liệu vào từng lớp. Vì vậy tài liệu có phân bố [0,1 ; 0,7 ; 0,2] đóng góp 0,1 cho lớp thứ nhất, 0,7 cho lớp thứ hai và 0,2 cho lớp thứ ba. Đây là đếm có trọng số, không phải chuyển sớm thành nhãn chắc chắn.
Với theta, q[votes[:, j] == c].sum(0) chỉ giữ các tài liệu mà quy tắc j đã bỏ phiếu c. Đóng góp của chúng được cộng vào cột của phiếu đó, rồi mỗi phân bố được chuẩn hóa trên ba phiếu khả dĩ.
Tham số alpha=1.1 thêm một tiên nghiệm Dirichlet nhỏ. Cụ thể, mã này cộng alpha - 1, tức 0,1, vào các số đếm trước khi chuẩn hóa. Điều này tránh xác suất đúng bằng không chỉ vì một tổ hợp chưa nhận đóng góp. Hậu tố map chỉ ước lượng maximum a posteriori: các quan sát được kết hợp với tiên nghiệm này.
Việc làm trơn này tránh một số bế tắc số; nó không tạo kiến thức mới về tài liệu. Các giả định của mô hình và chất lượng quy tắc vẫn giới hạn kết quả.
Quan sát ví dụ nhỏ trước khi huấn luyện đầy đủ
Trước hết, hãy chạy phép tính minh họa đi kèm.
docker compose run --rm tutorial python -m ged_supervision_faible_statuts.cli inspect-mechanism --config configs/minimum.yaml
Trong outputs/mechanism.json, ma trận minh họa không phải kết quả huấn luyện của corpus. Với phiếu [2, 1, -1], phân bố thu được xấp xỉ [0,0403 ; 0,5369 ; 0,4228]. Tổng của nó bằng một, trong giới hạn làm tròn. Với hàng không có phiếu, nó vẫn là [0,2 ; 0,5 ; 0,3].
Hãy so sánh với đa số: hai ý kiến mâu thuẫn tương tự cho nó một trường hợp đồng hạng. Ở đây, tỷ lệ lớp và tham số quy tắc tạo ra phân bố khác. Không có phiếu mới nào: cách diễn giải các phiếu đã thay đổi. Những con số này tự chúng không nói rằng diễn giải đó là đúng.
Bây giờ hãy điều chỉnh EM trên dữ liệu huấn luyện, với một lần gián đoạn, một đối chứng chạy liên tục, rồi tiếp tục và kiểm tra nó.
docker compose run --rm learning python -m ged_supervision_faible_statuts.cli fit-em --config configs/minimum.yaml --mode interrupt
docker compose run --rm learning python -m ged_supervision_faible_statuts.cli fit-em --config configs/minimum.yaml --mode continuous
docker compose run --rm learning python -m ged_supervision_faible_statuts.cli fit-em --config configs/minimum.yaml --mode resume
docker compose run --rm learning python -m ged_supervision_faible_statuts.cli verify-em --config configs/minimum.yaml
interrupt làm gián đoạn phép tính đã định để kiểm tra việc tiếp tục; continuous cung cấp quy trình đối chứng; resume tiếp tục trạng thái đã lưu. verify-em so sánh các quy trình liên quan.
Một checkpoint là trạng thái đã lưu cho phép tìm lại một phép tính. Tiếp tục không phải khởi động lại với tham số bất kỳ: cần tìm lại trạng thái và tiến trình mong đợi. Hãy xem các phân bố thu được và kiểm tra việc tiếp tục trước khi chuyển sang bộ phân loại.
Huấn luyện bộ phân loại với các mục tiêu mềm
Đừng biến sự do dự thành chắc chắn
Một mục tiêu là kết quả mà ta yêu cầu mô hình học. Mục tiêu cổ điển chỉ một lớp duy nhất. Trái lại, một mục tiêu mềm giữ lại một phân bố giữa các lớp.
Hãy lấy phân bố sư phạm [0,04 ; 0,54 ; 0,42], gần với ví dụ trước. Thay bằng [0, 1, 0] sẽ làm mất sự do dự giữa EN_REVUE và ANNONCE_APPROUVE. Với mục tiêu mềm, bộ phân loại cũng nhận được bất định được ước lượng này. Điều đó không khiến mục tiêu đúng hơn, nhưng tránh trình bày nó như sự chắc chắn mà nó không có.
Biến văn bản thành số bằng TF-IDF
Bộ phân loại nhận biểu diễn TF-IDF của tiêu đề và nội dung, chứ không trực tiếp nhận bảng tám phiếu. Vector là một dãy số; ở đây, các tọa độ mô tả những thuật ngữ được giữ lại trong văn bản.
TF, viết tắt của term frequency, phản ánh tần suất một thuật ngữ trong tài liệu. IDF, viết tắt của inverse document frequency, tính đến độ hiếm của nó trong các tài liệu huấn luyện. Sự kết hợp của chúng gán trọng số cho thuật ngữ, rồi dự án chuẩn hóa vector.
Để hình dung, hãy tưởng tượng gần như mọi tài liệu đều có từ « document », còn chỉ một số có « relecture ». Từ thứ hai có thể cho một dấu hiệu phân biệt hơn. Điều này không có nghĩa thuật ngữ hiếm tự động tiết lộ lớp đúng: giá trị của nó phụ thuộc dữ liệu và quá trình học. Tài liệu scikit-learn về TF-IDF trình bày sâu hơn biểu diễn này; các lựa chọn chính xác của quy trình vẫn là những lựa chọn trong ZIP.
Vì vậy có hai đối tượng không được nhầm lẫn: vector văn bản dùng làm đầu vào và phân bố trên ba lớp dùng làm mục tiêu. Số đặc trưng văn bản không phải số lớp cần dự đoán.
Hiểu hàm mất mát
Dự án dùng một MLP, hay perceptron đa lớp: mạng gồm các lớp tuyến tính được ngăn bởi các hàm kích hoạt. Các lớp kết hợp giá trị đầu vào với tham số đã học; các hàm kích hoạt giúp không bị giới hạn ở biến đổi tuyến tính đơn giản.
Mạng tạo ra ba logits, tức ba điểm số thô. Chúng chưa phải xác suất và không cần có tổng bằng một. Trong khi huấn luyện, một hàm mất mát đo độ chênh với mục tiêu. Quá trình học điều chỉnh tham số để giảm mất mát; gradient cho biết một thay đổi tham số ảnh hưởng thế nào đến phép tính.
Đây là hàm mất mát dùng với các mục tiêu mềm.
def soft_cross_entropy(logits, targets):
if (
logits.shape != targets.shape
or logits.ndim != 2
or not torch.isfinite(targets).all()
or (targets < 0).any()
or not torch.allclose(
targets.sum(1), torch.ones(len(targets), dtype=targets.dtype)
)
):
raise ValueError("Expected a distribution per example")
return -(targets * F.log_softmax(logits, dim=-1)).sum(1).mean()
logits và targets phải có cùng hình dạng: một hàng cho mỗi ví dụ trong lô và ba cột. Lô, hay batch, là nhóm tài liệu được xử lý cùng nhau. Các bước kiểm tra xác nhận, trong số những điều khác, rằng các giá trị mục tiêu là hữu hạn, không âm và đã được chuẩn hóa.
F.log_softmax(logits, dim=-1) biến các điểm số thành logarit xác suất trên các lớp. Phép nhân với targets gán trọng số cho mỗi đóng góp theo mục tiêu. .sum(1) cộng đóng góp của ba lớp cho mỗi tài liệu, rồi .mean() tính trung bình trên lô. Dấu trừ tạo ra mất mát entropy chéo.
Tài liệu PyTorch về entropy chéo cũng mô tả trường hợp mục tiêu xác suất này. Tài liệu nhắc rằng tính hợp lệ của chúng phải do mã gọi kiểm tra: các số bất kỳ không trở thành một phân bố chỉ vì được truyền vào hàm mất mát.
Học với nhiều mục tiêu hơn không nhất thiết hữu ích nếu chúng ít thông tin. Nếu các phân bố gần như lưỡng lự như nhau giữa các lớp, mạng nhận một tín hiệu rất khác so với chú thích đáng tin cậy. Cần đo xem nó làm gì với tín hiệu đó, không được cho rằng mô hình thứ hai sẽ tự động sửa mô hình thứ nhất.
Huấn luyện và kiểm tra việc tiếp tục
Hãy chạy các lần huấn luyện gián đoạn, liên tục và tiếp tục, rồi so sánh các trạng thái.
docker compose run --rm learning python -m ged_supervision_faible_statuts.cli train --config configs/minimum.yaml --mode interrupt
docker compose run --rm learning python -m ged_supervision_faible_statuts.cli train --config configs/minimum.yaml --mode continuous
docker compose run --rm learning python -m ged_supervision_faible_statuts.cli train --config configs/minimum.yaml --mode resume
docker compose run --rm learning python -m ged_supervision_faible_statuts.cli verify-resume --config configs/minimum.yaml
Quy trình kiểm tra riêng việc tiếp tục EM và việc tiếp tục bộ phân loại. Đây là hai phép tính khác nhau, có các trạng thái đã học riêng. Kết quả tham chiếu xác lập tính tái lập của chúng trong môi trường CPU này; chúng không đảm bảo đồng nhất số học sau khi thay đổi phụ thuộc hoặc môi trường.
Sau đó hãy tạo hai dự đoán: bộ phân loại chỉ dùng 90 điểm neo, rồi bộ phân loại dùng giám sát yếu.
docker compose run --rm inference python -m ged_supervision_faible_statuts.cli predict --config configs/minimum.yaml --variant anchors
docker compose run --rm inference python -m ged_supervision_faible_statuts.cli predict --config configs/minimum.yaml --variant main
Ở giai đoạn này, chúng ta thực hiện suy luận: dùng các tham số đã học để dự đoán. Đây không còn là bước điều chỉnh tham số từ mục tiêu. Dịch vụ inference không thấy các lớp tham chiếu.
Các tệp predictions-anchors.json và predictions-main.json cho phép so sánh các phương pháp theo từng tài liệu. Hãy tìm V-033 và tự hỏi: hai bộ phân loại có đề xuất cùng lớp không? Các phân bố của chúng biểu thị sự do dự nào? Đừng suy ra câu trả lời chỉ từ từ « validé »: hãy xem đầu ra thực sự được tạo ra.
Hiểu các chỉ số trước khi xem kết quả
Một chỉ số là thước đo được chọn để đánh giá chương trình. Ở đây, chúng ta muốn xem ba điều khác nhau: phương pháp có đưa ra ý kiến không, nó phân loại đúng không và nó gán xác suất hữu ích không? Một con số không trả lời được cả ba câu hỏi.
Độ phủ và accuracy: trả lời không có nghĩa là thành công
Độ phủ là tỷ lệ tài liệu mà một phương pháp đưa ra quyết định. Trên mười tài liệu, phương pháp phân loại bảy và không bỏ phiếu ba có độ phủ 0,7, ngay cả khi bảy quyết định đều đúng.
Accuracy, hay độ chính xác, đếm các quyết định đúng. Trong dự án này, mẫu số của nó gồm 360 tài liệu kiểm thử, kể cả các lần không bỏ phiếu. Vì vậy các tài liệu khó không bị loại để làm điểm số tốt lên một cách giả tạo.
Độ phủ 1 nghĩa là « có một quyết định cho mỗi tài liệu », không phải « mọi quyết định đều đúng ». Ngược lại, một phương pháp có thể đưa ra đề xuất tốt trong khi để nhiều tài liệu không có câu trả lời.
Precision, recall và macro-F1: nhìn riêng từng lớp
Với một lớp nhất định, precision hỏi: trong các tài liệu tôi đặt vào lớp này, bao nhiêu tài liệu thực sự thuộc về nó? Recall hỏi: trong các tài liệu lẽ ra phải đặt vào đó, tôi đã tìm được bao nhiêu?
Hãy lấy ví dụ sư phạm: bạn dự đoán bốn tài liệu EN_REVUE, trong đó ba đúng, trong khi năm tài liệu được mong đợi. Precision là 3/4, tức 0,75; recall là 3/5, tức 0,60. F1 kết hợp hai giá trị bằng 2 × precision × recall / (precision + recall), ở đây xấp xỉ 0,6667.
Macro-F1 tính F1 của từng trong ba lớp, rồi lấy trung bình. Mỗi lớp có trọng số như nhau trong trung bình này. Không bỏ phiếu cho một tài liệu được mong đợi trong một lớp là một tham chiếu bị bỏ lỡ: nó làm giảm recall của lớp đó.
Vì vậy macro-F1 không trực tiếp biểu thị phần trăm tài liệu được phân loại tốt. Nó cũng không kiểm tra xem mức tin cậy mà phương pháp hiển thị có chính đáng không.
Brier và log-loss: cũng đánh giá xác suất
Điểm Brier so sánh toàn bộ phân bố dự đoán với vector của lớp tham chiếu. Theo quy ước của dự án, ta cộng ba sai số bình phương rồi lấy trung bình trên các tài liệu. Khoảng giá trị từ 0 đến 2, và thấp hơn là tốt hơn. Tài liệu Brier đa lớp trình bày định nghĩa này; luôn kiểm tra quy ước được dùng trước khi so sánh điểm số của các dự án khác nhau.
Để tính minh họa, giả sử tham chiếu là EN_REVUE. Vector của nó là [0, 1, 0]. Với dự đoán [0,1 ; 0,7 ; 0,2], sai số là:
(0,1 - 0)² + (0,7 - 1)² + (0,2 - 0)² = 0,14.
Dự đoán đúng bằng tham chiếu cho giá trị không. Sự chắc chắn hoàn toàn ở lớp sai cho giá trị hai.
Log-loss xét xác suất gán cho lớp đúng, với phép tính -log(xác suất đúng) cho một tài liệu. Nếu xác suất này là 0,9, mất mát xấp xỉ 0,1054; nếu là 0,01, mất mát tăng lên khoảng 4,6052. Vì thế một lỗi với độ chắc chắn cao bị phạt nặng. Nguyên lý được trình bày chi tiết trong tài liệu về log-loss.
Hai điểm số này đều đánh giá xác suất nhưng không thể thay thế cho nhau. Chúng cũng không tự mình tạo thành chẩn đoán đầy đủ về calibration, tức sự phù hợp giữa độ tin cậy được thông báo và tần suất thành công. Tài liệu về calibration giải thích đặc biệt vì sao Brier trộn nhiều khía cạnh của chất lượng dự đoán.
Tính và diễn giải kết quả
Hãy chạy đánh giá.
docker compose run --rm tutorial python -m ged_supervision_faible_statuts.cli evaluate --config configs/minimum.yaml
Các giá trị sau đã được làm tròn; chúng áp dụng cho 360 tài liệu kiểm thử.
Trên thiết bị di động, hãy cuộn ngang bảng.
| Phương pháp | Độ phủ | Macro-F1 | Brier | Log-loss |
|---|---|---|---|---|
| Bỏ phiếu đa số | 0,7444 | 0,7721 | 0,2735 | 1,4439 |
| Mô hình ẩn EM | 1,0000 | 0,3789 | 0,6666 | 1,0985 |
| Bộ phân loại trên 90 điểm neo | 1,0000 | 0,7666 | 0,4260 | 0,7594 |
| Bộ phân loại với giám sát yếu | 1,0000 | 0,6118 | 0,6344 | 1,0511 |
Đa số đạt macro-F1 tốt nhất bảng, nhưng không bỏ phiếu cho 92 tài liệu. Vì vậy nó đưa ra quyết định cho 360 - 92 = 268 tài liệu, tức độ phủ 268/360 ≈ 0,7444. Cần đọc hai cột cùng nhau.
Brier của nó cũng thấp nhất, nhưng log-loss lại cao nhất. Đây không phải mâu thuẫn: các thước đo không phạt các phân bố theo cùng cách. Đặc biệt, xác suất rất thấp gán cho lớp đúng có thể ảnh hưởng mạnh đến log-loss. Muốn giải thích chính xác lỗi của quy trình, cần xem đầu ra theo từng tài liệu; chỉ bảng không cho biết chi tiết.
Mô hình ẩn EM trả lời ở mọi nơi, nhưng các phân bố của nó vẫn gần đều. Đây là mốc hoàn toàn toán học, không phải thí nghiệm mới: phân bố [1/3 ; 1/3 ; 1/3] cho Brier 2/3 ≈ 0,6667 và log-loss log(3) ≈ 1,0986, bất kể lớp đúng là gì. Các giá trị quan sát cho EM, 0,6666 và 1,0985, rất gần mốc này. Chọn thành phần cao nhất không có nghĩa mô hình có một dấu hiệu rõ ràng để phân xử.
Bộ phân loại trên 90 điểm neo ở đây tốt hơn bộ dùng giám sát yếu. Cả hai bao phủ mọi tài liệu, nhưng bộ thứ nhất có macro-F1 cao hơn và sai số xác suất thấp hơn. Trên corpus này, đi qua các quy tắc và mô hình ẩn vì thế không cải thiện mốc so sánh này.
Kết quả âm này là trọng tâm. Nó không chứng minh mọi giám sát yếu đều thất bại; nó cho thấy tổ hợp dữ liệu, quy tắc và mô hình này không đủ để đạt một mức cải thiện. Một kiến trúc phức tạp hơn phải được đối chiếu với kết quả, ngay cả khi chúng trái với trực giác ban đầu.
Các phương pháp không có cùng độ phủ: đa số không bỏ phiếu cho 92 tài liệu.
Những gì chúng ta đã học
Một quy tắc cung cấp ý kiến một phần. Nó có thể đúng, sai hoặc không đưa ra ý kiến. Không bỏ phiếu không được trở thành lớp nghiệp vụ mặc định, và mâu thuẫn không được che giấu để tạo ra câu trả lời bằng mọi giá.
EM ước lượng một lớp ẩn dựa trên giả định về các phiếu. Các điểm neo cho nó mốc tin cậy, nhưng các phân bố ít thông tin vẫn là tín hiệu huấn luyện hạn chế cho bộ phân loại kế tiếp.
Cuối cùng, độ phủ, macro-F1 và sai số xác suất kể những câu chuyện khác nhau. Quy trình dạy chúng ta vừa xây dựng một chuỗi giám sát yếu vừa nhận ra trường hợp nó không làm tốt hơn các chú thích đáng tin cậy đã có.
Bài tập: một quy tắc được sao chép có phải là thông tin mới?
Trước khi chạy thí nghiệm, hãy ghi giả thuyết của bạn: điều gì xảy ra nếu sao chép quy tắc « validé dans le titre »? Số phiếu có đổi không? Chúng ta có học được điều mới về tài liệu không? Hãy dự đoán riêng tác động lên mô hình ẩn và bộ phân loại văn bản.
Để giúp suy luận, hãy dùng lại ví dụ sư phạm [2, 1, -1]. Khi nhân đôi ý kiến đầu tiên, ta được [2, 1, -1, 2]. Đa số không còn thấy đồng hạng, dù không quan sát được thông tin mới nào. Hai bản sao của cùng điều kiện vẫn phụ thuộc, dù chúng chiếm hai cột riêng.
Biến thể được cung cấp sao chép quy tắc này, huấn luyện lại chuỗi liên quan và đánh giá trên cùng các tài liệu.
docker compose run --rm learning python -m ged_supervision_faible_statuts.cli exercise --config configs/minimum.yaml
docker compose run --rm inference python -m ged_supervision_faible_statuts.cli predict --config configs/minimum.yaml --variant duplicate
docker compose run --rm tutorial python -m ged_supervision_faible_statuts.cli evaluate-exercise --config configs/minimum.yaml
Hãy mở outputs/exercise-comparison.json và so sánh các thước đo với dự đoán của bạn. Trong quy trình tham chiếu, macro-F1 của mô hình ẩn giảm khoảng 0,2122, trong khi của bộ phân loại tăng khoảng 0,0144. Đây là chênh lệch điểm số, không phải thay đổi phần trăm tương đối.
Brier của bộ phân loại tăng nhẹ: vì vậy macro-F1 được cải thiện không đi cùng sự cải thiện của thước đo xác suất này. Bản sao không cung cấp quan sát độc lập nào và tác động của nó không giống nhau ở hai giai đoạn của chuỗi.
Vì vậy đừng giữ một quy tắc đơn giản hóa như « sao chép luôn làm giảm mọi điểm số ». Thay vào đó, hãy giữ câu hỏi cần đặt: thông tin nào thực sự đã được thêm vào, và ta quan sát tác động của nó ở giai đoạn nào?
Cho thí nghiệm riêng của bạn, tiếp theo hãy chọn một quy tắc và sửa cách nó xử lý một phủ định. Trước hết hãy viết điều cần thay đổi. Chạy lại quy trình trong một bản giải nén riêng, giữ nguyên các tập đánh giá, rồi so sánh phiếu bầu, các lần không bỏ phiếu, ví dụ và chỉ số.
Thay đổi cá nhân này khác biến thể tham chiếu: kết quả của nó không được biết trước. Trong một cách làm phát triển, hãy dùng xác thực để chọn cải tiến và giữ kiểm thử cuối tách khỏi các lựa chọn đó, thay vì liên tục chỉnh quy tắc để cải thiện điểm số của nó.
Kiểm tra hình và kiểm soát tệp
Hãy tạo các hình từ đầu ra của bạn, rồi kiểm tra checkpoint.
docker compose run --rm tutorial python lesson/figures/plot.py
docker compose run --rm tutorial python scripts/check_checkpoints.py
Các tệp outputs/rules.json, outputs/evaluation.json và hình cho phép quay từ trung bình về các ví dụ. Với V-033, hãy theo cùng đường đi với chương trình: dấu hiệu có trong tài liệu, ý kiến của quy tắc, phân bố ước lượng, rồi đề xuất của bộ phân loại. Một kết quả trung bình không thay thế việc kiểm tra quy trình này.
Kiểm thử âm xác minh chương trình từ chối đầu vào không nhất quán. Chúng đặc biệt bao phủ phiếu ngoài các lớp được phép, xác suất không hữu hạn, phân bố chuẩn hóa sai và điểm neo không hợp lệ. Thành công của kiểm thử là thấy sự từ chối mong đợi, không phải lấy được một lớp bằng mọi giá.
Khắc phục sự cố và dọn dẹp
Compose thất bại? Hãy bắt đầu bằng kiểm tra cấu hình của nó, có trong các lệnh đầu tiên. Hãy kiểm tra lỗi trước khi quy vấn đề cho mô hình.
Đã có trạng thái huấn luyện? Hãy dùng một bản giải nén sạch thay vì ghi đè. Trộn kết quả của hai lần thử sẽ khiến so sánh khó diễn giải.
Một phân bố bị từ chối? Hãy kiểm tra đầu vào và cách chúng được xây dựng. Tổng không đúng, giá trị âm hoặc không hữu hạn cho thấy một vấn đề cần hiểu. Đừng lặng lẽ thay xác suất bằng một lớp tùy ý để vượt qua kiểm tra.
Trước khi dọn dẹp, hãy giữ lại các hình và kết quả hữu ích. Các lệnh sau dừng dự án, chỉ xóa outputs/, rồi kết thúc quy trình. Corpus và checkpoint được giữ lại.
docker compose down
docker compose run --rm tutorial python scripts/clean.py --outputs-only
docker compose down
Từ hướng dẫn đến ứng dụng thực tế
Corpus tổng hợp cho phép nghiên cứu cơ chế; nó không đại diện cho mọi cách diễn đạt và mọi thực hành của một tổ chức. Với ứng dụng quản lý tài liệu điện tử, hay GED, hãy bắt đầu bằng các chú thích đáng tin cậy đại diện cho văn bản và sự phát triển của chúng.
Hãy đo tương quan giữa các quy tắc, độ phủ, lỗi theo lớp và calibration trên một tập riêng. Quy tắc khai thác thói quen soạn thảo hoặc ngày tháng có thể không còn hữu ích khi thực hành thay đổi. Tiêu đề, trích dẫn và ngày tháng không được trở thành bằng chứng cho một trạng thái hành chính.
Ở phía ứng dụng, hãy phân biệt rõ đề xuất của mô hình với trạng thái chính thức. Một màn hình có thể trình bày ý kiến được đề xuất, các dấu hiệu đã dùng và khả năng kiểm soát của con người; giao diện này là hướng tích hợp, không phải tính năng hướng dẫn đã cung cấp.
Hãy thêm quyền truy cập, tính bảo mật, quản lý phiên bản quy tắc, khả năng truy vết đề xuất, giám sát dịch vụ và kiểm soát của con người. Những biện pháp này bổ sung cho đánh giá thống kê: ngay cả điểm tốt hơn cũng không thay thế các quy tắc quyết định ai được xem tài liệu hay thay đổi trạng thái của nó.
Để đi xa hơn
Cải thiện một quy tắc từ các lỗi đã xác định. Hãy chọn một nhầm lẫn có thể quan sát, lập giả thuyết, rồi đo xem thay đổi có giảm lỗi này mà không chỉ chuyển chi phí sang lớp khác hay không.
So sánh nhiều ngân sách chú thích đáng tin cậy. Kết quả của 90 điểm neo là điểm khởi đầu để nghiên cứu tác động của nhiều hơn hoặc ít hơn ví dụ đã kiểm chứng. Nó không cho phép dự đoán điểm số của các thí nghiệm mới này.
Xem xét phụ thuộc giữa các quy tắc. Hai quy tắc có thể khác nhau trong mã nhưng hầu như luôn khai thác cùng một dấu hiệu. Bài tập sao chép là trường hợp đặc biệt dễ thấy của vấn đề này.
Xây dựng giao diện rà soát các trường hợp mâu thuẫn. Nó có thể hiển thị tài liệu, phiếu bầu, xác suất và chỉnh sửa của con người. Mục đích là làm lỗi có thể kiểm tra, không che giấu sự do dự sau một nhãn duy nhất.
Tài liệu tham khảo để tìm hiểu sâu hơn
Mã hoàn chỉnh trong ZIP mô tả các phép tính EM và quy trình tái lập R001. Các liên kết được thêm trong bài dẫn đến phần trình bày về giám sát yếu của Stanford cùng tài liệu SciPy, PyTorch và scikit-learn về cơ chế số học và các thước đo.
Các tài liệu tham khảo này bổ sung cho phần giải thích; chúng không phải xác thực mới cho dự án hay lời mời thay thế các cách triển khai của nó. Để tái lập kết quả, hãy giữ nguyên phụ thuộc, cấu hình và lệnh của ZIP. Các ví dụ sư phạm về phiếu bầu và xác suất được phân biệt với các phép đo corpus.