Python 【机器学习】 进阶 之 【实战案例】MNIST手写数字分类处理 之 [ ROC 曲线 ] [ 多类分类 ] [ 误差分析 ] [ 多标签分类 ] | 2/2

Python 【机器学习】 进阶 之 【实战案例】MNIST手写数字分类处理 之 [ ROC 曲线 ] [ 多类分类 ] [ 误差分析 ] [ 多标签分类 ] | 2/2

目录

Python 【机器学习】 进阶 之 【实战案例】MNIST手写数字分类处理 之 [ ROC 曲线 ] [ 多类分类 ] [ 误差分析 ] [ 多标签分类 ] | 2/2

 一、简单介绍

二、机器学习

1、为什么使用机器学习?

2、机器学习系统的类型,及其对应的学习算法

3、机器学习可利用的开源数据

五、对性能的评估

5、ROC 曲线

六、多类分类

七、误差分析

八、多标签分类

九、多输出分类

附录:

一、一些知识点

1、StratifiedKFold

二、源码工程

三、该案例的环境 package 信息如下


 一、简单介绍

Python是一种跨平台的计算机程序设计语言。是一种面向对象的动态类型语言,最初被设计用于编写自动化脚本(shell),随着版本的不断更新和语言新功能的添加,越多被用于独立的、大型项目的开发。Python是一种解释型脚本语言,可以应用于以下领域: Web 和 Internet开发、科学计算和统计、人工智能、教育、桌面界面开发、软件开发、后端开发、网络爬虫。

Python 机器学习是利用 Python 编程语言中的各种工具和库来实现机器学习算法和技术的过程。Python 是一种功能强大且易于学习和使用的编程语言,因此成为了机器学习领域的首选语言之一。Python 提供了丰富的机器学习库,如Scikit-learn、TensorFlow、Keras、PyTorch等,这些库包含了许多常用的机器学习算法和深度学习框架,使得开发者能够快速实现、测试和部署各种机器学习模型。

通过 Python 进行机器学习,开发者可以利用其丰富的工具和库来处理数据、构建模型、评估模型性能,并将模型部署到实际应用中。Python 的易用性和庞大的社区支持使得机器学习在各个领域都得到了广泛的应用和发展。

二、机器学习

机器学习(Machine Learning)是人工智能(AI)的一个分支领域,其核心思想是通过计算机系统的学习和自动化推理,使计算机能够从数据中获取知识和经验,并利用这些知识和经验进行模式识别、预测和决策。机器学习算法能够自动地从数据中学习并改进自己的性能,而无需明确地编程。这一过程涉及对大量输入数据的分析和解释,以识别数据中的模式和趋势,并生成可以应用于新数据的预测模型。

1、为什么使用机器学习?

使用机器学习的原因主要包括以下几点:

  1. 高效性和准确性:机器学习算法能够处理大规模数据集,并从中提取有价值的信息,其预测和决策的准确性往往高于传统方法。
  2. 自动化和智能化:机器学习能够自动学习和改进,减少了对人工干预的依赖,提高了工作效率和智能化水平。
  3. 广泛应用性:机器学习在各个领域中都有广泛的应用,如图像识别、语音识别、自然语言处理、推荐系统、金融预测等,为许多实际问题的解决提供了有效的方法和工具。
  4. 未来趋势:随着人工智能技术的不断发展,机器学习已成为未来的趋势,掌握机器学习技能将有助于提高职业竞争力和创造力。

2、机器学习系统的类型,及其对应的学习算法

机器学习系统可以根据不同的学习方式和目标进行分类,主要包括以下几种类型及其对应的学习算法:

  1. 监督学习(Supervised Learning)

    • 定义:使用带有标签的训练数据来训练模型,以预测新数据的标签或目标值。
    • 常见算法
      • 线性回归(Linear Regression):用于预测连续值。
      • 逻辑回归(Logistic Regression):用于分类问题,尤其是二分类问题。
      • 支持向量机(SVM, Support Vector Machines):用于分类和回归问题,通过寻找最优的超平面来分割数据。
      • 决策树(Decision Trees)随机森林(Random Forests):通过构建决策树或决策树集合来进行分类或回归。
      • 神经网络(Neural Networks):模仿人脑神经元的工作方式,通过多层节点之间的连接和权重调整来进行学习和预测。
  2. 无监督学习(Unsupervised Learning)

    • 定义:在没有标签的情况下,从数据中发现隐藏的结构和模式。
    • 常见算法
      • 聚类算法(Clustering Algorithms):如K均值(K-Means)、层次聚类分析(HCA)等,用于将数据分组为具有相似特征的簇。
      • 降维算法(Dimensionality Reduction Algorithms):如主成分分析(PCA)、t-分布邻域嵌入算法(t-SNE)等,用于减少数据的维度以便于分析和可视化。
      • 异常检测(Anomaly Detection):用于识别数据中的异常点或离群点。
  3. 强化学习(Reinforcement Learning)

    • 定义:通过与环境的交互学习,以最大化累积奖励为目标。
    • 特点:强化学习不需要明确的标签或监督信号,而是根据环境给出的奖励或惩罚来指导学习过程。
    • 应用场景:游戏AI、机器人控制、自动驾驶等领域。
  4. 半监督学习(Semi-Supervised Learning)

    • 定义:处理部分带标签的训练数据,通常是大量不带标签数据加上小部分带标签数据。
    • 特点:结合了监督学习和无监督学习的特点,旨在利用未标记数据来提高模型的泛化能力。
    • 常见算法:多数半监督学习算法是非监督和监督算法的结合,如自训练(Self-Training)、协同训练(Co-Training)等。

3、机器学习可利用的开源数据

开源数据集可以根据需要进行选择,涵盖多个领域。以下是一些可以查找的数据的地方,供参考:

(注意:代码执行的时候,可能需要科学上网)

。。。。。

五、对性能的评估

。。。。。

5、ROC 曲线

受试者工作特征(ROC)曲线是另一个二分类器常用的工具。它非常类似与准确率/召回率曲线,但不是画出准确率对召回率的曲线,ROC 曲线是真正例率(true positive rate,另一个名字叫做召回率)对假正例率(false positive rate, FPR)的曲线。FPR 是反例被错误分成正例的比率。它等于 1 减去真反例率(true negative rate, TNR)。TNR 是反例被正确分类的比率。TNR 也叫做特异性。所以 ROC 曲线画出召回率对(1 减特异性)的曲线。

为了画出 ROC 曲线,你首先需要计算各种不同阈值下的 TPR、FPR,使用roc_curve()函数:

from sklearn.metrics import roc_curve

# 使用roc_curve函数计算接收者操作特征(ROC)曲线
# y_train_5是训练数据的实际标签的布尔数组,表示标签是否为5
# y_scores是使用cross_val_predict得到的决策函数分数
# 这个函数返回三个数组:
# fpr: 在不同的阈值下计算的假正例率(False Positive Rate)
# tpr: 在不同的阈值下计算的真正例率(True Positive Rate),也称为召回率
# thresholds: 与每个fpr和tpr值对应的阈值
fpr, tpr, thresholds = roc_curve(y_train_5, y_scores)

然后你可以使用 matplotlib,画出 FPR 对 TPR 的曲线。下面的代码生成图 3-6.

import matplotlib.pyplot as plt

# 自定义函数plot_roc_curve,用于绘制接收者操作特征(ROC)曲线
def plot_roc_curve(fpr, tpr, label=None):
    """
    绘制ROC曲线。
    
    参数:
    fpr : ndarray
        不同阈值下的假正例率。
    tpr : ndarray
        不同阈值下的真正例率。
    label : str, optional
        曲线的标签(图例),默认为None。
    """
    # 绘制ROC曲线,linewidth设置线宽,label设置图例
    plt.plot(fpr, tpr, linewidth=2, label=label)
    
    # 绘制参考线,即FPR和TPR都为0或1的线,表示没有分类能力的情况
    plt.plot([0, 1], [0, 1], 'k--')  # 'k'代表黑色,'--'代表虚线
    
    # 设置轴的范围在[0, 1]之间
    plt.axis([0, 1, 0, 1])
    
    # 设置x轴标签和字体大小
    plt.xlabel('False Positive Rate', fontsize=16)
    
    # 设置y轴标签和字体大小
    plt.ylabel('True Positive Rate', fontsize=16)

# 设置绘图的大小为8x6英寸
plt.figure(figsize=(8, 6))

# 调用自定义函数绘制ROC曲线,没有提供标签
plot_roc_curve(fpr, tpr)

# 假设 savefig 是一个已经定义的函数,用于保存当前的图形
# 这里需要确保 savefig 函数能够正确执行,并且有保存图形的权限
plt.savefig("images/roc_curve_plot.png",bbox_inches='tight')

# 显示图形
plt.show()

运行结果:

图3-6 FPR 对 TPR 的曲线

这里同样存在折衷的问题:召回率(TPR)越高,分类器就会产生越多的假正例(FPR)。图中的点线是一个完全随机的分类器生成的 ROC 曲线;一个好的分类器的 ROC 曲线应该尽可能远离这条线(即向左上角方向靠拢)。

一个比较分类器之间优劣的方法是:测量 ROC 曲线下的面积(AUC)。一个完美的分类器的 ROC AUC 等于 1,而一个纯随机分类器的 ROC AUC 等于 0.5。Scikit-Learn 提供了一个函数来计算 ROC AUC:

from sklearn.metrics import roc_auc_score

# 使用roc_auc_score函数计算ROC曲线下的面积(AUC)
# y_train_5是训练数据的实际标签的布尔数组,表示标签是否为5
# y_scores是使用cross_val_predict得到的决策函数分数
# 这个值提供了模型在二分类问题中整体性能的一个度量
# AUC值越高,表示模型区分两个类别的能力越强
roc_auc = roc_auc_score(y_train_5, y_scores)
roc_auc

运行结果:

np.float64(0.9501774882898226)

因为 ROC 曲线跟准确率/召回率曲线(或者叫 PR)很类似,你或许会好奇如何决定使用哪一个曲线呢?一个笨拙的规则是,优先使用 PR 曲线当正例很少,或者当你关注假正例多于假反例的时候。其他情况使用 ROC 曲线。举例子,回顾前面的 ROC 曲线和 ROC AUC 数值,你或许认为这个分类器很棒。但是这几乎全是因为只有少数正例(“是 5”),而大部分是反例(“非 5”)。相反,PR 曲线清楚显示出这个分类器还有很大的改善空间(PR 曲线应该尽可能地靠近右上角)。

让我们训练一个RandomForestClassifier,然后拿它的的 ROC 曲线和 ROC AUC 数值去跟SGDClassifier的比较。首先你需要得到训练集每个样例的数值。但是由于随机森林分类器的工作方式,RandomForestClassifier不提供decision_function()方法。相反,它提供了predict_proba()方法。Skikit-Learn 分类器通常二者中的一个。predict_proba()方法返回一个数组,数组的每一行代表一个样例,每一列代表一个类。数组当中的值的意思是:给定一个样例属于给定类的概率。比如,70% 的概率这幅图是数字 5。

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_predict

# 创建RandomForestClassifier实例,使用随机森林分类器
# n_estimators=10:设置森林中决策树的数量为10
# random_state=42:设置随机状态以确保模型的可重复性
forest_clf = RandomForestClassifier(n_estimators=10, random_state=42)

# 使用cross_val_predict函数和RandomForestClassifier实例
# X_train是训练数据的特征集
# y_train_5是表示训练数据是否为标签5的布尔数组
# cv=3:设置交叉验证的折数为3
# method="predict_proba":指定使用predict_proba方法来获取每类的概率预测
# y_probas_forest是模型预测的概率结果,它是一个数组,包含了每个样本属于每个类别的概率
y_probas_forest = cross_val_predict(forest_clf, X_train, y_train_5, cv=3, method="predict_proba")

但是要画 ROC 曲线,你需要的是样例的分数,而不是概率。一个简单的解决方法是使用正例的概率当作样例的分数。

# 从随机森林分类器的预测概率y_probas_forest中提取正类(即标签为5)的概率
# 在sklearn中,predict_proba方法返回的是一个二维数组,其中每一列对应一个类别的概率
# 由于是二分类问题,我们通过索引1来获取正类的概率值
# y_scores_forest将是一个一维数组,包含了每个样本被预测为正类的概率
y_scores_forest = y_probas_forest[:, 1]  # score = probability of positive class

# 使用roc_curve函数计算随机森林分类器的ROC曲线
# y_train_5是训练数据的实际标签的布尔数组
# y_scores_forest是随机森林分类器预测的正类概率
# 这个函数返回三个数组:
# fpr_forest: 在不同阈值下计算的随机森林分类器的假正例率
# tpr_forest: 在不同阈值下计算的随机森林分类器的真正例率(召回率)
# thresholds_forest: 与每个fpr和tpr值对应的阈值
fpr_forest, tpr_forest, thresholds_forest = roc_curve(y_train_5, y_scores_forest)

现在你即将得到 ROC 曲线。将前面一个分类器的 ROC 曲线一并画出来是很有用的,可以清楚地进行比较。见图 3-7。

import matplotlib.pyplot as plt

# 设置绘图的大小为8x6英寸
plt.figure(figsize=(8, 6))

# 绘制SGD分类器的ROC曲线
# "b:" 表示使用蓝色虚线,linewidth=2 设置线宽为2
# label="SGD" 设置图例为"SGD"
plt.plot(fpr, tpr, "b:", linewidth=2, label="SGD")

# 调用之前定义的plot_roc_curve函数绘制随机森林分类器的ROC曲线
# fpr_forest 和 tpr_forest 是随机森林分类器的假正例率和真正例率
# label="Random Forest" 设置图例为"Random Forest"
plot_roc_curve(fpr_forest, tpr_forest, label="Random Forest")

# 添加图例,loc="lower right" 设置图例位置在右下角,fontsize=16 设置字体大小为16
plt.legend(loc="lower right", fontsize=16)

