Machine-Learning-Mastery-不平衡数据教程-三-

47 阅读1小时+

Machine Learning Mastery 不平衡数据教程(三)

原文:Machine Learning Mastery

协议:CC BY-NC-SA 4.0

开发严重偏斜的类分布的直觉

原文:machinelearningmastery.com/how-to-deve…

最后更新于 2020 年 1 月 14 日

不平衡分类问题是涉及预测类别标签的问题,其中类别标签在训练数据集中的分布不相等。

对于处理不平衡分类问题的初学者来说,一个挑战是特定的倾斜类分布意味着什么。例如,1:10 与 1:100 的班级比例的区别和含义是什么?

不平衡分类问题的类分布差异将影响数据准备和建模算法的选择。因此,对于不同的类分布,从业者发展一种直觉是至关重要的。

在本教程中,您将发现如何为不平衡和高度倾斜的类分布开发实用的直觉。

完成本教程后,您将知道:

  • 如何创建用于二进制分类的合成数据集,并按类绘制示例。
  • 如何创建任何给定类别分布的合成类别数据集?
  • 不同的倾斜类分布实际上是怎样的。

用我的新书Python 不平衡分类启动你的项目,包括分步教程和所有示例的 Python 源代码文件。

我们开始吧。

  • 2020 年 1 月更新:针对 Sklearn v0.22 API 的变化进行了更新。

How to Develop an Intuition Severely Skewed Class Distributions

为严重偏斜的类分布开发一种直觉 图片由鲍里斯·卡西莫夫提供,保留部分权利。

教程概述

本教程分为三个部分;它们是:

  1. 创建并绘制二分类问题
  2. 创建具有类别分布的合成数据集
  3. 偏斜类分布的影响

创建并绘制二分类问题

Sklearn Python 机器学习库提供了生成合成数据集的功能。

make_blobs()函数可用于从具有指定类别数的测试分类问题中生成指定数量的示例。该函数返回准备建模的每个示例的输入和输出部分。

例如,下面的代码片段将为具有两个输入变量的两类(二进制)分类问题生成 1000 个示例。类值的值为 0 和 1。

...
X, y = make_blobs(n_samples=1000, centers=2, n_features=2, random_state=1, cluster_std=3)

一旦生成,我们就可以绘制数据集以获得示例之间空间关系的直觉。

因为只有两个输入变量,所以我们可以创建散点图,将每个示例绘制为一个点。这可以通过散射()matplotlib 功能来实现。

然后可以根据类别值改变点的颜色。这可以通过首先为给定类的示例选择数组索引,然后只绘制这些点,然后为另一个类重复选择和绘制过程来实现。 where() NumPy 函数可用于检索符合标准的数组索引,例如具有给定值的类标签。

例如:

...
# create scatter plot for samples from each class
for class_value in range(2):
	# get row indexes for samples with this class
	row_ix = where(y == class_value)
	# create scatter of these samples
	pyplot.scatter(X[row_ix, 0], X[row_ix, 1])

下面列出了创建二进制分类测试数据集并将示例绘制为散点图的完整示例。

# generate binary classification dataset and plot
from numpy import where
from matplotlib import pyplot
from sklearn.datasets import make_blobs
# generate dataset
X, y = make_blobs(n_samples=1000, centers=2, n_features=2, random_state=1, cluster_std=3)
# create scatter plot for samples from each class
for class_value in range(2):
	# get row indexes for samples with this class
	row_ix = where(y == class_value)
	# create scatter of these samples
	pyplot.scatter(X[row_ix, 0], X[row_ix, 1])
# show the plot
pyplot.show()

运行该示例会创建数据集和散点图,用不同的颜色显示两个类的示例。

我们可以看到每个类中有相同数量的例子,在本例中是 500 个,我们可以想象画一条线来合理地划分类,很像分类预测模型在学习如何区分例子时可能做的那样。

Scatter Plot of Binary Classification Dataset

二元类别数据集的散点图

既然我们知道了如何创建一个合成的二进制类别数据集并绘制示例,那么让我们来看看示例中的类不平衡示例。

创建具有类别分布的合成数据集

make_blobs() 函数将始终创建类分布相等的合成数据集。

然而,我们可以使用这个函数来创建具有任意类分布的合成类别数据集,只需要几行额外的代码。

类别分布可以定义为字典,其中关键字是类别值(例如 0 或 1),值是要包含在数据集中的随机生成的示例数。

例如,每个类中有 5,000 个示例的相等类分布将被定义为:

...
# define the class distribution
proportions = {0:5000, 1:5000}

然后我们可以枚举不同的分布并找到最大的分布,然后使用 make_blobs() 函数为每个类创建一个包含如此多示例的数据集。

...
# determine the number of classes
n_classes = len(proportions)
# determine the number of examples to generate for each class
largest = max([v for k,v in proportions.items()])
n_samples = largest * n_classes

这是一个很好的起点,但会给我们比每个类别标签所需的更多的样本。

然后,我们可以枚举类标签,并为每个类选择所需数量的示例,以构成将要返回的数据集。

...
# collect the examples
X_list, y_list = list(), list()
for k,v in proportions.items():
	row_ix = where(y == k)[0]
	selected = row_ix[:v]
	X_list.append(X[selected, :])
	y_list.append(y[selected])

我们可以将它绑定到一个名为 get_dataset() 的新函数中,该函数将采用一个类分布,并返回一个具有该类分布的合成数据集。

# create a dataset with a given class distribution
def get_dataset(proportions):
	# determine the number of classes
	n_classes = len(proportions)
	# determine the number of examples to generate for each class
	largest = max([v for k,v in proportions.items()])
	n_samples = largest * n_classes
	# create dataset
	X, y = make_blobs(n_samples=n_samples, centers=n_classes, n_features=2, random_state=1, cluster_std=3)
	# collect the examples
	X_list, y_list = list(), list()
	for k,v in proportions.items():
		row_ix = where(y == k)[0]
		selected = row_ix[:v]
		X_list.append(X[selected, :])
		y_list.append(y[selected])
	return vstack(X_list), hstack(y_list)

该函数可以接受任意数量的类,尽管我们将把它用于简单的二进制分类问题。

接下来,我们可以使用上一节中的代码为已创建的数据集创建散点图,并将其放入助手函数中。下面是 plot_dataset() 函数,该函数将绘制数据集并显示一个图例,以指示颜色到类标签的映射。

# scatter plot of dataset, different color for each class
def plot_dataset(X, y):
	# create scatter plot for samples from each class
	n_classes = len(unique(y))
	for class_value in range(n_classes):
		# get row indexes for samples with this class
		row_ix = where(y == class_value)[0]
		# create scatter of these samples
		pyplot.scatter(X[row_ix, 0], X[row_ix, 1], label=str(class_value))
	# show a legend
	pyplot.legend()
	# show the plot
	pyplot.show()

最后,我们可以测试这些新功能。

我们将为每个类定义一个包含 5,000 个示例的数据集(总共 10,000 个示例),并绘制结果。

下面列出了完整的示例。

# create and plot synthetic dataset with a given class distribution
from numpy import unique
from numpy import hstack
from numpy import vstack
from numpy import where
from matplotlib import pyplot
from sklearn.datasets import make_blobs

# create a dataset with a given class distribution
def get_dataset(proportions):
	# determine the number of classes
	n_classes = len(proportions)
	# determine the number of examples to generate for each class
	largest = max([v for k,v in proportions.items()])
	n_samples = largest * n_classes
	# create dataset
	X, y = make_blobs(n_samples=n_samples, centers=n_classes, n_features=2, random_state=1, cluster_std=3)
	# collect the examples
	X_list, y_list = list(), list()
	for k,v in proportions.items():
		row_ix = where(y == k)[0]
		selected = row_ix[:v]
		X_list.append(X[selected, :])
		y_list.append(y[selected])
	return vstack(X_list), hstack(y_list)

# scatter plot of dataset, different color for each class
def plot_dataset(X, y):
	# create scatter plot for samples from each class
	n_classes = len(unique(y))
	for class_value in range(n_classes):
		# get row indexes for samples with this class
		row_ix = where(y == class_value)[0]
		# create scatter of these samples
		pyplot.scatter(X[row_ix, 0], X[row_ix, 1], label=str(class_value))
	# show a legend
	pyplot.legend()
	# show the plot
	pyplot.show()

# define the class distribution
proportions = {0:5000, 1:5000}
# generate dataset
X, y = get_dataset(proportions)
# plot dataset
plot_dataset(X, y)

运行该示例会像以前一样创建数据集并绘制结果,尽管这次是使用我们提供的类分布。

在这种情况下,我们有更多的每个类的例子和一个有用的图例来指示绘图颜色到类标签的映射。

Scatter Plot of Binary Classification Dataset With Provided Class Distribution

具有给定类别分布的二元类别数据集的散点图

现在,我们已经拥有了创建和绘制具有任意倾斜类分布的合成数据集的工具,让我们来看看不同分布的效果。

偏斜类分布的影响

对于不同阶级不平衡的空间关系,发展一种直觉是很重要的。

比如 1:1000 的类分配关系是什么样的?

这是一种抽象的关系,我们需要把它和具体的东西联系起来。

我们可以生成具有不同不平衡类分布的合成测试数据集,并以此为基础,为我们在真实数据集中可能遇到的不同偏斜分布开发直觉。

查看不同类别分布的散点图可以大致了解类别之间的关系,这在考虑将来处理类似类别分布时的技术选择和模型评估时非常有用。它们提供了一个参考点。

我们在前面的部分已经看到了 1:1 的关系(例如 5000:5000)。

请注意,当处理二进制分类问题,尤其是不平衡问题时,重要的是多数类被分配给类 0,少数类被分配给类 1。这是因为许多评估指标将假设这种关系。

因此,我们可以通过在对 get_dataset() 函数的调用中先定义多数类,再定义少数类来确保我们的类分布符合这一实践;例如:

...
# define the class distribution
proportions = {0:10000, 1:10}
# generate dataset
X, y = get_dataset(proportions)
...

在本节中,我们可以看到不同的倾斜类分布,少数类的大小以对数尺度增加,例如:

  • 1:10 或{0:10000,1:1000}
  • 1:100 或{0:10000,1:100}
  • 1:1000 或{0:10000,1:10}

让我们依次仔细看看每个班级的分布情况。

1:10 不平衡的阶级分布

包含 10,000 到 1,000 个示例的 1:10 类分布意味着数据集中有 11,000 个示例,其中 0 类约占 91%,1 类约占 9%。

下面列出了完整的代码示例。

# create and plot synthetic dataset with a given class distribution
from numpy import unique
from numpy import hstack
from numpy import vstack
from numpy import where
from matplotlib import pyplot
from sklearn.datasets import make_blobs

# create a dataset with a given class distribution
def get_dataset(proportions):
	# determine the number of classes
	n_classes = len(proportions)
	# determine the number of examples to generate for each class
	largest = max([v for k,v in proportions.items()])
	n_samples = largest * n_classes
	# create dataset
	X, y = make_blobs(n_samples=n_samples, centers=n_classes, n_features=2, random_state=1, cluster_std=3)
	# collect the examples
	X_list, y_list = list(), list()
	for k,v in proportions.items():
		row_ix = where(y == k)[0]
		selected = row_ix[:v]
		X_list.append(X[selected, :])
		y_list.append(y[selected])
	return vstack(X_list), hstack(y_list)

# scatter plot of dataset, different color for each class
def plot_dataset(X, y):
	# create scatter plot for samples from each class
	n_classes = len(unique(y))
	for class_value in range(n_classes):
		# get row indexes for samples with this class
		row_ix = where(y == class_value)[0]
		# create scatter of these samples
		pyplot.scatter(X[row_ix, 0], X[row_ix, 1], label=str(class_value))
	# show a legend
	pyplot.legend()
	# show the plot
	pyplot.show()

# define the class distribution
proportions = {0:10000, 1:1000}
# generate dataset
X, y = get_dataset(proportions)
# plot dataset
plot_dataset(X, y)

运行该示例将创建具有定义的类分布的数据集,并绘制结果。

虽然这种平衡看起来很明显,但情节表明,少数族裔与多数族裔相比,大约有 10%的分数没有我们想象的那么差。

这种关系看起来是可以管理的,尽管如果课程明显重叠,我们可以想象一个非常不同的故事。

Scatter Plot of Binary Classification Dataset With A 1 to 10 Class Distribution

具有 1 到 10 类分布的二元类别数据集的散点图

1:100 不平衡的阶级分布

包含 10,000 到 100 个示例的 1:100 类分布意味着数据集中有 10,100 个示例,其中 0 类大约占 99%,1 类大约占 1%。

下面列出了完整的代码示例。

# create and plot synthetic dataset with a given class distribution
from numpy import unique
from numpy import hstack
from numpy import vstack
from numpy import where
from matplotlib import pyplot
from sklearn.datasets import make_blobs

