Data Preprocessing
Tiền xử lý dữ liệu — Làm sạch trước khi học
Bạn có bảng dữ liệu căn hộ: 15% cột 'diện tích' bị thiếu, một hàng ghi tuổi chủ = -3 (lỗi nhập), một hàng ghi thu nhập = 9999 triệu (nhập nhầm). Bạn huấn luyện model, accuracy chỉ 55%. Vì sao?
Dữ liệu thô giống rau vừa hái — chưa rửa, chưa nhặt
Bạn đi chợ mua mớ rau cải. Ở ngoài đồng, có bùn, có lá sâu, có cọng héo, có con ốc bám rễ. Đầu bếp giỏi không bao giờ cho nguyên mớ rau vào nồi — người đó nhặt, rửa, cắt, rồi mới nấu.
Dữ liệu thô cũng thế.Thu thập từ form, từ cảm biến, từ log ứng dụng — luôn có ô để trống, có giá trị lỗi, có đơn vị khác nhau. Nếu “cho luôn vào nồi” (train model), món ăn sẽ dở — dù bạn có công thức “state of the art”.
Không làm sạch
Model “học” cả NaN và outlier → dự đoán lệch → báo cáo sai → sếp mất tiền.
Có làm sạch
Model nhận dữ liệu “chín tới” → học ra pattern thật → dự đoán ổn định cả khi deploy.
Hình minh họa
Đây là 8 hàng dữ liệu căn hộ Hà Nội. Cột có vấn đề đã được đánh dấu nền vàng. Bấm từng nút dưới đây để thấy hàng biến đổi thế nào. Bấm lại để tắt bước đó — so sánh trước / sau.
| id | tuổi | thu_nhập (triệu) | quận | loại | ghi chú |
|---|---|---|---|---|---|
| 1 | 28 | 25 | Hoàn Kiếm | Chung cư | |
| 2 | NaN | 40 | Ba Đình | Nhà phố | tuổi thiếu |
| 3 | 45 | 9999 | Hoàn Kiếm | Chung cư | thu nhập = 9999 (ngoại lai) |
| 4 | 35 | 60 | Cầu Giấy | Biệt thự | |
| 5 | 52 | NaN | Ba Đình | Nhà phố | thu nhập thiếu |
| 6 | 33 | 48 | Cầu Giấy | Chung cư | |
| 7 | -3 | 30 | Hoàn Kiếm | Nhà phố | tuổi = -3 (lỗi nhập) |
| 8 | 41 | 55 | Ba Đình | Biệt thự |
Bạn vừa chạy đoạn pandas tương đương
import pandas as pd
df = pd.read_csv('houses.csv')
# Bật các nút bên trên để thêm bước vào pipelineBốn thao tác trên không phải là “một nút bấm” duy nhất — mỗi thao tác có nhiều biến thể tuỳ tình huống. Bấm qua các tab để thấy trước/sau của từng lựa chọn, kèm đoạn pandas ngắn.
Có 4 chiến lược phổ biến cho dữ liệu thiếu. Mỗi chiến lược phù hợp với một tình huống.
Drop row
Xoá nguyên hàng có dữ liệu thiếu. Dùng khi số hàng thiếu < 5% và thiếu ngẫu nhiên.
Trước
[28, NaN, 45]
Sau
[28, 45]
Mean imputation
Điền bằng giá trị trung bình của cột. Dễ, nhưng dễ bị outlier kéo lệch.
Trước
[28, NaN, 45]
Sau
[28, 36.5, 45]
Median imputation
Điền bằng trung vị. 'Lì' hơn trước outlier — lựa chọn mặc định an toàn.
Trước
[28, NaN, 45, 500]
Sau
[28, 45, 45, 500]
Forward-fill
Dùng giá trị gần nhất trước đó. Hữu ích cho chuỗi thời gian (price, sensor).
Trước
[1.0, NaN, NaN, 1.3]
Sau
[1.0, 1.0, 1.0, 1.3]
import pandas as pd
df.dropna(subset=["thu_nhap"]) # 1. Drop row
df["thu_nhap"].fillna(df["thu_nhap"].mean()) # 2. Mean
df["thu_nhap"].fillna(df["thu_nhap"].median()) # 3. Median
df["thu_nhap"].ffill() # 4. Forward-fillis_missingvì “thiếu” chính là tín hiệu.Thứ tự thường gặp của cả pipeline:
Mô hình không biết dữ liệu của bạn bẩn đâu — nó học đúng những gì bạn đưa vào. Nếu bạn đưa tuổi = -3, model sẽ “học” rằng có tồn tại người tuổi âm. Nếu bạn cho nó NaN, nó sẽ khớp với NaN. Nếu “thu nhập” lớn gấp 10 lần “tuổi”, gradient descent sẽ chỉ nghe “thu nhập”.
Làm sạch dữ liệu không “xa xỉ” — nó là khoản đầu tư có lợi tức cao nhất trong toàn bộ pipeline ML. Data sạch + model đơn giản thường đánh bại data bẩn + model phức tạp.
Bạn đang xử lý dataset khách hàng với cột 'email_phụ'. 30% hàng có giá trị thiếu. Phương án hợp lý nhất?
Bạn có cột 'thu_nhập' range 5–500 triệu và cột 'tuổi' range 18–65. Bạn dùng KNN để phân loại. Vấn đề?
Giải thích
Tiền xử lý là tập hợp các bước biến dữ liệu thô thành dữ liệu sẵn sàng cho model. Ở đây, hai công thức thường gặp nhất trong công việc hàng ngày:
Công thức 1 — Chuẩn hoá bằng z-score
Nghĩa đơn giản: “giá trị x lệch bao nhiêu độ lệch chuẩn so với trung bình”. Sau khi biến đổi, cột có mean = 0 và std = 1. Gradient descent không còn bị cột “to” lấn át cột “nhỏ”, KNN xem mọi đặc trưng công bằng.
Công thức 2 — Min-max scaling
Kéo mọi giá trị vào đoạn [0, 1]. Hữu ích khi model yêu cầu đầu vào bị giới hạn (sigmoid, image pixels). Nhược điểm: nhạy với outlier — một giá trị cực đại sẽ “dí” mọi giá trị khác về gần 0.
Đoạn pandas thường dùng: z-score và min-max cùng một cột
from sklearn.preprocessing import StandardScaler, MinMaxScaler
std_scaler = StandardScaler()
minmax = MinMaxScaler()
X_train_std = std_scaler.fit_transform(X_train[["tuoi", "thu_nhap"]])
X_train_mm = minmax.fit_transform(X_train[["tuoi", "thu_nhap"]])
X_val_std = std_scaler.transform(X_val[["tuoi", "thu_nhap"]]) # không fit!
X_val_mm = minmax.transform(X_val[["tuoi", "thu_nhap"]])transform. Vi phạm quy tắc này gọi là data leakage — kết quả báo cáo cao giả tạo, khi deploy sẽ lộ ra.Ghép pipeline đầu tới cuối
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
clf = Pipeline([
("impute", SimpleImputer(strategy="median")),
("scale", StandardScaler()),
("model", LogisticRegression(max_iter=500)),
])
clf.fit(X_train, y_train)
clf.score(X_test, y_test)clf.predict(new_row) — các bước tiền xử lý được áp dụng tự động, không sợ lệch giữa training và serving.Pipeline cho cột số + cột chữ riêng biệt
Khi cột số và cột chữ cần xử lý khác nhau, tách thành hai nhánh rồi gộp lại bằng ColumnTransformer:
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder
num_pipe = Pipeline([
("impute", SimpleImputer(strategy="median")),
("scale", StandardScaler()),
])
cat_pipe = Pipeline([
("impute", SimpleImputer(strategy="most_frequent")),
("onehot", OneHotEncoder(handle_unknown="ignore")),
])prep = ColumnTransformer([
("num", num_pipe, ["tuoi", "thu_nhap"]),
("cat", cat_pipe, ["quan", "loai"]),
])
X_train_clean = prep.fit_transform(X_train)
X_test_clean = prep.transform(X_test) # không fit!Khi dữ liệu đã sạch, bước tiếp theo là feature engineering — tạo đặc trưng mới có ý nghĩa. Để ôn kỹ năng pandas nền tảng, xem lại Python cho ML.
- Làm sạch dữ liệu là bước có tác động lớn nhất lên chất lượng model — trước cả việc đổi kiến trúc.
- 4 thao tác cốt lõi: điền missing, lọc outlier, encode category, scale số.
- Luôn chia train/val/test TRƯỚC, rồi fit mọi biến đổi chỉ trên train.
- Median 'lì' trước outlier — dùng khi phân phối lệch hoặc có giá trị cực trị.
- Dùng sklearn Pipeline để đóng gói — mỗi bước áp dụng tự động khi deploy.
Sáu câu để chắc chắn bạn đã nắm được nguyên tắc.
Kiểm tra hiểu biết
Cột 'thu_nhập' có một hàng ghi 9999 triệu/tháng (rõ ràng lỗi nhập). Bạn nên xử lý thế nào đầu tiên?