跳到主要内容

分类算法

分类是监督学习中最常见的任务之一,目标是根据输入特征预测离散的类别标签。从垃圾邮件检测到疾病诊断,从图像识别到情感分析,分类算法在实际应用中无处不在。

本章将详细介绍 sklearn 中常用的分类算法,包括它们的原理、适用场景以及实际应用技巧。

分类问题基础

二分类与多分类

二分类(Binary Classification) 只有两个类别,如"是/否"、"正/负"。典型的例子包括:

  • 垃圾邮件检测:是垃圾邮件 / 不是垃圾邮件
  • 欺诈检测:是欺诈 / 不是欺诈
  • 疾病诊断:患病 / 健康

多分类(Multiclass Classification) 有三个或更多类别,如"猫/狗/鸟"、"优秀/良好/及格/不及格"。典型的例子包括:

  • 手写数字识别:0-9 共 10 个类别
  • 鸢尾花分类:山鸢尾、变色鸢尾、维吉尼亚鸢尾
  • 新闻分类:体育、财经、娱乐、科技等

sklearn 中的分类器大多原生支持多分类,或者通过一对多(One-vs-Rest)等策略自动处理多分类问题。

分类问题的核心概念

在深入算法之前,先理解几个关键概念:

决策边界(Decision Boundary) 是分类器划分不同类别区域的边界。线性分类器产生线性边界(如直线、平面),而非线性分类器可以产生复杂的边界。

概率输出 许多分类器不仅能预测类别,还能输出属于各类别的概率。概率输出对于需要评估预测置信度的场景很有用。

类别不平衡 当某些类别的样本数量远多于其他类别时,需要特别处理,否则模型会偏向多数类。

逻辑回归(Logistic Regression)

逻辑回归是最基础的分类算法,虽然名字带"回归",但实际上是分类算法。它简单、高效、可解释性强,是许多分类任务的首选基线模型。

原理详解

逻辑回归通过 Sigmoid 函数将线性组合映射到 0-1 之间,表示属于正类的概率:

P(y=1x)=σ(wTx+b)=11+e(wTx+b)P(y=1|x) = \sigma(w^T x + b) = \frac{1}{1 + e^{-(w^T x + b)}}

模型通过最大化似然函数来学习参数 wwbb。对于二分类问题,损失函数为交叉熵:

L=i[yilog(pi)+(1yi)log(1pi)]L = -\sum_{i} [y_i \log(p_i) + (1-y_i) \log(1-p_i)]

为什么逻辑回归是"逻辑"的?

Sigmoid 函数的形状像一个 S 形曲线,将任意实数映射到 (0, 1) 区间。当 wTx+b=0w^T x + b = 0 时,概率恰好为 0.5,这就是决策边界。正样本使 wTx+b>0w^T x + b > 0,概率大于 0.5;负样本使 wTx+b<0w^T x + b < 0,概率小于 0.5。

基本用法

from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report

# 加载数据(鸢尾花数据集有三个类别)
iris = load_iris()
X, y = iris.data, iris.target

# 划分数据集
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)

# 创建逻辑回归模型
clf = LogisticRegression(max_iter=200, random_state=42)
clf.fit(X_train, y_train)

# 预测
y_pred = clf.predict(X_test)
y_prob = clf.predict_proba(X_test) # 概率输出

print(f"准确率: {accuracy_score(y_test, y_pred):.2%}")
print("\n分类报告:")
print(classification_report(y_test, y_pred, target_names=iris.target_names))

重要参数

参数说明默认值
penalty正则化类型:'l1''l2''elasticnet'None'l2'
C正则化强度的倒数,值越小正则化越强1.0
solver优化算法:'lbfgs''liblinear''saga''lbfgs'
max_iter最大迭代次数100
multi_class多分类策略:'ovr'(一对多)、'multinomial'(多项式)'auto'

正则化参数 C 的影响

import numpy as np
from sklearn.linear_model import LogisticRegression

# C 值越小,正则化越强,模型越简单
models = {
"强正则化 (C=0.01)": LogisticRegression(C=0.01, max_iter=200),
"默认 (C=1.0)": LogisticRegression(C=1.0, max_iter=200),
"弱正则化 (C=100)": LogisticRegression(C=100, max_iter=200)
}

