Mở đầu: bạn không thể viết rule cho mọi thứ

Hãy thử viết một hàm nhận diện chữ số 7 viết tay. Bạn bắt đầu: "nét ngang trên cùng, rồi nét xiên xuống trái". Nhưng nét ngang dài bao nhiêu pixel? Nghiêng bao nhiêu độ thì vẫn là 7, bao nhiêu thì thành 1? Người Pháp gạch ngang giữa thân số 7 thì sao? Mực đậm nhạt, giấy nhăn, chụp lệch góc? Mỗi câu trả lời đẻ thêm mười trường hợp ngoại lệ — và đây mới là một chữ số, trong bài toán được coi là "dễ nhất" của thị giác máy. Rule viết tay sụp đổ không phải vì lập trình viên kém, mà vì bản thân tri thức "thế nào là số 7" không tồn tại dưới dạng quy tắc rõ ràng — nó nằm rải rác trong hàng nghìn ví dụ.

Học máy đảo ngược cách làm: thay vì người viết rule, ta đưa máy các ví dụ và để nó tự tìm quy luật. Bài mở màn của lộ trình 19 bài này dạy phiên bản nhỏ nhất có thể của ý tưởng đó: hồi quy tuyến tính — fit một đường thẳng qua đám điểm dữ liệu. Nhỏ, nhưng chứa đủ bộ xương của mọi hệ thống AI bạn sẽ gặp trong 18 bài sau: model có tham số, hàm loss đo độ sai, và dữ liệu làm thầy. Cuối bài, bạn kéo thả điểm trên canvas và xem đường thẳng tự điều chỉnh theo — cùng công thức mà chính GPT dùng để học, chỉ khác quy mô.


📚 Điều kiện tiên quyết
Bắt buộc: đại số phổ thông — phương trình đường thẳng $y = ax + b$, trung bình cộng; JavaScript căn bản (biến, vòng lặp, hàm) — Series JS Bài 1 nếu cần ôn. Không cần biết trước bất kỳ khái niệm AI/ML nào.
Tài nguyên ngoài: Khan Academy — Statistics, PyTorch (cho snippet đối chiếu cuối bài, không bắt buộc).

1. Từ quy tắc viết tay đến học từ dữ liệu

Lập trình truyền thống là: quy tắc + dữ liệu vào → đáp án ra. Bạn viết if (nhiet_do > 38) return 'sot'; — quy tắc do người nghĩ ra, máy chỉ thực thi. ML lật ngược sơ đồ: dữ liệu vào + đáp án mẫu → máy tự tìm quy tắc. Ta không bảo máy "số 7 có nét ngang" — ta đưa 10.000 tấm ảnh kèm nhãn đúng, máy tự điều chỉnh các tham số bên trong cho tới khi dự đoán khớp nhãn. Quy tắc học được không nằm dưới dạng if/else đọc được, mà nằm trong các con số — chính là "tham số" ta sẽ gặp ngay ở Mục 2.

Học máy có 3 nhánh lớn, phân biệt bằng việc dữ liệu cho máy cái gì:

Nhánh Dữ liệu có gì Ví dụ Học ở đâu trong series
Học có giám sát (supervised) Từng ví dụ kèm đáp án đúng (nhãn) Ảnh → chữ số; diện tích nhà → giá Bài này tới Bài 3, và hầu hết series
Học không giám sát (unsupervised) Chỉ có dữ liệu, không nhãn Gom khách hàng thành nhóm tự nhiên Bài 4 (K-means, PCA)
Học tăng cường (reinforcement) Không đáp án, chỉ có điểm thưởng thưa và đến trễ Chơi game, robot, RLHF của ChatGPT Bài 17 (Q-learning)

Bài này thuộc nhánh thứ nhất — dạng bài "cho x, đoán y" với y là số liên tục (giá nhà, nhiệt độ, doanh thu), gọi là hồi quy (regression). Khi y là loại (spam/không spam, chó/mèo) thì gọi là phân loại — Bài 3 sẽ xử lý.

