Skip to content

scikit-learn 实战指南

Python 机器学习的瑞士军刀——从数据预处理到模型部署


🧭 scikit-learn 设计哲学

scikit-learn 的核心是 一致的 API 设计:所有模型都遵循相同的接口模式。

from sklearn.xxx import SomeModel

model = SomeModel(hyperparam=value)  # 1. 初始化(设置超参数)
model.fit(X_train, y_train)          # 2. 训练(学习参数)
y_pred = model.predict(X_test)       # 3. 预测
score = model.score(X_test, y_test)  # 4. 评估

一旦学会一个模型,所有模型都会用


📦 Pipeline:所有东西串起来

from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.ensemble import RandomForestClassifier

# 数值特征预处理
numeric_transformer = Pipeline([
    ('imputer', SimpleImputer(strategy='median')),
    ('scaler', StandardScaler())
])

# 分类特征预处理
categorical_transformer = Pipeline([
    ('imputer', SimpleImputer(strategy='most_frequent')),
    ('onehot', OneHotEncoder(handle_unknown='ignore'))
])

# 组合预处理
preprocessor = ColumnTransformer([
    ('num', numeric_transformer, num_cols),
    ('cat', categorical_transformer, cat_cols)
])

# 完整 Pipeline
pipeline = Pipeline([
    ('preprocessor', preprocessor),
    ('classifier', RandomForestClassifier(n_estimators=100, random_state=42))
])

# 一键训练 + 预测
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)

Pipeline 的好处: - 不会数据泄露(transform 只在训练集上 fit) - 防止预处理和建模步骤不一致 - 可以整体做交叉验证和调参


🎯 分类模型全家桶

from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB

classifiers = {
    'Logistic Regression': LogisticRegression(max_iter=1000),
    'SVM': SVC(probability=True),
    'Decision Tree': DecisionTreeClassifier(max_depth=5),
    'Random Forest': RandomForestClassifier(n_estimators=100),
    'Gradient Boosting': GradientBoostingClassifier(n_estimators=100),
    'KNN': KNeighborsClassifier(n_neighbors=5),
    'Naive Bayes': GaussianNB(),
}

from sklearn.model_selection import cross_val_score

for name, clf in classifiers.items():
    scores = cross_val_score(clf, X_train_scaled, y_train, cv=5, scoring='accuracy')
    print(f"{name:25s}: {scores.mean():.4f} (+/- {scores.std()*2:.4f})")

分类报告 + 混淆矩阵

from sklearn.metrics import (
    classification_report, confusion_matrix,
    roc_auc_score, roc_curve, precision_recall_curve
)

y_pred = pipeline.predict(X_test)
y_proba = pipeline.predict_proba(X_test)[:, 1]

print(classification_report(y_test, y_pred))
print(f"AUC: {roc_auc_score(y_test, y_proba):.4f}")

# ROC 曲线
fpr, tpr, _ = roc_curve(y_test, y_proba)
plt.plot(fpr, tpr, label=f'AUC = {roc_auc_score(y_test, y_proba):.3f}')
plt.plot([0, 1], [0, 1], 'k--', alpha=0.3)
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curve')
plt.legend()

📈 回归模型全家桶

from sklearn.linear_model import LinearRegression, Ridge, Lasso, ElasticNet
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
from sklearn.svm import SVR

regressors = {
    'Linear': LinearRegression(),
    'Ridge': Ridge(alpha=1.0),
    'Lasso': Lasso(alpha=0.1),
    'ElasticNet': ElasticNet(alpha=0.1, l1_ratio=0.5),
    'Random Forest': RandomForestRegressor(n_estimators=100),
    'Gradient Boosting': GradientBoostingRegressor(n_estimators=100),
}

from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score

for name, reg in regressors.items():
    reg.fit(X_train_scaled, y_train)
    y_pred = reg.predict(X_test_scaled)
    rmse = np.sqrt(mean_squared_error(y_test, y_pred))
    r2 = r2_score(y_test, y_pred)
    print(f"{name:20s}: RMSE={rmse:.4f}, R²={r2:.4f}")

🔍 模型选择与调参

交叉验证

from sklearn.model_selection import (
    cross_val_score, KFold, StratifiedKFold
)

# 5 折交叉验证(分类问题用分层抽样)
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(pipeline, X_train, y_train,
                         cv=skf, scoring='roc_auc')
print(f"CV AUC: {scores.mean():.4f} ± {scores.std():.4f}")

网格搜索

from sklearn.model_selection import GridSearchCV

# 例:随机森林调参
param_grid = {
    'classifier__n_estimators': [50, 100, 200],
    'classifier__max_depth': [5, 10, 15, None],
    'classifier__min_samples_split': [2, 5, 10],
    'classifier__min_samples_leaf': [1, 2, 4],
}

grid = GridSearchCV(pipeline, param_grid, cv=5,
                    scoring='roc_auc', n_jobs=-1, verbose=1)
grid.fit(X_train, y_train)

print(f"最佳参数: {grid.best_params_}")
print(f"最佳分数: {grid.best_score_:.4f}")

随机搜索(更快,推荐)

from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint, uniform

param_dist = {
    'classifier__n_estimators': randint(50, 300),
    'classifier__max_depth': randint(3, 20),
    'classifier__min_samples_split': randint(2, 20),
    'classifier__min_samples_leaf': randint(1, 10),
}

