Token, OOV và ba cách tách
Token, OOV và ba cách tách
Mô hình không đọc chữ, nó đọc token. Cùng một câu, đổi cách tách là đổi luôn số token, chi phí, và cả việc mô hình có hiểu nổi từ đó hay không.
Trước mọi phép tính, văn bản phải được cắt thành các đơn vị rời gọi là token. Cắt thế nào là một lựa chọn, và lựa chọn đó kéo theo hai con số bạn sẽ gặp suốt môn này: số token (quyết định chi phí và độ dài ngữ cảnh) và tỉ lệ OOV, tức phần từ nằm ngoài từ vựng mà mô hình đành bó tay.
Hãy thử đúng một việc: giữ nguyên câu, bấm lần lượt ba chế độ. Từ tokenization ở chế độ Từ bị đánh dấu OOV vì nó không có trong từ vựng, nhưng ở chế độ Mảnh từ nó vỡ thành token cộng ##iza cộng ##tion, toàn mảnh quen. Ba thuật ngữ cần nhớ đúng nghĩa: token là đơn vị mô hình thật sự xử lý và không nhất thiết là từ, từ vựng là tập hữu hạn các token mà mô hình biết, còn OOV (out of vocabulary) là từ không có trong tập đó.
Ở trạng thái mở bài, sim đang nói gì
Câu trong ô dài 73 ký tự kể cả 13 dấu cách, và chế độ đang bật là Từ. Bốn ô số phía dưới đọc: 16 token, 60 ký tự sau khi bỏ khoảng trắng, 3.75 ký tự trên mỗi token, và tỉ lệ OOV 7%.
16 token đó không phải 16 từ. Chúng gồm 14 chip từ và 2 chip dấu câu, là dấu phẩy sau nhỏ và dấu chấm cuối câu. Chuyện này quan trọng vì tỉ lệ OOV chia cho số chip từ, không chia cho tổng số chip. Chỉ tokenization nằm ngoài từ vựng, nên con số thật là 1/14, tức 0.07142857142857142, và màn hình làm tròn thành 7%. Nếu chia cho cả 16 chip thì ra 6.25% và màn hình sẽ ghi 6%. Hai cách chia chỉ lệch nhau một chữ số trên hình mà lại là hai phát biểu khác nhau, nên khi ai đó báo cáo tỉ lệ OOV, câu hỏi tiếp theo phải là chia cho cái gì.
Giữ nguyên câu, bấm sang Ký tự: 60 token và đúng 1.00 ký tự trên mỗi token, vì mỗi ký tự khác khoảng trắng là một token. Ô OOV đổi thành một dấu gạch ngang chứ không phải 0%, vì ở chế độ này khái niệm từ lạ không tồn tại để mà đo. Bấm sang Mảnh từ: 18 token, 3.33 ký tự trên mỗi token, và ô cuối đổi tên thành "mảnh là phần nối", đọc 13%. Con số 13% đó là 2 mảnh nối trên 16 mảnh, tức đúng 0.125, và nó được làm tròn lên. Trong 18 chip có 14 mảnh mở đầu, 2 mảnh nối và 2 dấu câu; chỉ mỗi tokenization bị cắt, mọi từ khác vẫn nguyên một mảnh.
Ba con số 60, 16 và 18 là toàn bộ nội dung của bài này. Một câu, không sửa một chữ nào, ba cách đếm, ba kết quả. Tỉ số giữa cực đoan nhất và gọn nhất là đúng 3.75 lần, và đó cũng chính là con số trong ô "ký tự trên mỗi token".
Mọi con số phía sau đều treo trên con số này
Số token không phải một chi tiết kỹ thuật đứng riêng. Nó là mẫu số hoặc tử số của gần như mọi thứ bạn sẽ đo về sau: độ dài trung bình của câu, chi phí một lần gọi mô hình, số câu nhét vừa cửa sổ ngữ cảnh, và mọi độ đo có chữ "token" trong công thức. Đổi bộ tách là đổi hết một lượt, âm thầm, không có thông báo nào.
Cụ thể với câu trên: nếu bạn tính tiền theo token thì cùng một câu tốn 16 hay 60 đơn vị tuỳ bộ tách, gấp 3.75 lần. Nếu bạn báo cáo "câu trung bình dài 16 token" thì con số ấy chỉ có nghĩa khi đi kèm tên bộ tách. Và nếu bạn cắt tài liệu thành mẩu 512 token để đưa vào kho tìm kiếm, thì mẩu cắt bằng bộ tách này và mẩu cắt bằng bộ tách kia chứa lượng chữ khác hẳn nhau.
Tiếng Việt: khoảng trắng không cắt ra từ
Gõ học sinh vào ô và nhìn: chế độ Từ cho 2 token, Mảnh từ cũng 2, Ký tự cho 7. Không chế độ nào trả về 1. Nhưng học sinh là một từ, chỉ gồm hai âm tiết. Khoảng trắng trong tiếng Việt ngăn cách âm tiết, không ngăn cách từ, nên cắt ở khoảng trắng là cắt ra âm tiết.
Chỗ khó chịu là mất mát này không hiện lên ô nào. Cả học lẫn sinh đều nằm trong từ vựng nên tỉ lệ OOV đọc 0%, sạch sẽ, trong khi đơn vị đếm đã sai. Muốn thấy con số đổi thì phải tự viết liền: Học sinh học máy tính cho 5 token, còn Học_sinh học máy tính cho 4.
Sim này không có bộ tách từ ghép, và đó là giới hạn cố ý chứ không phải thiếu sót cần vá. Ghép âm tiết thành từ cần một từ điển cộng một thuật toán riêng, kèm những chỗ nó sai. Bài âm tiết không phải là từ đặt ba cách tách cạnh nhau trên một từ điển từ ghép, cũng là đồ chơi và chỉ có 40 mục, rồi đo xem điểm BLEU lệch bao nhiêu vì chuyện này. Ở đây chỉ cần nhớ một câu: mỗi lần bạn thấy chữ "token" trong một báo cáo tiếng Việt, hãy hỏi đó là âm tiết hay là từ.
Bấm preset Dấu tiếng Việt để thấy cùng vấn đề ở mặt khác. Câu đó cho 50 ký tự, 15 token theo từ, 35 token theo mảnh, và tỉ lệ OOV 58%, tức 7 trong 12 từ nằm ngoài từ vựng. Từ vựng đồ chơi ở đây chỉ có 68 mục nên tiếng Việt thường xuyên rơi ra ngoài, và đó chính là cảm giác mà một mô hình huấn luyện thiếu tiếng Việt gặp phải.
Dấu câu, số và chữ viết tắt phá luật tách đơn giản
Luật tách của chế độ Từ phát biểu bằng lời chỉ có một câu: cắt ở khoảng trắng, mỗi ký tự dấu câu tự nó là một token, khoảng trắng bị bỏ đi. Nghe vô hại. Hãy tự thêm vài thứ vào ô và đếm.
Lấy Mô hình đọc văn bản làm gốc, nó cho 5 token.
- Thêm một dấu chấm cuối câu:
6token. Đúng như chờ đợi, dấu chấm là một token. - Thêm
2026: cũng6token. Cả năm chỉ tốn một token, vì chữ số không phải dấu câu nên chúng dính liền nhau. - Thêm
T.S.:9token, tức thêm bốn. ChuỗiT.S.bị xé thànhT,.,S,..
Cùng độ dài bốn ký tự, T.S. tốn 4 token còn 2026 chỉ tốn 1, tức gấp bốn lần, và không có gì trên màn hình báo trước điều đó. Cùng cơ chế ấy, 3.14 không phải một số mà là ba token 3, ., 14, và vì cả 3 lẫn 14 đều không có trong từ vựng nên ô OOV nhảy lên 100%. Chuỗi v.v. cũng 100% OOV, trên hai token một chữ cái.
Hai ký tự nữa đáng biết vì chúng không nằm trong tập dấu câu: dấu gạch nối và dấu gạch dưới. Nhờ vậy bán-dẫn giữ nguyên 1 token và máy_tính cũng 1 token. Đó là một quyết định trong cài đặt, không phải chân lý; một bộ tách khác hoàn toàn có thể cắt ở gạch nối và cho ra con số khác.
Chế độ Mảnh từ phản ứng ngược lại với chữ số. Câu gốc ở đó là 7 mảnh, thêm 2026 thành 11, vì không chữ số nào có trong từ vựng mảnh nên 2026 vỡ thành 2, ##0, ##2, ##6. Một năm tốn 1 token theo từ và 4 token theo mảnh. Nếu bạn từng thấy mô hình làm toán rất tệ, đây là một phần lý do: con số nó nhìn thấy không phải một con số.
Chữ hoa: hai chế độ xử lý khác nhau
Còn một khác biệt nhỏ mà dễ làm người ta đếm nhầm. Ở chế độ Từ, chip giữ nguyên cách viết bạn gõ, nên chip đầu câu mẫu đọc Bộ có chữ B hoa. Ở chế độ Mảnh từ, chip đó đọc bộ chữ thường, vì bộ tách mảnh quy hết về chữ thường như phần lớn bộ tách thật.
Riêng phép tra từ vựng thì cả hai chế độ đều hạ chữ thường trước khi so. Gõ TOKEN hay ToKeN đều không bị dán nhãn OOV, dù chip vẫn hiện đúng cách viết của bạn. Nói cách khác, cái bạn nhìn thấy trên chip và cái được đem đi tra là hai thứ, và nếu bạn tự viết một bộ đếm type thì phải chọn một trong hai chứ không được lẫn lộn.
Cùng một văn bản, hai bộ tách, hai con số
Bốn nút preset cho bốn cặp số, đo trên chính engine của sim:
| Preset | Ký tự | Từ | Mảnh từ | OOV theo Từ |
|---|---|---|---|---|
| Câu mẫu | 60 | 16 | 18 | 7% |
| Từ hiếm | 48 | 14 | 29 | 38% |
| Tiếng Anh | 45 | 15 | 20 | 15% |
| Dấu tiếng Việt | 50 | 15 | 35 | 58% |
Nhìn cột Mảnh từ rồi nhìn cột Từ. Ở cả bốn preset, tách theo mảnh cho nhiều token hơn tách theo từ, có chỗ gấp hơn hai lần. Điều này ngược với câu hay được nói rằng mảnh từ cho chuỗi ngắn. Câu đúng, và cổng kiểm khẳng định đúng câu đó, là mảnh từ ngắn hơn ký tự, chứ không ngắn hơn từ. Ở đây từ vựng mảnh chỉ có 100 mục, quá nhỏ để phủ tiếng Việt, nên phần lớn âm tiết vỡ vụn. Một bộ tách thật với vài chục nghìn mục thì tỉ lệ này khác hẳn, nhưng chiều so sánh vẫn phải được đo chứ không được đoán.
Vậy lợi ích thật của mảnh từ nằm ở đâu? Ở cột OOV. Bấm preset Từ hiếm: 38% số từ là OOV ở chế độ Từ, còn ở chế độ Mảnh từ thì không chip nào bị dán nhãn OOV, vì luôn có đường lui về từng ký tự. Từ embedding vỡ thành ba mảnh quen là em, ##bed, ##ding, không mảnh nào lạ. Từ bịa zzyzx thì vỡ thành năm ký tự rời và cả năm đều bị gạch chân, tức hệ vẫn xử lý được nhưng không hiểu gì. Hai kiểu vỡ đó rất khác nhau, và ô OOV 0% không phân biệt được chúng.
Đây là chỗ rút ra bài học chính. So hai hệ thống bằng số token là so nhầm nếu chúng dùng bộ tách khác nhau. Một hệ báo "trung bình 14 token mỗi câu" và một hệ báo "29 token mỗi câu" có thể đang xử lý y hệt cùng một văn bản. Muốn so được thì hoặc dùng chung bộ tách, hoặc đổi sang một đơn vị không phụ thuộc bộ tách, ví dụ số ký tự: cột Ký tự trong bảng trên không đổi khi bạn bấm nút, vì nó không hỏi ý bộ tách nào cả.
Ghép hai đoạn thì số token có cộng lại được không
Câu hỏi này nghe vụn vặt nhưng nó là thứ làm hỏng các bộ đếm chạy theo lô. Cổng kiểm ghép từng cặp trong kho 27 đoạn văn bản, tức 729 cặp có thứ tự, rồi so số token của đoạn ghép với tổng hai lần đếm riêng.
- Chế độ Ký tự: cộng đúng ở cả
729cặp. Không có ngoại lệ nào, vì một ký tự là một token bất kể hàng xóm của nó. - Chế độ Từ: sai ở
280cặp, và mọi cái sai đều đúng bằng thiếu một token. Đó chính là280cặp mà ký tự cuối của đoạn trước và ký tự đầu của đoạn sau đều là ký tự chữ, nên hai từ dính vào nhau thành một. - Chế độ Mảnh từ: sai ở
140cặp, nhưng độ lệch không cố định: có chỗ thiếu1, có chỗ thừa1, có chỗ thừa2. Từ vừa dính lại được cắt lại từ đầu, và cách cắt mới có thể ra nhiều mảnh hơn hẳn.
Chỗ đáng chú ý là 140 cặp sai của mảnh từ là tập con của 280 cặp dính chữ, chứ không bằng nhau: 140 cặp còn lại tuy có dính chữ mà số mảnh vẫn không đổi. Nói cách khác, dính chữ là điều kiện cần chứ không đủ. Nếu bạn chỉ khẳng định "ghép thì thiếu một token" thì bạn đã ghim một đẳng thức sai ở hai trong ba chế độ.
Cách sửa thì tầm thường: chèn một dấu cách vào chỗ nối. Quét lại 729 cặp nhân 3 chế độ, tổng 2187 mẫu, số token cộng đúng ở toàn bộ 2187. Và dấu cách đó không tự biến thành token, vì khoảng trắng luôn bị bỏ đi. Đây là lý do các đường ống xử lý thật luôn nối văn bản bằng một dấu ngăn tường minh chứ không dán thẳng.
Từ vựng mảnh ở đâu ra
Từ vựng mảnh trong sim này gồm 100 mục viết tay, và mảnh nào dài nhất khớp được thì được lấy trước. Không ai ngồi viết bộ mảnh cho một mô hình thật. Nó được học từ ngữ liệu, và bài BPE học từ vựng như thế nào cho bạn bấm tay từng bước hợp nhất để thấy nó mọc ra. Còn chuyện giữ lại bao nhiêu mục thì đủ, và cái giá phải trả khi cắt bớt, là nội dung của bài kích thước từ vựng, độ phủ và đuôi dài Zipf.
Một chi tiết nhỏ đo được ở đây, đáng nói vì nó khiêm tốn hơn tôi tưởng. Luật "lấy mảnh dài nhất" nghe rất quyết định, nhưng trên chính 63 từ khác nhau của kho văn bản thử thì luật dài nhất và luật ngắn nhất chỉ khác nhau ở đúng một từ, là tokenizing. Lý do là từ vựng quá thưa: ở hầu hết vị trí chỉ có một mảnh khớp được, mà một mảnh thì dài nhất cũng là ngắn nhất. Phải cố ý chọn những từ có hai mảnh cùng khớp, ví dụ modelers (model cộng ##ers thay vì model cộng ##er cộng ##s), thì luật mới lộ ra là có tác dụng.
Những con số trong bài được kiểm bằng gì
Cổng checks/tokenizer.check.js khoá 224 khẳng định, và nó không tin phép tách của engine. Nó viết lại cả ba bộ tách bằng biểu thức khác: chế độ ký tự viết theo lối "xoá khoảng trắng rồi đo", chế độ từ viết bằng hai biểu thức chính quy thay vì vòng lặp gom ký tự, chế độ mảnh viết bằng cách hỏi một tập hợp thay vì quét một mảng đã sắp xếp. Hai đường phải khớp trên toàn bộ 81 phép đếm của kho thử, và khớp cả từng chuỗi chip chứ không chỉ khớp số lượng.
Nói rõ những gì cổng không bảo chứng. Nó không nói ba cách tách này tốt hay dở; mọi tỉ lệ trong bài là tính chất của đúng bộ từ vựng 68 mục và 100 mục ở đây, không phải quy luật chung. Nó cũng không đụng tới chuyện tách từ ghép tiếng Việt, vì engine không có từ điển để làm việc đó. Còn giới hạn duy nhất trong sim, là ô nhập chỉ tách 2000 ký tự đầu, thì được báo bằng chữ ngay dưới ô hướng dẫn khi bạn dán quá tay, chứ không cắt lặng lẽ.
Số token không phải thuộc tính của văn bản, nó là kết quả của một lựa chọn. Cùng một câu cho 60, 16 hay 18 token tuỳ bạn bấm nút nào, và mọi con số dựng trên đó đổi theo. Với tiếng Việt, khoảng trắng cắt ra âm tiết chứ không cắt ra từ, và mất mát đó không hiện lên ô OOV. Dấu câu và chữ viết tắt phá luật tách theo những cách khó đoán: T.S. tốn 4 token còn 2026 chỉ tốn 1. Và số token thậm chí không cộng được khi ghép hai đoạn, trừ khi bạn chèn một dấu cách vào chỗ nối.
- 1Ở trạng thái mở bài, sim đọc 16 token và OOV 7%. Con số 7% đó được tính bằng cách nào?
- 2Bạn dán hai đoạn văn bản sát nhau rồi đếm token. Kết quả so với tổng hai lần đếm riêng ra sao?
- 3Hệ A báo cáo trung bình 14 token mỗi câu, hệ B báo cáo 29. Kết luận nào đúng?