# Thực hành: chọn, cân bằng và sắp thứ tự ví dụ few-shot từ dữ liệu thật của khách

> Mười ví dụ bốc ngẫu nhiên từ kho ticket của khách có thể khiến model nghiêng hẳn về nhãn đa số, và chỉ đổi thứ tự cũng đủ làm kết quả thay đổi hẳn.

Bản gốc: https://fdetimes.net/vi/bach-khoa/chon-va-sap-xep-vi-du-few-shot-tranh-thien-lech/

Năm 2021, nhóm tác giả bài "Calibrate Before Use" chỉ ra một điều khó chịu: chỉ cần đổi thứ tự các ví dụ trong prompt, độ chính xác của model có thể đi từ mức gần như đoán mò lên gần state-of-the-art. Bộ ví dụ giữ nguyên, nhãn giữ nguyên, chỉ có cách xếp là khác.

Ở site khách hàng, bạn hiếm khi được chọn ví dụ trong điều kiện sạch sẽ. Dữ liệu là ticket, email, biên bản thật, và nhãn gần như luôn lệch: một lớp chiếm phần lớn, vài lớp còn lại hiếm nhưng quan trọng. Nếu bạn bốc đại vài ví dụ dán vào prompt, model sẽ học luôn cả cái lệch đó.

Quy trình dưới đây làm được ngay trên laptop: đo dữ liệu, lấy mẫu theo lớp, loại ví dụ trùng, xáo thứ tự và kiểm tra xem model có đang nghiêng về một nhãn hay không.

## Bạn sẽ xây gì, và cần chuẩn bị những gì?

Hãy hình dung một ví dụ giả định: khách là công ty viễn thông, đưa bạn 1.000 ticket đã gán nhãn, gồm 700 "hỏi thông tin", 200 "khiếu nại" và 100 "hủy dịch vụ". Nhiệm vụ là phân loại ticket mới bằng LLM. Cuối bài, bạn sẽ có một script chọn ra bộ ví dụ few-shot, sinh nhiều thứ tự khác nhau và đo độ ổn định.

Bạn cần Python 3 và một file `tickets.csv` có hai cột `text` và `label`. Code bên dưới chỉ dùng thư viện chuẩn. Hàm gọi model được để trống có chủ đích, vì mỗi khách dùng một nhà cung cấp khác nhau. Toàn bộ code là phiên bản đơn giản hoá để dạy quy trình, chưa phải code production.

## Bước 1: chạy zero-shot trước, đừng nhảy thẳng vào few-shot

Zero-shot là prompt không có ví dụ nào. Hướng dẫn của DAIR.AI khuyên chỉ thêm ví dụ khi zero-shot không đạt. Con số zero-shot là mốc so sánh của bạn: nếu few-shot không vượt được mốc này thì bộ ví dụ đang gây hại chứ không giúp gì.

Trước khi chạy, hãy đo dữ liệu:

```python
import csv
from collections import Counter

rows = list(csv.DictReader(open("tickets.csv", encoding="utf-8")))
print(Counter(r["label"] for r in rows))
# Counter({'hoi_thong_tin': 700, 'khieu_nai': 200, 'huy_dich_vu': 100})
```

**Kiểm tra:** tổng các lớp phải bằng số dòng, và không được có nhãn lạ do gõ sai, kiểu "khieu nai" lẫn với "khieu_nai". Dữ liệu khách hay dính lỗi này. Lỗi nằm ở nhãn thì không prompt nào sửa được.

## Bước 2: lấy mẫu theo lớp, vì lấy ngẫu nhiên là lấy luôn cái lệch

Với tỉ lệ 70/20/10, nếu bốc ngẫu nhiên 10 ví dụ, trung bình khoảng 7 ví dụ sẽ là "hỏi thông tin". Learn Prompting mô tả đúng hậu quả này: khi phân phối ví dụ lệch về một lớp, model cũng nghiêng về dự đoán lớp đó.

Với khách, điều đó có nghĩa là ticket "hủy dịch vụ" (loại cần giữ chân gấp nhất) bị xếp nhầm vào nhóm vô hại.

Cách debias đầu tiên mà Learn Prompting đưa ra là cân bằng số ví dụ giữa các lớp:

```python
import random

def sample_per_class(rows, k_per_class, seed=0):
rng = random.Random(seed)
by_label = {}
for r in rows:
by_label.setdefault(r["label"], []).append(r)
picked = []
for label, items in by_label.items():
picked += rng.sample(items, min(k_per_class, len(items)))
return picked

balanced = sample_per_class(rows, k_per_class=3)
```

Nhưng đừng vội coi cân bằng là đáp án cuối cùng. DAIR.AI cũng ghi nhận rằng trong thí nghiệm dùng nhãn ngẫu nhiên, lấy nhãn theo phân phối thật cho kết quả tốt hơn lấy theo phân phối đều. Vì thế, hãy dựng thêm một bộ theo tỉ lệ thật để so sánh, và để số liệu trên tập test của khách quyết định.

**Kiểm tra:** in `Counter` của `balanced`, mỗi lớp phải có đúng 3 ví dụ.

