TowardsDataScience 博客中文翻译 2021(五百一十二)

原文:TowardsDataScience Blog

协议:CC BY-NC-SA 4.0

无效假设和交替假设

原文:https://towardsdatascience.com/null-and-alternate-hypothesis-c5b6ae9d7845?source=collection_archive---------23-----------------------

…用简单明了的英语!让我们把基本情况弄清楚。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由马库斯Unsplash 上拍摄

统计数据听起来令人生畏,因为我们阅读的大部分文本使用困难的统计词汇来定义术语。😩当我们开始阅读一个概念及其定义时,我们很难在脑海中将其形象化,没有视觉画面,我们总是感到困惑。🙇🏻这往往会让我们多次搜索同一个概念。😰

在这篇文章中,我将试着用简单的方式解释无效假设和交替假设的概念。那么,我们开始吧。🏄

假设,我们有两种药物,药物 A 和药物 B,用来治疗流感。💊我们用药物 A 治疗 6 名流感患者,用药物 B 治疗 6 名不同的流感患者,并测量这些人从流感中康复的时间(以天为单位)。🤕正如我们所知,每个人都是不同的,我们已经意识到一个事实,即不是每个人都会在相同的天数内康复。因此,我们将着眼于平均时间,而不是个人时间。

以下是我们的测试统计数据:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

很容易找到每种药物的平均值。经过计算,我们发现服用药物 A 的人平均需要 3 天才能康复,而服用药物 B 的人平均需要 5.67 天才能康复。💪🏼

我们可以清楚地看到,恢复时间相差 2.67 天,这一差距非常大。虽然我们知道每个人都有不同的免疫力和不同的生活方式(饮食、锻炼、工作等)。)可以导致这样的结果,我们最有可能说药物 A 比药物 B 更好,因为它帮助人们更快地恢复。因此,我们可能会在下次患流感时开始服用这种药物。🤒

该数据使我们形成一个假设,即药物 A 的恢复时间比药物 b 的恢复时间少 2.67 天。

请注意,这只是一个玩具示例,实际上,在形成任何假设之前,我们会测试更多的人。

现在,假设我们在不同的人群中测试这两种药物,发现药物 B 的恢复时间比药物 a 的恢复时间少 1.5 天,这与我们之前的假设完全不同。所以,我们继续在另一组人身上试验这些药物。这一次,我们发现药物 A 的恢复时间比药物 b 的恢复时间少 4 天。如果我们继续做这个练习,我们可能会得到类似的结果,或者很可能会得到完全不同的结果。

在这一点上,统计学家迈出了重要的一步,他们不是在不同的人群中一次又一次地测试已形成的假设,而是实际上设计了一个新的假设。

在这种情况下,新的假设将是:

药物 A 和药物 b 的恢复时间没有差异。他们将这种假设命名为无效假设,以确定是否有任何差异。

针对一个已形成的假设检验无效假设,在这种情况下,药物 A 的恢复时间比药物 b 的恢复时间少 2.67 天。该假设被命名为替代假设

为了进行假设检验,我们对很多人进行了抽样调查。现在假设我们发现两种药物的恢复时间确实有差异。(虽然这个结果背后可能有许多隐藏的原因,但目前我们不会考虑这些原因。)在这种情况下,我们拒绝我们的零假设。🙅

让我们看看,如果结果相反,我们会怎么做。假设我们对很多人进行抽样,发现两种药物的恢复时间没有差异。在这种情况下,我们将无法拒绝零假设。🙆

此时,一个很好的问题是,为什么我们不接受基于我们观察的零假设。🙋

无效的假设永远不会被接受。我们要么拒绝它们,要么不能拒绝它们。原因很简单。我们抽取的样本总是随机的。我们从来不对整个人口进行测试,总是从人口中抽取一小部分样本来形成我们的假设。

因此,如果我们随机选择的样本显示的结果不同于零假设,我们就万事俱备了,事情变得简单了。我们简单地拒绝零假设。

然而,如果我们得到与我们的零假设相似的结果,我们不能拒绝零假设,这表明没有足够的证据表明零假设是错误的。我们这样做是因为仍然有很多关于人口的数据是我们没有的。

无效假设和替代假设是相互对立的。假设检验的主要目的是拒绝或未能拒绝零假设,因为统计学家从不接受它,即使他们采取的样本支持零假设,反对替代假设。

对于任何统计检验,我们都需要数据、零假设、备择假设。

我们通常用 H₀ 表示零假设,用 Hₐ 表示替代假设。

如何陈述零假设?✍️

我们通常通过定义零假设和替代假设将文字问题转化为统计假设检验。

让我们考虑我们上面的例子来写这两个假设。

从问题这个词中提取出另一个假设,然后我们把它写成数学形式。

备选假设:

药物 A 的恢复时间比药物 b 的恢复时间少 2.67 天

让我们用μ来表示平均恢复时间的差异。

Hₐ: μ = 2.67

零假设:

现在,为了写出零假设,我们陈述如果假设不成立会发生什么。基于我们的替代假设,我们有两种可能性:

μ >2.67 或μ <2.67

Any of these possibilities can form a null hypothesis. However, since the statisticians don’t have an idea about the truth, they generally frame it by saying that there is no difference in the recovery time between Drug A and Drug B. The mathematical form for the same will be:

H₀: μ =0

零假设中的‘零’是什么?🤔

据我所知,零假设并不意味着假设是零,而是它被称为零,因为统计学家的工作使这个假设无效。这背后很可能有其他隐藏的原因。

零假设和交替假设的概念不仅适用于统计学领域,也适用于其他领域。研究人员和科学家在进行任何统计测试之前形成这些假设。🔭 🔬 💊 💉 🌡

我希望这能让你对无效假设和替代假设有所了解。有一个与此相关的置信区间和 p 值的概念,但对于本文,我没有深入探讨。我的主要目的是让你们用简单的语言,而不是统计学术语,理解无效假设和替代假设的概念。😊

参考文献:

https://www.youtube.com/playlist?list = plbl H5 jkooluk 0 fluzwntyi 10 uqfuhsy 9

谢谢大家阅读这篇文章。请分享您对这篇文章的宝贵反馈或建议!快乐阅读!📗 🖌

领英

猜数字游戏

原文:https://towardsdatascience.com/number-guessing-game-504b5f3bb0e7?source=collection_archive---------35-----------------------

使用测试驱动开发方法的 Python 练习

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Unsplash迈特·蒂斯卡尔拍摄的照片

几周前,我开始阅读一本有趣的书:鲁文·m·勒纳的《Python 测试》。它由 50 个十分钟的练习组成。这是一本积累一些 Python 编程经验的理想书籍。

我自己开始阅读这本书,因为我的 Python 技能需要一些改进。有时你忙于学习另一种语言,以致于你的语法知识受到影响。

我最喜欢的解决编码问题的方法是测试驱动开发。为了让你开始,我决定分享我解决给定问题的方法。我们一起来看看如何解决这本书的第一个练习:猜数字游戏。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Python 锻炼手册

为企业发展和为自己发展有着巨大的区别。虽然书籍主要关注于为自己编码,但这是我试图有所作为的地方。

在本文中,首先,我们将看看测试驱动开发的一些优势。如果你已经理解了测试的重要性,你可以跳过这一节。

第二,我们要解决数字猜谜游戏,它由 5 个简单的步骤组成:

  • 写一个没有参数的函数“guessing_game”
  • 该函数选择 0 到 100(包括 0 和 100)之间的随机整数
  • 请用户猜测选择了哪个数字
  • 打印相应的猜测:“太高”,“太低”,或“刚刚好。”
  • 只允许程序在用户猜对后退出

尝试自己解决这些步骤,然后比较你的解决方案。有一些不同并不意味着你的解决方案是错误的。甚至可能更好!这个练习的目的是学习和分享知识,所以如果你有改进,请在评论中分享!

对于每一步,我会给你我的推理。

测试驱动开发的优势

用测试驱动的方式做这个练习至少有三个好处。因为你不同意一个论点,并不意味着其他论点都是废话。

  • 在这个练习之后,你将拥有 100%的分支代码覆盖率

如果你的同事想要改变你的实现,他们会有一个安全网来检查所有的需求。如果你独自工作,就像你在业余爱好项目或学习中所做的那样,你可能看不到以这种方式测试的任何附加价值。

但是在这个行业里,你不是一个人在工作。迟早有人会修改你的代码。这有风险。你不希望现有的价值被破坏。因为你预先编写了你的测试,所以不可能出现从未测试过的情况。

  • 迭代生成的代码可读性更好。

随着需求的增加,软件变得更加复杂。朝着一个解决方案反复工作会让一切变得更容易理解。

你可能同意下面的说法。当一个难题被分成多个部分时,它就变得不那么复杂了。每一部分本身都更容易消化。

测试驱动的开发方法允许你对任何问题都这样做。因为每一部分都是单独完成的,所以你的代码将是结构良好的,可读性强的。

  • 添加测试比从头开始更容易

几乎每个人都说软件测试会让你慢下来,尤其是当你第一次做的时候。但是你应该意识到测试是而不是可选的。

迟早,你必须开始,你拖得越久,事情就变得越复杂。除非你是自私的,并且无意与他人合作,否则你没有正当的理由不测试你的代码。

你应该什么时候开始?

为了获得所有的好处,你应该尽早开始。如果您在编写生产代码之前就开始测试,那将是最好的。虽然一开始可能会令人沮丧和困难,但它会让你更快。一旦你足够熟悉你的语言,测试驱动的开发就不会再拖你的后腿了。

猜数字游戏

写一个没有参数的函数“guessing_game”

如果你以前从未编写过测试,你需要花一些时间来熟悉它们。在第一步中,我们将从一个测试文件开始。number_guessing_game.py

在开始编写代码之前,我们需要进行第一次测试。这是测试驱动开发的黄金法则。我们从单元测试开始。因此,让我们导入unittest库。

没有实现就成功的测试并不好。我们想避免这种情况。因此,我们将不得不放弃失败的测试。在第一个测试中,您可以在定义方法之前编写方法名。这将被指示为错误。

为了通过这个测试,您必须创建一个方法。一个经验丰富的开发人员用他最喜欢的 IDE 中的快捷方式来做这件事,但是你也可以键入它。

这就是第一步。在一次成功的红绿迭代之后,我们有了一个没有参数的方法guessing_game

不用担心我下面代码中的 pass 语句。它只是未来代码的占位符。它什么也不做。如你所见,这个方法不返回任何东西。这个测试唯一要做的事情是检查是否有一个用名称guessing_game定义的方法。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

guessing_game一种没有任何论据的方法。

写一个从 0 到 100(包括 0 和 100)之间选择一个随机整数的函数

让我带你走完这稍微难一点的一步。我们要生成一个 0 到 100 之间的随机整数,包括 100。你可以采取多种方法。

首先,你可以试着自己写一个随机整数的函数。但这相当复杂。测试它甚至更复杂。

  • 如何让它变得真正随机?
  • 你的种子是什么?是时间还是别的什么

因此,我们没有重新发明轮子,而是引进了random。这是一个已经使用了很多年的库,根据文档,它的功能正是我们想要的。

因此,我编写了一个测试来验证正确的方法将被调用。我们没有将随机库作为方法参数传递,而是使用了一种不同的技术。我们在构造函数中注入这个库,使用的是dependency injection的原理。

  • 依赖注入使得嘲笑或监视变得更加容易。想象一下在现有代码上添加测试。您将遇到的第一个问题是决定您将模仿哪些库,不模仿哪些库。避免这个问题,尽早测试。
  • 依赖注入不会将我们的代码耦合到一个特定的库。我们可以很容易地把图书馆换成另一个。这只是一个小班级,但是想象一下使用一个大系统。只会变得更加复杂。

还有一件事。你可以用假的或者间谍。在这种情况下,生成随机数非常快。我们真的不需要模仿。作为开发人员,您必须做出权衡。

如果您更喜欢真正的实现,除非它需要太多的时间来执行,这将是最好的。库如何生成随机数只是一个细节。从我们现在掌握的信息来看,我们可以同意这个图书馆正是我们所需要的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一种生成 0 到 100 之间的随机整数的方法。

请用户猜一个数字。

乍一看,下一步并不比第一步复杂。但是和所有事情一样,细节决定成败。

输入法在 Python 中是如此常用。它甚至不需要进口。但这对我们来说很困难。如果没有导入,如何应用依赖注入?

嗯,有可能。如果你点击输入方法,你会看到它也包含在内置库中。所以,我们可以重复我们在第一步中看到的。

您编写第二个测试,验证输入法已经被称为对用户有用的消息。要让它通过,必须调用内置的输入库。确保您模拟了builtins输入法的返回值,因为否则,您的测试将在等待用户输入时停滞不前。

确保两个测试都成功。在第一个例子中,我们验证了随机数的产生。第二,我们测试用户的输入。这开始看起来像一步猜谜游戏。

你做得很好。让我们进入下一步。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

要求用户输入,以便他们能猜出数字。

当用户进行猜测时,输出“太高”、“太低”或“刚刚好”。

前面两步感觉不太像编程吧?这个会。在这一步,我们将为用户添加一些逻辑和反馈。我们将添加三个用例:

  • 如果猜测的数字高于真实数字,则打印“太高”
  • 如果猜测的数字低于真实数字,则打印“太低”
  • 如果猜测的数字正是真实的数字,则打印“恰到好处”

每个案例都将是一个新的测试驱动的步骤。我总是用最少的努力使测试成功。

  • 第一步,用户的每一次高输入都会输出“太高”。
  • 在第二步中,我们修改代码,否则输出“太低”。
  • 在第三步,我们将涵盖最后一种情况,“刚刚好。”

通过迭代工作,您可以为每个分支执行添加一个测试。在每个时间点,这都会导致 100%的分支代码覆盖率。我们以三个额外的测试结束,guessing_game现在是一个猜测的完整特性。

如果你一直跟着,我们就快完成了!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

基于用户输入的反馈。

只有当用户猜对时才退出

你已经到了这个练习的最后一步。循环往往会增加相当多的复杂性——尤其是这个。我们只能在猜对的情况下退出程序。

为了允许这种改变,我们必须修改一些测试。否则,测试将永远不会退出无限循环。每个测试最终都必须退出 while 循环。

在这一步中,最重要的部分是编写一个测试,在以正确的输入结束之前输入一些错误的输入。三个数字足以测试一个循环,但是您可以选择任何数字(例如,5 次尝试,参见下面的代码片段)。

一个重构改进可以将一个猜测的代码移动到它自己的函数中。这将减少对其他部分的更改量。你可以自己试试。如我所说,我提供的代码示例并不是最佳的。这只是我在一定时间内努力的结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

只有当用户输入 5 个正确的数字时才退出

