Skip to content

Sinh văn bản và tóm tắt văn bản

This content is not available in your language yet.

Sau bài này, bạn sẽ:

  • Đặt cạnh nhau các chiến lược giải mã (decoding strategy): tham lam, beam search, lấy mẫu (sampling), nhiệt độ (temperature), top-k, top-p và phạt lặp (repetition penalty), và chọn được cách hợp cho từng việc.
  • Phân biệt tóm tắt trích xuất (extractive summarization) với tóm tắt tóm lược (abstractive summarization), và biết BART, T5, PEGASUS học tóm tắt bằng cách nào.
  • Tự tính được ROUGE-1, ROUGE-2, ROUGE-L, và biết ROUGE đo được gì, không đo được gì.
  • Nhận ra hallucination (bịa thông tin) trong bản tóm tắt, phân biệt hallucination nội sinh và ngoại sinh, và biết vài cách giảm.

Sáng nay bạn đọc một bản tin bảy câu: Đà Nẵng mở tuyến xe buýt điện đầu tiên, tuyến dài bao nhiêu, giá vé, tháng đầu miễn phí, mỗi xe chạy được bao xa sau một lần sạc, người dân khen gì, năm sau mở thêm mấy tuyến. Sếp nhờ bạn rút gọn còn hai câu. Bạn có hai cách.

Cách thứ nhất: cầm bút dạ quang. Tô hai câu quan trọng nhất rồi chép nguyên văn. Không bao giờ viết sai điều bài báo nói, vì chữ nào cũng là chữ của bài báo. Nhưng hai câu chép rời có thể cụt: câu “Tuyến dài 18 km” đứng một mình thì người đọc không biết “tuyến” nào.

Cách thứ hai: tự viết lại. “Đà Nẵng mở tuyến xe buýt điện đầu tiên nối sân bay với biển Mỹ Khê. Tháng đầu miễn phí vé, năm sau mở thêm ba tuyến.” Gọn và trôi chảy hơn. Nhưng giờ bạn viết, nên bạn có thể viết sai: nhầm “đầu tiên” thành “thứ hai”, hay thêm một chi tiết bài báo không hề nói.

Máy làm cách thứ hai bằng cách sinh văn bản từng token một. Ở mỗi bước, mô hình cho một phân phối xác suất trên mọi token, và ta phải chọn một token. Chọn token chắc nhất, hay rút thăm cho đa dạng? Với tóm tắt, ta muốn đúng và ổn định. Với viết truyện, ta lại muốn bất ngờ.

Còn một câu hỏi nữa: làm sao biết bản tóm tắt nào tốt hơn, khi có cả nghìn bản cần chấm và không đủ người đọc?

Bài toán của bài này là: chọn token thế nào khi sinh văn bản; chọn câu hay viết lại khi tóm tắt; và chấm một bản tóm tắt bằng máy mà không bị đánh lừa.

Như ở bài GPT và huấn luyện phân tán, một mô hình ngôn ngữ nhân quả học p(xt∣x<t)p(x_t \mid x_{<t}): xác suất của token kế tiếp khi biết mọi token trước nó. Mô hình encoder–decoder như T5 học p(yi∣y<i,x)p(y_i \mid y_{<i}, \mathbf{x}), thêm điều kiện là văn bản nguồn x\mathbf{x} (bài Mô hình chuỗi sang chuỗi). Cả hai sinh văn bản theo cùng một vòng lặp tự hồi quy (autoregressive):

  1. Đưa các token đã có vào mô hình, lấy logit ziz_i cho mỗi token ii trong từ vựng.
  2. Biến logit thành một phân phối, rồi chọn một token theo một quy tắc.
  3. Nối token vừa chọn vào cuối, quay lại bước 1. Dừng khi gặp <EOS> hoặc đủ số token tối đa.

Mô hình chỉ làm bước 1. Chiến lược giải mã là quy tắc ở bước 2. Cùng một mô hình, đổi quy tắc là đổi hẳn văn bản ra.

Các chiến lược giải mã đặt cạnh nhau

Phần tiêu đề “Các chiến lược giải mã đặt cạnh nhau”

Bảng dưới gom các chiến lược hay gặp. Công thức của nhiệt độ, top-k và top-p, kèm ví dụ tính tay, có ở bài Sử dụng API văn bản. Beam search và phạt độ dài có ở bài Mô hình chuỗi sang chuỗi. Ở đây ta chỉ so chúng với nhau.

Chiến lượcChọn token thế nàoTham số (tên trong generate)MạnhYếu
Tham lam (greedy)Token xác suất cao nhấtnum_beams=1, do_sample=FalseNhanh, tất địnhKhông xem lại lựa chọn sớm; dễ lặp khi viết dài
Beam searchGiữ BB chuỗi có tổng log-xác suất cao nhấtnum_beams, length_penaltyTìm được chuỗi xác suất cao hơn tham lamVăn nhạt, lặp với sinh mở; tốn BB lần tính
Lấy mẫu thuầnRút token theo đúng phân phối của mô hìnhdo_sample=True, top_k=0Đa dạng nhấtThỉnh thoảng rút trúng đuôi vô nghĩa
Nhiệt độ TTChia logit cho TT trước softmax, rồi rúttemperatureMột núm vặn giữa chắc chắn và đa dạngKhông tự cắt đuôi
Top-kChỉ rút trong kk token đầutop_kChặn đuôikk cố định, không theo độ chắc của mô hình
Top-p (nucleus)Chỉ rút trong tập nhỏ nhất có tổng xác suất ≥p\ge ptop_pSố token giữ lại co giãn theo độ chắcVẫn ngẫu nhiên, khó tái lập

