Học kì 1, năm học 2026–2027
Viện Trí tuệ nhân tạo, Trường ĐH Công nghệ, ĐHQGHN
Bài này: đến lượt phần dữ liệu mà các quy tắc đó không xử lý được.
Cột khó xử lý nhất trong bảng thường là cột chứa văn bản tự do.
Bảng reviews của Inside Airbnb tại Santiago có khoảng 690 nghìn đánh giá:
reviews = pd.read_csv("reviews.csv.gz")
reviews[["listing_id", "date", "comments"]].head(3)
listing_id date comments
0 52811 2010-11-13 Cristian's a great host, and the apartment...
1 52811 2011-03-23 Excellent accommodation and location...
2 211498 2012-01-05 Un departamento cómodo, bien ubicado...
Có thể lọc theo date và nối bảng theo
listing_id; riêng comments
chưa thể đưa thẳng vào groupby.
Không có cột complaint nào
để value_counts() cả. Câu trả lời nằm bên trong văn bản.
Càng sang phải càng giàu thông tin; càng khó đưa vào
groupby. Phần lớn dữ liệu doanh nghiệp nằm ở phía phải.
Với mỗi đánh giá, mục tiêu là tạo được một dòng như sau:
| comments (đầu vào) | sentiment | aspects_khen | aspects_chê | lang |
|---|---|---|---|---|
| "Great location, but so noisy at night…" | mixed | location | noise | en |
| "Depto impecable, anfitrión atento." | positive | cleanliness, host | — | es |
Có bảng này rồi thì câu hỏi ở đầu bài chỉ còn là một
groupby.
Trước khi dùng LLM, hãy bắt đầu bằng công cụ đã học ở bài 7.
Mỗi khía cạnh một bộ từ khoá, quét bằng str.contains:
KEYWORDS = {
"noise": r"noisy|noise|loud|ruido",
"cleanliness": r"clean|dirty|limpio|sucio",
}
for aspect, pat in KEYWORDS.items():
reviews[aspect] = reviews["comments"].str.contains(
pat, case=False, regex=True)
reviews[["noise", "cleanliness"]].mean()
noise 0.020
cleanliness 0.164
Đoạn mã ngắn này có thể xử lý 690 nghìn đánh giá trong vài giây.
Đếm từ tích cực trừ từ tiêu cực — thô nhưng có ngay con số:
POS = r"great|perfect|excellent|excelente"
NEG = r"bad|dirty|noisy|terrible|malo"
score = (reviews["comments"].str.count(POS, flags=re.I)
- reviews["comments"].str.count(NEG, flags=re.I))
reviews["baseline_sent"] = np.select(
[score > 0, score < 0], ["positive", "negative"], "mixed")
Các từ điển đầy đủ hơn, như VADER, vẫn dựa trên cùng nguyên lý đếm từ.
Xây dựng phương pháp đối chứng trước, rồi đánh giá cả hai phương pháp trên cùng một bộ dữ liệu gán nhãn (phần 5).
LLM có thể xử lý nội dung văn bản và được gọi từ mã nguồn như các API ở bài 6.
| Mô hình | Hợp với | Giá trả phí (1M token vào/ra) |
|---|---|---|
| gemini-3.1-flash-lite | trích xuất hàng loạt — hạn mức miễn phí cao | $0.25 / $1.50 |
| gemini-3.5-flash | văn bản khó, cần suy luận | $1.50 / $9.00 |
Gói miễn phí có hạn mức theo ngày và theo phút. Xem mức hiện hành trong AI Studio, mục Rate limits, vì các con số này có thể thay đổi.
from google.colab import userdata # Colab: menu 🔑 Secrets
API_KEY = userdata.get("GEMINI_API_KEY")
from google import genai
client = genai.Client(api_key=API_KEY)
interaction = client.interactions.create(
model="gemini-3.1-flash-lite",
input="Chào bằng đúng 5 từ tiếng Việt.",
)
print(interaction.output_text)
Chào bạn, rất vui gặp!
Mỗi lời gọi gửi một yêu cầu và nhận một phản hồi. Cách làm tương tự
requests.get() ở bài 6, nhưng API này xử lý nội dung văn bản.
review = reviews.loc[7, "comments"]
prompt = f"""Bạn là công cụ phân tích đánh giá về chỗ ở.
Đánh giá sau khen gì, chê gì? Trả lời ngắn gọn.
Đánh giá: {review}"""
out = client.interactions.create(
model="gemini-3.1-flash-lite", input=prompt)
print(out.output_text)
Khen: vị trí trung tâm, chủ nhà thân thiện.
Chê: về đêm khá ồn do gần quán bar.
Chạy lại 3 lần, nhận 3 kiểu trả lời:
Lần 1: "Khen: vị trí. Chê: tiếng ồn."
Lần 2: "Khách hài lòng về vị trí nhưng phàn nàn về độ ồn."
Lần 3: "- Ưu điểm: location\n- Nhược: noise (ban đêm)"
Cả ba câu trả lời có nội dung tương đương nhưng không thể chuyển ổn định thành các cột. Vì vậy, mô hình cần trả lời theo một mẫu cố định → phần 4.
Yêu cầu LLM điền biểu mẫu thay vì trả lời bằng văn xuôi.
Khai báo biểu mẫu mà mô hình phải điền, gồm kiểu dữ liệu và tập giá trị được phép:
from pydantic import BaseModel
from typing import Literal
Aspect = Literal["location", "cleanliness", "host",
"value", "noise", "amenities"]
class ReviewInfo(BaseModel):
sentiment: Literal["positive", "mixed", "negative"]
aspects_positive: list[Aspect]
aspects_negative: list[Aspect]
language: str # mã ISO: "en", "es", ...
Literal = enum: ngoài danh sách này là không hợp lệ.
def call_llm(text): # client đã tạo ở phần 3
return client.interactions.create(
model="gemini-3.1-flash-lite",
input=f"Phân tích đánh giá về chỗ ở sau.\n\nĐánh giá: {text}",
response_format={
"type": "text",
"mime_type": "application/json",
"schema": ReviewInfo.model_json_schema(),
},
)
info = ReviewInfo.model_validate_json(call_llm(review).output_text)
info.aspects_negative
['noise']
Từ chuỗi JSON, model_validate_json tạo đối tượng và đồng thời kiểm tra kiểu dữ liệu.
NaN.
import time
results = {}
for rid, text in sample["comments"].items():
try:
out = call_llm(text) # hàm gói lời gọi ở slide trước
results[rid] = ReviewInfo.model_validate_json(
out.output_text)
except Exception as e:
results[rid] = None # ghi lại, xử lý sau
time.sleep(4) # tuân thủ giới hạn tốc độ
Gói miễn phí giới hạn số yêu cầu mỗi phút. Nếu không có khoảng chờ sleep, chương trình có thể gặp lỗi 429 khi đang chạy.
CACHE = Path("llm_cache.json")
cache = json.loads(CACHE.read_text()) if CACHE.exists() else {}
def extract(rid, text):
if str(rid) not in cache: # chỉ gọi khi chưa có
cache[str(rid)] = call_llm(text).output_text
CACHE.write_text(json.dumps(cache))
return ReviewInfo.model_validate_json(cache[str(rid)])
labels = pd.DataFrame(
{rid: r.model_dump() for rid, r in results.items()
if r is not None}
).T.join(sample)
(labels.explode("aspects_negative")
.groupby("aspects_negative").size()
.sort_values(ascending=False))
noise 214
cleanliness 102
value 78
host 31
Câu hỏi mở đầu ("khách chê gì nhiều nhất?") giờ là một groupby bình thường.
"Trông có vẻ đúng" chưa phải là đúng.
Ba lỗi bịa thông tin (hallucination) ghi nhận khi soạn bài:
Kết quả minh hoạ trên 100 nhãn tay (notebook bài này làm bản nhỏ hơn):
| Cảm xúc (accuracy) | Khía cạnh (F1) | Ngôn ngữ (accuracy) | |
|---|---|---|---|
| Đối chứng bằng từ khoá | 0.62 | 0.48 | 0.71 |
| LLM (flash-lite) | 0.89 | 0.81 | 0.97 |
Chênh lệch lớn nhất nằm ở phủ định và dữ liệu đa ngôn ngữ. Đây là phần giá trị gia tăng của LLM trong ví dụ này.
Mỗi lần tương tác trả về số token đã dùng:
out.usage.total_input_tokens, out.usage.total_output_tokens
(182, 47)
Ước tính cho 100.000 đánh giá (trung bình đo trên dữ liệu minh hoạ: ~175 token vào, ~36 token ra) với flash-lite ($0.25 / $1.50 mỗi 1M token):
17,5M token vào ≈ $4.4 + 3,6M token ra ≈ $5.4 → ~$9.8
Gói miễn phí có thể xử lý mẫu vài nghìn đánh giá nếu chạy trong nhiều ngày. Với bài tập lớn, cần chọn mẫu phù hợp và lưu kết quả vào cache.
| Tình huống | Công cụ hợp lý |
|---|---|
| Mẫu rõ ràng (mã bưu điện, giá, ngày…) | biểu thức chính quy — bài 7 |
| Ngữ nghĩa, đa ngôn ngữ, quy mô vừa | LLM + schema + bộ nhãn tay |
| Quyết định hệ trọng từng ca (pháp lý, y tế…) | con người thẩm định — LLM chỉ hỗ trợ sàng lọc |
Luôn tự hỏi: sai 10% thì phân tích của bạn còn đứng vững không?
Đây là quy trình kiểm chứng AI mà chính sách môn yêu cầu.