Tokenization và text classification

Từ nhận xét lớp học đến một baseline đọc được lỗi

Chương 6 · Bài 6.1

2

Từ nhận xét đến nhãn

Nhận xét: “ví dụ hay”, “bài giảng khó hiểu”, “không tệ”.

Có vài nghìn nhận xét. Muốn tìm những phần cần sửa trước.

Chuỗi ký tự → token → vector → nhãn.

Câu hỏi: bước nào làm mất thông tin khi biến câu thành số?

3

Mục tiêu

  • Phân biệt các cách tách token: ký tự, từ, subword.
  • Làm một bước BPE bằng tay.
  • Dựng TF-IDF với logistic regression; so với Naive Bayes.
  • Đọc lỗi của model: phủ định, từ chưa gặp, dữ liệu trùng.
01

Câu trở thành token

Câu trở thành token5

Khoảng trắng chưa chắc là ranh giới từ

Cùng một câu tách theo ký tự, âm tiết, từ hay subword cho số token khác nhau.“học sinh học máy”ký tự16 tokenhọc␣sinh␣học␣máyâm tiết4 tokenhọcsinhhọcmáytừ2 tokenhọc_sinhhọc_máysubword4 tokenhọc##sinhhọc##máycùng một câu, số token tùy đơn vị tokenizer chọn
Cùng một câu tách theo ký tự, âm tiết, từ hay subword cho số token khác nhau.
Câu trở thành token6

Unicode và token IDs

print(normalize_text(sentence).split())
nfc = unicodedata.normalize("NFC", "ho\u0323c")
print(nfc == "học")
Kết quả['học', 'sinh', 'học', 'máy']
True
  • NFC gộp “o” và dấu nặng rời thành “ọ”, vẫn giữ dấu.
  • ID là địa chỉ trong bảng, không phải độ lớn ý nghĩa.
  • Tokenizer đi cùng model: PhoBERT cần học_sinh.
Câu trở thành token7

Hai cách ghi chữ “ọ”

Hai chuỗi cùng hiện là “học” nhưng khác số ký tự; NFC đưa về một dạng.gõ sẵnh0x68ọ0x1ecdc0x63dấu rờih0x68o0x6f◌̣0x323c0x63NFC gộp lạivẫn giữ dấu
Hai chuỗi cùng hiện là “học” nhưng khác số ký tự; NFC đưa về một dạng.
Câu trở thành token8

Hỏi nhanh

"học sinh".split() trả về hai mảnh. Từ đó ta rút ra được điều gì?

Câu trở thành token9

Hỏi nhanh

Chuỗi b in ra trông như “học”, nhưng b == "học" lại là False, và [hex(ord(ch)) for ch in b] cho ['0x68', '0x6f', '0x323', '0x63']. Trước khi đếm token, nên xử lý thế nào?

02

Subword cho từ hiếm

Subword cho từ hiếm11

Một bước BPE

Train: low 3 lần, lower 2 lần, new 1 lần.

Cặp l,o: 3+2=53+2=5 lần.

Phá hòa theo thứ tự từ điển: gộp l,o trước. Lặp đến khi đủ từ vựng.

Kết quả('l', 'o') 5
low ['lo', 'w']
lower ['lo', 'w', 'e', 'r']
new ['n', 'e', 'w']

Đếm có trọng số, không đếm mỗi loại từ một lần.

Subword cho từ hiếm12

Hỏi nhanh

Sau bước gộp l,o, corpus gồm lo w 3 lần, lo w e r 2 lần, và n e w 1 lần. Bước BPE thứ hai gộp cặp nào?

Subword cho từ hiếm13

Hai bước gộp

BPE gộp dần cặp hay gặp nhất: l,o rồi lo,w, nên “low” thành một token.ký tựlowlowernewgộp l,o (5 lần)lowlowernewgộp lo,w (5 lần)lowlowernewlow × 3lower × 2new × 1tần suất có trọng số: l,o có trong low (3) và lower (2), cộng 5
BPE gộp dần cặp hay gặp nhất: l,o rồi lo,w, nên “low” thành một token.
Subword cho từ hiếm14

BPE học trên lời giảng của khóa

BPE học trên lời giảng của giáo trình: từ vựng càng lớn, câu càng ít token.
Subword cho từ hiếm15

WordPiece: mảnh dài nhất, không quay lui

Từ vựng có play, ##ing: playing thành play, ##ing.

## đánh dấu mảnh nối tiếp (BERT).

