Chuyển tới nội dung chính

Khớp theo nghĩa thay vì khớp chữ

Bảng nhúng đặt tayGhép đôi bằng cosineCó ca đối chứng

Khớp theo nghĩa thay vì khớp chữ

Bài BLEU để lại một câu hỏi treo: hai câu nói cùng một điều bằng bộ từ khác nhau thì đo bằng gì. Đây là câu trả lời, kèm cả chỗ nó hỏng.

Bài BLEU kết thúc ở một chỗ khó chịu. Bấm nút Đúng nghĩa, khác chữ trong sim của bài đó thì một bản dịch người đọc thấy hoàn toàn chấp nhận được vẫn nhận điểm 0, chỉ vì người dịch chọn từ khác. Nguyên nhân nằm ngay trong định nghĩa: BLEU hỏi hai cụm từ có trùng nguyên văn hay không, và câu trả lời chỉ có đúng hoặc sai. Không có chỗ nào để nói xe hơi với ô tô là gần nhau.

Cách sửa là thay câu hỏi. Thay vì hỏi hai token có giống nhau từng chữ không, ta gán cho mỗi từ một vector nhúng rồi hỏi hai vector đó có chỉ về cùng một hướng không, đo bằng cosine. Từ đó dựng ra một điểm số theo kiểu BERTScore. Trước khi bạn thử, một lời cảnh báo phải nói ngay chứ không giấu xuống cuối bài: bảng nhúng trong sim dưới đây do tôi gõ tay, 28 từ tiếng Việt với 9 chiều tôi tự đặt tên. Tôi chọn các con số sao cho những từ tôi coi là đồng nghĩa nằm sát nhau, nên chuyện chúng khớp đẹp không chứng minh được điều gì về mô hình thật.

Thang điểm ở đây cũng không giống thang điểm thật, và lệch theo chiều dễ gây hiểu lầm. Mô hình thật đọc hàng tỉ từ rồi tự rút ra bảng của nó, hàng trăm chiều không ai gọi tên được, nhưng các vector đó lại dồn gần hết vào một chóp hẹp, nên ngay cả hai câu chẳng liên quan gì cũng đã được cosine khá cao. Hệ quả là khoảng cách giữa một bản diễn đạt lại tử tế và một cặp câu ngẫu nhiên trong đời thật hẹp hơn nhiều so với khoảng cách bạn sắp thấy trong sim này, và đó chính là lý do bản cài đặt BERTScore chính thức còn kèm thêm bước chuẩn hoá theo một mức nền. Bảng của tôi thưa và gần như trực giao, nên nó tách các ca ra sạch sẽ một cách không thực tế. Sim này minh hoạ cơ chế, không minh hoạ chất lượng, và cũng không minh hoạ được thang điểm.

