# 机器学习从入坑到封神:面向Python程序员的Scikit-learn实战指南

欢迎来到机器学习的精彩世界!无论你是刚刚接触机器学习的新手,还是有一定基础的开发者,希望进一步提升,这篇教程都将为你提供一个系统、深入、实战导向的学习路径。我们将以Python为核心语言,Scikit-learn为主要工具,带你从基础理论到高级算法,一步步构建起完整的知识体系。教程中会穿插大量的代码示例、数学推导和实战技巧,让你不仅会用,更懂背后的原理。


# 第一部分:机器学习基础

# 1.1 什么是机器学习

机器学习是一门研究如何让计算机从数据中自动学习规律和模式,并利用这些规律对新数据进行预测或决策的学科。它并不是通过显式编程来解决问题,而是通过构建数学模型,利用数据驱动的方式不断优化模型参数,从而提升性能。

从数学角度看,机器学习可以形式化为:

其中 是输入特征, 是输出标签(如果是监督学习),我们希望学习一个映射函数 ,使得对于新的输入 ,能够准确预测其输出 。

# 1.2 机器学习的三大流派

根据训练数据是否有标签,机器学习可以分为三大类:

  • 监督学习(Supervised Learning):训练数据包含输入特征和对应的输出标签。模型学习从输入到输出的映射。典型任务包括分类(离散标签)和回归(连续值)。
  • 无监督学习(Unsupervised Learning):训练数据只有输入特征,没有输出标签。模型需要自行发现数据中的结构或模式。典型任务包括聚类、降维、异常检测等。
  • 强化学习(Reinforcement Learning):智能体在与环境的交互中通过试错学习,以获得最大累积奖励。典型应用包括游戏AI、机器人控制等。

本教程将重点覆盖前两大类,因为它们是 Scikit-learn 的核心领域。

# 1.3 机器学习项目工作流

一个典型的机器学习项目通常遵循以下步骤:

  1. 问题定义:明确业务目标,确定是分类、回归还是聚类问题,定义评价指标。
  2. 数据收集与预处理:收集相关数据,处理缺失值、异常值,进行数据清洗和转换。
  3. 特征工程:从原始数据中提取有意义的特征,可能包括特征选择、特征构造、特征缩放等。
  4. 模型选择与训练:选择适合问题的算法,在训练集上训练模型。
  5. 模型评估与调优:使用验证集或交叉验证评估模型性能,调整超参数以提升效果。
  6. 模型部署与监控:将模型部署到生产环境,持续监控其表现,必要时进行迭代更新。

Scikit-learn 提供了大量工具来支持上述每个环节。

# 1.4 Scikit-learn 环境搭建

在开始实战之前,我们需要搭建好 Python 环境。推荐使用 Anaconda 或 Miniconda 来管理包。安装必要的库:

pip install numpy pandas matplotlib scikit-learn
1

如果你希望使用 Jupyter Notebook 进行交互式编程,可以额外安装:

pip install jupyter
1

验证安装:

import sklearn
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

print(f"Scikit-learn版本: {sklearn.__version__}")
print(f"NumPy版本: {np.__version__}")
print(f"Pandas版本: {pd.__version__}")
# Scikit-learn版本: 1.8.0
# NumPy版本: 1.26.4
# Pandas版本: 3.0.1
1
2
3
4
5
6
7
8
9
10
11

# 1.5 Scikit-learn 核心设计哲学

Scikit-learn 的设计围绕几个核心概念展开,理解它们有助于高效使用该库:

  • 估计器(Estimator):任何可以基于数据集学习参数的对象都可以称为估计器。它通常有两个核心方法:
    • fit(X, y):在训练数据上学习模型参数。
    • 对于无监督学习,fit(X) 即可。
  • 预测器(Predictor):能够对新数据进行预测的估计器。它实现了 predict(X) 方法,返回预测结果。对于分类器,通常还有 predict_proba(X) 返回概率估计。
  • 转换器(Transformer):能够对数据进行变换的估计器。它实现了 transform(X) 方法,返回变换后的数据。典型的例子包括标准化、归一化、降维等。转换器通常也有 fit_transform(X, y) 方法,可以一步完成拟合和转换。
  • 流水线(Pipeline):将多个估计器(通常是转换器和最终的预测器)串联成一个整体,方便进行交叉验证和超参数搜索。Pipeline 本身也是一个估计器。

掌握了这些概念,我们就可以流畅地使用 Scikit-learn 完成各种机器学习任务。


# 第二部分:监督学习(基础模型)

# 第2章 K近邻算法

# 2.1 算法思想与数学原理

K近邻(K-Nearest Neighbors,KNN)是一种简单而直观的监督学习算法,既可以用于分类,也可以用于回归。它的核心思想是:如果一个样本在特征空间中的 k 个最相似的样本中的大多数属于某一个类别,则该样本也属于这个类别(分类);或者用这 k 个样本的标签的平均值作为预测值(回归)。

相似性通常用距离度量,最常用的是欧氏距离:

其他距离度量包括曼哈顿距离、闵可夫斯基距离等。KNN 是一种懒惰学习(lazy learning)算法,因为它并不显式地训练一个模型,而是在预测时才对整个训练集进行搜索。

K 值的选择对算法性能有重要影响:

  • 较小的 K:模型复杂度高,容易过拟合(对噪声敏感)。
  • 较大的 K:模型更平滑,但可能导致欠拟合(决策边界过于简单)。

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

# 2.2 从零实现 KNN

为了深入理解 KNN,我们先手动实现一个简单的分类版本。

import numpy as np
from collections import Counter

class KNN:
    def __init__(self, k=3):
        self.k = k

    def fit(self, X, y):
        self.X_train = X
        self.y_train = y

    def predict(self, X):
        predicted_labels = [self._predict(x) for x in X]
        return np.array(predicted_labels)

    def _predict(self, x):
        # 计算x与所有训练样本的距离
        distances = [np.sqrt(np.sum((x - x_train)**2)) for x_train in self.X_train]
        # 获取距离最近的k个样本的索引
        k_indices = np.argsort(distances)[:self.k]
        # 获取这些样本的标签
        k_nearest_labels = [self.y_train[i] for i in k_indices]
        # 投票决定类别
        most_common = Counter(k_nearest_labels).most_common(1)
        return most_common[0][0]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 2.3 Scikit-learn 实战:鸢尾花分类、手写数字识别

鸢尾花分类

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score, confusion_matrix, ConfusionMatrixDisplay
from sklearn.decomposition import PCA

# 1. 加载数据
iris = load_iris()
X, y = iris.data, iris.target
feature_names = iris.feature_names
target_names = iris.target_names

# 2. 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)

# 3. 数据可视化:选择前两个特征绘制散点图
plt.figure(figsize=(12, 5))

plt.subplot(1, 2, 1)
for i, target_name in enumerate(target_names):
    plt.scatter(X[y == i, 0], X[y == i, 1], label=target_name, alpha=0.7)
plt.xlabel(feature_names[0])
plt.ylabel(feature_names[1])
plt.title('原始数据分布(前两个特征)')
plt.legend()

# 4. 使用PCA降维到2D,展示完整特征空间
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)
plt.subplot(1, 2, 2)
for i, target_name in enumerate(target_names):
    plt.scatter(X_pca[y == i, 0], X_pca[y == i, 1], label=target_name, alpha=0.7)
plt.xlabel('PCA 第一主成分')
plt.ylabel('PCA 第二主成分')
plt.title('原始数据分布(PCA降维)')
plt.legend()

plt.tight_layout()
plt.show()

# 5. 选择最优K值(通过交叉验证)
k_range = range(1, 31)
cv_scores = []
for k in k_range:
    knn = KNeighborsClassifier(n_neighbors=k)
    scores = cross_val_score(knn, X_train, y_train, cv=5, scoring='accuracy')
    cv_scores.append(scores.mean())

# 绘制K值与准确率关系图
plt.figure(figsize=(10, 5))
plt.plot(k_range, cv_scores, marker='o')
plt.xlabel('K 值')
plt.ylabel('交叉验证平均准确率')
plt.title('不同K值下的模型性能')
plt.xticks(k_range[::2])
plt.grid(True)
plt.show()

# 选择最佳K值
best_k = k_range[np.argmax(cv_scores)]
print(f'最佳 K 值: {best_k}')

# 6. 使用最佳K值重新训练模型
knn_best = KNeighborsClassifier(n_neighbors=best_k)
knn_best.fit(X_train, y_train)
y_pred = knn_best.predict(X_test)

# 测试集准确率
test_acc = accuracy_score(y_test, y_pred)
print(f'测试集准确率: {test_acc:.4f}')

# 7. 混淆矩阵
cm = confusion_matrix(y_test, y_pred)
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=target_names)
disp.plot(cmap='Blues')
plt.title('混淆矩阵')
plt.show()

# 8. 绘制决策边界(使用前两个特征简化演示)
# 注意:实际分类器使用全部4个特征,此处仅为了可视化
knn_2d = KNeighborsClassifier(n_neighbors=best_k)
knn_2d.fit(X_train[:, :2], y_train)  # 只用前两个特征训练

# 创建网格
x_min, x_max = X[:, 0].min() - 0.5, X[:, 0].max() + 0.5
y_min, y_max = X[:, 1].min() - 0.5, X[:, 1].max() + 0.5
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02),
                     np.arange(y_min, y_max, 0.02))

# 预测网格点
Z = knn_2d.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)