random_search = RandomizedSearchCV(
    pipeline, param_dist, n_iter=50, cv=5,
    scoring='roc_auc', n_jobs=-1, random_state=42
)
random_search.fit(X_train, y_train)

🎛 常用预处理工具

from sklearn.preprocessing import (
    StandardScaler, MinMaxScaler, RobustScaler,
    LabelEncoder, OneHotEncoder, OrdinalEncoder,
    PolynomialFeatures, PowerTransformer, QuantileTransformer
)

# StandardScaler: (x - mean) / std — 适合大多数场景
# MinMaxScaler: 缩放到 [0, 1] — 适合神经网络
# RobustScaler: 用中位数和 IQR — 抗异常值
# PowerTransformer: Box-Cox / Yeo-Johnson — 使数据更接近正态
# QuantileTransformer: 映射到均匀/正态分布

缺失值处理

from sklearn.impute import SimpleImputer, KNNImputer

# 简单填充
SimpleImputer(strategy='mean')       # 均值(数值)
SimpleImputer(strategy='median')     # 中位数(抗异常值)
SimpleImputer(strategy='most_frequent')  # 众数(分类)
SimpleImputer(strategy='constant', fill_value=0)

# KNN 填充(考虑特征间关系,更精确但更慢)
KNNImputer(n_neighbors=5)

特征选择

from sklearn.feature_selection import (
    SelectKBest, f_classif, mutual_info_classif,
    RFE, SelectFromModel
)

# 1. 单变量筛选
selector = SelectKBest(mutual_info_classif, k=20)
X_selected = selector.fit_transform(X, y)

# 2. 递归特征消除(RFE)
from sklearn.ensemble import RandomForestClassifier
rfe = RFE(RandomForestClassifier(), n_features_to_select=20)
X_rfe = rfe.fit_transform(X, y)

# 3. 基于模型的重要性筛选
sfm = SelectFromModel(RandomForestClassifier(), threshold='median')
X_sfm = sfm.fit_transform(X, y)

🧪 处理不平衡数据

from sklearn.utils.class_weight import compute_class_weight
from imblearn.over_sampling import SMOTE
from imblearn.under_sampling import RandomUnderSampler
from imblearn.pipeline import Pipeline as ImbPipeline

# 方案 1: class_weight(不改变数据,修改损失函数)
class_weights = compute_class_weight(
    'balanced', classes=np.unique(y_train), y=y_train
)
weights_dict = dict(zip(np.unique(y_train), class_weights))

model = RandomForestClassifier(class_weight=weights_dict)

# 方案 2: SMOTE 过采样(合成少数类样本)
smote = SMOTE(random_state=42)
X_resampled, y_resampled = smote.fit_resample(X_train, y_train)

# 方案 3: Pipeline 中集成(推荐!)
imb_pipeline = ImbPipeline([
    ('smote', SMOTE(random_state=42)),
    ('classifier', RandomForestClassifier())
])

📊 聚类与降维

from sklearn.cluster import KMeans, DBSCAN, AgglomerativeClustering
from sklearn.decomposition import PCA, TruncatedSVD
from sklearn.manifold import TSNE

# K-Means
kmeans = KMeans(n_clusters=5, random_state=42)
clusters = kmeans.fit_predict(X_scaled)

# 如何选 K?——肘部法则
inertias = []
for k in range(1, 11):
    km = KMeans(n_clusters=k, random_state=42).fit(X_scaled)
    inertias.append(km.inertia_)
# 画 inertia vs k,找"肘部"

# DBSCAN(不需要指定 K)
dbscan = DBSCAN(eps=0.5, min_samples=5)
clusters = dbscan.fit_predict(X_scaled)

# PCA 降维
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled)
print(f"前 2 个主成分解释了 {pca.explained_variance_ratio_.sum():.1%} 的方差")

# t-SNE(可视化专用,不是降维)
tsne = TSNE(n_components=2, random_state=42)
X_tsne = tsne.fit_transform(X_scaled)

💾 模型持久化

import joblib

# 保存
joblib.dump(pipeline, 'model_pipeline.joblib')
joblib.dump(grid.best_estimator_, 'best_model.joblib')

# 加载
loaded_model = joblib.load('best_model.joblib')
y_pred = loaded_model.predict(X_new)

⚡ 性能技巧

  1. Pipeline + ColumnTransformer — 避免手动预处理,防止数据泄露
  2. RandomizedSearchCV 代替 GridSearchCV — 速度提升 10-50 倍
  3. n_jobs=-1 — 利用所有 CPU 核心
  4. 稀疏矩阵 — 文本数据用 OneHotEncoder(sparse_output=True) 节省内存
  5. warm_start=True — 增量训练(如在线学习场景)

⚠️ 常见陷阱

陷阱 后果 修复
在 split 前做标准化 数据泄露,测试集 AUC 虚高 Pipeline 中做
对树模型做标准化 浪费时间(树模型不受尺度影响) 只对线性模型/SVM/NN 做
random_state 不固定 结果不可复现 所有地方设 random_state=42
accuracy 衡量不平衡分类 99% 准确率但完全没用 用 AUC、F1、PR-AUC

🔗 相关资源