侧边栏壁纸
博主头像
秋码记录

一个游离于山间之上的Java爱好者 | A Java lover living in the mountains

  • 累计撰写 137 篇文章
  • 累计创建 297 个标签
  • 累计创建 45 个分类

多类别分类器(Machine Learning 研习十八)

多类别分类器

二元分类器可以区分两个类别,而多类别分类器(也称为多叉分类器)可以区分两个以上的类别。

一些 Scikit-Learn 分类器(如 LogisticRegressionRandomForestClassifier GaussianNB)能够原生处理多个类别。其他分类器则是严格的二进制分类器(如 SGDClassifierSVC)。不过,您可以使用多种策略来使用多个二进制分类器执行多类分类。

要创建一个能将数字图像分为 10 类(从 0 到 9)的系统,一种方法是训练 10 个二进制分类器,每个数字一个(0-检测器、1-检测器、2-检测器,以此类推)。然后,当您想对一幅图像进行分类时,您可以从每个分类器中得到该图像的判定分数,然后选择分类器输出分数最高的类别。这就是所谓的 “以一敌百”(OvR)策略,有时也称为 “以一敌众”(OvA)策略。

另一种策略是为每一对数字训练一个二元分类器:一个用于区分 0 和 1,另一个用于区分 0 和 2,还有一个用于区分 1 和 2,以此类推。这就是所谓的一对一(OvO)策略。如果有 N 个类别,则需要训练 N × (N - 1) / 2 个分类器。对于 MNIST 问题,这意味着要训练 45 个二进制分类器!当你想对一幅图像进行分类时,你必须让图像通过所有 45 个分类器,看看哪个分类器赢得了最多的对决。OvO 的主要优势在于,每个分类器只需在训练集中包含其必须区分的两个类别的部分进行训练。

有些算法(如支持向量机分类器)随训练集的大小而缩放,效果不佳。对于这些算法,OvO 是首选,因为在小训练集上训练多个分类器比在大训练集上训练少数分类器更快。不过,对于大多数二元分类算法来说,OvR 是首选。

Scikit-Learn 会检测你是否尝试在多分类任务中使用二元分类算法,并根据算法自动运行 OvROvO。让我们使用 sklearn.svm.SVC 类支持向量机分类器来尝试一下。我们只对前 2,000 张图像进行训练,否则会耗费很长时间:

from sklearn.svm import SVC

svm_clf = SVC(random_state=42) svm_clf.fit(X_train[:2000], y_train[:2000])  # y_train, not y_train_5

我们使用从 0 到 9 的原始目标类别(y_train),而不是 5 对其余目标类别(y_train_5)来训练 SVC。由于有 10 个类别(即多于 2 个),Scikit-Learn 使用 OvO 策略训练了 45 个二元分类器。现在,让我们对一幅图像进行预测:

image-20240403151324143

赢得决斗的次数加上或减去一个小调整(最大 ±0.33)以打破平局:没错!这段代码实际上进行了 45 次预测,每对班级一次,并选择了赢得决斗最多的班级。如果你调用 decision_function() 方法,就会看到它为每个实例返回 10 个分数:每个班级一个。根据分类器的得分,每个类得到的分数等于

image-20240403151515505

得分最高的是 9.3 分,确实是 5 级:

image-20240403151626521

训练分类器时,分类器会在 classes_ attribute中存储目标类列表,并按值排序。在 MNIST 的情况下,classes_ array 中每个类的索引很容易与类本身相匹配(例如,索引为 5 的类恰好是 “5 “类),但在一般情况下,你就没那么幸运了;你需要像这样查找类标签:

image-20240403151854514

如果您想强制 Scikit-Learn 使用一对一或一对多,可以使用 OneVsOneClassifier 或 OneVsRestClassifier 类。只需创建一个实例,并向其构造函数传递一个分类器(甚至不必是二进制分类器)。例如,这段代码使用 OvR 策略创建了一个基于 SVC 的多分类器:

from sklearn.multiclass import OneVsRestClassifier

ovr_clf = OneVsRestClassifier(SVC(random_state=42)) ovr_clf.fit(X_train[:2000], y_train[:2000]) 

让我们进行一次预测,并检查已训练分类器的数量:

image-20240403152207505

在多类数据集上训练 SGDClassifier 并用它进行预测也同样简单:

image-20240403152310056