Khớp theo nghĩa · ghép token bằng cosine của vector nhúng
nghĩa 0.983chữ 0.200
Bảng nhúng ở cuối sim này do tôi gõ tay, không học từ dữ liệu nào cả. Tôi tự đặt các con số sao cho từ tôi coi là đồng nghĩa chỉ về cùng một hướng, nên kết quả đẹp là chuyện đương nhiên. Mô hình thật đọc hàng tỉ từ rồi tự rút ra bảng của nó, với hàng trăm chiều không ai gọi tên được, và vector của nó dồn hết vào một chóp hẹp: hai câu chẳng liên quan gì cũng đã được cosine khá cao, nên khoảng cách giữa ca cùng nghĩa và ca khác nghĩa ngoài đời hẹp hơn nhiều so với ở đây. Hãy tin thứ tự giữa các ca, đừng tin giá trị tuyệt đối.
Năm cặp đồng nghĩa liên tiếp: bố với cha, tậu với mua, chiếc với cái, xe_hơi với ô_tô, và một từ trùng nguyên văn là tôi. Khớp chữ gần như bằng không, khớp nghĩa gần như tuyệt đối.
khớp nghĩa (F1 kiểu BERTScore)
0.983
precision 0.983 · recall 0.983
khớp chữ (trùng nguyên văn)
0.200
precision 0.200 · recall 0.200
chênh lệch
+0.783
Nghĩa cao hơn chữ rất nhiều: hai câu nói cùng một điều bằng bộ từ khác.
Phép ghép đôi tham lam: câu máy tìm bạn ghép bên tham chiếu
Câu máy viết
0.9961.0000.9670.9650.986bốtôitậuchiếcxe_hơichatôimuacáiô_tô
Câu tham chiếu
Ở cặp câu này precision và recall bằng nhau (0.983), vì các cặp ghép đối xứng: token nào chọn nhau ở chiều này thì cũng chọn nhau ở chiều kia. Đổi chiều vẫn đổi hàng nguồn trong bảng, chỉ là trung bình không nhúc nhích. Bấm Khác nghĩa hoàn toàn rồi đổi chiều để thấy hai con số tách hẳn ra.
Token nguồnGhép vớicosine
bốcha0.996
tôitôi1.000
tậumua0.967
chiếccái0.965
xe_hơiô_tô0.986
Trung bình cộng, tức precision0.983
precision nghĩa (mỗi token câu máy lấy cosine cao nhất)0.983
recall nghĩa (mỗi token tham chiếu lấy cosine cao nhất)0.983
F1 khớp nghĩa = 2PR / (P + R)0.983
F1 khớp chữ, cùng công thức nhưng cosine đổi thành 0 hoặc 10.200
Bảng nhúng đặt tay: 28 từ, 9 chiều. Dòng tô đậm là từ đang có trong hai câu.
Từngườigia đìnhxe cộđộng vậtmua bándi chuyểnnghỉ ngơiđồ đạchư từLoại
bố0.920.900000000thực từ
cha0.880.800000000.10thực từ
mẹ0.860.920000000thực từ
tôi0.950.100000000.05thực từ
mình0.900.140000000.08thực từ
người0.960.1800.0600000thực từ
xe_hơi000.95000.2000.150thực từ
ô_tô000.90000.1500.300thực từ
xe_máy000.85000.3500.200thực từ
mèo0.05000.9500.100.1500thực từ
chó0.05000.9400.180.1000thực từ
mua000.0500.950.0500.050thực từ
tậu000.2500.850.0500.150thực từ
bán000.0800.920.0500.100thực từ
chạy000.100.0500.95000thực từ
di_chuyển000.12000.94000thực từ
đuổi0000.1500.85000thực từ
ngủ0000.10000.9500thực từ
nằm0000.0800.050.900.120thực từ
nghỉ0.05000.05000.9200thực từ
ghế0000000.100.950thực từ
bàn0000000.060.940thực từ
nhà00.1500000.200.900thực từ
chiếc000.2500000.150.88hư từ
cái000.0500000.300.90hư từ
con0.100.1500.3000000.88hư từ
một000000000.95hư từ
trên000000.0500.050.95hư từ
Ba chỗ yếu nhìn thấy ngay trong bảng này, và hai chỗ đầu không cùng một loại. Một, muabán ngược nghĩa nhau mà cosine tới 0.998, vì tôi xếp chúng cùng một chiều mua bán: chỗ này vector nhúng thật cũng dính, do hai từ trái nghĩa hay dùng chung ngữ cảnh, chỉ là mô hình thật vẫn chừa lại một khoảng chứ không sát tới mức này. Hai, trên cái chẳng liên quan gì mà vẫn được 0.961: chỗ này thì không đổ cho mô hình thật được, nó thuần tuý là hệ quả của việc tôi chỉ dành đúng một chiều hư từ cho cả năm hư từ. Ba, mọi giá trị đều không âm nên cosine luôn nằm trong đoạn 0 tới 1, không bao giờ âm, trong khi vector nhúng thật thì có.

Ba bước của phép chấm

Cách chấm chỉ có ba bước, và cả ba đều nhìn thấy được trong hình.

Bước một, ghép đôi. Mỗi token của câu máy đi tìm token gần nhất bên câu tham chiếu, gần theo nghĩa cosine lớn nhất. Đó là các mũi tên trong hình, số trên mũi tên là cosine của cặp đó.

Bước hai, lấy trung bình theo hai chiều. Trung bình cộng các cosine vừa tìm được, tính theo hướng câu máy đi tìm, gọi là precision. Đổi vai hai câu cho nhau, để mỗi token của câu tham chiếu đi tìm token gần nhất bên câu máy, được recall. Nút chiều đang xem ở thanh công cụ chuyển qua lại giữa hai hướng đó. Trên cặp câu mặc định hai hướng cho đúng cùng một số, vì năm cặp ghép ở đây đối xứng: token nào chọn nhau ở chiều này thì cũng chọn nhau ở chiều kia. Muốn thấy hai con số tách ra thì bấm Khác nghĩa hoàn toàn, lúc đó precision là 0.441 còn recall là 0.351.

Bước ba, gộp lại. F1 là trung bình điều hoà 2PR / (P + R), đúng công thức F1 quen thuộc. Điểm khớp chữ hiện song song bên cạnh chạy qua cùng một cỗ máy, chỉ khác chỗ cosine bị thay bằng chỉ số trùng nguyên văn: bằng 1 nếu hai token là cùng một chuỗi ký tự, bằng 0 nếu khác. Nhờ vậy hai con số so được với nhau, chúng chỉ khác nhau ở cách đo độ giống của một cặp từ.

Đọc cặp câu mặc định

Cặp câu mở sẵn là bố tôi tậu chiếc xe_hơi so với cha tôi mua cái ô_tô. Từ ghép nối bằng gạch dưới để bộ tách theo khoảng trắng giữ nguyên một từ, đúng quy ước của các bộ tách từ tiếng Việt.

Chỉ có đúng một token trùng nguyên văn là tôi, tức 1 trên 5 ở cả hai chiều, nên khớp chữ bằng 0.200. Trong khi đó năm cặp ghép theo nghĩa lần lượt là bố với cha được 0.996, tôi với tôi được đúng 1.000, tậu với mua được 0.967, chiếc với cái được 0.965, xe_hơi với ô_tô được 0.986. Trung bình lại thành precision 0.983, và vì năm cặp này ghép qua lại đối xứng nên recall cũng 0.983, F1 vì thế bằng luôn 0.983. Ô chênh lệch trong sim ghi +0.783, và khoảng cách đó chính là toàn bộ bài học.

Có điều phải đọc con số 0.983 cho đúng. Nó không nói phương pháp này chính xác tới 98%. Nó chỉ nói rằng năm cặp từ vừa liệt kê đã được chính tôi đặt sát nhau trong bảng, mà tôi thì chọn cặp câu trước rồi mới viết vector cho các từ trong cặp câu đó. Bảo bảng của mình chấm ngữ liệu của mình thì cũng như tự chấm bài mình, điểm đẹp là chuyện đương nhiên chứ không phải bằng chứng. Thứ đáng tin ở trang này là thứ tự giữa các ca, tức là cùng nghĩa phải cao hơn khác nghĩa, chứ không phải giá trị tuyệt đối của bất kỳ ca nào.

Ca đối chứng, và một điểm số không chịu về 0

Một bảng nhúng đặt tay rất dễ rơi vào cảnh mọi cặp từ đều gần nhau, lúc đó điểm cao trở nên vô nghĩa. Nên phải thử chiều ngược lại. Bấm Khác nghĩa hoàn toàn: con mèo ngủ trên ghế so với cha tôi mua cái ô_tô. Không có chữ nào trùng nên khớp chữ về 0.000, còn khớp nghĩa tụt xuống 0.391. Tách bạch rõ so với 0.983, nên bảng nhúng có phân biệt thật.

Nhưng vì sao 0.391 chứ không phải gần 0. Nhìn cột cosine trong bảng ghép đôi thì thấy ngay: con ghép với cái được 0.880, và trên cũng ghép với cái được 0.961. Cả ba đều là hư từ, và trong bảng của tôi mọi hư từ đều dồn về chiều hư từ, nên chúng gần nhau bất kể chúng đang nói gì. Riêng hai cặp đó đã chiếm 83% tử số của precision, và bật nút bỏ hư từ thì 70% điểm nền bốc hơi.

Tới đây phải tách bạch hai chuyện, vì gộp chúng lại là tôi đang mượn uy tín của một hiện tượng có thật để bao che cho bảng của mình.

Chuyện có thật, không phải lỗi của tôi: hư từ lặp lại ở gần như mọi câu nên chúng luôn tìm được bạn ghép và luôn kéo điểm nền lên. Mọi bản cài đặt BERTScore đều phải đối phó với nó, hoặc bằng trọng số IDF để hạ giá các từ xuất hiện khắp nơi, hoặc bằng cách trừ đi một mức nền. Sàn điểm là thật.

Chuyện của riêng bảng đặt tay, và tôi phải nhận: độ cao của cái sàn đó là do tôi dựng, không phải do phương pháp. Tôi chỉ có đúng một chiều hư từ và nhét cả năm hư từ vào đó với trọng số từ 0.88 tới 0.95, nên trêncái gần như trùng hướng dù một bên là giới từ chỉ vị trí còn một bên là loại từ. Muốn thấy đây là ngoại lệ chứ không phải mức nền chung thì so với phần còn lại của chính bảng này: hai từ không dính nhau về nghĩa thì rơi thẳng về 0, ngủ với ô_tô đúng bằng 0.000, ngủ với cha cũng vậy. Bảng của tôi không hề có sàn 0.96 ở khắp nơi, riêng chỗ hư từ mới có, và có vì tôi gộp trọn một nhóm từ loại vào một chiều duy nhất. Nói gọn: hiện tượng thì thật, còn 0.391 là con số của riêng bảng này chứ không phải một mức nền bạn nên chờ đợi ở đâu khác.

Bật nút bỏ hư từ để thấy chuyện gì xảy ra khi ta gạt chiếc, cái, con, một, trên ra trước khi chấm. Ca đối chứng rơi từ 0.391 xuống 0.117, còn cặp câu mặc định gần như đứng yên, nhích từ 0.983 lên 0.987. Một thao tác làm hai cặp câu tách xa nhau thêm nữa, đúng như mong đợi.

Bốn chỗ cách chấm này hỏng

Không chỗ nào trong bốn chỗ dưới đây là lý thuyết suông, bạn bấm được cả bốn.

  • Nó không nhìn thứ tự từ. Bấm Đảo thứ tự: con chó đuổi con mèo so với con mèo đuổi con chó. Hai câu dùng đúng cùng một túi từ nên mọi token tự ghép với chính nó, cả khớp chữ lẫn khớp nghĩa đều bằng 1.000, dù nghĩa ngược hẳn nhau. Đây là lỗ hổng lớn nhất.
  • Ghép đôi tham lam, không ràng buộc. Mỗi token nguồn chỉ chộp lấy bạn ghép tốt nhất của nó, không ai kiểm tra xem token đích đã bị chộp mấy lần. Gõ bố tôi vào ô câu máy và cha mèo vào ô tham chiếu: ở chiều precision, cả bố lẫn tôi đều trỏ vào cha, còn mèo không nhận được mũi tên nào. Cách ghép một đối một tối ưu tồn tại, nhưng nó đắt hơn và không phải cái BERTScore dùng.
  • Điểm số phụ thuộc hoàn toàn vào bảng nhúng. Trong bảng ở cuối sim, muabán ngược nghĩa nhau mà cosine tới 0.998, vì tôi xếp cả hai vào chiều mua bán. Chỗ này thì vector nhúng thật cũng dính, và dính vì đúng cái lý do đó: hai từ trái nghĩa xuất hiện trong những ngữ cảnh gần như giống hệt nhau, cái gì mua được thì cũng bán được, nên mô hình học ra hai vector chỉ về gần cùng một hướng. Đây là hiện tượng có thật, không phải tôi gõ nhầm. Cái do tôi là mức độ, và đo được ngay trong bảng: mua với bán được 0.998, trong khi một từ so với chính nó được đúng 1.000. Tức là bảng của tôi gần như không phân biệt nổi cặp trái nghĩa với cặp trùng khít, chỉ vì tôi cho hai từ dùng chung một chiều duy nhất. Hướng của cái sai là thật, còn cái mức sát tới đồng nhất kia là của tôi. Đổi bảng là đổi điểm, và không có bảng nào trung lập.
  • Từ ngoài bảng thì trắng tay. Bấm Từ ngoài bảng: phi_thuyền không có vector nên mọi cosine của nó bằng 0, kể cả với một bản sao y hệt, và F1 rơi xuống 0.816. Đó là quy ước tôi chọn để không có giá trị vô định nào lọt ra màn hình, chứ không phải chân lý. Đẩy quy ước ấy tới cùng thì nó thành phản trực giác hẳn: gõ phi_thuyền phi_thuyền vào cả hai ô, hai câu giống nhau từng chữ, khớp chữ cho 1.000 còn khớp nghĩa cho 0.000, tức là độ đo theo nghĩa chấm điểm thấp nhất có thể cho một cặp câu trùng khít. Sim bắt được ca này và ô chênh lệch ghi thẳng ra rằng chữ đang cao hơn nghĩa. Mô hình thật không gặp cảnh đó vì chúng tách từ thành mảnh nhỏ hơn rồi ghép lại, nên cái gì cũng có vector.

Cùng với bài BLEU và bài ROUGE trong chương này, bạn có đủ hai họ độ đo để đối chiếu. BLEU đếm cụm n từ trùng khít theo hướng câu máy, ROUGE đếm theo hướng câu tham chiếu, cả hai đều chỉ biết chữ. Cách chấm ở bài này đổi phép so từng cặp từ, giữ nguyên bộ khung precision cùng recall cùng F1 mà hai bài kia đã dựng, nên bạn có thể đọc ba bảng điểm cạnh nhau mà không phải học lại khái niệm mới nào.

Điều rút ra

Đổi phép so hai token từ trùng chữ sang gần hướng vector là đủ để một cặp câu cùng nghĩa khác chữ đi từ 0.200 lên 0.983. Nhưng điểm mới đó thừa hưởng trọn vẹn mọi thiên lệch của bảng nhúng, vẫn mù trước thứ tự từ, và vẫn cho hai câu ngược nghĩa điểm tuyệt đối khi chúng dùng chung một túi từ. Nó đo độ trùng ý của từng từ rời, không đo độ đúng của câu. Còn hai con số 0.200 và 0.983 thì là của riêng bảng đặt tay trong sim này, đừng mang chúng đi đâu.

Câu hỏi tự kiểm0/3 đúngchưa trả lời
  1. 1Cặp câu mặc định được khớp nghĩa 0.983 trong khi khớp chữ chỉ 0.200. Vì sao?
  2. 2Bấm Đảo thứ tự: con chó đuổi con mèo so với con mèo đuổi con chó, cả hai độ đo đều bằng 1.000. Điều đó nói lên gì?
  3. 3Ca đối chứng con mèo ngủ trên ghế so với cha tôi mua cái ô_tô được 0.391 chứ không phải gần 0. Phần điểm đó từ đâu ra?