# 假设 savefig 是一个已经定义的函数,用于保存当前的图形
# "roc_curve_comparison_plot" 是保存的文件名
plt.savefig("images/roc_curve_comparison_plot.png",bbox_inches='tight')

# 显示图形
plt.show()

运行结果:

图 3-7 比较 ROC 曲线

如你所见,RandomForestClassifier的 ROC 曲线比SGDClassifier的好得多:它更靠近左上角。所以,它的 ROC AUC 也会更大。

from sklearn.metrics import roc_auc_score

# 使用roc_auc_score函数计算随机森林分类器的ROC曲线下的面积(AUC)
# y_train_5是训练数据的实际标签的布尔数组
# y_scores_forest是随机森林分类器预测的正类概率
# 这个值提供了模型在二分类问题中整体性能的一个度量
# AUC值越高,表示模型区分两个类别的能力越强
roc_auc_forest = roc_auc_score(y_train_5, y_scores_forest)
roc_auc_forest

运行结果:

np.float64(0.9931874532592573)

计算准确率:

from sklearn.model_selection import cross_val_predict

# 使用cross_val_predict函数执行交叉验证预测
# forest_clf是之前创建并配置好的RandomForestClassifier模型实例
# X_train是训练数据的特征集
# y_train_5是布尔数组,表示训练数据标签是否为5
# cv=3:设置交叉验证的折数为3
# 这个函数将返回在每个折上使用训练数据预测的标签
y_train_pred_forest = cross_val_predict(forest_clf, X_train, y_train_5, cv=3)

# 导入precision_score函数用于计算精确度
from sklearn.metrics import precision_score

# 使用precision_score函数计算随机森林模型预测的精确度
# y_train_5是训练数据的实际标签的布尔数组
# y_train_pred_forest是随机森林模型的预测结果
# 精确度是在所有被模型预测为正类(即5)的样本中,实际为正类的比例
# 这个指标有助于评估模型在预测正类时的准确性
precision_forest = precision_score(y_train_5, y_train_pred_forest)
precision_forest

运行结果:

np.float64(0.985497692814766)

计算召回率:

from sklearn.metrics import recall_score

# 使用recall_score函数计算随机森林模型预测的召回率
# y_train_5是训练数据的实际标签的布尔数组,表示标签是否为5
# y_train_pred_forest是使用cross_val_predict得到的随机森林模型的预测结果
# 召回率(recall)是在所有实际为正类(即5)的样本中,被模型正确预测为正类的比例
# 这个指标有助于评估模型捕捉所有正类样本的能力
recall_forest = recall_score(y_train_5, y_train_pred_forest)
recall_forest

运行结果:

np.float64(0.8273381294964028)

计算一下准确率和召回率:98.5% 的准确率,82.8% 的召回率。还不错。

现在你知道如何训练一个二分类器,选择合适的标准,使用交叉验证去评估你的分类器,选择满足你需要的准确率/召回率折衷方案,和比较不同模型的 ROC 曲线和 ROC AUC 数值。现在让我们检测更多的数字,而不仅仅是一个数字 5。

六、多类分类

二分类器只能区分两个类,而多类分类器(也被叫做多项式分类器)可以区分多于两个类。

一些算法(比如随机森林分类器或者朴素贝叶斯分类器)可以直接处理多类分类问题。其他一些算法(比如 SVM 分类器或者线性分类器)则是严格的二分类器。然后,有许多策略可以让你用二分类器去执行多类分类。

举例子,创建一个可以将图片分成 10 类(从 0 到 9)的系统的一个方法是:训练 10 个二分类器,每一个对应一个数字(探测器 0,探测器 1,探测器 2,以此类推)。然后当你想对某张图片进行分类的时候,让每一个分类器对这个图片进行分类,选出决策分数最高的那个分类器。这叫做“一对所有”(OvA)策略(也被叫做“一对其他”)。

另一个策略是对每一对数字都训练一个二分类器:一个分类器用来处理数字 0 和数字 1,一个用来处理数字 0 和数字 2,一个用来处理数字 1 和 2,以此类推。这叫做“一对一”(OvO)策略。如果有 N 个类。你需要训练N*(N-1)/2个分类器。对于 MNIST 问题,需要训练 45 个二分类器!当你想对一张图片进行分类,你必须将这张图片跑在全部 45 个二分类器上。然后看哪个类胜出。OvO 策略的主要优点是:每个分类器只需要在训练集的部分数据上面进行训练。这部分数据是它所需要区分的那两个类对应的数据。

一些算法(比如 SVM 分类器)在训练集的大小上很难扩展,所以对于这些算法,OvO 是比较好的,因为它可以在小的数据集上面可以更多地训练,较之于巨大的数据集而言。但是,对于大部分的二分类器来说,OvA 是更好的选择。

Scikit-Learn 可以探测出你想使用一个二分类器去完成多分类的任务,它会自动地执行 OvA(除了 SVM 分类器,它使用 OvO)。让我们试一下SGDClassifier.

# 使用SGDClassifier模型的fit方法对训练数据进行训练
# X_train是训练数据的特征集,包含了60000个样本的特征
# y_train是训练数据的标签集,包含了对应的60000个样本的标签
# 调用fit方法将训练SGDClassifier模型,学习特征和标签之间的关系
sgd_clf.fit(X_train, y_train)

# 使用训练好的SGDClassifier模型的predict方法对单个样本some_digit进行预测
# some_digit是一个784维的NumPy数组,表示一个已经预处理的手写数字图像
# predict方法将返回模型预测的标签
# 由于some_digit只是一个单一样本,predict方法将返回一个包含预测标签的数组
predicted_label = sgd_clf.predict([some_digit])
predicted_label

运行结果:

array([5], dtype=int8)

很容易。上面的代码在训练集上训练了一个SGDClassifier。这个分类器处理原始的目标类,从 0 到 9(y_train),而不是仅仅探测是否为 5 (y_train_5)。然后它做出一个判断(在这个案例下只有一个正确的数字)。在幕后,Scikit-Learn 实际上训练了 10 个二分类器,每个分类器都产到一张图片的决策数值,选择数值最高的那个类。

为了证明这是真实的,你可以调用decision_function()方法。不是返回每个样例的一个数值,而是返回 10 个数值,一个数值对应于一个类。

# 使用SGDClassifier模型的decision_function方法对单个样本some_digit进行评分
# some_digit是一个784维的NumPy数组,表示一个已经预处理的手写数字图像
# decision_function方法将返回模型对样本属于每个类别的原始分数
# 这些分数可以用于评估模型对样本分类的置信度
some_digit_scores = sgd_clf.decision_function([some_digit])

# 打印some_digit_scores,这将显示模型对some_digit图像属于每个类别的评分
# 注意,由于这是一个多分类问题,scores数组将包含10个元素,对应数字0到9的分数
some_digit_scores

运行结果:

array([[-249802.43533206, -483640.95704596, -156006.85951147,
        -195744.08672469, -465614.01876187,  143015.84803923,
        -538655.44089214, -323160.86696154, -690036.82487272,
        -555925.38879254]])

