Thuật toán MissForest là một phương pháp imputation (điền khuyết) dữ liệu mạnh mẽ, dựa trên thuật toán Random Forest (Rừng Ngẫu nhiên) để ước tính và điền vào các giá trị bị thiếu trong một tập dữ liệu. Nó được coi là một trong những phương pháp điền khuyết hiệu quả nhất, đặc biệt khi xử lý các tập dữ liệu phức tạp với các loại biến khác nhau (số và phân loại) và các cấu trúc tương quan phi tuyến.
Nguyên tắc hoạt động
Ý tưởng cốt lõi của MissForest là coi mỗi biến có giá trị bị thiếu như một biến mục tiêu và sử dụng các biến còn lại làm biến dự báo trong một mô hình Random Forest. Quá trình này được lặp đi lặp lại cho đến khi các giá trị điền khuyết hội tụ.
Các bước của thuật toán
Quá trình hoạt động của MissForest có thể được tóm tắt qua các bước sau:
- Khởi tạo:
- Các giá trị bị thiếu ban đầu được điền bằng một phương pháp đơn giản (ví dụ: điền bằng giá trị trung bình/median cho biến số, hoặc giá trị phổ biến nhất cho biến phân loại).
- Sắp xếp các biến:
- Các biến trong tập dữ liệu được sắp xếp theo thứ tự tăng dần của số lượng giá trị bị thiếu.
- Vòng lặp điền khuyết:
- Thuật toán lặp lại qua từng biến có giá trị bị thiếu (theo thứ tự đã sắp xếp).
- Đối với mỗi biến (tạm gọi là
Y
):- Các giá trị đã điền khuyết của
Y
được tạm thời loại bỏ. - Một mô hình Random Forest được huấn luyện, trong đó
Y
là biến mục tiêu và tất cả các biến khác (X
) là các biến dự báo. Mô hình này chỉ sử dụng các hàng màY
có giá trị gốc (không bị thiếu). - Mô hình vừa huấn luyện được sử dụng để dự đoán các giá trị bị thiếu trong
Y
. - Các giá trị bị thiếu trong
Y
được cập nhật bằng các giá trị dự đoán mới.
- Các giá trị đã điền khuyết của
- Quá trình này được thực hiện lần lượt cho tất cả các biến có dữ liệu khuyết.
- Điều kiện dừng:
- Vòng lặp ở bước 3 được tiếp tục cho đến khi sự khác biệt giữa tập dữ liệu đã điền khuyết ở vòng lặp hiện tại và vòng lặp trước đó không còn thay đổi đáng kể. Nói cách khác, thuật toán sẽ dừng lại khi các giá trị điền khuyết trở nên ổn định (hội tụ).
Ưu điểm và Nhược điểm
✅ Ưu điểm
- Độ chính xác cao: Thường cho kết quả tốt hơn so với các phương pháp điền khuyết đơn giản như điền giá trị trung bình, trung vị hoặc các phương pháp hồi quy tuyến tính.
- Xử lý được cả biến số và biến phân loại: Không cần phải biến đổi dữ liệu một cách phức tạp. Random Forest có thể xử lý tự nhiên cả hai loại biến này.
- Không yêu cầu chuẩn hóa dữ liệu: Random Forest không nhạy cảm với quy mô của các biến.
- Có khả năng nắm bắt các mối quan hệ phức tạp: Thuật toán có thể mô hình hóa các tương tác phi tuyến và phức tạp giữa các biến để đưa ra dự đoán tốt hơn.
- Ít yêu cầu tinh chỉnh tham số: Thường hoạt động tốt với các cài đặt mặc định.
❌ Nhược điểm
- Tốn kém về mặt tính toán: Huấn luyện nhiều mô hình Random Forest qua nhiều vòng lặp có thể rất chậm, đặc biệt với các tập dữ liệu lớn (cả về số hàng và số cột).
MissForest trong Python
bạn có thể dùng thư viện missingpy
. Đây là một thư viện chuyên dụng cho việc điền khuyết dữ liệu. Tuy nhiên, nó khá cũ và đã lâu không được update. Có một số package khác trong pythoncho MissForest, nhưng nhìn chung thì các package khác chạy cho ra kết quả không được tốt so với missingpy, đôi khi là tệ hơn rất nhiều.
Một giải pháp thay thế mạnh mẽ hơn là sử dụng IterativeImputer
từ thư viện scikit-learn
, vì nó có thể được cấu hình để hoạt động gần giống như MissForest bằng cách sử dụng RandomForestRegressor
.
Đây là cách thực hiện:
Python
import numpy as np
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
from sklearn.ensemble import RandomForestRegressor
# Dữ liệu gốc (sử dụng lại dữ liệu từ ví dụ trên)
X = np.array([
[1, 2, np.nan, 4],
[3, 4, 2, 1],
[np.nan, 6, 7, 8],
[8, np.nan, 1, 2],
[2, 3, 4, 5]
])
print("Dữ liệu gốc với giá trị bị thiếu:")
print(X)
# Khởi tạo IterativeImputer với mô hình Random Forest
# Đây chính là cách mô phỏng MissForest
imputer_sklearn = IterativeImputer(
estimator=RandomForestRegressor(n_estimators=100, random_state=42),
max_iter=10, # Số vòng lặp tối đa
random_state=42
)
# Áp dụng để điền khuyết
X_imputed_sklearn = imputer_sklearn.fit_transform(X)
print("\n----------------------------------\n")
print("Dữ liệu sau khi được điền khuyết bằng IterativeImputer (với Random Forest):")
print(X_imputed_sklearn)
Ví dụ 2 trên dữ liệu chứa biến phân loại:
import pandas as pd
import numpy as np
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
from sklearn.ensemble import RandomForestRegressor
from sklearn.preprocessing import LabelEncoder
# 1. Tạo dữ liệu mẫu
df = pd.DataFrame({
'Tuoi': [25, 32, np.nan, 45, 28, 50],
'Luong': [50000, 75000, 60000, 120000, np.nan, 150000],
'ThanhPho': ['Hà Nội', 'Đà Nẵng', 'Hồ Chí Minh', np.nan, 'Hà Nội', 'Hồ Chí Minh']
})
print("Dữ liệu gốc với giá trị bị thiếu:")
print(df)
# 2. Mã hóa biến phân loại một cách chính xác
df_encoded = df.copy()
encoders = {}
for col_name in df.columns:
# Chỉ xử lý các cột dạng object/categorical
if df[col_name].dtype == 'object':
le = LabelEncoder()
# Lấy ra các giá trị không bị thiếu trong cột
valid_data = df[col_name].dropna()
# Chỉ "dạy" (fit) encoder trên các giá trị hợp lệ này
le.fit(valid_data)
# "Biến đổi" (transform) các giá trị hợp lệ và gán vào DataFrame
# Các giá trị NaN sẽ được giữ nguyên là NaN
df_encoded[col_name] = df[col_name].apply(lambda x: le.transform([x])[0] if pd.notna(x) else np.nan)
encoders[col_name] = le # Lưu lại encoder
print("\n----------------------------------\n")
print("Dữ liệu sau khi mã hóa ĐÚNG CÁCH:")
print(df_encoded)
# 3. Áp dụng IterativeImputer
estimator = RandomForestRegressor(n_estimators=100, random_state=42)
imputer = IterativeImputer(estimator=estimator, max_iter=10, random_state=42)
X_imputed_encoded = imputer.fit_transform(df_encoded)
df_imputed_encoded = pd.DataFrame(X_imputed_encoded, columns=df.columns)
df_imputed_encoded['ThanhPho'] = np.round(df_imputed_encoded['ThanhPho']).astype(int)
# 4. Giải mã cột 'ThanhPho'
df_final = df_imputed_encoded.copy()
for col_name in encoders:
le = encoders[col_name]
df_final[col_name] = le.inverse_transform(df_final[col_name].astype(int))
print("\n----------------------------------\n")
print("KẾT QUẢ CUỐI CÙNG:")
print(df_final)
Kết quả
Dữ liệu gốc với giá trị bị thiếu:
Tuoi Luong ThanhPho
0 25.0 50000.0 Hà Nội
1 32.0 75000.0 Đà Nẵng
2 NaN 60000.0 Hồ Chí Minh
3 45.0 120000.0 NaN
4 28.0 NaN Hà Nội
5 50.0 150000.0 Hồ Chí Minh
----------------------------------
Dữ liệu sau khi mã hóa ĐÚNG CÁCH:
Tuoi Luong ThanhPho
0 25.0 50000.0 0.0
1 32.0 75000.0 2.0
2 NaN 60000.0 1.0
3 45.0 120000.0 NaN
4 28.0 NaN 0.0
5 50.0 150000.0 1.0
----------------------------------
KẾT QUẢ CUỐI CÙNG:
Tuoi Luong ThanhPho
0 25.00 50000.0 Hà Nội
1 32.00 75000.0 Đà Nẵng
2 31.67 60000.0 Hồ Chí Minh
3 45.00 120000.0 Hồ Chí Minh
4 28.00 56800.0 Hà Nội
5 50.00 150000.0 Hồ Chí Minh
Vì sao Label encoder ở đây phức tạp như vậy?
Nếu chúng ta chỉ dùng
series = df_encoded[col_name].astype(str)
le.fit_transform(series)
thì Python chuyển np.nan
thành chuỗi 'nan'
, sau đó dạy cho LabelEncoder
biết rằng 'nan'
là một trong những “Thành phố” hợp lệ!!!
Tại sao lại dùng label encoder khi biến phân loại không có thứ tự
Về mặt lý thuyết, LabelEncoder
dùng cho dữ liệu có thứ tự (ordinal), còn OneHotEncoder
mới là phương pháp chuẩn xác cho dữ liệu phân loại không có thứ tự (nominal / categorical) như ‘Thành phố’.
Vậy tại sao trong ví dụ trên, LabelEncoder
lại được sử dụng và vẫn hoạt động tốt? Lý do nằm ở bản chất của mô hình được sử dụng (Random Forest). Hãy xem xét ưu và nhược điểm của mỗi phương pháp khi dùng với Random Forest:
Phương pháp | Ưu điểm 👍 | Nhược điểm 👎 |
LabelEncoder | Giữ nguyên số chiều dữ liệu (1 cột vẫn là 1 cột). Điều này giúp thuật toán chạy nhanh hơn và đơn giản hơn rất nhiều trong vòng lặp điền khuyết. | Tạo ra một thứ tự giả (ví dụ: HCM > Đà Nẵng > Hà Nội). Đây là vấn đề lớn đối với các mô hình tuyến tính (Linear Regression), nhưng ít nghiêm trọng hơn với mô hình cây. |
OneHotEncoder | Về mặt lý thuyết là phương pháp đúng nhất, không tạo ra thứ tự giả. | Làm tăng số chiều dữ liệu (1 cột ‘Thành phố’ sẽ biến thành nhiều cột như ‘TP_Hà Nội’, ‘TP_Đà Nẵng’,…). Điều này làm cho việc điền khuyết phức tạp hơn và tốn kém tài nguyên tính toán hơn. |
Kết luận
Việc sử dụng LabelEncoder
trong các ví dụ trên là một sự đánh đổi có chủ đích giữa tính chính xác lý thuyết và tính hiệu quả thực tế.
- Đối với các mô hình dựa trên cây như Random Forest, việc “lạm dụng”
LabelEncoder
cho dữ liệu nominal thường được chấp nhận vì mô hình đủ mạnh để xử lý nó và giúp quy trình xử lý đơn giản hơn rất nhiều. - Nếu bạn sử dụng các mô hình khác nhạy cảm với thứ tự và độ lớn của giá trị (như Hồi quy tuyến tính, SVM, hay mạng nơ-ron), bạn bắt buộc phải sử dụng
OneHotEncoder
cho dữ liệu nominal.
Đọc thêm: Mối liên hệ ẩn dụ giữa MissForest và “Nhớ rừng”
Kết quả cuối cùng: dù dữ liệu khôi phục chỉ là ước lượng, còn ký ức của hổ chỉ là hoài niệm — nhưng cả hai đều mang trong mình khát vọng vẹn toàn
Dữ liệu khuyết trong MissForest giống như con hổ bị mất đi tự do, không thể hiện đầy đủ giá trị và sức mạnh vốn có.
Thuật toán MissForest như một quá trình “tái thiết ký ức” cho dữ liệu, giúp phục hồi lại bức tranh nguyên vẹn — cũng như cách con hổ trong “Nhớ rừng” luôn nhớ về và tái tạo hình ảnh oai hùng của mình nơi rừng xanh.
Ở mức sâu hơn:
MissForest dùng những mảnh dữ liệu còn lại để dự đoán phần thiếu → giống con hổ mượn ký ức để hình dung lại không gian tự do đã mất.
Cả hai đều là nỗ lực không cam chịu sự thiếu hụt: dữ liệu không chấp nhận “lỗ hổng”, con hổ không cam chịu “cũi sắt”.