Bỏ qua để đến nội dung

Tổng quan về Học máy

Sau bài này, bạn sẽ:

  • Hiểu học máy (machine learning) là gì: máy tự rút quy luật từ ví dụ, thay vì chạy theo luật do người viết sẵn.
  • Phân biệt ba nhóm học có giám sát (supervised), không giám sát (unsupervised) và tăng cường (reinforcement). Trong học có giám sát, phân biệt hồi quy (regression) với phân loại (classification) dựa vào kiểu của nhãn (label).
  • Đọc được các ký hiệu chung của cả phần Học máy: x\mathbf{x}, yy, y^=f(x)\hat{y} = f(\mathbf{x}), mm, nn và L\mathcal{L}.
  • Chạy được quy trình huấn luyện (training) và đánh giá bằng scikit-learn, và giải thích vì sao phải tách tập huấn luyện (training set) với tập kiểm tra (test set) để đo khả năng tổng quát hóa (generalization).

Bạn đang tìm thuê căn hộ. Bạn hỏi giá 10 căn quanh khu mình muốn ở và ghi lại thành bảng dưới đây. Một người quen báo có căn 65 m² sắp trống. Giá bao nhiêu thì hợp lý?

Giá thuê theo diện tích của 10 căn hộ. Số liệu tự tạo cho bài học, không phải giá thị trường.
Diện tích (m²)Giá thuê (triệu đồng/tháng)
256.7
306.5
368.8
428.8
489.4
5512.2
6012.0
6814.1
7513.9
8516.8

Cách thứ nhất là tự viết luật, ví dụ “mỗi m² khoảng 200 nghìn đồng”. Nhưng con số 200 nghìn là do bạn đoán. Muốn tính thêm vị trí, tầng, nội thất, bạn phải nghĩ thêm luật cho từng thứ, và giá đổi thì lại sửa tay.

Cách thứ hai là đưa cho máy các ví dụ: 10 căn hộ, mỗi căn có diện tích và giá thật. Máy tự tìm quy luật nối diện tích với giá, rồi dùng nó để đoán căn 65 m². Có dữ liệu mới thì cho máy học lại. Đó là ý tưởng của học máy. Trang giới thiệu cuốn Machine Learning của Tom Mitchell (1997) mô tả học máy là ngành nghiên cứu các thuật toán máy tính tự cải thiện nhờ kinh nghiệm. Ở đây, “kinh nghiệm” chính là dữ liệu.

Học máy đã có mặt quanh bạn: gợi ý phim, lọc thư rác, nhận ra khuôn mặt trong ảnh, dịch văn bản. Đó đều là những việc khó viết đủ luật bằng tay, nhưng có rất nhiều ví dụ.

Giả sử máy đã học xong. Làm sao biết nó học tốt? Một học sinh luyện một đề toán tới mức thuộc lòng đáp án, rồi thi lại chính đề ấy, thì điểm 10 không chứng tỏ em hiểu bài. Muốn biết, bạn phải cho một đề chưa từng thấy.

Máy cũng vậy. Một mô hình (model) có thể đoán đúng cả 10 căn hộ đã học chỉ bằng cách nhớ giá từng căn. Nhưng giá 10 căn đó bạn đã biết. Bạn cần nó đoán đúng căn 65 m² mà nó chưa thấy bao giờ.

Bài toán của bài này là: làm sao để máy rút ra quy luật từ ví dụ, và làm sao kiểm tra được quy luật ấy vẫn đúng với dữ liệu nó chưa từng thấy.

Học máy là một nhánh của trí tuệ nhân tạo (artificial intelligence, AI), và học sâu (deep learning) lại là một nhánh của học máy: ba khái niệm lồng nhau, không phải một. Bản thân học máy thường được chia theo tín hiệu mà máy dùng để học.

  • Học có giám sát (supervised learning). Mỗi ví dụ có cả đầu vào lẫn đáp án đúng, gọi là nhãn (label), như bảng căn hộ: đầu vào là diện tích, nhãn là giá thuê. Máy học cách đi từ đầu vào tới nhãn. “Giám sát” nghĩa là có nhãn dẫn đường, không phải có người ngồi canh.
  • Học không giám sát (unsupervised learning). Chỉ có đầu vào, không có nhãn. Máy tự tìm cấu trúc trong dữ liệu, ví dụ phân cụm (clustering): gom các khách hàng mua sắm giống nhau thành nhóm, dù không ai cho trước danh sách nhóm.
  • Học tăng cường (reinforcement learning). Một tác nhân (agent) hành động trong một môi trường (environment), nhận về phần thưởng (reward), dương hoặc âm, và học cách chọn hành động để tổng phần thưởng lớn nhất, như một chương trình tự chơi game để ghi điểm. Chương trình chơi cờ vây AlphaGo thì dùng kết hợp cả học có giám sát lẫn học tăng cường.

