Ở bài viết trước, chúng ta đã cùng nhau xây dựng mô hình Hồi quy tuyến tính từ những viên gạch cơ bản nhất. Chúng ta đã hiểu về hàm mất mát, về Gradient Descent, và cách triển khai chúng bằng những vòng lặp for.
Tuy nhiên, khi làm việc với các bộ dữ liệu lớn, bạn sẽ nhanh chóng nhận ra một vấn đề: vòng lặp for trong Python... rất chậm!
Vậy làm thế nào để các chuyên gia AI xử lý những bộ dữ liệu khổng lồ với hàng triệu điểm dữ liệu? Bí mật nằm ở một kỹ thuật gọi là Vectorization (Vector hóa). Hôm nay, chúng ta sẽ cùng nhau khám phá "siêu năng lực" này và biến đoạn code Hồi quy tuyến tính của mình thành một cỗ máy tốc độ cao với sự trợ giúp của thư viện Numpy.
1. Vấn Đề Của Vòng Lặp "for"
Hãy nhớ lại mô hình dự đoán doanh số dựa trên chi phí quảng cáo TV, Radio và Newspaper:
$$
Sales = w_1 \cdot TV + w_2 \cdot Radio + w_3 \cdot Newspaper + b
$$
Trong đó:
* Sales: Giá trị mục tiêu cần dự đoán (biến phụ thuộc).
-
w₁,w₂,w₃: Các trọng số (weights) tương ứng với từng đặc trưng, thể hiện mức độ quan trọng của đặc trưng đó. -
TV,Radio,Newspaper: Các giá trị của đặc trưng đầu vào (biến độc lập). -
b: Hệ số chặn (bias hoặc intercept), là giá trị dự đoán khi tất cả các đặc trưng đầu vào bằng 0.
Khi triển khai Gradient Descent, chúng ta thường viết code như thế này:
# Đoạn code minh họa ý tưởng (không chạy)
for i in range(number_of_samples):
# Lấy một mẫu dữ liệu
tv = tv_data[i]
radio = radio_data[i]
newspaper = newspaper_data[i]
actual_sales = sales_data[i]
# 1. Dự đoán
predicted_sales = w1*tv + w2*radio + w3*newspaper + b
# 2. Tính đạo hàm
error = predicted_sales - actual_sales
grad_w1 += 2 * tv * error
grad_w2 += 2 * radio * error
grad_w3 += 2 * newspaper * error
grad_b += 2 * error
# 3. Cập nhật (sau khi lặp hết)
w1 = w1 - learning_rate * (grad_w1 / N)
# ... tương tự cho w2, w3, b
Đoạn code trên hoàn toàn đúng về mặt logic, nhưng nó lặp qua từng điểm dữ liệu, thực hiện các phép tính riêng lẻ. Với Python, mỗi lần lặp như vậy đều tốn chi phí xử lý. Khi N (số lượng mẫu) lên tới hàng triệu, thời gian chờ đợi sẽ là vô tận.
2. Các bước của quy trình Vectorization
Vectorization là quy trình biến các vòng lặp xử lý từng phần tử thành các phép toán ma trận và vector duy nhất. Thay vì nhìn vào từng con số, chúng ta sẽ làm việc với toàn bộ tập dữ liệu cùng một lúc.
Bước 1: Gộp các tham số vào Vector theta
Đầu tiên, chúng ta sẽ gộp tất cả các tham số của mô hình (w1, w2, w3, ..., b) vào một vector duy nhất gọi là theta.
$$
\theta = \begin{bmatrix} b \\ w_1 \\ w_2 \\ w_3 \end{bmatrix}
$$
Trong đó:
* θ (theta): Vector tham số, chứa tất cả các tham số mà mô hình cần học.

