Scikit-learn 机器学习:从预处理到模型评估
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 的 ColumnTransformer 和 Pipeline 可以帮助我们将预处理步骤优雅地组合起来。
3.1 特征选择与缺失值处理
我们选择以下特征:pclass, sex, age, sibsp, parch, fare, embarked。目标列为 survived。
features = ['pclass', 'sex', 'age', 'sibsp', 'parch', 'fare', 'embarked']
X = titanic[features]
y = titanic['survived']
数据集中 age、embarked 和 fare 存在缺失值。我们需要为数值和类别变量分别制定填补策略:
- 数值列:用中位数填充
- 类别列:用众数(最常见值)填充
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. 保存与复用模型
训练好的模型可以通过 joblib 或 pickle 保存,以便在生产环境中部署。
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. 扩展练习
尝试在你的数据集上实践以下任务:
- 引入不同的分类器(SVC、KNN、GradientBoosting)并对比性能。
- 使用
SelectFromModel或递归特征消除进行特征选择。 - 处理更真实的缺失值场景,例如缺失值超过 30% 的列。
- 部署一个简单的 Flask API 服务,调用保存的模型。
Scikit-learn 的模块化和一致性设计让从实验到部署的路径变得极为高效。掌握上述流程,你就已经具备了解决大多数结构化数据机器学习问题的能力。