# 绘制
plt.figure(figsize=(8, 6))
plt.contourf(xx, yy, Z, alpha=0.3, cmap='viridis')
scatter = plt.scatter(X[:, 0], X[:, 1], c=y, cmap='viridis', edgecolor='k')
plt.xlabel(feature_names[0])
plt.ylabel(feature_names[1])
plt.title(f'KNN 决策边界 (K={best_k}, 仅使用前两个特征)')
plt.colorbar(scatter, ticks=[0,1,2])
plt.show()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104

手写数字识别

使用 MNIST 数据集的一个子集(手写数字 0-9)。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import classification_report, confusion_matrix, ConfusionMatrixDisplay
from sklearn.decomposition import PCA
from sklearn.manifold import TSNE

# 1. 加载数据
digits = load_digits()
X, y = digits.data, digits.target
images = digits.images  # 原始8x8图像
target_names = digits.target_names

print(f"数据集大小: {X.shape}")
print(f"目标类别数: {len(np.unique(y))}")
print(f"图像尺寸: {images[0].shape}")

# 2. 显示部分样本图像
fig, axes = plt.subplots(2, 5, figsize=(10, 5))
axes = axes.ravel()
for i in range(10):
    axes[i].imshow(images[i], cmap='gray')
    axes[i].set_title(f'Label: {y[i]}')
    axes[i].axis('off')
plt.suptitle('手写数字示例')
plt.tight_layout()
plt.show()

# 3. 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)

# 4. 使用PCA降维可视化数据分布(二维)
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)
plt.figure(figsize=(8, 6))
scatter = plt.scatter(X_pca[:, 0], X_pca[:, 1], c=y, cmap='tab10', alpha=0.6, edgecolor='k', linewidth=0.5)
plt.colorbar(scatter, ticks=range(10))
plt.xlabel('第一主成分')
plt.ylabel('第二主成分')
plt.title('PCA降维后的数字分布')
plt.show()

# 可选:t-SNE(更清晰但计算稍慢)
tsne = TSNE(n_components=2, random_state=42, perplexity=30)
X_tsne = tsne.fit_transform(X[:500])  # 取前500个样本加速
y_tsne = y[:500]
plt.figure(figsize=(8, 6))
scatter = plt.scatter(X_tsne[:, 0], X_tsne[:, 1], c=y_tsne, cmap='tab10', alpha=0.6, edgecolor='k', linewidth=0.5)
plt.colorbar(scatter, ticks=range(10))
plt.xlabel('t-SNE维度1')
plt.ylabel('t-SNE维度2')
plt.title('t-SNE降维后的数字分布(前500样本)')
plt.show()

# 5. 选择最优K值(交叉验证)
k_range = range(1, 30)
cv_scores = []
for k in k_range:
    knn = KNeighborsClassifier(n_neighbors=k)
    scores = cross_val_score(knn, X_train, y_train, cv=5, scoring='accuracy')
    cv_scores.append(scores.mean())

plt.figure(figsize=(10, 5))
plt.plot(k_range, cv_scores, marker='o')
plt.xlabel('K值')
plt.ylabel('交叉验证平均准确率')
plt.title('不同K值下的模型性能')
plt.xticks(k_range[::2])
plt.grid(True)
plt.show()

best_k = k_range[np.argmax(cv_scores)]
print(f"最佳K值: {best_k}")

# 6. 使用最佳K值重新训练
knn_best = KNeighborsClassifier(n_neighbors=best_k)
knn_best.fit(X_train, y_train)
y_pred = knn_best.predict(X_test)

# 7. 分类报告
print("\n分类报告:")
print(classification_report(y_test, y_pred, target_names=[str(i) for i in target_names]))

# 8. 混淆矩阵
cm = confusion_matrix(y_test, y_pred)
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=target_names)
disp.plot(cmap='Blues')
plt.title('混淆矩阵')
plt.show()

# 9. 错误分析:显示被错误分类的样本
misclassified = np.where(y_pred != y_test)[0]
print(f"错误分类样本数: {len(misclassified)}")

# 显示前10个错误分类的样本
fig, axes = plt.subplots(2, 5, figsize=(12, 6))
axes = axes.ravel()
for i, idx in enumerate(misclassified[:10]):
    axes[i].imshow(images[idx], cmap='gray')
    axes[i].set_title(f'真: {y_test[idx]}, 预: {y_pred[idx]}')
    axes[i].axis('off')
plt.suptitle('错误分类示例')
plt.tight_layout()
plt.show()

# 10. 显示正确分类的样本
correct = np.where(y_pred == y_test)[0]
fig, axes = plt.subplots(2, 5, figsize=(12, 6))
axes = axes.ravel()
for i, idx in enumerate(correct[:10]):
    axes[i].imshow(images[idx], cmap='gray')
    axes[i].set_title(f'真: {y_test[idx]}, 预: {y_pred[idx]}')
    axes[i].axis('off')
plt.suptitle('正确分类示例')
plt.tight_layout()
plt.show()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118

# 2.4 优缺点与适用场景

优点:

  • 简单直观,无需训练(懒惰学习)。
  • 适用于多分类问题。
  • 对数据分布没有假设。

缺点:

  • 预测时需要计算所有样本的距离,计算复杂度高(),不适合大规模数据集。
  • 对特征缩放敏感(因为距离计算),通常需要标准化。
  • 在高维空间中,距离度量变得不再有效(维度灾难)。

适用场景:

  • 小规模数据集。
  • 需要简单快速原型验证的场景。
  • 可作为基准模型。

# 第3章 线性回归

# 3.1 一元与多元线性回归

线性回归是研究一个因变量(目标)与一个或多个自变量(特征)之间线性关系的统计方法。其基本形式为:

一元线性回归:

多元线性回归:

写成向量形式:,其中 是 的矩阵(第一列为1,对应截距项), 是 的参数向量。

我们的目标是找到一组参数 ,使得模型预测值 与真实值 之间的误差最小。通常采用残差平方和(RSS)作为损失函数(也称为代价函数):

# 3.2 最小二乘法(正规方程)

最小二乘法通过求导直接得到解析解。将 对 求导并令其为零:

解得正规方程:

正规方程可以直接计算出最优参数,但计算 的复杂度为 ,当特征数量 很大时,计算代价高昂。此外,如果 不可逆(例如特征之间存在多重共线性),则需要使用伪逆或正则化方法。

# 3.3 梯度下降

梯度下降是一种迭代优化算法,适用于大规模数据或特征数量较多的情况。其思想是沿着代价函数梯度的反方向更新参数,逐步逼近最小值。

批量梯度下降的更新规则:

对于线性回归,梯度为:

其中 是学习率。

除了批量梯度下降,还有随机梯度下降(SGD)和小批量梯度下降(Mini-batch GD),它们在效率和收敛性上各有优劣。

# 3.4 Scikit-learn 实战:加州房价预测

我们将使用 Scikit-learn 内置的加州房价数据集,该数据集包含 8 个特征,目标是预测街区房价中位数。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score

# 设置绘图风格
sns.set_style('whitegrid')
plt.rcParams['figure.figsize'] = (12, 8)

plt.rcParams['font.sans-serif'] = ['WenQuanYi Zen Hei']
plt.rcParams['axes.unicode_minus'] = False

# ==================== 1. 加载数据 ====================
housing = fetch_california_housing()
X, y = housing.data, housing.target
feature_names = housing.feature_names

# 转换为DataFrame便于操作
df = pd.DataFrame(X, columns=feature_names)
df['MedHouseVal'] = y  # 目标变量

print("数据集信息:")
print(f"样本数: {df.shape[0]}")
print(f"特征数: {df.shape[1] - 1}")
print("\n前5行:")
print(df.head())
print("\n描述统计:")
print(df.describe())

# ==================== 2. 数据分布可视化 ====================
# 2.1 特征分布直方图
df.hist(bins=50, figsize=(16, 12))
plt.suptitle('特征与目标变量分布直方图', y=1.02)
plt.tight_layout()
plt.show()

# 2.2 特征箱线图(检查异常值)
fig, axes = plt.subplots(2, 4, figsize=(16, 8))
axes = axes.ravel()
for i, col in enumerate(feature_names):
    axes[i].boxplot(df[col])
    axes[i].set_title(col)
plt.suptitle('特征箱线图')
plt.tight_layout()
plt.show()

# 2.3 目标变量分布
plt.figure(figsize=(10, 4))
plt.subplot(1, 2, 1)
plt.hist(y, bins=50, edgecolor='k', alpha=0.7)
plt.xlabel('MedHouseVal')
plt.ylabel('频数')
plt.title('目标变量分布')

plt.subplot(1, 2, 2)
stats.probplot(y, dist="norm", plot=plt)
plt.title('Q-Q图(正态性检验)')
plt.tight_layout()
plt.show()

# ==================== 3. 特征相关性分析 ====================
plt.figure(figsize=(10, 8))
corr_matrix = df.corr()
sns.heatmap(corr_matrix, annot=True, cmap='coolwarm', fmt='.2f', linewidths=0.5)
plt.title('特征相关性热力图')
plt.show()

# 与目标变量的相关系数
print("\n各特征与目标变量的相关系数:")
print(corr_matrix['MedHouseVal'].sort_values(ascending=False))

# ==================== 4. 划分训练集和测试集 ====================
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# ==================== 5. 训练线性回归模型 ====================
lr = LinearRegression()
lr.fit(X_train, y_train)

# 预测
y_train_pred = lr.predict(X_train)
y_test_pred = lr.predict(X_test)

# 评估指标
train_mse = mean_squared_error(y_train, y_train_pred)
test_mse = mean_squared_error(y_test, y_test_pred)
train_r2 = r2_score(y_train, y_train_pred)
test_r2 = r2_score(y_test, y_test_pred)

