
Scikit-learn
用 Scikit-learn 完成一个端到端的分类建模流程
从读取 CSV 到保存模型,走一遍 Scikit-learn 的完整分类流程:拆分特征与标签、划分训练集与测试集、选择估计器、拟合与预测、评估指标、调参改进,以及用 joblib 持久化模型。适合刚接触 sklearn 的读者按步骤动手。
Scikit-learn(导入时写作 sklearn)是一个开源的 Python 机器学习库,建立在 NumPy 与 Matplotlib 之上。它把数据准备、模型训练、预测、评估这些环节都封装成了统一的 Python 接口,因此你不需要从零实现算法,也能把一条完整的建模流程跑通。这篇教程面向刚接触 sklearn 的读者,用一个心脏病分类数据集,把「读数据 → 拆特征与标签 → 划分训练/测试集 → 选模型 → 拟合 → 预测 → 评估 → 调参 → 保存模型」这条链路从头走一遍。文中涉及的版本号、默认参数与接口行为可能随版本变化,请以官网当前信息为准;如果你还想对照其他同类工具的做法,可以参考 Scikit-learn 的工具页。
准备工作
开始之前,需要确认几件事。
- Python 环境:需要一个可用的 Python 解释器,以及能安装第三方包的环境(虚拟环境或 conda 环境均可)。
- 依赖库:
scikit-learn、numpy、pandas、matplotlib。前两个是建模必需,pandas 用来读 CSV 与做表格操作,matplotlib 用来画图。 - 数据文件:本文示例使用一个名为
heart-disease.csv的表格文件,每行是一位匿名患者的记录,列是各项体征指标,其中target列表示该患者是否有心脏病(1表示有,0表示没有)。文件放在脚本上一级目录的data文件夹里,路径按你自己的实际位置调整。 - 交互环境:用 Jupyter Notebook 或 IPython 会更方便,因为可以逐步查看每个变量的输出。较新版本的 Jupyter 已不再需要
%matplotlib inline这类魔法命令。
安装依赖可以这样执行:
pip install scikit-learn numpy pandas matplotlib
安装完成后,先导入库并打印版本,确认环境可用:
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import sklearn
print(f"Using Scikit-Learn version: {sklearn.__version__}")
输出会形如 Using Scikit-Learn version: 1.3.1。示例代码在 1.3.1 上验证过,低于该版本时部分接口可能不存在,建议升级到不低于该版本。具体版本支持情况以官网当前信息为准。
操作步骤
第一步:把数据读进来并拆成特征与标签
用 pandas 读取 CSV,先看一眼前几行,确认列名与数据形态:
heart_disease = pd.read_csv('../data/heart-disease.csv')
heart_disease.head()
在这个表里,除 target 之外的列都是患者的特征(features),target 是要预测的标签(label)。因为标签只有「有」和「没有」两种取值,所以这是一个分类问题。
按照惯例,特征存到变量 X,标签存到变量 y:
# 特征:去掉 target 列
X = heart_disease.drop("target", axis=1)
# 标签:只保留 target 列
y = heart_disease["target"]
X.head()
y.head(), y.value_counts()
y.value_counts() 会给出两类的样本数量,例如 1 类 165 条、0 类 138 条。先看类别分布很重要,如果两类数量差距悬殊,后面的评估指标选择就要相应调整。
第二步:划分训练集与测试集
机器学习里最基本的一条纪律是:模型在训练集上学习规律,在测试集上检验学到的规律,训练过程中模型绝不能看到测试数据。这就像学生用学期中的材料复习(训练集),再用期末考试检验水平(测试集)。
sklearn 提供了 train_test_split 来做这件事:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25)
X_train.shape, X_test.shape, y_train.shape, y_test.shape
常见的划分比例有 80/20、70/30、75/25。test_size=0.25 表示留出 25% 做测试集。示例数据共 303 条,划分后得到 ((227, 13), (76, 13), (227,), (76,)),即 227 条训练、76 条测试,特征 13 列。
除了训练集和测试集,实践中还会再切出一个验证集(例如 70/15/15),专门用于调超参数。本文先聚焦训练集与测试集两部分,验证集的做法在后面的调参环节再展开。
第三步:选择模型并查看超参数
选什么模型取决于问题类型。分类问题与回归问题各有对应的推荐算法,sklearn 文档里有一张按问题类型推荐模型的图可以对照。在 sklearn 的术语里,模型被称为「估计器」(estimator),也常被简称为 clf(classifier 的缩写)。
模型的超参数是你可以调整的设置项,类似烤箱上的旋钮——调一调,做出来的菜不一样。这里是一个分类问题,先用随机森林分类器:
from sklearn.ensemble import RandomForestClassifier
clf = RandomForestClassifier()
clf.get_params()
get_params() 会返回当前全部超参数,例如:
{'bootstrap': True,
'ccp_alpha': 0.0,
'class_weight': None,
'criterion': 'gini',
'max_depth': None,
'max_features': 'sqrt',
'max_leaf_nodes': None,
'max_samples': None,
'min_impurity_decrease': 0.0,
'min_samples_leaf': 1,
'min_samples_split': 2,
'min_weight_fraction_leaf': 0.0,
'n_estimators': 100,
'n_jobs': None,
'oob_score': False,
'random_state': None,
'verbose': 0,
'warm_start': False}
sklearn 的模型默认设置通常已经比较合理,第一轮可以先原样使用,等评估完再决定调哪些。
第四步:拟合模型
拟合(fit)就是把数据交给模型,让它去找规律。有标签时(监督学习),模型学习特征与标签之间的关系;没有标签时(无监督学习),模型尝试把相似的样本聚在一起。
绝大多数 sklearn 模型都有 fit(X, y) 方法,X 是特征,y 是标签。注意这里要用训练集:
clf.fit(X=X_train, y=y_train)
第五步:用模型做预测
训练模型的目的是对未来做预测。模型拟合好之后,调用 predict() 传入一组特征,就能得到预测的标签:
y_preds = clf.predict(X_test)
这里有一个容易踩的坑:预测时输入的数据,形状和格式必须与训练时一致。比如下面这种写法就会报错,因为传入的是一维数组,而模型期待的是二维的特征矩阵:
y_label = clf.predict(np.array([0, 2, 3, 4]))
报错信息类似 UserWarning: X does not have valid feature names, but RandomForestClassifier ...,并伴随形状不匹配的错误。正确做法是传入与 X_train 列结构相同的二维数据,例如 X_test 本身,或者用 X_test.iloc[:1] 取单行。
第六步:评估模型
预测出来之后要判断模型到底学到了多少。分类问题常用的评估指标有准确率(accuracy)、精确率(precision)、召回率(recall)和 F1 分数。sklearn 在 sklearn.metrics 下提供了对应函数:
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
print("Accuracy:", accuracy_score(y_test, y_preds))
print("Precision:", precision_score(y_test, y_preds))
print("Recall:", recall_score(y_test, y_preds))
print("F1:", f1_score(y_test, y_preds))
也可以一次性拿到完整报告:
from sklearn.metrics import classification_report, confusion_matrix
print(classification_report(y_test, y_preds))
print(confusion_matrix(y_test, y_preds))
classification_report 会按类别列出 precision、recall、f1-score 与 support,并给出宏平均与加权平均;confusion_matrix 则给出真阳性、假阳性、真阴性、假阴性的分布。只看准确率有时会误导——尤其在类别不平衡时,混淆矩阵和分类报告能告诉你模型错在哪一类上。
如果想看模型对每个样本属于正类的把握程度,可以用 predict_proba():
y_probs = clf.predict_proba(X_test)
y_probs[:5]
返回的是每行两个概率值,分别对应类别 0 和类别 1。基于概率还可以画 ROC 曲线、计算 AUC:
from sklearn.metrics import roc_curve, roc_auc_score
y_probs_positive = y_probs[:, 1]
fpr, tpr, thresholds = roc_curve(y_test, y_probs_positive)
print("AUC:", roc_auc_score(y_test, y_probs_positive))
第七步:调参改进模型
第一轮结果只是基线。要提升效果,可以从两个方向入手:换超参数,或者换模型。超参数搜索的常用工具是网格搜索与随机搜索。
网格搜索会穷举给定的参数组合,并用交叉验证评估每一组:
from sklearn.model_selection import GridSearchCV
param_grid = {
"n_estimators": [10, 100, 200, 500],
"max_depth": [None, 10, 20, 30],
"min_samples_split": [2, 4, 6],
"min_samples_leaf": [1, 2, 4],
}
grid = GridSearchCV(
estimator=RandomForestClassifier(),
param_grid=param_grid,
cv=5,
scoring="accuracy",
verbose=2,
)
grid.fit(X_train, y_train)
grid.best_params_, grid.best_score_
随机搜索则在参数空间中随机采样固定次数,参数范围大时通常更快:
from sklearn.model_selection import RandomizedSearchCV
random_grid = {
"n_estimators": [10, 100, 200, 500, 1000],
"max_depth": [None, 5, 10, 20, 30],
"max_features": ["sqrt", "log2", None],
"min_samples_split": [2, 4, 6],
"min_samples_leaf": [1, 2, 4],
}
rs = RandomizedSearchCV(
estimator=RandomForestClassifier(),
param_distributions=random_grid,
n_iter=20,
cv=5,
scoring="accuracy",
verbose=2,
random_state=42,
)
rs.fit(X_train, y_train)
rs.best_params_, rs.best_score_
拿到最佳参数后,用它们重建模型并在测试集上评估,确认提升是否真实存在:
best_clf = rs.best_estimator_
y_preds_best = best_clf.predict(X_test)
print("Test accuracy:", accuracy_score(y_test, y_preds_best))
注意:交叉验证的分数来自训练集内部,测试集只应在最后用来做一次最终检验。反复用测试集调参,等于让模型间接看到了测试数据,评估结果会偏乐观。
第八步:保存模型供他人使用
模型训练好之后,可以序列化到磁盘,下次直接加载使用,不必重新训练。sklearn 官方推荐用 joblib 保存模型:
import joblib
joblib.dump(best_clf, "random_forest_heart_disease.joblib")
加载时:
loaded_clf = joblib.load("random_forest_heart_disease.joblib")
loaded_clf.score(X_test, y_test)
加载后的模型可以直接调用 predict() 与 score(),行为与保存前一致。注意保存的只是模型参数,不包含训练数据;如果后续要用新数据继续训练,需要自己保留数据或使用 warm_start 之类的机制。
一个完整示例
把上面的步骤串起来,下面是一段可以独立运行的完整脚本,从读数据一直走到保存模型:
import numpy as np
import pandas as pd
import joblib
import sklearn
from sklearn.model_selection import train_test_split, RandomizedSearchCV
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
print(f"Scikit-Learn version: {sklearn.__version__}")
# 1. 读数据
heart_disease = pd.read_csv("../data/heart-disease.csv")
# 2. 拆特征与标签
X = heart_disease.drop("target", axis=1)
y = heart_disease["target"]
# 3. 划分训练集与测试集
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, random_state=42
)
# 4. 建立基线模型并拟合
clf = RandomForestClassifier(random_state=42)
clf.fit(X_train, y_train)
# 5. 预测与评估
baseline_preds = clf.predict(X_test)
print("Baseline accuracy:", accuracy_score(y_test, baseline_preds))
print(classification_report(y_test, baseline_preds))
print(confusion_matrix(y_test, baseline_preds))
# 6. 随机搜索调参
param_distributions = {
"n_estimators": [10, 100, 200, 500],
"max_depth": [None, 5, 10, 20],
"min_samples_split": [2, 4, 6],
"min_samples_leaf": [1, 2, 4],
}
rs = RandomizedSearchCV(
estimator=RandomForestClassifier(random_state=42),
param_distributions=param_distributions,
n_iter=20,
cv=5,
scoring="accuracy",
random_state=42,
verbose=1,
)
rs.fit(X_train, y_train)
print("Best params:", rs.best_params_)
print("Best CV score:", rs.best_score_)
# 7. 用最佳模型在测试集上做最终评估
best_clf = rs.best_estimator_
final_preds = best_clf.predict(X_test)
print("Test accuracy:", accuracy_score(y_test, final_preds))
# 8. 保存模型
joblib.dump(best_clf, "random_forest_heart_disease.joblib")
# 9. 重新加载并验证
loaded_clf = joblib.load("random_forest_heart_disease.joblib")
print("Loaded model score:", loaded_clf.score(X_test, y_test))
运行这段脚本时,你会依次看到版本号、基线准确率、分类报告、混淆矩阵、最佳参数、交叉验证分数、测试集准确率,以及重新加载后的模型分数。如果加载后的分数与保存前一致,说明持久化没有问题。
注意事项
- 训练集与测试集必须严格分开。 模型在训练时看到测试数据,评估结果就失去意义。调参阶段用交叉验证在训练集内部评估,测试集留到最后只评一次。
- 预测输入的形状必须与训练输入一致。 传入一维数组或列名不匹配的 DataFrame 都会报错。用
X_test或X_test.iloc[[0]]这类保持二维结构的写法最稳妥。 - 类别不平衡时不要只看准确率。 示例数据中两类样本数量接近(165 对 138),准确率尚可参考;如果换成极端不平衡的数据,应结合 precision、recall、F1 与混淆矩阵判断。
- 随机性会影响结果。 随机森林的
random_state默认为None,每次运行结果可能不同。需要可复现时,在模型和搜索器里都显式设置random_state。 - 网格搜索代价随参数组合数增长。 参数多、取值多时,
GridSearchCV的训练次数是组合数乘以折数,耗时可能很长。参数空间大时优先用RandomizedSearchCV并控制n_iter。 - 版本差异。 本文示例基于 1.3.1 的接口,不同版本在默认参数、可用参数名、警告信息上可能有差异,升级前请查阅官网当前信息。
- 模型文件与代码要配套。 用某个版本训练并保存的模型,在差异较大的版本上加载可能失败或行为改变,部署时注意固定环境版本。