前言
前四篇已经准备好了机器学习的基本概念、Python 环境、NumPy 数组和 Pandas 表格数据。我们能够把原始表格整理成特征与标签,却还缺少一个真正负责训练模型的工具。scikit-learn 正好承担这个角色:它为许多传统机器学习任务提供了较统一的使用方式。
分类器、回归器和聚类模型的内部原理并不相同,但在 scikit-learn 中,它们通常遵循相似的调用流程:准备数据,调用fit()学习,再调用predict()预测,最后用合适的指标评估。掌握这套共同语言后,后面更换算法时,就不必重新学习一套完全不同的代码结构。
本文不推导某一种算法的数学原理,而是用 scikit-learn 内置的鸢尾花数据集完成一个小型分类任务,认识数据拆分、预处理、训练、预测和评估的通用写法。案例里的逻辑回归只是演示统一接口的分类器,其原理将在后续独立文章中讲解。
scikit-learn 是什么
scikit-learn 是 Python 中常用的传统机器学习库。它提供分类、回归、聚类、数据预处理、数据集拆分、特征选择、模型评估、Pipeline(流水线)和模型参数管理等工具。对于表格数据、中小规模的传统机器学习任务、教学实验,以及快速建立一个可比较的基线模型,它尤其方便。
它并不是包办所有数据工作的“万能工具”。大规模深度神经网络训练通常使用 PyTorch 或 TensorFlow;通用表格清洗更适合 Pandas;数据库管理和网页数据采集也各有专门工具。一个常见项目会让这些工具分工合作,而不是只依赖某一个库。
安装名称为什么是 scikit-learn,导入名称却是 sklearn
安装时使用的是发布名称scikit-learn:
conda install scikit-learn或者:
python -m pip install scikit-learn代码中的导入名称则是sklearn:
import sklearn也就是说,scikit-learn是软件包在 Conda 或 PyPI 中的发布和安装名称,sklearn是 Python 模块的导入名称。不要用pip install sklearn代替正确的安装命令。
如果终端显示安装成功,运行代码时却仍然找不到库,先检查 pip 与 Python 是否来自同一个环境:
python -m pip --version python -c "import sys; print(sys.executable)"使用python -m pip的好处是明确让当前这个 Python 执行 pip,能减少“包安装到了环境 A,代码却在环境 B 运行”的问题。已有 Conda 环境时,也不要在没有确认解释器的情况下混用多个环境里的 pip。
scikit-learn 的统一接口
scikit-learn 中能够从数据学习参数的对象,通常称为 Estimator(估计器)。这个名称听起来偏数学,但可以先把它理解为“能够执行学习步骤的对象”。常见对象大致分为模型、转换器和 Pipeline。
模型
分类器、回归器和聚类模型都属于这一类。监督学习模型最常见的两个方法是:
model.fit(X, y) model.predict(X)fit负责从训练数据学习,predict负责把已经学到的规律用于新样本。更换模型后,创建对象的类名和参数会变化,但训练与预测的主要调用方式往往不变。
转换器
标准化器、缺失值填充器和类别编码器都属于转换器。它们常见的方法是:
transformer.fit(X) transformer.transform(X) transformer.fit_transform(X)转换器不直接给出任务的最终预测,而是学习一套数据处理规则,再用这套规则改变数据表示。
Pipeline
Pipeline(流水线)按照顺序把预处理器与模型连接成一个整体:
pipeline.fit(X_train, y_train) pipeline.predict(X_test)从外部看,它仍然像一个普通模型;在内部,训练数据会先经过预处理,再进入分类器或回归器。不同算法内部原理不同,统一接口却能让实验骨架保持稳定。
统一接口为什么重要
初学时,我们很容易把注意力全部放在模型名称上,仿佛每换一种算法就要重新设计整个程序。统一接口把“不会变化的流程”与“会变化的模型”分开:特征和标签的组织方式基本不变,数据拆分方法不变,fit与predict的职责不变,评估结果也仍由明确的指标计算。真正需要替换的通常只是 Pipeline 中最后一个估计器及其参数。
这种设计还能让比较更公平。假设想比较逻辑回归与另一种分类器,可以让它们接收同一份训练集和测试集,并沿用同样的评估代码。如果每个模型都使用随意编写的预处理步骤,结果差异可能来自数据处理,而不是算法本身。统一接口不会自动保证实验严谨,但能提供一套更容易检查的结构。
还要注意,“调用方式相似”不表示模型完全等价。不同模型对特征缩放、缺失值、类别特征和参数的要求可能不同;同名的fit也可能学习完全不同的内容。统一接口解决的是程序组织问题,算法适用条件仍需在后续文章中分别理解。
fit、predict、transform 分别做什么
fit:从数据中学习
模型的fit可能学习回归系数、分类边界、决策树的分裂规则或聚类中心。转换器也可以执行fit,但学习的内容不同,例如训练集的均值、标准差、缺失值填充值或类别映射关系。fit改变的是对象内部保存的状态,不只是“把数据传进去运行一下”。
predict:对新样本作出预测
predict使用已经训练好的模型处理待预测特征。调用它之前必须先执行fit,否则模型没有可供使用的参数。预测阶段只接收特征,不应把测试标签交给模型学习。
transform:应用已经学好的转换规则
以标准化为例,转换器先在训练集上学习每列的均值和标准差,再用这些数值转换训练集、测试集和未来新数据。测试集应沿用训练集得到的规则,不能重新计算一套规则。
fit_transform:学习后立即转换
fit_transform(X)可以理解为先执行fit(X),再执行transform(X)。它通常用于训练数据;对验证集、测试集和新数据一般只调用transform。为什么错误地在测试集上重新fit会造成数据泄漏,将在下一篇“机器学习完整流程”中进一步说明。
特征矩阵 X 和标签 y
scikit-learn 通常把模型输入记为大写X,把需要预测的目标记为小写y。X通常是二维结构,形状为“样本数 × 特征数”;y通常是一维结构,其长度必须与X的样本数一致。
例如 150 个样本、每个样本 4 个特征:
X.shape = (150, 4) y.shape = (150,)这里X的每一行对应一个样本,每一列对应一种测量特征;y的每个元素对应同一行样本的类别。X.shape[0]必须等于len(y),否则无法确定标签和样本如何对应。
即使只有一个特征,X通常也应保持二维。形状(150,)是一维数组,而单特征矩阵应为(150, 1)。使用 Pandas 时,双中括号会保留 DataFrame 的二维结构:
X = data[["feature"]]单中括号得到的则是一维 Series:
X = data["feature"]scikit-learn 的模块组织方式
scikit-learn 按职责组织子模块。本篇只需要以下几项:
from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.pipeline import Pipeline from sklearn.linear_model import LogisticRegression from sklearn.metrics import accuracy_score, confusion_matrixdatasets提供教学数据集;model_selection提供数据拆分和模型选择工具;preprocessing负责预处理;pipeline连接处理步骤;linear_model包含线性模型;metrics提供评估指标。实际代码通常从具体子模块导入需要的类或函数,这比把整个sklearn想象成一个没有结构的大工具箱更容易阅读和维护。
第一个分类案例:认识鸢尾花数据集
鸢尾花数据集可以直接从 scikit-learn 载入,不需要联网或手动下载:
dataset = load_iris(as_frame=True) X = dataset.data y = dataset.target参数as_frame=True让特征以 Pandas DataFrame 形式返回,标签则是 Series。数据集中有 150 条样本、4 个数值特征和 3 个类别,特征是花萼与花瓣的长度、宽度。dataset.data保存特征,dataset.target保存数值类别标签,dataset.feature_names和dataset.target_names分别提供特征名称与类别名称。
本篇重点不是植物学知识,也不是解释逻辑回归的决策边界,而是观察这份小数据如何经过一套标准的 scikit-learn 流程。
使用 train_test_split 拆分数据
如果使用全部样本训练,又在同一批样本上评估,结果只能说明模型对已见数据的拟合情况,不能可靠反映它面对新样本的表现。因此先保留一部分数据作为测试集:
X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.25, random_state=42, stratify=y, )test_size=0.25表示约 25% 的样本留作测试;random_state=42固定随机拆分过程,使同一环境中的结果可复现;stratify=y让训练集和测试集尽量保持与原数据相近的类别比例。拆分后,代码只能用X_train和y_train训练,X_test与y_test留到评估阶段。本篇只设置训练集和测试集,下一篇再系统介绍训练集、验证集和测试集的职责。
使用 Pipeline 组合预处理与模型
本例把标准化和逻辑回归放入同一个 Pipeline:
model = Pipeline( steps=[ ("scaler", StandardScaler()), ( "classifier", LogisticRegression( max_iter=1000, random_state=42, ), ), ] )第一步StandardScaler对各列数值特征进行标准化;第二步LogisticRegression完成分类。逻辑回归在这里仅作为统一接口的示例,其数学原理留到分类算法文章展开。
Pipeline 把预处理和模型封装成一个对象。训练时,它会先用训练特征拟合标准化器并转换数据,再训练分类器;预测时,它会自动对测试特征应用同一套标准化规则,然后交给分类器。这样做便于复用代码,也能降低忘记转换、使用不同转换顺序等手动错误的概率。
训练和预测
创建 Pipeline 后,训练与预测只需要两行核心代码:
model.fit(X_train, y_train) predictions = model.predict(X_test)fit接收训练特征与训练标签,Pipeline 内部按既定顺序完成标准化和模型训练;predict只接收待预测特征。模型不能从测试标签中学习信息,y_test只用于事后比较。可以用下面的方式查看前几项结果:
print(predictions[:5]) print(y_test.iloc[:5])如何评估分类结果
准确率(accuracy)表示预测正确的测试样本占全部测试样本的比例:
accuracy = accuracy_score(y_test, predictions)本次实际运行的测试集有 38 条样本,其中 35 条预测正确,准确率为0.9211,约为 92.11%。这个数值来自本文代码的真实运行,不代表算法在所有数据上的固定水平。数据拆分、数据质量和任务难度改变后,结果也会变化。
准确率并不适合所有分类问题。如果正负类别极不平衡,只预测多数类也可能得到看似较高的准确率。因此还可以先查看混淆矩阵:
matrix = confusion_matrix(y_test, predictions)混淆矩阵的行通常表示真实类别,列表示预测类别。对角线是预测正确的数量,非对角线是误分类。本次实际结果为:
[[12 0 0] [ 0 12 1] [ 0 2 11]]三类在测试集中分别有 12、13、13 条样本。矩阵显示第一类 12 条全部正确;第二类有 1 条被预测成第三类;第三类有 2 条被预测成第二类。精确率、召回率和 F1 等指标将在后续分类文章中继续介绍。
score 方法与 metrics 的区别
许多 scikit-learn 模型提供model.score(X_test, y_test),但score的含义取决于模型类型:分类模型通常返回准确率,回归模型通常返回决定系数 R²。只看到一个score数字时,读者还需要查看文档才能确认它是什么。
教学和实际项目中,更清楚的写法是显式调用accuracy_score(...)、mean_absolute_error(...)或r2_score(...)。函数名直接说明评估口径,也能避免把不同任务的分数错误比较。
查看和修改模型参数
每个估计器都可以通过get_params()查看参数:
params = model.get_params()Pipeline 内部参数使用“步骤名称、双下划线、参数名称”的形式。例如分类器步骤名是classifier,修改它的C参数可以写成:
model.set_params(classifier__C=0.5)双下划线__表示继续进入某个 Pipeline 步骤查找参数。本例实际读取了classifier__max_iter,结果为1000。本篇只认识参数查看与设置方式,不进行超参数搜索;模型选择和验证会在下一篇继续讨论。
scikit-learn 与 NumPy、Pandas 的关系
Pandas 擅长读取和清洗表格、保留列名与索引,适合数据探索;NumPy 提供底层数组和高效数值计算,许多数据最终可以表示为 NumPy 数组;scikit-learn 接收整理好的特征与标签,完成预处理、模型训练、预测和评估。
一条常见的数据流是:
CSV 或其他数据源 → Pandas DataFrame → 数据清洗与特征选择 → X 和 y → scikit-learn Pipeline → 预测与评估三者并非互相替代。Pandas 解决“怎样整理表格”,NumPy 解决“怎样表示和计算数组”,scikit-learn 解决“怎样以一致方式训练与评估传统机器学习模型”。
scikit-learn 常见错误
1. No module named sklearn
当前环境可能没有安装 scikit-learn,也可能是安装和运行使用了不同解释器,或者 IDE 选错了环境。先运行:
import sys print(sys.executable)再与安装依赖时使用的 Python 路径比较。
2. Expected 2D array, got 1D array instead
模型期望二维特征矩阵,却收到一维数组。NumPy 单特征数据可以改为:
single_feature = values.reshape(-1, 1)Pandas 则可用双中括号选择单列,保持 DataFrame 结构。
3. X 和 y 的样本数量不一致
必须满足X.shape[0] == len(y)。常见原因包括分别删除了不同数据中的缺失值、切片范围不同,或按索引对齐时产生了缺失。清洗后应把特征与标签放在同一数据表中统一筛选。
4. predict 提示模型尚未训练
调用predict前必须先调用fit,否则可能出现NotFittedError。重新创建模型对象后,之前对象学到的状态也不会自动转移。
5. 训练和预测时特征列不一致
预测数据必须与训练时保持相同的特征数量、列含义、列顺序和预处理方式。Pipeline 能降低预处理不一致的风险,但不会自动修复错误的业务字段。
6. 字符串类别不能直接传给某些模型
部分模型要求输入特征为数值,文本类别需要先编码。具体选择哪种编码取决于类别含义,后续文章再讨论特征工程,不能简单地把任意字符串随意编号后就认为问题解决。
7. import sklearn 成功,但具体模型导入失败
检查 scikit-learn 版本、导入路径拼写,以及接口是否属于当前版本。还要检查当前目录是否有名为sklearn.py的本地文件;它可能遮蔽真正的第三方库。
完整实践案例
下面是05_sklearn_intro.py的完整内容。程序会检查环境、载入并验证数据、拆分样本、训练 Pipeline、预测、评估,并读取 Pipeline 参数。
"""用 scikit-learn 的统一接口完成鸢尾花分类。""" from __future__ import annotations import sys from pathlib import Path RANDOM_STATE = 42 TEST_SIZE = 0.25 def import_dependencies(): """导入依赖,并在缺少库时给出清楚的处理提示。""" if sys.version_info < (3, 8): raise RuntimeError("需要 Python 3.8 或更高版本。") try: import numpy as np import pandas as pd import sklearn from sklearn.datasets import load_iris from sklearn.linear_model import LogisticRegression from sklearn.metrics import accuracy_score, confusion_matrix from sklearn.model_selection import train_test_split from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler except ImportError as exc: missing_name = exc.name or "未知依赖" raise RuntimeError( f"缺少依赖 {missing_name!r}。请先确认当前 Conda 环境中已安装 " "NumPy、Pandas 和 scikit-learn。" ) from exc return { "np": np, "pd": pd, "sklearn": sklearn, "load_iris": load_iris, "LogisticRegression": LogisticRegression, "accuracy_score": accuracy_score, "confusion_matrix": confusion_matrix, "train_test_split": train_test_split, "Pipeline": Pipeline, "StandardScaler": StandardScaler, } def load_and_validate_data(deps): """载入 scikit-learn 内置数据,并检查形状、缺失值和数值有效性。""" np = deps["np"] pd = deps["pd"] iris = deps["load_iris"](as_frame=True) X = iris.data.copy() y = iris.target.copy() if not isinstance(X, pd.DataFrame) or not isinstance(y, pd.Series): raise ValueError("数据类型不符合预期:X 应为 DataFrame,y 应为 Series。") if X.shape[0] != y.shape[0]: raise ValueError("特征与标签的样本数不一致。") if X.empty or y.empty: raise ValueError("数据集为空,无法训练模型。") if X.isna().any().any() or y.isna().any(): raise ValueError("数据中存在缺失值,请先处理再训练。") if not np.isfinite(X.to_numpy()).all(): raise ValueError("特征中存在无穷大或非有限数值。") return X, y, list(iris.feature_names), list(iris.target_names) def build_pipeline(deps): """把标准化和逻辑回归组合成一个可统一训练、预测的 Pipeline。""" return deps["Pipeline"]( steps=[ ("scaler", deps["StandardScaler"]()), ( "classifier", deps["LogisticRegression"]( max_iter=1000, random_state=RANDOM_STATE, ), ), ] ) def main() -> None: deps = import_dependencies() np = deps["np"] pd = deps["pd"] print("=== 环境信息 ===") print(f"Python: {sys.version.split()[0]}") print(f"NumPy: {np.__version__}") print(f"Pandas: {pd.__version__}") print(f"Scikit-learn: {deps['sklearn'].__version__}") print(f"Interpreter: {Path(sys.executable).resolve()}") X, y, feature_names, target_names = load_and_validate_data(deps) print("\n=== 数据集 ===") print(f"完整特征形状: {X.shape}") print(f"标签形状: {y.shape}") print(f"特征名称: {feature_names}") print(f"类别名称: {target_names}") X_train, X_test, y_train, y_test = deps["train_test_split"]( X, y, test_size=TEST_SIZE, random_state=RANDOM_STATE, stratify=y, ) print("\n=== 数据拆分 ===") print(f"训练集样本数: {len(X_train)}") print(f"测试集样本数: {len(X_test)}") model = build_pipeline(deps) model.fit(X_train, y_train) predictions = model.predict(X_test) accuracy = deps["accuracy_score"](y_test, predictions) matrix = deps["confusion_matrix"](y_test, predictions) print("\n=== 预测与评估 ===") print("前 8 个真实类别:", [target_names[index] for index in y_test.iloc[:8]]) print("前 8 个预测类别:", [target_names[index] for index in predictions[:8]]) print(f"测试集准确率: {accuracy:.4f}") print("混淆矩阵:") print(matrix) params = model.get_params() print("\n=== Pipeline 参数 ===") print(f"Pipeline 分类器: {params['classifier']}") print(f"分类器最大迭代次数: {params['classifier__max_iter']}") if __name__ == "__main__": try: main() except (RuntimeError, ValueError) as exc: print(f"程序无法继续:{exc}", file=sys.stderr) sys.exit(1) except Exception as exc: # 避免初学者只看到难以理解的长堆栈 print(f"出现未预期错误:{type(exc).__name__}: {exc}", file=sys.stderr) sys.exit(1)在项目根目录运行:
python 05_sklearn_intro.py本文使用现有 Condabase环境实际运行,程序正常结束,输出如下:
=== 环境信息 === Python: 3.11.4 NumPy: 1.24.3 Pandas: 1.5.3 Scikit-learn: 1.3.0 Interpreter: C:\Users\32981\anaconda3\python.exe === 数据集 === 完整特征形状: (150, 4) 标签形状: (150,) 特征名称: ['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)'] 类别名称: ['setosa', 'versicolor', 'virginica'] === 数据拆分 === 训练集样本数: 112 测试集样本数: 38 === 预测与评估 === 前 8 个真实类别: ['setosa', 'versicolor', 'versicolor', 'versicolor', 'setosa', 'versicolor', 'virginica', 'virginica'] 前 8 个预测类别: ['setosa', 'versicolor', 'versicolor', 'versicolor', 'setosa', 'versicolor', 'versicolor', 'virginica'] 测试集准确率: 0.9211 混淆矩阵: [[12 0 0] [ 0 12 1] [ 0 2 11]] === Pipeline 参数 === Pipeline 分类器: LogisticRegression(max_iter=1000, random_state=42) 分类器最大迭代次数: 1000代码先集中导入并检查依赖,再验证特征、标签、缺失值和有限数值。验证通过后才拆分数据,且只用训练集调用fit。测试集预测结果进入显式的评估函数,最后通过get_params读取 Pipeline 内部分类器和最大迭代次数。这样既展示了主流程,也让环境或数据异常能够给出较明确的提示。
按执行顺序阅读完整代码
第一次看到完整脚本时,不必逐行记忆。可以先抓住四个阶段。第一阶段是环境入口:import_dependencies检查 Python 和依赖,并把本篇需要的类与函数集中起来。若导入失败,程序会提示缺少哪个包,而不是继续运行到模型训练处才报出难以定位的错误。
第二阶段是数据入口:load_and_validate_data使用内置数据集,不依赖当前目录中的文件。它确认X与y的类型、样本数量、缺失情况和数值有效性。这里的检查不是为了证明数据一定正确,而是尽早发现会让后续接口无法工作的基础问题。
第三阶段是建模:train_test_split先得到互不混用的训练与测试部分,build_pipeline再把标准化器和分类器装入一个对象。随后只有X_train、y_train进入fit,测试特征只进入predict。这条边界是阅读机器学习代码时需要特别检查的地方。
第四阶段是验证结果:准确率回答“总体预测对了多少”,混淆矩阵进一步展示错误发生在哪些类别之间。最后读取参数并不是为了调出更高分数,而是演示 Pipeline 内部对象仍然可以被检查。把代码看成这四段流程,比孤立记住每个函数更容易迁移到其他数据集和模型。
本文总结
scikit-learn 的价值不仅在于提供许多算法,还在于让不同算法共享一套较一致的使用方式。估计器用fit学习,模型用predict预测,转换器用transform应用处理规则,Pipeline 则把预处理和模型连成一个整体。
进入模型前,X应保持“样本数 × 特征数”的二维结构,y的长度要与样本数一致;训练前用train_test_split留出测试数据;评估时显式选择指标,并结合混淆矩阵理解错误。掌握这些接口后,后续学习线性回归、逻辑回归、K 近邻或决策树时,变化的主要是模型原理与参数,训练代码的骨架仍然熟悉。
下一篇预告
下一篇是第 6 篇《机器学习完整流程:数据、训练、验证与测试》。我们会在本篇统一接口的基础上,系统讨论问题定义、训练集、验证集、测试集、数据泄漏和实验比较,让“代码能运行”进一步变成“评估过程可信”。