for name, model in models.items():
model.fit(X_train, y_train)
train_acc = model.score(X_train, y_train)
test_acc = model.score(X_test, y_test)
print(f"{name}: 训练 {train_acc:.2%}, 测试 {test_acc:.2%}")

逻辑回归的优缺点

优点

  • 计算效率高,适合大规模数据
  • 输出概率,便于评估置信度
  • 可解释性强,系数表示特征重要性
  • 可以通过正则化防止过拟合

缺点

  • 只能处理线性可分问题(除非使用核技巧)
  • 对特征相关性敏感,需要预处理
  • 对异常值敏感

决策树(Decision Tree)

决策树通过一系列规则对数据进行划分,形成树状结构。它是最直观的机器学习算法之一,决策过程易于理解和可视化。

原理详解

决策树通过递归地选择最优特征进行分裂,每次分裂将数据分成更"纯净"的子集。

信息增益与基尼不纯度

  • 基尼不纯度(Gini Impurity):衡量一个节点中样本的混乱程度

Gini=1kpk2Gini = 1 - \sum_{k} p_k^2

其中 pkp_k 是类别 kk 在该节点中的比例。基尼不纯度为 0 表示节点完全纯净(所有样本属于同一类)。

  • 信息增益(Information Gain):基于熵的分裂标准

Entropy=kpklog2(pk)Entropy = -\sum_{k} p_k \log_2(p_k)

sklearn 默认使用基尼不纯度,因为计算更快。两者效果通常相近。

决策树的构建过程

  1. 从根节点开始,包含所有样本
  2. 选择使不纯度下降最多的特征和切分点
  3. 根据切分点将数据分成子节点
  4. 对每个子节点递归执行步骤 2-3
  5. 当满足停止条件时(如节点纯净、达到最大深度),停止分裂

基本用法

from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt

iris = load_iris()
X, y = iris.data, iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 创建决策树
clf = DecisionTreeClassifier(
max_depth=3, # 最大深度,防止过拟合
min_samples_split=5, # 分裂所需最小样本数
min_samples_leaf=2, # 叶节点最小样本数
random_state=42
)
clf.fit(X_train, y_train)

print(f"训练集准确率: {clf.score(X_train, y_train):.2%}")
print(f"测试集准确率: {clf.score(X_test, y_test):.2%}")
print(f"特征重要性: {clf.feature_importances_}")

# 可视化决策树
plt.figure(figsize=(12, 8))
plot_tree(clf, feature_names=iris.feature_names,
class_names=iris.target_names, filled=True)
plt.show()

重要参数

参数说明默认值
criterion分裂标准:'gini''entropy''gini'
max_depth树的最大深度None(不限制)
min_samples_split分裂节点所需最小样本数2
min_samples_leaf叶节点最小样本数1
max_features寻找最佳分裂时考虑的特征数None(所有)

控制树的复杂度

# 不限制深度:容易过拟合
clf_overfit = DecisionTreeClassifier(random_state=42)
clf_overfit.fit(X_train, y_train)
print(f"无限制深度 - 训练: {clf_overfit.score(X_train, y_train):.2%}, "
f"测试: {clf_overfit.score(X_test, y_test):.2%}")

# 限制深度:更好的泛化
clf_pruned = DecisionTreeClassifier(max_depth=3, random_state=42)
clf_pruned.fit(X_train, y_train)
print(f"深度限制为3 - 训练: {clf_pruned.score(X_train, y_train):.2%}, "
f"测试: {clf_pruned.score(X_test, y_test):.2%}")

决策树的优缺点

优点

  • 易于理解和可视化
  • 不需要特征缩放
  • 可以处理数值和类别特征
  • 能够捕获非线性关系

缺点

  • 容易过拟合
  • 对数据变化敏感(微小的数据变化可能导致完全不同的树)
  • 对类别不平衡敏感

随机森林(Random Forest)

随机森林是集成学习的代表,通过组合多棵决策树来提高模型的泛化能力。它是实际应用中最常用的分类算法之一。

原理详解

随机森林的核心思想是集成多个弱学习器形成强学习器。它通过两种方式引入随机性:

  1. Bootstrap 采样:每棵树使用从训练集有放回抽样的子集训练
  2. 随机特征选择:每次分裂只考虑随机选择的一部分特征