Khi học: chọn cặp có f(a,b)f(a) f(b)\frac{f(a,b)}{f(a)\,f(b)} cao nhất.

Không mảnh nào khớp: cả từ thành [UNK].

BPE lưu luật gộp; WordPiece chỉ giữ vocabulary cuối.

GPT-2: BPE trên byte, không cần token unknown.

Subword cho từ hiếm16

Hỏi nhanh

Từ vựng WordPiece chỉ có un, una, ##able và [UNK]. Mã hóa từ unable thì được gì?

Subword cho từ hiếm17

Tham lam rồi kẹt

WordPiece lấy mảnh dài nhất trước; phần còn lại không khớp nên cả từ thành UNK.từ vựngununa##able[UNK]unableuna+ ble ?##ble, ##bl, ##b đều không có→[UNK]tham lam lấy mảnh dài nhất, không quay lui thử un + ##able
WordPiece lấy mảnh dài nhất trước; phần còn lại không khớp nên cả từ thành UNK.
03

Một baseline từ vựng

Một baseline từ vựng19

TF-IDF

idf⁡j=log⁡1+n1+df⁡j+1\operatorname{idf}_j=\log\frac{1+n}{1+\operatorname{df}_j}+1

Số lần xuất hiện nhân IDF, rồi chuẩn hóa L2 từng câu.

Mục phổ biến giảm trọng số tương đối. Công thức không tự hiểu nghĩa: hai từ đồng nghĩa vẫn ở hai cột.

Một baseline từ vựng20

Hỏi nhanh

Sau TF-IDF có chuẩn hóa L2 mặc định, nhận xét “hay” và nhận xét “hay hay hay” cho ra hai vector thế nào?

Một baseline từ vựng21

Logistic regression đọc vector

z=w⊤x+bz=\mathbf{w}^{\top}\mathbf{x}+b p1=11+e−zp_1=\frac{1}{1+e^{-z}}

Nhãn 1: tích cực.

Nhãn 0: tiêu cực.

Unigram bỏ thứ tự; bigram giữ một phần thứ tự.

Một baseline từ vựng22

Model đã học từ nào

Các từ có trọng số lớn nhất đều là từ khen, chê trực tiếp; không có cột nào cho “không”.rốisaichánnhàm chánnhàmkhóvịràngrõrõ ràngdễhay← kéo về tiêu cựckéo về tích cực →
Các từ có trọng số lớn nhất đều là từ khen, chê trực tiếp; không có cột nào cho “không”.
Một baseline từ vựng23

Naive Bayes: học bằng đếm

log⁡P(y∣x)=log⁡P(y)+∑jxjlog⁡P(j∣y)+const\log P(y\mid\mathbf{x})=\log P(y)+\sum_j x_j\log P(j\mid y)+\text{const}

Làm trơn Laplace α=1\alpha=1: không có xác suất 0.

Giả định: token độc lập khi biết nhãn.

“theo” gần như luôn đi với “dõi”: bằng chứng bị cộng hai lần, xác suất quá tự tin.

Vẫn là bộ phân loại tuyến tính trên vector đếm.

Một baseline từ vựng24

Train học, validation biến đổi

Chuẩn hóa văn bản, TF-IDF fit trên train, rồi logistic regression.“bài học hay”NFC, viết thườngTF-IDF69 cộtlogisticp(tích cực)vocabulary và IDF chỉ fit trên 12 câu trainvalidation chỉ transform; từ chưa gặp như “không” bị bỏ
Chuẩn hóa văn bản, TF-IDF fit trên train, rồi logistic regression.
Một baseline từ vựng25

Dùng Pipeline

baseline = Pipeline([
    ("tfidf", TfidfVectorizer(
        preprocessor=normalize_text,
        ngram_range=(1, 2))),
    ("classifier", LogisticRegression(
        C=4.0, random_state=0, max_iter=1000)),
])
baseline.fit(train_texts, train_labels)

12 câu train, 6 câu validation tự soạn.

Mỗi câu validation đổi accuracy 1/6≈0.167{1/6\approx0.167}.

Đủ để soi lỗi, quá ít để so hai model.

Một baseline từ vựng26

Kết quả thật

Kết quảshape train: (12, 69)
accuracy: 0.667
macro-F1: 0.667
[[2 1]
 [1 2]]

69 cột: 31 âm tiết và 38 bigram của train.

Hàng: nhãn thật. Cột: nhãn đoán.

4/6 câu đúng, mỗi lớp có một câu bị nhầm.

Một baseline từ vựng27

Hai ô lệch là hai câu nào