# create a dataset with a given class distribution
def get_dataset(proportions):
	# determine the number of classes
	n_classes = len(proportions)
	# determine the number of examples to generate for each class
	largest = max([v for k,v in proportions.items()])
	n_samples = largest * n_classes
	# create dataset
	X, y = make_blobs(n_samples=n_samples, centers=n_classes, n_features=2, random_state=1, cluster_std=3)
	# collect the examples
	X_list, y_list = list(), list()
	for k,v in proportions.items():
		row_ix = where(y == k)[0]
		selected = row_ix[:v]
		X_list.append(X[selected, :])
		y_list.append(y[selected])
	return vstack(X_list), hstack(y_list)

# scatter plot of dataset, different color for each class
def plot_dataset(X, y):
	# create scatter plot for samples from each class
	n_classes = len(unique(y))
	for class_value in range(n_classes):
		# get row indexes for samples with this class
		row_ix = where(y == class_value)[0]
		# create scatter of these samples
		pyplot.scatter(X[row_ix, 0], X[row_ix, 1], label=str(class_value))
	# show a legend
	pyplot.legend()
	# show the plot
	pyplot.show()

# define the class distribution
proportions = {0:10000, 1:100}
# generate dataset
X, y = get_dataset(proportions)
# plot dataset
plot_dataset(X, y)

运行该示例将创建具有定义的类分布的数据集,并绘制结果。

1 比 100 的关系是一个很大的偏差。

与大多数阶级相比,剧情让这一点变得清晰起来,感觉像是一点点的分数。

现实世界的数据集很可能落在 1:10 和 1:100 的类分布之间,1:100 的图确实强调了需要仔细考虑少数类中的每个点,包括测量误差(例如异常值)和模型可能产生的预测误差。

Scatter Plot of Binary Classification Dataset With A 1 to 100 Class Distribution

1 到 100 类分布的二元类别数据集散点图

1:1000 不平衡的阶级分布

包含 10,000 到 10 个示例的 1:100 类分布意味着数据集中有 10,010 个示例,其中 0 类约占 99.9%,1 类约占 0.1%。

下面列出了完整的代码示例。

# create and plot synthetic dataset with a given class distribution
from numpy import unique
from numpy import hstack
from numpy import vstack
from numpy import where
from matplotlib import pyplot
from sklearn.datasets import make_blobs

# create a dataset with a given class distribution
def get_dataset(proportions):
	# determine the number of classes
	n_classes = len(proportions)
	# determine the number of examples to generate for each class
	largest = max([v for k,v in proportions.items()])
	n_samples = largest * n_classes
	# create dataset
	X, y = make_blobs(n_samples=n_samples, centers=n_classes, n_features=2, random_state=1, cluster_std=3)
	# collect the examples
	X_list, y_list = list(), list()
	for k,v in proportions.items():
		row_ix = where(y == k)[0]
		selected = row_ix[:v]
		X_list.append(X[selected, :])
		y_list.append(y[selected])
	return vstack(X_list), hstack(y_list)

# scatter plot of dataset, different color for each class
def plot_dataset(X, y):
	# create scatter plot for samples from each class
	n_classes = len(unique(y))
	for class_value in range(n_classes):
		# get row indexes for samples with this class
		row_ix = where(y == class_value)[0]
		# create scatter of these samples
		pyplot.scatter(X[row_ix, 0], X[row_ix, 1], label=str(class_value))
	# show a legend
	pyplot.legend()
	# show the plot
	pyplot.show()

# define the class distribution
proportions = {0:10000, 1:10}
# generate dataset
X, y = get_dataset(proportions)
# plot dataset
plot_dataset(X, y)

运行该示例将创建具有定义的类分布的数据集,并绘制结果。

正如我们可能已经怀疑的那样,1 比 1000 的关系是侵略性的。在我们选择的设置中,只有 10 个少数民族的例子呈现给 10,000 个多数民族。

在如此缺乏数据的情况下,我们可以看到,在以如此戏剧性的偏差建模问题时,我们可能应该花很多时间在可用的实际少数示例上,并看看领域知识是否可以以某种方式使用。自动建模方法将面临严峻的挑战。

这个例子也强调了与类分布正交的另一个重要方面,那就是例子的数量。例如,虽然数据集具有 1:1000 的类分布,但是只有 10 个少数类的例子是非常具有挑战性的。尽管,如果我们有相同的类分布,有 100 万个多数类和 1000 个少数类的例子,额外的 990 个少数类例子在开发一个有效的模型中可能是非常宝贵的。

Scatter Plot of Binary Classification Dataset With A 1 to 1000 Class Distribution

1 到 1000 类分布的二元类别数据集的散点图

进一步阅读

如果您想更深入地了解这个主题,本节将提供更多资源。

应用程序接口

摘要

在本教程中,您发现了如何为不平衡和高度倾斜的类分布开发实用直觉。

具体来说,您了解到:

  • 如何创建用于二进制分类的合成数据集,并按类绘制示例。
  • 如何创建任何给定类别分布的合成类别数据集?
  • 不同的倾斜类分布实际上是怎样的。

你有什么问题吗? 在下面的评论中提问,我会尽力回答。

不平衡分类为什么难?

原文:machinelearningmastery.com/imbalanced-…

不平衡分类作为一项预测性建模任务,主要是具有挑战性的,因为类别分布严重倾斜。

这是传统机器学习模型和评估指标表现不佳的原因,这些模型和指标假设了均衡的类分布。

然而,类别数据集还有一些附加的属性,这些属性不仅对预测建模具有挑战性,而且在建模不平衡数据集时会增加或加重难度。

在本教程中,您将发现加剧不平衡分类挑战的数据特征。

完成本教程后,您将知道:

  • 不平衡的分类是特别困难的,因为严重倾斜的类分布和不平等的错误分类成本。
  • 数据集大小、标签噪声和数据分布等属性加剧了不平衡分类的难度。
  • 如何对不同数据集属性对建模难度的复合影响形成直觉?

用我的新书Python 不平衡分类启动你的项目,包括分步教程和所有示例的 Python 源代码文件。

我们开始吧。

Problem Characteristics That Make Imbalanced Classification Hard

使不平衡分类变得困难的问题特征 乔舒亚·达马西奥摄,保留部分权利。

教程概述

本教程分为四个部分;它们是:

  1. 不平衡分类为什么难
  2. 数据集大小的复合效应
  3. 标签噪声的复合效应
  4. 数据分布的复合效应

不平衡分类为什么难

不平衡分类是由具有倾斜类分布的数据集定义的。

这通常以二进制(两类)分类任务为例,其中大多数示例属于 0 类,只有少数示例属于 1 类。分布的严重程度可能从 1:2、1:10、1:100 甚至 1:1000 不等。

由于类分布不均衡,大多数机器学习算法将表现不佳,需要修改以避免在所有情况下简单地预测多数类。此外,像分类这样的度量失去了意义,需要评估不平衡例子预测的替代方法,如曲线下的 ROC 面积

这是不平衡分类的基本挑战。

  • 倾斜的阶级分布

一个额外的复杂性来自于问题域,这些例子就是从这个问题域中抽取出来的。

多数类通常代表域中的正常情况,而少数类代表异常情况,如故障、欺诈、异常、异常、疾病状态等。因此,对错误分类错误的解释可能因类而异。

例如,将多数类的例子误分类为少数类的例子称为假阳性通常是不希望的,但不如将少数类的例子分类为属于多数类,即所谓的假阴性那么重要。

这被称为错误分类错误的成本敏感性,并且是不平衡分类的第二个基本挑战。

  • 错误分类错误的不等成本

这两个方面,偏斜的类分布和成本敏感性,在描述不平衡分类的困难时被典型地引用。

然而,分类问题还有其他特征,当与这些属性结合时,它们的效果会更好。这些是分类预测建模的一般特征,放大了不平衡分类任务的难度。

阶级不平衡被广泛认为是分类的一个复杂因素。然而,一些研究还认为,不平衡比率不是从不平衡数据中学习时表现下降的唯一原因。

—第 253 页,从不平衡数据集中学习,2018。

这样的特征有很多,但最常见的可能有三个:

  • 数据集大小。
  • 标签噪音。
  • 数据分发。

重要的是,不仅要承认这些属性,而且要对它们的影响形成一种直觉。这将允许您在自己的预测建模项目中选择和开发解决这些问题的技术。

了解这些数据的内在特征,以及它们与类别不平衡的关系,对于应用现有技术和开发新技术来处理不平衡数据至关重要。

—第 253-254 页,从不平衡数据集中学习,2018。

在接下来的部分中,我们将仔细研究这些属性及其对不平衡分类的影响。

数据集大小的复合效应

数据集大小只是指从域中收集的适合和评估预测模型的示例数量。

通常情况下,数据越多越好,因为它提供了更多的领域覆盖,也许会达到收益递减的程度。

具体来说,更多的数据提供了特征空间中特征的组合和变化以及它们到类标签的映射的更好的表示。由此,模型可以更好地学习和概括一个类边界,以辨别未来的新例子。

如果多数类和少数类中的示例的比例是固定的,那么随着数据集规模的扩大,少数类中会有更多的示例。

如果我们能收集更多的例子,这很好。

这是一个典型的问题,因为数据很难收集或收集成本很高,而且我们收集和处理的数据通常比我们可能喜欢的要少得多。因此,这可能会极大地影响我们从少数群体中获得足够大或有代表性的样本的能力。

分类中经常出现的一个问题是训练实例数量少。这一问题通常被报告为数据稀少或缺乏数据,与“缺乏密度”或“信息不足”有关。

—第 261 页,从不平衡数据集中学习,2018。

例如,对于具有平衡的类分布的适度分类任务,我们可能会满足于成千上万个例子,以便开发、评估和选择模型。

一个有 10,000 个例子的平衡二进制分类,每个类有 5,000 个例子。具有 1:100 分布且示例数相同的不平衡数据集只有 100 个少数民族示例。

因此,数据集的大小极大地影响了不平衡分类任务,并且当处理不平衡分类问题时,通常被认为大的数据集实际上可能不够大。

如果没有足够大的训练集,分类器可能无法概括数据的特征。此外,该分类器还可能过度训练训练数据,在样本外测试实例中表现不佳。

—第 261 页,从不平衡数据集中学习,2018。

为了有所帮助,让我们用一个工作实例来具体说明这一点。

我们可以使用make _ classification()sci kit-learn 函数创建一个给定大小的数据集,少数类与多数类的示例比例约为 1:100(1%到 99%)。

...
# create the dataset
X, y = make_classification(n_samples=1000, n_features=2, n_redundant=0,
	n_clusters_per_class=1, weights=[0.99], flip_y=0, random_state=1)

然后,我们可以创建数据集的散点图,并用分隔色为每个类的点着色,以了解示例的空间关系。

...
# scatter plot of examples by class label
for label, _ in counter.items():
	row_ix = where(y == label)[0]
	pyplot.scatter(X[row_ix, 0], X[row_ix, 1], label=str(label))
pyplot.legend()

然后可以用不同的数据集大小重复这个过程,以直观地显示类不平衡是如何受到影响的。我们将使用 100、1,000、10,000 和 100,000 个示例来比较数据集。

下面列出了完整的示例。

# vary the dataset size for a 1:100 imbalanced dataset
from collections import Counter
from sklearn.datasets import make_classification
from matplotlib import pyplot
from numpy import where
# dataset sizes
sizes = [100, 1000, 10000, 100000]
# create and plot a dataset with each size
for i in range(len(sizes)):
	# determine the dataset size
	n = sizes[i]
	# create the dataset
	X, y = make_classification(n_samples=n, n_features=2, n_redundant=0,
		n_clusters_per_class=1, weights=[0.99], flip_y=0, random_state=1)
	# summarize class distribution
	counter = Counter(y)
	print('Size=%d, Ratio=%s' % (n, counter))
	# define subplot
	pyplot.subplot(2, 2, 1+i)
	pyplot.title('n=%d' % n)
	pyplot.xticks([])
	pyplot.yticks([])
	# scatter plot of examples by class label
	for label, _ in counter.items():
		row_ix = where(y == label)[0]
		pyplot.scatter(X[row_ix, 0], X[row_ix, 1], label=str(label))
	pyplot.legend()
# show the figure
pyplot.show()

运行该示例使用四种不同的大小创建并绘制了具有 1:100 类分布的同一数据集。

首先,显示每个数据集大小的类分布。我们可以看到,在一个包含 100 个例子的小数据集中,我们只得到少数民族类中的一个例子,正如我们可能预期的那样。即使数据集中有 100,000 个例子,我们在少数民族类中也只能得到 1,000 个例子。

Size=100, Ratio=Counter({0: 99, 1: 1})
Size=1000, Ratio=Counter({0: 990, 1: 10})
Size=10000, Ratio=Counter({0: 9900, 1: 100})
Size=100000, Ratio=Counter({0: 99000, 1: 1000})

为每个不同大小的数据集创建散点图。

我们可以看到,直到非常大的样本量,类分布的底层结构才变得明显。

这些图强调了数据集大小在不平衡分类中的关键作用。很难想象一个给定了 990 个多数阶级和 10 个少数阶级的例子的模型,在画出 100,000 个例子后,如何能在同样的问题上做得很好。

Scatter Plots of an Imbalanced Classification Dataset With Different Dataset Sizes

具有不同数据集大小的不平衡类别数据集的散点图

标签噪声的复合效应

标签噪声是指属于一个类的例子被分配给另一个类。