预测错误确实会发生!这次,Scikit-Learn 在引擎盖下使用了 OvR 策略:因为有 10 个类别,所以它训练了 10 个二元分类器。现在,decision_function() 方法会为每个类别返回一个值。让我们看看 SGD 分类器为每个类别分配的分数:

image-20240403152423407

可以看出,分类器对自己的预测不是很有信心:几乎所有的分数都非常负面,而第 3 类的分数为 +1,824 分,第 5 类也不差,为 -1,386 分。当然,你需要在不止一幅图像上对这个分类器进行评估。由于每个类别中的图片数量大致相同,因此准确度指标也没有问题。像往常一样,你可以使用 cross_val_score() 函数来评估模型:

image-20240403152531168

它在所有测试折叠中的准确率超过 85.8%。如果使用随机分类器,准确率会达到 10%,所以这个成绩还不算太差,但还可以做得更好。只需缩放输入(如第 2 章所述),准确率就能提高到 89.1%以上:

image-20240403152658895

使用CSS计数器,在目录名称前加上了序号,让目录看起来更加井然有序
« 上一篇 2024-04-01
错误分析 (Machine Learning 研习十九)
下一篇 » 2024-04-10

相关推荐

  • 绘制特征曲线-ROC(Machine Learning 研习十七) 2024-03-29 11:43:43 +0800 +0800
    绘制特征曲线-ROC(Machine Learning 研习十七) 接收者操作特征曲线(ROC)是二元分类器的另一个常用工具。它与精确度/召回率曲线非常相似,但 ROC 曲线不是绘制精确度与召回率的关系曲线,而是绘制真阳性率(召回率的另一个名称)与假阳性率(FPR)的关系曲线。FPR(也称 “下降率”)是阴性实例被错误归类为阳性实例的比率。它等于 1 - 真阴性率 (TNR),即正确分类为阴性的阴性实例的比率。TNR 也称为特异性。因此,ROC 曲线是灵敏度(召回率)与 1 - 特异性的关系图 要绘制 ROC 曲线,首先要使用 roc_curve() 函数计算不同阈值的 TPR 和 FPR: from sklearn.metrics import roc_curve fpr, tpr, thresholds = roc_curve(y_train_5, y_scores) 然后可以使用 Matplotlib 绘制 FPR 与 TPR 的对比图。下面的代码可以绘制出 见下图 所示的图形。要找到与 90% 精度相对应的点,我们需要查找所需阈值的索引。由于在这种情况下阈值是按递减顺序排列的,因此我们在第一行使用 <= 而不是 >=: idx_for_threshold_at_90 = (thresholds <= threshold_for_90_precision).argmax() tpr_90, fpr_90 = tpr[idx_for_threshold_at_90], fpr[idx_for_threshold_at_90] plt.plot(fpr, tpr, linewidth=2, label="ROC curve") plt.plot([0, 1], [0, 1], 'k:', label="Random classifier's ROC curve") plt.plot([fpr_90], [tpr_90], "ko", label="Threshold for 90% precision") [.
  • 精确率(召回率)的权衡(Machine Learning 研习十六) 2024-03-21 19:43:43 +0800 +0800
    精确率(召回率)的权衡(Machine Learning 研习十六) 精确率(召回率)的权衡 为了理解这种权衡,让我们看看 SGDClassifier 如何做出分类决策。 对于每个实例,它根据决策函数计算分数。 如果该分数大于阈值,则将该实例分配给正类; 否则它会将其分配给负类。 图 3-4 显示了从左侧最低分数到右侧最高分数的几个数字。 假设决策阈值位于中心箭头(两个 5 之间):您会在该阈值右侧发现 4 个真阳性(实际为 5),以及 1 个假阳性(实际上为 6)。 因此,使用该阈值,精度为 80%(5 分之 4)。 但在 6 个实际的 5 中,分类器仅检测到 4 个,因此召回率为 67%(6 中的 4)。 如果提高阈值(将其移动到右侧的箭头),假阳性(6)会变成真阴性,从而提高精度(在本例中高达 100%),但一个真阳性会变成假阴性 ,将召回率降低至 50%。 相反,降低阈值会增加召回率并降低精确度。 Scikit-Learn 不允许您直接设置阈值,但它允许您访问它用于进行预测的决策分数。 您可以调用其decision_function()方法,而不是调用分类器的predict()方法,该方法返回每个实例的分数,然后使用您想要根据这些分数进行预测的任何阈值: SGDClassifier 使用等于 0 的阈值,因此前面的代码返回与 Predict() 方法相同的结果(即 True)。 让我们提高门槛: 这证实了提高阈值会降低召回率。 该图像实际上代表的是 5,当阈值为 0 时分类器会检测到它,但当阈值增加到 3,000 时分类器会错过它。 y_scores = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3, method="decision_function") 有了这些分数,使用 precision_recall_curve() 函数计算所有可能阈值的精度和召回率(该函数添加最后精度 0 和最后召回率 1,对应于无限阈值): from sklearn.
  • 对模型性能进行评估(Machine Learning 研习十五) 2024-03-15 18:26:43 +0800 +0800
    对模型性能进行评估(Machine Learning 研习十五) 在上一篇我们已然训练了一个用于对数字图像识别的模型,但我们目前还不知道该模型在识别数字图像效率如何?所以,本文将对该模型进行评估。 使用交叉验证衡量准确性 评估模型的一个好方法是使用交叉验证,让我们使用cross_val_score()函数来评估我们的 SGDClassifier 模型,使用三折的 k 折交叉验证。k-fold 交叉验证意味着将训练集分成 k 个折叠(在本例中是三个),然后训练模型 k 次,每次取出一个不同的折叠进行评估: 当您看到这组数字,是不是感到很兴奋?毕竟所有交叉验证折叠的准确率(预测准确率)均超过了 95%。然而,在您兴奋于这组数字前,还是让我们来看看一个假分类器,它只是将每张图片归入最常见的类别,在本例中就是负类别(即非 5): from sklearn.dummy import DummyClassifier dummy_clf = DummyClassifier() dummy_clf.fit(X_train, y_train_5) print(any(dummy_clf.predict(X_train))) # prints False: no 5s detected 您能猜出这个模型的准确度吗?让我们一探究竟: 没错,它的准确率超过 90%!这只是因为只有大约 10% 的图片是 5,所以如果你总是猜测图片不是 5,你就会有大约 90% 的时间是正确的。比诺斯特拉达穆斯还准。 这说明了为什么准确率通常不是分类器的首选性能指标,尤其是在处理偏斜 数据集时(即某些类别的出现频率远高于其他类别)。评估分类器性能的更好方法是查看混淆矩阵(CM)。 实施交叉验证 与 Scikit-Learn 现成提供的功能相比,您有时需要对交叉验证过程进行更多控制。在这种情况下,你可以自己实现交叉验证。下面的代码与 Scikit-Learn 的 cross_val_score() 函数做了大致相同的事情,并会打印出相同的结果: from sklearn.model_selection import StratifiedKFold from sklearn.base import clone skfolds = StratifiedKFold(n_splits=3) # add shuffle=True if the dataset is # not already shuffled for train_index, test_index in skfolds.
  • 图像识别之入门案例之数字识别(Machine Learning 研习十四) 2024-03-15 17:48:03 +0800 +0800
    图像识别之入门案例之数字识别(Machine Learning 研习十四) 在前面的文章中,我们曾提到最为常见的监督学习任务是回归(预测价值)和分类(预测类别)。我们使用线性回归、决策树和随机森林等各种算法探讨了回归任务,即预测房屋价值。现在,我们将把注意力转向分类系统。 MNIST数据集 我们将使用 MNIST 数据集,这是一组由人类手写的 70,000 张小数字图像。每张图片都标注了所代表的数字。人们对这个数据集的研究非常深入,以至于它经常被称为机器学习的 “hello world”:每当人们提出一种新的分类算法时,他们都会好奇地想看看这种算法在 MNIST 上的表现如何,而且任何学习机器学习的人迟早都会用到这个数据集。 Scikit-Learn 提供了许多下载流行数据集的辅助函数。MNIST 就是其中之一。以下代码从 OpenML.org 获取 MNIST 数据集: from sklearn.datasets import fetch_openml mnist = fetch_openml('mnist_784', as_frame=False) sklearn.datasets 包主要包含三种类型的函数:fetch_* 函数(如 fetch_openml())用于下载现实生活中的数据集;load_* 函数用于加载 Scikit-Learn捆绑的小型玩具数据集(因此无需通过互联网下载);make_* 函数用于生成假数据集,对测试非常有用。生成的数据集通常以 (X, y) 元组的形式返回,其中包含输入数据和目标数据,两者都是 NumPy 数组。其他数据集以 sklearn.utils.Bunch 对象的形式返回,这是一个字典,其条目也可以作为属性访问。它们通常包含以下条目: “DESCR” ​ 数据集描述 “data” ​ 输入数据,通常为Numpy二维数组 “target” ​ 标签,通常为Numpy一维数组 fetch_openml() 函数有点不寻常,因为默认情况下,它以 Pandas DataFrame 的形式返回输入,以 Pandas Series 的形式返回标签(除非数据集很稀疏)。但 MNIST 数据集包含图像,而 DataFrame 并不适合图像,因此最好设置 as_frame=False,以 NumPy 数组的形式获取数据。让我们来看看这些数组: 共有 70,000 幅图像,每幅图像有 784 个特征。这是因为每幅图像都是 28 × 28 像素,每个特征只代表一个像素的强度,从 0(白色)到 255(黑色)。让我们来看看数据集中的一个数字(图 3-1)。我们需要做的就是抓取一个实例的特征向量,将其重塑为 28 × 28 数组,然后使用 Matplotlib 的 imshow() 函数显示出来。我们使用 cmap="binary" 来获取灰度颜色图,其中 0 代表白色,255 代表黑色:
  • 微调模型(Machine Learning 研习之十二) 2024-03-09 14:59:44 +0800 +0800
    微调模型(Machine Learning 研习之十二) 现在正处于百模乱战的时期,对于模型微调,想必您是有所了解了,毕竟国外的大语言模型一开源,国内便纷纷基于该模型进行微调,从而开始宣称领先于某某、超越了谁。可到头来,却让人发现他们套壳了国外大语言模型对外开放的API。 好了,我们不说国内各种大模型宣称超过了谁,毕竟,嘴巴长在别人脸上,我们管不了,也管不着,吹牛终将是会露馅的! 当我们需要对开源大模型进行微调时,看看有几种方法可以做到这一点的! 网格搜索 手动调整超参数,直到找到超参数值的完美组合。 这将是一项非常乏味的工作,而且您可能没有时间去探索多种组合。 相反,您可以使用 Scikit-Learn 的 GridSearchCV 类来搜索您。 您需要做的就是告诉它您希望它试验哪些超参数以及要尝试哪些值,它将使用交叉验证来评估超参数值的所有可能组合。 例如,以下代码搜索 RandomForestRegressor 的最佳超参数值组合: from sklearn.model_selection import GridSearchCV full_pipeline = Pipeline([ ("preprocessing", preprocessing), ("random_forest", RandomForestRegressor(random_state=42)), ]) param_grid = [{'preprocessing__geo__n_clusters': [5, 8, 10], 'random_forest__max_features': [4, 6, 8]}, {'preprocessing__geo__n_clusters': [10, 15], 'random_forest__max_features': [6, 8, 10]}, ] grid_search = GridSearchCV(full_pipeline, param_grid, cv=3, scoring='neg_root_mean_squared_error') grid_search.fit(housing, housing_labels) 请注意,您可以引用管道中任何估计器的任何超参数,即使该估计器嵌套在多个管道和列转换器的深处。 例如,当 Scikit-Learn 看到“preprocessing__geo__n_clusters”时,它会在双下划线处分割该字符串,然后在管道中查找名为“preprocessing”的估计器并找到预处理 ColumnTransformer。 接下来,它在此 ColumnTransformer 中查找名为“geo”的转换器,并找到我们在纬度和经度属性上使用的 ClusterSimilarity 转换器。 然后它找到该变压器的n_clusters超参数。 同样,random_forest__max_features指的是名为“random_forest”的估计器的max_features超参数,这当然是RandomForest模型。 这个param_grid中有两个字典,因此GridSearchCV将首先评估第一个字典中指定的n_clusters和max_features超参数值的所有3×3=9个组合,然后它将尝试第一个字典中指定的所有2×3=6个超参数值组合 第二个字典。 因此,网格搜索总共将探索 9 + 6 = 15 种超参数值组合,并且每个组合都会对管道进行 3 次训练,因为我们使用的是 3 折交叉验证。 这意味着总共将有 15 × 3 = 45 轮训练! 这可能需要一段时间,但是完成后您可以获得如下参数的最佳组合: