Scikit-learn 机器学习:从预处理到模型评估

FreeGuideOnline 最新 2026-06-12

Scikit-learn 机器学习实践:从数据预处理到模型评估

Scikit-learn 是 Python 生态中最受欢迎的机器学习库之一,它提供了一致且高效的接口,覆盖从数据预处理、特征工程、模型训练到性能评估的全流程。本教程将带你使用 Scikit-learn 完整走通一个监督学习项目,即使你刚开始接触机器学习也能轻松跟上。

1. 环境准备与基础概念

在开始之前,请确保你的 Python 环境已安装必要依赖:

pip install scikit-learn pandas matplotlib seaborn

导入常用模块:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split, cross_val_score, GridSearchCV
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix

一个典型的机器学习工作流包含:加载数据 → 探索性分析 → 数据清洗与预处理 → 划分训练集/测试集 → 选择模型 → 训练 → 评估 → 调参与部署。


2. 加载与理解数据

我们以经典的泰坦尼克数据集(Titanic)为例,目标是预测乘客是否幸存(二分类问题)。

# 直接从 seaborn 加载数据集
titanic = sns.load_dataset('titanic')
print(titanic.head())
print(titanic.info())

查看前几行数据,我们能看到包含数值型特征(age, fare)和类别型特征(sex, embarked),还有一些无关特征(deck, embark_town 等),以及缺失值。

快速探索:

# 统计缺失值
print(titanic.isnull().sum())

# 目标变量分布
sns.countplot(x='survived', data=titanic)
plt.title('Survival Count')
plt.show()

3. 数据预处理与特征工程

Scikit-learn 的 ColumnTransformerPipeline 可以帮助我们将预处理步骤优雅地组合起来。

3.1 特征选择与缺失值处理

我们选择以下特征:pclass, sex, age, sibsp, parch, fare, embarked。目标列为 survived

features = ['pclass', 'sex', 'age', 'sibsp', 'parch', 'fare', 'embarked']
X = titanic[features]
y = titanic['survived']

数据集中 ageembarkedfare 存在缺失值。我们需要为数值和类别变量分别制定填补策略:

  • 数值列:用中位数填充
  • 类别列:用众数(最常见值)填充

3.2 定义数值与类别特征的预处理管道

# 数值特征
numeric_features = ['age', 'fare', 'sibsp', 'parch']
# 类别特征
categorical_features = ['pclass', 'sex', 'embarked']

# 数值管道:填补缺失值 → 标准化
numeric_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='median')),
    ('scaler', StandardScaler())
])

# 类别管道:填补缺失值 → 独热编码
categorical_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='most_frequent')),
    ('onehot', OneHotEncoder(handle_unknown='ignore'))
])

使用 ColumnTransformer 将两个管道组合:

preprocessor = ColumnTransformer(
    transformers=[
        ('num', numeric_transformer, numeric_features),
        ('cat', categorical_transformer, categorical_features)
    ])

3.3 构建完整的机器学习管道

将预处理与最终的分类器串联成一个整体管道,避免数据泄露并简化代码。这里我们先选用逻辑回归作为基线模型。

from sklearn.linear_model import LogisticRegression

pipeline = Pipeline(steps=[
    ('preprocessor', preprocessor),
    ('classifier', LogisticRegression(max_iter=1000))
])

4. 训练模型与调参

4.1 划分训练集和测试集

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

stratify=y 保证训练和测试集中类别比例与原数据一致,这对不平衡分类非常重要。

4.2 训练基线模型

pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)
print("Baseline Accuracy: {:.2f}".format(accuracy_score(y_test, y_pred)))

基线准确率大约在 80% 左右(因随机种子和数据版本可能浮动)。但这仅仅是起点,我们可以通过交叉验证和超参数调优提升性能。

4.3 使用交叉验证评估模型稳定性

cv_scores = cross_val_score(pipeline, X_train, y_train, cv=5, scoring='accuracy')
print("Cross-validation accuracy: {:.2f} (+/- {:.2f})".format(
    np.mean(cv_scores), np.std(cv_scores)))

4.4 超参数调优:网格搜索

我们尝试使用随机森林分类器,并对其超参数进行搜索。

from sklearn.ensemble import RandomForestClassifier

# 更新管道中的分类器
pipeline_rf = Pipeline(steps=[
    ('preprocessor', preprocessor),
    ('classifier', RandomForestClassifier(random_state=42))
])

param_grid = {
    'classifier__n_estimators': [50, 100, 200],
    'classifier__max_depth': [None, 5, 10],
    'classifier__min_samples_split': [2, 5]
}

grid_search = GridSearchCV(pipeline_rf, param_grid, cv=5,
                           scoring='accuracy', n_jobs=-1, verbose=1)
grid_search.fit(X_train, y_train)

print("Best parameters:", grid_search.best_params_)
print("Best CV accuracy: {:.2f}".format(grid_search.best_score_))

5. 模型评估与解释

调优完成后,我们在测试集上评估最终模型。

best_model = grid_search.best_estimator_
y_pred_final = best_model.predict(X_test)

print("Test Accuracy: {:.2f}".format(accuracy_score(y_test, y_pred_final)))
print("\nClassification Report:\n", classification_report(y_test, y_pred_final))

混淆矩阵可视化:

cm = confusion_matrix(y_test, y_pred_final)
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues')
plt.xlabel('Predicted')
plt.ylabel('Actual')
plt.title('Confusion Matrix')
plt.show()

评估要点:

  • 准确率 (Accuracy):整体正确率。
  • 精确率 (Precision) 与召回率 (Recall):关注正类的表现,尤其当数据不平衡时。
  • F1-score:精确率与召回率的调和平均。

若你需要解释模型决策,可使用 Scikit-learn 兼容的 SHAP 或内置的 feature_importances_ 查看随机森林的特征重要性。


6. 保存与复用模型

训练好的模型可以通过 joblibpickle 保存,以便在生产环境中部署。

import joblib

# 保存整个管道(包含预处理)
joblib.dump(best_model, 'titanic_model.pkl')

# 后续加载并直接预测
loaded_model = joblib.load('titanic_model.pkl')
new_data = X_test.iloc[:5]          # 假设这是新数据
predictions = loaded_model.predict(new_data)
print("Predictions:", predictions)

7. 常见问题与最佳实践

  • 数据泄露:任何预处理(缩放、填充、编码)都应在划分训练集后基于训练集进行 .fit(),再 .transform() 测试集。使用 Pipeline 可完全避免此问题。
  • 类别不平衡:当正负样本比例悬殊时,考虑使用 class_weight='balanced' 或采样方法(如 SMOTE,但需注意它与交叉验证的集成)。
  • 特征编码:对无序类别使用 OneHotEncoder,对有序类别(如学历)可考虑 OrdinalEncoder。树模型通常可以直接处理数值化的类别编码,但独热编码更加通用。
  • 数值缩放:逻辑回归、SVM、KNN 等基于距离或梯度的模型对特征尺度敏感,必须标准化;决策树和随机森林则不需要。
  • 模型选择路线:先建立一个简单基线(如逻辑回归、决策树),再尝试集成方法(随机森林、梯度提升),最后考虑深度学习。

8. 扩展练习

尝试在你的数据集上实践以下任务:

  1. 引入不同的分类器(SVC、KNN、GradientBoosting)并对比性能。
  2. 使用 SelectFromModel 或递归特征消除进行特征选择。
  3. 处理更真实的缺失值场景,例如缺失值超过 30% 的列。
  4. 部署一个简单的 Flask API 服务,调用保存的模型。

Scikit-learn 的模块化和一致性设计让从实验到部署的路径变得极为高效。掌握上述流程,你就已经具备了解决大多数结构化数据机器学习问题的能力。