这种"双重随机"使得每棵树都略有不同,最后通过投票(分类)或平均(回归)得到最终预测。

为什么随机森林比单棵决策树更好?

假设每棵树的错误率为 ϵ\epsilon,且树之间的错误是独立的。对于 nn 棵树的投票,错误率随 nn 增加而下降。虽然现实中树之间有相关性,但随机森林仍然显著降低了方差,提高了泛化能力。

基本用法

from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split, cross_val_score

iris = load_iris()
X, y = iris.data, iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 创建随机森林
clf = RandomForestClassifier(
n_estimators=100, # 决策树数量
max_depth=5, # 每棵树的最大深度
min_samples_split=5, # 分裂所需最小样本数
random_state=42,
n_jobs=-1 # 使用所有CPU核心并行训练
)
clf.fit(X_train, y_train)

print(f"训练集准确率: {clf.score(X_train, y_train):.2%}")
print(f"测试集准确率: {clf.score(X_test, y_test):.2%}")

# 特征重要性
import pandas as pd
importance = pd.DataFrame({
'feature': iris.feature_names,
'importance': clf.feature_importances_
}).sort_values('importance', ascending=False)
print("\n特征重要性:")
print(importance)

# 交叉验证
cv_scores = cross_val_score(clf, X, y, cv=5)
print(f"\n交叉验证准确率: {cv_scores.mean():.2%} (+/- {cv_scores.std()*2:.2%})")

重要参数

参数说明默认值
n_estimators决策树数量100
max_depth每棵树的最大深度None
min_samples_split分裂所需最小样本数2
min_samples_leaf叶节点最小样本数1
max_features每次分裂考虑的特征数'sqrt'
bootstrap是否使用 Bootstrap 采样True
oob_score是否使用袋外样本评估False

袋外评估(Out-of-Bag Score)

# 开启袋外评估
clf = RandomForestClassifier(
n_estimators=100,
oob_score=True,
random_state=42
)
clf.fit(X_train, y_train)

print(f"袋外评估准确率: {clf.oob_score_:.2%}")
print(f"测试集准确率: {clf.score(X_test, y_test):.2%}")

袋外评估不需要额外的验证集,利用训练时未抽中的样本进行评估。

随机森林的优缺点

优点

  • 通常不需要大量调参就能得到不错的结果
  • 能够估计特征重要性
  • 不容易过拟合
  • 支持并行训练

缺点

  • 训练和预测速度比单棵决策树慢
  • 模型较大,需要存储多棵树
  • 对于某些高维稀疏数据效果不如线性模型

梯度提升分类器(Gradient Boosting)

梯度提升是另一种强大的集成方法,与随机森林的并行集成不同,梯度提升采用串行的方式训练模型。每一棵新树都试图纠正前一棵树的错误,逐步提升模型的性能。

原理详解

梯度提升的核心思想是通过迭代地添加弱学习器来优化损失函数。每一轮迭代中,新的模型拟合当前模型的残差(即预测误差),然后通过学习率将新模型加入集成。

数学表示

mm 轮的模型可以表示为:

Fm(x)=Fm1(x)+ηhm(x)F_m(x) = F_{m-1}(x) + \eta \cdot h_m(x)

其中 Fm1(x)F_{m-1}(x) 是前一轮的预测,hm(x)h_m(x) 是新拟合的弱学习器,η\eta 是学习率。

为什么梯度提升有效?

每棵树专注于纠正之前树的错误,这使得模型能够逐步逼近最优解。通过控制学习率和树的数量,可以在偏差和方差之间找到平衡。

基本用法

from sklearn.ensemble import GradientBoostingClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split, cross_val_score

iris = load_iris()
X, y = iris.data, iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 创建梯度提升分类器
gb = GradientBoostingClassifier(
n_estimators=100, # 树的数量
learning_rate=0.1, # 学习率
max_depth=3, # 每棵树的最大深度
min_samples_split=5, # 分裂所需最小样本数
random_state=42
)
gb.fit(X_train, y_train)

print(f"训练集准确率: {gb.score(X_train, y_train):.2%}")
print(f"测试集准确率: {gb.score(X_test, y_test):.2%}")