## Bước 3: ví dụ phải giống dữ liệu thật nhưng không được giống nhau

Theo DAIR.AI, cả tập nhãn lẫn phân phối của văn bản đầu vào trong ví dụ đều ảnh hưởng đến kết quả few-shot. Vì vậy, ví dụ nên lấy từ ticket thật, giữ nguyên lỗi chính tả và kiểu viết tắt của khách hàng cuối, thay vì câu mẫu bạn tự viết cho gọn gàng.

Nhưng Learn Prompting cảnh báo rằng ví dụ quá giống nhau dễ khiến model khái quát hoá quá mức, và context window cũng giới hạn số ví dụ bạn nhét vào được. Bản đơn giản hoá dưới đây loại các cặp gần trùng bằng `difflib`. Trong dự án thật, bạn sẽ thay bằng độ tương đồng embedding:

```python
from difflib import SequenceMatcher

def dedupe(examples, threshold=0.8):
kept = []
for ex in examples:
if all(SequenceMatcher(None, ex["text"], k["text"]).ratio() < threshold
for k in kept):
kept.append(ex)
return kept
```

Nếu sau khi loại trùng mà một lớp bị thiếu, hãy lấy mẫu bổ sung cho lớp đó. Learn Prompting cũng nhắc tới hai hướng tự động hoá đáng tìm hiểu khi dữ liệu lớn hơn.

KNN chọn những ví dụ giống nhất với truy vấn đầu vào. Vote-K chọn ví dụ đa dạng và mang tính đại diện từ dữ liệu chưa gán nhãn, hữu ích khi khách chỉ có log thô.

## Bước 4: xáo thứ tự và đo, đừng tin một lần chạy

Learn Prompting cho rằng xếp ví dụ theo thứ tự ngẫu nhiên thường tốt hơn. Có một giả thuyết bạn nên tự kiểm chứng trên dữ liệu của khách: nếu dồn cả ba ví dụ "hủy dịch vụ" xuống cuối prompt, liệu model có dự đoán nhãn đó nhiều hơn so với khi xáo đều?

Thay vì đoán, hãy đưa thứ tự cố ý này vào làm một cấu hình để so.

Kết quả của Zhao và cộng sự cho thấy biên độ dao động do thứ tự lớn đến mức bạn phải coi thứ tự là một biến cần đo.

```python
def build_prompt(examples, query):
head = ("Phân loại ticket vào một trong: hoi_thong_tin, khieu_nai, huy_dich_vu.\n"
"Đánh giá từng ticket độc lập, không ưu tiên nhãn nào.\n\n")
shots = "".join(f"Ticket: {e['text']}\nNhãn: {e['label']}\n\n" for e in examples)
return head + shots + f"Ticket: {query}\nNhãn:"

def call_model(prompt):
raise NotImplementedError  # gọi API của nhà cung cấp khách đang dùng

for seed in range(5):
order = balanced[:]
random.Random(seed).shuffle(order)
# chạy build_prompt + call_model trên tập test, ghi lại kết quả theo seed
```

Dòng thứ hai của prompt có mục đích riêng. Learn Prompting ghi nhận rằng có thể yêu cầu model không thiên lệch một cách tường minh bằng một chỉ dẫn trong prompt.

Định dạng `Ticket: ... / Nhãn: ...` cũng phải giống hệt nhau ở mọi ví dụ. DAIR.AI lưu ý rằng định dạng ảnh hưởng đáng kể đến hiệu năng, còn Learn Prompting coi việc cố định cấu trúc đầu ra là lợi ích lớn nhất của few-shot.

**Kiểm tra:** in ra một prompt hoàn chỉnh và đọc bằng mắt. Chỉ cần thừa một dấu cách hay thiếu một dòng trống ở một ví dụ là định dạng đã không còn nhất quán.

## Bước 5: nhìn phân phối dự đoán, không chỉ nhìn accuracy

Bài "Calibrate Before Use" giải thích rằng sự bất ổn này đến từ việc model thiên về một số đáp án nhất định, và hiệu chỉnh theo ngữ cảnh giúp cải thiện độ chính xác.

Kỹ thuật đó được gọi là contextual calibration, và đây là từ khoá nên tìm đọc tiếp. Khác với các bước trên vốn chỉnh bộ ví dụ, hướng này nhắm thẳng vào thiên lệch của model đối với một số đáp án, nên đáng thử khi bộ ví dụ đã cân bằng mà model vẫn nghiêng về một nhãn.

Bạn chưa cần cài đặt kỹ thuật hiệu chỉnh đầy đủ để phát hiện triệu chứng. Với mỗi seed, hãy in ba thứ: accuracy, số lần model dự đoán từng nhãn, và recall của lớp hiếm:

```python
from collections import Counter

def report(seed, y_true, y_pred, rare="huy_dich_vu"):
acc = sum(t == p for t, p in zip(y_true, y_pred)) / len(y_true)
hit = sum(t == p == rare for t, p in zip(y_true, y_pred))
total = sum(t == rare for t in y_true)
print(f"seed {seed}: acc={acc:.2f} pred={Counter(y_pred)} "
f"recall_{rare}={hit}/{total}")

# Kết quả minh họa, không phải số đo thật:
# seed 0: acc=0.81 pred=Counter({'hoi_thong_tin': 78, 'khieu_nai': 18, 'huy_dich_vu': 4}) ...
# seed 1: acc=0.74 pred=Counter({'hoi_thong_tin': 86, 'khieu_nai': 12, 'huy_dich_vu': 2}) ...
# seed 2: acc=0.84 pred=Counter({'hoi_thong_tin': 72, 'khieu_nai': 19, 'huy_dich_vu': 9}) ...
```

Đọc kết quả minh họa trên với tập test giả định gồm 100 ticket theo tỉ lệ 70/20/10. Ở seed 1, model chỉ dự đoán "hủy dịch vụ" 2 lần. Kể cả khi cả 2 lần đều đúng, vẫn có ít nhất 8 trong 10 khách muốn rời đi bị bỏ sót, trong khi accuracy 0.74 trông vẫn tạm ổn nhờ lớp đa số kéo lên.

Con số recall đó mới là thứ khách quan tâm. Và khoảng cách giữa seed tốt nhất với seed tệ nhất chính là thứ bạn phải báo cáo, không phải chỉ con số đẹp nhất.

**Điểm mấu chốt:** Bộ ví dụ few-shot là dữ liệu huấn luyện thu nhỏ: chọn lệch thì model lệch theo.

## Những lỗi hay gặp nhất

| Lỗi | Triệu chứng | Cách sửa |
|---|---|---|
| Bốc ví dụ ngẫu nhiên từ dữ liệu lệch | Model dồn dự đoán vào lớp đa số | Lấy mẫu theo lớp, so thêm với bộ theo tỉ lệ thật |
| Tự viết ví dụ "sạch" | Chạy tốt khi demo, kém khi gặp ticket thật | Lấy ví dụ từ dữ liệu khách, giữ nguyên văn phong |
| Nhiều ví dụ gần trùng | Model khái quát quá mức, tốn context | Loại trùng, ưu tiên ví dụ đa dạng |
| Chỉ chạy một thứ tự | Kết quả không tái lập được giữa các lần thử | Chạy nhiều seed, báo cáo cả độ dao động |

Cũng đừng kỳ vọng quá nhiều. DAIR.AI thừa nhận few-shot chuẩn chưa phải kỹ thuật hoàn hảo, nhất là với các bài toán cần suy luận. Nếu nhãn của khách phụ thuộc vào chuỗi lập luận nhiều bước, cân bằng ví dụ khéo đến đâu cũng chỉ giải quyết được một phần.

## Ở site khách, thứ bạn bàn giao là một bảng chứ không chỉ một prompt

Bảng đó ghi rõ bộ ví dụ nào, chạy bao nhiêu thứ tự, accuracy dao động trong khoảng nào, và lớp hiếm bị bỏ sót bao nhiêu lần. Khi khách hỏi vì sao hôm qua model chạy tốt mà hôm nay lại kém, bảng đó là câu trả lời.

Khi đọc job description FDE, hãy để ý các cụm như "evaluation", "prompt reliability" hay "làm việc với dữ liệu khách hàng". Trong CV, đừng viết "có kinh nghiệm prompt engineering". Hãy viết rằng bạn đã đo được độ lệch nhãn, sửa nó, và chứng minh kết quả vẫn ổn định khi đổi thứ tự ví dụ.

Ai cũng viết được một prompt chạy được trong buổi demo. FDE được trả tiền để chứng minh prompt đó vẫn chạy đúng với ticket thứ một nghìn, kể cả khi đó là ticket mà lớp đa số không đại diện được.

**Thử ngay tuần này:**

- Lấy 200 bản ghi đã gán nhãn của một dự án bạn đang làm, chạy script đếm nhãn và ghi lại tỉ lệ từng lớp.
- Dựng hai bộ few-shot (cân bằng và theo phân phối thật), mỗi bộ chạy 5 thứ tự, rồi lập bảng accuracy, phân phối dự đoán và recall lớp hiếm theo từng seed.
- Thêm một dòng vào CV mô tả đúng việc này: 'thiết kế và kiểm định bộ ví dụ few-shot từ dữ liệu khách, đo độ ổn định theo thứ tự'.

## Nguồn

- [Few-Shot Prompting - Prompt Engineering Guide (DAIR.AI)](https://www.promptingguide.ai/techniques/fewshot)

- [Zero-Shot Prompting - Prompt Engineering Guide (DAIR.AI)](https://www.promptingguide.ai/techniques/zeroshot)

- [Prompt Debiasing: Ensuring Fair and Balanced LLM Outputs (Learn Prompting)](https://learnprompting.org/docs/reliability/debiasing)

- [Introduction to Few-Shot Prompting Techniques (Learn Prompting)](https://learnprompting.org/docs/advanced/few_shot/introduction)

- [Technique #3: Examples in Prompts: From Zero-Shot to Few-Shot (Learn Prompting)](https://learnprompting.org/docs/basics/few_shot)

- [Calibrate Before Use: Improving Few-Shot Performance of Language Models](https://arxiv.org/abs/2102.09690)
