Mã nguồn thí nghiệm
Mọi hình và mọi con số thực nghiệm trong giáo trình đều do các script dưới đây sinh ra. Chúng được chép nguyên vẹn vào site nên tải về là chạy được ngay.
Cách chạy lại toàn bộ#
Hai script dưới đây sinh ra toàn bộ 13 hình và mọi con số đo được của giáo trình. Chúng đặt hạt giống cố định nên chạy lại cho kết quả giống hệt.
pip install numpy scipy matplotlib scikit-learn
python code/nentang/experiments.py # Hình 2, 3, 4, 6–12, vài phút
python code/nentang/fig_diagrams.py # Hình 1, 5, 13
Trên Windows, nếu ghi kết quả ra tệp, đặt PYTHONIOENCODING=utf-8 để các dòng chữ tiếng Việt không gây lỗi mã hoá.
Môi trường đã dùng để sinh số liệu: Python 3.13, NumPy 2.3, SciPy 1.16, scikit-learn 1.7, matplotlib 3.10.
Số liệu của giáo trình gồm hai loại, liệt kê đầy đủ ở Phụ lục.
Loại thứ nhất là kiểm chứng đẳng thức: hồi quy logistic trùng hồi quy softmax với K = 2, PCA qua ma trận hiệp phương sai trùng PCA qua SVD, sai số tái tạo bằng tổng các trị riêng bị bỏ, ridge trùng MAP với tiên nghiệm Gauss, và các khẳng định về đối ngẫu và KKT của SVM. Những kết quả này phải khớp tới sai số của máy tính: sáu trong mười đẳng thức khớp ở mức 10⁻¹⁵, bốn đẳng thức còn lại bị giới hạn bởi dung sai của bộ giải. Nếu chúng không khớp thì mã có lỗi.
Loại thứ hai là mô phỏng trên dữ liệu sinh ngẫu nhiên, mỗi thí nghiệm cô lập một cơ chế. Chúng cho thấy cơ chế tồn tại và có độ lớn đáng kể, nhưng không dùng để suy ra con số cho một tập dữ liệu thật cụ thể.
Từng script#
code/nentang/experiments.py
viết thêm cho repo nàyTải vềSinh Hình 2, 3, 4, 6, 7, 8, 9, 10, 11, 12 và mọi số liệu đo được: nghiệm bình phương tối thiểu và đa cộng tuyến, tốc độ hội tụ theo số điều kiện, bước sai phân tối ưu khi kiểm tra gradient, chặn Novikoff, đẳng thức logistic và softmax, quan hệ Naive Bayes, LDA và QDA, các thước đo trên dữ liệu mất cân bằng, ridge và MAP với tiên nghiệm Gauss, kiểm tra tính lồi, đối ngẫu và KKT của SVM, định lý Eckart–Young, K-means, và lượng dữ liệu cần cho phân rã ma trận.
Xem mã nguồn
"""Thí nghiệm sinh số liệu và hình cho giáo trình "Nền tảng Machine Learning".
Mọi con số thực nghiệm trong tài liệu đều in ra từ script này. Hạt giống cố định
nên chạy lại cho kết quả y hệt. Chỉ cần NumPy, SciPy, scikit-learn, matplotlib.
python code/nentang/experiments.py
Nhiều khối ở đây không phải mô phỏng mà là **kiểm chứng một đẳng thức**: ridge
đúng bằng MAP với tiên nghiệm Gauss, Naive Bayes Gauss dùng chung ma trận hiệp
phương sai đúng bằng LDA, sai số tái dựng của PCA đúng bằng tổng các trị riêng bị
bỏ. Những khối ấy phải khớp tới sai số máy, không phải khớp "xấp xỉ".
"""
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
OUT = "figs/"
rng = np.random.default_rng(11)
plt.rcParams.update({
"figure.dpi": 150, "font.size": 9, "axes.grid": True,
"grid.alpha": .25, "axes.spines.top": False, "axes.spines.right": False,
})
C_MAIN, C_WARN, C_BAD, C_DIM = "#1f6f68", "#b8860b", "#b0413e", "#8a857c"
def head(t):
print("\n" + "=" * 74 + "\n" + t + "\n" + "=" * 74)
# =====================================================================
# (A) Hình 2 — Hồi quy tuyến tính: nghiệm giải tích, và cái giá của cộng tuyến
# =====================================================================
head("(A) Hoi quy tuyen tinh: nghiem giai tich va dieu kien so")
n, d = 200, 3
Xr = rng.normal(size=(n, d))
X = np.c_[np.ones(n), Xr]
w_true = np.array([2.0, -1.5, 0.8, 3.0])
y = X @ w_true + rng.normal(0, 0.5, n)
w_normal = np.linalg.solve(X.T @ X, X.T @ y)
w_pinv = np.linalg.pinv(X) @ y
w_lstsq = np.linalg.lstsq(X, y, rcond=None)[0]
print(f"{'cách giải':<34}{'w0':>9}{'w1':>9}{'w2':>9}{'w3':>9}")
for lab, w in [("phương trình chuẩn tắc", w_normal),
("giả nghịch đảo Moore-Penrose", w_pinv),
("lstsq (phân rã QR)", w_lstsq)]:
print(f"{lab:<34}" + "".join(f"{v:9.4f}" for v in w))
print(f"lech giua 3 cach giai: {np.abs(w_normal - w_pinv).max():.2e} / "
f"{np.abs(w_normal - w_lstsq).max():.2e} -> cung mot nghiem")
# Truong hop X^T X suy bien: mot cot bi lap lai.
Xs = np.c_[X, X[:, 1]]
gram = Xs.T @ Xs
print(f"\nKhi lap lai mot cot: hang cua X = {np.linalg.matrix_rank(Xs)} "
f"nhung X co {Xs.shape[1]} cot -> X^T X suy bien")
try:
np.linalg.solve(gram, Xs.T @ y)
print(" solve: khong bao loi (khong nen xay ra)")
except np.linalg.LinAlgError:
print(" solve: BAO LOI 'Singular matrix'")
w_ps = np.linalg.pinv(Xs) @ y
resid = np.linalg.norm(Xs @ w_ps - y)
w_alt = w_ps + np.array([0, 1.0, 0, 0, -1.0]) # cung du bao, khac he so
print(f" pinv: van cho mot nghiem, chuan ||w|| = {np.linalg.norm(w_ps):.4f}")
print(f" mot nghiem khac cung du bao y het: ||w|| = {np.linalg.norm(w_alt):.4f} "
f"(sai so du bao lech {abs(np.linalg.norm(Xs @ w_alt - y) - resid):.2e})")
print(" -> pinv chon nghiem CHUAN NHO NHAT trong vo so nghiem")
# Cong tuyen: he so dao lon, nhung du bao thi khong.
print(f"\n{'tuong quan 2 cot':>18}{'cond(X^T X)':>14}{'do lech chuan w1':>19}"
f"{'do lech chuan du bao':>22}")
Arows = []
for rho in [0.0, 0.9, 0.99, 0.999]:
z1 = rng.normal(size=n)
z2 = rho * z1 + np.sqrt(max(1 - rho ** 2, 1e-12)) * rng.normal(size=n)
Xc = np.c_[np.ones(n), z1, z2]
wt = np.array([1.0, 2.0, -1.0])
ws, preds = [], []
for s in range(300):
rr = np.random.default_rng(5000 + s)
yy = Xc @ wt + rr.normal(0, 0.5, n)
ws.append(np.linalg.pinv(Xc) @ yy)
preds.append(Xc @ ws[-1])
ws, preds = np.array(ws), np.array(preds)
cond = np.linalg.cond(Xc.T @ Xc)
sd_w, sd_p = ws[:, 1].std(), preds.std(axis=0).mean()
Arows.append((rho, cond, sd_w, sd_p))
print(f"{rho:18.3f}{cond:14.1f}{sd_w:19.4f}{sd_p:22.4f}")
print("Doc bang: cong tuyen lam HE SO dao manh (cot 3) nhung DU BAO gan nhu khong doi")
print("(cot 4). Vi vay cong tuyen la van de cua DIEN GIAI, khong phai cua du bao.")
fig, ax = plt.subplots(1, 2, figsize=(8.4, 3.0))
rhos = [a[0] for a in Arows]
ax[0].plot(rhos, [a[1] for a in Arows], "-o", ms=5, color=C_BAD)
ax[0].set_yscale("log"); ax[0].set_xlabel("tương quan giữa hai cột")
ax[0].set_ylabel("$\\mathrm{cond}(X^\\top X)$ (log)")
ax[0].set_title("Số điều kiện theo mức tương quan")
ax[1].plot(rhos, [a[2] for a in Arows], "-o", ms=5, color=C_BAD, label="độ lệch chuẩn của $w_1$")
ax[1].plot(rhos, [a[3] for a in Arows], "-o", ms=5, color=C_MAIN, label="độ lệch chuẩn của dự đoán")
ax[1].set_yscale("log"); ax[1].set_xlabel("tương quan giữa hai cột")
ax[1].set_ylabel("độ lệch chuẩn (log)"); ax[1].legend(frameon=False, fontsize=8)
ax[1].set_title("Độ lệch chuẩn của hệ số và của dự đoán")
fig.tight_layout(); fig.savefig(OUT + "nt02_linreg.png"); plt.close(fig)
# =====================================================================
# (B) Hình 3 — Gradient descent: tốc độ hội tụ phụ thuộc số điều kiện
# =====================================================================
head("(B) Gradient descent tren ham bac hai: ly thuyet doi chieu voi do duoc")
def run_gd(kappa, mode, tol=1e-8, itmax=200000):
"""Toi thieu f(x) = 0.5*(x0^2 + kappa*x1^2), nghiem la goc toa do."""
L, m = float(kappa), 1.0
if mode == "gd":
eta, beta = 2.0 / (L + m), 0.0
else: # heavy ball voi tham so toi uu
eta = 4.0 / (np.sqrt(L) + np.sqrt(m)) ** 2
beta = ((np.sqrt(L) - np.sqrt(m)) / (np.sqrt(L) + np.sqrt(m))) ** 2
A = np.array([1.0, float(kappa)])
x = np.array([1.0, 1.0]); v = np.zeros(2)
x0n = np.linalg.norm(x)
for k in range(1, itmax + 1):
g = A * x
v = beta * v - eta * g
x = x + v
if np.linalg.norm(x) / x0n < tol:
return k
return itmax
print(f"{'kappa':>8}{'GD (số vòng)':>15}{'heavy ball':>13}{'tỉ lệ':>9}{'căn kappa':>12}")
Brows = []
for kap in [1, 10, 100, 1000, 10000]:
a, b = run_gd(kap, "gd"), run_gd(kap, "hb")
Brows.append((kap, a, b))
print(f"{kap:8d}{a:15d}{b:13d}{a / max(b, 1):9.2f}{np.sqrt(kap):12.2f}")
print("Ly thuyet: GD can O(kappa) vong, heavy ball can O(sqrt(kappa)) vong.")
print("Cot 'ti le' bam sat cot 'can kappa' -> dung nhu ly thuyet du doan.")
fig, ax = plt.subplots(figsize=(5.6, 3.1))
ks = [b[0] for b in Brows]
ax.plot(ks, [b[1] for b in Brows], "-o", ms=5, color=C_BAD, label="gradient descent")
ax.plot(ks, [b[2] for b in Brows], "-o", ms=5, color=C_MAIN, label="có momentum")
ax.plot(ks, ks, ":", color=C_DIM, lw=1.2, label="$O(\\kappa)$")
ax.plot(ks, np.sqrt(ks) * 3, "--", color=C_DIM, lw=1.2, label="$O(\\sqrt{\\kappa})$")
ax.set_xscale("log"); ax.set_yscale("log")
ax.set_xlabel("số điều kiện $\\kappa$"); ax.set_ylabel("số vòng lặp tới sai số $10^{-8}$")
ax.set_title("Số vòng lặp theo số điều kiện")
ax.legend(frameon=False, fontsize=8)
fig.tight_layout(); fig.savefig(OUT + "nt03_gd.png"); plt.close(fig)
# =====================================================================
# (C) Hình 4 — Kiểm tra đạo hàm: vì sao epsilon quá nhỏ lại tệ hơn
# =====================================================================
head("(C) Kiem tra dao ham bang sai phan: diem toi uu cua epsilon")
f = lambda x: np.sin(x) * np.exp(0.3 * x)
df = lambda x: np.cos(x) * np.exp(0.3 * x) + 0.3 * np.sin(x) * np.exp(0.3 * x)
# Lay trung binh tren nhieu diem x0: neu chi dung mot diem thi sai so cat cut va
# sai so lam tron doi khi TRIET TIEU nhau ngau nhien, tao ra mot day gia tao rat sau.
x0s = np.linspace(0.7, 2.1, 25)
eps_grid = np.logspace(-1, -14, 53)
err_c = np.array([np.mean([abs((f(x0 + e) - f(x0 - e)) / (2 * e) - df(x0)) for x0 in x0s])
for e in eps_grid])
err_f = np.array([np.mean([abs((f(x0 + e) - f(x0)) / e - df(x0)) for x0 in x0s])
for e in eps_grid])
i_best = int(np.argmin(err_c))
u = np.finfo(float).eps
print(f"{'epsilon':>12}{'sai phân trung tâm':>22}{'sai phân tiến':>18}")
for i in range(0, len(eps_grid), 6):
print(f"{eps_grid[i]:12.1e}{err_c[i]:22.3e}{err_f[i]:18.3e}")
print(f"\nSai so nho nhat cua sai phan trung tam tai epsilon = {eps_grid[i_best]:.2e} "
f"(sai so {err_c[i_best]:.2e})")
print(f"Ly thuyet: epsilon* ~ u^(1/3) = {u ** (1 / 3):.2e} (u = {u:.2e})")
print(f"Voi sai phan tien thi epsilon* ~ u^(1/2) = {u ** 0.5:.2e}, "
f"do duoc {eps_grid[int(np.argmin(err_f))]:.2e}")
print("Epsilon NHO QUA thi sai so LON LEN: tru hai so gan bang nhau lam mat chu so co nghia.")
fig, ax = plt.subplots(figsize=(5.8, 3.2))
ax.loglog(eps_grid, np.maximum(err_c, 1e-18), lw=1.8, color=C_MAIN, label="sai phân trung tâm")
ax.loglog(eps_grid, np.maximum(err_f, 1e-18), lw=1.8, color=C_WARN, label="sai phân tiến")
ax.axvline(u ** (1 / 3), color=C_DIM, ls=":", lw=1.2)
ax.text(u ** (1 / 3) * 1.3, 1e-4, "$u^{1/3}$", fontsize=8, color=C_DIM)
ax.set_xlabel("$\\varepsilon$"); ax.set_ylabel("sai số tuyệt đối")
ax.invert_xaxis(); ax.legend(frameon=False, fontsize=8)
ax.set_title("Sai số cắt cụt và sai số làm tròn")
fig.tight_layout(); fig.savefig(OUT + "nt04_gradcheck.png"); plt.close(fig)
# =====================================================================
# (D) Perceptron: chặn số lần sai, và chuyện gì xảy ra khi không tách được
# =====================================================================
head("(D) Perceptron: chan Novikoff va truong hop khong tach duoc tuyen tinh")
def make_separable(n, gamma, seed):
r = np.random.default_rng(seed)
w_star = np.array([1.0, -2.0]); w_star /= np.linalg.norm(w_star)
pts, lab = [], []
while len(pts) < n:
x = r.uniform(-1, 1, 2)
s = x @ w_star
# Giu cac diem nam trong dai sat le: le that su cua tap bang dung gamma,
# va phan lon diem deu kho, nen chan Novikoff moi noi len dieu gi do.
if gamma <= abs(s) <= gamma + 0.05:
pts.append(x); lab.append(1 if s > 0 else -1)
return np.array(pts), np.array(lab)
def perceptron(Xp, yp, itmax=100000):
w = np.zeros(Xp.shape[1]); mistakes = 0
for _ in range(itmax):
err = 0
for i in range(len(Xp)):
if yp[i] * (w @ Xp[i]) <= 0:
w += yp[i] * Xp[i]; mistakes += 1; err += 1
if err == 0:
return w, mistakes, True
return w, mistakes, False
print(f"{'lề gamma':>10}{'R = max||x||':>14}{'chặn (R/gamma)^2':>19}"
f"{'số lần sai đo được':>21}{'hội tụ':>9}")
for gamma in [0.30, 0.20, 0.10, 0.05]:
Xp, yp = make_separable(200, gamma, 42)
R = np.linalg.norm(Xp, axis=1).max()
gam_thuc = np.abs(Xp @ (np.array([1.0, -2.0]) / np.sqrt(5))).min()
_, mis, ok = perceptron(Xp, yp)
print(f"{gam_thuc:10.3f}{R:14.4f}{(R / gam_thuc) ** 2:19.1f}{mis:21d}{str(ok):>9}")
print("Dinh ly Novikoff: so lan sai <= (R/gamma)^2, khong phu thuoc so diem hay so chieu.")
print("Bang tren: cot 4 luon nho hon cot 3 -> chan dung, va no khong chat.")
r = np.random.default_rng(3)
Xn = r.normal(size=(60, 2)); yn = np.where(Xn[:, 0] + Xn[:, 1] > 0, 1, -1)
yn[:6] *= -1 # lam nhieu nhan -> khong tach duoc
_, mis_n, ok_n = perceptron(Xn, yn, itmax=2000)
print(f"\nDu lieu KHONG tach duoc tuyen tinh: hoi tu = {ok_n}, "
f"so lan sai sau 2000 luot = {mis_n:,}")
print("Perceptron khong dung lai va khong co bao dam gi: no chi duoc chung minh cho")
print("truong hop tach duoc. Day la ly do can ham mat mat tron nhu hoi quy logistic.")
# =====================================================================
# (E) Hồi quy logistic: quan hệ với softmax, và vì sao dữ liệu tách được lại hỏng
# =====================================================================
head("(E) Hoi quy logistic va softmax: hai ten goi cua cung mot thu khi K = 2")
def sigmoid(z):
return 1.0 / (1.0 + np.exp(-z))
def fit_logistic(Xl, yl, lam=0.0, iters=4000, lr=0.5):
"""Nhi phan: mot vector trong so w, xac suat = sigmoid(Xw)."""
w = np.zeros(Xl.shape[1])
for _ in range(iters):
p = sigmoid(Xl @ w)
g = Xl.T @ (p - yl) / len(yl) + lam * w
w -= lr * g
return w
def fit_softmax(Xs_, ys_, K, lam=0.0, iters=4000, lr=0.5):
"""Da lop: ma tran trong so W (d x K), xac suat = softmax(XW)."""
W = np.zeros((Xs_.shape[1], K))
Y = np.eye(K)[ys_]
for _ in range(iters):
Z = Xs_ @ W
Z -= Z.max(axis=1, keepdims=True)
P = np.exp(Z); P /= P.sum(axis=1, keepdims=True)
G = Xs_.T @ (P - Y) / len(ys_) + lam * W
W -= lr * G
return W
r = np.random.default_rng(21)
Xe = np.c_[np.ones(300), r.normal(size=(300, 2))]
w_gen = np.array([0.3, 1.5, -2.0])
ye = (r.uniform(size=300) < sigmoid(Xe @ w_gen)).astype(int)
# Khong phat chuan: khi ay hai bai toan toi uu la MOT, nen nghiem phai trung
# khop toi sai so may chu khong phai "gan giong".
w_log = fit_logistic(Xe, ye.astype(float), lam=0.0, iters=60000, lr=1.0)
W_soft = fit_softmax(Xe, ye, 2, lam=0.0, iters=60000, lr=1.0)
p_log = sigmoid(Xe @ w_log)
Z = Xe @ W_soft; Z -= Z.max(axis=1, keepdims=True)
P = np.exp(Z); P /= P.sum(axis=1, keepdims=True)
print(f"Sai khac lon nhat giua xac suat cua logistic va cua softmax(K=2): "
f"{np.abs(p_log - P[:, 1]).max():.3e}")
print(f"Hieu hai cot trong so cua softmax: {(W_soft[:, 1] - W_soft[:, 0])}")
print(f"Trong so cua hoi quy logistic: {w_log}")
print(f" -> lech {np.abs((W_soft[:, 1] - W_soft[:, 0]) - w_log).max():.3e}")
print(f"Tong hai cot trong so cua softmax (phai bang 0): "
f"{np.abs(W_soft.sum(axis=1)).max():.3e}")
print("Softmax co K vector trong so nhung chi HIEU cua chung la xac dinh duoc:")
print("cong cung mot vector vao moi cot thi xac suat khong doi. Voi K = 2, hieu")
print("ay chinh la vector trong so cua hoi quy logistic -> hai mo hinh la MOT.")
# Du lieu tach duoc hoan toan: khong phat chuan thi ||w|| chay ra vo cuc.
Xsep = np.c_[np.ones(60), r.normal(size=(60, 2))]
ysep = (Xsep[:, 1] + Xsep[:, 2] > 0).astype(float)
print(f"\n{'số vòng lặp':>14}{'||w|| khi lam = 0':>22}{'||w|| khi lam = 0,01':>24}")
for it in [500, 2000, 10000, 50000]:
a = np.linalg.norm(fit_logistic(Xsep, ysep, lam=0.0, iters=it, lr=1.0))
b = np.linalg.norm(fit_logistic(Xsep, ysep, lam=0.01, iters=it, lr=1.0))
print(f"{it:14d}{a:22.3f}{b:24.3f}")
print("Du lieu tach duoc thi hop ly cuc dai KHONG co nghiem huu han: cang tang ||w||")
print("thi likelihood cang tang. Phat chuan chan dieu do lai -> day la ly do BAT BUOC.")
# =====================================================================
# (F) Naive Bayes, LDA, QDA: ba mo hinh Gauss khac nhau o dung mot cho
# =====================================================================
head("(F) Naive Bayes / LDA / QDA: khac nhau o gia thiet ve ma tran hiep phuong sai")
def gauss_disc(Xtr, ytr, Xte, shared, diag):
"""Bo phan lop sinh Gauss. shared: dung chung Sigma; diag: ep Sigma cheo."""
Ks = np.unique(ytr)
prior = np.array([(ytr == k).mean() for k in Ks])
mus = np.array([Xtr[ytr == k].mean(axis=0) for k in Ks])
p = Xtr.shape[1]
if shared:
S = np.zeros((p, p))
for i, k in enumerate(Ks):
D = Xtr[ytr == k] - mus[i]
S += D.T @ D
S /= (len(Xtr) - len(Ks))
if diag:
S = np.diag(np.diag(S))
Sig = [S] * len(Ks)
else:
Sig = []
for i, k in enumerate(Ks):
D = Xtr[ytr == k] - mus[i]
S = D.T @ D / (len(D) - 1)
Sig.append(np.diag(np.diag(S)) if diag else S)
out = np.zeros((len(Xte), len(Ks)))
for i in range(len(Ks)):
Si = Sig[i] + 1e-9 * np.eye(p)
L = np.linalg.cholesky(Si)
z = np.linalg.solve(L, (Xte - mus[i]).T)
out[:, i] = -0.5 * (z ** 2).sum(axis=0) - np.log(np.diag(L)).sum() + np.log(prior[i])
return out
r = np.random.default_rng(33)
mu0, mu1 = np.array([0.0, 0.0]), np.array([1.6, 1.2])
C0 = np.array([[1.0, 0.75], [0.75, 1.0]])
C1 = np.array([[1.4, -0.5], [-0.5, 0.6]])
n_each = 400
Xf = np.vstack([r.multivariate_normal(mu0, C0, n_each),
r.multivariate_normal(mu1, C1, n_each)])
yf = np.r_[np.zeros(n_each, int), np.ones(n_each, int)]
idx = r.permutation(len(Xf)); Xf, yf = Xf[idx], yf[idx]
Xtr, ytr, Xte, yte = Xf[:600], yf[:600], Xf[600:], yf[600:]
cfg = [("Naive Bayes Gauss (riêng, chéo)", False, True),
("LDA (dùng chung, đầy đủ)", True, False),
("QDA (riêng, đầy đủ)", False, False),
("NB dùng chung Sigma chéo", True, True)]
scores, accs = {}, {}
print(f"{'mô hình':<34}{'giả thiết Sigma':<26}{'độ chính xác':>14}")
for lab, sh, dg in cfg:
sc = gauss_disc(Xtr, ytr, Xte, sh, dg)
scores[lab] = sc
accs[lab] = (sc.argmax(axis=1) == yte).mean()
gt = ("dùng chung" if sh else "riêng từng lớp") + (", chéo" if dg else ", đầy đủ")
print(f"{lab:<34}{gt:<26}{accs[lab]:14.4f}")
def nb_tich_mot_chieu(Xtr_, ytr_, Xte_):
"""Naive Bayes dung dung dinh nghia: NHAN cac mat do Gauss MOT CHIEU cua
tung dac trung lai voi nhau. Khong dung ma tran hiep phuong sai o bat ky dau."""
Ks_ = np.unique(ytr_)
p_ = Xtr_.shape[1]
var = np.zeros(p_)
for k in Ks_:
var += ((Xtr_[ytr_ == k] - Xtr_[ytr_ == k].mean(axis=0)) ** 2).sum(axis=0)
var /= (len(Xtr_) - len(Ks_)) # phuong sai gop, rieng tung dac trung
out = np.zeros((len(Xte_), len(Ks_)))
for i_, k in enumerate(Ks_):
mu = Xtr_[ytr_ == k].mean(axis=0)
logp = np.full(len(Xte_), np.log((ytr_ == k).mean()))
for j_ in range(p_): # tich cac mat do -> tong cac log
logp = (logp - 0.5 * np.log(2 * np.pi * var[j_])
- (Xte_[:, j_] - mu[j_]) ** 2 / (2 * var[j_]))
out[:, i_] = logp
return out
d_lda = gauss_disc(Xtr, ytr, Xte, True, True) # Gauss NHIEU chieu, Sigma cheo
d_nbs = nb_tich_mot_chieu(Xtr, ytr, Xte) # tich cac Gauss MOT chieu
print("")
print("Kiem chung dang thuc: tich cac Gauss MOT CHIEU == Gauss NHIEU CHIEU, Sigma cheo")
print(" hai cach tinh duoc cai dat hoan toan doc lap nhau trong ma nguon.")
dl = d_lda[:, 1] - d_lda[:, 0]
dn = d_nbs[:, 1] - d_nbs[:, 0]
print(f" sai khac lon nhat cua hieu diem phan biet: {np.abs(dl - dn).max():.3e}")
print(f" hai mo hinh du bao giong nhau o "
f"{(d_lda.argmax(axis=1) == d_nbs.argmax(axis=1)).mean() * 100:.1f}% so mau")
b_shared = gauss_disc(Xtr, ytr, Xte, True, False)
lin = b_shared[:, 1] - b_shared[:, 0]
A = np.c_[Xte, np.ones(len(Xte))]
coef, *_ = np.linalg.lstsq(A, lin, rcond=None)
print(f" ham quyet dinh cua LDA co TUYEN TINH khong? sai so khop tuyen tinh = "
f"{np.abs(A @ coef - lin).max():.3e} -> co")
b_q = gauss_disc(Xtr, ytr, Xte, False, False)
linq = b_q[:, 1] - b_q[:, 0]
coefq, *_ = np.linalg.lstsq(A, linq, rcond=None)
print(f" con QDA thi sao? sai so khop tuyen tinh = {np.abs(A @ coefq - linq).max():.3e} "
f"-> khong, no bac hai")
print("\nDay la ca cau chuyen: ba mo hinh chi khac nhau o RANG BUOC dat len Sigma.")
print("Gia thiet 'naive' cua Naive Bayes chinh la ep Sigma thanh ma tran cheo.")
# =====================================================================
# (G) Hình 6 — Vì sao độ chính xác nói dối trên dữ liệu mất cân bằng
# =====================================================================
head("(G) Do chinh xac noi doi tren du lieu mat can bang")
def prf(yt, yp):
tp = int(((yp == 1) & (yt == 1)).sum()); fp = int(((yp == 1) & (yt == 0)).sum())
fn = int(((yp == 0) & (yt == 1)).sum()); tn = int(((yp == 0) & (yt == 0)).sum())
prec = tp / (tp + fp) if tp + fp else float("nan")
rec = tp / (tp + fn) if tp + fn else float("nan")
f1 = 2 * prec * rec / (prec + rec) if tp and (prec + rec) else 0.0
return tp, fp, fn, tn, prec, rec, f1
def roc_auc(yt, sc):
o = np.argsort(-sc); yt = yt[o]
P, N = yt.sum(), len(yt) - yt.sum()
tpr = np.r_[0, np.cumsum(yt) / P]; fpr = np.r_[0, np.cumsum(1 - yt) / N]
return float(np.trapezoid(tpr, fpr))
def pr_auc(yt, sc):
o = np.argsort(-sc); yt = yt[o]
tp = np.cumsum(yt); fp = np.cumsum(1 - yt)
rec = tp / yt.sum(); prec = tp / (tp + fp)
return float(np.trapezoid(prec, rec))
r = np.random.default_rng(44)
N_G, rate = 20000, 0.01
yg = (r.uniform(size=N_G) < rate).astype(int)
# Mot bo phan lop "tam duoc": diem cua lop duong cao hon mot chut.
sg = r.normal(0, 1, N_G) + 2.6 * yg
print(f"Ty le lop duong: {yg.mean() * 100:.2f}% ({yg.sum()} tren {N_G} mau)")
print(f"\n{'bộ phân loại':<30}{'độ chính xác':>14}{'precision':>11}{'recall':>9}{'F1':>8}")
allneg = np.zeros(N_G, int)
tp, fp, fn, tn, pr_, rc_, f1_ = prf(yg, allneg)
print(f"{'đoán TẤT CẢ là âm':<30}{(allneg == yg).mean():14.4f}{'—':>11}{rc_:9.4f}{f1_:8.4f}")
for thr, name in [(2.6, "ngưỡng 2,6"), (1.0, "ngưỡng 1,0"), (-1.0, "ngưỡng -1,0")]:
yp = (sg > thr).astype(int)
tp, fp, fn, tn, pr_, rc_, f1_ = prf(yg, yp)
print(f"{name:<30}{(yp == yg).mean():14.4f}{pr_:11.4f}{rc_:9.4f}{f1_:8.4f}")
print(f"\nROC-AUC = {roc_auc(yg, sg):.4f} nhung PR-AUC = {pr_auc(yg, sg):.4f}")
print(f"PR-AUC cua bo doan ngau nhien chinh bang ty le lop duong = {rate:.4f}")
print("ROC-AUC trong dep vi mau so cua FPR la so luong lop AM, ma lop am thi rat nhieu;")
print("them vai bao dong gia khong lam FPR nhuc nhich. PR-AUC khong bi the.")
fig, ax = plt.subplots(1, 2, figsize=(8.2, 3.2))
o = np.argsort(-sg); ys = yg[o]
tpr = np.r_[0, np.cumsum(ys) / ys.sum()]; fpr = np.r_[0, np.cumsum(1 - ys) / (len(ys) - ys.sum())]
ax[0].plot(fpr, tpr, color=C_MAIN, lw=1.8); ax[0].plot([0, 1], [0, 1], ":", color=C_DIM)
ax[0].set_xlabel("FPR"); ax[0].set_ylabel("TPR")
ax[0].set_title(f"ROC — AUC = {roc_auc(yg, sg):.3f}")
tpc = np.cumsum(ys); fpc = np.cumsum(1 - ys)
ax[1].plot(tpc / ys.sum(), tpc / (tpc + fpc), color=C_BAD, lw=1.8)
ax[1].axhline(rate, ls=":", color=C_DIM)
ax[1].text(0.45, rate * 1.6, "mức đoán ngẫu nhiên", fontsize=7.5, color=C_DIM)
ax[1].set_xlabel("recall"); ax[1].set_ylabel("precision")
ax[1].set_title(f"Precision–Recall — AUC = {pr_auc(yg, sg):.3f}")
fig.tight_layout(); fig.savefig(OUT + "nt06_metrics.png"); plt.close(fig)
# =====================================================================
# (H) Hình 7 — Ridge ĐÚNG BẰNG MAP với tiên nghiệm Gauss
# =====================================================================
head("(H) Phat chuan la tien nghiem: ridge == MAP Gauss, lasso == MAP Laplace")
from scipy.optimize import minimize
from sklearn.linear_model import Lasso
r = np.random.default_rng(55)
n_h, p_h = 80, 12
Xh = r.normal(size=(n_h, p_h))
w_star = np.array([3.0, -2.0, 1.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
sigma = 1.0
yh = Xh @ w_star + r.normal(0, sigma, n_h)
tau = 0.7
lam_ridge = sigma ** 2 / tau ** 2 # ket qua ly thuyet: lambda = sigma^2 / tau^2
w_ridge = np.linalg.solve(Xh.T @ Xh + lam_ridge * np.eye(p_h), Xh.T @ yh)
def neg_log_posterior(w):
"""-log p(w | du lieu) = -log p(du lieu | w) - log p(w), bo hang so."""
resid = yh - Xh @ w
return resid @ resid / (2 * sigma ** 2) + w @ w / (2 * tau ** 2)
res = minimize(neg_log_posterior, np.zeros(p_h), method="BFGS",
options={"gtol": 1e-12, "maxiter": 20000})
print(f"lambda ly thuyet = sigma^2/tau^2 = {sigma ** 2:.1f}/{tau ** 2:.2f} = {lam_ridge:.6f}")
print(f"Sai khac giua ridge dang dong va MAP toi uu bang so: "
f"{np.abs(w_ridge - res.x).max():.3e}")
print("Hai duong tinh hoan toan khac nhau (mot giai he tuyen tinh, mot chay BFGS tren")
print("hau nghiem) nhung ra CUNG MOT nghiem -> phat chuan L2 chinh la tien nghiem Gauss.")
print(f"\n{'lambda':>10}{'||w||':>10}{'số hệ số = 0 (ridge)':>24}{'số hệ số = 0 (lasso)':>24}")
Hrows = []
for lam in [0.01, 0.1, 1.0, 10.0, 100.0]:
wr = np.linalg.solve(Xh.T @ Xh + lam * np.eye(p_h), Xh.T @ yh)
las = Lasso(alpha=lam / n_h, max_iter=200000, tol=1e-12).fit(Xh, yh)
nz_r = int((np.abs(wr) < 1e-10).sum()); nz_l = int((np.abs(las.coef_) < 1e-10).sum())
Hrows.append((lam, np.linalg.norm(wr), nz_r, nz_l))
print(f"{lam:10.2f}{np.linalg.norm(wr):10.4f}{nz_r:24d}{nz_l:24d}")
print("Ridge KHONG BAO GIO cho he so bang dung 0; lasso thi co. Ly do nam o dao ham")
print("tai goc: |w| co diem gay nen nghiem bi 'dinh' vao 0, con w^2 thi tron.")
print(f"(su that: mo hinh sinh du lieu co {int((w_star == 0).sum())} he so bang 0)")
# Ridge co ngot theo tung huong rieng: he so d_i^2/(d_i^2 + lambda)
U, dsv, Vt = np.linalg.svd(Xh, full_matrices=False)
lam_demo = 10.0
w_svd = Vt.T @ ((dsv / (dsv ** 2 + lam_demo)) * (U.T @ yh))
w_cf = np.linalg.solve(Xh.T @ Xh + lam_demo * np.eye(p_h), Xh.T @ yh)
print(f"\nRidge qua SVD so voi dang dong: lech {np.abs(w_svd - w_cf).max():.3e}")
print(f"{'trị kỳ dị d_i':>16}{'hệ số co ngót d^2/(d^2+lam)':>32}")
for i in [0, 1, p_h // 2, p_h - 2, p_h - 1]:
f_ = dsv[i] ** 2 / (dsv[i] ** 2 + lam_demo)
print(f"{dsv[i]:16.4f}{f_:32.4f}")
print("Huong co tri ky di LON gan nhu khong bi dong toi; huong YEU bi co manh.")
print("Ridge khong co deu moi huong -> no co dung nhung huong du lieu noi it nhat.")
fig, ax = plt.subplots(1, 2, figsize=(8.4, 3.1))
lams = np.logspace(-2, 2.5, 60)
pr = np.array([np.linalg.solve(Xh.T @ Xh + l * np.eye(p_h), Xh.T @ yh) for l in lams])
pl_ = np.array([Lasso(alpha=l / n_h, max_iter=50000, tol=1e-10).fit(Xh, yh).coef_ for l in lams])
for j in range(p_h):
ax[0].plot(lams, pr[:, j], lw=1.2, color=C_MAIN if w_star[j] != 0 else C_DIM, alpha=.85)
ax[1].plot(lams, pl_[:, j], lw=1.2, color=C_MAIN if w_star[j] != 0 else C_DIM, alpha=.85)
for a, t in [(ax[0], "Ridge"),
(ax[1], "Lasso")]:
a.set_xscale("log"); a.axhline(0, color="k", lw=.7)
a.set_xlabel("$\\lambda$"); a.set_ylabel("hệ số"); a.set_title(t, fontsize=9)
fig.tight_layout(); fig.savefig(OUT + "nt07_regular.png"); plt.close(fig)
# =====================================================================
# (I) Hình 8 — Hàm lồi: kiểm tra bằng số, và vì sao lồi thì yên tâm
# =====================================================================
head("(I) Tinh loi: kiem tra bang dinh nghia, va hau qua len toi uu hoa")
def kiem_tra_loi(f_, lo, hi, n_thu=20000, seed=0):
"""Thu truc tiep bat dang thuc dinh nghia f(t*a+(1-t)*b) <= t*f(a)+(1-t)*f(b)."""
rr = np.random.default_rng(seed)
a = rr.uniform(lo, hi, n_thu); b = rr.uniform(lo, hi, n_thu); t = rr.uniform(0, 1, n_thu)
trai = f_(t * a + (1 - t) * b)
phai = t * f_(a) + (1 - t) * f_(b)
vi_pham = trai - phai
return float(vi_pham.max()), float((vi_pham > 1e-12).mean())
ham = [("$x^2$", lambda x: x ** 2, -3, 3),
("$|x|$", lambda x: np.abs(x), -3, 3),
("$e^x$", lambda x: np.exp(x), -3, 3),
("$\\log(1+e^x)$ (mất mát logistic)", lambda x: np.logaddexp(0, x), -6, 6),
("$x^4 - 3x^2$", lambda x: x ** 4 - 3 * x ** 2, -3, 3),
("$\\sin x$", lambda x: np.sin(x), -6, 6),
("$x^3$", lambda x: x ** 3, -3, 3)]
print(f"{'hàm':<36}{'vi phạm lớn nhất':>20}{'tỉ lệ vi phạm':>16}{'kết luận':>10}")
for lab, fn, lo, hi in ham:
vmax, tile = kiem_tra_loi(fn, lo, hi)
kl = "lồi" if vmax <= 1e-12 else "KHÔNG"
print(f"{lab:<36}{vmax:20.3e}{tile:16.4f}{kl:>10}")
print("Phep thu nay khong CHUNG MINH tinh loi, nhung mot phan vi du thi du de BAC BO.")
def gd_1d(fn, dfn, x0, eta=0.02, it=8000):
x = x0
for _ in range(it):
x -= eta * dfn(x)
return x
f_loi, df_loi = lambda x: x ** 2, lambda x: 2 * x
f_kl = lambda x: x ** 4 - 3 * x ** 2 + 0.5 * x
df_kl = lambda x: 4 * x ** 3 - 6 * x + 0.5
starts = np.linspace(-2.5, 2.5, 21)
end_loi = np.array([gd_1d(f_loi, df_loi, s) for s in starts])
end_kl = np.array([gd_1d(f_kl, df_kl, s) for s in starts])
print(f"\nXuat phat tu 21 diem khac nhau roi chay gradient descent:")
print(f" ham LOI x^2 : so diem dung khac nhau = "
f"{len(np.unique(np.round(end_loi, 6)))}, gia tri = {np.round(end_loi, 4).min():.4f}")
u_kl = np.unique(np.round(end_kl, 4))
print(f" ham KHONG LOI x^4-3x^2+x/2: so diem dung khac nhau = {len(u_kl)}, "
f"cac gia tri = {u_kl}")
print(f" gia tri ham tai hai diem do: {[round(float(f_kl(v)), 4) for v in u_kl]}")
print("Voi ham loi, MOI cuc tieu dia phuong deu la cuc tieu toan cuc, nen diem khoi tao")
print("khong quan trong. Day la ly do ta quan tam toi tinh loi den vay.")
fig, ax = plt.subplots(1, 2, figsize=(8.2, 3.0))
xs = np.linspace(-2.5, 2.5, 400)
ax[0].plot(xs, xs ** 2, color=C_MAIN, lw=1.8)
ax[0].plot(end_loi, end_loi ** 2, "o", ms=5, color=C_BAD)
ax[0].set_title("Hàm lồi: 21 điểm xuất phát, 1 điểm dừng", fontsize=9)
ax[1].plot(xs, f_kl(xs), color=C_WARN, lw=1.8)
ax[1].plot(end_kl, f_kl(end_kl), "o", ms=5, color=C_BAD)
ax[1].set_title("Không lồi: 21 điểm xuất phát, 2 điểm dừng", fontsize=9)
for a in ax:
a.set_xlabel("$x$"); a.set_ylabel("$f(x)$")
fig.tight_layout(); fig.savefig(OUT + "nt08_convex.png"); plt.close(fig)
# =====================================================================
# (J) Hình 9 — SVM: đối ngẫu mạnh, điều kiện KKT, và vai trò của C
# =====================================================================
head("(J) SVM: kiem chung doi ngau manh va dieu kien KKT bang so")
from sklearn.svm import SVC
r = np.random.default_rng(66)
n_j = 60
Xj = np.vstack([r.normal([-1.6, -1.0], 0.55, (n_j, 2)),
r.normal([1.6, 1.2], 0.55, (n_j, 2))])
yj = np.r_[-np.ones(n_j), np.ones(n_j)]
svm_hard = SVC(kernel="linear", C=1e8, tol=1e-12).fit(Xj, yj)
w_sk = svm_hard.coef_[0]
b_sk = float(svm_hard.intercept_[0])
sv = svm_hard.support_vectors_
alpha_y = svm_hard.dual_coef_[0] # bang alpha_i * y_i
alpha = np.abs(alpha_y)
y_sv = np.sign(alpha_y)
w_dual = alpha_y @ sv # w = sum alpha_i y_i x_i
print(f"Dung lai w tu nghiem doi ngau: lech so voi coef_ cua sklearn = "
f"{np.abs(w_dual - w_sk).max():.3e}")
marg_sv = y_sv * (sv @ w_sk + b_sk)
print(f"Vector ho tro co thoa y_i(w.x_i + b) = 1 khong? "
f"lech lon nhat = {np.abs(marg_sv - 1).max():.3e}")
print(f"So vector ho tro: {len(sv)} tren {len(Xj)} diem "
f"({len(sv) / len(Xj) * 100:.1f}%)")
primal = 0.5 * w_sk @ w_sk
K = sv @ sv.T
dual = alpha.sum() - 0.5 * (alpha_y @ K @ alpha_y)
print(f"\nDoi ngau manh:")
print(f" gia tri bai toan goc 0.5*||w||^2 = {primal:.10f}")
print(f" gia tri bai toan doi ngau = {dual:.10f}")
print(f" khe doi ngau = {abs(primal - dual):.3e}")
print(f"Le = 2/||w|| = {2 / np.linalg.norm(w_sk):.6f}; "
f"khoang cach nho nhat tu diem toi sieu phang x 2 = "
f"{2 * np.abs(Xj @ w_sk + b_sk).min() / np.linalg.norm(w_sk):.6f}")
# Le mem: hai lop CHONG LAN nhau, va C dieu khien danh doi le rong / bam du lieu.
r2j = np.random.default_rng(99)
n_tr, n_te = 120, 4000
mu_a, mu_b, sd_j = np.array([-0.9, -0.6]), np.array([0.9, 0.7]), 1.15
def sinh(nn, rr):
Xa = rr.normal(mu_a, sd_j, (nn // 2, 2)); Xb = rr.normal(mu_b, sd_j, (nn // 2, 2))
return np.vstack([Xa, Xb]), np.r_[-np.ones(nn // 2), np.ones(nn // 2)]
Xtr_j, ytr_j = sinh(n_tr, r2j)
Xte_j, yte_j = sinh(n_te, np.random.default_rng(100))
print("")
print("Hai lop chong lan nhau (le mem la bat buoc), quet C:")
print(f"{'C':>10}{'lề 2/||w||':>13}{'số VTHT':>10}{'đúng trên lề':>15}"
f"{'vi phạm lề':>13}{'sai số h.luyện':>17}{'sai số kiểm tra':>18}")
Jrows = []
for Cv in [0.003, 0.03, 0.3, 3.0, 30.0, 300.0]:
m = SVC(kernel="linear", C=Cv, tol=1e-10).fit(Xtr_j, ytr_j)
ww = m.coef_[0]
a = np.abs(m.dual_coef_[0])
eps_c = 1e-6 * max(Cv, 1.0)
tren_le = int(((a > eps_c) & (a < Cv - eps_c)).sum())
vi_pham = int((a >= Cv - eps_c).sum())
e_tr = float((m.predict(Xtr_j) != ytr_j).mean())
e_te = float((m.predict(Xte_j) != yte_j).mean())
Jrows.append((Cv, 2 / np.linalg.norm(ww), len(a), tren_le, vi_pham, e_tr, e_te))
print(f"{Cv:10.3f}{2 / np.linalg.norm(ww):13.4f}{len(a):10d}{tren_le:15d}"
f"{vi_pham:13d}{e_tr:17.4f}{e_te:18.4f}")
best = min(Jrows, key=lambda t: t[6])
print(f"C tot nhat tren tap kiem tra: C = {best[0]:g} (sai so {best[6]:.4f}); "
f"C lon nhat cho {Jrows[-1][6]:.4f}")
print("C nho -> le RONG, nhieu diem duoc phep vi pham, mo hinh don gian hon.")
print("C lon -> le HEP, co ep tung diem cho dung, bat dau bam vao nhieu.")
print("Dieu kien KKT chia cac diem lam ba nhom, va bang tren dem dung ba nhom ay:")
print(" alpha = 0 -> nam NGOAI le, khong anh huong gi toi nghiem")
print(" 0 < alpha < C -> nam DUNG TREN le")
print(" alpha = C -> VI PHAM le (lot vao trong, hoac sang han phia sai)")
# Kernel: du lieu khong tach duoc tuyen tinh.
th = r.uniform(0, 2 * np.pi, 200)
rad = np.r_[r.normal(1.0, 0.18, 100), r.normal(2.4, 0.18, 100)]
Xk = np.c_[rad * np.cos(th), rad * np.sin(th)]
yk = np.r_[-np.ones(100), np.ones(100)]
print(f"\nDu lieu hai vong tron dong tam:")
for name, mk in [("nhân tuyến tính", SVC(kernel="linear", C=10)),
("nhân đa thức bậc 2", SVC(kernel="poly", degree=2, C=10, gamma="scale")),
("nhân RBF", SVC(kernel="rbf", C=10, gamma="scale"))]:
mk.fit(Xk, yk)
print(f" {name:<24} do chinh xac = {(mk.predict(Xk) == yk).mean():.4f}")
print("Thu thuat nhan: thay vi anh xa x -> phi(x) roi tinh tich vo huong, ta tinh thang")
print("K(x, x') = phi(x).phi(x'). Voi RBF thi phi(x) o khong gian VO HAN chieu.")
fig, ax = plt.subplots(1, 3, figsize=(10.2, 3.1))
for a, Cv in zip(ax[:2], [0.003, 300.0]):
m = SVC(kernel="linear", C=Cv, tol=1e-10).fit(Xtr_j, ytr_j)
xx, yy = np.meshgrid(np.linspace(-4.4, 4.4, 300), np.linspace(-4.2, 4.4, 300))
Z = m.decision_function(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)
a.contourf(xx, yy, Z > 0, alpha=.13, cmap="coolwarm")
a.contour(xx, yy, Z, levels=[-1, 0, 1], colors=["#888", "k", "#888"],
linestyles=["--", "-", "--"], linewidths=[.9, 1.4, .9])
a.scatter(*Xtr_j[ytr_j == -1].T, s=13, color=C_MAIN)
a.scatter(*Xtr_j[ytr_j == 1].T, s=13, color=C_BAD)
a.set_title(f"$C = {Cv:g}$ — lề {2 / np.linalg.norm(m.coef_[0]):.2f}", fontsize=9)
mk = SVC(kernel="rbf", C=10, gamma="scale").fit(Xk, yk)
xx, yy = np.meshgrid(np.linspace(-3.2, 3.2, 300), np.linspace(-3.2, 3.2, 300))
Z = mk.decision_function(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)
ax[2].contourf(xx, yy, Z > 0, alpha=.13, cmap="coolwarm")
ax[2].contour(xx, yy, Z, levels=[0], colors="k", linewidths=1.4)
ax[2].scatter(*Xk[yk == -1].T, s=11, color=C_MAIN); ax[2].scatter(*Xk[yk == 1].T, s=11, color=C_BAD)
ax[2].set_title("Kernel RBF, hai đường tròn đồng tâm", fontsize=9)
fig.tight_layout(); fig.savefig(OUT + "nt09_svm.png"); plt.close(fig)
# =====================================================================
# (K) Hình 10 — PCA: định lý Eckart–Young, và khi nào PCA làm hỏng việc
# =====================================================================
head("(K) PCA: sai so tai dung DUNG BANG tong cac tri rieng bi bo")
r = np.random.default_rng(77)
n_k, p_k = 300, 8
Z = r.normal(size=(n_k, 3))
Bmix = r.normal(size=(3, p_k))
Xp_ = Z @ Bmix + 0.35 * r.normal(size=(n_k, p_k))
Xc = Xp_ - Xp_.mean(axis=0)
Cov = Xc.T @ Xc / (n_k - 1)
eigval, eigvec = np.linalg.eigh(Cov)
eigval, eigvec = eigval[::-1], eigvec[:, ::-1]
U, dsv, Vt = np.linalg.svd(Xc, full_matrices=False)
print(f"Hai duong tinh PCA:")
print(f" tri rieng cua ma tran hiep phuong sai : {np.round(eigval[:4], 6)}")
print(f" d_i^2/(n-1) tu SVD : {np.round(dsv[:4] ** 2 / (n_k - 1), 6)}")
print(f" lech lon nhat tren ca {p_k} tri: {np.abs(eigval - dsv ** 2 / (n_k - 1)).max():.3e}")
print(f" goc giua cac truc chinh (tri tuyet doi tich vo huong phai bang 1):")
print(f" {np.round(np.abs(np.diag(eigvec.T @ Vt.T))[:4], 10)}")
print(f"\n{'k':>4}{'||X - X_k||_F^2':>20}{'(n-1) * tổng trị riêng bỏ':>30}{'lệch':>12}"
f"{'% phương sai giữ':>19}")
tong = eigval.sum()
for k in range(1, p_k + 1):
Xk_ = (U[:, :k] * dsv[:k]) @ Vt[:k]
lhs = float(((Xc - Xk_) ** 2).sum())
rhs = float((n_k - 1) * eigval[k:].sum())
print(f"{k:4d}{lhs:20.6f}{rhs:30.6f}{abs(lhs - rhs):12.2e}"
f"{eigval[:k].sum() / tong * 100:19.2f}")
print("Day la dinh ly Eckart-Young: phep chieu PCA la xap xi hang k TOT NHAT theo")
print("chuan Frobenius, va sai so cua no dung bang tong cac tri rieng bi bo di.")
# Khi PCA lam hong viec: huong phuong sai lon nhat vo dung cho phan loai.
r2 = np.random.default_rng(88)
n_c = 400
noise_dir = r2.normal(0, 6.0, n_c) # phuong sai RAT lon, khong mang thong tin
sig = np.r_[-np.ones(n_c // 2), np.ones(n_c // 2)] * 1.0
Xl_ = np.c_[noise_dir, sig + r2.normal(0, 0.45, n_c)]
yl_ = (sig > 0).astype(int)
Xlc = Xl_ - Xl_.mean(axis=0)
ev, evec = np.linalg.eigh(Xlc.T @ Xlc / (n_c - 1))
pc1 = evec[:, np.argmax(ev)]
mu0_, mu1_ = Xl_[yl_ == 0].mean(axis=0), Xl_[yl_ == 1].mean(axis=0)
Sw = np.zeros((2, 2))
for c in (0, 1):
D = Xl_[yl_ == c] - (mu0_ if c == 0 else mu1_)
Sw += D.T @ D
w_lda = np.linalg.solve(Sw, mu1_ - mu0_); w_lda /= np.linalg.norm(w_lda)
def auc_1d(proj, lab):
o = np.argsort(proj); lab = lab[o]
P_, N_ = lab.sum(), len(lab) - lab.sum()
return float(np.trapezoid(np.r_[0, np.cumsum(lab) / P_], np.r_[0, np.cumsum(1 - lab) / N_]))
a_pca = auc_1d(Xl_ @ pc1, yl_); a_pca = max(a_pca, 1 - a_pca)
a_lda = auc_1d(Xl_ @ w_lda, yl_); a_lda = max(a_lda, 1 - a_lda)
print(f"\nDu lieu co mot huong nhieu bien do lon va mot huong tin hieu bien do nho:")
print(f" truc chinh thu nhat cua PCA: {np.round(pc1, 4)} "
f"(giu {ev.max() / ev.sum() * 100:.1f}% phuong sai)")
print(f" huong cua LDA : {np.round(w_lda, 4)}")
print(f" AUC khi chieu len truc PCA : {a_pca:.4f}")
print(f" AUC khi chieu len huong LDA: {a_lda:.4f}")
print("PCA giu gan het phuong sai nhung vut di toan bo kha nang phan loai. Ly do:")
print("PCA KHONG NHIN NHAN. Phuong sai lon khong dong nghia co ich.")
fig, ax = plt.subplots(1, 2, figsize=(8.4, 3.1))
ax[0].bar(np.arange(1, p_k + 1), eigval / tong * 100, color=C_MAIN, alpha=.85)
ax[0].plot(np.arange(1, p_k + 1), np.cumsum(eigval) / tong * 100, "-o", ms=4, color=C_BAD)
ax[0].set_xlabel("thành phần chính"); ax[0].set_ylabel("% phương sai")
ax[0].set_title("Phương sai từng thành phần và tích luỹ", fontsize=9)
ax[1].scatter(*Xl_[yl_ == 0].T, s=8, color=C_MAIN, alpha=.6)
ax[1].scatter(*Xl_[yl_ == 1].T, s=8, color=C_BAD, alpha=.6)
for vec, lab, col in [(pc1, "thành phần chính thứ nhất", C_DIM), (w_lda, "hướng LDA", "k")]:
ax[1].plot([-8 * vec[0], 8 * vec[0]], [-8 * vec[1], 8 * vec[1]], lw=1.8,
color=col, label=lab)
ax[1].legend(frameon=False, fontsize=8); ax[1].set_aspect("equal")
ax[1].set_title("Thành phần chính thứ nhất và hướng LDA", fontsize=9)
fig.tight_layout(); fig.savefig(OUT + "nt10_pca.png"); plt.close(fig)
# =====================================================================
# (L) Hình 11 — K-means: phụ thuộc khởi tạo, và nó giả định cái gì
# =====================================================================
head("(L) K-means: khoi tao quyet dinh ket qua, va gia thiet ngam ve hinh dang cum")
from itertools import permutations
def kmeans(Xm, k, init, seed, itmax=300):
rr = np.random.default_rng(seed)
if init == "ngau nhien":
C_ = Xm[rr.choice(len(Xm), k, replace=False)].copy()
else: # k-means++
C_ = [Xm[rr.integers(len(Xm))]]
for _ in range(k - 1):
d2 = np.min(((Xm[:, None, :] - np.array(C_)[None]) ** 2).sum(-1), axis=1)
C_.append(Xm[rr.choice(len(Xm), p=d2 / d2.sum())])
C_ = np.array(C_)
lab = np.zeros(len(Xm), int)
for _ in range(itmax):
lab = np.argmin(((Xm[:, None, :] - C_[None]) ** 2).sum(-1), axis=1)
new = np.array([Xm[lab == j].mean(axis=0) if (lab == j).any() else C_[j]
for j in range(k)])
if np.allclose(new, C_):
break
C_ = new
return C_, lab, float(((Xm - C_[lab]) ** 2).sum())
def do_chinh_xac_gom_cum(lab, truth, k):
"""Gom cum khong co nhan, nen phai thu moi cach ghep nhan va lay cach tot nhat."""
return max((np.array([p[l] for l in lab]) == truth).mean()
for p in permutations(range(k)))
# Bai toan kho hon: 8 cum, mot so cum gan nhau -> khoi tao moi thuc su quan trong.
r = np.random.default_rng(111)
cent = np.array([[0, 0], [1.9, 0.3], [4.6, 0.1], [6.3, 1.7],
[0.4, 3.4], [2.4, 3.9], [5.1, 4.2], [7.0, 4.0]], float)
Xm = np.vstack([r.normal(c, 0.55, (90, 2)) for c in cent])
truth_m = np.repeat(np.arange(len(cent)), 90)
K_M = len(cent)
N_SEED = 200
print(f"Chay {N_SEED} lan voi {K_M} cum, moi lan mot hat giong khac nhau:")
print(f"{'cách khởi tạo':<18}{'quán tính nhỏ nhất':>21}{'trung bình':>13}"
f"{'% lần kẹt ở nghiệm tồi':>26}")
best_global = min(kmeans(Xm, K_M, ini, s)[2]
for ini in ("ngau nhien", "k-means++") for s in range(N_SEED))
for init in ["ngau nhien", "k-means++"]:
vals = np.array([kmeans(Xm, K_M, init, s)[2] for s in range(N_SEED)])
ket = (vals > best_global * 1.02).mean() * 100
print(f"{init:<18}{vals.min():21.3f}{vals.mean():13.3f}{ket:26.1f}")
print("K-means chi bao dam hoi tu toi cuc tieu DIA PHUONG. k-means++ rai cac tam ban")
print("dau cho xa nhau theo xac suat ti le d^2, nen it roi vao nghiem toi hon han.")
# K-means gia dinh cum HINH CAU va co kich thuoc tuong duong.
t_ = np.linspace(0, np.pi, 300)
moon1 = np.c_[np.cos(t_), np.sin(t_)] + r.normal(0, .08, (300, 2))
moon2 = np.c_[1 - np.cos(t_), 1 - np.sin(t_) - 0.5] + r.normal(0, .08, (300, 2))
Xmoon = np.vstack([moon1, moon2]); ymoon = np.r_[np.zeros(300, int), np.ones(300, int)]
Xan = np.vstack([r.normal([0, 0], [3.0, 0.35], (300, 2)),
r.normal([0, 2.4], [3.0, 0.35], (300, 2))])
yan = np.r_[np.zeros(300, int), np.ones(300, int)]
r3 = np.random.default_rng(5)
cent3 = np.array([[0, 0], [5.0, 0.5], [2.5, 4.5]], float)
Xsph = np.vstack([r3.normal(c, 0.7, (200, 2)) for c in cent3])
ysph = np.repeat(np.arange(3), 200)
_, lab_sph, _ = kmeans(Xsph, 3, "k-means++", 0)
_, lab_moon, _ = kmeans(Xmoon, 2, "k-means++", 0)
_, lab_an, _ = kmeans(Xan, 2, "k-means++", 0)
acc_sph = do_chinh_xac_gom_cum(lab_sph, ysph, 3)
acc_moon = do_chinh_xac_gom_cum(lab_moon, ymoon, 2)
acc_an = do_chinh_xac_gom_cum(lab_an, yan, 2)
print(f"\nDo chinh xac gom cum (da thu moi cach ghep nhan, lay cach tot nhat):")
print(f" ba cum TRON, tach roi : {acc_sph:.4f}")
print(f" hai hinh LUOI LIEM long nhau : {acc_moon:.4f}")
print(f" hai DAI DET nam ngang, cach nhau : {acc_an:.4f}")
print("K-means gan moi diem cho tam GAN NHAT theo khoang cach Euclid, nen bien giua hai")
print("cum luon la mot sieu phang. Cum khong loi hoac bi keo det thi gia thiet do sai.")
# Khuyu tay: khong co dinh nghia toan hoc, chi la mot quy tac nhin bang mat.
print(f"\n{'k':>4}{'quán tính':>14}{'giảm so với k-1':>20} (dữ liệu có đúng 8 cụm)")
prev = None
for k in range(1, 13):
inr = min(kmeans(Xm, k, "k-means++", s)[2] for s in range(8))
gi = "—" if prev is None else f"{(prev - inr) / prev * 100:.1f}%"
mark = " <-- khuỷu tay" if k == K_M else ""
print(f"{k:4d}{inr:14.2f}{gi:>20}{mark}")
prev = inr
print("Quan tinh LUON giam khi k tang (k = n thi bang 0), nen KHONG the chon k bang")
print("cach toi thieu no. 'Khuyu tay' la cho muc giam dot ngot cham han lai.")
fig, ax = plt.subplots(1, 3, figsize=(10.2, 3.0))
C3, l3, _ = kmeans(Xsph, 3, "k-means++", 0)
ax[0].scatter(*Xsph.T, c=l3, s=7, cmap="viridis", alpha=.75)
ax[0].scatter(*C3.T, marker="X", s=80, color="k", edgecolor="w", linewidth=1)
ax[0].set_title(f"Ba cụm tròn — đúng {acc_sph * 100:.0f}%", fontsize=9)
ax[1].scatter(*Xmoon.T, c=lab_moon, s=7, cmap="viridis", alpha=.75)
ax[1].set_title(f"Hai lưỡi liềm — đúng {acc_moon * 100:.0f}%", fontsize=9)
ax[2].scatter(*Xan.T, c=lab_an, s=7, cmap="viridis", alpha=.75)
ax[2].set_title(f"Hai dải dẹt — đúng {acc_an * 100:.0f}%", fontsize=9)
fig.tight_layout(); fig.savefig(OUT + "nt11_kmeans.png"); plt.close(fig)
# =====================================================================
# (M) Hình 12 — Hệ gợi ý: cần bao nhiêu đánh giá mỗi người thì mới đoán được
# =====================================================================
head("(M) He goi y: nguong du lieu de phan ra ma tran hoat dong, va khoi dau lanh")
r = np.random.default_rng(123)
n_u, n_i, rank_true, sd_noise = 300, 200, 4, 0.4
P_true = r.normal(0, 1, (n_u, rank_true))
Q_true = r.normal(0, 1, (n_i, rank_true))
R_sach = P_true @ Q_true.T # tin hieu that, khong nhieu
R_quan_sat = R_sach + r.normal(0, sd_noise, R_sach.shape)
print(f"Ma tran danh gia: {n_u} nguoi x {n_i} muc, hang that = {rank_true}, "
f"nhieu quan sat sd = {sd_noise}")
print(f" mo hinh hang {rank_true} co {(n_u + n_i) * rank_true:,} tham so tu do, "
f"so voi {n_u * n_i:,} o -> {(n_u + n_i) * rank_true / (n_u * n_i) * 100:.1f}%")
print(f" do lech chuan cua tin hieu that: {R_sach.std():.4f}")
def mf(mask, Robs, k, lam=0.1, iters=300, seed=0):
"""Binh phuong toi thieu luan phien co phat chuan (ALS)."""
rr = np.random.default_rng(seed)
P = rr.normal(0, .1, (Robs.shape[0], k)); Q = rr.normal(0, .1, (Robs.shape[1], k))
I = np.eye(k)
for _ in range(iters):
for u in range(Robs.shape[0]):
idx = np.where(mask[u])[0]
if len(idx):
Qi = Q[idx]; P[u] = np.linalg.solve(Qi.T @ Qi + lam * I, Qi.T @ Robs[u, idx])
for i_ in range(Robs.shape[1]):
idx = np.where(mask[:, i_])[0]
if len(idx):
Pi = P[idx]; Q[i_] = np.linalg.solve(Pi.T @ Pi + lam * I, Pi.T @ Robs[idx, i_])
return P, Q
print(f"\n{'% ô thấy được':>15}{'đánh giá / người':>19}{'RMSE ô chưa thấy':>20}"
f"{'so với đoán bừa':>18}")
Mrows = []
for frac in [0.02, 0.03, 0.05, 0.10, 0.20, 0.40]:
mask = r.uniform(size=(n_u, n_i)) < frac
P, Q = mf(mask, R_quan_sat, rank_true, seed=1)
rm = float(np.sqrt((((P @ Q.T) - R_sach)[~mask] ** 2).mean()))
Mrows.append((frac, mask.sum(1).mean(), rm))
print(f"{frac * 100:15.0f}{mask.sum(1).mean():19.1f}{rm:20.4f}"
f"{rm / R_sach.std():18.2f}")
print(f"Nguong rat sac net: duoi ~20 danh gia moi nguoi thi phan ra ma tran HONG HAN")
print(f"(sai so con te hon doan bua bang 0); tu khoang 5 lan so hang tro len thi no")
print(f"khoi phuc gan nhu chinh xac. Ly do: moi nguoi dung co {rank_true} an so can uoc luong,")
print(f"nen ho phai co du rang buoc thi he moi giai duoc.")
mask = r.uniform(size=(n_u, n_i)) < 0.30
print(f"\nChon hang k (do o mat do 30%, noi viec khop la dang tin):")
print(f"{'hạng k':>10}{'RMSE ô chưa thấy':>20}{'RMSE trên ô đã thấy':>23}")
for k in [1, 2, 3, 4, 6, 10, 20]:
P, Q = mf(mask, R_quan_sat, k, seed=2)
Rh = P @ Q.T
rm_out = float(np.sqrt(((Rh - R_sach)[~mask] ** 2).mean()))
rm_in = float(np.sqrt(((Rh - R_quan_sat)[mask] ** 2).mean()))
star = " <- hang dung" if k == rank_true else ""
print(f"{k:10d}{rm_out:20.4f}{rm_in:23.4f}{star}")
print("Sai so tren o DA THAY giam don dieu theo k (mo hinh manh hon thi khop chat hon),")
print("nhung sai so tren o CHUA THAY cham day o dung hang that roi tang len. Day chinh")
print("la danh doi thien lech - phuong sai, lan nay o dang chon hang.")
mask_cold = mask.copy()
n_new = 10
mask_cold[-n_new:, :] = False # 10 nguoi dung hoan toan moi
P, Q = mf(mask_cold, R_quan_sat, rank_true, seed=3)
Rh = P @ Q.T
cu = ~mask_cold[:-n_new]
rmse_cu = float(np.sqrt(((Rh - R_sach)[:-n_new][cu] ** 2).mean()))
rmse_moi = float(np.sqrt(((Rh - R_sach)[-n_new:] ** 2).mean()))
print(f"\nKhoi dau lanh: {n_new} nguoi dung MOI, khong co lay mot danh gia nao")
print(f" RMSE cho nguoi dung cu : {rmse_cu:.4f}")
print(f" RMSE cho nguoi dung moi : {rmse_moi:.4f}")
print(f" RMSE neu doan bua bang 0 : {R_sach.std():.4f}")
print(f" ||P|| trung binh, nguoi cu : {np.linalg.norm(P[:-n_new], axis=1).mean():.4f}")
print(f" ||P|| trung binh, nguoi moi: {np.linalg.norm(P[-n_new:], axis=1).mean():.2e}")
print("Vector cua nguoi dung moi bi keo thang ve 0 vi khong co rang buoc nao ngoai phat")
print("chuan, nen mo hinh du bao 0 cho tat ca. Do la ly do he thuc te luon can mot duong")
print("du phong dua tren NOI DUNG: dac trung cua muc va ho so khai bao cua nguoi dung.")
fig, ax = plt.subplots(1, 2, figsize=(8.4, 3.1))
ax[0].plot([m[1] for m in Mrows], [m[2] for m in Mrows], "-o", ms=5, color=C_MAIN)
ax[0].axhline(R_sach.std(), ls=":", color=C_DIM)
ax[0].text(28, R_sach.std() * 1.05, "đoán mọi ô bằng 0", fontsize=7.5, color=C_DIM)
ax[0].axvline(5 * rank_true, ls="--", color=C_BAD, lw=1)
ax[0].text(5 * rank_true * 1.1, 1.2, "$5k$", fontsize=8, color=C_BAD)
ax[0].set_yscale("log"); ax[0].set_xlabel("số đánh giá trung bình mỗi người")
ax[0].set_ylabel("RMSE trên ô chưa quan sát (log)")
ax[0].set_title("Sai số theo số đánh giá mỗi người", fontsize=9)
im = ax[1].imshow(R_sach[:40, :40], cmap="coolwarm", aspect="auto")
ax[1].set_title("Ma trận đánh giá thật (hạng 4)", fontsize=9)
fig.colorbar(im, ax=ax[1], fraction=.046)
fig.tight_layout(); fig.savefig(OUT + "nt12_recsys.png"); plt.close(fig)
print("\n" + "=" * 74)
print("Da luu 10 hinh vao thu muc figs/: nt02, nt03, nt04, nt06, nt07, nt08, nt09, nt10, nt11, nt12")
print("=" * 74)
Kết quả chạy — experiments_output.txt
==========================================================================
(A) Hoi quy tuyen tinh: nghiem giai tich va dieu kien so
==========================================================================
cách giải w0 w1 w2 w3
phương trình chuẩn tắc 1.9906 -1.4985 0.7992 2.9749
giả nghịch đảo Moore-Penrose 1.9906 -1.4985 0.7992 2.9749
lstsq (phân rã QR) 1.9906 -1.4985 0.7992 2.9749
lech giua 3 cach giai: 1.22e-15 / 2.66e-15 -> cung mot nghiem
Khi lap lai mot cot: hang cua X = 4 nhung X co 5 cot -> X^T X suy bien
solve: BAO LOI 'Singular matrix'
pinv: van cho mot nghiem, chuan ||w|| = 3.8176
mot nghiem khac cung du bao y het: ||w|| = 4.0711 (sai so du bao lech 0.00e+00)
-> pinv chon nghiem CHUAN NHO NHAT trong vo so nghiem
tuong quan 2 cot cond(X^T X) do lech chuan w1 do lech chuan du bao
0.000 1.3 0.0324 0.0586
0.900 20.0 0.0772 0.0588
0.990 180.3 0.2373 0.0590
0.999 1958.9 0.8196 0.0585
Doc bang: cong tuyen lam HE SO dao manh (cot 3) nhung DU BAO gan nhu khong doi
(cot 4). Vi vay cong tuyen la van de cua DIEN GIAI, khong phai cua du bao.
==========================================================================
(B) Gradient descent tren ham bac hai: ly thuyet doi chieu voi do duoc
==========================================================================
kappa GD (số vòng) heavy ball tỉ lệ căn kappa
1 1 1 1.00 1.00
10 92 34 2.71 3.16
100 922 117 7.88 10.00
1000 9211 391 23.56 31.62
10000 92104 1297 71.01 100.00
Ly thuyet: GD can O(kappa) vong, heavy ball can O(sqrt(kappa)) vong.
Cot 'ti le' bam sat cot 'can kappa' -> dung nhu ly thuyet du doan.
==========================================================================
(C) Kiem tra dao ham bang sai phan: diem toi uu cua epsilon
==========================================================================
epsilon sai phân trung tâm sai phân tiến
1.0e-01 2.224e-03 6.075e-02
3.2e-03 2.225e-06 1.854e-03
1.0e-04 2.226e-09 5.855e-05
3.2e-06 1.717e-11 1.851e-06
1.0e-07 6.633e-10 5.878e-08
3.2e-09 2.415e-08 4.052e-08
1.0e-10 4.246e-07 8.008e-07
3.2e-12 1.975e-05 3.731e-05
1.0e-13 6.267e-04 9.376e-04
Sai so nho nhat cua sai phan trung tam tai epsilon = 5.62e-06 (sai so 1.16e-11)
Ly thuyet: epsilon* ~ u^(1/3) = 6.06e-06 (u = 2.22e-16)
Voi sai phan tien thi epsilon* ~ u^(1/2) = 1.49e-08, do duoc 1.78e-08
Epsilon NHO QUA thi sai so LON LEN: tru hai so gan bang nhau lam mat chu so co nghia.
==========================================================================
(D) Perceptron: chan Novikoff va truong hop khong tach duoc tuyen tinh
==========================================================================
lề gamma R = max||x|| chặn (R/gamma)^2 số lần sai đo được hội tụ
0.300 1.3273 19.5 4 True
0.201 1.2631 39.5 4 True
0.100 1.1703 136.8 10 True
0.050 1.1519 526.9 14 True
Dinh ly Novikoff: so lan sai <= (R/gamma)^2, khong phu thuoc so diem hay so chieu.
Bang tren: cot 4 luon nho hon cot 3 -> chan dung, va no khong chat.
Du lieu KHONG tach duoc tuyen tinh: hoi tu = False, so lan sai sau 2000 luot = 17,977
Perceptron khong dung lai va khong co bao dam gi: no chi duoc chung minh cho
truong hop tach duoc. Day la ly do can ham mat mat tron nhu hoi quy logistic.
==========================================================================
(E) Hoi quy logistic va softmax: hai ten goi cua cung mot thu khi K = 2
==========================================================================
Sai khac lon nhat giua xac suat cua logistic va cua softmax(K=2): 3.331e-16
Hieu hai cot trong so cua softmax: [ 0.10864027 1.53215349 -1.83422929]
Trong so cua hoi quy logistic: [ 0.10864027 1.53215349 -1.83422929]
-> lech 1.554e-15
Tong hai cot trong so cua softmax (phai bang 0): 6.661e-16
Softmax co K vector trong so nhung chi HIEU cua chung la xac dinh duoc:
cong cung mot vector vao moi cot thi xac suat khong doi. Voi K = 2, hieu
ay chinh la vector trong so cua hoi quy logistic -> hai mo hinh la MOT.
số vòng lặp ||w|| khi lam = 0 ||w|| khi lam = 0,01
500 10.164 3.993
2000 15.930 3.993
10000 24.703 3.993
50000 34.784 3.993
Du lieu tach duoc thi hop ly cuc dai KHONG co nghiem huu han: cang tang ||w||
thi likelihood cang tang. Phat chuan chan dieu do lai -> day la ly do BAT BUOC.
==========================================================================
(F) Naive Bayes / LDA / QDA: khac nhau o gia thiet ve ma tran hiep phuong sai
==========================================================================
mô hình giả thiết Sigma độ chính xác
Naive Bayes Gauss (riêng, chéo) riêng từng lớp, chéo 0.8550
LDA (dùng chung, đầy đủ) dùng chung, đầy đủ 0.8550
QDA (riêng, đầy đủ) riêng từng lớp, đầy đủ 0.8600
NB dùng chung Sigma chéo dùng chung, chéo 0.8550
Kiem chung dang thuc: tich cac Gauss MOT CHIEU == Gauss NHIEU CHIEU, Sigma cheo
hai cach tinh duoc cai dat hoan toan doc lap nhau trong ma nguon.
sai khac lon nhat cua hieu diem phan biet: 1.222e-08
hai mo hinh du bao giong nhau o 100.0% so mau
ham quyet dinh cua LDA co TUYEN TINH khong? sai so khop tuyen tinh = 4.441e-15 -> co
con QDA thi sao? sai so khop tuyen tinh = 1.780e+01 -> khong, no bac hai
Day la ca cau chuyen: ba mo hinh chi khac nhau o RANG BUOC dat len Sigma.
Gia thiet 'naive' cua Naive Bayes chinh la ep Sigma thanh ma tran cheo.
==========================================================================
(G) Do chinh xac noi doi tren du lieu mat can bang
==========================================================================
Ty le lop duong: 0.98% (196 tren 20000 mau)
bộ phân loại độ chính xác precision recall F1
đoán TẤT CẢ là âm 0.9902 — 0.0000 0.0000
ngưỡng 2,6 0.9900 0.4928 0.5204 0.5062
ngưỡng 1,0 0.8408 0.0559 0.9592 0.1056
ngưỡng -1,0 0.1699 0.0117 1.0000 0.0231
ROC-AUC = 0.9715 nhung PR-AUC = 0.4931
PR-AUC cua bo doan ngau nhien chinh bang ty le lop duong = 0.0100
ROC-AUC trong dep vi mau so cua FPR la so luong lop AM, ma lop am thi rat nhieu;
them vai bao dong gia khong lam FPR nhuc nhich. PR-AUC khong bi the.
==========================================================================
(H) Phat chuan la tien nghiem: ridge == MAP Gauss, lasso == MAP Laplace
==========================================================================
lambda ly thuyet = sigma^2/tau^2 = 1.0/0.49 = 2.040816
Sai khac giua ridge dang dong va MAP toi uu bang so: 1.973e-08
Hai duong tinh hoan toan khac nhau (mot giai he tuyen tinh, mot chay BFGS tren
hau nghiem) nhung ra CUNG MOT nghiem -> phat chuan L2 chinh la tien nghiem Gauss.
lambda ||w|| số hệ số = 0 (ridge) số hệ số = 0 (lasso)
0.01 4.0106 0 0
0.10 4.0056 0 1
1.00 3.9559 0 1
10.00 3.5234 0 7
100.00 1.7090 0 9
Ridge KHONG BAO GIO cho he so bang dung 0; lasso thi co. Ly do nam o dao ham
tai goc: |w| co diem gay nen nghiem bi 'dinh' vao 0, con w^2 thi tron.
(su that: mo hinh sinh du lieu co 9 he so bang 0)
Ridge qua SVD so voi dang dong: lech 2.304e-15
trị kỳ dị d_i hệ số co ngót d^2/(d^2+lam)
12.2680 0.9377
12.0678 0.9357
8.3651 0.8750
6.4450 0.8060
6.1730 0.7921
Huong co tri ky di LON gan nhu khong bi dong toi; huong YEU bi co manh.
Ridge khong co deu moi huong -> no co dung nhung huong du lieu noi it nhat.
==========================================================================
(I) Tinh loi: kiem tra bang dinh nghia, va hau qua len toi uu hoa
==========================================================================
hàm vi phạm lớn nhất tỉ lệ vi phạm kết luận
$x^2$ -1.497e-09 0.0000 lồi
$|x|$ 0.000e+00 0.0000 lồi
$e^x$ -3.987e-09 0.0000 lồi
$\log(1+e^x)$ (mất mát logistic) -2.535e-11 0.0000 lồi
$x^4 - 3x^2$ 2.237e+00 0.2075 KHÔNG
$\sin x$ 1.972e+00 0.5051 KHÔNG
$x^3$ 1.317e+01 0.4985 KHÔNG
Phep thu nay khong CHUNG MINH tinh loi, nhung mot phan vi du thi du de BAC BO.
Xuat phat tu 21 diem khac nhau roi chay gradient descent:
ham LOI x^2 : so diem dung khac nhau = 1, gia tri = 0.0000
ham KHONG LOI x^4-3x^2+x/2: so diem dung khac nhau = 2, cac gia tri = [-1.2645 1.1807]
gia tri ham tai hai diem do: [-2.8725, -1.6484]
Voi ham loi, MOI cuc tieu dia phuong deu la cuc tieu toan cuc, nen diem khoi tao
khong quan trong. Day la ly do ta quan tam toi tinh loi den vay.
==========================================================================
(J) SVM: kiem chung doi ngau manh va dieu kien KKT bang so
==========================================================================
Dung lai w tu nghiem doi ngau: lech so voi coef_ cua sklearn = 0.000e+00
Vector ho tro co thoa y_i(w.x_i + b) = 1 khong? lech lon nhat = 2.523e-08
So vector ho tro: 3 tren 120 diem (2.5%)
Doi ngau manh:
gia tri bai toan goc 0.5*||w||^2 = 1.1037868651
gia tri bai toan doi ngau = 1.1037868254
khe doi ngau = 3.961e-08
Le = 2/||w|| = 1.346085; khoang cach nho nhat tu diem toi sieu phang x 2 = 1.346085
Hai lop chong lan nhau (le mem la bat buoc), quet C:
C lề 2/||w|| số VTHT đúng trên lề vi phạm lề sai số h.luyện sai số kiểm tra
0.003 5.6121 114 0 114 0.1500 0.1795
0.030 2.6409 66 3 63 0.1750 0.1727
0.300 1.8286 48 3 45 0.1583 0.1790
3.000 1.7312 45 3 42 0.1667 0.1795
30.000 1.7312 45 3 42 0.1667 0.1795
300.000 1.7311 45 3 42 0.1667 0.1795
C tot nhat tren tap kiem tra: C = 0.03 (sai so 0.1727); C lon nhat cho 0.1795
C nho -> le RONG, nhieu diem duoc phep vi pham, mo hinh don gian hon.
C lon -> le HEP, co ep tung diem cho dung, bat dau bam vao nhieu.
Dieu kien KKT chia cac diem lam ba nhom, va bang tren dem dung ba nhom ay:
alpha = 0 -> nam NGOAI le, khong anh huong gi toi nghiem
0 < alpha < C -> nam DUNG TREN le
alpha = C -> VI PHAM le (lot vao trong, hoac sang han phia sai)
Du lieu hai vong tron dong tam:
nhân tuyến tính do chinh xac = 0.6150
nhân đa thức bậc 2 do chinh xac = 1.0000
nhân RBF do chinh xac = 1.0000
Thu thuat nhan: thay vi anh xa x -> phi(x) roi tinh tich vo huong, ta tinh thang
K(x, x') = phi(x).phi(x'). Voi RBF thi phi(x) o khong gian VO HAN chieu.
==========================================================================
(K) PCA: sai so tai dung DUNG BANG tong cac tri rieng bi bo
==========================================================================
Hai duong tinh PCA:
tri rieng cua ma tran hiep phuong sai : [11.52671 4.855133 2.064187 0.142466]
d_i^2/(n-1) tu SVD : [11.52671 4.855133 2.064187 0.142466]
lech lon nhat tren ca 8 tri: 7.105e-15
goc giua cac truc chinh (tri tuyet doi tich vo huong phai bang 1):
[1. 1. 1. 1.]
k ||X - X_k||_F^2 (n-1) * tổng trị riêng bỏ lệch % phương sai giữ
1 2242.010603 2242.010603 9.09e-13 60.59
2 790.325815 790.325815 4.55e-13 86.11
3 173.133807 173.133807 6.25e-13 96.96
4 130.536554 130.536554 4.26e-13 97.71
5 92.184562 92.184562 3.84e-13 98.38
6 58.835740 58.835740 2.13e-13 98.97
7 26.645634 26.645634 6.04e-14 99.53
8 0.000000 0.000000 3.56e-27 100.00
Day la dinh ly Eckart-Young: phep chieu PCA la xap xi hang k TOT NHAT theo
chuan Frobenius, va sai so cua no dung bang tong cac tri rieng bi bo di.
Du lieu co mot huong nhieu bien do lon va mot huong tin hieu bien do nho:
truc chinh thu nhat cua PCA: [-1. 0.0067] (giu 96.1% phuong sai)
huong cua LDA : [0.0025 1. ]
AUC khi chieu len truc PCA : 0.5020
AUC khi chieu len huong LDA: 0.9987
PCA giu gan het phuong sai nhung vut di toan bo kha nang phan loai. Ly do:
PCA KHONG NHIN NHAN. Phuong sai lon khong dong nghia co ich.
==========================================================================
(L) K-means: khoi tao quyet dinh ket qua, va gia thiet ngam ve hinh dang cum
==========================================================================
Chay 200 lan voi 8 cum, moi lan mot hat giong khac nhau:
cách khởi tạo quán tính nhỏ nhất trung bình % lần kẹt ở nghiệm tồi
ngau nhien 407.992 517.838 71.5
k-means++ 407.992 476.529 46.5
K-means chi bao dam hoi tu toi cuc tieu DIA PHUONG. k-means++ rai cac tam ban
dau cho xa nhau theo xac suat ti le d^2, nen it roi vao nghiem toi hon han.
Do chinh xac gom cum (da thu moi cach ghep nhan, lay cach tot nhat):
ba cum TRON, tach roi : 1.0000
hai hinh LUOI LIEM long nhau : 0.7517
hai DAI DET nam ngang, cach nhau : 0.5317
K-means gan moi diem cho tam GAN NHAT theo khoang cach Euclid, nen bien giua hai
cum luon la mot sieu phang. Cum khong loi hoac bi keo det thi gia thiet do sai.
k quán tính giảm so với k-1 (dữ liệu có đúng 8 cụm)
1 7119.53 —
2 3174.48 55.4%
3 1992.98 37.2%
4 1150.93 42.3%
5 902.24 21.6%
6 727.79 19.3%
7 563.93 22.5%
8 407.99 27.7% <-- khuỷu tay
9 385.82 5.4%
10 364.76 5.5%
11 342.56 6.1%
12 323.40 5.6%
Quan tinh LUON giam khi k tang (k = n thi bang 0), nen KHONG the chon k bang
cach toi thieu no. 'Khuyu tay' la cho muc giam dot ngot cham han lai.
==========================================================================
(M) He goi y: nguong du lieu de phan ra ma tran hoat dong, va khoi dau lanh
==========================================================================
Ma tran danh gia: 300 nguoi x 200 muc, hang that = 4, nhieu quan sat sd = 0.4
mo hinh hang 4 co 2,000 tham so tu do, so voi 60,000 o -> 3.3%
do lech chuan cua tin hieu that: 2.0772
% ô thấy được đánh giá / người RMSE ô chưa thấy so với đoán bừa
2 4.1 2.4759 1.19
3 5.9 3.0167 1.45
5 10.1 3.5444 1.71
10 19.8 0.3048 0.15
20 39.6 0.1834 0.09
40 80.5 0.1237 0.06
Nguong rat sac net: duoi ~20 danh gia moi nguoi thi phan ra ma tran HONG HAN
(sai so con te hon doan bua bang 0); tu khoang 5 lan so hang tro len thi no
khoi phuc gan nhu chinh xac. Ly do: moi nguoi dung co 4 an so can uoc luong,
nen ho phai co du rang buoc thi he moi giai duoc.
Chon hang k (do o mat do 30%, noi viec khop la dang tin):
hạng k RMSE ô chưa thấy RMSE trên ô đã thấy
1 1.8073 1.7769
2 1.4764 1.4206
3 1.0569 1.0128
4 0.1448 0.3762 <- hang dung
6 0.2217 0.3535
10 0.3501 0.3076
20 0.6291 0.1903
Sai so tren o DA THAY giam don dieu theo k (mo hinh manh hon thi khop chat hon),
nhung sai so tren o CHUA THAY cham day o dung hang that roi tang len. Day chinh
la danh doi thien lech - phuong sai, lan nay o dang chon hang.
Khoi dau lanh: 10 nguoi dung MOI, khong co lay mot danh gia nao
RMSE cho nguoi dung cu : 0.1459
RMSE cho nguoi dung moi : 2.2364
RMSE neu doan bua bang 0 : 2.0772
||P|| trung binh, nguoi cu : 2.0841
||P|| trung binh, nguoi moi: 1.54e-01
Vector cua nguoi dung moi bi keo thang ve 0 vi khong co rang buoc nao ngoai phat
chuan, nen mo hinh du bao 0 cho tat ca. Do la ly do he thuc te luon can mot duong
du phong dua tren NOI DUNG: dac trung cua muc va ho so khai bao cua nguoi dung.
==========================================================================
Da luu 10 hinh vao thu muc figs/: nt02, nt03, nt04, nt06, nt07, nt08, nt09, nt10, nt11, nt12
==========================================================================
code/nentang/fig_diagrams.py
viết thêm cho repo nàyTải vềSinh Hình 1, 5 và 13: ba thành phần của một thuật toán học có giám sát, bốn hàm mất mát phân loại vẽ trên cùng một trục, và sơ đồ sáu giáo trình của lộ trình.
Xem mã nguồn
"""Các sơ đồ khái niệm cho giáo trình "Nền tảng học máy".
Đây là hình vẽ tay bằng matplotlib chứ không phải kết quả thí nghiệm, để trong mã
nguồn cho sửa được và cho khớp với chữ trong bài.
python code/nentang/fig_diagrams.py
"""
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.patches import FancyBboxPatch, FancyArrowPatch
OUT = "figs/"
plt.rcParams.update({"figure.dpi": 150, "font.size": 9})
C_MAIN, C_WARN, C_BAD, C_DIM = "#1f6f68", "#b8860b", "#b0413e", "#8a857c"
C_SOFT, C_LINE = "#eef4f3", "#cfd8d6"
def hop(ax, x, y, w, h, text, fc=C_SOFT, ec=C_MAIN, fs=8.5, weight="normal", tc="#1a1a1a"):
ax.add_patch(FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.012,rounding_size=0.02",
facecolor=fc, edgecolor=ec, linewidth=1.1))
ax.text(x + w / 2, y + h / 2, text, ha="center", va="center",
fontsize=fs, color=tc, weight=weight, linespacing=1.45)
def mui_ten(ax, p0, p1, color=C_DIM, lw=1.1, style="-|>"):
ax.add_patch(FancyArrowPatch(p0, p1, arrowstyle=style, mutation_scale=11,
color=color, linewidth=lw,
shrinkA=2, shrinkB=2))
# ---------------------------------------------------------------------
# Hình 1 — Ba thành phần của mọi thuật toán học có giám sát
# ---------------------------------------------------------------------
fig, ax = plt.subplots(figsize=(8.6, 4.5))
ax.set_xlim(0, 1); ax.set_ylim(0, 1); ax.axis("off")
ax.text(.5, .955, "Các thành phần của một thuật toán học có giám sát",
ha="center", fontsize=11.5, weight="bold", color="#1a1a1a")
hop(ax, .04, .70, .27, .16,
"1. MÔ HÌNH\n$f_\\theta(x)$\nhọ hàm được phép chọn", fs=8.5, weight="bold")
hop(ax, .365, .70, .27, .16,
"2. HÀM MẤT MÁT\n$L(f_\\theta(x),\\, y)$\nđo mức sai của dự đoán", fs=8.5, weight="bold")
hop(ax, .69, .70, .27, .16,
"3. THUẬT TOÁN TỐI ƯU\n$\\min_\\theta \\sum_i L$\ncách tìm $\\theta$", fs=8.5, weight="bold")
vd = [("Hồi quy tuyến tính", "$w^\\top x + b$", "bình phương sai số",
"phương trình chuẩn\nhoặc gradient descent"),
("Hồi quy logistic", "$\\sigma(w^\\top x + b)$", "cross-entropy", "gradient descent"),
("SVM lề mềm", "$w^\\top x + b$", "hinge $+\\;\\lambda\\|w\\|^2$",
"quy hoạch toàn phương\n(bài toán đối ngẫu)"),
("Cây quyết định", "hàm hằng từng khúc", "Gini / entropy",
"tham lam, chia từng nút"),
("Mạng nơ-ron", "hợp nhiều lớp affine\nvà phi tuyến", "tuỳ bài toán",
"SGD + lan truyền ngược")]
y0, dy = .575, .102
ax.text(.04, y0 + .055, "Năm thuật toán, mô tả theo ba thành phần:", fontsize=9,
color=C_DIM, style="italic")
for i, (ten, mh, mm, tu) in enumerate(vd):
y = y0 - i * dy
ax.add_patch(FancyBboxPatch((.04, y - .045), .92, .088,
boxstyle="round,pad=0.006,rounding_size=0.012",
facecolor="#fbfbfa" if i % 2 else "#f4f6f5",
edgecolor=C_LINE, linewidth=.7))
ax.text(.065, y, ten, fontsize=8.6, weight="bold", va="center", color=C_MAIN)
ax.text(.30, y, mh, fontsize=8.2, va="center", ha="center")
ax.text(.545, y, mm, fontsize=8.2, va="center", ha="center")
ax.text(.82, y, tu, fontsize=8.2, va="center", ha="center", linespacing=1.3)
for x in (.175, .50, .825):
mui_ten(ax, (x, .695), (x, .645), color=C_LINE, lw=1.0)
ax.text(.5, .022,
"Thay một thành phần là được một thuật toán khác.",
ha="center", fontsize=8.6, color=C_DIM, style="italic", linespacing=1.5)
fig.tight_layout(); fig.savefig(OUT + "nt01_bando.png"); plt.close(fig)
# ---------------------------------------------------------------------
# Hình 5 — Bốn hàm mất mát cho phân loại: chúng đều thay cho mất mát 0–1
# ---------------------------------------------------------------------
fig, ax = plt.subplots(1, 2, figsize=(8.8, 3.3))
m = np.linspace(-3, 3, 600)
loss = [("mất mát 0–1", np.where(m <= 0, 1.0, 0.0), "k", 2.0, "-"),
("perceptron: $\\max(0, -m)$", np.maximum(0, -m), C_WARN, 1.6, "-"),
("hinge (SVM): $\\max(0, 1-m)$", np.maximum(0, 1 - m), C_BAD, 1.6, "-"),
("logistic: $\\log_2(1+e^{-m})$", np.log2(1 + np.exp(-m)), C_MAIN, 1.6, "-")]
for lab, v, c, lw, ls in loss:
ax[0].plot(m, v, label=lab, color=c, lw=lw, ls=ls)
ax[0].set_xlabel("lề $m = y\\,(w^\\top x + b)$")
ax[0].set_ylabel("mất mát")
ax[0].set_ylim(-.1, 3.1); ax[0].axvline(0, color=C_LINE, lw=.9)
ax[0].grid(alpha=.25)
ax[0].legend(frameon=False, fontsize=7.6, loc="upper right")
ax[0].set_title("Các hàm mất mát theo lề", fontsize=9)
ax[0].spines[["top", "right"]].set_visible(False)
ax[1].axis("off")
ax[1].set_xlim(0, 1); ax[1].set_ylim(0, 1)
rows = [("mất mát 0–1", "không lồi, đạo hàm bằng 0", "không dùng gradient được"),
("perceptron", "lồi, bằng 0 khi $m \\geq 0$", "dừng khi vừa phân loại đúng"),
("hinge", "lồi, bằng 0 khi $m \\geq 1$", "đòi hỏi lề, nghiệm thưa"),
("logistic", "lồi, trơn, luôn dương", "cho ra xác suất")]
ax[1].text(.5, .93, "Tính chất và hệ quả", ha="center", fontsize=10, weight="bold")
for i, (a, b, c) in enumerate(rows):
y = .78 - i * .168
ax[1].add_patch(FancyBboxPatch((.02, y - .065), .96, .13,
boxstyle="round,pad=0.008,rounding_size=0.02",
facecolor="#fbfbfa" if i % 2 else "#f4f6f5",
edgecolor=C_LINE, linewidth=.7))
ax[1].text(.06, y + .028, a, fontsize=8.6, weight="bold", color=C_MAIN, va="center")
ax[1].text(.06, y - .03, b, fontsize=7.9, color="#333", va="center")
ax[1].text(.96, y, c, fontsize=7.9, color=C_DIM, va="center", ha="right", style="italic")
ax[1].text(.5, .075,
"Hinge và logistic (log cơ số 2) là chặn trên của mất mát 0–1;\n"
"mất mát perceptron thì không, vì bằng 0 tại $m = 0$.",
ha="center", fontsize=8.2, color=C_DIM, style="italic", linespacing=1.5)
fig.tight_layout(); fig.savefig(OUT + "nt05_matmat.png"); plt.close(fig)
# ---------------------------------------------------------------------
# Hình 13 — Sáu giáo trình của lộ trình kỹ sư AI và thứ tự đọc
# ---------------------------------------------------------------------
fig, ax = plt.subplots(figsize=(11.2, 5.6))
ax.set_xlim(0, 1); ax.set_ylim(0, 1); ax.axis("off")
ax.text(.5, .965, "Lộ trình sáu giáo trình", ha="center", fontsize=12.5, weight="bold")
phan = [
("1 · Nền tảng", C_MAIN,
["Đại số tuyến tính, xác suất", "Hồi quy, phân loại", "Gradient descent",
"Đánh giá, regularization", "Tối ưu lồi, SVM", "PCA, K-means, gợi ý"]),
("2 · Học sâu", "#2f6f9f",
["Độ chệch – phương sai", "Cây, học tập hợp", "MLP, lan truyền ngược",
"Khởi tạo, chuẩn hoá", "CNN, RNN", "Transformer, giải mã"]),
("3 · Biểu diễn & Căn chỉnh", "#b5651d",
["Embedding", "Học chuyển giao, LoRA", "VAE, GAN, khuếch tán",
"Học tăng cường", "RLHF", "DPO"]),
("4 · Ứng dụng LLM", "#b0413e",
["Prompt, ngữ cảnh", "Chọn, triển khai mô hình", "Tìm kiếm vector",
"RAG", "Agent, MCP", "Đánh giá, an toàn"]),
("5 · Quantization", "#8a5fa8",
["Biểu diễn số", "Affine quantization", "Suy luận số nguyên",
"PTQ, QAT", "GPTQ, AWQ", "KV cache"]),
("6 · MLOps", C_WARN,
["Nợ kỹ thuật", "Dữ liệu, kiểm thử", "Phục vụ, ra mắt",
"Giám sát, dịch chuyển", "Huấn luyện lại", "LLMOps"]),
]
W, G = .148, .0184
x0 = (1 - (6 * W + 5 * G)) / 2
for k, (ten, col, muc) in enumerate(phan):
x = x0 + k * (W + G)
ax.add_patch(FancyBboxPatch((x, .33), W, .55,
boxstyle="round,pad=0.006,rounding_size=0.018",
facecolor="#fcfcfb", edgecolor=col, linewidth=1.4))
ax.add_patch(FancyBboxPatch((x, .785), W, .095,
boxstyle="round,pad=0.006,rounding_size=0.018",
facecolor=col, edgecolor=col, linewidth=1.4))
ax.text(x + W / 2, .832, ten, ha="center", va="center", fontsize=7.9,
weight="bold", color="white")
for i, m_ in enumerate(muc):
ax.text(x + .01, .705 - i * .066, "· " + m_, fontsize=6.9, va="center", color="#2a2a2a")
if k < 5:
mui_ten(ax, (x + W + .001, .56), (x + W + G - .001, .56), color="#6b7a78", lw=1.6)
# ba chặng lớn bên dưới
chang = [(0, 1, "Kiến thức nền", C_MAIN), (2, 3, "Mô hình ngôn ngữ và ứng dụng", "#b0413e"),
(4, 5, "Tối ưu và vận hành", C_WARN)]
for a_, b_, ten, col in chang:
xa = x0 + a_ * (W + G); xb = x0 + b_ * (W + G) + W
ax.plot([xa + .004, xb - .004], [.285, .285], color=col, lw=2.2, solid_capstyle="round")
ax.text((xa + xb) / 2, .245, ten, ha="center", va="center", fontsize=8.6, color=col, weight="bold")
ax.text(.5, .12,
"Giáo trình 1 và 2 là kiến thức nền mà roadmap AI Engineer giả định người học đã có; giáo trình 3 và 4 đi theo\n"
"các chặng của roadmap từ cách mô hình ngôn ngữ được huấn luyện tới cách xây ứng dụng trên nó; giáo trình 5 và 6\n"
"trình bày cách làm mô hình chạy hiệu quả và vận hành ổn định trong sản xuất.",
ha="center", fontsize=7.9, color="#333", linespacing=1.6)
fig.tight_layout(); fig.savefig(OUT + "nt13_mach.png"); plt.close(fig)
print("Da luu 3 hinh: nt01_bando, nt05_matmat, nt13_mach")