# 查看训练过程
print(f"\n前10轮训练得分: {gb.train_score_[:10]}")
print(f"最终训练得分: {gb.train_score_[-1]:.4f}")

重要参数

参数说明默认值
n_estimators弱学习器数量100
learning_rate学习率,收缩每棵树的贡献0.1
max_depth每棵树的最大深度3
min_samples_split分裂所需最小样本数2
min_samples_leaf叶节点最小样本数1
subsample每棵树使用的样本比例1.0
max_features每次分裂考虑的特征数None

学习率与树数量的权衡

# 较低的学习率需要更多的树
gb_slow = GradientBoostingClassifier(
n_estimators=500,
learning_rate=0.01,
max_depth=3,
random_state=42
)

# 较高的学习率需要较少的树
gb_fast = GradientBoostingClassifier(
n_estimators=50,
learning_rate=0.2,
max_depth=3,
random_state=42
)

# 交叉验证比较
scores_slow = cross_val_score(gb_slow, X_train, y_train, cv=5)
scores_fast = cross_val_score(gb_fast, X_train, y_train, cv=5)

print(f"低学习率: {scores_slow.mean():.3f} (+/- {scores_slow.std()*2:.3f})")
print(f"高学习率: {scores_fast.mean():.3f} (+/- {scores_fast.std()*2:.3f})")

经验法则:较低的学习率(如 0.01-0.1)配合更多的树(如 500-1000)通常能获得更好的泛化性能,但训练时间更长。

早停策略

通过监控验证集性能,可以在最优时机停止训练:

from sklearn.model_selection import train_test_split

# 额外划分验证集
X_train_sub, X_val, y_train_sub, y_val = train_test_split(
X_train, y_train, test_size=0.2, random_state=42
)

gb = GradientBoostingClassifier(
n_estimators=500,
learning_rate=0.05,
max_depth=3,
validation_fraction=0.1,
n_iter_no_change=10, # 如果10轮没有改进则停止
tol=1e-4,
random_state=42
)
gb.fit(X_train_sub, y_train_sub)

print(f"实际使用的树数量: {gb.n_estimators_}")
print(f"验证集准确率: {gb.score(X_val, y_val):.2%}")

梯度提升 vs 随机森林

方面随机森林梯度提升
训练方式并行(每棵树独立)串行(每棵树依赖前一棵)
树的深度通常较深通常较浅
过拟合风险较低较高,需要调参
参数敏感性较低较高
性能上限中等通常更高
训练速度快(可并行)慢(必须串行)

AdaBoost

AdaBoost(Adaptive Boosting)是提升算法的开创性工作,它通过调整样本权重来训练一系列弱学习器,每个学习器重点关注之前学习器分类错误的样本。

原理详解

AdaBoost 的工作流程:

  1. 初始化所有样本权重相等
  2. 训练一个弱学习器
  3. 计算该学习器的错误率
  4. 根据错误率计算学习器的权重(错误率越低,权重越高)
  5. 更新样本权重(错误分类的样本权重增加)
  6. 重复步骤2-5,直到达到指定的学习器数量

权重更新公式

wi(m+1)=wi(m)exp(αm1[yihm(xi)])w_i^{(m+1)} = w_i^{(m)} \cdot \exp(\alpha_m \cdot \mathbb{1}[y_i \neq h_m(x_i)])

其中 αm\alpha_m 是第 mm 个学习器的权重,1\mathbb{1} 是指示函数。

基本用法

from sklearn.ensemble import AdaBoostClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

iris = load_iris()
X, y = iris.data, iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# AdaBoost 默认使用深度为1的决策树(决策树桩)
ada = AdaBoostClassifier(
estimator=DecisionTreeClassifier(max_depth=1), # 弱学习器
n_estimators=50, # 弱学习器数量
learning_rate=1.0, # 学习率
random_state=42
)
ada.fit(X_train, y_train)

print(f"测试集准确率: {ada.score(X_test, y_test):.2%}")
print(f"使用的弱学习器数量: {ada.n_estimators}")

重要参数

参数说明默认值
estimator基学习器DecisionTreeClassifier(max_depth=1)
n_estimators弱学习器数量50
learning_rate学习率1.0
algorithm算法变体:'SAMME''SAMME.R''SAMME.R'