高数值是对应于类别 5 :

import numpy as np

# 使用numpy的argmax函数找到some_digit_scores中最大元素的索引
# some_digit_scores是模型对单个样本some_digit属于每个类别的评分数组
# np.argmax将返回scores数组中最大值的索引,这代表了模型最可能预测的类别
# 例如,如果返回值是2,这意味着模型预测some_digit最可能是数字3(索引从0开始)
predicted_digit_index = np.argmax(some_digit_scores)

# 打印predicted_digit_index,这将显示模型预测的数字类别
predicted_digit_index

运行结果:

np.int64(5)
# sgd_clf.classes_是SGDClassifier模型的一个属性
# 它包含了模型在训练后用于分类的所有类别的列表
# 对于MNIST数据集,这个列表将包含从0到9的整数,表示手写数字的10个类别
# 这个属性在模型经过fit方法训练后被设置,它反映了训练数据中出现的类别
# 可以通过打印sgd_clf.classes_来查看模型识别的类别
print(sgd_clf.classes_)

运行结果:

[0 1 2 3 4 5 6 7 8 9]
# sgd_clf.classes_属性包含了模型可以分类的所有类别的列表
# 通过访问特定的索引,比如[5],我们可以获取到对应索引的类别
# 例如,在MNIST数据集中,索引5对应的类别是数字5
# 这个操作可以用来检查模型是否学习到了正确的类别,或者用于获取特定类别的标签
category_index_5 = sgd_clf.classes_[5]

# 打印category_index_5,这将显示索引5对应的类别标签
print(category_index_5)

运行结果:

5

一个分类器被训练好了之后,它会保存目标类别列表到它的属性classes_ 中去,按照值排序。在本例子当中,在classes_ 数组当中的每个类的索引方便地匹配了类本身,比如,索引为 5 的类恰好是类别 5 本身。但通常不会这么幸运。

如果你想强制 Scikit-Learn 使用 OvO 策略或者 OvA 策略,你可以使用OneVsOneClassifier类或者OneVsRestClassifier类。创建一个样例,传递一个二分类器给它的构造函数。举例子,下面的代码会创建一个多类分类器,使用 OvO 策略,基于SGDClassifier

from sklearn.multiclass import OneVsOneClassifier
from sklearn.linear_model import SGDClassifier
import numpy as np

# 创建SGDClassifier实例,设置合适的参数
# max_iter=5:设置最大迭代次数为5
# tol=0:设置容忍度为0,意味着不会提前停止训练
# random_state=42:设置随机状态为42,以确保结果的可重复性
sgd_clf = SGDClassifier(max_iter=5, tol=0, random_state=42)

# 创建OneVsOneClassifier的实例,使用上面的SGDClassifier作为基分类器
ovo_clf = OneVsOneClassifier(estimator=sgd_clf)

# 使用fit方法训练OneVsOneClassifier
# X_train是训练数据的特征集,y_train是训练数据的标签集
# 这个fit方法将在内部为每一对类别训练一个SGDClassifier模型
ovo_clf.fit(X_train, y_train)

# 使用训练好的OneVsOneClassifier的predict方法对单个样本some_digit进行预测
# some_digit是一个784维的NumPy数组,表示一个已经预处理的手写数字图像
# predict方法将返回模型预测的标签
predicted_label_ovo = ovo_clf.predict([some_digit])
predicted_label_ovo

运行结果:

array([5], dtype=int8)
# OneVsOneClassifier在内部为每对类别组合训练一个SGDClassifier模型
# estimators_属性包含了所有这些训练好的SGDClassifier模型的列表
# len(ovo_clf.estimators_)将返回这些模型的数量

# 例如,在MNIST数据集中,因为有10个类别,将有10 * (10 - 1) / 2 = 45个SGDClassifier模型
# 这是因为每个类别将与其他9个类别进行比较,但比较是成对的,所以除以2
# 打印len(ovo_clf.estimators_)将显示训练好的SGDClassifier模型的总数
num_of_estimators = len(ovo_clf.estimators_)
num_of_estimators

运行结果:

45

训练一个RandomForestClassifier同样简单:

# 使用之前创建的RandomForestClassifier实例forest_clf对训练数据进行训练
# X_train是训练数据的特征集,包含了60000个样本的特征
# y_train是训练数据的标签集,包含了对应的60000个样本的标签
# 调用fit方法将训练RandomForestClassifier模型,学习特征和标签之间的关系
forest_clf.fit(X_train, y_train)

# 使用训练好的RandomForestClassifier模型的predict方法对单个样本some_digit进行预测
# some_digit是一个784维的NumPy数组,表示一个已经预处理的手写数字图像
# predict方法将返回模型预测的标签
predicted_label_forest = forest_clf.predict([some_digit])
predicted_label_forest

运行结果:

array([5], dtype=int8)

这次 Scikit-Learn 没有必要去运行 OvO 或者 OvA,因为随机森林分类器能够直接将一个样例分到多个类别。你可以调用predict_proba(),得到样例对应的类别的概率值的列表:

# 使用训练好的RandomForestClassifier模型的predict_proba方法对单个样本some_digit进行概率预测
# some_digit是一个784维的NumPy数组,表示一个已经预处理的手写数字图像
# predict_proba方法将返回模型预测的每个类别的概率
# 这与predict方法不同,后者返回最可能的类别标签,而predict_proba提供所有类别的概率分布
probabilities = forest_clf.predict_proba([some_digit])
probabilities

运行结果:

array([[0.1, 0. , 0. , 0. , 0. , 0.9, 0. , 0. , 0. , 0. ]])

你可以看到这个分类器相当确信它的预测:在数组的索引 5 上的 0.9,意味着这个模型以 80% 的概率估算这张图片代表数字 5。它也认为这个图片可能是数字 0 ,分别都是 10% 的几率。

现在当然你想评估这些分类器。像平常一样,你想使用交叉验证。让我们用cross_val_score()来评估SGDClassifier的精度。

from sklearn.model_selection import cross_val_score

# 使用cross_val_score函数计算SGDClassifier模型在X_train数据集上的交叉验证准确率得分
# sgd_clf是已经初始化的SGDClassifier模型实例
# X_train是训练数据的特征集
# y_train是训练数据对应的标签集
# cv=3表示将数据集分割成3个部分进行交叉验证,即3折交叉验证
# scoring="accuracy"指定了评分标准为准确率,即正确预测的样本数占总样本数的比例
# 这个函数将返回一个数组,包含每次折的准确率得分,这些得分可以用于评估模型的稳定性和性能
scores = cross_val_score(sgd_clf, X_train, y_train, cv=3, scoring="accuracy")
scores

运行结果:

array([0.8735, 0.8689, 0.8776])

在所有测试折(test fold)上,它有 86% 的精度。如果你是用一个随机的分类器,你将会得到 10% 的正确率。所以这不是一个坏的分数,但是你可以做的更好。举例子,简单将输入正则化,将会提高精度到 90% 以上。