⚠️ Cạm bẫy: dùng ML cho bài toán mà 3 dòng if-else giải được
Chiều ngược lại của "rule không viết nổi" cũng là một cái bẫy: nếu quy tắc tồn tại rõ ràng — "đơn hàng trên 500k được freeship", "số chẵn là số chia hết cho 2" — thì viết rule luôn thắng ML tuyệt đối: chạy nhanh hơn, đúng 100% thay vì 99%, giải thích được từng quyết định, và không cần thu thập dữ liệu hay train lại khi có bug. ML chỉ đáng giá khi quy tắc không thể viết ra (ảnh, âm thanh, ngôn ngữ) hoặc đổi liên tục theo dữ liệu (gợi ý sản phẩm, chống gian lận). Một hệ thống ML là một khoản nợ bảo trì: dữ liệu phải cập nhật, model phải theo dõi, kết quả khó giải thích — đừng trả cái giá đó cho bài toán mà một câu if giải xong.

2. Bộ ba Model – Loss – Data

Mọi hệ thống học máy — từ đường thẳng bài này tới GPT — đều gồm đúng 3 mảnh. Ta sẽ gọi tên chúng trên một bài toán cụ thể: dự đoán giá nhà (y, đơn vị trăm triệu) từ diện tích (x, đơn vị chục m²), với 4 căn đã bán được ghi lại: $(1, 2)$, $(2, 3)$, $(3, 5)$, $(4, 6)$.

Mảnh 1 — Model (giả thuyết): ta giả định quan hệ có dạng đường thẳng:

$$\hat{y} = w \cdot x + b$$

Trong đó $\hat{y}$ (đọc "y mũ") là dự đoán của model, $x$ là đầu vào, còn $w$ (độ dốc — weight) và $b$ (điểm cắt trục tung — bias) là 2 tham số học được. "Học", với model này, nghĩa đen là: tìm 2 con số $w, b$ sao cho đường thẳng đi sát đám điểm nhất. GPT-4 cũng chỉ là phiên bản phóng đại của câu này — thay vì 2 tham số, nó có hàng nghìn tỷ.

Mảnh 2 — Loss (hàm mất mát): "sát đám điểm nhất" phải đo được bằng một con số, nếu không thì không so sánh được hai đường thẳng với nhau. Chuẩn đo phổ biến nhất cho hồi quy là MSE:

$$L(w, b) = \frac{1}{n} \sum_{i=1}^{n} \left( \hat{y}_i - y_i \right)^2$$

Giải nghĩa: với từng điểm dữ liệu thứ $i$, lấy dự đoán $\hat{y}_i$ trừ đáp án thật $y_i$ (hiệu này gọi là residual — phần dư), bình phương lên, rồi lấy trung bình trên cả $n$ điểm. Loss càng nhỏ, đường càng khớp; loss bằng 0 nghĩa là đường xuyên qua đúng mọi điểm. Vì sao bình phương mà không lấy trị tuyệt đối? Hai lý do thật sự quan trọng: (1) bình phương phạt lỗi lớn nặng hơn hẳn — sai 2 bị phạt 4, sai 10 bị phạt 100, ép model ưu tiên sửa những chỗ sai to; (2) hàm bình phương khả vi tại mọi điểm (trơn tru, không gãy khúc như trị tuyệt đối tại 0) — điều kiện sống còn cho cả nghiệm giải tích ở Mục 3 lẫn gradient descent ở Bài 2.

mse.js (chạy đúng trong demo cuối bài và file tải về)
// MSE — trung binh binh phuong sai so giua du doan (w*x + b) va dap an y
function mse(points, w, b) {
  let s = 0;
  for (const p of points) {
    const err = w * p.x + b - p.y; // residual cua tung diem
    s += err * err;                // binh phuong: phat manh loi lon
  }
  return s / points.length;        // trung binh tren ca n diem
}

Mảnh 3 — Data: 4 điểm $(x, y)$ ở trên. Dữ liệu là "thầy giáo" duy nhất: model không biết gì về nhà đất, nó chỉ biết kéo loss xuống thấp nhất trên những ví dụ được đưa. Hệ quả trực tiếp — chất lượng model bị chặn trên bởi chất lượng dữ liệu, và đây là nơi cạm bẫy đầu tiên của nghề xuất hiện:

⚠️ Cạm bẫy: MSE nhạy outlier — 1 điểm nhập sai kéo lệch cả đường

