如何使用 Horovod 的大批量模拟来优化(高度)分布式训练的超参数调整
理解大数据
一个简单的技术可以帮你省下一大笔钱,以及如何将它与混合精确学习相结合
受加快学习速度的愿望激励,今天深度学习领域的一个常见做法是将培训活动分配给多个工作人员(例如 GPU)。在数据分布式训练中,每个工人并行地对训练数据的不同子集(本地批次)执行训练步骤,向所有其他工人广播其结果梯度,并基于所有工人计算的梯度更新其模型权重。
在之前的帖子中,我们扩展了数据分布式培训的一些复杂性和潜在挑战。在本帖中,我们将重点关注在数据分布式训练环境中执行超参数调整的挑战。
我要感谢 Dennis Kreinovich 对分布式训练和混合精度的贡献。
介绍
任何深度学习项目必不可少的一部分就是超参数调优。模型超参数是我们在运行训练算法之前修复的所有控制设置。它们不同于在训练期间学习的模型参数。常见超参数的示例包括:
- 优化器设置:优化器的选择和控制它的设置,例如学习率。
- 模型架构:模型层的选择,它们是如何组合在一起的,以及它们是如何配置的。这包括每层的通道数、卷积核的大小、正则化设置、辍学率、批量归一化设置、激活函数的选择等设置。
- 损失函数:损失函数的选择及其各自的配置设置。
不用说,超参数的选择对我们训练算法的成功有决定性的影响。超参数整定 (HPT)指的是为这些超参数寻找最优值的艺术,也就是寻找最终会导致最优训练结果的值。应当注意,对超参数的所有可行组合进行完全搜索实际上是不可能的。在实践中,大多数团队会修正大量的超参数,并将 HPT 限制到训练控制的子集。它们可以通过限制剩余超参数的搜索空间来进一步简化问题。
每一个成功的深度学习项目都包括某种形式的 HPT,即使在名称上没有明确地这样称呼。当你尝试使用额外的卷积层或信道,或者使用不同的学习率、正则化值、退出率或激活层来训练你的模型,只是为了看看它如何影响你的结果的质量时,你正在执行 HPT。当然,你在 HPT 战略上投入的越多,你成功的可能性就越大。以下是影响 HPT 战略优势的一些方面。
1。高级参数搜索算法
给定一个参数搜索空间,有多种方法可以进行搜索。琐碎的算法有随机搜索和网格搜索。在随机搜索中,我们随机选择参数组合。在网格搜索中,我们将搜索空间分成一个网格,并在网格的交叉点上测试组合。然而,有许多基于贝叶斯优化的更高级的算法,已经被证明比随机搜索和网格搜索执行得好得多(例如,参见这里的)。在撰写本文时,深度学习的 HPT 搜索算法仍然是一个活跃的研究领域。
2.实验并行化
显然,并行运行实验将加速 HPT 阶段,使您能够更快地得出关于最佳超参数的结论。然而,由于并行运行 N 个实验,潜在的好处可能比线性(乘以 N)加速要大得多。这是因为并行化增加了实验提前终止的可能性。让我们通过一个简单的例子来证明这一点。假设我们将 HPT 算法定义如下:
- 每个实验最多训练 8 个时期(或固定步数),并在每个时期后评估准确度。
- 在每个时期之后,我们将每个实验的准确度与达到相同步骤的所有其他实验的准确度结果进行比较。任何精度比最高计算精度差 10%以上的实验都将被终止。当一个实验提前停止时,它的相关资源可以用于后续的实验。
- 如果一个实验是唯一剩下的实验,或者如果它以最高的准确度完成了 8 个时期,则该实验被宣布为获胜者。
现在,假设在我们的 HPT 中的给定阶段,我们正在运行 4 个实验,这些实验将产生下表所示的精度:
样本精确度
如果我们并行运行所有四个试验,我们将在 1 个时期后终止试验 3,在 2 个时期后终止试验 2,在 4 个时期后终止试验 1 和试验 4,此时试验 4 将被宣布为获胜者。给定阶段将总共运行 11 个时期。另一方面,如果我们在实验中选择随机顺序并按顺序运行它们,通过应用简单的概率论定律,我们会发现总时期数的期望值将是 19.6。在这种情况下,并行化带来了 44%的预期节省。虽然这个例子是人为设计的,但是节省这么多钱的可能性并不遥远。节省的潜力只会随着并行化程度的增加而增加。
3.自动化
可以想象,人们可以定义参数搜索空间,选择 HPT 算法,手动实例化所有实验,并监控它们的进展。然而,HPT 算法的手动实现可能缓慢、乏味,并且极易出错。更好的建议是将 HPT 自动化。有许多工具和库支持 HPT 自动化。这些不同之处在于它们为不同的训练环境、搜索算法、并行化、早期停止等提供的支持。最流行的库之一(在撰写本文时)是 Ray Tune ,它支持各种各样的训练框架、前沿搜索算法和实验并行化。我希望在以后的文章中扩展 HPT 框架。
这篇博文的重点是实验并行化,或者更具体地说,是在分布式训练环境中运行并行实验的挑战。我们将在下一节介绍这一挑战。
挑战——分布式培训环境中的 HPT
一个强有力的 HPT 战略对于一般培训同样重要,对于分布式培训更是如此。这是由于以下两个因素:
- 调优的复杂性:数据分布式训练的净效应是训练批量的显著增加。用 k 表示工人数量,用 b 表示本地批量,对 k 工人进行分布式训练的结果是,在每一个训练步骤中,模型在一个全局批量 kb* 样本上进行训练。正如我们在上一篇文章中所讨论的,随着批量的增加,HPT 会变得更加困难。可能需要更高级的优化器(如 LAMB 和 LARS ),以及更精细的优化器设置调整。此外,最佳超参数可能高度依赖于全局批次的大小。大小为 X 的全局批次的调整结果可能与大小为 y 的全局批次的调整结果非常不同。
- 调整成本:在高度分散的环境中,培训的成本可能相当高。在单个 GPU 设置中可以容忍的 HPT 策略(例如使用原始搜索算法)的低效率在高成本的分布式设置中是不可原谅的。我们在这里和本文的其余部分提到的成本可以是使用基于云的培训服务的价格,也可以是过度使用公司内部培训资源的机会成本。
正如我们在上面看到的,强 HPT 策略的技术之一是并行化调优实验。并行化既可以加快整个过程,又可以通过提前终止实验来节省成本。在多员工环境中,这种成本节约非常显著。然而,当尝试并行化高度分布式的训练实验时,您可能会发现自己面临一个重大挑战,我们将通过下面的简单示例来演示这个挑战:
循环实验
您可以考虑的一个替代实验并行化的选项是以循环方式一次一个时期地执行每个实验。例如,在上面的例子中,我们将在 256 个 GPU 上运行第一个实验一个时期,然后第二个,然后第三个,直到第八个实验。此时,我们将比较中间结果,决定终止哪些实验,然后继续运行第二个时期的剩余实验,然后是第三个时期,依此类推。虽然这种方法花费的时间是完全并行化的 8 倍,但它能够实现相同类型的实验提前终止,并导致运行相同总数的时期。这种方法的问题是,实验之间的上下文切换(包括捕获和保存模型状态以及重建计算图)可能会引入大量开销,尤其是在分布式训练设置中。因此,早期终止实验的能力所带来的总体成本节约将低于并行化的情况。
我们在这篇文章中提出的解决方案是通过使用大批量模拟在更少的工人身上运行平行实验。
大批量模拟
在大批量模拟 (LBS)中,我们仅使用 Y ( < X) 工人来模拟对 X 工人的培训,其中 Y 是 X 的除数,通过在 Y 工人上运行与我们在 X 工人上相同的全局批量。例如,假设在上一节中看到的示例中,本地(每个工作线程)批处理大小为 32,全局批处理大小为 8192 (32x256)。使用 LBS,我们可以在 32 个 GPU(而不是 256 个)上并行运行 8 个 HPT 实验中的每一个,这样每次运行都将使用大小为 256(而不是 32)的本地批处理。全局批处理的结果大小将是 8192,与我们在 256 个 GPU 上运行时的大小相同。这样,当并行运行 8 个实验(每个实验使用 32 个 GPU)时,我们在 HPT 期间将使用与培训期间相同的 256 个 GPU。这里的 8 个实验也比完全并行化花费更多的时间(超过 2048 个 GPU)。然而,提前终止实验所节省的成本将等同于完全并行化所节省的成本。(事实上,由于梯度共享消息数量的减少,它甚至可能会更好一点。)
如果有可能随意增加培训工人的本地批量,实施 BTS 将是微不足道的。然而,在现实中,批量的大小受到培训工作者的可用内存的限制。为了克服这一点,我们将使用一种叫做梯度聚合的技术。
梯度聚合
在典型的训练步骤中,我们执行向前传递以计算本地批次的损失,执行向后传递以计算梯度,将结果传播给其他工人,然后根据所有工人计算的梯度更新模型权重。当我们执行梯度聚合时,我们不是立即共享和应用梯度,而是在预定义数量的步骤中收集它们,然后才广播它们的平均值并更新权重。用 B 表示本地批量大小,在 N 步上执行梯度累加的效果将与用本地批量大小 N x B 进行训练相同。并且在具有规模为 B 的本地批次和具有 Y 的工人的 N 个步骤上运行梯度聚合,将与具有规模为 B 的本地批次和具有X=NXY个工人的培训具有相同的效果。
这种用于模拟大批量的技术依赖于梯度计算的线性,即依赖于批量大小K=NxB的梯度与批量大小NBB的梯度平均值之间的等价性。
总之,我们提出的解决方案是使用 Y 工人模拟一个有 N x Y 工人的培训课程,通过对每个工人的 N 步执行梯度聚合。
使用 Horovod 的大批量模拟
Horovod 是一个流行的库,用于执行分布式培训,广泛支持 TensorFlow、Keras、PyTorch 和 Apache MXNet。Horovod 的工作方式是将梯度共享引入梯度计算步骤。虽然有各种方法来实例化 Horovod,但一种常见的方法是使用distributed optimizerAPI 将您的训练优化器与 Horovod 优化器包装在一起,如下面的 TensorFlow 代码片段所示。更多详情参见 Horovod 文档。
import horovod.tensorflow.keras as hvd
*# Initialize Horovod*
hvd.init()
*# Pin GPU, build model, and configure optimizer*
opt = ...
*# Wrap optimizer with Horovod Distributed Optimizer*
opt = hvd.DistributedOptimizer(opt)
从 0.21 版本开始, DistributedOptimizer API 新增了两个用于编程梯度聚合的标志: backward_passes_per_step 和average _ aggregated _ gradients。当 backward_passes_per_step 设置为大于 1 的值时,Horovod 将在后台使用一个梯度聚合器,该聚合器将在选择的步数上累积梯度,然后共享它们并使用它们将更新应用于模型权重。average _ aggregated _ gradients决定是否在共享累积的梯度之前对其进行平均。在下面的代码块中,我们展示了如何修改 Horovod 设置代码,以便将有效的全局批处理大小增加 8 倍:
import horovod.tensorflow.keras as hvd
*# Initialize Horovod*
hvd.init()
*# Pin GPU, build model, and configure optimizer*
opt = ...
*# Wrap optimizer with Horovod Distributed Optimizer*
opt = hvd.DistributedOptimizer(
opt,
*backward_passes_per_step=8,
average_aggregated_gradients=True*)
小心使用
虽然现代深度学习框架为您可能想要做的几乎所有事情提供了高级 API,但深入了解正在发生的事情总是一个好主意,例如,模型层如何工作,您选择的优化器正在运行什么操作,以及在训练步骤中实际发生了什么。当你试图做一些与众不同的事情时,比如 LBS,尤其如此。否则,你可能会发现你的程序失败了,却不知道为什么。以下是一些需要注意的例子:
定期更新超参数:当使用 Y 工人模拟具有X=NXY的培训环境时,需要注意的是, X 工人环境中的每一步都相当于 Y 工人环境中的 N 步。一些训练算法要求对优化器超参数进行定期更新。例如,通常的做法是使用学习率调度器以固定的迭代步骤调整优化器学习率。为了使模拟正常工作,我们需要通过将更新的预定步骤乘以系数 N 来修改超参数变化的时间表。
对梯度的非线性运算:我们所描述的 LBS 技术依赖于梯度计算的线性,也就是说,依赖于一批大小为 N x B 的梯度与一批大小为NB 的梯度的平均值之间的等价性。一些训练算法可能要求对梯度执行非线性操作。非线性操作的一个例子是梯度裁剪。只要这种非线性仅在完全累积的梯度上执行(即,仅在应用梯度时在台阶上执行),就不会有问题。请注意,TensorFlow 2.5 中的默认行为是对聚集的梯度执行裁剪(参见此处的)。
在正向传递过程中对模型参数的更新:我们已经描述的梯度聚集技术通过反向传递每一步步骤来延迟梯度更新对模型权重的应用。然而,除了梯度更新,我们还需要考虑模型参数的其他更新。在正向传递期间发生的模型参数更新的一个例子是在批标准化层中移动统计的重新计算。假设这些统计数据是在主要(0 级)工作人员上计算的(例如,我们不同步跨工作人员的批处理标准化统计数据),我们需要考虑到在模拟环境中,0 级工作人员将看到的数据量将是backward _ passes _ per _ step乘以 0 级工作人员在原始环境中看到的数据量。尽管批处理规范化统计数据往往会很快被整理出来,但是这种差异可能会导致评估指标的差异。
在这篇文章的附录中,我们描述了如何将 LBS 与混合精度训练结合起来,这个用例进一步证明了谨慎的必要性。
在下一节中,我们将在一些简单的实验中演示 LBS 的使用。
实验
以下实验是在 Amazon EC2 G4 实例上运行的。对于单个 GPU 实验,我们使用 g4dn.2xlarge 实例,对于四个 GPU 实验,我们使用 g4dn.12xlarge 实例。实验在 TensorFlow 版本 2.4.1 和 Horovod 版本 0.21.0 上运行。对于所有实验,我们运行了官方的 Horovod tf.keras mnist 示例,并做了以下更改:
- 我们应用了附录中描述的必要更改来支持混合精度的 LBS。
- 对于混合精度测试,我们对模型进行了以下突出显示的调整(参见混合精度文档以了解变化):
**policy = tf.keras.mixed_precision.Policy('mixed_float16')
tf.keras.mixed_precision.set_global_policy(policy)**mnist_model = tf.keras.Sequential([
tf.keras.layers.Conv2D(32, [3, 3], activation='relu'),
tf.keras.layers.Conv2D(64, [3, 3], activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=(2, 2)),
tf.keras.layers.Dropout(0.25),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(128, activation='relu'),
tf.keras.layers.Dropout(0.5),
tf.keras.layers.Dense(10),
tf.keras.layers.Activation('softmax',
**dtype='float32'**,
name='predictions')
])
- 为了运行 LBS,我们将 backward_passes_per_step 设置为 4,并相应地更新了学习率。
在下面的图表中,我们绘制了四个实验的精度与历元数的函数关系图:在有和没有混合精度设置的情况下在 4 个 GPU 上进行分布式训练,以及在单个 GPU 上运行相应的大批量模拟。
每个历元的精度
在所有情况下,我们看到精度遵循类似的收敛模式。虽然这是在一个简单的模型上演示的,但我们可以看到使用混合精度训练和大批量模拟的潜力。
结束语
在本帖中,我们重点讨论了如何使用 LBS 来解决在高度分散的训练环境中进行 HPT 的挑战。LBS 还可以在许多其他场景中发挥作用。这里有一些例子。
批量调整
在整篇文章中,我们假设培训工人的数量和全局批量大小都是固定的,并相应地解决了调整其他超参数的挑战。当然,在现实中,两者都可以被视为需要调整的超参数。我们讨论的 LBS 技术提供了一种调整批量大小和评估工人数量的选择如何影响培训质量的创造性方法。这可以通过在多台机器上运行平行实验来完成,每台机器的向后 _ 走刀 _ 每步设置值不同。
增加单个实例的批量大小
可能存在这样的情况,其中以大批量运行可能不是数据分布式训练的强制副作用,而是用于提高训练性能的期望工具。在这种情况下,可以使用 LBS 来解决内存约束带来的批量限制,并在我们想要的批量上运行训练。
动态 GPU 编排
现代人工智能公司面临的挑战之一是如何在多个团队和项目中最有效地分配培训资源。培训资源非常昂贵,而且有强烈的经济动机来最大限度地利用这些资源。一种常见的策略是根据团队的需求和项目优先级在团队之间动态分配 GPU。这种策略的效果是,在培训的不同阶段,你可以接触到不同数量的工人。
如果您依赖低成本的“现场”实例进行培训,也会出现类似的情况。许多云服务提供商为多余的计算实例提供大幅折扣。在 AWS 中这些被称为 亚马逊 EC2 Spot 实例 ,在 Google Cloud 中它们被称为 可抢占 VM 实例 ,在微软 Azure 中它们被称为 低优先级 VM。代价是,如果对它们的需求增加,这样的实例可以在使用过程中终止。你会再次发现,在培训过程中,分配的员工数量在不断变化。
考虑到资源分配中这种变化的可能性,明智的做法是在设计培训时考虑到这种可能性。具体来说,在任何给定的时间,您都希望利用尽可能多的可用工作人员,但也不希望重新分配其中的一部分。一个策略可能是天真地培训任何数量的可用工人。然而,全球批量将根据工人数量而变化,正如我们已经看到的,在每种情况下成功的培训并不总是简单的调整学习率。基于 LBS 的另一种策略是在各种情况下保持相同的有效批量。下面是这种工作方式的一个例子:
假设您设计您的模型来培训最多 256 名工人,本地批量为 B 。然而,当你开始培训时,你发现只有 128 名工人可用。使用伦敦商学院,你可以开始对 128 名工人进行培训,有效的本地批次规模为 2 x B 。现在假设培训开始一小时后,分配给你的员工数量下降到 128 人以下。同样,使用 LBS,您可以重新设置您对 64 名工人的培训,进行有效的本地批量 4 x B 培训。稍后,资源可能会开始释放,并且可能会重新设置您的培训,以便在 256 名员工上运行。
理论上,我们甚至可以通过使用 Elastic Horovod 来实施这一策略,而无需停止和重新开始训练,这是 Horovod 的一个引人注目的功能,即使在分配的资源数量发生变化的情况下,也可以进行不间断的训练。然而,在撰写本文时,Horovod APIs 不支持对backward _ passes _ per _ step设置的动态更新。想了解更多关于弹性按摩棒好处的信息,请看这篇最近的文章。
摘要
数据分布式培训带来了许多独特的挑战。幸运的是,这些挑战也是创新和创造力的机会。在这篇文章中,我们接受了在高度分散的训练环境中超参数调整的挑战。我们已经展示了如何使用 Horovod 的梯度聚合支持来实现并行实验以及随之而来的潜在成本效益。高度分散的培训可能很昂贵,应该充分探索任何降低成本的机会。
附录:结合大批量模拟和混合精度学习
在混合精度训练中,我们在训练期间混合了 16 位和 32 位浮点类型,而不是仅使用 32 位浮点类型。这个特性可以显著节省运行时间和内存使用。
在这篇文章中,我们将重点关注 TensorFlow 提供的混合精度支持,尽管我们要说的大部分内容也适用于其他框架。关于混合精度的 TensorFlow 指南很好地概述了混合精度如何工作以及如何配置。我们将提到与我们的讨论相关的一些混合精度训练的元素。
- 使用 16 位浮点编程时,主要关注的是由于较低的位精度导致的数值下溢或上溢。实际上,当执行混合精度训练时,最关心的是梯度计算期间下溢的可能性。
- TensorFlow 支持两种 16 位精度浮点类型,float16 和 bfloat16 。bfloat16 类型的指数位数与 float32 一样多,并且能够表示相同范围的数字。因此,bfloat16 型不存在下溢问题。虽然有许多处理器支持 bfloat16,但在撰写本文时,TensorFlow 的正式版本只支持 Google TPUs 上的 bfloat16。查看这篇文章中关于浮点表示如何工作的有趣概述。
- 使用 float16 时,克服下溢可能性的方法是执行损失缩放。当我们使用损失缩放时,我们在计算梯度之前将损失乘以一个缩放因子,然后在将它们应用于模型权重之前将结果梯度除以相同的因子。通过选择适当的比例因子,我们可以降低下溢的可能性。在 TensorFlow 中,该过程在losscale optimizer中实现。执行的步骤有:
1。衡量损失
2。计算梯度
3。取消渐变比例
4。如果所有梯度都是有限的,则将它们应用于权重
5。如果需要,根据当前梯度的统计数据更新比例因子
既然分布式训练和混合精确训练都有加速训练的共同目标,我们自然会对两者的结合感兴趣。特别是,我们需要基于 LBS 的解决方案来执行混合精度的 HPT。遗憾的是,Horovod 中 LBS 的当前默认实现与 TensorFlow 中的losscale optimizer的默认实现不兼容。然而,幸运的是,通过对每个特性进行小的修改,这些特性可以通过编程和谐地工作。以下是将 LBS 与混合精度相结合时需要解决的问题:
- 处理梯度中的 Nan 值:损失缩放的副作用之一是,由于数值溢出,我们可能偶尔会在梯度中得到 Nan 或 Inf 值。如果 LossScaleOptimizer 遇到这样的值,它简单地丢弃梯度并继续下一步。然而,目前 Horovod 中的梯度聚合代码没有考虑 Nan 值的可能性。这个问题的细节以及建议的修改可以在这里找到,但是需要覆盖默认的 Horovod 行为。希望这将在 Horovod 的未来版本中得到解决。
- 何时更新缩放系数:如上所述,losscale optimizer基于梯度统计对缩放系数进行周期性更新。在上一节中,我们建议在 LBS 期间应用超参数的定期更新时要谨慎。在这种情况下,如果我们不能将缩放系数的更新与我们应用(和重置)聚集梯度的步骤对齐,我们不仅会在大批量模拟中失败,而且还可能会完全无法训练。如果梯度在其缩放状态下累积(例如 TensorFlow 2.3),并且我们在累积期间重置缩放,则聚集的梯度将具有不匹配的缩放系数。因此,得到的未缩放梯度将是不正确的。即使梯度在其未缩放的状态下被累积(例如 TensorFlow 2.4 和 2.5),也有可能losscale optimizer将无法找到合适的缩放因子。
为了解决这个问题,您需要覆盖由lossscaleproptimizer调用的损失比例更新函数,以便损失比例更新仅应用于梯度聚合器被重置的步骤。见这里有一种方法可以解决这个问题。 - 梯度量化的非线性:需要记住的一点是,使用低精度时固有的量化构成了对梯度的非线性操作。事实上,你很可能会发现一批尺寸为 N x B 的梯度和 N 批尺寸为 B 的梯度的平均值之间存在很小的数字差异。当然,即使使用 32 位浮点数,量化也是存在的,但是数量上的影响远没有那么明显。尽管如此,我们还没有发现由于非线性而影响训练的数值差异,但是知道这一点是有好处的。
请注意,根据您使用的 TensorFlow 版本,可能需要进行一些额外的更改,以便将这两种功能结合起来。更多详情见此处。
如何使用 LightGBM 和 boosted 决策树预测销售
使用机器学习构建数据以预测未来销售的广泛指南。它介绍了如何使用 python 创建滞后变量、滚动方法和基于时间的功能。它涵盖了如何执行目标编码,训练测试分裂的时间相关模型,并建立一个梯度推进树模型预测下个月的零售商的销售。
问题陈述
大多数公司对了解他们未来的业绩感兴趣。上市公司必须向投资者提供指导,说明他们认为下一季度或下一年的财务表现会如何。
为了能够回答公司在未来时期的表现,许多公司雇佣分析师来构建预测业务表现的分析解决方案。这种分析往往侧重于平均历史表现,并将其外推至未来结果。移动平均线和滚动窗口是一种常见的做法,也是长期以来的标准做法。
随着数据科学的最新发展,这些模型可以使用更复杂的技术(如梯度推进三)得到显著改善。
在本指南中,我们将使用 ML 为一家俄罗斯零售商预测下个月的销售额。我们将预测每个商店售出的每件商品的销售量。
通过将数据组织为月度预测,我们可以利用我们拥有的关于商店和产品的非常精细的数据。我们将使用在 t-n 时刻获取的历史销售数据来预测 t+n 时刻的未来销售。
作者图片
数据
该数据集来源于 kaggle 的预测未来销售竞赛,包括几个数据文件,我们必须将它们结合在一起。对于那些了解数据库建模的人来说,sales_train 文件或训练集可以被认为是星型模式中的事实表,其中 items、item_ categories 和 shops 是我们可以用主键连接的维度表。
测试文件是具有类似关系的另一个事实。sales_train 文件和测试文件的关键区别在于,销售文件是每日,测试文件是每月。通常在实践中,我们可能希望预测月销售额,以便最终消费者更容易理解。这意味着我们还必须将每日数据汇总到每月数据,以便将其输入到我们的模型中。
数据集包含以下文件:
- sales_train.csv —培训集。2013 年 1 月至 2015 年 10 月的每日历史数据。
- test.csv —测试集。你需要预测这些商店和产品在 2015 年 11 月的销售额。
- sample_submission.csv —格式正确的示例提交文件。
- items.csv —物品/产品的补充信息。
- item_categories.csv —关于物品类别的补充信息。
- shop . CSV-店铺补充信息。
## Import lots of libraries to use
import pandas as pd
import numpy as np
from google_trans_new import google_translator
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.metrics import mean_squared_error
%matplotlib inline
from itertools import product
import time
from sklearn.model_selection import KFold
from sklearn import base
import lightgbm as lgb
from lightgbm import LGBMRegressor
from sklearn.preprocessing import LabelEncoder, OneHotEncoder, RobustScaler
from sklearn.model_selection import GridSearchCV, cross_val_score, StratifiedKFold, learning_curve, KFold, train_test_split
import calendar
from datetime import datetime
让我们读入数据。
## allows us to pick up the european formatting of the dates in the trainset
dateparse = lambda x: pd.datetime.strptime(x, ‘%d.%m.%Y’) # importing the trainset with dates correctly formatted
sales = pd.read_csv(‘sales_train.csv’, parse_dates = [‘date’], date_parser = dateparse)#import the rest of the files
test = pd.read_csv(‘test.csv’)
items = pd.read_csv(‘items.csv’)
item_categories = pd.read_csv(‘item_categories.csv’)
shops = pd.read_csv(‘shops.csv’)
现在我们已经读入了所有的文件,我们开始一个一个地分析文件。从项目类别开始。
项目类别文件
不幸的是,文件是俄文的。为了更好地理解数据,我们可以将单个类别转换成对非俄语使用者有意义的数据点。为此,我们可以使用 googletrans 将类别名称翻译成英语。我们将把翻译后的值存储在一个名为 Category_type 的列中。
item_categories.head()
作者图片
不会说俄语可能是一个劣势,但我们可以看到,似乎我们有一个模式,其中有一个单词后面跟着 PS2、PS3、PS4 和 PSP。这看起来很像它与各种索尼 playstation 平台有关。也许第一个可以帮助我们将这些物品归类在一起。我们来翻译一下单词,看看。
# Starting with translating the column item_category name from Russian to English. We will then append that to the original dataframe.
translator = google_translator()
list_a = []
for word in item_categories['item_category_name']:
try:
a = translator.translate(word)
list_a.append(a)
except:
list_a.append(word)
item_categories['English_Name'] = list(list_a)
print(list_a)
作者图片
这个翻译并不完美,因为它漏掉了一些术语。我们可以手动搜索单词,并用我们资助的最佳翻译替换它们,以便我们的类别更容易使用。
## Программы means Programs
item_categories[‘English_Name’]= item_categories[‘English_Name’].str.replace(“Программы”, “Programs”)## Книги means Books
item_categories[‘English_Name’]= item_categories[‘English_Name’].str.replace(“Книги”, “Books”)item_categories.head()
作者图片
啊哈!看起来每个项目类别的第一部分都有类别类型。在我们的例子中,类别类型是附件。让我们把它提取出来,存储在一个名为 Category_type 的新特性中。
## Create a feature called Variable type by splitting the English_Name strings where they either have a paranthesis or a dash.
list_a = []
for row in item_categories['English_Name']:
a = row.replace('(','-').split(' -')[0] ## replacing the opening parantheses with dash so we can use str.split function to split on it.
list_a.append(a)
item_categories['Category_type'] = list(list_a)
## Lets check out the categories we have
pd.DataFrame((item_categories['Category_type'].unique()))
看起来几个类别有相似的名称和含义。比如游戏 主机和游戏主机实际上是同一类型的品类。让我们清理一下,让这个新特性更加统一。
## Let's clean up some of this output in the categories:## Game Consoles are really the same thing as Gaming Consoles
item_categories['Category_type']= item_categories['Category_type'].str.replace("Gaming Consoles", "Game Consoles")## Payment cards with a lowercase c is the same as Payment Cards with upper case C
item_categories['Category_type']= item_categories['Category_type'].str.replace("Payment cards", "Payment Cards")## Cinema and movie tends to be synomomous. Let's change "The Movie" category type to Cinema
item_categories['Category_type']= item_categories['Category_type'].str.replace("The Movie", "Cinema")## Pure and Clean Media Seem Similar. Let's combine into Pure/Clean Media
item_categories['Category_type']= item_categories['Category_type'].str.replace("Clean media", "Pure/Clean Media")
item_categories['Category_type']= item_categories['Category_type'].str.replace("Pure Media", "Pure/Clean Media")
因为这个数据集比较大(对于笔记本电脑来说,无论如何都要处理),所以让我们删除不打算使用的列。这将允许我们在笔记本电脑上使用更少的内存。我们去掉英文名称 categories_name,只留下类别类型。
item_categories = item_categories.drop([‘item_category_name’,’English_Name’],axis =1)
商店
这个文件包含了商店的名称。它可以用作获取销售文件中商店 Id 名称的关键字。因为这个文件也是俄语的,我们将再次把单词翻译成英语。一旦我们有了英文名称,我们将提取这些商店所在的城市,并将其作为一个特征。
shops.head()
作者图片
尽管我学习了如何用俄语拼写配饰,但我担心我的俄语还不够好,看不清商店的名字。让我们把它们翻译成英语,看看这些单词是什么意思。
## Let’s translate this into English
translator = google_translator()
list_a = []
for word in shops[‘shop_name’]:
a = translator.translate(word)
list_a.append(a)
shops[‘English_Shop_Name’] = list_a
shops
作者图片
貌似城市是第一个词,后面是购物中心,TC 或者,SEC 之类的。让我们试着从这里提取城市。一些谷歌化的词让我觉得所有我检查过的地方,不管是 TC 还是 SEC 都是在购物中心。因此,我们没有创建一个突出的店铺名称的一部分。
我们将创建一个仅包含城市名称(第一个单词)的城市变量,并通过按空格分割 English _ Shops _ Name 字符串来创建一个名为城市类型的特性。
因为像圣彼得堡这样的城市名称中有一个空格,所以我们去掉了句号后面的空格和感叹号后面的空格。
list_a = []
for row in shops[‘English_Shop_Name’]:
a = row.replace(‘. ‘,’’).replace(‘! ‘,’’).split(‘ ‘)[0] ## remove spaces follwing period or exclaimation point and split based on spaces. First word is city
list_a.append(a)
shops[‘City’] = list(list_a)
## Lets check out the categories we have
pd.DataFrame((shops[‘City’].unique()))
作者图片
因为我们已经从中提取了城市信息,所以我们删除了商店名称,英文商店名称。
shops = shops.drop([‘shop_name’,’English_Shop_Name’],axis = 1)
shops.head()
作者图片
汇总数据
因为任务是进行每月预测,所以在进行任何编码之前,我们需要将数据聚集到每月级别。下面的代码单元正是为了这个目的。它还将 item_cnt_day 变量重命名为 Target(一旦我们将它设为每月聚合)。
gb = sales.groupby(index_cols,as_index=False).agg({'item_cnt_day':{target = 'sum'}})temp['total'] = pd.DataFrame(project_data.groupby(col1)[col2].agg({'total':'count'})).reset_index()['total']index_cols = [‘shop_id’, ‘item_id’, ‘date_block_num’]# For every month we create a grid from all shops/items combinations from that month
grid = []
for block_num in sales[‘date_block_num’].unique():
cur_shops = sales[sales[‘date_block_num’]==block_num][‘shop_id’].unique()
cur_items = sales[sales[‘date_block_num’]==block_num][‘item_id’].unique()
grid.append(np.array(list(product(*[cur_shops, cur_items, [block_num]])),dtype=’int32'))#turn the grid into pandas dataframe
grid = pd.DataFrame(np.vstack(grid), columns = index_cols,dtype=np.int32)#get aggregated values for (shop_id, item_id, month)
gb = sales.groupby(index_cols,as_index=False).agg({‘item_cnt_day’: ‘sum’})gb.columns = [‘shop_id’, ‘item_id’, ‘date_block_num’, ‘target’]
# gb = sales.groupby(index_cols,as_index=False).agg({‘item_cnt_day’:{‘target’:’sum’}})#fix column names
# gb.columns = [col[0] if col[-1]==’’ else col[-1] for col in gb.columns.values]
#join aggregated data to the grid
all_data = pd.merge(grid,gb,how=’left’,on=index_cols).fillna(0)
#sort the data
all_data.sort_values([‘date_block_num’,’shop_id’,’item_id’],inplace=True)
有时我们的训练数据中会有异常值。在这个特定的数据集中,我们知道如果我们的目标值为 20 或更大。这意味着,如果我们看到一个大于 20 的值,我们会自动将其称为 20。这对我们的 RMSE 分数有重大的积极影响。这可能并不总是适用于所有的预测模型。
all_data[‘target’]=all_data[‘target’].clip(0,20)
合并数据集
接下来,我们用训练集和测试集创建一个数据帧。我们将用 item_categories、items 和 shops 来加入它。我们将使用这个数据框架来创建我们的许多特征,并尽可能减少将它们应用于多个数据框架的需要。例如,当我们创建滞后变量时,我们需要为训练集、验证集和测试集创建它们。通过将所有数据合并到一个数据帧中,我们只需这样做一次。
在现实世界中,我们将构建一个预处理管道,将训练中使用的相同特征工程应用于未标记数据。那是另一天的话题。
我们将联合训练和测试设备。正如我们从上面的代码中看到的,我们在测试集中缺少了两列。这些是日期块编号和目标。现在,我们将把目标设定为零。我们还会将数字 34 分配给日期块编号。date_block_num 对应于数据集中的月份,因此由于我们需要预测下个月的 item_counts,我们将简单地查找训练集的最大值并加 1。(最大值为 33)
## Assign 34 to date_block_num and 0.0 to target
test[‘date_block_num’] = 34
test[‘target’] = 0.0TEST_ID = test[‘ID’] ## in case we need this later## Then we need to union them and save that back as our all_data dataframe
all_data = pd.concat([all_data,test], axis =0, sort=True)
all_data = all_data.drop(columns = [‘ID’])
接下来,我们将 all_data 数据框架与 items、item_categories 和 shops 数据框架合并。因为我们希望避免创建重复的行,所以我们将添加一些行计数检查器,以确保我们不会添加任何新行或删除任何行。
## Calculate number of rows prior to merge
prior_rows = all_data.shape[0]## Merge the sales train data with the items, item categoris and shops datasets to get the names of items, their categories and the shop names
all_data = pd.merge(all_data, items, on = “item_id”)
all_data = pd.merge(all_data, item_categories, on = “item_category_id”)
all_data = pd.merge(all_data, shops, on = “shop_id”)## Calcualte number and print of rows dropped (should be zero)
print(“Dropped {} rows”.format(prior_rows — all_data.shape[0]))
作者图片
特征工程
因为我们有大量可能具有预测性的数据,所以我们需要将其预处理成我们的模型可以使用的格式。这通常被称为特征工程。
处理日期、季节和日期
日期可以告诉我们很多关于销售的事情。例如,二月份的销售额可能低于一月份,仅仅是因为二月份的天数比其他月份少。日子的类型也很重要。更多的周末可能意味着更多的人经常光顾商店。季节也很重要,六月的销售可能与十二月不同。我们将创建与所有这些项目相关的功能。
首先,我们需要提取每个日期块的月份和日期,并将其存储在一个数据帧中。
## Pull out the last date of each dateblock and append it to the
from datetime import datetime
list_a = []
for dateblock in sales[‘date_block_num’].unique():
a = sales[sales[‘date_block_num’] == dateblock]
a = max(a[‘date’])
list_a.append(a)
list_a.append(datetime.strptime(‘2015–11–30’,’%Y-%m-%d’)) ## Manually adding the month for the test set
## Transform it to dataframe so we can merge with all_data
list_a = pd.DataFrame(list_a)
## Give the data a descriptive column header
list_a.columns = [‘Month_End_Date’]
现在已经提取了月份和日期,我们可以计算每个月中有多少个星期一、星期二等。
## Let’s calculate the number of specific days are in each month.
import calendar
## Create the empty lists
mon_list = []
tue_list = []
wed_list = []
thu_list = []
fri_list = []
sat_list = []
sun_list = []## Calculate the number of a specific day in a given month (for example, number of mondays in March of 2015)
for date in list_a[‘Month_End_Date’]:
mon_list.append((len([1 for i in calendar.monthcalendar(date.year,date.month) if i[0] != 0])))
tue_list.append((len([1 for i in calendar.monthcalendar(date.year,date.month) if i[1] != 0])))
wed_list.append((len([1 for i in calendar.monthcalendar(date.year,date.month) if i[2] != 0])))
thu_list.append((len([1 for i in calendar.monthcalendar(date.year,date.month) if i[3] != 0])))
fri_list.append((len([1 for i in calendar.monthcalendar(date.year,date.month) if i[4] != 0])))
sat_list.append((len([1 for i in calendar.monthcalendar(date.year,date.month) if i[5] != 0])))
sun_list.append((len([1 for i in calendar.monthcalendar(date.year,date.month) if i[6] != 0])))## Add these to our list we created with the dates
list_a[‘Number_of_Mondays’] = mon_list
list_a[‘Number_of_Tuesdays’] = tue_list
list_a[‘Number_of_Wednesdays’] = wed_list
list_a[‘Number_of_Thursdays’] = thu_list
list_a[‘Number_of_Fridays’] = fri_list
list_a[‘Number_of_Saturdays’] = sat_list
list_a[‘Number_of_Sundays’] = sun_list
我们还可以提取与年、月和月中天数相关的特征。
## Create the empty listsyear_list = []
month_list = []
day_list = []## Next lets calculate strip out the number of days in a month, the number of the month and the number of the year
for date in list_a['Month_End_Date']:
year_list.append(date.year)
month_list.append(date.month)
day_list.append(date.day)## Add to our dataframe
list_a['Year'] = year_list
list_a['Month'] = month_list
list_a['Days_in_Month'] = day_list
list_a 数据帧可以与 all_data 数据帧合并,我们还添加了一些日期特性。
## Merge the new dataframe with the all_data, using the index and the date_block_num as keys
all_data = pd.merge(all_data, list_a, left_on = ‘date_block_num’, right_index = True)
价格变量
我们最初的方法是添加交易的计数,我们没有对项目的价格做任何事情。让我们计算每月价格的平均值,并将该特性与我们的 all_data 数据框架合并。
## adding the average monthly price within a monthly block for each item at each store to the dataset
a = sales.groupby([‘date_block_num’,’shop_id’,’item_id’])[‘item_price’].mean()
a = pd.DataFrame(a)
all_data = pd.merge(all_data,a,how = “left”, left_on = [‘date_block_num’,’shop_id’,’item_id’], right_on = [‘date_block_num’,’shop_id’,’item_id’])
物品首次售出后的月数&物品上次售出后的月数
这些特征显示了自第一次出售该物品和最后一次出售该物品以来的日期块(月)的数量。这将有助于我们了解该商品有多新,并可能告诉我们该商品已不再销售。
我们将计算每个项目的最小值。这将给出它售出的第一个月。然后,我们将计算该数字与当前日期块之间的差值,以查看该项目的“旧”程度。
a = all_data.groupby(‘item_id’)[‘date_block_num’].min()
a = pd.DataFrame(a)
a = a.reset_index()
a.columns = [‘item_id’,’min_item_sale_date_block_num’]
all_data = pd.merge(all_data,a, left_on = ‘item_id’, right_on = ‘item_id’)
all_data[‘Months_Since_Item_First_Sold’] = all_data[‘date_block_num’]- all_data[‘min_item_sale_date_block_num’]
测试集中的一些数据是针对我们从未见过的产品的。让我们创建一个特性,只计算特定商品在第一个月的平均月销售额。我们会让其余的人归零。
我们还将把相同的逻辑应用于商品类别和商店 id 的组合。我们可以按类别计算第一个月的平均销售额
a = all_data[all_data[‘Months_Since_Item_First_Sold’] == 0].groupby([‘item_category_id’,’Months_Since_Item_First_Sold’])[‘target’].mean()
a = pd.DataFrame(a)
a = a.reset_index()
a.columns = [‘item_category_id’,’Months_Since_Item_First_Sold’,’avg_first_months_sales_by_item_category_id’]
all_data = pd.merge(all_data,a, left_on = [‘item_category_id’,’Months_Since_Item_First_Sold’], right_on = [‘item_category_id’,’Months_Since_Item_First_Sold’], how = ‘left’)
all_data[‘avg_first_months_sales_by_item_category_id’] = all_data[‘avg_first_months_sales_by_item_category_id’].fillna(0)
按类别和店铺 ID 计算第一个月的平均销售额。
a = all_data[all_data[‘Months_Since_Item_First_Sold’] == 0].groupby([‘item_category_id’, ‘Months_Since_Item_First_Sold’,’shop_id’])[‘target’].mean()
a = pd.DataFrame(a)
a = a.reset_index()
a.columns = [‘item_category_id’,’Months_Since_Item_First_Sold’,’shop_id’,’avg_first_months_sales_by_item_category_and_shop’]
all_data = pd.merge(all_data,a, left_on = [‘item_category_id’,’Months_Since_Item_First_Sold’,’shop_id’], right_on = [‘item_category_id’,’Months_Since_Item_First_Sold’, ‘shop_id’], how = ‘left’)
all_data[‘avg_first_months_sales_by_item_category_and_shop’] = all_data[‘avg_first_months_sales_by_item_category_and_shop’].fillna(0)
滞后变量
如果只允许我用一个数据点来预测下个月的销售额,我可能会用这个月的销售额。这个月的销售额是一个滞后变量。在时间序列分析中,目标变量的滞后是很常见的。我们将创建几个滞后变量(上个月的销售额)。我们将重复这一过程几个月。
为了创建一个滞后函数,熊猫图书馆有一个非常有用的函数叫做 shift。我们将它包裹在一个循环中,以产生几个月的多重滞后。我们还使用内置的填充值函数使 na 值为零。
## With pandas shift function
for each in [1,2,3,4,5,6,12]:
all_data[str("target_lag_"+str(each))] = all_data.groupby(['date_block_num','shop_id','item_id'])['target'].shift(each, fill_value = 0)
更多!滞后很有趣。让我们使用每个月的平均值以及类别、城市、商店或商品来创建特征。我们可以在模型中使用这些特征的滞后。
## Average number of sales by month and by item
all_data[‘avg_monthly_by_item’] = all_data.groupby([‘item_id’, ‘date_block_num’])[‘target’].transform(‘mean’)## Average number of sales by month and by shop
all_data[‘avg_monthly_by_shop’] = all_data.groupby([‘shop_id’, ‘date_block_num’])[‘target’].transform(‘mean’)## Average number of sales by month and by category
all_data[‘avg_monthly_by_category’] = all_data.groupby([‘Category_type’, ‘date_block_num’])[‘target’].transform(‘mean’)## Average number of sales by month and by city
all_data[‘avg_monthly_by_city’] = all_data.groupby([‘City’, ‘date_block_num’])[‘target’].transform(‘mean’)
移动平均线
另一种为数据添加更多特征的方法是创建滚动或移动平均值。滚动平均具有很强的预测性,有助于确定目标的历史水平。让我们创建两个滚动平均值,3 个月和 6 个月。
## 3-months rolling average
all_data[‘target_3_month_avg’] = (all_data[‘target_lag_1’] + all_data[‘target_lag_2’] +all_data[‘target_lag_3’]) /3## 6-months rolling average
all_data[‘target_6_month_avg’] = (all_data[‘target_lag_1’] + all_data[‘target_lag_2’] +all_data[‘target_lag_3’] + all_data[‘target_lag_4’] + all_data[‘target_lag_5’] +all_data[‘target_lag_6’]) /6
请注意我们是如何明确计算这些平均值的。这也可以通过熊猫滚动和平均功能来实现。
## 3-months rolling average
all_data['target_3_month_avg'] = all_data.groupby(['date_block_num','shop_id','item_id'])['target'].rolling(3, fill_value = 0).mean()## 6-months rolling average
all_data['target_6_month_avg'] = all_data.groupby(['date_block_num','shop_id','item_id'])['target'].rolling(3, fill_value = 0).mean()
提高内存使用率
哇,我们创造了很多功能。因为我们只使用一台笔记本电脑,所以我们应该确保将它存储在尽可能小的数据帧中。让我们检查内存使用情况和数据帧的数据类型。
all_data.info(memory_usage = “deep”)
运行 info 时,在输出的底部,您会看到数据框中不同数据类型的数量以及它们的总内存使用情况。
由于其中许多被列为 int64 或 float64,我们可能会将它们减少到更小的空间数据类型,如 int16 或 float8。向下转换意味着我们将每个特性的数据类型减少到最低可能的类型。
for column in all_data:
if all_data[column].dtype == ‘float64’:
all_data[column]=pd.to_numeric(all_data[column], downcast=’float’)
if all_data[column].dtype == ‘int64’:
all_data[column]=pd.to_numeric(all_data[column], downcast=’integer’)
## Dropping Item name to free up memory
all_data = all_data.drop(‘item_name’,axis =1)
## Let’s check the size
all_data.info(memory_usage = “deep”)
作者图片
使用向下转换,我们能够将数据集的大小减少到一半。
列车测试拆分
在更传统的最大似然模型中,我们会随机地将观测值分配给训练集、测试集和验证集。在预测情况下,我们需要考虑时间对数据集的影响,并相应地构建我们的训练和测试验证。既然我们的数据集已经向下转换,我们可以开始将数据分为训练(前 32 个月)、验证(第 33 个月)和返回到我们的测试集(第 34 个月)。
X_train = all_data[all_data.date_block_num < 33]
Y_train = all_data[all_data.date_block_num < 33][‘target’]
X_valid = all_data[all_data.date_block_num == 33]
Y_valid = all_data[all_data.date_block_num == 33][‘target’]
X_test = all_data[all_data.date_block_num == 34]
更多功能工程—目标编码
我们为什么以编码为目标?
XGBoost 和 LightGBM 等梯度增强的基于树的模型很难处理高基数的分类变量。目标编码通过用平均结果替换字符串或文本值,帮助将分类变量转换为数值。例如,如果一个*【PS3】的平均销售量是 300,PS2 是 200,我们将用 300 替换【PS3】*的字符串,用 200 替换 PS2。事实上,一个模特现在可以知道我们应该期待 PS3 的销量超过 PS2 的销量。这种类型的特征工程有助于提高模型性能。
为什么要正规化?
简单地计算目标变量的平均值会导致过度拟合,并且通常会降低模型推广到新数据的能力。所以我们需要规范
正则化技术:
- 培训数据中的交叉验证循环
- 缓和
- 添加随机噪声
- 排序和计算扩展平均值
我们将只在训练数据中进行交叉验证循环。首先,我们将定义两个助手函数,这是我从https://medium . com/@ pouryayria/k-fold-target-encoding-dfe 9a 594874 b中获得的
## Helpder Function to KFold Mean encoding
class KFoldTargetEncoderTrain(base.BaseEstimator,
base.TransformerMixin):
def __init__(self,colnames,targetName,
n_fold=5, verbosity=True,
discardOriginal_col=False):
self.colnames = colnames
self.targetName = targetName
self.n_fold = n_fold
self.verbosity = verbosity
self.discardOriginal_col = discardOriginal_col
def fit(self, X, y=None):
return self
def transform(self,X):
assert(type(self.targetName) == str)
assert(type(self.colnames) == str)
assert(self.colnames in X.columns)
assert(self.targetName in X.columns)
mean_of_target = X[self.targetName].mean()
kf = KFold(n_splits = self.n_fold,
shuffle = False, random_state=2019)
col_mean_name = self.colnames + '_' + 'Kfold_Target_Enc'
X[col_mean_name] = np.nan
for tr_ind, val_ind in kf.split(X):
X_tr, X_val = X.iloc[tr_ind], X.iloc[val_ind]
X.loc[X.index[val_ind], col_mean_name] = X_val[self.colnames].map(X_tr.groupby(self.colnames)[self.targetName].mean())
X[col_mean_name].fillna(mean_of_target, inplace = True)
if self.verbosity:
encoded_feature = X[col_mean_name].values
print('Correlation between the new feature, {} and, {} is {}.'.format(col_mean_name,self.targetName,
np.corrcoef(X[self.targetName].values,
encoded_feature)[0][1]))
if self.discardOriginal_col:
X = X.drop(self.targetName, axis=1)
return X## Helper function to get the Kfold Mean encoded on the test setclass KFoldTargetEncoderTest(base.BaseEstimator, base.TransformerMixin):
def __init__(self,train,colNames,encodedName):
self.train = train
self.colNames = colNames
self.encodedName = encodedName
def fit(self, X, y=None):
return self
def transform(self,X):
mean = self.train[[self.colNames,
self.encodedName]].groupby(
self.colNames).mean().reset_index()
dd = {}
for index, row in mean.iterrows():
dd[row[self.colNames]] = row[self.encodedName]
X[self.encodedName] = X[self.colNames]
X = X.replace({self.encodedName: dd})
return X
现在我们已经定义了两个助手函数,让我们用它们开始对变量进行均值编码:
- 项目标识
- 商店标识
- 城市
- 类别 _ 类型
- 项目 _ 类别 _ 标识
## item_id mean encoding
targetc = KFoldTargetEncoderTrain(‘item_id’,’target’,n_fold=5)
X_train = targetc.fit_transform(X_train)## shop_id mean encoding
targetc = KFoldTargetEncoderTrain(‘shop_id’,’target’,n_fold=5)
X_train = targetc.fit_transform(X_train)## City mean encoding
targetc = KFoldTargetEncoderTrain(‘City’,’target’,n_fold=5)
X_train = targetc.fit_transform(X_train)## Category_type mean encoding
targetc = KFoldTargetEncoderTrain(‘Category_type’,’target’,n_fold=5)
X_train = targetc.fit_transform(X_train)## Item_category_id mean encoding
targetc = KFoldTargetEncoderTrain(‘item_category_id’,’target’,n_fold=5)
X_train = targetc.fit_transform(X_train)
作者图片
对测试集应用类似的转换。
## Transform validation & test set## Apply item id mean encoding to test set
test_targetc = KFoldTargetEncoderTest(X_train,’item_id’,’item_id_Kfold_Target_Enc’)
X_valid = test_targetc.fit_transform(X_valid)
X_test = test_targetc.fit_transform(X_test)## Apply shop id mean encoding to test set
test_targetc = KFoldTargetEncoderTest(X_train,’shop_id’,’shop_id_Kfold_Target_Enc’)
X_valid = test_targetc.fit_transform(X_valid)
X_test = test_targetc.fit_transform(X_test)## Apply city mean encoding to test set
test_targetc = KFoldTargetEncoderTest(X_train,’City’,’City_Kfold_Target_Enc’)
X_valid = test_targetc.fit_transform(X_valid)
X_test = test_targetc.fit_transform(X_test)## Apply Category_type mean encoding to test set
test_targetc = KFoldTargetEncoderTest(X_train,’Category_type’,’Category_type_Kfold_Target_Enc’)
X_valid = test_targetc.fit_transform(X_valid)
X_test = test_targetc.fit_transform(X_test)## Apply item_category_id mean encoding to test set
test_targetc = KFoldTargetEncoderTest(X_train,’item_category_id’,’item_category_id_Kfold_Target_Enc’)
X_valid = test_targetc.fit_transform(X_valid)
X_test = test_targetc.fit_transform(X_test)
最终数据集
我们正在接近。我们的特写完成了。让我们做一些检查,以确保我们只有我们将使用的功能。
## drop first 12 months since we have lagged variables
X_train = X_train[X_train.date_block_num > 12]## Assign target variables to seperate variables
y= X_train[‘target’]
Y_valid = X_valid[‘target’]## Drop Categorical Variables that we mean encoded, the target and the item codes.
columns_to_drop = [‘target’, ‘Category_type’,’City’,’Month_End_Date’, ‘item_category_id’]
X_train= X_train.drop(columns_to_drop, axis = 1)
X_valid = X_valid.drop(columns_to_drop, axis = 1)
X_test = X_test.drop(columns_to_drop, axis = 1)
使用 LightGBM 建模
LightGBM 是一个梯度推进框架,使用基于树的学习算法。它被设计为分布式和高效的,具有以下优点:
- 训练速度更快,效率更高。
- 更低的内存使用率。
- 精确度更高。
- 支持并行和 GPU 学习。
- 能够处理大规模数据。
LightGBM 非常擅长处理大于 100K 记录的数据集,并且与 XGBoost 相比速度相对较快。
我们需要将训练和验证转换成建模所需的 lgb 数据集结构。
lgb_train = lgb.Dataset(X_train, y)
lgb_eval = lgb.Dataset(X_valid, Y_valid, reference=lgb_train)
像大多数增强模型一样,我们需要调整我们的超参数。这些是我最成功的,但这并不意味着它们是“终极”的。一般来说,当我调整参数时,我倾向于遵循文档提供的指导。
LightGBM 使用逐叶树生长算法,而许多其他流行的工具使用逐深度树生长算法。与深度增长相比,叶子增长算法可以更快地收敛。然而,如果不使用合适的参数,叶向生长可能会过度拟合。
为了使用逐叶树获得良好的结果,以下是一些重要的参数:
**叶子数。**这是控制树形模型复杂度的主要参数。理论上,我们可以设置 num_leaves = 2^(max_depth)来获得与深度方向树相同数量的叶子。然而,这种简单的转换在实践中并不好。原因是,对于固定数量的叶子,按叶排序的树通常比按深度排序的树更深。不受约束的深度会导致过度拟合。因此,在尝试调整 num_leaves 时,我们应该让它小于 2^(max_depth).例如,当 max_depth=7 时,深度方向的树可以获得良好的准确性,但是将 num_leaves 设置为 127 可能会导致过度拟合,而将其设置为 70 或 80 可能会获得比深度方向更好的准确性。
**叶中最小数据。**这是一个非常重要的参数,可以防止逐叶树中的过度拟合。它的最佳值取决于训练样本的数量和 num_leaves。将其设置为较大的值可以避免树长得太深,但可能会导致欠拟合。实际上,对于大型数据集,将其设置为数百或数千就足够了。最大深度。您还可以使用 max_depth 来显式限制树深度。
# specify the configurations as a dict
params = {
‘boosting_type’: ‘gbdt’,
‘objective’: ‘regression’,
‘metric’: ‘rmse’,
‘num_leaves’: 31,
‘learning_rate’: 0.05,
‘feature_fraction’: 0.9,
‘bagging_fraction’: 0.8,
‘bagging_freq’: 5,
‘verbose’: 0,
‘num_threads’ : 4
}print(‘Starting training…’)
# train
gbm = lgb.train(params,
lgb_train,
num_boost_round=10000,
valid_sets=lgb_eval,
early_stopping_rounds=100)print(‘Saving model…’)
# save model to file
gbm.save_model(‘model.txt’)print(‘Starting predicting…’)
# predict
y_pred = gbm.predict(X_valid, num_iteration=gbm.best_iteration)
# eval
print(‘The rmse of prediction is:’, mean_squared_error(Y_valid, y_pred) ** 0.5)
让我们来看看特征重要性图。
num_features = 50
indxs = np.argsort(gbm.feature_importance())[:num_features]
feature_imp = pd.DataFrame(sorted(zip(gbm.feature_importance()[indxs],X_train.columns[indxs])), columns=[‘Value’,’Feature’])plt.figure(figsize=(20, 20))
sns.barplot(x=”Value”, y=”Feature”, data=feature_imp.sort_values(by=”Value”, ascending=False))
plt.title(‘Top {} LightGBM Features accorss folds’.format(num_features))
plt.tight_layout()
plt.show()
作者图片
就是这样,我们成功地训练了一个 LightGBM 模型来预测下个月的销售额。
如果你觉得这很有用,请为这个故事鼓掌。
如何使用 loc 和 iloc 在 Pandas 中选择数据
熊猫帮助你开始数据分析的提示和技巧
当选择数据帧上的数据时,熊猫loc
和iloc
是两个最受欢迎的。它们快捷、快速、易读,有时还可以互换。
在本文中,我们将探索loc
和iloc
之间的差异,看看它们的相似之处,并检查如何用它们执行数据选择。我们将讨论以下主题:
loc
和iloc
的区别- 通过单个值进行选择
- 通过值列表进行选择
- 通过切片选择数据范围
- 通过条件选择并可调用
- 当标签是从 0 开始的整数时,
loc
和iloc
可以互换
源代码请查看笔记本。
1.loc
和iloc
的区别
loc
和iloc
的主要区别在于:
loc
是基于标签的,这意味着您必须根据行和列的标签来指定行和列。iloc
是基于整数位置的,所以你必须通过它们的整数位置值(基于 0 的整数位置)来指定行和列。
以下是loc
和iloc
的一些区别和相似之处:
loc 和 iloc 的异同(图片由作者提供)
为了进行演示,我们创建了一个 DataFrame,并使用 Day 列作为索引来加载它。
df = pd.read_csv('data/data.csv', **index_col=['Day']**)
作者图片
2.通过单个值进行选择
loc
和iloc
都允许输入单个值。我们可以使用以下数据选择语法:
loc[row_label, column_label]
iloc[row_position, column_position]
例如,假设我们想要检索星期五的温度值。
使用loc
,我们可以传递行标签'Fri'
和列标签'Temperature'
。
# To get Friday's temperature
>>> df.**loc['Fri', 'Temperature']**10.51
等效的iloc
语句应该采用行号4
和列号1
。
# The equivalent `iloc` statement
>>> df.**iloc[4, 1]**10.51
我们也可以使用:
返回所有数据。例如,要获取所有行:
# To get all rows
>>> df.loc**[:, 'Temperature']**Day
Mon 12.79
Tue 19.67
Wed 17.51
Thu 14.44
Fri 10.51
Sat 11.07
Sun 17.50
Name: Temperature, dtype: float64# The equivalent `iloc` statement
>>> df**.iloc[:, 1]**
要获取所有列:
# To get all columns
>>> df**.loc['Fri', :]**Weather Shower
Temperature 10.51
Wind 26
Humidity 79
Name: Fri, dtype: object# The equivalent `iloc` statement
>>> df**.iloc[4, :]**
注意以上 2 个输出为系列。loc
和iloc
当结果为一维数据时,将返回一个系列。
3.通过值列表进行选择
我们可以将标签列表传递给loc
来选择多行或多列:
# Multiple rows
>>> df.**loc[['Thu', 'Fri'], 'Temperature']**Day
Thu 14.44
Fri 10.51
Name: Temperature, dtype: float64# Multiple columns
>>> df.**loc['Fri', ['Temperature', 'Wind']]**Temperature 10.51
Wind 26
Name: Fri, dtype: object
类似地,可以将整数值列表传递给iloc
来选择多行或多列。下面是使用iloc
的等价语句:
>>> df**.iloc[[3, 4], 1]**Day
Thu 14.44
Fri 10.51
Name: Temperature, dtype: float64>>> df**.iloc[4, [1, 2]]**Temperature 10.51
Wind 26
Name: Fri, dtype: object
以上所有输出都是系列,因为它们的结果都是一维数据。
例如,当结果是二维数据时,输出将是一个数据帧,以访问多个行和列
# Multiple rows and columns
rows = ['Thu', 'Fri']
cols=['Temperature','Wind']df.**loc[rows, cols]**
等效的iloc
语句是:
rows = [3, 4]
cols = [1, 2]df.**iloc[rows, cols]**
4.通过切片选择数据范围
Slice(写为start:stop:step
)是一种强大的技术,允许选择一系列数据。当我们想要选择两个项目之间的所有内容时,这非常有用。
loc
带切片
使用loc
,我们可以使用语法A:B
从标签 A 到标签 B 中选择数据(包括 A 和 B ):
# Slicing column labels
rows=['Thu', 'Fri']df.loc[rows, **'Temperature':'Humidity'** ]
作者图片
# Slicing row labels
cols = ['Temperature', 'Wind']df.loc[**'Mon':'Thu'**, cols]
作者图片
我们可以使用语法A:B:S
从标签 A 中选择数据,以步长 S 标记 B (包括 A 和 B ):
# Slicing with step
df.loc[**'Mon':'Fri':2** , :]
作者图片
iloc
带切片
使用iloc
,我们也可以使用语法n:m
来选择从位置 n (包含)到位置 m (不包含)的数据。然而,这里的主要区别是端点( m )被排除在iloc
结果之外。
例如,选择从位置 0 到 3 的列(不包括):
df.iloc[[1, 2], **0 : 3**]
作者图片
同样,我们可以使用语法n:m:s
选择从位置 n (包含)到位置 m (不包含)的数据,步长为 s 。注意端点 m 被排除。
df.iloc[0:4:2, :]
作者图片
5.通过条件选择并可调用
情况
**loc**
带条件
我们通常希望根据条件过滤数据。例如,我们可能需要找到湿度大于 50 的行。
使用loc
,我们只需要将条件传递给loc
语句。
# One condition
df.loc[**df.Humidity > 50**, :]
作者图片
有时,我们可能需要使用多个条件来过滤数据。例如,查找湿度大于 50 且天气为阵雨的所有行:
## multiple conditions
df.loc[
**(df.Humidity > 50) & (df.Weather == 'Shower')**,
['Temperature','Wind'],
]
作者图片
**iloc**
同条件
对于iloc
,如果将条件直接传递到语句中,我们将得到一个值错误:
# Getting ValueError
df.**iloc[df.Humidity > 50, :]**
作者图片
我们得到这个错误是因为iloc
不能接受布尔序列。它只接受布尔列表。我们可以使用list()
函数将一个系列转换成一个布尔列表。
# Single condition
df.iloc[**list(df.Humidity > 50)**]
类似地,我们可以使用list()
将多个条件的输出转换成一个布尔列表:
## multiple conditions
df.iloc[
**list((df.Humidity > 50) & (df.Weather == 'Shower'))**,
:,
]
可调用函数
**loc**
带有可调用的
loc
接受一个可调用的作为索引器。callable 必须是具有一个参数的函数,该函数返回有效的索引输出。
例如选择列
# Selecting columns
df.loc[:, **lambda df: ['Humidity', 'Wind']**]
并使用可调用的过滤数据:
# With condition
df.loc[**lambda df: df.Humidity > 50**, :]
作者图片
**iloc**
带有可调用的
iloc
也可以带一个可调用的作为索引器。
df.iloc[**lambda df: [0,1]**, :]
作者图片
为了用 callable 过滤数据,iloc
需要list()
将条件的输出转换成一个布尔列表:
df.iloc[**lambda df: list(df.Humidity > 50)**, :]
作者图片
6.当标签是从 0 开始的整数时,loc
和iloc
可以互换
为了演示,让我们创建一个数据帧,用从 0 开始的整数作为标题和索引标签。
df = pd.read_csv(
'data/data.csv',
**header=None,**
**skiprows=[0],**
)
使用header=None
,熊猫将生成以 0 为基的整数值作为头。有了skiprows=[0]
,我们一直在用的那些头天气、**温度、**等都将被跳过。
作者图片
现在,loc
,一个基于标签的数据选择器,可以接受单个整数和一个整数值列表。例如:
>>> df**.loc[1, 2]** 19.67 >>> df**.loc[1, [1, 2]]** 1 Sunny
2 19.67
Name: 1, dtype: object
他们工作的原因是那些整数值(1
和2
)被解释为指数的 标签 。这种用法是而不是一个带有索引的整数位置,有点混乱。
在这种情况下,通过单个值或值列表进行选择时,loc
和iloc
可以互换。
>>> df.**loc[1, 2]** == df.**iloc[1, 2]**
True>>> df.**loc[1, [1, 2]]** == **df.iloc[1, [1, 2]]**
1 True
2 True
Name: 1, dtype: bool
请注意,通过切片和条件选择时,loc
和iloc
将返回不同的结果。它们本质上是不同的,因为:
- 切片:端点从
iloc
结果中排除,但包含在loc
中 - 条件:
loc
接受布尔序列,但iloc
只能接受布尔列表。
结论
最后,这里是一个总结
loc
基于标签,允许的输入有:
- 单个标签
'A'
或2
(注意2
被解释为索引的标签。) - 标签列表
['A', 'B', 'C']
或[1, 2, 3]
(注意1, 2, 3
解释为索引的标签。) - 带标签的切片
'A':'C'
(两者都包含) - 条件、布尔序列或布尔数组
- 一个只有一个参数的
callable
函数
iloc
基于整数位置,允许的输入有:
- 整数,例如
2
。 - 整数列表或数组
[1, 2, 3]
。 - 整数切片
1:7
(端点7
除外) - 条件,但只接受布尔数组
- 一个有一个参数的函数
当熊猫数据帧的标签是从 0 开始的整数时,loc
和iloc
可以互换
希望这篇文章能帮助你节省学习熊猫数据选择的时间。我建议你查看一下文档来了解你可以做的其他事情。
感谢阅读。请查看笔记本获取源代码,如果你对机器学习的实用方面感兴趣,请继续关注。
你可能会对我的其他一些熊猫文章感兴趣:
- Pandas cut()函数,用于将数值数据转换为分类数据
- 使用熊猫方法链接提高代码可读性
- 如何对熊猫数据帧进行自定义排序
- 为了数据分析你应该知道的所有熊猫移位()
- 何时使用 Pandas transform()函数
- 你应该知道的熊猫串联()招数
- Pandas 中 apply()和 transform()的区别
- 所有的熊猫合并()你应该知道
- 在熊猫数据帧中处理日期时间
- 熊猫阅读 _csv()你应该知道的招数
- 用 Pandas read_csv() 解析日期列应该知道的 4 个技巧
更多教程可以在我的 Github 上找到
如何在熊猫身上使用 loc
了解如何使用 pandas Python 库中的 loc 方法
马库斯·斯皮斯克在 Unsplash 上的照片
为熊猫的数据帧建立索引是一项非常重要的技能。索引仅仅意味着在数据帧或系列中选择特定的行和/或列。
在本教程中,我们将学习 loc 方法,这是最简单的和最通用的方法来索引 pandas 中的数据帧。
制作数据框
我们将使用 jupyter 笔记本中的这里找到的 ufo 目击数据集。让我们将数据读入数据帧,看看 ufo 数据帧的前 5 行:
我们 ufo 数据帧的前 5 行
让我们看看关于我们的数据框架的其他一些信息:
我们使用了 shape 和 columns dataframe 属性来分别获取数据帧的形状(行数、列数)和列名。
锁定方法
索引数据帧最通用的方法可能是loc方法。loc 既是 dataframe 又是 series 方法,这意味着我们可以对这些 pandas 对象中的任何一个调用 loc 方法。当在数据帧上使用 loc 方法时,我们通过使用以下格式指定我们想要的行和列:
data frame . loc[指定的行:指定的列]
有种不同的方式来指定我们想要选择的行和列。例如,我们可以传入一个单个标签、一个标签列表或数组、一个带标签的切片对象或一个布尔数组。
让我们来看一下这些方法!
使用单一标签
我们可以指定哪些行和/或哪些列的一种方法是使用标签。对于行,标签是该行的索引值,对于列,列名是标签。例如,在我们的 ufo 数据帧中,如果我们只想要第五行以及所有的列,我们将使用如下:
ufo.loc[4, :]
单一标签
我们传入的标签永远被解释为标签。它永远不会被解释为沿着索引的整数位置。所以我们通过使用特定行的标签来指定我们想要的行,这是 4,因为我们想要所有的列,我们将只使用冒号。
注意:我们可以省略冒号,我们会得到相同的输出,但是,为了代码可读性,最好保留冒号,以明确显示我们需要所有列。
标签的列表或数组
假设我们想要多个行和/或列。我们如何指定它?嗯,使用标签,我们可以输入一个标签列表,或者使用你可能熟悉的类似于切片符号的东西。
让我们从标签列表开始:
标签列表
注意我们如何用标签列表指定行和列标签。
切片对象
我们也可以使用以下格式的切片符号:
开始标签:停止标签
然而,与使用列表或字符串的切片符号相反,开始和停止标签都包含在我们的输出中:
带标签的切片对象
请注意行标签 3、4 和 5 是如何包含在我们的输出数据帧中的。还要注意 City、Colors Reported 和 Shape Reported 列是如何包含在内的,即使我们使用 slice 对象停止在 Shape Reported。记住,ufo.columns 返回了一个列表,其顺序为城市、报告的颜色、报告的形状、州和时间。我们包括从城市标签到形状报告标签的所有内容,其中还包括颜色报告标签。
布尔数组
最后,我们可以使用布尔值数组**。然而,这个布尔值数组的长度必须与我们正在使用的轴的长度相同。例如,根据我们上面使用的 shape 属性,我们的 ufo dataframe 的形状为 (18241,5) ,意味着它有 18241 行 5 列。**
所以如果我们想用一个布尔数组来指定我们的行,那么它也需要有 18241 个元素的长度。如果我们想使用一个布尔数组来指定列,它需要有 5 个元素的长度。创建这个布尔数组最常见的方法是使用一个 条件,它创建一个可对齐的布尔序列 。
例如,假设我们只想选择包含 Abilene 的行作为 ufo 目击事件发生的城市。我们可以从以下情况开始:
ufo.City == ‘Abilene’
注意这是如何返回长度为 18241 并且由布尔值(真或假)组成的熊猫序列(或类似数组的对象)的。这是我们需要能够使用这个布尔数组使用 loc 方法指定我们的行的值的确切数目!
想象我们将这一系列真值和假值覆盖在 ufo 数据帧的索引上。只要这个序列中有一个真的布尔值,这个特定的行将被选中并显示在我们的数据帧中。
我们可以在上面看到,第一个真值出现在第 4 行,标签为 3,这意味着一旦我们用 loc 方法使用这个布尔值数组,我们将看到的第一行是标签为 3 的行(或 ufo 数据帧中的第 4 行)。
ufo.loc[ufo.City == ‘Abilene’, :]
阿比林的不明飞行物目击事件
这正是我们所看到的!我们已经使用长度等于原始数据帧中的行数的布尔值数组指定了我们想要的行。
可调用函数
我们还可以向 loc 方法传递一个可调用的函数来指定行和/或列。该函数将接受一个参数,该参数将是调用序列或数据帧**,并且必须返回一个用于索引的**有效输出,该输出可以是上面我们讨论过的任何方法(比如布尔数组)。****
例如,我们可以使用 lambda 函数获得与上面相同的数据帧,其中包括所有以 Abilene 为城市的行:
ufo.loc[lambda df: df.City == ‘Abilene’, :]
我们使用带有一个参数的 lambda 函数来指定行。传递给 lambda 函数的参数是 ufo 数据帧(被索引的数据帧)。lambda 函数返回一个布尔数组,然后用于指定行。
** **
结合这些方法
记住,我们可以组合这些不同的指定行和列的方式,这意味着我们可以对行使用一种索引方式,对列使用不同的方式。例如:
ufo.loc[ufo.City == ‘Abilene’, ‘City’:’State’]
请注意我们如何使用返回布尔值数组的条件来指定行,以及如何使用标签来指定列的切片对象。
如果你喜欢阅读这样的故事,并想支持我成为一名作家,考虑注册成为一名媒体成员。每月 5 美元,你可以无限制地阅读媒体上的故事。如果你注册使用我的 链接 ,我会赚一小笔佣金。
**https://lmatalka90.medium.com/membership **
结论
在本教程中,我们学习了如何使用 loc 方法索引熊猫数据帧。我们了解了 loc 方法是如何既是 series 方法又是 dataframe 方法,并可用于在索引 dataframe 时指定行和列。我们研究了指定行和列的不同方法,包括标签的单个或列表、带标签的切片对象、通常使用条件创建的布尔数组,以及返回任何上述内容的可调用函数**。最后,我们学习了如何将这些不同的方法结合起来,同时用于索引熊猫的数据帧。**
如何用 R 在 Power BI 中使用机器学习
用人工智能赋能您的仪表盘
微软最近一直在大力调整开源技术,并将人工智能技术融入其产品中,Power BI 也包括在这一计划中。Power BI 是当今构建仪表板的主要工具之一,微软每天都在增加其开发能力和灵活性。
为了使仪表板的开发可行,Power BI 有几个用于数据处理的功能,其中最重要的一个是与 R 的集成工具,以及最近与 Python 的集成。使用 R 和 Python 开发语言的选项在 BI 工具中开辟了大量的可能性,其中一个可能性是使用机器学习工具并直接在 Power BI 中构建模型。
在本文中,我将从以下主题开始,逐步讲解如何使用 R 语言在 PowerBI 中直接使用机器学习模型进行训练和预测:
- 安装依赖项
- 分析数据
- 动手——代码
- 结果
- 结论
1.安装依赖项
第一步是在您的机器上安装 R Studio,因为开发将使用 R 语言。尽管 PowerBI 与 R 语言有着本机集成,但它要求用户在机器上安装 R 包。
可以通过这个链接下载:https://www.rstudio.com/products/rstudio/download/
安装完成后,您必须打开 R studio 并安装依赖库。
- 脱字号
- 数据集
- monomvn
要在 R 中安装软件包,R-bloggers 网站有一个很棒的教程,教你如何在 R Studio 中安装和加载软件包。链接:https://www . r-bloggers . com/2013/01/how-to-install-packages-on-r-screens/
分析数据
该数据集是从 kaggle.com 网站获得的,包括圣保罗一所大学的啤酒消费数据,以及每天的最低、最高和平均温度以及体积降雨量。
为了添加另一个重要的功能,我创建了一个名为“周末”的列,指示当天是周六还是周日,因为周末的消费量较高,所以我可以考虑周五,但对于这一时刻,我决定更保守一些。
数据集—作者
3-实践-代码
对于测试,我使用 monomvn 包(Bayesian Ridge Regression)建立了一个贝叶斯线性回归模型,以预测每天的啤酒消费数据(升),并通过 10 倍的交叉验证进行验证。
在本文中,我不会深入讨论这个模型及其结果,因为我们的目标是更多地关注与 Power BI 的集成,而不是建模。
代码的第一部分导入库
library(caret)
library(datasets)
library(monomvn)
在我们将 Power BI 数据作为数据集导入后,在 R
mydata <- dataset
mydata <- data.frame(mydata)
有了它,我们就可以创建模型并进行预测。在这种情况下,我没有定义测试数据集,我只是使用带有 CV10 验证的训练数据集来简要分析训练指标。
fitControl <- trainControl(
method = "repeatedcv",
number = 10,
repeats = 10)
lmFit <- train(mydata[1:365, 2:6],mydata[1:365,7], method ='bridge',trControl = fitControl)
predictedValues <- predict(lmFit,newdata = mydata[, 2:6])
最后,我们用模型预测生成的值在 PowerBI 数据集中创建了一个新列。
mydata$PredictedValues <- predictedValues
完整代码
library(caret)
library(datasets)
library(monomvn)
mydata <- dataset
mydata <- data.frame(mydata)
fitControl <- trainControl(
method = "repeatedcv",
number = 10,
repeats = 10)
lmFit <- train(mydata[1:365, 2:6],mydata[1:365,7], method ='bridge',trControl = fitControl)
predictedValues <- predict(lmFit,newdata = mydata[, 2:6])
mydata$PredictedValues <- predictedValues
4 —结果
下面是包含真实值、预测值和误差(%)的完整数据集。训练的平均误差为 7.82% ,最大 20.23%,最小 0.03%
预测—作者
下面是一个图表,黑色表示真实数据,红色表示预测数据,蓝色表示整个测试期间的误差。
真实 x 预测—作者
4.1-与温度的相关性
当绘制啤酒消费量(黑色/红色)与温度(蓝色)的关系图时,我们看到消费量很好地遵循了月份之间的温度变化,包括温度变化的“微观”(每日)变化和“宏观”(趋势)。我们看到温度的上升导致了更大的消费,例如在一年的岁末年初,这是夏天,而冬天较低的温度导致了啤酒消费的减少。
预测和温度—作者
4.2.真实数据和预测之间的相关性
在实际值和模型预测值之间应用相关性,我们会得到集中在黑色虚线(下图)中的理想数据,在这种情况下,预测数据将等于实际数据。通过制作这个相关图,我们可以看到模型预测的分散程度,以及预测的集中程度是被低估还是被高估。
当分析相关图时,我们看到初始模型的离差没有那么高,平均变化为 7.8%,如前所述。当分析数据的集中度时,我们看到模型在预测值大于或小于真实值之间变化,但在大多数情况下,模型略微高估了消费数据,预测的消费高于真实值。
预测 x 真实数据相关性—作者
4.3 测试— 2018 年数据
在用 2015 年的数据训练模型之后,我试图获得用于推断的数据,并获得了 2018 年圣保罗市的温度和降雨量数据的数据集。
下面要注意的是,2018 年数据中的推断值显示了与真实数据相同的模式,随着温度的降低,年中的消费量减少,周末出现峰值。
2018 年测试数据预测—作者
4.3.1 温度相关性
然后用蓝色标出的数值和温度一起证明了消费和温度之间的相关性,并在推断数据中展示了其全年的动态。
2018 年温度测试数据预测—作者
4.3.2 周末的季节性
解释季节性周期的一种方式是周末啤酒消费量的增加,我们可以在平均温度的消费量图表下方看到,黑色条代表周末、周六和周日。
与周末的相关性(测试)—作者
这种季节性也发生在实际数据中,当我们绘制 2015 年的实际消费时,高消费模式在周末重复出现,这表明该模型尽管简单,但却很好地适应了数据动态。
与周末的相关性(火车)—作者
5.结论
Power BI 作为一种图形工具,除了能够同时呈现数据库本身的探索性视图之外,还提供了从机器学习模型输出开发分析可视化的巨大多功能性和速度。对于在分析领域工作的开发人员来说,将机器学习模型的功能整合到 BI 工具中无疑是一项重大进步,PowerBI 以简单实用的方式带来了这一功能。
最后,关于这篇文章或机器学习、PowerBI、R、Python 等话题的任何问题或建议,请随时在 LinkedIn 上联系我:https://www.linkedin.com/in/octavio-b-santiago/
如何使用 Matplotlib 绘制对象检测数据集中的样本
为目标检测绘图
关于如何使用 Matplotlib 为对象检测任务绘制图形的简短教程
帕特里克·托马索在 Unsplash 上的照片
可视化数据集中的样本对于有效探索机器学习任务至关重要。它提供了关键的见解,帮助您从模型架构、数据扩充等几个可用选项中缩小选择范围。与分类或回归任务相比,绘制来自对象检测数据集的样本更加棘手。
下面是一个简短的教程,介绍如何使用 Matplotlib 库为对象检测样本创建高质量的绘图。
下载数据集和其他文件
我们将使用 Yolov5 PyTorch 版本的国际象棋数据集,你可以从 https://public.roboflow.com/object-detection/chess-full/23下载。我们还将使用上一篇文章“使用对象检测数据集在 PyTorch 中创建数据集管道的一般方法”中的数据帧。
如果你还没有这样做,请读一读。以下是数据框的链接。
- https://raw . githubusercontent . com/varun 9213/Blog _ machine _ learing/main/Data _ Blog _ 1/train _ FD . CSV
- https://raw . githubusercontent . com/varun 9213/Blog _ machine _ learing/main/Data _ Blog _ 1/valid _ FD . CSV
- https://raw . githubusercontent . com/varun 9213/Blog _ machine _ learing/main/Data _ Blog _ 1/test _ FD . CSV
你可以从这里下载完整的笔记本教程。
现在,让我们开始编码吧!!
首先,导入教程所需的所有库和文件
!pip install fastai --upgrade
from fastai import *
from fastai.vision.all import *
from fastai.imports import *
import cv2, os
from matplotlib import patches, text, patheffectsroot = Path("/content/data")!curl -L "https://public.roboflow.com/ds/pK9BobmV9A?key=G9IUWXkCZA" > roboflow.zip; unzip roboflow.zip; rm roboflow.ziptrain_df = pd.read_csv
("[https://raw.githubusercontent.com/Varun9213/Blog_machine_learing/main/Data_blog_1/train_fd.csv](https://raw.githubusercontent.com/Varun9213/Blog_machine_learing/main/Data_blog_1/train_fd.csv)")valid_df = pd.read_csv
("[https://raw.githubusercontent.com/Varun9213/Blog_machine_learing/main/Data_blog_1/valid_fd.csv](https://raw.githubusercontent.com/Varun9213/Blog_machine_learing/main/Data_blog_1/valid_fd.csv)")test_df = pd.read_csv
("[https://raw.githubusercontent.com/Varun9213/Blog_machine_learing/main/Data_blog_1/test_fd.csv](https://raw.githubusercontent.com/Varun9213/Blog_machine_learing/main/Data_blog_1/test_fd.csv)")train_df.head()
作者图片
履行
让我们先来看看我们希望最终的剧情是怎样的。
作者图片
好了,现在我们心中有了一个明确的目标,让我们看看实现。
如果你读过我以前的帖子,我喜欢把一个问题分解成更小的部分,所以,让我们从绘制数据集的图像开始,只为图像中的一个对象绘制一个边界框。
train_images = get_image_files(root/"train")fig, ax = plt.subplots()
path_img = train_images[0]img = cv2.imread(str(path_img))
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)ax.xaxis.tick_top()
ax.imshow(img)
作者图片
注*get _ image _ files()*是一个 fastai 函数,返回父目录下所有图片的路径列表,可以访问https://docs.fast.ai/进一步探索库。
def get_target_ds(name, df):
rows = df[df["name"] == name[:-4]]
return rows["class"].values, rows[bboxes_cols].valuesbboxes_cols = ["center_x", "center_y", "width", "height"]
labels, boxes = get_target_ds(path_img.name, train_df)x = (boxes[0][0] - boxes[0][2]*0.5)*img.shape[1]
y = (boxes[0][1] - boxes[0][3]*0.5)*img.shape[0]
w = boxes[0][2] * img.shape[1]
h = boxes[0][3] * img.shape[0]fig, ax = plt.subplots(figsize = (6,9))ax.xaxis.tick_top()
ax.imshow(img)
ax.add_patch(patches.Rectangle((x,y),w,h, fill=False, edgecolor='red', lw=2))
作者图片
好了,现在让我们来分解这个代码块,看看这个盒子到底是如何出现在图像中的。
ax.add_patch()是一个 Matplotlib 方法,用于在绘图上绘制一个图形或一个补丁,我们在这里使用它来绘制一个由边界框坐标给出的矩形。
补丁。Rectangle()是我们传递给 add_patch()方法的参数,该方法随后将矩形绘制到图像上。注意,需要传递给函数的边界框的格式是 ((x_min,y_min),width,height) 然而,数据集为边界框提供的格式是不同的,必须转换成所需的格式。
当我们选择数据集的 Yolo 版本时,边界框的格式是*【x _ center,y_center,width,height】。N* 注意,这里的坐标已经标准化,必须根据图像的分辨率按比例缩小。
来源:物体检测的白蛋白文档
注意您可以在此处找到的相册文档中的图像向您展示了坐标系在大多数绘图和增强库中是如何工作的。与我们通常使用的坐标系相比,它是上下颠倒的。它还向您展示了边界框的坐标是如何以各种格式表示的。
最后,让我们给盒子加上相应的标签。
fig, ax = plt.subplots(figsize = (6,9))
ax.xaxis.tick_top()
ax.imshow(img)ax.add_patch(patches.Rectangle((x,y),w,h, fill=False, edgecolor='red', lw=2))**ax.text(x,(y-50),str(labels[0]),verticalalignment='top',
color='white',fontsize=10,weight='bold')**
作者图片
这个图中似乎还缺少一些东西,我们的目标图比我们刚刚创建的要干净得多。
不同之处在于我们的目标图缺少轮廓,让我们添加轮廓以获得更清晰的图。
fig, ax = plt.subplots(figsize = (6,9))
ax.xaxis.tick_top()
ax.imshow(img)ax.add_patch(patches.Rectangle((x,y),w,h, fill=False, edgecolor='red', lw=2))**.set_path_effects([patheffects.Stroke(linewidth=4, foreground='black'), patheffects.Normal()])**ax.text(x,(y-50),str(labels[0]),verticalalignment='top',
color='white',fontsize=10,weight='bold')**.set_path_effects([patheffects.Stroke(linewidth=4, foreground='black'), patheffects.Normal()])**
作者图片
是啊!这个看起来好多了。最后,总结一下,让我们为每一步创建函数,并在图像中用相应的标签画出所有的方框。同样,让我们创建一个函数来为多个样本生成图。
def get_bb(bboxes, img): boxes = bboxes.copy()
boxes[:,0] = (boxes[:,0] - boxes[:,2]*0.5)*img.shape[1]
boxes[:,1] = (boxes[:,1] - boxes[:,3]*0.5)*img.shape[0]
boxes[:,2] = boxes[:,2] * img.shape[1]
boxes[:,3] = boxes[:,3] * img.shape[0] if boxes.shape[0] == 1 : return boxes
return np.squeeze(boxes)def img_show(img, ax = None, figsize=(7,11)):
if ax is None: fig, ax = plt.subplots(figsize=figsize)
ax.xaxis.tick_top()
ax.imshow(img) return axdef draw_outline(obj):
obj.set_path_effects([patheffects.Stroke(linewidth=4, foreground='black'), patheffects.Normal()])def draw_box(img, ax, bb):
patch = ax.add_patch(patches.Rectangle((bb[0],bb[1]), bb[2], bb[3], fill=False, edgecolor='red', lw=2))
draw_outline(patch)def draw_text(ax, bb, txt, disp):
text = ax.text(bb[0],(bb[1]-disp),txt,verticalalignment='top'
,color='white',fontsize=10,weight='bold')
draw_outline(text)def plot_sample(img, bboxes, labels, ax=None, figsize=(7,11)):
bb = get_bb(bboxes, img)
ax = img_show(img, ax=ax)
for i in range(len(bboxes)):
draw_box(img,ax,bb[i])
draw_text(ax, bb[i], str(labels[i]), img.shape[0]*0.05)def multiplot(dim:tuple, df, images, idxs = None, figsize=(18,10)):
if idxs is None: idxs = np.random.randint(0, len(images)-1, dim[0]*dim[1])
fig, ax = plt.subplots(dim[0],dim[1], figsize=figsize)
plt.subplots_adjust(wspace=0.1, hspace=0)
fig.tight_layout()
for i in range(dim[0]):
for j in range(dim[1]):
img = images[idxs[(i+1)*j]]
labels, bboxes = get_target_ds(img.name, df)
img = cv2.imread(str(img), cv2.IMREAD_UNCHANGED)
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
plot_sample(img, bboxes, labels, ax=ax[i][j]) plot_sample(img, boxes, labels)
作者图片
multiplot((2,4), train_df, train_images, figsize=(20,8))
作者图片
结论
在完成一个机器学习项目后,你意识到你的大部分编码时间都花在了预处理、EDA、绘制样本、增强和构建管道上。从长远来看,熟悉打印库可以节省大量时间。
我希望这篇文章能帮助你更有效地使用这些绘图功能。我将在下一篇文章中讨论数据增强,在我看来,如果使用得当,它会给你带来最佳的性能。
敬请关注,谢谢!!
如何使用元数据让您的数据堆栈面向未来
消除元数据孤岛、分散的专业知识和长期的数据管理复杂性。
当构建或重新思考他们的数据堆栈时,组织通常以工具优先的心态来完成任务。我们最近查看了流行的工具,如 Fivetran、dbt、Airflow 和 Looker on Snowflake,并注意到许多参考架构和实施计划都过于复杂。不一定要这样。
我们推荐了一个简化概念框架来考虑构建数据堆栈。我们相信,让元数据驱动堆栈提供了清晰的关注点分离,并鼓励简单性。此外,它还帮助数据团队进行战略性思考,并使用正确的工具来满足他们的数据战略需求。
我们的三层框架如下所示:
- 第 1 层:数据流
- 第 2 层:元数据
- 第 3 层:开发运维工具
图片由 Datacoral 提供。
让我们更深入地了解为什么元数据对于构建可伸缩和可维护的数据堆栈如此重要;为什么元数据是大多数公司事后才想到的;以及如何采用整体平台方法来满足数据目标。
缺少元数据
全球各地的公司都在构建数据堆栈,并在数据团队上投入巨资,这导致了用于管理数据处理、状态管理和配置的工具激增。这些工具非常适合构建接收和转换数据的管道,但是它们最终会产生元数据孤岛。这些孤岛导致不同工具之间的阻抗不匹配,从而导致缺乏内聚智能。通过将元数据从一个工具转换到另一个工具,需要大量粘合代码来集成工具。
为什么?在很大程度上,数据堆栈是基于普遍的业务需求零碎构建的。当您是一家拥有少量数据的小公司时,很难证明采用平台方法来构建数据架构是正确的。但是,随着公司的发展,工具越来越多,导致数据堆栈的视图支离破碎,复杂性呈指数级增长。
业务领导需要有价值的见解,而数据团队需要稳定的基础设施,以支持处理数百万条记录的数据管道。业务领导希望根据最新数据做出最新决策,而数据团队则担心模式更改对转型工作的连锁影响。我们可以看到数据堆栈的目标会有多快出现分歧。
当业务洞察力、稳定的基础设施和干净的元数据结合在一起时,有意义的价值可以在数据堆栈中快速轻松地获得。提供的图像是一个库存图像。
有一种元数据优先的方法来解决更广泛的行业开始看到的问题,但还不能完全解决。最近几个月,我们已经看到几个以元数据为中心的产品上市,这非常令人鼓舞。但是,这些产品中的大多数解决了更简单的短期问题,如数据源、仪表板、机器学习作业和电子表格的编目,以支持搜索、审计和合规性。这些元数据搜索工具真的很有用,但是它们并没有解决公司真正需要有一个一致堆栈的平台方法。
今天的元数据工具集中了来自不同系统的元数据,并试图提供一个单一窗口来显示整个堆栈中发生的事情。但是,不同工具之间的元数据并不一致,因为每个工具的构建方式不同,这意味着需要做更多的工作来标准化元数据。用元数据标准化玩打地鼠很快就会过时。数据从业者经常发现遗留的元数据系统并不总是最新的,所以工具本身随着对元数据的关注而变得陈旧。然后,元数据系统的主要用例变成了审计和遵从,只有当审计和遵从需求实际出现时才这样做。
如果你在构建完你的数据栈的其余部分后,还在考虑元数据系统,从某种意义上来说,你还没有选择构建整体数据栈的道路。**
当前的元数据解决方案最终强化了工具优先的思想。我们认为数据团队应该以一种非常不同的方式考虑元数据——使元数据成为数据管道关键路径的一部分。
我们认为元数据需要驱动整个系统。元数据层是关键,但通常是在实际数据堆栈本身之后添加的。元数据不是设计数据管道的关键路径的一部分,因此缺乏数据堆栈内的长期可预测性。
为什么是元数据?
我们今天所知的数据工程主要由复杂的集成和由各种工具驱动的定制代码组成。正如我们刚刚讨论的,有利的一面是数据连接器和转换工具的可用性和商品化。它们使得统一原始数据集和计算 KPIs 指标变得前所未有的简单。缺点是缺乏集成和端到端的智能。更重要的是,这些缺点增加了为应用程序、AI/ML 模型和业务领导者提供有意义的见解的难度。
数据堆栈最终会提供数据,但不能保证数据的质量。
如果工具是分散的,元数据是孤立的,团队最终会拥有分散的专业知识。少数人成为了 Fivetran 专家,一些人成为了 dbt 奇才,还有一些人成为了气流大师。很快,数据堆栈就产生了“一个工具导致另一个工具”的思维模式。如果没有工具之间的本机集成,就没有显示数据管道健康状况的统一端到端视图,也没有对模式依赖关系的整体理解。当出现数据问题时会发生什么?正如我们在数据框架文章中所探讨的,在这种情况下,调试流程相当复杂——在分析师、分析工程师和数据工程师之间,他们需要找到正确的工具,然后才能排除错误。
这些是关键的日常问题,增加了从数据堆栈中获取价值的摩擦。如果没有将数据堆栈作为一个平台的长期战略观点,可能会出现更深层次的问题。
数据调试流程。摘自《走向数据科学》文章 构建现代数据堆栈时要牢记的 3 件事。图片由 Datacoral 提供。
高价值的平台不是偶然建立起来的——它们需要一些前瞻性思维和前期投资架构的意愿。如果开局良好,该平台将允许在有意义的约束下实现有机增长。我们的主张是,首先投资于元数据对于建立一个可持续的数据平台至关重要。了解和规划元数据使得编排高效且可伸缩。DevOps 创作工具开发变得更加容易。预防性地解决了数据和代码冲突。转换变更和编排逻辑保持同步。
我们如何培养一种元数据思维模式?我们从数据管道开始。
数据管道给数据堆栈带来了活力
数据管道没有得到太多的爱(尽管有相反的努力),但是它们给数据栈带来了生命。从表面上看,它们似乎足够简单,甚至非技术人员也能理解它们的工作。在转换数据以生成洞察之前,数据从源移动到数据仓库,管道的效用立即为人所知。
如果一个管道给栈带来了生命,那么管道元数据就是栈的脉冲。我们将它视为需要管理并始终保持“干净”的命脉。**保持整洁的唯一方法是将元数据层放在数据堆栈成功的关键路径上。**因此,我们必须将元数据定位为管道的主要驱动力,否则它将成为另一个信息源。
通过跟踪和理解元数据,我们保持数据干净。清洁度允许系统无问题运行,并提供自我记录和可观察性。其他工具可以更轻松地使用干净的元数据。如果没有干净的元数据,很难理解数据的质量和新鲜度。
管道元数据是关于管道本身的所有信息,如配置、运行时状态。图片由阿帕奇气流公司提供。
今天的数据管道是使用 ETL 系统构建的,ETL 系统通常是工作流管理器。这些系统可以运行相互依赖的作业,并允许工程师将这些作业和依赖关系手工编码到管道中。
换句话说,数据管道由移动和转换数据的作业组成。一个作业的输出数据被用作另一个作业的输入数据,使得这些作业相互依赖。数据管道本质上变成了图,更具体地说,是 Dag(有向无环图),其中节点是作业,边表示作业之间的依赖关系。当执行数据管道时,我们必须确保作业以正确的依赖顺序运行。这种基于依赖性的执行是编排系统的工作。
工作流程管理
通常,ETL 工具是围绕编排系统构建的;气流就是一个很好的例子。数据工程师在工作流管理器中编写定义这些作业及其依赖关系的管道代码。工作流管理器解释该管道代码,以正确的顺序执行作业。数据工程师负责确保所有的数据依赖关系都正确地转换为工作流管理器要解释的作业依赖关系。
这些工作流管理器被提供关于作业及其依赖性的配置,并生成关于作业执行的元数据。但是对于一个分析师来说,数据依赖比工作依赖更重要。数据管道的元数据由以下内容组成:
- 连接器配置:如何从源中检索数据(以增量方式或快照方式),定期获取更改的行还是连续读取更改日志。
- 批处理配置:获取数据或刷新转换的频率
- 沿袭:不同的转换如何依赖来自连接器和其他转换的数据。此外,出版商如何依赖底层数据
- 管道运行时元数据:成功处理步骤、失败、新鲜度检查结果、用作管道中任务障碍的数据质量度量、历史同步的历史以及重新处理动作的日志
- 模式变化:输入数据和相应转换的模式如何随时间变化的历史。
通常,这些信息的大部分隐藏在作业定义本身或 ETL 系统或工作流管理器管理的元数据中,因此这些信息不会清楚地暴露给其他应用程序。通常需要集成到不同的系统中,然后该系统将这些元数据用于其他应用程序。
孤立管道元数据的挑战
在我们的简化概念框架文章中,我们回顾了一个示例数据堆栈,包括:
*谱系图显示了可能受数据转换作业影响的模式依赖关系。*图片由 Datacoral 提供。
- 用于摄取的五川
- 用于转换的 dbt
- 视觉化的观察者
- 存储用雪花
- 用于编排的气流
在这个例子中,气流是工作流管理器。触发 dbt 运行的任务被调度为在隐含理解 Fivetran 通常何时引入数据的情况下运行。
我们将使用一个常见的场景,Fivetran 每天从 Salesforce 和 MySQL 提取数据,并将其加载到 Snowflake。dbt 模型将来自这两个来源的数据结合起来,计算出每日报告。通常,Fivetran 在每天凌晨 1 点左右引入数据,因此为了安全起见,Airflow 可以将 dbt 安排在凌晨 3 点运行。大多数时候,这是有效的。
但是有一天,由于 MySQL 超载,从 MySQL 中提取数据有延迟,所以 Fivetran 被延迟了,直到凌晨 3:05 才导入数据。到那时,Airflow 已经触发了 dbt 运行,这意味着 Salesforce 数据是最新的,而 MySQL 数据不是,因此模型刷新会导致不正确的数据。
现在想象一下,Fivetran 每小时递增地接收 MySQL 数据,但是每天接收一次 Salesforce 数据。您马上会遇到一个问题,那就是必须等待全天 24 小时的 MySQL 更新和 Salesforce 的每日更新。这个问题通过在气流中编写几个传感器来解决,这些传感器等待 24 次 MySQL 摄取和一天的 Salesforce 摄取。当传感器满意时,一个任务触发 dbt 运行。
这种类型的设置会带来一些挑战。我们称之为围绕元数据的“裂脑”:
- dbt 为转换构建自己的 DAG,该 DAG 的操作复杂度与 Airflow 不同,air flow 是一个复杂的工作流管理器。dbt 更像是一个数据建模工具,而不是像 Airflow 一样的成熟的工作流管理器,因此很难找出如何在不重新计算整个 DAG 的情况下进行有效的再处理。
- Airflow 没有任何关于数据依赖性的信息,因为这些信息封装在 dbt 中。这意味着当 dbt 中的查询发生变化时,比如当 dbt 中的查询连接三个表而不是两个表时,作业依赖关系不会自动更新。因此,当三个表中只有两个被更新时,Airflow 可能会触发 dbt 运行,从而导致信息丢失。
- Airflow 没有关于 Fivetran 的数据加载器的信息。当 Fivetran 加载出现延迟时,或者如果从 Fivetran 加载的数据不完整,Airflow 不会察觉,它只会触发 dbt 运行。
- 要将每天的数据获取更改为每小时的数据获取,团队必须更改 Airflow 中的代码,以适当地处理下游的作业依赖性。
这些问题没有一个是不可解决的,但是它们需要在 Airflow 中实现大量的代码,以便理解数据依赖性并添加传感器来保证数据质量,等等。但是,即使在气流中设置了代码,数据管道的持续维护也需要昂贵的工程时间。对查询的每次更改都会导致更新气流管道的工作。而且涉及多个系统的事实意味着调试问题要花很长时间。
孤岛式管道元数据系统中出现的典型问题包括:
- 元数据被孤立在不同的系统中,导致了“大脑分裂”的问题此外,不同系统之间的编排不一致,如 fivetran 和 dbt。
- 重要的责任在于一个外部的工作流管理器,如气流来协调过程。这需要大量的编码来正确设置。
- 像模式更改、历史同步和重新处理这样的持续操作很难在不同的系统之间进行协调。
- 创作越来越困难,因为很难预见变化的影响
- 调试堆栈中的问题很困难,因为必须在多个系统之间跳转。
元数据优先设计的蓝图
我们提出了一种架构,其中我们首先定义元数据本身。我们首先对可能存在的元数据进行建模,而不是担心数据管道功能的不同部分是如何工作的(比如如何从不同的源接收数据,如何转换数据本身,以及如何编排整个管道)。
一旦我们定义了元数据本身,我们就可以构建由元数据驱动的编排。这个管道元数据从 ETL 系统的外部开始。ETL 系统使用这些元数据进行操作,它自己的元数据非常少。然后,其余的用例可以依赖元数据来执行它们的操作。
元数据层本身可以被认为有四个组成部分:
- 用于审计和可观察性的纯元数据应用程序
- 用于访问控制的增强元数据
- 模式、沿袭和统计的管道元数据
- 管弦乐编曲
从元数据开始,围绕它构建一切。图片由 Datacoral 提供。
当确定编排和堆栈的每个其他部分都依赖于元数据时,可以用一致的方式设计数据堆栈的所有组件。使用孤立的方法,只需生成元数据并将其捕获到元数据系统中。这使得元数据成为数据堆栈中的二等公民,元数据工具只对审计和合规性问题有用,并且只在需要时有用。
元数据优先的思想意味着编排可以自动协调模式更改、历史同步和重新处理如何在数据流的不同步骤中传播。正如我们在上面的例子中所展示的,孤立的元数据给编排和转换工作带来了许多不必要的挑战,而这些挑战本来可以通过提前规划元数据来解决。
Datacoral 的元数据方法
Datacoral 的数据管道平台采用元数据优先的方法构建。我们的优势显而易见,因为我们的平台帮助我们只见树木不见森林。我们只需监控元数据,就可以完全透明地输入和输出数据。事实上,我们提供了自动模式发现和变更传播。工程师和分析师可以轻松地为转换编写 SQL 查询,并为视图设置数据间隔,而不用担心冲突和数据完整性问题。一旦我们开始合作,我们就帮助我们的客户变得重视元数据。
【Datacoral 干净元数据的下一级可观察性提供了自动谱系捕获,可全面洞察数据管道的每一步。图片由 Datacoral 提供。
我们不仅捕获数据沿袭,而且我们的编排实际上利用数据沿袭来确保仅当上游数据更新时才执行转换。我们的连接器在从任何来源接收每一条新数据时都会发布元数据。我们的编排检查谱系图,以根据所接收的每一条新数据来判断哪些转换可以运行。当转换所依赖的所有数据都被更新时,编排触发转换。并且,当转换运行时,它再次触发编排系统更新沿袭图中的依赖关系。不仅如此,甚至模式变化也通过谱系图传播。我们的客户不会遇到数据错误,因为我们的平台带有内置的数据质量检查,当每条新数据进入数据堆栈时都会触发该检查。Datacoral 能够确保仓库中的所有数据都是一致的。
如果没有干净的元数据,或者没有将元数据放在数据管道的关键路径中,所有这些都是不可能的。我们的平台自动理解数据依赖。我们自动进行数据质量检查,并提供内置的可观察性,因为所有的元数据都已经可用。我们的平台和连接器可以扩展到任何规模,这要归功于我们屡获殊荣的无服务器架构,它提供了简单性和有机增长的能力。
我们的平台不是构建数据堆栈的零敲碎打的方法。因为我们对元数据和现代公司的数据期望有一个整体的理解,所以我们不会面临许多团队在按需工具集合方面遇到的问题。集成和一致的设计从第一天起就可用,我们的客户不需要大量的定制代码来集成不同的系统。
我们在行业中看到的最大挑战是涉及的系统数量。我们提供了一个模型,在这个模型中,我们从配置和状态管理元数据的清晰定义开始,然后构建数据处理和编排。我们的服务受到管理,因此我们的客户不必成为工具和开发专家。Datacoral 的管道将接收、转换、模型构建和发布服务与堆栈中每个变更的端到端视图连接起来。
结论
由工具组合构建的数据堆栈不可避免地会导致孤立的元数据、分散的专业知识和长期的数据管理复杂性。我们不仅在与新客户的日常交往中看到这一点,而且解决这些问题的新工具也在不断涌现。工具优先的思想需要大量的定制代码,并且不能保证数据管道和元数据的端到端可见性。即使像 Airflow 这样可靠的工作流管理解决方案也需要持续的代码维护和大量的工程投资。
当元数据作为数据管道的关键路径的一部分进行管理时,可以实现显著的短期和长期优势。像 Datacoral 这样的工具利用干净的元数据来实现自动的沿袭捕获和模式更改传播。借助级联数据依赖关系的现成可视化,可以快速轻松地更改转换作业。团队能够更快地行动,他们可以花更多的时间处理数据,而不是被迫从事管道工作。
在 Datacoral,我们针对数据堆栈的简化概念框架并没有停留在元数据层。关于数据流和 devops 工具层,我们将在以后的文章中分享更多内容。干净的元数据为可扩展、可持续、可管理和低成本的数据堆栈打开了新的大门。在我们的下一篇文章中,我们将强调其中的一些优势。
我们希望这篇文章强调了在数据行业内围绕元数据首先需要什么,以及它在现代数据堆栈的所有级别上提供的好处进行对话的必要性。如果你对我们的元数据第一的论文有任何想法,给我们在 hello@datacoral.co 写信。
如何使用度量学习:嵌入是你所需要的
实践教程
找出常规分类和度量学习之间的区别,并通过我在 PyTorch 中实现的监督对比损失亲自尝试一下
Boitumelo Phetla 通过 unsplash
机器学习中最简单、最常见的任务之一就是分类。例如,在计算机视觉中,你希望能够微调普通卷积神经网络(CNN)的最后几层,以正确地将样本分类到某些类别(类)。然而,有几种根本不同的方法来实现这一点。
公制学习就是其中之一,今天我想和大家分享一下如何正确使用它。为了让事情变得实际,我们将看看监督的对比学习 (SupCon),这是对比学习的一部分,而对比学习又是度量学习的一部分,但稍后会有更多的介绍。
完整代码可以在 GitHub repo 中找到。
分类通常是如何完成的
在我们深入度量学习之前,首先了解分类任务通常是如何解决的,这实际上是一个好主意。今天实用计算机视觉最重要的想法之一是卷积神经网络,它们由两部分组成:编码器和头部(在这种情况下是分类器)。
哈桑通过神经蜂巢
首先,你拍摄一幅图像,并计算出一组能够捕捉该图像重要品质的特征。这是使用卷积和池操作完成的(这就是为什么它被称为卷积神经网络)。之后,您将这些特征分解到单个向量中,并使用常规的全连接神经网络来执行分类。在实践中,您采用一些在大型数据集(如 ImageNet)上预先训练的模型(例如 ResNet、DenseNet、EfficientNet 等),并在您的任务中对其进行微调(或者只是最后几层,或者是整个模型)。
然而,这里有几件事需要注意。首先,通常你只关心网络 FC 部分的输出。也就是说,您获取其输出,并将其提供给损失函数,以便保持模型学习。换句话说,您并不真正关心网络中间发生了什么(例如,编码器的功能)。其次,*(再次,通常)*你用一些基本的损失函数,比如交叉熵,来训练这整件事。
Kolluru via 媒介
为了对这个两步过程(编码器+ FC)有更好的直觉,你可以这样想:编码器将图像映射到一些高维空间(例如,在 ResNet18 的情况下,我们谈论的是 512 维,而对于 resnet 101–2048)。之后,FC 的目标是在这些代表样本的点之间画一条线,以便将它们映射到类。这两样东西是同时训练的。所以你试图优化特性,同时“在高维空间中画线”。
这种方法有什么问题?嗯,没什么,真的。它实际上工作得很好。但这并不意味着没有其他方法。
度量学习
现代机器学习中最有趣的想法之一(至少对我个人来说)被称为度量学习(或深度度量学习)。简而言之:如果我们不去研究 FC 层的输出,而是更仔细地研究编码器生成的特征,会怎么样呢?如果我们设法用一些损失函数来优化这些特征,而不是网络末端的逻辑,会怎么样?这实际上就是度量学习的内容:用编码器生成好的特征(嵌入)。
但是“好”是什么意思呢?如果你仔细想想,在计算机视觉中,你希望相似的图像有相似的特征,而不相似的图像有截然不同的特征。
监督对比学习
Prannay Khosla via arxiv
好吧,假设在度量学习中,我们只关心好的特征。但是有监督的对比学习是怎么回事呢?老实说,这种特定的方法没有什么特别的。这只是最近的一篇论文,提出了一些不错的技巧和一个有趣的两步方法:
- 训练一个好的编码器,能够为图像生成好的特征。
- 冻结编码器,添加一个 FC 层,然后就这样训练。
你可能想知道常规分类器训练有什么不同。不同的是,在常规训练中,你同时训练编码器和 FC。另一方面,在这里,你先训练一个像样的编码器,然后冻结(不再训练),只训练 FC。这种逻辑背后的直觉是,如果我们设法首先为图像生成真正好的特征,那么优化 FC 应该很容易(正如我们前面提到的,它的目标是优化分隔样本的线)。
培训过程的细节
下面我们来深挖一下中控实现的细节。
Prannay Khosla via arxiv
在检查训练回路之前,关于中控有一点你要了解的是正在训练的是什么模型。这很简单:编码器(如 ResNet、DenseNet、EffNet 等),但没有用于分类的常规 FC 层。
代替分类头,这里我们有一个投影头。投影头是 2 个 FC 层的序列,将编码器功能映射到一个更低的维度空间(通常为 128 维,您甚至可以在上图中看到该值)。使用投影头的原因是,模型学习 128 个精心选择的特征比学习来自编码器的所有几千个特征更容易。
好了,是时候最后检查一下训练循环了。
- 构建一批 N 个图像。与其他度量学习方法不同,您不需要太在意那些样本的选择。能拿多少就拿多少,剩下的由损耗来处理。
- 将这些图像通过网络成对转发,其中一对被构造为[ 增强(image_i) ,增强(image_i) ],得到嵌入。将它们正常化。
- 拿某个形象做锚。在批中查找同一类别的所有图像。把它们作为阳性样本。找出所有不同类别的图像。用它们做阴性样本。
- 将 SupCon 损失应用于标准化嵌入,使正样本彼此更接近,同时远离负样本。
- 训练完成后,删除投影头,并在编码器上添加 FC(就像在常规分类训练中一样)。冻结编码器,并微调 FC。
这里需要记住几件事。第一,训练完成后,去掉投影头更有利可图,在它之前使用特性。作者解释这一事实是由于头部信息的损失,因为我们降低了嵌入的大小。第二,扩充的选择很重要。作者提出了裁剪和颜色抖动的组合。第三,Supcon 一次处理批中的所有图像(因此,不需要构造成对或三元组)。并且批量中的图像越多,model 就越容易学习(因为中控有一个隐式正反硬挖掘的质量)。第四,实际上可以停在第四步。这意味着可以仅使用嵌入进行分类,而无需任何 FC 层。为此,计算所有训练样本的嵌入。然后,在验证时,为每个样本计算一个嵌入,将其与每个训练嵌入进行比较*(比较=余弦距离)*,取最相似的,取其类。
PyTorch 实现
PyTorch 中实际上有一个中控的半官方实现。不幸的是,它包含非常恼人的隐藏错误。其中最严重的一个:回购的创建者使用了自己的 ResNets 实现,由于其中的一些 bug,批量大小比常规 torchvision 模型低两倍。最重要的是,回购没有验证或可视化,所以你不知道什么时候停止训练。在我的报告中,我修正了所有这些问题,并为稳定的训练增加了更多的技巧。
更准确地说,在我的实现中,您可以访问:
- 用白蛋白增强
- Yaml 配置
- t-SNE 可视化
- 使用 AMI、NMI、mAP、precision_at_1 等指标进行两步验证(针对投影头前后的特征) PyTorch 指标学习。
- 指数移动平均线用于更稳定的训练,随机移动平均线用于更好的概括和公正的整体表现。
- 自动混合精确训练,以便能够以更大的批量(大约 2 倍)进行训练。
- 标签平滑丢失,以及第二阶段训练的lr finder(FC)。
- 支持 timm 模型和 jettify 优化器
- 固定种子以便使训练具有确定性。
- 基于验证、日志保存重量—到常规。txt 文件,以及 TensorBoard 日志,以备将来检查。
从包装盒上看,repo 支持 Cifar10 和 Cifar100。然而,添加自己的数据集是非常简单的。为了运行整个管道,请执行以下操作:
python train.py --config_name configs/train/train_supcon_resnet18_cifar10_stage1.ymlpython swa.py --config_name configs/train/swa_supcon_resnet18_cifar100_stage1.ymlpython train.py --config_name configs/train/train_supcon_resnet18_cifar10_stage2.ymlpython swa.py --config_name configs/train/swa_supcon_resnet18_cifar100_stage2.yml
在那之后,你可以在相应的朱庇特笔记本中检查 t-SNE 可视化。例如,对于 Cifar10 和 Cifar100,您可能会看到如下内容:
Cifar10 与 t-SNE,验证和培训(中控),图片由作者提供
具有 t-SNE 的 Cifar10,验证和训练(交叉熵),图片由作者提供
带有 t-SNE 的 Cifar100,验证和培训(中控),图片由作者提供
带有 t-SNE 的 Cifar100,验证和训练(交叉熵),图片由作者提供
最后的想法
度量学习是一件非常强大的事情。然而,我很难达到普通 CE/LabelSmoothing 所能提供的准确度。此外,它还可能在训练期间计算量大且不稳定。我在各种任务(分类、超出分布预测、推广到新类别等)上测试了中控和其他度量损失,使用中控这样的东西的优势还不清楚。
等等,那有什么意义?我个人认为这里有两点。首先,中控(和其他度量学习方法)仍然可以提供比 CE 更结构化的集群,因为它直接优化了该属性。第二——多一项技能/工具让你尝试一下还是很有好处的。因此,有可能使用一组更好的扩充或不同的数据集(可能使用更细粒度的类),SupCon 可以产生更好的结果,而不仅仅是与常规分类训练相当。
所以我们必须尝试和实验。没有免费的午餐,对吗?
如何在 AWS 上使用 MLflow 来更好地跟踪你的机器学习实验
再现性的一步
尼古拉斯·托马斯在 Unsplash 上拍摄的照片
我们都知道跟踪你的机器学习实验有多痛苦。
你训练了一堆不同风格的模型:随机森林,XGBoost,神经网络…
对于每个模型,您将探索一个范围的超参数。然后计算一些测试数据的性能指标**。**
有时,您通过添加或删除特征并再次重新训练您的模型来更改训练数据。
其他时候,你必须在团队中工作,并与其他数据科学家比较你的结果。
你如何管理这些实验,使它们易于追踪和重现?你如何比较你的结果?
MLflow 非常适合这些任务。我们将在这篇文章中看到如何在本地使用它,以及如何在 AWS 上设置它。
让我们来看看!
PS:想更深入的现场使用 MLflow,看看我的视频教程:*
PS*:你可以在我的 Github 上找到代码* 回购 。
什么是 MLflow
“ML flow 是一个开源平台,用来管理包括 实验 , 再现性 , 部署 ,以及一个 中央模型注册表。”—mlflow.org**
简而言之,MLflow 是一个包,您可以将其安装到 python 环境中,以便:
- 执行实验跟踪*(本文主题)***
- 以可复制的方式打包数据科学代码
- 部署模型
- 从开发到生产管理模型
MLflow 可与任何机器学习或深度学习框架集成,如 Scikit-learn、TensorFlow、PyTorch、h2o.ai、XGBoost 等。
此外,它也是云不可知的。你可以在任何地方运行*:AWS,谷歌云平台或者 Azure 机器学习。我们将在这篇文章中看到如何在 AWS 上设置它。等到最后看看是怎么做的😉。***
使用 MLFlow 跟踪的快速入门
MLflow tracking 是一个组件,可以帮助您非常轻松地记录您的机器学习实验。
它被组织成个实验并且每个实验被分成次运行。****
作者图片— MLflow 术语
实验的一个例子可以是“训练二元分类器”。在这种情况下,每次运行对应一个模型拟合。
在不同的运行中拟合不同的模型时,您可以使用 MLflow 来跟踪大量数据:**
- ****参数:您使用什么来调整您的模型(例如,n_estimators、max_depth、epochs、kernel_size、dropout、batch_size 等。)
- 您的模型的指标*(损失、AUC、MAE、MSEF1 分数,准确性,R 平方(r2))***
- 数据:你的模型在每次运行中使用的不同版本的数据。
- ****保存的模型:与每次运行相关的二进制输出(想想 pickle 文件)
- 代码产生的其他输出,如图像、CSV、文本、HTML
- 源:负责运行的脚本/笔记本文件名 git 提交
- ****标签和注释:个人或团队注释
如何在我的代码中使用 MLflow?
相当容易。
- 首先使用
pip
安装 mlflow:
*****pip install mlflow*****
- 导入
mlflow
并使用set_tracking_uri
方法设置 MLflow 存储每次运行结果的路径 - 用适当的名称调用
create_experiment
方法
为了能够在每次运行时记录您的参数、指标并保存您的模型,您必须通过将experiment_id
指定为参数,将您的代码包装在mlflow.start_run
上下文中。
***在这个上下文中,MLflow 创建一个具有唯一 id *(run_id)的运行。本次运行将等待任何要跟踪的信息。
- 使用
mlflow.log_param
方法可以保存参数 - 可以使用
mlflow.log_metric
方法保存指标 - 将(scikit-learn)模型保存为 artifcat 可以通过调用
mlflow.sklearn.log_model
以简单的方式完成
作者图片
好了,现在发生了什么?成绩在哪里?
MLflow 提供了一个很好的用户界面来可视化每次运行的结果,以及相互比较运行结果。
要启动 UI,在mlruns
目录的相同位置运行以下命令:
*****mlflow ui*****
这将在端口 5000 启动一个本地服务器。
在左边,你会看到实验。在右边,你会看到跑道。该表是交互式的,您可以对运行进行排序,通过指定查询进行搜索,甚至进行比较(在第二个视图中)。
作者图片— MLflow UI(运行)
点击一次跑步后,您将被重定向到另一个包含更多详细信息的页面。
度量、参数、持续时间、状态、git 提交:
作者图片— MLflow UI(运行详情)
以及模型输出和工件:
作者提供的图片— MLflow UI(运行输出)
在 AWS 上设置跟踪服务器
到目前为止,我们在本地使用 MLflow。如果我们想与其他同事合作并在团队中跟踪实验,这并不理想。
幸运的是,设置远程 MLflow 跟踪服务器非常容易。让我们看看这是怎么做到的。
在本节中,我将使用 AWS,因此,如果您想要重现这些步骤,请确保您拥有一个帐户。
1 —使用 MLflow 设置远程 EC2 机器
- 创建一个 IAM 用户。拿起
Access key ID
和Secret access key
凭证,将它们存放在安全的地方。我们以后会需要它们。 - 对于同一个用户,创建一个 s3 存储桶来存储未来的工件:给这个存储桶命名。我的是
mlflow-artifact-store-demo
,但是你不能摘。注意,您不需要为您的 bucket 定制配置(例如,它不需要是公共的) - 启动一个 EC2 实例:它不必很大。一个
t2.micro
有资格自由层做得很好 - 将此实例的安全组配置为接受端口 5000 和任何 IP 地址上的入站 HTTP 流量,以便可以从外部访问 MLflow 服务器
- 使用 SSH 连接到您的实例,并运行以下命令来安装 pip、pipenv 和 mlflow
***# install pip
sudo apt update
sudo apt install python3-pip# install
sudo pip3 install pipenv
sudo pip3 install virtualenv
export PATH=$PATH:/home/[your_user]/.local/bin/# install mlflow, awscli and boto3
pipenv install mlflow
pipenv install awscli
pipenv install boto3***
- 在 EC2 机器上,使用用户凭证配置 AWS,以便跟踪服务器可以访问 s3 并在 UI 上显示工件。
输入aws configure
,然后按照提示输入凭证 - 通过将主机定义为
0.0.0.0
并将--default-artifact-root
定义为 S3 桶,在 EC2 实例上启动一个 MLflow 服务器
*****mlflow server -h 0.0.0.0 --default-artifact-root s3://mlflow-artifact-store-demo*****
现在您的 EC2 机器已经正确配置好了。
2 —设置您的环境
要允许 MLflow 将运行从您的本地环境推送到 EC2 和 S3,您必须:
- 通过
pip install boto3
在本地安装 boto3 - 将 AWS 凭证设置为环境变量,以便 MLflow 拥有 S3 读写访问的适当权限
*****export AWS_ACCESS_KEY_ID=<your-aws-access-key-id>
export AWS_SECRET_ACCESS_KEY = <your-aws-secret-access-key>*****
- 将代码中的跟踪 URI 更改为HTTP://:5000****
现在一切都应该设置正确。
如果再次执行代码,运行将不再保存在本地,而是保存在远程跟踪服务器上,您可以通过访问 URL 来检查远程跟踪服务器。
作者提供的图片— MLflow UI(在远程服务器上)
快速笔记
- 像我们这样设置 MLflow 对于相对较小的团队中的小项目来说是很好的。然而,它还不能投入生产。事实上,您必须保护对 MLflow UI 的访问,并在 EC2 上创建一个服务,以便在启动时在后台启动 MLflow
- 你可以在 数据块 上使用 MLFlow 的托管版本
- MLflow 跟踪与您正在进行的工作类型无关,无论是 NLP、计算机视觉还是无监督学习。只要您有指标和参数要跟踪,MLFlow 是一个很好的选择。
感谢阅读!所有代码都可以在我的 Github repo 上获得。
如何使用蒙特卡罗模拟帮助决策
使用蒙特卡罗模拟来做出现实生活中的决策
来源:https://unsplash.com/photos/vBWsG91aR_U
最近,我面临着一个非常困难的决定。出于不同的原因,我不得不在各种有趣的工作机会中做出选择。几个不眠之夜之后,我意识到一件事:为什么不用我所掌握的工具来帮我做决定呢?
本文将向您展示如何构建蒙特卡洛模拟来帮助您在生活中做出任何类型的决定。我将比较三个不同的工作机会,类似于我面临的问题。很明显,所有呈现的信息都是 100%虚构的,并不代表我所面对的现实。换句话说,它只是一个例子!
我们开始吧!
什么是蒙特卡洛模拟?
蒙特卡洛模拟是一种数学技术,用于在处理上述结果的不确定性时估计所有可能的结果。这个想法很简单。
假设你需要在回家的两条路上做出选择。第一个平均需要 20 分钟。它的灯很少,车也很少。很有可能你总是会在你期望的时间到达。第二个平均需要 15 分钟。然而,经常有更多的交通,灯等。有很多不确定性,你可能要花 10 到 45 分钟。
蒙特卡洛模拟通过利用概率分布建立可能结果的模型。通过模拟实验,比如说 10,000 次,你可以很好地了解各种选择的风险有多大。然后你可以决定哪条路更适合你。
设置模拟
假设你正在处理各种各样的工作机会。做决定的挑战在于总有一些不确定性。例如,你并不总是确切地知道你将做什么,谁将是你的同事,这份工作将如何帮助你的职业发展,等等。
这就是蒙特卡洛的用武之地。下面是设置实验要遵循的三个步骤。
- 定义做出决策的所有变量(工资、条件、地点、工作生活平衡等)。)
- 定义每个变量的重要性(权重)。换句话说,什么对你最重要?
- 对于每个选项,为每个变量定义一个您认为合适的可能性范围(本例中为 10 分)。当然,范围越大,就意味着你对这个变量的评分越不确定!范围代表变量的概率分布。
当然,第 2 部分和第 3 部分需要更定性方法。分数是基于你对事物的感知,你的观点。有人定义的 10/10 工作环境因人而异,没有简单通用的方法来量化。
然而,这种方法的有趣之处在于,它可以帮助你找出你潜意识中对各种选项的偏见。事实上,通过对所有变量本身进行分级,你可能会意识到一个选项在大多数方面都处于领先地位。
代码
这是我用来创建蒙特卡洛模拟的 Python 代码。
- 在第 6 行,我定义了所有要考虑的变量。
- 第 10 行是将所有权重分配给不同变量的地方。
- 第 14 行非常重要。它是根据您为此变量提供的范围生成随机值的地方。
- 第 20-22 行包含列表的列表,每个变量的等级范围。例如,对于 A,第一个列表[4,9]意味着
work
变量可以在模拟中的等级 4 和 9 之间的任何地方找到自己。
结果
这是经过 10,000 次迭代后得到的分布。
我们看到似乎有两个最爱,作业 A 和作业 C。有趣的是,作业 A 的分布更广,而作业 C 似乎更安全,可能是因为不确定性更少。
模拟允许我们在多次迭代中比较各种选项,以确定这些选项的实际风险有多大。然后,用户的任务是决定与期权相关的额外风险是否值得回报。
结论
当存在不确定性时,蒙特卡罗模拟可以帮助做出决策,因为我们事先不知道变量的确切结果。这是一个非常简单而强大的技术。对于任何给定的实际问题,可以随意重用和修改代码。
非常感谢你的阅读!
如何像知道自己在做什么一样使用正态分布
变得非常擅长正态分布
介绍
你在半夜叫醒一个统计学家,问他们关于正态分布的公式。半睡半醒时,他们会不折不扣地背诵这个公式:
正态分布是我们宇宙中最基本的东西之一。它几乎无处不在,自然,科学,数学。即使是最疯狂的现象,如质子相互碰撞,人群的行动等。可以用正态分布来模拟。
当我们绘制正态分布数据时,通常会形成一个钟形模式:
这就是为什么你也会听到它被称为钟形曲线或高斯分布(以发现它的德国数学家卡尔·高斯命名)。
这在商业领域也有许多含义。它无处不在,以至于数据科学家和统计学家从正态假设开始分析许多新数据。此外,由于我们将在后面讨论的中心极限定理,即使基础分布不正态,您也可以导出正态分布。
在这篇文章中,您将学习如何在您的日常工作流程中使用这种发行版,方法是建立理论上的理解,并通过代码应用这些思想。
https://ibexorigin.medium.com/membership
获得由强大的 AI-Alpha 信号选择和总结的最佳和最新的 ML 和 AI 论文:
https://alphasignal.ai/?referrer=Bex
正态分布预告
正态分布是你在分析过程中能想到的最好的东西。它有许多“好”的属性,使得它很容易使用和获得结果。
之前我们看了 ND 的钟形曲线。曲线的高度由标准偏差值决定。较小的标准差意味着更多的数据点聚集在平均值周围,而较大的值表示分布更加分散。这也可以解释为曲线的密度(稍后将详细介绍)是由标准偏差决定的。
ND 的平均值围绕 x 轴移动中心:
如你所见,理论上完美的 ND 有一个单峰,它也是对称线穿过分布的地方。此外,这些陈述可以是关于完美的 ND:
- 平均值、中值和众数都相等。
- 正好一半的值在对称线的左边,一半在右边。
- 曲线下的总面积始终等于 1。
当然,真实数据很少遵循完美的钟形模式。然而,估计完美 ND 和基础分布之间的相似性是值得的,以查看我们是否可以安全地将其视为正态分布。我们将在接下来的几节中看到如何生成这个相似性过程。
PDF 到底是什么?
到目前为止,我们一直在看 NDs 的图,但我们没有问是什么函数产生这些图。要回答这个问题,你需要理解什么是连续概率分布。
根据描述性统计,有两种类型的数据:离散的和连续的。通过计数记录的任何数据都是离散的(整数值),如考试成绩、每天吃的苹果数量、遇到红灯停下来的次数等。相反,连续数据是通过测量记录的任何数据,如身高、体重、距离等。时间本身也被认为是连续的数据。
连续数据的一个定义方面是相同的数据可以用不同的度量单位表示。例如,距离可以用英里、公里、米、厘米、毫米来度量,列表 继续 。无论多小,对于连续数据都可以找到更小的度量单位。这也意味着一次测量可以有无限多的小数点。
在概率上,如果一个随机实验产生了连续的结果,那么它就会有一个连续的概率分布。例如,假设随机变量 X 以英寸为单位存储每天的降雨量。现在,降雨量几乎不可能是一个整数值,因为我们不能说今天的降雨量是 2 英寸,而不是一个水分子的多或少。发生这种情况的概率很小,我们可以有把握地说是零。
其他值也是如此,例如 2.1 或 2.0000091 或 2.000000001。下雨的几率总是 0。这就是为什么对于连续分布,我们有不同的函数,叫做概率密度函数。如果你正在阅读我最近的帖子,概率密度函数会计算离散事件的概率,比如掷骰子、掷硬币或任何其他的伯努利试验。
概率质量函数将结果的概率编码为高度。以下是单个骰子滚动的 PMF 图示例:
如您所见,每个条形的高度代表单一结果的概率,如 1、3 或 5。它们都处于相同的高度,因为模具辊具有不连续的均匀分布。
现在,概率密度函数使用一个面积来表示某个概率。在我解释为什么之前,想想如果他们也在高度上编码概率会发生什么。如我所说,连续数据取某个值的概率很小,以至于高度都下降到 0,如下所示:
还记得统计学家最喜欢的公式吗?
这是正态分布的概率密度函数的公式。仅凭一己之力,它做不了多少事情。对于已知平均值和标准偏差的 ND,您可以在函数中输入任何 x 值。它输出 x 轴上该点处曲线的高度。注意,我不是说概率,而是说曲线的高度。我说过,对于连续分布,面积代表了一定的概率。图上的细线没有面积,所以我们需要重新定义我们最初的问题。
对于我们观察每天降雨量的随机实验,我们将不再问诸如降雨量为 3 英寸或 2.5 英寸的概率是多少之类的问题,因为答案总是 0。相反,我们现在问的是降雨量在 1.6 到 1.9 英寸之间的概率是多少。这相当于问这两条线之间的曲线下的面积是多少:
对于那些做过微积分作业的人来说,它是用这个积分公式计算的:
等等等等。先别走。我们在这里不会手工计算,甚至不会使用代码。稍后我会向你展示一个更简单的计算方法。
上面的公式得出了一个 0 到 1 之间的数字,即降雨量在 1.6 到 1.9 英寸之间的概率。现在,一个显而易见的问题是,既然没有给出概率,我们如何解释亚轴。嗯,我根本没有资格回答这个问题,所以我建议你看看 StackExchange 的这个帖子,看看 3Blue3Brown 的这个超赞的视频。
接下来,我们将讨论累积分布函数,它为我们提供了一个更好的工具来计算面积下的概率,也许是 NDs 的一个改进的可视化。
累积分布函数
在这一点上,我想我们已经同意正态分布图有一条曲线。我们已经看到了钟形的,现在,是时候看看乙状结肠了:
在解释这个之前,我们先了解一下什么是累积分布函数(CDF)。我们最好从一个简单的例子开始:
Cdf.from_seq([1, 2, 3, 4, 5, 6])
如果我们从任何分布中取一个随机数并输入到 CDF 中,结果告诉我们得到一个小于或等于该随机数的值的概率。以上是单个模辊的 CDF。模具辊具有 1、2、3、4、5、6 的分布。假设我们随机选择了 4 个。
从我们的分布中观察到小于或等于 4 的值的概率是多少?根据上面的 CDF,是 0.6666。同样,有 100%的机会观察到小于或等于 6 的值,因为在我们的分布中所有的值都小于或等于 6。
在符号中,这看起来像这样:
因此,要计算 CDF,第一步是计算每个结果的个体概率。然后,任何值 x 的累积概率将是我们分布中所有有序个体概率的总和,直到 x 。
现在,回到正态分布的 CDF,我们可以很容易地解释它:
这条曲线也被称为 s 形曲线。ND 的平均值是曲线的中心:
使用 CDF,你不必使用 ND 的公式。例如,要找到 X 落在 1.6 和 1.9 之间的概率,您将找到上限的 CDF 并减去下限的 CDF:
这么简单!
最后,Python 正常的东西
最后,我将揭示我是如何绘制所有这些钟形曲线和 CDF 的。首先,任何分布的钟形曲线都可以用 Seaborn 的kdeplot
函数生成。
为了绘制这个分布图,我们需要创建分布图本身。这可以通过使用numpy.random.normal
来完成:
这里,我们从平均值为 5、标准差为 2 的正态分布中抽取 10k 个样本。接下来,我们将使用kdeplot
来制作曲线:
kdeplot
将任意值序列作为分布。它的bw_adjust
参数控制曲线的平滑度。有关该功能的更多信息,请访问文档。
现在,创建 CDF 还需要做一些工作。我们可以手工生成它,但是我们将使用empiricaldist
库的Cdf
功能:
from empiricaldist import Cdf
Cdf
有一个from_seq
方法可以计算任何分布的 CDF:
接下来,我们将用pyplot
绘制这个分布图:
正如所料,我们在图上看到平滑的 s 形曲线。
最后,我们将看一个实际的用例。我们将使用 CDF 图来找出给定的分布是否正态。例如,我将从 Seaborn 加载 Iris 数据集:
在前面的章节中,许多自然现象遵循正态分布,因此我们可以假设鸢尾花物种的测量值遵循正态分布。让我们看看我们有多正确:
从一个眼球估计就已经可以看出,我们的假设是不正确的。不过,为了确保绘制完美的正常 ND 进行比较会更好。你问哪一个?当然,NDs 有无限多种,那么我们如何选择一种呢?
好吧,明智的做法是画出与基础分布具有相同均值和标准差的 ND,这样两个分布会很接近:
现在,更清楚的是,这两种分布是完全不同的。然而,在许多情况下,这两种分布彼此非常接近,但又不完全相同,即不是正态分布。对人眼来说,比较接近的钟形曲线不是一件容易的事情。所以我们需要用更好的视觉来比较这样的分布。我想你已经猜到了,但是我们将使用 CDF。
我们将使用与上面相同的技术,但是将 PDF 替换为 CDF:
像往常一样,我们生成萼片长度的 CDF 并绘制成一条线。接下来,我们取萼片长度的平均值和标准差,并用这些参数生成一个完美的正态分布。然后,我们也为其生成 CDF,并将它们绘制在彼此之上。为了清楚起见,我用点作为标记,省略了线条样式。这个视图提供了比逐行比较更好的视觉比较。
现在,解释这个,我们可以看到分布并没有很大的不同。我们还必须考虑到样本量非常小(150)。
简而言之,如果你想模拟一个正态分布,使用np.random.normal
。尝试增加样本量以获得更好的准确性。如果你想知道一个分布是否是正态分布,试着用基本分布的参数画出它的 CDF 和一个完美 nd 的 CDF。
结论
我们已经谈了这么多了。尽管如此,关于正态分布还有很多东西要学。也就是说,我们没有涵盖中心极限定理或经验法则,或许多其他主题。我留下了一些帮助我理解这个主题的资源链接:
- 正态分布【Brilliant.org
- Brilliant.org 的中心极限定理
- 正态分布由可汗学院提供
- 维基百科页面