from sklearn.preprocessing import StandardScaler
import numpy as np
from sklearn.model_selection import cross_val_score

# 创建StandardScaler实例,用于对特征进行标准化处理
# StandardScaler将数据转换为均值为0,标准差为1的标准正态分布
scaler = StandardScaler()

# 使用fit_transform方法对X_train进行标准化
# 首先将X_train的数据类型转换为np.float64,确保标准化处理的精度
# fit_transform方法先拟合X_train的数据分布,然后转换X_train的数据
# 结果存储在X_train_scaled,它是标准化后的X_train
X_train_scaled = scaler.fit_transform(X_train.astype(np.float64))

# 使用cross_val_score函数计算SGDClassifier模型在标准化后的X_train数据集上的交叉验证准确率得分
# sgd_clf是已经初始化的SGDClassifier模型实例
# X_train_scaled是经过标准化处理的训练数据特征集
# y_train是训练数据对应的标签集
# cv=3表示将数据集分割成3个部分进行交叉验证,即3折交叉验证
# scoring="accuracy"指定了评分标准为准确率
# 这个函数将返回一个数组,包含每次折的准确率得分
scores = cross_val_score(sgd_clf, X_train_scaled, y_train, cv=3, scoring="accuracy")
scores

运行结果:

array([0.9116, 0.9118, 0.9073])

七、误差分析

当然,如果这是一个实际的项目,你会在你的机器学习项目当中,跟随以下步骤(见附录 B):探索准备数据的候选方案,尝试多种模型,把最好的几个模型列为入围名单,用GridSearchCV调试超参数,尽可能地自动化,像你前面的章节做的那样。在这里,我们假设你已经找到一个不错的模型,你试图找到方法去改善它。一个方式是分析模型产生的误差的类型。

首先,你可以检查混淆矩阵。你需要使用cross_val_predict()做出预测,然后调用confusion_matrix()函数,像你早前做的那样。

from sklearn.model_selection import cross_val_predict
from sklearn.metrics import confusion_matrix

# 使用cross_val_predict函数在标准化后的X_train数据集上进行交叉验证预测
# sgd_clf是已经初始化的SGDClassifier模型实例
# X_train_scaled是经过标准化处理的训练数据特征集
# y_train是训练数据对应的标签集
# cv=3表示将数据集分割成3个部分进行交叉验证,即3折交叉验证
# 这个方法将返回在每个折上使用训练数据预测的标签
y_train_pred = cross_val_predict(sgd_clf, X_train_scaled, y_train, cv=3)

# 使用confusion_matrix函数计算y_train(实际标签)和y_train_pred(预测标签)之间的混淆矩阵
# 混淆矩阵是一个表格,用于描述分类模型的性能
# 它显示了每个类别的真实和预测标签的分布情况
# 对角线上的值表示正确分类的数量,而非对角线上的值表示分类错误的数量
conf_mx = confusion_matrix(y_train, y_train_pred)

# 打印conf_mx,这将显示模型性能的混淆矩阵
print(conf_mx)

运行结果:

[[5738    2   23    9   10   43   42    9   41    6]
 [   1 6480   43   24    7   38    8   12  117   12]
 [  56   34 5355   85   91   18   93   52  157   17]
 [  53   45  144 5320    4  231   40   54  143   97]
 [  20   27   35    9 5380    8   50   27   87  199]
 [  70   35   33  193   70 4570  110   30  205  105]
 [  34   20   37    2   49   81 5642    3   50    0]
 [  22   18   69   27   51   11    6 5772   22  267]
 [  51  153   74  149   16  148   55   25 5047  133]
 [  45   35   24   84  159   30    3  178   81 5310]]

这里是一对数字。使用 Matplotlib 的matshow()函数,将混淆矩阵以图像的方式呈现,将会更加方便。

import matplotlib.pyplot as plt

def plot_confusion_matrix(matrix):
    """
    绘制混淆矩阵的函数,使用颜色和颜色条增强可视化效果。
    
    参数:
    matrix : ndarray
        混淆矩阵,一个二维的NumPy数组,表示模型预测的性能。
        
    该函数创建一个图形,显示混淆矩阵,并使用颜色条来表示不同分类结果的密度或数量。
    """
    # 设置图形的大小为8x8英寸
    fig = plt.figure(figsize=(8,8))
    
    # 在图形中添加一个子图,111表示1x1网格中的单一子图
    ax = fig.add_subplot(111)
    
    # 使用matshow方法显示混淆矩阵,该方法会以矩阵形式显示数据,并使用颜色映射
    cax = ax.matshow(matrix)
    
    # 添加颜色条到图形中,cax是matshow返回的颜色映射轴对象
    # 颜色条提供了一个参考,用于解释矩阵中不同颜色的数值大小
    fig.colorbar(cax)

import matplotlib.pyplot as plt

# 使用plt.matshow方法显示混淆矩阵conf_mx
# cmap=plt.cm.gray指定了使用灰度颜色映射来显示矩阵
# 这将创建一个显示混淆矩阵的图形,颜色的深浅代表不同的数值大小
plt.matshow(conf_mx, cmap=plt.cm.gray)

# 假设 savefig 是一个已经定义的函数,用于保存当前的图形
# "confusion_matrix_plot"是指定的保存文件名
plt.savefig("images/confusion_matrix_plot.png",bbox_inches='tight')

# 显示图形,使用plt.show()可以在绘图窗口中查看图形
# 这允许用户在屏幕上查看混淆矩阵的可视化效果
plt.show()

运行结果:

这个混淆矩阵看起来相当好,因为大多数的图片在主对角线上。在主对角线上意味着被分类正确。数字 5 对应的格子看起来比其他数字要暗淡许多。这可能是数据集当中数字 5 的图片比较少,又或者是分类器对于数字 5 的表现不如其他数字那么好。你可以验证两种情况。

让我们关注仅包含误差数据的图像呈现。首先你需要将混淆矩阵的每一个值除以相应类别的图片的总数目。这样子,你可以比较错误率,而不是绝对的错误数(这对大的类别不公平)。

import numpy as np

# 计算混淆矩阵conf_mx的每一行的总和
# axis=1指定了沿着水平方向(即矩阵的行)进行求和
# keepdims=True表示保持维度,这样row_sums的形状将与conf_mx保持一致,除了求和的维度
row_sums = conf_mx.sum(axis=1, keepdims=True)

# 将混淆矩阵conf_mx通过行总和row_sums进行归一化
# 归一化后的矩阵norm_conf_mx中的每个元素代表对应类别的真实样本在该类别中所占的比例
# 这为我们提供了一个概率分布,其中每一行的和为1,表示对每个类别的预测分布
norm_conf_mx = conf_mx / row_sums

现在让我们用 0 来填充对角线。这样子就只保留了被错误分类的数据。让我们画出这个结果。

import numpy as np
import matplotlib.pyplot as plt

# 将归一化混淆矩阵norm_conf_mx的对角线元素填充为0
# 对角线元素代表正确分类的比例,这里将其设置为0是为了更清楚地显示错误分类的情况
np.fill_diagonal(norm_conf_mx, 0)