Hình dưới tóm tắt ba nhóm. Hãy nhìn dòng thứ hai của mỗi thẻ: ba nhóm khác nhau ở chỗ máy học từ cái gì.

Học máyCó giám sátHọc từ dữ liệu có nhãnVí dụ: đoán giá thuê, lọc thư rácKhông giám sátChỉ có dữ liệu, không có nhãnVí dụ: chia nhóm khách hàngTăng cườngHọc từ phần thưởngVí dụ: chương trình tự chơi game
Ba nhóm học máy. Bài này và các bài ngay sau tập trung vào học có giám sát, nơi mỗi mẫu có đầu vào x\mathbf{x} và nhãn yy.

Còn có học bán giám sát (semi-supervised learning): chỉ một phần nhỏ dữ liệu có nhãn, hay gặp khi gắn nhãn tốn kém, như ảnh y tế cần bác sĩ đọc. Phần còn lại của bài nói về học có giám sát.

Các ký hiệu này dùng chung cho cả phần Học máy.

Mỗi ví dụ là một mẫu (sample). Mẫu có các đặc trưng (feature), là những thông tin bạn đưa vào, như diện tích, số phòng, tầng. Các đặc trưng của một mẫu xếp thành vectơ x\mathbf{x}:

x=[x1,x2,…,xn]⊤\mathbf{x} = [x_1, x_2, \dots, x_n]^\top
  • nn là số đặc trưng. xjx_j là đặc trưng thứ jj.
  • yy là nhãn của mẫu đó, tức đáp án đúng.
  • mm là số mẫu. Mẫu thứ ii viết là (x(i),y(i))\left(\mathbf{x}^{(i)}, y^{(i)}\right). Chỉ số (i)(i) viết ở trên nghĩa là “mẫu thứ ii”, không phải số mũ.

Với bảng căn hộ, m=10m = 10 và n=1n = 1: mỗi căn chỉ có một đặc trưng là diện tích. Căn đầu tiên có x(1)=[25]\mathbf{x}^{(1)} = [25] và y(1)=6.7y^{(1)} = 6.7. Sách khác có thể dùng chữ khác, ví dụ Machine Learning cơ bản gọi số mẫu là NN.

Mô hình (model) là một hàm ff nhận đầu vào và trả về dự đoán:

y^=f(x)\hat{y} = f(\mathbf{x})

y^\hat{y} (đọc là “y mũ”) là giá trị dự đoán, để phân biệt với nhãn thật yy.

Mức sai của ff đo bằng hàm mất mát (loss function), ký hiệu L\mathcal{L}: một con số tính từ các cặp dự đoán y^(i)\hat{y}^{(i)} và nhãn thật y(i)y^{(i)}, càng lệch thì càng lớn. Với hồi quy, hàm quen thuộc là sai số bình phương trung bình (mean squared error, MSE): trung bình bình phương các sai lệch. Bài Hồi quy tuyến tính giải thích vì sao lại bình phương.

Huấn luyện (training) là chọn ra ff, trong một họ hàm cho trước, sao cho L\mathcal{L} trên dữ liệu đã có càng nhỏ càng tốt. Họ hàm có thể là mọi đường thẳng, hoặc một họ linh hoạt hơn nhiều. Các con số mà huấn luyện điều chỉnh để chọn ra ff gọi là tham số (parameter) của mô hình. Cũng có mô hình không học theo cách này: fit của KNN ở phần Ví dụ code chỉ ghi nhớ dữ liệu, tới lúc dự đoán mới tìm các căn gần nhất.

Học có giám sát có hai bài toán chính. Chúng khác nhau ở kiểu của nhãn yy:

  • Hồi quy (regression): nhãn là một đại lượng đo được, có thể nhận vô số giá trị. Ví dụ đoán giá thuê, nhiệt độ ngày mai, doanh số tháng sau.
  • Phân loại (classification): nhãn là một trong số ít lớp cho trước. Ví dụ thư rác hay không, ảnh chó hay mèo, khách trả nợ đúng hạn hay không. Các lớp có khi được ghi bằng số, như 0 và 1, nhưng đó chỉ là tên lớp, không phải đại lượng đo được, nên vẫn là phân loại.