对于大多数机器学习算法来说,这可能使得在特征空间中确定类边界成为问题,并且这种困难通常与标签中噪声的百分比成比例地增加。

文献中区分了两种类型的噪声:特征噪声(或属性噪声)和类别噪声。在 ML 中,类噪声通常被认为比属性噪声更有害[…]类噪声以某种方式影响观察到的类值(例如,通过某种方式将少数类实例的标签翻转为多数类标签)。

—第 264 页,从不平衡数据集中学习,2018。

原因通常是问题域中固有的,例如类边界上不明确的观察,甚至数据收集中的错误,这些错误可能会影响特征空间中任何地方的观察。

对于不平衡的分类,有噪声的标签具有更显著的效果。

鉴于正类中的例子如此之少,由于噪声而丢失一些会减少关于副类的可用信息量。

此外,来自多数类的例子被错误地标记为属于少数类会导致少数类的分离或分裂,因为缺少观察,少数类已经是稀疏的。

我们可以想象,如果沿着类边界有模棱两可的例子,我们可以识别并删除或纠正它们。标记为少数类的示例位于大多数类的高密度特征空间区域,也很容易识别、移除或纠正。

在这种情况下,两个类别的观测值在特征空间中都是稀疏的,这个问题通常变得特别困难,特别是对于不平衡分类。正是在这种情况下,未修改的机器学习算法将定义有利于多数类而牺牲少数类的类边界。

错误标记的少数民族类别实例将有助于增加感知的不平衡比率,并在少数民族类别的类别区域内引入错误标记的噪声实例。另一方面,错误标记的多数类实例可能导致学习算法或不平衡的处理方法关注输入空间的错误区域。

—第 264 页,从不平衡数据集中学习,2018。

我们可以举一个例子来说明这个挑战。

我们可以保持数据集大小不变以及 1:100 的类比率,并改变标签噪声的数量。这可以通过将“ flip_y ”参数设置为 make_classification() 函数来实现,该函数是每个类中更改或翻转标签的示例数量的百分比。

我们将探索从 0%、1%、5%到 7%的变化。

下面列出了完整的示例。

# vary the label noise for a 1:100 imbalanced dataset
from collections import Counter
from sklearn.datasets import make_classification
from matplotlib import pyplot
from numpy import where
# label noise ratios
noise = [0, 0.01, 0.05, 0.07]
# create and plot a dataset with different label noise
for i in range(len(noise)):
	# determine the label noise
	n = noise[i]
	# create the dataset
	X, y = make_classification(n_samples=1000, n_features=2, n_redundant=0,
		n_clusters_per_class=1, weights=[0.99], flip_y=n, random_state=1)
	# summarize class distribution
	counter = Counter(y)
	print('Noise=%d%%, Ratio=%s' % (int(n*100), counter))
	# define subplot
	pyplot.subplot(2, 2, 1+i)
	pyplot.title('noise=%d%%' % int(n*100))
	pyplot.xticks([])
	pyplot.yticks([])
	# scatter plot of examples by class label
	for label, _ in counter.items():
		row_ix = where(y == label)[0]
		pyplot.scatter(X[row_ix, 0], X[row_ix, 1], label=str(label))
	pyplot.legend()
# show the figure
pyplot.show()

运行该示例使用四种不同数量的标签噪声创建并绘制了具有 1:100 类分布的同一数据集。

首先,为具有不同标签噪声量的每个数据集打印类别分布。我们可以看到,正如我们可能预期的那样,随着噪音的增加,少数族裔中的例子数量也在增加,其中大多数都被错误地标记了。

我们可能会认为,少数类中这 30 个标签噪声为 7%的额外示例会对试图在特征空间中定义清晰类边界的模型造成相当大的损害。

Noise=0%, Ratio=Counter({0: 990, 1: 10})
Noise=1%, Ratio=Counter({0: 983, 1: 17})
Noise=5%, Ratio=Counter({0: 963, 1: 37})
Noise=7%, Ratio=Counter({0: 959, 1: 41})

为具有不同标签噪声的每个数据集创建散点图。

在这个具体的案例中,我们没有看到很多关于类边界混淆的例子。相反,我们可以看到,随着标签噪声的增加,少数类质量中的示例数量(蓝色区域中的橙色点)增加,这表示在建模之前确实应该识别并从数据集中移除的误报。

Scatter Plots of an Imbalanced Classification Dataset With Different Label Noise

具有不同标签噪声的不平衡类别数据集的散点图

数据分布的复合效应

另一个重要的考虑是特征空间中示例的分布。

如果我们在空间上考虑特征空间,我们可能希望一个类中的所有示例位于空间的一部分,而另一个类中的示例出现在空间的另一部分。

如果是这样的话,我们有很好的类可分性,机器学习模型可以画出清晰的类边界,达到很好的分类表现。这适用于类分布平衡或不平衡的数据集。

很少出现这种情况,更有可能的是,每个类都有多个“概念”,从而在特征空间中产生多个不同的示例组或集群。

……一个类下面的“概念”被分成几个子概念,分布在输入空间中,这是很常见的。

—第 255 页,从不平衡数据集中学习,2018。

这些组在形式上被称为“析取值”,来自基于规则的系统中的一个定义,该规则涵盖了由子概念组成的一组情况。小析取是指在训练数据集中涉及或“覆盖”几个例子的析取。

从例子中学习的系统通常不能成功地为每个概念创建一个纯粹的联合定义。相反,他们创建了一个由几个析取项组成的定义,其中每个析取项都是原始概念的子概念的合取定义。

——概念学习与小析取问题,1989。

这种分组使得类的可分性变得困难,需要隐式或显式地识别每个组或簇并将其包含在类边界的定义中。

在不平衡数据集的情况下,如果少数类在特征空间中有多个概念或聚类,这是一个特殊的问题。这是因为这个类中的例子密度已经很少了,而且很难用这么少的例子来区分不同的分组。它可能看起来像一个大的稀疏分组。

这种同质性的缺乏在基于分治策略的算法中尤其成问题,在这种算法中,子概念导致小析取的产生。

—第 255 页,从不平衡数据集中学习,2018。

例如,我们可以考虑描述患者是健康的(多数类)还是患病的(少数类)的数据。这些数据可能捕捉到许多不同类型的疾病,也可能有类似疾病的分组,但如果病例如此之少,那么类中的任何分组或概念可能都不明显,可能看起来像是与健康病例混合的扩散集合。

为了使这个具体化,我们可以看一个例子。

我们可以使用数据集中的聚类数作为“概念”的代理,并将每个类有一个示例聚类的数据集与每个类有两个聚类的第二个数据集进行比较。

这可以通过改变用于创建数据集的 make_classification() 函数的“*n _ clusters _ per _ class”*参数来实现。

我们预计,在不平衡的数据集中,例如 1:100 的类分布,集群数量的增加对多数类来说是显而易见的,但对少数类来说并非如此。

下面列出了完整的示例。

# vary the number of clusters for a 1:100 imbalanced dataset
from collections import Counter
from sklearn.datasets import make_classification
from matplotlib import pyplot
from numpy import where
# number of clusters
clusters = [1, 2]
# create and plot a dataset with different numbers of clusters
for i in range(len(clusters)):
	c = clusters[i]
	# define dataset
	X, y = make_classification(n_samples=10000, n_features=2, n_redundant=0,
		n_clusters_per_class=c, weights=[0.99], flip_y=0, random_state=1)
	counter = Counter(y)
	# define subplot
	pyplot.subplot(1, 2, 1+i)
	pyplot.title('Clusters=%d' % c)
	pyplot.xticks([])
	pyplot.yticks([])
	# scatter plot of examples by class label
	for label, _ in counter.items():
		row_ix = where(y == label)[0]
		pyplot.scatter(X[row_ix, 0], X[row_ix, 1], label=str(label))
	pyplot.legend()
# show the figure
pyplot.show()

运行该示例使用两个不同数量的聚类创建并绘制了具有 1:100 类分布的同一数据集。

在第一个散点图(左)中,我们可以看到每个类有一个聚类。多数阶级(蓝色)显然有一个集群,而少数阶级(橙色)的结构不太明显。在第二个图(右)中,我们可以再次清楚地看到多数类有两个聚类,少数类(橙色)的结构也是扩散的,并且不明显样本是从两个聚类中提取的。

这突出了数据集的大小与其揭示少数类中示例的底层密度或分布的能力之间的关系。由于例子如此之少,机器学习模型的泛化即使不是很成问题,也是具有挑战性的。

Scatter Plots of an Imbalanced Classification Dataset With Different Numbers of Clusters

具有不同聚类数的不平衡类别数据集的散点图

进一步阅读

如果您想更深入地了解这个主题,本节将提供更多资源。

报纸

蜜蜂

摘要

在本教程中,您发现了加剧不平衡分类挑战的数据特征。

具体来说,您了解到:

  • 不平衡的分类是特别困难的,因为严重倾斜的类分布和不平等的错误分类成本。
  • 数据集大小、标签噪声和数据分布等属性加剧了不平衡分类的难度。
  • 如何对不同数据集属性对建模难度的复合影响形成直觉?

你有什么问题吗? 在下面的评论中提问,我会尽力回答。

检测乳腺摄影微钙化的不平衡分类模型

原文:machinelearningmastery.com/imbalanced-…

最后更新于 2020 年 8 月 21 日

癌症检测是不平衡分类问题的一个常见例子,因为非癌症病例通常比实际癌症病例多得多。

一个标准的不平衡类别数据集是乳房 x 线摄影数据集,它涉及从放射扫描中检测乳腺癌,特别是在乳房 x 线摄影中出现明亮的微钙化簇。这个数据集是通过扫描图像,将它们分割成候选对象,并使用计算机视觉技术来描述每个候选对象来构建的。

由于严重的类别不平衡,它是不平衡分类的一个流行数据集,具体来说,98%的候选微钙化点不是癌症,只有 2%被有经验的放射技师标记为癌症。

在本教程中,您将发现如何开发和评估不平衡乳腺摄影癌症类别数据集的模型。

完成本教程后,您将知道:

  • 如何加载和探索数据集,并为数据准备和模型选择产生想法。
  • 如何评估一套机器学习模型,并利用数据成本敏感技术提高其表现。
  • 如何拟合最终模型并使用它来预测特定情况下的类标签。

用我的新书Python 不平衡分类启动你的项目,包括分步教程和所有示例的 Python 源代码文件。

我们开始吧。

Develop an Imbalanced Classification Model to Detect Microcalcifications

开发一个不平衡的分类模型来检测微钙化 照片由伯纳德·斯拉格。新西兰,保留部分权利。

教程概述

本教程分为五个部分;它们是:

  1. 乳腺摄影数据集
  2. 浏览数据集
  3. 模型测试和基线结果
  4. 评估模型
    1. 评估机器学习算法
    2. 评估成本敏感算法
  5. 对新数据进行预测

乳腺摄影数据集

在这个项目中,我们将使用一个标准的不平衡机器学习数据集,称为“乳腺摄影”数据集,有时也称为“伍兹乳腺摄影

该数据集归功于 Kevin Woods 等人和 1993 年发表的题为“乳腺摄影中检测微钙化的模式识别技术的比较评估”的论文

问题的焦点是从放射扫描中检测乳腺癌,特别是在乳房 x 光片上看起来明亮的微钙化簇的存在。

所涉及的数据集首先从扫描的 24 张已知癌症诊断的乳房 x 光照片开始。然后使用图像分割计算机视觉算法对图像进行预处理,以从乳房 x 光照片图像中提取候选对象。一旦被分割,这些对象就被有经验的放射科医生手工标记。

从被认为与模式识别最相关的分割对象中提取了总共 29 个特征,将其减少到 18 个,然后最终减少到 7 个,如下所示(直接取自论文):

  • 对象面积(以像素为单位)。
  • 对象的平均灰度。
  • 对象周边像素的梯度强度。
  • 对象中的均方根噪声波动。
  • 对比度,对象的平均灰度减去对象周围两像素宽边框的平均值。
  • 基于形状描述符的低阶矩。

有两类,目标是使用给定分割对象的特征来区分微钙化和非微钙化。

  • 非微钙化:阴性,或多数类。
  • 微钙化:阳性病例,或少数民族。

在最初的论文中对许多模型进行了评估和比较,例如神经网络、决策树和 k 近邻。使用 ROC 曲线对模型进行评估,并使用 ROC 曲线下的面积(简称 ROC AUC)进行比较。

选择 ROC 曲线和 ROC 曲线下面积的目的是最小化假阳性率(特异性的补充)和最大化真阳性率(灵敏度),ROC 曲线的两个轴。ROC 曲线的使用也暗示了对概率模型的期望,操作员可以从该概率模型中选择概率阈值作为可接受的假阳性率和真阳性率之间的界限。

他们的结果表明“线性分类器”(看似是高斯朴素贝叶斯分类器)表现最好,在 100 次运行中平均 ROC AUC 为 0.936。

接下来,让我们仔细看看数据。

浏览数据集

乳腺摄影数据集是一个广泛使用的标准机器学习数据集,用于探索和演示许多专门为不平衡分类设计的技术。

一个例子是流行的 SMOTE 数据过采样技术

该数据集的一个版本与原始论文中描述的数据集有一些不同。