Top-k được Fan, Lewis và Dauphin (2018) dùng để sinh truyện: mỗi bước rút trong k=10k = 10 token đầu. Họ thấy cách này tốt hơn hẳn beam search, vốn hay cho ra những cụm từ phổ biến và lặp lại. Top-p là đề xuất của Holtzman và cộng sự (2020), lý do nằm ở mục ngay sau.

Mô hình tốt cho văn bản tốt xác suất cao, vậy chọn văn bản xác suất cao nhất phải tốt nhất? Thực tế thì không. Holtzman và cộng sự (2020) cho GPT-2 Large viết tiếp 5 000 đoạn văn (tối đa 200 token mỗi đoạn) rồi đo tỉ lệ đoạn bị “kẹt” trong một vòng lặp (một cụm từ lặp ít nhất ba lần ở cuối):

Cách giải mãTỉ lệ bị lặp (%)
Văn bản của người0.28
Tham lam73.66
Beam search, B=16B = 1628.94
Lấy mẫu thuần0.22
Top-p, p=0.95p = 0.950.36

Vì sao tham lam lặp nhiều như vậy? Họ thấy xác suất của một cụm từ lặp lại tăng lên sau mỗi lần lặp. Đã lặp một lần thì lần sau còn “chắc” hơn, thành một vòng phản hồi dương. Văn bản của người thì khác: nó thường xuyên rẽ sang những token xác suất thấp hơn nhưng mang nhiều thông tin hơn.

Lấy mẫu thuần thì gần như không lặp, nhưng lại hỏng theo kiểu khác: văn bản rời rạc, có khi bịa ra từ không có thật. Thủ phạm là cái đuôi không đáng tin của phân phối: hàng chục nghìn token, mỗi token xác suất nhỏ, nhưng cộng lại thì không nhỏ. Top-p cắt cái đuôi đó đi. Nó không phải cách ít lặp nhất trong bảng (lấy mẫu thuần còn ít hơn), nhưng xét chung perplexity và HUSE (một thước đo kết hợp người chấm và thống kê), top-p cho văn bản gần văn bản của người nhất.

Holtzman và cộng sự gọi chung hai kiểu hỏng này (văn nhạt, lặp vòng khi chọn theo xác suất cao nhất; văn rời rạc khi rút từ cả đuôi) là văn bản thoái hóa (neural text degeneration).

Một cách chặn lặp trực tiếp là hạ điểm của những token đã xuất hiện. Keskar và cộng sự (2019), trong bài báo về mô hình CTRL, đề xuất lấy mẫu có phạt (penalized sampling). Gọi gg là danh sách token đã sinh. Phân phối của token kế tiếp là:

pi=exp⁡(zi/(T⋅I(i∈g)))∑jexp⁡(zj/(T⋅I(j∈g))),I(c)={θneˆˊu c đuˊng1neˆˊu khoˆngp_i = \frac{\exp\big(z_i / (T \cdot I(i \in g))\big)}{\sum_j \exp\big(z_j / (T \cdot I(j \in g))\big)}, \qquad I(c) = \begin{cases} \theta & \text{nếu } c \text{ đúng} \\ 1 & \text{nếu không} \end{cases}

Đọc từng phần:

  • Token chưa xuất hiện: I=1I = 1, logit chỉ chia cho TT như softmax có nhiệt độ thường.
  • Token đã xuất hiện: logit bị chia thêm cho θ\theta. Với θ>1\theta > 1 và logit dương, logit nhỏ đi, nên xác suất giảm.
  • θ=1\theta = 1 là không phạt. Các tác giả thấy giải mã gần như tham lam với θ≈1.2\theta \approx 1.2 cân bằng tốt giữa viết đúng và không lặp.

Một cái bẫy với logit âm. Chia một số âm cho θ>1\theta > 1 làm nó gần 0 hơn, tức lớn hơn: −1.0/1.2≈−0.83-1.0 / 1.2 \approx -0.83. Token đã lặp lại được ưu tiên hơn, ngược với ý định. Vì vậy repetition_penalty của Hugging Face Transformers chia logit dương cho θ\theta nhưng nhân logit âm với θ\theta. Với θ=1.2\theta = 1.2: logit 2.02.0 thành 2.0/1.2≈1.672.0 / 1.2 \approx 1.67, logit −1.0-1.0 thành −1.0×1.2=−1.2-1.0 \times 1.2 = -1.2. Cả hai đều giảm.

Một cách cứng hơn là no_repeat_ngram_size=n: cấm hẳn mọi nn-gram đã xuất hiện. Cả hai cách đều phạt luôn những lần lặp đúng. Bản tóm tắt về xe buýt điện có thể cần nhắc “xe buýt điện” hai lần, mà no_repeat_ngram_size=3 thì không cho.

Holtzman và cộng sự chia việc sinh văn bản làm hai loại:

  • Sinh có định hướng (directed generation): đầu ra bị đầu vào ràng buộc chặt, như dịch máy, tóm tắt, sinh văn bản từ bảng dữ liệu. Beam search (hoặc tham lam) là lựa chọn thường dùng. Ở đây lặp và nhạt ít là vấn đề, vì đầu vào đã quyết gần hết nội dung, còn sự ổn định và trung thành với nguồn thì quan trọng.
  • Sinh mở (open-ended generation): viết tiếp truyện, hội thoại, gợi ý ý tưởng. Lấy mẫu có cắt đuôi (top-p, hay top-k kèm nhiệt độ) cho văn bản đa dạng và tự nhiên hơn.

Đây là điểm xuất phát, không phải luật. File cấu hình của T5-small có ghi sẵn một cấu hình cho tóm tắt trong config.task_specific_params: beam B=4B = 4 cùng no_repeat_ngram_size=3, tức beam search kèm cấm lặp. generate không tự áp cấu hình này: gọi generate mà không truyền gì thì vẫn là tham lam, như dòng greedy ở ô T5 bên dưới.

Tóm tắt trích xuất chấm điểm từng câu của văn bản rồi giữ vài câu điểm cao nhất, theo thứ tự xuất hiện để mạch văn không đảo lộn. Có ba cách chấm từ đơn giản tới phức tạp.

Lấy vài câu đầu (lead). Tin tức thường viết theo kiểu “kim tự tháp ngược”: ý chính đặt ở đầu. Vì vậy chỉ lấy kk câu đầu đã là một mốc so sánh khó vượt với tin tức. Nhưng nó vô dụng với loại văn bản không viết như vậy, như bài khoa học, email hay biên bản họp.

So với vectơ của cả bài, bằng TF-IDF. Coi mỗi câu là một “văn bản” trong một kho nhỏ NN câu. Mỗi câu jj thành một vectơ sj\mathbf{s}_j có trọng số TF-IDF như ở bài Ngữ nghĩa vectơ và embedding: số lần từ tt xuất hiện trong câu nhân với idft=ln⁡1+N1+dft+1\mathrm{idf}_t = \ln\frac{1 + N}{1 + \mathrm{df}_t} + 1, với dft\mathrm{df}_t là số câu chứa tt. Cộng mọi vectơ câu được một vectơ đại diện cho cả bài:

c=∑j=1Nsj,scorej=cos⁡(sj,c)=sj⊤c∥sj∥ ∥c∥\mathbf{c} = \sum_{j=1}^{N} \mathbf{s}_j, \qquad \mathrm{score}_j = \cos(\mathbf{s}_j, \mathbf{c}) = \frac{\mathbf{s}_j^\top \mathbf{c}}{\lVert \mathbf{s}_j \rVert \, \lVert \mathbf{c} \rVert}

Câu có điểm cao là câu “giống cả bài nhất”: nó chứa những từ vừa được nhắc nhiều trong bài, vừa không có mặt ở mọi câu. Cosine chia cho độ dài, nên câu dài không tự động thắng.

TextRank. Mihalcea và Tarau (2004) biến văn bản thành một đồ thị: mỗi câu là một đỉnh, hai câu nối nhau bằng một cạnh có trọng số bằng số từ chung, chia cho log⁡∣Si∣+log⁡∣Sj∣\log |S_i| + \log |S_j| (với ∣Si∣|S_i| là số từ của câu ii) để câu dài không được lợi. Sau đó họ chạy một thuật toán xếp hạng kiểu PageRank (hệ số tắt dần d=0.85d = 0.85). Ý tưởng là bỏ phiếu: một câu quan trọng khi nó giống nhiều câu khác, nhất là giống những câu cũng quan trọng. Trên 567 bản tin của DUC 2002, mỗi bản tóm tắt 100 từ, TextRank không cần dữ liệu có nhãn mà vẫn cạnh tranh được với các hệ thống tốt nhất tham gia đánh giá năm đó.

Tóm tắt trích xuất không bịa câu mới, vì mọi câu đều lấy từ nguồn, nhưng một câu tách khỏi ngữ cảnh vẫn có thể gây hiểu sai. Đổi lại, nó không gộp được ý của hai câu, không bỏ được phần thừa trong một câu dài, và có thể để lại những câu cụt như “Tuyến dài 18 km”.

Tóm tắt tóm lược dùng một mô hình chuỗi sang chuỗi: encoder đọc văn bản, decoder sinh bản tóm tắt từng token, với một trong các chiến lược giải mã ở trên. Phần khó là dạy mô hình viết lại mà không cần hàng triệu cặp (văn bản, tóm tắt). Cách chung là pre-train bằng một mục tiêu khử nhiễu (denoising), rồi fine-tune trên dữ liệu tóm tắt.

  • BART (Lewis và cộng sự, 2020) và T5 (Raffel và cộng sự, 2020) là hai mô hình encoder–decoder pre-train bằng khử nhiễu. Cách chúng làm hỏng văn bản có ở bài Transformer nâng cao. T5 nói tác vụ bằng tiền tố summarize: . Bản T5 công bố được pre-train trên hỗn hợp nhiều tác vụ, trong đó có tóm tắt tin CNN/Daily Mail, nên gõ tiền tố đó là T5-small tóm tắt được ngay.
  • PEGASUS (Zhang và cộng sự, 2020) thiết kế mục tiêu pre-train riêng cho tóm tắt, gọi là sinh câu bị bỏ (gap sentences generation). Họ bỏ khỏi văn bản những câu quan trọng, thay mỗi câu bằng một token [MASK1], rồi bắt decoder viết lại các câu đó từ phần còn lại. Câu quan trọng là câu có ROUGE-1 F1 cao nhất khi so với phần còn lại của văn bản; trong các thí nghiệm so sánh, họ bỏ 30% số câu. Như vậy việc pre-train đã rất giống tóm tắt. Bản lớn nhất đạt kết quả tốt nhất thời đó trên cả 12 bộ dữ liệu tóm tắt họ thử, và chỉ với 1000 cặp fine-tune đã vượt kết quả tốt nhất trước đó trên 6 bộ.
  • LLM qua prompt. Một mô hình decoder-only đã được tinh chỉnh theo chỉ dẫn tóm tắt được khi bạn viết “Tóm tắt văn bản sau trong hai câu” (bài Prompt Engineering). Không cần fine-tune, độ dài và giọng văn đổi được bằng lời, nhưng chi phí và nguy cơ hallucination vẫn còn đó.

Với tiếng Việt, ViT5 (Phan và cộng sự, 2022) là một T5 pre-train trên văn bản tiếng Việt. Các tác giả báo cáo kết quả tốt nhất thời đó cho tóm tắt tóm lược trên hai bộ Vietnews và WikiLingua.

Tiêu chíTrích xuấtTóm lược
Đầu raCâu nguyên văn của nguồnCâu mới, có thể dùng từ không có trong nguồn
Bịa thông tinKhông bịa câu mới, nhưng câu tách khỏi ngữ cảnh có thể gây hiểu saiCó thể
Văn phongCó thể rời rạc, câu cụtTrôi chảy, gộp được ý
Cần gìKhông cần huấn luyện (lead, TF-IDF, TextRank)Mô hình encoder–decoder đã pre-train, hoặc LLM

Lin (2004) đưa ra ROUGE (Recall-Oriented Understudy for Gisting Evaluation): so bản tóm tắt của máy với một hay nhiều bản tóm tắt tham chiếu do người viết, bằng cách đếm các đơn vị trùng nhau. Gọi bản của máy là y^\hat{\mathbf{y}}, bản tham chiếu là y\mathbf{y}, cả hai là dãy từ.

ROUGE-N đếm các nn-gram (cụm nn từ liền nhau) trùng nhau. Với mỗi nn-gram gg, gọi cnty^(g)\mathrm{cnt}_{\hat{\mathbf{y}}}(g) và cnty(g)\mathrm{cnt}_{\mathbf{y}}(g) là số lần nó xuất hiện ở mỗi bên. Số lần trùng là:

hitn=∑gmin⁡(cnty^(g), cnty(g))\mathrm{hit}_n = \sum_{g} \min\big(\mathrm{cnt}_{\hat{\mathbf{y}}}(g),\ \mathrm{cnt}_{\mathbf{y}}(g)\big)

Lấy min⁡\min để một nn-gram lặp lại nhiều lần trong bản máy không được tính quá số lần nó có trong tham chiếu. Từ đó có ba con số:

Pn=hitnsoˆˊ n-gram của y^,Rn=hitnsoˆˊ n-gram của y,Fn=2PnRnPn+RnP_n = \frac{\mathrm{hit}_n}{\text{số } n\text{-gram của } \hat{\mathbf{y}}}, \qquad R_n = \frac{\mathrm{hit}_n}{\text{số } n\text{-gram của } \mathbf{y}}, \qquad F_n = \frac{2 P_n R_n}{P_n + R_n}
  • Precision PnP_n: trong những gì máy viết, bao nhiêu phần có trong tham chiếu. Viết dài lan man thì PnP_n thấp.
  • Recall RnR_n: trong tham chiếu, bao nhiêu phần máy viết lại được. Viết quá ngắn thì RnR_n thấp.
  • F1 FnF_n là trung bình điều hòa của hai số: chỉ cao khi cả hai cùng cao.

ROUGE gốc của Lin là recall (đúng như chữ R trong tên); các bài báo ngày nay thường báo cáo F1. Khi đọc một con số ROUGE, hãy xem nó là loại nào.

ROUGE-L dùng dãy con chung dài nhất (longest common subsequence, LCS): dãy từ xuất hiện ở cả hai bên theo cùng thứ tự, nhưng không cần liền nhau. Gọi độ dài của nó là LCS(y^,y)\mathrm{LCS}(\hat{\mathbf{y}}, \mathbf{y}), thì P=LCS/∣y^∣P = \mathrm{LCS} / |\hat{\mathbf{y}}| và R=LCS/∣y∣R = \mathrm{LCS} / |\mathbf{y}|, rồi tính F1 như trên. ROUGE-L thưởng cho việc giữ đúng trật tự mà không đòi các từ phải đứng sát nhau. (Lin dùng một dạng F có trọng số β\beta; ở DUC, β\beta được đặt rất lớn, nên thực chất chỉ còn recall. Khi bản tóm tắt có nhiều câu, ông dùng “LCS hợp” trên từng câu tham chiếu; ô code bên dưới coi cả bản tóm tắt là một dãy cho gọn.)

Ví dụ tính tay. Tham chiếu “trời hôm nay rất đẹp” (5 từ), bản máy “hôm nay trời đẹp” (4 từ). Mỗi âm tiết là một từ.

  • ROUGE-1. Trùng 4 từ: trời, hôm, nay, đẹp. P1=4/4=1P_1 = 4/4 = 1, R1=4/5=0.8R_1 = 4/5 = 0.8, F1=2⋅1⋅0.8/1.8≈0.889F_1 = 2 \cdot 1 \cdot 0.8 / 1.8 \approx 0.889.
  • ROUGE-2. Tham chiếu có 4 bigram (trời hôm, hôm nay, nay rất, rất đẹp), bản máy có 3 (hôm nay, nay trời, trời đẹp). Chỉ “hôm nay” trùng. P2=1/3P_2 = 1/3, R2=1/4R_2 = 1/4, F2=2⋅112/712=2/7≈0.286F_2 = 2 \cdot \frac{1}{12} / \frac{7}{12} = 2/7 \approx 0.286.
  • ROUGE-L. Dãy con chung dài nhất là “hôm nay đẹp”, dài 3 (“trời” đứng ở hai vị trí khác nhau nên không ghép thêm được). P=3/4P = 3/4, R=3/5R = 3/5, F1 =2⋅0.45/1.35≈0.667= 2 \cdot 0.45 / 1.35 \approx 0.667.

Đổi trật tự từ làm ROUGE-2 và ROUGE-L tụt, còn ROUGE-1 gần như không thấy.

  • Ý nghĩa bị đảo. Tham chiếu “mèo đuổi chuột”, bản máy “chuột đuổi mèo”: ROUGE-1 bằng 1, dù nghĩa ngược hẳn. ROUGE-2 bằng 0, ROUGE-L bằng 1/31/3.
  • Từ đồng nghĩa. “khai trương” và “mở” cùng nghĩa nhưng không trùng chữ nào, nên không được điểm.
  • Đúng hay sai. Đổi “đầu tiên” thành “thứ hai” chỉ làm mất vài từ trùng; bản tóm tắt sai sự thật vẫn có thể có ROUGE cao hơn một bản viết lại đúng. Bảng ở mục Minh họa cho thấy rõ điều này.
  • Cách tách từ. Với tiếng Việt, tách theo âm tiết (“xe”, “buýt”) hay theo từ (“xe_buýt”) cho số khác nhau (bài Tách từ). Chỉ so ROUGE giữa các hệ thống chấm cùng một cách.

ROUGE hữu ích để so nhanh nhiều hệ thống trên cùng một bộ dữ liệu. Lin cho thấy nó tương quan tốt với điểm của người trên dữ liệu DUC. Nó không thay được người đọc khi cần biết bản tóm tắt có đúng không.

Một bản tóm tắt trung thực (faithful) với nguồn khi mọi điều nó nói đều được nguồn ủng hộ. Phần không được nguồn ủng hộ gọi là hallucination (bịa thông tin). Maynez và cộng sự (2020) chia làm hai loại:

  • Hallucination nội sinh (intrinsic): dùng thông tin có trong nguồn nhưng bóp méo nó, ví dụ ghép sai tên với chức vụ, hay “đầu tiên” thành “thứ hai”.
  • Hallucination ngoại sinh (extrinsic): thêm thông tin không có trong nguồn, ví dụ một năm, một cái tên mà bài báo không hề nhắc.

Họ cho người đọc chấm bản tóm tắt một câu của 500 bản tin trong bộ XSum. Hơn 70% bản tóm tắt của mọi hệ thống có hallucination, phần lớn là ngoại sinh, và hơn 90% chỗ hallucination ngoại sinh là sai chứ không phải kiến thức nền đúng. Đáng chú ý: chính các bản tham chiếu của XSum cũng có 76.9% chứa hallucination, gần hết là ngoại sinh (73.1%), vì đó là câu mở đầu do phóng viên viết, có thêm thông tin không nằm trong bài. Mô hình học từ những cặp như vậy thì học luôn thói “thêm thắt”. Hai kết quả khác: các mô hình đã pre-train vừa có ROUGE cao hơn vừa trung thực hơn, và một thước đo dựa trên suy diễn văn bản (textual entailment: nguồn có suy ra được bản tóm tắt không) khớp với đánh giá trung thực của người tốt hơn ROUGE.

Một số cách giảm hallucination hay dùng trong thực hành:

  • Dữ liệu sạch hơn: bỏ những cặp có bản tham chiếu chứa thông tin ngoài nguồn trước khi fine-tune.
  • Giải mã thận trọng: beam hay tham lam thay cho lấy mẫu nhiệt độ cao khi cần chính xác.
  • Kiểm tra sau khi sinh: dùng một mô hình suy diễn văn bản kiểm từng câu, hay so từng con số và tên riêng với nguồn.
  • Lai hai cách: trích các câu chính trước, rồi chỉ viết lại từ những câu đó; hay yêu cầu LLM trích dẫn câu nguồn cho mỗi ý.
  • Văn bản rất dài: mô hình encoder–decoder có giới hạn số token đầu vào; phần vượt quá phải cắt bớt hoặc tóm tắt từng đoạn rồi gộp lại.
  • Nhiều văn bản một lúc (multi-document summarization): bài này chỉ xét một văn bản.
  • Đánh giá: ROUGE không đo được tính trung thực hay độ dễ đọc; các thước đo dựa trên suy diễn văn bản hay chấm bằng LLM cũng có sai số. Hộp thứ hai ở mục Lỗi hay gặp nói thêm.

Hình dưới là phân phối của token kế tiếp sau “Hôm nay trời rất”, với 10 ứng viên. Bài Sử dụng API văn bản đã dùng hình này để dạy từng thanh trượt (TT, top-k, top-p). Ở đây, hãy nhìn hai đầu của nó như hai chiến lược giải mã trong bảng ở mục Lý thuyết:

  1. Kéo TT về 0 rồi bấm “Lấy 100 mẫu”: “đẹp” có xác suất 1, ra 100 lần “đẹp”. Đó là giải mã tham lam: chạy lại bao nhiêu lần cũng ra một câu.
  2. Đặt T=1T = 1, tắt top-k và top-p, rồi bấm “Lấy 100 mẫu” vài lần: đó là lấy mẫu thuần. “đẹp” chỉ có 0.38, và cả những token cuối bảng như “tối” thỉnh thoảng cũng được rút. Ở từ vựng thật, đuôi này dài hàng chục nghìn token; top-k và top-p nằm giữa hai đầu này, cắt bớt đuôi.
Drag the three sliders to change how the next token is chosen, then draw samples. The model’s logits stay the same.
Text so far

The weather today is very  

Probability of each next token
TokenProbabilityCount
nice0.38 removed0
hot0.26 removed0
cold0.14 removed0
cool0.09 removed0
sunny0.06 removed0
humid0.03 removed0
clear0.02 removed0
windy0.01 removed0
wet0.01 removed0
dark0.00 removed0

10 of 10 tokens can still be drawn.

Ten tokens that may follow "The weather today is very", from the most likely to the least likely. The logits are made up for the illustration, not computed by a real model.

Bảng thứ hai cho thấy ROUGE nhìn thấy gì và bỏ sót gì. Tham chiếu là “Đà Nẵng mở tuyến xe buýt điện đầu tiên”; mỗi hàng là một bản máy khác nhau, điểm là F1, mỗi âm tiết là một từ (tính bằng các hàm ROUGE của ô code đầu tiên). Hãy so hàng “viết lại cùng nghĩa” với hai hàng sai sự thật.

Bản máyĐúng nghĩa?ROUGE-1ROUGE-2ROUGE-L
Đà Nẵng mở tuyến xe buýt điện đầu tiênĐúng, chép y nguyên1.0001.0001.000
Tuyến xe buýt điện đầu tiên mở ở Đà NẵngĐúng, đảo trật tự0.9470.7060.632
Đà Nẵng khai trương tuyến buýt chạy điện đầu tiênĐúng, viết lại0.7370.3530.737
Đà Nẵng mở tuyến xe buýt điện thứ haiSai0.7780.7500.778
Đà Nẵng đóng tuyến xe buýt điện đầu tiênSai, nghĩa ngược0.8890.7500.889

Câu “đóng tuyến” nói ngược hẳn nguồn mà có ROUGE-1 0.889, cao hơn câu viết lại đúng (0.737). ROUGE đo mức trùng chữ, không đo mức đúng.

Code trong bài chạy thử lần cuối tháng 11/2026: ô tóm tắt trích xuất với Python 3.14 trên Pyodide (chỉ dùng thư viện chuẩn), ô T5 với Python 3.14, transformers 5.22 và PyTorch 2.14 trên CPU. Ô thứ nhất chạy ngay trên trang. Ô thứ hai cần PyTorch và tải mô hình từ Hugging Face Hub, nên chạy trên máy bạn.

Ô dưới làm lại hai phần của mục Lý thuyết trên bản tin xe buýt điện (bảy câu, tự viết cho bài này). Phần đầu chấm mỗi câu bằng cosine giữa vectơ TF-IDF của câu và vectơ của cả bài, rồi giữ hai câu điểm cao nhất theo thứ tự xuất hiện. Phần sau cài ROUGE-1, ROUGE-2, ROUGE-L (F1) từ đầu, rồi so hai bản tóm tắt với một bản tham chiếu tự viết: bản TF-IDF, và bản “lead-2” lấy hai câu đầu.

Tóm tắt trích xuất bằng TF-IDF và ROUGE

# Tóm tắt trích xuất bằng TF-IDF, rồi chấm bằng ROUGE-1, ROUGE-2, ROUGE-L
import math
import re
from collections import Counter
text = (
"Sáng 3/11, Đà Nẵng đưa vào hoạt động tuyến xe buýt điện đầu tiên, "
"nối sân bay với biển Mỹ Khê. Tuyến dài 18 km, có 12 xe, chạy từ 5 giờ "
"sáng đến 10 giờ tối. Giá vé là 8 nghìn đồng một lượt, học sinh được "
"giảm một nửa. Trong tháng đầu, thành phố miễn phí vé xe buýt điện cho "
"mọi hành khách. Mỗi xe chạy được 250 km sau một lần sạc. Nhiều người "
"dân khen xe chạy êm và không có khói. Thành phố dự kiến mở thêm ba "
"tuyến xe buýt điện vào năm sau."
)
ref = ("Đà Nẵng mở tuyến xe buýt điện đầu tiên nối sân bay với biển Mỹ Khê. "
"Tháng đầu miễn phí vé, năm sau mở thêm ba tuyến.")
def words(s): # chữ thường, mỗi âm tiết là một từ
return re.findall(r"\w+", s.lower())
sents = re.split(r"(?<=\.)\s+", text)
toks = [words(s) for s in sents]
N = len(sents)
df = Counter(w for t in toks for w in set(t)) # số câu chứa mỗi từ
idf = {w: math.log((1 + N) / (1 + df[w])) + 1 for w in df}
vecs = [{w: c * idf[w] for w, c in Counter(t).items()} for t in toks]
center = sum(map(Counter, vecs), Counter()) # vectơ của cả văn bản
def cos(a, b):
dot = sum(x * b.get(w, 0) for w, x in a.items())
return dot / (math.hypot(*a.values()) * math.hypot(*b.values()))
score = [cos(v, center) for v in vecs]
print("scores:", " ".join(f"{s:.3f}" for s in score))
top = sorted(sorted(range(N), key=lambda i: -score[i])[:2]) # giữ thứ tự
print("picked sentences:", [i + 1 for i in top])
def ngrams(t, n):
return Counter(tuple(t[i:i + n]) for i in range(len(t) - n + 1))
def f1(hit, n_cand, n_ref): # precision, recall rồi trung bình điều hòa
p, r = hit / n_cand, hit / n_ref
return 0.0 if hit == 0 else 2 * p * r / (p + r)
def rouge_n(c, r, n): # & lấy số lần trùng nhỏ hơn của hai bên
hit = sum((ngrams(c, n) & ngrams(r, n)).values())
return f1(hit, len(c) - n + 1, len(r) - n + 1)
def rouge_l(c, r): # độ dài LCS bằng quy hoạch động
L = [[0] * (len(r) + 1) for _ in range(len(c) + 1)]
for i, a in enumerate(c):
for j, b in enumerate(r):
L[i + 1][j + 1] = (L[i][j] + 1 if a == b
else max(L[i][j + 1], L[i + 1][j]))
return f1(L[-1][-1], len(c), len(r))
for name, idx in [("tf-idf", top), ("lead-2", [0, 1])]:
c, r = words(" ".join(sents[i] for i in idx)), words(ref)
print(f"{name}: R1 {rouge_n(c, r, 1):.3f} R2 {rouge_n(c, r, 2):.3f}"
f" RL {rouge_l(c, r):.3f} ({len(c)} words)")

The first run downloads Python, about 10 MB.

Đọc kết quả:

  • Điểm bảy câu là 0.520, 0.473, 0.405, 0.466, 0.431, 0.360, 0.484. Câu 1 (mở tuyến đầu tiên) và câu 7 (năm sau mở thêm ba tuyến) cao nhất: cả hai nhắc “tuyến xe buýt điện”, cụm từ của cả bài. Câu 6 (người dân khen xe êm) thấp nhất, vì phần lớn từ của nó không có ở câu khác.
  • Bản TF-IDF chọn câu 1 và 7, được ROUGE-1 0.667, ROUGE-2 0.557, ROUGE-L 0.603. Bản lead-2 chỉ được 0.492, 0.413, 0.492: câu 2 nói độ dài tuyến và giờ chạy, mà bản tham chiếu không nhắc.
  • Tính lại ROUGE-1 của bản TF-IDF: bản máy có 36 từ, tham chiếu 27 từ, trùng 21. P=21/36≈0.583P = 21/36 \approx 0.583, R=21/27≈0.778R = 21/27 \approx 0.778, F1 ≈0.667\approx 0.667. Precision thấp hơn recall vì hai câu chép nguyên văn còn kéo theo “Sáng 3/11”, “đưa vào hoạt động”, “dự kiến”.
  • Ý “tháng đầu miễn phí vé” trong tham chiếu nằm ở câu 4 (0.466), xếp thứ ba nên bị bỏ. Muốn có nó thì phải giữ ba câu, hoặc viết lại.

Thử đổi [:2] thành [:3] ở dòng chọn câu: câu 4 vào bản tóm tắt. Hay thử thay điểm cosine bằng trung bình idf của các từ trong câu, rồi xem câu nào được chọn.

File dưới tóm tắt một bản tin tiếng Anh tự viết (mưa lớn ở Huế) bằng google-t5/t5-small (Raffel và cộng sự, 2020), khoảng 60 triệu tham số. Code gọi thẳng AutoTokenizer và AutoModelForSeq2SeqLM.generate, để bạn thấy rõ các tham số giải mã. Cùng một mô hình, cùng một đầu vào, ba cấu hình: tham lam, beam B=4B = 4, và lấy mẫu top-p 0.9 (đặt top_k=0, vì khi lấy mẫu generate mặc định còn bật top-k 50). Trước mỗi lần sinh, set_seed(0) cố định bộ sinh số ngẫu nhiên.

Run on your machine

Tóm tắt bằng T5-small: tham lam, beam, top-p

Install
pip install transformers torch
Hardware
CPU là đủ. Lần chạy đầu tải khoảng 244 MB (trọng số mô hình 242 MB và tokenizer). Khi đã có mô hình trong máy, cả file chạy khoảng 10–20 giây trên CPU; máy bận có thể mất vài phút
# Tóm tắt một đoạn tin tiếng Anh bằng T5-small với ba cách giải mã.
from transformers import AutoModelForSeq2SeqLM, AutoTokenizer, set_seed
NAME = "google-t5/t5-small"
tok = AutoTokenizer.from_pretrained(NAME)
model = AutoModelForSeq2SeqLM.from_pretrained(NAME)
text = (
"Heavy rain hit the central city of Hue on Monday, flooding more than "
"2,000 homes and closing 40 schools. Local officials said the river "
"rose 1.5 metres overnight, the highest level since 2020. About 3,000 "
"people were moved to safer areas, and no deaths have been reported. "
"Forecasters expect the rain to continue until Wednesday, and the city "
"has asked residents to avoid low-lying roads."
)
# T5 biết cần làm việc gì nhờ tiền tố tác vụ ở đầu đầu vào
inputs = tok("summarize: " + text, return_tensors="pt")
print("input tokens:", inputs["input_ids"].shape[1])
settings = {
"greedy": dict(num_beams=1, do_sample=False),
"beam 4": dict(num_beams=4, do_sample=False),
# top_k=0 tắt top-k (generate mặc định dùng top_k=50 khi lấy mẫu)
"top-p 0.9": dict(do_sample=True, top_p=0.9, top_k=0),
}
for name, kwargs in settings.items():
set_seed(0) # cố định seed để lần lấy mẫu lặp lại được
out = model.generate(**inputs, max_new_tokens=60, **kwargs)
summary = tok.decode(out[0], skip_special_tokens=True)
print(f"[{name}] {summary}")

Sample output

From one real run. A model gives a different answer each time.

input tokens: 83
[greedy] heavy rain hits central city of Hue on Monday, flooding more than 2,000 homes and closing 40 schools. the river rose 1.5 metres overnight, the highest level since 2020.
[beam 4] heavy rain hit central city of Hue on Monday, flooding more than 2,000 homes and closing 40 schools. the river rose 1.5 metres overnight, the highest level since 2020. no deaths have been reported.
[top-p 0.9] heavy rain hit the central city of Hue on monday. about 3,000 people are moved to safer areas, and no deaths have been reported. the river rose 1.5 metres overnight, the highest level since 2020.

Đọc kết quả:

  • Cả ba bản gần như là trích xuất: phần lớn câu chép từ nguồn, chỉ bỏ bớt vài từ (“Local officials said”). Bài báo T5 có ghi rằng trên CNN/Daily Mail, dữ liệu tóm tắt mà T5 đã học, các cách thuần trích xuất cũng làm tốt. Có lẽ vì vậy mô hình học được rằng chép là an toàn. Chữ thường ở đầu câu (“the river”, “monday”) có lẽ cũng là dấu vết của dữ liệu đó, vốn đã được đổi về chữ thường.
  • Tham lam dừng sau hai ý và đổi “hit” thành “hits”. Beam 4 tìm được một chuỗi dài hơn, thêm “no deaths have been reported”, một ý quan trọng. Hai bản này lần nào chạy cũng giống nhau.
  • Top-p 0.9 chọn khác: thêm 3 000 người được sơ tán, bỏ số nhà bị ngập, và đổi “were moved” thành “are moved”. Không có chi tiết nào bịa ra ở đây, nhưng mỗi lần đổi seed là một bản khác. Với tóm tắt, sự đa dạng này ít khi là thứ bạn cần.

Seed cố định nên trên cùng máy, cùng phiên bản thư viện, kết quả lặp lại được. Ở máy hay phiên bản khác, dòng top-p có thể khác. T5-small chỉ được pre-train trên tiếng Anh, nên đừng đưa bản tin tiếng Việt vào nó; với tiếng Việt, hãy tìm hiểu ViT5.

Question 1 of 5 Tham chiếu là "Huế mưa lớn suốt ba ngày", bản máy là "mưa lớn ở Huế ba ngày" (mỗi âm tiết là một từ, không phân biệt hoa thường). ROUGE-1, ROUGE-2 và ROUGE-L (F1) lần lượt bằng bao nhiêu?
Question 2 of 5 Bạn dùng phạt lặp θ=1.5\theta = 1.5 kiểu Hugging Face. Một token đã xuất hiện có logit −2.0-2.0. Logit mới là bao nhiêu, và vì sao không đơn giản chia cho θ\theta như bài báo CTRL?
Question 3 of 5 Bạn cần hai việc: (a) tóm tắt một hợp đồng thuê nhà cho khách đọc, (b) gợi ý 20 câu khẩu hiệu khác nhau cho một quán cà phê. Cách giải mã nào hợp lý nhất?
Question 4 of 5 Văn bản nguồn viết "Đà Nẵng mở tuyến xe buýt điện đầu tiên". Bản tóm tắt A viết "Đà Nẵng mở tuyến xe buýt điện thứ hai". Bản B viết "Đà Nẵng mở tuyến xe buýt điện đầu tiên do một công ty Nhật vận hành" (nguồn không nhắc ai vận hành). Theo cách chia của Maynez và cộng sự (2020), đây là gì?
Question 5 of 5 Trong ô tóm tắt trích xuất, bạn thay điểm câu bằng "trung bình idf của các từ trong câu" (không so với vectơ cả bài). Điều gì dễ xảy ra?

Ý chính:

  • Sinh văn bản là vòng lặp tự hồi quy: mô hình cho phân phối của token kế tiếp, chiến lược giải mã chọn token. Tham lam và beam search ổn định nhưng dễ lặp khi viết dài (văn bản thoái hóa); lấy mẫu thuần đa dạng nhưng dễ lạc vào đuôi; top-k và top-p cắt đuôi, top-p theo độ chắc của mô hình. Phạt lặp chia logit dương (nhân logit âm) của token đã xuất hiện cho θ\theta.
  • Tóm tắt trích xuất chọn câu nguyên văn (lead, cosine TF-IDF với vectơ cả bài, TextRank): không bịa được, nhưng có thể rời rạc. Tóm tắt tóm lược viết lại bằng encoder–decoder đã pre-train (BART, T5, PEGASUS với mục tiêu sinh câu bị bỏ) hay bằng LLM qua prompt: trôi chảy, nhưng có thể có hallucination.
  • ROUGE-N đếm nn-gram trùng (lấy min⁡\min hai bên), ROUGE-L dùng dãy con chung dài nhất; PP chia cho bản máy, RR chia cho tham chiếu, F1 là trung bình điều hòa. ROUGE không thấy đồng nghĩa và không đo đúng sai.
  • Hallucination nội sinh bóp méo thông tin của nguồn, hallucination ngoại sinh thêm thông tin nguồn không có. Maynez và cộng sự thấy hơn 70% bản tóm tắt một câu trên XSum có hallucination. Giảm bằng dữ liệu sạch, giải mã thận trọng, kiểm tra bằng suy diễn văn bản, và cách lai trích xuất rồi viết lại.

Đọc thêm:

Content licensed under CC BY-NC-SA 4.0. Code under the MIT license.About