TowardsDataScience-博客中文翻译-2021-一百-

龙哥盟 / 2024-10-23 / 原文

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

原文:TowardsDataScience Blog

协议:CC BY-NC-SA 4.0

你应该知道这三个数据科学可视化工具

原文:https://towardsdatascience.com/you-should-know-these-3-data-science-visualization-tools-e5afd43eabf6?source=collection_archive---------9-----------------------

意见

原因如下…

Domenico Loia 在Unsplash【1】上拍摄的照片。

目录

  1. 介绍
  2. (舞台上由人扮的)静态画面
  3. 谷歌数据工作室
  4. 熊猫简介
  5. 摘要
  6. 参考

介绍

可视化的使用是数据科学的一个重要方面。从数据科学开始过程的好处,到结束,可视化的目的是以有效的方式阐明复杂的想法。有些人,比如我,从观察视觉效果中学到了最多的东西——例如,描述数据分布的直方图要比观察实际的视觉效果更难理解。虽然并不总是如此简单,但可视化可以让您的数据科学过程易于向非数据科学家的人解释。说了这么多,让我们看看数据科学可视化的具体工具,以及为什么您应该了解和使用它们。

(舞台上由人扮的)静态画面

由劳伦·曼克在Unsplash【2】上拍摄。

这个流行的工具不仅是数据科学家的常用工具,也是产品经理、工程师、业务分析师和数据分析师的常用工具。然而,这种工具的主要缺陷是它很难获得,而且非常昂贵。如果你幸运地拥有这个工具,那么你应该知道为什么它对你这个数据科学家有好处,所以我将在下面描述它。

Tableau【3】是一个分析平台,允许用户通过可视化来理解数据,同时也易于使用。这一点并不仅仅意味着数据科学,但是为了这篇文章,我们将更深入地探讨 Tableau 对数据科学的好处。遵循数据科学的一般路径,即获取数据并分析它,构建模型,并报告结果,我们可以看到 Tableau 可以适合每个部分。

获取和分析数据:

Tableau 为您提供了从静态 CSV 文件中探索数据的能力,但它也允许您通过实时连接直接使用 SQL 查询数据库。这一特性意味着您可以将 Tableau 用于实时仪表板和自动更新的数据。一个具体的特性, Prep ,允许你塑造、组合、构建数据。一旦您的数据被接收,您就可以专注于使用探索性分析来分析它。您可以用统计重要性来检查趋势,也可以很好地可视化相关性。制作简单的图形或图表对于数据科学来说并不独特,但是能够轻松地制作地图是一大优势。您可以在世界地图、美国地图等等上创建、添加和聚合您的数据,而不是在熊猫数据框架中按州对您的数据进行分组。

构建模型:

除了回归、趋势线和预测之外,您还可以利用 聚类算法特性 ,它是基于 k-means 构建的。没错,你可以在 Tableau 中建立一个模型。Tableau 特别使用 Calinski-Harabasz 标准进行聚类。一些易于理解的用于汇总聚类的诊断方法是组间平方和组内平方和,分别用于查看聚类之间的间隔以及各个聚类点的相似性。当然,这种聚类以一种美丽的方式可视化,不仅为数据科学家优化,也为非数据科学专业的其他人优化,使其更容易理解和消化。

报告结果:

也许 Tableau 最大的用途是报告结果的特性。因为这个工具主要是为业务和数据分析师开发的,所以这个特性是最重要的。类似于在构建模型之前浏览数据,您可以浏览模型的结果。例如,如果您想正常报告模型的总体 MAE,您可能只报告数字,但使用 Tableau,您可以按您在 Tableau 中指定的类别对 MAE 进行分组,并将其可视化,以查看您需要改进的组,这也可以显示您公司内部需要的某些趋势和修正。一旦您确定了可视化模型结果的最佳方式,您就可以创建一个交互式的实时仪表板,任何有权访问它的人都可以根据您指定的特定标准过滤结果。这样,你公司内部的人可以在他们自己的时间里理解你的模型,并看到它在有和没有特定过滤器的情况下是如何影响的。

总体而言,Tableau 具有以下特点:

  • 易用性
  • EDA 的常用图表
  • 用于报告结果的有用图表
  • 连接实时数据的能力
  • 可以应用数据过滤器的仪表板
  • 易于汇总指标和功能
  • 可以执行预测和聚类

如您所见,Tableau 是一个非常强大的可视化工具,每个数据科学家都应该知道。但是,如果你在一个较小的公司,这个工具可能不可行。话虽如此,可以参考下两个工具,都是 免费

谷歌数据工作室

谷歌数据工作室概述。作者截图[4]。

从上面的截图可以看出,Google Data Studio【5】是一个类似 Tableau 的工具。与 Tableau 相比,一个突出的主要优势是 GDS 是免费的,甚至可能更容易使用。它还允许对图表进行更多的定制,调整图表大小和编辑图表在仪表板上的位置比 Tableau 更容易。

您现在可以使用这个工具,您需要做的就是通过它支持的几种方法之一连接您的数据(例如,文件上传、BigQuery、MySQL 的云 SQL、MySQL 以及更多)。然后,您可以对数据进行分组和组织,并将其显示在可过滤的实时仪表板上。

谷歌数据工作室的地图功能。作者截图[6]。

我最喜欢使用的图表之一是地图。虽然简单,但如果用其他工具创建地图,就像在普通的熊猫图书馆一样,会令人沮丧。该地图允许您根据总体人口密度对特定类别和州进行颜色编码。例如,如果一个州有更多的人,该州将会更暗,在仪表板中,您可以将鼠标悬停在该州上以查看确切的人口数量。虽然这个用例很简单,但是您可以在数据科学过程的开始,或者在分析模型结果的最后,将它应用到探索性数据分析中。

总的来说,Google Data Studio 具有以下品质:

  • 自由的
  • 易于使用
  • 更加可定制
  • 用于报告结果的有用图表
  • 连接实时数据的能力
  • 仪表板可以应用数据过滤器
  • 易于汇总指标和功能

熊猫简介

布鲁斯洪在Unsplash【7】上的照片。

现在我们已经讨论了两个令人惊奇的可视化工具,接下来我们可能还需要什么呢?这就是熊猫简介《T2》的用武之地。它在其他人缺乏的地方茁壮成长。也就是说,我们不需要把我们的 Jupyter 笔记本和 pandas profiling 放在一起,我们可以简单地用 Python 导入它,并在一个单元中获得它的好处。只需几行代码,你就可以输入你的数据框,并迅速看到熊猫概况是如何变魔术的。

from pandas_profiling import ProfileReportdf.profile_report(style={‘full_width’:True})

该工具的一些主要功能包括数据框架概述、变量分析、变量相关性和缺失值。您可以看到描述性统计数据、直方图、普通值以及极值。也有警告称,该报告将显示某个特性丢失了多少,以及它是否与另一个特性高度相关— 高基数

配置文件报告中的可变特征示例。作者截图[9]。

在上面的截图中,您可以看到如何使用普通的df.describe()之外的工具来探索您的数据。这个库的唯一缺点是,如果你有一个大的数据集,它会非常慢,所以为了减轻这种痛苦,你可以使用这个技巧:

profile = ProfileReport(large_dataset, minimal=True)

它将消除像相关性这样耗时的普通计算。如果您仍然想看到这些,您可以限制您的数据集,但获取一个可以外推至主要人群的样本。

总的来说,熊猫烧香有以下特点:

  • 自由的
  • 易于使用
  • 有用的 EDA 图表
  • 只用一行代码就可以执行
  • 可以在你的 Jupyter 笔记本上执行

摘要

所有这些讨论的工具都可以在您的数据科学职业生涯中为您提供帮助,最好的部分是它们易于使用、跨功能且功能强大。能够可视化复杂的概念、数据和结果对于大多数分析师,尤其是数据科学家来说非常有用。

总而言之,作为数据科学家,您应该知道以下三种可视化工具:

* Tableau* Google Data* Pandas Profile Report

我希望你觉得我的文章既有趣又有用!如果您已经利用了这些数据科学可视化工具中的任何一个,请随时在下面发表评论,这些工具是什么? 现在对你的数据科学事业有帮助吗?你同意还是不同意,为什么?

请随时查看我的个人资料和其他文章,也可以通过 LinkedIn 联系我。我与这些提到的公司没有关系。

感谢您的阅读!

参考

[1]照片由 Domenico Loia 在Unsplash(2017)上拍摄

[2]照片由劳伦·曼克在Unsplash(2016)拍摄

[3] TABLEAU 软件有限责任公司,一家 SALESFORCE 公司。, Tableau 网站,(2003–2021)

[4] M.Przybyla,使用虚拟数据的谷歌数据工作室概述,(2021 年)

[5]谷歌数据工作室,谷歌数据工作室主页,(2021)

[6] M.Przybyla,谷歌数据工作室的地图特征,(2021 年)

[7]图片由 Bruce Hong 在Unsplash(2018)上拍摄

[8] pandas profiling, pandas profiling GitHub ,(2021)

[9] M.Przybyla,简介报告中可变特征的例子,(2021 年)

在成为数据科学家之前,你应该先掌握数据分析

原文:https://towardsdatascience.com/you-should-master-data-analytics-first-before-becoming-a-data-scientist-5dbceaea9d3d?source=collection_archive---------4-----------------------

意见

以下是为什么…的 4 个原因

由Unsplash【1】上的新数据服务拍摄的照片。

目录

  1. 介绍
  2. 探索性数据分析
  3. 利益相关者协作
  4. 特征创建
  5. 精通可视化
  6. 摘要
  7. 参考

介绍

虽然在学习数据科学之前了解数据分析是关键,这一点乍一看似乎很明显,但你可能会惊讶地发现,有多少人在没有分析和呈现数据的正确基础的情况下就直接进入了数据科学。无论是实习、入门级职位,还是任何真正的数据分析职位,事先都有一定的好处。还需要注意的是,这种形式的经验可以通过完成在线课程和数据分析专业来获得。也就是说,如果你已经接受了数据科学的正规教育,你可能已经只在一门课程中学习了数据分析的基础知识,这是最有可能的,这就是为什么有必要在你的投资组合中添加一些以数据分析为重点的知识。然而,最好的方法是与其他人一起练习某种数据分析,正如我在下面讨论在学习数据科学之前掌握数据分析的四大好处时所看到的那样。

探索性数据分析

由卢卡斯·布拉塞克在Unsplash【2】上拍摄。

由于你专攻数据分析,你在探索数据方面变得高效就不足为奇了。作为一名数据科学家,这通常是数据科学过程的第一步,因此如果您跳过这一步,您的模型可能会导致错误、混乱和误导性结果。你必须记住,垃圾进来会产生垃圾出去。仅仅因为你向机器学习算法扔了一个数据集,并不意味着它会回答手边的业务问题。

您将不得不发现数据中的异常、聚合、缺失值、转换、预处理等等。首先理解数据当然很重要,因此精通数据分析至关重要。有几个 Python ( 和 R 以及 l)库可以帮助自动完成这项工作。然而,我经常发现,对于大型数据集,它们花费的时间太长,会导致内核崩溃,你不得不重新启动。这就是为什么手动观察数据也很重要。也就是说,我将在下面介绍的库有一个大型数据集模式,它可以跳过一些昂贵且耗时较长的计算。这种情况的参数在 Pandas Profiling 库的 profile 报告中:minimal=True

这里有一个非常容易使用的库:

from pandas_profiling import ProfileReportprofile = ProfileReport(df, title="Pandas Profiling Report")profile.to_widgets()# or you an do the followingdf.profile_report()

熊猫简介【3】,可以在你的 Jupyter 笔记本上查看。该库的一些独特功能包括但不限于类型推断、唯一值、缺失值、描述性统计、频繁值、直方图、文本分析以及文件和图像分析。

总的来说,除了这个库之外,还有无数种方法可以练习探索性的数据分析,所以如果你还没有,找一门课程,掌握分析数据。

利益相关者协作

照片由 DocuSign 在Unsplash【4】上拍摄。

数据科学家通常可以在他们的教育中很快学会复杂的机器学习算法,跳过与利益相关者沟通以实现目标和阐明数据科学过程的重要部分。如果您还没有注意到,那么您必须成为将业务用例转化为数据科学模型的大师。产品经理或其他利益相关者不会走到你面前,要求你创建一个有 80%准确率的监督机器学习算法。他们会告诉你一些数据,以及他们一直看到的问题,你对数据科学的指导很少,这当然是意料之中的,因为这是你的工作。你必须提出回归、分类、聚类、提升、打包等概念。您还必须与他们合作,以建立成功标准,例如,100 RMSE 意味着什么,以及您如何解决并将其转化为对利益相关者有意义的业务问题。

那么,怎样才能学会协作呢?与数据科学家相比,预先担任数据分析师通常需要更多的协作。作为一名数据分析师,你将几乎每天或至少每周与他人一起创建指标、进行可视化并开发分析洞察力。正如我们从上面学到的,这种实践对于成为更好的数据科学家至关重要。

通过数据分析角色实现利益相关方协作实践的优势:

  • 商业理解
  • 问题定义
  • 成功标准创建

如您所见,与利益相关方合作是数据分析师和数据科学家职位的重要组成部分。

特征创建

照片由米利安·杰西耶在Unsplash【5】上拍摄。

作为一名数据科学家,您将不得不执行要素工程,在该工程中,您将隔离有助于模型预测的关键要素。在学校或任何您学习数据科学的地方,您可能已经有了一个完美的数据集,但在现实世界中,您将不得不使用 SQL 查询您的数据库来开始查找必要的数据。除了表中已经有的列之外,您还必须创建新的列——通常,这些新特性可以是聚合的指标,例如clicks per user。作为一名数据分析师,您将最常使用 SQL,而作为一名数据科学家,如果您只知道 Python 或 R,这可能会令人沮丧——而且您不能一直依赖 Pandas,因此,如果不知道如何高效地查询数据库,您甚至无法开始模型构建过程。类似地,对分析的关注可以让你练习创建子查询和指标,就像上面所说的那样,这样你就可以添加一些到至少 100 个,完全由你创建的新特性,这些新特性可能比你现在拥有的基础数据更重要。

特征创建的好处:

  • 能够执行任何 SQL 查询
  • 提高模型精度和误差
  • 寻找关于数据的新见解

精通可视化

照片由威廉·艾文在Unsplash【6】上拍摄。

数据分析师通常会掌握可视化,因为他们必须以公司其他人易于理解的方式呈现调查结果。拥有一个充满值的复杂表格可能会令人困惑和沮丧,因此作为一名数据科学家,能够突出重要的指标、见解和结果也是非常有益的。同样,当你完成了你用来构建最终模型的复杂的机器学习算法时,你会兴奋地分享你的结果;然而,利益相关者只需要知道重点和关键要点。

通过可视化实现这一过程的最佳方式是,以下是创建这些可视化的一些关键方法:

  • (舞台上由人扮的)静态画面
  • 谷歌数据工作室
  • 检查员
  • Seaborn 图书馆
  • MatPlotLib

当然还有更多,但下面是我经常看到用的最多的。通过可视化表达见解和结果,你也可以帮助自己更好地学习过程和要点。

摘要

那么问题来了,在成为数据科学家之前,是否应该先成为一名数据分析师?我说是——或者至少是某种形式的,无论是实习、工作、类似业务分析师的工作,还是在数据分析课程中获得认证。除了我上面讨论的四个好处之外,另一个要强调的是,如果你在简历中有数据分析的头衔或经验,它肯定会帮助你找到一份数据科学家的工作。

总而言之,在成为数据科学家之前先成为数据分析大师有一些重要的好处:

Exploratory Data AnalysisStakeholder CollaborationFeature CreationMastered Visualizations

我希望你觉得我的文章既有趣又有用。如果你在成为数据科学家之前已经以某种方式成为了数据分析师,请在下面随意评论。这对你现在的数据科学事业有帮助吗?你同意还是不同意,为什么?

请随时查看我的个人资料和其他文章,也可以通过 LinkedIn 联系我。

参考

[1]照片由新数据服务于Unsplash(2018)拍摄

[2]由卢卡斯·布拉塞克在 Unsplash 上拍摄的照片,(2017)

[3]熊猫,熊猫简介,(2021)

[4]照片由医生在Unsplash(2021)上拍摄

[5]照片由 Myriam Jessier 在Unsplash(2020)上拍摄

[6]照片由 William Iven 在 Unsplash 上拍摄,(2015)

在成为数据科学家之前,你应该先掌握 Python

原文:https://towardsdatascience.com/you-should-master-python-first-before-becoming-a-data-scientist-8ac1b1b4e10f?source=collection_archive---------4-----------------------

意见

以下是为什么…的 4 个原因

钳工在Unsplash【1】上拍照。

目录

  1. 介绍
  2. 面向对象编程( OOP )
  3. 熊猫
  4. sci kit-学习
  5. 跨职能协作
  6. 摘要
  7. 参考

介绍

数据科学教育、理论和实践通常会关注统计学和机器学习算法。虽然这一重点当然是必要的,因为这些是数据科学的基础和主要方面,但还有另一项技能被忽视,尤其是在学校。我所指的这个技能是知道如何用 Python 编码。当然,现在作为一名数据科学家,你很可能已经知道如何用 Python 编程,但是当你第一次开始时,你可能只关注后者。在学习数据科学之前,首先掌握 Python 是很重要的,因为您将很难实现流行的库,并使用其他工程师也可以使用的可扩展代码。也就是说,我将强调为什么在学习数据科学之前应该先学习 Python 的几个原因。

面向对象编程( OOP )

由 Markus Spiske 在Unsplash【2】上拍摄的照片。

当你是技术行业的任何类型的工程师时,面向对象编程是至关重要的,或者至少这是我的经验,以及在该行业工作的其他人的经验。有时候,当你学习数据科学时,你可以直接进入机器学习算法的概念和理论,虽然这当然很有用,但你需要知道 如何 在实践中应用这些概念和理论——这通常是通过编程语言来实现的。一些数据科学家使用 R,一些使用 Python,所以这些原因不仅适用于 Python,也适用于 R。

面向对象编程由类、对象、继承、函数、方法和实例等属性组成。没有面向对象的编程,你仍然可以执行数据科学过程,大多数人在他们的研究中是这样做的,但是一旦你想扩大规模,并且被更多的人看到;代码库最终受益于更高的效率。

通过学习 Python 进行面向对象编程实践的好处:

  • 模块化的
  • 干净的代码结构
  • 可量测性
  • 代码重用
  • 安全
  • 解决纷争

牢记面向对象编程带来了几个好处,就像我上面提到的,还有更多。这些好处也可以在其他同事之间分享,我将在下面讨论我的最后一点。既然我们已经讨论了面向对象编程,我们可以深入研究将 OOP 与流行的 Python 库相结合的一些方法。

熊猫

帕斯卡·米勒在Unsplash【3】上的照片。

学习 Python 意味着可以学习熊猫。你说熊猫是什么?Pandas【4】是一个你可以用来进行数据分析的工具,快速且易于使用。虽然 Pandas 通常只与数据科学联系在一起(在很大程度上是),但它仍然是您可以提前学习的东西,用于数据分析以及不同角色的其他计算。学习熊猫有无数的好处,它对数据科学过程的开始和结束部分特别有用。数据科学中出现的许多预处理步骤都可以通过 Pandas 技术来执行,例如开始时的探索性数据分析,并且对最终模型结果的解释也可以使用 Pandas 来分析。

以下是用 Python 学习熊猫的好处:

  • 重塑数据
  • 子集观测值
  • 子集化变量
  • 汇总数据
  • 处理缺失数据
  • 制作新列
  • 组合数据集
  • 分组数据
  • 窗子
  • 测绘

如果你点击上面的链接,有一个备忘单进一步总结了所有这些。Pandas dataframe 是 Pandas 的一个强大部分,它允许您从上面执行所有这些数据操作,并且是将您的数据转换为数据科学模型或机器学习算法可以读取的格式的最简单方法之一。与 Pandas 类似,还有另一个流行的库,它易于使用并且功能强大,我将在下面讨论。

sci kit-学习

照片由 Tran Mau Tri Tam 在Unsplash【5】上拍摄。

数据科学家经常使用的另一个受欢迎的库或工具是 scikit-learn,这意味着如果您在开始学习算法的具体细节之前先专注于学习 Python,您将会有一个更好的算法基础。由于您将在进入机器学习理论之前学习这个库,所以您可能只想了解可能的算法和不同的高层次类型,以便一旦您开始更具体地学习数据科学,您将对最流行的 Python 库之一(数据科学家的)中的算法范围有所了解。这种技术可能有点非正统,但我相信对您最终要钻研的内容有一个简单的概述是有益的——比如先了解主要的算法类型以及如何用 Python 对它们进行编程,然后再深入细节,这样 Python 就不会成为前进的限制因素。

Scikit learn [6]是一个支持预测分析的工具,它构建在 NumPy、SciPy 和 matplotlib 之上。许多数据科学家使用这个库来处理各种算法。

使用 scikit-learn 的一些常用方法是执行以下操作(几个示例,不限于):

  • 分类-SVM,随机森林
  • 回归-最近邻
  • 聚类-k-均值
  • 模型选择—网格搜索、交叉验证
  • 预处理—转换

当你在 Python 中使用这些公共库时,你会希望能够使用它们,并讨论它们应该如何与其他工程师一起工作,这就引出了我的下一个观点。

跨职能协作

照片由马文·迈耶在Unsplash【7】上拍摄。

成为一名数据科学家意味着你将不得不与几种不同类型的工程师一起工作,比如软件工程师和机器学习工程师。能够与他们交流非常重要。一种方法是通过 Python。通常,数据科学家可以更专注于算法和一些代码;然而,当他们向其他工程师展示协作代码时,代码可能会更混乱或不清楚——更侧重于研究和一次性导向。能够以可伸缩和易读的方式编写 Python 代码将使您更好地在更大的代码库中实现代码。

以下是 Python 的一些优势,这些优势增加了跨职能的协作:

  • 能够通过 Python 翻译数据科学方法
  • 一起协作编写 Python 代码
  • GitHub/Git 拉取请求
  • 代码库

能够与他人合作当然是一项很好的技能,当你不仅可以将这种合作应用于想法和概念,还可以应用于你用来构建模型的代码时,这就更重要了。

摘要

如您所见,掌握 Python 也是学习数据科学的关键一步。要成为一名伟大的数据科学家,你应该事先掌握一些关键概念和技能,比如统计学和数据分析。在本文中,我讨论了在学习数据科学之前想要掌握 Python 的一些关键原因。

这些原因总结如下:

*Object-Oriented Programming (*OOP*)Pandasscikit-learnCross-Functional Collaboration*

我希望你觉得我的文章既有趣又有用。如果你在成为一名数据科学家之前已经以某种方式学习了 Python,请在下面随意评论。这对你现在的数据科学事业有帮助吗?你同意还是不同意,为什么?

请随时查看我的个人资料和其他文章,也可以通过 LinkedIn 联系我。

下面是我写的一篇类似的文章,关于数据分析是数据科学的先决条件[8]:

*

参考

[1]2015 年钳工在 Unsplash 上的照片

[2]照片由 Markus Spiske 在 Unsplash 上拍摄,(2017)

[3]Pascal müller 在 Unsplash 上拍摄的照片,(2018)

[4]熊猫— NumFOCUS ,熊猫主页,(2021)

[5]照片由陈茂三潭在Unsplash(2021)上拍摄

[6] scikit-learn, sci-kit learn 主页,(2021)

[7]由 Marvin Meyer 在 Unsplash 上拍摄的照片,(2018)

[8] M.Przybyla,在成为数据科学家之前,你应该首先掌握数据分析,(2021)*

你不应该在 Python 中重复计算

原文:https://towardsdatascience.com/you-should-never-repeat-computing-in-python-b097d57cf661?source=collection_archive---------11-----------------------

图片来自 NastasyaDay 来自 Pixabay

Python 中的内置函数工具帮助我们缓存

如果你做编程的很多,你一定知道一个编程原则就是“不重复自己”。那是在编码层面。能概括的就不要重复了。

但是,我要讨论的话题是“不要重复计算”。也就是说,如果我们已经计算了某个东西,我们就不应该在另一个时间计算它。您可能会想到一个可以缓存结果的解决方案。是的,我们可以用任何编程语言实现缓存机制。然而,在 Python 中,这是现成的!

在本文中,我将介绍一个名为lru_cache的 Python 内置装饰器,它可以启用开箱即用的缓存机制。在动态编程、Web 开发等很多场景下都会超级有效。

什么是 LRU 缓存?

图片来自 Pixabay 的 Valdas Miskinis

TL;博士

如果您没有完全理解本节中的概念,请不要害怕。从下一节中的例子开始并不是一个坏主意。然后,回到这里你就没事了:)

在我们开始之前,需要澄清一些概念。我们今天要讨论的lru_cache是一个 Python 装饰器。如果你仍然不知道什么是室内设计师,请看看这篇文章。

然后,它涉及到 LRU 的概念。它代表“最近最少使用的”。当最近的调用是即将到来的调用的最佳预测时,LRU 缓存工作得最好。例如,虽然我已经写了近 100 篇关于数据科学的文章,但前 10 篇文章贡献了近 80%的总阅读量。如果我要写一个网站来托管这些文章,缓存这 10 篇文章是个好主意,这样大多数读者就可以以最佳性能加载它们。

缓存大小限制

如果我们不知道哪些文章是最受欢迎的呢?这意味着我们不能指定缓存哪一个。我们应该缓存所有内容吗?当然不是,因为缓存一切就意味着什么都不缓存。

在 Python 中,我们可以为 LRU 缓存指定一个缓存大小限制,这样它就不会无限制地增长。这对于长时间运行的进程(如 Web 服务器)非常重要。

lru_cache()采用一个名为maxsize的参数。我们可以使用这个参数来限制缓存大小。如果maxsize设置为无,缓存可以无限制增长。当maxsize是 2 的幂时,LRU 特性表现最佳。

什么时候我们不应该使用 LRU 缓存?

基本上,除非我们懒得加或者计算过程预计不会重复,否则我们总是可以用的。

但是,在这种情况下,我们不应该使用 LRU 缓存。缓存有副作用的函数、需要在每次调用时创建不同的可变对象的函数或者不纯的函数(如 time()或 random())是没有意义的。

一个基本例子

图片来自 Pixabay

太多干的东西。让我们用一个例子来演示在 Python 中使用 LRU 缓存是多么容易。

LRU 缓存内置于 Python 中。所以,我们不需要下载任何包,但是我们需要在使用之前导入函数。

from functools import lru_cache

然后,让我们用lru_cache作为装饰器来定义一个函数。

[@lru_cache](http://twitter.com/lru_cache)(maxsize=1)
def compute_something():
    return pow(123, 456)

我们定义的函数只是为了演示的目的而“计算一些东西”。这里我们计算 123 的 456 次方。这将是一个很大的数字,但无论如何,这并不重要。

重要的是我们增加了lru_cache作为函数的装饰器。同样,我们已经定义了maxsize=1。因此,缓存将只保留 1 个计算结果。这是有意义的,因为这个函数只有一个计算结果。

现在,让我们用计时统计运行这个函数。

%%timeit
compute_something()

结果显示,最慢的运行(第一次运行)比最快的运行慢 144 倍。此外,检测到某些内容可能被缓存。是的,没错。我们知道结果被 LRU 缓存器缓存了。因此,除了第一次运行之外,其余所有运行都将只是从缓存中获得结果,而不是再次计算它。

缓存信息

我们如何证实这一点?是的,我们可以。使用cache_info()可以检查所有 LRU 缓存启用的功能。

compute_something.cache_info()

缓存信息包含四个统计信息:

  • hits缓存被使用的次数。

因此,我们可以使用这个值来衡量缓存机制节省了多少次重新计算相同内容的时间。

  • misses缓存未被使用的次数。

在这种情况下,我们只有一次运行没有使用来自 LRU 缓存的结果。那是第一轮。

  • maxsize我们定义的缓存限制的大小。
  • currsize当前缓存的结果数。

清除缓存

如果有必要,我们还可以清除 LRU 的缓存。

compute_something.cache_clear()

清除后,所有缓存的结果都将被删除,缓存信息也将被重置。

递归和动态编程

图片来自 Pixabay 的 Hans Braxmeier

现在,让我们编造一些实际的例子。受益于 LRU 缓存的一个典型场景是递归函数。动态编程被认为比简单的递归函数更高级,但是我们必须手动实现缓存机制。有了 LRU 缓存,所有这些都不是问题。

让我们用一个基本且流行的递归问题——斐波那契递归。这是它的递归树。

图片提供:http://theory of programming . com/2015/03/02/dynamic-programming-introduction-and-Fibonacci-numbers/

你看到fib(6)功能的问题了吗?许多子功能被计算了不止一次。比如fib(2)被计算了 5 次。如果我们正在计算一个更大的斐波那契数,重复的时间将会成倍增加。

嗯,这是在一个单独的斐波那契数计算的范围内。如果我们正在计算更多呢?比如我们要计算从 1 到 10 的斐波那契数,重复计算的次数会更大。将会有大量的 CPU 和执行时间的浪费。

让我们做一个简单的实验。

computing_times = 0def fib(n):
    global computing_times
    computing_times += 1
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

在上面的代码中,我们定义了斐波那契递归函数。除此之外,我们还定义了一个全局变量computing_times来计算函数被调用的次数。

现在,让我们写一个 for 循环来计算从 1 到 10 的所有斐波那契数列。

for n in range(10):
    fib(n)

显示该函数被调用了 276 次!

现在,让我们将 LRU 缓存添加到函数中。我懒得计算我们需要多大的尺寸,所以我只想设置maxsize=None。这没问题,因为我们知道在这个演示中缓存大小不会疯狂增长:)

computing_times = 0[@lru_cache](http://twitter.com/lru_cache)(maxsize=None)
def fib(n):
    global computing_times
    computing_times += 1
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)for n in range(10):
    _ = fib(n)

哇,就 10 次!这很有意义,我们实际上只需要计算从 1 到 10 的斐波纳契数。所以,这是 10 倍。所有剩余的计算都可以跳过,因为它们被缓存了。

我们所做的就是向函数添加装饰器。就是这样!

[@lru_cache](http://twitter.com/lru_cache)(maxsize=None)

当然,我们可以检查缓存信息。它还记录了统计数据。

Web 请求处理

图片来自 Pixabay 的 Martinelle

另一个更实际的例子是 Web 开发。假设我们正在为一个 Web 服务开发一个后端服务器。因此,我们需要接收所有用户的请求,并根据请求做出响应。如果我们的大多数用户请求少量的资源,这将是一个完美的缓存场景。

让我们在这个演示中使用 Python PEP (Python 增强提案)。假设我们正在开发一个基于数字检索 PEP 文档的函数。

启用 LRU 缓存后,完整实现的功能如下。

import urllib.request
import urllib.error[@lru_cache](http://twitter.com/lru_cache)(maxsize=32)
def get_pep(num):
    'Retrieve text of a Python Enhancement Proposal'
    resource = f'[http://www.python.org/dev/peps/pep-{num:04d}/'](http://www.python.org/dev/peps/pep-{num:04d}/')
    try:
        with urllib.request.urlopen(resource) as s:
            return s.read()
    except urllib.error.HTTPError:
        return 'Not Found'

该函数以 PEP 编号为参数,从 PEP 官网检索。LRU 缓存启用了maxsize=32,因为这一次我们不希望缓存变得太大。我们假设大多数用户对一些重要的 PEP 文档感兴趣。

现在,让我们用 for 循环来模拟请求。

for n in 8, 290, 308, 320, 8, 218, 320, 279, 289, 320, 9991:
    pep = get_pep(n)
    print(f'PEP-{n}, Length: {len(pep)}')

因为我们只是在测试 LRU 缓存,所以我只想计算 HTML 页面上的字符数。

如截图所示。红色的是第一次计算的,而绿色的则利用了缓存的结果。

因此,在这 11 个请求中,有 3 个来自缓存。这意味着我们不需要向 URL 发送 GET 请求,获取 HTML 内容并计算字符数。结果只是从缓存中获得。

在这种情况下,LRU 缓存不仅节省了计算时间,还节省了网络流量的时间。这可以为我们节省大量资源,并以更快的方式为用户服务。

摘要

图片由克里斯托·阿涅斯特夫从皮克斯拜拍摄

在本文中,我介绍了 LRU 缓存,它是 Python 内置的functools模块中的一个工具。它可以缓存函数的结果以防止重复计算。

这两个实例表明,LRU 缓存在递归函数、动态编程以及 Web 服务等需要重复的场景中有很大的价值。正确使用 LRU 缓存将节省我们大量的硬件资源和执行时间。

https://medium.com/@qiuyujx/membership

如果你觉得我的文章有帮助,请考虑加入 Medium 会员来支持我和其他成千上万的作者!(点击上面的链接)

如果你想成为一名数据科学家,你应该学习这些课程。

原文:https://towardsdatascience.com/you-should-take-these-courses-if-you-want-to-become-a-data-scientist-f7e1e1da601b?source=collection_archive---------21-----------------------

意见

…原因如下。

约翰·施诺布里奇在Unsplash【1】上拍摄的照片。

目录

  1. 介绍
  2. 自然语言处理
  3. 商业智能
  4. 机器学习操作
  5. 摘要
  6. 参考

介绍

虽然有无数与特定大学相关的有影响力的课程,但我想讨论你应该寻找的三个包含性课程,以及确保你的数据科学计划。这些课程代表了数据科学的关键主题。尽快获得这些方面的经验很重要,这样你才能知道你想成为什么类型的数据科学家。这个职业非常模糊和宽泛,所以缩小你的数据科学工作类型是很重要的,宜早不宜迟。我将专注于我个人经历过或了解过的课程,我认为这些课程应该有助于您成为一名数据科学家,更具体地说,就是您想要成为的那种数据科学家。虽然我将讨论的这三门课程对我来说很重要,但同样重要的是要注意,这些课程可能不是成为数据科学家所需的全部课程,但希望它们能让你了解你可以从事的数据科学职位的范围。精通这些课程中的任何一门都会让你成为这个职位的专家,但是,如果你掌握了这三门课程,你肯定会成为一名更全面的数据科学家。也就是说,我将在下面讨论每门课程的应用和用例示例。

自然语言处理

约翰·施诺布里希在Unsplash【2】上的照片。

如果你想成为一名数据科学家,参加一门自然语言处理课程,也称为 NLP 。主要原因是数据科学的这一方面非常独特,但对众多行业都很重要。拥有大量文本数据的公司很可能会要求你学习这门课程。即使只是一门课,你也能运用很多这些技巧。真正接触 NLP 是至关重要的,因为之后您可以将一般的数据科学实践应用到更多的应用程序中。

以下是一些在学习了相应课程后自然语言处理应用的示例:

  • 情感分析:用于分配给较大文本文档或产品评论的正面或负面情感的自动化
  • 聊天机器人:为了更好的分流客户,更好的客户关系
  • 主题建模:理解文档的主题以及它们在所有主题中的表现
  • 文本分类:根据文本特征对产品进行分类
  • 文本标记:根据关键词自动删除有害的多余评论或评论

这些只是学习 NLP 中一门课程的一些用例。当然,还有更多的用例,而且在 NLP 中很容易选择不止一门课程。一旦你实践了 NLP 概念,你就可以相当快地应用它们,这也将是一些数据科学职位的主要要求之一。然而,并不是每个数据科学家都需要了解 NLP,所以如果你想专注于数据科学的不同方面,你可以探索其他专业,如商业智能。

商业智能

Tyler Franta 在Unsplash【3】上拍摄的照片。

数据科学的这一方面经常与严格意义上的商业智能分析师角色相混淆,但您会看到一些公司重申数据科学家可能纯粹专注于商业智能。例如,脸书倾向于给数据科学家职位命名,这些职位的主要要求是定义商业智能分析师的素质,但也有更多的机器学习算法要求。因此,当你成为一名专业的数据科学家时,拥有一门明确以数据科学为重点的商业智能课程会非常有价值,Tableau 或 SQL 等课程或证书也同样重要。

一些大学或项目甚至提供数据科学方面的商业智能或商业分析师专业,因为企业的需求已经变得更加明确。例如,并不是每个公司都想要一个知道每个机器学习算法如何工作及其背后的数学的数据科学家,但更多的是专注于应用一个简单的模型,理解它,最重要的是——能够使用可视化向利益相关者解释清楚。

以下是一些参加相关课程后的商业智能应用示例( SMU 商业分析数据科学专业 ) [4]:

  • Tableau reporting: 创建仪表板来说明模型结果,以便公司中的更多人能够理解
  • SQL 查询:一些大学或课程不包括任何 SQL 实践,因此开设商业智能课程很可能会让你开始变得更加精通 SQL。您将能够查询表格,执行并非每个数据科学家都能做到的复杂功能。
  • ARIMA/萨里玛预测: ( 季节性)经济金融业务自回归综合移动平均建模
  • 供应和需求预测:可应用于各种行业,但在商业智能领域,能够将这一概念应用于更传统的数据科学(如决策树分类)是一个重要焦点
  • 与利益相关者的沟通:一些数据科学职位更低调,但是,如果你想更多地关注商业智能,你将拥有产品经理所拥有的一些素质,在这些素质中,你需要组织、更新和向利益相关者传达模型的影响

如您所见,商业智能课程应用程序侧重于报告、预测和沟通。如果这是你的强项,那么你将会成为商业智能领域的数据科学家。

机器学习操作

照片由马克西米利安·魏斯贝克尔在Unsplash【5】上拍摄。

虽然理解机器学习算法对于成为一名数据科学家来说当然至关重要,但参加机器学习 操作 课程也可能相当重要。

数据科学中的许多教育集中在这些算法上,但是没有将经验应用到这些算法的操作上。

例如,很多时候,数据科学家在完成一个程序时会有过多的算法经验,但当需要在业务基础设施中将模型实现到生产中时,这可能会非常令人生畏,您可能根本不知道如何去做。幸运的是,一些团队有机器学习工程师,他们专注于模型的部署,但不是每个公司都这么大,你可能有责任了解模型的整个生命周期,并能够不仅在 Jupyter 笔记本上,而且在部署平台上制作它。

以下是参加相应课程后机器学习操作应用的一些示例:

  • 自动部署:能够将预测提供给到达最终用户的表格
  • 质量测试模型:测试模型,不是根据准确性,而是为了质量保证,因为模型不会经历任何可能以编程方式失败的情况
  • 代码模块化:将凌乱的笔记本变成遵循干净高效的面向对象编程的代码库
  • 部署平台: Azure Machine Learning、Amazon SageMaker、MLflow、Google Cloud 以及其他可以帮助部署您的模型的平台

应用程序关注的是在模型被研究并最终构建之后会发生什么。模型的操作对成功的数据科学过程极其重要。重点还在于模型的代码,而不一定是算法背后的统计和数学。

摘要

无论你是想专攻上述课程或数据科学的某个方面,还是想成为一名全面发展的数据科学家,所有这些课程在某种程度上都证明是非常重要的。数据科学的其他几个方面也应该探索和掌握,所以请随意讨论您在教育或职业生涯中强调的那些方面。

总而言之,以下是成为数据科学家应该学习的一些课程:

Natural Language ProcessingBusiness IntelligenceMachine Learning Operations

我选择这些特定课程的主要原因是它们都非常不同,因此它们涵盖了广泛的数据科学。

我希望你觉得我的文章既有趣又有用。如果你学过这些课程,请在下面随意评论。这对你现在的数据科学事业有帮助吗?你同意还是不同意,为什么?

请随时查看我的个人资料和其他文章,也可以通过 LinkedIn 联系我。

参考

[1]约翰·施诺布里奇在 Unsplash 上拍摄的照片,(2018)

[2]照片由约翰·施诺布里奇在Unsplash(2020)拍摄

[3]照片由泰勒·弗兰塔在 Unsplash 上拍摄,(2018)

[4]南卫理公会大学, SMU 商业分析数据科学专业,(2021)

[5]Maximilian weis Becker 在 Unsplash 上拍摄的照片,(2018)

你的人工智能和你的数据质量一样好!

原文:https://towardsdatascience.com/your-ai-is-only-as-good-as-your-data-quality-42be9ab533b9?source=collection_archive---------16-----------------------

数据准备在模型创建过程中的重要性

梁杰森在 Unsplash 上的照片

介绍

许多人普遍的误解是,人工智能是一个神奇的黑匣子,它可以把你的数据变成黄金!这是部分正确的。人工智能确实能把你的数据变成金子(虽然不是字面上的),只要你给它提供高质量的数据!但在当今世界,高质量的数据只是虚构的。那么如何克服这一点呢?你如何将“高质量”的数据输入到你的模型中,以便你可以获得人工智能的所有好处?这正是数据准备过程的切入点。但是在我们进入有趣的部分之前,到底什么是“坏数据”?

照片由德米特里·维乔科在 Unsplash 拍摄

现实世界完全是一团糟。它生成的数据也是如此。它有各种形状和形式,从几行文本,可能是名人的推文或愤怒的客户的评论,到几百万行数字,可能是国家人口普查或来自物联网设备的传感器数据。也可能是介于两者之间的任何东西。由于数据源如此庞大,数据被包装和交付的可能性很小。没有。许多不一致的地方出现了。文本中可能会有一些表情符号或一些速记缩写,如 gm、np、lol 等。(感谢 Twitter 上 280 个字符的限制)。调查中也可能存在一些人为错误,因为犯错是我们的天性,不是吗?可能有一些数据丢失,因为一些绅士不想透露他的年龄,或者可能有一些错误的数据,因为一些女士减少了她的年龄几年!所以你看,不一致的数据是常见的,无论何时开始一个数据项目,你都可以预期某种形式的“坏数据”,除非你从 Kaggle 下载一个接近完美的数据集(无意冒犯!).在我们想到准备任何花哨的模型之前,准备好数据是我们的责任。那我们该怎么办?嗯,我们准备数据!

数据准备涉及的步骤

Bonnie Kittle 在 Unsplash 上的照片

你可能已经听过一千遍了,但是让我再告诉你一遍。数据准备是任何数据项目中最关键的阶段,占据了项目时间的 70%!但是我们在数据准备方面到底是怎么做的呢?不可能有一根魔杖能神奇地为你准备好数据,对吗?是的,我希望有,但是没有。那么在数据准备阶段会发生什么呢?嗯,在准备数据时,我们需要遵循几个步骤。

数据清理

这是数据准备阶段的第一步。在我们考虑下一步之前,我们需要确保数据是干净的。我们不想要任何缺失数据、异常值或有噪声的数据。在这个阶段,我们做的工作包括处理缺失值、处理有噪声的数据和异常值,以及删除任何不需要的数据。这些步骤中的每一步都有自己独立的步骤,并伴随着各自的挑战。让我们逐一了解一下。

处理异常值

威尔·梅尔斯在 Unsplash 上拍照

在统计学中,离群值是指与数据集中的其他数据观察值显著不同的数据点。异常值可能是由多种原因造成的。然而,正确处理异常值是很重要的,因为它们可能是数据的重要部分,也可能只是收集数据时的一些错误。例如,在收集公司的工资信息时,高级管理人员的工资可能比公司的其他成员高得多。这个数据点显然是一个异常值,因为这样的情况很少。但是这个数据点对于分析是至关重要的,因为它是完全有效的,并且是整个数据的重要部分。在类似的情况下,当收集中学生的身高数据时,55.6 英尺的值。是非常不可能的。它最初可能是 5.56 英尺。但是由于收集数据过程中的一个错误,我们的整个分析将会完全被搞砸!那么我们如何处理离群值呢?有一些技术可以处理异常值。然而,了解域以识别离群值是有效还是无效是很重要的。为了处理异常值,首先识别异常值是很重要的。

可以通过绘制直方图或执行聚类分析来识别异常值。您也可以绘制一个箱线图来找出异常值。通常,当数据点的大于(1.5 倍 IQR) +第三个四分位数或小于(1.5 倍 IQR)-第一个四分位数时,该数据点被视为异常值。

现在我们已经确定了异常值,接下来我们做什么?两种常见的做法是要么移除这些异常值,要么使用某种变换函数(如 log 或 box-cox 变换函数)来平滑它们。但是我们在选择删除异常值时应该小心,因为它们可能是数据的有效观察值。一点声音领域的知识有助于在这件事上选择适当的步骤。

处理丢失的值

伊琳娜在 Unsplash 上的照片

当数据集中没有存储变量的数据值时,就会出现值缺失。就像离群值一样,这些可能是由各种原因造成的。可能是研究人员/勘测人员的错误,或者是用于收集数据的仪器的错误。不管是什么原因,缺少值可能是一个严重的问题,必须解决。为什么?一些最大似然模型不能处理缺失数据。但是与异常值不同,识别缺失值非常简单。但是我们处理这些缺失的值会有所不同。当然有一些处理缺失数据的标准技术,但是在您的情况下选择哪种技术需要一些领域知识(这里,我再说一遍)。事不宜迟,让我们看看这些“标准”技术。

1。删除缺少值的行

这是所有其他技术中最简单的。但是只有当缺失值非常低的记录时才是可行的。如果缺少值的记录数量很大,简单地删除它们会导致整个数据集中的大量数据丢失。

2。用一个全局常数填充它们

另一种常用于填充缺失值的技术是使用一个全局常数,比如零(0)。只有当无法正确预测缺失值时,才会这样做。当上下文已知时,缺少的值也可以用 0 填充(有时不是将值记录为零,而是将它们记录为空值)。

3。用平均值或中间值填充缺失值

这也是准备数据时的常见做法。缺失值用相关特定属性的平均值或中值填充。然而,这种方法的缺点是它忽略了数据集中其他属性的上下文。

4。使用向前或向后填充

缺少的值用上一行或下一行的值填充。这种技术称为前向填充(ffil)或后向填充(bfil)。

5。创建一个模型来填充缺失值

这是最有效的,但也是最复杂的技术。在这项技术中,创建了一个机器学习模型来从数据中学习模式并预测缺失值。

处理数据冗余

约尔根·哈兰在 Unsplash 上的照片

在现实世界中,一个数据库包含数百个相互链接的表。此外,在收集所需数据时,通常会使用许多数据源(将在后续步骤中讨论)。在这种情况下,有可能出现数据冗余。但是首先,什么是数据冗余?数据冗余是指相同的信息或数据存储在多个位置的现象。例如,当连接两个表(雇员表和薪水表)时,这两个表都有雇员 ID,可以在结果表中创建相同信息(雇员 ID)的重复列。在这种情况下,必须识别并删除这种冗余数据。相关性分析有助于识别冗余数据。

数据集成

照片由派恩瓦特在 Unsplash 拍摄

在这个数字时代,到处都在收集数据。从你的谷歌搜索到你佩戴的健身腕带上的心率,数据无处不在。理想情况下,任何企业都应该拥有来自多个来源的数据。例如,一个电子商务网站可以在自己的数据库中保存客户的详细信息。他们可能会并行地使用 CRM 工具来管理他们的客户与其网站的交互。他们也可能使用不同的工具来提供客户支持。他们将拥有一个用于数字支付的支付网关。从所有这些不同的来源收集不同类型的数据。为了更进一步,你还可以利用 Twitter 的 tweets,在那里有人会提到这家商店。为了全面了解公司的情况,拥有 360 度的数据视图非常重要。虽然您需要的所有数据都已经存在,但并没有统一的视图。在为 ML 模型准备数据时,您通常需要将来自所有这些不同数据源的所有数据集成到一个单一的数据形式中。将不同来源的数据组合起来的数据准备阶段称为数据集成。

数据转换

桑迪·米勒在 Unsplash 上拍摄的照片

在将数据输入 ML 模型之前,进行必要的转换是很重要的。数据转换中也有一些技术或方法。

聚合

数据聚合是以汇总格式存储数据的过程。从几个来源收集的数据首先被整合,然后数据聚合将在任何需要的地方执行。这是数据转换过程中的重要一步,因为模型的质量最终取决于数据的质量(这正是本文的标题!).

[数]离散化

数据被转换成一组有限的间隔。例如,年龄属性可以离散成有限的区间,如(1–13)、(13–19)、(20–30)等。,或(儿童、青少年、年轻人)。宁滨也经常被用作离散化技术。离散化可用于显著提高模型的效率。

属性创建

通常,新的数据属性是通过使用数据集中已有的属性来创建的。例如,从现有的出生日期属性派生出年龄属性。这需要一段完善的领域知识(啊,我又说了吗?!).

正常化

这是数据转换过程的一个关键部分,因为真实世界的数据通常会在不同的尺度上进行测量。举个常见的例子,一个人的高度是用英尺来衡量的,而一条路的长度是用英里或公里来衡量的。对数据集中的所有属性进行规范化的过程称为规范化。然而,所有的规范化需求都不会这么简单。有几种归一化技术,如最小-最大归一化、z 值归一化等。

最后的想法

数据准备是创建机器学习或人工智能模型过程中的关键步骤。正如标题所暗示的和上面所讨论的,你的人工智能的质量完全取决于你提供给它的数据的质量。正如本文多次讨论的那样,对领域的扎实知识对于成功准备数据至关重要。

原载于 2021 年 5 月 14 日 https://praneethvasarla.com**的 我的博客

你的人工智能模型是“错误的”,但它可以改变你的业务

原文:https://towardsdatascience.com/your-ai-model-is-wrong-yet-it-can-transform-your-business-90dd48cd467e?source=collection_archive---------23-----------------------

欧文·坎普在 Unsplash 上的照片

为什么模型准确性被高估,如何说服你的经理

二十多年来,网飞一直痴迷于机器学习模型。

2006 年,该公司宣布向任何能够将其推荐算法的准确率提高 10%的人颁发百万美元奖金。超过 40,000 支队伍参加了全球挑战赛。

这项比赛持续了三年,只有两个团队成功超越了准确度门槛。网飞奖励了一名准确度提高了 10.06%的获胜者。

但是,他们抛弃了获胜的算法。

尽管精确度极高,但这种算法的工程成本和复杂性非常高,对于精确度的提高来说太高了。相反,网飞使用了一种排名较低但更简单、成本更低的算法,其准确率仅提高了 8.43%。

值得注意的是,这场历时 3 年的算法准确性马拉松结束时,网飞的商业模式已经发生了转变。焦点已经从 DVD 租赁转移到流媒体,因此减少了这些算法的效用。

今天,许多追求机器学习(ML)的组织都是准确性谬误的受害者。他们狭隘地专注于模型准确性,同时在过度优化中浪费宝贵的资源。商业领袖已经搁置了不能提供很高准确性的好的解决方案。

这对企业来说是巨大的机会损失。你的人工智能/人工智能模型可能不准确,但它仍然可以让你的业务变得更好。在这篇文章中,你将学习四种方法来确定你的模式是否能为你的企业带来变革

Marc A 在 Unsplash 上拍摄的照片

什么影响机器学习模型的准确性?

许多因素影响模型的准确性,但其中四个是关键的,安斯泰拉制药营销科学副总监瑞安·摩尔说。这四个因素是:

a)我们是否包括了所有可能影响结果的相关输入?

b)我们收集的历史数据质量如何?

c)我们的模型是否捕捉到了输入和输出之间的真实关系?

d)未来场景与用于训练模型的场景有多大不同?

您可以看到,模型准确性是一系列不太完美的连接中的最后一环。理解模型最多只能产生结果的近似值对于充分利用它们至关重要。

在给定的情况下,您的数据科学团队必须通过优化这些因素来努力提高准确性。一旦你有了一个机器学习解决方案,你必须评估它的商业影响。

由 Unsplash 上的 Nadir sYzYgY 拍摄

1.根据人的表现来衡量模型的准确性

《可预测的非理性》一书的作者丹·艾瑞里说:“大多数人不知道他们想要什么,除非他们在上下文中看到它。”。我们人类无法孤立地理解一个数字。

这是模型准确性的挑战。当用百分比表示时,人们希望它尽可能高,就好像这些是学校成绩一样。沃顿商学院的 Berkeley Dietvorst 说,我们最终在一个机器犯错的世界和一个从不犯错的世界之间做了一个错误的比较。

要了解模型的准确性,请将其与您组织内相同任务的人为错误率进行比较。假设你工作的人类准确率是 81%。一个 78%准确的模型距离基准只差 3%;不会差 22%的。

2.通过用人类智能增强模型来改善结果

组织经常让人工智能与人类对抗,就好像这是一场竞赛。当然,为了了解人工智能的状态,将人工智能模型的性能与人类的准确性进行对比是有用的。但是所有的比较都应该到此为止。与其让他们互相竞争,为什么不把这两个玩家带到同一个团队呢?

增强智能是一种以人为中心的合作伙伴关系,将人和人工智能结合在一起,以增强认知性能。在疫情期间,我们已经看到了增强智能的伟大例子。

在患者与医疗服务提供者的联系成为一个关键问题的时候,机器人辅助手术拯救了生命。《自然》报道了外科医生如何使用磁导航系统(MNS)支持的系统进行远程血管内手术。该系统不仅提供了合理的控制和灵活性,而且增加了程序的安全性。

为了改善结果,在设计机器学习系统时,你必须让人类参与进来。在由于缺乏足够的数据或高度的情境变化而导致模型准确性受损的情况下,人类可以介入提供支持。

当人和模型结合在一起时,所达到的净精度远远大于单独使用任何一个所能达到的精度。

照片由 Michael Dziedzic 在 Unsplash

3.检查模型是否有持续改进的空间

算法是了不起的学习者。当他们看到更多的数据时,他们会改进。当你给他们具体的反馈,告诉他们哪里做错了,他们就会进步。它们随着人工智能研究的进展而改进。

当我们做出金融投资决策时,我们计算的是钱的未来价值。但是,我们不会用算法精度来做这件事。相反,我们以表面价值评估它们。

在过去的 50 年里,蛋白质结构的预测一直是科学界最艰难的挑战之一。2018 年,CASP 竞赛(结构预测的关键评估)见证了 AI 的有效应用。由 DeepMind 构建的人工智能系统 AlphaFold 取得了惊人的成绩,分数比上一版提高了 10 多分。2020 年,AlphaFold 通过在短短两年内将分数大幅提高至 92.4,解决了这一重大挑战。

为了了解你的模型的真正潜力,考虑你通过更多数据、更好的反馈或人工智能的研究进展可能获得的好处。

4.计算模型结果的商业价值

前面的三个步骤解释了如何将您的准确性联系起来,通过增强来提高它,并预测未来的价值。现在,最后也是最关键的一步是量化和衡量你的业务成果。

不,你的 ML 模型不一定要完美到能带来商业价值。国际分析研究所副总裁德鲁·史密斯说,通常基线很低,即使 1%的改善也能产生巨大的商业收益。

在家具巨头宜家任职期间,史密斯报告称,排长队是顾客去别处购物的第三大原因。流量分析模型有助于预测商店中会形成的队列。

虽然算法并不完美,但史密斯说,预测比经理的直觉好得多。这使得商店团队能够建立队列破坏实践,从而减少了 30%的队列投诉。这被证明是卓越客户服务的无价工具。

问问自己,你的机器学习模型的真正目的是什么。

它应该帮助你增加收入还是降低成本?将净准确性改进转化为模型将交付的业务影响。量化影响,这将帮助您将对话从准确性转移到业务成果。

通过渐进式模型改进实现业务转型

照片由 Jungwoo Hong 在 Unsplash 上拍摄

为了解决与商业领袖在人工智能模型准确性上的脱节,你必须同情、教育他们,并与他们接触。

Smith 建议“这是一个发现更多关于利益相关者、他们的挑战以及他们想要抓住的机会的绝佳机会。我很好奇他们为什么要追求如此高的精度。”

教育他们机器学习模型的目的是什么。解释影响准确性的因素,以及如何实际管理这些因素来推动结果。

最后,与他们接触。“透明、客观地评估与利益相关者的权衡,以达成一致。摩尔说:“追求商业价值最大化的道路。”。

史密斯补充说,专注于快速、持续改善许多业务领域的团队,将会击败旨在完美解决一个大问题的团队。

网飞是一个依靠渐进式创新繁荣发展的组织的优秀范例。由于过去 20 年人工智能模型的持续微小改进,人们在网飞观看的 80%以上的视频是通过他们的推荐系统发现的。

这篇文章最早发表于《福布斯》上的https://www.forbes.com/sites/ganeskesari/2021/01/21/accuracy-isnt-everything-how-even-a-wrong-ai-model-can-transform-your-business/。增加了插图。

你的云并没有想象中的那么智能

原文:https://towardsdatascience.com/your-cloud-isnt-as-smart-as-it-could-be-9f700ae1ace9?source=collection_archive---------37-----------------------

面向云和 IT 基础架构的 AIOps 可以实现企业和任务关键型功能的智能运营

面向云和 IT 基础设施的 AIOps 可以实现企业和任务关键型功能的智能运营(图片由 Unsplash 上的 Markus Winkler 拍摄)

在更广泛的云市场中,超大规模数据中心、云服务提供商(CSP)以及商业和公共部门中公共云上的总体工作负载采用率持续增长。云的采用在过去几年中稳步增长,据估计,虽然 75%的工作负载保留在传统 it 和私有云环境中,但现在 25%的企业工作负载通过传统现代化和迁移以及云原生数字产品开发和工程驻留在公共云中(1)。公共云的采用仍在继续增长,但我们仍有很长的路要走,以继续采用、开发和发展云运营

然而,随着遗留应用程序的现代化和数据迁移,遗留的工作方式和操作结构也能够并且已经在云中复制它们自己。许多经典的 IT 运营流程、治理结构&财务管理方法已经从数据中心复制到私有云和公共云中。随着传统 IT 基础架构和云原生环境中异构 IT 环境的复杂性增加,IT 运营的负担也在增加。IT 部门必须管理的大量企业、运营和以任务为中心的数据,首席信息官不断要求用更少的资源做更多的事情,以及从传统安全模式向新一代安全模式的转变,这些都导致了运营效率低下(2)。

这严重限制了您的云& IT 基础设施环境的潜力。真正的云原生产品开发和运营的基础可以而且应该通过智能运营(也称为 AIOps)来增强。在技术堆栈的每一层和 IT 运营生命周期的每一个阶段整合 AIOps,可以增强您的云和 IT 基础架构环境的可预测性和智能性,甚至可以实现环境的“零接触”(例如自动修复和自我修复)。

什么是 AIOps?

AIOps(也称为 MLOps,取决于供应商和使用案例)中有一套快速增长的工具和方法来解决这些问题,这是一种利用大数据、高级分析和机器学习来增强和自动化 IT 运营和监控的方法,可实现 IT 环境的智能运营。虽然还处于早期,但 it 领导者已经开始在自己的职能部门中采用机器学习(ML)技术,以提高企业技术的运营效率并降低成本。在过去的几年中,许多供应商已经开始设计和开发强大的分析工具,以解决 IT 人员在管理、更新和运行整个企业的 IT 硬件和软件时面临的特殊挑战(3)。

核心 AIOps 通常涵盖软件开发生命周期(SDLC)关键领域的人工智能和机器学习(AI/ML)用例,包括:应用性能监控、IT 基础设施监控、IT 事件关联和分析以及网络性能监控和诊断。

云服务提供商(CSP)和独立软件供应商(ISV)带来了哪些功能?

在这一领域,工具和供应商驱动的促成因素混合在一起,再加上高度定制的方法,用于聚合运营数据并推动对每个 AIOps 用例的差异化洞察。一些亮点包括:

云服务提供商(CSP)。

更一般地说,AWS、GCP 和 Azure 高度支持将 AIOps 功能引入公共云生态系统的关键 ISV,并与之良好集成。

虽然许多本机工具通常是集成的,而不是本机的,但公共云服务的本机配置(例如 CI/CD 管道、数据服务)可以让您在这个领域走得更远。查看 GCP MLOps 、 Azure MLOps 和 AWS MLOps (以及 AWS Sagemaker ),在引入集成工具和服务之前,了解本地 CSP 服务及其嵌入式机器学习技术的可能性。

ISV 和商用现货(COTS)供应商。

Splunk “无所不包的数据平台”,以日志管理而闻名,已经扩展了它的平台,因为它是 AIOps 用例的逻辑事件和数据源。Splunk 机器学习工具包和用于 it 运营的人工智能都提供了启动套件和工具,以便在 Splunk 中本地运行。

Moogsoft 更像是一个专门为 AIOps 支持而设计的原生平台,集成了许多众所周知的工具和数据源,并支持下面详述的许多用例。Moogsoft 提供了一个企业级的云原生平台,使客户能够按照自己的速度以低得多的成本推动采用,根据他们的网站。

开放标准&开源遗产。

Elastic,ELK Stack的创建者,是已经扩展到 AIOps 功能的“开源遗产”解决方案的众多例子之一。Elastic 支持许多机器学习用例,包括来自事件源数据的异常检测。

信息技术服务管理(ITSM)平台。

总的来说,ITSM 平台和服务运营对于释放 AIOps 用例及功能的过程和数据是绝对重要的。两家主要的 ITSM 领先厂商正在提供现成的功能,分别是 ServiceNow AIOps 和 BMC AIOps 。

我如何在我的云和 IT 基础架构环境中实现 AIOps 并增加智能运营?

打好运营基础。

IT 服务管理(ITSM)和运营。也许这是另一天的专门文章,但是现代 ITSM 实践和操作对于 AIOps 是至关重要的和基础的,缺乏用于端到端 IT 过程的集成的、自动化的 ITIL 功能通常是将 AIOps 附加和集成到您的生态系统中以改善环境的根本原因。AIOps 应支持持续的服务改进和持续的反馈循环,以提高您的 ITSM 平台和 ITIL 流程的运营效率。

提高您环境的可见性和透明度。

确定你最初的 AIOps 战略。首先,确定您的目标是否是离散的使用案例和机会领域(例如,异常检测、降噪、事件预测、自动化问题补救),或者这是否将是更广泛的实验和发现工作,以确定更集中的深入探索和使用案例开发的机会领域。如果是前者,这种努力可以很快针对你需要获取的数据的性质和你试图解决的问题。如果是后者,该计划应该更多地以试点的方式进行试验,并通过广泛撒网来确定在您的环境中哪里有价值证据。

进行有针对性的初始数据采集。现在,您已经了解了您要解决的问题(或者想要确定它可能是什么),您可以将重点放在您需要作为数据捕获目标的工具、数据和基础架构堆栈层上。下图为您的独特输入以及它们将如何影响库存、静态配置和动态数据元素提供了一个很好的框架,这些数据元素在任何给定的用例中都非常适合捕获:

图 1:数据采集应该高度定制,以适应用例需求,以及客户数据可用性、保真度和唯一标准

迭代建立和训练数据模型。这种数据捕获为识别价值证据建立了一个起点。你创造了“干草堆”……现在你要找到“针”!

图 2:数据流和流程模型阐明了如何构建和运行端到端 AIOps 生命周期;方法是不可知的,而技术是特定于用例的

现在,通过获取这些摄取的原始数据资产并将其转换为应用的机器学习技术的输入变量,包括错误和事故计数、IT 运营数据中的阈值和趋势等,可以实现特征工程。

这些输入变量将为某些用例提供机会。例如,标准化的错误和事件数据可能会围绕异常检测用例提出价值证明,用于检测偏离正常操作基础架构和应用程序行为的早期迹象。然后,您可以选择某些分类和机器学习方法(例如隔离森林、聚类方法)来进一步细化数据,生成异常分数或其他可衡量的洞察单位,并生成一个有意的反馈循环来进一步细化和训练模型,以识别真正的故障事件并减少误报和操作。可能需要多次迭代、敏捷的改进周期,以将数据、模型和洞察力锐化为真正有价值的东西,从而推动决策制定,并最终自动处理您的云和 IT 基础架构环境中已识别的异常。

这是一个更加详细的视图,一步一步地描绘了一个在您的环境中完全启用的用例对您的影响:

图 3:一个更加详细的、由外向内的存储峰值异常检测用例示例,展示了 AI/ML 如何成为现实

降低噪音。

将可见性转化为发现,从而加深对问题的理解。这种强化数据和调查结果的迭代过程将产生“降噪”效果,通过消除假阳性和/或假阴性,揭示您环境中真正可操作的内容,或为某些行动提供信息,从而识别真正重要的内容。这些见解涵盖了广泛的核心用例,包括:

降噪:降噪行为本身是分析数据和警报模式的基础,并随后过滤掉误报,以便实现下面更进步的用例

分类和警报关联:集群和关联将您的云 IT 基础架构环境的各个部分转变为单个离散事件,以加快分类过程并提供透明度

服务影响分析:绘制跨已识别事件集群的依赖关系映射,以分析 IT 故障、中断和其他中断事件在技术和业务方面的下游或级联影响

根本原因分析:识别并推断应用程序和云/ IT 基础架构层中已实现中断事件的根本原因

预测:预测未来基础设施能力的应用需求&需求分配

事件预测:转换整个 IT 环境中检测到的异常,以预测和预防未来的故障

…“扩展”用例可能涉及将这些核心示例集成到 DevOps / DevSecOps 管道、业务成果、IT 服务管理(ITIL 模块和流程)以及核心网络和安全运营(NOC/SOC)中。

制定明智的决策和自动化的行动。

利用对问题的深刻理解提出建议,为决策提供信息,甚至立即采取自动补救措施。好的,我对我的企业环境有了更多的了解,所有这些都很棒…我实际上可以做些什么来将数据转化为可执行的东西。我是否可以更进一步,将数据分解为任务,在一个无需干预的自主环境中实现自动化。潜力肯定是有的!一些关键的行动用例包括:

补救建议(手动加速):使用机器学习技术开发“专家”建议系统,使 IT 运营部门能够更轻松地对事件响应进行故障排除

自动化问题补救(自动化&流程编排):在发生事故时自动触发解决流程,也称为“自我修复”

当我将所有的技术和流程组件放置到位时,这会是什么样子?

下面是一个指示性的参考架构,说明您的环境可能包括事件和数据源、用于识别和细化 AIOps 使用情形的自定义聚合器和数据湖、下游可见性和操作处理方法,以及用于不断改进您的云和 IT 基础架构环境和服务运营功能的反馈循环:

图 4:IT 基础设施 AIOps 的示例性环境架构

…这可以构建在任何内部环境、公共 CSP 环境中,甚至可以分布在混合和云计算生态系统中,具体取决于您的环境状况和目标使用情形。

这应该提供了一个高层次的概述,介绍了 AIOps 的可能性,以及如何利用异构、复杂的环境,将来自环境传感器的信号分解为一个真实、自主、持续的反馈循环,以改善企业云和 IT 基础架构的运营。虽然还处于初期阶段,但工具、功能、数据和方法都已具备,可以将您的云从初始运营能力转变为“智能云”。

如果您认为这篇文章信息丰富,也请查看 BCG 对 AIOps 市场和使用案例的更广泛的观点,以及我对美国联邦政府内的 AIOps 机会的观点,从行业特定的角度来实现 AIOps。


Matthew Leybold 是波士顿咨询集团(Boston Consulting Group)驻纽约的副总监,负责 BCG Platinion 北美分公司的云计算和 IT 基础设施主题以及公共部门。

引用的参考文献:

1.Gartner 预测 2019 年。https://www . Gartner . com/en/documents/3895580/predicts-2019-increasing-reliance-on-cloud-computing-tra

2.证明联邦政府 AIOps 的效率。GCN。https://gcn.com/articles/2020/10/14/aiops-efficiencies.aspx

3.不管准备好了没有,人工智能正在走向 IT 运营。卡介苗。https://www . BCG . com/en-us/publications/2019/artificial-intelligence-coming-information-technology-operations

你的彩色地图是坏的,这里是如何修复它

原文:https://towardsdatascience.com/your-colour-map-is-bad-heres-how-to-fix-it-lessons-learnt-from-the-event-horizon-telescope-b82523f09469?source=collection_archive---------28-----------------------

从视界望远镜中吸取的教训

不同的科学彩色地图应用于 GRHD 模拟的结果(见https://arxiv.org/ftp/arxiv/papers/1906/1906.11241.pdf)

你还记得那些模糊的 excel 表格和不可读的颜色的文件吗?没有吗?这很正常,没有人会。即使内容是王道,演示文稿也能激励读者阅读你讲述的故事。

一个重要的因素是信息的颜色编码有多好。数据的准确表示是至关重要的。许多常见的彩色地图通过不均匀的颜色梯度扭曲数据,对于色觉缺陷的人来说通常是不可读的。一个臭名昭著的例子是喷射彩色地图。这些彩色地图没有呈现你想要说明的所有信息,甚至更糟的是通过人工制品呈现虚假信息。不管你是不是科学家,你的目标是以最准确、最吸引人的方式传达视觉信息。此外,不要忽视色觉缺陷,这代表了 8%的(高加索)男性人口。

你会为你的演示选择什么颜色的地图?丑陋的喷射还是更平滑的热喷射?参见 https://github.com/ThomasBury/scicomap 中的

🗺色彩空间

色彩空间是色彩的特定组织,确保色彩的再现。主要原则如下。

感知一致性是指颜色空间中颜色之间的欧几里德距离应该与人类颜色感知距离判断相匹配。例如,相距 d 的蓝色和红色看起来应该与相距 d 的绿色和紫色一样可区分。

CAM02-UCS 色彩空间(统一色彩空间)是精确信息表示的良好基础。它的三个坐标通常用 J’、a’和 b’来表示。它的柱面坐标是 J’、c’和 h’。感知色彩空间戳类似于 Lab。然而,Jab 使用了一种更新的颜色外观模型,在理论上为辨别力测量提供了更高的精度。

  • :明度:也称为数值或色调,是颜色亮度的表示
  • 色度:物体的颜色和灰色之间的内在差异
  • 色调:一个刺激与被描述为红色、绿色、蓝色和黄色的刺激相似或不同的程度

CAM02-UCS 是 Matplotlib 的绿色、等离子、岩浆彩色地图所使用的空间,你可能已经知道了。

信息的🥽视觉编码

我们可以用这些自由度来编码信息。最佳实践是:

  • “明度 J”:对于标量值,强度。它必须随物理量线性变化
  • 色调 h '可以编码一个附加的物理量,色调的变化应该与该量成线性比例。色调 h’在使图像更有吸引力而不干扰像素值的表示方面也是理想的。
  • 色度较难识别,不应用于编码物理信息

🤔是什么让彩色地图成为好的彩色地图?

根据参考文献和理论,一个好的彩色地图是一个不会引入伪像的彩色地图,并且在人眼看来是均匀一致的。通过以下方式执行均匀化

  • 使彩色地图在 J '中呈线性
  • 提升彩色地图(使其更亮,即增加 J’的最小值)
  • 对称化色度以避免进一步的伪像
  • 避免色度曲线中的扭结和边缘
  • 双音对称与否

🛠如何修复一个坏的彩色地图?

使用 scicomap 包(科学彩色地图),很容易修复各种各样的彩色地图(顺序、发散、会聚、多顺序、圆形、定性)。您可以选择内置的彩色地图,也可以使用之前的 Matplotlib 彩色地图。

让我们举一个常见的颜色地图的例子,这是一个糟糕的选择:

import scicomap as sc# Import the colour map
sc_map = sc.ScicoSequential(cmap="hot")# Fix the colour map
sc_map.unif_sym_cmap(lift=40, bitonic=False, diffuse=True)# Visualize the rendering of information
fig_examples=sc_map.draw_example(cblind=False, figsize=(22, 4))
fig_cmap=sc_map.assess_cmap(figsize=(22,10))

左图:原始彩色地图。太暗,明度不是线性的且有边缘,色度不对称且不平滑。右图:相同的彩色贴图,但是固定:更亮、线性、平滑和对称。可视化建立在 EHTplot 库的基础上

顶行显示原始的热 cmap,底行显示固定的彩色 map。原始的彩色地图引入了许多假象,金字塔的逐步渲染(除了边缘的平滑功能)。修复后,伪影几乎消失,可读性更好。

如何与 Matplotlib 一起使用

import matplotlib.pyplot as plt
import matplotlib as mpl
import scicomap as sc
from scicomap.utils import _fn_with_roots# load the color map (built-in or any mpl compatible cmap)
div_map = sc.ScicoDiverging(cmap='watermelon')
# mpl_cmap_obj = plt.get_cmap("PRGn")
# div_map = sc.ScicoDiverging(cmap=mpl_cmap_obj)# correct the colormap
div_map.unif_sym_cmap(lift=15, 
                      bitonic=False, 
                      diffuse=True)# get the fixed color map
fixed_cmap = div_map.get_mpl_color_map()
print(type(fixed_cmap))# use it as you like
im = _fn_with_roots()
norm = mpl.colors.CenteredNorm()
divnorm = mpl.colors.TwoSlopeNorm(vmin=-1, vcenter=0, vmax=1.25)
fig = plt.figure(figsize=(3,3), facecolor="white")
ax = fig.add_subplot(1, 1, 1, facecolor="white")
pos = ax.imshow(im, cmap=fixed_cmap, aspect="auto", norm=divnorm)
fig.colorbar(pos, ax=ax);

用修正的 cmap 绘图

🌈不要忽视色彩不足的渲染

高达 8%的男性(白种人)是色盲用户,在制作图表时不要忽视这一点。使用 Scicomap 库,你可以很容易地把自己放在一个不能清楚区分颜色的人的位置上。

在使用 scicomap 绘制的示例中,您可以直观地看到缺乏色彩的人会看到什么,以及色彩映射表是否合适。尤其是红绿色图并不是最好的选择,即使对于没有色觉缺陷的用户来说,红绿色图被校正得看起来是一致的

div_map = sc.ScicoDiverging(cmap='watermelon')
div_map.unif_sym_cmap(lift=10, 
                      bitonic=False, 
                      diffuse=True)
f = div_map.draw_example()

红绿颜色地图如何呈现给不同类型的缺乏颜色的用户

你甚至可以快速比较不同的彩色地图

c_l =  ["cividis", "inferno"]
f = sc.plot_colorblind_vision(ctype='sequential', cmap_list=c_l, figsize=(30, 4), n_colors=11, facecolor="black")

🧰更多功能

scicomap 包提供了更多的功能(发散、发散、多序列、圆形和定性彩色地图),我将让你探索。没有借口使用喷射彩色地图了,✌

📚参考

  • 科学交流中颜色的误用
  • 为什么我们使用糟糕的彩色地图,你能做些什么
  • 彩虹已死…彩虹万岁!—系列大纲
  • 科学彩色地图
  • 为科学图形选择色标
  • 颜色
  • 好的彩色地图:如何设计
  • 包括高动态范围和宽色域的图像信号的感知均匀色彩空间
  • EHTplot 库

时间序列建模基础的综合指南

原文:https://towardsdatascience.com/your-comprehensive-guide-to-the-basics-of-time-series-modeling-f673398b5df3?source=collection_archive---------1-----------------------

简单解释了时间序列建模的理论基础

照片由作者拍摄

W 当我开始学习时间序列预测时,我首先阅读了许多博客帖子和文章。不幸的是,对于初学者来说,其中只有很少一部分是相当全面的。其他的缺乏完整性(即不包括残差分析)或者更糟,错误地应用了方法。幸运的是,我找到了一些非常好的书(见参考资料),并有很好的陪练。

我想出写这篇文章的想法有两个原因。首先,你不必像我一样经历眼泪和困惑的低谷。第二,像 AR(I)MA 这样的经典时间序列模型带有许多假设。因此,理解时间序列建模背后的思想及其基础是至关重要的。

根据需求和我的时间,我想写一个关于时间序列预测的系列。从最基础的到先进的时间序列模型,这些模型也用于像著名的 Makridakis 竞赛这样的竞赛中。但首先从本文开始,介绍时间序列建模的基本理论概念和工具。

在开始之前,我想感谢我的同事 Justina Ivanauskaite 的精彩对话和对本文的贡献。

本文有助于您理解以下主题:

  • 理解术语
  • 时间序列组件如何分解它们
  • 白噪声随机游走车型
  • 平稳性的概念
  • 自相关偏自相关函数

小心那些💡如果你赶时间,要点是什么?

术语

我们必须区分随机过程(也称为时间序列 过程或模型)和时间序列

随机过程

被描述为一组随机变量 {Y𝑡,𝑡∈𝑇,它们在时间上排序,并在一组时间点** 𝑇上定义,可以是连续离散。𝑇指示处于的哪个过程,可以被 观察到
一个例子可以是给定地区连续几天的新新冠肺炎感染人数。**

在这篇文章中,我们集中在离散时间(DT)随机过程和时间序列。这意味着 t 可以取 0,1,2,…等中的整数值。。因此,当我们在本文的后续课程中谈论时间序列过程、模型或时间序列时,我们总是假设𝑡是离散的。

时间序列

一个【观察】时间序列是一个时间序列过程实现。它可以用小写字母 y=(y₁,y₂,…,yₜ).来表示

根据上面新冠肺炎的例子,时间序列可以是从 2021 年 1 月到 2021 年 3 月底德克萨斯每天观察到的新新冠肺炎感染人数。

落后

当您处理时间序列时,您会经常听到术语滞后。一个滞后可以看作是两点之间的时间差。在每日数据的情况下,yₜ是时间序列的当前值,而 yₜ₋₁(或滞后 1)是昨天的值,yₜ₋₂(或滞后 2)是前天的观察值,以此类推。

💡关键要点

  • 一个时间序列过程或时间序列模型是有序随机(也叫随机)过程数学描述。
  • 一个时间序列就是这样一个描述过程的实现
  • 滞后是两个观测值或点之间的时间差

时间序列组件

时间序列充满了模式。因此,将我们的时间序列分成个不同的部分对于更深入地分析其底层结构是非常有用的:

  • 趋势周期 Tₜ :是数据中的长期增加减少,不一定总是线性的。在过去的 60 年里,世界范围内不断增长的电力消耗可以作为一个趋势的例子。
  • 季节性 Sₜ :代表具有固定且已知周期(即一年中的时间等因素)的变化 。一个例子是圣诞节期间零售额的增加和假期后的减少。
  • 余数 Rₜ :是一个时间序列中包含噪声(即测量误差引起的)或随机运动不规则部分。它有时也被称为残差。虽然我们无法直接观察到它在某种程度上总是存在于时间序列中。

根据不同的文献,您通常会发现三个四个组件。一些作者将周期描述为一个独立的成分,其他作者也引入了成分水平,它描述了系列的平均值。在这篇文章中,我决定坚持使用 Makridakis (1998)提到的三个不同的组件。

分解

既然我们已经熟悉了时间序列的组成部分,我们可以考虑如何将分割或分解一个时间序列为其组成部分。基本上,有两种方法可以组成一个时间序列。

添加剂分解

Yₜ = Sₜ + Tₜ + Rₜ

加法分解是最基本的一种。如果季节性波动的幅度(尖峰),或围绕趋势周期的变化不随时间序列的水平(均值)变化,则适用。因此,当季节变化随时间相对恒定时,这是有用的。

乘法分解

Yₜ = Sₜ × Tₜ × Rₜ

如果有增加趋势季节性活动幅度增加,那么乘法分解是有用的。换句话说,如果季节性的幅度取决于水平的,则使用分解

因为像这样的定义总是很枯燥,所以是时候举一些例子了。为了更清楚地理解加法和乘法分解,以及何时使用哪一种分解,我们将仔细看看下面的图(图 1)。

图一。加法和乘法合成的例子。

左手边的图(a)显示了 ausbeer 数据集的摘录。数据的季节性变化随着时间的推移保持相对恒定。换句话说:峰的高度相对恒定。这意味着加法分解对于这个时间序列是正确的。

在右侧(b ),我们可以看到绘制的航空乘客数据集。与左边的图相比,我们可以看到季节变化 并不随时间相对恒定。峰值不会保持不变,而是会改变高度(即 1959 年的峰值与 1953 年的峰值相比)。

如果你想测试你关于检测加性和倍增季节性的知识,我可以强烈推荐交互工具“加性和倍增季节性——你能正确识别它们吗?”尼古拉·考伦茨。

现在我们已经有了所有需要的理论知识,我们可以用 python 运行一个时间序列分解。 statmodels 包提供了一个名为季节性分解的函数。对于下面的例子,我们将分解航空乘客数据集。

该函数的两个最重要的参数是:

  • x :要分解的时间序列
  • 型号:分解的类型。

加法是模型参数的默认值。因此,如果您的时间序列需要乘法分解,您必须将参数值设置为“乘法”。

图二。使用 statsmodels 季节性分解函数的乘法分解示例。

绘制的输出(图 2)按时间顺序显示了分解的时间序列、趋势、季节和剩余(Resid)部分。像这样的图有助于我们更详细地直观分析时间序列的组成部分。

💡关键要点

  • 一个时间序列由三个组成部分:趋势周期、季节性和余数(也叫残差)。****
  • 这些组件可以以加法乘法的顺序粘在一起。
  • 数据的视觉分析可以告诉我们这些成分是处于加法还是乘法 合成
  • 如果趋势周期 周围的变化随着平均值变化,或者我们看到季节性活动振幅增加,我们可以考虑乘法分解
  • stats modelsseasonal _ decompose函数帮助我们将时间序列分解成它的组成部分。

白噪声和随机游走模型

既然我们已经熟悉了时间序列的术语、组成部分和分解,那么是时候讨论一些基本但也很重要的时间序列模型了。

白噪声

我们先来说说白噪声(图 3)模型。白噪声模型可以定义如下:Yₜ = εₜ

其中εₜ是白噪声,无法根据该系列的过去历史预测的部分

图 3。白噪声例子。

白噪声具有以下特征:

  • 它有一个为 0 的均值【T21(μ)】和一个恒定方差(也写作 WN(0,σ))。****
  • 不遵循任何模式,因此它完全随机(这就是它被称为“噪音”的原因)。
  • 它由独立同分布(也称 i.i.d.) 观测值组成。
  • 每个观察值与系列中的其他观察值有一个零相关性。

如果观测值遵循一个正态分布,我们说它是高斯白噪声理想情况下,我们的预测误差是(高斯)白噪声。这意味着我们用时间序列模型“捕捉”或模拟了所有重要的影响,剩下的只是不可预测的白噪声(见剩余的 Rₜ分量)。

随机漫步(有漂移)

除了白噪声,还有另一个非常基本但重要的模型,叫做随机游走(图 4)。
随机漫步模型可以定义为:Yₜ = Yₜ₋₁ + εₜ

其中,Yₜ代表当前值,Yₜ₋₁代表一个滞后之前的 y 值(例如,昨天的值),εₜ代表随机误差(也称为噪声)。

图 4。随机漫步和随机漫步与漂移的例子。

这意味着当前观测前一个相距随机步长,所有步长独立相同** 大小分布(“I . I . d .”)。
因此,随机游走无法合理预测。对 Yₜ的最佳预测是前值 Yₜ₋₁ ,也称为天真预测。随机漫步序列的一个典型现实例子是连续几天的股价。**

如果随机游走序列遵循上升或下降趋势,则它包含“漂移”。这就是为什么我们称之为随波逐流。可以定义为:
Yₜ= α + Yₜ₋₁ + εₜ其中α代表常数或漂移。

正如已经提到的,我们希望我们的残差在后面的建模步骤中是白噪声。这就给我们带来了一个问题,我们如何检验我们的时间序列是否是白噪声?你猜怎么着,有一个统计测试!

白噪声测试:永盒测试

这被称为“容格测试”。Ljung-Box 检验提出了一个无效假设和另一个假设:

  • H₀:这些数据都是独立分发的
  • Hₐ:的数据是非独立分发的

在 python 中,包 statsmodels 提供了一个名为 acorr_ljungbox 的函数。除了数据之外,我们还必须提供一些滞后时间,这就引出了一个问题“什么是合适的滞后时间?”

在他的博客文章中,Hyndman 建议:

  • 对于非季节性时间序列,使用ℎ=𝑚𝑖𝑛(10,𝑇/5)
  • 对于季节性时间序列,使用ℎ=𝑚𝑖𝑛(2𝑚,𝑇/5)
    其中 t 是滞后次数,𝑚是季节性周期。

对于下面的例子,我们将使用 720 次观察的白噪声数据。关于 Hyndman 的建议,这意味着我们将滞后的长度设置为 10。

第一个数组元素([0])包含 p 值,而第二个数组元素([1])包含 Ljung-Box 检验统计量。如果 p 值低于 0.05 阈值我们可以拒绝零假设,这意味着数据不是白噪声

在我们的例子中,所有的 p 值都在 0.05 的阈值(T1)之上(T0),这意味着我们不能拒绝零假设,并且(T2)我们的数据是白噪声(T3)。

💡关键要点

  • 一个白噪声时间序列均值** (μ) 为 0 ,一个恒定方差,并且由于完全随机所以无法预测。**
  • 我们拟合的模型残差应该是白噪声
  • 随机游走的最佳预测是来自上一步 Yₜ₋₁ 加上误差项 εₜ.的值
  • 具有趋势的随机游走称为具有漂移随机游走。
  • 为了测试一个时间序列的白噪声,我们可以使用永盒测试

平稳的概念

平稳性是时间序列的一个基本特征,因为 ARMA 等(经典)时间序列模型假设它,如果基础数据不是平稳的,可能会导致不正确的结果。

为了全面起见,我们必须区分严格平稳性弱平稳性

严格平稳性

  • 我们说一个时间序列过程是严格平稳的,如果它的属性不受时间起点变化的影响
  • yₜ,yₜ₊₁,…,yₜ₊ₙ的观测值的联合分布与 yₜ+h,yₜ₊ₕ₊₁,…,yₜ₊ₕ₊ₙ.的观测值的联合概率分布完全相同
  • 因此,它不受时间向前或向后移动的影响。

然而,很多现实生活中的过程并不是严格静止的。但是,即使一个过程是严格平稳的,我们通常没有潜在时间序列过程的明确知识。由于严格平稳性被定义为过程联合分布的一个属性,因此不可能从观察到的时间序列中得到证明。****

弱平稳性

我们将时间序列过程定义为弱平稳如果

  • 期望 E(yₜ)随时间是恒定的
  • 方差 Var(yₜ)随时间是常数
  • 协方差 Cov(Yₜ,Yₜ₊ₕ)只取决于滞后 h,Cov(Yₜ₁,Yₜ₂) = Cov(Yₜ₁₊ₕ,Yₜ₂₊ₕ)

广义地说,如果均值(即趋势)没有系统变化,方差没有系统变化,并且严格周期性变化(即季节性)被移除,则称时间序列为弱平稳。

这些定义或类型的平稳性在实践中是如何使用的?

实际上,人们通常用平稳性这个术语来表示弱平稳性,因为严格平稳性只是一个理论概念。为了在这里保持一致,当我们指弱平稳性时,我们将从现在起使用平稳性。

好了,现在我们知道了平稳性的概念意味着什么,让我们通过观察图 5 中的下图来测试我们的知识,并检查它们是否是平稳的。

图 5。平稳和非平稳时间序列的例子。

图(a)显示数据中有一个清晰的趋势(没有随时间变化的恒定平均值),因此该序列不可能是稳定的。

对面上的图(b)没有显示数据中的趋势或任何季节性。这表明曲线(b)是稳定的。

曲线(c)与曲线(a)一样,在数据中有一个清晰的趋势,因此也是非平稳的。

最后,Hyndman 和 Athanasopoulos (2018)提出了一个令人困惑的案例。曲线(d)由于其强循环而显得不稳定。然而,这些周期是非周期性的。当猞猁的数量变得太大而无法提供足够的食物时,它们就会停止繁殖,数量就会下降。然后它们食物来源的再生允许种群再次增长等等。这意味着从长远来看,这些非周期性循环的时间是不可预测的。由于这种既没有趋势也没有季节性的周期性行为,该序列是平稳的。

我敢打赌,你一定很惊讶图(d)竟然是稳定的。有时很难直观地判断时间序列是否是平稳的。因此,我们需要一种更健壮的方法来检查平稳性。

幸运的是,有几个统计检验(也称为单位根检验)来检查一个数列是否平稳

增强的迪基富勒( ADF )试验

一个最广泛使用的是扩展的迪基富勒测试。

还有很多单位根检验,比如科维亚特科夫斯基-菲利普斯-施密特-申( KPSS )检验或者埃利奥特-罗森伯格-斯托克( ADF-GLS )检验。

ADF 提出了一个无效假设和另一个假设。

  • H₀:系列有一个单位根,所以它是非固定
  • Hₐ:系列没有单位根,所以它是静止的

广义地说,一个 单位根 是一个随机游走的具有漂移的时间序列。它包含一个随机趋势,显示一个 不可预测的模式 。如果你对更深入的解释感兴趣,请参见 Makridakis (1998)。

为此,Statsmodels 为我们提供了 adfuller 函数。

对于这个例子,我使用了上面的 airpassengers 数据集,它不是固定的。由于数据集清楚地显示了趋势,我将函数的回归参数设置为 ct (常数和趋势)。**

该函数返回几个值。第二个代表计算的 p 值。由于 0.55 高于 0.05 的阈值,我们不能拒绝 H₀,所以数据不是平稳的。

既然我们知道我们的数据不是静态的,我们如何使它们稳定呢?

如何使数据稳定

如前所述,许多(经典)模型假设时间序列是平稳的。那么如果我们知道我们的数据不是静态的呢?使数据稳定的两种非常常见的方法是:

  • 转换数据(如对数和/或使用平方根转换)
  • 差异

转换数据是一种非常基本的方法,也用于回归等其他统计领域。一种方法是对数据应用对数平方根变换,使其稳定。****

另一种非常常见的方法是差分。它可以作为变换方法的替代或补充来应用。我们通常先取数列的差值。也就是所谓的一阶差分。我们正在创造一个新的时间序列的连续差异 Yₜ -Yₜ₋₁.例如,原始时间序列是 Y₁,Y₂,Y₃,…,Yₙ,然后我们应用一阶差分,这导致一个新的时间序列 Y₂-Y₁,Y₃-Y₂,Y₄-Y₃,…,Yₙ - Y。

如果这不起作用并且我们仍然有非平稳数据,那么我们也可以考虑二阶差分,通过取所产生的差分(不要与取二阶差分 Yₜ -Yₜ₋₂).混淆****

如果您的时间序列是数据帧格式,您可以使用 pandas 功能。diff() 。该函数将您想要进行差异计算的周期数作为参数。**

如果我们想应用一阶差分,我们只需添加函数。diff() 到我们的数据帧。在我们的例子中,我们应用了。对 ausbeer 数据集的 diff()方法(表 1)。**

表 1。差分 ausbeer 数据集。

因为我们取系列(Y₂-Y₁)的一阶差分,我们将丢失一个(第一个)数据点。这意味着如果我们应用二阶差分,我们会丢失前两个数据点。

💡关键要点

  • (弱) 平稳是许多(经典)时间序列模型假定的一个性质。
  • 如果一个时间序列的属性(均值,方差)在时间上 恒定并且协方差仅依赖于滞后 h ,则该时间序列是弱平稳的。
  • 为了测试我们的时间序列的平稳性,我们可以使用扩展的 Dickey-Fuller 测试
  • 为了使 时间序列平稳,我们可以使用差分变换技术如对数变换。****

自相关偏自相关****

了解了平稳性的含义以及如何使时间序列平稳后,我们现在将重点关注两个重要工具,以便在预测前进行更深入的时间序列分析。自相关函数偏自相关函数【PACF】
两者都可以用来识别时间序列滞后值之间的解释关系。

自相关函数

  • 计算自相关,即测量yₜ(变量的当前值)和 yₜ₋ₙ变量关系。例如,对于滞后-1 自相关,ACF 计算 Yₜ和 Yₜ₋ₙ.之间的相关性****
  • 还捕获间接影响(例如,yₜ与 yₜ₋₂相关只是因为两者都与 yₜ₋₁相关,而不是因为 yₜ₋₂包含的任何有助于预测 yₜ).的新信息

可以使用 statsmodels plot_acf 函数绘制自相关图:

滞后的数量没有固定的规则。然而,Montgomery (2015)建议,“一个好的一般经验法则是,至少需要 50 次观察才能给出 ACF 的可靠估计,并且单个样本自相关应计算到滞后 K,其中 K 约为 T/4 ”。

让我们检查一下 ausbeer 数据集,以便更好地理解如何解释自相关值(图 6)。

图 6。带有趋势和季节性附加信息的 ausbeer 数据集的 ACF 图。

左侧显示了 ausbeer 数据集(a)的自相关图。在右边,你会发现绘制的澳洲啤酒时间序列(b),它的趋势(c),和季节性成分(d)。

自相关图(a)向我们展示了观测值与其滞后值之间自相关的值。它还告诉我们,滞后 n 处的值是否具有统计显著性。如果自相关值位于蓝色区域内,则该值不重要。如果位于之外的** ,则有效**。****

当一个时间序列有一个趋势时,小滞后的自相关往往是大而正的。你可以在滞后 1 和滞后 2 的图(a)中找到这一点。

滞后 0 处的值将始终为 1 且非常重要,因为它是时间序列与其自身 corr(Yₜ,Yₜ).的自相关可以看做一个参照点。

当我们的时间序列是季节性的时,季节性滞后的自相关值会比其他滞后大。这可以在滞后 6、12、18 等处看到..

偏自相关函数(PACF)

如前所述,ACF 也捕捉到了间接影响。一种只关注 Yₜ与其滞后版本 Yₜ₋ₙ之间直接关系的方法是偏自相关函数(PACF)** 。一般来说,两个变量之间的“部分”相关性是它们之间的相关性数量,这不能用它们与一组特定的其他变量的相互相关性来解释。**

这里 statsmodels 还提供了一个函数来计算和绘制部分自相关值:

图 7。白噪声和随机游走数据的 PACF 图。

图 7 显示了用 plot_pacf 函数创建的两个图。第一种(a)将 pacf 应用于白噪声数据。由于白噪声的值之间没有相关性,我们在这里也看不到任何相关性(所有值都在蓝色区域,因此不重要)。

第二个图(b)将 pacf 应用于随机行走数据。如前所述,随机游走的最佳预测是一个滞后之前的值。

根据您的目的,在使用 ACF 和 PCF 图对时间序列进行分析之前,可能需要首先使时间序列平稳(即,如果您想要为 ARMA 模型选择 p 和 q 参数值)。

ACF 和 PACF 在时间序列建模的后期不仅在选择正确的参数值中发挥重要作用,而且在分析我们预测模型的残差中也发挥重要作用。白噪声的 ACF 和 PCAF 不包含** 任何 显著 自相关部分自相关。因此,我们可以稍后使用 ACF 和 PACF 来检查我们的残差是否有希望显示没有显著自相关和部分自相关。**

💡关键要点

  • 利用自相关函数(ACF)图,我们能够直观地分析****自相关及其显著性
  • 与 ACF 不同的是,部分自相关函数** (PACF)仅测量 Yₜ与其滞后版本 Yₜ₋ₙ.之间的**直接关系****
  • 两者都是为 ARMA 或 ARIMA 等模型选择正确参数的重要工具。

一篇解释某个领域理论基础的文章总是伴随着过于枯燥的风险。我希望本文中的大部分时间都是这样,而不是这样,现在您已经有了一个更好的时间序列建模的基础。如果您对更多信息和优秀文献感兴趣,请查看下面的参考资料。
如开头所说,最初的想法是把它做成一个系列。如果有需求,我有时间,我保证下面的文章会更有动手能力!

参考

Box,G. E. P .、Jenkins,G. M .、Reinsel,G. C .和 Ljung,G. M. 2015。 时间序列分析:预测与控制

Chatfield,c .和邢海鹏。2019. 《时间序列的分析——与 R 一起介绍》,CRC 出版社。

德特林,马塞尔。(未注明)。 应用时间序列分析—课程 2020

Hyndman,R. J .和 Athanasopoulos,G. 2018。 预测:原理与实践

Makridakis,S. G. 1998 年。 预测:方法与应用,(第三版。),纽约:威利。

2015 年蒙哥马利特区。【时间序列分析与预测导论】(第二版。)《概率与统计》中的威利级数,新泽西州霍博肯:威利。

你在网上的酷叶地图

原文:https://towardsdatascience.com/your-cool-folium-maps-on-the-web-313f9d1a6bcd?source=collection_archive---------13-----------------------

利用 flask 和 heroku 与世界分享您的交互式地图

美国宇航局在 Unsplash 拍摄的照片

这是系列的第二部分,旨在分享将您的交互式地图托管在 web 上或部署为 web 应用程序的步骤。如果你只是为了代码而来,那么开始。本文分为六个部分:

∘ 简介∘需要的文件和文件结构t13】∘部署使用 githubt16】∘部署使用 heroku CLIt19】∘最终想法t22】∘引用

介绍

你不能把你的交互式地图放在你的电脑上。为了让其他人使用它并从中受益,它必须在网络上。让我们来看看如何让这成为可能。

所需文件和文件结构

Heroku 需要特定的文件来使部署过程顺利进行。它们是:

Procfile:它告诉 heroku 如何运行我们的 flask 应用程序。Procfile 没有文件扩展名。

Procfile

Gunicorn 是一个 Python WSGI HTTP 服务器,它帮助我们的云应用平台 heroku 通过 WSGI 协议与 Flask 进行通信。

WSGI 代表“Web 服务器网关接口”。它用于将来自 web 服务器(如 Apache 或 NGINX)的请求转发到后端 Python web 应用程序或框架。从那里,响应被传递回 web 服务器以回复请求者。

需求文件:这是一个文本文件,包含运行 python 文件所需的库,即 Flaskgunicorn ,以及所使用的具体版本。Heroku 需要这个来创造你用来产生精确结果的精确环境。

requirements.txt

Templates 文件夹:创建好你的叶子地图并保存为 html 文件后,你需要把 the_map.html 放到这个 templates 文件夹中。 flask 将在这里搜索它,并将其呈现给 web 应用程序。

先睹为快' the_map.html '

Python 文件:Python 文件被命名为 app.py

在文件中:

  • Flask 类和 render_template 函数被导入。
  • 创建了 app 对象,它是 Flask 类的实例,与 gunicorn 一起将帮助 heroku 将我们的地图部署为 web 应用。
  • 创建了路由装饰器,并为其定义了一个函数。装饰器告诉应用程序哪个 URL 应该调用函数(render _ the _mAP())。该函数从 templates 文件夹中返回呈现的模板。呈现模板意味着将它转换成 HTML 页面。
  • 由于该文件将通过终端运行(请参见下面的段落),' if _ _ name _ _ = = _ _ main _ _ '的计算结果为 true,app .将运行。

app.py

你首先要确保网络应用程序在你的电脑上运行。如果是的话,它会在赫罗库星球上运行。要检查这一点:

  • 打开终端
  • 导航到您正在工作的目录
  • n

如果地图出现在终端显示的地址,你可以前往 heroku。

文件结构

Making-Cool-Maps-In-Python/
       |---> app.py
       |---> requirements.txt
       |---> Procfile
       |---> templates/ 
             |---> the_map.html

使用 Github 部署

通过以下步骤,您可以使用 github 部署地图:

  • 前往heroku.com并登录或注册
  • 点击创建新应用按钮,输入 a 名称,点击创建应用按钮
  • 选择一种部署方法—连接到 github 并登录
  • 选择要连接的存储库并连接
  • 您可以选择自动部署选项,这可以确保您对主分支所做的每个更改都会影响 web 应用程序
  • 点击部署分支,等待 heroku 安装并检测分支中的文件

Heroku 将地图发布到网络上

使用 Heroku CLI 部署

从这里下载 CLI。使用以下命令:

  • heroku login并输入您的详细信息
  • heroku create ‘app-name’其中“应用程序名称”是您自己的应用程序名称
  • git push heroku master
  • heroku config:set DEPLOY=heroku
  • heroku open或者通过您的 heroku 个人资料打开您的文件

最后的想法

我希望这有所帮助。那就去吧,试试看,以后再谢我吧!点击此处查看已部署的树叶地图。感谢阅读!😃👋

我听见了,我忘记了。我看见了,我记得。我知道,我也理解。—孔子

参考

什么是 wsgi?

烧瓶文件

栈溢出叶问题

你的交叉验证误差置信区间是错误的——以下是如何修正它们的方法

原文:https://towardsdatascience.com/your-cross-validation-error-confidence-intervals-are-wrong-heres-how-to-fix-them-abbfe28d390?source=collection_archive---------20-----------------------

思想和理论

一种利用嵌套交叉验证来消除交叉验证过程中偏差的新算法。

Stanford (2021) 的研究人员开发了一种方法,该方法使用嵌套交叉验证(NCV)来说明数据分割之间的相关性,从而允许我们计算误差周围的准确置信区间。尽管 NCV 有潜力,但它需要比计算量大的常规交叉验证更多的拟合迭代。但是,如果您能够访问多个计算节点/内核,这些迭代可以轻松实现并行化。

NCV 概述:内部 CV 循环是常规交叉验证。维持集是一个测试集,用于估计我们的模型的偏差— 来源(图 8)

1.技术 TLDR

论文中概述的方法首先创建了一个可以估计偏倚的有偏倚的嵌套交叉验证算法。然后,我们使用 NCV 中的偏差和误差项来调整我们的常规 CV 模型。最终产品是有效的 MSE 和置信区间,它控制数据折叠的相关性。

算法步骤的高级概述

1.1)使用嵌套交叉验证(NCV)创建一个过度偏倚的模型。我们首先对 K-1 折叠执行交叉验证,保持第 K 个维持组不变。一旦我们有了每个 K-1 折叠的损失向量,我们就在维持组上运行训练/测试分割,其中训练和测试集分别是第 K-1 和第 K 折叠。对每个折叠重复这一过程,创建下面的矩阵,其中每一列代表一次迭代:

NCV 算法的损失矩阵。每个单元格对应一个损失向量,而不是一个数字。

请注意,我们的“维持损失”是无偏的,因为测试数据(维持集)从未被训练数据(所有 K-1 其他折叠)看到。还要注意的是,我们的每个“fold_ n _loss”向量都是有偏差的,因为我们根据之前看到的数据重新调整了模型。

计算矩阵中每一列的 MSE。请注意,给定列的 MSE 定义为我们的“fold_ n _loss”向量的平均值减去“holdout_loss”向量的平均值。有关计算步骤,请参见下面的 python 代码。

MSE = []
for i in range(K): # iterate through folds
  folds_idxs = [j for j in range(K) if i != j] avg_fold_n_loss = mean(K_minus_1_folds_losses[folds_idxs, i])
  avg_holdout_loss = mean(holdout_loss[i, i]) error_sq = (avg_fold_n_loss - avg_holdout_loss)**2
  MSE.append(error_sq - var(holdout_loss))

1.3)使用常规 K 折 CV 训练另一个模型。由于我们只对误差项感兴趣,我们只存储每个折叠的样本外损失。

cv_errors = mean(K_fold_CV_loss)

1.4)计算偏差。

weight = (1 + ((n_folds — 2) / (n_folds))^(1.5))
bias = (MSE — cv_errors) * weight

1.5)计算这个去偏误差周围的置信区间。最终置信区间的形式为…

考虑数据折叠之间相关性的调整置信区间公式— 来源(等式 17)

  • Err (NCV) 是嵌套交叉验证模型误差,
  • Bias_hat 以上估算,
  • q 1-α/2 是我们置信度的 z 值,
  • K 是折叠的次数,并且
  • MSE_hat 是步骤 2 中估计的 MSE。

2。但是,到底是怎么回事呢?

让我们回溯并充分理解为什么我们数据中折叠之间的相关性会导致不正确的小置信区间。

在拟合预测模型时,我们的目标是可推广性,即我们希望我们的模型在新数据上表现良好。通过在我们的训练/测试分割中回收数据,我们实际上从未给我们的模型新的数据。那么,如何才能知道模型会泛化呢?

考虑一次 50%的训练/测试分割。这种分割是有效的,因为模型没有根据我们的测试数据进行训练,因此这些数据是全新的。然而,一旦我们采用 2 重 CV,用测试数据重新改装,一大块数据就被回收了。而且,因为模型之前看过数据,所以它认为我们的样本很能代表总体。

随着我们的模型的“信心”的增加,它减少了我们的置信区间的大小。

2.1 数学视角

既然我们希望对为什么我们的误差置信区间可能有偏差有一些直觉,让我们从数学的角度来看一下。

方差代表数据中的自然变化,是任何置信区间的关键组成部分。当估计总体方差时,即不考虑样本中具体数据的真实方差时,我们经常使用样本方差。然而,由于我们的数据折叠中存在相互依赖性,我们必须在每个数据折叠内部和之间纳入协方差。

我们数据折叠的协方差矩阵。— 来源(图 7)

使用上面的图,我们可以看到交叉验证数据集的方差有 3 个组成部分。

  1. 红色方块是褶皱内的协方差
  2. 蓝色方块是褶皱之间的协方差,以及
  3. 黑色方块是我们误差的方差。

我们将这一概念转化为上述 3 个部分的数学函数:

交叉验证模型的误差方差— 来源(等式 13)

  • n 是样本量,
  • K 是数据折叠的次数,
  • a1 是我们误差的方差var(e)
  • a2 为同一次、 cov(ei,ej) 误差的协方差,且
  • a3不同道次的误差协方差,( cov(ei,ej) )。

注意 ij 是上图中一个正方形对应的指数。

关键在于,大多数交叉验证方法假设 a2a3 为 0,即我们的折叠内或折叠间的误差不存在相互依赖性。然而, a2a3 通常是> 0,因为我们重用数据。

那么,你能看到当我们不假设独立性时,误差的方差是如何变大的吗?

3.解决方案

嵌套交叉验证(NVC)已经存在很多年了,但是在计算模型误差的置信区间时,独立性经常被错误地假设。那么如何计算我们褶皱的相互依存性并去除它呢?

有 3 个“简单”的步骤…

3.1 计算偏差

让我们来看看下面这个定义偏差的等式。

估计偏差的公式— 来源(等式 16)

  • K 是折叠的次数,
  • Err(NCV) 是我们过度偏向的 NCV 模型中的估计误差,以及
  • Err (CV) 是我们常规 CV 模型中的估计误差。

换句话说,偏差是我们的额外偏差模型Err(NCV)和常规偏差模型 Err (CV) 之间的加权差。

现在我们已经对我们的偏差有了一个估计,我们打算如何解释它呢?

3.2 计算 MSE

我们调整两个量。第一个是我们的均方误差(MSE ),第二个是误差的置信区间。

在下面的等式中,我们定义了偏差调整的 MSE 的估计值…

MSE 的概念定义—改编自来源(引理 4)

在不深入统计数据的情况下,让我们定义我们的术语…

  • Err XY,s (可以估计)是给定训练集 X 和 Y 的观测样本误差
  • Err XY,p (无法估计)是我们模型的理论误差其中 X 和 Y 不能自由变化,而
  • ebar (out) (可以估计)是通过我们的维持集计算的无偏误差的平均值。

这种偏差/方差分解的美妙之处在于,它允许我们绕过让我们的 Err XY,p 项的要求。相反,我们可以简单地计算误差的方差来代替最右边的项。

如果你想知道为什么要包含 Err XY,p 项,这是证明有效的必要条件。它不是一个可以计算的“真实”术语——它是一个统计概念,代表了我们当前模型的概括程度。

现在我们已经有了 MSE,让我们进入最后一步。

3.3 计算模型误差的置信区间

为了进行这种计算,让我们重温一下技术 TLDR 第 1.5 节中的公式。

考虑数据折叠之间相关性的调整置信区间公式— 来源(等式 17)

我们可以通过查找带帽子的所有内容(^)来确定需要估计的项,这里,帽子表示在前面的步骤中估计的值。第一个是 Err(NCV) ,我们的嵌套交叉验证错误。我们还需要计算这个。第二个是我们的偏差估计,在第 3.1 节中计算。最后,第三个是我们的 MSE ,在 3.2 节中计算。

不幸的是, Err(NCV) 由于 NCV 算法的复杂性,很难用数学方法来表示。然而,从概念的角度来看,这是我们的交叉验证错误的无效部分,即由我们的数据折叠中的相关性产生的部分。

在运行 NCV 算法得到 Err(NCV) 之后,我们可以插入我们的值并观察我们改进的置信区间。这些 CI 应展示更好的覆盖面,并具有强大的统计基础,能够说明我们数据的依赖性。

3.4 伪代码解决方案

由于我们没有使用数学符号完全涵盖 NCV 算法,这里有一些从作者写的 R 包改编的 pythonic 伪代码。注意,在某些情况下,为了计算效率,代码偏离了理论。还要注意,这段代码还没有运行——如果您看到错误,请留下评论,我会进行调整。

4.实施说明

  • NCV 在计算上非常昂贵,但是迭代很容易并行化。作者推荐 50 次迭代作为默认值,这将导致 10 倍 CV 的交叉验证拟合超过 500 次。
  • 这里我们关注 MSE 作为我们的损失函数,然而 NCV 框架理论上可以推广到任何损失函数。也就是说,还不清楚该算法如何处理非平方损失,即负数。
  • 虽然这篇文章的重点是交叉验证部分,但原始论文也讨论了样本之间的相关性概念如何影响 Mallow 的 Cp 和 bootstrap 采样等方法。
  • 最后,这篇论文于 2021 年 3 月发表。可能会有未来的发展,改进的文档,和一个 python 包。如果你知道资源,请在评论中留下它们或者给我发消息,我会添加它们。

查看我的评论,获得论文链接、R 包和一些潜在的后续步骤。

您的数据处理是您的数据科学品牌

原文:https://towardsdatascience.com/your-data-processing-is-your-data-science-brand-2d51db93f5c?source=collection_archive---------31-----------------------

让你的智力过程成为你的品牌

乔安娜·科辛斯卡在 Unsplash 上的照片

如果你喜欢我的内容,并想获得更多关于数据或作为数据科学家的日常生活的深入知识,请考虑在这里订阅我的时事通讯。

数据科学家的个人品牌是什么?这是他们做的项目吗?还是他们在社交媒体上表现出来的个性?还是他们受雇的公司?。在我看来,我刚才提到的一切都可能成为你的品牌——取决于你如何包装它。不过,我想就数据科学家个人品牌的另一个方面给出我的看法:数据处理

数据处理到底是什么?在这个阶段,我们处理收集或获取的原始数据,并将其转换为“更干净”的数据,以便随时用于任何目的(分析、可视化、描述等)。).从本质上讲,数据处理是从数据集中获得任何像样的结果/产品的支柱。

数据处理如何成为你的个人品牌?在这篇文章中,我将向您详细展示为什么数据处理是您作为数据科学家的个人品牌。让我们开始吧。

数据处理就是问正确的问题

您已经以某种方式收集或获取了数据集;接下来是什么?。你如何处理数据最终将取决于你想解决什么样的问题——这需要你提出正确的问题。

这似乎是一个简单的任务。任务是问一个问题,这有什么难的?不会有什么问题的,对吧?。根据上下文的不同,同一个问题可能有不同的含义,编写一个好的问题需要考虑业务和技术理解。

想象一下,你的公司有一个欺诈问题,并要求你的数据科学团队通过开发一个机器学习模型来检测任何潜在的欺诈行为。听起来很容易?你只需要从数据库中提取数据,处理数据,开发模型,然后嘣!你有机器学习模型。然后,业务用户问,让模型考虑“XYZ”案例,或者欺诈预测是“ABC”类型?。你已经考虑过那些案子了吗?。

这是经常发生的问题;你开发或者处理的东西不适合问题应该解决的东西或者遗漏了一些重要的细节。没有正确的信息,你会问的问题会变得不同,并导致你错误的数据处理。

每个人的独特之处在于他们如何解决“正确的问题”的问题。通常都是与业务用户讨论他们想要什么,但有时用户甚至不知道他们想要什么,直到他们得到产品。在这种情况下,你需要发挥你的创造力,根据你所拥有的信息和你所做的研究,写出正确的问题,其中数据处理将取决于所有这些活动——创意过程是你的品牌,是你应该展示的东西。

数据处理完全是一个智力过程。

在你的数据处理步骤中,你有哪些选择?缺失数据剔除或插补?离群点剔除还是转换?分类数据转换?在数据处理步骤的数据处理过程中,您可以做更多的事情。人与人之间的不同在于他们如何看待数据本身——即使对问题的理解是相同的。

我记得在我的一堂课上,有一次我向我的学生展示了同样的数据集,我告诉他们根据我提出的问题来处理数据。结果令人兴奋,因为我可以看到每个学生思维过程的差异。一名学生决定删除数据,因为他说这只是一些数据,而另一名学生坚持保留这些数据,因为每个数据都是有用的。保留或删除数据的决定是基于对什么是“正确的”的每一种理解,并且它是独特变化的。

我说数据处理是一个智力过程,因为你需要做研究和理解数据背景,以决定在数据处理部分做什么。就像我上面提到的例子,人们对每个处理问题的立场和他们为什么做某事的解释都不一样——这取决于他们的思维过程。

总而言之,你在数据处理过程中做出的选择是你的个人品牌,因为这里的活动经常因人而异。可能有一种正确的方法来删除数据或保留离群值,但要知道这需要大量的研究和创造力。你如何解释你在数据处理上的决定背后的原因,并且尽可能简洁,这很重要。

特征工程是一个创造性的过程。

数据处理的另一个步骤是特征工程或从现有特征创建特征。这一步很重要,因为它可以揭示任何以前看不到的模式或提高您的预测模型指标。为什么特征工程是你的个人品牌?我见过许多数据科学家用不同的方式进行特征工程,这对我来说很有趣,因为每个人都有自己的创造过程。特征工程就像艺术——每个人都有自己的风格。

虽然特征工程理论上看起来很简单,但事实是特征工程很难做到。许多人无法创建有意义的特征,因为缺乏对数据的理解或不知道该做什么。当他们需要改变数据维度,需要从另一个角度查看数据时,情况变得更加困难;例如,数据是从客户的角度来看的,但是他们需要将其转换成销售的角度。

许多数据科学竞赛的胜利都归结于获胜者提出的特性工程。许多功能是人们以前不会想到的,但他们设法做到了。这是他们的个人品牌,知道什么特征创造和利用他们的建模工程特征。

这也是你可以做的事情。做一个属于你自己的特色工程,想出能代表你个人品牌的特色。

结论

一个数据科学家的个人品牌可以有很多形式,但我想说的是数据处理是其中之一。你处理数据的方式因人而异。不同之处细节包括:

  • 你如何问这个问题
  • 你是如何处理数据的
  • 你如何做特征工程

每个细节都需要一个独特的智慧和创造性的过程,作为一名数据科学家,你应该表现得更像你自己的个人品牌。

在我的 LinkedInTwitter 上访问我。

如果您没有订阅为中等会员,请考虑通过我的推荐订阅。

你的数据科学面试准备——5 个激动人心的概率问题

原文:https://towardsdatascience.com/your-data-science-interview-prep-5-exciting-probability-problems-with-clear-solutions-3f0f007bca75?source=collection_archive---------30-----------------------

让我们一起为你下一次 DS 面试练习吧!

图片来源:https://unsplash.com/photos/c-w837v9gb4

在即将到来的博客文章系列中,我将解决一系列在数据科学面试中经常出现的数据科学问题。这些练习将帮助你把你的技能提高到一个新的水平,并在面试中感到更加自信。我将涵盖广泛的主题,如概率和统计(我的最爱!),Python,SQL,案例分析等。

在本帖中,我将向你展示几个有趣的概率问题,这些问题对你的知识宝库非常有用!⛱

让我们深入了解一下:

  1. 掷硬币💰:一枚硬币抛 9 次。得到至少一个人头的概率有多大?【易】
  2. 选牌🃏: 找出从一副 52 张牌中选择一张红色牌或一张 10 的概率。【易】
  3. 补事件☯️: 给定 P{A U B} = 0.6,P{A U B'} = 0.8(其中 b '是 b 的补事件),P{A}是什么?【中】
  4. 公交车站🚎:让我们假设出现在多伦多郊区公交车站的人数服从泊松分布,平均每小时 λ = 15 。两小时内不超过 35 人出现的概率是多少?【中】
  5. 骰子滚动🎲:一个公平的骰子被掷了 n 次。对于 1,2,… 6 中的每个 k ,掷出最大数字为 k 的概率是多少?【硬】

1 —扔硬币💰

解决方案: 至少一个头可能意味着 1、2、3、… 一直到 9 个头。这需要考虑和计算很多不同的情况!

当试图解决这类问题时,我们可以试着切换到互补事件,也就是看所给事物的反面。这是解决问题时常用的一个巧妙的技巧。在这种情况下,与至少有一个头相对的是根本没有头,或者 0 个头

我们也知道一次抛不到人头的概率是 0.5 或者 50%。连续 9 次投掷,那就是 0.5⁹.所以我们有:

P{至少有一个头} =
1 — P{根本没有头} =
1—(0.5 * 0.5 …… 0.5)= 1–0。⁵⁹≈
99.8%。

那是相当高的,果然!

2 —卡片选择🃏

我们需要找到 P{ 一张 10 被选中或者一张红卡被选中 }。我们可以把 P{ a 10 被选中 }和 P{ a 红牌被选中 }相加,但也有两者都成立的情况,所以我们不应该重复计算。因此,我们希望:

P{ a 10 被选中 } + P{ 红卡被选中 } — P{ a 10 被选中红卡被选中 },

所以我们有:

4/52(52 张牌中有 4 张是 10)+26/52(一半牌是红的)— 2/52 (2 张牌都是 10 和红的)=7/13

3-☯️补充活动

回顾一下,我们知道:

*P { A U B } = P { A }+P { B }—P { A∩B }(方程式)

我们可以用 B '代替 B,得到:

P { A U B ' } = P { A }+P { B ' }—P { A∩B ' }****(方程式)。****

因为“B and B”是补语事件,所以我们有 P{B'} = 1 — P{B} 。将此代入等式**,我们得到

P { A U B ' } = P { A }+1—P { B }—P { A∩B ' }。

也可以用 P{A ∩ B}来表示 P{A ∩ B'}:

**P { A∩B ' } = P { A }—P { A∩B }(我留做练习,让你验证这个)。

回到等式**,我们有:

P { A U B ' } = P { A }+1—P { B }—P { A }+P { A∩B }(方程式) *

看看等式和**,如果我们将这两个等式相加,将会抵消几项:

P{A U B} + P{A U B'} = 2P{A} + 1 — P{A}。

现在,如果我们插入给定的数字,我们有:

0.6 + 0.8 = P{A} + 1 ,因此P { A } = 0.4**。**

4 —公共汽车站🚎

设 X 为 2 小时内出现在公交车站的人数
X ~泊松(30) ,即 X 具有均值为 λ = 30 的泊松分布。因为每小时的平均人数是 15 人,所以 2 小时内平均有 30 人。我们要找的是
P { X<= 35 }。这将等于:

**P{X = 0}(没有人出现)+
P{X = 1}(正好有 1 个人出现)+
P { X = 2 }+

+P { X = 35 }

对于任意的 k :

P{X = k} = λᵏ e^( — λ) / k!

因为 X 遵循泊松分布。我们可以手动计算上面的每个概率,这有点繁琐,或者我们可以使用下面的 Python 代码:

从 scipy.stats 导入泊松

poisson.cdf(35,30,loc = 0)

其中 cdf 代表累积分布函数,它将我们需要的从 k = 1k = 35 的所有概率相加。

上面的代码得出P { X<= 35 } = 0.843 = 84.3%。凭直觉,我们可以预期这个概率很高。如果平均有 30 人在 2 小时内到达公交车站,那么在这 2 小时内有超过 35 人到达的概率将会很低。

关于泊松分布的更多见解,请查看这篇精彩的博文。

5 — 模具辊🎲

解决像这样的抽象问题的最好方法是用一种不那么抽象的方式。这里什么东西这么抽象?嗯,我们有 n ,它可以是任何自然数,还有 k ,它可以是 1 到 6 之间的任何数。为了减少抽象的数量,我们将迭代每个 k,看看当 k 取一个特定值时会发生什么。

k = 1 时,我们希望所有的 1 都在 n 次掷骰中,这样 k = 1 仍然是最大数。这个事件的概率显然是1/6 * 1/6 …… 1/6 =**(1/6)ⁿ)。

当 k = 2 时,我们希望序列中有 1 和 2,这样最大数就是 2。掷骰子中得 1 或 2 的概率是 2/6。但是在 n 次掷骰中得到 2 为最大数的概率不是2/6 * 2/6 …… 2/6。这将假设我们可以接受序列中只有 1 的极端情况,但事实并非如此,我们需要一堆 1 和 2,但最大值必须是 2。

和另一个 k -s 也是一样的道理,如果 k = 3 ,3 是掷出的最大数的概率不是3/6 * 3/6 …… 3/6。这里能发生的是,我们可能只得到 1 和 2,或者只得到 1,或者只得到 2,这些都包含在这个3/6 * 3/6 …… 3/6的概率里。不好,不好!🧐

所以让我们在这里引入一些正式但简单的数学。设 X1,X2,…,Xn 为第一、第二、…, n 次掷骰子的结果。我们正在寻找的是:

P{ max{X1,X2,…,Xn} = k }

这实际上等于

P{ max{X1,X2,…,Xn} < = k } — P{ max{X1,X2,…,Xn} < = k — 1 }

所以,你可以看到, P{ max{X1,X2,…,Xn} < = k } 是我们在讨论 k = 2k = 3 时计算出来的。因此:

P{ max{X1,X2,…,Xn} = k } = (k/6)ⁿ — ((k — 1)/6)ⁿ.

有反馈吗?请在下面的评论中分享你的见解。想了解更多信息吗?关注我Linkedin退房 Part 2 !😎

你的第一份数据科学工作可能不是你所期望的

原文:https://towardsdatascience.com/your-first-data-science-job-might-not-be-what-you-expect-e8e75103687c?source=collection_archive---------17-----------------------

数据科学工作的期望与现实

照片由 ahmad gunnaivi 在 Unsplash 上拍摄

不管你是新员工还是有经验的专业人士,每个人在开始一份新工作时都会有期望。

你可能参加过数据科学课程,解决过项目,听说过很多真实世界的数据科学经验,但作为初级数据科学家开始工作时,你会发现可能与你预期的不同。

作为一名初级数据科学家,你的现实可能会因公司的规模和你的确切角色而略有变化,但在大多数情况下,你会处于以下情景之一。

期望与现实

1.整天建模与清理和转换数据

数据科学中最令人兴奋的任务可能是模型构建、评估和部署,但作为一名初级数据科学家,您不会参与此类任务。

准备好处理最繁琐的数据科学任务。

收集和清理数据将是你日常工作的一部分。这听起来不像构建模型那么令人兴奋,但它们代表了数据科学家工作流程的很大一部分。事实上,数据科学家几乎 80%的时间都花在收集、清理和准备数据上。那些任务将是你的,把关键的和更有意义的职能留给年长者。

尽管这些任务可能看起来无关紧要,但它们确保了数据的高质量,并且在构建模型之前至关重要。

作为一名初级数据科学家,您可能会忙于单调乏味的任务,这可能会限制您获得新的数据科学技能的时间;然而,你将能够了解你所从事的业务,并在开展更多令人兴奋的活动之前打下坚实的基础。

我的建议是:只要有可能,试着将那些不会给你的职业带来价值的任务自动化。即使是花费几分钟,但不时会停止你的工作流程的最小的任务也是值得自动化的。我使用 Selenium for web automation 、Pandas 和 OS 模块来自动化一些普通的 Excel 任务和调度作业,以便在我需要的时候运行我的 Python 脚本。

2.随时可用的数据与自己收集数据

在参加了许多数据科学课程并从 Kaggle 下载了数百个数据集后,大多数人认为总会有一个干净的数据集可供数据科学项目使用。

事实是,真实世界的数据不像你用来学习数据科学的大多数数据集那样容易处理。

图米苏在 Pixabay 上的照片

如果你为一家大公司工作,你可能有可用的数据,但是你真的能信任这些数据吗?

通常,您或其他人会从数据库中提取数据。您可能擅长 SQL,但是您从中提取数据的表可能来自不同的来源。最新的更新、最后一刻的更改和缺乏沟通可能会导致使用错误的数据。因此,您将不得不再次重做所有繁琐的任务(收集和清理数据!)做一次就已经很有压力了,但是重新开始就是一场噩梦!

如果你在一家小公司工作,事情可能会变得有点复杂。在一些项目中,没有可用的数据。万一你的公司没有钱购买这些数据(或者这些数据是非卖品),你将不得不学习网络抓取。也就是说,学习 Python 中的 Selenium 或 Scrapy 来从网站中提取数据(如果你想获得这一技能,帮助你建立自己的数据集,考虑参加这些免费和付费的课程)

我的建议:不要把数据收集想当然。尽可能多地与同事互动,以确保数据的质量,并学习网络搜集,以便能够建立自己的数据集。这两项技能都有助于你成为一名数据科学家。

3.课程中教授的工具和编程语言与贵公司实际使用的工具

数据科学课程通常包括 SQL 和 Python 等编程语言。尽管您将作为初级数据科学家使用它们,但您可能仍然需要学习在您工作的公司中使用的额外工具。

在一些公司,你可能需要学习 SAS 来分析数据,Apache Spark(或 Hadoop)来处理批处理和流处理,BigML 来处理机器学习算法,MATLAB 来模拟神经网络,或 Tableau 来进行数据可视化。

即使您的职位高度依赖 Python,您也可能需要学习您的同事使用的库。如果你已经知道 Matplotlib,你可能需要学习 Seaborn 或 Plotly,如果你知道 NLTK,你可能需要学习 spaCy 或 Gensim,等等。

根据你工作的公司,你需要学习新的很酷的数据科学工具,或者在 Excel 或 Python 中找到变通方法。

我的建议:正如一位老教授曾经告诉我的,学习一个工具背后的核心概念和理论。如果你的公司使用 tool X,学习如何使用它,并关注它背后的核心概念,这样当你跳槽到另一家公司时,你就可以很容易地习惯它。

4.寻找推动业务变革的关键洞察力与了解业务

在你成为处理关键任务的团队的一员之前,你需要了解很多关于你的公司所属的行业。

这意味着要问问题,理解你所处理的数据,阅读大量关于业务的资料。所有这些都将有助于每个数据科学家在开始构建模型之前获得足够的业务理解。

想象一下,在对业务没有深入了解的情况下,说服非数据科学家为什么您的模型输出某些值。那是胡说!

结论

作为一名数据科学家,你的第一份工作可能不是你所期望的。您可能需要处理乏味的任务,确保您拥有的数据是您想要的实际数据,收集您自己的数据,学习课程中没有教授的工具(这些工具您可能再也不会看到了!),并在跳转到更有趣的任务之前了解更多关于业务的信息。然而,所有这些都将帮助你成为一名成熟的数据科学家,并在拥有关键和更有意义的功能之前积累足够的经验。

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

如果你喜欢阅读这样的故事,并想支持我成为一名作家,可以考虑报名成为一名媒体成员。每月 5 美元,你可以无限制地阅读媒体上的故事。如果你使用我的链接注册,我会赚一小笔佣金,不需要你额外付费。

https://frank-andrade.medium.com/membership

在非 Linux 操作系统上使用 Docker 的第一步

原文:https://towardsdatascience.com/your-first-step-to-use-docker-on-a-non-linux-os-c01d60057401?source=collection_archive---------23-----------------------

动手操作:在 Windows 10 上设置 Docker 开发环境

图片由理查德·萨格雷多提供。https://unsplash.com/photos/ZC2PWF4jTHc

当你读到这篇文章时,你可能已经听说过 Docker,并决定尝试一下。你可能也去了 docker 网站,准备为你的开发机器选择一个 docker 引擎。

如果你是 Linux 用户,恭喜你,你可以点击“Docker for Linux”在你的 Linux 机器上安装 Docker。

如果你像我一样使用非 Linux 操作系统,你可能会想“让我们试试 Docker 桌面。为什么不呢?”

等等,先不要点 Docker 桌面按钮。Docker 桌面可能不是你的最佳选择。我会告诉你为什么。

在这篇文章中,我将告诉你在非 Linux 操作系统上使用 Docker 的不同方法。我将试着解释 Docker 是如何工作的。更重要的是,我将向您展示如何建立一个环境,以便您在即将到来的 Docker 开发之旅中不必处理兼容性和配置问题。

这篇文章是写给谁的?

如果您是 Docker(或容器化、虚拟化)或 Linux 开发的新手,那么本文适合您。

这都是关于新手友好的解释和实践教程。

下面是这篇文章的提纲:

  • 容器是如何工作的?
  • (Windows/Linux 上的 Docker 与 Windows/Linux 上的 Docker)
  • Docker 引擎、Docker 机器、Docker-cli 等。所有那些模糊的词
  • 非 Linux 主机上 Docker 的三个选项
  • 使用 Virtualbox 在 Win10 上动手设置 Docker 环境

附:如果你想直接使用 Docker,你可以跳到这篇博客末尾的教程。我提供了一个循序渐进、经得起未来考验的解决方案。

容器是如何工作的?

容器架构

容器是运行在单个操作系统(OS)内核之上的虚拟运行时环境,它模拟操作系统而不是底层硬件。

让我们考虑下面的图表:

左图:普通系统与带容器的右系统(图片由作者提供)

左边是没有容器的本机操作系统,其中所有应用程序共享相同的依赖项(bin 和 libs)。在右边,应用程序由容器隔离,在每个容器中,应用程序都有自己的依赖项。

这种隔离的引入可以带来许多关于安全性和 DevOps 的好处。例如,一个容器的崩溃不会级联到其他容器。隔离还解决了依赖性冲突问题(即,app1 所需的依赖性与 app2 的依赖性冲突),因为应用将在它们自己的容器内运行,具有它们自己的依赖性。

Linux 容器与 Windows 容器

像 Docker Engine 这样的容器引擎通过利用内核的资源来工作。事实是,不同的操作系统如 Windows 和 Linux 有不同的内核。因此,容器很可能需要不同的依赖项才能在 Windows 操作系统或 Linux 操作系统中工作。

因为我们希望保持容器的轻量级,而不是让单个容器同时适用于 Linux 和非 Linux 系统,所以更明智的做法是让不同版本的容器分别适用于 Win OS 或 Linux OS。通俗地说,我们把在 Windows OS 上工作的容器称为“Windows 容器”,它利用了 Win OS 内核。它的对应物是 Linux 容器。

那么为什么这和今天的话题有关系呢?最终,您需要创建容器(更具体地说,构建映像来运行容器),这些容器可以部署在 Windows 或 Linux 主机上用于生产。像 Docker 这样的容器化技术可以很好地将你的容器从一个 Linux 内核环境转移到另一个 Linux 操作系统,因为所有的 Linux 发行版都共享同一个内核。但是使用 Windows 内核的容器化应用程序(即本地 Windows 容器)不能被运送到 Linux 主机。

Docker 是为开发和管理 Linux 容器而创建的,所以如果您是 Linux 用户,在您的开发机器上使用 Docker 没有任何问题。

但是像你我这样的非 Linux 用户呢?这就是虚拟机管理程序的由来。

虚拟机管理程序:一个虚拟机引擎

管理程序(或虚拟机监视器、VMM、虚拟化器)是创建和运行虚拟机的计算机软件、固件或硬件。虚拟机是计算机系统的虚拟化/仿真。例如,如果您在 PC 上使用 Win10,管理程序可以创建一个 Linux 虚拟机,让您感觉像在使用 Linux 系统。

Ubuntu 在 Windows“内部”运行(图片由作者提供)

注意:有两种类型的虚拟机管理程序:

类型 1 (Hyper-V):虚拟机管理程序位于硬件之上。

类型 2 (Oracle VirtualBox):虚拟机管理程序位于主机操作系统之上。

Windows 上的 Docker 引擎

Windows 平台上的 Docker 桌面与 Linux 虚拟机上的 Docker 以及 Docker 工具箱

我假设您想在 Windows PC 上创建一个 Linux 容器开发环境。有三种不同的解决方案可以实现这一点:

  • 解决方案 1:Docker Windows 桌面
  • 解决方案 Linux 虚拟机上的 Docker
  • 解决方案 3: Docker 工具箱(已弃用)

由于解决方案#3 已被弃用,因此解决方案#3 的核心概念与解决方案#2 相同,让我们关注前两个解决方案。以下是解决方案体系结构的比较:

左:Docker 桌面架构与 Linux 虚拟机上的右 Docker(图片由作者提供)

我应该使用哪种解决方案?

让我们来看看这两种解决方案的优缺点。

Mac 版 Docker/Windows 版 Docker(Docker 桌面版)

优点

开箱即用,安装 Docker Desktop 后,您就可以按照配置说明进行操作了

作为默认的解决方案,Docker 正在大力开发和完善这个解决方案。

缺点

在某些 macOS 硬件组合上,音量性能可能会有点慢。

不能使用 WSL2 运行持久的后台进程。

如果不熟悉 Windows 或 MAC 命令行工具,并不会感觉更“原生”。实际上,这感觉很糟糕,因为您基本上是在非 Linux 终端上编写类似 Linux 的 Docker 命令。

关于如何使用 Docker 桌面或配置 Hyper-V 的教程不多

Mac 版 Docker/Windows 版 Docker(Docker 桌面版)

优点

它提供了一个比 Docker 桌面更自然的环境。此外,在原生 Linux 环境下创建的 Docker 教程比 Docker 桌面环境下创建的要多,这对新手来说是一个很大的吸引力。

即使 Docker 目前正与微软紧密合作,WSL(Windows Subsystem for Linux)仍需要一些时间来成熟。另一方面,VirtualBox 是一个更成熟的解决方案。这意味着更少的兼容性问题。

它是面向未来的,因为开源软件是 Docker 的容器运行时环境 RunC 和 ContainerD 守护程序的基础,后者基于开放容器倡议(OCI)行业标准,并将在开源许可证下作为开源提供商进一步开发。

缺点

管理虚拟机需要更多的开销。

虚拟机应用程序可能会导致计算机像糖蜜一样迅速陷入困境。

建议:在你自己的虚拟机上使用 Docker

正如你所看到的,我站在安装你自己的虚拟机的一边。关于这个解决方案的缺点:

因为这是可控性和开销之间的权衡,并且对于开发环境来说,可控性得到了很好的回报,所以这并不是一个很大的缺点。

这是真的,VirtualBox 可以降低你的电脑。然而,当你开始学习 Docker 时,你不太可能处理一个真正的高效服务器的复杂程度。当您认为您已经准备好了,您可以轻松地在高性能服务器或云中部署应用程序。

使用 VirtualBox 在 Win10 上安装 Docker

最后,我们来看这篇文章的本质部分。让我们来设置 Docker 开发环境。

要在 Win10 上安装 Docker,需要下载 VirtualBox 和一个 Linux OS 镜像(推荐 Ubuntu )。在本教程中,我使用的是 VirtualBox 6.1.18,Ubuntu 20.10 Groovy Gorilla。您可以选择使用其他最新版本。

使用 Ubuntu 映像创建一个虚拟机(。vdi 文件)

在 Win10 上安装 VirtualBox 非常简单。下面的步骤将从从映像创建 Linux VM 开始。

  • 选择“机器”,然后点击“新建”

(图片由作者提供)

  • 选择“机器文件夹”路径和“名称”(虚拟机将位于$ $ 目录下
  • 选择“类型”作为 Linux。选择“版本”为“Ubuntu(64 位)”。注意:如果你把你的文件夹命名为 Ubuntu 或者类似的东西,VirtualBox 会很聪明的为你选择“类型”为“Linux”和“Ubuntu(64 位)”。
  • 点击“下一步”

(图片由作者提供)

选择虚拟机的内存大小。(因为您可以稍后根据虚拟机调整内存大小,所以您可以在此选择默认设置)

(图片由作者提供)

在“创建虚拟机”中

  • 选择“使用现有的虚拟硬盘文件”
  • 如果图像(。vdi 文件)未在下拉列表中列出,请单击文件夹导航按钮
  • 选择图像,然后点击“创建”

(图片由作者提供)

(图片由作者提供)

现在,您将看到刚刚创建的虚拟机。双击以启动虚拟机。

(如果你使用的是由osboxes.org创建的图像。首次登录时使用密码“osboxes.org”。)

(图片由作者提供)

故障排除:在我写这篇文章的这一刻(2021 年初),Window Hyper-V 和 VirtualBox hypervisor 之间发生了冲突。这种冲突会导致“古鲁冥想的严重错误”,如下所示: (

(小心绿海龟。如果出现,说明有问题)

(图片由作者提供)

要解决这个问题,请转到“打开和关闭 Windows 功能”并确保取消选择以下选项

  • 超 V
  • 虚拟机平台
  • 用于 Linux 的 Windows 子系统(WSL)

(图片由作者提供)

虚拟机配置

在我们真正在虚拟机上安装 Docker 引擎之前,还有一个步骤——配置虚拟机网络,以便 Docker 引擎可以从公共注册表中获取和推送映像。它还支持虚拟机运行非本地访问的容器化服务(例如 web 服务)。

  • (当虚拟机“关闭”时),右键单击感兴趣的虚拟机实例,然后单击“设置”
  • 在弹出窗口中,选择“网络”
  • 在“连接到:”下拉菜单中,选择“桥接适配器”。(你可以在这个链接中找到更多关于 VirtualBox 适配器的信息。)

(图片由作者提供)

现在,如果您再次启动虚拟机,打开一个终端(打开终端快捷键 Ctrl+Alt+T)您可以使用“ifconfig”(或“ip addr”)查询 IP 信息

这里,我的虚拟机的 IP 地址是 192.168.1.19。如果一切正常,您应该能够从您的 VM 主机(即您的 win 10)ping 这个地址。

(图片由作者提供)

提示:在你的虚拟机和主机操作系统之间共享剪贴板,使复制粘贴命令变得容易

  • 在“设备”菜单→“共享剪贴板”下,选择“双向”。
  • 在终端复制/粘贴是 Ctrl+Shift+C/V

(图片由作者提供)

在虚拟机上安装 Docker 引擎

下面是在你的 Ubuntu 虚拟机上安装 Docker 引擎的主要步骤。(如果在 Ubuntu 中安装 Docker 引擎有任何更新,请参考 Docker 文档。)

  • 卸载旧版本
$ sudo apt-get remove docker docker-engine docker.io containerd runc
  • 设置存储库
$ sudo apt-get update$ sudo apt-get install \\
    apt-transport-https \\
    ca-certificates \\
    curl \\
    gnupg-agent \\
    software-properties-common
  • 添加 Docker 的官方 GPG 密钥
  • 然后验证您是否拥有指纹为 9dc 8 5822 9fc 7 DD 38 854 a e2d 8 8d 81 803 c 0 ebf CD88 的钥匙
$ curl -fsSL <https://download.docker.com/linux/ubuntu/gpg> | sudo apt-key add -
$ sudo apt-key fingerprint 0EBFCD88pub   rsa4096 2017-02-22 [SCEA]
      9DC8 5822 9FC7 DD38 854A  E2D8 8D81 803C 0EBF CD88
uid           [ unknown] Docker Release (CE deb) <docker@docker.com>
sub   rsa4096 2017-02-22 [S]
  • 使用以下命令来设置稳定的存储库。
$ sudo add-apt-repository \\
   "deb [arch=amd64] <https://download.docker.com/linux/ubuntu> \\
   $(lsb_release -cs) \\
   stable"
  • 安装 docker 引擎(这个包叫做“docker-ce”,代表 Docker 社区版)
$ sudo apt-get update
 $ sudo apt-get install docker-ce docker-ce-cli containerd.io
  • 通过运行 hello-world 映像,验证 Docker 引擎是否安装正确。
$ sudo docker run hello-world

现在,您将看到如下内容。

(图片由作者提供)

故障排除:典型的网络配置问题可能会导致如下所示的错误。检查您的虚拟机网络配置,并确保适配器设置为“桥接适配器”。

(图片由作者提供)

临时设置

现在,如果您直接运行 Docker 命令,它将抛出一个错误。(Docker:尝试…时被拒绝许可。)

(图片由作者提供)

如果您想以非根用户的身份使用 docker,现在应该考虑将您的用户添加到“Docker”组中。这样你就不用每次都用 sudo docker 了。这里是你能做的。(记住注销并重新登录以使其生效!)

$ sudo usermod -aG docker <your-user>
$ sudo -s
[sudo] password for ubuntu: 
root@osboxes:/home/ubuntu# exit
exit

额外教程:使用 Docker 映像部署微服务

这个子教程将模拟供应和管理一个名为 NextCloud 的企业文件共享站点的过程。

Nextcloud 是一个开源软件套件,可以使用存储容量来保存、编辑和使用各种文档类型,包括音频/视频呼叫托管等服务。

我选择 NextCloud 作为一个例子,向您展示在测试/生产环境中部署应用程序(在这里是一个 web 服务)的容易程度。所以,我们开始吧。

NextCloud 文件共享服务(图片由作者提供)

  • 首先,从 Docker Hub 注册表中提取 NextCloud 映像(这可能需要一段时间)
$ docker pull nextcloud
  • 使用 apache 映像,它包含一个 web 服务器并公开端口 80。
$ docker run -d -p 8080:80 nextcloud

现在,如果您在虚拟机的 web 服务器(例如 Firefox)上输入“localhost:8080”或者更好的输入,请尝试在您的主机 PC 上使用“IpAddress:8080”(在我的例子中是 192.168.1.19:8080)访问 NextCloud。

NextCloud 欢迎页面(图片由作者提供)

哇哦,你有一个专业的文件共享系统。你可以创建一个帐户,玩一玩,看看 NextCloud 能为你提供什么。

注意:NextCloud 演示旨在让您体验 Docker,而不是用于生产。这里有一篇博客给你详细指导如何使用 Docker 部署生产级 NextCloud 服务。

这只是使用 Docker 容器创建服务的一个例子。拓展这个思路;您可以创建几个虚拟机,在每个虚拟机上部署各种应用程序,并让它们相互协作,就像微服务架构一样。

摘要

我希望现在你有足够的信心和兴奋开始玩 Docker。回顾一下,让我们看看这篇文章的一些要点。在这篇文章中:

  • 您了解 Windows 容器和 Linux 容器之间的区别
  • 您了解了使用虚拟机管理程序作为接口在本机 Windows 操作系统环境中开发 Linux 容器的不同解决方案
  • 您将学习如何使用 VirtualBox 创建一个 Linux VM 作为 Linux 容器开发的环境。此外,您还将学习在 Linux 虚拟机上安装 Docker 引擎,并在虚拟机上运行容器。

我希望你喜欢这个博客。下次见。;)

你朋友的朋友比你多

原文:https://towardsdatascience.com/your-friends-have-more-friends-than-you-e005796841bb?source=collection_archive---------32-----------------------

入门

友谊悖论的友好介绍

不友好的事实

你的朋友很可能比你有更多的朋友。你可能会觉得这令人谦卑、伤人、不可思议、不可能,或者只是古怪。但这是事实。

你的朋友比你朋友多到底意味着什么?假设你有 N 个朋友。你的第一个朋友有 F₁的朋友,你的第二个朋友有 F₂的朋友,等等。那么,你们每个朋友的平均好友数就是=(f₁+f₂+……)/n,对于你们大多数人来说,你们每个朋友的平均好友数(即< F >)大于你们的好友数(即 n)。

作者图片

自我挫伤不止于此。对于你所在的任何一个网络来说,相对于你的人脉来说,你的低连接性都是真实的。比如在 Twitter 或者 Medium 上,你的关注者比你的关注者多。或者说,在 LinkedIn 上,你的人脉比你多。或者,如果你是一名研究人员,你发表论文的合作者比你有更多的合作者。

甚至非感知实体的网络也遭受这种现象。大多数网页不如它们链接的网页突出。大多数研究论文被引用的次数比它们自己引用的论文少。

怎么会这样呢?这只是理论上的好奇,还是有更实际的意义?

为什么你没有你的朋友受欢迎?

将网络建模为图形。例如,在友谊网络中,每个个体都是图中的一个节点(或顶点)。如果两个人是朋友,那么有一条边(或链接)连接这两个人的节点。

这里有一个图表示例,表示一个由 6 个人组成的网络(简单地命名为 A、B、C、D、E、F ),其中每个人都是另外两个人的朋友:

作者图片

在这个例子中,6 个人中的每一个都有 2 个朋友。一个人的两个朋友中的每一个都有两个朋友。所以,每个人的朋友数量和他们每个朋友的朋友数量是一样的。那是相当多的。而且甚至没有体现出大多数人朋友比朋友少的属性。怎么回事?不像这个例子,真实的网络不是同质的。换句话说,在真实网络中,每个节点的连接数并不相同。此外,有少量离群点节点比其余节点连接得更多。让我们在示例图中添加一个这样的异常值 Z:

作者图片

现在,A 到 F 每个人都有 3 个朋友。Z 有 6 个朋友。对于 A 到 F 中的每一个,2 个朋友有 3 个朋友,1 个朋友有 6 个朋友,平均每个朋友有(3 + 3 + 6) / 3 或 4 个朋友。所以 A 到 F 每个人都不如他们的朋友受欢迎。只有 Z 比他们的朋友更受欢迎。Z 的 6 个好友,每个都有 3 个好友。所以 Z 的朋友平均有 3 个朋友,而 Z 有 6 个朋友。离群值 Z 扭曲了其他人的结果,他们变得不如他们的朋友受欢迎。

一般来说,如果网络中有一小部分 f 的节点比其余节点的连接度高得多,那么网络中接近 1-f 的节点的连接度将比它们的连接度低。大多数现实世界的网络,尤其是社交网络,有一小部分节点是高度连接的离群点。

我们可以用一个精确的数学结果来收紧手势。如果μ是网络中每个节点的平均连接数,σ是每个节点的连接数的标准偏差,那么朋友的朋友的平均数量正好是μ + σ /μ,只要节点间的连接数有一些变化,它就大于μ。所以,任何异质的网络都有比朋友更多的朋友。

下表显示了这种现象的三个例子。第一个示例是一个随机模拟的网络,其中 99%的节点是典型连接的,1%的节点是高度连接的。第二个例子使用的数据来自脸书友谊网络的一个小的子集。第三个例子使用理论物理预印本档案中所有研究合作的全部数据。在所有三种情况下,连接的平均连接数都大于平均连接数。并且很大比例的节点比它们的连接更少连接。

作者模拟计算

给朋友打电话

既然你已经接受了你的朋友比你更受欢迎的事实,你可以耸耸肩,继续前进。或者,你可以继续阅读,看看如何利用这些知识。

假设您想要影响在网络中连接的节点之间本地传播的东西的传播。例如,通过面对面交谈在社区中传播的信息,或者通过身体接触在人群中传播的传染病。如果你知道网络中哪些节点的连接度最高,你当然可以利用这一点来影响传播。例如,为了尽可能快地传播信息,将信息播种到连接程度最高的节点。但是,实际上,您通常不了解如此详细的网络拓扑知识。在这种情况下,一个简单的替代方法是随机选取一些节点,然后期待最好的结果。但是,有了本文中的知识,您可以使用“给朋友打电话”策略做得更好:我不播种随机采样的节点,而是播种该节点的随机连接。为什么这有意义?因为我们现在知道一个节点的连接比节点本身更有可能有更多的连接。这真的有用吗?绝对的。我用三个例子来演示。

快速八卦

假设你想在一个社区的少数人中传播一条信息,并依靠社区成员之间的流言蜚语来传播这条信息。如何挑选种子人群,让信息快速传播?使用给朋友打电话的策略。随机选择一组人,但不要给他们灌输信息。相反,让随机组中的每个人提名他们的一个朋友。然后用信息播种被提名的朋友。

作者图片

上图显示了模拟的结果。这些线条显示了随着流言的传播,被“感染”(即收到信息)的人的累计百分比。蓝线是随机播种 1%节点的基本情况。红线显示了从随机 1%的节点中播种每个节点的随机朋友后的传播。显然,红线显示的是每轮八卦后信息传播的速度更快。在第一轮之后,信息传播到 12%(与 3%的基线相比)。在第二轮之后,它扩散到 43%(与 20%的基线相比)。诸如此类。

如果你感兴趣,这里有更多关于模拟的细节(否则可以跳过这一步)。我构建了一个包含 10,000 个节点的随机图,其中 99%的节点与其他节点的连接关系比较密切(2%),1%的节点与其他节点的连接关系非常密切(71%)。这产生了一个图,其中一个节点的平均连接数约为 9,连接的平均连接数约为 56,并且 96%的节点的连接数少于它们的连接数。信息的传播是通过以下方式模拟的:在一轮八卦中,每个拥有信息的人都将其传播给随机的四分之一的关系人,然后对此感到厌倦,这样他们就不会在下一轮八卦中传播它。

早期发现

如何通过监测社区中的一小部分人来快速检测传染病在社区中的传播?以随机抽样的方式监视每个人的一个随机朋友。由于一个人的朋友通常比这个人有更多的朋友,这种疾病应该在电话给朋友策略下被监控的人群中更快出现。

作者图片

上图显示了模拟的结果。这些线条显示了随着疾病在社区中传播,受监测的感染人数。蓝线是监测随机 1%的人的基础情况。红线是从随机选择的 1%的人中监控每个人的随机朋友的情况。显然,红线表明,通过监测随机抽样的朋友,可以比通过监测随机抽样更早地发现传播。第一轮后,受监控的朋友中有 23 人被感染(基线时为 0)。在第二轮测试后,在 48 个随机的朋友中发现了这种疾病(相比之下,基线是 16 个)。诸如此类。

选择性免疫

这里有一个与全球疫情时代非常相关的场景,需要优先为谁接种疫苗。如果疫苗只够一小部分人使用,一个有效的策略是给每个随机选择的人的随机朋友接种疫苗。

作者图片

上图显示了模拟的结果。这些线条显示了感染人群的累计百分比,从随机的 1%的感染者开始。蓝线是随机接种 10%人群的基本情况。红线是从随机选择的 10%人群中给每个人的随机朋友接种疫苗。红线显示电话给朋友策略显著降低了传播。到第二轮,这种疾病只传播到 5%的人(基线时为 17%)。到了第三轮,这一比例扩大到了 8%(相比之下,基线时为 40%)。

摘要

  1. 在节点间的连接数存在一定差异σ的网络中,每个节点的平均连接数μ比连接的平均连接数少σ / μ。
  2. 如果一个网络有少量高度连接的节点,那么大多数其他节点的连接数都少于它们的平均连接数。所以,举例来说,在一个友谊网络中,一个典型的人的朋友数量会比他的朋友的平均数量少。
  3. 与节点本身相比,节点的连接的更高的平均连通性可以被用来影响使用朋友电话策略的网络中的信息/感染的本地传播。随机抽样网络中的一小组节点(基线样本)。用节点的随机连接替换采样集中的每个节点(朋友样本)。朋友样本将比基线样本具有更高的连接度。如果你在朋友样本中植入一些适合八卦的信息,它会比植入基线样本传播得更快。如果你监测朋友样本的传染病传播,你会比监测基线样本更早地发现它。如果你对朋友样本进行传染病免疫,你将比对基线样本进行免疫显著地减缓疾病的传播。

参考

  1. 斯科特·费尔德 1991 年的原始论文:https://www.journals.uchicago.edu/doi/10.1086/229693
  2. 给你的邻居接种疫苗:【https://physics.aps.org/story/v12/st23
  3. 用友谊悖论来取样一个社交网络:【https://physicstoday.scitation.org/doi/10.1063/1.3518199
  4. 斯坦福大型网络数据集集合:https://snap.stanford.edu/data/

你的基本 SQL 指南,同时学习以太坊

原文:https://towardsdatascience.com/your-guide-to-basic-sql-while-learning-ethereum-at-the-same-time-9eac17a05929?source=collection_archive---------4-----------------------

照片由 Gia Oris 在 Unsplash 上拍摄

实践教程

事实证明,以太坊是一个非常棒的关系数据库,可以用来学习和练习 SQL 查询!

如果你正在寻找更多的 web3 数据内容,请查看我的 30 天免费课程(带视频) !请务必 订阅我的简讯 了解更多课程和教育技巧。

> > 我在这里写了一个更最新更好的指南<<

当你学习如何在智能合约上存储和管理数据时,以太坊更容易理解,当你有不止两三个基本数据表要查询时,SQL 更容易学习。

到本文结束时,您将了解以太坊的基础知识,以及以下 SQL 函数:

  • 选择、位置、限制
  • 总和、计数、最大值、分组依据、拥有
  • 不同,计数不同
  • 加入
  • 时间转换/解析(日期 TRUNC,时间间隔)
  • 联合和联合所有

我还将做一个中级和高级 SQL/以太坊指南,所以如果你有兴趣阅读的话,一定要跟着我。在 Dune Analytics 上会有所有这些查询的链接,所以你可以随时编辑和运行它们。

什么是关系数据库?

如果您没有关系数据库的一般背景,让我们来分析一下它们是什么。关系数据库包含一组数据表,因此用户可以查询一些表的组合,只获得他们需要的数据(行)的子集。这通常比尝试将所有数据存储在一个文档中,然后在每次执行某个操作(无论是运行模型还是创建数据可视化)时读取整个数据集更有效。

表格可以被认为是 CSV 文件。每个表都有一个列键,每个列都有一个特定的数据类型(string、integer、boolean、JSON 是一些常见的例子)。每个表必须包含一个主键,用于确保特定列中的数据是唯一的(因此,如果您存储事务,那么该表中的每一行都有一个唯一的事务 id)。表还可能包含一个外键,它是关系数据库表中的一列或一组列,提供两个表中数据之间的链接。常见的外键包括从日期和时间到客户 id 或地理点的任何内容。

下面是一个关系数据库的例子,其中显示的字段是列键。PK 是主键,FK 是外键。

关系数据库的例子( CC 许可)

SQL 允许我们查询这些数据库——就这么简单!SQL 有各种版本,但略有不同,对于本文,我们将使用 PostgreSQL。

以太坊上的数据表是什么?

现在对于以太坊来说,你需要知道的是,有接近无限数量的钱包可以容纳这个名为 ETH 的令牌,而 ETH 经常充当你在区块链上进行交易时必须支付的汽油。也有智能合约,其行为类似于钱包,但可以同时进行一系列复杂的交易,但在这一点上不要太担心智能合约是什么只需将它们想象为绑定了 API 函数的数据库表。一旦你学会了如何浏览存储在合同上的数据,那么理解功能和语言就变得容易多了。

假设您已经创建了一个新的智能合约(即数据库表),并且想要发行一种名为 USDC 的新货币。这种智能合约将跟踪您发行的 USDC 总量、每个人钱包中的 USDC 余额以及所有使用 USDC 的钱包之间的历史支付(交易作为数据库表行)。

ETH 的价值就像实体经济中的天然气/石油一样,是不稳定的,很大程度上取决于供求关系。您可能想要一个更稳定的令牌(如 Dai)发送给其他方进行支付,但也不想失去 ETH 的任何上升潜力。要做到这一点,可以创建另一个智能合约,允许您存储 ETH 作为抵押,以向 Dai 借款。如果汽油的类比没有意义,那就把它想象成从你房子的价值中借美元(或你的当地货币)。

这种借贷方式是以太坊上最大的产品类型之一,其中 Aave 、 Compound 和 Maker 是一些主要的合同提供商。在本文的其余部分,我们将探索这些抵押债务头寸(CDP,或有时称为保险库)的属性,通过使用 SQL 查询来了解它们是如何工作的。

请记住,以太坊是数据库,智能合约是数据表,来自钱包的交易是每个表中的行

选择、位置和限制

智能合约通常在调用事务时发出函数调用和事件(日志)。作为契约的子集,每个函数和事件都是我们可以查询的表。

我们可以使用 Etherscan 来探索合同的功能,在本例中,是 Aave lending pool 合同。

在你借钱之前,你必须先存入抵押品。我们可以运行第一个查询来查看最近的 100 笔存款交易,方法是从 Aave lending pool 存款数据表中选择所有列(SELECT * FROM)并用(LIMIT 100)限制返回的行

https://duneanalytics.com/queries/34562

返回的前几行,列被截断

让我们分解查询返回的键列:

**_user**:发起存款的钱包地址

**_reserve**:作为抵押品存放的代币的地址

**_amount**:代币金额

**_timestamp**:交易被挖掘到区块链的时间戳

这是介绍地址的好时机。地址要么属于钱包,要么属于智能合约,在以太坊上总是唯一的。

让我们再次运行我们的查询,但是现在使用了一个WHERE语句来只过滤 ETH 储备(ETH 是特殊的,因为它没有智能合同,因为它在更深的层被跟踪,所以 Dune 使用\xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee作为地址。

https://duneanalytics.com/queries/34609

WHERE作为一个过滤器,意味着只有满足您设置的条件的行将被保留在返回的数据中。上图中,你可以看到_reserve列中的所有地址现在都代表 ETH。

前面我提到有一个智能契约跟踪我们的 USDC,该契约可以在地址0xa 0b 86991 c 6218 b 36 C1 d 19d 4 a 2e e9 EB 0 ce 3606 EB 48找到。让我们再次运行上面的查询,但是是在 lending pool borrow 数据表上,过滤 USDC。

https://duneanalytics.com/queries/34613

这里,我们对一组指定的列而不是所有的列运行了SELECT,正如您所看到的,这些正是返回的列。

计数、总和、最大值、分组依据、拥有、排序依据

既然我们已经知道了如何从表中选择列并根据某些条件进行过滤,我们将希望开始使用聚合函数来提供更多信息。

COUNTSUMMAX这样的聚合器会绕过您想要执行算术运算的列。

【https://duneanalytics.com/queries/34615】

sum列给出了 USDC 所有历史借款的总和_amount(因为这被 USDC 过滤掉了_reserve)。count列将给出该列中的行数。

我们返回的表中的这些名称信息不多,所以接下来我们将使用as重命名这些列。

如果你以前在 excel 中使用过数据透视表,那么GROUP BY就不会太难理解。如果没有,可以把它想象成对作为透视依据的列中的每个唯一 id 运行 select 查询。我们用_borrowRateMode,浮动利率贷款用1,固定利率贷款用2

https://duneanalytics.com/queries/34617

注意,我们将“sum”重命名为“usdc_total ”,这改变了我们返回的表中的列名

从这一点上,我们可以看到,似乎没有压倒性的偏好固定或浮动利率贷款。注意,我使用了GROUP BY 1,它将基于我们的SELECT查询中的第一个词。如果我放入GROUP BY 2,那么它会试图通过求和来透视它,这会返回一个错误,因为你不能通过聚合函数来透视。

我们现在可以引入第二个过滤函数HAVING。记住GROUP BYWHERE已经运行之后运行,所以如果我们想要过滤透视表,那么我们就不能再使用WHERE。让我们透视用户借的 USDC 总额,其中用户必须有大于 1,000,000 的借款。

https://duneanalytics.com/queries/34621

让我们对这个查询做一个小小的调整,我们希望按照 usdc_total 从大到小对表进行排序。为此,我们在查询的末尾添加了ORDER BY,并指定了一个列和DESC

与上面相同的查询链接

看起来有人借了超过 231 万亿 USDC!注意,这不是一笔未偿还的借款,所以已经偿还了很多次。

不同,计数不同

根据上一次查询中的行数(3,721),我们已经可以看到在 Aave 上为 USDC 调用 borrow 函数的用户的唯一数量,但是我们也可以使用DISTINCT函数在不进行旋转的情况下完成这项工作。COUNT DISTINCT是一个组合,我们可以用它来计算不同事件的数量。当我们想要计算某一列相对于另一列的唯一值时,这很有用。值得注意的是,如果您正在寻找一些特殊的组合,您可以在DISTINCT中放置多个列名。假设我们想要查看每个用户从 Aave 借用了多少种不同类型的令牌:

https://duneanalytics.com/queries/34625

总的来说,Aave 有 10,320 个不同的用户借用了某种令牌,其中一个人最多借用了 17 个不同的令牌(如 USDC)。

外部(如左)和内部连接

到目前为止,我们只是一次在一个表上运行查询——连接是 SQL 的一个重要部分,因为它被称为关系数据库是有原因的(还记得开头提到的那些外键吗?).您可以指定七种类型的连接,如下所示:

CC 许可下的图像

这是将我们的 lending pool 存款数据表带回到我们的查询中的好时机。让我们使用用户地址的内部连接来连接存款和借款,然后过滤 ETH 存款和 USDC 借款的储备对。

我们不能在原始的 Aave 借和存表上直接这样做,因为我们没有一组外键。取而代之的是,我们将进行子查询,在子查询中,我们将获得独立用户的枢轴以及从 USDC 借入或存入 ETH 的总金额。然后我们可以在 users 列上做一个内部连接。

https://duneanalytics.com/queries/34628

ETH 目前的价格是 2400 美元……所以已经存了很多 ETH 了!同样,这没有考虑还款或提款,所以这不是一个未偿金额。

我们的查询现在已经变得很长了,所以让我们逐个函数地分析一下。

(SELECT "_user", SUM(_amount) as USDC_total_borrowed FROM aave."LendingPool_evt_Borrow"
WHERE _reserve = '\xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
GROUP BY 1) as borrow

在这里,我们创建了一个子查询,它从前面的返回相同的表(没有HAVING),然后将该表重命名为“borrow”。我们对“存款”也重复这一点。现在我们有了两个有外键的表(_users),我们可以做一个INNER JOIN来在每个表的外键列ON deposit.”_user”=borrow.”_user”中只保留既存了 ETH 又借了 USDC 的用户。最后是选择部分,在这里我们使用点连接符SELECT borrow.”_user”, borrow.USDC_total_borrowed, deposit.ETH_total_deposited指定我们想要的表和列的组合。为了让这在将来更具可读性,我们可以使用常见的表表达式( CTE ),但是我将在下一篇文章中介绍这一点。

时间转换

Postgres 有一个可爱的名为date_trunc的日期解析函数和时间管理器interval,这使得处理时间戳数据变得轻而易举。假设我们想看看在过去的 7 天里 USDC 每天被借走了多少:

https://duneanalytics.com/queries/34631

date_trunc(‘day’, evt_block_time)获取 evt_block_time 列并解析它,以便给定一天中的所有时间戳都设置为该日期的午夜。我们也可以这样做几分钟、几周、几个月或几年。

我们添加了borrow.evt_block_time > now() — interval ‘7 days’作为另一个WHERE条件,只保留在查询运行(now())的一周内具有evt_block_time的行。

如果您以前没有使用过 DateTime 对象,请相信我的话,这两个函数(date_truncinterval)为您节省了大量工作。

联合和联合所有

这是最后一部分,希望这是一个容易理解的部分。联接按列工作,而联合是当我们想按行组合两个查询或表时。关键是使用UNION删除完全相同的行,而UNION ALL没有。

Aave 只是一个 lending pool/CDP 协议,所以让我们也将它与 Compound 进行比较。我们将获取每个协议的唯一用户和他们的总 USDC 借款,并获取联合。

https://duneanalytics.com/queries/34635

你可以在这里看到,前 13 名 USDC 借款人中只有一人使用 Aave——这可能有很多原因,包括他们的抵押品在哪里和借款利率。

我通过唯一用户和他们的总 USDC 借款进行旋转查询,然后在查询之间使用UNION ALL,最后通过usdc_borrow对组合表进行排序。另外,请注意,我创建了一个名为lending_pool的新列,在每个子查询中使用默认值aavecomp

你成功了!🎉

如果你做到了这一步,那么恭喜你!您已经掌握了使用 SQL 进行基本数据探索和分析的足够知识。如果你想进一步了解以太坊,请查看我的另一篇文章,这篇文章深入探讨了以太坊的工作原理。

如果您还没有点击任何查询链接,我强烈建议您这样做,因为 Dune Analytics 是一个测试查询和快速创建可视化甚至仪表板的好地方。当我开始学习 SQL 时,我希望有一个这样的工具来练习,而不是依赖 hackerank 或 leetcode 中的表(或者摆弄本地服务器,用网上的基本模拟数据表填充它)。

再次提醒,请关注本系列的下两部分!

您的中级 SQL 指南,同时学习以太坊

原文:https://towardsdatascience.com/your-guide-to-intermediate-sql-while-learning-ethereum-at-the-same-time-7b25119ef1e2?source=collection_archive---------8-----------------------

照片由 Unsplash 上的 israel palacio 拍摄

实践教程

让我们使您的查询更高效、更易读,同时也帮助您理解像以太坊上的 Uniswap 这样的去中心化交换。

如果你正在寻找更多的 web3 数据内容,请查看我的 30 天免费课程(带视频)

如果你错过了 SQL 和以太坊基础的第一部分,请务必先阅读第一部分。在 SQL 方面,今天我们将讨论这些稍微难一点的主题:

  1. 公共表表达式(cte)
  2. 自连接
  3. 窗口功能,如分割、超前、滞后、整体
  4. 在查询中使用索引来提高操作速度。
  5. 子查询以及子查询对查询效率的影响

在以太坊方面,上次我们学习了贷款池和抵押债务头寸。这一次,我将向你介绍一种分散式交易所(DEX)——把它想象成一种外汇,你可以把美元换成欧元,但你的汇率取决于你当地银行里每种货币的剩余量。把“货币”换成代币(还记得我上次讲的 USDC 智能合约)把“银行”换成 DEX 智能合约就行了。我们将关注的 DEX smart 合约是 Uniswap,它在上周(4/11/21)处理了价值8,870,691,188 美元的交易。

像往常一样,在 Dune Analytics 上保存了所有这些查询的链接,所以你可以随时编辑和运行它们。如果你没有回头看上一篇文章,那么这是帮助理解我们正在查询什么的初级读本:

记住,以太坊是 数据库 ,智能合约是 数据表 ,从钱包发送的交易是 每个表中的行 。钱包和智能合约都有地址, 在以太坊上永远是唯一的。

公共表表达式(cte)

从技术上来说,Uniswap 不仅仅是一个单一的智能合同,它会遇到某些合同大小限制( 24 KB ),并且开发起来极其复杂。正因为如此,我们的查询将大量地与子查询交织在一起,以获得我们想要的跨契约的数据。将子查询放在查询中是没有效率或可读性的,尤其是在多次使用同一个子查询的情况下。这就是 cte 发挥作用的地方!

如果你以前用面向对象的语言编写过脚本,比如 Python,那么你应该熟悉在变量中存储数据。cte 就像 SQL 的变量,在这里,您将一个子查询作为一个可重用的表存储在查询的其余部分中。

在我们开始查询之前,先快速回顾一下契约模式。Uniswap 遵循契约工厂模式,这意味着两个令牌之间的交换有一个基本模板( UniswapV2Pair.sol ),用于从 Uniswap 工厂契约部署新的令牌对。

图片作者( ERC20 参考)

现在,让我们看看是否可以获得上个月创建的所有对以及它们最近的储备余额(即,每个令牌还剩多少)

https://duneanalytics.com/queries/35061

不要被长度吓到!让我们一行一行地完成这个查询。WITH标志着 CTE 的开始,随后是我们的第一个 CTE cp,它是过去 7 天内创建的所有配对的集合。r是所有储备余额在过去 7 天内更新的货币对的集合。r_recent仅保留来自r的每个唯一配对合同(contract_address)的最新更新。现在不要担心partitionrow_number(),稍后我会解释这些概念。最后,我对协定地址对cpr_recent做了一个INNER JOIN运算,得到了我们的最终表。

根据行计数,我们可以看到在过去 7 天中创建了 390 个新对。请注意,pair_address 是 UniswapV2Pair.sol 的以太网地址,它是在从 UniswapV2Factory.sol 协定调用 createPair()后为特定的 tokenA/tokenB 对部署的。

您能想象使用子查询完成所有这些吗?不仅其他人很难读懂,调试起来也更加困难和耗时。使用 cte,我可以单独构建和测试每个视图,而不必等待整个查询重新运行。

自连接

上面提到的工厂和配对基础契约都是非常低级的,不经常被直接调用。相反,Uniswap 创建了一个 Router02 契约,它以一种用户/开发人员友好的方式抽象出了大部分契约复杂性。一旦创建了新的货币对合约,你现在可以通过 Router02 合约将流动性添加到其流动性池中(这些储备必须以某种方式进入合约中!).流动性池在分散金融中是一个非常重要的概念,所以我会花一些时间来解释它。

当增加一对货币的流动性时,你是在增加来自其他流动性提供者(LP)的储备的流动性。uni WAP 要求您根据每个令牌的$值添加 50/50 的令牌,然后您将获得一个 uni WAP 池令牌,代表您在总池中的份额。下面是某人通过 Router02 增加流动性的例子:

USDC/USDT 资金池增加流动性并收回统一 V2 令牌交易示例

当有人想用一个代币交换另一个代币时,他们会增加一个代币的储备,并从另一个代币的储备中提取——从而影响两个代币之间的汇率。我们将在下一节更深入地探讨互换和汇率定价曲线。

现在,假设我们想要获得所有向同一个池中添加了流动性的有限合伙人的集合。这就是SELF JOIN真正有用的地方,因为我们可以基于第二列在同一列的行之间建立关系。一个很好的例子是,当你有一列姓名和一列地点时,你用SELF JOIN来显示谁(原始姓名列)和谁(重复姓名列)住在同一个地点。这里,我们有一列按地址排列的有限合伙人,我们希望看到哪些有限合伙人向同一个池中添加了流动性。

https://duneanalytics.com/queries/35058

我的查询创建了在LP的过去 7 天中为每个令牌对增加流动性的唯一 LP 的 CTE。在最后一个查询中,我使用了CASE WHEN使该列比合同地址(存储在pair中)更具可读性,这只是一个“if-else”语句。SELF JOIN的关键是在最后两行,我选择了所有提供给同一个池的 LP 作为所选择的锚定 LP。然后,这将遍历该表,并列出每个配对契约的相关 LP,因此,如果我有 4 个 LP 提供给同一个配对契约,那么我将得到 12 行(4 个 uniques 充当锚,每个锚有 3 行/related _ LP)。第 20 行中的“不等于”操作符<>确保我们跳过锚点,否则它也会显示为一行。最后,第 21 行充当一个典型连接的ON操作符,用于连接LP1LP2

请注意,WETH 与 ETH 相同,只是在一个令牌包装器中实现了标准一致性

如果您的列有许多共享一个目标列的惟一 id,那么这个查询将很快耗尽内存(在本文末尾讨论相关子查询时,我们将回到这个问题)。这就是为什么我限制了日期间隔并预先选择了两个标记对(否则 77,000 个 LP 和 10,000 对可能会有超过一百万行)。我们现在不会对这些数据做任何其他事情,但我们可以使用它来开始对用户进行聚类,或者对成对的流动性池市场进行图形节点分析。

窗口功能,如分割、引导、滞后、整体等

到目前为止,我们已经引入了许多新概念,所以你可能会感到不知所措。如果是这样的话,休息一下或者玩一下链接查询,不要强迫自己一次完成所有这些!

在本节中,我们将讨论互换交易,例如,如果我想用我的天气换 USDC。为了理解这一点,我们需要了解 DEX 如何对互换进行定价:

x*y = k

这是一个非常简单的公式!x是 WETH 的合约准备金,y是 USDC 的合约准备金,k是他们的总流动性。这给了我们一个类似这样的定价曲线:

https://unis WAP . org/docs/v2/protocol-overview/how-unis WAP-works/

这意味着,当我用令牌 A 交换令牌 B 时,令牌 A 的储备增加,而令牌 B 的储备减少,这使得将来用令牌 A 交换令牌 B 的成本略微增加。如果这对你有意义,那么拍拍你自己的背——这篇文章的其余部分不再有新的以太坊概念了!

但是还有很多 SQL 需要学习,从窗口函数开始。可以认为这是允许聚合、偏移和统计函数,而不需要使用GROUP BY并保持原始表不变——您只是根据每行中的值添加列。

通常我们有一些特殊的功能,后面是OVER,然后是窗口(PARTITION BY column_name)。之前,我们的特殊函数是ROW_NUMBER(),它从 1 开始为每个唯一分区计算新行rn(在这种情况下,每个唯一对在contract_address中收缩)。这就是我们如何能够保持每对合同保留同步的最近一行(rn=1)。

ROW_NUMBER() OVER (PARTITION BY contract_address) rn

另一种看待 window/ PARTITION的方式是将它视为最初用于GROUP BY的列,尽管它也可以是只有一个唯一值的列。让我们试着计算一下过去 7 天里从 WETH 到 USDC 的所有掉期交易的百分位数。我们仍然使用 Router02 契约,但是使用了swapExactTokensForTokens()函数而不是addLiquidity()

https://duneanalytics.com/queries/35980

这里我们使用统计函数NTILE()代替计数器ROW_NUMBER()NTILE()将根据瓷砖总数(四分位数NTILE(4)、五分位数NTILE(5)、百分位数NTILE(100)等)为每行分配瓷砖。因为我们两次使用同一个窗口,所以我们可以创建一个WINDOW变量来减少冗长。contract_address对于整个列只有一个唯一值,因为它总是被调用 Router02,否则我们将有多个分区,并且contract_address中的每个唯一值将得到它自己的 100 个NTILE的集合。

下面的图表显示了我们的查询结果:

视觉化告诉我们的不仅仅是这里的表格。这个数据严重失真,所以从技术上来说对数会是一个更好的观点。

对于在 windows 中使用聚合,这里有一些基本的例子你可以遵循,因为我不会在这里展示任何例子。我确实想涵盖像LEADLAG这样的偏移函数,因为它们对于时间序列趋势分析非常有用。

https://duneanalytics.com/queries/36082

这里我们创建一个名为DAL的 CTE 来代表过去 28 天 wet 到 USDC 互换的每日总量amountIn。然后,当从DAL查询时,我们创建两个LAG列,一个滞后 1 行(天),另一个滞后 7 行(天):

如果我们在每个LAG栏前添加一个ETH_swapped_to_USDC -,那么我们可以得到掉期交易量的每日和每周差异:

我将查询从 28 天更改为 100 天,以提供更好的图表。

在查询中使用索引来提高操作速度。

每当您进行查询时,都会根据脚本中的操作运行一个执行计划。虽然已经为您处理了大量优化工作,但是您希望以最有效的方式组织查询和表。我们将在下一节讨论子查询的排序,首先让我们讨论索引。

如果列是主键,这意味着您可以创建一个链接到它的聚集索引。如果列是外键(非唯一值),则可以为其附加非聚集索引。在查询数据时,拥有这些索引会导致很大的差异,尤其是在使用WHERE和/或JOIN时。如果没有索引,您的查询将作为表扫描运行(即线性通过每一行)。对于索引,将改为运行索引扫描/查找。这就是二分搜索法搜索与线性搜索的区别,导致 O(log n)对 O(n)的搜索时间。最终,创建索引会导致更长的写时间(因为每次添加新数据时,索引都必须重新排序),但最终会获得更快的读时间。如果你想要一个更长的浏览和索引与非索引查询的比较,我强烈推荐你看一下这个视频。

即使表没有索引,您也可以通过在您正在过滤或连接的列上使用ORDER BY来改进线性扫描。这可以使查询更加高效,尤其是对于连接。

子查询以及子查询对查询效率的影响

最后,让我们谈谈子查询。有两种类型的子查询:相关子查询和非相关子查询,在相关子查询中,必须为外部查询的每一行重新计算子查询,在非相关子查询中,子查询只计算一次,然后用作外部查询的常数。

下面是一个不相关的子查询,因为在检查外部查询中的每一行“amountIn”之前,SELECT MAX(“amountIn”)/2 FROM swaps只被评估一次。

https://duneanalytics.com/queries/36434

在过去的 7 天里,只有 4 个掉期交易大于最大掉期交易的一半,根据之前的 NTILE(100)图,这是有意义的。

下一个查询是一个相关子查询,它看起来非常类似于一个SELF JOIN,但是只将行与其自身进行比较,而不是进行连接。我们只想获得 Uniswap 中每一对的平均互换金额:

https://duneanalytics.com/queries/36436注意,我正在检查交换路径是否相同,因为这通常表示一个令牌对。

尽管这仍然是一个聚合函数比较(使用 AVG 而不是最大值),但这个比较在超过 30 分钟后超时,而非相关查询用时不到 5 秒。您可以通过将相关子查询转换为连接子查询来加快查询速度,这只是需要一些心理准备。我们用这种方法把它缩短到 28 秒:

https://duneanalytics.com/queries/36436(上次查询的扩展)

有些路径不止一次交换,我认为这是存在的,如果没有直接对交换,所以它必须通过令牌 A -> WETH ->令牌 B

如果您绝对必须使用相关子查询,那么您应该利用我们在上一节中讨论的索引——否则它会花费很长时间运行,因为它变成了一个n_rows*n_rows = O(n²)比较操作。

最后,如果任何父查询中有多个子查询,请确保所有子查询都以最有效的方式排序。尤其是在加入时,确保在加入之前使用WHEREHAVINGT7 进行过滤,而不是之后。如果您的查询花费的时间太长,请尝试考虑子查询的类型以及您在逻辑和排序方面的其他选项,以使它更快。这是另一个例子,将子查询分解成 cte 可以帮助您以更快、更干净的方式重新组织和部署代码。

你(又)成功了!🎉

如果你(再次)走到这一步,那么恭喜你!您现在知道如何使用 SQL 进行更高级、更快速的查询。如果你想更多地了解以太坊,可以看看我的另一篇文章,这篇文章深入探讨了它是如何工作的。

如果您还没有点击任何查询链接,我强烈建议您这样做,因为 Dune Analytics 是一个测试查询和快速创建可视化甚至仪表板的好地方。当我开始学习 SQL 时,我希望有一个这样的工具来练习,而不是依赖 hackerank 或 leetcode 中的表(或者摆弄本地服务器,用网上的基本模拟数据表填充它)。

请关注本系列的最后一部分!

你的机器学习作品集就是你的护照

原文:https://towardsdatascience.com/your-machine-learning-portfolio-is-your-passport-7084d253601?source=collection_archive---------7-----------------------

如何建立机器学习作品集

利瓦伊·文图拉在 Unsplash 上拍摄的照片

如果没有护照,我们不会尝试去另一个国家旅行,所以你为什么要在没有投资组合的情况下尝试在机器学习行业中走动呢?这说不通。当试图导航机器学习行业时,你的投资组合是最重要的资产,建立一个投资组合并没有你想象的那么难。

如果你和我一样,过度思考投资组合中的内容是司空见惯的。如果我要计算我从 GitHub 中删除了多少项目,我必须包括我的脚趾[可能还有你的]。原因?在我眼里,他们还不够“T4”酷。随着时间的推移,我发现客户/雇主并不在乎你的项目有多酷——不要误解我,酷的项目是额外的奖励。但是他们更感兴趣的是你是否拥有你所说的技能,以及这些技能如何帮助他们。

展示你的作品集

你可以用许多不同的方式展示你的作品集。有些人决定把所有东西都放在他们的 GitHub 账户上的一个存储库中,而另一些人则决定用博客网站。就我个人而言,我不赞成 monorepo 风格,因为它不允许你写一份关于每个单独项目的全面深入的 READ.md 我认为这是让你的项目脱颖而出的最佳方式。而且,我不赞成使用博客网站(像媒体)的风格,因为它非常容易让东西迷失在内容中。

在我看来,更好的解决方案是建立一个个人网站,作为两者的混合体。个人网站可以让你把所有的东西都放在一个地方,这样就解决了组织的问题。此外,你将有更多的灵活性来定制你的投资组合,你可以让它对不太懂技术的读者来说更美观。

https://kurtispykes.github.io/#about

全力以赴

你的机器学习组合可以洞察你的个性和职业道德。里面是一个作品汇编,应该传达你的信仰,技能,资格,培训和经验。话虽如此,你决定着手并纳入投资组合的项目是极其重要的,尽管这不是一件需要过多考虑的事情。

让我们面对现实吧,成为每个领域的专家是不可能的。明确你想从事的领域,并选择与该领域最相关的机器学习项目。能够根据你想做的工作类型来定制你的机器学习项目,会向客户/雇主发出一个清晰的信息,告诉他们你可以如何让他们受益,如果你不得不参加面试,这将为你带来额外的优势。

要展示的项目类型

就像你的护照一样,你的投资组合也有到期日。一个项目做得好就足以让你得到一份工作,但你不应该停止更新你的投资组合,更新什么也同样重要。

你不会想解决像泰坦尼克号这样的普通问题。每当我想做一个项目时,我都会想一些能丰富我个人生活的事情。例如,我可能决定构建一个机器学习驱动的歌词生成器,以便我可以使用这些歌词来制作音乐。该项目必须是你和客户/雇主都感兴趣的东西。我发现当我必须自己研究和收集数据时,创建一个有趣的项目更容易,但是,没有理由为什么你不能解决现有的问题。

理想情况下,你希望在你的投资组合中包括一个端到端的机器学习项目,但展示数据探索等其他方面也无妨,以显示你是全面发展的。

[## 机器学习工作流程

towardsdatascience.com](/the-machine-learning-workflow-1d168cf93dea)

投资组合示例

对于视觉学习者来说,这里有一些我认为非常有见地的投资组合的例子。不是所有人都使用我建议的个人网站,但它们仍然很棒。

  • 萨加尔·夏尔马
  • 安德烈·卢克云年科
  • 克劳迪娅十 Hoope
  • 多恩·马丁
  • 燕汉娜

最后的想法

你的机器学习作品集是关于清楚地展示你处理机器学习问题的过程。它可以作为一种机制来塑造你的个性和职业道德。过多地考虑什么应该进入你的投资组合是停滞不前的一个好方法,因此我觉得开始并随着你的前进而调整是没问题的。

感谢阅读!

如果你喜欢这篇文章,请通过订阅我的每周简讯与我联系。不要错过我写的关于人工智能、数据科学和自由职业的帖子。

相关文章

** [## 如何创建引人注目的 Github 产品组合

towardsdatascience.com](/how-to-create-a-compelling-github-portfolio-a229e7472a92) https://medium.datadriveninvestor.com/blogging-for-your-data-science-portfolio-61b07da8005d **

你的 MCAR 数据技术指南

原文:https://towardsdatascience.com/your-mcar-data-technique-guide-b13a06914ed6?source=collection_archive---------30-----------------------

处理完全随机丢失数据的技巧

凯文·巴加特在 Unsplash 上的照片

你好。我看过描述处理缺失数据的技术的文章。但是我还没有看到任何按照缺失数据的类型(MCAR、马尔和 MNAR)来组织技术的东西。通过这篇文章和接下来的两篇文章,我将填补这个空白。我们将以下列方式探索完全随机缺失数据的技术:

  1. 用一个可视化的例子描述这种技术,包括下面的样本数据集和它适用的数据类型(分类和/或数字)。

作者创建的样本数据集。

2.每种技术的优缺点。

我不会在本文中包含任何代码,因为我想分享这些技术,而不管您的编码语言偏好如何。我将在以后的帖子中创建代码示例,并在这里链接它们。也就是说,让我们先快速定义完全随机数据缺失,然后开始。

照片由罗曼·卡夫在 Unsplash 上拍摄

什么是完全随机缺失的数据?

完全随机缺失的数据(或简称为 MCAR)是由于与数据集中的其他数据没有关联而缺失的数据。没有模式可以导致丢失数据的原因。幸运的是,确实存在一个测试来确定你的数据是否是 MCAR 与利特尔的 MCAR 测试。有了这个基础,让我们来看看处理 MCAR 数据的技术。

注:我在这里分享的所有技术都是从 Soledad Galli (非附属链接)的这个 特征工程课程中学到的。

完成案例分析(或列表删除)

完全案例分析是指只对非空条目的数据进行计算。如果一行缺少一个值,整行数据都将被删除。这适用于分类数据和数值数据,如示例所示。

完整的案例分析示例。图片由作者提供。

这确实过滤了当前数据。但是这是有代价的。过滤掉数据可能会导致您丢失不会包含在您的分析中或在您的机器学习模型中训练的信息。在上面的例子中,我们用这种技术丢失了一半的数据集!这可能导致有偏见的结果或有偏见的机器学习模型。因此,只有当数据集中最多有 5%的数据丢失时,才应该使用完整的案例分析。

模式插补(单变量数据)

这种技术涉及用单个列中非空数据的模式来填充该列中缺失的数据值。这种技术可用于分类数据和数值数据,但它更适合分类数据,因为值中的方差可能更小,并且更有可能具有众数。在下面我们修改的职业列的例子中,由于职业数据的模式是“看门人”,我们将用“看门人”替换所有缺少的值。

模式插补示例。图片由作者提供。

这是一种填写 MCAR 数据的非常快速简单的方法。但是,如果单列数据的模式在原始数据中出现得太频繁,就要小心了。如果该列中最初有太多的空值,这可能会导致该值被过度表示为 false。这还会破坏数据列与数据集中其他数据列的关系(例如,创建错误的关联)。

缺失类别插补(单变量数据)

这种形式的插补仅适用于分类数据。这里,缺失的分类数据被替换为一个标签,如下面示例中的“缺失”或“不适用”。

缺失类别插补示例。图片由作者提供。

这实际上是一种灵活的技术,因为无论数据是 MCAR 还是其他类型的缺失数据,您都可以这样做。这是因为只切换相同类别的每个缺失值(在本例中为“缺失”)。这种技术的简单性也使它成为处理数据的另一种快速简单的方法。唯一的缺点是数据列中缺失值的数量很少。如果这些是分类模型的类,您可能会在不平衡的数据集上结束训练。这将导致模型的训练出现问题,然后您必须实现处理不平衡数据集(如 SMOTE)所需的技术。

随机抽样插补(单变量数据)

这种技术包括用数据列中存在的值之一随机替换丢失的值。使用我们下面的例子,薪水的缺失数据将只替换为值 45000、63000、50000、70300、42000、60000 或 40000,因为这些是该数据列中仅有的当前值。

随机抽样插补示例。图片由作者提供。

这种技术可以用于数值和分类数据,因为您是从可用的值中提取的。但是,其中一个缺点是,如果有很大比例的缺失值,您正在输入的数据列与数据集中其他数据列之间的关系可能会被破坏。还有一个问题是使用更多的内存来训练你的机器学习模型,因为你需要存储这些值来从中提取。随着数据集和/或数据列中缺失值的百分比越来越大,这可能会成为一个越来越严重的问题。

还有第三个缺点,当在这个估算的数据上训练机器学习模型时可能发生,但是可以避免。您训练机器学习模型所基于的随机估算数据可能会导致不同的预测,因为每次运行模型时都可能会估算新值。您可以通过设置插补的种子来避免这种情况。如果设置了种子,数据每次都将被输入相同的随机值。

添加缺失指标列(单变量数据)

这种技术更多的是上述插补技术的延伸。它可以应用于任何数据类型。对于每个缺少数据的列,您创建一个列来指示该列中的条目是否缺少。这可以是一个二叉列,其中 0 =“未缺失”和 1 =“缺失”。在应用插补技术之前,你应该这样做。在我们的示例中,我们将创建列 Salary_Missingoccupation _ Missing来显示这些列中数据丢失的位置

修改样本数据集。图片由作者提供。

从原始样本数据集中添加缺失指标示例。图片由作者提供。

这种方法很容易操作,一旦您使用插补方法填充了这些空值,就可以帮助您跟踪最初丢失的数据。这种技术的一个代价是数据集的扩展。您正在为每一列丢失的数据添加一个新的数据列。这可以快速累加,并创建比您预期的更广泛的数据集来训练您的机器学习模型,从而增加训练时间。

最后的想法

艾伦·德拉克鲁兹在 Unsplash 上的照片

当您争论 MCAR 数据时,请记住这些技术的优缺点。插补技术可以帮助您完成数据的拼图,但是如果您不小心的话,也可能在数据中创建虚假的故事和关系。您还可以引入领域知识来帮助您选择技术。

感谢阅读!我希望本指南能为如何处理数据集中完全随机缺失的数据提供清晰的说明。如果您想了解更多关于处理丢失数据的信息,请查看下面的文章:

如果你喜欢这篇文章,请随意查看我下面的其他数据科学相关文章:

如果你有任何问题,请随时在 Linkedin 和 Twitter 上发表评论或联系我们。对 Twitter 上的 DM 开放。

下次见!

约翰·德杰苏斯

你的 MNAR 数据技术指南

原文:https://towardsdatascience.com/your-mnar-data-technique-guide-9b7e85ad67?source=collection_archive---------16-----------------------

处理你丢失的非随机数据的技巧

莉迪亚·塔兰特在 Unsplash 上的照片

大家好!这是处理缺失数据技术的第二篇文章。第一个是处理 MCAR(完全随机缺失)数据的。

正如你所猜测的,我们将在本帖中讨论 MNAR(非随机缺失)数据。我们将遵循与 MCAR 文章中相似的格式,我们将:

  1. 用一个可视化的例子描述这种技术,包括下面的样本数据集和它适用的数据类型(分类和/或数字)。

作者创建的样本数据集。

2.每种技术的优缺点。

我不会在本文中包含任何代码,因为我想分享这些技术,而不管您的编码语言偏好如何。也就是说,让我们先快速定义缺失的非随机数据,然后开始吧!

本·杜蒙德在 Unsplash 上的照片

不是随意遗漏了什么数据?

MNAR 数据是由于外部因素导致的数据缺失,这些外部因素没有反映在数据或其所在的数据集中。这可能包括提交数据的人、捕获数据时的错误,甚至是由于整个数据收集过程。如果您对数据是如何生成的没有任何背景知识,就很难确定这种类型的丢失数据的原因。现在我们已经定义了 MNAR 数据,让我们来看看处理它的几种方法。

注:我在这里分享的所有技术都是从 Soledad Galli (非附属链接)的这个 特征工程课程中学到的。

领域知识和他人的洞察力

领域知识是你在某一领域所拥有的背景知识。例如,教育领域的数据科学家将拥有关于学校如何运作的背景知识,并且可以更容易地理解来自该领域的数据集。虽然它不像下面的技术和 MCAR 的技术那样是一个技术动作,但我相信它是你处理这类数据的一个较好的机会。正是这种背景可以帮助您了解数据是如何收集的,并解决获取丢失数据的问题所在。

现在,假设你是一名转行的数据科学家,或者你自己是一名有抱负的数据科学家。从哪里可以获得你需要的领域知识?如果你是一名数据科学家,你可以利用你的同事和你的组织/公司中的人来帮助找到答案。你甚至可以找到负责你正在处理的数据的数据收集过程的人。从那里你也许可以解开你的 MNAR 数据之谜。

如果你是一名有抱负的数据科学家,请记住你有完整的互联网社区可以利用。问这个问题可能会令人生畏,但这并没有什么坏处。如果你在教育领域有什么问题,我很乐意回答你的问题。

在 Unsplash 上由海拉戈斯蒂奇拍摄的照片

任意值插补(单变量数据)

这种插补法类似于缺失类插补法,缺失数据被标上新的标签,如“缺失”。主要区别在于,对于数字数据,您需要选择一个不在数据列中的数值。选择不太可能是您的列或列范围内的值的内容。例如,我们不会使用任何大于或等于零的值来表示薪水列中缺失的值。当我不得不这样做时,我更喜欢使用-1 这样的值,因为薪金永远不会是负数。

任意插补示例。图片由作者提供。

除了选择一个合适的任意值来使用之外,这是一个简单的技术。然而,使用这种形式的数值插补会改变该列中数据的分布。如果您选择的任意值与平均值、中值或众数过于相似,从而改变了这些值,也会导致问题。另一个缺点是其他数据列对协方差的破坏。最后,当有很大比例的丢失数据时应用这种技术会增加对上述结果的破坏。

添加缺失指标列(单变量数据)

注意:如果你读了 MCAR 的文章,你可以随意跳过这一部分,因为它重复了同样的技巧。

这种技术更多的是上述插补技术的延伸。它可以应用于任何数据类型。对于每个缺少数据的列,您创建一个列来指示该列中的条目是否缺少。这可以是一个二叉列,其中 0 =“未缺失”和 1 =“缺失”。在应用插补技术之前,你应该这样做。在我们的示例中,我们将创建列 Salary_Missingoccupation _ Missing来显示这些列中数据丢失的位置

修改样本数据集。图片由作者提供。

从原始样本数据集中添加缺失指标示例。图片由作者提供。

这种方法很容易操作,一旦您使用插补方法填充了这些空值,就可以帮助您跟踪最初丢失的数据。这种技术的一个代价是数据集的扩展。您正在为每一列丢失的数据添加一个新的数据列。这可以快速累加,并创建比您预期的更广泛的数据集来训练您的机器学习模型,从而增加训练时间。

最后的想法

照片由 NeONBRAND 在 Unsplash 上拍摄

请记住,MNAR 数据没有明确的缺失原因。花点时间回顾一下我们讨论过的几项技术。除了领域知识之外,其他技术都是可替代的。它们是指示数据缺失的符号,其形式与该数据列中存在的值相同(数值或分类)。领域知识以及可以帮助您确定原因的任何资源将是潜在完成 MNAR 数据难题的最佳策略。

感谢阅读!我希望本指南能为如何处理数据集中的非随机缺失数据提供清晰的说明。如果你喜欢这篇文章,请随意查看我下面的其他数据科学相关文章:

如果你有任何问题,请随时在 Linkedin 和 Twitter 上发表评论或联系我们。对 Twitter 上的 DM 开放。

下次见!

约翰·德杰苏斯

您的 Python 输出可以更漂亮

原文:https://towardsdatascience.com/your-python-output-can-be-prettier-c697ace3f161?source=collection_archive---------9-----------------------

照片由 Pixabay 上的 LNLNLN 拍摄

从 Python Pretty 打印机库的基础到高级用法

每种编程语言都可以输出。控制台中的打印变量可以被认为是最基本的调试方法——至少您知道它包含什么。如果您使用诸如 Jupyter Notebook 之类的交互式笔记本,这对 Python 来说更是如此,因为运行一个单元后,输出就是您所拥有的全部。

但是,您是否对输出的格式感到困扰?例如

  • 有许多打印在一行中的键-值对的字典
  • 嵌套列表打印在不可读的一行中
  • 有非常深的不感兴趣的嵌套对象的字典

你可能已经知道 Python 中漂亮的打印机库,也可能不知道。无论哪种情况适合你,本文都将尝试引导你进入其中,并进行深入探讨。

请注意,漂亮的打印机库是 Python 内置的,因此您不需要下载任何东西。

1.基本用法

照片由詹姆斯·德莫斯在 Pixabay 拍摄

首先,我们需要导入这个库,并为演示目的构建一个示例字典。

import pprint as ppsample_dict = {
    'name': 'Chris',
    'age': 33,
    'message': 'Thank you for reading my article!',
    'topic':'Python Programming'
}

现在,如果我们简单地打印这本字典,所有内容都将在一行中输出。

print(sample_dict)

嗯,这可能还不算太糟,但是如果我们有更多的键值对或者一些值非常长怎么办?阅读起来会很困难。现在,让我们来看看漂亮的打印机库能做些什么。

pp.pprint(sample_dict)

首先,每个键值对都显示在一行中,这样可读性更好。此外,您可能没有注意到,字典会自动按字母顺序对键名进行排序。

2.文本换行

照片由 SpencerWing 在 Pixabay 上拍摄

我猜大多数 Python 开发者都知道上面显示的基本用法。然而,你知道漂亮的打印机库有更多的参数和标志,可以用来进一步定制输出吗?

其中一个示例用法是文本换行。假设我们不仅满足于每行有一个键-值对,还希望在值太长时有文本换行。在这种情况下,我们可以使用width参数。

pp.pprint(sample_dict, width=30)

因此,我们可以使用宽度来约束行的长度,以获得更好的可读性。除此之外,indent参数可以在每一行前面添加缩进。

pp.pprint(sample_dict, width=30, indent=10)

3.嵌套对象截断

由 Pixabay 上的 JerzyGorecki 拍摄的照片

有时,我们可能不想查看输出内容的所有细节。例如,我们可能想防止递归打印,或者只是对嵌套对象中更深层次的内容不感兴趣。

假设我们有一个如下的嵌套 Python 列表。

sample_list = ['level1', ['level2', ['level3']]]

如果我们使用普通漂亮的打印机,将不会有什么不同,从正常的打印。

pp.pprint(sample_list)

但是,如果我们指定了depth参数,任何比参数更深的内容都将被截断。

pp.pprint(sample_list, depth=2)
# OR
pp.pprint(sample_list, depth=1)

4.实例化漂亮的打印机

Pixabay 上 leulietmorgan 的照片

到目前为止,它是非常酷的。然而,编写上面演示的代码只是为了打印某些东西,每次都是,这可能太冗长和累了。

的确,我不会建议用漂亮的打印机来代替默认的打印功能。听起来有点怪:)所以,只在必要的时候使用。例如,当您调试来自 web 服务的 JSON 响应时。

然而,这是另一个问题。当你想不止一次而是多次使用漂亮的打印机时。每次都写带参数的函数也很累。事实上,我们可以用所有必要的参数实例化 Pretty Printer 类。然后,我们可以反复使用它。

PrettyPrinter类位于pprint包的根层。所以,还是重新导入吧。

import pprint

然后,我们可以在构造函数中传递参数,如下所示。

pp = pprint.PrettyPrinter(depth=2)

之后,我们得到了具有预定义样式的实例,可以直接使用。

pp.pprint(sample_list)

5.实践中的例子

由在 Pixabay 上的自由照片拍摄的照片

最后但同样重要的是,我想向您展示一个实际的例子来说明我们何时需要漂亮的打印机库。

让我们使用 PyPI 样例项目中的样例 JSON。这个例子也在官方文档中使用。

import json
import pprint
from urllib.request import urlopen
with urlopen('[https://pypi.org/pypi/sampleproject/json'](https://pypi.org/pypi/sampleproject/json')) as res:
    project_info = json.load(res)['info']del project_info['description']

请注意,我已经删除了description键,因为它太长了,无法在本文中恰当地演示。

如果我们简单地打印它,我们从输出中得不到任何有用的信息。一切都在一条线上。

print(project_info)

我们不能简单地使用 Pretty Printer 库来生成可读的输出,而不是复制输出并使用 Sublime 等额外的文本编辑器来格式化和美化 JSON 文档。

pprint.pprint(project_info, depth=1, width=60)

摘要

由免费拍摄的照片在 Pixabay 上

在本文中,我介绍了 Python 内置的漂亮的打印机库。当然,我们不一定要用它来代替默认的打印功能。我们必须在使用前导入它,代码会变得冗长。然而,在某些情况下,我们可以使用它来生成更好的可读输出,以方便我们的编程和调试活动。

**https://medium.com/@qiuyujx/membership

如果你觉得我的文章有帮助,请考虑加入灵媒会员来支持我和成千上万的其他作家!(点击上面的链接)**

你很可能学错了数据科学,以下是学习方法

原文:https://towardsdatascience.com/youre-likely-learning-data-science-wrong-here-s-how-to-learn-instead-47f63f04eea7?source=collection_archive---------9-----------------------

入门

打破普遍持有的谬误,设计一套新的原则

密歇根湖上的日落。作者图片

我接触过的大多数年轻、有抱负的数据科学家都被这些荣誉所吸引,就像我一样。我花了很多时间研究完美的课程、项目和成就,这样我就可以拥有一套让别人印象深刻的徽章。

问题?你给错误的人留下了深刻的印象,因为那些技术高超的人根本不在乎那些东西。

追逐荣誉和徽章会让你在一个月内完成那些为期一个月的 Coursera 课程,这样你就可以分享这些课程,同时不会感到实际应用这些技能的压力。一年读 52 本书,然后告诉别人你做到了,而不是在同一年真正掌握一两本书,这让你感觉很特别。这条路不仅会让你达不到你想要的结果,还会让你产生自己的冒名顶替综合症。

让我为你实现梦想提出一条更好的路线。

介绍一种更好的学习数据科学的方法可以被我发现的人们目前持有的一些谬误打破。这可能是由于不恰当的工艺/技能信息和过多的宣传误导了人们对他们的旅程应该是什么的想法。

谬误 1:需要知道一切。

任何在谷歌上搜索过数据科学、机器学习、人工智能或其他相关术语的人都知道这项技术涉及多少技能。统计、线性代数、微积分、Python、R、Spark、SQL、云、领域知识等。这对所有参与其中的人来说都是难以置信的。

由于对工艺的曲解,有时对需求的误解,我们也得到类似的工作角色,这增加了我们需要了解和成为一切的感觉。我们需要有 10 年以上的 Tensorflow 经验(lol)以及统计学博士学位,以及金融市场领域的专业知识和超人的能力。

该行业发展得足够快,这些工作描述变得越来越好,但感觉仍然是从业者需要知道和成为一切。这种谬误导致人们选修每一门课程,阅读每一本书,做每一个他们力所能及的项目来充实他们的简历。

这种策略的问题在于:这只会给其他低年级学生留下深刻印象。

年长者及以上,真正做决定的人,以及你真正想给他们留下深刻印象的人关心结果、价值和你能提供什么。他们并不真正看重你的 Coursera 成绩。不过,让我们明确一点:每个人都从某个地方开始,我一点也不低估 MOOCs。事实上,我写了一个关于如何开始低成本 MOOCs 的完整故事,所以我真的支持他们降低许多人的准入门槛。我只是说,不要欺骗自己,认为这将让你获得比个人成就感更多的东西,这可以说仍然非常有价值。

与其掉进这个陷阱,感觉筋疲力尽,没有成功地得到你想要的东西,我会给你以下建议:花 6 个月时间研究一两个主题,然后再花 6 个月时间应用你学到的东西。

前 6 个月,我会使用费曼技巧。参加 Coursera 课程或书籍或任何你能找到的东西,并按照以下步骤学习:

  1. 选择一个你想了解的概念。如果它不能激发你的好奇心,找一些关于这个主题的应用或者这个主题的未来发展可以引起你的兴趣。如果还是什么都没有,那你可能学错了东西。好奇心产生的兴趣是一个至关重要的起点。
  2. 向一个 12 岁的孩子解释。如果你不能简单地解释,你在任何面试中都不会成功。人们不是在寻找数学公式背诵,他们在寻找深刻的理解。
  3. 反思、提炼和简化。检查你的假设两次,并不断提高你批判性思考你所学内容的能力。不要从表面上看你读到的东西,试着去分析为什么事情是这样运作的。
  4. 整理和复习。在纸上组织你的想法。把事情写下来,在现实世界中通过挑战别人来测试它们。倾听他们的辩论、反驳和试探性问题,看看你如何应对。如果不能,你可能没有想象中理解的那么好。

在 6 个月内只学习一个主题可能看起来有些多余,但是如果你做对了(尤其是第三步),你会发现自己的视野变宽了很多,可以真正学习一个主题。但是你不会专注于错误的事情或者只是完成任意的课程,你会专注于真正理解的黄金。

接下来的 6 个月和第一个月一样重要。你必须建造,你必须建造一些蓝图没有交给你的东西。如果你只是想了解数据科学,那么你可能不需要这个,但如果你真的想成为一名从业者(并且是一名优秀的从业者),那么你必须进军未知领域,并表明你可以坚持自己的立场。

人们犯了一个巨大的错误,去 kaggle 做那些每个人都已经做过十亿次的显而易见的项目。这对你的学习和时间的合理利用没有价值,对你应该努力给人留下深刻印象的人也没有价值。

相反,从一个你感兴趣的行业或领域的问题开始,然后查找与之相关的开放 API 数据。花费大量时间收集一些数据并清理/处理它们。这很可能会很混乱,而且你很可能会从完全不同的来源获取信息。这听起来很难,但这是实践中的一大技巧,所以它会让你在竞争中脱颖而出。

然后用剩下的时间,利用你在前 6 个月学到的技能,构建一个解决方案。如果你能写下它或者创造出分享你所学到的或你失败的内容,那将会加分。顺便说一下,这样做也意味着你不得不在这 6 个月里学习更多的技能。

这真的是学习这门手艺的唯一有意义的方法,最好的人知道这一点。原因是很少有人真正做到这一点,但随着时间的推移,这些人会变得非常有才华。即使他们最终没有完全成功地构建出完美的项目,他们在这个过程中学到的经验和他们能够讲述的故事也要有价值得多。

谬误 2:过分强调数学,不重视人。

我认为这是由于对技术和成为一名成功的数据科学家/机器学习工程师真正需要的东西的误解。虽然数学和统计理论真的很有价值,但是你通常不需要一个拥有这种技能的团队。事实上,如果你正在组建一个团队,你希望更多的人是懂工程的,而不是懂数学的。这就是为什么人们发现仅仅知道数学理论而没有太多项目很难得到角色的原因。

如果你知道数学理论,你绝对是游戏规则的改变者,但前提是你在编程的环境中知道它。如果你不能编码或展示你的编码能力,那么这就是 100%你需要开始的地方。

然而,这一谬论的后续问题是,编程和数学这两个世界一直在争论,但沟通和人际交往技能却被认为是“软的”这样做实际上对未来的潜在客户非常不利,因为影响他人和沟通是这门手艺的一个重要部分,而且非常困难。其实太难了,我很自信的说,大部分自以为有这个本事的人真的真的没有。

这种能力包括但不限于:

  1. 能够真正倾听客户或业务合作伙伴的需求,并提出具体的问题,使问题变得清晰。
  2. 提出一个解决方案,直接解决他们所拥有的、能够理解的、能够维护的问题。
  3. 在短时间内与客户建立信任关系,让他们相信你会解决他们的问题并提供足够的支持。
  4. 能够影响正确的领导来改变或尝试不同的想法,这些想法你认为会起作用,但不是最初提出的。能够把自己的想法卖出去,在这个功能里几乎每天都在做。
  5. 讲故事。这不是每个人都有的技能,也不仅仅是数据可视化。讲故事是一门艺术,它以清晰简洁的方式传达复杂的思想,因此人们会受到它有意义的影响。通常,在商业环境中,这意味着一些行动或政策的改变以回应故事。

如果你没有人际交往能力,以上这些都行不通。我的工作是整天与人打交道,所以请确保你在学习这门手艺的技术深度的同时也在发展这些技能。技术深度也许能让你迈出第一步,但如果你不能影响他人,你会很快碰壁。

谬误 3:优化目的地而不是旅程。

数据科学,机器学习,AI 都不是手艺就是最终点。它们是需要你不断学习和重塑自我的手艺。不一定是为了保持相关性,但更多的是为了不断提升你传递价值的方式(为自己和他人)。

你必须是一个珍惜丰富你的思想的人,以保持成长和提高,否则这门手艺对你来说真的很痛苦。这是为那些做得比他们被告知的更多的人建造的工艺,主要是因为他们对学习和建造的热情。

因此,你必须热爱这个旅程,因为这是一个无限的游戏。这个行业没有现实的终点,如果你认为有,那你就玩错了游戏。即使是一个项目,在交付的时候,你也有无数的方法来改进最初的想法或模型,但是你必须知道什么时候离开,并且对你所做的感到满意。这个领域永远不会停止进化,因为它有太多的深度需要穿越。

如果你认为最有价值的东西是你的“数据科学家”头衔,那么它永远不会结束(高级、经理、副总裁等。)而你关注的东西实际上不会提供任何意义。注意那些拥有这些技能的人实际上能做什么,实际上正在做什么。注意他们是如何思考的,以及他们是如何建立那些心智模型的。他们的竞争优势不是拥有头衔和荣誉,而是他们以令人难以置信的高效速度进行批判性思考,用技术解决极其复杂的业务问题的能力。

只有当你喜欢花时间不断进步时,才能获得这种技能。

一套新的原则

打破普遍持有的谬误需要一套新的原则来指导你自己的旅程。不管这些谬误的原因是什么(期望值低、招聘启事不好、宣传过度等等)。),你必须阅读、交谈并向该领域的实际从业者学习有意义的进步方法。

原则 1:用动力而不是速度来学习

专注于速度会导致你武断地完成每一门 MOOC,而不是专注于理解一个项目所需的一个概念。带着动力学习意味着你知道如何构建正确的基础模块,让你在未来走得更快。这是为自己利用复合增长的最好方式。

原则 2:与他人一起学习,而不是独自学习

积极加入一个社团的最佳时间是昨天,第二好的时间是今天。与你周围的人接触,问他们关于他们具体知道什么的问题,并试图深入了解他们的想法。公开分享你的知识和失败,这样未来的从业者会更好。正如被证实的&减轻风险的真正方法是分散风险,被证实的&复合增长的真正方法是分散你的社区。

原则 3:用不确定性代替确定性来学习

无限游戏是指没有一套明确的规则、起点/终点和任意竞争者的游戏。无限博弈是不断进化的,并随着不确定性而成熟,而有限博弈则完全相反。这艘船的运动速度远远超过了人们的合理速度,但它是我们做生意和生活的未来。从这里开始只会越来越快,所以你必须在旅程的不确定性中跳舞,而不是沉迷于任何任意目的地的确定性。

如果您对我可能遗漏了哪些谬误或这三个没有涵盖的原则有任何想法,请评论并参与!我很想拓宽我的视野,让讨论继续下去。

您没有意识到您公司数据的全部价值

原文:https://towardsdatascience.com/youre-not-realizing-the-full-value-of-your-company-s-data-e7c3eb10418d?source=collection_archive---------28-----------------------

如何将您公司的数据分析转变为不仅仅是一个公司术语

图片由 阿什利·朱利斯 Unsplash

本文由 巴尔·摩西蒙特卡洛 Jit Papneja共同撰写,他是多家财富 100 强公司的全球洞察&分析领导者,包括**

你在雪花和 Looker 上?太好了。但对于大多数公司来说,拥有云数据堆栈只是大规模运营数据和分析的冰山一角。我们分享了企业在成为数据驱动型企业时面临的五个不明显的障碍,并强调了一些行业领先的数据工程和分析团队正在采取哪些措施来克服这些障碍。

2021 年,你将很难找到一家不想被视为“数据驱动”或“数据优先”的公司然而,对大多数人来说,数据分析只是一个时髦词,而不是关键商业价值的创新来源。

为了让您的企业的数据分析战略发挥其全部潜力,您必须在整个组织中建立一种数据欣赏和采用的文化。我们分享了阻碍卓越分析的五个最常见的挑战,以及如何应对这些挑战。

挑战#1:试点太多,规模化计划太少

很多时候,数据被用来解决试点和实验环境中孤立的问题,而不是完全生产化的项目。这很常见,尤其是在构建数据驱动型企业的早期阶段,但这是一个你应该尽早解决的地雷。

太多的飞行员在公司的其他部门造成了一种误解——数据是用于实验的,而不是创造商业价值的。这种看法使得开始建立认同和对数据和分析的可能价值的共同理解变得困难。

通过将您的数据和分析愿景与您的整体业务战略保持一致,尽快进入战略位置。确定数据和分析解决方案可以明确提供最关键元素的优先事项,并通过快速将经过验证的概念验证试点投入生产来释放潜在价值。

在确定关键优先事项时,问问自己— 您的数据和分析解决方案的目标是什么? 你想回答哪些商业问题?根据你的工作,你会授权企业领导人采取什么行动?这些举措将创造什么价值?

挑战#2:组织瓶颈

即使您已经准备好了结构良好的数据,您也需要在正确的团队中有具备正确技能的正确的人来使用它。开发出如何利用数据解决问题的绝妙想法的同一团队可能不具备测试和扩展解决方案的能力,从而造成瓶颈并削弱对数据支持计划的信心。

应对这一挑战的一个行之有效的方法是采用“中心辐射”模式,由一个专门的中心负责战略和治理、人才管理,并提供全公司范围的数据和分析服务供整个组织利用。

“中枢”通过向企业交付具有适当功能的关键服务来加速商业价值。“分支”团队制定职能路线图并执行职能数据和分析计划,因为他们拥有深厚的职能知识、特定于职能的技能组合,并且更好地了解职能需求和流程。“发言人”遵循由“中心”设定的通用指导方针和原则,并对“中心”支持的跨职能企业范围计划的采用和价值实现负责您还应该组建敏捷交付团队,这是一个跨职能的团队,有自己明确的目标,他们会自主地朝着这个目标努力。每个小组都有一个“产品负责人”,并对要完成的工作进行优先排序。

后退一步,检查你的组织结构。您是否有合适的人才组合、合适的工具和途径来将想法从概念验证转化为全面生产? 你有明确定义的角色、职责和工作方式吗? 团队是否作为一个团队,用一个声音,朝着一个目标运作?

挑战#3:大量数据,但缺乏洞察力

当公司试图提高他们在分析方面的投资回报率时,数据本身很少是一个挑战,而是员工从大量数据中收集有意义的见解的能力。团队需要知道什么数据用于什么目的,在哪里可以找到它,以及如何访问它。我们称之为数据民主化。

为了应对这一挑战,设定两个主要的最终目标员工应该有一个真实的数据来源,组织应该将数据视为一种战略性的宝贵资产,不应滥用或浪费。

这不是一项简单的任务。团队的团队需要跨多个跨职能团队工作,通过实施通用数据获取框架和引入标准数据治理计划,从孤立的所有权中获取数据。您还应该实施一个成熟的数据目录系统,使员工能够查找和访问在整个组织内共享的数据集,并相信在日常工作中可以使用可靠的数据。

伟大的成就不会在一夜之间发生,但领导者可以通过举办信息会议或“午餐和学习”来建立动力,向您的团队展示如何在日常工作中利用他们现在可以访问的数据和分析。

在走向数据民主化的过程中,问问自己: 你有明确定义的数据策略吗? 你如何传达你的战略,并在你的整个组织内培养采用共享数据和分析方法的兴趣?

挑战 4:努力成为每个人的一切

机器学习和其他人工智能学科已经成为推动盈利增长的强大解决方案,同时产生和扩展分析。然而,随着许多公司急于采用 ML 和 AI,数据和分析领导者面临着试图成为每个人的一切的挑战——应用新技术和方法只是因为你可以,而不是停下来确定你是否应该。

与第一个挑战类似,当你开始将人工智能引入你的数据程序时,首先要确定机器学习和其他自动化解决方案可以最有效地推动有意义的业务成果的问题。

例如,数据质量管理是任何严肃的数据策略的必要组成部分,通常需要大量的手动阈值设置和元数据输入。ML-first 方法,如 端到端数据可观察性 允许数据工程师和分析师专注于真正移动指针的项目,而不是临时救火或手工劳作。由于数据团队花费 80%的时间,而公司每年在数据质量问题上浪费 1500 万美元,当您在整个组织中应用数据时,数据可观察性使团队能够毫不费力地提高数据准确性。

ML 还被用来做预测,比如优步如何指引司机去一个需求即将激增的地区,或者 Airbnb 如何推荐价格以在动态市场中实现收入最大化。在每种情况下,预测的质量完全取决于用于训练机器学习模型的数据的质量。

如果您计划构建定制模型,首先要确保您有足够的数据来进行项目评估,并意识到可能会出现的潜在偏差。

在利用 AI 和 ML 之前,请回答以下问题: 我们的数据集是否足够干净和准确,足以消除人工疏忽? 我们是否已经确定了偏见可能发挥作用的点,我们是否知道 如何检测和纠正 它们?同样,这也是稳健的数据可观测性方法可以提供帮助的地方。

挑战#5:安全性、隐私和治理

一如既往,安全性和隐私是影响您的数据程序成功的永恒因素。当您努力打破孤岛并增加对数据的访问时,您需要确保数据(包括它涉及的每个接入点和管道)是安全的。

牢记您的长期目标和成果,尽早采用正确的数据治理方法,将有助于您随着时间的推移更轻松地实施和扩展分析。您应该确保您的整个数据堆栈,包括仓库、目录和 BI 平台,符合您的公司、行业和地区的数据治理指南。

所有这些挑战都是重大的,但并非不可克服。凭借这些最佳实践、战略愿景和正确的技术,您的数据分析计划可以推动每个业务部门发生有意义的变化,并在未来几年成为一个力量倍增器。

在贵公司实施数据分析战略?我们洗耳恭听!伸出手去 巴尔摩西 或者Jit Papneja****

要了解我们在 蒙特卡洛 的更多信息,以帮助公司克服这些瓶颈并获得可靠的数据, 查看我们的

熊猫和海牛的 YouTube 趋势视频分析

原文:https://towardsdatascience.com/youtube-trending-video-analysis-with-pandas-and-seaborn-8eec48a5e2d9?source=collection_archive---------44-----------------------

第 1 部分:数据清理和操作

诺德伍德主题公司在 Unsplash 上拍摄的照片

我在 Youtube 上看视频比看电视还多。我坚信很多人都是这样做的。我在电视上看到的一些评论员现在正在 Youtube 上主持节目。

首要的动机当然是金钱。人们可以在 Youtube 上赚很多钱。你挣多少取决于你的视频被看了多少。

在这两篇文章中,我们将分析美国的趋势视频统计数据。第一部分包括数据清理和对熊猫的操作,因为数据集不是最吸引人的分析格式。在的第二部分,我们将对数据进行分析和可视化,以推断出有价值的见解。我们将使用 Seaborn 库来创建可视化。

第一步是导入库并将数据集读入数据帧。

import numpy as np
import pandas as pd
import seaborn as sns
sns.set(style='darkgrid')us = pd.read_csv("/home/soner/Downloads/datasets/USvideos.csv")us.shape
(40949, 16)

数据集包含 16 列和大约 4 万行。

下面是 16 列的列表。

us.columnsIndex(['video_id', 'trending_date', 'title', 'channel_title', 'category_id','publish_time', 'tags', 'views', 'likes', 'dislikes', 'comment_count','thumbnail_link', 'comments_disabled', 'ratings_disabled','video_error_or_removed', 'description'],
dtype='object')

有些列对于我们的分析来说是多余的,因此我们将删除它们。

us.drop(['video_id','thumbnail_link', 'description'], axis=1, inplace=True)us.shape
(40949, 16)

已经删除了三列。axis 参数指示是删除列还是行。inplace 参数用于保存数据帧中的更改。

我们应该经常检查列的数据类型,因为有些函数和方法只能用于某些数据类型。例如,日期必须以 datetime 数据类型存储,以便我们可以使用 pandas 的日期时间功能。

us.dtypes
trending_date             object
title                     object
channel_title             object
category_id                int64
publish_time              object
tags                      object
views                      int64
likes                      int64
dislikes                   int64
comment_count              int64
comments_disabled           bool
ratings_disabled            bool
video_error_or_removed      bool
dtype: object

趋势日期和发布时间列具有需要作为日期时间更改的对象数据类型。我们先来看看这些栏目。

(图片由作者提供)

发布时间列可以很容易地用 astype 函数进行转换,因为它的格式是正确的。

us['publish_time'] = us['publish_time'].astype('datetime64[ns]')

对于趋势日期列,我们需要一些重新格式化。前两个字符代表年份的后两位数字(例如 17 是 2017)。由于数据属于 2017 年和 2018 年,一个简单的解决方案是用“20”连接该列。

us['trending_date'] = ['20'] + us['trending_date']

趋势日期列中的顺序是年、日和月,这不是标准格式。但是,我们可以使用 to_datetime 函数的 format 参数轻松处理它。

us['trending_date'] = pd.to_datetime(us['trending_date'], format = "%Y.%d.%m")

这是这两列现在的样子。

(图片由作者提供)

让我们也检查一下数据类型。

us[['trending_date','publish_time']].dtypestrending_date    datetime64[ns]
publish_time     datetime64[ns]
dtype: object

视频发布时间和其趋势时间之间的差异可能是分析的有用信息。因为两列都有 datetime 数据类型,所以我们可以很容易地计算时差。

us['time_diff'] = us['trending_date'] - us['publish_time']us['time_diff'][:5]
0   0 days 06:46:59
1   0 days 16:30:00
2   1 days 04:54:36
3   0 days 12:59:56
4   1 days 05:58:19
Name: time_diff, dtype: timedelta64[ns]

我们通过使用一个减号来计算差值,并将其分配给一个名为“time_diff”的新列。此列的数据类型是 timedelta,这是时间间隔的标准。

发布时间列包含日期和时间。通过使用 dt 访问器下的日期和时间方法,我们可以很容易地访问每个部分。

print(us['publish_time'].dt.date[0])
2017-11-13print(us['publish_time'].dt.time[0])
17:13:01

我们还应该对文本数据进行一些预处理。例如,我们可能需要在热门视频标题中找到最常见的词。

us['title'][:5]0                   WE WANT TO TALK ABOUT OUR MARRIAGE
1    The Trump Presidency: Last Week Tonight with J...
2    Racist Superman | Rudy Mancuso, King Bach & Le...
3                     Nickelback Lyrics: Real or Fake?
4                             I Dare You: GOING BALD!?
Name: title, dtype: object

有些标题包含所有大写字母,而有些是大写字母和小写字母的混合。让我们把它们都转换成小写字母。

str 访问器的底层方法将为我们完成这项工作。

us['title'] = us['title'].str.lower()us['title'][:5]0                   we want to talk about our marriage
1    the trump presidency: last week tonight with j...
2    racist superman | rudy mancuso, king bach & le...
3                     nickelback lyrics: real or fake?
4                             i dare you: going bald!?
Name: title, dtype: object

最好也去掉标点符号,这样我们的文本数据会更清晰。去掉标点符号有很多替代方法。使用 str 访问器的 replace 函数并执行基于正则表达式的替换的最简单方法之一。

us['title'] = us['title'].str.replace(r'[^\w\s]+', '')us['title'][:5]0                   we want to talk about our marriage
1    the trump presidency last week tonight with jo...
2    racist superman  rudy mancuso king bach  lele ...
3                       nickelback lyrics real or fake
4                                i dare you going bald
Name: title, dtype: object

你可能会因为第二行和第三行的点而感到困惑。这些不是这些单元格中字符串的一部分。根据当前的显示设置,它们比熊猫显示的字符数要多。

这是第二行的标题。

us['title'][1]'the trump presidency last week tonight with john oliver hbo'

我们已经完成了数据清理和操作。让我们做几个简单的计算。例如,我们可能想知道一个视频从发布到成为流行之间的平均时间差。

time diff 列的数据类型是 timedelta,它允许使用聚合函数,因此我们可以应用 mean 函数。

us.time_diff.mean()
Timedelta('16 days 05:21:53.236220664')

平均时间是 16 天零 5 个小时,比我预期的要快。

数据集包含大约 4 万行。不知道是不是大部分不同渠道上传的视频。给我们洞察力的一个方法是检查唯一通道的数量。

us.channel_title.nunique()
2207

在数据集中有 2207 个独特的频道,这意味着其中一些有许多趋势视频。

结论

我们对 Kaggle 上的 Youtube 趋势视频统计数据集进行了一些数据清理和操作。在的第二篇文章中,我们将关注数据分析部分,并尝试获得一些关于视频趋势的见解。

我认为预测一个视频是否会成为流行趋势是一项极具挑战性的任务。但是,我们可以探究它们之间的相似之处。

敬请期待下一篇文章!

感谢您的阅读。如果您有任何反馈,请告诉我。

熊猫和海牛的 YouTube 趋势视频分析

原文:https://towardsdatascience.com/youtube-trending-video-analysis-with-pandas-and-seaborn-c9903a0f811d?source=collection_archive---------38-----------------------

第 2 部分:数据分析和可视化

诺德伍德主题公司在 Unsplash 上拍摄的照片

在本系列的第一部分中,我们对包含美国趋势视频统计数据的数据集进行了数据清理和操作。在本文中,我们将对数据进行分析和可视化,以推断出有价值的见解。我们将使用熊猫和 Seaborn 图书馆进行数据分析和可视化。

在清理和一些预处理之后,数据集包含如下 13 列:

us.columnsIndex(['trending_date', 'title', 'channel_title', 'category_id',
'publish_time', 'tags', 'views', 'likes','dislikes','comment_count',
'comments_disabled', 'ratings_disabled', 'video_error_or_removed',
'time_diff'],
dtype='object')

time_diff 列表示视频发布和成为趋势之间的差异。我们先来算一下这一列的平均值。

us.time_diff.mean()
Timedelta('16 days 05:21:53.236220664')

平均时间 16 天 5 小时。这个值不能告诉我们太多关于时差的信息。例如,time diff 列可能包含大部分低值和一些非常高的值,这些值会提高平均值。

为了全面了解时差的分布情况,我们还需要计算其他描述性统计数据,如中位数和众数。另一个解决方案是可视化该列的分布,这样更容易理解。

time_diff 列的数据类型是 timedelta。我们需要把它转换成一个数字变量来绘制它的分布。一种方法是将 time_diff 列除以另一个 timedelta 间隔。例如,我们可以将它转换为小时,如下所示。

us['time_diff_hour'] = us['time_diff'] / pd.Timedelta('1 hour')us['time_diff_hour'].mean()
389.36us['time_diff_hour'].mean() / 24
16.22

当我们取平均值并除以 24 时,我们得到的值与 time_diff 列的平均值相同。

给我们提供分布概况的一种可视化形式是箱线图。

sns.boxplot(data=us, y='time_diff_hour')

时间差异小时的箱线图(图片由作者提供)

有些极值会扭曲分布图。该列的平均值是 389,但是我们观察到异常值高达 100000。让我们看看我们是否有能力消除异常值。

len(us)
40949len(us[us.time_diff_hour > 600])
588

时差超过 600 小时的行数为 588,与数据集中的总行数相比非常小。因此,我们可以丢弃这些异常值。

us = us[us.time_diff_hour <= 600]

我们已经剔除了大量的异常值。检查分布的另一种可视化方法是直方图,它将连续变量的值范围划分为离散的区间,并计算每个区间中的观察值数量。

Seaborn 的 displot 函数可用于创建如下直方图。

sns.displot(data=us, x='time_diff_hour', kind='hist',
aspect=1.5, bins=20)

(图片由作者提供)

大多数值都在 100 左右,所以它可能会在大约 4 天内成为趋势。

我想知道哪些频道有最热门的视频。使用熊猫的 value_counts 函数,我们可以很容易地看到前 10 名。

us.channel_title.value_counts()[:10]ESPN                                      203
The Tonight Show Starring Jimmy Fallon    197
TheEllenShow                              193
Vox                                       193
Netflix                                   193
The Late Show with Stephen Colbert        187
Jimmy Kimmel Live                         186
Late Night with Seth Meyers               183
Screen Junkies                            182
NBA                                       181

我们还可以比较这些频道发布的趋势视频的平均浏览量。检查平均观看次数的顺序是否与趋势视频计数的顺序相同是很有趣的。

具有多种聚合功能的熊猫按功能分组会给我们所需要的。

us['views_mil'] = us['views'] / 1000000us[['channel_title','views_mil']].groupby('channel_title')\
.agg(['mean','count'])\
.sort_values(by=('views_mil','count'), ascending=False)[:10]

(图片由作者提供)

屏幕瘾君子频道的平均收视率最高,约为每部视频 175 万。ESPN 的平均排名倒数第二,尽管它的热门视频数量最多。

我们还可以发现热门视频的数量如何随时间变化。第一步是按日期对观察结果(即行)进行分组。然后,我们将按日期对它们进行排序,以获得合适的时间序列。

daily = us[['trending_date']].value_counts().reset_index()\
.sort_values(by='trending_date').reset_index(drop=True)

(图片由作者提供)

每日数据帧包含日期和每个日期成为趋势的视频数量。我们现在可以根据每日数据框架生成一个线图。

sns.relplot(data=daily, x='trending_date', y=0,
kind='line', aspect=2.5)

我们使用 Seaborn 的 relplot 函数,并通过使用 kind 参数来选择线图。纵横比参数调整可视化的宽度和高度的比率。

(图片由作者提供)

我们观察到一个有趣的趋势。大部分数值在 190 到 200 之间,有几天例外。

我们还可以找到观看次数最高的热门视频。有多种方法可以完成这项任务。我们要做的是按照视图降序排列数据帧,并显示第一行的标题和视图。

us.sort_values(by='views', ascending=False)[['title','views']].iloc[0,:]title    childish gambino  this is america official video
views                                           217750076

就观看次数而言,最热门的视频已被观看超过 2 亿次。

结论

我们公布了一些关于 2017 年和 2018 年 Youtube 上发布的趋势视频的统计结果。自 2018 年以来,Youtube 的受欢迎程度进一步提高,因此现在的统计数据可能会有很大不同。

然而,这两篇文章的主要重点是用 Pandas 和 Seaborn 练习数据分析和可视化。当然,我们可以在这个数据集上做更多的事情。你可以自由探索,使用熊猫和海牛或者任何你喜欢的图书馆。

感谢您的阅读。如果您有任何反馈,请告诉我。

使用 SQL 进行 YouTube 趋势视频分析

原文:https://towardsdatascience.com/youtube-trending-video-analysis-with-sql-b022993d0e94?source=collection_archive---------30-----------------------

如何将 SQL 用作数据分析工具

艾萨克·史密斯在 Unsplash 上拍摄的照片

SQL 是一种用于管理关系数据库中的数据的语言,关系数据库以带有标签的行和列的表格形式存储数据。我们使用 SQL 的 select 语句从关系数据库中查询数据。就数据转换和过滤操作而言,select 语句具有高度的通用性和灵活性。

从这个意义上说,SQL 可以被认为是一种数据分析工具。使用 SQL 进行数据转换和过滤的好处是,我们只检索我们需要的数据。这比检索所有数据然后应用这些操作更加实用和高效。

在本文中,我们将使用 SQL 语句和函数来分析 YouTube 趋势视频统计数据。数据集可在 Kaggle 上获得。我创建了一个 SQL 表,其中包含这个数据集的一小部分。

注意:我使用 MySQL 作为数据库管理系统。尽管所有数据库管理系统的 SQL 语法基本相同,但可能会有一些细微的差别。

该表称为“趋势”,其结构如下。

趋势表(作者图片)

我们有视频发布和成为热门的日期。我们还有视频的标题和频道。视图和喜欢是数据集包含的另外两个特征。

关于所有这些特征(即列),我们可以进行一系列不同的操作。例如,一个简单的方法可以是根据趋势视频的数量找到前 5 个频道。

mysql> select channel_title, count(*) as number_of_videos
    -> from trending
    -> group by channel_title
    -> order by number_of_videos desc
    -> limit 5;+-----------------+------------------+
| channel_title   | number_of_videos |
+-----------------+------------------+
| Washington Post |               28 |
| Netflix         |               28 |
| ESPN            |               27 |
| TED-Ed          |               27 |
| CNN             |               27 |
+-----------------+------------------+

我们选择频道标题列并计算行数。“as”关键字用于为聚合列指定一个新名称。group by 子句用于根据频道对视频(即行)进行分组。最后,我们使用 order by 子句对结果进行降序排序,并显示前 5 个结果。

视频的数量似乎太低了,因为我只包括了 2018 年 1 月发布的视频。

我们可能想看看点击率最高的视频的标题。

mysql> select title, views 
    -> from trending
    -> where views = (select max(views) from trending);

(图片由作者提供)

上面的查询包含一个嵌套的 select 语句。它与 where 子句一起用于查找所需的条件,即视图列中的最大值。

这个表格中被观看次数最多的视频已经被观看了将近 6000 万次。

SQL 为过滤数据提供了许多不同的选项。在前面的例子中,我们发现观看次数最多的视频属于布鲁诺·马斯。我们可以过滤标题,只查看属于布鲁诺·马斯的视频。

mysql> select distinct(title)
    -> from trending
    -> where title like "%Bruno Mars%";

(图片由作者提供)

如果使用 like 关键字,我们不必为过滤提供确切的值。“%”代表任何字符,因此“%布鲁诺·马斯%”代表任何包含“布鲁诺·马斯”短语的值。distinct 关键字用于删除重复项。

如果我们不确定字符是小写还是大写,我们可以在过滤前将所有字符转换成小写或大写。

mysql> select distinct(lower(title))
    -> from trending
    -> where title like "%bruno mars%";

(图片由作者提供)

该数据集包含视频的发布日期以及它们成为趋势的时间。我们可以计算出一个视频成为热门的平均时间。

在计算差异之前,我们需要从发布时间列中提取日期部分,因为它包含日期和时间。

mysql> select trending_date, publish_time
    -> from trending
    -> limit 3;
+---------------+---------------------+
| trending_date | publish_time        |
+---------------+---------------------+
| 2018-01-02    | 2018-01-01 15:30:03 |
| 2018-01-02    | 2018-01-01 01:05:59 |
| 2018-01-02    | 2018-01-01 14:21:14 |
+---------------+---------------------+

date 函数提取日期部分,datediff 函数计算差值。因此,我们可以计算平均差异如下:

mysql> select avg(datediff(trending_date, date(publish_time)))
    -> as avg_diff
    -> from trending;+----------+
| avg_diff |
+----------+
|   3.9221 |
+----------+

datediff 函数采用由逗号分隔的两个日期,并计算其差值。一个视频成为热门平均需要 3.92 天。

我们还可以计算在特定时间段发布的视频的平均差异。我们只需要添加一个 where 子句进行过滤。

mysql> select avg(datediff(trending_date, date(publish_time))) as avg_diff
    -> from trending
    -> where hour(publish_time) > 20;+----------+
| avg_diff |
+----------+
|   4.4825 |
+----------+

我们从发布时间中提取小时值,并在 where 子句中使用它进行过滤。

SQL 提供了可以在 select 语句中实现的数据聚合函数。例如,我们可以计算网飞发布的视频的平均点赞率。

mysql> select avg(likes / views) 
    -> from trending
    -> where channel_title = "Netflix";+--------------------+
| avg(likes / views) |
+--------------------+
|         0.01816295 |
+--------------------+

平均值接近 0.02,因此网飞视频的点击率约为 2%。

让我们编写一个稍微复杂一点的查询,计算发布超过 25 个视频的频道的平均视频浏览量。我们还将根据平均值对结果进行降序排序。

mysql> select channel_title, avg(views) as avg_views,
    -> count(title) as number_of_videos
    -> from trending
    -> group by channel_title
    -> having number_of_videos > 25
    -> order by avg_views desc;

(图片由作者提供)

检索到的数据包含 3 列。一个是频道标题列,另外两个是聚合列。我们根据视频数量过滤频道。

您可能已经注意到,我们使用了“having”子句而不是“where”子句进行过滤。“having”子句用于根据聚合列进行筛选。

结论

我们做了一些例子来分析 YouTube 趋势视频统计。这些例子清楚地表明,SQL 也可以用作数据分析工具。

我认为 SQL 是数据科学家或分析师的必备技能。我们至少应该知道如何查询关系数据库。在检索数据时执行数据转换和操作操作的复杂查询有可能节省内存。它们也减轻了以后需要完成的任务。

感谢您的阅读。如果您有任何反馈,请告诉我。

yq:在命令行中掌握 YAML 处理

原文:https://towardsdatascience.com/yq-mastering-yaml-processing-in-command-line-e1ff5ebc0823?source=collection_archive---------16-----------------------

学习使用yq命令行实用程序和这个简单的备忘单更有效地解析和操作 YAML 文件

照片由马特·阿特兹在 Unsplash 上拍摄

如今,YAML 几乎用于配置所有东西(无论是好是坏),所以无论你是使用 Kubernetes 或 Ansible 的 DevOps 工程师,还是使用 GitHub 操作配置 Python 或 CI/CD 日志的开发人员,你都必须至少不时地处理 YAML 文件。因此,能够有效地查询和操作 YAML 是我们所有工程师的基本技能。最好的学习方法是掌握 YAML 处理工具,如yq,它可以让你在许多日常任务中更加高效,从简单的查找到复杂的操作。所以,让我们浏览并学习yq所提供的一切——包括遍历、选择、排序、归约等等!

安装

在我们开始使用yq之前,我们首先需要安装它。当你谷歌yq的时候,你会发现两个项目/资源库。首先,在https://github.com/kislyuk/yq是围绕jq的包装器 JSON 处理器。如果你已经熟悉了jq,你可能想抓住这个并使用你已经知道的语法。然而在本文中,我们将使用另一个更受欢迎的项目,来自 https://github.com/mikefarah/yq。这个版本并不 100%匹配jq语法,但它的优势是它是独立的(不依赖于jq),有关差异的更多上下文,请参见下面的 GitHub 问题。

要安装它,请转到文档并选择适合您系统的安装方法,只需确保您安装的是版本 4,因为这是我们将在这里使用的。最重要的是,你可能想要设置 shell 完成,关于它的信息可以在https://mikefarah.gitbook.io/yq/commands/shell-completion获得。

现在我们已经安装了它,我们还需要一些 YAML 文件或文档来测试我们将要运行的命令。为此,我们将使用以下文件,这些文件包含了您可以在 YAML 中找到的所有常见内容—属性(普通的和嵌套的)、数组和各种值类型(字符串、整数和布尔值):

这个说完了,我们来学习基础知识吧!

基础知识

我们将运行的所有命令都将以相同的基数yq eval开始,后面是带引号的表达式和您的 YAML 文件。一个例外是 YAML 文件的漂亮打印,在这种情况下,你可以省略表达式——例如yq eval some.yaml——如果你熟悉jq,那么这相当于cat some.json | jq .

可选地,我们也可以添加一些额外的标志,一些更有用的标志是-C强制彩色输出,-I n设置输出缩进到n空格,或者-P漂亮打印。

至于基本表达式,我们可以做很多事情,但最常见的是遍历 YAMLs,或者换句话说,在 YAML 文档中查找某个键。这是使用.(点)操作符完成的,在最基本的形式中,看起来是这样的:

除了基本的地图导航之外,您可能经常想要在一个数组中查找特定的索引(使用[N]):

最后,您可能还会发现 splat 操作符非常有用,它可以展平贴图/数组(请注意与我们看到的第一个示例的不同之处):

除了基本的遍历,你可能还想熟悉选择的,它允许你通过布尔表达式过滤。为此我们使用select(. == "some-pattern-here")。这里,简单的例子可以是基于前导数字的过滤:

这个例子还展示了管道(|)——我们使用它首先导航到我们想要过滤的文档部分,然后将它传递给select(...)

在上面的例子中,我们使用了==来查找与模式相等的字段,但是您也可以使用!=来匹配不相等的字段。此外,您可以完全省略select函数,您将只获得匹配的布尔结果,而不是值:

无论您是第一次使用yq还是已经使用了一段时间,您肯定会遇到这样的问题,您不知道为什么您的查询没有返回您想要的结果。在这些情况下,您可以使用-v标志来产生详细的输出,这可能会为您提供为什么查询会这样的信息。

高级查询

前一节展示了基本的功能,这些功能对于快速查找和过滤来说已经足够了,但是有时您可能希望使用更高级的函数和操作符,例如在自动化某些涉及 YAML 输入和/或输出的任务时。所以,让我们探索一下yq能提供的更多东西。

有时候,对文档中的键进行排序可能很有用,例如,如果您在 git 中对 YAMLs 进行版本控制,或者只是为了提高可读性。如果你需要区分两个 YAML 文件,这也很方便。为此我们可以使用'sortKeys(...)'功能:

如果输入的 YAML 文档是动态的,并且您不确定将会出现什么键,那么首先用has("key")检查它们是否存在可能是有意义的:

与使用has("key")的情况类似,在对文档进行某些操作之前,您可能需要首先获得动态的键列表,为此您可以使用keys函数:

检查值的长度对于输入过滤/验证或者确保值不会溢出一些预定义的界限可能是必要的。这是使用length功能完成的:

对于具有参数化输入的自动化任务,您肯定需要将环境变量传递到yq查询中。显然,您可以使用普通的 shell 环境变量,但是您最终会遇到非常棘手和难以理解的引号转义。因此,最好使用yqenv()功能:

为了简化一些字段或数组的处理,你也可以使用一些字符串函数,如joinsplit来连接或拆分文本:

本节最后一个也可能是最复杂的例子是使用ireduce的数据转换。要有一个使用这个函数的好理由,你需要一个相当复杂的 YAML 文档,这不是我想在这里转储的东西。因此,相反,为了至少给你一个函数如何工作的概念,让我们用它来实现前面例子中的join“穷人的”版本:

这个不像前几个那样一目了然,所以让我们把它分解一下。查询的前半部分(.user.orders[] as $item ireduce)从 YAML 获取一些可迭代字段(序列)并将其赋给变量——在本例中是$item。在第二部分中,我们定义初始值"";(空字符串)和一个表达式,该表达式将为每个$item清空——这里是之前存在的值,用空格((. + " "))连接,后跟我们当前迭代的项(+ $item)。

操纵和修改

大多数情况下,您只需要对现有文档进行搜索、查找和过滤,但有时您可能还需要操作 YAMLss 并从中创建新的 YAML。yq提供了几个操作符来完成这类任务,所以让我们简单回顾一下,看几个例子。

最简单的就是 union 操作符,其实就是一个,(逗号)。它允许我们组合多个查询的结果。如果您需要同时提取 YAML 的多个部分,但无法通过单个查询完成,这将非常有用:

另一个相当常见的用例是向数组中添加记录或者连接两个数组。这是通过+(加号)运算符完成的:

另一个方便的是更新操作符(=),它(惊奇,惊奇)更新一些字段。在我们的示例 YAML 中更新日志级别的非常简单的示例:

这里需要指出的是,默认情况下,结果被发送到标准输出,而不是原始文件。要进行就地更新,您需要使用-i选项。

还有一些操作符可用,但不是特别有用(大多数时候),所以我不会给你看一堆可能对你没有帮助的例子,相反,我会给你一些到文档的链接,以防你想知道得更深入一点:

  • 数字减法
  • 乘法(合并)
  • 删除

方便的例子

现在我们知道了这个理论,让我们来看看一些例子和方便的命令,你可以马上把它们整合到你的工作流程中。

出于明显的原因,我们从 Kubernetes 开始,因为它可能是使用 YAML 进行配置的最流行的项目。yq可以帮助我们做的最简单但非常有用的事情是漂亮地打印 Kubernetes 资源或查询清单的特定部分:

我们可以做的另一件事是列出资源名称和特定属性。这对于查找或提取服务的所有监听端口非常方便,例如查找名称空间中每个 pod 的 pod 图像:

注意,上面我们不得不使用.items[],因为当你get一个资源的所有实例时,返回的种类items列表

在诸如 pod、部署或服务之类的资源的情况下,通常在每个名称空间中有许多实例,仅仅将它们全部转储到控制台并手动筛选它们可能是不可取的。因此,您可以根据某些属性对它们进行过滤,例如,仅列出在特定端口上公开的服务的名称和监听端口:

所有的 Kubernetes“YAML 工程师”都知道,有时很难记住某个特定资源中的所有字段,那么为什么不直接查询所有的关键字,例如部署的spec.template.spec

从 Kubernetes 继续,来点docker-compose怎么样?也许你需要临时删除一些部分,比如volumes或者healthcheck——好了,这就是了(这是破坏性的,所以要小心):

以类似的方式,你也可以从一个可行的剧本中删除任务。说到这里,把所有任务中的remote_user都改成root怎么样?

结束语

我希望这个“速成班”能帮助你开始使用yq,但是和任何工具一样,你只能通过实践和实际执行真实世界的任务来学习使用它,所以下次你需要在 YAML 文件中查找某些东西时,不要只是把它转储到终端中,而是写一个yq查询来为你完成工作。此外,如果你正在努力为你的特定任务提出查询,而谷歌搜索没有找到任何有用的东西,尝试搜索使用jq的解决方案——查询语法几乎相同,考虑到它是更受欢迎/常用的工具,你可能会更幸运地搜索到jq解决方案。

本文最初发布于martinheinz . dev

https://itnext.io/advanced-git-features-you-didnt-know-you-needed-ed8455c45495

禅宗和数据科学的艺术——如何避免冒险陷阱

原文:https://towardsdatascience.com/zen-and-the-art-of-data-science-how-to-avoid-gumption-traps-a3590deef870?source=collection_archive---------7-----------------------

即使是最优秀的数据科学家也会在他们的项目中面临挫折和失去动力。哲学家罗伯特皮尔西格对于处理这种难题和挫折会有什么建议呢?

哈雷戴维森在 Unsplash 上的照片

对我来说,罗伯特·皮尔西格的畅销书《禅宗与摩托车维修艺术》 (1974)绝对是我见过的最吸引人的书之一。本文的目的不是给出一个关于质量形而上学的概要,而是提取一些可能在数据科学家对他们的项目感到沮丧时有所帮助的见解。

禅与摩托车维修艺术

对于那些没有读过这本书的人,或者那些需要快速回顾的人,这本书将作者的哲学人生之旅与全美摩托车公路之旅交织在一起。它提供了一个关于质量概念的论题,将对世界的浪漫古典感知统一起来。作为这两种观点的例证,摩托车维修的名义行为是贯穿全书的一个广泛的轶事。浪漫的灵魂主要对摩托车的驾驶、外观和感觉感兴趣。机械故障只不过是沮丧的来源。另一方面,古典灵魂关心的是机器的内部机制,想要确切地知道每个部分做什么。对他们来说,崩溃实际上可能是一个有趣的挑战。

不难看出与编程和数据科学的相似之处。当面对软件故障或无意义的结果时,许多用户会绝望地放弃。钻研底层数据集或错误消息需要一定的毅力和分析思维。古典灵魂在这里发挥作用。

然而,即使是一个对世界有着严格的传统观点的人也不能免于沮丧。谁不想在又一个不可理解的错误信息后将笔记本电脑扔在黑暗的角落里,谁在徒劳地运行了五种不同的机器学习算法后仍然保持动力?

没有神奇的药丸或深刻的哲学见解能让你一直保持动力。尽管如此,你对让你止步不前的原因了解得越多,你就越有可能解决问题并重新开始。皮尔西格全面概述了人们陷入困境并失去动力的常见原因。如果它在七十年代适用于摩托车,为什么在现代数据科学时代就不适用呢?

进取心陷阱

我从来没有试图把一辆摩托车拆成一千块,然后试图重新组装,但显然这不是一件容易的事。皮尔西格也不是职业机械师;对他来说,这是一次重要的学习经历。像任何人一样,他经常迷路,陷入困境,失去动力,无聊,沮丧。他将所有这些感觉归为的“进取心陷阱”的共同特征,并提供了如何克服它们的具体建议。

进取心不一定是我们在日常生活中使用的词,所以给它下一个定义可能是合适的。有多个(包括像精明主动这样的术语),但是“常识或足智多谋”似乎很好地抓住了它的意思。进取心陷阱反过来是导致对完成项目失去兴趣或信心的事件或心态。“陷阱”部分指的是自我强化的反馈回路。如果你感到气馁,你会在项目上投入更少的努力,导致令人失望的结果,甚至更沮丧,导致…你明白了。

陷阱可分为外部因素 ( 挫折)和内部因素 ( 阻碍)。后者进一步分解为价值陷阱真相陷阱肌肉陷阱。皮尔西格讨论了所有这些问题的共同原因和解决方案。在这里,我尝试将它们转化为数据科学领域的具体应用。

照片由 Erik Karits 在 Unsplash 上拍摄

挫折

皮尔西格主要将外部挫折归咎于缺乏知识。不仅仅是缺乏知识;这就是战略顾问们所说的“未知的未知”。填补你意识到的知识缺口已经够难了,然而不知道这种缺口的存在可能更糟。在后一种情况下,挫折似乎是不可抗拒的,因为你根本没有意识到你所面临的障碍。这就像在没有地图的情况下在暗礁中航行。

在数据科学项目中,知识和理解的缺乏通常是实质性的。这并不是对一个人智力能力的反映:现实世界的项目通常是混乱的、定义不清的,是一路形成的。

这就是“数据科学家”中的“科学家”应该出现的地方。皮尔西格的总体建议是缓慢而谨慎地前进。不要直接跳入编码,而是提前计划。花时间对项目、期望和潜在的障碍有一个正确的理解。经常停下来反思和分析。

具体来说,以下步骤可能有助于减轻挫折:

  • 做笔记。制定计划和与利益相关者交谈时,花些时间记下你的想法。确保期望明确一致。把事情写出来有助于过程的形式化和结构化。
  • 列出需求和目标。了解成功的项目需要满足的条件和构成要素。尽可能具体。使用智能标准通常很有帮助。
  • 定义明确的研究问题。你应该从数据中获得哪些洞见?你的新算法应该具备哪些能力?如果你想找到正确的答案,确保你问了正确的问题。
  • 提前计划。将项目分解成一系列综合步骤的逻辑序列。每一步需要什么活动,需要多长时间?成功完成项目需要哪些数据和工具?一条清晰的道路大大增加了成功的机会。
  • 勤反省。一旦出发,前面的步骤就不是一成不变的了。项目是动态的,目标和问题可能会改变,新出现的障碍应该被解决。在你的数据科学项目中花时间反思,不要害怕改变方向。

自然,总会有意想不到的障碍。计划从来都不是完美的,一些外部因素会超出你的控制范围。挫折不可避免地会出现,但是一个谨慎和系统的方法将会保持对项目的关注,并减少被抛下游戏的风险。记住数据科学很少是一条直路:慢慢开。

约翰·吉本斯在 Unsplash 上的照片

烦恼

与挫折不同,的烦恼源于内部因素。项目不仅会因为外部环境或不可预见的障碍而受阻,还会因为信息不足或错误的问题角度而受阻。当处理数据或代码时,很容易完全陷入问题的一个微小方面;一个统计异常值,一个错误,一个视觉细节。通常最好的解决办法是快速休息,重新评估你是否真正面对一个大问题。如果是这样的话,你可能要回到绘图板一步一步地解决这个问题。

皮尔西格将挂断分为三类:价值陷阱真相陷阱肌肉陷阱。定义和示例如下。

价值陷阱

价值陷阱是发生在头脑中的障碍。人不是机器:他们希望自己的工作有意义,希望自己的假设得到证实,希望获得成功。当在某个解决方案方向上花费大量精力时,很难重新评估你的心境(即你的价值观)。然而,数据科学是一个动态的过程,人们必须灵活地处理新的事实和信息。还要记住:从商业角度来看,你投资的解决方案可能不是最好的。

应对价值陷阱的典型答案是,当遇到以下任何一点时,后退一步,重新评估手头的问题和事实,在需要时收集更多信息。通常,脱离你的超聚焦状态就足够了。

  • 利己主义:无偏见经营,说起来容易做起来难。数据可能不支持你希望或期望看到的结论。你认为很棒的机器学习算法并没有提供你想要的精确度。你的自我意识可能会让你忽略不方便的事实,看到不存在的模式。不过这是一个数据驱动的领域:保持怀疑,让数据说服你。
  • 焦虑:说实话,数据科学并不容易。你需要很多领域的知识,并且在处理一个新项目或挑战时不会总是充满自信。无论是 Git、贝叶斯统计、PyTorch 还是 Spark,从一开始就详细说明需求,并复习手头任务的知识。即时学习没有错,但是要相信你有完成工作的核心能力。
  • 看不到进步,厌倦感必然会产生。皮尔西格建议,一有厌倦的迹象就立即停止,但这在实践中很少可能。喝杯咖啡或者和同事聊聊天可能足以让你重新振作起来。经过一上午毫无结果的调试,也许你可以花一下午的时间来编写新的特性。从长远来看,切换到不同的团队或项目可能是一个解决方案。
  • 不耐烦:我们都想看到昨天的结果,而不是现在。停滞不前时,放弃一个项目似乎很有吸引力。同样,皮尔西格建议的解决方案——不确定的项目时间——通常不适用。然而,如果你总是忙于修补东西,总是从一个补丁跑到另一个补丁,这可能是项目没有足够的时间或支持的迹象。现实的时间表和足够的灵活性是成功的先决条件。

真相陷阱

真相陷阱要么与环境收到的误解反馈有关,要么与问题和答案之间的不匹配有关。因此,“真相”被扭曲了,经常导致沮丧,因为似乎没有解决的途径。

  • 对反馈的误解:这可能是来自最终用户、利益相关者、你的编译器的反馈,或者仅仅是一个结果。不正确地评估反馈会让你感到失落或者关注错误的修正。花时间适当地吸收反馈,并在需要时要求澄清。如果出现非描述性的错误信息:谷歌一下。它们可能不像表面上看起来那么神秘。
  • 依赖是/否二元性:数据分析并不总是提供明确的是或否答案。意想不到的发现和异常值经常出现,尽管见解应该以一种全面的方式呈现,但过度暗示它们是错误的。正如爱因斯坦所说:

"一切都应该尽可能简单,但不能再简单了."

  • 问错误的问题:如果,尽管你尽了最大努力,你还是难以回答这个问题,你可能需要重新考虑研究问题是否符合问题的背景。

肌肉陷阱

数据科学需要许多技能,但身体实力通常不在其中。你很快就会看到,这与你的卧推或 10 英里记录无关。一般来说,肌肉陷阱包括你、你的电脑和你的工作环境之间的相互作用。

  • 工具不足:要完成工作,拥有合适的工具至关重要。在数据科学中,硬件和软件都属于“工具”的范畴。你是否经常在模拟运行结束时等待数小时,你鼠标的右边按钮是否有一半时间失灵,Panda 缓慢的数据摄取是否是一个持续的挫折来源?你可能要重新评估你使用的工具。记下你遇到的问题,并寻找替代方案。
  • 恶劣的工作环境:简单的环境因素,如屏幕上的阳光、办公室的高温或令人分心的音乐,都会严重影响工作效率。在家工作了将近两年,也要认真对待你的家庭办公室。不要在厨房的餐桌上,弯腰看 13 英寸的笔记本电脑。投资一把合适的椅子,一个显示器,一个键盘。从长远来看,这是值得的。
  • 肌肉不敏感:基本上不砸键盘,不往显示器上扔东西。这解决不了问题。

照片由 Francisco Requena 在 Unsplash 上拍摄

最后的话

我意识到,本文中提出的大多数解决方案似乎都很明显,甚至微不足道。作为人类,我们也会犯错。我们用一个不充电的坞站工作了几个月。我们一遍又一遍地被同样的错误信息弄得沮丧,却从来没有去查找它到底告诉了我们什么。我们日夜不停地超频我们的笔记本电脑,虽然很明显,我们需要一个集群的工作。我们无休止地调整我们花了这么多时间的神经网络,忽略了告诉我们它根本不起作用的危险信号。

皮尔西格的核心信息是不要停下来喝杯咖啡或更换你的鼠标。他的论点是对你的项目采取一种缓慢的、有意识的和深思熟虑的方法,经常缩小和评估大图。作为一名数据科学家,一个定义明确、信息充分的行动计划对于成功至关重要。当感到停滞不前或沮丧时,需要一种缓慢而系统的方法来解决问题并解决潜在的障碍。

每个人都会时不时地经历本文列举的进取心陷阱。我们手头并不总是有正确的信息或技能,障碍会突然出现,是的,甚至像无聊这样的世俗感觉也会出现。没关系。只要你意识到你可能会走进的陷阱,并有一个摆脱它们的计划,你就已经成功了一半。

所以,下次你对你的项目感到沮丧,准备认输的时候,关掉屏幕,给自己做一杯双份浓缩咖啡。它会让你成为更好的数据科学家。

塔比瑟·特纳在 Unsplash 上拍摄的照片

外卖食品

  • 一个进取心陷阱是当感觉陷入一个项目时失去热情或主动性。这往往会自我强化,因为热情降低会降低成功的几率,反之亦然。在数据科学中,表现不佳的算法和令人费解的数据集是冒险陷阱的典型驱动因素。
  • 挫折是外生的进取心陷阱,通常源于缺乏知识和意识。对数据科学项目采取缓慢而系统的方法有助于及时发现知识差距、障碍和瓶颈。
  • 障碍是内生的进取心陷阱。通常情况下,他们会抓住误导性的事实不放,或者在信息或工具不足的情况下工作。挂机可分为价值陷阱真相陷阱、和肌肉陷阱
  • 价值陷阱主要是心理障碍,通常涉及不愿放弃某种假设或解决方法。我们投入的精力越多,就越不愿意接受不方便的数据。有时候,休息一下,缩小范围,验证你是否仍然在解决正确的问题会有所帮助。
  • 真相陷阱涉及曲解反馈或试图回答错误的问题。尝试澄清模糊的反馈(从错误信息到利益相关者的评论),并重新评估回答研究问题是否能解决手头的问题。
  • 肌肉陷阱需要在工具不足或者效率低下的环境中工作。确保您拥有适合您工作的软件、硬件和办公用品。批判性地评估你的瓶颈和挫折,并评估不同的工具是否可以解决问题。
  • 最终,避免——并走出——进取心陷阱是关于心态和自我意识。通过有意识地后退一步,放慢速度,就有可能采用传统的思维模式,系统地解决问题,消除前进的障碍。

参考

皮尔西格博士(1974 年)禅与摩托车保养艺术:价值观探究。复古出版社。

免费字典。进取心。https://www.thefreedictionary.com/gumption

维基百科(2021)。禅与摩托车维修艺术。https://en . Wikipedia . org/wiki/Zen _ and _ the _ Art _ of _ Motorcycle _ Maintenance # guption _ traps

芝诺的不朽典范

原文:https://towardsdatascience.com/zenos-illustrative-example-bb371b99f25a?source=collection_archive---------19-----------------------

2500 年后解决了一个争议,现在作为一个参考框架的起源,几何级数是改善数学教育的关键吗?

以利亚的芝诺展示了通往真理和谬误的大门。马德里埃斯科里亚尔图书馆的壁画。(照片由佩莱格里诺·蒂巴尔迪拍摄——【https://commons.wikimedia.org/w/index.php?curid=8665907】http://web . madritel . es/personales 2/jcdc/presocraticos/pinac 06 _ zenon . htm、公共领域)

介绍

2500 年前,希腊数学家从一个地方走到另一个地方有一个问题。在身体上,他们能够像我们今天一样行走,甚至可能更好。然而,从逻辑上讲,他们认为每一个无限长的大于零的数字列表的总和都是无穷大。因此,当 Elea (约公元前 500 年)的芝诺指出,为了从一个地方走到另一个地方,你首先必须走一半的距离,然后你必须走剩余距离的一半,然后你必须走剩余距离的一半,你继续无限次地将剩余距离减半,因为无论剩余距离有多小……你仍然必须走它的前半部分。在他的例子中,Elea 的 Zeno 将一小段步行距离转换成一个无限长的列表,剩余距离减半,所有距离都大于零。这就是问题所在:一个距离怎么能在直接测量时是短的,而在其无穷的一半余数列表上求和时又是无限的呢?这一悖论揭示了当时希腊数学家普遍持有的假设有问题,即每一个无限长的大于零的数字列表的总和为无穷大。

以利亚悖论的芝诺中使用的级数 1/2 + 1/4 + 1/8 + 1/16 + 1/32 + …,现在被称为几何级数,形式为a+ar+ar+ar+ar⁴+…,其中 a 被称为系数将级数设置为等于总和 S 并使用几何级数
s=a+ar+ar+ar+ar⁴+…的规格化形式
S / a= 1+r+r+r+r⁴+…或其规格化向量形式
s/a=【1 1 1 1 1 1 1 1……】rrr**r⁴…]ᵀ或其规格化部分数列 a= 1+r+r+r+…+rⁿ,其中 n 为部分和 S ₙ.包含的最后一项的幂(或次数)

在这篇文章中,我提出,2500 年前解决了希腊数学家争论的那个几何级数现在是改进今天数学教育的关键。根据杰夫·霍金斯在他的书《一千个大脑—一种新的智力理论》第 88 页中的说法,“成为专家主要是为了找到一个好的参考框架来安排事实和观察。”在这篇文章中,几何级数是一个拟议的参考框架的来源,用于安排关于数学知识主体的事实和观察。

根据维基百科的说法,短语“知识体”的意思是“组成一个专业领域的一整套概念、术语和活动”考虑到数学知识体系涵盖了如此多的概念,很难想象出一个“好的参考框架来安排事实和观察结果”关于数学的“一整套概念、术语和活动”。在这篇文章中,我建议用一个多层建筑的平面图作为类比。具体来说,我为正式称为多项式向量空间(即所有可能的多项式的集合)提出了一个平面图,然后我试图证明这个多项式向量空间应该是数学所有其他层面的底层入口点。

所有多项式函数空间的一个建议参考系。本文描述了标绘项目的列表及其在参考框架中的位置。

虽然这篇文章关注的是多项式向量空间作为数学知识主体的底层,但是在这个建筑物的类比中,当然还有许多其他的层,每一层都有自己的度量单位来测量距离。例如,也许一个好的二楼可以容纳所有与概率和统计相关的概念。或许范畴理论可以用来揭示许多不同楼层之间的相似之处。然而,本文的重点只是多项式向量空间底层,如上图所示,其中对数对数图的原点是几何级数,到其他多项式向量空间界标(例如,复傅立叶级数)的距离是作为远离几何级数的自由度来测量的。

假设是,提供一个好的数学知识体系的参考框架将显著增加每个人对数学的理解。当然,一个假设需要被检验以使它不仅仅是一个假设。因此,本文的结论提出了检验假设的方法。在得出这个结论之前,这篇文章在提出的参考框架中定位了大约十几个里程碑,偶尔还会提到在神经科学和心理学领域的发现。为了清楚起见,那些提到神经科学和心理学的地方用斜体表示,并在左边空白处用竖线突出显示,如下所示。

杰夫·霍金斯在他的书《一千个大脑—一种新的智力理论》的第 50 页写道,“……我们需要把新大脑皮层看作主要处理参考框架。大部分电路用于创建参考框架和跟踪位置。感官输入当然是必不可少的。正如我将在接下来的章节中解释的那样,大脑通过将感官输入与参照系中的位置相关联来构建世界模型。

为什么参考系如此重要?大脑从拥有它们中获得了什么?首先,参照系可以让大脑学习某个东西的结构。咖啡杯之所以是一个东西,是因为它是由一组在空间上相对于彼此排列的特征和表面组成的。同样,脸是相对位置排列的鼻子、眼睛和嘴巴。你需要一个参照系来确定物体的相对位置和结构。

第二,通过使用参考系定义一个对象,大脑可以一次性操纵整个对象。例如,一辆汽车有许多相互关联的特征。一旦我们学会了一辆车,我们可以想象它从不同的角度看起来是什么样子,或者在一个维度上被拉伸。为了完成这些壮举,大脑只需旋转或拉伸参考框架,汽车的所有功能都随之旋转和拉伸。

第三,需要一个参照系来计划和创造动作。假设我的手指碰到了手机正面,我想按顶部的电源按钮。如果我的大脑知道我手指的当前位置和电源按钮的位置,那么它可以计算出将我的手指从当前位置移动到所需的新位置所需的移动。进行这种计算需要相对于电话的参考系。"

除了在数学知识主体的底层表示参考系原点之外,几何级数还可以作为数学中许多不同抽象层的一致介绍,这些抽象层就是本文的结构:

  1. 数量,
  2. 数字,
  3. 代数,
  4. 几何学,
  5. 功能,
  6. 向量空间,以及
  7. 矩阵指数。

为了简洁,这篇文章没有说明超过七层的抽象,但是它可以。同样为了简洁,本文中使用的软件是附录中的可选读物。然而,在拉丁谚语Docendo discimus中有很多真理,意思是“通过教学,我们学习”,软件是“教”计算机完成任务的一种形式,经常暴露教师的许多最初被忽视的误解。因此,快速浏览附录可能是一个很好的练习。扩展附录中的代码来“教”你自己的计算机一些新的东西会更好。

需要明确的是,我不是数学家、心理学家、教师或神经科学家。然而,作为一名软件工程师,我经常想知道为什么这么多人(包括我自己)觉得数学比其他学科更难学。如果学习数学的相对困难是由于它缺乏一个好的参考框架和/或它的许多抽象层,也许这篇文章提出的平面图类比及其在每个抽象层的介绍中使用几何级数作为一致和熟悉的立足点的方法可能是一个有助于学习数学的一般结构,特别是对于正在形成他们关于数学的心理模型的高中生。

理想情况下,这篇文章将吸引一些真正的数学家、心理学家、教师和神经科学家来测试这篇文章的假设(即,当向学生介绍数学概念时,一贯使用几何级数作为参照系原点将显著提高他们对这些主题的理解)是正确的...或者不是。然而,有历史证据表明持续的重复是有效的。

维基百科用这些例子描述了虚幻的真理效应:“虽然真理效应只是在最近几年才得到科学证明,但它是一种人们已经熟悉了几千年的现象。一项研究指出,罗马政治家卡托在每次演讲结束时都呼吁摧毁迦太基,他知道重复会产生一致意见,而且拿破仑据说“说在修辞学中只有一个非常重要的数字,即重复”,由此重复的肯定在头脑中“以某种方式固定下来,最终被接受为被证明的真理”。其他利用了真相效应的还有昆体良、罗纳德里根、比尔克林顿、小布什、唐纳德川普、莎翁的 朱利叶斯凯撒 中的阿非利加努斯·戈狄亚努斯二世。重复关于产品的毫无根据的说法的广告可能会促进销售,因为一些观众可能会认为他们是从客观的来源听到这些说法的。新闻媒体也使用真相效应,这也是政治宣传的一个主要内容。”

鉴于我们都倾向于相信熟悉的事物(不幸的是,即使这种熟悉是由于重复错误的陈述),为什么不采用同样的偏见来鼓励数学学生的信心呢?将当前完全不同的数学概念与相同的几何级数联系起来,会增加对不熟悉的数学主题的熟悉程度。

抽象层 1:数量

在几何学中,当一个三角形的三个角与另一个三角形的三个角相同时,两个三角形相似。这是通过用某个因子 w 缩放一个三角形的三个边长来计算另一个三角形的边长。因为三角形的面积是 A = bh /2,其中 Abh 分别是三角形的面积、底边和高,所以边长由 w 缩放的三角形的面积是A=(WB)(wh)/2 = .

图一。归一化几何级数(公比 r=1/2)的项,显示为重叠相似三角形的面积。最大的三角形(即系列中的第一项)的底边被设置为 2,使得代表第一项的三角形的面积等于其高度。

在本文中,几何级数的每一项都表示为相似三角形的面积。因此,在本文中,一系列相似的三角形都具有相同的三个角,并且具有按公比 r 递增的面积。为了用公比因子 r 来说明相似三角形面积渐进变化,相似三角形的边长必须以因子 r、渐进变化,因为rA=rbh/2 =(WB)(wh)/2 =w**BH/2

虽然将数量表示为面积(例如,饼图的切片)并不罕见,但将数量表示为部分重叠(或遮挡)的面积却不常见。例如,在图 1 中,红色大三角形的面积为 1(归一化几何级数中第一项的值),因为三角形的底边是 b =2,三角形的高度是 h =1,三角形的面积是 A = bh /2 = 2/2 =1,但只有该区域最右边的 1-r可见。同样,下一个最大的(绿色)三角形的面积是 r (几何级数中第二项的值)因为三角形底是 2 r ,三角形高是 r ,三角形面积是A=BH/2 = 2r【t3t 继续,下一个最大的(蓝色)三角形的面积是 r (几何级数中第三项的值)因为三角形底是 2 r ,三角形高是 r ,三角形面积是A=BH/2 = 2RR/2 =r,但只有最右边的 r 【T57 从最大到最小,每一个连续的几何相似三角形的面积以因子 r 减少,因为三角形的底和高都以因子r减少。同样,面积 r ⁿ与面积 r ⁿ⁺的重叠只留下最右边的r

这篇文章使用重叠的相似三角形面积来表示几何级数项的值,乍一看似乎有些笨拙和不必要。然而,相似三角形的重叠是揭示几何级数的几个几何观点的基础。事实上,作为一个预览,这篇文章的其余部分基本上是关于操作这些重叠的相似三角形:

  • 改变那些三角形区域的形状(即去除重叠),
  • 改变这些区域的方向(即,围绕基座旋转),
  • 改变这些区域的高度(即改变系数),以及
  • 为这些领域的总和提供了新的视角。

根据 Scott Johnson 在加州大学洛杉矶分校婴儿实验室的视觉感知发展https://www.babylab.ucla.edu/wp-content/uploads/sites/8/2016/09/Johnson2010b.pdf中的高级综述,“……新生儿无法感知闭塞,这种闭塞感知在出生后的最初几个月出现。有趣的是,婴儿的所有这些效应都依赖于遮光器后面移动的被遮挡刺激,而不像成年人即使有静态图像也能感知遮挡。”Johnson 博士继续在一篇发展心理学文章中写道“……直到 6-8 个月之前,还没有观察到静态遮挡显示中的知觉完成。”****

尽管这篇文章将几何级数的项表示为部分遮挡区域是不寻常的,但识别现实世界中部分遮挡物体的问题非常重要,足以保证这种能力的早期开发(例如,6-8 个月)。

抽象层 2:数字

系数为 a =1/2,公比为 r =1/2 的 Elea 几何级数的齐诺是数字计算机中分数的二进制编码[逼近的基础。具体来说,以其归一化向量形式书写的几何级数是s/a=【1 1 1 1 1 1……】【1】r rt20】rt22】r⁴…]ᵀ.保持基函数[1r rrrrr⁴…]ᵀ的列向量不变,但推广行向量[t30】[1 1 1 1 1……]使得每个条目可以是 0 或 1,这允许对任何分数进行近似编码。例如,值 v = 0.34375 被编码为
v/a=【0 1 0 1 1 0……】【1*r
r
rr⁵…]ᵀ其中系数 a = 1/2,公比r*= 1/1 通常,行向量被写成更紧凑的二进制形式 v = 0.010110,即十进制的 0.34375。](https://en.wikipedia.org/wiki/Binary_number)
****

同样,系数 a =1,公比 r =2 的部分几何级数是数字计算机中二进制编码整数的基础。还是那句话,用其归一化向量形式写成的几何级数是s/a=
【1 1 1 1 1 1……】【1】r r*r
r⁴…]ᵀ.保持基函数的列向量
【1
r r
r
r⁴…]ᵀ不变,但是将行向量[1 1 1 1 1 1……]一般化,使得每个条目可以是 0 或 1,从而允许任何整数的编码。例如:值 v = 151 编码为
v/
a=【1 1 1 0 1 0 1 0 0 1 0……】【1rt31】rt33】r⁴rrrr*⁸…]ᵀ凡通常情况下,行向量以相反的顺序书写(因此最高有效位在前),采用更紧凑的二进制形式 v = …010010111 = 10010111,即十进制的 151。
****

图 2a。根据 IEEE 754 标准对 32 位浮点数进行编码的位字段。

如下图所示,32 位浮点数字的标准二进制编码是一个二进制编码的整数和一个二进制编码的分数的组合,从最高有效位开始

  • 符号位,后面是
  • 8 位整数指数字段,假设偏移量为 127(因此值 127 表示指数值为 0),基数为 2,表示指数值指定分数字段的位移,其后是
  • 一个 23 位的分数字段,假定但未编码为 1,作为分数的最高有效非零位,如果被编码,它将位于第 23 位。

基于具有二进制编码 0.010110 的 0.34375 的先前示例,浮点编码(根据 IEEE 754 标准)0.34375 是

  • 符号位为 0,因为数字不是负数,
  • 8 位整数指数字段,其必须指定对 2 位左移进行计数以获得从 0.010110 到 1.0110 的原始二进制编码的移位,并且恢复原始二进制编码的计数器移位是由指数值 125 指定的 2 位右移(因为 125-127 =-2 是 2 位右移),其在二进制中是 0111 1101,****
  • 一个 23 位的分数字段:. 0110 0000 0000 0000 0000 000 000。

虽然像这样手工编码浮点数是可能的,但让计算机来做更容易,也不容易出错。以下 Julia 代码确认了数字 0.34375 的手算浮点编码:

*****julia> bitstring(Float32(0.34375))
"00111110101100000000000000000000"*****

具有持续不断的重复模式的十进制分数(例如,0.3333…或 0.09090909…或 0.12341234…)只能在编码为浮点数时进行近似,但它们总是可以精确地定义为两个整数的比率,并且这些整数可以使用几何级数进行计算。诀窍是使用几何级数的封闭形式即 S =a/(1r)当| r | < 1 时。几何级数的封闭形式将在本文对抽象层 3(代数)的讨论中导出,并在抽象层 4(几何)的讨论中再次导出。然而,即使在这些推导之前,你也可以确认 s/a*= 1/(1-t14】r)= 1+r+r+r+r⁴+…当| r | < 1 除以 1(1-t28)*********

举个用几何级数确定分子分母的例子,有理数 0.3333…可以写成几何级数S= 0.3+0.3(1/10)+0.3(1/10)+0.3(1/10)+…其中系数 a = 0.3,公比 r = 1/10。因此,| r | < 1 和S=a/(1--r)=(3/10)/(1-(1/10))=(3/10)/(9/10)= 3/9 = 1/3 = 0.333…同样,0.09090909…可以写成几何级数 S = 0.09+0.09(1/100)+0.09(1/100)+0.09(1/100)+…其中系数 a = 0.09,公比 r = 1/100,从而得到闭合形式 S =a*/(1-***

图 2b。图 1 所示的相同几何级数(系数 a=1,公比=1/2),但公比 r 乘以 exp(i θ) ,导致几何级数(现在是一个复杂的几何级数)的各项以基频的整数倍旋转。(该动画的帧由附录 c 中列出的 Julia 代码生成。)

除了能够计算出有理数的精确分子和分母,并且是大多数计算机使用的二进制编码的核心,几何级数还有另一个技巧:它的项可以以谐波频率旋转。如相邻动画所示,通过设置公比 r 为复数r= |r|eⁱᶿ,可以将几何级数扩展为复数的集合,其中| r |为该复数的幅值,θ为其在复平面中的方向角。使公比 r 变得复杂,通过添加一个在图 1 中指向屏幕外、在图 2b 中指向上的虚拟数字的 z 轴,将图 1 从二维扩展到三维,从而 yz 平面是一个复平面。图 1 所示的量围绕图 2b 中的 x 轴旋转。使这种旋转特别有用的是,代表复杂几何级数项的相似三角形区域以不同的速度旋转,具体来说是以某个基频的整数倍旋转。每一个复几何级数的所有旋转项之和总是描出一个正圆,只要| r | < 1。

人们思考各种各样的话题,从深奥的话题,如编码数字,到对思维机制本身的普遍好奇。在人类大脑中,新皮层是大脑皮层最外层和最大的部分,而大脑是大脑最外层和最大的部分。大脑的进化在旧的部分上增加了新的部分,它们都相互影响。按体积计算,新大脑皮层约占人脑的 70%。如果拉直,新大脑皮层只有大约 2.5 毫米(0.10 英寸)厚,但它有许多折叠和折痕,可以将更多的自己装入头骨。

抽象层 3:代数

除了前面提到的几何级数的规范化形式和部分级数形式之外,封闭形式非常实用,因为它们大大减少了计算级数所需的运算次数。众所周知的几何级数封闭形式的推导减去了许多内部项,如下所示:

sₙ/a= 1+r+r+r+…+rⁿ⁻+rⁿ,
rsₙ/
a=r+r+r+…+r sₙ/arsₙ/a= 1rⁿ⁺、
(1
r)sₙ/a= 1rⁿ⁺、
sₙ/
a

r < 0 时,部分级数的内项用加法而不是减法抵消。代入p=--r-0(其中 p 代表正值)有助于跟踪符号。******

sₙ/a= 1p+p-p+…-pⁿ⁻+pⁿ(n 为偶数时)、
psₙ/a=pp+p…+pⁿ⁻p+pⁿ⁺(n 为偶数时)
Sₙ/ a
sₙ/a=(1+pⁿ⁺)/(1+p)(当 n 为偶数时)
sₙ/a=(1-rⁿ⁺)/(1-r)(图********

同样的,

sₙ/a= 1p+p-p++pⁿ⁻-pⁿ(n 为奇数时)、
psₙ/a=p—t101】p+p—t109】pⁿ⁻+p—t115】p【t111
sₙ/a=(1-pⁿ⁺)/(1+p)(n 为奇数时),
sₙ/a=(1-rⁿ⁺)/(1******

方便地说,sₙ/a=(1--rⁿ⁺)/(1--r)是相同的结果****

  • **r0 为偶数或奇数 n、
  • **r0 为偶数 n,且
  • **r0 为奇数 n。

要将部分和 Sₙ/ a 扩展到全和 S/ a ,请注意,当 n 趋近于无穷大时, r ⁿ⁺趋近于 0 当且仅当| r | < 1。那么,几何级数的封闭形式是

s/a= 1/(1--r)但仅限于范围| r | < 1 内。****

当公比 r 为复数时,该封闭形式(及其证明)仍然适用。

虽然新皮层约占大脑体积的 70%,但另外 30%是至关重要的,经常支配你的新皮层。例如,你的大脑皮层可能会说服你屏住呼吸。然而,对大多数人来说,大约一分钟后,脑干的自主呼吸功能就会显示出来,并让你的新大脑皮层停止让你屏住呼吸的想法。

STEM 老师通常专注于训练学生的新皮层,而忽略了大脑中古老但至关重要的 30%部分,它并不是新皮层。相比之下,广告商往往完全关注大脑古老的 30%,操纵我们的偏见和情绪,以及我们对不寻常或意想不到的事物的迷恋。因此,大多数广告商比大多数 STEM 教师更有吸引力。

抽象层 4:几何

图 4a。系数 a = 4/9,公比 r = 1/9 的几何级数的几何透视。

从稍微不同的角度观察一个数学问题偶尔会神奇地简化解决方案。比如,系数 a = 4/9,公比 r = 1/9 的几何级数 4/9 + 4/9 + 4/9 + 4/9⁴ + …,从级数的展开形式看,有一个和并不明显。但是,使用代数推导的几何级数闭合形式公式当| r | < 1 时,4/9 + 4/9 + 4/9 + 4/9⁴ + …之和很容易计算为
s =
a
/(1-
r
)=(4/9)/(1–1/9)=(4/9)/(8/9)= 1/2。下图将该几何级数的每一项的值表示为一个紫色正方形的面积,展示了一个使 4/9 + 4/9 + 4/9 + 4/9⁴ + …之和更容易计算的透视图。具体地,通过观察单位正方形由无限个 L 形区域组成,每个区域具有等量的紫色和黄色区域(即,每个 L 形区域中有 4 个紫色正方形和 4 个黄色正方形),无限个紫色区域的总和必须是单位正方形面积的一半,即 1/2(1) = 1/2。尽管图 4a 的几何透视有助于快速揭示系数为 a = 4/9 且公比为 r = 1/9 的特定几何级数的和,但几何级数闭合形式公式本身的几何透视将有助于可视化每个收敛的几何级数。为此,我们抛开正方形,回到几何级数项的类似三角形面积表示。****

去除图 1 所示的相似三角形之间的所有重叠,揭示了接近几何级数闭合形式的几何部分级数的简单几何解释:随着部分级数中的项数接近无穷大,表示级数和的面积接近三角形的面积。具体地说,每一项都可以表示为一个梯形的面积,这些梯形聚集成一个更大的梯形,随着更多项的增加,这个梯形收敛成一个三角形。学习几何级数封闭形式的几何解释有几个好处:

  • 梯形转化为三角形是对众所周知的几何级数收敛的代数解释的一种令人耳目一新的几何解释。
  • 能够将聚集梯形末端的收缩三角形尖端视为部分几何级数近似误差(即,s-Sₙ,这是部分级数 sₙ少于完整级数 s 的量)也很好。
  • 能够从几何学上解释众所周知的几何级数的封闭形式,就像终于能够将一张脸与一个人的名字联系起来。

图 4b。举例说明 0

In the adjacent figure, the common ratio is in the range 0<r1 的情况下,几何级数的封闭形式的三步几何证明。这个几何级数封闭形式的几何推导有三个主要步骤:**

  1. (顶部曲线)将 S ₙ/ a 的前 n+1 项表示为重叠的类似直角三角形的区域,如图 1 所示。
  2. (中间图)按照从大到小的顺序,删除每个三角形的重叠区域,该区域始终是其面积的一小部分 r ,并按 1/(1r的比例缩放该三角形的剩余 1r非重叠区域,这样以前重叠的三角形的面积(现在是非重叠梯形的面积)保持不变。******
  3. (底部绘图)将生成的 n+1 个非重叠梯形聚合成一个非重叠梯形,并计算其面积。聚合梯形的面积表示部分序列的值。那个面积等于最外面的三角形减去空三角形尖:
    sₙ/
    a
    =(1
    -
    t23】rⁿ⁺)/(1--r)简化为 s/a= 1/(1-r)当 n 趋近于无穷大时| r | 【T38******

图 4c。-1

当公比在范围**1<r<0 内时,类似的三角形区域在正负区域之间交替变化,如下图所示。将所有正面积相加得到一个公比为 r 的几何级数。类似地,合计所有负面积也会产生一个具有公比 r 的几何级数,但该级数也会按 r 进行缩放。将针对 0<<1 的情况导出的 S/a= 1/(1--r)应用于这两个具有公比 r 的几何级数,得到 S/a= 1/(1--r【T25)+r/(的总面积 =(1+r)/(1-r)= 1/(1-r)对于-1<r<0 的情况,与 0<r<1】1 的情况结果相匹配。 因此几何推导确认了几何级数的封闭形式的代数推导:S/a= 1/(1--r)当| r | < 1。顺便说一句,在几何推导中,当| r |≥1 时,代表部分数列的聚合梯形不再收敛于固定面积三角形。******

图 4d。放大收敛的几何级数的项,表示为重叠的相似三角形的面积,或者等价地,非重叠的梯形的面积。

如邻图所示,几何级数的项表示为重叠相似三角形的面积,或者等价地,非重叠梯形的面积,放大后具有无限重复的自相似性。换句话说,连续放大表示几何级数项的区域会发现,收敛的几何级数的较小项与较大项具有相同的形状(但比例不同)。

图 4e。系数 a=1 和公比 r=eⁱᶿ/2.的复几何级数

有了先前的几何证明,即重叠的相似三角形的区域(例如,图 1)可以被变换成不重叠的梯形的区域(例如,图 4b),图 2b 的旋转相似三角形可以被变换成相邻图 4d 中的旋转梯形。注意,图 4d 中梯形的高度按 0.5 的比例缩放,使得旋转梯形符合图 2b 的相同绘图界限。因此,图 4e 中旋转梯形的高度实际上是图 2b 中旋转三角形高度的两倍。当然,旋转三角形和旋转梯形的面积是相同的,因为它们都表示复几何级数的相同项。

随着更多项的增加,图 2b 中旋转三角形的面积之和以及图 4e 中旋转梯形的面积之和描绘出更接近于正圆形的形状。反演几何是一种优雅的方法,用来展示每个复杂的几何级数,S/a= 1/(1-r*),当项数接近无穷大, r 是复数,| r | < 1 时,都描绘了一个完美的圆。*****

图 4f。半径为 1 的反转圆(显示为红色)、以 1+i0 为中心的半径为 1/2 的圆(显示为绿色)以及该偏移圆的反转圆(显示为蓝色)的反转几何图。根据反演几何的定义,| OP | | OP ' | = | OQ | | OQ ' | = | OR | | OR ' | = 1。请注意,倒圆中样本的不均匀间距(蓝色所示)与图 4c 中的动画一致,其中手臂完全伸展时移动最快。

在上图 4e 中,根据几何级数闭合形式公式,s/a= 1/(1-)eⁱᶿ/2).分母(即 1eⁱᶿ/2)清楚地描绘了一个半径为 1/2 的圆(下图中显示为绿色圆),当θ从 0 到 2π变化时,该圆以 1+i0 为中心。逆几何证明那个(绿色)圆的逆也是圆(下图蓝色的圆)。在这种情况下,倒圆就是单位圆(下图中的红色圆)。非正式地说,反转将(红色)反转圆圈的内容翻转过来,****

  • 将红色圆圈外的点(例如,点 P)映射到红色圆圈内的点(例如,点 P’是点 P 逆的简称),
  • 将红色圆圈内的点(例如,点 Q)映射到红色圆圈外的点(例如,点 Q’是点 Q 逆的简称),以及
  • 将红色圆圈上的点(例如,点 R)映射到它们自身,

其中映射被约束为| OP | | OP ' | = | OQ | | OQ ' | = | OR | | OR ' | = 1。注意,这个约束是针对反转圆的归一化形式的。一般情况下,约束条件写成| op | | op ' | = | OQ | | OQ ' | = | or | | or ' | =rᵢₙᵥ其中 r ᵢₙᵥ是反演圆的半径。还要注意,圆内反演的映射约束是几何平均的一种形式,可以表示为几何序列的一部分。例如,参考图 4f,递增序列|OQ|, r ᵢₙᵥ,|OQ'|可以写成 r ᵢₙᵥ/α, r ᵢₙᵥ,α r ᵢₙᵥ其中α > 1 等于比率|OQ'|/ r ᵢₙᵥ,并且是该部分序列中的公比。为了简洁,这篇文章不包括逆几何的定理和证明。总的来说,那些由 Kozai 和 Libeskind 的这篇论文彻底涵盖的定理和证明,具有反复出现的主题相似三角形和内切角。

这篇文章总体上强调几何级数的视觉几何解释,尽管可能有人更喜欢几何级数的代数解释,也可能有人视觉敏锐度较低。杰夫·霍金斯在他的新书《一千个大脑》—《智力新理论》第 157 页中写道:“大脑皮层的大小因人而异。例如,V1 区域,主要的视觉区域,在某些人中可能是其他人的两倍大。每个人的 V1 厚度都是一样的,但是面积,也就是柱子的数量会有所不同。一个 V1 相对较小的人和一个 V1 相对较大的人都具有正常的视力,并且两个人都没有意识到这种差异。然而,这是有区别的;V1 大的人有更高的敏锐度,这意味着他们能看到小东西。例如,如果你是一名钟表匠,这可能会很有用。”****

即使对于视力很差或没有视力的人来说,重叠的相似三角形转化为不重叠的梯形的概念在学习几何级数时也是一个有用的视角。

图 4g。数学知识体系的参考框架中的前几点。

现在,我们已经讨论了编码数、几何级数和复几何级数,可以开始填充本文提出的数学知识体系的参考框架了,如下图所示。请注意,封闭形式是系列的约束版本。具体地,几何级数和复杂几何级数的闭合形式都要求| r | < 1,并且该图通过在表示级数的圆内绘制表示闭合形式的三角形来指示该约束。该图的图例描述了绘制的四个项目。连同它们标绘的位置,这四个项目在概念上类似于现实世界的地标,我们的大脑通过使用大脑海马中的位置细胞来建模。与我们的大脑通过使用大脑内嗅皮层中的网格细胞来模拟真实世界的距离不同,图 4g 中的“距离”测量的是自由度。例如,几何级数的公比与复几何级数的公比相隔一个自由度的“距离”,因为几何级数的公比可以是来自实数线的任何数(即一个自由度或 D.O.F .),而复几何级数的公比可以是来自实数线的任何数和来自虚数线的任何数的组合(即两个自由度)。大脑如何模拟一种新型的距离?

在第 62 页的 一千个大脑一种新的智能理论中,杰夫·霍金斯写道,“……为了学习一个事物的完整模型,你需要网格细胞和位置细胞。网格单元创建一个参考框架来指定位置和计划移动。但是您还需要由位置单元表示的感测信息,以便将感测输入与参考系中的位置相关联。****

新大脑皮层中的映射机制并不是旧大脑中映射机制的精确复制。证据表明,新大脑皮层使用相同的基本神经机制,但在几个方面有所不同。这就好像大自然将海马体和内嗅皮层剥离到最小的形式,复制成千上万份,并将它们并排排列在皮层列中。成为了新大脑皮层。"

抽象层 5:函数

作为对本文到目前为止所涵盖的要点的快速回顾,

  • 几何级数a+ar+ar+ar+ar⁴+…完全由两个参数规定:系数 a 和公比 r******
  • 对于几何透视,几何级数的项可以被绘制为重叠的相似三角形的区域,
  • 那些重叠的相似三角形可以被转换成不重叠的梯形,当几何级数项的数量增加时,这些不重叠的梯形总体上汇聚成一个三角形,
  • 使公比 r 成为复数以谐波频率旋转复几何级数的项,
  • 这些旋转项可被绘制为重叠的相似三角形或围绕其底边旋转的不重叠的梯形,并且
  • 假设| r | < 1,当 r 的角度(在复平面中)从 0 度到 360 度移动时,表示复几何级数的项的那些有向和旋转面积的总和描绘出一个完美的圆。

通过对几何级数的所有这些扩展,系数 a 没有被扩展...直到现在。允许几何级数的每一项的系数独立于其他系数而变化将几何级数变成了一个幂级数并且可以写成a₀+ar+ar+ar+ar⁴+…******

从几何学上讲,幂级数的各项仍然可以被认为是几何级数的重叠的相似三角形或非重叠的梯形,只是现在每一项的面积在高度上按其自己的系数缩放。对于复几何级数的情况,能够改变项系数允许复幂级数描绘比正圆更多的形状。请注意,如果所有项都以相同的速度旋转,即使系数不同,它们的总和仍然会描绘出一个完美的圆,但是,因为这些项以某个基频的整数倍旋转,所以复幂级数可以描绘出更多的圆,但不能描绘出任何任意形状。非正式地说,一个复杂的幂级数所能描绘的形状是蓬松的、云状的,当然包括圆形,当所有的系数都相同时,幂级数又变成了几何级数。

需要扩展什么以使复幂级数可以追踪任意形状的问题可以归结为需要扩展什么以使复幂级数可以追踪直线段的问题。答案就在欧拉公式中:

eⁱᶿ = cosθ + i sinθ,
e⁻ⁱᶿ= cosθI sinθ,
eⁱᶿ + e⁻ⁱᶿ = 2cosθ,
cosθ = (eⁱᶿ + e⁻ⁱᶿ)/2.

从几何学的角度来看,欧拉公式表明,两个相同的反向旋转的矢量之和描绘了一条线段。基本上,反向旋转抵消了复平面中运动的一个维度。移除所有项必须以相同方向旋转的约束以及移除所有项必须以相同方向(或相位)开始旋转的约束,使得复幂级数能够扩展到复傅立叶级数,该复傅立叶级数能够描绘任意二维闭合图形(即,在同一点开始和结束的图形)。

代数上把复幂级数a₀+ar+ar+ar+ar⁴+…推广到复傅立叶级数 f(θ, c)=…c₋₄e⁻ⁱ⁴ᶿ+c₋₃e⁻ⁱᶿ+c₋₂e⁻ⁱᶿ+c₋₁e⁻ⁱᶿ+c₀+【c₁eⁱᶿ+c₂eⁱᶿ+【c₃eⁱᶿ+c】允许项在任一方向上旋转,并允许系数为复数,以便它们可以指定每个项旋转的起始方向或相位。 这些项的和可以写成 f(θ, C )来表示一个函数 f,它根据旋转变量θ和变量 C 映射到某个单值,变量C是可能的复系数的向量。因为 f(θ, C )在变量θ的每一次 360 度旋转中重复自身,所以 f(θ, C )是一个周期函数。******

图 5a。周期 T=1 的复傅立叶级数方程。上面的等式描述了如何计算复函数 f(t ),方法是对在基频的所有谐波(正负)下旋转的无限个复数加权向量求和。下面的等式描述了如何计算这些系数(即复数权重)。

该复傅立叶级数以稍微不同的符号写在相邻图的顶部等式中。先将复傅立叶级数 f(θ,c)=…c₋₄e⁻ⁱ⁴ᶿ+c₋₃e⁻ⁱᶿ+c₋₂e⁻ⁱᶿ+c₋₁e⁻ⁱᶿ+c₀+c₁eⁱᶿ+c₂eⁱᶿ+c₃eⁱᶿ+c‖e‖t+…的展开形式缩短为∑求和记数法。第二,函数的输入是 t 而不是θ,但是θ等于 2π f ₀t,并且缩放后的输入对函数的输出没有影响。最后,为了稍微简化函数的参数化及其积分,周期 t 被任意设置为 1,这意味着 f ₀ (=1/T)也是 1。通过将展开形式的复傅立叶级数 f(t)代入积分,并注意到所有旋转项积分为零,仅留下从时间 0 到 1 的常数 cₙ的积分,即 cₙ.,可以确认图 5a 的底部方程为真这个 3blue1brown 视频很好地说明了旋转项积分为零。****

图 5b。绘制字母“e”(代表指数)的复杂傅立叶级数的动画。附录 B 包含生成该动画的帧(和音频)的 Julia 代码列表。

作为复傅立叶级数追踪任何二维闭合形状的能力的示例,相邻的动画显示了复傅立叶级数收敛到字母“e”(代表指数)的图形。所有逆时针方向旋转的项,根据右手法则是正方向,聚合成一个右“臂”(延伸右“手”法则解剖学比喻)。类似地,所有顺时针方向旋转的术语都聚合到一个左臂中。右臂端点和左臂端点之和是复傅立叶级数的近似值,如动画中的符号“+”所示。从原点到“+”符号点的中途也是右臂端点和左臂端点之间的中途。双中点被绘制为符号“o ”,就像一只蜘蛛包裹着一个最近的受害者,动画的左臂和右臂围绕着“o”旋转。

让两只手臂绘制图 5b 中的复傅立叶级数似乎是让一只手臂绘制图 4e 中的复几何级数的自然延伸。如图 5b 的后平面所示,将许多旋转向量聚集到两个臂中的替代方法是将它们聚集到单个臂中,其中相邻的段以之字形在顺时针旋转和逆时针旋转之间交替。双臂动画提供了一种透视,揭示了协调运动的内在顺序。例如,图 5b 中动画的参数化以逆时针方向穿过大的内弧,然后以顺时针方向穿过大的外弧。在逆时针方向穿过大的内弧时,左臂(负责顺时针旋转)保持静止。类似地,在顺时针方向穿过大的外弧时,右臂(负责逆时针旋转)保持静止。

图 5c。线性最小二乘误差拟合复傅立叶级数系数的幅度的对数,以从复傅立叶级数系数中提取“幻象”公比 r。

尽管复傅立叶级数的双臂动画可能看起来像是复几何级数的单臂动画的自然延伸,但复几何级数具有公比 r ,而复傅立叶级数没有公比(即,在图 5a 中的复傅立叶级数方程中没有提到公比,并且公比定义了图 5b 中的旋转梯形区域的形状)。然而,如相邻图所示,通过对复傅立叶级数系数数据执行几何级数模型 ar ⁿ的最小平方误差拟合,可以计算复傅立叶级数的“虚公比”,不包括指定字母“e”任意位置的常数项的系数。这种扩展的回报是,它使单个 3D 图能够显示空间数据(字母“e”的轨迹)和频率数据(以不同速度旋转的梯形的高度)以及相位数据(这些旋转梯形的起始方向)的共享上下文。

观察图 5b 中所有错综复杂的协调运动,会惊奇地发现所有的协调都是由图 5a 中的两个简单方程来解释和定义的。同样,在你的大脑中有许多复杂的协调工作:你的大脑大约有 150,000 个皮质柱,每个都由数百个“微柱”组成,每个微柱都包含略多于一百个神经元,每个神经元都有数千个,有时是数万个突触连接到其他神经元。然而对于人类大脑来说,我们还没有找到简单的原理来解释和定义这种复杂的协调。

根据杰夫·霍金斯在他的书《一千个大脑》的第 39 页所说,“人们常说大脑是宇宙中最复杂的东西。他们由此得出结论,对于它是如何工作的,不会有一个简单的解释,或者说,也许我们永远也不会理解它。科学发现的历史表明他们是错误的。重大发现几乎总是以令人困惑的复杂观察为先导。有了正确的理论框架,复杂性并没有消失,但它似乎不再令人困惑或望而生畏。”

图 5d。将复傅立叶级数“地标”添加到数学知识主体的建议参考系“景观”中。

将复傅立叶级数“landmark”添加到本文提出的数学知识体系的参考框架中,相邻的图显示复傅立叶级数是距离复几何级数 4n+1 的自由度“距离”,其中 n 是部分复傅立叶级数中某项的最高幂。4n+1 的自由度“距离”是因为复几何级数的系数是从实数行中选取的单个实数(即一个选择自由度)。相比之下,复傅立叶级数有 2n+1 项:n 项正向旋转,n 项负向旋转,一项根本不旋转。由于能够为每一项指定任何初始旋转相位,这 2n+1 项中的每一项都可以具有复系数,从而导致系数选择的 2(2n+1)个自由度。差值为 2 *(2n+1)1 = 4n+1。*****

抽象层 6:向量空间

(待办事项)

图 6a。归一化几何级数的向量形式

如几何级数的归一化向量形式的邻图所示,列向量的项是与归一化的几何序列的项相匹配的基函数。与函数非常相似,行向量将多个输入量(列向量条目)映射到单个输出量(总和)。

图 6b。幂级数的向量形式。

去掉所有系数必须相同的约束,几何级数概括为下图中向量形式所示的幂级数,其中列向量变量从 r 变为 x ,因为该级数不再是具有公比 r 的几何级数。任何多项式函数都可以用列向量中这些基函数的正确线性组合来指定。换句话说,图 6b 所示级数的函数空间是所有多项式函数的空间。一些函数在这个函数空间中,即使它们不具有多项式函数的典型外观(例如,3 x +2 x +7)。例如,定义函数 eˣ的方法之一是使用多项式函数 eˣ = 1 + x /1!+ x /2!+ x /3!+ x ⁴/4!+ … .然而,许多函数(例如,sin(x))不在该函数空间中,并且只能由多项式函数来近似。因此可能存在两个维度的近似:第一个维度是近似整个序列 s 的部分序列 Sₙ,第二个维度是近似实际函数 f 的多项式函数空间( x )。泰勒级数通过定义如何设置幂级数的系数以使多项式函数逼近实际函数 f(x)来处理第二维近似。

要计算一个函数的泰勒级数系数,首先用多项式 f(x)≈c₀+cx+cx+cx+…)的展开形式来逼近该函数,并设置 x =0 来确定 c 【T19 然后取一阶导数 f⁽⁾(x)≈c₁+2cx+3cx+…,设 x =0 确定 c ₁≈f⁽ ⁾(0).然后取二阶导数 f⁽⁾(x)≈2c₂+3! cx + …,设置 x =0 来确定 c ₂≈f⁽ ⁾(0)/2.然后取三阶导数 f⁽ ⁾( x ) ≈ 3! c ₃ + 4! cx …,设置 x =0 确定 c ₃≈f⁽ ⁾(0)/3!。然后取四阶导数 f⁽⁴⁾( x ) ≈ 4!₄+5! cx …,设置 x =0 确定 c ₄≈f⁽⁴⁾(0)/4!。假设函数 f(x)足够平滑,以使导数存在,以这种方式继续到 k 阶导数,以确定 c ₖ ≈ f⁽ᵏ⁾(0)/k!这是泰勒级数系数公式,用于逼近以 x =0 为中心的 f( x )的一部分。**

从技术上讲,这些系数被称为 Maclaurin 级数系数,因为它们近似 f( x )的一部分,该部分以 x =0 为中心。很快,近似的中心点将移动到 x ₀,这些系数将被恰当地称为泰勒级数系数。但在此之前,以下是推导这些马克劳林级数系数的另一种方法。替代方法证明了导数和积分是多项式函数(即行向量中的系数)上的线性算子(即矩阵乘数)。

图 6c。导数 d 和反导数 D⁻的幂级数和线性算子的向量形式。将 eˣ的系数乘以 d,以确认结果与 eˣ.的系数相同,这是一项很有意义的工作

下图中最上面的等式显示了线性运算符 L 如何乘以行向量中的系数。接下来的两个方程是导数矩阵 d 和反导数矩阵 D⁻。线性算子 l 可以是 d,或者可以是 D⁻,或者可以是它们或其他线性算子的某种组合。例如,L = D⁻ D = I 是单位矩阵。当然,L = DD = D 是二阶导数,L = D⁻ D⁻ = D⁻是二阶反导数,通常称为二阶积分。请注意,不定整数常数的生成是行向量前加 1、列向量前加 0 的原因。还要注意,冒着引起混淆的风险,但按照惯例,不定积分常数用大写字母 C 表示,幂级数系数用小写字母 C 表示。

图 6d。几种导数的线性算子的向量形式。

类似于前面所示的 Maclaurin 级数系数的推导,从用幂级数的展开形式近似的函数开始,下面的矢量形式推导也是计算导数的练习。相邻图像中显示了几个导数运算符,它们是矩阵乘法的结果。将图 6c 的上等式中带有前缀 1 的行向量称为 R ,将带有前缀 0 的列向量称为 X ,Maclaurin 级数系数的向量求导的起点是近似 f( x )≈ RX ,在 x =0 处计算得到 c₀≈f(0).一阶导数 f⁽ ⁾( x )≈ RDXx =0 处计算得出 c ₁≈f⁽ ⁾(0).二阶导数 f⁽⁾(x)≈rdx**在 x =0 处计算得出 c ₂≈f⁽ ⁾(0)/2.三阶导数 f⁽⁾(x)≈rd****xx =0 处求值得出 c ₃≈f⁽ ⁾(0)/6=f⁽ ⁾(0)/3!。四阶导数 f⁽⁴⁾(x)≈rdxx =0 处求值得到c₄≈f⁽⁴⁾(0)/24=f⁽⁴⁾(0)/4!。再一次,假设函数 f(x)足够平滑以至于导数存在,以这种方式继续到第 k 阶导数 f⁽ᵏ⁾(x)≈rdx以确定 c ₖ≈f⁽ᵏ⁾(0)/k!在 x =0 时进行评估。******

图 6e。关于点 x= x ₀.的泰勒级数近似的向量形式

为了近似 f(x)的一部分,而不是以 x = x ₀为中心,定义一个新的函数 g( x ,它等价于 f( x ),但是沿着 x 轴移动,使得它的原点在 f(x=x₀): g(x)= f(x+重复现在熟悉的计算多项式导数的练习,但是现在对于 g( x ),结果是 cₖ≈g⁽ᵏ⁾(0)/k!但是 g(0)=f( x ₀)所以 cₖ≈f⁽ᵏ⁾( x ₀)/k!。与 Maclaurin 级数一样,泰勒级数中的多项式变量是距近似值中心点的距离,但是由于中心点向 x = x ₀的移动,泰勒级数距中心点的距离是x-x₀,而不是 x 。因此,矢量形式的泰勒级数近似是图 6e 中的方程。****

(2021 年 12 月 11 日注:这篇文章的其余部分正在变化,我希望在这个月的某个时候完成编辑……在今年年底。)

图 4c。收敛到封闭形式的几何级数。

虽然几何级数的闭合形式(即S/a= 1/(1--r)可以用代数和几何推导,但几何推导提供了收敛速度随公比 r 变化的更好视角。下图中的动画显示了五个归一化的几何级数(公比r=-0.74,0.37,0,0.37,0.74)随着部分级数中项数的增加而收敛到闭合形式。不出所料,公比 r =0 的几何级数只需要一项就能收敛。正如所料,梯形随着| r |的增加而变细,这增加了收敛所需的项数。然而,鉴于动画中青色曲线S/a= 1/(1--r)的不对称性,关于收敛速度的 r =0 的完美对称可能会令人惊讶。代数上,(s-sₙ)/s =rⁿ⁺,当 n 是奇数时,不管 r 的符号是什么,都是一样的。一种思考方式是,在接近 r =1 时,这些项(幅度均小于 1)构成一个无穷大的和,而在接近r=-1 时,这些项(幅度也均小于 1)构成一个无穷大的斜率。在 n 值较大的动画中,Sₙ/ a 太大,在 r =1 附近不可见,但在r=-1 sₙ/a附近继续可见,您可以看到小尾巴来回翻转,越来越接近无限斜坡。****

尽管行向量乘以列向量形式的求和(例如,图 6a)暗示了向量空间,并且具有无限数量的基函数的向量空间(例如,所有多项式函数的空间)比仅具有几个数字的基向量的向量空间稍微神秘一些,但是向量空间的固有神秘是由于定义根本没有指定任何特定的基。相反,向量空间需要满足八个公理:

  1. 向量加法是结合式的(例如u+(v+w)=(u+v)+w),
  2. 向量加法是可交换的(如u+v=v+u),
  3. 向量加法有单位元(如v+0=v),
  4. 向量加法具有逆元素(例如,v+(v)=0),
  5. 标量和场乘法兼容(例如,a(bv)=(ab)v),****
  6. 标量乘法有单位元(如 1 v = v ),
  7. 标量乘法相对于向量加法是分布式的(例如,a(u+v)=au+av),****
  8. 标量乘法相对于场加法是分布式的(例如,(a+b)v=av+bv)。****

作为大量可能的向量空间的一个例子,我目前正试图设计一个类别向量空间,作为一种新型字典的一部分。现在判断这次尝试是否会成功还为时过早。

根据杰夫·霍金斯在他的书《一千个大脑》第 88 页中的说法,“成为专家主要是为了找到一个好的参考框架来安排事实和观察。阿尔伯特·爱因斯坦从和他同时代的人一样的事实出发。然而,他发现了一种更好的方法来排列它们,一种更好的参考框架,这使他能够看到相似之处并做出令人惊讶的预测。爱因斯坦与狭义相对论有关的发现最吸引人的是,他用来制作这些发现的参照系是日常用品。他想到了火车、人和手电筒。他从科学家的经验观察入手,比如绝对光速,用日常参考系推导狭义相对论的方程。正因为如此,几乎任何人都可以遵循他的逻辑,理解他是如何做出发现的。相比之下,爱因斯坦的广义相对论需要基于称为场方程的数学概念的参考系,而场方程不容易与日常物体联系起来。爱因斯坦和几乎所有其他人一样,觉得这很难理解。”

抽象层 7:矩阵指数

(待办事项)

结论

图 8。展示数学知识体系参考框架的课堂海报。教师可能希望在海报上找到当前的课程,在便利贴上有一个“我们在这里”的箭头。

在 1974 年加州理工学院的毕业典礼演讲,理查德·费曼(1965 年诺贝尔物理学奖获得者)谈到了货物邪教科学,他用这个短语来描述一个关于伪科学的故事。“在南海有一群崇拜货物的人。在战争期间,他们看到飞机带着许多好材料着陆,他们希望同样的事情现在也能发生。所以他们安排做一些像跑道一样的东西,沿着跑道边生火,做一个供一个人坐的木头小屋,在他的头上有两个像耳机一样的木头,竹条像天线一样伸出来——他是控制器——他们等待飞机着陆。他们做得很好。形式很完美。它看起来和以前一模一样。但是不管用。没有飞机降落。所以我称这些东西为货物崇拜科学,因为它们遵循所有明显的规则和科学调查的形式,但它们遗漏了一些本质的东西,因为飞机没有着陆。

[……]我注意到货物崇拜科学中有一个普遍缺失的特征。[……]这是一种科学的完整性,一种与完全诚实相对应的科学思想原则——一种向后倾斜。例如,如果你正在做一个实验,你应该报告你认为可能使它无效的所有事情——不仅仅是你认为它是正确的:可能解释你的结果的其他原因;以及你认为已经通过其他实验消除的东西,以及它们是如何工作的——确保其他人能够知道它们已经被消除了。

如果你知道的话,必须给出可能会对你的解释产生怀疑的细节。你必须尽你所能——如果你知道任何事情是错误的,或者可能是错误的——去解释它。例如,如果你提出一个理论,并宣传它,或者把它公布于众,那么你也必须写下所有不同意它的事实,以及同意它的事实。”

“但后来我开始想,我们还相信什么?(然后我想到了巫医,通过发现没有任何东西真正起作用来检查他们是多么容易。)所以我发现了更多人相信的东西,比如我们有一些如何教育的知识。阅读方法和数学方法有很大的流派,等等,但是如果你注意的话,你会发现阅读分数一直在下降——或者几乎没有上升——尽管事实上我们一直在用同样的人来改进方法。[…]

然而这些东西据说是科学的。我们研究它们。而且我觉得有常识性想法的普通人都被这种伪科学吓倒了。一个对如何教她的孩子阅读有一些好主意的老师被学校系统强迫用其他方法去做——或者甚至被学校系统愚弄,认为她的方法不一定是好方法。”

在这篇文章中,我提出了一个让数学更容易学习的方法。这个方法可能好,也可能不好。鉴于我们甚至还不了解大脑,每一项教育改进的提议都应该受到质疑。那么,如何测试这种提议的方法,以证明它确实有效,而不是又一个货物崇拜科学的例子呢?我认为调查它有两个要求:

  1. 不干涉。让参与调查的数学教师选择是否使用所提出的方法。提供一张大海报,展示数学知识体系的参考地图,张贴在教室的一侧,远离教师通常在课堂上讲话的地方。每当老师站在偏僻的海报旁边,智能手机就会自动录制被转录的视频。
  2. 说明人的可变性。学生的能力范围很广。老师也是。为了建立教师和学生能力的统计基线,只有在同一所学校的同一个班级有至少五年教学经验的教师才应被纳入调查。然后将学生数学考试成绩的变化与建议参考图的参考数量(在转录的视频中)相关联。换句话说,拟议的调查将生成一个剂量反应图,就像药物调查一样,但这里的剂量是对拟议参考图的参考,而反应是数学测试的分数。

附录

编写或探索实现数学概念的算法通常会导致对该概念的更好理解。下面的附录列出了生成本文中的图形和动画帧的源代码。我最初使用 GNU Octave 来生成动画帧,源代码在附录 a 中。但是,为了利用其最先进的 LLVM 编译器的效率,该编译器生成动画帧的速度提高了四倍(例如,30 分钟而不是 120 分钟), 我将 GNU Octave 应用程序移植到 Julia,用于生成复杂傅立叶级数动画帧的 Julia 源代码(使用 Julia 版本 1.7.0 测试)在附录 b 中。用于生成复杂几何级数动画帧的 Julia 源代码在附录 c 中。将复杂傅立叶级数动画帧生成分配到六个 CPU 内核的小 Julia 代码片段在附录 d 中。(这种并行处理将生成动画帧的时间减少到 11.1 分钟,是 GNU Octave 生成类似动画帧所用时间的十分之一。 )用于生成各种图(例如,几何级数自相似动画和数学知识体系的参考框架的图形)的 Julia 源代码在附录 e 中。用于生成二次和三次贝塞尔曲线的动画的 Julia 源代码在附录 f 中。

附录 A. GNU Octave 源代码

下面的 GNU Octave 代码为字母“e”的复傅立叶级数绘制动画生成帧。

**% define some plotting parameters 
nT = 801    % number of Time samples
nTM = nT - 1;   % number of Time sample Midpoints
T = linspace(0, 1, nT); % Time samples
nT4 = nTM/4;
dT = 1/nTM;    % difference between Time samples
TM = T(1:end-1) + dT/2; % Time sample Midpoints
THETAM = 2*pi*TM;  % angle samples
XZ = zeros(1,nT);  % X Zeros
CT = ['r'; 'g'; 'b'; 'y'; 'w'; 'k']; % Color TabletrapScale = 0.5;  % trapezoid scale
dCross = 0.15;   % diameter of '+' cross-hair
yOffset = 0.5;   % y offset of F(t)
nAng = 15;
ANGDOT = linspace(0,2*pi,nAng);
XDOT = zeros(1,nAng);
YDOT = dCross/pi*cos(ANGDOT); 
ZDOT = dCross/pi*sin(ANGDOT);% define parameterized trapezoid
wChar = 0.1; 
rInner = 0.5;
rOuter = rInner+wChar;
cy = 0.4;
thetaChar = 7*pi/4;
F = zeros(1, 2*nT); % left half real, right half imaginary
M = zeros(2,1); % slope (Re and Im)P = zeros(1,7); % Perimeter segments
P(1) = cy + rInner;
P(2) = rInner * thetaChar;
P(3) = wChar;
P(4) = rOuter * thetaChar;
P(5) = wChar;
P(6) = cy + rOuter;
P(7) = wChar;
pSum = sum(P);
TS = P/pSum; % Time Segments
TSC = cumsum(TS); % Time Segments CumulativeV = zeros(2, 7); % Vertices
V(1,1) = cy + rInner;
V(2,1) = 0;
V(1,2) = -rInner + rInner * cos(thetaChar);
V(2,2) = rInner * sin(thetaChar);
V(1,3) = wChar * cos(thetaChar);
V(2,3) = wChar * sin(thetaChar);
V(1,4) = -rOuter * cos(thetaChar) + rOuter; 
V(2,4) = -rOuter * sin(thetaChar);
V(1,5) = 0;
V(2,5) = -wChar;
V(1,6) = -cy - rOuter;
V(2,6) = 0;
V(1,7) = 0;
V(2,7) = wChar;
VC = cumsum(V, 2); % Vertices CumulativeiSegment = 1;
i0 = 1;
tOffset = 0;
nTS = 1 + floor(TS(1)/dT);
i1 = i0 + nTS - 1;
M = V(:,1) ./ TS(1);
F(i0:i1) = M(1) * T(i0:i1);
F(nT+i0:nT+i1) = 0;++iSegment; % 2
i0 = i1 + 1;
tOffset = T(i0) - TSC(1);
nTS = 1 + floor((TS(2) - tOffset)/dT);
i1 = i0 + nTS - 1;
period = 8*TS(2)/7;
THETA = 2*pi/period*(T(i0:i1) - TSC(1));
F(i0:i1) = cy + rInner*cos(THETA);
F(nT+i0:nT+i1) = rInner*sin(THETA);++iSegment; % 3
i0 = i1 + 1;
tOffset = T(i0) - TSC(2);
nTS = 1 + floor((TS(3) - tOffset)/dT);
i1 = i0 + nTS - 1;
M = V(:,3) ./ TS(3);
F(i0:i1) = VC(1, 2) + M(1) * ...
 (T(i0:i1) - TSC(2)); 
F(nT+i0:nT+i1) = VC(2, 2) + M(2) * ...
 (T(i0:i1) - TSC(2));++iSegment; % 4
i0 = i1 + 1;
tOffset = T(i0) - TSC(3);
nTS = 1 + floor((TS(4) - tOffset)/dT);
i1 = i0 + nTS - 1;
period = 8*TS(4)/7;
THETA = thetaChar - 2*pi/period*(T(i0:i1) - TSC(3));
F(i0:i1) = cy + rOuter*cos(THETA);
F(nT+i0:nT+i1) = rOuter*sin(THETA);++iSegment; % 5
i0 = i1 + 1;
tOffset = T(i0) - TSC(4);
nTS = 1 + floor((TS(5) - tOffset)/dT);
i1 = i0 + nTS - 1;
M = V(:,5) ./ TS(5);
F(i0:i1) = VC(1, 4); 
F(nT+i0:nT+i1) = VC(2, 4) + M(2) * ...
 (T(i0:i1) - TSC(4));++iSegment; % 6
i0 = i1 + 1;
tOffset = T(i0) - TSC(5);
nTS = 1 + floor((TS(6) - tOffset)/dT);
i1 = i0 + nTS - 1;
M = V(:,6) ./ TS(6);
F(i0:i1) = VC(1, 5) + M(1) * ...
 (T(i0:i1) - TSC(5)); 
F(nT+i0:nT+i1) = VC(2, 5);++iSegment; % 7
i0 = i1 + 1;
tOffset = T(i0) - TSC(6);
nTS = 1 + floor((TS(7) - tOffset)/dT);
i1 = i0 + nTS - 1;
M = V(:,7) ./ TS(7);
F(i0:i1) = VC(1, 6);
F(nT+i0:nT+i1) = VC(2, 6) + M(2) * ...
 (T(i0:i1) - TSC(6));% apply y offset shift then take midpoints
F(1:nT) += yOffset;
FM = [ ...
 (F(1:nT-1) + F(2:nT))/2 ...
 (F(nT+1:end-1) + F(nT+2:end))/2 ];% integrate complex coefficient
NTERM = [1:8, 12:4:24];
nTerm = size(NTERM,2)
nTermMax = NTERM(end)
FEM = zeros(1, 2*nTM);
CR = zeros(5, 2*nTermMax+1); % coefficient Real component
CI = zeros(5, 2*nTermMax+1); % coefficient Imag component
iA = 0;
for iTerm=-nTermMax:nTermMax
 iA++;
 EM = [cos(-iTerm*THETAM) sin(-iTerm*THETAM)];
 FEM(1:nTM) =  ... 
  FM(1:nTM) .* EM(1:nTM) - ...
  FM(nTM+1:end) .* EM(nTM+1:end);
 FEM(nTM+1:end) =  ... 
  FM(1:nTM) .* EM(nTM+1:end) + ...
  FM(nTM+1:end) .* EM(1:nTM);
 V = reshape(FEM .* dT, nTM, 2);
 VR = reshape(V(:,1),nT4,4);
 CR(1:4,iA) = (sum(VR))';
 VI = reshape(V(:,2),nT4,4);
 CI(1:4,iA) = (sum(VI))';
endfor;
CR(5,:) = sum(CR(1:4,:));
CI(5,:) = sum(CI(1:4,:));% calculate common ratio r that best fits coefficients
A = zeros(2, 1); % [a ccw terms; a cw terms]
R = zeros(2, 1); % [r ccw terms; r cw terms]
I_CMAG4 = zeros(2*nTermMax+1,5); % columns 3 thru 5 are for testing
I_CMAG4(:,1) = -nTermMax:nTermMax;
I_CMAG4(:,2) = sqrt(CR(5,:).^2 + CI(5,:).^2);
X = [ones(nTermMax,1) I_CMAG4(nTermMax+2:end,1)];
Y = log(I_CMAG4(nTermMax+2:end,2));
PHI = (X'*X) \ (X'*Y); % is faster than PHI = inv(X'*X)*X'*Y
EPHI = exp(PHI);
A(1) = EPHI(1);
R(1) = EPHI(2);
X(:,2) = abs(I_CMAG4(1:nTermMax,1));
Y = log(I_CMAG4(1:nTermMax,2));
PHI = (X'*X) \ (X'*Y); % is faster than PHI = inv(X'*X)*X'*Y
EPHI = exp(PHI);
A(2) = EPHI(1);
R(2) = EPHI(2);
Rclf;
figure(1);
plot(I_CMAG4(nTermMax+2:end,1), ...
 log(A(1)*R(1).^I_CMAG4(nTermMax+2:end,1)), ...
 'k-');
box off;
grid on;
hold on;
plot(I_CMAG4(1:nTermMax,1), ... 
 log(A(2)*R(2).^abs(I_CMAG4(1:nTermMax,1))), ... 
 'k-');
ICOLOR = mod(abs(I_CMAG4(:,1)),3) == 0;
plot(I_CMAG4(ICOLOR,1), log(I_CMAG4(ICOLOR,2)), ...
 'ko', ...
 'markerfacecolor', 'r', ...
 'markersize', 5);
ICOLOR = mod(abs(I_CMAG4(:,1)),3) == 1;
plot(I_CMAG4(ICOLOR,1), log(I_CMAG4(ICOLOR,2)), ...
 'ko', ...
 'markerfacecolor', 'g', ...
 'markersize', 5);
ICOLOR = mod(abs(I_CMAG4(:,1)),3) == 2;
plot(I_CMAG4(ICOLOR,1), log(I_CMAG4(ICOLOR,2)), ...
 'ko', ...
 'markerfacecolor', 'b', ...
 'markersize', 5);
xlabel("coefficient index n");
ylabel("ln(coefficient magnitude)");
strTitle1 = "LSE fit to coefficient magnitude data";
strTitle2 = sprintf("+ index common ratio r = %.4f", R(1));
strTitle3 = sprintf("- index common ratio r = %.4f", R(2));
title({strTitle1, strTitle2, strTitle3});
hold off;
print("-dpng", "-r400", "-Farial:10", '_pco.png');% plot coefficient magnitudes as trapezoids
SMB = zeros(2, 1); % Sum M*B; dims 1: +/- coefficients
SB = zeros(2, 1); % Sum B; dims 1: +/- coefficients
TH = zeros(2, nTermMax+1); % Trapezoid Height; dims 1: +/- coefficients; dims2: trapezoid height (1-based coefficient index)
X = zeros(2, 2); % dims 1: +/- coefficients; dims 2: x of tall/short height
M = zeros(2, 1); % trapezoid slope; dims 1: +/- coefficients
B = zeros(2, 1); % trapezoid base width; dims 1: +/- coefficientsX(:,2) = 2 * (1 .- R .^ 0.5);
TH(:,1) = [I_CMAG4(nTermMax+1,2); I_CMAG4(nTermMax+1,2)] / 2; % split DC component into +/- coefficients
TH(:,1) = TH(:,1) ./ (1 .- R); % trapezoid height: dims 1: +/- coefficients
XTRAP = [ ...
 X(1,1); X(1,1); X(1,2); X(1,2); X(1,1); ...
 NaN; ...
 -X(2,1); -X(2,1); -X(2,2);, -X(2,2); -X(2,1)];
YTRAP = [ ...
 0; TH(1,1); TH(1,1)*R(1)^0.5; 0; 0; ...
 NaN; ...
 0; TH(2,1); TH(2,1)*R(2)^0.5; 0; 0];
I_CMAG4(nTermMax+1,3) = sum(TH(:,1) .* (1 .- R), 1);xlim([-2,2]);
set(gca, 'xtick', -2:2:2);
plot(XTRAP, YTRAP, 'r-');box off;
grid on;
hold on;
for jTerm=1:nTermMax
 iColor = mod(jTerm,3) + 1; X(:,1) = X(:,2);
 X(:,2) = 2 * (1 .- R .^ ((jTerm+1)/2));
 B = abs(diff(X, 1, 2));
 TH(:,jTerm+1) = [ ...
  I_CMAG4(nTermMax+1+jTerm,2); ...
  I_CMAG4(nTermMax+1-jTerm,2)] ./ ...
  (R .^ (jTerm/2) .* (1 .- R));
 M = TH(:,jTerm+1) .* (R .- 1) ./ B;
 XTRAP = [ ...
  X(1,1); X(1,1); X(1,2); X(1,2);, X(1,1);
  NaN;
  -X(2,1); -X(2,1); -X(2,2); -X(2,2); -X(2,1)];
 YTRAP = [ ...
  0; TH(1,jTerm+1); TH(1,jTerm+1)*R(1)^0.5; 0; 0;
  NaN;
  0; TH(2,jTerm+1); TH(2,jTerm+1)*R(2)^0.5; 0; 0];
 plot(XTRAP, YTRAP, CT(iColor)); 
 SB += B;
 SMB += M .* B;

 I_CMAG4(nTermMax+1+jTerm,3) = TH(1,jTerm+1) * R(1)^(jTerm/2) * (1 - R(1));
 I_CMAG4(nTermMax+1-jTerm,3) = TH(2,jTerm+1) * R(2)^(jTerm/2) * (1 - R(2));
endMWA = SMB ./ SB % M (i.e., slope) Weighted Average
X = [0; 2; NaN; 0; -2];
Y = [-MWA(1)*2; 0; NaN; -MWA(2)*2; 0];
plot(X, Y, ...
 'color', [1.0, 0.65, 0]);
hold off;
print("-dpng", "-r400", "-Farial:10", '_pto.png');
XMWA = [0, 2, NaN, 0, -2];
YMWA = [-trapScale*MWA(1)*2, 0, NaN, -trapScale*MWA(2)*2, 0];
ZMWA = [0, 0, NaN, 0, 0];% calculate partial series terms
PS = zeros(2*nTermMax+1, 2*nTM); % Partial Sum
iTerm0 = nTermMax + 1;
PS(1,1:nTM) = CR(5,iTerm0); % DC component doesn't rotate
PS(1,nTM+1:end) = CI(5,iTerm0); % DC component doesn't rotate
for jTerm=1:nTermMax
 iPS = 2 * jTerm;
 iTerm = iTerm0 + jTerm;
 EM = [cos(jTerm*THETAM)' sin(jTerm*THETAM)']; % nTM x 2
 PS(iPS,1:nTM) = ...
  CR(5,iTerm) .* EM(:,1) - ...
  CI(5,iTerm) .* EM(:,2);
 PS(iPS,nTM+1:end) = ...
  CI(5,iTerm) .* EM(:,1) + ...
  CR(5,iTerm) .* EM(:,2); iPS = 2 * jTerm + 1;
 iTerm = iTerm0 - jTerm;
 EM = [cos(jTerm*THETAM)' sin(-jTerm*THETAM)']; % nTM x 2
 PS(iPS,1:nTM) = ...
  CR(5,iTerm) .* EM(:,1) - ...
  CI(5,iTerm) .* EM(:,2);
 PS(iPS,nTM+1:end) = ...
  CI(5,iTerm) .* EM(:,1) + ...
  CR(5,iTerm) .* EM(:,2);
endfor;
PSC = cumsum(PS, 1);% split terms according to direction of rotation
PSP = zeros(nTermMax+1, 2*nTM); % Partial Sum Positive rotation
PSP(1,:) = PS(1,:) / 2;
PSP(2:end,:) = PS(2:2:2*nTermMax+1,:);
PSPC = cumsum(PSP); % Partial Sum Positive rotation Cumulative
PSN = zeros(nTermMax+1, 2*nTM); % Partial Sum Negative rotation
PSN(1,:) = PS(1,:) / 2;
PSN(2:end,:) = PS(3:2:2*nTermMax+1,:);
PSNC = cumsum(PSN); % Partial Sum Negative rotation Cumulative
PST = PSP + PSN; % Partial Sum Total
PSTC = cumsum(PST, 1); % Partial Sum Total Cumulative% test PSP and PSN in column 4 of I_CMAG4
kTM = 50;  % test should work for any value of kTM where 1<=kTM<=nTM
I_CMAG4(nTermMax+1,4) =  (PSP(1,kTM)^2 + PSP(1,kTM+nTM)^2)^0.5;
I_CMAG4(nTermMax+1,4) += (PSN(1,kTM)^2 + PSN(1,kTM+nTM)^2)^0.5;
for jTerm=2:nTermMax+1
 I_CMAG4(nTermMax+jTerm,4) =   (PSP(jTerm,kTM)^2 + PSP(jTerm,kTM+nTM)^2)^0.5;
 I_CMAG4(nTermMax+2-jTerm,4) = (PSN(jTerm,kTM)^2 + PSN(jTerm,kTM+nTM)^2)^0.5;
end% animate
figure(3, 'position', [600 400 800 450]);
FRAME0 = nTM/2 * ones(nTerm, 1);
FRAME0(1) = 0;
FRAME0(9) *= 2;
FRAME0 = cumsum(FRAME0, 1);
iTerm = 0;
for kTerm=NTERM
 iTerm++;
 iFrame = FRAME0(iTerm);
 for iTM=1:nTM % occasionally skip every other frame for speed
  if ((kTerm!=8) && (kTerm!=24))
   if (mod(iTM,2)==0)
    continue;
   endif;
  endif;

  % setup plot figure
  clf;
  figure(3, 'position', [600 400 800 450]);
  daspect([0.5 0.4 4]);
  xlim([-2 2]);
  ylim([-1 2]);
  zlim([-1 1]);
  set(gca, "fontsize", 12);
  set(gca, 'xtick', -2:2:2);
  set(gca, 'ytick', -1:2);
  set(gca, 'ztick', -1:1);
  grid on;
  hold on;
  xlabel('trapezoid baseline');
  ylabel('Re {\bfS_n}');
  zlabel('Im {\bfS_n}');
  axis('equal');
  view([72.5 20]);
  strTitle1 = sprintf( ...
   "complex Fourier series drawing of 'e'");
  strTitle2 = sprintf( ...
   "(trapezoid scale=%.2f, t=%.4f, n=+/-%d)", ...
   trapScale, iTM/nTM, kTerm);
  title({strTitle1,strTitle2}); % plot 'e' (closed form) in x=-2 plane
  plot3(XZ-2, F(1:nT), F(nT+1:end), 'c-'); % plot weighted average trapezoid slopes (MWA)
  plot3(XMWA, YMWA, ZMWA, ':', ...
   'color', [1.0, 0.65, 0]); % plot zigzag aggregated vectors (x=-2 plane)
  XVEC = [-2 -2];
  YVEC = [0 PS(1,iTM)];
  ZVEC = [0 PS(1,iTM+nTM)];
  plot3(XVEC, YVEC, ZVEC, CT(1), ...
   'linewidth', 1);
  XVEC = [-2, -2, -2];
  for iPS=2:2:2*kTerm
   iColor = mod(floor(iPS/2),3) + 1;
   YVEC = [PSC(iPS-1,iTM), PSC(iPS,iTM), PSC(iPS+1,iTM)];
   ZVEC = [PSC(iPS-1,iTM+nTM), PSC(iPS,iTM+nTM), PSC(iPS+1,iTM+nTM)];
   plot3(XVEC, YVEC, ZVEC, CT(iColor), ...
    'linewidth', 1);
  endfor; % plot axes
  plot3(xlim(), [0 0], [0 0], '-', ...
   'color', 0.85+[0  0 0]);
  plot3([2 2], ylim(), [0 0], '-', ...
   'color', 0.85+[0  0 0]);
  plot3([2 2], [0 0], zlim(), '-', ...
   'color', 0.85+[0  0 0]); % plot trapezoids
  for iPS=1:kTerm+1
   iColor = mod(iPS-1,3) + 1;
   S = R .^ ((iPS-1)/2) .* (1 .- R); % plot trapezoids (mid planes)
   XTRAP = [ ...
    2*(1-R(1)^((iPS-1)/2));
    2*(1-R(1)^((iPS-1)/2));
    2*(1-R(1)^(iPS/2));
    2*(1-R(1)^(iPS/2));
    2*(1-R(1)^((iPS-1)/2));
    NaN;
    -2*(1-R(2)^((iPS-1)/2));
    -2*(1-R(2)^((iPS-1)/2));
    -2*(1-R(2)^(iPS/2));
    -2*(1-R(2)^(iPS/2));
    -2*(1-R(2)^((iPS-1)/2))];
   YTRAP = [ ...
    0;
    trapScale*PSP(iPS,iTM)/S(1);
    trapScale*PSP(iPS,iTM)/S(1) * R(1);
    0;
    0;
    NaN;
    0;
    trapScale*PSN(iPS,iTM)/S(2);
    trapScale*PSN(iPS,iTM)/S(2) * R(2);
    0;
    0];
   ZTRAP = [ ...
    0;
    trapScale*PSP(iPS,iTM+nTM)/S(1);
    trapScale*PSP(iPS,iTM+nTM)/S(1) * R(1);
    0;
    0;
    NaN;
    0;
    trapScale*PSN(iPS,iTM+nTM)/S(2);
    trapScale*PSN(iPS,iTM+nTM)/S(2) * R(2);
    0;
    0];
   plot3(XTRAP, YTRAP, ZTRAP, CT(iColor), ...
    'linewidth', 0.3);

   % fill test column 5 of I_CMAG4
   if ((kTerm == nTermMax) && (iTM == 1))
    B = 2 .* R.^(iPS-1) .* (1 .- R.^0.5);
    I = [2; 8];
    H = ((YTRAP(I).^2 + ZTRAP(I).^2).^0.5) ./ trapScale;
    if (iPS > 1)
     I = [nTermMax+iPS; nTermMax+2-iPS];
     I_CMAG4(I,5) = H .* R .^ ((iPS-1)/2) .* (1 .- R);
    else
     SDC = H .* B .* (1 .+ R.^0.5) ./ 2;
     I_CMAG4(nTermMax+1,5) = sum(SDC);
    end
   end
  endfor; % plot 'e' (closed form) in x=2 plane
  plot3(XZ+2, F(1:nT), F(nT+1:end), 'c-'); % plot dotted trail of partial sum
  if ((kTerm >= 1) && (kTerm <= 10))
   YVEC = PSTC(kTerm, 1:nTM);
   ZVEC = PSTC(kTerm, nTM+1:end);
   alpha = 0.8;
   plot3(XZ(1:nTM)-2, YVEC, ZVEC, ':', ...
    'color', [0 0 0]+alpha);
   plot3(XZ(1:nTM)+2, YVEC, ZVEC, ':', ...
    'color', [0 0 0]+alpha);
  endif;
  YVEC = PSTC(kTerm+1, 1:iTM);
  ZVEC = PSTC(kTerm+1, nTM+1:nTM+iTM);
  plot3(XZ(1:iTM)-2, YVEC, ZVEC, 'k:');
  plot3(XZ(1:iTM)+2, YVEC, ZVEC, 'k:'); % plot aggregated vectors (x=2 plane)
  XVEC = [2 2 NaN 2 2];
  for iPS=1:kTerm+1
   iColor = mod(iPS-1,3) + 1; YVEC = [ ...
    PSPC(iPS,iTM),
    PSPC(iPS,iTM)-PSP(iPS,iTM),
    NaN,
    PSNC(iPS,iTM),
    PSNC(iPS,iTM)-PSN(iPS,iTM)];
   ZVEC = [ ...
    PSPC(iPS,iTM+nTM),
    PSPC(iPS,iTM+nTM)-PSP(iPS,iTM+nTM),
    NaN,
    PSNC(iPS,iTM+nTM),
    PSNC(iPS,iTM+nTM)-PSN(iPS,iTM+nTM)];
   plot3(XVEC, YVEC, ZVEC, CT(iColor), ...
    'linewidth', 2);
  endfor; % plot '+' vector addition cross-hair
  iPS = kTerm + 1;
  YVEC = [ ...
   PSTC(iPS,iTM)-dCross/2,
   PSTC(iPS,iTM)+dCross/2,
   NaN,
   PSTC(iPS,iTM),
   PSTC(iPS,iTM)];
  ZVEC = [ ...
   PSTC(iPS,iTM+nTM),
   PSTC(iPS,iTM+nTM),
   NaN,
   PSTC(iPS,iTM+nTM)-dCross/2,
   PSTC(iPS,iTM+nTM)+dCross/2];
  plot3(XVEC-4, YVEC, ZVEC, 'k-', ...
   'linewidth', 1);
  plot3(XVEC, YVEC, ZVEC, 'k-', ...
   'linewidth', 1);
  plot3( ...
   XDOT+2, ...
   YDOT+PSTC(iPS,iTM)/2, ...
   ZDOT+PSTC(iPS,iTM+nTM)/2, ...
   'k-', ...
   'linewidth', 1); % print scene's frames
  hold off;
  fname = sprintf("./cfsanim3B/frame%05d.png", ...
   iFrame);
  print("-dpng", "-r400", "-Farial:10", fname);
  iFrame++;
 endfor; % iT=1:nT
endfor; % for kTerm=NTERM% plot _pceo.png (wher PCEO is Plot Coefficient Error in Octave)
figure(4);
X = I_CMAG4(:,1);
Y = diff(I_CMAG4(:,2:5), 1, 2); % diff of 1 in horizontal direction
plot(X, Y);
print("-dpng", "-r400", "-Farial:10", "_pceo.png");
I_CMAG4(nTermMax-4:nTermMax+6,:)% print 3 seconds of trailer still frames
for i=1:60
 fname = sprintf("./cfsanim3B/frame%05d.png", ...
  iFrame);
 print("-dpng", "-r400", "-Farial:10", fname);
 iFrame++;
endfor;% get final snapshot of side view
PV = view(); % Plot View
view([0 0]);
drawnow();
print("-dpng", "-r400", "-Farial:10", 'cfs3b3.png');
view(PV);% /tool/ffmpeg-4.3.1/bin/ffmpeg -f image2 -i frame%05d.png -vf scale=1080:-2,setsar=1:1 -f mp4 -q:v 0 -vcodec mpeg4 -r 20 _cfs3b.mp4
% /tool/ffmpeg-4.3.1/bin/ffmpeg -i _cfs3b.mp4 -c:v libvpx-vp9 -crf 30 -b:v 4M  cfs3b.webm
% see [https://trac.ffmpeg.org/wiki/Encode/VP9](https://trac.ffmpeg.org/wiki/Encode/VP9)**

附录 B. Julia 源代码

下面的 Julia 代码为描绘字母“e”的复傅立叶级数的动画生成帧。

**# file: cfse.jl (complex Fourier series tracing 'e')
# import Pkg; Pkg.add("Plots"); Pkg.add("PyPlot")
using Plots, Printf
pyplot()# cfse: Complex Fourier Series animation tracing the letter 'e'
# - media_mask bit 0: set to disable generation of frames as .png images
# - media_mask bit 1: set to disable generation of audio  as .wav file(s)
# - iProc: 1-based index of CPU core
# - nProc: number of CPU cores
# example: cfse(1) # skips image frame generation
function cfse(media_mask::Int64=0, iProc=1, nProc=1)

 # define some plotting parameters 
 nT = Int32(801)   # number of Time samples
 nTM = nT - 1   # number of Time sample Midpoints
 T = range(0, stop=1, length=nT) # Time samples
 nT2 = Int32(nTM/2)
 nT4 = Int32(nTM/4)
 dT = 1/nTM    # difference between Time samples
 TM = T[1:nTM] .+ dT/2 # Time sample Midpoints
 THETAM = 2*pi*TM  # angle samples
 XZ = zeros(nT,1)  # X Zeros
 CSEQ = [:red, :green, :blue, :yellow, :white, :black] # Color SEQuence

 trapScale = 0.5   # trapezoid scale
 dCross = 0.15   # diameter of '+' cross-hair
 yOffset = 0.5   # y offset of F(t)
 nAudioBuf = 3530
 nAng = 15
 ANGDOT = range(0, stop=2*pi, length=nAng)
 XDOT = zeros(nAng, 1)
 YDOT = dCross/pi*cos.(ANGDOT) 
 ZDOT = dCross/pi*sin.(ANGDOT)

 # parameterize function F for drawing letter 'e' 
 wChar = 0.1
 rInner = 0.5
 rOuter = rInner+wChar
 cy = 0.4
 thetaChar = 7*pi/4
 F = zeros(1, 2*nT)  # left half real, right half imaginary
 M = zeros(2,1)   # slope (Re and Im)

 P = zeros(1,7)   # Perimeter segments
 P[1] = cy + rInner
 P[2] = rInner * thetaChar
 P[3] = wChar
 P[4] = rOuter * thetaChar
 P[5] = wChar
 P[6] = cy + rOuter
 P[7] = wChar
 pSum = sum(P)
 TS = P/pSum     # Time Segments
 TSC = cumsum(TS, dims=2)  # Time Segments Cumulative

 V = zeros(2, 7)    # Vertices
 V[1,1] = cy + rInner
 V[2,1] = 0
 V[1,2] = -rInner + rInner * cos.(thetaChar)
 V[2,2] = rInner * sin.(thetaChar)
 V[1,3] = wChar * cos.(thetaChar)
 V[2,3] = wChar * sin.(thetaChar)
 V[1,4] = -rOuter * cos.(thetaChar) + rOuter 
 V[2,4] = -rOuter * sin.(thetaChar)
 V[1,5] = 0
 V[2,5] = -wChar
 V[1,6] = -cy - rOuter
 V[2,6] = 0
 V[1,7] = 0
 V[2,7] = wChar
 VC = cumsum(V, dims=2)   # Vertices Cumulative

 iSegment = 1    # segment 1
 i0 = Int32(1)
 tOffset = 0
 nTS = 1 + Int32(floor(TS[1]/dT))
 i1 = i0 + nTS - 1
 M = V[:,1] ./ TS[1]
 F[i0:i1] = M[1] * T[i0:i1]
 F[nT+i0:nT+i1] .= 0

 iSegment += 1     # segment 2
 i0 = i1 + 1
 tOffset = T[i0] - TSC[1]
 nTS = 1 + Int32(floor((TS[2] - tOffset)/dT))
 i1 = i0 + nTS - 1
 period = 8*TS[2]/7
 THETA = 2*pi/period*(T[i0:i1] .- TSC[1])
 F[i0:i1] = cy .+ rInner*cos.(THETA)
 F[nT+i0:nT+i1] = rInner*sin.(THETA)

 iSegment += 1     # segment 3
 i0 = i1 + 1
 tOffset = T[i0] - TSC[2]
 nTS = 1 + Int32(floor((TS[3] - tOffset)/dT))
 i1 = i0 + nTS - 1
 M = V[:,3] ./ TS[3]
 F[i0:i1] = VC[1, 2] .+ M[1] .* (T[i0:i1] .- TSC[2]) 
 F[nT+i0:nT+i1] = VC[2, 2] .+ M[2] * (T[i0:i1] .- TSC[2]) 

 iSegment += 1     # segment 4
 i0 = i1 + 1
 tOffset = T[i0] - TSC[3]
 nTS = 1 + Int32(floor((TS[4] - tOffset)/dT))
 i1 = i0 + nTS - 1
 period = 8*TS[4]/7
 THETA = thetaChar .- 2*pi/period*(T[i0:i1] .- TSC[3])
 F[i0:i1] = cy .+ rOuter*cos.(THETA)
 F[nT+i0:nT+i1] = rOuter*sin.(THETA)

 iSegment += 1     # segment 5
 i0 = i1 + 1
 tOffset = T[i0] - TSC[4]
 nTS = 1 + Int32(floor((TS[5] - tOffset)/dT))
 i1 = i0 + nTS - 1
 M = V[:,5] ./ TS[5]
 F[i0:i1] .= VC[1, 4] 
 F[nT+i0:nT+i1] = VC[2, 4] .+ M[2] * (T[i0:i1] .- TSC[4]) 

 iSegment += 1     # segment 6
 i0 = i1 + 1
 tOffset = T[i0] - TSC[5]
 nTS = 1 + Int32(floor((TS[6] - tOffset)/dT))
 i1 = i0 + nTS - 1
 M = V[:,6] ./ TS[6]
 F[i0:i1] = VC[1, 5] .+ M[1] * (T[i0:i1] .- TSC[5]) 
 F[nT+i0:nT+i1] .= VC[2, 5]

 iSegment += 1     # segment 7
 i0 = i1 + 1
 tOffset = T[i0] - TSC[6]
 nTS = 1 + Int32(floor((TS[7] - tOffset)/dT))
 i1 = i0 + nTS - 1
 M = V[:,7] ./ TS[7]
 F[i0:i1] .= VC[1, 6]
 F[nT+i0:nT+i1] = VC[2, 6] .+ M[2] * (T[i0:i1] .- TSC[6])

 # apply y offset shift then take midpoints
 F[1:nT] .+= yOffset
 FM = [(F[1:nT-1]+F[2:nT])/2 (F[nT+1:end-1]+F[nT+2:end])/2]

 # integrate complex coefficient
 NTERM = [1:10; 12:2:22]
 nTerm = size(NTERM,1)
 nTermMax = last(NTERM)
 FEM = zeros(1, 2*nTM);
 CR = zeros(5, 2*nTermMax+1)
 CI = zeros(5, 2*nTermMax+1)
 for iTerm=-nTermMax:nTermMax
  iA = iTerm + nTermMax + 1
  EM = [cos.(-iTerm*THETAM) sin.(-iTerm*THETAM)]
  FEM[1:nTM] = FM[1:nTM] .* EM[1:nTM] - 
   FM[nTM+1:end] .* EM[nTM+1:end]
  FEM[nTM+1:end] = FM[1:nTM] .* EM[nTM+1:end] + 
   FM[nTM+1:end] .* EM[1:nTM]
  V = reshape(FEM .* dT, (nTM, 2))
  VR = reshape(V[:,1], (nT4, 4))
  CR[1:4,iA] = sum(VR, dims=1)'
  VI = reshape(V[:,2], (nT4, 4))
  CI[1:4,iA] = sum(VI, dims=1)'
 end
 CR[5,:] = sum(CR[1:4,:], dims=1)
 CI[5,:] = sum(CI[1:4,:], dims=1)
 if (iProc == 1)
  fp = open("ecoeff.dat", "w")
  write(fp, CR[5,:])
  write(fp, CI[5,:])
  close(fp)
 end

 # calculate common ratio r that best fits coefficients
 A = zeros(2, 1) # (a ccw terms; a cw terms)
 R = zeros(2, 1) # [r ccw terms, r cw terms]
 I_CMAG4 = zeros(2*nTermMax+1, 5) # columns 3 thru 5 are for testing
 I_CMAG4[:,1] .= -nTermMax:nTermMax
 I_CMAG4[:,2] .= (CR[5,:].^2 + CI[5,:].^2).^0.5
 X = [ones(nTermMax,1) I_CMAG4[nTermMax+2:end,1]]
 Y = log.(I_CMAG4[nTermMax+2:end,2])
 PHI = (X'*X) \ (X'*Y) # is faster than PHI = inv(X'*X)*X'*Y
 EPHI = exp.(PHI)
 A[1] = EPHI[1]
 R[1] = EPHI[2]
 X = [ones(nTermMax,1) abs.(I_CMAG4[1:nTermMax,1])]
 Y = log.(I_CMAG4[1:nTermMax,2])
 PHI = (X'*X) \ (X'*Y) # is faster than PHI = inv(X'*X)*X'*Y
 EPHI = exp.(PHI)
 A[2] = EPHI[1]
 R[2] = EPHI[2]
 if (nProc == 1)
  display("R"); display(R)
 end

 # plot L.S.E. fit to natural log of coefficient magnitudes
 plot(I_CMAG4[nTermMax+2:end,1], 
  log.(A[1]*R[1].^I_CMAG4[nTermMax+2:end,1]), 
  color = :black);
 plot!(I_CMAG4[1:nTermMax,1], 
  log.(A[2]*R[2].^abs.(I_CMAG4[1:nTermMax,1])), 
  color = :black);
 ICOLOR = mod.(abs.(I_CMAG4[:,1]),3) .== 0;
 plot!(I_CMAG4[ICOLOR,1], 
  log.(I_CMAG4[ICOLOR,2]), 
  seriestype = :scatter,
  markersize = 8,
  color = :red);
 ICOLOR = mod.(abs.(I_CMAG4[:,1]),3) .== 1;
 plot!(I_CMAG4[ICOLOR,1], 
  log.(I_CMAG4[ICOLOR,2]), 
  seriestype = :scatter,
  markersize = 8,
  color = :green);
 ICOLOR = mod.(abs.(I_CMAG4[:,1]),3) .== 2;
 plot!(I_CMAG4[ICOLOR,1], 
  log.(I_CMAG4[ICOLOR,2]), 
  seriestype = :scatter,
  markersize = 8,
  color = :blue);
 strTitleCoefficient = [@sprintf](http://twitter.com/sprintf)(
  "%s\n+ index common ratio r = %.4f\n- index common ratio r = %.4f",
  "LSE fit to coefficient magnitude data",
  R[1], 
  R[2]);
 plot!(legend = false,
  xlabel = "coefficient index n", 
  ylabel = "ln(coefficient magnitude)", 
  title = strTitleCoefficient);
 savefig("_pc.png")

 # plot coefficient magnitudes as trapezoids
 SMB = zeros(2, 1) # Sum M*B; dims 1: +/- coefficients
 SB = zeros(2, 1) # Sum B; dims 1: +/- coefficients
 TH = zeros(2, nTermMax+1) # Trapezoid Height; dims 1: +/- coefficients; dims2: trapezoid height (1-based coefficient index)
 X = zeros(2, 2) # dims 1: +/- coefficients; dims 2: x of tall/short height
 M = zeros(2, 1) # trapezoid slope; dims 1: +/- coefficients
 B = zeros(2, 1) # trapezoid base width; dims 1: +/- coefficients

 X[:,2] = 2 .* (1 .- R .^ 0.5)
 TH[:,1] = [I_CMAG4[nTermMax+1,2], I_CMAG4[nTermMax+1,2]] ./ 2 # split DC component into +/- coefficients
 TH[:,1] = TH[:,1] ./ (1 .- R) # trapezoid height: dims 1: +/- coefficients
 XTRAP = [
  X[1,1], X[1,1], X[1,2], X[1,2], X[1,1],
  NaN,
  -X[2,1], -X[2,1], -X[2,2], -X[2,2], -X[2,1]]
 YTRAP = [
  0, TH[1,1], TH[1,1]*R[1]^0.5, 0, 0,
  NaN,
  0, TH[2,1], TH[2,1]*R[2]^0.5, 0, 0]
 plot(XTRAP, YTRAP, 
  color = CSEQ[1], # coefficient 0
  legend = false,
  xlims = (-2,2),
  xticks = -2:2:2)

 # SB += B        # intentionally skip inclusion of DC component
 # SMB += M .* B  # in calculation of weighted average slope
 I_CMAG4[nTermMax+1,3] = sum(TH[:,1] .* (1 .- R), dims=1)[1]

 for jTerm=1:nTermMax
  iColor::Int16 = mod(jTerm,3) + 1;

  X[:,1] = X[:,2]
  X[:,2] = 2 .* (1 .- R .^ ((jTerm+1)/2))
  B = abs.(diff(X, dims=2))
  TH[:,jTerm+1] = [
   I_CMAG4[nTermMax+1+jTerm,2];
   I_CMAG4[nTermMax+1-jTerm,2]] ./
   (R .^ (jTerm/2) .* (1 .- R))
  M = TH[:,jTerm+1] .* (R .- 1) ./ B
  XTRAP = [
   X[1,1], X[1,1], X[1,2], X[1,2], X[1,1],
   NaN,
   -X[2,1], -X[2,1], -X[2,2], -X[2,2], -X[2,1]]
  YTRAP = [
   0, TH[1,jTerm+1], TH[1,jTerm+1]*R[1]^0.5, 0, 0,
   NaN,
   0, TH[2,jTerm+1], TH[2,jTerm+1]*R[2]^0.5, 0, 0]
  plot!(XTRAP, YTRAP, 
   color = CSEQ[iColor]) # coefficient +/- jTerm
  SB += B
  SMB += M .* B

  I_CMAG4[nTermMax+1+jTerm,3] = TH[1,jTerm+1] * R[1]^(jTerm/2) * (1 - R[1])
  I_CMAG4[nTermMax+1-jTerm,3] = TH[2,jTerm+1] * R[2]^(jTerm/2) * (1 - R[2])
 end

 MWA = SMB ./ SB # M (i.e., slope) Weighted Average
 plot!(
  [0, 2, NaN, 0, -2],
  [-MWA[1]*2, 0, NaN, -MWA[2]*2, 0],
  linestyle = :dot,
  color = :orange)
 savefig("_pt.png")
 if (nProc == 1)
  display("MWA"); display(MWA)
 end

 # calculate partial series terms
 PS = zeros(2*nTM, 2*nTermMax+1) # Partial Sum
 iTerm0 = nTermMax + 1
 PS[1:nTM,1] .= CR[5,iTerm0]
 PS[nTM+1:end,1] .= CI[5,iTerm0]
 for jTerm=1:nTermMax
  iPS = 2 * jTerm
  iTerm = iTerm0 + jTerm
  EM = [cos.(jTerm*THETAM) sin.(jTerm*THETAM)] # nTM x 2
  PS[1:nTM,iPS] = 
   CR[5,iTerm] .* EM[:,1] - 
   CI[5,iTerm] .* EM[:,2]
  PS[nTM+1:end,iPS] = 
   CI[5,iTerm] .* EM[:,1] + 
   CR[5,iTerm] .* EM[:,2]iPS = 2 * jTerm + 1
  iTerm = iTerm0 - jTerm
  EM = [cos.(jTerm*THETAM) sin.(-jTerm*THETAM)] # nTM x 2
  PS[1:nTM,iPS] = 
   CR[5,iTerm] .* EM[:,1] - 
   CI[5,iTerm] .* EM[:,2]
  PS[nTM+1:end,iPS] = 
   CI[5,iTerm] .* EM[:,1] + 
   CR[5,iTerm] .* EM[:,2]
 end
 PSC = cumsum(PS, dims=2);

 # split terms according to direction of rotation
 PSP = zeros(2*nTM, nTermMax+1) # Partial Sum Positive rotation
 PSP[:,1] = PS[:,1] ./ 2
 PSP[:,2:end] = PS[:,2:2:2*nTermMax+1]
 PSPC = cumsum(PSP, dims=2) # Partial Sum Positive rotation Cumulative
 PSN = zeros(2*nTM, nTermMax+1) # Partial Sum Negative rotation
 PSN[:,1] = PS[:,1] ./ 2
 PSN[:,2:end] = PS[:,3:2:2*nTermMax+1]
 PSNC = cumsum(PSN, dims=2) # Partial Sum Negative rotation Cumulative
 PST = PSP + PSN # Partial Sum Total
 PSTC = cumsum(PST, dims=2) # Partial Sum Total Cumulative

 # test PSP and PSN in column 4 of I_CMAG4
 kTM = 100  # test should work for any value of kTM where 1<kTM<=nTM
 I_CMAG4[nTermMax+1,4] =  (PSP[kTM,1]^2 + PSP[kTM+nTM,1]^2)^0.5
 I_CMAG4[nTermMax+1,4] += (PSN[kTM,1]^2 + PSN[kTM+nTM,1]^2)^0.5
 for jTerm=2:nTermMax+1
  I_CMAG4[nTermMax+jTerm,4] =   (PSP[kTM,jTerm]^2 + PSP[kTM+nTM,jTerm]^2)^0.5
  I_CMAG4[nTermMax+2-jTerm,4] = (PSN[kTM,jTerm]^2 + PSN[kTM+nTM,jTerm]^2)^0.5
 end

 # calculate frame offsets where new terms are added to partial series
 FRAMEOFFSET = nTM/2 * ones(Int32, nTerm+1, 1)
 FRAMEOFFSET[1] = 0
 FRAMEOFFSET[nTerm+1] *= 2 # n=24 is slow motion
 FRAMEOFFSET = cumsum(FRAMEOFFSET, dims=1)

 # generate audio tracks
 # [https://isip.piconepress.com/projects/speech/software/tutorials/production/fundamentals/v1.0/section_02/s02_01_p05.html](https://isip.piconepress.com/projects/speech/software/tutorials/production/fundamentals/v1.0/section_02/s02_01_p05.html)
 if ((media_mask&0x2==0) && (iProc==1))
  io = open("./ez2armst/_ez2armst.wav", "w")

  # write 44 byte .WAV header
  write(io, "RIFF")    # RIFF tag (4 bytes)
  uival::UInt32 = 0  # (to be completed after writing of audio data)
  write(io, uival) +  # tag block size (4 bytes)
  write(io, "WAVE") +  # WAVE tag (4 bytes)
  write(io, "fmt ")  # format description header (4 bytes)
  uival = 16
  write(io, uival)  # tag block size (4 bytes)
  usval::UInt16 = 1
  write(io, usval)  # specify PCM encoding (2 bytes)
  usval = 2
  write(io, usval)  # specify stereo sampling (2 bytes)
  uival = 44100
  write(io, uival)  # specify sampling rate (4 bytes)
  uival *= 4
  write(io, uival)  # specify bytes/second (4 bytes)
  usval = 4
  write(io, usval)  # specify  block alignment (2 bytes)
  usval = 16
  write(io, usval)  # specify bits per sample (2 bytes)
  write(io, "data")   # data description header (4 bytes)
  uival = 0    # (to be completed after writing of audio data)
  write(io, uival)  # specify # bytes of audio data (4 bytes)

  # write audio track data (subtracting off DC offset in PSC[1,:]
  AUDIO = zeros(Int16, nT4, 2)
  AUDIO2 = zeros(Int16, nT4, 2)
  maxPSC = maximum(abs.(PSC - repeat(PSC[:,1], outer=(1,2*nTermMax+1))))
  if (nProc == 1)
   display(maxPSC)
  end
  I = 1:4:nTM
  J = 1:2:nT2
  for iTerm = 1:nTerm
   kTerm = NTERM[iTerm]
   iPS = 2*kTerm + 1 # +1 to skip DC component in PS
   if (iTerm != nTerm)
    AUDIO[:,1] = Int16.(round.((PSC[I,iPS]-PSC[I,1]) .* 
     (0x7fff*0.8/maxPSC)))
    AUDIO[:,2] = Int16.(round.((PSC[I.+nTM,iPS]-PSC[I.+nTM,1]) .* 
     (0x7fff*0.8/maxPSC)))
    for iBuf=1:nAudioBuf
     write(io, AUDIO'[:])
    end
   else
    AUDIO[:,1] = Int16.(round.((PSC[J,iPS]-PSC[J,1]) .* 
     (0x7fff*0.8/maxPSC)))
    AUDIO[:,2] = Int16.(round.((PSC[J.+nTM,iPS]-PSC[J.+nTM,1]) .* 
     (0x7fff*0.8/maxPSC)))
    AUDIO2[:,1] = Int16.(round.((PSC[J.+nT2,iPS]-PSC[J.+nT2,1]) .* 
     (0x7fff*0.8/maxPSC)))
    AUDIO2[:,2] = Int16.(round.((PSC[J.+(nTM+nT2),iPS]-PSC[J.+(nTM+nT2),1]) .* 
     (0x7fff*0.8/maxPSC)))
    for iBuf=1:nAudioBuf
     write(io, AUDIO'[:])
     write(io, AUDIO2'[:])
    end
   end
  end # for iTerm=1:nTerm

  # complete .WAV file header and close file
  fsize::UInt32 = position(io)
  seek(io, 4)
  write(io, fsize - UInt32(8))
  seek(io, 40)
  write(io, fsize - UInt32(44))
  close(io)
 end

 # animate
 if (media_mask&0x1 == 0)
  if (nProc == 1)
   print("generating 3D animation frames ") # start progress report line
  end
  iTerm1 = 1 + div((iProc-1)*nTerm, nProc, RoundNearest)
  iTerm2 = div(iProc*nTerm, nProc, RoundNearest)
  if (nProc > 1)
   str = [@sprintf](http://twitter.com/sprintf)("partition %d:%d ", iTerm1, iTerm2)
   print(str)
  end
  for iTerm = iTerm1:iTerm2 # partitioned loop
#  for iTerm = 1:nTerm   # original unpartitioned loop
   print(".") # progress report: one '.' for each processed term in NTERM
   kTerm = NTERM[iTerm]
   iFrame::Int32 = FRAMEOFFSET[iTerm]
   for iTM = 1:nTM

    # typically skip every other frame for speed
    if (iTerm != nTerm)
     if (mod(iTM,2)==0)
      continue;
     end
    end

    # restore background plot but with new title
    X = 2*ones(nT, 1);
    plot(X,F[1:nT],F[nT+1:2*nT], 
     color = :cyan)
    plot!( # M (i.e., slope) Weighted Average
     [0, 2, NaN, 0, -2],
     [-trapScale*MWA[1]*2, 0, NaN, -trapScale*MWA[2]*2, 0],
     [0, 0, NaN, 0, 0],
     color = :orange,
     linestyle = :dot,
     linewidth = 0.5);
    strTitle = [@sprintf](http://twitter.com/sprintf)("%s\n(%s=%.2f, %s=%4d,\nt=%.4f, n=+/-%d)",
     "complex Fourier series tracing letter 'e'",
     "trapezoid height scale",
     trapScale,
     "sound frequency scale",
     nAudioBuf,
     iTM/nTM,
     kTerm)
    plot!(background=:white,
     camera = (73, 20),
     xlabel = "trapezoid baseline     ", 
     ylabel = "Re Sn", 
     zlabel = "\n\nIm Sn",
     title = strTitle);
    plot!(legend = false,
     xlims = (-2,2),
     ylims = (-0.99,1.99),
     zlims = (-1,1),
     xticks = 0:2:2,
     yticks = -1:1:2,
     zticks = -1:1:1,
     figsize = (800,450))
    plot!([-2, 2], [0, 0], [0, 0], 
     color = RGB(0.82,0.82,0.82))
    plot!(-X,F[1:nT],F[nT+1:2*nT], color="cyan")

    # plot zigzag aggregated vectors (x=-2 plane)
    XVEC = [-2, -2]
    YVEC = [0, PS[iTM,1]]
    ZVEC = [0, PS[iTM+nTM,1]]
    plot!(XVEC, YVEC, ZVEC, 
     color = CSEQ[1], 
     linewidth = 1);
    XVEC = [-2, -2, -2]
    for iPS=2:2:2*kTerm
     iColor::Int16 = mod(floor(iPS/2),3) + 1;
     YVEC = [PSC[iTM,iPS-1], PSC[iTM,iPS], PSC[iTM,iPS+1]]
     ZVEC = [PSC[iTM+nTM,iPS-1], PSC[iTM+nTM,iPS], PSC[iTM+nTM,iPS+1]]
     plot!(XVEC, YVEC, ZVEC, 
      color = CSEQ[iColor], 
      linewidth = 1);
    end

    # plot axes
    plot!(xlims(), [0, 0], [0, 0], 
     color = :gray85);
    plot!([2, 2], [0, ylims()[2]], [0, 0], 
     color = :gray85);

    # plot trapezoids
    for iPS=1:kTerm+1
     iColor::Int16 = mod(iPS-1,3) + 1;
     S = R .^ ((iPS-1)/2) .* (1 .- R)

     # plot trapezoids (mid planes)
     XTRAP = [
      2*(1-R[1]^((iPS-1)/2)),
      2*(1-R[1]^((iPS-1)/2)),
      2*(1-R[1]^(iPS/2)),
      2*(1-R[1]^(iPS/2)),
      2*(1-R[1]^((iPS-1)/2)),
      NaN,
      -2*(1-R[2]^((iPS-1)/2)),
      -2*(1-R[2]^((iPS-1)/2)),
      -2*(1-R[2]^(iPS/2)),
      -2*(1-R[2]^(iPS/2)),
      -2*(1-R[2]^((iPS-1)/2))]
     YTRAP = [
      0,
      trapScale*PSP[iTM,iPS]/S[1],
      trapScale*PSP[iTM,iPS]/S[1] * R[1]^0.5,
      0,
      0,
      NaN,
      0,
      trapScale*PSN[iTM,iPS]/S[2],
      trapScale*PSN[iTM,iPS]/S[2] * R[2]^0.5,
      0,
      0]
     ZTRAP = [
      0,
      trapScale*PSP[iTM+nTM,iPS]/S[1],
      trapScale*PSP[iTM+nTM,iPS]/S[1] * R[1]^0.5,
      0,
      0,
      NaN,
      0,
      trapScale*PSN[iTM+nTM,iPS]/S[2],
      trapScale*PSN[iTM+nTM,iPS]/S[2] * R[2]^0.5,
      0,
      0]
     plot!(XTRAP, YTRAP, ZTRAP, 
      color = CSEQ[iColor],
      linewidth = 0.3);

     ## fill test column 5 of I_CMAG4 ##
     # left edge of trapezoid is
     # x(i) = 2*(1 - r^(i/2))
     #
     # width of trapezoid base is
     # b(i) = x(i+1) - x(i)
     #      = 2*r^(i/2)*(1 - r^(1/2))
     #
     # given lefth height (h(i)), right height of trapezoid is
     # h(i+1) = h(i)*r^(1/2)
     #
     # trapezoid area is
     # |C| = h(i)*b(i) - 1/2(h(i) - h(i+1))*b(i)
     #     = h(i)*r^(i/2)*(1 - r)
     #
     # where h is trapezoid height, r is common ratio,
     # and i is the power of the term represented by
     # the trapezoid area
     if ((kTerm == nTermMax) && (iTM == 1))
      I = [2; 8]
      H = ((YTRAP[I].^2 + ZTRAP[I].^2).^0.5) ./ trapScale
      if (iPS > 1)
       I = [nTermMax+iPS; nTermMax+2-iPS]
       I_CMAG4[I,5] = H .* R .^ ((iPS-1)/2) .* (1 .- R)
      else
       ADC = H .* (1 .- R) # Area of DC trapezoids
       I_CMAG4[nTermMax+1,5] = sum(ADC, dims=1)[1]
      end
     end
    end

    # plot dotted trail of partial sum
    if ((kTerm >= 1) && (kTerm <= 10))
     YVEC = PSTC[1:nTM,kTerm]
     ZVEC = PSTC[nTM+1:end,kTerm]
     plot!(XZ[1:nTM] .- 2, YVEC, ZVEC, 
      linestyle = :dot,
      color = :gray80)
     plot!(XZ[1:nTM] .+ 2, YVEC, ZVEC, 
      linestyle = :dot,
      color = :gray80)
    end
    YVEC = PSTC[1:iTM,kTerm+1]
    ZVEC = PSTC[nTM+1:nTM+iTM,kTerm+1]
    plot!(XZ[1:iTM] .- 2, YVEC, ZVEC, 
     linestyle = :dot,
     color = :black)
    plot!(XZ[1:iTM] .+ 2, YVEC, ZVEC, 
     linestyle = :dot,
     color = :black)

    # plot 2-arm aggregated vectors (x=2 plane)
    XVEC = [2, 2, NaN, 2, 2]
    for iPS=1:kTerm+1
     iColor::Int16 = mod(iPS-1,3) + 1

     YVEC = [PSPC[iTM,iPS],
      PSPC[iTM,iPS]-PSP[iTM,iPS],
      NaN,
      PSNC[iTM,iPS],
      PSNC[iTM,iPS]-PSN[iTM,iPS]]
     ZVEC = [PSPC[iTM+nTM,iPS],
      PSPC[iTM+nTM,iPS]-PSP[iTM+nTM,iPS],
      NaN,
      PSNC[iTM+nTM,iPS],
      PSNC[iTM+nTM,iPS]-PSN[iTM+nTM,iPS]]
     plot!(XVEC, YVEC, ZVEC, 
      color = CSEQ[iColor], 
      linewidth = 2)
    end

    # plot '+' vector addition cross-hair
    iPS = kTerm + 1
    YVEC = [
     PSTC[iTM,iPS]-dCross/2,
     PSTC[iTM,iPS]+dCross/2,
     NaN,
     PSTC[iTM,iPS],
     PSTC[iTM,iPS]]
    ZVEC = [
     PSTC[iTM+nTM,iPS],
     PSTC[iTM+nTM,iPS],
     NaN,
     PSTC[iTM+nTM,iPS]-dCross/2,
     PSTC[iTM+nTM,iPS]+dCross/2];
    plot!(XVEC .- 4, YVEC, ZVEC, 
     color = :black,
     linewidth = 1);
    plot!(XVEC, YVEC, ZVEC, 
     color = :black,
     linewidth = 1);
    plot!(XDOT .+ 2, 
     YDOT .+ PSTC[iTM,iPS]/2,
     ZDOT .+ PSTC[iTM+nTM,iPS]/2, 
     color = :black, 
     linewidth = 1);

    # print scene's frames
    iFrame += 1
    fname = [@sprintf](http://twitter.com/sprintf)("./ez2armst/frame%05d.png", iFrame);
    savefig(fname)
    # print("-dpng", "-r400", "-Farial:10", fname);
   end # iT=1:nT
  end # for kTerm=NTERM
  print("\n") # end progress report line
 end # if (media_mask&0x1 == 0)

 # plot _pce.png (where PCE is Plot Coefficient Error) to show 
 #    consistency of calculated coefficient magnitude |C| values in I_CMAG4
 if (nProc == 1)
  print("plotting (_pce.png) the numerical (e.g., trig approximation) errors in coefficient calculations ...\n")
  X = I_CMAG4[:,1]
  Y = diff(I_CMAG4[:,2:5], dims=2)
  plot(X, Y)
  savefig("_pce.png")
 # display("I_CMAG4[nTermMax+1-5:nTermMax+1+5,:]"); display(I_CMAG4[nTermMax+1-5:nTermMax+1+5,:])
 end

 # /tool/ffmpeg-4.3.1/bin/ffmpeg -f image2 -i frame%05d.png -i _ez2armst.mp3 -vf scale=1080:-2,setsar=1:1 -f mp4 -q:v 0 -vcodec mpeg4 -r 20 _ez2armst.mp4
 # /tool/ffmpeg-4.3.1/bin/ffmpeg -f image2 -i frame%05d.png -vf scale=1080:-2,setsar=1:1 -f mp4 -q:v 0 -vcodec mpeg4 -r 20 _ez2armst.mp4
 # /tool/ffmpeg-4.3.1/bin/ffmpeg -i _ez2armst.mp4 -c:v libvpx-vp9 -crf 30 -b:v 4M  _ez2armst.webm
 # see [https://trac.ffmpeg.org/wiki/Encode/VP9](https://trac.ffmpeg.org/wiki/Encode/VP9)
end**

附录 C. Julia 源代码

下面的 Julia 代码生成复杂几何级数的动画帧。

**# import Pkg; Pkg.add("Plots"); Pkg.add("PyPlot")
using Plots, Printf
pyplot()# cgs: Complex Geometric Series 
# isTrapezoid: 0/1 flag for rotating triangles/trapezoids
# a: coefficient
# r: common ratio
# n: power of last term in partial series
# nAudioBuf: audio buffer count per animated fundamental period
# example: cgs() 
function cgs(isTrapezoid::Int64=0,
 a::Float64=1.0, 
 r::Float64=0.5, 
 n::Int64=10, 
 nAudioBuf::Int64=3530)

 # define some plotting parameters 
 nT = Int32(400)   # # of non-repeated Time samples
 nT1 = nT + 1   # # of Time samples
 T = range(0, stop=1, length=nT1) # Time samples
 nT2 = Int32(nT/2)
 THETA = 2*pi*T   # angle samples of plot's 'o' symbol
 NTERM = 1:n
 nTerm = size(NTERM,1)
 nTermMax = last(NTERM)
 maxSum = a/(1-abs(r)) # for setting plot limits
 maxRadius = a*abs(r)/(1-r^2)
 dCross = 0.15   # diameter of '+' symbol
 trapScale = 0.5   # trapezoid scale
 CSEQ = [:red, :green, :blue, :yellow, :white, :black] # Color SEQuence

 # calculate partial series terms
 PS = zeros(2*nT, nTermMax+1) # Partial Sum
 PS[1:nT,1] .= a
 for jTerm=1:nTermMax
  EM = [ sin.(jTerm*THETA)] # nT x 2
  PS[1:nT,jTerm+1] = (a * r^jTerm) .* 
   cos.(jTerm*THETA[1:end-1])
  PS[nT+1:end,jTerm+1] = (a * r^jTerm) .*
   sin.(jTerm*THETA[1:end-1])
 end
 PSC = cumsum(PS, dims=2);

 # calculate frame offsets where new terms are added to partial series
 FRAMEOFFSET = nT * ones(Int32, nTerm+1, 1)
 FRAMEOFFSET[1] = 0
 FRAMEOFFSET = cumsum(FRAMEOFFSET, dims=1)

 # generate audio tracks
 io = open("./cgs/cgs.wav", "w")

 # write 44 byte .WAV header
 write(io, "RIFF")    # RIFF tag (4 bytes)
 uival::UInt32 = 0  # (to be completed after writing of audio data)
 write(io, uival) +  # tag block size (4 bytes)
 write(io, "WAVE") +  # WAVE tag (4 bytes)
 write(io, "fmt ")  # format description header (4 bytes)
 uival = 16
 write(io, uival)  # tag block size (4 bytes)
 usval::UInt16 = 1
 write(io, usval)  # specify PCM encoding (2 bytes)
 usval = 2
 write(io, usval)  # specify stereo sampling (2 bytes)
 uival = 44100
 write(io, uival)  # specify sampling rate (4 bytes)
 uival *= 4
 write(io, uival)  # specify bytes/second (4 bytes)
 usval = 4
 write(io, usval)  # specify  block alignment (2 bytes)
 usval = 16
 write(io, usval)  # specify bits per sample (2 bytes)
 write(io, "data")   # data description header (4 bytes)
 uival = 0    # (to be completed after writing of audio data)
 write(io, uival)  # specify # bytes of audio data (4 bytes)

 # write audio track data (subtracting off DC offset in PSC[1,:]
 AUDIO = zeros(Int16, nT2, 2)
 I = 1:2:nT
 for iTerm = 1:nTerm
  kTerm = NTERM[iTerm]
  if (mod(iTerm,2) == 0)  # if power of last term is even
   minmaxmid = a*(1 - (abs(r))^(kTerm+2)) / (1 - (abs(r))^2)
  else      # else power of last term is odd
   minmaxmid = a*(1 - (abs(r))^(kTerm+1)) / (1 - (abs(r))^2)
  end
  AUDIO[:,1] = Int16.(round.((PSC[I,kTerm+1] .- minmaxmid) .* 
   (0x7fff*0.8/maxRadius)))
  AUDIO[:,2] = Int16.(round.(PSC[I.+nT,kTerm+1] .* 
   (0x7fff*0.8/maxRadius)))
  for iBuf=1:nAudioBuf
   write(io, AUDIO'[:])
  end
 end # for iTerm=1:nTerm

 # complete .WAV file header and close file
 fsize::UInt32 = position(io)
 seek(io, 4)
 write(io, fsize - UInt32(8))
 seek(io, 40)
 write(io, fsize - UInt32(44))
 close(io)

 # define circle traced by complex geometric series
 ycenter = a / (1 - r^2)
 zcenter = 0
 TH = range(0, stop=2*pi, length=nT1)
 XCIRC = 2*ones(nT1, 1)
 YCIRC = ycenter .+ maxRadius .* cos.(TH)
 ZCIRC = zcenter .+ maxRadius .* sin.(TH)

 # animate
 print("generating 3D animation frames ") # start progress report line
 PO = Vector{Plots.Plot{Plots.PyPlotBackend}}(undef,nTerm) # Plot Objects
 for iTerm = 1:nTerm
  print(".") # progress report: one '.' for each processed term in NTERM
  kTerm = NTERM[iTerm]
  iFrame::Int32 = FRAMEOFFSET[iTerm]
  for iT = 1:nT

   # restore background plot but with new title
   if (isTrapezoid > 0)
    strTitle = [@sprintf](http://twitter.com/sprintf)(
     "%s\n(%s=%.2f, %s=%.2fexp(iθ),\n%s=%.2f, %s=%d,\nt=%.4f, n=%d)",
     "complex geometric series",
     "coefficient a", a,
     "common ratio r", r,
     "trapezoid scale", trapScale,
     "sound frequency scale", nAudioBuf,
     (iT-1)/nT, 
     kTerm)
   else
    strTitle = [@sprintf](http://twitter.com/sprintf)(
     "%s\n(%s=%.2f, %s=%.2fexp(iθ),\n%s=%d,\nt=%.4f, n=%d)",
     "complex geometric series",
     "coefficient a", a,
     "common ratio r", r,
     "sound frequency scale", nAudioBuf,
     (iT-1)/nT, 
     kTerm)
   end
   PO[iTerm] = plot(background=:white,
    camera = (73, 20),
    xlabel = "x", 
    ylabel = "y", 
    zlabel = "z",
    title = strTitle)
   plot!(legend = false,
    xlims = (0,2),
    ylims = (-0.5,2.0),
    zlims = (-0.85,0.85),
    xticks = 0:2:2,
    yticks = 0:1:2,
    zticks = -1:1:1,
    figsize = (800,450))

   # plot axes
   plot!([2, 2], [0, ylims()[2]], [0, 0], 
    color = :gray85);

   # plot trapezoids or triangles
   if (isTrapezoid > 0)
    # plot trapezoids
    for iPS=1:kTerm+1
     s = r ^ ((iPS-1)/2) * (1 - r)
     iColor::Int16 = mod(iPS-1,3) + 1;
     XTRAP = [
      2*r^((iPS-1)/2),
      2*r^((iPS-1)/2),
      2*r^(iPS/2),
      2*r^(iPS/2),
      2*r^((iPS-1)/2)]
     YTRAP = [
      0,
      trapScale*PS[iT,iPS]/s,
      trapScale*PS[iT,iPS]/s * r^0.5,
      0,
      0]
     ZTRAP = [
      0,
      trapScale*PS[iT+nT,iPS]/s,
      trapScale*PS[iT+nT,iPS]/s * r^0.5,
      0,
      0]
     plot!(XTRAP, YTRAP, ZTRAP, 
      color = CSEQ[iColor],
      linewidth = 0.3);
    end
   else
    # plot triangles
    for iPS=1:kTerm+1
     iColor::Int16 = mod(iPS-1,3) + 1;
     XTRI = [
      0,
      2*r^((iPS-1)/2),
      2*r^((iPS-1)/2),
      0]
     YTRI = [
      0,
      0,
      2*PS[iT,iPS]/XTRI[3],
      0]
     ZTRI = [
      0,
      0,
      2*PS[iT+nT,iPS]/XTRI[3],
      0]
     plot!(XTRI, YTRI, ZTRI, 
      color = CSEQ[iColor],
      linewidth = 0.3);
    end
   end
   plot!(XCIRC, YCIRC, ZCIRC, color="cyan")

   # plot dotted trail of partial sum
   if ((kTerm >= 1) && (kTerm <= 10))
    YVEC = PSC[1:nT,kTerm]
    ZVEC = PSC[nT+1:end,kTerm]
    plot!(XCIRC[1:nT], YVEC, ZVEC, 
     linestyle = :dot,
     color = :gray80)
   end
   YVEC = PSC[1:iT,kTerm+1]
   ZVEC = PSC[nT+1:nT+iT,kTerm+1]
   plot!(XCIRC[1:iT], YVEC, ZVEC, 
    linestyle = :dot,
    color = :black)

   # plot 1-arm aggregated vectors (x=2 plane)
   XVEC = [2, 2]
   for iPS=1:kTerm+1
    iColor::Int16 = mod(iPS-1,3) + 1
    YVEC = [
     PSC[iT,iPS],
     PSC[iT,iPS]-PS[iT,iPS]]
    ZVEC = [
     PSC[iT+nT,iPS],
     PSC[iT+nT,iPS]-PS[iT+nT,iPS]]
    plot!(XVEC, YVEC, ZVEC, 
     color = CSEQ[iColor], 
     linewidth = 2)
   end

   # print scene's frames
   iFrame += 1
   fname = [@sprintf](http://twitter.com/sprintf)("./cgs/frame%05d.png", iFrame);
   savefig(fname)
  end # iT=1:nT
 end # for kTerm=NTERM
 print("\n") # end progress report line

 # /tool/ffmpeg-4.3.1/bin/ffmpeg -f image2 -i frame%05d.png -i cgs.mp3 -vf scale=1080:-2,setsar=1:1 -f mp4 -q:v 0 -vcodec mpeg4 -r 20 cgs.mp4
 # /tool/ffmpeg-4.3.1/bin/ffmpeg -f image2 -i frame%05d.png -vf scale=1080:-2,setsar=1:1 -f mp4 -q:v 0 -vcodec mpeg4 -r 20 cgs.mp4
 # /tool/ffmpeg-4.3.1/bin/ffmpeg -i cgs.mp4 -c:v libvpx-vp9 -crf 30 -b:v 4M  cgs.webm
 # see [https://trac.ffmpeg.org/wiki/Encode/VP9](https://trac.ffmpeg.org/wiki/Encode/VP9)
end**

附录 D. Julia 源代码

下面的 Julia 代码将复杂的傅立叶级数动画帧生成分发到六个 CPU 内核,并在 11.1 分钟内完成,比 GNU Octave 生成动画帧所需的 120 分钟快 10 倍。

**# file: cfsed.jl (complex Fourier series tracing 'e' distributed)
# import Pkg.add("Distributed")
using Distributedaddprocs(4, exeflags="--project=.")
[@everywhere](http://twitter.com/everywhere) begin
 include("cfse.jl")
endnProc = 8
pmap(1:nProc) do i
 cfse(0, i, nProc)
end**

附录 E. Julia 源代码

下面是 Julia 代码,它生成各种图形,包括几何级数自相似动画和一个几何级数图形,作为数学知识主体的参考系的原点。

**# file: some1.jl (miscellaneous code for Summer Of Math Exposition #1)
# import Pkg; Pkg.add("Plots"); Pkg.add("PyPlot")
using Measures, Printf, LaTeXStrings, Plots
pyplot(dpi=600)function tstplt6()
 a = 1:3
 b = [1, 2, 7]
 plot(a, b, 
  label = "randData", 
  xlabel = "numbers", 
  ylabel = "Rand data",
  color = :red, 
  legend = :topleft,
  tick_direction = :in,
  grid = :on,
  left_margin=2cm, right_margin=8cm, top_margin=2cm)
 p = twinx()
 plot!(p, a, log.(a), 
  label = "log(x)", 
  ylabel = "The right Y label",
  color = :green,
  legend = :topright,
  tick_direction = :out,
#  grid = :on,
  left_margin=2cm, right_margin=8cm, top_margin=2cm,
  box = :on)
 savefig("tstplt6.png")
endfunction tstplt7()
 a = 1:3
 b = [1, 2, 7]
 plot(a, b, 
  label = "randData", 
  xlabel = "numbers", 
  ylabel = "Rand data",
  color = :red, 
  legend = :topleft,
  grid = :on, left_margin=2cm, right_margin=8cm, top_margin=2cm)
 plot!(twinx(), a, log.(a), 
  label = "log(x)", 
  ylabel = "The right Y label",
  color = :green,
  legend = :bottomright,
        grid = :on, left_margin=2cm, right_margin=8cm, top_margin=2cm)
 savefig("tstplt7.png")
end# bok: Body Of Knowledge
# iPoint: index specifying last point to plot
# example: bok() 
function bok(iPoint::Int64=13)

 # initialize
 n = 12
 xmax = div(n^2,10,RoundUp) * 10
 ymax = div(4*n+2,10,RoundUp) * 10
 plot(seriestype = :scatter,
  xaxis = :log10,
  yaxis = :log10,  
  xlims = (0.4, 180),
  ylims = (0.8, 120),
  xticks = ([0.5, 1, 2, 10, 100, n^2],
   [latexstring(L"-\infty"),
   latexstring(L"10^0"),
   "2",
   latexstring(L"10^1"),
   latexstring(L"10^2"),
   latexstring(L"n^2")]),
  xlabel = latexstring("common ratio ", L"r", "\ndegrees of freedom"), 
  ylabel = latexstring("coefficient ", L"a", "\ndegrees of freedom"),
  grid = :on,
  left_margin=2cm, right_margin=8cm, top_margin=2cm) 
 plot!(twinx(),
  seriestype = :scatter,
  xaxis = :log10,
  yaxis = :log10,  
  xlims = (0.4, 180),
  ylims = (0.8, 120),
  yticks = ([1, 6, 8, 10, n+1, 4*n+2, 100], 
   ["", 
   "6", 
   "8", 
   "", 
   latexstring(L"n+1"), 
   latexstring(L"4n+2"), 
   ""]),
  grid = :on,
  left_margin=2cm, right_margin=8cm, top_margin=2cm)# geometric series
 A = [1]
 R = [1]
 plot!(R, A,
  marker = :circle,
  markersize = 16,
  color = :white,
  markerstrokecolor = :red,
  label = "geometric series")

 # geometric series closed form
 A = [1]
 R = [1]
 plot!(R, A,
  xaxis = :log10,
  yaxis = :log10,  
  marker = :utriangle,
  markersize = 10,
  color = :red,
  linecolor = :white,
  label = "geometric series closed form")
 if (iPoint <= 2)
  [@goto](http://twitter.com/goto) printplot
 end

 # complex geometric series
 A = [1]
 R = [2]
 plot!(R, A,
  marker = :circle,
  markersize = 16,
  color = :white,
  markerstrokecolor = :green,
  label = "complex geometric series")

 # complex geometric series closed form
 A = [1]
 R = [2]
 plot!(R, A,
  marker = :utriangle,
  markersize = 10,
  color = :green, 
  linecolor = :white,
  label = "complex geometric series closed form")
 if (iPoint <= 4)
  [@goto](http://twitter.com/goto) printplot
 end

 # Laurent series
 A = [4*n+2]
 R = [2]
 plot!(R, A,
  marker = :circle,
  markersize = 16,
  color = :white,
  markerstrokecolor = :red,
  label = "Laurent series")

 # complex Fourier series
 A = [4*n+2]
 R = [2]
 plot!(R, A,
  marker = :pentagon,
  markersize = 10,
  color = :red,
  linecolor = :white,
  label = "complex Fourier series")
 if (iPoint <= 6)
  [@goto](http://twitter.com/goto) printplot
 end

 # power series
 A = [n+1]
 R = [1]
 plot!(R, A,
  marker = :circle,
  markersize = 16,
  color = :white,
  markerstrokecolor = :blue,
  label = "power series")

 # Taylor series
 A = [n+1]
 R = [1]
 plot!(R, A,
  marker = :square,
  markersize = 10,
  color = :blue,
  linecolor = :white,
  label = "Taylor series")
 if (iPoint <= 8)
  [@goto](http://twitter.com/goto) printplot
 end

 # binary encoded numbers
 A = [n+1]
 R = [0.5]
 plot!(R, A,
  marker = :circle,
  markersize = 12,
  color = :white,
  markerstrokecolor = :black,
  markerstrokewidth = 3,
  label = "binary encoded numbers")
 annotate!(0.65, 0.8, # impose text white background
  text("█", 
   :white,
   :center))
 annotate!(0.65, 0.8, # denote axis split
  text("/ /",
   :black,
   :center))
 if (iPoint <= 9)
  [@goto](http://twitter.com/goto) printplot
 end# Bezier curves
 A = [6]
 R = [1]
 plot!(R, A,
  marker = :utriangle,
  markersize = 12,
  color = :white,
  markerstrokecolor = :black,
  markerstrokewidth = 3,
  label = "quadratic Bezier curves")
 A = [8]
 R = [1]
 plot!(R, A,
  marker = :square,
  markersize = 12,
  color = :white,
  markerstrokecolor = :black,
  markerstrokewidth = 3,
  label = "cubic Bezier curves")
 if (iPoint <= 11)
  [@goto](http://twitter.com/goto) printplot
 end

 # matrix polynomials
 A = [1]
 R = [n^2]
 plot!(R, A,
  marker = :circle,
  markersize = 16,
  color = :white,
  markerstrokecolor = :green,
  label = "matrix polynomials")

 # matrix exponentials
 A = [1]
 R = [n^2]
 plot!(R, A,
  marker = :hexagon,
  markersize = 10,
  color = :green,
  linecolor = :white,
  label = "matrix exponentials")

[@label](http://twitter.com/label) printplot
 if (iPoint < 13)
  fname = [@sprintf](http://twitter.com/sprintf)("bok%02d.png", iPoint)
 else
  fname = "bok.png"
 end
 savefig(fname)
endrectangle(w, h, x, y) = Shape(x .+ [0,w,w,0], y .+ [0,0,h,h])function fp32()
 # initialize
 nBit = 32
 x0 = 1
 x1 = 15
 y0 = 3
 y1 = 4
 plot(legend = false,
  showaxis = false,
  size = (800, 300),
  ticks = false,
  xlims = (0, 16),
  ylims = (0, 6),
  title = "binary32 IEEE 754 standard\n" *
   "single-precision floating point number format")

 # color bit fields
 plot!(rectangle(23*(x1-x0)/nBit,1, x1-23*(x1-x0)/nBit,y0), 
  opacity = 0.3,
  color = :red)
 plot!(rectangle(8*(x1-x0)/nBit,1, x0+(x1-x0)/nBit,y0), 
  opacity = 0.3,
  color = :green)
 plot!(rectangle((x1-x0)/nBit,1, x0,y0), 
  opacity = 0.3,
  color = :blue)

 # draw bit boundaries
 plot!([x0; x1], [y0; y0],
  color = :black)
 plot!([x0; x1], [y1; y1],
  color = :black)
 for iBit=0:nBit
  x = x1 - iBit*(x1-x0)/nBit
  plot!([x; x], [y0; y1], color = :black)
  if (iBit < nBit)
   annotate!(x-(x1-x0)/(2*nBit), y1+0.5, 
    text(string(mod(iBit,10)), :black, :hcenter))
  end
  if (mod(iBit,10) == 0)
   annotate!(x-(x1-x0)/(2*nBit), y1+1.2, 
    text(string(div(iBit,10)), :black, :hcenter))
  end 
 end

 # write labels
 annotate!(x0, y1+0.8, text("bit #:", :black, :right))
 annotate!(x1-23/2*(x1-x0)/nBit, y0-0.5, text("fraction (23 bits)", :black, :hcenter))
 annotate!(x0+(1+8/2)*(x1-x0)/nBit, y0-0.5, text("exponent (8 bits)", :black, :hcenter))
 annotate!(x0+(x1-x0)/(2*nBit), y0-0.7, text("sign", :center, rotation = 90))

 fname = "fp32.png"
 savefig(fname)
endtrapezoid(x0, y0, x1, y1) = Shape([x0,x0,x1,x1], [0,y0,y1,0])function selfsim()
 print("selfsim")

 # initialize
 CSEQ = [:red, :green, :blue] # Color SEQuence
 nTrap::Int32 = 25
 nFrame::Int32 = 80
 xmaxmax = 2.0
 xmaxmin = 1.0
 dgrid = 0.5
 r = (1/2)^(2/3)

 # for each animation frame
 for iFrame = 0:nFrame-1

  # display progress
  if (mod(iFrame+1,10) == 0)
   print(".")
  end

  # start plot of new frame
  xmax = xmaxmax - (xmaxmax-xmaxmin)*iFrame/nFrame
  # print("\n", xmax, ">")
  plot(legend = false,
   box = true,
   ticks = false,
   aspect_ratio = :equal,
   xlims = (0, xmax),
   ylims = (0, 1.5*xmax))

  # prepare first (smallest) trapezoid
  x0 = 2*r^(nTrap/2)
  y0 = r^(nTrap/2)/(1-r)
  x1 = 2*r^((nTrap-1)/2)
  y1 = r^((nTrap-1)/2)/(1-r)

  # for each trapezoid
  for iTrap = nTrap-1:-1:0
   iColor::Int16 = mod(iTrap,3) + 1;

   # color trapezoid area
   plot!(trapezoid(x0,y0, x1,y1),  
    opacity = 0.3,
    color = CSEQ[iColor])

   # prepare next trapezoid
   x0 = x1
   y0 = y1
   x1 = 2*r^((iTrap-1)/2)
   y1 = r^((iTrap-1)/2)/(1-r)
  end
  fname = [@sprintf](http://twitter.com/sprintf)("./selfsim/frame%05d.png", iFrame);
  savefig(fname)
 end
 print("\n")

 # /tool/ffmpeg-4.3.1/bin/ffmpeg -i _selfsim.mp4 -r 12 _selfsim.gif
 # /tool/ffmpeg-4.3.1/bin/ffmpeg -f image2 -i frame%05d.png -vf scale=1080:-2,setsar=1:1 -f mp4 -q:v 0 -vcodec mpeg4 -r 20 _selfsim.mp4
 # see [https://trac.ffmpeg.org/wiki/Encode/VP9](https://trac.ffmpeg.org/wiki/Encode/VP9)
end# tsa: Taylor Series Approximation 
# nDegree: degree of last term in partial series
# example: tsa() 
function tsa(nDegree::Int64=19)

 # define coefficients (1-based index)
 C = [
  0, 1/factorial(1),
  0, -1/factorial(3),
  0, 1/factorial(5),
  0, -1/factorial(7),
  0, 1/factorial(9),
  0, -1/factorial(11),
  0, 1/factorial(13),
  0, -1/factorial(15),
  0, 1/factorial(17),
  0, -1/factorial(19)]
 if (nDegree >= length(C))
  [@printf](http://twitter.com/printf)("error: nDegree cannot be more than %d\n",
   length(C)-1)
  return
 end

 # define sin function
 nPoint = 201 
 X = LinRange(-1.0, 1.0, nPoint)
 F = sin.(X)

 # define Taylor series approximation
 TSF = zeros(nPoint, nDegree+1)
 for iDegree = 0:nDegree
  TSF[:,iDegree+1] = C[iDegree+1] .* X .^ iDegree
 end
 TSFC = cumsum(TSF, dims=2)

 # show Taylor series approximation error
 p1 = plot(X, F,
  legend = false)
 p2 = plot(X, TSFC[:,end] - F,
  legend = false)
 M = maximum(abs.(TSFC-repeat(F,1,nDegree+1)), dims=1)
 display(M)
 p3 = plot(M',
  legend = false,
  yaxis = :log)
 plot(p1, p2, p3, layout=(3,1))
end**

零投和少投学习

原文:https://towardsdatascience.com/zero-and-few-shot-learning-c08e145dc4ed?source=collection_archive---------7-----------------------

使用 FlairNLP 和 Huggingface 的低资源印尼语示例!

照片由 Abdullah Ahmad 在 Unsplash 上拍摄

介绍

NLP 领域每天都变得越来越令人兴奋。直到几年前,我们还不能充分利用网上的大量数据来源。随着无监督学习方法和迁移学习的惊人成功,NLP 社区已经建立了作为多个 NLP 任务的知识库的模型。

然而,我们仍然依赖带注释的数据对下游任务进行微调。通常情况下,获取带标签的数据并不容易,而且是一项相对昂贵且耗时的工作。如果我们没有任何标记的数据或者数据非常少,我们能做什么?这个问题的答案是零炮和少炮学习。

零炮和少炮方法没有单一的定义。相反,可以说它的定义是任务相关的。

零镜头分类是指我们在一些类上训练一个模型,并对一个新的类进行预测,这个模型以前从未见过。显然,类名需要存在于类的列表中,但是这个类没有训练样本。

在更广泛的意义上,零镜头设置指的是使用一个模型去做一些它没有被训练去做的事情。

让我们假设我们在一个大的文本语料库上训练一个语言模型(或者使用一个像 GPT-2 这样预先训练好的语言模型)。我们的任务是预测一篇给定的文章是关于体育、娱乐还是科技。通常情况下,我们会将此作为一个带有许多标记示例的微调任务,并在语言模型之上添加一个用于分类的线性层。但是对于 zero shot,我们直接使用语言模型(没有任何显式的微调)。我们可以给出文章以及解释性标签名称,并得到一个预测。

现在听起来还很困惑吗?不要烦恼!并深入这些概念背后的直觉,让事情变得更清晰。此外,最后的例子将确保对其可用性有更深的理解。

零投和少投学习背后的直觉

作为人类,我们储存了大量的信息,我们从每一个资源中学习,无论是书籍,新闻,课程,还是仅仅是经验。

如果我们被要求做以下任务:
“从英语翻译成法语”:你好吗?- >?

从任务的描述中,我们很清楚这里要做什么。我们已经使用我们的知识库来推断翻译的意思。

另一个任务可以如下:
“我爱这部电影!”- >快乐还是悲伤

阅读自我解释的任务解释(快乐或悲伤),我们明白这是一个分类任务。我们的知识库也帮助我们理解句子并推断它是快乐的!

这正是零射击分类的工作原理。我们有一个预先训练好的模型(如语言模型)作为知识库,因为它已经在许多网站的大量文本上进行了训练。对于任何类型的任务,我们给出相关的类描述符,并让模型推断任务实际上是什么。

不用说,我们提供的标签数据越多,结果就越好。而且很多时候零拍都不太管用。如果我们有一些标签数据的样本,但不足以进行微调,那么可以选择少量拍摄。正如在 GPT-3 中所使用的,“语言模型很少使用学习者”,作者证明了与较小的模型相比,非常大的语言模型可以用更少的标记数据在下游任务中有竞争力地执行。

少数镜头只是零镜头的扩展,但用几个例子来进一步训练模型。

拍打和拥抱脸来救援!

FlairNLP 和 Huggingface 都有针对英语的零镜头分类管道(因为它们使用 bert 作为模型)。尽管 flairNLP 使用 bert-base-uncased for english 作为它的基本模型,但它对于简单的印度尼西亚文本却出奇地好用。在下面的例子中,我将带你在印度尼西亚文本上使用 flairNLP 中的 TARS 模型完成零镜头和少镜头学习的步骤。

huggingface 实现的零镜头分类管道有一些很优秀的文章和演示。看看这个优秀的博客和这个现场演示关于拥抱脸的零镜头分类。

由于我使用的是印尼语,所以我不会使用 huggingface 零射击管道,而是使用 GPT-2 模型(由 cahya 在印尼语上培训,并在 huggingface 上可用)并实现零射击分类。

例子

FlairNLP

使用 zero shot 分类器( TARS )模型来分类句子是关于体育(olahraga)还是政治(politik)

from flair.models.text_classification_model import TARSClassifier
from flair.data import Sentence# 1\. Load our pre-trained TARS model for English
tars = TARSClassifier.load('tars-base')# 2\. Prepare a test sentence
sentence1 = Sentence("Inggris mengalahkan Australia dengan 4 gawang")
sentence2 = Sentence("Para menteri kabinet memainkan permainan yang tidak menyenangkan")# 3\. Define some classes that you want to predict using descriptive names
classes = ["olahraga","politik"]#4\. Predict for these classes
tars.predict_zero_shot(sentence1, classes, multi_label=False)
tars.predict_zero_shot(sentence1, classes, multi_label=False)# Print sentence with predicted labels
print(sentence1)
print(sentence2)

输出

Sentence: "Inggris mengalahkan Australia dengan 4 gawang"   [− Tokens: 6  − Sentence-Labels: {'label': [olahraga (0.6423)]}]
Sentence: "Para menteri kabinet memainkan permainan yang tidak menyenangkan"   [− Tokens: 8  − Sentence-Labels: {'label': [olahraga (0.0951)]}]

英语模式在印度尼西亚语上出奇地好用!虽然它错误地预测了第二个例子,但是“内阁大臣们正在玩一场肮脏的游戏”这句话已经被归类为体育而不是政治。大概是因为这个词打混淆了型号吧!让我们用 4 个例子(每个班 2 个)让它学习,并使用少数镜头分类对同一个例子进行重新评估。

from flair.data import Corpus
from flair.datasets import SentenceDataset# training dataset consisting of four sentences (2 labeled as "olahraga" (sports) and 2 labeled as "politik" (politics)
train = SentenceDataset(
    [
        Sentence('Pemilu akan dimulai bulan depan').add_label('olahraga_atau_politik', 'politik'),
        Sentence('Perdana menteri mengumumkan dana bantuan banjir besar-besaran').add_label('olahraga_atau_politik', 'politik'),
        Sentence('Olimpiade akan diadakan di Tokyo').add_label('olahraga_atau_politik', 'olahraga'),
        Sentence('Asian Games 2018 merupakan ajang multi sport yang diadakan di Jakarta').add_label('olahraga_atau_politik', 'olahraga')
    ])# test dataset consisting of two sentences (1 labeled as "olahraga" and 1 labeled as "politik")
test = SentenceDataset(
    [
        Sentence('Para menteri kabinet memainkan permainan yang tidak menyenangkan').add_label('olahraga_atau_politik', 'politik'),
        Sentence('Ayo pergi dan bermain basket').add_label('olahraga_atau_politik', 'olahraga')
    ])# make a corpus with train and test split
corpus = Corpus(train=train, test=test)

根据新数据训练模型

from flair.trainers import ModelTrainer# 1\. load base TARS
tars = TARSClassifier.load('tars-base')# 2\. make the model aware of the desired set of labels from the new corpus
tars.add_and_switch_to_new_task("OLAHRAGA_POLITIK", label_dictionary=corpus.make_label_dictionary())# 3\. initialize the text classifier trainer with your corpus
trainer = ModelTrainer(tars, corpus)# 4\. train model
trainer.train(base_path='resources/taggers/olahraga_politik', # path to store the model artifacts
              learning_rate=0.02, 
              mini_batch_size=1, 
              max_epochs=10, 
              )# 5\. Load few-shot TARS model
tars = TARSClassifier.load('resources/taggers/olahraga_politik/final-model.pt')# 6\. Prepare a test sentence
sentence = Sentence("Para menteri kabinet memainkan permainan yang tidak menyenangkan")# 7\. Predict for olahraga and politik
tars.predict(sentence)
print(sentence)

输出

Sentence: "Para menteri kabinet memainkan permainan yang tidak menyenangkan"   [− Tokens: 8  − Sentence-Labels: {'label': [politik (0.9968)]}]

使用在印度尼西亚语上训练的 GPT-2 进行零射击分类

from torch.nn import functional as F
from transformers import GPT2Tokenizer, GPT2Model# 1\. Load pertained GPT-2 model and tokenizer
model_name='cahya/gpt2-small-indonesian-522M'
tokenizer = GPT2Tokenizer.from_pretrained(model_name)
model = GPT2Model.from_pretrained(model_name)# 2\. Prepare a test sentence and labels
sentence = 'Para menteri kabinet memainkan permainan yang tidak menyenangkan'
labels = ['olahraga', 'politik']# 3\. Since there is no padding token in this tokenizer, add a token. # A separate pad token can be added using the add_special_token
# function
tokenizer.pad_token = tokenizer.eos_token# 4\. Concatenate sentence with lables
inputs = tokenizer([sentence]+labels, return_tensors='pt',padding='longest')input_ids = inputs['input_ids']
attention_mask = inputs['attention_mask']
output = model(input_ids, attention_mask=attention_mask)[0]
sentence_rep = output[:1].mean(dim=1)
label_reps = output[1:].mean(dim=1)# now find the labels with the highest cosine similarities to
# the sentence
similarities = F.cosine_similarity(sentence_rep, label_reps)
closest = similarities.argsort(descending=True)
for ind in closest:
    print(f'label: {labels[ind]} \t similarity: {similarities[ind]}')

输出

label: politik 	 similarity: 0.5492470860481262
label: olahraga 	 similarity: 0.48411038517951965

结论

零炮和少炮学习方法正在减少对注释数据的依赖。GPT-2 和 GPT-3 模型已经显示出显著的结果来证明这一点。然而,对于像印度尼西亚语这样的低资源语言,它仍然是一个活跃的研究领域。然而,感谢像 cahya 这样的宝贵资源,我们有了专门为印尼人训练的 GPT-2 模型。然而,由于印度尼西亚语的数据并不庞大,因此少击比零击学习更受欢迎。尽管如此,我们只用几个例子就能取得的成就绝对是了不起的,flairNLP 和 huggingface 这样的库是如何让实现前沿模型变得如此容易的!

干杯!

Eram

零膨胀回归

原文:https://towardsdatascience.com/zero-inflated-regression-c7dfc656d8af?source=collection_archive---------11-----------------------

实践教程

教你的回归器如何输出零

由迈克尔·泽兹奇在 Unsplash 上拍摄的照片

零膨胀数据

在处理回归问题时,通常会有在某个范围内连续均匀分布的目标值。让我来说明我的意思。考虑以下数据集二维数据集:

图片由作者提供。

在这种情况下,目标值都分散在大约-300 到 400 的范围内。应用线性回归,我们最终得到以下模型:

图片由作者提供。

没有新意,没有花哨。

然而,有些数据集中有非常多的零目标。这就是我们所说的零膨胀数据集。

图片由作者提供。

这种行为的例子是以稀有事件计数为目标的数据集。这包括以下金额

  • 制造中的缺陷,
  • 特定日期和地点的龙卷风或其他自然灾害,或
  • 一些街区的犯罪。

常见的模式是,通常不会发生任何不好的事情,即目标为零。但偶尔,例如当一台制造机器出现几个小时的故障时,这个数字会飙升。在这种情况下,模型实际上必须预测不同于零的值。

零膨胀数据的问题是

许多模型可能会被这种行为分散注意力,其中包括线性回归、支持向量机以及神经网络。这些模型中的一个输出零的机会非常小。

例如,以一个简单的线性回归为例,其中一个特征如上所述。该算法的输出是一条线,该线恰好一次取值为零。这个问题在更高维度中也依然存在。或者回想一下,神经网络可以被表达为一个复杂的嵌套公式——如果你插入随机数,你很可能不会得到零输出。

但是,让我们用一个玩具数据集来形象化这个问题,而不是谈论假设的场景。

图片由作者提供。

该数据集由两列组成:

  • 一个人的特征年龄
  • 该人员的目标工资 来源于工作

在这个虚构的数据集中,我们可以看到儿童和青少年没有收入。从某个年龄开始,赚钱就开始了。人们获得了更多的经验,因此薪水会随着年龄的增长而增加。最后,人们退休了,不再挣工资了。人生的故事。

显而易见,简单的线性回归在这里并不适用。无论如何让我们做它。😎

图片由作者提供。

不太适合。一条线不能捕捉数据中的所有变化,但是请注意,如果我们将一条线拟合到大约 20 岁到 70 岁之间的数据,那么在这个区间内的拟合将会很好。

图片由作者提供。

但这仅仅解决了问题的一部分。我们想要一个通用的模型,它也能够为年轻人和老年人做出预测。除了基于树的方法,让我们看看一个简单而有效的方法来解决这个问题。

零膨胀回归量

我们现在将定义一个估计器来解决与零膨胀数据集相关的问题。之后,我们在工资数据集上使用它的一个已经实现的版本。

定义

这个想法很简单。考虑以下两步方法:

  1. 训练一个分类器 C 告诉我们回归输出是否为零。
  2. 在具有非零目标的数据部分上训练一个回归量 R

如果数据点进入模型,分类器首先检查输出是否应该为零。如果是,输出零。否则,输出该数据点的回归结果。

图片由作者提供。

这就是我们所说的元模型,即由其他模型组成的模型。最棒的是你可以插入任何你喜欢的分类器和回归器。

使用模型

让我们回到我们的工资数据集。

图片由作者提供。

我们看到分类器必须捕获年龄轴两端的零目标。简单的逻辑回归无法做到这一点(想想为什么!),但是具有 RBF 核的 SVM 分类器可能就足够了。

问题是我们现在必须实现这个元回归器。虽然我相信您可以自己完成这项工作,但我想向您介绍一个非常棒的,它已经可以实现这种方法。

注意:我是这个实现的创建者,因此对它有偏见。

这是你们每个人都应该知道的神话般的“sci kit-Lego”项目的一部分。这个库包含许多有用的 scikit-learn 兼容扩展,比如估计器、转换器和更多的构建模块(因此得名)。让我引用一下该项目的 Github 页面:

“它是由 Matthijs Brouns 和 Vincent D. Warmerdam 发起的,作为一个教人们如何为开源做贡献的工具。”

向文森特和马特希斯大喊!所以,把它放在心上,看看这个项目,想想你是否也能贡献点什么。我已经做了。

现在让我们最终编码。

*import pandas as pd
import numpy as np
from sklearn.svm import SVC
from sklearn.linear_model import LinearRegression
from sklego.meta import ZeroInflatedRegressor

# Create the salary dataset.
np.random.seed(0)
dataset = pd.DataFrame({'Age': np.random.randint(1, 100, size=200)})
dataset['Salary'] = np.where((dataset.Age > 17) & (dataset.Age < 70), 1500*dataset.Age + 5000 + 10000*np.random.randn(200), 0).round()

# Instantiate the zero-inflated regressor.
zir = ZeroInflatedRegressor(
    classifier=SVC(),
    regressor=LinearRegression()
)

# Easy fit!
zir.fit(dataset[['Age']], dataset.Salary)*

我们现在可以画出结果:

图片由作者提供。

这正是我们想要看到的行为!

唯一的问题是,我们必须扣除一些分数,因为 SVM 在 70 岁时没有实现零产出。这反过来为线性回归创建了一个异常值,然后将线向下倾斜了一点。幸运的是,我们还可以在一些超参数优化中使用ZeroInflatedReressor

*from sklearn.model_selection import GridSearchCV

grid = GridSearchCV(
    estimator=zir,
    param_grid={
        'classifier__C': range(1, 10)
    }
)

grid.fit(dataset[['Age']], dataset.Salary)*

你可以通过grid.best_params_得到最佳的 SVM 参数 C,在我们的例子中是 4。结果是:

图片由作者提供。

结论

我们研究了以大量零为目标的回归数据集——零膨胀数据。这可能会扰乱许多回归,如支持向量机以及神经网络。

为了解决这个问题,我们引入了零膨胀回归器,这是一个由基本分类器和基本回归器组成的元模型。分类器检查基础回归器是否必须工作,或者预测值是否应该为零。否则,基本回归器将接管并输出一个数字。当然,这个数字也可以是零,但通常情况下,它不是。

从某种意义上说,分类器使回归者的生活变得更容易,因为它使回归者的头脑摆脱了混乱。

最后,你可能会问:“你提到了基于树的模型。他们怎么了?他们也应该能够处理零膨胀数据集。”是的,他们可以!让我们在同一个数据集上训练一个深度为 4 的决策树。**

图片由作者提供。

这看起来也很好,但它有着我们在树木中常见的台阶形状。这并不奇怪,因为决策树基本上只是分段常数函数。随机森林和梯度提升算法也是如此,因为它们是树的和。

拟合很好,但不如我们的好——在我们的情况下,配备支持向量机的简单线性回归效果更好。像往常一样,为手头的数据集尝试不同的模型。现在,您有了另一个可以尝试的工具。

玩得开心!

我希望你今天学到了新的、有趣的、有用的东西。感谢阅读!

作为最后一点,如果你

  1. 想支持我多写点机器学习和
  2. 无论如何都要计划获得中等订阅量,

为什么不做 通过这个环节 ?这将对我帮助很大!😊

说白了,给你的价格不变,但大约一半的订阅费直接归我。

非常感谢,如果你考虑支持我的话!

有问题就在LinkedIn上写我!

使用连体网络的零射击意图分类

原文:https://towardsdatascience.com/zero-shot-intent-classification-with-siamese-networks-35900471c7fd?source=collection_archive---------8-----------------------

通过零触发意图分类有效定位域外意图

意图识别是面向目标的对话系统的基本任务。意图识别(有时也称为意图检测)是用标签对每个用户话语进行分类的任务,该标签来自预定义的一组标签。

分类器对标记数据进行训练,并学习区分哪个话语属于哪个类别。如果分类器得到一个看起来不像任何训练发音的发音,有时结果会很尴尬。这就是为什么我们也把域外话语归类,它们根本不属于那个域。

分类话语的意图分类器。这里,示例域是机票预订和话语“嘿,兄弟,你是机器人吗?”是一个域外的话语。作者图片

问题是用户和我们开发者生活在不同的世界里。开发人员希望呆在语义丛林中的安全农场(所谓的域),但是用户并不十分了解分类器或聊天机器人 NLU 是如何工作的(他们也不需要了解)。你不能期望用户停留在正确的语义领域,相反,你应该给你的聊天机器人提供处理食物话语的技能。

在口语的野性中,你安全的语义农场。你永远不知道什么来自野外!作者图片

在这篇文章中,我们将讨论如何通过为我们的司机语音助手 Chris 检测哪些话语属于该领域,哪些话语不属于该领域,来保持我们的领域完好无损。首先,我将介绍克里斯域和话语,然后我们将通过暹罗网络学习文本分类。

Chris 域和数据

克里斯是司机的语音助手。我们的克里斯是司机助理,由德国 Autolabs 打造。克里斯可以发送/阅读短信、WhatsApp 信息、给手机联系人打电话、播放音乐、导航、回复天气查询和闲聊。

我们的克里斯,在左边。右边是克里斯和他的能力。图片来自德国 Autolabs 网站。

Chris 是一个面向任务的对话代理,因此用户的话语通常简明扼要。这些是典型的用户话语:

play music
some music please
stop the music
send a message
send a message to Tess
send a whatsapp
read my messages
do I have any new messages
display the latest message
start the navigation
show navigation
show the map
stop the navigation
nearest gas station
find a parking spot
read it
no cancel
cancel
yes
next
no next
tell me the time
how is the weather today

意图名由一个子域名和一个动作名组成。以下是一些与上述说法相对应的意图名称:

music.play
music.pause
messages.write
messages.read
navigation.start
navigation.stop
universals.accept
universals.reject
universals.next
misc.time
misc.weather

域外话语呢?这里也有一些例子:

hey siri
launch siri
wake up siri
open my instagram
share my video on instagram
delete my facebook account
play my audibooks
set an alarm
show alarms
please delete all alarms
open the doors

要求一些你的聊天机器人根本不具备的能力,完全是正常用户的行为。例如,克里斯根本不具备打开车门的技能。即使 UX 团队在每个包装中都包含了关于克里斯技能的指南,用户也可能会跳过阅读😄这就是为什么你的聊天机器人 NLU 应该随时准备处理大量的域外话语,用户要求他们可以想象你的聊天机器人可以实现的能力,而不是用户手册中写的那些。

乍一看,简短的话语可能看起来“容易”,但也有一定的挑战。语音识别错误可能会造成困难,尤其是在简短的话语中,因为话语中可能会丢失语义上至关重要的单词(如“播放音乐”中的“播放”)。此外,语音引擎必须在正确的时间开始监听,否则它可能会错过单个单词的简短话语,如“是”、“否”,这些话语对于解析上下文至关重要。在 WER 方面,只缺少 3 个字母看起来不像是一个很大的 ASR 错误,但是如果您的代理多次要求用户批准,这可能会给用户带来挫败感。所有语音机器人都有声学和语义方面的挑战,永远不要低估语音工作的挑战。

什么是零拍文本分类?

零短文本分类的任务是在一组类别标签上训练分类器,并用分类器在训练集中看不到的类别标签测试分类器。NLP 最近的工作集中在更广泛的背景下的零射击学习,零射击学习 NLP 现在意味着训练一个模型去做它没有明确训练过的任务。GPT-3 是一个零射击学习者,吸引了相当多的注意力。

在零镜头分类中,我们用一些视觉线索或类名向分类器描述一个看不见的类。对于零镜头文本分类,通常使用意图名来描述意图的语义。当我第一次开始做克里斯 NLU,数据是为了“通常”的意图分类。然后我开始尝试 ood,发现我们的类命名方案domain . action(music . play,navigation.start 等)的确非常适合零镜头学习

将标签和话语嵌入同一个空间

零镜头文本分类中的一种常见方法是将意图名称和话语嵌入到同一空间中。这样,零拍算法可以通过语义组学习意图名称和话语之间的语义关系,就像我们人类一样。这根本不是一个新的想法,研究人员使用词向量来表示固定维度中的文本和意图名称(例如在 Veeranna 中)。2016 )。随着变形金刚的发明,密集表示经历了一场革命,现在我们有了更多高质量的句子和单词嵌入。

在我们的研究中,我们首先使用平均池单词向量来表示话语,然后使用 BERT 来编码我们的话语以生成话语向量。首先,让我们看看当我们使用单词向量时,意图名称是如何与话语结合在一起的。我们使用 100 维手套向量。我们通过平均单词的单词向量来为话语生成句子嵌入。为了获得标签的嵌入(例如,music.play),我们对域(音乐)和动作(播放)的嵌入进行了平均。在为所有话语生成嵌入之后,我们用 t-SNE 将我们的数据集转换成二维(用于可视化目的)。以下散点图显示了所有数据集话语和意图名称,具有相同意图的话语用相同的颜色着色:

克里斯说,每种颜色都代表一种意图

这就是话语和意图名称的对齐方式:

将意图名称和话语嵌入到同一空间中

如果我们放大一点,我们会看到意图名称和相应的话语确实排列得很好:

放大上面的图像

数据集的话语创建了一个相当漂亮的散点图,没有太多的离群值,相同意图的话语与意图名称组合在一起。

如果我们想用 BERT 来嵌入话语和意图名称呢?这对表达很有效,但是意图名不是真正的句子和简短的表达。BERT 是为完整的句子而训练的,对于像我们的意图名这样的短表达可能不太管用。在这种情况下,我们可以通过 BERT 为话语创建 768 维嵌入,通过词向量为意图名称创建 100 维嵌入。为了将它们嵌入到同一空间中,我们需要计算一个投影矩阵ϕ,将 768 维话语向量投影到 100 维意图向量上。因为我们有带注释的数据,我们可以通过回归学习投影矩阵。(尽管包含正则化以防止过度拟合很重要)。然后,我们有一个类似于上面的视觉对齐。

用于零镜头文本分类的连体网络

上面的探索性数据分析向我们展示了意图名称和话语之间的语义相似性是非常明显和可学习的。然后,我们可以让一个连体神经网络学习意图名称和话语之间的相似性。

传统的意图分类器输入话语并生成类别标签。通常我们用 LSTM 或伯特对话语进行编码,然后将编码后的话语送入密集层,得到一个类别标签:

传统的意图分类器。图片 bu 作者

相反,我们的零触发意图分类器学习标签和话语是否语义相似。我们使用了暹罗网络架构,这对于计算语义相似度非常有用。我们的暹罗网络输入一个意向名称和一个话语;输入相关或不相关时的输出。

架构和我在上一篇暹罗 NN 文章中描述的一样。该架构包括

  • 编码话语和标签的 LSTM/伯特层
  • 随后是距离层以计算话语和标签之间的语义距离
  • 最后一个密集层挤压距离矢量到一个二进制值

这种结构仍然是一个文本分类器,但这次输入的数量是 2,输出向量的维数只有 1。输出是二进制的,0 表示标签和话语不相关;1 表示话语属于该标签的类别。

连体零射击意图分类器。作者图片

只要我们能够为标签提供良好的嵌入,我们就可以向暹罗分类器请求我们想要的任何标签(即使分类器以前从未见过这个标签)。这对于域外的话语来说是非常好的,因为即使你没有任何 ood 类的标记数据或者只有几个例子,暹罗零镜头分类器仍然可以决定一个话语是与 Chris 域相关还是与该域不相关。

暹罗网络在很长一段时间内被用于语义相似性,然而玩一些技巧可以带我们到一个毫不费力的零射击意图预测模型。有时候想法一直在你眼前,但你得换个角度去看。

在本文中,我们继承了一种全新的方法来解决“传统的”意图分类问题。首先,我们对克里斯的话语进行了语义分组。然后,我们刷新了我们关于暹罗网络的知识。最后,我们看到了如何使用暹罗网络进行零镜头分类。

我希望所有读者喜欢我们的 Chris 的能力和数据,并希望在更多的 Chris 文章中与您见面。在那之前,请继续关注健康的❤️

参考

  • 语言模型是很少出手的学习者,【https://arxiv.org/abs/2005.14165
  • 预应变变压器在意图分类方面是否稳健?
    超范围意图检测评估中缺失的成分https://arxiv.org/pdf/2106.04564.pdf
  • 检测发给虚拟个人助理的域外话语https://www . Microsoft . com/en-us/research/WP-content/uploads/2014/09/IS14-orphan 2 . pdf
  • 利用语义相似度对文本文档进行多标签零炮
    分类,https://www . esann . org/sites/default/files/proceedings/legacy/es 2016-174 . pdf

Zillow 崩溃证明 AI 并不完美:让我解释一下

原文:https://towardsdatascience.com/zillow-collapse-proves-ai-is-not-perfect-let-me-explain-3bdeb03b7713?source=collection_archive---------7-----------------------

社区笔记

我从 Zillow 崩溃的故事中学到的两个教训

由杰里米·贝赞格在 Unsplash 上拍摄的照片

最近的疫情导致我们周围的许多事情发生变化,包括房地产市场。房地产市场的剧烈变化导致用于预测房价的机器学习模型不再有效。这就是 Zillow 崩溃的原因,主要是因为他们过于依赖人工智能算法来预测房价。

Zillow 的房屋交易部门在他们承认他们的房价预测算法(也称为 Zestimate )无法再为他们的业务创造价值后彻底关闭。在疫情之后,该算法无法以足够的精度预测房价,从而让他们更好地决定买卖房产。该算法仍然为在线用户提供房价的猜测。问题是“明知失败,还会用这些结果吗?”

房价预测是数据科学应用的经典例子之一,仅次于“预测泰坦尼克号乘客的生存”或“识别 MNIST 数据集上的手写数字”。Zillow 崩溃不能将房价预测从经典的机器学习挑战中剔除;然而,它提醒我们人工智能解决方案可能会失败和工作。在这篇文章中,我想分享两个值得吸取的教训。

—历史不押韵,AI 就失败了。

历史不会重复自己,但经常押韵。当它押韵时,它可以被 AI 算法预测。在这个语境中,预测指的是房屋在几个月的范围内,装修后的未来价格。我们在过去两年中经历的劳动力市场或供应链的波动,使得预测与疫情之前相比几乎是不可能的。

人工智能是预测的有力工具;然而,它只有在未来建立在过去的基础上时才起作用。发生在疫情之后的事情以前没有发生过;所以依赖历史数据的人工智能算法可能不会很好地工作。每当问题中出现时间因素时,我们必须更加小心。这就是为什么估计比预测更容易。在这里,估价是指给定一系列特征,如位置、大小和建筑年份,房屋的当前价格。

历史不会重复自己,但经常押韵。当它押韵时,它可以被 AI 算法预测。

由于其他挑战,您可能无法使用人工智能为商业案例创造价值。你可以阅读下面的文章,了解如何评估在商业案例中使用人工智能的可行性。

https://medium.com/swlh/if-you-consider-using-ai-in-your-business-read-this-5e666e6eca23

—系综,先是神经网络,后来。

集成方法在需要快速结果和持续改进的行业中非常有用,例如在开发的早期。当你展示人工智能如何为企业创造价值时,必须使用更复杂、性能更高的方法,如神经网络。我听说过一些失败的故事,高管们在早期要求开发神经网络模型,而人工智能模型的价值尚未得到证明。坚持在早期建立神经网络模型会增加团队失败的几率。为什么?主要是因为很难对他们进行适当的培训,或者公司内部还不了解人工智能引擎的价值(不考虑实现技术)。要阅读另一个关于系综模特的成功故事,你可以阅读下面的文章。

Zestimate 是预测房价的 Zillow 算法,最初由大约 1000 个模型组成。你现在不奇怪了!在他们能够用他们的人工智能算法创造价值后,他们转向神经网络模型。新方法将算法误差减少了约 10%,同时为更频繁地更新模型创造了机会。Zillow 的数据科学团队甚至已经开始探索其他人工智能技术,如文档理解或自然语言处理,直到它们因疫情而失败。最后一点是,当问题由于大数据漂移而变得无法解决时,无论您使用什么技术都无关紧要。这不仅仅是可解的!

坚持在早期建立神经网络模型会增加团队失败的几率。

临终遗言

简而言之,我学到的教训是:“当历史不押韵时,人工智能就失败了。”以及“先整体,后神经网络”。我想听听你对这件事的看法。这件事对你如何看待人工智能的力量有影响吗?请在这里分享你的看法。

感谢阅读!

如果你喜欢这个帖子,想支持我…

  • 跟我上
  • 亚马逊 上查看我的书!
  • 成为 中的一员
  • 连接上Linkedin
  • 关注我 推特

https://pedram-ataee.medium.com/membership

在 Matplotlib 中放大和缩小以更好地理解数据

原文:https://towardsdatascience.com/zooming-in-and-zooming-out-in-matplotlib-to-better-understand-the-data-b4a5f5b4107d?source=collection_archive---------17-----------------------

钱德勒·克鲁登登在 Unsplash 上的照片

为每个地块提供完整的代码

Matplotlib 可以说是 Python 中最流行的可视化库。还有,其他一些更高端的库也是基于 Matplotlib 构建的。我有几篇关于 Matplotlib 可视化技术的文章。请随意查看。我在这一页的底部有链接。

本文将重点介绍一些缩放技术。有时,当我们制作散点图或线图时,我们可能会发现许多数据杂乱地放在一个地方。在这种情况下,放大那些杂乱的地方会有助于真正清楚地理解数据点。同样,如果数据点过于分散,就很难看出是否有趋势。缩小有助于查看数据中的任何趋势。

幸运的是,Matplotlib 库有一些非常酷的技巧可以帮助我们,我们也可以使用一些简单的技术来放大和缩小。

让我们来看一些例子。

让我们先导入并读取数据集。我用的是 Kaggle 的自动数据集。以下是数据集的链接:

https://github.com/rashida048/Data-visualization-in-python/blob/main/auto_clean.csv

这是一个开放的数据集,这里提到的。

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as snsd = pd.read_csv("auto_clean.csv")

这个数据集相当大。所以我不能在这里分享任何截图。这些是列:

d.columns

输出:

Index(['symboling', 'normalized-losses', 'make', 'aspiration', 'num-of-doors', 'body-style', 'drive-wheels', 'engine-location', 'wheel-base', 'length', 'width', 'height', 'curb-weight', 'engine-type', 'num-of-cylinders', 'engine-size', 'fuel-system', 'bore', 'stroke', 'compression-ratio', 'horsepower', 'peak-rpm', 'city-mpg', 'highway-mpg', 'price', 'city-L/100km', 'horsepower-binned', 'diesel', 'gas'], dtype='object')

首先,我想在缩小技术上下功夫。

为此,我将绘制一个长度与宽度的散点图。这是完整的代码,我会在情节之后解释一下:

fig = plt.figure(figsize = (8, 6))x = d['length']
y = d['width']
c = d['price']ax = plt.scatter(x, y, s = 25, c = c)plt.xlabel('Length', labelpad = 8)
plt.ylabel('Width', labelpad = 8)
plt.title("Length vs Width and Color Represents the Changes of Price")ax_new = fig.add_axes([0.2, 0.7, 0.2, 0.2])
plt.scatter(x, y, s=5, c = c)

作者图片

看,小区里面一个小的缩小窗口。我假设你已经知道如何做散点图了。我不想看那些代码。缩放窗口来自 fig.add_axes()函数,其中有一个参数。这是四个元素的列表[0.2,0.7,0.2,0.2]。这里最后两个元素 0.2 和 0.2 表示缩放窗口的高度和宽度。前两个元素 0.2 和 0.7 定义了缩放窗口的位置。请随意更改这些数字,看看会发生什么。

我们来看一些放大的技巧。这次我将使用长度与价格的关系图。我们需要先导入 mark_inset 和 inset_axes 函数。inset_axes 函数将定义缩放窗口的大小和缩放窗口的位置。另一方面,mark_inset 函数将绘制从原始点到缩放窗口的线。请查看下面代码中的注释,以获得一些清晰的理解。以下是完整的代码:

from mpl_toolkits.axes_grid1.inset_locator import mark_inset, inset_axesplt.figure(figsize = (8, 5))
x = d['length']
y = d['price']ax = plt.subplot(1, 1, 1)
ax.scatter(x, y)
ax.set_xlabel("Length")
ax.set_ylabel("Price")#Defines the size of the zoom window and the positioning
axins = inset_axes(ax, 1, 1, loc = 1, bbox_to_anchor=(0.3, 0.7),
                   bbox_transform = ax.figure.transFigure)axins.scatter(x, y)x1, x2 = 0.822, 0.838
y1, y2 = 6400, 12000#Setting the limit of x and y direction to define which portion to #zoom
axins.set_xlim(x1, x2)
axins.set_ylim(y1, y2)#Draw the lines from the portion to zoom and the zoom window
mark_inset(ax, axins, loc1=1, loc2=3, fc="none", ec = "0.4")
plt.show()

作者图片

这是最后一个放大的例子。这次我将使用支线剧情来呈现 zoom_in 窗口。上面会有两个小的放大窗口,下面是原来的大图。要缩放的部分将用颜色突出显示,连接线将清晰显示。请仔细检查代码中的注释,以便更清楚地了解代码。以下是完整的代码:

from matplotlib.patches import ConnectionPatchfig = plt.figure(figsize=(8, 5)) #the plot with red dots
plot1 = fig.add_subplot(2,2,1) # two rows, two columns, fist cell
plot1.scatter(d['city-mpg'], d['highway-mpg'], color = 'red')
plot1.set_xlim(15, 20)
plot1.set_ylim(17, 25)
plot1.set_ylabel('highway-mpg', labelpad = 5)#the plot with blue dots
plot2 = fig.add_subplot(2, 2, 2)
plot2.scatter(d['city-mpg'], d['highway-mpg'], color = 'blue')
plot2.set_xlim(25, 30)
plot2.set_ylim(25, 35)#the original plot
plot3 = fig.add_subplot(2,2,(3,4)) # two rows, two colums, combined third and fourth cell
plot3.scatter(d['city-mpg'], d['highway-mpg'], color = 'darkorchid', alpha = .7)
plot3.set_xlabel('city-mpg', labelpad = 5)
plot3.set_ylabel('highway-mpg', labelpad = 5)#highlighting the portion of original plot to zoon in 
plot3.fill_between((15, 20), 10, 60, facecolor= "red", alpha = 0.2)
plot3.fill_between((25, 30), 10, 60, facecolor= "blue", alpha = 0.2)#connecting line between the left corner of plot1 and the left #corner of the red hightlight
conn1 = ConnectionPatch(xyA = (15, 17), coordsA=plot1.transData,
                       xyB=(15, 20), coordsB=plot3.transData, color = 'red')
fig.add_artist(conn1)#connecting line between the rightcorner of plot1 and the right #corner of the red hightlight
conn2 = ConnectionPatch(xyA = (20, 17), coordsA=plot1.transData,
                       xyB=(20, 20), coordsB=plot3.transData, color = 'red')
fig.add_artist(conn2)#connecting line between the left corner of plot2 and the left #corner of the blue hightlight
conn3 = ConnectionPatch(xyA = (25, 25), coordsA=plot2.transData,
                       xyB=(25, 30), coordsB=plot3.transData, color = 'blue')
fig.add_artist(conn3)#connecting line between the right corner of plot2 and the right #corner of the blue hightlight
conn4 = ConnectionPatch(xyA = (30, 25), coordsA=plot2.transData,
                       xyB=(30, 30), coordsB=plot3.transData, color = 'blue')
fig.add_artist(conn4)

作者图片

如果您对本文中的任何代码有任何疑问,请在评论部分提问。

结论

对于这个数据集,放大或缩小可能不太重要。但在现实生活中,有很多数据集实际上需要放大或缩小才能更好地理解数据。我希望你能在现实生活的项目中使用这些技术,做一些很酷的工作。

欢迎随时关注我的推特(Twitter)和我的新 T2 YouTube 频道(T3)。

更多阅读