首先,下载数据集并将其保存在您当前的工作目录中,名称为“乳房 x 线摄影. csv

查看文件的内容。

文件的前几行应该如下所示:

0.23001961,5.0725783,-0.27606055,0.83244412,-0.37786573,0.4803223,'-1'
0.15549112,-0.16939038,0.67065219,-0.85955255,-0.37786573,-0.94572324,'-1'
-0.78441482,-0.44365372,5.6747053,-0.85955255,-0.37786573,-0.94572324,'-1'
0.54608818,0.13141457,-0.45638679,-0.85955255,-0.37786573,-0.94572324,'-1'
-0.10298725,-0.3949941,-0.14081588,0.97970269,-0.37786573,1.0135658,'-1'
...

我们可以看到数据集有六个而不是七个输入变量。论文中列出的第一个输入变量(以像素为单位的区域)可能已从该版本的数据集中删除。

输入变量是数值型的(实值),目标变量是多数类的“-1”和少数类的“1”字符串。这些值需要分别编码为 0 和 1,以满足分类算法对二进制不平衡分类问题的期望。

可以使用 read_csv()熊猫函数将数据集加载为数据帧,指定位置和没有标题行的事实。

...
# define the dataset location
filename = 'mammography.csv'
# load the csv file as a data frame
dataframe = read_csv(filename, header=None)

加载后,我们可以通过打印数据框的形状来总结行数和列数。

...
# summarize the shape of the dataset
print(dataframe.shape)

我们还可以使用 Counter 对象总结每个类中的示例数量。

...
# summarize the class distribution
target = dataframe.values[:,-1]
counter = Counter(target)
for k,v in counter.items():
	per = v / len(target) * 100
	print('Class=%s, Count=%d, Percentage=%.3f%%' % (k, v, per))

将这些联系在一起,下面列出了加载和汇总数据集的完整示例。

# load and summarize the dataset
from pandas import read_csv
from collections import Counter
# define the dataset location
filename = 'mammography.csv'
# load the csv file as a data frame
dataframe = read_csv(filename, header=None)
# summarize the shape of the dataset
print(dataframe.shape)
# summarize the class distribution
target = dataframe.values[:,-1]
counter = Counter(target)
for k,v in counter.items():
	per = v / len(target) * 100
	print('Class=%s, Count=%d, Percentage=%.3f%%' % (k, v, per))

运行该示例首先加载数据集,并确认行数和列数,即 11,183 行,6 个输入变量和 1 个目标变量。

然后总结阶级分布,确认严重的阶级不平衡,多数阶级(无癌症)大约 98%,少数阶级(癌症)大约 2%。

(11183, 7)
Class='-1', Count=10923, Percentage=97.675%
Class='1', Count=260, Percentage=2.325%

数据集似乎与 SMOTE 论文中描述的数据集基本匹配。具体来说就是反面与正面的例子之比。

典型的乳腺摄影数据集可能包含 98%的正常像素和 2%的异常像素。

——SMOTE:合成少数过采样技术,2002 年。

此外,少数族裔和多数族裔的具体例子数量也与论文相符。

实验是在乳腺摄影数据集上进行的。本来多数班有 10923 例,少数班有 260 例。

——SMOTE:合成少数过采样技术,2002 年。

我相信这是同一个数据集,尽管我无法解释输入特征数量的不匹配,例如,六个与原始论文中的七个相比。

我们还可以通过为六个数字输入变量创建直方图来查看它们的分布。

下面列出了完整的示例。

# create histograms of numeric input variables
from pandas import read_csv
from matplotlib import pyplot
# define the dataset location
filename = 'mammography.csv'
# load the csv file as a data frame
df = read_csv(filename, header=None)
# histograms of all variables
df.hist()
pyplot.show()

运行该示例会为数据集中的六个数字输入变量创建带有一个直方图子图的图形。

我们可以看到,变量有不同的尺度,大多数变量呈指数分布,例如,大多数情况下落入一个仓,其余的落入一个长尾。最后一个变量似乎呈双峰分布。

根据建模算法的选择,我们期望将分布缩放到相同的范围是有用的,并且可能使用一些幂变换。

Histogram Plots of the Numerical Input Variables for the Mammography Dataset

乳腺摄影数据集数值输入变量的直方图

我们还可以为每对输入变量创建散点图,称为散点图矩阵。

这有助于查看是否有任何变量相互关联或朝同一方向变化,例如相互关联。

我们还可以根据类别标签为每个散点图的点着色。在这种情况下,多数类(无癌症)将被映射到蓝点,少数类(癌症)将被映射到红点。

下面列出了完整的示例。

# create pairwise scatter plots of numeric input variables
from pandas import read_csv
from pandas.plotting import scatter_matrix
from matplotlib import pyplot
# define the dataset location
filename = 'mammography.csv'
# load the csv file as a data frame
df = read_csv(filename, header=None)
# define a mapping of class values to colors
color_dict = {"'-1'":'blue', "'1'":'red'}
# map each row to a color based on the class value
colors = [color_dict[str(x)] for x in df.values[:, -1]]
# pairwise scatter plots of all numerical variables
scatter_matrix(df, diagonal='kde', color=colors)
pyplot.show()

运行该示例会创建一个显示散点图矩阵的图形,六个图乘以六个图,将六个数字输入变量相互比较。矩阵的对角线显示了每个变量的密度分布。

每对在从左上方到右下方的对角线的上方和下方出现两次,提供了两种方式来查看相同的变量交互。

我们可以看到,对于两类标签,许多变量的分布确实不同,这表明在癌症和无癌症病例之间进行一些合理的区分是可行的。

Scatter Plot Matrix by Class for the Numerical Input Variables in the Mammography Dataset

乳腺摄影数据集中数值输入变量的分类散点图矩阵

现在我们已经回顾了数据集,让我们看看开发一个测试工具来评估候选模型。

模型测试和基线结果

我们将使用重复的分层 k 折叠交叉验证来评估候选模型。

k 倍交叉验证程序提供了一个良好的模型表现的总体估计,至少与单个列车测试分割相比,不太乐观。我们将使用 k=10,这意味着每个折叠将包含大约 11183/10 或大约 1,118 个示例。

分层意味着每个文件夹将包含相同的混合类实例,即大约 98%到 2%的无癌到癌对象。重复表示评估过程将执行多次,以帮助避免侥幸结果,并更好地捕捉所选模型的方差。我们将使用三次重复。

这意味着单个模型将被拟合和评估 10 * 3 或 30 次,并且这些运行的平均值和标准偏差将被报告。

这可以通过使用repeated stratifiedfold Sklearn 类来实现。

我们将使用 ROC 曲线下的面积或通过 roc_auc_score()函数计算的 ROC AUC 来评估和比较模型。

我们可以定义一个函数来加载数据集,并将列分成输入和输出变量。我们将正确地将类标签编码为 0 和 1。下面的 load_dataset() 函数实现了这一点。

# load the dataset
def load_dataset(full_path):
	# load the dataset as a numpy array
	data = read_csv(full_path, header=None)
	# retrieve numpy array
	data = data.values
	# split into input and output elements
	X, y = data[:, :-1], data[:, -1]
	# label encode the target variable to have the classes 0 and 1
	y = LabelEncoder().fit_transform(y)
	return X, y

然后,我们可以定义一个函数来评估数据集上的给定模型,并返回每次折叠和重复的 ROC AUC 分数列表。

下面的 evaluate_model() 函数实现了这一点,将数据集和模型作为参数,返回分数列表。

# evaluate a model
def evaluate_model(X, y, model):
	# define evaluation procedure
	cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
	# evaluate model
	scores = cross_val_score(model, X, y, scoring='roc_auc', cv=cv, n_jobs=-1)
	return scores

最后,我们可以使用这个测试工具在数据集上评估一个基线模型。

一个预测随机类与每个类的基本比率成比例的模型将导致 0.5 的 ROC AUC,这是该数据集上表现的基线。这是一个所谓的“无技能”分类器。

这可以通过使用 Sklearn 库中的 DummyClassifier 类并将“策略”参数设置为“分层”来实现。

...
# define the reference model
model = DummyClassifier(strategy='stratified')

一旦模型被评估,我们可以直接报告 ROC AUC 评分的平均值和标准差。

...
# evaluate the model
scores = evaluate_model(X, y, model)
# summarize performance
print('Mean ROC AUC: %.3f (%.3f)' % (mean(scores), std(scores)))

将这些结合起来,下面列出了加载数据集、评估基线模型和报告表现的完整示例。

# test harness and baseline model evaluation
from collections import Counter
from numpy import mean
from numpy import std
from pandas import read_csv
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import RepeatedStratifiedKFold
from sklearn.dummy import DummyClassifier

# load the dataset
def load_dataset(full_path):
	# load the dataset as a numpy array
	data = read_csv(full_path, header=None)
	# retrieve numpy array
	data = data.values
	# split into input and output elements
	X, y = data[:, :-1], data[:, -1]
	# label encode the target variable to have the classes 0 and 1
	y = LabelEncoder().fit_transform(y)
	return X, y

# evaluate a model
def evaluate_model(X, y, model):
	# define evaluation procedure
	cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
	# evaluate model
	scores = cross_val_score(model, X, y, scoring='roc_auc', cv=cv, n_jobs=-1)
	return scores

# define the location of the dataset
full_path = 'mammography.csv'
# load the dataset
X, y = load_dataset(full_path)
# summarize the loaded dataset
print(X.shape, y.shape, Counter(y))
# define the reference model
model = DummyClassifier(strategy='stratified')
# evaluate the model
scores = evaluate_model(X, y, model)
# summarize performance
print('Mean ROC AUC: %.3f (%.3f)' % (mean(scores), std(scores)))

运行该示例首先加载和汇总数据集。

我们可以看到我们加载了正确的行数,并且我们有六个计算机视觉导出的输入变量。重要的是,我们可以看到类标签正确映射到整数,多数类为 0,少数类为 1,这是不平衡二进制类别数据集的惯例。

接下来,报告 ROC AUC 分数的平均值。

不出所料,无技能分类器实现了平均 ROC AUC 约为 0.5 的最坏情况表现。这提供了一个表现基线,在这个基线之上,模型在这个数据集上可以被认为是熟练的。

(11183, 6) (11183,) Counter({0: 10923, 1: 260})
Mean ROC AUC: 0.503 (0.016)

现在我们已经有了测试工具和表现基线,我们可以开始在这个数据集上评估一些模型了。

评估模型

在本节中,我们将使用上一节中开发的测试工具来评估数据集上的一套不同技术。

目标是既演示如何系统地解决问题,又演示为不平衡分类问题设计的一些技术的能力。

报告的表现良好,但没有高度优化(例如,超参数没有调整)。

**你能做得更好吗?**如果你能用同样的测试装具达到更好的 ROC AUC 表现,我很想听听。请在下面的评论中告诉我。

评估机器学习算法

让我们从评估数据集上的混合机器学习模型开始。

在数据集上抽查一套不同的线性和非线性算法可能是一个好主意,以便快速找出哪些算法运行良好,值得进一步关注,哪些算法运行不佳。

我们将在乳腺摄影数据集上评估以下机器学习模型:

  • 逻辑回归
  • 支持向量机(SVM)
  • 袋装决策树
  • 随机森林
  • 梯度增压机

我们将主要使用默认的模型超参数,除了集成算法中的树的数量,我们将设置为合理的默认值 1000。

我们将依次定义每个模型,并将它们添加到一个列表中,以便我们可以顺序评估它们。下面的 get_models() 函数定义了用于评估的模型列表,以及用于以后绘制结果的模型简称列表。

# define models to test
def get_models():
	models, names = list(), list()
	# LR
	models.append(LogisticRegression(solver='lbfgs'))
	names.append('LR')
	# SVM
	models.append(SVC(gamma='scale'))
	names.append('SVM')
	# Bagging
	models.append(BaggingClassifier(n_estimators=1000))
	names.append('BAG')
	# RF
	models.append(RandomForestClassifier(n_estimators=1000))
	names.append('RF')
	# GBM
	models.append(GradientBoostingClassifier(n_estimators=1000))
	names.append('GBM')
	return models, names

然后,我们可以依次列举模型列表,并对每个模型进行评估,报告平均 ROC AUC,并存储分数以供以后绘制。

...
# define models
models, names = get_models()
results = list()
# evaluate each model
for i in range(len(models)):
	# evaluate the model and store results
	scores = evaluate_model(X, y, models[i])
	results.append(scores)
	# summarize and store
	print('>%s %.3f (%.3f)' % (names[i], mean(scores), std(scores)))

在运行结束时,我们可以将每个分数样本绘制成一个方框,并用相同的比例绘制晶须图,这样我们就可以直接比较分布。

...
# plot the results
pyplot.boxplot(results, labels=names, showmeans=True)
pyplot.show()

将所有这些结合起来,下面列出了在乳腺摄影数据集上评估一套机器学习算法的完整示例。

# spot check machine learning algorithms on the mammography dataset
from numpy import mean
from numpy import std
from pandas import read_csv
from matplotlib import pyplot
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import RepeatedStratifiedKFold
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.ensemble import BaggingClassifier

