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

Cross-entropy và perplexity

Sửa ngữ liệu và câu thửBảng từng tokenPPL = 2^H

Cross-entropy và perplexity

Hỏi một mô hình ngôn ngữ là nó đoán giỏi tới đâu, câu trả lời gọn nhất là một con số. Con số đó có hai bộ mặt: cross-entropy tính bằng bit, và perplexity tính bằng số phương án. Chúng là cùng một thứ.

Cho một câu thử w_1 w_2 ... w_N, mô hình lần lượt gán cho mỗi vị trí một xác suất P(w_i | ngữ cảnh). Đoán giỏi nghĩa là các xác suất đó cao. Muốn gộp N con số ấy thành một, ta không lấy trung bình cộng mà lấy trung bình của log xác suất rồi đổi dấu, được cross-entropy: H = -(1/N) × Σ log2 P(w_i | w_i-1), đơn vị là bit trên token. Nâng cơ số lên H thì ra perplexity: PPL = 2^H.

Trong sim dưới đây, xác suất không phải do tôi gõ tay. Nó đến từ một mô hình bigram ước lượng ngay tại chỗ từ ngữ liệu bạn thấy trong ô bên trái, làm mượt bằng add-1 để không bao giờ có xác suất bằng 0. Sửa ngữ liệu hoặc sửa câu thử là mọi ô trong bảng tính lại.

Hai điều phải nói trước khi bạn tin bất kỳ con số nào ở đây. Thứ nhất, add-1 là một quy ước chứ không phải chân lý. Nó là cách làm mượt thô sơ nhất, tôi chọn vì bạn tính tay lại được từng ô, và đổi sang backoff hay Kneser-Ney thì mọi con số dưới đây đổi theo, kể cả thứ tự lớn nhỏ giữa hai câu thử. Thứ hai, ngữ liệu mặc định chỉ có ba câu, quá nhỏ để giống bất kỳ mô hình ngôn ngữ thật nào. Nó đủ để bạn kiểm từng phép chia bằng tay, và chỉ nên dùng vào đúng việc đó. Các lựa chọn cài đặt còn lại nằm ở mục Những quy ước tôi đã chọn gần cuối bài.

Cross-entropy và perplexity · hai mặt của một con số
PPL 4.549
Câu thử có mặt nguyên vẹn trong ngữ liệu nên mọi cặp đều từng gặp, perplexity xuống thấp hơn hẳn cỡ từ vựng.
Cross-entropy2.1856bit trên tokenH = -(1/N) × Σ log2 P(w_i | w_i-1)
Perplexity4.549số lựa chọn tương đươngPPL = 2^H
Số token được chấm7cỡ từ vựng |V| = 11N = 6 từ + 1 dấu kết câu
Đổi cơ sốH2 = 2.1856 bitHe = 1.5149 natnhưng2^H2 = 4.549074 = e^He = 4.549074lệch 0
#Ngữ cảnh w-1Token wc(w-1, w)c(w-1)P(w | w-1)log2 PGóp vào H
1<s>con334/14 = 0.2857-1.80740.2582
2conmèo233/14 = 0.2143-2.22240.3175
3mèonằm122/13 = 0.1538-2.70040.3858
4nằmtrên223/13 = 0.2308-2.11550.3022
5trêntấm233/14 = 0.2143-2.22240.3175
6tấmthảm223/13 = 0.2308-2.11550.3022
7thảm</s>223/13 = 0.2308-2.11550.3022
Tổng-15.29902.1856
Kiểm chứng: perplexity chính là số lựa chọn tương đương

Cho mô hình bó tay hoàn toàn, tức mọi token đều nhận đúng xác suất 1/|V|, rồi đẩy qua đúng hàm tính đang chạy ở trên với N = 7 token. Chọn một cỡ từ vựng:

H = log2(11)3.459432 bit
PPL = 2^H11.000000
Khớp |V| = 11. Perplexity bằng P nghĩa là mô hình đang lúng túng y như phải bốc đều giữa P phương án, và với câu thử ở trên con số đó là 4.549 trong khi từ vựng có 11 từ.
Ngữ liệu: 3 câu, 20 cặp bigram, 9 từ khác nhau. Từ vựng dự đoán được gồm 9 từ đó cộng </s><unk>, thành |V| = 11. Nhãn <s> chỉ làm ngữ cảnh, không bao giờ bị chấm.

Đọc bảng theo từng token

Ở trạng thái mặc định, ngữ liệu có 3 câu và 9 từ khác nhau. Cộng thêm dấu kết câu </s> và ô chứa từ lạ <unk>, từ vựng dự đoán được có |V| = 11 phần tử. Câu thử con mèo nằm trên tấm thảm cho N = 7 vị trí được chấm, tính cả dấu kết câu.

Mỗi dòng bảng là một phép chia rất đơn giản. Dòng thứ ba chẳng hạn: cặp mèo nằm xuất hiện 1 lần, từ mèo đứng đầu 2 cặp, nên P(nằm | mèo) = (1+1)/(2+11) = 2/13, tức khoảng 0,1538. Cộng bảy giá trị log2 lại rồi chia cho 7 và đổi dấu, ta được H = 2,1856 bit trên token, và PPL = 2^2,1856 = 4,549.

Cột cuối là phần đóng góp của từng token vào H. Token nào xác suất thấp thì đóng góp lớn, và đó chính là chỗ kéo perplexity lên. Sim tô đỏ những dòng có xác suất thấp hơn 1/|V|, nghĩa là ở vị trí đó mô hình còn đoán tệ hơn cả bốc ngẫu nhiên đều trên toàn bộ từ vựng.

Perplexity đúng nghĩa là số phương án

Câu nói quen thuộc "perplexity bằng P nghĩa là mô hình lúng túng như thể phải chọn đều giữa P phương án" không phải một phép ví von cho dễ nhớ. Nó là một đẳng thức, và bạn kiểm được ngay:

  • Bấm Ngữ liệu rỗng. Không có câu nào để đếm thì mọi ngữ cảnh đều lạ, add-1 trả về đúng 1/|V| ở mọi dòng. Từ vựng lúc này chỉ còn </s><unk> nên |V| = 2, và perplexity ra đúng 2.
  • Khối Kiểm chứng ở cuối sim làm điều đó ở quy mô lớn hơn: nó nạp một dãy xác suất 1/|V| vào đúng hàm tính tổng mà bảng phía trên đang dùng, rồi cho bạn đổi |V|. Chọn |V| = 1000 thì H = 9,965784 bit và PPL = 1000. Chọn 50000 cũng vậy.

Vậy con số 4,549 ở mặc định đọc là: mô hình đã thu hẹp 11 lựa chọn xuống còn khoảng 4,5 lựa chọn tương đương. Càng nhỏ càng tốt, và ngưỡng để so là |V|.

Đổi cơ số thì cross-entropy đổi, perplexity thì không

Chuyển công tắc Cơ số log sang ln. Cross-entropy tụt từ 2,1856 xuống 1,5149, vì bit đổi thành nat. Nhưng perplexity vẫn đứng nguyên ở 4,549. Dải chữ ngay dưới ba con số lớn hiện cả hai cùng lúc để bạn khỏi phải bấm qua bấm lại.

Lý do là một dòng đại số: H_e = H_2 × ln2, nên e^(H_e) = e^(H_2 × ln2) = 2^(H_2). Cross-entropy là một con số có đơn vị và đơn vị đó do bạn chọn. Perplexity là cái nằm dưới, không có đơn vị, không phụ thuộc lựa chọn ấy. Nếu đọc được một bài báo ghi cross-entropy mà không ghi cơ số thì bạn chưa biết họ nói gì; ghi perplexity thì biết ngay.

Ba cái bẫy

Với cách làm mượt này, không hiểu gì lại nhẹ tội hơn hiểu sai. Bấm Câu lạ hoàn toàn: cả 7 từ đều ngoài từ vựng, tất cả rơi về <unk>. Dòng đầu vẫn có ngữ cảnh quen là <s> nên chỉ được 1/14, còn 7 dòng sau lấy <unk> làm ngữ cảnh mà <unk> chưa từng đứng đầu cặp nào, nên nhận đúng 1/11. Perplexity ra 11,337, tức xấp xỉ |V|, đúng như mô tả "bó tay, đoán đều". Bây giờ bấm Đảo trật tự từ: lần này là sáu từ của câu mặc định xếp ngược lại chứ không phải từ lạ, vậy mà perplexity lên 13,420, cao hơn cả câu không có lấy một chữ quen. Lý do nhìn thấy trong bảng: các ngữ cảnh đều quen nên mẫu số phình lên thành 13 hay 14, trong khi tử số chỉ còn 1 vì cặp đó chưa từng gặp, nên mọi dòng đều tụt xuống dưới 1/11.

Hai preset đó dài ngắn khác nhau nên đừng vội kết luận từ riêng cặp số ấy. Tôi đã chạy lại với một câu lạ dài đúng sáu từ để N bằng nhau: perplexity ra 11,386, vẫn thấp hơn 13,420. Dán ba dòng ngữ liệu thêm một lần nữa cho số đếm dày gấp đôi thì câu đảo tệ thêm, lên 15,827, còn câu lạ gần như đứng yên ở 11,706. Cơ chế đúng là như bảng nói: ngữ cảnh chưa từng gặp luôn được add-1 phát cho đúng 1/|V| bất kể ngữ liệu to tới đâu, còn ngữ cảnh đã quen thì mẫu số lớn hơn |V| nên cặp lạ nào rơi vào cũng nằm dưới cái sàn ấy, và càng quen càng nằm sâu.

Nhưng đây là tính nết của add-1 chứ không phải quy luật của mọi mô hình ngôn ngữ, nên đừng mang câu "tự tin sai bị phạt nặng hơn thú nhận không biết" đi chỗ khác. Cái sàn 1/|V| dành cho ngữ cảnh lạ là một ưu ái riêng của add-1. Với backoff hay Kneser-Ney, ngữ cảnh lạ bị lùi về phân phối unigram chứ không về phân phối đều, mà <unk> trong phân phối unigram thì xác suất rất nhỏ, nên câu toàn từ lạ hoàn toàn có thể tụt xuống thua cả câu đảo trật tự. Điều sim này chứng minh được là "add-1 phạt cặp lạ sau ngữ cảnh quen nặng hơn phạt ngữ cảnh lạ", chỉ vậy thôi.

Perplexity dính chặt vào bộ tách token. Ở đây tôi tách theo khoảng trắng và bỏ dấu câu, nên mỗi âm tiết tiếng Việt là một token. Tách theo từ ghép, theo subword hay theo byte đều cho N khác và |V| khác, nên H khác và PPL khác, dù mô hình y hệt. Đổi bộ tách rồi khoe perplexity giảm là so hai thứ không so được.

Perplexity dính chặt vào tập kiểm. Bấm Ngữ liệu dày hơn: vẫn câu thử cũ nhưng ngữ liệu lặp lại nhiều hơn, perplexity xuống 3,790. Không có gì thần kỳ, chỉ là số đếm lớn lên thì add-1 bớt pha loãng. Ngược lại, nếu tập kiểm chứa toàn câu lạ thì con số vọt lên. Hai perplexity chỉ đặt cạnh nhau được khi cùng bộ tách và cùng tập kiểm.

Những quy ước tôi đã chọn

Sim này phải chốt vài thứ mà sách vở làm khác nhau, và mỗi lựa chọn đều làm đổi con số:

  • <s> chỉ làm ngữ cảnh, không bao giờ bị chấm, vì không có gì đứng trước nó để dự đoán nó. </s> thì bị chấm, vì biết chỗ nào nên dừng câu cũng là một phần việc của mô hình. Tắt công tắc Chấm cả dấu kết câu thì N giảm 1 và perplexity đổi theo, nhưng đổi theo hướng nào là tuỳ câu chứ không cố định: ở trạng thái mặc định nó nhích lên 4,586, còn với câu thử con chó nằm trên sân nó tụt từ 5,469 xuống 5,368. Chấm </s> không phải là gỡ bỏ một khoản giảm giá, nó chỉ thêm một vị trí nữa vào trung bình, và vị trí ấy có thể dễ hoặc khó hơn phần còn lại của câu.
  • Từ lạ quy về <unk>, và <unk> là thành viên thật của từ vựng, nhờ vậy tổng xác suất trên |V| vẫn bằng 1.
  • Làm mượt add-1 là cách thô sơ nhất. Tôi dùng nó vì bạn tính tay lại được từng ô, chứ không phải vì nó tốt. Với ngữ liệu thật, cộng 1 vào mọi ô sẽ hút quá nhiều xác suất về phía các cặp chưa gặp, và người ta chuyển sang Kneser-Ney hoặc backoff.
  • Câu thử rỗng, mà lại tắt luôn chấm </s>, thì không còn vị trí nào để chấm. Sim quy ước N = 0, H = 0, PPL = 1 và nói thẳng ra bằng chữ, thay vì để NaN lọt lên màn hình. Đó là định nghĩa cho gọn, không phải mô hình đang hoàn hảo.

Nhắc lại điểm yếu đã báo ở đầu bài, lần này kèm con số cụ thể. Với ba câu và |V| = 11, cộng 1 vào mọi ô là một lượng xác suất khổng lồ so với số đếm thật: một cặp đã gặp 2 lần nhận 3/13, một cặp chưa gặp bao giờ nhận 1/13, chênh nhau đúng 3 lần. Mô hình thật đếm trên hàng tỉ cặp, khoảng cách đó rộng hơn nhiều bậc. Vậy nên mọi thứ tự lớn nhỏ bạn thấy trong sim này chỉ chắc chắn trong sim này, và cách duy nhất để biết nó có còn đúng ở chỗ khác không là chạy lại ở chỗ khác.

Điều rút ra

Cross-entropy và perplexity là cùng một phép đo, khác nhau ở cách trình bày: H là số bit trung bình phải trả cho mỗi token, PPL = 2^H là số phương án tương đương mà mô hình còn đang phân vân. Đổi cơ số log thì H đổi số, PPL đứng yên. Ngưỡng để đọc PPL|V|: bằng |V| là chưa học được gì, nhỏ hơn nhiều mới đáng kể. Và hai con số perplexity chỉ so được với nhau khi cùng bộ tách token và cùng tập kiểm.

Câu hỏi tự kiểm0/3 đúngchưa trả lời
  1. 1Bạn tính cross-entropy của cùng một câu hai lần, một lần bằng log2 và một lần bằng ln. Kết quả thế nào?
  2. 2Một mô hình gán xác suất đều 1/|V| cho mọi vị trí, với |V| = 1000. Perplexity bằng bao nhiêu?
  3. 3Nhóm A báo perplexity 42, nhóm B báo 35 trên cùng bộ dữ liệu nhưng nhóm B tách token theo subword còn nhóm A tách theo từ. Kết luận nào đúng?