AdaBoost 的优缺点

优点

  • 不容易过拟合
  • 自动调整样本权重
  • 对噪声相对不敏感

缺点

  • 对异常值敏感
  • 需要调节弱学习器数量和学习率
  • 只能用于分类问题(sklearn实现)

支持向量机(SVM)

支持向量机是一种强大的分类算法,特别适合中小规模数据集。它通过找到最优的超平面来分割不同类别的数据。

原理详解

SVM 的目标是找到一个超平面,使得两类数据之间的间隔最大化。间隔是指超平面到最近的样本点的距离,这些最近的点称为支持向量

最大间隔原理

假设超平面为 wTx+b=0w^T x + b = 0,间隔可以表示为:

margin=2w\text{margin} = \frac{2}{\|w\|}

最大化间隔等价于最小化 w2\|w\|^2,同时满足所有样本正确分类的约束。

核技巧(Kernel Trick)

对于线性不可分的数据,SVM 可以通过核函数将数据映射到高维空间,使其线性可分。常用的核函数包括:

  • 线性核K(x,x)=xTxK(x, x') = x^T x',适用于线性可分数据
  • 多项式核K(x,x)=(γxTx+r)dK(x, x') = (\gamma x^T x' + r)^d,可以捕获特征间的多项式关系
  • RBF 核(高斯核)K(x,x)=exp(γxx2)K(x, x') = \exp(-\gamma \|x - x'\|^2),最常用,可以拟合复杂的边界

基本用法

from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

iris = load_iris()
X, y = iris.data, iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# SVM 对特征尺度敏感,需要标准化
pipeline = Pipeline([
('scaler', StandardScaler()),
('svm', SVC(kernel='rbf', C=1.0, gamma='scale', random_state=42))
])

pipeline.fit(X_train, y_train)
print(f"测试集准确率: {pipeline.score(X_test, y_test):.2%}")

重要参数

参数说明默认值
C正则化参数,值越大对错误分类的惩罚越大1.0
kernel核函数类型:'linear''poly''rbf''sigmoid''rbf'
gammaRBF、多项式、Sigmoid 核的系数'scale'
degree多项式核的次数3

参数调优示例

from sklearn.model_selection import GridSearchCV

# 定义参数网格
param_grid = {
'svm__C': [0.1, 1, 10, 100],
'svm__gamma': ['scale', 0.001, 0.01, 0.1, 1],
'svm__kernel': ['rbf', 'linear']
}

# 网格搜索
grid_search = GridSearchCV(
pipeline, param_grid, cv=5, scoring='accuracy', n_jobs=-1
)
grid_search.fit(X_train, y_train)

print(f"最佳参数: {grid_search.best_params_}")
print(f"最佳交叉验证分数: {grid_search.best_score_:.2%}")
print(f"测试集准确率: {grid_search.score(X_test, y_test):.2%}")

SVM 的优缺点

优点

  • 在高维空间中表现良好
  • 对于小样本数据有效
  • 通过核技巧可以处理非线性问题

缺点

  • 对大规模数据训练较慢
  • 对特征尺度敏感,需要预处理
  • 调参相对复杂

K 近邻(KNN)

K 近邻是最简单的分类算法之一,它不需要显式的训练过程,而是直接根据邻居的类别进行投票。

原理详解

KNN 的核心思想是"物以类聚":一个样本的类别由其最近邻居的类别决定。

算法流程

  1. 计算待分类样本与训练集中所有样本的距离
  2. 选取距离最近的 K 个邻居
  3. 统计这 K 个邻居的类别
  4. 将出现次数最多的类别作为预测结果

距离度量

  • 欧氏距离d=i(xiyi)2d = \sqrt{\sum_{i}(x_i - y_i)^2}
  • 曼哈顿距离d=ixiyid = \sum_{i}|x_i - y_i|
  • 闵可夫斯基距离d=(ixiyip)1/pd = (\sum_{i}|x_i - y_i|^p)^{1/p}

K 值的选择

  • K 值较小:模型复杂,容易过拟合,对噪声敏感
  • K 值较大:模型简单,可能欠拟合,边界更平滑

通常通过交叉验证选择最优的 K 值。

基本用法

from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split, cross_val_score

iris = load_iris()
X, y = iris.data, iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# KNN 对特征尺度敏感
pipeline = Pipeline([
('scaler', StandardScaler()),
('knn', KNeighborsClassifier(n_neighbors=5))
])

pipeline.fit(X_train, y_train)
print(f"测试集准确率: {pipeline.score(X_test, y_test):.2%}")

# 寻找最优 K 值
k_range = range(1, 31)
k_scores = []

for k in k_range:
knn = Pipeline([
('scaler', StandardScaler()),
('knn', KNeighborsClassifier(n_neighbors=k))
])
scores = cross_val_score(knn, X_train, y_train, cv=5)
k_scores.append(scores.mean())

best_k = k_range[k_scores.index(max(k_scores))]
print(f"最优 K 值: {best_k}")

重要参数

参数说明默认值
n_neighbors邻居数量 K5
weights权重:'uniform'(等权重)或 'distance'(距离加权)'uniform'
metric距离度量'minkowski'
p闵可夫斯基距离的参数(p=2 为欧氏距离)2

KNN 的优缺点

优点

  • 简单直观,无需训练
  • 天然支持多分类
  • 对异常值相对稳健(当 K 较大时)

缺点

  • 预测速度慢,需要计算与所有训练样本的距离
  • 对特征尺度敏感
  • 对高维数据效果差(维度灾难)
  • 需要存储所有训练数据

朴素贝叶斯(Naive Bayes)

朴素贝叶斯基于贝叶斯定理,假设特征之间相互独立。尽管这个假设在现实中很少成立,但朴素贝叶斯在很多场景下仍然表现良好。

原理详解

贝叶斯定理:

P(yx)=P(xy)P(y)P(x)P(y|x) = \frac{P(x|y)P(y)}{P(x)}

其中:

  • P(yx)P(y|x) 是后验概率,给定特征 xx 时类别 yy 的概率
  • P(xy)P(x|y) 是似然,给定类别 yy 时特征 xx 的概率
  • P(y)P(y) 是先验概率,类别 yy 出现的概率
  • P(x)P(x) 是证据,特征 xx 出现的概率

由于假设特征独立,似然可以分解为:

P(xy)=jP(xjy)P(x|y) = \prod_{j} P(x_j|y)

不同类型的朴素贝叶斯

  • 高斯朴素贝叶斯(GaussianNB):假设特征服从高斯分布,适用于连续特征
  • 多项式朴素贝叶斯(MultinomialNB):适用于离散特征,如词频
  • 伯努利朴素贝叶斯(BernoulliNB):适用于二值特征

基本用法

from sklearn.naive_bayes import GaussianNB, MultinomialNB
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

iris = load_iris()
X, y = iris.data, iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 高斯朴素贝叶斯
clf = GaussianNB()
clf.fit(X_train, y_train)

print(f"测试集准确率: {clf.score(X_test, y_test):.2%}")

# 查看各类别的先验概率
print(f"先验概率: {clf.class_prior_}")

朴素贝叶斯的优缺点

优点

  • 训练和预测速度非常快
  • 对小样本数据效果不错
  • 可以处理多分类问题
  • 对缺失值不敏感

缺点

  • 特征独立性假设通常不成立
  • 对特征相关性敏感

模型评估

混淆矩阵

混淆矩阵是评估分类模型的基本工具,展示了预测结果与真实标签的对应关系。

from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
import matplotlib.pyplot as plt

# 训练模型
clf = RandomForestClassifier(random_state=42)
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)