# load the dataset
def load_dataset(full_path):
	# load the dataset as a numpy array
	data = read_csv(full_path, header=None)
	# retrieve numpy array
	data = data.values
	# split into input and output elements
	X, y = data[:, :-1], data[:, -1]
	# label encode the target variable to have the classes 0 and 1
	y = LabelEncoder().fit_transform(y)
	return X, y

# evaluate a model
def evaluate_model(X, y, model):
	# define evaluation procedure
	cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
	# evaluate model
	scores = cross_val_score(model, X, y, scoring='roc_auc', cv=cv, n_jobs=-1)
	return scores

# define models to test
def get_models():
	models, names = list(), list()
	# LR
	models.append(LogisticRegression(solver='lbfgs'))
	names.append('LR')
	# SVM
	models.append(SVC(gamma='scale'))
	names.append('SVM')
	# Bagging
	models.append(BaggingClassifier(n_estimators=1000))
	names.append('BAG')
	# RF
	models.append(RandomForestClassifier(n_estimators=1000))
	names.append('RF')
	# GBM
	models.append(GradientBoostingClassifier(n_estimators=1000))
	names.append('GBM')
	return models, names

# define the location of the dataset
full_path = 'mammography.csv'
# load the dataset
X, y = load_dataset(full_path)
# define models
models, names = get_models()
results = list()
# evaluate each model
for i in range(len(models)):
	# evaluate the model and store results
	scores = evaluate_model(X, y, models[i])
	results.append(scores)
	# summarize and store
	print('>%s %.3f (%.3f)' % (names[i], mean(scores), std(scores)))
# plot the results
pyplot.boxplot(results, labels=names, showmeans=True)
pyplot.show()

运行该示例依次评估每个算法,并报告平均值和标准差 ROC AUC。

:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。

在这种情况下,我们可以看到所有测试的算法都有技巧,实现了高于默认值 0.5 的 ROC AUC。

结果表明,决策树算法的集成在这个数据集上表现更好,也许随机森林表现最好,ROC AUC 约为 0.950。

有趣的是,尽管我们使用了不同的模型评估程序,但这比论文中描述的 ROC AUC 要好。

该评估对 LR 和 SVM 算法有点不公平,因为我们在拟合模型之前没有缩放输入变量。我们可以在下一节探讨这一点。

>LR 0.919 (0.040)
>SVM 0.880 (0.049)
>BAG 0.941 (0.041)
>RF 0.950 (0.036)
>GBM 0.918 (0.037)

创建一个图形,显示每个算法结果样本的一个方框和须图。方框显示中间 50%的数据,每个方框中间的橙色线显示样本的中值,每个方框中的绿色三角形显示样本的平均值。

我们可以看到,BAG 和 RF 都有一个紧密的分布,平均值和中值紧密对齐,这可能表明分数的非偏斜和高斯分布,例如稳定。

Box and Whisker Plot of Machine Learning Models on the Imbalanced Mammography Dataset

不平衡乳腺摄影数据集上机器学习模型的盒须图

现在我们有了第一组好的结果,让我们看看是否可以用成本敏感的分类器来改进它们。

评估成本敏感算法

当拟合模型时,一些机器学习算法可以被调整以更加关注一个类而不是另一个类。

这些被称为成本敏感的机器学习模型,它们可以通过指定与类分布成反比的成本来用于不平衡分类。例如,对于多数类和少数类的 98%到 2%的分布,我们可以指定少数类的误差权重为 98,多数类的误差权重为 2。

提供这种功能的三种算法是:

  • 逻辑回归
  • 支持向量机(SVM)
  • 随机森林

这可以在 Sklearn 中通过将“ class_weight ”参数设置为“ balanced ”来实现,以使这些算法对成本敏感。

例如,下面更新的 get_models() 函数定义了要在我们的数据集上评估的这三个算法的成本敏感版本。

# define models to test
def get_models():
	models, names = list(), list()
	# LR
	models.append(LogisticRegression(solver='lbfgs', class_weight='balanced'))
	names.append('LR')
	# SVM
	models.append(SVC(gamma='scale', class_weight='balanced'))
	names.append('SVM')
	# RF
	models.append(RandomForestClassifier(n_estimators=1000))
	names.append('RF')
	return models, names

此外,当探索数据集时,我们注意到许多变量具有看似指数的数据分布。有时,我们可以通过对每个变量使用幂变换来更好地传播变量的数据。这将特别有助于 LR 和 SVM 算法,也可能有助于 RF 算法。

我们可以使用管道在交叉验证模型评估过程的每个折叠中实现这一点。第一步将学习训练套摺上的电力变压器,并将其应用于训练和测试套摺。第二步将是我们正在评估的模型。然后可以使用我们的*评估 _ 模型()*函数直接评估管道,例如:

...
# defines pipeline steps
steps = [('p', PowerTransformer()), ('m',models[i])]
# define pipeline
pipeline = Pipeline(steps=steps)
# evaluate the pipeline and store results
scores = evaluate_model(X, y, pipeline)

将这些结合起来,下面列出了在乳腺摄影数据集上评估功率转换的成本敏感型机器学习算法的完整示例。

# cost-sensitive machine learning algorithms on the mammography dataset
from numpy import mean
from numpy import std
from pandas import read_csv
from matplotlib import pyplot
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import PowerTransformer
from sklearn.pipeline import Pipeline
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import RepeatedStratifiedKFold
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier

# load the dataset
def load_dataset(full_path):
	# load the dataset as a numpy array
	data = read_csv(full_path, header=None)
	# retrieve numpy array
	data = data.values
	# split into input and output elements
	X, y = data[:, :-1], data[:, -1]
	# label encode the target variable to have the classes 0 and 1
	y = LabelEncoder().fit_transform(y)
	return X, y

# evaluate a model
def evaluate_model(X, y, model):
	# define evaluation procedure
	cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
	# evaluate model
	scores = cross_val_score(model, X, y, scoring='roc_auc', cv=cv, n_jobs=-1)
	return scores

# define models to test
def get_models():
	models, names = list(), list()
	# LR
	models.append(LogisticRegression(solver='lbfgs', class_weight='balanced'))
	names.append('LR')
	# SVM
	models.append(SVC(gamma='scale', class_weight='balanced'))
	names.append('SVM')
	# RF
	models.append(RandomForestClassifier(n_estimators=1000))
	names.append('RF')
	return models, names

# define the location of the dataset
full_path = 'mammography.csv'
# load the dataset
X, y = load_dataset(full_path)
# define models
models, names = get_models()
results = list()
# evaluate each model
for i in range(len(models)):
	# defines pipeline steps
	steps = [('p', PowerTransformer()), ('m',models[i])]
	# define pipeline
	pipeline = Pipeline(steps=steps)
	# evaluate the pipeline and store results
	scores = evaluate_model(X, y, pipeline)
	results.append(scores)
	# summarize and store
	print('>%s %.3f (%.3f)' % (names[i], mean(scores), std(scores)))
# plot the results
pyplot.boxplot(results, labels=names, showmeans=True)
pyplot.show()

运行该示例依次评估每个算法,并报告平均值和标准差 ROC AUC。

:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。

在这种情况下,我们可以看到,与它们的非转换和成本不敏感的版本相比,所有三种测试算法在 ROC AUC 上都有所提升。在没有转换的情况下重复实验,看看是转换还是算法的成本敏感版本,或者两者都导致了表现的提升,这将是很有趣的。

在这种情况下,我们可以看到 SVM 获得了最佳表现,在本节和上一节中表现优于射频,平均 ROC AUC 约为 0.957。

>LR 0.922 (0.036)
>SVM 0.957 (0.024)
>RF 0.951 (0.035)

然后创建方框图和须图,比较 ROC 曲线的分布。

与其他两种模型相比,SVM 分布显得紧凑。因此,表现可能是稳定的,并可能成为最终模型的良好选择。

Box and Whisker Plots of Cost-Sensitive Machine Learning Models on the Imbalanced Mammography Dataset

不平衡乳腺摄影数据集上成本敏感机器学习模型的盒须图

接下来,让我们看看如何使用最终模型对新数据进行预测。

对新数据进行预测

在本节中,我们将拟合最终模型,并使用它对单行数据进行预测

我们将使用 SVM 模型的成本敏感版本作为最终模型,并在拟合模型和进行预测之前对数据进行幂变换。使用管道将确保始终对输入数据正确执行转换。

首先,我们可以将模型定义为管道。

...
# define model to evaluate
model = SVC(gamma='scale', class_weight='balanced')
# power transform then fit model
pipeline = Pipeline(steps=[('t',PowerTransformer()), ('m',model)])

一旦定义好了,我们就可以在整个训练数据集中使用它。

...
# fit the model
pipeline.fit(X, y)

一旦适合,我们可以通过调用 predict() 函数来使用它对新数据进行预测。这将返回类别标签 0 表示“T2 无癌症”,或 1 表示“T4 癌症”。

例如:

...
# define a row of data
row = [...]
# make prediction
yhat = model.predict([row])

为了证明这一点,我们可以使用拟合模型对一些病例的标签进行一些预测,在这些病例中,我们知道该病例是无癌症还是癌症。

下面列出了完整的示例。

# fit a model and make predictions for the on the mammography dataset
from pandas import read_csv
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import PowerTransformer
from sklearn.svm import SVC
from sklearn.pipeline import Pipeline

# load the dataset
def load_dataset(full_path):
	# load the dataset as a numpy array
	data = read_csv(full_path, header=None)
	# retrieve numpy array
	data = data.values
	# split into input and output elements
	X, y = data[:, :-1], data[:, -1]
	# label encode the target variable to have the classes 0 and 1
	y = LabelEncoder().fit_transform(y)
	return X, y

# define the location of the dataset
full_path = 'mammography.csv'
# load the dataset
X, y = load_dataset(full_path)
# define model to evaluate
model = SVC(gamma='scale', class_weight='balanced')
# power transform then fit model
pipeline = Pipeline(steps=[('t',PowerTransformer()), ('m',model)])
# fit the model
pipeline.fit(X, y)
# evaluate on some no cancer cases (known class 0)
print('No Cancer:')
data = [[0.23001961,5.0725783,-0.27606055,0.83244412,-0.37786573,0.4803223],
	[0.15549112,-0.16939038,0.67065219,-0.85955255,-0.37786573,-0.94572324],
	[-0.78441482,-0.44365372,5.6747053,-0.85955255,-0.37786573,-0.94572324]]
for row in data:
	# make prediction
	yhat = pipeline.predict([row])
	# get the label
	label = yhat[0]
	# summarize
	print('>Predicted=%d (expected 0)' % (label))
# evaluate on some cancer (known class 1)
print('Cancer:')
data = [[2.0158239,0.15353258,-0.32114211,2.1923706,-0.37786573,0.96176503],
	[2.3191888,0.72860087,-0.50146835,-0.85955255,-0.37786573,-0.94572324],
	[0.19224721,-0.2003556,-0.230979,1.2003796,2.2620867,1.132403]]
for row in data:
	# make prediction
	yhat = pipeline.predict([row])
	# get the label
	label = yhat[0]
	# summarize
	print('>Predicted=%d (expected 1)' % (label))

运行该示例首先在整个训练数据集上拟合模型。

然后从数据集文件中选择用于预测无癌症病例标签的拟合模型。我们可以看到所有的情况都是正确预测的。

然后将一些实际癌症病例用作模型的输入,并预测标签。正如我们所希望的那样,所有情况下都能预测到正确的标签。

No Cancer:
>Predicted=0 (expected 0)
>Predicted=0 (expected 0)
>Predicted=0 (expected 0)
Cancer:
>Predicted=1 (expected 1)
>Predicted=1 (expected 1)
>Predicted=1 (expected 1)

进一步阅读

如果您想更深入地了解这个主题,本节将提供更多资源。

报纸

蜜蜂

资料组

摘要

在本教程中,您发现了如何开发和评估不平衡乳腺摄影癌症类别数据集的模型。

具体来说,您了解到:

  • 如何加载和探索数据集,并为数据准备和模型选择产生想法。
  • 如何评估一套机器学习模型,并利用数据成本敏感技术提高其表现。
  • 如何拟合最终模型并使用它来预测特定情况下的类标签。

你有什么问题吗? 在下面的评论中提问,我会尽力回答。

如何开发不平衡分类模型来检测漏油

原文:machinelearningmastery.com/imbalanced-…

最后更新于 2021 年 1 月 5 日

许多不平衡的分类任务需要一个熟练的模型来预测一个清晰的类标签,其中两个类同等重要。

不平衡分类问题的一个例子是卫星图像中漏油或油膜的检测,其中需要类别标签,两个类别同等重要。泄漏的检测需要动员昂贵的响应,错过一个事件也同样昂贵,对环境造成损害。

评估预测清晰标签的不平衡分类模型的一种方法是计算阳性类别和阴性类别的单独准确度,称为灵敏度和特异性。然后,可以使用几何平均值(称为 G 平均值)对这两个度量进行平均,该几何平均值对偏斜的类分布不敏感,并正确报告模型在这两个类上的技能。

在本教程中,您将发现如何开发一个模型来预测卫星图像中漏油的存在,并使用 G 均值指标进行评估。