print("\n模型评估:")
print(f"训练集 MSE: {train_mse:.4f}, R²: {train_r2:.4f}")
print(f"测试集 MSE: {test_mse:.4f}, R²: {test_r2:.4f}")

# 交叉验证
cv_scores = cross_val_score(lr, X, y, cv=5, scoring='r2')
print(f"5折交叉验证 R² 均值: {cv_scores.mean():.4f} (+/- {cv_scores.std() * 2:.4f})")

# 模型系数
coef_df = pd.DataFrame({'特征': feature_names, '系数': lr.coef_})
print("\n模型系数:")
print(coef_df)

# ==================== 6. 预测值与真实值对比 ====================
plt.figure(figsize=(12, 5))

# 训练集
plt.subplot(1, 2, 1)
plt.scatter(y_train, y_train_pred, alpha=0.3, edgecolor='k', linewidth=0.5)
plt.plot([y_train.min(), y_train.max()], [y_train.min(), y_train.max()], 'r--', lw=2)
plt.xlabel('真实值')
plt.ylabel('预测值')
plt.title(f'训练集: R²={train_r2:.3f}')

# 测试集
plt.subplot(1, 2, 2)
plt.scatter(y_test, y_test_pred, alpha=0.3, edgecolor='k', linewidth=0.5)
plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], 'r--', lw=2)
plt.xlabel('真实值')
plt.ylabel('预测值')
plt.title(f'测试集: R²={test_r2:.3f}')

plt.tight_layout()
plt.show()

# ==================== 7. 残差分析 ====================
residuals_train = y_train - y_train_pred
residuals_test = y_test - y_test_pred

plt.figure(figsize=(15, 5))

# 残差直方图
plt.subplot(1, 3, 1)
plt.hist(residuals_test, bins=40, edgecolor='k', alpha=0.7)
plt.xlabel('残差')
plt.ylabel('频数')
plt.title('测试集残差分布')

# 残差Q-Q图
plt.subplot(1, 3, 2)
stats.probplot(residuals_test, dist="norm", plot=plt)
plt.title('残差Q-Q图')

# 残差 vs 预测值
plt.subplot(1, 3, 3)
plt.scatter(y_test_pred, residuals_test, alpha=0.3, edgecolor='k', linewidth=0.5)
plt.axhline(y=0, color='r', linestyle='--')
plt.xlabel('预测值')
plt.ylabel('残差')
plt.title('残差 vs 预测值')

plt.tight_layout()
plt.show()

# ==================== 8. 特征重要性可视化 ====================
plt.figure(figsize=(8, 5))
colors = ['green' if c > 0 else 'red' for c in lr.coef_]
plt.barh(feature_names, lr.coef_, color=colors)
plt.axvline(x=0, color='black', linestyle='-', linewidth=0.5)
plt.xlabel('系数值')
plt.title('线性回归系数(正负影响)')
plt.tight_layout()
plt.show()

# ==================== 9. 残差的自相关图(可选) ====================
# 绘制残差的自相关函数,检查独立性(时间序列数据不适用此处,但可以作为参考)
from pandas.plotting import autocorrelation_plot
plt.figure(figsize=(10, 4))
autocorrelation_plot(residuals_test)
plt.title('残差自相关图')
plt.show()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174

# 3.5 正则化线性模型(Ridge、Lasso、Elastic Net)

当特征数量较多或存在多重共线性时,普通线性回归可能过拟合或系数不稳定。正则化通过在损失函数中加入参数的惩罚项来限制模型复杂度。

  • 岭回归(Ridge):在损失函数中加入 L2 范数惩罚项 。目标是:

    它倾向于让所有系数都变小,但不为零。

  • Lasso 回归:加入 L1 范数惩罚项 。它可以将一些不重要的系数压缩为零,从而实现特征选择。

  • 弹性网络(Elastic Net):结合了 L1 和 L2 惩罚,比例由混合参数 控制:

在 Scikit-learn 中,可以轻松使用这些模型:

from sklearn.linear_model import Ridge, Lasso, ElasticNet

ridge = Ridge(alpha=1.0)
ridge.fit(X_train, y_train)

lasso = Lasso(alpha=0.1)
lasso.fit(X_train, y_train)

elastic = ElasticNet(alpha=0.1, l1_ratio=0.5)
elastic.fit(X_train, y_train)
1
2
3
4
5
6
7
8
9
10

# 3.6 多项式回归与非线性问题

线性回归只能拟合线性关系,但可以通过添加特征的幂次项(多项式特征)来扩展其表达能力,拟合非线性关系。

例如,将原始特征 扩展为 ,然后应用线性回归。这就是多项式回归。

Scikit-learn 提供了 PolynomialFeatures 来生成多项式特征:

from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import Pipeline

# 创建流水线:先扩展多项式特征,再线性回归
poly_model = Pipeline([
    ('poly', PolynomialFeatures(degree=3, include_bias=False)),
    ('linear', LinearRegression())
])

poly_model.fit(X_train, y_train)
y_pred_poly = poly_model.predict(X_test)
1
2
3
4
5
6
7
8
9
10
11

注意:多项式回归容易过拟合,需要谨慎选择 degree,并配合正则化使用。

# 3.7 模型评估指标与总结

对于回归问题,常用评估指标包括:

  • 均方误差(MSE):
  • 均方根误差(RMSE):,与目标单位一致。
  • 平均绝对误差(MAE):
  • R² 分数:,表示模型解释了目标变量方差的百分比。

线性回归简单高效,可解释性强,是许多回归问题的首选基线模型。当特征与目标存在线性关系时效果良好;当关系非线性时,可通过多项式扩展或使用更复杂的模型(如决策树、神经网络)来改进。


# 第4章 逻辑回归

# 4.1 从回归到分类:Sigmoid 函数

逻辑回归(Logistic Regression)虽然名字里有“回归”,但实际上是一种广泛应用于二分类问题的线性模型。它通过一个 Sigmoid 函数将线性回归的输出压缩到 [0,1] 之间,表示样本属于正类的概率。

Sigmoid 函数定义为:

其图像呈 S 形,输入 越大,输出越接近 1;输入越小,输出越接近 0。

在逻辑回归中,我们假设:

决策边界为 ,此时概率为 0.5。

# 4.2 损失函数:对数损失

逻辑回归不使用平方损失,因为那样会导致非凸优化问题。它采用对数损失(Log Loss),也称为交叉熵损失:

这个损失函数是凸的,可以通过梯度下降找到全局最优解。


# 4.3 梯度下降更新规则

对数损失对参数 的偏导数为:

有趣的是,这一梯度形式与线性回归的梯度完全相同!因此,梯度下降的更新规则也一致:

其中 是学习率。

# 5.2 分类树与回归树

  • 分类树:输出为离散类别。使用熵或基尼系数进行分裂,叶节点用多数类表示。
  • 回归树:输出为连续值。分裂时最小化每个子节点的 MSE,叶节点用该节点样本的平均值表示。

# 5.3 Scikit-learn 实战(可视化、参数调优)

我们使用决策树对鸢尾花数据集进行分类,并可视化树的结构。

# ==================== 导入库 ====================
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split, GridSearchCV, learning_curve
from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.metrics import accuracy_score, confusion_matrix, ConfusionMatrixDisplay
import warnings
warnings.filterwarnings('ignore')

# 设置中文字体(根据系统环境调整,此处使用文泉驿正黑,若不存在则自动回退)
plt.rcParams['font.sans-serif'] = ['WenQuanYi Zen Hei', 'SimHei', 'Microsoft YaHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

# ==================== 1. 加载数据 ====================
iris = load_iris()
X, y = iris.data, iris.target
feature_names = iris.feature_names
target_names = iris.target_names

print("数据集信息:")
print(f"样本数: {X.shape[0]}")
print(f"特征数: {X.shape[1]}")
print(f"类别: {target_names}")

# ==================== 2. 划分训练集和测试集 ====================
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)

# ==================== 3. 初始决策树训练(max_depth=3) ====================
tree_clf = DecisionTreeClassifier(max_depth=3, random_state=42)
tree_clf.fit(X_train, y_train)

# 评估
y_pred = tree_clf.predict(X_test)
acc = accuracy_score(y_test, y_pred)
print(f"\n初始模型(max_depth=3)测试集准确率: {acc:.3f}")

# ==================== 4. 决策树可视化 ====================
plt.figure(figsize=(20, 12))
plot_tree(tree_clf, 
          filled=True, 
          feature_names=feature_names, 
          class_names=target_names,
          rounded=True,
          proportion=False,
          precision=2,
          fontsize=12)
plt.title("决策树结构 (max_depth=3)", fontsize=16)
plt.show()

# ==================== 5. 特征重要性 ====================
importances = tree_clf.feature_importances_
indices = np.argsort(importances)[::-1]

plt.figure(figsize=(8, 5))
plt.barh(range(len(indices)), importances[indices], align='center')
plt.yticks(range(len(indices)), [feature_names[i] for i in indices])
plt.xlabel('重要性')
plt.title('特征重要性')
plt.gca().invert_yaxis()  # 重要性最高的在上方
plt.show()

# ==================== 6. 决策边界可视化(使用前两个特征) ====================
# 为简化可视化,仅用前两个特征训练一个决策树(但此处使用完整模型预测,仅展示二维投影)
# 注意:这只是一个近似展示,并非模型实际使用的全部特征
X_2d = X[:, :2]  # 仅取前两个特征
X_train_2d, X_test_2d, y_train_2d, y_test_2d = train_test_split(X_2d, y, test_size=0.2, random_state=42, stratify=y)

tree_2d = DecisionTreeClassifier(max_depth=3, random_state=42)
tree_2d.fit(X_train_2d, y_train_2d)