# 混淆矩阵
cm = confusion_matrix(y_test, y_pred)
print("混淆矩阵:")
print(cm)

# 可视化
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=iris.target_names)
disp.plot(cmap='Blues')
plt.show()

分类报告

分类报告提供精确率、召回率和 F1 分数的详细信息。

from sklearn.metrics import classification_report

print(classification_report(y_test, y_pred, target_names=iris.target_names))

指标解释

  • 精确率(Precision):预测为正的样本中有多少是真正的正样本 Precision=TPTP+FP\text{Precision} = \frac{TP}{TP + FP}

  • 召回率(Recall):真正的正样本有多少被正确预测 Recall=TPTP+FN\text{Recall} = \frac{TP}{TP + FN}

  • F1 分数:精确率和召回率的调和平均 F1=2×Precision×RecallPrecision+RecallF1 = 2 \times \frac{\text{Precision} \times \text{Recall}}{\text{Precision} + \text{Recall}}

ROC 曲线与 AUC

ROC 曲线展示不同阈值下的真阳性率(TPR)和假阳性率(FPR)。AUC 是 ROC 曲线下的面积,越接近 1 表示模型越好。

from sklearn.metrics import roc_curve, auc, RocCurveDisplay
from sklearn.preprocessing import label_binarize
import numpy as np