Chính ưu điểm "phạt mạnh lỗi lớn" là con dao hai lưỡi: một điểm dữ liệu nhập sai tạo lỗi khổng lồ, bình phương lên nó lấn át mọi điểm đúng cộng lại. Thử bằng số thật (đã kiểm chứng bằng self-test trong file tải về): với 4 điểm ví dụ, đường fit là $w = 1{,}4$, $b = 0{,}5$, $R^2 = 0{,}98$ — gần hoàn hảo. Giờ thêm đúng một điểm gõ nhầm $(4, 60)$ — ai đó nhập 6,0 thành 60:

  • $w$: từ $1{,}4$ nhảy lên $\approx 10{,}91$ — độ dốc tăng gần 8 lần;
  • $b$: từ $0{,}5$ rơi xuống $\approx -15{,}35$;
  • $R^2$: từ $0{,}98$ sụp còn $\approx 0{,}32$ — đường fit gần như vô dụng.

Bốn điểm đúng không cứu nổi một điểm sai. Bài học thực chiến: luôn nhìn dữ liệu trước khi train (vẽ nó ra!) và lọc/kiểm outlier — đừng tin mù quáng vào con số loss. Bấm nút "💥 Thêm outlier" trong demo Mục 5 để tận mắt xem đường thẳng bị giật khỏi đám điểm.

Hậu quả thực tế cụ thể ra sao? Thử dùng cả hai model để đoán giá một căn nhà mới 50m² ($x = 5$, đã kiểm chứng bằng số): model sạch cho $\hat{y} = 1{,}4 \times 5 + 0{,}5 = 7{,}5$ (750 triệu đồng); model bị outlier kéo lệch cho $\hat{y} = 10{,}91 \times 5 - 15{,}35 \approx 39{,}21$ (3,92 tỷ đồng) — chênh lệch hơn 3,1 tỷ đồng chỉ vì một ô dữ liệu gõ nhầm. Đây không phải sai số học thuật trừu tượng — đây là tiền thật bị định giá sai gấp hơn 5 lần.

3. Nghiệm giải tích: cho đạo hàm bằng 0

Vậy tìm $w, b$ tốt nhất bằng cách nào? Với model tuyến tính + MSE, may mắn hiếm hoi của toán học xảy ra: loss $L(w, b)$ là một mặt parabol lồi (hình cái bát) theo cả $w$ lẫn $b$ — có đúng một điểm thấp nhất, và tại đó đạo hàm theo cả hai tham số đều bằng 0. Giải hệ "đạo hàm = 0" cho ra công thức đóng (closed-form), gọi là nghiệm least squares:

$$w = \frac{\sum_{i} (x_i - \bar{x})(y_i - \bar{y})}{\sum_{i} (x_i - \bar{x})^2} \qquad b = \bar{y} - w \bar{x}$$

Giải nghĩa: $\bar{x}, \bar{y}$ là trung bình cộng của các $x_i$ và $y_i$; tử số của $w$ đo mức $x$ và $y$ cùng dao động quanh trung bình của chúng (hiệp phương sai), mẫu số đo độ trải của riêng $x$ (phương sai); còn $b$ được suy ra từ việc đường fit tốt nhất luôn đi qua điểm trung bình $(\bar{x}, \bar{y})$. Tính tay từng bước với 4 điểm dữ liệu của Mục 2:

Bước Tính Kết quả
Trung bình $\bar{x} = (1+2+3+4)/4$ ; $\bar{y} = (2+3+5+6)/4$ $\bar{x} = 2{,}5$ ; $\bar{y} = 4$
Tử số của $w$ $(-1{,}5)(-2) + (-0{,}5)(-1) + (0{,}5)(1) + (1{,}5)(2)$ $3 + 0{,}5 + 0{,}5 + 3 = 7$
Mẫu số của $w$ $(-1{,}5)^2 + (-0{,}5)^2 + (0{,}5)^2 + (1{,}5)^2$ $2{,}25 + 0{,}25 + 0{,}25 + 2{,}25 = 5$
$w$ và $b$ $w = 7/5$ ; $b = 4 - 1{,}4 \times 2{,}5$ $w = 1{,}4$ ; $b = 0{,}5$
Kiểm lại MSE residual: $0{,}1$; $-0{,}3$; $0{,}3$; $-0{,}1$ → $(0{,}01+0{,}09+0{,}09+0{,}01)/4$ $L = 0{,}05$

Đường fit tốt nhất là $\hat{y} = 1{,}4x + 0{,}5$: mỗi chục m² thêm vào, giá tăng 140 triệu. Mọi con số trong bảng đều tái lập được bằng file linear_regression.js tải về cuối bài — chạy node linear_regression.js là thấy self-test đối chiếu đúng từng giá trị.

fit_least_squares.js (nghiệm giải tích — đúng code demo dùng)
function fitLeastSquares(points) {
  const n = points.length;
  if (n < 2) return null; // 1 diem co vo so duong di qua — khong fit duoc
  let sumX = 0, sumY = 0;
  for (const p of points) { sumX += p.x; sumY += p.y; }
  const meanX = sumX / n, meanY = sumY / n;
  let sxy = 0, sxx = 0;
  for (const p of points) {
    sxy += (p.x - meanX) * (p.y - meanY); // tu so: x va y cung dao dong
    sxx += (p.x - meanX) * (p.x - meanX); // mau so: do trai cua rieng x
  }
  if (sxx === 0) return null; // moi diem cung hoanh do — duong thang dung
  const w = sxy / sxx;
  return { w, b: meanY - w * meanX };
}

Đẹp như vậy, vì sao 18 bài còn lại không dùng tiếp công thức đóng? Vì nó là đặc sản chỉ có ở model tuyến tính + MSE. Chỉ cần model cong đi một chút (thêm hàm phi tuyến — Bài 6) hoặc tham số phình lên hàng triệu, hệ "đạo hàm = 0" không còn giải được bằng tay hay bằng công thức nào nữa. Khi đó ta cần chiến lược hoàn toàn khác: không giải phương trình nữa, mà dò dốc từng bước nhỏ — đứng ở điểm hiện tại, nhìn hướng nào làm loss giảm, bước một bước nhỏ về hướng đó, lặp lại nghìn lần.

🧭 Sẽ học ở Bài 2: Gradient Descent
Phương pháp "dò dốc từng bước" ấy tên là gradient descent — trái tim của toàn bộ deep learning, và là nội dung trọn vẹn của Bài 2. Snippet PyTorch cuối bài này thực chất đã dùng nó (vòng lặp 2.000 bước) thay vì công thức đóng — bạn sẽ thấy cả hai cho ra cùng đáp án $w = 1{,}4$, $b = 0{,}5$ trên cùng dữ liệu: hai con đường, một đích đến.
🔬 Đào sâu: vì sao chắc chắn đó là điểm thấp nhất?
Với hàm bất kỳ, "đạo hàm = 0" có thể là đỉnh đồi, đáy thung lũng, hoặc điểm yên ngựa. Nhưng $L(w, b)$ của MSE là hàm bậc hai với hệ số bậc hai không âm theo từng tham số — mặt cong luôn "mở lên trời" như parabol $ax^2$ với $a > 0$. Hàm như vậy gọi là lồi (convex): chỉ có đúng một điểm dừng và đó chắc chắn là cực tiểu toàn cục. Đây là lý do hồi quy tuyến tính "hiền" nhất làng ML — không bao giờ kẹt ở nghiệm xấu. Từ Bài 6 trở đi, khi model có hàm phi tuyến, mặt loss trở nên gồ ghề với vô số thung lũng — và cuộc chơi đổi hẳn tính chất (Bài 2 sẽ cho bạn thấy trước địa hình đó).

4. Đánh giá model: R² và dữ liệu chưa thấy

MSE = 0,05 là tốt hay tệ? Không trả lời được — đơn vị của nó là "bình phương trăm triệu đồng", to nhỏ tuỳ thang đo dữ liệu. Để có một thang điểm không phụ thuộc đơn vị, ta so model với đối thủ ngây thơ nhất có thể: đường nằm ngang $\hat{y} = \bar{y}$ — tức "diện tích nào cũng đoán giá trung bình". Thang điểm đó là $R^2$:

$$R^2 = 1 - \frac{\sum_i (y_i - \hat{y}_i)^2}{\sum_i (y_i - \bar{y})^2}$$

Giải nghĩa: tử số là tổng bình phương sai số của model, mẫu số là tổng bình phương sai số của đường ngang ngây thơ; lấy 1 trừ đi tỉ lệ đó. $R^2 = 1$: model hoàn hảo (sai số 0). $R^2 = 0$: model chẳng hơn gì đoán trung bình. Với 4 điểm ví dụ: mẫu số $= 4 + 1 + 1 + 4 = 10$, tử số $= 0{,}2$, nên $R^2 = 1 - 0{,}02 = 0{,}98$ — model giải thích được 98% biến động của giá nhà.

⚠️ Cạm bẫy tinh vi: $R^2$ âm — có thể xảy ra, nhưng không phải ở đâu bạn nghĩ
Dễ tưởng tượng rằng cứ outlier kéo lệch đường fit đủ mạnh thì $R^2$ sẽ tụt xuống âm — nhưng trên chính tập dữ liệu dùng để fit, điều đó không thể xảy ra: nghiệm least squares được CHỌN để tối thiểu hoá sai số, mà đường ngang $\hat{y} = \bar{y}$ (ứng với $w = 0$) cũng là một lựa chọn khả dĩ trong đúng họ model đó — nên nghiệm tối ưu không bao giờ tệ hơn đường ngang. Đã kiểm chứng bằng số: ngay cả khi đẩy outlier từ $(4, 60)$ lên $(4, 500)$, $R^2$ trên tập fit vẫn dương ($\approx 0{,}27$), không hề âm.

$R^2$ âm chỉ xuất hiện khi đánh giá model trên dữ liệu KHÁC với dữ liệu đã dùng để fit — chính xác là tình huống train/test ở Bài 8. Ví dụ đã kiểm chứng: fit model trên 5 điểm có outlier $(4, 60)$ ra $w \approx 10{,}91$, $b \approx -15{,}35$; đem model đó đi chấm điểm trên một tập dữ liệu MỚI tuân theo đúng quan hệ tuyến tính sạch (không có outlier) thì $R^2 \approx -100{,}2$ — tệ hơn đường ngang tới hơn 100 lần. Đây là lý do "$R^2$ âm" gần như luôn là dấu hiệu model đang được đánh giá ngoài mẫu (out-of-sample), chứ hiếm khi là lỗi tính toán trong-mẫu.
r_squared.js
function rSquared(points, w, b) {
  const n = points.length;
  let meanY = 0;
  for (const p of points) meanY += p.y;
  meanY /= n;
  let ssRes = 0, ssTot = 0;
  for (const p of points) {
    const pred = w * p.x + b;
    ssRes += (p.y - pred) * (p.y - pred);   // sai so cua MODEL
    ssTot += (p.y - meanY) * (p.y - meanY); // sai so cua duong ngang ngay tho
  }
  if (ssTot === 0) return 1; // moi y bang nhau — model nao cung "hoan hao"
  return 1 - ssRes / ssTot;
}

Nhưng có một sự thật khó chịu mà cả series sẽ quay lại nhiều lần: $R^2 = 0{,}98$ ở trên được chấm trên chính 4 điểm dùng để fit — như học sinh tự chấm bài bằng đề đã lộ đáp án. Câu hỏi thật sự của học máy không phải "model khớp dữ liệu cũ tốt cỡ nào" mà là "model dự đoán dữ liệu CHƯA THẤY tốt cỡ nào" — căn nhà thứ 5 chưa bán mới là thứ ta cần đoán. Model càng phức tạp càng dễ "học thuộc lòng" tập train mà trượt thảm hại trên dữ liệu mới.

🧭 Sẽ học kỹ ở Bài 8: Overfitting
Hiện tượng "thuộc lòng đề cũ, trượt đề mới" tên là overfitting — và nghi thức chống nó (chia dữ liệu thành tập train/validation/test riêng biệt, không bao giờ chấm điểm trên dữ liệu đã dùng để học) là nội dung Bài 8. Với đường thẳng 2 tham số của bài này, nguy cơ còn thấp; nhưng hãy giữ phản xạ ngay từ giờ: điểm số trên tập train luôn là con số lạc quan hơn sự thật.

5. Thực hành: kéo thả điểm, đường thẳng tự fit

Toàn bộ lý thuyết trên gói trong demo dưới đây — chạy đúng 3 hàm fitLeastSquares / mse / rSquared vừa học, không giả lập. Bấm vào chỗ trống để thêm điểm, kéo điểm để di chuyển (cách kéo thả trên canvas được giải thích ở Series Canvas Bài 9), và xem đường fit + residual (đoạn đứt màu cam nối điểm với đường) + bảng số cập nhật theo thời gian thực:

📈 Hồi quy tuyến tính — kéo thả điểm, đường tự fit
Đang tải…

Bấm chỗ trống để thêm điểm · kéo điểm để di chuyển · bấm "💥 Thêm outlier" rồi nhìn w, b và R² — đó chính là cạm bẫy MSE ở Mục 2 diễn ra trước mắt bạn.

Hai "trạng thái trống" của demo đều là hành vi đúng, không phải lỗi: dưới 2 điểm thì có vô số đường đi qua nên không tồn tại đường fit duy nhất; mọi điểm cùng hoành độ thì đường fit tối ưu thẳng đứng — không viết được dưới dạng $y = wx + b$ (hàm fitLeastSquares trả về null thay vì chia cho 0 ra NaN).

Cuối cùng, bản đối chiếu công nghiệp. Đoạn PyTorch dưới đây là bản 1-1 của bài này — nn.Linear(1, 1) chính là $\hat{y} = wx + b$, MSELoss chính là công thức Mục 2 — chỉ khác nó tìm $w, b$ bằng vòng lặp 2.000 bước (gradient descent — Bài 2) thay vì công thức đóng. Kết quả hội tụ về đúng $w \approx 1{,}4$, $b \approx 0{,}5$ như ta tính tay (đã kiểm chứng bằng cách mô phỏng lại đúng vòng lặp này trong Node, từ nhiều điểm khởi tạo khác nhau):

linear_regression.py (chạy ngoài browser — cần cài PyTorch)
# Doi chieu 1-1 voi bai hoc — cai dat: pytorch.org/get-started/locally
import torch
import torch.nn as nn

# Cung 4 diem du lieu voi vi du tinh tay trong bai
x = torch.tensor([[1.0], [2.0], [3.0], [4.0]])
y = torch.tensor([[2.0], [3.0], [5.0], [6.0]])

model = nn.Linear(1, 1)              # y_mu = w*x + b — dung model cua bai
loss_fn = nn.MSELoss()               # dung cong thuc MSE cua Muc 2
optimizer = torch.optim.SGD(model.parameters(), lr=0.05)

for epoch in range(2000):            # phuong phap LAP — Bai 2 giai thich co che
    optimizer.zero_grad()
    loss = loss_fn(model(x), y)
    loss.backward()                  # Bai 7 se tu xay co che nay
    optimizer.step()

print(model.weight.item(), model.bias.item())  # ~1.4 va ~0.5 — khop nghiem giai tich

Tóm lược

Bộ xương của mọi hệ thống học máy đã nằm gọn trong bài đầu tiên này:

  • ML đảo ngược lập trình truyền thống: dữ liệu + đáp án mẫu → máy tự tìm quy tắc, dùng khi rule không viết nổi — và KHÔNG dùng khi 3 dòng if-else giải được.
  • Bộ ba model–loss–data: giả thuyết $\hat{y} = wx + b$ với tham số học được; MSE đo độ sai (bình phương để phạt mạnh lỗi lớn + khả vi); dữ liệu là thầy duy nhất.
  • Nghiệm giải tích: loss lồi → cho đạo hàm bằng 0 ra công thức đóng; tính tay được $w = 1{,}4$, $b = 0{,}5$ — nhưng đặc sản này không mở rộng cho model phi tuyến/triệu tham số.
  • $R^2$: so model với đường ngang ngây thơ, không phụ thuộc đơn vị — và điểm chấm trên tập train luôn lạc quan hơn sự thật (chờ Bài 8).
  • MSE nhạy outlier: 1 điểm nhập sai kéo $w$ tăng 8 lần, $R^2$ sụp từ 0,98 xuống 0,32 — luôn nhìn dữ liệu trước khi train.

Tải file code thực hành minh họa bài học

File JavaScript gồm 3 hàm fitLeastSquares / mse / rSquared của bài kèm self-test đối chiếu đúng ví dụ tính tay (chạy node linear_regression.js, không cần cài thêm gì):

Tải về linear_regression.js

📖 Tài liệu tham khảo

Bài viết liên quan trong series

Bài 2: Gradient Descent & đạo hàm Quay lại Lộ trình Series Trí Tuệ Nhân Tạo

Bình luận