Hai câu đoán sai đều chứa “không”.2112đoán 0đoán 1thật 0thật 1“bài giảng không hay”“khóa học không tệ”accuracy 4/6 = 0.667, macro-F1 0.667
Hai câu đoán sai đều chứa “không”.
Một baseline từ vựng28

Hỏi nhanh

Ta muốn chạy 5-fold cross-validation cho baseline trên cả 18 câu có nhãn. Làm cách nào thì điểm mới tin được?

Một baseline từ vựng29

Từ phủ định đã bị bỏ

Kết quảthật=0 đoán=1 p1=0.597 | bài giảng không hay
thật=1 đoán=0 p1=0.448 | khóa học không tệ
'không' có trong vocabulary: False

Từ lạ không có cột.

Bigram chưa học cũng không có cột.

Đọc token và feature trước khi đổi model.

Một baseline từ vựng30

Xác suất trên sáu câu

Hai câu sai chứa “không”, từ chưa có trong vocabulary.01bài học hay0.651lời giải sai0.407bài giảng không hay0.597ví dụ rõ ràng0.700nội dung rối0.370khóa học không tệ0.448p(tích cực)tích cực thậttiêu cực thậtviền: sai
Hai câu sai chứa “không”, từ chưa có trong vocabulary.
Một baseline từ vựng31

Đổi classifier không cứu được

naive_bayes = Pipeline([
    ("count", CountVectorizer(
        preprocessor=normalize_text,
        ngram_range=(1, 2))),
    ("classifier", MultinomialNB(alpha=1.0)),
])
Kết quảNB đoán: [1 0 1 1 0 0]
thật:    [1 0 0 1 0 1]

Sai đúng hai câu “không”.

Cùng vocabulary nên cùng lỗi.

Một baseline từ vựng32

Hỏi nhanh

Cả logistic regression lẫn Naive Bayes đều đoán ngược hai câu có chữ “không”. Bước tiếp theo nào nhắm đúng nguyên nhân?

Một baseline từ vựng33

TF-IDF của một câu

3 câu: “hay dễ”, “hay rõ”, “khó”.

IDF của “hay” là 1.288, của “dễ” là 1.693.

Sau L2: (0.605,0.796)(0.605,0.796).

Trọng số giả định (1,−1)(1,-1), bias 0:

z≈−0.191z\approx-0.191, p≈0.452p\approx0.452.

Nếu “hay” có ở cả ba câu: IDF bằng 1, giảm tương đối chứ không bị xóa.

Một baseline từ vựng34

Hỏi nhanh

Giữ IDF như trên, trọng số giả định là (1,−1)(1, -1) và bias bằng 0. Câu “hay hay dễ” cho pp xấp xỉ bao nhiêu?

Một baseline từ vựng35

Khi đi thi

  • TF-IDF + logistic regression trước.
  • Validation chia giống test; loại bản trùng.
  • Đếm độ dài bằng đúng tokenizer.
04

Luyện đề

Luyện đề37

Luyện đề 1

Câu được mã hóa thành [BOS] + từ + [EOS], dài quá thì cắt tokens[:max_len]. Với max_len = 8 và một câu 9 từ, lỗi chính là gì?

Phỏng theoNOAI 2026 Singapore · Chung kết · Câu 19

Luyện đề38

Luyện đề 2

Bình luận sản phẩm có nhiều tên hãng mới và lỗi gõ như “đẹppp”. Tokenizer theo từ đã có 300 nghìn mục mà test vẫn đầy từ lạ. Chuyển sang subword như BPE hay WordPiece thì được lợi chính ở đâu?

Phỏng theoNOAI 2026 Singapore · Vòng sơ loại · Câu 235

Luyện đề39

Luyện đề 3

Corpus là list từ ["bun"]*5 + ["sun", "sing", "king", "ring"]. Một bạn đếm các cặp ký tự kề nhau trong vòng for word in set(corpus), và phá hòa theo thứ tự từ điển. Bước gộp BPE đầu tiên đúng ra là gì, và code này gộp gì?

Phỏng theoUSA-NA-AIO 2026 · Vòng 1 · Part 5.1-5.2

Luyện đề40

Chốt ý

Tokenization quyết định thông tin nào vào được model; TF-IDF chỉ giữ từ vựng.

Hai model cùng sai một câu: xem biểu diễn trước khi đổi model.

Luyện đề41

Sau giờ học

Làm 10 câu kiểm tra cuối bài trên trang bài học.

Chạy notebook và đọc hai câu phủ định trước khi thử một thay đổi có kiểm chứng.