预测建模的目标是创建能够在新数据上进行良好预测的模型。
我们在训练时无法访问这些新数据,因此我们必须使用统计方法来估算模型在新数据上的性能。
这类方法称为重采样方法,因为它们会重采样您可用的训练数据。
在本教程中,您将学习如何在Python中从头开始实现重采样方法。
完成本教程后,您将了解:
- 如何实现数据的训练集和测试集拆分。
- 如何实现数据的k折交叉验证拆分。
用我的新书《从零开始的机器学习算法》启动您的项目,其中包括分步教程和所有示例的Python源代码文件。
让我们开始吧。
- 更新于 2017 年 1 月:更改了 `cross_validation_split()` 中 `fold_size` 的计算,使其始终为整数。修复了 Python 3 的问题。
- 更新于2018年5月:修正了LOOCV的拼写错误。
- 2018 年 8 月更新:测试并更新以与 Python 3.6 配合使用。

如何在Python中从零开始实现重抽样方法
照片来自Andrew Lynch,保留部分权利。
描述
重采样方法的目的是充分利用您的训练数据,以便准确估算模型在新数据上的性能。
然后,可以使用准确的性能估算来帮助您选择要使用的模型参数集或要选择的模型。
一旦选择了模型,您就可以在整个训练数据集上训练最终模型,并开始使用它进行预测。
您可以使用两种常见的重采样方法:
- 数据的训练集和测试集拆分。
- k折交叉验证。
在本教程中,我们将探讨每种方法的使用以及何时使用一种方法而不是另一种。
教程
本教程分为3个部分:
- 训练集和测试集拆分。
- k折交叉验证拆分。
- 如何选择重采样方法。
这些步骤将为您提供处理数据重采样以估算算法在新数据上的性能所需的基础。
1. 训练集和测试集拆分
训练集和测试集拆分是最简单的重采样方法。
因此,它是最常用的。
训练集和测试集拆分涉及将数据集分成两部分:
- 训练数据集。
- 测试数据集。
训练数据集用于机器学习算法训练模型。测试数据集被保留下来,用于评估模型的性能。
分配给每个数据集的行是随机选择的。这是为了确保模型训练和评估的客观性。
如果比较多个算法或比较同一算法的多个配置,则应使用相同的数据集训练集和测试集拆分。这是为了确保性能比较的一致性或“苹果对苹果”的比喻。
我们可以通过在拆分数据之前以相同方式设置随机数生成器的种子,或者保留同一数据集的拆分供多个算法使用来实现。
我们可以在一个函数中实现数据集的训练集和测试集拆分。
下面是一个名为train_test_split()的函数,用于将数据集拆分为训练集和测试集。它接受两个参数:要拆分的数据集(列表的列表)和一个可选的拆分百分比。
默认的拆分百分比为0.6或60%。这将把60%的数据集分配给训练数据集,剩余的40%留给测试数据集。60/40的训练/测试拆分是一个很好的默认数据拆分。
该函数首先计算从提供的数据集中需要多少行用于训练集。创建原始数据集的副本。随机选择并从副本中移除行,然后将它们添加到训练集中,直到训练集包含目标行数。
副本中剩余的行随后作为测试数据集返回。
random模块中的randrange()函数用于生成介于0和列表大小之间的随机整数。
1 2 3 4 5 6 7 8 9 10 11 |
from random import randrange # 将数据集拆分为训练集和测试集 def train_test_split(dataset, split=0.60): train = list() train_size = split * len(dataset) dataset_copy = list(dataset) while len(train) < train_size: index = randrange(len(dataset_copy)) train.append(dataset_copy.pop(index)) return train, dataset_copy |
我们可以使用一个包含10行、每行一列的示例数据集来测试此函数。
完整的示例如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
from random import seed from random import randrange # 将数据集拆分为训练集和测试集 def train_test_split(dataset, split=0.60): train = list() train_size = split * len(dataset) dataset_copy = list(dataset) while len(train) < train_size: index = randrange(len(dataset_copy)) train.append(dataset_copy.pop(index)) return train, dataset_copy # 测试训练/测试拆分 seed(1) dataset = [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10]] train, test = train_test_split(dataset) print(train) print(test) |
该示例在拆分训练数据集之前固定了随机种子。这是为了确保每次执行代码时都能进行相同的数据拆分。如果我们希望多次使用相同的拆分来评估和比较不同算法的性能,这将非常有用。
运行该示例将产生以下输出。
打印了训练集和测试集中的数据,显示6/10或60%的记录被分配给训练数据集,4/10或40%的记录被分配给测试集。
1 2 |
[[3], [2], [7], [1], [8], [9]] [[4], [5], [6], [10]] |
2. k折交叉验证拆分
使用训练集和测试集拆分方法的局限性在于,它会得到有噪声的算法性能估算。
k折交叉验证方法(也称为交叉验证)是一种重采样方法,可以提供更准确的算法性能估算。
它首先将数据分成k个组。然后,算法被训练和评估k次,并通过取平均性能得分来总结性能。每个数据组被称为一个折(fold),因此得名为k折交叉验证。
它的工作原理是首先在k-1组数据上训练算法,并在第k个保留组上进行评估,作为测试集。重复此过程,以便每个k组都有机会被保留并用作测试集。
因此,k的值应能被训练数据集中的行数整除,以确保每个k组具有相同的行数。
您应该选择一个k值,将数据分成具有足够行数的组,以便每组仍然能代表原始数据集。一个好的默认值是小数据集k=3,大数据集k=10。检查折大小是否具有代表性的快速方法是计算均值和标准差等汇总统计量,并查看这些值与整个数据集的相同统计量之间存在多大差异。
我们可以重用上一节中关于创建训练集和测试集拆分的知识,在这里实现k折交叉验证。
我们不需要两个组,而是需要返回k个折或k个数据组。
下面是一个名为cross_validation_split()的函数,它实现了数据的交叉验证拆分。
与之前一样,我们创建数据集的副本,从中随机抽取行。
我们计算每个折的大小,即数据集大小除以所需的折数。
1 |
折大小 = 总行数 / 总折数 |
如果数据集不能被折数整除,可能会有一些余数行,它们将不会在拆分中使用。
然后,我们创建一个具有所需大小的行列表,并将其添加到折列表中,最后返回该列表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
from random import randrange # 将数据集分成 k 折 def cross_validation_split(dataset, folds=3): dataset_split = list() dataset_copy = list(dataset) fold_size = int(len(dataset) / folds) for i in range(folds): fold = list() while len(fold) < fold_size: index = randrange(len(dataset_copy)) fold.append(dataset_copy.pop(index)) dataset_split.append(fold) return dataset_split |
我们可以使用与上面相同的简陋数据集来测试这种重采样方法。每一行只有一个列值,但我们可以想象一下这如何扩展到标准的机器学习数据集中。
完整的示例如下所示。
和以前一样,我们固定了随机数生成器的种子,以确保每次执行代码时,相同的行都使用相同的折。
为了演示目的,使用了k值为4。我们预计10行数据分成4折,每折2行,余下2行不用于拆分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
from random import seed from random import randrange # 将数据集分成 k 折 def cross_validation_split(dataset, folds=3): dataset_split = list() dataset_copy = list(dataset) fold_size = int(len(dataset) / folds) for i in range(folds): fold = list() while len(fold) < fold_size: index = randrange(len(dataset_copy)) fold.append(dataset_copy.pop(index)) dataset_split.append(fold) return dataset_split # 测试交叉验证拆分 seed(1) dataset = [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10]] folds = cross_validation_split(dataset, 4) print(folds) |
运行该示例将产生以下输出。打印了折列表,显示正如预期的那样,每折确实有两行。
1 |
[[[3], [2]], [[7], [1]], [[8], [9]], [[10], [6]]] |
3. 如何选择重采样方法
估算机器学习算法在新数据上性能的黄金标准是k折交叉验证。
当配置得当,与训练集和测试集拆分等其他方法相比,k折交叉验证可以提供稳健的性能估算。
交叉验证的缺点是它可能运行耗时,需要训练和评估k个不同的模型。如果您的数据集非常大,或者您正在评估一个训练耗时较长的模型,这会是一个问题。
训练集和测试集拆分重采样方法是最常用的。这是因为它易于理解和实现,并且因为它提供了快速的算法性能估算。
只构建和评估一个模型。
尽管训练集和测试集拆分方法可能对模型在新数据上的性能提供有噪声或不可靠的估算,但如果您的数据集非常大,这个问题就会减小。
大数据集是指包含数十万甚至数百万条记录的数据集,这些数据集足够大,以至于将其分成两半时,两个数据集具有几乎相同的统计特性。
在这种情况下,可能无需使用k折交叉验证进行算法评估,而训练集和测试集拆分可能同样可靠。
扩展
在本教程中,我们已经了解了两种最常见的重采样方法。
您可能希望作为本教程的扩展来研究和实现其他方法。
例如
- 重复的训练集和测试集拆分。这是使用训练集和测试集拆分,但该过程会重复多次。
- LOOCV或留一法交叉验证。这是k折交叉验证的一种形式,其中k的值固定为n(训练样本的数量)。
- 分层。在分类问题中,这是强制每个组的类别值平衡与原始数据集匹配。
您是否实现了扩展?
在下面的评论中分享您的经验。
回顾
在本教程中,您学习了如何在Python中从头开始实现重采样方法。
具体来说,你学到了:
- 如何实现训练集和测试集拆分方法。
- 如何实现k折交叉验证方法。
- 何时使用每种方法。
您对重采样方法或本文有任何疑问吗?
在评论中提出您的问题,我将尽力回答。
在使用分层k折交叉验证时,为什么shuffle参数设置为True?
你好Alan,
来自API文档
你好,我在运行cross_validation_split函数时收到此错误。我使用的是Python 3.4。
line 26, in cross_validation_split
index = randrange(len(dataset_copy))
File “C:\Python34\lib\random.py”, line 186, in randrange
raise ValueError(“empty range for randrange()”)
ValueError: randrange() 的范围为空
我在本地运行相同的代码示例,并收到此错误。如果我在函数外部使用randrange()和len(dataset),则可以正常工作。任何帮助都将非常感激!
我解决了。我需要fold_size = len(dataset) / folds使用双斜杠//将其转换为整数。应该是:fold_size = len(dataset) // folds
我遇到了同样的问题,即使我尝试了双斜杠。有没有什么方法可以解决它?
您在使用Python 3吗?
这可能是Python 3的问题,我会对此进行研究。
我也遇到了这个问题。当我添加Isauro提到的内容后,该方法对我来说就可以工作了。我使用的是Python 3.5。
如果有多行,函数将如何改变?我知道我们可以使用sklearn的交叉验证包来处理更大的数据集,但我想为更大的数据集编写交叉验证的逻辑。您或其他人能否展示如何做到这一点?也许以鸢尾花数据集为例。
您说的多行是什么意思?交叉验证需要多行才能进行选择。
1. 重采样数据的最佳方法是什么?是在完整数据集上还是在拆分为训练集和测试集之后?
2. 各种过采样和欠采样技术的平衡比例是多少?如何确定平衡比例。
这取决于您的数据——您必须通过实验来发现什么最有效。
你好,我怎样才能实现一些指标来衡量交叉验证的方法?
你具体指的是什么?
嘿,我如何为朴素贝叶斯分类器使用10折交叉验证?我在你的帖子中看到了朴素贝叶斯分类器。(https://machinelearning.org.cn/naive-bayes-classifier-scratch-python/)
但你在那里使用了普通的训练和测试拆分。
请指导我,因为我对机器学习相当陌生。
抱歉,我唯一用于此的有效示例是在这本书中
https://machinelearning.org.cn/machine-learning-algorithms-from-scratch/
嗨,Jason,
您提到如下:
LOOCV或留一法交叉验证。这是k折交叉验证的一种形式,其中k的值固定为1。
应该是1还是N?
您说得对,LOOCV中的k等于N。
在k折交叉验证方法中,计算折大小的公式是总行数/总折数,这意味着总行数可以被总折数(k)整除。
但在您的文章中,您说“k的值应该能被行数整除”。我有点困惑。是我理解错了还是陈述不正确?
是的,应该反过来:行数应该能被k整除。
你好,
我正尝试为多标签分类问题从头开始实现LOOCV。我想知道我是否做得正确,以及将模型拟合到每次迭代中的数据是否是一个问题。如果模型在除“X”样本之外的所有数据上进行训练,然后下一次迭代在“Y”样本上进行测试,那么它之前是否被拟合到包含“Y”样本的训练集?这有问题吗?希望我说明白了。
如果这里有一个例子,我很乐意购买其中一本书。sklearn库的交叉验证似乎不能用于多标签数据
我很乐意提供帮助,但我没有能力审查/调试您的代码,抱歉。
听到sklearn中的LOOCV不适用于您的情况,我感到很惊讶。它应该与问题类型无关。具体是什么问题?
嘿,你是曼彻斯特大学的吗?)
信息丰富且描述性的内容。感谢作者!
谢谢!