5
Scikit-learn:机器学习瑞士军刀
Scikit-learn ML Pipeline
Scikit-learn 的精髓是统一 API:不管你用线性回归还是随机森林,都是 model.fit(X, y) 训练、model.predict(X) 预测。学会一个,就会用所有。这章把机器学习的标准流程走一遍。
机器学习标准流程
论六步走
① 拿数据 → ② 预处理(标准化、编码分类变量)→ ③ 切分训练集/测试集 → ④ 选模型训练 → ⑤ 评估 → ⑥ 预测新数据。这套流程适用于所有经典机器学习任务,闭着眼背下来。
预处理与切分
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
# X 是特征矩阵(每行一个样本,每列一个特征),y 是标签
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 80% 训练,20% 测试;random_state 保证每次切分一样
# 标准化:把特征缩到均值0、方差1
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test) # 测试集用训练集的 scaler,别重新 fit!
监督学习:分类与回归
| 模型 | 干什么 | 什么时候用 |
| LinearRegression | 回归(预测连续值) | 房价预测、销量预测。 |
| LogisticRegression | 分类(虽然名字叫回归) | 二分类:垃圾邮件/正常邮件。 |
| DecisionTree | 决策树,可解释 | 需要"为什么这么判"的场景。 |
| RandomForest | 随机森林,一堆树投票 | 表格数据上的强力 baseline。 |
| SVM | 支持向量机 | 小数据集、高维。 |
| KNN | K 近邻,看邻居是谁 | 简单 baseline,速度慢。 |
| 朴素贝叶斯 | 基于概率,文本分类快 | 垃圾邮件、文本分类。 |
KNN 和朴素贝叶斯的最小例子
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB
# KNN:K=3,看最近的 3 个邻居投票
knn = KNeighborsClassifier(n_neighbors=3)
knn.fit(X_train, y_train)
print("KNN 准确率:", knn.score(X_test, y_test))
# 朴素贝叶斯:假设特征独立,快到飞起
nb = GaussianNB()
nb.fit(X_train, y_train)
print("贝叶斯准确率:", nb.score(X_test, y_test))
# 逻辑回归:名字叫回归,其实是分类
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression(max_iter=1000)
lr.fit(X_train_scaled, y_train)
print("逻辑回归准确率:", lr.score(X_test_scaled, y_test))
# max_iter 加大是因为数据多了默认迭代次数不够
# SVM:小数据集、高维数据上表现好
from sklearn.svm import SVC
svm = SVC(kernel="rbf", C=1.0, random_state=42)
svm.fit(X_train_scaled, y_train)
print("SVM 准确率:", svm.score(X_test_scaled, y_test))
# 注意:SVM 对量纲敏感,必须先标准化!
完整例子:鸢尾花分类
from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, confusion_matrix
# 1. 拿数据(sklearn 自带鸢尾花数据集)
iris = load_iris()
X, y = iris.data, iris.target
# 2. 切分
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 3. 训练随机森林
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
# 4. 评估
pred = model.predict(X_test)
print("准确率:", accuracy_score(y_test, pred))
# 准确率:1.0(这个数据集太简单了)
print("混淆矩阵:")
print(confusion_matrix(y_test, pred))
# 最常用的一张表:precision/recall/F1 一把全给
from sklearn.metrics import classification_report
print(classification_report(y_test, pred,
target_names=iris.target_names))
# 5. 看哪个特征最重要
for name, imp in zip(iris.feature_names, model.feature_importances_):
print(f"{name}: {imp:.3f}")
预处理:编码分类变量
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
# 标签编码:把"红/绿/蓝"变成 0/1/2(有序类别用)
le = LabelEncoder()
y = le.fit_transform(["猫", "狗", "猫", "鸟"])
# 反向:le.inverse_transform([0,1,2])
# One-Hot:把城市列变成"北京/上海/广州"三列 0/1(无序类别用)
# 树模型不用 One-Hot,线性/SVM 必须用
ohe = OneHotEncoder(sparse_output=False)
city_encoded = ohe.fit_transform(df[["城市"]])
交叉验证与网格搜索
from sklearn.model_selection import cross_val_score, GridSearchCV
# 交叉验证:把数据切 5 份,轮流当训练/验证,更可靠
scores = cross_val_score(model, X_train, y_train, cv=5, scoring="accuracy")
print("5折平均分:", scores.mean())
# 网格搜索:自动试一堆参数组合
param_grid = {
"n_estimators": [50, 100, 200],
"max_depth": [3, 5, None]
}
grid = GridSearchCV(
RandomForestClassifier(random_state=42),
param_grid, cv=5, scoring="accuracy"
)
grid.fit(X_train, y_train)
print("最优参数:", grid.best_params_)
print("最优分数:", grid.best_score_)
best_model = grid.best_estimator_
实战:房价预测(回归任务)
from sklearn.datasets import fetch_california_housing
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score
house = fetch_california_housing()
X, y = house.data, house.target
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 标准化(线性模型对量纲敏感)
scaler = StandardScaler()
X_train_s = scaler.fit_transform(X_train)
X_test_s = scaler.transform(X_test)
# 训练线性回归
reg = LinearRegression()
reg.fit(X_train_s, y_train)
pred = reg.predict(X_test_s)
# 回归指标:MSE 越小越好,R² 越接近 1 越好
print("MSE:", mean_squared_error(y_test, pred))
print("R²:", r2_score(y_test, pred))
模型评估指标
| 指标 | 什么时候看 |
| accuracy 准确率 | 类别均衡时用;不均衡时会骗人。 |
| precision 精确率 | 预测为正的里面,真的有多少正。 |
| recall 召回率 | 所有真的正例,被你找出来多少。 |
| F1 | precision 和 recall 的调和平均,两者都要。 |
| ROC-AUC | 二分类综合指标,0.5 是瞎猜,1.0 完美。 |
| 交叉验证 | cross_val_score:把数据切多份轮流当训练/测试,更稳。 |
| GridSearchCV | 自动试一堆参数组合,找最优。 |
一句话记忆:看病(漏诊代价高)看重 recall,抓坏人(误伤代价高)看重 precision。
看模型:特征重要性
树模型(随机森林、XGBoost)能告诉你"哪个特征最管用"——这在业务上很值钱,比单纯一个准确率数字有用得多。
# 训练完后画个条形图
import pandas as pd
import matplotlib.pyplot as plt
importance = pd.Series(model.feature_importances_,
index=iris.feature_names)
importance.sort_values().plot(kind="barh")
plt.title("特征重要性")
plt.show()
# 一眼看出"花瓣长度"对鸢尾花分类最关键
无监督:K-Means 聚类 & PCA
from sklearn.cluster import KMeans
from sklearn.decomposition import PCA
# K-Means:把数据自动分成 K 组(无标签)
kmeans = KMeans(n_clusters=3, random_state=42, n_init=10)
labels = kmeans.fit_predict(X)
# PCA:降维,把高维数据压成 2 维方便画图
pca = PCA(n_components=2)
X_2d = pca.fit_transform(X)
print("保留的方差比例:", pca.explained_variance_ratio_)
机器学习流水线:Pipeline + ColumnTransformer
论为什么要 Pipeline
手动调预处理步骤很容易搞混顺序、导致数据泄漏。Pipeline 把"预处理 + 模型"打包成一个对象,fit 时按顺序跑,predict 时也按顺序跑,杜绝泄漏。ColumnTransformer 让你对不同列做不同预处理(数值列标准化、类别列 One-Hot)。
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.ensemble import RandomForestClassifier
# 假设 df 有数值列 [年龄, 收入] 和类别列 [城市, 性别]
numeric_features = ["年龄", "收入"]
categorical_features = ["城市", "性别"]
# 数值列:先填缺失值,再标准化
numeric_transformer = Pipeline(steps=[
("imputer", SimpleImputer(strategy="median")),
("scaler", StandardScaler())
])
# 类别列:填缺失值,再 One-Hot
categorical_transformer = Pipeline(steps=[
("imputer", SimpleImputer(strategy="most_frequent")),
("onehot", OneHotEncoder(handle_unknown="ignore"))
])
# 把两个预处理拼成一个"列转换器"
preprocessor = ColumnTransformer(
transformers=[
("num", numeric_transformer, numeric_features),
("cat", categorical_transformer, categorical_features)
])
# 完整流水线:预处理 → 分类器
clf = Pipeline(steps=[
("preprocessor", preprocessor),
("classifier", RandomForestClassifier(n_estimators=100, random_state=42))
])
clf.fit(X_train, y_train)
print("准确率:", clf.score(X_test, y_test))
# 新数据来时直接 predict,预处理自动跟上
clf.predict(X_new)
交叉验证:StratifiedKFold / TimeSeriesSplit
from sklearn.model_selection import (
StratifiedKFold, TimeSeriesSplit, cross_val_score
)
# 1. 分层 K 折:分类任务标配(每折类别比例一致)
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(clf, X, y, cv=skf, scoring="accuracy")
print("每折分数:", scores)
# [0.93 0.91 0.94 0.92 0.90]
print("平均:", scores.mean(), "标准差:", scores.std())
# 平均:0.92 标准差:0.014
# 2. 时间序列切分:不能随机打乱(未来数据不能当过去用)
tscv = TimeSeriesSplit(n_splits=5)
for fold, (train_idx, test_idx) in enumerate(tscv.split(X)):
print(f"Fold {fold}: train={len(train_idx)}, test={len(test_idx)}")
# Fold 0: train=100, test=100
# Fold 1: train=200, test=100
# ...
超参数搜索:GridSearchCV / RandomizedSearchCV
from sklearn.model_selection import GridSearchCV, RandomizedSearchCV
from scipy.stats import randint
# 1. 网格搜索:穷举所有组合(小范围用)
param_grid = {
"classifier__n_estimators": [50, 100, 200],
"classifier__max_depth": [3, 5, None],
"classifier__min_samples_leaf": [1, 2, 4]
}
grid = GridSearchCV(clf, param_grid, cv=5, scoring="f1", n_jobs=-1)
grid.fit(X_train, y_train)
print("最优参数:", grid.best_params_)
print("最优分数:", grid.best_score_)
best_model = grid.best_estimator_
# 2. 随机搜索:参数空间大时更高效
param_dist = {
"classifier__n_estimators": randint(50, 500),
"classifier__max_depth": [3, 5, 10, None],
"classifier__min_samples_split": randint(2, 20)
}
random_search = RandomizedSearchCV(
clf, param_dist, n_iter=50, cv=5,
scoring="roc_auc", random_state=42, n_jobs=-1
)
random_search.fit(X_train, y_train)
特征工程全套路
| 操作 | 什么时候用 | 代码 |
| 标准化 StandardScaler | 线性模型、SVM、神经网络(量纲敏感) | (x - mean) / std |
| 归一化 MinMaxScaler | 希望数据压缩到 [0,1](如图像像素) | (x - min) / (max - min) |
| 鲁棒缩放 RobustScaler | 有异常值时(用中位数和 IQR) | (x - median) / IQR |
| One-Hot 编码 | 无序类别(城市、血型) | OneHotEncoder(sparse_output=False) |
| 标签编码 | 有序类别(学历、评分) | LabelEncoder() |
| 目标编码 | 类别基数高(比如用户 ID) | TargetEncoder(sklearn 1.3+) |
| 多项式特征 | 线性模型想拟合非线性关系 | PolynomialFeatures(degree=2) |
| 方差过滤 | 去掉不变特征 | VarianceThreshold(threshold=0.01) |
| 互信息 / SelectKBest | 挑和标签最相关的 K 个特征 | SelectKBest(mutual_info_classif, k=20) |
Scikit-learn 章节面试题
面试 · Scikit-learn
Q1. 过拟合和欠拟合怎么判断?分别怎么解决?
查看答案
过拟合:训练集好、测试集差(高方差)——加数据、简化模型、加正则、Dropout、早停。欠拟合:训练集和测试集都差(高偏差)——换更复杂模型、加特征、减少正则。
Q2. 偏差-方差权衡是什么?
查看答案
偏差:模型假设和真实规律的差距(欠拟合);方差:模型对训练集波动的敏感度(过拟合)。模型越复杂方差越高、偏差越低;总误差 = 偏差² + 方差 + 不可约误差,要找平衡。
Q3. 为什么分类任务用 StratifiedKFold?
查看答案
普通 KFold 随机切分,类别不平衡时某一折可能没有少数类。分层 K 折保证每折的类别比例和整体一致,评估更可靠。
Q4. 什么时候用标准化,什么时候不用?
查看答案
线性模型、SVM、KNN、神经网络对量纲敏感,必须标准化;树模型(决策树、随机森林、XGBoost)不敏感,可以不标准化。
Q5. Pipeline 的好处?
查看答案
① 防止数据泄漏(预处理只 fit 在训练折上);② 代码简洁;③ 和 GridSearchCV 配合时超参数搜索自动包含预处理步骤;④ 部署时一个对象端到端。