Làm chủ Scikit-learn Pipelines để hợp lý hóa quy trình máy học của bạn. Học cách tự động hóa tiền xử lý, huấn luyện mô hình và điều chỉnh siêu tham số.
Scikit-learn Pipeline: Hướng Dẫn Tối Ưu Để Tự Động Hóa Quy Trình ML
Trong thế giới máy học, việc xây dựng một mô hình thường được mô tả là bước cuối cùng đầy hào nhoáng. Tuy nhiên, các nhà khoa học dữ liệu và kỹ sư ML dày dạn kinh nghiệm biết rằng hành trình đến một mô hình mạnh mẽ được lát bằng một loạt các bước quan trọng, thường lặp đi lặp lại và dễ xảy ra lỗi: làm sạch dữ liệu, chia tỷ lệ đặc trưng, mã hóa các biến phân loại, v.v. Việc quản lý các bước này riêng lẻ cho các tập huấn luyện, xác thực và kiểm tra có thể nhanh chóng trở thành một cơn ác mộng về mặt hậu cần, dẫn đến các lỗi nhỏ và nguy hiểm nhất là rò rỉ dữ liệu.
Đây là lúc Pipeline của Scikit-learn xuất hiện để giải cứu. Nó không chỉ là một sự tiện lợi; nó là một công cụ cơ bản để xây dựng các hệ thống máy học chuyên nghiệp, có thể tái tạo và sẵn sàng sản xuất. Hướng dẫn toàn diện này sẽ hướng dẫn bạn mọi thứ bạn cần biết để làm chủ Scikit-learn Pipelines, từ các khái niệm cơ bản đến các kỹ thuật nâng cao.
Vấn đề: Quy trình làm việc máy học thủ công
Hãy xem xét một nhiệm vụ học có giám sát điển hình. Trước khi bạn có thể gọi model.fit(), bạn cần chuẩn bị dữ liệu của mình. Một quy trình làm việc tiêu chuẩn có thể trông như thế này:
- Chia dữ liệu: Chia tập dữ liệu của bạn thành các tập huấn luyện và kiểm tra. Đây là bước đầu tiên và quan trọng nhất để đảm bảo bạn có thể đánh giá hiệu suất mô hình của mình trên dữ liệu chưa thấy.
- Xử lý các giá trị bị thiếu: Xác định và điền vào dữ liệu bị thiếu trong tập huấn luyện của bạn (ví dụ: sử dụng giá trị trung bình, trung vị hoặc một hằng số).
- Mã hóa các đặc trưng phân loại: Chuyển đổi các cột không phải số như 'Quốc gia' hoặc 'Danh mục sản phẩm' thành định dạng số bằng các kỹ thuật như Mã hóa One-Hot hoặc Mã hóa thứ tự.
- Chia tỷ lệ các đặc trưng số: Đưa tất cả các đặc trưng số về một tỷ lệ tương tự bằng các phương pháp như Tiêu chuẩn hóa (
StandardScaler) hoặc Chuẩn hóa (MinMaxScaler). Điều này rất quan trọng đối với nhiều thuật toán như SVM, Hồi quy Logistic và Mạng nơ-ron. - Huấn luyện mô hình: Cuối cùng, khớp mô hình máy học bạn đã chọn trên dữ liệu huấn luyện đã được tiền xử lý.
Bây giờ, khi bạn muốn đưa ra dự đoán trên tập kiểm tra của mình (hoặc dữ liệu mới, chưa thấy), bạn phải lặp lại chính xác các bước tiền xử lý tương tự. Bạn phải áp dụng chiến lược điền khuyết giống nhau (sử dụng giá trị được tính từ tập huấn luyện), lược đồ mã hóa giống nhau và các tham số chia tỷ lệ giống nhau. Việc theo dõi thủ công tất cả các bộ biến đổi đã khớp này là tẻ nhạt và là một nguồn gây ra lỗi lớn.
Rủi ro lớn nhất ở đây là rò rỉ dữ liệu. Điều này xảy ra khi thông tin từ tập kiểm tra vô tình bị rò rỉ vào quá trình huấn luyện. Ví dụ: nếu bạn tính giá trị trung bình để điền khuyết hoặc các tham số chia tỷ lệ từ toàn bộ tập dữ liệu trước khi chia tách, mô hình của bạn đang ngầm học hỏi từ dữ liệu kiểm tra. Điều này dẫn đến ước tính hiệu suất quá lạc quan và một mô hình thất bại thảm hại trong thế giới thực.
Giới thiệu Scikit-learn Pipelines: Giải pháp tự động
Pipeline Scikit-learn là một đối tượng xâu chuỗi nhiều bước chuyển đổi dữ liệu và một ước tính cuối cùng (như bộ phân loại hoặc bộ hồi quy) thành một đối tượng thống nhất duy nhất. Bạn có thể coi nó như một dây chuyền lắp ráp cho dữ liệu của bạn.
Khi bạn gọi .fit() trên một Pipeline, nó sẽ tuần tự áp dụng fit_transform() cho từng bước trung gian trên dữ liệu huấn luyện, chuyển đầu ra của bước này làm đầu vào cho bước tiếp theo. Cuối cùng, nó gọi .fit() ở bước cuối cùng, công cụ ước tính. Khi bạn gọi .predict() hoặc .transform() trên Pipeline, nó chỉ áp dụng phương thức .transform() của từng bước trung gian cho dữ liệu mới trước khi đưa ra dự đoán với công cụ ước tính cuối cùng.
Các lợi ích chính của việc sử dụng Pipelines
- Ngăn ngừa rò rỉ dữ liệu: Đây là lợi ích quan trọng nhất. Bằng cách đóng gói tất cả các bước tiền xử lý trong pipeline, bạn đảm bảo rằng các chuyển đổi chỉ được học từ dữ liệu huấn luyện trong quá trình xác thực chéo và được áp dụng chính xác cho dữ liệu xác thực/kiểm tra.
- Đơn giản và có tổ chức: Toàn bộ quy trình làm việc của bạn, từ dữ liệu thô đến mô hình đã huấn luyện, được cô đọng thành một đối tượng duy nhất. Điều này làm cho mã của bạn sạch hơn, dễ đọc hơn và dễ quản lý hơn.
- Khả năng tái tạo: Một đối tượng Pipeline đóng gói toàn bộ quá trình mô hình hóa của bạn. Bạn có thể dễ dàng lưu đối tượng duy nhất này (ví dụ: sử dụng `joblib` hoặc `pickle`) và tải nó sau để đưa ra dự đoán, đảm bảo rằng các bước chính xác giống nhau được tuân theo mỗi lần.
- Hiệu quả trong Grid Search: Bạn có thể thực hiện điều chỉnh siêu tham số trên toàn bộ pipeline cùng một lúc, tìm các tham số tốt nhất cho cả các bước tiền xử lý và mô hình cuối cùng cùng một lúc. Chúng ta sẽ khám phá tính năng mạnh mẽ này sau.
Xây dựng Pipeline đơn giản đầu tiên của bạn
Hãy bắt đầu với một ví dụ cơ bản. Hãy tưởng tượng chúng ta có một tập dữ liệu số và chúng ta muốn chia tỷ lệ dữ liệu trước khi huấn luyện mô hình Hồi quy Logistic. Đây là cách bạn xây dựng một pipeline cho việc đó.
Đầu tiên, hãy thiết lập môi trường của chúng ta và tạo một số dữ liệu mẫu.
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.metrics import accuracy_score
# Generate some sample data
X, y = np.random.rand(100, 5) * 10, (np.random.rand(100) > 0.5).astype(int)
# Split data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
Bây giờ, hãy xác định pipeline của chúng ta. Một pipeline được tạo bằng cách cung cấp một danh sách các bước. Mỗi bước là một bộ chứa một tên (một chuỗi bạn chọn) và chính đối tượng biến đổi hoặc ước tính.
# Create the pipeline steps
steps = [
('scaler', StandardScaler()),
('classifier', LogisticRegression())
]
# Create the Pipeline object
pipe = Pipeline(steps)
# Now, you can treat the 'pipe' object as if it were a regular model.
# Let's train it on our training data.
pipe.fit(X_train, y_train)
# Make predictions on the test data
y_pred = pipe.predict(X_test)
# Evaluate the model
accuracy = accuracy_score(y_test, y_pred)
print(f"Pipeline Accuracy: {accuracy:.4f}")
Vậy là xong! Chỉ trong một vài dòng, chúng ta đã kết hợp chia tỷ lệ và phân loại. Scikit-learn xử lý tất cả logic trung gian. Khi pipe.fit(X_train, y_train) được gọi, nó trước tiên gọi StandardScaler().fit_transform(X_train) và sau đó chuyển kết quả đến LogisticRegression().fit(). Khi pipe.predict(X_test) được gọi, nó áp dụng bộ chia tỷ lệ đã được khớp bằng StandardScaler().transform(X_test) trước khi đưa ra dự đoán với mô hình hồi quy logistic.
Xử lý dữ liệu không đồng nhất: `ColumnTransformer`
Các tập dữ liệu thực tế hiếm khi đơn giản. Chúng thường chứa sự kết hợp của các loại dữ liệu: các cột số cần chia tỷ lệ, các cột phân loại cần mã hóa và có thể là các cột văn bản cần vector hóa. Một pipeline tuần tự đơn giản là không đủ cho việc này, vì bạn cần áp dụng các chuyển đổi khác nhau cho các cột khác nhau.
Đây là nơi ColumnTransformer tỏa sáng. Nó cho phép bạn áp dụng các bộ biến đổi khác nhau cho các tập hợp con cột khác nhau trong dữ liệu của bạn và sau đó kết hợp các kết quả một cách thông minh. Nó là công cụ hoàn hảo để sử dụng làm bước tiền xử lý trong một pipeline lớn hơn.
Ví dụ: Kết hợp các đặc trưng số và phân loại
Hãy tạo một tập dữ liệu thực tế hơn với cả các đặc trưng số và phân loại bằng pandas.
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder
from sklearn.impute import SimpleImputer
# Create a sample DataFrame
data = {
'age': [25, 30, 45, 35, 50, np.nan, 22],
'salary': [50000, 60000, 120000, 80000, 150000, 75000, 45000],
'country': ['USA', 'Canada', 'USA', 'UK', 'Canada', 'USA', 'UK'],
'purchased': [0, 1, 1, 0, 1, 1, 0]
}
df = pd.DataFrame(data)
X = df.drop('purchased', axis=1)
y = df['purchased']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Identify numerical and categorical columns
numerical_features = ['age', 'salary']
categorical_features = ['country']
Chiến lược tiền xử lý của chúng ta sẽ là:
- Đối với các cột số (
age,salary): Điền các giá trị bị thiếu bằng giá trị trung vị, sau đó chia tỷ lệ chúng. - Đối với các cột phân loại (
country): Điền các giá trị bị thiếu bằng danh mục thường xuyên nhất, sau đó mã hóa one-hot chúng.
Chúng ta có thể xác định các bước này bằng cách sử dụng hai mini-pipelines riêng biệt.
# Create a pipeline for numerical features
numeric_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler())
])
# Create a pipeline for categorical features
categorical_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='most_frequent')),
('onehot', OneHotEncoder(handle_unknown='ignore'))
])
Bây giờ, chúng ta sử dụng `ColumnTransformer` để áp dụng các pipelines này cho các cột chính xác.
# Create the preprocessor with ColumnTransformer
preprocessor = ColumnTransformer(
transformers=[
('num', numeric_transformer, numerical_features),
('cat', categorical_transformer, categorical_features)
])
`ColumnTransformer` lấy một danh sách các `transformers`. Mỗi transformer là một bộ chứa một tên, đối tượng biến đổi (có thể là một pipeline), và danh sách tên cột để áp dụng nó cho.
Cuối cùng, chúng ta có thể đặt `preprocessor` này làm bước đầu tiên trong pipeline chính của chúng ta, tiếp theo là công cụ ước tính cuối cùng của chúng ta.
from sklearn.ensemble import RandomForestClassifier
# Create the full pipeline
full_pipeline = Pipeline(steps=[
('preprocessor', preprocessor),
('classifier', RandomForestClassifier(random_state=42))
])
# Train and evaluate the full pipeline
full_pipeline.fit(X_train, y_train)
print("Model score on test data:", full_pipeline.score(X_test, y_test))
# You can now make predictions on new raw data
new_data = pd.DataFrame({
'age': [40, 28],
'salary': [90000, 55000],
'country': ['USA', 'Germany'] # 'Germany' is an unknown category
})
predictions = full_pipeline.predict(new_data)
print("Predictions for new data:", predictions)
Lưu ý cách nó xử lý một quy trình làm việc phức tạp một cách thanh lịch. Tham số `handle_unknown='ignore'` trong `OneHotEncoder` đặc biệt hữu ích cho các hệ thống sản xuất, vì nó ngăn ngừa lỗi khi các danh mục mới, chưa thấy xuất hiện trong dữ liệu.
Các kỹ thuật Pipeline nâng cao
Pipelines cung cấp thậm chí nhiều sức mạnh và tính linh hoạt hơn. Hãy khám phá một số tính năng nâng cao cần thiết cho các dự án máy học chuyên nghiệp.
Tạo các bộ biến đổi tùy chỉnh
Đôi khi, các bộ biến đổi Scikit-learn tích hợp sẵn là không đủ. Bạn có thể cần thực hiện một chuyển đổi dành riêng cho miền, như trích xuất logarit của một đặc trưng hoặc kết hợp hai đặc trưng thành một đặc trưng mới. Bạn có thể dễ dàng tạo các bộ biến đổi tùy chỉnh của riêng bạn tích hợp liền mạch vào một pipeline.
Để làm điều này, bạn tạo một lớp kế thừa từ `BaseEstimator` và `TransformerMixin`. Bạn chỉ cần triển khai các phương thức `fit()` và `transform()` (và `__init__()` nếu cần).
Hãy tạo một bộ biến đổi thêm một đặc trưng mới: tỷ lệ giữa `salary` và `age`.
from sklearn.base import BaseEstimator, TransformerMixin
# Define column indices (can also pass names)
age_ix, salary_ix = 0, 1
class FeatureRatioAdder(BaseEstimator, TransformerMixin):
def __init__(self):
pass # No parameters to set
def fit(self, X, y=None):
return self # Nothing to learn during fit, so just return self
def transform(self, X):
salary_age_ratio = X[:, salary_ix] / X[:, age_ix]
return np.c_[X, salary_age_ratio] # Concatenate original X with new feature
Sau đó, bạn có thể chèn bộ biến đổi tùy chỉnh này vào pipeline xử lý số của bạn:
numeric_transformer_with_custom = Pipeline(steps=[
('imputer', SimpleImputer(strategy='median')),
('ratio_adder', FeatureRatioAdder()), # Our custom transformer
('scaler', StandardScaler())
])
Mức độ tùy chỉnh này cho phép bạn đóng gói tất cả logic kỹ thuật đặc trưng của bạn trong pipeline, làm cho quy trình làm việc của bạn cực kỳ di động và có thể tái tạo.
Điều chỉnh siêu tham số với Pipelines bằng `GridSearchCV`
Đây được cho là một trong những ứng dụng mạnh mẽ nhất của Pipelines. Bạn có thể tìm kiếm các siêu tham số tốt nhất cho toàn bộ quy trình làm việc của bạn, bao gồm các bước tiền xử lý và mô hình cuối cùng, tất cả cùng một lúc.
Để chỉ định tham số nào cần điều chỉnh, bạn sử dụng một cú pháp đặc biệt: `step_name__parameter_name`.
Hãy mở rộng ví dụ trước của chúng ta và điều chỉnh các siêu tham số cho cả imputer trong bộ tiền xử lý của chúng ta và `RandomForestClassifier`.
from sklearn.model_selection import GridSearchCV
# We use the 'full_pipeline' from the ColumnTransformer example
# Define the parameter grid
param_grid = {
'preprocessor__num__imputer__strategy': ['mean', 'median'],
'classifier__n_estimators': [50, 100, 200],
'classifier__max_depth': [None, 10, 20],
'classifier__min_samples_leaf': [1, 2, 4]
}
# Create the GridSearchCV object
grid_search = GridSearchCV(full_pipeline, param_grid, cv=5, verbose=1, n_jobs=-1)
# Fit it to the data
grid_search.fit(X_train, y_train)
# Print the best parameters and score
print("Best parameters found: ", grid_search.best_params_)
print("Best cross-validation score: ", grid_search.best_score_)
# The best estimator is already refitted on the whole training data
best_model = grid_search.best_estimator_
print("Test set score with best model: ", best_model.score(X_test, y_test))
Hãy xem xét kỹ các khóa trong `param_grid`:
'preprocessor__num__imputer__strategy': Điều này nhắm mục tiêu đến tham số `strategy` của bước `SimpleImputer` có tên `imputer` bên trong pipeline số có tên `num`, bản thân nó nằm bên trong `ColumnTransformer` có tên `preprocessor`.'classifier__n_estimators': Điều này nhắm mục tiêu đến tham số `n_estimators` của công cụ ước tính cuối cùng có tên `classifier`.
Bằng cách làm điều này, `GridSearchCV` thử chính xác tất cả các tổ hợp và tìm thấy tập hợp tham số tối ưu cho toàn bộ quy trình làm việc, ngăn chặn hoàn toàn rò rỉ dữ liệu trong quá trình điều chỉnh vì tất cả các bước tiền xử lý được thực hiện bên trong mỗi fold xác thực chéo.
Trực quan hóa và kiểm tra pipeline của bạn
Các pipeline phức tạp có thể trở nên khó lý giải. Scikit-learn cung cấp một cách tuyệt vời để trực quan hóa chúng. Bắt đầu từ phiên bản 0.23, bạn có thể nhận được một biểu diễn HTML tương tác.
from sklearn import set_config
# Set display to 'diagram' to get the visual representation
set_config(display='diagram')
# Now, simply displaying the pipeline object in a Jupyter Notebook or similar environment will render it
full_pipeline
Điều này sẽ tạo ra một sơ đồ hiển thị luồng dữ liệu qua từng bộ biến đổi và công cụ ước tính, cùng với tên của chúng. Điều này cực kỳ hữu ích cho việc gỡ lỗi, chia sẻ công việc của bạn và hiểu cấu trúc của mô hình của bạn.
Bạn cũng có thể truy cập các bước riêng lẻ của một pipeline đã khớp bằng cách sử dụng tên của chúng:
# Access the final classifier of the fitted pipeline
final_classifier = full_pipeline.named_steps['classifier']
print("Feature importances:", final_classifier.feature_importances_)
# Access the OneHotEncoder to see the learned categories
onehot_encoder = full_pipeline.named_steps['preprocessor'].named_transformers_['cat'].named_steps['onehot']
print("Categorical features learned:", onehot_encoder.categories_)
Các cạm bẫy phổ biến và các phương pháp hay nhất
- Khớp trên dữ liệu sai: Luôn luôn, luôn khớp pipeline của bạn CHỈ trên dữ liệu huấn luyện. Không bao giờ khớp nó trên toàn bộ tập dữ liệu hoặc tập kiểm tra. Đây là quy tắc cơ bản để ngăn ngừa rò rỉ dữ liệu.
- Định dạng dữ liệu: Hãy chú ý đến định dạng dữ liệu mà mỗi bước mong đợi. Một số bộ biến đổi (như những bộ biến đổi trong ví dụ tùy chỉnh của chúng ta) có thể hoạt động với mảng NumPy, trong khi những bộ biến đổi khác thuận tiện hơn với Pandas DataFrames. Scikit-learn thường giỏi xử lý việc này, nhưng đó là điều cần lưu ý, đặc biệt là với các bộ biến đổi tùy chỉnh.
- Lưu và tải Pipelines: Để triển khai mô hình của bạn, bạn sẽ cần lưu pipeline đã khớp. Cách tiêu chuẩn để làm điều này trong hệ sinh thái Python là với `joblib` hoặc `pickle`. `joblib` thường hiệu quả hơn cho các đối tượng mang mảng NumPy lớn.
import joblib # Save the pipeline joblib.dump(full_pipeline, 'my_model_pipeline.joblib') # Load the pipeline later loaded_pipeline = joblib.load('my_model_pipeline.joblib') # Make predictions with the loaded model loaded_pipeline.predict(new_data) - Sử dụng tên mô tả: Đặt tên rõ ràng, mô tả cho các bước pipeline của bạn và các thành phần `ColumnTransformer` (ví dụ: 'numeric_imputer', 'categorical_encoder', 'svm_classifier'). Điều này làm cho mã của bạn dễ đọc hơn và đơn giản hóa việc điều chỉnh siêu tham số và gỡ lỗi.
Kết luận: Tại sao Pipelines là không thể thương lượng đối với ML chuyên nghiệp
Scikit-learn Pipelines không chỉ là một công cụ để viết mã gọn gàng hơn; chúng đại diện cho một sự thay đổi mô hình từ việc viết script thủ công, dễ xảy ra lỗi sang một cách tiếp cận có hệ thống, mạnh mẽ và có thể tái tạo đối với máy học. Chúng là xương sống của các phương pháp kỹ thuật ML âm thanh.
Bằng cách áp dụng pipelines, bạn đạt được:
- Độ mạnh mẽ: Bạn loại bỏ nguồn gây ra lỗi phổ biến nhất trong các dự án máy học — rò rỉ dữ liệu.
- Hiệu quả: Bạn hợp lý hóa toàn bộ quy trình làm việc của mình, từ kỹ thuật đặc trưng đến điều chỉnh siêu tham số, thành một đơn vị gắn kết duy nhất.
- Khả năng tái tạo: Bạn tạo một đối tượng tuần tự hóa duy nhất chứa toàn bộ logic mô hình của bạn, giúp bạn dễ dàng triển khai và chia sẻ.
Nếu bạn nghiêm túc về việc xây dựng các mô hình máy học hoạt động đáng tin cậy trong thế giới thực, thì việc làm chủ Scikit-learn Pipelines là không bắt buộc — nó là điều cần thiết. Bắt đầu kết hợp chúng vào các dự án của bạn ngay hôm nay và bạn sẽ xây dựng các mô hình tốt hơn, đáng tin cậy hơn nhanh hơn bao giờ hết.