# 使用plt.matshow方法显示归一化混淆矩阵norm_conf_mx,突出显示错误分类
# cmap=plt.cm.gray指定了使用灰度颜色映射来显示矩阵
plt.matshow(norm_conf_mx, cmap=plt.cm.gray)

# 假设 savefig 是一个已经定义的函数,用于保存当前的图形
# "confusion_matrix_errors_plot"是指定的保存文件名
plt.savefig("images/confusion_matrix_errors_plot.png",bbox_inches='tight')

# 显示图形,使用plt.show()可以在绘图窗口中查看图形
# 这允许用户在屏幕上查看归一化混淆矩阵的可视化效果,特别是错误分类的部分
plt.show()

运行结果:

现在你可以清楚看出分类器制造出来的各类误差。记住:行代表实际类别,列代表预测的类别。第 8、9 列相当亮,这告诉你许多图片被误分成数字 8 或者数字 9。相似的,第 8、9 行也相当亮,告诉你数字 8、数字 9 经常被误以为是其他数字。相反,一些行相当黑,比如第一行:这意味着大部分的数字 1 被正确分类(一些被误分类为数字 8 )。留意到误差图不是严格对称的。举例子,比起将数字 8 误分类为数字 5 的数量,有更多的数字 5 被误分类为数字 8。

分析混淆矩阵通常可以给你提供深刻的见解去改善你的分类器。回顾这幅图,看样子你应该努力改善分类器在数字 8 和数字 9 上的表现,和纠正3/5的混淆。举例子,你可以尝试去收集更多的数据,或者你可以构造新的、有助于分类器的特征。举例子,写一个算法去数闭合的环(比如,数字 8 有两个环,数字 6 有一个, 5 没有)。又或者你可以预处理图片(比如,使用 Scikit-Learn,Pillow, OpenCV)去构造一个模式,比如闭合的环。

分析独特的误差,是获得关于你的分类器是如何工作及其为什么失败的洞见的一个好途径。但是这相对难和耗时。举例子,我们可以画出数字 3 和 5 的例子

import numpy as np
import matplotlib.pyplot as plt

# 定义两个类别的标签值,例如数字3和数字5
cl_a, cl_b = 3, 5

# 使用布尔索引选择X_train中真实标签和预测标签都为cl_a的样本
X_aa = X_train[(y_train == cl_a) & (y_train_pred == cl_a)]

# 使用布尔索引选择X_train中真实标签为cl_a,但预测标签为cl_b的样本
X_ab = X_train[(y_train == cl_a) & (y_train_pred == cl_b)]

# 使用布尔索引选择X_train中真实标签为cl_b,但预测标签为cl_a的样本
X_ba = X_train[(y_train == cl_b) & (y_train_pred == cl_a)]

# 使用布尔索引选择X_train中真实标签和预测标签都为cl_b的样本
X_bb = X_train[(y_train == cl_b) & (y_train_pred == cl_b)]

# 设置图形的大小为8x8英寸
plt.figure(figsize=(8,8))

# 绘制四个子图,分别展示X_aa, X_ab, X_ba, X_bb中的样本图像
# 每个子图显示25个样本,每行显示5个图像
plt.subplot(221); plot_digits(X_aa[:25], images_per_row=5)
plt.subplot(222); plot_digits(X_ab[:25], images_per_row=5)
plt.subplot(223); plot_digits(X_ba[:25], images_per_row=5)
plt.subplot(224); plot_digits(X_bb[:25], images_per_row=5)

# 假设 savefig 是一个已经定义的函数,用于保存当前的图形
# "error_analysis_digits_plot"是指定的保存文件名
plt.savefig("images/error_analysis_digits_plot.png",bbox_inches='tight')

# 显示图形,使用plt.show()可以在绘图窗口中查看图形
# 这允许用户在屏幕上查看不同分类情况下的样本图像
plt.show()

运行结果:

上边两个5*5的块将数字识别为 3,下边的将数字识别为 5。一些被分类器错误分类的数字(比如左下角和右上角的块)是书写地相当差,甚至让人类分类都会觉得很困难(比如第 8 行第 1 列的数字 5,看起来非常像数字 3 )。但是,大部分被误分类的数字,在我们看来都是显而易见的错误。很难明白为什么分类器会分错。原因是我们使用的简单的SGDClassifier,这是一个线性模型。它所做的全部工作就是分配一个类权重给每一个像素,然后当它看到一张新的图片,它就将加权的像素强度相加,每个类得到一个新的值。所以,因为 3 和 5 只有一小部分的像素有差异,这个模型很容易混淆它们。

3 和 5 之间的主要差异是连接顶部的线和底部的线的细线的位置。如果你画一个 3,连接处稍微向左偏移,分类器很可能将它分类成 5。反之亦然。换一个说法,这个分类器对于图片的位移和旋转相当敏感。所以,减轻3/5混淆的一个方法是对图片进行预处理,确保它们都很好地中心化和不过度旋转。这同样很可能帮助减轻其他类型的错误。

八、多标签分类

到目前为止,所有的样例都总是被分配到仅一个类。有些情况下,你也许想让你的分类器给一个样例输出多个类别。比如说,思考一个人脸识别器。如果对于同一张图片,它识别出几个人,它应该做什么?当然它应该给每一个它识别出的人贴上一个标签。比方说,这个分类器被训练成识别三个人脸,Alice,Bob,Charlie;然后当它被输入一张含有 Alice 和 Bob 的图片,它应该输出[1, 0, 1](意思是:Alice 是,Bob 不是,Charlie 是)。这种输出多个二值标签的分类系统被叫做多标签分类系统。

目前我们不打算深入脸部识别。我们可以先看一个简单点的例子,仅仅是为了阐明的目的。

from sklearn.neighbors import KNeighborsClassifier
import numpy as np

# 将y_train转换为一个布尔数组,其中的元素表示原始标签是否大于或等于7
y_train_large = (y_train >= 7)

# 将y_train转换为一个布尔数组,其中的元素表示原始标签是否为奇数
y_train_odd = (y_train % 2 == 1)

# 使用np.c_将y_train_large和y_train_odd两个布尔数组按列合并
# 这将创建一个新的多标签数组y_multilabel,其中每行代表一个样本的多个标签
y_multilabel = np.c_[y_train_large, y_train_odd]

# 创建KNeighborsClassifier的实例,这是一个k近邻分类器
knn_clf = KNeighborsClassifier()

# 使用fit方法训练knn_clf模型
# X_train是训练数据的特征集,y_multilabel是对应的多标签数组
# 这个fit方法将训练knn_clf模型,使其能够根据X_train的特征预测y_multilabel中的标签
knn_clf.fit(X_train, y_multilabel)

运行结果:

这段代码创造了一个y_multilabel数组,里面包含两个目标标签。第一个标签指出这个数字是否为大数字(7,8 或者 9),第二个标签指出这个数字是否是奇数。接下来几行代码会创建一个KNeighborsClassifier样例(它支持多标签分类,但不是所有分类器都可以),然后我们使用多目标数组来训练它。现在你可以生成一个预测,然后它输出两个标签:

# 使用训练好的KNeighborsClassifier模型knn_clf对单个样本some_digit进行预测
# some_digit是一个784维的NumPy数组,表示一个已经预处理的手写数字图像
# predict方法将根据knn_clf模型的学习和some_digit的特征,预测其对应的标签
# 对于多标签分类问题,预测结果将是一个布尔数组,表示样本对于每个标签的隶属情况
predicted_labels = knn_clf.predict([some_digit])
predicted_labels

运行结果:

array([[False,  True]])

它工作正确。数字 5 不是大数(False),同时是一个奇数(True)。

有许多方法去评估一个多标签分类器,和选择正确的量度标准,这取决于你的项目。举个例子,一个方法是对每个个体标签去量度 F1 值(或者前面讨论过的其他任意的二分类器的量度标准),然后计算平均值。下面的代码计算全部标签的平均 F1 值:

from sklearn.model_selection import cross_val_predict
from sklearn.metrics import f1_score

# 使用cross_val_predict函数在X_train数据集上使用knn_clf模型进行交叉验证预测
# knn_clf是已经初始化的KNeighborsClassifier模型实例
# X_train是训练数据的特征集
# y_multilabel是对应的多标签数组,包含了样本的多个标签
# cv=3表示将数据集分割成3个部分进行交叉验证,即3折交叉验证
# n_jobs=-1表示使用所有可用的CPU核心来加速交叉验证过程
# 这个方法将返回在每个折上使用训练数据预测的标签
y_train_knn_pred = cross_val_predict(knn_clf, X_train, y_multilabel, cv=3, n_jobs=-1)

# 使用f1_score函数计算knn_clf模型在多标签分类问题上的F1分数
# y_multilabel是真实标签数组,y_train_knn_pred是预测得到的标签数组
# average="macro"表示计算宏平均F1分数,即每个标签的F1分数的简单平均
# 宏平均对所有类别的F1分数给予相同权重,不考虑类别样本数量的不平衡
f1_macro = f1_score(y_multilabel, y_train_knn_pred, average="macro")
f1_macro

运行结果:

np.float64(0.9769702839959413)

这里假设所有标签有着同等的重要性,但可能不是这样。特别是,如果你的 Alice 的照片比 Bob 或者 Charlie 更多的时候,也许你想让分类器在 Alice 的照片上具有更大的权重。一个简单的选项是:给每一个标签的权重等于它的支持度(比如,那个标签的样例的数目)。为了做到这点,简单地在上面代码中设置average="weighted"

九、多输出分类

我们即将讨论的最后一种分类任务被叫做“多输出-多类分类”(或者简称为多输出分类)。它是多标签分类的简单泛化,在这里每一个标签可以是多类别的(比如说,它可以有多于两个可能值)。

为了说明这点,我们建立一个系统,它可以去除图片当中的噪音。它将一张混有噪音的图片作为输入,期待它输出一张干净的数字图片,用一个像素强度的数组表示,就像 MNIST 图片那样。注意到这个分类器的输出是多标签的(一个像素一个标签)和每个标签可以有多个值(像素强度取值范围从 0 到 255)。所以它是一个多输出分类系统的例子。

分类与回归之间的界限是模糊的,比如这个例子。按理说,预测一个像素的强度更类似于一个回归任务,而不是一个分类任务。而且,多输出系统不限于分类任务。你甚至可以让你一个系统给每一个样例都输出多个标签,包括类标签和值标签。

让我们从 MNIST 的图片创建训练集和测试集开始,然后给图片的像素强度添加噪声,这里是用 NumPy 的randint()函数。目标图像是原始图像。

import numpy as np

# 创建一个与X_train相同形状的数组,其中填充0到100之间的随机整数
# 这将作为噪声添加到原始的X_train数据中
noise = np.random.randint(0, 100, (len(X_train), 784))

# 将噪声添加到X_train的原始数据中,形成新的训练数据集X_train_mod
X_train_mod = X_train + noise

# 用相同的方法创建噪声并添加到测试数据集X_test中,形成X_test_mod
noise = np.random.randint(0, 100, (len(X_test), 784))
X_test_mod = X_test + noise

# 下面的两行代码是错误的,因为它们将训练和测试标签分别赋值为X_train和X_test的数据,
# 这不符合逻辑,因为标签应该是原始数据集的真实标签,而不是特征数据。
# 这可能是一个错误,通常我们不会这样修改标签。
y_train_mod = X_train  # 错误:标签不应该被修改为特征数据
y_test_mod = X_test     # 错误:标签不应该被修改为特征数据
import matplotlib.pyplot as plt

# 选择一个特定的索引来获取X_test_mod和y_test_mod中的图像
# 这个索引用于获取两个数组中相应位置的样本
some_index = 5500

# 设置图形为1行2列的子图布局,并激活第1个子图(从0开始计数)
plt.subplot(121); 
# 调用plot_digit函数绘制X_test_mod中索引为some_index的图像
# 这应该是一个添加了噪声的手写数字图像
plot_digit(X_test_mod[some_index])

# 激活第2个子图(索引为1)
plt.subplot(122); 
# 调用plot_digit函数绘制y_test_mod中索引为some_index的图像
# 根据之前的代码,y_test_mod错误地被赋值为X_test,所以这将不是一个标签图像
# 正确的做法应该是绘制原始的y_test中的真实标签图像
plot_digit(y_test_mod[some_index])
 
# 假设 savefig 是一个已经定义的函数,用于保存当前的图形
# "noisy_digit_example_plot"是指定的保存文件名
plt.savefig("images/noisy_digit_example_plot.png",bbox_inches='tight')

# 显示图形,使用plt.show()可以在绘图窗口中查看图形
# 这允许用户在屏幕上查看添加了噪声的图像和对应的真实标签图像
plt.show()

运行结果:

左边的加噪声的输入图片。右边是干净的目标图片。现在我们训练分类器,让它清洁这张图片:

# 使用修改后的训练数据集X_train_mod和错误赋值的标签集y_train_mod训练knn_clf模型
# 这里使用fit方法更新knn_clf模型,使其学习带有噪声的数据集特征
knn_clf.fit(X_train_mod, y_train_mod)

# 使用训练好的knn_clf模型对单个样本X_test_mod[some_index]进行预测
# predict方法将返回模型预测的最可能的类别
# 然而,由于KNeighborsClassifier是用于分类的,它实际上不直接输出一个图像
# 这里可能的意图是使用knn_clf来估计噪声并尝试“清洁”图像
# 但正确的做法是使用去噪技术或获取一个干净的图像数据集
clean_digit = knn_clf.predict([X_test_mod[some_index]])

# 假设plot_digit函数用于绘制单个图像,这里将绘制knn_clf预测的“清洁”后的图像
# 然而,由于predict返回的是类别标签,而不是图像,这个调用可能是错误的
# 如果clean_digit确实是一个图像,那么可以绘制它
plot_digit(clean_digit)