Cùng bảng căn hộ có thể thành cả hai bài toán: nhãn là giá thuê thì là hồi quy, đổi nhãn thành “đắt” (từ 10 triệu trở lên) hay “rẻ” thì thành phân loại, dù đầu vào vẫn là diện tích. Ô code đầu tiên sẽ làm đúng như vậy.

Quy trình: dữ liệu, huấn luyện, đánh giá

Phần tiêu đề “Quy trình: dữ liệu, huấn luyện, đánh giá”

Một dự án học có giám sát thường đi qua các bước sau:

  1. Thu dữ liệu có nhãn, ví dụ 10 căn hộ có giá thật.
  2. Tách dữ liệu ngay từ đầu, ngẫu nhiên, thành tập huấn luyện (training set) để học và tập kiểm tra (test set) để giữ riêng, gọi tắt là tập train và tập test.
  3. Huấn luyện chỉ trên tập train. Mô hình không được thấy mẫu test nào.
  4. Đánh giá trên tập test: so dự đoán với nhãn thật bằng một thước đo như MSE.
  5. Dùng mô hình cho dữ liệu mới, nếu kết quả đủ tốt.

Tập test đóng vai “đề thi chưa từng thấy” ở phần Trực giác.

Tổng quát hóa là khả năng làm tốt trên dữ liệu mà mô hình chưa thấy khi huấn luyện. Đây mới là mục tiêu thật. Sai số trên tập train thường lạc quan hơn thực tế, vì mô hình đã được chỉnh để khớp đúng những mẫu đó. Một mô hình đủ linh hoạt có thể đưa sai số train về 0 chỉ bằng cách nhớ từng mẫu, mà vẫn đoán tệ trên dữ liệu mới. Tập test không tham gia huấn luyện, nên sai số trên nó là một ước lượng cho sai số trên dữ liệu mới.

Khi sai số train nhỏ mà sai số test lớn hơn hẳn, mô hình đang học cả những chi tiết tình cờ của tập train. Hiện tượng này gọi là overfitting, chủ đề của bài Overfitting và Underfitting.

Một tập test tốt cần: đủ lớn, đại diện cho dữ liệu mà mô hình sẽ gặp ngoài đời, và không chứa mẫu nào trùng với tập train.

  • Tập test nhỏ cho ước lượng dao động mạnh. Với 10 căn hộ, tập test chỉ còn 3 căn (ô code thứ hai).
  • Tập test “mòn” khi dùng lặp lại để sửa mô hình (hộp thứ hai ở mục Lỗi hay gặp). Cần thêm một tập kiểm định (validation set), bạn sẽ gặp ở các bài sau.
  • Mô hình chỉ học được từ dữ liệu nó có. Dữ liệu không đại diện thì cả mô hình lẫn tập test đều lệch. Đoán ở ngoài vùng dữ liệu cũng dễ sai, xem hộp “Tin vào dự đoán ở ngoài vùng dữ liệu” ở bài Hồi quy tuyến tính.

Hình dưới vẽ quy trình học có giám sát trên bảng căn hộ, đúng như ô code thứ hai sẽ làm. Hãy nhìn tập kiểm tra: mũi tên duy nhất đi ra từ nó dẫn thẳng tới bước đánh giá, không đi qua bước huấn luyện.

Dữ liệu10 căn hộTách ngẫu nhiên70% và 30%Tập huấn luyện7 cănTập kiểm tra3 cănHuấn luyệnfitKhông đưa vàohuấn luyệnMô hìnhĐánh giápredict, tính MSEDùng chocăn hộ mới
Quy trình học có giám sát trên bảng căn hộ. Tập kiểm tra được giữ riêng từ đầu, nên MSE trên đó ước lượng được sai số của y^=f(x)\hat{y} = f(\mathbf{x}) trên căn hộ chưa thấy. Với chỉ 3 căn, ước lượng này còn dao động theo cách chia. Số 7 và 3 khớp ô code thứ hai (test_size=0.3).

Với random_state=4, ô code thứ hai đưa 3 căn 42, 75 và 48 m² vào tập kiểm tra, 7 căn còn lại vào tập huấn luyện.

Hai ô dưới đây chạy độc lập, chạy ô nào trước cũng được. Ô chạy đầu tiên lâu hơn một chút, vì trình duyệt phải tải thêm scikit-learn.

Ô đầu tiên coi mô hình như hộp đen, chỉ cần hai bước: fit để học, predict để dự đoán. Ô này giải cả hai bài toán trên cùng bảng: hồi quy giá thuê, rồi phân loại đắt hay rẻ.

fit và predict: hồi quy và phân loại