# 创建网格
x_min, x_max = X_2d[:, 0].min() - 0.5, X_2d[:, 0].max() + 0.5
y_min, y_max = X_2d[:, 1].min() - 0.5, X_2d[:, 1].max() + 0.5
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02),
                     np.arange(y_min, y_max, 0.02))

# 预测网格点
Z = tree_2d.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)

# 绘制
plt.figure(figsize=(8, 6))
plt.contourf(xx, yy, Z, alpha=0.3, cmap='viridis')
scatter = plt.scatter(X_2d[:, 0], X_2d[:, 1], c=y, cmap='viridis', edgecolor='k', linewidth=0.5)
plt.xlabel(feature_names[0])
plt.ylabel(feature_names[1])
plt.title('决策边界 (仅使用前两个特征,max_depth=3)')
plt.colorbar(scatter, ticks=[0,1,2])
plt.show()

# ==================== 7. 混淆矩阵 ====================
cm = confusion_matrix(y_test, y_pred)
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=target_names)
disp.plot(cmap='Blues', values_format='d')
plt.title('混淆矩阵')
plt.show()

# ==================== 8. 学习曲线(评估过拟合) ====================
train_sizes, train_scores, test_scores = learning_curve(
    DecisionTreeClassifier(max_depth=3, random_state=42),
    X, y, cv=5, scoring='accuracy',
    train_sizes=np.linspace(0.1, 1.0, 10), random_state=42)

train_mean = np.mean(train_scores, axis=1)
train_std = np.std(train_scores, axis=1)
test_mean = np.mean(test_scores, axis=1)
test_std = np.std(test_scores, axis=1)

plt.figure(figsize=(8, 5))
plt.fill_between(train_sizes, train_mean - train_std, train_mean + train_std, alpha=0.1, color='blue')
plt.fill_between(train_sizes, test_mean - test_std, test_mean + test_std, alpha=0.1, color='orange')
plt.plot(train_sizes, train_mean, 'o-', color='blue', label='训练集准确率')
plt.plot(train_sizes, test_mean, 'o-', color='orange', label='交叉验证准确率')
plt.xlabel('训练样本数')
plt.ylabel('准确率')
plt.title('学习曲线 (max_depth=3)')
plt.legend(loc='best')
plt.grid(True)
plt.show()

# ==================== 9. 超参数调优(寻找最佳max_depth) ====================
param_grid = {'max_depth': range(1, 10)}
grid = GridSearchCV(DecisionTreeClassifier(random_state=42), param_grid, cv=5, scoring='accuracy')
grid.fit(X_train, y_train)

print(f"\n最佳参数: {grid.best_params_}")
print(f"最佳交叉验证准确率: {grid.best_score_:.3f}")

# 使用最佳参数重新训练
best_tree = grid.best_estimator_
y_pred_best = best_tree.predict(X_test)
best_acc = accuracy_score(y_test, y_pred_best)
print(f"最佳模型测试集准确率: {best_acc:.3f}")

# 绘制不同深度下的准确率
results = grid.cv_results_
plt.figure(figsize=(8, 5))
plt.plot(param_grid['max_depth'], results['mean_test_score'], marker='o')
plt.xlabel('max_depth')
plt.ylabel('交叉验证准确率')
plt.title('不同深度下的模型性能')
plt.xticks(param_grid['max_depth'])
plt.grid(True)
plt.show()

# ==================== 10. 最终模型决策树可视化 ====================
plt.figure(figsize=(20, 12))
plot_tree(best_tree, 
          filled=True, 
          feature_names=feature_names, 
          class_names=target_names,
          rounded=True,
          proportion=False,
          precision=2,
          fontsize=12)
plt.title(f"最佳决策树结构 (max_depth={best_tree.max_depth})", fontsize=16)
plt.show()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158

参数调优:决策树容易过拟合,需要通过剪枝、限制最大深度、最小样本分裂数等参数来控制模型复杂度。常用参数包括:

  • max_depth:树的最大深度。
  • min_samples_split:内部节点再划分所需的最小样本数。
  • min_samples_leaf:叶节点最少样本数。
  • max_leaf_nodes:最大叶节点数量。
  • criterion:分裂准则('gini' 或 'entropy')。

可以通过网格搜索(GridSearchCV)来寻找最佳参数组合。

# 5.4 剪枝与优缺点

剪枝:决策树剪枝分为预剪枝(在构建过程中提前停止)和后剪枝(先构建完整树,再自底向上剪除对泛化能力贡献不大的子树)。Scikit-learn 主要支持预剪枝。

优点:

  • 模型直观,易于解释。
  • 几乎不需要数据预处理(无需特征缩放)。
  • 可以处理混合类型特征。

缺点:

  • 容易过拟合,需要进行剪枝。
  • 对数据微小变化敏感(不稳定)。
  • 可能无法捕捉特征之间的复杂关系(如 XOR 问题)。

# 第三部分:集成学习

# 第6章 集成学习基础

# 6.1 集成学习思想

集成学习(Ensemble Learning)通过结合多个基学习器(base learners)的预测结果来提升整体性能。通常,集成模型的泛化能力优于单个学习器,正所谓“三个臭皮匠,顶个诸葛亮”。根据基学习器的生成方式,集成学习主要分为三大类:

  • Bagging(Bootstrap Aggregating):基学习器并行训练,彼此独立,通过有放回采样(bootstrap)从原始训练集中生成多个不同的子集,分别训练模型,最后通过投票(分类)或平均(回归)得到最终结果。Bagging 主要降低模型的方差,对不稳定学习器(如决策树)效果显著。

  • Boosting:基学习器串行训练,每个新学习器都重点关注前一个学习器犯错的样本,通过加权调整样本分布,最终将所有学习器加权组合。Boosting 主要降低模型的偏差,能够将弱学习器提升为强学习器。

  • Stacking(堆叠):训练多个不同的基学习器,然后将它们的输出作为特征,再训练一个元学习器(meta-learner)进行最终预测。Stacking 可以融合不同模型的优势。

# 6.2 Bagging 与随机森林

Bagging 的核心是自助采样(bootstrap sampling)。给定包含 m 个样本的训练集,每次随机抽取一个样本放入采样集,然后放回,重复 m 次。这样,有些样本可能在采样集中出现多次,而有些从未出现(约占 36.8%)。用这些采样集训练基学习器,最后进行聚合。

随机森林(Random Forest) 是 Bagging 的一个扩展变体。它在 Bagging 的基础上,进一步在决策树训练时引入了随机特征选择:每个节点分裂时,不是从所有特征中选择最优特征,而是随机选择一个特征子集,再从子集中选择最优特征。这进一步降低了树之间的相关性,提升了模型的多样性,从而增强了泛化能力。

随机森林的步骤如下:

  1. 通过自助采样生成 k 个训练子集。
  2. 对每个子集,训练一棵决策树:
    • 在树的每个节点,随机选择 个特征(通常 或 )。
    • 从这 个特征中选择最优分裂特征。
  3. 对于分类任务,采用投票法;回归任务,采用平均法。

随机森林有两个重要参数:

  • n_estimators:树的数量,通常越大越好,但计算开销也越大。
  • max_features:每个节点考虑的最大特征数。

实战:随机森林分类(乳腺癌数据集)

# ==================== 导入库 ====================
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split, cross_val_score, GridSearchCV, learning_curve
from sklearn.metrics import (accuracy_score, classification_report, confusion_matrix, 
                             ConfusionMatrixDisplay, roc_curve, auc, RocCurveDisplay)
import warnings
warnings.filterwarnings('ignore')


# 设置绘图风格
sns.set_style('whitegrid')
plt.rcParams['figure.figsize'] = (12, 8)

# 重新设置中文字体(覆盖 seaborn 的修改)
plt.rcParams['font.sans-serif'] = ['WenQuanYi Zen Hei', 'SimHei', 'Microsoft YaHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

# ==================== 1. 加载数据 ====================
cancer = load_breast_cancer()
X, y = cancer.data, cancer.target
feature_names = cancer.feature_names
target_names = cancer.target_names

# 转换为DataFrame便于查看
df = pd.DataFrame(X, columns=feature_names)
df['target'] = y

print("数据集信息:")
print(f"样本数: {df.shape[0]}")
print(f"特征数: {df.shape[1] - 1}")
print(f"类别: {target_names[0]} (0), {target_names[1]} (1)")
print(f"类别分布:\n{df['target'].value_counts()}")

# ==================== 2. 划分训练集和测试集 ====================
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)

# ==================== 3. 基础随机森林训练 ====================
rf_base = RandomForestClassifier(n_estimators=100, max_features='sqrt', oob_score=True, random_state=42)
rf_base.fit(X_train, y_train)

y_pred_base = rf_base.predict(X_test)
acc_base = accuracy_score(y_test, y_pred_base)
print(f"\n基础模型测试集准确率: {acc_base:.4f}")
print(f"OOB 分数: {rf_base.oob_score_:.4f}")

# ==================== 4. 分类报告与混淆矩阵 ====================
print("\n分类报告:")
print(classification_report(y_test, y_pred_base, target_names=target_names))

cm = confusion_matrix(y_test, y_pred_base)
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=target_names)
disp.plot(cmap='Blues', values_format='d')
plt.title('混淆矩阵')
plt.show()

# ==================== 5. 特征重要性分析 ====================
importances = rf_base.feature_importances_
indices = np.argsort(importances)[::-1]

# 条形图
plt.figure(figsize=(10, 8))
plt.barh(range(20), importances[indices[:20]], align='center')  # 显示前20个特征
plt.yticks(range(20), [feature_names[i] for i in indices[:20]])
plt.xlabel('重要性')
plt.title('特征重要性 (Top 20)')
plt.gca().invert_yaxis()
plt.tight_layout()
plt.show()