完成本教程后,您将知道:

  • 如何加载和探索数据集,并为数据准备和模型选择产生想法。
  • 如何评估一套概率模型,并通过适当的数据准备提高它们的表现。
  • 如何拟合最终模型并使用它来预测特定情况下的类标签。

用我的新书Python 不平衡分类启动你的项目,包括分步教程和所有示例的 Python 源代码文件。

我们开始吧。

  • 2021 年 1 月更新:更新了 API 文档的链接。

Develop an Imbalanced Classification Model to Detect Oil Spills

开发不平衡分类模型来检测漏油 图片由莱尼·K 摄影拍摄,保留部分权利。

教程概述

本教程分为五个部分;它们是:

  1. 漏油数据集
  2. 浏览数据集
  3. 模型测试和基线结果
  4. 评估模型
    1. 评估概率模型
    2. 评估平衡逻辑回归
    3. 用概率模型评估重采样
  5. 对新数据进行预测

漏油数据集

在本项目中,我们将使用标准的不平衡机器学习数据集,称为“漏油”数据集,“油膜数据集”或简称为“

该数据集是由米罗斯拉夫·库巴特等人在 1998 年发表的论文中引入的,该论文的标题为“用于检测卫星雷达图像中漏油的 T2 机器学习”该数据集通常被归功于该论文的合著者罗伯特·霍尔特

该数据集是从海洋的卫星图像开始开发的,其中一些包含漏油,一些不包含。图像被分割成多个部分,并使用计算机视觉算法进行处理,以提供特征向量来描述图像部分或块的内容。

[系统]的输入是来自雷达卫星的原始像素图像。使用图像处理技术[……]图像处理的输出是每个可疑区域的固定长度特征向量。在正常操作期间,这些特征向量被馈送到分类器中,以决定图像中的哪些图像和哪些区域呈现给人类检查。

——卫星雷达图像漏油检测的机器学习,1998。

该任务被赋予一个矢量,该矢量描述了一幅卫星图像的内容,然后预测该图像是否包含石油泄漏,例如非法或意外倾倒在海洋中的石油。

有 937 例。每个案例由 48 个数字计算机视觉衍生特征、一个补丁编号和一个类别标签组成。

总共有九幅卫星图像被处理成小块。数据集中的案例按图像排序,数据集中的第一列表示图像的面片编号。这是为了估计每个图像的模型表现而提供的。在这种情况下,我们对图像或补丁编号不感兴趣,可以删除第一列。

正常情况下,没有漏油被指定为 0 级标签,而漏油被指定为 1 级标签。无漏油 896 例,漏油 41 例。

石油泄漏领域的第二个关键特征可以称为不平衡训练集:反面例子相似物比正面例子油膜多得多。相对于 41 个正面的例子,我们有 896 个负面的例子,因此多数类构成了几乎 96%的数据。

——卫星雷达图像漏油检测的机器学习,1998。

我们无法访问用于从卫星图像中准备计算机视觉特征的程序,因此我们只能处理收集并提供的提取特征。

接下来,让我们仔细看看数据。

浏览数据集

首先,下载数据集并将其保存在您当前的工作目录中,名称为“漏油. csv

查看文件的内容。

文件的前几行应该如下所示:

1,2558,1506.09,456.63,90,6395000,40.88,7.89,29780,0.19,214.7,0.21,0.26,0.49,0.1,0.4,99.59,32.19,1.84,0.16,0.2,87.65,0,0.47,132.78,-0.01,3.78,0.22,3.2,-3.71,-0.18,2.19,0,2.19,310,16110,0,138.68,89,69,2850,1000,763.16,135.46,3.73,0,33243.19,65.74,7.95,1
2,22325,79.11,841.03,180,55812500,51.11,1.21,61900,0.02,901.7,0.02,0.03,0.11,0.01,0.11,6058.23,4061.15,2.3,0.02,0.02,87.65,0,0.58,132.78,-0.01,3.78,0.84,7.09,-2.21,0,0,0,0,704,40140,0,68.65,89,69,5750,11500,9593.48,1648.8,0.6,0,51572.04,65.73,6.26,0
3,115,1449.85,608.43,88,287500,40.42,7.34,3340,0.18,86.1,0.21,0.32,0.5,0.17,0.34,71.2,16.73,1.82,0.19,0.29,87.65,0,0.46,132.78,-0.01,3.78,0.7,4.79,-3.36,-0.23,1.95,0,1.95,29,1530,0.01,38.8,89,69,1400,250,150,45.13,9.33,1,31692.84,65.81,7.84,1
4,1201,1562.53,295.65,66,3002500,42.4,7.97,18030,0.19,166.5,0.21,0.26,0.48,0.1,0.38,120.22,33.47,1.91,0.16,0.21,87.65,0,0.48,132.78,-0.01,3.78,0.84,6.78,-3.54,-0.33,2.2,0,2.2,183,10080,0,108.27,89,69,6041.52,761.58,453.21,144.97,13.33,1,37696.21,65.67,8.07,1
5,312,950.27,440.86,37,780000,41.43,7.03,3350,0.17,232.8,0.15,0.19,0.35,0.09,0.26,289.19,48.68,1.86,0.13,0.16,87.65,0,0.47,132.78,-0.01,3.78,0.02,2.28,-3.44,-0.44,2.19,0,2.19,45,2340,0,14.39,89,69,1320.04,710.63,512.54,109.16,2.58,0,29038.17,65.66,7.35,0
...

我们可以看到第一列包含补丁号的整数。我们还可以看到,计算机视觉导出的特征是实值的,具有不同的比例,例如第二列中的千分之一和其他列中的分数。

所有输入变量均为数值,没有标有“”的缺失值?“性格。

首先,我们可以加载 CSV 数据集并确认行数和列数。

可以使用 read_csv()熊猫函数将数据集加载为数据帧,指定位置和没有标题行的事实。

...
# define the dataset location
filename = 'oil-spill.csv'
# load the csv file as a data frame
dataframe = read_csv(filename, header=None)

加载后,我们可以通过打印数据框的形状来总结行数和列数。

...
# summarize the shape of the dataset
print(dataframe.shape)

我们还可以使用 Counter 对象总结每个类中的示例数量。

...
# summarize the class distribution
target = dataframe.values[:,-1]
counter = Counter(target)
for k,v in counter.items():
	per = v / len(target) * 100
	print('Class=%d, Count=%d, Percentage=%.3f%%' % (k, v, per))

将这些联系在一起,下面列出了加载和汇总数据集的完整示例。

# load and summarize the dataset
from pandas import read_csv
from collections import Counter
# define the dataset location
filename = 'oil-spill.csv'
# load the csv file as a data frame
dataframe = read_csv(filename, header=None)
# summarize the shape of the dataset
print(dataframe.shape)
# summarize the class distribution
target = dataframe.values[:,-1]
counter = Counter(target)
for k,v in counter.items():
	per = v / len(target) * 100
	print('Class=%d, Count=%d, Percentage=%.3f%%' % (k, v, per))

运行该示例首先加载数据集并确认行数和列数。

然后总结了类别分布,确认了漏油和非漏油的数量以及少数和多数类别中的案例百分比。

(937, 50)
Class=1, Count=41, Percentage=4.376%
Class=0, Count=896, Percentage=95.624%

我们还可以通过为每个变量创建直方图来查看每个变量的分布。

有 50 个变量,有很多情节,但我们可能会发现一些有趣的模式。另外,有这么多的图,我们必须关闭轴标签和图标题,以减少混乱。下面列出了完整的示例。

# create histograms of each variable
from pandas import read_csv
from matplotlib import pyplot
# define the dataset location
filename = 'oil-spill.csv'
# load the csv file as a data frame
dataframe = read_csv(filename, header=None)
# create a histogram plot of each variable
ax = dataframe.hist()
# disable axis labels
for axis in ax.flatten():
	axis.set_title('')
	axis.set_xticklabels([])
	axis.set_yticklabels([])
pyplot.show()

运行该示例会为数据集中的 50 个变量分别创建一个直方图子图。

我们可以看到许多不同的分布,有些是类高斯分布,有些是看似指数或离散分布。

根据建模算法的选择,我们期望将分布缩放到相同的范围是有用的,并且可能使用一些幂变换。

Histogram of Each Variable in the Oil Spill Dataset

漏油数据集中每个变量的直方图

现在我们已经回顾了数据集,让我们看看开发一个测试工具来评估候选模型。

模型测试和基线结果

我们将使用重复的分层 k 折叠交叉验证来评估候选模型。

k 倍交叉验证程序提供了一个良好的模型表现的总体估计,至少与单个列车测试分割相比,不太乐观。我们将使用 k=10,这意味着每个折叠将包含大约 937/10 或大约 94 个示例。

分层意味着每个折叠将包含相同的混合类示例,即大约 96%到 4%的非溢出和溢出。重复意味着评估过程将执行多次,以帮助避免侥幸结果,并更好地捕捉所选模型的方差。我们将使用三次重复。

这意味着单个模型将被拟合和评估 10 * 3 或 30 次,并且将报告这些运行的平均值和标准偏差。

这可以通过使用repeated stratifiedfold Sklearn 类来实现。

我们正在预测卫星图像补丁是否包含溢出的类别标签。我们可以使用许多测量方法,尽管论文的作者选择报告这两个分数的敏感性、特异性和几何平均值,称为 G 均值。

为此,我们主要使用了几何均值(g 均值)[……]这种度量具有独立于类间示例分布的独特性质,因此在这种分布可能随时间变化或在训练和测试集中不同的情况下是稳健的。

——卫星雷达图像漏油检测的机器学习,1998。

回想一下,灵敏度是阳性类别准确性的量度,特异性是阴性类别准确性的量度。

  • 灵敏度=真阳性/(真阳性+假阴性)
  • 特异性=真阴性/(真阴性+假阳性)

G 均值寻求这些分数的平衡,即几何均值,其中一个或另一个的不良表现导致低 G 均值分数。

  • g-均值= sqrt(灵敏度*特异性)

我们可以使用不平衡学习库提供的几何均值分数()函数来计算模型所做的一组预测的 G 均值。

首先,我们可以定义一个函数来加载数据集,并将列分成输入和输出变量。我们还将删除第 22 列,因为该列包含一个值,第一列定义了图像补丁号。下面的 load_dataset() 函数实现了这一点。

# load the dataset
def load_dataset(full_path):
	# load the dataset as a numpy array
	data = read_csv(full_path, header=None)
	# drop unused columns
	data.drop(22, axis=1, inplace=True)
	data.drop(0, axis=1, inplace=True)
	# retrieve numpy array
	data = data.values
	# split into input and output elements
	X, y = data[:, :-1], data[:, -1]
	# label encode the target variable to have the classes 0 and 1
	y = LabelEncoder().fit_transform(y)
	return X, y

然后,我们可以定义一个函数来评估数据集上的给定模型,并返回每次折叠和重复的 G 均值分数列表。

下面的 evaluate_model() 函数实现了这一点,将数据集和模型作为参数,返回分数列表。

# evaluate a model
def evaluate_model(X, y, model):
	# define evaluation procedure
	cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
	# define the model evaluation metric
	metric = make_scorer(geometric_mean_score)
	# evaluate model
	scores = cross_val_score(model, X, y, scoring=metric, cv=cv, n_jobs=-1)
	return scores

最后,我们可以使用这个测试工具在数据集上评估一个基线模型。

预测所有情况下的多数类标签(0)或少数类标签(1)的模型将导致 G 均值为零。因此,一个好的默认策略是以 50%的概率随机预测一个或另一个类别标签,目标是 0.5 左右的 G 均值。

这可以通过使用 Sklearn 库中的 DummyClassifier 类并将“策略”参数设置为“制服”来实现。

...
# define the reference model
model = DummyClassifier(strategy='uniform')

一旦模型得到评估,我们就可以直接报告 G 均值分数的均值和标准差。

...
# evaluate the model
result_m, result_s = evaluate_model(X, y, model)
# summarize performance
print('Mean G-Mean: %.3f (%.3f)' % (result_m, result_s))

将这些结合起来,下面列出了加载数据集、评估基线模型和报告表现的完整示例。

# test harness and baseline model evaluation
from collections import Counter
from numpy import mean
from numpy import std
from pandas import read_csv
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import RepeatedStratifiedKFold
from imblearn.metrics import geometric_mean_score
from sklearn.metrics import make_scorer
from sklearn.dummy import DummyClassifier

# load the dataset
def load_dataset(full_path):
	# load the dataset as a numpy array
	data = read_csv(full_path, header=None)
	# drop unused columns
	data.drop(22, axis=1, inplace=True)
	data.drop(0, axis=1, inplace=True)
	# retrieve numpy array
	data = data.values
	# split into input and output elements
	X, y = data[:, :-1], data[:, -1]
	# label encode the target variable to have the classes 0 and 1
	y = LabelEncoder().fit_transform(y)
	return X, y

# evaluate a model
def evaluate_model(X, y, model):
	# define evaluation procedure
	cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
	# define the model evaluation metric
	metric = make_scorer(geometric_mean_score)
	# evaluate model
	scores = cross_val_score(model, X, y, scoring=metric, cv=cv, n_jobs=-1)
	return scores