import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.neighbors import KNeighborsClassifier
# Bảng 10 căn hộ (dữ liệu minh họa, tự tạo cho bài học).
# area: diện tích (m²), rent: giá thuê (triệu đồng/tháng).
# dataset: apartments
area = np.array([25, 30, 36, 42, 48, 55, 60, 68, 75, 85])
rent = np.array([6.7, 6.5, 8.8, 8.8, 9.4, 12.2, 12.0, 14.1, 13.9, 16.8])
# Đầu vào X: m = 10 hàng (mẫu), n = 1 cột (đặc trưng).
X = area.reshape(-1, 1)
new = np.array([[40], [65]]) # hai căn mới, chưa có trong bảng
# Hồi quy: nhãn là một con số (giá thuê).
reg = LinearRegression()
reg.fit(X, rent) # học từ 10 ví dụ
for a, p in zip(new[:, 0], reg.predict(new)):
print(f"regression: {a} m² -> {p:.2f}")
# Phân loại: cùng đầu vào, nhưng nhãn là "đắt" (từ 10 triệu) hay "rẻ".
label = np.where(rent >= 10, "expensive", "cheap")
# Mô hình này xem 3 căn có diện tích gần nhất, rồi theo số đông.
clf = KNeighborsClassifier(n_neighbors=3)
clf.fit(X, label) # vẫn hai bước: fit rồi predict
for a, p in zip(new[:, 0], clf.predict(new)):
print(f"classification: {a} m² -> {p}")

Lần chạy đầu cần tải Python, khoảng 10 MB.

Mô hình hồi quy đoán căn 40 m² giá 8.82 triệu và căn 65 m² giá 13.06 triệu. Mô hình phân loại trả lời căn 40 m² là cheap (rẻ), căn 65 m² là expensive (đắt). Mô hình phân loại ở đây là K-Nearest Neighbors (KNN, k láng giềng gần nhất): nó xem 3 căn có diện tích gần nhất trong bảng, rồi theo số đông. Với căn 40 m², ba căn gần nhất là 42, 36 và 48 m², đều rẻ.

Hai mô hình rất khác nhau, nhưng cách dùng giống hệt: tạo mô hình, gọi fit(X, y), rồi predict. Hầu hết mô hình trong scikit-learn theo khuôn này. X là bảng mm hàng, nn cột, nên mới cần reshape(-1, 1). Đổi sang phân loại, bạn chỉ đổi nhãn, không đổi X. Bài Hồi quy tuyến tính sẽ mở hộp đen LinearRegression ra.

Ô thứ hai làm đúng quy trình ở mục Minh họa. train_test_split giữ lại 30% dữ liệu làm tập test, random_state cố định cách chia để lần nào chạy cũng ra cùng kết quả. Ô này so đường thẳng LinearRegression với một mô hình “học thuộc”: đoán giá mỗi căn bằng giá của căn train có diện tích gần nhất (KNeighborsRegressor(n_neighbors=1)). Cuối cùng, ô thử thêm hai cách chia khác.

Tách train/test và đánh giá

import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsRegressor
# Bảng 10 căn hộ (dữ liệu minh họa, tự tạo cho bài học).
# dataset: apartments
area = np.array([25, 30, 36, 42, 48, 55, 60, 68, 75, 85])
rent = np.array([6.7, 6.5, 8.8, 8.8, 9.4, 12.2, 12.0, 14.1, 13.9, 16.8])
X = area.reshape(-1, 1)
# Giữ lại 30% (3 căn) làm tập test. Mô hình không được thấy chúng.
X_train, X_test, y_train, y_test = train_test_split(
X, rent, test_size=0.3, random_state=4)
print(f"test: {X_test[:, 0]} m²")
line = LinearRegression()
# Mô hình "học thuộc": đoán bằng giá của căn train có diện tích gần nhất.
memo = KNeighborsRegressor(n_neighbors=1)
for name, model in [("line", line), ("memorize", memo)]:
model.fit(X_train, y_train) # chỉ học từ tập train
mse_train = mean_squared_error(y_train, model.predict(X_train))
mse_test = mean_squared_error(y_test, model.predict(X_test))
print(f"{name}: MSE train = {mse_train:.4f}, test = {mse_test:.4f}")
# Tập test chỉ có 3 căn: chia kiểu khác là điểm số đổi theo.
for seed in [8, 2]:
X_tr, X_te, y_tr, y_te = train_test_split(
X, rent, test_size=0.3, random_state=seed)
mse = [mean_squared_error(y_te, m.fit(X_tr, y_tr).predict(X_te))
for m in (line, memo)]
print(f"seed {seed}: test {X_te[:, 0]}, "
f"line {mse[0]:.4f}, memorize {mse[1]:.4f}")