结论

几乎所有的文章和书籍都专注于编写高质量的代码。但是他们倾向于将测试移到它自己的章节。这并不理想。测试应该从软件开发的早期阶段开始。

您已经看到了将一个复杂的任务分成更小的可测试步骤是可能的。每一步都应该通过测试、实现和重构步骤。这产生了经过良好测试的、可维护的和可读的代码。

为了结束这篇文章,我将添加最终的解决方案。您将看到这个解决方案与原作者提出的几乎相同。您可以自己尝试一下,或者在其他编码挑战中尝试一下。

猜数字游戏

猜数字游戏测试。

数字评分指标

原文:https://towardsdatascience.com/numeric-scoring-metrics-acd3896c5eff?source=collection_archive---------42-----------------------

为预测模型找到正确的指标

KNIME 数据科学家玛瑞·威德曼

量化数据有说不完的故事!

每日收盘价告诉我们股票市场的动态,小型智能仪表告诉我们家庭的能源消耗,智能手表告诉我们人体在锻炼时发生了什么,以及关于一些人在某个时间点对某个话题的自我评估的调查。不同类型的专家可以讲述这些故事:金融分析师、数据科学家、体育科学家、社会学家、心理学家等等。他们的故事基于模型,例如,回归模型时间序列模型方差分析模型

为什么需要数字评分指标?

这些模型在现实世界中有许多后果,从投资组合经理的决策到一天、一周和一年中不同时间的电力定价。需要数字评分指标,以便:

  • 选择最准确的型号
  • 估计模型误差对现实世界的影响

在本文中,我们将描述数值预测模型的五个真实用例,在每个用例中,我们从稍微不同的角度来衡量预测准确性。在一种情况下,我们衡量一个模型是否有系统偏差,在另一种情况下,我们衡量一个模型的解释能力。本文最后回顾了数字评分指标,展示了计算它们的公式,并总结了它们的属性。我们还将链接到几个在 KNIME 分析平台中构建和评估预测模型的示例实现。

五个指标:预测准确性的五个不同角度

(根)均方误差,MSE——哪种模型最能捕捉到动荡的股票市场的快速变化?

在下面的图 1 中,你可以看到 LinkedIn 收盘价从 2011 年到 2016 年的变化。在该时间段内,行为包括突然的峰值、突然的低点、更长时间的增加和减少值,以及几个稳定期。预测这种不稳定的行为具有挑战性,尤其是从长期来看。然而,对于 LinkedIn 的利益相关者来说,这是有价值的。因此,我们更喜欢捕捉突然变化的预测模型,而不是在五年内平均表现良好的模型。

我们选择具有最低(根)均方误差的模型,因为与小误差相比,该指标更重视大误差,并且支持能够对短期变化做出反应并节省利益相关者资金的模型。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图一。2011-2016 年 LinkedIn 每日股市收盘价:规律少,突变多,可预测性低的数据。我们选择具有最低(根)均方误差的预测模型,因为它对较大的预测误差加权更大,并且更倾向于可以捕捉突然的峰值和低点的模型。

平均绝对误差,MAE——哪个模型能最好地估计长期能耗?

在图 2 中,您可以看到都柏林 2009 年 7 月的每小时能耗值,这些值是从一组家庭和行业收集的。能源消耗显示出相对规律的模式,工作时间和工作日的数值较高,夜间和周末的数值较低。这种有规律的行为可以相对准确地预测,允许对能源供应进行长期规划。因此,我们选择一个具有最低平均绝对误差的预测模型。我们这样做是因为它对大误差和小误差进行了同等加权,因此对异常值具有鲁棒性,并显示了在整个时间段内哪个模型具有最高的预测准确性。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图二。都柏林 2009 年 6 月的每小时能耗值,收集自一组家庭和行业。该数据显示了相对规律的行为,因此可以进行长期预测。我们选择具有最低平均绝对误差的预测模型,因为这个度量对于异常值是稳健的。

平均绝对百分比误差,MAPE——不同产品的销售预测模型是否同样准确?

炎炎夏日,汽水和冰淇淋的供应都要有保障!我们要检查预测这两种产品销售的两个预测模型是否同样准确。

这两个模型都以同样的单位——售出商品的数量——生成预测,但规模不同,因为苏打水的销量比冰淇淋大得多。在这种情况下,我们需要一个相对误差度量,并使用平均绝对百分比误差,它报告相对于实际值的误差。在图 3 中,在左侧的线图中,您可以看到 2020 年 6 月苏打水的销售额(紫色线)和冰淇淋的销售额(绿色线)以及这两种产品的预测销售额(红色线)。汽水的预测线似乎比冰淇淋偏离得稍微多一点。然而,起泡水的实际值较大,这使可见的比较产生偏差。实际上,预测模型对苏打水的预测比对冰淇淋的预测更好,据 MAPE 报告,苏打水的预测值为 0.191,冰淇淋的预测值为 0.369。

但是请注意,当实际值接近零时,MAPE 值可能会有偏差。例如,与夏季相比,冰淇淋在冬季的销量相对较低,而牛奶的销量则全年保持不变。当我们通过 MAPE 值比较牛奶和冰淇淋预测模型的准确性时,冰淇淋销售额中的小值使得冰淇淋预测模型看起来比牛奶预测模型差得多。

在图 3 中,在中间的线图中,您可以看到牛奶(蓝线)和冰淇淋(绿线)的销售额以及这两种产品的预测销售额(红线)。如果我们看一下 MAPE 值,牛奶(MAPE = 0.016)的预测准确度显然比冰淇淋(0.266)好得多。然而,这种巨大的差异是由于冬季月份冰淇淋销售的低价值。图 3 中右边的线形图显示了冰淇淋和牛奶的实际和预测销售额,冰淇淋销售额每月增加 25 件。如果没有接近于零的偏差,冰淇淋(MAPE=0.036)和牛奶(MAPE=0.016)的预测精度现在更加接近。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3。三个线图显示了冰淇淋和苏打水(左边的线图)以及冰淇淋和牛奶(中间和右边的线图)的实际值和预测值。在右侧的线图中,冰淇淋销售额按比例放大了 25 倍,以避免由小的实际值引起的平均绝对百分比误差的偏差。

平均符号差——一个正在运行的应用程序是否提供了不切实际的期望?

智能手表可以连接到一个正在运行的应用程序,该应用程序可以估计 10 公里跑的完成时间。有可能,作为一种激励,应用程序估计的时间比实际预期的要低。

为了测试这一点,我们收集了一组跑步者六个月的预计和实际完成时间,并将平均值绘制在图 4 的线图中。如您所见,在这六个月中,实际完成时间(橙色线)比预计完成时间(红色线)减少得更慢。我们通过计算实际完成时间和估计完成时间之间的平均符号差来确认估计值中的系统偏差。它是负的(-2.191),所以这个应用程序确实提出了不切实际的期望!但是,请注意,这个度量并不能提供关于误差大小的信息,因为如果有一个跑步者实际上跑得比预期时间更快,这个正误差会补偿一部分负误差。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4。在六个月的时间里,10k 跑的预计(红线)和实现(橙线)完成时间。估计的时间向下偏移,这也由平均符号差的负值示出。

R 平方——我们多少年的教育可以通过查阅文献来解释?

在图 5 中,你可以看到在一个人口样本中,获得文学(x 轴)和教育年数(y 轴)之间的关系。对数据拟合线性回归线,以模拟这两个变量之间的关系。为了测量线性回归模型的拟合度,我们使用 R 平方

r 平方表示模型解释了目标列(受教育年限)的多少方差。基于该模型的 R 平方值 0.76,获得文献解释了受教育年限中 76%的差异。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 5。线性回归线模拟文学作品的获取与受教育年限之间的关系。R-squared 用于测量模型拟合度,即目标列(受教育年限)中的方差有多少可以由模型解释,在本例中为 76%。

回顾五个数字评分指标

上面介绍的数字评分标准如图 6 所示。这些指标与用于计算它们的公式以及每个指标的一些关键属性一起列出。在公式中,yi 是实际值,f(xi)是预测值。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6。常见的数字评分指标、它们的公式和关键属性。在公式中,yi 是实际值,f(xi)是预测值,n 是样本量。

总结

在本文中,我们介绍了最常用的错误度量,以及它们对模型性能的影响。

通常建议查看多个数字评分指标,以全面了解模型的性能。例如,通过查看平均符号差,您可以看到您的模型是否有系统偏差,而通过研究(根)均方误差,您可以看到哪个模型最能捕捉到突然的波动。可视化,例如,线形图,补充了模型评估。

对于实际的实现,请看一下可视化数据科学工具 KNIME Analytics Platform 中构建的示例工作流。

从 KNIME Hub 下载并检查这些免费的工作流程:

为首次出版的 新栈

NumPy 基础知识备忘单(2021),用于数据科学的 Python

原文:https://towardsdatascience.com/numpy-basics-cheat-sheet-2021-python-for-data-science-89c483773880?source=collection_archive---------6-----------------------

2021 年初学者学习 NumPy 的绝对基础

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

约翰·李的照片来自 Unsplash

NumPy 库是 Python 中科学计算的核心库。它提供了一个高性能的多维数组对象和处理数组的工具。

查看下面的不同部分,了解 NumPy 提供的各种数组函数和工具。

章节:
1。创建数组
2。检查你的数组
3。数组数学
4。比较
5。聚合函数
6。子集化,切片,索引7。添加/删除元素
8。阵列操纵
9。复制数组
10。排序数组
11。数据类型

创建数组

NumPy 数组是由相同类型的值组成的网格。维数是数组的秩;数组的形状是一组整数,给出了数组在每个维度上的大小。

在本节中,您将学习如何创建这些不同类型的数组。

  • 一维数组
>>> a = np.array([1,2,3])
 array([1, 2, 3])
  • 二维数组
>>> b = np.array([(1.5,2,3),(4,5,6)], dtype = float)
 array([[1.5, 2\. , 3\. ],
        [4\. , 5\. , 6\. ]])
  • 三维阵列
>>> c = np.array([[(1.5,2,3),(4,5,6)],[(3,2,1),(4,5,6)]], dtype = float)
  • 零数组
>>> np.zeros((3,4))
 array([[0., 0., 0., 0.],
        [0., 0., 0., 0.],
        [0., 0., 0., 0.]])
  • 一的数组
>>> np.ones((3,4))
 array([[1., 1., 1., 1.],
        [1., 1., 1., 1.],
        [1., 1., 1., 1.]])
  • 等距值数组(步长值)
>>> np.arange(10,25,5)
 array([10, 15, 20])
  • 等距值数组(样本数)
>>> np.linspace(0,2,9)
 array([0.0, 0.25, 0.5 , 0.75, 1.0, 1.25, 1.5 , 1.75, 2.0])
  • 2x2 单位矩阵
>>> np.eye(2)
 array([[1., 0.],
        [0., 1.]])
  • 随机值数组
>>> np.random.random((2,2))
 array([[0.42326354, 0.56737208],
        [0.01597192, 0.79065649]])

检查您的阵列

在本节中,您将学习如何提取数组的具体特征,包括长度、数据类型、大小和维度。

下面代码中的 ab 在本节中用作数组示例。

>>> a = np.array([1, 2, 3])
>>> b = np.array([[1.5, 2\. , 3\. ], [4\. , 5\. , 6\. ]])
  • 数组维度
>>> b.shape
 (2,3)
  • 数组长度
>>> len(a)
 3
  • 数组维数
>>> b.ndim
 2
  • 数组元素的数量
>>> b.size
 6
  • 数组元素的数据类型
>>> b.dtype
 dtype('float64')
  • 将数组转换为不同的类型
>>> b.astype(int)
 array([[1, 2, 3],
        [4, 5, 6]])

阵列数学

在这一节中,您将学习如何使用两个不同的数组执行各种算术运算。

下面代码中的 ab 在本节中用作数组的例子。

>>> a = np.array([1, 2, 3])
>>> b = np.array([[1.5, 2\. , 3\. ], [4\. , 5\. , 6\. ]])
  • 减法
>>> a - b
 array([[-0.5,  0\. ,  0\. ],
        [-3\. , -3\. , -3\. ]])
  • 减法. v2
>>> np.subtract(a,b)
 array([[-0.5,  0\. ,  0\. ],
        [-3\. , -3\. , -3\. ]])
  • 添加
>>> a + b
 array([[2.5, 4\. , 6\. ],
        [5\. , 7\. , 9\. ]])
  • add . v2
>>> np.add(a,b)
 array([[2.5, 4\. , 6\. ],
       [5\. , 7\. , 9\. ]])
  • 分开
>>> a/b
 array([[0.66, 1\. , 1\. ],
        [0.25, 0.4, 0.5]])
  • 第二版
>>> np.divide(a,b)
 array([[0.66, 1\. , 1\. ],
        [0.25, 0.4, 0.5]])
  • 增加
>>> a*b
 array([[ 1.5,  4\. ,  9\. ],
       [ 4\. , 10\. , 18\. ]])
  • 乘法. v2
>>> np.multiply(a,b)
 array([[ 1.5,  4\. ,  9\. ],
       [ 4\. , 10\. , 18\. ]])
  • 指数运算
>>> np.exp(b)
 array([[  4.48168907,   7.3890561 ,  20.08553692],
       [ 54.59815003, 148.4131591 , 403.42879349]])
  • 平方根
>>> np.sqrt(b)
 array([[1.22474487, 1.41421356, 1.73205081],
       [2\.        , 2.23606798, 2.44948974]])
  • 对数
>>> np.log(b)
 array([[0.40546511, 0.69314718, 1.09861229],
        [1.38629436, 1.60943791, 1.79175947]])

比较

在本节中,您将学习如何使用特定元素或另一个数组来比较数组。

下面代码中的 ab 在本节中用作数组的例子。

>>> a = np.array([1, 2, 3])
>>> b = np.array([[1.5, 2\. , 3\. ], [4\. , 5\. , 6\. ]])
  • 逐元素比较
>>> a == b
 array([[False,  True,  True],
       [False, False, False]])
  • 基于元素的比较. v2
>>> a < 2
 array([True, False, False])
  • 基于数组的比较
>>> np.array_equal(a,b)
 False

聚合函数

在本节中,您将学习如何在数组中使用各种聚合函数,如 sum、min、max、mean 和 median。

在本节中,下面代码中的 ab 用作数组示例。

>>> a = np.array([1, 2, 3])
>>> b = np.array([[1.5, 2\. , 3\. ], [4\. , 5\. , 6\. ]])
  • 数组和
>>> a.sum()
 6
  • 数组最小值
>>> a.min()
 1
  • 数组行的最大值
>>> b.max(axis = 0)
 array([4., 5., 6.])
  • 元素的累积和
