Skip to content

Lập trình hướng đối tượng (OOP)

This content is not available in your language yet.

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

  • Hiểu rõ bản chất của Lớp (Class) và Đối tượng (Object), làm chủ phương thức khởi tạo __init__ và tham số thực thể self.
  • Phân biệt biến thực thể (instance attribute) độc lập với biến lớp (class attribute) dùng chung để tránh lỗi rò rỉ trọng số mô hình.
  • Nắm vững 4 trụ cột của lập trình hướng đối tượng (Đóng gói, Kế thừa, Đa hình, Trừu tượng) và liên hệ trực tiếp với các framework AI hàng đầu như PyTorch và Scikit-Learn.
  • Sử dụng các phương thức ma thuật (dunder methods như __len__, __getitem__, __call__) để xây dựng các lớp nạp dữ liệu và mô hình máy học chuyên nghiệp.

Hãy tưởng tượng bạn đang xây dựng một hệ thống thị giác máy tính với 50 tầng mạng nơ-ron:

  • Mỗi tầng mạng có hàng nghìn tham số ma trận trọng số W\mathbf{W} và hệ số chệch b\mathbf{b}.
  • Mỗi tầng cần lưu lại kết quả trung gian để phục vụ thuật toán lan truyền ngược (backpropagation).
  • Nếu chỉ dùng các hàm rời rạc và các biến từ điển (dict) trôi nổi, bạn sẽ phải liên tục truyền hàng chục tham số qua lại giữa các hàm. Chỉ cần sơ suất gán nhầm tên biến, toàn bộ hệ thống sẽ sụp đổ trong im lặng.

Lập trình hướng đối tượng (OOP) giải quyết triệt để bài toán này. Nó cho phép bạn coi mỗi tầng mạng như một “cỗ máy nhỏ” tự quản:

  • Cỗ máy tự cất giữ trọng số riêng bên trong thân máy (Đóng gói).
  • Cỗ máy có thể được nâng cấp từ một thiết kế chuẩn có sẵn mà không cần chế tạo lại từ đầu (Kế thừa).
  • Bất kỳ cỗ máy nào cũng chỉ cần một nút bấm “Dự đoán” duy nhất để vận hành, dù bên trong là mạng tích chập hay mạng Transformer (Đa hình).
  • Lớp (Class): Là một bản thiết kế (blueprint) định nghĩa cấu trúc dữ liệu và các thao tác có thể thực hiện.
  • Đối tượng (Object / Instance): Là một thực thể cụ thể được sinh ra trong bộ nhớ từ bản thiết kế đó.
  • __init__: Phương thức khởi tạo đặc biệt được Python tự động kích hoạt khi một đối tượng mới được tạo ra.
  • self: Tham số đầu tiên trong các phương thức thực thể, đại diện cho chính đối tượng đang thực hiện hành vi.
class Neuron:
def __init__(self, weight: float, bias: float):
# Thuộc tính thực thể (instance attributes)
self.w = weight
self.b = bias
def activate(self, x: float) -> float:
# Phương thức thực thể (instance method)
return self.w * x + self.b

Một trong những cạm bẫy lớn nhất trong lập trình AI là nhầm lẫn giữa hai loại biến:

  1. Biến thực thể (Instance Attribute): Được gán thông qua self.ten_bien bên trong các phương thức. Mỗi đối tượng sở hữu một vùng nhớ độc lập hoàn toàn.
  2. Biến lớp (Class Attribute): Được định nghĩa trực tiếp trong thân class, nằm ngoài mọi phương thức. Tất cả các đối tượng tạo từ lớp này đều trỏ chung vào cùng một vùng nhớ. Nếu biến lớp là kiểu khả biến (như list hay dict), việc một đối tượng sửa đổi sẽ làm thay đổi giá trị của tất cả đối tượng còn lại!

Đóng gói là cơ chế gom nhóm dữ liệu (thuộc tính) và các hàm thao tác trên dữ liệu đó (phương thức) vào cùng một đơn vị, đồng thời hạn chế sự can thiệp trực tiếp từ bên ngoài.

Trong Python, ta dùng quy ước đặt tên với dấu gạch dưới:

  • _attribute: Biến nội bộ (protected convention), cảnh báo người dùng không nên truy cập trực tiếp từ bên ngoài.
  • __attribute: Kích hoạt cơ chế đổi tên (name mangling) để tránh xung đột tên trong kế thừa.

Kế thừa cho phép một lớp mới (lớp con - child class) tiếp nhận toàn bộ thuộc tính và phương thức của một lớp đã có (lớp cha - parent class), đồng thời mở rộng hoặc định nghĩa lại (override) các hành vi cần thiết.

Trong PyTorch, mọi mô hình học sâu đều bắt buộc kế thừa từ torch.nn.Module:

# Lớp con kế thừa lớp cha nn.Module
class MyClassifier(nn.Module):
def __init__(self, in_features: int, out_features: int):
super().__init__() # Kích hoạt khởi tạo của nn.Module
self.linear = nn.Linear(in_features, out_features)
def forward(self, x):
return self.linear(x)

Hàm super().__init__() kích hoạt hạ tầng của nn.Module, giúp mô hình tự động quản lý gradient, theo dõi thiết bị (CPU/GPU) và lưu trữ checkpoint.

Đa hình là khả năng các đối tượng khác nhau phản hồi theo cách riêng của mình trước cùng một lời gọi phương thức.

Hệ sinh thái Scikit-Learn là ví dụ điển hình nhất về tính đa hình:

  • Mọi mô hình ước lượng (Estimator) — từ LinearRegression, DecisionTreeClassifier đến RandomForestRegressor — đều cung cấp hai phương thức chuẩn hóa:
    • model.fit(X, y): Huấn luyện tìm tham số tối ưu.
    • model.predict(X): Suy luận đưa ra kết quả dự đoán.
  • Nhờ đa hình, bạn có thể viết một hàm kiểm thử duy nhất nhận bất kỳ mô hình nào vào làm tham số mà không cần sửa đổi dù chỉ một dòng code.

Trừu tượng hóa là việc ẩn đi các chi tiết cài đặt phức tạp bên dưới, chỉ cung cấp ra bên ngoài một giao diện làm việc tối giản và thiết yếu.

Python hỗ trợ tính trừu tượng thông qua module chuẩn abc (Abstract Base Classes). Lớp cơ sở trừu tượng quy định các phương thức bắt buộc bằng decorator @abstractmethod, buộc các lớp con kế thừa phải cài đặt đầy đủ logic nếu không muốn bị báo lỗi ngay khi khởi tạo.

Python sở hữu các phương thức đặc biệt có hai dấu gạch dưới ở đầu và cuối (Double Underscore - viết tắt là Dunder):

  • __len__(self): Cho phép gọi hàm len(obj). Thường dùng để đếm số mẫu hoặc số batch trong tập dữ liệu.
  • __getitem__(self, idx): Cho phép truy xuất phần tử theo cú pháp chỉ số obj[idx].
  • __call__(self, *args): Biến đối tượng thành một hàm có thể gọi được obj(x). Đây chính là cách PyTorch cho phép bạn viết output = model(input) thay vì gọi tường minh model.forward(input).

Mối quan hệ kế thừa và giao diện đa hình chuẩn mực trong kiến trúc học máy:

┌────────────────────────────────────────────────────────┐
│ Lớp Trừu Tượng: BaseEstimator │
│ - Giao diện trừu tượng: fit(X, y), predict(X) │
└───────────────────────────┬────────────────────────────┘
│
┌────────────────────┴────────────────────┐
▼ ▼
┌───────────────────────┐ ┌───────────────────────┐
│ Lớp LinearRegression │ │ Lớp DecisionTreeModel │
│ - self.w, self.b │ │ - self.tree_nodes │
│ - fit(): giải OLS │ │ - fit(): chia nhánh │
│ - predict(): w*x + b │ │ - predict(): duyệt cây│
└───────────────────────┘ └───────────────────────┘
│ │
└────────────────────┬────────────────────┘
│ (Tính đa hình Polymorphism)
▼
┌──────────────────────────────────────────────────┐
│ Pipeline / cross_val_score(model, X_val, y_val) │
│ Gọi chung: model.fit(...) và model.predict(...) │
└──────────────────────────────────────────────────┘

Các ô dưới đây chạy độc lập. Bạn có thể nhấn nút Chạy để xem ngay kết quả.

Ô thứ nhất xây dựng một lớp LinearModel hoàn chỉnh từ đầu: đóng gói trọng số self.w và độ lệch self.b, cung cấp phương thức fit() tối ưu hóa gradient descent và phương thức predict() suy luận.

Xây dựng lớp mô hình hồi quy tuyến tính với OOP

class LinearModel:
def __init__(self, learning_rate: float = 0.05):
self.lr = learning_rate
self.w = 0.0
self.b = 0.0
def predict(self, x: float) -> float:
return self.w * x + self.b
def fit(self, x_data: list[float], y_data: list[float], epochs: int = 50):
m = len(x_data)
for _ in range(epochs):
# Tính gradient sai số MSE cho trọng số w và độ lệch b
dw = sum(2 * (self.predict(x) - y) * x for x, y in zip(x_data, y_data)) / m
db = sum(2 * (self.predict(x) - y) for x, y in zip(x_data, y_data)) / m
self.w -= self.lr * dw
self.b -= self.lr * db
# Dữ liệu huấn luyện mẫu (quan hệ thực tế: y = 2x + 1)
X_train = [1.0, 2.0, 3.0, 4.0]
y_train = [3.0, 5.0, 7.0, 9.0]
# Khởi tạo mô hình và tối ưu hóa tham số
model = LinearModel(learning_rate=0.05)
model.fit(X_train, y_train, epochs=200)
print(f"Weight w: {model.w:.3f}")
print(f"Bias b: {model.b:.3f}")
print(f"Prediction for x=5: {model.predict(5.0):.3f}")