# 累积重要性图
sorted_importances = importances[indices]
cumulative = np.cumsum(sorted_importances)
plt.figure(figsize=(8, 5))
plt.plot(range(1, len(cumulative)+1), cumulative, marker='o')
plt.xlabel('特征数量')
plt.ylabel('累积重要性')
plt.title('特征累积重要性')
plt.grid(True)
plt.show()

# 打印前5个最重要特征
print("\n最重要的5个特征:")
for i in range(5):
    print(f"{i+1}. {feature_names[indices[i]]}: {importances[indices[i]]:.4f}")

# 6. 可视化单棵决策树(随机森林中的第一棵树)
# 注意:随机森林中的树通常很深,这里限制深度以便可视化
from sklearn.tree import plot_tree

# 取第一棵树,设置最大深度为3以便显示
first_tree = rf_base.estimators_[0]
plt.figure(figsize=(20, 10))
plot_tree(first_tree, max_depth=3, feature_names=feature_names, class_names=target_names, 
          filled=True, rounded=True, fontsize=8)
plt.title('随机森林中第一棵决策树(限制深度为3)')
plt.show()

# ==================== 7. ROC曲线与AUC ====================
y_pred_prob = rf_base.predict_proba(X_test)[:, 1]  # 正类概率
fpr, tpr, thresholds = roc_curve(y_test, y_pred_prob)
roc_auc = auc(fpr, tpr)

plt.figure(figsize=(8, 6))
plt.plot(fpr, tpr, color='darkorange', lw=2, label=f'ROC曲线 (AUC = {roc_auc:.3f})')
plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('假阳性率 (FPR)')
plt.ylabel('真阳性率 (TPR)')
plt.title('接收者操作特征 (ROC) 曲线')
plt.legend(loc='lower right')
plt.grid(True)
plt.show()

# ==================== 8. OOB误差与树数量的关系 ====================
# 训练不同树数量的随机森林,记录OOB误差
n_estimators_range = range(10, 201, 10)
oob_scores = []
for n in n_estimators_range:
    rf = RandomForestClassifier(n_estimators=n, max_features='sqrt', oob_score=True, random_state=42, n_jobs=-1)
    rf.fit(X_train, y_train)
    oob_scores.append(rf.oob_score_)

plt.figure(figsize=(8, 5))
plt.plot(n_estimators_range, oob_scores, marker='o')
plt.xlabel('树的数量')
plt.ylabel('OOB 准确率')
plt.title('OOB 误差随树数量的变化')
plt.grid(True)
plt.show()

# ==================== 9. 学习曲线(评估模型随训练数据量增加的表现)====================
train_sizes, train_scores, test_scores = learning_curve(
    RandomForestClassifier(n_estimators=100, max_features='sqrt', random_state=42),
    X, y, cv=5, scoring='accuracy', train_sizes=np.linspace(0.1, 1.0, 10), random_state=42, n_jobs=-1)

train_mean = np.mean(train_scores, axis=1)
train_std = np.std(train_scores, axis=1)
test_mean = np.mean(test_scores, axis=1)
test_std = np.std(test_scores, axis=1)

plt.figure(figsize=(8, 5))
plt.fill_between(train_sizes, train_mean - train_std, train_mean + train_std, alpha=0.1, color='blue')
plt.fill_between(train_sizes, test_mean - test_std, test_mean + test_std, alpha=0.1, color='orange')
plt.plot(train_sizes, train_mean, 'o-', color='blue', label='训练集准确率')
plt.plot(train_sizes, test_mean, 'o-', color='orange', label='交叉验证准确率')
plt.xlabel('训练样本数')
plt.ylabel('准确率')
plt.title('学习曲线')
plt.legend(loc='best')
plt.grid(True)
plt.show()

# ==================== 10. 超参数调优(网格搜索)====================
# 定义参数网格
param_grid = {
    'n_estimators': [50, 100, 200],
    'max_depth': [None, 5, 10, 15],
    'min_samples_split': [2, 5, 10],
    'min_samples_leaf': [1, 2, 4]
}

rf = RandomForestClassifier(max_features='sqrt', random_state=42, n_jobs=-1)
grid_search = GridSearchCV(estimator=rf, param_grid=param_grid, cv=5, scoring='accuracy', n_jobs=-1, verbose=1)
grid_search.fit(X_train, y_train)

print(f"\n最佳参数: {grid_search.best_params_}")
print(f"最佳交叉验证准确率: {grid_search.best_score_:.4f}")

# 使用最佳参数重新训练
best_rf = grid_search.best_estimator_
y_pred_best = best_rf.predict(X_test)
best_acc = accuracy_score(y_test, y_pred_best)
print(f"最佳模型测试集准确率: {best_acc:.4f}")

# 绘制网格搜索结果(可选,仅展示部分)
results = pd.DataFrame(grid_search.cv_results_)
# 可以绘制某个参数的影响,例如不同n_estimators下的表现
plt.figure(figsize=(8, 5))
for depth in param_grid['max_depth']:
    subset = results[results['param_max_depth'] == depth]
    plt.plot(subset['param_n_estimators'], subset['mean_test_score'], marker='o', label=f'max_depth={depth}')
plt.xlabel('n_estimators')
plt.ylabel('交叉验证准确率')
plt.title('不同参数组合下的模型性能')
plt.legend()
plt.grid(True)
plt.show()

# ==================== 11. 最终模型评估 ====================
# 混淆矩阵
cm_best = confusion_matrix(y_test, y_pred_best)
disp_best = ConfusionMatrixDisplay(confusion_matrix=cm_best, display_labels=target_names)
disp_best.plot(cmap='Blues', values_format='d')
plt.title('最佳模型混淆矩阵')
plt.show()

# ROC曲线
y_pred_prob_best = best_rf.predict_proba(X_test)[:, 1]
fpr_best, tpr_best, _ = roc_curve(y_test, y_pred_prob_best)
roc_auc_best = auc(fpr_best, tpr_best)

plt.figure(figsize=(8, 6))
plt.plot(fpr_best, tpr_best, color='darkorange', lw=2, label=f'ROC曲线 (AUC = {roc_auc_best:.3f})')
plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')
plt.xlabel('假阳性率')
plt.ylabel('真阳性率')
plt.title('最佳模型 ROC 曲线')
plt.legend(loc='lower right')
plt.grid(True)
plt.show()

# ==================== 12. 保存最佳模型(可选)====================
import joblib
joblib.dump(best_rf, 'best_random_forest.pkl')
print("最佳模型已保存为 best_random_forest.pkl")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221

随机森林可以输出特征重要性,它是通过计算每个特征在树节点分裂时减少的不纯度(基尼系数或熵)的平均值来衡量的。

# 6.3 Boosting(AdaBoost、梯度提升)

Boosting 的核心思想是串行训练一系列弱学习器,每个学习器都试图纠正前一个的错误。常见的有 AdaBoost 和 Gradient Boosting。

AdaBoost(Adaptive Boosting):

  • 初始化样本权重为 。
  • 对于每一轮 :
    • 基于当前权重训练一个弱学习器(通常是深度为1的决策树,即决策桩)。
    • 计算该学习器的加权错误率 。
    • 计算学习器权重 。
    • 更新样本权重:被正确分类的样本权重降低,被错误分类的样本权重升高,然后归一化。
  • 最终预测为所有弱学习器的加权投票。

梯度提升(Gradient Boosting):

  • 不是调整样本权重,而是让每个新学习器拟合前一个学习器的残差(或负梯度)。
  • 以回归为例:首先用常数模型 拟合目标,然后计算残差 ,接着训练一个回归树 拟合残差,更新模型 ,其中 是学习率(shrinkage),防止过拟合。重复这个过程。

梯度提升决策树(GBDT) 是梯度提升的常用实现。Scikit-learn 中的 GradientBoostingClassifier 和 GradientBoostingRegressor 实现了该算法。

实战:梯度提升分类(乳腺癌数据集)

from sklearn.ensemble import GradientBoostingClassifier

gb = GradientBoostingClassifier(n_estimators=100, learning_rate=0.1, max_depth=3, random_state=42)
gb.fit(X_train, y_train)
y_pred_gb = gb.predict(X_test)
print(f"梯度提升准确率: {accuracy_score(y_test, y_pred_gb):.4f}")
1
2
3
4
5
6

# 6.4 Voting 与 Stacking

Voting(投票):直接对多个不同模型的预测结果进行投票(硬投票)或概率平均(软投票)。适用于已训练好的模型集合。

from sklearn.ensemble import VotingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC

# 定义三个不同的基分类器
log_clf = LogisticRegression(max_iter=10000, random_state=42)
svm_clf = SVC(probability=True, random_state=42)  # 需 probability=True 才能软投票
rf_clf = RandomForestClassifier(n_estimators=100, random_state=42)

voting_clf = VotingClassifier(
    estimators=[('lr', log_clf), ('svc', svm_clf), ('rf', rf_clf)],
    voting='soft'  # 软投票,基于概率平均
)
voting_clf.fit(X_train, y_train)
y_pred_vote = voting_clf.predict(X_test)
print(f"Voting 准确率: {accuracy_score(y_test, y_pred_vote):.4f}")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Stacking(堆叠):使用一个元学习器来结合基学习器的输出。通常将训练集划分为两部分,一部分用于训练基学习器,另一部分用于生成元特征(即基学习器在该部分上的预测),然后用元特征训练元学习器。为了避免过拟合,常采用交叉验证的方式生成元特征。