# define the location of the dataset
full_path = 'oil-spill.csv'
# load the dataset
X, y = load_dataset(full_path)
# summarize the loaded dataset
print(X.shape, y.shape, Counter(y))
# define the reference model
model = DummyClassifier(strategy='uniform')
# evaluate the model
scores = evaluate_model(X, y, model)
# summarize performance
print('Mean G-Mean: %.3f (%.3f)' % (mean(scores), std(scores)))

运行该示例首先加载和汇总数据集。

我们可以看到我们加载了正确的行数,并且我们有 47 个计算机视觉导出的输入变量,删除了常数值列(索引 22)和补丁号列(索引 0)。

重要的是,我们可以看到类标签正确映射到整数,多数类为 0,少数类为 1,这是不平衡二进制类别数据集的惯例。

接下来,报告 G 均值分数的平均值。

在这种情况下,我们可以看到基线算法实现了约 0.47 的 G 均值,接近理论最大值 0.5。这个分数提供了模特技能的下限;任何平均 G 均值高于约 0.47(或真正高于 0.5)的模型都有技能,而得分低于该值的模型在该数据集上没有技能。

(937, 47) (937,) Counter({0: 896, 1: 41})
Mean G-Mean: 0.478 (0.143)

有趣的是,尽管模型评估程序不同,但论文中报告的良好 G 均值约为 0.811。这为该数据集上的“良好的”表现提供了一个粗略的目标。

现在我们已经有了测试工具和表现基线,我们可以开始在这个数据集上评估一些模型了。

评估模型

在本节中,我们将使用上一节中开发的测试工具来评估数据集上的一套不同技术。

目标是既演示如何系统地解决问题,又演示为不平衡分类问题设计的一些技术的能力。

报告的表现良好,但没有高度优化(例如,超参数没有调整)。

**能得多少分?**如果你能用同样的测试装具获得更好的 G 均值表现,我很想听听。请在下面的评论中告诉我。

评估概率模型

让我们从评估数据集上的一些概率模型开始。

概率模型是那些在概率框架下适合于数据的模型,并且通常对于不平衡类别数据集表现良好。

我们将使用数据集中的默认超参数评估以下概率模型:

  • 逻辑回归
  • 线性判别分析
  • 高斯朴素贝叶斯

LR 和 LDA 都对输入变量的规模敏感,并且通常期望和/或执行得更好,如果具有不同规模的输入变量被标准化或规范化作为预处理步骤。

在这种情况下,我们将在拟合每个模型之前标准化数据集。这将使用管道标准缩放器类来实现。管道的使用确保了标准缩放器适合训练数据集,并应用于每个 k 倍交叉验证评估中的训练集和测试集,避免了任何可能导致乐观结果的数据泄漏。

我们可以定义一个模型列表来评估我们的测试工具,如下所示:

...
# define models
models, names, results = list(), list(), list()
# LR
models.append(Pipeline(steps=[('t', StandardScaler()),('m',LogisticRegression(solver='liblinear'))]))
names.append('LR')
# LDA
models.append(Pipeline(steps=[('t', StandardScaler()),('m',LinearDiscriminantAnalysis())]))
names.append('LDA')
# NB
models.append(GaussianNB())
names.append('NB')

一旦定义好,我们就可以枚举列表并依次评估每个列表。评估时可打印 G 均值分数的均值和标准差,并可存储分数样本。

算法可以直接根据它们的平均 G 均值分数进行比较。

...
# evaluate each model
for i in range(len(models)):
	# evaluate the model and store results
	scores = evaluate_model(X, y, models[i])
	results.append(scores)
	# summarize and store
	print('>%s %.3f (%.3f)' % (names[i], mean(scores), std(scores)))

在运行结束时,我们可以使用分数为每个算法创建一个方框和触须图。

并排创建图可以比较平均得分的分布,也可以比较第 25 和第 75 个百分点之间的中间 50%的分布。

...
# plot the results
pyplot.boxplot(results, labels=names, showmeans=True)
pyplot.show()

将这些联系在一起,下面列出了使用测试工具对漏油数据集上的三个概率模型进行比较的完整示例。

# compare probabilistic model on the oil spill dataset
from numpy import mean
from numpy import std
from pandas import read_csv
from matplotlib import pyplot
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import RepeatedStratifiedKFold
from sklearn.metrics import make_scorer
from sklearn.linear_model import LogisticRegression
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.naive_bayes import GaussianNB
from imblearn.metrics import geometric_mean_score
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

# load the dataset
def load_dataset(full_path):
	# load the dataset as a numpy array
	data = read_csv(full_path, header=None)
	# drop unused columns
	data.drop(22, axis=1, inplace=True)
	data.drop(0, axis=1, inplace=True)
	# retrieve numpy array
	data = data.values
	# split into input and output elements
	X, y = data[:, :-1], data[:, -1]
	# label encode the target variable to have the classes 0 and 1
	y = LabelEncoder().fit_transform(y)
	return X, y

# evaluate a model
def evaluate_model(X, y, model):
	# define evaluation procedure
	cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
	# define the model evaluation metric
	metric = make_scorer(geometric_mean_score)
	# evaluate model
	scores = cross_val_score(model, X, y, scoring=metric, cv=cv, n_jobs=-1)
	return scores

# define the location of the dataset
full_path = 'oil-spill.csv'
# load the dataset
X, y = load_dataset(full_path)
# define models
models, names, results = list(), list(), list()
# LR
models.append(Pipeline(steps=[('t', StandardScaler()),('m',LogisticRegression(solver='liblinear'))]))
names.append('LR')
# LDA
models.append(Pipeline(steps=[('t', StandardScaler()),('m',LinearDiscriminantAnalysis())]))
names.append('LDA')
# NB
models.append(GaussianNB())
names.append('NB')
# evaluate each model
for i in range(len(models)):
	# evaluate the model and store results
	scores = evaluate_model(X, y, models[i])
	results.append(scores)
	# summarize and store
	print('>%s %.3f (%.3f)' % (names[i], mean(scores), std(scores)))
# plot the results
pyplot.boxplot(results, labels=names, showmeans=True)
pyplot.show()

运行该示例评估数据集上的每个概率模型。

:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。

您可能会从 LDA 算法中看到一些警告,例如“变量共线”。目前可以安全地忽略这些,但是建议该算法可以受益于特征选择来移除一些变量。

在这种情况下,我们可以看到每个算法都有技巧,实现了 0.5 以上的平均 G 均值。结果表明,线性判别分析可能是测试模型中表现最好的。

>LR 0.621 (0.261)
>LDA 0.741 (0.220)
>NB 0.721 (0.197)

G 均值分数的分布通过每个算法的方框图和触须图进行总结。我们可以看到,LDA 和 NB 的分布都是紧凑而巧妙的,LR 在运行过程中可能会有一些结果,其中该方法表现不佳,从而压低了分布。

这突出表明,选择模型时不仅要考虑平均表现,还要考虑模型的一致性。

Box and Whisker Plot of Probabilistic Models on the Imbalanced Oil Spill Dataset

不平衡漏油数据集上概率模型的盒须图

我们有一个好的开始,但我们可以做得更好。

评估平衡逻辑回归

逻辑回归算法支持将分类错误的重要性调整为与类别权重成反比的修改。

这允许模型更好地学习有利于少数类的类边界,这可能有助于整体 G 均值表现。我们可以通过将物流配送的“类权重参数设置为“平衡来实现这一点。

...
LogisticRegression(solver='liblinear', class_weight='balanced')

如上所述,逻辑回归对输入变量的规模很敏感,在标准化或标准化输入下表现更好;因此,对于给定的数据集,测试这两者是一个好主意。此外,可以使用功率分布来分散每个输入变量的分布,并使那些具有类似高斯分布的变量更具高斯性。这有利于像逻辑回归这样对输入变量的分布做出假设的模型。

power transom 将使用支持正输入和负输入的 Yeo-Johnson 方法,但我们也将在转换前对数据进行标准化。此外,用于转换的电力变压器类也将在转换后标准化每个变量。

我们将使用三种不同的数据准备方案,特别是归一化、标准化和幂变换,将具有平衡类权重的逻辑推理与相同算法进行比较。

...
# define models
models, names, results = list(), list(), list()
# LR Balanced
models.append(LogisticRegression(solver='liblinear', class_weight='balanced'))
names.append('Balanced')
# LR Balanced + Normalization
models.append(Pipeline(steps=[('t', MinMaxScaler()),('m', LogisticRegression(solver='liblinear', class_weight='balanced'))]))
names.append('Balanced-Norm')
# LR Balanced + Standardization
models.append(Pipeline(steps=[('t', StandardScaler()),('m', LogisticRegression(solver='liblinear', class_weight='balanced'))]))
names.append('Balanced-Std')
# LR Balanced  + Power
models.append(Pipeline(steps=[('t1', MinMaxScaler()), ('t2', PowerTransformer()),('m', LogisticRegression(solver='liblinear', class_weight='balanced'))]))
names.append('Balanced-Power')

将这些联系在一起,下面列出了平衡逻辑回归与不同数据准备方案的比较。

# compare balanced logistic regression on the oil spill dataset
from numpy import mean
from numpy import std
from pandas import read_csv
from matplotlib import pyplot
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import RepeatedStratifiedKFold
from sklearn.metrics import make_scorer
from sklearn.linear_model import LogisticRegression
from imblearn.metrics import geometric_mean_score
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import MinMaxScaler
from sklearn.preprocessing import PowerTransformer

# load the dataset
def load_dataset(full_path):
	# load the dataset as a numpy array
	data = read_csv(full_path, header=None)
	# drop unused columns
	data.drop(22, axis=1, inplace=True)
	data.drop(0, axis=1, inplace=True)
	# retrieve numpy array
	data = data.values
	# split into input and output elements
	X, y = data[:, :-1], data[:, -1]
	# label encode the target variable to have the classes 0 and 1
	y = LabelEncoder().fit_transform(y)
	return X, y

# evaluate a model
def evaluate_model(X, y, model):
	# define evaluation procedure
	cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
	# define the model evaluation metric
	metric = make_scorer(geometric_mean_score)
	# evaluate model
	scores = cross_val_score(model, X, y, scoring=metric, cv=cv, n_jobs=-1)
	return scores

# define the location of the dataset
full_path = 'oil-spill.csv'
# load the dataset
X, y = load_dataset(full_path)
# define models
models, names, results = list(), list(), list()
# LR Balanced
models.append(LogisticRegression(solver='liblinear', class_weight='balanced'))
names.append('Balanced')
# LR Balanced + Normalization
models.append(Pipeline(steps=[('t', MinMaxScaler()),('m', LogisticRegression(solver='liblinear', class_weight='balanced'))]))
names.append('Balanced-Norm')
# LR Balanced + Standardization
models.append(Pipeline(steps=[('t', StandardScaler()),('m', LogisticRegression(solver='liblinear', class_weight='balanced'))]))
names.append('Balanced-Std')
# LR Balanced  + Power
models.append(Pipeline(steps=[('t1', MinMaxScaler()), ('t2', PowerTransformer()),('m', LogisticRegression(solver='liblinear', class_weight='balanced'))]))
names.append('Balanced-Power')
# evaluate each model
for i in range(len(models)):
	# evaluate the model and store results
	scores = evaluate_model(X, y, models[i])
	results.append(scores)
	# summarize and store
	print('>%s %.3f (%.3f)' % (names[i], mean(scores), std(scores)))
# plot the results
pyplot.boxplot(results, labels=names, showmeans=True)
pyplot.show()

运行该示例评估数据集上平衡逻辑回归模型的每个版本。

:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。

你可能会看到第一个平衡 LR 模型的一些警告,比如“ Liblinear 未能收敛”。这些警告目前可以安全地忽略,但是建议该算法可以受益于特征选择来移除一些变量。

在这种情况下,我们可以看到逻辑回归的平衡版本比前面部分评估的所有概率模型表现得更好。

结果表明,也许使用平衡的 LR 和数据归一化进行预处理在这个数据集上表现最好,平均 G 均值得分约为 0.852。这在 1998 年论文报告的结果的范围内或更好。

>Balanced 0.846 (0.142)
>Balanced-Norm 0.852 (0.119)
>Balanced-Std 0.843 (0.124)
>Balanced-Power 0.847 (0.130)

为每种算法创建一个带有方框图和须图的图形,允许比较结果的分布。

我们可以看到,平衡 LR 的分布总体上比前一节中的非平衡版本更紧密。我们还可以看到标准化版本的中值结果(橙色线)高于平均值,高于 0.9,这令人印象深刻。与中间值不同的平均值表明结果的分布是偏斜的,将平均值拉低会有一些不好的结果。

Box and Whisker Plot of Balanced Logistic Regression Models on the Imbalanced Oil Spill Dataset

不平衡漏油数据集上平衡逻辑回归模型的盒须图

我们现在用很少的工作就取得了优异的表现;让我们看看能否更进一步。

用概率模型评估数据采样

数据采样提供了一种在拟合模型之前更好地准备不平衡训练数据集的方法。

也许最流行的数据采样是 SMOTE 过采样技术,用于为少数民族创建新的合成示例。这可以与编辑的最近邻 (ENN)算法配对,该算法将从数据集中定位和移除不明确的示例,使模型更容易学会区分这两个类别。