The first run downloads Python, about 10 MB.

Toàn bộ quá trình tính đạo hàm riêng và cập nhật tham số được gói gọn gàng bên trong phương thức fit(). Người dùng bên ngoài chỉ cần tương tác với mô hình qua giao diện sạch sẽ, trực quan.

Ô thứ hai xây dựng lớp DataBatcher mô phỏng cơ chế mini-batching trong Deep Learning, minh họa tính đóng gói dữ liệu và phương thức ma thuật __len__.

Xây dựng lớp DataBatcher phân chia mini-batch

import math
class DataBatcher:
def __init__(self, features: list[list[float]], labels: list[int], batch_size: int = 2):
self._features = features
self._labels = labels
self.batch_size = batch_size
def __len__(self) -> int:
return math.ceil(len(self._features) / self.batch_size)
def get_batch(self, index: int) -> tuple[list[list[float]], list[int]]:
start = index * self.batch_size
end = start + self.batch_size
return self._features[start:end], self._labels[start:end]
# Dữ liệu gồm 5 mẫu với 2 đặc trưng và nhãn nhị phân
data_x = [[1.2, 0.7], [-0.5, 1.4], [2.1, -1.0], [0.3, 0.9], [-1.2, -0.4]]
data_y = [1, 0, 1, 1, 0]
# Khởi tạo batcher với kích thước mỗi mini-batch là 2
batcher = DataBatcher(data_x, data_y, batch_size=2)
print(f"Total batches: {len(batcher)}")
# Lặp qua từng batch để mô phỏng một chu kỳ huấn luyện (epoch)
for i in range(len(batcher)):
bx, by = batcher.get_batch(i)
print(f"Batch {i}: {len(bx)} samples, labels = {by}")

The first run downloads Python, about 10 MB.

Đối tượng batcher che giấu cấu trúc danh sách bên trong, tự động tính toán tổng số batch và chia nhỏ tập dữ liệu tương ứng với từng bước lặp huấn luyện.

Question 1 of 4 Cho định nghĩa lớp sau: class DenseLayer: weights = []. Nếu ta khởi tạo hai đối tượng layer1 = DenseLayer() và layer2 = DenseLayer(), rồi gọi layer1.weights.append(0.5), giá trị của layer2.weights sẽ là gì?
Question 2 of 4 Trong Python, từ khóa self trong định nghĩa phương thức def forward(self, x): đóng vai trò gì?
Question 3 of 4 Khi xây dựng một kiến trúc mạng nơ-ron tùy biến kế thừa từ lớp cha (ví dụ torch.nn.Module), vì sao bước đầu tiên trong def __init__(self): luôn phải là super().__init__()?
Question 4 of 4 Tính chất nào của OOP cho phép Scikit-Learn chạy hàm đánh giá chéo cross_val_score(model, X, y) trên hàng chục mô hình khác nhau (LinearRegression, RandomForest, SVM) mà không cần viết lại mã nguồn kiểm tra?

Ý chính:

  • Lớp là bản thiết kế, đối tượng là thực thể cụ thể; self đại diện cho chính đối tượng đang thực thi.
  • Luôn khởi tạo thuộc tính bên trong __init__ để bảo đảm tính độc lập dữ liệu giữa các thực thể.
  • 4 trụ cột OOP (Đóng gói, Kế thừa, Đa hình, Trừu tượng) là nền tảng kiến trúc của mọi thư viện AI chuyên nghiệp (PyTorch, Scikit-Learn, Hugging Face).
  • Các phương thức ma thuật (__len__, __getitem__, __call__) biến các lớp tự định nghĩa thành những đối tượng chuẩn mực của Python.

Đọc thêm:

  • Python Software Foundation (2024). The Python Tutorial: Classes. Hướng dẫn chính thức về cú pháp hướng đối tượng, scope và không gian tên trong Python.
  • PyTorch Team (2024). torch.nn.Module Documentation. Hướng dẫn về lớp cơ sở của mọi mạng nơ-ron trong PyTorch.
  • Scikit-Learn Developers (2024). Developing Scikit-Learn Estimators. Tài liệu về quy chuẩn thiết kế lớp ước lượng máy học với các giao diện fit, predict.

Content licensed under CC BY-NC-SA 4.0. Code under the MIT license.About