Scikit-learn 提供了 StackingClassifier 和 StackingRegressor:

from sklearn.ensemble import StackingClassifier

stacking_clf = StackingClassifier(
    estimators=[('lr', log_clf), ('rf', rf_clf), ('svc', svm_clf)],
    final_estimator=LogisticRegression(),  # 元学习器
    cv=5  # 使用5折交叉验证生成元特征
)
stacking_clf.fit(X_train, y_train)
y_pred_stack = stacking_clf.predict(X_test)
print(f"Stacking 准确率: {accuracy_score(y_test, y_pred_stack):.4f}")
1
2
3
4
5
6
7
8
9
10

# 第7章 集成学习进阶:XGBoost 与 LightGBM

# 7.1 XGBoost 原理与实战

XGBoost(eXtreme Gradient Boosting)是梯度提升决策树的高效实现,以其速度快、精度高而著称,在 Kaggle 竞赛中广受欢迎。它在传统 GBDT 的基础上进行了多项优化:

  • 二阶泰勒展开:使用损失函数的一阶和二阶导数信息,加速收敛。
  • 正则化:在目标函数中加入树的复杂度惩罚(叶子节点数和叶子权重的 L2 正则),防止过拟合。
  • 列采样:类似随机森林,每棵树随机选择部分特征,增强多样性。
  • 缺失值处理:自动学习缺失值的最佳分裂方向。
  • 并行计算:在特征粒度上支持并行,训练速度快。

XGBoost 的目标函数为:

其中 , 为叶子节点数, 为叶子权重。

安装与实战:

首先安装 xgboost 库:pip install xgboost

使用 XGBoost 对乳腺癌数据集进行分类:

import xgboost as xgb
from sklearn.metrics import accuracy_score

# 创建 DMatrix 格式(XGBoost 内部优化数据结构)
dtrain = xgb.DMatrix(X_train, label=y_train)
dtest = xgb.DMatrix(X_test, label=y_test)

# 设置参数
params = {
    'objective': 'binary:logistic',
    'max_depth': 3,
    'eta': 0.1,
    'subsample': 0.8,
    'colsample_bytree': 0.8,
    'eval_metric': 'logloss',
    'seed': 42
}

# 训练
num_rounds = 100
model = xgb.train(params, dtrain, num_rounds)

# 预测
y_pred_prob = model.predict(dtest)
y_pred = (y_pred_prob > 0.5).astype(int)
print(f"XGBoost 准确率: {accuracy_score(y_test, y_pred):.4f}")

# 也可以使用 Scikit-learn 风格的接口
from xgboost import XGBClassifier

xgb_clf = XGBClassifier(n_estimators=100, max_depth=3, learning_rate=0.1, subsample=0.8, colsample_bytree=0.8, random_state=42)
xgb_clf.fit(X_train, y_train)
y_pred_xgb = xgb_clf.predict(X_test)
print(f"XGBClassifier 准确率: {accuracy_score(y_test, y_pred_xgb):.4f}")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# 7.2 LightGBM 原理与实战

LightGBM 是微软开源的梯度提升框架,它主要针对 XGBoost 在大数据量下训练速度慢、内存占用高的问题进行了优化。其核心创新包括:

  • 基于直方图的决策树算法:将连续特征离散化为 k 个桶(bins),构建直方图,寻找最优分裂点时只需遍历桶,大幅减少计算量。
  • GOSS(基于梯度的单边采样):在计算梯度时,保留大梯度的样本,对小梯度样本随机采样,同时补偿数据分布,减少计算复杂度。
  • EFB(互斥特征捆绑):将互斥的特征(很少同时非零)捆绑在一起,减少特征维度。

LightGBM 的训练速度通常比 XGBoost 更快,内存占用更少,且精度相当。

安装与实战:

安装:pip install lightgbm

import lightgbm as lgb
from sklearn.metrics import accuracy_score

# 创建 Dataset
train_data = lgb.Dataset(X_train, label=y_train)
test_data = lgb.Dataset(X_test, label=y_test, reference=train_data)

# 参数
params = {
    'objective': 'binary',
    'metric': 'binary_logloss',
    'boosting_type': 'gbdt',
    'num_leaves': 31,
    'learning_rate': 0.1,
    'feature_fraction': 0.8,
    'bagging_fraction': 0.8,
    'bagging_freq': 5,
    'verbose': -1
}

# 训练
model_lgb = lgb.train(params, train_data, valid_sets=[test_data], num_boost_round=100)

# 预测
y_pred_prob_lgb = model_lgb.predict(X_test)
y_pred_lgb = (y_pred_prob_lgb > 0.5).astype(int)
print(f"LightGBM 准确率: {accuracy_score(y_test, y_pred_lgb):.4f}")

# Scikit-learn 风格
from lightgbm import LGBMClassifier

lgb_clf = LGBMClassifier(n_estimators=100, num_leaves=31, learning_rate=0.1, feature_fraction=0.8, bagging_fraction=0.8, bagging_freq=5, random_state=42)
lgb_clf.fit(X_train, y_train)
y_pred_lgb_sk = lgb_clf.predict(X_test)
print(f"LGBMClassifier 准确率: {accuracy_score(y_test, y_pred_lgb_sk):.4f}")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# 7.3 模型对比与选择

  • 随机森林:训练快,易于调参,对异常值鲁棒,能输出特征重要性。适合作为基线模型。
  • 梯度提升(GBDT):通常比随机森林精度更高,但需要更细致的调参,容易过拟合。
  • XGBoost:在精度和速度上平衡较好,支持正则化,是竞赛常用模型。
  • LightGBM:训练速度最快,尤其适合大规模数据集,但可能对噪声敏感。

选择时需考虑数据规模、特征类型、精度要求、训练时间等因素。通常,可以先尝试随机森林,再用 XGBoost 或 LightGBM 进一步提升。


# 第四部分:无监督学习

# 第8章 聚类算法

聚类是无监督学习的核心任务之一,旨在将数据划分为若干个簇,使得同一簇内的样本相似度高,不同簇之间的样本相似度低。

# 8.1 K-Means 聚类

原理:K-Means 试图将数据划分成 K 个簇,每个簇由其质心(centroid)表示。算法步骤如下:

  1. 随机初始化 K 个质心。
  2. 重复以下步骤直至收敛:
    • 将每个样本分配给距离最近的质心(通常用欧氏距离)。
    • 根据当前分配的结果,重新计算每个簇的质心(该簇所有样本的均值)。
  3. 最终输出簇分配和质心。

目标函数是最小化样本到其所属簇质心的平方距离之和(惯性):

K-Means 对初始质心敏感,通常多次运行取最佳结果。Scikit-learn 的 KMeans 默认使用 k-means++ 初始化,能有效改善初始质心选择。

肘部法则(Elbow Method) 用于选择适当的 K 值:计算不同 K 值下的惯性(inertia),绘制曲线,选择曲线拐点处的 K。

实战:K-Means 聚类(鸢尾花数据集,去掉标签)

from sklearn.cluster import KMeans
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt

iris = load_iris()
X = iris.data

# 尝试不同的 K
inertias = []
for k in range(1, 11):
    kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
    kmeans.fit(X)
    inertias.append(kmeans.inertia_)

plt.plot(range(1, 11), inertias, marker='o')
plt.xlabel('K')
plt.ylabel('Inertia')
plt.title('肘部法则选择K')
plt.show()

# 选择 K=3 进行聚类
kmeans = KMeans(n_clusters=3, random_state=42, n_init=10)
y_kmeans = kmeans.fit_predict(X)

# 可视化前两个特征
plt.scatter(X[:, 0], X[:, 1], c=y_kmeans, cmap='viridis')
plt.scatter(kmeans.cluster_centers_[:, 0], kmeans.cluster_centers_[:, 1], s=300, c='red', marker='X')
plt.xlabel(iris.feature_names[0])
plt.ylabel(iris.feature_names[1])
plt.title('K-Means 聚类结果')
plt.show()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 8.2 DBSCAN 密度聚类

DBSCAN(Density-Based Spatial Clustering of Applications with Noise)是一种基于密度的聚类算法,可以发现任意形状的簇,并自动识别噪声点。它有两个关键参数:

  • eps:邻域半径。
  • min_samples:成为核心点所需的最小邻居数。

核心思想:

  • 核心点:在半径 eps 内包含至少 min_samples 个点的样本。
  • 边界点:不是核心点,但落在某个核心点的邻域内。
  • 噪声点:既不是核心点也不是边界点。

算法通过连接核心点及其邻域内的点形成簇。

DBSCAN 的优点是不需要指定簇的数量,能处理不规则形状的簇,对噪声鲁棒。缺点是 eps 和 min_samples 对结果敏感,且在高维数据中效果不佳。

实战:DBSCAN 聚类(生成半月形数据)

from sklearn.cluster import DBSCAN
from sklearn.datasets import make_moons
import matplotlib.pyplot as plt

X_moons, _ = make_moons(n_samples=200, noise=0.05, random_state=42)

# K-Means 难以处理这种形状
kmeans_moons = KMeans(n_clusters=2, random_state=42, n_init=10)
y_kmeans_moons = kmeans_moons.fit_predict(X_moons)

# DBSCAN
dbscan = DBSCAN(eps=0.2, min_samples=5)
y_dbscan = dbscan.fit_predict(X_moons)

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12,4))
ax1.scatter(X_moons[:,0], X_moons[:,1], c=y_kmeans_moons, cmap='viridis')
ax1.set_title('K-Means')
ax2.scatter(X_moons[:,0], X_moons[:,1], c=y_dbscan, cmap='viridis')
ax2.set_title('DBSCAN')
plt.show()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 8.3 层次聚类与树状图

