Nhận dạng thực thể (NER)
Mục tiêu
Phần tiêu đề “Mục tiêu”Sau bài này, bạn sẽ:
- Hiểu nhận dạng thực thể có tên (named entity recognition, NER) là một bài toán gán nhãn chuỗi (sequence labeling), và vì sao chỉ tra từ điển tên là không đủ.
- Mã hóa được thực thể bằng nhãn BIO, giải thích vì sao cần nhãn B-, và giải mã ngược nhãn thành các đoạn thực thể.
- Giải thích được CRF ở mức ý tưởng, cách BERT gán nhãn cho từng token, và cách căn nhãn với subword bằng giá trị
-100. - Tính được precision, recall và F1 mức thực thể, và nhận ra vì sao độ chính xác mức token làm điểm đẹp hơn thật.
Cần biết trước
Phần tiêu đề “Cần biết trước”- BERT, thuộc phần Xử lý ngôn ngữ tự nhiên (Đã học)
Trực giác
Phần tiêu đề “Trực giác”Ai, ở đâu, tổ chức nào
Phần tiêu đề “Ai, ở đâu, tổ chức nào”Đọc câu sau:
Ông Nguyễn Văn An đến Hà Nội làm việc cho Vietcombank từ tháng 3/2024.
Bạn thấy ngay: “Nguyễn Văn An” là một người, “Hà Nội” là một nơi chốn, “Vietcombank” là một tổ chức, “tháng 3/2024” là một mốc thời gian. Những cụm gọi tên một đối tượng cụ thể như vậy là thực thể có tên (named entity). Tìm ra chúng, đúng ranh giới và đúng loại, là việc của NER.
Việc này nghe nhỏ nhưng nằm dưới rất nhiều hệ thống. Báo điện tử gắn thẻ người và công ty cho từng bài. Ngân hàng đọc hồ sơ để lấy tên, địa chỉ. Hệ thống hỏi đáp cần biết câu hỏi “ai” thì câu trả lời phải là một người.
Tra từ điển tên có đủ không?
Phần tiêu đề “Tra từ điển tên có đủ không?”Cách đầu tiên ai cũng nghĩ tới: lập một danh sách tên người, tên địa danh, tên công ty, rồi tìm trong câu. Danh sách như vậy gọi là gazetteer (từ điển tên). Nó hữu ích, nhưng vỡ ở ba chỗ:
- Cùng một chuỗi, khác loại. “Hà Nội” trong “đến Hà Nội” là nơi chốn. Trong “Chị Mai chuyển từ Bia Hà Nội sang Vinamilk”, “Hà Nội” chỉ là một phần của tên công ty “Bia Hà Nội”. Danh sách không biết ngữ cảnh.
- Tên trùng từ thường. Tên người Việt hay là từ thường: Mai, Lan, Hoa, An, Hải. “Chị Mai” là người, “sáng mai” thì không. “Ông An” là người, “bình an” thì không.
- Tên mới xuất hiện mỗi ngày. Công ty mới, người mới nổi tiếng, sản phẩm mới. Danh sách luôn chậm hơn thực tế.
Dấu hiệu thật sự nằm ở ngữ cảnh: “Ông” hay “Chị” đứng trước, “làm việc cho” đứng trước, “đến” đứng trước. Một mô hình cần nhìn cả câu để quyết định cho từng từ.
Mỗi từ một nhãn
Phần tiêu đề “Mỗi từ một nhãn”Nếu mỗi từ được gắn một nhãn cho biết nó thuộc thực thể nào, ta đọc ngược ra được thực thể. Nhưng một thực thể có thể dài nhiều từ, và hai thực thể có thể nằm sát nhau, như trong tít báo “Trận Việt Nam Thái Lan”. Nhãn phải đủ để biết chỗ một thực thể bắt đầu.
Bài toán của bài này là: gán cho mỗi từ trong câu một nhãn, sao cho từ dãy nhãn đọc lại được mọi thực thể với đúng ranh giới và đúng loại; rồi đo xem mô hình tìm đúng bao nhiêu thực thể.
Lý thuyết
Phần tiêu đề “Lý thuyết”Các loại thực thể
Phần tiêu đề “Các loại thực thể”Mỗi bộ dữ liệu chọn một tập loại thực thể. Bộ phổ biến nhất cho tiếng Anh là CoNLL-2003 (Tjong Kim Sang và De Meulder, 2003), lấy từ tin tức Reuters, với bốn loại:
| Loại | Nghĩa | Ví dụ |
|---|---|---|
PER | người | Nguyễn Văn An, Angela Merkel |
LOC | nơi chốn | Hà Nội, sông Mê Kông |
ORG | tổ chức | Vietcombank, Liên Hợp Quốc |
MISC | tên riêng khác | World Cup, tính từ quốc tịch như German |
Phần tiếng Anh của CoNLL-2003 có tập huấn luyện khoảng 203 nghìn token và 23.5 nghìn thực thể. Với tiếng Việt, có hai cuộc thi chung (Nguyễn Thị Minh Huyền và cộng sự, 2018): VLSP 2016 (dữ liệu dạng cột kiểu CoNLL, đã tách từ) và VLSP 2018 (văn bản thô gắn thẻ XML, có thực thể lồng nhau). Cả hai dùng các loại chính PER, LOC, ORG, cùng một loại MISC nhỏ.
Còn “tháng 3/2024”? Bốn loại trên không có thời gian. Bộ khác, như OntoNotes, thêm DATE, TIME, MONEY và nhiều loại nữa. Chọn tập loại nào là quyết định của bài toán cụ thể, không có đáp án chung. Bài này dùng PER, LOC, ORG cho mọi ví dụ.
NER là gán nhãn chuỗi
Phần tiêu đề “NER là gán nhãn chuỗi”Gọi câu là dãy từ , với là độ dài câu. NER gán cho mỗi từ một nhãn , được dãy nhãn cùng độ dài. Đây là gán nhãn chuỗi: đầu ra dài đúng bằng đầu vào và thẳng hàng với nó. Gán nhãn từ loại (part-of-speech tagging: danh từ, động từ…) cũng là bài toán dạng này. Khác với dịch máy ở bài Seq2Seq, ở đây không có chuyện độ dài hay trật tự đổi đi.
Mã hóa BIO
Phần tiêu đề “Mã hóa BIO”Cách đơn giản nhất là gắn mỗi từ thẳng loại của nó: PER, LOC, hoặc O (outside, ngoài mọi thực thể). Thử với tít báo:
| Từ | Trận | Việt | Nam | Thái | Lan |
|---|---|---|---|---|---|
| Chỉ ghi loại | O | LOC | LOC | LOC | LOC |
| BIO | O | B-LOC | I-LOC | B-LOC | I-LOC |
Ở đây tên nước được coi là nơi chốn. Có bộ hướng dẫn gán nhãn coi đội bóng là tổ chức; chọn loại nào thì hai tên vẫn cùng loại và đứng sát nhau. Hàng “chỉ ghi loại” không phân biệt được “Việt Nam” + “Thái Lan” với một địa danh dài bốn từ “Việt Nam Thái Lan”. Thông tin bị mất là chỗ bắt đầu.
Mã hóa BIO sửa đúng chỗ đó. Mỗi nhãn là một tiền tố cộng một loại:
B-X(begin): từ đầu tiên của một thực thể loại X.I-X(inside): từ thứ hai trở đi của thực thể loại X đang mở.O: ngoài mọi thực thể.
Với loại thực thể, tập nhãn có phần tử. CoNLL-2003 có nên có 9 nhãn. Ý tưởng này đến từ bài toán tách cụm danh từ của Ramshaw và Marcus (1995). Có hai biến thể dễ lẫn. Trong IOB1, dạng của tệp CoNLL-2003 gốc, mọi từ trong thực thể đều mang I-, chỉ khi hai thực thể cùng loại đứng sát nhau thì từ đầu của thực thể thứ hai mới mang B-. Trong IOB2, dạng mà bài này và hầu hết thư viện ngày nay dùng, mọi thực thể đều mở bằng B-. Người ta thường gọi IOB2 là “BIO”.
Một biến thể giàu thông tin hơn là BIOES (còn gọi IOBES): thêm E-X cho từ cuối và S-X cho thực thể chỉ có một từ. “Nguyễn Văn An” thành B-PER I-PER E-PER, “Vietcombank” thành S-ORG. Lample và cộng sự (2016) dùng BIOES, nhưng ghi nhận nó không hơn BIO đáng kể trong thí nghiệm của họ.
Giải mã ngược: đọc dãy nhãn từ trái sang phải. B-X mở một thực thể mới. I-X nối dài thực thể đang mở nếu nó cùng loại X. O, hoặc I- khác loại, đóng thực thể đang mở. Còn một trường hợp mô hình hay sinh ra mà dữ liệu đúng (trong IOB2) không bao giờ có: I-X không đứng ngay sau B-X hay I-X, tức là ở đầu câu, sau O, hoặc sau một nhãn khác loại. Ta gọi nó là I- mồ côi. Bài này theo quy ước của công cụ chấm điểm conlleval (dùng ở CoNLL) và chế độ mặc định của thư viện seqeval: I- mồ côi mở một thực thể mới. Chế độ strict của seqeval thì coi nó là sai và bỏ đi. Hai quy ước cho điểm khác nhau, nên khi báo điểm hãy ghi rõ mình dùng quy ước nào.
Từ đặc trưng thủ công tới CRF
Phần tiêu đề “Từ đặc trưng thủ công tới CRF”Trước học sâu, một hệ NER điển hình mô tả mỗi từ bằng các đặc trưng (feature) do người thiết kế: từ có viết hoa không, có số không, có nằm trong gazetteer không, từ đứng trước là gì (“Ông”, “tại”, “công ty”), từ đứng sau là gì. Sau đó một mô hình học xem đặc trưng nào báo hiệu nhãn nào.
Nếu phân loại từng từ một cách độc lập, mô hình có thể sinh ra dãy vô lý như O I-PER hay B-PER I-LOC. Nó không có cách nào biết nhãn của từ bên cạnh. Các mô hình chuỗi trước đó như HMM và MEMM đã chấm điểm cả dãy nhãn, nhưng HMM cần giả định các từ độc lập với nhau khi biết nhãn, còn MEMM mắc lỗi thiên lệch nhãn (label bias). Trường ngẫu nhiên có điều kiện (conditional random field, CRF) của Lafferty, McCallum và Pereira (2001) chấm điểm cả dãy nhãn một lần mà tránh được cả hai vấn đề đó. Dạng thường dùng cho NER cộng hai loại điểm:
- là điểm tại chỗ: từ thứ , nhìn đặc trưng của nó và của các từ xung quanh, hợp với nhãn tới đâu.
- là điểm chuyển (transition score), lấy từ một ma trận học được, mỗi ô ứng với một cặp (nhãn trước, nhãn sau). Khi huấn luyện trên dữ liệu IOB2, ô (O, I-PER) học được giá trị rất âm, vì trong dữ liệu đó
I-PERkhông bao giờ đứng ngay sauO.
Điểm được đổi thành xác suất bằng cách lấy mũ rồi chia cho tổng trên mọi dãy nhãn có thể có, ký hiệu :
Đây là softmax, chỉ khác ở chỗ các “lớp” bây giờ là cả dãy nhãn. Số dãy nhãn tăng theo hàm mũ của (9 nhãn, câu 20 từ là dãy), nhưng vì điểm chỉ phụ thuộc từng cặp nhãn kề nhau, cả tổng ở mẫu số lẫn dãy có điểm cao nhất đều tính được bằng quy hoạch động. Tổng ở mẫu số tính bằng thuật toán forward, dãy tốt nhất tìm bằng Viterbi. Muốn cấm hẳn một chuyển, ta đặt ô tương ứng của bằng : mọi dãy chứa chuyển đó có .
Năm 2016, Lample và cộng sự thay đặc trưng thủ công bằng một mạng BiLSTM (mạng hồi tiếp đọc câu cả hai chiều) để tính , giữ lớp CRF ở trên. Mô hình BiLSTM-CRF này đạt F1 90.94 trên CoNLL-2003 tiếng Anh mà không cần gazetteer.
BERT gán nhãn cho từng token
Phần tiêu đề “BERT gán nhãn cho từng token”Với BERT, cách làm đơn giản hơn nữa. Đưa câu vào BERT, bạn nhận được một vectơ cho mỗi token, với là số chiều ẩn, đã trộn thông tin của cả câu theo hai chiều. Lưu ý: ở mục này, chỉ số chạy trên các token subword của BERT, không phải trên từ như ở phần CRF; mục sau nói cách nối hai cách đếm này. Đặt lên trên một lớp tuyến tính dùng chung cho mọi vị trí:
Ở đây , là tham số mới, với là số nhãn BIO. Mỗi là một vectơ logit, mỗi nhãn một số. Đây chỉ là một bộ phân loại trên mỗi vectơ đầu ra, gọi là gán nhãn token (token classification). Khi fine-tune, cả BERT lẫn lớp mới cùng được cập nhật. Hàm mất mát là cross-entropy trung bình trên các vị trí có nhãn, gọi tập các vị trí đó là :
Devlin và cộng sự (2019) làm đúng như vậy, không dùng CRF, và đạt F1 92.4 (BERT-base) và 92.8 (BERT-large) trên tập kiểm tra CoNLL-2003. Lý do CRF ít cần hơn: mỗi đã “thấy” cả câu, nên gián tiếp biết từ bên cạnh nhiều khả năng mang nhãn gì, và lỗi kiểu O I-PER hiếm đi. Hiếm, chứ không hết: ô code ở dưới sẽ cho thấy một lỗi như vậy.
Căn nhãn với subword
Phần tiêu đề “Căn nhãn với subword”Dữ liệu NER gán nhãn theo từ, còn BERT đọc theo subword. Như bài Tách từ đã nói, WordPiece cắt từ lạ thành nhiều mảnh, mảnh sau có tiền tố ##. Đây là cách bộ tách từ của model trong ô code cắt câu “Nguyen Thi Lan works for Vietcombank” (lấy từ lần chạy thật):
| Token | [CLS] | Ng | ##uy | ##en | T | ##hi | La | ##n | works | for | V | ##iet | ##comb | ##an | ##k | [SEP] |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Từ số | – | 0 | 0 | 0 | 1 | 1 | 2 | 2 | 3 | 4 | 5 | 5 | 5 | 5 | 5 | – |
| Nhãn | -100 | B-PER | -100 | -100 | I-PER | -100 | I-PER | -100 | O | O | B-ORG | -100 | -100 | -100 | -100 | -100 |
Sáu từ thành 16 token. Cách của BERT gốc, và của hướng dẫn token classification trên Hugging Face: chỉ mảnh đầu của mỗi từ mang nhãn của từ. Các mảnh sau và token đặc biệt [CLS], [SEP] nhận giá trị -100. Vì sao là -100? Hàm cross-entropy của PyTorch có tham số ignore_index, mặc định bằng -100: vị trí mang nhãn này không vào tập , không đóng góp vào loss.
Vì sao không chép nhãn của từ cho mọi mảnh?
- Chép nguyên
B-ORGcho cả 5 mảnh của “Vietcombank” thì lúc giải mã, mỗiB-mở một thực thể mới: một tên thành năm thực thể. - Chép rồi đổi mảnh sau thành
I-thì hợp lệ, nhưng từ có 5 mảnh chiếm 5 số hạng trong loss, từ “for” chỉ 1. Tên riêng lạ (đúng thứ ta cần tìm) bị cắt vụn nhiều nhất, nên được tính trọng số lệch đi so với cách chấm điểm theo từ. - Điểm số được chấm theo từ. Ta cần đúng một dự đoán cho mỗi từ, và mảnh đầu là chỗ tự nhiên để lấy nó.
Hệ quả cần nhớ: nếu model được huấn luyện theo cách -100, nhãn nó đoán cho các mảnh ## chưa bao giờ được dạy. Lúc dùng, hãy đọc nhãn ở mảnh đầu của mỗi từ.
Đánh giá mức thực thể
Phần tiêu đề “Đánh giá mức thực thể”Một thực thể dự đoán chỉ được tính là đúng khi khớp chính xác (exact match) với một thực thể thật: cùng điểm đầu, cùng điểm cuối, cùng loại. Đây là cách chấm của CoNLL-2003, và của seqeval. Gọi là số thực thể mô hình đưa ra, là số thực thể thật, là số khớp chính xác:
Precision trả lời: trong những gì mô hình đưa ra, bao nhiêu phần đúng. Recall trả lời: trong những gì có thật, mô hình tìm được bao nhiêu phần. là trung bình điều hòa của hai số. Thay và vào rồi rút gọn ở tử và mẫu, ta được một dạng dễ nhớ:
(Nhân cả tử và mẫu với : tử còn , mẫu còn .)
Luật “khớp chính xác” rất khắt khe. Mô hình đoán “Văn An” thay vì “Nguyễn Văn An” thì bị tính hai lần lỗi: một dự đoán sai (giảm ) và một thực thể bỏ sót (giảm ). Ở mức token, cùng lỗi đó chỉ sai một hai trên mười mấy token. Hơn nữa, phần lớn token trong văn bản là O, nên một mô hình đoán O khắp nơi vẫn có độ chính xác mức token cao mà không tìm ra thực thể nào. Ô code đầu tiên đo cả hai con số trên cùng dữ liệu.
Thách thức của tiếng Việt
Phần tiêu đề “Thách thức của tiếng Việt”- Âm tiết hay từ. Chữ Việt viết cách nhau theo âm tiết: “Hà Nội” là hai âm tiết nhưng một từ. Nhiều bộ dữ liệu và mô hình (như PhoBERT của Nguyễn Đạt Quốc và Nguyễn Tuấn Anh, 2020) làm việc trên văn bản đã tách từ, viết “Hà_Nội”. Lỗi tách từ thì kéo theo lỗi biên thực thể. Ví dụ trong bài gán nhãn theo âm tiết cho đơn giản.
- Viết hoa không đáng tin. Tiếng Việt viết hoa cả chữ đầu câu, danh xưng (“Ông”, “Chủ tịch”) và nhiều từ không phải tên. Trên mạng xã hội thì ngược lại: “di ha noi voi lan”, không hoa, không dấu. Mô hình học trên báo chí chuẩn sẽ hụt mạnh trên kiểu văn bản này.
- Tên trùng từ thường như đã thấy ở phần Trực giác, và tên viết không dấu thì càng mơ hồ.
Giới hạn
Phần tiêu đề “Giới hạn”- Thực thể lồng nhau. “Đại học Quốc gia Hà Nội” là tổ chức chứa một nơi chốn. BIO chỉ biểu diễn được một tầng; CoNLL-2003 thường chỉ gán tầng ngoài cùng.
- Phụ thuộc miền. Model học trên tin tức Reuters không biết tên thuốc, tên linh kiện. Đổi miền thì cần dữ liệu gán nhãn mới, xem hộp thứ ba ở mục Lỗi hay gặp.
- Nhận dạng chưa phải hiểu. NER tìm ra “Vietcombank” nhưng không biết đó là ngân hàng nào trong cơ sở tri thức. Việc nối tên với một mục cụ thể là bài toán khác, gọi là liên kết thực thể (entity linking).
Minh họa
Phần tiêu đề “Minh họa”Bảng dưới là câu đầu tiên trong ô code, với nhãn đúng và nhãn mà một mô hình giả định dự đoán. Hãy nhìn vào cột “Nguyễn” và “Văn”: mô hình chỉ lệch một vị trí ở đầu thực thể.
| Từ | Nhãn đúng | Nhãn dự đoán | Khớp? |
|---|---|---|---|
| Ông | O | O | ✓ |
| Nguyễn | B-PER | O | ✗ |
| Văn | I-PER | B-PER | ✗ |
| An | I-PER | I-PER | ✓ |
| đến | O | O | ✓ |
| Hà | B-LOC | B-LOC | ✓ |
| Nội | I-LOC | I-LOC | ✓ |
| làm | O | O | ✓ |
| việc | O | O | ✓ |
| cho | O | O | ✓ |
| Vietcombank | B-ORG | B-ORG | ✓ |
| từ | O | O | ✓ |
| tháng | O | O | ✓ |
| 3/2024 | O | O | ✓ |
Mức token: 12 trên 14 token đúng, khoảng 0.86. Mức thực thể: giải mã nhãn đúng ra ba thực thể PER “Nguyễn Văn An”, LOC “Hà Nội”, ORG “Vietcombank”. Giải mã nhãn dự đoán ra PER “Văn An”, LOC “Hà Nội”, ORG “Vietcombank”. “Văn An” không khớp chính xác với “Nguyễn Văn An”, nên chỉ 2 trên 3 thực thể đúng, và người tên Nguyễn Văn An coi như không được tìm thấy. Đổi nhãn thì thành một tên người khác hẳn.
Câu thứ tư trong ô code cho thấy vì sao cần B-. Mô hình giả định ở đây quên mở thực thể mới ở “Thái”:
| Từ | Trận | Việt | Nam | Thái | Lan | … | Mỹ | Đình |
|---|---|---|---|---|---|---|---|---|
| Nhãn đúng | O | B-LOC | I-LOC | B-LOC | I-LOC | … | B-LOC | I-LOC |
| Nhãn dự đoán | O | B-LOC | I-LOC | I-LOC | I-LOC | … | O | O |
Chỉ một nhãn khác ở “Thái”, nhưng giải mã ra một thực thể dài “Việt Nam Thái Lan” thay vì hai. Cả hai thực thể thật bị tính là bỏ sót, cộng thêm một dự đoán sai.
Ví dụ code
Phần tiêu đề “Ví dụ code”Code trong bài chạy thử lần cuối tháng 11/2026: ô chấm điểm với Python 3.14 trên Pyodide (chỉ Python thuần), ô BERT với Python 3.14, transformers 5.22 và PyTorch 2.14 trên CPU. Ô chấm điểm chạy ngay trên trang và độc lập với ô kia. Ô BERT cần PyTorch và tải model nên chạy trên máy bạn.
Ô đầu tiên cài hàm giải mã BIO thành tập thực thể, theo quy ước ở phần Lý thuyết, rồi chấm bốn câu tiếng Việt. Mỗi thực thể là một bộ (loại, đầu, cuối) với điểm cuối không tính, như lát cắt của Python. Nhờ vậy “khớp chính xác” chỉ là phép giao hai tập: G & P.
Giải mã BIO, F1 mức thực thể và độ chính xác mức token
# Giải mã nhãn BIO thành thực thể, rồi chấm điểm mức thực thể và mức tokendef spans(tags): """Trả về tập (loại, đầu, cuối); cuối không tính, như lát cắt.""" out, start, typ = set(), None, None for i, tag in enumerate(tags + ["O"]): # "O" cuối để đóng thực thể pre, _, ty = tag.partition("-") # Đóng thực thể đang mở khi gặp O, B- hoặc I- khác loại if start is not None and (pre != "I" or ty != typ): out.add((typ, start, i)) start = None # Mở thực thể mới ở B-, hoặc ở I- mồ côi (quy ước của conlleval) if pre == "B" or (pre == "I" and start is None): start, typ = i, ty return out
data = [ # (câu, nhãn đúng, nhãn dự đoán) ("Ông Nguyễn Văn An đến Hà Nội làm việc cho Vietcombank từ tháng 3/2024", "O B-PER I-PER I-PER O B-LOC I-LOC O O O B-ORG O O O", "O O B-PER I-PER O B-LOC I-LOC O O O B-ORG O O O"), ("Chị Mai chuyển từ Bia Hà Nội sang Vinamilk", "O B-PER O O B-ORG I-ORG I-ORG O B-ORG", "O B-PER O O O B-LOC I-LOC O B-ORG"), ("Ông An mở tài khoản ở Ngân hàng Ngoại thương", "O B-PER O O O O B-ORG I-ORG I-ORG I-ORG", "O B-PER O O O O O I-ORG I-ORG I-ORG"), ("Trận Việt Nam Thái Lan diễn ra ở Mỹ Đình", "O B-LOC I-LOC B-LOC I-LOC O O O B-LOC I-LOC", "O B-LOC I-LOC I-LOC I-LOC O O O O O"),]n_gold = n_pred = n_ok = tok_ok = tok_all = all_o = 0for text, g, p in data: words, g, p = text.split(), g.split(), p.split() G, P = spans(g), spans(p) n_gold, n_pred, n_ok = n_gold + len(G), n_pred + len(P), n_ok + len(G & P) tok_ok += sum(a == b for a, b in zip(g, p)) all_o += g.count("O") tok_all += len(g) for ty, s, e in sorted(G - P, key=lambda x: x[1]): print("missed:", ty, " ".join(words[s:e])) for ty, s, e in sorted(P - G, key=lambda x: x[1]): print("wrong: ", ty, " ".join(words[s:e]))prec, rec = n_ok / n_pred, n_ok / n_goldprint(f"gold={n_gold} pred={n_pred} correct={n_ok}")print(f"entity P={prec:.3f} R={rec:.3f} F1={2*prec*rec/(prec+rec):.3f}")print(f"token accuracy={tok_ok / tok_all:.3f} ({tok_ok}/{tok_all})")print(f"all-O token accuracy={all_o / tok_all:.3f}")Lần chạy đầu cần tải Python, khoảng 10 MB.
Đọc kết quả:
- Câu 1 (lỗi biên):
missed: PER Nguyễn Văn Anvàwrong: PER Văn An, đúng như bảng ở mục Minh họa. - Câu 2 (lỗi biên và loại): mô hình thấy “Hà Nội” là nơi chốn, mất cả tổ chức “Bia Hà Nội”. Đây chính là chỗ nhập nhằng mà gazetteer cũng mắc.
- Câu 3 (I- mồ côi): nhãn dự đoán bắt đầu bằng
I-ORGsauO. Theo quy ước conlleval, nó vẫn mở thực thể, nhưng ra “hàng Ngoại thương”, thiếu chữ “Ngân”, nên vẫn sai. - Câu 4 (thiếu
B-): ba thực thể thật bị bỏ sót (hai nước bị gộp, “Mỹ Đình” không được tìm), cộng một dự đoán sai “Việt Nam Thái Lan”.
Tổng lại có 11 thực thể thật, mô hình đưa ra 9, khớp chính xác 5. Vậy , , và , khớp với dòng entity. Trong khi đó độ chính xác mức token là 34/43, khoảng 0.791: nghe như mô hình đúng gần 80%, nhưng thật ra nó chỉ tìm đúng chưa tới một nửa số thực thể. Dòng cuối là một mô hình đoán O cho mọi token: độ chính xác 0.488 mà không tìm được thực thể nào. Bốn câu ở đây dày đặc tên riêng; trong văn bản thật, tỉ lệ O cao hơn nhiều, nên khoảng cách giữa hai cách đo còn lớn hơn.
Thử sửa nhãn dự đoán của câu 1 thành giống nhãn đúng rồi chạy lại: missed của câu 1 biến mất, lên 6.
Ô thứ hai chạy một model BERT đã fine-tune cho NER: dslim/bert-base-NER. Theo model card, đó là bert-base-cased (Devlin và cộng sự, 2019) fine-tune trên phần tiếng Anh của CoNLL-2003, nên nhận bốn loại PER, LOC, ORG, MISC và đọc câu tiếng Anh. Ô in nhãn trên từng token trước, rồi gộp token thành thực thể theo hai cách.
Chạy trên máy bạn
Pipeline NER với BERT, trên từng token và sau khi gộp
- Cần cài
pip install transformers torch- Phần cứng
- CPU là đủ. Lần đầu tải model khoảng 433 MB; mỗi lần chạy sau mất chừng một phút trên máy phát triển, phần lớn là nạp thư viện và model
# Nhận dạng thực thể bằng BERT đã fine-tune trên CoNLL-2003from transformers import pipeline
text = ("Sundar Pichai met Nguyen Thi Lan at the Vietcombank office " "in Hanoi before flying to Berlin.")ner = pipeline("ner", model="dslim/bert-base-NER")
# Không gộp: mỗi token (kể cả mảnh ## của WordPiece) một nhãn B-/I-for tok in ner(text, aggregation_strategy="none")[:11]: print(f"{tok['entity']:6} {tok['score']:.3f} {tok['word']}")
# Gộp token thành thực thể theo hai cách:# "simple" gộp theo nhãn của từng token, "first" lấy nhãn subword đầufor strategy in ["simple", "first"]: print("---", strategy) for ent in ner(text, aggregation_strategy=strategy): word = text[ent["start"]:ent["end"]] print(f"{ent['entity_group']:4} {ent['score']:.3f} " f"[{ent['start']:2}, {ent['end']:2}) {word}")Kết quả mẫu
Lấy từ một lần chạy thật.
B-PER 1.000 SunI-PER 0.581 ##darI-PER 1.000 PiI-PER 0.997 ##chaI-PER 0.595 ##iB-PER 0.999 NgI-PER 0.973 ##uyB-PER 0.991 ##enI-PER 0.999 TI-PER 0.957 ##hiI-PER 0.990 La--- simplePER 0.834 [ 0, 13) Sundar PichaiPER 0.986 [18, 22) NguyPER 0.977 [22, 32) en Thi LanORG 0.978 [40, 51) VietcombankLOC 0.999 [62, 67) HanoiLOC 1.000 [85, 91) Berlin--- firstPER 1.000 [ 0, 13) Sundar PichaiPER 0.996 [18, 32) Nguyen Thi LanORG 0.993 [40, 51) VietcombankLOC 0.999 [62, 67) HanoiLOC 1.000 [85, 91) BerlinNgoài các dòng trên, thư viện còn in ra vài dòng báo cáo nạp trọng số, trong đó hai tham số bert.pooler được ghi UNEXPECTED. Lớp pooler chỉ dùng cho phân loại cả câu, bộ gán nhãn token không cần nó, nên bỏ qua được.
Đọc kết quả:
- Phần không gộp (
aggregation_strategy="none") trả về các token không mang nhãnO, và ô chỉ in 11 token đầu tiên trong số đó. “Nguyen” bị cắt thànhNg ##uy ##en, và mảnh##ennhậnB-PER, một nhãn vô lý giữa một từ. Nhiều khả năng model được huấn luyện theo cách mảnh đầu như bài báo BERT, nên nhãn ở##enkhông đáng tin. Model card không nói rõ cách huấn luyện, chỉ cảnh báo model đôi khi gán nhãn thực thể cho các mảnh subword. - Gộp kiểu
simplenối các token liền nhau theo nhãn của từng token.B-PERở##enmở một thực thể mới, nên một người thành hai mẩu vô nghĩa “Nguy” và “en Thi Lan”. Đây là lỗi biên mà F1 mức thực thể sẽ phạt hai lần. - Gộp kiểu
firstlấy nhãn ở mảnh đầu của mỗi từ, khớp với cách huấn luyện phổ biến. “Nguyen Thi Lan” ra trọn mộtPER. Mọi thực thể khác có cùng biên và loại như kiểusimple, chỉ điểm là khác. - Hai số trong ngoặc là vị trí ký tự đầu và cuối (cuối không tính) trong chuỗi gốc, nên
text[18:32]là “Nguyen Thi Lan”. Điểm là xác suất softmax của nhãn, lấy trung bình trên các token (hoặc các từ) của thực thể.
Model không có ngẫu nhiên khi dự đoán, nên mỗi lần chạy trên cùng máy cho cùng kết quả; bản thư viện khác có thể lệch ở chữ số cuối của điểm. Thử thay text bằng một câu tiếng Việt có dấu: model chỉ học tiếng Anh nên sẽ hụt nhiều, lý do có ở hộp cuối của mục dưới.
Lỗi hay gặp
Phần tiêu đề “Lỗi hay gặp”Kiểm tra nhanh
Phần tiêu đề “Kiểm tra nhanh”Tóm tắt và đọc thêm
Phần tiêu đề “Tóm tắt và đọc thêm”Ý chính:
- NER tìm các cụm gọi tên người, nơi chốn, tổ chức… với đúng ranh giới và loại. Nó là bài toán gán nhãn chuỗi: mỗi từ một nhãn. Gazetteer không đủ vì cùng một chuỗi đổi loại theo ngữ cảnh, và tên hay trùng từ thường.
- Mã hóa BIO dùng
B-X,I-X,O( nhãn);B-cần để tách hai thực thể cùng loại đứng sát nhau. Giải mã ngược thì phải chọn một quy ước cho I- mồ côi. - CRF chấm điểm cả dãy nhãn bằng điểm tại chỗ cộng điểm chuyển, nên tránh được dãy vô lý; BiLSTM-CRF thay đặc trưng thủ công bằng mạng. BERT chỉ cần một lớp tuyến tính trên vectơ mỗi token; nhãn đặt ở subword đầu, các mảnh sau nhận
-100để bỏ khỏi loss. - Đánh giá bằng F1 mức thực thể với luật khớp chính xác, . Độ chính xác mức token làm điểm đẹp hơn thật vì đa số token là
O.
Đọc thêm:
- Nguyễn Thị Minh Huyền, Ngô Thế Quyền, Vũ Xuân Lương, Trần Mai Vũ, Nguyễn Thị Thu Hiền (2018). VLSP Shared Task: Named Entity Recognition, Journal of Computer Science and Cybernetics 34(4), tr. 283–294. Mô tả dữ liệu và kết quả hai cuộc thi chung NER tiếng Việt VLSP 2016 và 2018.
- Jurafsky, D., Martin, J. H. (bản nháp 2026). Speech and Language Processing, ấn bản 3, chương 18 “Sequence Labeling for Parts of Speech and Named Entities”. Trình bày gán nhãn chuỗi, BIO, CRF, Viterbi và cách đánh giá NER.
- Tjong Kim Sang, E. F., De Meulder, F. (2003). Introduction to the CoNLL-2003 Shared Task: Language-Independent Named Entity Recognition, CoNLL 2003, mục 2 và 4. Bộ dữ liệu chuẩn, lược đồ nhãn IOB1 và luật khớp chính xác khi chấm điểm.
- Lafferty, J., McCallum, A., Pereira, F. (2001). Conditional Random Fields: Probabilistic Models for Segmenting and Labeling Sequence Data, ICML 2001. Bài gốc của CRF. Bài báo dùng dạng tổng quát với hàm đặc trưng; điểm tại chỗ cộng ma trận chuyển trong bài này là một trường hợp riêng.
- Lample, G. và cộng sự (2016). Neural Architectures for Named Entity Recognition, NAACL 2016, mục 2. BiLSTM-CRF với embedding ký tự, có giải thích vì sao cần ma trận chuyển và so BIO với BIOES.
- Devlin, J. và cộng sự (2019). BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding, NAACL 2019, mục 5.3. NER trên CoNLL-2003 bằng bộ phân loại trên subword đầu, không CRF.
- Hugging Face. Token classification, tài liệu
transformers. Hướng dẫn fine-tune cho NER, có hàm căn nhãn vớiword_ids()và-100, chấm điểm bằngseqeval.
Trình duyệt đang chặn lưu dữ liệu. Tiến độ sẽ mất khi bạn tải lại trang.
Nội dung theo giấy phép CC BY-NC-SA 4.0. Mã nguồn theo giấy phép MIT.Giới thiệu