高效互联的远程数据科学
在家建立数据科学职业生涯
在 Unsplash 上由 Cameron Venti 拍摄的照片
远程工作越来越受欢迎,因为雇主希望留住那些恰好不在附近的人才。然而,由于 Covid 的爆发,它很快从一个美好的必须品变成了一种必需品,像谷歌这样的公司将它作为一种限制传播的预防措施。在政府强制隔离的地方,远程工作是唯一的选择。
通常,数据科学工作只需要一台计算机、互联网和你的智慧。有人可能会认为,无论你是在家里、在海滩上(有 WIFI),还是在开放式办公室里戴着耳机分区,在数据科学这样的岗位上工作都很容易做到高效。这在一定程度上是对的,但当你唯一的办公室伴侣是一株植物,而你的同事们都眼不见心不烦的时候,你也很容易失去注意力,感到孤立无援,并很快变得沮丧。
在我的职业生涯中,我已经在不同的技术岗位上远程工作了五年多。我最近的远程工作是作为一名数据科学家。以下是一套帮助我作为远程员工继续保持高效工作和职业发展的策略。幸运的是,作为一名数据科学家,您可能已经熟悉了各种工具,可以继续在高水平上做出贡献!
保持联系
当你在远处时,很容易害怕错过。根据组织的文化,大量的创新和交流可能与地理上的接近有关。走廊谈话和白板会议是合作创新的源泉,反馈通常来自于隔着立方体墙壁的谈话。然而,这并不意味着你不能远程继续或合作创造性的数据科学。以下是一些建议。
擅长 Git
你已经在使用版本控制来共享你的笔记本和脚本了,对吗?除了基础知识之外,学习如何使用高级版本控制功能,如拉请求和版本区分,可以帮助您在团队成员不在时快速了解共享项目中的变化,这也将确保您的贡献在更广泛的组织中得到体现。
实际上,这适用于你所拥有的任何协作软件。代替白板,实际上很有可能在基于协作云的文档、电子表格和绘图程序(如 Google Drawings 或 Miro)上远程取得实时进展。关键是要认识到,虽然你可能从婴儿时期就开始画画,但软件界面可能需要一点练习才能“实时”擅长。熟悉捷径是值得的!好的一面是,一旦你使用这些工具中的一个创建了一些东西,你现在就有了永久清晰的文档,这可能不是潦草的白板会议的情况。这就引出了文档的话题。
记录一切
您花了一天时间探索一个新的数据源,并且希望分享您所学到的知识,以便您的团队可以决定是否使用它来构建模型。问题是你不能直接告诉他们,因为他们在地球的另一边,直到你睡着了才会起床。即使距离没有那么远,你仍然面临着用一个更有限的工具箱有效地分享你的详细想法的挑战。
为此,详细记录你的同事需要了解的一切。如果是代码,不要跳过注释,链接到任何必要的参考资料。如果是模型质量评估,清楚地写下模型如何工作,如何评估,以及在哪里可以找到任何验证集。许多工具都可以做到这一点,但维基和其他允许记录问答的工具对于捕捉最终产品如何形成的背景尤其有用。
当你不在的时候,有考虑周全的文档可以让队友取得进展。如果需要面对面的时间来解释,通常可以压缩时间来回答具体的问题,而不是讨论每一个细节。作为最后的奖励,当你想记住为什么或如何选择做某事时,你的文档会有答案!
走向高带宽
有时候你真的需要比打字速度更快地交换信息。如果松弛的信息或电子邮件不能传达重点,不要害怕开始一个即兴的松弛的电话,缩放,闲逛或任何让你使用语音,最好是视频。这不一定是正式的预约电话。只要所有人都确认他们有空,一个五分钟的聊天现在伴随着手的挥动和快速清晰的纸质图表可以减少很多挫败感。
保持高带宽另一种方法是清楚地确定你什么时候在工作,什么时候有空,什么时候没有。一个简单的“我要离线一小时”就可以了。当你们不在一起的时候,没有人知道你必须去办点事,不能回答问题。虽然我并不提倡管理者对办公桌前的时间进行微观跟踪,但在一个高度沟通的组织中,让相关人员知道你暂时没空可以避免不回应的表现。
优化你的环境
在家工作比和同事在办公室工作呈现出不同的物理动态。认识到这一点,你可以通过选择环境和控制时间表来积极主动地充分利用它。
仔细选择你的位置
当你在办公室的时候,很清楚你为什么在那里。虽然现代办公室肯定不是没有干扰的,但在一个特定的环境中可以提示你以这样或那样的方式思考。如果你熟悉睡眠卫生的概念——不要在床上做繁重的脑力工作,这样你的大脑就会养成在那个地方关闭的习惯——这听起来应该很熟悉。同样,在你的家里创造一个“工作区”也会有所帮助。即使它不是一个单独的房间,有一个你总是去工作的不受干扰的地方可以帮助你进入状态。
另一方面,远离办公室可以提供一些在办公室里不容易得到的东西。当你在一个问题上感到停滞不前时,试着换个位置。去另一个房间,或者你最喜欢的咖啡屋想一想,你会发现你正从另一个角度看问题。
把工作和生活分开
所以你离开了开放的办公室,不再去关注地板上每个同事的谈话。从理论上讲,这应该是一个万能药。现在你有安静的时间去进行那些深刻的思考了!除了现在,任何时候你都可以倒垃圾或者穿上网飞。诱惑是自然的,所以最好做好准备。一个策略是保持明确的工作时间。从 A 点到 B 点,你在工作,但不是在之后或之前。你也不做任何其他事情。如果你确实需要一些分散注意力的东西来打破安静或单调,那也计划一下吧。对我来说,最有效的方法是计划在午休时做一些与工作无关的事情。如果我想洗碗或者沉迷于娱乐,我会在休息吃饭的时候设置一个计时器。当计时器停止时,回去工作。
时间纪律的好处是,你可以保持高效率,而不会觉得有必要把工作时间延长到比正常情况下更晚。
听马斯洛的
到目前为止,这个列表主要集中在保持高效的实用技巧上。如果你打算长期在家工作,还有一个方面必须讨论。简而言之,保持你自己的精神健康和工作满意度。
腾出时间闲聊
很容易开始觉得你的同事是一些抽象的概念——只是一个松弛频道另一端的问答/提问聊天机器人农场。那可能不理想。即使对于那些因为电脑似乎比人更容易打交道而开始与电脑打交道的人来说,日常人际交往的数量大幅减少也会开始变得孤独。如果你的同事也离你很远,他们可能也会有同样的感觉。
如果你在办公室,当你在被称为咖啡机的建筑瓶颈处相遇时,每天至少会有几分钟的聊天。由于这种情况不再自然发生,所以可能需要有意识地考虑。如果你开始花几分钟时间在一对一的会议上补上一些不那么“议程驱动”的项目,它至少可以取代一个好的工作场所所能给予的那种团体感。和许多这些建议一样,这都是关于计划一些可能会自然发生的事情。
被听到
成为一名数据科学家的部分回报是有一个伟大的想法,然后看到它被使用,并且你的创造力得到认可。建立一个成功分享的好想法的组合是我们前进的方式。当与你互动的那群人受到限制时,你就更难确保自己的进步被人所知,而且感觉你的职业生涯可能会陷入停滞。这不一定是不可克服的。
一个策略是确保你的研究进展通过之前推荐的文档被广泛分享。多个利益相关者可能想要知道你的模型如何工作的一些细节,比如优点、缺点和准确性度量。共享这些数据的最佳方式可能取决于您所在组织的政策,但出于透明的考虑,在内部公共场所记录这些数据可能会很好。在公司的 wiki 上主动记录和参考细节,不仅可以让你不必不断地回答同样的问题,还可以在你取得的进步上打上你的印记。即使结果不是你所希望的,分享它们会建立信任,并表明你在认真测试你的方法。
更广泛地分享东西的一个可怕之处是可能会招致批评。然而,这是通向更好最终结果的途径的一部分。如果你的产品管理同事指出你的数据集缺少关键的人口统计数据,这并不意味着你的工作失败了。相反,这是一个优雅地解释你的理由或接受建议并改进的机会。最好在过程的早期就开始这种开放式复习的循环,而不是在过程结束时进行,因为那时课程的修改更加困难。
一些最后的话
远程工作可能会带来一系列挑战,这些挑战在共享空间中根本不存在,而且可能并不适合所有人。不过,主动适应 it 也能带来真正的机会。成功过渡到远程工作意味着保持专注、保持联系,并继续关注您的健康。如果你对此积极主动,你可能会发现你采用的实践实际上可以使你的数据科学更有影响力,即使最终你回到了办公室。
时间序列异常检测的有效方法
在当前的形势下,整个世界到处都在经历前所未有的情景,这通常被每个人称为*“新常态”。但在成为“*新常态 *”,*这些异常或反常的结果会对任何组织产生积极或消极的影响,对制定长期业务战略来说,跟踪这些结果非常重要。因此,每个领域中的异常检测都将是一个重要的讨论主题,关于执行异常检测的有效方法的知识将是任何数据科学家和数据分析师都必须掌握的一项重要技能。
在我们深入研究之前,我们必须澄清,异常现象到底是什么?异常的定义可能因域而异。在我们上面看到的封面图片中,母狮是斑马群中的异类。因此,从技术上概括地说,我们可以说异常是异常数据点,它不遵循大多数数据点的集体共同模式,因此可以很容易地从其余数据中分离或区分出来。
现在,进入今天的主题范围,*。我们将在本文中讨论什么*、为什么和如何时间序列异常检测部分。详细的 编码走读 ,请访问我的 网站 。
我在 YouTube 上的一段视频记录了一个类似的话题:
*** 更新 *如果你喜欢这篇文章,并希望更多地支持我对社区的贡献,请看看我的书 " 应用机器学习可解释性技术 " ,这是 GitHub 资源库,其中包含了书中涉及的各个章节的许多实践教程:https://GitHub . com/packt publishing/Applied-Machine-Learning-explability-Techniques。如果你喜欢 GitHub 资源库中提供的教程,请在资源库中做 fork 和 star,以示你对这个项目的支持!请订购该书的 纸质本 或 电子本 以示支持。
* [## 应用机器学习可解释技术:使 ML 模型可解释和可信…
应用机器学习可解释技术:使 ML 模型可解释和可信赖的实践…
www.amazon.com](https://www.amazon.com/Applied-Machine-Learning-Explainability-Techniques/dp/1803246154?encoding=UTF8&pd_rd_w=Wr6SJ&content-id=amzn1.sym.716a1ed9-074f-4780-9325-0019fece3c64&pf_rd_p=716a1ed9-074f-4780-9325-0019fece3c64&pf_rd_r=6P2PM599T97MRG7NZD9J&pd_rd_wg=m4qUW&pd_rd_r=6e349d93-5ba0-4bfe-9055-905c0153fe58&linkCode=li3&tag=adib0073-20&linkId=35506e1847de5c011fc57aa66c2b1d8e&language=en_US&ref=as_li_ss_il)
什么是时间序列异常?
在时间序列数据中,异常值或离群值可以被定义为不遵循整个数据的共同集体趋势或季节或周期模式的数据点,并且与其余数据明显不同。大多数数据科学家所说的显著性是指统计显著性,换句话说,这意味着数据点的统计属性与序列的其余部分不一致。
突出显示异常数据点的时间序列图(图片由作者提供)
从上面的时间序列图中,我们可以看到,与整个序列显著不同的 5 个数据点用红圈突出显示。因此这 5 个异常数据点不符合时间序列的总体正弦性质,因此可以被称为 时间序列异常 。
为什么时间序列异常检测如此重要?
如前所述,为了评估*【新常态】*以及重组和重构业务战略和决策流程,跟踪每个部门的异常情况非常重要,并且迫切需要持续详细地研究这些异常情况。数据科学家的角色不仅在这些困难时期变得至关重要,而且对于数据科学家来说,想出一种方法来跟踪、研究和分析异常数据点并获得对业务有意义的信息也成为一种自然的期望。从销售和营销,到供应链和制造,企业的每个阶段都需要足够的信息,特别是关于这些异常的信息,以塑造其流程并最大限度地提高生产率和成果。因此,只要我们在数据中有一个共同的模式,特别是对于时间序列数据,这是非常重要的隔离离群值,并抽出时间和精力来研究这些。
时间序列异常检测怎么做?
现在,进入本文最重要的部分,关于部分如何做时间序列异常检测**。从一个非常高的层次和一个非常通用的方式,时间序列异常检测可以通过三种主要方式来完成:**
- 通过预测置信水平方法
- 统计剖析方法
- 基于聚类的无监督方法
在这一节中,我们将只关注技术,在下一篇文章或帖子中,我们将体验算法的确切代码部分以及如何用 Python 编程这些方法的有效方式。
基于预测置信度方法的时间序列异常检测
通过置信度方法进行异常检测(图片由作者提供)
使用时间序列数据进行异常检测的一种方式是通过使用历史数据建立预测模型,以估计和了解时间序列数据的总体共同趋势、季节性或周期性模式。使用预测模型来预测未来值,并基于误差率(可以使用 MAPE-平均绝对百分比误差来计算),我们可以得出预测值的置信区间或置信带,任何超出该置信带的实际数据点都是异常的。为了建立预测模型,也可以有效地使用流行的时间序列建模算法,如 ARIMA、萨里玛、GARCH、VAR 或任何回归或基于机器学习和深度学习的算法,如 LSTM。这种方法的主要优点是发现局部异常值,但主要缺点是,这种方法高度依赖于预测模型的效率。预测模型中的任何循环漏洞都可能导致假阳性和假阴性。
通过统计剖析方法进行时间序列异常检测
通过统计分析方法进行异常检测(图片由作者提供)
这种方法可能是统计学家和数学家最喜欢的方法,在经济和金融领域得到了有效的应用。生成给定数据的统计模型或概况可能是最快和最有用的方法,因为这种方法可以提供更可控和更可解释的结果。这可以通过计算统计值来实现,如历史数据的平均值或中值移动平均值,并使用标准偏差来得出一组统计值,这些统计值可以定义上限和下限,超出这些范围的任何值都可能是异常值。正如我所说的,这种方法非常方便,并且总是可以作为基线方法,而不是使用任何需要大量微调并且可能无法解释的复杂方法。这对于高度不稳定的时间序列也非常有效,因为当数据高度不稳定时,大多数时间序列预测模型算法都会失败。但是这种方法的主要缺点是检测局部异常值。正如我们在上图中看到的,在五个明显的异常点中,只有两个最重要的异常点被检测到。
通过基于聚类的无监督方法进行时间序列异常检测****
通过基于聚类的无监督方法进行异常检测(图片由作者提供)
无监督的方法对于异常检测非常有用,因为它不需要任何标记的数据,提到特定的数据点是异常。因此,聚类算法对于时间序列异常检测非常方便。目前,用于异常检测的聚类算法的一个常见缺陷或瓶颈是定义聚类数,这是大多数聚类算法需要的输入。虽然有许多估计聚类数的技术,但是对于时间序列数据,动态估计每个序列的聚类数是不可行的。这时,带噪声的应用程序的基于密度的空间聚类(DBSCAN) 成为自然的选择。 DBSCAN 不需要任何预定义的聚类数,只有两个参数(一个聚类中的最小点数和ε,聚类之间的距离),所以非常容易调优,性能非常快。由于这些优点,DBSCAN 成为进行异常检测的最明显的选择,并且它不像传统的硬聚类技术(如 K-Means)那样将所有数据点分组到一个聚类中。DBSCAN 不将异常或离群数据点分组到任何集群,因此它变得非常容易应用。此外,DBSCAN 有助于绘制大多数其他方法可能会失败的【新常态】*。但是 DBSCAN 也有一些缺点。一些异常数据点如果在稀疏间隔内重复多次,根据 DBSCAN,这些可能不会被映射为异常。因此,在这种情况下,使用基于滚动窗口的 DBSCAN* 有助于更有效地绘制这些局部异常。**
更新如果你喜欢这篇文章,并希望更多地支持我为社区所做的贡献,请看看我的书“ 【应用机器学习可解释技术 ”,这是 GitHub 资源库,其中包含许多关于书中各个章节的实践教程:https://GitHub . com/packt publishing/Applied-Machine-Learning-explability-Techniques。如果你喜欢 GitHub 资源库中提供的教程,请在资源库中做 fork 和 star,以示你对这个项目的支持!请订购本书的** 实体本 或 电子本 以示支持。**
** [## 应用机器学习可解释技术:使 ML 模型可解释和可信…
应用机器学习可解释技术:使 ML 模型可解释和可信赖的实践…
www.amazon.com](https://www.amazon.com/Applied-Machine-Learning-Explainability-Techniques/dp/1803246154?encoding=UTF8&pd_rd_w=Wr6SJ&content-id=amzn1.sym.716a1ed9-074f-4780-9325-0019fece3c64&pf_rd_p=716a1ed9-074f-4780-9325-0019fece3c64&pf_rd_r=6P2PM599T97MRG7NZD9J&pd_rd_wg=m4qUW&pd_rd_r=6e349d93-5ba0-4bfe-9055-905c0153fe58&linkCode=li3&tag=adib0073-20&linkId=35506e1847de5c011fc57aa66c2b1d8e&language=en_US&ref=as_li_ss_il)
因此,这就把我们带到了本文的结尾。在 我的个人网站 中,我 写了一篇详细的文章,讨论并提供了如何应用这些技术的精确 python 代码,并更深入地解释了常见的优点和缺点 。在那之前,请鼓掌并激励我进行更多的讨论,并与社区分享我的发现。希望我能帮上忙!继续关注:https://medium.com/@adib0073和我的网站:https://www.aditya-bhattacharya.net/***
有效的数据过滤。位置[]
照片由韦德·奥斯丁·埃利斯在 Unsplash 上拍摄
学习使用的多种方法。loc[]过滤 Pandas 中的数据帧
Pandas 是用于数据科学研究的最流行的 Python 包之一。它收集了大量用于处理结构化数据的强大方法。处理这些数据表(即 pandas 中的数据帧)的一个常规任务是过滤符合某个预定义标准的数据。在我自己的研究中,在各种过滤方法中,我经常使用 DataFrame 的 loc 属性过滤数据。在本文中,我将向您展示我们如何使用。loc[]用于有效的数据过滤。
基本设置
假设您已经构建了自己的 IDE,并在计算机上安装了 Python 和 pandas,基本的设置步骤如下所示。如果你想在 Visual Studio 代码中运行 Jupyter Notebook,请参阅我以前的文章。
首先,我将pandas
导入到笔记本中。出于当前教程的目的,我从 Kaggle 下载了 city_attributes.csv 数据集。这个数据集有 4 列:城市、国家、纬度和经度。当我导入文件时,我将城市设置为索引,以便以后进行更有意义的索引。
基本设置
在我们进行任何数据处理之前,看看我们正在处理的数据集总是一个好主意。为此,我只使用了sample()
和shape
来获得 2 个随机行,并检查数据帧的大小,这向我们显示数据有 36 条记录。
数据过滤
使用标签
第一种方法是使用标签过滤数据。这里有几个例子。如您所见,我们只需指定行和列的标签,符合这些标签的特定数据记录就会显示出来。
使用标签过滤数据
从上面的代码片段中,有几件事情需要强调。
- 我们可以指定单个标签或标签列表来过滤 loc[]中的数据。
- 筛选的数据可以是不同的类型:单个值、系列或数据帧,分别如示例所示。
- 当只设置了一个标签或标签列表时,它将返回所有列。
使用范围
另一种常用的方法是使用行和列标签的范围。下面是一些例子。类似地,如上所述,我们可以设置一个或两个范围,前者显示所有列,后者显示范围内的列。
使用范围进行数据过滤
使用布尔值
过滤数据的一个有效方法是使用与我们正在处理的轴的长度相匹配的布尔值列表。例如,在下面的代码中,我们能够通过只显示加拿大的城市来过滤数据。具体来说,我们通过将国家的值与字符串“Canada”进行比较来创建一系列布尔值,该系列的长度与数据帧的行号相匹配。由于 pandas 将 True 评估为 1,当我们请求这个系列的总和时,我们得到了 3,这正是我们通过运行cities.loc[cities[‘Country’]==‘Canada’]
得到的行数。
使用布尔进行数据过滤
当然,我们可以有一个更复杂的布尔求值表达式。下面给出一个例子。
使用布尔值进行数据过滤(多条件)
使用 Lambdas 或自定义函数
有时,我们需要有一个更高级的数据过滤标准,在这种情况下,我们可以使用 lambdas,如下所示。在 lambda 函数中,我们指定了名为 row 的参数,该参数引用数据帧中的系列,这样我们就可以像上面所做的那样对其求值以生成布尔列表。换句话说,这个方法是建立在使用布尔值的数据过滤方法之上的,尽管它们在概念上是不同的。
使用 Lambdas 进行数据过滤
如果我们想要设置更多的标准,我们甚至可以编写一个数据过滤函数。如下所示,我们有一个名为filtering_cities()
的函数,它评估多个标准并返回一个布尔值列表,可用于过滤数据。我们将简单地在 loc[]中设置这个函数来过滤数据。
使用函数进行数据过滤
结论
本文向您展示了如何使用 loc[]通过多种方式有效地过滤数据。当然,本教程只是通过介绍使用 loc[]进行数据过滤的基本元素来提供一个概念证明,还有其他几种数据过滤方法,我们稍后可以一起探讨。
有效的数据可视化
构建有效的数据可视化的技巧可以简化为 3 个简单的步骤
由于各种原因,如我们的智能手机成瘾,数据一直在以指数速度增长。我不想让任何人感到内疚,因为我也一样内疚——问题是……数字化前所未有地将全球人类联系在一起。我们非常依赖我们的智能手机来保持我们的信息——如果不是因为 Twitter,我不会知道周四晚上 8 点是 NHS 的感谢日,因此我们为什么鼓掌 1 分钟——结果数据是每天大量产生的,因此为什么 21 世纪被大量预测为数据时代。
“数据是 21 世纪的石油”
随着数据量的增长,无论是在 excel 电子表格中,还是在文本或图像中,从越来越大的数据批次中识别关键见解都变得极其困难。人类大脑试图识别模式以帮助学习和存储信息,但是在大型文本语料库(和其他形式的数据)中,如果没有一些视觉数据,找到关键信息可能会非常困难。机器学习的发展允许系统在没有显式编程的情况下自动学习和改善经验,简化了检测数据模式的任务,我们可以通过预测分析创建有用的模式可视化。此外,在我们开始建立预测模型或做出决策之前,了解我们掌握的数据至关重要,甚至超出了数据科学和数据分析的领域。
关于这一点,在这篇文章中,我将提供 3 个技巧来建立可视化,有效地传达你想要表达的信息。为此,我使用了 Kaggle 的 房价 数据集,您可以从房价竞赛的数据部分下载该数据集( 单击此处 )。要访问我用来生成图形的代码,可以在我的 Github 上访问(下面的链接)。
与中等博客文章相关的演示代码。-路径/到/文件;链接到文章有效的数据可视化…
github.com](https://github.com/kurtispykes/demo/tree/master)
图 1:NASA 在 Unsplash 拍摄的照片
什么是数据可视化?
在我们继续之前,如果我们想要构建有效的可视化,了解什么是数据可视化及其背后的目的是很重要的,所以不再多说…数据可视化是以可视化格式表示数据。可视化数据的目的是以易于理解的可视化方式总结和呈现数据,向读者突出显示数据中的关键信息。
在上面对数据可视化目的的描述中,我们可以提取两个要点,如果在任何数据可视化任务中记住这两个要点,将会立即使我们的可视化效果更好:
第 1 点:可视化应该概括并呈现容易理解的信息。“容易”这个词在这里很关键,因为它没有提到视觉化的复杂性。这一点很重要,因为有些图形可能需要更复杂,以达到易于理解的目的。另一方面,如果以简单的方式创建一些可视化效果来满足相同的目的,效果会好得多。到本文结束时,您应该能够区分何时使用简单或复杂的可视化。
**第二点:**数据即将呈现!在某些情况下,可能只有可视化的创建者才能看到,例如,当您自己处理机器学习分类任务时,您希望了解您的预测模型正在犯什么类型的错误。但是几乎总是我们最终会向观众展示我们的发现。因此,可视化必须针对特定的受众,也就是说,读者会对所获得的见解感兴趣,否则他们会感到厌烦。用前面的例子来说,向 CEO 展示你的预测模型所犯错误的可视化效果是无效的,而且可能会让你失去高管们的信任。相反,更好的做法是呈现可视化效果,允许采取可操作的步骤来带来商业价值。
“有超过 99 种分散注意力的方式,有趣的视觉化效果不是第一种”——没有人
从根本上说,我们进行数据可视化的原因是为了帮助读者看到正在分析的数据中的模式或趋势。从今以后,我将提供一个简单的一步一步的指南来产生有效的可视化。
提示#1 明确应该通过可视化来回答的问题
扭曲关键信息的可视化会妨碍做出有意义或准确决策的能力。发生这种情况的一种方式是使用一个繁忙的图,试图在任何时候回答许多问题。通过有一个你希望通过你的视觉化来回答的特定问题,它防止了矫枉过正和试图在一个数字上做太多事情。图 2 旨在确定数据集中缺失变量的数量,并使用类似交通灯的系统来突出显示任何一个要素中缺失变量的数量。可以通过添加说明每种颜色含义的图例来进一步增强该图,例如,红色=缺失数据超过 75%的值。
图 2:显示特性、每个特性中缺失值的数量以及相对于实例数量的百分比的表格。
提示 2 选择正确的图表
“一幅画胜过千言万语”
这听起来很简单,但可能非常困难…图表应该揭示你想告诉读者什么。如果你试图告诉读者,从收集的数据来看,目标特征或因变量的分布,那么直方图、violin 图、qq 图等图可以有效地完成工作。图 3 中的图结合了 3(直方图、Violin 图和 QQ 图),让我们确信数据的分布是正偏的,这对于我们选择用于解决问题的模型非常重要。为了改进这个图,我会将标题移到中间,将 核密度估计器 添加到直方图中,并决定使用不同的颜色来突出我正在制作的点——注意,这些改进只是为了使图表更漂亮,因为它很好地通知了我们特征是倾斜的。
图 3:目标变量的分布
提示 3 强调最重要的信息
使用颜色、大小、比例、形状和标签将读者的注意力引导到你想让他们识别的关键信息上——图 2 在交通灯系统中做得很好。我们希望向我们的读者揭示最重要的信息——在图 2 的情况下,读者可能是将设计功能和构建模型的机器学习工程师。红绿灯立即将我们的注意力吸引到哪些实例可能是一个挑战,以及哪些可能需要从数据中删除。值得注意的是,可视化允许机器学习工程师或数据科学家推迟思考的困难任务,因为可视化立即向我们显示我们的数据有什么问题,同时产生可以实施以改进我们的数据的想法。用于实现这一点的工程不一定是用于最终模型的工程,关键是它有助于我们做出可以采取行动的决策,即产生的想法可以用于设计快速基线模型。
图 4:显示特性相关性的热图。
在图 4 中可以看到另一个强调重要信息的例子。热图显示了功能之间的相关性,并使用更明亮的颜色来强调两个功能之间更强的关系。这使得挑选相关特征并对其做进一步分析变得容易。这样的信息对数据科学项目中的其他团队成员是有益的,因为它将有助于确定哪种方法对于这项任务是可行的。为了改进该表,我们可以在 x=y 线下方(或上方)的图上放置一个遮罩,因为它是冗余数据。
虽然我没有强调本文中可以使用的工具,但在某些方面,我们使用的工具可以帮助我们实现数据可视化的目的。数据科学家会知道 Matplotlib 和 Seaborn 等工具,但在本文中,我用来生成数据的工具是 Ploty.py。该框架允许交互式图表,这使得我们的可视化对读者来说更有吸引力。了解这个框架更多信息的一篇好文章是由 Will Koehrsen 撰写的,标题是“Python 中数据可视化的下一个层次,T2”。在这篇文章中,他提到了他从传统可视化工具切换到现代工具的理由,并给出了很好的入门演示。
结论
在可视化数据时应该记住的关键因素是,数据可视化是以一种容易理解的方式总结和呈现数据。因此,我们希望确保我们用可视化来回答特定的问题,在决定使用什么图表来显示数据时做出正确的选择。最后但同样重要的是,我们要确保我们强调的是我们希望读者抓住的关键信息,而不是用多余的信息混淆它们。
有很多方法可以可视化数据,提高这一技能的最好方法是实践。我用来为本文生成图表的 Github 代码远非最佳,没有充分利用 Plotly.js 的功能,并且缺乏深度——在这些数据中还有很多可以探索的地方。因此,有一个很好的机会让你去实践(我已经给了你一个轻微的开端)——简单地分叉我的工作(下面 Github 的链接),从 Kaggle ( 点击这里下载 )下载数据,安装需求,然后开始工作。
与中等博客文章相关的演示代码。-路径/到/文件;链接到文章有效的数据可视化…
github.com](https://github.com/kurtispykes/demo/tree/master)
如果你想分享你如何利用这篇文章来改进我的工作(或你已经完成的工作),这将是真正令我兴奋的,或者你只是想就任何与数据科学有关的事情联系我(即接下来可能要写的东西),你可以通过 Linkedin @KurtisPykes 联系我,或者只是对这篇文章发表评论。此外,我非常有兴趣听到你对这篇文章的反馈,所以请不要犹豫与我联系!
非常感谢您的宝贵时间!
其他资源关于此主题的有用资源…
Georgin Lau 和潘磊博士- 数据可视化的 5 步指南
鲁米博物馆- 与房价的详细回归
佩德罗马塞利诺- 用 Python 进行全面的数据探索
使用 PyCharm 和 Docker 的有效深度学习开发环境
借助全面承诺的 IDE 和虚拟化提高您的工作效率。
塞缪尔·伯克在 Unsplash 上的照片
在本文中,当您在远程环境(内部工作站、AWS、GPUs)中使用 GPU 时,我将指导您为深度学习培训建立一个有效的开发环境。
最后,您将在本地计算机上使用 PyCharm IDE 开发您的 ML/DL 代码,就好像它在您的本地环境中使用 Python 解释器一样。
动机
1.为什么要用 PyCharm 这种成熟的 IDE?
“因此,如果你想走得快,如果你想快速完成,如果你想让你的代码易于编写,就让它易于阅读。”
― 罗伯特·c·马丁, 干净的代码:敏捷软件工艺手册
- 当你训练你的深度学习代码时,你会做很多实验。你改变你的超参数,添加/删除层来优化模型,调整你的数据集…所以,过一会儿,代码可能会很难管理。因此,您需要编写整洁且可维护的代码。像 PyCharm 这样的 IDE 可以给你提供这样的环境。
- 在编码时,你必须实时观察你的变量和函数,看看它们是否如你所愿,否则你可能会在长时间的训练后遇到意想不到的结果。打印变量的值或函数的返回值是很常见的,大多数人都是通过在行间打字来完成的。这不是观察变量的正确方法;相反,你应该从外部观察它们*。使用 PyCharm 变量资源管理器和 IPython 控制台可以轻松查看代码的行为和变量值。*
- 阅读库的文档来为我们的任务找到合适的函数或对象是很费时间的。自动完成在编写快速代码时非常方便。然而,在像 Python 这样的动态类型语言中很难做到这一点。尽管其他 ide 和 Jupyter Lab 也有自动完成功能,但它们并不总是能正常工作。皮查姆非常擅长那项工作。
我认为这三点是使用 PyCharm 的关键因素,但还有许多其他的巨大优势:自动格式化、高级搜索、错误检查、轻松导航、智能重构、剖析、着色…
2.为什么应该使用 Docker?
如果你在 Ubuntu 机器上建立了深度学习环境,你就会知道匹配所有这些驱动程序、软件和库的版本有多难。如果你没有遇到任何困难,祝贺你!但是你可能还没有更新你的任何驱动,CUDA,或者深度学习(Tensorflow,PyTorch…)库,这意味着你随时都可能遇到这个困难。
深度学习框架(尤其是 TensorFlow)在版本更新上非常激进,这可能导致与 CUDA 版本或 NVIDIA 驱动程序不兼容。有时候,很难匹配这些软件的发布。此外,如果您不小心同时安装了不同版本的 NVIDIA 或 CUDA 驱动程序,处理起来可能会很复杂,您可能需要格式化您的系统。
Docker 解决了这些问题。你只需要安装 NVIDIA 驱动,其余的由 Docker 容器管理。
比如你正在做 TensorFlow,但是你看到在 PyTorch 的新版本中,有针对你的任务的完美功能,你想尝试一下。在同一台机器上管理不同的框架(和不同的版本)是一项挑战。使用 Docker,您可以管理它。它抽象了硬件驱动之上的系统;因此,您可以同时运行不同版本的框架和 CUDA。它类似于虚拟机,但没有虚拟机那么重。使用 Docker 有很多好处,但在这篇文章中,我不打算告诉这些。网上已经有很多很棒的文章,你可以广泛地阅读和获取信息。这篇文章是关于连接这两个产品。我们开始吧。
设置系统
流程图
1。 在本地电脑上安装 PyCharm (使用远程解释器需要专业版,PyCharm Professional 对学生免费)。
2。在你的远程机器上安装 Docker 。
2.1。 熟悉Docker 环境(可能需要一个工作日才能完全理解什么是映像、容器、如何从 Docker Hub 获取以及 Docker 系统的一般工作方式,但你会得到回报)。
**2.2。**把你喜欢的深度学习框架的 Docker 镜像下载到远程机器上。
现在,是时候将 PyCharm Python 解释器连接到 Docker 容器中的 Python 环境了。
**1。**启动 Docker 容器。转发端口 8022(可以这样改)到 22 ( 对于 SSH 连接来说是必须的)。示例:
sudo docker run --gpus all -it --rm -p 8022:22 -v /home/workstation/Desktop:/home/gorkem/Desktop --name ssh_container pytorch/pytorch:1.4-cuda10.1-cudnn7-devel bash
**2。**使用主操作系统的终端,检查您是否确实将您的端口 8022 转发到了 22 号集装箱。
sudo docker port <your container name> 22
-> 0.0.0.0:8022
**3。**在 Docker 容器中设置 SSH 服务器。
apt update && apt install -y openssh-servermkdir /var/run/sshdecho 'root:<USE_YOUR_OWN_STRONG_PASSWORD>' | chpasswd
# Root password was changed with <USE_YOUR_OWN_STRONG_PASSWORD>sed -i 's/PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_configsed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshdecho "export VISIBLE=now" >> /etc/profileservice ssh restart
**4。**我无法通过 SSH 进入容器中的 root 用户,所以我创建了另一个 sudo 用户并建立了一个 SSH 连接。
adduser gorkem
adduser gorkem sudo
**5。**现在,你应该提交你的容器作为一个新的图像。否则,当您终止它时,您将丢失您对 contanier 所做的所有更新。我通常会在提交的图像末尾添加-ssh
标签。
docker commit <container_name> <name>/<name>:<tag>
6。关闭容器。
7。从提交的映像启动一个新的 Docker 容器。
sudo docker run --gpus all -it --rm -p 8022:22 -v /home/workstation/Desktop:/home/gorkem/Desktop --name ssh_container pytorch/pytorch:1.4-cuda10.1-cudnn7-devel-ssh bash
8。检查 SSH 状态,容器启动时会禁用(但可以设置为容器启动时自动启动),启动 SSH 服务器。
service ssh status
-> * sshd is not runningservice ssh start
-> * Starting OpenBSD Secure Shell server sshdservice ssh status
-> * sshd is running
9。SSH 服务器正在你的 Docker 容器中运行,现在你可以按照官方的 PyCharm 指南将你的 PyCharm 远程解释器连接到这个容器。在两个问题上你应该小心:
9.1 您的用户名是您在 Docker 中创建的用户名(在我的例子中,它是gorkem
)。
9.2 虽然 SSH 默认工作在端口 22 上,但是你必须使用转发端口(在我的例子中是 8022 )。PyCharm 首先连接到远程机器,然后 SSH 连接被转发(由 Docker)到我们的容器。
在从 PyCharm 直接连接到 Docker 容器之前,您可以使用类似于 Putty 的程序来测试 SSH 连接(还是要注意端口号和用户名),以确保容器端一切正常。
现在您可以在 PyCharm IDE 上开发您的算法,就好像您的培训是在您的本地 PC 上运行一样。
如果您有任何问题或改进,请评论。
觉得这篇文章有用?看看我下面的其他文章:
传统的 CNN(Alex net,VGG,GoogLeNet,ResNet,DenseNet …)在样本较多的情况下有很好的表现…
medium.com](https://medium.com/swlh/deep-learning-architectures-that-you-can-use-with-a-very-few-data-8e5b4fa1d5da) [## 如何解决你深度学习训练中的数据加载瓶颈
即使你没有固态硬盘!
medium.com](https://medium.com/@GorkemPolat/how-to-solve-data-loading-bottlenecks-in-your-deep-learning-training-1ddfcc24449b)
有效的 SQL 技能提升
Domenico Loia 在 Unsplash 拍摄的照片
如何通过现成的 leader 数据库(免费)使您的 SQL 升级更有效。
任何数据主题中的“提升技能”都意味着两件互补的事情:学习新技能(语言、算法等)。),并不断更新您已经建立的技能。
在这个故事中,我的目标是与你分享一些提升 SQL / PL-SQL / T-SQL 技能的方法。
让我们远离 SQL 懒惰
任何数据科学学科,或任何包含“…数据…”word 在某些时候需要查询数据库并进行一些开发。事实上,我参加过的任何工作面试(无论是作为面试官还是被面试者)都会测试应聘者的 SQL 专业知识以及其他方面。
但问题来了:一旦我们得到了这份工作,我们倾向于只使用我们技能组合中的一小部分来处理公司的数据生态系统,并最终实现老板的期望。我承认:有时我在编写查询或进行数据库开发时变得“懒惰”,只是因为数据库和工具允许我选择肮脏的方式来做事。一个显著的例子是:我曾经做过一个客户流失分析项目,在这个项目中,一个经过良好调整的 Teradata 集群被用来处理相对少量的电信交易。它可以在几毫秒内运行任何脏的分析查询。
另一方面,当数据库配置很差、容量太大或者没有适当调整时,我们必须找到构建查询或在其上开发的聪明方法。查询需要一个完美优化的代码,开发需要使用正确的对象(正确类型的索引、分区、统计信息集合等)…).此外,在这种情况下,我们需要深入了解我们正在处理的平台:在 MS-SQL Server 上最有效的技巧可能与在 Oracle 中不同,等等。
我很确定你也遇到过一些蹩脚的 DB,对吧?因此,在任何情况下,我们能找到的最好的解决方案就是不断提升我们的数据库和 SQL 知识。
如果你想知道为什么数据人员需要掌握 SQL,这里有一个很好的故事:
[## 每个有抱负的数据科学家必须学习 SQL 的 5 个理由
随着海量数据的出现,企业和行业正在收集和产生数十亿的数据…
medium.com](https://medium.com/analytics-vidhya/5-reasons-every-aspiring-data-scientist-must-learn-sql-2bab007a8d76)
我们的目标:提升 SQL 技能
在这里,我将解释一段时间以来我一直在做的事情,提升自己的技能,为他人提供培训,以及更广泛地分享知识。
简而言之,我用的是两个免费平台:【SQL Live】和【Dev Gym】。要访问它们,您需要创建一个 Oracle 登录名。
1.“SQL Live”在线数据库
它提供了对实时 Oracle(企业版!)数据库,已经设置了各种模式和模型类型,其中有一些默认数据,您可以实时查询这些数据。
作者截图
最有用的是 SQL 工作表,连接数据库和运行任何脚本(DML、DDL 等)的实际实时会话…).
作者截图
2.“开发健身房”培训平台
它包括锻炼、课程、关于 SQL、PL/SQL 的测验、分析、数据库设计、调优等…从完全的初学者到专家的任何水平。
我认为自己是专家,但我可以保证,在 Dev Gym,你总会找到比你更专业的人,从他们身上你可以学到很多东西。
我不会对你应该做哪些锻炼提出建议,你可以根据 SQL 水平和感兴趣的话题浏览和选择。我想给你的唯一建议是,先固定好自己的目标,然后按周或按月制定一个自我训练时间表,这样你就可以连贯地规划你想学的内容。
让 Dev Gym 和 SQL Live 一起工作
Dev Gym 的优势在于,您选择的任何培训(锻炼、课程、测验)都已经有了配置练习所需的源脚本,因此您不需要花费时间来构建虚假的表格和数据。
您只需要获取这些脚本,并在 SQL Live 中运行它们来创建必要的对象,然后您可以使用这些对象进行实际的训练和编码。
让我们来看一个例子:我们参加“sensive SQL”测验,并选择“关于分析函数的测验”模块。Code 按钮允许您导入代码,然后在 SQL Live 会话中运行代码,以构建和加载表。
作者截图
作者截图
现在,您可以构建查询,并通过 SQL 工作表对创建的对象进行锻炼
作者截图
SQL-Live 内置数据模型
使用“SQL Live”的另一个好方法是利用几个内置的模式,用不同类型的模型来表示现实世界中模型的简化版本。静态数据、OLTP、星型模式化已经可以使用了,或者您甚至可以导出模式和数据,并在其他数据库中重新创建它们,方法是根据您的需要修改 Oracle 语法。
作者截图
保持你的知识更新
几乎每个主要数据库供应商的新版本都引入了新功能。在过去的几年中,所有主要的数据库供应商都增加了许多新的聚类、分析和统计功能。
事情是这样的,其中一个玩家首先推出新功能,然后所有其他玩家通常会跟着做同样的事情。从这个角度来看,甲骨文通常是领先者。
“SQL Live”的另一个优势是它会定期升级到最新的数据库版本,这样您就可以尝试和学习新发布的功能,而无需花费时间升级数据库。
局限性和解决方法
“SQL Live”和“Dev Gym”基于 Oracle 技术,这意味着其中的所有材料都使用 Oracle 的 SQL 和 PL/SQL。那么,我们能做些什么来在另一个数据库上练习同样的练习、锻炼等呢?
这里有一个技巧:所有的“SQL Live”和“Dev Gym”脚本都可以导出(您将在编辑器附近看到导出代码按钮),并且您可以修改它们以在任何数据库上运行,主要是通过改变 DDLs 语句中的数据类型、修改 DMLs 或替换函数(数据转换、分析等)…)与您的目标数据库所接受的进行比较。
如果您不熟悉 Oracle 数据库,我相信它是值得学习的:Oracle 总是排在 Garner 魔力象限的最顶端。
提升 SQL 技能的其他方法
如果您不想使用上面解释的 Oracle 环境,我可以提出另外几个想法:
- https://www.db-fiddle.com/允许在各种数据库上运行实时 SQL 代码:MS SQL Server,MySQL,PostgreSQL。但是没有预先建立的物体、锻炼或训练:你需要准备你自己的环境。另一个好处是你不需要创建一个登录。
- 获取并安装 MySQL :设置需要 15 分钟,但是值得拥有。我已经描述了如何设置它,并可选地将其连接到 ETL 工具、R(对于 Python 也是可能的,但我在我的另一篇文章中没有解释如何做),或 Dashboarding 工具:
我在家里的“真实世界”数据科学环境:数据库,ETL,数据分析,仪表板,9 个步骤。
medium.com](https://medium.com/@maw.ferrari/create-a-real-world-data-science-environment-at-home-7a4255ff6fa5)
简而言之…
- 通过免费访问预先构建的数据库,可以在世界一流的数据库上升级 SQL,从而避免在安装和数据设置过程中花费时间
- 我解释了如何通过使用一个活动的 Oracle 数据库(SQL Live)和一组内置的模型、练习和锻炼(Dev Gym)来实现这一点。
- 然后,我们看到了如何将 Oracle 模型和练习迁移到其他平台(SQL Server 等…).
- 最后,我提到了 Oracle 的另外两种替代方案,它们允许 SQL 升级。
感谢阅读!**
如果你想订阅《灵媒》,请随意使用我的推荐链接https://medium.com/@maw-ferrari/membership:对你来说,费用是一样的,但它间接有助于我的故事。
随着时间的推移有效地可视化数据,以讲述更好的故事
使用 Python 和 Plotly 构建清晰易读的时序数据可视化,以支持您的叙述。
2018-2019 NBA 常规赛
不言而喻,时间是我们所做的几乎每一件事情不可或缺的一部分,包括构建我们对周围世界理解的叙事。当谈到基于数据的 T2 讲故事时,情况也是如此,我们经常需要将数据可视化。无论是公司的股票价格、候选人的选举民调数据,还是一个国家的人口数量,数据随时间的变化都是叙事的关键要素。
但是可视化时间序列数据可能是一个具有挑战性的命题。当有多个数据系列时尤其如此,这些数据系列在小空间内相互交错,就像一群第一次踢足球的不守规矩的 6 岁儿童。
看看下面的图表,它是由 StackOverFlow 用户发布的。
你能看懂这张图表吗?(来自斯塔克伟福
正如你可能想象的那样,发帖人正在寻求帮助,如何使图表更清晰易读。甚至很难识别出图中的单个痕迹,更不用说弄清楚任何可能存在的模式了。
我注意到这个图表只包括四个轨迹。
另一方面,在 FiveThirtyEight 看一看这位来自 Andrew Flowers 的美女。
那更好!(五三八)
该图表包括了不到 60(!)痕迹。但是 FiveThirtyEight 对饱和度、颜色和标签的明智使用彻底改变了游戏。即使在这 60 个数据系列之外,关键的 10 个系列也清楚地显示出来,使读者能够识别每个系列,并随着时间的推移跟踪它,以支持所写的散文。
显然,我们都希望我们的图表看起来更像后者,而不是前者。所以在这篇文章中,我想谈谈如何做到这一点,使用我常用的数据可视化工具(Plotly)来可视化数据。至于数据,让我们用 2018–2019 NBA 赛季数据来尝试重新讲述它的故事;当然,我们可以很容易地使用股票、城镇人口或我的体重数据。我们走吧。
在开始之前
数据
我把代码和数据放在我的 GitLab repo 这里 ( nba_storytime
目录)。所以请随意使用它/改进它。
Packages
我假设您熟悉 python。即使你相对较新,这个教程也不应该太难。
你需要pandas
和plotly
。用一个简单的pip install [PACKAGE_NAME]
安装每一个(在您的虚拟环境中)。
获取您的数据
我选择的数据集是每个 NBA 球队比赛结果的集合。用以下内容加载csv
文件:
all_records_df = pd.read_csv('srcdata/2018_2019_season_records.csv', index_col=0)
数据集包括每一场比赛,包括季后赛。数据集包括字符串形式的比赛日期,因此我们可以按日期过滤它们:
records_df = all_records_df[pd.to_datetime(all_records_df.date) < pd.to_datetime('2019-04-12')]
看起来是这样的:
这些数据包括每支球队在这些比赛日期的累计净得分、比赛次数、胜败情况。
换句话说,随着时间的推移,这些数据讲述了每个球队的赛季故事。我们来看看如何最好地讲述每个团队的故事,如何突出关键时刻,通过数据发现什么有趣的故事。
用数据讲故事
介绍
让我们把数据绘制成散点图。我们可以生成图表来查看每场比赛的累积胜率:
import plotly.express as px
fig = px.scatter(records_df, x='date', y='wins', color='team')
fig.show()
赢与玩的游戏——分散
这是一个很好的开始,但这是一个相当拥挤的图表,尤其是在赛季初。一些颜色在这里和那里很突出,但对我来说,似乎有太多的重叠,挑选出特定的数据集或注意数据中的模式是很清楚的。
让我们来看看其他一些可视化数据的方法。
整理数据
如果我们把散点图的点连接起来,把它转换成线图,会怎么样?
(代码方面,这将做到:fig.update_traces(mode=’markers+lines’)
)
赢与玩的游戏—带标记的折线图
好吧,还不错。因为我们想讲述一个团队发展的故事,所以折线图可能是比散点图更好的选择。(如果您不想看到这些点,请指定:fig.update_traces(mode=’lines’)
)
赢与玩的游戏—仅折线图
不过,这里有 30 条跟踪,很难分辨出哪条跟踪在做什么。此外,我们的边上的传说似乎是在一个随机的顺序,使发现困难。让我们解决这个问题。
我们所要做的就是传递一个类别参数值的实参,以你喜欢的任何顺序作为一个列表。我只是将数据按字母顺序排序,然后像这样传递:
tm_names = [i for i in records_df.team.unique()]
tm_names.sort()
fig = px.scatter(records_df, x='games', y='wins', color='team', category_orders={'team': tm_names})
使用有序名称
右边的图例现在按字母顺序排列,使我们能够更容易地找到并隔离正确的轨迹。
说到寻找痕迹——你注意到每条痕迹的颜色都变了吗?那是因为这些颜色是任意分配的。(当然是——我们还没有指定任何颜色映射!)这在某些情况下没问题,但我们可以做得更好。通常,一个数据源会有一种颜色,读者会将这种颜色与它联系起来。在这种情况下,为什么我们不给每条赛道分配颜色呢?
丰富多彩的语言
我包含了一个我整理好的字典文件,格式为{teamname: teamcolour}
。加载它:
import pickle
with open('srcdata/teamcolor_dict.pickle', 'rb') as f:
team_col_dict = pickle.load(f)
我们使用列表理解来构建一个颜色列表,其顺序与我们上面使用的团队列表(tm_names
)的顺序相同:
team_cols_list=[team_col_dict[tm] for tm in tm_names]
简单地传递这个列表作为color_discrete_sequence
的一个参数:
fig = px.scatter(records_df, x='games', y='wins', color='team', category_orders={'team': tm_names}, color_discrete_sequence=team_cols_list)
fig.update_traces(mode='markers+lines')
fig.show()
现在用队服!
这并不完美——太多的队服颜色是深绿色和红色的变体。但是,至少现在更容易知道哪个跟踪可能指向哪个团队。
还记得我们在上面看到的 538 图表吗?该图以去饱和灰色显示了大部分轨迹,有助于突出彩色轨迹。让我们试试类似的东西。
最简单的方法是修改我们传递的颜色列表。只需保持我们感兴趣的痕迹的颜色,并将所有其他颜色改为灰色。有很多方法可以做到这一点,但我通过构建一个全新的列表来做到这一点:
base_col = '#C0C0C0'
team_cols_list = list()
for i in range(len(tm_names)):
tm = 'TORONTO_RAPTORS'
if tm_names[i] == tm:
team_cols_list.append(team_col_dict[tm])
else:
team_cols_list.append(base_col)
以完全相同的方式绘制图表,您会看到:
特别强调一条线索(在这种情况下,是最终的 NBA 冠军)
想突出多个痕迹?这很容易做到。再加上猛龙的赛区对手。
base_col = '#C0C0C0'
hero_teams = ['TORONTO_RAPTORS', 'PHILADELPHIA_76ERS', 'BOSTON_CELTICS', 'BROOKLYN_NETS', 'NEW_YORK_KNICKS']
team_cols_list = list()
for i in range(len(tm_names)):
if tm_names[i] in hero_teams:
tm = tm_names[i]
team_cols_list.append(team_col_dict[tm])
else:
team_cols_list.append(base_col)
fig = px.scatter(records_df, x='games', y='wins', color='team', category_orders={'team': tm_names}, color_discrete_sequence=team_cols_list)
fig.update_traces(mode='markers+lines')
fig.show()
大西洋赛区记录(抱歉,尼克斯球迷)
太好了,但是还有一个小问题。一些痕迹(像黑色的布鲁克林篮网的痕迹)被灰色的痕迹掩盖了。我理解这是由于 Plotly 渲染散点图的方式。轨迹实际上是在图例上从上到下呈现的。因为网队的名字出现在顶部,它被埋在底部。
所以让我们改变一下我们队名的顺序。我们通过以不同的方式构造我们的团队名称列表来做到这一点——之后可以使用相同的剩余代码。
hero_teams = ['TORONTO_RAPTORS', 'PHILADELPHIA_76ERS', 'BOSTON_CELTICS', 'BROOKLYN_NETS', 'NEW_YORK_KNICKS']
tm_names = [i for i in records_df.team.unique() if i not in hero_teams]
tm_names.sort()
tm_names = tm_names + hero_teams
更好,对吗?(还是对不起尼克斯球迷)
收尾
支线剧情
我们的情节开始看起来相当不错——所以让我们对它们做一些最后的润色。第一,我想比较一下联盟中的两个“联盟”,并强调一下西部的西南赛区。
将分队添加到我们的列表中,并将‘conference’
传递给我们的facet_col
参数来创建两个支线剧情:
# Separate Conferences, and add highlights for
hero_teams = [
'TORONTO_RAPTORS', 'PHILADELPHIA_76ERS', 'BOSTON_CELTICS', 'BROOKLYN_NETS', 'NEW_YORK_KNICKS',
'HOUSTON_ROCKETS', 'SAN_ANTONIO_SPURS', 'DALLAS_MAVERICKS', 'MEMPHIC_GRIZZLIES', 'NEW_ORLEANS_PELICANS'
]
tm_names = [i for i in records_df.team.unique() if i not in hero_teams]
tm_names.sort()
tm_names = tm_names + hero_teams
team_cols_list = list()
for i in range(len(tm_names)):
if tm_names[i] in hero_teams:
tm = tm_names[i]
team_cols_list.append(team_col_dict[tm])
else:
team_cols_list.append(base_col)
fig = px.scatter(records_df, x='games', y='wins', color='team',
category_orders={'team': tm_names}, color_discrete_sequence=team_cols_list, facet_col='conference')
fig.update_traces(mode='markers+lines')
fig.show()
支线剧情
通过将两组数据分成次要情节,我们可以立即看出两次会议之间有趣的对比。很明显,右边的支线剧情(西部联盟)包括更多的轨迹,这些轨迹以更高的比分结束,表明更多的球队赢得了更高的总比分。除了一个异常点,这张图表表明 2018-19 赛季联盟最差的球队主要在东部联盟。
绘图主题/模板
这真是太棒了,我刚刚才知道,所以我想在这里简单地分享一下——尽管我正在学习如何使用它们。Plotly 包括一个“主题/模板”功能,允许您快速更改绘图的外观。
我可能会在另一篇文章中深入研究更多的细节,但是现在,请注意,传递模板参数将允许您改变许多绘图格式,例如:
fig = px.scatter(records_df, x='games', y='wins', color='team', category_orders={'team': tm_names},
color_discrete_sequence=team_cols_list, facet_col='conference', **template="plotly_white"**)
带有“plotly_white”主题
“plotly_black”主题
也许你更像是一个 ggplot2 的人
由于我指定了轨迹的团队颜色,轨迹颜色没有改变,但是主题化也会改变默认的颜色图。点击查看文档。
标题和字体大小
为了完整起见,这里有一个版本的图,标题、支线剧情名称和轴标题都经过了格式化处理——以产生一些接近我将在分析文章中使用的内容。
fig = px.scatter(records_df, x='games', y='wins', color='team', category_orders={'team': tm_names},
color_discrete_sequence=team_cols_list, facet_col='conference', template="ggplot2",
title='2018-19 NBA Regular season wins',
labels={'games': 'Games played', 'wins': 'Season Wins'}
)
fig.for_each_annotation(lambda t: t.update(text=t.text.replace('conference=', 'Conference: ')))
fig.update_traces(mode='markers+lines')
fig.show()
除了.for_each_annotation
方法之外,上述所有方法可能都非常简单。
我在这里做的是用我认为可读性更好的‘Conference: ‘
替换部分自动生成的支线剧情标题文本(比如‘conference=Eastern’
)。
这是由 lambda 函数完成的,其中 Plotly 的。for_each_annotation 负责遍历每个注释对象并应用 lambda。
结果如下:
这是我之前做的一个
这看起来是非常简单的数据,但在这种形式下,数据比我们的第一个图更容易消化。如果你不相信我——只需向上滚动,看看我们生成的前两个图。我可以等。
对吗?
通过突出个人的痕迹,我们不仅可以快速展示这些特定的团队是如何在会议的背景下被放置的,还可以展示他们自己的部门的海洋。每个轨迹的梯度显示了它们在季节的不同阶段经历的热(陡)或冷(平)条纹。
此外,在线 Plotly 图表提供了与鼠标悬停数据的交互性,这为用户提供了很好的服务,使他们不必在图表和图例之间来回切换。
不要忘记交互性
对于时间序列数据,我们几乎可以做无限多的事情,因此很难将数据跨时间可视化。如果做得不好,看起来实际上有无限的数据点和数据系列要绘制并传达给我们的读者。
因此,能够从噪音中获取关键细节或信息是很重要的,对我来说,简单的东西会帮助你做到这一点——无论是颜色、比例、支线剧情还是其他什么。事实上,我经常发现一个关键的区别是不要让读者被信息淹没。事实证明,越少往往越多!
这就是我今天的全部内容——感谢您的阅读,如果您有任何建议或要求,请告诉我。
如果你喜欢这个,比如说👋/关注推特,或点击此处获取更新。ICYMI:我还写了这篇关于用 Plotly Dash 构建 web 数据仪表板的文章。
[## 使用 Python 在几分钟内构建一个 web 数据仪表板
通过将您的数据可视化转换为基于 web 的仪表板,以指数方式提高功能和可访问性…
towardsdatascience.com](/build-a-web-data-dashboard-in-just-minutes-with-python-d722076aee2b)
另外,这是我写的另一篇关于可视化类别变量的文章:
[## 用 Python 处理和可视化多个分类变量——NBA 的赛程挑战
使用平行分类图和树形图来可视化、识别和比较多个输入变量在…
towardsdatascience.com](/processing-and-visualizing-multiple-categorical-variables-with-python-nbas-schedule-challenges-b48453bff813)
使用 Pandas 的 Groupby 功能进行有效的数据汇总和分析
迈克尔·佩恩在 Unsplash 上的照片
学习使用聚合函数,数据转换,过滤,映射,在数据框架中应用
Groupby 是熊猫很受欢迎的功能。它非常擅长汇总、转换、过滤和其他一些非常重要的数据分析任务。在本文中,我将结合实例详细讲解 groupby 函数的应用。
资料组
在本文中,我将使用 Kaggle 的“学生表现”数据集。请从这里随意下载数据集:
此时您不能执行该操作。您已使用另一个标签页或窗口登录。您已在另一个选项卡中注销,或者…
github.com](https://github.com/rashida048/Datasets/blob/master/StudentsPerformance.csv)
在这里,我将导入必要的包和数据集:
import pandas as pd
import numpy as np
df = pd.read_csv('StudentsPerformance.csv')
df.head()
Groupby 是如何工作的?
Groupby 函数根据您定义的条件拆分数据集。这里我展示了 groupby 函数背后的过程。它会给你一个概念,如果没有 groupby 函数,我们要做多少工作。在本节中,我将创建一个包含两列的更小的新数据集来进行演示。这两栏是“性别”和“阅读分数”。
test = df[['gender', 'reading score']]
test.head()
让我们找出不同性别的平均阅读分数
首先,我们需要根据性别分割数据集。仅生成女性数据。
female = test['gender'] == 'female'
test[female].head()
以同样的方式,为男性生成数据,
male = test['gender'] == 'male'
test[male].head()
使用上面的女性和男性数据集分别计算女性和男性的平均阅读分数。
fe_avg = test[female]['reading score'].mean()
male_avg = test[male]['reading score'].mean()
print(fe_avg, male_avg)
女性的平均阅读分数为 72.608,男性的平均阅读分数为 65.473。现在,做一个女性和男性平均阅读分数的数据图表。
df_reading = pd.DataFrame({'Gender': ['female', 'male'], 'reading score': [fe_avg, male_avg]})
现在,**让我们用 groupby 函数解决同样的问题。**根据性别分割数据,并对其应用“平均值”,只需一行简单的代码:
test.groupby('gender').mean()
这一小段代码给出了相同的结果。
分组依据中的组
我现在将使用原始数据集“df”。按“种族/民族”分组。
race = df.groupby('race/ethnicity')
print(race)
输出:<pandas.core.groupby.generic.dataframegroupby object=“” at=“”></pandas.core.groupby.generic.dataframegroupby>
它返回一个对象。现在检查“race”的数据类型。
type(race)
输出:pandas . core . group by . generic . data frame group by
因此,我们生成了一个 DataFrameGroupBy 对象。对此 DataFrameGroupBy 对象调用组将返回每个组的索引。
race.groups#Here is the Output:
{'group A': Int64Index([ 3, 13, 14, 25, 46, 61, 62, 72, 77, 82, 88, 112, 129, 143, 150, 151, 170, 228, 250, 296, 300, 305, 327, 356, 365, 368, 378, 379, 384, 395, 401, 402, 423, 428, 433, 442, 444, 464, 467, 468, 483, 489, 490, 506, 511, 539, 546, 571, 575, 576, 586, 589, 591, 597, 614, 623, 635, 651, 653, 688, 697, 702, 705, 731, 741, 769, 778, 805, 810, 811, 816, 820, 830, 832, 837, 851, 892, 902, 911, 936, 943, 960, 966, 972, 974, 983, 985, 988, 994], dtype='int64'), 'group B': Int64Index([ 0, 2, 5, 6, 7, 9, 12, 17, 21, 26, ... 919, 923, 944, 946, 948, 969, 976, 980, 982, 991], dtype='int64', length=190), 'group C': Int64Index([ 1, 4, 10, 15, 16, 18, 19, 23, 27, 28, ... 963, 967, 971, 975, 977, 979, 984, 986, 996, 997], dtype='int64', length=319), 'group D': Int64Index([ 8, 11, 20, 22, 24, 29, 30, 33, 36, 37, ... 965, 970, 973, 978, 981, 989, 992, 993, 998, 999], dtype='int64', length=262), 'group E': Int64Index([ 32, 34, 35, 44, 50, 51, 56, 60, 76, 79, ... 937, 949, 950, 952, 955, 962, 968, 987, 990, 995], dtype='int64', length=140)}
看看上面的输出。Groupby 函数将数据分成子组,现在可以看到每个子组的指数。太好了!但是仅有指数是不够的。我们需要看到每组的真实数据。函数“get_group”对此有所帮助。
race.get_group('group B')
我在这里展示部分结果。原来产量大很多。
找出每组的大小
对“race”对象调用 size 将给出每个组的大小
race.size()
循环每组
您可以在各组之间循环。这里有一个例子:
for name, group in race:
print(name, 'has', group.shape[0], 'data')
多变量分组
在上面所有的例子中,我们只按一个变量分组。但是通过多个变量分组也是可能的。在这里,我按“种族/民族”和“性别”分组。这将返回按性别分类的每个种族的数据数量。
df.groupby(['gender', 'race/ethnicity']).size()
此示例使用“size”聚合数据。还有其他聚合函数。以下是所有聚合函数的列表:
总和()
平均值()
大小()
计数()
标准()
风险值()
扫描电镜()
最小值()
中位数()
请试用它们。只需替换这些集合函数中的任何一个,而不是上面示例中的“size”。
使用多个聚合函数
我们可以在多个变量上使用 groupby,使用多个聚合函数也是可行的。下一个示例将按“种族/民族”分组,并将使用“最大”和“最小”函数进行聚合。
df.groupby('race/ethnicity').agg([np.max, np.min])
这里的聚合函数作用于每一列。因为我们没有指定任何列。
制作每场比赛的数学、阅读和写作的最低和最高分数的数据图表。
要做到这一点,使用前面的代码并像这样添加分数:
df.groupby('race/ethnicity')['math score', 'reading score', 'writing score'].agg([np.max, np.min])
对多个变量进行分组并使用多个聚合函数
为了证明这一点,我们将按“种族/民族”和“性别”进行分组。此外,使用两个聚合函数“min”和“max”。让我们制作一个数据框架,包含按性别划分的每个小组在数学、阅读和写作方面的最高和最低分数。
df.groupby(['race/ethnicity', 'gender'])['math score', 'reading score', 'writing score'].agg([np.max, np.min])
不同列上的不同聚合函数
按“种族/民族”分组,对数学成绩使用最大值和平均值,对阅读成绩使用中值和最小值。
df.groupby('race/ethnicity').agg({'math score': ['max', 'mean'],
'reading score': ['median','min']})
很酷,对吧?但是列名没有那么好和清晰。它们应该更清晰、更好。我们可以像这样更改列名:
math_read = df.groupby('race/ethnicity').agg({'math score': ['max', 'mean'], 'reading score': ['max', 'mean']})
math_read.columns=['Max Math Score', 'Average Math Score', 'Max Reading Score', 'Average Reading Score' ]
完全相同的数据框架,但更有条理。
对名义列使用聚合函数
在上面的所有例子中,我们在数字列上使用了聚合函数。对一些名义列(如“午餐”和“父母教育水平”)应用聚合函数。
df.groupby(['race/ethnicity', 'gender']).agg({'lunch': pd.Series.mode, 'parental level of education': pd.Series.mode, 'math score':np.mean})
正如您在上面的代码中看到的,对于名义数据,语法是不同的。就像提醒模式是出现最多的数据。
在 Groupby 中应用函数
计算一下有多少孩子的“父母教育水平”是高中水平。
df.groupby(df['parental level of education'].apply(lambda x: 'high' in x)).size()
如果您不习惯使用 lambda,请查看这篇文章:
[## 使用 Lambda、Map、Filter 和 Sorted 进行高效的 Python 编程
使用数字、字符串和字典列表的快乐编程
towardsdatascience.com](/efficient-python-programming-with-lambda-map-filter-and-sorted-cfdf536afc36)
将“数学分数”栏分成三个偶数桶,并将其定义为低、中、高分
df.groupby(pd.qcut(x=df['math score'], q=3, labels=['low', 'average', 'high'])).size()
如果你想设置分界点,并定义你的低,平均,高,这也是一个简单的方法。
df.groupby(pd.cut(df['math score'], [0, 40, 70, 100])).size()
使用 Groupby 转换
在数据帧“df”中生成一个新列,并添加一个包含每个数学分数与平均数学分数之差的列。
df['Distance From the Mean'] = df.groupby(['race/ethnicity', 'gender'])['math score'].transform(lambda x: x - x.mean())
看上面的数据框。末尾有一个名为“与平均值的距离”的新列。
使用 Groupby 筛选
您可以根据特定条件筛选出数据,使数据更有意义。筛选数据少于 100 行的组或种族。
df_n = df.groupby('race/ethnicity').filter(lambda x: len(x) > 100)
上面的代码说保留长度超过 100 的组。检查原始数据帧“df”和过滤数据帧“df_n”的长度。
print(len(df))
print(len(df_n))
原始数据帧的长度为 1000,应用过滤器后,数据帧的长度变为 911。
地图
绘制每组的平均阅读分数,并生成一个新列。
df['New'] = df['race/ethnicity'].map(df.groupby(['race/ethnicity'])['reading score'].mean())
看看这个数据框。最后有一个名为“新”的新列,包含相应组的平均阅读分数。
使用 Groupby 进行可视化
众所周知,一图胜千言。下面是 groupby 中可视化技术的一些演示。做一个父母教育水平的柱状图。
import matplotlib.pyplot as plt
plt.clf()
df.groupby('parental level of education').size().plot(kind='bar')
请尝试制作一个饼图。你只需要在上面的代码中使用’ pie ‘而不是’ bar’。如果你没有传递任何’种类’,情节将是一个简单的线情节。让我们画出每组的平均数学分数。
df.groupby('race/ethnicity')['math score'].mean().plot()
结论
在本文中,您学习了以多种不同的方式对数据进行分组和汇总。您学习了使用 groupby 使用聚合函数、数据转换、过滤、映射和可视化。
我在这个视频中也解释了同样的内容:
欢迎在推特上关注我,喜欢我的 T2 脸书页面。
更多阅读:
完整的可视化课程
towardsdatascience.com](/your-everyday-cheatsheet-for-pythons-matplotlib-c03345ca390d) [## 使用 Python 的 Scikit-Learn 库的完整推荐系统算法:分步指南
一个简单、容易、有用的算法,只有几行代码
towardsdatascience.com](/a-complete-recommendation-system-algorithm-using-pythons-scikit-learn-library-step-by-step-guide-9d563c4db6b2) [## 数据科学家使用 Python 进行假设检验的完整指南
用样本研究问题、解决步骤和完整代码清楚地解释
towardsdatascience.com](/a-complete-guide-to-hypothesis-testing-for-data-scientists-using-python-69f670e6779e) [## 编程、软件工程和数据科学的最佳免费课程
麻省理工、哈佛和斯坦福等顶尖大学的免费课程
towardsdatascience.com](/best-free-courses-for-computer-science-software-engineering-and-data-science-50cd88cafd74) [## Python 中从头开始的完整异常检测算法:分步指南
基于概率的异常检测算法
towardsdatascience.com](/a-complete-anomaly-detection-algorithm-from-scratch-in-python-step-by-step-guide-e1daf870336e) [## 如何在 Python 中呈现多个变量之间的关系
了解如何使用 Python 中的多元图表和绘图来呈现要素之间的关系
towardsdatascience.com](/how-to-present-the-relationships-amongst-multiple-variables-in-python-70f1b5693f5)
使用 React 实现高效的数据可视化
如何高效处理 amCharts 图表
在 Unsplash 上由 Carlos Muza 拍摄的照片
amCharts 是一个用于数据可视化的 JavaScript 库,内置了对 TypeScript 和 ES6 模块的支持。也完全兼容角度、反应、 Vue.js 。amCharts 提供了多种图表类型供选择,如这里的所示。
我已经使用 amCharts 几个月了,现在我知道它的主要优点和缺点。当用于静态图表时,简直令人惊叹。动态图表呢?
初始化 amCharts 图表需要大量的时间和资源。想象一下,每当我们需要改变可视化的数据时,例如,作为事件的结果,都要这样做。这很容易成为 web 应用程序的瓶颈,尤其是在同一个页面上有很多图表的情况下。这就是记忆发挥作用的地方!
请注意,本文的主要目的不是展示 amCharts 是如何工作的,或者什么是记忆化。这就是为什么我强烈推荐阅读 这个 和 这个 第一个。
让我们构建一个高效的 React 组件来包装任何类型的 amCharts 图表。
掌握 React 组件组成
codeburst.io](https://codeburst.io/a-complete-guide-to-props-children-in-react-c315fab74e7c)
构建图表组件
我们的通用组件可以定义如下:
[am4core.create](https://www.amcharts.com/docs/v4/reference/instance_module/#create_method)
函数返回图表实例,这允许我们操纵图表本身的行为。图表的创建,即它的初始化,应该只在组件第一次呈现时执行**。否则,这个组件会变得极其低效。这就是为什么我们将初始化函数包装在一个 useMemo 回调中。这样,我们就记住了返回 char 实例的函数,只有在第一次调用组件时才会调用它。每次组件重新呈现时,都会使用chart
缓存值,避免初始化开销。**
如果我们希望图表可以被操作,而不是每次都重新创建,我们需要公开它对父组件的引用。为此,我们添加了onInitialization
道具。有了它,父级可以直接在它的实例上改变图表的外观、数据和行为。
这种方法的主要优势有两个:
- 通过为每个图表类型创建一个组件来避免代码重复
- 允许父组件随时随地与图表直接交互
运行中的图表组件
假设我们想要创建一个时态演变折线图,就像这样:
用 amCharts 制作的时间演变线图
要可视化的数据将从 AJAX 调用中检索,其结果取决于用户选择的时间跨度。首先,我们来定义一下TemporalEvolutionChart
组件:
父组件负责调用检索要在图表中显示的数据,并将其传递给它。这是通过将 AJAX 调用的结果赋给图表实例的[data](https://www.amcharts.com/docs/v4/concepts/data/)
属性来实现的。 amCharts 图表会自动呈现新的数据。
瞧啊。一旦用户更改了感兴趣的时间跨度,就会发出一个 AJAX 调用(这要感谢这里引入的 API 定义层),图表也会相应地更新。这是一种非常有效的方式,不需要每次都初始化图表。
结论
在本文中,我们展示了一种使用 amCharts 和 React 的有效方法。初始化 amCharts 图表是一项耗时的操作,应仅在绝对必要时执行。这种方法是一个很好的例子,说明了如何节省资源,并防止用户因非常慢的网页而感到沮丧。感谢阅读!我希望这篇文章对你有所帮助。如有任何问题、意见或建议,请随时联系我。
熊猫中有效的欧几里德距离计算
马库斯·斯皮斯克在 Unsplash 上拍摄的照片
-一个巧妙的小把戏
在数据科学中,我们经常会遇到地理问题,比如经典的房价预测问题。在大多数情况下,作为特性工程的一部分,使用k-nearest neighborhood(k-NN)或类似策略来计算基于位置的参考价格不会有什么坏处。
k-NN 中最重要的超参数是距离度量,欧几里德距离是地理空间问题的明显选择。也称为“直线”距离或 L 范数,使用以下公式计算:
使用 k-NN 进行特征训练的问题在于,理论上,它是一个 O (n)运算:每个数据点都需要将每隔一个数据点视为潜在的最近邻。在没有像空间索引这样的专门技术的情况下,我们可以通过一些矢量化来加快速度。
让我们从一组地理空间数据点开始:
我们通常不直接从latitude
和longitude
计算欧氏距离。相反,它们被投影到地理上适当的坐标系中,其中x
和y
共享相同的单位。我将在以后的帖子中详细阐述这一点,但请注意
在地球上的大多数地方,东北纬度与经度不同。离赤道越远,这种差异就越大。
非矢量化欧几里得距离计算如下所示:
在上面的例子中,我们计算相对于第一个数据点的欧几里得距离。因为我们使用了pandas.Series.apply
,所以我们循环了data['xy']
中的每个元素。如果我们对每个数据点重复这个过程,函数euclidean
将被连续调用 n 次。我们可以通过矢量化来提高效率。
计算现在是矢量化的。但是它的可读性较差,并且有许多中间变量。有没有更干净的方法?
事实证明,高效的欧几里德距离计算的诀窍在于一个不起眼的数字函数:numpy.absolute
。
Python 的一个经常被忽视的特性是复数是内置的原语。我们可以将它们转换成复数,而不是将xy
表示为二元元组。
注意数据类型已经从object
变成了complex128
。
除非你是受过纯数学训练的人,否则你可能直到现在才意识到(像我一样)复数可以有绝对值,并且绝对值对应于离原点的欧几里得距离。应用这些知识,我们可以将代码简化为:
还有最后一个问题:如果您需要持久化您的表,复数并不容易序列化。幸运的是,把一个复数分解回它的实部和虚部并不太难。
希望你会发现这是有用的。
Pandas 数据帧上条件逻辑的高效实现
数据科学,编程,熊猫,效率
是时候停止过于依赖。iterrows()和。应用()
Python 可以说是目前最酷的编程语言(感谢机器学习和数据科学),但与最好的编程语言之一 c 相比,它的效率并不太为人所知。条件逻辑就是一个例子。当开发机器学习模型时,很常见的是,我们需要根据从统计分析或前一次迭代的结果中得出的硬编码规则,以编程方式更新标签。承认这一点并不丢人:我一直用 Pandas apply 编写代码,直到有一天我厌倦了嵌套块,于是我决定研究(也就是谷歌)替代的、更易维护和更有效的方法(也就是其他方法)
演示数据集
我们将要使用的数据集是虹膜数据集,您可以通过 pandas 或 seaborn 免费获得。
虹膜数据集的前 5 行
150 排,听起来有点业余…
150,000 行,现在我们更认真了
假设在我们的初始分析之后,我们想要用以下逻辑来标记数据集:
- 如果萼片长度< 5.1, then label 0;
- otherwise if sepal width > 3.3,萼片长度< 5.8, then label 1;
- otherwise if sepal width > 3.3,花瓣长度> 5.1,则标注 2;
- 否则,如果萼片宽度> 3.3,花瓣长度< 1.6 and either sepal length < 6.4 or petal width < 1.3, then label 3;
- otherwise if sepal width > 3.3 且任一萼片长度< 6.4 or petal width < 1.3, then label 4;
- otherwise if sepal width > 3.3,则标记为 5;
- 否则标记为 6
在深入研究代码之前,让我们快速地将新的标签列设置为 None:
iris['label'] = None
熊猫。iterrows() +嵌套的 If-Else 块
如果你还在用这个,这篇博文绝对是适合你的地方!
1min 29s ± 8.91 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
还有跑的时间…好了,我们继续…
熊猫。应用()
Pandas [.apply()](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.apply.html)
直接用于沿数据帧或的轴对序列的值应用函数**。例如,如果我们有一个函数 f,它对一个可迭代的数求和(即可以是一个list
、np.array
、tuple
等等)。),并将其传递给如下所示的数据帧,我们将对一行求和:**
def f(numbers):
return sum(numbers)df['Row Subtotal'] = df.apply(f, axis=1)
在上面的代码片段中,axis=1
表示应用函数的方向。.apply()
会默认有axis=0
,即逐列应用该功能;而axis=1
将逐行应用该函数。
现在我们对熊猫.apply()
有了一个基本的了解,让我们编写分配标签的逻辑代码,看看它会运行多长时间:
1.43 s ± 115 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
对于 150,000 行来说,1.43 秒是一个非常大的进步,但仍然非常慢。想象一下,如果您需要处理一个包含数百万交易数据或信用审批的数据集,每次我们想要应用一组规则并将设计好的功能分配给一个列时,将会花费超过 14 秒的时间。运行足够多的专栏,你就可以在下午剩下的时间里放松下来。
熊猫。loc[]索引
如果你熟悉 SQL,用.loc[]
给一个新列赋值实际上只是一个带有大量WHERE
条件的UPDATE
语句。因此,这肯定比对每一行或每一列应用一个函数要好得多。
13.3 ms ± 837 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
现在我们花的时间只有以前的十分之一,这意味着当你在家工作时,你有更少的借口离开办公桌开始网飞。然而,我们现在只使用内置的熊猫功能。虽然 pandas 为我们提供了一个非常方便的高级接口来与数据表交互,但效率可能会因为抽象层而降低。
Numpy。哪里()
Numpy 有一个低级接口,允许与 n 维可迭代对象(即向量、矩阵、张量等)进行更有效的交互。它的方法通常是基于 C 语言的,当涉及到更复杂的计算时,它使用优化的算法,这使得它比我们重新发明的轮子要快得多。根据 numpy 的官方文档,np.where()
接受以下语法:
**np.where**(*condition, return value if True, return value if False*)
本质上,这是一个二分逻辑,其中条件将被评估为布尔值,并相应地返回值。这里的诀窍是条件实际上可以是可迭代的(即布尔 ndarray 类型)。这意味着我们完全可以将df['feature'] == 1
作为条件传入,并将 where 逻辑编码为:
**np.where**(*df['feature'] == 1**,
'It is one',
'It is not one'* )
所以你可能会问,我们如何用一个像np.where()
这样的二分法函数来实现我们上面陈述的逻辑呢?答案很简单,却令人不安。嵌套np.where()
……(剧透:这个片段可能会触发)
3.6 ms ± 149 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
恭喜,你挺过来了。我不能告诉你我花了多少时间来计算右括号,但是嘿,这就完成了任务!我们又砍掉了熊猫的 10ms。然而,这个片段是不可维护的,这意味着,它是不可接受的。
Numpy。选择()
Numpy .select()
,与.where()
不同的是,该函数旨在实现多通道逻辑。
**np.select****(***condlist***,** *choicelist***,** *default=0***)**
它使用与np.where()
相似的语法,除了第一个参数现在是一个条件列表,它应该与选项具有相同的长度。使用np.select()
时要记住的一件事是,一旦满足第一个条件,就会选择一个 选项。这意味着,如果列表中某个超集规则位于某个子集规则之前,则该子集选项将永远不会被选中。具体来说:
condlist = [
df['A'] <= 1,
df['A'] < 1
]choicelist = ['<=1', '<1']selection = np.select(condlist, choicelist, default='>1')
因为命中df['A'] < 1
的所有行也将被df['A'] <= 1
捕获,所以没有行将被标记为'<1'
。为了避免这种情况发生,请确保在制定更具体的规则之前先制定一个不太具体的规则:
condlist = [
df['A'] < 1, # < ───┬ swapped here
df['A'] <= 1 # < ───┘
]choicelist = ['<1', '<=1'] # remember to update this as well!!selection = np.select(condlist, choicelist, default='>1')
正如你从上面看到的,你将需要更新condlist
和choicelsit
来确保代码运行顺畅。但是说真的,当网飞触手可及的时候,谁有时间做这些呢?好吧,我抓住你了,伙计!通过将其更改为字典,我们将获得大致相同的时间和内存复杂度,但代码片段更易于维护:
6.29 ms ± 475 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
大约是嵌套的np.where()
的两倍,但是这不仅将你从翻表括号调试中拯救出来,而且还改变了心不在焉的choicelist
。我将是第一个咬紧牙关的人,我已经忘记更新choicelist
太多次了,我已经花了四倍多的时间来调试我的机器学习模型。相信我,np.select()
跟dict
。值得!
荣誉提名
- Numpy 的矢量化运算:如果您的代码涉及循环和评估一元函数、二元函数或对数字序列进行运算的函数。您肯定应该通过将数据转换成 numpy ndarray 来重构代码,并充分利用 numpy 的矢量化运算来极大地提高脚本的速度。结论:这是一个情境赢家!在 Numpy 的官方文档中查看 此处 的一元函数、二元函数或运算数字序列的函数示例!
**np.vectorize**
:不要被这个函数的名字所迷惑。这只是一个方便的函数,实际上并没有让代码运行得更快。要使用这个函数,您需要首先将您的逻辑编码成一个可调用的函数,然后运行np.vectorize(your_function)(your_data_series)
。另一个大的缺点是,为了传递给“矢量化”函数,您需要将数据帧转换成一维的可迭代对象。结论:如果不方便用**np.vectorize**
,想都别想。**numba.njit**
:这才是真正的业务,真正的向量化。当谈到优化循环时,没有超级天才的 hacky 移动,你根本无法轻松击败 numba。它试图将任何 numpy 评估移动到尽可能接近 C 以提高效率。虽然它可以加快数字计算的速度,但它也将自己限制在数字计算上,这意味着没有 pandas 系列,没有字符串索引,只有 numpy 的 ndarray,类型有int
、float
、datetime
、bool
和category
。结论:如果你习惯于只使用 Numpy 的 ndarray,并且把你的逻辑转换成数值计算,那么你就是一个真正的冠军。从 这里了解更多 。
你可能也喜欢这个
如果你对其他 Python 技巧感兴趣,我为你整理了一份简短博客列表:
如果你想了解更多关于 Python、数据科学或机器学习的知识,你可能想看看这些帖子:
如果你想了解更多关于如何将机器学习应用于交易和投资的信息,这里有一些你可能感兴趣的帖子:
在你走之前…
如果可能的话,去找numba.njit
;否则,np.select
带着dict
会帮你远航,我的朋友。记住,每一点点的改进都是有帮助的!如果你从这里学到了新的东西,请告诉我!也请让我知道我是否还遗漏了其他一些巧妙的技巧!
再见!
[## Louis Chan—FTI Consulting | LinkedIn 数据科学总监
雄心勃勃的,好奇的和有创造力的个人,对分支知识和知识之间的相互联系有强烈的信念
www.linkedin.com](https://www.linkedin.com/in/louis-chan-b55b9287)
深度学习中的高效推理——问题在哪里?
卷积神经网络可以为许多计算机视觉任务(如图像分类、分割和检测)提供卓越的解决方案。众所周知,神经网络模型的大小与其准确性相关。随着模型大小的增加,精确度也会增加。这可以从表 1 中看出,表 1 显示了几种已知模型在 ImageNet 分类基准测试中的最高精度:
表 ImageNet 上不同分类模型的比较。
众所周知的摩尔定律表明,硬件容量每 1.8 到 2 年翻一番。此外,根据摩尔第二定律,半导体芯片制造厂的成本每 4 年翻一番。另一方面,在 2012 年推出 AlexNet 和 2018 年推出 Alpha-Go 之间的几年里,大型 AI 系统所需的计算量每 3 到 4 个月就会翻一番!(如下图所示)。这个突出的数字意味着深度网络的加速将取决于开发轻量级和算法高效模型的能力。因此,深度学习的软件效率对于推理产生系统将是至关重要的。
图 1:按年份和所需千万亿次浮点运算量(用于训练)划分的神经网络模型。
大多数实际应用的目标是以尽可能短的运行时间获得最高的精度。这在高效深度学习领域提出了几个有趣的问题。在本帖中,我们将解决以下问题:
- 网络架构如何影响运行时间?
- 我们如何在不降低模型准确性的情况下减少运行时间?
在本文的其余部分,我们将回答这两个基本问题。
架构与推理运行时
如表 1 所示,模型越大,越精确。为了找到运行时间最短、最精确的架构,我们需要了解三个因素之间的权衡:
- 浮点运算(FLOPs)
- 运行时间
- 准确(性)
下面的数字表明,虽然这三者之间有明显的正相关,但它们并不完全相关。这可以通过图的厚度看出,图中显示,对于运行时间(或 FLOPs 数量)相同的两个网络,精度可能会有显著不同。
图 ImageNet 上运算次数(千兆次)与最高精度的对比
图 3:以毫秒为单位的网络运行时间与 ImageNet 上最高精度的对比。
为了理解 FLOPs 和运行时之间的差距,需要考虑几个参数,例如框架、硬件、架构等等。让我们看一个例子,解释为什么 FLOPs 没有一对一的精度函数。在这个例子中,我们考虑深度可分卷积(DSC),它是由 Howard et al. 引入的,用于减少深度网络的参数数量。假设我们想要应用大小为 K×K×D 的卷积核,使用 DSC,我们将滤波器分成两个独立的核:对不同通道上的相同条目进行操作的深度核,以及对同一通道中的不同条目进行操作的点状核。例如,如果我们在具有 64 个输入通道的 112 x 112 特征图上进行 3 x 3 深度方向卷积,我们执行 3 x 3 x 64 x 112 x 112 = 7,225,344 次 FLOPs。然后,我们应用可分离分量,它是使用 1 x 1 核的常规卷积。对于该操作,我们取先前的 112×112×64 大小的卷积输出,并将其投影到 128 维的特征图中;这需要 64 x 112 x 112 x 128 = 102,760,448。将两个卷积的运算次数相加得到 109,985,792 次浮点运算。相比之下,相同大小的常规 3 x 3 卷积将需要 924,844,032 次浮点运算,这几乎是 9 倍的运算量!请注意,这两个模型的表达能力是不同的。然而,使用这个想法,的原始论文,用少得多的网络参数显示了相当的准确性。这个例子清楚地展示了如何用少得多的触发器实现相当的精度。但是,手术次数只是故事的一部分。内存带宽是第二部分——但我们将在另一篇文章中讨论。
现在我们来处理第二个问题。
在保持准确性的同时减少运行时间
从模型的角度来看,在深度神经网络中建立了几个启发式算法来减少推理的运行时间。让我们简单解释一下其中的几个:
量化 —在给定模型的情况下,量化的目标是将权重映射到更小的低精度量化级别集合中。级数反映了精度,并大致反映了存储数据所需的位数(通过对级数应用 log)。在量化过程中,我们希望一方面保持模型的准确性,另一方面将模型量化到尽可能少的级别。量化的影响是双重的。首先,它允许我们用更少的比特,即更少的内存来存储模型。第二,它帮助我们用便宜得多的手术取代昂贵的手术。例如,用半精度(16 位)浮点运算替换双精度(64 位)浮点运算。这反过来使我们能够减少给定网络的推理时间。量化的好处各不相同,取决于数据、量化精度、硬件等。
修剪— 现代架构通常过度参数化。然而,这使得大量的权重在推理时是多余的。修剪指的是将这些冗余权重设置为零的过程。与量化类似,修剪的最终目标是将尽可能多的权重设置为零,同时保持准确性。剪枝后,需要对模型进行微调。虽然在某些情况下,修剪可以保持准确性,但它不能用于轻松地减少等待时间,因为权重修剪会导致权重的零星擦除,从而难以利用该结构来实现更快的矩阵乘法。此外,如果通道被修剪(即,使用通道修剪),模型精度通常会降低。
知识升华— 这个术语指的是将复杂模型(老师)学到的知识转移到更简单模型(学生)的过程。最终,学生网络可以达到如果直接在相同的数据上训练则无法达到的准确度。因此,用更简单的模型也能达到同样的精度。这通常表现为较短的运行时间。虽然这个想法听起来很吸引人,但是最近的研究表明在很多情况下这是不切实际的。
激活统计— 在现代架构中,ReLU 是使用最广泛的激活函数之一。这种激活具有独特的稀疏特性,可以用来提高效率。例如,AlexNet 的特征地图的稀疏度在 19%到 63%之间。给定这样的稀疏特征映射,可以使用更有效的实现来减少存储器访问和 FLOPs。虽然这种解决方案可以保持准确性,但其运行时间优势可能会因数据集和网络架构而恶化。
神经架构搜索— 近年来,已经进行了几次自动搜索有效架构的尝试,以减少运行时间并保持准确性。这通常是通过在所有可能的架构空间或其子集内进行启发式搜索来完成的。NASNet、EfficientNet 和 AmoebaNet 就是这种类型的一些架构。这种解决方案的主要问题是找到这样一种架构需要大量的计算。通常,这种方法需要数千小时的 GPU 来寻找最佳架构。正因如此,对于大多数深度学习从业者来说是不适用的。
结论
近年来,深度学习效率的想法越来越受欢迎。提高深度学习芯片的硬件容量有几种方法;然而,这些要么是昂贵的,要么不能超过对计算资源的需求。这一基本问题引发了对算法解决方案的需求,这些解决方案将提高与深度网络推理相关的运行时间。在这篇文章中,我们看到了一些与设计高效深度学习算法相关的解决方案和挑战。在这个广泛的研究领域,所有的方法都有利弊。因此,在不牺牲准确性的情况下加速推理是一项极具挑战性的任务。
本帖最初发表于-https://deci . ai/efficient-inference-in-deep-learning-where-the-problem/
Vespa.ai 上高效的开放领域问答
开放领域问答已经成为衡量系统阅读、表现和检索一般知识能力的基准。基于检索的问答系统需要连接各种系统和服务,如 BM25 文本搜索、向量相似性搜索、NLP 模型服务、标记器和中间件,以将所有这些结合在一起。其中大部分是 Vespa.ai 的核心功能。在本帖中,我们在 Vespa.ai 上的一个可扩展的生产就绪应用程序中重现了基于检索的问答系统的最先进基线。
由莱斯特·索尔巴肯和乔·克里斯蒂安·贝古姆拍摄。
乔恩·泰森在 Unsplash 上的照片
介绍
科学进步的一些最有效的驱动力是基准。基准测试提供了一个共同的目标,一个目的,来提高每个人都可以使用的数据集的艺术水平。排行榜额外增加了竞争动机,提供了在同行中脱颖而出的机会。竞赛增加了激励研究人员实际完成工作的截止日期,而不是无休止地修补相关指标。
在机器学习领域,基准测试对于刺激创新和进步尤为重要。一项新的竞赛 NeurIPS 2020 高效开放域问答挑战赛,旨在推动问答系统的发展。这里的目标是开发一个能够回答问题而没有任何话题限制的系统。随着自然语言处理领域的最新进展,这一领域已经成为衡量系统阅读、表示和检索一般知识能力的基准。
当前基于检索的最新技术是密集段落检索系统,如开放领域问答论文密集段落检索中所述。它由一组主要为研究开发的 python 脚本、工具和模型组成。在这样一个系统中有许多部分。其中包括两个基于 BERT 的模型,用于将文本编码为嵌入向量,另一个基于 BERT 的模型,用于提取答案,近似最近邻相似性搜索和基于文本的 BM25 方法,用于检索候选项、分词器等。将这样的系统投入生产并不容易。我们认为将这些不同的部分整合起来,并演示如何用 Vespa.ai 构建一个开放域问答服务系统,以达到最先进的准确性,这将是非常有趣的。
如果你不熟悉, Vespa.ai 是一个针对大型数据集的低延迟计算引擎。它存储和索引您的数据,以便在服务时可以高效地执行对数据的查询、选择和处理。Vespa.ai 用于各种任务,如搜索、个性化、推荐、广告、甚至寻找爱情,以及许多其他需要在查询时进行计算的应用。在任何给定时间,它每秒钟在全球范围内处理数十万个查询。
问答系统的大多数组件都是 Vespa 的核心功能。不久前,我们改进了 Vespa 对基于术语的检索和排名的文本搜索支持。我们最近添加了高效的近似最近邻,用于语义、密集向量召回。对于混合检索,Vespa 支持许多类型的机器学习模型,例如神经网络和决策森林。我们还改进了对 TensorFlow 和 PyTorch 模型的支持,以运行更大的 NLP 和 Transformer 模型。
这很有趣,因为虽然这在研究环境中有明显的好处,但这种系统的真正价值在于它们在应用中的最终用途。Vespa 被设计为高性能和可扩展的生产就绪系统。因此,它提供了在生产中部署的简化途径,而无需处理维护许多不同子系统的复杂性。这使得 Vespa 成为一个有吸引力的包装。
在这篇博文中,我们将谈到
- 语义密集向量检索的快速近似最近邻。
- 稀疏向量检索的基于术语(BM25)检索。
- 在 Vespa 中导入多个预训练的基于 BERT 的模型,用于编码嵌入向量和提取答案。
- 标记化和其他事情的定制逻辑。
有关更多详细信息,请参考配套示例应用。
这篇文章的目标是为自然问题基准重新创建密集段落检索(DPR)论文结果。我们将首先对基于检索的问答系统如何在本文的上下文中工作进行高级概述。然后,我们将展示如何在没有任何外部服务或插件的情况下在 Vespa 上实现这一切,并重现该论文的最先进结果,该结果由给定一组问题的答案的精确匹配来衡量。我们将以对未来的展望和本系列的下一篇文章结束。
背景:基于检索的问答系统的剖析
自然问题基准由自然语言问题和答案组成。如何检索和表示回答问题所需的知识取决于每个系统。有两种主要的方法:检索和参数化。基于检索的系统使用搜索词或语义表示向量来回忆一组句子或段落,随后用机器学习模型进行评估以提取精确答案。参数系统将答案或多或少地直接存储在大型神经网络模型的权重中。也有对混合检索和参数化系统的研究,如检索增强生成系统,它最近从整体上提高了自然问题基准的艺术水平。我们将在这里关注基于检索的系统,但是将在以后的博客文章中探索参数化和混合的方法。
基于检索的问答系统通常将其“知识”存储在信息检索系统中。这可以是句子、段落或整个文档。这里我们将使用维基百科数据集,其中每一页被分成 100 个单词的段落。该数据集包含 2100 万个这样的段落。回答问题时,我们首先检索最有可能包含答案的段落。然后用机器学习模型对它们进行分析,以提取最有可能导致正确反应的跨度。这些阶段分别被称为“检索者”和“读者”。
从短文中提取答案。(图片作者)
寻回犬
检索器负责生成一组候选段落。由于后续阅读器组件的评估成本很高,因此拥有有效的检索机制至关重要。有两种主要的段落检索方法:基于术语的(稀疏的)如 BM25,和嵌入的(密集的)向量,每一种都有它们的优点和缺点。
基于术语的(稀疏)检索
基于术语的检索是经典的信息检索方法,涵盖了 TF-IDF 和 BM25 等算法。从概念上讲,文本由一个向量表示,其中每个维度代表词汇表中的一个术语。非零值表示它的存在。由于每个文本只包含词汇表中可能术语的子集,这些向量很大而且很稀疏。对于 TF-IDF 或 BM25,可以通过稀疏向量之间的点积来计算两个文本(例如,文档和查询)之间的相似性,其中对稀疏向量稍作修改(例如,术语重要性)。基于术语的方法依赖于倒排索引结构来进行有效的检索。在某些情况下,这可以通过 WAND 等算法进一步加速。
除了词汇化、词干提取和可能的停用词删除等预处理之外,术语与文本中找到的完全匹配。这可能是优点,也可能是缺点。对于非常突出的术语,例如姓名和地点,这大大缩小了搜索空间。然而,不包含确切术语的潜在相关文档将不会被检索到,除非使用查询扩展或相关技术。密集段落检索(DPR)论文使用 ElasticSearch 作为 BM25 的提供系统。
基于嵌入(密集)的检索
词汇表中潜在术语的数量可能非常巨大。嵌入向量背后的基本思想是将这个非常高维的稀疏向量压缩成一个非常小的密集向量,其中大多数维度包含非零值。这具有将查询或文档向量投射到低维空间的效果。可以这样做,使得几何上接近的向量在语义上也是接近的。DPR 的论文使用了两种 BERT 模型来编码文本:一种用于编码查询,另一种用于编码文档。在双塔配置中同时训练这两个模型,以最大化可能回答问题的段落的点积。
与稀疏表示相反,没有精确的方法来有效地找到最近的邻居。因此,我们在所谓的近似最近邻(ANN)中用准确性换取效率。已经提出了许多不同的人工神经网络搜索方法。有些与倒排索引结构兼容,因此它们可以很容易地在现有的信息检索系统中实现。例如 k-means 聚类、乘积量化(及其相关)和位置敏感散列,其中质心或桶可以被索引。与倒排索引不兼容的一种方法是 HNSW (分层可导航小世界)。HNSW 基于图结构,非常高效,并且有一个吸引人的特性,即图可以在运行时增量构建。这与大多数其他需要离线、面向批量索引构建的方法形成对比。
基于语义嵌入的检索很好地补充了基于术语的检索。语义相似的文档可以被召回,即使它们不包含完全相同的术语。与基于术语的检索的单词包方法不同,词序可以提供额外的上下文。然而,从历史上看,在问答问题上,基于术语的检索优于语义嵌入,但 DPR 的论文表明,如果编码经过专门训练,密集检索可以得到极大改善。DPR 的论文使用带有 HNSW 索引的 FAISS 进行相似性搜索。
朗读者
虽然检索器组件的工作是产生一组候选段落,这些段落有希望包含问题的答案,但是阅读器提取这些段落的实际答案。这需要某种形式的自然语言理解模型,使用的就是 BERT(或其他 Transformer)模型。这些模型通常很大,评估起来很昂贵,所以只有少量的候选段落通过它们。
转换器模型将令牌序列作为输入。文本的标记化可以通过几种不同的方式来平衡词汇大小和序列长度。由于 BERT 模型的完全注意机制,评估时间随着序列长度的增加而二次增加。因此,必须找到一个合理的平衡,基于 BERT 的模型使用一个单词块或类似的算法将不常用的单词分成子单词。
读者模型的输入是表示问题、文档标题和段落本身的标记的串联。该模型查找每个标记的嵌入表示,并通过一系列层,为每个标记产生新的表示。这些表示可以用于不同的任务。对于问答,添加了一个附加层来计算三个必要的输出:文章与问题的相关性,以及答案的开始和结束索引。
回答问题的伯特(图片作者
为了提取最终答案,使用产生最大相关性分数的段落。该模型的另外两个输出是每个标记成为开始标记和结束标记的概率。通过找到开始概率和结束概率之和最大的区间来选择最终答案。这会产生一个标记序列,在返回之前,标记器必须将其转换为单词。DPR 论文使用基于 BERT 的模型来输出跨度预测。
把这些放在一起
如上所述的基于检索的问答系统,能够进行基于术语和基于嵌入的检索,至少需要以下组件:
- BM25 信息检索系统存储了 2100 万条维基百科文本。
- 存储段落嵌入向量的高效向量相似性搜索系统。
- 一个模型服务系统,用于三种不同的基于 BERT 的模型:查询编码器、文档编码器和读者模型。
- 一个基于 BERT 的标记器。
- 中间件将这一切粘合在一起。
记号赋予器为文本生成记号序列。这些令牌被存储以供在读取器模型中使用。它们还被用在基于 BERT 的文档编码器模型中,以创建用于密集检索的嵌入向量。为了快速检索,需要对文本和嵌入向量进行索引。
每个查询都遵循类似的过程。记号赋予器产生一个记号序列,用来在基于查询 BERT 的编码器中产生一个嵌入向量。第一阶段,检索,是使用基于术语的检索或基于嵌入的检索来完成的。前 N 篇文章被传递给读者模型并相应地排序。分析最佳段落以提取包含答案的最佳跨度。
这是实现问答系统需要设置的一系列服务。在下一节中,我们将展示如何在一个 Vespa 应用中实现所有这些功能。
在 Vespa 上复制基线
上面提到的大部分组件都成为了 Vespa 中的核心功能。在下文中,我们将介绍如何在 Vespa 中进行设置。详情可参见配套示例应用。
(计划或理论的)纲要
使用 Vespa 创建应用程序时,通常从文档模式开始。该模式包含每个文档应该存储哪些数据的定义。在我们的例子中,每个文档都是维基百科数据集中的一段。因此,我们建立了一个模式,允许我们已经讨论过的不同检索方法:
- 稀疏检索使用传统的 BM25 基于术语的检索。
- 密集检索使用经过训练的模型编码的矢量表示。
- 混合检索使用上述的组合。
我们在一个文档模式中设置了所有这些:
schema wiki {
document wiki { field title type string {
indexing: summary | index
} field text type string {
indexing: summary | index
} field title_token_ids type tensor(d0[256]) {
indexing: summary | attribute
} field text_token_ids type tensor(d0[256]) {
indexing: summary | attribute
} field text_embedding type tensor(x[769]) {
indexing: attribute | index
attribute {
distance-metric: euclidean
}
}
}
}
这里,每个段落的标题和文本内容都由一个字符串和一个令牌序列字段表示。字符串字段被索引以支持 BM25 并支持 WAND 以加速检索。令牌序列被表示为张量,并被用作阅读器模型的输入。
标题和文本的嵌入向量被预先计算并存储为张量。该向量用于密集检索,因此我们在该字段上启用 HNSW 索引用于近似最近邻匹配。Vespa 一个很好看的特点就是 HNSW 索引不是离线预建的;它是随着数据被索引而在线构建的。这使得应用程序能够对输入系统的新数据做出更快的响应。
检索和排序
其中,Vespa 中的查询定义了 Vespa 应该如何召回文档(称为匹配阶段)以及 Vespa 应该如何对文档进行评分(称为排序阶段)。Vespa 提供了丰富的查询 API ,其中查询是用Vespa YQL 语言指定的。由于不同的检索策略(基于术语的和基于嵌入的)具有不同的查询语法,我们构建了一个定制的搜索器组件,它允许我们构建一个统一的搜索界面,并且只将实际的问题和检索策略作为参数传递。这简化了方法之间的比较。
检索策略在回忆内容和评分方式上都有所不同。在 Vespa 中,使用在文档模式中配置的等级表达式来表示得分。Vespa 支持多阶段排名,我们在这里利用了这一点,因此第一阶段代表检索者,第二阶段代表读者。我们设置了第一阶段的等级配置文件,如下所示:
rank-profile sparse inherits openqa {
first-phase {
expression: bm25(text) + bm25(title)
}
}
rank-profile dense inherits openqa {
first-phase {
expression: closeness(field, text_embedding)
}
}
rank-profile hybrid inherits openqa {
first-phase {
expression: 1000*closeness(field, text_embedding) + bm25(text) + bm25(title)
}
}
这里,我们设置了三个排序配置文件,一个用于稀疏检索,一个用于密集检索,一个用于混合检索。稀疏排名简档使用针对查询的标题和文本字段的 BM25 分数作为评分函数。密集简档使用接近度,例如查询和文档嵌入字段之间的欧几里德距离。混合概要文件是一个将两者结合起来进行混合检索的例子。第一阶段代表取回者。
对于读者模型,我们设置了基本配置文件 openqa ,它引入了检索策略之间的第二个共同阶段:
onnx-model reader {
file: files/reader.onnx
input input_ids: input_ids
input attention_mask: attention_mask
output output_0: start_logits
output output_1: end_logits
output output_2: relevance_logits
}rank-profile openqa {
second-phase {
rerank-count: 10
expression: onnxModel(reader).relevance_logits
}
summary-features {
onnxModel(reader).start_logits
onnxModel(reader).end_logits
}
}
这里,前 10 个文档由 reader 模型重新排序,这是 onnxModel rank 特性定义的。我们使用的实际模型是 DPR 团队在 HuggingFace 的模型库上发布的预训练模型。HuggingFace 发布了一款优秀的变压器模型导出,可以轻松将变压器模型(从 PyTorch 或 TensorFlow)导出为 ONNX 格式。在将模型导出到 ONNX 之后,我们可以将模型文件放在应用程序包中,并配置它在文档模式中的使用,如上所示。为了达到这个规模,Vespa 将这个模型分发到集群中的所有内容节点。因此,在排序期间,在内容节点上评估模型,避免将数据传输到外部模型服务。
读者模型有三个输出,其中 relevance_logits 输出用于评分。另外两个分别表示每个记号作为问题最终答案的开始或结束记号的概率。这些由定制的搜索器拾取,并且这些字段代表的实际跨度在那里被提取。
该应用程序包含一个额外的模型,即用于在运行时为查询生成嵌入向量的问题编码器模型。来自维基百科数据集的预先计算的文档嵌入由脸书研究所发布。我们直接使用这些嵌入。
Vespa 容器中间件——将所有这些放在一起
该应用程序具有以下在 Vespa 容器中作为 Java 实现的自定义插件:
- 一个 BERT 记号赋予器组件,负责从文本中生成 BERT 记号序列。
- 一种在索引过程中使用 BERT 符号化的定制文件处理程序。
- 一个定制的搜索器,为调用 BERT 问题编码器模型的查询检索嵌入表示。
- 控制检索逻辑的自定义搜索器(例如,稀疏、密集或混合)。
- 一个自定义搜索器,它读取阅读器模型的输出,提取最匹配的答案范围,并将该令牌序列转换为返回给用户的实际文本。
结果是一个服务,它接受一个文本问题并返回预测的答案,所有这些都在一个 Vespa 应用程序中。
数据和对数据的计算(例如 ML 模型评估)被分布到所有内容节点。自定义处理发生在无状态容器上。(图片作者)
结果
我们在这个应用程序中使用的基准是自然问题数据集。本节中的所有实验都是通过查询 Vespa 实例并检查黄金参考答案的预测答案来运行的。
寻回犬准确度总结
我们使用 Recall@position 作为检索者的主要评估指标。寻回犬的明显目标是在尽可能低的位置获得尽可能高的回忆。由于使用基于 BERT 的阅读器对最终的顶级段落进行重新排序,因此我们需要评估的段落越少,运行时间复杂度和性能就越好。
下表总结了使用开放领域问答任务的自然问题中的原始 3610 个 dev 问题的检索器准确性( NQ-open.dev.jsonl )。
检索准确性
DPR 论文报告召回 79.4 的@20,因此我们的结果与他们报告的密集检索方法的结果一致。我们将这种细微的差异归因于 HNSW 指数的不同设置。
阅读器准确度总结
我们使用精确匹配(EM)指标来评估阅读器的准确性。精确匹配指标衡量与任何一个基本事实答案精确匹配的预测的百分比。要使查询的 EM 分数为 1,答案预测必须与数据集中给出的最佳答案完全匹配。这很有挑战性。例如,“最后一次登月是什么时候?”有黄金答案“1972 年 12 月 14 日 UTC”或“1972 年 12 月”,预测答案“1972 年 12 月 14 日”将被评分为 0。
结果与上面的数据集相同,检索器对前 10 个结果进行了重新排序:
阅读器准确度
以上结果重现了 DPR 论文的结果,该论文是基于检索的系统的当前技术状态。
进一步的工作
在这篇博文中,我们一直专注于在 Vespa 的一个实例中重现 DPR 论文的结果。在本系列的下一部分中,我们将介绍一个更复杂的混合模型,并展示如何使用其他模型类型来提高准确性。我们将发表另一篇文章,讨论如何将总系统延迟降低到更合理的水平,同时衡量这对准确性的影响。
敬请期待!
使用 Lambda、Map、Filter 和 Sorted 进行高效的 Python 编程
使用数字、字符串和字典列表的快乐编程
一些内置函数,比如 lambda 函数,可以以更简洁的方式修改列表。我喜欢使用 lambdas,因为我可以用更少的代码编写程序,并且仍然清晰易懂。另外,它不会使你的程序变慢。
下面是语法:
lambda x,…. : expression
我在 x 后面加了几个点来象征更多的争论。
Lambdas 可以接受任意数量的参数,但只能接受一个表达式。
Lambda 用于匿名函数,即没有名称的函数。我们使用“def”来定义 python 函数。但是当我们使用 lambda 时,我们可以不使用’ def '来创建函数。如果要保存,一个变量就够了。
让我们看一些例子。这个函数有一个参数 x,返回 x 的平方。
def squared(x):
return x*x
现在用 lambda 写同样的函数。
squared = lambda x: x*x
两个平方函数的工作方式完全相同。
现在来看一个简单的加法函数,它接受两个参数 x 和 y,并返回它们的和:
add = lambda x, y: x+y
使用 lambda 不是更简洁吗?它节省了额外的一行。
调用函数“add”并向其传递两个整数。
add(4, 5)
输出:
9
在处理列表时,它看起来会更有效率!
地图
Python 中的 Map 函数接受列表和函数,并返回由函数修改的列表。让我们来看一些例子。这里有三个列表 a,b,c。
a = [3,4,5,2,7,8]
b = [7,9,2,4,5,1]
c = [5,7,3,4,5,9]
在列表 a 和 b 上调用 add 函数:
add(a,b)
输出:
[3, 4, 5, 2, 7, 8, 7, 9, 2, 4, 5, 1]
这没有给出元素方面的增加。当你简单地添加到列表中时,它们只是连接在一起。它只是创建了一个更大的列表,包含了两个列表中的所有元素
通常,我们对两个相同大小的列表进行元素相加的方法是使用“for 循环”,如下所示:
res = []
for i in range(0, len(a)):
res.append(a[i]+ b[i])res
输出:
[10, 13, 7, 6, 12, 9]
我不得不写三行代码来添加两个列表。现在,让我们使用“map”用一行代码来完成它。
使用“map ”,您可以调用我们之前在列表 a 和 b 上定义的 add 函数来获得元素相加。它的工作方式完全类似于“for 循环”,并给出相同的输出。
list(map(add, a, b))
输出:
[10, 13, 7, 6, 12, 9]
是不是简洁优雅了很多?
让我们总结一下我们所做的。我们首先定义了一个名为“add”的函数,然后使用一个“map”函数来获得两个列表的元素范围的相加。我们能做得更好吗?
是啊!我们可以在同一行代码中调用’ add '函数。
list(map(lambda x, y: x+y, a, b))
输出:
[10, 13, 7, 6, 12, 9]
我们在这里做的是,
- 我们用 lambda 定义了一个函数,它有两个参数 x 和 y。
- 向函数传递了两个列表 a 和 b。
- 使用地图,因为 a 和 b 是列表。我们想要 a 和 b 的元素相加。
事实上,您可以添加所有三个列表或任意数量的参数:
list(map(lambda x, y, z: x+y+z, a, b, c))
输出:
[15, 20, 10, 10, 17, 18]
如果你有一个包含三个参数的公式,而不是简单的加法:
list(map(lambda x, y, z: 2*x + 2.5*y + z, a, b, c))
输出:
[28.5, 37.5, 18.0, 18.0, 31.5, 27.5]
过滤器
过滤器将列表作为参数,将函数作为表达式。它在通过函数过滤掉元素后返回修改后的列表。这里有一些例子。
只返回列表中大于 5 的数字。在此之前,制作一个新列表 c:
c = [4,2,9,6,10,4,1]
list(filter(lambda x: x > 5, c))
输出:
[9, 6, 10]
在这里,我定义了返回大于 5 的数字的函数,并将列表 c 作为参数。看,它返回了所有大于 5 的 c 元素。
让我们来看另一个例子。只返回列表中的偶数。这一次,我将传递列表 a。
a = [3,4,5,2,7,8]
list(filter(lambda x: x % 2==0, a))
输出:
[4, 2, 8]
我们也可以在字符串上使用 lambda 和 filter。
上面所有的例子都有数字。接下来的几个例子是关于字符串的。这是一份名单:
names = [‘Abram’, ‘Arib’, ‘Bob’, ‘Shawn’, ‘Aria’, ‘Cicilia’, ‘John’, ‘Reema’, ‘Alice’, ‘Craig’, ‘Aaron’, ‘Simi’]
返回所有以 will 'A '开头的名字。
list(filter(lambda x: x[0]==’A’, names))
输出:
['Abram', 'Arib', 'Aria', 'Alice', 'Aaron']
分类的
sorted 函数是一种非常简单、容易的按字母排序数字或字符串的方法。以下是一些例子:
按名字的第一个字母对上面列表中的名字进行排序。
sorted(names, key=lambda x: x[0])
输出:
[‘Abram’, ‘Arib’, ‘Aria’, ‘Alice’, ‘Aaron’, ‘Bob’, ‘Cicilia’, ‘Craig’, ‘John’, ‘Reema’, ‘Shawn’, ‘Simi’]
太棒了!但是一个更简单的选择是:
sorted(names)
输出:
[‘Aaron’, ‘Abram’, ‘Alice’, ‘Aria’, ‘Arib’, ‘Bob’, ‘Cicilia’, ‘Craig’, ‘John’, ‘Reema’, ‘Shawn’, ‘Simi’]
这样,名字就按字母顺序排列了。同样的事情也适用于列表。
sorted(c)
输出:
[1, 2, 4, 4, 6, 9, 10]
λ、映射、过滤和用字典排序
使用 lambda、map、filter 和 sorted 来处理字典要简单和高效得多。
这是一个有四本字典的列表。每本词典都包括一个人的名字和他或她的年龄。
dict_a = [{'name': 'John', 'age': 12},{'name': 'Sonia', 'age': 10},{'name: 'Steven', 'age': 13},{'name': 'Natasha', 'age': 9}]
仅返回上面列表中的姓名列表:
list(map(lambda x: x['name'], dict_a))
输出:
['John', 'Sonia', 'Steven', 'Natasha']
同样,您可以只从 dict_a 中输出年龄:
list(map(lambda x: x['age'], dict_a))
输出:
[12, 10, 13, 9]
如果您希望姓名按字母排序或“年龄”列表按字母排序,只需在前面加一个 sorted:
sorted(list(map(lambda x: x['age'], dict_a)))
输出:
[9, 10, 12, 13]
你可能会想,把整本字典按年龄分类会更有用。在这种情况下,我们只需要使用 lambda 的密钥:
sorted(dict_a, key=lambda x: x['age'])
输出:
[{'name': 'Natasha', 'age': 9},{'name': 'Sonia', 'age': 10},{'name': 'John', 'age': 12},{'name': 'Steven', 'age': 13}]
输出最小的孩子的信息:
sorted(dict_a, key=lambda x: x['age'])[0]
输出:
{'name': 'Natasha', 'age': 9}
输出最大的孩子的信息:
sorted(dict_a, key=lambda x: x['age'])[-1]
输出:
{‘name’: ‘Steven’, ‘age’: 13}
如果我们需要列表以降序排列,而不是升序排列:
sorted(dict_a, key=lambda x: x['age'], reverse =True)
输出:
[{'name': 'Steven', 'age': 13},{'name': 'John', 'age': 12},{'name': 'Sonia', 'age': 10},{'name': 'Natasha', 'age': 9}]
或者,如果您想按字母顺序对姓名进行排序:
sorted(dict_a, key=lambda x: x['name'])
输出 10 岁以上儿童的信息:
list(filter(lambda x: x['age'] > 10, dict_a))
输出:
[{'name': 'John', 'age': 12},{'name': 'Steven', 'age': 13}]
三年后,你又回到了孩子们的年龄。所以,每个年龄加 3 就行了:
list(map(lambda x: x['age']+3, dict_a))Output: [15, 13, 16, 12]
结论
我希望本教程有助于提高您的 python 编程。
以下是 YouTube 上的视频教程:
欢迎在推特上关注我,喜欢我的 T2 脸书页面。
更多阅读:
当你在寻找一个范围而不是一个确切的数值,一个等级而不是一个分数
towardsdatascience.com](/sort-and-segment-your-data-into-bins-to-get-sorted-ranges-pandas-cut-and-qcut-7785931bbfde) [## Numpy 完全指南
日常工作中需要的所有数字方法
towardsdatascience.com](/a-complete-guide-to-numpy-fb9235fb3e9d) [## Python Matplotlib 的日常备忘单
完整的可视化课程
towardsdatascience.com](/your-everyday-cheatsheet-for-pythons-matplotlib-c03345ca390d) [## Python 中的线性回归算法:一步一步
学习线性回归的概念,并使用 python 从头开始开发一个完整的线性回归算法
towardsdatascience.com](/basic-linear-regression-algorithm-in-python-for-beginners-c519a808b5f8) [## Pandas 的 Groupby 功能详细,可进行高效的数据汇总和分析
学习对数据进行分组和汇总,以使用聚合函数、数据转换、过滤、映射、应用函数…
towardsdatascience.com](/master-pandas-groupby-for-efficient-data-summarizing-and-analysis-c6808e37c1cb) [## 学习使用 Python 的 Scikit_learn 库通过项目开发 KNN 分类器
适合机器学习新手
towardsdatascience.com](/clear-understanding-of-a-knn-classifier-with-a-project-for-the-beginners-865f56aaf58f)
高效 PyTorch —消除瓶颈
什么是高效的 PyTorch 培训渠道?它是产生具有最佳精确度的模型的那个吗?还是跑得最快的那个?还是容易理解和扩展的那个?可能是容易平行的那种?以上都是。
哈里·谢尔顿在 Unsplash 上拍摄的照片
PyTorch 是一款用于研究和生产领域的优秀工具,斯坦福大学、Udacity、SalelsForce、Tesla 等都采用了这一深度学习框架,这清楚地表明了这一点…然而,每种工具都需要投入时间来掌握技能,以便最有效地使用它。在使用 PyTorch 两年多后,我决定总结一下我使用这个深度学习库的经验。
高效的—(指系统或机器)以最少的浪费努力或费用获得最大的生产率。(牛津语言)
高效 PyTorch 系列的这一部分给出了识别和消除 I/O 和 CPU 瓶颈的一般技巧。第二部分将揭示一些关于有效张量运算的技巧。第三部分——高效的模型调试技术。
免责声明:这篇文章假设你至少对 PyTorch 有所了解。
我将从最明显的一个开始:
Roshni Sidapara 在 Unsplash 上拍摄的照片
建议 0: 知道代码中的瓶颈在哪里
命令行工具,如[nvidia-smi](https://developer.nvidia.com/nvidia-system-management-interface)
、[htop](https://hisham.hm/htop/)
、[iotop](https://linux.die.net/man/1/iotop)
、[nvtop](https://github.com/Syllo/nvtop)
、[py-spy](https://pypi.org/project/py-spy/)
、[strace](https://strace.io/)
等…应该成为你最好的朋友。您的培训渠道是否受限于 CPU?IO 绑定?GPU 绑定?这些工具将帮助你找到答案。
你可能甚至没有听说过它们,或者听说过但没有使用过。没关系。如果你没有立即开始使用它们也没关系。请记住,其他人可能会用它们来训练 5%-10%-15%的模型…比你更快,这最终可能会决定你是赢得还是失去市场,或者在工作岗位上获得批准还是拒绝。
数据预处理
几乎每个培训渠道都从Dataset
班开始。它负责提供数据样本。任何必要的数据转换和扩充都可以在这里进行。简而言之,Dataset
是一个抽象,它报告其大小,并根据给定索引返回数据样本。
如果您正在处理类似图像的数据(2D、3D 扫描),磁盘 I/O 可能会成为瓶颈。要获得原始像素数据,您的代码需要从磁盘读取数据,并将图像解码到内存中。每个任务都很快,但是当您需要尽可能快地处理成千上万的任务时,这可能会成为一个挑战。像 NVidia Dali 这样的库提供了 GPU 加速的 JPEG 解码。如果您在数据处理管道中面临 IO 瓶颈,这绝对值得一试。
还有一个选择。SSD 磁盘的访问时间约为 0.08–0.16 毫秒。RAM 的访问时间为纳秒。我们可以将数据直接存入内存!
建议 1: 如果可能的话,把你的数据全部或者部分移动到 RAM 。
如果您有足够的 RAM 来加载并在内存中保存所有的训练数据,这是从管道中排除最慢的数据检索步骤的最简单方法。
这个建议对云实例特别有用,比如亚马逊的p3.8xlarge
。这个实例有 EBS 磁盘,对于默认设置,它的性能非常有限。然而,这个实例配备了惊人的 248Gb 内存。这足以在内存中保存所有 ImageNet 数据集!你可以这样做:
我亲自面对了这个瓶颈问题。我有一台配备 4x1080Ti GPUs 的家用 PC。有一次,我拿了一个有四个 NVidia Tesla V100 的p3.8xlarge
实例,并把我的训练代码移到那里。鉴于 V100 比我的老款 1080Ti 更新更快,我预计训练速度会提高 15-30%。当每个时期的训练时间增加时,我感到很惊讶!这是我的教训,要注意基础设施和环境的细微差别,而不仅仅是 CPU 和 GPU 的速度。
根据您的情况,您可以在 RAM 中保持每个文件的二进制内容不变,并“即时”解码,或者解压缩图像并保留原始像素。但是无论你选择哪条路,这里有第二条建议:
忠告二:简介。测量。比较一下。每次你对渠道进行任何改变时,都要仔细评估它会产生什么样的整体影响。
这个建议只关注训练速度,假设你没有引入模型、超参数、数据集等的变化…您可以有一个神奇的命令行参数(神奇的开关),当指定时,它将为一些合理数量的数据样本运行训练。有了这一功能,您可以随时快速分析您的管道:
# Profile CPU bottlenecks
python -m cProfile training_script.py --profiling# Profile GPU bottlenecks
nvprof --print-gpu-trace python train_mnist.py# Profile system calls bottlenecks
strace -fcT python training_script.py -e trace=open,close,read
建议 3: 离线预处理一切
如果您正在对 512x512 大小的图像进行训练,这些图像是由 2048x2048 图片组成的,请事先调整它们的大小。如果您使用灰度图像作为模型的输入,请离线进行颜色转换。如果您正在进行 NLP —预先进行标记化并保存到磁盘。在训练中一遍又一遍地重复同样的操作是没有意义的。在渐进式学习的情况下,您可以保存多种分辨率的训练数据,这仍然比在线调整到目标分辨率要快。
对于表格数据,考虑在Dataset
创建时将pd.DataFrame
对象转换为 PyTorch 张量。
建议 4: 调整数据加载器的工作线程数量
PyTorch 使用 DataLoader 类来简化为训练模型而生成批处理的过程。为了加快速度,它可以使用 python 的多处理并行处理。大多数情况下,它开箱即可正常工作。有几件事要记住:
每个进程生成一批数据,这些数据通过互斥同步提供给主进程。如果您有 N 个工作线程,那么您的脚本将需要 N 倍多的 RAM 来在系统内存中存储这些批处理。你到底需要多少内存?
我们来计算一下:
- 假设我们为批量大小为
32
的城市风景和大小为512x512x3 (height, width, channels)
的 RGB 图像训练图像分割模型。我们在 CPU 端进行图像标准化(稍后我会解释为什么它很重要)。在这种情况下,我们最终的图像张量将是512 * 512 * 3 * sizeof(float32) = 3,145,728
字节。乘以批处理大小,我们得到100,663,296
字节,或者大约 100 Mb。 - 除了图像,我们还需要提供真实的面具。它们各自的大小是(默认情况下,masks 的类型是 long,8 字节)——
512 * 512 * 1 * 8 * 32 = 67,108,864
或者大约 67 Mb。 - 因此,一批数据所需的总内存为
167 Mb
。如果我们有 8 个工人,所需的内存总量将是167 Mb * 8 = 1,336 Mb.
听起来不算太糟,对吧?当您的硬件设置能够处理的批处理数量超过 8 个工作线程所能提供的数量时,就会出现问题。人们可以天真地放置 64 个工人,但这至少会消耗将近 11 Gb 的 RAM。
如果你的数据是 3D 体积扫描,事情会变得更糟;在这种情况下,即使是单通道卷512x512x512
的一个样本也将占用 134 Mb,对于批量大小 32,它将是 4.2 Gb,对于 8 个工人,您将只需要 32 Gb 的 RAM 来在内存中保存中间数据。
这个问题有一个部分的解决方案,您可以尽可能地减少输入数据的通道深度:
- 将 RGB 图像保持在每通道 8 位深度。图像转换为浮点和归一化可以很容易地在 GPU 上完成。
- 在数据集中使用 uint8 或 uint16 数据类型,而不是 long。
通过这样做,您可以大大降低 RAM 需求。对于上面的例子,内存高效数据表示的内存使用量将是每批 33 Mb,而不是 167 Mb。那就是 5 倍还原!当然,这需要模型本身的额外步骤来将数据规范化/转换为适当的数据类型。然而,张量越小,CPU 到 GPU 的传输时间就越快。
应该明智地选择DataLoader
的工人数量。您应该检查您的 CPU 和 IO 系统有多快,您有多少内存,以及 GPU 处理这些数据的速度有多快。
多 GPU 训练和推理
神经网络模型变得越来越大。如今的趋势是使用多个 GPU 来增加训练时间。幸运的是,它还经常提高模型的性能,使批量更大。PyTorch 拥有在几行代码内实现多 GPU 的所有特性。然而,有些警告乍一看并不明显。
model = nn.DataParallel(model) # Runs model on all available GPUs
使用多 GPU 最简单的方法是在nn.DataParallel
类中包装一个模型。在大多数情况下,它工作得很好,除非你训练一些图像分割模型(或任何其他产生大尺寸张量作为输出的模型)。在正向传递结束时,nn.DataParallel
将从主 GPU 上的所有 GPU 收集输出,以反向运行输出并进行梯度更新。
有两个问题:
- GPU 负载不平衡
- 在主 GPU 上采集需要额外的视频内存。
首先,只有主 GPU 在进行损失计算、反向传递和梯度步骤,而其他 GPU 在 60C 下等待下一批数据。
其次,在主 GPU 上收集所有输出所需的额外内存通常会迫使您减少批量大小。事情是这样的,nn.DataParallel
在 GPU 之间平均分割批处理。假设您有 4 个 GPU,总批量为 32。然后,每个 GPU 将获得其 8 个样本的块。但问题是,虽然所有非主 GPU 都可以轻松地将这些批处理放入其相应的 VRAM 中,但主 GPU 必须分配额外的空间来容纳来自所有其他卡的输出的 32 个批处理大小。
对于这种不均衡的 GPU 利用率,有两种解决方案:
- 继续使用
nn.DataParallel
并在训练中计算向前传球的损失。在这种情况下,您不会将密集预测掩码返回给主 GPU,而是只返回一个标量损失。 - 使用分布式训练,又名
nn.DistributedDataParallel
。在分布式培训的帮助下,您可以解决上述两个问题,并享受观看所有 GPU 100%加载的乐趣。
如果您知道要了解更多关于多 GPU 培训的信息,并深入了解每种方法的优缺点,请查看这些精彩的帖子以了解更多信息:
- https://medium . com/hugging face/training-large-batches-practical-tips-on-1-GPU-multi-GPU-distributed-settings-EC 88 C3 e 51255
- https://medium . com/@ the accelerators/learn-py torch-multi-GPU-proper-3eb 976 c 030 ee
- https://towards data science . com/how-to-scale-training-on-multi-GPU-DAE 1041 f 49d 2
建议 5: 如果你有 2 个以上的 GPU,考虑使用分布式训练模式
它将节省多少时间在很大程度上取决于您的场景,但我观察到,当在 4x1080Ti 上训练图像分类管道时,时间减少了约 20%。
同样值得一提的是,您也可以使用nn.DataParallel
和nn.DistributedDataParallel
进行推理。
关于自定义损失函数
编写自定义损失函数是一项有趣且令人兴奋的工作。推荐大家不定期尝试一下。在用复杂的逻辑实现损失函数时,有一件事你必须记住:它都运行在 CUDA 上,你有责任编写 CUDA 高效的代码。CUDA-efficient 的意思是“没有 python 控制流”。在 CPU 和 GPU 之间来回切换,访问 GPU 张量的单个值可能会完成工作,但性能会很糟糕。
前一段时间,我实现了一个定制的余弦嵌入损失函数,用于实例分割,来自“使用余弦嵌入和递归沙漏网络分割和跟踪细胞实例”的论文。它的文本形式非常简单,但是实现起来有点复杂。
我写的第一个天真的实现(除了 bug)花了几分钟(!)来计算单个批次的损失值。为了分析 CUDA 瓶颈,PyTorch 提供了一个非常方便的内置分析器。它使用起来非常简单,并且为您提供了解决代码瓶颈的所有信息:
建议 9: 如果您正在设计定制模块&损耗—剖析&测试它们
在对我的初始实现进行概要分析之后,我能够将我的实现速度提高 100 倍。关于用 PyTorch 编写高效张量表达式的更多内容将在高效 PyTorch —第 2 部分中解释。
时间与金钱
帕特里克·福尔在 Unsplash 上拍摄的照片
最后但同样重要的一点是,有时投资于更强大的硬件比优化代码更有价值。软件优化永远是一个高风险的旅程,有不确定的结果。升级 CPU、RAM、GPU 或全部一起升级可能更有效。金钱和工程时间都是资源,正确利用二者是成功的关键。
建议 10: 一些瓶颈可以通过硬件升级更容易地解决
结论
充分利用你的日常工具是精通的关键。试着不要走捷径,如果有些事情你不清楚,一定要深入挖掘。总有机会获得新知识。问问你自己或你的伙伴——“我的代码如何改进?”。我真的相信,对于计算机工程师来说,这种完美感和其他技能一样重要。
Eugene 是计算机视觉和机器学习工程师,拥有超过 10 年的软件开发经验。《掌握 OpenCV 用于实用计算机视觉项目》一书的作者。卡格尔大师。白蛋白核心团队成员。《pytorch-toolbelt 的作者。OpenCV、PyTorch 和 Catalyst 贡献者。https://www.linkedin.com/in/cvtalks/
高效 py torch——增压培训管道
为什么只报告你的模型的最高精度通常是不够的。
将你的 train.py 脚本转化为具有一些额外特性的强大管道(照片由 Nur Faizin 在 Unsplash 上拍摄)
每一个深度学习项目的最终目标都是为产品带来价值。当然,我们希望有最好的模型。什么是“最好的”——取决于特定的用例,我将在这篇文章中不讨论这个问题。我想谈谈如何从您的 train.py 脚本中获得最大收益。
在本帖中,我们将介绍以下技巧:
- 相反,高级框架自己制造了训练循环
- 使用其他指标监控培训进度
- 使用张量板
- 可视化模型的预测
- 使用 Dict 作为数据集和模型的返回值
- 检测异常并解决数值不稳定性
免责声明:在下一节中,我将包括一些源代码清单。它们中的大多数是为 Catalyst 框架(版本 20.08)定制的,并且在 pytorch-toolbelt 中可用。
不要重新发明轮子
建议 1 —利用 PyTorch 生态系统的高级培训框架
PyTorch 在从头开始编写训练循环时提供了极好的灵活性和自由度。理论上,这为编写任何训练逻辑提供了无限的可能性。在实践中,你很少会为训练 CycleGAN、提取 BERT 或从头实现 3D 对象检测编写奇异的训练循环。
从头开始编写一个完整的训练循环是学习 PyTorch 基础知识的极好方法。然而,我强烈建议,一旦你有所领悟,就转向高级框架。选项很多:触媒、 PyTorch-Lightning 、 Fast。AI 、点燃、其他。高级库通过以下方式节省您的时间:
- 提供久经考验的训练循环
- 支持配置文件
- 支持多 GPU 和分布式训练
- 检查点/实验的管理
- 自动记录培训进度
从这些高级库中获取最大价值需要一些时间。然而,这种一次性投资从长远来看是值得的。
优点
- 训练管道变得更小——代码越少——出现错误的机会就越少。
- 更简单的实验管理。
- 简化的分布式和混合精度训练
缺点
- 另一个抽象层次——通常,当使用高级框架时,我们必须在特定框架的设计原则和范例内编写代码。
- 时间投资—学习额外的框架需要时间。
给我看看指标
由 Mikail McVerry 在 Unsplash 上拍摄的照片
建议 2——在培训期间查看其他指标
在 MNIST、CIFAR 甚至 ImageNet 中,几乎每一个用于图像分类的快速入门示例项目都有一个共同点——它们在培训期间和培训之后报告一组最少的指标。最常见的是前 1 名和前 5 名的准确性、错误率、训练/验证损失,仅此而已。虽然这些指标是必不可少的,但这只是冰山一角!
现代图像分类模型有数千万个参数。你想只用一个标量值来计算它吗?
具有最佳 Top-1 准确度的 CNN 分类模型在泛化方面可能不是最佳的。根据您的领域和要求,您可能希望保存假阳性/假阴性率最低的模型或平均精度最高的模型。
让我给你一些建议,在培训期间你可以记录哪些数据:
- Grad-CAM 热图——查看图像的哪些部分对特定类别的贡献最大。
可视化 Grad-CAM 热图有助于识别模型制作预测是基于真实的病理还是图像伪影(尤金·赫韦德钦亚)
- 混淆矩阵——向你展示哪一对职业对你的模型最具挑战性。
混淆矩阵揭示了模型对特定类型做出错误分类的频率(Eugene Khvedchenya,ALASKA2 Image Steganalysis,Kaggle)。
- 预测分布——让您洞察最佳决策边界。
该模型的负面和正面预测的分布显示,有很大一部分数据该模型无法以置信界限进行分类(Eugene Khvedchenya,ALASKA2 Image Steganalysis,Kaggle)。
- 所有层的最小/平均/最大梯度值——允许识别模型中是否有消失/爆炸梯度或初始化不良的层。
使用仪表板工具监控培训
建议 3——使用 TensorBoard 或任何其他解决方案来监控训练进度
在训练模型时,您最不想做的事情可能就是查看控制台输出。一个功能强大的仪表盘可以让你一次看到所有的指标,这是一种更有效的检查培训结果的方式。
Tensorboard 允许在本地快速检查和比较您的跑步记录(Eugene Khvedchenya)
对于少量实验和非分布式环境,TensorBoard 是金标准。从版本 1.3 开始,PyTorch 完全支持它,并提供了一组丰富的功能来管理试验。还有更先进的基于云的解决方案,如Weights&bias、 Alchemy 和 TensorBoard.dev ,这使得在多台机器上监控和比较训练课程变得更加容易。
使用 Tensorboard 时,我通常会记录一组指标:
- 学习率和其他可能改变的优化参数(动量、重量衰减等)。)
- 花费在数据预处理和模型内部的时间
- 跨训练和验证的损失(每批和每个时期的平均值)
- 跨培训和验证的指标
- 最终度量值的训练会话的超参数
- 混淆矩阵、精密度-召回曲线、AUC(如果适用)
- 模型预测的可视化(如果适用)
一幅画胜过千言万语
直观地看模型的预测是超级重要的。有时候训练数据是有噪音的;有时,模型会过度拟合图像的伪影。通过可视化最佳和最差批次(基于损失或您感兴趣的度量),您可以获得对模型表现良好和较差的情况的宝贵见解。
建议 5 —可视化每个时期的最佳和最差批次。它可能会给你宝贵的洞察力
Catalyst 用户提示:此处使用可视化回调 I 示例:https://github . com/blood axe/Catalyst-Inria-Segmentation-Example/blob/master/fit _ predict . py # L258
例如,在全球小麦检测挑战中,我们需要检测图像上的麦穗。通过可视化最佳批次的图片(基于地图度量),我们看到该模型在查找小对象方面做得近乎完美。
最佳模型预测的可视化揭示了模型在小对象上表现良好(Eugene Khvedchenya,Global Wheat Detection,Kaggle)
相比之下,当我们看最差批次的第一个样本时,我们看到该模型很难对大型物体做出准确的预测。可视化分析为任何数据科学家提供了宝贵的见解。
最差模型预测的可视化揭示了模型在大型对象上表现不佳(Eugene Khvedchenya,Global Wheat Detection,Kaggle)
查看最差批次也有助于发现数据标签中的错误。通常,标签错误的样品损失更大,因此会出现在最差的批次中。通过在每个时期对最差批次进行目视检查,您可以消除这些错误:
标签错误的例子。绿色像素表示真阳性,红色像素表示假阴性。在此示例中,地面实况掩膜在实际上不存在的位置有一个建筑物覆盖区。(Eugene Khvedchenya,Inria 航空影像标注数据集)
使用Dict
作为数据集和模型的返回值
建议 4 —如果您的模型返回多个值,请使用
***Dict***
返回结果。不要用***tuple***
。
在复杂模型中,返回多个输出并不少见。例如,对象检测模型通常会返回包围盒及其标签,在图像分割 CNN-s 中,我们经常会返回中间遮罩以进行深度监督,多任务学习目前也很流行。
在许多开源实现中,我经常看到这样的情况:
出于对作者的尊重,我认为这是一种从模型返回结果的非常糟糕的方法。以下是我推荐使用的替代方式:
这个建议与“Python 之禅”中的一个假设有点共鸣——“显式比隐式好”。遵循这条规则将会使你的代码更加整洁和易于维护。
那么,为什么我认为第二种选择更好呢?出于几个原因:
- 返回值有一个与之相关联的显式名称。您不需要记住元组中元素的确切顺序
- 如果需要访问返回的字典中的特定元素,可以通过它的名称来实现
- 从模型中添加新的输出不会破坏代码
使用Dict
,你甚至可以改变模型的行为,根据需要返回额外的输出。例如,这里有一小段代码演示了如何为度量学习返回多个“主要”输出和两个“辅助”输出:
同样的建议也适用于数据集类。对于 Cifar-10 toy 示例,可以将图像及其对应的标签作为元组返回。但是在处理多任务或多输入模型时,您希望以 Dict 类型从数据集中返回样本:
当您的代码中有字典时,您可以在任何地方引用带有名称常量的输入/输出。遵循这条规则将使你的培训渠道非常清晰,易于遵循:
检测训练中的异常
就像人类可以阅读其中有许多错误的文本一样,深度学习模型也可以在训练管道中出现错误时学习“一些合理的东西”。作为开发人员,您负责搜索异常,并对它们的出现进行推理。(照片由布雷特·乔丹在 Unsplash 上拍摄)
建议 5 —使用
***torch.autograd.detect_anomaly()***
在训练过程中查找算术异常
如果你在培训过程中看到损失/指标中有任何 nan 或 Inf,你的脑海中应该响起警报。这表明您的管道出现了问题。通常,它可能由以下原因引起:
- 模型或特定层的初始化错误(你总是可以通过查看梯度大小来检查是哪些)
- 数学上不正确的运算(
torch.sqrt()
从负数,torch.log()
从非正数,等等)。) torch.mean()
和torch.sum()
归约使用不当(零大小张量上的均值会给出 nan,大张量上的求和容易导致溢出)- 在损失中使用
x.sigmoid()
(如果你需要损失函数中的概率,更好的方法是使用x.sigmoid().clamp(eps,1-eps
或torch.logsigmoid(x).exp()
来防止梯度消失) - Adam 类优化器中的低ε值
- 使用 fp16 训练时不使用动态损耗缩放
为了在代码中找到 Nan/Inf 第一次出现的确切位置,PyTorch 提供了一个易于使用的方法torch . autograded . detect _ anomaly():
将其用于调试目的,否则禁用,因为异常检测会带来计算开销,并使训练循环变慢约 10–15%。
结束了
感谢阅读!我希望你喜欢它,并从这篇文章中找到一些外卖。你想分享什么技巧和诀窍?请在评论中写下你的诀窍,或者如果你对 PyTorch 相关的特定主题感兴趣,也请告诉我!
Eugene 是一名计算机视觉和机器学习工程师,拥有十多年的软件开发经验。《掌握 OpenCV 用于实用计算机视觉项目》一书的作者。卡格尔主人。白蛋白核心团队成员。pytorch -toolbelt 的作者。OpenCV、PyTorch 和 Catalyst contributor。https://www.linkedin.com/in/cvtalks/