DHDataHọc
Lý thuyếtBài 4/84 phút đọc+10 XP

Quy trình huấn luyện với scikit-learn

Bạn sẽ học được

  • Đóng gói tiền xử lý và mô hình vào một Pipeline.
  • Xử lý cột số và cột phân loại khác nhau bằng ColumnTransformer.
  • Tránh rò rỉ khi chuẩn hoá và điền thiếu.
  • Chạy cross-validation trên toàn pipeline.
  • Lưu và tái sử dụng pipeline khi triển khai.

Khái niệm cốt lõi

Một mô hình thực tế gồm nhiều bước: điền giá trị thiếu, mã hoá biến phân loại, chuẩn hoá, rồi mới đến mô hình.

Nếu làm thủ công từng bước, rất dễ fit scaler trên toàn bộ dữ liệu — một dạng rò rỉ. Pipeline buộc mọi bước phải fit trên đúng tập train.

code
Pipeline = tiền xử lý → mô hình

Khi gọi pipeline.fit(X_train, y_train), mọi bước chỉ thấy train. Khi gọi predict, chúng dùng tham số đã học từ train.

Ví dụ đầy đủ

code
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler

numeric = ["tenure_months", "monthly_spend", "support_tickets"]
categorical = ["plan", "country", "channel"]

preprocess = ColumnTransformer(
    transformers=[
        (
            "num",
            Pipeline([
                ("impute", SimpleImputer(strategy="median")),
                ("scale", StandardScaler()),
            ]),
            numeric,
        ),
        (
            "cat",
            Pipeline([
                ("impute", SimpleImputer(strategy="most_frequent")),
                ("onehot", OneHotEncoder(handle_unknown="ignore")),
            ]),
            categorical,
        ),
    ]
)

model = Pipeline([
    ("preprocess", preprocess),
    ("clf", LogisticRegression(max_iter=1000, class_weight="balanced")),
])

model.fit(X_train, y_train)

Ba chi tiết quan trọng:

  • handle_unknown="ignore" để dữ liệu mới có giá trị phân loại chưa từng thấy không làm lỗi.
  • class_weight="balanced" giúp mô hình không bỏ qua lớp thiểu số.
  • SimpleImputer đặt trong pipeline, không chạy trước khi chia dữ liệu.

Vì sao thứ tự quan trọng

Sai:

code
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)          # fit trên toàn bộ dữ liệu
X_train, X_test = train_test_split(X_scaled, ...)

scaler đã học trung bình và độ lệch chuẩn của cả tập test. Thông tin test rò rỉ vào train, điểm đánh giá cao hơn thực tế.

Đúng:

code
model.fit(X_train, y_train)   # scaler chỉ thấy X_train
score = model.score(X_test, y_test)

Với dữ liệu lớn, chênh lệch có thể nhỏ và khó phát hiện — nhưng nguyên tắc vẫn phải giữ.

Cross-validation trên pipeline

code
from sklearn.model_selection import cross_val_score

scores = cross_val_score(
    model,
    X_train,
    y_train,
    cv=5,
    scoring="roc_auc",
)
print(scores.mean().round(3), scores.std().round(3))

Điểm quan trọng: truyền pipeline, không truyền dữ liệu đã tiền xử lý. Mỗi fold tự fit lại toàn bộ tiền xử lý trên phần train của fold đó.

Nếu tiền xử lý trước rồi mới cross-validate, mỗi fold đã thấy thông tin của fold khác.

Thiếu giá trị và giá trị ngoài miền

code
print(X.isna().mean().sort_values(ascending=False).head())

Nếu một cột thiếu hơn 40–50%, cân nhắc bỏ thay vì điền. Điền quá nhiều tạo ra tín hiệu giả.

Với cột số, median bền hơn mean khi có ngoại lai. Với cột phân loại, most_frequent là mặc định hợp lý; có thể thêm nhãn "missing" như một phạm trù riêng nếu việc thiếu dữ liệu mang thông tin.

Lưu và triển khai

code
import joblib

joblib.dump(model, "churn_pipeline.joblib")

loaded = joblib.load("churn_pipeline.joblib")
loaded.predict(new_customers)

Lưu cả pipeline, không lưu riêng mô hình. Nếu chỉ lưu mô hình, lúc dự đoán phải tự nhớ thứ tự và tham số tiền xử lý — gần như chắc chắn sẽ lệch.

Phiên bản thư viện cũng phải khớp khi tải lại. Ghi phiên bản scikit-learn vào tài liệu triển khai.

Kiểm tra pipeline trước khi báo cáo

  • Fit trên train, dự đoán trên validation: điểm phải hợp lý, không sát 1,0.
  • Kiểm tra model.named_steps để chắc các bước có mặt.
  • Đếm số đặc trưng sau ColumnTransformer bằng preprocess.get_feature_names_out().
  • Chạy một dòng dữ liệu mới chưa từng thấy để chắc không lỗi.

Lỗi thường gặp

  • Fit scaler trên toàn bộ dữ liệu: rò rỉ.
  • Điền thiếu trước khi chia: rò rỉ.
  • One-hot không có handle_unknown: lỗi khi gặp phạm trù mới.
  • Chỉ lưu mô hình, không lưu pipeline: dự đoán sai lệch.
  • Cross-validate dữ liệu đã tiền xử lý: điểm lạc quan.
  • Quên class_weight: mô hình bỏ qua lớp thiểu số.
  • Đổi thứ tự cột khi dự đoán: kết quả sai nhưng không báo lỗi.

Thực hành

  1. Dựng ColumnTransformer cho ba cột số và ba cột phân loại.
  2. Bọc trong Pipeline với logistic regression.
  3. Chạy cross_val_score với 5 fold.
  4. So sánh điểm khi tiền xử lý sai thứ tự.
  5. Lưu pipeline bằng joblib và dự đoán một dòng mới.

Lỗi thường gặp

Triệu chứngNguyên nhânCách sửa
Điểm CV cao hơn test nhiềuTiền xử lý ngoài pipelineĐưa vào pipeline
Lỗi khi dự đoán dữ liệu mớiPhạm trù chưa thấyhandle_unknown="ignore"
Kết quả khác sau khi tải lạiChỉ lưu mô hìnhLưu cả pipeline
Cột phân loại bị bỏ quaSai tên cộtKiểm tra get_feature_names_out
Mô hình đoán toàn lớp đa sốKhông cân bằng trọng sốclass_weight="balanced"

Tóm tắt

  • Pipeline giữ tiền xử lý và mô hình thành một khối.
  • ColumnTransformer xử lý cột số và phân loại riêng.
  • Fit mọi thứ trên train, không bao giờ trên toàn bộ dữ liệu.
  • Cross-validate trên pipeline, không trên dữ liệu đã xử lý.
  • Lưu cả pipeline khi triển khai.

Dùng ← → để chuyển bài