借助模糊逻辑对简单配色服装进行分类
实践教程
跟随我们创建一个智能助手,它将帮助我们决定一套服装是否有匹配的颜色
概观
在下面的文章中,我们将介绍一个概念验证项目的完整开发过程,它将帮助我们对由衬衫、下装和鞋子组成的简单配色进行分类。本文将分为以下几个部分:
- 了解问题(问题的起源)
- 快速回顾我们的主要工具‘模糊逻辑’
- HSV 颜色表示概述
- 建立模糊逻辑模型
- 添加装备规则过滤匹配的装备
- 最终结果和结论
了解问题
在 Covid 来到我们国家之前,大学里的一天,我和一些同事正在等老师开始讲课,突然出现了服装的话题。在谈话的某个时候,我们问我们的色盲朋友,他是如何选择每天穿的衣服的。他继续解释说,他过去常常依靠家人告诉他衣服是否相配,自从他搬出去开始独立生活后,他再也不能向他们求助了。他目前的方法要求他通过识别服装的品牌或图案来猜测或记住好的服装,这听起来非常适得其反。
我无法想象看不到完整色谱的概念及其对我日常生活的影响,这让我开始思考颜色和时尚之间的关系。那天晚些时候,我决定联系 Valeria,她是我的朋友,目前正在学习时尚和设计,希望获得一些知识,并找到解决我朋友问题的方法。我问她在选择服装配色方案时遵循的流程,在一次富有成效的长谈后,我发现自己在想,“难怪没有一个通用的解决方案,它比我想象的更加复杂和主观!”。我试图缩小指导方针的范围,这些是经常出现的规则:
- 中性色(白色、黑色、灰色、藏青色等。)可以适合任何装扮。
- 有暖色(红色)和冷色(蓝色)两种颜色,可以用不同的组合来搭配一套服装。
- 每种颜色都有不同的色调,搭配衣服时也要考虑到这一点。
当时我正在上机器学习的入门课。我天真地认为,很可能有一种简单的方法来训练一个模型来对匹配的衣服进行分类,并且这个模型会从所有输入的数据中推断出这些规则。最初的想法相对简单明了:
- -收集匹配和不匹配服装的数据并进行预处理
-用数据训练神经网络
-检查结果并进行调整
-使用模型将三种颜色的任意组合分类为匹配/不匹配
这个计划听起来可行,但是当我开始设计一个更具体的解决方案时,我发现有一些主要的细节我没有考虑进去。
我遇到的第一个障碍与颜色有关,这个话题我很快就会谈到。第二个障碍与训练模型和收集使其工作所需的大量数据有关。有很多关于时尚的数据库,但没有一个是以颜色为中心的。我想过自己收集数据,但这也带来了一些耗时的问题。
这一切都开始崩溃,看起来像一个太复杂而无法完成的项目,所以随着时间的推移,我失去了兴趣,并将其添加到“有趣但目前不可行”的项目列表中。直到我在计算机智能课程上听了一次特别的讲座,我才重新获得了继续这个项目的动力,这次脑子里有了一个新概念:模糊逻辑。
模糊逻辑概述
为了使这篇文章简短,我将用简单的术语总结我对模糊逻辑的理解,足以让你完成这个项目的其余部分。我绝不是这方面的专家,因为我自己刚刚才知道,所以如果你想要更深入地解释模糊逻辑,我建议你看看https://www.guru99.com/what-is-fuzzy-logic.html或电脑爱好者关于这个话题的视频。
我们首先需要回到“经典逻辑”。经典逻辑依赖于离散的、定义明确的值,如“真”或“假”。这些值之间没有混淆,它们可以完整地描述一种状态或结果,例如,如果加热器是“开”或“关”,则没有类似“稍微开”的中间值。另一方面,模糊逻辑可以使用没有严格定义的“中间”值。让我们来看一个最常见的例子:温度。
假设你想创建一个逻辑系统,当房间很热的时候关掉一会儿加热器,问题是,你如何定义“热”?热、暖、冷没有明确的界限;它似乎以连续的方式转变,并且在某一点上主观上变暖或变冷。模糊逻辑允许您定义没有明确界限的变量集,如[‘热’、‘暖’、‘冷’],并使用它们和一组规则来确定不同的结果。一个规则的例子可以是:如果房间“热”,关掉加热器“几分钟”。请记住,您需要指定这些模糊集及其隶属函数,这有助于模型计算某个值属于某个子集(模糊值)的程度。让我们看一个例子:
来源:https://commons . wikimedia . org/wiki/File:Fuzzy _ logic _ temperature _ en . SVG
这是“温度”隶属函数的图形表示。它解释了与温度变量相关的模糊集[‘冷’,‘暖’,‘冷’]。给定一个明确的值,可以认为是变量的可测量值(在本例中是以摄氏度或华氏度为单位的温度),该函数将确定每个模糊元素描述该值的程度。在这个例子中,我们看到枢轴被放置成与“冷”和“暖”相交。我们可以推断,这个地方的温度并不完全冷,但也没有热到可以认为完全暖和。如果将其描述为 cold 更准确,它将给出与 cold 最匹配的模糊值。
在本例中,我们看到的是梯形隶属函数,但您可以使用不同的函数来映射每个变量,包括:三角形、高斯形、钟形等。
一旦我们模糊了温度的明确值,我们就可以使用一组以直观方式描述的规则来指定预期输出,例如:
- 如果温度“很高”,关闭加热器“几分钟”
- 如果温度“温暖”,关闭加热器“几分钟”
这些规则将被推理机用来计算输出。最后,我们可以通过将结果输出映射到一个描述分钟数[‘一些’,‘几个’,…]的隶属函数来消除其模糊性。这个反模糊值将给出数字结果,表明我们应该关闭加热器的精确分钟数。
理解数字颜色表示
鉴于我们的助手将生活在数字空间,我们所知道的颜色将需要用计算机能够处理的方式来表示。颜色有几种数字表示,最著名的是 RGB(红绿蓝)。
自然,我们可以开始考虑使用 RGB 作为模糊逻辑模型的清晰值,但是我们对 RGB 了解得越多,我们对使用它就越不乐观。RGB 允许超过 1600 万种不同的颜色,在一种颜色和另一种颜色之间没有直观的过渡/模式,除了一个人可能有更多的“红色”和更少的“蓝色”的想法。
这可能感觉像是一个巨大的挫折,但是如果你曾经使用过像 Photoshop 这样的照片编辑工具,并试图选择一种新的颜色,你可能会记得看到许多不同的选项来指定它。在这些选项中,我们可以看到 RGB、十六进制、CYM 和 HSV。如果我们对所有这些选项进行一些调查,并阅读它们之间的差异,我们可以观察到 HSV 具有一些值得探索的有用特征。
HSV 代表色调、饱和度和价值。每个组件在表示中都有特定的功能和一系列可能的值:
- 色调:代表感知的颜色,不考虑阴影。值从[0,360]
- 饱和度:表示颜色的丰富程度。饱和度越低,颜色就越淡,越灰。值从[0,100]
- 值:表示颜色的亮度,或发出的光量。值越低,颜色越深。值从[0,100]
这是 HSV 的视觉表示,我们可以看到每个维度如何以其独特的方式影响颜色。来源:https://es . m . Wikipedia . org/wiki/Archivo:HSV _ color _ solid _ cylinder . png
HSV 表示法的最大优点是:
- 颜色与色调/强度分离
- 色调成分对光线变化不太敏感
这里有一个例子,我们在不同的光线下有相同的颜色:
RGB (50,132,219)
HSV ( 211 ,77,86)
RGB (30,80,133)
HSV ( 211 ,77,52)
正如您所看到的,RGB 表示在每个维度上为两个元素提供了不同的值,而 HSV 保留了色调分量,只对值分量进行了调整。
这很有用,因为我们可以分离色调来确定一种颜色是“冷”还是“暖”温度,而无需考虑它的阴影/色调,然后使用饱和度和值来描述色调。
我们还可以通过同时检查饱和度和值来确定颜色是否为“中性”。低饱和度会产生接近灰色的褪色颜色。低值会产生暗(黑)色,高值会产生亮(白)色。所有这些都可以归类为“中性”颜色,可以在搭配服装时用作通配符。
改进的项目计划
现在我们对数据和问题有了更好的理解,我们可以制定一个新的计划,分两步来解决这个项目:
1.创建一个模糊逻辑模型,给定一种 HSV 格式的颜色,可以确定该颜色是“中性色”、“暖色”还是“冷色”,并指示其色调是“暗”还是“亮”。例如,给定 HSV 中的一种颜色,按照“暗暖色”或“亮冷色”2 返回一个描述。使用模型获得每件衣服的描述,然后使用时尚规则来确定服装是否匹配。《出埃及记》冬季服装应该包含所有“深色”,最好是“冷”色调。
模糊逻辑模型
第一步围绕着创建模糊逻辑模型,这将为我们提供颜色的描述。目标是以 HSV 格式输入一种颜色,并得到该颜色的描述性文本。
如果我们看一下在与 Valeria 交谈期间收集的笔记,我们会看到这些描述颜色的单词不断出现:“中性”、“暖色”、“冷色”、“暗色”、“亮色”,因此找到一种将 HSV 的颜色转换为类似颜色的方法可能会很有用。这些词主要用于表示一套服装的良好搭配。两种“暗冷”色和一种“亮冷”色,或者两种“中性色”和一种“亮暖色”。
HSV 将色调与其他成分分开,这样即使它们具有不同的色调,您也可以获得相同的色调。这将有助于我们对颜色的温度进行分类(“暖”或“冷”)。
(左)修改图片来自https://commons . wikimedia . org/wiki/File:RGB _ color _ wheel _ 360 . SVG——(右)图片作者
这是“温度”变量的隶属函数,由模糊集[“温暖”、“凉爽”]和范围从[0,360]的清晰值组成。正如在色轮中看到的,给定其圆形表示,色调在冷色、暖色和回到冷色之间过渡。有两种过渡色(绿色和棕色),其中“暖”和“冷”的概念变得非常主观。
作者提供的图片
这些隶属函数变得更加简单。
第一张图描述了颜色“值”的隶属函数,它可以帮助我们识别颜色从“黑”到“非常亮”的所有亮度。
第二个标识“饱和度”函数的成员,该函数可以基于饱和度值描述颜色是否完全“灰色”、“褪色”或不同程度的“饱和”。为了简单起见,这些都是均匀分割的高斯函数,但并不需要这样。
这两种成分的混合可以帮助我们识别不同的色调(“暗”或“亮”)和“中性”颜色,因为低饱和度导致灰色,低或高值分别导致黑色或白色。
我们需要添加规则来指导音调变量的计算。我们可以从一些简单的规则开始,例如:
- 1。饱和度被标识为“灰色”或“非常褪色”的颜色将被视为中性色
2。具有“黑色”值的颜色将被视为中性
3。“饱和”和“非常暗”的颜色将被视为深色
4。“褪色”和“深色”的颜色将被视为深色
5。“非常饱和”和“暗”的颜色将被视为暗
6。“褪色”和“非常亮”的颜色将被视为明亮的颜色
7。“饱和”和“明亮”的颜色将被视为明亮
8。“非常饱和”和“非常亮”的颜色将被视为明亮
如果我们要映射所有不同的值与饱和度的组合,我们将得到下面演示的表格。红色单元格表示在规则集(ej)中特别指定的结果值。“饱和”和“非常暗”导致“暗”色调)。白细胞不是显式编程的,但模型将根据前面的规则推断它们。如果这些结果不能正常工作,我们总是可以根据自己的喜好改变规则集。
有了这些规则,我们只需要为音调变量创建隶属函数,这样我们就可以将推理机的结果映射到其对应的类别。
作者图片
这是音调变量的隶属函数的图形表示。我们可以使用各种函数来帮助我们将推理机的结果映射到相应的模糊值。这些功能背后的直觉源于一个想法,即色调可以在暗色调和冷色调之间转换,但我们仍然必须考虑中性色。中性色可以被认为是那些足够暗或褪色到没有可感知的色调的颜色,这就是为什么我们看到中性色的映射接近深色。通常,我们会对推理机的结果进行去模糊化,以获得一个清晰的值,但对于这个特定的项目,我们感兴趣的是描述性的模糊值,如“明亮”。
下图显示了数据在清晰值和模糊值之间进行修改和转换的各个阶段,从 HSV 格式的数字颜色表示开始,并产生颜色的描述性文本。
作者图片
完成这些操作后,下一步将是将 HSV 值输入到它们各自的模糊化器中,并组合结果以获得由(TONE,TEMP)组成的描述。以下是一些测试结果:
作者图片
不错!这个过程似乎正在起作用。在某些情况下,我们可以用不同的方式来描述颜色,但这也可能是主观的。幸运的是,由于这是一个由规则引导的模糊逻辑模型,我们可以立即根据自己的喜好调整规则。现在我们已经有了描述,我们可以继续拼图的最后一块了。
*中性色包含了一个温度描述,因为在过程中还在单独计算色相,但是可以忽略。
重要的是要重申,我们可以增加模型的复杂性,使其更加准确,例如增加精确的色调集,或添加特殊的硬编码“中性”情况,如可能难以计算的棕色或米色。我们还可以添加一些偏差,这样冷色就有“暗”色调偏差,暖色就有“亮”色调偏差。为了完成这篇文章,我们将继续我们目前的结果。
添加匹配装备规则
我们可以遵循不同的服装配色方案,每一种都有自己的一套规则。我们能找到的最常见的是下面的*(在大多数这些‘中性’颜色中,它们可以用作通配符,因为它们基本上在任何地方都匹配)*:
- -中性色:只由中性色组成
-基本(safe-bet):任何颜色之间没有高对比度,不超过一个明亮的暖色
-对比度:两个深色和一个亮色,可以混合暖色和冷色。
-类比:不同色调下相同温度的颜色
-冬季:多为暗冷色
-夏季:多为暖色和一些对比鲜明的颜色
我们可以为备选服装配色方案创建不同的规则集,并将服装颜色描述传递给一个函数,该函数将验证规则并决定服装是否匹配特定的配色方案。
作者图片
有了这些规则,我们可以寻找一个特定的服装搭配,或者通过所有的方案运行颜色,并检查至少一个匹配的结果。
结果
为了尝试和测试这种算法,我们可以用谷歌图片快速搜索匹配和不匹配的服装。找到搭配的例子很容易,因为有很多关于搭配衣服的指南。我们可能很难找到不匹配的服装,因为这些服装并不公开。
来源:https://burst.shopify.com/photos/casual-urban-menswear?c =型号
如果所有颜色都是中性色,则按预期工作。“类比”分类在技术上是正确的,但更准确的描述应该是“单色”,因为颜色属于同一色调,但具有不同的色调。
来源:https://www . pexels . com/photo/woman-in-pink-turtle-weather-1649896/
按预期工作。
来源:https://stylishlyme.com/style/fur-coat-outfit/
由于这套服装主要由深色和冷色组成,因此效果与预期一致。
来源:https://www.freepik.com/photos/man—由 astock_productions 创作的人物照片
按预期工作。我们可以从这幅图中观察到一个潜在的问题。我们知道鞋子是白色的,但是照片的背景给了它们一些蓝色。分类仍然是准确的,但应该进行更多的测试来查看任何类似的问题。
来源:https://I . pinimg . com/736 x/0b/1c/c8/0 B1 cc 861706 edabe 86 c 089 da 600122 b 3 . jpg
按预期工作。这件衣服是在搜索“不匹配的衣服”时发现的。
来源:https://www.freepik.com/photos/people—由 lookstudio 创作的人物照片
按预期工作。这看起来像是一套色彩丰富但有风险的服装,所以它没有被归类为“基本款”。
最后的想法
总的来说,我们可以发现这些结果对于概念验证项目来说已经足够好了,但是可以进一步调整以提高准确性。有一些棘手的颜色,如紫色,棕色和绿色,应该进行更彻底的调查,并可能作为特例添加。
如果这个项目作为针对色盲的产品投入生产,它很可能只会考虑更安全的服装,如基本、中性、冬季风格,并避免一些高对比度的危险服装,如 Analogous 和 Summer。话又说回来,根据具体情况,一些危险的服装也可能成为时尚。
值得一提的是,搭配一套简单的衣服并不难,当衣服中加入更多的物品时,比如夹克、皮带、袜子等,搭配不当的可能性就会增加…此外,在选择服装时,还有一些其他细节需要考虑:
- -质地/图案也可能对服装有很大影响
-一件衣服的正式/优雅也应考虑在内。polo 衫(正式)与运动裤(非正式)
-肤色可能会影响整套服装的整体外观
至于模糊逻辑模型,我们总是可以增加它的复杂性,以获得更精确的描述,以及深入挖掘不同的隶属函数,可以与模糊变量一起使用,以获得更好的结果。
理想的最终产品将实现这一解决方案以及计算机视觉,以识别服装各自的颜色。此外,这可以开发为任何即将推出的 AR 眼镜的 AR 应用程序,以模拟实时服装助手,可以帮助色盲或任何想要时尚建议的人。
欢迎评论中的任何反馈或建设性的批评
github Repo:https://github . com/FCARRILLOM/ClassifyingColorMatchingOutfits
特别感谢:
瓦莱里娅,分享她的时尚知识。(在 Instagram @wtf.isfashion 上找到她的时尚简介)
朱安·迭戈,分享他色盲的经历。
蛋白质基准数据集分类
实现图形卷积网络的两种方法
这是图论和图卷积网络系列的第四部分。
如果你一直在阅读这整个系列,那么你已经伴随我走过了这整个旅程——通过讨论什么是图论以及为什么它很重要,什么是图卷积网络甚至是和它们如何工作,现在我们在这里,到了有趣的部分——构建我们自己的 GCN。
如果你是这个系列的新手,那也完全没问题!不管是哪种情况,让我们开始编码吧!
在本教程中,我们将通过 GCNs 的两个实现来对蛋白质基准数据集进行分类。如果你想找到这些数据的归属或论文,或者自己下载看看,可以在“生物信息学”标题下 这里 *找到。*你也可以在这里看一看整个笔记本。代码的属性可以在这个项目的库中找到。
第一部分:带频谱的 GCNs
什么是光谱?根据他们的主页:
Spektral 是一个用于图形深度学习的 Python 库,基于 Keras API 和 TensorFlow 2。这个项目的主要目标是为创建图形神经网络(GNNs)提供一个简单而灵活的框架。
开始使用 Spektral 非常容易,因为对项目进行了预先考虑——如果你已经使用 Keras 或 Tensorflow 进行过建模,我想你会发现 Spektral 非常直观。
此外,Spektral 有许多内置的基准图数据集,这意味着您不必担心需要确保您的数据是使用 GNNs 建模的正确格式,并且可以很容易地开始实验。
无论您是在 Colab 中学习本教程,还是仅仅学习一本普通的笔记本,我们的第一步都是一样的。Spektral 不是 Colab 的内置库之一,所以我们需要安装它:
PROTEINS 是来自 TU Dortmund 的图形内核的基准数据集之一。您可以从TUDataset
类中访问这个数据集类,我们通过首先导入然后实例化它的一个对象来访问它,我们要访问的数据集的名称被传入:
当我们加载数据集时,我们可以看到它在n_graphs
属性中包含了多少个图。在这里,我们可以看到这个数据集有 1113 个图形。在这个数据集中,这些被分成两个不同的类。
Spektral 的GCNConv
层基于 Thomas N. Kipf 和 Max Welling 的论文:“使用图卷积网络的半监督分类”。这是完美的,因为这是我们一直在参考的论文,如果你一直在关注这个系列的话。如果您还没有,我建议您看看这篇论文以及我写的关于这些网络如何工作的文章,以便更好地了解 Spektral 在幕后为我们做了什么!
因为这是我们想要使用的层,我们将不得不执行一些预处理。Spektral 用他们的GCNFilter
类让这变得非常简单,该类只用两行代码就为我们执行了预处理步骤(在论文中有概述)。
首先,从spektral.transforms
导入GCNFilter
,然后在我们的数据集上调用.apply()
,传入GCNFilter
的实例:
在这个阶段,我们希望确保执行我们的训练/测试分割。在这个简单的例子中,我通过混排数据,然后取切片(分别约 80/20%)来实现,但是一旦您熟悉了这个实现,欢迎您进一步优化这个步骤!
现在,让我们导入我们的模型需要的层:
等一下——这些导入语句不是来自 Keras 吗?别担心——这不是偶然的。因为 Spektral 是建立在 Keras 之上的,所以我们可以很容易地使用 Keras functional API 来构建我们的模型,在我们处理图形结构数据时添加 Spektral 特定的层。
我们导入Dense
和Dropout
层— Dense
是典型的执行前向传播的密集神经网络层,而Dropout
以我们设定的速率将输入单元随机设置为 0。这里的直觉是,这一步可以帮助避免过度拟合*。
然后,我们导入我们之前介绍的GCNConv
层和GlobalSumPool
层。Spektral 为我们简明地定义了这一层:
全局汇总层。通过计算图的节点特征的总和来汇集图。
这就是全部了!让我们建立我们的模型:
这里,我们使用模型子类来定义我们的模型。当我们实例化模型进行训练时,我们将向模型传递n_hidden
:隐藏层数和n_labels
:标签(目标类)数。
然后,在__init__
中,我们将所有的层定义为属性。在call
中,我们定义了这个方法,通过按顺序调用我们的输入层来创建和返回我们想要的输出。
让我们为训练实例化我们的模型!
在这里,我们将初始化 32 个隐藏层和数据标签的数量。当我们读入时,Spektral 方便地在我们的TUDataset
上给了我们一个n_labels
属性。这样做的好处是,如果您想探索其他数据,您可以对任何其他光谱数据集使用相同的代码,无需修改!
上面,我们在我们的模型上调用.compile()
。如果你熟悉 Keras,你就会熟悉这个方法。我们将传递我们的优化器,adam
,并定义我们的损失函数,categorical crossentropy
。
现在我们遇到了一个障碍。你们中熟悉 Tensorflow 和 Keras 的人可能会尝试给model.fit()
打电话,然后就到此为止。然而,即使 Spektral 在 Keras 上无缝地构建 GNNs,我们也不能完全以同样的方式处理我们的数据。
因为我们使用的是图形结构的数据,所以需要创建批处理来填充我们的 Keras 模型。对于这项任务,Spektral 仍然通过提供装载机让我们的生活变得更加轻松。
现在我们已经处理了批处理,我们可以调用model.fit()
。我们不需要指定批处理,只需传入我们的加载器,因为它作为一个生成器工作。我们需要提供我们的steps_per_epoch
参数用于培训。
对于这个简单的例子,我们只选择了 10 个时期。为了验证,让我们为测试数据创建一个加载器:
我们将通过调用.load()
把它提供给我们的模型。
用 Spektral 构建 gcn 到此结束!我强烈建议您试着优化这个例子,或者深入到可以用 Spektral 构建的其他 gnn 中去。
第二部分:Pytorch 几何的 GCNs
尽管 Spektral 为我们提供了一个优秀的图形神经网络层、加载器、数据集等库,但有时我们可能需要更精细的控制,或者我们可能需要另一个工具来完成这项工作。
Pytorch-Geometric 还提供了基于 Kipf & Welling paper 以及基准数据集的 GCN 图层。PyTorch 的实现看起来略有不同,但仍然易于使用和理解。
我们开始吧!我们将从同一个笔记本开始,从标题“Pytorch 几何 GCN”的正下方开始。提供了此代码的归属。
像往常一样,我们的第一步是安装我们需要的包:
现在,让我们拿起数据集:
看看我们的数据:
这里,我们演示了 Pytorch-Geometric 在我们的 TUDataset 对象上提供的各种属性。这为我们提供了大量信息,我们可以使用这些信息在以后微调我们的方法,并深入了解我们的数据。
现在我们知道了数据的样子,我们将执行我们的训练/测试分割。对于这个例子,我也使用了一个简单的洗牌和切片方法,但是和往常一样,我鼓励你研究一下这个步骤的优化!
Pytorch 还为我们提供用于配料的DataLoaders
:
现在我们已经完成了这一步,我们可以构建我们的模型了。我们将使用类似的方法,但是记住现在我们使用 Pytorch 而不是 Keras。
我们将导入我们的功能层(类似于 Keras 的Dense
层)、我们的GCNConv
层和一个global_mean_pool
层。这执行了与 Spektral 的GlobalSumPool
类似的汇集操作,但是取平均值而不是相邻节点的和。
在构建我们的模型时,我们继承了 Pytorch 的GCN
模型,然后初始化三个卷积层。我们将在实例化模型时传递隐藏通道的数量。
然后,我们构建一个forward()
方法,它类似于我们之前在特定 GCN 中构建的call()
方法。这告诉我们的模型如何通过卷积层传播我们的输入。使用 Pytorch,我们显式地定义了我们的激活函数。在这个例子中,我们使用relu
。
在我们的最终分类之前,我们执行我们的池,然后设置我们的 dropout 并通过最终的线性层传递我们的输入。
虽然有很多机会来定制和微调我们的 Spektral 模型,但我喜欢我们用 Pytorch 明确定义我们的架构的方式。当谈到“哪种方法更好”时,像大多数事情一样,这取决于你的团队需要优先考虑什么(例如,解释能力比快速证明概念的效率更重要)。
让我们来看看由此产生的架构:
接下来,我们需要:
- 设置我们的优化器——我们也将在这个实现中使用
adam
- 定义我们的损失函数——同样,我们将保持
categorical crossentropy
- 定义训练和测试函数,然后在设定的时间段内调用它们。
我们在这个例子中使用了更多的纪元,因此,我们获得了更好的指标。除了我用来帮助构建模型和学习各个库的代码示例之外,没有什么内在的原因。此外,鉴于 Pytorch-Geometric 实现是我的最终实现,我比早期的实验更关注结果。一如既往地,我鼓励您尝试和优化代码,使它变得更好!
查看最近 20 个训练时期,我们看到我们实现了大约 71.34%的训练准确度和大约 62.67%的测试准确度。评估 186 年前后的测试数据的准确性,一个潜在的优化将是早期停止回调,这确保我们一旦达到收敛就停止训练,以避免过度拟合模型。
我们还可以进行更多的优化,但这是一篇很长的文章,所以我们就此打住,我将让您自己进行试验和优化!
注意事项:
- —要了解更多关于辍学或为什么辍学的信息,请查看这个来源。
将 Dash 和 Plotly 与 Docker 结合使用,为 AI/ML 应用提供简洁的架构
使用 Dash 和 Plotly 创建企业级仪表板,在 Dash 中了解最佳实践、项目结构、架构模式
几乎每个数据科学项目都需要某种形式的可视化,如可视化输入数据、使用直方图或散点图的探索性数据分析、使用盒须图查找异常值或绘制统计数据、使用网络图可视化节点之间的关系、使用相关矩阵检查变量之间的关系、帮助理解高维数据集内的关系的可视化技术、可视化模型的性能或
此外,数据可视化可能成为任何演示文稿的重要补充,也是理解数据的最快途径。
正如你所看到的,数据可视化是任何数据科学项目的关键部分,但是创建一个仪表板并不是一项简单的任务。有很多库可以用来生成漂亮的图,但是在我看来,如果你正在使用 python,Dash 是最好的选择。
为什么 Dash
正如您在官方文档中看到的,Dash 是一个用于构建 web 分析应用程序的高效 Python 框架。Dash 是基于最流行的框架和库编写的**,比如 Flask、React 和 Plotly** ,它是构建数据可视化应用的理想选择。它很容易学习,因为 Dash 抽象了构建基于 web 的交互式应用程序所需的所有技术和协议。使用 Dash 你可以完全在 Python 中工作,后端,前端也可以用 Python 编写。
Dash 的优势
- 需要很少的样板文件就可以开始
- Dash 应用程序完全由 Python,甚至 HTML 和 JS 生成
- 它是“反应式”的,这意味着很容易推断出复杂的 ui 有多输入、多输出以及依赖于其他输入的输入
- Dash 应用程序本质上是多用户应用程序:多个用户可以查看应用程序并进行独立的会话
- Dash 应用程序可以有多个输入、多个输出以及依赖于其他输入的输入
- 因为 Dash 应用程序使用 Flask 作为后端,所以我们可以使用 Gunicorn 来运行它们,所以很容易通过增加工作进程的数量来扩展这些应用程序,以服务数百或数千个用户
- 使用 React.js 渲染组件,并包含一个插件系统,用于使用 React 创建您自己的 Dash 组件
- 因为它使用 Flask 作为服务器,所以您可以像部署 Flask 应用程序一样部署 Dash 应用程序
- 开源库,在许可的 MIT 许可下发布
- Dash 有很好的文档记录,并且在 Stack Overflow 和 Github 上有一个很好的响应社区
Dash 的缺点
- 反应图中没有中间值,对于中间数据,您必须添加一个隐藏的 div(正如 Plotly 所建议的)
- 您必须为每个输出编写单独的函数,这迫使您重新构建代码(有时这也是一种优势)
- 两个 Python 回调不可能更新同一个元素
- 不能有没有输入或没有输出的回调
每个框架或库也有缺点,但是在 Dash 的情况下,优点多于缺点,并且可以使用一些变通方法来解决缺点。
总的来说,如果你想为你的数据科学模型或算法创建一个企业级交互式仪表板,带有小样本的**,完全用 Python 编写(这样你就可以在后端和前端之间重用代码)Dash 是最好的选择。**
Dash 的干净建筑
注: 我们将在虚拟环境中工作。创建和激活虚拟环境参见 Python 虚拟环境 。
获得清晰架构的第一步是定义项目的文件结构**。任何严肃的 web 应用程序都有多个组件和多个页面。为了便于添加功能而不影响其他布局,并引入清晰的关注点分离,我推荐以下文件结构😗*
作者图片
资产文件夹
如果您想要在项目中自动包含图像、样式表或 Javascript 文件,Dash 要求您将它们添加到 Assets 文件夹中。Dash 将自动处理该文件夹中包含的所有文件。参见仪表板外部资源。
组件文件夹
该文件夹将包含可重用的组件,如在多个页面上使用的按钮、可视化数据的表格、在多个页面上使用的标题等。
例如,它可以包含基于数据帧构建表的函数:
布局文件夹
web 应用程序将有一个始终可见的通用布局。通常,这是由侧边栏和/或菜单和内容页面组成的。在我们的例子中,我们有一个可折叠的侧边栏和内容页面。
作者图片
正如我们在上面的图片中看到的,作为一个常见的布局,我们有一个侧边栏(用绿色突出显示),它总是可以看到的(除非它是折叠的),以及内容,它会根据侧边栏中的页面选择而变化。
对于侧栏,我们有两个文件,一个用于定义视图(sidebar.py),另一个用于定义行为,以声明回调(sidebar_callbacks.py)。
内容是一个简单的 div,最终的通用布局由侧边栏、内容 Div 组成,它还包括破折号的位置组件,代表 web 浏览器中的位置或地址栏。通过它的 href 、路径名、搜索、和哈希属性,你可以访问加载应用的 URL 的不同部分,这对路由和导航非常有帮助。
页面文件夹
一个企业级的 web 应用程序通常由多个页面组成,我们将为每个页面创建一个新的文件夹。每个页面将由三个不同的文件组成,这样我们就可以将 MVC 架构模式也应用于 Dash。我们将在本教程的后面描述如何在 Dash 中使用 MVC。
实用程序文件夹
该文件夹将包含与特定页面或组件无关但可通用的通用和可重用功能。例如,我们可以在这里添加货币转换、十进制数字格式化或其他数学函数。
它还将包含全局使用的常量或指向外部资产的链接(指向样式表、图标、字体等的链接)。)
环境文件夹
因为不同的环境(开发、测试、试运行、生产前、生产或其他)将具有不同的配置文件和不同的环境变量,所以我们需要一种在这些配置文件之间进行选择的方法。该文件夹将包含不同的配置文件以及根据环境在这些文件之间进行选择的机制。
在我们的教程中,我们将有两个环境,开发和生产。
对于这两个环境,我们会有两个不同。环境文件。以便从中读取键值对。env 文件并将它们添加到环境变量中,我们使用【dotenv】库。
对于开发,我们将使用 .env.development 配置文件。
对于生产,我们将使用。env** 文件。**
主机必须是 0.0.0.0,因为在本教程的后面,我们将为我们的示例应用程序创建一个 Docker 容器,Docker 要求主机=0.0.0.0。
根据环境读取不同配置的逻辑可以在 settings.py 文件中找到。
在第 5 行,我们根据环境文件环境变量的值设置了正确配置文件的路径。对于开发,这将具有值 “.env.development” ,在生产环境中,我们将此变量设置为*“。env”* 。这样,我们将根据不同的环境拥有不同的配置。
要设置环境变量 ENVIRONMENT_FILE 的值,在 VS 代码中,我们可以使用下面的 launch.json 文件:
我们的生产环境将是 Docker,因此在 Docker 文件中,我们将使用 ENV ENVIRONMENT_FILE= “来设置这个环境变量。env” 命令。
缓存目录
这个文件夹是由 Dash 的所谓 记忆 能力自动生成的。由于 Dash 的回调本质上是功能性的(它们不包含任何状态),所以很容易添加内存化缓存。记忆化在函数被调用后存储它的结果,如果用相同的参数调用该函数,则重用该结果。
为了设置这种记忆能力,我们必须定义缓存类型。
代替本地文件系统,我们也可以使用一个 Redis 缓存。
设置 Redis 超出了本教程的范围,因此我们将使用文件系统来缓存和重用调用之间的数据。
仪表板中的 MVC
在 MVC(模型-视图-控制器)架构设计模式的情况下,每个相互连接的组件都是为承担开发过程中的特定任务而构建的。
使用这种模式有多重好处😗***
- 快速并行开发——因为组件是解耦的,所以多人可以并行工作
- 单个模型的多个视图
- 稳定的业务逻辑 —很明显,我们对视图做了更多的修改(改变颜色、位置等)。)而不是在业务逻辑上,并且改变视图不会影响业务层
- 易于重复使用、维护和测试组件
- 它通过将应用程序分成独立的(MVC)单元来避免复杂性
- 它提供了一个清晰的关注点分离(SoC)。****
- ****逻辑分组将一个控制器上的相关动作组合在一起
MVC 模式最重要的缺点之一是,通常我们需要了解多种技术,因为视图和控制器+模型是使用不同的技术实现的(比如 React/Angular/Vue/等等)。为了风景和。net/Java/Python/Node/Go/等。用于后端)。如果您使用 Python 和 Dash,那么这个问题就解决了,您可以将 Python 用于视图和控制器+模型。
作者图片
正如你在上面的图片中看到的,在我们的项目中,每一页我们将有三个文件,我们有一个共同的布局**,正如我们在本文前面已经介绍过的。**
现在让我们看一个例子,我们如何创建一个使用 MVC 模式的页面。我们将创建一个示例页面,其中我们将根据 GDP 绘制不同国家的预期寿命图表。
型号示例
在上面的代码中,我们使用了 Dash 的内存化特性,因为我们不想多次读取相同的数据,所以我们缓存数据**,这种方式提高了我们应用程序的性能。**
模型不依赖于视图或控制器,它是解耦的,它的职责是处理数据,它不必处理用户界面或数据处理。
视图示例
视图的唯一职责是向用户显示数据。如您所见,它使用模型(dataframe)并定义 Html/Css/Js/其他标记语言元素来将数据呈现给用户。
正如您在上面的代码中看到的,我们的示例只使用了模型,并创建了表示元素**。它没有定义处理用户事件的逻辑,也不包含业务逻辑。**
控制器示例
控制器解释来自用户的鼠标和键盘输入,通知模型和视图适当地改变。
正如您在上面的例子中所看到的,update_figure 回调函数获取用户在滑块上选择的值作为输入(因此它处理用户事件),并基于从模型中读取的数据从视图中更新图形作为输出。因此,正如 MVC 模式所定义的,控制器是在我们从视图获得请求之后,在用我们的模型更新数据库之前,我们处理数据的部分。
按指定路线发送
为了在页面之间导航,我们需要定义路由规则。为此,在根文件夹中,我们创建了一个名为routes . py的文件。
正如我们在代码中看到的,我们定义了一个回调函数,它接受活动路径作为输入(当路径改变时,这个方法将被触发),输出将呈现在主布局的内容中(页面内容)。
为了呈现适当的页面,我们检查活动路径,并基于活动路径返回页面。
归档我们的 Dash 应用程序
第一步是定义 Dockerfile 。
为了减小图像的大小,我们使用了 slim 版本,我们还移除了缓存文件**(第 8 行)。**
该应用将由 Gunicorn 提供服务,因此我们必须为其定义配置(参见 gunicorn_config.py )。
在 docker 镜像中,主机必须是 0.0.0.0 否则无法访问。
Dash 实例在单独的 app.py 中定义,而运行 app 的入口点是 index.py 。这种分离是避免循环导入所必需的:包含回调定义的文件需要访问 Dash app 实例,然而,如果这是从 index.py 导入的,那么 index.py 的初始加载将最终要求它自己已经被导入,这是无法满足的。
对于 Gunicorn 来说,重要的是将服务器明确定义为 Falsk 应用**(第 12 行)。**
由于我们正在添加对 app.layout** 中不存在的元素的回调,Dash 将引发一个异常,警告我们可能做错了什么。我们可以通过设置suppress _ callback _ exceptions = True来忽略该异常(这在多页面应用上很正常)。**
注意,我们从环境中读取主机、端口、调试和 dev_tools_props_check 的值。****
对于 Gunicorn,我们还需要定义 Procfile 。
如您所见,我们从索引文件(index:server)运行服务器(这是我们在 app.py 中定义的 Falsk 应用程序)。
为了安装所有需要的依赖项,我们定义了 requirements.txt 文件。
我们还定义了一个 docker-compose.yml 文件,这样就很容易构建和运行我们的 docker 映像。
为了设置 V E R S I O N 和 VERSION 和 VERSION和TARGET 变量,我们定义了一个。docker-compose.yml** 旁边的 env 文件(在同一个文件夹中)**
为了构建和运行我们的应用程序,我们运行docker-compose build**,然后运行docker-compose up-d命令(在根文件夹中)。**
结论
在本教程中,我们描述了组织 Dash 应用的一种可能方式,我们展示了如何应用架构设计模式来引入关注点分离并编写干净且可维护的代码**。我们还介绍了如何使用多页面,如何使用Dash 的内存化来缓存和重用数据,如何为不同的环境使用不同的配置,并且我们描述了一种干净的方式为您的 Dash 应用设置和创建 Docker 容器。**
我真的很喜欢咖啡,因为它给了我写更多文章的能量。
如果你喜欢这篇文章,那么你可以请我喝杯咖啡来表达你的欣赏和支持!
****成为媒介上的作家:【https://czakozoltan08.medium.com/membership】T2
后续步骤
在我们的 下一篇教程 中,我们将呈现 Dash 更高级的概念,比如在回调之间共享数据、持久性、使用 Redis 的内存化、动态创建回调以及使用模式匹配回调。****
代码
本教程的代码可以在 这个 git 资源库 中找到。(喜欢我的代码请留个✩)
最后的话
感谢您阅读这篇长文!
如果你想要一些【愚蠢简单】的解释**,请跟我上媒!**
有一个正在进行的《愚蠢简单的 AI》系列**。前两篇可以在这里找到: SVM 和内核 SVMKNN 在 Python 。**
如果想要一些关于 Kubernetes 的“愚蠢简单”的讲解和教程**,可以查看我的 愚蠢简单 Kubernetes 系列。**
谢谢大家!
面向数据科学家的干净代码
第 3 部分—团队合作
如果你是一个热爱干净代码的人(坦白地说,你怎么可能不是呢?)和你在一个团队工作,这就是你的位置。在团队中工作时,每个人对如何编写代码都有自己的想法,对什么是干净的代码以及我们是否需要它都有自己的想法(但是你知道正确的答案,对吗?).
当我们是团队的一部分时,我们通常共享我们的项目、我们的存储库、我们的助手功能和工具。此外,我们可能会使用其他人的代码,或者在团队成员请病假的当天处理他代码中的 bug。意思是——我们需要对我们团队的所有代码感到舒服,而不仅仅是我们自己的代码。帮助你理解你团队的代码并感到舒服的最好方法之一是确保你们都使用相同的语言——干净的代码。无论您是团队领导还是团队成员,您都可以开始干净代码革命!
传播干净的码字
我们如何让干净的代码成为我们团队核心价值的一部分?我们如何创建长期有效的编码标准?
1.分享为什么
第一步,有点像这个博客系列的第一部分,是确保你的队友理解为什么作为数据科学家写干净的代码如此重要。许多数据科学家并没有计算机科学背景(我也是,罪名成立),所以他们可能不熟悉这个话题,或者觉得这与他们的工作无关。
理解动机对于编写干净的代码至关重要(一般来说,作为数据科学家也是如此)。如果人们不理解其中的原因,他们不仅会缺乏动力,而且也不会写出最干净的代码。编写干净的代码需要你理解代码背后的逻辑——例如,我们编写有意义的名字,以使我们的代码可读,并在阅读时创建准确的预期。不了解动机,就无法判断自己是否写了干净的代码。
2.符合您的期望
在现实生活中,这些努力大多因实施困难而失败。您或您的团队成员可能至少有一次团队编写干净代码的失败尝试。
确保这些努力不会白费的最好方法之一就是设定期望值。确保你的团队成员和经理明白这是一个漫长的过程(几个月),它往往会有暂时的倒退(例如,在发布日期前后),它会在短期内减缓开发。这需要时间和耐心,直到所有团队成员都一致,代码会慢慢变得越来越好。
只有当每个人都认同这件事的重要性,并且我们在这个过程中设定了我们的期望,我们才能开始工作。
3.写下指导方针
安排一次与团队的会议,然后写下你自己团队编写代码的指导方针。作为干净代码的大使,建议你准备好一些建议。然后,与您的团队一起检查这些建议,讨论并决定您想要采纳哪些建议。一份书面的、简短的、保存的、共享的文件会让每个人都遵守这些指导方针,并使他们在需要提醒时能够回头阅读。另外,当一个新的团队成员加入进来时,这是一个添加到你的入职计划中的很好的文档。只要保证不要太长(不超过 5 页),分成有意义的主题,用项目符号写出来,就不会变得难读了。
4.建议循环编码会话
您可能会发现与您的团队的这个指导方针会议太短了,无法将您所想的所有干净代码实践都包含进去。或者你可能会发现你想讨论其他日常协议,比如使用 Git、模型版本控制、使用笔记本等等。这就是为什么我建议把这个会议变成一个定期的会议。不一定是一周一次,可以是隔周一次,也可以是一个月一次。但是有一个讨论代码协议的地方是你可以给你的团队的一份大礼。如果你碰巧没有话题了(不现实,我知道),你总是可以抓住一段旧的、杂乱的代码,作为一个团队一起重构它。你会惊讶于它有多有趣,而且它可以引出更多的问题来讨论。
5.代码审查
另一种确保你的团队编写出最整洁、最干净的代码的常见方法是代码评审。检查彼此的代码,确保它们遵循你心中的准则,并讨论任何不匹配的地方。这个过程对于确保你们仍然保持一致是至关重要的。这些代码审查还可能导致:
- 如有必要,更新指南文件
- 在你的一个循环代码会话中讨论经常出现的错误
并不是你写的每一段代码都需要经过代码评审过程(当然取决于你团队的政策)。如果你觉得,作为一个团队,代码评审占用了你太多的时间,你可以选择只对你的代码库的更重要的部分进行代码评审,而不是每次更新的时候。与其有一个强制性的、负担沉重的过程,不如执行更少的代码审查,让它们变得有意义(实际上从中学习)。
顺便说一句,如果你想确保你的代码是真正干净和可读的,建议只需将它发送给你的同事(使用拉请求或任何其他你希望的方法),而无需任何事先的对话。不要告诉他们去哪里看,你的代码做什么,你希望改进哪一部分——让他们来判断。这将使您的代码审查更加客观,并且符合它的目的——检查您的代码是否对任何人、任何时间都是可读的。
6.结对编程
“结对编程是一种开发技术,其中两个程序员在一个工作站一起工作。一个是驱动程序,编写代码,而另一个是观察者或导航者,在输入代码时检查每一行代码。这两个程序员经常交换角色”。(维基百科)
虽然结对编程乍看起来非常耗时(因为两个团队成员在一个任务上工作),但它也是提高您的编码技能的一个好方法。当你和身边的同事一起编码时,他们会不断提出你没有想到的问题,丰富你干净的编码思路。偶尔尝试将结对编程融入到工作流程中,你不会后悔的。
总而言之,如果干净的代码对你很重要,你应该让它成为你团队的日常习惯。解释的重要性,确定的指导方针,尝试安排一个定期会议,并确保你们团队有足够的代码评审。如果你有更多的想法,请与我分享,并让我知道它如何为您的团队工作。祝你好运!
面向数据科学家的干净代码
萨曼莎·加德斯在 Unsplash 上的照片
第 1 部分——动机
你好,如果你是一名数据科学家,并且关心你的代码,那么你来对地方了。这是为数据科学家撰写的干净代码系列文章的第一部分:
第一部分——这是你目前正在阅读的内容。在这一部分,我将分享为什么我认为把你的时间和精力投入到编写干净的代码中是很重要的。
第 2 部分——实施的实用技巧。
我将首先解释什么是干净的代码,以及为什么它对于数据科学家来说是至关重要的实践。
什么是干净代码?
干净的代码是人们可读的代码,而不仅仅是编译器可读的代码。很容易改变和维护。我发现描述干净代码的最好方式是度量——“每分钟 WTFs”。也就是说,当你阅读一段对你来说是新的代码时,你会有多少次对自己说“哇哦?”:
图片由 Glen Lipka 在 commadot 上拍摄
当我们想到干净的代码时,我们可能会想——嗯,这是软件工程师和架构师需要担心的事情。所以实际上没有。请继续关注,因为我将试图让您相信干净的代码对于您作为数据科学家的工作是至关重要的,并提供一些实用的工具和实现它的技巧。
干净代码对数据科学家的重要性
一般来说,干净的代码是可读的,所以更容易调试和重构。这种方便使得代码易于维护,因此添加或更改代码没什么大不了的。这意味着生产率不会随着时间和复杂性的增加而下降,也不会随着我们添加更多的代码而下降。另外,干净的代码对错误更免疫。
作为数据科学家,我们做研究,我们学习,我们计划,但最终— 我们需要编写代码来实现这一切。如果你的代码对别人(和你自己)来说是错误的或者不可读的,即使是最聪明的算法也一定会出错。我们的算法和我们的代码一样好。
作为数据科学家,我们有时单独工作,有时团队工作。在这两种情况下,编写干净的代码至关重要:
- 在团队中,这将使更容易理解和重构团队成员的代码(这可能是干净代码的第一条规则——剧透警告)。这也将使新团队成员的开始和入职更加顺利(说心里话——因为我最近刚开始一个新职位)。
- 当你独自工作,或者独自完成大部分项目时, cleaner code 将帮助你在项目之间跳转,并以最少的“我在这里做了什么?”阶段。
鲍勃叔叔说“花在阅读和写作上的时间比远远超过 10 比 1”。在编写新代码的过程中,我们不断地阅读旧代码。编写新的代码并不容易,更不用说当你试图在弄清楚之前有什么的时候。
举个例子,看看这个简短的函数,试着理解它的作用:
现在看看这个函数,做着完全相同的事情:
你只看名字就有这么多信息,每一行都讲述了一个故事。现在想象一下你的代码只由混乱的代码组成——难道不需要更长的时间来理解那里发生了什么吗?更别说重构了。事实是——我只花了大约半分钟的时间来编写更好的代码。
数据科学家的干净代码之争
你可能会达到这一点,并说——“是的!我想写更干净的代码,为什么我一直没有这么做?”。事实是你可能有很好的理由。首先,它需要知识、实践和渴望。作为数据科学家,保持我们的清单像我们希望的那样整洁并不总是容易的。
高风险代码编写
根据我的经验,第一个原因是我们工作的“高风险”性质。意思是,当我们在脚本中编写第一行代码时,我们通常不知道它会发生什么——它会工作吗?会量产吗?我们还会用它吗?它有价值吗?
我们最终可能会在高风险的概念验证或一次性数据探索上花费大量时间。在这种情况下,编写最简洁耗时的代码可能不是正确的方法。但是,我们以粗略的方式编写的这个 POC 变成了一个实际的项目,它甚至进入了生产阶段,而它的代码却是一团糟!听起来很熟悉?我以前经常遇到这种事。
耗时的
所有代码编写者的共同点是时间方面。写干净的代码首先要花费更多的时间因为你在写任何一行代码之前都需要三思。我们总是被督促或鼓励快速完成工作,这可能会以牺牲代码为代价。
请记住——在匆忙中快速完成工作,可能会在你每天处理 bug 的时候伤害到你。你花在编写干净代码上的时间肯定会在 bug 上节省的时间上得到回报。
韵律学
斗争的另一个原因是衡量我们的方式。作为数据科学家,我们的雇主正在寻找结果 —准确的预测、有见地的数据发现、最新的技术。但是通常,没有人会看着引擎盖下的东西,只有当我们的代码整洁时,才标记这个季度的任务是成功的。代码基础设施和条件是团队的内部目标,很难让其他人意识到。
一些基本原则
如果你已经做到了这一步,并且想让你的代码更整洁,这里有一些我最喜欢的指导方针:
让你的代码质量与草稿水平相匹配
高风险的概念验证或数据探索可以从草稿开始,随着您的进展,您的代码会变得更加整洁
遵循标准惯例
这包括变量、函数和类的有意义的名字;常数而不是硬编码的字符串和整数;格式一致。
让你的功能变得强大
每个功能应该只做一件事。他们应该是短期的,没有副作用。
少即是多
你的代码应该尽可能的短,不带注释(除非他们告诉你为什么,但从不告诉你怎么做)。另外,确保删除注释掉的代码,因为它只会造成混乱。
保持简单
越简单越好,所以尽量降低复杂度。
童子军规则——让露营地比你发现时更干净
在团队中工作时——不要害怕改变别人的代码。单独工作时——在重新审视代码时,修改自己的代码。
报纸文章原则
脚本的顶部应该是最顶层的函数,越往下越详细。
如果你想知道更多,欢迎你来查看我的教程 的第二部分 ,它解释了如何实施这些指导方针,并列出了更多值得关注的内容。
改变从现在开始
任何人在任何时间编写干净的代码都是可能的。不管你是新人还是有经验的人,所需要的只是一件简单的事情——想要它。改变从内部开始。
在瑜伽中,有一种想法是想做点什么,即使你不一定会成功。你的意图才是最重要的。
我发现写代码也很类似。我们不一定要遵循所有的协议或做得完美。只要把它放在我们的脑海中,想要它,并思考它,就一定会让我们的代码变得更好。当然,熟能生巧。如果你不相信我,你自己试试看吧!
面向数据科学家的干净代码
JESHOOTS.COM在 Unsplash 上拍照
第 2 部分—方法
如果你在这里,这意味着你理解干净代码的重要性,并且你想掌握它。太神奇了。正如我在上一篇文章中提到的,你的意图和想法将会完成大部分工作。让我们添加一些实用的提示,你就可以开始了。
我想写干净的代码,但是怎么做呢?
如果你真的很认真,你想知道整个理论,我推荐阅读罗伯特·c·“鲍勃叔叔”马丁的**《干净代码:敏捷软件工艺手册》**。但是,如果您没有时间,或者您正在寻找更多面向数据科学的技巧,那也没关系。我将与你分享我的两分钱和我所学习和使用的东西。
但是,我必须警告你,从现在开始——这只是建议。没有对错之分(比如命名惯例),你只需要坚持某件事。另外,可能还有更多指导方针,我只是列出了最适合我的和我认为最方便的:
- 将您的代码质量与草稿级别相匹配
- 童子军规定——让露营地比你发现时更干净
- 保持简单
- 深思熟虑地命名变量、类和函数
- 确保你的函数很短,只做一件事,并且没有副作用
- 报纸文章——脚本的顶部应该是最顶层的函数,越往下越详细
- 少即是多——简短,无评论
将您的代码质量与草稿级别相匹配
正如我在上一篇文章中提到的,作为数据科学家,编写干净代码的挑战之一是,很多时候,我们开始一个项目(POC/数据探索),我们不确定它是否可行,以及我们是否会再次使用它。因此很难(当然也没有回报)投入很多时间为这些情况编写干净的代码。当它工作时,问题出现了,我们将它集成到我们的解决方案中,并在生产中发现这个写得很差的代码。
克服这个问题的方法——不要在探索性代码上投入太多,但要保持现实生活中的解决方案整洁,就是使代码质量与草稿水平相匹配。
我说的代码质量是什么意思?为了更好地定义代码质量,我把我的代码分成三个质量等级。
1.一团糟
想象一下你自己正在尝试一种你刚刚学到的新算法,检查它是否对你的数据有效,或者得到一个新的数据集并了解其中的内容。它将是快速而肮脏的——没有花哨的类,没有经过深思熟虑的变量名,您可能会将您的数据框命名为“df ”,而这一切都将被扔进一个脚本中,就像您在洗衣篮中的衣服一样。
2.相对可读的代码
这可能不是最棒的一段代码,但是其他人将能够理解发生了什么。变量名应该有意义,应该很好地划分成函数或类。它不会遵循所有的协议,可能不包括单元测试,但它是一个开始和重构的好地方。
3.最重要的是——代码的蒙娜丽莎
这是将授予你诺贝尔奖最伟大的代码工匠转变的代码。是的,我可能夸大了。这是生产级代码,所以它应该反映出你所得到的最好的东西,并且符合你希望遵循的所有准则,因为你会希望这段代码在未来尽可能地不受 bug 影响,并且最容易维护(无论是你自己还是你的团队成员)。
现在,考虑到这些质量水平,我根据任务及其成熟度匹配我的写作水平,并让它沿着这条路发展:
- 如果你正在编写一个低风险算法并且你对自己的方法有把握,那么从一个高水平的代码质量(介于 2 到 3 之间)开始。
- 如果您正在开始 POC、实施**高风险算法、**或进行可能是一次性的数据探索,从 1 开始,随着您的进步并了解什么可行,什么不可行,逐渐过渡到 2 。这样,你从低质量的代码开始,快速前进,尝试不同的方法、算法等。并在您开始看到潜力时改进您的代码。
- 出于任何其他目的,使用 2 并逐渐将其向上移动至 3 以准备释放。
- 最重要的规则是永远不要留下乱七八糟的代码!在进入之前,基本上进行重构,同时你还记得你试图实现什么以及如何实现。留下的杂乱代码在未来将是无用的,你可能不得不重写它。
一般准则
童子军规则
如果要我说出我的第一条规则,那就是童子军规则。童子军规定各州要保持营地比你发现时干净。或者在我们的例子中— 总是让代码比你发现的更好。这是什么意思?
- 在团队中工作时,不要害怕改变别人的代码。当你遇到一个写得不好的代码,不管是大的还是小的,需要改变的时候——改变它。你不需要请求许可或仔细考虑——这是你的职责。
- 独自工作时——重新审视自己的代码时修改它,不断重构和编辑以使它变得更好。
- 应用童子军规则将确保您的代码库始终处于最高水平。
- 当你在高质量测试和高测试覆盖率的代码上工作时,遵循童子军规则是最简单的。这样,你会感到安全,因为重构不会造成任何伤害。然而,缺乏测试不应该阻止你重构。你总是可以写一些测试来确保功能保持不变并进行重构。
保持简单
第二个重要的指导方针是“保持简单”。我曾经得到一个很好的建议— 写代码的时候,用一半的脑细胞去读代码。原因很明显——阅读代码要困难得多,所以我们在编写代码时应该以简单为目标。总是问自己——这能写得更简单吗?
命名规格
写代码就像讲故事,而你的名字选择是故事的一个重要部分。在编写代码时,确保你投入时间和精力在选择能透露尽可能多信息的名字上。
当你为你的函数/变量选择一个名字时,为了下一个阅读它的人(当然,可能是未来的你),这个名字创造了一个对那里将会有什么的期望。这些期望是两个因素的结果:
- 该名称的语义含义。我们期望函数“def load_training_set()”加载数据,但我们不会期望它过滤掉空值,对吗?我们期望变量“student”代表一个人,所以如果它是一个列表,那就令人困惑了。
- 格式——怎么写。如果标准约定是用大写字母书写常量值,并且您将数据框命名为“TRAINING_DATA ”,那么您的代码可读性更差,也更难解释。
当在代码中命名一个实例时,你应该总是考虑你创建的期望,并确保它匹配它的内容。如果不是,重命名它。
以下指南包含一般建议和具体约定。需要注意的是这种特殊的约定并不神圣,重要的是要有一个符合目的的一致约定——让你的代码更具可读性,符合读者的期望,并且有一个标准化的代码库。
通常,在整个代码中,请注意以下几点:
- 连贯的术语——每个概念使用一个术语(例如,不要同时使用“获取”和“取得”来描述同一个动作)。
- 避免将同一个词用于两个目的。
- 使用变量名作为一种方式来说明代码正在做什么。
常量变量
用常量变量代替硬编码的字符串或整数,写成全大写:
- 例如,使用“CLASSIFICATION_THRESHOLD=0.8”代替 0.8。
- 给它们一个描述性的名称。
- 将它们放在代码的顶部或外部设置文件中。
- 阅读、搜索、更改值和测试将变得更加容易。
变量名
您的变量名应该是:描述性的、可发音的、可搜索的、解释性的、揭示意图的:
- 比如不用“df”,用“train_set”。
- 对数组和列表使用复数,而不是命名它们的类型。例如,用“学生”代替“学生列表”。
- 使用单词,而不是缩写。比如用“learning_rate”代替“lr”。
- 长总比暧昧好。
类名
您的类名应该是:
- 名词或名词短语,如分类器、数据处理程序。
- 避免使用通用名称,如管理器、处理器。
功能名称
您的函数名应该:
- 说他们做什么。
- 包含动词或动词短语名称,如“evaluate_model”。
- 尽可能具体。比如用“load_training_data”代替“load”。
- 模仿你告诉你朋友这个函数是做什么的方式
- 如果它们仅供本课程内部使用,则以 _ 开头。比如“_load_training_data”。
功能
- 应该是小的——把一个大的功能分成几个小的。
- 应该比那个小。
- 经验法则——不要超过 20 行。当一个函数中有这么多行代码时,很难理解它做了什么。但是如果你把它分成更小的块,每个块都有一个有意义的名字,那就完全是另外一回事了。
- 如果你发现自己在函数中寻找几行代码——它们应该是自己的函数。
- 每个函数应该做一件事,而且只做一件事。它要么做点什么,要么回答点什么,而不是两者都做。
- 不要重复自己——这就是函数的作用。如果需要——将函数拆分成更小的方法,然后重用它们。
- 没有副作用——不要改变任何你不应该改变的东西。
- 当调用一个函数时,总是写下参数名和它们的值。这将使它更容易阅读和理解。
- 参数越少越好—尽量避免超过 3 个参数。
- 尽量用函数封装复杂的条件句。例如,不使用“if (timer.has_expired()和(timer.is_recurrent())”,而是使用“if should_be_deleted(timer)”
- 避免消极条件句。例如,不使用“if (not follow_up_needed())”,而是使用“if (follow_up_not_needed()”。
格式化
格式化是你设计代码的方式——它看起来不需要深入到名字和动作中。正确的格式有两个目的:
- 沟通——如果你的变量/函数名是口头沟通,那么格式化就是非口头部分。这说明了很多问题。如果您选择将两个函数一个接一个地放置,它可能会告诉读者它们是相关的,或者会一个接一个地运行。
- 专注——一旦格式化做得很好,代码看起来有条理,它释放你的思想去处理真正重要的东西——它的内容。看着视觉上混乱的代码会转移你对其含义的注意力。
以下是我尝试在代码中应用的一些格式准则:
- 概念之间的垂直开放性——每行代码只做一件事。
- 每一个空行都是一个视觉提示,可以识别一个新的独立的概念,所以要明智地使用它。
- 相关代码(和相关函数)应该垂直排列。
- 声明与其用法相近的变量。
报社负责人
我最喜欢的格式指南之一是报纸原则,它指出你的脚本应该从高级功能开始**,并向下滚动**深入细节。
- 源文件的最上面部分应该提供高级的概念和算法。
- 随着我们向下移动,细节应该增加,直到最后,我们在源文件中找到最低级别的函数和细节。
- 这就像读报纸一样——你从顶部的标题开始,然后是副标题,然后是全文和细节。
少即是多
尽量保持代码简洁:
- 简短简短——你的代码越短,你阅读和理解它的速度就越快。当然,不是以一团糟为代价。你仍然需要为每一个动作写一行,但是尽量避免不必要的动作。
- 删除注释掉的代码 —相信我,没有人会错过它。
争取零评论
你应该尽量在代码中没有注释——干净的代码不需要注释。
为什么?
- 代码应该在 90%以上的时间里自我解释——使用你的函数和变量名来解释,而不是注释。
- 我们经常在更新代码的时候忘记更新注释。
- 它增加了杂波。
如果您添加了评论:
- 不应该是关于你如何做某事,而应该是关于你为什么做这件事。
- 确保它是必要的,**非琐碎的,**而不是多余的。
- 使尽可能短。
- 随着代码的变化,删除过时的注释。
- 不要注释掉代码——只是删除它。
如果你想了解更多并看到一些真实的例子,我推荐这篇文章:
总而言之,只要记住作为一名数据科学家,你最好的算法只能和用写的代码一样好。写得好的算法将是(尽可能接近)防错的,并且易于维护。它所需要的只是你的良好意愿,一些实用的知识和技巧,这些你现在都有了。让我知道进展如何!
参考资料:
- “干净的代码:敏捷软件工艺手册”,作者罗伯特·c·“鲍勃叔叔”马丁。
- 7 2 个理由心理学将帮助你写出更好的代码作者莫兰·韦伯&乔纳森·阿维诺尔(演讲用希伯来语)
- 阿图罗·赫雷罗演讲中的干净代码幻灯片
使用 DataPrep 进行交互式清理、连接和可视化
适用于您的数据分析过程的一体化软件包
数据准备是任何数据专业人员都要做的第一步。无论是要对数据进行分析,还是要对机器学习模型的数据进行预处理,都需要准备好数据。
准备数据意味着您需要收集、清理和探索数据。为了完成我提到的所有活动,开发了一个名为 DataPrep 的 Python 包。这个包裹对我们有什么帮助?让我们一起来探索一下。
数据准备
DataPrep 是为准备数据而开发的 Python 包。这个包包含三个主要的 API 供我们使用,它们是:
- 数据探索(
dataprep.eda
) - 数据清理(
dataprep.clean
) - 数据收集(
dataprep.connector
)
DataPrep 包被设计成具有快速的数据浏览,并且与 Pandas 和 Dask DataFrame 对象配合良好。为了探索 DataPrep 功能,我们需要首先安装这个包。
pip install -U dataprep
在我们安装完这个包之后,让我们使用 API 来准备我们的数据。
数据准备探索
DataPrep 让我们用一行代码创建一个交互式的概要报告。这个报告对象是一个 HTML 对象,从我们的笔记本中分离出来,有许多探索选项。让我们用示例数据来尝试一下 API。
from dataprep.datasets import load_dataset
from dataprep.eda import create_report
df = load_dataset("titanic")
df.head()
作者图片
我们将使用泰坦尼克号样本数据集作为我们的数据。加载完数据后,我们将使用create_report
函数来生成交互式报告。
create_report(df).show_browser()
作者 GIF
正如我们在上面的 GIF 中看到的,API 创建了一个很好的交互式报告供我们探索。让我们试着逐一剖析这些信息。
概览选项卡(按作者分类的图像)
从 overview 选项卡中,我们可以看到数据集的所有概述信息。我们可以获得的信息包括缺失数据数和百分比、重复数据、变量数据类型以及每个变量的详细信息。
变量选项卡(按作者分类的图像)
variables 选项卡为我们提供了数据集中每个变量的详细信息。几乎所有您需要的信息都是可用的,例如,唯一、缺失数据、分位数和描述性统计、分布和正态性。
交互选项卡(按作者分类的图片)
interactions 选项卡将从两个数字变量创建一个散点图。我们可以自己设置 X 轴和 Y 轴,这让我们可以控制如何可视化它。
相关性选项卡(按作者分类的图片)
“相关性”选项卡为我们提供了数值之间的统计相关性计算热图。目前,我们可以使用三种计算方法——Pearson、Spearman 和 KendallTau。
缺失值选项卡(按作者排序的图像)
“缺失值”选项卡为我们提供了关于选项卡中缺失值的所有详细信息。我们可以选择条形图、光谱图、热图和树状图来充分探索缺失值信息。
数据准备清洗
DataPrep 清理 API 集合提供了 140 多个 API 来清理和验证我们的数据帧。例如,我们可以使用的 API 有:
还有很多。我们可以尝试的功能太多了,本文不可能涵盖所有的 API。如果你有兴趣,可以在这里查阅文档。
让我们以 Titanic 数据集为例,尝试一下列标题清理功能。
from dataprep.clean import clean_headers
clean_headers(df, case = 'const').head()
作者图片
使用“Const”的情况下,我们将结束所有大写的列名。如果我们把这个案例换成“骆驼”
clean_headers(df, case = 'camel').head()
作者图片
结果是除“sibSp”列之外的所有较低的列名,在“sibSp”列中,它们的列名中有两个单词。
如果你想要一个完整干净的数据框架,我们可以使用来自 DataPrep 的clean_df
API。这个 API 有两个输出—推断的数据类型和清理的数据帧。
from dataprep.clean import clean_df
inferred_dtypes, cleaned_df = clean_df(df)
作者图片
有许多参数可以在 API 中使用。我建议您阅读所有文档,看看哪些参数适合您的数据准备目的。
DataPrep 集合
DataPrep 收集 API 用于从数据库或 Web API 收集数据。如果我们可以访问数据库,比如 MySQL 或 PostgreSQL,您可以用 DataPrep API 连接它,但是也可以使用 DataPrep connect
API 访问公共 API。
如果您想从 web 上收集数据,仍然需要 API 代码,但是一切都被简化了。如果你想了解更多关于集合 API 的内容,你可以在这里阅读全部。
结论
DataPrep 是一个单行 Python 包,用于清理、连接和浏览您拥有的数据集。该功能包括:
- 数据探索(
dataprep.eda
) - 数据清理(
dataprep.clean
) - 数据收集(
dataprep.connector
)
希望有帮助!
如果您喜欢我的内容,并希望获得更多关于数据或数据科学家日常生活的深入知识,请考虑在此订阅我的简讯。
如果您不是作为中等会员认购,请考虑通过 我的推荐 进行认购。
清洁能源开发商需要兑现他们的数据
不采用大数据工具将意味着能源行业的死亡
太阳能、风能和储能发电厂的公用事业规模开发商在一个利润微薄、资本支出极高且时间表漫长的行业中竞争。每一个新的发展都是一场赌博,只有在数年和数百万美元后才显示出它的可能性。鉴于该行业的历史性质,大多数用于评估这些发展的工具都是 20 世纪的人工制品。然而,“清洁技术”品牌吸引了大数据公司的注意,这些公司以强大的竞争优势进入了数据丰富的行业。如果保守的能源公司不采用最佳数据实践,他们高昂的开发成本将扼杀未来项目成功的任何希望。
是时候让开发公司意识到他们是大数据公司了。
数据驱动发展
开发的目标是通过最小化不确定性来最小化项目风险。一个完美开发的项目将确切地知道它将产生多少能量,确切地知道它的成本将是多少(以及被完全许可和准备建造)。为了降低风险,开发人员使用“数据驱动开发”它们会产生数量惊人的数据,从历史风速和辐照度等传统格式到工程报告或许可文件等组织性较差的格式。来自所有这些来源的数据在决策者的头脑中汇集,然后他们预测项目的经济效益。这种预测必须有足够的证据来支持它,以说服其他关键的利益相关者(如希望贷款给项目的金融家或希望购买项目的公用事业公司)。开发商通过以尽可能低的成本充分降低经济项目的风险而获得成功。
减少开发费用通常意味着产生更少的数据(因为你为更少的研究付费)。数据越少,项目的风险就越大。因此,开发公司削减项目成本的效率是有限的,他们必须寻求建筑、技术和性能方面的创新来获得优势。当然,除非你有办法在不需要获得更多数据的情况下,为关键涉众减少足够的项目不确定性。如果更少的数据并不意味着更高的项目风险呢?如果您可以从现有数据中获得更多见解,会怎么样?
大数据创新
在“大数据”的世界中,存在可以从我们生成的数据中提取出无数奇迹的工具,丰富的数据源就像等待数据科学“49 人组”挖掘的金矿很难找到比项目开发更丰富的未开发数据来源。然而,这些数据中的大部分都没有被传统的开发者使用。这里有一个例子:
示例开发
比方说,一家开发商正在考虑将其“Bertrand”太阳能发电场竞标到一些即将到来的提案请求中(RFP:当一家公司有兴趣从太阳能发电场购买电力时,要求开发商向他们发送项目以供选择)。为了决定是否支付投标这个项目的价格,以及如果有任何研究,它应该提前开始以进一步降低开发风险,开发商举行“思想会议”这些决策涉及许多因素,为了准备这次会议,公司的不同部门被要求总结他们对项目开发的每个关键部分的发现。
对于一个这样的组件,一名工程师阅读了一份初步的岩土工程研究,以评估该场地的土壤将大幅增加建筑成本的风险。这位工程师必须浏览几百页的文档,从中提取关键信息,然后将这些信息提交给她的经理,经理再将这些信息传递给她的经理,以此类推,直到在会议上提交。生成这份文件的研究花费了数十万美元,耗时数月才完成。所有这些时间和费用都转化为决策者在做出关于 RFP 的决定之前在许多其他要点中消化的一条知识。
如果你曾经有过认知偏差,或者你曾经玩过“电话”游戏,你知道在这个过程中做出的决定会受到很多“噪音”的影响,干扰了正确的“信号”。决策者无法对项目做出公正的决策,所提供的数据可能会遗漏它所保证的细微差别,而且整个过程既及时又昂贵。与此同时,这家开发公司拥有大量相关数据,但这些数据并没有在这个决策中使用。精通数据的公司面临所有这些问题。
更好的发展
为了降低这个项目的开发成本,公司可以引入附近项目已经生成的所有数据,以及相关的公开可用数据集。为了减少决策时的不确定性和偏见,机器学习模型可以提供数据驱动的建议。为了减少误解,项目的所有数据都可以在一个位置方便地访问和汇总。
下面的仪表板提供了一个排名系统,用于将一个项目与附近的其他开发项目进行比较,显示项目时间表和即将交付的成果,甚至估计该项目赢得即将到来的 RFP 的概率。决策者可以利用这些信息来了解应该启动哪些开发任务,以及应该投标或回避哪些 RFP。最重要的是,通过项目开发周期生成的每个数据点都被整合到影响整个开发组合的数据池中,减少了对未来研究的需求。
仪表板示例(按作者)
这个例子只是触及了大数据驱动的开发者所能获得的东西的表面。那些还运营发电厂的公司可以将运营和发展数据结合起来,以获得更大的洞察力,而参与电力市场竞标的独立发电商提供了另一个丰富的数据源。
结论
目前,有效的数据管理和分析为当前的公用事业规模开发商提供了巨大的竞争优势,但这将是短暂的,因为改进的数据平台的低成本和高收益迫切需要快速采用。迟早,适当的数据处理将成为行业的必需品。在已经成为 21 世纪能源行业标志的情况下,那些适应缓慢的公司将很快倒闭。
如果你是一家开发公司,是时候认识到你是一家大数据公司了。给自己提供成功所需的工具,培训能源专家,让他们的数据为自己服务。关于数据科学如何让您的团队受益的另一个例子,您可能有兴趣阅读这篇文章。你可以在这里找到提升你的团队的免费资源路线图。至少,和你的团队讨论一下如何更好地使用你的数据。你会对唾手可得的水果感到惊讶。
用 Python 中的傅里叶变换清除数据噪声
用傅立叶变换清理时间序列数据用最短的 Python 代码
来自维基的约瑟夫·傅立叶
傅立叶变换是一种从完全不同的角度查看数据的强大方法:**从时域到频域。**但是这个强大的运算看起来很可怕,因为它的数学方程式。
将时域波转换到频域;
从时域到频域,也称为前向离散傅立叶变换或 DFT
下图很好地说明了傅立叶变换:将一个复杂的波分解成许多规则的正弦波。
图片来自维基
下面是完整的动画,解释了将时域波形数据转换到频域视图时会发生什么。
来自 wiki 的 Gif
我们可以很容易地操纵频域中的数据,例如:去除噪声波。之后,我们可以使用这个逆方程将频域数据转换回时域波:
从频域到时域的逆变换,也称为离散傅里叶逆变换或 IDFT
让我们暂时忽略 FT 方程的复杂性。假设我们已经完全理解了数学方程的含义,让我们使用傅立叶变换在 Python 中做一些实际的工作。
理解任何事物的最好方法是使用它,就像学习游泳的最好方法是潜入水中变湿。
将干净的数据与噪声混合
创建两个正弦波并将它们合并成一个正弦波,然后有目的地用从np.random.randn(len(t))
生成的数据污染干净的波。
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['figure.figsize'] = [16,10]
plt.rcParams.update({'font.size':18})#Create a simple signal with two frequencies
data_step = 0.001
t = np.arange(*start*=0,*stop*=1,*step*=data_step)
**f_clean = np.sin(2*np.pi*50*t) + np.sin(2*np.pi*120*t)
f_noise = f_clean + 2.5*np.random.randn(len(t))**plt.plot(t,f_noise,*color*='c',*Linewidth*=1.5,*label*='Noisy')
plt.plot(t,f_clean,*color*='k',*Linewidth*=2,*label*='Clean')
plt.legend()
(将两个信号组合形成第三个信号也称为卷积或信号卷积。这是另一个有趣的主题,目前广泛用于神经网络模型中)
我们有带噪音的波浪,黑色是我们想要的波浪,绿色线条是噪音。
干净的海浪夹杂着噪音,作者安德鲁·朱
如果我隐藏图表中的颜色,我们几乎无法从干净的数据中分离出噪音。傅立叶变换在这里可以有所帮助,我们需要做的就是将数据转换到另一个角度,从时间视图(x 轴)到频率视图(x 轴将是波频率)。
从时域到频域的变换
可以用numpy.fft
或者scipy.fft
。我发现scipy.fft
非常方便,功能齐全。这里我将在本文中使用scipy.fft
,但是如果您想要使用其他模块,这是您的选择,或者您甚至可以基于我在开始时给出的公式构建一个您自己的模块(见后面的代码)。
from scipy.fft import rfft,rfftfreq
n = len(t)
yf = rfft(f_noise)
xf = rfftfreq(n,data_step)
plt.plot(xf,np.abs(yf))
- 在代码中,我使用了
rfft
而不是fft
。r 的意思是减少(我想)以便只计算正频率。所有负镜像频率将被忽略。而且速度也更快。更多讨论见此处。 - 来自
rfft
函数的yf
结果是一个复数,形式类似于a+bj
。np.abs()
函数将计算复数的√(a + b)。
这是我们原始波的神奇的频域视图。x 轴代表频率。
《频域数据》,作者 Andrew Zhu
在时域看起来复杂的东西现在被转换成非常简单的频域数据。两个峰值代表两个正弦波的频率。一波是 50 赫兹,另一波是 120 赫兹。再回头看看产生正弦波的代码。
f_clean = np.sin(2*np.pi***50***t) + np.sin(2*np.pi***120***t)
其他频率都是噪声,下一步会很容易去除。
去除噪声频率
在 Numpy 的帮助下,我们可以很容易地将这些频率数据设置为 0,除了 50Hz 和 120Hz。
yf_abs = np.abs(yf)
**indices = yf_abs>300 # filter out those value under 300**
**yf_clean = indices * yf # noise frequency will be set to 0**
plt.plot(xf,np.abs(yf_clean))
现在,所有的噪音都被消除了。
清理噪音,作者安德鲁·朱
逆回到时域数据
代码:
from scipy.fft import irfft
**new_f_clean = irfft(yf_clean)**
plt.plot(t,new_f_clean)
plt.ylim(-6,8)
结果表明,所有的噪声波都被消除了。
洁净的波浪,作者安德鲁·朱
转变是如何进行的
回到转换等式:
从时域到频域,也称为前向离散傅立叶变换或 DFT
原始时域信号由小写 x 表示。 x[n] 表示 n 位置(时间)的时域数据点。频域中的数据点由大写字母 X 表示。*X【k】*表示频率为 k 时的值
比方说,我们有 10 个数据点。
x = np.random.random(10)
N
应该是 10,所以n
的范围是 0 到 9,10 个数据点。k
代表频率#,其范围是 0 到 9,为什么?极端情况是每个数据点代表一个独立的正弦波。
n 和 k 的范围。
在传统的编程语言中,需要两个用于循环,一个用于k
,另一个用于n
。在 Python 中,对于循环,可以用 0 显式对操作进行矢量化(表达式 Python)。
而且 Python 对复数的原生支持也很牛逼。让我们建立傅立叶变换函数。
import numpy as np
from scipy.fft import fft
*def* DFT_slow(*x*):
*x* = np.asarray(*x*, *dtype*=float)# ensure the data type
N = *x*.shape[0] # get the x array length
n = np.arange(N) # 1d array
k = n.reshape((N, 1)) # 2d array, 10 x 1, aka column array
M = np.exp(-2*j* * np.pi * k * n / N)
return np.dot(M, *x*) # [a,b] . [c,d] = ac + bd, it is a sum
x = np.random.random(1024)
np.allclose(DFT_slow(x), fft(x))
与来自numpy
或scipy
的函数相比,这个函数相对较慢,但对于理解 FFT 函数如何工作已经足够了。为了更快地实现,请阅读杰克的理解 FFT 算法。
进一步的思考——不是总结
傅立叶变换所表达的思想是如此深刻。它提醒我,世界可能不是你看到的那样,你生活的世界可能有一个完全不同的新面貌,只有通过某种变换才能看到,比如傅立叶变换。
不仅可以转换声音数据,还可以转换图像、视频、电磁波,甚至股票交易数据( Kondratiev 波)。
傅立叶变换也可以用波生成圆来解释。
来自维基
大圈是我们的国家,我们的时代。我们作为一个个体是一个小小的核心圈子。没有那个驱动一切的大圈子,我们真的是做不到太多。
工业革命发生在英国而不是其他国家,不仅仅是因为采用了蒸汽机,还有许多其他原因。— 英国为什么先工业化。就是那个时候只存在于英格兰的大圈。
如果你很富有或者在某些时候、某些地方非常成功,这可能不全是因为你自己的优点,而很大程度上是因为你的国家、你周围的人或者你工作的好公司。没有那些驱使你前进的大圈子,你可能无法实现你现在所拥有的。
我对傅立叶变换了解得越多,我就越对约瑟夫·傅立叶感到惊讶,他在 1822 年提出了这个令人难以置信的方程。他永远也不会知道,在 21 世纪,他的作品现在到处都在使用。
附录—四种傅立叶变换
本文中提到的所有傅立叶变换都是指离散傅立叶变换。
《傅立叶变换的四种类型》,作者安德鲁·朱
当你坐在计算机前,试图用傅立叶变换做一些事情时,你只会使用 DFT——本文讨论的变换。如果你决定投身于数学沼泽,这里有两本推荐阅读的书:
2.《数字信号处理》一书的第 8 章**——作者史蒂文·W·史密斯他们提供了在线链接: DSP Ch8**
参考
- 傅立叶变换互动指南:https://better explained . com/articles/an-Interactive-Guide-To-The-Fourier-Transform/
- 用 scipy.fft 进行傅立叶变换:Python 信号处理:https://realpython.com/python-scipy-fft/
- 了解 FFT 算法:http://jakevdp . github . io/blog/2013/08/28/understanding-the-FFT/
- 频域和傅立叶变换:https://www . Princeton . edu/~ cuff/ele 201/kulkarni _ text/frequency . pdf
- 用 FFT 去噪数据【Python】:https://www.youtube.com/watch?v=s2K1JfNR7Sc&ab _ channel = Steve brunton
- FFT 算法——简单的一步一步来:https://www.youtube.com/watch?v=htCj9exbGo0&ab _ channel = Simon Xu
如果你有任何问题,请留下评论,我会尽力回答,如果你发现了错误,不要犹豫,把它们标记出来。你们的阅读和支持是驱使我继续多写的大圈子。谢谢你。
带有匈牙利符号的更清晰的代码
我最近开发的微生物组数据产品的 JSON 配置。记下文件名。
超越英语用语的限制,更好地组织和更快地解释你的工作。
前言
你还记得你什么时候学会了如何格式化日历日期进行字母数字排序吗?你或者有月首美国格式的日期,例如09–11–1991,或者有日首欧洲格式的日期,例如11–09–1991,并且你意识到排序不起作用。日期排序需要首先按年份进行,然后按月份进行,最后按月份进行,例如1991–09–11。
或者——您还记得您什么时候学习了更清晰的变量、方法和类命名的更好实践吗?也许当你看着别人的代码,却不知道变量 isOvrLn 是什么意思的时候,或者不知道名为 mkZro()的函数是什么意思的时候,就会发生这种情况;
哈!我本能地用分号结束了最后一句话。
无论如何,我在这里想说的是,从你认识到这些更好的编码实践的那一刻起,一切都变了。有时这个教训会产生如此大的影响,你不得不回去重构遗留代码,以帮助未来的读者和避免潜在的尴尬。
另一个类似的顿悟最近又发生在我身上——在我 20 年的编程生涯中。
匈牙利符号
这里有一个维基百科链接来复习一下——这是一个相对古老的概念,可以归结为你可能想用代码命名一个东西——例如变量、类、接口等等。—包括关于它是什么的信息,即它的类型。
匈牙利符号仍然有争议,所以在这篇文章中,我将关注我的一个特殊用例,以及为什么我认为它有效。
微生物组数据产品示例
在这里,我使用低代码 JSON 模板从源数据文件定义一个 Tag.bio 数据产品。您应该能够在不了解 Tag.bio 语法的情况下理解这些示例。
这个顶层文件 config.json 概述了创建数据模型所需的所有文件和函数。
注意我们是如何使用文件路径来引用嵌套代码的——包含表对象的文件以表 _ 为前缀,包含解析器对象的文件以解析器 _ 为前缀。
您可能会注意到,表目标文件已经位于一个名为表/ 的文件夹中,而解析器目标文件已经位于一个名为解析器/ 的文件夹中。那么,为什么会有冗余呢?
因为我并不总是通过文件的完整路径来查看文件。很多时候,我会在编辑器中将文件名单独作为一个标签来看。
这里,我们使用类型前缀来区分同一个gene familiesaspect的表目标文件和解析器目标文件。
此外,当查看我的 IDE 的资源管理器视图中列出的许多文件时,我希望直接从文件名中快速了解每个文件的用途,而不必通过视觉扫描来识别父文件夹。
为什么使用前缀和后缀?
这是它有助于远离英语的地方。在英语中,我们把形容词放在名词前面,例如大河。在许多其他语言中,形容词放在名词之后,例如西班牙语中的 el rio grande 。
鉴于本文中的代码——以及绝大多数已编写的代码——都是英文的,为什么我们决定将文件命名为table _ gene families . JSON而不是gene families _ table . JSON?这不是显而易见的。当我和我的同事谈到这些对象时,我们肯定会说“genefamilies 表”,而不是“genefamilies 表”。
我使用匈牙利符号前缀的理由——在这个特定的例子中是*——与 IDE 以及文件名如何组织、显示和排序有关。*
当您打开许多编辑器选项卡时,文件名通常会被截断。通过使用前缀,名称中最重要的部分——在本例中是类型——最后被删除。
在构建文件夹层次结构时,我们已经声明了每个对象的类型比方面更重要——即解析器被放入解析器/ 文件夹,而表被放入表/ 文件夹。
前缀不仅清楚地表明每个文件包含什么类型的对象,而且前缀的冗余性——在左侧水平对齐——也清楚地表明我正在查看哪个父文件夹。
确定我在解析器/ 文件夹中并查看解析器对象后,我可以沿着最右边的信息“列”垂直扫描,以确定我需要打开的特定文件——或者快速了解该文件夹中所有文件的范围。
值得注意的是,在 Tag.bio 中,我们已经为我们的 JSON 文件建立了一个约定,首先围绕类型,其次围绕方面进行组织。我们更喜欢嵌套更少的更宽的文件夹,并且在相同类型的文件之间有许多有用的代码复制/粘贴。开发人员更频繁地在多个表或多个解析器上工作,而在单个方面内工作的频率较低——例如 genefamilies 。
有时候后缀会更好
切换到一个不同的代码示例——因为我已经注意到在 Web 应用程序世界中,像 Angular 这样的框架已经迁移到代码主要由方面组织而不是由类型组织的惯例。
在这个来自 Tag.bio web 应用程序(Angular)代码库的示例中,我们围绕用户页面方面对文件进行分组,包括同一文件夹中不同类型的文件。在这里,带有后缀的匈牙利符号对于类型——即文件扩展名——是一个更好的选择。
Angular 社区意识到,开发人员经常处理与特定方面相关的文件,例如用户.组件,而较少处理与特定类型相关的文件,例如**。css** 。
仔细想想,文件扩展名可能是匈牙利符号中争议最小的形式。
这一切不是很明显吗?
那么,检查你的代码库,自己决定文件名是否足够清楚。另一个开发人员会仅仅通过阅读文件名来理解每个文件的用途吗?
感谢阅读!
用“看门人”包清理和探索数据
探索 clean_names()之外的功能
照片由 Pexels 的 Pixabay 拍摄
看门人包
《T2》看门人套装可在 CRAN 上下载,由山姆·菲尔克、比尔·丹尼、克里斯·海德、瑞安·奈特、马尔特·格罗塞和乔纳森·扎德拉创作。虽然可以说最出名的是它非常有用的 clean_names()函数(我将在本文的后面介绍),但是看门人包有大量的函数可以简化数据清理和探索。该软件包旨在与 tidyverse 兼容,因此可以无缝集成到大多数数据准备工作流中。我所参考的看门人软件包功能的有用概述可以在这里和这里找到。此外,以下引用的所有数据和代码都可以在GitHub repo中访问。
GNIS 数据
我们将在本教程中使用的数据来自地理名称信息系统(GNIS),这是一个由美国地名委员会创建的数据库。这个数据集出现在 Jeremy Singer-Vine 的 Data is Plural 时事通讯中,本质上是一个国内地名的可搜索目录。
为了下载数据,我们首先需要导航到查询表单:
我选择使用代表马萨诸塞州伯克郡地名的数据,这个地区包括我的家乡。为了创建该搜索,从州下拉菜单中选择“马萨诸塞州”,从县下拉菜单中选择“伯克希尔”。然后,点击“发送查询”按钮:
查询结果将如下所示。在结果表下方,单击“另存为管道”|“分隔文件”将数据保存到本地:
数据应该开始自动下载,并将出现在一个. csv 文件中,其特征由“|”分隔:
将这个 csv 文件保存到新 R 项目的“data”文件夹中。让我们将数据放入 R 中,将这些列分离出来,并执行一点修改,以便于我们的看门人包探索。首先,在一个新的 R Markdown 文件中加载 tidyverse 和看门人包。使用 read.csv()函数将数据作为“place_names”加载:
library(tidyverse)
library(janitor)place_names = read.csv("./data/GNIS Query Result.csv")
数据看起来应该和它在 Excel 中的样子差不多,一个巨大的列包含了我们所有的数据:
让我们来研究一下这个。首先,我们将名称“columns”分配给这个单独的列,以避免在代码中包含像默认列名称那样混乱和冗长的内容。接下来,我们使用 separate()函数将这一列分成它的所有组成部分。然后,我们将数据过滤到伯克希尔县,随着对数据的进一步检查,很明显,我们的伯克希尔县查询中包含了该县以外的一些条目。然后,我们在 mutate()步骤中稍微修改一下数据,以便以后整理它。str_replace()用于将 ID“598673”替换为数据集中已经存在的 ID 号“598712”,以便创建一个重复的 ID。最后,使用 NAs 在每行中创建了一个名为“extra_column”的额外列:
colnames(place_names) = "columns"place_names =
place_names %>%
separate(columns, c("Feature Name", "ID", "Class", "County", "State", "Latitude", "Longitude", "Ele(ft)", "Map", "BGN Date", "Entry Date"), sep = "[|]") %>%
filter(County == "Berkshire") %>%
mutate(
ID = str_replace(ID, "598673", "598712"),
extra_column = NA
)
在继续之前,让我们快速创建第二个名为“non_ma_names”的数据集,其中包含实际上并非来自伯克希尔县的条目。我们再次读入“GNIS 查询结果. csv”文件并分离出列名。然后,我们应用看门人包中的 clean_names()函数,这将在下一节中深入讨论。最后,我们在 mutate 步骤中使用 as.numeric()和 as.factor()将 ele_ft 变量转换为数字变量,将 map 变量转换为因子:
non_ma_names = read.csv("./data/GNIS Query Result.csv")colnames(non_ma_names) = "columns"non_ma_names =
non_ma_names %>%
separate(columns, c("Feature Name", "ID", "Class", "County", "State", "Latitude", "Longitude", "Ele(ft)", "Map", "BGN Date", "Entry Date"), sep = "[|]") %>%
filter(County != "Berkshire") %>%
clean_names() %>%
mutate(
ele_ft = as.numeric(ele_ft),
map = as.factor(map)
)
现在让我们看看看门人能做什么!
使用看门人
行名()
您可能已经收到了大量的数据文件,可能在。xlsx 表单,在实际数据开始之前,电子表格顶部有几行。这些行可以是空白的,也可以填充信息和公司徽标。当您将这些数据加载到 R 中时,这些前导行的内容可能会自动成为您的列标题和第一行。看门人包中的 row_to_names()函数允许您指出数据框中的哪一行包含实际的列名,并删除该行之前的所有内容。对我们来说很方便的是,GNIS 数据已经在正确的位置有了列名。不管怎样,让我们试一下,假设列名实际上在第三行。
我们使用 row_to_names()函数创建一个名为“test_names”的新数据帧。row_to_names()函数采用以下参数:数据源、列名应该来自的行号、是否应该从数据中删除该行以及是否应该从数据中删除上面的行:
test_names = row_to_names(place_names, 3, remove_row = TRUE, remove_rows_above = TRUE)
我们可以看到,第 3 行的信息现在构成了我们的列名。该行及其上方的行已从数据框中移除,我们可以看到数据现在从第 4 行开始:
虽然这个函数不是清理 GNIS 数据所必需的,但是它对于其他数据集来说肯定是很方便的!
清理名称()
这个函数是我几乎每次将新数据集加载到 r 中时都会用到的函数。如果您还没有使用这个函数,我强烈建议将它合并到您的工作流中。这是这个包中最受欢迎的功能,因为它非常有用!
tidyverse style guide 推荐对象名和列名使用 snake case(由下划线分隔的单词,如 this)。让我们花一点时间回顾一下我们的列名。有各种大写字母和空格(如“功能名称”、“BGN 日期”)以及符号(“Ele(ft)”)。clean_names()函数将把所有这些转换成 snake case。
使用 clean_names()非常简单,如下所示:
place_names = clean_names(place_names)ORplace_names =
place_names %>%
clean_names()
正如您在下面看到的,这个函数处理了数据集中出现的各种混乱的列名。现在一切看起来干净整洁:
移除 _empty()
remove_empty()函数,顾名思义,删除空的列。在准备数据时,我们在“place_names”数据框中创建了一个空列,因此我们知道至少有一列会受到此函数的影响。让我们试一试:
place_names =
place_names %>%
remove_empty()
如您所见,empty_column 已从我们的数据框中删除,只留下包含数据的列:
bgn_date 列看起来是空的,但是它没有被 remove_empty()删除的事实告诉我们,至少在一行中必须有数据。如果我们向下滚动,我们会看到事实就是这样:
移除常量()
remove_constant()函数删除所有行中具有相同值的列。我们的数据集目前有其中的两个,因为我们将数据过滤到了伯克希尔县,所有伯克希尔县都在马萨诸塞州内,所有行的 county = "Berkshire "和 state = “MA”。这些行保留在数据集中不是特别有用,因为它们不提供特定于行的信息。我们可以简单地使用 select()来删除这些列,但是 remove_constant()的好处是,这个函数可以对我们假设的所有条目都相同的情况进行双重检查。事实上,使用 remove_constant()是我第一次发现原始数据中 1968 个条目中有 38 个实际上不是来自伯克希尔郡!
像 remove_empty()一样,remove_constant()函数需要的所有信息都是它应该操作的数据集:
place_names =
place_names %>%
remove_constant()
正如您在下面看到的,县和州列已被删除:
compare_df_cols()
有没有试过使用 rbind()将两个数据帧堆叠在一起,却遇到意外错误?compare_df_cols()函数直接比较两个数据框中的列,对于解决这个问题非常有用。让我们通过将我们的“地名”数据框与我们创建的包含伯克郡以外条目的数据框“非马萨诸塞州名称”进行比较来尝试一下:
compare_df_cols(place_names, non_ma_names)
输出是一个比较两个数据帧的方便的表格。我们在地名中看到“NA”代表县和州,在非地名中看到“character”代表这些变量。这是因为我们用 remove_constant()从 place_names 中删除了这些列,但从未对 non_ma_names 中的默认字符变量做任何事情。在 non_ma_names 中,我们还将 ele_ft 视为数字,将 map 视为因子变量,这是我们在数据准备期间特别指定的。如果我们试图将这些数据框合并在一起,了解哪些列缺失以及哪些列在数据框中具有不一致的类型会很有用。在包含许多列的数据框中,compare_df_cols()可以显著减少进行这些比较所花费的时间。
get_dupes()
我经常从事具有唯一患者 id 的项目,您不希望在您的数据集中看到重复的 id。在很多其他情况下,您可能希望确保某个 ID 变量具有完全唯一的值,包括我们的 GNIS 数据。您应该还记得,我们在准备数据时创建了一个重复的 ID。让我们看看 get_dupes()是如何检测到这一点的。该函数只需要数据框的名称和作为标识符的列的名称:
get_dupes(place_names, id)
如下所示,数据框被过滤为 ID 列中有重复值的行,从而便于调查任何问题:
泰伯基()
tabyl()函数是 table()函数的 tidyverse 兼容替代函数。它还与 knitr 包兼容,对于数据探索非常有用。
让我们先用一个变量来试一下。假设我们对伯克郡的每个城镇有多少所学校感兴趣。我们首先将我们的类变量过滤为“School”,然后将 tabyl()函数用于我们的 map (location)变量。最后,我们将它输入到 knitter::kable()中,将输出格式化成一个漂亮的表格:
place_names %>%
filter(class %in% "School") %>%
tabyl(map) %>%
knitr::kable()
运行这个非常基本的代码块会产生下面的输出表:
当我们编织 Rmd 文件时,kable()函数将很好地格式化表格,如下所示。我们可以方便地得到每个城镇中学校的数量,以及该城镇中所有学校的百分比。很容易对这些数据进行观察,例如 29.5%的学校在皮茨菲尔德东部,那里有 41 所学校。或者这三个镇太小了,只有一所学校:
现在让我们试试两个变量的交叉列表。让我们看看每个城镇中有多少每种类型的地标:
place_names %>%
tabyl(map, class) %>%
knitr::kable()
我们的桌子的一部分(一旦编织)如下所示。对于每个城镇,我们可以清楚地看到每个地标类型在数据库中有多少:
虽然像这样简单的计数可能非常有用,但也许我们更关心列百分比。换句话说,每个城镇中每个地标类型的条目占多大比例?这很容易通过“装饰百分比()”函数用 tabyl()来研究:
place_names %>%
tabyl(map, class) %>%
adorn_percentages("col") %>%
knitr::kable()
现在我们看到的是这些列的百分比而不是计数,但是这个表很难阅读:
我们可以用 attear _ pct _ formatting()函数稍微清理一下,它允许用户指定输出中包含的小数位数。精度对于这个探索性的表不是特别重要,所以让我们使用 0 个小数位来使这个表更容易阅读:
place_names %>%
tabyl(map, class) %>%
adorn_percentages("col") %>%
adorn_pct_formatting(digits = 0) %>%
knitr::kable()
好多了!现在,阅读表格和理解我们的列百分比就容易多了:
使用 embody _ percentages()来查看行百分比(在我们的例子中,是属于每个地标类型的每个城镇的条目的百分比)同样简单:
place_names %>%
tabyl(map, class) %>%
adorn_percentages("row") %>%
adorn_pct_formatting(digits = 0) %>%
knitr::kable()
其他功能
在本文中,我描述了看门人包中的函数,我发现这些函数在我的日常工作中很有用。然而,这并不是看门人功能的详尽列表,我建议参考文档以获得关于这个包的更多信息。
也就是说,这里至少有几个其他函数值得注意:
- excel_numeric_to_date(): 该函数设计用于处理 excel 的许多日期格式,并将这些数字变量转换为日期变量。对于那些经常在 Excel 中处理数据的人来说,这似乎可以节省大量时间。作为一个不常使用 Excel 的用户,我非常依赖 lubridate 包来处理日期变量。
- round_to_fraction(): 此函数允许您将小数四舍五入为精确的分数分母。想要将所有值四舍五入到最接近的四分之一,还是使用小数来表示一小时中的分钟数?round_to_fraction()函数可能会对您有所帮助。
- top_levels(): 该函数生成一个频率表,将分类变量折叠成高、中、低三个级别。常见的用例包括简化李克特式秤。
结论
在这一点上,大多数数据分析师和数据科学家将大部分时间投入到数据清理和探索中,这是一个常识,因此,我总是发现新的包和函数可以使这些过程更加高效,这是一件令人兴奋的事情。
无论您以前是否使用过看门人包,我希望这篇文章已经向您介绍了一些函数,这些函数将被证明是对您的数据科学工具包的有用补充。
熊猫数据帧中 JSON 的清洗和提取
包含 Github 回购
揭示 JSON 嵌入式专栏中隐藏的见解
照片由 Gerold Hinzen 在 Unsplash 上拍摄
我认识的一个人最近遇到了一个有趣的问题,我花了一分钟才弄明白。他们在 Kaggle 上发现了这些数据,其中包含嵌入了 JSON 的列。有多个这样的列,他们想从这些列中提取数据,并在同一个数据帧上表示它们。
让我们更详细地看看这个问题。这是一个数据的例子。
步骤 1:解码 JSON
JSON (JavaScript Object Notation)是大量信息在互联网上传输的方式。幸运的是,Python 标准库附带了一个名为json
的库。这意味着如果你已经安装了 Python,那么你就已经有了这个模块。如果您有兴趣了解更多信息,请查看文档!
通过导入json
包,我们可以将所有的 JSON 对象转换成它们各自的 Python 数据类型。Python 和 Pandas 不会明确地告诉你什么是 JSON,但是这通常很容易确定你是否在花括号({}
)中嵌套了数据,转换为str
类型。
下面是我写的将 JSON 解码成 Python 的代码:
步骤 2:跨多个列表示 JSON 数据
除非我们能从 JSON 中提取数据,否则我们所做的一切都没有用。为此,我创建了一个函数,它可以与熊猫 [apply](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.apply.html#pandas-dataframe-apply)
方法一起使用,并按行而不是按列应用(axis=1
)。
我的想法是对数据进行一次热编码,以保持一种整洁的格式。这意味着每行代表一个观察,每列代表观察的一个属性。
我为数据中看到的每个键和值创建了一个新列,如果在感兴趣的观察中看到了该键或值,则用1
表示,如果没有看到,则用0
代替。
代码如下:
让我们解释一下代码在做什么,首先我们循环遍历字典列表的长度,并创建一个列表来存储key:value
对。然后我们遍历每个字典,并使用[.items()](https://docs.python.org/3/library/stdtypes.html?highlight=dict%20items#dict.items)
提取key
和values
。.items()
将返回一个元组列表,其中第一个元素是key
,第二个元素是关联的value
。从这里开始,我们将把key
和value
加上一个下划线附加到我们的ls
对象上。
**# store values**
ls = []**# loop through the list of dictionaries**
for y in range(len(x[0])):**# Access each key and value in each dictionary**
for k, v in x[0][y].items():
# append column names to ls
ls.append(str(k)+ "_" +str(v))
接下来,我们将为在ls
对象中看到的key:value
对的每个组合创建一个新列。如果该列不存在,那么我们将创建一个具有关联名称的新列,并将所有值设置为0
,同时将当前观察值更改为1
。
**# create a new column or change 0 to 1 if keyword exists**
for z in range(len(ls)):**# If column not in the df columns then make a new column and assign zero values while changing the current row to 1**
if ls[z] not in df.columns:
df[ls[z]] = 0
df[ls[z]].iloc[x.name] = 1
else:
df[ls[z]].iloc[x.name] = 1
return
x.name
返回当前观察的指标。当axis=1
这将返回索引值,当axis=0
这将返回列名。在这里看文件。
步骤 3:应用函数
**# Loop over all columns and clean json and create new columns**
for x in json_cols:
df[[x]].apply(clean_json2, axis=1)print("New Shape", df.shape)
最后的想法
该代码返回一个超过 29,000 列的 DataFrame,需要很长时间来运行。在对所有数据进行测试之前,您可能希望对数据的子集进行测试。
在使用这段代码之前,您可以搜索每个 JSON 列,看看有多少惟一值。你总是可以将不太常见的值转换为“Other”来帮助加速运行。
现在你知道了,在 Pandas 中使用 JSON 数据并没有那么糟糕,只要有一点创造力,你就可以编写代码来实现你想要的任何东西。请随意调整代码,这样它就不会保存某些值或键。所提供的代码旨在作为您正在处理的任何 JSON 相关问题的样板。
在这里找到项目的代码。
在建模或分析之前,清理和准备 R 中的营销数据
实践教程
一个基本的、循序渐进的指南,帮助你清理 R 中典型的杂乱营销数据
TLDR
本文着眼于在分析或输入 ML 模型之前处理 R 中杂乱数据的基本工作流程。具体来说,它将讨论:
- 正确导入 csv 数据
- 添加、删除和重命名列
- 识别独特性,处理重复的价值和 NAs
- 一致的大小写和格式
- 查找和替换字符串
- 处理不同的日期格式🙀!
- 将不同的数据集合并成一个
- 将每日数据转换为每周数据
- 将长数据转换为宽格式
我一直深入到基础,一步一步地帮助新的 R 用户一行一行地掌握 R。如果时间允许的话,我希望很快能把这变成一个关于营销组合建模的系列🤗!
挑战
在本练习中,我特意设置了以下挑战:
- 使用两个不同的数据集,这两个数据集都被认为是混乱的
- 尽可能坚持使用底座/默认 R
- 尽可能多地注释代码
让我们先解决这个问题:营销数据总是混乱的。
为什么?这是因为这些数据通常存放在多个平台上,有些在线,有些离线,全部由不同的团队、外部合作伙伴等管理。简直是遍地。
让我们举例说明:如果你是一名负责衡量数字营销活动有效性的分析师,你可能需要从 Google Analytics 收集网站表现数据,从 Search Ads 360 或 Tradedesk 收集广告表现数据,然后从 CRM 或 SQL 数据库检索客户数据及其订单。
接受会有不完整的数据。将会有 NAs 或许多丢失的值。将会有格式和结构问题。有些数据甚至可能不是你所想的那样(嘘!看到那个叫做“转换”的专栏了吗这不是收到的查询总数,实际上是衡量用户点击提交查询按钮的次数——查询是否成功是另一回事🙄。)
在我们可以在任何建模或分析中可行地使用这些数据之前,我们需要擦洗、清理和整理这些数据。我们开始吧!💪
与 R 中混乱的数据争论
数据集
在本练习中,我们将查看两个数据集:
- 一个是每日营销绩效数据,具有典型的指标,如日期、渠道、每日支出、展示、转化、点击等。我们可以假设这来自一个分析平台。
- 另一个是日常销售数据(订单 id、收入、税收、运输等)。我们可以假设这来自一个客户关系管理系统。
- 这两个数据集都可以被认为是杂乱的(你马上就会明白为什么!🤭)
👉你可以在这里访问 Kaggle 上的数据集。感谢 Firat,数据集所有者,感谢他让我在帖子中使用它!
使用基本/默认 R
我已经决定尽可能地坚持使用基地 R 。为什么?因为在使用尽可能少的 R 包时,我的目标是尽可能减少依赖。当你加载一个 R 包时,它有时也会在后台加载额外的库。这没有错,使用 R 的好处之一是我们可以在他人的工作基础上进行构建,并为社区做出贡献。然而,使用 base R,这里讨论的许多任务都可以简单地(有时同样快速地)完成。
也就是说,一些像 View()、str()、aggregate()、merge()这样的函数在技术上不是 base R,它们来自于已经默认加载的包(例如{utils}、{stats}等)。从这个意义上说,我们将把它们视为默认函数,并可以在这个挑战中使用它们。
另外,我真诚地认为,如果这是您第一次尝试使用 R,那么在直接进入 tidyverse 之前,学习 R 基础是很重要的。根据经验,我发现有 R 基础的基础非常有用。
尽可能多的评论
我将尽可能地对我的代码进行注释,一行一行地注释,以帮助新的 R 用户更容易了解这一点。注释代码也应该是很好的练习 btw!因为如果你要和你的团队共享代码模板,这有助于他们理解每个代码块或代码行的作用。
加载数据
我们有 2 个数据集。让我们下载这些 CSV,在 R 中加载它们,看看它们是什么样子的:
第一行主要使用 read.csv()函数读取文件,然后将其保存到名为 marketing_df 的数据帧中。
下一行中的 View() 函数(注意大写字母“V”)应该会打开 R 中的文件,这样您就可以查看它了:
呸!数据看起来乱糟糟的!CSV 文件可以用不同的分隔符保存。默认情况下,CSV 以逗号分隔(因此缩写为😉)但是这个是用分号隔开的,就像截图中的分号可以看到的。让我们再次加载数据,这次指定分隔符,如下所示:
好多了!我们现在可以看到每个值在各自的列中!
现在我们已经处理了营销数据,让我们获取订单数据并将其保存到名为 orders_df: 的数据帧中
很好,我们已经加载了两个数据集。显然,我们可以看到有工作要做,以清理他们。让我们首先处理营销数据集,然后我们将处理订单数据集。
清理营销数据
marketing_df 数据框架有许多有用的列。为了便于说明,假设我们只关心以下 3 列:
- 周期代码(第一列)
- 平台代码(第 4 列)
- 营销投资(第 7 列)。
我们将抓取这些列,并将它们保存到一个名为 marketing_df_clean 的新数据框架中。我们还想用更容易理解的名称来重命名这些列。
我们走吧:
所以我们把数据框从这里转了过来:
对此👇
我们将它保存到一个更小的数据框架中,名为 marketing_df_clean ,只有 3 列。查看数据,滚动并注意这些值是如何显示的。你会注意到几件事:
- 日期很奇怪。我的意思是,我们当然认识到它们是日期,但它们也可能被 R 解读为其他任何东西。我们需要将它们标准化成合适的日期格式。
- 通道列中的值有各种大小写。让我们将它们都强制转换为小写,以保持一致。
还有一些价值需要更新,这就是**领域知识派上用场的地方:**由于付费媒体已经被定义(如脸书、Adwords),那么“非付费”可以被视为“有机的”。“未跟踪”可能意味着“直接”,而“silverpop”是一个电子邮件平台,因此可以重命名为“电子邮件”。 - 如果您仔细查看支出列,您还会注意到值是用逗号分隔的。例如 20,29。
您可以通过运行以下代码行来检查 R 是如何读取这些值的:
str(marketing_df_clean)
这个 str() 函数向您展示了 R 如何查看 R 对象的内部结构,在本例中是一个包含 3 列的数据帧:
这个输出显示,R 错误地将日期列读取为整数,而不是格式日期。花被读作文字是不正确的!花费应该是数字。这可能是因为数字之间的逗号导致它被读为文本。
基于以上所述,我们知道我们需要做以下工作:
- 设置日期列的格式,以便将其正确识别为日期。不幸的是,日期格式化真的很麻烦😡。因此对于这一部分,我将使用奇妙的{lubridate}包将这些日期强制转换成正确的格式。公平地说,我认为这是我在这个练习中使用的唯一一个非基本/非默认包。我们将最后安排日期。
- 强制频道列中的所有文本值小写
- 一旦小写,检查通道列,查看所有可能的唯一值
- 选择我们想要重命名的频道
- 最后,确保花费一栏被识别为数字。
这是:
注意:是的,我经常使用 str()、View()和 head(),我建议你也这样做。它帮助我检查我没有在争论中损坏我的数据。
然后,用 StatQuest 的乔希·斯塔默的睿智的话来说:
嘭!!!💥
你可以在 r 中点击来排序你的表,你也可以过滤和搜索。
日期现在可以正确格式化并被识别。花费现在被正确地识别为数字。我们在做生意!!
呃,实际上不完全是。再次查看数据-似乎有重复的行(例如,同一个频道,同一天)。
还没有脱离险境
我们要确保每天只有一个渠道,支出是该渠道在这一天的总支出。回想一下,我们之前已经删除了不需要的其他列。这些列用于进一步细分数据(例如,按桌面或移动设备)。当我们删除这些列时,我们需要记住按渠道合计当天的支出。
aggregate() 函数很好地完成了这一任务:
marketing_df_clean <- aggregate(spend ~ date + channel, data = marketing_df_clean, sum)
这给了我们一张漂亮的桌子,像这样:
每天按渠道列出的总支出。没有重复。
耶!我们现在已经获得了每个频道每天花费的清晰数据集。
清除订单数据
现在,让我们执行与上面相同的步骤,但是这次使用的是订单数据。为了提醒我们自己, orders_df dataframe 看起来像这样:
在本练习中,我们只关心 createdAt(第 2)、platformCode(第 4)和 revenue(第 18)列。
还要注意,日期后面附加了这些时间戳。在使用{lubridate}将日期格式化为我们之前使用的一致的年-月-日格式之前,我们需要先去掉这些内容。
除此之外,我们可以对订单数据使用与营销数据相同的工作流程,如下所示:
这应该会产生这个干净的表:
r 还将日期列识别为日期格式,收入是数字。订单数据现在完成了!对营销和订单数据进行排序后,我们现在可以将它们转换成每周数据集。
将每日数据转换为每周数据
将每日数据集转换为每周数据集有很多原因。在营销组合建模和数字营销的一般预测中,通常使用每周数据,因为并非所有渠道都能在同一天对销售产生直接影响,转化可能发生在最初印象后的第二、第三或第四天。
我们可以在 r 中很容易地做到这一点
- 首先,我们将创建一个名为“week”的新列。这将表示日期所在的一年中的第几周。例如,2020 年 3 月 15 日是 2020 年的第 11 周。(我发现这个网站真的很有助于快速查周。)
- 然后我们将创建一个名为“monthyear”的新列。对于日期 2020–03–15,month year 将输出“03–2020”。
- 我们将对营销和订单数据都这样做👇
你现在应该有 2 个新的数据帧。
市场营销 _ df _ 周刊:
和 orders_df_weekly:
如您所见,这些值还没有按周进行聚合和求和。我们只是在每个数据帧中引入了 2 个新列来告诉我们每个特定日期属于哪一周。
接下来,我们将把营销和订单数据连接到一个数据框架中,我们称之为 weekly_df 。我们使用 merge() 函数来实现这一点:
weekly_df <- merge(marketing_df_weekly, orders_df_weekly)
View(weekly_df)
使用上面的代码行应该会得到这个结果👇—每天按渠道列出支出和收入的表格。“星期”和“月份”列显示了日期所属的星期数和月份。
现在,我们可以按周汇总支出和收入,如下所示:
- 按渠道合计每周所有支出。将其保存到名为 weekly_spend_df 的数据帧中
- 按渠道汇总每周所有收入。将其保存到名为 weekly_rev_df 的数据帧中
- 获取每周的第一天,将该列称为“weekdatestart”并保存到名为 weekly_df_dates 的数据帧中。移除任何重复的行。
- 将这 3 个数据帧合并成一个干净的每周聚合数据帧,称为 weekly_df_updated 。
太棒了。您现在应该会得到如下所示的数据帧:
坚持住!你注意到这有什么奇怪的吗🤔?
顾名思义,totalrevenuefortheweek 是该周的总收入,定义如下:
weekly_rev_df <- aggregate(revenue ~ week, data = weekly_df, sum)
然而, weeklyspend 指的是给定一周特定渠道的总支出*,定义如下:*
weekly_spend_df <- aggregate(spend ~ week + channel, data = weekly_df, sum)
从上表可以看出,从 2020 年 1 月 2 日开始的一周,在“adwords”上花费 70,326.29 英镑产生了 1,513,643 英镑的收入。在同一周内,在“heurekacz”频道上花费 262.32 英镑获得了另外 1,513,643 英镑的收入!诸如此类。
👎这当然是不正确的!
我们想要展示的是,在给定的一周内,在 adwords、电子邮件、rtbhouse 和其他渠道上的支出的综合表现,在该周总共产生了 1,513,643 英镑的收入。
为了正确理解这一点,我们需要将这种长数据格式转换为宽数据格式。
将长数据转换为宽数据
将长数据转换成宽数据非常简单,反之亦然!基本的 reshape() 函数让你在一行代码中就能做到,就像这样:
*weekly_reshaped_channel <- reshape(weekly_df_updated, idvar = c("weekdatestart","totalrevenueforweek"), timevar = "channel", direction = "wide")*
当您查看(weekly _ reshaved _ channel)时,您应该会看到下表:
这更有意义,因为您可以看到每周实现的总收入和该周每个渠道的支出!
处理 NAs
您可以使用 print() 查看控制台中的数据并检查 NA 值。或者,如果您喜欢像我们在本练习中所做的那样,以电子表格的方式查看它,只需使用 View()即可:
如果检测到 NAs,您可以使用 is.na() 将 NA 值替换为 0,如下所示:
*#view the data and check for NA or missing values
print(weekly_reshaped_channel)#if found, replace any NA with 0
weekly_reshaped_channel[is.na(weekly_reshaped_channel)] <- 0#check
str(weekly_reshaped_channel)
View(weekly_reshaped_channel)*
结束了
👏干得好,一直到最后!我们终于得到了我们需要的数据集,我们可以用它来执行以后的建模。
完整的 R 代码
👉从我的 Github repo 这里获取完整的 R 代码。
我希望这篇文章对你有所帮助。毫无疑问,有更快更好的方法来达到同样的最终结果,但这篇文章的目的是深入基础,帮助新的 R 用户掌握 R。如果你做过类似的练习,并找到了更好的方法,请在评论中分享🤗!
我还想在下一篇文章中继续使用同样的数据来制作营销组合模型。我们会看到的。TTFN!
通过构建 NLP 管道清理和预处理文本数据
用 python 处理文本数据的完整演练
艾莉娜·格鲁布尼亚克在 Unsplash 上的照片
有一段时间,我在处理文本数据,我意识到在当今世界,有必要知道自然语言处理是如何工作的,以及为了从文本数据中获得洞察力需要涉及的主要步骤。
众所周知,为了从数字数据中获得洞察力,我们可以应用许多统计学和数学。
但是说到文本数据的繁琐形式,我们在很多地方都缺乏。
什么是 NLP 文本预处理?
NLP 文本预处理是一种清理文本的方法,目的是使文本可以提供给模型。文本中的噪音有多种形式,如表情符号、标点符号、不同的大小写。所有这些噪音对机器都没有用,因此需要清理。
为什么 NLP 文本预处理很重要?
重要性:
经过适当清理的数据将有助于我们进行良好的文本分析,并帮助我们对业务问题做出准确的决策。因此,机器学习的文本预处理是一个重要的步骤。
在我让你们了解 NLP 文本预处理的主要步骤之前,我想说的是,你们可以根据自己的数据和需求增加或删除一些步骤。
在本教程中,我几乎不会给你任何步骤的定义,因为互联网上有很多。相反,我将解释为什么您应该应用特定的步骤,如何使用 python 进行文本预处理,我在每个步骤中理解了什么,以及我的数据集的结果是什么。
好吧!!说够了。现在让我们深入研究代码。😄
库&所需的文本预处理工具:
*# Importing Libraries*
**import** **unidecode**
**import** **pandas** **as** **pd**
**import** **re** **import** **time**
**import** **nltk** **from** **nltk.corpus**
**import** stopwords
nltk.download('stopwords')
**from** **nltk.tokenize** **import** word_tokenize
**from** **nltk.stem** **import** WordNetLemmatizer
**from** **autocorrect** **import** Speller
**from** **bs4** **import** BeautifulSoup
**from** **nltk.corpus** **import** stopwords
**from** **nltk** **import** word_tokenize **import** **string**
读取数据集:
*# Read Dataset* Df = pd.read_csv('New Task.csv', encoding = 'latin-1')
*# Show Dataset*
Df.head()
以下是一些文本预处理步骤,您可以根据您拥有的数据集添加或删除这些步骤:
步骤 1:移除换行符&标签
您可能会在文本数据集和选项卡中无缘无故地遇到许多新行。因此,当您抓取数据时,网站上结构化内容所需的换行符和制表符在您的数据集中是不需要的,并且还会被转换为无用的字符,如\n,\t。因此,我编写了一个函数来删除所有这些无意义的内容。
步骤 2:剥离 HTML 标签
当您抓取数据时,如果您在抓取时没有处理它,您可能会在数据集的文本中看到 HTML 标记。因此,有必要在以后处理这些标签。在这个函数中,我删除了文本中所有匹配 HTML 标签的内容。
步骤 3:移除链接
这一步将删除所有与任何类型的超链接相似的内容。我在这里添加了这个函数,因为我已经在数据集上处理了它。
第四步:删除空白
如下所述,可以执行单行功能来移除额外的空白。在执行进一步的 NLP 任务之前,这一步至关重要。
自然语言处理文本预处理的主要步骤是什么?
下面列出的文本预处理步骤非常重要,我已经按顺序写了所有这些步骤。
第一步:删除重音字符
这是将所有类似重音字符的字符转换成机器可理解语言的关键一步。以便可以容易地实施进一步的步骤。重音字符是指字符上方有音调符号的字符,如“、”或“”。
步骤 2:案例转换
这是系列中真正重要的下一步,因为 case 是机器的两个不同的词。因此,您应该将文本的大小写转换为小写或大写才能继续。
步骤 3:减少重复字符和标点
这一步很重要,因为可能会出现字符重复过多的情况,这是拼写检查器无法检测到的。因此,在应用拼写检查功能之前,需要预先处理这种情况。我在工作中还会遇到另一种情况,也可能会出现重复的标点符号。所以也需要遇到他们。
当我们非常兴奋时,我们确实会覆盖那些让读者不知所措的东西。
示例:- Cheeeeeerrrrrrss!!!!!!
在上述正则表达式中使用某些符号的说明
\1 →相当于重新搜索(…)。组(1)。它指的是第一个捕获组。\1 匹配与第一个捕获组匹配的完全相同的文本。
{1,} →这意味着我们正在匹配出现不止一次的重复。
DOTALL - >它也匹配换行符,不像点运算符那样匹配给定文本中除换行符以外的所有内容。
sub() →此函数用于用另一个子字符串替换特定子字符串的出现。该函数将以下内容作为输入:要替换的子字符串。要替换的子字符串。
r’\1\1’ →将所有重复限制在两个字以内。
r’\1’ →将所有重复限制为仅一个字符。
{2,} →表示匹配出现两次以上的重复
第四步:扩张收缩
为了在下一步中删除停用词,首先处理缩写是至关重要的。缩写只不过是诸如“不要”、“不会”、“它是”等词的速记形式。宫缩是任何类似这些例子不要,不会,它的。
步骤 5:删除特殊字符
在这一步中,我们将学习如何删除特殊字符,为什么要删除它们,以及应该保留哪些特殊字符。
所以,我写了一个函数,它将删除一组指定的特殊字符,并保留一些重要的标点符号,如(,。?!)不包括括号。特殊字符应该被删除,因为当我们要对文本进行标记时,以后,标点符号不会以更大的权重出现。
从文本中删除数字:
您所需要做的就是修改给定的正则表达式
Formatted_Text = re.sub(r"[^a-zA-Z:$-,%.?!]+", ' ', text)
只需排除 0-9 范围,以便从文本中删除所有数字表示。我没有在我的数据集上执行这一特定步骤,因为这些数字对我的情况非常重要。
根据我的数据集,我正在考虑的标点符号很重要,因为我稍后必须执行文本摘要。
,。?! →这是一些经常出现的标点符号,需要保留下来,以便理解文本的上下文。
: →根据数据集,这个也很常见。保持是很重要的,因为每当出现像晚上 9:05 这样的时间时,它就有了意义
这个词也经常出现在许多文章中,更准确地讲述数据、事实和数字。
$ →这个用在很多考虑价格的文章里。因此,省略这个符号对那些只剩下一些数字的价格没有太大意义。
步骤 6:移除停用词
如果您正在执行标记化、文本摘要、文本分类或任何类似的任务,则应该删除停用词。如同没有停用字词一样,您可以理解呈现给您的文本数据的上下文。需要移除它们以减轻它们的重量。
你应该考虑这一步删除停用词,因为它不适合我的进一步分析。正如我在生成 n-gram 时发现的那样,有停用词的文件往往比没有停用词的文件给出更可靠的结果。
步骤 7:纠正拼写错误的单词
尝试这一步时,你应该非常小心。因为这个功能可能会改变单词的真正含义。所以你必须非常小心,试着看看在应用这个函数时事情是如何发展的。如果您正在处理一些特定于行业的数据集,那么您可能需要考虑关联字典,该字典明确地告诉该函数保持那些特定的单词不变。
步骤 8:词汇化/词干化
在这一步,我将准确地谈论两件事,词汇化和词干化。
人们对这两种技术感到困惑,比如哪一种更有效,该用什么。那么,让我告诉你我的经历,我喜欢什么,为什么?
因此,这两种技术实际上都将单词修整为其词根形式,就像 Planning 将被修整为 plan,但词干不会在单词“Better”的情况下工作,而如果您应用词汇化,它会将单词转换为其词根形式,即“good”。这是一个主要的区别,即 Lemmatization 工作效率高,我只在工作中使用过它。
虽然我已经写了这个函数,但是经过进一步的分析,我发现它的性能并不好,反而产生了噪音。因此,在对一些令牌进行位分析时,我意识到最好不要应用术语化。
例如:-有一个词“饲料”,这是频繁出现在所有的文章,这也是非常重要的。但是在词条化上,“饲料”→简化为→“饲料”。你可以看到它是如何改变了它的整个含义。
主要调查结果:
数据清理步骤完全取决于数据集的类型。根据数据,可以包括更多的步骤。必须删除多余的空间以减小文件大小。
结论:
这些是我在对文本数据进行预处理时一直考虑的一些重要步骤。如果您也必须处理文本数据,您可以利用共享的代码,或者不要忘记让我知道您是否尝试了其他方法来清理文本数据。这些步骤是特定于我所使用的数据集的,所以在方便的时候可以随意添加或删除它。
您还可以访问完整的 Github 资源库,其中包含一系列实现中的所有步骤,并附有简要说明。
https://github.com/techykajal/Data-Pre-processing/blob/main/Text_Preprocessing.ipynb
干杯!!你已经坚持到最后了。😉
所以,如果我的博客帖子对你有所帮助,而你此刻觉得很慷慨,请不要犹豫,请给我买杯咖啡。☕😍
是的,点击我。
And yes, buying me a coffee **(and lots of it if you are feeling extra generous)** goes a long way in ensuring that I keep producing content every day in the years to come.
您可以通过以下方式联系我:
- 订阅我的 YouTube 频道 视频内容即将上线 这里
- 跟我上 中
- 通过 LinkedIn 联系我
- 跟随我的博客之旅:-https://kajalyadav.com/
- 成为会员:https://techykajal.medium.com/membershipT21
也可以看看我的其他博客:
</8-ml-ai-projects-to-make-your-portfolio-stand-out-bfc5be94e063> </15-free-open-source-data-resources-for-your-next-data-science-project-6480edee9bc1>
清理 GitHub(用于数据科学)
保持您的存储库数量易于管理
呀。图片作者。
你使用 GitHub 已经有一段时间了,也许你开始注意到你的存储库页面(pages???)比以前丰满了。也许,事实上,它变得如此之满,以至于它更像是一个代码转储,而不是一个有意义的代码集合。在写这篇文章的时候,我的 GitHub 明显属于前一类(是的,那就是我),感觉是时候改变这种情况了。
毕竟,在许多方面,你的 GitHub 是你向世界其他地方展示的编码面孔,因此,你希望它(如果不是通过 GitHub 的网站设计,而是通过对人类精神的设计)部分是作品集,部分是诗歌,部分是可视化,部分是对话——但完全整洁,记录良好,易于导航。
据我所见,有五种主要的方法来清理你的存储库:
- 删除旧的存储库
- 在 Github 上模仿一个目录式的架构:
- 子树/子模块
- 项目
- 组织
策略 1:删除旧的存储库
我们现在都已经读过《T2》了,对吗?浏览你的每一个储存库,问你自己,“这能激发快乐?”如果没有,就把它处理掉。
环顾网络,这是一个令人惊讶的受欢迎的选择。你一下午就完成的 Udemy 课程的知识库?摆脱它。你几个月或几年没做的事?摆脱它。我承认,这个选项可能会触发我的强迫症(“如果我以后需要这个生成猫的 GAN 怎么办?”),但这是你清除 GitHub 的第一站。也许浏览一下你所有的存储库,特别是如果它们和我的一样有数百个,问问你是否真的需要它们。也许你可以放走一些。
这种方法有一个潜在的缺点。嗯,两个。第一,代码不见了,第二,如果你删除了一个给定的库,那么在你的贡献图上显示的任何东西都会消失。如果这令人不安,考虑一些替代方法来重新填充你的贡献图
策略 2:模仿目录结构
Github,从它的概念来看,提供了相对简约的结构。它缺少真正的目录或文件结构。例如,我的 339 个知识库中的绝大多数来自 Flatiron School 的数据科学训练营,但我不能把它们都放在一个“Flatiron”文件夹中,因为 GitHub 天生就没有这种结构。虽然有许多模仿这种结构的选择,但工具的原始目的需要不同程度的低劣化。
子树(可能还有子模块)
出于清理存储库页面的目的,我认为 subtree 是两者中的赢家,但我肯定有人会更好地使用git submodule
。关于这两者区别的详细讨论,请阅读这里的、这里的和这里的,但主要区别是:
- 子模块在外部存储库内留下一个指针,指向内部存储库中的一个特定提交(它不会像我们想象文件在文件夹内移动那样将内部存储库移动到外部存储库内)并且在 git 中有一个明确的命令
git submodule
,这使得设置很容易,但之后很难维护; - 另一方面,子树实际上将内部回购的代码移动到外部回购中,就像将文件移动到文件夹中一样,但它在 git 中没有默认命令,这使得设置起来有点困难,但之后更容易维护。
如果试图在数百个回购中利用这两种方法的结构,它们会很快变得耗时,但你会得到你的贡献图。
项目
仅仅从名字上看,这一个听起来很有前途——当听到这个名字时,我的直觉是“我将多个存储库组合在一个项目下”——但是它有一个更接近于待办事项列表的功能,用于跟踪问题、拉请求和注释。尽管如此,我还是听说有人使用这个特性来组织存储库(每个项目最多 5 个 repos)。
组织
这个对我来说是赢家。您可以创建组织来对 repos 进行分组,它 1)从您的存储库页面中删除 repos,2)列出组织名称。因此,从这个重要的角度来看,这可以说是 GitHub 上最接近于模仿目录结构的了。看起来是这样的:
左下角用红色圈出的新组织。图片作者。
组织的悬停效应。图片作者。
将鼠标悬停在它上面,它会提供关于该组织的一些信息。在图中,它仍然显示我有大约 300 个存储库,但这是因为我还没有将其中的大部分转移到新的组织中。将存储库转移到组织中,即使是您拥有的组织,也会从您的个人资料页面上的存储库列表中删除该存储库,并从您的贡献图中删除对这些存储库所做的任何贡献。对我来说,这是一个很小的代价,可以让我有一个很好的地方把这 300 个存储库藏在一起,看不见,但我确信对一些人来说这是两个世界中最糟糕的:回购仍然存在,我失去了贡献图。
更好的例子,请看安德烈·乔阿拉的文章。
一旦你清理了你的 GitHub 档案,我认为考虑如何更好地构建你的 GitHub 是一个好主意。最好的方法之一就是看看其他人是如何组织他们的。你可以从这里开始,看看一些漂亮的数据科学投资组合的例子。我个人最喜欢的是汉娜·韩嫣的 GitHub 和个人网站。她将存储库的数量保持在 20 个以内(尽管她有更多的项目),并且由于智能回购名称和描述,很容易找到它们。
对于任何一个刚刚开始在 git 仓库之外运作的技术训练营的人来说,我的建议是只去那些你付出了原创努力的实验室(大约 10 秒,而不是 100 秒的回购),或者在 GitHub 上从一开始就建立一个组织来学习所有这些课程。
清晰存储:存储面部图像保留政策的伦理
2021 年春天,华特·迪士尼世界测试了面部识别软件,旨在将客人的面部与他们的公园预订联系起来,引发了一场关于数据所有权的隐私辩论
虽然面部识别的话题令人不安,但听到它在一个与儿童般天真无邪相关的地方应用,对那些担心迪士尼公司侵犯他们隐私的人来说,尤其不和谐。应该指出的是,在迪士尼的案例中,对面部扫描仪感到不舒服的客人可以选择侵入性较小的门票扫描,并且没有关于该技术以非自愿方式实施的报告。
选择加入?
然而,并非所有公司都允许客户选择不收集面部数据。7 月,The Verge 报道称 Lowe’s、Macy’s 和 Ace Hardware 目前都在使用面部识别算法,而麦当劳、沃尔格林甚至 7–11 都在考虑未来使用面部识别。虽然这听起来很可怕,但这种做法并不违法,因为面部识别技术在美国和世界大部分地区都不受监管。虽然面部识别的立法被忽视了(目前),但有一个关于长期数据保留的更大的辩论正在激烈进行。当一起讨论时,很明显,动态或静态面部图像的长期存储对任何规模的组织都提出了道德和基础设施的困境。
数据常被比作石油;然而,与化石燃料不同,数据是一种可再生资源。它不需要被无限期地存储和访问才是无价的。
欧盟保留:被遗忘的权利
对于总部位于美国的公司来说,制定数据保护和保留政策目前是可选的,但加利福尼亚州除外,在加利福尼亚州,企业受加利福尼亚州消费者隐私法(CCPA)的约束,应用户的要求披露数据。对于在欧盟运营或影响欧盟用户的公司,数据透明是强制性的。《通用数据保护条例》( GDPR)没有规定公司被允许存储客户数据的时间期限。然而,它确实包括了具体的数据保留指南,并且是目前为止关于这个主题最全面的资源。几个亮点:
- 公司不得将个人数据保留超过他们需要的时间
- 公司必须能够证明他们为什么保存数据
- 公司需要建立保留期的政策
- 公司必须对他们如何使用数据进行定期审查
- 只有在符合公共利益的情况下,公司才能长期保留数据
最后,GDPR 向欧盟公民保证,他们对自己的数据拥有最终所有权,包括被遗忘的权利:
如果您不再需要数据,个人有权擦除数据——一般数据保护法规(GDPR)
斯蒂芬·菲利普斯-Hostreviews.co.uk 在 Unsplash 上拍摄的照片
图像和隐私无法扩展
然而,值得注意的是,这些定律通常应用于结构化的、基于文本的数据,而不是非结构化的图像数据。就基础设施而言,图像文件通常比文本文件大几十倍或几百倍,使用谷歌或亚马逊等公司提供的基于云的数据库来存储图像文件,公司每月将花费更多的成本,特别是因为谷歌按千兆字节收取活动存储费用。最重要的是,虽然存储方法可以用于较小的数据库,但扩展数百万图像文件的存储可能是一个基础架构挑战。
基础设施和成本可以预测和调整;决定存储一些可以收集的最私人的数据的道德性,一个人的脸,在服务器上多年,涉及数据相关领域内外的那些人。十年前,医学成像领域的研究人员在云计算出现之前就面临这些问题。甚至在谷歌云产品等基于云的服务器广泛使用之前,这些人就认识到了在将图像数据批量或流式传输到异地服务器之前存储和加密图像数据的挑战。
建立一套清晰的道德准则可以帮助人工智能开发者和商业领袖确定如何以平衡他们收集数据的权利和消费者隐私权的方式构思、测试和部署面部识别。非营利组织隐私论坛的未来建议,利用面部识别的组织必须关注隐私原则,如同意、数据安全和透明度。然而,对于这些数据的长期存储,却没有明确的指导方针。因此,出现了许多伦理难题,特别是在从弱势群体中收集面部数据方面。
例如,这里有一个假设:公司应该有权收集、利用和无限期存储从未成年人那里收集的面部图像数据吗?
在一个父母对在社交媒体上发布他们年幼孩子的照片持谨慎态度的时代,公司可以捕捉并永久存储他们的面部图像这一事实是一种令人担忧的边缘反乌托邦思想。
被遗忘的辩论
有时候,似乎唯一与数据科学相关的伦理争论是负责任的人工智能设计和使用。当谈到面部识别时,对模型偏差和数据集缺乏多样性的担忧是可以理解的,也是认可的。坦率地说,这些都是相关的、有趣的讨论。诚然,数据保留并不是一个令人兴奋的话题。然而,随着越来越多的网站和应用程序兜售选择退出功能,技术公司和数据科学家将不得不应对的一个问题是:我们是否在道德上采购、处理和保护我们的数据?
慢慢地,我们开始看到公司,尤其是 FAANG 公司,解决这个问题。最近,谷歌更新了其 BigQuery 数据库产品以包括数据到期参数,以阻止用户保留数据超过合理的时间范围。谷歌本身正在带头努力改革数据保留政策。2020 年,谷歌宣布将在 18 个月后开始自动删除位置历史、网页历史和语音记录。谷歌在其新的数据保留政策中包括非结构化个人身份信息(PII)数据,如录音,这一事实应该为组织如何存储敏感的多媒体数据提供一些先例。
对于在数据行业工作的任何人,包括我自己,虽然我们没有任何希波克拉底誓言,但有一个隐含的责任是从道德上访问、操作和存储数据。限制存储能力或监管行业的想法不一定是可怕的想法。相反,对 PII 数据收集的监管,可以让企业建立、甚至在某些情况下重新赢得用户的信任,这些用户习惯于交出自己的信息,希望有人在后台考虑他们的最佳利益。
基于离散策略梯度算法的悬崖行走问题
用 Python 实现了一个完整的增强算法。手动执行这些步骤来说明该过程的内部工作原理。
詹妮弗·卡佩尔在 Unsplash 上的照片
教科书上的悬崖行走问题继续引人入胜。虽然非常简单,但它阐明了强化学习算法的许多有趣的方面。在处理了一些基于价值的实现(这里是 SARSA 和 Q-learning,这里是深度 Q-learning)之后,现在是时候转向基于策略的实现了。尽管现在有很多库,我们还是用 Python 手动实现了这个过程,来演示离散策略梯度算法背后的机制。最后,我们与 SARSA 进行了面对面的比较。
悬崖行走问题
先把问题勾勒出来。我们的世界是一个相当全面的网格,除了应该避免的致命悬崖。代理从悬崖的左手边开始,目标(代理不知道)在右手边等待。目标是找到到达目标的最短路径而不坠入悬崖,这直接结束了这一集。
悬崖世界[图片由作者提供]
存在各种强化学习解决方案。本文主要讨论策略梯度算法。简单地说,策略告诉代理在给定状态下做什么——比计算和利用对应于每个动作的值函数更直接。在此设置中,您可能倾向于将策略视为一个表格,其中列出了 48 个图块中每个图块的最佳操作。
不过这有点复杂,因为这种方法依赖于随机策略。策略不是简单地告诉我们最好的行动,而是提供了我们应该采取每个行动的概率。不涉及太多细节,随机政策通常在未知环境中是有意义的,并为探索提供了一种内置机制。不良行为的概率应该趋近于零。
离散策略— Softmax 函数
是时候正式制定政策了。我们使用一个 softmax 策略,它有效地将任何一系列真实值映射到一系列概率。作为输入,它需要一个特征向量和一个相应权重的向量。
作为特征向量 *ϕ(s,a,*我们定义了一个 48⋅4 向量,每四个元素代表任何给定瓷砖上的移动——本质上是一个查找表(或者可以使用后决策状态方法,只需要 48 个特征,但丢失了细节)。该向量是一位热编码的,这意味着我们有一个具有 191 个 0 和 1 个 1 的向量,对应于状态 s 中的动作 a 。如前所述, θ 是一个权重向量,每个 (s,a) 对一个。 ϕ(s,a) 和 θ 的点积捕捉了位于特定瓷砖上的值,与众所周知的 q 值相当。
现在,我们可以将 softmax 策略形式化如下:
Softmax 政策。
ϕ(s,a)^⊤ θ越大,a采取行动的概率越高。用正常的语言来说:我们很有可能采取措施,让我们更接近目标。但是在我们知道这些步骤之前,我们首先需要了解政策。
离散政策梯度
定义了策略本身之后,让我们来看看如何更新它。这就是政策梯度定理的用武之地。为了保持文章的范围,我们将自己限制在权重更新过程中:
策略梯度方法的更新功能
这里的 α 简单来说就是学习率。奖励函数 G_t 也很简单:
累积奖励函数
主要的挑战在于讨厌的 ∇_θ对数(π_θ(a|s)) — 也称为得分函数— 我们如何将它转化为可实施的规则?从数学上来说,这是相当多的工作(详细推导这里),所以让我们直接跳到最终结果:
离散政策梯度的得分函数。得分函数是所选动作的特征向量减去所有动作的加权特征向量。
在教科书中,最下面的方程通常是作为最终结果出现的,但实际上我发现其他方程更有用。
举个例子吧。处理一个 48 元素的向量有点不方便,所以假设我们有一个只有 4 个元素的特征向量。首先,我们的一键向量 ϕ(s,a) 可能看起来像[0 1 0 0]
——代表我们采取的行动。其次,我们计算我们的期望,它本质上是一个加权特征向量。假设我们有一个概率向量[0.2 0.5 0.1 0.2]
,每个动作将我们引向一个不同的瓦片(状态)。所有的动作都是一次性编码的,所以期望可以写成
weighted_phi: [1 0 0 0]*0.2 + [0 1 0 0]*0.5 + [0 0 1 0]*0.1 + [0 0 0 1]*0.2 = [0.2 0.5 0.1 0.2]
对,那和概率向量本身完全一样。事情并不总是这么简单——特征向量不一定是一个热门向量——但是你会明白的。现在,我们只需要从特征向量中减去期望值就可以得到
score function: [0 1 0 0] - [0.2 0.5 0.1 0.2] = [-0.2 0.5 -0.1 -0.2]
概括地说,我们采用了与我们选择的动作相对应的特征向量,然后减去所有动作的加权特征向量,从而获得该动作的得分。
为了执行权重更新δθ,我们将得分函数与奖励 G_t 和学习率 α 相乘。例如:
Weight update: Δθ = α * score_function * G_t = 0.01 *[-0.2 0.5 -0.1 -0.2]*10 = [-0.02 0.05 -0.01 -0.02]
直觉上,其中的含义是清楚的。如果我们把一个积极的分数(行动 2)乘以一个积极的回报,这个概率会增加,我们会在未来更频繁地采取行动。更微妙:奖励信号越强,更新越大。渐渐地,概率在最坏的情况下会收敛到局部最优,在最好的情况下会收敛到全局最优。
Python 实现
回到我们的悬崖漫步问题。最初,我们不知道什么行为是好的,因此相等地初始化概率是有意义的(简单地将所有的 θ 设置为 0)。我们的代理人会在一段时间内磕磕绊绊,但最终应该击中目标。这给了倒数第二块瓷砖一个强烈的奖励信号(增加向下移动的概率),从那里开始就是连锁反应。
请注意,尽管该方法是基于政策而不是基于价值的,但奖励轨迹隐含地保留了整个过程中的核心角色。更新只发生在一集的结尾,所以如果我们走了 20 步,我们执行 20 次更新,追溯完整的轨迹(即蒙特卡罗学习)。
完整的实现可以在我的 GitHub 上找到,在这里我将重点放在最相关的片段上(为了可读性做了一些清理)。
首先,让我们定义策略:
相应的 softmax 可以表示如下。实际的实现通常涉及缩放—指数可能会变得非常大。
使用相应的折扣,为存储的轨迹中的每个决策时刻 t 计算奖励函数 G_t 。通常,您还需要在这里执行一些奖励标准化。
最后,评分函数 ∇_θ log(π_θ(a|s)) ,需要执行更新*δθ=α⋅g_t⋅∇_θlog(π_θ(a | s))。*提醒一下,我们只是简单的从实际的特征向量(选择的动作)中减去加权的特征向量(所有动作)。
这就是我们所需要的。现在让我们运行它,使用 SARSA(另一个基于策略的方法,但是基于值)进行比较。
10,000 集的步骤数,使用策略梯度和 SARSA 进行培训。SARSA 更快地收敛到更好的解,并且经历更少的噪声。[图片由作者提供]
政策梯度和 SARSA 的培训路径。由于更高的探索,梯度剂走了更长的弯路。两者都不走最短路径,都是带有嵌入式探索的策略方法。[图片由作者提供]
显而易见,政策梯度更新比 SARSA 更新要混乱得多。这部分是随机策略所固有的(我们的 SARSA 代理只探索了 5%的时间,梯度方法探索了更多的时间)-即使在 10,000 次迭代之后,代理也要绕着悬崖走一大段路,以对抗随机移动的不利影响。
此外,SARSA 使用时间差异,而政策梯度使用完全回报轨迹——后者具有高得多的方差。因此,我们也需要较低的学习速率 α 来获得稳定的结果。
虽然政策梯度法在这里没有得到好评,但这并不意味着被驳回。像任何算法一样,对某些问题来说,这是一个很好的选择,但对其他问题来说,这是一个很差的选择。这些结果只是作为一个典型的强化行为的例子,可悲的是,它包含了高度的可变性和向局部最优的收敛。一如既往,适当的调整,奖励正常化,功能设计等。对提高性能大有帮助。
但是现在,让我们庆幸我们又一次勇敢地面对了悬崖。
完整的代码,包括其他针对悬崖行走问题的 RL 解决方案,可以在我的 GitHub 上找到:
https://github.com/woutervanheeswijk/cliff_walking_public
外卖食品
- 为了解决悬崖行走问题,我们可以采用离散策略,使用 softmax 策略选择动作。
- 更新依赖于得分函数,该函数是对应于所选动作的特征向量 ϕ(s,a) 减去所有动作的加权特征向量 ϕ(s,⋅) 。
- 逐渐地,策略收敛到每个状态的最佳动作的高概率(在这种情况下是平铺)。
- 由于奖励轨迹的高可变性和高探索性,在这种基本实现中,收敛比基于价值的方法慢。
Q-learning 和 SARSA 的实现:
深度 Q 学习的实现:
首先在离散政策梯度的基础上迎头赶上?查看我的文章最小工作实例和理论推导:
参考
Benderski,E. (2016 年)。soft max 函数及其导数。https://Eli . the green place . net/2016/the-soft max-function-and-its-derivative/
西格尔,E. (2029)。政策梯度。https://siegel.work/blog/PolicyGradient/
西尔弗博士(2020)。第七讲:政策梯度https://www . David silver . uk/WP-content/uploads/2020/03/pg . pdf
萨顿和巴尔托(2018 年)。强化学习:介绍。麻省理工出版社。
威廉姆斯,R. J. (1992)。连接主义强化学习的简单统计梯度跟踪算法。机器学习,8(3–4):229–256。
于,鄂(2017)。用 Python 从零开始的策略渐变。http://quant.am/cs/2017/08/07/policy-gradients/