深入浅出 Scikit-learn:从入门到实战的机器学习工具包指南
深入浅出 Scikit-learn:从入门到实战的机器学习工具包指南
引言:为什么选择 Scikit-learn?
在机器学习领域,工具的选择直接影响开发效率与模型效果。对于 Python 开发者而言,Scikit-learn(简称 sklearn) 无疑是最受欢迎的机器学习库之一 —— 它以简洁的 API、丰富的算法集成、完善的文档支持和强大的兼容性,成为从入门到工业级应用的 “一站式工具包”。
自 2007 年首次发布以来,sklearn 始终遵循 “实用优先” 的设计理念,核心优势体现在三个方面:
- 低学习成本:统一的 API 设计(如所有模型均通过
fit()
训练、predict()
预测),让开发者无需重复学习不同算法的调用逻辑; - 算法全覆盖:涵盖分类、回归、聚类、降维、特征工程、模型评估等机器学习全流程,无需在多个库间切换;
- 强兼容性:与 NumPy、Pandas、Matplotlib 等 Python 数据科学生态无缝衔接,可直接处理主流数据格式。
无论你是刚接触机器学习的新手,还是需要快速落地项目的工程师,sklearn 都能帮你高效实现想法。本文将从环境搭建开始,逐步深入 sklearn 的核心概念、关键模块与实战案例,带你系统掌握这个强大工具。
第一章:环境搭建与基础准备
在使用 sklearn 前,需先完成环境配置。sklearn 本身轻量,但依赖多个底层库(如数值计算库 NumPy、数据分析库 Pandas、可视化库 Matplotlib),建议通过pip
或conda
统一安装,避免版本冲突。
1.1 环境安装(Windows/macOS/Linux 通用)
方式 1:使用 pip 安装(推荐新手)
打开终端 / 命令提示符,执行以下命令,安装最新稳定版:
bash
# 先升级pip(可选但推荐)
pip install --upgrade pip# 安装核心依赖库+sklearn
pip install numpy pandas matplotlib scikit-learn
方式 2:使用 conda 安装(适合 Anaconda 用户)
若已安装 Anaconda,执行:
bash
conda install scikit-learn numpy pandas matplotlib
1.2 验证安装是否成功
安装完成后,通过 Python 代码验证环境是否正常:
python
# 导入核心库
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn import __version__
from sklearn.datasets import load_iris # 加载sklearn内置数据集# 检查版本(sklearn 1.0+为稳定版,本文基于1.3.0测试)
print(f"Scikit-learn版本:{__version__}") # 输出应为1.0以上# 加载鸢尾花数据集,验证数据加载功能
iris = load_iris()
print(f"数据集形状:{iris.data.shape}") # 输出(150, 4),表示150个样本,4个特征
print(f"数据集类别:{iris.target_names}") # 输出['setosa' 'versicolor' 'virginica']
若代码无报错且正常输出信息,说明环境已搭建成功。
1.3 sklearn 核心概念:三大基石
在开始实战前,必须理解 sklearn 的三个核心抽象概念 ——估计器(Estimator)、转换器(Transformer)、评估器(Evaluator),它们是 sklearn API 设计的灵魂,贯穿所有模块。
概念 | 作用 | 核心方法 | 典型示例 |
---|---|---|---|
估计器(Estimator) | 用于模型训练,学习数据中的模式 | fit(X, y) :用数据训练模型;predict(X) :预测新数据 | 逻辑回归、随机森林、K-Means |
转换器(Transformer) | 用于数据预处理 / 特征工程(如缩放、编码) | fit(X) :学习数据统计信息;transform(X) :应用转换;fit_transform(X) :合并两步 | StandardScaler、OneHotEncoder |
评估器(Evaluator) | 用于模型性能评估 | score(X, y) :计算模型在数据上的得分;metrics.xxx :计算具体指标(如准确率) | accuracy_score、r2_score |
关键原则:所有 sklearn 组件都遵循 “惰性计算”—— 仅在调用fit()
时才学习数据信息,transform()
或predict()
仅应用已学习的规则,避免重复计算。
第二章:数据预处理 —— 机器学习的 “地基”
“数据决定模型上限,算法只是逼近这个上限”。在训练模型前,需通过预处理将原始数据转化为模型可接受的格式(如数值化、无缺失值、特征尺度统一)。sklearn 的preprocessing
和impute
模块提供了全套预处理工具,本节将覆盖最常用的场景。
2.1 缺失值处理:填补或删除
现实数据中常存在缺失值(如 “年龄” 字段为空),若直接输入模型会导致报错。sklearn 的SimpleImputer
是处理缺失值的核心工具,支持均值、中位数、众数等填充策略。
案例:处理泰坦尼克号数据集的缺失值
python
# 1. 加载数据(使用seaborn的泰坦尼克号数据集,需先安装seaborn:pip install seaborn)
import seaborn as sns
from sklearn.impute import SimpleImputer
import pandas as pd# 加载数据并查看缺失值
titanic = sns.load_dataset('titanic')
print("原始数据缺失值统计:")
print(titanic.isnull().sum()) # 输出每列缺失值数量,可见age、deck、embarked有缺失# 2. 分离数值型特征和分类特征(缺失值处理策略不同)
numeric_features = ['age', 'fare'] # 数值型特征:年龄、票价
cat_features = ['embarked', 'deck'] # 分类特征:登船港口、船舱# 3. 处理数值型特征的缺失值(用中位数填充,抗异常值)
numeric_imputer = SimpleImputer(strategy='median') # strategy可选:mean(均值)、median(中位数)、most_frequent(众数)
titanic[numeric_features] = numeric_imputer.fit_transform(titanic[numeric_features])# 4. 处理分类特征的缺失值(用众数填充,或用"Unknown"标记)
# 方法1:用众数填充(适合缺失率低的分类特征,如embarked)
cat_imputer_mode = SimpleImputer(strategy='most_frequent')
titanic['embarked'] = cat_imputer_mode.fit_transform(titanic[['embarked']]) # 注意:fit_transform需传入2D数组# 方法2:用自定义值填充(适合缺失率高的分类特征,如deck)
cat_imputer_custom = SimpleImputer(strategy='constant', fill_value='Unknown')
titanic['deck'] = cat_imputer_custom.fit_transform(titanic[['deck']])# 5. 验证缺失值是否已处理
print("\n处理后缺失值统计:")
print(titanic.isnull().sum()) # 所有列缺失值应为0
关键说明:
- 数值型特征优先用中位数(避免异常值影响),若数据无异常可用品均值;
- 分类特征若缺失率低(<5%)用**众数**,缺失率高(>30%)用 “Unknown” 等自定义值,或直接删除该特征;
SimpleImputer
仅处理数值型缺失值,文本型缺失值需手动用fillna()
处理(如df['col'].fillna('Unknown')
)。
2.2 特征缩放:消除量纲影响
许多模型(如 SVM、逻辑回归、梯度下降)对特征尺度敏感 —— 例如 “身高(米)” 范围是 1-2,“体重(公斤)” 范围是 40-100,若不缩放,模型会过度重视体重特征。sklearn 提供两种主流缩放方法:标准化(StandardScaler) 和归一化(MinMaxScaler)。
方法 | 原理 | 适用场景 | 优点 |
---|---|---|---|
StandardScaler | 均值 = 0,标准差 = 1(Z-score 标准化) | 数据近似正态分布、模型对异常值不敏感(如 SVM) | 保留数据分布特征,抗异常值较强 |
MinMaxScaler | 缩放到 [0,1] 或指定范围(如 [-1,1]) | 数据分布未知、需要固定特征范围(如神经网络输入) | 直观易懂,适合稀疏数据 |
案例:缩放鸢尾花数据集特征
python
from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler, MinMaxScaler
import numpy as np# 1. 加载数据(仅取数值特征)
iris = load_iris()
X = iris.data # 特征:花萼长度、花萼宽度、花瓣长度、花瓣宽度
print("原始特征统计:")
print(f"均值:{np.mean(X, axis=0)}") # 输出各特征均值(如花瓣长度均值约3.76)
print(f"标准差:{np.std(X, axis=0)}") # 输出各特征标准差(如花瓣长度标准差约1.76)
print(f"最小值:{np.min(X, axis=0)}") # 输出各特征最小值
print(f"最大值:{np.max(X, axis=0)}") # 输出各特征最大值# 2. 标准化(StandardScaler)
scaler_std = StandardScaler()
X_std = scaler_std.fit_transform(X)
print("\n标准化后特征统计:")
print(f"均值:{np.round(np.mean(X_std, axis=0), 2)}") # 接近[0,0,0,0]
print(f"标准差:{np.round(np.std(X_std, axis=0), 2)}") # 接近[1,1,1,1]# 3. 归一化(MinMaxScaler)
scaler_minmax = MinMaxScaler(feature_range=[0, 1]) # 缩放到[0,1],可改range=[-1,1]
X_minmax = scaler_minmax.fit_transform(X)
print("\n归一化后特征统计:")
print(f"最小值:{np.round(np.min(X_minmax, axis=0), 2)}") # 全为0
print(f"最大值:{np.round(np.max(X_minmax, axis=0), 2)}") # 全为1
关键提醒:
- 特征缩放仅对训练集执行 fit (),测试集需用训练集的缩放规则(即仅调用 transform ()),避免数据泄露(Data Leakage);
- 决策树、随机森林等基于树的模型无需特征缩放(它们通过分裂节点判断特征重要性,与尺度无关)。
2.3 分类变量编码:将文本转为数值
模型只能处理数值,需将 “性别(男 / 女)”“颜色(红 / 绿 / 蓝)” 等分类变量转为数值。sklearn 提供两种编码方式:标签编码(LabelEncoder) 和独热编码(OneHotEncoder),需根据分类类型(有序 / 无序)选择。
2.3.1 标签编码(LabelEncoder):适合有序分类
有序分类指类别有明确顺序(如 “评分:低 / 中 / 高”),LabelEncoder 将类别映射为 0,1,2,...
2.3.2 独热编码(OneHotEncoder):适合无序分类
无序分类指类别无顺序(如 “颜色:红 / 绿 / 蓝”),OneHotEncoder 将每个类别转为一个二进制列(如 “红”→[1,0,0],“绿”→[0,1,0]),避免模型误解类别顺序。
案例:编码泰坦尼克号数据集的分类变量
python
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
from sklearn.compose import ColumnTransformer # 批量处理不同类型特征
from sklearn.pipeline import Pipeline # 串联多个预处理步骤
import pandas as pd# 1. 加载数据(仅取关键分类特征)
titanic = sns.load_dataset('titanic')
X = titanic[['sex', 'embarked', 'pclass']] # sex(性别:男/女)、embarked(登船口:S/C/Q)、pclass(舱位:1/2/3)
print("原始分类特征:")
print(X.head())# 2. 区分有序分类和无序分类
ordinal_features = ['pclass'] # 有序分类:1>2>3
nominal_features = ['sex', 'embarked'] # 无序分类:无顺序# 3. 定义预处理逻辑(用ColumnTransformer批量处理不同特征)
# 有序分类:LabelEncoder
ordinal_transformer = Pipeline(steps=[('label', LabelEncoder()) # 注意:LabelEncoder输入需1D,此处用Pipeline包装
])# 无序分类:OneHotEncoder(drop='first'避免多重共线性)
nominal_transformer = Pipeline(steps=[('onehot', OneHotEncoder(drop='first', sparse_output=False)) # sparse_output=False输出数组(默认是稀疏矩阵)
])# 批量应用预处理
preprocessor = ColumnTransformer(transformers=[('ord', ordinal_transformer, ordinal_features), # 处理有序特征('nom', nominal_transformer, nominal_features) # 处理无序特征])# 4. 执行编码
X_encoded = preprocessor.fit_transform(X)
print("\n编码后特征形状:", X_encoded.shape) # 输出(891, 4):pclass(1列)+sex(1列)+embarked(2列)
print("\n编码后前5行数据:")
print(X_encoded[:5])
关键说明:
OneHotEncoder
的drop='first'
参数:删除第一个类别列(如 embarked 的 S 列),避免 “多重共线性”(如已知不是 C 和 Q,必然是 S);ColumnTransformer
:可对不同类型特征应用不同预处理逻辑,是工业级代码的常用工具;Pipeline
:将多个预处理步骤串联(如 “缺失值填充→编码→缩放”),避免代码冗余,且能防止数据泄露。
第三章:特征选择 —— 提升模型效率与泛化能力
特征并非越多越好 —— 冗余特征(如 “身高” 和 “体重” 高度相关)会增加模型复杂度、导致过拟合。特征选择的目标是筛选出 “关键特征”,在减少计算成本的同时提升模型泛化能力。sklearn 的feature_selection
模块提供三类主流方法:过滤法、包裹法、嵌入法。
3.1 过滤法(Filter):基于统计指标筛选
过滤法不依赖模型,直接通过统计指标(如方差、相关性)筛选特征,速度快、适合预处理阶段。
3.1.1 方差过滤(VarianceThreshold):删除低方差特征
方差低的特征(如 “所有样本的性别都是男”)对模型无贡献,可直接删除。
3.1.2 相关性过滤(SelectKBest):选择与目标最相关的特征
通过统计检验(如分类问题用 F 检验、回归问题用皮尔逊相关系数)计算特征与目标的相关性,选择 Top-K 特征。
案例:用过滤法筛选加州房价数据集的特征
python
from sklearn.datasets import fetch_california_housing
from sklearn.feature_selection import VarianceThreshold, SelectKBest, f_regression
import pandas as pd
import matplotlib.pyplot as plt# 1. 加载数据(回归任务:预测房价)
housing = fetch_california_housing()
X = pd.DataFrame(housing.data, columns=housing.feature_names) # 8个特征(如平均收入、房屋年龄)
y = housing.target # 目标:房屋中位数价格(单位:10万美元)
print("原始特征:", X.columns.tolist())
print("原始特征形状:", X.shape) # (20640, 8)# 2. 步骤1:方差过滤(删除方差<0.1的特征)
var_thresh = VarianceThreshold(threshold=0.1)
X_var = var_thresh.fit_transform(X)
# 查看被删除的特征
deleted_features_var = [col for col, keep in zip(X.columns, var_thresh.get_support()) if not keep]
print(f"\n方差过滤删除的特征:{deleted_features_var}") # 本例中无特征被删除(方差均>0.1)
print("方差过滤后特征形状:", X_var.shape) # 仍为(20640, 8)# 3. 步骤2:相关性过滤(选择Top-5与目标最相关的特征)
# f_regression:用于回归问题的F检验,计算特征与目标的线性相关性
select_kbest = SelectKBest(score_func=f_regression, k=5)
X_kbest = select_kbest.fit_transform(X_var, y)
# 查看特征得分与筛选结果
feature_scores = pd.DataFrame({'feature': X.columns[var_thresh.get_support()], # 方差过滤后保留的特征'score': select_kbest.scores_,'p_value': select_kbest.pvalues_
}).sort_values('score', ascending=False)print("\n特征相关性得分(降序):")
print(feature_scores)
# 查看被选择的特征
selected_features_kbest = [col for col, keep in zip(X.columns[var_thresh.get_support()], select_kbest.get_support()) if keep]
print(f"\n相关性过滤选择的Top-5特征:{selected_features_kbest}") # 如MedInc(平均收入)、AveRooms(平均房间数)等
print("相关性过滤后特征形状:", X_kbest.shape) # (20640, 5)# 4. 可视化特征得分
plt.figure(figsize=(10, 6))
plt.barh(feature_scores['feature'], feature_scores['score'])
plt.xlabel('F检验得分(越高相关性越强)')
plt.ylabel('特征')
plt.title('加州房价数据集特征与目标的相关性得分')
plt.gca().invert_yaxis() # 倒序显示,得分高的在顶部
plt.show()
关键提醒:
- 方差阈值需根据业务调整(如文本特征的词频方差低,阈值可设更小);
SelectKBest
的k
值可通过交叉验证确定(如尝试 k=3,5,7,选择模型效果最好的);- 过滤法仅考虑单特征与目标的关系,可能忽略特征间的交互作用(如 “收入 × 房间数” 对房价的影响)。
3.2 包裹法(Wrapper):基于模型性能筛选
包裹法将模型作为 “evaluator”,通过反复训练模型筛选最优特征子集,能捕捉特征交互作用,但计算成本高(适合小数据集)。sklearn 的RFE
(递归特征消除)是经典包裹法工具。
案例:用 RFE 筛选随机森林模型的特征
python
from sklearn.datasets import fetch_california_housing
from sklearn.feature_selection import RFE
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
import pandas as pd# 1. 加载数据并划分训练集/测试集(避免数据泄露)
housing = fetch_california_housing()
X = pd.DataFrame(housing.data, columns=housing.feature_names)
y = housing.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)# 2. 定义基础模型(随机森林回归)
rf = RandomForestRegressor(n_estimators=100, random_state=42)# 3. 初始化RFE(筛选出5个特征)
rfe = RFE(estimator=rf, n_features_to_select=5, step=1) # step=1:每次删除1个最差特征
rfe.fit(X_train, y_train)# 4. 查看筛选结果
feature_rfe = pd.DataFrame({'feature': X.columns,'selected': rfe.support_,'rank': rfe.ranking_ # 排名:1表示被选择,2表示第1次被删除,依此类推
}).sort_values('rank')print("RFE特征筛选结果:")
print(feature_rfe)
selected_features_rfe = X.columns[rfe.support_].tolist()
print(f"\nRFE选择的5个特征:{selected_features_rfe}")# 5. 评估筛选前后的模型性能
# 筛选前的模型
rf_full = RandomForestRegressor(n_estimators=100, random_state=42)
rf_full.fit(X_train, y_train)
score_full = rf_full.score(X_test, y_test) # R²分数,越高越好# 筛选后的模型
X_train_rfe = rfe.transform(X_train)
X_test_rfe = rfe.transform(X_test)
rf_rfe = RandomForestRegressor(n_estimators=100, random_state=42)
rf_rfe.fit(X_train_rfe, y_train)
score_rfe = rf_rfe.score(X_test, y_test)print(f"\n筛选前模型R²得分:{score_full:.4f}") # 约0.80
print(f"筛选后模型R²得分:{score_rfe:.4f}") # 约0.79(特征减少但性能接近,说明筛选有效)
关键说明:
- RFE 的
estimator
需选择支持feature_importances_
或coef_
的模型(如随机森林、逻辑回归); - 包裹法计算成本高(本例中需训练 8-5+1=4 次模型),大数据集建议用过滤法或嵌入法。
3.3 嵌入法(Embedded):特征选择与模型训练同步
嵌入法将特征选择嵌入模型训练过程,通过模型自身的特征重要性(如随机森林的feature_importances_
、L1 正则化的coef_
)筛选特征,兼顾效果与效率,是工业界常用方法。
案例:用 L1 正则化(Lasso)筛选回归特征
L1 正则化会使不重要特征的系数变为 0,从而实现特征选择。
python
from sklearn.datasets import fetch_california_housing
from sklearn.linear_model import Lasso
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
import pandas as pd
import numpy as np# 1. 加载数据并预处理(Lasso对特征尺度敏感,需先标准化)
housing = fetch_california_housing()
X = pd.DataFrame(housing.data, columns=housing.feature_names)
y = housing.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)# 标准化(Lasso必须做!)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)# 2. 训练Lasso模型(调整alpha参数控制正则化强度)
# alpha越大,正则化越强,越多特征系数变为0
lasso = Lasso(alpha=0.01, random_state=42)
lasso.fit(X_train_scaled, y_train)# 3. 查看特征系数(系数为0的特征被筛选掉)
feature_lasso = pd.DataFrame({'feature': X.columns,'coef': lasso.coef_,'selected': np.abs(lasso.coef_) > 1e-10 # 系数绝对值>1e-10视为非零
}).sort_values('coef', ascending=False)print("Lasso特征系数与筛选结果:")
print(feature_lasso)
selected_features_lasso = feature_lasso[feature_lasso['selected']]['feature'].tolist()
print(f"\nLasso选择的特征:{selected_features_lasso}")# 4. 评估模型性能
score_lasso = lasso.score(X_test_scaled, y_test)
print(f"\nLasso模型R²得分:{score_lasso:.4f}") # 约0.57(线性模型性能低于随机森林,但特征更简洁)
关键提醒:
- L1 正则化(Lasso)适合线性模型,非线性模型(如随机森林)可直接用
feature_importances_
筛选特征; alpha
参数需通过交叉验证调优(如用LassoCV
自动选择最优 alpha),避免正则化过强导致欠拟合。
第四章:sklearn 模型实战 —— 分类、回归与聚类
sklearn 集成了数十种经典机器学习算法,且 API 高度统一。本节将针对三大核心任务(分类、回归、聚类),选择最常用的模型进行实战,涵盖模型训练、预测、评估全流程。
4.1 分类任务:预测类别(如 “垃圾邮件 / 正常邮件”)
分类任务的目标是将样本分为预定义的类别,本节以鸢尾花物种分类为例,实战逻辑回归、随机森林、SVM 三种经典模型,并对比性能。
4.1.1 数据准备与划分
python
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler# 1. 加载数据
iris = load_iris()
X = iris.data # 特征:4个
y = iris.target # 目标:3个类别(0=setosa,1=versicolor,2=virginica)# 2. 划分训练集(80%)和测试集(20%)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y # stratify=y:保持测试集类别分布与训练集一致
)# 3. 特征缩放(SVM和逻辑回归需要,随机森林不需要,但统一缩放不影响)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)print(f"训练集形状:{X_train_scaled.shape}") # (120, 4)
print(f"测试集形状:{X_test_scaled.shape}") # (30, 4)
4.1.2 模型训练与预测
python
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import SVC
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
import seaborn as sns
import matplotlib.pyplot as plt# 1. 定义模型(均使用默认参数,后续可调优)
models = {"逻辑回归": LogisticRegression(max_iter=200, random_state=42), # max_iter:增加迭代次数确保收敛"随机森林": RandomForestClassifier(n_estimators=100, random_state=42),"SVM": SVC(kernel='rbf', random_state=42) # kernel='rbf':径向基核,适合非线性数据
}# 2. 训练模型并预测
results = {}
for name, model in models.items():# 训练模型model.fit(X_train_scaled, y_train)# 预测测试集y_pred = model.predict(X_test_scaled)# 计算准确率accuracy = accuracy_score(y_test, y_pred)# 保存结果results[name] = {"model": model,"y_pred": y_pred,"accuracy": accuracy}# 输出模型性能print(f"\n=== {name} ===")print(f"准确率:{accuracy:.4f}")print("分类报告:")print(classification_report(y_test, y_pred, target_names=iris.target_names))# 3. 可视化混淆矩阵(以随机森林为例)
plt.figure(figsize=(8, 6))
cm = confusion_matrix(y_test, results["随机森林"]["y_pred"])
sns.heatmap(cm, annot=True, # 显示数值fmt='d', # 数值格式(整数)cmap='Blues',xticklabels=iris.target_names,yticklabels=iris.target_names
)
plt.xlabel('预测类别')
plt.ylabel('真实类别')
plt.title('随机森林模型混淆矩阵')
plt.show()
4.1.3 结果分析
- 鸢尾花数据集简单,三种模型准确率均接近 100%(随机森林和 SVM 通常能达到 100%);
- 混淆矩阵显示:随机森林无错分样本,所有预测均正确;
- 分类报告中的
precision
(精确率)、recall
(召回率)、f1-score
(调和平均)可评估模型在每个类别上的性能,适合不平衡数据。
4.2 回归任务:预测连续值(如 “房价”“温度”)
回归任务的目标是预测连续数值,本节以加州房价预测为例,实战线性回归、随机森林回归、梯度提升回归,并通过 RMSE(均方根误差)评估性能。
4.2.1 数据准备与模型训练
python
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
import numpy as np
import matplotlib.pyplot as plt# 1. 加载数据
housing = fetch_california_housing()
X = housing.data
y = housing.target # 目标:房价(10万美元)# 2. 划分训练集/测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42
)# 3. 特征缩放(线性回归需要,树模型不需要)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)# 4. 定义回归模型
reg_models = {"线性回归": LinearRegression(),"随机森林回归": RandomForestRegressor(n_estimators=100, random_state=42),"梯度提升回归": GradientBoostingRegressor(n_estimators=100, random_state=42)
}# 5. 训练模型并评估
reg_results = {}
for name, model in reg_models.items():# 训练模型(线性回归用缩放后的数据,树模型用原始数据)if name == "线性回归":model.fit(X_train_scaled, y_train)y_pred = model.predict(X_test_scaled)else:model.fit(X_train, y_train)y_pred = model.predict(X_test)# 计算评估指标mae = mean_absolute_error(y_test, y_pred) # 平均绝对误差mse = mean_squared_error(y_test, y_pred) # 均方误差rmse = np.sqrt(mse) # 均方根误差(与目标同量纲,更直观)r2 = r2_score(y_test, y_pred) # 决定系数(1表示完美预测)# 保存结果reg_results[name] = {"y_pred": y_pred,"mae": mae,"rmse": rmse,"r2": r2}# 输出结果print(f"\n=== {name} ===")print(f"MAE(平均绝对误差):{mae:.4f}")print(f"RMSE(均方根误差):{rmse:.4f}")print(f"R²(决定系数):{r2:.4f}")# 6. 可视化预测值与真实值(以梯度提升回归为例)
plt.figure(figsize=(10, 6))
# 取前200个样本(避免点太多重叠)
sample_idx = np.arange(200)
plt.scatter(sample_idx, y_test[sample_idx], label='真实房价', alpha=0.6)
plt.scatter(sample_idx, reg_results["梯度提升回归"]["y_pred"][sample_idx], label='预测房价', alpha=0.6, color='orange')
plt.xlabel('样本索引')
plt.ylabel('房价(10万美元)')
plt.title('梯度提升回归:真实房价 vs 预测房价(前200个样本)')
plt.legend()
plt.show()# 7. 可视化特征重要性(以随机森林回归为例)
rf_model = reg_models["随机森林回归"]
feature_importance = pd.DataFrame({'feature': housing.feature_names,'importance': rf_model.feature_importances_
}).sort_values('importance', ascending=False)plt.figure(figsize=(10, 6))
sns.barplot(x='importance', y='feature', data=feature_importance)
plt.xlabel('特征重要性')
plt.ylabel('特征')
plt.title('随机森林回归:特征重要性排序')
plt.show()
4.2.2 结果分析
- 线性回归性能最差(R²≈0.57),因为房价与特征的关系是非线性的;
- 随机森林和梯度提升回归性能更好(R²≈0.80),树模型能捕捉非线性关系;
- 特征重要性显示:
MedInc
(平均收入)是影响房价的最关键特征,符合常识。
4.3 聚类任务:无监督分组(如 “用户分群”)
聚类是无监督学习任务,无需标签,通过样本相似性将其分组。本节以鸢尾花数据集聚类为例,实战 K-Means 和 DBSCAN 两种经典算法,并通过轮廓系数评估聚类质量。
4.3.1 数据准备与聚类实战
python
from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans, DBSCAN
from sklearn.metrics import silhouette_score # 轮廓系数:越接近1聚类效果越好
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA # 降维可视化(4维→2维)# 1. 加载数据(无监督学习,不使用标签y)
iris = load_iris()
X = iris.data
y_true = iris.target # 仅用于对比聚类结果,不参与训练# 2. 特征缩放(聚类对尺度敏感,必须做)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)# 3. 方法1:K-Means聚类(需指定聚类数n_clusters)
# 先通过肘部法则选择最优n_clusters
inertia = [] # 簇内平方和:越小聚类越紧凑
k_range = range(2, 10)
for k in k_range:kmeans = KMeans(n_clusters=k, random_state=42, n_init=10) # n_init=10:多次初始化避免局部最优kmeans.fit(X_scaled)inertia.append(kmeans.inertia_)# 可视化肘部法则
plt.figure(figsize=(8, 4))
plt.plot(k_range, inertia, 'bo-')
plt.xlabel('聚类数k')
plt.ylabel('簇内平方和(Inertia)')
plt.title('K-Means肘部法则:选择最优k')
plt.grid(True)
plt.show() # 从图中可见k=3时惯性下降明显减缓(肘部)# 用最优k=3训练K-Means
kmeans = KMeans(n_clusters=3, random_state=42, n_init=10)
y_kmeans = kmeans.fit_predict(X_scaled)# 4. 方法2:DBSCAN聚类(无需指定聚类数,基于密度)
# 调整eps(邻域半径)和min_samples(核心点最小样本数)
dbscan = DBSCAN(eps=0.5, min_samples=5) # 需通过经验或网格搜索调参
y_dbscan = dbscan.fit_predict(X_scaled) # -1表示噪声点# 5. 评估聚类质量(轮廓系数)
sil_score_kmeans = silhouette_score(X_scaled, y_kmeans)
# DBSCAN需排除噪声点计算轮廓系数
if len(set(y_dbscan)) > 1: # 确保有至少2个簇sil_score_dbscan = silhouette_score(X_scaled[y_dbscan != -1], y_dbscan[y_dbscan != -1])
else:sil_score_dbscan = -1 # 无有效聚类print(f"K-Means轮廓系数:{sil_score_kmeans:.4f}") # 约0.45(中等聚类效果)
print(f"DBSCAN轮廓系数:{sil_score_dbscan:.4f}") # 约0.35(效果略差,因存在噪声点)
print(f"DBSCAN噪声点数量:{sum(y_dbscan == -1)}") # 约1个噪声点# 6. 降维可视化聚类结果(用PCA将4维→2维)
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled)# 可视化K-Means结果
plt.figure(figsize=(12, 5))plt.subplot(1, 2, 1)
plt.scatter(X_pca[:, 0], X_pca[:, 1], c=y_kmeans, cmap='viridis', s=50, alpha=0.8)
plt.scatter(kmeans.cluster_centers_[:, 0], # 聚类中心的PCA坐标kmeans.cluster_centers_[:, 1],c='red', marker='x', s=200, label='聚类中心'
)
plt.xlabel('PCA维度1')
plt.ylabel('PCA维度2')
plt.title(f'K-Means聚类结果(k=3,轮廓系数={sil_score_kmeans:.4f})')
plt.legend()# 可视化DBSCAN结果
plt.subplot(1, 2, 2)
# 噪声点用黑色表示
plt.scatter(X_pca[y_dbscan == -1, 0], X_pca[y_dbscan == -1, 1], c='black', s=50, label='噪声点', alpha=0.5)
# 其他簇用不同颜色
unique_clusters = [c for c in set(y_dbscan) if c != -1]
colors = plt.cm.viridis(np.linspace(0, 1, len(unique_clusters)))
for cluster, color in zip(unique_clusters, colors):mask = y_dbscan == clusterplt.scatter(X_pca[mask, 0], X_pca[mask, 1], c=[color], s=50, alpha=0.8, label=f'簇{cluster}')
plt.xlabel('PCA维度1')
plt.ylabel('PCA维度2')
plt.title(f'DBSCAN聚类结果(轮廓系数={sil_score_dbscan:.4f})')
plt.legend()plt.tight_layout()
plt.show()
4.3.2 结果分析
- K-Means 需指定聚类数 k,通过 “肘部法则” 或轮廓系数选择最优 k;本例中 k=3 与真实类别(鸢尾花 3 个物种)一致,聚类效果好;
- DBSCAN 无需指定 k,但需调参
eps
和min_samples
:eps
过小会产生大量噪声点,过大则簇会合并; - 轮廓系数:K-Means(0.45)优于 DBSCAN(0.35),说明 K-Means 在鸢尾花数据集上更适合。
第五章:模型调优与部署 —— 从 “可用” 到 “最优”
训练出基础模型后,需通过调优提升性能,并将最优模型部署到生产环境。本节将介绍 sklearn 的模型调优工具(网格搜索、随机搜索)和模型保存方法。
5.1 模型调优:寻找最优参数
模型性能很大程度上依赖参数选择(如随机森林的n_estimators
、SVM 的C
和gamma
)。sklearn 的model_selection
模块提供网格搜索(GridSearchCV) 和随机搜索(RandomizedSearchCV) 两种自动调参工具。
案例:用网格搜索调优随机森林分类器
python
from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split, GridSearchCV, cross_val_score
from sklearn.metrics import accuracy_score# 1. 加载数据并划分训练集/测试集
iris = load_iris()
X = iris.data
y = iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y
)# 2. 定义待调优的参数网格
param_grid = {'n_estimators': [50, 100, 200], # 决策树数量'max_depth': [None, 10, 20, 30], # 树的最大深度(None表示不限制)'min_samples_split': [2, 5, 10], # 分裂节点所需最小样本数'min_samples_leaf': [1, 2, 4] # 叶子节点最小样本数
}# 3. 初始化网格搜索
# GridSearchCV:穷举所有参数组合,用5折交叉验证评估
grid_search = GridSearchCV(estimator=RandomForestClassifier(random_state=42),param_grid=param_grid,cv=5, # 5折交叉验证scoring='accuracy', # 评估指标(分类用accuracy)n_jobs=-1 # 用所有CPU核心加速计算
)# 4. 执行网格搜索(训练所有参数组合)
grid_search.fit(X_train, y_train)# 5. 查看调优结果
print("=== 网格搜索结果 ===")
print(f"最优参数组合:{grid_search.best_params_}") # 如{'max_depth': None, 'min_samples_leaf': 1, 'min_samples_split': 2, 'n_estimators': 50}
print(f"最优交叉验证得分:{grid_search.best_score_:.4f}") # 约0.98
print(f"最优模型:{grid_search.best_estimator_}")# 6. 用最优模型评估测试集
best_model = grid_search.best_estimator_
y_pred = best_model.predict(X_test)
test_accuracy = accuracy_score(y_test, y_pred)
print(f"\n最优模型测试集准确率:{test_accuracy:.4f}") # 约1.0
关键说明:
- 网格搜索(GridSearchCV):穷举所有参数组合,适合参数少的场景(如 3 个参数,每个 3 个取值,共 3³=27 种组合);
- 随机搜索(RandomizedSearchCV):随机采样参数组合,适合参数多的场景(如 5 个参数,每个 10 个取值,采样 100 种组合),效率更高;
- 交叉验证(cv=5):将训练集分为 5 份,每次用 4 份训练、1 份验证,避免单次划分的偶然性,更客观评估模型性能。
5.2 模型保存与加载:部署到生产环境
训练好的模型需保存为文件,以便在生产环境中加载使用。sklearn 推荐用joblib
(适合大模型,如随机森林)或pickle
(适合小模型)保存。
案例:保存与加载最优模型
python
import joblib
import pickle
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris# 1. 训练一个简单模型(或使用上节的最优模型)
iris = load_iris()
X, y = iris.data, iris.target
model = RandomForestClassifier(n_estimators=50, random_state=42)
model.fit(X, y)# 2. 方法1:用joblib保存(推荐)
joblib.dump(model, 'random_forest_iris.joblib')
print("模型已用joblib保存")# 加载joblib模型
loaded_model_joblib = joblib.load('random_forest_iris.joblib')
print(f"joblib加载模型预测结果:{loaded_model_joblib.predict(X[:5])}") # 输出[0 0 0 0 0]# 3. 方法2:用pickle保存
with open('random_forest_iris.pkl', 'wb') as f:pickle.dump(model, f)
print("\n模型已用pickle保存")# 加载pickle模型
with open('random_forest_iris.pkl', 'rb') as f:loaded_model_pickle = pickle.load(f)
print(f"pickle加载模型预测结果:{loaded_model_pickle.predict(X[:5])}") # 输出[0 0 0 0 0]
关键提醒:
joblib
比pickle
更高效,尤其对于包含大量 NumPy 数组的模型(如随机森林、梯度提升树);- 保存模型时,建议同时保存预处理工具(如
StandardScaler
、OneHotEncoder
),避免部署时预处理规则与训练时不一致; - 生产环境中,可将模型封装为 API(如用 Flask/FastAPI),提供 HTTP 接口供业务系统调用。
第六章:综合实战 —— 泰坦尼克号生存预测
为串联前文所有知识点,本节将完成一个完整的机器学习项目:泰坦尼克号乘客生存预测,涵盖数据探索、预处理、特征工程、模型训练、调优与评估全流程。
6.1 项目流程总览
- 数据加载与探索性分析(EDA);
- 数据预处理(缺失值处理、分类变量编码);
- 特征工程(提取新特征,如 “家庭人数”“姓名头衔”);
- 模型训练(对比多个分类模型);
- 模型调优(网格搜索);
- 模型评估与结果分析。
6.2 完整代码实现
1. 数据加载与 EDA
python
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np# 1. 加载数据
titanic = sns.load_dataset('titanic')
print("数据集基本信息:")
print(f"形状:{titanic.shape}") # (891, 15)
print("\n数据类型:")
print(titanic.dtypes)
print("\n缺失值统计:")
print(titanic.isnull().sum())# 2. 探索目标变量(生存情况:0=死亡,1=生存)
survival_count = titanic['survived'].value_counts()
print(f"\n生存情况分布:")
print(f"死亡人数:{survival_count[0]}({survival_count[0]/len(titanic)*100:.1f}%)")
print(f"生存人数:{survival_count[1]}({survival_count[1]/len(titanic)*100:.1f}%)")# 3. 可视化关键特征与生存的关系
# 3.1 性别与生存
plt.figure(figsize=(12, 10))plt.subplot(2, 2, 1)
sns.countplot(x='sex', hue='survived', data=titanic)
plt.title('性别与生存关系(0=死亡,1=生存)')
plt.xlabel('性别')
plt.ylabel('人数')# 3.2 舱位与生存
plt.subplot(2, 2, 2)
sns.countplot(x='pclass', hue='survived', data=titanic)
plt.title('舱位与生存关系(1=一等舱,2=二等舱,3=三等舱)')
plt.xlabel('舱位等级')
plt.ylabel('人数')# 3.3 年龄与生存(用直方图)
plt.subplot(2, 2, 3)
sns.histplot(data=titanic, x='age', hue='survived', multiple='dodge', bins=10)
plt.title('年龄与生存关系')
plt.xlabel('年龄')
plt.ylabel('人数')# 3.4 家庭人数与生存(SibSp=兄弟姐妹/配偶数,Parch=父母/子女数)
titanic['family_size'] = titanic['sibsp'] + titanic['parch'] + 1 # +1表示自己
plt.subplot(2, 2, 4)
sns.countplot(x='family_size', hue='survived', data=titanic)
plt.title('家庭人数与生存关系')
plt.xlabel('家庭人数')
plt.ylabel('人数')plt.tight_layout()
plt.show()
2. 数据预处理与特征工程
python
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer# 1. 提取目标变量和特征
y = titanic['survived']
X = titanic.drop(['survived', 'name', 'ticket', 'cabin', 'boat', 'body', 'home_dest'], axis=1) # 删除无关特征# 2. 特征工程:提取新特征
# 2.1 从姓名中提取头衔(如Mr.、Mrs.、Miss.)——前文未删除name,此处临时使用
titanic['title'] = titanic['name'].str.extract(' ([A-Za-z]+)\.', expand=False)
# 合并稀有头衔
rare_titles = ['Dona', 'Lady', 'Countess', 'Capt', 'Col', 'Don', 'Dr', 'Major', 'Rev', 'Sir', 'Jonkheer']
titanic['title'] = titanic['title'].replace(rare_titles, 'Rare')
titanic['title'] = titanic['title'].replace('Mlle', 'Miss')
titanic['title'] = titanic['title'].replace('Ms', 'Miss')
titanic['title'] = titanic['title'].replace('Mme', 'Mrs')
# 将头衔加入特征
X['title'] = titanic['title']# 2.2 家庭人数分组(小家庭:1-2人,中家庭:3-4人,大家庭:≥5人)
X['family_size'] = titanic['family_size']
X['family_group'] = pd.cut(X['family_size'], bins=[0, 2, 4, 100], labels=['Small', 'Medium', 'Large'])# 3. 定义特征类型
# 数值特征:age, fare, sibsp, parch, family_size
numeric_features = ['age', 'fare', 'sibsp', 'parch', 'family_size']
# 有序分类特征:pclass(1>2>3)
ordinal_features = ['pclass']
# 无序分类特征:sex, embarked, title, family_group
nominal_features = ['sex', 'embarked', 'title', 'family_group']# 4. 定义预处理管道
# 数值特征预处理:缺失值(中位数)+ 标准化
numeric_transformer = Pipeline(steps=[('imputer', SimpleImputer(strategy='median')),('scaler', StandardScaler())
])# 有序分类特征预处理:缺失值(众数)+ 标签编码
ordinal_transformer = Pipeline(steps=[('imputer', SimpleImputer(strategy='most_frequent')),('label', LabelEncoder())
])# 无序分类特征预处理:缺失值(众数)+ 独热编码
nominal_transformer = Pipeline(steps=[('imputer', SimpleImputer(strategy='most_frequent')),('onehot', OneHotEncoder(drop='first', sparse_output=False))
])# 批量应用预处理
preprocessor = ColumnTransformer(transformers=[('num', numeric_transformer, numeric_features),('ord', ordinal_transformer, ordinal_features),('nom', nominal_transformer, nominal_features)])# 5. 划分训练集/测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y
)# 6. 执行预处理(训练集fit+transform,测试集仅transform)
X_train_processed = preprocessor.fit_transform(X_train)
X_test_processed = preprocessor.transform(X_test)print(f"预处理后训练集形状:{X_train_processed.shape}") # (712, 14)
print(f"预处理后测试集形状:{X_test_processed.shape}") # (179, 14)
3. 模型训练与对比
python
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.svm import SVC
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix# 1. 定义模型列表
models = {"逻辑回归": LogisticRegression(max_iter=500, random_state=42),"随机森林": RandomForestClassifier(random_state=42),"梯度提升": GradientBoostingClassifier(random_state=42),"SVM": SVC(random_state=42)
}# 2. 训练并评估所有模型
model_results = {}
for name, model in models.items():# 训练模型model.fit(X_train_processed, y_train)# 预测y_pred = model.predict(X_test_processed)# 计算准确率accuracy = accuracy_score(y_test, y_pred)# 保存结果model_results[name] = {"model": model,"y_pred": y_pred,"accuracy": accuracy}# 输出报告print(f"\n=== {name} ===")print(f"准确率:{accuracy:.4f}")print("分类报告:")print(classification_report(y_test, y_pred))# 3. 选择准确率最高的模型(假设是梯度提升)
best_model_name = max(model_results.keys(), key=lambda x: model_results[x]['accuracy'])
best_model = model_results[best_model_name]['model']
print(f"\n最优模型:{best_model_name}(准确率:{model_results[best_model_name]['accuracy']:.4f})")
4. 模型调优与最终评估
python
from sklearn.model_selection import GridSearchCV# 1. 定义梯度提升模型的参数网格
param_grid_gb = {'n_estimators': [100, 200, 300],'max_depth': [3, 5, 7],'learning_rate': [0.01, 0.1, 0.2],'min_samples_split': [2, 5]
}# 2. 初始化网格搜索
grid_search = GridSearchCV(estimator=GradientBoostingClassifier(random_state=42),param_grid=param_grid_gb,cv=5,scoring='accuracy',n_jobs=-1
)# 3. 执行网格搜索
grid_search.fit(X_train_processed, y_train)# 4. 查看调优结果
print("=== 梯度提升模型调优结果 ===")
print(f"最优参数:{grid_search.best_params_}")
print(f"最优交叉验证准确率:{grid_search.best_score_:.4f}")# 5. 用最优模型评估测试集
best_gb = grid_search.best_estimator_
y_pred_best = best_gb.predict(X_test_processed)
test_accuracy = accuracy_score(y_test, y_pred_best)
print(f"\n最优模型测试集准确率:{test_accuracy:.4f}")# 6. 可视化混淆矩阵
plt.figure(figsize=(8, 6))
cm = confusion_matrix(y_test, y_pred_best)
sns.heatmap(cm,annot=True,fmt='d',cmap='Blues',xticklabels=['死亡', '生存'],yticklabels=['死亡', '生存']
)
plt.xlabel('预测结果')
plt.ylabel('真实结果')
plt.title(f'泰坦尼克号生存预测混淆矩阵(准确率:{test_accuracy:.4f})')
plt.show()# 7. 保存最优模型
joblib.dump(best_gb, 'titanic_survival_model.joblib')
joblib.dump(preprocessor, 'titanic_preprocessor.joblib')
print("\n最优模型和预处理工具已保存")
6.3 项目总结
- 数据洞察:泰坦尼克号生存与 “性别”(女性生存率高)、“舱位”(一等舱生存率高)、“家庭人数”(小家庭生存率高)强相关;
- 特征工程:提取 “头衔”“家庭分组” 等新特征,显著提升模型性能;
- 模型性能:梯度提升模型经调优后测试集准确率约 85%,是本次项目的最优模型;
- 部署准备:已保存模型和预处理工具,可直接封装为 API 供业务使用。
第七章:学习资源与进阶建议
sklearn 的功能远不止本文所覆盖,若想深入学习,可参考以下资源:
- 官方文档:Scikit-learn 官方指南—— 最权威、最全面的学习资料,包含算法原理、API 细节和案例;
- 书籍:《Python 机器学习》(Andreas Müller 著,sklearn 核心开发者之一)、《机器学习实战》;
- 课程:Coursera《Machine Learning》(Andrew Ng)、Kaggle 竞赛教程(通过实战提升);
- 社区:Kaggle(查看顶级选手的 sklearn 代码)、Stack Overflow(解决具体问题)。
进阶建议:
- 理解算法原理:sklearn 封装了细节,但深入理解算法(如随机森林的袋外误差、SVM 的核函数)能帮助你更好地调参;
- 处理复杂数据:学习用 sklearn 处理文本数据(
CountVectorizer
、TfidfVectorizer
)、图像数据(需结合 OpenCV 或 PIL); - 集成学习:探索更高级的集成方法(如 XGBoost、LightGBM,虽非 sklearn 原生,但可与 sklearn 兼容);
- 生产部署:学习用 Flask/FastAPI 封装模型为 API,用 Docker 容器化部署,确保模型在生产环境稳定运行。