这种组合被称为 SMOTE-ENN,可以使用不平衡学习库中的 SMOTEENN 类来实现;例如:

...
# define SMOTE-ENN data sampling method
e = SMOTEENN(enn=EditedNearestNeighbours(sampling_strategy='majority')))

当输入数据被预先缩放时,SMOTE 和 ENN 都工作得更好。这是因为这两种技术都涉及在内部使用最近邻算法,并且该算法对不同尺度的输入变量敏感。因此,我们将要求首先对数据进行标准化,然后进行采样,然后将其用作(不平衡的)逻辑回归模型的输入。

因此,我们可以使用不平衡学习库提供的管道类来创建一系列数据转换,包括数据采样方法,并以逻辑回归模型结束。

我们将比较数据采样的逻辑回归模型的四种变体,具体来说:

  • sm teen+lr
  • 归一化+SMOTENN+LR
  • 标准化+SMOTENN+LR
  • 归一化+幂+ SMOTEENN + LR

期望 LR 在 smooteen 上表现更好,而 smooteen 在标准化或规范化上表现更好。最后一个案例做了很多工作,首先标准化数据集,然后应用功率变换,标准化结果(回想一下,默认情况下,功率变换器类将标准化输出),应用 SMOTEENN,然后最终拟合逻辑回归模型。

这些组合可以定义如下:

...
# SMOTEENN
models.append(Pipeline(steps=[('e', SMOTEENN(enn=EditedNearestNeighbours(sampling_strategy='majority'))), ('m', LogisticRegression(solver='liblinear'))]))
names.append('LR')
# SMOTEENN + Norm
models.append(Pipeline(steps=[('t', MinMaxScaler()), ('e', SMOTEENN(enn=EditedNearestNeighbours(sampling_strategy='majority'))), ('m', LogisticRegression(solver='liblinear'))]))
names.append('Norm')
# SMOTEENN + Std
models.append(Pipeline(steps=[('t', StandardScaler()), ('e', SMOTEENN(enn=EditedNearestNeighbours(sampling_strategy='majority'))), ('m', LogisticRegression(solver='liblinear'))]))
names.append('Std')
# SMOTEENN + Power
models.append(Pipeline(steps=[('t1', MinMaxScaler()), ('t2', PowerTransformer()), ('e', SMOTEENN(enn=EditedNearestNeighbours(sampling_strategy='majority'))), ('m', LogisticRegression(solver='liblinear'))]))
names.append('Power')

将这些联系在一起,完整的示例如下所示。

# compare data sampling with logistic regression on the oil spill dataset
from numpy import mean
from numpy import std
from pandas import read_csv
from matplotlib import pyplot
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import RepeatedStratifiedKFold
from sklearn.metrics import make_scorer
from sklearn.linear_model import LogisticRegression
from imblearn.metrics import geometric_mean_score
from sklearn.preprocessing import PowerTransformer
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import MinMaxScaler
from imblearn.pipeline import Pipeline
from imblearn.combine import SMOTEENN
from imblearn.under_sampling import EditedNearestNeighbours

# load the dataset
def load_dataset(full_path):
	# load the dataset as a numpy array
	data = read_csv(full_path, header=None)
	# drop unused columns
	data.drop(22, axis=1, inplace=True)
	data.drop(0, axis=1, inplace=True)
	# retrieve numpy array
	data = data.values
	# split into input and output elements
	X, y = data[:, :-1], data[:, -1]
	# label encode the target variable to have the classes 0 and 1
	y = LabelEncoder().fit_transform(y)
	return X, y

# evaluate a model
def evaluate_model(X, y, model):
	# define evaluation procedure
	cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
	# define the model evaluation metric
	metric = make_scorer(geometric_mean_score)
	# evaluate model
	scores = cross_val_score(model, X, y, scoring=metric, cv=cv, n_jobs=-1)
	return scores

# define the location of the dataset
full_path = 'oil-spill.csv'
# load the dataset
X, y = load_dataset(full_path)
# define models
models, names, results = list(), list(), list()
# SMOTEENN
models.append(Pipeline(steps=[('e', SMOTEENN(enn=EditedNearestNeighbours(sampling_strategy='majority'))), ('m', LogisticRegression(solver='liblinear'))]))
names.append('LR')
# SMOTEENN + Norm
models.append(Pipeline(steps=[('t', MinMaxScaler()), ('e', SMOTEENN(enn=EditedNearestNeighbours(sampling_strategy='majority'))), ('m', LogisticRegression(solver='liblinear'))]))
names.append('Norm')
# SMOTEENN + Std
models.append(Pipeline(steps=[('t', StandardScaler()), ('e', SMOTEENN(enn=EditedNearestNeighbours(sampling_strategy='majority'))), ('m', LogisticRegression(solver='liblinear'))]))
names.append('Std')
# SMOTEENN + Power
models.append(Pipeline(steps=[('t1', MinMaxScaler()), ('t2', PowerTransformer()), ('e', SMOTEENN(enn=EditedNearestNeighbours(sampling_strategy='majority'))), ('m', LogisticRegression(solver='liblinear'))]))
names.append('Power')
# evaluate each model
for i in range(len(models)):
	# evaluate the model and store results
	scores = evaluate_model(X, y, models[i])
	# summarize and store
	print('>%s %.3f (%.3f)' % (names[i], mean(scores), std(scores)))
	results.append(scores)
# plot the results
pyplot.boxplot(results, labels=names, showmeans=True)
pyplot.show()

运行该示例使用数据集上的逻辑回归模型评估每个版本的 SMOTEENN。

:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。

在这种情况下,我们可以看到 SMOTEENN 的加入提高了默认 LR 算法的表现,实现了 0.852 的平均 G 均值,而在第一组实验结果中看到的是 0.621。这甚至比没有任何数据缩放的平衡 LR(上一节)更好,后者的 G 均值约为 0.846。

结果表明,标准化、幂变换和标准化的最终组合可能比 SMOTEENN 的默认 LR 得分略高,G 均值约为 0.873,尽管警告消息暗示了一些需要解决的问题。

>LR 0.852 (0.105)
>Norm 0.838 (0.130)
>Std 0.849 (0.113)
>Power 0.873 (0.118)

结果的分布可以与箱线图和须线图进行比较。我们可以看到这些分布都有大致相同的紧密分布,结果平均值的差异可以用来选择模型。

Box and Whisker Plot of Logistic Regression Models with Data Sampling on the Imbalanced Oil Spill Dataset

不平衡漏油数据集上数据采样的 Logistic 回归模型的盒须图

对新数据进行预测

在没有任何数据缩放的情况下,直接将 SMOTEENN 与逻辑回归结合使用,可能会提供最简单且表现良好的模型,可用于未来。

在我们的测试线束上,该模型的平均重力平均值约为 0.852。

我们将把它作为我们的最终模型,并利用它对新数据进行预测。

首先,我们可以将模型定义为管道。

...
# define the model
smoteenn = SMOTEENN(enn=EditedNearestNeighbours(sampling_strategy='majority'))
model = LogisticRegression(solver='liblinear')
pipeline = Pipeline(steps=[('e', smoteenn), ('m', model)])

一旦定义好了,我们就可以在整个训练数据集中使用它。

...
# fit the model
pipeline.fit(X, y)

一旦适合,我们可以通过调用 predict() 函数来使用它对新数据进行预测。这将返回分类标签 0,表示没有漏油,或 1 表示漏油。

例如:

...
# define a row of data
row = [...]
# make prediction
yhat = pipeline.predict([row])
# get the label
label = yhat[0]

为了证明这一点,我们可以使用拟合模型对一些我们知道没有漏油的情况和一些我们知道有漏油的情况下的标签进行一些预测。

下面列出了完整的示例。

# fit a model and make predictions for the on the oil spill dataset
from pandas import read_csv
from sklearn.preprocessing import LabelEncoder
from sklearn.linear_model import LogisticRegression
from imblearn.pipeline import Pipeline
from imblearn.combine import SMOTEENN
from imblearn.under_sampling import EditedNearestNeighbours

# load the dataset
def load_dataset(full_path):
	# load the dataset as a numpy array
	data = read_csv(full_path, header=None)
	# retrieve numpy array
	data = data.values
	# split into input and output elements
	X, y = data[:, 1:-1], data[:, -1]
	# label encode the target variable to have the classes 0 and 1
	y = LabelEncoder().fit_transform(y)
	return X, y

# define the location of the dataset
full_path = 'oil-spill.csv'
# load the dataset
X, y = load_dataset(full_path)
# define the model
smoteenn = SMOTEENN(enn=EditedNearestNeighbours(sampling_strategy='majority'))
model = LogisticRegression(solver='liblinear')
pipeline = Pipeline(steps=[('e', smoteenn), ('m', model)])
# fit the model
pipeline.fit(X, y)
# evaluate on some non-spill cases (known class 0)
print('Non-Spill Cases:')
data = [[329,1627.54,1409.43,51,822500,35,6.1,4610,0.17,178.4,0.2,0.24,0.39,0.12,0.27,138.32,34.81,2.02,0.14,0.19,75.26,0,0.47,351.67,0.18,9.24,0.38,2.57,-2.96,-0.28,1.93,0,1.93,34,1710,0,25.84,78,55,1460.31,710.63,451.78,150.85,3.23,0,4530.75,66.25,7.85],
	[3234,1091.56,1357.96,32,8085000,40.08,8.98,25450,0.22,317.7,0.18,0.2,0.49,0.09,0.41,114.69,41.87,2.31,0.15,0.18,75.26,0,0.53,351.67,0.18,9.24,0.24,3.56,-3.09,-0.31,2.17,0,2.17,281,14490,0,80.11,78,55,4287.77,3095.56,1937.42,773.69,2.21,0,4927.51,66.15,7.24],
	[2339,1537.68,1633.02,45,5847500,38.13,9.29,22110,0.24,264.5,0.21,0.26,0.79,0.08,0.71,89.49,32.23,2.2,0.17,0.22,75.26,0,0.51,351.67,0.18,9.24,0.27,4.21,-2.84,-0.29,2.16,0,2.16,228,12150,0,83.6,78,55,3959.8,2404.16,1530.38,659.67,2.59,0,4732.04,66.34,7.67]]
for row in data:
	# make prediction
	yhat = pipeline.predict([row])
	# get the label
	label = yhat[0]
	# summarize
	print('>Predicted=%d (expected 0)' % (label))
# evaluate on some spill cases (known class 1)
print('Spill Cases:')
data = [[2971,1020.91,630.8,59,7427500,32.76,10.48,17380,0.32,427.4,0.22,0.29,0.5,0.08,0.42,149.87,50.99,1.89,0.14,0.18,75.26,0,0.44,351.67,0.18,9.24,2.5,10.63,-3.07,-0.28,2.18,0,2.18,164,8730,0,40.67,78,55,5650.88,1749.29,1245.07,348.7,4.54,0,25579.34,65.78,7.41],
	[3155,1118.08,469.39,11,7887500,30.41,7.99,15880,0.26,496.7,0.2,0.26,0.69,0.11,0.58,118.11,43.96,1.76,0.15,0.18,75.26,0,0.4,351.67,0.18,9.24,0.78,8.68,-3.19,-0.33,2.19,0,2.19,150,8100,0,31.97,78,55,3471.31,3059.41,2043.9,477.23,1.7,0,28172.07,65.72,7.58],
	[115,1449.85,608.43,88,287500,40.42,7.34,3340,0.18,86.1,0.21,0.32,0.5,0.17,0.34,71.2,16.73,1.82,0.19,0.29,87.65,0,0.46,132.78,-0.01,3.78,0.7,4.79,-3.36,-0.23,1.95,0,1.95,29,1530,0.01,38.8,89,69,1400,250,150,45.13,9.33,1,31692.84,65.81,7.84]]
for row in data:
	# make prediction
	yhat = pipeline.predict([row])
	# get the label
	label = yhat[0]
	# summarize
	print('>Predicted=%d (expected 1)' % (label))

运行该示例首先在整个训练数据集上拟合模型。

然后,从数据集文件中选择拟合模型,用于预测我们知道没有漏油的情况下的漏油标签。我们可以看到所有的情况都是正确预测的。

然后将实际漏油的一些情况用作模型的输入,并对标签进行预测。正如我们所希望的,正确的标签再次被预测。

Non-Spill Cases:
>Predicted=0 (expected 0)
>Predicted=0 (expected 0)
>Predicted=0 (expected 0)
Spill Cases:
>Predicted=1 (expected 1)
>Predicted=1 (expected 1)
>Predicted=1 (expected 1)

进一步阅读

如果您想更深入地了解这个主题,本节将提供更多资源。

报纸

蜜蜂

文章

摘要

在本教程中,您发现了如何开发一个模型来预测卫星图像中是否存在漏油,并使用 G 均值指标对其进行评估。

具体来说,您了解到:

  • 如何加载和探索数据集,并为数据准备和模型选择产生想法。
  • 如何评估一套概率模型,并通过适当的数据准备提高它们的表现。
  • 如何拟合最终模型并使用它来预测特定情况下的类标签。

你有什么问题吗? 在下面的评论中提问,我会尽力回答。