层次聚类(Hierarchical Clustering)通过构建一个簇的层次结构来进行聚类,分为凝聚(自底向上)和分裂(自顶向下)两种方式。凝聚层次聚类是最常用的:开始时每个样本为一个簇,然后迭代合并最近的两个簇,直到达到预设的簇数或满足停止条件。

簇间距离的度量方法(链接准则):

  • 单链接(single):两个簇之间最近样本的距离。
  • 全链接(complete):两个簇之间最远样本的距离。
  • 平均链接(average):两个簇之间所有样本对距离的平均。
  • 质心法(centroid):两个簇质心的距离。
  • Ward 法:最小化合并后簇内平方和的总增量。

层次聚类的结果可以用树状图(dendrogram)可视化,帮助确定簇的数量。

实战:层次聚类与树状图(使用 scipy)

from scipy.cluster.hierarchy import dendrogram, linkage
from sklearn.cluster import AgglomerativeClustering
import numpy as np

# 生成数据
np.random.seed(42)
X_hc = np.random.randn(10, 2)  # 小样本便于展示

# 计算链接矩阵
Z = linkage(X_hc, method='ward')

# 绘制树状图
plt.figure(figsize=(10,5))
dendrogram(Z)
plt.title('树状图')
plt.xlabel('样本索引')
plt.ylabel('距离')
plt.show()

# 使用 AgglomerativeClustering 进行聚类
hc = AgglomerativeClustering(n_clusters=3, linkage='ward')
y_hc = hc.fit_predict(X_hc)
print("层次聚类结果:", y_hc)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 8.4 聚类评估指标

由于无监督学习没有真实标签,评估聚类质量通常有两种方式:

  • 内部指标:仅利用数据本身的特征,如轮廓系数、Davies-Bouldin 指数等。
  • 外部指标:需要真实标签,常用于评估聚类算法在已知标签数据上的表现,如调整兰德指数(ARI)、互信息等。

轮廓系数(Silhouette Coefficient): 对于每个样本 i,定义:

  • :样本 i 到同簇其他样本的平均距离(簇内不相似度)。
  • :样本 i 到其他簇所有样本的平均距离的最小值(簇间不相似度)。 则轮廓系数 ,取值范围 ,越接近 1 表示聚类效果越好。

整体轮廓系数是所有样本的平均值。

调整兰德指数(Adjusted Rand Index, ARI): 给定真实标签和聚类结果,计算样本对划分的一致性,并调整机会的概率。取值范围 ,1 表示完全一致,0 表示随机划分。

实战:计算轮廓系数和 ARI

from sklearn.metrics import silhouette_score, adjusted_rand_score

# 假设真实标签为 y_true
# 使用 K-Means 聚类结果
y_kmeans = kmeans.predict(X)

# 轮廓系数(不需要真实标签)
sil_score = silhouette_score(X, y_kmeans)
print(f"轮廓系数: {sil_score:.3f}")

# 调整兰德指数(需要真实标签)
ari = adjusted_rand_score(iris.target, y_kmeans)
print(f"调整兰德指数: {ari:.3f}")
1
2
3
4
5
6
7
8
9
10
11
12
13

# 第9章 朴素贝叶斯

# 9.1 贝叶斯定理与朴素假设

朴素贝叶斯(Naive Bayes)是一种基于贝叶斯定理的分类算法,它假设特征之间条件独立(即“朴素”假设)。尽管这个假设在现实中往往不成立,但朴素贝叶斯在许多任务中表现出色,尤其是文本分类。

贝叶斯定理:

对于分类,我们选择后验概率最大的类别:

其中 是类先验概率, 是条件概率。

# 9.2 三种朴素贝叶斯

根据特征分布假设的不同,朴素贝叶斯有多种变体:

  • 高斯朴素贝叶斯(GaussianNB):假设连续特征服从高斯分布 ,适用于连续特征。
  • 多项式朴素贝叶斯(MultinomialNB):假设特征由多项式分布生成,常用于文本分类中的词频或 TF-IDF 特征。
  • 伯努利朴素贝叶斯(BernoulliNB):假设特征是二元变量(如词是否出现),适用于二值特征。

# 9.3 实战:文本分类(20 Newsgroups)

20 Newsgroups 是一个常用的文本分类数据集,包含约 20,000 篇新闻文档,分为 20 个主题。我们将使用多项式朴素贝叶斯进行分类,并利用 TF-IDF 特征提取。

from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import Pipeline
from sklearn.metrics import classification_report

# 加载部分类别(为了快速演示,只选4个类别)
categories = ['alt.atheism', 'soc.religion.christian', 'comp.graphics', 'sci.med']
news_train = fetch_20newsgroups(subset='train', categories=categories, shuffle=True, random_state=42)
news_test = fetch_20newsgroups(subset='test', categories=categories, shuffle=True, random_state=42)

# 构建流水线:TF-IDF 转换 + 多项式朴素贝叶斯
text_clf = Pipeline([
    ('tfidf', TfidfVectorizer(stop_words='english')),
    ('clf', MultinomialNB())
])

text_clf.fit(news_train.data, news_train.target)
y_pred = text_clf.predict(news_test.data)

print(classification_report(news_test.target, y_pred, target_names=news_test.target_names))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

朴素贝叶斯训练速度快,尤其适合高维稀疏数据,但特征独立性假设可能导致在某些问题上表现不佳。


# 第五部分:高级算法

# 第10章 支持向量机(SVM)

# 10.1 SVM 原理(间隔、核技巧)

支持向量机(Support Vector Machine, SVM)是一种强大的分类器,其核心思想是找到一个超平面,使得两类样本之间的间隔(margin)最大化。对于线性可分数据,SVM 寻找的是能够正确划分训练集且几何间隔最大的超平面。

假设超平面为 ,则样本点到超平面的函数间隔为 ,几何间隔为 。SVM 的目标是最大化最小几何间隔,等价于最小化 ,约束为 。

对于非线性问题,SVM 通过核技巧(kernel trick)将数据映射到高维特征空间,使得原本线性不可分的数据变得线性可分。常用的核函数有:

  • 线性核:
  • 多项式核:
  • 高斯径向基核(RBF):
  • Sigmoid 核:

RBF 核是最常用的,因为它只有一个参数 ,且可以处理非线性关系。

对于不完全线性可分的情况,SVM 引入了软间隔(soft margin),允许部分样本在间隔内部甚至被误分类,通过在目标函数中加入惩罚项 ( 为松弛变量), 是正则化参数,控制对误分类的容忍度。

# 10.2 Scikit-learn 实战(分类、网格调参)

使用 SVC(支持向量分类)对乳腺癌数据集进行分类,并利用网格搜索优化参数。

from sklearn.svm import SVC
from sklearn.model_selection import GridSearchCV

# 定义参数网格
param_grid = {
    'C': [0.1, 1, 10, 100],
    'gamma': [1, 0.1, 0.01, 0.001],
    'kernel': ['rbf']
}

svc = SVC(random_state=42)
grid = GridSearchCV(svc, param_grid, refit=True, verbose=2, cv=5, scoring='accuracy')
grid.fit(X_train, y_train)

print("最佳参数:", grid.best_params_)
print("最佳交叉验证分数:", grid.best_score_)

# 在测试集上评估
y_pred_svm = grid.predict(X_test)
print(f"SVM 测试准确率: {accuracy_score(y_test, y_pred_svm):.4f}")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 10.3 支持向量回归(SVR)

SVR 是 SVM 在回归问题上的扩展。它试图找到一个函数 ,使得所有样本点距离 不超过 ,同时尽可能平滑(即最小化 )。允许一定的误差(由松弛变量处理)。

Scikit-learn 提供了 SVR 类,参数与 SVC 类似。

from sklearn.svm import SVR
from sklearn.metrics import mean_squared_error

# 使用加州房价数据集的一小部分演示
X_svr, y_svr = X_train[:500], y_train[:500]  # 简化

svr = SVR(kernel='rbf', C=100, gamma=0.1, epsilon=0.1)
svr.fit(X_svr, y_svr)
y_pred_svr = svr.predict(X_test[:100])  # 只预测前100个
print("SVR 均方误差:", mean_squared_error(y_test[:100], y_pred_svr))
1
2
3
4
5
6
7
8
9
10

# 第11章 EM 算法与高斯混合模型(GMM)

# 11.1 EM 算法思想

期望最大化(Expectation-Maximization, EM)算法是一种在含有隐变量(latent variables)的概率模型中迭代优化参数的方法。它交替执行两个步骤:

  • E 步:基于当前参数,计算隐变量的后验概率(即隐变量的期望)。
  • M 步:最大化完全数据的对数似然函数的期望,更新参数。

EM 算法保证每次迭代都能提高似然函数的值,最终收敛到局部最优。

# 11.2 高斯混合模型原理与实战

高斯混合模型(Gaussian Mixture Model, GMM)是一种概率模型,假设数据由多个高斯分布混合生成。每个高斯分布代表一个簇。GMM 的目标是估计每个高斯分布的参数(均值、协方差)以及混合系数。

模型概率密度函数:

其中 是混合系数,满足 。

参数估计通常使用 EM 算法:

  • E 步:计算每个样本属于每个高斯分量的责任度(后验概率):

  • M 步:更新参数:

    ,,

GMM 不仅可以用于聚类(软聚类,给出样本属于每个簇的概率),还可以用于密度估计和生成新样本。

实战:GMM 聚类与生成样本

from sklearn.mixture import GaussianMixture
from sklearn.metrics import adjusted_rand_score  # 添加这一行
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris

# 加载数据
iris = load_iris()
X = iris.data

