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)
⚡ 性能技巧¶
- Pipeline + ColumnTransformer — 避免手动预处理,防止数据泄露
- RandomizedSearchCV 代替 GridSearchCV — 速度提升 10-50 倍
n_jobs=-1— 利用所有 CPU 核心- 稀疏矩阵 — 文本数据用
OneHotEncoder(sparse_output=True)节省内存 warm_start=True— 增量训练(如在线学习场景)
⚠️ 常见陷阱¶
| 陷阱 | 后果 | 修复 |
|---|---|---|
| 在 split 前做标准化 | 数据泄露,测试集 AUC 虚高 | Pipeline 中做 |
| 对树模型做标准化 | 浪费时间(树模型不受尺度影响) | 只对线性模型/SVM/NN 做 |
random_state 不固定 |
结果不可复现 | 所有地方设 random_state=42 |
用 accuracy 衡量不平衡分类 |
99% 准确率但完全没用 | 用 AUC、F1、PR-AUC |