# 对于二分类问题
y_bin = label_binarize(y_test, classes=[0, 1, 2])

# 训练一个可以输出概率的模型
clf = LogisticRegression(max_iter=200)
clf.fit(X_train, y_train)
y_score = clf.predict_proba(X_test)

# 计算每个类别的 ROC
plt.figure(figsize=(8, 6))
for i in range(len(iris.target_names)):
fpr, tpr, _ = roc_curve(y_bin[:, i], y_score[:, i])
roc_auc = auc(fpr, tpr)
plt.plot(fpr, tpr, label=f'{iris.target_names[i]} (AUC = {roc_auc:.2f})')

plt.plot([0, 1], [0, 1], 'k--')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curves')
plt.legend()
plt.show()

算法选择指南

选择合适的分类算法需要考虑多个因素:

因素建议
数据规模大数据集优先选择线性模型(逻辑回归、线性SVM);小数据集可以尝试复杂模型
特征数量高维稀疏数据适合线性模型;低维密集数据可以尝试非线性模型
特征关系线性关系用逻辑回归、线性 SVM;非线性关系用决策树、随机森林、核 SVM
可解释性需求需要可解释性选择逻辑回归、决策树
训练时间训练时间有限选择逻辑回归、朴素贝叶斯
预测速度需要快速预测选择逻辑回归、决策树、朴素贝叶斯

实践建议

  1. 从简单模型开始:先用逻辑回归建立基线
  2. 尝试多种算法:比较不同算法的性能
  3. 交叉验证:使用交叉验证评估模型的泛化能力
  4. 关注业务指标:准确率不是唯一标准,根据业务需求选择合适的评估指标
  5. 调参优化:对表现较好的模型进行超参数调优

完整示例

下面是一个完整的分类任务示例,展示从数据加载到模型评估的完整流程:

from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split, cross_val_score, GridSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import SVC
from sklearn.metrics import classification_report, confusion_matrix
import pandas as pd

# 1. 加载数据
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, stratify=y
)

# 3. 定义模型字典
models = {
"逻辑回归": Pipeline([
('scaler', StandardScaler()),
('clf', LogisticRegression(max_iter=200, random_state=42))
]),
"决策树": DecisionTreeClassifier(max_depth=5, random_state=42),
"随机森林": RandomForestClassifier(n_estimators=100, random_state=42),
"SVM": Pipeline([
('scaler', StandardScaler()),
('clf', SVC(kernel='rbf', random_state=42))
])
}

# 4. 比较模型
results = []
for name, model in models.items():
# 交叉验证
cv_scores = cross_val_score(model, X_train, y_train, cv=5)

# 训练和测试
model.fit(X_train, y_train)
test_score = model.score(X_test, y_test)

results.append({
"模型": name,
"交叉验证均值": cv_scores.mean(),
"交叉验证标准差": cv_scores.std(),
"测试集准确率": test_score
})

# 5. 显示结果
df_results = pd.DataFrame(results)
print("模型比较:")
print(df_results.to_string(index=False))

# 6. 选择最佳模型进行详细评估
best_model_name = df_results.loc[df_results["测试集准确率"].idxmax(), "模型"]
print(f"\n最佳模型: {best_model_name}")

best_model = models[best_model_name]
y_pred = best_model.predict(X_test)

print("\n分类报告:")
print(classification_report(y_test, y_pred, target_names=iris.target_names))

print("混淆矩阵:")
print(confusion_matrix(y_test, y_pred))

这个示例展示了如何系统地比较不同分类算法,并选择最佳模型。在实际项目中,你可能还需要进行特征工程、超参数调优等步骤来进一步提升模型性能。