Lộ trình học Python cho người học Data Science / AI
Data Science và AI là hai lĩnh vực bị hiểu sai nhiều nhất trong ngành công nghệ. Người ngoài nghĩ đây là công việc "dạy máy tính thông minh". Thực tế, 70–80% thời gian là làm sạch dữ liệu và hiểu vấn đề — phần xây model chỉ chiếm phần nhỏ.
Bài viết này là lộ trình 24 tuần, nói thẳng về những gì cần học và những gì không cần.
🎯 Trước khi bắt đầu: ba sự thật cần biết
1. Toán quan trọng, nhưng không phải toán thi đại học. Bạn cần đại số tuyến tính (ma trận, vector), xác suất thống kê, và giải tích cơ bản (đạo hàm). Bạn không cần giải tích phức, phương trình vi phân, hay hình học vi phân (trừ khi làm research thuần).
2. Data Science ≠ AI Engineer ≠ ML Engineer.
| Vị trí | Công việc chính | Công cụ |
|---|---|---|
| Data Analyst | Trả lời câu hỏi kinh doanh | SQL, pandas, BI tools |
| Data Scientist | Phân tích, thí nghiệm, xây model dự đoán | Python, scikit-learn, thống kê |
| ML Engineer | Đưa model vào sản phẩm, scale | PyTorch, Docker, MLOps, cloud |
| AI Research | Nghiên cứu thuật toán mới | Toán nặng, PyTorch, đọc paper |
| AI Application Dev | Xây app dùng LLM/API | Python, prompt engineering, RAG |
Đa số việc làm ở Việt Nam nằm ở Data Analyst → Data Scientist → ML Engineer. Research thuần rất ít và thường yêu cầu bằng tiến sĩ.
3. Thị trường đã thay đổi.
Với sự bùng nổ của LLM, công việc "train model từ đầu" giảm mạnh. Nhu cầu tăng ở:
- MLOps — đưa model vào production, giám sát, cập nhật.
- AI application — dùng LLM API, RAG, fine-tuning nhẹ.
- Data engineering — hạ tầng dữ liệu cho AI.
💡 Lời khuyên thực tế: nếu bạn muốn làm AI, hãy học kỹ thuật phần mềm + dữ liệu thật tốt, rồi thêm AI. Người chỉ biết gọi
model.fit()mà không biết deploy, không biết xử lý dữ liệu, sẽ rất khó có việc.
🗓 Lộ trình 24 tuần
Tuần 1–4 Python cho khoa học dữ liệu: numpy, pandas
Tuần 5–7 Toán nền tảng: đại số tuyến tính, xác suất, giải tích
Tuần 8–10 Phân tích dữ liệu & trực quan hoá
Tuần 11–15 Machine Learning với scikit-learn
Tuần 16–19 Deep Learning với PyTorch
Tuần 20 NLP và LLM cơ bản
Tuần 21 MLOps: đóng gói và triển khai model
Tuần 22–23 Dự án portfolio
Tuần 24 Phỏng vấn và định hướng
🐍 Tuần 1–4: Python cho khoa học dữ liệu
pip install numpy pandas matplotlib seaborn jupyter
jupyter lab
NumPy — nền tảng của mọi thứ
import numpy as np
# Mảng và hình dạng
a = np.array([[1, 2, 3], [4, 5, 6]]) # shape (2, 3)
print(a.shape, a.dtype, a.ndim)
# Vector hoá — nhanh hơn vòng lặp Python hàng chục lần
arr = np.arange(1_000_000)
print((arr * 2 + 1).sum())
# Broadcasting — quy tắc then chốt
diem = np.array([[8, 7, 9], [6, 8, 7]]) # (2, 3) — 2 học sinh, 3 môn
he_so = np.array([1.0, 1.5, 2.0]) # (3,) — tự broadcast
print(diem * he_so) # (2, 3)
# Thống kê dọc theo trục
print(diem.mean(axis=1)) # điểm TB của từng học sinh → [8.0, 7.0]
print(diem.mean(axis=0)) # điểm TB của từng môn → [7.0, 7.5, 8.0]
# Tạo dữ liệu giả để thử nghiệm
rng = np.random.default_rng(42) # seed để tái lập kết quả
du_lieu = rng.normal(loc=100, scale=15, size=(1000, 5))
# Đại số tuyến tính — nền tảng của ML
X = np.array([[1, 2], [3, 4], [5, 6]])
print(X.T) # chuyển vị
print(X @ X.T) # nhân ma trận
print(np.linalg.inv(np.array([[2.0, 1.0], [1.0, 3.0]])))
⚠️ Quy tắc số 1 của NumPy: không bao giờ lặp qua từng phần tử. Mọi thứ đều vector hoá được, và vector hoá nhanh hơn 10–100 lần.
pandas — công cụ làm việc hàng ngày
import pandas as pd
df = pd.read_csv("du-lieu.csv", parse_dates=["ngay"])
# Luôn khám phá trước khi làm gì khác
print(df.shape, df.dtypes.to_dict())
print(df.isna().mean().sort_values(ascending=False).head(10))
print(df.describe(include="all"))
# Biến đổi
df["doanh_thu"] = df["so_luong"] * df["don_gia"]
df["thang"] = df["ngay"].dt.to_period("M")
df["nhom"] = pd.cut(df["doanh_thu"], bins=[0, 1e6, 5e6, np.inf],
labels=["Thấp", "Trung bình", "Cao"])
# Nhóm
tom_tat = (df.groupby(["thang", "nhom"], observed=True)
.agg(so_don=("id", "count"),
dt=("doanh_thu", "sum"),
tb=("doanh_thu", "mean"))
.reset_index())
# Merge — như JOIN trong SQL
df_full = df.merge(khach_hang, on="khach_hang_id", how="left", validate="many_to_one")
validate= trong merge là tham số ít người biết nhưng cực hữu ích: nó báo lỗi nếu quan hệ bạn giả định không đúng (ví dụ bạn nghĩ many_to_one nhưng thực tế là many_to_many → dữ liệu sẽ nhân lên).
🧮 Tuần 5–7: Toán nền tảng
Học toán song song với code, không học chay.
Đại số tuyến tính — quan trọng nhất
| Khái niệm | Ý nghĩa trong ML | Ứng dụng |
|---|---|---|
| Vector | Một điểm dữ liệu (các đặc trưng) | Mọi dòng dữ liệu là một vector |
| Ma trận | Toàn bộ tập dữ liệu | X shape (n_samples, n_features) |
| Tích vô hướng | Độ tương đồng | Cosine similarity, attention |
| Nhân ma trận | Biến đổi tuyến tính | Mọi layer của neural network |
| Trị riêng / vector riêng | Hướng chính của dữ liệu | PCA, giảm chiều |
| Chuẩn (norm) | Độ lớn vector | Regularization (L1, L2) |
import numpy as np
# Tích vô hướng = độ tương đồng
a = np.array([1, 2, 3])
b = np.array([2, 4, 6]) # b = 2a → cùng hướng
cos = (a @ b) / (np.linalg.norm(a) * np.linalg.norm(b))
print(f"Cosine similarity: {cos:.4f}") # = 1.0
# PCA từ đầu — hiểu nó hoạt động thế nào
X = np.random.default_rng(42).normal(size=(200, 5))
X_tam = X - X.mean(axis=0) # 1. chuẩn hoá tâm
cov = np.cov(X_tam, rowvar=False) # 2. ma trận hiệp phương sai
gia_tri_rieng, vector_rieng = np.linalg.eigh(cov)
idx = np.argsort(gia_tri_rieng)[::-1] # 3. sắp xếp giảm dần
X_pca = X_tam @ vector_rieng[:, idx[:2]] # 4. chiếu xuống 2 chiều
print(f"Tỷ lệ phương sai giữ lại: "
f"{gia_tri_rieng[idx[:2]].sum() / gia_tri_rieng.sum():.2%}")
Hiểu PCA ở mức này quan trọng hơn gọi PCA(n_components=2).fit_transform(X) mà không biết nó làm gì.
Xác suất thống kê
| Khái niệm | Dùng ở đâu |
|---|---|
| Phân phối xác suất | Giả định của model, kiểm tra dữ liệu |
| Bayes | Naive Bayes, suy luận xác suất |
| Kỳ vọng, phương sai | Hàm mất mát, đánh giá |
| Ước lượng hợp lý cực đại | Nền tảng của hồi quy logistic |
| Khoảng tin cậy, p-value | Đánh giá thí nghiệm A/B |
| Định lý giới hạn trung tâm | Vì sao bootstrap hoạt động |
Giải tích — chỉ cần đạo hàm
# Đạo hàm = hướng và tốc độ thay đổi → cơ sở của gradient descent
def ham_mat_mat(w):
return (w - 3) ** 2 + 2 # cực tiểu tại w = 3
def dao_ham(w):
return 2 * (w - 3)
# Gradient descent từ đầu
w = 0.0
learning_rate = 0.1
for buoc in range(50):
grad = dao_ham(w)
w -= learning_rate * grad
if buoc % 10 == 0:
print(f"Bước {buoc:>2}: w = {w:.6f}, loss = {ham_mat_mat(w):.6f}")
print(f"→ Hội tụ tại w = {w:.6f} (đáp án đúng: 3.0)")
Viết gradient descent bằng tay một lần giúp bạn hiểu optimizer.step() về sau.
📊 Tuần 8–10: EDA và trực quan hoá
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
df = pd.read_csv("du-lieu.csv")
# --- 1. Kiểm tra phân phối và outlier ---
fig, axes = plt.subplots(2, 3, figsize=(16, 9))
for ax, cot in zip(axes.ravel(), df.select_dtypes("number").columns[:6]):
sns.histplot(df[cot].dropna(), kde=True, ax=ax, color="#2563eb")
ax.set_title(f"Phân phối: {cot}")
plt.tight_layout()
plt.show()
# --- 2. Ma trận tương quan ---
plt.figure(figsize=(12, 10))
corr = df.select_dtypes("number").corr()
sns.heatmap(corr, annot=len(corr) <= 12, fmt=".2f", cmap="RdBu_r",
center=0, square=True, cbar_kws={"shrink": 0.8})
plt.title("Ma trận tương quan")
plt.show()
# --- 3. Tìm tương quan mạnh với biến mục tiêu ---
muc_tieu = "gia"
tuong_quan = (corr[muc_tieu].drop(muc_tieu)
.sort_values(key=abs, ascending=False))
print("Tương quan với biến mục tiêu:")
print(tuong_quan.head(10).round(3))
Ba điều cần nhớ về tương quan:
- Chỉ áp dụng cho quan hệ tuyến tính. Hai biến có quan hệ hình chữ U có thể có
r ≈ 0. - Nhạy với outlier. Một điểm dị thường có thể tạo tương quan giả.
- Tương quan ≠ nhân quả. Luôn luôn.
🤖 Tuần 11–15: Machine Learning với scikit-learn
Đây là phần cốt lõi. scikit-learn vẫn là công cụ quan trọng nhất — không phải deep learning.
Quy trình 7 bước chuẩn
import numpy as np
from sklearn.model_selection import train_test_split, cross_val_score, GridSearchCV
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, roc_auc_score, confusion_matrix
# --- 1. Tách dữ liệu TRƯỚC khi làm bất cứ gì khác ---
X = df.drop(columns=["nhan"])
y = df["nhan"]
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y # stratify giữ tỷ lệ lớp
)
# --- 2. Tiền xử lý bằng Pipeline (KHÔNG fit trên test!) ---
cot_so = X.select_dtypes("number").columns.tolist()
cot_chu = X.select_dtypes("object").columns.tolist()
tien_xu_ly = ColumnTransformer([
("so", Pipeline([
("dien_khuyet", SimpleImputer(strategy="median")),
("chuan_hoa", StandardScaler()),
]), cot_so),
("chu", Pipeline([
("dien_khuyet", SimpleImputer(strategy="constant", fill_value="Không rõ")),
("ma_hoa", OneHotEncoder(handle_unknown="ignore", sparse_output=False)),
]), cot_chu),
])
# --- 3. Pipeline hoàn chỉnh ---
model = Pipeline([
("tien_xu_ly", tien_xu_ly),
("phan_loai", RandomForestClassifier(random_state=42, n_jobs=-1)),
])
# --- 4. Cross-validation TRƯỚC khi tuning ---
diem_cv = cross_val_score(model, X_train, y_train, cv=5, scoring="roc_auc")
print(f"ROC-AUC CV: {diem_cv.mean():.4f} ± {diem_cv.std():.4f}")
# --- 5. Tuning siêu tham số ---
luoi = {
"phan_loai__n_estimators": [100, 300, 500],
"phan_loai__max_depth": [None, 10, 20],
"phan_loai__min_samples_leaf": [1, 2, 5],
}
tim_kiem = GridSearchCV(model, luoi, cv=5, scoring="roc_auc", n_jobs=-1, verbose=1)
tim_kiem.fit(X_train, y_train)
print(f"Bộ tham số tốt nhất: {tim_kiem.best_params_}")
# --- 6. Đánh giá trên tập test (CHỈ MỘT LẦN) ---
mo_hinh_tot_nhat = tim_kiem.best_estimator_
y_du_doan = mo_hinh_tot_nhat.predict(X_test)
y_xac_suat = mo_hinh_tot_nhat.predict_proba(X_test)[:, 1]
print(classification_report(y_test, y_du_doan))
print(f"ROC-AUC test: {roc_auc_score(y_test, y_xac_suat):.4f}")
print(f"Ma trận nhầm lẫn:\n{confusion_matrix(y_test, y_du_doan)}")
# --- 7. Feature importance ---
ten_dac_trung = mo_hinh_tot_nhat.named_steps["tien_xu_ly"].get_feature_names_out()
do_quan_trong = mo_hinh_tot_nhat.named_steps["phan_loai"].feature_importances_
for ten, diem in sorted(zip(ten_dac_trung, do_quan_trong),
key=lambda x: -x[1])[:10]:
print(f"{ten:<40} {diem:.4f}")
Những lỗi nghiêm trọng nhất trong ML
1. Data leakage — lỗi chết người số 1:
# ❌ RÒ RỈ DỮ LIỆU: fit scaler trên TOÀN BỘ dữ liệu
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X) # đã "thấy" cả tập test!
X_train, X_test = train_test_split(X_scaled, ...)
# ✅ ĐÚNG: chỉ fit trên train (Pipeline làm việc này tự động)
X_train, X_test, y_train, y_test = train_test_split(X, y)
# rồi dùng Pipeline như ví dụ trên
Leakage làm model báo accuracy 95% khi validation nhưng chỉ 60% khi thực tế.
2. Không có baseline:
from sklearn.dummy import DummyClassifier
# Luôn so sánh với baseline trước
baseline = DummyClassifier(strategy="most_frequent")
baseline.fit(X_train, y_train)
print(f"Baseline accuracy: {baseline.score(X_test, y_test):.4f}")
# Nếu model của bạn = 0.72 và baseline = 0.70 → model gần như vô dụng!
3. Chỉ nhìn accuracy với dữ liệu mất cân bằng:
Nếu 99% giao dịch là hợp lệ, model "luôn dự đoán hợp lệ" đạt 99% accuracy — và bắt được 0% gian lận.
from sklearn.metrics import precision_recall_fscore_support
p, r, f1, _ = precision_recall_fscore_support(y_test, y_du_doan, average="binary")
print(f"Precision: {p:.4f} | Recall: {r:.4f} | F1: {f1:.4f}")
| Chỉ số | Ý nghĩa | Khi nào ưu tiên |
|---|---|---|
| Accuracy | Tỷ lệ đúng | Dữ liệu cân bằng |
| Precision | Trong số dự đoán "positive", bao nhiêu đúng | Khi false positive tốn kém (spam filter) |
| Recall | Trong số "positive" thật, bắt được bao nhiêu | Khi false negative nguy hiểm (ung thư, gian lận) |
| F1 | Trung bình điều hoà của hai cái trên | Cần cân bằng |
| ROC-AUC | Khả năng phân biệt hai lớp | So sánh model tổng quát |
| PR-AUC | Như ROC nhưng tốt hơn khi mất cân bằng | Dữ liệu lệch mạnh |
🧠 Tuần 16–19: Deep Learning với PyTorch
pip install torch torchvision
import torch
import torch.nn as nn
from torch.utils.data import DataLoader, TensorDataset
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
# --- Chuẩn bị dữ liệu ---
X, y = make_classification(n_samples=5000, n_features=20, n_informative=12,
random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
scaler = StandardScaler().fit(X_train)
X_train = scaler.transform(X_train)
X_test = scaler.transform(X_test)
train_ds = TensorDataset(
torch.tensor(X_train, dtype=torch.float32),
torch.tensor(y_train, dtype=torch.float32).unsqueeze(1),
)
train_loader = DataLoader(train_ds, batch_size=64, shuffle=True)
# --- Định nghĩa model ---
class MangPhanLoai(nn.Module):
def __init__(self, so_dac_trung: int):
super().__init__()
self.mang = nn.Sequential(
nn.Linear(so_dac_trung, 128),
nn.ReLU(),
nn.Dropout(0.3),
nn.Linear(128, 64),
nn.ReLU(),
nn.Dropout(0.3),
nn.Linear(64, 1),
)
def forward(self, x):
return self.mang(x)
thiet_bi = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = MangPhanLoai(X_train.shape[1]).to(thiet_bi)
ham_mat_mat = nn.BCEWithLogitsLoss()
bo_toi_uu = torch.optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-4)
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(bo_toi_uu, patience=5, factor=0.5)
# --- Vòng lặp huấn luyện ---
so_vong = 50
lich_su = {"train_loss": [], "val_loss": []}
X_test_t = torch.tensor(X_test, dtype=torch.float32).to(thiet_bi)
y_test_t = torch.tensor(y_test, dtype=torch.float32).unsqueeze(1).to(thiet_bi)
for vong in range(so_vong):
model.train()
tong_loss = 0.0
for xb, yb in train_loader:
xb, yb = xb.to(thiet_bi), yb.to(thiet_bi)
bo_toi_uu.zero_grad()
du_doan = model(xb)
loss = ham_mat_mat(du_doan, yb)
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
bo_toi_uu.step()
tong_loss += loss.item() * len(xb)
# Đánh giá trên validation
model.eval()
with torch.no_grad():
val_loss = ham_mat_mat(model(X_test_t), y_test_t).item()
train_loss = tong_loss / len(train_ds)
lich_su["train_loss"].append(train_loss)
lich_su["val_loss"].append(val_loss)
scheduler.step(val_loss)
if (vong + 1) % 10 == 0:
print(f"Vòng {vong + 1:>3}/{so_vong} — "
f"train loss {train_loss:.4f} | val loss {val_loss:.4f} | "
f"lr {bo_toi_uu.param_groups[0]['lr']:.2e}")
# --- Đánh giá cuối ---
model.eval()
with torch.no_grad():
xac_suat = torch.sigmoid(model(X_test_t)).cpu().numpy().ravel()
from sklearn.metrics import roc_auc_score
print(f"\nROC-AUC: {roc_auc_score(y_test, xac_suat):.4f}")
Bốn khái niệm PyTorch phải nắm:
| Khái niệm | Vai trò |
|---|---|
nn.Module | Lớp cơ sở cho mọi model |
forward() | Định nghĩa luồng tính toán |
loss.backward() | Tính gradient |
optimizer.step() | Cập nhật trọng số |
model.train() / .eval() | Bật/tắt Dropout, BatchNorm |
torch.no_grad() | Tắt tính gradient khi suy luận (tiết kiệm bộ nhớ) |
⚠️ Lỗi khiến 90% người mới mất hàng giờ: quên
model.eval()khi đánh giá. Dropout vẫn hoạt động → kết quả validation sai và không tái lập được.
Khi nào dùng deep learning thay vì scikit-learn?
| Dùng scikit-learn | Dùng deep learning |
|---|---|
| Dữ liệu dạng bảng (tabular) | Ảnh, văn bản, âm thanh |
| Dữ liệu ít (< 100k dòng) | Dữ liệu lớn (> 1M mẫu) |
| Cần giải thích được quyết định | Cần độ chính xác tối đa |
| Cần nhanh, ít tinh chỉnh | Có GPU và thời gian |
| Baseline và prototype | Bài toán thực sự phức tạp |
💡 Sự thật quan trọng: với dữ liệu dạng bảng, XGBoost/LightGBM thường đánh bại neural network. Đừng dùng deep learning chỉ vì nó nghe "hiện đại" hơn.
💬 Tuần 20: NLP và LLM cơ bản
# --- Cách 1: Dùng API (thực tế nhất hiện nay) ---
from openai import OpenAI
client = OpenAI()
def phan_tich_cam_xuc(doan_van: str) -> dict:
phan_hoi = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content":
"Bạn là chuyên gia phân tích cảm xúc. Trả về JSON với "
"'cam_xuc' (tich_cuc|tieu_cuc|trung_tinh) và 'ly_do'."},
{"role": "user", "content": doan_van},
],
response_format={"type": "json_object"},
temperature=0,
)
import json
return json.loads(phan_hoi.choices[0].message.content)
print(phan_tich_cam_xuc("Sản phẩm giao nhanh, đóng gói cẩn thận. Rất hài lòng!"))
Các khái niệm LLM cần biết:
| Khái niệm | Ý nghĩa |
|---|---|
| Token | Đơn vị văn bản model xử lý (~0.75 từ tiếng Anh) |
| Embedding | Vector biểu diễn ngữ nghĩa |
| Context window | Số token tối đa model xử lý một lần |
| Temperature | 0 = ổn định, 1+ = sáng tạo/ngẫu nhiên |
| RAG | Truy xuất tài liệu liên quan rồi đưa vào prompt |
| Fine-tuning | Huấn luyện thêm trên dữ liệu riêng |
| Hallucination | Model bịa thông tin nghe hợp lý |
RAG — kỹ thuật quan trọng nhất khi làm AI application:
import numpy as np
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("keepitreal/vietnamese-sbert")
def tim_tai_lieu_lien_quan(cau_hoi: str, kho_tai_lieu: list[str], top_k: int = 3):
"""Tìm đoạn tài liệu liên quan nhất bằng độ tương đồng cosine."""
vector_kho = model.encode(kho_tai_lieu, normalize_embeddings=True)
vector_hoi = model.encode([cau_hoi], normalize_embeddings=True)[0]
diem = vector_kho @ vector_hoi # cosine đã chuẩn hoá
idx = np.argsort(diem)[::-1][:top_k]
return [(kho_tai_lieu[i], float(diem[i])) for i in idx]
kho = [
"Chính sách đổi trả: khách hàng được đổi trong 7 ngày.",
"Phí vận chuyển nội thành là 30.000 đồng.",
"Thời gian bảo hành sản phẩm điện tử là 12 tháng.",
]
for noi_dung, diem in tim_tai_lieu_lien_quan("Tôi muốn đổi hàng thì sao?", kho):
print(f"[{diem:.3f}] {noi_dung}")
🚀 Tuần 21: MLOps — đưa model vào sản phẩm
Đây là kỹ năng được trả lương cao nhất và cũng là thứ thiếu hụt nhất trên thị trường.
"""Đóng gói model thành API hoàn chỉnh."""
from contextlib import asynccontextmanager
from pathlib import Path
import joblib
import pandas as pd
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
class DuLieuVao(BaseModel):
dac_trung: list[float] = Field(min_length=20, max_length=20)
class KetQuaRa(BaseModel):
du_doan: int
xac_suat: float
phien_ban_model: str
@asynccontextmanager
async def vong_doi(app: FastAPI):
"""Load model một lần khi khởi động — không load mỗi request."""
duong_dan = Path("model/xgb_v1.2.0.joblib")
if not duong_dan.exists():
raise RuntimeError(f"Không tìm thấy model: {duong_dan}")
app.state.model = joblib.load(duong_dan)
app.state.phien_ban = duong_dan.stem.split("_")[-1]
print(f"✅ Đã load model phiên bản {app.state.phien_ban}")
yield
app.state.model = None
app = FastAPI(title="API Dự đoán", lifespan=vong_doi)
@app.get("/suc-khoe")
async def suc_khoe():
return {"trang_thai": "ok", "model": app.state.phien_ban}
@app.post("/du-doan", response_model=KetQuaRa)
async def du_doan(du_lieu: DuLieuVao):
try:
X = pd.DataFrame([du_lieu.dac_trung])
du_doan = int(app.state.model.predict(X)[0])
xac_suat = float(app.state.model.predict_proba(X)[0][1])
except Exception as e:
raise HTTPException(500, f"Lỗi suy luận: {e}") from e
return KetQuaRa(du_doan=du_doan, xac_suat=xac_suat,
phien_ban_model=app.state.phien_ban)
Bốn trụ cột MLOps cần biết:
| Trụ cột | Công cụ | Mục đích |
|---|---|---|
| Versioning | DVC, MLflow | Theo dõi dữ liệu và model theo phiên bản |
| Serving | FastAPI, TorchServe | Đưa model thành API |
| Monitoring | Evidently, Prometheus | Phát hiện model "xuống cấp" |
| Pipeline | Airflow, Prefect | Tự động hoá train → test → deploy |
Data drift — vấn đề mà người mới không biết:
Model train trên dữ liệu 2025 có thể sai dần vào 2026 vì hành vi người dùng thay đổi. Cần giám sát phân phối dữ liệu đầu vào:
from scipy.stats import ks_2samp
import numpy as np
def kiem_tra_drift(du_lieu_train: np.ndarray, du_lieu_hien_tai: np.ndarray,
nguong: float = 0.05) -> dict:
"""Kiểm định Kolmogorov-Smirnov cho từng đặc trưng."""
ket_qua = {}
for i in range(du_lieu_train.shape[1]):
_, p_value = ks_2samp(du_lieu_train[:, i], du_lieu_hien_tai[:, i])
ket_qua[f"dac_trung_{i}"] = {
"p_value": round(p_value, 4),
"co_drift": p_value < nguong,
}
so_drift = sum(1 for v in ket_qua.values() if v["co_drift"])
print(f"⚠️ {so_drift}/{len(ket_qua)} đặc trưng có drift")
return ket_qua
💼 Tuần 22–23: Dự án portfolio
Ba dự án nên làm (chọn 2):
| Dự án | Thể hiện được |
|---|---|
| Dự đoán giá/churn có deploy | Toàn bộ pipeline: EDA → model → API → deploy |
| Hệ thống RAG trả lời tài liệu nội bộ | LLM, embedding, vector DB, đánh giá |
| Pipeline MLOps có monitoring | Tự động train, versioning, phát hiện drift |
Cấu trúc repo chuẩn:
du-an/
├── README.md ← quan trọng nhất
├── requirements.txt
├── data/
│ ├── raw/ ← dữ liệu gốc, không sửa
│ └── processed/
├── notebooks/
│ ├── 01_eda.ipynb
│ └── 02_modeling.ipynb
├── src/
│ ├── data/ ← hàm load + làm sạch
│ ├── features/ ← tạo đặc trưng
│ ├── models/ ← train + predict
│ └── api/ ← FastAPI serving
├── tests/
├── models/ ← model đã train (dùng DVC nếu lớn)
├── Dockerfile
└── .github/workflows/ci.yml
Điều nhà tuyển dụng tìm kiếm:
- Tái lập được — có seed, có
requirements.txt, có hướng dẫn chạy. - Code sạch, không chỉ notebook — notebook để khám phá,
src/để sản phẩm. - Có deploy thật — link truy cập được, không chỉ screenshot.
- Hiểu hạn chế của model — bạn biết nó sai ở đâu.
- Có test — kể cả vài test đơn giản cho hàm tiền xử lý cũng gây ấn tượng.
🎤 Tuần 24: Phỏng vấn
Cấu trúc phỏng vấn Data Scientist / ML Engineer:
| Vòng | Nội dung | Trọng số |
|---|---|---|
| 1 | Sàng lọc, giới thiệu dự án | 10% |
| 2 | SQL + Python coding | 25% |
| 3 | ML theory (bias-variance, regularization, metrics) | 25% |
| 4 | ML system design | 25% |
| 5 | Behavior + culture | 15% |
Câu hỏi lý thuyết hay gặp:
- Bias-variance tradeoff — giải thích và cách xử lý từng phía.
- L1 và L2 regularization khác nhau thế nào? Khi nào dùng cái nào?
- Vì sao Random Forest giảm overfitting so với một cây đơn lẻ?
- Gradient boosting hoạt động thế nào? Khác Random Forest ở đâu?
- Xử lý dữ liệu mất cân bằng — kể 4 cách và ưu nhược điểm.
- Cross-validation dùng thế nào cho dữ liệu chuỗi thời gian?
- Precision và recall — khi nào ưu tiên cái nào? Cho ví dụ thực tế.
- Data leakage là gì? Cho 3 ví dụ tinh vi.
Câu hỏi ML system design (quan trọng nhất, chiếm 25%):
"Thiết kế hệ thống phát hiện gian lận giao dịch thẻ tín dụng cho ngân hàng có 10 triệu giao dịch/ngày."
Khung trả lời:
1. LÀM RÕ YÊU CẦU
- Độ trễ cho phép? (gợi ý: < 100ms cho giao dịch online)
- Chi phí false positive vs false negative?
- Có nhãn không? Bao lâu có nhãn? (chargeback mất 30-60 ngày)
2. DỮ LIỆU
- Nguồn: giao dịch, thiết bị, lịch sử khách hàng, vị trí
- Mất cân bằng cực nặng (~0,1% gian lận)
- Nhãn đến muộn → cần chiến lược cập nhật
3. ĐẶC TRƯNG
- Tần suất giao dịch 1h/24h/7d
- Lệch giữa vị trí giao dịch và vị trí thường ngày
- Số merchant khác nhau trong 24h
- Thời gian từ giao dịch trước
4. MODEL
- Baseline: rule-based + ngưỡng
- Chính: LightGBM (nhanh, xử lý mất cân bằng tốt, giải thích được)
- Metric: PR-AUC + recall@precision=0.9
5. TRIỂN KHAI
- Serving online < 100ms: model nhỏ, feature store
- Batch scoring cho phân tích hậu kiểm
- Fallback: nếu model lỗi → dùng rule cũ
6. GIÁM SÁT
- Data drift, model drift
- Tỷ lệ false positive theo thời gian (đo mức làm phiền khách)
- Retrain hàng tuần khi có nhãn mới
7. ĐÁNH ĐỔI
- Chặn nhiều → giảm gian lận nhưng tăng khách bực bội
- Cần quy trình review thủ công cho giao dịch nghi ngờ
🧪 Đánh giá model đúng cách
Phần lớn người học dành 90% thời gian để tăng accuracy và 10% để kiểm tra xem con số đó có đáng tin không. Tỷ lệ này nên đảo ngược.
Chiến lược chia dữ liệu phải khớp với thực tế
from sklearn.model_selection import (train_test_split, KFold, GroupKFold,
TimeSeriesSplit, LeaveOneGroupOut)
# --- Dữ liệu độc lập, phân phối giống nhau ---
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.2,
random_state=42, stratify=y)
# --- Dữ liệu chuỗi thời gian: KHÔNG được chia ngẫu nhiên ---
# Chia ngẫu nhiên cho model "thấy tương lai" → kết quả đẹp giả tạo
tscv = TimeSeriesSplit(n_splits=5)
for train_idx, val_idx in tscv.split(X):
...
# --- Dữ liệu nhóm: cùng một khách hàng không được xuất hiện ở cả train và test ---
gkf = GroupKFold(n_splits=5)
for train_idx, val_idx in gkf.split(X, y, groups=df["khach_hang_id"]):
...
# --- Leave-one-group-out: kiểm tra khả năng tổng quát hoá sang nhóm mới ---
logo = LeaveOneGroupOut()
for train_idx, val_idx in logo.split(X, y, groups=df["khu_vuc"]):
...
⚠️ Ví dụ điển hình của chia sai: bài toán dự đoán khách hàng rời bỏ dịch vụ. Nếu chia ngẫu nhiên theo dòng, cùng một khách hàng có thể nằm cả ở train và test. Model chỉ cần "nhớ" khách hàng thay vì học pattern → accuracy 0,95 khi validation, 0,65 khi thực tế.
Chọn metric khớp với chi phí nghiệp vụ
| Tình huống | Metric nên dùng | Vì sao |
|---|---|---|
| Dữ liệu cân bằng, sai sót ngang nhau | Accuracy, F1 | Đơn giản, dễ hiểu |
| Mất cân bằng nhẹ (1:10) | F1, ROC-AUC | Accuracy bắt đầu gây hiểu nhầm |
| Mất cân bằng nặng (1:1000) | PR-AUC, Recall@Precision | ROC-AUC bị lạc quan quá mức |
| Chi phí false positive cao | Precision | Ví dụ: tự động chặn tài khoản |
| Chi phí false negative cao | Recall | Ví dụ: tầm soát bệnh, phát hiện gian lận |
| Cần xếp hạng, không cần ngưỡng | ROC-AUC, NDCG | Gợi ý sản phẩm, tìm kiếm |
| Kết quả cần xác suất đáng tin | Log loss, Brier score, calibration | Dự đoán rủi ro, định giá |
from sklearn.metrics import (average_precision_score, precision_recall_curve,
brier_score_loss, log_loss, roc_curve, auc)
import numpy as np
ap = average_precision_score(y_test, y_xac_suat) # PR-AUC
brier = brier_score_loss(y_test, y_xac_suat) # hiệu chuẩn
ll = log_loss(y_test, y_xac_suat)
print(f"PR-AUC: {ap:.4f} | Brier: {brier:.4f} | LogLoss: {ll:.4f}")
# Chọn ngưỡng theo mục tiêu nghiệp vụ, KHÔNG dùng 0.5 mặc định
precision, recall, nguong = precision_recall_curve(y_test, y_xac_suat)
# Ví dụ: cần precision >= 0.90, tìm recall tốt nhất có thể
idx = np.where(precision[:-1] >= 0.90)[0]
if len(idx):
i = idx[np.argmax(recall[i])]
print(f"Ngưỡng {nguong[i]:.3f} → precision {precision[i]:.3f}, recall {recall[i]:.3f}")
Ngưỡng 0,5 là mặc định của thư viện, không phải lựa chọn có căn cứ. Với bài toán gian lận, bạn cần ngưỡng cao hơn nhiều để giảm false positive.
Calibration — xác suất có đáng tin không?
from sklearn.calibration import calibration_curve, CalibratedClassifierCV
import matplotlib.pyplot as plt
xac_suat_thuc, xac_suat_du_doan = calibration_curve(y_test, y_xac_suat, n_bins=10)
plt.figure(figsize=(7, 7))
plt.plot([0, 1], [0, 1], "k--", label="Hoàn hảo")
plt.plot(xac_suat_du_doan, xac_suat_thuc, "o-", label="Model")
plt.xlabel("Xác suất dự đoán")
plt.ylabel("Tỷ lệ dương thực tế")
plt.title("Biểu đồ hiệu chuẩn")
plt.legend()
plt.show()
# Hiệu chỉnh nếu model tự tin quá mức
model_hieu_chuan = CalibratedClassifierCV(model, method="isotonic", cv=5)
model_hieu_chuan.fit(X_train, y_train)
Vì sao quan trọng: nhãn "khách hàng này có 70% khả năng mua" chỉ có ích nếu 70% số khách hàng như vậy thực sự mua. Nếu chỉ 35% mua, mọi quyết định dựa trên con số đó đều sai.
Error analysis — bước mà hầu hết mọi người bỏ qua
Đừng dừng ở việc biết accuracy. Hãy đọc từng dự đoán sai:
import pandas as pd
# Xây bảng dự đoán sai kèm thông tin ngữ cảnh
danh_gia = X_test.copy()
danh_gia["thuc_te"] = y_test.values
danh_gia["du_doan"] = y_du_doan
danh_gia["xac_suat"] = y_xac_suat
sai = danh_gia[danh_gia["thuc_te"] != danh_gia["du_doan"]]
# Phân tích lỗi theo từng nhóm
print(sai.groupby("khu_vuc").size().sort_values(ascending=False).head())
print(sai.groupby("nhom_tuoi").size().sort_values(ascending=False).head())
# Lỗi "tự tin nhưng sai" — nguy hiểm nhất
tu_tin_sai = sai[sai["xac_suat"].between(0.85, 0.999)]
print(f"Số dự đoán tự tin nhưng sai: {len(tu_tin_sai)} "
f"({len(tu_tin_sai)/len(sai):.1%} tổng số lỗi)")
# Xem vài ví dụ cụ thể để hiểu nguyên nhân
print(tu_tin_sai[['khu_vuc', 'nhom_tuoi', 'xac_suat']].head(10))
Câu hỏi cần trả lời sau error analysis:
- Lỗi tập trung ở một nhóm cụ thể không? (nếu có → model có định kiến)
- Có lỗi "tự tin nhưng sai" không? (nguy hiểm nhất trong thực tế)
- Lỗi có liên quan đến dữ liệu thiếu hoặc ngoài phân phối không?
- Nếu chỉ cải thiện được một loại lỗi, loại nào đáng cải thiện nhất?
Với bài toán phân loại, ma trận nhầm lẫn theo từng nhóm thường tiết lộ vấn đề mà metric tổng thể che mất.
📖 Cách tự học và cập nhật kiến thức
Lĩnh vực này thay đổi nhanh đến mức khoá học bạn mua hôm nay sẽ lỗi thời sau 18 tháng. Kỹ năng quan trọng nhất là tự học có hệ thống.
Bốn nguồn học chất lượng nhất
| Nguồn | Dùng để | Ghi chú |
|---|---|---|
| Paper gốc | Hiểu thuật toán từ nguồn | arXiv, paperswithcode |
| Documentation chính thức | Dùng đúng API | Luôn cập nhật nhất |
| Source code | Hiểu cơ chế bên trong | scikit-learn, PyTorch |
| Blog/khảo sát chất lượng | Nắm bức tranh tổng thể | Distill, Lil'Log, Kaggle |
Cách đọc một paper ML
Đừng đọc từ đầu đến cuối. Đọc theo thứ tự này:
1. Tiêu đề + Abstract (1 phút) — bài này giải quyết vấn đề gì?
2. Hình 1 + bảng 1 (3 phút) — trực giác cốt lõi là gì?
3. Kết luận (2 phút) — kết quả có gì đáng chú ý?
4. Method (kỹ) (20 phút) — cách làm cụ thể
5. Thí nghiệm + bảng số liệu (15 phút) — có gì đáng nghi ngờ không?
6. Related work (5 phút) — đặt trong bối cảnh nào
Ba câu hỏi phải tự trả lời sau khi đọc:
- Ý tưởng cốt lõi, giải thích trong một câu.
- So với phương pháp trước đó, cải thiện ở điểm nào?
- Có dùng được cho bài toán của mình không, và cần điều kiện gì?
Duy trì kỹ năng code song song với kỹ năng mô hình
# Mỗi tháng: đọc source của một thư viện bạn dùng hàng ngày
python -c "import sklearn; print(sklearn.__file__)"
Mở source sklearn/linear_model/_logistic.py và đọc cách họ tổ chức code, cách họ xử lý tham số, cách họ viết docstring. Đây là cách học nhanh nhất từ người giỏi mà hầu như không ai làm.
Lộ trình tự học 12 tháng song song công việc
| Tháng | Hoạt động | Sản phẩm |
|---|---|---|
| 1–2 | Đọc 1 paper/tuần + tóm tắt | 8 bài tóm tắt |
| 3–4 | Tái lập 1 paper đơn giản | Repo GitHub |
| 5–6 | Tham gia 1 competition Kaggle | Xếp hạng + notebook |
| 7–8 | Đóng góp cho 1 thư viện mã nguồn mở | 1 PR được merge |
| 9–10 | Viết 3 bài blog kỹ thuật | Blog cá nhân |
| 11–12 | Xây 1 dự án end-to-end có deploy | Portfolio |
Viết lại là cách học hiệu quả nhất. Khi bạn cố giải thích một thuật toán cho người khác, bạn phát hiện ra mình chưa hiểu chỗ nào. Đó chính là giá trị.
⚠️ 7 sai lầm khiến bạn học 1 năm mà không xin được việc
- Chỉ học model, không học làm sạch dữ liệu. 80% công việc thật là xử lý dữ liệu.
- Không biết SQL. Mọi vòng phỏng vấn đều có SQL.
- Chỉ làm Kaggle, không deploy. Model không deploy = dự án chưa xong.
- Học deep learning trước machine learning cổ điển. Như học chạy trước khi biết đi.
- Không có baseline. Không biết model của mình tốt hay chỉ ngang đoán bừa.
- Không hiểu hạn chế của model. Nhà tuyển dụng hỏi câu này để kiểm tra bạn có thật sự làm hay copy.
- Bỏ qua kỹ năng kỹ thuật phần mềm. Git, Docker, testing — không có thì không qua vòng kỹ thuật.
❓ Câu hỏi thường gặp
Cần bằng thạc sĩ/tiến sĩ không? Cho vị trí ML Engineer và AI Application Developer: không cần. Cho Research Scientist: thường cần. Đa số việc làm ở Việt Nam không yêu cầu bằng cao học.
Không giỏi toán thì có học được không? Được — cho vị trí Data Analyst, ML Engineer, AI Application Developer. Không được — cho Research Scientist. Bạn cần đại số tuyến tính và xác suất cơ bản ở mọi vị trí, nhưng không cần toán chuyên sâu.
Học Data Science bao lâu thì có việc? Nếu đã biết lập trình và SQL: 6–9 tháng. Nếu từ con số 0: 12–18 tháng. Bất kỳ ai hứa "3 tháng thành Data Scientist lương cao" đều đang bán khoá học, không bán sự thật.
AI có thay thế Data Scientist không? Thay thế phần viết code lặp lại và làm EDA cơ bản. Không thay thế phần hiểu vấn đề, chọn metric đúng, phán đoán về dữ liệu và đánh đổi nghiệp vụ.
Nên học TensorFlow hay PyTorch? PyTorch. Nó chiếm ưu thế trong nghiên cứu và ngày càng phổ biến trong công nghiệp. TensorFlow vẫn có chỗ đứng ở production mobile/embedded nhưng PyTorch là lựa chọn an toàn hơn để học.
Có nên học LLM trước Machine Learning truyền thống không? Không. Học ML cổ điển trước để hiểu overfitting, validation, metric, và đánh đổi. Những khái niệm này áp dụng nguyên vẹn cho LLM.
🎯 Tóm lại
4 tuần → Python: numpy, pandas
3 tuần → Toán: đại số tuyến tính, xác suất, đạo hàm
3 tuần → EDA và trực quan hoá
5 tuần → Machine Learning với scikit-learn ← phần quan trọng nhất
4 tuần → Deep Learning với PyTorch
1 tuần → NLP và LLM cơ bản
1 tuần → MLOps: đóng gói và deploy
2 tuần → Dự án portfolio
1 tuần → Phỏng vấn
Sự thật cuối cùng: người có việc làm trong ngành này không phải người biết nhiều thuật toán nhất, mà là người có thể đưa một model từ notebook ra production và chứng minh nó tạo ra giá trị. Hãy xây dựng theo hướng đó ngay từ dự án đầu tiên.