# 使用鸢尾花数据
gmm = GaussianMixture(n_components=3, random_state=42)
gmm.fit(X)

# 预测簇标签
y_gmm = gmm.predict(X)
print("GMM 聚类调整兰德指数:", adjusted_rand_score(iris.target, y_gmm))

# 生成新样本
X_new, _ = gmm.sample(100)
plt.scatter(X[:,0], X[:,1], c=iris.target, cmap='viridis', alpha=0.5, label='原始数据')
plt.scatter(X_new[:,0], X_new[:,1], c='red', marker='x', label='生成样本')
plt.xlabel(iris.feature_names[0])
plt.ylabel(iris.feature_names[1])
plt.legend()
plt.show()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 11.3 模型选择(BIC/AIC)

选择 GMM 的组件数(即簇数)可以使用信息准则:

  • AIC(赤池信息准则):
  • BIC(贝叶斯信息准则): 其中 为参数数量, 为似然函数的最大值。AIC/BIC 越小,模型越好。
bic_scores = []
aic_scores = []
K_range = range(1, 10)

for k in K_range:
    gmm = GaussianMixture(n_components=k, random_state=42)
    gmm.fit(X)
    bic_scores.append(gmm.bic(X))
    aic_scores.append(gmm.aic(X))

plt.plot(K_range, bic_scores, label='BIC')
plt.plot(K_range, aic_scores, label='AIC')
plt.xlabel('组件数')
plt.ylabel('分数')
plt.legend()
plt.show()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 第12章 隐马尔可夫模型(HMM)

# 12.1 HMM 简介与三要素

隐马尔可夫模型(Hidden Markov Model, HMM)是用于时间序列数据的概率模型,它假设系统是一个马尔可夫过程,其状态是隐藏的,只能通过观测值来推断。HMM 广泛应用于语音识别、自然语言处理、生物信息学等领域。

HMM 由三要素定义:

  1. 初始状态概率分布 :,表示初始时刻处于状态 的概率。
  2. 状态转移概率矩阵 :,表示从状态 转移到 的概率。
  3. 观测概率矩阵 :对于离散观测,;对于连续观测,通常用高斯混合分布表示。

HMM 有三个基本问题:

  • 评估问题:给定模型 和观测序列 ,计算 (前向算法)。
  • 解码问题:给定模型和观测序列,找到最可能的状态序列(维特比算法)。
  • 学习问题:给定观测序列,估计模型参数(Baum-Welch 算法,即 EM 算法在 HMM 上的应用)。

# 12.2 使用 hmmlearn 进行实战

Scikit-learn 未包含 HMM,但可以使用第三方库 hmmlearn。安装:pip install hmmlearn

示例:生成数据并训练 Gaussian HMM

假设有两个隐藏状态,每个状态对应一个高斯分布。

import numpy as np
import matplotlib.pyplot as plt
from hmmlearn import hmm

# 构建一个 HMM 模型
# 假设有两个状态,每个状态观测服从一维高斯分布
model = hmm.GaussianHMM(n_components=2, covariance_type="diag")

# 初始状态概率
model.startprob_ = np.array([0.6, 0.4])

# 转移概率矩阵
model.transmat_ = np.array([[0.7, 0.3],
                             [0.4, 0.6]])

# 每个状态的观测均值
model.means_ = np.array([[0.0], [3.0]])

# 每个状态的观测方差(对角线协方差)
model.covars_ = np.array([[0.5], [0.5]])

# 生成观测序列和隐藏状态序列
X, Z = model.sample(100)

plt.plot(X, label='观测值')
plt.plot(Z, label='隐藏状态', linestyle='--')
plt.xlabel('时间步')
plt.legend()
plt.show()

# 训练一个新模型(不知道真实参数)
model2 = hmm.GaussianHMM(n_components=2, covariance_type="diag", n_iter=100)
model2.fit(X)

# 预测隐藏状态
Z_pred = model2.predict(X)

print("真实转移矩阵:\n", model.transmat_)
print("学习到的转移矩阵:\n", model2.transmat_)

# 评估准确率(注意状态标签可能互换,需要匹配)
from sklearn.metrics import accuracy_score
# 由于状态编号可能互换,我们计算两种可能的准确率
acc1 = accuracy_score(Z, Z_pred)
acc2 = accuracy_score(Z, 1 - Z_pred)  # 互换标签
print(f"最佳准确率: {max(acc1, acc2):.3f}")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

HMM 的应用需要仔细设计状态数和观测分布,对于复杂问题可扩展为 GMM-HMM。


# 第六部分:总结与进阶

# 第13章 模型对比与选择

# 13.1 各算法特点总结表

算法 类型 适用问题 优点 缺点 关键参数
KNN 监督(惰性) 分类/回归 简单,无需训练 预测慢,对特征缩放敏感 n_neighbors
线性回归 监督 回归 简单,可解释性强 只能拟合线性关系 无
逻辑回归 监督 分类 输出概率,可解释 线性决策边界 C(正则化)
决策树 监督 分类/回归 可解释,无需缩放 容易过拟合,不稳定 max_depth, min_samples_split
随机森林 集成(Bagging) 分类/回归 精度高,抗过拟合 模型大,解释性差 n_estimators, max_features
GBDT 集成(Boosting) 分类/回归 精度高 串行训练慢,调参复杂 n_estimators, learning_rate, max_depth
XGBoost 集成(Boosting) 分类/回归 高效,正则化 参数较多 eta, max_depth, subsample
LightGBM 集成(Boosting) 分类/回归 速度最快 可能对噪声敏感 num_leaves, learning_rate
SVM 监督 分类/回归 核技巧处理非线性 大数据集训练慢,调参难 C, gamma, kernel
朴素贝叶斯 监督 分类 训练快,适合高维数据 特征独立假设强 无(或平滑参数)
K-Means 无监督 聚类 简单,快速 需指定K,对形状敏感 n_clusters
DBSCAN 无监督 聚类 任意形状,抗噪 参数难调,高维效果差 eps, min_samples
GMM 无监督 聚类/密度估计 软聚类,可生成样本 计算复杂度高 n_components

# 13.2 如何根据问题选择模型

  • 数据规模:

    • 小规模数据(<1000 样本):可尝试 SVM、决策树、KNN。
    • 中等规模(1000-100k):随机森林、GBDT、XGBoost、LightGBM。
    • 大规模(>100k):优先考虑线性模型、LightGBM、随机森林(但需调参)。
  • 特征类型:

    • 数值型连续特征:线性模型、SVM(需缩放)、树模型(不需缩放)。
    • 类别型特征:树模型、朴素贝叶斯(需编码)。
    • 文本数据:朴素贝叶斯、线性 SVM、神经网络。
  • 任务类型:

    • 分类:首选逻辑回归作为基线,然后尝试随机森林、XGBoost。
    • 回归:线性回归作为基线,然后岭回归、Lasso、梯度提升回归。
    • 聚类:先尝试 K-Means(如果数据球形),若效果不好,尝试 DBSCAN、GMM。
  • 可解释性要求:

    • 高:线性模型、决策树、KNN。
    • 低:集成模型、神经网络。
  • 训练时间限制:

    • 有限:线性模型、朴素贝叶斯、KNN(预测慢但训练快)。
    • 充裕:可尝试复杂集成模型。

最终,建议使用交叉验证比较多个模型的性能,并结合领域知识进行选择。

# 第14章 从入坑到封神:学习路径建议

# 14.1 基础 → 入门 → 进阶 → 深入 → 实战 → 扩展

  1. 基础阶段:

    • 掌握 Python 编程基础(NumPy, Pandas, Matplotlib)。
    • 学习统计学基础(均值、方差、概率、分布)。
    • 理解机器学习基本概念(监督/无监督、过拟合、欠拟合、模型评估)。
  2. 入门阶段:

    • 熟练掌握 Scikit-learn 的基本用法(估计器、流水线)。
    • 实践 KNN、线性回归、逻辑回归、决策树等基础模型。
    • 学会数据预处理(标准化、缺失值处理、编码)。
    • 参与 Kaggle 入门比赛(如 Titanic)积累经验。
  3. 进阶阶段:

    • 深入理解集成学习(随机森林、GBDT、XGBoost、LightGBM)。
    • 学习特征工程技巧。
    • 掌握模型调参方法(网格搜索、随机搜索)。
    • 学习无监督学习算法(聚类、降维)。
  4. 深入阶段:

    • 研究 SVM、GMM、HMM 等高级算法原理。
    • 学习深度学习基础(神经网络、CNN、RNN),可转向 PyTorch/TensorFlow。
    • 阅读经典论文,理解算法背后的数学。
  5. 实战阶段:

    • 参与真实项目或竞赛,解决端到端问题。
    • 学习模型部署(Flask, Docker, 云服务)。
    • 学习使用 MLflow 等工具管理实验。
  6. 扩展阶段:

    • 探索前沿领域(强化学习、生成对抗网络、自监督学习)。
    • 关注学术界和工业界的最新进展,保持学习。

# 14.2 最后的寄语

机器学习是一门需要理论和实践紧密结合的学科。不要害怕数学,它是理解算法的钥匙;也不要沉迷理论而忽视动手,代码才是检验真理的标准。遇到困难时,多查文档、多看源码、多与社区交流。记住,每个专家都曾是新手,坚持和热情会让你从“入坑”走向“封神”。

希望这篇教程能成为你机器学习旅程中的良师益友。愿你在数据的世界里乘风破浪,探索无限可能!

最近更新: 2026/9/14
 |  Copyright © -Present Kelsen All Rights Reserved. | Author KelsenJing
机器学习从入坑到封神:面向Python程序员的Scikit-learn实战指南