Mạng nơ-ron
Một mô hình tuyến tính chỉ vẽ được một đường thẳng cắt đôi không gian dữ liệu. Nhưng dữ liệu thực tế thường không nằm gọn hai bên một đường thẳng: hai lớp có thể xoắn vào nhau, lồng vào nhau, hoặc tách thành nhiều cụm. Lúc đó ta cần một mô hình biết bẻ cong ranh giới.
Mạng nơ-ron nhân tạo (artificial neural network) là cách xếp chồng nhiều nơ-ron đơn giản thành nhiều tầng, để máy học được những mối quan hệ phi tuyến mà một mô hình tuyến tính không thể nắm bắt. Đây là nền tảng của gần như mọi hệ thống AI hiện đại, từ nhận dạng ảnh đến các mô hình ngôn ngữ lớn.
Thử ngay: xem perceptron xoay ranh giới qua từng vòng
Nơ-ron đơn lẻ, dạng sơ khai nhất của mạng, gọi là perceptron. Nó học bằng một quy tắc rất thô: cứ gặp một điểm mình xếp nhầm lớp thì nhích trọng số theo hướng sửa đúng điểm đó. Bấm nút để xem quy tắc ấy chạy: mỗi vòng, perceptron gặp điểm phân loại sai thì xoay đường ranh giới, cho tới khi tách sạch hai lớp điểm. Đây là trường hợp tách được bằng một đường thẳng. Chạy xong, bạn hãy bấm vào khung để thêm điểm cho từng lớp: nếu xếp hai lớp xen kẽ nhau thì một đường thẳng không tài nào tách nổi, và đó chính là lý do ta cần nhiều tầng nơ-ron ở phần sau.
Ý tưởng cốt lõi
Một nơ-ron nhân tạo lấy các đầu vào, nhân với trọng số, cộng lại, thêm một độ lệch rồi cho qua một hàm kích hoạt. Viết gọn bằng lời:
output = activation(w*x + b)
trong đó w là vector trọng số, x là vector đầu vào, b là độ lệch (bias), còn
w*x hiểu là tổng tích từng cặp w[i]*x[i].
Từ perceptron tới nơ-ron hiện đại
- Perceptron của Rosenblatt (1958) là nơ-ron sơ khai nhất: nó tính
z = w*x + brồi cho ra1nếuzlớn hơn0, ngược lại cho ra0. Đây là một ngưỡng cứng. - Quy tắc học rất giản dị: nếu dự đoán đúng thì không làm gì; nếu sai thì điều chỉnh trọng
số theo công thức
w = w + eta * (y - y_du_doan) * x, vớietalà tốc độ học. - Perceptron chỉ vẽ được ranh giới thẳng. Nó học được hàm AND, OR nhưng bó tay với hàm XOR, vì không có một đường thẳng nào tách bốn điểm XOR thành đúng hai nhóm.
Vì sao cần nhiều tầng
Bài toán XOR cho thấy: độ khó không nằm ở dữ liệu, mà ở cách ta biểu diễn nó. Khi thêm một tầng ẩn, mạng tự biến đổi dữ liệu sang một biểu diễn mới, ở đó bài toán trở nên tách được tuyến tính. Đó chính là tinh thần của học sâu: học cách biểu diễn.
Cụ thể với XOR, chỉ cần hai nơ-ron ẩn: một học hàm OR, một học hàm NAND. Qua chúng, hai điểm
nhãn 1 bị đẩy trùng nhau, tách hẳn khỏi hai điểm nhãn 0; ở biểu diễn mới đó một
perceptron duy nhất (chính là hàm AND) cắt gọn hai nhóm.
Điều kiện then chốt là hàm kích hoạt phải phi tuyến. Nếu mọi tầng đều tuyến tính, xếp
chồng bao nhiêu tầng cũng chỉ tương đương một tầng duy nhất, và mạng suy biến về hồi quy
tuyến tính: hai tầng liên tiếp cho ra W2 * (W1 * x + b1) + b2, khai triển thành
(W2*W1) * x + (W2*b1 + b2), vẫn đúng dạng W*x + b của một tầng.
Hàm kích hoạt
Hàm kích hoạt quyết định nơ-ron có phi tuyến hay không. Bốn hàm thường gặp:
- Sigmoid:
sigmoid(z) = 1 / (1 + e^(-z)). Cho ra giá trị trong khoảng từ0tới1, hợp với việc diễn giải như xác suất. Đạo hàm đẹp:sigmoid'(z) = sigmoid(z) * (1 - sigmoid(z)). Nhược điểm là bão hòa ở hai đầu, khiến gradient gần0trong mạng sâu (hiện tượng vanishing gradient). - Tanh:
tanh(z) = (e^z - e^(-z)) / (e^z + e^(-z)). Hình chữ S giống sigmoid nhưng cho giá trị từ-1tới1, tức đối xứng quanh gốc, nhờ đó thường hội tụ nhanh hơn sigmoid. Đạo hàm gọn:tanh'(z) = 1 - tanh(z)^2. Tuy vậy tanh vẫn bão hòa ở hai đầu. - ReLU:
relu(z) = max(0, z). Cực kỳ đơn giản, chỉ là một phép so sánh. Đạo hàm bằng1khizlớn hơn0và bằng0khiznhỏ hơn0, nên không bị vanishing ở phần dương. Đây là lựa chọn mặc định cho tầng ẩn của mạng hiện đại. Cái giá phải trả là nơ-ron chết (dying ReLU): nơ-ron luôn chozâm thì gradient luôn bằng0và không học lại được nữa. - Softmax: biến một vector điểm số thành một phân phối xác suất trên nhiều lớp; chỉ dùng ở tầng đầu ra cho bài toán phân loại nhiều lớp.
Quy tắc thực hành: dùng ReLU cho các tầng ẩn, sigmoid cho đầu ra phân loại nhị phân, và softmax cho đầu ra phân loại nhiều lớp.
Mạng nhiều tầng (MLP)
MLP (multi-layer perceptron) gồm một tầng đầu vào, một hay nhiều tầng ẩn và một tầng
đầu ra. Mỗi tầng nhận đầu ra của tầng trước, làm một phép tuyến tính z = W*a + b rồi cho
qua hàm kích hoạt a = activation(z). Vì mỗi nơ-ron nối với mọi nơ-ron của tầng liền
trước, kiến trúc này gọi là fully-connected.
Mỗi nơ-ron với hàm kích hoạt sigmoid thực ra chính là một hồi quy logistic. MLP là một chuỗi các bộ hồi quy logistic xếp chồng, và chính cấu trúc giản dị nhân lên hàng triệu lần ấy là nền tảng của AI hiện đại.
Lan truyền xuôi (forward pass)
Lan truyền xuôi là quá trình đẩy dữ liệu đi từ đầu vào qua từng tầng tới đầu ra:
- Đặt
aở tầng0bằng đầu vàox. - Với mỗi tầng từ
1tớiL: tínhz = W*a_truoc + brồia = activation(z). - Giá trị
aở tầng cuối cùng chính là dự đoán của mạng.
Một tầng có n đầu vào và m nơ-ron cần n * m phép nhân, tức đúng một phép nhân ma trận
với vector. Đây là lý do GPU thống trị học sâu.
Hàm mất mát
Dự đoán vừa có tốt hay tệ? Ta cần một con số đo mức sai, gọi là hàm mất mát (loss function). Huấn luyện quy về một việc: làm con số ấy nhỏ đi.
- Sai số bình phương trung bình (MSE), cho hồi quy:
MSE = trung_binh((y_du_doan - y)^2). Bình phương phạt nặng sai lệch lớn, nên MSE rất nhạy với điểm ngoại lai. - Cross-entropy nhị phân, cho phân loại hai lớp:
L = -(y * log(y_du_doan) + (1 - y) * log(1 - y_du_doan)). Mạng càng tự tin sai thì bị phạt càng nặng. - Cross-entropy nhiều lớp, đi kèm softmax: lấy
-logcủa xác suất mạng gán cho lớp đúng.
Vì sao phân loại không dùng MSE? Vì ghép MSE với sigmoid thì gradient chứa thừa số
sigmoid'(z), gần 0 ở vùng bão hòa, nên mạng sai nặng nhất lại học chậm nhất. Cross-entropy
triệt tiêu thừa số đó, để lại gradient y_du_doan - y, tỉ lệ thẳng với mức sai.
Lan truyền ngược (backpropagation) và gradient descent
Có hàm mất mát L rồi, muốn cải thiện mạng, ta cần biết mỗi trọng số ảnh hưởng tới L ra
sao, tức là tính gradient của L theo từng tham số.
- Backpropagation chính là chain rule (quy tắc đạo hàm hàm hợp) áp dụng cho đồ thị tính toán của mạng. Nó lan ngược tín hiệu sai số từ đầu ra trở về đầu vào, tái sử dụng các đạo hàm trung gian thay vì tính lại.
- Tại tầng đầu ra, với cross-entropy cộng sigmoid, sai số có dạng gọn:
delta = y_du_doan - y. - Lan ngược qua một tầng:
delta_truoc = (W_sau_chuyen_vi * delta) * activation'(z_truoc), trong đó dấu nhân cuối là nhân theo từng phần tử. - Gradient của trọng số một tầng:
grad_W = delta * a_truoc, vàgrad_b = delta.
Chỉ cần một lượt xuôi và một lượt ngược là tính được gradient cho toàn bộ mạng. Đây là phát hiện thay đổi cuộc chơi, vì nó nhanh tương đương một lượt xuôi.
Có gradient rồi, ta cập nhật tham số bằng gradient descent: đi ngược hướng gradient để giảm mất mát.
w = w - eta * grad_w
với eta là tốc độ học (learning rate). Lặp lại lan truyền xuôi, lan truyền ngược và cập
nhật nhiều lần, mạng dần học được hàm cần tìm.
Tốc độ học là độ dài mỗi bước chân khi đi xuống dốc: quá nhỏ thì học rất lâu, quá lớn thì mất mát dao động hoặc phóng ra vô cực.
Quá khớp và cách giảm
Mạng nơ-ron có rất nhiều tham số, nên thừa sức học thuộc lòng dữ liệu huấn luyện thay vì
hiểu quy luật. Hiện tượng đó gọi là quá khớp (overfitting): sai số trên tập huấn luyện
tụt gần 0, nhưng sai số trên dữ liệu mới lại tệ. Giống một sinh viên học tủ, thuộc đáp án
của đúng 20 đề cũ, gãy ngay khi đề đổi số. Dấu hiệu nhận biết là theo dõi hai đường cong:
khi mất mát trên tập huấn luyện tiếp tục giảm còn mất mát trên tập kiểm định (validation)
quay đầu đi lên, quá khớp đã bắt đầu. Các cách giảm:
- Thêm dữ liệu, kể cả tăng dữ liệu nhân tạo (data augmentation) bằng cách lật, xoay, cắt.
- Giảm kích thước mạng: ít tham số thì mạng không đủ chỗ học thuộc.
- Điều chuẩn L2 (weight decay): cộng vào hàm mất mát một khoản phạt tỉ lệ với tổng bình
phương trọng số, ép trọng số về gần
0và khiến mạng ưa hàm trơn. - Dropout: mỗi lượt huấn luyện tắt ngẫu nhiên một tỉ lệ nơ-ron, nên mạng không dám phụ thuộc vào một nơ-ron riêng lẻ nào. Lúc suy luận thì bật lại toàn bộ.
- Dừng sớm (early stopping): dừng ngay khi mất mát trên tập kiểm định bắt đầu tăng.
Ngược lại là thiếu khớp (underfitting): mạng quá nhỏ hoặc huấn luyện quá ít nên sai cả trên tập huấn luyện lẫn dữ liệu mới. Cách chữa ngược hẳn: mạng lớn hơn, huấn luyện lâu hơn.
Nhiều tầng luôn tốt hơn. Sai: mạng sâu hơn dễ quá khớp hơn, và khó huấn luyện hơn do gradient suy giảm dần khi lan ngược qua nhiều tầng.
Khởi tạo mọi trọng số bằng 0. Khi đó mọi nơ-ron cùng tầng nhận đúng một gradient và mãi
mãi giống hệt nhau; cả tầng suy biến thành một nơ-ron. Phải khởi tạo ngẫu nhiên.
Quên chuẩn hóa đầu vào. Nếu một đặc trưng cỡ hàng nghìn còn đặc trưng khác cỡ 0.01,
gradient descent sẽ zigzag rất chậm.
Mã nguồn Python
Dưới đây là cài đặt thuần bằng list và float, không dùng thư viện ngoài. Đầu tiên là
một perceptron, sau đó là lan truyền xuôi cho một mạng một tầng ẩn nhỏ.
import math
def sigmoid(z):
# ham kich hoat sigmoid
return 1.0 / (1.0 + math.exp(-z))
def relu(z):
# ham kich hoat ReLU
return z if z > 0.0 else 0.0
def tich_vo_huong(w, x):
# tinh tong w[i] * x[i]
tong = 0.0
for i in range(len(w)):
tong += w[i] * x[i]
return tong
def perceptron(w, b, x):
# mot perceptron voi nguong cung: tra ve 1 hoac 0
z = tich_vo_huong(w, x) + b
return 1 if z > 0.0 else 0
def no_ron(w, b, x, activation):
# mot no-ron tong quat: output = activation(w*x + b)
z = tich_vo_huong(w, x) + b
return activation(z)
def forward_pass(x, W1, b1, W2, b2):
# mang 1 tang an: dau vao -> tang an (ReLU) -> dau ra (sigmoid)
# W1 la danh sach cac vector trong so cho moi no-ron an
an = []
for j in range(len(W1)):
an.append(no_ron(W1[j], b1[j], x, relu))
# tang dau ra chi co mot no-ron
out = no_ron(W2, b2, an, sigmoid)
return an, out
# vi du chay thu
if __name__ == "__main__":
x = [1.0, 2.0]
# perceptron hoc ham OR (vi du trong so co san)
print("perceptron:", perceptron([1.0, 1.0], -0.5, x))
# mang 1 tang an: 2 dau vao -> 2 no-ron an -> 1 dau ra
W1 = [[0.1, 0.3], [0.2, 0.4]] # trong so cho 2 no-ron an
b1 = [0.0, 0.0]
W2 = [0.5, 0.6] # trong so cho no-ron dau ra
b2 = 0.0
an, out = forward_pass(x, W1, b1, W2, b2)
print("tang an:", an)
print("dau ra:", out)
Bài tập thực hành
Bài tập 1: perceptron cho hàm AND
Tìm bộ trọng số w và độ lệch b để perceptron(w, b, x) ở trên cài đúng hàm AND, tức là
chỉ cho ra 1 khi cả hai đầu vào đều bằng 1.
Gợi ý
Chọn w = [1.0, 1.0] và b = -1.5. Khi đó z = x[0] + x[1] - 1.5. Chỉ với đầu vào
[1, 1] thì z = 0.5 lớn hơn 0, còn ba trường hợp còn lại đều cho z âm. Hãy thử lần
lượt bốn đầu vào để kiểm chứng.
Bài tập 2: vì sao một perceptron không học được XOR
Hãy giải thích bằng lời tại sao không có bộ w và b nào khiến một perceptron đơn cho ra
đúng bảng XOR (cho ra 1 khi đúng một trong hai đầu vào bằng 1).
Gợi ý
Một perceptron chỉ tạo được một ranh giới thẳng. Bốn điểm XOR có hai điểm nhãn 1 nằm
chéo nhau và hai điểm nhãn 0 cũng chéo nhau, nên không đường thẳng nào tách hai nhóm. Cộng
các điều kiện bất phương trình lại sẽ dẫn tới mâu thuẫn. Cần một tầng ẩn để giải XOR.
Bài tập 3: đổi hàm kích hoạt tầng ẩn
Sửa hàm forward_pass để tầng ẩn dùng sigmoid thay cho relu, rồi chạy lại với cùng đầu
vào. Quan sát giá trị tầng ẩn thay đổi thế nào.
Gợi ý
Chỉ cần đổi relu thành sigmoid trong lời gọi no_ron(W1[j], b1[j], x, relu). Giá trị
tầng ẩn sẽ luôn nằm trong khoảng từ 0 tới 1 (do sigmoid), thay vì có thể lớn tùy ý như
ReLU. Đầu ra cuối cũng đổi theo vì đầu vào của nơ-ron đầu ra đã khác.
Tự kiểm tra
Vì sao hàm kích hoạt phải phi tuyến?
Nếu mọi tầng đều tuyến tính thì việc xếp chồng nhiều tầng tương đương với một phép biến đổi tuyến tính duy nhất, và mạng không mạnh hơn hồi quy tuyến tính. Hàm kích hoạt phi tuyến (như sigmoid hay ReLU) mới cho mạng khả năng học các ranh giới cong và các hàm phức tạp.
Lan truyền ngược tính cái gì, và để làm gì?
Lan truyền ngược tính gradient của hàm mất mát theo từng tham số (trọng số và độ lệch),
bằng cách áp dụng chain rule và lan tín hiệu sai số từ đầu ra về đầu vào. Gradient này được
dùng trong gradient descent để cập nhật tham số w = w - eta * grad_w, giúp mạng dần giảm
sai số qua nhiều vòng huấn luyện.
Mạng đạt độ chính xác 99 phần trăm khi huấn luyện nhưng chỉ 62 phần trăm trên dữ liệu mới. Chuyện gì đã xảy ra, và sửa thế nào?
Đây là quá khớp: mạng học thuộc tập huấn luyện thay vì nắm quy luật. Cách chữa: thêm dữ liệu, thu nhỏ mạng, thêm điều chuẩn L2 hoặc dropout, dừng sớm. Điều không nên làm là huấn luyện thêm nhiều epoch, vì chỉ khiến mạng học thuộc kỹ hơn. Nếu cả hai con số đều thấp thì đó là thiếu khớp, chữa ngược hẳn: mạng lớn hơn, huấn luyện lâu hơn.
Một bạn thay toàn bộ ReLU ở tầng ẩn bằng hàm đồng nhất f(z) = z. Mạng 5 tầng còn học được XOR không?
Không. Hàm đồng nhất là tuyến tính, nên cả năm tầng gộp lại vẫn tương đương một phép biến
đổi dạng W*x + b. Mạng suy biến thành một perceptron, mà perceptron thì không tách được XOR
bằng một đường thẳng. Chiều sâu chỉ có giá trị khi giữa các tầng có phi tuyến.
Câu hỏi tự kiểm
- 1Phát biểu nào đúng về khả năng xấp xỉ của mạng nơ-ron nhiều tầng?
- 2Nếu thay mọi hàm kích hoạt tầng ẩn bằng hàm đồng nhất f(z) = z thì mạng nhiều tầng trở thành gì?
- 3Mạng đạt 99 phần trăm trên tập huấn luyện nhưng 62 phần trăm trên dữ liệu mới. Nên làm gì?
- 4Vì sao bài toán phân loại thường dùng cross-entropy thay vì MSE khi đầu ra qua sigmoid?
Tóm tắt
- Một nơ-ron tính
output = activation(w*x + b): nhân cộng rồi bẻ cong. - Perceptron chỉ vẽ được ranh giới thẳng nên chịu thua XOR. Tầng ẩn cho mạng tự tìm một biểu diễn mới, ở đó bài toán trở nên tách được tuyến tính.
- Hàm kích hoạt bắt buộc phải phi tuyến, nếu không mọi tầng gộp lại chỉ còn một tầng.
- Lan truyền xuôi cho ra dự đoán; hàm mất mát đo mức sai; lan truyền ngược cho ra gradient; gradient descent sửa tham số.
- Quá khớp: chống bằng thêm dữ liệu, thu nhỏ mạng, điều chuẩn L2, dropout và dừng sớm.
Một nơ-ron tính output = activation(w*x + b). Xếp chồng nhiều nơ-ron qua nhiều tầng với
hàm kích hoạt phi tuyến (sigmoid, ReLU) tạo thành MLP, đủ sức xấp xỉ mọi hàm liên tục
trên một miền giới nội.
Lan truyền xuôi cho ra dự đoán; lan truyền ngược dùng chain rule để tính gradient;
gradient descent cập nhật tham số w = w - eta * grad_w để mạng học dần.