Lần chạy đầu cần tải Python, khoảng 10 MB.

Kết quả với cách chia ở mục Minh họa (tập test là 3 căn 42, 75 và 48 m²):

Mô hìnhMSE trainMSE test
Đường thẳng0.21901.0007
Học thuộc0.00002.6267

Nếu chỉ nhìn tập train, mô hình học thuộc là hoàn hảo: MSE bằng 0, vì căn gần nhất với mỗi căn train chính là nó. Nhưng trên 3 căn chưa thấy, MSE của nó gấp khoảng 2.6 lần đường thẳng. Nó chép nguyên giá của một căn hàng xóm, kể cả phần chênh lệch tình cờ của căn đó (vị trí, tầng, nội thất). Gần như toàn bộ sai số đến từ căn 48 m²: căn train gần nhất là căn 55 m² giá 12.2 triệu, trong khi giá thật chỉ 9.4 triệu. Đường thẳng thì chỉ giữ xu hướng chung “rộng hơn thì đắt hơn”.

Giờ nhìn hai dòng cuối. Đây là bài học quan trọng nhất của ô này:

Cách chiaCác căn test (m²)MSE test, đường thẳngMSE test, học thuộc
random_state=442, 75, 481.00072.6267
random_state=875, 60, 850.67992.4567
random_state=248, 30, 550.73270.1467

Cùng hai mô hình, cùng dữ liệu, chỉ đổi 3 căn nào rơi vào tập test, mà MSE test của đường thẳng dao động từ 0.68 tới 1.00, còn của mô hình học thuộc từ 2.63 rơi xuống 0.15. Với random_state=2, mô hình học thuộc còn thắng đường thẳng, vì căn train gần nhất của cả ba căn test tình cờ chỉ chênh giá 0.2 tới 0.6 triệu. MSE test ở đây là trung bình của đúng 3 con số, nên một căn may hay rủi là đủ làm lệch kết quả.

Vậy với 10 mẫu, một lần tách train/test không đủ để kết luận mô hình nào tốt hơn. Dữ liệu thật thường có hàng trăm tới hàng triệu mẫu, nên tập test đủ lớn để ước lượng ổn định hơn. Khi dữ liệu ít, người ta tách nhiều lần rồi lấy trung bình, gọi là kiểm định chéo (cross-validation), bạn sẽ gặp ở các bài sau.

Câu 1/4 Một ngân hàng muốn dự đoán một khách hàng có trả nợ đúng hạn hay không, dựa vào thu nhập, tuổi và số tiền vay. Đây là bài toán gì?
Câu 2/4 Hai mô hình đoán giá thuê. Mô hình A: MSE train 0.00.0, MSE test 3.53.5. Mô hình B: MSE train 0.40.4, MSE test 0.60.6. Bạn nên dùng mô hình nào cho căn hộ mới?
Câu 3/4 Một cửa hàng có lịch sử mua sắm của 10 000 khách, không có cột nào ghi khách thuộc nhóm nào. Họ muốn chia khách thành vài nhóm mua sắm giống nhau. Đây là nhóm học máy nào?
Câu 4/4 Bạn thử 50 mô hình. Mỗi lần thử, bạn xem MSE trên tập test, rồi giữ mô hình có MSE test nhỏ nhất và báo cáo con số đó. Con số này thế nào?

Ý chính:

  • Học máy để máy tự rút quy luật từ ví dụ. Ba nhóm chính: có giám sát (học từ nhãn), không giám sát (tự tìm cấu trúc), tăng cường (học từ phần thưởng).
  • Học có giám sát: từ mm mẫu (x(i),y(i))\left(\mathbf{x}^{(i)}, y^{(i)}\right) có nn đặc trưng, tìm y^=f(x)\hat{y} = f(\mathbf{x}) làm hàm mất mát L\mathcal{L} nhỏ. Nhãn là đại lượng đo được (giá, nhiệt độ) thì là hồi quy; là một lớp, kể cả lớp ghi bằng 0 và 1, thì là phân loại.
  • Quy trình: tách tập test ngay từ đầu, huấn luyện chỉ trên tập train, đánh giá trên tập test. Trong scikit-learn: train_test_split, fit, predict.
  • Mục tiêu là tổng quát hóa. Điểm train thường lạc quan; mô hình học thuộc đạt điểm train hoàn hảo mà vẫn đoán tệ. Tập test nhỏ thì điểm test dao động mạnh.

Đọc thêm:

Nội dung theo giấy phép CC BY-NC-SA 4.0. Mã nguồn theo giấy phép MIT.Giới thiệu