>>> b.cumsum(axis = 1)
 array([[ 1.5,  3.5,  6.5],
        [ 4\. ,  9\. , 15\. ]])
  • 平均
>>> a.mean()
 2

子集化、切片、索引

在本节中,您将学习如何从数组中检索特定的值。

下面代码中的 ab 在本节中用作数组示例。

>>> a = np.array([1, 2, 3])
>>> b = np.array([[1.5, 2\. , 3\. ], [4\. , 5\. , 6\. ]])
  • 选择第二个索引处的元素
>>> a[2]
 3
  • 选择第 2 列第 1 行的元素
>>> b[1,2]
 6
  • 选择索引 0 和 1 处的项目
>>> a[0:2]
 array([1, 2])
  • 选择第 1 列第 0 行和第 1 行的项目
>>> b[0:2,1]
 array([2., 5.])
  • 选择第 0 行的所有项目
>>> b[:1]
 array([[1.5, 2\. , 3\. ]]
  • 反向排列
>>> a[::-1]
 array([3, 2, 1])
  • 选择少于 3 的元素
>>> a[a<3]
 array([1, 2])

添加/删除元素

在这一节中,您将学习如何将特定元素添加到数组中,以及如何移除它们。

下面代码中的 ab 在本节中用作数组示例。

>>> a = np.array([1, 2, 3])
>>> b = np.array([[1.5, 2\. , 3\. ], [4\. , 5\. , 6\. ]])
  • 返回一个形状为(1,6)的新数组
>>> b.resize(1,6)
 array([[1.5, 2\. , 3\. , 4\. , 5\. , 6\. ]])
  • 将项目追加到数组
>>> np.append(a,7)
 array([1, 2, 3, 7])
  • 在数组中插入项目
>>> np.insert(a,1,9)
 array([1, 9, 2, 3])
  • 从数组中删除项目
>>> np.delete(a,[1])
 array([1, 3])

数组操作

在本节中,您将学习如何更改数组的外观。您将学习如何展平、转置、整形和连接数组。

以下代码中的 **a、b、**和 d 在本节中用作数组示例。

>>> a = np.array([1, 2, 3])
>>> b = np.array([[1.5, 2\. , 3\. ], [4\. , 5\. , 6\. ]])
>>> *d =* np.array([10, 15, 20])
  • 展平数组
>>> b.ravel()
 array([1.5, 2\. , 3\. , 4\. , 5\. , 6\. ])
  • 重塑,但不要改变数据
>>> b.reshape(3,2)
 array([[1.5, 2\. ],
        [3\. , 4\. ],
        [5\. , 6\. ]])
  • 串联数组
>>> np.concatenate((a,d),axis = 0)
 array([ 1, 2, 3, 10, 15, 20])
  • 转置阵列
>>> np.transpose(b)
 array([[1.5, 4\. ],
        [2\. , 5\. ],
        [3\. , 6\. ]])

复制数组

在本节中,您将学习如何创建数组的副本以供将来使用。

下面代码中的在本节中用作数组的示例。

>>> a = np.array([1, 2, 3])
  • 创建阵列的副本
>>> np.copy(a)
 array([1, 2, 3])
  • 创建数组的深层副本
>>> a.copy()
 array([1, 2, 3])

排序数组

在这一节中,您将学习如何对数组进行排序,使其从减少到增加,反之亦然。

下面代码中的 ac 在本节中用作数组示例。

>>> a = np.array([1, 2, 3])
>>> c = np.array([[(1.5,2,3),(4,5,6)],[(3,2,1),(4,5,6)]]
  • 对数组排序
>>> a.sort()
 array([1, 2, 3])
  • 对数组轴的元素进行排序
>>> c.sort(axis = 0)

数据类型

NumPy 数组中可以有不同的数据类型。

  • 64 位整数类型:np.int64
  • 双精度浮点:np.float32
  • 布尔型字符串真假:np.bool
  • Python 对象类型值:np.object
  • 固定长度字符串类型:np.string
  • 固定长度 Unicode 类型:np.unicode_

现在和可预见的将来,Python 都是数据科学领域的佼佼者。NumPy 是其最强大的库之一,了解 NumPy 通常是当今数据科学家的一项要求。

开始时使用这个备忘单作为指南,需要时再回头看,这样你就可以很好地掌握 NumPy 库了。

与 2k+人一起加入我的电子邮件列表,免费获得完整的 Python for Data Science 备忘单小册子。

Python 中 NumPy 的简单指南

原文:https://towardsdatascience.com/numpy-basics-for-people-in-a-hurry-8e05781503f?source=collection_archive---------34-----------------------

一个用于数据科学的有用的 Python 库。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片(Canva 上制作)

NumPy 是一个 Python 库,大多数数据科学包如 SciPy(科学 Python)、Matplotlib 和 Scikit-learn 在某种程度上都依赖于它。它增加了对大型多维数组和矩阵的支持,以及对这些数组进行操作的大量高级数学函数。

毫无疑问,如果你是一个数据科学爱好者,Numpy 是一个你应该学习的库。这就是为什么,在本指南中,我将向您展示使用 Numpy 库可以做的所有事情。

下面,您将找到本指南涵盖的主题:

**Table of Contents** 1\. [How to Create an Array](#ab1b)
 - [Create arrays from lists](#9254)
 - [Create arrays with 0’s, 1’s, and a random number](#2adf)
 - [Create arrays with a range of elements](#170f)
2\. [Basic Operations](#9853)
3\. [Array Manipulation](#ffcf)
 - [Transposing and Reshaping a matrix](#c675)
 - [Indexing and Slicing](#d1e9)
 - [Concatenate arrays](#99fc)

入门指南

从本指南开始,让我们安装 Numpy 库。

pip install numpy

在这之后,我们必须导入库来开始使用 Numpy。

import numpy as np

如何创建数组

在 Numpy 中,大多数时候我们使用数组。数组是一个数值网格,包含有关原始数据、如何定位元素以及如何解释元素的信息。数组的一些例子有向量(一个单列的数组)和矩阵(一个多列的数组)。

在 Numpy 中创建数组有不同的方法。让我们写一些代码来创建数组。

从列表创建数组

我们可以从 Python 列表中创建一个数组,我们只需将列表插入到np.array()中,如下所示。

a = np.array([1,2,3])IN [0]: print(a)
IN [1]: print(f'Dimension: {a.ndim}')OUT [0]: [1 2 3]
OUT [1]: Dimension: 1

我们刚刚创建了一个 1D 数组(我们可以使用.ndim获得维度)。现在让我们创建一个 2D 数组。

b = np.array([[1.5,2,3], [4,5,6]],dtype=float)IN [0]: print(b)
IN [1]: print(f'Dimension: {b.ndim}')OUT [0]: [[1.5 2\.  3\. ]
          [4\.  5\.  6\. ]]
OUT [1]: Dimension: 2

2D 数组以矩阵或类似表格的格式存储多个相同类型的数据元素,具有许多行和列。

用 0、1 和一个随机数创建数组

我们可以很容易地创建一个用 0 或 1 填充的数组,甚至可以指定维数。

让我们创建一个 2 行 3 列的数组,用 0 填充。

IN [0]: np.zeros([2,3])
OUT [0]: array([[0., 0., 0.],
                [0., 0., 0.]])

如果你想创建一个 1D 数组,只写你想得到的列数。

IN [0]: np.ones(4)
OUT [0]: array([1., 1., 1., 1.])

此外,您可以使用np.full()创建一个随机数数组

IN [0]: np.full([3,3], 8)
OUT [0]: array([[8, 8, 8],
                [8, 8, 8],
                [8, 8, 8]])

创建包含一系列元素的数组

就像内置的range()函数一样,您可以创建一个包含一系列元素的数组。为此,请使用np.arange()

IN [0]: np.arange(5,30,5)
OUT [0]: array([ 5, 10, 15, 20, 25])

上述代码中包含的三个参数是:

  • start :返回整数序列的起始整数
  • stop: 返回整数序列之前的整数(范围截止于stop - 1)
  • **步骤:**整数值,决定序列中每个整数之间的增量

基本操作

使用 Numpy 可以轻松执行基本操作,如加、减、乘、除等。

我们将使用下面的 Numpy 数组进行下面的操作。

a = np.array([2,3,4])
b = np.array([[3,4,5], [6,7,8]])

添加

要添加两个或更多数组,使用np.add(a,b)+符号。

IN [0]: a+b
OUT [1]: np.add(b,a)OUT [0]: [[ 5  7  9]
          [ 8 10 12]]

减法

要从一个数组中减去另一个数组,使用np.subtract(a,b) 符号

IN [0]: a-b
OUT [1]: np.subtract(a,b)OUT [0]: [[-1 -1 -1]
          [-4 -4 -4]]

乘法

要将两个数组相乘,使用np.multiply(a,b)*符号。

IN [0]: a*b
OUT [1]: np.multiply(a,b)OUT [0]: [[ 6 12 20]
          [12 21 32]]

分部

要划分两个数组,使用np.divide(a,b)/符号。

IN [0]: b/a
OUT [1]: np.divide(b,a)OUT [0]: [[1.5  1.33  1.25]
          [3\.   2.33  2\.  ]]

聚合函数

下面列出了一些有用的聚合函数。

a.sum()
a.min()
b.max(axis= 0)
b.cumsum(axis= 1) #Cumulative sum 
a.mean()
np.std(b) #Standard deviation

数组操作

就像 Pandas dataframes 一样,您可以使用 Numpy 数组进行转置、索引、切片等操作。

转置和重塑矩阵

您可以使用.reshape() 改变矩阵的形状,并指出您想要的行数和列数。

我们将使用下面的 Numpy 数组来测试转置和整形。

IN[0]: c = np.array([4,5,6,7,8,9])
IN[1]: print(c)OUT[0]: [4 5 6 7 8 9]

我们将其设置为 2 行 3 列。

IN[0]: c.reshape(2,3)
OUT[0]: [[4 5 6]
         [7 8 9]]

现在,让我们将其设置为 3 列 2 行,并转置数组。要转置数组,您可以使用.transpose().T

IN[0]: c.reshape(3,2)
IN[1]: c.TOUT[0]: [[4 5]
         [6 7]
         [8 9]]IN [1]: [[4 6 8]
         [5 7 9]]

索引和切片

索引和切片 NumPy 数组的工作方式与 Python 列表相同。让我们看一看。

子集化

要获得数组的特定元素,我们可以使用方括号[]并指定行和列

IN[0]: b = np.array([[3,4,5], [6,7,8]])
IN[1]: b[1,2]OUT[0]: array([[3, 4, 5],
              [6, 7, 8]])IN [1]: 8

限幅

切片允许访问数组序列的一部分。您可以指定切片的开始位置和结束位置。

IN[0]: a = np.arange(5,20,2)
IN[1]: a[0:2]OUT[0]: [5 7 9 11 13 15 17 19]
IN [1]: [5 7]

布尔索引

我们也可以在方括号中设置条件来过滤掉元素。

IN[0]: a = np.arange(5,20,2)
IN[1]: a[a<10]OUT[0]: [5 7 9 11 13 15 17 19]
IN [1]: array([5, 7, 9])

串联数组

您可以用np.concatenate()连接两个数组。默认情况下,它的轴=0(行)

a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])IN[0]: np.concatenate((a, b))
OUT[0]: array([[1, 2],
               [3, 4],
               [5, 6],
               [7, 8]])

如果您想要按列连接,请设置 axis=1。

IN[0]: np.concatenate((a, b), axis=1)
OUT[0]: array([[1, 2, 5, 6],
               [3, 4, 7, 8]])

就是这样!本指南所写的所有代码都可以在我的 Github 上找到。你可以把它和熊猫一起使用来解锁更多的 Numpy 功能。下面是使用 Pandas 和 Numpy 库从 Excel 迁移到 Python 的指南。

与 3k 以上的人一起加入我的电子邮件列表,获取我在所有教程中使用的 Python for Data Science 备忘单(免费 PDF)

初学者的 Numpy

原文:https://towardsdatascience.com/numpy-for-beginners-ba9ca0bba441?source=collection_archive---------25-----------------------

承载你的数据科学项目的沉默巨人

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Geran de Klerk 在 Unsplash 上拍摄的照片

到目前为止,我们已经大致了解了 Python 编程,哪些操作符何时使用如何简化可重复的任务或者使用控制流做出决策。因为在水文学(&气象学)中,我们主要与大量数字打交道,所以我们需要进一步研究可以帮助我们处理大量数字的工具。因此,本文涵盖了数据科学界非常流行的库 Numpy。

这篇文章的结构如下:

  • 介绍
  • 创建数组
  • 塑造和重塑
  • 访问元素和分割数组
  • Numpy 的数学和统计
  • 额外内容 Numpy 的速度优势
  • 结论

享受学习的乐趣!

介绍

为什么要用 Numpy?打印时,整数或浮点数的 Python 列表看起来与 Numpy 数组完全一样。两者都可以对一堆数字进行数学运算,两者都可以进行统计计算和比较……所以你可能认为 Numpy 只是一个数学库,具有与列表相似的功能,但真的是这样吗?让我解释一下…

Numpy 数组中的数据是同质类型的,这意味着数组中的所有数据都是同一类型,而列表只是指向对象的指针,即使所有数据都是同一类型。因此,Numpy 数组使用的内存比常规列表少得多。此外,大多数 Numpy 操作都是用 C 语言实现的,这意味着避免了 Python 循环和数据类型动态检查的开销。与 Python 列表相比,这显著提高了处理速度。

我们经常会遇到包含数万行数据的大型数据集,只要想想某个国家或地区的每小时气温测量值就知道了,因为测量是从该地区开始的。如果你的气象服务正在测量过去 50 年的每小时气温,那么仅仅一个气象站就有超过 400 000 行的数据。

如何安装 Numpy?

如果使用 Anaconda,Numpy 是预安装在基础环境中的。然而,通常情况下,为新项目创建新环境是一个好的实践。要安装 Numpy,我们运行 Anaconda 提示符并键入:

康达安装数量

或者

conda install -c 蟒蛇 numpy

如果正在使用 pip,可以通过键入以下命令来安装 Numpy:

pip 安装数量

如何导入 Numpy?

当导入包括 Numpy 在内的某些库时,我们遵循一个约定,基本上这意味着我们对库使用公认的缩写。在 Numpy 的情况下,我们使用“ np ”。

将 numpy 作为 np 导入

目标是我们的代码是可复制的,并且世界上的每个 Python 程序员都知道下面这行代码的作用:

a = np.array([3,4])

恭喜你,如果你已经导入了 Numpy,并且使用了上面的命令,你已经成功地创建了你的第一个 Numpy 数组。让我们看看如果把它打印出来会发生什么。打印给我们一些看起来像列表的东西,但它不是。当我们检查类型时,我们看到这是一个" numpy.ndarray " ( n 维数组)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

向量?

在这个例子中,我们看到了如何创建一维数组。如果你还记得数学中的向量,一维 Numpy 数组基本上是一个向量。由于我们给出了两个数字,3 和 4,这个向量位于 2 维空间(几何平面)。这和数学中有一个矢量是一样的。

v= 3i + 4j

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

摘自:https://en.wikiversity.org/

在计算机科学中,向量只是列表,其中列表的长度(在我们的例子中是 2)是向量的维数。在数据科学术语中,向量代表一个或多个对象的特征。想想周一的气象测量,你可以测量气温、降雨量、风速、积雪深度等。要了解更多关于矢量的知识,我强烈推荐 3Blue1Brown 的视频。

创建数组

上面我们已经看到了如何在 Numpy 中创建一个简单的一维数组。通常,我们的数据来自更多的维度,我们有多个特征(如上),但也有一周中多天的测量。在这种情况下,我们需要向数组中添加第二个维度。让我们来看一些二维数组。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

为了创建一个二维数组,我们提供了一个包含两个列表的列表。将该数组视为周一(第一行)和周二(第二行/列表)的测量值,其中第一列是气温,第二列是降水量,第三列是风速,第四列是积雪深度。excel 截图应该把事情说清楚了。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

Numpy 还提供了一些有用的函数来创建 0 或 1 的数组。自己尝试以下命令,并打印出结果。

为了演示如何获得新创建的数组的维数,我将使用 np.ones 函数和 ndim 属性。

我们的数组是四维的,但是四维数组是什么样子的呢?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

如果我们仔细观察,我们可以确定维数,如果我们计算数组开头或结尾的方括号,这也是一个方便的方法🙂

另一个有用的方法是 arange 。它用于获得均匀间隔的数组。我们需要指定结束号 ( intfloat )。

Numpy 然后假设起点是零。我们还可以提供起点终点

并且,我们可以指定步骤如下:

类似地,使用方法 linspace 我们可以创建一个数组,但是代替步骤的是, linspace 获取数组中元素的数量。在这里,我们创建了一个包含 7 到 12 个元素的数组。同样,与 np.arange 和大多数 Python 方法相反,这里的最后一个数字(结束数字)是包括在内的**。**

塑造和重塑

之前,我们检查了我们的one数组有多少个维度(或轴)。但是如果我们对每个维度中有多少元素感兴趣呢?shape 属性就派上用场了。因为我们有 4 个维度,所以我们得到 4 个数元组。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

为了计算整个数组中元素的数量,我们使用了 size 属性。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

为了改变数组的形状,我们使用。shape()方法。不过要小心,新整形的数组必须和旧的一样大。让我解释一下…

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

最初的零数组具有(2,3)的形状,我们可以将其整形为(3,2)、(6,1)或(1,6),因为它的大小为 6 个元素。我要提到的是,在将它重新整形为(6,1)或(1,6)的情况下,我们将维数从二维数组更改为一维数组,但是只要我们注意数组的大小,我们就是安全的。

一维数组的一个便捷“捷径”是 flatten()ravel() 方法。不同之处在于 flatten 创建原始数组的一维副本,而 ravel 创建对原始数组的引用。因此,使用 ravel()的结果是改变了新创建的数组中的一些数据,同时也改变了原始数组中的数据。

用法取决于具体的任务,大多数时候我使用的是 flatten() 方法。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

最后但同样重要的是,我们不要忘记 transpose()方法。这个方法只是交换数组的行和列。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

在这种情况下,结果与之前的整形相同。在多维数组的情况下,所有的维度都被交换,让我们看看。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

访问元素和分割数组

到目前为止,我们已经了解了如何创建、寻找比例以及重塑或展平数组。现在让我们把注意力转向使用索引切片从数组中提取数据。对数组进行切片意味着通过提供想要的元素索引来访问它的元素。

切片的默认语法包括数组名和方括号,就像 Python 列表一样,如下所示:

数组名称[开始索引:结束索引:步长大小]

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

在我们的阵列中,温度测量从 1:00 开始。为了说明问题,我们打印出了 12:00、14:00、16:00、18:00 和 20:00 的每小时温度。

如果我们不定义步长,指定范围内的每个元素都会被返回。例如,如果我们需要从 7:00 到 12:00 的每小时温度。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

在列表中通常如此,在 Numpy 中也是如此,start_index 包含在内,而 end_index 不包含在内。此外,数组中的第一个索引始终为零。因此,为了访问 7:00 的温度,我们输入第 6 个索引。由于我们需要测量到 12(在第 11 个索引处),我们提供了第 12 个索引(不包括)。

分割二维数组

当分割一个二维数组时,我们需要指定我们想要的元素的行和列。一开始可能有点棘手,但是当在几个例子中尝试后,很快就会变得非常简单。

对二维数组进行切片的语法如下:

array _ name[行 _ 开始 _ 索引:行 _ 结束 _ 索引:行 _ 步长大小,列 _ 开始 _ 索引:列 _ 结束 _ 索引:列 _ 步长大小]

为了了解如何分割一个二维数组,我首先将 weather_data 数组扩展到一整周,这样我们将得到一个 shape (7,4)数组。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

假设我们想知道每周的降水量。所以我们需要切掉所有的行和第二列。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

在本例中,为了选择所有的行,我们使用了一个冒号符号( : ) 为了选择第 2 列,我们使用索引值为 1 的(记住,索引从零开始)。

随意尝试其他的可能性,我会比较数学中的切片和积分,有一定的规则可循,但是熟能生巧

负切片也是允许的,其工作方式与 python 列表相同。数组中的最后一项的索引为-1。

在数组中“查找”数据

在数组中查找数据的另一种方法是使用 Numpy 中一个非常流行的函数, np.where() 。该函数返回满足条件的元素的索引。通常,它用于查找大于、等于或小于一个数字的元素。 np.where() 的基本语法如下:

np.where(条件[,x,y])

xy 是参数,可以用来用替换数组中满足给定条件的值。要么我们不提供 x 和 y (我们只需要找到满足条件的索引或值),要么提供 x 和 y 然后找到的索引处的值由 x(如果为真)改变,或者 y(如果条件为假)。非常类似于 MS Excel 中的 IF() 函数。

假设我们想打印出气温高于 14.5 摄氏度的日子的指数。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

这里我们有两件重要的事情:首先,我们使用上面学到的切片来选择第一列(所有行,因为我们搜索所有工作日),然后我们设置条件> 14.5。

假设我们想要将所有大于 14.5 的温度值转换为华氏温度,并将结果数组存储到变量 weather_b

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

同样,我们首先提供一个条件(> 14.5°C),然后我们给出如果为真时使用什么值(将该值乘以 1.8 并加 32),如果为假时使用什么值(使用现有值)。注意,我们总是对数组进行切片,因为我们只处理第一列。

我们现在可以创建一个名为 weather_f(与 weather_data 相同)的新数组,但是温度值将被替换为 Fahrenheits 中的值。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

首先,我们复制一份 weather_data(记住 flatten()和 ravel()方法),以避免原始 weather_data 数组发生变化,然后我们对新的 weather_f(第一列)进行切片,并用 weather_b 中计算的值替换这些值。

Numpy 的数学和统计

最后,我们来到了我最喜欢的数字部分,数学和统计运算。在我看来,这就是 Numpy 如此伟大的原因,它优于普通列表的相同操作。这是处理大量数字数据时的简单性和速度优势。我们先来看看数学运算。我会提供一个关于除法的例子,但是一般的语法对于其他运算是一样的,可以在官方的 Numpy pages 中查找。

我们的 weather_data 数组包含以毫米为单位的降水数据,让我们将它们转换成米。要将毫米转换成米,我们需要将数值除以 1000。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

我们再次使用切片来选择数组的第二列,并将值除以 1000。在实践中,尝试用转换成米的值替换 weather_f 数组中的降水值。(您可以直接在 weather_f 数组上进行更改)

至于统计示例,我将使用最常见的情况,我们需要计算一周的平均温度、降水量、风速和雪深值。计算平均值的 Numpy 函数称为 np.mean()np.mean() 函数的基本语法如下:

np.mean(a,axis=None,dtype=None,out=None,keepdims= ,*,where= )

对于我们的情况,重要的部分是轴。因为我们的目标是计算每一列的平均值,所以我们需要将轴参数设置为 0。将轴设置为 1,将产生行方式的结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

其他统计函数保留了相同或相似的语法,可以在 Numpy 官方网站的统计部分中查找。

额外内容 Numpy 的速度优势

我提到过 Numpy 也有一些速度优势,这可能会让你动心。让我们看看实际情况。Numpy 真的比 for 循环快吗?

首先,我们将创建一个随机的浮动数组,假设它是在美国某个地方测量的每小时气温。数组的长度是 30 000。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

我们想把这些数字转换成摄氏度。让我们使用 for 循环和 Python 列表来测量所需的时间,然后使用 Numpy。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

因此,在 Python 列表上使用循环所花费的时间大约为 5 ms,而使用 Numpy 数组,相同的操作花费的时间不到 1 ms。因此,在这个特定的情况下(任务), Numpy 大约快 5 倍。

此外,请考虑这个测试并不完全适用,它真的取决于你的计算机的速度(主要是 CPU)和选择的任务。因为这不是本文的主题,所以我将把它留给您去查看其他介绍 Numpy 的速度优势的文章,并且希望您自己在使用数据的特定任务上尝试一下。

结论

恭喜,我们已经介绍了 Numpy 的基础知识。但是请记住,Numpy 远不止这些,练习很重要。这里,我主要介绍了我在任务和项目中经常使用的函数,但是 Numpy 中还有更多函数和方法。

请务必查看 Numpy 官方网站,了解更多与您的项目或任务相关的有用信息。一开始不要气馁,所有这些语法看起来都有点复杂,但是相信我,比你想象的要快,你会很快采用它们。🙂

我的下一篇文章将介绍数据科学应用程序中另一个流行的库,名为 Pandas。请继续收听,直到下次继续练习,因为熟能生巧。

对于这篇文章或我在媒体上的其他文章有任何问题或建议,欢迎通过 LinkedIn 联系我。

感谢您抽出时间,干杯!🙂

基于英伟达 RTX A6000 的数据科学工作站

原文:https://towardsdatascience.com/nvidia-rtx-a6000-based-data-science-workstation-2e05a4b846cc?source=collection_archive---------9-----------------------

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我的基于英伟达 RTX A6000 的机器学习工作站

我的主要机器学习工作站是围绕一个 NVIDIA RTX A6000 构建的,NVIDIA 很好心地提供给我的 YouTube 频道。上面可以看到完整的机器。该系统采用了 AMD 锐龙线程 3960X,3.8 GHz 处理器,24 个内核,128 GB 3200 RAM,英伟达 RTX A6000 w/48 GB GPU DRAM,以及 4 TB M.2 固定存储。这是我的泰坦基于 RTX 的机器学习工作站的升级,我以前写过关于的文章。

不是每个人都适合制造电脑。在高端,建立一个机器可以节省资金,并允许您准确地指定您希望的硬件。此外,随着更高级的组件变得可用或更经济,自定义计算机版本允许您计划和执行增量升级路径。我建造的机器学习工作站将花费大约 8000 美元来建造你自己。我还提供了一些建议来提高或降低这个价格。

我将从描述我的用例开始。我处理的任务可能是 GPU 或 CPU 密集型的。有时,我确实希望有大量的 RAM 用于数据处理和暂存。对于超出这台机器能力的内存需求,我通常使用 DaskRAPIDSBlazingSQL 。因为这些需求,我在 CPU、RAM 和硬盘访问上花费了相当多的钱。因为我的处理需求最常见的瓶颈是神经网络训练,所以我选择了高端 GPU。我也喜欢在 Windows 和 Ubuntu 之间双重启动。为了实现双启动,我使用了两个 2TB M.2 固态驱动器。我不喜欢把 Windows 和 Linux 放在同一个硬盘的不同分区上。

机器规格和零件清单

我构建的计算机是一个 3.8 GHz(4.5 GHz 的加速时钟速率)24 核 AMD Threadripper (3960X),128GB 内存和一个英伟达 RTX A6000 。在我发明电脑的时候,A6000 是台式机中最先进的选择。该版本的亮点包括:

硬盘足够快,程序加载非常快。此外,在内存和硬盘之间移动我的数据并不慢。我额外支付了更快的 RAM,希望加快 CPU 和 GPU RAM 之间的加载速度。进一步的基准测试将让我知道这有多好。

选择 GPU

GPU 是机器学习工作站的重要考虑因素。而机器学习工程师可能希望运行使用机器的图形潜力的复杂可视化;大多数现代 GPU 应该能够处理用户的图形需求。GPU 的数字处理能力是机器学习工作站的一个重要特征。对于游戏系统来说,应该在 AMD 和 NVIDIA 之间做出选择。对于机器学习,特别是深度学习,GPU 的选择真的只是英伟达。

CUDA 或 OpenCL 是允许 GPU 充当软件数学引擎的功能。TensorFlow、PyTorch 等常用 CUDA,需要和 NVIDIA。OpenCL 更加开放,支持来自英特尔、AMD 和 NVIDIA 的 GPU。然而,由于各种原因,主要是性能原因,CUDA 得到了最广泛的支持。还有,NVIDIA 主导了 AWS 和 Azure 的云平台。谷歌云平台(GCP)确实提供了一种叫做张量处理单元(TPU)的替代技术;然而,TPU 在本地系统上并不常见。出于这些原因,尤其是云兼容性,我坚持使用 NVIDIA 的 GPU 产品。

NVIDIA 为游戏提供 GeForce GPUs,为高级工作站提供 NVIDIA RTX A6000,为加密挖掘提供 CMP,为服务器机房提供 A100/A40。以深度学习为中心的 GPU,如英伟达 RTX A6000 和 GeForce 3090,提供了更多的内存,3090 为 24 个,A6000 为 48 个。这里显示了我的系统中的 A6000 GPU。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

英伟达 RTX A6000

这张图片也显示了我的 CPU 冷却器,我使用的是一体式(AIO)液体冷却器。我更喜欢液体冷却器,因为它比大型空气冷却器更容易接近我的零件。

英特尔还是 AMD

我希望我的工作站足够灵活,能够为 GPU 和以 CPU 为中心的任务提供高性能。基于 GPU 的深度学习有多伟大;我确实发现自己在使用 CPU 繁重的任务进行数据预处理和一些可视化。另外,由于我经常用 Python 编写自动化任务的代码;我能够利用多核技术来设计我的软件。除非你正在制造一台计算机,否则你很少能直接选择英特尔或 AMD 有时你可以从硬件制造商那里选择 CPU 类型。

在观看/阅读了相当数量的关于英特尔 vs AMD 现状的报道后;我得出了以下结论。AMD 提供更多核心;然而以稍微降低的时钟速度。所以 AMD 在多线程软件上效率会更高。英特尔将在并行性较低的软件上更加高效,这些软件受益于更大的单核速度优势。因为我的大部分软件是多线程的,我可以选择设计自己定制的多线程软件,所以我选择了 AMD。

我选了一个 24 核的 AMD RYZEN ThreadRipper,适配一个 TRX4 插座,这是目前 AMD 最新的插座类型。这意味着我以后可以轻松地将我的 CPU 升级到更高级的 AMD 产品。传统上,我一直使用英特尔。我在 AMD 遇到的唯一小麻烦是,有时我必须等待最新的“微软内幕”预发布 Windows 版本。

操作系统选择

对于这台电脑,我决定用 Windows 10 Pro。我对微软的 Linux 子系统(LSU)能力印象非常深刻;尤其是现在 Linux 子系统可以访问底层的 GPU。我刚刚开始使用 LSU-2,所以我对这个系统的看法还在发展中。我希望在后面的文章和视频中发布更多关于 LSU 的内容。

与云的成本比较

在构建这台机器之前,我将我的大部分 GPU 工作负载发送到了云。我最常见的任务要么是 Kaggle 竞赛,要么是为我在华府大学深度学习课程重新运行和优化示例。这些工作负载的云成本并不小。为了将这台机器与 AWS 云进行比较,我使用了以下成本(截至 2020 年 7 月):

  • AWS 工作区:16 个 vCPU,122 GiB 内存,1 个 GPU,8 GiB 视频内存,每月 999.00 美元或每月 66.00 美元,每小时 11.62 美元。

上面引用的 AWS workspaces 实例比我的机器弱得多;然而,它是最接近的等效物。我有 24GB 的显存;而 AWS 机器只有 8 个。这可能需要对神经网络训练大小进行一些修改。此外,我有 24 个 CPU,而 AWS 上有 16 个,但 AWS 上的内存更多。在 999 美元/月,这对于一个沉重的负荷来说是最有意义的,我会在 5 个月内出来。

如果你愿意围绕你的工作流程设计一些管道代码,并使用一些 AWS 专有技术,你可以通过使用 SageMaker 节省可观的 AWS 费用。我在这里不考虑 SageMaker 或直接的 EC2 实例,因为我正在寻找与我刚刚构建的系统最接近的桌面体验。

缩小比例

我敢肯定,阅读这篇文章的人都觉得我在这台机器上花了太多或太少。我曾经使用过基于特斯拉 V100 的先进系统,其造价是这台机器造价的 5 到 10 倍。如果你想少花钱,有很多选择。

其中最简单的就是审美。RGB 在系统构建者使用的组件中非常流行。你可以在下图中看到我的系统上的一些 RGB。不熟悉 RGB?任何发光的都是 RGB。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

RGB 很漂亮,但是增加了成本

我是一个 Youtuber 用户,所以电脑是我“生活”中一个有趣的组成部分如果你打算把机器塞到桌子下面,买一个便宜但容易拿的箱子,不要用 RGB 组件。

您可以缩减的零件:

  • 硬盘速度:实际上,硬盘速度只是将数据加载到 RAM 中,一旦数据进入 RAM,硬盘速度就变得不那么重要了。
  • RAM 速度:较慢的 RAM 仍然可以让你通过。可能反正大部分处理都是在 GPU 上完成的。
  • CPU :对于机器学习来说,内核越多越好。内核越少,性能越差。
  • GPU : CUDA 核心决定你的 GPU 训练的速度。GPU 内存决定了你需要对你的批处理和网络结构有多聪明来运行一些东西。你不一定需要英伟达 RTX A6000。一个或多个 3080 或 3090 可能是一个选项。

已完成构建的 YouTube 视频

我在 YouTube 上制作了这台电脑的视频。我的侄子内森协助建造。你可以在这里看到这个视频。

https://www.youtube.com/watch?v=85-K7qTSvS8

物体探测解释:R-CNN

原文:https://towardsdatascience.com/object-detection-explained-r-cnn-a6c813937a76?source=collection_archive---------3-----------------------

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

马特·阿特兹途经 Unsplash

基于区域的卷积神经网络

目标检测包括两个独立的任务,即分类和定位。R-CNN 代表基于区域的卷积神经网络。R-CNN 系列背后的关键概念是地区提案。区域建议用于定位图像中的对象。在接下来的博客中,我决定写一些在物体检测中使用的不同方法和架构。因此,我很高兴从基于 R-CNN 的物体探测器开始这次旅程。

工作细节

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

RCNN:工作细节。来源:【https://arxiv.org/pdf/1311.2524.pdf

如上图所示,在将图像通过网络之前,我们需要使用选择性搜索等算法提取区域建议或感兴趣区域。然后,我们需要调整(包装)所有提取的作物,并通过网络传递它们。

最后,网络从 C + 1 中分配一个类别,包括给定作物的“背景”标签、类别。此外,它还预测 delta Xs 和 Ys 来塑造给定的作物。

提取区域建议

选择性搜索是一种用于对象定位的区域提议算法,它基于区域的像素强度将区域分组在一起。因此,它根据相似像素的层次分组来对像素进行分组。在原始论文中,作者摘录了大约 2000 条建议。

正面和反面的例子

在我们提取我们的区域提案之后,我们还必须为它们添加标签以便进行培训。因此,作者将 IOU 至少为 0.5 的所有提案标上任何基本事实边界框及其相应的类别。但是,IOU 低于 0.3 的所有其他区域提案都被标记为背景。因此,其余的都被忽略了。

包围盒回归

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

包围盒回归。来源:https://arxiv.org/pdf/1311.2524.pdf

上图显示了 CNN 预测的三角洲。所以,x,y 是中心坐标。而 w、h 分别是宽度和高度。最后,G 和 P 分别代表地面实况包围盒和区域提议。值得注意的是,边界框丢失仅针对正样本进行计算。

失败

总损失计算为分类和回归损失的总和。但是后一个有一个系数λ,原文中是 1000。注意,对于负面例子,回归损失被忽略。

体系结构

通常,我们通过 VGG 16 或 ResNet 50 传递调整后的裁剪,以获得特征。它们随后通过输出预测的完全连接的层。

如果你想看完整的代码,你可以很容易地在我的 GitHub 上找到一个木星笔记本。

一些遗言

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

气球数据集

我只训练了它 5 个时期,所以你可以看到它能够检测到图像中的一些气球。为什么不再使用它有几个缺点。最大的缺点是用于提议提取的选择性搜索算法。考虑到算法是在 cpu 上执行的,推理时间变得很慢。此外,所有提案都必须调整大小并通过网络传递,这也增加了开销。因此,我将写一些其他的算法来克服这些问题。

丰富的特征层次,用于精确的对象检测和语义分割

相关文章

https://medium.com/dataseries/understanding-selective-search-for-object-detection-3f38709067d7

使用 Keras 和确定的对象检测

原文:https://towardsdatascience.com/object-detection-with-keras-and-determined-2120e7ef2df9?source=collection_archive---------36-----------------------

由萨姆希塔·阿拉和尼尔·康威组成

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由社拍摄。从去毛刺处切掉

目标检测是计算机视觉中的一项重要任务。使用深度学习进行对象检测可以产生高度准确的模型,但开发人员也可能遇到几个挑战。首先,深度学习模型的训练成本非常高——即使使用 GPU,现代物体检测模型也可能需要许多小时的计算才能从头开始训练。管理这些 GPU 并在许多 GPU 上并行运行工作负载变得非常复杂。第二,对象检测模型通常具有许多超参数。虽然存在自动调整超参数的算法,但是在实践中应用这些算法需要运行数百或数千个训练任务,并比较所有这些模型的性能。谈复杂!

Determined 使得训练深度学习模型和调整超参数变得更加容易。它可以自动将作业调度到一个 GPU 集群(本地或云中)上,并包括用于高效超参数调整的一流算法。它提供了一个实时仪表板来跟踪和可视化结果,使您能够清楚地了解您的模型的实时表现——作为一名模型开发人员,您可以快速启动培训工作或惠普搜索,并确定将处理其余工作。

Determined 还可以轻松地同时利用多个 GPU,这意味着您可以更快地训练单个模型(使用分布式训练)或在更短的时间内搜索巨大的超参数空间——无需修改代码或手动管理 GPU。Determined 让您专注于模型开发,因此您可以更快地从一个想法变成生产中的工作模型。

决心比看上去的要多得多!要了解决心还能做什么,请查看 文档

在本教程中,您将从在 AWS 上安装 Determined 开始,然后修改一个现成的tf.keras对象检测模型来使用 Determined。接下来,您将在单个 GPU 上训练模型,最后进行超参数搜索。

正在使用的模型基于使用 Keras 教程的 RetinaNet 的对象检测。

RetinaNet 目标检测模型概述

RetinaNet 是一个两级检测器,它可以定位和检测图像中的物体。它使用特征金字塔网络在多个尺度上检测对象,并引入新的损失,焦点损失函数,以缓解前景-背景类别极端不平衡的问题。要深入了解该模型,可以参考原文,密集物体探测的焦损

设置已确定

我已经在 AWS p2.xlarge (P2)实例上部署了深度学习模型。但是,您可以使用 GCP 或者自己选择的本地 Linux 实例。运行主服务器的实例应该至少有 200GB 的磁盘空间;每个代理实例应该有 50GB 的磁盘空间。对于本教程,我们将使用单个代理,并将主代理和代理部署到同一个 AWS 实例。

要在单个 AWS 实例上安装 Determined,请按照下列步骤操作:

每一步都附有与 Amazon Linux/RHEL/CentOS Linux 实例兼容的代码。

步骤 1 :设置您的 Linux 实例——AWS、GCP 或内部部署。

步骤 2 :确保 Python3 和 Docker 安装在您的 Linux 实例上。

sudo yum install python3 docker

第三步:启动 docker 服务。

sudo service docker start

步骤 4 :给 docker 套接字分配全局读、写和执行权限。(这不是生产系统的推荐做法!)

sudo chmod 777 /var/run/docker.sock

步骤 5 :使用 pip 安装det-deploy,这是一个工具,我们将用它来安装已确定系统的其余部分。

sudo pip3 install determined-deploy

第六步:使用 pip 安装 Determined 的 CLI。CLI 用于提交新的深度学习工作负载,以供所确定的系统的其余部分执行。

sudo pip3 install determined-cli

第七步:设置 DET_MASTER 环境变量;这将告诉 CLI 所确定的主服务器正在运行的 IP 地址或主机名。在本例中,我们将主服务器部署到安装了 det-deploy 的同一个实例,因此我们可以使用“localhost”。

export DET_MASTER=<master-hostip>

第八步:调出确定的主人和代理。

如果您的系统有 GPU,请确保安装了 NVIDIA 驱动程序,以及 NVIDIA 容器工具包。参考链接:特斯拉集装箱工具包

如果您使用带有 GPU 的 AWS 实例,请运行以下命令:

det-deploy local cluster-up

如果您使用不带 GPU 的 AWS 实例,请运行以下命令:

det-deploy local cluster-up --no-gpu

现在,您应该在工作区中设置了一个确定的集群。

上述过程不是安装 Determined 的唯一方法;对于生产部署,建议您在单个实例(无 GPU)上运行 master,并配置 Determined,以便在提交新的深度学习工作负载时,自动供应配备 GPU 的实例,称为“动态代理”。有关更多详细信息,请查阅安装文档

调整 RetinaNet 以使用 Determined

将模型移植到 Determined 包括使模型与 Determined API 兼容。有关修改现有模型代码以使用 Determined 的更多信息,请查看 Determined tf.keras 教程

一旦我们的模型代码适应了 Determined,我们将训练模型的单个实例并执行超参数搜索——而不改变我们的模型代码!

你可以在这里下载模型源代码

概观

将模型移植到 Determined 的 API 通常很简单;如果我们有一个现有的训练脚本,大部分代码可以保持不变,我们只需要“重构”它以适应四个不同的步骤:

  • 初始化您的模型
  • 定义您的模型
  • 加载训练数据集
  • 加载验证数据集

你首先要初始化一个模型。这一步包括定义训练模型时可能需要的所有超参数。

接下来,定义模型的层,以及使用的优化器和损耗。

分别加载训练和验证模型所需的训练和验证数据。

然后在一个基于 TensorFlow 的试验类中安排这四个方法——一个继承自determined . keras . tfkerastrial类的 Python 类,如下所示:

这个试验类定义了你的模型的原型。

深度学习算法建模中通常涉及的实际训练和测试程序如何?

已确定提供内置的训练循环,在加载训练数据后会自动调用该循环。它记录训练和验证指标,并定期检查模型的状态。

有了原型之后,定义每个试验类方法。

初始化

这一步涉及到我们在 Python 类中看到的典型的__init__()方法。Determined 向该方法传递一个参数 TrialContext ,该参数包含有关训练时使用的超参数的信息,以及其他有用的数据。

对于当前的对象检测模型,将上下文存储到实例变量中。

**def** __init__(self, context: TFKerasTrialContext):
    self.context = context

创建一个名为startup-hook.sh的文件,内容如下所示。一个启动钩子是一个特殊的文件,在调用用户的模型代码之前,它将在每个试验容器的启动过程中被执行。这对于定制容器环境(例如,设置环境变量)和安装额外的依赖项很有用。在这种情况下,我们将使用一个启动钩子来安装tensorflow_datasets库:

pip install -q -U tensorflow-datasets==4.1.0

构建模型

这一步包括定义模型的架构。试用类中的 build_model() 方法返回一个编译后的 tf.keras.Model 对象。在这个方法中,模型在编译之前必须通过调用self . context . wrap _ model()进行包装,优化器需要通过调用self . context . wrap _ optimizer()进行包装。

**def** build_model(self):
    resnet50_backbone = get_backbone()
    loss_fn = RetinaNetLoss(self.context.get_hparam("num_classes"))
    model = RetinaNet(self.context.get_hparam("num_classes"), resnet50_backbone)
    model = self.context.wrap_model(model)
    learning_rate_fn = tf.optimizers.schedules.PiecewiseConstantDecay(
        boundaries=[125, 250, 500, 240000, 360000],
        values=self.context.get_hparam("learning_rate")
    )

    optimizer = tf.optimizers.SGD(learning_rate=learning_rate_fn, momentum=0.9)
    optimizer = self.context.wrap_optimizer(optimizer)
    model.compile(loss=loss_fn, optimizer=optimizer)
    **return** model

加载数据

接下来,分别使用方法build _ training _ data _ loader()build _ testing _ data _ loader()加载训练和测试数据集。Determined 支持几个将数据加载到 tf.keras 模型中的 API,包括 tf.keras.Sequence、tf.data.Dataset,甚至一对 NumPy 数组(用于小型数据集或测试运行)。

现在,将对象检测数据加载到一个 tf.data.Dataset 对象中。

**def** build_training_data_loader(self):
    label_encoder = LabelEncoder()
    train, dataset_info = tfds.load(
        "coco/2017", split="train[:5%]", with_info=True
    )

    autotune = tf.data.experimental.AUTOTUNE
    train = train.map(preprocess_data, num_parallel_calls=autotune)
    train = self.context.wrap_dataset(train)
    train_dataset = train.cache().shuffle(8 * self.context.get_hparam("batch_size"))
    train_dataset = train_dataset.padded_batch(
        batch_size=self.context.get_hparam("batch_size"),
        padding_values=(0.0, 1e-8, -1),
        drop_remainder=True
    )

    train_dataset = train_dataset.map(
        label_encoder.encode_batch, num_parallel_calls=autotune
    )

    train_dataset = train_dataset.apply(tf.data.experimental.ignore_errors())
    train_dataset = train_dataset.prefetch(autotune)

    **return** train_dataset

类似地,使用一个 tf.data.Dataset 对象加载测试数据。

**def** build_validation_data_loader(self):
    label_encoder = LabelEncoder()
    test, dataset_info = tfds.load(
        "coco/2017", split="validation", with_info=True
    )

    autotune = tf.data.experimental.AUTOTUNE
    test = test.map(preprocess_data, num_parallel_calls=autotune)
    test = self.context.wrap_dataset(test)
    test_dataset = test.padded_batch(
        batch_size=1, padding_values=(0.0, 1e-8, -1), drop_remainder=True
    )

    test_dataset = test_dataset.map(label_encoder.encode_batch, num_parallel_calls=autotune)
    test_dataset = test_dataset.apply(tf.data.experimental.ignore_errors())
    test_dataset = test_dataset.prefetch(autotune)
    **return** test_dataset

确保包含运行模型所需的其他函数。该模型的完整源代码可以在这里找到。

训练模型

现在您已经构建了您的试验类,是时候定义超参数来训练您的模型了。为此,创建一个实验,通过在const.yaml文件中定义超参数来训练模型的单个实例。

以下是对其中一些设置的详细描述:

  • global_batch_size:用于训练的批量。每个实验都必须指定。
  • num_classes:数据集中存在的类的数量。
  • records_per_epoch:用于训练的每个历元的记录数。
  • searcher:如何寻找实验的超参数空间。暂时设置为single不做超参数搜索。
  • metric:评估模型性能的验证度量的名称。
  • max_length:模型应该训练多长时间。在这种情况下,我们配置 Determined 来根据 20 个时期的训练数据训练模型。一个时期中的记录数由上面的records_per_epoch变量设置,因此这相当于在 100,000 条数据记录上训练模型。
  • entrypoint:试验班名称。model_def是 Python 文件,ObjectDetectionTrial是类。
  • environment:这里我们配置任务环境使用 TensorFlow 2.2,这是该模型所需要的。默认情况下,确定使用 TensorFlow 1.x

要了解更多关于实验配置的信息,请参见实验配置参考

进行实验

原型制作完成后,您现在可以使用确定的 CLI 创建一个实验。为此,请使用以下命令:

det experiment create -f const.yaml .

‘.’指定要使用的目录(.表示当前目录),而const.yaml是您之前创建的配置文件。-f标志用于在您的终端上打印详细的输出,并实时“跟踪”实验的进度。

评估模型

模型评估由 Determined 自动完成。要查看输出,请转到http://<master-hostname>:8080并使用空密码作为“已确定”用户登录。

您可以选择您的实验,并在 TensorBoard 中查看训练和验证损失曲线。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

使用上述设置训练模型,我们得到约 3.40 的验证损失。

您还可以观察最佳验证指标和检查点,如确定的仪表板中所提供的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

调整超参数

超参数调整是训练和评估模型的重要步骤。该模型的有效性和准确性高度依赖于其超参数的值。然而,超参数搜索是一个具有挑战性的问题,既耗时又难以跟踪。

Determined 为用户提供了一个易于使用的超参数搜索界面,以自动使用内置算法,跟踪和可视化实验。

Determined 提供了多种搜索算法,您可以使用其中任何一种算法来执行您的超参数搜索。对于大多数情况,“adaptive _ Asha”搜索方法通常是一个不错的选择:它是一种基于早期停止的最先进技术,通过有原则地定期放弃低性能超参数配置,优于随机搜索等传统技术。

用以下内容创建一个新文件adaptive.yaml:

Determined 让模型开发人员来声明哪些超参数是重要的,并为这些超参数中的每一个定义搜索空间。在这种情况下,除了学习率之外,我们保持所有超参数固定不变,在学习率中,我们指定了一个可能的学习率列表来研究。

这里,我们将adaptive_asha搜索器配置为总共探索两个不同的超参数配置,并针对最多十个时期的训练数据训练每个配置。这是一个最小的超参数搜索,所以它应该运行得很快!

使用det experiment create -f adaptive.yaml .命令创建实验后,您可以查看与两次试验相对应的训练和验证损失图。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

您还可以比较两次试验之间的验证损失。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

这只是一个起点:您可以修改配置文件,使 Determined 搜索许多其他超参数的良好设置,还可以增加资源预算,以便探索更多的配置—然后您可以观察模型的性能如何变化!

结论

如上所述,Determined 在没有任何人工干预的情况下进行自动模型评估,从而简化了模型开发和优化。已确定自动执行设备初始化、计划、模型检查点和容错。

此外,Determined 还提供了一个执行分布式训练的选项,以加快训练过程并优化利用您的处理能力。

后续步骤

基于 Tensorflow 模型和 OpenCV 的目标检测

原文:https://towardsdatascience.com/object-detection-with-tensorflow-model-and-opencv-d839f3e42849?source=collection_archive---------0-----------------------

使用经过训练的模型来识别静态图像和实时视频上的对象

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

来源

在这篇文章中,我将演示如何使用一个经过训练的模型来检测图像和视频中的对象,使用两个最好的库来解决这类问题。对于检测,我们需要一个模型,能够预测图像中的多个类别,并返回这些对象的位置,以便我们可以在图像上放置盒子。

模型

我们将使用来自 Tensorflow Hub 库中的模型,该库中有多个在各种数据集内训练过的准备部署模型,可以解决各种问题。对于我们的使用,我过滤了为对象检测任务训练的模型和 TFLite 格式的模型。这种格式通常用于物联网应用,因为它的尺寸小,性能比更大的模型更快。我选择这种格式,因为我打算在未来的项目中在 Rasberry Pi 上使用这种模型。

选择的模型是 EfficientDet-Lite2 物体探测模型。它在具有 91 个不同标签的 COCO17 数据集上进行训练,并针对 TFLite 应用进行了优化。该模型返回:

  1. 检测的框边界;
  2. 检测分数(给定类别的概率);
  3. 检测类;
  4. 检测次数。

检测物体

我将把这个部分分为两个部分:静态图像的检测和网络视频的检测。

静态图像

我们将从检测 Unsplash 图像中的对象开始:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

来源

因此,我们要做的第一件事是加载这个图像,并将其处理为 TensorFlow 模型的预期格式。

基本上,我们使用 OpenCV 加载原始图像,并将其转换为模型格式的 RGB 张量。

现在我们可以加载模型和标签了:

该模型是直接从网站上加载的,但是,您可以将其下载到您的计算机上,以获得更好的加载性能。文本标签 CSV 可在项目报告中找到。

现在,我们可以创建预测,并将找到的方框和标签放入图像中:

现在,如果我们运行 plt.imshow(img_boxes) ,我们会得到以下输出:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

修改后的来源

实时网络摄像头视频

现在,我们可以使用电脑上的网络摄像头实时检测物体。

这一部分并没有看起来那么难,我们只需要在一个循环中插入我们用于一个图像的代码:

然后我们得到:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者 GIF

我们使用 open cv 中的 VideoCapture 从计算机网络摄像头加载视频。然后,我们对静态图像进行了同样的处理,并预测了标签和位置。主要区别在于图像输入是连续的,所以我们在 while 循环中插入了代码。

所有使用的代码和笔记本都在这个库中:

https://github.com/gabrielcassimiro17/raspberry-pi-tensorflow

在不久的将来,我会将它加载到一个 raspberry pi 中,使用一个能够检测对象的模型来创建一些交互,并将结果发布在这里。

如果你喜欢内容,想支持我,可以给我买杯咖啡:

从图像中提取目标

原文:https://towardsdatascience.com/object-extraction-from-images-2423f51ef67e?source=collection_archive---------16-----------------------

使用 Skimage 包从图像中提取对象

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来自马库斯·温克勒拍摄的 Unsplash

这个故事是关于什么的?

如今,像 TensorflowKeras 这样强大的开源平台和库已经大大提高了深度学习模型的效率和可访问性。我们现在可以建立神经网络来预测未来的温度,识别句子的情感,等等,只需要几行代码。从深度学习模型中受益匪浅的一个领域是图像识别。使用卷积神经网络(CNN),我们可以建立能够识别数千种不同物体的模型,从蝴蝶到马,从卡车到飞机。然而,机器学习模型的性能在很大程度上取决于输入的质量。较好的输入不仅可以提高模型的精度,还可以减少训练时间。考虑下面的一对图像:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(图片由作者提供)

两幅图像包含相同的东西:一个手写的数字“5”。然而,左图像具有 28×28 像素,而右图像具有 128×128 像素。如果我们将正确的图像作为训练样本,我们需要 21 倍的 RAM 来存储输入数据。此外,模型要正确识别图像会困难得多。两幅图像都只包含一个对象。如果你有成千上万张像下面这样的图片,你需要从图片中提取数字作为你的训练样本,那该怎么办?然后,您将需要一种有效的方法来帮助您从给定的图像中提取您想要的对象。在这个故事中,我将向你展示一个非常简单而有用的从图像中提取物体的方法。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(图片由作者提供)

使用撇除法的对象提取

假设你有一张如下图,和上图一模一样,只是中间我手动加了一个“白色污点”。你的目标是提取“0”和“5”,并使它们成为独立的图像。使用 Skimage,你只需两步就能做到。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(图片由作者提供)

**第一步:*第一步是使用 Skimage 中【测量】*模块的 【查找 _ 轮廓】 功能。这个函数有两个重要的参数——数组(图像)和级别。Array (image)只是图像的 NumPy 数组表示。但是什么是“水平”参数呢?让我用两张图片来解释一下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用“级别=10”找到的等高线(图片由作者提供)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用“级别=150”找到的等高线(图片由作者提供)

对于上面的两幅图像,左边的图形显示了找到的所有轮廓,右边的图形说明了如何找到数字“5”的轮廓。聪明的人可能已经注意到,“级别”值为 150 时,轮廓中包含的像素更多。天才可能已经发现,轮廓本质上将小于“级别”值的像素值和大于“级别”值的像素值分开。没错。! **find_contour 函数试图找到一个闭环,使得环外的像素值小于“级别”,而环内的像素值大于或等于“级别”。**因此,“级别”越大,轮廓内的像素就越少。

要找到轮廓,你只需要一行代码!注意,为了方便起见,我还显示了绘制等高线的代码。

**第二步:**现在,我们只需要从轮廓中提取物体。首先,让我们了解一下 find_contours 函数返回什么:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

正如我们在上面看到的,它返回一个数组列表。数组中的值表示原始图像的坐标值:contour[:,1]表示 x 坐标(横轴),contour[:,0]表示 y 坐标(纵轴)。请注意,等高线值不是整数。这是因为它试图在你的像素数组中找到精确的“级别”值。但是,您的值可能不完全等于“级别”值。例如,如果您的“级别”值设置为 10,并且(1,1)和(1,2)处的两个值分别为 0 和 20,则这两个点之间的轮廓位置将为(1,1.5)。

由于轮廓的值代表坐标值,我们可以利用它们来裁剪我们的图像并获得我们想要的对象!具体来说,对象必须位于 y 坐标的最大值(轮廓[0])和最小值(轮廓[0])之间,x 坐标的最大值(轮廓[1])和最小值(轮廓[1])之间。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用上述代码裁剪的数字“5”(图片由作者提供)

既然我们成功地抽取了数字“5 ”!但是,对于“0”,我们可以注意到有两个轮廓:橙色的较大轮廓包含整个“0”数字,而绿色的较小轮廓包含内部部分,这不是我们想要的。此外,由于我们在中间有一个“污点”,它也以某种方式被函数选中。解决这个问题的简单方法是只选择最长的轮廓。因为我们知道只需要提取两个对象,所以我们可以选择两个最长的轮廓。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用上述代码裁剪的数字“0”(图片由作者提供)

现在你知道了!如上所述,您可能需要使用这段代码来处理成千上万的图像。为此,只需将代码转换成函数,这样就可以轻松地重复应用了!

旁注

有时,您的图像可能过于清晰,这意味着对象内的像素值可能会有很大的变化。如果像素值在“级别”值上变化,则可以在对象内部检测到多个轮廓。在这种情况下,您可以应用“平滑过滤器”来减少变化。一个流行的选择是"中值滤波器",在这里你用像素的中值替换像素区域的值。您也可以使用 Skimage 应用一个中值滤波器:

下图说明了如何使用“中值滤波器来帮助轮廓检测:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

平滑前(左)和平滑后(右)在 t 恤图像中发现的轮廓(图片由作者提供)

结论

从图像中检测和提取目标是为机器学习创建新数据集或改进现有数据集的重要技术。在这个故事中,我分享了一个非常方便和强大的方法,使用 Skimage 包从图像中检测和提取对象。如果你想看完整的笔记本,请随意查看我的 GitHub 库:

https://github.com/KuanWeiBeCool/Object-Extraction-From-Images

感谢阅读!

在 Python 中检查对象是否具有属性

原文:https://towardsdatascience.com/object-has-attribute-python-ffce6d1ba633?source=collection_archive---------12-----------------------

Python 编程

了解如何确定 Python 对象是否具有特定属性

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

丹尼尔·施鲁迪在 Unsplash 上的照片

介绍

Python 是一种通用编程语言,作为标准库的一部分,它具有相当丰富的内置功能。此外,由于它的流行,它也有一个活跃的社区,这意味着有无数的第三方库和模块建立在内置功能之上。

因此,如果不查看相应的文档,很难记住每个模块、类甚至方法是如何工作的。在今天的文章中,我们将讨论如何通过编程来确定一个特定的对象是否具有特定的属性。

具体来说,我们将探索如何

  • 确定对象是否具有特定属性
  • 列出对象的所有属性
  • 列出实例化对象的属性值

首先,让我们创建几个伪 Python 类,我们将在本文中引用它们,以演示一些概念并提供一些更实际的例子。

class Vehicle: def __init__(self, no_of_wheels, color):
        self.no_of_wheels = no_of_wheels
        self.color = color

    def print_characteristics(self):  
        print(
            f'No. of wheels: {self.no_of_wheels}\n'
            f'Color: {self.color}'
        ) class Car(Vehicle): def __init__(self, no_of_wheels, color, is_electrical):
        super().__init__(no_of_wheels, color)
        self.is_electrical = is_electrical def print_characteristics(self):  
        print(
            f'No. of wheels: {self.no_of_wheels}\n'
            f'Color: {self.color}\n'
            f'Is Electrical: {self.is_electrical}\n'
        ) def paint_car(self, new_color):
       self.color = new_color class Bicycle(Vehicle): def  __init__(self, no_of_wheels, color, is_mountain):
        super().__init__(no_of_wheels, color)
        self.is_mountain = is_mountain def print_characteristics(self):  
        print(
            f'No. of wheels: {self.no_of_wheels}\n'
            f'Color: {self.color}\n'
            f'Is Mountain: {self.is_mountain}\n'
       )

检查对象是否具有特定属性

如果你想确定一个给定的对象是否有特定的属性,那么[**hasattr()**](https://docs.python.org/3/library/functions.html#hasattr)方法就是你要找的。该方法接受两个参数,字符串格式的对象和属性。

hasattr(object, name)

如果提供的字符串对应于某个对象属性的名称,该方法将返回True,否则将返回False

例如,考虑下面的检查

>>> car = Car(4, 'white', True)
>>> bike = Bicycle(2, 'blue', False)
>>> **hasattr(car,  'paint_car')**
True
>>> **hasattr(bike, 'paint_car')**
False

或者,您甚至可以调用[getattr()](https://docs.python.org/3/library/functions.html#getattr)方法并捕捉当对象没有指定属性时引发的AttributeError。本质上,这是我们之前讨论的hasattr()方法的实际实现。

car = Car(4, 'white', True)**try:
    getattr(car, 'is_mountain')
except AttributeError:
    # Do something
    pass**

列出对象的所有属性

另一个有用的命令是[**dir()**](https://docs.python.org/3/library/functions.html#dir),它返回一个包含指定对象属性的列表。本质上,这个方法将返回包含在__dict__属性中的键。请注意,如果您覆盖了__getattr__属性,这种行为可能会被修改,因此dir()结果可能不准确。

>>> from pprint import prrint
>>>
>>> bike = Bicycle(2, 'blue', False)
>>> **pprint(dir(bike))**
['__class__',
'__delattr__',
'__dict__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__le__',
'__lt__',
'__module__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'__weakref__',
'color',
'is_mountain',
'no_of_wheels',
'print_characteristics']

列出实例化对象的属性值

最后,如果你想列出一个实例化对象的属性值,那么你可以调用[vars()](https://docs.python.org/3/library/functions.html#vars)方法,该方法将返回指定对象的__dit__属性。

>>> from pprint import prrint
>>>
>>> bike = Bicycle(2, 'blue', False)
**>>> pprint(bike.vars())
{'color': 'blue', 'is_mountain': False, 'no_of_wheels': 2}**

最后的想法

在今天的简短指南中,我们探讨了如何找出某个特定属性是否属于某个类。此外,我们讨论了如何列出指定对象的所有属性,以及如何使用vars()推断实例化对象的实际属性值。

成为会员 阅读介质上的每一个故事。你的会员费直接支持我和你看的其他作家。你也可以在媒体上看到所有的故事。

https://gmyrianthous.medium.com/membership

你可能也会喜欢

</16-must-know-bash-commands-for-data-scientists-d8263e990e0e> https://betterprogramming.pub/11-python-one-liners-for-everyday-programming-f346a0a73f39

利用极坐标的目标定位/分割

原文:https://towardsdatascience.com/object-localization-segmentation-with-polar-coordinates-62be64da0097?source=collection_archive---------33-----------------------

在这篇博文中,我分享了一个关于如何更精确地定位对象(与边界框相比)而不显著改变深度学习模型训练管道的初步想法

“尽管盒子是愚蠢的,但我可能是面具的忠实信徒,除非我不能让 YOLO 学会它们。”j .雷德蒙

当我在*“yolov 3:一个增量改进”*的论文中读到这些话时,我想对一个像边界框这样普通的结构说这些可能太尖锐了。它有什么问题?事实上,对象检测和实例分割模型是为了解决不同的任务而创建的,不是吗?这是正确的,但在一定程度上。它们都旨在定位某个对象,但精度不同。分段遮罩旨在捕捉对象的形状,而不考虑其复杂性。边界框要简单得多。扩展一下“愚蠢”这个词,我会说包围盒不能告诉你任何关于物体的形状,它的实际占据面积,而且包围盒经常捕捉太多的背景噪音。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

分段遮罩与边界框。图片来源:https://aeroaffaires . com/private-jet-hire/涡桨飞机/king-air-350/

有时,对于计算机视觉解决方案来说,这可能是一件大事。如果对象的裁剪图像随后被管道中的另一个模型使用,边界框的上述缺点可能会显著影响性能。另一方面,目标检测网络通常用于边缘设备,目的是实时处理。在边缘环境中运行复杂的逐像素分割神经网络成为一项重要的任务。在这里,我们面临的问题是:我们想提取一些东西,而不仅仅是一个简单的矩形,然而模型可以做到这一点需要不可负担的计算资源。知道了所有这些,问题的表述将如下:我们能否以某种方式扩展对象检测,使得它能够以分割级别的精度找到对象,但同时保持轻量级对象检测网络所具有的实时性能的所有优点?

让我们考虑一下。多边形可以很好地替代边界框。然而,多边形组件的数量是变化的,它取决于形状的复杂性。因此,多边形不可能是具有固定输出维度的神经网络的直接输出。据我所见,解决深度学习中多边形预测问题的研究方向有很多。这些模型的实际应用实际上取决于项目细节。我想强调一下我最近遇到的一种方法——偏光板。在我们深入细节之前,我想回顾一下 PolarMask 所基于的极坐标表示的一些基础知识。不是用 xy 的绝对值,而是用角度和距离来表示点。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

极坐标。作者图片

下面是 PolarMask 的想法:让我们找到一个对象的中心,并以一定的间隔(例如 10 度,360/10=总共 36 条光线)从它投射一组光线。光线与物体轮廓相交的点就是目标多边形的点。这些点的数量是固定的,因为角度是预先定义的。模型唯一需要预测的是离原点(中心)的距离。基本上,多边形组件数量变化的问题在这里以一种相当精确的方式得到了解决。现在,我们可以将我们的目标数据从未定义和多样的维度转换为简单和统一的维度。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来源:https://www.robots.ox.ac.uk/~vgg/data/pets/

算法工作所缺少的另一条信息是对象的中心,这也是模型输出中所期望的。基本上就是两个值的回归而已。然而,这里棘手的部分是我们究竟以哪个点为中心。关键是,对于自由形式的对象,中间坐标不是最佳选择。还不如把“质心”作为一个地面真值点。这将给出所有光线到达它们的最佳交点的更好的概率。

因此,每个对象的模型输出是角度网格的中心坐标和距离矢量。

为什么我个人觉得上述想法很有趣:

  • 推理时间的角度来看,它几乎与包围盒回归相同,我们只是有更多的值要回归。此外,边界框可以被认为是具有 4 个元素的多边形的特殊情况。换句话说,我们几乎可以免费获得更复杂、更一般化的预测。
  • 多边形的极坐标表示可以应用于各种各样的神经网络架构,因为这只是一种构建模型输出的灵活方式。例如,诸如 YOLO 或 FCOS 的众所周知的对象检测架构可以被修改以产生对象多边形而不是边界框,而不需要那么大的努力。
  • 它给出了介于对象检测和实例分割之间的输出结构。因此,它可以是一个折衷解决方案,不需要显著改变每像素分割的整个流水线。
  • 当实用的解决方案出现在深度学习中时,这是,因为深度学习被理论谈论和基准测试所淹没。当工程头脑想到这样的想法,并把不同的人类知识连接成一个有用的和可行的解决方案时,真是令人兴奋。

在下一篇文章中,我将讲述我自己使用这种方法的实验。没有什么突破性的东西,但是我想看看从头开始实现这样的多边形回归有多容易。让我们保持联系。

使用预先训练的 CNN 模型(如 MobileNet、ResNet 和 Xception)进行目标定位

原文:https://towardsdatascience.com/object-localization-using-pre-trained-cnn-models-such-as-mobilenet-resnet-xception-f8a5f6a0228d?source=collection_archive---------4-----------------------

目标定位使用不同的预先训练的 CNN 模型在来自牛津 Pet 数据集的图像中定位动物面孔

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由凯文·巴加Unsplash 上拍摄

这项工作将向您介绍使用预先训练的 CNN 和一些额外的有趣的改编,以根据上下文在其中找到最佳执行模型的单个对象定位。

先来解释一下本地化。简而言之,在图像中定位一个物体。定位意味着在识别一个对象之后,将该对象引入一个边界框或者精确地引入一个矩形来定位该对象。有单对象定位、多对象定位和语义分割,用于做手段相似但做目的形式不同的事情。

在这里,我将坚持单个对象定位,这将识别图像中所需的对象,然后使用 CNN 对其进行定位。另外,请注意,我将分别使用 Mobile Net、ResNet 和 Xception 作为预训练的卷积神经网络,并将为每个网络执行整个分类器和定位器。在此过程中,Union 上的交集)将变得熟悉,并将为每个 IOU 打印出相同的结果,最后,我们将看到哪个预训练网络对我们使用的数据集表现良好。

对于这个项目,牛津宠物数据集是合适的:你可以从下面的链接下载。

http://www.robots.ox.ac.uk/~vgg/data/pets/

现在让我们分析数据集。数据集包含动物的图像,并且每个图像包含单个动物。我们可以看到这些动物是不同类型的猫和狗。请注意,图像的对齐、位置和结构在每个图像中都是不同的,这可以帮助我们获得更准确的结果。通过上面的链接,我们可以下载数据集和地面实况数据。一旦我们下载了数据,我们将在两个文件中结束:图像和注释。我们可以在 annotations 文件夹中获得 xml 注释、类列表和所有内容。一旦我们掌握了所有这些,让我们进入使用不同的预先训练的 CNN 模型的目标定位。

在开始之前,我们将引入 IOU 作为衡量指标。并集上的交集(IOU) 有助于理解预测的边界框与真实的边界框相差多少。这是理解我们的预测如何进行的一个很好的方法…

PS:对于所有用预先训练好的网络分别训练后用包围盒打印出来的图片,我们会在每张图片下面打印出 IOU……

首先,我们需要导入所有必需的库和包。

from collections import namedtupleimport csvimport tensorflow as tffrom tensorflow.keras.applications.mobilenet_v2 import MobileNetV2, preprocess_inputfrom tensorflow.keras.applications.resnet50 import ResNet50, preprocess_inputfrom tensorflow.keras.applications.xception import Xception, preprocess_inputfrom tensorflow.keras import backend as Kfrom tensorflow.keras.layers import Dense, GlobalAveragePooling2D, Dropout, Flattenfrom tensorflow.keras.models import Model, Sequentialfrom tensorflow.keras.preprocessing.image import ImageDataGeneratorfrom tensorflow.keras.utils import to_categoricalimport matplotlib.pyplot as pltimport matplotlib.patches as patchesimport numpy as npimport os# import the necessary packagesfrom collections import namedtupleimport numpy as npimport cv2# define the `Detection` object for IOU(Detection = namedtuple("Detection", ["image_path", "gt", "pred"])from PIL import Image, ImageOps# importing XML parsing library for parsing the dataimport xml.etree.ElementTree as ET

现在,让我们导入数据——我通常使用谷歌合作实验室。因此,我将我的 google drive 安装到 colab 上(您可以通过任何方式导入数据,只要您方便就可以保存到任何地方)。

此外,这里我们可以将目标大小设置为(224,224),,我们将使用 Mobile Net、ResNet 和 Xception 作为预训练网络来比较它们中的每一个。

from google.colab import drivedrive.mount('/content/drive')data_images = '/content/drive/MyDrive/AI_dataset_pets/images'data_ClassList = '/content/drive/MyDrive/AI_dataset_pets/annotations/list.txt'data_xmlAnnotations = '/content/drive/MyDrive/AI_dataset_pets/annotations/xmls'TARGET_SIZE = (224, 224)

现在定义边界框来定位图像中的动物。

#BoundingBoxBounding_Box = namedtuple('Bounding_Box', 'xmin ymin xmax ymax')# The following function will read the xml and return the values for xmin, ymin, xmax, ymax for formulating the bounding boxdef building_bounding_box(path_to_xml_annotation):tree = ET.parse(path_to_xml_annotation)root = tree.getroot()path_to_box = './object/bndbox/'xmin = int(root.find(path_to_box + "xmin").text)ymin = int(root.find(path_to_box + "ymin").text)xmax = int(root.find(path_to_box + "xmax").text)ymax = int(root.find(path_to_box + "ymax").text)return Bounding_Box(xmin, ymin, xmax, ymax)

因此,让我们做填充使图像成为一个完美的正方形,并根据填充和缩放对边界框进行必要的修改。

在下面的代码中,标准化也已经完成

def resize_image_with_bounds(path_to_image, bounding_box=None, target_size=None):image = Image.open(path_to_image)width, height = image.sizew_pad = 0h_pad = 0bonus_h_pad = 0bonus_w_pad = 0#the following code helps determining where to pad or is it not necessary for the images we have.# If the difference between the width and height was odd((height<width)case), we add one pixel on one side# If the difference between the height and width was odd((height>width)case), then we add one pixel on one side.#if both of these are not the case, then pads=0, no padding is needed, since the image is already a square itself.if width > height:pix_diff = (width - height)h_pad = pix_diff // 2bonus_h_pad = pix_diff % 2elif height > width:pix_diff = (height - width)w_pad = pix_diff // 2bonus_w_pad = pix_diff % 2# When we pad the image to square, we need to adjust all the bounding box values by the amounts we added on the left or top.#The "bonus" pads are always done on the bottom and right so we can ignore them in terms of the box.image = ImageOps.expand(image, (w_pad, h_pad, w_pad+bonus_w_pad, h_pad+bonus_h_pad))if bounding_box is not None:new_xmin = bounding_box.xmin + w_padnew_xmax = bounding_box.xmax + w_padnew_ymin = bounding_box.ymin + h_padnew_ymax = bounding_box.ymax + h_pad# We need to also apply the scalr to the bounding box which we used in resizing the imageif target_size is not None:# So, width and height have changed due to the padding resize.width, height = image.sizeimage = image.resize(target_size)width_scale = target_size[0] / widthheight_scale = target_size[1] / heightif bounding_box is not None:new_xmin = new_xmin * width_scalenew_xmax = new_xmax * width_scalenew_ymin = new_ymin * height_scalenew_ymax = new_ymax * height_scaleimage_data = np.array(image.getdata()).reshape(image.size[0], image.size[1], 3)# The image data is a 3D array such that 3 channels ,RGB of target_size.(RGB values are 0-255)if bounding_box is None:return image_data, Nonereturn (image_data, Bounding_Box(new_xmin, new_ymin, new_xmax, new_ymax))

因此,根据输入数据,我们已经重塑了图像和边界框。

def setting_sample_from_name(sample_name):path_to_image = os.path.join(data_images, sample_name + '.jpg')path_to_xml = os.path.join(data_xmlAnnotations, sample_name + '.xml')original_bounding_box = get_bounding_box(path_to_xml)image_data, bounding_box = resize_image_with_bounds(path_to_image, original_bounding_box, TARGET_SIZE)return (image_data, bounding_box)

注意黄色框预测边界框,蓝色框真实边界框真实边界框。

现在让我们编写函数来绘制图像数据和边界框,并找到两个框在并集 IOU 上的交集。它可以计算为 IOU =重叠面积/并集面积。

代码在下面的函数‘plot _ with _ box’中。

def plot_with_box(image_data, bounding_box, compare_box=None):fig,ax = plt.subplots(1)ax.imshow(image_data)# Creating a Rectangle patch for the changed oneboxA = patches.Rectangle((bounding_box.xmin, bounding_box.ymin),bounding_box.xmax - bounding_box.xmin,bounding_box.ymax - bounding_box.ymin,linewidth=3, edgecolor='y', facecolor='none')# Add the patch to the Axesax.add_patch(boxA)#Creating another Rectangular patch for the real oneif compare_box is not None:boxB = patches.Rectangle((compare_box.xmin, compare_box.ymin),compare_box.xmax - compare_box.xmin,compare_box.ymax - compare_box.ymin,linewidth=2, edgecolor='b', facecolor='none')# Add the patch to the Axesax.add_patch(boxB)#FOR FINDING INTERSECTION OVER UNIONxA = max(bounding_box.xmin, compare_box.xmin)yA = max(bounding_box.ymin, compare_box.ymin)xB = min(bounding_box.xmax, compare_box.xmax)yB = max(bounding_box.ymax, compare_box.ymax)interArea = max(0, xB - xA + 1) * max(0, yB - yA + 1)boxAArea = (bounding_box.xmax - bounding_box.xmin + 1) * (bounding_box.ymax - bounding_box.ymin + 1)boxBArea = (compare_box.xmax - compare_box.xmin + 1) * (compare_box.ymax - compare_box.ymin + 1)iou =interArea/float(boxAArea+boxBArea-interArea)
#By intersection of union I mean intersection over union(IOU) #itselfprint('intersection of union =',iou)plt.show()

现在,让我们绘制一个随机图像,看看发生了什么,并检查预测边界框的工作情况。

sample_name = 'Abyssinian_10'image, bounding_box = setting_sample_from_name(sample_name)plot_with_box(image, bounding_box)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

这样我们就有了物体的边界框。

现在,让我们处理所有的数据。此外,让我们删除所有没有注释的图像。并将其转换为 Numpy 数组。

data_pros = []with open(data_ClassList) as csv_list_file:csv_reader = csv.reader(csv_list_file, delimiter=' ')for row in csv_reader:if row[0].startswith('#'): continue# Unpack for readabilitysample_name, class_id, species, breed_id = row# Not every image has a bounding box, some files are missing.So, lets ignore those by the following linestry:image, bounding_box = setting_sample_from_name(sample_name)except FileNotFoundError:# This actually happens quite a lot, as you can see in the output.# we end up with 7349 samples.print(f'cannot find annotations for {sample_name}: so skipped it')continue# cat = 0 and dog = 1.data_tuple = (image, int(species) - 1, bounding_box)data_pros.append(data_tuple)print(f'Processed {len(data_pros)} samples')data_pros = np.array(data_pros)

现在,一旦我们完成了这个,让我们用 6 张随机的图片来测试整体

#for checking lets print 6 of themfor _ in range(6):i = np.random.randint(len(data_pros))image, species, bounding_box = data_pros[i]if species == 0:print(i, "it is cat")elif species == 1:print(i, "it is dog")else:print("ERROR FOUND: This is of invalid species type")plot_with_box(image, bounding_box)

结果是这样的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

分割给定数据进行包围盒预测。

x_train = []y_class_train = []y_box_train = []x_validation = []y_class_validation = []y_box_validation = []validation_split = 0.2for image, species, bounding_box in processed_data:if np.random.random() > validation_split:x_train.append(preprocess_input(image))y_class_train.append(species)y_box_train.append(bounding_box)else:x_validation.append(preprocess_input(image))y_class_validation.append(species)y_box_validation.append(bounding_box)x_train = np.array(x_train)y_class_train = np.array(y_class_train)y_box_train = np.array(y_box_train)x_validation = np.array(x_validation)y_class_validation = np.array(y_class_validation)y_box_validation = np.array(y_box_validation)

我们将使用一些使用迁移学习的预训练模型。

首先,我使用移动网络,我将同时执行分类器和定位器。

base_model = MobileNetV2(weights='imagenet', include_top=False, input_shape=(TARGET_SIZE[0], TARGET_SIZE[1], 3))chopped_mobilenet = Model(inputs=[base_model.input], outputs=[base_model.layers[90].output])classification_output = GlobalAveragePooling2D()(chopped_mobilenet.output)classification_output = Dense(units=1, activation='sigmoid')(classification_output)localization_output = Flatten()(chopped_mobilenet.output)localization_output = Dense(units=4, activation='relu')(localization_output)model = Model(inputs=[chopped_mobilenet.input], outputs=[classification_output, localization_output])model.summary()

一旦打印出以上内容,我们将获得使用 MobileNet 构建模型的详细摘要。

现在,绘制每个时期模型的精确度和损失。

plot_training_history(history1, model)

这些图是:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

真实框是蓝色的,预测框是黄色的

for _ in range(18):i = np.random.randint(len(processed_data))img, species, true_bounding_box = processed_data[i]pred = model.predict(np.array([preprocess_input(img)]))if pred[0][0] < .5:print("it is a Cat")else:print("it is a dog")plot_with_box(img, Bounding_Box(*pred[1][0]), true_bounding_box)

结果是:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

请注意,这里所有的图像都使用 MobileNet 检测为猫。随机抽取一些样本进行检查,以了解模型的完善程度:

some_random_samples = ['Abyssinian_174','american_bulldog_59']for sample_name in some_random_samples:path_to_image = os.path.join(data_images, sample_name + '.jpg')print(path_to_image)img, _ = resize_image_with_bounds(path_to_image, target_size=TARGET_SIZE)pred = model.predict(np.array([preprocess_input(img)]))if pred[0][0] < .5:print("Yes,Its a Cat")else:print("Yes Its a dog")plot_with_box(img, Bounding_Box(*pred[1][0]),true_bounding_box)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

使用 MobileNet 时的 IOU 值还不错…但是在有一些小匿名的图片中,IOU 值太小了……

让我们看看它是如何使用 ResNet 和 Xception 的。

现在,使用 ResNet 预训练网络进行同样的尝试

base_model1 = ResNet50(weights='imagenet', include_top=False, input_shape=(TARGET_SIZE[0], TARGET_SIZE[1], 3))chopped_resnet1 = Model(inputs=[base_model1.input], outputs=[base_model1.layers[90].output])classification_output1 = GlobalAveragePooling2D()(chopped_resnet1.output)classification_output1 = Dense(units=1, activation='sigmoid')(classification_output1)localization_output1 = Flatten()(chopped_resnet1.output)localization_output1 = Dense(units=4, activation='relu')(localization_output1)model1 = Model(inputs=[chopped_resnet1.input], outputs=[classification_output1, localization_output1])model1.summary()

请浏览一下这个摘要,一旦你把它打印出来。这将有助于你对网络有一个清晰的了解。

现在,我们将继续编译和拟合用 Resnet 制作的模型。

model1.compile(optimizer='adam', metrics=['accuracy'],loss=['binary_crossentropy', 'mse'],loss_weights=[800, 1]  )#lets run it through 10 epochshistory2=model1.fit(x_train, [y_class_train, y_box_train], validation_data=(x_validation, [y_class_validation, y_box_validation]),epochs=10,verbose=True)history2

我不包括每个时期的总结和验证准确性和损失。一旦你实现了它们,你就可以看到它们,下面给出了每个时期的准确度和损失图。

def plot_training_history(history, model):plt.plot(history.history['dense_3_accuracy'])plt.plot(history.history['val_dense_3_accuracy'])plt.ylabel('accuracy')plt.xlabel('epoch')plt.legend(['training', 'validation'], loc='best')plt.show()plt.plot(history.history['dense_3_loss'])plt.plot(history.history['val_dense_3_loss'])plt.title('model loss')plt.ylabel('loss')plt.xlabel('epoch')plt.legend(['training', 'validation'], loc='best')plt.show()plot_training_history(history2, model1)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

让我们打印更改后的图像,并使用 ResNet 进行训练,并打印两个框的 IOU(交集/并集),看看我们的预测有多好。

for _ in range(3):i = np.random.randint(len(processed_data))img, species, true_bounding_box = processed_data[i]pred = model1.predict(np.array([preprocess_input(img)]))if pred[0][0] < .5:print("it is a Cat by ResNet")else:print("it is a dog by ResNet")plot_with_box(img, Bounding_Box(*pred[1][0]), true_bounding_box)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

这是检测一只狗作为一只猫,但 IOU 值相当不错!

现在让我们试试 Xception 预训练网络。下面给出了实现它的代码。

base_model2 = Xception(weights='imagenet', include_top=False, input_shape=(TARGET_SIZE[0], TARGET_SIZE[1], 3))chopped_Xception = Model(inputs=[base_model2.input], outputs=[base_model2.layers[90].output])classification_output2 = GlobalAveragePooling2D()(chopped_Xception.output)classification_output2 = Dense(units=1, activation='sigmoid')(classification_output2)localization_output2 = Flatten()(chopped_Xception.output)localization_output2 = Dense(units=4, activation='relu')(localization_output2)model2 = Model(inputs=[chopped_Xception.input], outputs=[classification_output2, localization_output2])model2.summary()

通过异常网络编译和拟合模型:

model2.compile(optimizer='adam', metrics=['accuracy'],loss=['binary_crossentropy', 'mse'],loss_weights=[800, 1]  )#lets run it through 10 epochshistory3=model2.fit(x_train, [y_class_train, y_box_train], validation_data=(x_validation, [y_class_validation, y_box_validation]),epochs=10,verbose=True)history3

画出它的精确度和损耗。

def plot_training_history(history, model):plt.plot(history.history['dense_9_accuracy'])plt.plot(history.history['val_dense_9_accuracy'])plt.ylabel('accuracy')plt.xlabel('epoch')plt.legend(['training', 'validation'], loc='best')plt.show()plt.plot(history.history['dense_9_loss'])plt.plot(history.history['val_dense_9_loss'])plt.title('model loss')plt.ylabel('loss')plt.xlabel('epoch')plt.legend(['training', 'validation'], loc='best')plt.show()plot_training_history(history3, model2)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

让我们使用异常打印更改和训练后的图像,并打印两个箱子的借据

让我们看看使用 Xception 处理的图像:

for _ in range(6):i = np.random.randint(len(processed_data))img, species, true_bounding_box = processed_data[i]pred = model2.predict(np.array([preprocess_input(img)]))if pred[0][0] < .5:print("it is a Cat by Xception")else:print("it is a dog by Xception")plot_with_box(img, BoundingBox(*pred[1][0]), true_bounding_box)

结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

现在,我们将使用少量随机样本来测试模型。

#testing with randSome_Random_samples = ['Abyssinian_174','american_bulldog_59']for sample_name in Some_Random_samples:path_to_image = os.path.join(data_images, sample_name + '.jpg')print(path_to_image)img, _ = resize_image_with_bounds(path_to_image, target_size=TARGET_SIZE)pred = model2.predict(np.array([preprocess_input(img)]))if pred[0][0] < .5:print("Yes,Its a Cat by Xception")else:print("Yes Its a dog by Xception")plot_with_box(img, Bounding_Box(*pred[1][0]),true_bounding_box)

结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者图片

Xception 表现很好,给出了相当准确的预测。

在尝试使用 Xception、MobileNet 和 Resnet 时,IOU 值看起来不错。

对于 MobilNet = 0.8125,获得最终层的最终验证精度

对于 ResNet = 0.7969,获得最终层的最终验证精度

对于例外 = 0.8438,获得最终层的最终验证精度

(当您在每个模型的训练中获得每个时期的结果时,您可以获得准确的最终验证准确性)。

所有预先训练好的网络,如 MobileNet、ResNet 和 Xception,都表现得令人满意。

但就准确性而言,MobileNet 和 Xception 做得很好,但就 IoU 而言,预测在所有这些网络中都有波动。

当涉及到匿名图片时,情况就不一样了。

但是大部分图片的欠条都相当不错。

使用 IOU 的测量清楚地使我们了解在哪个图片中我们得到了坏的 IOU,以及预测的边界框与真实的边界框不同到什么程度。

每个模型的精度图和损失图的观察结果

对于第一个预训练模型 MobileNet,我发现在绘制每个历元中的精度和损失时,初始历元之后的训练精度高于验证精度。模型中的验证损失也很高。

对于第二个预训练模型 ResNet,当从初始或起始时期本身绘图时,训练精度大大高于验证精度。验证损失太高了!

对于第三个预训练模型——例外——在绘图时,发现训练精度高于第 4 个历元本身的验证精度。此外,验证损失很高。

也就是说,模型训练过度了。

我觉得根据 IOU,除了一些令人困惑的图像之外,所有这些模型都表现得相当好。

除此之外,一些图片也给出了很好的 IOU 值!

总的来说,Xception 在这个数据集上表现良好。

我希望从这篇文章中你能了解如何在数据集中进行对象定位,并尝试各种预先训练好的网络,如 Mobile Net、ResNet 和 Xception。

结果可能会因您将采用的数据集而异,但这肯定会帮助您通过执行各种实验或测试来利用数据,并根据您的研究背景得出最佳的预训练网络。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值