Bước 2: Khởi tạo lại dữ liệu đầu vào với Ma trận X
Để phép toán ma trận hoạt động, chúng ta cần một "thủ thuật" nhỏ: thêm một cột chứa toàn số 1 vào đầu ma trận dữ liệu đầu vào. Cột số 1 này sẽ tương ứng với tham số b (bias).
Nếu dữ liệu ban đầu là:
| TV | Radio | Newspaper |
|---|---|---|
| 230.1 | 37.8 | 69.2 |
| 44.5 | 39.3 | 45.1 |
Thì ma trận X mới sẽ là:
$$ X = \begin{bmatrix} 1 & 230.1 & 37.8 & 69.2 \\ 1 & 44.5 & 39.3 & 45.1 \end{bmatrix} $$
Bước 3: Viết lại công thức
Toàn bộ các phép tính của Gradient Descent có thể được viết lại bằng các phép toán ma trận đơn giản.
Dự đoán (Prediction)
- Trước (vòng lặp):
predicted_sales = w1*tv + w2*radio + ... + b -
Sau (vector hóa):
$$ \hat{y} = X \cdot \theta $$
Trong đó:
- ŷ (y-hat): Vector các giá trị dự đoán. Mỗi phần tử trong vector này là một giá trị dự đoán cho một điểm dữ liệu tương ứng.
-
X: Ma trận thiết kế (Design Matrix). Mỗi hàng là một điểm dữ liệu, và mỗi cột là một đặc trưng. Cột đầu tiên của ma trận này luôn là số 1 để nhân với hệ số chặn b trong vector θ. -
θ: Vector tham số đã được định nghĩa ở trên.
Một dòng lệnh duy nhất có thể tính ra giá trị dự đoán cho toàn bộ tập dữ liệu
# y_hat sẽ là một vector chứa tất cả các dự đoán
y_hat = np.dot(X, theta)
# Hoặc cách viết ngắn gọn hơn
y_hat = X @ theta
Tính Hàm Mất Mát (Loss Function)
- Trước (vòng lặp): Tính tổng bình phương sai số của từng điểm.
- Sau (vector hóa):
$$ L = \frac{1}{N} (\hat{y} - y)^T (\hat{y} - y) $$
Trong đó:
- L: Giá trị của hàm mất mát (Loss), là một số vô hướng duy nhất đại diện cho mức độ sai sót của mô hình trên toàn bộ dữ liệu.
-
N: Tổng số lượng mẫu (samples) trong tập dữ liệu. -
ŷ: Vector các giá trị dự đoán. -
y: Vector chứa các giá trị thực tế (ground truth). -
(...)ᵀ: Toán tử chuyển vị (Transpose). Phép nhân(vector_lỗi)ᵀ ⋅ (vector_lỗi)chính là cách tính tổng bình phương của các phần tử trong vector lỗi.
error = y_hat - y
loss = (1/N) * (error.T @ error)
Tính Đạo Hàm (Gradient)
Đây là bước ấn tượng nhất. Toàn bộ vòng lặp tính grad_w1, grad_w2... được thay thế bằng một công thức duy nhất:
- Trước (vòng lặp): Lặp và cộng dồn đạo hàm riêng.
- Sau (vector hóa):
$$ \nabla_{\theta}L = \frac{1}{N} X^T (\hat{y} - y) $$
Trong đó:
- ∇_θ L: Gradient của hàm mất mát L theo θ. Đây là một vector, mỗi phần tử trong đó là đạo hàm riêng của L theo một tham số tương ứng trong θ (ví dụ: ∂L/∂b, ∂L/∂w₁, ...). Vector này cho biết hướng và tốc độ tăng nhanh nhất của hàm mất mát.
-
Xᵀ: Ma trận chuyển vị của ma trận thiết kế X. -
N,ŷ,y: Có ý nghĩa như trên.
gradients = (1/N) * (X.T @ error)
Cập Nhật Tham Số (Update)
- Trước: Cập nhật
w1,w2,briêng lẻ. -
Sau (vector hóa):
$$ \theta = \theta - \eta \cdot \nabla_{\theta}L $$
theta = theta - learning_rate * gradients

3. Tổng Hợp Lại: Code Hoàn Chỉnh
Bây giờ, hãy xem toàn bộ quá trình huấn luyện Hồi quy tuyến tính trông "gọn gàng" và hiệu quả như thế nào khi được vector hóa. Dưới đây là ví dụ đầy đủ cho bài toán dự đoán giá nhà.
import numpy as np
import matplotlib.pyplot as plt
# Dữ liệu: Diện tích (x100m^2) và Giá (Tael)
areas = np.array([6.7, 4.6, 3.5, 5.5])
prices = np.array([9.1, 5.9, 4.6, 6.7])
# Chuẩn bị dữ liệu cho vector hóa
N = areas.shape[0]
# Thêm cột 1 vào dữ liệu đầu vào
X = np.c_[np.ones(N), areas]
y = prices.reshape(N, 1)
# Khởi tạo tham số
theta = np.zeros((2, 1)) # [b, w]
# Hyperparameters
learning_rate = 0.01
epochs = 100
# Quá trình huấn luyện
for epoch in range(epochs):
# 1. Dự đoán (cho toàn bộ dữ liệu)
y_hat = X @ theta
# 2. Tính mất mát (để theo dõi)
error = y_hat - y
loss = (1/(2*N)) * (error.T @ error) # Sử dụng 1/2N cho tiện tính toán đạo hàm
# 3. Tính gradient
gradients = (1/N) * (X.T @ error)
# 4. Cập nhật theta
theta = theta - learning_rate * gradients
print("Các tham số tối ưu (b, w):")
print(theta)
# Vẽ đồ thị
plt.scatter(areas, prices, label='Dữ liệu thực tế')
plt.plot(areas, X @ theta, color='red', label='Đường hồi quy')
plt.xlabel('Diện tích (x 100m^2)')
plt.ylabel('Giá (Tael)')
plt.legend()
plt.show()
Kết Luận
Vectorization không chỉ là một thủ thuật tối ưu hóa, nó là một cách tư duy cốt lõi trong Khoa học dữ liệu và Học sâu. Bằng cách chuyển đổi các vòng lặp thành các phép toán ma trận, chúng ta không chỉ làm cho code chạy nhanh hơn gấp nhiều lần mà còn làm cho nó ngắn gọn, dễ đọc và gần gũi hơn với các công thức toán học.
Chưa có bình luận nào. Hãy là người đầu tiên!