Chuyển tới nội dung chính

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.

Perceptron học ranh giới quyết địnhepoch 0
Trọng số ban đầu
lớp A lớp Bchưa học
w1 = 0.00, w2 = 0.00, b = 0.00
Hai lớp điểm chưa có ranh giới. Bấm để chạy một vòng, perceptron sẽ chỉnh đường mỗi khi gặp điểm phân loại sai.
Bấm vào khung để thêm điểm thuộc lớp đang chọn, bấm trúng một điểm để xoá nó. Thử xếp hai lớp xen kẽ nhau xem perceptron có tách nổi không. Lưu ý: khi trọng số ban đầu bằng 0, tốc độ học chỉ nhân tỉ lệ w1, w2, b nên ranh giới không đổi. Hãy đổi sang khởi tạo ngẫu nhiên rồi mới so các tốc độ học với nhau.

Ý 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 + b rồi cho ra 1 nếu z lớn hơn 0, ngược lại cho ra 0. Đâ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ới eta là 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ừ 0 tới 1, 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ần 0 trong 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ừ -1 tới 1, 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ằng 1 khi z lớn hơn 0 và bằng 0 khi z nhỏ hơn 0, 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 cho z âm thì gradient luôn bằng 0 và 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ầng 0 bằng đầu vào x.
  • Với mỗi tầng từ 1 tới L: tính z = W*a_truoc + b rồi a = 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 -log củ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 0 và 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.

Ba hiểu nhầm thường gặp

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 listfloat, 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]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ộ wb 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

Kiểm tra nhanh: mạng nơ-ron0/4 đúngchưa trả lời
  1. 1Phát biểu nào đúng về khả năng xấp xỉ của mạng nơ-ron nhiều tầng?
  2. 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ì?
  3. 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ì?
  4. 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.
Ghi nhớ

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.