# 假设 savefig 是一个已经定义的函数,用于保存当前的图形
# "cleaned_digit_example_plot"是指定的保存文件名
# 这个函数将保存绘制的“清洁”后的图像
plt.savefig("images/cleaned_digit_example_plot.png",bbox_inches='tight')

运行结果:

看起来足够接近目标图片。现在总结我们的分类之旅。希望你现在应该知道如何选择好的量度标准,挑选出合适的准确率/召回率的折衷方案,比较分类器,更概括地说,就是为不同的任务建立起好的分类系统。

附录:

一、一些知识点

1、StratifiedKFold

StratifiedKFoldscikit-learn 库中的一个类,它用于实现分层K折交叉验证(Stratified Cross-Validation)。这种交叉验证方法特别适用于分类问题,尤其是在数据集中的类别分布不均衡时。以下是 StratifiedKFold 的一些关键点:

定义和目的

  • 分层K折交叉验证:将数据集分割成K个子集(或“折”),每个子集在验证模型性能时都作为测试集,而其余的K-1个子集合并作为训练集。这个过程重复K次,每次选择不同的子集作为测试集。
  • 分层:确保每个子集(训练集和测试集)中的类别分布与整个数据集的类别分布大致相同。这对于提高模型评估的准确性和减少评估偏差非常重要。

特点

  • 保持类别比例StratifiedKFold 通过分层抽样来保持每个折中正类和负类的比例,与整个数据集的比例一致。
  • 适用于不均衡数据:在类别不均衡的数据集中,普通的K折交叉验证可能导致训练集和测试集中某些类别的样本数量过少,而 StratifiedKFold 可以减少这种情况。

使用方法

使用 StratifiedKFold 通常涉及以下步骤:

  1. 实例化:创建 StratifiedKFold 对象,指定 n_splits(折数)和其他参数,如 random_state(随机状态,用于可重复性)和 shuffle(是否在分割前打乱数据)。
  2. 分割数据:使用对象的 split 方法对数据集进行分割,该方法返回训练集和测试集的索引。
  3. 交叉验证循环:在每次迭代中,使用返回的索引从原始数据集中分割出训练集和测试集。
  4. 模型训练和评估:对每个折进行模型训练和评估,记录性能指标。
from sklearn.model_selection import StratifiedKFold

# 假设y是目标变量,包含类别标签
skf = StratifiedKFold(n_splits=5, random_state=42, shuffle=True)

for train_index, test_index in skf.split(X, y):
    X_train, X_test = X[train_index], X[test_index]
    y_train, y_test = y[train_index], y[test_index]
    # 在这里训练和评估模型

注意事项

  • 当数据集中的类别分布非常不均匀时,StratifiedKFold 尤其有用。
  • 如果数据集足够大,即使在分层抽样后,某些类别在某些折中的样本数量也可能很少。这需要在使用时加以注意。

StratifiedKFold 是评估分类模型性能的强大工具,特别是在类别分布不均衡的情况下,它提供了一种更加公平和准确的评估方法。

二、源码工程

GitHub - XANkui/PythonMachineLearnIntermediateLevel: Python 机器学习是利用 Python 编程语言中的各种工具和库来实现机器学习算法和技术的过程。Python 是一种功能强大且易于学习和使用的编程语言,因此成为了机器学习领域的首选语言之一。这里我们一起开始一场Python 机器学习进阶之旅。

下的 02HandwritingDatabaseClassificationHandler

三、该案例的环境 package 信息如下

Package                   Version
------------------------- --------------
anyio                     4.4.0
argon2-cffi               23.1.0
argon2-cffi-bindings      21.2.0
arrow                     1.3.0
asttokens                 2.4.1
async-lru                 2.0.4
attrs                     23.2.0
Babel                     2.15.0
beautifulsoup4            4.12.3
bleach                    6.1.0
certifi                   2024.7.4
cffi                      1.16.0
charset-normalizer        3.3.2
colorama                  0.4.6
comm                      0.2.2
contourpy                 1.2.1
cycler                    0.12.1
debugpy                   1.8.2
decorator                 5.1.1
defusedxml                0.7.1
executing                 2.0.1
fastjsonschema            2.20.0
fonttools                 4.53.1
fqdn                      1.5.1
h11                       0.14.0
httpcore                  1.0.5
httpx                     0.27.0
idna                      3.7
ipykernel                 6.29.5
ipython                   8.26.0
ipywidgets                8.1.3
isoduration               20.11.0
jedi                      0.19.1
Jinja2                    3.1.4
joblib                    1.4.2
json5                     0.9.25
jsonpointer               3.0.0
jsonschema                4.23.0
jsonschema-specifications 2023.12.1
jupyter                   1.0.0
jupyter_client            8.6.2
jupyter-console           6.6.3
jupyter_core              5.7.2
jupyter-events            0.10.0
jupyter-lsp               2.2.5
jupyter_server            2.14.2
jupyter_server_terminals  0.5.3
jupyterlab                4.2.4
jupyterlab_pygments       0.3.0
jupyterlab_server         2.27.3
jupyterlab_widgets        3.0.11
kiwisolver                1.4.5
MarkupSafe                2.1.5
matplotlib                3.9.1
matplotlib-inline         0.1.7
mistune                   3.0.2
nbclient                  0.10.0
nbconvert                 7.16.4
nbformat                  5.10.4
nest-asyncio              1.6.0
notebook                  7.2.1
notebook_shim             0.2.4
numpy                     2.0.1
overrides                 7.7.0
packaging                 24.1
pandas                    2.2.2
pandocfilters             1.5.1
parso                     0.8.4
pillow                    10.4.0
pip                       24.1.2
platformdirs              4.2.2
prometheus_client         0.20.0
prompt_toolkit            3.0.47
psutil                    6.0.0
pure_eval                 0.2.3
pycparser                 2.22
Pygments                  2.18.0
pyparsing                 3.1.2
python-dateutil           2.9.0.post0
python-json-logger        2.0.7
pytz                      2024.1
pywin32                   306
pywinpty                  2.0.13
PyYAML                    6.0.1
pyzmq                     26.0.3
qtconsole                 5.5.2
QtPy                      2.4.1
referencing               0.35.1
requests                  2.32.3
rfc3339-validator         0.1.4
rfc3986-validator         0.1.1
rpds-py                   0.19.1
scikit-learn              1.5.1
scipy                     1.14.0
Send2Trash                1.8.3
setuptools                70.1.1
six                       1.16.0
sniffio                   1.3.1
soupsieve                 2.5
stack-data                0.6.3
terminado                 0.18.1
threadpoolctl             3.5.0
tinycss2                  1.3.0
tornado                   6.4.1
traitlets                 5.14.3
types-python-dateutil     2.9.0.20240316
typing_extensions         4.12.2
tzdata                    2024.1
uri-template              1.3.0
urllib3                   2.2.2
wcwidth                   0.2.13
webcolors                 24.6.0
webencodings              0.5.1
websocket-client          1.8.0
wheel                     0.43.0
widgetsnbextension        4.0.11

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

仙魁XAN

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值