TowardsDataScience-博客中文翻译-2021-三十二-
TowardsDataScience 博客中文翻译 2021(三十二)
检测大脑发出的哔哔声
行业笔记
开发更好的深部脑刺激治疗方法。
作者: 乔丹·泰勒和梅尔文·雅普(神经科学研究小组在马克斯·凯尔森)
图片编辑自https://unsplash.com/photos/IHfOpAzzjHM。感谢 Robina Weermeijer。
我们利用老鼠大脑中少数神经元的活动来预测它们听到的音频哔哔声的时间。我们为这个问题开发的模型和见解有望最终有助于改善帕金森病和其他神经疾病的治疗。
背景
深部脑刺激正被用于帮助缓解严重帕金森病、肌张力障碍和癫痫的症状(最近在治疗严重抑郁症方面取得了有希望的结果)。它包括将电极插入大脑的特定部位,如图 1 所示,然后调整电脉冲以缓解症状。一旦设置了脉冲模式,它通常会持续发出脉冲。
图 1:脑深部刺激。图片来自https://commons . wikimedia . org/wiki/File:Typical _ deep _ brain _ simulation _ setup . jpg
最近,人们对使用深部脑刺激电极来测量患者大脑中周围神经元的活动越来越感兴趣。如果可以从神经活动中解码症状的严重程度,这可能用于自动调整脉冲响应模式,如图 2 所示。这种想法被称为闭环 或自适应深度大脑刺激。
能够在不植入额外传感器的情况下实施自适应深部脑刺激将需要仅从大脑中局部场电位的几个记录通道来可靠地监测患者的症状。这是一个难题,类似于查看计算机中的几个晶体管并试图猜测恶意软件是否正在运行。我们认为这听起来像是一个适合机器学习(ML)的任务。
已经有很多尝试使用 ML 来解码局部神经信号,一些实际上使用了来自植入深度大脑刺激的人的数据。然而,大多数神经活动的解码使用传统的方法,尽管在许多情况下 ML 被证明是更优越的。我们与昆士兰州大脑研究所合作,开始将 ML 方法应用于他们的一些预先存在的数据,作为探索最适合适应性深度大脑刺激的技术的第一步。
图 2:在自适应深部脑刺激(aDBS)中,将从深部脑刺激电极测量局部场电位(LFP)信号,可能除了皮层脑电图(ECoG)信号之外,还通过一个设备进行处理,该设备通过同一电极返回针对症状定制的脉冲。图片来自https://doi.org/10.3389/fnhum.2020.00054.s001
数据
数据集由暴露于不同音调哔哔声几分钟的老鼠的神经活动组成。图 3 显示了一个会话示例:红色显示哔哔声,白色垂直线显示神经元的放电。在这个数据集中有五只老鼠,每只老鼠有 20-25 次会话,每次会话包含 1-50 个神经元的活动。这些记录是使用一个类似于深部脑刺激电极的电极进行的。
图 3:数据集中的一个会话示例。红色方块是老鼠听到的哔哔声(这里有两种不同的音调)。白色垂直线是神经元放电的时间(这里选取了 9 个不同的神经元)。青色线是我从放电时间中提取的神经元放电率。
我们的重点是从神经活动中解码哔哔声的时间,作为从神经活动中解码症状严重性的第一步。虽然哔哔声来自大鼠基底神经节,而不是人类的海马,并可能引起比帕金森氏症更快和不同的神经反应,但这两种声音都代表了从少数神经元活动中得出的一种推论。
这种数据的主要挑战是其异质性和微小的规模。整个数据集在 114 个会话中只有 15 兆字节。除此之外,每一次会议之间都有很大的差异,每只老鼠都记录了 20 种不同的蜂鸣音,不同数量和类型的神经元,不同的老鼠在一些会议中被限制对一些蜂鸣音模式做出反应,而对其他的则没有。
这些问题不太可能随着更现实的临床数据而消失。获取堆积如山的人体深部脑刺激数据是不可行的,每个患者都会有不同的神经元记录,并表现出不同的反应。因此,我们开发的处理小规模和高度异质性大鼠数据的任何技术也应该适用于人类数据。
无监督学习
首先,我尝试了无监督学习,用托普利兹逆协方差聚类(TICC) 。这是一种专门为多通道时间序列数据开发的无监督聚类技术。它包括学习每个聚类的相关网络,包含该聚类的相邻时间上的数据信道之间的特征相关。每个聚类的相关网络在时间上滑动,每个时间点被放入响应最强的相关网络的聚类中,如图 4 所示。
图 4:基于 Toeplitz 逆协方差的聚类包括学习每个聚类的神经通道和相邻时间之间的相关网络。图片来自 https://arxiv.org/abs/1706.03161
由于大鼠和疗程间数据的异质性,我将 TICC 独立应用于每个疗程。如图 5 所示,TICC 在少数神经元对蜂鸣声有明显反应的时段表现良好。即使在这些阶段,神经元通常只对一种哔哔声有明显的反应,而对另一种则没有。如图 6 所示,在没有明显神经反应的一两次会议中,无监督的集群也与一些蜂鸣声匹配。然而,在大约 85–90%的会话中,集群没有与蜂鸣声对齐。如果哔哔声导致了任何一致的神经活动模式,那么与老鼠脑子里的其他想法相比,这可能是微不足道的。我认为模型需要知道寻找什么,因此需要监督学习。
图 5: TICC 在少数几个时段表现良好,神经元对其中一个蜂鸣音有明显的反应。
图 6: TICC 还揭示了神经信号和一些蜂鸣声之间的对应关系,在一个或两个会话中没有明显的神经响应(17 个神经通道的触发时间未显示)。
监督学习
为了尝试监督学习,我不得不面对异质性和小数据集的问题。由于这些问题,直接将监督学习应用于整个会话产生了差的结果。为了克服它们,我决定只查看小的时间窗口或“片段”,并预测是否会出现哔哔声。以这种方式克服异质性和数据集规模的代价是,每个神经元的反应在短短 40 秒的片段中被有效地视为独立、相同和本地化的。图 7 显示了其中的一些片段。它们要么与哔哔声的开始时间一致,要么与任何哔哔声相隔一段时间。用肉眼对片段进行分类似乎是一项几乎不可能完成的任务:人类不可能比偶然表现得更好。如果有一些微妙的模式,我们希望 ML 能够发现它们。
图 7:每个会话中的神经通道被切割成 40 秒的片段。这些片段要么与哔哔声的开始重合(右),要么不重合(左)。人类在用眼睛辨别这一点上比运气好不了多少。
我首先用在峰间间期系列数据上训练的机器学习模型复制了神经活动分类的一些方法(Lazarevich 等人,2018)。其中包括创建一个神经元放电时间之间的时间间隔列表,称为棘波间隔(ISIs),并使用 tsfresh 统计包自动生成数百个关于训练集的相关统计数据,用作机器学习模型的特征。tsfresh 生成的统计数据包括傅立叶分量、小波变换系数和自相关。我不会在这里解释用于学习这些统计数据的模型,但是它们分为两个主要类别:全局方法和模式包方法,如图 8 所示。
图 8:基于 ISI 统计的神经活动分类方法,使用在峰间间期系列数据上训练的机器学习模型(2018) 。我还尝试了各种关于 ISIs 和 ISI 统计的神经网络(NN)方法。所有器件的精度都低于 XGBoost。
总的来说,我发现基于模式包的方法表现不佳,在不同随机采样片段的平衡数据集上进行训练和测试时,准确率不超过 52%,而 XGBoost(梯度增强库)等全局方法表现更好,准确率高达 61%。这证实了 Lazarevich 等人发现的一般趋势。,其中基于模式袋的方法很难在区分清醒和睡眠神经活动方面达到 58%以上的准确率,而 XGBoost 也表现得最好,准确率超过 70%。与他们的结果相比,我们的结果精度较低,这表明了我们的问题的难度,以及我们数据集的挑战。
为了改进这些结果,我决定尝试以更直接的方式将数据输入到模型中。为了做到这一点,我将每个触发时间片段转换成一个直方图。我发现最好的结果是 400 个直方图仓,每 0.1 秒一个。我再次尝试了许多在这些直方图上学习的 ML 模型,包括密集神经网络(NNs)、1D 卷积神经网络、局部连接神经网络、残差神经网络、长短期记忆神经网络和 XGBoost。我再次发现了 XGBoost 的最佳结果,它在随机采样的片段直方图上实现了 72%的准确率,或者如果给定片段来自哪个大鼠和神经元的额外信息,则达到了 74%。这与基于 ISI 统计的方法的 61%的准确率相比是一个很大的飞跃,比人类能够实现的要好得多,甚至与 Lazarevich 等人(2018) 的清醒/睡眠分类准确率相当。考虑到他们基于统计的方法在我们的数据上表现如此之差,看看我们基于直方图的方法在他们的数据上表现如何会很有趣。
图 9:使用直方图来表示片段中的神经触发时间是一个相当 1:1 的转换,可以达到 74%的准确度。
当然,对随机抽样的神经活动片段进行分类,与提取整个会话中的哔哔声时间相比,还有很长的路要走。首先,模型应该根据早期会话的片段进行训练,并在后期会话中进行测试,而不是根据随机时间的片段进行训练和测试。然而,在这个实验中,老鼠只在后来的训练中被训练(条件反射)来对哔哔声做出反应。在早期阶段训练我们的模型意味着它只看到无条件的老鼠,然后被要求做出预测,即使老鼠的大脑对他们所学的做出反应。当我们只在像这样的无条件大鼠上训练模型时,片段的准确性从 74%下降到 68%,但考虑到数据集的变化,这仍然是一个非常令人印象深刻的结果。
预测蜂鸣声次数
直接尝试在整个会话中运行未经训练的片段直方图 XGBoost 模型来预测哔哔声发生的时间会产生很差的结果(参见图 10)。每半秒钟,模型就会被输入一个神经通道中接下来 40 秒钟的活动直方图。然后,这些预测在神经通道之间进行平均,并使用高斯窗口在时间上进行平均。如果模型预测的音调的平均概率大于某个阈值(例如 52%),则预测音调已经出现。在这些滑动预测中表现不佳的原因可能是因为该模型仅在片段开始时开始的蜂鸣音上进行训练,所以当在整个会话中滑动时,它并不健壮。
图 10:使用在片段直方图上训练的 68%准确的 XGBoost 模型来预测哔哔声发生的时间不会产生好的结果。该模型仅在片段开始时准确开始的哔哔声上进行训练,因此当在整个会话中滑动时,它并不健壮。
通过查看每个片段中的哪些时间对模型最重要,使用有条理的附加解释来说明鲁棒性的缺乏。平均而言,每个片段的前十分之一秒的神经活动对模型决策的影响比任何其他时间都大 10 倍,如图 11 所示。该模型发现,它不需要过多地关注更深层次的神经活动模式,因为前十分之一秒已经告诉了它很多它需要知道的关于哔哔声是否刚刚发生的信息。当模型被要求在整个会话中进行滑动预测时,这种狭窄的焦点正是让模型失望的原因,因为在滑动预测窗口开始的那微小的 0.1 秒期间,神经活动有许多机会看起来相似,从而导致许多错误的预测。
图 11:该模型对片段前十分之一秒的神经活动比对其他任何事情更感兴趣。这可能是导致它缺乏健壮性的原因。
为了使模型在整个会话中更加健壮,我将 40 个样本分布中的每个训练片段从 2 秒前偏移到 2 秒后。这导致片段的准确性较低,但在预测整个会话的蜂鸣时间方面性能更好,如图 12 所示。在某些会话中,当 TICC 不能生成有用的聚类,并且人眼看不到明显的响应模式时,我们的模型的预测甚至是完美的,如图 13 所示。另一方面,似乎有一些时段,神经元真的对一个或多个蜂鸣音没有反应,因此模型很难建立。
图 12:在时间分布上稍微偏移的片段上训练 XGBoost 模型使得它在预测片段时不太准确,但是在预测整个会话中的蜂鸣时间时更加健壮(与图 10 相比)。
图 13:偏移片段 XGBoost 模型的最佳执行会话是完美的,即使 TICC 和人眼看不到与哔哔声相对应的明显模式。
为了量化我们的模型,我们在整个会话测试集上运行它,选择高斯时间平均窗口和蜂鸣声预测阈值,在每个会话的前半部分最大化真阳性,最小化假阳性,然后在每个会话的后半部分评估指标。该模型在每次会话中平均拾取了 47%的蜂鸣声,如果该模型预测到蜂鸣声,则有 41%的机会真的出现蜂鸣声。当然,在某些时段,这是 100%和 100%,如图 13 所示,而在其他时段,这更接近 0%和 0%,这再次说明了不同神经元在不同时间对不同大鼠的不同蜂鸣声的反应有多么不同。
结论
虽然最终结果肯定低于临床级别,但我们已经展示了许多对哔哔声时间的准确预测,即使对人类或其他 ML 模型没有明显的反应。我的主要发现是:
- 对于神经数据,Toeplitz 逆协方差聚类是一种非常有效的无监督方法。在未来的工作中,它应该应用于允许跨会话学习的数据集。
- 作为克服我们数据的异质性和微小规模的一种手段,我们使用片段直方图的方法优于其他已发表的神经解码方法对我们数据的基于统计的方法。
- 通过在大脑信号的基础上输入额外的信息,可以提高模型的准确性。
- 像 XGBoost 这样的梯度推进模型在这种小数据的情况下始终优于所有其他模型。自定义神经网络仍可能凭借大量数据胜出。
- 用匀称的附加解释分析特征的重要性对于了解模型在看什么以及如何使它们更健壮非常有用。
- 向模型提供经过处理的数据可以使其更具概括性和健壮性,这通常比测试准确性更重要。
随着我们越来越接近深度大脑刺激数据,这些见解应该仍然适用,深度大脑刺激数据将共享类似的问题。我们克服数据的小规模和异构性的方法应该特别有用,例如制作片段并操纵它们以打破模型的隧道视野。在大脑信号上添加额外的信息也可以改善自适应深度大脑刺激的结果。
最后,为了改进我们的工作并弥补从检测蜂鸣音到实施自适应深度大脑刺激之间的差距,我们应该:
- 转向更真实的数据,例如从具有扩展症状样活动的大脑记录的原始局部场电位。
- 研究更广泛的技术,如迁移学习和强化学习,这些技术可能对实时自适应深度大脑刺激有用。
最重要的是,我们希望我们的研究最终有助于改善人们的生活。
承认
我们要感谢 Pankaj Sah 的实验室,特别是昆士兰脑研究所的 Alan Woodruff 博士和 Francois Windels 博士提供的数据和讨论的发现。我们还要感谢 Maciej Trzaskowski,以及 Max Kelsen 的所有人,感谢他们在这个项目中的支持和监督。
从卫星图像检测森林砍伐
全栈深度学习项目
Janusz Maniak 在 Unsplash 上拍摄的照片
TL;速度三角形定位法(dead reckoning)
使用来自亚马逊雨林的高分辨率卫星图像和一个良好的 ol'ResNet [1],我们在检测与森林砍伐相关的陆地场景方面获得了超过 95%的有希望的结果,当应用于世界其他地区时,也获得了有趣的结果。我们还展示了一个真实的森林砍伐检测工具的概念验证,包括一个由 Streamlit [2]和 Google Cloud [3]上的数据基础设施组成的仪表板。在本文中,您可以跟随这些见解和整个项目的旅程。
为什么
这次我将从为什么开始😉(这可能是对 Simon Sinek [4]的一个伟大 TED 演讲的老掉牙的引用,也是对所有数据科学爱好者和我妈妈的一个暗示,她们读过我以前的文章[5])。
我们正面临气候变化,这一危机已经影响到我们的生活,除非迅速果断地采取行动,否则可能会让我们所有人陷入大麻烦,对此你可能不会感到惊讶。此外,这在很大程度上是由人类活动引起的,伴随着温室气体的释放,这意味着我们对此负有责任,但也意味着我们有可能解决它。如果你对此仍持怀疑态度,有许多资源可供你参考,但我建议你阅读《明日气候变化指南》和比尔·盖茨的新书《如何避免气候灾难》。
气候变化的原因之一是森林砍伐。当砍伐树木和植物时,我们也在砍伐自然碳汇,也就是说,即使在我们给她带来了所有的垃圾之后,大自然似乎仍想帮助我们的少数部分之一。除了减少吸收二氧化碳的树木,我们还破坏了当地的生态系统,对野生动物、食物来源和水资源储备造成潜在的复杂影响,并可能用更多的污染因素,如牛或化石燃料动力设施,填充现在空置的区域。尽管有这些不利因素,我们仍然面临着世界上一些地方森林砍伐的增加,包括巴西,那里 2020 年的森林砍伐率是过去十年中最高的[8]。
当面对这些担忧时,我们可能会陷入绝望
内德,没必要太紧张。
或者否认。
一刻也不能停下来。
然而,尽管我们必须意识到这个问题的严重性,但我们也应该是顽固的乐观主义者[9],相信我们能够克服这种逆境,并为此采取实际行动。
我们搞定了。
幸运的是,有几项技术创新可以帮助我们减少碳足迹。虽然我们首先想到的可能是可再生能源技术,但我认为卫星也有很大用处。虽然卫星本身不能减排,但它们可以告知我们进展情况,并指导我们的行动计划。经常获得公正的、全球性的见解对于确定优先事项和推进具有坚实基础的政策可能至关重要。在这个意义上,它们的重要性正在得到认可,因为我们看到越来越多更好的遥感卫星被 Planet [10]、Satellite Vu [11]和 Satellogic [12]等发射到太空,以及更多的组织出于可持续发展的目的使用它们,如 Climate TRACE [13]、TransitionZero [14]和 Carbon Mapper [15]。
卫星图像本身并不实用,因为我们不能真的雇佣一些人在整个地球上运行一种闭路电视监控。但是我们可以通过机器学习管道自动提取洞察力。在这方面,我们也很幸运,因为在过去几年里,计算机视觉已经取得了很大的进步,而且,随着我们收集的所有卫星数据,它也开始变得越来越成为一个诱人的机器学习领域。
如果所有这些还不够激励的话,这是我和 Karthik Bhaskar 为全栈深度学习-2021 年春季在线课程开展的一个小组项目。因此,我们也有好奇心以某种完整的方式从头到尾从事机器学习项目。
什么
这个计划
项目建议书,有初步计划。
为了开始这个项目,我们在 idea[16]中建立了一个项目管理工作区,在这里我们可以管理我们的任务,并拥有一个集中的知识库,其中包含我们所有的笔记和我们项目的相关链接(这在撰写一篇关于它的文章时会派上用场😁).你实际上可以检查那些页面[17],因为我们把一切都公开了。
通过这个平台,我们开始讨论我们的动机、我们想要实现的目标以及可能的项目想法。正如您在上一节中看到的,砍伐森林的主题对我们来说非常有趣,因为我们为这项任务找到了高质量的数据集(您将在后面看到),这似乎是一个可行的项目。在一些聊天和视频通话后,我们开始定义我们的 MVP(最小可行项目),即我们想要实现的基本功能,以及一些额外的目标。
最有价值球员
对于 MVP,我们决定了以下项目:
- 模型经过训练,在检测森林砍伐方面具有可接受的性能。(什么是“可接受的性能”是通过与基线比较来确定的)
- 仪表板显示我们数据集上的预测。
- GitHub 包。
- 超参数调谐。(即使用优化工具来改进我们的模型)
- 数据存储。(即,为我们的项目需求设置强大的数据存储解决方案)
我们觉得这些最低目标已经满足了我们以端到端的方式从事机器学习项目的愿望,涵盖了编码、数据工程、建模和部署的许多重要方面。
奖金
至于奖金,就像在时间和可用性允许的情况下最好拥有的东西一样,我们定义如下:
- 域外 / 分布转移分析 —研究我们的模型在不同于其训练数据的数据上的性能。
- 数据飞轮 —建立一个系统,定期收集新数据以改进模型,这可以在用户的帮助下完成。
- 仪表板中的可解释性—允许用户更好地理解模型如何导出每个输出。
- 测试我们的包。
- 性能比 MVP 车型更高的车型。
虽然我们对所有这些额外的里程碑都很感兴趣,但我们对进行一些域外性能分析特别感兴趣,考虑到它可以对我们的模型进行很好的验证,以及多个与森林砍伐相关的数据集的可用性和数据飞轮[18]。最近,这一点已经被谈论了很多,包括 Andrej Karpathy 和他的项目 vacation [19],它似乎是人工智能的一种发展方向,作为一种自动化,它可以在机器学习的后期阶段提供帮助,因为进一步改进模型变得越来越难,而在初始阶段,我们可能没有足够的数据来训练足够好的模型。
怎么
数据集
为了训练一个可以从太空中检测森林砍伐的模型,我们需要一些带标签的数据,由卫星图像和标签组成,这些数据应该与森林砍伐的存在与否有关。正如我们所讨论的,虽然卫星图像的数量在不断增加,但标记的数据仍然相对较少。如果我们只搜索看起来与我们的目标完全一样的数据集,例如每个图像都有一个单一的二进制deforestation
标签,我们可能会发现这很难。所以首先,为了拓宽我们可能的解决方案,我们需要定义什么是森林砍伐,至少从上面的视觉信号来看。话虽如此,我们还是决定搜索那些标注了伐木或近期人类在自然生态系统中的潜在开发活动(如农业、采矿和城市扩张)的数据集。
根据设定的标准,我们的搜索引导我们找到了这三个数据集:
- 从太空了解亚马逊【20】—追踪亚马逊雨林中人类足迹的多标签数据集;我们通常称之为亚马逊数据集。
- WiDS data thon 2019【21】—婆罗洲油棕榈种植园检测的二进制数据集;我们通常称之为油棕数据集。
- 检测森林砍伐【22】—用于检测亚马逊雨林中咖啡种植园的二进制数据集;我们通常称之为咖啡数据集。
我们主要关注的是亚马逊数据集,但在下一节中您会发现原因。
EDA 和数据处理
像数据科学中的通常情况一样,在深入研究建模之前,我们从探索性数据分析(EDA)开始,它可以指导我们定义问题、潜在的解决方案,以及在训练模型之前我们需要的预处理步骤。让我们在这里分析三个潜在的数据集,以及我们决定对哪一个数据集做什么。
鉴于其规模和质量,亚马逊数据集立即引起了我们的注意。如果不算 Kaggle 不共享标签的测试集,这里有超过 40k 个样本,有 17 个独立的标签,从农业到采矿,城市基础设施,自然景观和天气。大小和上下文相关的标签使该数据集成为模型训练的良好候选。
它包含了 2016 年 1 月至 2017 年 2 月期间收集的亚马逊雨林各地的样本,包括巴西、秘鲁、乌拉圭、哥伦比亚、委内瑞拉、圭亚那、玻利维亚和厄瓜多尔。所有图像都来自行星卫星,空间分辨率为 3 米(每个像素代表地面上 3 米)。这些信息之所以相关,不仅仅是因为它让我们感觉到数据集中在空间和时间上的多样性,还因为如果我们想要将基于它训练的模型与域外数据(可能在不同的时间和/或地点和/或卫星上)进行比较,它可能证明是有用的。
这些图像以两种格式给出:典型的 RGB 图像和 TIFF 文件,具有额外的近红外波段。虽然 TIFF 文件可能有用,但考虑到额外的波段,我们决定坚持使用 RGB 图像,因为它们简单(所有图像都已经过预处理,像素值在 0 到 255 之间),并且在处理来自其他数据集和用户的其他图像时具有灵活性。无论哪种方式,该数据集的另一个简化功能是所有图像都具有相同的 256 x 256 大小。
亚马逊数据集中像素值的近似分布。
由于其他数据集不太可能采用相同的标注方法,并且我们对突出森林砍伐事件和风险感兴趣,我们将相关标签映射为与森林砍伐相关,如下所示。
亚马逊数据集的所有标签以及与森林砍伐相关的地图。
把habitation
当成毁林标签可能不值得。有这个标签的样本似乎大多是一些时间以前就存在的城市或村庄,因此它们可能与检测当前的森林砍伐无关。
亚马逊数据集中的标签分布。记住每个标签都是独立的,除了天气标签。
大部分与森林砍伐相关的标签(除了agriculture
、road
、cultivation
之外)似乎都很少见(即< 1k 样本)。但是将它们结合在一起,我们仍然可以获得相当数量的阳性样本,从而产生 15719 个具有森林砍伐信号的样本,代表大约 38.83%的标记数据→稍微平衡的数据集🙂
油棕榈数据集对我们来说很奇怪。它与亚马逊有很多相似之处,包括使用相同的卫星,相同的图像格式和分辨率,以及类似的探测森林砍伐的目的。但它也有一些显著的差异,主要是因为它的焦点更窄,只有一个标签用于检测油棕种植园,而且它位于世界的另一个地方,所有图像都来自东南亚的婆罗洲岛,而不是南美洲的亚马逊雨林。它目前也只有大约 2k 的标记图像,远低于亚马逊数据集的 40k,主要是因为标签和图像文件之间存在不匹配的问题[23]。
油棕榈数据集中像素值的近似分布。请注意,与亚马逊数据集相比,图中的峰值向左移动,像素值降低。
尽管存在问题,但成像的相似性和数据分布的差异使油棕榈数据集成为域外测试集的一个好选择。此外,数据集的标签has_oilpalm
在 1089:1089 的样本上完全平衡。
虽然咖啡数据集有可能成为至少一个像油棕榈数据集一样的域外数据集,也使用相同的卫星并代表种植园检测的二元任务,但它有太多的问题在我们的项目中不实用。作者降低了图像分辨率并将其转换为灰度,这是与其他数据集不兼容的限制,我们可能需要牺牲很多模型性能来标准化所有图像以适应这些限制。因此,我们丢弃了这个咖啡数据集。
因此,概括一下,我们使用亚马逊数据集作为主要数据集,在那里我们训练我们的模型,油棕榈数据集作为额外的测试集,使用域外数据。
建模
正如在整个全栈深度学习课程中所建议的那样,拥有基线非常重要,这样才能知道在开发我们的模型时会有什么预期以及要比较什么。在我们的例子中,很容易找到基线,因为我们在 Kaggle 比赛中使用的数据集中训练我们的模型,也就是说,我们有几个提交来进行比较。了解这一点后,首先要确保我们计算的性能指标与竞争中使用的相同,这样我们才能充分比较我们的解决方案和排行榜。亚马逊数据集中使用的指标是 F2 得分,这是一个结合了精确度和召回率的指标,尽管召回率的权重更高。这意味着,为了让我们的模型得分高,一个主要的标准是它有很少的假阴性。考虑到我们对检测森林砍伐的兴趣、假阳性的低风险,以及有森林砍伐的样本比没有森林砍伐的样本更少的事实,这个指标在这里似乎也有意义。
F2 分数,其中较高的权重被给予回忆,即具有很少的假阴性。
为了实际开发这个模型,当我们在一个小组中工作时,我们从两种不同的方法开始。我们中的一个人尝试在 TensorFlow [24]上训练模型,另一个人在 FastAI [25]上训练模型。对于 TensorFlow 方法,我们尝试了更强大的管道,首先用笔记本测试模型对小批量的过度拟合(正如课程中所建议的,这似乎是一个好主意,可以确认模型对给定数据的预期效果),通过权重和偏差进行模型跟踪,选择使用在其他遥感数据集上预先训练的模型[26],基于验证性能的早期停止和自动学习率衰减,以及嵌套在整洁的训练脚本中的其他功能。然而,事实证明 FastAI 让我们获得了更好的结果,即使没有其他方向的所有花哨功能。因此,我们专注于使用 FastAI 模型。
有时候更简单的解决方案是最好的解决方案。
那么我们为 FastAI 模型做了什么呢?它只是采取了这些简单的步骤:
- 设置 FastAI 数据加载器,批量大小为 256,图像大小调整为 128 x 128,以及一些简单的数据扩充,包括图像翻转、亮度、缩放和旋转。
- 将准确度和 F2 分数设置为指标。
- 定义一个 ResNet50 模型。
- 搜索最佳初始学习率。
- 以固定 4 个时期的最佳学习速率开始训练模型,然后以衰减的学习速率再训练 6 个时期。
训练我们 FastAI 模型的核心部分。你可以在的 Colab【28】中看到剩余的代码并亲自尝试。
正如你所看到的,大多数建模步骤都由 FastAI 负责,这使得我们的代码很短,开发过程也很快(我想知道它们的名字是从哪里来的🤔).这确实有它的缺点,因为它锁住了其他不太抽象的框架所提供的一些灵活性和定制性。但是现在可以了。
还要注意我们是如何选择 ResNet50 模型的。虽然这种架构来自 2015 年,在现代快节奏的研究世界中已经可以被视为是旧的,但它的效率和广泛的社区支持使它非常实用,至少作为计算机视觉任务的初始迭代。最近的模型,如 ViT [27]和insert what model is state of the art today在大型数据集上表现出更好的性能,但它们非常大的尺寸可能使它们无法用于较小的数据集和/或个人硬件设置,此外,有时还没有公开的模型权重和/或代码。
正如你在我们的笔记本【28】中看到的,我们的模型在验证集上取得了 92.7% 的 F2 分和 95.6% 的准确率。这些结果本身看起来不错,当我们看到 Kaggle 上的排行榜冠军获得 93.3%的 F2 分数时,我们也很高兴。它仍然比我们的高,并且在一个稍微不同的集合上,因为它是在 Kaggle 的测试集上计算的(我们没有提交)。但尽管如此,这种表现似乎足够好,让我们从建模继续前进。
我还想指出的是,当在油棕榈数据集上运行推断并作为二元毁林任务计算分数时(使用我们之前讨论的毁林映射),我们得到了 66.8%的准确性和 86.7%的 F2 分数。我们确实预计性能会比训练模型的数据集差,但这仍然显示了一些有趣的见解。首先,该模型必须有很少的假阴性,才能以较低的准确性获得如此高的 F2 分数,这意味着它可能仍然可以指出毁林事件。另一方面,这些结果也告诉我们,该模型有很多假阳性。这可能是由几个因素造成的,包括数据集之间的区域差异,以及可能略有不同的图像预处理。我们也已经看到了这个油棕榈数据集的图像往往比亚马逊数据集中的像素值低,这可能会欺骗我们的模型认为样本是阳性的。我们可以尝试几个选项来提高我们的模型在该数据集上的性能,例如标准化两个数据集上的像素分布,或者在训练中包括来自该数据集的一些样本,但我们现在将保持原样。
仪表盘
我们希望在项目结束时有一个仪表板,这样我们就可以有一个结合所有部分的交互式演示,并作为我们端到端项目中的一个简单部署步骤。考虑到它的易用性和我们对学习它的兴趣,我们决定使用 Streamlit 来开发仪表板。但是在深入编码之前,我们需要看完整个画面,想想我们希望我们的仪表板是什么样子的。对于其核心功能,我们希望包括:
- 用户输入 —用户在他们的图像上运行我们的模型的选项。
- 聚合性能 —显示我们的模型在亚马逊数据集上的全局结果。
- 样本探索 —显示每个样本的模型结果。
- 域外性能 —显示油棕榈数据集的模型结果。
- (奖金)数据飞轮——收集可以帮助我们改进模型的用户数据。
当我们开发一个仪表板时,我们希望它是直观的和视觉上吸引人的,我们也应该预先考虑 UI 本身。所以,我们想到了两页纸,并草拟了草稿:
- 游乐场 —这是专门为用户制作的初始页面,用户可以在他们的图像上尝试模型;我们也可以使用这个页面来存储用户上传的图片和他们的反馈。
- 概述 —用于查看我们的模型性能和数据集数据特征的页面。
仪表板的操场(左)和概览(右)页面的草稿。这体现了 AUC 指标,而不是 F2 分数,然后当我们优先考虑该指标时,F2 分数被替换。
请注意,我们遵循 Streamlit 应用程序的典型设计准则,因为页面内容包含所有结果和图表,而用户输入在侧边栏中设置。
这些草案与最终的仪表板在一些细节上有所不同,但我想强调的是,与草案所建议的相反,我们只是使用了一个模型,这个模型被训练来预测亚马逊数据集的 17 个类别。这里是最终仪表板 [29]:
最终仪表板上的操场页面。
最终仪表板上的概览页面。
这些看起来和草稿相似,对吗?😃我还想指出一些额外的功能:
- 几个信息卡,引导用户通过仪表板,使它更直观。
- 如果输出与森林砍伐无关,则显示为绿色,否则显示为红色。
- 实现了一个简单的数据飞轮,因为我们可以保存用户给我们的图像,以及模型的输出和用户的反馈。我们还给用户一个按钮来自动删除我们在交互中存储的所有数据。
- 缓存和过滤数据子集用于提高应用程序的性能。
仪表板已经完全部署好,随时可供每个人探索,因为我们依靠 Streamlit 共享服务【30】来轻松发布它。
数据存储
对于仪表板的部署,我们希望它对于任何尝试使用它的人来说都尽可能实用。务实意味着我们应该避免强迫每个人下载完整的数据集到他们的电脑上来尝试。随着我们转向 Streamlit 共享方向,务实也意味着我们不会只是试图将所有数据集放在我们的 GitHub 存储库中。最实际的解决方案是将我们的数据存储在云中,仪表板通过连接到我们的数据库,只在需要的时候访问它需要的东西。因为我们想从用户那里收集数据,我们无论如何都需要一个集中的数据库,所以还不如把所有东西都包含在我们的数据基础设施中。
考虑到我们的项目,我们的数据需求是:
- 存储标签表,即将标签关联到每个图像的数据帧。
- 存储我们数据集中的所有图像。
- 存储用户的数据,其中包括他们的图像、模型在其上的输出、用户的反馈以及额外的数据,如时间戳和唯一标识符。
我们发现谷歌云满足了所有这些要求,因为我们可以按照以下方式组织我们的数据:
- 将标签表存储在 BigQuery 表中。
存储在 BigQuery 表中的 Amazon 数据集的标签。
- 将图片存储在谷歌云存储桶中。
亚马逊数据集的图像存储在谷歌云存储桶中。
- 将用户的表格数据存储在 BigQuery 中,将他们的图片存储在bucket中。
存储在 BigQuery 表中的用户数据。图像存储在一个单独的桶中,但是它们通过 image_id 列链接到这个表。
除了拥有存储数据所需的工具,Google Cloud 还很好地集成了 Python,这使得读写数据变得很容易。
从 Python 加载数据和上传数据到 Google Cloud 的例子。
虽然该数据基础设施由我们管理,但它可以在用户上传和删除数据时自行更新,如仪表板上的前一部分所示。因此,我们需要确保仪表板具有连接到我们的数据库的凭证,而不会公开泄露它们。幸运的是,通过 Streamlit 的秘密管理[31],这很容易做到。
决赛成绩
期末专题提交的视频。
在经历了我们所做的一切之后,让我们回到我们开始要做的事情,看看我们成功实现了什么。
从 MVP 来看:
- 经过训练的模型在检测森林砍伐方面具有可接受的性能
☑️训练的模型在与森林砍伐相关的分类任务上具有 95.6%的准确性和 92.7%的 F2 分数,其性能可与 Kaggle challenge 中的排行榜冠军相媲美。 - 仪表板为了显示我们数据集上的预测
☑️开发了一个带有 Streamlit 的仪表板,它不仅显示数据和模型结果的概览,还允许用户输入。 - GitHub 包
- 超参数调整
☑️对学习率进行了一次短暂的调整。 - 数据存储☑️建立了一个谷歌云工作区来处理我们所有的数据需求。
所以所有的基本条件都满足了🙂
奖金呢?
- 域外 / 分布偏移分析
☑️ 分析了我们的模型在油棕数据集上的表现。 - 数据飞轮 ☑️设置了一个数据库和 UI,允许收集用户的输入,这些输入可以用来进一步改进模型。
- 仪表板中的可解释性
❌无法在我们的 FastAI 模型上运行 SHAP [33],一个流行的可解释性库。 - 我们的包
的测试❌没有集成任何定制测试,无论是代码、模型还是数据。 - 性能比 MVP 车型有所提升的车型
❌没有对该车型进行更多的迭代;然而,它在亚马逊数据集上已经有了令人印象深刻的表现。
我们还完成了两项奖金!🎉
为适度的成功而欢呼!
但在前往庆祝派对之前,我们应该对发生的事情和接下来可能采取的措施进行更多的反思。
经验教训
牛逼赢了
- 即使在今天,ResNet 的巨大成果也一直让我惊讶,用一个简单的 ResNet 模型获得巨大成果是多么容易;至少对于不太复杂的任务,诀窍似乎越来越依赖于数据质量而不是模型质量。
- Streamlit “简单而有用——第一次接触这款工具时,它非常容易学习,而且事实证明,从理想化仪表板到创建甚至部署它,这是一个快速的过程。
- Google Cloud 的完整性——至少在数据存储方面,Google Cloud 似乎真的为大多数用例提供了解决方案,并且具有良好的 Python 集成。
洞察力失败
- FastAI ,它的局限性,以及探索未知领域的危险——这是另一个我个人以前没有用过的框架,尽管我的队友 Karthik 用过;与大多数其他工具相反,这个工具给我们带来了一些困难,特别是它的低灵活性、混乱的文档和相对较低的社区支持(例如,无法找到让它与 SHAP 一起工作的方法)。
- 与 TensorFlow 的过于雄心勃勃的冒险——我们仍然花了相当长的时间试图在 TensorFlow 中开发一个强大的模型训练管道,只是看到 FastAI 让我们获得了更好的结果,快得多;下次可能需要更好地重新考虑建模优先级及其时间线。
- Streamlit 过于简化的脚本运行——和 FastAI 一样,Streamlit 的简单可能是一个缺点;因为 Streamlit 在每次仪表板中有变化时都会重新运行整个脚本,比如用户输入,所以很难让它像期望的那样快,也很难防止它崩溃;对于复杂的应用程序,我们需要注意缓存和性能技巧。
- 未维护的数据集 —我们在油棕榈数据集中遇到了一些明显不正确或过时的文件问题,但从未得到作者的回应。
- 疾病、摔坏的笔记本电脑等来自地狱的故事——人生无常,墨菲定律邪恶;尽管我们设法实现了大部分我们想要做的事情,并且仍然相信我们的项目计划思维,我们应该考虑更详细地讨论最坏的情况,准备 b 计划,并且可能重新调整我们对完成某些任务所需时间的估计。
未来的工作
这个项目有几个方面可以改进。我们认为,主要的是这些:
- 包括可解释性分析与 SHAP——增加可解释性可以帮助理解模型的决策,这反过来可以帮助我们调试它,获得对它的信任,并带来更愉快的体验。
- 改进我们的模型,可能会有更多的数据和超参数调整—我们仍然没有获得排行榜的最佳性能,来自域外数据集的指标也不是很好。
- 实施适当的模型跟踪,例如通过权重&偏差【34】——随着新模型被训练,记录实验和比较它们变得更加重要。
- 添加代码、模型和数据测试——这在一个更长期、更成熟的项目中感觉至关重要,以确保在进一步的开发和使用中一切都按预期运行;全栈深度学习课程在这方面有很棒的讲座[35]。
- 让仪表盘更快更有性能——有时候仪表盘仍然感觉很慢,我们不得不做出一些牺牲来确保它的实用性和不崩溃;我们可以深入调查。
参考
[1]何等,深度残差学习用于图像识别 (2015)
[2] 流线型
[3] 谷歌云
[4]西蒙·西内克,伟大的领导者如何激励行动 (2009),TED 演讲
[5] André Ferreira,解读多元时间序列上的递归神经网络 (2019),走向数据科学
[6]奥利维尔·科拉迪,气候变化——实用指南 (2020),明日博客
[7]比尔·盖茨,如何避免气候灾难 (2021),阿尔弗雷德·a·克诺夫
[8] Celso H. L. Silva Junior 等人,2020 年巴西亚马逊森林砍伐率是十年来最大的 (2020),自然
[9]克里斯蒂安娜·菲格雷斯,(2020)气候顽固乐观主义案例,TED 演讲
[11] 卫星 Vu
[12] 卫星逻辑
[13] 气候痕迹
[14] 跃迁零点
[15] 碳测绘仪
[16] 观念
[17] 安德烈·费雷拉和卡蒂克·巴斯卡尔的观念工作空间 (2021)
[18] Josh Tobin 等人,讲座 5:人工智能项目 (2021),全栈深度学习—2021 年春季
[19]安德烈·卡帕西, PyTorch at Tesla (2019),PyTorch DevCon 2019
[20] 星球:从太空了解亚马逊 (2017),卡格尔
[21]WiDS data thon 2019(2019),Kaggle
[22] 朝向检测森林砍伐 (2020),卡格尔
[23] CP_Padubidri,数据集—不匹配 (2021),Kaggle
[24] 张量流
[26] 张量流枢纽遥感
[27] Alexey Dosovitskiy 等人,一幅图像抵得上 16x16 个字:用于图像识别的变形金刚 (2021),
[28]卡希克·巴斯卡尔,FSDL _ 最终 _ 模型笔记本 (2021),Colab
[29]安德烈·费雷拉,项目仪表板 (2021),Streamlit
[30] Adrien Treuille,介绍 Streamlit 共享 (2020),Streamlit 博客
[31]詹姆斯·汤普森,为你的 Streamlit 应用程序添加秘密 (2021),Streamlit 博客
[32] André Ferreira 和 Karthik Bhaskar,项目存储库 (2021),GitHub
[33] Scott Lundberg 等人,解释模型预测的统一方法 (2017),NIPS 2017
[35] Josh Tobin 等人,讲座 10:测试&可解释性 (2021),全栈深度学习—2021 年春季
从推文中检测灾难(经典的 ML 和 LSTM 方法)
克里斯·j·戴维斯在 Unsplash 上的照片
使用 NLP 并比较两种方法的分类任务。
在本文中,我将应用两种不同的方法来完成分类任务。我将首先应用使用梯度推进分类器的经典机器学习分类算法。在代码的后面,我将使用 LSTM 技术来训练一个 RNN 模型。因为我们正在处理推文,所以这是一项 NLP 任务,我将分享一些技术,这样你将更加熟悉大多数 NLP 项目中的一些常见步骤。
我将使用来自 Kaggle 挑战赛的数据,该挑战赛名为“自然语言处理灾难推文”。你可以在下面链接的“数据”部分找到“ train.csv ”文件。
https://www.kaggle.com/c/nlp-getting-started/overview
数据集有 5 列。列“ target ”是标签列,这意味着我将使用其他列,如“ text ”、“ location ”和“ keyword ”来训练一个可以预测列“ target 的值的模型。现在,首先让我们了解每一列的含义:
id
-每条推文的唯一标识符text
-推文的文本location
-发送推文的位置(可能为空)keyword
-推文中的特定关键词(可能为空)target
-仅在 train.csv 中,这表示一条推文是否是关于一场真正的灾难(1
)或者不是(0
)
对于这个任务,我将使用 Sklearn 和 Keras 等库来训练分类器模型。 Sklearn 用于使用梯度增强分类器训练模型,而 Keras 用于训练 LSTM 模型。
**import** **pandas** **as** **pd**
**import** **numpy** **as** **np**
**import** **matplotlib.pyplot** **as** **plt**
**import** **seaborn** **as** **sns**
**import** **re**
**import** **nltk**
nltk.download('stopwords')
**from** **nltk.corpus** **import** stopwords
**from** **nltk.tokenize** **import** word_tokenize
**from** **nltk.stem** **import** SnowballStemmer
**from** **sklearn** **import** model_selection, metrics, preprocessing, ensemble, model_selection, metrics
**from** **sklearn.feature_extraction.text** **import** CountVectorizer
**import** **tensorflow** **as** **tf**
**from** **tensorflow.keras.models** **import** Model
**from** **tensorflow.keras.preprocessing.text** **import** Tokenizer
**from** **tensorflow.keras.preprocessing.sequence** **import** pad_sequences
**from** **tensorflow.keras.layers** **import** Conv1D, Bidirectional, LSTM, Dense, Dropout, Input
**from** **tensorflow.keras.optimizers** **import** Adam
了解数据:
对于此任务,我们仅使用' train.csv '并将它分解为训练和测试数据集两部分。我将把数据加载到 Pandas Dataframe 中,并查看前几行。
*# Rreading train dataset*
file_path = "./train.csv"
raw_data = pd.read_csv(file_path)
print("Data points count: ", raw_data['id'].count())
raw_data.head()
首先,我想更熟悉数据集,以了解特性(列)。列"目标"是我们的模型将要学习预测的列。由于它只有两个唯一值0
和1
,因此这是一个二元分类任务。我想知道标签为0
与1
的推文的比例,所以让我们基于列“目标”绘制数据。
作者图片
正如你所看到的,标签为0
的数据点较多,表示推文并非灾难推文,标签为1
的数据点较少,表示推文与灾难相关。通常,对于有一些倾斜标签的数据,建议使用 F 分数而不是准确性来进行模型评估,我们将在本文的结尾解决这个问题。
接下来,我想知道我们的数据集中每一列的缺失数据点的情况。下面的热图显示“关键字”列几乎没有缺失的数据点,我将填充缺失的数据点,并将此列用作一个特征。列位置数据缺失严重,数据质量很差。它具有与位置无关的值。所以我决定不使用这个专栏,不再写了。列" text "是包含需要处理和清理的实际 tweet 的主列。它没有丢失数据。
作者图片
我也注意到有些推文包含不到 3 个单词,我认为两个单词的句子可能无法很好地传递知识。为了了解句子的字数,我想看一下每个句子的字数直方图。
作者图片
正如我们所见,大多数推文都在 11 到 19 个单词之间,所以我决定删除少于 2 个单词的推文。我相信三个单词的句子足以说明这条推文。删除超过 25-30 个单词的推文可能是个好主意,因为它们可能会减慢训练时间。
数据清理和预处理:
在处理 tweets 的 NLP 任务中,常见的数据清理步骤是删除特殊字符、删除停用词、删除 URL、删除数字和进行词干处理。但是,让我们首先更熟悉一些 NLP 数据预处理概念:
矢量化:
单词矢量化是一种将单词映射到实数的技术,或者更好地说是实数的向量。我使用过 Sklearn 和 Keras 库中的矢量化工具。
标记化:
记号化的任务是将一个短语(可以是任何东西,比如一个句子、一个段落或者仅仅是一个文本)分解成更小的部分,比如一系列单词、一系列字符或者一系列子单词,它们被称为记号。标记化的一个用途是从文本生成标记,然后将标记转换为数字(矢量化)。
填充:
神经网络模型要求输入具有相同的形状和大小,这意味着一个接一个输入到模型中的所有 tweets 必须具有完全相同的长度,这就是填充在这里有用的地方。数据集中的每条推文都有不同的字数,我们将为每条推文设置最大字数,如果推文较长,那么如果推文的字数少于最大字数,我们可以用固定值如“0”填充推文的开头或结尾。
词干:
词干提取的任务是将单词中多余的字符减少到单词的词根或词根。例如,词干中的“工作”和“已工作”都变成了“工作”。
我使用了雪球斯特梅尔,这是一种词干算法(也称为 Porter2 词干算法)。这是波特斯特梅尔的一个更好的版本,因为在这个词干分析器中修复了一些问题。
单词嵌入:
单词嵌入是对文本的学习表示,其中具有相同含义的单词具有相似的表示。每个单词被映射到一个向量,向量值以类似于神经网络的方式被学习。
现在让我们来看看完整的数据清理代码:
**def** clean_text(each_text):
*# remove URL from text*
each_text_no_url = re.sub(r"http\S+", "", each_text)
*# remove numbers from text*
text_no_num = re.sub(r'\d+', '', each_text_no_url)
*# tokenize each text*
word_tokens = word_tokenize(text_no_num)
*# remove sptial character*
clean_text = []
**for** word **in** word_tokens:
clean_text.append("".join([e **for** e **in** word **if** e.isalnum()]))
*# remove stop words and lower*
text_with_no_stop_word = [w.lower() **for** w **in** clean_text **if** **not** w **in** stop_words]
*# do stemming*
stemmed_text = [stemmer.stem(w) **for** w **in** text_with_no_stop_word]
**return** " ".join(" ".join(stemmed_text).split())
raw_data['clean_text'] = raw_data['text'].apply(**lambda** x: clean_text(x) )
raw_data['keyword'] = raw_data['keyword'].fillna("none")
raw_data['clean_keyword'] = raw_data['keyword'].apply(**lambda** x: clean_text(x) )
为了能够同时使用" text 和" keyword "列,有多种方法可以应用,但我应用的一个简单方法是将这两个特性组合成一个新特性,称为" keyword_text "
*# Combine column 'clean_keyword' and 'clean_text' into one*
raw_data['keyword_text'] = raw_data['clean_keyword'] + " " + raw_data["clean_text"]
我已经用 Sklearn 的“ train_test_split ”函数做了一个带数据洗牌的训练和测试分割。
feature = "keyword_text"
label = "target"
*# split train and test*
X_train, X_test,y_train, y_test = model_selection.train_test_split(raw_data[feature],raw_data[label],test_size=0.3,random_state=0,shuffle=**True**)
正如我已经提到的矢量化,我们必须将文本转换为数字,因为机器学习模型只能处理数字,所以我们在这里使用“反矢量化”。我们对训练数据进行拟合和转换,并且只对测试数据进行转换。确保测试数据没有出现拟合现象。
*# Vectorize text*
vectorizer = CountVectorizer()
X_train_GBC = vectorizer.fit_transform(X_train_GBC)
x_test_GBC = vectorizer.transform(x_test_GBC)
梯度增强分类器:
梯度推进分类器是一种机器学习算法,它将许多弱学习模型(如决策树)结合在一起,以创建强预测模型。
model = ensemble.GradientBoostingClassifier(learning_rate=0.1,
n_estimators=2000,
max_depth=9,
min_samples_split=6,
min_samples_leaf=2,
max_features=8,
subsample=0.9)
model.fit(X_train_GBC, y_train)
评估我们模型性能的一个很好的指标是 F-score。在计算 F 分数之前,让我们先熟悉一下精度和召回。
精度:在我们正确标注阳性的数据点中,有多少我们正确标注阳性。
回忆:在我们正确标记为阳性的数据点中,有多少实际上是阳性的。
F-score: 是查全率和查准率的调和平均值。
*# Evaluate the model*
predicted_prob = model.predict_proba(x_test_GBC)[:,1]
predicted = model.predict(x_test_GBC)
accuracy = metrics.accuracy_score(predicted, y_test)
print("Test accuracy: ", accuracy)
print(metrics.classification_report(y_test, predicted, target_names=["0", "1"]))
print("Test F-scoare: ", metrics.f1_score(y_test, predicted))Test accuracy: 0.7986784140969163
precision recall f1-score support
0 0.79 0.88 0.83 1309
1 0.81 0.69 0.74 961
accuracy 0.80 2270
macro avg 0.80 0.78 0.79 2270
weighted avg 0.80 0.80 0.80 2270
Test F-scoare: 0.7439775910364146
作者图片
混淆矩阵是显示分类模型与两个类别相比的性能的表格。正如我们在图中看到的,我们的模型在检测目标值“0”时比检测目标值“1”时具有更好的性能。
LSTM:
LSTM 代表长期短期记忆网络是一种 RNN(递归神经网络),能够学习长期依赖关系,它们可以长时间记住信息,因为设计了内部记忆系统。
我已经在上面谈到了单词嵌入,现在是时候把它用于我们的 LSTM 方法了。我用的是斯坦福的手套嵌入,你可以从这里的下载。在我们读取手套嵌入文件后,我们使用 Keras 创建一个嵌入层。
*# Read word embeddings*
embeddings_index = {}
**with** open(path_to_glove_file) **as** f:
**for** line **in** f:
word, coefs = line.split(maxsplit=1)
coefs = np.fromstring(coefs, "f", sep=" ")
embeddings_index[word] = coefs
print("Found **%s** word vectors." % len(embeddings_index))*# Define embedding layer in Keras*
embedding_matrix = np.zeros((vocab_size, embedding_dim))
**for** word, i **in** word_index.items():
embedding_vector = embeddings_index.get(word)
**if** embedding_vector **is** **not** **None**:
embedding_matrix[i] = embedding_vector
embedding_layer = tf.keras.layers.Embedding(vocab_size,embedding_dim,weights[embedding_matrix],input_length=sequence_len,trainable=**False**)
对于 LSTM 模型,我从一个嵌入层开始,为每个输入序列生成一个嵌入向量。然后,我使用了一个卷积模型来减少特征的数量,然后是一个双向 LSTM 层。最后一层是致密层。因为这是一个二元分类,我们使用s 形作为激活函数。
*# Define model architecture*
sequence_input = Input(shape=(sequence_len, ), dtype='int32')
embedding_sequences = embedding_layer(sequence_input)
x = Conv1D(128, 5, activation='relu')(embedding_sequences)
x = Bidirectional(LSTM(128, dropout=0.5, recurrent_dropout=0.2))(x)
x = Dense(512, activation='relu')(x)
x = Dropout(0.5)(x)
x = Dense(512, activation='relu')(x)
outputs = Dense(1, activation='sigmoid')(x)
model = Model(sequence_input, outputs)
model.summary()
对于模型优化,我使用了 Adam 优化,用 binary_crossentropy 作为损失函数。
*# Optimize the model*
model.compile(optimizer=Adam(learning_rate=learning_rate), loss='binary_crossentropy', metrics=['accuracy'])
模型训练完成后,我想看看训练准确性和损失的学习曲线。该图显示了随着每个时期模型精度的增加和损失的减少。
作者图片
现在我已经训练了模型,所以是时候评估它的模型性能了。我将得到模型的准确性和测试数据的 F 值。因为预测值是介于 0 和 1 之间的浮点数,所以我用 0.5 作为阈值来区分“0”和“1”。
*#Evaluate the model*
predicted = model.predict(X_test, verbose=1, batch_size=10000)
y_predicted = [1 **if** each > 0.5 **else** 0 **for** each **in** predicted]
score, test_accuracy = model.evaluate(X_test, y_test, batch_size=10000)
print("Test Accuracy: ", test_accuracy)
print(metrics.classification_report(list(y_test), y_predicted)) Test Accuracy: 0.7726872
precision recall f1-score support
0 0.78 0.84 0.81 1309
1 0.76 0.68 0.72 961
accuracy 0.77 2270
macro avg 0.77 0.76 0.76 2270
weighted avg 0.77 0.77 0.77 2270
正如我们在混淆矩阵中看到的,RNN 方法的表现与梯度推进分类器方法非常相似。该模型在检测“0”方面比检测“1”做得更好。
作者图片
结论:
如您所见,两种方法的输出非常接近。梯度推进分类器的训练速度比 LSTM 模型快得多。
有许多方法可以提高模型的性能,如修改输入数据、应用不同的训练方法或使用超参数搜索算法,如 GridSearch 或 RandomizedSearch 来找到超参数的最佳值。
您可以在这里找到完整的代码:
参考:
https://keras.io/examples/nlp/pretrained_word_embeddings/
从开普勒任务的光变曲线探测系外行星
分析 FFT 和递归图在多大程度上影响系外行星分类的准确性
什么是系外行星,它们是如何被探测到的?
在开始系外行星探测之前,我想重点谈谈为什么寻找系外行星很重要。真的值得搜索吗?太阳系外是否也可能存在生命,这是一个有史以来意义深远的问题。如果一颗富含生命的行星被发现,那么它将永远改变人类。除此之外,这个问题还将回答关于我们存在的最根本的问题。这就是系外行星探测发挥作用的地方。
行星是由气体、尘埃等组成的物体。绕着一颗恒星运行。太阳系以外所有围绕恒星旋转的行星都被称为系外行星。由于恒星发出耀眼的光,仅用望远镜很难探测到系外行星。为了回答这个问题,科学家们设计了一种方法来探测这些不同的行星。他们不是通过望远镜直接观察这些行星,这并不总是可行的,而是寻找这些行星对它们所围绕的恒星的影响。
找到这些行星的一种方法是寻找不稳定的恒星。行星围绕其旋转的恒星往往会摆动。这是由于旋转行星的质量。用这种技术已经发现了许多行星。但问题是,只有像 Jupyter 这样的大质量行星才能对其恒星产生引力影响,从而导致恒星抖动。像地球这样较小的行星对恒星的影响较小,因此不稳定的运动很难被探测到。那如何探测更小的系外行星呢?
图 2:由于系外行星的引力,恒星摇摆不定(图片来源:https://en.wikipedia.org/wiki/File:Dopspec-inline.gif
开普勒使用另一种叫做“凌日法”的技术探测到了较小的行星。凌日是指一颗行星从它的恒星和观测者面前经过。由于这次凌日,到达观察者的光的强度有一个小的下降。从而使它不太亮。围绕恒星旋转的行星会显示出周期性的光强下降。这可以在下图中看到,
图 3:凌日导致的光强变化(来源:https://en . Wikipedia . org/wiki/Transit _(天文学))
日全食表示由于系外行星的阻挡,从恒星到达观察者的光线强度下降。因此,通过研究连续凌日之间的时间间隔,人们可以对它是行星还是某个天体进行分类。在这项研究中,我使用了类似技术的输出结果来将一个天体分为系外行星和非系外行星。
从天文数据中提取光变曲线
时间序列数据是从开普勒网站下载的。此数据具有. FITS 扩展名。灵活的图像传输系统也称为 FITS,是一种交换天文数据的标准格式,独立于硬件平台和软件环境。在 python 中,ASTROPY 库用于读取天文数据。阳性和阴性样本都被下载用于训练目的。下载的数据包含具有多个值的多维数组。下表显示了各种值,
图 4:天文数据。适合文件(来源:作者图片)
在所有这些列中,SAP_FLUX 用于训练 ML 模型。阳性和阴性数据的时间对 SAP_FLUX 的可视化如下所示,
图 5:正负数据点的时间序列(来源:图片由作者提供)
很明显,积极的数据有特定的模式。这是由于系外行星围绕恒星的凌日运动。然而,对于负数据,看不到重复的模式。此外,在某些情况下,负数据集中存在随机时间序列。这个时间序列数据是使用 SVM 训练的。可以看出,简单模型的准确率在 52%左右。与准确性一起,分类报告和混淆矩阵被提出用于评估目的。剩下的文章讨论了 FFT 和 RP 作为时间序列数据的预处理技术对分类精度的影响。
图 6:时间序列 ML 模型的分类报告和混淆矩阵(来源:图片由作者提供)
快速傅立叶变换和递归图模型
快速傅立叶变换将数据从时域转换到频域。Scipy 具有将时间序列通量数据转换到频域的内置功能。FFT 后的数据可以显示如下:
图 7:应用于时间序列数据的 FFT(来源:作者提供的图片)
在对时间序列数据应用 FFT 之后,转换后的数据用于训练 SVM 模型。应用 FFT 后的结果如下所示,
图 FFT ML 模型的分类报告和混淆矩阵(来源:图片由作者提供)
可以看出,对于相同数量的数据点和相同的模型,精确度从 52%增加到几乎 59%。FFT 后,使用递归图评估结果。
重现图是从代表每个时间点之间距离的时间序列中获得的图像。这项技术可以用来提高系外行星分类的准确性。Python 有一个名为 pyts 的库,其中包含作为内置函数的 RecurrencePlot。时间序列作为函数的输入。它生成一个图像作为输出。输入数据的递归图图像可以被可视化如下:
图 9:应用于时间序列数据的递归图。(来源:图片由作者提供)
从上面的图像可以看出,对于正数据点,也就是系外行星为真的数据点,在图像中形成了特定的模式。相反,在非系外行星的情况下,找不到特定的模式。图像具有随机噪声。
将时间序列数据转换为 RP 后,这些图像用于训练 VGG16 模型。应用 RP 后的分类报告如下所示:
图 10:递归图模型的分类报告和混淆矩阵(来源:作者提供的图片)
如上所述,FFT 比其他技术性能更好。但为什么选择 FFT 进行这项研究。原因是,在开普勒任务中,系外行星是使用凌日方法探测到的,正如本文开头所解释的。系外行星将显示到达观察者的光强度的周期性下降。如果数据代表系外行星,则周期性时间序列数据被转换到频域,这使得模式对于正类更加明显,并且由于随机噪声,对负类几乎没有影响。因此,具有 FFT 预处理的 ML 模型比其他技术执行得更好。
结论
- 以上结果表明,直接使用时间序列数据训练数据时的准确率为 52%。
- 当 FFT 应用于时间序列数据时,准确率提高到 59%。因此,通过使用相同的输入数据和相同的模型,准确度提高了 7%。这是因为在训练模型之前对数据应用了预处理技术(FFT)。
- FFT 的性能也优于 RP 预处理技术。
注意:由于硬件限制,用于训练的数据较少,因此精度在 50%的范围内。如果使用更多的数据,模型会达到更高的精度。尽管如此,FFT 方法比直接使用时间序列数据进行系外行星分类表现更好。
这个问题的完整代码可以在 Github 上找到。
参考文献
- Asif Amin,R. M .、Talha Khan,a .、Raisa,Z. T .、Chisty,n .、SamihaKhan,s .、Khaja,M. S .和 Rahman,R. M. (2018)。利用自适应神经模糊系统探测开普勒光变曲线中的系外行星系统,2018 智能系统国际会议(IS),第 66-72 页。
- https://www . science direct . com/science/article/pii/s 2213133719300319
- https://spaceplace.nasa.gov/all-about-exoplanets/en/
- 【https://exoplanets.nasa.gov/search-for-life/why-we-search/
联系人
想了解更多与量子计算和机器学习相关的故事,请关注我的媒体。还有,看看我的 Github 和 Linkedin 。
利用图案匹配从 PCB 检测基准标记
位置,位置,位置。
这可能很容易忘记,但无论我们的电子设备内部发生了什么,都不是魔法。想到有些产品如此复杂,以至于没有一个人能够理解每个部分是如何精确地相互作用的,我感到很惊讶,然而制造过程是存在的,从工厂运出的东西就是这样工作的。任何涉及集成电路的东西都属于这一类。
如果你仔细观察印刷电路板(PCB),你可能会注意到没有与任何其他结构电连接的金色小圆盘。这些是基准标记(或基准):它们的目的是让视觉系统尽可能容易地找到它们,在图像中定位 PCB。
PCB 的基准标记。图片由作者提供。
在本文中,我们将通过 OpenCV 的match template()函数来完成定位基准标记的步骤。你可以在这个库里找到代码和图片。
为什么多氯联苯需要基准标记?
在 PCB 组装过程中,自动化系统会将分立元件(芯片、电容器、连接器等)放置在 PCB 上各自的位置,然后焊接到焊盘上。基准标记的检测允许在 PCB 上精确放置组件。放置后,自动检测系统将检查所有物品是否都在应该在的位置,并且放置在公差范围内。同样,基准标记将提供将 PCB 图中的物理点(以毫米为单位)转换为图像点(以像素为单位)所需的参考点。
预处理
我们从将图像转换为灰度开始,因为我们依赖于基准标记的独特形状,而不是它们的特定颜色。这是用 OpenCV 的 cvtColor() 函数完成的。
图像已转换为灰度。图片由作者提供。
该 PCB 上的基准标记由两个直径分别为 26 和 68 像素的同心圆盘组成。
手动测量内盘直径。图片由作者提供。
OpenCV 的 matchTemplate() 函数需要目标对象的模板图像。在这种情况下,它将是一个基准标记的图像。我们可以在我们的图像中裁剪三个基准标记中的一个,并将其用作我们的模板,但是这种方法容易遗漏与任意选择的模板图像具有微小外观差异的真正的基准标记。相反,我们将创建一个理想基准标记的合成图像:
创建合成基准标记图像。作者代码。
合成基准标记。图片由作者提供。
基准标记图像被标准化为零均值和单位标准偏差,因为我们希望 matchTemplate() 的结果在搜索图像的均匀值区域中接近零,并且阳性匹配的强度值接近 1。
模式匹配
我们现在拥有了调用 matchTemplate() 所需的所有要素:
模式匹配。作者代码。
值得注意的是,我们用零填充得到的匹配图像,以使图像具有与原始图像相同的尺寸。这是必要的,因为, matchTemplate() 基于卷积,输出图像具有维度(Wₒᵣᵢ-Wₚₐₜₜₑᵣₙ+1,Hₒᵣᵢ-Hₚₐₜₜₑᵣₙ+1).在我们的例子中,具有维度(69,69)的模式图像, matchTemplate() 的输出具有比原始图像窄 68 像素和短 68 像素的维度。为了补偿这种影响,我们在图像外围零填充了 34 个像素。我们还重新调整了匹配图像的灰度等级,从[-1,1]到[0,255]以便于可视化。
零填充和重缩放的匹配图像。图片由作者提供。
我们感兴趣的是匹配图像强度的峰值,由上面图像中的亮点表示。决定哪些亮点是我们正在寻找的基准标记是一个设置正确阈值的问题。
我们不是手动设置阈值,而是利用这样一个事实,即我们知道在这个匹配图像中应该有三个基准标记,因此应用正确的阈值应该会产生正好有三个斑点的二值图像。为了做到这一点,我们将逐渐降低阈值,从 255 开始,并在结果阈值图像中计数斑点。当我们到达三个点时,我们停下来。
通过降低阈值来寻找最佳阈值,直到找到预期数量的对象。作者代码。
找到的三个基准标记之一的位置。图片由作者提供。
变换矩阵的计算
PCB 平面上的参考点(以毫米为单位)与其在图像中的相应位置(以像素为单位)之间的三个对应关系是计算单应性所需的最少,即允许我们将毫米坐标转换为像素坐标的变换矩阵。利用这个变换矩阵,我们可以用 PCB 边缘来标注图像。
图像注释,显示三个找到的基准标记和 PCB 边缘。图片由作者提供。
上面带注释的图像证实了三个基准标记被正确找到,并且计算出的变换矩阵是准确的。假设我们在 PCB 上有给定元件的物理坐标,现在可以裁剪图像中需要检测的任何区域。
在制造环境中,待检查的物体在传送带上移动是很常见的。我们不能假设被检查的物体将总是精确地位于相对于摄像机的相同位置。即使从一个对象到下一个对象存在平移和旋转,根据基准标记的检测计算的变换矩阵也允许我们检索 PCB 上感兴趣的给定区域。
我们刚刚做的是自动化检查流程的第一部分。这可能不是最困难的部分,但肯定是最关键的部分:如果对象定位失败,其他任何事情都没有意义。
下次你看 PCB 时——提醒自己这不是一个神奇的人工制品,而是一个工程奇迹——试着找出便于组装和自动检测的基准标记。
如果你有一个应用程序需要基准标记的位置,让我知道。我很高兴听到这件事。
[1]在计算机视觉中,斑点是一组相连的像素。
使用 1D 有线电视新闻网在你看不到的数据上检测心脏异常
使用分裂神经网络和 PySyft 保护敏感训练数据隐私
TL;博士:
我们能否应用分裂学习架构来训练 1D CNN 心跳数据模型,并在保护数据隐私的同时准确检测心脏异常?
嗯,那还是太长了,而且字数便宜,给我看看代码就行了!
这里你去吧。尽情享受吧!
介绍
机器学习(ML) 是人工智能的一个子领域,算法被训练从海量数据集中发现模式。然后,这些模式被用于对新数据进行决策和预测。ML 今天面临的一个问题是数据共享:数据科学家需要从数据所有者那里收集大量数据,以便训练他们的算法。这通常并不理想,尤其是对于医疗保健或金融等行业的敏感数据。分裂学习是隐私保护机器学习(PPML)中试图解决这一数据隐私问题的方法之一。
分裂学习是指将深度神经网络(DNN)切割成两个或更多部分的过程。在最简单的场景中,即只有一个数据所有者(客户端)和一个数据科学家(服务器),DNN 被分成两部分。DNN 的第一部分在数据驻留的客户机上使用,第二部分在服务器端使用。客户端的模型将从数据集中学习一组特征(也称为“激活图”),然后将这些激活图发送到服务器以继续训练过程。然后,在向后传递期间,服务器计算损失函数和直到分离层的损失梯度,然后将这些梯度发送回客户端,以便他可以继续向后传递。这样,服务器/数据科学家永远不会看到输入的训练数据,但仍然可以训练网络。你可以从这个教程中学到更多关于拆分学习的基础知识。
在这篇博文中,我们将通过使用 OpenMined 的框架 PySyft 来训练分裂神经网络的过程:这是一个 Python 库,用于计算你不拥有也看不到的数据。在 OpenMined 的免费课程“私人计算基础”中,已经有一个教程,介绍如何使用 PySyft 的 Duet 和两个 Jupyter 笔记本来训练分裂的 DNN:一个代表客户端,另一个代表服务器。然而,如果你正在开发一种新的分割学习方法,使用两个笔记本是相当麻烦的,因为你必须来回切换。幸运的是,PySyft 的另一个特性叫做 VirtualMachine,它允许我们只在一个 jupyter 笔记本或 python 文件中开发一个分割 DNN。今天我们将学习如何使用它,以及 PySyft 的其他特性,如 RemoteDataset 和 RemoteDataLoader,来加载自定义远程数据集。最重要的是,我们将基于[1]的工作,发现如何训练一个分裂 1D CNN 神经网络来检测从不离开客户端机器的输入数据上的心脏异常。
让我们开始吧
首先,我们需要导入必要的包并定义必要文件的路径。我用了torch 1.8.1+cu102
和syft 0.5.0.
用于导入包的代码(图片由作者提供)
为数据导入设置训练和测试文件名(图片由作者提供)
定义客户端和服务器
使用 PySyft 的 VirtualMachine,我们可以在这个场景中定义抽象参与者,如下面的代码所示。
定义服务器和客户端虚拟机(图片由作者提供)
客户端:加载和浏览数据集
首先,让我们假设是客户端(数据所有者)并发现数据集。我们将使用麻省理工学院-BIH 心律失常,这是一个用于 ECG 信号分类或心律失常诊断的流行数据集[2]。您可以在这里找到原始数据集,但是,我们在这里使用来自的处理数据。下面是从train_ecg.hdf5
和test_ecg.hdf5
加载数据集所需的代码。
用于加载 ECG 数据集的类(图片由作者提供)
后处理数据集由总共 26 490 个心跳样本组成,每个样本是长度为 128 的时间序列向量。有 5 种不同类型的心跳作为分类目标:正常搏动(0 类)、左束支传导阻滞(1 类)、右束支传导阻滞(2 类)、房性早搏(3 类)、室性早搏(4 类)。我们可以在下面的图 1 中看到每个类的例子。
图 1:心电图数据集中的一些例子(图片由作者提供)
然后,客户端加载数据集,保存到.pt
文件中,并使用下面的代码将它们发送到服务器。
客户端创建数据集并保存到.pt
文件中(图片由作者提供)
如果使用duet
,他可以用这个语法向服务器发送字符串路径(注意,这次我们没有使用duet
)
如果使用 duet(按作者排序的图像),用于向数据集发送字符串路径的代码
服务器:创建远程数据集和远程数据加载器
现在,在从客户端收到数据集的.pt
路径后,服务器在远程端创建 RemoteDataset 和 RemoteDataLoader。
服务器创建远程数据集和远程数据加载器(图片由作者提供)
让我们遍历远程数据加载器,看看里面有什么。注意,我在调试时使用了来自冰淇淋包的ic
来打印变量;这很方便。
查看远程数据加载器中的训练数据(图片由作者提供)
使用上面的代码,我们将得到X
和y
作为指向相应火炬张量的指针,但不是真正的张量本身,如下图所示。
图 2:循环访问远程数据加载器时的输出(图片由作者提供)
服务器可以通过使用X.get()
或X.get_copy()
请求访问张量,但是这需要被客户端接受。这里,为了方便起见,我们假设客户机接受来自服务器的所有请求。然而,我们将在后面的训练循环中看到,客户机永远不会请求访问训练输入数据。此外,由于我们只加载了 50 个示例,并且批次大小为 32,因此只有两个批次,一个有 32 个样本,另一个有 18 个样本。
类似地,服务器为测试数据集创建远程数据集和数据加载器。
服务器为测试数据创建远程数据集和远程数据加载器(图片由作者提供)
服务器:定义分割神经网络架构以在 ECG 数据集上进行训练
下图 3 显示了用于在心电图数据集上进行训练的 1D CNN 神经网络的架构。客户端的模型包含两个 1D 卷积层(我们将在后面了解更多),带有泄漏 Relu 激活函数。每个 conv 层之后是一个 1D 最大池操作。服务器的模型包含两个完全连接的层,后面是 softmax 激活功能。使用的损失函数是交叉熵损失。
图 3:分裂学习 1DCNN 模型架构(图片由作者提供)
图 4: 1D 卷积层 vs 2D 卷积层(图片由作者提供)
让我们了解一下 1D 卷积层。它只是一种沿一维滑动权重核的方法。图 4 显示了 1D 卷积与 2D 卷积运算的关系。1D 卷积适用于 1D 数据,例如 ECG 信号中的时间序列。如果你想了解更多关于 1D、2D 和 3D 卷积的知识,这篇博文提供了非常清晰的解释。
现在我们可以继续,用下面的代码在客户端定义神经网络模型。这是一个继承自syft.Module
的类。注意,在第 3 行,我们将torch_ref
作为构造函数的参数,稍后我们将把remote_torch
传递给它。所有的层都是使用这个torch_ref
模块构建的。
在客户端定义分裂神经网络部分的代码(图片由作者提供)
服务器模型也继承自syft.Module
;它的构造函数仍然得到torch_ref
作为参数,但是,这些层是用普通的torch.nn
模块定义的,因为它们是在本地训练的。
在服务器端定义分裂神经网络部分的代码(图片由作者提供)
然后,服务器将客户端的模型发送到远程客户端(下面代码中的第 2 行)。
创建模型并发送客户的模型(图片由作者提供)
服务器和客户端:训练和测试循环
在训练和测试循环之前,我们需要定义一些超参数:
设置超参数和随机种子(图片来自作者)
最后,让乐趣开始。下面是训练和测试循环的代码:
我们的分裂 1D CNN 模型的训练和测试循环
在正向传递中,我们首先获得指向批处理数据的指针(第 12 行)。在将所有梯度初始化为 0 之后(第 15、16 行),客户端的模型从训练输入数据中提取激活图(第 18 行)。然后,服务器请求访问这些激活图(第 20 行),并继续向前传递(第 22 行)。服务器还要求访问地面实况输出数据(第 24 行)以计算损失(第 26 行)。
在反向传递中,服务器开始反向传播,直到分离层(第 30 行),然后将梯度发送到客户端(第 32 行)。在接收时,客户继续反向传播并计算他的梯度(第 34 行)。最后,当计算了损失函数相对于权重的所有梯度时,客户端和服务器都可以更新参数。
在每个时期的测试循环中,我们只需要进行正向传递并计算测试损耗。
图 5:训练和测试循环的结果(图片由作者提供)
最后,在 400 个周期结束后,我们可以打印出最佳测试精度,并绘制训练/测试损耗和精度,如图 6 和 7 所示。正如我们所看到的,分裂学习 1D CNN 方法可以在 351 个时期后在测试数据集上达到 98.85%的准确率。一点也不差。
图 6:打印出最佳测试精度(图片由作者提供)
图 7:培训/测试损失和准确性(图片由作者提供)
缺点和未来方向
虽然分裂学习方法取得了有希望的结果,但是还有几个问题需要解决。首先,服务器仍然需要访问地面实况输出数据来计算损失。为了解决这个问题,我们可以使用 U 型分裂学习配置[3]。其次,从客户端发送到服务器的激活映射仍然会泄露关于输入训练数据的信息。文献[1]的作者已经试验了差分隐私来解决这个问题,然而,它极大地阻碍了算法的准确性。第三,使用 PySyft 训练分裂网络所需的时间非常长,在英特尔至强 CPU 2.60GHz 和 6 核上几乎需要 14 个小时。用 GPU 在本地训练同一个网络只需要几分钟。目前,PySyft 还不支持在 GPU 上训练。解决这些问题将是今后工作的重点。
结论
在这篇博文中,我们介绍了在心电图数据集上训练分裂 1D CNN 模型的过程。采用分裂学习架构,该算法可以预测高达 98.85%的心脏异常,同时保持患者心跳数据的私密性。感谢你的阅读,希望你找到有用的东西。在其他关于安全和私人人工智能的博客文章中再见。
参考
[1] Sharif Abuadbba 等,我们能在 1D CNN 模型上使用分裂学习进行隐私保护训练吗? (2020),ACM 亚洲计算机与通信安全会议(ACM ASIACCS 2020)
[2]穆迪 GB,马克 RG。麻省理工学院-BIH 心律失常数据库的影响(2001),电气和电子工程师学会医学和生物工程 20(3):45–50(2001 年 5 月-6 月)
[3] Praneeth Vepakomma 等,分裂学习促进健康:不共享原始患者数据的分布式深度学习 (2018)
检测函数图中的拐点/肘点
利用 Python 包“kneed”中实现的“Kneedle”算法
Lucaxx Freire 在Unsplash【1】上拍摄的照片
理论
在处理数据时,了解数据点的“增加某些可调参数的相对成本不再值得相应的性能优势”有时很重要(Satop、Albrecht、Irwin 和 Raghavan,2011 年,[2],第 1 页)。算法“Kneedle”根据连续函数曲率的数学定义,在离散数据集中检测那些显示最佳平衡固有折衷的有益数据点,称为“膝”(具有负凹度的曲线),有时称为“肘”(具有正凹度的曲线)。在这篇文章中,我想总结“Kneedle”所经历的步骤,展示这种算法的好处,并展示 Python 包“kneed”的应用。
Satop、Albrecht、Irwin 和 Raghavan (2011 年[2])发表了“Kneedle”算法,使用曲率的概念作为函数与直线差异的数学度量。Satop 等人(2011 年,[2],第 2 页)得出结论,“因此,最大曲率捕捉了操作员用于识别膝盖的平稳效应”。
对于连续函数,曲率描述如下:
https://en.wikipedia.org/wiki/Curvature#Graph_of_a_function
例如,让我们创建 N = 10,000 个随机标准正态分布数据点,并将它们显示在直方图中:
随机标准正态分布数据绘制成直方图,采用核密度估计(图片由作者提供)
现在,我们可以按升序对值进行排序,以获得一个累积分布函数(CDF ),它在 x 轴上显示每个 x 值,在 y 轴上显示其出现的概率:
排序后的数据显示为 CDF,最大曲率表示为 x = 1.16(图片由作者提供,灵感来自 Satop 等人 2011 年的图 1 [2])
satop et al .(2011,[2],第 2 页)指出“曲率对于连续函数是定义良好的”,但对于离散数据没有明确的曲率定义。在这种情况下,曲率可以拟合为连续函数,但当数据有噪声时,这种拟合会变得更加困难。这就是“Kneedle”算法发挥作用的地方。
让我们更深入地看看这个算法是如何工作的。为此,我使用了 Satop et al(2011,[2])发表的会议论文中图 2 的原始离散数据。它是由 Python 包“kneed”提供的:
import kneed
kneed.DataGenerator.figure2()
这是绘制的原始数据:
原始数据(图片由作者提供)
让我们完成“Kneedle”算法的步骤:
1。平滑
使用样条来“平滑”原始数据的形状。
平滑数据(作者提供的图片)
2。归一化到单位正方形
x 和 y 值的范围将被标准化为 0 到 1(见轴)。
标准化数据(图片由作者提供)
3 。计算差异曲线变化
计算垂直距离(从数据点到从第一个到最后一个数据点的对角线的欧几里德距离)…
指示垂直距离的垂直线(作者图片,受 Satop 等人 2011 年[2]图 2a 的启发)
…并将它们逆时针旋转 45 度。这些旋转垂直线的大小表示差值。
旋转 45 度的垂直线代表差值(图片由作者提供,灵感来自 Satop 等人 2011 年的图 2b[2])
4 。计算差异曲线的局部最大值
在归一化曲线中的差异曲线的局部最大值处寻找“膝”点(对于检测肘点,图形将被反转)。
x = 0.22 时的最大曲率(图片由作者提供,灵感来自 Satop 等人 2011 年的图 2c[2])
5。计算差异曲线中每个局部最大值的阈值
对于差异曲线中的每个局部最大值,都有一个唯一的阈值,该阈值基于 x 值和灵敏度参数之间的平均差异(请参见 Satop 等人,2011 [2],第 4 页,进一步了解如何详细计算该阈值)。该灵敏度参数测量在声明“拐点”之前在原始未修改数据中预期会看到多少“平坦”点,并且可以调整:较小的灵敏度值检测拐点更快,而较大的灵敏度值更保守。
y = 0.43 时指示的阈值,敏感度为 1(图片由作者提供,灵感来自 Satop 等人 2011 年的图 2c[2])
6。将每个差值与阈值进行比较
如果差值在达到局部最大值之前下降到阈值以下,则该算法宣布出现“拐点”。相反,阈值被重置为零。
有节的
现在让我们更深入地了解一下 Kevin Arvai 编写的 Python 包“kneed ”,它使用了“Kneedle”算法:
https://pypi.org/project/kneed/
这样,我们可以直接绘制包括“拐点”在内的归一化差异曲线:
Satop 等人 2011 年[2]的图 2 标有“kneed”(图片由作者提供)
我们可以使用函数 KneedLocator() 在我们的数据中查找膝盖/肘部:
# calculate and show knee/elbow
kneedle = kneed.KneeLocator(…)
knee_point = kneedle.knee #elbow_point = kneedle.elbow
print('Knee: ', knee_point) #print('Elbow: ', elbow_point)
kneedle.plot_knee()
这些是 KneedLocator() 最重要的参数:
- 曲线:膝盖的凹,肘部的【凸】——基于函数的负/正凹度
- 方向:递增为正斜率,“递减”为函数的负斜率
- 在线:检测到膝盖/肘部作为第一个元素(假),或者如果收到点,必要时纠正“旧”膝盖/肘部值(真)
- S :膝/肘检测灵敏度(S=0 或更大);Satop 等人[2]指出,当灵敏度为 0 时,离线设置中的“kneedle”具有完美的信息,而在在线设置中,总的来说,灵敏度为 1** 显示出最佳的整体性能,但它可能与接收到的数据点不同。**
应用程序
使聚集
使用“kneedle”算法检测肘部对于聚类算法来说非常方便:
a)K-表示
使用肘方法,您可以计算组内平方和作为组内方差的度量,并检测要形成的组数的最佳值( k ):
肘法(图片由作者提供)
肘部检测肘部方法与“kneed”(图片由作者提供)
b) DBSCAN
当计算每个数据点的距离度量(通常是欧几里德距离)并按升序对它们进行排序时,它们可以绘制在k-距离图中,以找到用于定义聚类的阈值:
k 距离图(图片由作者提供)
带有“kneed”的 k 距离图中的膝盖检测(图片由作者提供)
分类
即使对于分类,当查看真阳性率以及假阳性率时,“Kneedle”也变得非常有用。两者的权衡都表现在接收器工作特性 曲线中。 ROC 曲线中的“拐点”可以很好地衡量阈值,因为 TPR 的增加速度减慢,而 FPR 在这一点开始加速增加;
接收机工作特性曲线(图片由作者提供)
带“kneed”的接收器操作特性曲线的膝部检测(图片由作者提供)
结束语
“kneed”还在 Streamlit 上提供了一个名为“ikneed”的交互式 API,在这里可以分别测试“膝盖”和“肘部”的(小)数据集。在这里,您还可以随意调整参数(请参见链接以直接查看它们的影响):
https://share.streamlit.io/arvkevi/ikneed/main/ikneed.py
https://github.com/arvkevi/ikneed
在我看来,“kneed”是目前用于膝盖检测的最强大的 Python 包,因为它提供了许多有用的功能。尽管如此,还有其他类似的 Python 包用于查找曲线的拐点,例如“kneebow”:
https://pypi.org/project/kneebow/
参考文献
[1]照片由 Lucaxx Freire 在 Unsplash 上拍摄
[2] V. Satop,J. Albrecht,D. Irwin 和 B. Raghavan,“大海捞针:检测系统行为中的拐点” (2011),第 31 届分布式计算系统国际会议研讨会,2011,第 166–171 页,doi: 10.1109/ICDCSW.2011.20。
初学者用深度学习检测疟疾
图像分类和卷积神经网络(CNN)初学者指南
图片由卡西·乔希拍摄
在这个项目中,我们将仔细检查由美国国家卫生研究院提供的数据集,该数据集包含来自 150 名患者的 27,558 个不同的细胞图像,这些患者被称为恶性疟原虫的寄生虫感染,并与来自 50 名健康患者的细胞图像混合,这些图像可通过链接此处下载。我们的任务是建立一个机器学习/深度学习算法,能够对检测到的细胞是否被寄生虫感染进行分类。
在这个项目中,我们将应用深度学习算法,特别是卷积神经网络(CNN)算法,该算法能够通过简单地将算法训练到给定的图像作为训练来分类来自某个细胞的图像是否被感染。由于这是一个数据超过 330 MB 的重载项目,我会建议在 Jupyter 笔记本中应用这一点。
首先,我们需要导入必要的启动库:
import pandas as pd
import numpy as np
import os
import cv2
import random
如果这是您第一次进行影像分类项目,您可能一直在使用 panda.read_csv()导入 csv 格式的数据集,但是,由于我们所有的数据都是 png 格式,我们可能需要使用 os 和 cv2 库以不同的方式导入它们。
OS 是一个强大的 Python 库,允许你与操作系统交互,无论它们是 Windows、Mac OS 还是 Linux,例如,重命名或删除文件,从给定路径列出某个文件夹中的所有对象,等等。从笔记本到个人电脑。另一方面,cv2 是一个 OpenCV 库,专门用于解决各种计算机视觉问题,如读取图像和将图像加载到笔记本中。
首先,我们需要设置一个变量来设置我们的路径,因为我们稍后会继续使用它:
root = '../Malaria/cell_images/'
in = '/Parasitized/'
un = '/Uninfected/'
从上面的路径看,名为“疟疾”的文件夹是我的细胞图像文件文件夹的存储位置,也是寄生和未感染细胞图像的存储位置。这样,我们将能够更容易地操纵这些路径。
现在让我们用 os.listdir("path ")函数列出被感染和未被感染文件夹中的所有图像,如下所示:
Parasitized = os.listdir(root+in)
Uninfected = os.listdir(root+un)
使用 OpenCV 和 Matplotlib 显示图像
与 csv 文件不同,在 CSV 文件中,我们只能使用名为 head 的 pandas 函数来列出多个数据,例如,df.head(10)来显示 10 行数据,对于图像,我们需要使用 for 循环和 matplotlib 库来显示数据。因此,首先让我们导入 matplotlib 并绘制每个寄生和未感染细胞的图像,如下所示:
import matplotlib.pyplot as plt
然后绘制图像:
plt.figure(figsize = (12,24))
for i in range(4):
plt.subplot(1, 4, i+1)
img = cv2.imread(root+in+ Parasitized[i])
plt.imshow(img)
plt.title('PARASITIZED : 1')
plt.tight_layout()
plt.show()plt.figure(figsize = (12,24))
for i in range(4):
plt.subplot(2, 4, i+1)
img = cv2.imread(root+un+ Uninfected[i+1])
plt.imshow(img)
plt.title('UNINFECTED : 0')
plt.tight_layout()
plt.show()
因此,为了绘制图像,我们需要 matplotlib 中的 figure 函数,然后我们需要从 figsize 函数中确定每个图形的大小,如上所示,其中第一个数字是宽度,后者是高度。然后在 for 循环中,我们将使用 I 作为变量来迭代 range()中指示的次数。在这种情况下,我们将只显示 4 幅图像,这就是为什么我们在其中放了 4 幅图像。随后,我们将使用来自库中的 subplot 函数来指示多少行、多少列以及某些迭代的绘图数,这就是为什么我们使用 i+1 然后继续对右侧的列进行每次迭代的原因。
由于我们已经创建了子情节,但是子情节仍然是空的,因此,我们需要通过使用 cv2.imread()函数并在其中包含路径和 I 变量来使用 OpenCV 库导入图像,这样它将一直循环到下一张图片,直到提供最大范围。最后,我们将使用 plt.imshow()函数将 cv2 库导入的图像插入到空的子绘图中,我们将得到以下输出:
将图像和标签分配到变量中
接下来,我们希望将所有图像(无论是寄生细胞图像还是未感染细胞图像)以及它们的标签插入到一个变量中,其中 1 表示寄生细胞,0 表示未感染细胞。因此,首先,我们需要为图像和标签创建一个空变量。但在此之前,我们需要导入 Keras 的 img_to_array()函数。
from tensorflow.keras.preprocessing.image import img_to_array
假设存储图像的变量称为数据,存储标签的变量称为标签,那么我们可以执行以下代码:
data = []
labels = []for img in Parasitized:
try:
img_read = plt.imread(root+in+ img)
img_resize = cv2.resize(img_read, (100, 100))
img_array = img_to_array(img_resize)
data.append(img_array)
labels.append(1)
except:
None
for img in Uninfected:
try:
img_read = plt.imread(root+un+ img)
img_resize = cv2.resize(img_read, (100, 100))
img_array = img_to_array(img_resize)
data.append(img_array)
labels.append(0)
except:
None
在将变量数据和标签分配给空数组之后,我们需要使用 for 循环插入每个图像。这里有些不同,for 循环也包含 try 和 except。因此,基本上,try 用于在 for 循环中照常运行代码,另一方面,except 用于代码遇到错误或崩溃时,这样代码就可以在这种情况下继续循环。
与上面的图像显示代码类似,我们可以再次使用 plt.imread("path ")函数读取图像,但这一次,我们不需要显示任何子情节。你可能想知道为什么我们这次使用 plt.imread()而不是 cv2.imread()。嗯,两者功能相同,其实还有很多其他的读图库,比如枕头库的 image.open()或者 Scikit-Image 库的 io.imread()。然而,OpenCV 按照 BGR 或者蓝、绿、红的顺序读取图像,这就是为什么上面显示的图像是蓝色的,而实际的图片是粉红色的。因此,我们需要使用以下代码将其转换回 RGB 顺序:
cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
另一方面,Matplotlib、Scikit-Image 和 Pillow image 读取功能会自动以 RGB 顺序读取图像,因此,我们不再需要来回转换图像,如下图所示的细胞图像实际颜色:
plt.imshow(data[0])
plt.show()
然后,我们可以使用 OpenCV 的 cv2.resize()函数来调整图像的大小,将加载的图像设置为特定的高度和宽度,如上所示,宽度为 100,高度为 100。接下来,因为我们的图像是抽象格式的,我们以后不能训练、测试或插入它们到变量中,因此,我们需要使用 keras 的 img_to_array()函数将它们转换成数组格式。这样,我们将能够通过使用。append()函数,用于在不改变原始状态的情况下,将对象插入到数组的最末尾。
所以在我们的循环中,我们在每个循环中不断追加一个新的图像,直到整个图像填满了变量的空括号。
预处理数据
与我们可以立即拆分数据的机器学习项目不同,在深度学习中,特别是对于神经网络,以便减少方差,防止偏差,并减少过度拟合。在 Python 中,有很多种混洗数据的方法,比如使用 Sklearn,如下所示:
from sklearn.utils import shuffle
或者使用随机,如下所示:
from random import shuffle
但是在这个项目中,我们将使用 Numpy 的随机函数。因此,我们需要将我们的数组转换为 Numpy 的数组函数,然后对图像数据进行混洗,如下所示:
image_data = np.array(data)
labels = np.array(labels)idx = np.arange(image_data.shape[0])
np.random.shuffle(idx)
image_data = image_data[idx]
labels = labels[idx]
首先,我们将数据和标签变量转换成 Numpy 格式。然后,我们可以在使用 np.random.shuffle()函数对数据进行洗牌之前,先使用 np.arange()对数据进行适当的排序。最后,我们将混洗后的数据赋回其原始变量,以确保混洗后的数据得到保存。
为培训、测试和验证拆分数据
在数据被打乱之后,是时候通过导入必要的库将它们分成训练、测试和验证标签和数据了,如下所示:
from tensorflow.keras.utils import to_categorical
from sklearn.model_selection import train_test_split
然后,我们设置一个函数,将数据转换为 32 位数据,以节省 PC 内存:
def prep_dataset(X,y):
X_prep = X.astype('float32')/255
y_prep = to_categorical(np.array(y))
return (X_prep, y_prep)
然后,通过使用 Sklearn 库,我们可以将数据分为训练、测试和验证:
X_tr, X_ts, Y_tr, Y_ts = train_test_split(image_data,labels, test_size=0.15, shuffle=True,stratify=labels,random_state=42)
X_ts, X_val, Y_ts, Y_val = train_test_split(X_ts,Y_ts, test_size=0.5, stratify=Y_ts,random_state=42)X_tr, Y_tr = prep_dataset(X_tr,Y_tr)
X_val, Y_val = prep_dataset(X_val,Y_val)
X_ts, _ = prep_dataset(X_ts,Y_ts)
由于存在验证数据,因此,我们需要执行两次拆分。在分裂函数中,我们需要分配数据和标签中的前两个参数,然后,我们告诉它们被分成的百分比数,而随机状态是为了确保它们产生的数据总是处于一致的顺序。
建立卷积神经网络模型
在建立 CNN 模型之前,让我们从理论上对它有一点深入的了解。CNN 主要应用于图像分类,由于其高性能和高精度,自 1998 年发明以来一直流行至今。那么它是如何工作的呢?
图解卷积神经网络。来源:维基共享资源
正如我们从上面的插图中看到的,CNN 通过提取图像的一部分并通过几个层进行处理来分类图像,从而对图像进行分类。从这几层中,它们可以分为三种类型的层,即卷积层、池层和全连接层。
卷积层
第一层,卷积层,是 CNN 中最重要的部分之一,它的名字由此而来。其目的是利用核从输入图像中提取特征。该内核将通过使用点积来持续扫描输入图像,以创建一个新的分析图层,称为专题地图/激活地图。该机制如下所示:
卷积层机制。来源: ErUM 数据协作会议
**2 * 1+4 * 2+9 * 3+2 (-4)+1 * 7+4 * 4+1 * 2+1 (-5)+2 * 1 = 51
并继续下去,直到所有的特征地图的细胞完全填满。
汇集层
在我们创建了特征地图,我们将应用池层,以减少其大小,以减少过度拟合。这一步有几个操作,但是最流行的技术是最大汇集,其中特征图的扫描区域将只取最大值,如下图所示:
最大池和平均池机制。来源:研究之门
卷积和池化步骤可以重复进行,直到最终确定理想的大小,然后我们可以继续到分类部分,在这里它被称为全连接层。
全连接层
在这一阶段,变换后的特征图将被展平成列向量,该列向量将在几个时期的每次迭代中经历前馈神经网络和反向传播过程。最终,CNN 模型将通过使用 Softmax 分类来区分主导和低级特征,以对图像进行分类。
在 Python 中,我们需要从 Keras 库中导入 CNN 函数:
from tensorflow.keras import models, layers
from tensorflow.keras.callbacks import EarlyStopping
然后,我们将建立自己的 CNN 模型,下面有 4 个卷积层和池层:
model = models.Sequential()#Input + Conv 1 + ReLU + Max Pooling
model.add(layers.Conv2D(32,(5,5),activation='relu',padding='same',input_shape=X_tr.shape[1:]))model.add(layers.MaxPool2D(strides=4))
model.add(layers.BatchNormalization())# Conv 2 + ReLU + Max Pooling
model.add(layers.Conv2D(64,(5,5),padding='same',activation='relu'))model.add(layers.MaxPool2D(strides=2))
model.add(layers.BatchNormalization())# Conv 3 + ReLU + Max Pooling
model.add(layers.Conv2D(128,(3,3),padding='same',activation='relu'))model.add(layers.MaxPool2D(strides=2))
model.add(layers.BatchNormalization())# Conv 4 + ReLU + Max Pooling
model.add(layers.Conv2D(256,(3,3),dilation_rate=(2,2),padding='same',activation='relu'))
model.add(layers.Conv2D(256,(3,3),activation='relu'))
model.add(layers.MaxPool2D(strides=2))
model.add(layers.BatchNormalization())# Fully Connected + ReLU
model.add(layers.Flatten())model.add(layers.Dense(300, activation='relu'))
model.add(layers.Dense(100, activation='relu'))#Output
model.add(layers.Dense(2, activation='softmax'))model.summary()
Keras 中的卷积层
好的,在这段代码中你可能会混淆的第一件事可能是 models.sequential()。在使用 Keras 构建深度学习模型时,有两种选择,一种是顺序模型,另一种是功能模型。两者之间的区别是顺序只允许以单向格式逐层建立模型,另一方面,功能模型允许各层连接回以前的层,连接到多个层,甚至连接到你想要建立的任何一层更复杂的模型。因为我们正在构建一个简单的 CNN 模型,所以我们将使用序列模型。
我们之前了解到,CNN 由卷积层组成,后来通过使用池化层进行了简化,因此,我们需要使用 Keras 的函数:model.add()添加层,然后在圆括号中添加我们的首选层。由于我们的图像是 2D 形式,我们只需要 2D 卷积层,再次使用 Keras 函数卷积层:层。Conv2D()。正如您在每一层中所看到的,第一层中有一个数字 32,第二层中有 64,依此类推,从上一层中乘以 2。这就是所谓的过滤器。它试图捕捉图像的模式,因此,随着滤波器大小的增加,我们可以捕捉图像中更多的模式,我们需要从较小的数量开始,以捕捉图像中的噪声像素,尽管这不是必须的,但这被认为是最佳方法。
接下来,( 5,5)和(3,3)矩阵是我们上面讨论的用于创建特征图的核。然后这里有两个有趣的部分:ReLu 的激活和填充。那些是什么?先讨论一下 ReLu。它是整流线性单元的缩写,被视为神经网络中的一个神经元,建议用于更简单的模型,通过输出正输入(0 或 1)来提高训练过程的效率。还有另外两个神经元,分别是 Sigmoid 和 Softmax,其中 Softmax 通常用于输出层,也用于这个项目的输出层。Sigmoid 常用于分类项目,然而,由于我们刚刚开始深度学习,所以我们使用 ReLu 激活。
下一个问题是填充。正如我们所知,卷积层不断减小图像的大小,因此,如果我们在新特征图的所有四个边上添加填充,我们将保持相同的大小。所以基本上,我们在处理过的图像周围添加新的值为 0 的单元格,以保持图像的大小。填充有两个选项:相同或零。接下来是 input_shape,它只是我们的输入图像,只在第一层使用,用于输入我们的训练数据。
Keras 中的池层
让我们继续到池层,在这里我们可以通过使用 Keras 的层来应用。MaxPool2D()函数。这里有一种独特的东西叫做步幅。它基本上是内核将传递的步数。因此,如果我们用 5x5 内核计算 4 个步长,内核将计算图像的最左边部分,然后向右跳过 4 个单元来执行下一次计算。
你可能会注意到,每个卷积层和池层都以批量归一化结束。这是为什么呢?因为在每一层中,都有不同的分布,并且因为它需要适应每一层而减慢训练过程,这有时被称为内部协变量移位。但是,如果我们强制所有层都有相似的分布,我们可以跳过这一步,提高训练速度。这就是为什么我们在每一层中应用批量归一化,通过如下所示的 4 个步骤来归一化每一层中的输入:
在喀拉斯完全连接
现在已经到了 CNN 的最后阶段,也就是全连接阶段。我们快到了。但在进入这个阶段之前,因为我们将在完全连接阶段使用“密集”层,我们需要通过使用 Keras 的层将处理后的数据展平为一维。Flatten()函数,以便将垂直和水平方向的数据合并到一列中。
将 3D 数据平铺到 1D。来源:维基媒体
在我们展平数据后,现在我们需要使用密集函数将它们完全连接起来:图层。Dense()然后指定我们想要用作输出的神经元的数量,考虑到当前的神经元是 1024 个单元。所以我们先生产 300 个,然后 100 个神经元单位,并使用 ReLu 对其进行优化。
最后,我们到达输出阶段,这也是通过使用稠密函数来完成的,但是,由于我们的分类只有 2 个概率,即感染疟疾或未感染疟疾,因此,我们将我们的输出单位设置为 2。再者,在分类任务中,我们更有可能在输出层使用 Softmax 激活,而不是 ReLu,因为 ReLu 将所有负类设置为零。
总之,我们建立的模型如下:
然后,我们将把我们的训练和验证数据放入模型中:
model.compile(optimizer='adam',
loss='categorical_crossentropy',
metrics=['accuracy'])es = EarlyStopping(monitor='val_accuracy',mode='max',patience=3,verbose=1)history= model.fit(X_tr,Y_tr,
epochs=20,
batch_size=50,
validation_data=(X_val,Y_val),
callbacks=[es])
由于数据量很大,这个过程需要一些时间。如下图所示,我平均每个时期运行 9 分钟。
估价
我们可以通过绘制评估图来评估我们的模型性能,但在此之前,我们需要导入 seaborn 库:
import seaborn as sns
然后将精度和损耗绘制如下:
fig, ax=plt.subplots(2,1,figsize=(12,10))
fig.suptitle('Train evaluation')sns.lineplot(ax= ax[0],x=np.arange(0,len(history.history['accuracy'])),y=history.history['accuracy'])
sns.lineplot(ax= ax[0],x=np.arange(0,len(history.history['accuracy'])),y=history.history['val_accuracy'])ax[0].legend(['Train','Validation'])
ax[0].set_title('Accuracy')sns.lineplot(ax= ax[1],x=np.arange(0,len(history.history['loss'])),y=history.history['loss'])
sns.lineplot(ax= ax[1],x=np.arange(0,len(history.history['loss'])),y=history.history['val_loss'])ax[1].legend(['Train','Validation'])
ax[1].set_title('Loss')plt.show()
这将为我们提供以下输出:
看起来性能平均在 90%以上,这是非常好的,正如 CNN 模型所预期的。但是,为了让我们更具体地了解数据性能,让我们使用 Sklearn 构建一个混淆矩阵,并预测输出:
from sklearn.metrics import confusion_matrix, accuracy_scoreY_pred = model.predict(X_ts)Y_pred = np.argmax(Y_pred, axis=1)conf_mat = confusion_matrix(Y_ts,Y_pred)
sns.set_style(style='white')
plt.figure(figsize=(12,8))
heatmap = sns.heatmap(conf_mat,vmin=np.min(conf_mat.all()), vmax=np.max(conf_mat), annot=True,fmt='d', annot_kws={"fontsize":20},cmap='Spectral')
heatmap.set_title('Confusion Matrix Heatmap\n¿Is the cell infected?', fontdict={'fontsize':15}, pad=12)
heatmap.set_xlabel('Predicted',fontdict={'fontsize':14})
heatmap.set_ylabel('Actual',fontdict={'fontsize':14})
heatmap.set_xticklabels(['NO','YES'], fontdict={'fontsize':12})
heatmap.set_yticklabels(['NO','YES'], fontdict={'fontsize':12})
plt.show()print('-Acuracy achieved: {:.2f}%\n-Accuracy by model was: {:.2f}%\n-Accuracy by validation was: {:.2f}%'.
format(accuracy_score(Y_ts,Y_pred)*100,(history.history['accuracy'][-1])*100,(history.history['val_accuracy'][-1])*100))
这将使我们的精确度为:
误差样本
让我们看看错误样本是什么样的:
index=0
index_errors= []for label, predict in zip(Y_ts,Y_pred):
if label != predict:
index_errors.append(index)
index +=1plt.figure(figsize=(20,8))for i,img_index in zip(range(1,17),random.sample(index_errors,k=16)):
plt.subplot(2,8,i)
plt.imshow(np.reshape(255*X_ts[img_index], (100,100,3)))
plt.title('Actual: '+str(Y_ts[img_index])+' Predict: '+str(Y_pred[img_index]))
plt.show()
这将为我们提供以下输出:
尽管细胞未被感染,但错误的预测图像似乎在细胞上包含几个紫色斑块,因此,可以理解的是,模型实际上将它们预测为感染的细胞。
我想说 CNN 是一个非常强大的图像分类模型,不需要做很多预处理工作,因为它包含在卷积和池层中。
我希望深度学习,特别是通过使用卷积神经网络进行图像分类,现在通过这篇文章变得更有意义。感谢您的阅读。
检测 Android 应用中的恶意软件
检查 Android 应用商店中的恶意内容
丹尼·米勒在 Unsplash 上的照片
TL;DR :坏人滥用权限和过时软件感染你的设备。
背景:Epic Games 公司诉苹果公司。
在写这篇文章的时候,苹果公司和 Epic Games,Inc. 正处于法律纠纷的阵痛中。Epic Games 声称苹果的应用商店是垄断的,不应该对应用内购买有控制权。苹果反诉 Epic,指控其违约。这一裁决的结果可能会从根本上改变苹果公司运营其应用商店的方式,以及应用开发商如何利用他们的内容赚钱。
这场诉讼有一个方面引起了我的兴趣:苹果公司主张维持其目前完全控制 App Store 的模式。
这种观点认为,苹果对 App Store 的严格控制保证了一个安全的环境,在这个环境中,每一个应用程序都要接受恶意软件审查,并符合苹果的标准。因此,用户可以免受恶意软件的攻击,可以专注于重要的事情:找到你喜欢的应用程序。这里经常使用围墙花园的比喻,因为只要你呆在围墙的范围内,你就可以采摘和享用花园里的任何水果。
这让我不禁要问:与苹果相比,第三方商店的安全状况如何?我们能否创建一个简单的数据驱动模型来检查应用程序中的恶意软件,而不管谁控制着商店?
完全披露:整个项目是在苹果 MacBook 上完成的,我不会在法律案件中偏袒任何一方。
目标
在这篇文章中,我的目标是展示数据驱动的方法如何标记恶意的智能手机应用程序,并为实现这一目标的每个步骤提供解释。我也将看看我的研究结果与其他相关课题的研究相比如何。
被调查的操作系统是安卓系统,它占据了全球移动操作系统市场 72%的份额。鉴于它的广泛流行,它更容易被恶意行为者滥用。
给我们看看代码
可以在以下位置找到与此项目相关的存储库:
https://github.com/NadimKawwa/AndroidMalware
获取数据
数据来自 Kaggle 用户 Saurabh Shahane ,可一键下载:
https://www.kaggle.com/saurabhshahane/android-malware-dataset
该数据集最初由亚历杭德罗的马丁在论文“ ADROIT: Android 恶意软件检测使用元信息”中使用;卡列哈,亚历杭德罗;埃克托尔·梅嫩德斯;胡安·塔皮阿多;大卫·卡马乔。
数据集中的每一行都代表了在 Aptoide 应用商店网站上可用的应用的元信息,以及来自应用的 Android 清单的信息。此外,本文还将作为模型性能的基准。
请注意,我假设数据集中的信息尽可能真实准确。对这一基本假设的任何改变都可能完全改变本文的发现。此外,我强烈推荐阅读这篇论文,因为它解释了数据是如何聚集和标记的。
构建特征空间
在这一节中,我给出了如何将不同的数据类型转换成可操作的特性的一般描述。
目标变量和成功指标
很简单,目标变量取两个值之一:良性或恶意软件。这些数据大约 70%是良性的,30%是恶意软件。相比之下,据苹果首席执行官蒂姆·库克称,苹果应用商店中只有 1-2%是恶意软件。
这篇论文的作者获得了 94%的 AUC。我将看看我能否用我的方法复制类似的结果。
此外,我还将使用 F1 分数来检查模型性能。事实上,ROC 是所有可能阈值的平均值,而 F1 分数适用于 ROC 曲线上的任意点。
使用 F1 分数也提出了核心的商业问题:我们会为了更多的回忆而牺牲精确度吗?错过一个恶意软件应用程序的成本比禁止善意的开发者使用市场(误报)高得多吗?
换句话说,对于像 Aptoide 这样的商店来说,如果他们禁止一个善意的开发者,他们会损失多少潜在收入?另一方面,遗漏恶意软件会造成多大的声誉损害、用户伤害和收入损失?
此外,并不是所有的恶意软件都是一样的。事实上,它可能是广告软件、间谍软件、勒索软件或其他种类的软件。
这些问题与核心业务基准相关,超出了本文的范围。
数字特征
在 166 个数字特征中,160 个是二元的,6 个是连续的。我们手上有太多的特征,这可能会妨碍模型的预测性能。因此,查看每个要素的方差并移除方差较小的要素非常有用。
对于连续变量,我们必须考虑异常值。这与应用程序获得的评级数量特别相关,因为绝大多数应用程序从未获得过一次评级。
为了剔除异常值,我使用了图基的方法:异常值是来自四分位数的超过四分位数间距 1.5 倍的值,或者低于Q1 1.5 iqr,或者高于 Q3 + 1.5IQR 。
文本特征
谈到 NLP,我通常会提出以下问题:
- 略读文字能明白一个专栏是讲什么的吗?
- 它是否使用拉丁字母,是否都是同一种语言?
- 需要哪些预处理函数?
- 最紧凑最有代表性的文字改造方式是什么?
事实证明,许多条目都有汉字描述(也有一些土耳其语、韩语等……)。如果有一天我想生产或解释这个模型,我必须理解并向利益相关者解释它的选择。然而,我无法解释我不理解的东西,因此决定从文本中删除非拉丁字符。
以下是一个应用程序描述的示例:
Tap Tap Gems is a very addictive puzzle game. Tap two or more gems with the same colour to remove them from the board. You must think which gems should be removed next. To pass each level you must score certain amount of points. The more gems removed the more points you score. Game features 120 levels and 7 different gems.
另一个例子显示,即使使用严格的拉丁字符,人们可能仍然不知道发生了什么(我猜这是越南语?).
Game bi dn gian Vit bao gm 15+ cc mini quen thuc vi ngi Vit ta t xa n nay nh: Xm, Phm, Tin ln min Nam, Ling, Poker, X t, Mu binh, Bi co, X dzach v Tin ln min Nam m l. t lu Tin Ln Min Nam m L - Game bi dn gian Vit c cc game th yu qu bi ha cc p, giao din c chm sc rt k. Cc chc nng trong game hn hn cc game cng loi khc nh
预处理后,使用 TF-IDF 矢量化对文本数据进行转换。为了优化预测,使用网格搜索交叉验证来调整 TF-IDF 参数。
型号选择
起初我想选择 XGBoost,因为它的 AUC 最高。所以我开始做一个普通的数据科学工作流程,在数据管理之后:
- 实例化 TF-IDF 矢量器
- 实例化管道以避免泄漏
- 使用 GridSearchCV 研究参数如何影响模型预测
AUC 为 0.94,我很高兴,因为它符合作者的发现。然而,当比较训练/测试 AUC 时,下图表明我过度拟合:
相比之下,随机森林(RF)显示拟合不足:
逻辑回归没有过度拟合,但可能严重拟合不足。
XGBoost 应该是这里的明显赢家,但是,我决定继续使用普通的逻辑回归。它具有相当简单的可解释性,省去了我使用诸如 SHAP 、特征置换或时间等方法的麻烦。
语料库规模对预测的影响
由于这个任务涉及到 NLP,所以我想从文本数据中挖掘尽可能多的信息。处理稀疏数据时,逻辑回归往往会受到影响,而处理文本时,稀疏数据是典型的。
下面我们有两个图,一个是 ROC 曲线,一个是精准召回曲线。多条曲线显示了当我们添加更多的功能时,模型的性能只是略有提高。
剧透警告:文本数据无关紧要,下一节将说明 Android 清单中的数字特征是分类的最重要因素。
结果的解释
可以假设黑盒模型并不十分流行。因此,我开始理解分类器如何衡量其决策,并深入研究这些特征。
恶意软件的指标
就恶意软件的强有力指标而言,前 5 位是:
- Android . permit . read _ SYNC _ SETTINGS
- Android . permission . write _ SYNC _ SETTINGS
- Android . permission . manage _ ACCOUNTS
- com . Android . launcher . permission . uninstall _ SHORTCUT
- Android . permission . receive _ BOOT _ COMPLETED
以允许写同步设置为例,这意味着什么?当一个设备同步时(同步),它会将你手机上的数据与该服务的服务器同步。
下表是来自训练数据的快照,它表明给未安装的应用程序这些权限是一个需要谨慎的强有力的例子。
此外, MANAGE_ACCOUNTS ,允许应用程序添加/删除帐户并删除其密码。
此外, RECEIVE_BOOT_COMPLETED 是一个明显的妥协迹象。android 文档很好地总结了这一点:
虽然拥有此权限没有任何安全隐患,但它会增加系统启动的时间,并允许应用程序在用户不知情的情况下自行运行,从而对用户体验产生负面影响。
最后,不再支持 UNINSTALL_SHORTCUT 权限。这可能与恶意软件有关,因为旧软件往往有更多突出的漏洞。
针对恶意软件的指标
针对恶意软件的指标,主要特征并不明显:
- android.permission .蓝牙
- android.permission.READ_LOGS
- 。//Min_SDK
- Android . permission . read _ EXTERNAL _ STORAGE
- "铃声"
我不太清楚为什么连接配对的蓝牙和读取低级系统日志文件的权限是针对恶意软件的。也许它不像允许设备向附近的蓝牙设备做广告的其他权限那样糟糕(参见 BLUETOOTH_ADVERTISE )。
此外,我也不明白为什么在应用程序描述中有“铃声”会给你开绿灯下载它。
然而,我可以理解设置一个更高的 SDK 版本( Min_SDK )如何保护你的设备免受感染。该特性与 READ_EXTERNAL_STORAGE 非常一致。后者从 API 级别 19 开始实施,并提示用户自己启用/禁用权限。
不幸的是,参考文件没有代码或模型权重的解释。因此,我无法严格地将我的发现与作者的进行比较。
结论
处理 Aptoide 商店的数据表明,第三方商店中有大量的恶意软件。因此,如果苹果的商业行为保证了用户的安全,那么它们看起来是合理的。
然而,可以利用数据科学来创建一个快速而准确的分类系统,而苹果公司没有必要进行这种限制。与现有文献相比,逻辑回归模型易于建立并提供足够的性能。
像这样的项目有可能提高应用商店的安全性,并让人们更深入地了解漏洞是如何被利用的。该方法可以扩展到其他商店和操作系统。
在你走之前
如果您有兴趣了解更多关于网络安全和数据科学之间的交集,请查看另一个项目:
使用 Yolo-V4 检测面罩开/关或佩戴不当
利用计算机视觉解决当前现实世界的问题。
Yoav Aziz 在 Unsplash 上拍摄的照片
至少对我们大多数人来说,过去的两年是超现实的。这些天来,你环顾四周,无论你在世界的哪个角落,都有可能看到有人戴着面具。
特别是,如果你来自对戴口罩有严格要求的城市或国家,你可能会看到大多数人戴口罩,很少有人会戴错,有些人甚至根本不戴。我想看看机器视觉算法是否能完成完全相同的工作,而不是用一个权威单位来管理这个地区。
【https://github.com/kmt112/probable-lamp】Github 回购:
数据集引用:
原始掩模数据集:https://www.kaggle.com/andrewmvd/face-mask-detection
附加屏蔽数据集:https://github.com/cabani/MaskedFace-Net
问题陈述
首先,由于机器视觉算法必须能够在实时设置中进行预测,我们需要一种能够在相对较低的 FPS 环境中工作的算法。其次,该模型必须反应迅速,因为人们通常相对较快地超过摄像机视野。因为它必须准确地预测所有三个类别(戴上面具、摘下面具和面具佩戴不正确)。基于 mAP (mean 平均精度)来评价模型会更好。
Yolo-v4
根据上面的问题陈述,这使得 yolov4 成为理想的算法。Yolov4 在实时检测(FPS: > 45)方面始终具有较高的平均精度,此外,yolov4 是一个单级对象检测器,因此计算量较轻。有关 YoloV4 性能的更多信息,请参考https://blog . robo flow . com/PP-yolo-beats-yolov 4-object-detection/。
资料组
最初使用的数据集来自 T4 大学。该数据集由 800 多张带标签的照片组成。照片数据集也包括团体照片和个人照片。然而,初步的探索性数据分析表明,不同阶层之间存在巨大的阶层不平衡。对于戴错口罩的数据集。因此,有必要增加面罩佩戴不当的等级。
图一。阶层失衡(图片由作者提供)
改进数据集
巨大的阶级不平衡会导致你的模型在代表性不足的阶级中表现不佳,这就是为什么在训练中不同阶级的平等代表性是重要的。由于 yolo v4 模型只接受适当标签格式的照片,因此需要将 XML 文件格式转换为 TXT 文件格式。此外,还对图像进行了归一化处理,使其对图像分辨率的变化更加鲁棒。
添加新数据。戴口罩的人和不戴口罩的人很容易区分。然而,确定口罩是否佩戴不当要困难得多。因此,更重要的是包括更多关于不正确佩戴的口罩的数据。虽然在互联网上可以找到许多可用的数据集。我使用了数据集 MaskedFace-Net [1,2] 中的数据集。
图二。来自屏蔽网络数据集的样本数据(左),来自 kaggle 的屏蔽和未正确佩戴的样本图像(右)
标记新数据。不幸的是,没有办法自动化这个过程,因为我希望数据是干净的,我必须物理标记数据和边界框。使用这个 python 程序手动标记边界框和类。
数据建模
我们将利用 COCO 数据集的预训练权重进行迁移学习。这仅在第一次迭代中使用,此后我们将使用您在每个间隔保存的预训练权重。由于 yolo V4 是用 C 和 Cuda 编写的,为了调优超参数,必须在配置文件中完成。我已经上传了一个可以使用的配置文件,我将简要说明我为改进地图所做的更改。
width/height
:将分辨率大小改为 416,增加 yolov4 的宽度和高度提高了分辨率。batches
:当批次被细分时,这决定了将被并行处理的图像的数量。saturation = 1.5, Hue = 1.5
:改变饱和度和色调mosaic = 1
:马赛克数据增强将 4 幅训练图像以一定的比例组合成一幅(而不是 cutmix 中的两幅)。这可以防止过度依赖任何关键功能。blur = 1
:50%的时间会随机应用模糊。jitter = 0.3
:随机改变图像的大小和长宽比。
图像增强可以从现有的训练数据中创建新的训练样本,因此减少了收集更多数据的需要。这也使得模型对于图像中的扰动更加鲁棒。
结果
为了显示不平衡数据和正确表示的数据之间的差异,我为这两种数据训练了模型。首先,跨地图比较模型。
平均精度平均值
图 3。不平衡数据训练(左),平衡数据训练(右),作者图片
两个数据集之间模型性能没有太大差异。事实上,具有更平衡数据的模型在初始迭代中表现稍差。
IOU 阈值与映射图
图 4。IOU 阈值与图的关系,图由作者提供
并集上的交集和映射的一个重要区别。也许在这一点上,有必要解释一下 IOU 和 mAP 是什么,以及它们之间的关系。一般来说,mAP 和 IoU 是反向相关的。这是因为 IoU 可以被认为是包围盒的紧密度。
图 5。欠条插图,图片来自https://www.mdpi.com/2073-8994/13/2/262/htm
如果你设置了一个很高的 IoU 阈值,你的模型将不得不定义一个非常精确的边界框。这可以从图 5 中看出,当 IoU 设置为 0.9 时,mAP 显著下降。
结果可视化
这里有一些例子来说明当不同的类达到平衡时模型的表现。
图 6。旧型号(左)和新型号(右),作者图片
图 7。旧型号(左)和新型号(右),作者图片
当更多面具佩戴不正确的照片被添加到训练模型中时,该模型在检测面具是否佩戴正确方面表现得明显更好。当比较新旧模型时,这一点在图 5 中也很明显。
快速部署时间
该模型在处理单个帧中的大量类时也没有困难。
图 8。许多人的预测,图片来自 Kaggle
结论
总的来说,Yolo-V4 的表现比预期的要好,不仅结果很好,而且部署和验证也很快。虽然我最初的计划是在现实生活中部署这一点,但我当地的公共交通提供商抢先一步,在公交和火车站部署了一个 CV 解决方案。
展望未来,我在 yolo-v4 上遇到的一个问题是我不熟悉配置文件、C 和 Cuda 编程语言。由于对 python 和 pytorch 更加熟悉,我认为 yolo-v5 可以让我更好地理解并调整参数来改进我的模型。
也就是说,本教程大量引用了原版 darknet github repohttps://github.com/pjreddie/darknet的内容。
参考
- Adnane Cabani、Karim Hammoudi、Halim Benhabiles 和 Mahmoud Melkemi,“新冠肺炎背景下正确/不正确遮盖面部图像的数据集”,《智能健康》,ISSN 2352–6483,Elsevier,2020 年,DOI:10.1016/j . smhl . 2020 . 100144
- Karim Hammoudi、Adnane Cabani、Halim Benhabiles 和 Mahmoud Melkemi,“通过自拍验证防护面罩的正确佩戴:设计一个移动应用程序“CheckYourMask”以限制新冠肺炎病毒的传播”,CMES——工程与科学中的计算机建模,第 124 卷,第 3 期,第 1049-1059 页,2020 年,DOI:10.32604/cmes . 2020 . 1663
用 YOLOv5 检测城市场景中的物体
理解大数据
作为我在 MILA (魁北克的人工智能研究所)攻读机器学习硕士学位的一部分,在蒙特利尔市工作期间, I 开发了一个人工智能城市物体检测解决方案,用于来自平移-倾斜-缩放(PTZ)交通摄像机的视频馈送。该原型可以检测五种不同类别的对象,即车辆、行人、公共汽车、骑自行车的人和建筑对象。这是一个检测一帧的例子,拍摄于蒙特利尔市中心的 De La Montagne 街和 René-Levesque 大道的交叉口。
显示所有五个对象类别的对象检测示例-按作者分类的图像
为了显示该模型如何推广到另一个城市环境,这里有一个离线检测(即,非实时)的例子,其中为这个测试 youtube 视频的每一帧产生一个推理。
来自 youtube 视频的测试城市场景的离线检测示例——视频作者
感谢蒙特利尔市的开放数据政策和开源软件政策,我很高兴地宣布我的项目正在变成开源项目!本周,我们发布了代码、训练模型以及所有带注释的图片(数据集)。
我还写了一份详细的技术报告,等待 MILA 的审查和批准,我也将能够分享(敬请期待!).与此同时,我写这篇博客的目的是简要概述这个项目及其成果,让人们了解这个新开放的数据集。
重要提示:在继续之前,我想澄清一下这个项目的动机,让那些可能对这项技术的使用有疑虑的人放心。T2 城市交通管理中心(CGMU)是蒙特利尔市智能交通系统的心脏和大脑。CGMU 运营商在整个地区安装了 500 多个交通摄像头(见地图),可以监控道路网络上的交通状况,并在出现问题时(如事故或车辆故障)进行处理。为了帮助他们更快地检测事故,蒙特利尔市希望建立一个自动道路异常检测系统。我们现在发布的这个对象检测解决方案构成了一个重要的构建模块,将有助于实现这一长期目标。
同样重要的是:在整个项目中,尊重公民隐私是非常重要的,尤其是现在我们正在发布注释。值得注意的是,这些注释与已经通过城市开放数据网站公开发布的图像相关联。此外,虽然安装用于获取这些图像的摄像机是为了协助 CGMU 操作员执行日常交通管理任务,但它们也被调整为限制收集的信息。例如,相机分辨率被设置为既不能识别人脸也不能识别车牌,并且图像只以 5 分钟的间隔保存和发布。蒙特利尔的《数字数据宪章》是一份很好的参考资料,有助于了解该市如何管理和规范数字数据的生命周期。
既然我们已经弄清楚了这一点,让我们开始吧!
关键特征
以下是最终目标检测模型的一些关键特征。他们:
- 我们在单个 GPU 上使用近 19k 幅图像进行了训练(来自蒙特利尔市的 7007 幅训练图像+来自 MIO-TCD 数据集的 11877 幅图像)
- 包括较小和较大的神经网络体系结构,它们可以分别在 CPU 上以 167 ms 的延迟和在 GPU 上以 16.6 ms 的延迟运行。
- 兼容不同的相机品牌和型号、图像分辨率以及不同的变焦和方向设置。
- 对视觉伪像(例如失焦、镜头上的雨滴或灰尘、阳光眩光等)以及不同的天气和天气条件具有鲁棒性。
资料组
蒙特利尔市数据集由 10,000 幅分辨率为 350x288、352x240 和 704x480 的图像组成,带有 Pascal VOC 格式的车辆、公共汽车、行人、骑自行车者和建筑物体的相关物体检测注释。所有注释都是使用 CVAT (计算机视觉注释工具)生成的。然后,数据集被分成四部分,如下所示:
- 70%的图像用于训练,
- 10%用于验证,
- 10%用于域内测试
- 10%用于域外测试
训练集用于学习模型权重和偏差,而验证集在训练期间用于监控性能并确定模型何时开始过度拟合。它还用于评估超参数的不同配置。测试集在项目结束时用于报告绩效。
训练集、验证集和域内测试集都使用相同的摄像机子群体(即交叉点),而域外测试分割使用在训练期间不可见的保留摄像机群体。
下表显示了每个集合中包含的每个类的实例数量。我们可以看到,阶层分布不均衡,车辆远远多于骑自行车的人和公交车。
每个数据分割中不同类别的影像和对象总数-蒙特利尔市数据集-按作者分类的影像
为了帮助提供更多代表性不足的类别的训练示例,使用了额外的数据集,即 MIOvision 交通摄像机数据集 (MIO-TCD)。因为它包含了视角非常相似、分辨率非常相似的图像,所以非常符合我们的需求。它还包含冬季月份的图像,考虑到蒙特利尔市数据集只包含夏季月份的图像,这是一个有趣的补充。从总共 11877 幅附加图像中选择了包含骑自行车的人、行人和公共汽车的所有图像。因此,我们能够将 2260 名骑自行车的人和 8319 辆公共汽车添加到训练示例中。但是,MIO-TCD 不包含任何带标签的构造对象。
来自 MIO 的样本图片-TCD 数据集 —作者图片
模型
YOLOv5 是一个对象检测模型,于 2020 年 5 月发布,作为 github 上的 Pytorch 实现,并被选为该项目的基础。在评估我们的选项时,YOLOv5 是可用的最快和最准确的对象检测模型之一。此外,它受益于一个非常大的用户社区,这意味着它正在积极开发中,每周都有改进。为了稳定起见,commitbb 8872(2020 年 9 月 8 日发布)被分叉用于项目具体开发。YOLOv5 包括 4 种不同的网络架构大小:小型(S)、中型(M)、大型(L)和 X-大型(X)。
单发多箱探测器 (SSD)被选为基准型号,与 YOLOv5 进行比较。在 2015 年创建时,SSD 是最快的型号之一,这使得它非常适合实时应用程序。它不再被认为是最先进的,但由于其简单性,仍然经常被用作基线模型。
迁移学习
在非常大的数据集上预先训练模型以学习有意义的表示,并随后在感兴趣的任务上对其进行微调,通常有利于性能。这种策略被用于 YOLOv5(在 MS COCO 对象检测数据集上预先训练)和 SSD(在 ImageNet 图像分类数据集上预先训练)。
增加
YOLOv5 和 SSD 都在训练时使用数据扩充来获得不容易过度拟合的解决方案。从一个时期到另一个时期,各种增强被采样并应用于相同的输入图像,这导致数据集大小和输入图像可变性的人为增加。光度和几何放大的一些例子如下所示:
光度扩展示例-作者提供的图片
光度扩展的其他示例-图片由作者提供
几何增强的例子——作者图片
马赛克增强是一种新的图像增强方法,在 YOLOv4 中引入,并在 YOLOv5 中保留。镶嵌增强包括混合 4 个不同的训练图像,这具有允许检测正常背景之外的对象的效果,因此提高了泛化能力。另一个好处是,它减少了使用大的小批量的需要。
计算资源
所有实验和训练都是使用谷歌 Colab Pro 实例(包括英伟达特斯拉 P100 或 V100)进行的,预期超参数搜索是使用谷歌云平台(GCP)上的 GPU 实例进行的。这里是有用的教程为训练我们的模型设置 GCP。
结果
乍一看,与基准 SSD 模型相比,YOLOv5 的检测性能(以平均精度(mAP) 表示)远远优于基准 SSD 模型。当仅在蒙特利尔市数据集上训练时,具有 26.7 M 参数的 SSD 实现了 0.466 的 mAP,而 yolov 5‘M’模型在大致相同的模型大小下实现了 0.663 的 mAP。事实上,所有四种 YOLOv5 变体在推理和训练过程中都更快。
模型大小对训练时间、性能和延迟的影响。输入图像尺寸:320x320。仅在蒙特利尔市数据集上训练,300 个时期-图片由作者提供
除了模型架构大小之外,另一个被发现会显著影响性能和延迟的配置设置是输入图像大小。基准 SSD 使用 300x300 的输入图像大小,而 320x320、512x512 和 704x704 的大小在 YOLOv5 'X '型号上进行了测试。蒙特利尔市数据集的最大图像分辨率为 704x480,因此在输入图像尺寸为 704x704 的情况下,不会丢失任何像素信息。这可能会对小物体的检测性能产生很大影响,例如行人和建筑圆锥体,因为它们往往非常狭窄。下表显示,随着图像输入大小的增加,检测性能会提高,但 GPU 延迟和训练时间会增加。
输入图像大小对训练时间、性能和延迟的影响。批量大小 1 和 32 分别用于 CPU 和 GPU 推断。数据集:蒙特利尔市,经过 300 个纪元的训练-图片由作者提供
在固定架构和输入图像大小的情况下,增加 MIO-TCD 数据集可以提高 YOLOv5 模型的性能,但不能提高 SDD 的性能。下表显示了每个类别的增量收益。所有的职业都有进步,除了建筑类的表现有所下降。这并不奇怪,因为在 MIO-TCD 数据集中没有构造对象。组合两个数据集的一个缺点是训练时间的增加,考虑到训练样本的数量增加了一倍以上,这并不奇怪。
添加 MIO-TCD 数据集对成绩和训练时间的影响。经过 300 个纪元的训练—作者提供的图片
使用 Orion 进行了严格的超参数搜索,Orion 是一种用于黑盒函数优化的异步框架,已经集成为蒙特利尔市刚刚发布的代码的一部分。这方面的更多细节将在报告中提供。一个有趣的发现是,通过超参数搜索获得的性能提升与使用原始 YOLOv5 存储库中的基线超参数获得的性能提升相比微不足道。这意味着使用基线超参数已经可以预期非常好的性能,并且节省了他们自己的探索过程。
在测试集上评估了最佳的最终模型。下表显示了域内(训练期间看到的交叉点)和域外(新交叉点)测试图像的最终性能。
在域内和域外测试集上的最终模型性能—图片由作者提供
正如预期的那样,在看不见的交叉路口,性能会下降,但对人眼来说,检测质量仍然很好,如下图所示的小模型。
YOLOv5 Small,输入图像大小为 512x512:四个域外测试集图像—图像由作者提供
结论
由于未来的目标是检测道路网络上的事件和其他异常,蒙特利尔市在不久的将来可以遵循的自然下一步是将一个预先训练的 YOLOv5 架构纳入多对象跟踪神经解决方案。为了帮助生成训练该解决方案所需的多对象跟踪数据集,可以使用我们提出的最佳模型作为预注释图像的手段,并最小化人类注释者的负担。
使用 Python 检测异常值
使用隔离森林进行自动异常检测
鲁珀特·布里顿在 Unsplash 上的照片
什么是离群点检测?
在构建任何类型的机器学习模型之前探索数据时,检测异常值可能很重要。异常值的一些原因包括数据收集问题、测量错误和数据输入错误。检测异常值是分析数据点中潜在错误的一个步骤,这些错误可能需要在模型训练之前消除。这有助于防止机器学习模型学习不正确的关系并潜在地降低准确性。
在本文中,我们将模拟一个来自两个分布的数据集,看看我们能否检测出异常值。
数据生成
为了测试异常值检测模型,生成了来自两个样本的虚拟数据集。从一个分布中随机抽取 200 个点,从一个单独的移位分布中随机抽取 5 个点,这样我们就有了下面的起点。你会看到蓝色的 200 个初始点和橙色的异常值。我们知道哪一个是哪一个,因为这是生成的数据,但在未知的数据集上,目标是在没有内部知识的情况下从本质上发现异常值。让我们看看一些现成的 scikit-learn 算法能做得有多好。
初始数据集
隔离森林
检测异常值的一种方法是使用 scikit-learn 的隔离森林模型。这使我们能够构建一个类似于随机森林的模型,但旨在检测离群值。
数据生成后的 pandas 数据框架起点如下——一列为数值,另一列为我们可用于准确性评分的基本事实:
初始数据帧—前 5 行
拟合模型
第一步是拟合我们的模型,注意拟合方法只接受 X,因为这是一个无监督的机器学习模型。
预测异常值
使用 predict 方法,我们可以预测一个值是否是异常值(1 不是异常值,接近-1 就是异常值)。
查看结果
为了查看结果,我们将绘制并计算精确度。在原始数据集上绘制新的预测列会产生以下结果。我们可以看到离群值被恰当地提取出来;然而,我们的标准分布也有一些尾部。我们可以进一步修改污染参数,将其调整到我们的数据集,但这是一个很好的开箱即用的过程。
在这个例子中,还可以简单地计算准确度、精确度和召回率。该模型有 90%的准确性,因为初始数据集中的一些数据点被错误地标记为异常值。
Output: Accuracy 90%, Precision 20%, Recall 100%
解释规则
我们可以使用决策树分类器来解释这里发生的一些事情。
|--- Value <= 1.57
| |--- Value <= -1.50
| | |--- class: Outlier
| |--- Value > -1.50
| | |--- class: Standard
|--- Value > 1.57
| |--- class: Outlier
基本规则是将-1.5 和 1.57 作为确定“正常”的范围,其他一切都是异常值。
椭圆形信封
隔离林不是检测异常值的唯一方法。另一个适合高斯分布数据的是椭圆包络。
代码本质上是相同的,我们只是换出了正在使用的模型。由于我们的数据是从随机样本中抽取的,这导致了一个稍微更好的拟合。
Output: Accuracy 92%, Precision 24%, Recall 100%
可以对我们的数据运行不同的异常值检测模型来自动检测异常值。这可能是分析可能对我们的建模工作产生负面影响的潜在数据问题的第一步。
所有的例子和文件都可以在 Github 上找到。
在 Orange 机器学习/深度学习平台下检测胸部 x 光图像中的肺炎
Orange 是一个面向数据科学和机器学习项目的可视化编程环境。在 Orange 下,用户可以拖放类似乐高的组件来构建一个完整的解决方案,包括他/她的项目的数据操作/可视化和模型构建/训练/验证。这篇文章阐述了开发和比较不同的二进制分类模型对正常胸部 x 光图像和肺炎胸部 x 光图像橙色的过程。
图一。橙色屏幕截图—小部件(左)、工作流程(中)、图像查看器(右)。
以下是关于数据、模型和结果的简介:
- 1341 张正常胸片和 3875 张肺炎胸片;
- 66%用于培训,34%用于验证;
- 用于特征提取的 Inception v3 和用于特征分类的多层感知器一起实现 AUC 为 0.996,F1 得分为 0.972;
这篇文章的灵感来自于使用橙色的图像分类——通过胸部 x 光预测肺炎。视频和这篇帖子的区别在于,视频的重点是预测过程,而这里的重点是展示不同模型的开发和比较过程。
这篇文章的其余部分组织如下:介绍数据集,开发特征提取和特征分类的机制,并介绍不同模型的性能结果。
胸部 X 射线图像数据集
https://www.kaggle.com/paultimothymooney/chest-xray-pneumonia
数据集分为 3 个文件夹(train/val/test ),并包含每个图像类别(正常/肺炎 jpeg)的子文件夹。表 1 显示了所有文件夹下的图像数量。这篇文章只使用火车文件夹进行研究。
表 1。图像文件夹。
肺炎图像分类的工作流程
图二。完整的工作流程。
图 2 显示了构建、训练和比较我们的模型的工作流程。
- 读取正常胸片图像并提取其特征 —【图 2/左上】读取正常使用导入图像控件从训练/正常中读取正常胸片图像;"图像嵌入小工具"将图像转换成其特征(其机制将在后面讨论);“图像查看器控件”和“数据表控件”分别用于检查图像和特征。
- 读取肺炎胸片图像并提取其特征 —【图 2/左下】读取肺炎使用导入图像控件从列车/肺炎读取肺炎胸片图像;"图像嵌入小工具"将图像转换成其特征(其机制将在后面讨论);“图像查看器微件”和“数据表微件”分别用于检查图像和特征。
- 合并正常胸片图像和肺炎胸片图像的特征列表 —【图 2/中心】连接小工具合并特征列表,并指定图像来源为标签。
- 训练和比较四个特征分类器 —【图 2/右】四个特征分类器,kNN、随机森林、SVM、MLP 神经网络,分别从各自的 widgets 导入。测试和范围小部件显示最终结果。
图 3 显示了由图像查看器小工具导出的一些正常的胸部 x 光图像。”
图三。“图像查看器小部件”显示一些胸部 x 光图像。
图 4 展示了深度学习模型(如 VGG16 、 VGG19 、 Inception V3 、 SqueezeNet 等)。)由"图像嵌入小工具支持。**《盗梦空间》v3 被选为研究对象。注意:这里的过程是一个预先训练好的深度卷积神经网络进行特征提取和传统的分类器或多层感知器进行特征分类的结合。请查阅参考资料,了解更多详情。
图四。“图像嵌入小部件”支持几种深度学习模型。
性能赋值
图 5 显示了 kNN、随机森林、SVM 和 MLP 神经网络四种分类模型下的性能指标。
图 5。“测试和范围小部件”显示验证结果。
结论
作为一名程序员,我习惯于一行一行地编码,并在试错的基础上开发/比较特定问题的不同模型,我很欣赏 Orange 为图像分类提供了如此优雅的解决方案。首先,Orange 提供了一种快速原型化基本想法的简单方法,这是深入了解新问题的必要步骤。第二,Orange 为未来的基准测试和改进提供了一个基线,例如应用高级模型、迁移学习、数据扩充等。第三。可能最重要的是,Orange 展示了为数据科学和机器学习设计可视化编程环境的哲学和原则,例如,使用预先训练的模型来获得嵌入,提供简单的机制来可视化原始数据和表示等。相比之下,在 Orange 中缺乏最新的高级深度学习图像分类模型似乎是解决问题的一个小挫折。
感谢阅读。如果你有任何意见,请随时给我留言。
参考文献
- 橙色图像分析[ 链接
- 可视化编程环境中支持深度学习的不同方法[ 链接
- 在图像聚类中使用 Keras 的预训练模型进行特征提取[ 链接
- Keras 中预先训练的模型[ 链接 ]
- PyTorch [ 链接 ]中的预训练模型
使用 TinyML 和 TensorFlow 在 Arduino 上检测口袋妖怪
用颜色来预测这是皮卡丘还是妙蛙种子
机器学习(ML)模型的部署环境正在发生变化。最近几年,我们从本地培训模型并在独立脚本上运行它们,发展到在大规模和专门的设置中部署它们。然而,该行业并不仅仅关注大规模生产的 ML,还关注其小型、便携式和可访问的对应产品——因为机器学习已经在嵌入式系统中找到了一席之地。
改进机器学习不仅仅是让算法变得更智能、更大。随着该领域的发展,它们的速度、尺寸和计算效率也有所提高。这一进步导致了 TinyML ,这是机器学习的子领域,涉及像微处理器这样的功率受限设备中的模型。
这篇文章解释了如何在一个 Arduino NANO 33 BLE Sense 微处理器上创建一个 TensorFlow 模型来检测神奇宝贝皮卡丘和妙蛙种子。我们将在这里看到的内容包括数据收集程序(在 Arduino 上完成)、简要的数据分析、模型训练、如何将所述模型转换为 Arduino 的tensor flow Lite for micro controllers库理解的格式,以及如何在设备上部署它。
图 1:皮卡丘、妙蛙种子和阿杜伊诺。被我。
用于微控制器的 Arduino Nano 33 BLE 传感和 TensorFlow Lite
Arduino Nano 33 BLE 传感器是一个配备了各种传感器的微处理器。其中,我们将使用其 APDS 9960 传感器来测量光的颜色、光的强度,并检测接近度。光色探测器测量物体的R(ed)G(reen)B(lue)颜色强度;光检测器测量环境光的强度;近程探测器测量传感器附近是否有物体。
这款微处理器是少数支持微控制器 TensorFlow Lite的微处理器之一,TensorFlow C++库用于在微控制器上执行 tensor flow 模型。它是一个轻量级的库——它的核心运行时重约 16 KB,不需要操作系统支持或任何 C 或 C++库(source)——适合小型嵌入式设备。然而,它的大小是该库的一个限制,因为它缺少作为更大的 TensorFlow 库的一部分的许多操作符,例如用于 Python 的 TensorFlow。所以,我怀疑它能否运行大型 GAN 或变压器。
欲了解更多信息,请参考以下链接微控制器 TensorFlow Lite】。
检测草图
我们的草图 (Arduino 对程序的术语)是一个皮卡丘和妙蛙种子检测器,采用了 TensorFlow (Python)中训练的逻辑回归模型。要触发预测,用户必须将口袋妖怪放在颜色传感器附近,以生成一组 RGB 值,模型将使用这些值来预测该对象是皮卡丘还是妙蛙种子。如果预测是皮卡丘,Arduino LED 变成黄色,或者如果是妙蛙种子,变成绿色。不管结果如何,它也会打印标签。
老实说,我可以把这个项目称为“黄色或绿色探测器”或“香蕉或梨探测器”,因为它测量的是颜色,而不是实际的物体。尽管如此,使用口袋妖怪是我最喜欢的用例,“检测黄色和绿色”不像“检测皮卡丘和妙蛙种子”那样吸引人
皮卡丘和妙蛙种子。我从我的游戏中截取的截图。任天堂的。
我的设置
对于这个项目,我使用的是 Arduino Nano 33 BLE Sense 和 Arduino 的桌面 IDE,你可以在 https://www.arduino.cc/en/software 的找到。安装并打开后:
- 进入工具->电路板->电路板管理器搜索“纳米 BLE”安装电路板相关的代码。
- 进入工具->管理库,搜索“Arduino _ apds 9960”——传感器的库——并安装它。
更多信息请参见https://blog . arduino . cc/2019/10/15/get-started-with-machine-learning-on-arduino/。
开始吧!
数据收集
没有数据就没有机器学习。因此,第一步涉及收集训练数据。为了收集它,我写了一个草图,它读取 RGB 强度颜色并将其值作为七个元素的元组打印到 serial,其中前三个是传感器读取的 RGB 值,接下来的三个是 RGB 比率——通过将颜色值除以所有值的总和获得——最后一个是类标签** —皮卡丘或妙蛙种子——我在脚本中指定的字符串。(我收集了两种格式的颜色读数,因为我想使用原始值分析数据,并用比率进行训练。)然后,使用串行监视器(相当于标准输出,或者可以看到草图打印内容的地方),扫描您的对象并将打印值复制粘贴到 CSV 文件中。我创建了两个 CSV,一个用于皮卡丘(类标签为 0 ),另一个用于妙蛙种子(类标签为 1)。图 3 是我的串口监视器的截图;请注意带有列名和七个值的标题行。**
图 3。数据打印到串行。
下面是收集数据的代码,从这里的获得,并根据我的项目需要进行修改。让我们过一遍。
/*
Object color sampler
--------------------
Samples the color of objects and outputs CSV logfile to serial console
Hardware: Arduino Nano 33 BLE Sense board.
Usage: Place object of interest to the color sensor
This example code is in the public domain.Taken and modified from
[https://create.arduino.cc/editor/TensorFlowExamples/ca761558-13ed-4190-baee-89ced06147c3/preview](https://create.arduino.cc/editor/TensorFlowExamples/ca761558-13ed-4190-baee-89ced06147c3/preview)
*/#include <Arduino_APDS9960.h>void setup()
{Serial.begin(9600);// Check if the Serial port is ready
while (!Serial)
{
};// Check if the color sensor is ready
if (!APDS.begin())
{
Serial.println("Error initializing APDS9960 sensor.");
}// Print the header
Serial.println("Red,Green,Blue,RedRatio,GreenRatio,BlueRatio,Class");
}void loop()
{
// a is the ambient light intensity
int r, g, b, a, p;
float sum;// Check if both color and proximity data sample is available.
while (!APDS.colorAvailable() || !APDS.proximityAvailable())
{
}// Read the color and proximity sensor.
APDS.readColor(r, g, b, a);
sum = r + g + b;
p = APDS.readProximity();// if object is close and well enough illumated
if (p == 0 && a > 10 && sum > 0)
{float redRatio = r / sum;
float greenRatio = g / sum;
float blueRatio = b / sum;// Print the data in CSV format; the second argument is the number's precision.
Serial.print(r);
Serial.print(',');
Serial.print(g);
Serial.print(',');
Serial.print(b);
Serial.print(',');
Serial.print(redRatio, 3);
Serial.print(',');
Serial.print(greenRatio, 3);
Serial.print(',');
Serial.print(blueRatio, 3);
Serial.print(',');
// This number is the class. Remember to change it!
Serial.print('0');
Serial.println();
}
}
草图从setup()
功能开始。这里,我们设置串行输出,检查颜色传感器是否就绪,并打印 CSV 文件的文件头。第二个函数,loop()
,是程序的主例程;除非你结束它,否则它会永远运行下去。它的第一条语句定义了 RBG 颜色强度变量、环境光强度和邻近度。然后,我们用APDS.readColor(r, g, b, a)
读取颜色(和光强度),接着对颜色值求和,用APDS.readProximity()
读取接近度。该函数返回一个从 0 到 255 的值,其中 0 表示最近,255 表示最远(源)。
读取值后,我们需要一个 if 语句,如果对象在传感器附近(p == 0),如果场景光照充足(a > 10),如果存在颜色(sum > 0),则执行该语句。如果为真,它将计算颜色比率,并以 CSV 格式打印到串行。
现在我们有了数据集(图 4)。
图 4。数据集的示例。
分析数据
我喜欢数据——包括探索数据和分析数据。在收集颜色数据时,我想知道如果我将原始颜色元组升级并转换为 RGB 颜色,会得到什么颜色。如前所述,传感器输出的 RGB 强度不是被扫描物体的真实 RGB 颜色。例如,扫描皮卡丘不会产生一串(255,255,0)-黄色的 RGB 颜色-而是一个类似(98,97,63)的元组,即“有很多红色、绿色,蓝色较少。”尽管如此,我还是很好奇,想看看这些元组在 RGB 尺度下是什么样子。(注意:RGB 颜色是一个加法系统,其中红色、绿色和蓝色光相加在一起,并表示为一个三元组,其中每个值都在 0 到 255 的范围内。)
下面是分析代码:
import cv2
import pandas as pd
import numpy as np
import matplotlib.pyplot as pltfrom sklearn.preprocessing import MinMaxScaler
from mpl_toolkits.mplot3d import Axes3D# For sampling the dataset
LENGTH = 2000# Read data
pikachu = pd.read_csv('data/pikachu_complete.csv')
bulbasaur = pd.read_csv('data/bulbasaur_complete.csv')# Get a sample of length LENGTH
pikachu = pikachu.sample(n=LENGTH, random_state=1)
bulbasaur = bulbasaur.sample(n=LENGTH, random_state=1)# Index the wanted columns
pikachu_vals = pikachu[['Red', 'Green', 'Blue']]
bulbasaur_vals = bulbasaur[['Red', 'Green', 'Blue']]# Upscale the values to the RGB scale.
pikachu_scaler = MinMaxScaler(feature_range=(0, 255))
bulbasaur_scaler = MinMaxScaler(feature_range=(0, 255))# Create new DataFrame with the scaled values.
pikachu_vals_scaled = pd.DataFrame(pikachu_scaler.fit_transform(
pikachu_vals), columns=pikachu_vals.columns)
bulbasaur_vals_scaled = pd.DataFrame(bulbasaur_scaler.fit_transform(
bulbasaur_vals), columns=bulbasaur_vals.columns)def draw_colors_rectangle(df):
"""
This function draws the colors.
"""
# The first two values of np.zeros(...) represent the length of the plot
# the 3 is because of RGB
plot_length = 150
plot = np.zeros((plot_length, LENGTH, 3), dtype="uint8")# This loop draws in a rectangle the DataFrame's colors tuple.
# I'm using sort to sort the colors based on its red color.
# The reverse is for drawing them from darkest to lightest.
for idx, val in enumerate(sorted(df.to_numpy(), key=lambda x: (x[0]),
reverse=True)):
cv2.rectangle(plot, (int(idx), 0), (int(idx+1), plot_length),
color=list(val), thickness=-1)plt.axis("off")
plt.imshow(plot)
plt.show()def draw_3d_plot(colors):
r, g, b = zip(*colors)
r = np.array(r) / 255.0
g = np.array(b) / 255.0
b = np.array(b) / 255.0x = np.array(colors)fig = plt.figure()
ax = Axes3D(fig)ax.scatter(r, g, b, c=x/256.0)
ax.set_title("Pixel 3D plot")
ax.set_xlabel('R')
ax.set_ylabel('G')
ax.set_zlabel('B')
fig.set_size_inches(14, 8)
plt.show()draw_colors_rectangle(pikachu_vals_scaled)
draw_colors_rectangle(bulbasaur_vals_scaled)
draw_3d_plot(pikachu_vals_scaled.to_numpy())
draw_3d_plot(bulbasaur_vals_scaled.to_numpy())
首先,我加载了所需的库—scikit—learn, Matplotlib ,OpenCV,和Pandas—然后是数据集和索引想要的列。然后,我使用 scikit-learn 的 MinMaxScale 将值提升到 RGB 比例,例如pikachu_scaler = MinMaxScaler(feature_range=(0, 255))
,并创建了draw_colors()
函数。这个函数有一个参数 RGB 数据帧——并使用[cv2.Rectangle()](https://docs.opencv.org/master/d6/d6e/group__imgproc__draw.html#ga07d2f74cadcf8e305e810ce8eed13bc9)
创建一个矩形,其中每个条目都是一种颜色。创建矩形的循环迭代经过排序和反转的数据帧,从最暗(255)到最亮(0)绘制颜色。图 5 和图 6 显示了颜色。****
图 5。皮卡丘的数据集放大了 RGB 颜色。
图 6。妙蛙种子的数据集放大了 RGB 颜色。
我的小颜色实验并没有像我想象的那样成功。第一张图片展示了皮卡丘升级的传感器读取的颜色,它没有黄色。另一方面,妙蛙种子的颜色似乎有各种各样的绿色调。在第二次尝试将颜色可视化时,我将它们绘制在一个 3D 散点图中(图 7 和图 8)。
图 7。皮卡丘的数据集在 3D 中放大了 RGB 颜色。
图 8。妙蛙种子的数据集在 3D 中放大了 RGB 颜色。
除了绘制颜色,这些可视化显示它们的范围和关系。皮卡丘的原色是黄色,一种由红色和绿色获得的颜色。这就是为什么图表显示了这些音调之间的线性关系。然而,第二张图有更纯的绿色和更多的蓝色,因为这些是妙蛙种子的颜色。
训练模型
我的皮卡丘和妙蛙种子分类器是用 Python 中的 TensorFlow 训练的逻辑回归模型。网络有一个单元的层,输入形状为 3(因为我们有三个特征),它用 Adam 优化器训练,学习率为 0.01,并使用二进制交叉熵损失函数。由于激活是 sigmoid,所以它的输出是一个 0 到 1 之间的数其中 0 是皮卡丘,1 是妙蛙种子;在逻辑回归模型中,我们通常将阈值设置为 0.50,这意味着其下的任何值都是 0 类,大于或等于的值是 1 类。我使用 10 个时期、0.1 的验证分割和回调来拟合模型,以查看 TensorBoard 中的进度。以下是培训代码:
**import datetime
import osimport pandas as pd
import tensorflow as tf
from sklearn.metrics import classification_report
from sklearn.model_selection import train_test_splitLENGTH = 2000
ts = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")# TensorBoard directory
log_dir = "/tmp/tensorboard/{}".format(ts)
tensorboard_callback = tf.keras.callbacks.TensorBoard(
log_dir=log_dir, histogram_freq=1)# Select a subset of the data
pikachu = pd.read_csv('data/pikachu_df.csv').sample(n=LENGTH, random_state=1)
bulbasaur = pd.read_csv(
'data/bulbasaur_df.csv').sample(n=LENGTH, random_state=1)# Create the DataFrame
X = pd.concat([pikachu, bulbasaur])
y = X.pop('Class')# Use 30% of data for testing
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=0)model = tf.keras.Sequential(
tf.keras.layers.Dense(units=1,
input_shape=[3],
activation='sigmoid')
)model.compile(
optimizer=tf.optimizers.Adam(learning_rate=0.01),
loss=tf.keras.losses.BinaryCrossentropy(),
metrics="accuracy"
)model.fit(X_train, y_train,
epochs=10,
validation_split=0.1,
callbacks=[tensorboard_callback],
)# Predict and assess
y_pred = model.predict(X_test)
preds = [1 if x > 0.50 else 0 for x in y_pred]
print(classification_report(y_test, preds))**
至于其训练性能,该模型在训练期间实现了 0.97 和 0.43 的准确度和损失,在验证数据上实现了 0.96 和 0.42。图 9 和图 10 给出了张量板图。要访问 TensorBoard,从终端执行tensorboard --logdir /tmp/tensorboard
;确保您使用的是第 13 行中的父目录。
图 9:训练和验证数据的历元精度。
图 10:训练和验证数据的纪元丢失。
训练之后,我使用测试数据集进行预测。然后,我将结果转换为 0 或 1,这取决于可能性是小于还是大于 0.50 的阈值。最后,我使用 scikit-learn 的分类报告来评估模型的性能。图 11 显示了这些数字。
图 11:测试数据集的分类报告。
由于到处都是 0.97+的值,我们可以假设模型按预期工作。
转换模型
我们不能在 Arduino 上使用这个模型。首先,我们必须将其转换为 TensorFlow Lite 模型,然后在 C++头文件中将其编码为字节数组。就像这样(将下面几行添加到培训脚本中):
**# Convert the model
converter = tf.lite.TFLiteConverter.from_keras_model(model)
tflite_model = converter.convert()# Save the model to disk
model_path = "model/pika_bulba{}.tflite".format(ts)
open(model_path, "wb").write(tflite_model)print("Model is {} bytes".format(os.path.getsize(model_path)))**
这些行将模型转换为 TensorFlow Lite 模型,并将其保存到磁盘。导出后,我们检查模型的大小(以字节为单位)以确保它很小——我的是 984 字节。然后,从终端运行xxd -i model_path > model.h
将模型编码为一个名为 model.h 的 C++头文件中的字节数组。这个文件是我们将加载到 Arduino 程序中的模型。
它应该是这样的(为了简单起见,我删除了大部分值):
**unsigned char model[] = {
0x1c, 0x00, 0x00, 0x00, 0x54, 0x46, 0x4c, 0x33, 0x14, 0x00, 0x20, 0x00,
0x04, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00,
0x18, 0x00, 0x1c, 0x00, 0x14, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
...
};
unsigned int model_pika_bulba20210108_190240_tflite_len = 984;**
Arduino 的分类器草图
现在是我们期待已久的教程部分:编写 Arduino 的分类器草图。作为数据收集草图,我使用了一个官方示例作为基础,并根据我的情况进行了修改。为了更好地解释它,我将把代码分成三部分:includes 语句和变量初始化,以及 setup 和 loop 函数。
在 Arduino 的 IDE 中,进入文件->新建开始一个新的草图。创建一个新文件,将其命名为 classifier.ino ,并添加:
**/*
Object classifier by color
--------------------------Uses RGB color sensor input to Neural Network to classify objects
Outputs object class to serial using unicode emojisNote: The direct use of C/C++ pointers, namespaces, and dynamic memory is generally
discouraged in Arduino examples, and in the future the TensorFlowLite library
might change to make the sketch simpler.Hardware: Arduino Nano 33 BLE Sense board.Created by Don Coleman, Sandeep Mistry
Adapted by Dominic PajakThis example code is in the public domain.Example inspired by
[https://create.arduino.cc/editor/TensorFlowExamples/8508c70f-5155-4e3b-b982-c5f6bd36ea5c/preview](https://create.arduino.cc/editor/TensorFlowExamples/8508c70f-5155-4e3b-b982-c5f6bd36ea5c/preview).
I've modified it to suit my use case.
*/#include <TensorFlowLite.h>#include "tensorflow/lite/micro/all_ops_resolver.h"
#include "tensorflow/lite/micro/micro_error_reporter.h"
#include "tensorflow/lite/micro/micro_interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
#include "Arduino_APDS9960.h"
#include "model.h"// Set up logging
tflite::MicroErrorReporter tflErrorReporter;// Add all the TensorFlow Lite Micro operations
tflite::AllOpsResolver tflOpsResolver;const tflite::Model *tflModel = nullptr;
tflite::MicroInterpreter *tflInterpreter = nullptr;
TfLiteTensor *tflInputTensor = nullptr;
TfLiteTensor *tflOutputTensor = nullptr;// Create a static memory buffer for TFLM, the size may need to
// be adjusted based on the model you are using.
constexpr int tensorArenaSize = 8 * 1024;
byte tensorArena[tensorArenaSize];**
这段代码包含加载库和初始化几个变量的 include 语句。第一个是[tflErrorReporter](https://www.tensorflow.org/lite/microcontrollers/get_started#4_set_up_logging)
,它是一个日志记录器,传递给解释器来写日志。接下来是tflOpsResolver
,一个[AllOpsResolver](https://www.tensorflow.org/lite/microcontrollers/get_started#6_instantiate_operations_resolver)
的实例,一个加载所有 TensorFlow Lite 微库操作的类。然后我们有模型、解释器(负责预测)、保存模型输入和输出的张量,以及用于输入、输出和中间传感器的内存分配。
在继续之前,创建一个名为 model.h 的新文件,并复制/粘贴我们在上一节中创建的编码模型。
接下来是setup()
函数(将这部分代码添加到byte tensorArena[tensorArenaSize]
行之后)。
**void setup()
{
Serial.begin(9600);
while (!Serial)
{
};Serial.println("Pokemon classification using RGB color sensor");
Serial.println("--------------------------------------------");
Serial.println("Arduino Nano 33 BLE Sense running TensorFlow Lite Micro");
Serial.println("");if (!APDS.begin())
{
Serial.println("Error initializing APDS9960 sensor.");
}// Initialize the led's.
pinMode(LEDR, OUTPUT);
pinMode(LEDG, OUTPUT);// Ensure it is off by default
// On the Arduino NANO 33 BLE Sense, HIGH is off.
digitalWrite(LEDR, HIGH);
digitalWrite(LEDG, HIGH);// get the TFL representation of the model byte array
tflModel = tflite::GetModel(model);
if (tflModel->version() != TFLITE_SCHEMA_VERSION)
{
Serial.println("Model schema mismatch!");
while (1)
;
}// Create an interpreter to run the model
tflInterpreter = new tflite::MicroInterpreter(tflModel, tflOpsResolver, tensorArena, tensorArenaSize, &tflErrorReporter);// Allocate memory for the model's input and output tensors
tflInterpreter->AllocateTensors();// Get pointers for the model's input and output tensors
tflInputTensor = tflInterpreter->input(0);
tflOutputTensor = tflInterpreter->output(0);
}**
它首先设置串行,打印一些行,然后等待传感器准备就绪。接下来,它初始化红色和绿色 led 并关闭它们。然后,它加载模型,初始化解释器(注意参数是前面声明的变量),分配内存,并获得指向模型输入和输出的指针。现在来了loop()
函数(写在setup()
之后):
**void loop()
{
// a is the ambient light intensity.
int r, g, b, a, p;
float sum;// Check if both color and proximity data sample is available.
while (!APDS.colorAvailable() || !APDS.proximityAvailable())
{
}// Read the color and proximity sensor.
APDS.readColor(r, g, b, a);
p = APDS.readProximity();
sum = r + g + b;// Check if there's an object close and well illuminated enough.
if (p == 0 && a > 10 && sum > 0)
{// Normalize the values.
float redRatio = r / sum;
float greenRatio = g / sum;
float blueRatio = b / sum;// Input sensor data to the loaded model.
tflInputTensor->data.f[0] = redRatio;
tflInputTensor->data.f[1] = greenRatio;
tflInputTensor->data.f[2] = blueRatio;// Invoke the inference.
// This is a great guide explaining the process
// [https://www.tensorflow.org/lite/guide/inference](https://www.tensorflow.org/lite/guide/inference)
TfLiteStatus invokeStatus = tflInterpreter->Invoke();
if (invokeStatus != kTfLiteOk)
{
Serial.println("Invoke failed!");
while (1)
;
return;
}// 0.50 is my threshold
if (tflOutputTensor->data.f[0] < 0.50)
{
Serial.print("Pikachu: ");
// Turn on the red and green LEDs to get yellow.
digitalWrite(LEDR, LOW);
digitalWrite(LEDG, LOW);
}
else
{
Serial.print("Bulbasaur: ");
// Turn on the green LED.
digitalWrite(LEDG, LOW);
}Serial.print(float(tflOutputTensor->data.f[0]), 5);
Serial.println();// Wait until the sensor does not detect the object.
while (!APDS.proximityAvailable() || (APDS.readProximity() == 0))
{
}// Turn off the LEDs.
digitalWrite(LEDR, HIGH);
digitalWrite(LEDG, HIGH);
}
}**
loop()
函数的第一行与颜色取样器草图中的相同;它声明颜色、光强度、接近度和总和变量,并检查是否有可用的颜色和接近度数据。如果是这样,我们读取颜色值,并检查对象是否在附近,场景是否光照充足。在if
中,我们将这些值标准化,并将它们作为模型在tfInputTensor
变量中的输入。然后,我们使用解释器调用推理(相当于 Python 的 TensorFlow 的 predict)。与其他 ML 库不同,调用解释器不会返回预测向量;相反,它返回预测的状态(成功还是失败)。**
为了得到预测值,我们必须使用 setup 函数中声明的输出张量(解释器通过引用将预测传递给这个张量)。在读取预测时,我们检查该值是否低于 0.50 阈值。如果为真,那么预测值就是皮卡丘,所以我们打印“皮卡丘”并打开红色和绿色 LED——红色+绿色=黄色,Arduino 只有一个红色、绿色和蓝色 LED。否则,我们打印“妙蛙种子”并打开绿色 LED。最后,我们打印预测,等到传感器检测不到物体,并重置 led。**
就是这样!快乐推断:)
以下是一些例子(注意 Arduino 的 LED 颜色):
图 12:检测到皮卡丘!
图 13:检测到白头翁!
总结和结论
TinyML 是机器学习领域,涉及适用于低功耗和嵌入式设备的模型。在本文中,我展示了一个如何使用其中一个的示例,一个 Arduino Nano 33 BLE Sense,以及用于微控制器的 TensorFlow Lite 库,来创建一个将我的口袋妖怪 plushies 分类为皮卡丘或妙蛙种子的程序。在本教程中,我解释了如何使用 Arduino 收集数据,展示了探索数据集颜色的数据分析,在 TensorFlow 中训练了一个逻辑回归模型,将所述模型导出到 TensorFlow Lite,并编写了一个草图,将模型加载到 Arduino,以便我们可以根据它进行预测。
有关 Arduino 和 tensor flow Lite for micro controllers 库的更多信息,请访问以下链接:
- 在 Arduino 上开始机器学习
- 皮特·沃顿和丹尼尔·斯图纳亚克的书
- 面向微控制器的 TensorFlow Lite】
- 微控制器入门
- TensorFlow Lite 推断
您可以在以下位置找到完整的代码(包括我的数据集和模型):
****https://github.com/juandes/pokemon-classifier-tinyml
感谢阅读!****
检测 GitHub 中潜在的不良行为者
行业笔记
使用无监督机器学习在开源软件作者中发现异常行为
来源: Unsplash
庞大的开源软件生态系统包含数百万个软件包和数千万贡献作者。这既是开源软件的优势,也是它的弱点:它的众包性质意味着软件包会不断更新和创新(免费!),同时使它们容易被某些人插入一些有害的代码。因此,为了真正检测开源软件生态系统中的风险,我们应该像关心代码本身一样关心它的社会组成部分——成千上万软件作者的行为和互动。
作为网络安全初创公司 Phylum 的一名数据科学家,我正在接受这个挑战,在原本良性的干草堆中寻找众所周知的恶意针头:我们能否使用机器学习来检测开源软件作者的异常行为?Phylum 的使命是保护代码世界,从开源软件供应链安全开始,通过构建应用机器学习、深度分析和静态代码分析的技术来保护系统免受远远超过已知软件漏洞的影响,如软件作者的潜在社会风险。这就是我来的原因:我在这里探索大量的数据,发现风险的模式,并建立可以自动检测大规模风险的模型。
那么,我们为什么要关心代码作者的行为,例如,谁在什么时候贡献了代码?更具体地说,当某人的行为显得“不寻常”时,我们为什么要在意呢?嗯,不良行为者可能会损害正常软件作者的 GitHub 或 GitLab 帐户,将恶意代码插入到软件包中。通过这种方式,他们可以在一个正直的软件公民借来的面具下,迅速将有害代码匿名混入包中,降低被发现的几率。这个问题进一步延伸到包维护者的安全模型,因为他们是供应链攻击的重要目标。
在本帖中,我们概述了通过分析对版本控制系统(如 GitHub 或 GitLab)的提交来标记潜在异常和/或恶意行为的能力。
使用数据来理解作者的行为
GitHub 数据允许我们了解作者的代码贡献活动。例如,在图 1 中,我们可以看到这位作者有一个非常规律的行为模式:他们倾向于在每天 07:00 或 08:00 UTC 推送代码,这种行为模式随着时间的推移保持稳定,除了 2017 年末和 2018 年初的一些提交。
像这样的见解是建立对作者“常规”行为的基线理解的关键。尽管 GitHub 提供了 UTC 时区的所有提交时间戳,并且作者缺乏具体的地理位置数据,但我们可以使用提交行为数据来了解作者的正常工作日。以我为例:由于我在西海岸工作,我的大部分工作提交活动可能发生在世界协调时 15:00-24:00(当地时间上午 8 点到下午 5 点),任何课外项目提交都发生在晚上,比如世界协调时 00:00-05:00(当地时间下午 5 点到 10 点)。因此,我们可以根据我写代码的时间推断出我的活动时间——甚至可能是我的时区。
通过了解什么是正常行为,我们可以更好地标记不寻常的提交,并可能是受损帐户的工作。例如,如果我的帐户在太平洋标准时间凌晨 3 点提交了一个我以前从未参与过的新项目,它应该会抛出一个大红旗!类似地,仅仅观察下图中作者的行为,我们可能会本能地质疑在通常的 07:00/08:00 窗口之外的几个提交。
当然,单独的异常活动可能不会带来安全风险。(或许,我是在异地度假的时候推代码的吧!)但是,如果我们能够将这种行为分析的证据与 Phylum 的其他分析结合起来,我们就可以更有把握地标记潜在的安全问题。例如,如果我们发现一个有很多突发异常行为的作者也推送了带有潜在恶意元素的代码,我们就更确定这是一个不良行为者的作品,而不是单独使用任何一条信息。
图 1——我们可以从一个作者的 Github 数据中提取的行为数据的例子。每个面板比较 Github 提交数据的不同维度。显示的每个点都是作者对项目的提交。
教机器发现不寻常的作者行为
虽然我们当然可以手动检查每个作者的行为,手动标记可疑的提交,但我们在 Phylum 致力于使用自动和严格的方法来发现安全问题。所以取而代之,我们用机器学习!这允许我们扫描数百万作者的行为,并自动标记可疑的提交行为。
为了找到不寻常的作者行为,我们使用DBS can——一种经典的无监督学习方法——将数据聚类为“正常”和“不寻常”的作者行为。如图 2 所示,这种方法使用数据点的密度来确定聚类的位置:彼此相距一定距离的点被认为是邻居,并且是同一聚类的一部分;没有任何邻居的点被视为异常值。(顺便说一句,作为一名以社交网络为中心的博士,我非常喜欢这种聚类算法,因为它本质上构建了一个数据点网络,在我们的数据人群中找到派系和孤独者。)
与使用的其他聚类方法相比,DBSCAN 在发现数据中的异常值方面特别有优势。首先,它需要提前设置更少的参数。其他聚类方法(例如 K-means)要求我们提前设置聚类数,这一选择会极大地影响模型的性能。相反,对于 DBSCAN,我们只需要告诉算法数据点需要与标记的“邻居”有多近,以及需要多少个邻居才能被认为是聚类的一部分。(我们也可以使用尖端方法自动设置这些参数。)其次,DBSCAN 自然地将我们的孤立数据点标记为离群值,因为它们没有被分配到一个集群。其他算法需要在聚类后进一步按摩以检测异常值:例如,K-means 会首先将所有点分配给一个聚类,然后让我们计算每个点与其分配的聚类中心的距离,并确定将每个点分类为异常值的阈值距离。
图 2 —机器学习聚类算法 DBSCAN 的可视化解释。彼此在一定距离内的点被认为是邻居。具有足够多邻居的点被认为是一个聚类的核心。几乎没有邻居的点标记了一个聚类的边缘。没有邻居的点是异常值。(图改编自 DBSCAN 上的维基百科词条。)
自动检测异常行为
回到我们之前的例子作者:这种无监督的机器学习方法可以发现不寻常的行为吗?
我们为机器学习清理和准备我们的数据,缩放数据并将某些数据点(例如,一周中的某一天)转化为模型可以理解的定量测量。我们还使用一种允许我们使用定量(例如,一天中的时间、一周中的日期)和定性(例如,GitHub 项目 ID)数据的方法,为我们的模型提供更多可供学习的信息!
幸运的是,我们可以使用提交的确切日期(和时间)来为每个作者创建一种“滚动”的正常感觉。我们通常希望将行为作为一个时间序列来分析:人们可能会因为合理的原因而移动位置(以及时区)并改变他们的行为。因此,在我们的模型中使用提交日期/时间可以确保我们将同一时间段的数据相互比较,从而允许模型在识别行为的突然变化的同时考虑行为的逐渐、自然的变化。
在我们的示例作者上运行我们的模型,我们看到它成功地挑选出异常行为!请看图 3,我们看到模型标记了那些不符合通常的 07:00/08:00 模式的提交。然而,我们看到该模型还发现了一些其他不寻常的提交,主要是在周末。
因此,我们看到这个模型已经超越了仅仅观察数据和发现行为的能力,否则很难发现这些行为。
图 3 — ( 左)显示了作者在一周中的每一天和一天中的每一个时间的提交,模型认为不寻常的提交用橙色标记。(右图)使用 PCA 将作者的提交数据(具有许多特征)投影到二维空间中,使我们可以轻松地显示数据簇(蓝色阴影)和异常值(橙色)。
大规模检测高风险作者
该模型已经显示了它在学习一个作者的行为方面的用途,但是如果我们同时在数千或数百万作者中运行它会怎么样呢?Phylum 构建的平台支持这种类型的分析,以及更多。我们使用这篇文章中概述的工作为每个作者创建 ML 模型,让我们能够了解他们特定的行为模式。现代分布式系统真的令人难以置信!
Phylum 正在不断改进我们的作者风险分析,以允许用户管理使用互联网上随机陌生人编写的代码所带来的风险。这里记录的工作提供了有价值的证据,作为对 Phylum 的作者风险评分的输入。这与其他分析、启发和模型相结合,以持续保护 Phylum 的用户。
为了创建作者风险的证据,我们采用我们的 ML 模型的发现,并使用一个简单的逻辑:一个有风险的作者有相对高的提交数量和相对高的异常行为百分比。为什么?嗯,一个很少提交的作者为机器学习模型提供了很少的证据——毕竟,很难从几十个提交中建立一个行为模式。另一方面,一个提交数千次的作者会创建一个清晰的行为模式。因此,具有强烈行为模式和高异常提交率的用户给了我们很大的信心,认为他们的帐户有异常。
使用这种逻辑,我们通过将作者在(a)提交数量和(b)被我们的 ML 模型标记为异常的提交的百分比中的相对排名相乘来构建我们的证据。具有高证据分数的作者在(a)和(b)中都会很高。
如图 4 所示,我们的方法将一些突出的作者标记为高风险。这些作者通常会提交成百上千个提交,但也有大量不符合他们通常行为模式的不规则提交。例如,一些用户可能对许多不同的项目进行大量的一次性提交,这种模式并不能完全建立信心。
结合其他证据,这些证据为作者风险评分提供了输入,有助于 Phylum 用户避免使用受到可疑和/或恶意作者显著影响的包或版本。
图 4 —我们的作者风险评分结合了提交的相对数量和 ML 模型认为不寻常的提交的相对百分比。最后,一些作者因为潜在的风险而脱颖而出。
走向
我们将继续迭代和改进这个模型,首先也是最重要的,通过继续寻找新的数据并将其集成到我们的模型中。例如,我们能解释项目/提交的语言来发现不寻常的行为吗?我通常是一个 Python 和 R 的家伙,所以如果你突然看到我提交一个大的 C++ commit,我希望你能仔细看看!
我们正在努力将这个 ML 模型与我们的密码泄露数据集结合起来,使我们能够询问用户的行为是否在他们的一个在线帐户被泄露后不久突然变得异常。
总的来说,在 Phylum 平台中,我们整合了像这样的严格分析的输出,以创建可以组合的证据,来充分捕捉开源软件生态系统中的风险程度。请留意更多详细介绍我作为数据科学家的工作的帖子!
利用深度学习检测阿尔茨海默病的前兆
使用 3D CNNs 帮助研究人员寻找治疗老年痴呆症的方法
痴呆症
我们都会忘记一些事情。在某个时候,我们走进一个房间,却忘了我们为什么去那里。对大多数人来说,这种情况不会每天都发生,但对有些人来说却是这样。对于大约 4400 万人来说,忘记了他们在哪里,他们和谁在一起,他们刚刚说的是生活的事实。阿尔茨海默氏症是一种进行性疾病,是脑细胞死亡的结果。症状包括认知思维减少、记忆力减退、睡眠障碍和言语丧失。十分之一的人一生中会得老年痴呆症,但更糟糕的是,这种疾病无法治愈。目前可用的治疗只能温和地减轻症状,这对我来说不是很好。
为什么我们没有解药?这是因为科学家们仍在试图找出到底是什么原因造成的。我决定进行一次小小的研究冒险,以了解更多关于老年痴呆症的病因。我偶然发现了一篇论文,该论文指出老年痴呆症可能与大脑缺乏血液流动有关。阿尔茨海默氏症患者一直生活在一种类似的感觉中,当你站起来太快时,会感到头晕。 太可怕了 。有趣的是,这种与阿尔茨海默氏症的联系多年前就已为人所知。
那么,为什么科学家没有针对血液流动呢?嗯,这是因为科学家们对血流不足如何影响记忆没有准确的理解。一些研究认为,这可能是由于白细胞粘附在毛细血管上,从而限制了血液流动。我深入兔子洞,发现了一个名为 StallCatchers 的组织,该组织收集老鼠大脑中血液流动的视频,并将它们分为停滞或流动。他们致力于对数千个视频进行分类,这些视频被发送给科学家,科学家使用分类视频来了解更多关于停滞的信息,并试图找出治疗疾病的方法。这些视频来自康乃尔大学的研究人员,他们使用双光子激发显微镜拍摄小鼠活体脑组织的视频。作为一名人工智能爱好者,我认为必须有一种更好的方法来使用人工智能对这些视频进行分类。令我惊讶的是,我发现 Mathworks,一家对数据科学很感兴趣的公司,正在举办一场开发视频分类器的比赛,以帮助捕捉摊位的人。很自然,我报名了,然后我们就在这里了。
模型概述和方法
方法 1
我最初的想法是使用一个卷积神经网络(CNN) 对每张图像进行分类,然后取视频中所有帧的平均值,输出基于平均值的类。在完成这个项目后,我对我得到的测试准确度并不完全满意(~50%)。你可以在这里查看我的文章,它解释了我的第一个思维过程和模型:[使用人工智能对阿尔茨海默氏症的视频进行分类(第一次尝试)](http://Using AI to Classify Videos for Alzheimer’s(First Attempt))
方法 2
我的下一个想法是用一个 CNN+LSTM (长短期记忆)对整个视频进行分类。我使用了一个时间分布的 CNN,它允许将另一个维度(帧)添加到数据中(-1,48,48,1) 🡪 (-1,26,48,48,1)。然后有一个 LSTM 把 CNN 的输出作为输入。在我的第一种方法中,它只是帧分类,而 CNN+LSTM 将它变成了视频分类,因为我正在对由 26 帧组成的视频进行分类。添加的 LSTM 用于分析时间序列数据(每个视频中的每个帧如何随时间变化)。然而,我在这里的准确率也很低(52%),并且模型不能有效地训练。
方法 3(最终方法)
由于在 CNN 和 CNN+LSTM 模型中缺乏成功,我最终创建了第三个模型:一个 3D 卷积神经网络(3DCNN) 。3DCNN 与典型的 2D CNN 的不同之处在于,它多了一个维度作为输入。在我的场景中,这个维度是每个视频中的 26 帧。这允许该模型对全部视频进行分类。
在我进入代码之前,这里有一个非常简短的 3DCNNs 概述:
2DCNN:
为了理解 3DCNN,从什么是 2DCNN 开始更容易。
CNN 结构的基本概述。信用: Aphex34 ,via wiki (CC BY-SA 4.0)
2D 卷积神经网络通常用于分类图像。计算机以数组的形式读取图像,CNN 通过多层传递这个数组。
这些层包括:卷积层、汇聚层和全连接层。
卷积层和汇集层具有不同大小的内核,这些内核通过图像阵列,将单个新值输出到较小的矩阵中。卷积层将权重和偏差应用于像素值,而池层可以例如采用内核范围中的最高值。可以把这些图层想象成一个手电筒,从图像上掠过,输出最重要(或最亮)的值。
完全连接的层类似于神经网络,因为它们具有应用权重和偏差的节点,最终给出输出类别预测。
*注:如果你想更深入地了解一个 2DCNN 作品是如何运作的,可以看看我上一篇文章的开头: 用 AI 实时翻译手语 ***
3DCNN:
好了,现在我们知道了什么是 2D CNN,让我们深入了解什么是 3D CNN。
如前所述,3DCNN 与 2DCNN 非常相似,只是增加了一个维度。这个增加的维度导致每一层都略有变化。
卷积层/池:2D CNN 中的卷积和池层由经过图像的 2D 核组成。这意味着如果内核是(2,2)内核看起来会像这样:
信用:自我
现在有了 3D 卷积层或池层,就有了一个 3D 内核来传递视频。这意味着(2,2)内核可以变成(2,2,2)内核,如下所示:
信用:自我
每个框架都堆叠在彼此之上,允许内核同时在两个框架上滑动。这使得模型也可以考虑时态数据。这种情况下的时间数据是每帧图像的变化。
对于卷积层,内核对每个像素应用数学运算,对于池层(最大池),内核取最高(最亮)的像素值。
然后,这些层的输出进入密集层或典型的神经网络,用于对提取的特征进行分类。
现在我们知道了 3DCNN 是如何工作的,让我们继续数据预处理!
数据预处理
我会说这个项目最乏味的部分是数据组织,它涉及帧提取、裁剪和颜色去除,以确保模型只在感兴趣的区域进行训练。
每帧数据准备的最终目标
帧提取和限制
第一个目标是从每个视频中提取帧,因为模型的输入是一系列“堆叠”的帧。然而,我们需要为每个视频提取相同数量的帧,因为一些视频有 30 帧,而其他的有 60 帧。需要一个标准的帧计数,因为稍后当我们为训练而调整数据时,各个帧将被分组在一起以形成它们的原始视频,如果帧计数有任何变化,将会导致帧混淆。我把帧数定为 26(最短视频的帧数)。然而,由于许多视频有 50+帧,我发现拍摄前 26 帧有时会错过视频中最重要的部分。为了最大限度地提取每个视频中最重要的部分,我决定选取中间的帧。
信用:自我
您可以计算它,并通过如下方式设置开始帧:
framecount = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))dist = framecount-26
startframe = dist/2cap.set(cv2.CAP_PROP_POS_FRAMES, startframe)
首先,它获取帧数,然后从中减去 26,再除以 2,并将该数字(在 50 帧的示例中为 12)设置为开始帧。
从第一帧开始(左)与从中间帧开始(右)
ROI 裁剪
下一个目标是将每一帧裁剪到感兴趣的区域。为了获得最佳效果,图像应该代表图像中最重要的部分,或者模型可以识别的两个类别的不同之处。在这些图像中,可以看到显示感兴趣区域(ROI)的红色圆圈。最合理的做法是只关注图片的这一部分,而忽略其他部分。这也使得模型更容易分类,因为它只处理重要的区域。还必须添加填充,以便每个裁剪后的图像大小完全相同。
这两个步骤看起来像这样:
th = cv2.inRange(img, (7, 13, 104), (98, 143, 255))
points = np.where(th>0)
p2 = zip(points[0], points[1])
p2 = [p for p in p2]
rect = cv2.boundingRect(np.float32(p2))
cv2.rectangle(img, (rect[1], rect[0]), (rect[1]+rect[3], rect[0]+rect[2]), 0)height = (rect[0]+rect[2]) - rect[0]
width = (rect[1]+rect[3]) - rect[1]
y = rect[0]
x = rect[1]
roi = img[y:y+height, x:x+width]desired_size = 48
im = roi
old_size = im.shape[:2]
ratio = float(desired_size)/max(old_size)
new_size = tuple([int(x*ratio) for x in old_size])
im = cv2.resize(im, (new_size[1], new_size[0]))
delta_w = desired_size - new_size[1]
delta_h = desired_size - new_size[0]
top, bottom = delta_h//2, delta_h-(delta_h//2)
left, right = delta_w//2, delta_w-(delta_w//2)
color = [0, 0, 0]
new_im = cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)
首先,我使用阈值处理(一种限制图片中出现的颜色的技术)来识别红色圆圈的位置,并在它周围画一个矩形。然后我压缩了圆圈周围的点,并画了一个矩形来连接这些点。
接下来,我把画出的矩形裁剪成矩形。这很简单,因为这些点已经在前面的矩形图中给出了。这会在红色圆圈(ROI)周围输出一个裁剪后的图像。
最后,为了得到大小为(48,48)的图片,我在需要的地方添加了填充。
脱色
下一个目标是移除图像中的红色圆圈。在这个项目的第一次和第二次迭代中,我犯了在前一步停止数据预处理的错误。然而,我意识到,通过在每一帧上留下红色圆圈,模型有很大的机会分类错误。当帧通过卷积层和池层时,模型可能会丢失血流(白线)并只对红圈进行分类。然后,该模型将尝试对红圈的形状、大小等进行分类。并最终得到较差的精度。这可能会发生,尤其是在池层,因为最大池采取最亮/最高对比度的像素,在一些帧可能会结束为圆。因此,我选择用黑色(0,0,0)替换所有红色像素,这将允许模型对血流进行分类。
下面的代码是我选择这样做的:
image = Image.open('*filepath*'+str(i)+'.jpg')
image_data = image.load()
height,width = image.sizefor loop1 in range(height):
for loop2 in range(width):
r,g,b = image_data[loop1,loop2]
if(r > -1 and r < 256 and g > -1 and g < 160 and b > -1 and b<100):
image_data[loop1,loop2] = 0,0,0image.save('*filepath*'+str(i)+'.jpg')
这里它打开图像,扫描图像中的每个像素。如果每个像素的 rgb 值都在一个范围内(红色圆圈的范围),它会用黑色替换该像素。if 语句中的范围还考虑了图像中可以看到的一些白噪声。最终的输出是一个没有红色圆圈的图像。
把所有的放在一起
现在我们将这三个部分结合在一起,使处理更加有效。
现在该训练了!
模型
我用的 3DCNN 模型如下:
卷积层
模型的第一部分是三个卷积层。每层有 4 个部分: conv3D 、激活、汇集、批量正常化和退出。
信用:自我
Conv3D:
第一层是卷积层。这是 3D 内核滑过图像堆栈的层。内核就像一个手电筒,它观察一个特定的区域,对内核范围内的每个像素进行数学运算,并将新值输出到一个更小的矩阵中。这个较小的矩阵有助于降低模型的复杂性,并突出显示图像中最重要的部分。在这个特定模型中,第一个模型中有 8 个内核,第二个和第三个模型中有 16 个内核。每个内核的过滤器大小为(3,3,3)。我选择使用少量的内核来减少数据集过度拟合的机会。此外,我选择只有 3 个卷积层,因为我认为没有必要增加更多的层。添加更多的层会进一步缩小图像的重要部分,我觉得没有必要,特别是因为输入图像已经非常清晰。
激活:
使用的激活函数是漏 ReLU 函数,与 ReLU 略有不同。使用泄漏 ReLU 的好处是它可以防止“死亡”的神经元。偶尔,在训练过程中,神经元可以输出小于 0 的值。如果使用 ReLU 激活功能,神经元可能会变得不活跃,并且不能恢复活跃。然而,如果使用泄漏的 ReLU 函数,它将最终允许神经元恢复活动。
信用:自我
其原因是漏 ReLU 允许稍微负数,而 ReLU 在 0 停止。
统筹:
池层通过减小矩阵大小进一步降低了模型的复杂性。池层类似于卷积层,因为它也有一个在图像堆栈上滑动的 3D 内核。在这种情况下,使用了 max pooling,它取内核范围内的最高像素值。Max pooling 识别最亮和具有最大对比度的像素(例如边缘),这对于识别血液如何移动很重要。过滤器大小设置为(2,2,2)。
批量归一化:
批量标准化减少了隐藏层值的移动。这使得训练更容易,因为它稳定了重量,提高了准确性。它还会重新调整数据的中心和比例,从而减少下一层输入数据的变化。这提高了整体训练速度和准确性。
辍学:
丢弃层从模型中丢弃随机节点。这使得模型更加通用,因为它不太依赖任何节点。这种泛化有助于减少数据的过度拟合,从而允许模型对其他数据集做出准确的预测。
完全连接的层
信用:自我
模型的第二部分由致密层组成。在这些密集层旁边,还有激活、批量正常化和退出。
密集:
密集层类似于典型的神经网络,因为它有几个节点,这些节点将数学运算应用于输入数据。神经元相互连接,相互传递数据。在这种情况下,有 16 和 32 个神经元。
激活,批量正常化,退出:
所有层都与前面提到的类似。
思维过程
你可以注意到,我选择了相对较小的内核数和密集层数。这样做的原因是,如果我添加更多的参数(更密集和内核),模型往往会过度拟合。最可能的原因是,我正在训练的数据集有 550 个视频,这是一个相对较小的数据集。因此,由于过度拟合,我减少了参数的数量,从而简化了网络。
结果
培养
经过 140 个历元的训练,模型达到了 80%的训练和验证准确率,相当不错!
但是,您可以注意到,在大约第 130 个历元之后,精确度降低,损耗增加。这促使我利用 Keras 中的早期停止来阻止模型在损失开始增加时进一步训练。这使得模型能够获得最高的精度。我使用的代码如下:
es_callback = keras.callbacks.EarlyStopping(monitor='val_loss', patience=15)history = model.fit(xtrain, ytrain, batch_size=10, epochs=250, verbose=1, validation_split=0.3, callbacks = [es_callback])
测试
在测试数据集上评估该模型时,准确率达到了 69%。这比我以前的模型 52%的测试精度有了很大的提高。
学习和后续步骤
我从这个项目中学到了很多,但我最大的收获是数据为王。没有高质量的数据,即使你有一个强大的模型,你的准确性也可能很差。通常获得高质量的数据是项目中最困难的部分。
仍然有方法来改进我的模型,更具体地说是测试精度。过度拟合是一个主要问题,它阻碍了这个项目的准确性,所以应用不同的方法,如 L1,L2 正则化,可能有助于改善我目前所做的。
感谢阅读,我希望你有一个伟大的一天!
联系我:
领英:https://www.linkedin.com/in/vikram-menon-986a67193
电子邮件:vikrammenon03@gmail.com
S 特别感谢劳拉·k·纳尔逊博士对这个模型的提示和建议
在胸部 x 光检查中检测肺部异常
用于图像识别的卷积神经网络
应用数据科学和人工智能已经并将继续为许多不同的领域和行业做出贡献,其中一个例子是医疗保健和医学研究。
如今,能够进行 X 射线扫描的技术很容易以相对较低的成本获得。然而,从所述扫描中做出诊断所需的合格的放射学专家并不多,尤其是在发展中国家。
在这一领域实施人工智能解决方案可能会推动发展向前迈出关键的一步。
这个项目背后的动机是探索建立卷积神经网络(CNN)模型的可能性,以检测胸部 X 射线扫描图像中的肺部异常,希望使诊断过程更快,更便宜,减少对人类专家干预的依赖。
完成项目的阶段如下:
- 探索和预处理训练数据
- 构建、评估和调整 CNN 模型
- 讨论该方法的成功之处、局限性和潜在的未来改进
第一部分:训练数据的预处理和探索性分析
本项目的训练数据集将包括 800 幅胸部 x 光图像:其中 662 幅由中国深圳市第三人民医院提供,其余 138 幅来自美国蒙哥马利县卫生与公众服务部。
数据集中的每幅图像都已经过放射科医师的检查,并且提供了一个标签来指示患者是否表现出由结核病表现引起的肺部异常。
该数据集最初由美国国家医学图书馆发布,旨在为计算机辅助肺部疾病诊断领域的研究提供足够的公共训练数据。本项目中使用的数据由用户 K Scott Mader 通过 Kaggle 获得。所有相关的引用都可以在本文末尾的参考文献部分找到。
图 1:训练图像的两个例子,一个显示异常(左),一个不显示异常(右)
编码图像
在图像数据可以被分析或传递到机器学习模型之前,它必须首先被转换成某种数字格式。为此,我们可以使用 OpenCV Python 包对图像进行编码。
这个包的imread
方法将为每幅图像创建一个 numPy 数组,数组中的每一项代表一个像素的编码灰度,有三个单独的层用于蓝色、绿色和红色。
每个阵列的尺寸将等于图像的高度(像素)、宽度(像素)和颜色层的数量(三个)。
虽然 X 射线图像不包含从白色到黑色范围以外的颜色,但我们稍后将用于构建 CNN 模型的包( Keras )要求每个输入变量都是 3D 数组,因此我们无法读取 2D 灰度的图像。
正在检索目标标签
训练数据来源中的描述解释了每个图像的基本事实标签(由医学专家确定)作为后缀存储在其文件名中:用 1 表示扫描显示异常,用 0 表示不显示异常。
为了检索目标标签,我们可以简单地在 filetype 前面添加最后一个字符()。png )到一个列表中,这个列表将作为目标变量。
我们可以使用如下定义的嵌套 for 循环为两个目录中的每个目录中的每个图像创建编码特征变量的数组和目标标签的列表:
def encode_images():
X = []
y = [] directories = ['xray_images/ChinaSet_AllFiles/ChinaSet_AllFiles/CXR_png/',
'xray_images/Montgomery/MontgomerySet/CXR_png/']for directory in directories:
for filename in os.listdir(directory):
if filename.endswith('.png'):
X.append(cv2.imread(directory + filename))
y.append(int(filename[-5]))
return np.array(X), np.array(y)
评估和调整图像大小
CNN 模型要求每个输入条目的形状相同。在我们的数据的上下文中,这将对应于每个图像具有相同的大小,因此每个特征阵列具有相同的维度。
让我们通过在训练数据中绘制图像的高度和宽度分布来检查是否是这种情况:
图 2:图像尺寸的分布
我们可以看到图像大小存在差异。为了解决这个问题,我们可以重写我们的编码函数来实现 For 循环开始处的 Pillow 包中的resize
方法。
但是我们应该为输入图像选择多大的尺寸呢?
理论上,保持尽可能高的分辨率可以得到更精确的模型。我们可以从上面的分布中看到,这些图像的分辨率相当高,几乎所有图像的高度和宽度都至少有 1000 像素。
但是,我们也需要考虑计算速度。保持超过 1000 平方像素可能需要很长时间才能使模型适应训练数据,特别是因为我们将在本地运行代码。像往常一样,我们在准确性和速度之间有所取舍。
作为一个折衷点,并且记住选择 2 的指数的图像尺寸通常是一个好的实践,让我们将图像的大小调整为 256 像素的正方形:
img = Image.open(directory + filename)
img = img.resize((256, 256))
img.save(directory + filename)
标准化图像的比例
像素的灰度代码可以从 0(黑色)到 255(白色)。目前,我们的编码特征数组包含高达 255 的值。机器学习模型,特别是那些涉及梯度下降的模型,如神经网络,通常在标准化数据上表现更好。
在进入建模阶段之前,为了说明这一点,让我们将特征数组除以最大值(255)来归一化每个条目,使其范围从 0 到 1:
print((np.min(X), np.max(X)))
X = X.astype('float32') / 255
print((np.min(X), np.max(X)))
>>> (0, 255)
>>> (0.0, 1.0)
分析目标变量
在继续构建模型之前,获得目标变量的一些可见性也是一个好主意。
让我们来看看训练数据中正面分类和负面分类之间的差异:
图 3:目标变量分割
从上面我们可以看到,我们的训练数据在各个类之间是平衡的,超过 49%的图像显示异常。这应该作为以后评估模型准确性的基准。
现在,我们已经将训练数据预处理成适合 CNN 模型的格式,并通过一些探索性分析获得了对它的更好理解,我们可以进入 CNN 建模阶段。
第二部分:构建 CNN 模型
分割训练数据
为了便于在模型构建和训练后对其进行评估和调整,我们需要将数据分成训练集和测试集。
当使用任何形式的神经网络时,建议将数据分成三个独立的集合(训练、验证和测试),而不是传统的两个集合。
这是因为神经网络模型使用训练集作为输入来馈通模型,计算损失,并在每个时期调整权重和偏差,然后使用单独的验证集来确定新参数是否是对来自前一时期的参数的改进。
由于模型在训练期间已经“看到”了验证数据,使用相同的数据来评估最终模型在技术上可能会导致数据泄漏,因此需要在初始分割时创建第三个测试集。
我们可以通过使用 scikit-learn 的train_test_split
方法来执行这种分割,首先将数据分割成训练集和测试集,然后再将测试集分割成单独的验证集和测试集:
train_size = 0.6
val_size = 0.2
test_size = 0.2X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=(val_size + test_size), random_state=42)X_val, X_test, y_val, y_test = train_test_split(X_test, y_test, test_size=(test_size / (val_size + test_size)), random_state=42)print('Training: {}; Validation: {}; Testing: {}'.format((len(X_train), len(y_train)), (len(X_val), len(y_val)), (len(X_test), len(y_test))))
>>> Training: (480, 480); Validation: (160, 160); Testing: (160, 160)
编码目标变量
不管可能的类的数量是多少,Keras 中的任何神经网络模型都要求目标变量被分类(一次性)编码。在开始构建模型之前,我们需要对三个目标集进行分类转换:
y_train = keras.utils.to_categorical(y_train, len(set(y)))
y_val = keras.utils.to_categorical(y_val, len(set(y)))
y_test = keras.utils.to_categorical(y_test, len(set(y)))print(y_train.shape, y_val.shape, y_test.shape)
>>> (480, 2) (160, 2) (160, 2)
构建基线模型
在 Keras 中构建 CNN 模型的第一步是定义模型架构。与其他神经网络一样,该架构必须包括:
- 初始输入图层,我们在其中指定输入要素的形状
- 零个或多个“隐藏”层,试图揭示数据中的模式
- 最终输出层,它将根据输入对每个实例进行分类
基线模型架构的代码可以写成如下形式:
base_model = Sequential()
base_model.add(Conv2D(filters=16, kernel_size=2, padding='same', activation='relu', input_shape=X[0].shape))
base_model.add(MaxPooling2D(pool_size=2))
base_model.add(Flatten())
base_model.add(Dense(len(set(y)), activation='softmax'))
关于基线模型有几点需要注意:
- 我们需要指定想要使用的过滤器的数量以及输入层中方形内核的大小
- 我们还需要指定填充,以防滤镜溢出图像边缘,为此我们将使用
‘same’
- 我们将从 ReLu 激活函数开始,它保持正值不变,并将负值设置为 0。当我们调整模型时,我们可以测试不同的功能
- 由于我们调整了所有要素输入的大小,因此它们不会发生变化,我们可以使用第一个实例来指定输入形状
- 添加了最大池图层,通过从每个 2x2 方块中获取最大值来减少数据的维数
- 在到达输出层之前,我们需要将数据展平到一个 2D 数组中,以便它可以被馈送到一个完全连接的层中
- 在输出层中,我们需要指定模型应该预测多少个类(等于目标数组中唯一值的数量,在本例中为 2 ),并使用 Softmax 激活函数将输出归一化为概率分布
我们还需要编译模型,这包括定义我们将用来衡量其性能的度量标准。因为我们发现在研究数据时,目标变量在各个类之间是均衡的,所以纯粹的准确性应该是一个合适的度量标准:
base_model.compile(loss='categorical_crossentropy', optimizer='rmsprop', metrics=['accuracy'])
您可以看到,compile
方法也用于为模型选择合适的损失函数和优化指标,以便在训练时使用。
一旦我们定义并编译了模型的架构,我们就可以使它适合训练数据:
base_model.fit(X_train, y_train, epochs=10, batch_size=32, validation_data=(X_val, y_val), verbose=1, shuffle=True)
fit
方法中的参数本质上是告诉模型适应训练集,并使用验证集作为评估性能的看不见的数据。
批量大小用于将数据分成子集,以便一次一个地通过网络。这具有极大地减少完成每个训练步骤所需的计算能力的优点。
历元数告诉模型应该通过网络推送数据多少次,然后反向传播权重和偏差。我们将很快讨论要使用的最佳历元数,但首先我们将使用 10。
评估基线模型
一旦我们在指定数量的时期内拟合了模型,我们就可以在以前看不到的测试集上评估它的真实性能:
round(base_model.evaluate(X_test, y_test, verbose=0)[1], 4)
>>> 0.7812
我们可以看到基线模型可以准确地对 78%的测试样本进行分类。考虑到大约一半的数据集包含积极标记的实例,我们可以推断简单基线模型已经比我们随机猜测每个图像的类别表现得更好。
这提供了一个很好的起点,但是让我们深入研究一下,看看我们是否可以采取措施来提高模型的性能。
选择历元的数量
为了确定拟合模型时使用的最佳时段数,我们可以在fit
方法中使用不同的时段数从基础模型的多次测试运行中收集准确度分数:
epochs = [5, 10, 20, 50, 100, 200]
scores = []for e in epochs:
test_model = Sequential()
test_model.add(Conv2D(filters=16, kernel_size=2, padding='same', activation='relu', input_shape=X[0].shape))
test_model.add(MaxPooling2D(pool_size=2))
test_model.add(Flatten())
test_model.add(Dense(len(set(y)), activation='softmax'))
test_model.compile(loss='categorical_crossentropy', optimizer='rmsprop', metrics=['accuracy'])
test_model.fit(X_train, y_train, epochs=e, batch_size=32, validation_data=(X_val, y_val), verbose=False,
shuffle=True)
scores.append(test_model.evaluate(X_test, y_test, verbose=False)[1])
并随后绘制结果:
图 4:每个时期的准确度分数
从上面我们可以看到,使用 20 个历元而不是 5 个或 10 个显著提高了模型的性能。然而,随后时代数量的增加,即使有改进,也不会产生太大的改进。
虽然对神经网络模型运行更大数量的时期可以使其变得更加精细,但增加到某个级别以上最终会导致模型对训练数据过度拟合,从而损害测试集的性能。
考虑到性能和计算效率,我们在调整基线模型时使用 20 个时期。
实现图像增强
一种常用的使 CNN 模型更健壮并进而提高性能的方法是图像增强。这实质上是指在训练数据内生成新图像,这些新图像是现有图像的翻译,作为一种为杂乱的真实世界数据准备模型的手段,在真实世界数据中,要识别的对象可以以多个角度和大小出现在图像空间内的任何地方。
在 Keras 中执行图像增强相对简单。我们首先需要为每个训练集和验证集创建和调整图像生成器对象:
datagen_train = ImageDataGenerator(width_shift_range=0.1, height_shift_range=0.1, horizontal_flip=True)
datagen_val = ImageDataGenerator(width_shift_range=0.1, height_shift_range=0.1, horizontal_flip=True)datagen_train.fit(X_train)
datagen_val.fit(X_val)
然后可以将其传递给 CNN 的fit
方法。为了测试这种技术是否提高了模型的性能,让我们在相同的条件下重新创建基线模型,但是这一次,当我们将数据生成器对象与定型数据相匹配时,要传递数据生成器对象:
batch_size = 32aug_base_model.fit(datagen_train.flow(X_train, y_train, batch_size=batch_size),
steps_per_epoch=X_train.shape[0] / batch_size, epochs=20, verbose=1, callbacks=[checkpointer],
validation_data=datagen_val.flow(X_val, y_val, batch_size=batch_size),
validation_steps=X_val.shape[0] / batch_size)
请注意,我们需要在函数中指定每个时期要执行的步骤数。这可以被定义为在给定的批量中输入实例的数量。
我们可以使用与之前相同的方法来评估增强基线模型的性能:
round(aug_base_model.evaluate(X_test, y_test, verbose=0)[1]
>>> 0.6875
以上表明,使用图像增强实际上阻碍了我们的模型的性能,而不是提高它。
为了理解为什么会这样,让我们试着应用一些领域知识。虽然图像增强可以在典型的非结构化图像的环境中提高模型的鲁棒性,例如识别道路图片中的汽车,但同样的规则可能不适用于我们的数据环境。
x 射线扫描是在受控条件下进行的,因此我们可以预期在训练集和测试集中的图像之间存在结构一致性。因此,在训练中包括真实图像的翻译可能只会通过创建不现实的环境来混淆模型。
考虑到这一点,让我们决定在不使用图像增强的情况下进行调整。
调整模型
CNN 模型提供了无数的调谐和调整机会,以提高性能。因为这是一个据说应用领先于理论的领域,所以发现模型的最佳条件的最有效的方法就是简单地钻研和试验。
调谐 CNN 是一个反复尝试的过程。由于这个项目的代码可以在最后链接的资源库中找到,我不会在本文中包括所有的调优步骤,但下面是调优过程中调整和测试的各种条件的概述:
- 实现附加卷积层
- 在最终输出图层之前添加密集图层
- 实现丢弃以随机“关闭”每层中的一些节点
- 尝试不同的激活函数,如 Sigmoid 函数
- 在卷积层中使用更大的步幅
最终产品
经过多轮调整后,产生最高准确度分数(84%)的模型包括:
- 增加滤波器数量的三个卷积层
- 每层中的 ReLu 激活函数
- 每层之后的最大池层大小为 2
- 第三卷积层之后的概率为 0.3 的丢弃层
图 5:最终模型的架构
第三部分:探讨方法
成功
使用由专家放射科医生预先标记的 X 射线图像数据集,我们能够构建一个简单的 CNN 模型,以正确识别肺部异常 78% 的时间。
然后,我们能够通过实施额外的卷积层和 dropout 等功能来调整模型,以提高性能,实现最终的准确度分数 84% 。
限制
虽然最终的模型可能为进一步的研究提供有用的基础,但 84%的准确率可能被认为不足以应用于现实世界的医疗环境。在这种型号真正投入使用之前,需要达到更高的性能水平。
这种方法最明显的缺点是训练数据集的大小。在机器学习的背景下,800 的样本量被认为是非常小的,我们可以通过使用更大的训练集来提高模型的性能。
然而,这也会带来自身的问题。CNN 是计算复杂的算法,因此将训练数据的大小增加到超过某一点,最终在本地机器上训练将变得不切实际。
这种方法的另一个局限性是数据集只包含一种疾病的基本事实诊断:结核病。如果这种类型的模型被用于现实世界病例的诊断,它将需要能够检测由其他肺部疾病引起的异常。
解决这一问题将再次需要使用扩展的训练数据集,其中包括多种可能疾病的标签,从而将该方法转化为多标签分类问题,而不是二进制问题。
在未来版本中开发模型
如上所述,在未来版本中建立改进模型的第一步是收集更大和更多样的训练数据集。
为了克服在本地 CPU 上训练复杂 CNN 的不切实际性,使用基于云的、支持 GPU 的服务器(比如 AWS)的服务也是明智的。
另一个尝试改进模型的途径是使用迁移学习。这将涉及采用预训练的神经网络,并用针对该问题定制的完全连接的层来替换最终输出层。
这在小的训练数据集的环境中特别有用,因为它可以获得在大得多的数据集上训练的模型的好处,并且将相同的模式应用于所讨论的数据。
结束语
该项目的目的是探索构建 CNN 模型的可能性,以检测胸部 X 射线扫描图像中的肺部异常,作为通过应用人工智能辅助诊断的一种手段。
这是因为尽管有 x 光机,但缺乏专业的放射科医生,特别是在发展中国家。
从原始图像数据集到最终工作模型的过程包括:
- 调整图像数据的大小、编码和标准化
- 构建和评估基线 CNN 模型的架构,以此作为起点
- 试验和调整模型的许多条件,如历元数、卷积层数和 dropout 的使用,以提高性能
有兴趣更详细地研究代码(包括各种调优阶段)的读者可以在我的 Github 的资源库中找到它。随时欢迎关于改进模型的反馈、问题和建议。
参考
K Scott Mader 肺部胸部 x 光异常https://www . ka ggle . com/k Mader/Pulmonary-胸部 x 光异常
Jaeger S,Candemir S,Antani S,Wáng YX,Lu PX,Thoma G. 用于计算机辅助肺部疾病筛查的两个公共胸部 X 射线数据集。量子成像医学外科,2014 年;4(6):475–477.2014 年 11 月 20 日
耶格 S、卡拉吉里斯 A、坎迪米尔 S、福利欧 L、西格尔曼 J、卡拉汉 F、薛 Z、帕拉尼亚潘 K、辛格 RK、安塔尼 S、托马斯 G、王 YX、卢 PX、麦克唐纳 CJ。利用胸片进行自动结核病筛查。IEEE 跨医学成像。2014 年 2 月;33(2):233–45.多伊岛:10.1109/TMI 2013 . PMID:24101 . 13838383621
坎迪米尔 S,耶格 S,帕拉尼亚潘 K,穆斯科 JP,辛格 RK,薛 Z,卡拉吉里斯 A,安塔尼 S,托马斯 G,麦克唐纳 CJ。使用非刚性配准的解剖图谱在胸片中进行肺部分割。IEEE 跨医学成像。2014 年 2 月;33(2):577–90.多伊岛:10.1109/TMI 2013 . 2291 . PMID:24233243223
检测强化学习中的奖励退化
一个检测事件信号退化的最佳测试,例如基于 RL 的代理的回报
作者图片
RL 中的绩效意识
强化学习是机器学习的一个分支,在这一分支中,一个智能体学习在一定的环境中顺序地做出决策。虽然该领域的大多数研究都集中在代理的训练上,但是在许多应用中(尤其是对风险敏感的应用,如医疗系统和自动驾驶汽车),训练必须停止,并且在部署代理以在生产中运行之前必须对其进行修复。在这样的框架中,了解代理在任何时间点的性能都是至关重要的。特别是,尽快知道性能是否开始恶化是至关重要的(例如,如果汽车控制器开始不稳定,我们希望在它实际撞上任何东西之前注意到)。
由于我们关注的是代理的后培训阶段,有人可能会认为这个问题与统计监控有关,而不是 RL。然而,有两个因素使得 RL 的上下文特别有趣。首先,在 RL 中,我们有几个特征性的信息来源:奖励、观察和对主体的理解(例如,可以通过状态估计或价值函数来表达)。第二,在 RL 中,所有这些信号通常以情节的方式出现,在情节中,信号不是独立的,也不是同分布的(在我们的框架中也不是马尔可夫的)。
报酬恶化的最优检验
在我们的工作中,我们关注代理人的报酬,这使得监控系统完全是外部的,并且独立于代理人。我们假设“有效”奖励的参考数据集是可用的(在实际应用中,这可能是,例如,系统测试的记录),并且我们顺序地测试,以便注意到奖励相对于参考何时恶化。
这样的测试写起来非常简单:只需取最近几集的平均奖励,并与参考值进行比较。然而,事实证明这是非常次优的:例如,如果一集的某个时间步长具有高度变化的奖励,而另一个时间步长具有非常稳定的奖励,那么显然第二个时间步长对于检测退化会更有帮助。
在我们的工作中,我们使用 Neyman-Pearson 引理来表明,实际上,如果我们假设奖励是正态分布的,那么最佳测试(即具有最佳显著性/功效阈值的测试)将考虑奖励的加权平均值而不是简单平均值,其权重对应于奖励的逆协方差矩阵的行和。我们还表明,在缺乏正态性的情况下,加权均值仍然优于简单均值(即使在所有可能的测试中不一定是最优的)。我们还量化了它相对于简单均值的优势,并表明它随着回报协方差矩阵谱的异质性而增加。
调整测试阈值
所以我们知道退化测试应该考虑哪个统计量,但是什么是阈值,低于这个阈值我们会喊“退化”呢?我们希望在参考数据集上使用自举来确定这个阈值,但是这样做有困难:(1)回报不是同分布的,因此自举中的采样可能破坏信号的分布;(2)测试是以重叠的方式连续进行的,因此简单的假阳性概率不是测试显著性的良好描述符。
我们通过利用情节设置和对完整情节进行采样来处理非 i.i.d 问题。关于顺序框架,我们通过“在顺序测试的 h 集期间,虚警概率不得超过 α ”的要求来定义测试的重要性,并在 BFAR 中相应地设计自举—用于非 i.i.d .信号中虚警率控制的自举。
数字结果
为了测试我们的程序,我们在几个环境中训练了一个代理,然后对环境进行了改变,并让回报恶化(没有进一步的训练)。在所有环境和所有统计测试中,在没有环境修改的情况下,错误警报被成功控制——这表明 BFAR 的成功。在存在修改的情况下,我们建议的测试比替代测试更快、更频繁地检测到退化——通常是几个数量级。作为它的竞争对手,我们选择了朴素简单均值(在实践中通常用于 RL)、CUSUM(来自序贯检验)和 Hotelling(来自多元均值漂移检验)。
不同退化测试(UDT、PDT 和 MDT 是我们测试的变体,其他供参考)和不同场景(有动作噪声的钟摆、控制成本增加的 HalfCheetah 和腿尺寸增大的人形机器人)的退化检测概率与时间的关系。我们的测试比现有的测试更快地检测到由环境改变引起的退化(注意时间轴上的对数标度)。
这项工作发表在 NeurIPS 2020 年 RWRL 研讨会上。
https://github.com/ido90/DriftDetectionInEpisodicData
检测 A/B 测试中的样本比率不匹配
这篇文章帮助你识别你的数字实验中是否有不平衡的群组,并结合了一些基本的 Python 函数来帮助 SRM 检测
作者创建的图像(弗兰克·霍普金斯)
介绍
当一个实验被暂停,我们期待调查一个给定测试的统计数据时,我们经常思考我们的发现。我们可能未能检测到总体上具有统计学意义的结果,因此按照年龄、设备、地理位置或平台类型对我们的数据进行分层,以查看我们的实验条件是否对给定的细分市场表现更佳。此外,我们可以在事后测试中调整显著性阈值,以查看我们是否达到了各种α阈值的统计显著性,从而为利益相关者提供给定实验条件下的置信度。这两种额外的分析都是评估你对结果的信心的非常有效的方法,但是如果你收集的数据缺乏基本的完整性,这两种方法可能会失效。因此,在对我们的数据提出因果关系之前,我们必须在实验过程中进行统计测试,以确保我们收集的数据符合我们对实验数据收集方式的预期。
当处理随机样本时(我们经常在数字实验中这样做),我们可以执行的一个简单且必要的统计评估是样本比率不匹配(SRM) 测试。在受控实验的世界中,SRM 意味着两个测试组之间的用户随机分配与指定分配比例下的预期分配(预期样本比率,通常是组之间的平均分配)显著不同。更简单地说,SRM 是预期采样比和观测采样比之间的不匹配。
好消息是,确定 SRM 是否存在于您的实验数据中非常简单。您需要收集的唯一数据点是针对您的控制和实验条件的观察样本量和预期样本量。然后你在一个 卡方独立性检验 中使用这些值;如果测试的结果在统计上是显著的,那么您知道您观察到的数据中的差异不是虚假的,并且在您的 A/B 测试供应商或您如何实现您的测试平台中可能有潜在的原因。
在本文中,我将分解如何在 Python 中执行 SRM 测试,作为事后测试和整个实验中的单独增量。
Python 应用程序
执行 SRM 分析的第一步是导入数据。您将需要确保您的数据帧格式正确,适合我编写的 SRM 函数。这将有三列:日期和每个实验条件(对照和变体)中各自的独特用户数。重要的是,您要利用用户而不是会话数据,因为一个用户可能有多个会话,由于您的实验条件发生了变化,这些会话可能会有本质上的不同。然而,用户数量应该与您在 A/B 测试平台中设置的分布成正比。出于分析目的,我们将使用一个简单的 A/B 测试:
data_1 = pd.pivot_table(data_1, values='distinct_users_x', index='date',
columns='Variant', aggfunc=np.sum)data_1.reset_index(inplace=True)
data_1 = data_1[['date', 'Control', 'Variant']]
data_1
我们 SRM 分析的第一步是计算测试中的用户总数:
total_users_in_test = sum(data_2['Control']) + sum(data_2['Variant'])
然后是每个实验组中用户的各自比例:
control = sum(data_2[‘Control’]) / total_users_in_test
variant = sum(data_2[‘Variant’]) / total_users_in_test
print(100*round(control,5),"% users are in the Control group.")
print(100*round(variant,5),"% users are in the Variant group.")50.493 % users are in the Control group.
49.507 % users are in the Variant group
然后您需要创建一个 tuple,它包含两个组中观察到的用户总数:
observed = [sum(data_2[‘Control’]), sum(data_2[‘Variant’])][5143598.5600000005, 5043244.959999999]
和总流量的数值:
total_traffic = sum(observed)10186843.52
然后,您需要计算每个实验条件下的预期用户数,对于 50/50 A/B 测试,应该是测试中的用户总数除以 2。如果您在测试中有不同的流量分布,则必须修改以下代码:
expected = [ total_traffic/2, total_traffic/2 ][5093421.76, 5093421.76]
现在,您可以将观察到的和预期的数据输入到独立性的卡方检验中:
chi = chisquare(observed, f_exp = expected)print(chi)if chi[1] < 0.01:
print(“Sample ratio mismatch (SRM) may be present”)
else:
print(“Sample ratio mismatch (SRM) probably not present”)Power_divergenceResult(statistic=988.6129116627776, pvalue=5.363182731279153e-217)
Sample ratio mismatch (SRM) may be present
正如您所看到的,观察到的比率和预期的比率在统计上是不同的,这表明 SRM 可能存在于我们的实验数据中。上面选择了 0.01 的 alpha 阈值,因为我们希望非常确信 SRM 存在于我们的数据中,但是该阈值可以更改为有利于您自己的分析。
到目前为止,我们已经执行了 SRM 测试,并使用统计分析来确定我们的团队是否平衡,以及在临时设置中是否符合我们的预期。虽然这有很好的实用性,但我们也希望能够在递增的时间点识别这一点。大数定律会让我们相信,随着数据量的增加,我们会期望每个组中的用户比例都接近预期的比例,对于 A/B 测试,这个比例是 1(每个组中有 50%)。如果每一组之间存在根本的差异,我们将不会看到这种趋同,并且在每一个实验条件下,用户之间存在一致的差异。
我写了一个 srm_pivot 函数,它将处理上面的数据框对象,并计算每个时间点的观察数据和预期数据(在本例中是日期)。卡方 f 和 p 值也在每个时间间隔计算,以确定比率是否随着时间变得更加相似:
下面是上述函数的输出:
作者创建的图像(弗兰克·霍普金斯)
如您所见,两组用户的比例并没有随着时间的推移而趋于一致,这清楚地表明 SRM 存在于数据中。接下来的步骤将是对为什么会出现这种情况进行调查分析。建议采取的步骤如下:
- 调查用户群
- 分析时间段
- 分析 KPI
- SRM 的计数频率
- 执行模拟/模拟实验并计算 SRM
- 检查 SRM 的严重性(独立地和在实验之间)
还有一些有用的根本原因分析技巧,在一篇由 法比詹、维梅尔和德米特列夫 写的文章中推荐。
结论
A/B 测试已经成为决定你应该开发的网站版本的黄金标准。我们在数据科学和数字实验方面投入了大量的时间和精力来分析结果,并试图确定我们的实验和性能指标之间的因果关系。然而,很少有人相信我们数据的质量和完整性。如果我们的实验组没有在预期阈值内加权(由我们在 A/B 测试平台中配置的分割决定),我们很难在事后测试中完全忠实于我们计算的任何给定统计数据。SRM 测试使用基本的独立性卡方检验,帮助我们确定我们的组在统计上是否与我们预期的流量分布相似/不同。本文中使用的代码和函数可以/应该与您的所有实验实践一起使用,以确保维护数据完整性。还建议您记录检测 SRM 的测试量,因为您的计数频率可用于 SRM 的根本原因分析。
检测图像数据中的语义漂移
行业笔记
使用 whylogs 监控完整的上下文数据
你的机器学习模型通过它的训练数据的镜头来看世界。这意味着随着现实世界离你的训练数据越来越远,你的模型变得越来越短视。然而,在操作机器学习应用程序时,升级您的眼镜(重新训练您的模型)并不是我们唯一关心的问题。我们还控制着在生产过程中向我们的模型提供信息的数据管道,因此有责任确保其质量。
数据中的概念漂移可能有不同的来源,可能源于数据管道的不同阶段,甚至在数据收集本身之前。为了采取正确的措施,人们必须能够确定其来源。然而,无论问题的来源是什么,需要采取什么样的纠正措施,这一切都始于一个基本要求:我们需要意识到一个问题首先存在,这本身就是一个挑战。
在本文中,我们将展示 whylogs 如何通过启用概念漂移检测来帮助您监控机器学习系统的数据摄取管道,特别是针对图像数据。为此,我们将使用几个演示用例。
对于第一种情况,我们将从两个不同的数据集获取图像:风景图片和 IEEE 信号处理协会相机型号识别。我们将创建一个语义发生巨大变化的场景,并监控特定的特性,从而发现意外的数据变化。我们还将监控与内容相关的图像元数据,或者与图像创建相关的环境及其上下文。
在第二个场景中,我们将演示如何创建更一般化的语义度量。加上定义自定义转换的能力,我们可以以一致、通用的方式直接从我们的数据集中监控专门的语义信息。在这种情况下,我们将从预训练的 DenseNet 模型中生成图像嵌入,并将它们用作我们的全语义数据。我们将使用不同CIFAR-10
类的数据集来模拟部署,作为如何在我们的数据和模型中捕获和潜在检测这些变化的示例。
目录
1.元数据和特征∘图像特征t13】∘图像元数据t16】2。语义漂移
∘ 自定义特征——距聚类中心的距离
3。结论
元数据和功能
即使我们没有用于预测的实际模型,让我们假设我们的模型输入预计主要由风景图像组成。使用模拟生产阶段,我们可以测试是否有可能通过记录一些基本的映像特征来检测域外映像的存在。这些可以包括元数据信息和属性,如色调、饱和度和亮度。
我们将图像存储在如下文件夹中:
作者图片
landscape_baseline/baseline
子文件夹包含 1800 张JPEG
风景图片。我们将使用这些图像作为比较的基线。其余三个文件夹包含生产过程中要监控的图像数据:
landscape_images
拥有 200 张 JPG 风景图片camera_images
有 200 张 TIFF 图像,没有特定的类别,从相机型号识别数据集中提取。mixed_images
以 50/50 的比例组合了前两个数据集。
图像特征
检测数据变化的一种方法是创建并比较图像属性直方图,如色调、饱和度或亮度(HSB
) )。我们可以使用 whylogs 来自动记录这些属性,然后使用分析信息来绘制直方图。请注意,HSB 色彩空间比RGB
色彩空间及其相关直方图更有助于理解内容,因为这些属性与图像的视觉解释直接相关。
在这种情况下,我们将创建 3 个不同的数据集:
- 第一个包含关于基线数据集的信息
- 第二个将模拟预期的一批图像:只有风景图像
- 第三种将只包含“看不见的”图像,与风景无关
这个想法是,在生产过程中收到的批次的数据分布应该类似于我们的基线集的分布。如果我们可以检测到两者之间的分布开始偏离,这可能是进一步检查的警报。
让我们首先创建一个会话,这是我们的应用程序与 whylogs 交互的方式:
然后使用log_local_dataset()
记录每个文件夹的内容
正如您在上面的代码中看到的,我们可以在使用image_feature_transforms()
登录之前应用图像转换。我们还可以通过ComposeTransforms()
混合和匹配功能来创建我们定制的特征转换管道。在这种情况下,我们将图像大小调整为 50x50 像素,然后获取每个像素的饱和度值。
我们还必须记录landscape_images
和camera_images
的图像。由于它与上面的代码完全相同,我们将避免在此重复。唯一的区别在于dataset_name
、 root_dir
和*_baseline
变量。
然后,我们可以定义绘制直方图的函数:
这些函数将通过组合输入列表中每个轮廓的信息来绘制所选要素的直方图。如果定义了基线,基线数据集的直方图也会绘制在背景中以供比较。
让我们画出它们的分布图:
正如我们所看到的,景观集的最终饱和度分布比我们的“漂移”批次更类似于基线集。如果我们知道会发生什么,像第二个这样的情节肯定会引起注意。
图像元数据
获取有用信息的第二种方法是监控记录的图像的元数据信息。不是每个文件都会有它们,但是我们可以试着从可用的文件中获益,这些文件将由 whylogs 自动记录。我们还可以记录和搜索特定的标签。EXIF
Tags 是大多数相机和图像软件制造商使用的标准。不幸的是,当你进入更模糊的数据格式时,你会遇到自定义元数据,幸好它们通常是TIFF
标签。下面是在我的一张个人相机图片中发现的一些TIFF
标签的例子。
╔═══════════════════════════╦══════════════════════╗
║ Tag ║ Value ║
╠═══════════════════════════╬══════════════════════╣
║ Manufacturer ║ CANNON ║
║ Model ║ EOS-600D ║
║ Orientation ║ top-left ║
║ Software ║ Ver2.21 ║
║ Date and time ║ 20200:08:11 16:55:22 ║
║ YCbCr positioning ║ centered ║
║ Compression ║ JPEG compression ║
║ file_format ║ jpg ║
║ X resolution ║ 72.00 ║
║ Y resolution ║ 72.00 ║
║ Resolution unit ║ Inch ║
║ Compressed bits per pixel ║ 4 ║
║ Exposure bias ║ 0.0 ║
║ Max. aperture value ║ 2.00 ║
║ Metering mode ║ Pattern ║
║ Flash ║ Flash did not fire ║
║ Focal length ║ 16.1 mm ║
║ Color space ║ sRGB ║
║ Pixel X dimension ║ 2240 ║
║ Pixel Y dimension ║ 1680 ║
╚═══════════════════════════╩══════════════════════╝
像Resolution unit
这样的标签以及 X 和 Y 分辨率,让我们看到了潜在的对象缩放问题,如果每个像素的单位大小改变,而相机位置(相对于感兴趣的对象)没有改变,我们可能会遇到比以前遇到的更小或更大的对象,特别是如果在这些方面没有增强步骤的话。而Compressed bits per pixel
可能会通知我们像素强度缩放或图像的新上下文,例如可能在基线集中不存在的压缩伪影。
在这个简单的例子中,我们将利用file_format
信息。如前所示,风景图像应该是JPEG
格式,而我们的“噪声”图像都是TIFF
格式。因此,我们可以监控整个数据集中唯一值的计数,以确保批次之间的一致性。
为了演示,我们将在同一个数据集中记录一系列五个批次。其中四个数据集仅包含景观图像,另一个是景观图像和域外图像的混合:
通过dataset_timestamp
给每批分配一个日期,然后用log_local_dataset()
记录。我们可以通过绘制数据集内每个记录批次的file_format
特征的唯一值计数来继续:
作者图片
正如所料,该图反映了每批数据的独特文件格式的数量。尽管这是一个非常简单的案例,但它展示了文件格式数量的变化如何表明存在数据问题。
元数据让我们能够快速发现日期、仪器、图像大小、编码、作者等潜在问题。这反过来使得监控图像管道能够提供快速故障保护。对于监控静态数据集的人来说,这可能并不重要,但是一个保持医疗图像一致性和实验可重复性的大公司会非常感激。
此外,这些可以在回顾期间调试故障时产生有价值的上下文信息。
语义漂移
在典型的影像分类任务中,了解类别间分布的变化非常重要。虽然基本事实标签通常不容易获得,但我们可以从迁移学习中受益,以生成特征嵌入,从而获得关于我们数据的一些语义见解。在这个场景中,我们将使用来自[CIFAR-10](https://www.cs.toronto.edu/~kriz/cifar.html)
数据集的一个子集,即bird
、cat
、deer
和horse
类,来形成一个“动物”基线。我们将创建一个由来自ship
和automobile
类的样本组成的单独的倾斜“车辆”集合。
自定义要素-距聚类中心的距离
在这种情况下,我们希望生成并记录能够从语义上表示从记录的图像到每个类的“理想”表示的距离的特征。为此,我们可以利用 whylogs 的能力来创建定制函数,以生成特定的图像特征。这样,我们可以记录从图像到两个类的距离,并绘制直方图来评估每一批的距离分布。
首先,我们需要将原始像素值嵌入到一个 N 维向量中。在这个例子中,特征提取器基于DenseNet-121
架构,生成 1024 维的向量。这些嵌入可以进一步归一化,因此它们之间的标量积将位于归一化的范围内,将每个向量放在超球面的表面中。
查看苹果 ML 团队的这篇帖子,了解类似的嵌入技术是如何在他们的人脸识别管道中使用的。
因为在训练阶段我们有了每个图像的标签,所以我们可以预先计算每个类的质心坐标。有了聚类的中心,我们可以选择离这些中心最近的图像或物体作为我们的语义中心。这些图像为我们计算和监控嵌入的分布提供了依据。我们可以创建一个自定义函数,通过计算图像之间的点积来记录从每个图像到中心的距离:
这个方法可以用来扩展 whylogs 来收集完整的语义度量
和前面的例子一样,我们可以应用自定义转换来记录两个数据集。一个是由在CIFAR-10
数据集中随机选择的四个动物类(bird
、cat
、deer
、horse
)组成的集合。语义完整对象的未知图像集由两种车辆类别(ship
、automobile
)组成,这将导致我们的数据集中的漂移。
基线(动物)和“未知”语义对象(载体)嵌入的 UMAP 投影。作者图片
在上图中,我们使用UMAP
地图作为嵌入的可视化,以及每个类别的语义中心在原始维度中的位置,由类别标签的位置给出。请注意,由于我们使用的是普通的UMAP
嵌入,因此空间顺序不会保留在投影中。尽管如此,它们仍然让我们看到嵌入是如何围绕语义相似的对象聚集的。
我们只使用原始基线中心(鸟、猫、鹿、马)来计算我们的全语义度量,因为我们知道这些是我们原始基线的一部分。在下图中,我们绘制了从基线(左图)的每个语义中心到仅车辆分布(右图)的距离分布。
左图显示了基线中存在的对象距语义中心的距离的分布,而右图仅包括不在基线分布中的对象,因此距离的分布趋向于峰值接近 0。作者图片
然后,我们可以将这些距离组合成一个度量,如上面的自定义函数中所定义的。在下图中,我们显示了来自每个基线语义中心的仅载体嵌入的分布,以及同时嵌入动物和载体(混合集)以及仅动物嵌入的分布,作为一种可视化语义漂移的方式。
“SemanticCenterDistance”中为嵌入定义的自定义函数给出的距离组合分布,仅包括基线(仅动物)和其他。作者图片
为了量化这种漂移,我们将每个分布的 KS 统计量与基线(仅动物)分布进行比较。例如,比较基线和车辆嵌入导致 KS 统计值为 0.25。考虑到我们用于计算上述分布的大量嵌入,我们得到零假设的可能性非常小(p < 0.0001). In this simple experiment it can be shown that we would need approximately 200 embeddings from baseline and shifted distribution of vehicles to significantly reject the null-hypothesis using this embedding model and classes. This number gives us the optimal batch size for detecting such changes in our pipeline for this particular example.
Conclusion
Real world data can behave a lot differently than you might expect. Distributional drift is one of the most common problems encountered in AI applications and, as such, you should have tools to effectively detect those issues the moment they appear.
In this article we covered some common use cases to demonstrate how whylogs 可以帮助您检测图像中的数据漂移问题,从使用默认图像特征和元数据信息到应用自定义转换来生成特定特征。
也就是说,本文中的例子可以进一步扩展。例如,在饱和的情况下,我们可以通过量化直方图偏移来继续我们的分析,比如 KS 统计或其他分布度量。此外,这些指标及其相关阈值可能会包含在您的 CI/CD 管道中——例如——查看这篇帖子,了解如何在您的 github 操作中包含 whylogs 约束检查。
当可用时,其他类型的元数据也可以提供有价值的见解,例如纵横比和分辨率,或者对象检测和分割数据,以进一步理解和给出主题的上下文。或者,在语义分布的情况下,我们可以逐渐增加未知类别的比例,以定义一个可行的阈值 KS 统计,或其他度量,如 KL-divergence 或 Hellinger 距离。此外,这些方法和其他方法可以集成到数据管道的不同阶段,以提供机器学习应用程序的完全可观察性。
这里有一种最近的方法,它使用基于变压器的模型来提供一种快速的食品检测方法!它们还提供了一些用于确定此类任务有效性的通用指标。
如果你有兴趣为 whylogs 做贡献或者有任何问题,请随时联系@lalmei,或者前往我们的 OS repo【https://github.com/whylabs/whylogs】T2。你也可以在 https://docs.whylabs.ai 查看我们的文档页面,了解更多关于 whylogs 的信息。
在卫星图像中探测雪和云块
实践教程
在 2020 年哥白尼黑客马拉松期间,看看我们如何解决在真实世界卫星图像中检测雪和云块的问题。
2020 年 9 月底,我们参加了哥白尼黑客马拉松瑞典 2020 。在由 Arctic Business 和 Innovatum Startup 组织的黑客马拉松上,所有参赛团队都通过欧洲哥白尼项目利用卫星图像来解决与气候变化、陆地生命和新冠肺炎有关的各种问题。
在这篇博文中,我们将看看我们如何使用深度学习解决在真实世界卫星图像中检测雪和云的问题。
雪和云的检测问题
黑客马拉松提出了解决各种问题的挑战,从水资源短缺到自动圈地到新冠肺炎。我们选择了雪和云的探测问题。这里的挑战是建立能够检测图像中的雪和云的模型。由于二者具有相似的光谱特性,在卫星图像数据中区分雪和云是一个相当困难的问题。
我们不仅想知道图像中是否有雪和/或云,还想知道这些区域在图像中的位置。由此产生的问题是所谓的图像分割问题,其中我们预测属于(这里:雪、云或其他)图像中每个单独像素的类别,而不是图像整体。
卫星数据
在我们的挑战中,我们利用了来自哨兵 2 号卫星的瑞典卫星图像数据。数据通过开放数据立方体平台提供。在数据中,我们可以访问各种哨兵-2 波段。我们自然有红色、绿色和蓝色(RGB)波段,但也有像红外线、水蒸气地图等东西。使用 RGB 波段,我们可以展示瑞典北部地区不同时间的几幅图像。
来自卫星数据的示例图像。(图片由作者提供)
通过在不同时间从不同地区收集大量这样的图像,我们构建了自己的数据集,用于训练和评估解决手头任务的模型。
k 均值聚类
由于没有直接可用的图像分割的注释数据,我们的第一种方法是使用 K-means 聚类来聚类图像数据,因为这是一种无监督的方法。对于三个期望的类(雪、云和其他),我们在三个集群中尝试了这种方法。让我们看一些例子来了解结果。
K 均值聚类的结果示例。(图片由作者提供)
通过分析聚类结果,我们可以看到 K-means 算法成功地将雪和云与其他所有东西分开。然而,该算法无法区分雪和云。正如我们所看到的,雪和云的斑块合并成两个集群,而不是分离成单独的一个。
无监督聚类方法显然不足以解决这个问题。因此,我们将改为在监督设置中训练图像分割模型,以尝试解决我们的问题。
图象分割法
虽然直接进入图像分割模型的构建令人兴奋,但我们将首先看看我们如何为卫星图像数据集创建注释,以便我们可以训练和评估我们的模型。
释文
没有直接可用的带注释的图像遮罩(pixelwise 类属性)。幸运的是,Sentinel-2 数据提供了两个有用的波段,即降雪概率和云概率波段。顾名思义,这些波段由每个图像像素包含雪和云的概率组成,由它们自己的模型产生。
将这些波段的值与两个概率的阈值相结合(以及一些平滑技巧),我们能够生成我们自己的注释掩码,用于监督学习。下面我们可以看到一些例子。紫色区域是雪,绿色区域是云,黄色区域是其他的。
卫星数据的示例注释掩码。紫色区域是雪,绿色区域是云,黄色区域是其他的。(图片由作者提供)
使用其他模型的结果作为您自己模型的注释当然不是首选的方法。然而,在这种情况下,这是进行监督学习的唯一选择,而不必我们自己注释图像。它还提供了一个机会来改善 Sentinel-2 自己的模型的结果,并为我们提供了一个模型,该模型可用于雪和云概率带不可用的其他图像数据。
模型
对于大多数图像分析问题,深度学习是必由之路。我们解决这个图像分割问题的方法没有什么不同。具体来说,我们在模型中使用了深度神经网络 U-Net 结构。U-Net 模型通常用于图像分割任务,实际上是为生物医学图像分割而开发的。
U-Net 网络架构有两个主要部分——下采样器和上采样器——分别由下图中的左右部分表示。缩减采样堆栈将逐步缩减图像采样,如下所示:
128 x128→64x 64→32x 32→16x 16→8 x8→4x 4
并通过这种方式找到模型其余部分可以使用的输入的良好表示。这个下采样部分因此作为模型的特征提取器工作。为了增强我们的模型,我们使用了预训练的下采样堆栈,因此能够通过迁移学习从我们自己的数据集之外的其他图像中整合有用的信息。
U-Net 网络架构。来源:https://csaybar.github.io/blog/
U-Net 模型的上采样器部分将再次对图像进行上采样,如下所示:
4x4 → 8x8 → 16x16 → 32x32 → 64x64
在最后的卷积层将图像恢复到 128×128 大小之前。
上图中的那些绿色箭头是怎么回事?它们象征着所谓的跳过连接,这意味着模型例如将来自下采样器的 16x16 表示直接发送到上采样器的 16x16 → 32x32 层,以及来自先前 8x8 → 16x16 层的输出。跳跃连接减少了训练期间消失梯度的影响,并由此加速和简化了模型训练。
结果
有了数据集和模型,我们只剩下模型训练和评估了。在将整个数据集分成训练集、验证集和测试集之后,我们在训练集上对模型进行了 20 个时期的训练,并得到了以下学习曲线:
从训练和验证模型中学习曲线。(图片由作者提供)
除了由于一个相当小的数据集导致的曲线中的“尖峰”,所有曲线看起来都像我们希望的那样,并且我们能够达到良好的准确度分数。对测试集的最终预测显示了 91%的准确度,即,给定我们之前构建的注释遮罩,我们的模型预测了测试集中所有图像中所有像素的 91%的正确类别归属(雪、云或其他)。
最后,让我们看看我们的模型在测试图像上做出的一些预测。
测试集上模型预测的例子。紫色区域是雪,绿色区域是云,黄色区域是其他的。(图片由作者提供)
除了 91%的高准确率之外,我们还可以在这里看到该模型工作得非常好!不像我们上面做的聚类,我们的图像分割模型也是能够检测和区分雪和云。有趣的是,至少在这两个例子中,预测的掩码似乎比带注释的略好。也许我们能够改进现有的 Sentinel-2 模型来探测雪和云。
结论
总之,在哥白尼项目中,我们解决了从 Sentinel-2 卫星的卫星图像中检测雪和云块的挑战。在意识到无监督聚类方法不足以解决这个图像分割问题后,我们注释并训练了我们自己的 U-Net 神经网络模型,并取得了非常好的结果。
为了进一步改善结果,我们可以:
- 使用更大的卫星图像数据集
- 提供更好的、可能是人为的注释
- 寻找其他预先训练好的模型,以纳入我们自己的模型
总的来说,这是一个成功而有趣的项目!
进一步阅读
在这篇由瑞典 AI 撰写的文章中,你可以读到更多关于我们使用雪和云检测模型来绘制地图和预测路况的想法。
使用 OpenCV 和 Dlib 检测虹膜并改变其颜色
费利佩·库尼亚的眼睛和科托姆布罗的照片,https://www.pexels.com/@cottonbro
在这个简短的教程中,我们将讨论如何在图像上定位某人的虹膜,并创建一个遮罩,这样我们就可以改变它的颜色。
精确定位虹膜边界的问题并不简单。我们想要创建一个非常精确的蒙版,这样最终的效果看起来更真实。为了努力取得成功,我们需要执行以下步骤:
- 执行面部标志检测。
- 使用相应的标志为双眼创建蒙版。
- 为图像的适当颜色通道设定阈值。这一步取决于主体的虹膜色调。例如,如果您试图检测一个蓝眼睛的人的虹膜,您应该使用图像的红色或绿色通道来增加与白色背景的对比度。
- 找到二值化虹膜图像的质心。
- 找到二值化虹膜图像的轮廓。
- 找出所有虹膜轮廓的最小封闭圆。
- 比较步骤 4 中获得的质心与所有轮廓的最小封闭圆的质心之间的距离。
- 选择质心到质心距离最小的圆。
- 使用步骤 8 中获得的圆创建一个蒙版和一个反向蒙版。
- 将颜色转换应用于整个图像,并将此图像乘以虹膜蒙版。
- 将原始图像乘以反向蒙版。
- 将步骤 10 和 11 的结果图像相加。
请检查以下实施情况:
而结果呢!
右:原始图像/左:处理后的图像
谢谢你看我的文章!我很想听听大家对改进这段代码的意见。我想尝试的是通过分析主体虹膜的 BGR 通道强度来自动进行阈值处理。这也可以让算法自动确定合适的颜色通道,以找到虹膜的边界。
使用无监督学习技术检测巴伦西亚最受欢迎的旅游景点
DSCAN 算法用于发现巴伦西亚(西班牙)最受欢迎的拍照地点。
Jonny James 在 Unsplash 上拍摄的照片
瓦伦西亚是西班牙最国际化、最具活力的城市之一。巴伦西亚位于地中海海岸,是西班牙第三大城市和大都会区。在 2000 多年的历史中,这座城市是许多不同文化的发源地。罗马人、西哥特人和穆斯林占领了巴伦西亚,留下了丰富的艺术收藏品和独特的建筑遗产。目前,巴伦西亚是一个受游客欢迎的地方,每年接待大约 200 万游客。
在本文中,我们将使用 Flickr 提供的照片的地理信息来分析巴伦西亚最相关的景点。为此,我们将使用算法 DBSCAN ,这是一种无监督学习技术,它根据密度提供数据聚类。
开源代码库
这个项目的代码可以从 GitHub 上的 Jupyter 笔记本中获得。
https://github.com/amandaiglesiasmoreno/dbscan_photos_valencia
Flickr API
Flickr 是最受欢迎的照片分享网站之一。要使用 Flickr API ,你需要一个 Flickr API key 和一个 Flicker 用户 ID 。一旦您有了用户 ID 和 API 密钥,您就可以使用 flickrapi 库来搜索图像。
项目的第一部分包括导入所有需要的库以及创建一个 FlickrAPI 对象。
使用flickr.photos.search
功能获取照片
下一步包括使用 Flickr API 获取感兴趣的照片。flickr.photos.search
函数根据给定的参数返回 Flickr 存储库的照片列表。为了简化分析,我们将只从存储库中下载 2019 年在巴伦西亚拍摄的照片。我们可以使用参数(1) min_upload_date
和(2) max_upload_date
来指定利息日期(2019)** 。另外,我们将只下载平台上的公开照片 ( media='photos'
和privacy_filter=1
),不包括视频和私人照片。最后,我们提供 Valencia** 的边界框来保证只检索这个特定区域的照片。
定义区域的边界框
如上面的代码所示,我们需要向flickr.photos.search
函数(参数bbox
)提供 Valencia 的边界框,以仅检索在该区域拍摄的照片。要获得该区域的坐标,我们可以使用OpenStreetMap数据库。在地图上手动绘制感兴趣区域后,位于左上角的文本框中会出现边界框坐标(左下角经度,左下角纬度,右上角经度,右上角纬度),如下图所示。然后,我们需要将这些坐标作为字符串提供给bbox
参数,其中的值用逗号分隔。
用 OpenStreetMap 获取瓦伦西亚的边界框
将搜索函数的输出(XML 元素)转换成 Pandas 数据帧
当您调用函数时,flickr.photos.search
函数发回一个解析过的 XML 元素。该元素包含关于符合搜索标准的照片的多个细节。rsp
标签在属性stat
中指示调用是否成功执行。photos
标签提供了搜索结果的摘要。在这种情况下,有 17997 张图片符合组织在 72 页中的搜索标准。在photos
标签中,有一个photo
标签列表,每个标签包含一张特定照片的信息。在本例中,感兴趣的属性只有四个:(1)id,(2)纬度,(3)经度,(4)照片的 URL;然而,正如您在下面看到的,搜索功能提供的描述照片的属性数量要多得多。
搜索功能的输出
如上面的代码所示,我们将感兴趣的信息存储在名为photo_information
的字典中,其中的键表示感兴趣的属性,值是包含数据的列表,每个索引存储一张照片的详细信息。
最后,我们可以使用pandas.DataFrame
构造函数轻松地将这个字典转换成 pandas 数据框,如下所示。
数据清理
在检索感兴趣的信息之后,所获得的数据帧仅包含四列:(1) id
、( 2) latitude
、(3) longitude
和(4) url_n
。这是一个非常简单的数据集;但是,我们不能直接使用它来对数据进行聚类。我们需要先把它清理干净。
数据集不包含缺失值;但是,纬度和经度列的数据类型被错误检测。我们需要将这些列转换成浮点数,以便稍后在 DBSCAN 算法中使用它们。
纠正错误的数据类型后,我们分析数据帧是否包含重复照片。Flickr 中的每个照片商店都有自己唯一的 id。因此,不可能有两张具有相同 id 的不同图片。正如你在下面可以观察到的,大部分照片都是重复的,所以我们需要把它们从数据框中剔除。
一旦删除了重复的条目,我们就从数据集中删除id
和url_n
列,因为不再需要它们了。
现在,我们可以使用这些数据来获得指示该城市最受欢迎的景点的聚类。
DBSCAN 算法
理论
DBSCAN (含噪声应用的基于密度的空间聚类)方法是一种基于密度的聚类算法,用于将高密度区与低密度区分开。
该算法基于两个超参数:
- 半径( eps ): 被视为邻居的两个样本之间的最大距离。
- 最小点数( MinPts ): 考虑一个观察核心点的邻域样本数。
基于这些超参数, DBSCAN 算法根据以下规则将数据集中的每个观察值分类为核心、边界或异常点:
- 核心点:在其半径 eps 内至少有 MinPts 个观测值的数据点。
- 边界点:半径 eps 小于 MinPts 点的数据点;然而,该点在核心点的半径 eps 内。
- 离群点:既不是核心点也不是边界点的数据点。
核心点、边界点和异常点—由作者创建的图像
然后,根据聚类的类型将这些点分配给聚类。每个集群包含至少一个核心点和所有可从其到达的边界点。
优点和缺点
相对于其他聚类算法,DBSCAN 算法提供了多种优势。DBSCAN 算法的主要优势在于它可以找出任何形状的簇。聚类不必具有斑点形状。此外,在执行算法之前没有必要固定聚类数,因为我们必须使用 K-means 方法。此外, DBSCAN 能够检测数据集中的噪声,这与基于分区的算法(例如将所有点分配到一个聚类的 K-means)形成对比。使用 DBSCAN,位于低密度区域的点不会被分配给任何聚类。
然而,DBSCAN 方法也有一些缺点。该算法的主要挑战是找到两个超参数(eps 和 MinPts)的正确组合。这些超参数的选择是任意的,并且高度影响算法获得的结果。一种常见的做法是测试不同的超参数集,并选择一个产生可接受结果的超参数集,同时考虑聚类数和生成的异常值。
DBSCAN 算法的优缺点——作者创建的图像
用散点图可视化观察结果
下面的图显示了 2019 年在巴伦西亚拍摄的照片的位置(在 Flickr 平台上可用)。 x 轴代表照片拍摄地的经度,y 轴代表纬度。如您所见,有些位置显然拍摄了更多照片(高密度区域)。下一步包括使用 DBSCAN 算法来识别这些位置。
用 Scikit-Learn 实现 DBSCAN 算法
为了实现 DBSCAN 算法,我们需要首先实例化一个DBSCAN
模型,它可以从sklearn.cluster
导入。如下所示,为这个特定数据集选择的超参数是:(1) eps=
和(2) min_samples=
。这些参数是通过反复试验确定的。注意,在应用 DBSCAN 算法之前,我们已经用MinMaxScaler
类对数据点进行了规范化,因此所有属性(纬度和经度)都具有相同的范围【0,1】。
我们可以通过查看标签的唯一值来确定聚类的数量。正如你所看到的,一些观察值的指数等于-1,这意味着这些观察值被算法检测为异常值 T21。
最后,我们可视化排除噪声的集群(与等于-1 的标签相关联的观察)。此外,我们在聚类中心绘制了与每个组相关联的标签。
在交互式地图上可视化聚类的中心
下一步是在巴伦西亚的地图上可视化集群中心。这种方法可以让我们很容易地将群体的中心和城市中的地点联系起来。
首先,我们构建一个带有位置和缩放级别的叶子地图。这将产生一个给定位置的空地图(在本例中是 Valencia)。然后,我们用Marker
函数在聚类中心的位置绘制标记。
集群协会——巴伦西亚最重要的景点
我们可以通过在巴伦西亚地图上可视化这些群体的中心来识别以下兴趣点:
- 集群 0:艺术与科学之城
- 集群 1 :火车北站
- 第二组 : Burjassot 大街
- 集群 3 :市政厅广场
- 第四集群:贝尼卡拉普公园
- 第五组团:城市的历史中心
- 第六组:生物圈公园(动物园)
- 第 7 组:Parroquia San Jose maría escrivá
- 集群 8 :埃尔卡门街区
- 第九集群:体育中心(朝歌)
- 集群 10 :瓦伦西亚美术博物馆
- 第 11 组:巴伦西亚现代艺术学院
- 第 12 组:怡和集团
- 第 13 组:卡帕雷拉公园
- 第 14 组:海洋
- 第 15 组:拉斯阿里纳斯度假村
- 第 16 组:马尔瓦罗萨海滩
我最喜欢的地方😍
集群——城市的历史中心
巴伦西亚的历史中心毫无疑问是这座城市最迷人的部分。如今,这座历史中心是现代巴伦西亚的休闲和贸易中心,商店、餐馆和酒吧林立。中央市场、大教堂、丝绸交易所和埃尔卡门街区是巴伦西亚老城最重要的组成部分。
照片由艾·埃尔默斯在 Unsplash 上拍摄
集群——艺术与科学之城
艺术与科学城是一个由瓦伦西亚建筑师圣地亚哥·卡拉特拉瓦设计的文化与科学综合体。它位于图里亚河床的尽头,是巴伦西亚最受欢迎的景点之一。于 1998 年 4 月正式落成的艺术与科学之城由 7 座建筑组成: L'Hemisfèric (1998 年)El Museu de les ciències feli PE(2000 年) L'Umbracle (2001 年)L ' oceanograf(2000 年)该综合建筑全年提供广泛的文化活动和事件。
照片由彼得·劳伦斯在 Unsplash 上拍摄
替代可视化—照片的热图
分析的最后一步是使用热图可视化照片的位置(纬度和经度)。热图使用颜色来显示一个区域中某个量的变化。在这种情况下,我们再次使用 lyum 创建一个热图,覆盖在 Valencia 的地图之上。
如下图所示,历史中心、艺术与科学城、港口和动物园是巴伦西亚最受欢迎的地方。
阿曼达·伊格莱西亚斯
通过 AMTENnet 检测深度伪造和其他面部图像操作
一种检测数字面部操作的新方法
杰佛森·桑多斯在 Unsplash 上拍摄的照片
最先进的图像处理技术允许任何人改变图像和视频来交换一个人的身份。像 Deepfake 和 Face2Face 这样的技术是最近流行起来的两种相对新颖的技术。除了这些技术之外,各种基于 GAN 的人脸交换方法也已经发布,并附有代码。
DeepFakes 和 Face2Face 的使用有一个伦理因素;随着这些技术的不断改进,与之相关的伦理风险也在不断增加。因此,为了应对这种新出现的道德威胁,脸书研究创建了“ DeepFake 检测挑战(DFDC)数据集”和一个 Kaggle 竞赛来提高检测 DeepFakes 的准确性。在这场比赛中,黑盒环境中的最高准确率约为 65%。然而,更高的精度已经实现。
这个主题已经成为学术界的趋势,因此在学术界(以及学术界以外)围绕面部操作检测领域进行了大量的研究。来自中国湖南和南京大学的研究人员最近发表的一篇期刊论文提出了一种通过自适应操纵轨迹提取网络(AMTEN)检测 DeepFakes 操纵的新方法。
阿姆滕解释道
AMTEN 作为预处理模块工作,用于抑制图像内容的副作用。典型的面部操纵取证试图预测痕迹的操纵并提取它们。然而,AMTEN 使用输入图像和输出特征图之间的差异来提取这些操作轨迹。这种新的方法似乎可以很好地检测出倾向于篡改检测任务的操纵痕迹。
AMTEN 模块与卷积神经网络的集成产生了 AMTENnet,这是一种深度神经网络,旨在寻找图像和视频中使用的面部图像处理技术。
最近关于假脸检测的工作集中在二进制分类上,即找出一幅图像是假的还是真的。除了二进制分类,AMTENnet 还考虑了面部图像处理技术的多种分类。
人脸图像处理技术可以分为三类,即身份处理、表情处理和属性转移。
- 身份操纵:这是一种生成完全虚构的人的假脸图像的行为,以及用另一个人的脸替换一个人的脸(通过 DeepFake 或 FaceSwap)。
- 表情操纵:是指将面部表情从源人脸转移到目标人脸。
- 属性转移:当人脸图像的风格改变时,例如,改变性别、头发颜色或年龄。
AMTENnet 的网络架构在发表它的期刊论文中有充分的解释(包括图表),因此我鼓励读者看一看这篇开放存取的期刊论文,以了解关于该架构的更多细节。为了准确起见,我将引用下面的 AMTENnet 论文来解释网络架构是如何工作的:
给定一幅输入的 RGB 图像,我们使用 AMTEN 中的 Conv 1 来获取图像的特征图。然后,从 Conv 1 中的特征图中减去原始图像,提取低级操纵痕迹 Fmt 。此外,通过重用 Fmt 获得稳定的高级操纵轨迹,即 Freu 。接下来,将 Freu 传递到后续卷积层进行分层特征提取,以获得高级取证特征。最后,我们使用全连接层和 softmax 函数对图像进行分类。
AMTENnet 的准确性和优越性
AMTENnet 针对以下最先进的基线模型进行了测试:
此外,其他最先进的模块被用来取代 AMTEN 模块,从而产生了几个混合模型。这些模块分别是[ 周等,2018 ]的 SRM 滤波器核、[【巴亚尔和斯塔姆,2018 ]的约束 Conv 和[ 莫等,2018 ]的手工制作的特征提取器。
两个数据集用于训练、验证和测试所有网络,可在 GitHub 此处 和 此处 找到。对两种情况进行了训练、验证和测试;首先,人脸图像处理技术的分类;第二,人脸图像操纵的二元分类(图像是不是假的)。
实验结果表明,AMTENnet 具有较高的检测精度和泛化能力,特别是对于多分类的人脸图像处理技术,检测精度提高了 7.61%。对于二元分类数据集,AMTENnet 有大约 1%的增长,虽然很低,但仍然是更好的。用数字来透视多重分类问题,MISLnet 达到了 74.03%的准确率,而 AMTENnet 达到了 81.13%的准确率。我鼓励读者在这里查看完整的文章https://arxiv.org/abs/2005.04945来检查所有的结果。
结束语
AMTENnet 提高了检测 DeepFakes 的准确性,但也展示了一种方法,如果正确遵循,可以被研究社区复制,并用于推动知识的边界,甚至比 AMTEN 更进一步。我敢肯定,到今年年底,DeepFakes 检测模型的准确性将进一步提高。计算机科学的研究速度比以往任何时候都快,这在很大程度上要归功于脸书人工智能研究所、OpenAI 和 DeepMind 的数据集和研究论文。开源被证明是加速知识扩展的最佳方式。
感谢您的阅读,感谢您对本文的任何反馈。你可以在我的 GitHub 页面上看到我在机器学习方面的公开工作,并随时关注我或通过 LinkedIn 联系我。
利用决策树检测脊柱病变
用 Scikit-learn 库构建决策树
由 Joyce McCown 在 Unsplash 上拍摄的照片
人工智能在医疗保健领域有着巨大的潜力,并且在过去几年中一直在这一领域不断发展。医疗行业正在利用人工智能做出更智能、更准确的决策。机器学习在医疗保健领域的应用非常广泛,从疾病诊断和识别到机器人手术,在大多数情况下都提供了超出人类能力的结果。
在本文中,您将学习如何使用决策树构建二元分类模型来检测脊柱病变患者。这些模型是使用 Python 特别是 Scikit-learn 库构建的;然而,重要的是要强调,许多其他编程语言也有可用于轻松构建分类模型的库(例如 R 和 Java)。
为了便于理解文章的内容,每一节都包含理论介绍和解释性插图。它们支持更容易地理解用于构建和评估分类模型的编程代码。❤️
数据集
本文使用的数据集可在加州大学欧文分校机器学习知识库和中获得,包含六个生物力学特征,用于将患者分为两组 : (1)正常,和(2)异常。类别NO
(正常)包括 100 名没有脊柱疾病的患者,而类别AB
(异常)包括 210 名患有脊柱问题的患者。
https://archive.ics.uci.edu/ml/datasets/Vertebral+Column
开源代码库
这个项目的代码可以从 GitHub 上的 Jupyter 笔记本中获得。
https://github.com/amandaiglesiasmoreno
数据读取和清理
分析的第一步包括使用pandas.read_csv
函数读取和存储 Pandas 数据帧中的数据。在 UCI 存储库中可用的 CSV 文件不包含指示列名的标题,因此它们必须用参数names
手动指定。
数据集包含 6 个独立变量,表示脊柱的生物力学属性:(1) pelvic_incidence
、(2) pelvic_tilt
、(3) lumbar_lordosis_angle
、(4) sacral_slope
、(5) pelvic_radius
、(6) degree_spondylolisthesis
。列class
表示患者是否患有脊柱疾病。
目标是使用所有生物力学特征建立一个模型,以预测脊柱病理的存在。在这个特殊的例子中,我们将只使用决策树算法来构建模型;然而,一个更深思熟虑的分析将包括使用更多的监督学习算法,目的是找到具有更大预测能力的模型。
探索性数据分析
探索性数据分析包括分析数据集的主要特征,通常采用可视化方法和汇总统计。目标是理解数据,发现模式和异常,并在执行进一步评估之前检查假设。
在 EDA 之初,我们希望了解尽可能多的关于数据的信息,这就是pandas.DataFrame.info
方法派上用场的时候。该方法打印数据帧的简明摘要,包括列名及其数据类型、非空值的数量以及数据帧使用的内存量。
如前所示,数据集不包含空值,所有数据类型都是正确的;因此,该数据无需额外修改即可用于分类模型的构建。
在 EDA 过程中,可视化变量的分布以及它们之间的相关性也很有趣。配对图是快速探索这两种事物的简单方法。他们在图的主对角线提供直方图,以检查数值变量的分布。此外,主对角线之外的位置提供了数字变量的散点图,以便于分析它们之间的关系。
作者用 seaborn 生成的图像
查看配对图,我们可以得出结论,当患者有脊柱问题时,属性degree_spondylolisthesis
呈现高值,并且根据直方图,预期该属性在对患者进行分类时具有巨大的重要性。
关于其他属性,低pelvic_radius
的患者更容易出现脊柱异常;然而,当患者是健康的并且没有遭受任何脊柱病变时,其余的属性呈现较低的值。
数据拆分:训练集和测试集
建立模型的第一步是将数据分成两组,通常称为训练和测试集。机器学习算法使用训练集来构建模型。测试集包含不属于学习过程的样本,用于评估模型的性能。使用看不见的数据来评估模型的质量以保证客观的评估是很重要的。
训练集和测试集(图片由作者创建)
首先,我们创建一个变量 X 来存储独立属性(生物力学特征)。类似地,我们创建一个变量 y,只包含目标变量(类)。
然后,我们可以使用sklearn.model_selection
包中的train_test_split
函数来创建训练集和测试集。
默认拆分百分比将 75%的观察值分配给训练集,25%分配给测试集;然而,我们可以使用参数train_size
和test_size
来表示不同的分布。在这种情况下,我们使用默认的分割百分比 75/25。
参数shuffle
决定数据在分割前是否应该洗牌。建议随机分割数据,以确保目标变量的分布在两个数据集(训练和测试集)中相似,这意味着两个数据集都具有代表性。默认情况下,参数shuffle
的值为True
;但是也可以显式指定,表示shuffle=True
。
重要的是要记住,我们每次运行代码得到的结果都是一样的,因为参数random_state
被设置为 40,而不是无(默认值)。该参数控制分割前应用于数据 的混洗将始终相同。
分割数据后,建议检查目标变量是否在两组数据中均匀分布。如下所示,在训练和测试组中,脊柱病变患者的百分比相似(约 65%)。
决策树算法—理论
元素
决策树是基于一系列分层问题的监督机器学习算法。这些问题会把空间分成多个线性空间来预测结果。响应可以是离散的(一类)或连续的(一个实数)。
决策树是由的三个元素组成的:节点、树枝和树叶。节点描述了一个属性的测试条件,而分支代表了基于该测试条件的结果。叶,也称为终端节点,代表决策路径(响应)的终点,与内部节点不同,它们不再进一步分裂。
决策树的组成部分(图片由作者创建)
最后,重要的是要提到决策树算法是递归的,这意味着数据被分成更小的块,直到所有的叶子都是纯的(如果没有实施预修剪技术)。
优点和缺点
与神经网络等其他机器学习算法相比,决策树的主要优势在于获得的模型是高度可解释的。神经网络在准确性方面比决策树取得更好的结果;然而,它们并没有提出一个容易理解的模型。无法解释决策是如何根据网络的权重做出的。相比之下,即使是非技术人员,决策树也很容易可视化和理解。
另一个优势是决策树不需要密集的预处理任务,例如数据的标准化。存在对变量范围高度敏感的机器学习算法,例如 K-means。K-means 受属性比例的影响,因为是一种基于距离的算法。在这种情况下,规范化是非常必要的。如果没有实现,具有更高值的特征将主导学习过程,并且获得的聚类将是错误的。然而,决策树是基于规则而不是距离的。因此,它们不受变量范围的影响,也不需要标准化。此外,当处理分类特征时,决策树不需要创建虚拟变量。
决策树是一种强大易用的机器学习算法;然而,它们也存在缺点。决策树的主要问题是过拟合。决策树对训练集过于敏感,学习数据中存在的噪声。如果训练数据存在大量噪声,则获得的树将非常复杂,难以跟踪。幸运的是,大多数机器学习库中已经实现了两种机制来避免过度拟合:预修剪和后修剪技术。
决策树的优缺点(图片由作者创作)
分割标准
构建决策树有不同的分裂标准。Scikit- learn 库有两个用于测量杂质的标准:(1)基尼指数(默认选项)和(2)熵。
基尼系数标准是基于最小化错误分类的可能性。指数 0 代表完美的分裂,这意味着所有的观察都属于同一个类别。相反,指数 0.5 代表可能的最高紊乱(在二元分类中)。当所有类别的概率都相同时,就会出现这种情况。
基尼指数公式
在二叉决策树中,分裂的基尼指数是通过将子节点(左右)的基尼指数乘以每个节点的案例比例来计算的。选择具有最大基尼系数(父基尼系数减去分割基尼系数)的属性作为分割属性。
熵标准也常用于在构建决策树时分割数据。如下所示,这个标准的范围是从 0 到 1 ,其中 0 代表完美分裂(纯节点),就像基尼标准一样。然而,熵的最大值是 1,而基尼系数的值是 0.5。
熵公式
熵(作者创建的图像)
在每个阶段,数据将根据呈现更多信息增益(父节点的熵减去子节点的加权平均熵)的属性进行拆分。
需要指出的是基尼系数和熵值这两种分割方法哪个更合适并没有确定的规则。事实上,在实践中,两者产生的结果是相似的。
用 Scikit-learn 构建决策树
在这个简短的理论解释之后,是时候使用 Scikit-learn 库构建第一个决策树了。
为此,我们将使用来自tree
模块的DecisionTreeClassifier
类。这个类接受几个参数作为输入(所有参数都有一个默认值)。在这种特殊情况下,我们使用熵作为构建树的分裂方法;因此,我们必须设置参数criterion='entropy'
,因为默认的分割方法是基尼系数。一旦定义了决策树,我们就调用 fit 方法来训练算法。
我们可以用tree
模块中的export_graphviz
函数来可视化得到的决策树。该函数以点格式导出决策树,可被graphviz
库用来生成 png 图像。
具有默认参数的决策树
没有预修剪,树会长到所有的叶子都是纯净的。正如你所观察到的,所有树叶的熵都等于 0。这给出了一个树,其中训练集的所有实例都被正确分类。如果我们不限制树的深度,我们就冒着获得复杂模型的风险,这种模型不能正确地推广到新数据。在接下来的部分中,我们将应用预剪枝方法来降低树的复杂度,从而在不损失性能的情况下获得更加简单的决策树。
特征重要性
特征重要性表示特征在区分不同类别时的相关程度。在 Scikit-learn 中,我们可以用feature_importances_
属性获得每个特性的相对重要性。特征的重要性计算为该特征带来的杂质的(标准化)总减少量。
数字越大,特性的重要性越大。如上所示,当预测患者是否有脊柱问题时,属性degree_spondylolisthesis
表现出更高的重要性。
混淆矩阵
混淆矩阵,也称为误差矩阵,用于通过检查正确和错误分类的观察值数量来评估机器学习模型的性能。矩阵的每一列包含预测类别,而每一行代表实际类别,反之亦然。在完美的分类中,除了对角线之外,混淆矩阵将全为零。主对角线之外的所有元素代表错误分类。重要的是要记住,混淆矩阵允许我们观察错误分类的模式(错误分类的类别和程度)。
在二元分类问题中,混淆矩阵是一个由 4 个元素组成的 2 乘 2 矩阵:
- TP(真阳性):被正确归类为患病的脊柱问题患者人数。
- TN(真阴性):被正确分类为健康的无病变患者的数量。
- FP(假阳性):被错误归类为患病的健康患者人数。
- FN(假阴性):被误分类为健康的脊柱疾病患者数量。
作者创造的形象
既然模型已经训练好了,是时候使用测试集来评估它的性能了。首先,我们使用之前的模型(决策树)来预测测试数据的类别标签(使用predict
方法)。然后,我们使用来自sklearn.metrics
包的confusion_matrix
函数构建混淆矩阵,以检查哪些观察值被正确分类。输出是一个 NumPy 数组,其中行表示真实值而列表示预测类。
如上所示,测试数据的 65 个观察值被模型正确分类(45 个真阳性和 20 个真阴性)。相反,我们可以观察到 13 个错误分类(7 个假阴性和 6 个假阳性)。
评估指标
评估模型的质量是机器学习过程的基本部分。最常用的绩效评估指标基于混淆矩阵的元素进行计算。
- 准确性:表示预测被正确分类的比例。准确性是最常用的评估指标;但是,请务必记住,在处理不平衡的数据集时,准确性可能会产生误导。
- 灵敏度:表示被识别为阳性样本(患病患者)的比例。
- 特异性:表示阴性样本(健康患者)的比例。
- 精度:它代表实际正确的正面预测的比例。
我们可以使用混淆矩阵的数字来手动计算评估指标。或者,Scikit-learn 已经实现了函数classification_report
,该函数为提供了关键评估指标的摘要。分类报告包含每个类别达到的精度、灵敏度、f1 值和支持度(样本数)。
如上所示,我们获得了 0.87 (45/(45+7))的灵敏度和 0.77 (20/(20+6))的特异性。所获得的模型更准确地预测患有脊柱病变的患者。这不应该让我们感到惊讶,因为决策树通常偏向于有更多观察的类(在这个例子中是AB
)。
您可能已经注意到,之前的总结不包含分类的准确性。然而,这可以使用metrics
模块中的函数accuracy_score
轻松计算。
模型做出正确预测的比例为 83.33%。
预修剪决策树
决策树容易过拟合,为此,通常应用修剪技术来防止树过拟合数据中的噪声。这些技术可以分为两组:
- 预修剪:这种方法根据给定的条件阻止树继续生长。
- 后期修剪:树长到所有的叶子都是纯净的。然后,不重要的分支被转换成叶子。
下面的预剪枝方法可以应用于DecisionTreeClassifier
构造函数,防止树继续生长:
- 用参数
max_depth
限制树的最大深度。此参数采用缺省值 None,这意味着树将一直增长,直到所有训练数据的观察结果都被正确分类。注意该参数的限制是很重要的,因为低值容易产生拟合不足的模型(过于笼统)。 - 用参数
max_leaf_nodes
限制最大叶片数。此参数设置决策树可以拥有的最终节点的最大数量。默认情况下,它的值为 None。 - 用参数
min_samples_split
确定一个节点中继续分裂的最小观察次数。该参数采用默认值 2,因为具有两个样本的节点可以被分成两个节点(每个节点具有一个样本)。
下面的代码创建了一个带有预修剪的决策树模型。在这种情况下,决策树的最大层数被设置为 8。8 轮吐槽后决策树就要停止生长了。由于这个原因,期望一个比以前简单得多的树,因为没有预修剪,决策树的层数超过 8。
如下所示,带有预修剪的评估指标与之前获得的非常相似。模型做出正确预测的比例为 82.05%。和以前一样,决策树更准确地预测脊柱病变患者(灵敏度高于特异性)。
但是,带有预修剪的决策树更简单,也更容易理解,如下图所示。
带有预剪枝的决策树
在这种情况下,决策树并不完全符合训练数据。有些树叶的熵大于 0。因此,得到的树比前面的树更容易解释。
预剪枝方法比后剪枝技术更有效(时间复杂度更低),因为不需要生成整个树,然后删除不能为模型提供更高精度的分支。然而,正如您可能已经想到的,预修剪方法有一个缺点。通常很难精确估计何时停止树木的生长。
加权决策树
在一些分类问题中,存在比其他更严重的特定错误。在医学诊断问题中,通常更糟糕的是犯下假阴性错误分类,因为这表示将患者诊断为健康,而实际上她/他患有疾病(在这种情况下是脊柱问题)。
成本矩阵允许克服这个问题,更严厉地针对代价高昂的误分类(在这种情况下是假阴性)。它基本上使一些错误分类比其他分类更昂贵。成本矩阵是 n*n 矩阵(n 等于类的数量),其中主对角线的所有元素都等于零,因为它们代表正确的分类(真阳性和真阴性)。其余元素将大于零,其中一个非对角线权重大于另一个,以便更严重地惩罚一种类型的错误分类(在这种情况下为假阴性)。
我们可以通过参数class_weight
在 Scikit-learn 中实现成本敏感决策树。该参数是一个字典,其中键是类标签,而值是成本矩阵的权重。
在构建决策树时,算法将考虑错误成本而不是信息增益。在下面的例子中,假阴性的误分类成本比假阳性的误分类成本大 4 倍。
因此,加权决策树比以前的模型呈现出更高的灵敏度(90%) 。在这种情况下,我们可以更有效地检测出患病的患者,只有 5 例假阴性。消极的一面是特异性的降低。现在,我们检测出表现较差的健康患者。
该模型甚至更偏向于具有更多观察值的类别(AB
-具有脊柱病理的患者),这与我们想要以更高的准确度预测的类别相匹配。重要的是要记住,在医疗保健领域,假阴性比假阳性要糟糕得多。告诉病人健康可能会危及生命,因为他们无法接受所需的治疗。
选择超参数—网格搜索
超参数的选择会极大地影响机器学习模型的性能。网格搜索是寻找最佳超参数的常用方法。使用这种技术,我们分析超参数的所有组合,并最终选择性能最佳的组合。我们可以使用sklearn.model_selection
包中的GridSearchCV
类在 Scikit-learn 中轻松实现网格搜索。
首先,我们使用字典(grid_parameters
)指定参数值集,其中键是超参数,而值是我们想要评估的选项集。然后,我们定义了用于尝试不同参数组合的GridSearchCV
对象。该类将以下参数(以及其他默认值)作为输入:
- 估计器:用于超参数调整的机器学习算法。
- param_grid: 包含超参数名称和值的字典。
- 评分:我们希望最大化的绩效策略。
拟合网格对象后,我们可以使用best_params_
属性获得最佳参数。在这种特殊情况下,我们希望最大化灵敏度(回忆分数),因为我们希望专注于正确预测患者的病理状态。我们不想把任何生病的病人送回家。
最后,我们使用GridSearchCV
对象使用最佳参数进行预测({'class_weight': {'AB': 4,' NO': 1},' criterion': 'gini ',' max_depth': 3})。如上所示,该模型的灵敏度为 96% (远大于之前获得的灵敏度)。然而,该模型对无脊柱病变患者的预测准确性较低(特异性为 54%)。
网格搜索是一种广泛使用的超参数调整方法;然而,这种技术有一个主要的缺点——时间复杂性。为每个超参数组合构建一个模型(决策树)(在本例中为 293=54 个模型)。如果超参数和值的数量非常大,那么要评估的模型数量也会相应增加。
重要考虑因素——随机性
在 Scikit-learn 中,当两个分裂并列时,DecisionTreeClassifier
将随机选择一个特性,这意味着它们的改进标准是相同的。这就是为什么不同的代码运行可能提供不同的结果。因此,如果您重新运行代码并获得与本文中提供的结果不同的结果,请不要担心。为了保证每次代码运行时都有相同的结果,我们应该使用random_seed
参数设置一个随机种子(在初始化DecisionTreeClassifier
构造函数时)。
用 Scikit-learn 构建分类模型是一个简单明了的过程。这是因为已经实现了大量的算法和评估指标,使得构建分类模型的过程非常顺利。决策树不提供深度学习算法所提供的准确性;然而,它们很容易被非技术用户理解。所获得的结果可以帮助医生和研究人员了解哪些特征对脊柱病理影响最大,从而提高他们的检测能力。
感谢你阅读❤️
阿曼达·伊格莱西亚斯
具有张量流的检测器-分类器神经网络结构
当引入一类新的可检测对象时,构建灵活且易于重新训练的对象检测模型。
在训练和部署对象检测神经网络模型中,一个永远具有挑战性的问题是——如何向已经训练好的模型中添加一个新类?通常,这项任务会变得非常艰巨,包括用扩展数据集重新训练整个模型。对象检测模型的训练可能需要很多小时,如果不是几天的话,更不用说用我们想要引入的新类来标记额外的例子所需的时间了。如果那些新的物体也出现在已经被标记的图像中呢?我们将不得不回去审查他们所有人!
也许有更好的方法来解决这个问题。我想向你展示我为我们的客户想出的解决方案。由于显而易见的原因,我无法提供确切的解决方案。尽管如此,我还是要解释该解决方案中使用的架构,以及它如何在不同的数据集上执行——在本例中为,牛津-IIIT Pet 数据集。
我们将通过 TensorFlow 对象检测 API 来检查对象检测模型的训练,即检测器,并使用该模型为我们的分类模型提取数据。之后我们将训练分类器对猫的品种进行分类。接下来的步骤将集中在这个架构如何帮助我们解决第一句话中提出的问题——向我们的分类器引入另一个类(猫品种)。
我不会在这篇文章中包含太多的代码;为此,请看看我的 GitHub 上的笔记本。您可以随意在自己的环境中运行它,并使用不同的数据集测试该架构。
准备好了吗?我们走吧!
1.检测器—张量流对象检测
资料组
如上所述,选择的数据集是牛津-IIIT Pet 数据集,它包含“37 个类别的 Pet 数据集,每个类别大约有 200 个图像”。这是训练我们模型的大量数据。
该数据集还包含注释,即图片中动物面部的确切位置。我们将构建只检测猫脸的检测器,因此只需要从数据集中过滤猫的品种。此外,让我们拿出三个品种来添加它们,以测试探测器在面对它从未见过的猫品种时的表现。这将有助于我们测试这个模型在多大程度上概括了“猫”是什么的概念。
用于探测器训练的猫品种——这只是一个范例。我删除了二项式名称,因为我意识到在我训练完检测模型后,我的脚本根本不会加载它们。
目标检测模型
对于对象检测,我们将使用 TensorFlow 对象检测(TFOD)。Cat 检测模型基于来自 TensorFlow 检测模型 Zoo 的 EfficientDet D2 模型。
安装完所有必要的库并准备好数据集后,我们就可以开始模型训练了—这可能需要几个小时!然而,这一步只需要做一次,不管图片中发现的是什么品种的猫,猫探测器都应该能够完成它的任务。
保存 TFOD 模型看起来很复杂,但是非常简单——我们运行一个随库一起安装的脚本,选择输入类型——这可以是一个图像张量,或者一个 B64 编码的字符串——指向我们的配置文件、检查点目录,最后是输出目录。
查看笔记本,了解培训过程的更详细说明。
将已训练的 TFOD 模型另存为。pb 模型文件。
我们已经训练好了模型,现在让我们测试一张有很多猫的图片!
啊哦…这看起来不太对。如果图片中有四只猫,为什么模型只检测到一只?
原因很简单——我们的数据集每张图像只有一个注释,所以这也是我们的模型要做的——每张图像只检测一只猫。其他任何东西——即使是一只猫——都被赋予很低的可信度;因此,当我们将置信度阈值设置为 50%时,它不会传到我们的输出中。
让我们来看看模型是如何对上图中的作物进行处理的:
好多了!猫探测器成功地探测到了除一种作物以外的所有作物中的猫。我们可以预期,随着更多的训练时间和/或更多的数据,这个模型会表现得更好,但我们现在拥有的是好的——这个模型学会了什么是猫!
但是,如果我们的数据集内的图像中猫脸的位置已经是已知的,我们为什么还需要一个检测器模型呢?有了这个模型,我们可以从任何图像中创建猫脸的剪影,只要有一个模型可以识别的猫脸。此外,最好根据将要用于分类的数据来训练分类器,这些数据是从检测器输出得到的作物。最后但同样重要的是,分类器可以只专注于分析猫的面部,而不需要处理大得多的图像,在某些情况下,这可能会导致重要信息的丢失,实际上是一个错误的分类。
2.分类器——张量流迁移学习
红阿比 _10.jpg ->根据探测器输出进行裁剪。
现在,我们可以使用检测器来检查 cat 数据集,以获得用于训练分类器的剪切片段。目前,只有我们用来训练检测模型的猫品种。
这也是评估探测器性能的好地方!
Breeds detecting network never seen before:
Breed: Russian Blue number of files:191
Breed: British Shorthair number of files:185
Breed: Maine Coon number of files:200
Breed: Persian number of files:160
Breed: Egyptian Mau number of files:180
Breed: Ragdoll number of files:183Breeds used for training:
Breed: Abyssinian number of files:190
Breed: Bengal number of files:176
Breed: Birman number of files:196
Breed: Bombay number of files:176
Breed: Sphynx number of files:197
Breed: Siamese number of files:199
假设每个品种有大约 200 个例子,我们可以看到我们的召回率在每个类的 85–90%以上。请记住,探测器是在所选品种数据的一个小子集上训练的,因为缺乏,我们不能使用所有的图像。xml 注释文件。尽管如此,用于训练的品种和其他品种在召回率上没有任何显著差异。这意味着检测器表现非常好,并且理解“猫”通常是什么。
有了这些剪枝——我们最终可以使用迁移学习来训练我们的分类器。这样我们节省了很多时间——在启用 GPU 运行时的 Google Colab 上进行的训练只需要不到 5 分钟就可以达到 85%的准确率!如果我们想要用新的数据和类定期更新我们的分类器,那么如此短的训练时间是极其重要的。
迁移学习可以通过几行代码完成:
用 TensorFlow 迁移学习。
首先,我们加载 Xception 模型,这是一个已经训练好的图像识别模型。我们省略了模型的顶部分类层,以便我们可以添加适合于我们的检测的分类层。我们冻结加载的模型,以便它不会受到训练的影响——它已经被训练从图像中提取重要特征,这是一项需要花费最多时间和精力来完成的任务。有了迁移学习,我们就不需要再这样做了;我们只训练——在这种情况下——6 层,而不是构建 Xception 模型的 132 层。
6 类训练的训练集的准确性和损失。
6 类训练验证集的准确性和损失。
在训练集上的最终准确率为 82.19% ,在验证集上的最终准确率为 85.02% 。训练持续了 20 个时期——增加这个数字也可以增加模型的最终精度。每个时期的时间是 6 秒+验证阶段的几秒。
3.添加新类别—“未定义/未知”
一点理论
我们的分类器只对 6 种猫进行过训练。现在,如果我们给它看一个它没有训练过的猫品种的面部剪影会怎么样?它必须将其归类为这 6 个类别中的一个,无论它是哪一个类别,这种分类都是错误的。
此外——如果我们的探测器输出的是甚至不是猫的东西的剪影呢?同样,我们的分类器仍然会将它归类为这 6 个品种中的一个。
这就是为什么我们引入一个否定类,或者一个伞类来收集我们的检测器输出的所有不属于我们想要分类器学习的任何类的东西。
在猫的例子中,检测器没有输出任何我们可以归类到“未定义/未知”组的东西,但是我们可以通过在从互联网上拍摄的猫的不同图像上运行检测器来模拟它。
为了更好地理解这个问题,让我们考虑这个架构的一个不同的应用——汽车品牌标志检测。在这种情况下,我们可以预期我们的检测器将输出不是汽车标志的剪切部分。根据我们算法中设定的置信度阈值,我们可以控制检测器输出的错误截断程度。但是,请记住,随着置信度阈值的提高,您也牺牲了未达到该阈值的正确检测。我们想要做的是在不牺牲太多精度的情况下,最大化召回。那些参数是什么?
假设你有一张 10 辆车的图片;每辆车的前保险杠上都有一个汽车品牌标志。你的模型检测到 12 个汽车品牌标识,但其中只有 8 个是汽车标识;剩余的 4 个检测结果是挡风玻璃上的一个奇怪的反射,背景中栅栏上的一个模糊的贴纸,人行道上的一个圆形油渍,以及汽车牌照上的一个字母。
召回 —如果您的模型在图片中的 10 个品牌标识中检测到 8 个,则意味着召回率为 80%。
Precision —我们检测到的车标有多少,在本例中,8/12 或 75%。在本例中,精度也是准确度,因为我们只对一个标签进行分类。
现在,我们可以降低置信度阈值来捕捉这两个未被检测到的徽标,但这样一来,精确度就会下降。这是因为,除了这两个新的检测之外,输出中还会包括一些错误的检测,所以最终,我们会得到 20 个剪切部分——10 个汽车品牌标志和 10 个其他东西。100%的召回率,50%的准确率。
50%的阈值是一个很好的中间值,但是您可以根据您的使用情况和检测器的性能以及分类器的性能来增加或减少它。
添加新类别
要添加新的类别,您需要对一组新的图像运行检测过程。在这种情况下——这是我们从必应上下载的猫的图片。如果您想从数据集中引入一个新的猫品种,只需在这些图像上运行检测过程。
如果检测机不仅输出想要分类的对象的剪切图,还输出错误的对象(如车标检测示例中所述),您需要检查整个剪切图集,并首先将它们分配到正确的文件夹中。
回到猫!
在对猫的图片运行检测器后,我们最终得到了 150 个新的剪辑。7 个类别——我们之前提取的 6 个猫品种和一个“未知”类别,该类别包含不属于这 6 个类别中任何一个的猫的图片。
现在,我们可以训练分类器,使它不仅能够对 6 个品种进行分类,还能对其他不属于任何品种的猫进行分类。
用于 6 +未知类训练的训练集的准确性和损失。
6 +未知类训练的验证集的准确性和损失。
经过 20 个历元后,训练集上的准确率为 87.50% ,验证集上的准确率为 76.19% 。这比之前的 6 级训练略低,但未知的级别可能包含已经定义并用于训练的品种,这意味着它们会混淆模型并降低其准确性。
3.所有课程的最终培训
最终培训——12 节课+未知
用于 12 +未知类训练的训练集的准确性和损失。
12 +未知类训练的验证集的准确性和损失。
在 40 个历元之后——比之前的训练长了 20 个历元——模型在训练集上达到了 78.59% 的准确度,在验证集上达到了 74.45% 。
出于好奇,让我们看看没有“未知”课程的培训过程是什么样的:
12 类训练集的准确性和损失。
12 类验证集的准确性和损失。
在训练集上的最终准确率为 86.25%,在验证数据集上的最终准确率为 79.74%。比以前高!未知类别会降低模型的准确性,因为如前所述,它可能包含属于数据集中已定义品种的猫。此外,多一个类意味着更大的数据集,这需要更多的训练时间,甚至可能需要改变模型架构。
4.关键要点
好了,现在我们已经完成了这个过程,并且看到它运行得很好——让我们总结一下这一切吧!
- 该架构由两个神经网络 — 检测器和分类器组成。检测器是对象检测神经网络。这一个我们训练——希望——只有一次。我们训练它只识别一个类,这个类概括了我们想要分类的东西的一般特征——一只猫,一个手机应用程序,一个汽车品牌标志。
- 然后检测器用于提取被检测物体的切口。根据置信度阈值和模型质量,错误检测的数量可能更高或更低。这个想法是捕捉所有正确的类,尽可能少的错误类——高召回率,相当好的精确度。
- 有了所有的剪切图,现在是时候进行一些手工操作了——现在每个提取出来的剪切图都必须放在一个合适的文件夹中。这些是分类器要识别的每个类别的文件夹,一个文件夹存储不属于任何这些类别的所有内容。
- 然后根据这些数据训练一个分类器。值得使用迁移学习来构建架构。这减少了必要的培训时间,使连续学习或增加一个新的类成为一个相对平稳和快速的过程。
- 为了引入新的类别,必须对新的数据重复该过程——提取剪切块,手动分类到适当的类别中,然后重新训练分类器以识别 n+m 个类别,其中 n 是先前类别的数量,m 是新引入的类别的数量。
- 最后,我们的检测过程看起来是这样的:图像进入检测器,检测器输出剪切部分。然后,剪切块被馈送到分类器,以获得每个剪切块的最终分类,从而获得分析图像中所有检测到的对象。
我希望你发现这篇文章很有见地,有助于解决你的机器学习挑战。请随时询问关于方法或代码的问题——无论是在下面还是在 GitHub 上。
楚斯!
利用咖啡豆密度确定咖啡的峰值风味
咖啡数据科学
对其他人的数据重新排序
几周前,我被指向理查德·梅斯顿的网站,在那里他花了几个月的时间探索咖啡豆的密度。他探索用咖啡豆的密度来决定喝咖啡的最佳日期,以及开始使用一袋新咖啡豆的等待时间。
他根据豆子密度优化了其他参数。单次测量有可能为研磨以及其他注射参数(如温度、压力、剂量和输出比)提供一个良好的起点。这种潜力将是咖啡店避免拨号浪费的礼物。
所以我看了一眼他的数据,我觉得有必要重新排序,这样我可以看得更清楚。
背景
理查德的主要想法是,如果一个咖啡烘焙师在他们的袋子上印上咖啡豆的密度,这将消除人们如何烘焙的一些变量。这也将有助于烤烤变化。然后豆子的密度可以告诉你什么时候开始使用豆子,这样你就可以减少浪费。
他目前的网站告诉你如何便宜地测量密度,然后如果你输入到他的计算器,它会给你他使用的参数和日期范围。
他通过将相同的绿豆烘焙到不同的程度来评估不同的豆子密度。然后他试着做了意式浓缩咖啡,加旁路水的意式浓缩咖啡(加热水),然后是加牛奶的意式浓缩咖啡。
数据
我不喜欢搜索豆子的密度,我想看看总体趋势。他建立了一个模型来从他的数据中获得更多的粒度,所以我花了一些时间来收集密度间隔为 0.01 的数据。他的数据值从 0.304 到 0.553 克/毫升。
所有图片由作者提供
首先,我观察了几个趋势。他根据密度修改了一些参数,以给出最佳的拍摄效果。我唯一的批评是,他在整个测试中没有测量总溶解固体(TDS)和提取率(EY),所以这是一个主观的味道测试。他试图通过别人而不是他自己的盲品来减轻一些主观性。
我们可以得到每个密度的日变化范围,然后我们可以把它们画出来。我们可以看“可以”、“好”、“非常好”和“最好”,或者“非常好”和“最好”。趋势是明显的,有趣的是看到越暗的烘焙越快变坏。
我问他是否记录了每密度的研磨设置,他很大方地把数据发给了我。他用的是 Flair Pro 2(45.5 毫米篮筐),他的研磨机是 Kinu M47。根据他的数据,单位密度的最佳研磨有一个稳定的趋势。
建议:
- 注意烤枣,尤其是颜色较深的烤枣。
- 以此为指导,发现自己最喜欢什么。
- 跟踪你的研磨机的豆密度和研磨设置,随着时间的推移,你应该能够有一个更好的研磨设置。
- 买咖啡时要让你得到最大的味道。
我已经开始为我的家庭烘焙记录豆子的密度。到目前为止,他的数据与我对中度烘焙的数据和建议一致,中度烘焙在烘焙后 3 到 5 周达到风味和萃取的顶峰。希望在接下来的一年里,我可以通过在我的数据表中包含 bean density 来获得关于这个主题的更好的数据。
如果你愿意,可以在 Twitter 和 YouTube 上关注我,我会在那里发布不同机器上的浓缩咖啡照片和浓缩咖啡相关的视频。你也可以在 LinkedIn 上找到我。也可以在中关注我。
我的进一步阅读:
确定准时交货的 SLA:描述性统计的应用
如何解释商业决策中的百分位数
作为一名在电商平台负责物流服务的数据分析师,我们需要确定让客户对配送服务满意的方法。其中一个重要因素是确保准时交货。本着“让它更好”的精神,我们希望消除送货过程中的漏洞,并最大限度地提高卖家对途中发生的事情的了解。几个月前,我们开始与第三方物流(3PL)合作伙伴就寻找司机的持续时间确定服务水平协议(SLA)。
卖家等待司机取包裹的旅程(图片由作者提供)
在实施之前,我们的卖家只能确定司机多久能到卖家地址取包裹(SLA 取货)。然而,这两个过程之间仍然存在漏洞,3PL 的系统需要先指派一名司机,然后再指示司机去取包裹。因此,为了确保提货过程顺利,我们希望通过确定 SLA 寻找驱动程序应该多长时间来跟踪寻找驱动程序的活动。为了实现这一点,我们接着采用了描述性分析方法。
为什么使用描述性分析?
- 我们希望根据历史条件来确定 SLA。
- 假设历史条件可以代表未来的条件(例如,使用相同的第三方物流合作伙伴,具有恒定的服务质量,相同的驱动能力)。
- 历史性能数据显示了某一段时间内的固定模式。
针对当前提升预测分析能力的分析趋势,我们认为 描述性分析经常被低估。 描述性分析在解决现实生活中的问题时仍然非常得心应手,尤其是在有限的时间(和成本)内。因此,我们需要在实施分析时考虑周全,以便既有效又高效,同时还能理解何时使用某种方法。
我们应该何时使用预测分析?
- 我们希望模拟几个变量发生变化的未来情况(例如,使用具有不同特征的不同第三方物流合作伙伴,针对不同的客户群)。
- 我们正在检测当前性能的变化,并希望预测未来的趋势。
步骤 1:确定指标
在确定 SLA 发现驱动因素时,我们要分析 2 个指标:
- 从卖家要求提货到指派司机的持续时间(从 RPU 到指派司机)。
- 从卖方请求提货到创建 CS 票证的持续时间(从 RPU 到票证创建)。
第一个指标用于分析我们的第三方物流合作伙伴的表现:他们为我们的卖家分配驱动程序的系统能力有多快。另一方面,第二个指标用于分析卖家对我们服务的期望:他们愿意等多久才提交投诉单。通过分析这两个指标,我们旨在优化双方利益相关者的体验。
步骤 2:探索性数据分析(EDA)
在深入研究数据集之前,我们需要先通过执行 EDA 来了解数据的分布。我们想知道我们应该通过哪个集中趋势的度量(平均值或中值)进行进一步的分析。通过将两个指标可视化为箱线图,我们可以了解数据是否有偏差。
使用箱线图检查数据分布(图片由作者提供)
从上面的箱线图中,我们发现分配给驾驶员的RPU和分配给票证创建者的RPU数据都非常右偏,这意味着由于异常值,平均值可能比中值高得多。因此,我们通过分析中值和百分位值来继续分析。
步骤 3:分析百分位数
在 EDA 之后,我们将分配给驾驶员的 RPU 和分配给票证创建者的 RPU 的持续时间的百分点分解为第 10、25、50、75 和 90 个百分点。
测量的每个持续时间的百分位数(虚拟数据)
上面的持续时间表可以解释为:
- 如果我们将 SLA 设置为 51 分钟(根据第 90 百分位的 RPU 分配给驾驶员的持续时间),那么在违反 SLA 之前,大约有 90%的接送请求可以被成功分配给驾驶员。但是,我们只能部分解决与查找驱动程序问题相关的 CS 票证,因为仍有 10–25%的票证将在 SLA 之前创建。
- 如果我们将 SLA 调整为 26 分钟,那么在 SLA 被违反之前只有 50%的接送请求可以被成功分配给司机,而CS 票的数量可以从当前票数量的 10–25%减少到< 10% 。
步骤 4:为第三方物流和销售商确定最佳 SLA
通过分析,我们认识到在确定 SLA 时,在 3PL 分配的驱动程序系统容量和销售者创建的 CS 票证之间有一个折衷。所以我们可能需要在分析中再加入一个因素,比如一个月创造多少接机单和 CS 票。我们还需要更深入地了解什么是卖方迟迟找不到驱动系统的后果。
如果创建的票证数量不太高,我们可以考虑延长 SLA 查找驱动程序的时间。相反,如果票证经常被创建并带有强烈的抱怨,我们需要考虑缩短 SLA。考虑到第三方物流方和卖方都是我们有价值的客户,应该确定最佳 SLA,以提供最佳体验并最大限度地提高我们客户的满意度。
通过确定 SLA 寻找驱动因素,现在我们的销售人员不仅可以了解他们的订单何时会被提货,还可以知道是否为他们分配了驱动因素。如果 SLA 已经被违反,并且系统还没有为它们找到驱动程序,卖方也有能力重试提货过程。随着这一新功能的开发,卖家有望获得更好的体验,更好地控制订单的按时交付。
贝叶斯线性回归中特征重要性的确定
在这里,我们研究一种方法来确定用于训练贝叶斯线性回归模型的最佳特征。
在本文中,我将介绍一种确定多元贝叶斯线性回归模型中预测变量真正重要性的方法。该方法分析不同预测变量之间的残差,以确定哪个特征向我们的后验分布近似值添加了最多信息。这可能看起来太复杂了,在这一点上难以理解,但是当您阅读本文时,我将解释这是一种非常简单和直观的方法,用于理解在创建贝叶斯回归模型时选择哪些特性。
引入二次近似( quap )
二次近似,也称为拉普拉斯近似,是一种确定后验分布的最大后验概率(MAP) 估计的方法。基本上,贝叶斯统计是一个统计领域,它基于数据本身更新其关于收集数据的总体的信念。在看到任何数据之前,您首先通过估计先验来初始化模型,这是您对总体的初步了解。
**> library(rethinking)
> data("WaffleDivorce")
> d <- WaffleDivorce
> head(d[,c(1,4,5,7)])**
Location MedianAgeMarriage Marriage Divorce
1 Alabama 25.3 20.2 12.7
2 Alaska 25.2 26.0 12.5
3 Arizona 25.8 20.3 10.8
4 Arkansas 24.3 26.4 13.5
5 California 26.8 19.1 8.0
6 Colorado 25.7 23.5 11.6
出于解释的目的,我将使用 r 中的https://www.rdocumentation.org/packages/rethinking/versions/1.59包中的waffle development数据集。在该数据集中,我们希望创建一个回归模型来估计美国各州的离婚率。我们将找到一条基于一些预测变量的预测回归线。首先,假设我们想根据美国结婚年龄的中位数来模拟离婚率。假设人们结婚越早,婚姻以离婚告终的可能性就越大是有道理的。我们想用现有的数据来检验这个假设。将你的变量标准化,使它们之间更加兼容,这是一个很好的做法。为此,您可以使用反思包中简单的标准化命令。
这是我们将要建模的最终数据集,其中 A 代表标准化结婚年龄中位数, M 代表标准化结婚率, D 代表标准化离婚率。
***> d$A <- standardize(d$MedianAgeMarriage)
> d$M <- standardize(d$Marriage)
> d$D <- standardize(d$Divorce)
> head(d[,c(1,14,15,16)])**
Location A M D
1 Alabama -0.6062895 0.02264406 1.6542053
2 Alaska -0.6866993 1.54980162 1.5443643
3 Arizona -0.2042408 0.04897436 0.6107159
4 Arkansas -1.4103870 1.65512283 2.0935693
5 California 0.5998567 -0.26698927 -0.9270579
6 Colorado -0.2846505 0.89154405 1.0500799*
现在,我们使用 r 中的重新思考包中的 quap 函数制作第一个模型。
*model1 <- quap(
alist(
D ~ dnorm(mu, sigma),
mu <- a + bA *A,
a ~ dnorm(0,0.2),
bA ~ dnorm(0,0.5),
sigma ~ dexp(1)
),data = d
)*
该模型使用您提供的先验知识,根据您当前的信念创建许多回归线,然后使用提供的数据排除可能性较小的回归线,以确定这些线可能位于的真实区间。为了更好地理解这一点,这里有一张图片来说明我们之前对中位结婚年龄离婚率分布的理解,以及在查看数据后我们对分布的最新看法。我还绘制了结婚率的单变量回归作为预测变量:以说明两个不同的预测变量如何与不同州的离婚率相关联。
左边:先验预测分布,说明我们在看到数据之前对两个变量之间关系的信念。右边:*后验回归线,说明我们在查看数据后对两个变量之间关系的最新看法。*
多元回归模型
在这里,您的直觉可能会告诉您创建一个模型,用预测变量(结婚年龄和结婚率)来预测离婚率,因为每个预测变量和离婚率之间都有很强的相关性。
此时要问的问题是,创建一个带有两个预测变量的回归模型是否有优势,这两个预测变量都是 M 和 A 。这种模型被称为多元回归模型。问这个问题之所以重要,是因为预测变量之一和离婚率之间可能没有实际的因果关系。
结婚率完全有可能取决于结婚年龄;因此,当结婚年龄改变时,结婚率和离婚率都会随之改变。这最终给我们的印象是,结婚率和离婚率之间有因果关系,因为它们是相关的。我们需要问自己的是,在我们已经使用结婚年龄中位数来模拟离婚率之后,知道结婚率是否会给我们带来任何关于离婚率的额外信息。
为了理解为什么这很重要,我建立了一个双变量回归模型,以结婚率和结婚年龄中位数作为预测变量。在下图中,我展示了一旦我们向模型中添加额外的预测变量,我们对参数值 bM 和 bA 的置信度是如何变化的。
模型 1 仅使用中位年龄作为预测变量,模型 2 仅使用结婚率作为预测变量,模型 3 同时使用结婚率和中位结婚年龄作为预测变量。该图显示了所有三个模型中【bM】(结婚率系数)和【bA】*(结婚年龄系数中位数)的变化。*
在这一点上,重要的是要注意,如果只对结婚率建模导致结婚率和离婚率之间的良好相关性,一旦我们知道结婚年龄,我们对结婚率预测变量系数的信心就会下降。在这里,我们有必要确定结婚率是否与离婚率有因果关系,或者它只是通过依赖与离婚率相同的混杂因素来伪装一种关系。我们如何从这幅图中看出这一点?我们可以看到,即使将结婚率作为一个预测变量,结婚年龄系数与离婚率的关系仍然完全是负的,但这与我们在结婚率系数的变化中看到的并不相反。
当然,这不是确定预测变量重要性的好方法,因此我们查看残差图来确定模型中预测变量的真正重要性。
预测残差图
预测残差图是一种确定模型中某个要素的真正重要性的方法,它告诉您在已知第一个预测变量后,从第二个预测变量中获得了多少额外的**信息。为了做到这一点,我们用另一个变量作为条件变量,然后测量这个条件变量与离婚率的关系。我们该怎么做?我们在两个预测变量之间创建另一个二次近似模型。**
*model4 <- quap(
alist(
M ~ dnorm(mu, sigma),
mu <- a + bA * A,
a ~ dnorm(0,0.2),
bA ~ dnorm(0, 0.5),
sigma ~ dexp(1)
), data = d
)
mu <- link(model4)
mu_mean <- apply(mu, 2, mean)
mu_resid <- d$M - mu_mean
plot(d$D ~ mu_resid, col = col.alpha(rangi2, 1),
ylab = 'Divorce rate standardized',
xlab = 'Marriage rate conditioned on age of marriage')
abline(v = 0)
abline(b = cor(mu_resid, d$D), a= 0)*
在这个代码块中,我们在两个预测变量之间创建了一个模型。模型 4* 使用结婚年龄中位数来预测结婚率。接下来,我们提取每个州的结婚率值,如使用重新思考包中的 链接 函数预测的结婚年龄中位数。最后,我们从实际结婚率中减去预测结婚率。这给了我们结婚率的残差,或者换句话说,这给了我们每个州的结婚率,在以结婚年龄中位数为条件后。此时, mu_resid 包含了我们从结婚率预测变量中获得的所有附加信息。*
现在我们看到了这些条件结婚率和离婚率之间的关系,我们可以检查在我们的回归模型中哪个特征更重要。在下图中,我也画出了另一种选择,在右边,结婚年龄取决于结婚率。
左一:以结婚年龄为条件后,离婚率与结婚率的关系图。右边一个*:以结婚率为条件后,离婚率与结婚年龄的关系图*
我们可以在图中清楚地看到,在我们已经使用结婚年龄作为预测变量后,结婚率对预测没有什么重要性。我们如何看待这一点?在我们将结婚率以结婚年龄为条件后,结婚率和离婚率之间几乎没有关联。这也说明结婚率几乎完全取决于结婚年龄,想想也有道理。如果结婚年龄的中位数下降,我们将会看到结婚率上升,因为年轻人比老年人多。这总结了一种可以用来确定贝叶斯线性回归模型中某个特性的重要性的方法。本文使用的所有代码都可以在这里找到。
参考
[1] Richard McElreath,用 R 和 Stan (2020)中的例子进行统计反思
确定最佳口袋妖怪队的口袋妖怪灿烂的钻石和闪耀的珍珠与纸浆
实践教程
由 Thimo Pedersen 在 Unsplash 上拍摄的照片
像许多 21 世纪初的孩子一样,我喜欢玩口袋妖怪长大。捕捉那些像动物一样的生物,并训练它们与其他口袋妖怪战斗,形成了我童年最美好的回忆。随着经典的口袋妖怪钻石和珍珠视频游戏、灿烂的钻石和闪亮的珍珠的重拍即将于下个月推出,我认为使用我的数据科学知识来确定口袋妖怪的最佳团队将会很有趣。
底漆
首先,给那些不熟悉口袋妖怪系列的人一点背景知识。口袋妖怪是像动物一样的生物,可以被捕获并训练来与其他口袋妖怪战斗。在每个游戏开始时,你可以在三个初始口袋妖怪中进行选择,即所谓的“新手口袋妖怪”。在钻石和珍珠游戏中,这些是草苗龟,草型乌龟口袋妖怪;火苗猴,火型黑猩猩口袋妖怪;还有波加曼,一只水型企鹅口袋妖怪。他们和许多其他口袋妖怪可以经历两次称为进化的蜕变,使他们变得更强大,在某些情况下获得额外的类型。
《钻石与珍珠》中的三个初级口袋妖怪,关于它们的一些简单的游戏信息,以及它们在下面的发展。作者图片
大多数游戏的目标是训练最多六只口袋妖怪的队伍,并成为该地区最强的训练者。为了实现这个目标,你必须与八个被称为健身房领导的老板战斗,他们每个人都专注于口袋妖怪的特定元素或类型。比如口袋妖怪吉祥物皮卡丘就是电动型的口袋妖怪。这意味着它对水型口袋妖怪有天然优势,但对地型口袋妖怪有弱点。为了占上风,你想用口袋妖怪强对抗你面对的类型(把它想象成石头、布、剪刀的高级版本)。在你击败所有八个健身房领导后,你可以在口袋妖怪联盟中对抗精英四人组。他们被认为是这个国家最强的训练者,就像健身房的领导一样,专门训练一种特殊类型的口袋妖怪。精英四人组之后是与口袋妖怪冠军的最后一场 boss 战,在钻石与珍珠中,她是辛西娅:她使用了无数不同类型的口袋妖怪,被认为是整个系列中最难的 boss 战之一。有 18 种类型,你一次最多可以使用 6 个口袋妖怪。在这种情况下,在辛西娅之前,你能召集的最好的口袋妖怪团队是什么?
资料组
我将使用两个数据集,都来自 Kaggle。一个是截至 2013 年的所有口袋妖怪的列表(这完全符合我们的目的,因为最初的钻石和珍珠游戏是在 2006 年发布的),另一个是不同类型比赛的矩阵。我们将使用 Python 中的 pandas 来读取数据:
让我们来看看最初的几个条目:
poke_df.head()
作者图片
每个条目都给出了一个口袋妖怪的名字,它在口袋妖怪国家索引(在游戏中称为 Pokedex)中的编号,它的主要类型(类型 1)和任何次要类型(类型 2,如果没有次要类型,则使用 NaN),总统计数据(total)和每个统计数据的单独细分(总的来说,这些是表明口袋妖怪有多强的指标),它是在哪一代引入的,以及它是否是一个传说中的口袋妖怪。对于那些不熟悉术语的人来说,世代指的是口袋妖怪首次亮相的游戏集。比如皮卡丘最早出现在口袋妖怪原版游戏中:口袋妖怪红蓝黄。因此,它被认为是第一代口袋妖怪。口袋妖怪钻石和珍珠(以及更新版本白金)代表了该系列发布的第四套游戏,因此其中引入的任何口袋妖怪都被视为第四代口袋妖怪。游戏的当前周期是第 8 代,因此即将推出的 Diamond 和 Pearl 的复刻版将是那一代的一部分(在本文的持续时间内,我在提到原版游戏时将使用缩写 DPPt,而复刻版将使用 BDSP)。另一列,传奇,代表稀有和非常强大的口袋妖怪,带有游戏中的神话,通常在游戏结束时或主要故事情节完成后捕捉。这些口袋妖怪通常有更高的属性,自动赋予任何使用它们的训练者不公平的优势。为了平衡起见,我们将这些口袋妖怪排除在我们的分析之外。你可能也注意到了口袋妖怪 Venusaur 有一个二级入口,Mega Venusaur。这是在第 6 代游戏中引入的游戏机制 Mega Evolution 的一部分。它不存在于 BDSP 中,所以这些条目也将被忽略。
我们希望在主要故事情节中过滤只存在于 DPPt Pokedex 中的口袋妖怪(我假设在翻拍中仍然会存在)。这包括第四代引入的 107 个口袋妖怪,以及前三代口袋妖怪中的 110 个其他口袋妖怪,总共 217 个。
我们将首先重新格式化一些列名,使它们更容易索引,比如将#列重命名为 number 的常见缩写,No。
poke_df = poke_df.rename(columns={"#": "No"})
poke_df.head()
作者图片
接下来,我们将在一个列表中存储第 4 代中引入的口袋妖怪的索引,编号为 387 到 493
# Note: Sinnoh is the fictional region these games take place in
sinnoh = list(range(387,494))
我们将同样存储游戏中可用的前代口袋妖怪的索引。不幸的是,没有简单的方法从我查看的数据集或任何其他数据集提取这些口袋妖怪的指数,所以我手动记录了来自 Sinnoh Pokedex 中一个著名的口袋妖怪数据库的单个指数:
sinnoh_expat = [63, 64, 65, 129, 130, 315, 41, 42, 169, 74, 75, 76,
95, 208, 66, 67, 68, 54, 55, 265, 266, 267, 268, 269,
214, 190, 92, 93, 94, 200, 198, 118, 119, 339, 340,
358, 307, 308, 77, 78, 185, 122, 113, 242, 173, 35, 36,
172, 25, 26, 163, 164, 143, 201, 194, 195, 278, 279, 203,
298, 183, 184, 223, 224, 72, 73, 349, 350, 226, 215, 207,
299, 280, 281, 282, 108, 133, 134, 135, 136, 196, 197,
333, 334, 175, 176, 228, 229, 81, 82, 114, 193, 357, 111,
112, 355, 356, 137, 233, 123, 212, 239, 125, 240, 126,
220, 221, 361, 362, 359]
通过len(sinnoh_expat)
快速确认口袋妖怪的预期数量,得出110
,我们连接这些列表并相应地过滤我们的 Pokedex:
作者图片
我们看到我们有一些重复的条目(例如,条目 492,洁咪),我们可以使用drop_duplicates()
功能删除它们(这也删除了任何额外的表单,例如大型进化):
sinnoh_dex = sinnoh_df.drop_duplicates(subset=['No'])
sinnoh_dex
作者图片
厉害!我们已经从 Sinnoh Pokedex 得到了所有 217 个口袋妖怪。我们的数据几乎可以进行分析了。现在我们只需要移除所有传说中的口袋妖怪。幸运的是,因为我们有一个布尔变量用于该类别,所以这是一个非常简单的过滤过程。
sinnoh_dex = sinnoh_dex[sinnoh_dex.Legendary == False]
sinnoh_dex
作者图片
现在我们的数据已经准备好进行分析,我们需要准备我们的类型图矩阵。这是我下载的另一个数据集,显示了不同类型的匹配:
作者图片
让我们来看一下这个图表,行代表我们正在使用的口袋妖怪类型,列代表对手队的类型。2 表示某一类型的口袋妖怪在行中比在列中的对手口袋妖怪更有优势。举个例子,假设电动类型的皮卡丘对一只口袋妖怪发动雷电。如果它用那个动作攻击一个水型口袋妖怪,那它是超级有效的,所以我们用 2 来量化这个效果;相比之下,如果它攻击龙型口袋妖怪,它不会非常有效,所以基线效应将减半,因此,我们在矩阵中的条目中有 0.5。造成中性伤害的招式有 1(例如皮卡丘攻击火系口袋妖怪)。对于电气类型需要指出的一点(这也适用于其他一些类型)是接地类型列下的 0。这意味着电动类型的移动对地面类型的口袋妖怪没有任何效果;后者对它们免疫。
关于健身房领导和精英四,有 12 种类型我们要选择:岩石,草,战斗,水,鬼,钢,冰,电,虫,地,火,心灵,所以我们将相应地过滤我们的类型图表的列:
作者图片
重新审视我们的目标,我们想要一个由 6 个口袋妖怪组成的团队,他们的类型很强,可以对抗健身房领导和精英四人组。我们还将施加一个约束,即我们的团队中必须有一个首发口袋妖怪(你不选择的其他人在主要故事情节中是无法获得的)。在所有这些约束条件下追求最佳团队,您可能会认为这是一个优化问题。在这些问题中,我们有一个想要优化的目标函数。在这种情况下,我们希望最大限度地表达
在以下约束条件下:
其中 T 是口袋妖怪 i 的总统计数据(第一个数据集中的列总数), P 是指示该口袋妖怪是否在我的团队中的决策变量, S 是起始口袋妖怪(包括它们的进化)的集合, A 是指示口袋妖怪 i 相对于对手口袋妖怪 j (1
为了解决这个优化问题,我们将使用pulp
包,这是一个直观的库,用于使用线性编程来构造和求解这些数学模型。可以通过conda install pulp
安装。
首先,我们将创建向量和矩阵来存储上述变量。
现在,我们可以创建我们的模型。
我们现在可以拉出产生的六个口袋妖怪:
Infernape
Garchomp
Electivire
Dusknoir
Cresselia
Manaphy
所以我们的最佳团队是阴尸(火/战斗)、烈咬陆鲨(龙/地)、埃蒂维尔(电)、夜巨灵(鬼)、克雷色利亚(通灵)和玛纳霏(水)。不同类型的团队。然而,敏锐的口袋妖怪爱好者可能会注意到,克雷色利亚实际上是一个传说中的口袋妖怪,而玛纳霏是一个神话中的口袋妖怪。后一个类别在统计数据和游戏中的知识方面基本上与传奇相同,但它们在历史上更难获得(它们通常通过独占事件提供)。不用说,由于他们的属性更高,他们给了我们不公平的优势,但是我们可以通过添加另一个限制他们使用的约束等式来解决这个问题:
其中 C 和 M 分别指克雷色利亚和玛纳霏。然后,我们可以修改我们的代码,以包括如下限制:
Gyarados
Umbreon
Infernape
Garchomp
Electivire
Togekiss
现在我们有了一个更加平衡的队伍,有了加拉多斯(水/飞行),月精灵(黑暗),阴尸(火/战斗),烈咬陆鲨(龙/地),埃蒂维尔(电)和波克基斯(精灵/飞行)。
从左至右:加拉多斯、月精灵、因弗纳普、烈咬陆鲨、埃蒂维尔和波克基斯。作者图片
这里一个常见的口袋妖怪是阴尸,火苗猴的进化形式,是你可以选择的入门口袋妖怪之一。让我们假设你喜欢企鹅口袋妖怪波加曼,并想围绕它的进化形式 Empoleon 建立一个团队。你会如何调整算法?
我们可以像这样限制《口袋妖怪入门》只包括波加曼一家
其中企鹅是波加曼及其进化家族(波皇子和 Empoleon)的集合。我们可以这样编码:
Snorlax
Empoleon
Garchomp
Electivire
Togekiss
Dusknoir
从左到右:卡比兽(正常)、Empoleon(水/钢)、烈咬陆鲨(龙/地)、Electivire(电)、波克基斯(精灵/飞行)、夜巨灵(幽灵)。作者图片
我们同样可以对草苗龟周围的团队进行调整,草地开始:
Snorlax
Torterra
Garchomp
Magmortar
Togekiss
Dusknoir
从左到右:卡比兽(正常)、土台龟(草/地)、烈咬陆鲨(龙/地)、鸭嘴焰龙(火)、波克基斯(神仙/飞天)、夜巨灵(鬼)。图片作者。
结论
在本文中,我向您展示了如何使用线性编程来确定在 Pokemon Brilliant Diamond 和 Shining Pearl 中使用的最佳团队(假设没有偏离原始 Pokedex)。我们可以进一步优化其他所需的定制(例如,额外的口袋妖怪你可能想使用,像皮卡丘;优化某些属性,如攻击和速度,而不是总属性等)。你也可以将此改编到另一个口袋妖怪游戏中,例如最近的主要系列游戏口袋妖怪剑与盾。编码快乐!
参考资料:
[1]https://www.serebii.net/pokedex-dp/
[3]https://hookedondata.org/pokemon-type-combinations/
如果你是新手,欢迎!如果你喜欢这篇文章,并想充分享受我的其他故事,以及无限制地访问其他媒体作家的故事,请考虑使用下面我的个性化链接成为媒体成员;我会赚取你的一部分会员费,不需要你额外付费:https://medium.com/@jashahir/membership
使用样本硬度确定基于 DNN 的分类器输出的可信度
思想与理论,值得信赖的深度学习
您只需要样品的硬度
可信机器学习中最重要的挑战之一是测量分类器输出的可信度。在关键和安全敏感的任务中,确定分类器输出的信任度是至关重要的。我们使用样本的硬度作为估计分类器输出可信度的新度量。首先,我们需要定义样品的硬度。
由于基于 DNN 的分类器的训练过程是在几个时期中完成的,因此我们可以将分类器训练过程视为一系列分类器,其中最后一个分类器被视为实际使用的最终分类器。假设分类器 f_t 被训练了 m 个时期。分类器 f_t 的训练过程可以表示为如下序列:
其中 f_t^i 是第个时段结束时的分类器 f_t 。在每个时段结束时创建的分类器被称为子分类器,第 i 个子分类器 f_t^i 是在时段 i 结束时创建的分类器。当 f_t^e 是由 f_t^e 分配的标签等于所有后续子类预测标签的第一个子类时,我们说样本 x_i 在时期 e 中被学习。一般来说,随着历元数量的增加,分类器 f_t 的性能会提高。因此,当一个样本在早期学习时,我们认为它是一个简单的样本,当它在更高的时期学习时,我们认为它是一个更难的样本。
因此,【ϕ_{f_t}(x_i】显示的分级机 f_t 样品 x_i 的硬度直接关系到 f_t 学习 x_i 的历元数,因此,【ϕ_{f_t}(x_i】定义如下:
硬度范围取决于子类的数量。当我们有 m 个子分类器时,样品的硬度在范围[0,m-1]内。我们在 CIFAR10 和 CIFAR100 训练集上训练了三种不同类型的分类器,包括 DenseNet121、ResNet18 和 MobileNet,共 100 个时期。使用动量为 0.9 且批量为 128 的随机梯度下降来训练所有分类器。学习率是 0.1,并且计划在每个时期以常数因子 0.955 减少。我们在每个分类器的训练过程中保存所有 100 个子分类器,用它们来计算一个样本的硬度程度。
图 1 显示了在 CIFAR10 和 CIFAR100 测试集的 10 个硬度范围内,样品被正确和错误分类的百分比。该图表明,随着样品硬度的增加,被正确分类的样品比例减少。超过 99%和 95%的样本在前 30 个时期(硬度< 30) are correctly classified in CIFAR10 and CIFAR100 test sets, respectively. On the other side, less than 55% and 36% of samples being learned in the last 10 epochs (hardness degree ≥ 90) are correctly classified in Cifar10 and Cifar100 datasets, respectively. Therefore, as the hardness degree of a sample is increased, the magnitude of trust in the classifier output for that sample is reduced.
Figure 1: Blue and red bars show that the percentage of test samples in each range of hardness degrees being correctly or wrongly classified, respectively. For each range of hardness degrees, Data Fraction indicates the percentage of CIFAR10 and CIFAR100 test samples whose hardness degrees are in that range.
…
Reference:
Amir Mahdi Sadeghzadeh、Amir Mohammad Sobhanian、Faezeh Dehghan 和 Rasool Jalili)被学习。" HODA:面向硬度的模型提取攻击检测"(2021).
使用 Go 开发一个松弛机器人
通过这个循序渐进的教程,学习如何在 Go 中构建一个 Slack bot
安德里亚·德·森蒂斯峰在 Unsplash 拍摄的照片
Slack 是开发者和公司用来分享信息和沟通的沟通工具。近年来它变得非常受欢迎。
在本文中,我们将介绍如何设置和构建一个能够与 Slack 工作空间和通道交互的 bot。我们将研究如何创建斜杠命令和可视化请求,如按钮。bot 应用程序将通过 Websocket 向 Go 后端发送请求,这在 slack 世界中称为 Socket-Mode。
创建时差工作区
如果你还没有一个工作空间可以使用,确保通过访问 slack 并按Create a new Workspace
来创建一个新的工作空间。
松弛时间-创建新的工作空间按钮
继续填写所有表格,您需要提供团队或公司的名称、新的渠道名称,并最终邀请其他队友。
创建松弛应用程序
我们需要做的第一件事是创建 Slack 应用程序。访问 slack 网站创建应用程序。选择From scratch
选项。
slack——为我们的机器人创建一个新的应用程序
您将看到向应用程序和工作空间添加名称的选项,以允许使用该应用程序。您应该能够看到您连接到的所有工作区。选择适当的工作空间。
一个应用程序有许多不同的用例。您将被要求选择添加什么功能,我们将创建一个机器人,所以选择机器人选项。
Slack —添加到应用程序中的特性/功能
单击机器人后,您将被重定向到帮助信息页面,选择添加范围的选项。我们需要添加到应用程序中的第一件事是执行任何操作的实际权限。
Slack 添加范围以允许 Bot 执行操作
按下Review Scopes to Add
后,向下滚动到机器人范围,开始添加我已经添加的 4 个范围。图像中显示了范围的说明。
Slack —添加 bot 权限以扫描频道和发布消息
添加范围后,我们就可以安装应用程序了。如果你是应用程序的所有者,你可以简单地安装它,否则,就像我一样,我必须请求管理员的许可。
Slack —向工作区请求或安装应用程序
如果你可以安装或被允许安装,你会看到另一个屏幕上的信息,选择适当的渠道,机器人可以用来张贴作为一个应用程序。
SlackBot —将 Bot 安装到工作区
一旦你点击Allow
,你会看到一个长字符串、一个 OAuth 令牌和一个 Webhook URL。记住它们的位置,或者把它们保存在另一个安全的地方。
打开您的 slack 客户端并登录到工作区。我们需要将应用程序邀请到一个我们希望他可用的频道中。我使用了一个名为 percybot 的频道。
转到那里,开始输入命令信息,通过用/
开始信息来完成。我们可以通过输入/invite @NameOfYourbot
来邀请机器人。
Slack —邀请 bot 进入可以使用它的频道。
从 Golang 连接到 Slack
既然我们已经启动了 Slack 应用程序和身份验证令牌,我们就可以开始与 Slack 通道通信了。
我们将使用[go-slack](http://go get -u github.com/slack-go/slack)
,它是一个支持常规 REST API、WebSockets、RTM 和事件的库。我们还将使用[godotenv](https://github.com/joho/godotenv)
来读取环境变量。
让我们创建一个新的 golang 包并下载它。
mkdir slack-bot
cd slack-bot
go mod init programmingpercy/slack-bot
go get -u github.com/slack-go/slack
go get -u github.com/joho/godotenv
首先,我们将创建一个.env
文件,用于存储您的秘密令牌。我们还将在这里存储一个频道 ID。您可以在创建应用程序的 web UI 中找到令牌,如果您选择频道并按胡萝卜箭头转到Get channel details
,则可以在 UI 中找到频道。
松弛—按黄色圆圈项目查找频道 ID。
。env——我们将在机器人中使用的秘密
创建main.go
,这样我们就可以开始编码了。我们将从简单地连接到工作区并发布一条简单的消息来确保一切正常开始。
我们将使用godotenv
来读入.env
文件。然后创建一个松弛附件,这是一个发送到通道的消息。需要理解的重要一点是,Slack 包利用了一种模式,在这种模式下,大多数功能都采用一个配置片。这意味着可以在每个请求中添加Option
个函数,并且数量可变。
我们还将在消息中添加一些字段,用于发送额外的上下文数据。
main.go —在松弛信道上发送简单消息
通过运行 main 函数来执行程序,您应该会在 slack 通道中看到一条新消息。
go run main.go
Slack —我们发送的第一条机器人消息
使用时差事件 API
松弛事件 API 是处理松弛通道中发生的事件的一种方式。有许多事件,但对于我们的机器人,我们想听提及事件。这意味着每当有人提到这个机器人,它就会收到一个触发事件。事件通过 WebSocket 传递。
您可以在文档中找到所有可用的事件类型。
您需要做的第一件事是在 web UI 中访问您的应用程序。
我们将激活名为Socket Mode
的东西,这允许机器人通过 WebSocket 连接。另一种方法是让 bot 托管一个公共端点,但是您需要一个域来托管它。
Slack —启用套接字模式以允许 Websocket 而不是 HTTP
那么我们还需要加上Event Subscriptions
。您可以在“功能”选项卡中找到它,输入并激活它。然后将app_mentions
范围添加到事件订阅中。这将使提及触发应用程序的新事件
Slack —为应用程序启用事件订阅
Slack —确保订阅正确的事件,app_mentions
我们需要做的最后一件事是生成一个应用程序令牌。现在我们只有一个 Bot 令牌,但是对于事件,我们需要一个应用程序令牌。
进入Settings->Basic Information
,向下滚动到名为应用级令牌的章节,按下Generate Tokens and Scope
,为您的令牌填写一个名称。
Slack —创建应用程序级令牌
我已经将connections:write
作用域添加到该令牌中,确保您也保存了该令牌,将其作为SLACK_APP_TOKEN
添加到.env
文件中。
。env —将所有必需的字段添加到。包封/包围(动词 envelop 的简写)
要使用套接字模式,我们还需要得到一个叫做套接字模式的slack-go
的子包。
go get github.com/slack-go/slack/socketmode
slack 包需要为套接字模式创建一个新的客户机,所以我们将有两个客户机。一个使用常规 API,另一个用于 websocket 事件。让我们从连接开始,以确保所有权限都是正确的。注意 Websocket 客户机是如何通过调用socketmode.New
创建的,并把常规客户机作为输入。我还在普通客户端的创建中添加了一个OptionAppLevelToken
,因为现在需要它来连接到套接字。
main.go —创建一个通过 Socketmode 连接到 EventsAPI 的 Bot
确保运行该程序并验证连接的输出,将会有一个 ping hello 发送。
main.go —运行程序的输出
是时候开始选择要监听的所有事件了。在程序的最后,我们调用socketClient.Run()
,它将在socketClient.Events
阻塞和接收通道上的新 Websocket 消息。因此,我们可以使用 for 循环来持续等待新事件,slack-go 库也附带了预定义的事件类型,因此我们可以使用类型开关来轻松处理不同类型的事件。所有事件都可以在这里找到。
由于socketClient.Run()
正在阻塞,我们将生成一个 goroutine 在后台处理传入的消息。
我们将从每当 Slack 中触发 EventAPI 上的事件时简单地登录开始。因为我们首先需要在 websocket 上键入 switch 消息,如果它是一个EventsAPI
类型,然后根据实际发生的事件再次切换,我们将把事件处理分解到一个单独的函数中,以避免深度嵌套的切换。
main.go —我们现在监听任何事件并将其打印出来
如果要测试,运行程序然后用@yourbotname
输入 Slack 和由 bot 提及。
go run main.go
slack——提到机器人
您应该能够在运行 bot 的命令行中看到记录的事件。
main.go —事件日志的输出
看看打印的事件,你就会明白为什么我们需要使用多种类型的开关。我们得到的事件属于类型event_callback
,该事件包含一个payload
,其中包含实际执行的事件。
所以首先我们需要测试它是否是一个回调事件,然后测试它是否是一个app_mention
有效负载事件。
让我们实现将继续类型切换的handleEventMessage
。我们可以使用type
字段来知道如何处理该事件。然后我们可以通过使用InnerEvent
字段到达有效载荷事件。
handleEventMessagev1 —用于处理回调事件并查看它是否为 AppMentionEvent 的函数
用新的handleEventMessage
函数替换主函数中打印事件的先前日志。
Main.go 使用 handleEventMessage 而不是嵌套类型开关
现在记录事件并不能成为一个有趣的机器人。我们应该让机器人回应提到他的用户,如果他们说你好,它也应该问候他们。
首先登录到应用程序并将users:read
范围添加到 bot 令牌。我相信你现在不用指导就能做到,或者回去看看我们以前是怎么做的。
一旦完成,我们将创建handleAppMentionEvent
函数。这个函数将把一个*slackevents.AppMentionEvent
和一个slack.Client
作为输入,这样它就可以响应。
事件在event.User
中包含用户 ID,因此我们可以使用该 ID 来获取用户信息。在event.Channel
中也提供了要响应的通道。我们需要的最后一条信息是用户在提及时发送的实际信息,可以在event.Text
中找到。
handleAppMentionEvent—bot 中提及的处理程序
要开始使用这个函数,我们还需要添加客户机作为输入参数。所以我们要更新handleEventMessage
才能接受。
handleEventMessage —现在接受客户端作为输入参数
重新启动程序,试着打个招呼,并说些别的话,看看它是否如预期的那样工作。如果您得到一个“missing_scope”错误,那么您已经错过了一些范围。
bot 运行当前需要的所有作用域
这是我当前运行的机器人的输出
Slack —机器人按照预期做出响应
是时候向前看,看看如何添加斜杠命令了。
向 Slack bot 添加斜杠命令
我经常看到 Slack 机器人使用 slash 命令。这意味着你可以输入/发送一个特殊的命令。有许多内置命令,如/call
允许您开始通话等。
我们将添加一个自定义命令/hello
。当这个命令被触发时,我们将让机器人发送一条问候消息。
同样,您需要在 web UI 中添加命令。访问网站并在功能选项卡中选择Slash Command
。
松弛—在 UI 中添加新的斜线命令。
我们将创建一个接受单一参数的命令,该参数是要问候的用户名。
填写要求的字段,注意我们使用套接字模式,因此不需要提供请求 URL。
Slack —向 hello 用户名添加新的斜杠命令
添加完命令后,不要忘记重新安装应用程序。这是必要的,因为我们已经改变了应用程序。如果您忘记了如何安装,那么请重新访问本文前面安装应用程序的部分。
您可以通过打开 slack 和应用程序被邀请的通道并键入/hello
命令来验证所有东西都已安装。
键入/hello 时出现 Percy-Bot
这很简单,让我们重做我们对 EventsAPI 所做的,但是这次我们将为EventTypeSlashCommand
添加一个类型开关。
我们将在SlashCommand.Command
中找到调用的命令,在SlashCommand.Text
中找到输入文本。因此,我们将首先根据命令的输入路由命令,然后将问候语返回到文本字段。
首先更新main.go
文件,以包含 websocket 上新类型消息事件的监听器。
Main.go 添加了 EventTypeSlashCommand 消息的案例。
不要忘记发送确认,否则您将在 slack 中看到一条错误消息,指出该消息未被正确发送。
Slack —如果您忘记确认 websocket 消息的检索
我们将有一个名为handleSlashCommand
的路由器函数,它将简单地重定向到另一个函数。现在这看起来有点大材小用,但是如果你打算添加更多的功能,那么创建多个小功能会更容易。尤其是当你使用单元测试的时候。
实际的响应将来自handleHelloCommand
,它将简单地获取在/hello
命令之后设置的用户名,并在通道中发送一个问候。
handleSlashCommand —将/hello 重新路由到正确功能的路由器功能
重启程序并尝试从 slack 客户端发送命令。当我输入/hello reader
时,我看到下面的输出。
slack——来自/hello 命令的输出
前进斜线命令和交互
我们将看看如何实现一个斜杠命令来触发机器人问一个问题,我们可以用是或否按钮来回答。
首先在 Slack web UI 中添加新命令,这样我们就可以触发它。
松弛时间—添加新命令
我们将首先对 main 函数做一个小的修改,在接受斜杠命令事件的类型开关中,我们目前在处理消息之前进行确认。我们将改变这一点,并在确认中返回响应,因为这是可能的。
main.go —更新我们接受斜杠命令的类型开关
现在你会看到我们可以多么容易地添加新命令,我们需要做的就是在handleSlashCommand
中添加一个新的 case 选项来检查。当然,我们也需要处理实际的命令,但是这种结构很容易扩展。我们将更新handleSlashCommand
,以便它也返回一个interface{}
。这是将包含在确认中的有效负载响应。
handleSlashCommand —我们现在将两个斜杠命令路由到它们相应的处理程序
我们将路由到一个名为handleIsArticleGood
的函数,该函数将使用名为 Block-Kit 的东西向用户触发一个双按钮问卷。这是一个松散的实现,允许我们发送 HTML 组件。有大量的选项和组件要发送,但现在让我们坚持按钮。
这些块被添加到我们之前用来发送简单消息的slack.Attachment
中。它有一个名为Blocks
的字段,接受要发送的块的数组。每个块都是要发送的可视组件。
我们将使用一个 Section 块,Slack 库帮助我们使用接受几个参数的NewSectionBlock()
创建一个。
第一个参数是一个slack.TextBlockObject
,这是发送文本的标准方式,包含要使用的类型,其中我们将使用 markdown。它还包含要在文本块中显示的值。
第二个参数是要添加的字段,比如我们之前用来添加上下文数据的字段,让它保持为 nil。
第三个参数是一个slack.Accessory
,它是一个 block 元素的容器,你可以在 slack 文档中找到 JSON 布局。我们将向附件添加一个复选框元素,它包含两个选项,[是,否]。请记住,我们只是返回响应,在这种情况下,我们不像在 hello 处理程序中那样发送它。注意 CheckBoxGroupsBlockElement 中的answer
,这是用于识别执行了哪种交互的动作。
handleIsArticleGood —构建我们在确认中发送的可视响应的处理程序
重启你的机器人,尝试在 slack 中执行命令。
Slack —来自/was-this-article-used 命令的响应
当你选择某样东西时,什么也不会发生,因为我们还不接受后端的响应。这个响应将触发一个Interaction
事件,所以如果我们想要接受这个响应,我们需要在 main 函数中监听这个事件。
过程和以前一样,转换成正确的消息类型。在这种情况下,它是一个InteractionCallback
。
main.go —添加对交互的支持
我们将添加一个handleInteractionEvent
,它将简单地打印关于交互和所选选项的信息。
handleInteractionEvent —打印交互的信息
尝试执行命令,并选择一个选项。
按“是/否”时交互的输出
结论
我们已经介绍了开始构建您的机器人所需的大部分项目。
我们已经讨论了这些主题
- 如何设置 Slack-Bot 应用程序
- 从 Golang 服务连接到应用程序
- 收听频道中的机器人提及
- 向 bot 添加斜杠命令
- 使用时差事件 API
- 将可视化的块发送到 Slack
- 监听用户交互
这是这一次,希望你喜欢这篇文章。一如既往,请随时联系我,给我反馈或问题。
现在出去建造一些机器人吧!
用 Python 开发语言翻译系统
想知道语言检测和翻译系统是如何工作的,使用开源 Python 库用几行代码就可以开发出同样的系统
图片由皮克斯拜的 Gerd Altmann 提供
文本语言识别是指预测给定文本的语言的过程,而文本翻译是指将给定文本从一种语言翻译成另一种语言的过程。在自然语言处理项目中,我们经常会遇到这样的情况:文本的语言是未知的,或者给定文本文档的语言根据我们的需要而变化。因此,检测文本并将其翻译成另一种语言本身就是一项任务。
在本文中,我们使用了 google 的一些开源库来检测文本的语言,并将其翻译成我们想要的语言。
Google Compact 语言检测器 v3:
(图片由作者提供),谷歌翻译 UI
Google Compact Language Detector v3 也称为 Google CLD3 是一个基于神经网络的语言识别库。该软件包包含一个经过训练的模型,可以直接用来识别给定文本的语言。目前,它支持约 107 种语言,并预测 BCP 47 风格的语言代码的输出语言。
(图片由作者提供),一些语言代码
引擎盖下的 CLD3:
CLD3 包包含一个推理代码和一个经过训练的神经网络模型。这个包从输入文本中提取 n-grams 字符串,并计算它在文本中的出现频率。
例如,对于给定的字符串“banana”,唯一的单字是:“b”、“a”、“n”。双字是:“把”、“那”、“安”,三字是:“班”、“阿那”、“南”。
(图片由作者提供),输入文本的 n 元语法:“香蕉”
然后,n 元文法被散列成一个 id,每个 id 由一个密集的向量嵌入来表示,作为训练的输入。
为了获得输入文本的语言预测,我们简单地通过网络执行一个正向传递。该模型根据分数平均对应于每个 ngram 类型的嵌入,并且平均的嵌入被连接以产生嵌入层。网络的其余组件是一个隐藏(校正线性)层和一个 softmax 层。
(来源),建筑
安装:
CLD3 包可以从 PyPl 安装,使用:
**!pip install gcld3**
用法:
- 初始化:安装 CLD3 库后,使用该库的
**NNetLanguageIdentifier()**
函数初始化文本识别的对象。
**import gcld3****detector = gcld3.NNetLanguageIdentifier(min_num_bytes=0, max_num_bytes=1000)**
对于给定的输入文本:" Este es un texto en inglés . APL auda si te gusta El artículo "
- 单一语言预测:使用检测器对象预测给定输入文本的最可能语言及其预测概率和可靠性。
(图片由作者提供),使用 CLD3 预测单一语言的代码
- 前 N 个预测语言: CLD3 还具有预测 N 个最有可能的语言列表及其相应概率得分的功能。
(图片由作者提供),使用 CLD3 预测前 N 名语言的代码
谷歌翻译:
图片来自 Pixabay 的 Gerd Altmann
Googletrans 是一个实现 Google Translate API 的开源 Python 库。它非常快速可靠,实际上它使用的服务器与 translate.google.com 使用的服务器相同。Googletrans 具有自动语言检测功能,因此不需要指定输入文本的语言。
- 它使用 Google Translate Ajax API 来调用方法进行检测和翻译。
- Googletrans 对单个文本的最大字符限制是 15k。
安装:
Googletrans 包可以从 PyPl 安装,使用:
**pip install googletrans**
用法:
- 初始化:安装 Googletrans 库后,使用该库的
**Translator()**
函数初始化文本翻译器的对象。 - 翻译: Googletrans 有一个
**translate()**
功能,可以将输入的文本翻译成想要的语言。如果没有给出源语言,google translate 会尝试检测源语言。
(图片由作者提供),使用 Googletrans 进行翻译的代码
结论:
在本文中,我们讨论了如何使用 Google 的 CLD3 库识别给定文本的语言,以及如何使用 Googletrans 库将文本翻译成所需的语言。Googletrans 库也有检测文本语言的功能。
参考资料:
[1] Google CLD3 文档:https://pypi.org/project/pycld3/
[2] Googletrans 文档:【https://pypi.org/project/googletrans/
感谢您的阅读
使用 Streamlit 为微服务开发交互式 UI
将您的 python 微服务立即转变为交互式仪表盘
总结精简
Streamlit 是一个开源应用程序框架,是数据科学家中一个受欢迎的工具,因为它可以帮助他们快速将开发部署到交互式 GUI /仪表板中。此外,如果您了解 python,实现 Streamlit 并不复杂,只需要投入少量时间来拿起这个工具并开始使用它。
概述
在本文中,我们将在 Google Cloud Run 上部署一个微服务,它将拥有一个使用 python 和 Streamlit 创建的交互式仪表盘。如果你想看原始脚本,可以在 Github 上找到。
(1)让我们从准备 Streamlit 应用程序代码开始— (app.py)
我们需要导入我们的应用程序将要使用的 python 包。为了让 streamlit 工作,我们需要添加“导入 Streamlit”。
需要导入模块
接下来,我们将准备应用程序的主体,它由 python 函数和 Streamlit 代码组成,这些代码决定了界面上将显示什么。让我们从添加标题、副标题和表情符号开始。
Streamlit:标题、副标题、表情符号
- 主标题:适用于定义申请的主标题。
- st.subheader :可以使用的合适标题级别(另一个标题级别将是 st.header )。
- 这种方法可以用来给 Interface⭐️.添加表情符号
现在我们已经定义了标题和副标题,让我们在界面的左侧添加一个下拉菜单,这样用户就可以选择了。在本例中,我们将提供 2 个选项供用户选择。
Streamlit:侧栏
- st.sidebar :将我们的小部件钉在左边。
- st.sidebar.selectbox :侧边栏上的下拉菜单,供用户选择选项。
随着选择选项的实施,我们将需要包括导航逻辑,如果用户选择选项“查看所有书籍”,将打印所有书籍的表格,如果用户选择选项“按评级搜索书籍”,将显示一个交互式滚动条,用户可以按评级查看书籍。这里我们将使用 if…elif 条件。
Streamlit:打印表格
- st.write :可用作瑞士军刀打印文本/变量。
- st.table :打印一个静态表格,整个表格将被打印到屏幕上。打印数据框架表的另一种方法是使用 st.dataframe 。
流线:滑块
- st.slider :允许通过使用滑块传递 2 个元素来选择数值范围。
我们就快成功了,主应用程序脚本已经完成,现在我们可以将微服务部署到 Google Cloud Run 上了。
(2)准备所需的库—(requirements . txt)
- 在 requirements.txt 文件中添加“streamlit”
我们需要在 requirements.txt 文件中添加 Streamlit 库,Docker 容器用它来安装这里列出的包。
# File: requirements.txt google-api-core
google-cloud-bigquery
pandas
pandas-gbq
gcsfs
**streamlit**
(3)在 Dockerfile 中添加运行我们的 Streamlit 应用程序的命令— (Dockerfile)
当我们在 Google Cloud Run 上部署微服务时,我们需要使用 docker 将应用程序容器化。为了运行应用程序,需要在docker 文件 中添加命令“streamlit run”。如果你想了解更多关于 Docker,你可以参考他们的文档。
CMD streamlit run --server.port 8080 --server.enableCORS false app.py
你也可以参考 Github 上完整的docker file。
(4)部署应用&看看我们最后的结果!
我们将在 Google Cloud Run 上部署我们的应用程序,这是一个无服务器环境平台。为了成功地将我们的应用程序部署到 Google Cloud Run,我们需要构建 docker 映像并将该映像推入 Google Cloud Registry。构建完成后,我们可以在 Cloud Run 上部署新构建的映像。在构建 docker 映像之前,请确保您拥有部署应用程序所需的文件。
构建 Docker 映像所需的文件
下面是在 Google Cloud Run 上部署我们的应用程序的命令(您可以在 Cloud Shell 上运行脚本):
# Set the Region
gcloud config set run/region asia-southeast1#Build a new Docker Image
gcloud builds submit --tag gcr.io/sue-gcp-learning-env/books_ui#Deploy the Image
gcloud run deploy --image gcr.io/sue-gcp-learning-env/books_ui --platform managed
成功部署后,您可以通过提供的 URL 地址访问应用程序(您也可以从您的云运行实例中检索链接)。
完成部署后的应用程序 URL
以下是我们部署的采用 Streamlit 接口的微服务的外观:
简化界面
结论:
在本文中,我们将学习如何使用 Streamlit 创建交互式微服务。一个具有选择选项、在表格中打印结果、添加表情符号和使用滑块过滤值功能的界面。还有更多的你可以用 Streamlit 实现的,本文没有涉及到,比如进度条、地图绘制、绘制图表等。我建议你花点时间玩玩 Streamlit,因为我玩得很开心!查看您开发的产品并与之互动是一件有趣的事情。我也希望这篇教程对学习使用 Streamlit 的人有用。如果你有任何问题或者你可能感兴趣的教程,请在评论中告诉我!
参考和链接:
[2]https://raw . githubusercontent . com/omnidan/node-e moji/master/lib/e moji . JSON
[3]https://docs.streamlit.io/en/stable/
https://docs.streamlit.io/en/stable/api.html#display-text
用 Python 在 15 分钟内开发和部署一个 UI
Streamlit:使应用开发民主化并赋予程序员权力
作者视觉。
介绍
我已经做了 14 年的程序员,其中有整整 6 年都是以这样或那样的方式围绕 Python 展开的。作为一名软件开发人员,我已经非常依赖 Python,并虔诚地在工作的各个方面使用它的优点。在那段时间里,我目睹了同事们在我带着熊猫跑在前面的时候被 Excel 卡住了。我观察到一些同事使用糟糕的鼠标记录器来模仿重复的 web 抓取任务,而我为此派出了 Selenium。当我让 Numpy 工作时,我看着同学们在他们的(不)科学计算器上单调乏味地输入一个又一个数组。
然而,我渴望的一件事是能够以图形方式将我的软件展示给全世界,而不是通过命令提示符与它交互。在你使用 PyQt,Tkinter,Flask 或者 Django 之前,我可以说,你已经做到了。虽然这些“遗留”UI 绑定中的每一个都做出了巨大的努力来弥合这一差距,但它们中没有一个真正适合我们这些 Python 忠实者。作为一名 Python 程序员,我几乎可以在后台创造奇迹,但我不能编写 HTML 或 CSS 来拯救我的生命。所以这是事情的前端。我需要的是一个纯 Python 工具包,它可以在我的指尖呈现一个用户界面。然后出现了细流。
细流
这是一个不引人注意的夜晚。我正要打瞌睡,但就在那之前,我的电话响了。经检查,我意识到这只不过是另一个讨厌的电子邮件广告。这是一个关于用 Python 构建 web 应用程序的课程,虽然我起初有所保留,但我决定进一步研究。我很高兴我这样做了,否则我今天不会在这里告诉你 Streamlit 是一个多么改变游戏规则的东西!
Streamlit 是一个纯粹的 Python web 框架,几乎填补了 Python 程序员的应用开发空白。这是一个健壮的、可伸缩的 API,学习曲线非常浅,将开发时间从几周减少到几分钟,真的。尽管它被标榜为机器学习和数据科学应用的框架,但事实上这并不明显,因为许多人(包括我自己)已经利用 Streamlit 创建了优雅的通用应用程序。凭借 Streamlit 及其最新功能— 组件,极限就是你想象的天花板。
用户界面开发
在这里,我将向您展示如何开发和部署您自己的 Data Explorer UI。首先,启动 Anaconda 并在您的环境中安装 Streamlit。或者,您可以在 Anaconda 提示符下运行以下命令。
pip install streamlit
接下来,在刚刚安装 Streamlit 的相同环境中启动您选择的 Python IDE。现在,让我们创建一个简单的用户界面,用户可以上传数据集,改变数据,并同时将其可视化。
继续导入必要的包:
import streamlit as st
import pandas as pd
为您的用户界面创建标题:
st.title('Data Explorer')
现在创建一个侧边栏,我们可以在其中创建一个文件上传器小部件:
st.sidebar.subheader('Upload a file')uploaded_file = st.sidebar.file_uploader("Upload a file")if uploaded_file is not None:
df = pd.read_csv(uploaded_file)
下面我们创建一个 info 小部件,通知用户文件已经成功上传:
st.sidebar.info('File successfully uploaded')
让我们将主页分成两个等间距的栏:
col1, col2 = st.beta_columns(2)
现在让我们在第一列中将上传的文件显示为数据帧:
with col1:
st.write(df)
如果您想改变列,可以使用 multiselect 小部件删除不必要的列,如下所示:
with col2:
columns = list(df.columns)
columns_sel = st.multiselect('Select columns',columns,columns)
df = df[columns_sel]
st.write(df)
现在,您可能想要修改数据框中的行数:
start, stop = st.slider('Rows',0,len(df)-1,[0,len(df)-1],1)
df = df.iloc[start:stop]
st.write(df.iloc[start:stop])
对数据框架进行变异后,是时候对其进行可视化了,但在此之前,您首先需要借助单选按钮选择您希望绘制图表的列:
value = st.radio('Select column',columns_sel)
继续可视化你的数据框架:
df = df[value]
st.line_chart(df)
完整的源代码如下:
没错,你没看错,这是一段庞大的 26 行代码(不含空格)。
要在本地运行您的 UI,请在 Anaconda 提示符下键入以下命令。首先,将根目录更改为保存源代码的位置:
cd C:/Users/...
然后键入以下内容运行您的应用程序:
streamlit run file_name.py
云部署
如果你不能把一个优雅的 UI 部署到云上让全世界看到,那它是什么?在你考虑 AWS、Azure、GCP 或 Heroku 之前,不,只是不。不是说没有人喜欢 IaaS 或 PaaS 提供商,而是有了 Streamlit 的一键式部署,生活从未如此简单。
首先将你的代码推送到一个公共的 GitHub 库,然后前往 Streamlit sharing ,注册并链接你的 GitHub 账户,如下所示:
图片作者。
选择相关的存储库和脚本,然后点击 Deploy!
图片作者。
结果
现在你有了它,一个优雅的、高度交互的、云可部署的用户界面就在你的指尖,用了 26 行代码和 15 分钟的工作!如果你发现自己处于和我之前相似的困境,那么考虑参加一个在线课程,比如 Coursera 的Streamlit Guided Project。这是一项有价值的投资,会让你很快上手。
图片作者。
如果您想了解更多关于数据可视化和 Python 的知识,请随时查看以下(附属链接)课程:
使用 Streamlit 开发 Web 应用程序:
使用 Python 实现数据可视化:
面向所有人的 Python 专业化:
简化导向的项目:
GitHub 资源库:
https://github.com/mkhorasani/streamlit_ui
新到中?你可以在这里订阅和解锁无限文章。
使用 Python Dash 开发和部署交互式仪表盘
Dash 框架使用指南。
Artem Podrez 在的照片
破折号简介
Plotly 提出了一个名为 Dash 的新框架,允许用户使用 plotly express 提供的可视化功能创建交互式仪表盘。Dash 核心组件用作每个应用程序的构建模块,在属性方面非常详尽。您可以立即用它为您的 Python 应用程序添加一个界面。
【Dash 入门
例如,我们正在构建一个简单的仪表板,通过图表显示每个球员的 IPL 统计数据(得分、击球次数)。这将让你对应用 Python dash 框架有一个基本的了解,这个框架可以进一步扩展。你可以从这里下载 IPL 数据集。
作者在 Heroku 上的图片
你需要确保你的机器上安装了 dash,plotly 和 pandas 库。如果没有,您可以通过在命令提示符下将 CD 放入 python 安装文件夹,或者如果您正在使用 Anaconda,只需打开 Anaconda 提示符并执行下面的行。
pip install dash
pip install plotly
pip install pandas
Dash 将是 3 个库中最突出的,因为它将有助于在浏览器上呈现仪表板。Plotly 将用于创建图形。最后,我们将使用 pandas 进行数据操作。
除此之外,你还需要非常基本的 HTML 和 CSS,你可以像我一样随时随地学习。
创建仪表板框架
好吧,那我们开始吧。让我们首先在仪表板上创建我们的部分(HTML div ),在那里我们将为我们的应用程序放置 dash 组件。我们需要在顶部的仪表板下拉列表的一个部分和显示仪表板图表的两个平行部分。除此之外,我们将把每个部分封装成一个大的部分,这个大的部分将包含我们完整的视口。
作者图片
import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
from dash.dependencies import Input, Output
import plotly.express as pxapp = dash.Dash(__name__)
app.title = "IPL Dashboard"#Creating a section to place our dropdown
dropdown_box = html.Div(
children = ["dropdown", dcc.Dropdown()], #dash dropdown elementstyle = {'width': '90%', "position": "fixed", "left": "5%",
'display': 'inline-block', 'height':"5%", "top": "1%",
'z-index':'1', 'border': '2px solid green',
'text-align': 'center'}
)#Creating a section to place our graphs Graphs = html.Div(children = [
html.Div(children = ["Graph 1", dcc.Graph()], #dash graph elementstyle = {'width': '45%', "position": "fixed", "left": "2%",
'display': 'inline-block', 'text-align': 'center',
'top': '10%', 'border': '2px solid red', 'height':'50%'}
),html.Div(children = ["Graph 2", dcc.Graph()], #dash graph elementstyle = {'width': '45%', 'position': 'fixed', 'left': '53%',
'display': 'inline-block', 'text-align': 'center',
'top': '10%', 'border': '3px solid red', 'height':'50%'}
)])#creating and adding the main html div which encapsulates all elements on the viewportapp.layout = html.Div(id ="main_div", children=[input_box, Graphs],style = {'background-color': '#FFFFFF', 'padding':'0',
'width':'100%', 'height':'100%', 'position': 'fixed',
'top': '0%', 'left': '0%', 'bottom': '0%', 'height':'100%',
'border': '2px solid blue'}
)if __name__ == "__main__":
app.run_server(debug=False, port = 8080)
向仪表板添加交互性
现在 dashboard 的框架已经准备好了,让我们来创建它的灵魂,使它具有交互性。首先,我们需要为所有的 dash 组件添加一个id
属性。这将为每个组件提供一个唯一的标识符,该标识符将用于在仪表板上获取输入和显示输出。除此之外,我们将使用更多的下拉属性,如 clearable、placeholder、value 和 options。你可以在这里查看 dash 核心部件的所有可用属性。
为了增加交互性,dash 提供了一个回调函数,允许我们与仪表板上的组件进行交互。回调函数使用 dash dependencies 类接收输入并抛出输出。我们可以使用每个仪表板组件的id
和property
属性从仪表板获取多个输入,同样也可以使用相同的属性呈现输出。我们将在代码中详细看到这一点。限制是— 只能有一个回调函数。然而,我们可以在这个回调函数中调用多个函数。
因此,我们定义了两个函数来绘制 plotly 图形。我们将从回调函数中调用这些函数。
那么,现在让我们看看如何在 Python 中应用同样的方法。
我们对 CSS 中的框架版本做了一些修改,以增强外观,比如移除边框、改变背景颜色等。
在 web 上部署仪表板:
有多种方法可以在网络上部署这个 dash 应用程序。然而,我们同样选择了 Heroku。为此,你需要以下的东西。
1.Git 上的帐户(注册)。
2.Git bash 安装在您的机器上。
3.Heroku 账号(注册)
4.从这里下载 Heroku CLI。
5.可选但有用— Pycharm/Visual Studio 安装在您的计算机上。
设置好上述资源后,请按照下面的步骤操作。
- 登录 Heroku,点击- 创建新应用。
- 为您的应用命名,然后点击- 创建应用。
- 现在,您需要在 Pycharm/Visual Studio 中创建新项目,万一您没有,也不用担心。您只需要创建一个文件夹,并将我们将在后续步骤中创建的文件放在该文件夹中。
- 打开项目路径,将
.py
文件添加到项目文件夹中。这个文件将包含所有的 Python 代码。 - 在你的。py 文件下
app = dash.Dash(__name__)
添加这一行:server = app.server
- 打开终端/命令提示符,并光盘到您的项目文件夹。
- Pip 安装您的特定版本的所有库。py 文件需要运行。
pip install pandas==1.2.0
pip install plotly==4.8.0
pip install dash==1.12.0
pip install gunicorn==20.0.4 #Required to run app on Heroku.
- 在项目文件夹中创建一个文件,命名为
.gitignore
。
作者图片
- 在项目文件夹中创建一个文件,将其命名为
Procfile
,并将这一行添加到其中web: gunicorn YourPythonFileNameWithoutPy:server
作者图片
返回终端/命令提示符,运行以下命令。
- pip 冻结> requirements.txt
- heroku 登录
- git 初始化
- heroku git:remote-a yourhoku appname
作者图片
- git 添加。
- git commit -am“初始启动”
- 饭桶推 heroku 主人
恭喜你!!!您已经在 web 上部署了 Dash 应用程序。您可以共享 Heroku 应用程序 URL,以便向任何人展示该应用程序。这提供了一个不可思议的解决方案来显示报告或给你的 Python 程序一个用户/客户可以与之交互的接口。
快乐编码,享受乐趣,保持健康。如果你在应用它时遇到任何问题,我只是一个评论。
在本地开发和测试谷歌云功能并进行部署
管理、开发和部署 Google 云功能的简单设置和模板
我想在这里描述一下我和我的队友 Crosi 是如何找到一种方法,让我们在本地开发谷歌云功能,然后使用谷歌构建服务以自动化的方式部署它们。我们已经阅读了无数的文献,现在想在这里整理我们的发现。
目前,我们正致力于建立一个数据湖。我们当时试图找到一种方法,让我们能够开发谷歌云功能,处理来自谷歌云存储的文件。Google 允许在 Google 代码编辑器的 web 界面上编写云函数。然而,它缺少像 PyCharm 这样的普通 IDE 那样的琐碎功能。无代码完成。没有警告。部署前没有本地测试/执行。我们想要面对这些问题。
先决条件
你的电脑应该安装了 Google Cloud SDK 和 git。我假设你对谷歌云功能有基本的了解。
我想介绍以下工作流程,并指导您如何建立它。在克隆了我们的模板库之后,你可以在每个新的分支上创建你自己的云函数。每个分支代表一个云函数。之前可以在本地测试或者运行这个云功能。一旦开发完成,使用 Google Cloud Build 服务将这个云功能部署到 Google Cloud Function 服务中。可以说,您的开发与所提供的功能直接相关。未来对云功能分支的每一次推动都会导致云功能的自动重新部署。
目标开发工作流程。您的回购中的每个分支都是一个特定的云功能,将使用云构建进行部署。
步伐
可选:设置本地 GCP 开发的自动身份验证
如果您 没有 为本地开发生成密钥文件,请执行以下步骤。
- 打开你的命令行。
- 用 gcloud :
gcloud config set project your-project-name
切换到当前项目 - 用 gcloud 允许本地开发并生成一个密钥文件:
gcloud auth application-default login
- 弹出一个窗口。确认一切。 gcloud 现在生成一个 JSON 格式的密钥文件,我们的应用程序通过这个文件自动验证自己的 GCP。所以代码中不需要指定凭证!
使用我们的回购作为模板
https://github.com/raki2305/gcp_cloudfunction_dev
- 克隆我们的库并在 Google 云资源库上设置它
- 克隆您刚刚创建的云存储库。
现在用git branch your-function-name
和 checkout 为每个未来的云功能创建一个单独的分支。
在 src/ 下,你会发现一个 main.py 文件和一个 requirements.txt :
- main.py 包含了你未来的云功能。
- 开发完云功能后,在 requirements.txt 中指定所有使用的包。
这是 main.py,你可以单独编辑。
在 utils/,下你会找到一个 main_local.py 。您可以使用该文件来模拟触发器。请注意文件中的注释。
这是 main_local.py,用于通过执行这个脚本来模拟触发器。
在 config/,下,你会发现一个 cloudbuild.yaml 文件,它使用 Google Cloud Build 服务自动部署云功能。替换相应的占位符。请注意文件中的注释。你的云函数开发完成后,就可以直接推送到你的 git 了。
使用云构建服务进行部署
云构建用于设置 CI/CD 管道。在左侧的 Google Cloud Build 服务中,单击“触发器”,然后单击“创建触发器”。
请相应地填写所有内容:
- 在节事件中,请选择“推送至分支”。
- 在部分,来源请参考你的云储存库和包含你的云功能的分支。
在截面配置中:
- Type = "云构建配置文件(yaml 或 json)"
- location =/config/cloud build . YAML
最后点击“创建”。
下面是一个创建的触发器的例子,您也可以通过单击“运行”来手动运行它。
注意:每次推送至指定分支,云功能自动部署。
结论
现在你可以为每个云函数创建一个新的分支。在这个分支中,您可以开发与其他云功能相隔离的云功能。通过 main_local.py 你可以在之前执行它。通过 push 命令上传的云功能的每次更新都会重新部署云功能。
如有疑问,欢迎留言评论。
使用短期凭证在 AWS 中开发
了解如何快速、免费、安全地刷新 AWS 访问会话,而无需为较小的环境建立联盟
我曾经在亚马逊网络服务公司( AWS )担任安全顾问。我们的众多职责之一是确保客户和我们自己的 DevOps 和数据科学家团队使用短期凭证。对于我们的许多初级会员和新客户来说,这通常是一项艰巨的任务。这是因为客户刚刚起步,没有完全的身份联盟。在许多情况下,我见过拥有长期证书的开发人员使用标准的静态 IAM 密钥,这些密钥肯定不会每天轮换。
为了满足这一需求,您可以利用 AWS 的一个免费解决方案,称为单点登录(SSO)。您不必担心将生产身份解决方案(如 Active Directory)混合在一起。对于较小的任务,您可以简单地在您的开发或登台环境中部署和使用它。使用我将要向您展示的方法将帮助您让您的网络安全团队更加快乐。
注意:在我们继续之前,我鼓励您使用您自己的 AWS 帐户关注我,以便您对正在发生的事情以及为什么要阐述我的观点有一个战术上的感觉。继续登录您的 AWS 帐户,让我们开始吧!
为短期编程凭据启用 SSO
即使我们没有外部的 IdP 用于联合,我们仍然可以使用我们自己的 IAM 用户在 SSO 自己的目录服务中集成 SSO。这将要求我们使用 MFA,因为我们为用户设置了 MFA 。默认情况下,该角色将持续一个小时。根据您希望重新进行身份认证的频率,您可以随时将此时间更改为 2-4 小时。它将与我们的 AWS CLI v2 集成,供我们以后使用。这样,我们就不再需要使用默认凭据了!
注意:我们不要求您删除您的静态长期凭证,这些凭证已通过“aws configure”配置为您的默认配置文件。但是,出于生产环境和安全性的原因,您应该通过再次运行 aws configure 并使用假值来这样做,并且始终使用我们将创建的 SSO 概要文件。
启用 AWS SSO。如果出现提示,请启用 AWS 组织并将此帐户设置为您的 root 帐户。在开始之前,建议 t(但可选 ) 您自定义您将通过选择底部的“自定义”按钮登录以进行身份验证的 SAML 凭据 URL:
AWS SSO 服务控制板
从“选择您的身份源”开始。我们将尽可能启用 MFA,因此提示用户使用 MFA。作为一个好的媒介,让我们通过选择配置来使用“上下文感知”模式:
MFA 的 SSO 配置和您的自定义门户 URL
设置上下文感知模式,并允许使用内置授权码和授权码应用程序。要求注册也是一种最佳做法,但是您可以根据自己的喜好进行设置:
AWS SSO MFA 的选项
使用面包屑返回到您的 AWS SSO 主菜单,然后选择“管理对您的 AWS 帐户的 SSO 访问”。接下来,选择权限集:
为您的目录帐户设置 AWS SSO 权限集
选择“权限集”后,继续根据“现有工作职能策略”创建一个新的权限集:
按工作职能创建 AWS SSO 权限集
选择“AdministratorAccess ”,因为只有您在使用它(或者创建一个针对您的安全操作要求的自定义访问权限:
以选择默认工作功能为例
最佳做法是设置您的权限集标记。这些可以是任何键值对,但是我可以推荐一些像您的成本中心、项目名称或部门名称以及一个有意义的值:
将您的资源标记设置为最佳实践,以便您可以跟踪您的使用情况
检查您的设置,以及如何将默认名称设置为小时。选择创建:
具有管理权限的权限集
您将返回到屏幕,继续选择“AdministratorAccess ”,这样它将打开一个编辑器,并将持续时间设置为(2 小时)和有意义的描述:
管理员权限集,会话默认持续时间为 2 小时
返回到主 AWS SSO 欢迎屏幕,然后选择“Groups”并创建一个名为 Admins:
为您的 SSO 目录用户创建组
接下来,选择用户并创建一个新的 SSO 用户,并填写所需的用户名和详细信息。这是一个独立于 IAM 的用户,因为 SSO 免费拥有自己的用户目录。如果您是企业,这可能已经联合并同步到您的身份提供商。对于我们的实验室,我们将创建一个手动用户。将其命名为有意义的名称,因为您希望在 MFA 令牌中与 IAM 用户中对其进行不同的识别:
创建新的 SSO 目录用户(不是 IAM 用户)
将您的用户添加到管理员组,并选择“添加用户:”完成
将您的用户添加到您在 SSO 中创建的组中
您将返回到屏幕。按照电子邮件中的说明设置您的密码并注册一个 MFA 软令牌,如“google 认证”应用程序:
创建您的 SSO 目录用户并注册 MFA
接受邀请,按照说明设置密码并注册 MFA 设备:
发送到您自己电子邮件的 SSO 门户和邀请示例
接受注册并开始添加 MFA 软令牌
如果您的 SSO 注册成功,您将被重定向到门户页面。当然,你没有应用程序也没关系:
你没有任何应用程序,因为这只是为了验证你的 AWS 帐户
现在返回您的 SSO 页面,转到“AWS 帐户”。选择您的 root 用户帐户,然后选择“分配用户”:
将您的用户添加到您希望使用短期凭据的 AWS 开发帐户
选择您刚刚创建的帐户,然后选择“下一步:权限集”:
将您的管理权限集分配给您的 SSO 目录帐户
将“AdministratorAccess”权限集添加到您的 SSO 用户,然后选择 finish。
绑定到您的 SSO 目录帐户的原始管理权限集
打开一个终端,输入“aws 配置 sso ”,并输入来自“欢迎使用 AWS SSO”页面的自定义 URL。将您的地区设置为 us-east-2 ,并设置您的个人资料详情。您还可以将您的配置文件名设置为“sso ”,这样当您指定要使用短期凭证时,就不必键入长文件名:
使用 AWS CLI 终端配置使用区域和帐户的 SSO 身份验证
现在,只要您希望使用 CLI 利用刷新的令牌凭据,而不使用长期静态凭据,就可以使用“aws
因为我们都是开发人员,所以我真的不希望不得不每隔 2 小时在 CLI 中为不支持“SSO”认证类型的第三方工具或扩展“手动”刷新我自己的 IAM 会话令牌。例子包括 AWS 的 VSCode 扩展以及在本文撰写之时的 CDK 。为此,我们有另外一个免费的补救措施。输入 YawSSO 。
YawSSO 会把你的新会话信息输入你的默认”。aws/credentials”文件。为此,您需要在 Windows、Mac 或 Linux 上安装 Pyhon3 ,并且您可以利用以下工具每隔 1-2 小时安装并同步您的令牌。如下例所示安装和使用:
pip3 install yawsso
aws sso login — profile <sso-profile-name-syntax>
yawsso — profile <sso-profile-name-syntax>
集成了 AWS SSO 的 YawSSO 使用示例
通过创建一个简单的 shell 脚本来进一步实现自动化,该脚本可以作为 cron 作业运行,或者在我的开发主机上作为 8 小时会话刷新程序运行:
#!/bin/bash
for i in {1..8}
do
echo "Getting SSO credentials for an 8 hour day..."
aws sso login —profile <sso-profile-name-syntax>
#yawsso will sync your CLIv1 credential profiles
yawsso —profile <sso-profile-name-syntax>
sleep 3600
done
你是 Windows 用户吗?没问题,这里有一个简单的 Powershell 一行程序来做同样的事情:
1..8 | ForEach-Object { Write-Host "refreshing tokens with sts..."; aws sso login --profile sso; start-sleep 1; yawssso; start-sleep }
如果你不使用 YawSSO,另一种方法是当你使用带有 AWS 工具扩展 的 VSCode 等 IDE 时,手动 保持从 STS 导出带有令牌的访问和秘密访问密钥。如果您从未见过 AWS STS 会话令牌,下面是它的样子:
JSON 中的 AWS STS 会话令牌示例
现在,您可以拥有短期凭证,而不必经历可能需要重新架构和等待批准的麻烦,从而以一种简单而免费的方法联合您的开发环境。像往常一样,如果您觉得我的建议很有帮助,或者想就您的安全需求进行更深入的咨询,请随时拨打 www.scissecurity.com的电话联系我
快乐安全发展!
在一行 Python 代码中生成交互式绘图
Plotly Express 库基本指南
科林·伯伦斯来自的图片
探索性数据分析是数据科学模型开发流程的重要组成部分。数据科学家将大部分时间花在执行 EDA 上,以便更好地理解数据并从中获得洞察力。
有各种单变量、双变量和多变量可视化技术来执行 EDA。Matplotlib、Seaborn 是一些用于生成静态图和图表的流行库。使用 seaborn 和 matplotlib 生成的图本质上是静态的,需要多行 Python 代码来进一步定制图。
在本文中,我们将探索 Plotly Express 库,它可用于生成交互式绘图。
为什么是交互式可视化?
Matplotlib 和 Seaborn 是数据科学社区中流行的库,它们可以为可视化生成漂亮的图形和图表,那么交互式可视化的要求是什么呢?
数据科学家必须花费大量时间使用 seaborn 或 matplotlib 库来生成和定制绘图。
让我们使用 seaborn 生成一个示例静态条形图,以可视化 2007 年几个国家的人口。
**sns.barplot(data=df, x='country', y='pop')
plt.show()**
(图片由作者提供),Seaborn 酒吧图
上面展示每个国家人口的条形图样本不可读。数据科学家必须编写多行 Python 代码来定制条形图,以使其易于理解。需要调整绘图的大小并旋转 x 刻度标签。
**plt.figure(figsize=(14,4))
sns.barplot(data=df, x='country', y='pop')
plt.xticks(rotation=90)
plt.grid(axis='y')
plt.show()**
(图片由作者提供),自定义 Seaborn 条形图
X 轴有 142 个类别(国家),这使得剧情很难解读。为了更好地可视化,必须过滤排名前 60 位的国家。
**plt.figure(figsize=(14,4))
sns.barplot(data=df[df['country'].isin(list(df['country'].unique())[:60])], x='country', y='pop')
plt.xticks(rotation=90)
plt.grid(axis='y')
plt.show()**
(图片由作者提供),为前 50 个类别定制 Seaborn 条形图
人们必须编写多行 Python 代码,以使情节美观直观。尽管如此,这些情节本质上是静态的,从情节中获得每个国家的准确人口有点困难。
Plotly Express 是一个高效的库,可以在一行 Python 代码中生成漂亮且可解释的图表。现在让我们使用 Plotly Express 库生成相同的图。
Plotly Express:
来自 Plotly 文档:
Plotly Express 是一个封装了 Plotly 库的包装器。Plotly Express 的函数 API 旨在尽可能保持一致和易于学习,从而在整个数据探索会话中轻松地从散点图切换到条形图、直方图和日射图。
Plotly Express 在一行 Python 代码中生成直观的交互式绘图。
安装:
Plotly Express 可以从 PyPI 安装,使用
**pip install plotly**
用法:
使用 Plotly Express 生成交互式柱状图只需一行 Python 代码。
**import plotly.express as px****px.bar(data_frame=df, x='country', y='pop')**
(GIF 由作者提供),使用 Plotly Express 的条形图
Plotly Express 提供了 30 多种功能来创建不同类型的绘图和图形。阅读库文档以更好地理解代码用法。
结论:
Plotly Express 是一个方便的工具,可以生成交互式的图和图形,使 EDA 管道更加直观。用户可以将鼠标悬停在图形上与图形进行交互,并且可以在使用笔记本进行演示时提高效率。
然而,与 seaborn 或 matplotlib 相比,Plotly Express 的绘图不太可定制。因此,seaborn 或 matplotlib 可以优先用于生成可定制的图,而 Plotly Express 可以用于从图中生成快速洞察。
参考资料:
[1] Plotly Express 文档:【https://plotly.com/python/plotly-express/
感谢您的阅读
发展生命科学行业数据科学家的职业生涯
挑战和机遇
在大型生命科学(制药、医疗营养、生物技术)公司的研发(R&D)领域,数据科学家是一个相对较新的角色。对于职业生涯早期的数据科学家,或对进入生命科学研究感兴趣的已有数据科学家来说,随着该领域的成熟以及与已有科学学科一起获得认可,该领域的机会越来越多。
要在生命科学研究环境中成为一名成功的数据科学家,尤其是在一家大型公司中,需要与任何其他行业相同的技能和经验。但是有一些显著的不同:
- 比商业意识更重视科学研究专业知识;
- 软技能以及与科学专家建立和保持伙伴关系的能力可能比纯粹的商业咨询技能更有价值。
作为一门新学科,围绕这一点可能是最大的挑战。尝试新事物总是令人兴奋的,但是会带来额外的需求,直到新事物成为常态。
成为街区里的新成员会很艰难
在一个成熟的组织中成为一门新学科的最大挑战之一是需要确定这门新学科将带来什么样的额外价值。在生命科学行业尤其如此,因为它的重点是科学研究——R&D 的主要作用是科学发现带来新的商业机会。R&D 生命科学系通常拥有许多学术上令人印象深刻的简历,博士比比皆是,就像长期同行评审的科学出版物参考书目一样。在这种情况下引入数据科学家的新角色可能是敏感的,有时甚至是具有挑战性的,因为这些非常聪明的人多年来一直在做自己的数据分析,这是他们现有工作的重要部分。从这个意义上来说,数据科学家,至少对某些人来说,可以被看作是一个闯入者。数据科学和数据科学家是有些模糊的术语,这使得情况更加复杂。
根据我的经验,新数据科学家需要特别考虑的两个方面是:
- 如何有效地与科学同事合作——可以说是迈出了第一步——在项目或项目负责人是科学家的项目中,这是一种典型的工作方式。以前的项目没有数据科学家,而且很成功,那么为什么现在需要一个呢?
- 一旦参与到项目中,如何在项目活动中获得合适且有回报的角色,以及如何提高数据科学家成为未来项目重要组成部分的机会。
帮助应对这些挑战的一个关键学习是非常了解科学家同事的世界观和观点——设身处地为他们着想。如果以前的类似项目使用经典的统计建模技术(t 检验、非参数、PCA 等)获得成功。)并通常由科学家自己进行,然后提出新的(潜在复杂的)方法,如人工智能、机器学习或高级统计建模,科学家不熟悉的方法可能会失败,导致沮丧和浪费精力。
阿瑟尼·托古列夫在 Unsplash 上拍摄的照片
在项目中引入新的数据科学建模技术可能需要相当的谨慎,并且可能需要以比预期慢得多的速度来完成。这可能会令人沮丧,因为找到一个聪明的新方法来解决现有的问题是令人兴奋的,并且想要全速前进是很自然的。我在这里的建议是要有耐心,它会发生,但要给它时间。良好的软技能是关键,温和地教育并带上同事。当以前的出版物——已经过同行评审,因此被认为具有良好的科学质量——使用现有的建模方法时,一些新方法在纯技术基础上更好的论点可能没有什么吸引力。要考虑的重要一点是采用新方法的用例是什么?是否引人注目?你能让它引人注目吗?
向您介绍一位数据科学家,他拥有大量强大的新功能!—但是不熟悉的建模工具变成一个成功的科学家团队是一种改变,改变需要时间,有些人会接受,有些人不会。我的建议是关注那些拥抱变化的人,赢得他们的信任,然后他们可能会成为你和数据科学的大使,帮助展示数据科学家可以给项目带来的附加值。
数据科学的专业标准
将数据科学家引入生命科学研究与将生物统计学家引入临床试验和生物医学研究有一些相似之处。随着更多机会的出现,生物统计学家进入数据科学角色也并不罕见。生物统计学是制药公司从初级职位到高级管理层的一条非常成熟的职业道路。
然而,与 R&D 生命科学协会相比,一个关键的区别是,临床试验是一个高度管制的领域,良好临床实践(GCP)要求任何数据分析都必须由合格人员进行。实际上,这意味着某人拥有统计学方面的正式资格/证书。这种治理框架意味着生物统计学作为一门学科,与科学家和医学研究人员一样拥有保留的地位。但这不是一蹴而就的,临床研究有着非常悠久的历史。数据科学和数据科学家的角色作为一个学科和工作岗位仍然在不断发展。
皇家学会在“什么造就了数据科学家”中讨论了在生命科学研究中引入数据科学的治理框架和专业标准。为什么专业技能和行为比以往任何时候都更重要。这是积极的一步,但考虑到生命科学研发的巨大多样性和范围,这是雄心勃勃的,但也是未来几年值得关注的领域。
增长和机遇
虽然生命科学环境中的数据科学仍处于早期阶段,但它已经感觉像是一条与“主流”数据科学完全不同的职业道路。除了上面提到的差异,比如强调科学而不是商业意识,还有许多实际的差异。例如,在我自己的日常工作中, R 而不是 Python 是大多数项目的首选工具,Python 是我们生态系统的一部分,但只是一小部分。这也反映了我在潜在数据科学家候选人中寻找的技能差异。例如,我收到的简历中一个越来越常见的方面是列出候选人有经验的所有不同的技术/ML 框架。这可能适合技术部门的职位,但对于生命科学研发部门的职位来说并不合适。
我在早期职业数据科学家中寻找的是研究能力和对建模技术方法论理解的深度。特定技术或编程语言的经验在我看来更像是一种需要学习的商品。这可能不同于其他部门或角色,在这些部门或角色中,必须在特定语言或框架方面拥有高水平的经验,例如,重点是开发新的数据科学工具以部署到生产中。例如,预测模型或决策支持系统。这与 R&D 生命科学不同,在那里,数据科学活动主要是通过与科学家合作来产生新的研究见解。
总之,作为生命科学行业的数据科学家,职业生涯带来了挑战,但也带来了丰厚的回报——通过为现有数据和建模问题带来新的思维和新工具,有可能真正大放异彩。提供新的突破和创新。毕竟,雇佣数据科学家是有原因的。生命科学家作为兼职数据分析师的多任务处理不会改变,也不应该改变,但通过与科学家同事合作,建立强大的网络和关系,数据科学作为生命科学的职业选择将有一个非常光明的未来。
关于作者
这是我,从 2000 年获得博士学位以来,我一直从事生物医学和生命科学研究,特别是生物统计学和数据科学。我目前在荷兰达能的研发数据科学部门工作,在此之前我在利洁时的研发数据科学部门工作。
开发对话式人工智能程序
如何避免常见陷阱并释放客户和业务价值
由 EPAM 系统有限公司 产品经理 Rachel Bimbi 负责
信贷:EPAM 系统
对话式人工智能技术在过去十年中发展迅速,聊天机器人、虚拟代理、语音助手和对话式用户界面现在已经成为我们日常生活的一部分。这种向人工智能辅助的爆炸性转变并不是来自单个的技术创新,而是作为我们生活和数字服务之间的辅助层而发展起来的多种创新,无论我们是在问路、在线购物还是在银行。事实上,IDC 预测,从 2020 年到 2024 年,全球在人工智能上的支出将翻一番,增长到超过 1100 亿美元,其中零售银行预计支出最多。
令人惊讶的是,尽管对话式人工智能提供了所有好处,但许多项目都因一开始发现不足而失败,这就是为什么在前期花时间检查正在构建什么以及它将向客户提供的价值至关重要。由于在该领域学到的经验有助于提高成功几率,以下七个步骤可以作为几乎每个行业的企业在着手或推进现有对话式人工智能平台时的指南:
1。确定你试图解决的问题
仅从数字来看,聊天机器人的商业价值通常是显而易见的。但是数字本身并不能保证成功。当决定从哪里开始或下一步做什么时,平衡 ROI 和客户需求至关重要。例如,许多银行会花几个月的时间建立一个系统,结果却发现客户对交付的东西毫无兴趣。有多种方法可以在早期与客户一起测试创意。例如,通过 Botmock 或 Botsociety,可以在研究环境中创建快速原型并交付给客户。许多企业使用绿野仙踪方法,该方法允许用户在真实环境中与充当虚拟助理的真实代理进行交互,以测试假设并验证他们做出的风险假设,从使用聊天机器人切换抵押贷款交易到从 Alexa 接收帐户余额。从长远来看,这可以节省几个月浪费的设计和开发时间。
2。根据对话式人工智能愿景调整组织
为了确保你的对话式人工智能程序不被视为一个数字副业,创建一个共同的愿景,并确保它是更广泛的接触战略的一个重要支柱。无论是尝试第一个对话式界面,还是开发更复杂的平台和体验能力,企业的利益相关者都应该接受对话式人工智能,不仅仅是一种界面类型,而是实现更大组织目标的一种手段,这一点很重要。为了确保企业围绕共同愿景保持一致,并为实现这一愿景做好组织准备,请主动解决以下四个核心领域的问题:
1.阐明一个现实而又雄心勃勃的未来愿景
2.解决组织孤岛问题
3.知道何时转向
4.灵活构建未来路线图
3。战略性地考虑你的对话平台架构
当心那些声称实现这种技术既快又容易的组织的推销。尽管在自然语言生成(NLG)方面取得了进步,但在没有正确方法的情况下,实现和训练对话式人工智能相对来说是手动和耗时的。具有正确构建模块和数据驱动方法的灵活架构是尽可能多地实现流程自动化并快速实现价值的关键。在可能的情况下,利用现有投资,并考虑所有可能希望在未来部署对话式人工智能解决方案的业务部门的需求。将虚拟代理视为具有两个不同的架构阶段是有帮助的:
1.一个与关键通信渠道集成的对话平台,可以无缝地移交给这些渠道中的人工代理。它没有与后端企业系统的任何集成,但它已经可以提供重要的价值。
2.一个带有身份验证的对话平台,可以挂接到后端企业系统,以解锁端到端用例,如事务性查询。
选择合适的 NLU 很重要(IBM Watson,Google Dialogflow 或 Amazon Alexa 等。)和对话构建工具,如果提供 NLU 的平台不能提供满足所需需求的工具。市场上没有最佳方案,因为最佳解决方案将取决于业务需求、更广泛的生态系统和技术以及云提供商。
4。获得合适的资金并创造动力
虽然创建一个对话式人工智能程序可能被证明是必要的,但一些程序可能会因为实现和运行一个成功的程序所需的时间和金钱而推迟。因此,从较低的初始投资开始,展示好处,然后扩展到数百万客户,如果这是目标的话。为了帮助做出决策,请回答以下问题:
这项技术是否有助于利用数据比以前更好地了解客户?
员工如何从虚拟代理中受益?
对话式人工智能如何帮助提高收入?
是否有可以通过自动化解决的关键投诉驱动因素?
如何利用这项技术来利用其他形式的人工智能和自动化来真正解决业务和客户问题?
如果人工智能的能力从对话式人工智能中释放出来,这如何再投资?
虚拟代理能够启动或扩展客户想要的更多渠道吗?
虚拟代理如何帮助执行主动营销或参与?
对话式人工智能如何帮助减少欺诈或确保合规?
5。为合适的人才配备员工:从对话分析师开始
最重要的一点是,对于 web 或应用程序团队来说,提供出色对话体验所需的角色和技能是不同的。预计将不得不建立新的角色简介,并从外部招聘。困难的部分是找到在这个领域有相关经验的人才,而这个行业对他们的需求如此之大。可能有必要创建当前可能不存在的新角色类型,如对话分析师,他将使用机器学习算法和自然语言处理来研究您的客户说话的方式,并使用这些见解来培训您的虚拟代理,以及对话设计师,文案/UX 设计师,他们可以创建对话流,编写对话,利用丰富的功能(如快速回复,按钮和传送带),并随着时间的推移优化体验。
6。尽早为你的对话式人工智能创造一个角色
实现对话式人工智能的最终目标是创建一个虚拟代理,它是一个具有迷人角色的品牌大使。首先考虑典型客户的人口统计和心理特征。如果可能,使用客户角色;如果没有,从头开始创建。然后创建一个背景故事作为这个对话式人工智能程序的指南。考虑年龄、性别、种族、家庭背景、经历、职称、喜欢、不喜欢和性格特征。进行客户测试,深入了解影响客户个性认知的众多因素,包括入口点和界面的大小、风格和布局、丰富功能的使用、发送消息的长度以及文本气泡的发送速度等等。从参与的角度来看,人物角色很重要,但这也是鼓励客户使用自然语言与您的虚拟代理交谈并释放这项技术的真正力量的唯一方式。
7。通过敏捷设计和交付优化您的虚拟代理
为了交付一个成功的对话式人工智能解决方案,采用敏捷的思维方式并拥抱设计思维。许多对话式人工智能团队仍然严重依赖过程映射工具,如 Visio 或 Lucid Chart,来创建设计。相反,选择在无代码、快速原型对话设计工具中进行设计。这使得设计师可以快速创建模型,甚至使用自然语言与原型进行交互。这样做最大的好处是能够在几个小时内和真正的客户一起测试虚拟助理,并快速学习,完全独立于开发团队。
一旦启动,持续监控并根据需要进行改进,以满足并超越客户的需求。毫无疑问,今天的组织有机会站在下一波转型的最前沿,无缝集成对话式人工智能,以解决在旅程的每一步为时间紧迫的客户提供服务和帮助的复杂挑战。当然,对话式人工智能不是万能的解决方案,但几乎可以肯定的是,通过识别能够以最小的努力实现最大价值的客户交互,可以获得立竿见影的效果。
使用直接来自图像目录的未标记图像文件开发卷积神经网络模型
艾莉娜·格鲁布尼亚克在 Unsplash 上的照片
使用图像生成器将图像自动标记为子目录
卷积神经网络是图像分类的重要工具。它也可以执行其他人工智能任务。但是本文将主要关注图像识别。我有一篇关于卷积神经网络的前向传递如何工作以及如何在 Tensorflow 和 Keras 中实现卷积神经网络的详细文章。请在这里随意查看:
在上面文章的例子中,每张图片都为我们做了标记。但是在现实世界中,图像并不总是被贴上标签,贴标签需要花费金钱和时间。因此,本文将使用一种不同的技术。
问题概述
本文中的示例没有带标签的数据。我们将图像文件作为输入,模型将直接从目录中获取图像文件,并了解图像。最后,如果我们输入一个图像文件,模型应该以相当高的精度对其进行分类。
资料组
如果您正在使用卷积神经网络教程搜索图像分类,您可能会发现许多关于胸部 X 射线图像的图像分类以检测肺炎的示例。
我还使用了来自 Kaggle 的开源二进制数据集,其中包含胸部 x 光图像。这里是数据集的列表信息。请随意从这个链接下载数据文件夹并跟随。
在本练习中,X 射线图像将以图像文件的形式输入。对于训练,我们将有 1349 个正常胸部状态的图像文件和 3883 个肺炎胸部的图像文件。对于测试,我们有 124 个正常图像文件和 390 个肺炎图像文件。
数据预处理
Kaggle 中的数据集已经分别组织在训练和测试文件夹中。所以,我们不必为了训练和测试而拆分文件。此外,在训练文件夹中,正常胸部图像和肺炎胸部图像被分离到单独的子文件夹中。我把数据下载到我的电脑上,直接使用。
这是邮件文件夹,该文件夹包含以下两个子文件夹:
每个子文件夹都有两个子文件夹:
这些子目录的名称将用作模型中数据的标签。当子目录的名称为“正常”时,标签也为“正常”,当子目录的名称为“肺炎”时,标签为“肺炎”。
在进入模型之前,让我们检查子目录中的一些图片。首先,我们需要保存正常和肺炎 X 射线图像的目录路径。我只是从培训目录中打印。这些是我笔记本电脑中的文件夹位置。你需要提到你的笔记本电脑中的文件夹位置。
import ostrain_normal = os.path.join (r"C:\Users\rashi\OneDrive\Desktop\New folder 1\Launchcode\Tensorflow\chest_x-ray_CNN\Pediatric Chest X-ray Pneumonia\train\NORMAL")train_pneumonia= os.path.join(r"C:\Users\rashi\OneDrive\Desktop\New folder 1\Launchcode\Tensorflow\chest_x-ray_CNN\Pediatric Chest X-ray Pneumonia\train\PNEUMONIA")
现在,我们可以简单地将文件名与目录路径连接起来,以找到文件的路径并绘制它们。os.listdir 给出了目录中文件的名称。
import matplotlib.image as mpimgnormal_img = [os.path.join(train_normal, file)
for file in os.listdir(train_normal)[:3]]
plt.figure(figsize=(12, 3))
for i, img_path in enumerate(normal_img):
sp = plt.subplot(1, 3, i+1)
sp.axis('Off')
img = mpimg.imread(img_path)
plt.imshow(img)
plt.show()
作者图片
肺炎目录中的一些图片:
pneumonia_img = [os.path.join(train_pneumonia, file)
for file in os.listdir(train_pneumonia)[:3]]
plt.figure(figsize=(12, 3))
for i, img_path in enumerate(pneumonia_img):
sp = plt.subplot(1, 3, i+1)
sp.axis('Off')
img = mpimg.imread(img_path)
plt.imshow(img)
plt.show()
作者图片
我们需要使用 TensorFlow 的 ImageDataGenerator 函数来识别文件是正常的还是肺炎的。如果需要,ImageDataGenerator 函数可以重新调整数据。然后图像文件将直接从目录中流出。我们将指定图像文件的目标大小为 300x300,文件将自动调整大小。
以下是所有这些的全部代码:
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layersfrom tensorflow.keras.preprocessing.image import ImageDataGeneratortrain_datagen = ImageDataGenerator(rescale=1/255)
validation_datagen = ImageDataGenerator(rescale=1/255)train_generator = train_datagen.flow_from_directory(
r'C:\\Users\\rashi\\OneDrive\\Desktop\New folder 1\\Launchcode\\Tensorflow\\chest_x-ray_CNN\\Pediatric Chest X-ray Pneumonia\\train',
target_size=(300, 300),
batch_size=128,
class_mode='binary'
)validation_generator = validation_datagen.flow_from_directory(
r"C:\\Users\\rashi\\OneDrive\\Desktop\\New folder 1\\Launchcode\\Tensorflow\\chest_x-ray_CNN\\Pediatric Chest X-ray Pneumonia\\test",
target_size=(300, 300),
batch_size = 32,
class_mode = 'binary'
)
输出:
Found 5232 images belonging to 2 classes.
Found 624 images belonging to 2 classes.
您可以看到,它将两个子文件夹(NORMAL 和 PNEUMONIA)检测为训练和测试数据的两个类别。
数据处理完毕。
模型结构
在这个模型中,我使用了四个卷积层,32 个大小为 3×3 的过滤器和 2×2 的最大池层。之后是一个有 512 个神经元的致密层。除了输出层之外,激活功能是“relu”。如果你想了解所有这些元素是如何工作的,请参阅开头提到的文章。
模型如下:
model = tf.keras.models.Sequential([
#Note the input shape is the size of the image 300x300 with 3 bytes color
tf.keras.layers.Conv2D(16, (3,3), activation='relu', input_shape=(300, 300, 3)),
tf.keras.layers.MaxPooling2D(2, 2),
tf.keras.layers.Conv2D(32, (3,3), activation='relu'),
tf.keras.layers.MaxPooling2D(2,2),
tf.keras.layers.Conv2D(64, (3,3), activation='relu'),
tf.keras.layers.MaxPooling2D(2,2),
tf.keras.layers.Conv2D(64, (3,3), activation='relu'),
tf.keras.layers.MaxPooling2D(2,2),
tf.keras.layers.Conv2D(64, (3,3), activation='relu'),
tf.keras.layers.MaxPooling2D(2,2),
# Flatten the results to feed into a DNN
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(512, activation='relu'),
tf.keras.layers.Dense(512, activation='relu'),
tf.keras.layers.Dense(1, activation='sigmoid')
])
模型准备好了。模型摘要如下所示:
model.summary()
下一步是编译模型。我用的是 RMSprop 优化器,学习率 0.0001。你可能会发现精度有点波动。如有必要,请尝试较小的学习速率。
from tensorflow.keras.optimizers import RMSpropmodel.compile(loss="binary_crossentropy",
optimizer=RMSprop(learning_rate=0.0001),
metrics=['accuracy'])
现在是训练时间。模型已准备好接受训练。我只会运行 15 个纪元。这里使用每个时期的步数。这意味着在一个时期完成之前将有 8 个步骤。批量大小将是输入数据除以 8。
history = model.fit(
train_generator,
steps_per_epoch=8,
epochs=15,
verbose=1,
validation_data = validation_generator,
validation_steps = 8
)
精度在 15 个时期内发生了怎样的变化:
import matplotlib.pyplot as plt
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.title('Model Accuracies')
plt.ylabel('Accuracy')
plt.xlabel('Epoch')
plt.legend(['train', 'test'], loc='best')
plt.show()
作者图片
正如你所看到的,验证精度波动很大。让我们看看损失在 15 个时期内是如何变化的:
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('Model Loss')
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.legend(['train', 'test'], loc='best')
plt.show()
作者图片
正如预期的那样,验证损失也大幅波动。
原因可能是学习率太高。我会尝试用较小的学习率。此外,有可能在整个时期不断改变学习率。这超出了本文的范围。改天我会在单独的练习中解决这个问题。总的准确度是好的。所以,我的教程到此为止。
请随意设计一些不同的模型。以下是一些想法:
- 尝试不同数量的卷积层和最大池层。
- 您可以使用不同数量和大小的过滤器。
- 使用不同的激活功能,而不是“relu”。
- 请随意尝试不同数量的神经元,并添加更密集的层。
- 使用不同的优化器而不是 RMSprop。
- 之前已经讲过学习率了。
- 此外,尝试更多的纪元。我只运行了 15 个纪元。并且可以像开头提到的文章中提到的那样随意使用回调。
结论
希望这篇文章是有帮助的!如果你正在尝试不同的想法,并获得更好更稳定的结果,请随时在评论区分享。还有很多其他的方法来改进一个模型。我很快会带着更多的想法回来。
欢迎随时关注我的推特(Twitter)和我的新 T2 YouTube 频道(T3)。
更多阅读
https://pub.towardsai.net/text-data-visualization-with-wordcloud-of-any-shape-in-python-8cec334e5c4f
为多人游戏开发一个通用的 Elo 分级系统
体育分析
用它来证明我是个糟糕的扑克玩家
这篇文章主要是关于多人游戏分级系统的数学设计,它可以应用于任何数量的游戏或运动。在这个过程中,我开发了一个 Python 包,并构建了一个 web 应用程序来展示我(缺乏的)扑克技巧。如果你对这些话题感兴趣,请继续阅读!
Elo 或许是最著名的评级系统。最初发明它是为了衡量棋手的相对技能,从那以后它被应用到许多其他游戏中。近年来,Elo 已经被 FiveThirtyEight 推广开来——例如,他们用它来预测 NFL。
Elo 最大的优点是简单。每个玩家(或团队)在游戏开始前都有一些评级,在我们观察游戏结果后,我们使用一个简单的公式来计算每个玩家的新 Elo 评级。这使得跟踪每个玩家的评级变得容易。如你所料,收视率会发生变化。赢了总会提高玩家的等级,输了总会伤害它。打败一个强大的玩家比打败一个弱小的玩家更有价值。(稍后将详细介绍数学细节。)
一个明显的缺点是 Elo 只对双人游戏有效。许多游戏有两个以上的玩家——赛跑、视频游戏、棋盘游戏等等——我们可能想给这些游戏中的玩家分配等级。如果这些游戏有一个简单的分级系统就好了,但是我们必须超越 Elo。
在和朋友一起玩扑克游戏后,我有动力建立一个多人游戏分级系统。在新冠肺炎疫情期间,扑克是我们被隔离期间的爱好之一——大约每周一次,我们会跳上一个视频电话,每人凑几美元,玩几个小时的扑克。这是一个保持社交距离的好方法,但有一个大问题:我不是一个好的扑克玩家。我一开始也是这么想的,反正我很快意识到我错了。我是一个糟糕的扑克玩家。
对你来说幸运的是,亲爱的读者,我也是一个喜欢把数学带入兴趣爱好的大书呆子。我不满足于知道自己是一个糟糕的扑克玩家;我想精确地量化我有多糟糕。所以我开发了一个标准 Elo 评级系统的简单扩展。
Elo 如何工作
我们先来了解一下 standard Elo 是如何与两个玩家合作的。生成 Elo 评级有三个步骤:
- 预期比分:预测一场比赛的结果。
- 实际得分:观察结果。
- 更新:根据结果增加或减少每个玩家的等级。
让我们更详细地看一下每个步骤。为了预测哪个玩家会赢,我们将每个玩家的当前评级插入一个简单的逻辑函数。假设我们有玩家 A 和 B ,评级分别为 Rᴀ 和 Rʙ 。那么玩家 A 的“期望得分”是
等式 1:标准(双人)Elo 的期望得分公式。
期望得分告诉你参与人 A 赢的可能性有多大。例如,0.75 的预期分数意味着玩家 A 将在 75%的时间里击败玩家 B。评级的较大差异导致较高评级玩家的较大预期分数。注意这里有一个变量 D 。这是我们可以在 Elo 算法中调整的参数之一,它控制着评分差异如何转化为获胜概率。设置 D = 400 是很常见的。
接下来我们观察比赛的结果。让我们使用下面的二进制表示,这样我们就可以很容易地将结果与预期分数进行比较:
等式 2:在标准 Elo 中编码游戏结果。
现在,我们准备更新我们的球员的评级。玩家评分的变化取决于预期结果和观察结果的差异。玩家 A 的新等级是
等式 3:在观察一场比赛的结果后,更新一个玩家的 Elo 等级。
很明显,如果观察到的结果大于(好于)预期分数,评级就会上升。否则就完蛋了。K 值是另一个 Elo 参数,它粗略地决定了一个玩家在一场游戏后的等级可以改变多少。把 K 设置成某个固定值很常见,比如 K = 32。
有时候浏览一个例子会很有帮助。假设玩家 A 以 1200 的评分开始,玩家 B 以 1000 的评分开始,假设玩家 A 赢了这场游戏。上面的等式告诉我们,玩家 a 的期望分数是 Eᴀ = 0.76,实际分数是 Sᴀ = 1,新评分是rˇᴀ= 1207.7。(我在这个例子中用了 D = 400 和 K = 32。)
将 Elo 扩展到多人游戏
这里是我们思考的地方。让我们看看是否可以修改 Elo 计算的每一步,以适应两个以上的玩家。我们将一步一步地解决它,遵循与标准 Elo 相同的三个步骤。
预期分数
Elo 只知道一次比较两个球员。让我们将多人游戏重新定义为两两配对的组合。例如,一个有三个玩家的游戏有三个不同的比赛(A 对 B,A 对 C,B 对 C)。更一般地说,一个有 N 名玩家的游戏有 N ( N -1)/2 个不同的两两对决。
让我们使用标准 Elo,使用上一节中的公式来计算所有成对预期分数。然后我们将每个玩家的个人期望分数相加,得到每个玩家的总期望分数。最后,我们将缩放分数,使所有玩家的分数总和为 1(允许我们将分数解释为概率,就像我们在标准 Elo 中所做的那样)。用数学术语来说,我们对玩家 A 的期望分数如下:
等式 4:多人游戏 Elo 的期望分数计算。我将让读者来验证所有玩家的期望分数之和是 1,并且当 N = 2 时,这个方程收敛于标准的 Elo 方程。
这个等式看起来有点笨拙,但它与标准的 Elo 过程并没有太大的不同(与上面的等式 1 相比)。第一步,完成。
实际分数
接下来是观察和记录结果。这对于 standard Elo 来说非常简单,但在这里会更复杂一些。与标准 Elo 类似,我们希望用这样的分数对结果进行编码:
等式 5:我们如何在多人游戏 Elo 中对结果进行编码的一般形式。
但是 s ₁、 s ₂、…、 sɴ 的价值观到底应该是怎样的呢?对于两个玩家来说,这是微不足道的——胜利者得到 1 分,失败者得到 0 分——但是对于更多的玩家来说,有更多的可能性。是应该 s ₁ = 1,还是应该 s ₁ + … + sɴ = 1?从一个地方到下一个地方,值应该线性增加,还是应该有一些其他的关系?我提出了分数应该满足的几个条件:
- 分数必须单调递减。也就是说,第一名的分数高于第二名,第二名的分数高于第三名,依此类推。
- 最后一名的分数必须为 0。最后一名永远不会提高玩家的评分。
- 所有玩家的得分总和必须为 1。这样,它们与我们上面定义的预期分数处于相同的范围内。
这些规则看起来很合理,但是仍然有不止一种方法来分配满足他们的分数给 N ≥ 3 个玩家——事实上,有无数种方法!但这种灵活性实际上对我们的评级体系是一件好事。让我们称任何满足我们三个规则的函数为得分函数。考虑以下两个得分函数,我们称之为“线性”和“指数”得分函数:
等式 6:线性(顶部)和指数(底部)得分函数。注意,当α→1 时,指数得分函数收敛到线性得分函数,并且当 N = 2 时,两者都产生二进制得分(证明留给读者)。
在这两个等式中, N 是玩家人数, pᴀ 是玩家 a 的名次(1 为第一名,2 为第二名,…, N 为最后一名)。指数得分函数有一个额外的参数 α ,我们可以给它任何大于 1 的值(我也将它称为指数函数的“基数”)。但是,我们不要太纠结于方程式——图形上更清楚:
图 1:N = 5 个玩家的三种不同得分函数的比较。
得分函数在不同玩家之间分享奖励的方式有明显的不同。线性得分函数不在乎你是接近顶端还是底端——从第五名提高到第四名和从第二名提高到第一名一样有价值。另一方面,指数得分函数将更多的奖励给予排名第一的玩家——从第二名提高到第一名价值很大,而第四名仅比第五名多一点奖励。
那么我们应该选择哪个得分函数呢?这取决于我们的用例。我在构建这个评级系统时考虑到了扑克,所以让我们从这里开始,看看它会把我们引向何方。想象一下,你正在参加一场扑克锦标赛——10 名玩家参加,前 3 名将赢得奖金。假设你开始时运不济,发现自己在游戏初期筹码不足。我过于简单化了,但是你有两个基本的选择:
- 被动玩。尽量避免成为第一个筹码用完的玩家。你很可能不会排在最后,但你几乎没有获胜的机会。你可能会慢慢出血,并在接近中间或底部时结束。
- 积极进取,做一些高风险高回报的打法。如果成功了,你就有机会赢了。但是很有可能风险得不到回报,你会获得最后一名。
大多数扑克玩家都会同意策略 2 更好。为什么?因为第四名到最后一名都有相同的现实奖励:0 美元。策略 1 可能会让你免于最后一名的尴尬,但你没有赢钱的机会。策略 2 给了你一些机会来赚取红利,不管多小,所以这是一个更好的策略。从概率的角度来看,策略 2 的收益期望值更高。有时候在扑克游戏中,最好像瑞奇·鲍比一样思考:如果你不是第一名,你就是最后一名。
这和分数函数有什么关系?我们应该选择最能反映游戏最优策略的得分函数。如果我们为扑克游戏选择线性得分函数,我们的评级系统将奖励选择次优策略 1 的玩家——通过避免最后一名,他们获得大量 Elo 评级点数。相反,我们应该使用一个指数得分函数,它更强调接近顶端。同样,这完全取决于用例。对于不同类型的游戏——可能是赛车——我们可能会选择使用线性得分函数。
更新评级
最后,我们准备在游戏结束后更新每个玩家的 Elo 等级。这一步几乎与标准双人 Elo 版本相同:
等式 7:多人游戏 Elo 的等级更新规则。几乎和标准 Elo 一模一样!
我们所做的唯一调整是,我们根据比赛中的玩家数量进行调整。通过这种方式,获胜的玩家将因击败 N -1 名其他玩家而获得足够的奖励,而不仅仅是一名玩家。
就是这样!我们有一个通用版本的 Elo,适用于两个以上玩家的游戏!我最喜欢这个评级系统的特性是,当你只有两名球员时,它会收敛到标准 Elo 将 N = 2 代入公式,你就可以看到为什么了。这就是为什么我们可以认为这是 Elo 的通用版本,而不是一个全新的评级系统。
处理领带
一些游戏允许平手(包括国际象棋,Elo 评级就是为此而发明的)。在标准的双人 Elo 中,当玩家平手时,实际得分为 0.5。在多人游戏 Elo 中,我们正常计算实际得分曲线,然后平均所有平手玩家的相应值。这样,实际分数的总和仍然是 1,其余的计算可以正常进行。
Python 实现
现在我们有了一个方法论。太好了。我们实际上如何使用它?
我构建了一个 Python 包!你可以从 GitHub 上我的 multielo 库安装它。自述和演示笔记本应该包含您开始使用所需的所有信息。
这个包主要实现了上一节中的数学,加上一些类和函数,用于跟踪许多玩家的 Elo 评分。它还包括一些我不会在本文中讨论的东西,比如模拟多人游戏中玩家的获胜概率。如果你对这个包有任何疑问,请在这里或 GitHub 上告诉我!
给我看看结果吧!
好了,终于到了扔一些真实数据的时候了。看我悲伤的扑克之旅:
我的扑克组中所有玩家的 Elo 历史。哎哟。
就目前的情况来看,我在最后一名的位置上有一种束缚感,这种束缚感随着每一场比赛的进行而变得更加强烈。不可思议的是,我赢了第一局(注意最左边向上的小光点),但那一定是新手的运气。从那以后就一直在走下坡路。
我通过计算每个玩家在每场比赛结束时的新 Elo 等级来创建这个图表。每个玩家从 1000(任意值)开始,然后根据他们的结果向上或向下移动。每场比赛结束后,玩家的评分变化取决于他们结束的位置以及其他玩家的参与情况。
扑克爱好者:需要注意的一点是,我们玩的是锦标赛风格的游戏,所以谁第一、第二、第三等等总是很清楚的。如果我们玩的是现金游戏,这种类型的评级系统就不容易应用,因为在游戏结束时不一定有明确的赢家和输家。]
交互式网络应用
如果你想更详细地了解收视率,可以查看我在这里制作的网络应用程序:【https://poker-elo-dashboard.herokuapp.com
我为我的扑克小组创建的网络应用的演示。(没错,它是以一种不知名的威斯康星啤酒命名的。)
在应用程序中,你会发现我们的扑克游戏的完整历史,加上一些工具来编辑游戏历史和 Elo 参数,看看它会如何影响评级。
我使用 Dash 构建了这个应用程序,Dash 是一个仅使用 Python 创建仪表盘和 web 应用程序的流行框架。该应用程序从谷歌工作表中读取数据,并使用我的 multielo 包来计算 elo 评级。该应用程序被托管在云平台 Heroku 上,这让你可以免费托管小型网络应用程序。
web 应用的源代码也可以在 GitHub 的上找到。这是我开发的第一个 Dash 应用,很容易上手。有很多很棒的 Dash 教程,但如果你想让我详细说明我是如何构建我的应用程序的,请留下评论。
如果你做到了这一步,我希望你能找到一些有趣或有用的东西。如果您对 Elo 评级系统、Python 包或 web 应用程序有任何问题或建议,请留下评论以便联系。或者你有什么建议可以帮助我提高扑克技巧,我洗耳恭听!
成为媒体会员访问成千上万作家的故事!
为银行营销数据集开发多层感知器
通过分析银行营销数据集预测客户是否会订阅定期存款
克里斯·利维拉尼在 Unsplash 上的照片
在这篇文章中,我将一步一步地解释如何开发一个多层感知器模型。为此,我将获取一个与葡萄牙银行机构的直接营销活动(电话)相关的数据集(可从 UCI 机器学习库访问),并预测客户是否会订阅定期存款。
本教程的先决条件
在本教程中,我假设您知道什么是神经网络,神经网络由什么组成,以及学习过程如何在神经网络(NN)中发生:通过不断计算特定训练轮次的误差值并更新学习率。
因此,在本文中,我直接解释什么是多层感知器模型(MLP ),以及如何开发 MLP 来解决现实世界的问题。
在本教程中,我还假设您知道如何使用来自 Google Colab 的 Python 或任何其他工具创建一个基本的神经网络模型。
本文最后给出了本文描述的代码的链接。
本文分为以下几个部分。因此,如果您已经知道某个特定的部分,您可以直接进入您需要了解的部分。
- 教程所需的背景知识
- 数据集概述
- 开发 MLP 神经网络模型
1.教程所需的背景知识
在开始理解如何开发 MLP 模型之前,让我们首先确定我们在这些概念上是一致的。
什么是人工神经网络中的多层感知器模型?
一个多层感知器 (MLP)是一个神经网络在一个有向图中连接多个层,这意味着连接层中节点的路径只是单向的。除输入节点外,每个节点都有一个非线性激活函数和。一个 MLP 使用反向传播作为一种监督学习技术[1],以便当考虑到模型已经学习的内容时,误差值可以以一种非常成功的方式更新。
银行营销和定期存款领域知识
定期存款有时也称为定期存款,是银行的一种投资形式;它是一笔一次性的金额,按照约定的利率在银行存一段固定的时间。客户倾向于定期存款,以确保他们的钱在未来的几年里得到更多的价值,并且他们能够存下更多的钱,而不是维持一个储蓄账户。
另一方面,银行向客户提供定期存款是有益的,因为这是银行获得更多资金的一种方式。相反,银行向客户提供比储蓄账户更好的利率。
银行开展促销活动,吸引客户在他们那里开立定期存款账户;如电话、电子邮件、广告活动。
2.数据集概述
如上所述,该数据集是从 UCI 机器学习储存库中获得的,并且具有以下特征。
作者截图-数据集一瞥
有 17 个变量,包括 16 个特性和类变量。数据集中所有特征的描述在数据源中给出,该数据源可从 UCI 机器学习库中找到。
当观察数据集时,我们可以看到数据集包含分类名义值(如工作、婚姻状况、住房贷款可用性等。)、分类序数值(如教育,可以从最低点到最高点确定一个顺序),最后是离散和连续值。
检查类值计数以了解数据集中有多少个类以及每个类标签有多少个值。
作者截图—检查类标签的值计数
通过考虑类变量的计数,我们可以看到数据集是一个不平衡的数据集,因为“否”的数量几乎是“是”类的 8 倍。在本教程中,我不会处理类的不平衡,但可以通过 SMOTE 等技术来处理。
3.开发 MLP 神经网络模型
当开发一个神经网络模型时,有三个主要阶段需要我们关注。他们是;
a.按照数据挖掘过程中指定的方式预处理数据集
b.如有必要,执行特征工程
c.将数据集分为训练集和测试集。训练模型,测试和预测预期结果。
现在,我们将研究如何执行上述每个阶段。
a.按照数据挖掘过程中指定的方式预处理数据集
预处理数据包括处理缺失值和异常值,必要时应用特征编码技术,缩放和标准化特征。
检查缺失值
在使用 Pandas 时,我们可以使用 isnull()找到标准缺失值(Pandas 可以检测到的缺失值),并使用 isnull()获得缺失值的汇总。sum()。[2] [3]
按作者划分的屏幕截图-缺失值的摘要
另一种类型的缺失值是非标准缺失值,这是熊猫自己无法找到的,需要我们的帮助[2]。当手动扫描数据集时,我们可以看到有 4 个字段包含值“未知”。
作者截图—缺失值“未知”的摘要
处理异常值
离群值是偏离标准数据集很多的数据点。在训练和构建模型时,数据集中的异常值会影响最终的准确性。因此,我们必须找到并消除这些异常值。
我们只能检查数值特征中的异常值。因此,我一个接一个地检查了每个数字特征,画出箱线图来识别异常值,并去除它们。
我检查过的异常值的数字特征有“年龄”、“余额”、“日”、“持续时间”、“活动”、“天数”和“先前”。成功移除 119 个异常值后,数据集中新的数据点数是 45092(之前是 45211)。
盒式图在笔记本/Colab 文件中一个接一个地显示。
特征编码
在这个过程中,分类数据被编码成数字数据。
- LabelEncoder 用于对类值进行编码。
- OrdinalEncorder 用于对“教育”特征进行编码,因为当考虑该字段中存在的值时,订单可以被视为二级、三级等。
- OneHotEncoding 用于对具有其他分类值的要素进行编码。(功能—“工作”、“婚姻”、“联系人”、“月份”、“poutcome”)
- 手动二进制编码(使用字典)用于对具有是/否值的其余特征进行编码(“默认”、“住房”、“贷款”)
至此,我已经将数据集中的所有值编码成数值
拆分数据
我用 20%的测试规模分割了数据。
特征缩放
对分类数据进行编码后,数据集由具有不同数据范围的要素组成。这些值是标准化的,特征缩放按如下方式进行。数字特征通过去除平均值和缩放至单位方差(标准缩放器)进行缩放,如下所示。
b.特征工程
特征选择是机器学习的核心概念之一,它会极大地影响模型的性能。您用来训练机器学习模型的数据特征对您可以实现的性能有着巨大的影响。不相关或部分相关的特征会对模型性能产生负面影响。
绘制相关矩阵
因此,我将执行相关系数检查机制,以检查不同特征与输出之间的关系。
这些相关类型中的每一种都可以存在于由从 0 到 1 的值表示的谱中,其中轻微或高度正相关特征可以是类似于 0.5 或 0.7 的值。如果存在强的和完美的正相关,那么结果由 0.9 或 1 的相关分值表示。[4]
作者截图—关联矩阵
我生成的关联矩阵有很多元素,因为在 OneHotEncoding 之后,数据集中的列数增加了。
生成相关矩阵后,我们可以看到,在矩阵的右侧,有一些具有非常高相关性的特征。我们通常会删除相关性高的特征,因为它们与其他特征存在某种线性相关性。这些特征对预测输出贡献很小,但增加了计算成本。[5]
很明显,相关特征意味着它们带来相同的信息,因此移除其中一个是合乎逻辑的。[6]
为了找到具有高相关值的确切列,我执行了下面的代码。我正在检查相关矩阵的上部三角形,因为上部和下部三角形是彼此的镜像,被相关矩阵中的对角线分割。在这里,我检查相关值大于 0.95 的列,希望删除它们。
但是,在执行了上面的代码之后,我们可以看到没有任何列的相关性超过 0.95,因此没有要删除的列。
应用 PCA
主成分分析(PCA)是一种降维方法,通常用于降低大型数据集的维数,方法是将一个大型变量集转换为一个较小的变量集,该变量集仍包含大型数据集中的大部分信息。[7]
这里,我没有手动设置 PCA 模型的 n_components。我们希望解释的方差在 95–99%之间。因此,我将 PCA 的 n_components 设置为 0.95 [8]
c.开发多层感知器模型
我用 scikit 学习包中的 MLPClassifier 制作了 MLP 模型。[9]
生成混淆矩阵
使用混淆矩阵,我们可以发现有多少真阳性、假阳性、假阴性和真阴性。
作者截图——我构建的模型的混淆矩阵
上面的混淆矩阵显示有 340 个真阳性和 7400 个假阴性,这对于不平衡的数据集来说仍然是好的。假阳性的数量是 570,真阴性的数量是 750。
最后,我列出了均方差值、训练集分数、测试集分数等。如下。
作者截图—培训测试成绩输出
我们可以看到,我创建的这个模型的训练准确率为 97.369%,而测试准确率为 85.386%。
为这个任务写的代码可以从这里找到。
参考
[2] 用 Python 和 Pandas 清理数据:检测缺失值
[3] 处理熊猫中的缺失数据
[4] 为什么特征相关性很重要…很多!
【5】如何在 Python 中去掉高度相关的特性?— DeZyre
[7] 主成分分析(PCA)的逐步解释
【8】PCA—如何选择分量数?
使用 GCP 开发一个具有强化学习的类似 rogue 的游戏
强化学习(RL)的许多应用都是专门为了将人从循环中解放出来。例如,OpenAI Gym [1]为训练 RL 模型充当 Atari 游戏中的玩家提供了一个框架,并且许多出版物描述了将 RL 用于机器人。然而,一个经常讨论不足的领域是应用 RL 方法来改善人类的主观体验。
为了演示这种应用,我开发了一个简单的游戏,名为“禁冰宫殿的考验”[2]。这款游戏采用强化学习,通过为用户量身定制游戏难度来提高用户体验。
游戏如何运作
游戏是传统的 roguelike 游戏:回合制地牢爬虫,有 RPG 元素和大量的程序生成。玩家的目标是逃离冰宫,一层一层地,一路上与怪物战斗,收集有帮助的物品。虽然每个楼层出现的敌人和物品传统上是随机生成的,但这个游戏允许 RL 模型根据收集的数据生成这些实体。
由于强化学习算法是出了名的数据饥渴,所以游戏被创建为具有以下约束以降低 RL 模型的复杂性:
1)游戏共有 10 层,之后玩家获胜
2)每层可以繁殖的敌人和物品数量是固定的
强化学习和环境
强化学习的核心概念是自动代理通过观察和采取行动与环境交互,如图 1 所示。通过与环境的互动,代理可以获得奖励(积极的或消极的),代理用这些奖励来学习和影响未来的决策。
图一。用于强化学习的代理和环境之间的交互(图片由作者提供)
对于这个应用程序,代理是 RL 算法,它根据选择产生的实体来调整游戏的难度,游戏是 RL 算法可以观察和控制的环境。
状态
状态是代理对环境进行的任何观察,它可以用于决定采取哪些行动。虽然代理可能会观察到大量不同的数据(玩家的健康状况、玩家前进一层所需的回合数等),但游戏的第一个版本的变量只考虑玩家已经到达的楼层和玩家角色的级别。
行动
由于游戏是按程序生成的,代理将决定随机生成怪物/物品,而不是每次都有确定性的决定。由于有很大的随机性,代理人不会以典型的 RL 方式探索/利用,而是控制游戏中不同敌人/物品的加权概率。
当代理选择行动时,基于利用迄今为止最好的学习模式,它将通过学习 Q 矩阵的加权随机抽样来决定在游戏中产生哪个敌人/物品;然而,如果代理人选择探索,代理人将从游戏中的所有实体中以相等的概率产生一个敌人/物品。
奖励
强化学习算法的奖励模型对于学习模型应该显示的预期行为的发展至关重要,因为机器学习方法以走捷径来实现其目标而闻名。因为预期目标是最大化玩家的乐趣,所以根据 RL 算法的奖励,已经进行了以下假设来量化乐趣:
-一个玩家在游戏中比早死的人进步更快,会有更多的乐趣
-玩家每次都赢而没有挑战的游戏是无聊的
考虑到这些目标,当玩家前进到表 I 中所示的新楼层时,以及当游戏如表 II 中概述的那样完成时,RL 模型接收奖励。
表 1:玩家进展的奖励模型
表二:游戏完成的奖励模型
考虑到上面的进展和完成评分机制,RL 算法将通过允许玩家进展到第 8 层来最大化奖励,在第 8 层,玩家应该最终迎接他们的死亡。为了尽量减少意外行为的机会,RL 算法也会因玩家过早死亡而受到惩罚。
更新模型
RL 算法采用 Q 学习,它已经被修改以适应由代理执行的随机动作。从传统的 Q-Learning [3]修改而来,在传统的 Q-Learning[3]中,代理在状态之间采取 1 个动作,代理的动作被更新,考虑到为楼层产生的所有敌人/物品的概率分布,如下式所示。
其中Q’(s _ t,a_t) 是 Q 矩阵的更新值, Q(s_t,a_t) 是状态 s 和动作 a 对在时间步 t , α 是学习率, r_t 是从过渡到状态 t+1 提供的奖励, γ 是
由于强化学习方法需要大量的训练数据,因此从玩家会话中收集游戏数据来训练全局 AI 模型,新玩家可以使用该模型作为起点。
通过 GCP 全球化 RL 培训
使用由所有玩家收集的游戏数据来训练全局 AI 模型,并且当玩家还没有玩游戏时,将其用作基础 RL 模型。新玩家在第一次开始时会获得一个全局 RL 模型的本地副本,当他们玩游戏时,它会根据他们自己的游戏风格进行定制,而他们的游戏数据将用于进一步增强未来新玩家的全局 AI 模型。
图 2 数据管道和 RL 模型训练架构(图片由作者提供)
图 2 所示的体系结构概述了如何收集数据以及如何更新和分发全局模型。使用 GCP 是因为他们的免费层使用产品最适合为模型训练收集和存储游戏数据[4]。在这方面,游戏经常调用云函数调用 GCP 来将数据存储在 Firebase 数据库中。
结论
本文中介绍的工作描述了如何使用强化学习来增强玩家玩游戏的体验,这与更常见的用于自动化人类动作的 RL 应用相反。使用自由层 GCP 架构的组件收集所有玩家的游戏会话数据,从而允许创建全局 RL 模型。当玩家以全局 RL 模型开始游戏时,他们的个人经历创建了定制的局部 RL 模型以更好地适应他们自己的游戏风格。
参考
[1] 奥鹏艾健身房,https://gym.openai.com T2
【2】罗格里克尔——禁冰宫试炼,https://drmkgray.itch.io/roguelikerl-tfip
[3]卡尔布林、利特曼和摩尔(1996 年)。强化学习:综述。《人工智能研究杂志》, 4,237–285。https://arxiv.org/pdf/cs/9605103.pdf
[4]https://cloud.google.com/free、GCP 自由级
开发用于实时视频处理的 streamlit-webrtc 组件
实践教程
介绍用于实时媒体流的 WebRTC 组件
在开发各种计算机视觉或机器学习模型时,实时视频处理是最重要的应用之一。它很有用,因为它允许用户通过自己的设备(如网络摄像头或智能手机)快速验证他们的模型可以做什么。
但这也给我们这些使用 Streamlit 的人带来了挑战,因为 Streamlit 本身并不支持实时视频处理。
我创建了 streamlit-webrtc ,一个使 streamlit 能够通过网络处理实时媒体流的组件来解决这个问题。在这篇深入的教程中,我还将简要地向您介绍 WebRTC(查看我的文章,这里有关于 WebRTC 的更深入的信息)。如果你想直接玩组件,这里有一个示例应用程序。
准备好了吗?
让我们开始吧。
(本教程需要 Python >= 3.6 和网络摄像头。)
现有方法的问题是
Streamlit 被许多开发人员和研究人员积极用于构建由计算机视觉和机器学习模型支持的应用原型,但它还不能天生支持实时视频处理。
利用 Streamlit 实现实时视频处理的一种现有方法是使用 OpenCV 来捕获视频流。然而,这仅在 Python 进程可以访问视频源时有效,换句话说,仅当摄像机连接到运行应用程序的同一主机时有效。
由于这一限制,将该应用程序部署到远程主机并将其用于本地网络摄像头的视频流一直存在问题。cv2.VideoCapture(0)
消耗来自第一个(索引为 0)本地连接设备的视频流,当应用托管在远程服务器上时,视频源是连接到服务器的摄像设备——而不是本地网络摄像头。
WebRTC 如何解决这个问题
webRTC (Web 实时通信)使 Web 服务器和客户端(包括 Web 浏览器)能够通过网络以低延迟发送和接收视频、音频和任意数据流。
它现在受到 Chrome、Firefox 和 Safari 等主流浏览器的支持,其规范是开放和标准化的。像 Google Meet 这样基于浏览器的实时视频聊天应用是 WebRTC 使用的常见例子。
WebRTC 扩展了 Streamlit 在前端和后端进程(如浏览器 JavaScript 和服务器端 Python)之间传输视频、音频和任意数据流的强大功能。
WebRTC 基础知识
下面的教程使用了关于 WebRTC 概念的知识,比如“信令”、“提供”和“应答”。下图简单总结了如何建立 WebRTC 连接。
- WebRTC 有一个称为“信令”的准备阶段,在此期间,对等体交换称为“提供”和“回答”的数据,以便收集必要的信息来建立连接。
- 开发人员可以选择任意的信令方法,比如 HTTP req/res 机制。
如果你想了解更多这些概念,请阅读这篇文章。
正如在上面链接的文章中一样,本教程将使用用于 WebRTC 的 Python 库 **aiortc**
,以及来自 [**aiortc**](https://github.com/aiortc/aiortc/tree/2362e6d1f0c730a0f8c387bbea76546775ad2fe8/examples/server)
资源库 的 示例作为我们示例项目的基础。
Streamlit 执行模型的基础
要进一步阅读,您应该了解 Streamlit 双向定制组件的开发以及 Streamlit 的执行模型。你可以在这里了解一下。
这里有一个简短的总结:
- 每次执行时,Python 脚本都是从上到下执行的。
- Python 脚本的每次执行都会呈现前端视图,将数据作为组件的参数从 Python 发送到 JS。
- 前端通过
Streamlit.setComponentValue()
触发下一次执行,将数据作为组件值从 JS 发送到 Python。
将aiortc
集成到一个简化组件中
在本节中,为了理解如何将 WebRTC 实现集成到 Streamlit 定制组件中,我们将创建一个名为tiny-streamlit-webrtc
的streamlit-webrtc
的最小版本,作为实践教程。
tiny-streamlit-webrtc
的源代码托管在 GitHub 上。在本教程中,我们将参考这个存储库,一步一步地检查每个中间提交,以获得最终版本。
建议您克隆存储库:
$ git clone [https://github.com/whitphx/tiny-streamlit-webrtc.git](https://github.com/whitphx/tiny-streamlit-webrtc.git)
$ cd tiny-streamlit-webrtc
使用下面的命令,您可以检查每个部分中引用的特定修订,以便查看整个代码库并实际尝试运行它。
$ git checkout <revision>
安装依赖项
安装必要的软件包。注意,本教程不适用于最新版本的aiortc
( 1.1.1
),必须使用1.0.0
。
$ pip install streamlit opencv-python
$ pip install aiortc==1.0.0
设置项目
像往常一样,我们从双向组件的官方模板开始。参考[tiny-streamlit-webrtc](https://github.com/whitphx/tiny-streamlit-webrtc)
实施基于修订4b90f52
。
复制模板文件后,完成其余的设置,包括以下步骤。
- 将
my_component
重命名为tiny_streamlit_webrtc
。 - 在
tiny_streamlit_webrtc/frontend
中运行npm install
。 - 删除现有的代码、注释和文档字符串。
- 添加必要的文件,如
.gitignore
查看这部分做什么,代码版本[f6daf28](https://github.com/whitphx/tiny-streamlit-webrtc/tree/f6daf280c650c04ae45f114fff4a4d0fd39a14c1)
。
推出第一个前端实施
让我们开始写代码。
首先,我们将简单地从[aiortc](https://github.com/aiortc/aiortc/tree/2362e6d1f0c730a0f8c387bbea76546775ad2fe8/examples/server)
示例中的index.html
和client.js
复制并粘贴一些代码到我们的 React 组件中,但是做了一些修正。
[e3f70e4](https://github.com/whitphx/tiny-streamlit-webrtc/commit/e3f70e44bbd17d383abfdbef2f2d9e961d1a47e6)
是实际的编辑,您可以通过检查提交来尝试这个版本,如上所述。
$ git checkout e3f70e4
该视图只包含一个带有autoPlay
和playsInline
属性的<video />
元素,就像在最初的index.html
中一样,以及一个启动 WebRTC 会话的按钮元素。开始按钮的onClick
处理程序被绑定到start()
方法,该方法是从client.js
复制过来的,并稍作修改,删除了本教程不需要的一些行,并调整为基于 React 类的组件样式。我们将对negotiate()
和createPeerConnection()
做同样的事情。
让我们以简化定制组件开发的通常方式运行这个组件。
$ cd tiny_streamlit_webrtc/frontend/
$ npm start$ streamlit run tiny_streamlit_webrtc/__init__.py
用网络浏览器打开应用程序后,打开开发者工具,点击“开始”按钮。您可以在控制台中看到报价的生成和打印,如下所示。
这是通过这条线打印的。请按照步骤进行。这段代码相当于在向 Python 服务器发送要约之前原始示例 中的代码。是的,这个案例与原来的例子不同。我们如何将提议发送到 Python 流程?
(您还可以看到,自从[navigator.mediaDevices.getUserMedia()](https://github.com/whitphx/tiny-streamlit-webrtc/commit/e3f70e44bbd17d383abfdbef2f2d9e961d1a47e6#diff-c0bb5335a5a993d716414831b4151b2a7070e4533adb552831b5f22a4b32da1cR95)
请求使用以来,您的网络摄像头已变为活动状态。)
将报价从 JS 发送到 Python
为此,streamlit-webrtc
使用了Streamlit.setComponentValue()
。我们将在本节中了解它。
[7b7dd2d](https://github.com/whitphx/tiny-streamlit-webrtc/commit/7b7dd2d2f9289b7f6697a3ab915fd8dc6438afd9)
是下次更新。使用git checkout 7b7dd2d
来检查它。
通过这种改变,报价作为组成值从前端发送到服务器。
const offerJson = offer.toJSON()
Streamlit.setComponentValue({
offerJson,
})
该报价可以在服务器端读取,如下所示。
component_value = _component_func(key=key, default=None)
if component_value:
offer_json = component_value["offerJson"]
让我们运行这个版本,并确认在单击“Start”按钮后显示要约,这意味着要约被 Python 进程接收,并在这里显示为st.write()
。
使用asyncio
的服务器端实现
现在要约在服务器端被接收,所以让我们实现代码来处理它。就像我们做前端一样,让我们从示例server.py
复制粘贴到我们的streamlit_webrtc/__init__.py
,像这个,它是从示例 [server.py](https://github.com/aiortc/aiortc/blob/2362e6d1f0c730a0f8c387bbea76546775ad2fe8/examples/server/server.py#L102-L167)
中的[offer()](https://github.com/aiortc/aiortc/blob/2362e6d1f0c730a0f8c387bbea76546775ad2fe8/examples/server/server.py#L102-L167)
协程复制过来的。
注意,暂时从track
事件监听器中省略了一个视频转换器,就像现在的一样,以专注于 WebRTC 部分。它现在只是通过输入轨道到达输出。
然而,正如您所看到的,这段代码包含了async
和await
,并且在函数中不起作用。因此,我们必须将这部分封装在一个像 this 这样的协程中。
请运行此版本: [a6f7cc0](https://github.com/whitphx/tiny-streamlit-webrtc/commit/a6f7cc050b5fd07f49800bf264ec7fc34d70bdbb)
并确认答案显示在此处的报价之后。这意味着服务器端的pc
对象已经处理了报价并生成了答案。
接下来我们要做的就是把它送回前端。
将答案从 Python 发回 JS
要做到这一点,streamlit-webrtc
只需依赖如下所示的 Streamlit 从 Python 到 JavaScript 的数据发送机制。
_component_func(key=key, answer=answer)
然而,一个问题出现了。我们已经打电话给component_value = _component_func(...)
并从它那里得到了报价。之后,我们生成了答案。那么,我们如何再次将参数设置为已经调用过的_component_func()
?
简单地像下面这样调用第二个_component_func()
不起作用,因为在 Streamlit 应用程序中,不同的_component_func()
调用被识别为组件的不同实例。
component_value = _component_func()
offer = component_value["offer"]
answer = generate_answer(offer) # Pseudo code
_component_func(answer=answer) # This does not work!
为了解决这个问题,我们不得不介绍一个黑客:SessionState
和st.experimental_rerun()
。使用这些工具,我们可以重新运行脚本,再次调用同一行中的_component_func()
,并在运行期间保存一个变量,以便在第二次和以后的执行中将其提供给_component_func()
。
SessionState
已在本论坛主题的中讨论过,来源可在本页面的要点中找到。
st.experimental_rerun()
顾名思义,似乎是一个实验性的 API,还没有文档。这个 GitHub 问题已经讨论过了,现在可以用了。
请看这个版本的服务器端代码,其中SessionState
和st.experimental_rerun()
用于将生成的答案反馈给组件。
这说明了它是如何工作的。
这里另一件重要的事情是key
参数不再是可选的,而是必须像 this 一样被显式提供。由于答案是作为参数提供给_component_func()
的,并且它的值会随着运行而变化,因此key
作为组件实例的稳定标识符是必要的。
如果key
是None
,则 Streamlit 基于key
之外的参数来标识组件实例,因此当答案改变时,Streamlit 无法在运行期间跟踪组件实例的身份。
注意这个 if 子句被添加来仅在服务器端进程第一次从前端获得提议时调用st.experimental_rerun()
。一旦报价被传递给 Python,这也可以通过在前端重置组件值来实现。
用这个版本: [aa2ab49](https://github.com/whitphx/tiny-streamlit-webrtc/commit/aa2ab49606060e4a038f392500aecbe95c4ec758)
,可以看到答案是作为args
道具的一个字段提供的,就像这个在前端一样。我们用浏览器的 devtools 确认一下吧。
实施processAnswer()
现在我们在前端有了答案。让我们像和一样实现其余的前端代码。
该代码是在收到示例 [client.js](https://github.com/aiortc/aiortc/blob/2362e6d1f0c730a0f8c387bbea76546775ad2fe8/examples/server/client.js#L95-L99)
中的答案后从部分复制的,并根据我们的进行调整。
引入一个运行脚本执行的线程
似乎我们已经做了所有我们要做的事情,但是当你用这个版本: [7fbf0eb](https://github.com/whitphx/tiny-streamlit-webrtc/commit/7fbf0eb5a72de4ea84b21708df80a396fa3222ff)
点击“开始”按钮时,没有视频出现。
问题出在服务器端。来自aiortc
的服务器端 WebRTC 代码运行在一个事件循环上,这个事件循环现在是从asyncio.run()
隐式开始的。创建一个事件循环,在整个 Streamlit 脚本执行过程中,aiortc
函数依赖于该事件循环。但是这个事件循环将在下一个脚本执行中被丢弃,并且aiortc
不能继续工作。
为了解决这个问题,我们将分叉一个线程,并在其中创建一个事件循环来运行aiortc
函数。并且线程对象被存储在SessionState
中以在多个 Streamlit 脚本执行期间被维护。
见本版代码: [093f81b](https://github.com/whitphx/tiny-streamlit-webrtc/commit/093f81be648ad66082e15448b90b74e24e5897b8)
。这个 [webrtc_worker()](https://github.com/whitphx/tiny-streamlit-webrtc/commit/093f81be648ad66082e15448b90b74e24e5897b8#diff-6abc149a63c4cc57ae904cb1f4bad9f0d063f70011a4cb9266fb4411ec839a7eR45-R67)
函数在这里分叉为线程。在这个线程中,创建了一个新的事件循环并且process_offer()
协程正在其上运行——在之前的代码版本中由asyncio.run()
调用。有了这个变化,[queue.Queue](https://github.com/whitphx/tiny-streamlit-webrtc/commit/093f81be648ad66082e15448b90b74e24e5897b8#diff-6abc149a63c4cc57ae904cb1f4bad9f0d063f70011a4cb9266fb4411ec839a7eR92)
T15 被引入到在主线程中获取答案对象,现在在分叉线程中生成。
分叉线程有一个缺点——当你点击Ctrl+c
时streamlit run
命令不会停止。这是因为即使在主线程终止后,分叉线程仍然存在。
要强制终止进程,发送 SIGKILL 如下。
$ ps aux | grep python | grep streamlit # Find the process ID whitphx 19118 11.2 0.6 4759304 99928 s003 S+ 5:27PM 0:02.06 /path/to/venv/bin/python3.8 /path/to/venv/bin/streamlit run tiny_streamlit_webrtc/__init__.py
$ kill -9 19118 # Send SIGKILL to the process specified with the ID
要修复它,分叉螺纹的daemon
选项设置为True
,就像这个一样。有了这个标志,脚本会在必要时正确停止。
线程可以被标记为“守护线程”。这个标志的意义在于,当只剩下守护线程时,整个 Python 程序退出。 【线程对象】【Python.org】
组件高度调节
让我们试试现在的版本: [fc48060](https://github.com/whitphx/tiny-streamlit-webrtc/commit/fc48060224bd69e85528a4b0859107a70dfbe0bf)
。现在,WebRTC 工作了,视频出现了这个组件!但是,显示的视频被裁剪,其下部被隐藏,如下图所示。
要修复它,我们必须在<video />
元素的大小改变时调用Streamlit.setFrameHeight()
。虽然它是在道具更新时自动调用的,但元素 resize 与道具更新无关,而是与开始视频流相关。
现在在<video />
元素上附加onCanPlay
事件处理程序,并像 this 一样从它调用Streamlit.setFrameHeight()
。(虽然使用[ResizeObserver](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver)
可能是观察 DOM 元素调整大小的正确方法,但为了简单起见,我们在这里使用onCanPlay
事件作为替代。)
酷!现在它工作正常。🎉[1a57a97](https://github.com/whitphx/tiny-streamlit-webrtc/commit/1a57a9755e0325ed3839bb259a7655bfe94b679e)
就是这个版本。
现在,WebRTC 的所有核心部分都完成了。我们将在接下来的小节中实现其余部分。
实现您自己的视频过滤器
首先,让我们尝试实现一些视频过滤器。[3ba703d](https://github.com/whitphx/tiny-streamlit-webrtc/commit/3ba703d1604c9edecedcf9bb3bed32706ada023a)
是一个简单的边缘提取器的例子,复制自 [aiortc](https://github.com/aiortc/aiortc/blob/2362e6d1f0c730a0f8c387bbea76546775ad2fe8/examples/server/server.py#L66-L75)
的示例代码。
实现一个停止按钮
参考[aiortc](https://github.com/aiortc/aiortc/blob/2362e6d1f0c730a0f8c387bbea76546775ad2fe8/examples/server/client.js#L184-L210)
示例创建一个停止按钮来优雅地停止流。是当前版本。
streamlit-webrtc
的执行模式
我们按照步骤使用 WebRTC 开发了一个最小的 Streamlit 组件来传输视频。
正如我们在该组件中看到的,我们选择了一种设计,其中计算机视觉代码在分叉线程的回调中运行,由来自输入流的新帧触发,与 Streamlit 的脚本执行时间无关。第一次看到它时,它看起来有点奇怪,但在处理实时流时,这是必要和自然的。
让我们从更抽象的角度来看。当处理来自实时流的帧时,流是除了通过前端视图的用户交互之外的附加事件源。在普通的 Streamlit 应用中,所有触发 Python 脚本执行的事件都只来自前端,它们被 Streamlit 很好地封装起来。
有了它的执行模型,开发者可以在一个干净的世界里编写应用程序,没有回调,没有(或很少)副作用。反过来,如果我们希望以良好的性能处理流,我们必须显式地处理来自流的事件,如帧生成,这打破了优雅的封装,导致回调和事件出现在脚本中。
tiny-streamlit-webrtc
缺少什么
尽管我们已经创建了[streamlit-webrtc](https://github.com/whitphx/streamlit-webrtc)
、tiny-streamlit-webrtc
的一个小子集,但它仍然缺少streamlit-webrtc
拥有的许多重要特性。在这里,我们将回顾其中的一些。
来自 Streamlit 组件的参数输入
使用 Streamlit 的最大好处之一是交互式控件,如滑块和单选按钮。通过计算机视觉和机器学习模型,这些控制对于在执行过程中改变参数非常有用。
因为计算机视觉代码在这个组件的分叉线程中运行,所以我们必须通过线程将从 Streamlit 小部件获得的值传递给 CV 代码。但这并不困难,就像中的 [streamlit-webrtc](https://github.com/whitphx/streamlit-webrtc/blob/f03d3150adfa27c44bb7f2d22d495351090d9341/app.py#L184)
中的样。
使用tiny-streamlit-webrtc
,您可以通过向VideoTransformTrack
添加一个公共属性,并从每个线程中读取和写入它,就像上面链接的示例代码一样。有兴趣的请尝试一下,传递复杂值时注意线程安全。
丢帧
我们在教程中使用了边缘提取作为例子。然而,如果你用像深度神经网络这样计算更昂贵的过滤器来代替它,你会看到显示的视频变慢。只需将time.sleep(1)
放入VideoTransformTrack.recv()
中即可测试。
这是因为VideoTransformTrack.recv()
逐个处理所有的输入帧——如果它延迟,产生输出帧也会延迟。
为了解决这个问题,VideoTransformTrack.recv()
不得不在每次运行时丢弃一些输入帧并选择最新的一个。在streamlit-webrtc
中,当[async_transform](https://github.com/whitphx/streamlit-webrtc/blob/f03d3150adfa27c44bb7f2d22d495351090d9341/streamlit_webrtc/__init__.py#L98)
https://github.com/whitphx/streamlit-webrtc/blob/f03d3150adfa27c44bb7f2d22d495351090d9341/streamlit_webrtc/__init__.py#L98选项设置为True
时,在这里完成。
可扩展性
在tiny-streamlit-webrtc
中,视频转换被硬编码在VideoTransformTrack.recv()
中,但当然,作为一个库,这是糟糕的设计。为了可重用,它应该公开一个可注入的接口,开发人员可以通过该接口实现任意类型的视频转换,封装细节,如VideoTransformTrack.recv()
和 WebRTC 相关代码。
有了streamlit-webrtc
,开发者可以通过创建一个类扩展VideoTransformerBase
类来实现自己的视频转换,比如这个和这个。
关键要点
Streamlit 是一个漂亮的框架,有一个有用的库,但它本身不能很好地处理实时视频处理。
WebRTC 通过支持服务器端流程和客户端以低延迟通过网络发送和接收数据流,使 Streamlit 更加出色。
在脑海中有一个使用 WebRTC 的惊人项目吗?请在评论中与我们分享或给我发消息。
信用
由 Yu Tachibana(@ z _ reactor)审核
原载于 2021 年 2 月 12 日https://blog . streamlit . io。
使用 Plotly 在 Python 中开发时间序列热图
使用 Plotly 创建每月和每小时数据的可视化热图
作者视觉。
介绍
任何接触过数据的人都知道,时间序列数据可以说是我们日常处理的最丰富的数据类型。用日期、时间和/或两者索引的数据因此被分类为时间序列数据集。通常,将我们的时间序列呈现为每月和每小时的热图是有帮助的。这种强大的可视化非常有助于消化以其他方式呈现的数据,这些数据可能不会被我们高度可视化的自我吸收。这些渲染图通常水平描绘小时,垂直描绘月,并利用颜色来传达底层单元格所代表的值的强度。在这里,我们将把一个随机生成的时间序列数据集转换成一个交互式热图,这是 Python 最强大的绑定之一。
编程堆栈
除了 Python,我们还将利用 Plotly、Pandas 和 Streamlit——数据科学社区中一些最强大的工具。我想很多人会对熊猫更加熟悉;另一方面,Plotly 和 Streamlit 可能没有敲响多少钟。以下是对每一项的快速回顾:
1。熊猫
在处理数据时,Pandas 无疑是 Python 中最有效的绑定之一。Pandas 允许你在你的设备上执行一系列的变换,所有这些都是通过调用几个简短的命令来完成的。在我们的应用程序中,我们将使用 Pandas 从/向 CSV 文件中读取/写入我们的数据,并将我们的时间戳重组为一天中的月份和小时。
2。阴谋地
Plotly 是一个健壮、敏捷的数据可视化库,专门为机器学习和数据科学工具定制。虽然它基于 plotly.js,而 plotly . js 本身是一个本地 Javascript 绑定,但它已经扩展到支持 Python、R 和其他流行的脚本语言。通过在 Python 脚本中使用几行 JSON,您可以轻松地调用交互式可视化,包括但不限于折线图、直方图、雷达图、热图等等。在这种情况下,我们将使用 Plotly 来呈现我们的月与小时热图。
3。流线型
Streamlit 是 Python 库的无名英雄。它是一个纯粹的 Python web 框架,允许你开发和部署你的应用程序作为 web 应用程序,而不需要编写一行 HTML 或 CSS,别开玩笑了。对我个人来说,我在 2020 年夏天开始使用 Streamlit,从那以后我不记得在我写的脚本中没有使用过它。Streamlit 允许您使用优雅且高度互动的用户界面即时呈现您的应用程序。对于此应用程序,我们将使用 Streamlit 在本地浏览器上描绘我们的热图和数据框。
安装软件包
首先,在您选择的 Anaconda 环境中安装下面的包。
每个包都可以通过在 Anaconda 提示符下键入以下相应的命令来安装。
pip install plotly
资料组
我们将使用这个随机生成的数据集,它有一个日期、小时和值的列,如下所示。
图片作者。
日期格式如下:
YYYYMMDD
而小时的格式为:
HHMM
您可以使用任何其他适合您需要的格式来设置日期和/或小时的格式,但是您必须确保在您的脚本中声明它,如下一节所述。
数据预处理
在我们继续之前,我们需要预处理数据集,以确保日期和时间的格式可以进一步处理。
最初,我们需要删除 hour 列中值的所有尾随小数位,并添加前导零以防时间少于一个整小时,即 12:00AM 引用为 0。随后,我们需要将日期添加到小时中,并使用 Python 中的 datetime.strptime 绑定,以可理解的格式解析它们。最后,我们可以通过使用 strftime 函数将日期转换为月份,将小时转换为 12 小时制:
为了使用其他日期时间格式,请参考这篇文章。
一旦我们的数据经过预处理,我们就可以使用 Pandas 中强大的group by类将我们的数据集重新分组为月和小时的时间平均值,如下所示:
请注意,可以使用其他方法代替平均值,即求和、最大值或最小值,方法是改变:
df.groupby(['Month','Hour'],sort=False,as_index=False).mean()
到
df.groupby(['Month','Hour'],sort=False,as_index=False).sum()
热图功能
现在,我们已经将数据重新分组为月和小时,我们首先需要将 Pandas 数据框转换为一个字典,然后转换为一个数组,可以输入到 Plotly 中来创建热图。
声明一个字典,请确保添加一年中的所有月份,不要截断,如下所示:
随后,我们将把 Pandas 数据框中的值插入到字典中,并使用它创建一个数组,分别对应于每月和每小时的平均值,如下所示:
最后,我们将使用之前创建的数组来渲染我们的 Plotly 热图:
下载 CSV
您可能会发现将重新分组的月与小时数据框下载为 CSV 文件非常方便。如果是这样,请使用以下功能在您的 Streamlit 应用程序中创建一个可下载的文件。
该函数的参数— name 和 df 分别对应需要转换为 CSV 文件的可下载文件和数据帧的名称。
简化应用程序
最后,我们可以将所有内容以 Streamlit 应用程序的形式组合在一起,该应用程序将呈现热图、数据框和一个链接,以便将我们重新分组的数据下载为 CSV 文件。
您可以通过在 Anaconda 提示符下键入以下命令来运行您的最终应用程序。首先,将根目录更改为保存源代码的位置:
cd C:/Users/...
然后键入以下内容运行您的应用程序:
streamlit run file_name.py
结果
这就是交互式渲染,使您能够将时间序列数据集可视化为月与小时的热图。
图片作者。
如果您想了解更多关于数据可视化和 Python 的知识,请随时查看以下(附属链接)课程:
使用 Streamlit 开发 Web 应用程序:
使用 Python 实现数据可视化:
面向所有人的 Python 专业化:
GitHub 资源库:
https://github.com/mkhorasani/timeseries_heatmap
新到中?您可以在此订阅和解锁无限文章。
开发基于人工智能的 Android 图像注释应用程序
易于使用的 Android 应用程序,用于生成使用 PyTorch、Kotlin 和 SQLite 构建的图像的描述性标签
作者图片
项目定义和总结
大数据和新兴技术对我们的日常生活产生了深刻的影响,这一点很难忽视。具体来说,视觉信息(如照片和社交媒体图像)的数量近年来呈指数级增长,这推动了可以高效处理大容量图像集合的软件的开发。图像标记被认为是常见的计算机视觉问题之一,意指基于图像的视觉内容生成文本标记的过程。因此,自动检索图像的相关文本标签的能力允许自动化图像标记(例如,在线目录),组织高容量图像内容(例如,照片管理器),并且有益于现有的图像搜索和共享应用(例如,推荐引擎)。
为了解决这些问题,开发了一个 Android 应用程序 ImageTagger,用于自动生成图像的相关文本描述。为此,应用了深度学习和经典机器学习技术的组合。使用预训练的卷积神经网络(CNN)实现特征提取,使用非参数高斯朴素贝叶斯分类器定义相关标签。在这里,一个通用的图像分类数据集 Tiny ImageNet 被用于使用 MobileNet V2 生成图像嵌入,后者的性能使用精确度度量进行评估,精确度度量被广泛用于多类分类问题。这种方法的优点是不需要超参数调整,因为所选择的分类器没有超参数调整。
由此产生的 Android 应用程序允许用户上传他们想要描述的图像,并获得给定图像的相关标签。此外,ImageTagger 使用户能够更新现有的标签并向图像添加新的标签。随着时间的推移,系统基于明确的用户反馈,以在线方式从新的输入图像中学习标签,并更好地预测自己的描述。
方法学
利用计算机视觉进行嵌入生成
在这里,深度学习和经典机器学习技术的结合已经被应用。整个分析可以分为两个步骤,即特征提取和分类。特征提取部分使用深度卷积神经网络 (CNN)实现,待分配的标签使用非参数高斯朴素贝叶斯模型计算。图 1 显示了对以前看不到的数据进行贝叶斯推理所需的图像处理和计算机视觉管道。首先,图像通过 CNN,CNN 产生相应的图像嵌入 x 。然后,计算每个类别的统计数据(平均值 μ 和标准偏差 σ) 并存储到数据库中。
图一。ImageTagger 的数据处理和计算机视觉组件
结果
通过成千上万的图像学习概念
为了生成图像嵌入,使用了通用图像分类数据集 Tiny ImageNet 。该数据集是完整 ImageNet 的较小版本,包含 10 万多张图像,均匀分布在 200 个类别中,属于不同的类别,如动物、设备、衣服等。因此,使用微小的 ImageNet 数据允许 CNN 模型从不同的知识领域学习图像的表示和描述。
出于项目目的,使用了在 ImageNet 数据集上预先训练的 CNN 模型。为了统一图像表示(形状和颜色分布),输入图像被缩放并居中裁剪到 256 x 256 px 的大小,还使用完整 ImageNet 的统计数据以每个通道的方式进行标准化。上述程序适用于培训和评估步骤。
图二。归一化图像嵌入的 2D 投影
图 2 显示了属于 10 个随机选择的微型 ImageNet 类并使用 MobileNet V2 模型生成的图像嵌入的低维表示。定性分析清楚地表明语义相似的实体在嵌入空间中也是接近的,导致相关联的图像(例如,与食物相关的)在投影表面中分散在一起(例如,浓咖啡、冰淇淋和比萨饼在图的左下方)。
为输入图像生成相关标签
接下来,基于从图像嵌入中提取的预先计算的统计量,使用高斯朴素贝叶斯分类器(Eq)计算类别概率。1)并且后者被过滤,仅保留那些最大的并且总计总概率至少为 0.9 的。如果用户修改了任何分配的标签,则完成推断后校正。在这种情况下,包含每个类统计数据的应用程序数据库正在使用 Welford 的在线算法(Eq)进行更新。2,3),其中动量项 α =0.1。
模型评估和论证
四种网络架构被认为是特征提取步骤的可能主干: ResNet-18 、 DenseNet-121 、 MobileNet V2 、 ShuffleNet V2 。为了选择最合适的 CNN 模型来生成图像嵌入,使用准确性度量在微型 ImageNet 数据集上评估了模型性能(图 3 ),该度量提供了正确预测与使用该模型得出的预测总数的比率。由于数据集的作者提供了正式的训练和验证数据分割,因此没有必要使用交叉验证方法。因此,V2 移动网络被选为进一步分析的对象,因为该架构表现出最佳性能。
图 3。CNN 模型在微型 ImageNet 数据集上的性能
模型的大小作为其计算复杂性的间接度量,并且易于计算的神经架构是优选的,因为该模型将被部署在移动设备上。为了访问模型的大小,如图 4 所示计算可训练参数的数量。结果表明,ShuffleNet-18 架构的模型规模最小,其次是 V2 移动网络、V2 shuffle net 和 ResNet-18。
图 4。CNN 模型的可训练参数数量和性能的比较
最后,选择 MobileNet V2 模型架构是基于其准确性和大小的组合,因为它提供了最高的性能,同时提供了较少数量的可训练参数。
优化-使用量化加速网络
由于深度神经网络在计算上非常昂贵,推理速度通常成为瓶颈,尤其是在移动设备上。为了解决这个问题并增加模型的吞吐量,将模型量化应用于 MobileNet V2。该方法是指通过使用基于半精度浮点( float16 或整数( int8 )数执行计算的低精度算术来降低计算复杂度。这种优化允许以验证准确度的边际降低(从 56.09%到 55.67%)为代价,显著减少执行特征提取步骤所需的时间(在诺基亚 7.1 上从 212.0 +/- 12.6 毫秒到 50.1 +/- 9.1 毫秒)。
设计移动应用架构
随着计算机视觉组件的开发,ImageTagger 的 UI 被设计(图 5)并实现,如后面所演示的。在左侧,有一个初始图片选择过程的概述,该过程导致将所选图片传递到私有图像集合中。用户可以上传设备上现有的图像或拍摄照片。在右边,流程图描述了从图像集合中获取图片并将它们显示给用户。后者可以选择它们中的一个来获得自动描述并对分配的标签进行编辑。
图 5。ImageTagger 的用户交互流程图
ImageTagger 的现场演示
这是最终的 Android 应用程序的样子(图 6):
图 6。现场演示:Android 版 ImageTagger
结论和未来工作
在这里,开发了用于 Android 的应用程序 ImageTagger 来自动生成用户图像的相关文本注释。考虑在目标(例如,MS COCO)上微调 CNN 或者在更大的数据集(例如,YFCC100M)上训练它,可以实现进一步的精度提高。此外,使用支持基于 GPU 的推理的现代设备可以提高模型速度。
未来的发展可以通过引入使用客户端-服务器或点对点技术(例如区块链的 torrent)的用户间照片共享来实现,从而允许以安全和隐私保护的方式执行分布式图像搜索和检索。
在您的设备上获取 image tagger
为了在您的移动设备上启动 ImageTagger,请遵循清单并确保您采取了所有步骤来成功安装它:
- 从 GitHub 下载 ImageTagger 到您的 Android 设备
- 在您的 Android 设备上打开应用程序
- 享受自动图像标记
注意:移动设备预计运行 Android 8.1 及以上版本;应该允许安装来自未知来源的 apk。
担心隐私和安全?对安装来源不明的应用程序感到怀疑?只需克隆项目库并使用 Android Studio 4.1 或更新版本自行构建 ImageTagger。
https://github.com/slipnitskaya/image-tagger
使用 FastAPI、Jinja2、SQLAlchemy、Docker 和 AWS 开发和部署一个完整的项目
作者 Gif。
介绍
你可以在 rdok.net 的上访问最终版 app。代码在 GitHub 上是开源的。这主要是一篇程序性文章。目的是描述创建应用程序的完整过程,而不是用代码片段轰炸读者。
这个应用程序是关于什么的?这款应用名为“随机知识剂量”,外观如下
图片由作者提供。
有 4 个按钮,每个按钮来自一个特定的子编辑。当用户点击一个按钮时,会显示这个子编辑的随机帖子以及顶部的评论。我想你对 Reddit 很熟悉,但不熟悉也没关系。你可以把这款应用想象成一个用户可以不断点击按钮来了解一些很酷的事实的地方。在页面的底部,有一个交互式图表,显示每个按钮的累计点击次数。
如何阅读这篇文章
- 潜在读者 1: 只对过程感兴趣。希望以一种非常抽象和快速的方式看到创建这样一个项目的步骤。
如果你是 reader 1,不要像这样分块读内容,不要读代码片段。
预计时间:5 分钟。
- 潜在读者 2: 对自己不知道的具体部分感兴趣。如何用 FastAPI 使用 HTML?如何对项目进行归档?如何在 EC2 上部署?
如果你是读者 2,快速浏览文章,找到你想了解的内容和/或关键词。
预计时间:视情况而定。
- 潜在读者 3: 对流程和代码感兴趣。想要为自己重新创建这个应用程序。
如果你是 reader 3,克隆 GitHub 代码。阅读文章中的所有内容。再读一遍。复制它。
预计时间:最多 8 小时。
使用的框架/软件/技术/平台:
- 饭桶
- 米尼康达
- FastAPI
- SQLAlchemy
- HTML,CSS
- Jinja2
- Reddit API
- 蟒蛇(熊猫,Plotly)
- 码头工人
- AWS EC2,ECR
1.Git 和虚拟环境
首先,创建一个将承载您的应用程序的文件夹。我的叫做随机知识剂量。 下载 anaconda 或 miniconda 创建虚拟环境。进入文件夹,打开终端,键入:
git init. # initialize git repository
conda create -n *rdok* python=3.8 # create a virt. env. called rdok
conda activate rdok # activate it
创建一个名为 requirements.txt 的文件,并在其中写入:
fastapi
uvicorn
SQLAlchemy
pandas
plotly
aiofiles
requests
Jinja2
python-multipart
在终端类型:pip install -r requirements.txt
我们现在有了 git 和一个虚拟环境,其中包含了我们需要的所有包。
2.在本地创建应用程序
在这一部分,我给出了文件结构,解释了文件,我们看了一眼一些文件中最重要的部分。完整的文件在 GitHub 上。
**random-dose-of-knowledge/**
|-- .gitignore
|-- Dockerfile
|-- requirements.txt
**|-- app/**
|-- __init__.py
|-- main.py
**|-- db/**
| |-- __init__.py
| |-- database.py
| |-- random-knowledge.db
**|-- models/**
| |-- __init__.py
| |-- sql_table_entities.py
**|-- utils/**
| |-- __init__.py
| |-- reddit_api.py
**|-- templates/**
| |-- base.html
| |-- index.html
**|-- static/**
| **|-- css/**
| | |-- style.css
| **|-- images/**
| | |-- favicon.png
。gitignore
包含 git 不会跟踪的所有文件和文件夹。这里是
Dockerfile 文件
包括组装 docker 图像的命令。稍后会有更多内容
init。巴拉圭
将这个空文件包含在目录中,让 python 将它们视为包
随机知识数据库
我们的数据库
database.py
使用 SQLAlchemy 创建数据库连接的脚本
sql _ 表 _ 实体. py
包括我们数据库的表的定义
在表‘posts’和‘comments’之间存在一对多的关系,因为一篇文章可以有多个评论。
如何将数据插入这些表格:
下面是 main.py 在表中插入数据的主要部分。特别注意第 25 行。
在第 4 行的
subreddit: str = Form('todayilearned')
中,我们定义了一个类型为str
的参数subreddit
。它的值将是按钮单击的表单输出。例如,如果用户点击“科学”按钮,表单的输出将是“科学”。从第 4 行可以看出,默认值是“todayilearned”。
reddit_api.py
Python 工作完成的文件。定义连接到 Reddit API、获取请求、预处理从 Reddit 帖子和评论中提取的数据的函数。此外,还要定义创建应用程序情节的函数。
如何使用 Reddit API 。到这里去。滚动到底部。点击“创建另一个应用程序”。插入一个名称,选择脚本,将描述和关于 url 留空,输入一个网站(可以是你的个人网站,github 或 linkedin)到重定向 uri。您的 CLIENT_ID 是“个人使用脚本”。你的秘密钥匙就是“秘密”。为了连接,您还需要您的 REDDIT_USERNAME 和 REDDIT_PASSWORD。将后者保存在. txt 文件中
base.html 和 index.html
这些文件位于需要在 main.py 中定义的 templates 文件夹中(见下面 main.py 脚本第 2 行)
可以用 FastAPI 创建一个完整的网站,而不仅仅是返回 json 文件。我的应用程序是一个单页网站。index.html 是包含在网站所有其他页面中的基础文件(在我的例子中,只有 index.html)。index.html 是该应用程序的登录页面。
FastAPI + Jinja2 + HTML 。将 FastAPI 函数的结果返回到 HTML 页面:
1。在函数
@app.get('/', response_class=HTMLResponse)
的装饰器中定义 response_class2.在请求类型
*def my_function(request: Request):*
的函数中定义一个参数3.返回指定着陆的 TemplateResponse。html 页面、请求参数和要传递的变量
return templates.TemplateResponse('index.html', { 'request': request, 'variable1': var_1, 'variable2': var_2})
4.使用 Jinja2
在 HTML 中使用传递的变量,例如,如果 var_1 是一个数据帧,我们可以这样使用它:
`*{% for i, row in var_1.iterros() %}{{row['body']}}{% endfor %}*`
main.py
调用和运行应用程序的脚本。下面的代码包含 main.py 文件的最基本的(导入除外)
第 12 行运行名为“app”的应用程序,如文件“main”的第 1 行所定义。该应用程序将在本地 localhost:8000 上提供
此时应用程序已经准备好,你可以在本地运行它: python main.py
3.将应用程序归档
安装对接器。
请参考文件结构以了解路径。创建 Dockerfile :
# install python in the container
FROM python:3.8.8-slim # copy the local requirements.txt file to the
# /app/requirements.txt in the container
# (the /app dir will be created)
COPY ./requirements.txt /app/requirements.txt# install the packages from the requirements.txt file in the container
RUN pip install -r /app/requirements.txt# expose the port that uvicorn will run the app
EXPOSE 8000# copy the local app/ folder to the /app fodler in the container
COPY app/ /app# set the working directory in the container to be the /app
WORKDIR /app# execute the command python main.py (in the WORKDIR) to start the app
CMD ["python", "main.py"]
从 docker 文件构建一个 docker 映像。转到 Dockerfile 所在的文件夹。类型:
docker build -t rdok:1.1 .
别忘了圆点!图像的名称是 rdok,标签是 1.1
查看图像:
docker images
从图像创建一个容器:
docker run --name rdok -d -p 80:8000 rdok:1.1
使用 name,我们用-d 指定容器的名称
,容器将在分离模式下运行
使用-p 80:8000,我们将本地端口 80 绑定到 docker 端口 8000
rdok:1.1 是映像的名称
奖金。我们可以把本地 app 文件夹和 docker app 文件夹连接起来。docker 应用程序中的更改将被复制到本地文件夹中,反之亦然。容器也将是持久的。如果我们停止甚至删除容器,更改将会保留。为此,将以下内容添加到 docker run 命令中:
-v absolute/path/to/the/local/app:/app
左侧是我们的/app/文件夹所在的本地机器上的绝对路径。右边部分是我们之前在容器中创建的/app/文件夹(在 Dockerfile 中)。
检查运行容器:
docker ps
应用程序正在运行!转到 localhost:80 或者简单的 localhost 。
4.在 AWS 上部署
在 AWS 上记账并连接。按照图片中的步骤创建一个 EC2 实例。这将是我们的服务器,将托管我们的应用程序:
图片由作者提供。
在终端中,导航至.pem
键并键入:
chmod 400 my_key_pair.pem
如果你在 windows 机器上这样做,而不是。
继续连接到您的实例。在你的终端中(你在一个有.pem
键的文件夹中)运行下图中的最后一个命令。
图片由作者提供。
你现在在你的实例里面。运行以下命令在实例中安装 Docker:
sudo yum update -y
sudo yum install docker
sudo service docker start
sudo usermod -a -G docker ec2-user
sudo dockerd
现在重新启动实例,并按照上述相同的步骤再次连接。
类型:
systemctl start docker
sudo service docker start
Docker 已安装!
现在让我们在 AWS 上创建一个 ECR Docker 容器注册表。
图片由作者提供。
在继续之前,下载 AWS CLI ,并通过使用您的 AWS 凭证验证进行配置。
认证后,转到您的 ECR,选择存储库并单击“查看推送命令”。
将会打开一个窗口,指导您如何将本地 docker 映像推送到 ECR 实例。
这些命令将在您的本地计算机上运行:
aw ecr get-login-pa...
验证本地机器和 ECR 之间的连接。docker build..
建立形象。我们已经在 docker 部分建立了我们的应用程序的 Docker 图像。- 它复制并重命名本地图像。键入
docker images
查看。 docker push..
将复制的图像推送到 ECR 存储库。
我们的图像现在存放在我们的 ECR 存储库中。让我们把它放到我们的 EC2 存储库中。
转到 EC2 实例中的终端。
我们首先必须认证 EC2 和 ECR 之间的连接。像在本地机器上一样运行相同的aw ecr get-login-pa...
命令,然后执行docker pull name-of-the-copied-image
。
现在我们的图像在我们的服务器里了!键入docker images
即可查看。
让我们运行它:
docker run --name my-app -d -p 80:8000 IMAGE_NAME:TAG
超级重要的一步
在 EC2 仪表板上转到 AWS 网站,单击存储库,然后转到“安全”选项卡。单击“安全组”下的链接。接下来,单击“编辑入站规则”。单击“添加规则”。类型:HTTP,协议 TCP,端口范围 80,源自定义 0.0.0.0/0 并保存。
您的规则应该是这样的:
图片由作者提供。
要访问你的应用,请访问公共 IPv4 地址,但要写 http 而不是 https。
恭喜你!您已到达这篇文章的结尾。我希望你在这个项目中学到了和我一样多的东西。
最后,我再次分享这个项目的 GitHub 链接。
你可以在 LinkedIn 上和我联系。
回头见!
使用 Python 在顶点人工智能上开发和部署机器学习模型
编写让您的 MLOps 团队满意的培训渠道
编写让您的 MLOps 团队满意的 ML 管道:遵循模型代码和 Ops 代码之间清晰的责任分离。本文将向您展示如何做到这一点。
普里西拉·杜·普里兹在 Unsplash 上的照片
为什么要分开责任?
在我之前关于 Vertex AI 的两篇文章中,我向您展示了如何使用 web 控制台创建和部署 AutoML 模型,以及如何获取您以某种方式训练的 TensorFlow 模型并将其部署到 Vertex AI 。但是这两种方法都不能真正扩展到数百个模型和大型团队。
当您使用 Google Cloud web 控制台创建 AutoML 模型时,您会得到一个可以监控的端点,并且可以在其上设置持续评估。如果你发现模型在漂移,自动根据新数据重新训练它是很困难的——你不会想在凌晨 2 点醒来使用用户界面来训练模型。如果您可以仅使用代码来训练和部署模型,那就更好了。对于您的 MLOps 团队来说,代码自动化要容易得多。
将您在 Jupyter 笔记本中训练的 TensorFlow 模型部署到 Vertex AI 也有同样的问题。再培训将是困难的,因为运营团队将不得不设置所有的运营、监控和调度,而这些都是非常笨重且完全非最小化的。
对于再培训来说,整个过程——从数据集创建到培训再到部署——由代码驱动要好得多。做到这一点,您的运营团队将感谢您让他们的工作变得简单,因为他们清楚地将模型代码与运营代码分开,并且用 Python 而不是笔记本来表达所有内容。
如何在 Vertex AI 中获得这种分离是我在本文中要展示给你的。
在 Python 文件中完成
Jupyter 笔记本非常适合开发,但是我强烈建议不要将这些笔记本直接投入生产(是的,我确实知道 Papermill )。
我建议您将最初的原型模型代码转换成 Python 文件,然后在其中继续所有的开发。扔掉朱庇特笔记本。您将从一个临时笔记本中调用提取(和维护)的 Python 代码,以供将来试验。
你可以在https://github . com/Google cloud platform/data-science-on-GCP/tree/edition 2/09 _ vertexai中看到我的例子。请参见文件 model.py 和 train_on_vertexai.py,并使用它们进行后续操作。
编写模型. py
文件 model.py 包含了我的 Jupyter 笔记本中所有的 Keras 模型代码( flights_model_tf2.ipynb 在同一个 GitHub 目录下)。不同之处在于它是可执行的,笔记本的大部分代码被提取到一个名为 train_and_evaluate.py 的函数中:
def train_and_evaluate(train_data_pattern, eval_data_pattern, test_data_pattern, export_dir, output_dir):
... train_dataset = read_dataset(train_data_pattern, train_batch_size)
eval_dataset = read_dataset(eval_data_pattern, eval_batch_size, tf.estimator.ModeKeys.EVAL, num_eval_examples) model = create_model()
history = model.fit(train_dataset,
validation_data=eval_dataset,
epochs=epochs,
steps_per_epoch=steps_per_epoch,
callbacks=[cp_callback]) # export
logging.info('Exporting to {}'.format(export_dir))
tf.saved_model.save(model, export_dir)
有三点需要注意:
- 数据从分别用于训练、验证和测试数据集的由 train_data_pattern、eval_data_pattern 和 test_data_pattern 指定的 URIs 中读取。
- 模型创建代码提取到一个名为 create_model 的函数中
- 模型被写出到 export_dir,任何其他中间输出被写入 output_dir。
数据模式和输出目录在 model.py 中从环境变量中获得:
OUTPUT_DIR = 'gs://{}/ch9/trained_model'.format(BUCKET)
OUTPUT_MODEL_DIR = os.getenv("AIP_MODEL_DIR")
TRAIN_DATA_PATTERN = os.getenv("AIP_TRAINING_DATA_URI")
EVAL_DATA_PATTERN = os.getenv("AIP_VALIDATION_DATA_URI")
TEST_DATA_PATTERN = os.getenv("AIP_TEST_DATA_URI")
这非常重要,因为它是你的代码和 Vertex AI 之间的契约,是所有自动发生的事情所需要的。
然而,您可能需要在 Vertex AI 之外运行这段代码(例如,在开发期间)。在这种情况下,不会设置环境变量,因此所有变量都是 None。寻找这种情况,并将它们设置为您的开发环境中的值:
if not OUTPUT_MODEL_DIR:
OUTPUT_MODEL_DIR = os.path.join(OUTPUT_DIR,
'export/flights_{}'.format(time.strftime("%Y%m%d-%H%M%S")))
if not TRAIN_DATA_PATTERN:
TRAIN_DATA_PATTERN = 'gs://{}/ch9/data/train*'.format(BUCKET)
if not EVAL_DATA_PATTERN:
EVAL_DATA_PATTERN = 'gs://{}/ch9/data/eval*'.format(BUCKET)
这些文件可能非常小,因为它们仅用于开发。实际的生产运行将在顶点 AI 内部运行,在那里将设置环境变量。
一旦你写完 model.py,确保它能正常工作:
python3 model.py --bucket <bucket-name>
现在,您已经准备好从顶点 AI 管道中调用它了。
编写培训管道
训练管道(参见 train_on_vertexai.py)需要在代码中做五件事:
- 在 Vertex AI 中加载托管数据集
- 设置培训基础结构以运行 model.py
- 运行 model.py,并传入托管数据集。
- 找到要将模型部署到的端点。
- 将模型部署到端点
1。托管数据集
这就是如何加载一个表格数据集(有图像、文本等选项。数据集,对于 BigQuery 中的表格数据):
data_set = aiplatform.TabularDataset.create(
display_name='data-{}'.format(ENDPOINT_NAME),
gcs_source=['gs://{}/ch9/data/all.csv'.format(BUCKET)]
)
请注意,我传入了所有的数据。Vertex AI 将负责将数据分成训练、验证和测试数据集,并将其发送给训练程序。
2。培训设置
接下来,创建一个训练作业,传入 model.py、训练容器映像和服务容器映像:
model_display_name = '{}-{}'.format(ENDPOINT_NAME, timestamp)
job = aiplatform.CustomTrainingJob(
display_name='train-{}'.format(model_display_name),
script_path="model.py",
container_uri=train_image,
requirements=[], # any extra Python packages
model_serving_container_image_uri=deploy_image
)
(关于为什么要给模型分配时间戳名称,请参见如何将 TensorFlow 模型部署到顶点 AI
3。运行培训作业
运行作业包括在某些硬件上的托管数据集上运行 model.py:
model = job.run(
dataset=data_set,
model_display_name=model_display_name,
args=['--bucket', BUCKET],
replica_count=1,
machine_type='n1-standard-4',
accelerator_type=aip.AcceleratorType.NVIDIA_TESLA_T4.name,
accelerator_count=1,
sync=develop_mode
)
4。寻找终点
我们想要部署到一个预先存在的端点(阅读见如何部署一个 TensorFlow 模型到顶点 AI 了解什么是端点)。因此,找到一个现有端点,否则创建一个:
endpoints = aiplatform.Endpoint.list(
filter='display_name="{}"'.format(ENDPOINT_NAME),
order_by='create_time desc',
project=PROJECT, location=REGION,
)
if len(endpoints) > 0:
endpoint = endpoints[0] # most recently created
else:
endpoint = aiplatform.Endpoint.create(
display_name=ENDPOINT_NAME, project=PROJECT, location=REGION
)
5。部署型号
最后,将模型部署到端点:
model.deploy(
endpoint=endpoint,
traffic_split={"0": 100},
machine_type='n1-standard-2',
min_replica_count=1,
max_replica_count=1
)
就是这样!现在,您有了一个 Python 程序,您可以随时运行它来重新训练和/或部署训练好的模型。当然,MLOps 人员通常不会大规模替换模型,而是只向模型发送一小部分流量。他们可能还会在 Vertex AI 中设置对端点的监控和持续评估。但是你让他们很容易做到这一点。
代码中的端到端自动 ML
如果我想使用 AutoML 而不是我的定制培训工作,上面的管道会有什么变化?嗯,我不需要我自己的模型。因此,我将使用 AutoML 来代替 CustomTrainingJob。
设置和运行培训作业(上面的步骤 3 和 4)现在变成了:
def train_automl_model(data_set, timestamp):
# train
model_display_name = '{}-{}'.format(ENDPOINT_NAME, timestamp)
job = aiplatform.AutoMLTabularTrainingJob(
display_name='train-{}'.format(model_display_name),
optimization_prediction_type='classification'
)
model = job.run(
dataset=data_set,
target_column='ontime',
model_display_name=model_display_name,
budget_milli_node_hours=(300 if develop_mode else 2000),
disable_early_stopping=False
)
return job, model
这是唯一的变化!管道的其余部分保持不变。当我们说你有一个 ML 开发的统一平台时,这就是我们的意思。
事实上,您可以将 ML 框架类似地更改为 PyTorch 或 sklearn 或 XGBoost,就 MLOps 人员而言,只有很小的更改。在我的 train_on_vertexai.py 中,我使用命令行参数在自定义 Keras 代码和 AutoML 之间切换。
以非默认方式拆分数据
默认情况下,Vertex AI 会对数据进行部分拆分(80%用于训练,10%用于验证和测试)。如果你想控制分裂怎么办?有几个选项可用(基于时间等。).
假设您想要向数据集中添加一个控制拆分的列,您可以在创建数据时执行此操作:
CREATE OR REPLACE TABLE dsongcp.flights_all_data ASSELECT
IF(arr_delay < 15, 1.0, 0.0) AS ontime,
dep_delay,
taxi_out,
...
**IF (is_train_day = 'True',
IF(ABS(MOD(FARM_FINGERPRINT(CAST(f.FL_DATE AS STRING)), 100)) < 60, 'TRAIN', 'VALIDATE'),
'TEST') AS data_split**
FROM dsongcp.flights_tzcorr f
...
基本上,有一个我称为 data_split 的列接受值 TRAIN、VALIDATE 或 TEST。因此,托管数据集中的每一行都被分配给这三个拆分之一。
然后,当我训练作业时(无论是自定义模型还是 automl),我指定预定义的拆分列是什么:
model = job.run(
dataset=data_set,
# See [https://googleapis.dev/python/aiplatform/latest/aiplatform.html#](https://googleapis.dev/python/aiplatform/latest/aiplatform.html#)
**predefined_split_column_name='data_split',**
model_display_name=model_display_name,
就是这样!Vertex AI 将负责剩下的工作,包括将所有必要的元数据分配给正在训练的模型。
一句话:随着越来越多的 it 变得自动化管理,MLOps 变得越来越容易。通过在你的代码中遵循清晰的职责分离,你可以了解这一点。
尽情享受吧!
更多关于 Vertex AI 的阅读:
- 给谷歌云上的新统一 ML 平台 Vertex AI 一个旋转 :
我们为什么需要它,无代码 ML 培训到底有多好,所有这些对数据科学工作意味着什么? - 如何将 TensorFlow 模型部署到 Vertex AI :在 Vertex AI 中使用保存的模型和端点
- 使用 Python 在 Vertex AI 上开发和部署机器学习模型:编写让你的 MLOps 团队满意的训练管道
- 如何在 Vertex AI 中为超参数调整构建 MLOps 管道 :
为超参数调整设置模型和协调器的最佳实践
从头开始发展和解释交叉熵
继续阅读,理解交叉熵背后的直觉,以及为什么机器学习算法试图最小化它。
约尔根·哈兰在 Unsplash 上的照片
交叉熵是一个重要的概念。它通常在机器学习中用作成本函数——通常我们的目标是最小化交叉熵。但是为什么我们要最小化交叉熵,交叉熵到底是什么意思?让我们来回答这些问题。
首先,我们需要充分理解信息和熵的概念。如果你想彻底理解这些概念,我写了一篇关于它们的详细文章在这里。总而言之,信息是衡量一个事件有多令人惊讶/低概率的指标。概率越低(越让人吃惊),信息量越高。信息也可以解释为表示一个事件需要多少位。公式为 I = -log_2(p) bits,其中 I 为信息,p 为概率。熵是所有可能事件的平均值,所以熵的公式是 E = -∑ p_i * log_2(p_i)。
有了这个背景,我们可以继续讨论交叉熵。交叉熵提出的问题是:当我用不同的概率分布代替真实的概率分布时,信息/比特表示长度会发生什么变化?让我们来看一个具体的例子,看看这是什么意思。
假设我们有一枚公平的硬币。真正的概率分布是正面,反面。头和尾事件的信息是相同的:I = -log_2(1/2) = 1 位。熵是两个事件的信息的平均值,所以也是 1 比特。现在让我们假设,出于某种原因,我们认为硬币是不公平的(即使它实际上是公平的)。我们认为概率分布是正面,反面。在我们看来,正面比实际情况更罕见,反面更常见。因此,我们认为正面包含的信息比它实际包含的多,反面包含的信息少。从我们的角度来看,确切的信息量是 I_heads = -log_2(1/4) = 2,I_tails = -log_2(3/4) = 0.42。
然而,真正的概率分布仍然是正面,反面。正因为如此,从我们的角度来看,翻转的期望信息将是* I _ heads+* I _ tails = * 2+* 0.42 = 1.21。这个数字就是交叉熵:通过假设与真实概率分布不同的概率分布计算出的平均信息。如果真实概率记为 p_i,假设概率记为 q_i,则交叉熵的公式为 CE = -∑ p_i * log_2(q_i)。
让我们从另一个角度来看这个例子,以建立更多的直觉。与其抛硬币,不如考虑一个只有两个字母的字母表:A 和 B。我和我的朋友都使用这个字母表,但我更喜欢字母 A,他更喜欢字母 B。假设我的 A 到 B 的字母用法是 80/20,我朋友的是 20/80。我想想出一个高效的二进制代码来表示这个字母表。因为我使用字母 A 的次数远远多于 B,所以我将用较少的比特数表示 A,用较多的比特数表示 B。我的朋友也想出了一个代码,但是由于他用 B 比用 A 多,他的代码用 A 的比特多,用 B 的比特少。
现在,考虑当我试图用我的代码来表示他所说的话(他的语言)时会发生什么。因为他说了很多次 B,所以我很多次都要用我的高比特位表示 B,用我的低比特位 A 表示的机会不多。因此,我的代码的总比特使用量(在我朋友的语言中)将远远高于他自己的代码(有一个低位 B 和一个高位 A)。同样,我朋友的代码在表示我的语言时会非常低效。那么这个例子有什么意义呢?我的代码需要我朋友的语言的位数是交叉熵。他的代码为他的语言使用的比特数是熵。如我们所见,交叉熵高于熵。
我们还可以改变字母使用分布。比如说我的信使用分布是 60/40 A/B 而不是 80/20,我的朋友是 40/60 而不是 20/80。现在,由于我们的发行版更接近了,使用我的优化代码来表示我朋友的语言不再像以前那样低效了。换句话说,交叉熵更小。你可以通过插入我们上面陈述的交叉熵公式来自己看到这一点。
这表明了交叉熵的一个重要用途:比较两个概率分布。两个分布越接近,交叉熵就会越小。因此,如果我们的目标是使一个概率分布尽可能接近另一个,我们需要来最小化它们之间的交叉熵。这是怎么用的?在机器学习问题中,一个常见的模式是,我们有一个真实分布 P 和一个输出另一个分布 Q 的模型 M。目标是找到 M 的正确参数,使 P 和 Q 之间的交叉熵尽可能小。一种流行的方法是最大化对数似然,这与最小化交叉熵是一回事。你可以在这里看到证明。
在本文中,我们解释了什么是交叉熵,探索了一些交叉熵构建直觉的例子,并讨论了它在机器学习中的用法。我希望一切都很清楚,请留下您可能有的任何问题/意见。如果你对这类话题感兴趣,我打算再写一些关于重要理论机器学习概念的帖子,敬请关注!
为非侵入式数字健康可穿戴设备开发机器学习模型
关于特征选择、生物标记识别和疾病预测的综述
介绍
精准医疗与大健康数据相结合,为健康监测提供了一个机会,让人们能够更好地控制自己的健康。智能手表等数字健康可穿戴设备为健康监测增加了一个强大的维度。数字健康可穿戴设备使持续监测健康成为可能,而不是依赖于医生出诊和定期检查期间获得的信息快照。有了这项技术,医疗保健提供者可以对健康和疾病进展有全面的了解。
这是一场创造终极智能手表的竞赛。很快,智能手表不仅可以监测血压、含氧量、体温、身体活动、睡眠模式和血糖,还可以预测几种疾病状况。
这种能力的增加将意味着个人健康数据的增加。可穿戴设备的发展依赖于识别和开发能够检测生物标记的传感器。然后,获得的数据可用于训练稳健的机器学习(ML)算法。该项目旨在使用机器学习模型来选择特征,识别生物标志物和预测糖尿病。此外,该项目希望阐明为数字健康可穿戴设备开发鲁棒的机器学习模型的过程。
数据和型号概述
本节概述了全球 COVID 患者入院健康数据、数据集、特征和预测模型。
数据集:用于训练和评估模型的数据集包括从全球 COVID 患者入院处获得的常规健康记录。这个数据集被放在一起预测糖尿病,并将其用作 COVID 疾病严重程度的指标。该训练数据集包含 180 个特征和 150,000 个观察值。缺失值和唯一标识符超过 40,000 个的列将从数据集中移除,从而生成包含 100 个要素的数据集。
目标:糖尿病是本次预测的目标。目标有一个不平衡类。数据集中有 78.4%的非糖尿病患者和 21.7%的糖尿病患者。这种类别不平衡在预测非糖尿病患者方面表现良好,准确率约为 85%,而预测非糖尿病患者的准确率仅为 65%。通过对非糖尿病类进行欠采样,对这些类进行重新采样,得到 56,302 个观察大小。
预测变量
医院数据包含 100 个特征,经过特征工程和数据争论后减少到 49 个。这些特征分为 ICU 状态、疾病状态、血液化学、生物统计学和生理学。
然后将得到的数据集以 80:20 的比例分成训练和验证两部分,并使用目标变量进行分层。
预测模型
随机森林和 Xgboost 分类器模型用于预测糖尿病。随机森林分类器的特征重要性使用 ELi5 来识别。任何要素重要性为零或更低的要素都将被移除,因为它们对生成 39 个要素的模型没有贡献。这些特征和目标用于训练 Xgboost 分类器。
表 1:特性的重要性(前 14 个特性)(按作者排序的表)
绩效指标
准确度、精确度和召回率:基线模型准确度——与多数类百分比相同,为 50%。Xgboost 分类器模型的准确率为 75%。如下面的分类报告所示,糖尿病分类(1)的召回率和精确度分别为 0.74 和 0.75。
XGboost 分类器模型的分类报告(图片由作者提供)
AUC-ROC
Xgboost 模型的 AUC- ROC 评分为 0.82。该模型擅长区分糖尿病和非糖尿病的观察结果。下图显示了 AUC-ROC 曲线。将阈值概率设置为 0.38 将召回率从 1 类(糖尿病)的 74%增加到 84%。这个概率下的精度是 0.70。对于疾病预测来说,回忆尤其重要,因为预测中的所有资源都被分配以确保每个有疾病的人都被识别。
ROC 曲线(图片由作者提供)
将阈值概率设置为 0.38 以提高召回率。(图片由作者提供)
部分相关图
糖尿病葡萄糖的部分相关图(PDP)如下图所示。PDP 显示概率从 50 毫克/升开始下降,直到达到 100 毫克/升,并开始增加到 250,然后趋于平稳。
(图片由作者提供)
沙普利值
下图显示了 68 岁老人的 Shapley 值。0.19 的预测值预测此人患有糖尿病,因为它低于 0.5 的阈值。概率在 0.5 以上的人在这个预测中不是糖尿病。降低概率从而增加某人患糖尿病的机会的特征包括葡萄糖、身体质量指数、gcs_unable_apache、血红蛋白、肌酸酐等。增加概率并因此增加一些人不患糖尿病的机会的特征包括年龄、ICU _los_days、WBC 等。
68 岁个体的 Shapley 结果(图片由作者提供)
调查结果
发现一:医疗保健提供者使用糖尿病来衡量某人患 COVID 疾病严重程度的可能性。血糖水平对这一预测贡献最大。开发能够有创测量血糖水平的数字健康产品不仅为消费者提供了管理血糖的机会,还可以预测 COVID 等病毒引起的疾病的严重程度。
发现二:数字健康可穿戴设备的出现有助于医疗保健提供商对患者的健康状况有全面的了解,也有助于人们对自己的健康采取积极的态度。正如在预测中所看到的,糖尿病的前两个预测因子是葡萄糖和身体质量指数,它们都可以通过生活方式的调整来改变。
发现三:生物标志物血红蛋白,是糖尿病预测的第四个贡献因素,可以使用传感器非侵入性地确定。这种生物标志物通常用于预测贫血的医疗保健。使用机器学习研究健康数据可以帮助重新利用生物标记来预测其他疾病,从而使数字健康可穿戴设备变得强大。
结论
机器学习模型有助于数字健康可穿戴设备的生物标记研究,并通过重新利用预测多种状况的特征来提高传感器的鲁棒性。
在这里找到 GitHub repo:https://GitHub . com/felly love/Biotech/blob/master/build week 2 _ sburris . ipynb
来源:
W.Luo,D. Phung 和 T. Tranet,生物医学研究中开发和报告机器学习预测模型的指南:多学科观点(2016)。医学互联网第 18(12)号决议
动词 (verb 的缩写)Kannan,M. Shapiro 和 M. BilgicKannan,芝加哥食品检验预测模型的事后分析(2019)。在 2019 年 AAAI 秋季研讨会系列(FSS)上发表:
A.Vaid,S. Jaladanki 和 J. Xu,联合学习电子健康记录以改善新冠肺炎住院患者的死亡率预测:机器学习方法(2021)。JMIR 医学信息学第 9 卷
长度 Muhammad,E. Algehyne 和 S. Usman,使用流行病学数据集预测 COVID-19 感染的监督机器学习模型(2021)。SN 计算机科学 2:11
预测反弹概率的全栈机器学习网络应用
在这篇文章中,我将介绍如何开发一个基于 ML 的 web 应用程序,它可以预测单个球员和球队在球场上抢篮板的几率。
首先,让我展示一下这个应用最终的样子:
去看看http://okc-thunder-rebounds.herokuapp.com/(图片作者)
从上图可以看出,用户将可以得到每个球员/每一方抢到篮板球的概率,这对于篮球经营者需要安排关于篮板球的战术是很有用的。从上面的 GIF 中可以看出:
- 我们需要在前端的一个界面,允许用户通过拖动项目放置在球场上的球员;
- 我们需要一个后端内核,它可以根据球员在球场上的位置预测球员抢篮板的任何个人概率(以及球队的总概率);
- 我们需要一台 24/7 全天候托管网站的服务器。
在文章的其余部分,我将按照这个顺序描述我的想法。
前端设计
在开发过程中,我们需要解决两个主要困难:
- 我们如何让用户通过拖动面板上的元素来放置播放器?
- 我应该如何将数据从前端传输到后端,以便我们的模型使用它进行预测?
对于第一个困难,我幸运地发现了这个有用的链接,它可以在网页上拖动任何元素。我更新的是我限制了面板中代表玩家的 div 元素:
对于难度#2,我的变通方法是在
内插入一个不可见的
到目前为止,我们可以让用户把播放器放在他们想放的任何地方,而且机器也能够识别位置。
机器学习模型训练
为了明确这一点,我们需要一个模型来预测球场上任何一个球员或一支球队获得篮板的几率。换句话说,输入将是球员位置的坐标,而输出是被反弹的球员的名字和他的球队。
来看看我的原始数据:
输入列(作者图片)
输出栏(作者图片)
注意:
- 位置代表一个球员在投球时所站的位置,用 x-y 坐标表示。
- X 坐标以英尺为单位,代表从球场中心到球场长度方向的距离。-47 代表进攻队结束的底线。47 代表卫冕队的底线。
- Y 坐标以英尺为单位,表示从篮筐开始的横向距离。-25 代表球场右侧,25 代表球场左侧(对于面对进攻篮下的人)。
- 在输出中,有空值,这意味着进行了相应的投篮或罚球。因为我们只关心有反弹的场景,所以我们需要以后再处理这个问题。
数据清理
首先,让我们去掉不需要的行值:
# remove the rows where the shots or free throws were made
train = train[train['f.oreb'].isna()==False]
接下来,将篮板球员 id 与他在球队中的位置和球队状况(进攻/防守)进行匹配:
# target columns is a list containing the input columns' names
target_columns = []
for event in ['off', 'def']:
for i in range(1, 6):
target_columns.append('playerid_' + event + '_player_' + str(i))reb_player_id_df = train[target_columns].eq(train['reb_player_id'], axis = 0)
reb_player_position_df = reb_player_id_df.idxmax(1).where(reb_player_id_df.any(1)).dropna()# encode all players on court
# 1~5 means a player is an offending one while 6~10 means a defending one
position_code = {
'playerid_off_player_1': 1,
'playerid_off_player_2': 2,
'playerid_off_player_3': 3,
'playerid_off_player_4': 4,
'playerid_off_player_5': 5,
'playerid_def_player_1': 6,
'playerid_def_player_2': 7,
'playerid_def_player_3': 8,
'playerid_def_player_4': 9,
'playerid_def_player_5': 10
}
output = reb_player_position_df.apply(*lambda* x: position_code[x])# reset the index
output = output.reset_index(drop=True)
现在,在很大程度上,标准化数据通常在机器学习中表现更好,因为它减少了离群值的影响,避免陷入局部最优点。因此,由于 x 和 y 坐标都有一定的范围,我们可以尝试最小-最大归一化器:
train[[col for col in location_columns if '_y_' in col]] = (25 - train[[col for col in location_columns if '_y_' in col]]) / (25 - (-25))
train[[col for col in location_columns if '_x_' in col]] = (47 - train[[col for col in location_columns if '_x_' in col]]) / (47 - (-47))
现在数据已经准备好了!
型号选择
我尝试了一套预计在概率预测方面表现良好的模型,包括逻辑回归、线性判别分析、二次判别分析、高斯朴素贝叶斯分类器和多项式朴素贝叶斯分类器。
# define models
models = [LogisticRegression(n_jobs=-1), LinearDiscriminantAnalysis(), QuadraticDiscriminantAnalysis(), GaussianNB(), MultinomialNB()]
交叉验证:
names, values = [], []# evaluate each model one by one
# and store their names and log loss values
for model in models:
# get a name for the model
name = type(model).__name__[:15]
scores = evaluate_model(train, LabelEncoder().fit_transform(output), model)
# output the results
print('>%s %.3f (+/- %.3f)' % (name, np.mean(scores), np.std(scores)))
names.append(name)
values.append(scores)
作者图片
结果表明,线性判别分析优于所有其他同行,所以我选择它作为我的核心算法。
# save the model
dump(LDA, 'LDA.joblib')
后端设计
有了模型在手,就该把它合成一个使用它的模型了。在这个阶段,我的主要工具是 Python 和 Flask。 Flask 是一个用 Python 编写的 lite web 开发框架,常用于机器学习产品。它简单、灵活,允许用户决定实现什么以及如何控制他们的应用程序。
基本上,我们需要一个网页(主页)显示面板和其他位置时,分配。当会话开始时,通过“GET”方法访问主页,而通过“POST”方法访问结果页,因为直到用户单击“submit”按钮,结果页才会显示。
哇!现在我们成功了,我们的开发完成了!只要在工作目录下做以下命令,就可以在浏览器上看到 127.0.0.1:5000 的 app。
> set FLASK_APP=app.py (for windows; for linux, it should be "export FLASK_APP=app.py")
> flask run
服务器部署
你可以有很多选择来托管一个 web 应用。现在我的选择是 Heroku,因为它简单。通常,我会通过 GitHub 部署我的应用程序,这个也不例外。所以现在第一步是为你的程序创建一个新的 repo,包括你的主函数( app.py )、静态目录,以及模板目录。下面是您的文件夹现在的样子:
├── README.md
├── app.py
├── LDA.joblib
├── templates
│ ├── index.html
│ ├── output.html
│ └── output2.html
├── static
├── bar.png
├── basketball.jpg
└── court.png
当你收集这些东西时,还应该包括另外两件东西:
- requirements.txt,这是需要的依赖项列表,您可以通过以下方式快速收集它们:
pip freeze > requirements.txt
注意,冻结后,一定要在 requirements.txt 中追加 gunicorn 。
以下是整个开发过程中需要的内容:
Flask==1.1.2
joblib
matplotlib
pandas
scikit-learn
gunicorn
2.Procfile,它记录了让 Heroku 服务器知道如何激活应用程序的命令。因为我们的应用程序是使用 Flask 开发的,所以我们的命令应该是:
web: gunicorn app:app --log-level debug
一切就绪!您现在可以将此文件夹上传到您新创建的存储库中:
git init
git clone your_github_repo_link
git add .
git commit -m "Initial Commit"
git push
当我们处理完 GitHub 上的问题,是时候看看 Heroku 了。确保你已经在 Heroku 上注册了并且还有空位。
如果是,请在您的个人仪表板上创建一个新应用程序:
作者图片
完成后一般会重定向到 app 的首页。所以你点击“部署”标签,选择通过 GitHub 部署应用程序:
作者图片
并选择您希望它连接的存储库:
作者图片
最后,只差一步了:点击“部署分支”。
作者图片
霍雷。你成功了,现在你可以在插槽提供的链接上看到你所创建的东西了!
结论
这是我第一次使用传统的前端工具(即 HTML、JS 和 CSS)做一些事情,所以我会将它视为我挑战极限的尝试之一,因为我通常会使用更方便的工具,如 Python 和 streamlit。因此,我从这个项目中得到的启示是,永远不要让你的工具限制你,相反,让它们为你的需要服务!
而且你需要这个项目的全部源代码,下面是链接:
https://github.com/MemphisMeng/rebound-app
使用 OptBinning 在 Python 中开发记分卡
仅用几行代码创建行业级记分卡
1.介绍
记分卡是贷款企业用来评估试图获得信贷的客户的风险模型。一个完善的记分卡可以为金融机构带来很多价值,并且对于制定信贷政策至关重要。尽管记分卡背后的数学和逻辑并不复杂,但开发一个性能良好的模型可能很难,因为组织和处理数据需要花费大量时间。
传统的方法是将变量按数字或分类进行分离,并应用宁滨方法,使用每个值的证据权重对与目标(通常为二进制)显示相似关系的值进行分组。宁滨的这个过程可能是耗时的和不完美的,因为关于是否合并库的一些决定可能是判断性的,并且受到记分卡开发者的影响。这也是银行和其他机构需要几个月时间来开发或重新训练记分卡模型的原因之一。
2.选择救援!
OptBinning 试图填补宁滨功能和记分卡开发的可靠性与用 Python(一种广泛用于数据分析的语言)编写库的灵活性之间的差距。
“opt binning是一个用 Python 编写的库,实现了一个严格而灵活的数学编程公式,用于解决二进制、连续或多类目标类型的最优宁滨问题,合并了以前未解决的约束”。
OptBinning 不仅提供了执行宁滨的强大方法,还提供了选择特性、创建记分卡和可视化开发过程中的性能所需的各种工具。
OptBinning 使用 Scikit-Learn BaseEstimator
作为其宁滨类的结构,通过fit
和transform
方法使其使用起来很直观,就像任何 Scikit-Learn 估计器一样。
3.最优宁滨背后的逻辑
宁滨是将一个连续变量的值分成在某个特征方面具有相似行为的组的过程。这种将值离散到桶中的技术对于理解要素和目标之间的关系非常有价值。宁滨是记分卡开发中的一个重要步骤,因为每个 bin 都与一个记分卡值相关联,有助于为模型带来可解释性。
从建模的角度来看,宁滨技术可以解决普遍存在的数据问题,如缺失值的处理、异常值和统计噪声的存在以及数据缩放
— 最优宁滨:数学规划公式,纳瓦斯-帕伦西亚公司
存在许多可用于执行宁滨的技术,并且尽管一些可以被成功实现,但是不能保证它们能够达到最优的仓。变量的最佳宁滨是一个过程,在此过程中,您将样本分组离散化,以满足特定的约束条件,同时优化散度(或性能)指标。该约束可以是特定数量的箱或每个箱的最小样本数。
OptBinning 提供了最佳宁滨过程的有效实施,使您能够控制参数和约束。
4。未理解的选择类
OptBinning 有 3 种主要的类类型,它们在等级上相互关联,执行绑定要素和创建记分卡所需的所有处理。以下课程简要介绍了这些课程的结构。更多详情请参考 OptBinning 官方文档。
4.1.优化组合、连续优化组合和多类优化组合
OptimalBinning
是用二进制目标执行特征宁滨的基类。对于连续或多类目标,还有另外两类可用:ContinuosOptimalBinning
和MulticlassOptimalBinning
。
如前所述,这 3 个类是按照sklearn.base.BaseEstimator
结构用fit
和transform
方法构建的。宁滨使用上述类的一个特性就像下面的代码一样简单:
**# 1) Define your feature and target arrays** X = df_train['feat_name']
y = df_train['target']**# 2) Instantiate class and fit to train dataset** optb = OptimalBinning(name='feat_name', dtype="numerical")
optb.fit(X, y)**# 3) To perform the binning of a dataset** X_binned = optb.transform(X)**# 4) To visualize the results table and plot** optb.binning_table.build()
optb.binning_table.plot(metric="woe")
默认情况下,宁滨类返回相应 bin 类别的证据权重值。除了要素名称和数据类型(数值型或分类型)之外,还有更多的参数可供使用,为这个过程提供了相当大的自定义级别。
4.2.装箱过程
构建类BinningProcess
的目的是在整个数据集上执行最优宁滨,而不仅仅是上面会话中举例说明的一个要素。
所以查看BinningProcess
的最好方式是作为OptimalBinning
的包装器。用法相当简单,只需要几个参数就可以执行完整数据集的宁滨。
**# 1) Define list of features and categorical ones** list_features = df_train.drop(columns=['TARGET']).columns.values
list_categorical = df_train.select_dtypes(include=['object', 'category']).columns.values**# 2) Instantiate BinningProcess** binning_process = BinningProcess(
categorical_variables=list_categorical,
variable_names=list_features)**# 3) Fit and transform dataset** df_train_binned = binning_process.fit_transform(df_train, y)
4.3.记分卡
类ScoreCard
提供了将从BinningProcess
生成的入库数据集与 Scikit-Learn 的线性估计器相结合的可能性,以生成生产就绪记分卡。
**# 1) Define a linear estimator (model)** from sklearn.linear_model import LogisticRegression
logreg = LogisticRegression()**# 2) Instatiate a ScoreCard and fit to dataset** scaling_method = "min_max"
scaling_method_data = {"min": 0, "max": 1000}
scorecard = Scorecard(
target='TARGET',
binning_process=binning_process,
estimator=logreg,
scaling_method=scaling_method,
scaling_method_params=scaling_method_data,
intercept_based=False,
reverse_scorecard=True,
)scorecard.fit(df_application_train)
因此,只需几行代码,您就可以创建一个随时可以测试并投入生产的记分卡模型!关于如何创建和验证记分卡的教程将在下一节课中详细介绍。
图 1 总结了 OptBinning 中的类之间的关系。
图一。选择类的层次结构。
5.教程:使用 OptBinning 创建记分卡
为了说明使用 Optbinning 创建生产就绪记分卡的流程,我们将使用 Kaggle 的家庭信用风险违约数据集。你可以在教程资源库 上找到有代码的 Jupyter 笔记本。
5.1.加载数据集
从 Kaggle 的页面下载数据集文件并解压文件夹后,您将得到几个 CSV 文件。这些文件是 Kaggle 的挑战描述的一部分,其中包含有关功能和表格的信息。我们将使用application_train.csv
文件来演示 OptBinning。在将数据集作为 Pandas Dataframe 加载后,我们将列SK_ID_CURR
设置为索引(第 8 行),并将数据集分为 train 和 test(第 11 行)。
5.2.探索功能和入库流程
特征工程是任何模型开发中最重要的步骤之一,记分卡也不例外。由于我们的重点是演示记分卡开发的 OptBinning 用法,因此我们不会从数据集中探究太多工程特性的可能性。我们的方法是将分类特征从数字特征中分离出来,并在实例化BinningProcess
时定义它们,因为最佳宁滨过程以不同的方式处理这些类型的特征。我们在这个阶段需要设置的另一个参数是selection_criteria
,它是用来定义最优 bin 的约束。
5.3.选择线性估计量
OptBinning 的一个很大的特点是可以灵活地选择 Scikit_learn 的任何线性模型(估计器)用于您的记分卡。通过调整线性估计器的参数,您可以提高记分卡的性能。我们将使用逻辑回归来说明这种用法,但也可以随意探索其他估计量。
5.4.创建记分卡
在实例化了一个BinningProcess
和一个线性估计器之后,您需要指定缩放参数(第 2 行和第 3 行)并将它们传递给您的Scorecard
实例。接下来,对数据集运行 fit 方法(第 16 行),就这样!几秒钟后,您的记分卡就可以进行绩效验证了。
5.5.可视化和验证记分卡
OptBinning 为您提供了多种可视化和评估记分卡的方法。您可以访问包含每个入库特性指标的记分卡表,并将其保存为 CSV 格式,以记录模型开发。
下面您可以看到记分卡表的一部分:
最后,您可以使用optbinning.scorecard.plots
模块中的函数来可视化您的记分卡绩效。
图。经过训练的记分卡模型的 KS 和 ROC-AUC 图。
5.6.在生产中使用记分卡
OptBinning 最令人难以置信的特性之一是其易于投入生产的能力。您可以用 pickle 保存您的记分卡模型,存储它并在生产中使用它。要执行预测,您只需解开记分卡对象,用您用来开发模型的特性对 Pandas 数据框架中的样本进行评分。
5.结束语
多年来,记分卡的发展局限于大型金融机构,这些机构有钱购买像 SAS 这样昂贵的软件工具。我们展示了 OptBinning 在宁滨变量和创建记分卡方面的强大功能和多功能性。
将 Optbinning 与 Python 的数据科学和分析库(Pandas、Numpy、Matplotlib、Scikit-Learn)相结合,可以提供开发行业级记分卡模型所需的所有工具。
这可能会改变小企业和金融科技公司的游戏规则,因为所有这些提到的库都是开源的,这意味着这些公司需要做的唯一投资是人力资源。
非常感谢你阅读我的文章!
资源
有关 OptBinning 的更多信息,请查看项目 GitHub 页面和文档页面。有关最优宁滨背后的逻辑和数学的信息,您可以在 Guillermo Navas-Palencia 的文章“最优宁滨:数学规划公式”中找到描述。
https://github.com/guillermo-navas-palencia/optbinning https://github.com/GabrielSGoncalves/optbinning_tutorial
为 ML 工程师的角色开发软件工程技能
如何打包一个井字游戏上传到 PyPI
正在玩井字游戏;作者 GIF
我注意到数据科学领域的一个增长趋势是 MLOps 的兴起。MLOps 包含了将实验机器学习模型引入生产 web 系统的一组新兴最佳实践。
实验是伟大的,但总有一天你真的想建造一些有形的东西,让别人从中受益——至少我知道我的时间到了。正是因为这个原因,我一直在提高我的编程和软件工程技能,因为这些技能在将实验性机器学习项目投入生产中发挥着至关重要的作用。
动机
我的文章的读者,特别是那些阅读了 的读者,当我意识到数据科学证书不会推动我的职业发展 时,他们会知道我更喜欢在发展新技能时采取动手的方式。因此,我决定自己开发一个有趣的井字游戏项目,让自己熟悉 Python 的面向对象编程(OOP ),然后我将项目打包并上传到 PyPI——是的,这意味着您可以直接安装我的项目。
https://github.com/kurtispykes/board_games
步骤 1 —添加结构
与我们数据科学家习惯于在我们的 Jupyter 笔记本上进行的实验不同,软件工程项目一开始就有非常好的结构,因为他们试图完成的事情已经有了明确的最终目标——这只能在数据科学的大量实验之后才能知道。
在机器学习项目中,实际编写的代码只占机器学习系统的一小部分,因此有理由将大规模的机器学习项目视为软件项目。因此,花时间构建机器学习项目从长远来看是有回报的,还有什么比创建一个真正的软件项目更好的获取经验的方式呢?
board_game/
├── LICENSE
├── pyproject.toml
├── README.md
├── setup.cfg
├── setup.py # optional, needed to make editable pip installs work
├── tic_tac_toe/
│ └── __init__.py/
│ └── board.py
└── tests/
分解文件/文件夹
上面的代码单元格详细描述了项目的结构,但下面是每个文件的含义:
- 这告诉安装你的软件包的用户他们可以使用你的软件包的条款。如需挑选许可证的帮助,请参见https://choosealicense.com/。
pyproject.toml
:一个文件,通知构建工具,如pip
和build
,正在使用的系统以及构建所必需的东西。如果这个文件在您的项目中不存在,将会采用经典的 setuptools 构建系统,但是明确一点总是好的。README.md
:一个README.md
通知其他人如何导航你的项目。阅读 如何让你的数据科学项目脱颖而出 。setup.cfg
:setup.cfg
是静态元数据,通知 setuptools 您正在创建的包(即包的名称、版本等)。)以及应该包含哪些代码文件。setup.py
:setup.py
是动态元数据,用作 setuptools 的构建脚本。事实上,因为我使用了setup.cfg
,所以我不需要包含setup.py
——文档更深入地解释了这是为什么。- 这是我们保存源代码的目录。
board_game/tic_tac_toe/__init__.py
需要将目录作为一个包导入,并且应该为空。 tests/
:该目录是测试文件的占位符。我目前还没有写任何测试,但是我计划在将来写——测试驱动开发(TDD)的倡导者为此毁了我,我认为软件工程师一般也会这么做。测试是至关重要的,因为它在交付给客户之前发现缺陷/错误,这保证了软件的质量(你不会搭上一辆没有经过测试的车吧?).
步骤 2 —构建应用程序
因为我已经非常熟悉编程了,所以我不想浪费太多时间去思考程序的逻辑——记住,我的主要精力是发展我的 OOP 技能,并获得打包项目的经验,然后可以上传到 PyPI。
为了避免项目的逻辑问题,我从井字游戏——Python Tkinter GUI 教程中提取了代码,但是我没有按原样提取代码,而是将脚本转换成了 Python 对象。
什么是阶级?
A class
定义了在 Python 中创建对象的模板——为了帮助简化对象的概念,考虑现实世界中的事物,比如人、汽车等。所有的物体都有特征,这被称为属性,例如,我是一个人(物体),但我有区别于其他人的特征(属性)(例如,我身高 5 英尺 9 英寸,我有一头黑发,我是一名自由职业者,等等)。
除了我的属性之外,我还可以执行允许我在现实世界中有效运行的动作,这些动作被称为方法。例如,我可以锻炼,看书,看网飞等。
这实际上是 Python 中对象的组成部分。要用 Python 创建一个对象,我们必须调用class
。下面是我用来创建井字游戏对象的代码。
注意:在未来,我希望回到这个代码来重构和改进它,因此,我非常欢迎批评。
步骤 3 —生成分发归档文件
为了允许其他人在进行pip install
时访问我们的包,我们必须为我们的包生成分发包。我使用的是 Windows 操作系统,所以我将用它来生成这个包——Unix/MAC OS 操作系统的用户应该参考 PyPA 文档。
流程的第一部分是确保我们安装了 PyPA 的build
的最新版本。从终端,确保您处于项目虚拟环境中,并输入:
py -m install --upgrade build
接下来,导航到包含pyproject.toml
的目录并输入:
py -m build
运行该命令后,您将在终端中看到大量文本,但是为了确保一切顺利进行,您可以检查运行该命令的目录中是否有一个包含 2 个文件的dist/
目录。这是我的样子…
要加载到 Dist 目录中的示例文件;作者图片
步骤 4 —将包上传到 PyPI 测试
完成后,我们现在可以将我们的包上传到 Python 包索引(PyPI)中。
为了上传一个包,你必须在 PyPI 的索引中注册——这是你必须执行的最简单的步骤——使用下面的链接分别在 PyPI 和 TestPyPI 中注册。
注意 : PyPI 测试是 PyPI 的一个独立实例。它用于测试和实验。
注册后,我们可以上传twine
包来帮助我们上传发行包。
# installing twine
py -m pip install --upgrade twine# uploading the archives under the dist directory
py -m twine upload --repository testpypi dist/*
从您的终端运行上面的命令将提示您插入用于注册 TestPyPI 的用户名和密码。完成后,您的包现在应该可以在 TestPyPI 上看到了。这是我的…
https://test.pypi.org/project/board-game-kurtispykes/0.0.1/
步骤 5 —安装软件包
为了验证我们的包能够工作,使用pip
来安装它并测试它的功能是一个好主意。
pip install -i https://test.pypi.org/simple/ board-game-kurtispykes==0.0.1
接下来,转到一个新的终端,输入py
,通过运行以下命令来测试这个包:
作者图片
输出应该会为井字游戏打开一个新窗口。
最后的想法
我现在已经将这个包上传到 PyPI,所以您可以简单地安装这个包并通过运行pip install board_game_kurtispykes
来测试它。虽然我很清楚这个包符合 Scikit-Learn 或 NumPy 的标准,但我对我在整个过程中获得的经验非常满意,并期待在以后重新访问这个包,以改进我的代码、额外的测试和可能的更多功能(包括更多游戏)——我非常乐意接受批评,因为我知道软件工程技能不是我的强项。
感谢您的阅读!
如果你喜欢这篇文章,请通过订阅我的免费 每周简讯与我联系。不要错过我写的关于人工智能、数据科学和自由职业的帖子。
相关文章
💔-ways-to-drastically-improve-your-programming-skills-on-your-own-9359ce670099> </5-laws-for-self-taught-data-professionals-4bf351ac5c24> [## 自学成才的数据专业人员的 5 条法则
towardsdatascience.com](/5-laws-for-self-taught-data-professionals-4bf351ac5c24)
开发 SQL 表
只有通过创建和开发 SQL 表,我们才能理解如何最好地使用可用内存。
彼得·赫尔曼在 Unsplash 上的照片
介绍
本文旨在解决维护 SQL 表时所需的四个核心步骤。我们正在讨论的是四种运算;创建、读取、更新和删除,通常称为 CRUD。使用 CRUD,我们能够从数据库中当前可用的任何信息或者我们在最初创建数据库之后添加的任何信息构建 SQL 表。
SQLite 用于完成以下分析。正在审查的示例将着眼于创建一个包含足球运动员详细信息的简单数据集。
创建和读取
创建表的操作有助于创建一个空壳。有了一个空的外壳,用户就能够用适当的数据填充它。理解正确的数据格式是创建这个空表的关键。有许多不同的变量格式可用,字符串和数字构成了变量值的大部分。因此,能够为所需的作业正确分配正确的格式确实需要一些规划。
而对表的读取可以被认为是查看包含在表中的数据。通过拍摄变量的快照,我们能够了解当前值的范围。此外,它还为开发人员提供了探索信息的机会,这些信息可以通过以不同方式查询数据来获得。
在表的初始开发过程中,很可能会涉及一些试错。特别是,如果要输入到表中的数据在计划阶段没有全部可用。我们将通过一些例子来了解如何克服这种未知的品质。
SQL 代码 1.1 使用不同的变量名和格式创建初始表
在 SQL 代码 1.1 中,我们用四个不同的变量创建了 player_details 表。通过使用字符串格式 VARCHAR(),它允许我们为数据集中的每一行提供不同长度的字符值。需要强调的一点是,对于 SQLite 数据库,括号中包含的值将被忽略,并且没有长度限制。下面的示例细节有助于读者理解在括号中赋值的概念。这个值最常见的情况是对许多其他具有更严格数据结构的 SQL 语言产生影响。第一个括号(100)中包含的值与可用于变量数据输入的 100 个唯一值相关。
如果我们为 player_name 包含的值只包含 4 个字符,则还会包含 96 个空字符。因此,这可能被视为内存使用效率低下,因为与填充的值相比,空值更多。但是,如果这个变量的最大值是 3,并且我们已经提供了值“Mark”,那么字符串将被截断以显示“Mar”,因为变量中没有足够的可用值。在 SQL 表中开发字符变量时,我们必须意识到这个未知的量。通过了解所有潜在输入值的最大长度,我们可以有效地为字符变量分配一个合适的最大值。
由于表中的前三个变量是字符串格式,因此在为输入值适当分配足够的空间方面,每个变量都面临着上述相同的挑战。第四个变量与表中唯一的数字变量有关。在这个实例中,整数格式值(INT)作为变量类型提供。由于所有要分配的值都是整数,没有球员会被分配半场出场,即使他们只打了半场比赛,整数格式也是最有意义的。如果数值变量需要十进制值,那么使用的格式应该是 float。了解将要使用的数值范围有助于分配有效的变量格式。
SQL 代码 1.2 从创建的表 player_details 中读取值
为了从创建的 SQL 表中读取值,我们尝试了 SELECT 语句。然而,当执行该查询时,没有返回任何结果。
输出 1.1 产生初始的 player_details 表
我们可以通过浏览数据库中可用的表来查看上面显示的输出值。在 player_details 表中,显示了变量名,但没有要查看的值。在接下来的部分中,我们将了解如何在这个新表中插入和删除数据行。
插入
有了这个表,我们现在可以插入值并开始执行数据分析。插入值时,我们总是希望确保正确的值与正确的变量对齐。这就是变量名与初始表设计顺序相同有助于避免混淆的地方。
SQL 代码 1.3 将值插入表变量
使用上面的 SQL 代码插入的值已经被分配给正确的变量。为了更好地理解这一点,我们突出显示了要插入数据的表。下面的括号中提供了需要填充的变量。变量顺序与表设计对齐后,可以更新括号中的值并将其插入到表中。
输出 1.2 插入新值后查看表格结果
运行 insert SQL 查询后,我们可以执行 select 语句来读取表中的值,并查看所有内容是否都已正确添加。这个简短的 insert 语句强调了如何将一行值插入到表中。此外,可以使用相同的方法提供和插入多行值。但是,随着行数的增加,使用这种方法可能会出现数据输入错误。因此,执行 select 语句从其他导入的表中检索数据并将这些数据插入表中有助于减少发生错误的可能性。
更新
随着数据插入到表中,有时开发人员需要调整已经分配的值。我们可以重新开始创建和插入过程。但是,如果表是使用已经被删除的数据创建的,那么这可能不是一个可行的选择。存在一个替代解决方案,这就是使用 update 语句。
SQL 代码 1.4 更新初始值以调整其中一个变量值
正如我们在 SQL 代码中看到的,使用了一个 update 语句将团队名称从最初的缩写调整为长名称格式。我们首先请求将 update 语句应用于适当的表。在这里,我们使用 set 语句来突出显示我们希望更新的变量,并提供相应的值。为了确保只更新需要更新的行值,我们使用 where filter 语句来选择行。因为这个表中只有一个值需要更新,所以添加这一行额外的代码可能没有意义。但是,习惯于过滤所需的适当行值是一个很好的实践。除了在一个变量上使用 set 语句之外,我们还可以提供多个变量名并更新值,如果这些都需要更改的话。
输出 1.3 确认新变量值已被正确更新
上面的输出结果显示,表中的更改已经按照预期进行了更新。如果需要更新变量但保留原始变量值的记录,可以向表中添加一个额外的变量。这种维护表格变化记录的过程可以被记录下来以供将来参考。
删除
最后,在创建、读取、插入和更新表中的值并执行适当的数据分析之后,我们可能必须删除不再需要的值。一种常见的做法是执行数据分析以产生与业务利益相关者共享的管理信息(MI)或商业智能(BI ),然后必须删除数据以供存储。我们能够维护使用所有 SQL 语句创建分析所需的流程步骤,但是可能需要删除信息才能执行其他分析。
SQL 代码 1.5 删除表中与 where 筛选器子句相关的值
类似于 insert 和 update 语句,对于 delete 语句,我们必须概述要执行查询的表。此外,我们还提供了 where filter 子句,以确保只删除需要删除的数据。如果没有指定 where 子句,则附加数据可能会被删除。
输出 1.4 删除值后的表格摘要
上面的输出表显示数据已从表中成功删除。在执行任何 delete 语句时,我们必须非常小心,确保所执行的操作是必需的。此外,我们可能需要更高级别的授权来执行该语句,因为应该始终谨慎处理数据的永久删除。在这个例子中,我们概述了可以用来确保只删除某些数据值的语句。
结论
在本文中,我们发现了作为 SQL 开发人员在使用表时的无限可能性。我们从创建一个初始表的过程开始,该表用于存储未来的值。在这里,我们展示了如何将值插入到表中,然后读取这些值以查看它们是否被正确插入。与任何表一样,有些值可能需要调整,这就是使用 update 语句更改变量值的地方。最后,我们看到了在执行所有数据分析后,我们如何能够删除数据集。正是这个删除步骤可以由数据库自动执行以节省内存。但是,如果我们有能力永久删除任何数据,我们应该始终保持谨慎。
非常感谢您阅读
从零开始发展信息和熵的概念
香农的信息和熵是数据科学中的关键概念。继续读下去,看看它们来自哪里。
马库斯·温克勒在 Unsplash 上的照片
我们在日常生活中经常使用信息这个词。读完一本好书后,我们会说它提供了很多信息,看完无聊的广告后,我们会抱怨我们浪费了时间——我们没有得到任何有用的信息。因为高信息量往往是有用的,所以人们试图对信息的确切含义给出一个更严格的定义。
信息最流行的定义是它衡量某事有多令人惊讶。一个事件越令人惊讶,它包含的信息就越多。例如,考虑两种可能的事件:明天有飓风,明天没有飓风。第二个事件几乎总是正确的,因为飓风很少发生。因此,如果你正在看天气预报,天气预报员说“明天晴空万里,阳光明媚”,你可能不会太在意,只是继续你的一天。如果他说将会有飓风,你会立即开始计划,打电话给你所有的朋友和家人,等等。这是稀有和高信息量的结果。我们之前关于好书和电视广告的例子也适用于信息的惊人定义。好书是罕见的——因此当我们读一本时,它是一件大事(高信息)。电视广告很常见,所以我们不在意(低信息量)。
使用惊奇作为信息的定义的好处是,它与概率有着非常自然的关系。也就是说,令人惊讶的事情概率低,暗示高信息对应低概率。数学上,这意味着 I 与 1/p 正相关,其中 I 为信息量, p 为事件发生的概率。然而,这种正相关到底是什么样子的呢?我们可以通过做一个抛硬币思维实验来回答这个问题。
让我们从简单的开始:我们有一个公平的硬币,抛一次。有两种可能的结果(正面/反面),所以我们至少需要 1 位(回想一下,计算机位取值 0 或 1)来表示所有可能的结果。得到正面的概率是。现在让我们想想如果我们抛两次硬币会发生什么。有四种可能的结果,我们至少需要 2 位(00,01,10,11)来表示它们。两次翻转都是正面的概率是。
现在我们问一个关键问题。表示连续获得 1 个头的事件需要多少信息(p =)?如果我们让比特作为我们的信息单位,就像我们刚刚讨论的那样,需要 1 比特的信息。连续两个头呢(p =)?2 位。扩展该模式,一行中的三个头(p = 1/8)将占用 3 位,一行中的 n 个头(p = (1/2)^n)将占用 n 位。从这个模式我们可以看出一个关系: I (比特数)= log_2(1/p) 。这是最广泛使用的信息方程,也被称为香农信息,以纪念发现这个方程的克劳德·香农。
我们可以使用这个公式来确定我们需要用来表示其他事物的信息量/位数。假设我们有一个包含三个字母的字母表:A、B 和 C,它们分别以 50%、25%和 25%的概率出现。我们想用比特找到字母表的最紧凑的表示。根据我们的公式,对于 A,我们需要 log_2(2) = 1 位,对于 B 和 C,我们需要 log_2(4) = 2 位。当然,这些例子是幸运的,因为倒数概率的以 2 为底的对数是一个整数。在实践中,大多数时候情况并非如此。例如,根据公式,你可能得到需要 3.56 位的东西。然而,即使在这些情况下,该公式也为您提供了关于多少位是最佳的良好指导。换句话说,如果您知道您至少需要 3.56 位,那么您使用 20 位的表示法可能会有所改进。
到目前为止,我们已经给出了信息的定义:一个事件有多令人惊讶,或者更精确地说,表示该事件所需的位数。我们还可能对所有可能事件的平均信息(或平均位数)感兴趣。这被称为熵。回到我们的硬币例子,让我们考虑两次翻转的情况。有四种可能性,可能性都一样大。因此,熵只是一种可能情况的信息,其概率为 25%: log_2(4) = 2。在我们的 ABC 示例中,熵将是(1/2)(log _ 2(2))+(1/4)(log _ 2(4))+(1/4)(log _ 2(4))= 1.5。因此,一枚硬币平均抛两次比美国广播公司的一封信给我们更多的信息。
从熵出发,下一步是开发交叉熵的概念,交叉熵基本上是熵(平均信息),当事件的位表示是从不同于实际、真实概率分布的概率分布中导出时。交叉熵常用于机器学习算法中。然而,这个话题更复杂,将在另一篇文章中解释。
在这篇文章中,我们提出了一种表示信息的数学方法,并引入了熵的概念。请留下您可能有的任何问题/评论,如果您感兴趣,请留意交叉熵上的帖子。感谢阅读!
Developing the Go Game (围棋) using matplotlib and NumPy — Part 1
用 matplotlib 绘制围棋棋盘
来源:https://en . Wikipedia . org/wiki/Go _(game)#/media/File:floor goban。JPG
大多数有抱负的数据科学家对 matplotlib 并不陌生,matplotlib 是一个数据可视化库,旨在用 Python 创建静态、动画和交互式可视化。大多数时候,您使用 matplotlib 的经验可能是用它来绘制图表,如条形图、直方图、饼图,甚至 3D 图表。
对我来说,我想探索如何将 matplotlib 用于更有趣的事情——用它来创建一个棋盘游戏,比如围棋。所以我开始挑战自己,看看我是否能创建一个允许用户玩围棋的应用程序。
Go (or more commonly known in Chinese as Weiqi, Weichi (simplified Chinese: 围棋; traditional Chinese: 圍棋; pinyin: wéiqí) is an abstract strategy board game for two players in which the aim is to surround more territory than the opponent. Source: https://en.wikipedia.org/wiki/Go_(game)
我对这个项目的意图很简单:
- 使用 matplotlib 作为 UI 库来绘制围棋棋盘
- 探索 matplotlib 的交互特性,允许用户在围棋棋盘上放置棋子
- 使用 NumPy 来检测一组石头是否被包围并需要从棋盘上移除
- 计算玩家占领的领域数量,并决定游戏的赢家
- 使用套接字编程允许用户通过网络玩游戏
- 在未来,使用机器学习和深度学习使应用程序能够与人类对手进行比赛。
这在很大程度上是一个正在进行的项目,但我想分享一下事情是如何进行的。如果你有比我更好的技术,请一定在评论中与我分享。
在本系列的第一部分,我将向您展示我是如何使用 matplotlib 绘制围棋棋盘的。
画板
让我们开始画围棋棋盘。对于这个项目,我将创建一个名为 go.py 的文本文件。
为了绘制棋盘,我将定义一个名为draw_board()
的函数,并将其添加到 go.py 文件中:
import matplotlib.pyplot as plt
import numpy as npdef draw_board():
# create a figure to draw the board
fig = plt.figure(figsize=[9,9]) # set the background color
fig.patch.set_facecolor((0.85,0.64,0.125)) ax = fig.add_subplot(111) # turn off the axes
ax.set_axis_off() return fig, axfig, ax = draw_board()
plt.show()
在上面的函数中,我:
- 创建了一个尺寸为 9 英寸乘 9 英寸(宽,高)的 matplotlib 图形
- 使用图形的
Patch
对象的set_facecolor()
功能设置电路板颜色
一个
Patch
对象是一个具有面部颜色和边缘颜色的 2D 艺术家。
- 在当前图形中创建了一个绘图,它返回一个
Axes
对象 - 使用
set_axis_off()
功能关闭轴标签 - 将图形和轴对象返回给函数的调用者
要运行该应用程序,请在终端中键入以下内容:
$ **python go.py**
空的围棋棋盘现在看起来像这样:
作者图片
绘制网格
画板画好了,现在是画格子的时候了。一个标准的围棋棋盘有 19×19 的线网格,包含 361 个点。要绘制网格线,首先要绘制 19 条垂直线,每条线的坐标如下所示:
作者图片
可以用plot()
功能画一条直线。plot 函数接受以下参数:
作者图片
对于水平线,坐标如下所示:
作者图片
现在,您可以将它编写成代码。为此,我定义了draw_grids()
函数来绘制 19 条垂直线和 19 条水平线:
import matplotlib.pyplot as plt
import numpy as npdef draw_board():
... return fig, ax**def draw_grids(ax):
# draw the vertical lines
for x in range(19):
ax.plot([x, x], [0,18], 'k')** **# draw the horizontal lines
for y in range(19):
ax.plot([0, 18], [y,y], 'k')**fig, ax = draw_board()
**draw_grids(ax)**plt.show()
围棋棋盘现在看起来像这样:
作者图片
注意到网格周围有相当多的边界(也是偏心的),所以让我们试着减少网格周围的空间。为此,让我们首先打印出地块的边界:
def draw_grids(ax):
# draw the vertical lines
for x in range(19):
ax.plot([x, x], [0,18], 'k') # draw the horizontal lines
for y in range(19):
ax.plot([0, 18], [y,y], 'k') **print(ax.get_position().bounds)**
您将看到以下输出:
(0.125, 0.10999999999999999, 0.775, 0.77)
上面的输出对应于下面所示的点和尺寸:
作者图片
要使网格居中,请按如下方式设置轴的位置:
def draw_grids(ax):
# draw the vertical lines
for x in range(19):
ax.plot([x, x], [0,18], 'k') # draw the horizontal lines
for y in range(19):
ax.plot([0, 18], [y,y], 'k') **ax.set_position([0, 0, 1, 1])**
print(ax.get_position().bounds)
网格现在位于图形的中心:
作者图片
由于我想在底部留出一些空间(用于稍后的一些按钮),我将进一步调整 y 值:
ax.set_position([0, **0.2**, 1, 1])
这是网格的最终位置:
作者图片
画出星点
围棋棋盘上有九个虚线点,称为星点。
中央的点也被称为中央星。
此时,了解网格中各点的坐标非常有用:
作者图片
因此,如果您想要绘制一个点,如下图所示,该点的坐标为 (3,9) :
作者图片
你现在可以定义一个名为draw_star_points()
的函数来绘制所有的星点:
import matplotlib.pyplot as plt
import numpy as npdef draw_board():
...return fig, axdef draw_grids(ax):
...**def draw_star_points(ax, x, y):
ax.plot(x,y,'o',markersize=8,
markeredgecolor=(0,0,0),
markerfacecolor='k',
markeredgewidth=1)**fig, ax = draw_board()
draw_grids(ax)**# draw the 9 star points on the board
draw_star_points(ax, 3,3)
draw_star_points(ax, 3,9)
draw_star_points(ax, 3,15)****draw_star_points(ax, 9,3)
draw_star_points(ax, 9,9)
draw_star_points(ax, 9,15)****draw_star_points(ax, 15,3)
draw_star_points(ax, 15,9)
draw_star_points(ax, 15,15)**plt.show()
最终的围棋棋盘现在看起来像这样:
作者图片
摘要
至此,我已经使用 matplotlib 成功绘制了一个围棋棋盘。看起来不错,像一个真正的围棋棋盘。下一部分将是让用户把石头放在棋盘上,并有能力在白色和黑色的石头之间交替。敬请期待!
https://weimenglee.medium.com/membership
Developing the Go Game (围棋) Using matplotlib and NumPy — Part 2
执行围棋规则
来源:https://en . Wikipedia . org/wiki/Go _(game)#/media/File:Four _ Arts _ China _ Japan . jpg
在我之前的文章中,我讨论了如何使用 matplotlib 绘制围棋棋盘:
</developing-the-go-game-围棋-using-matplotlib-and-numpy-part-1-3f94127d73e6>
现在你已经能够画出围棋棋盘了,是时候做下一步了,在棋盘上放置石头——这是这个项目最有趣的方面之一。也就是说,您将允许用户在棋盘上放置石头,并以编程方式实现 Go 规则。您的计划将:
- 检查用户是否已将棋子放在围棋棋盘上的正确位置
- 确保被对手包围的一块(或一组相连的)石头将被从棋盘上移走
- 确保用户不会将石头放在导致他自己的石头(或一组相连的石头)被移走的位置
在我们开始之前,这里是我们在上一篇文章中开发的程序( go.py )。我们将从这里继续:
import matplotlib.pyplot as plt
import numpy as npdef draw_board():
# create a figure to draw the board
fig = plt.figure(figsize=[9,9]) # set the background color
fig.patch.set_facecolor((0.85,0.64,0.125)) ax = fig.add_subplot(111) # turn off the axes
ax.set_axis_off() return fig, axdef draw_grids(ax):
# draw the vertical lines
for x in range(19):
ax.plot([x, x], [0,18], 'k') # draw the horizontal lines
for y in range(19):
ax.plot([0, 18], [y,y], 'k') ax.set_position([0,0.02,1,1])def draw_star_points(ax, x, y):
ax.plot(x,y,'o',markersize=8,
markeredgecolor=(0,0,0),
markerfacecolor='k',
markeredgewidth=1)#-----main-----
fig, ax = draw_board()
draw_grids(ax)# draw the 9 star points on the board
draw_star_points(ax, 3,3)
draw_star_points(ax, 3,9)
draw_star_points(ax, 3,15)
draw_star_points(ax, 9,3)
draw_star_points(ax, 9,9)
draw_star_points(ax, 9,15)
draw_star_points(ax, 15,3)
draw_star_points(ax, 15,9)
draw_star_points(ax, 15,15)plt.show()
处理代表围棋棋盘的图形上的事件
为了让用户在棋盘上放置石头,您需要处理 matplotlib 图形上的事件。要处理图形上的点击,使用canvas.mpl_connect()
函数将一个事件连接到一个函数(事件处理器):
**# event handler to handle click on the board
def on_click(event):
print(f'x: {event.xdata} y: {event.ydata}')****fig.canvas.mpl_connect('button_press_event', on_click)**
**button_press_event**
事件是鼠标左键被按下时触发的事件。在连接到该事件的处理程序中,可以通过event
参数知道用户点击图形的位置(xdata
和ydata
):
def on_click(**event**):
print(f'x: {**event.xdata**} y: {**event.ydata**}')
下图显示了代表 Go 板的图形的 x 和 y 坐标,供您参考:
作者图片
现在让我们将事件和事件处理程序添加到 go.py 文件中:
import matplotlib.pyplot as plt
import numpy as npdef draw_board():
...def draw_grids(ax):
...
def draw_star_points(ax, x, y):
...**# event handler to handle click on the board
def on_click(event):
print(f'x: {event.xdata} y: {event.ydata}')
**
fig, ax = draw_board()
draw_grids(ax)# draw the 9 star points on the board
draw_star_points(ax, 3,3)
draw_star_points(ax, 3,9)
draw_star_points(ax, 3,15)
draw_star_points(ax, 9,3)
draw_star_points(ax, 9,9)
draw_star_points(ax, 9,15)
draw_star_points(ax, 15,3)
draw_star_points(ax, 15,9)
draw_star_points(ax, 15,15)**cid = fig.canvas.mpl_connect('button_press_event', on_click)**plt.show()
让我们通过在终端中运行应用程序来试验代码:
$ **python go.py**
当你运行它时,点击板上的一个点,观察打印的坐标。例如,如果您单击下图中用红点表示的点,您应该会看到类似的输出:
作者图片
现在让我们声明一些全局变量:
...
draw_star_points(ax, 15,9)
draw_star_points(ax, 15,15)**# 0 for empty
# 1 for white
# -1 for black****# color of the stone to start
white = True****# stones is for storing the plot (containing the stone)
stones = np.full((19,19), None)****# stones_values stores the value (color) of each point
stones_values = np.full((19,19),0)**cid = fig.canvas.mpl_connect('button_press_event', on_click)
plt.show()
在上面的代码片段中,您有:
- 全局变量
white
指示当前石头颜色是否为白色。如果其值为True
,则当前石头颜色为白色,否则为黑色。 - 2 个 NumPy 阵列—
stones
和stones_values
。stones
数组用于存储显示每块石头的图形。当一个棋子被放置在棋盘上的一个特定的交叉点时,一个代表该棋子的图形被创建。这个情节然后被保存在stones
2D 阵列中。在空白板上,数组中的每个值都被设置为None
。另一方面,stones_values
数组存储棋盘上每颗棋子的整数值。如果放置了白石,则存储值 1;如果石头是黑色的,那么值是-1。值为 0 表示板上有一个空的交叉点。
当用户点击棋盘上的一个交叉点时,你就可以开始画石头了。为了在板上画一块石头,我将定义一个名为draw_stone()
的函数:
# draw the stone on the board
def draw_stone(x,y,color):
stone = ax.plot(x,y,'o',markersize=28,
markeredgecolor=(0,0,0),
markerfacecolor=color,
markeredgewidth=1)
return stone
该功能:
- 接受 3 个参数——放置石头的交叉点的 x 和 y 坐标,以及要绘制的石头的颜色
- 返回包含绘制的石头的图
要绘制石头,只需调用on_click()
事件处理程序中的draw_stone()
函数:
# event handler to handle click on the board
def on_click(event):
print(f'x: {event.xdata} y: {event.ydata}') ** # reference the gloabl variable
global white** **# get the points clicked on the board
if event.xdata == None or event.ydata == None:
return
x = int(round(event.xdata))
y = int(round(event.ydata))** **#----------------------------
# Draw the stone on the board
#----------------------------
# make sure the area clicked is within the board
if 0<=x<=18 and 0<=y<=18:
# if the user left clicked to add a new stone on the board
if event.button == 1 and stones[x,y] == None:
stones[x,y] = draw_stone(x,y,'w' if white else 'k')
stones_values[x,y] = 1 if white else -1
white = not white # switch the color** **# when user right-clicked to remove a stone on the board
elif event.button == 3 and stones[x,y] != None:
stones[x,y].pop().remove() # remove the plot
stones[x,y] = None
stones_values[x,y] = 0
else:
return
plt.draw() # update the figure** **print(stones)
print(stones_values)
else:
return**
在上面的代码片段中,您:
- 对 x 坐标和 y 坐标进行舍入,并将其转换为整数值
- 确保点击的坐标在板的范围内
- 如果用户点击左键(
**event.button == 1**
)并且当前点没有石头,则向棋盘添加一颗石头 - 如果用户右击(
**event.button ==**
3 )鼠标,从棋盘上移除一颗棋子 - 打印
stones
和stones_values
数组的值 - 一旦一块石头被放在棋盘上,就要改变它的颜色(白色变成黑色,黑色变成白色)
在围棋规则中,一旦棋子被放在棋盘上,玩家就不能移动(撤消)它,除非它被对手包围。在我们的实现中,出于测试目的,我允许这样做。
让我们通过在终端中运行应用程序来测试代码:
$ **python go.py**
让我们在棋盘的左下角添加一块白石:
作者图片
您应该会看到以下输出。这是stones
数组的输出:
[[**list([<matplotlib.lines.Line2D object at 0x12b57bfd0>])** None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]]
下一个输出是stones_values
数组的值:
[[**1** 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]]
在实际的围棋棋盘上,左下角截距的坐标是(0,0),而在 2D 数组表示中,位置(0,0)在左上角。下图对此进行了描述:
作者图片
如果您想看到棋盘和 2D 阵列在同一方向,您可以使用np.rot90()
功能将 2D 阵列逆时针旋转 90 度:
print(**np.rot90(stones)**)
print(**np.rot90(stones_values)**)
为了测试这一点,让我们再次运行该程序,这一次,放置两块石头,如下所示:
作者图片
现在,输出应该与电路板布局相对应:
[[None None None None None None None None None None None None None None None None None None **list([<matplotlib.lines.Line2D object at 0x11b383c70>])**]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[None None None None None None None None None None None None None None None None None None None]
[**list([<matplotlib.lines.Line2D object at 0x12eb0b070>])** None None None None None None None None None None None None None None None None None None]][[ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 **-1**]
[ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[ **1** 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]]
要移除石头,只需右击石头,它就会从棋盘上被移除。
围棋规则
往棋盘上放石头是最简单的部分。最激动人心的部分是执行围棋规则。让我们在下面的章节中讨论围棋的规则。
自由
在围棋中,沿着水平轴和垂直轴与一颗石头相邻的一个空点被称为自由点。下图显示了自由的例子:
- 棋盘顶部的白色石头有 4 个自由度——它有四个相邻的空点——左、右、上、下。
- 左下角的白色石头只有一个自由——顶。
- 右下角的白色石头有两个自由度——上和右
作者图片
没有自由的石头被认为被对手包围,必须从棋盘上拿走:
作者图片
成组的连锁石头
石头也可以水平和/或垂直链接。如果一个团体没有至少一个自由,那么这个团体必须被移除。在下图中,一组白色石头被锁链锁住,并被黑色石头包围,因此必须全部移除:
作者图片
边缘案例
放在棋盘边缘的石头呢?考虑下面的例子:
- 黑色的石头被白色包围着:
作者图片
- 那群黑色的石头被白色包围着:
作者图片
- 这群白色的石头被黑色包围着:
作者图片
- 这群白色的石头被黑色包围着:
作者图片
- 这群白色的石头被黑色包围着:
作者图片
自杀案件
自杀的情况是玩家将石头放在一个位置,导致他/她自己的石头被移走。一般来说,围棋中不允许自杀。
这是一个自杀的例子,如果一个玩家在中间放一块黑色的石头:
作者图片
这是另一个例子。如果将一块白石放在黑石组内的空交叉点上,这将导致现有的白石也被移除:
作者图片
然而,在有些情况下,看起来像自杀的案例是有效的。考虑下面左边的图。如果将一颗黑石放在一组白石中的空白交叉点上,这将基本上移除白石(见右图)。在这种情况下,此举是有效的。
作者图片
移除周围的石头
既然您已经熟悉了 Go 的规则,那么是时候使用 Python 以编程方式实施所有这些规则了。让我们考虑几种不同的情况:
简单案例
最简单的情况是检查单个的石头是否被对手包围。假设当前玩家刚刚在以下位置放置了一颗白石:
作者图片
一旦放置了白棋,你只需要检查棋盘上是否有黑棋被白棋完全包围(即没有自由)。没有自由的石头可以从棋盘上移走。
边缘情况
稍微复杂一点的情况是边缘情况。假设您刚刚将黑石放置在如下所示的位置:
作者图片
放置黑石后,你需要立即检查棋盘上是否有白石被包围。对于边缘情况,您需要将“假想的”石头放在边缘之外,如下所示:
作者图片
如果当前出的石头是白色的,那么边缘外的假想石头也是白色的:
作者图片
然后你可以继续移除被包围的石头。
群体案例
最复杂的情况是当石头被成组地用链子锁起来。你需要发现被对手包围的一群石头。考虑以下情况,白棋刚刚在棋盘上放了一颗石子,需要检查是否有任何黑棋需要被移走:
作者图片
哪块黑石头至少有一项自由,哪块没有任何自由?下图显示了至少有一个自由度的黑色石头,用值 8 表示,而没有自由度的石头则用它们现有的颜色代码表示(-1 表示黑色)。
作者图片
如前所述,没有自由的石头可以从棋盘上移除。然而,上面的例子表明,黑色的石头形成了一个群体。显然,在这个群体的顶端有一种自由。这意味着黑色的石头群还活着。所以在这种情况下,你还不能移除黑石。为了确保所有其他黑色石头都不会被移除,你需要“感染”值为 8 的连接石头(这意味着该石头拥有自由):
作者图片
重复这个过程,直到整组石头都是 8:
作者图片
感染的最终结果表明,这一整组黑石头仍然活着,因此它们不应从棋盘上移除:
作者图片
在“感染”阶段结束时,所有没有自由的石头(用它们自己的颜色代码表示)将被移除。
履行
我们现在终于可以使用 Python 和 NumPy 实现 Go 的规则了。首先,让我们编写一个名为remove_stones()
的函数,它有以下参数和返回值:
color
—要移除的石头的颜色remove
—如果你要从棋盘上拿走石头;该参数对于检查自杀案例非常有用(检查自杀时,将该参数设置为False
)- 该函数返回移除(或需要移除)的石头数量。
下面显示了remove_stones()
功能的实现:
**#------------------------------------------------
# Remove specified stones surrounded by opponent
#------------------------------------------------
def remove_stones(color, remove=True):
# create a new array with 1 padding of 1 around the sides
# (for edge case)
temp_stones_values = np.full((21,21), color)
# copy the current stones_values into the padded array
temp_stones_values[1:-1, 1:-1] = stones_values** **#-----------------------------------------------
# Checking the liberties of the opponent stones
#-----------------------------------------------
# for each of opponent's stones, check to see if it has an
# liberty around it
# you are looking inside the border, so [1:-1,1:-1]
for x,y in zip(
np.where(temp_stones_values[1:-1,1:-1] == color)[0],
np.where(temp_stones_values[1:-1,1:-1] == color)[1]):** **x+=1 # need to add one because of the padding
y+=1 # need to add one because of the padding
# as long as you have a surrounding that is 0 (empty), the
# stone is alive (at least one liberty)
if temp_stones_values[x-1,y] == 0 or \
temp_stones_values[x+1,y] == 0 or \
temp_stones_values[x,y-1] == 0 or \
temp_stones_values[x,y+1] == 0:
temp_stones_values[x,y] = 8 # stone is alive** **#--------------------------------------
# Find groups of stones that are alive
#--------------------------------------
# if a stone is still alive, infect those of the same color
flipped=True** **while flipped:
flipped=False
# find all the opponent's stones
for x,y in zip(
np.where(temp_stones_values[1:-1,1:-1] == color)[0],
np.where(temp_stones_values[1:-1,1:-1] == color)[1]):** **x+=1 # need to add one because of the padding
y+=1 # need to add one because of the padding
if temp_stones_values[x-1,y] == 8 or \
temp_stones_values[x+1,y] == 8 or \
temp_stones_values[x,y-1] == 8 or \
temp_stones_values[x,y+1] == 8:
temp_stones_values[x,y] = 8 # alive
flipped = True
#----------------------------
# remove all the dead groups
#----------------------------
count = 0
for x in range(1,20):
for y in range(1,20):
if temp_stones_values[x,y] == color:
if remove:
stones[x-1,y-1].pop().remove()
stones[x-1,y-1] = None
stones_values[x-1,y-1] = 0
count += 1
plt.draw()** **# return the number of stones removed
return count**
您将调用on_click()
函数中的remove_stones()
函数:
# event handler to handle click on the board
def on_click(event):
global white
print(f'x: {event.xdata} y: {event.ydata}')
# get the points clicked on the board
if event.xdata == None or event.ydata == None:
return
x = int(round(event.xdata))
y = int(round(event.ydata))
#-----------------------------
# Draw the stone on the board
#-----------------------------
# make sure the area clicked is within the board
if 0<=x<=18 and 0<=y<=18:
# if the user left clicked to add a new stone on the board
if event.button == 1 and stones[x,y] == None:
stones[x,y] = draw_stone(x,y,'w' if white else 'k')
stones_values[x,y] = 1 if white else -1
white = not white # switch the color
# when user right-clicked to remove a stone on the board
elif event.button == 3 and stones[x,y] != None:
stones[x,y].pop().remove()
stones[x,y] = None
stones_values[x,y] = 0
else:
return
plt.draw()
# print(stones)
# print(stones_values)
print(np.rot90(stones))
print(np.rot90(stones_values))
else:
return **#-----------------------------------
# Remove stones that are surrounded
#-----------------------------------
# color of the opponent
color = 1 if white else -1 # white is 1; black is -1
# remove stones of surrounded opponent
remove_stones(color)**
你现在可以运行这个程序,被包围的石头会被自动移除。试试吧!很好玩!
检查自杀事件
如前所述,自杀是指玩家将石头放在某个位置,导致自己的石头被移走。在我们的实现中,每当玩家自杀,我们将播放一个音频通知。在 Python 中,可以使用 beepy 模块来播放音频通知。
您首先需要在终端中安装 beepy :
$ **pip install beepy**
然后导入它:
import matplotlib.pyplot as plt
import numpy as np
**import beepy**
现在,让我们添加以下粗体陈述,以检查自杀情况(在评论中解释):
# event handler to handle click on the board
def on_click(event):
global white
print(f'x: {event.xdata} y: {event.ydata}')
# get the points clicked on the board
if event.xdata == None or event.ydata == None:
return
x = int(round(event.xdata))
y = int(round(event.ydata))
#----------------------------
# Draw the stone on the board
#----------------------------
# make sure the area clicked is within the board
if 0<=x<=18 and 0<=y<=18:
# if the user left clicked to add a new stone on the board
if event.button == 1 and stones[x,y] == None:
stones[x,y] = draw_stone(x,y,'w' if white else 'k')
stones_values[x,y] = 1 if white else -1
white = not white # switch the color
# when user right-clicked to remove a stone on the board
elif event.button == 3 and stones[x,y] != None:
stones[x,y].pop().remove()
stones[x,y] = None
stones_values[x,y] = 0
else:
return
plt.draw()
# print(stones)
# print(stones_values)
print(np.rot90(stones))
print(np.rot90(stones_values))
else:
return #----------------------------------
# Remove stones that are surrounded
#----------------------------------
# color of the opponent
color = 1 if white else -1 # white is 1; black is -1 # remove stones of surrounded opponent **#---Comment out the following statement---
# remove_stones(color)** **if remove_stones(color) == 0: # if no stones are removed, check
# for suicide
white = not white
color = 1 if white else -1
if remove_stones(color, remove=False) == 0: # there is no
# suicide
white = not white
else: # there is suicide
# UNDO - remove the stone that was placed on the board
stones[x,y].pop().remove()
stones[x,y] = None
stones_values[x,y] = 0
plt.draw()
beepy.beep(sound="ping")**
特别是,只有当用户放置石头时没有石头被移走,您才检查自杀情况。如果有自杀案例,石头会从棋盘上移走,并播放“ping”通知。
摘要
到目前为止,我们已经实现了围棋规则。你现在准备好和你的朋友通过轮流在棋盘上放置石头来一决胜负。在下一篇文章中,我将展示如何确定游戏的获胜者,以及如何通过网络玩游戏。在那之前,玩得开心!
完整的程序
下面是完整的程序( go.py ):
import matplotlib.pyplot as plt
import numpy as np
import beepydef draw_board():
# create a figure to draw the board
fig = plt.figure(figsize=[9,9]) # set the background color
fig.patch.set_facecolor((0.85,0.64,0.125)) ax = fig.add_subplot(111) # turn off the axes
ax.set_axis_off() return fig, axdef draw_grids(ax):
# draw the vertical lines
for x in range(19):
ax.plot([x, x], [0,18], 'k')
# draw the horizontal lines
for y in range(19):
ax.plot([0, 18], [y,y], 'k') ax.set_position([0,0.02,1,1])
def draw_star_points(ax, x, y):
ax.plot(x,y,'o',markersize=8,
markeredgecolor=(0,0,0),
markerfacecolor='k',
markeredgewidth=1)# draw the stone on the board
def draw_stone(x,y,color):
stone = ax.plot(x,y,'o',markersize=28,
markeredgecolor=(0,0,0),
markerfacecolor=color,
markeredgewidth=1)
return stone#---------------------------------------------------------
# calculate the territories surrounded by black and white
#---------------------------------------------------------
def calculate_score():
for color in [-1,1]: # check black color first
# create a new array with 1 padding of 1 around the sides
# (for edge case)
temp_stones_values = np.full((21,21), color) # copy the current stones_values into the padded array
temp_stones_values[1:-1, 1:-1] = stones_values # look for empty spaces
for x,y in zip(
np.where(temp_stones_values[1:-1,1:-1] == 0)[0],
np.where(temp_stones_values[1:-1,1:-1] == 0)[1]):
x+=1 # need to add one because of the padding
y+=1 # need to add one because of the padding
# as long as you have a surrounding that is the opposite
# color, the space is not occupied
if temp_stones_values[x-1,y] == -color or \
temp_stones_values[x+1,y] == -color or \
temp_stones_values[x,y-1] == -color or \
temp_stones_values[x,y+1] == -color:
temp_stones_values[x,y] = 8 # space is not
# occupied flipped=True
while flipped:
flipped=False
for x,y in zip(
np.where(temp_stones_values[1:-1,1:-1] == 0)[0],
np.where(temp_stones_values[1:-1,1:-1] == 0)[1]):
x+=1 # need to add one because of the padding
y+=1 # need to add one because of the padding
if temp_stones_values[x-1,y] == 8 or \
temp_stones_values[x+1,y] == 8 or \
temp_stones_values[x,y-1] == 8 or \
temp_stones_values[x,y+1] == 8:
temp_stones_values[x,y] = 8 # not occupied
flipped = True print(f'Space occupied by {color}:',
len(temp_stones_values[temp_stones_values==0]))
# count the total number of stones for this color
print('Total stones: ',
len(stones_values[stones_values==color]))
print('------')#--------------------------------------
# Remove stones surrounded by opponent
#--------------------------------------
def remove_stones(color, remove=True):
# create a new array with 1 padding of 1 around the sides
# (for edge case)
temp_stones_values = np.full((21,21), color)
# copy the current stones_values into the padded array
temp_stones_values[1:-1, 1:-1] = stones_values #-----------------------------------------------
# Checking the liberties of the opponent stones
#-----------------------------------------------
# for each of opponent's stones, check to see if it has an
# liberty around it
# you are looking inside the border, so [1:-1,1:-1]
for x,y in zip(
np.where(temp_stones_values[1:-1,1:-1] == color)[0],
np.where(temp_stones_values[1:-1,1:-1] == color)[1]):
x+=1 # need to add one because of the padding
y+=1 # need to add one because of the padding
# as long as you have a surrounding that is 0 (empty), the
# stone is alive (at least one liberty)
if temp_stones_values[x-1,y] == 0 or \
temp_stones_values[x+1,y] == 0 or \
temp_stones_values[x,y-1] == 0 or \
temp_stones_values[x,y+1] == 0:
temp_stones_values[x,y] = 8 # stone is alive #--------------------------------------
# Find groups of stones that are alive
#--------------------------------------
# if a stone is still alive, infect those of the same color
flipped=True while flipped:
flipped=False
# find all the opponent's stones
for x,y in zip(
np.where(temp_stones_values[1:-1,1:-1] == color)[0],
np.where(temp_stones_values[1:-1,1:-1] == color)[1]):
x+=1 # need to add one because of the padding
y+=1 # need to add one because of the padding
if temp_stones_values[x-1,y] == 8 or \
temp_stones_values[x+1,y] == 8 or \
temp_stones_values[x,y-1] == 8 or \
temp_stones_values[x,y+1] == 8:
temp_stones_values[x,y] = 8 # alive
flipped = True
#----------------------------
# remove all the dead groups
#----------------------------
count = 0
for x in range(1,20):
for y in range(1,20):
if temp_stones_values[x,y] == color:
if remove:
stones[x-1,y-1].pop().remove()
stones[x-1,y-1] = None
stones_values[x-1,y-1] = 0
count += 1
plt.draw() # return the number of stones removed
return count# event handler to handle click on the board
def on_click(event):
global white
print(f'x: {event.xdata} y: {event.ydata}')
# get the points clicked on the board
if event.xdata == None or event.ydata == None:
return
x = int(round(event.xdata))
y = int(round(event.ydata))
#-----------------------------
# Draw the stone on the board
#-----------------------------
# make sure the area clicked is within the board
if 0<=x<=18 and 0<=y<=18:
# if the user left clicked to add a new stone on the board
if event.button == 1 and stones[x,y] == None:
stones[x,y] = draw_stone(x,y,'w' if white else 'k')
stones_values[x,y] = 1 if white else -1
white = not white # switch the color
# when user right-clicked to remove a stone on the board
elif event.button == 3 and stones[x,y] != None:
stones[x,y].pop().remove() # remove the plot
stones[x,y] = None
stones_values[x,y] = 0
else:
return
plt.draw() # update the figure
# print(stones)
# print(stones_values)
print(np.rot90(stones))
print(np.rot90(stones_values))
else:
return #-----------------------------------
# Remove stones that are surrounded
#-----------------------------------
# color of the opponent
color = 1 if white else -1 # white is 1; black is -1
# remove stones of surrounded opponent
if remove_stones(color) == 0: # if no stones removed, check for
# suicide
white = not white
color = 1 if white else -1
if remove_stones(color, remove=False) == 0: # there is no
# suicide
white = not white
else: # there is suicide
# remove the stone that was placed on the board
stones[x,y].pop().remove()
stones[x,y] = None
stones_values[x,y] = 0
plt.draw()
beepy.beep(sound="ping")#-----main-----
fig, ax = draw_board()
draw_grids(ax)# draw the 9 star points on the board
draw_star_points(ax, 3,3)
draw_star_points(ax, 3,9)
draw_star_points(ax, 3,15)
draw_star_points(ax, 9,3)
draw_star_points(ax, 9,9)
draw_star_points(ax, 9,15)
draw_star_points(ax, 15,3)
draw_star_points(ax, 15,9)
draw_star_points(ax, 15,15)#-------
# 0 for empty
# 1 for white
# -1 for black
#-------# color of the stone to start
white = True# stones is for storing the plot (containing the stone)
stones = np.full((19,19), None)# stones_values stores the value (color) of each point
stones_values = np.full((19,19),0)fig.canvas.mpl_connect('button_press_event', on_click)plt.show()
https://weimenglee.medium.com/membership
开发值得信赖的新冠肺炎计算机视觉系统
避开安全转向监视的诱饵和开关
当我们努力适应后新冠肺炎世界时,一个普遍重复出现的主题跨越了政府、法定机构、私营部门、国际组织,它们一直在利用人工智能等技术来应对疫情。
但人工智能的快速崛起产生了一种双速动态,即使研究人员和专业人士涌入该领域,公众也认为技术正在超越监管:
- 斯坦福以人为中心的人工智能中心报告称,与 2012 年相比,世界上最大的人工智能会议 NeurIPS 的出席人数增加了 800%以上;
- 与此同时,爱德曼信任晴雨表中 61%的受访者担心“政府对新兴技术的了解不足以对其进行有效监管。”
公众担心的正是人工智能目前超越监管监督的差距。
面部识别——银弹还是危险武器?
这场辩论的一个关键部分是面部识别系统:通过将个人面部图像与记录数据库进行比较来识别个人的软件。如果阅读上面的定义让你充满了一种模糊的不安,你并不孤单。人工智能的广泛部署是一把双刃剑,因为这些解决方案的有效性取决于大规模收集个人数据。从那里可以很快进入国家监控系统。
从外行人的角度来看,我们害怕诱饵和开关:促进我们安全的技术被重新用于跟踪我们,消除了隐私和匿名的残余。
美国的面部识别行业目前价值 50 亿美元,并以极快的速度增长,预计到 2025 年将翻一番。
虽然最先进的面部识别算法达到了超过 99.9%的准确率,但问题仍然存在于性能之外。这项技术的快速发展在活动家、政客、学者甚至警察中引发了广泛的争论,主要关注点是隐私、相称性以及缺乏同意的机会。
此外,在卓越的准确性指标之下,可能隐藏着更令人不安的算法偏差。麻省理工学院媒体实验室的性别阴影项目揭示了人工智能模型应用于某些人口统计数据时的结果中令人不安的偏见。这些模型在男性身上比在女性身上更准确,在浅色皮肤的测试对象身上比深色皮肤的测试对象身上更准确。当查看交叉结果时(例如,深色皮肤的女性),这些结果甚至更具罪证性,其中模型的精确度在 20.8%到 34.7%之间比低。
这意味着,如果你是面部识别系统的买家或用户,标题的准确性可能会产生误导。
这意味着,如果你是面部识别系统的购买者或用户,标题准确性可能会产生误导——可能自称“99.9%准确性”的系统实际上是“在测试条件下测试样本的 99.9%准确性”的简写。这种真实和测试条件之间的巨大差距被斯坦福大学人工智能中心称为“ 域转移 ”,目前正在努力传播对这些风险的认识,并围绕这项新兴技术的运作方式设置护栏。
https://www.media.mit.edu/projects/gender-shades/overview/
责任人工智能的崛起
进入负责任、值得信赖的人工智能新兴领域。如果你还没有遇到这个术语,你可能会遇到——这个曾经是未来学家和研究人员的小众兴趣正在迅速达到临界点。2020 年 8 月,一场错误的面部识别匹配导致一名密歇根州男子因莫须有的罪名被错误逮捕,这可能是此类案件中的首例。
技术研究和咨询公司 Gartner 宣布,负责任的人工智能和人工智能治理已经成为人工智能在工业规模上的优先事项,在政策方面,全球发布了不少于 84 份关于开发人工智能系统的道德准则的文件,其中近 90%是在过去 4 年中发布的。
负责任的人工智能和人工智能治理也成为工业规模人工智能的优先事项— Gartner,2020 年 9 月
但负责任的人工智能涵盖了很多内容,主题包括从算法偏见和可解释的人工智能到 deepfakes 等热门领域,以及围绕人工智能责任、人类尊严和工作置换的更广泛的社会挑战。
回到一个更具体的问题——我们能够获得人工智能系统的好处,保护我们免受新冠肺炎病毒的攻击,同时保护我们的个人隐私吗?
可信计算机视觉系统的双模式视图
归根结底是适当性——用于正确目的的正确工具。
面部识别和生物识别技术可以在某些特定的环境下成为一股向善的力量。美国的支持者经常认为,生物特征监控技术应该保留给最大的风险,例如帮助处理暴力犯罪、T2 恐怖威胁和 T4 人口贩卖。
然而,出于生产力、洞察力和安全等原因,人工智能在公共空间的使用可以也应该采取不同的形式。将计算机视觉人工智能系统等同于生物识别系统是一种错误的等同。现有的技术、方法和设计模式可以部署人工智能系统,这些系统可以捕捉详细的行为,而不会损害个人的身份或隐私。
将计算机视觉人工智能系统等同于生物识别系统是一种错误的等同。
这是我希望将成为最佳实践的区别,因为围绕人工智能问题的对话开始凝聚成具体的实施指南——我们限制个人身份信息的猖獗收集(PII)和对少数有必要的用例使用生物识别技术,并优先考虑其余的数据隐私。
照片由 Jesús Rocha 在 Unsplash 上拍摄
可以实施的保护和捍卫隐私的措施包括一系列方法,从识别和模糊面部,到只处理人的 T10 博客式轮廓 T11,以及一些架构步骤,如确保任何流式数据不包含可能危及个人身份的敏感信息。
在 Lauretta.io 我们实现了上述内容的高级版本,能够理解详细的活动,但永远不会识别个人。我们的解决方案从一开始就被设计为隐私第一,我们相信在不损害个人隐私的情况下获得人工智能的好处。
我们鼓励受过教育的人下次在公共场合看到视频人工智能摄像机时提出三个简单的问题,包括:
- 隐私—我的隐私受到保护了吗?系统是否存储了我的个人数据?
- 目的——人工智能系统的用途是什么?
- 偏差——系统是否在真实环境中进行了偏差测试?
基于视频的人工智能与新冠肺炎战斗
在已经非常复杂的人工智能系统之上,以上所有这些看起来似乎都是很大的努力。那么,是什么让这些负责任的人工智能措施值得努力呢?为什么这在现在特别重要?
一个强大的驱动力是有充分根据的希望,即人工智能可以成为我们在全球范围内对抗新冠肺炎的有力武器。从技术团队的角度来看,世界卫生组织(世卫组织)提出的许多关键策略可以被重新定义为这个精确的人工智能工具包所支持的问题。从他们的战略文件中取样:
- 通过物理距离抑制社区传播目前正由安全距离大使实施。但是把人置于危险的境地是一个冒险的提议,没有人能在任何时候出现在任何地方。但是通过摄像头网络工作的人工智能系统可以,当与隐私优先原则相结合时,这种计算机视觉系统可以成为削弱疫情的有力武器。
- 通过呼吸礼仪(主要通过戴口罩)的预防同样包括大规模监控公共场所。这也是计算机视觉系统容易完成的任务,并且在不需要识别个人的情况下也能有效完成。违规可以简单地触发对值班人员的通知。
迈向更安全的人工智能驱动的未来
随着人工智能和面部识别都处于巨大的增长轨道上,理清这两者很重要。人工智能系统是适当使用技术来解决的一个新领域,随着该领域的指导方针在全球范围内仍处于萌芽状态,我们不必成为正在展开的人工智能故事的被动观众——我们所有人都有责任塑造它。
本文最初发表于 THINK Digest 第 7 期:未雨绸缪我们的复苏 为“隐私第一视频人工智能如何帮助管理流行病:新冠肺炎案例研究”
以上显示的所有图像仅用于非商业说明目的。本文是以个人身份撰写的,不代表我所工作或隶属的组织的观点。
集成 fast.ai 数据块 API 和 Label Studio 的基准数据集的开发
创建分段蒙版的顺序图(图片由作者提供)
我很高兴使用 Label Studio ,因为它在 MLOps 社区引起了很大反响,并且在他们的营销网页上有一些令人印象深刻的视觉效果。我们正在处理的用例是为少量图像生成详细的分段掩模,这些图像可以用作基准数据集,因为我们训练了一个模型来识别图片帧的“画布”部分。在另一篇博客文章中,我们将讨论使用合成数据方法的实际训练数据。基准数据集将帮助我们对用合成数据创建的模型进行“野外”评估。Label Studio 使贴标机能够描述图像的区域。该演示展示了如何将标签从 Label Studio 服务器导出到 FastAI 和 PyTorch 可以识别的分段数据加载器。
这项工作是在一个名为 wallshots.co的项目下进行的,该项目正在为数字艺术收藏家开发直播工具。这个演示的创作者是亚伦·索林格和威尔·昆兹
目标
目标是减少交付时间并提高分段掩码创建和标签维护操作的准确性,例如添加新类别、重新标记区域、微调标签。这将是评估标签工作室作为一个贴标工具包,将能够提高贴标速度和准确性。我在寻找以下特征:
- 经过深思熟虑的依赖关系,适合我的架构
- 快速设置
- 易于维护
- 用于区域标记的良好 GUI
- 可配置标签
- 针对分段掩码的产品
- 与 Python 的互操作性(很好)
- 可与 PyTorch 生态系统工具互操作(很好)
演示
装置
docker run -it -p 8080:8080 -v `pwd`:/label-studio/data heartexlabs/label-studio:latest
对于这个演示,我使用了 Docker 安装。让服务器运行起来很容易,但我熟悉 Docker 部署。总之,从安装到初始配置我的建模环境只花了不到 30 分钟,这令人印象深刻。
标记
贴标签的过程很直观。在实现 Label Studio 之前,我使用了 draw.io 和 Mac 的照片查看器的组合。这种设置允许我看到图像点的像素索引,但这个过程一点也不愉快。这非常乏味而且容易出错。围绕这种方法扩大数据标注操作是不可思议的。这就是为什么我需要 Label Studio 这样的标签工具。
Label Studio GUI 使我有可能从 draw.io+Mac 照片查看器过程中大大改进。我最喜欢的 Label Studio 功能是:
- 配置的设置和编辑很容易。我添加了新的标记类别,然后返回到新的类别,改变我以前用一种方式标记的区域。配置似乎很少,我很惊讶。
- 它记录了交付时间、标签标识归属、跟踪的项目状态(例如,已完成、未完成等…)!
- 它支持数据接收和输出到云存储桶。
我遇到的一些问题:
- 不清楚如何有效地标记非矩形区域
- 界面缩放、拖动功能虽然实用,但很笨重。我发现自己分两步贴标签。我将首先标记近似的顶点,然后继续微调拖动近似的顶点到适当的位置。奏效了。
- 重叠区域导致了一些问题,因为不可能在现有标记区域的顶部添加/创建点。但是,可以将现有的点拖动到现有的标记区域的顶部。最终我们找到了解决方案,但这肯定是未来需要改进的地方
- 目前 Label Studio 只提供多边形的顶点,用户需要自己绘制区域。在最好的情况下,这需要贴标机和数据科学家之间紧密的沟通。这包括所有标准以及贴标活动中出现的任何边缘情况的沟通。如果标记区域是非凸集,问题会变得更加复杂。在这种情况下,重建区域需要对顶点列表进行排序。比提供订购积分的能力更好的方法是只走“最后一英里”并交付区域。这减少了数据科学家需要深入研究标记活动细节的原因,从而减少了交付训练模型的延迟。
使用 Label Studio 创建标签(视频由作者提供)
将标签放入建模运行时
我了解到从服务器获取最新最好的标签的最好方法是使用 LabelStudio API 。这是令人困惑的,因为有一个从标记活动自动生成的文件没有不断更新。该文件存储在export/
目录中。推荐的解决方案是使用 Label Studio REST API,其中需要从我的训练运行时向标签服务器发出 HTTP 请求,在那里需要数据。这需要我调整我的环境,因为我的训练运行时和我的标签服务器都运行在 Docker 容器中,不一定相互通信。我通过向 training docker 容器添加-net=host
功能解决了这个问题。要举例说明,见问号(?)如下:
我的开发服务器架构的相关部分(图片由作者提供)
我们使用 Label Studio API ,它是作为 REST api 实现的。可以在运行 Label Studio 服务器的localhost:8092
上访问它。下面是用于从 Label Studio 服务器获取标签的代码。Label Studio API 会自动缓存结果,这是不可取的。注意:我在 Label Studio GUI 的帐户设置页面中找到了这个令牌。
# Docs: [https://api.labelstud.io/](https://api.labelstud.io/)
ls_export = "[http://localhost:8902/api/projects/{id}/export?exportType=JSON](http://localhost:8902/api/projects/{id}/export?exportType=JSON)"
proj_id = "1"# got the token from account settings in the gui
# [http://localhost:8902/user/account](http://localhost:8902/user/account)
token = "***"bm_data = requests.get(
ls_export.format(
id=proj_id),
headers={
'Authorization': 'Token {}'.format(token),
'Cache-Control': 'no-cache'
}
).content
bm_data = json.loads(bm_data)
PyTorch 分段数据加载器的预处理
与往常一样,将数据加载到数据加载器的预处理是整个过程中最困难和最耗时的部分。(这实际上是我想写这篇文章,分享我的代码的原因)
使用 numpy 将顶点转换为多边形:
通过使用numpy.linspace
可以画出垂直线,然后在蒙版的每一行循环并填充。这是“从头开始”的解决方案。问题是,它遭遇了一堆边缘案例。这是我通常不喜欢维护的代码,所以我在开源社区寻找已经解决了这个问题的人。以下是可行的解决方案:
import requests
import json
from pathlib import Pathclass NonConvexityDetected(Exception):
passdef label_path_to_docker_path(label_path:str, rawdatadir:Path):
"""
Converts the path used in Label Studio server to local path.
Uses: instance.data.image from bm_data
"""
return Path(label_path.replace('/data', str(rawdatadir)))def key_points_to_pixels(key_points, width, height):
"""Converts proportion space to pixel indices."""
out = np.floor(np.matmul(
key_points,
np.array([[width, 1],[1, height]])
)).astype('int')
bounds_checker = lambda x,x_max: x_max if x>x_max else x
bounds_checker = np.vectorize(bounds_checker)
out[:,1] = bounds_checker(out[:,1],height-1)
out[:,0] = bounds_checker(out[:,0],width-1)
return outdef apply_polygon_horizontal_bounds(maskbase, label_data, label_num):
"""Labels the bounds in each row, there will be 0, 1, or 2 bounds."""
width, height = label_data['original_width'], label_data['original_height']
closed_poly = label_data['value']['points']
closed_poly.append(closed_poly[0])
props = np.array(closed_poly)
out = key_points_to_pixels(
key_points=props*0.01,width=width, height=height,
)
# draw lines
last = out[0]
for i, pair in enumerate(out):
if i == 0: continue
start_y = last[1]
end_y = pair[1]
# for each row, label the bounds of y
min_pts = 3 if not start_y == end_y else 2
label_pts = np.floor(
np.linspace(
start=last,
stop=pair,
num=max(min_pts, abs(end_y-start_y)+1)
)
).astype(int)
for pt in label_pts:
maskbase[int(pt[1]),int(pt[0])] = label_num
last = pair
def fill_rowwise(maskbase, fill_label):
"""Looks for bounds drawn in a previous step and fills between them."""
for i in range(len(maskbase)):
xs = np.where(maskbase[i] == fill_label)[0]
if len(xs) == 0:
# print('Region not present', xs)
continue
if len(xs) == 1:
print(
'Could be a local max/min in row={}, found {} in {}'
.format(
i, len(xs), xs
)
)
# it's already labeled
continue
bounds = [[i,xs[0]],[i,xs[1]]]
maskbase[i, bounds[0][1]:bounds[1][1]] = fill_label
def delete_duplicates(l):
out = []
for e in l:
if e not in out:
out.append(e)
return out
def prep_mask_for_combining(mask):
"""Prepares elements for combining."""
mask = mask.astype(str)
mask = np.where(mask == '0', '', mask)
return maskdef combine_masks(masks):
"""Combines a set of masks."""
apply_multi_label_rules = np.vectorize(
lambda x: multi_label_rules[
''.join(
sorted(
''.join(delete_duplicates(list(x)))
)
)
]
)
outmask = prep_mask_for_combining(masks[0])
for mask_i in masks[1:]:
outmask = np.core.defchararray.add(
outmask,
prep_mask_for_combining(mask_i)
)
return apply_multi_label_rules(outmask)def compute_segmask_from_labelstudio(instance, rawdatadir, labels_map, multi_label_rules):
"""Processes the labeled region export from LabelStudio into a segmentation mask."""
t = instance
raw_fp = label_path_to_docker_path(
label_path=t['data']['image'],
rawdatadir=rawdatadir
)
baseimg = MaskedImg()
baseimg.load_from_file(fn=raw_fp)
imgname = Path(raw_fp).name
maskbase = np.zeros(shape=baseimg.img.shape[:2], dtype=int)
i = 0
masks = []
for label_data in t['annotations'][0]['result']:
mask_i = maskbase.copy()
print('######', i)
label = label_data['value']['polygonlabels'][0]
label_num = labels_map[label]
apply_polygon_horizontal_bounds(
maskbase=mask_i,
label_data=label_data,
label_num=label_num
)
fig = plt.figure(figsize=(9,9))
plt.title('Drawn row-wise bounds')
plt.imshow(baseimg.img)
plt.imshow(mask_i,alpha=0.25)
plt.show()
fill_rowwise(
maskbase=mask_i,
fill_label=label_num
)
fig = plt.figure(figsize=(9,9))
plt.title('Filled between bounds')
plt.imshow(baseimg.img)
plt.imshow(mask_i,alpha=0.25)
plt.show() masks.append(mask_i) print('\n\n#########')
final_mask = combine_masks(masks)
fig = plt.figure(figsize=(9,9))
plt.title('Final Mask')
plt.imshow(baseimg.img)
plt.imshow(final_mask, alpha=0.25)
plt.show()
return imgname, baseimg, final_mask
对我来说,具体实现是这样定义的:
rawdatadir = Path('/ws/data/wallshots-framefinder/benchmark_data/1/media')# A place to define ones own labels corresponding to named regions in LabelStudio
labels_map = {
'Background': 0,
'Canvas': 1,
'Obstruction': 2,
'PartialCanvas': 3
}# TODO: Automate this from labels_map
# This object defines new labels based on the overlapping regions.
# E.g. '12' is when region 1 and 2 are both present in a pixel.
multi_label_rules = {
'': 0,
'1': 1,
'2': 2,
'3': 3,
'12': 4,
'13': 5,
'23': 6
}saveto = Path('benchmark')
saveto.mkdir(exist_ok=True, parents=True)
(saveto/'scenes').mkdir(exist_ok=True, parents=True)
(saveto/'masks').mkdir(exist_ok=True, parents=True)import imageiofor instance in bm_data:
imgname, img, mask = compute_segmask_from_labelstudio(
instance=instance,
rawdatadir=rawdatadir,
labels_map=labels_map,
multi_label_rules=multi_label_rules
)
basename = imgname.split('.')[0]
fn = '{}.jpg'.format(basename)
img.save(saveto=saveto/'scenes'/fn)
fn = '{}.tif'.format(basename)
im = Image.fromarray(
mask.astype(np.uint8)
)
im.save(saveto/'masks'/fn)
这将打印出您的图像,并显示叠加在其上的分段蒙版。为了避免这种情况,您可以注释掉plt
行。对于每个带标签的区域,这段代码将为垂直线绘制一个图表,为填充区域绘制另一个图表。然后,在它显示单独绘制的所有单个区域后,它会将它们全部合并到最终的分割蒙版中(加载到模型训练中的蒙版)。下面的视频展示了 Label Studio 如何与培训运行时配合使用,以便随着新标签的添加或微调而进行更新。
展示标签流程如何与培训笔记本配合使用。(作者视频)
以下示例依次展示了遮罩创建算法的工作原理:
显示定义将被填充的区域的垂直线(图片由作者提供)
显示填充区域(作者提供的图像)
显示了最终的遮罩,在这种情况下只有一个区域。(图片由作者提供)
此设置适用于同一基础图像中的多个标记区域,即使它们是重叠的。请参见下面的示例,其中包含非方形矩形和重叠区域。重叠区域很难看到,但图像中阻挡图片框的植物被单独标记为“障碍物”。
具有多个区域的分段遮罩的图示,其中一些区域是重叠的(图片由作者提供)
为基准集创建 FastAI/PyTorch 数据加载器
来自 Label Studio 的数据以点列表的形式出现,这些点是我们标记区域的顶点或角。在我们的例子中,多边形被标记为几个类别中的一个。比如“帆布”、“梗阻”、“PartialCanvas”。我们读入区域顶点数据,获得原始图像,并将其转换为“分割蒙版”,分割数据加载器可以读取该蒙版。将我们的基准数据集作为数据加载器进行加载的好处是,我们可以获得数据验证,以及集成到与数据加载器的交互中的日志记录功能。最终,野生图像可能会取代我们创建的合成训练数据,或者至少它会增加合成数据集。
def get_y_fn(x):
return str(x) \
.replace('scenes', 'masks') \
.replace('jpg', 'tif')size = 250
dls = SegmentationDataLoaders.from_label_func(
saveto,
bs=12,
fnames=[
name
for name in saveto.iterdir()
if not name.is_dir()
],
label_func=get_y_fn,
item_tfms=[Resize((size,size),)],
batch_tfms=[
Normalize.from_stats(*imagenet_stats)
]
)dls.show_batch()
从 FastAI 数据加载器中,我们可以看到与原始图像相结合的分割蒙版。这证明数据处理正确。
结论
Label Studio 为分割问题中的数据标注提供了强大的工具集。Label Studio 还支持其他类型的标注任务,但这里我们只关注分段遮罩功能。未来的工作将展示当我们引入主动学习循环时会发生什么。我们将把基准数据转移到云中,这样就可以与核心产品用户流集成。
设计测试来衡量 GPT-3 的基础科学知识
学生可以从 OpenAI 的最新语言模型中学习并将其用作全天候顾问吗?学生可以用它在考试中作弊吗?GPT-3 能帮助基础研究吗?它不知道的时候会说吗?
介绍
生成式预训练转换器(GPT)是在大规模文本语料库上训练的深度学习自回归语言模型,在给定输入提示的情况下,合成旨在作为人类书写的文本传递的输出。特别是 GPT-3 是由旧金山人工智能研究实验室 OpenAI 创建的 GPT-n 系列的第三代语言模型。GPT-3 的完整版本包括 1750 亿个参数,比亚军多 10 倍,目前代表了基于预训练语言表示的自然语言处理(NLP)系统的艺术水平。
GPT-3 在一个巨大而多样的文本数据集语料库上接受训练,这些数据集主要来自于公共抓取项目(它“解析”来自网络的内容),加上来自数字化书籍、维基百科文章和网络文本 2 的大量贡献。当然,如此庞大的训练集的生成只需要很少的人工监督,不需要手工标注数据——这可能会带来一些问题,下面将对此进行讨论。
浅谈 GPT-3 的工作和性能
在其最广泛的使用形式中,GPT-3 作为通用接口工作,读入一些文本,然后写出可以以不同方式形成的新文本。例如,在主要选项中,输出可以看起来像一个问题的答案、给定主题的详细说明或输入文本的摘要。GPT-3 原则上应答复几乎任何种类的语言任务;有些人甚至证明它可以从自由文本形式的指示中产生计算机代码。如果它所产生的信息是正确的,那将是非常强大的。
GPT-3 生成的文本质量如此之高,以至于很难确定它是否是人类写的。一项对人类受试者的实验发现,他们能够区分人类和 GPT 3 书写的文本,比随机猜测好不了多少。我自己从 GPT-3 生成的文本片段中收集了一个合理大纲的故事,并做了最少的编辑,以确保我同意其中的主要信息:
如此高的输出质量当然是好的,但同时需要注意与 GPT-3 未来实际应用相关的一些非常重要的风险。例如,GPT-3 可能会泄露错误的信息,产生假新闻,产生不正确的指示,创建包含不适当内容的文本…所有这些即使应用它的程序员没有恶意。GPT 3 号从如此多的来源和如此少的人工干预中学到了如此多的东西,以至于我们无法确定它将如何对给定的提示做出准确的反应。用 OpenAI 自己的话说,潜在的问题包括包含错误信息的文本、垃圾邮件、网络钓鱼、辱骂、欺诈性学术写作、社会工程等。这些和其他可能无法预见的问题是如此严重,以至于 OpenAI 介绍 GPT-3 的论文警告了这些风险,并呼吁进行研究以理解和减轻这些风险。我特别感兴趣的是这些工具作为一种灵活的咨询来源来帮助科学研究和科学教育的潜力。因此,当被问及不同复杂程度的基础科学主题时,我想在本文和后续文章中说明 GPT-3 得出的事实的真实性。我想解决这类问题:学生们可以使用 GPT-3 作为一个全天候的老师来帮助他们完成学习和家庭作业吗?他们能用它在考试中作弊吗?研究者可以使用 GPT-3 作为灵活的咨询资源吗?GPT-3 能“意识到”(并告知)它对自己的输出没有把握吗?
我想在这个平台上的这篇文章和后续文章中说明,当被问及不同复杂程度的科学时,GPT-3 得出的事实的真实性。我想解决这类问题:学生们可以使用 GPT-3 作为一个全天候的老师来帮助他们完成学习和家庭作业吗?他们能用它在考试中作弊吗?研究者可以使用 GPT-3 作为灵活的咨询资源吗?GPT-3 能“意识到”(并告知)它对自己的输出没有把握吗?
间奏曲:为什么这么感兴趣?我对科技如何帮助我们更有效地学习和应用科学非常感兴趣,尤其是化学、物理和生物:
https://pub.towardsai.net/interactive-augmented-reality-web-apps-to-enable-immersive-experiences-for-science-education-dce51889473f https://medium.com/age-of-awareness/how-the-rich-geek-culture-sustained-hands-on-science-education-at-home-during-the-pandemic-9ad2aaa04a8b
测试 GPT-3
OpenAI 最近发布了一种用户友好的方式,通过一个简单的在线界面运行 GPT-3,无需编码,或通过 API 以编程方式运行。他们甚至建立了一个游乐场,在那里你可以获得一些免费积分来在线测试模型,然后可能为进一步的测试或使用付费。
通过开放这个工具,OpenAI 希望人们能帮助他们探索 GPT 3 的优势和局限性。这正是我在这篇文章和后续文章中要做的,特别关注 GPT3 对科学教育的实际效用,以及在协助科学研究方面的实际效用。我将向 GPT-3 提出关于物理、化学和生物等不同主题的不同复杂程度的问题,并评估其答案。我将在几篇文章中报告这些测试的结果。
建立一些初步实验
下面是一个例子,我问了近 50 个关于化学、物理和生物的基本问题,难度从小学后期到高中都有。为了运行这个测试,我用我的帐户登录了 OpenAI 的在线 GPT 3 游乐场,在示例下,我选择了 Q & A. 这是原则上更适合我的目标的格式;另外, Q & A 用的是“最聪明”的“达芬奇”型号。也是最贵的一个;在这次测试中,我使用了价值 2.28 美元的代币(从您注册时获得的免费测试资助中扣除)。
结果如何?这里是图片格式的结果,用于简单演示。您可以在此链接中找到它们进行全面检查。如你所见,我把问题组织成聊天,有点像考试:
我在基础生物学、化学和物理学上测试 GPT-3 的第一个结果的第一页。卢西亚诺·阿布利亚塔。
我的第一个测试结果的第 2 页 GPT-3 的基础生物,化学和物理。卢西亚诺·阿布利亚塔。
我在基础生物学、化学和物理学上测试 GPT-3 的第一个结果的第 3 页。卢西亚诺·阿布利亚塔。
我在基础生物、化学和物理上测试 GPT-3 的第一个结果的第 4 页。卢西亚诺·阿布利亚塔。
我在基础生物学、化学和物理学上测试 GPT-3 的第一个结果的第 5 页。卢西亚诺·阿布利亚塔。
我的第一个结论是什么?
- 该模型似乎非常了解那些可能写在互联网上某个地方的信息,事实上可能是多次——牛顿运动定律、花朵的一部分、细胞内结构、光合作用。
- 但是对于需要计算的东西却不多。例如,在化学中,它可以很好地谈论元素周期表中的不同种类的元素,但却无法获得分子式或分子量。一方面,我有点担心我的测试可能弄乱了下标,而下标对于正确描述分子式是必不可少的……这可能源于我对系统的不正确使用,或者可能代表了一种真正的限制。你碰巧知道吗?另一方面,我很失望 GPT-3 不能得到我要求的基本公式和摩尔重量,这些在互联网上随处可见。我没有要求一个不常见的分子,它必须“思考”或计算,而是要求化合物在化学教育网页上非常受欢迎。
- 如果你跟踪我与 GPT-3 的聊天,你会发现一些段落,让你认为它是一种“思考”(显然只是回忆其训练数据集中的类似情况)。但是在其他情况下,即使你告诉它它出错了,或者你试图纠正它的路线,这个模型还是开始出错,并且一直出错。
- 至少在这些测试中,GPT-3 从未说过它不知道;它实际上非常确信地陈述了它所有的答案,甚至是不正确的答案。不仅说服自己,而且对答案表现出信心;例如通过明确的“是”或“否”。我认为这种信心增加了该模型应用于辅助教育或工作的工具的固有风险。
从全球范围来看,我认为期望涉及一些思考而不仅仅是信息检索的问题的正确答案是不切实际的。然而,一些需要“思考”的问题确实得到了正确的答案。虽然这很可能是由于模型回忆起相似的学习文本序列,但我认为这仍然是值得注意的。一时间,它似乎在运用逻辑进行论证。
同样值得注意的是,该模型可以使用上下文来塑造其答案,产生(至少对我来说)在一个定义的主题上进行流畅对话的感觉——但随着我们转换区域而适应。
根据第一次测试的观察结果,我将设计实验来测量 GPT-3 在化学、生物和物理等更集中的领域的表现,也可能是其他学科,不同的难度水平。显然,我们可以预期这个模型对于“思考”问题非常有限,对于需要计算的问题可能毫无用处。但是基于我上面展示的初步测试,该模型可能对从化学和物理定律、描述生物学等中检索事实数据有用。以新的方式,更具互动性,更容易对着电脑摆姿势。
有趣的故事来了,敬请期待!
GPT-3 或它的进一步进化有一天会教会人类并帮助他们工作吗?由作家卢西亚诺·阿布利亚塔创作的人物。
进一步阅读
arxiv 上的 GPT-3 原始论文【T1:】https://arxiv.org/abs/2005.14165
https://github.com/openai/gpt-3/blob/master/model-card.mdGitHub:的车型卡
维基百科词条(我推荐整篇文章,尤其是关于申请/评论/批评的部分):https://en.m.wikipedia.org/wiki/GPT-3
OpenAI 的 GPT-3 API 官方页面:https://openai.com/api/
OpenAI 的博客词条:https://openai.com/blog/openai-api/
我是一个自然、科学、技术、编程、DIY 爱好者。生物技术专家和化学家,在潮湿的实验室和电脑前。我写我广泛兴趣范围内的所有东西——“luciano sphere”。查看我的 列表 了解更多故事。 成为中等会员 访问其所有故事和 订阅获取我的新故事 通过电子邮件 (我为其获得小额收入的平台的原始附属链接,无需向您支付特殊费用)。
各种类型的询盘, 联系我这里 。对于 小工作 (关于编程、数据分析、加密货币、biotech+bio info 项目评估、科学推广+传播、分子数据分析与设计、分子图形、摄影、moleculARweb 教程、科学教学与辅导等。)查看我的 服务页面这里 。
诊断糟糕的浓缩咖啡
咖啡数据科学
数据样本会告诉你什么
休斯顿,我们有麻烦了。我的提取率突然下降。我不知道为什么,但我需要调查。从神射到沉射(我说的沉,是指我的嘴),有一种相当绝望的感觉。我想我最好做一个快速的记录,以防其他人对我如何诊断问题感兴趣。
镜头设置
在我说发生了什么之前,我们先来谈谈那次枪击。我不拍普通镜头,也不用普通工具。我进行断奏击球、断奏夯实击球或断奏击球。这些都是分层的镜头。
此外,我在镜头的底部和中间使用了一个布过滤器,这改善了味道和提取。这也让我有更多的信息点进行故障分析。
除了其他变量之外,我还跟踪每一次拍摄的提取率和口味评分。
发射失败
我注意到提取率(EY)和味道下降,我开始修改不同的参数来改善 EY。我的大部分镜头都没用,无论我做什么都没用。
我开始给冰球底部拍照,拍了几张之后,一个清晰的图案出现了。这表明底部滤布过滤器出现故障。我对布过滤器没有年龄方面的问题,当我发现这一点时,过滤器已经使用了 50 次。我之前用了 100 次布过滤器,拍摄质量没有下降。
我甚至试过在沸水和异丙醇中清洗过滤器,但没有用。
左:将滤布过滤器放入篮中的常规方向。右图:以常规方式上下颠倒。
我让水以常规方式和倒置方式通过过滤器,结果差别很大。
常规取向
颠倒方向
查看数据
我回去查看了 Extraction Yield (EY),因为我想弄清楚哪里出了问题,因为它并不是突然出了问题。我很容易看出下降的趋势。
我仔细看了看照片,发现有一个镜头是关着的。EY 是 17.9%,但味道评分是前几个镜头的一半。这是多次烘烤的混合镜头。我喝四杯一种的烤肉,然后换成另一种,我积累的量足够一周一次混合一杯。这是一张有趣的百搭牌。
我调出了下面的图片,显示咖啡从一边流出的速度比另一边快得多。
这种模式似乎与我之前看到的有问题的模式一致
对于混合拍摄,我拍了滤镜的照片,但我没有拍滤镜下面的照片,这可能会混淆问题。
我在这个故事中看到了两个寓意:
- 不要使用干布或纸质过滤器;仅使用潮湿的过滤器。
- 数据在导致咖啡问题的根源方面非常有效
随着我对这个潜在问题的关注,我注意到过滤器上多一点水有很大的不同。我想知道这是否是为什么在冰球顶部喷水有助于流动的部分原因。
如果你愿意,可以在 Twitter 和 YouTube 上关注我,我会在那里发布不同机器上的浓缩咖啡视频和浓缩咖啡相关的东西。你也可以在 LinkedIn 上找到我。也可以关注我中。
我的进一步阅读:
对话式回应生成的大规模生成性预训练
研究论文摘要
图片由来源
对话系统或对话代理是一种旨在与人类对话的计算机系统。对话系统采用文本、语音、图形、触觉、手势和其他模式中的一种或多种在输入和输出通道上进行通信——T5 维基百科
几个月前,微软的研究人员发布了他们的大规模对话模型— DialoGPT ,该模型在对话系统设置中生成相关和一致的响应方面实现了最先进的性能。响应生成可以被视为文本生成的子问题,其思想是生成自然流畅的文本,该文本以输入提示序列为条件并与之相关。
在我们继续之前,让我们快速回顾一下 GPT-2 。所以,GPT-2 是一个大规模的基于转换器的语言模型,它是在 40GB 的互联网文本上训练出来的。这是一个由多个解码器转换单元组成的堆栈,具有一些先进的学习概念,如屏蔽自我注意力、多头、剩余连接、层规范化等,使其成为最好的文本生成器之一。GPT-2 试图优化的目标是在看到过去的单词后预测序列中的下一个单词。语言模型的一些化身的一些真实世界的部署就像智能手机上的自动完成特性,电子邮件系统中的自动排版,等等。如果你是第一次接触 GPT-2,我会鼓励你去看看这篇有趣的文章。
作者在本文中谈到了开放域响应生成系统当前面临的一些挑战,如风格不一致、平淡、未能捕获长期依赖等。他们认为,一个基于转换器的语言模型,如 GPT-2,使用多层掩蔽的自我关注层,已经被证明在生成流畅的文本方面表现非常好,可能会被用来解决其中的一些限制。
作为预防性预处理措施,他们在将 I/O 样本输入模型之前,对其采用了多种修剪规则。下面提到了 7 个预处理步骤-
- 移除了源或目标具有/是 URL 的实例。
- 删除了有有毒语言痕迹的实例。基于预先确定的单词列表来检测毒性。
- 删除了响应中不包含英语中 top- 50 最常用单词列表中的任何单词的情况。—这一步可能有助于确认句子是英语的。
- 删除了响应包含特殊标记的实例,如“[”或“]”测试标记语言。
- 删除了源+目标的长度为> 200 字的情况。
- 删除了目标包含至少 3 个单词的单词重复的情况。
- 删除了至少 90%三元模型出现超过 1000 次的实例。—帮助修剪乏味的回应。
从 2005 年到 2017 年,他们在 Reddit 评论线程的 1.47 亿次对话交流中训练了他们的模型。作为一种建模策略,作者将响应生成的任务转化为学习语言模型的任务。他们扩展了 GPT-2 来解决这个问题(因此命名为 DialoGPT) ,假设这种方法可以以更精细的粒度捕获会话流中 P(目标,源)的联合分布。
他们从 Reddit 讨论的顶层到叶层提取多个线程,每个线程充当一个包含多轮对话的训练实例。它们将一个会话中的所有对话连接成一个长文本 x1,x2,…,xN,eos,其中 N 是序列长度, eos 是文本结束标记。设 S=x1,…,xM 为源序列,T=xM+1,…,xN 为目标序列,那么我们希望最大化,
图片由来源
作者使用 Top-k 解码策略 (k-10)在任何给定的情况下对 16 个候选响应进行采样。他们还采用 MMI ( 互信息最大化 )策略对候选响应重新排序。MMI 也是预训练的 GPT-2 模型,其被训练为在给定 T(响应)的情况下预测 S(上下文)。选择产生最低反向模型损失的响应。这种选择有助于修剪平淡的响应,因为平淡的响应对于大多数查询来说是常见的,因此对于任何特定的查询来说应该产生低概率。
他们分别训练了 5 个、5 个和 3 个时期的小型(117 米)、中型(345 米)和大型(762 米)GPT-2 模型。在工程方面,他们压缩并将所有数据放入一个惰性加载数据库,以便在需要时加载数据。他们还将相似长度的对话分组到同一个批次中,以便更好地训练,并且还采用了异步数据处理来缩放训练。
下图显示了一些测试时间的例子
图片来源来源
下面的代码片段显示了试用预训练模型的示例代码
!pip install transformers==3.0.2 from transformers import AutoModelWithLMHead, AutoTokenizer
import torch tokenizer = AutoTokenizer.from_pretrained("microsoft/DialoGPT-medium") model = AutoModelWithLMHead.from_pretrained("microsoft/DialoGPT-medium") for step in range(3):
new_user_input_ids = tokenizer.encode(input(">> User:") + tokenizer.eos_token, return_tensors='pt')
bot_input_ids = torch.cat([gen_ids, new_user_input_ids], dim=-1) if step > 0 else new_user_input_ids
gen_ids = model.generate(bot_input_ids, max_length=200, pad_token_id=tokenizer.eos_token_id)
print("DialoGPT: {}".format(tokenizer.decode(gen_ids[:, bot_input_ids.shape[-1]:][0], skip_special_tokens=True)))
我们从导入预先训练好的标记器和模型开始。对于每个用户话语,我们使用预先训练的标记器对其进行编码,并在末尾添加标记结束。如果它是第一个话语,那么它将成为历史,否则先前生成的句子将作为历史。在生成历史向量时,还可以计算要考虑的先前话语的数量。我们传递历史和用户输入的连接版本来生成函数。我们进行解码,直到到达 eos ,并选择最后生成的片段作为模型的响应。随意复制粘贴上面的代码,并放入 print()来调试向量
我的想法
我认为这项研究通过训练更深层次的网络、更长时间的训练以及处理数据格式来解决更复杂的任务,推动了语言模型的能力。此外,看到一个语言模型能够做得如此之好是如此之酷(尽管这并不奇怪,嗯,GPT-3 😄)。除了开放域之外,我更感兴趣的是它在封闭域/面向任务的对话环境中的性能,以及它在健壮性、在生产中部署的响应质量方面的可行性。此外,我认为,除了预处理输入语料库以处理这些细微差别之外,还可以尝试将多任务损失用于处理平淡、重复、毒性等。此外,我没有发现作者在对话语建模时对任何一种说话者的身份进行编码。不过,我不确定这是否会有任何帮助。
你也可以查看我写的其他研究论文解释—
有兴趣了解更多细节吗?然后看这个—
对话视频讲解器
附:你可以阅读 这篇博客 在自己的语料库上对 DialoGPT 模型进行微调。
参考文献
1。原创论文——https://arxiv.org/abs/1911.005362。微软博客—https://www . Microsoft . com/en-us/research/project/large-scale-pre training-for-response-generation/
3 .github—https://github.com/microsoft/DialoGPT
我希望这本书值得你花时间去读。
谢谢!
Dials、Tune 和 Parsnip: Tidymodels 创建和调整模型参数的方式
管理参数值的三种简洁方法及预测企鹅体重的示例代码
伊恩·帕克在 Unsplash 上的照片
找到最佳值可以显著改善机器学习模型。我喜欢在调整过程中尝试各种值,并看到我的模型在预测方面越来越好。然而,必须弄清楚参数的名称、范围、值和其他方面可能会很麻烦。令人高兴的是,dials
库是tidymodels
的一部分,它的创建使得参数调整变得更加容易。
在这篇文章中,我介绍了用tidymodels
调整参数的三种方法,并提供了示例代码。到目前为止,我看到的大多数tidymodels
的例子都没有将dials
包括在建模中。虽然dials
不是必需的,但它有助于参数调整。
概观
我使用tidymodels
进行演示,其中rsample
用于拆分数据,parsnip
用于建模,workflow
用于绑定流程,tune
用于调优,dials
用于参数管理。如果你正在读这篇文章,我假设你对tidymodels
有所了解。总的来说,有三种使用参数的方法(据我所知):
- 在
parsnip
中应用默认值 - 使用
tune
创建一个调整网格并交叉验证parsnip
模型 - 用
dials
创建一个调整网格,供tune
交叉验证parsnip
模型
我总是在我的帖子中强调这一点,但我将在这里再次强调:这绝不是一个详尽的指南。数据科学和tidymodels
一样,在不断发展变化。我正在分享我到目前为止学到的东西,并希望帮助人们了解如何使用这些图书馆。如果您有任何问题或者您觉得这篇文章有帮助,请告诉我。
输入数据
我使用了 tidymodels 中包含的企鹅数据集。删除具有 NA 值的行并排除“孤岛”功能后,它有 344 行和 7 列。数据集相对较小,但混合了名义变量和数值变量。目标变量是body_mass_g
,它是数值型的,使得这成为一个回归任务。
**library**(tidymodels)
data("penguins")
penguins <- penguins %>%
select(-island) %>%
drop_na()
penguins %>% glimpse()
## Rows: 344
## Columns: 7
## $ species <fct> Adelie, Adelie, Adelie, Adelie, Adelie…
## $ island <fct> Torgersen, Torgersen, Torgersen…
## $ bill_length_mm <dbl> 39.1, 39.5, 40.3, 36.7, 39.3, 38.9, …
## $ bill_depth_mm <dbl> 18.7, 17.4, 18.0, 19.3, 20.6, 17.8,…
## $ flipper_length_mm <int> 181, 186, 195, NA, 193, 190, 181, 195,…
## $ body_mass_g <int> 3750, 3800, 3250, NA, 3450, 3650, 3625…
## $ sex <fct> male, female, female, NA, female, male…
因为目标是演示参数调整的各种方法,所以我不做探索性的数据分析、预处理、特征工程和其他通常在机器学习项目中执行的工作。在这里,我将数据分为训练和测试,并设置了 5 重交叉验证,重复 2 次。
set.seed(300)
split <- initial_split(penguins, prop = 0.75)
penguins_train <- training(split)
penguins_test <- testing(split)
folds_5 <- vfold_cv(penguins_train, v = 5, repeats = 2)
创建随机森林模型
在筛选模型选项之前,我通常需要考虑项目目标、数据类型和其他因素。在这里,我决定使用随机森林,因为它有几个参数,我喜欢它。
在 tidymodels 中,parsnip
为模型提供了一个整洁的、统一的接口。R 中的一个挑战是函数自变量和参数的不同。例如,randomForest
和ranger
都是构建随机森林模型的函数。但是,randomForest
有mtry
和ntree
,ranger
有mtry
和num.trees
。尽管ntree
和num.trees
指的是同一个概念,但它们的名称不同。我发现自己在编码时经常使用?randomForest
或?ranger
来计算参数名。创建parsnip
是为了提供问题的解决方案。
查找可用的引擎
首先,我从参考页面中找到随机森林模型的名称。令人惊讶的是,不是rf
而是rand_forest
。parsnip
为每个型号提供了几个引擎,我可以调用show_engines(“rand_forest”)
列出所有可用的引擎。
## # A tibble: 6 x 2
## engine mode
## <chr> <chr>
## 1 ranger classification
## 2 ranger regression
## 3 randomForest classification
## 4 randomForest regression
## 5 spark classification
## 6 spark regression
接下来,show_model_info("rand_forest")
显示模式、自变量、拟合模块和预测模型。下面的代码块被裁剪成只显示参数,输出清楚地翻译了跨rand_forest
和三个引擎的参数名。
## arguments:
## ranger:
## mtry --> mtry
## trees --> num.trees
## min_n --> min.node.size
## randomForest:
## mtry --> mtry
## trees --> ntree
## min_n --> nodesize
## spark:
## mtry --> feature_subset_strategy
## trees --> num_trees
## min_n --> min_instances_per_node
至此,我选择rand_forest
作为模型,randomForest
作为引擎,我知道模型有三个参数:mtry
、trees
和min_n
。在接下来的部分中,我将讨论三种不同的使用参数的方法,如概述中所述。
1.在 parsnip 中使用默认参数
rf_spec
是用parsnip
创建的随机森林模型规范。我没有为任何参数指定值,因此使用了默认值。像往常一样,我然后在训练数据上拟合模型。打印默认参数。
# Create random forest specification
rf_spec <-
rand_forest(mode = "regression") %>%
set_engine("randomForest")# Fit training data
model_default <-
rf_spec %>%
fit(body_mass_g~., data = penguins_train)
model_default
## parsnip model object
##
## Fit time: 133ms
##
## Call:
## randomForest(x = maybe_data_frame(x), y = y)
## Type of random forest: regression
## Number of trees: 500
## No. of variables tried at each split: 1
##
## Mean of squared residuals: 89926.71
## % Var explained: 85.51
使用默认参数的模型没有经过交叉验证。因为只有一组参数,所以交叉验证不会改进模型,但会使训练数据的性能更接近测试。一旦训练完成,我就对测试数据进行预测,并用yardstick
计算性能指标。
model_default %>%
predict(penguins_test) %>%
bind_cols(penguins_test) %>%
metrics(body_mass_g, .pred)
## # A tibble: 3 x 3
## .metric .estimator .estimate
## <chr> <chr> <dbl>
## 1 rmse standard 317\.
## 2 rsq standard 0.877
## 3 mae standard 242.
2.使用 tune 来调整 parsnip 模型
之前,rf_spec
使用默认参数值。为了调整参数,我需要添加参数。下面我提供了两种方法。
# Add parameters
rf_spec <-
rf_spec %>%
update(mtry = tune(), trees = tune())# Option 2: Start again
rf_spec_new <-
rand_forest(
mode = "regression",
mtry = tune(),
trees = tune()
) %>%
set_engine("randomForest")
tune()
是等待调整的值的占位符。所以现在,rf_spec
需要尝试不同的参数,找到最好的。这就是交叉验证的切入点。对于如此小的数据集,交叉验证的模型性能可能更能代表新数据。
听起来很不错,对吧?tidymodels
也让它变得非常简洁,因为workflow
捆绑了预处理、建模和后处理请求。也常用于用recipes
包含数据预处理,但我跳过了。相反,我指定了结果和预测,并添加了模型规格。
# Create a workflow
rf_workflow <-
workflow() %>%
add_variables(
outcomes = body_mass_g, predictors = everything()
) %>%
add_model(rf_spec)
手动提供值
现在,我们终于到了调音阶段
函数包括交叉验证和参数网格,应该是参数组合的数据框。expand.grid()
是一种生成所有输入组合的简单方法。我为mtry
和trees
分别列出了三个值,产生了九种组合(三乘以三)。
我不知道这是否是一个警钟,我应该更换我五年前的 MacBook,或者建模只是需要时间,调整总是需要大量的时间。完成后,我打电话给collect_metrics()
查看结果。
set.seed(300)
manual_tune <-
rf_workflow %>%
tune_grid(
resamples = folds_5,
grid = expand.grid(
mtry = c(1, 3, 5),
trees = c(500, 1000, 2000)
)
)collect_metrics(manual_tune)
## # A tibble: 18 x 8
## mtry trees .metric .estimator mean n std_err .config
## <dbl> <dbl> <chr> <chr> <dbl> <int> <dbl> <chr>
## 1 1 500 rmse standard 306\. 10 9.84 Preprocessor1_Model1
## 2 1 500 rsq standard 0.858 10 0.0146 Preprocessor1_Model1
## 3 3 500 rmse standard 301\. 10 14.3 Preprocessor1_Model2
## 4 3 500 rsq standard 0.854 10 0.0178 Preprocessor1_Model2
## 5 5 500 rmse standard 303\. 10 14.5 Preprocessor1_Model3
## 6 5 500 rsq standard 0.852 10 0.0180 Preprocessor1_Model3
## 7 1 1000 rmse standard 305\. 10 9.82 Preprocessor1_Model4
## 8 1 1000 rsq standard 0.859 10 0.0143 Preprocessor1_Model4
## 9 3 1000 rmse standard 300\. 10 14.5 Preprocessor1_Model5
## 10 3 1000 rsq standard 0.854 10 0.0180 Preprocessor1_Model5
## 11 5 1000 rmse standard 304\. 10 14.5 Preprocessor1_Model6
## 12 5 1000 rsq standard 0.851 10 0.0180 Preprocessor1_Model6
## 13 1 2000 rmse standard 306\. 10 10.1 Preprocessor1_Model7
## 14 1 2000 rsq standard 0.858 10 0.0144 Preprocessor1_Model7
## 15 3 2000 rmse standard 300\. 10 14.5 Preprocessor1_Model8
## 16 3 2000 rsq standard 0.854 10 0.0179 Preprocessor1_Model8
## 17 5 2000 rmse standard 304\. 10 14.7 Preprocessor1_Model9
## 18 5 2000 rsq standard 0.851 10 0.0181 Preprocessor1_Model9
要读的东西太多?我同意。让我们来关注一下使用show_best()
性能最好的一个。建议mtry = 3
和trees = 2000
为最佳参数。
show_best(manual_tune, n = 1)
## # A tibble: 1 x 8
## mtry trees .metric .estimator mean n std_err .config
## <dbl> <dbl> <chr> <chr> <dbl> <int> <dbl> <chr>
## 1 3 2000 rmse standard 300\. 10 14.5 Preprocessor1_Model8
manual_tune
不是一个模型,而是一个调整网格,所以我需要用最好的参数(mtry = 3
& trees = 2000
)来最终确定,并适合整个训练数据。测试的 RMSE 是 296,低于使用默认参数的模型的 317,表明调优产生了改进。
manual_final <-
finalize_workflow(rf_workflow, select_best(manual_tune)) %>%
fit(penguins_train)
manual_final %>%
predict(penguins_test) %>%
bind_cols(penguins_test) %>%
metrics(body_mass_g, .pred)
## # A tibble: 3 x 3
## .metric .estimator .estimate
## <chr> <chr> <dbl>
## 1 rmse standard 296\.
## 2 rsq standard 0.881
## 3 mae standard 238.
指定自动生成的网格大小
或者不使用expand.grid()
作为grid
参数来手动输入值,我可以用一个整数指定要尝试的候选数。这里,我要求模型尝试五组参数。由于collect_metrics()
同时返回 RMSE 和 R 平方,每组有两行输出,五组导致十行结果。
set.seed(300)
random_tune <-
rf_workflow %>%
tune_grid(
resamples = folds_5, grid = 5
)collect_metrics(random_tune)
## # A tibble: 10 x 8
## mtry trees .metric .estimator mean n std_err .config
## <int> <int> <chr> <chr> <dbl> <int> <dbl> <chr>
## 1 5 1879 rmse standard 304\. 10 14.5 Preprocessor1_Model1
## 2 5 1879 rsq standard 0.851 10 0.0181 Preprocessor1_Model1
## 3 2 799 rmse standard 298\. 10 13.6 Preprocessor1_Model2
## 4 2 799 rsq standard 0.857 10 0.0171 Preprocessor1_Model2
## 5 3 1263 rmse standard 300\. 10 14.5 Preprocessor1_Model3
## 6 3 1263 rsq standard 0.854 10 0.0179 Preprocessor1_Model3
## 7 2 812 rmse standard 297\. 10 13.7 Preprocessor1_Model4
## 8 2 812 rsq standard 0.858 10 0.0171 Preprocessor1_Model4
## 9 4 193 rmse standard 302\. 10 14.9 Preprocessor1_Model5
## 10 4 193 rsq standard 0.852 10 0.0182 Preprocessor1_Model5
同样,我使用show_best()
来关注最佳结果。提醒一下,手动调谐的最佳交叉验证 RMSE 是 300,带有mtry = 3
和trees = 2000
。
show_best(random_tune)
## # A tibble: 1 x 8
## mtry trees .metric .estimator mean n std_err .config
## <int> <int> <chr> <chr> <dbl> <int> <dbl> <chr>
## 1 2 812 rmse standard 297\. 10 13.7 Preprocessor1_Model4
类似地,我完成工作流程,根据训练数据拟合模型,并测试它。不错!RMSE 从 296 降至 295。
random_final <-
finalize_workflow(rf_workflow, select_best(random_tune)) %>%
fit(penguins_train)
random_final %>%
predict(penguins_test) %>%
bind_cols(penguins_test) %>%
metrics(body_mass_g, .pred)
## # A tibble: 3 x 3
## .metric .estimator .estimate
## <chr> <chr> <dbl>
## 1 rmse standard 295\.
## 2 rsq standard 0.883
## 3 mae standard 233.
3.使用刻度盘创建参数值
dials
处理parameter
对象。在我的随机森林模型中,有mtry
和trees
。每个对象包含关于范围、可能值、类型等的信息。我发现dials
是一个非常强大和有用的工具,因为太多时候,我不得不通读文档,翻阅我的书籍,或者谷歌来找出给定参数的范围和值。
检查参数信息
先来关注一下mtry
。我看到它是一个数量参数,指的是随机选择的预测因子的数量。范围是从 1 到…等等…?一个问号?我们再试试range_get()
看看范围。现在还不得而知。
mtry()
*## # Randomly Selected Predictors (quantitative)*
*## Range: [1, ?]*
mtry() %>% range_get()
*## $lower*
*## [1] 1*
*##*
*## $upper*
*## unknown()*
嗯,这是因为区间上限取决于数据中预测因子的个数,我需要手动指定值。我这样做是通过得到列数并减去 1(结果变量)。有两种方法可以做到这一点,如下所示。
# Option 1: Use range_set
mtry() %>% range_set(c(1, ncol(penguins_train) - 1))
*## # Randomly Selected Predictors (quantitative)*
*## Range: [1, 5]*# Options 2: Include in the argument
mtry(c(1, ncol(penguins_train) - 1))
*## # Randomly Selected Predictors (quantitative)*
*## Range: [1, 5]*
用trees
试试吧。参数不依赖于数据,因此提供了范围。
trees()
*## # Trees (quantitative)*
*## Range: [1, 2000]*
为参数创建值
现在,我知道了参数,我如何创造价值?有两种方法。我可以使用value_seq()
生成一个跨越整个范围的n
数字序列。在这里,我试图得到 4、5 和 10 个数字。如您所见,包括了trees
的最小值和最大值。
trees() %>% value_seq(n = 4)
*## [1] 1 667 1333 2000*
trees() %>% value_seq(n = 5)
*## [1] 1 500 1000 1500 2000*
trees() %>% value_seq(n = 10)
*## [1] 1 223 445 667 889 1111 1333 1555 1777 2000*
或者我可以用value_sample()
生成随机数。
set.seed(300)
trees() %>% value_sample(n = 4)
*## [1] 590 874 1602 985*
trees() %>% value_sample(n = 5)
*## [1] 1692 789 553 1980 1875*
trees() %>% value_sample(n = 10)
*## [1] 1705 272 461 780 1383 1868 1107 812 460 901*
为参数创建网格
让我们回忆一下tune_grid()
,调整参数的功能。它要求格网是一个数据框。返回向量的两种方法。那么我如何生成一个网格呢?当然,我可以简单地将向量转换成数据帧。但是,dials
有更好的方法做到这一点。同样,有两种方法:创建一个数字序列和创建一组随机数。
使用grid_regular()
创建带序列的网格。添加自变量mtry()
和trees()
中的参数,并指定每个参数的级别。我希望每个参数有三个级别,产生九个组合(三乘以三)。
set.seed(300)
dials_regular <- grid_regular(
mtry(c(1, ncol(penguins_train) - 1)),
trees(),
levels = 3
)
dials_regular
*## # A tibble: 9 x 2*
*## mtry trees*
*## <int> <int>*
*## 1 1 1*
*## 2 3 1*
*## 3 5 1*
*## 4 1 1000*
*## 5 3 1000*
*## 6 5 1000*
*## 7 1 2000*
*## 8 3 2000*
*## 9 5 2000*
对于随机数,使用grid_random()
并指定size
。
set.seed(300)
dials_random <- grid_random(
mtry(c(1, ncol(penguins_train) - 1)),
trees(),
size = 6
)
dials_random
*## # A tibble: 6 x 2*
*## mtry trees*
*## <int> <int>*
*## 1 2 1980*
*## 2 2 1875*
*## 3 1 1705*
*## 4 4 272*
*## 5 5 461*
*## 6 1 780*
使用带有 tune_grid()的刻度盘
这两种方法都创建了一个准备好与tune_grid()
一起使用的数据帧。对于grid_regular()
:
dials_regular_tune <-
rf_workflow %>%
tune_grid(
resamples = folds_5, grid = dials_regular
)show_best(dials_regular_tune, n = 1)
*## # A tibble: 1 x 8*
*## mtry trees .metric .estimator mean n std_err .config*
*## <int> <int> <chr> <chr> <dbl> <int> <dbl> <chr>*
*## 1 3 1000 rmse standard 300\. 10 14.4 Preprocessor1_Model5*dials_regular_final <-
finalize_workflow(
rf_workflow, select_best(dials_regular_tune)
) %>%
fit(penguins_train)
dials_regular_final %>%
predict(penguins_test) %>%
bind_cols(penguins_test) %>%
metrics(body_mass_g, .pred)
*## # A tibble: 3 x 3*
*## .metric .estimator .estimate*
*## <chr> <chr> <dbl>*
*## 1 rmse standard 296\.*
*## 2 rsq standard 0.881*
*## 3 mae standard 237.*
对于grid_random()
:
dials_random_tune <-
rf_workflow %>%
tune_grid(
resamples = folds_5, grid = dials_random
)show_best(dials_random_tune, n = 1)
*## # A tibble: 1 x 8*
*## mtry trees .metric .estimator mean n std_err .config*
*## <int> <int> <chr> <chr> <dbl> <int> <dbl> <chr>*
*## 1 2 1875 rmse standard 297\. 10 13.6 Preprocessor1_Model2*dials_random_final <-
finalize_workflow(
rf_workflow, select_best(dials_random_tune)
) %>%
fit(penguins_train)
dials_random_final %>%
predict(penguins_test) %>%
bind_cols(penguins_test) %>%
metrics(body_mass_g, .pred)
*## # A tibble: 3 x 3*
*## .metric .estimator .estimate*
*## <chr> <chr> <dbl>*
*## 1 rmse standard 296\.*
*## 2 rsq standard 0.882*
*## 3 mae standard 234.*
结论
我在文章中解释了三种调整参数的方法。
- 在
parsnip
中应用默认值:不指定参数值,parsnip
将使用所选引擎的默认值。 - 将
tune
与parsnip
一起使用:tune_grid()
功能交叉验证一组参数。它可以处理预定义的数据帧或生成一组随机数。 - 用
dials
创建将在tune
中使用的值,以交叉验证parsnip
模型:dials
提供关于参数的信息,并为它们生成值。这些值可以是一系列跨越一定范围的数字,也可以是一组随机数。
我非常喜欢简洁明了的代码。刚开始玩dials
的时候感觉很多余,没必要,因为我本来可以直接用tune
的。然而,随着我对dials
越来越熟悉,我开始明白最初为什么要创建它。我再也不用回头看文档看参数是整数还是浮点数,有没有范围限制,是否需要变换。
我花了一段时间才把从rsample
和recipe
到tune
和dials
的所有东西完全拼在一起。我希望你喜欢这篇文章,并有一个美好的一天!以下是所有代码的要点:
参考
Gorman KB、Williams TD、Fraser WR (2014)南极企鹅(Pygoscelis属)群落中的生态两性异形和环境可变性。PLoS ONE 9(3): e90081。https://doi.org/10.1371/journal.pone.0090081
【https://github.com/allisonhorst/palmerpenguins】
政府类型对新冠肺炎限制的影响
威权政府比民主政府更有可能颁布更具限制性的新冠肺炎措施吗?
乍一看,独裁政府对新冠肺炎的最初反应似乎比民主政府快得多。但这是正确的说法吗?(图片由作者提供)
新冠肺炎疫情病毒首次爆发已经一年多了。自那以后,我们看到各国以截然不同的方式处理疫情问题,许多人思考了可以用来预测各国不同反应和经历的指标。一些学者认为,由于民主对人民的责任,民主政府(而不是专制政府,如中国的)是成功控制疫情的关键(Alon et al .,2020)。卡内基国际和平基金会(Carnegie Endowment for International Peace)的研究提出了一个更具批判性的观点,认为即使是韩国和意大利等民主国家也采用了近乎独裁主义的方法,如侵犯隐私的数字合同追踪应用程序或违反隔离的刑事处罚(Kleinfeld,2020)。
在这篇文章中,我考察了政府对新冠肺炎的反应和国家的民主水平之间的关系。我要问的是,正如经济学人智库(EIU)所描述的那样,政府的政权类型是否会对政府的反应产生影响。我假设专制国家对制定内部流动限制的反应更快,而民主国家反应更慢。本文旨在介绍探索性分析,调查新冠肺炎疫情最初几个月与政府类型的关系,并鼓励未来的研究。
我使用 Python 和标准数据科学包进行分析。我用BeautifulSoup
从维基百科上抓取民主指数。我使用pandas
来处理我的大部分数据争论、切片、合并和操作。我用seaborn
来绘图。用于进行这些分析的 Jupyter 笔记本可在 my GitHub 上获得。
数据
我使用两个来源的数据:
- OxCOVID19 数据库:这是牛津大学编译的新冠肺炎数据数据库。它是几个数据库的广泛集合,不仅包括与 COVID 病例和死亡相关的标准流行病学统计数据,还包括关于流动模式、天气模式和政府应对指标的数据。我使用
EPIDEMIOLOGY
表按国家确定第一个新冠肺炎病例。我还使用了GOVERNMENT_RESPONSE
表,其中包含了各国政府为应对疫情而采取的遏制和关闭政策的信息(这是牛津新冠肺炎政府应对跟踪 (Mahdi 等人,2020 年)的一部分)。推荐大家去查阅一下指标的完整列表,可以在码本中找到。在这篇文章中,我特别关注其中一个指标:c7_restrictions_on_internal_movement
- 民主指数:经济学人智库(EIU)计算出一个数值分数来衡量每个国家的民主程度,数值越高,政府越民主。这一民主分数被映射到四种政体类型之一:完全民主、有缺陷的民主、混合民主和专制。民主指数是从相应的维基百科页面收集的。
关于民主指数的一点信息
EIU 2020 年民主指数用于衡量民主和政体类型(经济学人智库,2020)。民主指数吸收了每个国家的 60 个不同指标,计算出代表民主的数值分数,数值越高,政府越民主。这一民主分数被映射到四种政体类型之一:完全民主、有缺陷的民主、混合民主和专制。民主指数来自维基百科页面。虽然民主指数有其自身的批评和局限性(将在本文后面讨论),但一些研究,包括 Bashar 和 Tsokos (2019) 的研究,声称它是最全面的民主指标。
每个国家第一个新冠肺炎病例的日期是从 OxCOVID19 的流行病学表中通过确定第一个病例数超过 0 的条目来计算的。内部流动的严格性来自 OxCOVID19 的政府响应表。这一具体指标可以取下列值之一:0(无措施)、1(建议不要在区域/城市之间旅行)、2(内部流动限制到位)和空白(无数据)。这一分析侧重于二级限制,因为它旨在将最严格的政府措施与威权主义联系起来。
结果
乍看之下,专制国家在制定严格的内部流动规则方面似乎比民主国家反应更快。一些威权国家(如利比亚和委内瑞拉)在报告首例新冠肺炎病例前几天颁布了限制措施。
Figure 1
按政权类型对颁布内部限制措施的反应速度进行分组。政府的政体类型越专制,对内部流动限制的严厉反应就越快。这似乎证实了一个假设,即独裁政府在制定内部限制措施方面行动更快,而民主政府则行动更慢。
(图片由作者提供)
然而,在按照第一个新冠肺炎病例的日期来划分反应速度时,分析表明,第一次感染的日期对反应速度有很大影响。Figure 2
显示了政府响应时间,按制度类型分开,第一个新冠肺炎病例的日期按月和按周汇总。对于后来的第一批新冠肺炎病例,政权的威权性质似乎对反应时间的影响较小。
(图片由作者提供)
在按月累计的响应时间中,与其他类型的政体相比,威权政体的平均响应时间在前两个月明显更快。到第三个月,所有制度类型的平均值似乎相等。在按周汇总的响应时间中,随着时间的推移,下降趋势变得明显。对于较早出现首例新冠肺炎病例的国家(在第 8 周之前),红色数据点显示,独裁政权往往比其他类型的政权反应更快。对于首次新冠肺炎日期较晚(即 8 周后)的国家,由于数据点紧密聚集且变化较小,因此用药制度类型对反应时间的影响较小。
总体而言,第一例新冠肺炎病例的日期对一国政府颁布限制措施所需的天数有影响,尽管该国政府的政体类型不容忽视。对此的一种解释是,对于那些较早出现首例新冠肺炎病例的国家,独裁政权更有可能实施严格的国内运动措施。对于后来出现首批新冠肺炎病例的国家,制度类型对严格措施的影响较小。
Figure 3
显示了政府对 2020 年 2 月 15 日前后出现首例新冠肺炎病例的国家实施内部流动限制的回应。选择 2 月 15 日作为截止日期是因为,正如在Figure 2
中看到的,大约在第 8 周聚集行为开始出现。2020 年的第八周始于 1 月 16 日。
(图片由作者提供)
对于在 2 月 15 日之前出现首例病例的国家,威权国家的反应平均要快 20 天左右。对于 2 月 15 日之后出现首例病例的国家,所有国家都倾向于做出更快的反应,威权政权对反应时间的影响较小。对于在 2 月 15 日之后出现首例病例的国家,民主国家往往需要最长时间做出反应,而对于在 2 月 15 日之前出现首例病例的国家,这一趋势并不明显。
Figure 4
显示了每种制度类型的响应分布。专制、混合和有缺陷的民主政权往往是单峰的,有长的右尾巴,这意味着他们的反应有一些统一,但在模式之外有一些变化。紫线所示的民主政权反应时间的分布就不那么确定了。民主政权的反应时间分布是双峰的和可变的,这表明不同民主政权的反应差异很大。
(图片由作者提供)
Figure 5
针对每种制度类型,绘制响应速度最慢的前 3 个国家和响应速度最快的前 3 个国家。圆点表示第一个新冠肺炎病例的日期,箭头的末端表示第一次响应颁布内部移动限制的日期。向左箭头表示该国在他们的第一个病例之前已经作出反应。虚线上方是每个政权类型中反应最快的前 3 个国家。虚线下方是每个制度类型中反应最慢的前 3 个国家。
(图片由作者提供)
该图证实了上述观察结果,即较晚出现新冠肺炎首例病例的国家比较早出现新冠肺炎首例病例的国家反应更快。然而,即使在这个框架内,我们可以注意到最快的民主国家的反应与其他 3 种政体类型中最快的国家的反应有很大的不同。即使是最快的民主国家也没有积极响应(即在新冠肺炎确诊病例之前),而其他类型的政权却这样做了。
至于反应最慢的国家,各种制度类型之间似乎差异不大。无论何种政体类型,较早出现首例新冠肺炎病例的国家往往比较晚出现首例新冠肺炎病例的国家需要更长的时间做出反应。
讨论和限制
该项目表明,虽然在某些情况下,专制政权实施内部限制的反应更快,但许多其他因素影响了这一观察。几乎所有后来出现新冠肺炎病例的政体都迅速做出了反应,不仅仅是威权政体,也不是所有威权政体都迅速做出了反应。这与民主政府和独裁政府各有优缺点的观点相一致,它们都不擅长应对新冠肺炎的威胁(Stasavage,2020)。
有几个突出的因素没有包括在这个分析中。这一分析中未包括的因素包括国家人口、新冠肺炎严重程度和国家以往大流行的历史。鼓励未来的研究人员调查一些因素,如这些国家与病毒最初爆发的中国在地理上的接近程度。也许蒙古反应如此迅速的原因之一(在他们的第一个新冠肺炎病例前 18 天)是因为他们与中国接壤。
数据来源的有效性
对分析中使用的数据来源持批评态度是很重要的。EIU 的民主指数被用来代表一个国家的威权和民主水平,但该指数一直受到批评。例如,Bashar 和 Tsokos (2019)认为,EIU 的民主指数可能不太准确,因为它没有考虑到收集的数据之间的相互作用和相关性。
OxCOVID19 数据库中也有一些空白。没有以下国家的病例数数据:瓦努阿图、皮特凯恩岛、也门、福克兰群岛、所罗门群岛、香港、南苏丹、澳门、马拉维、土库曼斯坦、塔吉克斯坦和莱索托。也可能有人担心为每个国家计算的第一个新冠肺炎病例的准确性。一项研究使用统计方法表明,独裁政府很可能操纵了他们公布的新冠肺炎数据(Kapoor 等人,2020)。由于数据库中的数据可能不准确或伪造,第一个新冠肺炎病例的日期可能不准确,并且本文的分析具有误导性。
未来方向
这项研究只调查了国家对国内流动实施严格限制的时间。我们鼓励未来的研究结合世界价值观调查数据对其他类型的限制进行重复研究。更重视贸易的国家会不会更慢地颁布国际旅行管制的限制?更重视教育的国家在颁布关闭学校的限制方面往往更慢吗?这项研究中的分析可以用关于每个国家的额外数据来充实,如国内生产总值、卫生基础设施状况和以前大流行的历史。有许多方向可以将当前的研究扩展到对各国应对全球疫情的新理解。
参考
阿龙,我,法雷尔,m .,,李,S. (2020)。政体类型和新冠肺炎反应。FIIB 商业评论,9,152–160 页。
巴沙尔河和措科斯河(2019 年)。世界各国民主指数得分的统计分类。
牛津新冠肺炎政府响应跟踪码本(2020)。GitHub 仓库。2020 年 11 月 18 日检索自https://github . com/ox cgrt/covid-policy-tracker/blob/master/documentation/code book . MD。
民主指数。(2020).民主指数。2020 年 11 月 29 日,从 https://en.wikipedia.org/wiki/Democracy_Index检索。
经济学人信息部。(2020).民主指数 2020。于 2021 年 3 月 27 日从https://www.eiu.com/topic/democracy-index取回。
m .卡普尔、a .马拉尼、s .拉维和 a .阿格拉瓦尔(2020 年)。独裁政府似乎操纵 COVID 数据。arXiv:普通经济学。
柯菲德河(2020)。专制国家还是民主国家更好地应对流行病?。卡内基国际和平基金会。2020 年 11 月 29 日检索,来自https://Carnegie endowment . org/2020/03/31/do-authoritative-or-democratic-countries-handle-pandemics-better-pub-81404。
Mahdi,a .、Blaszczyk,p .、Dlotko,p .、d .、Chan,t .、Harvey,j .、Gurnari,d .、Wu,y .、Farhat,a .、Hellmer,n .、Zarebski,a .、Hogan,b .、& Tarassenko,L. (2020)。OxCOVID19 数据库:更好理解新冠肺炎全球影响的多模态数据库。medRxiv。
斯塔萨维奇博士(2020 年)。民主、专制和紧急威胁:新冠肺炎过去一千年的教训。国际组织,1–17。doi:10.1017/S0020818320000338。
在家工作会毁了人们的背吗?
用数据回答
使用 Prophet 分析谷歌搜索趋势,寻找在家工作导致更多背痛的证据
由 Unsplash 上的 CHUTTERSNAP 拍摄
对于我们这些能够在家(WFH)工作的幸运儿来说,这种经历可能是喜忧参半。我们中的许多人还没有准备好在家里用简陋的办公桌和不符合人体工程学的工作台工作。很快,研究、论文和新闻文章开始报道和抱怨在家工作给我们许多人带来了背痛问题。
然而与此同时,人们开始投入更多的时间和金钱来照顾自己。大多数健身房都关门了,但是人们很快就适应了,他们投资购买了家用健身器材和更符合人体工程学的设备。这导致了哑铃、重量和室内自行车等健身器材的需求激增和短缺(来源,以及包括站立式办公桌和人体工程学办公椅在内的 WFH 设置(来源)。此外,WFH 让数百万人避免了长时间的开车和通勤,这增加了背部问题的风险。因此,同样有可能的是,WFH 可能通过将人们从长时间的通勤和办公室工作中解救出来,减轻了人们的背痛。
因此,带着这些混杂的信号,我想知道:在疫情期间,是否有更多的人因为在家工作而感到背痛?我使用谷歌趋势数据和 Prophet 分析搜索量时间序列找到了我的答案。
背痛的数据
回答这个问题的一个数据来源是医院和保险公司。然而,在 covid 期间,医院提供的服务有限,并不是每个患有背痛的人都会通过医院和保险接受治疗。相反,当人们经历背痛时,他们可能会做的第一件事是在网上搜索与背痛有关的事情,如背痛的症状,如何治疗背痛,或他们家附近的脊医。因此,一种获得是否有更多人经历背痛的信号的方法是看看在疫情期间对背痛或脊椎指压治疗师的搜索是否增加。
谷歌趋势提供了这些数据。从 2013 年开始,我搜索了“背痛”、“下背痛”和“我附近的脊医”的趋势。
初步看看如何搜索背痛随着时间的推移而增加(截图由作者从来源)。
分析搜索趋势数据
分析时间序列数据不是一件容易的事情,但是一个简单的开始方法是使用 Prophet 包,它提供了一个非常易于使用的界面来进行预测,同时考虑数据中的趋势、季节性和异常值。在对季节性和趋势进行控制后,我们可以测试在疫情期间是否有统计学意义上的背痛相关关键词的搜索量的增加。
但首先,快速浏览一下数据似乎表明,大多数与背痛相关的搜索在 2020 年 3 月下降,这是庇护所订单开始启动的时候。然而,在 2020 年 4 月之后,搜索量再次飙升。你还可以看到,自 2013 年以来,这些搜索有明显增加的趋势。这对于搜索词“我附近的按摩师”尤为明显。然而有趣的是,对背痛和下背痛的搜索似乎在 2018 年中期达到高峰后就稳定下来了。
统计测试背痛搜索是否因疫情而增加的一种方法是将地面实况搜索量与我们基于历史数据的最佳估计进行比较。更具体地说,我们可以比较基于历史趋势的从 2020 年 4 月到现在搜索数量的预测与自 2020 年 4 月以来发生的背痛搜索的地面真实数据。
这可以通过下面的代码来完成:
背痛的结果如下图所示,红线显示“背痛”的预测搜索结果,而蓝线表示“背痛”的真实搜索量。
寻找背痛。蓝色为基本事实,红色为基于历史数据和趋势的预测搜索。图片由作者(来源)提供,数据来自来源。
有趣的是,在疫情的最初几个月,背痛的搜索量有所下降。然而,搜索量在夏季开始回升,2021 年的搜索量远远超过了基于历史趋势的搜索量。将每月的搜索预测与基本事实进行比较,结果是基本事实搜索的数量明显大于预测(t(19) = 3.02,p = .007)。搜索词“我附近的按摩师”也是这种情况,其真实搜索量明显大于预测搜索量(t(19)=3.21,p=.005)。
搜索我附近的脊医。蓝色为基本事实,红色为基于历史数据和趋势的预测搜索。图片由作者(来源)提供,数据来自来源。
调查的结果
总体而言,主要调查结果可总结如下:
- “背痛”的搜索量在过去十年中稳步增长,但在 2018 年停滞不前,出现了略微下降的趋势。
- 对“背痛”和“我附近的脊椎指压治疗师”的搜索在疫情的最初几个月略有下降,但从 2020 年夏天开始,增长速度明显超过历史趋势。
- 这些结果强烈表明,疫情期间在家工作很可能导致美国工人背部疼痛加剧。
这些发现意味着什么?这组分析表明,来自谷歌趋势的数据可以成为分析公共卫生趋势的有效方法,因为它可能是健康问题的第一个迹象,即使是对那些没有住院的人来说。结果还显示,疫情似乎确实对许多美国人的背部健康造成了损害。希望随着疫情逐渐消失,回到办公室可以帮助人们回到更符合人体工程学的办公桌前。
最后但并非最不重要的一点是,这里有一个到 Colab 笔记本的链接,可以复制这些分析。
自动编码器(AE)和可变自动编码器(VAE)的区别
如何压缩数据,甚至从随机值生成数据?这就是自动编码器和变型自动编码器。
简化的能力意味着消除不必要的东西,让必要的东西说话——汉斯·霍夫曼
上下文—数据压缩
数据压缩是训练网络的重要阶段。这个想法是压缩数据,这样同样数量的信息可以用更少的比特来表示。这也有助于解决维数灾难的问题。具有许多属性的数据集不适合训练,因为它往往会使模型过拟合。因此,在数据集可用于训练之前,需要应用维数减少技术。
这就是自动编码器(AE)和可变自动编码器(VAE)发挥作用的地方。它们是用于压缩输入数据的端到端网络。自动编码器和变分自动编码器都用于将数据从高维空间转换到低维空间,本质上实现压缩。
自动编码器— AE
这是什么?
Autoencoder 用于学习给定网络配置的未标记数据的有效嵌入。自动编码器由两部分组成,编码器和解码器。编码器将数据从高维空间压缩到低维空间(也称为潜在空间),而解码器则相反,即将潜在空间转换回高维空间。解码器用于确保潜在空间可以从数据集空间捕获大部分信息,方法是强制它输出作为输入提供给解码器的内容。
框图
框图如下所示。
自动编码器—作者图片
在训练期间,输入数据 x 被馈送到编码器功能 e_theta(x) 。输入通过一系列层(由变量 theta 参数化),减少其维度以获得压缩的潜在向量 z 。层数、层的类型和尺寸以及潜在空间尺寸是用户控制的参数。如果潜在空间的维数小于输入空间的维数,就实现了压缩,实质上消除了冗余属性。
解码器 d_phi(z) 通常(但不一定)由编码器中使用的层的近似互补层组成,但顺序相反。层的近似补充层是可用于撤销原始层的操作(在某种程度上)的层,诸如将 conv 层转置到 conv 层、汇集到取消汇集、完全连接到完全连接等。
损失函数
整个编码器-解码器架构在损失函数上被集体训练,该损失函数鼓励在输出端重构输入。因此,损失函数是编码器输入和解码器输出之间的均方误差。
自动编码器损失函数—作者图片
这个想法是有一个非常低维的潜在空间,以便实现最大的压缩,但同时,误差足够小。将潜在空间的维度减小超过某个值将导致信息的显著损失。
潜在空间的价值/分布没有限制。它可以是任何东西,只要当解码器功能应用于它时,它可以重构输入。
潜在空间可视化
以下是通过在 MNIST 数据集上训练网络而生成的潜在空间的示例。
自动编码器的潜在空间——作者图片
可以看出,相同的数字倾向于在潜在空间中自我聚集。另一个需要注意的重要事情是,潜在空间的某些部分并不对应于任何数据点。使用这些作为编码器的输入将导致输出看起来不像来自 MNIST 数据的任何数字。这就是我们所说的潜在空间没有被正则化的意思。这种潜在空间仅具有几个具有生成能力的区域/聚类,这意味着对潜在空间中属于一个聚类的任何点进行采样都将生成该聚类所属的数据的变化。但整个潜在空间不具备再生能力。不属于任何簇的区域将产生垃圾输出。一旦网络被训练,并且训练数据被移除,我们就无法知道解码器从随机采样的潜在向量生成的输出是否有效。因此 AE 主要用于压缩。
对于有效的输入,AE 能够将它们压缩到更少的比特,基本上消除了冗余(编码器),但是由于非正则化的潜在空间 AE,解码器不能用于从潜在空间采样的向量的潜在中生成有效的输入数据。
为什么要用 AE 进行压缩?
线性自动编码器的潜在空间非常类似于在数据的主分量分析期间获得的特征空间。输入空间维度 n 和潜在空间维度设置为 m < n 结果的线性自动编码器将跨越与 PCA 的第一个 m 特征向量所跨越的向量空间相同的向量空间。如果 AE 和 PCA 差不多,为什么要用 AE?声发射的威力来自它的非线性。添加非线性(例如非线性激活函数和更多的隐藏层)使得 AE 能够以少得多的信息损失在较低维度中学习输入数据的相当强大的表示。
可变自动编码器
这是什么?
变分自动编码器解决了自动编码器中非正则化潜在空间的问题,并为整个空间提供了生成能力。AE 中的编码器输出潜在向量。VAE 编码器不是输出潜在空间中的向量,而是为每个输入输出潜在空间中预定义分布的参数。然后,VAE 对这种潜在分布施加约束,迫使其成为正态分布。这个约束确保了潜在空间的正则化。
框图
VAE 的框图如下所示。在训练期间,输入数据 x 被馈送到编码器函数 e_theta(x) 。就像 AE 一样,输入通过一系列层(由变量 theta 参数化)来减少其维度,以获得压缩的潜在向量 z 。然而,潜在向量不是编码器的输出。相反,编码器输出每个潜在变量的平均值和标准差。然后,根据该平均值和标准偏差对潜在向量进行采样,然后将其馈送到解码器以重构输入。VAE 中的解码器与 AE 中的解码器工作原理相似。
损失函数
损失函数由 VAE 目标定义。VAE 有两个目标
- 重建输入
- 潜在空间应该是正态分布的
因此,VAE 的训练损失被定义为这些和 相似性损失 的总和。与 AE 一样,重构误差是输入和重构输出的均方损耗。相似性损失是潜在空间分布和标准高斯(零均值和单位方差)之间的 KL 散度。那么损失函数就是这两个损失的总和。
变分自动编码器损失函数—图片由作者提供
如前所述,在将潜在向量馈送到解码器之前,从编码器生成的分布中对其进行采样。这种随机采样使得编码器很难发生反向传播,因为我们无法追溯这种随机采样导致的错误。因此,我们使用重新参数化技巧来模拟采样过程,这使得误差有可能通过网络传播。潜在向量 z 被表示为编码器输出的函数。
重新参数化技巧用于将潜在向量 z 表示为编码器输出的函数。
潜在空间可视化
训练试图在两个损失之间找到平衡,并以看起来像单位范数的潜在空间分布结束,其中聚类将相似的输入数据点分组。单位范数条件确保潜在空间被均匀地展开,并且在聚类之间没有明显的间隙。事实上,相似数据输入的聚类通常在某些区域重叠。下面是通过在相同的 MNIST 数据集上训练网络生成的潜在空间的示例,该数据集用于可视化 AE 的潜在空间。请注意,簇之间没有间隙,空间类似于单位范数的分布。
**
变型自动编码器的潜在空间——作者图像
需要注意的重要一点是,当潜在向量从具有重叠聚类的区域中采样时,我们得到变形的数据。当我们对从一个聚类移动到另一个聚类的潜在空间进行采样时,我们得到了解码器输出之间的平滑过渡。
VAE 的潜在空间被规则化——作者的意象
摘要
本文介绍了自动编码器(AE)和变分自动编码器(VAE)的概念,它们分别用于数据压缩和数据生成。VAE 解决了 AE 的非正则化潜在空间的问题,这使得它能够从潜在空间的随机采样向量中生成数据。AE 和 VAE 的主要总结点如下
自动编码器
用于在潜在空间中生成输入的压缩变换
潜在变量没有被正则化
选择一个随机的潜在变量会产生垃圾输出
潜在变量具有不连续性
潜在变量是确定性值
潜在空间缺乏生成能力
可变自动编码器(VAE)
将潜在变量的条件强制为单位范数
压缩形式的潜在变量是均值和方差
潜在变量是平滑连续的
潜在变量的随机值在解码器处产生有意义的输出
解码器的输入是随机的,并且是从具有编码器输出的均值和方差的高斯样本中采样的。
正则化潜在空间
潜在空间具有生成能力。
如果这篇文章对你有帮助,或者你想了解更多关于机器学习和数据科学的知识,请关注Aqeel an war,或者在LinkedIn或Twitter上联系我。**
*https://aqeel-anwar.medium.com/membership *
Sql 和 NoSql 的区别
你需要知道的 6 个关键区别!
TL;博士:
SQL 与 NoSQL 的主要区别
- SQL 数据库有严格的模式,而 NoSQL 数据库没有。
- SQL 数据库有关系,而 NoSQL 数据库没有任何关系。
- SQL 数据库由表组成,而 NoSQL 数据库有集合和文档。
- SQL 数据库有多个表,而在 NoSql 数据库中,数据被压缩到几个集合中。
- SQL 数据库是垂直可伸缩的,但是 NoSQL 数据库是垂直和水平可伸缩的。
- SQL 数据库可以限制每秒查询的数量,而 NoSQL 数据库则没有。
现在我们已经看到了 SQL 和 NoSQL 数据库之间的主要区别,让我们深入了解它们实际上是什么以及它们是如何工作的。我们还将比较这两种数据库的不同优缺点。
让我们开始吧!
SQL:结构化查询语言
SQL 代表结构化查询语言,它允许你编写数据库查询。所以说到底,SQL 只是一种语言,而不是数据库本身。但是可以使用 SQL 查询数据库。典型的 SQL 查询可能如下所示:
从用户中选择 id、名称、电子邮件;
在此示例查询中,大写的关键字称为 SQL 关键字,查询的其他部分由所需的自定义列和数据库中的表名组成。
当然,SQL 有更多的关键字和命令。SQL 可用于在数据库上执行任何 CRUD 操作(创建、读取、更新和删除)。
当我们通常谈论 SQL 与 NoSQL 时,我们实际上谈论的是它们背后的数据库。
对于 SQL,我们通常使用的数据库是关系数据库。这意味着我们有一个在某些假设下工作的数据库,它支持结构化查询语言。
这样的数据库与表一起工作。可以把表想象成特定类型数据的容器。类似于用户表和产品表的东西。如果我们想要存储关于我们的应用程序用户的信息,我们将它存储在 users 表中,并将产品信息存储在 products 表中。
关系数据库中的表通过称为键的公共列相互关联。关系数据库中有主键和外键。主键有助于唯一地标识表中的记录,而外键有助于关联其他表中的相关信息。
然而,在使用 SQL 查询的关系数据库中,对于如何存储数据会有一些严格的要求。这是因为数据库必须遵循固定的模式。每列都有指定的数据类型,不能更改。即使向表中添加一个新列也不像您希望的那样灵活。假设您需要存储一条额外的信息,可能是客户对订单的特殊指示。理想情况下,您会希望获得该订单的特殊说明信息。但是使用 SQL,这是不可能的。您必须向整个表中添加一个名为特殊说明的新列,即使这意味着所有其他记录都将包含空值。虽然这并不是一个坏主意,但也不太灵活。
但是,SQL 是相当强大的。尽管由于每次请求都需要处理多个连接而带来了很多复杂性,SQL 仍然是许多开发人员的首选。如果你打算进入数据科学领域或者已经进入了这个领域,SQL 将会非常有帮助。SQL 是一个很好的数据争论工具,几乎可以做 python 为数据争论所做的一切。点击阅读更多关于 SQL 对数据科学的重要性。
既然我们已经讨论了 SQL,那么让我们来看看什么是 NoSQL 以及它是如何工作的。
NoSQL:不仅是 SQL
NoSQL 代表“不仅是 SQL ”,这是因为 NoSQL 支持非结构化数据的存储,这是其本质决定的。NoSQL 数据库不像 SQL 数据库那样有固定的模式。
事实上,NoSQL 数据库甚至没有像 SQL 数据库那样的表和索引。那么我们如何存储数据呢?在 NoSQL,我们以分层的形式存储数据。信息不是以表格的形式存储,而是以集合和文档的形式存储。可以把 NoSQL 数据库集合想象成 SQL 数据库的表,把 NoSQL 文档想象成 SQL 记录。
但是与具有固定模式的 SQL 表不同,NoSQL 集合没有这样的限制。文档可以有任意多的值。有了这种令人难以置信的灵活性,现在每个文档中可以有不同的信息。回到我们在 SQL 部分讨论的特殊指令列的例子,现在可以只为那个特定的顺序添加特殊指令列,而不改变整个集合。
此外,在 NoSQL 没有所谓的关系。“哦,那你到底怎么把不同收集的数据结合起来呢,普拉尼思?”你可能会问。
嗯,即使从技术上讲,NoSQL 数据库中没有“关系型”这样的东西,但是将所有相关的东西放在一个地方仍然是可能的。那就是将你需要的所有数据放入一个集合中。没错。这听起来可能非常低效,但这大大降低了查询的复杂性,并使我们能够非常快速地显示数据,而不需要像在 SQL 数据库中那样连接多个表。
然而,这样做的缺点是,我们最终会得到大量重复的数据。例如,您将拥有一个用户集合和一个产品集合。当您需要在另一个集合中存储与订单相关的信息时,您必须在 orders 集合中再次包含用户信息和产品信息。
鸣谢:作者
缩放比例
对于 SQL 数据库,很难/不可能进行水平扩展。但是 NoSQL 的数据库却不是这样。但是,嘿,水平/垂直缩放到底是什么?
水平扩展是指随着数据的增长向数据库添加额外的服务器。而垂直扩展只是增加了现有服务器的能力。这是 SQL 数据库的一个很大的缺点,因为在某种程度上,它真的很难进一步扩展。但是有了 NoSQL,您可以在扩展时添加额外的服务器,轻松生活!
两种类型的利弊
这两种数据库都有各自的优缺点。
- 虽然固定模式有利于数据库中的预测性布局和可靠字段,但它不够灵活。在这方面,SQL 有一个预测性的布局,但在灵活性上输了,而 NoSQL 有所有的灵活性,但缺乏字段的可靠性。
- 由于表之间的关系,SQL 数据库可以很容易地处理更新操作。另一方面,由于缺乏固定的模式,NoSQL 擅长处理读取请求,而不擅长更新操作。
- 当构建一个长期的应用程序时,伸缩性可能是一个问题。由于其水平扩展能力,NoSQL 是非常可行的。
最后的想法
实际上,可以用这两个数据库中的任何一个构建任何类型的应用程序。这完全取决于你的特殊需求和业务性质。只有当数据变大时,问题才会出现。
您是否需要数据之间的大量关系,并且需要频繁地更改这些关系?您需要定义一个清晰的模式吗?使用 SQL。
您的应用程序是否发出大量读取请求,而不是大量写入请求?想要快速显示数据而不需要任何复杂的查询吗?结垢是一个问题吗?和 NoSQL 一起去。
这两者之间没有明显的赢家,它只取决于应用程序的特定需求。
原载于我的博客【praneethvasarla.com】
获得正确的软件架构
复合组件架构:第一集——软件架构——幽灵的威胁
约瑟夫·巴里恩托斯在 Unsplash 上拍摄的照片
所以这个问题总是会出现,我会在本文最后给出我的答案。建议你先自己回答问题,再继续看文章。你会发现很难用一种容易理解的方式来定义这个术语。
应用程序的架构是整个软件系统的结构。这将与一组规则一起构成,软件系统必须根据这些规则来实现。这样一套规则保证了与质量方面的一致性将推动项目走向长期的成功。在我看来,该架构包括以下元素:
- 模块化
- 模式的使用
- 规则和准则
- 证明文件
从内容上来说,这个答案是正确的。然而,它不能给出一个直截了当的答案。软件开发中的架构要么意味着一切,要么什么都不是。软件项目的结构发生在不同的层次上。首先,我们必须解释有哪些层次,然后是架构的类型。一旦介绍完毕,我们就可以查看各自的任务、目标和角色并给出答案。
建筑等级
模块化是建筑最重要的目的之一。它一步一步地将软件系统划分为服务、层、组件、类、方法等等。
如图 1 所示,这创建了要开发的软件的层次树表示。
图 1 一个人管理应用程序的模块化示例
现在,树结构的每一层都可以分配给一个实现元素。例如,在编程语言中,子系统 PersonManager 和 CategoryManager (图 1)是类。
然后,每个级别都被赋予其实现元素的名称,结果就是所谓的级别模型,如图 2 所示。
图 2:层模型(从图 1 的模块化结构转移而来)
术语定义
任何在书籍、文章或其他媒体中研究过建筑主题的人都知道可以使用各种类型的术语。这些术语包括系统架构、软件架构、服务架构、微观和宏观架构等等。
让我借此机会解释一下我在这个问题上的术语。我使用的术语是系统架构、软件架构、和实现(设计&编码)。图 2 显示了这些术语位于我的哪个区域。
系统架构
Alexandre Debiève 在 Unsplash 上拍摄的照片
系统架构定义了系统由哪些子系统组成;这些通常涉及服务(web)或外部系统,如打印机、数据库等。图 1 中的 PersonManagerApp 由两个服务组成,一个 web API 和一个前端。
在图 2 中,“定义范围”没有延伸到服务级别的中间:一个系统架构仅仅识别系统的服务,它们通信的方式,以及在那个级别使用的概念。然而,没有关于服务实现的规范。
因为我们在这里只创建了应用程序的粗略结构,所以其他来源称之为“粗略架构”或“宏架构”。
没有建筑师为每个项目重新发明轮子,但是他们建立在已经在许多其他项目中被证明有价值的被证明的设计之上。我相信你对这些所谓的“模式”很熟悉。
系统架构主要负责可伸缩性、故障安全、互操作性等方面。因此,从长远来看,如果一个系统架构是未计划的和未实现的,或者是计划和实现不当的,那么在这些方面就存在问题的风险。
软件架构
我在图 2 中展示了软件架构的职责,它决定了软件系统的内部结构,就像图 1 中的单个服务一样。这里,指定了服务中的层和组件的结构,但是没有精确地描述组件的内部生命。
就像系统架构一样,仍然有模式留给我们现成的解决方案:软件架构模式。例如:
- 庞然大物
- 插件
- 管道和过滤器
- 多层
类似于系统架构 , 软件架构也负责具体的定性方面,如可重用性、可互换性和可分析性。
在这个层次上,定性的方面包括可重用性、可互换性、可分析性、可测试性和可修改性。前两点用引号括起来是因为可重用性和可互换性通常发生在组件级别。因此,一个 DLL 文件,比一个类有更大的机会被重用。
实施(设计和编码)
编程是图 2 中最后一个可见的阶段。在这个阶段,从语句、表达式和控制结构实现应用程序的逻辑。再读一遍前面的句子:这是逻辑编码的地方。
是什么让它如此重要?想象一下使用 Main()方法实现一个大型应用程序,比如 Visual Studio。从纯技术角度来看,这不成问题;从实际的角度来看,很明显,这是不可能的。
但它确实指出了一些非常重要的事情。如图 3 所示,直到实现级别的所有级别都是可选的,并且仅用于构建应用程序和使用定性方面。我们也可以在单元测试中看到这一点:我们编写这些来检查源代码级别——所有其他级别都不相关,因为它们对逻辑来说并不重要。
图 3:应用程序的结构和逻辑
与前面的类型一样,实现模式是解决重复出现的问题的可用模式。例如:
- 环
- 分支
- 分配
鉴于这不再发生在“结构化”层面,所讨论的定性方面也减少了。例如,这将包括可分析性和可修改性。
那么什么是建筑呢?
也许我们在研究完这篇文章后,已经接近一个更清晰的答案了。在软件开发中,我编写了不同层的术语架构。因此:
架构概述了软件系统的层次结构,以实现定性方面,从而实现项目的长期目标。
在这种情况下,架构包括与结构相关的所有级别:
- 系统
- 服务
- 层
- 成分
- 班级
- 方法
我根据系统架构类型、软件架构和设计来划分这些层。这涉及到系统和软件架构师和开发人员的角色。
结论
即使系统和软件架构师代表他们工作:架构是团队的事情。只有系统和软件架构师以及训练有素的开发人员才能实现一个结构合理的软件提案。
本期文章的目的是介绍“复合组件架构”,这是最新的软件架构版本。希望这第一集可以展示我们可以用它解决什么问题。
为你选择阅读!
https://medium.com/nerd-for-tech/understand-typescript-after-a-3-minute-guide-06-ff2634dba57a https://medium.com/next-level-source-code/forever-remember-object-oriented-programming-fae13462bb9f https://medium.com/codex/5-methods-that-drastically-shorten-drastically-your-code-in-c-8-0-e7bc1ca1b480
参考
[1] 软件架构
【2】软件架构指南—马丁·福勒
【3】软件架构基础—马克·理查兹&尼尔·福特
Python 中不同的条形图
使用 python 库的条形图
作者图片
条形图用于测量跨类别的项目或监控随时间的变化。这些是最古老的图表之一,通过“条”来表示数据可视化。这些是最常见的表示数据的图表之一,信息丰富且易于理解。在本文中,使用 python 库制作了不同类型的条形图。对于这篇文章,https://archive.ics.uci.edu/ml/datasets/Breast+Cancer 图书馆的数据被用做“T0”。
为了对所有的分类变量有一个基本的了解,下面绘制了柱状图的支线图。为此,使用了 Seaborn 库[1]的 countplot 函数。为了更好地理解这一点,还使用二项式因变量给出了色调。在继续之前,需要导入以下 python 基本库。
导入库
下面的支线剧情是由上述代码绘制的。为此,使用 countplot 函数。这些是最常见的图,由色调给出的两种颜色代表二项式因变量。
作者图片
要创建类似的条形图,但填充是透明的,边缘带有颜色,可将 facecolor 选项设置为(0,0,0,0)并可获得下面的条形图。第一个 3 代表 RGB 颜色,而第四个是控制 alpha。采用 facecolor 选项(0,0,0.9,0.4)绘制的第二个图形。类似地,如果没有给定 alpha 值,那么条形的内部颜色将是黑色。
作者图片
此外,单条/多条的颜色也可以改变。除此之外,酒吧的宽度也可以通过宽度选项进行管理。代码如下。
作者图片
作者图片
可以通过两种条形图选项来比较不同的组或类别,即分组条形图和堆叠条形图。此外,归一化堆栈条形图也可以如下制作。
作者图片
作者图片
作者图片
也可以绘制水平条形图,代码非常简单,但为了绘制不同的图,制作了两个支线图,然后改变标记和它的颜色以获得不同的外观。在第二个图表中,使用了 hlines 选项。
作者图片
现在,人口金字塔或年龄-性别-金字塔非常普遍。这是一个很好的并排比较方法。为了更好地理解该图,制作了新数据。
作者图片
同样,以下情节作了进一步探讨。虽然这不是人口金字塔,但任何两个阶层都可以通过这个图进行比较。在该图中比较了大于 30 和小于 30 的肿瘤大小计数。
作者图片
结论
这里探讨了不同类型的条形图,即子图、分组条形图、堆积和标准化堆积条形图、水平条形图、人口金字塔图。根据客观要求,选择正确有效地表示数据的图形是非常必要的。此外,根据目标,这些图表还需要数据准备。下面的参考文献可以做进一步的探索。
所有的代码都是这里。
感谢阅读!
参考资料:
- https://seaborn.pydata.org/generated/seaborn.barplot.html
- https://matplotlib . org/stable/API/_ as _ gen/matplotlib . py plot . bar . html
处理缺失数据的不同插补方法
处理数据集中缺失值的插补方法。
图片来自我的手机
目录:
当我们试图分析和理解我们的数据时,我们经常会遇到缺失值。这在现实世界的数据中很常见。由于数据可能损坏或某些收集错误,将会有丢失的值。缺少值会导致偏差,并会影响模型执行的效率。怎么才能解决这个问题?
有许多方法可以处理丢失的数据。在这篇文章中,我将讨论其中的一些。
让我们以下面的数据为例,以供进一步参考。
f1 有缺失值
2。什么是归罪?
插补是用替代数据替换缺失值的过程。这是作为预处理步骤完成的。
3。正常插补
在我们的示例数据中,我们有一个带有缺失值的 f1 特征。根据特征 f1 的数据类型,我们可以用以下方法替换缺失值。
- 平均
- 中位数
- 方式
如果数据是数值型的,我们可以用均值和中值来代替,否则如果数据是分类型的,我们可以用众数,这是一个经常出现的值。
在我们的例子中,数据是数字,所以我们可以使用平均值。请注意,只有 4 个非空单元格,因此我们将只取 4 的平均值。
平均替换
4。基于类别标签的插补
这里,我们不采用特征中所有值的平均值、中值或众数,而是采用基于类的值。
取特征 f1 中属于类别 0 或 1 的所有值的平均值,并替换缺失值。中位数和众数也一样。
基于类别的插补
5。基于模型的插补
这是一种处理缺失数据的有趣方式。我们将 feature f1 作为类,将所有剩余的列作为特性。然后,我们用任何模型训练我们的数据,并预测缺失值。
训练数据
测试数据
这里,我们有特征 f1 中缺失值的训练数据和测试数据。让我们使用 K-最近邻算法,并且取 k=2 来训练我们的模型,因为它简单并且使用邻域概念。
缺失值将表示为 NaN,而不是数字。
6。创建缺失值特征
除了对特征进行插补,我们还可以创建新的相应特征,这些特征具有二进制值,表示特征中的数据是否缺失,0 表示不缺失,1 表示缺失。我们这样做是为了记录,缺失值也是有用信息的来源。
7。结论
在这篇文章中,我们讨论了不同的插补方法,我们可以使用这些方法来处理缺失数据。处理的方法有时可能是一般的/直观的,也可能取决于我们必须咨询领域专家才能继续进行的领域。
8。参考文献
[1]缺失值:https://en.wikipedia.org/wiki/Missing_data
[2]插补:https://en . Wikipedia . org/wiki/attumption _(statistics)
SQL 中数据透视表的不同方法
汇总数据的数据透视表和案例指南
JESHOOTS.COM在 Unsplash 上拍照
数据专业人员经常查看事务性数据,并需要一个数据透视表来进行进一步分析。例如,银行专家可能需要审查每笔交易以确定某些账户如何结算,或者销售分析师可能需要审查单笔交易以确定某些产品的销售情况。
数据透视表是数据操作的一项基本技能,通常是更广泛分析的第一步。因为它非常基础,主要的工作表应用程序提供了函数来创建数据透视表,但是它们依赖于完整的数据集。
在许多大数据应用程序中,分析工作表中的数百万行是不可行的。虽然试图通过您最喜欢的编程语言和它的库来处理这些数据可能会奏效,但是从一开始就不查询这么多数据会更有效。相反,可以在 SQL 的查询级别上创建数据透视表。
数据
为了演示如何创建数据透视表,将生成一个显示事务数据的简单数据集。
CREATE TABLE transactions(
id INTEGER PRIMARY KEY,
client_id INTEGER,
value INTEGER
);
在这种情况下,事务表包含三个字段:每个事务的唯一 ID、与事务相关联的客户端的客户端 ID 以及事务的值。
示例数据
为了简洁起见,只生成了五行随机数据,但是实际的生产数据库可以轻松地容纳数十万行(如果不是数百万行的话)事务记录。
查询应该能够返回每个客户端 ID 的交易总值,而不是提取整个数据集。
枢纽功能
PIVOT 函数是迄今为止创建数据透视表最直接的方法。从字面上看,它是为数据透视表设计的一个实用程序,语法应该易于理解。
SELECT *column* AS *column_alias*,
*pivot_value1, pivot_value2,...pivot_value_n*
FROM
*source* AS *source_alias*
PIVOT (
SUM(*aggregate_column*)
FOR *pivot_column*
IN (*pivot_value1, pivot_value2, ... pivot_value_n*)
) AS *pivot_alias*;
查询从 SELECT 语句开始,其中的列是数据透视表中第一个出现的列。下面的 pivot_values 是将要进行透视的值。FROM 指定查询的来源。
然后,PIVOT 函数关闭查询。在其中,聚合函数(在本例中为 SUM)接受要聚合的列。FOR 子句指定它所透视的列,最后 IN 接受一个 pivot_values 的列表。
虽然上面的代码是抽象的,但下面提供了一个实际的例子:
SELECT 'TotalValue' AS TotalValueByClient,
[1], [2]
FROM (
SELECT client_id, value FROM transactions
) AS Source
PIVOT (
SUM(value)
FOR client_id
IN ([1], [2])
);
SELECT 子句指定第一列将被指定为“TotalValue ”,其标题为“TotalValueByClient ”,第一列将作为标签而不是实际的数据列。方括号中的下列数字表示要聚合的客户端 id。
FROM 子句不会源自表,而是源自子查询。在这种情况下,子查询非常简单地从 transactions 表中提取客户机 ID 和值。
PIVOT 函数开始采用接受值字段的 SUM 函数,因为将计算每个客户端的每个值的总和。接下来,FOR 子句指定将透视客户端 ID,IN 子句指定将计算哪些客户端 ID。
根据上述查询创建的数据透视表
虽然 PIVOT 操作符提供了一个很好的资源来快速、轻松地创建数据透视表,但它并没有被广泛实现。大多数在微软产品中使用,那些使用其他数据库解决方案的人需要依靠其他方法来达到类似的结果。
CASE 函数
虽然其他数据库不使用 PIVOT 函数,但是可以在 SQL 查询中使用条件来轻松地重构它,尤其是使用 CASE 函数。
SELECT *pivot*_*column*,
SUM(
CASE
WHEN *pivot*_*column* = *pivot*_*value* THEN *aggregate_column*
WHEN *pivot*_*column* = *pivot*_*value* THEN *aggregate_column*
ELSE 0
END
) AS *alias*
FROM *table*
GROUP BY *pivot*_*column;*
在 SELECT 子句中,聚合函数(在本例中为 SUM)封装了一个 case 条件。基本的 pivot_column 将在每个 WHEN 子句中保持不变,但是每个 pivot_value 必须与生成摘要的每个值相关联。最后, aggregate_column 表示具有将被求和的实际数值的列。
为了可读性,SUM 函数中添加了一个别名,而 FROM cause 指定了数据源,它可以是表或子查询。最后的 GROUP BY 子句通过将 pivot_column 值组合在一起来最终确定数据透视表。
SELECT client_id,
SUM(
CASE
WHEN client_id = 1 THEN value
WHEN client_id = 2 THEN value
ELSE 0
END
) AS totals
FROM transactions
GROUP BY client_id
在这个特定的示例中,SELECT 子句接受客户机 ID 和 SUM 函数。CASE 语句列出了每个客户机 ID,实际上是告诉查询对于每个客户机 ID,取它们的总和。末尾的别名将这个新列命名为“totals”。最后,GROUP BY 子句将所有客户机 id 组合在一起。
当正确执行时,最终结果将返回一个数据透视表。
从上面的代码片段返回的数据透视表
在不牺牲太多可读性的情况下,使用 CASE 将创建一个有效的数据透视表,它将适用于几乎所有的 SQL 实现。值得注意的是,在这种情况下,与 PIVOT 函数相比,它会切换行和列。
结论
数据透视表是几乎每个数据专业人员的基本工具。虽然工作表和像 Pandas 这样的外部库可以创建它们,但是查询一个数据透视表需要更少的内存。Microsoft 产品提供 PIVOT 函数的目的是创建数据透视表,但是对于那些使用其他解决方案的人来说,巧妙使用条件和聚合函数可以产生相同的结果。
评估二元分类模型的不同度量和选择正确模型的一些策略
本文是一篇 综合概述 用于评估二进制分类模型的不同指标,以及为您的用例选择正确指标的一些策略。
介绍
作为一名数据科学家,关键是不要在真空中建立机器学习模型,你必须始终提出正确的问题并验证假设,以便为你的模型选择正确的评估指标。但是,如何选择正确的指标呢?在本文中,我们将在讨论了二进制分类模型的不同评估指标之后,尝试回答这个问题。
了解一些重要术语
讨论不同的评估技术
为您的模型选择正确指标的策略
了解一些重要术语
问题陈述
在开始概述指标之前,让我们先设置一个简单的例子来说明问题。让我们想象一下,你是一家旨在诊断病人的初创公司。你想看一看每个病人的健康信息,并确定相应的病人是否有心脏病。将有两种选择:
- 患者患有心脏病的情况,在这种情况下,他/她被标记为 1、是或真
- 患者没有心脏病的情况,在这种情况下,他/她被标记为 0、否、假
这种特殊的问题被称为二进制分类,您可以构建一个二进制分类器来执行它(如下所示)。
作者图片
让我们假设您已经在一些历史患者的数据集上训练了您的模型。现在,您希望对剩余的 300 名患者的数据进行模型评估,包括所有必需的输入及其相应的基础真值/标签。
让我们假设你要建立一个预测是或否的分类器;真或假,1 或 0。
该分类器可以在所有 300 名患者的数据上运行,计算每个人的预测,并最终将它们与实际情况进行比较。让我们假设模型预测:
- 150 的患者患有心脏病(是),而实际上患有心脏病(是),这被认为是真阳性
- 120 名患者没有心脏病(否),并且实际上没有心脏病(否),这被认为是真正的阴性
- 20 名患者没有心脏病(否),但实际上患有心脏病(是),这被认为是假阴性
- 10 的患者有心脏病(是),但实际上没有心脏病(否),这被认为是假阳性
混淆矩阵
所有这些预测结果都可以放入一个 2x2 的表格中,称为混淆矩阵。这个矩阵很好地总结了模型的正确和错误预测的数量。它还有助于计算模型的其他性能指标。
作者图片
在混淆矩阵中,数据集中的观察总数对应于 TP、FP、TN 和 FN 值的总和。
总观察值= TP + FP + TN + FN = 150 + 10 + 120 + 20 = 300
I 型和 II 型错误 混淆矩阵有时会有点混乱,尤其是在记忆 I 型和 II 型错误时,这两种错误在现实世界中可能会互换使用,分别指误报和漏报。我发现下面的图片是记忆它们的简单方法。
图片来自美国医师执照考试 (USMLE)
讨论不同的评估技术
从混淆矩阵中,我们可以生成不同的度量来评估模型的性能
准确(性)
该指标旨在回答以下问题:“在模型做出的所有预测中,它的正确率是多少?”。正确的预测对应真正的积极和真正的消极
精确
这是用来回答下面的问题:“所有的是预测中,有多少是正确的?”
重要的是,不要对模型的精度大于其准确性这一事实得出任何结论,因为这些指标完全不同,不具有可比性。
在这种情况下,模型做出了 160 个肯定的预测,其中 150 个是正确的。
回忆—敏感性
为了能够获得模型的敏感性,我们必须回答下面的问题:“模型在预测实际的肯定事件方面有多好?”,这可以看做是精度的翻转。
数据集包含 170 个“是”的观察值,模型正确预测了其中的 150 个。
回忆—特异性
这个指标非常类似于敏感度,可以通过这个问题的答案来计算:“这个模型在预测实际 NO 事件方面有多好?”。
在这种情况下,我们有 130 个 NO 观测值,模型正确预测了其中的 120 个。
F1 分数
当数据集中存在类别不平衡时,有时会使用此指标,这意味着数据集中一个类别/标签的数量远远多于另一个类别/标签的数量(例如,250 个实际是,50 个实际否)。它对应于精确度和召回率的调和平均值。
中华民国的 AUC
更早的时候,模型的输出直接要么是零(假),要么是一(真)。但是,许多分类模型(如逻辑回归)输出概率值,而不是 0 或 1。对于那些模型,基于一些阈值进行到 1 或 0 的最终转换。
例如,最终预测将是:
- 1 如果概率值高于阈值=0.5
- 否则为 0
作者图片
每个阈值的选择导致一个分类,从中我们可以导出相应的混淆矩阵。由于单个分类器可能有数千个阈值,这是否意味着我们必须为每个阈值建立一个混淆矩阵?既然如此,如何比较不同的量词呢?第一个问题的答案是否定的,这就是 ROC AUC 曲线的用处。
图片由统计如何由作者调整
我们可以把这条曲线的每一个点想象成有自己的混淆矩阵,它有自己的真阳性率和假阳性率值。因此,我们在遍历每个分类器的所有可能阈值后得到每条曲线。
因为真阳性率和假阳性率都在 0 和 1 之间,所以 AUC 也在 0 和 1 之间。然后,对于 AUC:
低于 0.5 的值表示模型非常差。
值为 0.5 意味着该模型并不比随机机会更好地预测结果。
值超过 0.7 表示模型良好。
超过 0.8 的值表示强模型。
值为 1 意味着模型完美地预测了那些将经历某种结果的群体成员和那些不会经历某种结果的群体成员。
从上面展示的这两个分类器,我们可以看到蓝色分类器下面的面积大于红色分类器的面积,因此,我们可以得出结论,蓝色分类器比红色分类器好。
为您的模型选择正确指标的策略
您知道如何使用不同的度量来评估您的分类模型,但是您应该为您的用例选择哪一个呢?这个问题的答案并不明显,因为它取决于分类器运行的上下文。一旦在生产环境中使用这些模型,就会产生假阴性和假阳性的成本,因此在与对这些成本有一定专业知识的业务合作伙伴讨论后,权衡这些假阳性和假阴性的成本是很重要的。
选择准确性
这是在以下情况下使用的指标:
- 假阳性和假阴性的成本大致相当
- 真正的积极和真正的消极的好处大致相等
例子 一个被训练来将一张图片分类为****大象与否的模型可能会用准确度来评估(假设没有类别不平衡),因为在这种情况下,假阳性和假阴性的代价似乎差不多,假设我们只想知道我的 Instagram 个人资料中的哪些图片是大象。
选择精度
这是在以下情况下使用的指标:
- 假阳性的代价比假阴性高得多
- 真正积极的好处比真正消极的好处多得多
示例 一个经过训练的模型将电子邮件分类为垃圾邮件或非垃圾邮件。在这种情况下,误报会将相应的电子邮件移动到垃圾文件夹中,用户可能不会再看到,或者可能很晚才看到。另一方面,假阴性会将垃圾邮件留在收件箱中,这可能会使收件箱有点拥挤,需要一些手动任务来删除垃圾邮件,但至少任何重要的消息都会被错过。
选择召回
使用灵敏度和特异性的目的是使假阴性和假阳性的数量尽可能低,因为我们可能希望增加模型的灵敏度。召回是在以下情况下使用的指标:
- 假阴性的代价比假阳性高得多
- 真正的负面影响的成本比真正的正面影响要高得多
例 在一个被训练用来识别患者是否患有心脏病的模型的情况下,假阴性可能代价非常高,因为它可能会延误患者的治疗,甚至可能导致死亡,因为快速识别疾病可能有助于更好的治疗。另一方面,假阳性可能只会导致额外的测试,这可能在经济上是昂贵的,但不像一个人的生命那样昂贵。
结论
我希望您喜欢这篇文章。如果您有任何问题或意见,我将很高兴欢迎他们进行进一步的讨论。
如果你喜欢阅读我的故事,并希望支持我的写作,可以考虑成为一名灵媒。每月支付 5 美元,你就可以无限制地阅读媒体上的故事。
请随时关注我的社交网络。讨论人工智能、人工智能、数据科学、自然语言处理和人工智能是一种乐趣!
理解疾病传播的不同类型的房室模型
如何通过数学透镜观察传染病的传播
来自美国疾病控制中心——1976 年埃博拉病毒分离株的电子显微镜图像
快乐、悲伤、健康、生病都是生命周期的一部分。我们大多数人都是病毒感染的受害者,无论是普通流感还是感冒,一生中至少有一次。
这绝对是一个奇迹,一个甚至不能在显微镜下看到甚至不能自我复制的微小颗粒如何能够进入任何生物,并利用生命形式的资源,复制成千上万的病毒,有时甚至对生物物种是致命的。
为了与这些感染作斗争,了解其原因、预防、控制方法并采取先发制人的方法是至关重要的。与其他实验不同,用传染病做实验是不道德的。另一方面,数学模型可以相当充分地解释疾病是如何传播的。
可以采用的数学模型的一种形式是房室模型。这篇文章着重于研究人员用来理解疾病在大量人群中传播行为的各种房室模型。
什么是房室模型?
分室模型是一个框架,其中一个系统被看作是一组分室。它研究这些隔间之间的元素流动,并试图理解其行为。
为了简单地理解这一点,让我们想象一个有三个房间的房子:一个厨房,一个卧室和一个浴室。这座房子是我们的系统,住在里面的人是我们的人口。隔间是三个房间。
在给定的时刻,一个人会在厨房、卧室或浴室,因为他们不能同时在两个地方。类似地,在隔室范式中,一个人在给定的时刻只会在一个隔室中。
正如人们可以在房子里从一个房间走到另一个房间一样,在这个模型中,他们可以根据一定的准则从一个房间移动到另一个房间。在模型中,几个数学参数将用于理解流动,微分方程将用于解释每个隔间中的人数如何随时间变化。
如果房子是封闭的,没有人进出,里面的人数保持不变;但是,如果人们开始离开和进入,里面的人数就会发生变化;然而,房子里的人数仍然可以很容易地计算出来,因为我们知道有多少人在那里,离开和进入。同样,如果系统是封闭的,我们假设人口规模保持不变,忽略出生、死亡和移民等因素。如果不是这样,我们也会考虑这些事件。
房室模型的使用?
那么,为什么我们要创建所有这些方程,并试图弄清楚流量如何随时间变化?简单地说,通过在流行病期间提供对控制措施的必要干预的洞察力,这可以大大有助于防止一个国家陷入混乱。
下图就是一个例子,它是一个称为 SIR 的房室模型的输出。传输速率是模型中使用的参数之一。它会显示特定传输速率下的最大感染次数大约在什么时候达到。这使得必要的当局能够确定他们是否有足够的卫生保健能力,或者他们是否需要通过实施封锁或隔离等基本措施来推迟或减少高峰。这可以用作旨在使曲线变平的干预的起点。
来源:作者图片
如果你需要更多地了解这一点,下面的文章涵盖了 SIR 模型和每个微分方程是如何建立的,以及可能从模型中推断出的各种解释,以帮助疾病控制。
这篇文章的目的是给出各种合作模式的一个总的概述,以及它们通常在什么时候被使用。在这方面已经做了很多研究,不同的人根据感染的类型和当时所需的控制措施改变了它们。
不同类型的隔间
让我们从一个非常基本的模型 SI 开始。这里只有两个部分:易感者和感染者。该模型可用于传染性疾病,如巨细胞病毒,其中没有完全恢复状态。
如果疾病开始时既有易感者又有感染者,随着时间的推移,易感者被感染,那么可接受区室中的人数就会减少(-βSI),因为他们会转移到感染区(+βSI)。
来源:作者图片— SI 模型
当一种无法治愈的疾病随着时间的推移进入休眠状态时(如果γ是速率,那么 I 区室将减少γI),患者变得不具有传染性。因此,它们可以被重新安置到易感区室。这种模式被称为 SIS。
来源:作者图片-SIS 模型
上面是一个简单模型及其衍生结构的图示。文献中使用的各种类型的隔室有许多扩展,如下。
- Recovered (R)
如果一个感染可以被恢复,将会创建一个名为 Recovered 的新隔离专区。 - 暴露(E) 当一个易感者与一个感染者相互作用,暴露于病毒但还没有传染性时,一个称为暴露的新区室被添加到系统中。
当这些个体被识别时,可以对他们进行密切监控,并且可以控制相对于其他隔间的暴露率。如果随着时间的推移,暴露似乎过高导致高感染率,政府将有必要采取严格控制的事先迹象,如封锁或检疫立法。 - Quarantined(Q) 有些车型有一个单独的隔间叫隔离区,如果疾病控制需要这样的措施。
识别隔离区随时间的变化,可以识别传染率控制的需要。如果对这一动态的理解可用于将隔离和感染率控制在最佳水平,则隔离和卫生保健能力可得到管理,这可导致死亡率降低,即使没有 ICU 床位容量等,人们也不会死亡。 - 接种过的(V)
如果有疫苗可供免疫的人,那些已经接种过疫苗的人将被重新安置到接种过疫苗的隔间。有时可以通过使用亚群模型来确定免疫接种对发病率或死亡率的有效性。这可能有利于为特殊群体优先接种疫苗。 - 警告(W)
如果疫苗的免疫增加只是短暂的,那么在文献中有各种模型使用标记为“警告”的隔室用于待转移的人。这将对及时接种疫苗和一个国家无限期控制疫情的能力有极大的帮助。 - 死亡(D)
如果感染是致命的,死亡的和康复的将被区分,并且可以制定政策来区分不同组的优先次序或使曲线变平,以通过与其他间隔平行分析来提供足够的医疗保健来降低死亡率。 - 母亲(M)
婴儿出生时对某些疾病具有免疫力,如麻疹,由于母体抗体的存在,婴儿在最初几个月仍会如此。因此,当在这种情况下建模时,还包括母体隔间。
来源:作者提供的图片——不同隔间模型的示例
上面的例子只是我遇到的文献中的一小部分。还有一些扩展,包括二次暴露的组合和其他亚组的模型,如地理区域、年龄组等。
结论
如前所述,这些模型可以针对大规模人群开发,并针对不同的感染类型和情况进行独特开发。
如果像比率这样的特征被适当地建模用于大量人群,那么这些模型所带来的洞察力将是无价的。这些类型的建模练习将使当局能够在危机中领先一步,因为他们将能够看到在各种情况下事情将如何发展。
此外,洞察力将有助于及时决策,或许可以拯救生命,减少因决策不当而导致的不必要的经济崩溃。
参考
- https://en.wikipedia.org/wiki/Flattening_the_curve
- https://en . Wikipedia . org/wiki/compartment al _ models _ in _ epidemiology
- 【https://en.wikipedia.org/wiki/Virus
计算 R 中唯一值的不同方法
r 通过各种方法使困难的问题变得更容易,尽管有时它也会使简单的问题变得复杂。这里有一个简单的例子来说明
介绍
我最初是 Python 用户。我在日常工作中使用 Python 来探索、可视化和分析数据。然而,目前,由于我正在攻读统计学硕士学位,所以需要学习 R。由于 R 是一个开源程序,由不同的开发人员和统计人员提供了大约 10,000 个库和软件包,因此它缺乏针对特定问题开发算法的一致性。因此,起初,我发现很难熟悉 R 语法及其解决各种问题的方法。在本文中,我将向您展示一个简单的例子,说明 R 中不同的库是如何聚合值的
安装软件包
我们将需要几个包,其中包括 dplyr 和 data.table.
#Installing Packagesinstall.packages("dplyr")
install.packages("data.table")#Loading Packages
library('dplyr')
library("data.table")
资料组
我将使用从 Kaggle 下载的杂货店数据集。作为参考,您可以从以下链接下载数据:
https://www.kaggle.com/heeraldedhia/groceries-dataset
让我展示一些数据信息。
图 1:数据表概述
图 2:数据描述
我将把数据表的日期转换成新的格式 YYYY-MM,并将日期格式的新列存储为 month_year
DF1 <- DATA
DF1$month_year <- format(as.Date(DF1$Date, format = "%d-%m-%Y"), "%Y-%m")
按组计算单个变量
计算每个月-年中唯一客户的数量
这个计算有几种解决方案,我将向大家展示一些。
- 使用基数 R 的 aggregate()函数计算唯一值
group_by_month <-
aggregate(x = DF1$Member_number, # Specify data column
by = list(DF1$month_year), # Specify group indicator
FUN = function(x) length(unique(x))) #Desired function
或
group_by_month1 <-
aggregate(Member_number ~ month_year, #Data column group by period
data = DF1,
FUN = function(x) length(unique(x)))
2 。使用 group_by()计算唯一值&总结 dplyr 包的函数
group_by_month_2 <-
DF1 %>% #Applying group_by &summarise
group_by(month_year) %>%
summarise(unique_customer = n_distinct(Member_number))
3。使用 length() & unique()函数& data.table 包计算唯一值
group_by_month_3 <-
data.table(DF1)[ , .(unique_cus = length(unique(Member_number))),
by = month_year]
3 种不同方式的结果与下图中前 5 行的结果相同:
图 3:不同时期的不同客户
按组计算多个变量
计算每个月-年商店的条目数、独特顾客数和购买的独特产品数
- 使用 group_by() &总结 dplyr 包的功能
group_by_month_2a <-
DF1%>%
group_by(month_year) %>%
summarise(unique_customer = n_distinct(Member_number),
number_of_entries = n(),
unique_product = n_distinct(itemDescription))
2。使用 length() & unique()函数&包 data . table
group_by_month_3a <-
data.table(DF1)[, list(unique_customer = n_distinct(Member_number),
number_of_entries = .N,unique_product = n_distinct(itemDescription)), by=month_year]
最终,两者都会产生相同的结果:
图 4:生成结果的前 5 行
结论
由于支持许多不同的包,R 为用户开发不同的算法和模块来解决问题提供了优势。r 通过各种方法使困难的问题变得更容易,尽管有时它也会使简单的问题变得复杂。作为一个 R 初学者,我发现这些不同的包和语法很难掌握。
尽管有这种困惑,我仍将努力学习 R,因为它是一种有效的统计工具,当然它将有利于我在:D 研究生院的成绩
不同方式过滤熊猫数据帧
吉姆·卡利加斯在 Unsplash 上的照片
介绍
子集选择是数据操作中最常执行的步骤之一。到目前为止,Pandas 提供了许多不同的方法来过滤您的数据帧,以获得您选择的数据子集。在本文中,我将向您展示一些我在操作数据时遇到最多的案例。
在讨论细节之前,我将首先创建一个示例数据框架。
#Create a simple dataframedf = pd.DataFrame({
'name' : [ 'Chi', 'Alex', 'Sam', 'Hoang', 'Linh', 'Dung', 'Anh'],
'function' : [ 'Marketing', 'Tech', 'Tech', 'Finance', 'Finance', 'Marketing', 'HR'],
'address' : [ 'Hanoi', 'Saigon', 'Hanoi', 'Saigon', 'Hanoi', 'Hanoi', 'Saigon'],
'gender' : ['F', 'M', 'F', 'M', 'M', 'M', 'F'],
'favourite_fruit' : [ 'apple', 'banana', 'orange', 'watermelon', 'grape', np.NAN, 'kumquat'],
'age' : [20,25,21,26,30,31,23]
})
下面是我的 df 数据帧的概述:
图 1:我的示例数据框架
基于多个条件选择行
有几种方法可以根据某些特定条件选择行的范围。
#Using dataframe method
df[(df.age >=25) & (df.address == 'Hanoi')] #Using query function
df.query('age >= 25 & address == "Hanoi"') #Using loc function
df.loc[(df.age >= 25) & (df.address == 'Hanoi')]
所有这些方法产生相同的结果如下:
图 2:结果
使用 loc,iloc 选择一系列行
在本节中,我们将了解使用 loc 和 iloc 过滤数据帧的几种方法。
使用 loc 选择一系列行
df.loc[0:3]
输出:
图 3:使用 loc 选择行的范围
使用 iloc 选择一系列行
df.iloc[0:3]
输出:
图 4:使用 iloc 选择行的范围
为什么 df.loc[0:3]返回 4 行,而 df.iloc[0:3]只返回 3 行?
如您所见,使用 loc 和 iloc 的结果是不同的。这种差异的原因是由于:
- loc 不基于索引位置返回输出,而是基于索引的标签。
- iloc 根据索引中的位置选择行。这也意味着 iloc 只能处理整数。
更多参考可以参考下面的例子。在本例中,我将对列‘name’设置索引,这样您可以更清楚地理解用法和我的解释。
df4 = df.copy()
df4.set_index('name', inplace = True)
输出:
图 5:将索引设置为“name”列
现在,让我们再次尝试上面的例子,看看 loc 是如何处理索引标签的。
index = ['Chi', 'Sam', 'Hoang']
df4.loc[index]
输出:
图 6:输出
选择带条件的必需列
如果有数百列,而您只想选择其中的几列,您可以在 loc 语句中的条件后添加一个列列表。
在下面的例子中,我将尝试获取年龄≥ 25 岁的人的 【姓名】【性别】 和 【地址】 【地址】。
df.loc[(df.age >=25), ['name','gender','address']]
输出:
图 7:输出
同时选择行和列
使用 loc,iloc 选择所有行和一些特定列
#Using iloc
df4.iloc[:,[1,2,3]]#Using loc and column names
df4.loc[:, ['address','gender','favourite_fruit']]
输出:
图 df4 的所有行和第 2、第 3、第 4 列
使用 loc,iloc 选择一系列特定的行和列
#Using iloc
df4.iloc[2:6,3:4]#Using loc
df4.loc['Sam':'Dung',['favourite_fruit']]
输出:
图 9:输出
使用 loc,iloc 选择不连续的行和列
#Using iloc
df4.iloc[[1,3],[2,4]]#Using loc
df4.loc[['Alex','Hoang'],['gender','age']]
输出:
图 10:输出
使用 get.loc 和 index 方法选择行和列
column_start = df4.columns.get_loc('address')
column_end = df4.columns.get_loc('favourite_fruit')
df4.iloc[1:4,column_start:column_end]
您也可以使用执行类似的任务。锁定方法。这两个操作产生相同的结果。
row_start = df4.index[1]
row_end = df4.index[3]
df4.loc[row_start:row_end, 'address':'gender']
输出:
图 11:使用 get.loc 和索引方法
选择数据框架中的单个元素
选择数据集中的单个元素有几种方法,包括 loc , iloc , iat 和 at 方法。
看看 df4 的数据,找出 Hoang 最喜欢的水果是什么?
row_position = df4.index.get_loc('Hoang')column_position = df4.columns.get_loc('favourite_fruit')#Using iloc
df4.iloc[row_position,column_position]#Using iat
df4.iat[row_position,column_position]#Using loc
df4.loc['Hoang','favourite_fruit'] #Using at
df4.at['Hoang','favourite_fruit']
基本上,以上 4 种方式检索到的是同一个输出,即‘西瓜’。但是,这些方法的运行速度略有不同。因此,IAT方法比 loc 和 iloc 方法要快一点。您可以看看下面的图 12 以获得更多参考。
图 12:不同方法的速度
结论
以上是我在做数据过滤时的一些小技巧。虽然它很简单,但我希望它能成为您未来工作的有用参考来源。
掌握量子机器学习的不同方法
我艰难地学会了量子机器学习。但是有更好的方法
本帖是本书的一部分: 用 Python 动手做量子机器学习 。
这是一条艰难的路
我在大学里没有上过量子计算课。更不用说量子机器学习的一门课了。在那个时候,无论如何都不会很有趣。21 世纪初,量子计算刚刚开始从纯理论走向实验室评估。这是理论物理学家和数学家的领域。
当时,我甚至没有听说过它。当我第一次听说量子计算的时候,我想是在 2008 年左右,研究人员已经成功地纠缠了量子比特,并且能够控制它们。当然,当我听说物理上分开的两个粒子可以共享一种状态,从而可以通过观察一个粒子的状态来改变另一个粒子的状态时,我想到了星际旅行般的运输。
作者弗兰克·齐克特的图片
然而,直到 2014 年前后,我都没有太在意。我正忙着写我的博士论文,关于评估软件开发项目中需求所引起的努力。当我回到正常生活的时候,我正好及时地经历了第二个 AI 冬天的结束和实用机器学习的来临。迄今为止的理论现在变成了现实。
当我进入机器学习领域时,这个领域已经相当成熟了。Scikit-Learn、后来的 Keras、TensorFlow 和 PyTorch 等库使得机器学习算法的开发变得很方便。尽管我最喜欢的书是后来才出版的,但是已经有很多好书和学习资料了。
注 :我最喜欢的书有:aurélien géRon 2017 年发布的hand-On Machine Learning with Scikit-Learn 和 TensorFlow 和 Francois Chollet 年发布的Deep Learning with Python。
但是我们今天开发的模型变得越来越难以训练。Open AI 的 GPT-3 模型使用深度学习来产生类似人类的文本,这需要在单个 GPU 上运行 355 年,并花费 460 万美元来训练。很难相信即将到来的里程碑可以经典地达到。
这一见解将量子计算带回了我的关注点。量子计算有望大幅降低某些算法的计算复杂度。它承诺在几秒钟内解决经典计算机需要几千年才能完成的任务。它承诺防止我们进入下一个人工智能冬天,这将是由于无法达到机器学习的下一个里程碑而导致的。
作者弗兰克·齐克特的图片
2018 年开始深耕量子机器学习。我只能找到一些科学论文和一些理论书籍。这些并没有涵盖量子机器学习,而是一般的量子计算。我对每一小块都很满意。
看完这些量子计算的出版物,剩下我一头雾水。大多数论文都很重数学,并且假设你熟悉很多物理术语。我甚至找不到一个合适的起点或者一些关于如何组织我的学习努力的指导。
失败的尝试让我沮丧,我花了几个小时在谷歌上搜索。我搜寻量子教程,却一无所获。
我可以清楚地看到量子计算对于机器学习的潜在价值。然而,我看不出量子计算的所有这些部分是如何结合在一起的。入门级的材料很难找到。实用指南根本不存在。我想开始,但我的努力没有任何表现,除了桌子上一叠我几乎看不懂的量子计算论文。
最后,我求助于先学习理论。我听说过 Qiskit,它是 IBM 为 Python 开发的量子 SDK。当时,它的文档相当糟糕,特别是如果你不熟悉所有的物理术语和它的基本理论。但它让我体验到叠加、纠缠和干涉在实践中意味着什么。
这种实践知识使我能够将量子计算与我从机器学习中了解到的算法联系起来。我通过无数次试错实验、无数次熬夜和大量的耐力,找到了量子机器学习成功的方法。
我真的相信,煞费苦心地将所有事情分解成小块对我理解量子机器学习的方式产生了影响。不过,我建议不要走同样的路。
有更好的方法
我个人的收获是:
- 在开始应用之前,你不需要死记硬背所有的理论
- 你不需要处理大量的方程
- 掌握量子机器学习不需要成为数学家
- 你不需要成为物理学家来理解量子机器学习
- 作为程序员、工程师、数据科学家或任何其他职业,你都会做得很好。
- 但是量子机器学习的教学方式是错误的
当我开始研究量子机器学习的量子部分时,我深入研究了理论和数学。因为这是大多数量子计算资源所关注的。
当然,理解基础数学和理论是可取的。但更重要的是,你需要理解这些概念在实践中意味着什么。如果你知道你能做什么,你需要怎么做,你就不需要一直考虑它(物理上)是如何工作的。
不要误解我。在量子机器学习中,物理和数学很重要。但是如果你不使用理论知识并将其应用于解决现实世界的任务,那么你将很难在量子机器学习世界中找到自己的空间。你需要从一开始就成为量子机器学习的实践者。
与我刚开始工作的时候相比,今天有相当多的可用资源。但是它们中的大多数属于以下类别之一
- 有大量方程的理论论文证明了某种算法的量子加速。然而,它们没有显示任何代码。
- 关于量子计算的教科书通常以可理解的方式解释概念。但是他们没有展示如何使用它们来达到某个目的。
- 博客文章向您展示了代码中的实际算法。但是他们没有把代码和任何潜在的概念联系起来。你看这很有效。但是你不知道它为什么和如何工作。
我绝不想说这些资源不值得一读。但是这些资源对于学习如何应用量子机器学习是没有用的。对于一个刚刚开始量子机器学习的人来说,你需要投入大量的时间和精力,但几乎没有实际回报。
理论和实践之间存在着根本性的脱节。我想用 来填补这个空白,用 Python 来实践量子机器学习,这样你就能以更高效、更好的方式学习。
这是我刚开始研究量子机器学习时就希望拥有的书。在这本书里,你会找到带有大量代码的实践演练和实践教程。这本书及时介绍了新理论,你需要它来采取下一步行动。你会学到很多理论。但你并不孤单。我们直接运用新学到的知识来解决实际问题。
我们不仅会实现不同的量子机器学习算法,比如量子朴素贝叶斯和量子贝叶斯网络。但是我们也用它们来解决来自 Kaggle 的实际问题。
当你读完这本书的时候,你会知道这些算法,它们做什么,你为什么需要它们,它们如何工作,最重要的是如何使用它们。
用 Python 实践量子机器学习 力求在教科书教授的理论和你实现现实世界解决方案所需的实际动手知识之间达到完美平衡。
这本书是你入门量子机器学习的全面指南”——将量子计算用于机器学习任务。
免费获取前三章点击这里。
可微发电机网络:导论
深度学习基础
VAEs、gan 及其挑战介绍
图片来自https://thispersondoesnotexist.com/(开源)
介绍
训练生成模型比训练分类器或回归器要困难得多。当在监督学习问题中训练分类器时,我们知道训练示例的输入和输出之间的最佳映射。然而,训练生成模型涉及优化标准,这是棘手的。
在本文中,我将介绍生成模型的基础知识,以及两个核心可微分生成网络的功能。这些是变分自动编码器(VAEs)和生成敌对网络(GANs)。
生成模型
在进入可微分的发电机网络之前,我们可以回顾一下生成模型。
生成模型的最终目标是生成与训练数据尽可能相似的合成数据。总之,这都是关于在我们的训练数据 p(x) 上建模一个概率密度函数。给定一组训练数据,生成模型试图构建一个函数,该函数允许我们确定任何数据点属于训练集的可能性有多大。这种密度允许生成模型做有趣的事情,比如评估新生成的数据点 p(x|y=y)的概率,或者从密度中采样以生成新数据。
生成模型:GMM
高斯混合模型是一种非常简单的生成方法。GMM 通过期望最大化来学习,通过迭代地评估对数似然来最大化模型的性能。在以后的文章中,我将更详细地介绍他们的学习。
GMM 假设数据的分布由一组高斯分布组成。他们通过在训练数据上拟合 K 个高斯 pdf 来近似训练数据空间,有点像 k-mean 聚类,但是在每个聚类上拟合高斯分布,而不是识别类成员。通过一组高斯分布来近似训练数据空间,我们获得了以下优点:
- 您可以将新样本的概率表示为 K 个高斯分布的加权和
- 您可以对分布进行采样,以生成属于每个分类的数据
生成模型的全部要点是在我们的训练数据空间 p(x) 上构建我们的密度函数。GMM 是通过一组高斯密度来近似这个潜在空间的。通过一组高斯近似潜在空间是有利的,因为它允许我们评估该空间中的任何新样本,我们可以用它来了解新数据样本是否很好地符合我们现有的数据。此外,通过具有完全定义的潜在空间,可以对每个聚类进行采样,生成新的合成数据。
可微分发电机网络
当生成发电机网络时,我们想要可微分的模型,学习潜在变量 Z (来自随机分布)到我们的数据分布 X. 之间的映射
可微分生成器网络假设每个样本携带的信息可以在比数据空间更简单的空间中表示。即使数据生活在高维空间(比如高分辨率图像),图像中的信息也可以用更简单的流形来表达。假设我们有手写数字的图片,即使训练数据存在于高维空间中(图像可能是 32×32 像素),每个图像背后的潜在信息也要小得多。所有可能的手写数字的空间比所有可能的 32×32 像素图像小得多。
生成模型在潜在变量 Z 上构建低维潜在空间,然后使用它来近似训练数据空间。有两种主要的方法来利用这个潜在空间产生新的数据:
- 将潜在变量 Z 转换成新样本 X(来自 p(x)的近似样本)
- 将 Z 转换为 X 上的分布(生成一个函数来逼近 p(x ),然后从中采样)
因此,生成模型要么生成新样本(从 Z 生成 X),要么生成分布,其中该子空间中的所有样本都代表训练数据(从 Z 生成 X 上的分布)。
根据您使用的创成式模型,模型将以上述两种方式之一生成数据。在本文接下来的部分中,我将讨论具体的模型架构,以及如何使用这些架构来生成合成数据。
可变自动编码器
优化阀门
GMM 和 VAEs 都试图在训练数据 X 上建立一个分布,然后可以对其进行采样以生成新数据(从 Z 生成 X 上的分布)。然而,vae 并不在我们的训练数据 X 上构建直接的 PDF,相反,它们利用前面陈述的假设,并且学习我们的潜在变量 Z 和我们的训练数据 X(其中 Z 中的潜在空间比我们的数据 X 的空间简单得多)上的分布之间的映射。
在 VAEs 中,我们选择一个非常简单的潜在变量 Z 的分布(通常是正态分布),形式为 N(0,I)。通过这样做,VAEs 假设数据的变化是高斯型的。以前面的手写数字为例,如果您想到所有可能的手写“3”,所有可能的手写“3”之间的变化将大致为高斯型。实际上,这通常是一个很好的假设,因为传感器数据中的噪声通常是高斯噪声。
我们希望找到数据的分布 p(x) 来最大化训练数据的概率。根据概率乘积和求和规则,我们可以将 p(x) 表示如下:
其中 p(x|z,θ) 是后验概率(给定我们的潜在样本 Z 和我们的模型参数θ的情况下,我们的训练数据 X 的概率),而 p(z) 是先验分布,我们假设该先验分布是具有零均值和恒等协方差矩阵的高斯分布。
目标是找到最大化该概率的一组权重θ,最大化 p(x) 给出:
由此产生的表达式称为 ELBO(证据下限),理解其术语非常重要。
第一项是给定我们的潜在变量(我们希望最大化这个变量)时,我们的数据的概率的期望值。这告诉我们,在给定 z 的情况下,模型是否正在产生 X 的良好样本。当假设后验概率为高斯时,最大化此项实际上相当于最小化自动编码器的均方误差。
第二项是后验概率和先验概率之间的 Kullback-Leibler 散度。最小化这一项本质上是试图使我们的后验概率尽可能接近均值为零且 sigma 为 1 的高斯分布。同样,我们假设数据中的变化是高斯型的,因此我们试图迫使后验概率朝着这个形状,同时仍然试图产生代表我们训练数据的良好样本。
VAEs 架构
作者图片
VAEs 的结构分为两部分,编码器和解码器。编码器将输入数据的维度降低到二维。这些维度用于参数化我们的潜在空间 p(z)的正态分布。然后,可以对该分布进行采样,这些样本被用作解码器的输入。解码器试图重建训练数据空间中对应于潜在空间中的样本的数据点。
因此,VAEs 将输入数据映射到 z 上的一个潜在空间。然后,解码器将该潜在空间映射回 x 上的图像空间。对潜在分布 p(z) 进行采样,并将输入通过解码器基本上允许我们从 p(x)进行采样。这是我们的目标!
当训练 VAEs 时,由于这些自始至终都是可微分的函数,通过使用上面定义的 ELBO 损失函数,可以像任何其他神经网络结构一样,通过反向传播和您喜欢的优化器来更新网络的权重。
生成对抗网络
甘斯理论
到目前为止,我们已经看到 VAEs 能够从潜在分布 p(z) 生成训练数据 p(x) 的分布。gan 生成数据的方式不同。他们不是生成分布,而是生成可能属于该分布的样本。
作者图片
GANs 由一个发生器和一个鉴别器组成。这是两个可微分的函数,一起作为一个大模型工作。这两种模式是对立的,每一种模式都想实现与另一种模式相反的目标,它们本质上是竞争的。生成器获取随机变量 Z,并将其映射到我们的数据空间 x 中的生成样本。鉴别器从训练数据和生成器生成的图像中获取图像,并尝试分类哪些图像属于训练集,哪些图像是假的。生成器的目标是骗过鉴别器,鉴别器的目标是正确识别生成的图像。
如您所见,这种生成图像的方式与 VAEs 生成图像的方式有着根本的不同。我们并没有产生一个密度函数 p(x) ,而是产生了类似于 p(x) 的样本。
这两个函数都是可微的,因此可以一起优化。它们像任何其他神经网络一样通过反向传播进行训练。我不会在这篇文章中讨论它们的损失函数,但也许在以后的文章中会讨论。
然而,gan 有一些问题:
图片来自https://thispersondoesnotexist.com/(开源)
众所周知,GANs 倾向于不稳定的训练,导致输出没有意义。看上面的图片,这些人是不存在的,是从 GANs 生成的样本。正如你所看到的,虽然这个模型很接近真实的人脸,但我相信你可以找出每张图片的一些问题。
GANs 很难训练,因为有四个主要障碍
- 你的 GAN 可能永远不会收敛到最优解。这是因为鉴别器和发生器之间的梯度可能具有相反的符号,因此您的参数可能会卡在鞍点。
- 没有什么可以阻止生成网络学习单一类型的数据。例如,如果生成动物的图片,模型可以学习只生成狗的图片而不生成其他的。
- 因为 GANs 生成样本,而不是生成分布,然后从中采样,我们失去了 Z 和 x 之间的平滑映射。这可能使 GANs 在某些情况下不如 VAEs 有用。
- 没有什么可以阻止生成器学习复制训练数据。
深度卷积 gan(dcgan)通过提出以下建议解决了其中一些问题:
- 人们不应该使用池层,发现这些层的性能比在生成器中使用步进卷积层更差。
- 卷积图层后使用批量归一化。
- 避免使用过多的密集层,而是使用更多的卷积层,使模型更深入。
- 发生器中应使用 ReLu 激活功能,鉴别器中应使用 LeakyReLu。
DCGANs 的论文是在一系列实验后得出这些结论的。自 1980 年以来,DCGANs 一直被广泛用于在各种领域生成数据。
结论
生成模型极难训练,因为训练中涉及的映射是难以处理的。生成模型要么学习生成新的数据样本,要么学习生成训练数据的分布。在本文中,我将介绍 VAEs(生成发行版)和 gan(生成数据样本)。我概述了这些是如何工作的,以及这些是如何产生数据的根本不同的方法。理解这一点将使您在选择应用哪个模型时做出更好的决定。
支持我
希望这对你有所帮助,如果你喜欢,你可以 关注我!
你也可以通过我的推荐链接成为的媒体会员,访问我所有的文章以及更多:https://diegounzuetaruedas.medium.com/membership
你可能喜欢的其他文章
可区分硬件
保罗·范·科特姆在 Unsplash 上的照片
业内笔记
人工智能如何帮助恢复摩尔定律的良性循环
在全球芯片短缺之后,据报道 TSMC提高了芯片价格和推迟了 3 纳米工艺。不管它是否准确或预示着一种长期趋势,这种消息应该提醒我们注意摩尔定律衰落的日益恶化的影响,并迫使我们重新思考人工智能硬件。人工智能硬件会受到这种衰退的影响,或者帮助扭转这种局面吗?
假设我们想恢复摩尔定律的良性循环,即软件和硬件相互推动,让现代智能手机比过去十年占据仓库的超级计算机更强大。普遍接受的后摩尔良性循环是不可持续的,即更大的数据导致更大的模型需要更强大的机器。除非我们重新定义并行性,否则我们不能再指望缩小晶体管来构建越来越宽的并行处理器。我们也不能依赖特定领域架构(DSA ),除非它促进并适应软件的进步。
我们不是要弄清楚哪个硬件是为 AI 这个前进的移动目标准备的,而是从 AI 的核心是可区分编程的角度来看待 AI 硬件。在这里,人工智能软件程序是一个计算图,由一起训练的计算节点组成,以实现端到端的目标。只要是可微分的,深度流水线 DSA 硬件可以作为计算节点。软件程序员可以自由地将可区分的硬件插入到计算图中,以获得高性能和创造性的问题解决方案,就像一个预先构建的可定制软件组件一样。人工智能硬件不再有“纯度”检查,现在可以包括可区分的硬件。
人工智能硬件不再有“纯度”检查,现在可以包括可区分的硬件。
希望软件和硬件将再次通过良性循环并行前进,就像摩尔定律如火如荼时一样。
人工智能硬件架构师的苦恼
在人工智能市场的众多 GPU 竞争者中,特斯拉推出了 Dojo 超级计算机。Dojo 似乎是联网、集成和可伸缩性方面的杰作。另一方面,D1 芯片,Dojo 的构建块,很难说是架构上的突破。我们可以将 GPU 竞争者分为两个阵营,众核和众 MAC。作为众核阵营的代表,D1 是由许多 CPU 内核组成的“网格”。另一方面,Tesla FSD 或 Google TPU 是多 MAC 阵营的缩影,拥有少量大型 MM 加速器,每个加速器都在“网格”中打包了许多乘法累加(MAC)单元正如我们所见,人工智能架构的争论是在网格和 GPU 之间进行的。
在制造芯片所需的巨大努力的压力下,人工智能硬件架构师必须紧张地跟踪媒体关于基准测试和会议的报道。旨在取代 GPU 的人工智能硬件经常难以运行基准测试和最新的神经网络模型,讽刺的是,在“老式”GPU 上运行得很好。如下图所示,众核和 GPU 在交换数据上有本质区别。前者通过互连网格传递数据,而后者通过内存层次结构共享数据。这个区别和 AI 关系不大。众核处理器,如 D1 芯片,最终是否会超过 GPU 还有待观察。我稍后将介绍许多 MAC。
比较众核(左)和 GPU(右)中的数据交换。(图片由作者提供)
现在,让我们快速回顾一下高性能计算(HPC)中网格和 GPU 的共同根源。
HPC 的遗产
HPC 用于解决计算密集型军事研究问题、科学发现、石油和天然气勘探等。超级计算机,简称为 Super,已经成为 HPC 的关键硬件解决方案。与处理指针丰富的数据结构(如树和链表)的通用程序相比,HPC 程序主要花时间在“循环”中重复数据并行计算。
向量超级的兴衰
从 20 世纪 70 年代到 90 年代,旨在通过展开向量中的数据并行循环来加速 HPC 程序的 Vector Super 主导了 HPC 市场。在那段时间里,一台超级计算机被默认为矢量超级计算机。
在 20 世纪 90 年代,当摩尔定律完全生效时,通过将许多现成的 CPU 排列成网状或某种类似的拓扑结构来构建超级计算机变得可行。这种趋势催生了分布式超级,HPC 社区怀疑地称之为黑仔微处理器的攻击,这里的“微”指的是微处理器。这种观点源于微处理器是片上 CPU,而“CPU”通常是由分立元件组成的系统。最终,分布式超级计算机取代了矢量超级计算机,成为今天超级计算机的同义词。
GPGPU 中向量超的回归
在 21 世纪初,摩尔定律显示出老化的迹象,这停止了对 CPU 时钟速度的争夺,CPU 时钟速度是单芯片计算性能的主要来源。业界的反应是在一个芯片上放置多个 CPU 内核,期望并行性成为新的主要性能来源。这一趋势导致了双核、四核,并最终导致了众核,实际上是分布式超级单芯片,通常将 CPU 内核排列成网状。众核的例子包括英特尔两次试图应对 GPU 的失败,Larrabee 用于 3D 市场,Larrabee 的后代 Xeon Phi 系列用于 HPC。
传统上,GPU 会在顶点、三角形和像素等图形实体上展开“循环”。GPU 架构师将这种能力扩展到了 HPC 应用中的循环,使 GPU 成为有效的矢量超级芯片。然后他们将 GPU 在 HPC 中的用法命名为通用 GPU (GPGPU)。致命的是,当 Vector Super 在 HPC 市场上让位于分布式 Super 时,它化身为 GPU 来报复它的对手。我们可以看到 GPU 在顶级超级计算机中的商业成功,如橡树岭国家实验室的泰坦超级计算机和瑞士国家超级计算中心的Piz Daint。
简单地
- 分布式超级计算将 Vector 踢出了 HPC 市场。
- 众核是分布式超级芯片,GPU 是面向高性能计算的矢量超级芯片。
矩阵乘法和人工智能
网格,计算机架构中的旧锤子,如何被重新命名和改造为人工智能的新钉子?
MM 和 HPC
计算机体系结构中一个永恒的规则是,移动数据比计算数据更昂贵,这使得计算机体系结构必须对更少的数据进行更多的计算。幸运的是,HPC 社区从几十年的实践中了解到,他们可以用 MM 来表达大多数 HPC 问题,MM 具有很高的计算与通信比率,粗略地说, N MAC 对 2 N 数据进行运算。如果实现得当,使用 MM 解决问题可以通过隐藏数据传输来实现高性能。所以一个 HPC 程序员只需要有一个超级计算机厂商提供的健全的 MM 库就可以了。当计算一个毫米时,今天的分布式超级可以充分利用分布在几十万平方英尺上的几十万个节点,有效地保持每个节点忙于计算。
MM 在 AI 的崛起
使用神经网络的机器学习(ML)是现代人工智能的特征。一个神经网络模型由多层最大似然核组成。在卷积神经网络(CNN)之前,最流行的神经网络(NN)是多层感知器(MLP)。MLP 的基本 ML 内核是矩阵向量乘法(MVM ),它对 N 个数据进行大约 N 个 MAC 运算,几乎不重用数据。另一方面,当前 CNN 的主要原语是张量卷积(TC)。正如我在文章《所有张量都暗暗希望自己是中解释的那样,“MM 和 TC 在数据移动和共享方面是结构等价的,所以我们经常互换使用张量和矩阵。
使用 MM 作为原语带来了 HPC 和 AI 的突破。CNN,主要使用 MM,引发了人工智能在计算机视觉上的突破。同样广泛使用 MM 的变形金刚,点燃了自然语言理解(NLP)的 AI 突破。
由于人工智能及其对 MM 的大量使用,计算机架构社区有了百年一遇的机会来专注于优化 MM 的锐利目标,同时对总体计算产生广泛影响——更物有所值。
众核可以运行为分布式超级计算开发的相同的 MM 算法。从某种意义上说,人工智能的众核可以追溯到它的 HPC 根源。
多 MAC 的潮流
脉动阵列于 1982 年推出,用于加速 MM 和其他应用。如果在人工智能背景下加速 MM 像今天这样酷,脉动阵列研究人员甚至不会为除了 MM 之外的应用而烦恼。脉动阵列是一种比 CPU 内核更密集地封装 MAC 单元的机制。然而,缺点是我们不能在其他地方使用 MM MAC 单元。由于缺乏通用性,脉动阵列没有得到市场的认可,直到人工智能成为 MM 的杀手级应用,促使谷歌在 TPU 采用它作为 MM 加速器。从那时起,市场已经衍生出各种变体来改进原来的版本。在这里,我将原始脉动阵列及其变体都称为多 MAC。为了处理非 MM 操作,Many-MAC 增加了配套处理器。
另一方面,众核中的 CPU 核心,如 D1 芯片或 GPU 着色器核心,可以使用小得多的多 MAC,有效地成为多 MAC 容器。
简单地
- AI 和 HPC 有交集,因为它们都由 MM 主导。
- 众核和众 MAC 并不比 GPU 更特定于人工智能。
域转移和特定域并行
暗硅和电源墙
2010 年后不久,业界意识到,将并行度(计算性能的主要来源)翻倍,CPU 内核翻倍,无法保持良性循环。每个 CPU 内核无法将其功耗降低一半,或者每瓦特并行度降低两倍。经过几次内核加倍的迭代,我们会看到在相同的功耗预算下,大多数内核仍然没有供电,从而导致暗硅,或者更准确地说,暗内核。如下面的概念图所示,当我们从 2 个内核到 4 个内核时,4 个内核中只有 3 个可以供电,当我们从 4 个内核到 8 个内核时,只有 4 个可以供电。最后,16 个内核中只有 4 个可以供电,从 8 个内核升级到 16 个内核没有任何好处。我们把这种现象称为碰壁。
由于这个原因,相当一部分计算机体系结构社区回避并行。此外,悲观主义者倾向于将并行性——贫瘠的指针丰富的计算作为主流,而将并行性——丰富的 HPC 作为利基。他们认为良性循环会过早地停止在阿姆达尔的天花板上,限制了并行性所能达到的程度。
深色硅或深色核心。(图片由作者提供)
人工智能拯救世界
巧合的是,艾正是在这种悲观情绪中出现的。根据斯坦福 AI 指数报告,AI 一直在前进,就好像力量墙不存在一样!
关键是主流软件中可能存在域转移,从而导致不同类型的并行性。如下面的概念图所示,当主流软件经历从指针丰富到数据并行计算的领域转变时,它将一个并行度重新定义为单指令多数据(SIMD)通道,而不是 CPU 核心。我们看到一条比 CPU-核心曲线更高的曲线(标记为数据并行的 SIMD 通道)。接下来,当主流软件进入 MM-heavy AI 空间时,添加了一条更高的曲线(标记为 MM-heavy 的 MM MAC),一个 MM MAC 代表一个并行度。正如我们所看到的,通过探索更高效的特定于域的并行性和提高 Amdahl 的上限,计算性能在电源墙后继续增长。
对了,MM-heavy AI 有自己的 Amadhl 的天花板。一个人工智能应用程序需要循环前端,以将 MM 操作分配给并行计算资源,并需要循环后端来收集串行操作(如规范化或 softmax)的结果。当有足够多的 MM MACs 来加速 MM 时,Amdahl 定律就会发挥作用,从而使循环前端和后端都成为瓶颈。
此外,随着摩尔定律的衰落越来越严重,制造更宽的机器来加速 MM 是否能维持人工智能的良性循环也成了问题。为了解决这个问题并进一步提高 Amdahl 的上限,我们需要执行新的域转移并探索新的特定于域的并行性。换句话说,我们需要添加一条新的曲线(???)到下面的概念图。
不同域转换的并行性扩展概念图。(图片由作者提供)
简单地
- 通过从指针丰富、数据并行到毫米级繁重计算的领域转换,我们一直呆在电源墙后面。
下一次域转移
差分编程
英特尔的 Raja Koduri 说:“神经网络是新的应用。我们看到的是,每一个插座,[无论是 CPU,GPU,[或] IPU,都会有矩阵加速。”
特斯拉的 Ganesh Venkataramanan 将他们的 D1 芯片描述为一台“纯粹的”ML 机器,运行“ML 内核”,没有遗留硬件。或许,他暗示 GPU 不像 D1 那样纯粹,因为它在人工智能处理期间有图形专用硬件闲置。
以上两种意见提出了两个问题。AI 定义域转移是否应该止步于加速矩阵乘法?AI 硬件是否排除遗留的特定领域设计?
现在,我们从人工智能的角度探索人工智能硬件的不同观点,人工智能的核心是可微分编程 (DP)。AI 软件程序是一个计算图,如下图所示,由参数化的计算节点组成,每个节点将输入作为上游节点的输出,并将计算输出提供给下游节点。我们通过“训练”确定所有计算节点的参数,首先使用最终输出计算端到端损耗,然后计算该损耗的输出梯度。它进一步使用标准的微积分链规则重复计算中间梯度,遵循输出的相反方向。
DP 只要求一个计算节点是可微分的,允许与所有其他节点的联合优化,以通过梯度下降来最小化端到端损失。计算节点的可微性使其能够维持从其下游到其上游邻居的反馈路径,从而完成端到端的反馈回路。在 DP 下,计算节点不一定是传统的“ML 内核”计算图可以是异构的,以包括非 ML 软件和硬件节点,只要它们满足可区分性要求。
计算图表
我们在下面展示了一个计算图的概念图。
概念计算图(图片由作者提供)
使用参数 w 计算输出 y 给定输入 x 的计算节点评估并记忆用于计算输入梯度的输出/输入微分。蓝色曲线虚线表示反馈路径将输入梯度传播到上游节点。如果需要,它计算并记忆输出/参数微分,用于计算参数梯度以调整参数。让我们看一些例子。
可区分的图形在环
越来越多的神经网络模型具有异构计算节点,符合可微规划的定义。很好的例子就是那些解决逆图形问题的例子。与从 3D 场景参数生成 2D 图像的正向图形相反,反向图形从 2D 图像恢复场景参数。新兴的基于人工智能的反向图形解决方案通常包括可区分的图形渲染器,这不同于传统的渲染器。它将梯度反向传播到上游节点,参与梯度下降以最小化端到端损耗。具有可区分图形在环的反向图形管道的强大之处在于使反向图形“自我监督”,如下图所示。
作者图片
重建神经网络从真实世界图像获得场景参数,而可微分图形从场景参数渲染虚拟世界图像。相同的两个下游神经网络准备真实世界和虚拟世界图像,以计算它们之间的端到端损耗。如果循环中没有可微分的图形,我们必须为场景参数准备 3D 地面事实。相反,真实世界的图像有效地充当了虚拟世界图像的基础事实,使得该过程自我监督。
当前的可区分渲染器,如软光栅化器、 DIB-R ,以及那些在 AI 框架中使用的渲染器,如 PyTorch3D 、 TensorFlow Graphics ,都是不使用图形专用硬件的软件渲染器。这种软件实现不像典型的 ML 内核那样是 MM-heavy,因此不能利用 MM 加速。
另一方面,GPU 架构师设计并提供具有足够深度的流水线的图形专用硬件,以便它们速度很快,很少成为瓶颈。现在,想象我们制造这样一个管道“可区分的硬件”软件程序员可以有效地使用计算图中的可区分硬件,类似于使用预构建的软件组件。由于图形专用硬件的深度流水线并行性,这种硬件图形在环应该比软件快得多。
可区分的 ISP 在回路中
除了使用差分硬件作为预构建的软件组件,我们还可以通过梯度下降调整其参数来对其进行“编程”,就像我们如何“训练”一个 ML 内核一样。例如,图像信号处理器(ISP)通过镜头捕捉图像,并在管道中处理它们,以产生供人类消费或下游图像理解(IU)任务(如对象检测或语义分割)的图片。传统的 ISP 具有充足的参数空间,需要专家调整以满足人们的需求。这个参数空间主要未被训练下游 IU NN 模型的专家开发。相反,专家使用 ISP 使用特定参数设置预先捕获和预处理的图像来训练 NN 模型。此外,捕获图像的透镜系统在制造和操作期间可能会有缺陷。如果没有 ISP 的联合优化和设备上的调整,IU NN 模型的性能将不会令人满意。
已经出现了用 NN 模型代替某些 ISP 处理阶段的蓬勃发展的建议,这些建议在具有特定功率和实时约束的情况下不一定实用或更好。另一方面,已经出现了努力开发 ISP 的未开发参数空间的研究。以下是一些例子:
- 非微分 ISP 硬件在环用于非 ML 优化的参数自动调整。
- 一个经过训练的神经网络模型,模仿一个 ISP 作为一个可区分的代理,使用 ML 进行参数自动调整。
上面的研究表明,通过为特定的 IU 任务设置端到端的目标,自动调优的 ISP 比没有自动调优的 ISP 表现更好。
第一种方法不能与其他神经网络模型联合优化不可微 ISP。另一方面,虽然使用可区分代理的第二种方法有助于训练,但它的缺点是我们需要在仔细控制的设置中单独训练这个代理。
现在,想象一下让一个 ISP 与众不同。我们可以利用 ISP 在回路中构建一个自适应传感管道,如下图所示。它可以在设备上与 ISP 前和 ISP 后的神经网络模型联合调整自身,以适应操作环境和 UI 任务。请注意,我们不会像 GPU 架构师不会指定图形着色器那样固定 ISP 前和 ISP 后的 NN 模型(参见我的文章GPU 会在计算机架构的黄金时代开始吗)。
作者图片
结论
我们已经通过环路中的图形和环路中的 ISP 的例子介绍了可区分硬件的概念。想象一下,我们已经有了一个可区分的 ISP 和一个可区分的 GPU。我们还希望自我监督的反向图形和自我调整的传感。如下图所示,我们可以通过连接环路中的图形和环路中的 ISP 管道来构建新的管道。
作者图片
正如我们所看到的,可区分的硬件单元在以下三个方面是可编程的:
- 人工智能程序员可以在计算图中使用它,因为他们在软件开发中使用预构建和可定制的软件组件。
- 人工智能程序员可以使用用于训练神经网络模型的相同 ML 框架来自动调整这些可区分的硬件单元参数。
- AI 程序员可以自由选择各种 NN 模型来使用这种可区分的硬件单元,就像图形程序员可以自由编程不同类型的着色器一样。
人工智能在主流软件中引入了一个领域转移到毫米级计算。软件程序员可以将各种各样的应用程序简化为 ML 内核。为了恢复摩尔定律的良性循环,我们将需要另一次领域转移。我们应该遵循人工智能的核心——差异化编程——来改变我们设计和使用计算硬件的方式,而不是找出哪个硬件适合人工智能这个前进的移动目标。人工智能硬件不再有“纯度”检查,现在可以包括可区分的硬件。
希望硬件可以在创新软件中延长其生命周期,软件可以利用硬件作为预构建和可定制的组件。两者可以在新的良性循环中互相推动,就像摩尔定律全面实施时一样。
希望硬件可以在创新软件中延长其生命周期,软件可以利用硬件作为预构建和可定制的组件。两者可以在新的良性循环中互相推动,就像摩尔定律全面实施时一样。
差分和自适应学习率——神经网络优化器和调度器揭秘
动手教程,直观深度学习系列
用简单的英语,用优化器和调度器来提高模型训练和超参数调整的温和指南
优化器是神经网络架构的关键组件。而调度器是你深度学习工具包中至关重要的一部分。在训练过程中,它们在帮助网络学会做出更好的预测方面起着关键作用。
但是他们有什么“旋钮”来控制他们的行为呢?如何充分利用它们来优化超参数,从而提高模型的性能?
定义模型时,需要做出几个重要的选择——如何准备数据、模型架构和损失函数。然后当你训练它的时候,你必须选择优化器和可选的调度器。
很多时候,我们可能最终只是为我们的大多数项目选择我们“最喜欢的”优化器——可能是 SGD 或 Adam。我们添加它,然后忘记它,因为它只有一行代码。对于许多简单的应用程序来说,这就很好了。
但是我们能做些什么来更有效地训练我们的模型吗?
优化器由三个参数定义:
- 优化算法,如 SGD,RMSProp,Adam,…
- 优化超参数,如学习率、动量等
- 优化训练参数
我还有一篇文章详细讨论了第一点。它涵盖了最常用的优化算法的核心原则,您可能会喜欢阅读。
在今天的文章中,我们将探讨如何利用#2 和#3。
为了解释这些主题,我们将从快速回顾优化器在深度学习架构中扮演的角色开始。
这可能是你已经知道的东西,但是请耐心,我们需要这些,这样当我们到达更有趣的部分时,我们可以在它们的基础上进行构建😄。
神经网络中的优化
在非常高的水平上,神经网络在训练期间通过多次迭代执行以下步骤:
- 基于当前参数生成输出的正向传递(即权重)和输入数据
- 损失函数,用于计算当前输出和期望目标输出之间的差距的“成本”
- 计算损失相对于参数的梯度的反向过程
- 优化步骤,使用梯度来更新参数,以便减少下一次迭代的损失
网络的组成部分和训练过程中的步骤(图片由作者提供)
既然这些参数起着如此重要的作用,那么它们到底是什么呢?
模型参数
一个网络的架构是由多层构成的,每一层都有一些参数。例如,线性或卷积层具有权重和偏差参数。您也可以创建自己的自定义层并定义其参数。
一个模型的参数是所有层的所有参数(图片由作者提供)
像 Pytorch 和 Keras 这样的深度学习框架有特定的数据类型来表示模型参数,即参数和可训练变量数据类型。
模型参数是一个张量。像所有的张量一样,它们包含一个数字矩阵,但是它们有特殊的行为。它们具有关联的梯度,每当在正向传递中对参数执行操作时,该梯度由框架自动计算。
每当定义一个新类型的层时,无论是内置的还是自定义的,您都可以使用这个数据类型来明确地告诉框架哪些张量应该被视为参数。
优化器培训参数
因此,当您构建网络架构时,模型的参数包括该架构中所有层的参数。
当您创建优化器时,您告诉它负责在训练期间更新的参数集。在大多数情况下,这包括模型的所有参数。但是,在许多情况下,您只想为训练提供参数的子集。
一个优化器,包含了它所有参数的列表,是模型参数的子集(图片由作者提供)
例如,在生成敌对网络(GAN)中,该模型具有两个而不是一个优化器。每个优化器只管理模型的一半参数。
训练开始时,用随机值初始化这些参数。然后,在向前和向后传递之后,优化器检查它所管理的所有参数,并根据以下各项用更新值更新每个参数:
- 参数的当前值
- 参数的渐变
- 学习率和其他超参数值
优化器更新它所管理的所有参数(图片由作者提供)
例如,随机梯度下降优化器的更新公式为:
对于优化器不管理的其他模型参数,不计算梯度。
优化超参数
所有优化器都需要一个学习率超参数。此外,其他超参数取决于您使用的特定优化算法。例如,基于动量的算法需要一个“动量”参数。其他超参数可能包括“β”或“重量衰减”。
创建优化器时,您为优化算法所需的所有超参数提供值(或使用默认值)。
您选择的超参数值对训练的速度以及基于评估指标的模型性能有很大影响。因此,选择好这些值并调整它们以获得最佳结果是非常重要的。
由于超参数如此重要,神经网络为您提供了许多设置其值的细粒度控制。
概括地说,有两个轴可以控制。第一个涉及参数组,我们将在接下来探讨。
模型参数组
之前我们谈到超参数时,好像整个网络只有一组值。但是如果你想为网络的不同层选择不同的超参数呢?
参数组让您可以做到这一点。可以为网络定义多个参数组,每个参数组包含模型层的一个子集。
模型的不同层可以放在不同的参数组中(图片由作者提供)
现在,使用这些,您可以为每个参数组选择不同的超参数值。这就是所谓的差异学习,因为实际上,不同的层次“以不同的速度学习”。
迁移学习的差异学习率
应用差异学习的一个常见用例是迁移学习。迁移学习是计算机视觉和自然语言处理应用中非常流行的技术。这里,您采用了一个大型模型,该模型已经过预训练,例如使用 ImageNet 数据集进行图像分类,然后将它重新用于来自您的应用程序域的一组不同的、小得多的图像。
执行此操作时,您希望重复使用所有预先学习的模型参数,并且仅对数据集进行微调。你不想从头开始重新学习参数,因为那是非常昂贵的。
在这种情况下,通常将网络分成两个参数组。第一组由提取图像特征的所有早期 CNN 层组成。第二组由最后几个线性图层组成,用作这些要素的分类器。
用图像分类器进行迁移学习。CNN 层和线性分类器层具有不同的学习速率。(图片由作者提供)
CNN 层学到的关于一般图像特性的许多知识也将应用于您的应用程序的图像。所以你可以用一个很低的学习率来训练第一个参数组,这样权重变化很小。
但是您可以对第二个参数组使用更高的学习率,以便分类器学习区分新域图像而不是原始集合的类。
Pytorch 和 Keras 的差异学习
Pytorch 的优化器在定义参数组和为每个组定制的超参数方面给了我们很大的灵活性。这使得做差分学习非常方便。
Keras 没有对参数组的内置支持。您必须在自定义训练循环中编写自定义逻辑,以这种方式用不同的超参数划分模型的参数。
我们刚刚看到了如何使用参数组调整超参数。对超参数调优进行细粒度控制的第二个方面涉及到调度器的使用。
自适应学习率的调度器
到目前为止,我们已经讨论了超参数,好像它们是在训练之前预先确定的固定值。如果您想随着训练的进行改变超参数值呢?
这就是调度程序的用武之地。它们让您根据训练时期决定超参数值。这有时被称为适应性学习率。
有标准的调度算法,使用各种数学曲线来计算超参数。Pytorch 和 Keras 有几个流行的内置调度程序,比如指数、余弦和循环调度程序。
选择想要使用的调度程序算法,并指定超参数值范围的最小值和最大值。在每个训练时期开始时,算法使用公式、最小/最大范围和时期数来计算超参数值。
调度器修改每个训练时期的学习率和超参数值(图片由作者提供)
调度程序被认为是一个独立的组件,是模型的可选部分。如果不使用调度器,默认行为是超参数值在整个训练过程中保持不变。调度程序与优化程序一起工作,而不是优化程序本身的一部分。
我们现在已经看到了在训练过程中控制超参数值的所有不同方法。最简单的技术是对模型使用单一的固定学习率超参数。最灵活的是沿两个轴改变学习率和其他超参数——不同层的不同超参数值,并在训练周期的过程中随时间同时改变它们。
为不同的层以及基于训练时期改变训练超参数(图片由作者提供)
不要与调整学习率的优化算法相混淆
快速澄清一下——你可能会读到一些优化算法(如 RMSProp)根据参数的梯度为不同的参数选择不同的学习速率。
这是优化算法内部的问题,由算法自动处理。作为模型设计师,你是看不见的。学习率基于梯度变化,而不像调度器那样基于训练时期。
这与我们在本文中讨论的机制无关,所以不要混淆这两者。
结论
我们刚刚看到了优化器和调度器做了什么,以及它们提供的允许我们增强模型的功能。这些都是可以在一系列深度学习应用中使用的便捷技术。
Pytorch 和 Keras 包含内置特性,使得采用这些技术相当容易。
在以后的文章中,我计划更深入地探索特定类型的优化器和调度器,以及它们是如何工作的。
最后,如果你喜欢这篇文章,你可能也会喜欢我关于变形金刚、音频深度学习和地理定位机器学习的其他系列。
让我们继续学习吧!
机器学习中的差分隐私和 k-匿名
作者图片—加拿大多伦多
在当今数据驱动的世界中,用户隐私日益受到关注。我们将调查使用匿名技术对公共医疗相关数据集的影响,其中存在一些患者的私人信息,这可能导致重新识别攻击。
我们将在 MATLAB 的帮助下评估一个前馈神经网络,该网络使用具有拉普拉斯噪声的局部差分隐私和 k-匿名来匿名化我们的数据集。
概观
近年来,数据隐私受到了广泛关注。由于用户信息泄露是我们当今面临的一个经常性问题,公司使用不同的策略来保护他们的用户信息。许多公司收集用户数据供内部使用,他们有时会通过数据集公开这些数据。为了保护用户的身份,数据工程师使用差分隐私技术和其他策略来保护用户的隐私信息。
我们将研究使用两种最常见的数据匿名算法来掩盖糖尿病研究参与者的私人信息。我们提出了一个前馈神经网络模型,该模型即使在使用拉普拉斯噪声和 k-匿名对数据进行匿名化时也能获得高精度。
本演示使用的所有代码如下:
差异隐私
数据隐私是公司和客户经常关心的问题。差异隐私允许数据提供商以安全的方式公开共享私人信息。这意味着数据集用于描述群体的模式和统计数据,而不是特定的单个个体。
为了保护个人隐私,差分隐私在数据中添加噪声以掩盖真实值,从而使其成为隐私。通过这样做,我们隐藏了个人的身份,对数据的效用几乎没有影响。这意味着数据集的统计结果不应受到个人贡献的影响,因为数据代表了整个人口的特征。设 D 和 D’表示仅在一个数据集上不同的两个不同的相邻数据集。差分隐私声明通过添加噪声𝜖来保护给定数据集中的私有属性,我们无法预测特定条目是否存在于数据库中[1]。
假设𝑃𝑟是𝑃算法的随机性,𝑆𝑠是𝑃𝑟的子集,它代表了𝑅.所有可能的结果如果等式 1 为真,我们将算法表示为 d '和 d 上的𝜖-differentially 私有算法[1]。
差分隐私最常见的噪声类型是拉普拉斯、指数和高斯机制。它们通过向原始数据条目添加噪声来工作,并且可以应用于真实特征和分类特征。拉普拉斯策略是指数分布的对称版本,它根据等式 2 [1]将来自对称连续分布的噪声添加到真实答案中
另一方面,指数机制以与等式 3 成比例的概率选择并输出元素𝑟 ∈ 𝑅。
其中,𝑥是一个输入,𝑢是一个具有广义灵敏度δ𝑢.的效用函数
k-匿名
k-匿名最早是在[4]中提出的,并指出为了实现 k-匿名,包含在发布的数据集中的每个人的信息不能与至少𝑘1 个人的信息相区分,该个人的信息也出现在发布的数据集中。
实现 k-匿名有两种方法:抑制和推广。前一种方法用星号' * '替换一些条目,而后一种方法将条目分组。
资料组
我们将使用的数据集是早期糖尿病 [2],它可以在 UCI 机器学习知识库上公开获得。该数据集包括 520 个记录,这些记录是使用直接问卷从 2020 年孟加拉国 Sylhet 的 Sylhet 糖尿病医院的患者中收集的。它包含与 Sylhet 糖尿病医院收治的患者的医疗条件和特征相关的二元和实数特征。
这是一个平衡的数据集,共有 16 个特征,包括参与者的年龄和性别。虽然我们在分析中认为是私有属性的数据只是年龄
前馈神经网络
我们将使用前馈神经网络(FFNN)来训练我们的模型。选择这个模型是因为它的简单性和分类的有效性。我们需要洗牌数据以消除任何偏差。数据集分为三组:80%用于训练,20%用于测试。我们将使用 20%的训练集对模型进行验证。让我们使用 MATLAB 上的代码创建一个具有两个隐藏层的神经网络,分别具有 48 和 32 个神经元。
当我们运行上面的代码时,我们可以虚拟化我们的网络。
神经网络训练
对于传递函数,我们将对称 sigmoid 传递函数与交叉熵损耗(逻辑损耗)结合使用。让我们将损失函数的正则化参数𝜆设置为 0.01 来表示过拟合。这里用于训练的算法是比例共轭梯度反向传播。
实验评估
FFNN 的准确率和 f1 值分别达到 97.1%和 97.6%。
模糊神经网络的混淆矩阵
这一性能略好于对该数据集[2]的原始研究报告,其中评估了 Naïve 贝叶斯、逻辑回归和随机森林(RF)模型。在[3]中评估了其他几种算法,包括支持向量机、决策树、K-最近邻、Naïve 贝叶斯分类器、随机森林(RF)分类器和逻辑回归,但没有一种算法达到我们模型的准确性。将数据集分为训练和测试的相同配置应用于[2,3]。与我们的方法性能相似的唯一方法是在[3]中使用的自适应粒子灰狼优化(APGWO)模型,准确率为 97%。
数据匿名化
我们的第一个实验包括将拉普拉斯噪声添加到私有属性中,使它们匿名。通过这样做,我们在数据集上执行本地差分隐私。对于年龄类别的每个条目,我们添加了一个敏感度(δ𝑢)等于 1、𝜖 = 0.1 的噪声。然后,我们生成平均值为 0 且δ𝑢标度为的拉普拉斯噪声,并将其添加到𝜖原始值,如等式 4 所示。
我们首先需要在 MATLAB 中实现拉普拉斯噪声。有几种方法可以做到这一点,这里有一种:
现在,让我们将噪声添加到数据集中。
新数据集的年龄分布与原始数据相似。新的年龄分布直方图如下图所示。
左边是原始数据集的年龄分布,右边是添加了拉普拉斯噪声的数据集。
第二种方法包括对数据集应用k-匿名策略,对参与者的年龄进行数据匿名化。我们将使用概化技术,因为年龄要素可以在不同的范围内进行分组。因此,我们创建了 9 个不同的组,并根据下图对该属性进行了分类。
9 个年龄组的年龄分布。
比较
我用这两种匿名算法运行我们的模型,并记录了结果。下表汇总了我们的模型在使用和不使用数据匿名化的情况下的性能。
数据匿名化的 FFNN 性能。
可以看出,我们的 FFNN 在与匿名化数据集一起使用时具有出色的性能。当使用拉普拉斯噪声对数据集进行匿名化时,达到了最佳性能。这种配置仅降低了 2.9%的精确度。然而,k-anonymity 上使用的泛化算法在数据集的效用方面也表现出了良好的性能,仅降低了 3.8%的数据效用。因此,具有拉普拉斯噪声和 k-匿名的前馈神经网络在该数据集上表现良好。
结论
在发布新数据集时,数据效用和隐私对于预先提供敏感信息至关重要。差分隐私和 k-匿名是用于数据匿名化的一些策略,围绕这些主题已经开发了几种解决方案。当使用匿名化数据集时,我们看到了用于模型预测的前馈神经网络(FFNN)模型。
我们还评估了两种匿名化方法的性能,拉普拉斯噪声和 k-匿名,用于匿名化早期糖尿病数据集中的私有属性。
那都是乡亲们!我希望你喜欢这个关于数据匿名化的小演示。
关于我
我是约克大学的一名硕士研究生,骨子里是一名软件工程师。在过去的十年里,我一直在软件开发、云计算和系统工程等领域的几个行业工作。目前,我正在 PACS 实验室进行云计算和分布式系统的研究。
参考
[1]辛西娅·德沃克、亚伦·罗斯等,2014 年。差分隐私的算法基础。找到了。趋势理论。计算机。Sci。9, 3–4 (2014), 211–407.https://www.cis.upenn.edu/~aaroth/Papers/privacybook.pdf
[2] M. M. Faniqul Islam、Rahatara Ferdousi、Sadikur Rahman 和 Hu- mayra Yasmin Bushra。2020.利用数据挖掘技术早期预测糖尿病的可能性。医学图像分析中的计算机视觉和机器智能。).新加坡斯普林格,新加坡,113–125
[2]使用数据挖掘技术的阶段。医学图像分析中的计算机视觉和机器智能。).新加坡斯普林格,新加坡,113-125。
[3] Tuan Minh Le、Thanh Minh Vo、Tan Nhat Pham 和 Son Vu Truong Dao。2021.一种新的基于包装器的特征选择,用于早期糖尿病预测,用元启发式算法增强。IEEE 访问 9 (2021),7869–7884。【https://doi.org/10.1109/ACCESS.2020.3047942
[4]皮耶安吉拉·萨马拉蒂和拉坦娅·斯威尼。1998.披露信息时保护隐私:k-匿名及其通过推广和抑制的实施。(1998).https://epic.org/privacy/再鉴定/萨马拉蒂 _ 斯威尼 _paper.pdf
基于花和不透明的差分私有联合学习
了解如何使用 Flower 和 Opacus 框架通过 DP-SGD 训练联邦模型
由米切尔·林辛克在 Unsplash 上拍摄的原始照片。由作者编辑。
在之前的帖子中,我已经描述了什么是联合学习,并给出了一个如何在 Flower 框架中使用它的例子。我还展示了如何使用多处理来扩展您的实验,以避免 GPU 内存混乱。这个教程是基于上一个的,所以我建议如果你是第一次接触 Flower 的话,先快速回顾一下,获取更多的背景知识。
差分隐私的(真正的)简短介绍:
通过将实体之间交换的信息流从数据本身转移到仅模型参数,联合学习(FL)比机器学习中的普通方法实现了更高程度的隐私。事实证明,这对于以移动应用为目标的用例非常有用[ 1 ],因为大量参与者可能有时间接受培训,并提供了在其他情况下无法获得的数据上进行培训的可能性。然而,FL 本身不足以实现完全隐私,因为它表明模型可以记住给出的例子,而不仅仅是学习如何使用呈现的特征来完成任务。这带来了问题,因为成员推理攻击可以被执行并恢复用于训练模型的原始样本。
解决这个问题的常见方法是在将模型的权重发送给另一个实体(可能是服务器或另一个对等体)之前对其进行加密。然而,这些技术会给工作流带来更高的计算和通信成本。一种替代方法是使用差分隐私(DP),它分析由给定机制引起的潜在隐私泄露。DP 最初是由 Dwork [ 2 ]在 2006 年提出的,它基于相邻数据库的概念,即数据库最多只有一个元素不同,定义如下:
与 D 和 D '相邻的数据库。图片取自[ 3 ]。
DP 保证当在数据库中添加或删除一个给定的元素时,来自我们机制的答案不会改变一定的量:exp(ε) + δ。通过添加独立的高斯噪声来实现机制的差分私有的普通方式,其中方差与机制的灵敏度成比例。为了评估随机机制的隐私预算,必须进行分析,以便我们可以跟踪隐私损失。这种分析包括雷尼差分隐私(RDP) [ 3 ,并具有到(ε,δ)-DP 的转换。DP 最有利的特性之一是它对后处理是健壮的,这意味着无论对获得的结果做什么,隐私保证都将保持不变。
DP 是一个很大的主题,许多其他人已经写了关于它的文章,所以我在这里将保持简短,但是,如果你想更好地了解它的细节,你可以看看媒体上的这个其他帖子。
现在,人们可以决定直接对原始数据应用 DP 来保护它。然而,噪声的增加是以精度为代价的,在我们的工作流程中应用 DP 越晚越好。这就是为什么 ML 模型的常见方法是使用由 Abaddi 等人[ 4 ]介绍的 SGD 优化器的 DP 版本。该算法不将噪声应用于数据或模型参数,而是在梯度下降的每一步直接应用于梯度。它还使用裁剪来限制单个元素对训练过程的影响。这是我们今天要使用的算法,在 PyTorch 的团队 Opacus 库中实现,并使用 RDP 作为隐私分析。
设置环境
该代码可从 GitHub 上获得,并详细说明了如何安装这些库。我推荐用一个用诗搭建的虚拟环境,Flower 的团队也在用。
助手文件
和第一篇文章一样,我们将从编写一个名为flower_helpers.py
的助手文件开始,我们的大多数函数都将位于这个文件中。从进口开始:
所有需要的库都在这里:Flower (flwr)、Torch + Torchivision、Numpy 和 Opacus。还有一些是打字问题。您可以注意到我们从 Flower 导入了 FedAvg,这是库用来定义如何在联邦过程中更新权重的策略。我们需要制定策略来适应 DP 案例。从 Opacus 只导入了两件东西:PrivacyEngine 和 sampler。该引擎将允许我们将其附加到任何 torch 优化器,以在其上执行 DP-SGD 步骤。至于采样器,我们将在一段时间内看到它。下一步是为模型定义我们的设备:
之前,我们也使用了 CIFAR10 作为数据集,但为了简单起见,我们保持了它的完整性。然而,为了更好地理解 DP 对我们工作流的贡献,我们将创建自己的联邦数据集。大多数情况下,集中式数据集受益于完全相同且独立分布(IID),这意味着每个类都是平衡的,并且其中的样本共享相同的要素分布。然而,在联邦环境中,由于数据来自不同的实体,找到完美的分布式本地数据集的机会几乎为零。以 MNIST 为例,如果我们根据参与创作的每个人来划分,我们会注意到,每个人抽取的数字可能不同,但他们的写作风格也不同。这是一个非 IID 的例子,但是数据集可以有很多非 IID 的方式,这是联邦应用程序的主要关注点,因为我们无法看到我们正在使用的数据。在我们的教程中,我们将保持简单,仅在 N 个客户端中分离 CIFAR10 数据集,它们可以保存的样本数量有所不同。从分割功能开始:
每个分割最初包含相同数量的样本,然后我们通过交替地从一个分割中移除样本并将它们添加到另一个分割中来调整它。我们用一个比率来确定一次分割可以拥有的最小样本数。75%的比率意味着我们最小的可能分割包含平均值的 25%。接下来,我们加载数据:
要加载客户端数据,我们首先加载 CIFAR10 并对其进行规范化,然后根据我们想要使用的客户端数量创建拆分。然后,在将子集提供给 DataLoader 类之前,创建一个子集。这不是最佳方式,但由于 CIFAR10 是一个非常小的数据集,我们负担得起。你在这里注意到了 uniformwithrecreplacementsampler 的使用,它也出现在所有 Opacus 的教程中,但是他们从来没有解释过为什么要使用它。这是非常小的一步,但却是非常重要的一步,因为这个采样器允许我们修改批处理的创建方式。就训练表现而言,我们甚至看不到区别,但是没有区别,DP 保证就不成立。如果什么都不做,将按顺序选择批,直到 dataloader 中没有元素。问题是 DP 的保证是基于这样一个事实,即对于每一批,每个样本都有相同的概率 p 被抽样。在一个时期内,一个样本可以被多次看到,并且批次甚至大小不一。uniformwithrecreplacementsampler 正是这样做的,它以概率 p (由sample_rate
参数定义)获取一个样本,并将其放回下一批的数据集中。如果不使用,Opacus 给出的最终隐私预算不会改变,但不会是真实的。
接下来是定义我们的模型:
该模型基于“py torch:60 分钟闪电战”教程。当使用 DP-SGD 时,我们必须在建立模型时考虑它,因为算法引起的噪声和削波会极大地影响模型的行为。例如,建议通过 Tanh [ 5 ]等有界函数切换 ReLU 激活函数,这些函数是无界的。这使得模型支持更好的渐变裁剪并提高了性能。我们这里没有,但是放弃辍学也是一个不错的选择,因为 DP 已经是一个强大的规范器。如果在您的网络中使用任何 BatchNormalization 层,您应该用其他层替换它们(如 GroupNormalization 或 LayerNormalization),因为批处理方式的计算操作不是 DP 友好的,并且不受 Opacus 支持。最后一个好的选择,也是尚未被证明的[ 5 ],可以尝试减少你的模型的容量(它的参数数量)。DP 的过度拟合会妨碍你的模型,所以尽量避免比平时多一倍。同样,超参数应针对差压设置进行微调。
接下来是一些与 Flower 相关的函数,用于设置和获取模型的权重:
为每个客户端计算目标δ的函数:
δ表示ε给出的隐私保证不成立的概率,因此理想情况下,它应该低于数据集大小的倒数。它通常被设置得低一个数量级。
接下来是我们的培训功能:
我们将在这里回顾一些重要的步骤。首先,您可以注意到有两个批量大小:vbatch_size
(v 代表虚拟)和batch_size
。vbatch_size
是我们的目标批量,而batch_size
将是数据加载器使用的实际批量。使用这些方法可以将 PrivacyEngine 的优化步骤分解为两个部分:计算梯度,然后应用噪声和梯度下降。这给了我们两个好处,首先,我们之前创建的采样器给出的实际批量大小并不完全是我们给出的。因此,累积多个批次的梯度可以让我们趋向于我们的初始目标值。其次,使用 DP-SGD 增加了训练过程的计算开销,随着批量的增长需要更多的内存。正如 Opacus 在他们的教程中所说:
"Opacus 计算并存储每样本渐变,因此对于每个法线渐变,Opacus 将在每一步上存储 *n=batch_size*
每样本渐变,从而至少增加 *O(batch_size)*
的内存占用。然而,实际上,与非私有模型相比,峰值内存需求是 *O(batch_size^2)*
。这是因为每样本梯度计算中的一些中间步骤涉及对两个矩阵的操作,每个矩阵都将 batch_size 作为其中一个维度。”
这意味着通过保持虚拟步骤的小批量,我们可以有效地管理任何批量的内存需求。在代码中,它转化为确定所需的累积步骤数(*n_acc_steps*
),稍后当我们计算优化器步骤时,我们执行虚拟步骤或正常步骤,并刷新梯度:
if ((i + 1) % n_acc_steps == 0) or ((i + 1) == len(train_loader)): optimizer.step() # real step optimizer.zero_grad()else: optimizer.virtual_step() # take a virtual step
然后你会注意到我们正在训练函数中加载数据和模型。这是因为我们将使用多重处理来允许每次选择新客户端时释放 GPU 内存。如果我们在真实的环境中,客户端会在连接到服务器之前加载它们。您还可以注意到优化器从 SGD with momentum 更改为 Adam。Opacus 可以与任何优化器一起工作,但我个人发现 Adam 更容易使用 DP 设置。
alpha 列表包含由 Opacus 执行的 RDP 分析的订单。这个列表取自 Opacus 的教程,一般来说,你不需要修改它,但是如果你看到在你的例子中选择的 alpha 一直是一个极值,你可能想要调整它。
接下来是 PrivacyEngine,它是来自 Opacus 的自定义对象,允许执行 DP-SGD。使用它的方法是将它附加到任何优化器上。它以模型、采样速率、阶数α、噪声乘数(添加的噪声量)、最大梯度范数(削波)和目标δ值为参数。通过给它们这些参数,它将执行 RDP 分析,并为训练过程的当前状态提供ε值。该值是在给定数据集上执行的总步骤数的情况下计算的,这些步骤存储在引擎的state_dict
中,可以通过调用相关方法获得。这就是我们通过联合培训跟踪客户隐私预算的方式。每次选择一个客户时,他们将存储产生的状态字典,以便在下次参与时恢复它。
我想指出的最后一点是,我们只是在为一个单一的本地时代进行训练。原因是因为 FedAvg 被证明在非 IID 数据集上不收敛[ 6 ]。
培训后,我们定义不包括任何 DP 步骤的测试:
最后,我们需要为服务器定义一个行为来管理客户端。我们可以只使用标准的联合平均策略,但如果我们的目标是尊重所需的隐私预算,我们需要处理客户的意见:
我们只添加一个名为max_epsilon
的参数来跟踪全球隐私预算。由于合成定理,独立机制的组合所产生的隐私预算将等于最大值。
每个客户端将在其度量字典中返回他们的隐私预算是否超过目标值。如果是这样的话,服务器会断开他们的连接,这样他们就不必再参加培训了。
最后,最初的FedAvg
类中的configure_evaluate
在每一轮都被调用,如果最初没有评估函数被传递给策略,它将选择一些客户端来执行评估。同样,在最后一轮,它将对每个可用的客户端进行评估。当我们在服务器端执行评估时,我们希望避免这两种行为,但是如果没有足够的客户端可用于训练,我们会打印一条消息,因为我们断开了其中一些客户端。
客户端文件
我们所有有用的函数都被定义了,然后我们可以编写我们的客户端文件。从进口开始:
在 main 函数中,我们从脚本的参数开始:
然后,我们通过从 NumpyClient 类继承来定义我们的客户端
遵循 fit 方法,首先是训练过程:
获得结果:
最后是评估函数(我们在这个例子中没有用到):
最后要做的是启动客户端并运行 main:
服务器文件
只剩下服务器文件。导入和全局变量:
服务器端的评估函数:
主要功能和启动服务器:
Bash 文件
我们所有的核心元素都已经编写好了,现在我们可以使用 bash 脚本组合并启动它们:
在将脚本转换为可执行文件后,在您的终端中运行./run.sh
将默认运行 2 个客户端 3 轮。您可以根据需要修改参数。
由于只有两个客户端和一个稍微非 IID 的数据集,该模型应该执行 close(如果不使用 FL 的话)。经过一段时间的训练后,你可能会发现网络卡在 40%以下,很难再提高。这是因为如果没有 DP(55%左右),我们正在使用的模型本身就不能表现得那么好。您可以通过使用更好的模型(增加每个卷积层的滤波器数量)甚至使用迁移学习来缓解这一问题。随意实验!
结论和展望
通过这篇文章,您了解了如何使用 Flower 和 Opacus 在联邦模型上执行 DP-SGD。然而,有些部分我们没有涉及到。例如,没有解决为联合 DP-SGD 寻找超参数的问题。人们可以选择在对每个客户端进行训练之前直接计算它们,这导致了额外的隐私成本,或者他们可以选择预先对类似的本地数据集进行微调,并使用为所有客户端找到的超参数。我们也可能想知道 DP-SGD 对我们的模型的真正影响,以及(ε,δ)-DP 预算在保护方面的真正含义,[ 7 对此提供了一些答案,但这仍然是一个开放的话题。寻找对增加的噪声更具弹性的架构,从而减少应用 DP 引起的性能下降也很重要。
如果你想了解更多关于如何将 Opacus 应用于不同情况的知识,你可以查看他们的教程部分,这里有两个额外的 NLP 示例。同样的道理也适用于花,虽然我们在这里用 PyTorch 使用它,但它并不限于此,你可以用任何你喜欢的框架进行实验,甚至是 Scikit-learn 。同样,我们只涵盖了我们案例的一个基本数据集,但是我们可以使用一些专门为联邦案例设计的数据集,比如 LEAF 基准。
本教程到此结束。代码可在这里获得。我希望这将是有用的,并帮助你们中的一些人更好地理解如何花和 Opacus 可以一起工作。不要犹豫留下反馈和/或问题!
参考
[1] A. Hard *et al.*, “[Federated Learning for Mobile Keyboard Prediction](http://arxiv.org/abs/1811.03604),” *arXiv:1811.03604 [cs]*[2] C. Dwork, “[Differential Privacy](https://doi.org/10.1007/11787006_1),” in *Automata, Languages and Programming*, Berlin, Heidelberg, 2006, pp. 1–12\. doi: 10.1007/11787006_1.[3] I. Mironov, “[Renyi Differential Privacy](https://doi.org/10.1109/CSF.2017.11),” *2017 IEEE 30th Computer Security Foundations Symposium (CSF)*, pp. 263–275, Aug. 2017, doi: 10.1109/CSF.2017.11.[4] M. Abadi *et al.*, “[Deep Learning with Differential Privacy](https://doi.org/10.1145/2976749.2978318),” *Proceedings of the 2016 ACM SIGSAC Conference on Computer and Communications Security*, pp. 308–318, Oct. 2016, doi: 10.1145/2976749.2978318.[5] N. Papernot, S. Chien, S. Song, A. Thakurta, and U. Erlingsson, “[Making the Shoe Fit: Architectures, Initializations, and Tuning for Learning with Privacy](https://openreview.net/forum?id=rJg851rYwH),” OpenReview[6] X. Li, K. Huang, W. Yang, S. Wang, and Z. Zhang, “[On the Convergence of FedAvg on Non-IID Data](http://arxiv.org/abs/1907.02189),” *arXiv:1907.02189 [cs, math, stat]*[7] M. Jagielski, J. Ullman, and A. Oprea, “[Auditing Differentially Private Machine Learning: How Private is Private SGD?](http://arxiv.org/abs/2006.07709),” *arXiv:2006.07709 [cs]*
数字文本分析:荷兰语区的街头诗歌
街景诗的数字视角。
埃因霍温的街头诗歌。照片由西贝·希姆斯特拉拍摄。
街头诗。一旦你对它的存在开放,你就不能视而不见。有时很大,非常现实,其他时候更离散和适度。2016 年,网站 www.straatpoezie.nl 由 Kila van der Starre 博士作为论文的一部分推出,期间调查了公共空间中不同形式的诗歌。该网站是一个众包数据库,拥有超过 3000 首诗歌,可以在荷兰和比利时的街道上找到。
在荷兰人口稀少的 Meijel 地区的一个小镇的市场广场上,坐落着一个小型的文学艺术作品。就在这个村庄的核心之外是国家公园 De Peel,“一个拥有无与伦比的动植物的无限空间。许多小说、诗歌、歌曲等的灵感来源”,比如这个:
En d'n hemel waas veurroej (天空火红) 薄雾兴 op 't land (薄雾挂在大地上) 的 Oaves laat da stong de Peel in brand(深夜果皮着火)
这一小段文字来自林堡乐队 Rowwen Hèze 的歌曲“品牌中的皮尔”(The Peel on fire)。它描述了一个小男孩如何发现 De Peel 自然保护区的雾与红色的夕阳结合在一起就像一场火。
尽管选择这个关于 De Peel 的片段并不是没有争议的(乐队不是来自 Meijel…),但选择这个位于 Meijel 的文学艺术作品是有意义的。这个城镇把自己描绘成国家公园的大门。当你在地图上查看它时,你会发现它确实与德·皮尔密切相关。不管某首诗甚至歌曲背后的动机是什么,研究街景中选择的诗歌是否以某种方式反映或代表了周围环境的一部分是很有趣的。最直接的方法之一(目前)是检查特定单词在特定条件下是否出现以及出现的频率。在这篇文章中,我们将放大两个特定单词的存在,“stad”(城市)和“zee”(海),给出这首诗是否位于城市地区的条件。其中一个假设是,“海”出现在海的附近,出现在赞美海的力量和吸引力的诗歌中。而带有‘市’的诗词,大多会出现在市区。毕竟——当你身处一个不像城市的环境中时,你为什么要赞美(甚至命名)这个城市呢?
路线图
数据集的编译者 Kila van der Starre 博士在我联系她的时候让我将数据集用于我自己的研究目的。首先,我们要看一下数据(一个新的数据集可以在很多方面给你带来惊喜)。之后,一个多边形被上传到世界上所有的城市区域,我们用纬度/经度来划分,所以只有荷兰和比利时是可见的。最后,我们用单词“sea”和“city”创建两个独立的数据帧。
众包的巨大优势在于,科学家可以收集大量的数据,而如果单独完成这项工作,则需要数年时间。然而,缺点是信息并不总是准确、完整和整洁的。众包数据集很有趣,迫使数据科学家发挥创造力。你必须尝试多种解决方案,然后才能找到一种可行的方案。
当使用熊猫打开数据集时,我们会看到以下内容:
作者图片
当浏览条目时,可以立即注意到条目的填充字段之间的差异有多大。有些条目只有两个必填字段,而其他条目在“与位置的关系”、“备注”和“更多信息”下有完整的内容。一位 NLP 科学家享用了像这样的不同文本专栏!;-)反正此时此地,最有趣的栏目是纬度、经度和文字。
系统描述
下一步是导入荷兰和比利时所有城市区域的多边形:
左边的是我们刚刚导入的多边形,右边的地图供参考:
右图:谷歌地图(2021)荷兰/比利时。地形图,检索到https://www . Google . com/maps/@ 51.8509528,4.1818916,7.5z/data=!5m1!1e4
我们快到了。为了进一步处理这些数据,我们需要合并每个条目的纬度和经度。此外,为了确保城市/非城市的分布不会过于倾斜,我们做了一个快速检查:
当计算“Is_urban”列中的值时,我们获得了 1886(城市)与 1014(非城市)的比率,因此大约 2/3 的条目位于城市区域。
最后,我们可以用任何我们想要的单词创建一个数据帧,以查看它们在荷兰或比利时地图上的使用位置。让我们把“海”和“城”像我们在引言中讨论的那样。
就是这样!
作者图片
解读
当我们选择带有“海”这个词的诗歌时,我们会惊奇地发现,只有极小一部分含有“海”这个词的诗歌实际上位于海的附近。一个临时的结论是,诗歌不仅仅反映或表现他们周围的环境;他们也可以声明缺席。一个可以遵循的线索是,这些诗歌是否位于其他类型的自然水域,如湖泊和河流。
当选择带有“stad”(城市)一词的诗歌时,我们看到大多数诗歌确实是在城市地区。有趣的是,在瓦登群岛上(在左图中,四个集体的点在“海”的下面),有“海”,没有一首诗提到这座城市。
未来工作
在下一篇文章中,我们将深入探讨街头诗歌片段背后的诗人。关于这些作家的年龄、性别、民族、活跃年限有什么可以调查的?请关注我的频道。
参考文献
Karsdorp,m . Kestemont 和 a . Riddell(2021 年)。人文数据分析。阿姆斯特丹大学出版社。
范德斯塔尔,基拉。(2021).pozie buiten het boek。流通在他的街道上。乌特勒支大学。
Python 的“数字孪生”:实践示例
使用 Python 构建电子开关(晶体管)数字孪生示例的分步指南
图片来源:作者创作
什么是数字孪生?
IBM 将数字双胞胎定义如下“数字双胞胎是一种虚拟模型,旨在准确反映物理对象。
他们继续描述创建数字双胞胎的主要促成因素是如何收集数据的传感器和将数据以某种特定格式/ 模型插入对象的数字副本的处理系统。
更进一步,IBM 说“一旦获知了这样的数据,虚拟模型就可以用来 运行模拟研究性能 问题和 产生可能的改进 ”。
所以,我们可以画出这个心智模型,不是吗?
图片来源:作者创作
使用 Python 创建数字孪生
那么,我们如何使用我们最喜欢的语言 Python 来创建数字双胞胎呢?为什么我们认为它会起作用?
答案看似简单。只需看看上面的图,然后看看下面的图,就可以看到数字孪生模型和经典 Python 对象之间的等价性。我们可以用合适的方法/函数模拟传感器和数据处理器,将收集到的数据存储在数据库或内部变量中,并且将所有东西都封装到一个 Python 类中。**
图片来源:作者创作
一旦我们做到了这一点,我们还可以希望 Python 对象
- 可以用在合适的模拟程序中,
- 可以探测数据,并且
- 甚至可以进行优化程序以增强合适的内部参数。
当然,我们可以在这个方案中增加无限复杂的层次,让这个物体成为一个真正复杂的数字结构。但是,遵循奥卡姆剃刀原则,我们应该从简单开始,随着我们的发展,增加复杂性。
…创建数字双胞胎的主要促成因素是收集数据的传感器和处理系统……
在本文中,我们将采取一种简单的循序渐进的方法,用单个半导体器件(物理对象)创建一个数字孪生对象。为简单起见,我们甚至不会对传感器进行建模,而是将其模拟为半导体器件上的简单端电压。
物理对象——MOSFET 器件
我们周围有一些精选的实物,它们相当壮观地定义和体现了现代文明的发展。不完整的列表可能如下所示,
- 内燃机——体现了所有的机动性
- 印刷机——包含所有的知识
- 电机——体现所有工业运动
- 基于半导体的晶体管——体现了所有电子产品/互联网
在加入数据科学领域之前,我已经在半导体行业工作了十多年。很自然,我会被这个数字孪生演示的相关例子所吸引,不是吗?
图片来源:作者创作
那么,什么是 MOSFET?
虽然第一批半导体晶体管是所谓的“双极结型器件”,但几乎所有现代晶体管都是 MOSFET。它是一个首字母缩写词,代表“Met al-Oxide-S半导体Ffield-Eeffect-T晶体管”。
基本上,它是由金属和半导体(例如硅或锗)层制成的器件,在它们之间夹有薄的氧化物(或另一种电绝缘体)材料层。
这是最早的 MOSFET 专利之一的历史图像,
我们可以采用三端 — 漏极、源极、栅极的器件模型(也称电路模型)来简化内部结构。
图片来源:作者创作
我们想要建模的特征
这是构建数字双胞胎时首先要考虑的问题之一——我们希望在我们的数字对象中建模什么特征。这决定了 twin 的复杂性和数据结构选择。
例如,虽然我们可以用 MOSFET 结构模拟各种复杂的物理现象,但我们可以选择限制自己只模拟最基本的特性,即。最简单形式的漏源电流和电压关系。
图片来源:作者创作
现在,我们没有足够的篇幅在本文中详细解释所有这些特征(以及它们背后的物理原理)。有兴趣的读者可以参考优秀的网上资料,
MOSFET 是一种数字开关
记住 MOSFET 行为的最简洁的方法是把它想象成一个数字开关。这是它的工作原理,
- 如果栅极和源极之间的电压低于某个阈值,则开关断开,并且没有电流(或信息)在漏极和源极之间流动。这在上图的右下角显示。
- 当栅极-源极电压高于该阈值时,开关接通。漏极到源极的电流也由它们之间的电压决定。这在上图的右上角显示。
因此,MOSFET 的基本用途是作为一个压控开关,即我们可以通过控制第三个端子上的电压来控制其两个端子之间的电流(或信息)量。
我们希望在我们的数字对象中模拟什么特征?这决定了数字孪生的复杂性和数据结构选择
要记住的参数
考虑到这一点,创建数字孪生的唯一需要记住的是通用 MOSFET 的三个重要参数,
- Vth :为阈值电压(在栅极和源极之间),高于该值时开关接通。
- GM:MOSFET 一旦导通,就可以在漏极和源极之间携带电流。这个数字越高,电流越大。它可以被认为是电阻(表示给定电压下电流流动的阻力)的倒数。
- BV :这叫做“击穿电压”。这在理想开关描述中没有讨论,在数字孪生中也不会模拟。这表示当 MOSFET 处于关闭状态时,其漏极和源极之间可以保持多少电压的限制。高于此限值,MOSFET 再次开始导通,但不是以受控方式,即我们无法控制电流,它基本上是无用的。然而,该参数对于设计和建模非常重要,因为它限制了特定应用中特定器件的选择。
MOSFET 的基本用途是作为电压控制开关……
我们的 Python 数字双胞胎
这个演示的样板代码在我的 Github repo 中的。为简洁起见,本文中我将只展示部分代码片段。
主 MOSFET 类别
我们定义了主 MOSFET 类别,用户还可以选择定义一些参数和端电压。下面显示了部分代码,
*class MOSFET:
def __init__(self,params=None,terminals=None):
# **Params**
if params is None:
self._params_ = {'BV':20,
'Vth':1.0,
'gm':1e-2}
else:
self._params_ = params
# **Terminals**
if terminals is None:
self._terminals_ = {'source':0.0,
'drain':0.0,
'gate':0.0}
else:
self._terminals_ = terminals*
在这段代码中,我们可以看到我们之前详细讨论过的常见终端和参数。还有一些默认参数。
我们也可以用一个__repr__
方法来描述一行中的对象,
*def __repr__(self):
return "Digital Twin of a MOSFET"*
一种用于确定开关状态的方法
通过在类中定义一个方法,我们可以很容易地将数字开关(开/关)的 MOSFET 特性转换成编程逻辑。
*def determine_state(self,vgs=None):
"""
"""
if vgs is None:
vgs = self._terminals_['gate'] - self._terminals_['source']
else:
vgs = vgs if vgs > self._params_['Vth']:
return 'ON'
else:
return 'OFF'*
在__init__
方法中,我们可以从对象的实例化中确定状态。
*# Determine state
self._state_ = self.determine_state()*
你已经掌握了用数字双胞胎的特征和行为来定义它的窍门,是吗?
让我们看一个简单的例子
在深入研究其他特征之前,让我们先来看看这个数字双胞胎的行为。
*mosfet = MOSFET()*
在 Jupyter 笔记本中,我们键入这个来测试单行描述,
*mosfet>> Digital Twin of a MOSFET*
我们测试mosfet
对象的状态。
*mosfet._state_>> 'OFF'*
我们以这种方式获取默认参数的字典,
*mosfet._params_>> {'BV': 20, 'Vth': 1.0, 'gm': 0.01}*
现在,如果我们用一些终端和参数值显式定义一个对象,
*mosfet = MOSFET(terminals={'source':0.0,
'drain':0.0,
'gate':2.0},
params={'BV':20,
'Vth':1.0,
'gm':1e-2})*
你认为这个物体是什么状态?让我们检查一下,
*mosfet._state_>> 'ON'*
MOSFET(或其数字孪生)开启,因为栅极和源极之间的电压大于实例中定义的参数 Vth 。我再次重复代码片段,突出显示感兴趣的部分,
*terminals={**'source':0.0**,
'drain':0.0,
**'gate':2.0**}*
和
*params={'BV':20,
**'Vth':1.0**,
'gm':1e-2})*
具有分析模型的方法
接下来,我们定义一种方法,利用一个简单的一阶分析模型计算开态 MOSFET 的漏极至源极电流。代码和等式都在笔记本里,这里我只展示了部分代码片段,
*def id_vd(self,vgs=None,vds=None,rounding=True):
"""
Calculates drain-source current from
terminal voltages and gm
"""
<code>
if state=='ON':
if vds <= vgs - vth:
ids = self._params_['gm']*(vgs - vth - (vds/2))*vds
else:
ids = (self._params_['gm']/2)*(vgs-vth)**2
<more code...>*
更有趣的是,检查数字孪生是否可以产生类似于物理设备的电流-电压特性。对于漏极* - 源极电压范围,我们可以计算出一系列漏极 - 源极电流。*
*ids = []
mosfet = MOSFET()
vds_vals = [0.01*i for i in range(1,501)]
for v in vds_vals:
ids.append(mosfet.id_vd(vgs=3.0,vds=v,rounding=False))*
结果看起来非常符合一般的理想 MOSFET 特性。这里 Vds(x-轴)代表漏极* - 源极电压,Ids(y-轴)代表漏极 - 源极电流。*
图片来源:作者创作
分析模型还涉及栅极* - 源极电压(Vgs),因此,我们可以计算一系列 Vgs 的 Ids 与 Vds 曲线。结果看起来像是,*
图片来源:作者创作
现代数字双胞胎的力量——包括深度学习
数字孪生建模不一定需要来自数据科学或机器学习的工具。然而,在数据驱动模型占主导地位的现代世界中,在任何适用和合适的地方使用这种建模技术都是谨慎的。
这并不意味着你应该从你的数字双胞胎中消除所有的分析模型,并开始在每个特征上投入深度学习模型。作为一名领域专家(或者如果你不是领域专家,在专家的帮助下),你必须确定一个分析和机器学习模型(或者甚至是离散模拟模型)的平衡组合*,以嵌入数字孪生对象中,用于表示物理资产的真实世界特征。*
图片来源:作者创作
数字孪生建模不一定需要来自数据科学或机器学习的工具
用神经网络模拟亚阈值泄漏
那么,在我们的数字孪生对象中,我们应该在哪里应用合适的 ML 技术呢?事实证明,计算(或用 ML 估计器预测)所谓的“亚阈值泄漏”将是一个很好的选择。
这个“亚阈值泄漏”是什么?
如前所述,当 Vgs 低于 Vth 时,MOSFET 处于关断状态。因此,它的理想行为是在漏极和源极之间承载零电流。然而,由于量子力学奇妙的异想天开的本质(细节我们不需要知道),一个真正的 MOSFET 即使在关闭状态下也会携带少量的“泄漏”电流。我们可以尝试使用 ML 模型来计算这一点,特别是深度神经网络(DNN)。
ML 模型——为什么以及如何选择?
那么,为什么我们选择一个解析方程来模拟导通状态下的 Ids-Vds,并选择一个 DNN 来模拟漏电流呢?
这个问题没有对错之分。这取决于几个因素,
- 您拥有的数据的类型和性质
- 你试图模拟的物理过程的性质和复杂性
- 数字孪生模型的性能和精度之间的权衡
在这种情况下,漏电流的测量通常是有噪声的。实际值在某种程度上也是随机的,是制造物理 MOSFET 的材料属性和制造工艺的自然可变性的强函数。物理学还表明,它是端电压和一些其他内部参数的非线性函数。所有这些使得 ML 方法适合于建模这种特性。
对于你创造的每一个数字双胞胎,你必须深入调查,并根据你的判断做出选择。然而,数字孪生的美妙之处在于,它允许你随时用 ML 模型替换分析模型(反之亦然)。
你必须确定一个分析和机器学习模型(甚至是离散模拟模型)的平衡组合,以嵌入数字孪生对象中
模型训练法
模型训练和预测界面的选择是完全灵活的。在此示例中,我们有单独的训练和预测方法。培训方法的代码片段如下所示。
*def train_leakage(self,data=None,
batch_size=5,
epochs=20,
learning_rate=2e-5,
verbose=1):
"""
Trains the digital twin for leakage current model with experimental data
Args:
data: The training data, expected as a Pandas DataFrame
batch_size (int): Training batch size
epochs (int): Number of epochs for training
learning_rate (float): Learning rate for training
verbose (0 or 1): Verbosity of display while training
"""*
请注意其中包含的参数,如batch_size
、epochs
和learning_rate
——熟悉构建深度学习模型的任何人都很熟悉。作为主要输入,您所要提供的就是data
,它应该是一个熊猫数据帧。
在内部,代码构建一个 Tensorflow Keras 模型,对其进行编译和训练,并将训练好的模型保存为数字孪生的内部属性(self.leakage_model
)。
*# Deep-learning model
model = build_model(num_layers=3,
architecture=[32,32,32],
input_dim=3)
# Compile and train
model_trained = compile_train_model(model,
X_train_scaled,
y_train_scaled,
batch_size=batch_size,
epochs=epochs,
learning_rate=learning_rate,
verbose=verbose)self.leakage_model = model_trained*
在这种实现中,隐藏层数和激活函数等超参数是预先固定的,但它们可以很容易地暴露给在现实应用中使用数字孪生的开发人员。
数字孪生的美妙之处在于,它允许你随时用 ML 模型替换分析模型(反之亦然)
ML 的数据源
对于这个演示,我们使用一个特殊的助手函数(包含在笔记本中)创建了一些合成数据来训练 DNN。这个助手包括随机变量来模拟前面提到的制造和测量中的可变性和噪声。
图片来源:作者创作
然而,在现实生活中,半导体工厂/晶圆厂将对数百万个 MOSFETs 运行物理测试套件(使用其他机器),并记录它们在各种 Vgs 偏置电压下的漏电流。它还会记录测试中这些 MOSFETs 的 Vth。所有这些数据将流入数字双胞胎,模型将不断训练和更新。
图片来源:作者创作
通过这种安排,我们只需编写一行代码,用作为df
DataFrame 提供的训练数据来训练模型,
*mosfet.train_leakage(df)*
熟悉的训练开始了,
该预测方法
我们编写了一个单独的泄漏方法来预测跨导、Vgs 和 Vth 作为输入的任何组合的亚 Vth 泄漏。
*def leakage(self,
w_l=1e-2,
vgs=None,
vth=None):
"""
Calculates leakage current using the deep learning model
"""
if not self._leakage_:
return "Leakage model is not trained yet"
# Vgs
if vgs is None:
vgs = self._terminals_['gate'] - self._terminals_['source']
else:
vgs = vgs
# Vth
if vth is None:
vth = self._params_['Vth']
else:
vth = vth
# Predict
x = np.array([w_l,vgs,vth])
ip = x.reshape(-1,3)
result = float(10**(-**self.leakage_model.predict**(ip)))
return result*
代码中的幂运算(10 的幂)是由于我们提供了实际泄漏值的负 10 对数作为 DNN 模型的训练目标。这样做是为了实现更快的收敛和更高的精度。
数字孪生的扩展
数字双胞胎张开双臂欢迎朋友。过道两边也是。
基本上,它们特别适合连接到工业系统——上游和下游——并利用这种连接实现整个工厂/工业系统的最佳行为。在用编程语言设计 Digital Twin 或任何其他现代面向数据的工具时,记住这一点很重要。
连接。交流。合作。这些是打造成功且受欢迎的数字双胞胎的关键。因此,设计师必须仔细评估数据结构、编程平台和 AI/ML 方法的选择,以确保它们符合设计理念。
图片来源:作者创作。 Pixabay 图像已使用(免费使用)
在本文中,我们通过展示 Python 编程框架的一些简单步骤,剥离了数字孪生设计的上层。这取决于你——读者——将这些概念向前推进,并向你选择的系统/资产添加更复杂的编程层、逻辑层和数据科学层,并设计强大的数字双胞胎。
工业 4.0/智能工厂已经在这里,并准备好利用数字化转型、人工智能和机器学习的所有进展。数字双胞胎是这一旅程的重要组成部分。
连接。交流。合作。这些是打造成功且受欢迎的数字双胞胎的关键
喜欢这篇文章吗?成为 中等会员 继续 无限制学习 。如果你使用下面的链接, ,我会收到你的一部分会员费,而不会给你带来额外的费用 。
*https://medium.com/@tirthajyoti/membership *
能源网的数字双胞胎
大数据和机器学习如何塑造清洁能源的未来
电网等物理系统非常复杂,因此很难建模。数字双胞胎提供了一个解决方案。
文森特·范·扎林格在 Unsplash 上拍摄的照片
“数字双胞胎是顶级技术趋势之一”
在这里,我们将讨论由博世工程研究人员进行的文献综述(2021) 。该评论主要关注如何将数字孪生和“大数据”技术应用于复杂的物理系统,如能源电网。
事不宜迟,我们开始吧。
1 —问题
复杂的物理系统很难处理。就以下图为例。在这个由几个城市组成的网络中,有数百万个电力系统需要一触即发的电力。
没有数据的帮助,优化和迭代这样一个大系统是非常困难的。
2 —解决方案
数字双胞胎是物理系统的数字副本。通过数字化物理和供需机制,我们可以对新能源共享政策、电网结构和其他各种以前无法测试的领域进行模拟。
几乎每个实体行业都在广泛采用数字双胞胎,其中一些行业包括供应链优化和火箭设计。
根据文献综述,在能源网格空间中,有 4 个广泛的领域应用了数字双胞胎。让我们依次讨论每一个。
2.1 —资产建模
资产建模关注能量是如何产生的。总的来说,这些模型已经在小规模系统上成功实现,但在电网规模上还没有实现。为这样的系统开发一个有凝聚力的数字双胞胎需要从许多供应商那里收集标准化的数据,比如太阳能发电厂和天然气发电厂。
一些私人公司如 ABB 和 T2 霍尼韦尔已经成功地为他们各自的项目开发了本地资产模型,但是并没有试图将他们的模型扩展到组织之外。
一篇有趣的论文应用机器学习模型来优化本地节点的能量存储,然后按需分配。
另一个明显的应用是负荷,即需求预测。如果我们能够完美地预测电力需求的数量和位置,我们就可以减少存储需求和停电。
最后,让我们用一个有趣的事实来结束这一部分。根据 EIA,约 66%的电力浪费在生产和运输过程中。虽然其中的绝大部分都在发电过程中损失掉了,但即使是 1%的美国电力消耗也相当于数百万美元。
2.2 —故障建模
故障建模侧重于预测和预防硬件故障。断层建模是一个较不发达的领域,但拥有巨大的潜力。
一些潜在的故障原因包括硬件故障、自然灾害和网络攻击。让我们来看看一些解决方案…
一般故障在此处理——对各种故障类型进行分类和系统回顾。如果你对故障诊断的清洁能源方面特别感兴趣,这里有一篇类似的论文关注可再生能源。
在网络安全方面, ANGEL 是一个框架,在这个框架中,一对数字双胞胎与物理网格并行。当观察到差异时,会发出警报。很直观,对吧?
还有一些很酷的离散时间模型被用来预测故障。然而,如上所述,除了几个特定的应用,这一领域的数字双胞胎能源电网优化是相当落后的。
2.3 —运营建模
操作建模关注整个电网的能量分布。通过优化储存和运输能源,我们可以减少上述 66%的数量。然而,与故障建模一样,没有非常健壮的解决方案。
一个很酷的应用专注于开发零能耗建筑。这是一篇利用层次流程图和建筑信息模型来评估和优化建筑设计的论文。本文关注优化建筑设计的热组件。
另一个更具技术挑战性的领域是提供网格负载的实时优化。研究人员和桑迪亚国家实验室已经开发了一个电网负载实时优化框架,专门针对太阳能。
正如你可能想象的那样,实时运营优化可能是数字双胞胎中最具挑战性的应用——它需要硬件和软件在很大程度上协调一致。目前,我们没有非常可靠的数据来开发这些模型。
2.4 —业务建模
最后,商业建模侧重于优化生产者和消费者之间的能源转移经济学。商业是一个特别有趣的应用,因为它可以用来测试广泛的行为相关活动,比如税收减免或总量管制和交易系统。
首先,数字双胞胎是非常有效的交流工具。它们在以前无法测试的市场中提供概念验证,例如整个城市。对于寻找资金的初创公司来说,这些模型与可视化相结合,可以成为无价的融资工具。
论文中提到的一个商业模式描述了一个能源交易市场。在这个系统中,像你我一样的终端用户成为电力的临时所有者。如果你有剩余,你可以按市价出售。如果没有 digital twin 框架,测试这个模型将需要大量的工作。
3 —摘要
现在你知道了!数字孪生及其在能源领域的应用概述。
这些系统的进一步开发允许快速原型和新框架的评估。他们还可以通过提供当前系统的副本进行比较来增强当前操作。
这项技术还远远没有发展起来,但它为企业家精神和更大规模的商业扩张提供了大量的机会。
感谢阅读!我会再写 26 篇文章,把学术研究带到 DS 行业。查看我的评论,链接到这篇文章的主要来源和一些有用的资源。
客户流失的维度建模
从原始 Salesforce 案例数据到数据仓库的有组织维度和事实表。
好的,大约 3 个月前,一个利益相关者想了解为什么客户会从 XYZ 产品中流失。客户流失是指客户取消订购我们提供的服务或产品的情况。这个利益相关者对这个直截了当的问题和业务的组成部分感到好奇是很公平的。当然,像 Shopify 这样规模和重要性的公司已经有了这方面的漂亮的模型数据。这些数据肯定会在一份现成的自助式报告中,这样我们就可以专注于更重要的事情,比如解决世界饥饿问题的机器学习算法,对吗?
没有。至少对 XYZ 产品来说不是。
所以今天,我写的是我为产品流失建立一个维度模型的经历,而不是我为世界饥饿建立的机器学习算法,这需要等待。
学完本课程后,您将会很好地理解我们是如何以及为什么以这种方式做事的,并有望了解如何将它应用到您的业务中!
广场一号。
像现在的许多企业一样,Shopify 在 Salesforce 中管理大量的客户资料、关系和互动。Shopify 使用 Salesforce 中的“案例”来记录、审查和批准产品 XYZ 的流失请求。
鉴于上述情况,我们理解客户流失的起点是一个原始的不干净的 salesforce 案例数据集,看起来像这样;
乍一看,这似乎不是一个太糟糕的数据集,我们有许多重要的关键字,如 customer_id 和 date,我们可以使用它们来获得有关这些客户流失案例的更多信息。然而,当我们试图想象这一点时,我们会遇到一些问题:
- 有不同的案例风格(特征 X!=特征 X)
- 我们在不同的标签下有相似的理由(定价,低感知价值)。我们或许应该创建一张地图,将类似的流失原因归入总体类别。
- 我们应该把我新的热门播放列表发给 105 号顾客🔥
鉴于上述情况,并且因为客户流失是我们希望长期了解的业务的一个组成部分,所以我们进行前期投资并为客户流失创建一个清晰的维度模型是有意义的。
你可能会问,什么是维度模型?谷歌会告诉你;
维度建模(DM) 是一种针对数据仓库中的数据存储而优化的数据结构技术。维度建模的目的是优化数据库,以便更快地检索数据。维度建模的概念是由 Ralph Kimball 提出的,由“事实”表和“维度”表组成。
快速便捷地检索数据对于快速做出重大决策至关重要,这也是 Shopify 的一项重要文化价值观。因此,我们需要易于被产品 XYZ 或客户流失主题的新分析师理解的数据,并创建一个几乎无摩擦的路径来回答以下问题:
- 客户流失趋势如何?
- 哪些客户因定价问题而受到影响?
- 最突出的流失原因是什么?
- 客户流失需要多长时间?
那么我们如何做到这一点呢?让我带你看看我的过程。
了解业务流程。
对我来说,起点是很好地理解业务流程以及它与我们的数据集的关系。在我们的例子中,业务流程是由账户管理团队提交客户流失请求。他们用于提交案例的字段将出现在我们的案例数据集中。我在下面为我们描绘了这个业务流程。
给定我们的输入数据集和上面的流程,我们知道以下内容:
- 流失请求可以被取消
- 流失请求在完成之前需要审批
- 单个流失请求可以与多个原因相关联
- 流失原因需要被格式化和分组
所以现在我们认为我们理解了业务流程,我总是去检查拥有流程的团队和我的高级数据科学家,这样他们就可以告诉我哪里错了。在对他们的反馈进行几个周期的迭代并使每个人都一致后,我们可以进入下一步。
设计你的维度模型。
此时,我们知道了我们想要回答的问题,我们知道了业务流程,现在是时候开始策划我们的数据仓库设计了。
为了能够回答我们上面概述的问题,我们知道在我们的模型中可能需要下面的列。
时间戳:
- 请求打开于
- 请求结束于
描述性数据:
- 流失原因(功能 X、功能 Y、定价、讨厌的 Mike 播放列表)
- 流失原因类别(产品局限性、低感知价值、支持体验)
- 客户详细信息
当前状态数据:
- 请求是成功完成、取消还是待定?
除此之外,我们将利用现有的客户、员工、日期和地区维度来获得有助于理解客户流失的其他见解,如客户年龄或地区。
知道我们有事务性数据(客户流失请求可以被认为是一个事务)和伴随这些事务的描述性数据(客户流失原因、客户流失原因类别)的混合,这很好地表明我们将需要事实和维度。
简而言之,我们可以将事实表视为一个存储度量的地方,以及一个存储与事实度量相关联的描述性属性的维度。在本练习中,我们将有一个客户流失原因维度,这是客户流失事实的一个属性,但与大多数事实一样,我们也能够连接客户维度、日期维度和国家维度。
我们的数据集(总是有一个)的警告是,不幸的是,在业务流程(客户流失请求)和我们想要测量的属性之间存在一对多的关系;流失原因。幸运的是,拉尔夫·金博尔想到了这一点,并给了我们桥梁尺寸的礼物。我们可以使用桥接维度将多值行解析成单独的行,这样我们就可以将一个关键字上的案例流失原因干净地连接到流失原因维度。我们的最终关系将是这样的:
下面是我们的示例数据将如何流经我们的数据模型。
在这个关系中,我们可以通过桥维度和 case_id / churn_reason_key 将客户流失请求事实连接到客户流失原因维度。
然而,在我们构建之前,我们的模型在实践中看起来如何?
构建 SQL 原型。
现在我们知道了模型应该回答的问题,并且我们理解了模型将提供给我们的列和表。让我们检查一下,以确保我们想回答的问题得到了回答!
-
随着时间的推移搅动?
-
已完成的客户流失及相关原因列表?
将有许多方法来剪切和拼接这些数据,但我们的核心问题通过这种设计得到了解决——这是我们需要确定的。
检查一下&发货吧!
很好,概括一下。
- 我们了解业务流程
- 我们理解我们想要回答的问题
- 我们有一个设计可以回答这些问题
作为最后的健全性检查,我召集了利益相关者来获得以下反馈
- 有没有这个模型没有包括的需要解决的核心问题?
- 这个模型灵活吗,能够回答小到必要的粒度的问题吗?
- 这些数据的未来用途是什么?例如,如果它最终可能会被卷到客户维度中,那么这种设计是否容易/可能做到这一点?
一旦所有人都一致了,就该开始构建了!去拿吧。
评论,让我知道你是否有兴趣在后续的帖子中看到这个项目的构建是什么样子!
降维
PCA & T-SNE
介绍
降维的目的是从高维向量中保留尽可能多的信息。主成分分析(PCA) [1]和 T-分布随机相邻实体(T-SNE) [2]是两种最常用的方法。第一种被认为是用数学方法解决问题,而第二种则是用统计学方法。
PCA 的主要目的在于保留数据中具有较高可变性的矢量分量,同时丢弃增加较少信息的分量。这种分解可以通过两种不同的方式实现。一种是通过分解数据协方差矩阵的特征值。第二种方法是在通常将初始数据标准化后,对数据矩阵进行单值分解[1]。
另一方面,T-SNE 将点之间的相似性转换成联合概率。并且最小化低维嵌入和高维数据上的这些概率之间的 Kullback-Leibler 散度。这种方法具有非凸的成本函数,因此,不同的初始化可能产生不同的降维向量[2]。
能够可视化高维数据是至关重要的,尤其是在对执行聚类感兴趣的情况下。根据应用,社区发现算法可以输入不同的阈值参数,这些阈值参数影响集群的大小和连通性。能够可视化数据如何分布允许在选择这些值时使用人类推理。
个案研究
Recipe1M+是一个包含大约 100 万个食谱的数据集。在多个参数中,每个参数都包含各自的成分列表。[3]
https://luisrita.medium.com/recipe1m-dataset-2ecb62a43804
基于这些成分的共现,可以使用单词嵌入(Word2Vec)或图形嵌入(Metapath2Vec)将它们和各自的配方表示为向量。
使用 T-SNE,Recipe1M+中 1480 种成分中的 1000 种被绘制在下面视频的矢量空间中。橙色节点是指具有预测数量的新冠肺炎跳动分子的成分[4]。各自的半径与具有这种性质的不同分子的数量成正比。蓝色节点对应所有其他成分。
视频 11000 种食材的 3D 嵌入。一些具有预测的新冠肺炎跳动特性
同样的方法使我们能够进一步降低 2D 空间向量的维数,并更好地了解食谱中不同成分是如何配对的。允许在选择最佳配对时同时考虑新冠肺炎跳动分子的预测数量。
图 1 2D 包埋与视频 1 中相同的成分。为了清楚起见,只显示了一些成分
这些嵌入允许我们在尝试根据类别(如水果、蔬菜或肉类)对配料进行聚类时选择最佳的模型超参数。并进一步将其用于食谱分类。或者,根据它们在 2D 空间中的距离直接识别最佳配料对(图 2)。
图 2recipe 1m+中具有最多预测新冠肺炎打浆性能的分子的前 5 种成分的最佳配对
参考
[1] I. T. Jolliffe 和 J. Cadima,“主成分分析:回顾与近期发展”,英国皇家学会哲学汇刊 A 数学物理与工程科学,2016 年第 374 卷第 2065 期。
[2] L. v. d. Maaten 和 G. Hinton,“使用 t-SNE 可视化数据”,《机器学习研究杂志》,第 9 卷,第 2579-2605 页,2008 年。
[3] J. Marin,A. Biswas,F. Ofli,N. Hynes,A. Salvador,Y. Aytar,I. Weber 和 A. Torralba,“Recipe1M+:用于学习烹饪食谱和食物图像的跨模态嵌入的数据集”, IEEE 模式分析和机器智能汇刊, 2019。
[4] Laponogov,I .,Gonzalez,g .,Shepherd,M. 等网络机器学习绘制富含植物化学成分的“Hyperfoods”来对抗新冠肺炎。 Hum Genomics 15、 1 (2021)。https://doi.org/10.1186/s40246-020-00297-x
降维备忘单
5 分钟内你应该知道的降维知识
降维。作者图片
在这篇文章中,你会发现一个完整的降维备忘单。在五分钟内,你将能够知道它是什么,并刷新主要算法的记忆。
降维算法代表了一种技术,即减少数据集中特征(非样本)的数量。
在下面的示例中,任务是减少输入要素的数量(将 swissroll 从 3D 展开到 2D ),同时保存最大比例的信息。这就是降维任务和这些算法的本质。
降维例子。公共领域
降维算法的两个主要应用是:
- 数据可视化 & 数据分析 —将输入要素的数量减少到三个或两个,并使用数据可视化技术深入了解数据
- 其他机器学习算法的预备工具。更多的输入特征通常会使预测任务更加难以建模,这就是所谓的维数灾难。由于许多算法(来自监督和非监督学习(例如回归/分类、聚类))不能很好地处理稀疏或高维数据,因此降维算法可以大大提高质量。通常,这也提供了更快和更简单的计算。
方法通常分为:
- 特征选择 —找到输入特征的子集
- 特征投影(或特征提取 ) —寻找原始数据到某个低维空间的最佳投影
接下来,我们将讨论第二组方法。查看特征工程以获得更多特征选择工具,例如套索回归、相关性分析等。事实上,以下算法也可用作特征选择工具,区别在于这些不再是原始特征,而是它们的一些修改(例如在 PCA 情况下的线性组合)。
主成分分析
为了降低维数,主成分分析( PCA )使用原始数据到主成分的投影。主分量是描述残差变化的最大量的正交向量(使用奇异值分解找到它们)。
具有两个主成分的高斯分布的主成分分析。公共领域
因此,通过选择第一个N
主成分(其中N < M, M is the number of features
,我们从 M 维空间移动到 N 维空间,其中新特征是现有特征的线性组合。
为了选择组件的数量,使用了所谓的弯头方法。绘制解释方差的累积和图,然后选择解释所需信息比率的分量数(通常为 80%或 95%)。
PCA 需要数据缩放和居中(sklearn.decomposition.PCA
类自动完成)。
这些算法有很多流行的修改,但最流行的是:
- 增量 PCA —用于在线学习或数据不适合存储时
- 随机化的 PCA —一种允许快速估计前 N 个分量的随机算法
- 内核 PCA — 内核技巧允许执行复杂的非线性投影
流形学习
流形学习算法基于某种距离度量守恒。这些算法在减少维度的同时节省了物体之间的距离。
Scikit Learn 的流形学习方法比较。图像来源
- LLE
LLE ( 局部线性嵌入)研究原始空间中数据点之间的线性连接,然后试图移动到更小的维度空间,同时保持在局部邻域内。这个算法有很多修改,像修改的局部线性嵌入(MLLE) 、基于 Hessian 的 LLE (HLLE) 等等。
- Isomap
Isomap(是等距映射的缩写)с通过将每个实例连接到其最近的邻居来创建一个图,然后在试图保留实例之间的测地线距离(图中两个顶点之间的距离)的同时减少维度。
- t-SNE
t-SNE 代表 t 分布随机邻居嵌入。通过保存空间中各点之间的相对距离来减少维数,这样可以使相似的实例彼此靠近,而不相似的实例彼此分开。最常用于数据可视化。
自动编码器
我们也可以使用神经网络进行降维。Autoencoder 是一个网络,当网络结构暗示一个瓶颈——一个神经元数量比输入层少得多的层时,它会尝试输出与输入尽可能相似的值。
自动编码器结构。公共领域
如果我们使用线性激活函数,我们将得到线性降维规则,像 PCA 。但是如果我们使用非线性激活函数,我们可以得到更复杂的潜在表示。不幸的是,我们必须有大量的数据。幸运的是,这些数据是未标记的,所以通常很容易收集。
至于其他算法,有很多不同的变体,比如:
- 降噪自动编码器可以帮助清理图像或声音
- 处理分布而不是特定值的变型自动编码器
- 图像卷积自动编码器
- 用于时间序列或文本的循环自动编码器
如何选择一种降维算法?
首先,确保你对数据进行了缩放。几乎所有的降维算法都要求这样。
如果你为数据可视化减少维度,你应该首先尝试 t-SNE 。
如果你有很多数据,自动编码器可以帮助你找到非常复杂的潜在表示。
如果没有大量数据,可以尝试 PCA 进行线性降维,流形学习算法 ( LLE , Isomap,等)进行非线性降维。
降维算法选择。作者图片
请注意,几乎每个算法都有许多变体,还有许多其他不太流行的算法,如:
- 非负矩阵分解(NMF)
- 随机预测
- 线性判别分析
- 多维标度(MDS)
- 以及其他等等
本文是以下内容的一部分:
您可能还对以下内容感兴趣:
感谢您的阅读!
- 我希望这些材料对你有用。在 Medium 上关注我可以获得更多类似的文章。
- 如果您有任何问题或意见,我将很高兴得到任何反馈。在评论中问我,或者通过 LinkedIn 或 Twitter 联系我。
- 为了支持我作为一名作家,并获得数以千计的其他媒体文章,使用我的推荐链接获得媒体会员资格(不收取额外费用)。
解释了降维(PCA)
用 Python 解释和实现 PCA
图片来自 Unsplash
目录
- 介绍
- 什么是降维?
- 线性组合
- 特征工程和特征选择
- 数据
- PCA
-直觉
-数学分解
-优点
-缺点
-实现 - 摘要
- 资源
简介
降维是数据科学家常用的机器学习中的一种流行方法。本文将重点介绍一种非常流行的无监督学习降维方法,即主成分分析(PCA)。这是一篇介绍性文章,旨在直观地理解什么是降维、PCA 如何工作以及如何用 Python 实现它。
什么是降维
在跳到降维之前,我们先来定义一下什么是维度。给定矩阵 A,矩阵的维数是行数乘以列数。如果 A 有 3 行 5 列,那么 A 就是一个 3x5 的矩阵。
A = [1, 2, 3] --> 1 row, 3 columns
The dimension of A is 1x3
现在用最简单的术语来说,降维就是它听起来的样子,你把矩阵的维度降低到比现在更小。给定一个正方形(n 乘 n)矩阵 A,目标是将这个矩阵的维数减少到小于 n×n。
Current Dimension of A : n
Reduced Dimension of A : n - x, where x is some positive integer
你可能会问为什么有人想这样做,最常见的应用程序是为了数据可视化的目的。在大于 3 维的空间中,很难形象化地描述事物。通过降维,您将能够将 1000 行和列的数据集转换成一个足够小的数据集,以便在 3 / 2 / 1 维中可视化。
线性组合
无需深入研究线性代数背后的数学知识,降低矩阵 A 维数的最简单方法是将其乘以向量/矩阵 X,使乘积等于 B。公式为Ax=B
,当且仅当 B 是 A 的列的线性组合时,该公式才有解。
示例
The goal is to reduce the matrix 4x4 dimension matrix A to a matrix of 4x2 dimensions. The values below are from a random example.A = [[1,2,3,4], x = [[1,2],
[3,4,5,6], [3,4],
[4,5,6,7], [0,1],
[5,6,7,8]] [4,0]]Ax = [[23,13],
[39,27],
[47,34],
[55,41]]
The output B now has a dimension of 4x2.
特征工程和特征选择
给定包含各种要素的数据集,您可以通过要素工程和要素选择来减少要素的数量。这本身就直观地减少了您正在处理的原始数据集的维度。假设您有一个包含以下各列的数据框架:
您发现,通过做一些算术,您可以组合一组特征,而不会丢失关于这些特征的信息。然后,新特征将替换产生它的特征对。通过某种预处理手段创建新特征的过程称为特征工程,而为训练模型而选择特定特征的过程称为特征选择。
**Example**
Feature 1 + Feature 2 = Feature 6Now your new features are :
Feature 3 | Feature 4 | Feature 5 | Feature 6
数据
我将展示如何使用 NBA 数据通过 sklearn 实现 PCA。你可以直接从我的 GitHub 库这里下载数据,或者从它的原始来源 Kaggle 这里下载
主成分分析
PCA 是一种高度使用的无监督学习技术,用于降低大型数据集的维度。它将大量的变量转换成包含大部分信息的更小的部分。减少数据集的大小自然会导致信息的丢失,并会影响模型的准确性,但这种负面影响会因便于探索、可视化和分析而抵消。
直觉
假设我们正在查看一个数据集,该数据集与一组运动员及其所有相关统计数据相关联。每一项数据都衡量了运动员的各种特征,比如身高、体重、臂展、效率、得分、篮板、盖帽、失误等等。这将基本上为我们提供一个与我们每个球员相关的各种特征的大型数据集,然而,许多特征可能彼此相关,从而产生冗余信息。
PCA 的目的是创造新的特征,总结我们最初的特征。通过寻找旧特征的线性组合,PCA 可以构建新特征,同时尽量减少信息损失。例如,一个新的特征可以计算为一个球员的平均得分减去每场比赛的平均失误数。它通过识别玩家之间强烈不同的特征来做到这一点。
数学分解
数学上,PCA 可以分解为 4 个简单的步骤。
- 确定数据的中心,将数据和中心重新定位到原点
-这可以通过取每列的平均值并减去原始数据的平均值来完成 - 计算中心矩阵的协方差矩阵
- 计算协方差矩阵的特征向量
- 通过点积将特征向量投影到协方差矩阵上
在数学上,它是一个高维对象在一个低维向量空间中的投影。
优势
- 移除相关要素
- 允许更容易的可视化
- 有助于减少过度拟合
不足之处
- 变量变得更难解释
- 信息损失
- 数据标准化
履行
注意:您可能需要更改第 11 行上与您保存数据的位置相关的路径。
NBA 数值特征的二维 PCA 可视化(图片由作者提供)
摘要
降维是机器学习中常用的方法,有许多方法可以降低数据的维度,从特征工程和特征选择到无监督学习算法(如 PCA)的实现。PCA 旨在通过识别线性组合来创建概括数据集初始特征的新特征。
资源
- https://builtin . com/data-science/step-step-explanation-principal-component-analysis
- https://machinelingmastery . com/calculate-principal-component-analysis-scratch-python/
- https://data science . stack exchange . com/questions/60351/intuition-behind-the-PCA-algorithm
- https://stats . stack exchange . com/questions/2691/making-sense-of-principal-component-analysis-features vectors-environments
- https://www . I2 tutorials . com/what-is-the-pros-and-cons-of-the-PCA/
感谢你通读我的文章,如果你喜欢这篇文章,这里有一些你可能感兴趣的我写的其他文章。
[## 贝叶斯 A/B 测试解释
towardsdatascience.com](/bayesian-a-b-testing-explained-344a6df88c1a)
机器学习的降维
理解大数据
Python 中特征约简和选择的初学者指南
什么是高维数据?它如何影响你的机器学习模型?你有没有想过为什么你的模型不符合你的期望,你已经尝试超调参数,直到地球的尽头,没有改善?理解您的数据和模型可能是关键。在这样一个巨大而复杂的罩下,您可能会担心很少有甚至没有方法来获得对您的数据以及您的模型的更多洞察。虽然这看起来令人生畏,但最重要的是,您使用成功的、经过验证的方法来理解正在发生的事情,以使您的模型按照它的方式运行。这将焦点放在降维、预处理和特征工程上。
在我开始调试一个模型或者对它进行超调优化之前,我总是处理我的数据。我发现理解我的数据并以一种使我的模型具有最佳性能的方式处理它将最终决定我的模型参数,甚至在某些情况下决定我的整个模型。虽然预处理和特征工程是它们自己的主题,但我将在本文中讨论降维和选择。
在整篇文章中,我将使用 sklearn 库,因此拥有 sklearn 经验将是一个优势,但不是必需的。此外,对 Python 编程语言以及基本的数据科学/机器学习知识有相当好的理解是理解本文的先决条件。如果你还不知道这两个网站,你可以在我下面的主页上了解它们。我还将提到我为本文创建的 Kaggle 笔记本,它可以在下面列出的链接中找到。
https://scikit-learn.org/ https://medium.com/@third_eye_cyborg
数据集引用:
Dean De Cock (2011)爱荷华州埃姆斯:波士顿住房数据作为期末回归项目的替代方案,统计教育杂志,19:3,DOI:10.1080/10691898 . 11
那么,什么是高维数据呢?
简而言之,高维数据是带有许多特征的数据。如果数据有十个以上的特征,通常被认为是高维的。这些数据集有时会降低您的模型性能,并耗费您宝贵的计算/内存资源。对整个模型没有任何价值的特征也会增加噪音,使模型更难以最佳方式运行。为了解决这个问题,人们开发了许多工具来帮助解决这个问题。其中一些工具我将简单介绍一下。我们将使用 RFE(递归特征消除)模型删除特征行,并在 Kaggle 数据集上使用 PCA(主成分分析)模型选择关键特征,以提供一些常用工具的清晰度,帮助降低数据维数,同时保持模型的性能和准确性。我将在 XGBoost 回归模型上进行这项工作,但是也可以随意使用这些工作流来减少其他模型类型上的数据维数。之后,我会将我们的模型提交给 Kaggle 竞争房价——高级回归技术。
理解特征约简(RFE)和特征选择(PCA)不同于特征提取是非常重要的。特征提取试图从现有特征中创建新的有意义的特征,而特征缩减试图通过丢弃对整体模型精度或性能不重要的特征数据的列或行来减少特征的数量。而特征选择试图找到属于数据的最关键特征,当涉及目标变量预测时,该数据解释了模型的大部分准确性或性能。找到这些较少的关键特征听起来可能很容易,但可能相当复杂。第一种可进行的要素缩减是通过任意丢弃您认为对模型整体精度不重要的要素来缩减要素。当某个要素的值很明显时,这种要素约简方法会很有用,但在更复杂的数据集上就没那么有用了。
原始模型
xgb = XGBRegressor(objective=’reg:squarederror’)
原模型输出:
output:
17662.736729452055
递归特征消除
https://scikit-learn.org/stable/modules/generated/sklearn.feature_selection.RFE.html
顾名思义,RFE 模型本质上是递归的。他们试图通过用所有原始特征训练一个估计器,然后用较少的特征重新训练来减少特征。它重复这个过程,直到找到最佳数量的特征。这种类型的特征约简很受欢迎,因为它实现起来非常简单和有效。RFE 模型的应用可以远远超出本教程所涵盖的内容,因为这只是一个初学者的基本例子。
RFE 模型采用估计量(又名。模型)作为第一个参数自变量。然后将特征的数量减少到。在示例代码中,我选择将特性从 36 个减少到 30 个。可以通过.ranking_
属性得到排名。您也可以通过.support_
获得选择的列。由于 RFE 模型的递归性质,它们通常有运行较慢的趋势。
作者的 RFE 模型
output:
{'MSSubClass': 100, 'LotFrontage': 42, 'LotArea': 4, 'OverallQual': 1, 'OverallCond': 1, 'YearBuilt': 2, 'YearRemodAdd': 1, 'MasVnrArea': 6, 'BsmtFinSF1': 1, 'BsmtFinSF2': 12, 'BsmtUnfSF': 18, 'TotalBsmtSF': 1, '1stFlrSF': 11, '2ndFlrSF': 1, 'LowQualFinSF': 60, 'GrLivArea': 1, 'BsmtFullBath': 24, 'BsmtHalfBath': 79, 'FullBath': 54, 'HalfBath': 51, 'BedroomAbvGr': 23, 'KitchenAbvGr': 1, 'TotRmsAbvGrd': 47, 'Fireplaces': 1, 'GarageYrBlt': 5, 'GarageCars': 1, 'GarageArea': 1, 'WoodDeckSF': 43, 'OpenPorchSF': 52, 'EnclosedPorch': 21, '3SsnPorch': 57, 'ScreenPorch': 8, 'PoolArea': 1, 'MiscVal': 90, 'MoSold': 39, 'YrSold': 78, 'MSZoning_C (all)': 1, 'MSZoning_FV': 3, 'MSZoning_RH': 96, 'MSZoning_RL': 110, 'MSZoning_RM': 1, 'Street_Grvl': 146, 'Street_Pave': 147, 'Alley_Grvl': 9, 'Alley_Pave': 89, 'LotShape_IR1': 74, 'LotShape_IR2': 82, 'LotShape_IR3': 91, 'LotShape_Reg': 1, 'LandContour_Bnk': 1, 'LandContour_HLS': 62, 'LandContour_Low': 121, 'LandContour_Lvl': 64, 'Utilities_AllPub': 158, 'Utilities_NoSeWa': 164, 'LotConfig_Corner': 33, 'LotConfig_CulDSac': 50, 'LotConfig_FR2': 27, 'LotConfig_FR3': 190, 'LotConfig_Inside': 86, 'LandSlope_Gtl': 45, 'LandSlope_Mod': 56, 'LandSlope_Sev': 166, 'Condition1_Artery': 1, 'Condition1_Feedr': 113, 'Condition1_Norm': 13, 'Condition1_PosA': 126, 'Condition1_PosN': 1, 'Condition1_RRAe': 30, 'Condition1_RRAn': 16, 'Condition1_RRNe': 118, 'Condition1_RRNn': 191, 'Condition2_Artery': 193, 'Condition2_Feedr': 196, 'Condition2_Norm': 195, 'Condition2_PosA': 182, 'Condition2_PosN': 162, 'Condition2_RRAe': 172, 'BldgType_1Fam': 177, 'BldgType_2fmCon': 178, 'BldgType_Duplex': 116, 'BldgType_Twnhs': 114, 'BldgType_TwnhsE': 88, 'HouseStyle_1.5Fin': 97, 'HouseStyle_1.5Unf': 122, 'HouseStyle_1Story': 66, 'HouseStyle_2.5Fin': 155, 'HouseStyle_2.5Unf': 168, 'HouseStyle_2Story': 125, 'HouseStyle_SFoyer': 123, 'HouseStyle_SLvl': 46, 'RoofStyle_Flat': 37, 'RoofStyle_Gable': 36, 'RoofStyle_Gambrel': 159, 'RoofStyle_Hip': 140, 'RoofStyle_Mansard': 134, 'RoofStyle_Shed': 169, 'RoofMatl_CompShg': 138, 'RoofMatl_Membran': 141, 'RoofMatl_Metal': 173, 'RoofMatl_Roll': 136, 'RoofMatl_Tar&Grv': 115, 'RoofMatl_WdShake': 143, 'RoofMatl_WdShngl': 14, 'MasVnrType_BrkCmn': 148, 'MasVnrType_BrkFace': 80, 'MasVnrType_None': 152, 'MasVnrType_Stone': 68, 'ExterQual_Ex': 93, 'ExterQual_Fa': 179, 'ExterQual_Gd': 104, 'ExterQual_TA': 71, 'ExterCond_Ex': 197, 'ExterCond_Fa': 7, 'ExterCond_Gd': 32, 'ExterCond_Po': 176, 'ExterCond_TA': 26, 'Foundation_BrkTil': 69, 'Foundation_CBlock': 94, 'Foundation_PConc': 98, 'Foundation_Slab': 153, 'Foundation_Stone': 132, 'Foundation_Wood': 192, 'BsmtQual_Ex': 1, 'BsmtQual_Fa': 73, 'BsmtQual_Gd': 15, 'BsmtQual_TA': 150, 'BsmtCond_Fa': 76, 'BsmtCond_Gd': 112, 'BsmtCond_Po': 175, 'BsmtCond_TA': 106, 'BsmtExposure_Av': 84, 'BsmtExposure_Gd': 1, 'BsmtExposure_Mn': 61, 'BsmtExposure_No': 1, 'BsmtFinType1_ALQ': 63, 'BsmtFinType1_BLQ': 101, 'BsmtFinType1_GLQ': 1, 'BsmtFinType1_LwQ': 95, 'BsmtFinType1_Rec': 83, 'BsmtFinType1_Unf': 174, 'BsmtFinType2_ALQ': 77, 'BsmtFinType2_BLQ': 99, 'BsmtFinType2_GLQ': 185, 'BsmtFinType2_LwQ': 120, 'BsmtFinType2_Rec': 187, 'BsmtFinType2_Unf': 171, 'Heating_Floor': 183, 'Heating_GasA': 181, 'Heating_GasW': 188, 'Heating_Grav': 194, 'Heating_OthW': 17, 'Heating_Wall': 189, 'HeatingQC_Ex': 34, 'HeatingQC_Fa': 102, 'HeatingQC_Gd': 72, 'HeatingQC_Po': 180, 'HeatingQC_TA': 105, 'CentralAir_N': 1, 'CentralAir_Y': 161, 'Electrical_FuseA': 87, 'Electrical_FuseF': 20, 'Electrical_FuseP': 157, 'Electrical_Mix': 170, 'Electrical_SBrkr': 108, 'KitchenQual_Ex': 1, 'KitchenQual_Fa': 1, 'KitchenQual_Gd': 38, 'KitchenQual_TA': 10, 'Functional_Maj1': 186, 'Functional_Maj2': 184, 'Functional_Min1': 1, 'Functional_Min2': 25, 'Functional_Mod': 107, 'Functional_Typ': 19, 'FireplaceQu_Ex': 167, 'FireplaceQu_Fa': 35, 'FireplaceQu_Gd': 44, 'FireplaceQu_Po': 165, 'FireplaceQu_TA': 92, 'GarageType_2Types': 124, 'GarageType_Attchd': 1, 'GarageType_Basment': 156, 'GarageType_BuiltIn': 85, 'GarageType_CarPort': 154, 'GarageType_Detchd': 48, 'GarageFinish_Fin': 55, 'GarageFinish_RFn': 53, 'GarageFinish_Unf': 75, 'GarageQual_Ex': 135, 'GarageQual_Fa': 70, 'GarageQual_Gd': 40, 'GarageQual_Po': 133, 'GarageQual_TA': 1, 'GarageCond_Ex': 163, 'GarageCond_Fa': 28, 'GarageCond_Gd': 160, 'GarageCond_Po': 151, 'GarageCond_TA': 109, 'PavedDrive_N': 22, 'PavedDrive_P': 149, 'PavedDrive_Y': 41, 'PoolQC_Ex': 145, 'PoolQC_Fa': 139, 'PoolQC_Gd': 119, 'Fence_GdPrv': 29, 'Fence_GdWo': 59, 'Fence_MnPrv': 67, 'Fence_MnWw': 65, 'MiscFeature_Gar2': 144, 'MiscFeature_Othr': 131, 'MiscFeature_Shed': 130, 'SaleType_COD': 49, 'SaleType_CWD': 142, 'SaleType_Con': 127, 'SaleType_ConLD': 128, 'SaleType_ConLI': 103, 'SaleType_ConLw': 58, 'SaleType_New': 1, 'SaleType_Oth': 129, 'SaleType_WD': 111, 'SaleCondition_Abnorml': 1, 'SaleCondition_AdjLand': 117, 'SaleCondition_Alloca': 137, 'SaleCondition_Family': 81, 'SaleCondition_Normal': 31, 'SaleCondition_Partial': 198}output:
16787.60477311644
产量大,比原来的型号好,功能少!
主成分分析
https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.PCA.html
PCA 模型试图选择最佳的关键特征。首先创建一个模型,然后用它进行拟合和预测。请注意申请是多么简单。此外,PCA 模型被认为速度更快。同样,存在更复杂的 PCA 架构,应予以考虑。我总是建议好好阅读这些文件。
作者的五氯苯甲醚模型
output:
18985.349823416094
不是很好的输出,但很接近,我们的数据噪音较小。让我们看看现在是否可以用减少的数据获得更好的 MAE 评分。
让我们加快速度
作者提出的更好的 PCA 模型
output:
17447.526995933218
很好!该模型比具有较少特征的原始数据集获得了更好的结果!
臣服于 Kaggle
虽然这不一定会是一个好成绩,但它将演示如何在 Kaggle 竞争数据集上进行降维。我将提交 RFE 模型的结果,因为它产生最低的平均绝对误差。
作者提交的 Kaggle 文件
按作者输出
得分
总的竞争分数只有0.15381
,这并不是什么了不起的事情,但是很高兴知道,如果你正在处理高定义的数据,你总是可以将它减少到重要的特征,并剔除其余的。
结论
降维的应用远远超出了本文所包含的内容。我推荐阅读关于 RFE 模型和 PCA 模型的文档。模型选择、结构、体系结构、参数调整以及对输出进行详细分析,这些都是能够产生比示例中更好结果的方法。此外,如果你看到我犯的任何错误,请随时给我发信息、回复或给我发电子邮件info@thirdeyecyborg.com,我会尽快改正。感谢您的阅读和快乐编码!
python 中使用 PCA 进行彩色图像压缩的快速指南
使用 python 逐步解释如何使用 PCA 对彩色图像进行降维
如果你是数据科学或机器学习的爱好者,你一定遇到过 PCA(主成分分析),这是一种流行的无监督机器学习算法,主要用于大型数据集的降维。我们也可以使用 PCA 对图像进行降维。
一个简单的用例
让我们考虑一个案例,你正在做一个处理图像的 AI-ML 项目。通常情况下,图像有许多像素来保持其清晰度,但当系统必须处理多幅图像时,这会显著增加其大小并降低系统的性能。为了克服这种情况,我们可以使用无监督机器学习下的降维技术。为什么我们不检查一下 PCA 在这种情况下是否有用?我们将在本文中使用一张图片,并减少其尺寸,或者换句话说,使用 python 中的 PCA 压缩图像。
在文章的最后,我们将比较结果图片与原始图片,以验证我们的努力。
我在之前的文章中解释了主成分分析背后的数学原理。如果您尚未浏览,您可以点击此处浏览。
现在让我们开始吧!
我们首先将图像分成三个通道(蓝色、绿色和红色),然后对代表每个通道的每个数据集分别执行 PCA,然后将它们合并以重建压缩图像。因此,如果我们的彩色图像的形状是(m,n,3),其中(m X n)是图像在三个通道(b,g,r)上的像素总数。
我们也可以执行相同的事情,而不分裂成蓝色、绿色和红色通道,并且将数据整形为(m,n×3)像素,但是我们已经发现,如果我们使用前面段落中提到的分裂方法,由相同数量的 PCA 分量给出的所解释的方差比会更好。
我将使用下面的照片进行演示。
作者照片
加载并预处理图像
让我们先导入库:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
import cv2
from scipy.stats import stats
import matplotlib.image as mpimg
现在让我们阅读图像 rose.jpg 并显示它。
img = cv2.cvtColor(cv2.imread('rose.jpg'), cv2.COLOR_BGR2RGB)
plt.imshow(img)
plt.show()
输出:
使用以下代码检查形状:
img.shape
输出:
(485, 485, 3)
现在,我将图像分成 3 个通道,并显示每个图像:
#Splitting into channels
blue,green,red = cv2.split(img)# Plotting the images
fig = plt.figure(figsize = (15, 7.2))
fig.add_subplot(131)
plt.title("Blue Channel")
plt.imshow(blue)fig.add_subplot(132)
plt.title("Green Channel")
plt.imshow(green)fig.add_subplot(133)
plt.title("Red Channel")
plt.imshow(red)plt.show()
输出:
我们来验证一下蓝色通道的数据:
blue_temp_df = pd.DataFrame(data = blue)
blue_temp_df
输出:
我将所有通道的所有数据除以 255,以便数据在 0 和 1 之间缩放。
df_blue = blue/255
df_green = green/255
df_red = red/255
拟合和转换 PCA 中的数据
我们已经看到,每个通道有 485 个维度,现在我们将只考虑 PCA 的 50 个维度,并拟合和转换数据,检查在将数据减少到 50 个维度后解释了多少差异。
pca_b = PCA(n_components=50)
pca_b.fit(df_blue)
trans_pca_b = pca_b.transform(df_blue)pca_g = PCA(n_components=50)
pca_g.fit(df_green)
trans_pca_g = pca_g.transform(df_green)pca_r = PCA(n_components=50)
pca_r.fit(df_red)
trans_pca_r = pca_r.transform(df_red)
我们已经拟合了 PCA 中的数据,让我们检查每个通道的变换图像的形状:
print(trans_pca_b.shape)
print(trans_pca_r.shape)
print(trans_pca_g.shape)
输出:
(485, 50)
(485, 50)
(485, 50)
这是意料之中的。让我们检查每个通道的 50 个 PCA 分量(即最主要的 50 个特征值)的解释方差比率的总和。
print(f"Blue Channel : {sum(pca_b.explained_variance_ratio_)}")
print(f"Green Channel: {sum(pca_g.explained_variance_ratio_)}")
print(f"Red Channel : {sum(pca_r.explained_variance_ratio_)}")
输出:
Blue Channel : 0.9946260772755372
Green Channel: 0.9918219615668648
Red Channel : 0.987736292777275
哇,太棒了!因为只使用 50 个组件,我们可以保留数据中大约 99%的方差。
让我们绘制条形图,分别检查 3 个通道的每个特征值的解释方差比:
fig = plt.figure(figsize = (15, 7.2))
fig.add_subplot(131)
plt.title("Blue Channel")
plt.ylabel('Variation explained')
plt.xlabel('Eigen Value')
plt.bar(list(range(1,51)),pca_b.explained_variance_ratio_)fig.add_subplot(132)
plt.title("Green Channel")
plt.ylabel('Variation explained')
plt.xlabel('Eigen Value')
plt.bar(list(range(1,51)),pca_g.explained_variance_ratio_)fig.add_subplot(133)
plt.title("Red Channel")
plt.ylabel('Variation explained')
plt.xlabel('Eigen Value')
plt.bar(list(range(1,51)),pca_r.explained_variance_ratio_)plt.show()
输出:
重建图像并可视化
我们已经完成了 PCA 降维。现在,我们将再次可视化图像,为此,我们必须首先反向转换数据,然后将所有 3 个通道的数据合并为一个。我们继续吧。
b_arr = pca_b.inverse_transform(trans_pca_b)
g_arr = pca_g.inverse_transform(trans_pca_g)
r_arr = pca_r.inverse_transform(trans_pca_r)print(b_arr.shape, g_arr.shape, r_arr.shape)
输出:
(485, 485) (485, 485) (485, 485)
我们可以将数据逆变换为原始形状(尽管每个通道仍然是分离的),但正如我们所知,所有图像都已经被压缩。
我们将把所有的通道合并成一个,并打印出最终的形状:
img_reduced= (cv2.merge((b_arr, g_arr, r_arr)))
print(img_reduced.shape)
输出:
(485, 485, 3)
看到我们一开始导入的原始图像的精确形状真是太好了。现在我们将并排显示两幅图像(原始图像和缩小图像)。
fig = plt.figure(figsize = (10, 7.2))
fig.add_subplot(121)
plt.title("Original Image")
plt.imshow(img)fig.add_subplot(122)
plt.title("Reduced Image")
plt.imshow(img_reduced)plt.show()
输出:
令人惊讶的是,压缩后的图像与原始图像非常相似(至少我们仍然可以识别出它是一朵玫瑰),尽管我们已经将每个通道的维度从 485 减少到了 50。但是,我们已经实现了我们的目标。毫无疑问,现在计算机处理缩小的图像会快得多。
结论
我已经解释了我们如何使用 PCA 通过将彩色图像分成 3 个通道来降低其维度,然后将其重建回来用于可视化。
我希望你喜欢阅读这篇文章并从中学习。
你可以从下面提到的 github 链接下载完整的代码和我在这里展示的图片,它们在同一个目录下:
图像数据集的降维
使用 Ivis 降低超大型数据集的维度
在 Unsplash 上由 Hitesh Choudhary 拍摄的照片
使用高维数据创建机器学习模型会导致模型具有非常高的方差和过度拟合,这意味着该模型不够一般化以用于看不见的数据。在数据科学领域,它也被称为维数灾难。
为了摆脱这种维数灾难,我们可以对数据进行降维处理,这样不仅可以使模型具有足够的泛化能力,而且对未知数据具有更好的性能和更高的精度。有不同的 Python 库有助于降维。Ivis 就是这样一个库。
Ivis 是一个开源 Python 库,用于降低非常大的数据集的维度。它是可扩展的,这意味着它是快速和准确的,也是通用的,这意味着它可以用于解决多个问题。
在本文中,我们将探讨 Ivis 及其功能。
让我们开始吧…
安装所需的库
我们将从使用 pip 安装来安装 Ivis 开始。下面给出的命令将使用 pip 安装 Ivis。
!pip install git+https://github.com/beringresearch/ivis
导入所需的库
在这一步中,我们将导入加载数据、降低数据维度以及可视化数据所需的所有库。
import random
import numpy as np
import tensorflow as tf
from ivis import Ivis
from sklearn.datasets import fetch_rcv1
from sklearn.utils import resample
from sklearn.decomposition import TruncatedSVD
import matplotlib.pyplot as plt
正在加载数据集
我们将在本文中使用的数据集将从 Sklearn 加载。该数据集的名称为 RCV1,由于它是一个巨大的数据集,因此需要一些时间来加载。
rcv1 = fetch_rcv1()
rcv1.data.shape
数据集(来源:作者)
创建模型
现在,我们将创建模型,并使用 IVIS 对其进行训练,以降低维度。
N_SAMPLES = 10000
X, y = resample(rcv1.data, rcv1.target, replace=False,
n_samples=N_SAMPLES, random_state=1234)
X = TruncatedSVD(n_components=150).fit_transform(X)
ivis = Ivis(model='maaten', n_epochs_without_progress=5)
ivis.fit(X)
经过训练的模型(来源:作者)
可视化维度
现在我们将把减少的维度形象化。
from IPython.display import set_matplotlib_formats
set_matplotlib_formats('retina')
embeddings = ivis.transform(X)
plt.scatter(embeddings[:, 0], embeddings[:, 1], s=0.1, c=np.argmax(y.toarray(), axis=1))
尺寸(来源:作者)
在这里,你可以清楚地看到使用 IVIS 嵌入减少的维度。尝试使用不同的数据集,并在回复部分告诉我您的意见。
本文是与皮尤什·英格尔合作完成的。
在你走之前
感谢 的阅读!如果你想与我取得联系,请随时通过 hmix13@gmail.com 联系我或我的 LinkedIn 个人资料 。可以查看我的Github*简介针对不同的数据科学项目和包教程。还有,随意探索* 我的简介 ,阅读我写过的与数据科学相关的不同文章。
自动编码器与 PCA 的降维比较
神经网络可以像经典的主成分分析一样进行降维吗?
PCA(左)和 Autoencoder(右)降维示例。
介绍
主成分分析是最流行的降维算法之一。主成分分析的工作原理是找出在相互正交的数据中占最大方差的轴。 i ᵗʰ轴称为 i ᵗʰ主分量(PC)。执行 PCA 的步骤是:
- 将数据标准化。
- 从协方差矩阵或相关矩阵中获取特征向量和特征值,或者进行奇异值分解。
我们将使用 sklearn 的实现来执行 PCA:它使用来自 scipy ( [scipy.linalg](https://docs.scipy.org/doc/scipy/reference/linalg.html#module-scipy.linalg)
)的奇异值分解(SVD)。SVD 是 2D 矩阵 a 的因式分解,它可以写成:
s 是一个 1D 数组,包含了 A 的奇异值; U 和 V^H 为一元(uᵗu=uuᵗ= I)。大多数 PCA 实现执行 SVD 以提高计算效率。
另一方面,自动编码器 ( AE )是一种特殊的神经网络,它被训练成将其输入复制到其输出。首先,它将输入映射到一个降维的潜在空间,然后将潜在表示编码回输出。AE 通过减少重建误差来学习压缩数据。
具有编译码器结构的自动编码器的简单结构。
我们马上会看到如何实现和比较 PCA 和 Autoencoder 的结果。
我们将使用来自 sklearn 的 make_classification 生成我们的数据,这也将为我们提供一些标签。我们将使用这些标签来比较我们的方法之间的聚类效率。将会有三个部分。
首先,我们将实现一个简单的欠完整线性自动编码器:也就是说,一个具有比输入维数低的单层的自动编码器。第二部分将介绍堆叠式线性自动编码器。在第三部分,我们将尝试修改激活函数,以解决更多的复杂性。
结果将与 PCA 进行图形比较,最后我们将尝试使用简单的随机森林分类算法和交叉验证来预测类别。
数据准备
下面是设置数据的所有代码:首先,我们导入必要的库,然后用 scikit-learn 的“make classification”方法生成数据。在 50 个特征中,我们将指定只有 15 个是信息性的,并且我们将约束我们的归约算法只挑选 5 个潜在变量。
一旦我们有了数据,我们就把它分成训练-验证-测试。我们将使用神经网络(NN)的验证数据,而测试集将用于检查最终的一些分类性能。
最后,我们标准化我们的数据:注意,我们仅在训练数据集上拟合缩放器,然后转换验证和测试集。
生成—拆分—数据标准化。
左侧的数据关联热图显示了一些相关的要素,但大多数并不相关。在右侧,我们绘制了特征的方差,从中我们可以推断出只有 10–15 个变量可以为我们的数据集提供信息。
数据集的关联热图(左)。每个变量的方差(右)。
欠完整线性自动编码器
在本节中,我们将看到如果我们执行这些操作,数据会发生什么变化:
- 将数据压缩到 5 个潜在维度
- 使用线性激活函数
- 使用“MSE”作为损耗度量
让我们来设置这个 AE :
自动编码器
首先,我们定义编码器模型:注意,输入形状被硬编码到数据集维度,并且潜在空间被固定为 5 维。解码器模型是对称的:在这种情况下,我们指定 5(潜在维度)的输入形状,其输出将是原始空间维度。最终的模型“autoencoder”将耦合这两者。
然后,我们使用随机梯度下降 SGD 优化器编译具有均方误差度量的自动编码器,固定学习率为 0.1
在拟合步骤中,我们简单地指定验证数据,并且如果验证损失没有改善,则使用早期停止回调来停止训练。
最后,我们可以只使用模型的第一部分“encoder . predict(X _ tr _ STD)来计算编码。这个模型非常简单:
自动编码器的型号摘要。
AE 的学习曲线。
从学习曲线中,我们看到拟合在 60 个时期后停止,损失约为 0.7。产生的前两个编码如下所示:
编码器原始数据的前 2 个潜在维度。
PCA 实现:使用 sklearn 很简单。
下面我们绘制了前 3 个组件的 PCA(左图)和编码器(右图)的聚类训练数据。
PCA 的组件 0–1–2(左)和编码器(右)之间的聚类比较。
从上面的图来看,似乎两种算法都以相似的方式进行了降维。很难推断哪个表现更好。让我们来看看这些编码的一些性质。
下面我们绘制了 PCA(左)和 Autoencoder(右)组件的标准偏差。
成分的标准偏差。
从这些柱状图中,我们可以看出,对于与 PCA 相关的数据,主成分的标准偏差随着每一个主成分的减少而减少,这是意料之中的。另一方面,所有编码的标准偏差几乎相等。
下面我们绘制了 PCA(左)和编码器(右)组件的相关图。
PCA 成分(左)和编码(右)的皮尔逊相关热图。
从该图中我们还可以看到,虽然 PC 彼此正交:它们不相关,但对于编码来说却不是这样。它们之间显示出某种相关性。事实上,我们的神经网络模型对这种行为没有任何限制。网络找到了在潜在空间中映射原始输入的最佳方式。
从原始空间到两个不同潜在空间的分类性能如何?有区别吗?我们可以使用交叉验证来试验 RandomForestClassifier,看看结果。代码如下:
三个数据集的分类分数。
从上面的柱状图中我们可以看到,除了 5 个以外,其他的都被移除了,这降低了预测的准确性。PCA 和 AE 数据减少的性能是可比较的,然而 AE 的性能比 PCA 稍差。
现在,我们将尝试了解更复杂的 AE 叠加是否会导致更好的结果。
线性堆栈编码器-解码器
这里,我们实现了一个编码器-解码器网络结构,但在这种情况下,我们将堆栈更多的隐藏层。这应该允许网络更灵活地学习不同的、最具信息性的特征。
编码器层数为:50(输入)—20–15–5。损失与之前的不良事件相当。解码器正好对称。组件如下所示。
PCA(左)和 AE(右)的前 3 个成分散点图。
分类性能方面的结果是:
不同数据集之间的分类性能。
显然,叠加隐藏层比简单的声发射效果更好。然而,这些层的确切配置可以针对具体问题进行微调。
非线性堆叠编码器-解码器
非线性堆叠 AE 将容易实现为具有激活功能的堆叠 AE。我们还在 SGD 优化器上引入了一个衰减常数,这样学习率就会随着时间的推移而降低。我们选择“卢瑟”作为所有层的激活层。请注意,这里我们已经增加了更多的复杂性:我们可以尝试找到最佳数量的隐藏层,最佳的激活函数和形状的每一层的具体问题。
以下是前三个组成部分:
非线性激活的 PCA(左)和 AE(右)的前三个组件。
至于分类性能,允许网络学习非线性特征有助于提高整体性能,这似乎比 PCA 平均好,但在误差范围内。
所有数据简化算法的分类性能。
结论
我们使用来自 sklearn 的 make_classification 构建了一个玩具数据集,用于分类练习,包含 50 个特征。我们的目的是比较 PCA 和自动编码器神经网络,看看维数减少是否具有可比性。
我们查看了分数/编码的属性,我们看到来自 AE 的编码具有一些相关性(协变矩阵不像 PCA 中那样是对角的),并且它们的标准偏差是相似的。
从只有 1 层的简单线性欠完整 AE 开始,我们看到增加的复杂性有助于模型达到更好的性能,根据分类准确性进行评估。
最后,我们看到,非线性模型仍然可以比其他两个模型(单层 AE 和堆叠 AE)执行得更好,但性能仍然可以与该数据集中的 PCA 相媲美。
我们现在可以回答最初的问题:神经网络可以像经典的主成分分析一样进行降维吗?
答案是肯定的,它可以像 PCA 一样执行维数减少,因为网络将找到在潜在空间中编码原始向量的最佳方式。但不,潜在向量的属性是不同的,网络本身可能需要针对特定的任务进行调整,以便更好地执行,就像我们对更多隐藏层所做的那样。
DINO:自监督视觉变压器中的新兴特性摘要
对脸书惊人的视觉变形金刚迪诺的深入总结
DINO 是脸书人工智能公司开发的一个新的自我监督系统,能够从未标记的数据中学习令人难以置信的表示。下面是一个视频,显示了它的注意力地图,我们看到该模型能够自动学习特定于类的特征,从而实现精确的无监督对象分割。这是在他们的论文“自我监督视觉变形金刚的新兴特性”中介绍的
这里有一个它如何工作的总结👇
TLDR;
网络:
这个网络通过一个叫做“自我升华”的过程来学习。有一个教师和学生网络都具有相同的架构,一个视觉转换器(ViT) 。
老师是一个动量老师这意味着它的权重是学生权重的指数加权平均值。在论文“无监督视觉表征学习的动量对比”中引入了动量老师,以便在老师和学生是相同的并且不管输入如何都输出相同的嵌入时防止模式崩溃。
教师权重的更新规则是:
等式 1:更新规则,其中θt 和θs 是教师和学生的权重(来源)
在本文的训练期间,λ遵循从 0.996 到 1 的余弦规律。
数据:
正如在自我监督学习中常见的那样,对一幅图像进行不同的裁剪。小作物称为局部视图(<50% of the image) and large crops( >图像的 50%)称为全局视图。
所有作物都通过学生传递,而只有全局视图通过教师传递。 这鼓励“地方到全球”的对应,训练学生从少量作物中插入上下文。 见图 1。
颜色抖动、高斯模糊和曝晒的随机增强也应用在视图上,以使网络更加健壮。
失败
教师和学生各自预测一维嵌入。应用 softmax 和交叉熵损失来使学生的分布与教师的分布相匹配
Softmax 就像一个归一化,它转换原始激活来表示每个特征相对于整体的存在程度。例如)[-2.3,4.2,0.9,2.6,6]-->[0.00,0.14,0.01,0.03,0.83]因此,我们可以说最后一个功能的强度为 83%,我们希望学生的也是如此。因此,我们要求我们的学生网络拥有与教师相同的功能比例。 具有较大上下文的教师预测学生也必须匹配的更多高级特征。
交叉熵损失试图使两个分布相同,就像在知识提取中一样。
这也可以看作是一个虚构的分类问题。我们要求我们的网络提出一个分类问题,这样网络就可以从局部视图中学习有意义的全局表示。
图 2:迪诺流量(来源)
居中和锐化
模式折叠有两种形式:不管输入是什么,模型输出在所有维度上都是相同的(即任何输入的输出都是相同的),或者由一个维度支配。居中和锐化旨在防止这两种情况。
居中:从教师的原始激活中减去其指数移动平均值。简单来说就是:
Logits = Logits-Logits _ mean
这意味着当激活高于平均值时,激活有时必须为正,当低于平均值时,激活有时必须为负。 这防止了任何一个特征占主导地位,因为平均值将处于范围的中间。我们知道 softmax 给负数很低的值,给正数很高的值。
锐化:锐化等同于对 softmax 应用一个温度,人为地使分布更加尖峰 ,即放大微小的差异,从而有一个或一些高值和一些低值。这防止了所有的激活都是相同的值,因为小的差异被夸大了。这与不断改变高激活的中心协同作用。锐化也有助于学生获得更强的信号,它应该增加哪些功能。
DINO Psedudocode:
图 3: DINO Psedudocode( 来源)
可视化:
这里有一些注意力地图显示,迪诺能够专注于图像中感兴趣的物体。这意味着 DINO 很好地理解了对象语义,它的注意力图看起来就像分段掩码。
图 4:注意力地图(来源
我们还发现,恐龙的潜在空间甚至在动物群体中也有很好的分类,这意味着它的特征足够丰富,可以区分物体的微小差异。这使得它非常适合下游任务和迁移学习。
它在识别重复图像方面也做得很好,如下所示。
总结:
学生 ViT 学习在动量老师 ViT 的嵌入的交叉熵损失的监督下,从局部面片预测图像的全局特征,同时进行居中和锐化以防止模式崩溃…哇,这是一大堆术语!
— — — — — — — — — — — — — — — — — — — — — — — — — —
自监督视觉变压器中新兴特性的相关链接:
GitHub:【https://github.com/facebookresearch/dino
论文:https://arxiv.org/abs/2104.14294
https://twitter.com/schrep/status/1388189398496202752的推文:https://twitter.com/schrep/status/1388189398496202752
扬尼克:https://www.youtube.com/watch?v=h3ij3F3cPIk&t = 2106s
Dionysos 2.0🍇:葡萄酒推荐系统和互动品尝空间
如何将一个人的口味与他们可能喜欢的葡萄酒相匹配(另外:根据他们的口味对葡萄酒进行最先进的可视化)
狄俄尼索斯,或巴克斯,是希腊罗马的酒神。他可能会批准这项工作。图片由我的好友马西莫·德安东尼斯制作
H ello 那里。今天我将继续我之前在葡萄酒世界的冒险,以及如何利用它的数据。事实上,在我上一篇文章的结束语中,我想知道为每一瓶酒制作一个单独的口味配置文件会有多有趣,这样就可以与一个人的口味偏好相匹配。事实证明,没有必要在葡萄酒销售网站上工作来做到这一点。对了,这叫“推荐系统”。
今天的轶事风格将有所不同。它不会是一个编码教程,因为有很多这样的教程,由比我更有资格的老师制作。虽然告诉你下面的一切都可以通过 Python 来完成可能有些有趣,而且网上有很多免费资源可以学习如何做。
无论如何,我想谈两件事:第一,如何将一个人的口味与他们潜在喜欢的葡萄酒相匹配,这是一个简单得令人麻木的过程。第二个是创新的、互动的、数据驱动的葡萄酒地图,根据成千上万的用户的判断。据我所知,这是第一次制作这样的地图,所以我很兴奋能展示它。
首先,我们需要关于葡萄酒的数据,特别是关于人们给每瓶葡萄酒赋予某种口味的数据。在野外没有这样的数据集,所以必须从头开始构建。一种方法是编写一个“ webscraper ”来从一个葡萄酒销售网站上收集这样的数据。现在这在技术上是不允许的,但是有很多博客帖子公开说他们已经做到了,所以把我也算进去。此外,从浪漫的角度来看,这可以被视为对罗宾汉传奇的现代扭曲,每天从我们这里提取数据的网站本身就是他们计划的受害者。不谦虚的文学比较放在一边,我没有从中赚到任何东西,所以它应该是好的。
因此,我们神奇地拥有了一个葡萄酒的数据集,并对它们的味道特征进行了估计,这是非常简洁的。显然,为了减少数据中的噪音,我们将选择一些阈值,只包括具有一定数量用户投票的葡萄酒。然后,我们需要用户的口味档案,包括他或她对葡萄酒瓶的所有评价,这些葡萄酒有自己的口味特征。对我们来说幸运的是,这也很容易免费获得。我随机挑选了一个用户作为案例研究,因为他碰巧喜欢我最喜欢的一瓶酒,因此显然品味不错。我们就叫他“Em”。
Em 喜欢许多不同的葡萄酒,它们的口味特征会有很大的不同,所以我们将缩小他最喜欢的类型的模型:波尔多红酒。下一步将包括对他的用户数据运行线性回归(即 " 一种机器学习算法 " ),然后将我们的模型推广到葡萄酒数据中的其余瓶子。
让我们来看一下这个过程:
首先,分配给葡萄酒的味道特征太多了,所以我们需要在不丢失信号的情况下压缩它们。这些特征也高度共线,这实质上意味着它们是相关的,这对模型是不利的。举个简单的例子,如果我们考虑“咖啡”、“摩卡”和“浓缩咖啡”等口味,那么来自不同用户的投票就有道理了,这些投票分散在所有这三个实际上来自同一源头的特征上:一种类似咖啡的味道。手动处理这个问题的想法令人望而生畏。幸运的是,有更好的选择;其中之一就是主成分分析 (PCA)。
用一个非常非技术性的解释来说,PCA 所做的是“挤压”数据集中的特征,同时保持它们的描述性。正是我们需要的!多亏了主成分分析,我们可以从 200 多种味觉特征减少到 20 种,所以它实际上是在不断发现新的原型味觉。缺点是这些压缩的特性不能像原始的那样被解释,但是这不是问题,你马上就会明白为什么。
我们将主成分分析应用于所有的数据,即用户,或"训练,数据集和葡萄酒,或"测试,数据集。否则,数据集之间的压缩要素将不匹配。然后,我们可以对用户数据运行线性回归,或“假设”,将评分作为因变量。这样做的目的是在用户的评分和压缩的口味特征之间建立一条线。通过这样做,它"学习"将什么数字分配给系数,这些系数乘以味觉特征值以确定评级。如果这不是 100%清楚的,我有一种感觉可能不是,我会尝试用好的线性回归公式来做到这一点:
希望这澄清了上述内容。这些系数也被称为贝塔系数(这里使用的符号)或西塔系数,这并没有什么帮助,这取决于你问的是谁。数学是关于精确性和一致性的,对吗?作者图片
答然而,正如你所看到的,我们获得的这些β/θ/系数可以乘以任何葡萄酒的相应味道特征的值,给出 Em 对该瓶酒的预测评级。
换句话说,它们代表了 Em 对波尔多红酒独特口味偏好的组合。我说过这很容易,不是吗?
显然,这个模型应该应用于与训练集一致的葡萄酒。我对 307 瓶波尔多梅多克葡萄酒(这是 Em 评级最高的葡萄酒)进行了测试。在下面,您可以滚动查看 Em 潜在喜欢和不喜欢的葡萄酒的所有口味特征的值(即它们的预测评级高于或低于某些阈值)。葡萄酒的名称和价格被故意省略了。
我们正在查看 PCA 压缩前的原始数据,因此味道特征仍然有意义。事实上,研究结果似乎表明,新兴市场更喜欢带有黑醋栗和红色水果味道以及小维多葡萄味道的朴实和烟熏葡萄酒,而不喜欢带有强烈巧克力和皮革味道以及番茄味道的葡萄酒。
当然,检验这一点的唯一方法是让他们品尝这些葡萄酒,然后告诉我们他的想法。由于我个人并不了解他,我一直在记录自己喜欢和不喜欢的葡萄酒,以便最终在自己身上进行实验。如果你有这种类型的数据,并想尝试让我知道!
最近,最大的葡萄酒销售网站之一为他们的用户推出了这样一个品酒匹配服务。我敢打赌,它和我在这里展示的不会有太大的不同。
P.S 机器学习爱好者会注意到,我跳过了“交叉验证”这一步,在这一步中,我会避免使用用户数据中的部分葡萄酒来训练算法,而是通过比较预测评级与实际评级,使用这个子集来评估学习的贝塔系数的有效性。然而,考虑到这篇文章的范围,这一步被忽略了。从更严格的角度来看,这可能是最基本的。
既然我们已经解决了这个问题,
让我们进入帖子的第二部分。它与前一部分有联系,因为我们将对数据集中的所有葡萄酒应用 PCA 的进化版本。可以把这想象成类固醇上的主成分分析,它可以用来压缩数据,同时把实体之间的各种关系表示为二维(或三维)地图上的距离。在这种情况下,我使用的是 UMAP ,如果你被这种算法吸引,我建议你看看 亚历克斯·泰利亚 的作品,他在这个主题上做了一些相当有趣的事情。
不管怎样,通过这个算法,我制作了一个有意义的、可探索的葡萄酒地图,根据成千上万的用户对葡萄酒的口味(T21)进行排列。或者更具体的说,一张 3834 款酒的地图,来自 110 不同特产,按照他们在 169 不同口味中的分数排列,由大约 492 投票产生。 516 独特的人。
我不是要和任何侍酒师以及他们对这一主题的严格研究唱反调,但这 50 万人的集体思维可能是一个比酒瓶上的简要描述更可靠的葡萄酒味道评估者。
让我向你展示交互式的地图,你可以在那里探索这个地图。据我所知,你将是第一批有机会这样做的人。这和探索一个新的大陆是不一样的,但它仍然很迷人,不是吗?
在一位精通 JavaScript 的朋友的指导下,我添加了按钮,突出显示与相应口味相关的葡萄酒。这些口味可以按瓶分类,也可以按整个品种分类。我选择了后者,因为它给出了更多关于为什么算法选择在二维空间中排列葡萄酒的见解,尽管它使用的唯一信息是单个瓶子的味道分数。
不幸的是,我不能在这里直接嵌入互动情节,所以你必须去这个链接:【https://dionysus-stempio.netlify.app/。与此同时,这里有一个 GIF 预览:
您可以放大和缩小、单击和拖动来浏览地图。每个点对应一瓶葡萄酒,并根据其地理来源(底部的图例)进行着色。当您点击一个品尝按钮时,具有所选口味的葡萄酒专业名称将出现在右上角,其对应的点将保持彩色,而不具有所选口味的点将变为透明。将鼠标悬停在单款葡萄酒上,会显示出它们的葡萄和特色。作者 Gif
又一次,葡萄酒的名称和价格被故意省略了。虽然我短暂地修补了将情节转变为一种工具的想法,在这里人们可以看到高级奢侈葡萄酒的廉价替代品,应该在味道方面得到类似的评判。然而,最后我还是决定不要。众所周知,人类不善于将葡萄酒的价格与其客观口味区分开来。况且其他奢侈品也是如此(有人说苹果吗?).
我认为在像波尔多的葡萄酒城这样的博物馆里给游客们一个视觉上更加修饰的地图版本可能是个不错的主意,甚至可能是三维的。我向他们提出了这个想法,但从未收到_(ツ)_/的回复
除非我有新的灵感,否则这个关于葡萄酒及其数据的系列到此结束!尽管很少有东西像好酒一样令人兴奋,但我会在这个数据时代寻找新的惊喜。
感谢阅读!
如果你想知道哪种葡萄酒是最受赞赏的特色酒,请参考以下图片:
仅包括额定至少 40 瓶的专业产品。作者图片
CSV 到 MATLAB 结构的目录
一个很好的小递归函数,用于将表格读入 MATLAB
汤姆·波德莫尔在 Unsplash 上的照片
我在工作中经常处理的一件事是将 CSV 文件读入 MATLAB。通常情况下,我有一个嵌套的目录集,其中包含。csv 文件,需要解析成我的代码。主要的烦恼是不能保证有多深和多少。csv 文件将位于子目录中。那么我们如何解决这个问题呢?递归!
示例目录结构
假设我们有以下需要解析的结构:
具有 CSV 的多级目录结构(由作者使用 Creately 创建的图像)
目的是加载所有这些。csv 文件转换为具有与上述相同命名约定的结构,并且每个端点节点都是读入 MATLAB 的实际表格。一旦我们有了这个,生活就会变得容易处理得多。
递归
这是完成所有工作的函数:
**function configurationObject = structifyCSV(entry_path)** configurationObject = struct;d_struct = dir(entry_path); for i=1:length(d_struct) if d_struct(i).isdir==0 && contains(d_struct(i).name,’.csv’) d = readtable([d_struct(i).folder ‘/’ d_struct(i).name]); configurationObject.(strrep(d_struct(i).name,’.csv’,’’))=d; else if d_struct(i).isdir==1 && ~ismember(d_struct(i).name
{‘.’,’..’}) % Go deeper ** co = structifyCSV([d_struct(i).folder ‘/’
d_struct(i).name]); % RECUR (1)** configurationObject.(d_struct(i).name)=co; end end endend
伪码
这里棘手的部分是确保您正确选择了有效的。用于 readtable 加载的 csv 文件或可能包含其他文件的新子目录。csv 文件和子文件夹。作为一个额外的技巧,当执行 dir 命令时,它将传回一个也包含' . '的结构和‘..’用作当前目录替代的目录('.')并向上移动一个目录(“..”).
在对 dir output 命令的循环中,我首先使用 ~isdir 检查传回的内容是否是一个文件,以及名称字符串是否包含。csv 子串。如果这两个条件都满足,那么我们可以使用 readtable 加载它。
在另一种情况下,如果我们看到它是一个目录,我还要确保它不是两种特殊情况的成员。,..).(NB: ismember 是目前为止我最喜欢的功能!).如果这都是真的,那么这个函数调用它自己,然后再做一遍!
在上面的代码中,你可以看到,在注释(1)中,我们只是再次调用了函数。随着深度的不断增加,它会越来越频繁地重现,然后爆炸!你得到了你想要的。
你可以这样称呼它:
configurationObject = structifyCSV([my_path]);
摘要
MATLAB 很棒,但是有些任务有时有点烦人。这是我面临的一个常见问题,所以将逻辑放在一个可重用的函数中会让事情变得容易得多。未来的凯尔感谢我。
机器学习中的关键数据集 BookCorpus 的肮脏秘密
公平和偏见
仔细看看 BookCorpus,这是一个帮助 Google、OpenAI、Amazon 和其他公司训练大型语言模型的文本数据集
据 HuggingFace 报道,BookCorpus 已经帮助训练了至少三十个有影响力的语言模型(包括谷歌的 BERT ,OpenAI 的 GPT ,以及亚马逊的 Bort )。
但是 BookCorpus 里面到底是什么呢?
这是我和 Nicholas Vincent 在一篇新的工作论文中提出的研究问题,这篇论文试图解决机器学习研究中的一些“文档债务”——这是由 Emily M. Bender 博士和 Timnit Gebru 博士等人在他们的随机鹦鹉论文中讨论的概念。
虽然自从 BookCorpus 首次推出以来,许多研究人员已经使用了它,但文档仍然很少。介绍该数据集的原始论文将其描述为“来自网络的 11,038 本书的语料库”,并提供了 6 项汇总统计数据(7400 万个句子,9.84 亿个单词等。).
我们决定仔细看看——这是我们的发现。
一般注意事项
首先要注意的是,BookCorpus 包含了来自 Smashwords.com 的书籍样本,该网站自称是“世界上最大的独立电子书分销商”
截至 2014 年,Smashwords 托管了33.64 万本图书。相比之下,在同一年,国会图书馆总共收藏了 23,592,066 本编目图书(大约是它的 70 倍)。
收集 BookCorpus 的研究人员下载了每本超过 20000 字的免费书籍,结果是 11038 本——占所有 Smashwords.com 书籍的 3%。但是正如下面所讨论的,我们发现这些书中有数千本是重复的,只有 7185 本是独特的,所以 BookCorpus 只是所有关于词汇书籍的 2%的样本。
在完整数据表中,我们提供了关于资金的信息(谷歌和三星是资金来源之一)、BookCorpus 的原始用例(句子嵌入),以及数据表标准中概述的其他细节。在这篇博文中,我将重点介绍一些更令人关注的发现。
🚨主要关切
🙅🏻侵犯版权
2016 年, Richard Lea 在 The Guardian 中解释说,谷歌没有征得 BookCorpus 作者的同意,这些作者的书有助于推动谷歌的技术。
更进一步,我们发现有证据表明 BookCorpus 直接违反了数百本不应该通过免费数据集重新分发的书籍的版权限制。例如,BookCorpus 中有超过 200 本书明确声明“不得出于商业或非商业目的复制、拷贝和分发”
我们还发现,包括在免费图书语料库数据集中的至少 406 本书现在需要在数据集的来源 Smashwords 上付费。截至 2021 年 4 月,购买这 406 本书将花费 1182.21 美元。
📕📕复制书籍
BookCorpus 通常被描述为包含 11,038 本书,这是原作者报告的内容。然而,我们发现数千本书是重复的,事实上数据集中只有 7185 本书是唯一的。具体细分如下:
- 4,255 本书出现一次(即没有重复)
- 2101 本书出现两次📕📕
- 741 本书出现了三次📗📗📗
- 82 本书出现了四次📘📘📘📘
- 6 本书出现了 5 次📙📙📙📙📙
📊倾斜的流派表现
与一个名为 BookCorpusOpen 的新版本和另一个关于 Smashwords 的所有书籍的数据集(Smashwords21)相比,原始的 BookCorpus 有一些重大的体裁偏差。下表列出了所有详细信息:
值得注意的是,BookCorpus 过度代表了言情小说类型,考虑到自助出版的更广泛模式,这并不奇怪(作者一直发现言情小说的需求量很大)。它还包含了相当多的吸血鬼类型的书籍,这些书籍可能已经被淘汰,因为 Smashwords21 中没有出现吸血鬼书籍。
就其本身而言,在训练大型语言模型时,偏斜的表示可能会导致问题。但是当我们看一些“浪漫”小说时,很明显一些书引起了更多的关注。
需要进一步关注的⚠️潜在问题
🔞有问题的内容
虽然在确定 BookCorpus 中有问题内容的程度方面还有更多工作要做,但我们的分析表明它确实存在。举个例子,想想《图书文集》中的一本名为 的小说《警察和咖啡店里的女孩》 。
https://www.smashwords.com/books/view/308710
这本书的序言明确指出“这本书的内容面向 18 岁以上的人。”这本书的标签包括“强势男性”和“顺从女性”
虽然有见识的成年人阅读这样的书可能没有什么害处,但将它作为训练材料喂给语言模型会导致这些技术中有据可查的性别歧视。
🛐教潜在地扭曲了宗教代表性
当谈到歧视时,最近推出的大胆框架还建议关注世界上最常见的七种宗教:锡克教、犹太教、伊斯兰教、印度教、基督教、佛教和无神论。
虽然我们还没有适当的元数据来全面分析 BookCorpus 中的宗教表现,但我们确实发现 BookCorpusOpen 和 Smashwords21 表现出偏斜,这表明这也可能是原始 BookCorpus 数据集中的一个问题。下面是细目分类:
需要做更多的工作来澄清 BookCorpus 原始版本中的宗教表述,然而,BookCorpus 确实使用了与 BookCorpusOpen 和 Smashwords21 相同的来源,所以类似的偏差是可能的。
⚖️不平衡的作者贡献
另一个潜在的问题是不平衡的作者贡献。同样,我们还没有完整分析 BookCorpus 所需的所有元数据,但我们可以根据 Smashwords21 数据集进行估计。
在 Smashwords21 中,我们发现作者的贡献相当不平衡,前 10%的作者贡献了数据集中所有单词的 59%。单词贡献大致遵循帕累托原则(即 80/20 法则),排名前 20%的作者贡献了所有单词的 75%。
同样,在图书贡献方面,前 10%的作者贡献了所有图书的 43%。我们甚至发现了一些“超级作者”,比如已经出版了 800 多本书的肯尼斯·基。
如果 BookCorpus 从整体上看与 Smashwords.com 相似,那么数据集中的大部分书籍可能是由少数作者写的。在许多情况下,研究人员可能希望在使用数据集时考虑这些不平衡的贡献。
下一步是什么?
有了数据集文档的新 NeurIPS 标准,甚至有了专门用于数据集的全新轨道,追溯文档工作的需求(就像这里展示的)有望下降。
与此同时,像这样的努力可以帮助我们理解和改善支持机器学习的数据集。
如果你想了解更多,你可以在这里查看我们的 代码和数据 ,以及 全文在这里 ,其中包括以下总结我们发现的“数据卡:
如果你有问题/评论,请到 Github 讨论 !也可以直接伸手 尼克 或者 本人 。感谢阅读到最后🙂
求职中的失望——如何应对职位被拒
没有人在处理拒绝方面是完美的,有时这会很痛
图片来自像素上的安德里亚·皮亚卡迪奥
生活充满了失望,但我们在网上看到的都是人们庆祝他们的新工作、胜利和积极的一面。老实说,我自己也这么做。当我在 LinkedIn 上发帖时,我在分享我和我的团队的积极经历。我发布我们取得的成功、我这一年的领导经验以及我参加的活动。很少,如果有的话,我分享失望和困难。最近,我看到其他人开始分享他们在求职中的挣扎,这激励我分享我在寻找成为数据科学经理的过程中的经历。找工作很有挑战性,经常会遇到没有答案的问题、被忽视的邮件和拒绝。
从我自己的经历来看,拒绝可能是最具挑战性的部分,尤其是当拒绝带来的问题多于答案的时候。在过去的几年里,当我申请轮岗项目、管理职位等等时,我已经处理了我的拒绝份额。
据 Glassdoor 报道,大多数公司的招聘信息会吸引 250 份简历。在这 250 份简历中,只有 4 到 6 名候选人会被通知参加面试,只有 1 人会得到这份工作。意味着你被拒绝的几率很高。有鉴于此,TalentWorks 表示,在你得到一份工作之前,可能需要 100 到 200 份或更多的求职申请。
看着这些统计数据,我不禁想起了自己的经历:在找到合适的公司之前,我发出了数百份求职申请,而这家公司看到了我能带来的价值。在同一个职位上接下来的两年让我开始考虑我的选择,以及我希望我的职业生涯走向何方。2020 年第三季度,我开始寻找新的东西。我申请了公司内部的竞争性领导轮岗计划。在一轮我需要回答的虚拟视频问题之后,我获得了面试机会。我会见了来自轮岗计划委员会的三个人,并与他们进行了很好的交谈。他们问了我目前的职位、职业目标以及处理不同情况的行为风格问题。
征求反馈
不到一个月,我收到了回复,是否定的。我没有盲目地接受拒绝,而是主动向项目负责人寻求反馈。我想了解这个决定背后的动机,以及我本可以做得更好的地方。在一年后的下一次申请之前,有没有我需要改进的地方?
这种策略并不总是奏效。有些公司不会回复你。幸运的是,在这个职位上,项目负责人确实伸出了手。面试小组喜欢我的申请和我的背景。我的垮台?他们不得不在我和另一位候选人之间做出艰难的决定。另一位候选人的职业目标与这个项目更接近,这巩固了我的拒绝。有时他们不得不做出艰难的决定,这可以归结为一些小细节。
了解你的职业目标
征求反馈回来的好消息是一个关于职业建议的会议。我会见了项目负责人,并与她和人力资源部讨论了其他选择。在这次通话中,她进一步讨论了我申请的项目,以及另一个将于 2021 年开放的技术项目。她相信即将到来的技术项目将会与我的目标紧密结合,在当时,她是对的。如果我觉得我还想重新申请被拒绝的轮岗项目;她鼓励我在 2021 年第三季度这样做。
当我开始寻找新职位时,我非常关注技术。我的目标是成为公司的技术人员。作为一个没有管理和领导的技术人员,技术研究员是你能达到的最高水平。在过去的一年半时间里,随着我继续在我的职业生涯中穿梭,我已经与来自各种背景的不同人进行了多次一对一的交流。我想了解我可以选择的不同道路。在此期间,从我的队友那里得到反馈,我开始调整我的职业目标,使之与我最重要的技能保持一致。我把我的思维模式从技术发展转向了领导力发展。我想开始发展团队,培养人才,实现以数据为中心的愿景。
当我继续寻找其他职位申请时,一个新的机会出现了。我公司内一个新成立的团队正在寻找一名数据科学经理。在担任团队领导一年多后,我有了新的职业目标,我决定我已经准备好尝试管理角色了。我更新了我的简历,在导师的帮助下写了一封措辞强硬的求职信,强调了我的成就和我能给团队带来的东西,并提交了三份推荐信。这些人包括一名业务经理、一名首席数据科学家和一名主管。
在我看来,我的技能得到了很好的应用和展示。来自我的队友的反馈包括对我的工作和我将成为的经理类型的积极看法。尽管如此,我的申请还是被拒绝了。我从来没有机会面试和证明我的技能,因为招聘团队正在寻找一个工程 4 级的人,而我是 3 级。
建立关系网,畅所欲言
在我今年申请的所有东西中,我最难接受这次拒绝。我的申请得到的唯一反馈是,尽管我的技能和背景表明我能胜任这份工作,但我没有达到他们要求的水平。在没有其他反馈和目标的情况下,我做了退而求其次的事情。我联网了。
我开始与公司不同部门的管理层进行一对一的会谈。这些 1:1 会议是小时会议,讨论我目前的角色、职业目标,并为我希望在未来担任的职位辩护。我想确保公司里的其他人知道我想要什么,这样他们就能帮我找到合适的机会。如果没有人知道你在寻找什么,你不为自己主张,你将如何前进?
这个故事没有快乐的结局,因为它还没有结束。与我领导的团队和其他同事的讨论鼓励我继续沿着我的道路前进,并为成为领导者的职业目标而奋斗。我仍在我目前的职位上,并在寻找下一步的目标,但我在为自己辩护,并与企业中能帮助我实现目标的人合作。我想建立我的团队,制定我们的路线图,培养我的员工。在有人给我我想要的职位之前,我会继续在我目前的职位上做一名领导者,并为我的同事争取权益。
当你面对拒绝,努力寻找下一个角色时,请记住:
- 征求对你的申请的反馈。他们可能不会提供,但至少值得要求。
- 了解自己的职业目标。在接下来的一年、五年甚至更长的时间里,你想实现什么目标?你想在哪里提高自己的技能?这将帮助你找到符合这些目标的合适机会。
- 找到那些愿意为你辩护的人,和他们一起工作。这些人会帮助你找到下一个职位。大声点!这是你谈论你是谁和你想去哪里的时候了。让人们知道你的目标。
没有人能完美地处理拒绝,有时这很伤人,但想办法从中吸取教训,看看什么才是适合你的。
如果你想阅读更多,看看我下面的其他文章吧!
Azure 存储、SQL、Synapse 和 Cosmos DB 的灾难恢复场景
数据中心的服务器,图像由 Kvistholt 摄影在 Unsplash 上拍摄
了解如何为 Azure 中的数据服务规划灾难恢复
0.介绍
灾难恢复的目的是在发生破坏性事件后保持 IT 系统的连续性。数据服务是每个 IT 系统的重要组成部分,应针对这些事件进行保护。大多数 Azure PaaS 数据服务都有支持区域冗余的服务层。这意味着当灾难局限于单个区域时,数据服务不会受到影响,或者影响被最小化。但是,一些破坏性事件需要更多的规划,而不仅仅是选择正确的层。这些事件分为以下三种主要情况:
- 区域性灾难:由自然灾害(如断电、地震)导致的区域内多个数据中心的中断。
- 全局灾难:升级错误或计划内基础架构维护期间发生的意外问题。
- 客户错误:由应用程序错误或人为错误导致的数据损坏或删除。
这些场景用于规划以下服务的灾难恢复:Azure 数据湖存储、SQLDB、Synapse SQL 池和 Cosmos DB。作为参考,下面已经提供了一个概述。
Azure PaaS 数据服务灾难恢复场景概述—作者图片
在本概述中,可以看到区域和全球灾难恢复是一起讨论的。基本原理是成对的区域应该用于规划在发生区域性灾难时的灾难恢复。成对区域的一个好处是顺序更新用于防止两个区域都受到升级错误或计划外维护错误的影响。此外,使用区域恢复命令 ,以便在断电的情况下优先恢复两个区域中的一个。
最后,应该为整个 IT 系统规划灾难恢复,而不仅仅是孤立的数据服务。将本文的范围限制在数据服务的基本原理如下:
- 数据服务是每个 IT 系统的重要组成部分。人们可以从数据服务的灾难恢复计划开始,并从那里推断出所坚持的应用程序需要什么。
- 企业经常怀疑什么数据服务用于什么用例。本文可能有助于确定哪种数据服务最能满足灾难恢复需求。
在本文的剩余部分,将讨论 Azure Blob 存储、Azure 数据湖存储、SQLDB、Synapse SQL 池和 Cosmos DB 的灾难恢复。
1a。灾难恢复— Azure Blob 存储
Azure Storage 针对存储大量非结构化数据进行了优化。地理冗余存储在主区域中被复制至少三次,然后被复制到辅助区域。万一发生区域性/全球性灾难,存储帐户上的数据不会丢失。关于可用性(读/写数据)和客户错误,请参见以下段落。
1a.1 .区域性/全球性灾难
可以进行以下测量:
- 如果应用程序只需要读取数据(并且写入数据可以被推迟或暂存到另一个存储帐户),应用程序可以使用存储帐户的配对区域中的次端点。请看这个 git 项目应用程序如何检测到一个区域关闭并开始使用辅助端点
- 对于写入,可以向辅助帐户发起故障转移。请注意,在故障切换后,辅助帐户变成了 LRS。此外,故障切换不会自动启动,需要一些时间。或者,应创建自定义解决方案,其中应用程序将数据写入不同区域的存储帐户/队列。
1a.2 .客户错误
可以进行以下测量:
- blob 存储支持时间点还原恢复(PITR ),可以在文件级和容器级完成,请参见此处的和。然而,PiTR 使用相同的存储帐户来保护客户免受错误,但不能防止使用恶意软件加密数据的恶意实体。在这种情况下,需要备份,见下一条。完整的 datalake 恢复解决方案请看我的后续博客:https://towards data science . com/how-to-recover-your-azure-data-lake-5b 5e 53 f 3736 f
- 为了保护存储帐户免受恶意实体的攻击,可以创建到另一个存储帐户的自定义增量备份。创建备份可以使用数据工厂基于时间,也可以使用 blob 触发器基于事件。请看这里的例子 git 项目。
1b。灾难恢复— Azure 数据湖存储
Azure 数据湖存储是一个使用分层文件结构而不是基于对象的存储的存储帐户。使用真正的文件夹结构,管理任务(如重命名文件夹)可以轻松完成。它具有与常规 Azure Storge 相同的耐用性,但是尚不支持 PiTR 和故障转移。请参阅关于灾难恢复的后续段落。
1b.1 .区域性/全球性灾难
可以进行以下测量:
- 与常规 Azure Blob 存储类似,可以使用一个辅助端点进行读取。
- 对于写入,Azure 数据湖存储不支持故障转移。应创建自定义解决方案,其中应用程序将数据写入不同区域的存储帐户/队列。如果主区域再次启动,以后将与主区域的存储账户合并。或者,一旦 Azure 数据湖存储启动并再次运行,可以在重新运行中暂停数据接收。
1b.2 .客户错误
可以进行以下测量:
- Azure 数据湖存储尚不支持 PiTR,但是,文件快照可用于创建自定义 PiTR 解决方案
- 为了保护存储帐户免受恶意实体的攻击,可以创建一个定制的备份解决方案,如前一段和本示例 git 项目中讨论的常规存储。
- 由于 Azure Data Lake Store 经常从原始/管理/瓶装区域中的 Azure Data Factory 管道接收和存储数据,因此也可以决定重新运行管道。条件是源系统将数据保存一段时间,并且管道可以等幂运行。
2.灾难恢复— Azure SQL
Azure SQL 数据库是为云构建的智能、可扩展的关系数据库服务,并针对在线事务处理(OLTP)进行了优化。请参阅关于灾难恢复的后续段落。
2.1。区域性/全球性灾难
可以进行以下测量:
- 如果一个应用程序只需要从一个数据库中读取数据,并且主区域关闭,应用程序可以访问一个辅助数据库进行只读操作,参见这里的
- 对于写入,Azure SQL 中支持自动故障转移。这样,当主区域中发生灾难时,可以使用不同区域中的辅助数据库进行写入。一旦主区域重新启动并运行,数据将从主区域复制回辅助区域。数据冲突可能会发生,并根据配置的策略来解决。
2.2。客户错误
可以进行以下测量:
- PITR 可以通过利用自动化数据库备份功能来实现。自动数据库备份每周创建一次完整备份,每 12–24 小时创建一次差异备份,每 5 到 10 分钟创建一次事务日志备份。还原数据库时,该服务确定需要还原哪些完整备份、差异备份和事务日志备份。
3.灾难恢复—Synapse SQL 池
Azure Synapse Analytics 是一项无限的分析服务,它将数据集成、企业数据仓库和大数据分析结合在一起。在 Synapse Analytics 中,SQL 池用于存储关系数据。它还可以与外部表一起使用,以查询 Azure 数据湖存储上的数据。有关灾难恢复计划,请参见接下来的段落。
3.1。区域性/全球性灾难
可以进行以下测量:
- 为了便于阅读,可以创建一个定制解决方案,每天在不同的区域恢复备份,暂停 SQL 池,并且仅在主区域停机时在不同的区域运行 SQL 池。
- 在数据湖存储上仅使用外部表的情况下,也可以在不同的区域中创建 SQL 池,并将 SQL 池指向不同区域中 Azure 数据湖存储帐户的辅助端点。
- Synapse SQL 池不支持自动故障转移。对于写入,应创建自定义解决方案,其中应用程序将数据写入不同区域的存储帐户/队列。也可以决定暂停数据接收管道,直到主区域再次启动,请参见下一段。
3.2。客户错误
可以进行以下测量:
- 快照可以追溯到 7 天,并且可以在数据损坏后恢复。为防止数据丢失,应再次运行数据管道,见下一条。
- Synapse SQL 池通常用于数据湖环境,其中 Azure 数据工厂或 Synapse 管道用于接收数据。数据或者被直接摄取到 SQL 池,或者被摄取到属于 Azure Synapse 工作区的 Azure 数据湖存储帐户,并在 Synapse SQL 池中用作外部表。一旦主要区域再次启动,也可以决定重新运行管道。条件是源系统将数据保存一段时间,并且管道可以等幂运行。
4.灾难恢复— Cosmos DB
Azure Cosmos DB 是一个完全托管的多数据库服务。它使您能够在全球范围内构建高度响应的应用程序。作为 Cosmos DB 的一部分,支持 Gremlin、Mongo DB、Cassandra 和 SQL APIs。有关灾难恢复计划,请参见接下来的段落。
4.1。区域性/全球性灾难
可以进行以下测量:
- 为了读取,应用程序可以使用配对区域中的次端点。
- Azure Cosmos DB 支持自动故障转移。如果主区域关闭,辅助区域中的数据库将成为主区域,并可用于写入数据。根据 Cosmos DB 的一致性级别,可能会出现数据冲突,并得到解决。
4.2。客户错误
可以进行以下测量:
- Azure Cosmos DB 创建自动备份。如果数据库被破坏或删除,需要恢复,可以请求恢复,请参见中的流程
- Azure Cosmos DBchange feed在 Azure Cosmos DB 容器中提供了一个持久的记录日志。更改提要中的项目可以在第二个容器中重放,直到数据损坏发生之前,然后保存回原始容器。
5.摘要
在本文中,我们讨论了在灾难发生时规划 Azure data PaaS 服务。尽管 PaaS 服务和配对区域的生产就绪层已经解决了很多问题,但客户仍然需要采取措施来应对灾难。在本文中,将讨论 Azure storage、SQL、Cosmos DB 和 Synapse 的度量,请参见下面的概述。
Azure PaaS 数据服务灾难恢复场景概述—作者图片
发现 9 个咨询领域,开始任何经验水平的激动人心的数据科学之旅
还有更多指导让你咨询准备就绪
在 Unsplash 上由 Rendy Novantino 拍摄的照片
许多数据科学家梦想从事咨询工作。我可以肯定这是一份令人兴奋的工作。
咨询提供了一个独特的机会,可以看到许多不同的公司、行业、职能领域,并与客户并肩工作。顾问被雇来做新的分析,这样你就可以在需要新想法的项目上工作。你在团队和一个由优秀人才和专家组成的网络中工作,你可以很快获得许多领域的非凡知识。你去很酷的地方旅行,享受团队的社交活动。最后,当你决定离开咨询行业时,你会在你的客户那里找到很好的工作。
咨询工作让你觉得自己很重要。
我的职业生涯始于一名分析师,后来成为全球领先咨询公司的合伙人。我在几个不同的国家和几个行业工作过。当我开始做分析师时,我的目标是呆两到三年,然后在我们的一个客户那里寻找一个好职位,然后搬到那里。但是这三年变成了五年,五年变成了十年,最后,变成了 15 年。还有,在我现在的岗位上,战略咨询仍然是我工作的一部分。
因此,让我们来看看你的梦想在哪里可以实现。
没有一家咨询公司或一个角色。相反,这是一个分散的市场,每个咨询公司都有自己的重点和优势,数据科学任务应该支持这一使命。
为了阐明不同的咨询工作机会和你所能期待的,我将正文组织如下。
- 你的数据科学咨询任务是什么?
- 如何开始数据科学顾问的职业生涯?
- 哪里可以找到数据科学家的咨询工作?
你的数据科学咨询任务有哪些?
数据科学需要专业技能。不仅仅是技术上和方法上,尤其是分析算法之外的所有任务。在企业层面开展数据科学项目需要项目和利益相关者管理技能、技术基础设施知识以及战略和财务管理知识。
这些是很多公司都不具备的独特技能。
决策总是在分析的基础上做出,至少部分是如此。在科技公司成功的推动下,今天,数据驱动的决策和商业模式是公司关注的焦点。因此,他们需要资源和有经验的人来支持他们的发展。
这不仅仅是大公司的议程。所有公司都希望利用数据驱动的决策和业务的优势。每家公司都需要创收,都希望增长。他们有一个反映目标的战略,并根据市场情况量身定制。随着技术和数据的兴起,企业的战略通常会反映这种转变,他们需要数据人员。
虽然中小型公司既没有能力也没有知识来建立一个数据科学团队,但他们也不需要一个拥有几个 100%专业角色的大型数据科学团队。因此,他们只在必要时借用知识和能力。
另一方面,较大的组织通常结构复杂。数据科学项目从哪里开始?他们在哪些方面最有可能成功转型为数据驱动型企业?除了传授经验来建立这种关系之外,他们还需要能够在不中断正在进行的业务的情况下开始进行这种转变的人。
最后,拥有以技术和科学为基础的人才需要许多公司的文化变革和明确的整合方案。
您可以看到,成功使用数据科学来推动决策和产品需要广泛的专业知识和技能。对支持的需求范围从
- 数据科学、人工智能和数据战略咨询
- 数据科学转型中的项目和利益相关者管理
- 展示在特定业务领域使用复杂方法和基础设施的价值证明
- 公司的使用案例,即在哪里应用数据科学并获得积极的投资回报
- 组织结构如何嵌入数据科学家
- 文化转变为数据驱动的思维模式
- 支持建立数据科学团队
- 培训和教育客户团队
- 能够应用数据科学方法并了解相关工具的人员
- 将公司的数据输入 IT 系统进行分析,并对其进行扩展
- 集成到业务决策工作流和业务流程中
- 关于使用哪些技术、基础设施、平台和工具以及在公司中部署它们的建议
- 构建和部署技术、数据和 IT 架构,以执行数据科学
- 执行数据科学任务的人才
因此,咨询公司或部门是集中培训的人,掌握所需的最新技能。他们根据需要提供所需的知识、能力和熟练的专家,并确保公司的业务连续性。
如何开始数据科学顾问的职业生涯?
传统上,咨询职业道路是这样建立的,你从初级开始,通过许多不同的项目和客户成功工作多年,然后一级一级晋升为经理。通常,十年或更长时间后,你会成为合伙人。随着向技术和数据驱动型咨询的转变,这种职业道路被稀释了。如今,他们在各个层面都雇佣了许多数据和 IT 技术人员。了解工商管理不再是必须的,也欢迎书呆子。随着这一开放,工作和任务变得更广泛,更具技术性。
进入具有各种背景的咨询公司的例子有:
- 研究生或(自学)菜鸟数据科学家:从任何咨询公司的初级数据科学家做起
- 高级数据工程师:作为经理或高级经理加入专注于数据平台项目的技术实施咨询公司
- 主要具有理论数据科学知识的业务分析师:作为初级或高级人员加入数据分析战略团队
- 高级数据科学家:从任何咨询公司的数据科学经理或高级经理(项目负责人)做起
- 解决方案架构师:作为(首席)解决方案架构师加入一家专注于技术实施的咨询公司
- 具备数据科学基础知识的数据分析师:从初级数据科学家或数据分析战略团队中的初级人员做起
今天,有很多机会加入一家来自不同背景的咨询公司。技能是稀缺的,公司争夺这些人。许多这样的公司也提供实习机会。
哪里可以找到数据科学家的咨询工作?
数据科学咨询角色非常普遍。战略和管理咨询公司投身于数据科学。科技公司整合信息技术的时间更长。多年来,数据分析也是四大为其审计工作开发的 DNA。但是,本地市场、小众精品和专注于特定行业的咨询公司也需要数据科学家。
与此同时,有专注于分析和大数据的咨询公司和平台提供商提供综合咨询服务。如今,每家公司都有一个由数据科学家组成的内部咨询部门。最后,许多创业公司将数据科学算法及其结果集成到产品中,并提供数据科学咨询作为产品的一个组成部分。
我把它们分成了九个部分,并给出了你可以期待的味道。
免责声明:所提供的咨询公司列表并不全面,应被视为各个细分市场的示例。有许多优秀的咨询公司有特定的市场或行业重点和具体的优势。它们为数据科学家提供了许多应该考虑的机会。
1。麦肯锡、波士顿咨询公司和贝恩公司(MBB)
麦肯锡(McKinsey)、波士顿咨询集团(BCG)和贝恩也转向了数据分析和数据科学。虽然麦肯锡的 Quantum Black 和波士顿咨询公司的 BCG Gamma 都有自己的数字化部门和数据科学家,但贝恩已经将他们完全整合到他们的战略咨询团队中。
粗略地说,数据科学家有两种工作。一个是从事技术数据科学工作,即构建应用程序,另一个是执行数据科学战略咨询。
这些项目包含的任务包括帮助建立公司的数据分析战略,进行价值验证,分析(提议的)解决方案对组织的价值,建立路线图、组织结构以及建立数据科学实践所需的条件(人才、技术)。
2。四大
德勤、EY、毕马威和普华永道被称为四大会计师事务所。他们的业务包括提供外部审计服务、管理咨询、战略咨询、法律和税务咨询、法务服务以及企业融资和收购交易服务。
数据科学家融入了所有这些领域。在外部审计领域,重点是开发和应用支持财务数据审计的工具。一个例子是分析账户中的预订,以及是否可能存在潜在的欺诈。
在管理咨询领域,任务包括为各种行业开发分析试点,直接在客户团队中提供支持,以及技术实施,主要是与 SAP 或 Salesforce 等提供商合作。
在法医服务中,数据科学家支持许多数据准备和分析,以识别欺诈案件。
类似的任务也包含在公司财务和交易服务中,但要对公司进行评估。
明确您的数据科学工作属于哪个服务领域非常重要。
3。技术咨询
最著名的科技咨询公司有(按字母顺序排列)埃森哲、凯捷、凯捷、高知、DXC 科技、IBM、印孚瑟斯、塔塔咨询服务和威普罗。
他们提供所谓的端到端技术咨询、实施和外包。这意味着他们为客户提供战略或管理咨询,实施数据平台和 IT 系统、机器学习算法,并且是这些服务的外包提供商。
所以,你的任务取决于你的背景和你被雇佣的职位。如上所述,它可以是战略咨询、机器学习、带有数据平台的数据工程、软件工程或架构设计。而且你会在那里遇到很多技术书呆子。
4。专注于本地市场的咨询精品店和咨询公司
市场上许多咨询公司的关注点与前三个细分市场相似,但他们的关注点只集中在少数几个市场和行业。他们通常被称为“二级公司”、“精品店”或“专注于本地市场”这些术语没有定义,只是意味着它们不属于前面提到的三个部分。
我没有给出名字,因为名单会变得太长。只要在搜索引擎中输入“咨询公司”,你就会得到一个数百家公司的列表。
他们中的许多人还提供数据科学咨询。他们通常更具有家族性,因为他们更小,一个人更早承担更多的责任和更广泛的任务。
此外,在为客户建立新的咨询领域或数据科学工具方面,你可以更具创业精神。不少大型国际咨询公司的合伙人和资深专家会因为更多的创业自由而跳槽到更小的公司。
5。利基和行业咨询
利基咨询公司专注于单一行业、单一地区或单一方法。
这类咨询公司的例子有保时捷咨询公司(与汽车和类似行业相关的一切)、盖洛普咨询公司(民意测验、员工参与度调查、个性评估)、Trinity Partners(生命科学)、Willis Towers Watson(风险管理、员工福利和人力资源及精算)、Putnam Associates(医疗保健)或 EY-帕特农公司(战略咨询)。
其中一些项目还需要数据科学家。当你有兴趣成为某个行业的专家时,强烈推荐小众咨询公司。
6。大数据和分析咨询公司
有许多专业的大数据和分析公司。你可以在 GoodFirms 上找到从两人公司到超过 10,000 名员工的公司列表。
并非所有需要的人才都是数据科学家,而是很多。服务范围很广,从特定方法、技术、平台、基于客户或行业的专业服务到一般服务。选择大数据和分析公司时,要明确他们的业务重点和你的工作重点。
如果技术和技术方法对你来说比任何行业、基于客户和非数据科学相关的焦点都更重要,那么选择这些咨询公司之一。
7。平台和软件提供商
所有的数据平台和软件提供商都有自己的咨询业务。知名公司的例子(按字母顺序排列)有:Alteryx、Amazon、Cloudera、Databricks、Dataiku、Dell EMC、Google、HP、IBM、Microsoft、Oracle、Palantir Technologies、SAP、SAS、Tableau、TIBCO、VMware 等等。
需求与技术咨询相同。而且需求还在稳步增长。尤其是,围绕云计算的所有数据科学咨询需求非常高。
如果你想专注于这个特定领域,平台提供商应该是你的首选。
8。公司内部咨询
所有跨国公司都有内部咨询部门。它是为公司开发技能和战略解决方案并支持不同部门实施的中心。
大多数公司的内部咨询部门都有数据科学家。
内部咨询团队的角色与外部顾问相同,从数据科学战略咨询到原型设计和实施机器学习算法,再到数据工程或数据平台实施。不少外部数据科学顾问加入了这样的公司团队。
9。创业公司
最后,许多初创公司提供咨询服务作为其产品的一部分,例如,循环经济、营销、基于深度学习的金融服务风险管理、教育平台、政府大数据分析等。
当你有创业天赋,并想塑造一个新的公司和服务的成功,这应该是你的第一选择。
下一步
快速浏览一下 LinkedIn 和 T9,就会发现美国和欧洲有一千多个与咨询相关的数据科学工作。而且需求量越来越大。
按照三个步骤进入数据科学咨询工作:
#1 你的焦点
我们在上面看到有太多的机会,你必须清楚自己的需求:
- 你喜欢有固定角色的大型国际咨询集团吗?
- 还是想加入一家规模较小、任务范围更广、可以发挥创业精神的咨询公司?
- 你想成为一个行业的专家吗?
- 你的目标是做一名与特定软件平台相关的顾问吗?
- 你愿意为公司工作吗?
- 还是想体验一下创业的感觉?
- 你喜欢具体的工作地点和当地的咨询公司吗?
基于这种自我评估,选择两到三个细分市场或地点作为重点。
#2 做研究
在咨询公司找工作首先需要研究,我指的是大量的研究:
- 搜索 LinkedIn 和其他许多求职平台上的数百份工作机会,找出哪家公司在寻找什么样的人才,什么样的工作能引起你的共鸣
- 搜索咨询公司的名单,例如我的咨询服务,熟悉他们工作的市场和行业,同样,也要熟悉你的共鸣
- 看看玻璃门或实际上的雇主评级并阅读评论
我推荐这种广泛的搜索练习,你可以找到你喜欢的咨询工作。在我的职业生涯中,有几个雇员在两三个月后再次辞职,因为这不是他们想做的咨询类型。它极大地提高了你工作满意度的成功率。
#3 准备招聘
最重要的是知道他们期望你给公司带来什么品质。这些品质是技术上的和你的性格。
- 在你的关系网中寻找从事咨询工作的人,最好是你想加入的人。向他们询问文化和工作。请他们帮你联系在你感兴趣的领域工作的人
- 参加咨询公司的公开活动,在那里你可以看到他们是如何工作的。你见到人们,可以问他们你的问题
- 看在 Quora 上。很多关于咨询公司的问题已经有了答案。如果没有,你可以发表你的问题。
这一信息至关重要。你越了解他们在寻找什么,你得到梦想中的咨询工作的机会就越大。
一个部分的开始并不排除移动到另一个部分。我在不同的部分之间移动。每一次,我都非常努力地完成了这三个步骤,并取得了成功。
没有任何限制。你是你职业道路的唯一驱动力。数据科学咨询是一个令人兴奋的行业。
免责声明: 我既没有从这些公司获得报酬,也没有与它们有任何关联。提到的公司只是为了举例,而不是为了促销。
你喜欢我的故事吗?在这里你可以找到更多。
</5-exciting-industries-for-a-data-scientist-job-outside-of-the-tech-sector-2e5d2c456d16>
使用主题建模从 GPS 数据中发现隐藏的旅行主题
利用潜在狄利克雷分配从 GPS 跟踪数据中提取潜在的出行主题
可视化 LDA 提取的旅行主题,按作者排序
每一个文本都是由一个作者产生的,他的话语,带着意图表达的话语单位,都被保存在文字中。因此,大量的文本可以用来研究文化。像主题建模这样的无监督机器学习方法允许我们从大量文本数据中提取潜在的文化主题。随着大数据在交通领域的增长,一个新的分析机会出现了,研究人员可以寻求从移动性的痕迹中提取意义和文化。
在评估城市中的新交通模式时,地方政府领导和城市规划者可能会想知道用户使用新服务时通常会采取什么类型的出行方式。例如,在过去几年里,电动滑板车如此迅速地流行起来,以至于城市管理者仍在寻找监控和优化城市运营的最佳方式。疫情极大地扰乱了当前的微移动操作,但那是另一天的主题。在这篇文章中,我将详细介绍如何使用主题建模从非结构化的估计旅行集合中提取潜在的旅行主题。由于主题建模通常用于文本分析,因此我将首先讨论一些关于潜在狄利克雷分配的背景信息,以及为什么它是揭示潜在出行模式的合适方法,这些出行模式是大量 GPS 追踪数据的特征。
出行分析中的潜在狄利克雷分配
潜在狄利克雷分配(LDA)是由 David M. Blei、Andrew Y. Ng 和 Michael I. Jordan 开发的离散数据集合的生成混合模型。由于该模型是在文本分析的上下文中创建的,所以它假设非结构化文档集合中的每个文档都由混合的主题组成。使用隐藏的狄利克雷随机变量对文档建模,并且模型的输出是潜在的低维主题空间上的概率分布。换句话说,从非结构化的文档集合中,模型提取相关的主题。LDA 是一种主题建模,因为它是一种发现文档集合中隐藏主题的方法。除了文本数据,主题建模还被用于在图像数据、社交网络数据甚至基因数据中寻找模式。
在行程环境中,提取的电动踏板车行程包含一组离散的 GPS 点。虽然不是很明显,但在标记化时,一组有序的 GPS 坐标对看起来就像标记化的文档。我们可以将每个 GPS 坐标对视为词汇表中的一个独特的单词。然后,行程可以被视为包含许多 GPS 点字的文档类型。从这个角度来看,当我们有大量非结构化的旅行时,主题建模是一种非常合适的分析技术。使用主题建模,我们可以提取旅行集合中隐藏的路线模式。这将是有效的,因为受欢迎的 GPS 点将在数据集中的几个不同的行程中同时出现。类似的行程将包含与相同 GPS 点的显著重叠。
为 LDA 准备 GPS 行程数据
为了执行主题建模,必须以与准备作为 LDA 输入的文本数据相同的方式准备电动滑板车行程数据。回想一下,我们将把每个唯一的 GPS 经度-纬度对视为一个单词。我们的词汇表是数据集中唯一的 GPS 点的完整集合。在文本分析中,数据科学家通常会执行一些预处理步骤,有效地缩小词汇表,包括删除停用词和词干。在这种情况下,词干是相关的,我们将执行完成类似壮举的步骤。在词干提取中,单词被减少到它们的词根,使得像“work”、“worked”和“working”这样的单词被分组在单词“work”下,以确保词频被更好地考虑。对于我的 GPS 数据,原始跟踪点精确到小数点后第四位。在没有对接近点进行分组的任何预处理的情况下,我从 42,301 次旅行的集合中获得了 52,879 个唯一 GPS 点的起始词汇表。在文本分析中,通常通过各种过程将词汇表缩减到前 5000 个单词,这些过程包括移除停用词、词干和移除罕见词等。为了高效的模型性能。在这种情况下,我将 GPS 点四舍五入到小数点后 3 位,有效地将描述相同路线点的点组合在一起。四舍五入后,词汇量减少到可管理的 3,312 个 GPS 点。
根据全球定位系统数据的文献术语矩阵:作者提供的出行点矩阵图
为了在电动滑板车行程数据上实现主题建模,我们利用了一个流行的机器学习库 scikit-learn 。所需的输入是上图所示的文档术语矩阵。回想一下,我们将把每一次旅行都视为一份文件。对于 42,301 个提取的行程,我们构建了一个大小为 42,301 行乘 3,312 列的行程点矩阵(根据 GPS 词汇表大小的行程数),其中的值指示词汇表中的 GPS 点在每次行程中出现了多少次。接下来,我们将 trip-point 矩阵作为输入提供给 LDA 模型,将其设置为提取 15 个主题。然后,LDA 模型的输出是 15 个主题,这些主题最好地描述了构成每个主题的 GPS 点的概率分布的行程集合。此外,我们可以检索每次旅行主题的概率分布。对于这个数据集,模型运行时间只有大约 3 分钟。
from sklearn.decomposition import LatentDirichletAllocationlda_model = LatentDirichletAllocation(n_components=15)
lda_output = lda_model.fit_transform(doc_term_matrix)
可视化主题
最后,我们可视化我们的主题和主题分布。在文本的主题建模中,主题通常通过显示每个主题的前 k 个最频繁出现的单词来可视化。然后,具有领域知识的分析师可以通过检查这些顶级单词组来确定语料库中的潜在主题。虽然顶部的单词容易阅读和解释,但仅仅通过查看顶部的 k GPS 坐标对并没有什么价值。相反,我们需要将顶部的 k 个 GPS 对处理回纬度和经度字段,然后在地图上绘制顶部的 k 个点。在这项研究中,每个主题的前 50 个 GPS 点绘制在夏洛茨维尔的地图上,以可视化提取的旅行主题。为了绘制 GPS 点,在连接之前,将顶部的单词连接回四舍五入的 GPS 坐标。
15 个提取的旅行主题—空间可视化,按作者分类
查看上图,我们可以看到 LDA 模型揭示了一组多样化的潜在出行类型,这些类型表征了一大组电动滑板车 GPS 跟踪数据,将数据分成离散的分组。这些主题,在上面的图中按颜色分类,可以由城市规划者、城市管理者或具有夏洛茨维尔景观领域知识的当地研究人员来解释。最后,下图显示了十五个提取的旅行主题是如何分布的。
按作者列出的旅行主题分布图
我是潜在狄利克雷分配的粉丝,因为它提供了一种发现隐藏在大量数据中的文化意义的方法。虽然主题建模通常用于发现集体文本中的趋势,但在这里,我将探索它帮助我们理解集体运动趋势的潜力。由于数据集不包含任何用户信息,这项研究不会造成任何直接的用户隐私问题。探索 LDA 在文本数据上下文之外的适用性是很有趣的。感谢阅读。
借助脸书图形 API 和 Python,从您的 Instagram 商业账户中发现真知灼见
如何使用 Python 从 Instagram 业务中获得洞察力的分步指南
在 Instagram 上发布产品照片是社交媒体驱动的商业世界不可或缺的一部分
I nstagram。多功能社交媒体,在这里你可以发布你最喜欢的食物,捕捉你最喜欢的消遣,在这里你可以看到你的朋友或家人在做什么,在这里你可以获得你的每日新闻摘要,在这里你可以获得你有趣的宠物视频,在这里你可以获得你每日的迷因,在这里你可以研究一些产品,服务,甚至“研究”你喜欢的人。基本上是社交媒体的瑞士军刀。
2021 年,每月超过 10 亿人使用 insta gram【1】。我每天打开我的 Instagram。我的朋友也是,我的家人也是,我想,你也是。
Instagram 拥有大量的活跃用户,加上他们中的一些人拥有高水平的可支配收入,当然,insta gram 仍然是你的企业销售或推广你的产品或服务的最热门的地方之一。
毫无疑问,在 Instagram 上在线展示很重要。有些人甚至认为这比线下的存在更重要。世界上的疫情状况只会强化这种情绪。
现在,你想创业。或者,可能已经有了生意,然后你想从 Instagram 那里分一杯羹。
你如何在 Instagram 上为你的企业树立形象?首先,你可以注册你的 Instagram 商业账户,然后,定期在 Instagram 上发布内容/帖子,以建立你的社区,增加你的粉丝数量,或创造知名度。
假设你发表了一系列不同的帖子或内容,但你很好奇你的内容被人们接受的程度。你想知道哪些人对你的产品感兴趣,你的帖子到达了哪些城市,等等。你可以从你的 Instagram 个人资料中看到这些见解。但老实说,这个观点本身是非常有限的。
你想从你的 Instagram 上看到更全面的洞察数据吗?或者从这些见解中构建自己的数据表或仪表板?下面跟我一起深入挖掘 Instagram Graph API !
Instagram 商业账户
在深入探讨代码或者 Instagram Graph API 之前,对于不熟悉 Instagram 商业账号的人,我们先退一步。
Instagram 上大多数人用的是 Instagram 个人账号。默认情况下,每次新用户在 Instagram 上创建账户,都是个人账户。但是如果你打算将 Instagram 用于商业目的,你应该将你的个人账户转换成商业账户。
通过转换为企业帐户,您可以获得更多功能来帮助您的业务增长,例如获得洞察力来帮助您了解谁在使用您的内容,通过广告触及未知的潜在市场,以及在您的个人资料中添加更多联系信息。
通过阅读这份脸书蓝图,你可以了解更多关于如何建立你的商业账户的信息。这是一个很短的过程,你可以在 10 分钟内完成。
脸书开发者帐户
一旦你创建了一个 Instagram 商业账户,让我们前往脸书开发者页面为你创建一个开发者账户。
之后,你需要先做一个应用程序来访问脸书的 insight 数据。您的应用程序类型将决定哪些 API 可供您使用。
制作您的第一个脸书应用程序
- 进入我的应用,然后点击创建应用。
- 为您的应用程序类型选择业务,因为您稍后将使用 Instagram Graph API 。
- 写下你的显示名和联系邮箱。
我在脸书的开发者应用页面|作者图片
创建应用程序前的最后一步|作者图片
一旦你的应用成功创建,你可以进入基本设置来检查你的应用 ID 和应用密码。应用 ID 是你的应用的唯一标识符,因此脸书可以识别是谁从应用发出数据请求。而 App Secret 将用于解码来自脸书的加密信息。这两个是你 app 特有的,以后会用到。
基本设置包含应用 ID 和应用机密|作者图片
其他关键信息
在继续探索 Instagram Graph API 之前,让我们稍微离开一点,收集我们将用于挖掘洞察力的另一个关键信息:脸书页面 ID。
您可以通过查看您的脸书业务页面并查看“关于”选项卡的底部,轻松获得您的脸书页面 ID 。
脸书商业页面—页面 ID |作者图片
Instagram 图形 API
现在,让我们将 Instagram Graph API 添加到您的应用程序中。您可以轻松选择 Instagram Graph API ,然后点击设置。你可以在这里阅读更多关于 Instagram Graph API 。
脸书应用提供的产品选择|作者图片
图形 API 浏览器
一旦您添加了 Instagram Graph API ,您就可以转到“工具”选项卡,使用 Graph API Explorer 来定义您需要或想要的权限类型,并生成访问令牌。
图形 API 浏览器主页|作者图片
有一个很大的权限列表可供我们选择,您可以在这里查阅它们。显然,当构建一个应用程序或从脸书请求数据时,我们只需要少量数据,而不是全部。
存在仅访问基本信息、访问见解、发布评论、发布内容等的权限。你可以在上面的链接中深入阅读。
由于我们的目标是利用 Instagram Insights 的力量,我们将要求提供一个具体的权限列表。我们现在要做的是向脸书/Instagram 询问这些见解:
- 来自每个 Instagram 帖子的基本见解(赞和评论)
- 来自每个 Instagram 帖子的更深入的见解(印象、触及范围等。)
- 来自 Instagram 账户的受众和人口统计洞察
如果你看过上面的权限链接,你就会知道,要获取那些数据,我们必须要有这些权限: instagram_basic ,insta gram _ manage _ insights, pages_show_list , pages_read_engagement ,以及 pages_read_user_content。
一旦你选择了所有的权限,点击生成访问令牌,会弹出一个小窗口。在确认后续流程之前,脸书还会通知我们的 Instagram 商业账号 ID 。把这个复制到你的记事本上,以后会很方便。
Instagram 商业账户 ID |作者图片
确认随后的窗口,将您的应用链接到脸书,然后您将最终获得一个访问令牌。请记住,该令牌只是一个短期访问令牌,仅在一小时内有效。我们将生成一个长期访问令牌,其有效期为 60 天。系好安全带,因为我们现在要使用 Python 了!
从脸书图形 API 中获得洞察力
准备
打开你的 Jupyter 笔记本、 Google Colab 或任何其他 Python IDE,然后我们将首先从导入所需的库开始。
因为我们将使用 GET 请求到脸书,我们必须根据我们需要请求的数据遵守特定的语法。为了让我们更容易使用各种 GET 语法,让我们在一个名为‘params’的字典中编译我们需要的细节。
您可能会对代码中显示了多少屏蔽的细节感到惊讶,但实际上,我们已经在之前的过程中收集了这些细节!让我解释一下:
- access_token : 我们在开始编写代码之前直接获得的短期访问令牌
- client _ ID:App ID来自设置
- client _ Secret:App Secret来自设置
- page_id : 脸书页面 ID 来自您的业务页面的“关于”选项卡
- instagram_account_id :确认生成令牌过程中显示
对于‘graph _ version’,我使用的是 v12.0 ,因为那是我目前可用的版本。如果您使用 API 的另一个版本,只需相应地更新它。
检查访问令牌
在继续请求洞察之前,让我们先检查一下我们的访问令牌和应用范围。我们可以通过编写这段代码来做到这一点。
运行这些代码将导致以下结果:
访问令牌检查结果|按作者排序的图像
我们可以确认我们的应用范围,更重要的是,知道我们的令牌将于何时到期。为了将那个‘expires _ at’数字转换成适当的时间戳格式,让我们做一个简单的重新格式化。
现在我们确切地知道了我们的令牌实际上有多短暂:)
短期访问令牌过期时间|作者图片
将短期访问令牌更改为长期访问令牌
由于 1 小时的到期时间可能会干扰我们学习从 Instagram 请求 insights 数据的过程,因此让我们将这个短期令牌转换为一个长期访问令牌。
API Endpoint:https://graph.facebook.com/{graph-api-version}/oauth/access_token?grant_type=fb_exchange_token&client_id={app-id}&client_secret={app-secret}&fb_exchange_token={your-access-token}
基于来自脸书的 API 端点,让我们编写一些代码来获得长期访问令牌。
长期访问令牌|作者图片
现在我们得到了一个长期访问令牌,让我们通过将它放回我们的参数字典( params['access_token'] )来检查我们的最新令牌到期时间,然后重新运行检查访问令牌代码。
长期访问令牌过期时间|作者图片
我们已经成功地将访问令牌的到期时间从 2021 年 10 月转换为 2021 年 12 月!现在我们能够更灵活地执行数据请求,因为我们有了更多的时间。
Instagram 帖子——基本观点
让我们先从基本的见解开始,比如来自你的 Instagram 商业帖子的基本元数据,以及它们的喜欢数和评论数。
API Endpoint:https://graph.facebook.com/{graph-api-version}/{ig-user-id}/media?fields={fields}
通过运行这些代码,我们可以获得从你注册商业账户开始的任何帖子的元数据和基本信息。请记住,如果您的帐户最初是个人帐户,那么您将只能从您更改为企业帐户后发布的帖子中获得见解。
基本见解结果|作者图片
如您所见,您将收到 JSON 字典形式的数据。所有这些数据都非常重要,所以让我们简单地将它们转换成熊猫数据帧。
基本见解—数据框架结果|作者图片
Instagram 帖子——更多见解
我们可以通过使用上面不同的 API 端点来更深入地研究每篇文章。
API Endpoint:https://graph.facebook.com/{graph-api-version}/{ig-media-id}/insights?metric={metric}
这一次,我们将遍历从以前的代码中获得的‘媒体 id’,以请求更深入的洞察指标。
更多见解—结果
并非上述所有数据都重要,我们只想获得‘name’和‘value’,因此我们必须正确解析它,然后将其转换为 Pandas DataFrame 。
更多见解—数据框架结果| |作者图片
我们已经成功地为我们的帖子收集了更深入的见解。不仅是订婚 ( 喜欢 + 评论),我们还可以看到印象、达成、保存,这些都是 Instagram 的算法中越来越重要的指标。我们甚至可以通过将这种更深入洞察的数据框架与基本洞察的数据框架结合起来,进一步整理我们的数据。
Instagram 账户——受众洞察
我们可能从我们的个人帖子中获得了见解,但我们也可以从我们的 Instagram 商业帐户中请求见解,例如,观众见解。
API Endpoint:https://graph.facebook.com/{graph-api-version}/{ig-media-id}/insights?metric={metric}
受众观察结果|作者图片
我们现在有不同层次的观众人口统计洞察:按国家的、、按城市的、和按性别-年龄的。有了这些数据,我们可以看到我们的帖子到达了哪些城市,哪个城市最喜欢我们的内容,我们的内容吸引了哪些人群。
我们可以通过简单的编码来制作熊猫数据框,例如,我们按城市创建观众的数据框,按性别-年龄创建观众的数据框。**
城市受众—数据框架结果|作者图片
按性别-年龄划分的受众—数据框架结果|按作者划分的图片
利用上述信息,我们可以制定更好的内容创作或营销策略。我们对用户了解得越多,我们就能更好地为他们量身定制活动或广告。
结论
现在,您可以从您的 Instagram 商业帐户中提取几个 insights 数据,如喜欢数、评论数、印象数、已保存数、和观众见解,您可以更好地分析您的 Instagram 帐户的表现。
这只是一个开始,因为你可以为上面的数据制作你的仪表板或数据可视化,或者你可以通过寻找更多的见解进行更深入的研究。
感谢阅读
我希望你喜欢这篇文章!我希望你能从中学到一些新的有用的东西。如果你有,请在下面鼓掌,如果你有任何反馈,请在下面留下你的想法!
保持联系
参考
[1]sproutsocial.com,巴恩哈特,b .2021 年你需要知道的最重要的 Instagram 统计数据
[2] Gotter,a .,2021 年你需要知道的 29 个 Instagram 统计数据 (2021),adespresso.com
用 expert.ai 发现音乐的情绪
音乐是一种强大的情绪调节器:NLP 可以应用于歌词或评论,根据音乐的情绪对艺术家进行分类
难怪人们会根据自己的感受来创建播放列表。音乐是一种非常强大的情绪调节剂,我们都至少使用过一次,来给我们所需的能量,或者将它与我们的悲伤配对。如果我们可以根据音乐的情绪对音乐进行分类,而不必主动听音乐或手动给每首歌曲贴标签,会怎么样?
使用 NLP(自然语言处理)功能,我们可以通过分析某个艺术家的歌词或评论,根据歌曲在听众中引起的情绪对歌曲进行分类。使用评论的文本,我们还可以将它们从最积极的到最消极的进行分类,以便从最高评级到最低评级的专辑来组织艺术家。一旦你有了一个分类,你可以用它来安排你的音乐,并实现一个应用程序,只为某个预定义的情绪加载曲目,或者你可以找到新的艺术家,创造一个类似于你最喜欢的歌曲的情绪。
在本文中,我们将看到如何为这样的目标建立一个简单的 NLP 应用程序,我们将使用 expert.ai NL API,因为它提供了情绪和心情分类。
数据集
在这篇文章中,我们将关注企鹅咖啡馆乐团,他是我最喜欢的艺术家之一,我们将看到如何构建一个音乐分类应用程序的核心。我们使用了皮耶罗·斯卡鲁菲的网站提供的评论,其中包含了对不同音乐流派的许多艺术家的丰富而有趣的分析。第一步是将所选艺术家的专辑评论复制粘贴到文本文件中,并以相应专辑的名称保存在我的本地机器上。请注意:如果你想使用斯卡鲁菲的评论,请务必阅读使用条款并在有疑问时联系他。
企鹅咖啡馆乐团是关于什么的?
首先,让我们通过分析评论中使用的单词来看看评论中会出现什么。我们将首先在一个变量中连接所有的评论,以便对整个艺术家的作品有一个单一的评论。然后,我们将看看最常用的词,希望它们能揭示更多关于企鹅咖啡馆乐团的信息:
让我们看看这种浅层语言学方法在艺术家评论上能做什么,并产生一个词云来突出管弦乐队的关键元素:
作者图片
正如你所看到的,我们现在对他们创作的音乐类型有了更详细的了解,我们可以根据这里出现的一些词对其进行分类,例如他们混合的流派,他们演奏的乐器,等等…不幸的是,没有浅层语言学的方法可以给我们关于文本情绪的提示。我们需要使用一种更精细的方法来解决这个问题。
他们的音乐让你感觉如何?
多亏了“云”这个词,我们对企鹅咖啡馆乐团有了更多的了解,但我们仍然不知道他们音乐的基调。通过斯卡鲁菲的评论,我们可以从他们的作品中找到更多的情感。为此,我们将使用 expert.ai NL API。如果你还没有,请注意,你可以在这里注册,并在我之前的文章中找到 101 指南。一旦创建了凭证,我们就可以开始使用 expert.ai NL API 了。
我们将使用 情感特征分类法 ,这是基于情感的文本分类器。要使用它,我们需要初始化客户端,然后设置正确的参数:我们选择情感分类,设置文档的语言,并将文本传递给服务。请记住,我们的文本是企鹅咖啡馆乐团专辑所有评论的集合。然后我们获得每种情绪及其百分比。数字越高,这种情绪在文本中就越突出:
['Happiness', 'Excitement', 'Joy', 'Amusement', 'Love']
[15.86, 31.73, 15.86, 31.73, 4.76]
我们可以使用 matplotlib 绘制这些数据,并生成一个饼图:
作者图片
既然我们有了基于情感的分类,我们可以根据评论的好坏来排列企鹅咖啡馆乐团的专辑。未来的应用程序可以根据情绪对音乐进行分类,然后将包含每位艺术家最佳作品的播放列表作为第一个结果呈现出来。
他们最好的专辑是什么?
为了根据作者表达的观点对文本进行分类,我们可以使用情感分析。这给了我们一个分数:如果它高于 0,这意味着该文本表达了积极的观点,如果它正好是 0(或非常接近于 0),那么我们可以说该文本是中性的,如果它小于 0,它是消极的。情绪分析根据积极和消极的极性进行分类,不应该与情绪分类法混淆,后者根据过多的不同情绪进行分类。
我们可以看一下对每张专辑评论的情感分析,以了解哪张专辑的评论最好。为此,我们对每张专辑的评论进行迭代,并使用 expert.ai NL API 来检索其情感和力量:
[11.6, 2.7, 10.89, 3.9]
['Broadcasting From Home', 'Concert Program', 'Music From the Penguin Cafe', 'Signs of Life']
我们可以使用 matplotlib 绘制这些数据,并制作一个条形图——根据评论,条形图越高,专辑越好:
作者图片
在上面的柱状图中,我们很容易看到所有专辑评论都是正面的,说明评论者喜欢每一张专辑。我们还可以看到,有些专辑比其他专辑更积极,而且在家广播是得分最高的专辑,这意味着根据评论家的说法,这是最好的专辑。
结论
我们每天遇到的许多意义都是通过语言来传达的:这当然适用于我们收到的电子邮件和我们写的推文,但对于其他话题也是如此。利用我们可以在网上自由找到的东西,比如歌词或评论,我们可以将 NLP 应用到音乐中,并定制像这样的个人体验。
我们希望您喜欢本教程,并期待了解使用 expert.ai NL API 可以做些什么。
发现数学和数据科学问题之间的联系
入门
L 了解如何使用你的数学和统计学知识最有效地解决数据科学和机器学习问题。这是一个数列,我们将从随机数开始。
数据科学博客系列:
当我开始重温我的数学和统计知识(阅读线性代数、微积分和概率)以将这些知识与机器学习概念结合起来,最终应用于金融时,我经历了巨大的困难。
我并没有纠结于数学和统计概念本身——我有一些相关的背景,谢天谢地,只要点击一下鼠标,就可以获得大量的在线参考资料——但我纠结于如何在机器学习(ML)和深度学习(DL)问题中使用这些概念。我努力在数学概念和它们在 ML 和 DL 中的应用之间建立清晰的逻辑联系,我努力在 ML 和 DL 的上下文中找到这些概念背后的目的。
不幸的是,几个小时的谷歌搜索没有帮助,因为没有精确和简明的材料可以解决这一点。我不得不通过艰难的方式了解这一点,这时候我想到要开始一个博客系列来为同病相怜的人降低它的音量。
在本系列中,我将挑选一个统计或数学主题,并将其简化到最基本的水平,以便人们可以在 ML 和 DL 的应用程序中使用它。我的意图不是阐明数学和统计理论,也不是 ML 和 DL 概念,而是这两者之间的联系。换句话说,我会假设你对这些概念有一个基本的了解,我不会试图教你这些概念(至少在这个博客系列中不会)。
那我们系好安全带,从不起眼的随机数开始。
随机性:在我们进入随机数之前,让我们先了解随机性及其重要性。我们日常和职业生活中的未来事件没有一件是绝对确定的;下周末见你的朋友,后天和你的老板打电话,计划明天一早去慢跑,没有什么是确定的。
而涉及到商业问题时;一只股票下周五的收盘价,一只债券下月底的信用评级,未来三个月的新客户数量,这些都是不确定的。这是因为大量的因素影响他们的结果。
不确定性越多,随机性就越大。但是我们没有任何选择,我们必须预测商业事件的结果以便做出决定。
怎么办?我们必须减少事件结果中随机性的影响,这样才能有把握地做出商业决策。如何减少随机性的影响?简单。将随机性纳入结果的计算中,以减轻随机性的影响。怎么做呢?进来的是随机数。
随机数(RN) : 关于 RN 的几个要点。我们借助计算机为我们生成 RN。然而,计算机内部发生的事情没有一件是随机的,因此计算机生成的 RN 来自确定性的程序,并且是伪随机的。因此,像加密解决方案这样的关键应用不应该基于伪随机数。此外,随机数生成(RNG)过程越随机,我们的计算就越有预测能力,因此选择一个强大的 RNG (Mersenne-Twister 是最常见的,对我们的大多数常规业务问题都相当有效)。
随机数分布(RND) :所以我们明白我们需要 RN,但这还不够,我们还需要知道我们需要什么类型的 RN,因为每种类型的 RN 都与特定类型的业务问题相关联。RN 的类型由它们所来自的概率分布(PD)决定(例如,均匀、正态等。).在这种情况下,RND 和 PD 是相同的。为了理解随机数和 PD 之间的联系,我们需要知道随机变量的定义。随机变量是一个可以以一定概率呈现一定随机数的变量。这些概率的集合被称为随机变量的概率分布。概率分布指定总概率(始终为 1)如何在各种可能的结果中分布。例如,你已经用相应的概率计算了明天布伦特原油收盘价的所有可能值,并将它们存储在一个随机变量 S 中。在这种情况下, S 的不同值及其概率就是 S 的 PD。
每个 PD 遵循一定的规则,并具有一组参数(例如,平均值、方差、偏斜度、峰度等。)决定了 RN 的产生。
现在是最后一个问题,我们如何知道我需要哪个 RND 来解决我的业务问题?为此,彻底了解业务问题,并选择最能描述问题的 RND。当分布的条件与问题的条件相匹配时,RND 最好地描述了问题的特征。例如,你认为一只股票明天的开盘价将是今天收盘价的 90%到 110%,每种可能性都是一样的。在这种情况下,您可以绘制一组 10,000 个一致的 RN,其下限和上限分别为-0.1 和 0.1,并将今天的收盘价乘以这些 RN,然后对其进行平均,以得出明天的开盘价(股票价格预测并不完全是这样做的,但希望您明白这一点)。
另一方面,如果你认为一个股票指数的月度运动遵循一个具有特定平均值和标准差的正态分布模式,那么你需要画出一组具有特定平均值和标准差的正态 rn,并将这些 RN 应用于股票指数价格的计算。同样不同的是,如果你持有不同到期日的高风险债券投资组合,并且你认为一年内平均有 3 只债券违约,那么你需要从参数λ = 3 的泊松分布中抽取随机数,以在一定程度上模拟你的投资组合中的实际违约数量。
对于任何称职的数据分析师或从业者来说,了解 PD 的概念是不容置疑的。它为分析和推断统计提供了基础。在数以百计的发行版中,只有少数几个我们需要在日常使用中更深入地了解。我在这里选了其中的五个:均匀、二项、正态、泊松和指数。我计划有一个帖子来讨论它们的应用,以及这些分布在未来量子金融中的用例。
无论如何,这是足够的理论,现在让我们用 Python 实际执行这些概念。
下面是我们前面提到的不同类型的 PDs 的计算工具,以及 Numpy 中相关的随机数生成函数的例子和用法。
均匀分布
当一个随机变量在给定范围内取任何值的可能性相等(即具有相同的概率)时,称该随机变量遵循均匀分布。在 Numpy 中,均匀分布的随机数生成函数种类最多。让我们一个一个来看看。
**np.random.randint(40, 100, 10)**array([64, 82, 43, 75, 48, 40, 90, 78, 81, 76])
—从半开区间中的“离散均匀”分布返回 10 个介于 40 和 100 之间的随机整数。半开区间表示随机选择中不包括高边界值,但包括低边界值。注意:您可以将下限值设置为负值。
**np.random.randint(10, size = 50)** array([3, 7, 0, 8, 3, 4, 3, 9, 5, 7, 9, 3, 3, 2, 9, 7, 8, 3, 4, 3, 0, 3,2, 8, 8, 0, 6, 2, 3, 0, 2, 9, 8, 8, 3, 4, 0, 9, 0, 7, 5, 9, 7, 3, 4, 4, 5, 2, 3, 0])
—如果您只向np.random.randint
传递一个参数,那么该参数将被视为高值,低值将默认为零。例如,上面的命令从“离散均匀”分布返回 50 个整数,高值为 10,低值为 0。
**np.random.random_integers(10, size = 50)**array([5, 2, 9, 10, 1, 8, 9, 4, 2, 6, 6, 4, 5, 4, 7, 9, 9, 6, 7, 8, 10, 1, 1, 5, 6, 7, 1, 6, 10, 1, 8, 8, 4, 4, 3, 10, 1, 8, 4, 3, 7, 4, 7, 8, 1, 2, 2, 7, 1, 8])
—返回“离散均匀”分布的整数,与np.random.randint
有一些微小的差异。np.random.randint
的默认低电平是零,而np.random.random_integers
的默认低电平是 1。还有,前者半开,后者全开。
注意:np.random.random_integers
即将弃用。
**np.random.random_sample((3,4))** array([[0.61879859, 0.61965074, 0.25047645, 0.9547143 ],
[0.62274851, 0.25153697, 0.79753309, 0.78389591],
[0.30247623, 0.00885101, 0.87795343, 0.48111766]])
—从半开放空间中的“连续均匀”分布返回介于 0 和 1 之间的随机浮点数的 3*4 数组。np.random.random_sample
有两个别名:np.random.random
和np.random.ranf
**np.random.rand(2, 3)**array([[0.40629507, 0.59574751, 0.04639712],
[0.65445587, 0.96331397, 0.45717752]])**np.random.random_sample((2,3))**array([[0.39825425, 0.70531165, 0.33423643],
[0.04037576, 0.34760155, 0.98907074]])
— np.random.rand
同样从半开放空间中的“连续均匀”分布返回 0 和 1 之间的随机浮点数。唯一的区别在于如何处理参数。使用[np.random.rand](https://docs.scipy.org/doc/numpy/reference/generated/numpy.random.rand.html)
,输出数组的每个维度的长度是一个单独的参数。对于numpy.random.random_sample
,shape 参数是一个单一的元组。
**np.random.uniform(1, 10, size = (3,4))**array([[8.84719423, 7.7877443 , 6.69353674, 1.47646953],
[6.0509388 , 9.49876916, 3.5175086 , 5.32753145],
[8.79560377, 3.93732568, 3.67322864, 9.94109316]])
—从半开放空间中的“连续均匀”分布返回介于 1 和 10 之间的随机浮点数的 3*4 数组。早期的函数总是返回 0 和 1 之间的浮点数,而 np.random.uniform 可以返回任何用户定义范围之间的浮点数。您可以将下限值设置为负值。
注意:对于上述所有函数,如果没有提供参数,则返回一个值。
正态分布
当一个随机变量的大部分值在平均值附近,少数值在尾部时,这个随机变量具有正态分布。分布离平均值越远,得到的值就越少,从而表明平均值更有可能出现(即概率更高)。让我们看看下面几个正态分布生成函数:
**np.random.randn(3,4)**array([[ 0.05056171, 0.49995133, -0.99590893, 0.69359851],
[-0.41830152, -1.58457724, -0.64770677, 0.59857517],
[ 0.33225003, -1.14747663, 0.61866969, -0.08798693]])
—返回一个 3*4 的随机浮点数组,平均值为 0,标准正态分布的标准偏差为 1
**np.random.standard_normal((3,4))**array([[ 0.05056171, 0.49995133, -0.99590893, 0.69359851],
[-0.41830152, -1.58457724, -0.64770677, 0.59857517],
[ 0.33225003, -1.14747663, 0.61866969, -0.08798693]])
同 np.random.randn 唯一不同的是提供论据的方式。这个函数采用一个元组(而不是整数)来指定输出的大小
**np.random.normal(1,2, size=(3,4))**array([[ 1.88245497, 0.3382597 , 5.86154237, 0.49581574],
[ 1.21921968, 4.16496223, -0.81846481, -0.18327332],
[ 1.37520645, 0.34026008, -1.38552922, 0.59024698]])
—帮助生成具有用户定义的平均值和标准偏差的随机数。上面的示例返回一个 3*4 的随机浮点数组,平均值为 1,标准正态分布的标准偏差为 2。您还可以明确提及 loc 和 scale 参数,以明确定义平均值和标准偏差,如下所示
**np.random.normal(loc = 1, scale = 2, size=(3,4))**array([[ 1.88245497, 0.3382597 , 5.86154237, 0.49581574],
[ 1.21921968, 4.16496223, -0.81846481, -0.18327332],
[ 1.37520645, 0.34026008, -1.38552922, 0.59024698]])
正态离散分布
上面所有的例子都是生成具有正常连续分布的随机数的函数。没有默认函数可用于生成正态离散分布(即正态随机整数值)。下面是实现这一点的几种替代方法,这些函数从标准正态分布生成 10*5 的随机整数数组,平均值为 2,标准偏差为 3。
# There is no direct way to generate random integers for normal distribution, following are few workarounds and the best is 'd' as there is no trailing decimals**a = np.trunc(np.random.normal(2, 3, size=(10,5)))
b = np.rint(np.random.normal(2, 3, size=(10,5)))
c = np.round(np.random.normal(2, 3, size=(10,5)))
d = np.random.normal(2, 3, size=(10,5)).astype(int)**# to replace -0 with 0
**a = np.where(a == -0, 0, a)
b = np.where(b == -0, 0, b)
c = np.where(c == -0, 0, c)****print(a); print('\n')
print(b); print('\n')
print(c); print('\n')
print(d)**[[ 6\. 0\. 0\. -1\. 4.]
[-4\. 7\. 0\. 2\. 1.]
[ 6\. -4\. 1\. 0\. 5.]
[-1\. 1\. 0\. 2\. 3.]
[-1\. 5\. 4\. 3\. 4.]
[ 0\. 1\. 0\. 1\. 3.]
[ 0\. 0\. 0\. 0\. 0.]
[ 1\. -1\. 2\. 6\. 4.]
[ 1\. 0\. 0\. 7\. 2.]
[ 0\. 2\. 8\. 2\. 3.]]
[[ 3\. 1\. -1\. 1\. 1.]
[ 4\. 5\. 5\. 3\. 5.]
[ 0\. 6\. 4\. 1\. 3.]
[ 2\. 5\. 7\. 9\. -2.]
[-2\. 0\. 2\. 5\. 3.]
[-4\. 1\. 4\. 3\. 4.]
[ 1\. 1\. 3\. 3\. 3.]
[ 2\. 0\. 3\. 2\. 5.]
[ 6\. 3\. 1\. 0\. 3.]
[ 2\. 1\. 2\. 0\. 4.]]
[[ 1\. 6\. 3\. 4\. -1.]
[ 3\. 4\. -1\. 1\. 2.]
[-2\. 3\. 5\. -1\. 3.]
[-2\. 2\. -3\. 5\. 3.]
[ 2\. 0\. 6\. 8\. -4.]
[ 6\. 7\. 3\. -2\. 5.]
[ 1\. 0\. -2\. 4\. 4.]
[ 0\. 4\. -1\. 4\. 2.]
[ 1\. 2\. 5\. 4\. 4.]
[ 2\. 2\. 4\. 3\. 4.]]
[[ 1 -5 5 8 3]
[ 1 1 1 2 -1]
[ 0 0 2 1 3]
[ 1 4 2 8 -3]
[ 0 4 9 1 2]
[ 1 5 1 4 1]
[-1 2 3 5 1]
[ 0 3 2 2 1]
[ 5 3 7 5 3]
[-2 3 3 4 5]]
二项式分布
当一个随机变量在任何给定的情况下只取两个值中的一个时,它具有二项式分布,并且任何值出现的概率在每种情况下都是相同的(试验)。换句话说,二项式随机变量显示了特定事件在固定次数的试验中发生的频率。
**np.random.binomial(22, .5, 100)**array([ 9, 11, 9, 12, 12, 15, 10, 10, 10, 12, 10, 10, 11, 8, 10, 12, 11, 9, 8, 9, 12, 14, 11, 9, 8, 8, 12, 13, 11, 8, 8, 10, 12, 11, 11, 13, 10, 9, 11, 10, 10, 12, 7, 11, 11, 12, 10, 8, 8, 11, 11, 11, 11, 13, 11, 15, 9, 13, 10, 14, 16, 12, 6, 9, 10, 16, 6, 11, 12, 7, 11, 13, 8, 11, 11, 12, 12, 11, 12, 8, 13, 12, 15, 8, 12, 10, 12, 9, 10, 13, 13, 12, 13, 10, 9, 9, 4, 12, 12, 7])
— 从二项分布中返回 100 个随机整数,进行 22 次试验,每次试验的成功概率为 50%。可以这样想,假设从历史数据来看,苹果股票价格在任何一天上涨的概率是 50%,一个月有 22 个交易日(平均),你想知道苹果价格在一个月内不同天数上涨的概率。所以你做了 100 次这样的实验,得出了上面显示的结果。
为了更好地理解这些数字,你需要绘制一个直方图。
**import seaborn as sns
plt.xlabel('Nos of days Apple price goes up in a month')**
**sns.distplot(np.random.binomial(n=22, p=0.5, size=100), hist=True, kde=True))
plt.show**
从上面的直方图中,你可以清楚地看到苹果股票价格在一个月内不同天数上涨的概率。一个月内价格上涨 11 天的概率是 25%,一个月内上涨 14 天的概率是 10%,一个月内上涨 4 天的概率是 5%,以此类推。如果您绘制如下的计数图,将会更加清楚:
**plt.xlabel('Nos of days Apple price goes up in a month')
sns.countplot(np.random.binomial(n=22, p=0.5, size=100))
plt.show**
上面的计数图显示,在你进行的 100 次实验中——在 20 次实验中,苹果价格在一个月内上涨了 12 天,在 10 次实验中,苹果价格上涨了 14 天,以此类推。
二项分布的一种特殊情况是伯努利分布,其中试验次数总是 1。下面是伯努利随机数发生器函数的一个例子
**np.random.binomial(1, 0.6, size=100)**array([1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0,1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1,
1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0,
0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0])
泊松分布
当随机变量用某些值来描述某一事件在给定空间或时间内发生的概率时,它具有泊松分布。
**np.random.poisson(5, 100)** array([9, 3, 2, 5, 3, 8, 9, 5, 8, 7, 5, 4, 2, 6, 5, 2, 4, 4, 5, 5, 6, 6, 6, 2, 8, 6, 4, 4, 5, 2, 6, 5, 2, 4, 5, 5, 2, 7, 7, 4, 5, 6, 7, 7, 4, 5, 5, 8, 5, 6, 3, 4, 4, 4, 6, 3, 9, 4, 9, 7, 4, 8, 7, 3, 2, 3,
5, 3, 4, 9, 4, 4, 3, 3, 2, 6, 5, 8, 3, 3, 4, 5, 7, 4, 4, 8, 5, 8,
3, 5, 3, 3, 3, 4, 7, 8, 6, 1, 1, 2])
— 从泊松分布返回 100 个随机整数,期望比率为 5。这么想吧,假设过去的数据显示,平均来说,公司债券领域一个月内有五只债券违约。你想知道一个月内不同数量的债券违约的概率。所以你进行了 100 次实验,得出了上面显示的结果。
像二项式一样,为了更好地理解这些数字,你可以绘制直方图和计数图。直方图显示了一个月内不同数量的债券违约的概率,而计数图描述了你进行的 100 次实验中一个月内不同债券违约的数量。
**plt.xlabel('Nos of bonds default in a month')
sns.distplot(np.random.poisson(5, 100), hist=True, kde=True)
sns.countplot(np.random.poisson(5, 100))
plt.show**
指数分布
指数分布与泊松分布成反比。当随机变量用某些值来描述特定事件发生之前的时间时,它遵循指数分布。换句话说,与泊松过程相关的等待时间分布是指数分布。
**random.exponential(scale=2.4, size=100)**array([ 9.06140756, 0.95891383, 4.90338297, 2.20290367, 2.56006299, 0.03202048, 1.88378882, 6.63501052, 0.42636625,
.
.
.
0.44203044, 3.54720377, 1.14886891, 11.75031166, 1.59421014,
1.40063617, 3.2650319 , 6.32941804, 1.91424698, 0.95795208])
—从平均等待时间为 2.4 时间单位的指数分布中返回 100 个随机整数。继续前面的泊松例子,如果一个月内有五只债券违约,那么两次违约之间的时间间隔是 2.4 天(1/5)。您想要找出两次违约之间不同天数的概率。所以你进行了 100 次实验,得出了上面显示的结果(为了节省空间,我压缩了输出)。
像前面一样,如果你画出分布图,你就能更好地解读它。下面的直方图显示,下一次违约前 0 到 2 天的等待时间具有最大的可能性。注意,这个结论只有在单位时间内发生 5 次违约时才成立,反之,在两次违约之间,衰减率为 2.4 个单位的等待时间。
**plt.xlabel('Nos of waiting time between two defaults in a month')
sns.distplot(random.exponential(scale=2.4, size=100), hist=False, kde=True)
plt.show**
注意:根据设计,二项式和泊松分布是离散分布,因此这些函数总是返回整数,而指数分布是连续分布(也是根据设计),并且总是返回浮点数。
尾注
从 Numpy 1.17 开始,引入了一个新的随机数生成器来从各种分布中生成样本值。在新的方法中,生成器被定义为一个需要实例化的容器类,然后该实例将用于从您喜欢的任何分布中生成随机值。例如:
new_rand = np.random.default_rng()new_rand.uniform(0,1,5) # generate 5 Uniform Random Variables from Uniform Distribution
欲了解更多关于新发电机的详情,请点击此链接https://numpy.org/doc/stable/reference/random/
结论
这是我在博客系列中的第一篇文章,我们探索了数学和数据科学的实际应用之间的联系,以及我们的数学和统计知识如何帮助我们轻松有效地解决数据科学问题。希望关于随机数及其相关概念以及它们在数据科学中的用法的简要概述对您有所帮助。
其他资源和参考:
有关随机变量和随机数生成的详细信息:
https://docs . scipy . org/doc/numpy-1 . 16 . 0/reference/routines . random . html
对于单变量分布关系的可视化表示:
【http://www.math.wm.edu/~leemis/chart/UDR/UDR.html】T5T6
如果你想了解更多关于梅森图勒工艺的信息:
https://www . si cara . ai/blog/2019-01-28-how-computer-generate-random-numbers
一篇关于如何用 Python 生成随机数的好文章:
https://machinelingmastery . com/how-to-generate-random-numbers-in-python/
感谢阅读!请继续探索,如有任何疑问或建议,请随时给我发邮件:
sasank.majumdar@gmail.com
https://www.linkedin.com/in/sasank-majumdar-5616047/
发现 DAX 中 FILTER()的强大功能
DAX 中的 FILTER()函数是强大的,但是它有一些复杂性。让我们深入研究这些细节,以便更好地理解 FILTER()函数。
介绍
大多数人都知道 DAX 中的 FILTER()函数。
但是,有可能你误用它或者没有使用这个功能的全部力量。
例如,前段时间,我看到一个类似这样的查询:
EVALUATE
SUMMARIZECOLUMNS(
‘Product’[BrandName]
,”Sales”, CALCULATE([Sum Online Sales]
,FILTER(‘Product’
,’Product’[ProductCategoryName] = “Computers”
)
)
)
虽然这个查询在语法上是正确的,但它不是最佳的。
在下图中,您可以看到来自 DAX Studio 的计时信息:
图 1 —使用过滤器的查询时间(按作者分类)
一个更好的版本是这个:
EVALUATE
SUMMARIZECOLUMNS(
‘Product’[BrandName]
,”Sales”, CALCULATE([Sum Online Sales]
,’Product’[ProductCategoryName] = “Computers”
)
)
这是 DAX Studio 中不带滤波器的服务器计时:
图 2 —不使用过滤器的查询计时()(图由作者提供)
当您查看 SE CPU 时间时,可以看到第二个查询在没有过滤的情况下几乎需要一半的处理时间。
而且,虽然第一个查询需要三个存储引擎操作才能完成,但第二个查询只需一个 SE 操作就可以处理。
让我们看看为什么会发生这种情况,以及我们可以用 FILTER()函数做些什么。
到底什么是过滤器()
过滤函数是一个迭代器,就像 SUMX 和其他 X 函数一样。
因此,您可以使用行上下文和上下文转换来释放 FILTER()的全部功能。
如果您不熟悉 DAX 中的上下文转换,可以看看我关于这个主题的文章:
https://towards data science . com/whats-fancy-about-context-transition-in-DAX-efb 5d BC 4c 01
FILTER()是一个迭代器的事实解释了为什么当它与 CALCULATE()或 CALCULATETABLE()一起使用时,它比添加一个“普通”过滤器要慢,如上所示。
但是,当您需要使用基于行的表达式或基于度量过滤数据时,您需要使用 filter()。
另一个事实是 FILTER()返回一个表。因此,您可以使用 FILTER()生成过滤表,并在您的数据模型或度量中使用它们。
使用过滤器()创建 DAX 表
因为 FILTER()返回一个表,所以您可以使用它在 Power BI 中创建一个计算表或查询您的模型。或者,您可以使用函数在模型中创建一个计算表。
以下查询显示了一个使用 FILTER 查询表并筛选结果的示例:
EVALUATE
FILTER(Store
,Store[StoreType] <> “Store”
)
作为迭代器,您可以使用行上下文来过滤结果:
EVALUATE
FILTER(‘Online Sales’
,’Online Sales’[UnitPrice] * ‘Online Sales’[SalesQuantity] > 1000
)
当然,这是一个非常昂贵的查询,因为必须对在线销售表中的每一行执行两列的乘法。但是它可以完全由存储引擎运行,因此可以使用所有可用的 CPU 内核,这使得这种方法非常高效。
虽然存储引擎(SE)可以在多个 CPU 核心上处理数据,但公式引擎(FE)每次查询只能使用一个 CPU 核心。因此,无论什么可以由 SE 处理,都比用 FE 处理更有效。
接下来,我们可以使用上下文转换来查找销售额超过 1,000,000 的商店:
EVALUATE
FILTER(Store
,[Sum Retail Sales] > 1000000
)
正如我们将在下一节中看到的那样,使用度量来过滤表的可能性对于计算结果是很方便的。
但是,这些查询总是返回筛选表中的所有列。如果我只想检索列的子集,该怎么办?
在这种情况下,我可以使用表函数只获取我感兴趣的列。
例如:
EVALUATE
FILTER(SUMMARIZECOLUMNS(Store[StoreType]
,Store[StoreName]
,Store[StoreManager])
,[Sum Retail Sales] > 1000000
)
下面是上面查询的结果:
图 3 —带列选择的查询(作者提供的图)
FILTER()接受所有表函数作为第一个参数。
在 CALCULATE()和 CALCULATETABLE()中使用
CALCULATE()和 CALCULATETABLE()接受表作为筛选参数。
由于这个事实,您可以使用 FILTER()生成一个表,并使用这种机制修改测量的结果。
查看以下带有度量值[大型商店]的查询:
DEFINE
MEASURE ‘All Measures’[Large Stores] =
CALCULATE([Sum Retail Sales]
,FILTER(Store
,[Sum Retail Sales] > 100000000
)
)
EVALUATE
CALCULATETABLE(
SUMMARIZECOLUMNS(Store[StoreName]
,”Sales”, [Sum Retail Sales]
,”Is large Store”, IF([Large Stores]>0
,TRUE(), FALSE())
)
,’Date’[Year] = 2020
)
ORDER BY [Is large Store] DESC, [Sum Retail Sales] DESC
该查询旨在查找 2020 年销售额超过 100,000,000 美元(美元、€或其他金额)的所有商店。
查询结果见下图:
图 4 —使用过滤器()的测量结果(由作者提供)
让我们分析一下这里发生了什么:
- 该查询使用 SUMMARIZECOLUMNS()生成所有商店的列表
- CALCULATETABLE()用于添加 2020 年的过滤器
- 我调用[零售总额]度量来获得 2020 年每家商店的销售额
- 我调用度量[大型商店]并检查结果是否大于 0
a。如果是,我返回 TRUE()
b。如果不是,我返回 FALSE()
该度量使用现有的筛选器上下文来计算结果。过滤器上下文包含 2020 年的过滤器。
FILTER()函数在评估销售额超过 100,000,000 的商店列表时会考虑此筛选器。
FILTER()返回高于阈值的商店列表。
CALCULATE()仅对这些商店执行[零售总额]的计算,因为 FILTER()的结果用作度量值中的筛选修饰符。
因此我们可以在查询中使用一个 check,像 IF(【大型商店】> 0,TRUE(),FALSE()),来生成期望的输出。
注意上下文转换
当您使用 FILTER()时,上下文转换是一个重要的主题。
假设我们想要过滤我们的销售交易,以便只获得值为 1000 或更大的行。
您可能想使用 FILTER()来构造一个表,并将这个表用作 SUMX()的源:
Large Sales Amount =SUMX(
FILTER(
‘Retail Sales’
,’Retail Sales’[SalesQuantity] * ‘Retail Sales’[UnitPrice] >= 1000
)
,[Sum Retail Sales]
)
这里,我使用 FILTER()生成一个高于上述阈值的事务列表。
虽然结果可能是正确的,但我通过调用 SUMX 中的[零售总额]度量来触发上下文转换。
更好更快的方法是以下措施:
Large Sales Amount =CALCULATE (
[Sum Retail Sales],
FILTER (
ALL ( ‘Retail Sales’[SalesQuantity]
,‘Retail Sales’[UnitPrice] ),
‘Retail Sales’[SalesQuantity] * ‘Retail Sales’[UnitPrice] >= 1000
)
)
在这里,我使用 CALCULATE()中的度量,并使用 FILTER()在“OnlineSales”表上应用一个过滤器来计算所需的结果。
性能上的差异非常明显,如下图所示:
图 5 —好的与坏的过滤器()使用的比较(作者提供的图表)
第一个度量显示了第一个度量的计时和查询计划,使用 SUMX()和 FILTER()。在下面,您可以看到第二个测量的计时使用了 CALCULATE()和 FILTER()。
原因在查询计划中是显而易见的,您可以看到查询处理了 1'451'337 行两次。此外,还执行了两个 CallbackDataID 步骤,在公式和存储引擎之间推送数据。这些操作非常昂贵。
结论
FILTER()函数对于 DAX 工具箱来说是必不可少的。
您需要了解他的能力以及使用该功能时的潜在问题。
但是,它给了你很多增强 DAX 表达的机会。
总结一下:
- FILTER()是迭代器
- FILTER()返回一个表格
- 可以在 CALCULATE()中使用 FILTER()来设置过滤器上下文
- 您可以在 FILTER()中使用上下文转换
- 如果使用不当,可能会降低 DAX 表达式的速度
一旦您正确理解了这个函数,您就可以开始在正确的地方使用它,并释放 FILTER()的全部威力。
参考
FILTER()函数在本页描述:过滤器— DAX 指南
这个页面有一个嵌入的视频,有更多的信息和例子。
关于在 CALCULATE()中使用 FILTER()时应避免的信息,可在此处找到:避免在 DAX 中使用 FILTER 作为筛选参数— DAX | Microsoft Docs
我使用 Contoso 样本数据集,就像我以前的文章一样。你可以从微软这里免费下载 ContosoRetailDW 数据集。
Contoso 数据可以在 MIT 许可下自由使用,如这里的所述。
我扩大了数据集,使 DAX 引擎工作更努力。
在线销售表包含 6300 万行(而不是 1260 万行),零售表包含 1550 万行(而不是 340 万行)。
https://medium.com/@salvatorecagliari/membership
发现有效自学数据科学的强大方法
基于众包的更好更快学习方法
安妮·斯普拉特在 Unsplash 上的照片
我要在这里分享的技术并不新奇。事实上,这是我们每个人在某个时候都会用到的东西。但不仅仅是在学习数据科学时被普遍采用。在大学考试前。当涉及的话题太多时。学生们通常会把话题分成几个小组。每个人都将研究分配给他们的课题。最后,每个人都聚在一起分享他们的学习成果。这是一种非常有效的方法,可以快速覆盖大量的主题。
这种技术经常在许多场景中被采用。一些例子是,
- 有创业公司利用众筹来成长。
- 有些公司利用众包从员工和客户那里获得创意。
- 奖励通常用于让人们识别软件产品中的错误。
- 众所周知,科技公司会奖励那些在其平台中发现漏洞的人。
这个概念背后的基本原理很简单。而不是委托一个个人或者一小群人。很多人参与的时候,成功的概率很大。
如果我们考虑学习数据科学的人。他们可以大致分为两种人。其中一个是注册了结构化项目的学生,比如数据科学硕士。另一类是做自学的。那些自学的人错过了大学提供的优势。本文是为那些试图自学数据科学的人准备的。在本文中,我将分享使用众包技术最有效地学习数据科学所需遵循的步骤。
学习数据科学不是几天或者几周就能完成的事情。这确实需要很多时间。很难在整个旅程中保持这种兴趣和精力。但是当你作为一个团队做同样的事情时,你会突然觉得事情变得容易多了。下面是你需要遵循的步骤。
步骤 1-列出要学习的主题
第一步是理解主题。你需要确定你需要学习的各种主题。这取决于你的经验、技能和教育背景。你需要确定哪些话题你需要更多的关注,哪些是你比较擅长的。一般来说,打算从头开始的人会考虑学习以下主题。
- 基本程序设计
- 数据科学中常用的库和包
- 数据可视化
- 探索性数据分析
- 统计数字
- 特征工程
- 算法
- 评估指标
这里重要的目的不仅仅是列出题目。更多的是了解需要学习的东西。还有,要自省以上每个题目的专业水平。这将有助于了解你的优势和劣势。
第二步——确定一个团队或学习伙伴
接下来就是找一个学习伙伴或者一群感兴趣的人。找到真正感兴趣的人是非常重要的。当你自己学习时,你的成功取决于你自己。但在这里,每个参与者都扮演着重要的角色。每个人都应该坚持到底。
关注他人的一个最好方法是,首先关注你的密友。然后开始关注像 Kaggle 这样的平台。Kaggle 拥有超过 500 万注册用户。他们中的许多人热衷于在数据科学领域发展事业。如果仍然不成功,请参加数据科学会议。数据科学无疑是热门领域之一,因此寻找学习伙伴应该不成问题。
第三步——制定每日计划
想出一个详细的计划来学习各种主题。Coursera 上有许多课程可供选择。如果你想找一个受欢迎的课程。下面是一门评价很高的课程。超过 25%完成这门课程的人开始了新的职业生涯。本课程介绍了数据科学项目不同阶段的各种活动。
https://www.coursera.org/professional-certificates/ibm-data-science
这是一门初学者友好的课程,但你需要成为 Coursera 的付费会员才能学习这门课程。如果你正在寻找一个没有成本的选择。然后,我有一个自定义计划解释在下面的文章。这包括学习成功开展数据科学职业所需的所有基本主题。
https://medium.com/swlh/a-complete-guide-to-learn-data-science-in-100-days-8c6557154102
你的日常计划需要详细。您可以定制上述计划,以满足您的需求和技能。这里的目标是了解数据科学所需的不同技能集。然后想出一个最符合你需求的计划。
第四步——分配主要和次要主题
完成计划后,就该和你的团队一起工作了。你需要在你的小组中分配话题。人们退出数据科学的一个常见原因是疲惫。当一个人长时间非常努力地工作时。很容易筋疲力尽而放弃。
这个问题可以通过给你团队的每个成员分配主要和次要的主题来解决。有一个主要主题的人需要详细地研究这个主题,收集参考资料和有用的资源。然后那个人需要和团队分享。如果主要话题的人向其他人解释这个话题就更好了。这种方法可以确保平均分配工作量。此外,拥有一个团队将增加责任感。
另一个好处是解决问题和澄清疑问会更容易。疲惫的一个主要原因是学习数据科学时遇到的问题。当有一个团队的时候,事情变得相对容易解决。
这种学习数据科学的方式将创造一个健康的竞争环境。这将进一步有助于增加承诺和更好的学习。
第五步——选择一个平台来分享和跟踪进展
不可能找到一个每天都能见面的团队。在 covid 之后,我们学到的一件事是,我们工作/学习的地点并不重要。如果你有一个分享和跟踪进展的计划,那就足够了。
寻找建议?当你的学习小组很小时,电子邮件是一个很好的选择。当你有一个更大的团队时,不和可能是一个好的选择。共享内容和跟踪进度会更容易。创建语音渠道和与团队通话很容易。
在学习各种数据科学主题时,最好
这在你以后想参考某个话题的时候会很有帮助。
第六步——有一个导师
有一个好的导师将会改变游戏规则。当你为自己选择导师时。永远选择那些在职业生涯中至少领先你 5 年的人。
一个好的导师会一直为你奉献时间。他们会努力理解你的优势和劣势。他们会愿意向你介绍他们的关系网。同时,引导你的学习之旅。
如果你有兴趣找到一个好的导师,并充分利用你的导师,那么请阅读下面的文章。下面的文章讨论了拥有导师的好处,寻找导师,以及确保成功的导师-学员关系的简单事情。
第七步——测试你的技能
另一个关键方面是测试你的技能。测试你在数据科学方面的技能的最好方法是将它们付诸实践。是的,你需要做项目。Kaggle 是一个优秀的平台,拥有许多有趣的数据集和笔记本。而不是像泰坦尼克号一样选择流行的数据集。如果您选择不太受欢迎的数据集,将会非常有帮助。如果能自己刮数据就更好了。
这里是我的频道中关于使用 Kaggle 掌握数据科学技能的视频。如果你正在寻找一些有趣的数据科学项目,请查看下面的文章。
HackerRank 是一个免费的平台,可以用来测试你在 Python、R 和 SQL 等编程语言方面的专业水平。
收尾技巧——寻找灵感
成功的一个重要因素是保持足够高的动力。有许多非编程背景的人在数据科学领域获得了成功。数据科学家所需的所有技能都可以获得。你只需要有一个成长的心态。
阅读和了解不同人进入数据科学的旅程可能会很有启发性。这绝对有助于坚持不懈地追求。这是我从销售到数据科学的旅程。
有兴趣加入 250+人学习数据科学?
我最近开了一个众包平台,学习数据科学。到目前为止,已经有 250 多人加入了这个小组。这个计划是每个工作日学习一个话题,并连续 100 个工作日都这样做。主题和参考资料将与参与者分享。鼓励小组中的每个人分享和学习。这是一个试点项目,旨在研究这一概念在学习数据科学方面的有效性。
如果你有兴趣,加入这个不和谐服务器。
保持联系
- 如果你喜欢这篇文章,并且对类似的文章感兴趣,在 Medium 上关注我。成为中级会员,访问数千篇与职业、金钱等相关的文章。
- 我在我的 YouTube 频道上教授和谈论各种数据科学主题。在这里订阅我的频道。
- 在此注册我的电子邮件列表,获取更多数据科学提示,并与我的工作保持联系
像数据科学家一样发现电影的灵魂
从描述中提炼出这部电影固有的缺点
Ahmet Yal nkaya 在 Unsplash 上拍摄的照片
在这个故事中,我们有一个特定的目标:给定一部电影的情节,我们如何发现它的灵魂?怎样才能定位到最有意义的词,让我们想投入时间去看一部电影?哪些概念容易引起我们的共鸣,哪些符号让我们对图片的波长产生共鸣?
在本文中,我们探索如何使用非负矩阵分解(NMF)来模拟一组电影的类型或主题。此外,我们为每种类型确定最重要的词,并提出描述每部电影的潜在特征。我们可以稍后通过迁移学习使用这些特性来解决其他问题。
学习率是为那些对 AI 和 MLOps 的世界感到好奇的人准备的时事通讯。你会在每周五收到我关于最新人工智能新闻和文章的更新和想法。在这里订阅!
什么是 NMF?
NMF 是一种矩阵分解技术,很像奇异值分解(SVD),但我们将结果矩阵约束为非负的,而不是正交的。
给定一个矩阵X
,我们想把它分解成两个矩阵W
和H
,这样X ≈ W x H
。我们使用近似等号≈
,因为与奇异值分解不同,NMF 产生原始矩阵的近似值。而且W
和H
的每个元素都是非负的。
如果矩阵
*X*
保存人脸图像,*W*
将捕捉这些人脸的面部特征以及*H*
这些特征在每幅图像中的相对重要性。
直观地说,我们可以把这两个矩阵想成这样:假设我们有一个矩阵X
,其中每一列都是一张人脸的矢量化图像。W
表示面部特征(如鼻子、眉毛、胡须等。)和H
捕捉每个图像中特征的相对重要性。
作者图片
既然我们对 NMF 的成就有了一个展望,我们就准备动手了。但是首先,我们再简单地转到单词规范化和 TF-idf。
密度与重要性
词频——逆文档频率 (TF-idf) 是信息检索中常用的一种权重度量。它的作用是衡量一个词对语料库中的文档有多重要。
TF-idf 由两个术语组成。第一个是术语频率,计算一个单词在文档中出现的归一化频率。因此,单词在文档中出现的次数除以该文档中的总单词数。
TF 衡量一个单词在文档中出现的频率,而 idf 计算一个单词的重要性。
第二个术语是反向文档频率(idf)。这是通过语料库中文档数量的对数除以出现特定术语的文档数量来计算的。因此,从第一项中,我们得到了单词的频率,而第二项通过对频繁出现的单词进行加权并对罕见的单词进行放大,为我们提供了每个单词的重要性。
回到我们的任务
现在我们对 NMF 和 TF-idf 有了一个很好的了解,我们已经准备好着手解决眼前的问题;我们如何从一个电影情节中发掘更多?
数据集
对于这个实验,我们使用由 JustinR 在CC BY-SA 4.0creative commons 许可下创建的维基百科电影情节数据集。该数据集包含来自世界各地的 34,886 部电影的信息,如标题、类型、上映年份、导演、情节、等。不过我们将要使用的尺寸只是标题和 T21 情节。
此外,我们还使用了 Philippe Rémy 的英语常用名数据集,原因我们将在下面进一步解释。
用 NMF 进行主题建模
现在让我们深入研究代码,并从我们的工作中获益。首先,我们需要加载数据。我们将维基百科电影情节数据加载到内存中,对 50%的电影进行采样以避免内存问题,并只保留我们关心的列(即电影标题和情节)。我们还加载英语名字数据集。
接下来,我们准备电影情节,将它们存储在一个列表中,使用 TF-idf 对它们进行规范化和矢量化。目的是建立一个矩阵X
,其中行类似于电影,列是在所有情节组合中出现的唯一单词。因此,矩阵X
中的单元格ij
捕获了j
这个词在电影i
的情节中出现了多少次。例如,在下面的矩阵中,我们可以看到单词2
在电影1
的情节中出现了7
次。
作者图片
现在让我们看看 python 代码。我们使用方便的 scikit-learn 的TfidfVectorizer
类来实现我们的目的。这个类将一个叫做stop_words
的东西作为参数。这些都是常用词,如“一”、“是”、“该”、“T22”等。,没有任何实际意义,也没有任何价值。
除了这些单词之外,我们还会传递英文名字,因此我们也可以将它们排除在流程之外。在矢量器完成对单词的处理后,我们可以得到最终的词汇表,供以后使用。
现在我们有了矩阵X
,我们想像之前看到的那样,把它分解成两个矩阵W
和H
。为此,我们可以使用 scikit-learn 提供的NMF
因子分解器。分解需要一个名为n_components
的参数。这些是主题的数量,或者在我们的例子中是电影主题,我们希望返回。
我们现在准备好研究每一个流派的灵魂。我们想找到每个主题最重要的词是什么。这个过程从情节中提取主题,并对电影主题进行建模。除了两个辅助函数之外,我们已经具备了实现这一目标所需的一切。让我们在下面定义它们。
结果是每个18
主题的前10
个单词,因为我们将n_components
设置为18
。那么,它们是什么?
18 个主题中每个主题的前 10 个单词—图片由作者提供
有些主题乍一看没有意义,但它们中的大多数都是特定流派的代表。例如,倒数第二个是关于动画和卡通的,第二个是关于犯罪的,我们在倒数第三排有一个西部主题,在中间的某个地方有一个医学主题。对于 10 到 15 行代码来说,这是一个令人印象深刻的结果!
结论
在这个故事中,我们探讨了非负矩阵分解(NMF)以及如何使用它进行主题建模。NMF 在人脸分解、协同过滤、化学、基因表达等等方面都有各种应用。您现在已经有了进一步检查它并在项目中使用它所需的东西。
这个故事的灵感来自于雷切尔·托马斯关于计算线性算法的课程。
关于作者
我的名字是 Dimitris Poulopoulos ,我是一名为 Arrikto 工作的机器学习工程师。我曾为欧洲委员会、欧盟统计局、国际货币基金组织、欧洲央行、经合组织和宜家等主要客户设计和实施过人工智能和软件解决方案。
如果你有兴趣阅读更多关于机器学习、深度学习、数据科学和数据操作的帖子,请关注我的媒体、 LinkedIn 或 Twitter 上的 @james2pl 。
所表达的观点仅代表我个人,并不代表我的雇主的观点或意见。
Discover Urban viewer:一个交互式 web 地图,用于可视化全球城市地区的人口
如何在传单中创建包含全球城市群的网络地图
图片由作者提供。城市浏览器:包含全球城市群的交互式网络地图
世界正在城市化。2018 年,全球城市住区容纳了约 55.3%的世界人口。联合国的预测估计,到 2030 年,60%的世界人口将居住在城市聚集区。城市化的主要趋势揭示了政府对使人类住区具有包容性、安全性、弹性和可持续性的关注[1]。由于城市新陈代谢(废物),自然正在产生毁灭性的影响,当局主要关注的是通过创建有效的减少废物的过程来减少这种影响,例如回收利用、替代能源或化石燃料的转换,等等。
联合国做了一个引人入胜的人口修订,名为: 世界城市化前景:2018 年修订的显示了全球城市群的人口预测。因此,我们将正确理解城市群的概念,这是我们在此实践中使用的划界,并将其与另外两个城市概念区分开来:市区和都市圈。
市区代表市区的行政核心。它保持了一个中心界限。下一个概念是城市群,它指的是划定城市边界的毗连城区或建成区的范围。最后,大都市区域*代表了经济和社会相互联系的边界,通过商业或通勤模式相互联系。这些概念可以在提到的 2018 年修订版中进行审查。*
“本练习的目的是创建一个交互式网络地图,以活页形式显示 2020 年全球城市群的估计人口”
数据
我们在这个地图可视化实践中使用的数据集来自联合国经济和社会事务部人口动态部。具体来说,我们使用的是文件 12* ,其中包含了 1950-2035 年按国家划分的 2018 年拥有 30 万或以上居民的城市群的人口(千人)[2]该数据集在 3.0 版的知识共享协议下获得许可。*
《2018 年人口修订版》发布了包含大量城市预测信息的出版物,可在 出版物部分 找到。
练习
该练习分为两个部分:1)数据格式,以及 2)城市查看器 web 地图创建。
1)数据格式
从下载页面下载数据集[WUP2018-F12-Cities_Over_300K.xls](https://population.un.org/wup/Download/Files/WUP2018-F12-Cities_Over_300K.xls)
后。您可能会注意到,这是一个 excel 文件,其结构不适合使用,尤其是在传单地图中。此外,您必须正确构建数据集,例如:
图片由作者提供。表格的正确结构示例。
您可以直接在 excel 中完成,您可能需要保留列:Urban Agglomeration
、Latitude
、Longitude
和2020
。然后,您必须以这种方式更改列名:Urban Agglomeration
到City_name
和2020
到a2020
,以便它匹配城市查看器的代码。
当你有了合适的结构后,将它保存为.csv
,然后在 geopandas 中打开它,并将几何列(经度,纬度)作为一个几何对象包含进来。您可以在文章赫尔辛基的自行车共享系统运动:用交互式流程图进行聚合和可视化 的 数据生成部分回顾这个过程。 最后保存为 GeoJSON,名称pop_cities.geojson
。您会发现数据集已经在存储库中。
为小叶映射手动完成的最后一步是在数据集中添加变量的名称。小叶网有个例子。因此,我们要使用的最终数据集应该是这样的:
图片由作者提供。数据集可在城市查看器中使用
如果您想要自动创建更多不同年份的数据集,或者在创建 GeoJSON 时需要帮助,请随时寻求支持。您可以在LinkedIn上的我的个人资料中联系我,或者干脆在本文中留言。
2)城市观者创作
现在,我们必须创建一个包含必要文件的存储库,以使 Urban Viewer 正常工作。我们添加了一个名为css
的文件夹和另一个名为js.
的文件夹,我们还添加了一个名为index.html
的 HTML 文件,你可以通过在本地磁盘上克隆这个库来获得这些文件。因此,您的本地文件夹可能如下所示:
图片由作者提供。知识库结构。
注意在数据文件夹*中已经有一个pop_cities.geojson
文件,它是步骤 1)的结果。要使用文件进行 web 映射,我推荐使用Atom或者简单地使用notepad++。*****
首先,我们将下载并复制 传单 API 用于文件夹js.
中的 web 制图。此外,我们添加一个空的 JS 文件,在本例中命名为main.js
图片由作者提供。js 文件夹的结构
然后,在文件夹css
中,我们添加来自传单的 CSS 文件和一个空文件,我们在本例中称之为style.css
CSS 文件夹,如下所示:
图片由作者提供。CSS 文件夹的结构
2.1)将文件加载到 index.html
我们将使用 Atom 或 Notepad++打开index.html文件,并开始加载文件。它将包含一个头部和一个主体。在主体部分,我们包含了运行地图动画的主要文件,还有传单文件和数据文件。它包括来自 ESRI 的基本地图。**
***<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Urban Viewer Demo</title> <!--link to stylesheet-->
<link rel="stylesheet" href="css/style.css"><!-- link to leaflet stylesheet-->
<link rel="stylesheet" href="css/leaflet.css"></head><body>
<!-- title of your map-->
<h1> 🌎Urban viewer - Urban agglomerations worldwide </h1><!-- division div for the map -->
<div id="map"></div><!-- link to leaflet javascript library-->
<script src="js/leaflet-src.js"></script><!-- load Esri Leaflet because we want to use an Esri basemap -->
<script src="[https://unpkg.com/esri-leaflet@2.3/dist/esri-leaflet.js](https://unpkg.com/esri-leaflet@2.3/dist/esri-leaflet.js)"></script><!--link to the files that contains geoJson data-->
<script src="data/pop_cities.geojson" type="text/javascript"> </script><!-- link to main javascript file -->
<script src="js/main.js"></script></body>
<html>***
2.2)map-style . CSS 中的参数
我们必须设计 HTML 对象的样式,我们在。css 文件中的对象:标题、正文、图例和地图。打开style.css
,包含下一段代码:**
***h1{
position: fixed;
font-family: "Times New Roman", Times, serif;
font-size: 24px;
box-shadow: 2px 2px 3px 3px black;
background: lightgray;
color: black;
margin-left:5%;
margin-top: 0.6%;
z-index: 2;
}body{
width: 100%;
height: 100%;
margin: 0px;
font-family: "Helvetica Neue", Arial, Helveticam sans-serif;
}.info {
padding: 6px 8px;
font-family: "Times New Roman", Times, sans-serif;
font-size: 20px;
background: white;
background: rgba(255,255,255,0.8);
box-shadow: 0 0 15px rgba(0,0,0,0.2);
border-radius: 5px;
}
.info h3 {
font-size: 24px;
font-family: "Times New Roman", Times, serif;text-shadow: 2px 2px 5px gray;
margin: 0 0 5px;
color: #282825 ;
}.legendcolor {
line-height: 18px;
color: #555;
}
.legend i {
width: 18px;
height: 18px;
float: left;
margin-right: 8px;
opacity: 1;
}.legend .circle {
border-radius: 50%;
width: 15px;
height: 15px;
margin-top: 8px;
}#map {
height:100%;
width:100%;
left:0%;
overflow:hidden;
position:fixed;
border:1px #444 solid;
}***
您可能会注意到它包含可视化参数,如字体、位置或地图大小。如果你想重新设计你自己的城市观察器,你可以改变它们。
添加 CSS 代码后,如果您在浏览器中打开 index.html,您可能会看到如下所示的空白画布:
图片由作者提供。空画布
2.3)创建城市浏览器
在这里,我们将开始用传单创建地图。是时候打开并开始编辑main.js
文件了。我们一步一步来。你可以看一下 传单互动 Choropleth Map 的例子,我在建立 Map 变量和定义图例颜色时得到了一些帮助。
首先,我们为地图添加一个变量,定义缩放级别和中心。然后,我们添加 OSM (OpenStreetMap)和 ESRI 作为底图。您可以在此处更改属性,例如您的姓名或机构名称,以便在最终的 web 地图中显示。
***// ADDING BASE MAPS, MAP AND SCALE BAR
var map = L.map('map').setView([25, 12], 3);var osm =
L.tileLayer('[https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{attribution:'Open](https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{attribution:'Open) Street Maps | Bryan R. Vallejo'});var esri =
L.tileLayer('[https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}'](https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}'), {attribution: 'Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community | Bryan R. Vallejo',maxZoom: 18});var esridark =
L.esri.basemapLayer('DarkGray',{attribution: 'Bryan R. Vallejo'});var esrigray =
L.esri.basemapLayer('Gray',{attribution: 'Bryan R. Vallejo'});esridark.addTo(map)var basemaps={
'DarkGray': esridark,
'Satellite': esri,
'OSM': osm
}L.control.scale({imperial:false, position:'bottomleft'}).addTo(map);***
如果在浏览器中刷新 index.html 文件,它应该是这样的:
图片作者。从带有 ESRI 底图的传单添加地图对象
现在,我们将添加数据集,并在信息框中包含检索人口信息的函数。如有必要,务必检查活页示例。**
***//FUNCTIONS/Function to highlight Featuresfunction highlightFeature(e) {
var activefeature = e.target;
activefeature.setStyle({
weight: 5,
color: '#F0F92B',
dashArray: '',
fillOpacity: 0.3
});
if (!L.Browser.ie && !L.Browser.opera) {
activefeature.bringToFront();
} info.update(activefeature.feature.properties);
}//function for resetting the highlight
function resetHighlight(e) {
cities.resetStyle(e.target);
info.update();
}function zoomToFeature(e) {
map.flyTo(e.target.getLatLng(),6);
}//to call these methods we need to add listeners to our features
//the word ON is a short version of addEventListenerfunction interactiveFunction(feature, layer) {
layer.on({
mouseover: highlightFeature,
mouseout: resetHighlight,
click: zoomToFeature,
} );
}***
然后,我们将继续添加功能。第一个是根据人口数量定义圆的 半径大小 。第二个,圆圈 的 颜色。第三个,根据点的大小(人口)和颜色定义点的 样式 。
***// calculate the circles' radius given the cities' populationfunction getRadius(pop) {
var maxSymbolsize = 20; // maximum symbol size
var maxValue = 37393129; // highest population value in the dataset
r = maxSymbolsize * Math.sqrt(pop/maxValue); // proportional by area
return r;
}// create the circles' stylefunction getColor(d) {
return d > 10000000 ? '#d7301f' :
d > 5000000 ? '#fc8d59' :
d > 1000000 ? '#fdcc8a' :
'#fef0d9' ;
}// radius calculated with function above and population property form GeoJSON as inputfunction style(feature) {
return {
radius: getRadius(feature.properties.a2020),
fillColor:getColor(feature.properties.a2020),
color: "#000",
weight: 1,
opacity: 0,
fillOpacity: 0.9
};
}***
目前,我们已经定义了创建 web 地图交互的功能,以及城市群的风格(大小和颜色)。
下一步,添加城市层。
***// Add circles, popups and tooltips to the mapvar cities=L.geoJson(pop_cities, {
pointToLayer: function (feature, latlng) {
return L.circleMarker(latlng, style(feature));
},
onEachFeature: interactiveFunction
}).addTo(map)***
如果您在浏览器中刷新index.html
,它可能看起来像这样:
图片由作者提供。《城市观察》中的城市群初窥
下一步是在 infobox 中检索每个城市的居民数量。为此,我们可以包含一个 infobox 对象和使它具有交互性的功能。
***//ADDING A INFO CONTROL BOX
var info = L.control();info.onAdd = function (map) {
this._div = L.DomUtil.create('div', 'info'); // create a div with a class "info"
this.update();
return this._div;
};// method that we will use to update the control based on feature properties passed
info.update = function (props) {
this._div.innerHTML = '<h3> Cities and population </h3>' + (props ?
'<b>'+props.City_name +'</b>'+ ' 🌆' +'<br/>' + props.a2020 + ' Inhabitants in 2020':
'Hover the mouse over the map to see data.'+'<br/>'+'¡Try clicking over the cities!' );
};info.addTo(map);***
如果刷新 index.html 文件,信息框可能如下所示:
图片由作者提供。添加到城市查看器的信息框
然后,我们通过一个循环添加定义类颜色的图例。
***//ADDING A LEGEND WITH COLORSvar legendcolor = L.control({position: 'bottomleft'});
legendcolor.onAdd = function (map) {
var div = L.DomUtil.create('div', 'info legend'),
grades = [300000, 1000000, 5000000, 10000000],
labels = [];
// loop through our density intervals and generate a label with a colored square for each intervalfor (var i = 0; i < grades.length; i++) {
div.innerHTML +=
'<i class ="circle" style="background:' + getColor(grades[i] + 1) + '"></i> ' +
grades[i] + (grades[i + 1] ? '–' + grades[i + 1] + '<br>' : '+');
}
return div;
};legendcolor.addTo(map);***
如果您在浏览器中刷新index.html
文件。您会注意到,当您将鼠标悬停在城市上方时,infobox 会检索信息。此外,还会添加带有适当颜色的图例。看起来是这样的:
图片由作者提供。添加了图例的城市查看器。
最后一步是添加一个图层控制器,如果需要,可以打开底图和城市。
***//ADDING A LAYER CONTROL
var features={
'Cities': cities
}var legend = L.control.layers(basemaps, features, {position: 'bottomleft', collapsed:true}).addTo(map);***
现在,如果需要,你可以改变基本地图并关闭城市。最后,使用 OSM 底图,城市查看器看起来是这样的:
图片由作者提供。带有 OSM 底图的城市查看器。
推荐
如果你在 JavaScript 方面经验丰富,你可以继续给城市浏览器添加功能。例如,你可以添加搜索框,这样你就可以输入城市。这取决于你想赋予它的效用。本练习中解释的主要用于可视化和信息。用户可以快速查看全球城市群的人口。**
如果您对创建城市查看器感兴趣,并且需要支持来创建您自己的基于 web 地图的项目。在我的个人资料上 Ping 我LinkedIn。****
参考文献
[1]联合国经济和社会事务部人口司(2018 年)。2018 年世界城市—数据手册(ST/ESA/ SER。一/417)。
[2]联合国经济和社会事务部人口司(2018 年)。 《世界城市化前景:2018 年修订本 ,网络版。
通过探索性文本分析发现您的完美眼镜
品酒大师欢欣鼓舞!
我第一个承认——我对酒一无所知。我只知道偶尔会抿一口喜欢的,大部分时候会抿一口实在不喜欢的。因此,尽管我个人无法区分雪松味的葡萄酒和橡木味的葡萄酒(或者说实话,雪松味和泡泡糖味的葡萄酒),但我知道有些东西让我喜欢某些葡萄酒,只是我不知道是什么。
输入文本分析和超过 130,000 条不同葡萄酒评论的数据集,从 9 美元的 Trader Joe's Chardonnay 到 15,000 美元的法国香槟的 Dom Perignon 玫瑰金(谢谢,Kaggle !).
这个数据集对于文本分析/自然语言处理来说是惊人的,因为它包括了数千个段落来描述选择中的每一种葡萄酒,在混合中使用了神话般的词语,如 " "酸涩"、"乡村"、"辛辣"、"白垩"、"多汁",甚至是"奶酪"。为了更好地理解这些描述有多宏大,下面是第 20 条观察的全文:
成熟的黑浆果香味混合着丰富的黑胡椒、烤香草和烟草的味道。口感自然是橡木味,但酸红醋栗的味道闪闪发光,给人一点轻浮的感觉。”
或者观察#25:
这款来自高海拔地区的葡萄酒被命名为皮诺,散发着橡木和泥土混合在一起的浓郁芳香。产量很小,它提供了浓郁浓郁的覆盆子和黑莓的味道,充满了烟熏的香料和柔滑的口感。”
光是看这些描述就让我想成为一个品酒师!
本教程的数据
我已经将 Kaggle 数据上传到这个 Github repo 中,所以你可以下载它并使用wine.csv
跟踪它。您还需要导入一些库(典型的库,如 NumPy、Pandas 和用于文本分析的 NLTK 库),然后我们就可以开始比赛了。
如果您想查看文本框,可以使用data['description'][i]
功能:
这就是我们要做的!我们♥️预先清理数据。
1 |标记化
标记化往往是文本分析的第一步。它描述了将复杂的句子分解成更小单元的过程,这通常意味着将一个句子分解成一系列单词。使用 NLTK,标记文本框非常简单:
输出如下所示:
如您所见,word_tokenize
函数会自动假设您希望每个单词组成一个新的单元,但是您也可以根据需要按单词的成对或三元组进行标记。
如果你正在使用 R,我相信stringr
包将是你完成标记化的最好朋友(这里有一个我找到的关于 R 使用 stringr 的很好的教程)。
2 |删除停用字词
停用词是语言中最常见的词。单词“The”、“a”、“at”、“for”、“above”、“on”、“is”、“all”是停用词的一些例子。这些单词在文本块中通常是最没有意义的,因此在分析之前通常被移除。
我们可以使用 NLTK 库删除这些停用词,如下所示:
结果是一个新列表,其中的单词“The”、“and”和“from”是从文本中的每个列表中新删除的:
奇怪的是,删除停用词并没有删除像“.”这样无意义的标点符号还有“,”。为了消除这些问题,我们将继续讨论…
3 |词性标注
词性标注用于根据定义和上下文为给定文本中的每个单词分配词性,然后我们可以对其进行过滤。原来在小学花在学习名词、动词和形容词之间的区别上的所有时间可能最终会有回报!
Clippy:从 2005 年开始给我带来噩梦(GIF 来自 GIPHY
有许多工具可用于词性标记,一些最广泛使用的标记在 NLTK、Spacy、TextBlob、Standford 和 CoreNLP 库中。幸运的是,我们可以再次使用 NLTK 库:
在我的循环中使用“NN”、“JJ”、“JJS”和“JJR”告诉计算机保留所有名词和形容词。要查看所有可能缩写的完整列表,你可以参考 guru99 这里的列表。
只过滤名词和形容词,这是我们的新结果:
现在你知道了!(边注:如果有人知道什么是“纳瓦拉”,请在评论中启发我们。)
4 |词干/词汇化(可选)
词干化是指将单词规格化为其基本形式或词根形式。例如,单词诱饵,诱饵和诱饵都变成了词根是‘诱饵’。类似地,词条化是用一点额外的努力将一个单词转换成它的基本形式的过程。
那有什么区别呢?词汇化考虑上下文,将单词转换成它有意义的基本形式,而词干化只是删除最后几个字符,不那么好奇。词汇化可以带来更好的结果,但前提是人类已经首先进入并为计算机提供了一个有意义的词汇来匹配单词和词根。词干提取需要较少的人工干预,计算成本也较低,但是更容易出现错误和拼写错误。
2005 年的电脑在词汇化方面不会很出色(GIF 也来自 GIPHY
在我的计算机转换回拨号之前,让我们继续看代码…
下面是上述代码块的最终输出:
如您所见,我将这一部分指定为最可选的,因为词干化和词汇化不一定会改善您的列表。在使用 Kaggle 的真实世界葡萄酒数据时,stemming 只是把“blackberry”改成了“blackberry”,把“mouth”改成了“mout”,看起来一点用都没有。需要人工输入的词汇化没有提取任何已知的基本词汇,因此列表保持不变。
要点:词干化和词汇化可能有用——但是如果你这样做的话,请确保你对数据做了双重处理!
结论
在本教程中,我们复习了标记化、停用词、词性标注、词汇化和词干化的基础知识。我一次针对一个列表分析每个函数,但是人们可以使用来自我的 Github 的这些脚本一次分析所有的数据。你可以通过下载完整的 zip 文件并运行filters.py
来试用我的葡萄酒推荐器,在那里我将这些浓缩的列表与字典中常见的描述符甜、咸、土、果味、花香和苦味的葡萄酒进行了比较。谁知道呢,也许你下一个最喜欢的酒就在你当地的熟食店卖呢!
特别感谢我的 CS 5010 项目团队,其中我的贡献是你在上面看到的文本分析/NLP 部分,以及 Dhilip Subramanian,他有一个自己的很棒的文本挖掘教程。再次感谢阅读,干杯!🥂
来源
- https://www.kaggle.com/zynicide/wine-reviews
- https://medium . com/forward-artificial-intelligence/text-mining-in-python-steps-and-examples-78 B3 F8 FD 913 b
- https://www.guru99.com/pos-tagging-chunking-nltk.html
- https://www.mjdenny.com/Text_Processing_In_R.html
使用网络分析发现实体连接洞察(第 1 部分)
PCH . vector-www.freepik.com 创建的人向量
了解网络分析以及如何最好地利用它
分析就是使用统计方法和计算工具来发现、解释和交流我们拥有的数据中的模式。大多数时候,您可以通过查看单个实体级别粒度的数据(例如,客户、采购、产品等)来获得洞察力。在其他一些时候,当你以更高的视角观察这些实体之间的相互联系(即客户 A 与哪些其他客户有联系)时,这可能会更有用。发现这种洞察力的一种常见技术是使用网络或图形分析。
网络分析简介
什么是网络分析?
引用 A.M. Chiesi 在《社会&行为科学国际百科全书中的话,“网络分析(NA)是一套综合技术,用于描述参与者之间的关系并分析这些关系循环出现的社会结构。基本假设是,通过分析实体之间的关系,可以更好地解释社会现象。”
一些复杂的现象,如信息传播或社会互动,要求我们将问题作为一个系统来看待。网络允许我们通过将它们表示为网络来简化问题,然后使用某些工具来关注影响网络的某些关键实体。它可以用于多个不同领域的问题,从生物网络、交通网络、社会网络等等。
理论概念
在深入研究网络的使用和网络分析技术之前,我们需要首先理解网络的概念。
在数学中,网络通常被称为图。它是由 节点 (或顶点或点)通过 边 (也称链接)连接而成的数学结构。每个节点和边可以拥有描述其特征的特定属性。在一个社会网络中,节点可以是一个人,边可以是他们之间的关系(即友谊)。每个 person 节点可以有一些属性,比如人名、年龄或位置;而友谊边可以具有像第一次见面日期这样的属性。
示例图/网络(图片由作者提供)
为了进一步模拟真实世界的网络,几种图形可以丰富建模。
- 无向图。在上面的例子中,图表没有方向,因为鲍比和杰克之间的友谊关系是相互的。这种图叫做无向图。
- 有向图。我们也可以有一个图,其中的边有方向。这种图称为有向图或有向图。它可以用来建模节点之间的非交互,比如 Twitter 中的用户交互(A 跟随 B,但是 B 可能不跟随 A)。
- 多边有向图。在该图中,两个节点由具有特定方向的多条边连接。这可以用于对一个关系中的多个事务进行建模,比如公共汽车在 A 站和 B 站之间的行程。
- 加权图。图的边可以具有权重,可用于表示连接具有一些数字属性的结构,如从 A 点到 b 点的距离/成本/时间。
- 自循环。这些是连接到自身的节点。这可以用于社交媒体用户喜欢他/她的帖子的情况。
图表类型(图片由作者提供)
在现实世界的应用中,当图中的节点用于解释现实世界的主题和概念时,该图通常被称为网络。因此,在商业/产品分析领域,使用图表的分析通常被称为网络分析。
现实世界应用中的网络分析
为什么重要?
网络分析最适用于对实体之间的交互进行建模,并获得关注交互而非每个特定实体的见解。以下是网络分析的主要应用。
- 中心性分析。中心性用于标识网络或图形中的(最多)中心节点,表示该节点的重要性级别。为此,我们测量每个节点的中心度(节点的总实际邻居与节点的总可能邻居的比率)并对它们进行排序。这对于需要找到关键实体的用例很有帮助,比如找出社交网络中有影响力的人,关键的交通枢纽等。
- 连通性分析。如果说在中心性分析中,我们关注的是找到关键节点,那么在连通性分析中,我们关注的是节点连接的具体特征,比如流入和流出边缘之间的比较。例如,在欺诈检测案例中,我们可以对用户帐户和交易进行建模,找到与已知欺诈用户相关的用户,并限制他们的交易以防止欺诈。
- 社区分析。使用图形,我们可以找到几个中心节点,并发现它们之间的组/社区。例如,在 Instagram 这样的社交网络中,除了他们的朋友或亲戚,人们还会关注那些引发他们兴趣的原因,如摄影、美食或时尚。映射这些联系可以提取它们之间的社区,这对于创建推荐很有用。
- 路径分析。路径分析主要用于加权图,可用于计算出在图的节点之间移动的所需路径。具体到获取最短路径,有一个专门的 Djikstra 的算法来解决这个问题。路径分析可用于交通用例,如计算城市间的最短旅行时间,或用于供应链用例,如确定配送中心的位置。
它会有什么用呢?
网络分析中的上述分析技术可以应用于许多现实生活中的应用。下面是一些例子。
- 社交媒体营销
随着社交媒体的使用越来越多,营销人员正在利用这个平台通过吸引社交媒体影响者来推广他们的品牌和产品。网络分析可以帮助有效地选择影响者。在网络分析中使用连接性和社区分析,我们可以找出影响者及其追随者,并随后找出其追随者与的联系,确定具有影响力追随者的关键影响者,以创造口碑连锁效应并获得对产品的更高印象。
2。欺诈检测
欺诈行为或产品滥用通常可以首先通过查看当前交易模式的异常值或与已知欺诈者的接触来识别。网络分析可以在这方面有所帮助,尤其是通过可视化实体的亲和力。例如,在电子商务产品上,我们可以通过可视化他们与已知欺诈用户的联系来识别欺诈用户(例如,使用相同的送货地址,使用相同的信用卡或银行账户)。利用这一点的另一种方法是查看商家与客户之间的互动,看他们之间是否有任何排他性(即一个商家仅由一个客户交易,而该客户仅在该商家交易多次),他们利用电子商务平台的许多促销手段。
3。建筑推荐系统
通过网络中的社区分析,我们可以根据网络属性(即话题兴趣)识别用户群。假设紧密联系的用户具有相似的兴趣,用户喜欢的一个项目最有可能被紧密联系的用户喜欢,因此该项目可以被推荐给他们。这种方法被称为推荐引擎上的协同过滤。
结论
这概括了网络分析以及如何将其用于现实世界的应用。数学/计算概念可能已经有几个世纪的历史了,但它仍然与现代实施相关,从欺诈检测到营销改进和供应链建立。在一天结束的时候,这都是关于理解你的数据和试验你如何将你的实体映射成网络/图形格式。只有这种理解才能定义您使用网络分析进行分析的过程。
使用网络分析发现实体连接洞察(第 2 部分)
将关系数据转换为图形,使用 NetworkX 构建可视化
我在之前的文章中已经分享了网络分析的概念和可用性。这是一项用于分析实体之间关系的伟大技术,并且可以用于对欺诈检测、营销改进的社会影响和供应链设置等一些复杂问题进行建模。
在这篇文章中,我将关注如何将这些概念应用到实际的数据集,并从中发现洞见。我将分享将你的关系数据转换成图表格式的步骤,将它可视化,并最终探索它。具体到图形可视化,我将使用 NetworkX ,这是一个 Python 包,专门用于创建、操作和研究复杂网络的结构、动态和功能。
这个分析中使用的代码可以在这个 GitHub 库中找到。
1。预分析
1.1 问题陈述
与任何类型的数据分析一样,您首先要定义分析目标,您想要回答什么问题,或者您想要解决什么问题。在这篇分析中,我将探索印尼菜肴中各种配料的联系。
在印度尼西亚众多的 T21 美食中,我很想知道:
印尼菜的关键配料/成分是什么?
哪些配料组合在一起效果很好(在许多菜肴中一起使用)?
1.2 数据准备
一旦问题清楚了,我就继续进行数据收集过程。为了回答上面的问题,我需要一个印度尼西亚菜肴及其所有配料的数据集。原始数据集是从 维基数据 查询服务 中检索的,针对原产地为印尼的食品的所有实例。
从维基数据中检索到的表格(图片来自作者)
我做了一些数据清理来(1)转换具有科学名称的成分,以及(2)规范化类似的项目名称,如【矿泉水、饮用水和水】,因为它们指的是同一项目。我还做了一些数据丰富来分类配料项目,如水果、蔬菜、调味料、香料、碳水化合物、坚果、奶制品和鸡蛋、动物和植物蛋白。
2.转换数据
现在来看这篇文章的重点,即将我们上面收集的关系数据转换成图形/网络格式。
2.1 确定要建模的连接
在进入转换代码之前,首先我们需要确定图中要建模的连接。
网络分析建模需要识别的关键点:(1)实体,( 2)实体间的连接,( 3)图类型,( 4)连接的属性(如果有的话)。
要建模的连接的图示(图片来自作者)
在本例中,实体是一个项目配料,要建模的连接是印尼菜肴中的事件。这些实体具有 item 类型的属性,连接具有绑定它们的 dish 名称的属性。这里的图类型是一个 无向图 ,因为 A 项和 B 项之间的关系是相互的,没有发生联系的方向。****
2.2 创建连接列表
现在,建模时间!
首先,我列出了所有的连接(菜名)和要连接的实体(配料项目),每个都在一条记录下。配料项目在这里由空格字符分隔。
连接及其实体(图片来自作者)
然后,我收集了列表中所有项目之间的共现,并以矩阵形式表示出来。它的工作方式是,对于每个food_name
,我遍历相关的ingredient_list
,并以矩阵格式列出它们的共现。****
所以对于“阿顿-阿顿科罗”,我创建了{生姜和香料}、{生姜和椰奶}、{生姜和棕榈糖}、{香料和椰奶}、{香料和棕榈糖}、{椰奶和棕榈糖}之间的联系。对数据框中的所有食物/菜肴名称都这样做。对于现有项目之间的任何附加连接,该值将递增到矩阵中。
实体连接矩阵(图片来自作者)
之后我把矩阵总结成了更简单的表格格式。在该表中,每一行代表我们拥有的每一条边,而代表相关的权重,在本例中,权重是配料对同时出现的次数。该边填充了一个连接了的节点元组,在本例中是项目配料。****
最终边缘列表和权重(图片来自作者)
3.可视化和探索网络
网络分析的优势在于图形的可视化,可用于轻松发现项目之间的关系。在这个分析中,我使用了 NetworkX 包来创建、可视化和分析网络。
3.1 创建图表
与任何数据可视化一样,您需要仔细选择要可视化的数据点。可视化中有太多的数据会让你难以专注于关键的洞察。对于这个分析,我试图找到印度尼西亚菜肴的关键成分,因此我只关注上面的 100 个边缘。
import networkx as nx# Create dictionary of edges and their weights
d = df_compiled.head(100).set_index(‘bigram’).T.to_dict(‘records’)# Create network plot
G = nx.Graph()# Create connections between nodes
for k, v in d[0].items():
G.add_edge(k[0], k[1], weight=(v * 10))
下面是用来给网络添加边的字典d
的截图。
用于在 NetworkX 中向图中添加边的字典(图片来自作者)
3.2 形象化图表
最后,可视化图表!
NetworkX 有多个绘图功能,可以根据分析需要使用。在这个分析中,我使用的是 NetworkX 的 弹簧布局 。spring 布局使用 Fruchterman-Reingold 力定向算法在图中定位节点,通俗地说就是 排列节点,使边具有相似的长度和最小交叉边 ,使其在视觉上更令人愉悦。
其他一些布局类型包括 随机布局 (节点在单位正方形内随机均匀定位) 圆形布局 (节点在一个圆上) 二分布局 (节点在两条直线上) 光谱布局 (节点使用图的拉普拉斯特征向量定位)。其他绘图功能可以在这里探索。
NetworkX 中的可视化布局示例(来源:NetworkX 文档https://networkx.org/grave/latest/gallery/plot_layout.html)
底层的 NetworkX 可视化使用 Matplotlib,因此我们可以使用 Matplotlib 函数来定义图表的图形大小并显示可视化。下面是 NetworkX 简单可视化的示例代码。
fig, ax = plt.subplots(figsize=(16, 12))pos = nx.spring_layout(G, k=2)# Plot networks
nx.draw_networkx(G, pos,
font_size=8,
width=3,
edge_color='grey',
node_color='#00b4d9',
with_labels = True,
ax=ax)plt.show()
现在我们得到了我们的第一次可视化!
网络可视化——印尼菜肴的配料(图片来自作者)
3.3 修改可视化效果
我们可以保留上面的可视化,但是我们为什么不把它做得更好,以便于分析呢?
可以在这里进行一些定制:
- 改变节点尺寸。我们可以根据节点度(它所连接的边的数量)来修改节点的大小。
- 改变节点的颜色。我们有一个配料类别(即碳水化合物、水果、蔬菜、香料等),这是之前确定的。我们可以根据这个类别给节点着色,这样我们就可以很容易地检查来自同一个类别的项目是否紧密相连。
import matplotlib# Create network plot
G_mod = nx.Graph()# Create connections between nodes
for k, v in d[0].items():
G_mod.add_edge(k[0], k[1], weight=(v * 10))# Get the dataframe of ingredient name and it's category
carac = pd.DataFrame()
carac = total_ingredient[['ingredient_name','ingredient_category']]# Reindex the dataframe to align with graph's nodes
carac = carac.set_index('ingredient_name')
carac = carac.reindex(G_mod.nodes())carac['ingredient_category'] = pd.Categorical(carac['ingredient_category'])
carac['ingredient_category'].cat.codes# Specify colors, number of colors listed should align with the number of categories
cmap = matplotlib.colors.ListedColormap(['C0', 'darkorange', 'lightgreen', 'lightyellow', 'darkgreen', 'darkblue', 'purple', 'red', 'pink', 'brown'])# Get node degree in a dict for node size parameter
d = dict(G.degree)# Draw graph
fig, ax = plt.subplots(figsize=(16, 12))pos = nx.spring_layout(G, k=2)nx.draw(G, pos,
font_size=10,
width=1,
edge_color='grey',
node_color=carac['ingredient_category'].cat.codes,
cmap=cmap,
with_labels = True,
nodelist=d.keys(),
node_size=[v * 12 for v in d.values()])
plt.show()
瞧啊。我们的可视化在这里!
网络可视化——印尼菜肴的配料(图片来自作者)
3.4 分析图表
从图中我们可以看到椰奶位于图 的 中心,具有相当大的节点大小和相当高的边连通数。但是是吗?
NetworkX 也有一些分析功能来帮助我们获得每个节点的 中心度 值,我们可以用它来回答这个问题。
# Compute the degree centralities of G: deg_cent
deg_cent = nx.degree_centrality(G)# Compute the maximum degree centrality: max_dc
max_dc = max(deg_cent.values())# Find the item(s) that have highest co-occurrence: prolific_collaborators
prolific_collaborators = [n for n, dc in deg_cent.items() if dc == max_dc]
事实证明,是的,椰奶是同现率最高的项目!仔细想想,它在印尼菜肴中被广泛使用。从像 Soto ayam 这样的浓汤菜到像 Nasi uduk 这样的正餐以及像 B ubur ketan hitam 这样的甜点——它们的配料中都有椰奶!
我们还可以使用 NetworkX 来查找网络中的组。在这里,我们用 嫡系 函数来表示它。看起来很简单,我们可以使用下面的命令列出派系。
我们还可以识别一些感兴趣的项目,并具体放大它,找出该项目的联系。
# Define get_nodes_and_nbrs()
def get_nodes_and_nbrs(G, nodes_of_interest):
"""
Returns a subgraph of the graph `G` with only the `nodes_of_interest` and their neighbors.
"""
nodes_to_draw = []# Iterate over the nodes of interest
for n in nodes_of_interest:# Append the nodes of interest to nodes_to_draw
nodes_to_draw.append(n)# Iterate over all the neighbors of node n
for nbr in G.neighbors(n):# Append the neighbors of n to nodes_to_draw
nodes_to_draw.append(nbr)return G.subgraph(nodes_to_draw)# Extract the subgraph with the nodes of interest: T_draw
T_draw = get_nodes_and_nbrs(G, ['coconut_milk'])# Draw the subgraph to the screen
nx.draw(T_draw, with_labels=True)
plt.show()
子图可视化
查看上面的可视化和探索 NetworkX 函数,我们可以很容易地找到所定义的问题陈述的答案。
印度尼西亚菜肴的主要配料包括椰奶,盐和糖等调料,还有蒜、葱、姜和辣椒等香料。使用的顶级蛋白质包括鸡蛋、豆腐、鸡肉。
我们可以通过研究通常提到的派系来找到能够很好地协同工作的成分。在这种情况下,我们找到盐和糖(显然!);c 椰子奶,和米粉(印尼 kuehs 常用)。
很有帮助,不是吗?
结论
这就总结了网络分析在探索印度尼西亚菜肴中的配料方面的应用。正如我在上一篇文章中提到的,网络分析有许多方法可以用于其他用例,从欺诈检测到营销改进和供应链设置。
如果你渴望了解更多关于网络分析的知识,你可以探索这些资源——这也是我学习的地方:)
快乐学习探索!
使用神经网络发现系外行星
利用美国航天局开普勒空间天文台的数据和 PyTorch 的神经网络
完成应用计算硕士项目中神经网络学科学分的项目。因为我必须展示一个实验和一篇文章,所以知识库将会有所有的实验代码,这个中间故事将会展示大部分关于我的工作的有价值的内容。
“艺术家概念。美国宇航局的开普勒太空望远镜发现了 100 多颗行星,其中包括四颗地球大小的行星,围绕着一颗矮星运行。正如我们所知,其中两个行星太热,无法支持生命,但其中两个位于恒星的“宜居”区,表面可能存在液态水。 演职员表:NASA/JPL
开普勒空间天文台
2009 年,美国宇航局发射了开普勒太空天文台,旨在回答这个问题:
该天文台自 2014 年以来正在进行第二次任务,并已标记了超过 10,000 颗可能的系外行星。
开普勒在 2016 年扫描了 1284 颗新的系外行星。与 2017 年一样,总共确认了超过 3000 颗系外行星(使用所有探测方法,包括地基方法)。天文台仍在工作,并继续寻找其他系外行星。
我们这个项目的目标是使用开普勒太空天文台在 Kaggle 收集的数据,建立一个神经网络模型解决方案,考虑到开普勒收集的特征,这是可行的,可以识别系外行星。我不会深入讨论数据清理或我正在使用的特征,那是为了一个未来的故事项目。
什么是系外行星?
我们的恒星是太阳,看着黑暗的天空,那是我们星系中的其他多颗恒星。这些恒星中的每一颗都可以或不可以有我们的行星绕着它们转,所以它们有自己的太阳系。那些行星是系外行星,我们也可以说系外行星是太阳系以外的行星。
"当一颗行星从它的恒星前面穿过时,被观测者称为凌日。" 演职员表:NASA 艾姆斯
美国宇航局这次任务的另一个重要目标是寻找“其他地球”。开普勒任务概述称,其目标之一是“确定在各种恒星的可居住区或附近的类地行星和更大行星的百分比”。那些是体积是地球一半或两倍的行星。下面你可以找到适合居住的行星的例子。
“在过去的九年中,美国宇航局开普勒任务对小型宜居行星的搜索。第一颗比地球小的行星是开普勒-20e,它于 2011 年 12 月被发现,每六天绕着一颗比我们的太阳稍微冷一点、小一点的类日恒星运行一周。但是它是灼热的,无法维持大气或液态水海洋。开普勒-22b 在同一个月宣布,作为第一颗位于类太阳恒星可居住区的行星,但它的大小是地球的两倍多,因此不太可能有固体表面。开普勒-186f 于 2014 年 4 月被发现,是第一颗在一颗小而冷的 M 矮星的可居住区发现的地球大小的行星,这颗矮星的大小和质量大约是我们太阳的一半。开普勒-452b 是与太阳非常相似的恒星的可居住区中的第一颗近地大小的行星。” 鸣谢:NASA 艾姆斯/w·斯滕泽尔
实验…
使用的机器是 MacBook Pro 2017,采用 Mac OS Mojave 操作系统,500Gb 固态硬盘,RAM 16Gb,处理器为 2.9 GHz 四核英特尔酷睿 i7。使用的编程语言是 Python 3.6,而 PyTorch 库用于构建神经网络。
数据库ˌ资料库
数据库是由 Kaggle 平台拍摄的。而数据清理是基于 Ismael Araujo 的工作。7803 个样本有 38 个特征,其中一个是外行星候选者那一个将成为标签。最后,我有 37 个不同的特征用作输入和二进制输出。
演职员表:作者图片。
演职员表:作者图片。
数据分为三部分,其中 70%用于培训,15%用于验证,15%用于测试。使用最小-最大缩放对特征进行标准化,验证和训练转换为使用 PyTorch 的数据加载器。参见下面的代码:
神经网络
因为我相信媒体社区在解释机器学习主题方面非常丰富,所以我不会深入定义这个实验中使用的算法的理论部分。虽然这个aye bilge gündüzpost如果你没有理论背景的话有非常好的数学基础。
我使用了三个不同的神经网络:一个更简单的感知器和两个多层感知器,一个有三个隐藏层,另一个有两个。它们的激活功能也各不相同。最后,构建了五个神经网络,下表是为了更好地理解其中的每一个:
演职员表:作者图片。
所使用的所有神经网络在其输出层上具有 Sigmoid 函数,0.5 的阈值用于最终分类。下图显示了三种不同架构的代表:
感知器。演职员表:作者图片。
图 3:具有 3 个隐藏层拓扑的 MLP。演职员表:作者图片。
图 2:具有两个隐藏层拓扑的 MLP。演职员表:作者图片。
下面你还可以看到一个多层感知器的代码,有两个隐藏层,使用 PyTorch 构建。你可以注意到它的最后一层有 Sigmoid 函数。
培养
使用 Adam 作为优化器,使用 0.01 作为学习率,使用均方误差作为损失函数进行训练。进行了两次不同的训练,一次 100 个周期,另一次 50 个周期。培训的所有其他配置保持不变。这样总共做了 10 次实验。
两个实验的训练损失图:
图 5:实验 b 过程中的训练损失.鸣谢:图片由作者提供。
图 4:实验 a 过程中的训练损失.鸣谢:图片由作者提供。
你可以看到,使用 50 个历元的训练是使用 100 个历元的训练的简短版本,记住种子在所有训练中保持不变是很重要的。尝试 50 个时代的原因是为了了解是否有可能用更少的努力获得相同的结果。
结果
演职员表:作者图片。
上面的表格显示了使用所有 10 种不同网络配置的结果。这里我们将 100 个历元的实验称为实验 A ,50 个历元的实验称为实验 B 。
最后的想法
正如我们可以看到的,实验的结果都具有非常接近的性能值,作为结论,最不复杂的神经网络可以给我们带来与具有更多层的神经网络非常相似的结果。是什么让我们相信一个感知器会对我们的问题执行一个好的分析算法。
所有的数据和代码都可以在github库中找到。
原载于https://github.com/blendaguedes/find-exoplanets/。
发现矩阵行列式
基本原则
解开特征分解的关键元素
由 Ricardo Gomez Angel 在 Unsplash 拍摄的照片
线性代数中有一些概念,第一次出现时很容易理解。可能是因为它们的应用和用例相对来说比较明显,概念也比较明确。
然而,矩阵行列式的概念对我来说却完全相反——与其说是启发,不如说是困惑。我的困惑主要源于这样一个事实,我严重缺乏直觉——我无法想象行列式的意义和目的。
在接下来的几节中,我们将建立一些直觉,学习一些计算行列式的程序,并初步了解它的应用。
快速的事实和直觉
行列式描述了将矩阵映射到标量的函数。它是由所有特征值的乘积定义的,允许一个稍微不那么抽象的,更具几何意义的解释。
根据矩阵的维数,行列式也可以分别解释为面积或体积。粗略地说,它告诉我们乘以给定的矩阵会拉伸或收缩多少空间。
几何解释的一个例子[图片由作者提供]
假设我们有一个行列式为零的矩阵。乘以矩阵,会缩小空间,把所有东西都挤到一行上。因此,面积也将等于零。
现在,我们获得了一些基本的直觉,我们可以列出一些关于行列式的事实来进一步理解:
- 行列式只为方阵(M×M)定义。
- 一个矩阵只有一个行列式,因为它是一个标量,包含矩阵的信息。
- 奇异矩阵的行列式等于零。或者换句话说——具有线性相关性的矩阵,秩
r < M
,具有零行列式。 - 行列式通常被表示为下列之一:
注:要记住一件事——行列式在理论上很重要,但在实践中很难计算,因为计算“大”矩阵时会出现数值不稳定。
矩阵行列式怎么算?
在上一节中,我们学习了一些关于行列式的基本知识以及如何解释行列式。但是我们怎么计算呢?
计算行列式的过程相当繁琐。幸运的是,有一些快捷方式,只适用于小矩阵(2x2,3x3)。我们先谈捷径,再谈一般程序。
注意:幸运的是,我们不必手动计算行列式,因为内置的 NumPy 函数 numpy.linalg.det(a) 会为我们完成这项工作。
2x2 快捷方式
2×2 矩阵的行列式相对容易计算。我们只需将主对角线上的每个元素相乘,然后减去非对角线元素的乘积。
让我们来看一些数字示例:
在第一个例子中,我们使用快捷方式来计算一个 2 乘 2 单位矩阵的行列式,等于 1。我们也可以把这个解想象成一个平面的面积,其中一边由单位向量 v=[1,0]定义,另一边由单位向量 w=[0,1]定义。
2x2 单位矩阵的行列式[图片由作者提供]
奇异矩阵的行列式为零,这正是我们在第二个例子中看到的。由于两列彼此线性相关,我们有一个秩亏矩阵,导致行列式为零。从几何学的角度考虑这个解决方案,我们必须想象两个向量形成一条面积为零的直线。
奇异 2x2 矩阵的行列式[图片由作者提供]
3x3 快捷方式
3 乘 3 矩阵的行列式的计算已经更加复杂,但是相对类似于 2 乘 2 的捷径。
我们对从左上到右下的所有对角线元素的乘积求和,并减去从右上到左下的所有非对角线元素的乘积之和。我们可以用两种不同的方式来想象这个过程。
- 一种方法是通过级联来扩充矩阵:
增强矩阵可视化[图片由作者提供]
2.另一种方法是想象沿着对角线“环绕”:
环绕矩阵可视化[图片由作者提供]
一般程序
现在,事情变得非常复杂。因此,以下示例将仅基于 4×4 矩阵。
通常,该过程是迭代第一行的每个元素,通过排除当前第一行元素的列来创建子矩阵(3x3),计算子矩阵的行列式并乘以当前第一行元素。这将产生四个数字,我们将以交替模式[+,-,+,-]对它们进行加减运算。
我们应该把解开复杂描述的过程形象化:
我们可以看到程序要复杂得多。对于相对较小的 4x 4 矩阵,我们必须计算 4 个子矩阵的 4 个行列式,其中我们还需要计算子矩阵的行列式,等等。因此,该算法可以递归应用,因此计算量很大。
然而,这个通用程序可以扩展到任何规模的矩阵——幸运的是,我们不必手工计算。
行列式的应用
我们现在知道行列式是什么,如何解释它,如何计算它,但有一个问题仍然没有答案——它是用来做什么的?
例如,它的一些应用提供了一种寻找给定矩阵的逆矩阵或特征值的方法。后者尤其重要,因为特征值和特征分解在主成分分析中起着中心作用。
假设我们有一个 2 乘 2 的矩阵,并且我们已经知道了行列式。将矩阵元素放在等式的一边,将行列式放在等式的另一边,可以让我们求解矩阵的特定元素。
让我们考虑一个数字例子,让事情变得更加明显。
现在,如果我们基于这一想法,从主对角线上的实数中减去多个未知数,并推广该方程,我们将获得矩阵的特征多项式。
特征多项式是有用的,因为它不仅允许我们用多项式来表示矩阵,而且允许我们计算矩阵的特征值。如果特征多项式设置为零,λ——多项式的根——描述矩阵的特征值。
结论
在本文中,我们学习了矩阵行列式的概念。如何解读和计算,以及它的一些应用。
两个最重要的应用是矩阵求逆的计算,也许更有趣的是发现特征值。
正如我们前面简要提到的,矩阵行列式在理论上是一个很好的概念,但在实践中很难计算,特别是对于“大”矩阵,由于数值的不稳定性。为了避免这样的问题,例如当计算特征值时,存在一整族的迭代算法(例如幂法)。
尽管有实际的问题,行列式仍然是一个需要了解和理解的重要而基本的概念。
喜欢这篇文章吗?成为 中等会员 继续无限学习。如果你使用下面的链接,我会收到你的一部分会员费,不需要你额外付费。
https://medium.com/@marvinlanhenke/membership
参考资料/更多资料:
- 深度学习(Ian J. Goodfellow,Yoshua Bengio 和 Aaron 库维尔),第二章,麻省理工学院出版社,2016 年。
- 迈克·科恩博士。线性代数:理论,直觉,代码。
- 3 蓝色 1 棕色—行列式
发现 22 R 探索性分析包的宝藏
找出在 R 中进行 EDA 的最快和最有用的方法
@fergregory/AdobeStock
ython 如今在数据科学领域风靡一时;然而,R 多年来一直在悄悄地发展,并收集了目前包含在近 17K 包中的惊人功能(在撰写本文时)。所以可以毫不夸张地说,它已经在数据科学的许多领域积累了大量的软件包,其中之一就是探索性数据分析(ed a)。使用 EDA 这个词,我们封装了以下功能:
- 一般数据框架描述:维度、数据类型(连续/离散)、缺失情况。
- 单变量统计:均值、方差和其他变量统计,用表格或图表表示,如直方图和箱线图。
- 双变量统计:变量关系,用表格或图表形式的相关性、散点图等表示。
- 离群值,即与其他数据点显著不同的数据点。
正如您所预料的,许多 EDA 功能在各种 R 包中被复制,因此筛选共性是很重要的。在这篇文章中,我们将执行数据集的 EDA,并获得 EDA 包,这样对于每个 EDA 步骤,我们都会给出一些最佳选项。
1.EDA 包和我们的数据集
L 让我们先熟悉一下我们将要探索的包的名字。以下是按字母顺序排列的:AEDA、化学计量学、corrplot、DataExplorer、dataMaid、ExPanDaR、extremevalues、funModeling、ggcorrplot、Hmisc、inspectdf、OutlierDetection、outlier、PerformanceAnalytics、ggcorplot、Ggally、mvoutlier、prettyR、psych、RtutorR、skimr、summarytools。我们将使用的辅助包: dplyr,ggplot2,guardian,plotly,gmodels。
我们的数据集是 professor-salaries.csv (可在 Kaggle 上获得)。数据集的尺寸为 397 X6。这六栏分别是教授的职级(可以是助理教授、副教授、正教授)学科(可以是 A 或 B)、获得博士学位的年限(yrs sinf . PhD .)、服务年限(yrs service)、性别(性别)、工资。最初,数据集没有缺失值,但是,因为我们希望在有缺失案例时检查各个包的行为,所以我们在 yrs.since.phd 列中引入了安娜,在 yrs.service 列中引入了安娜。
2。数据帧描述
在此之前,我们将获得关于数据集维度、变量数量/类型、唯一行、重复数据和缺失数据的信息。我们提出一个问题,每个问题之后都有代码和代码输出。在每个代码片段中,第一个命令库(包)显示了我们使用的包。如果没有包含库语句,那么使用的函数来自 base R 包。
问:我的数据集的维度是什么?
基础包中的 dim() 有答案,其中 sal 是我们数据集的名称。
问:我的数据集有多少个变量,它们的类型是什么?
来自基地的 str() 告诉我们。部分输出如下所示。
问:如果有的话,有多少重复的行?
这里我们使用基础包中的 unique() 函数。如下所示,有四个重复的行。
问:为了进一步探究,哪些是重复的行?
包的看门人有这个函数 get_dupes() 。部分输出如下所示。
问:是否有任何数据缺失?
函数introduce()from packagedata explorer给我们答案。如下所示,有两个值缺失。
问:为了进一步探究,哪些变量有缺失值?
来自 base R 的多功能 apply() 函数给了我们答案。我们看到变量yers。因为. phd* 、yers . service各有一个缺失值。*
问:这些丢失的值出现在哪些行中?
没有被 complete.cases() 选中的行就是答案。
3。单变量统计
3.1 单变量列统计
前面提到的 R 包中有很多函数,它们报告单变量统计数据。我发现以下内容最有用:
- 对于基本的单变量统计信息:来自基础包的 summary() 函数。此函数报告以下统计数据:连续变量的最小值、最大值、平均值、中值、第一/第三四分位数、NA 的数量以及分类变量的每个级别的观察数量。
- 详细的单变量统计信息:来自 psych 包的 describe() 函数。它的输出包括最小值、最大值、中值、平均值、修整平均值、范围、偏斜度、峰度。
- 为了便于报告:dataMaid包中的 makeDataReport() 函数创建了一个方便的 HTML 文件,其中包含每个连续变量的以下信息:最小值、最大值、中值、第一/第三四分位数、缺失观测值的数量、唯一值的数量和直方图。对于分类变量,它提供了关于每个级别的观察次数的信息。
包 summarytools、inspectdf、Hmisc、prettyR、funModeling、skim r 的单变量描述函数可以在我的 github 目录下的 R 脚本中找到(路径在文末给出)。
3.2 单变量组统计
有时,我们想知道连续变量(如薪水)的统计数据如何受到分类变量(如性别)不同水平的影响。以下是实现这一点的两种方法:
-
对于一些基本信息:我们可以使用基础包中的 tapply() 函数。下面, tapply() 报道了男女教授的平均工资。
-
更详细的群组信息:来自 psych 包的 describeBy() 信息:
-
对于复杂的分组和报告:我们可以使用非常流行的 dplyr 函数 filter() 、 group_by() 和summary()。下面是一个例子,我们根据级别显示了专业 A 的男女平均工资:
-
因为一张图胜过千言万语:箱线图和条形图,用 ggplot2 函数创建。
下面的图 1 和图 2 提供了有价值的见解:
(a)一些男教师的工资非常高。
(b)女教师的工资被压向低端
(c)正教授比助理教授或副教授多得多
(d)在所有级别上,男性教员都多于女性教员。
图一。男性和女性工资的箱线图。
图二。等级条形图,颜色由教员的性别决定。
4.二元统计
4.1 交互式散点图
查看两个变量之间关系的标准方法是散点图。如果我们把散点图做成交互式的,我们可以让它包含更多的信息。这可以通过包装来实现。要在 plotly.com 的上查看您的图,在 Chart studio 上获得一个帐户,然后在您的 R 会话中存储您的认证凭证(用户名、API 密钥),如下所示:
plotly.com上的互动情节,也可以嵌入到你的在线出版物中,如图 3 和图 4 所示。这是变量年交互式散点图的代码。自. phd* 对薪水,与 v 变量性别用于给地块的点着色。*
图 3。交互式散点图。
4.2 交互式 3D 绘图
包 plotly 也允许创建交互式 3D 情节,我们可以放大,改变视角等。图 4 显示了薪水对年数、自博士和年数、服务的 3D 图,其中颜色由变量性别提供。
图 4。交互式 3D 绘图
4.3 相关性
相关性是查看两个变量之间关系的另一种方式。R 中的几个软件包计算相关性,并以数字或图形方式显示它们。
4.3.1 以数字显示的相关性
stats 包有 cor() 函数,以表格形式显示数值变量的相关性:
为了确定相关性是否具有统计显著性,我们可以使用 cor。test()stats 包的方法。比如工资和年数的相关性。服务*具有统计显著性,因为它产生了 p 值=6.901e-12*
4.3.2 以图形显示的相关性
corrplot 是一个包,它提供了许多有见地的方法来查看数字相关性。你可以选择你的主要形状(圆形、方形、饼形、椭圆形等),相关程度通过改变形状的大小和/或颜色来显示。在下面的图 5 中,我们使用了饼形。相关性越弱,饼形越不完整,颜色也越浅。
图 5。数字变量的相关性
包 ggcorrplot 产生数字和图形相关输出。它的数字输出非常类似于包 stats 产生的输出,它的图形输出类似于 corrplo t 产生的输出,所以我们不会在这里展示它。
包 PerformanceAnalytics 包含函数图表。Correlation(),它在上半部分面板中显示数值变量的相关性,在对角线中显示数值变量的直方图,在下半部分显示变量对的散点图(图 6)。
图 6。图表的输出。相关性()。
现在,我们将看看三个包( DataExplorer、psych 和 GGally ),它们与数值变量相关性一起,也包括分类变量信息。包 DataExplorer 包含函数 plot_correlation() 以彩色单元格显示相关性。有趣的是,它还显示了不同分类变量水平的相关性,如下图所示(图 7)。
图 7。plot_correlation()的输出。
包 psych 和 Ggally 提供函数对。面板()和 ggpairs() 分别显示相关性、直方图和散点图。我最喜欢的是 ggpairs() 的输出,它让您可以鸟瞰数据集的变量及其关系,也包括分类变量(图 8)。
图 8。ggpairs()函数的输出。
4.4 独立性检验和统计显著性差异
虽然不是探索性分析的传统部分,但我个人从对列联表进行独立性测试中获得了许多有用的见解。这些表格至少有两行和两列,显示至少两个分类变量的多元频率计数。统计测试,如卡方测试和 Fisher 测试,可用于确定显示的分类变量是否独立。
**卡方和费歇尔检验产生相似的 p 值 ( 卡方检验 p 值 =0.0140,费歇尔检验 p 值 =0.0114),表明等级和性别不独立(显著性水平为 0.05)。
关于连续变量,我们经常想知道连续变量是否在两组之间变化。例如,我们想知道男性教员的工资在统计上是否与女性教授的工资不同。如下图,一个 t 型测试可以帮助我们回答这个问题:
t 检验得出一个 p 值 =0.002664(显著性水平=0.05),这意味着男女教员的工资在统计上是不同的。更进一步,我们想知道这种差异是否是由于不同级别的男女人数不同造成的。所以为了消除等级的影响,让我们对男女正教授的工资做一个 t 检验(等级* =' 教授')。该测试表明,当变量等级固定时(p 值 p 值 = 0.3098,显著性水平=0.05),两组的工资在统计上没有差异*
5.极端值
极限是远离变量总体分布的点。它们可能是完全无用的,例如由测量误差引起的,或者在异常检测应用中是有用的。在这里,我们将讨论如何检测它们,而不是我们是否或如何删除它们。我们将区分单变量和多变量异常值检测。在单变量的情况下,我们要对抗单个变量的极值。在多变量的情况下,我们感兴趣的是发现多维离群值,例如,这在聚类应用中非常有用。
5.1 单变量异常值检测
在这里,我们将检查三种单一异常值检测方法,它们利用了:(a)箱线图(b)包极值* (c)包异常值检测。*
(a)箱线图:在箱线图中,离群值被确定为位于胡须下方或上方距离大于四分位数间距 1.5 倍的点。R 中的方框图标记了异常值,但是,它们没有显示它们的值。如前所述,我们可以使用 plotly 使它们交互,这样,用户可以点击离群值并查看它们的值:
图 9。交互式箱线图
通过点击图 9 中最大的男性异常值,我们看到它的值是$231,545.00。接下来的两个异常值(实际上重叠)的值分别为$205,500.00 和$204,000.00。
(b)软件包 extremevalues :它提供了一个函数,使用对数据子集的回归来发现异常值( getOutliers() )。在使用函数 outlierPlot(),创建的图 10 中,我们再次看到三个值被识别为异常值。
图 10。离群值的 QQ 图。
(c)包 OutlierDetection :它提供了UnivariateOutlierDetection(),这个函数的默认离群点检测方法是 bootstrapping。这种方法基于一个巧妙的观察,即异常值有时不包括在 bootstrap 估计中[1]。从下面的图 11 中可以看出,它确定了远不止 3 个异常值,但是位置 44、365、250 处的三个最远的异常值对应于直方图中确定的薪金值$231,545、$205,500、$204,000。
图 11。UnivariateOutlierDetection()的输出
5.2 多元异常值检测
这里我们将讨论 OutlierDetection 和 chemometrics 包中的方法。
(a)程序包 OutlierDetection 提供了一个同名函数,该函数给出了两个变量(在我们的示例中为 salary 和yers。service* ,创建一个散点图,用红色表示异常值,如图 12 所示。关于下面代码的一些注意事项:(a)我们需要删除 NAs ,以便从 OutlierDetection() 获得正确的输出。如果你还记得,我们在变量yers . service中引入了一个 NA 。(b)在第四行中,我们颠倒了数据帧的列,以使变量 salary 在图上显示为变量 2,从而获得与上面图 11 中方向相同的图。(c)最后,因为 NA 移除从我们的数据框架中移除了一行,所以图 12 中所示的工资异常值的指数是图 11 中所示的工资异常值的指数的-1(43,249,364)。*
图 12。OutlierDetection()的输出
(b)包化学计量学有一个函数 Moutlier() ,它使用测量两点之间距离的马氏距离来检测异常值。与欧几里德距离相比,马氏距离考虑了点值的相关性,并且距离是相对于质心来测量的。只有当这些点不相关时,马氏距离才等于欧氏距离。
如上所述,位置 43 处的点具有最高的 Mahalanobis 距离,其次是点 282、131、330 等。如果我们回头看图 12,我们可以看到这些数字对应于图上看到的一些最远的异常值,因此在确定异常值时,Mahalanobis 距离方法和 bootstrapping 方法之间总体上是一致的。最后,程序包mv 离群值也使用马氏距离来计算离群值,但是,它使用马氏距离的稳健估计。结果类似于常规的马氏距离,尽管排序不同。
6。自动 EDA
最后,让我们讨论一下自动 EDA,也就是说,用一个命令就能创建一个完整的(描述性的、单变量的、双变量的)EDA 报告。我们将讨论提供这一功能的四个包:
- 数据浏览器
- AEDA (可从https://github.com/tuanle618/AEDA获得)
- 扩展器
- RtutoR
DataExplorer ,是一个流行的 EDA 包,其 EDA 报告(report.html)生成如下:
它很好地总结了缺失数据、单变量分布(直方图、QQ 图、条形图)、相关性分析、双变量分析(箱线图、散点图),由目标变量指导。它还包括主成分分析。
**AEDA(main report . rmd)的 EDA 报告创建为:
通过加载可以创建 HTML 格式的报告。rmd* 报告 Rstudio 并点击针织按钮。它的优点是数字变量的详细报告,包括偏度、峰度和高级分析,包括聚类摘要报告、主成分分析、多维标度报告和因子分析报告。*
ExPanDaR 的 auto-EDA 的优点是它允许你启动一个闪亮的应用程序:
这个闪亮的应用程序允许交互指定多个变量,包括:
- 显示条形图的因子(包括在同一图表上选择附加因子)。
- 要显示直方图的数字变量(包括单元格编号的说明)。请注意,在显示每个直方图后,还会报告极端观察值。
- 要显示散点图的变量对。此外,它还允许指定反映在绘图颜色中的变量。
- 回归分析的目标变量和自变量。也是固定效应的分类变量的规范。
最后,包 RtutorR 提供了一个简洁、基本但方便的 Powerpoint EDA 报告:
其单变量分析包含数值变量的直方图(以及最小值、最大值、百分位信息)以及分类变量的条形图(以及每一水平的出现次数)。其双变量分析包含按分类变量分组的数值变量箱线图和数值变量散点图(以及相关系数)。
你可以在我的 github 目录https://github.com/theomitsa/R-EDA中找到我的 R 脚本和前述包的 pdf 版本的自动化 EDA 报告
auto-EDA 报告的 HTML 版本可在 HTML 链接找到
感谢阅读!
参考
[1] Singh,k .和 M. Xie,Bootlier 绘图-基于 Bootstrap 的异常值检测绘图,第 65 卷,第 3 部分,第 532–559 页,2003 年。
发现神经影像分析(第二部分)
通过先进的可视化技术发现人脑
回波平面成像(图片由作者提供)
材料
这是该系列的第二篇文章,即“腹侧颞叶皮层时空功能磁共振成像的认知计算模型”。如果你想了解整个系列,请点击下面的链接。
我将介绍发现神经影像分析的主题,它们在大脑解码研究中的用例。让我们开始吧。
所有相关资料都放在我的 GitHub 页面上。别忘了去看看。如果你是一个纸质爱好者,你可以阅读这一系列文章的纸质版,也可以在我的回购中找到。
在本文中,我们利用最先进的解释性神经成像技术,如回波平面、感兴趣区域(RoI)、统计图、解剖学和玻璃脑方法,来可视化和预分析 fMRI 样本的视觉结构。
像在 ML 问题中一样,在处理之前理解、分析和可视化数据是至关重要的。
在这里,我们利用“Nilearn”框架,这是建立在“Scikit-Learn”之上的统计计算和神经成像平台,使研究人员能够管理、屏蔽、预处理和解码神经科学数据。在上一篇文章中,我展示了如何安装和导入 Nilearn framework。别担心。我将在本文中重复这个过程。让我们开始吧。
通过神经成像发现功能磁共振成像分析
我基于神经成像技术进行了预分析,以可视化数据集。为了实现这一点,我利用回波平面平均进行四维可视化,感兴趣区域(RoI),统计地图,解剖,玻璃脑可视化工具嵌入在统计学习和神经成像框架中。
从现在开始,我将介绍不同的技术和可视化工具,以及它们在 Python 中简单而有效的实现。系好安全带!
但是,在此之前,如果您错过了上一篇文章(第一部分),我提供如下安装指南。
安装代码
进口
正在获取哈克斯比数据
现在,我们可以开始实际的可视化过程。
fMRI 体积和 EPI 的四维可视化
回波平面成像是一种非常快速的磁共振(MR)成像技术,能够在几分之一秒内获得完整的 MR 图像[18]。在单次回波平面成像中,图像的所有空间编码数据都可以在单次射频激发后获得[18]。在这里,我们从额部、轴部和侧部区域的切面来可视化 Haxby 实验的受试者的 EPI。EPI 可视化提供了对大脑中激活区域的现实洞察,这在时空大脑解码中起着至关重要的作用。为了简单起见,我提供了与文章开头相同的图像。
功能磁共振成像体积(图片由作者提供)
回波平面成像(图片由作者提供)
感兴趣区域分析
分析 fMRI 数据的一种常见方法是执行感兴趣区域(RoI)分析,包括从特定区域提取信号。ROI 分析在理论上最不可知的用途是简单地探索全脑体素分析背后的潜在信号[17]。在提取统计上有意义的区域后,我们可以对多个统计测试而不是大脑中的大量体素执行校正的严重性。此外,大多数 ML 解码器是在对象的 RoI 上执行的,而不是在全脑介质上执行的。
RoI 可视化(作者图片)
统计地图
统计参数映射是指构建和评估空间扩展的统计过程,用于测试关于功能成像数据的假设[5]。一般来说,统计 fMRI 的前一步是创建一个阈值统计图,代表活跃的区域(高于阈值)[17]。因此,它在检查神经科学实验中记录的大脑活动差异方面是有用的。
统计地图计算
统计地图可视化(图片由作者提供)
直接 fMRI 可视化
fMRI 数据的简单和紧凑的可视化在神经成像的背景下是一个非常重要的课题,因为它使研究人员能够观察大脑皮层的活动。因此,我直接绘制了受试者 2 的时间平均 fMRI 数据,以便进一步可视化。
fMRI 数据的可视化(图片由作者提供)
解剖可视化
我将受试者 2 的时间平均 fMRI 数据获得的 fMRI 解剖结构可视化(默认为 3 个切面:正面、轴向和侧面),以在解码前产生洞察力。
解剖可视化(图片由作者提供)
玻璃脑
玻璃大脑是一种最先进的实时大脑可视化技术,创建于 Unity 3D 游戏引擎上,由英伟达的 GPU 计算提供支持[19]。它的输入包括从高分辨率 MRI-DTI 大脑扫描获得的个人大脑结构,包括组织和纤维束结构。使用高密度 EEG(脑电图)将实时大脑活动和网络间的功能交互叠加在大脑结构上[19]。这里,我投影了 5 号受试者的时间平均 fMRI 数据的正面、轴向和侧面。
玻璃脑视觉(图片由作者提供)
如果您想更进一步,以交互式 3D 方式可视化数据,下面是代码。
耶!本文到此为止。我深入讨论了最常见的 fMRI 数据可视化技术。
恭喜你!你完成了第二篇文章,并通过认知计算方法对人脑解码迈出了一步。
在下一篇文章中,我们将执行功能连接和相似性分析,以进一步捕捉人脑的统计特性。
文章链接
- 发表文章
https://cankocagil.medium.com/discovery-neuroimaging-analysis-part-ii-b2cdbdc6e6c3
2.在路上(即将到来…)
- 第五部分的占位符
进一步阅读
我在机器学习和神经科学方面的研究中使用了以下参考文献列表。我强烈建议复制粘贴参考资料,并简要回顾一下。
参考
[1]巴、基罗斯和辛顿。图层归一化,2016。
[2] L. Buitinck,G. Louppe,M. Blondel,F. Pedregosa,A. Mueller,O. Grisel,V. Niculae,P. Prettenhofer,A. Gramfort,J. Grobler,R. Layton,J. VanderPlas,a .乔利,B. Holt,10 和 G. Varoquaux。机器学习软件的 API 设计:scikit-learn 项目的经验。在 ECML PKDD 研讨会:数据挖掘和机器学习的语言,第 108–122 页,2013。
[3]褚,田,王,张,任,魏,夏,沈。双胞胎:重新审视《视觉变形金刚》中空间注意力的设计,2021。
[4] K .克拉默、o .德克、j .凯舍特、s .沙莱夫-施瓦兹和 y .辛格。在线被动攻击算法。2006.
[5] K. J .弗里斯顿。统计参数映射。1994.
[6]格罗斯、罗查-米兰达和本德。猕猴下颞皮质神经元的视觉特性。神经生理学杂志,35(1):96–111,1972。
[7] S. J .汉森、t .松坂和 J. V .哈克斯比。用于物体识别的腹侧颞叶组合编码。
[8]哈克斯比、戈比尼、富里、伊沙伊、斯豪滕和彼得里尼。《视觉物体识别》,2018。
[9]赫克曼、哈伊纳尔、贾巴尔、吕克特和哈默斯。结合标记传播和决策融合的自动解剖脑 mri 分割。神经影像,33(1):115–126,2006。
10d .亨德里克斯和 k .金佩尔。高斯误差线性单位(gelus),2020。
[11]黄少华,邵文伟,王明林,张德庆.人脑活动视觉信息的功能解码:简要综述。国际自动化和计算杂志,第 1-15 页,2021。
[12] R. Koster、M. J. Chadwick、Y. Chen、D. Berron、A. Banino、E. Duzel、D. Hassabis 和 D. Kumaran。海马系统内的大循环复发支持跨发作的信息整合。神经元,99(6):1342–1354,2018。
[13]马奥尔。勾股定理:4000 年的历史。普林斯顿大学出版社,2019。
[14] K. A. Norman、S. M. Polyn、G. J. Detre 和 J. V. Haxby 超越读心术:功能磁共振成像数据的多体素模式分析。认知科学趋势,10(9):424–430,2006。
[15]奥图尔、江、阿卜迪和哈克斯比。腹侧颞叶皮层中物体和面孔的部分分布表征。认知神经科学杂志,17(4):580–590,2005。
[16] F .佩德雷戈萨、g .瓦洛夸、a .格拉姆福特、v .米歇尔、b .蒂里翁、o .格里塞尔、m .布隆德尔、p .普雷登霍弗、r .魏斯、v .杜伯格、j .范德普拉斯、a .帕索斯、d .库尔纳波、m .布鲁彻、m .佩罗特和 e .杜切斯内。sci kit-learn:Python 中的机器学习。机器学习研究杂志,12:2825–2830,2011。
17 r . a .波尔德拉克。功能磁共振成像的感兴趣区域分析。社会认知和情感神经科学,2(1):67–70,2007。
[18] M. Poustchi-Amin、S. A. Mirowitz、J. J. Brown、R. C. McKinstry 和 T. Li。回波平面成像的原理和应用:普通放射科医师回顾。放射学,21(3):767–779,2001。
[19] R. P. Reddy,A. R. Mathulla 和 J. Rajeswaran。心理健康专家的观点采择和情绪传染的初步研究:移情的玻璃脑观点。印度心理医学杂志,0253717620973380,2021 页。
[20]史密斯、米勒、萨利米-科尔希迪、韦伯斯特、贝克曼、尼科尔斯、拉姆齐和伍尔利奇。功能磁共振成像的网络建模方法。神经影像,54(2):875–891,2011。
21 田中先生。下颞叶皮层和物体视觉。神经科学年度评论,19(1):109–139,1996。
[22] M. S .特雷德。Mvpa-light:一个多维数据的分类和回归工具箱。神经科学前沿,14:289,2020。
[23] M. P .范登赫维尔和 H. E .波尔。探索大脑网络:静息态功能磁共振成像功能连接综述。欧洲神经精神药理学,20(8):519–534,2010。
[24] G. Varoquaux,A. Gramfort,J. B. Poline 和 B. Thirion。大脑协方差选择:使用群体先验的更好的个体功能连接模型。arXiv 预印本 arXiv:1008.5071,2010。
[25] Y. Wang,J. Kang,P. B. Kemmer 和 Y. Guo。一种利用偏相关估计大规模脑网络功能连接的有效可靠的统计方法。神经科学前沿,10:123,2016。
26s . Wold、K. Esbensen 和 P. Geladi。主成分分析。化学计量学和智能实验室系统,2(1–3):37–52,1987。
27s . Wold、K. Esbensen 和 P. Geladi。主成分分析。化学计量学和智能实验室系统,2(1–3):37–52,1987。
用 Python 解释离散随机变量和 PMF
学习如何掌握概率质量函数(PMF)
当我刚开始学习统计学的时候,我多次使用随机变量的概念,但并不真正知道随机变量是什么。我们在假设检验、模型拟合和无数其他领域使用它。在本帖中,我们将基于之前建立的概率基础来理解随机变量有多重要。
让我们从形式定义开始
(数学定义)随机变量是一个映射或函数x:ω→ℝ,它给每个结果ω分配一个实数 X(ω)。
ω和ω应该看起来很熟悉,因为我们在以前的帖子中已经详细讨论过它们。然而,由于几个原因,这个定义可能会令人困惑。首先,我们将每个结果分配给什么实数?还有,我们为什么要给实数赋值呢?为什么我们需要随机变量?
这些问题都会有答案,继续看下去就好。
让我们把这个概念和流行文化联系起来。在之前的帖子中,我们讨论了漫威电影宇宙(MCU)中的无限宝石。这些宝石是贯穿 MCU“阶段”的故事的核心,也被称为“无限传奇”。
让我们从给每个代表其整体能力的无限宝石赋值开始。假设我分配了以下总分数:
- 灵魂:6(最强大)
- 功率:5
- 时间:4
- 头脑:3
- 现实:2
- 空间:1(最弱)
我想我选择这些值的原因是为了另一天。我们让随机变量 X 是选择的两个无限宝石的分数之和。我意识到每个宇宙只有六块石头,所以让我们也假设我们从一个使用量子领域的另一个现实中借用了六块相同的石头。我们知道在样本空间ω中有 36 种可能的样本结果:
所以,现在我们有了随机变量配方中的所有成分,首先,我们描述了实验(选择两个无限宝石),其次,我们给每个样本结果赋予一个值。这组值 X 被认为是一个随机变量。请记住,随机变量不像代数中那样采用单一值,它可以采用给定集合中的任何值({2,3,4,5,6,7,… })。它随机假设这些值中的任何一个,因此被称为“随机”变量。这和概率有什么关系?让我们来看看下面的符号
这是以下内容的“简写”:
“ℙ(X = x”简单来说就是随机变量 x 取值 x 的概率是多少,所以让我们回到我们的例子来真正理解这个方程。假设我们在选择了两个无限大的石头后,构建了一个分数总和的矩阵:
力量值等于 10 的概率是多少?嗯,本质上这个问题是问ℙ(X = 10) =?。我们知道这一点
你有它!现在我们明白随机变量和概率的关系了!我们刚刚讨论的例子被认为是两种随机变量中的一种。它被称为离散随机变量,因为它可以假设有可数个值。第二种随机变量被称为连续随机变量。
现在,如果有一个函数来描述获得一个随机变量可以假设的所有不同值的概率之间的关系,那不是很好吗?正好有这样的功能!该函数被称为概率函数,对于离散随机变量
,我们将该函数称为概率质量函数 (PMF),定义如下:
那么,让我们用 python 这种奇妙的语言来更好地理解概率函数的威力吧!我们不会详细讨论代码,我们假设你们大多数人都具备 python 的工作知识。
正如你所看到的,PMF 允许我们可视化一个随机变量,并使我们能够轻松地回答问题,如ℙ(X ≤ 6)。这种说法本质上是在问:选择两块石头导致总分小于等于 6 的概率是多少?现在,如果我们看我们的 PMF 图形,我们可以看到它是对称的,以 7 为中心。我们也知道所有的概率需要加到 1。所以只要做以下事情:
我们使用强大的 PMF 来确定有 42%的机会选择 2 颗总分不超过 6 分的无限宝石。
在这篇文章中,我们定义了一个离散的随机变量,提供了一个例子,编码,并演示了如何使用 PMF 可视化和研究随机变量。
多模光纤中的离散锥形发射
思想和理论
在这项工作中,我们介绍了导致离散锥形发射的多模光纤中非线性脉冲传播的数值模拟结果。
非线性光学是光学的一个分支,它研究的是由于光引起的物质光学性质的改变而产生的现象。光纤是研究非线性现象的一种非常有吸引力的介质。由于纤芯的小尺寸和光纤的几何形状允许光传播很长的距离,即使初始脉冲的功率相对较低,波导也会表现出非线性。
光纤中非线性现象研究的重要部分涉及单模光纤。由于它们的设计,对于给定的光频率,它们只允许一种模式传播。在实践中,它们被用于光纤网络传输信息。最近,对多模光纤的兴趣带来了关于时空非线性效应和模间频率转换的有趣结果[1–3]。这种兴趣与多模光纤在电信网络中的潜在用途有关。在这种类型的光纤中,对于给定的频率,存在许多被引导的光的空间分布。以不同模式发送信息允许吞吐量的进一步倍增,尽管这需要开发以多种模式同时发送、重定向和接收信息的方法【4】。
多模光纤
多模光纤可以支持给定波长的多种导模。它们的数量由所研究光纤的光频和折射率分布决定。每种导模都以特定的方式传播,这可以表现为其电磁场分布。线偏振(LP)模式的识别基于确定沿两个场横截面的极值数量:径向和横向。根据波长和折射率分布,相同模式的场分布可能略有不同。
多模光纤中模式解的电磁场归一化分量示例。图片作者。
同时,我们可以把多模光纤看作是块体材料和单模光纤之间的过渡材料。这使得它们成为探索这两种物质介质中发生的非线性现象之间的联系的理想选择。这种非线性现象之一是圆锥形发射,大量观察到围绕中心光点的彩色环【5,6】。此外,在我们最近的工作中,我们将体介质中描述的锥形波的概念推广到结构化介质,如多模光纤,其中只有离散和有限数量的模式可以传播[7,8]。
数值模拟
我们比较了模拟多模光纤中非线性传播的两种数值方法:广义多模非线性薛定谔方程(MM-GNLSE) [9],它考虑了电场的模式分解和模间非线性耦合,以及单向脉冲传播方程(UPPE) [10,11],它已被广泛用于研究大块透明介质和气体中的非线性光学。
在这两种方法中,我们可以区分色散项和非线性项。一般来说,作为一个严格的规则,色散部分决定了介质中的光速和一些电磁损耗,而非线性部分与光纤中观察到的非线性效应有关(特别是可以以一些频移的形式观察到)。通常使用分步傅里叶变换方法(SSFM)分离和描述色散和非线性,该方法在所有用于这类问题的比较数值方案中花费最少的计算时间。
数值工具为在多模光纤中发生的非线性现象的计算研究中深入检查光传播提供了极好的机会。此外,获得的数值模拟结果支持实验工作,并允许表明诱人的现象。通过数值模拟,人们可以获得关于光在整个传播距离上的所有信息(不仅仅在输出上测量)。此外,可以打开和关闭求解方程的不同项(事实上不包括特定的现象),以理解潜在的物理学。
然而,大多数时候,导出的数值模型仅支持定性的实验结果,特别是当涉及的模式数量变得显著(即高于 10)并且超宽带频率转换过程(即超过一个倍频程)也发生时。迫切需要阐明数值模型的潜力和适用范围,以促进未来非线性和多模光纤器件的精确设计。
离散锥形发射
在我们的研究中,我们研究了超短脉冲在多模光纤中的传播(输入激光脉冲的持续时间为飞秒),其峰值功率在临界功率附近,临界功率被定义为可以施加到材料上而不破坏它的最高可能功率。我们观察到的形成,我们称之为离散锥形波,确切地说是离散 X 波。
所考虑设置的示意图。图片作者。
在色散非线性体介质中,特别是在成丝机制中,已经对锥形发射进行了深入研究[6]。对正常色散(群速度随光频增加而降低)中的光丝的解释通常呈现典型特征,如脉冲分裂和锥形发射,并且可以假设脉冲为自发出现的非线性 X 波来解释。更具体地说,离轴(圆锥形)尾出现在输入波长的蓝移侧和红移侧,形成圆锥形发射的通用清晰 X 形图案。发现输入脉冲可以向最终稳态演化,最终稳态具有正常色散范围内的 X 波或异常色散范围内的 O 波的形式(其中群速度随着光学频率的降低而降低)。非线性 X 波和 O 波因其明显的 X 形和 O 形而得名,它们分别出现在近场和远场中。
色散的符号决定了飞秒细丝的动力学,并因此决定了相关超连续谱的时间-频率内容(产生的白光由非常宽的频率范围组成)。已经证明,色散景观是准确模拟锥形发射的关键因素,因为它决定了锥形发射模式的具体形状:正常色散为 X 形,异常色散为 O 形,或者当泵浦接近零色散时为鱼形。在多模光纤的情况下,只有离散数量的导模可用,并且期望离散的锥形发射。每种模式中锥形波成分的光谱位置可以通过使用实验得出的相位匹配条件来预测[7]。
两种建模方法传播 2 厘米后的模式数分辨 X 波锥形发射光谱。白色方块表示每个空间模式中的中心相位匹配频率。在 MM-GNLSE 的情况下,我们只研究了 15 种模式,它们可以在所研究的光纤中传播。图片作者[8]。
当比较使用两种模型获得的结果时,我们看到关于与自聚焦相关的强光谱展宽以及来自基模 LP01 的高阶模中的能量扩散的定性一致。我们还指出了 X-模式形成的典型特征。然而,与 UPPE 方法相比,MM-GNLSE 显然高估了整体非线性响应,从而导致更大的频谱、时空域中更高的峰值功率以及高阶模式中更大的功率谱密度。
结论
在我们的实验中,我们通过比较两种众所周知的建模方法,数值研究了超短脉冲在多模阶跃折射率石英光纤中传播的高度非线性机制。
我们的主要结果显示了非线性响应的频率色散对飞秒范围内脉冲分裂和超连续谱动力学的强烈影响。从实验的角度来看,这种容易实现的泵浦方式似乎是揭示近似数值模型(如 MM-GNLSE)的一些局限性的良好起点。此外,我们表明,由此产生的时空动力学与锥形波的形成。
波兰国家科学中心共同资助的项目(项目编号 2018/30/E/ST7/00862,索纳塔 BIS 8 计划)。
原载于 2021 年 8 月 18 日https://majsylw . netlify . app。
文学
[1] W. H .伦宁格和 F. W .怀斯。渐变折射率多模光纤中的光孤子。《自然通讯》, 4:1716–1719,2013。
[2] L. G. Wright、D. N. Christodoulides 和 Frank W. Wise多模光纤中可控的时空非线性效应。《自然光子学》, 2015 年 9:306–310。
[3] K .克鲁帕、a .托内洛、a .巴泰勒米、t .曼苏尔扬、v .库德尔茨、g .米洛特、p .格雷卢、d .莫托托、S. A .巴宾和 s .瓦布尼茨。多模非线性光纤,时空大道。APL 光子学,4(11):110901,2019。
[4] D.J .理查森、约翰·菲尼和林恩·纳尔逊。光纤中的空分复用。《自然光子学》, 7:354–362,2013 年 5 月。
[5]阿尔法诺和夏皮罗。在玻璃中通过四光子耦合在 4000 到 7000 范围内发射。物理评论快报,24:584–588,1970 年。
[6] D. Faccio,A. Couairon 和 P. Di Trapani。锥形波、细丝和非线性成丝光学。阿拉卡内,罗马,2007 年。
[7] B .基布勒和 p .贝约特。多模光纤中的离散锥形波。物理评论快报,126(2):023902,2021。
[8] K. Tarnowski、S. Majchrowska、P. Bejot 和 B. Kibler。多模光纤中超短脉冲传播和锥形发射的数值模拟。美国光学学会学报 B,38(3):732,2021。
[9]波莱蒂和霍拉克。描述超短脉冲在多模光纤中的传播。美国光学学会杂志 B,25:1645–1653,2008。
[10] M .科莱西克、J . V .莫洛尼和 M .姆莱涅克单向光脉冲传播方程。物理评论快报,89(28):283902,2002。
11j . Andreasen 和 M. Kolesik。光在结构化介质中的非线性传播:广义单向脉冲传播方程。物理评论 E —统计、非线性和软物质物理学,86(3):1–9,2012。
https://www.osapublishing.org/josab/abstract.cfm?uri=josab-38-3-732
用公式表示简单 CNN 的形状和参数数量
以及 CNN 项目的其他挑战
维基上的照片由 Aphex34 拍摄
今天我想展示一个例子,如何计算一个简单的卷积神经网络的形状和参数数量,还包括一些其他的经验。它以狗分类项目为例。这是入门级的,不精通技术,但欢迎您的专业意见,以帮助我更好地理解这个主题。
我们开始吧。
问题 1:计算 CNN 模型的形状和参数个数
假设您了解 CNN 的架构。一个 CNN 模型有五个卷积层,包括池层,三个全连接层,1000 个输出分类,如下图所示。
图片来自 Udacity DL ND
请提醒:
我们需要为全连接层 1 的空间维度计算 CNN 模型的形状,并且我们不需要计算模型的参数数目来构造 CNN 模型,但是必须知道卷积层中的参数数目。
CNN 模型中的参数:
假设__init__
函数中的卷积层使用以下格式:
self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding)
self.pool = nn.MaxPool2d(kernel_size,stride)
self.fc = nn.Linear(in_features, out_features)
将使用的参数有:
in_channels
-输入的数量(深度),例如 RGB 图像为 3。out_channels
-输出通道的数量,即构成卷积层的过滤“图像”的数量,或者将应用于输入的唯一卷积核的数量。kernel_size
-指定(平方)卷积内核和池内核的高度和宽度的数字。in_features
-每个输入样本的大小out_features
-每个输出样本的大小
除了全连接第 1 层的 in_features(这是计算模型形状的目标)之外,所有参数都将给出(由您决定)。
现在让我们检查给定的 CNN 模型。它包含卷积层、批量标准化层、激活函数、池层和完全连接层。
计算卷积层的形状
当我们说卷积层的形状时,它包括空间维度和层的深度。
卷积层的 空间维度 (x,y)可以计算为: ***(W_in−F+2P)/S+1*** *.*
卷积层的 深度 将始终等于滤波器的数量 *K*
。
K
-卷积层中的滤波器数量F
-卷积滤波器的高度和宽度S
——回旋的大步P
-填充W_in
-前一层的宽度/高度(平方)
同时:
K
=out_channels
F
=kernel_size
S
=stride
W_in
是input_shape
元组的第一个和第二个值(比如 224*224,W_in=224)。
代码给出的参数:
input_shape
元组:224* 224* 3(RGB)
out_channels
从 conv1,2,3 是:32,64,128
kernel_size
=F : 3x3,
padding
=1 用于三个卷积层
stride
在 conv1,2,3=1
stride
在 maxpool=2
我们来计算一下:
Conv1 输入:W_in =224,F=3,P=1,S=1,in_channels=3,out_channels=32
Conv1 输出:conv1 输出的(x,y)大小:(224–3+2 x1)/1+1 = 224,depth_1=out_channels=32。
池化第 1 层输入:W_in=224,kernel_size=2,stride=2
池层 1 输出:
最大池层后输出的 x-y 大小:224/2(kernel_size)=112
Conv2 输入:W_in=112,内核大小=3,in_channels=32,out_channels=64
Conv2 输出:conv2 输出的 x-y 尺寸:(112–3+2 x1)/1+1 = 112,depth_2=64
池化第 2 层输入:W_in=112,kernel_size=2,stride=2
第 2 层输出池:112/2=56
Conv3 输入:W_in=56,内核大小=3,in_channels=64,out_channels=128
Conv3 输出:conv3 输出的 x-y 尺寸:(56–3+2 x1)/1+1 = 56,depth3=128
池化第 3 层输入:W_in=56,kernel_size=2,stride=2
池化第 3 层输出:56/2=28
来自池层 3 的输出大小:2828128=100352,将作为全连接层的输入。
FC1:尺寸变为 1003521,输出 25001
FC2:尺寸从 25001 到 5001
FC3:尺寸从 5001 到 1331
现在我们看看 1282828 是怎么来的。理论上,一旦我们得到了形状,模型的设计就完成了,但我们最好知道这个模型有多少参数,以便更清楚地了解这个过程。
计算卷积层中的参数数量
卷积层中参数的数量取决于所提供的filters/out_channels
、kernel_size
和input_shape
的值。
D_in
-前一层的深度F
-卷积滤波器的高度和宽度K
-卷积层中滤波器的数量
同时:
K
= out_channels
F
= kernel_size
D_in
是input_shape
元组中的最后一个值,通常为 1 或 3(分别为 RGB 和灰度)。
计算过程为:
- 每个过滤器都有
F*F*D_in
重量 - 卷积层由
K
滤波器组成 - 卷积层中权重的总数是
K*F*F*D_in
- 每个滤波器有一个偏差项,卷积层有
K
偏差 - 卷积层中的 参数数量 为
***K*F*F*D_in + K***
对于每一层:
输入层:输入层所做的就是读取输入图像,所以这里没有你可以学习的参数。
池层:该层没有需要学习的参数。
卷积层:
conv1 :每个滤波器有 333 个权重,该层由 32 个滤波器组成,该层的权重数为 33332,加上 32 个偏差,该层的参数数为 33332+32=896
conv2 : k=64,F=3,D_in=32,参数个数= 6433*32+64=18496
conv3 : k=128,F=3,D_in=64,参数个数=12833*64+128=73856
FC1 :在全连接层中,所有输入单元对每个输出单元都有单独的权重。对于 100352 个输入和 2500 个输出,权重数为 1003522500=250880000。此外,该层对于每个输出节点都有一个偏差,因此有(100352+1)2500=250882500 个参数。
FC2:(2500+1)* 500 = 1250500
FC3 : (500+1)*133=66633
现在我们看看有多少参数将被模拟。
附加信息:
- 添加 BatchNorm2d 层有助于通过最小化内部协变量偏移来提高测试精度。
- “放弃”设置为 0.2,以消除过度拟合效果。
我知道这个过程很长,可能会很无聊:-),但这是基本的,如果一旦理解了这个案例,对理解类似的案例会有很大的帮助。
问题 2:自己验证空间维度公式***(W_in−F+2P)/S+1***
如何验证空间维度公式?可以参考cs 231n 课程链接查看理论。但对我来说,一个例子会帮助我牢牢记住它。
来源:图片由作者提供
情况 1: padding =0,stride=0,输入:6x6,滤波器:3x3,输出(x,y)大小怎么样?(6–3)/1+1=4
情况二:填充=1,步幅=0,输入:6x6,滤波器:3x3,输出(x,y)大小怎么样?(6–3+2)/1+1=6
没有公式,你有什么答案?以及你的答案是否符合公式(W_in-F+2p)/S+1 的结果?
我们可以看到,填充保持了目标与输入具有相同的大小,这将减少信息损失。
问题 3:图像转换器的一些隐含知识和规则
****一些隐性知识更好地了解:
OpenCV 的检测器需要输入的图像是灰度的,但对大小没有要求。
VGG16、VGG19 和 ResNet 接受 224x224 的图像大小,而 Inception V3 和 Xception 接受 299×299 的图像大小。
使用 ImageNet 中的图像创建 CNN 事实上,您可以选择自己的图像大小,不同于 VGG16: 240x240 或 256x256,但您必须确定大小,因为 ImageNet 中的图像大小是多种多样的。
图像数据集
CIFAR-10:包含 10 个固定大小的类图像:32x32x3,有 60000 个图像,每个类包括 6000 个。这十类是飞机、汽车、鸟、猫、鹿、狗、青蛙、马、船和卡车。
MNIST:包含 60,000 个 28×28 像素的灰度图像,手写的位数在 0 到 9 之间。
ImageNet:ImageNet 中的图像有各种尺寸,如 224×224、227×227、256×256 和 299×299 等。
一些规则:
图像预处理我没有找到确切的规则,只有一些零散的规则。有欢迎分享。下面的规则是我从这个 StackOverflow 线程中搜索到的,很有帮助:
- " 随机数据扩充只允许在训练集上进行,您可以将数据扩充应用于验证集和测试集,前提是这些扩充都不是随机的"。
- “当您在验证和测试集上使用规范化时,您必须使用与您在训练集上使用的完全相同的因子。”
- 需要调整大小然后居中裁剪是因为 val 集需要来自训练集的同一域,因此,如果前者被随机调整大小并裁剪为 224,则 val 集需要确定性地调整大小和裁剪。
在今天的故事中,我将重点讨论计算一个简单 CNN 模型的形状和参数数量,并包括一些其他挑战。
感谢您的阅读。
参考资料:
- **【https://pytorch.org/docs/stable **
- Udacity 深度学习纳米学位
- https://cs231n.github.io/convolutional-networks
黑客新闻中的隐私讨论:探索性文本挖掘分析
我们用 Google BigQuery 汇编了一个数据集,并用简单的文本挖掘方法分析了关于隐私的帖子。
凯文·Ku 在 Unsplash 上的照片
作为 NGI 前进项目的一部分,德拉布·UW正在支持欧盟委员会的下一代互联网计划,确定与互联网相关的新兴技术和社会问题。我们的团队一直在尝试各种自然语言处理方法,以发现不同类型的在线媒体中的趋势和隐藏模式。您可以在 https://fwd.delabapps.eu 找到我们的工具和演示。
简介
近年来,隐私一直是科技讨论的关键话题之一。虽然自 GDPR 问世以来已经过去了近 3 年,但隐私争论仍未尘埃落定。事实上,新的发展每天都在出现:仅举几个最近几周的新闻,例如“ cookie 启示录”、围绕新的热门社交媒体平台 Clubhouse 的数据实践的问题,或者 Whatsapp 隐私政策的争议性更新。因此,鉴于这一领域发生了如此多的事情,我们想更深入地了解是什么推动了 infosec 社区中的对话。
在这个分析中,我们关注黑客新闻中的帖子和评论。使用谷歌的 BigQuery,我们收集了 2018 年 1 月至 2021 年 1 月期间所有带有“隐私”一词的帖子。要查看我们关于 BigQuery 和黑客新闻的教程(以及我们如何设法收集帖子的所有评论——这是一项需要一些 SQL 技巧的任务),请前往这个 Kaggle 笔记本。
数据收集的分步指南在这里
数据集
我们的数据集包含大约 65000 个故事(帖子),收到了近 50000 条评论。每个故事的平均评论数是 7.6,但是,它们的分布非常不均匀,因为 69%的故事没有收到任何评论。另一方面,评论最多的故事吸引了 626 条评论。
每月的故事和评论数量如图 1 所示。平均每月有 175 篇报道被发布,收到 1330 条评论。虽然随着时间的推移,这些价值观的演变并没有显示出强劲的趋势,但在 2018 年 4 月,就在 GDPR 被采纳前后,评论和帖子都出现了显著上升。
图一。每月故事/评论的分布
结果
首先,让我们找出评论最多的故事:
1.1.1:快速、隐私优先的消费者 DNS 服务
告诉 HN: Triplebyte 逆转,邮件道歉
告诉 Triplebyte 面试?您的个人资料即将公开
问 HN:Gmail 的最佳替代品是什么?
Zoom 需要清理其隐私法
这些标题让我们知道是什么样的故事在推动参与:交流对隐私保护技术(如 DNS 解析器、电子邮件服务)的见解,并讨论有争议的数据处理实践。
为了更全面地了解热门报道,让我们关注评论数量最多的 20%的报道(最少 8 条评论)。在对标题进行标记并删除停用词之后,最常见的二元模型被识别出来,如图 2 所示。除了常见的疑点(如数据隐私、隐私安全等),列表中还可以找到一些有趣的概念。这样的例子是 差分隐私 ,这是一套新兴的方法学,在包含个人信息的数据库中保护个人用户的匿名性。
图二。标题中最常见的二元结构
我们可以更进一步,给我们的研究增加一个新的维度:情绪。使用 VADER 我们计算了每个评论的综合情感得分,并根据评论的平均情感得分对故事进行了排序。VADER 将社交媒体文本文档分为三类:正面(> 0.05 分)、中性(> -0.05 和< 0.05)和负面(< -0.05)。为了对这些故事有一个总体的了解,让我们来看看 10 个最积极和最消极的故事的标题:
阳性(得分在 0.75 — 0.59 之间)
问 HN:iPad 作为以 SSH 为中心的开发机器准备好了吗?
向 HN 展示:米德——我是如何反击媒体的
问 HN:我如何准备一份实施顾问的工作?
我们是 Mozilla 新的创业孵化器。AMA
Windows 10 vs. Mac,什么更能避免遥测隐私问题?
启动 HN:测序生物(YC S19)——纽芬兰的药物发现遗传学
展示 HN:用于互联网透明和数据隐私的开源 Chrome 扩展
用 Tor?您的隐私可能被泄露
发布 HN: Papercups (YC S20) —开放核心内部通信替代方案
问 HN:生活在贫困线上,你如何省钱?
阴性(得分在-0.32 — -0.17 之间)
DuckDuckGo 的隐私滥用——当前、历史和代理
是时候换一个隐私浏览器了
第九巡回法庭更进一步,在边界设备搜索中保护隐私
问 HN:科技人群——你对利用科技解决枪支暴力有什么想法?
隐私不平等:你所能想象的最残酷的不平等形式
谷歌和脸书在国会面前滔滔不绝地谈论隐私
为什么隐私很重要,即使“你没什么好隐瞒的”
苹果在数据隐私上是个伪君子
问 HN:为什么我们允许谷歌的滥用?
《我没什么好隐瞒的》和其他对隐私的误解(2007)
为了更深入,让我们也检查一下属于这些故事的评论中最常见的二元模型(图 3)。
图三。评论中的二元模型(10 个最积极/消极的帖子)
标题和评论的洞察力为我们提供了这些讨论的地图。黑客新闻是创业公司介绍自己的热门地点(例如发布 HN 系列),因此一些关注隐私的新创企业和开源项目被捕获。该列表还包括精通技术的用户认可的工具(例如,用 iPad 连接到远程工作站或提供隐私信息的开源浏览器扩展)。
另一方面,负面评论的帖子不仅涉及隐私和常见嫌疑人(广告业务中的科技巨头),还涉及关于用技术解决社会挑战的辩论,如枪支暴力。最负面的故事也包括意想不到的嫌疑人,如保护隐私的搜索引擎 Duckduckgo。然而,仔细研究后,似乎 Duckduckgo 上的故事有最多的负面评论,因为用户对帖子中的论点提出了质疑。
其余正面和负面故事中的评论如何?图 4。列出了前 50 个故事中的二元模型,不包括我们已经讨论过的前 10 个。
图 4。评论中的二元模型(前 11-50 个帖子)
虽然正面评论没有提供更多信息,但负面评论介绍了技术和政策方面的关键概念:被遗忘的权利,言论自由,面部识别(狗屎?可能没那么重要)。
结论
在 Google BigQuery 的帮助下,我们在 Hacker News 上收集了一组涉及隐私的帖子。这个练习表明,即使是简单的探索性分析(频繁的二元模型和情感分析)也能为技术社区讨论的两极分化的话题提供有意义的见解。数据的潜力要大得多:首先,数据结构支持其他关系的分析,比如评论中的同现。第二,可以实现各种更高级的方法,例如从维度减少或网络分析空间。
请在 Kaggle 随意探索数据集,如果有想法,请与我们联系!
在 ICML、VLDB 和 ICLR 讨论 ML 中的信任、道德和责任
随着人工智能研究中的道德和责任问题开始在全球人工智能社区中引起共鸣,我们来看看今年致力于这项事业的一些研讨会。
人工智能研究和相关讨论正在如火如荼地进行——这对任何人来说都不奇怪。另一方面,“负责任的人工智能”的说辞还有待改进。根据去年的一项调查,目前只有 25%的公司认为无偏见的人工智能是一个有价值的目标。大型科技公司已经采取措施来改变这种状况,但总的来说,私营部门还没有把负责任的人工智能作为优先事项。
与此同时,当你申请银行贷款时,通常是机器根据交叉引用年龄、性别、邮政编码和语言等因素的算法来决定你是被批准还是被拒绝。许多工作申请现在也是这样评估的。当你进入一辆现代汽车,语音识别被使用,你被理解的程度(或者根本不被理解)取决于你的口音。自然,这些因素引起了对 ML 模型如何构建和实现的合理关注。
一些世界领导人非常清楚受控和良好维护的人工智能实践的重要性。例如,去年,欧洲议会发表了一份 100 页的综合报告,内容是关于与不受控制的人工智能发展相关的潜在问题。WEF 和经合组织也公开反对黑箱算法和不道德的数据使用。
显然,ML 科学家今天从事和监督的研究有着深远的影响。这种发展必须为透明管道留出空间,以适应公平和道德的决策,避免歧视和不公正。或者至少没有损坏的数据——这将是第一个危险信号,接下来会有更多的麻烦。
世界各地的许多 ML 研讨会和会议已经开始定期关注这些关键问题,希望有所作为。让我们来看看今年最有趣的三个例子。
→负责任的人工智能:对公平性、可解释性、安全性、健壮性和超越性之间的实际限制和权衡的跨学科视角
作为今年 ICLR 会议的一部分,5 月 7 日举行了一次跨学科研讨会。该研讨会聚集了各个人工智能子领域的专家,从学术界和商业到政府-包括研究人员和从业人员。目的是从公平性、安全性和可解释性的角度评估 ML 管道。通过一系列谈话,该活动提出了反映在道德和法律准则和实践中的透明度和偏见(黑盒算法)问题,其中包括刑事司法、医学和教育。
演讲者
所有的谈话都被记录下来,可以在这里找到。
📣这篇题为“负责任的解释:仅有良好的意愿是不够的”的演讲是由谷歌大脑的科学家贝金博士发表的。金从社会责任的角度提出了她对曼梯·里未来的看法。她展示了一系列作品,并重新审视了可解释 ML 中的目标和方法。
📣下一个演讲的题目是“作为一名社会科学家与计算机科学家一起工作的挑战和机遇”,由南加州大学的副教授埃里克·赖斯博士主讲。Eric 讨论了人工智能的当代研究,分享了他对人工智能驱动的干预计划的见解,并介绍了他对无家可归的青少年进行艾滋病毒预防干预的 3 项主要研究结果。
📣另一个题为“创造人们可以使用的模型:来自健康应用的经验”的演讲是由哈佛大学副教授约翰·l·勒布和 T2 的压轴博士多希-维勒兹做的。Finale 讨论了人类受试者实验,这些实验测试了旨在促进人工智能和临床医生在抗抑郁治疗背景下的交互的界面。她揭示了临床医生提出的最常见的改进要求,以及他们对正确和不正确的人工智能建议的反应。
关于 ICLR
学习表征国际会议(ICLR)是每年春天举行的国际 ML 会议。第一届 ICLR 会议于 2013 年举行,自那以来已经成为计算机科学家的主要聚会。今年有近 3000 篇论文提交,30%的接受率,今天大会被认为是人工智能和人工智能领域的前三大全球活动。
→规模化众包数据管理中的信任、道德和卓越
作为 VLDB 今年第 47 期的一部分,一个群体科学研讨会将在会议的最后一天,即 8 月 20 日举行。该研讨会名为“大规模众包数据管理中的信任、道德和卓越”,将涵盖数据标签众包方法的三个主要问题:大规模数据卓越、人群-人工智能相互作用以及信任和道德。
在研讨会期间,三位主旨发言人将考察众包在大规模研究中的作用,重点是众包工作者的福利和注释数据的可信度。
扬声器
📣题为“计算与组织”的第一个研讨会演讲由获奖作者、斯坦福大学副教授迈克尔·伯恩斯坦博士主讲。迈克尔将研究工作和在线平台的未来。他将讨论未来如何组织工作,以及当前的基础设施如何导致权利被剥夺和异化。然后,他将从工程和设计的角度以及政策的角度描述应对这些负面结果的可能对策。
📣第二个演讲的题目是“健康字节:培育群体工作的可持续未来”,由荷兰代尔夫特理工大学助理教授 Ujwal Gadiraju 博士主讲。虽然大多数与众包市场相关的问题和解决方案主要集中在数据质量上,但加迪拉朱博士将研究该领域鲜为人知的问题——大众工作者的福祉。通过使用 MTurk 和 perspective 作为例子,研究人员将从那些完成任务的人的角度剖析微任务众包,包括他们的薪酬和身心健康。将讨论管理众包的合同法的必要性。
📣题为“数字社区驱动的众包”系列的最后一个演讲由Wuraola oye wus主讲,他是尼日利亚数据科学的研究和创新负责人。演讲者将提供她对众包“人性化”方面的见解,即众包工作者不仅可以与众包平台互动,还可以在全球和区域社区内互动。将讨论众包背景下人与人之间的学习和反馈,并提供新的员工培训模式。
关于 VLDB
成立于 1975 年的超大型数据库国际会议 (VLDB)是致力于数据库管理的研究人员和开发人员最重要的年度聚会之一。由美国 VLDB 基金会组织,每年在不同的国家举行,备受期待的 2021 年 VLDB 奥运会将于 8 月 16 日至 20 日在丹麦哥本哈根举行。
→社会责任 ML
作为 ICML 今年第 38 期的一部分,一个虚拟研讨会将于 7 月 24 日举行。该研讨会将汇集理论和应用研究人员,目的是讨论如何建立对社会负责的 ML 管道。在其他主题中,将提出易受安全和隐私攻击、数据泄露以及种族和性别偏见等问题。该研讨会将讨论缺乏透明度的问题,以及它如何导致现实生活中故意或意外的数据扰动。
关于 ICML
机器学习国际会议(ICML)是人工智能和人工智能领域的领先全球会议。首次于 1980 年在匹兹堡举行,这一年度活动致力于统计和数据科学,重点是机器视觉,计算生物学,语音识别和机器人技术。这是目前世界上发展最快的人工智能聚会之一。今年的会议日期是 7 月 18 日至 24 日。
主要外卖
随着人工智能研究的进展,道德和公平的问题变得日益重要。对透明和建造良好的 ML 管道的需求是这种日益增长的关注的中心。目标是确保新兴技术的明显优势不会被不负责任的人工智能开发带来的问题所掩盖。本文中提到的研讨会详细研究了这些伦理问题,并提供了一些解决方案。
在基于 web 的地图上显示网格化数据集
理解大数据
使用全息视图、散景和数据阴影显示大型地理差异的分步指南
(图片由作者提供,使用https://www.openstreetmap.org/**holo viewsSRTM 高程数据 )**
问题是
你的老板/客户刚刚给你安排了一项新任务。他们在 GeoTIFF 文件中有一些地理信息,你必须“把它放在像谷歌地图这样的地图上,你知道,有街道,缩放和滚动”然后在网上提供。可视化应该使数据第一眼看上去是一幅真实的彩色图像。此外,在鼠标悬停时,用户还应该看到该位置的实际数据值和精确坐标。此外,GeoTIFF 文件相当大(高达几百或几千兆字节),因此将其全部加载到浏览器中并不是一个好选择。
你并不担心,尽管你从未尝试过这样的事情。这听起来像是一种很常见的情况,所以网络上应该充满了开箱即用的解决方案和工作示例。但事实并非如此。据我所知,没有一个例子或教程展示过类似的东西。现有的方法有很多种,我们将在本文的最后讨论它们。但是与上述规格相比,它们都有所不足。
解决方案
我将向您展示如何使用 Rioxarray 读取数据文件,然后使用 Holoviews + Bokeh 将结果显示在覆盖在地图上的图像层上,并使用 Datashader 处理一些自动调整大小的魔术。本文的目标读者是除了 Python 的一些基本经验之外,不需要任何先验知识就可以从事这项工作的任何人。你们中的一些人可能有广泛的 GIS、web 开发和数据可视化背景,而其他人没有。如果您只想查找代码,请向下滚动到“加载和理解数据”部分。
基础知识
任务是将 GeoTIFF 文件中的数据以某种方式放到基于图块的地图上。首先,我们来看看我们对这些东西本身了解多少。
1.什么是 GeoTIFF 文件?
GeoTIFF 文件(如果非常简化的话)就像一个 excel 表,其中每个单元格(像素)包含一个数字,而行和列的名称是坐标。这些值可以是任何值:慕尼黑的人口密度地图、马来西亚的卫星夜灯地图、德克萨斯州的温度记录,或者出于本文的目的,肯尼亚的高程地图。
知道坐标意味着我们可以精确地知道这个数据网格在地图上的位置:不仅仅是角落,还包括其中的每个像素。需要注意的是,该文件仅包含原始数据;它没有任何颜色信息。因此,传统的图像文件(如 bmp、jpeg 或 png)将包含左上角像素的信息,即它是“红色的”,而 GeoTIFF 文件将包含一个数值(如它具有以米为单位的“123”高程)。由我们来告诉我们的可视化系统每个值对应什么颜色。
大多数图像查看器应用程序可以使用默认的色阶打开和显示 TIFF 文件,通常假设最低值应该是黑色,最高值应该是白色,以及介于各种灰色阴影之间的所有内容。但是,重要的是要记住 TIFFs 本身并不是彩色的,它们只包含数据,我们需要通过使用色彩映射表来给它们着色。
一个图像浏览器应用程序如何使用默认灰度色图显示 TIFF 文件的例子。(图片由作者提供)
另一个需要考虑的重要因素是文件的分辨率,也就是一个像素覆盖的区域大小。可以有两个类似的 GeoTIFF 文件,两者覆盖相同的总面积,一个具有 50x50 米像素,另一个具有 5000x5000 米像素。很容易理解,第一个需要比第二个多 100 * 100 = 10000 倍的像素才能覆盖同样的面积。因此,这将是更大的文件大小,但会给我们更多的细节。第二个文件要小得多,但是每 25 平方公里只有一个数据点。在本练习中,我们将使用这两种分辨率。我们从一个低分辨率的原型开始,一旦我们学会如何在不冻结一切的情况下使用它,我们就切换到高分辨率的数据集。
低分辨率和高分辨率数据的区别。(图片由作者使用https://www.openstreetmap.org/holo viewsSRTM 高程数据 )****
事实上,在地理标志中会发生更多的奇迹。为了简单起见,我们假设我们的 GeoTIFF 包含一个“区带”(就像包含一个工作表的 excel 文件)。此外,我们并不关心它的云优化图像金字塔的内部细节或精确的坐标参考系统。然而,我强烈推荐阅读这些只是为了好玩。
2.什么是 tilemap?
基于磁贴的地图是我们都熟悉的谷歌地图。用户有一个交互式视口,他们可以四处拖动、放大和缩小,地图提供商服务器向他们发送适当大小的地图切片,以查看该缩放级别的合理详细信息。虽然将谷歌地图集成到您的应用程序中需要开发者密钥和付费订阅,但我们可以使用许多免费的地图提供商。幸运的是,大多数图表库使它们易于导入。
这里重要的是不要将 tilemaps 与“绘制的地图”(根据上下文和格式,也称为 geo maps、geojsons、vector maps、shapefiles)混淆。后者主要是国家或其他地区的静态轮廓,没有这里提到的任何功能。
显示在平铺地图和矢量地图上的肯尼亚地区。(图片由作者使用https://openstreetmap.org/)****
3.使用的工具
Rioxarray (及其依赖项 xarray 和 rasterio )是一个相当强大的模块,可以像 GeoTIFF 一样处理网格地理数据集,因此我们将使用它来打开和转换我们的数据,但没有必要了解太多。
然而,Holoviz 生态系统(包含 Holoviews、Bokeh、Datashader 等等)是一个更加令人兴奋的实体。这是一套开源工具,由 Nvidia 和 Anaconda 等公司出资。
Bokeh 是一个制图工具:它让你用 Python 创建交互式 web 图表,并通过 JavaScript 中的 BokehJS 库自动在浏览器中显示它们。如果你熟悉的话,这有点类似于 Plotly。一个主要的区别是,Bokeh 本身有一个内置的 web 服务器,所以它可以创建深度交互的图表,其中浏览器中的用户交互(如拖动滑块)被发送到服务器,Bokeh 在服务器上修改数据并无缝地发回结果。(Plotly 用破折号也是为了同样的效果。)这也是将要在这个笔记本中发生的事情:每次我们创建一个交互式可视化,一个散景网络服务器将在后台旋转并为我们服务,而我们不必担心它。
Holoviews 在层级中的位置更高一些。在 Bokeh 中,你必须手动创建所有的图表(即使有许多合理的默认值和帮助函数),而 Holoviews 希望给你一个工具,用你想要的可视化来描述你的数据,然后让绘图模块(如 Bokeh)做琐碎的工作。区别有点像烹饪,你必须处理过程中的每个细节,而去餐馆只是描述你想要的食物,让员工制作而不必处理细节。虽然有了 Holoviews,如果你想按照你需要的方式做一些步骤,你也可以进入厨房(绘图模块)。
Holoviews 还允许您使用各种绘图库。我们将让它在这里使用散景,因为我们将需要一些特定的功能。在许多其他情况下,只需将一个关键字转换为“plotly”或“matplotlib ”,就可以得到在这些系统中创建的完全相同的图表。
相同的代码以默认设置显示在 Matplotlib、Bokeh 和 Plotly 中。(图片由作者提供。)
正如我们将要看到的,Holoviews 擅长用几行代码创建真正的高级功能。
另一方面,Datashader 只做很少的事情,但是实现得很好。也就是说,它可以根据数据以任何方式创建图像。通常基于大量的数据。比如数十亿个独特的点或数十亿字节的网格数据,否则这些数据是不可能显示的。
我与 Holoviz 生态系统没有任何关系,但我觉得它是一组被严重低估的工具,应该在数据科学社区中获得更大的关注。它没有得到它的一个可能的原因是,对于某些功能,文档落后于它的实际能力,特别是当一个人必须在没有先验知识的情况下理解正在发生的事情时。这就是为什么本教程会遍历可能的陷阱,并试图让一切工作方式变得透明,希望让这些工具更容易被更广泛的受众使用。
数据源
- NASA/CGIAR 地球引擎的 SRTM 数字高程数据第 4 版
- 肯尼亚国家边界从到OSM 边界的公开街道地图
- 由作者在地球引擎中截取到 OSM 国家边界并导出的数据。
设置
我们将使用 Google Colabs,这是一个免费的 Jupyter 笔记本环境,可以很容易地进行实验和分享结果。你可以通过 Colab 笔记本跟随整篇文章,也可以在你看完这个之后用它来做实验。
它已经预装了许多模块,但我们还需要更多,而且我们想知道我们是否有我们需要的完全相同的版本。
我们可以通过在命令行前添加!
来运行命令行命令,因此我们将使用它来运行 pip 并在 2021-04-16 为这些模块安装最新版本。我们可能会遇到一些底层依赖的不兼容错误,但是它们不会给这个练习带来任何问题。
接下来,我们用他们的短手导入所有必要的模块。我们会经常用到这些快捷键,所以如果您不确定我们使用的一些函数来自哪里,请随时回来。我们还展示了一些模块的版本,以确保一切正常。
**Versions: {'holoviews': '1.14.3', 'bokeh': '2.3.1', 'rioxarray': '0.3.1'}**
我们还需要数据本身。我已经将肯尼亚的海拔地图作为云优化的 GeoTIFF 文件从 Earth Engine 导出到我的 Google Drive。(如果你对如何使用各种可用数据集完成这项工作的教程感兴趣,请在评论中告诉我。)
通过预装的gdown
工具,谷歌可以将公开共享的文件从 Drive 下载到 Colab。
如果你想将自己的数据集导入 Google Colab,只需将你的 Google Drive 文件分享给“任何有链接的人”。你会得到这样一个链接:
https://drive.google.com/file/d/16 dkulax 8 rkkuodqualrariwtg _ IBD msz/查看?usp =共享
这不是直接下载链接。它会在 Google Drive 预览窗口中打开文件。要获得一个可以用来下载文件的链接,你需要复制你的原始链接的 ID 部分(在上面的例子中用粗体标出)并放入这个链接: https://drive.google.com/u/0/uc?id=idcomesere&export = download
你最终的结果会是这样: https://drive.google.com/u/0/uc?id=**16 dkulax 8 rkkuodqualrariwtg _ IBD msz&export = download
这是一个你的文件的直接下载链接,所以我们可以用下面的方式把这个作为
*source_location*
与*gdown*
:**
*gdown -O local_destination source_location*
由于 Google Colab 的默认工作目录是/content
,我们首先创建一个名为kenya
的目录,然后将我共享的两个示例文件下载到该目录中。
在下载日志中,你可以看到较小的文件是 60.3KB,较大的 329MB。这是一个巨大的差异,所以首先,我们从较小的一个开始。
加载和理解数据
我们将使用 rioxarray 模块将文件中的数据加载到一个 xarray 数据结构中。
旁注:如果 Google Colab 单元格包含的最后一行只有一个变量,它将以一种有意义的方式显示它。从现在开始,我们将使用它来查看每一步的结果。
不用深入研究这种数据格式的细节,我们可以看到我们的数据有一个 218 乘 179 的维度,只有一个波段:高程数据本身。我们还可以看到,x 值大约在 30 到 42 之间,而 y 值大约在-5 到 5 之间,因此它们看起来像是合理的坐标值。
为了更熟悉我们的数据,我们可以很容易地将其转换成熊猫数据集并显示出来。我们可以看到它确实是一个网格数据集,坐标是行和列标题,以米为单位的高程数据是值。(当我将数据裁剪成肯尼亚的边界形状时,一个数字值显示缺少的值。)
这完全没有必要,甚至毫无意义,但为了强调“我们正在处理无色的网格数据,而不是实际的图像”这一点,我们甚至可以将数据导出并下载为 CSV 文件,如果我们愿意,可以在 Excel 中打开它。
Excel 中显示的同一个数据集。(图片由作者提供。)
配置
在继续之前,我们将设置一些配置值,从我们的色彩映射表开始。通常,我们可以给 Holoviews 一个颜色列表或一个现有颜色图的名称,但我喜欢为当前数据集使用颜色图的特定子集。我不打算在这里解释细节,因为您自己的数据不需要这个。只要知道我们取了terrain
色图的 25%-100%部分,并得到了一个在该范围内分布有 192 种颜色的列表就足够了。
产生的颜色列表
下一步,我们设置所有 Holoviews 元素从现在开始将使用的默认选项。在这里和这里见它们的文档。我们确保使用我们的颜色图和宽度/高度设置,显示颜色条。我们还打开了悬停工具提示,以及通过鼠标滚轮或触摸板滚动来放大和缩小的功能。
只有两件事需要多解释一下。
- 我将在每个单元格的开头包含
hv.extension('bokeh', logo=False)
行。这让 Holoviews 知道我希望它显示带有散景的元素(但不是每次在输出中都显示散景标志)。如果你在本地运行你的脚本(不是在 Google Colab 上),你只需要这样做一次。据说也有可能让 Colab 笔记本记住它而不重复,但事实证明这对我来说有点不可靠,所以我更喜欢这样使用它。 - 该数据不包含肯尼亚边界以外的数值。默认情况下,它会显示为灰色区域,但我们不希望这样,所以用
clipping_colors
参数,我强制它不可见(#00000000
RGBA 代码的最后两位数字表示 0 不透明度)。
将数据显示为图像
现在我们已经做好了一切准备,让我们将数据加载到一个数据集中。变化不大,但 Holoviews 稍后将需要这种格式的数据。dataarray[0]
指我们dataarray
中的第一个波段。我们还让它知道要使用的值('elevation'
)和坐标(['x', 'y']
),就像我们在上面定义它们一样。
我们将立即看到为什么这是方便的,因为现在使用我们的默认配置,将这个数据集输入到 Holoviews 图像并让 Holoviews 自动显示它是非常容易的:
正如你所看到的,我们有一个很好的显示,有交互图标、缩放、颜色条等。我们可以在坐标轴上看到图像的坐标,y 轴以 0 为中心,肯尼亚在赤道上。我们甚至有一个自动悬停工具提示(上面用tools=['hover']
配置创建的),如果鼠标在图像上,它会显示精确的坐标和高度值。
现在,让我演示一下 TIFF 只包含原始数据而没有任何颜色信息的好处。
下面你可以看到我们用和上面完全相同的数据创建的完全相同的 Holoviews 图像。我刚刚给了他们另外两张彩色地图。第一种使用inferno
连续色彩映射表,将较低值涂成黑色,较高值涂成紫色、橙色和黄色。第二个使用了用于分类可视化的Dark2
颜色图:仅仅通过使用这个颜色方案,我们立即将我们的数据分类到 8 个不同的海拔高度,而不改变数据本身。
但是让我们回到手头的任务。这是一件好事,我们有我们的形象,但我们也需要一个 tilemap。幸运的是,很容易得到一个。你可以在 Holoviews 网站上看到所有不同的选项(黑暗、光线和地形)。
我们现在要选择开放的街道地图。
将图像放到地图上
好的,那是一张地图,可以缩放,什么都有,但是它是空的。这就是 Holoviews 的“它只是工作”魔力发挥作用的地方:使用*
操作符,我们只是将图像放在我们的地图上,并期待最好的结果:
嗯,这是尴尬的…图像是可见的,但不是在地图上,而是在白色背景上,轴也显示一些奇怪的坐标…
为了理解这种现象(这导致了 StackOverflow 上绝大多数“地图上的图像很奇怪”的问题,不管使用什么工具),我们需要了解一点关于坐标系的知识,这通常过于简单。
我们的图像使用一个平面坐标系,其中的值通常在-180 到 180 度之间。它很棒,因为它让我们可以使用像地球这样的球形物体上的位置。另一方面,每个可用的 tilemap 引擎都使用 Web 墨卡托坐标系,该坐标系以米为单位测量所有事物。墨卡托经过优化,可以在平面上显示地图,也就是我们的屏幕。这可能非常令人困惑,因为这些墨卡托地图确实向我们显示了轴上的平面坐标(因为这些数字是每个人都熟悉的)。但那只是一个显示功能;内部都是用米。因此,当你给他们一个平面坐标约为-4.2151 或 32.14 的数据集时,这些值应该被理解为-4.2151 度和 32.14 度。同时,tilemap 会将它们解释为距离原点-4.2151 米和 32.14 米。虽然 360 度足够绕地球一周,但 360 米是一个非常非常小的距离。
结果是,我们的图像确实显示在地图上,就在 0,0 点周围,向每个方向延伸几米。因为 Holoviews 元素自动放大地图以显示完整的数百米图像,所以地图被放大到甚至不能显示海洋中部的任何分辨率的地图切片。如果你开始缩小,你就能看到这种情况发生,就像这张 g if 上看到的那样:
重新投影数据
现在我们明白了为什么会发生这种情况,让我们做点什么吧。我们需要将数据转换到适当的坐标系。平面坐标和墨卡托坐标之间的转换不是我们应该手动完成的。不仅仅是因为试图找出如何将一个或多或少呈球形的对象转换为 2D 是一件非常痛苦的事情,而且在现实中,数据集可能会使用许多略有不同的平面坐标系,我们绝对不想处理这些坐标系。(编程 101:永远不要试图重新实现处理日期、时区、翻译或坐标系统细节的功能。)
我们只是碰巧运气好,因为我们用来读取 GeoTIFF 的rioxarray
模块有一个处理这种确切情况的函数。我们只需要给它所需投影的代码(在这种情况下,墨卡托的代码是'EPSG:3857'
)。让我们重新开始,这次用正确的投影。
注意:重新投影一个巨大的文件会消耗大量资源,所以在生产环境中,我建议将结果保存在一个 Parquet 文件中,然后在显示数据时用 Dask 加载,但是这已经超出了我们的范围。
这次我们已经可以在 x 值中看到(使用科学记数法,3.775e+06
意为3.775 * 10^6
)它们在 3775000 到 4666000 之间。这些数字肯定比以前大得多,正如我们现在用米来计量时所预期的那样。让我们运行与之前完全相同的步骤(没有仍然有效和可用的配置部分):
万岁,成功了!有点…
我们有地图,我们在正确的位置上有它上面的图像,我们可以缩放和拖动,我们甚至有一个悬停工具提示…现在坏了:
叹气。这是有道理的:这一次,我们给了系统墨卡托坐标来显示,所以地图也不能在工具提示中显示熟悉的平面坐标。相反,我们得到的所有东西都是以米为单位的,尽管如此,这对于我们的大多数用户来说毫无用处。
因此,当我们显示悬停工具提示时,我们必须动态地将墨卡托坐标转换回平面坐标。为此,我们需要使用一些 JavaScript。如果你不熟悉它,不要害怕,你可以把它作为一个复制粘贴解决方案,然后忘掉它。
创建自定义悬停工具提示
如果你还记得,我提到过全息视图可以使用多个绘图库,在这个例子中,我们使用的是散景。它是一个 python 模块,让你编写 Python 代码,然后它将代码翻译成 JavaScript,并在浏览器中用 BokehJS 库显示。幸运的是它有一个专门的工具可以将墨卡托坐标转换回平面坐标。虽然到目前为止,我们甚至没有直接使用 python Bokeh 模块,但是这个设置让我们可以直接向 BokehJS 提供一些关于如何显示悬停工具提示的信息。
让我们再试一次,这次使用新创建的定制工具。
完美!
Pfew。那是一次旅行…
但是巨大的像素呢?
房间里有一头大象:到目前为止,我们只使用了每个像素覆盖 5 公里 5 公里的小数据集。由于这一点,整个压缩文件的大小是 60KB,所以我们可以很容易地在服务器上解压缩数据,并将整个内容发送到浏览器。我们为此付出的代价是,我们的图像非常像素化,尤其是放大后。*
这在某些情况下可能行得通,但是我们理论上的老板/客户有他们想要使用的非常详细的地图。该地图上的每个像素覆盖 50 米 50 米,因此整个数据集约为 22,000 像素高,18,000 像素宽。这将让我们显示更多的详细信息,如果我们的用户需要特定位置的值,这可能是至关重要的。问题是,对于高分辨率数据,即使是压缩的 GeoTIFF 也超过 300MB。如果我们试图一次显示整个数据集,解压缩后的数据将以千兆字节计算,这会导致巨大的下载时间,并保证浏览器中的内存冻结。*
这样做似乎也有点不必要。想想看,当用户在默认缩放级别下看到整个图像时,他们会看到所有的 22k*18k 像素。他们的屏幕分辨率低得多,所以分辨率低得多的快照就足够了。更高分辨率的图像只有在越来越放大时才有意义。但是当这种情况发生时,他们只能看到图像越来越小的部分,所以把不可见的部分也发给他们是没有用的。
这意味着我们需要一个解决方案
- 它可以捕捉当前的视窗和缩放级别,
- 向浏览器发送仅覆盖可见区域的图像
- 并以合理的分辨率完成。
这听起来像是一个企业级的功能,我们将花费余生来开发。除了 Holoviews 与 Datashader 模块集成,它们一起可以精确地完成这一点。
(快速补充说明:Datashader 是一个不可思议的工具,可以从不可显示的大数据集生成可显示的图像。它可以让您显示数十亿个独立的坐标,或者在我们的例子中,显示否则无法使用的网格数据集。如果你打算使用全息视图,你绝对应该 查看数据。)
集成 Datashader 以动态显示高分辨率图像
让我们再重复一遍我们上面所做的,但最后是大数据集。
请注意,这一次我没有显示结果图像。原因在于,根据具体的数据集,要么 Colab 内核会完全崩溃,要么整个进程会一直挂起,直到您的浏览器因内存不足而崩溃。
我们没有将这个hv.Image()
直接与地图结合,而是首先基于它创建一个动态图像。我们只需将它放入 Holoviews 提供的 Datashader 函数中,并将其导入为 hd.regrid。然后,我们将这个新创建的动态图像与地图结合起来。
如果我们缩放和平移,我们可以看到令人惊叹的事情正在发生,我们自动获得更高分辨率的图像。但与此同时,图片会被奇怪地重新着色,而且颜色条每次都在变化。这些事情以前没有发生过。
对此的解释是,之前,我们的小部件使用了整个低分辨率数据集,因此它知道最低值和最高值。由此,它可以创建一个强大的色标,覆盖从大约 0 到大约 5000 米的整个数据光谱。这意味着无论我们在哪里缩放,相同的值总是得到相同的颜色。
然而,我们新的动态系统的显示部分只接收数据集的可见部分。每次用户交互时,小部件都必须使用当前可见的最低和最高值来拉伸它们之间的色阶。如果我们放大到最高值只有几百米的平坦区域,该区域现在将被着色为保留给最高值的颜色(白色),棕色、黄色和绿色覆盖其下的值。相反,如果我们放大到一个山顶,即使是最低的可见点也在 2000 米以上,那么从这个高度开始,它将被染成绿色、黄色、棕色和白色。这就是为什么图像每次都被重新着色,为什么颜色条显示新的值。这实际上在很多情况下可能是一个有价值的功能,因为它使我们关注的任何小细节突出出来,真正突出了微小的差异,但如果你想有一个一致的图像,这真的很糟糕。
修复彩条
为了解决这个问题,我们将查询数据集并获得整个数据集的实际最小值/最大值。
**(-20.0, 5035.0)**
接下来,我们将颜色范围限制更新为这些值。我们不更新默认选项,因为此信息与此数据密切相关。如果我们想在它旁边显示一些其他的数据集,使用这些限制也是不合理的。相反,我们将它们直接应用于我们的组合元素。
从这里去哪里?
希望你的客户/老板接受 Colab 笔记本作为最终结果。否则,您将需要一种更专业的方式来发布您的小部件。我们将在未来可能的文章中探讨这个主题。
现在,让我举一个简单的例子,说明我们已经发现的一些特性是如何在一个交互式小部件中呈现的。
您可以查看 Colab 笔记本中的实例:
可能的替代方案
无论你不想或不能使用上述解决方案的原因是什么,我知道有一些替代方案,它们都有一些警告。
地理视图
在同一个 Holoviz 生态系统中,有一个模块专门用于处理这类任务: Geoviews 。假设hv.element.tiles.OSM() * hd.regrid(gv.util.load_tiff('your/file.tif'))
行本身给你的结果与上面的整个代码相同(尽管我没有测试它),所以选择它看起来是显而易见的。
然而,它有两个问题。一个是严重缺乏该特性的文档,因为甚至他们自己的网站也只在一个发布说明中提到过一次“load_function”字符串。另一个是 Geoviews 能够在后台做许多令人惊叹的 GIS 魔术,因为它严重依赖于许多科学 GIS 模块。其中一些需要手动安装,这需要花费大量的时间、精力和经验。喜欢,很多。我做过几次,都获得了不同程度的成功,但这真的很痛苦。例如,基于论坛,我甚至不确定是否有可能在 Google Colabs 上设置它。如果你有一个轻松的 Linux 服务器,你只需要设置一次,并且你可以长时间使用同一个环境,那么它可能是你最好的解决方案。如果你像我一样,通过本地 Windows 开发从 Colab 跳到任何 Linux 服务器或无服务器产品,你最好不要依赖于你不确定下次还能不能安装的东西。
开发者知道这个问题。希望他们能够重构和重写模块,只需要较少麻烦的依赖,因为它是一个非常天才的工具。但目前来看,事实就是如此。
破折号+情节
破折号和情节上有点类似于商业领域,就像 Holoviz 生态系统对于科学领域一样。可以说,他们的学习曲线不太陡峭,文档也更全面。
然而,他们没有像我们一样将网格数据集放到地图上的方法,至少在写这篇文章的时候没有。这是一个遗憾,因为他们有各种网格表示的图表(如 px.imshow,go.image,go.heatmap),他们对基于图块的地图有很好的支持,但没有办法将它们结合起来。这是可悲的,因为他们甚至有全息视图和数据阴影集成。因此,你可以做的最好的事情是遵循他们的 Plotly+Datashader 教程之一,这将允许你把一个视觉图像(即使是由 Datashader 创建的)放到地图上,但你不会访问数据本身,所以没有悬停工具提示。如果您不需要访问数据,而您需要将您的项目集成到 Dash 应用程序中,这可能是一个合理的折衷方案。
顺便说一下:如果在任何时候 Plotly 将能够在地图上显示网格数据,由于 Holoviews-Plotly 的集成,我们上面的整个练习只需要一个改变:hv.extension('plotly', logo=False)
,然后一切都应该显示在 Plotly 中,准备在 Dash 中显示。手指交叉。
dash-传单+陶土(或任何其他方式来创建和展示瓷砖)
你可以做的另一件事是将你的 GeoTIFF 转换成这些地图已经使用的相似 tileset,并将那个 tileset 放在原始地图的上面。Mapbox.com 切片服务是实现这一点的一种方式,但还有一个名为 Terracotta 的 python 模块,它可以获取您的地理信息,创建地图切片,并按需作为服务器提供服务。从那里你只需要一张可以使用它们的地图。
有很多选择,但Dash-leaf是最好的选择之一。这是流行的 LeafletJS 映射库的 Python 实现,移植它的人甚至有一个terra cotta-Dash-Leaflet demo来展示完全相同的东西,所以很容易安装和运行。它与 Plotly+Dash 解决方案具有相同的缺点。由于浏览器只接收来自服务器的可视图像,所以它没有办法在悬停工具提示上显示数据。然而,它有一个聪明的解决办法,在鼠标点击时,它可以向服务器发送请求,并在该位置获得准确的数据。它不像工具提示那样无缝,但是如果您只需要在很少的情况下查看数据,这可能是一个很好的解决方案。此外,如果您有任何问题,Dash 传单库的创建者 Emil 在 Dash 论坛上提供了一些企业级支持。
但是正如我提到的,有多种方法可以创建和提供 tilesets,如果你有,你可以使用几乎任何图表库,可以显示基于 tile 的地图,所以你这样做。
你知道其他方法吗?请在评论中告诉我!
承认
感谢菲利普·鲁迪格、布莱恩·范·德·Ven、詹姆斯·a·贝德纳尔和马克·斯科夫·麦德森,不仅仅是因为他们对 Holoviz 生态系统的贡献,也因为他们回答了我不断提出的问题。
还要感谢谷歌的@blois 立即调查了 Colab 和 Holoviews 在交互功能方面的一些不兼容问题。
在 Airflow UI 中显示 AWS ECS Fargate 日志
在气流任务中正确设置 Cloudwatch 日志组和流前缀
照片由 Unsplash 上的 Aditya Saxena 拍摄
在我从事的一个项目中,我使用 AWS 弹性容器服务来运行 Fargate 任务。为什么?我有需要运行的作业,所以很容易打包到 Docker 容器中,并部署在 Fargate 上,只需为使用的资源付费。我用气流来安排这些任务。因为 Airflow 在 Fargate 上运行任务,所以所有的作业日志都是在 Fargate 任务(Docker 容器)中创建的。幸运的是,Fargate 支持将日志流式传输到 CloudWatch,但如果您有十几行日志,在 CloudWatch 中浏览就不是超级友好的了(例如在线阅读和分页)。幸运的是,Airflow ECS Operator 支持在 Airflow UI 中显示来自 Fargate 任务的日志,这对我来说更好也更有用,因为我把所有东西都放在一个地方,我不必去 CloudWatch 调查。
我在配置 Airflow/Fargate 来显示日志时遇到了问题,所以我想描述一下我的方法和我的理解。通常,当显示来自 CloudWatch 的日志出现问题时,气流日志中会出现异常:
An error occurred (ResourceNotFoundException) when calling the GetLogEvents operation: The specified log group does not exist.
并且气流任务被标记为失败。我的问题是正确设置日志配置,以便 Airflow 可以读取它(这可能是由于缺乏 CloudWatch 日志如何工作的知识造成的)
1.设置 Fargate 任务
当设置 Fargate 任务和容器部分时,有日志配置选项,我选中“自动配置 CloudWatch 日志”,然后一切都是预定义的,当然,也可以设置自定义值。
使用这些设置,云观察中的日志组会随后自动创建。
在我的例子中,容器名和 Fargate 任务名都是“fargate_logging”。
2.设置气流任务。
在 Airflow ECSOperator 中,我用这些值设置日志:
awslogs_group 是 "/ecs/fargate_logging"
awslogs_stream 为“ECS/fargate _ logging”(开头不带“/”)。
任务可能如下所示(非完整配置):
task = ECSOperator(
dag=dag,
task_id=task_id,
task_definition=task_definition,
cluster=cluster,
region_name=region_name,
launch_type=launch_type,
overrides = {},
awslogs_group='/ecs/fargate_logging',
awslogs_stream_prefix='ecs/fargate_logging'
)
3.工作原理(引擎盖下):
在 CloudWatch 中检查是否创建了日志组,以及那里是否确实有日志:
我们看到日志流(名称)由“ECS/fargate _ logging”+某个数字组成
Fargate 任务执行后,Airflow ECS 操作员正在读取 CluodWatch 日志和流作为 Airflow 日志,这是核心部分。我们看到它正在解析 task_id ,并与 awslogs_stream_prefix 一起将 stream_name 放在一起。Task_id 是执行的 Fargate 任务的 id。
self.log.info('ECS Task logs output:')
task_id = self.arn.split("/")[-1]
stream_name = "{}/{}".format(self.awslogs_stream_prefix, task_id)
self.log.info("aws log group {}".format(self.awslogs_group))
self.log.info("stream name: {}".format(stream_name))
self.log.info("task_id: {}".format(task_id))
for event in self.get_logs_hook().get_log_events(self.awslogs_group, stream_name):
dt = datetime.fromtimestamp(event['timestamp'] / 1000.0)
self.log.info("[{}] {}".format(dt.isoformat(), event['message']))
我添加了日志行,以查看在我设置值后什么会作为 stream_name。这是我在气流日志界面上看到的:
[2021-03-31 07:23:53,773] {ecs_operator.py:172} INFO - ECS Task logs output:
[2021-03-31 07:23:53,774] {ecs_operator.py:175} INFO - aws log group /ecs/fargate_logging
[2021-03-31 07:23:53,774] {ecs_operator.py:176} INFO - stream name: ecs/fargate_logging/571504e3bf504615b39d2fdbea15a987
[2021-03-31 07:23:53,774] {ecs_operator.py:177} INFO - task_id: 571504e3bf504615b39d2fdbea15a987
重点是 awslog_group 和 stream_name 和 Cloud Watch 里的相匹配。
例如,在 CloudWatch 中,我们有一个名为 ecs/fargate_logging/571504 E3 BF 504615 b 39 D2 FD bea 15 a 987 的日志流, awslogs_stream_prefix 是“ECS/fargate _ logging”。
我在调试时使用了下面的代码片段来直接读取日志:
import boto3
client = boto3.client('logs')
response = client.get_log_events(
logGroupName='/ecs/fargate_logging',
logStreamName='ecs/fargate_logging/571504e3bf504615b39d2fdbea15a987'
)
print(response)
同样,在配置 Airflow 时,ECS Operator awslog_group 和 awslogs_stream_prefix 必须与 CloudWatch 中的内容相匹配(而不是相反)!!!
在 Airflow 中,您不需要设置您想要的值,而是在 CloudWatch 中设置它们,以便您可以正确读取它们。当我最初设置气流(对 ECS 没有太多经验)时,我认为我是在为日志设置它应该如何配置(它们将如何编写),但正如我已经强调的那样,情况正好相反。
我希望这将帮助那些有类似问题的人。
使用自定义地图切片显示地理信息
实践教程
了解如何为交互式地图创建自定义切片
交互式地图现在是我们日常数字生活的主要部分。我们用它们来了解我们的行踪,计划下一次旅行,或者回顾我们过去的旅行。在专业环境中,地图成为各种商业规划、运营和分析的无价工具。
一个交互式地图显示了一个拼凑的方块,每个方块包含完整图像的一小部分。基于云的服务通过从缓存中检索或动态生成来提供这些切片。地图软件管理显示,就像显示连续的位图,但是在后台以图块为基础操作。无论何时需要,地图软件都会向服务请求新的切片,并在不再需要时将其丢弃。当用户更改缩放级别或向任何方向平移地图时,会出现这种情况。
放大或缩小时,地图软件会在检索新级别的图块时拉伸或压缩当前显示。然后,它将新的瓷砖覆盖在旧的瓷砖上,提供了令人愉快的视觉连续性。
没有什么比一个现场地图示例更能帮助你建立对幕后发生的事情的直觉。请点击前面的链接并选择“显示磁贴边框”选项。浏览地图,查看软件如何处理切片,如何拉伸切片,以及如何替换和刷新内容。将光标悬停在每个单幅图块上时,软件会显示该单幅图块的坐标。
平铺坐标
我们可以将整个 tileset 设想为一个二维空间,其中每个 tile 接收一组唯一的整数坐标,用于任何缩放级别。第一个缩放级别包含整个可绘制的地球表面,并将其放入一个单幅图块。每个后续缩放级别将前一个级别的平铺分割为四个,从而使细节和整数坐标的范围加倍。在第一级缩放时, x 和 y 坐标的范围在 0 到 1 之间,而在第二级缩放时,它们在 0 到 3 之间变化。此过程重复进行,直到达到支持的最大缩放级别,通常在 18 到 23 之间。我们可以说元组( x , y , zoom )唯一地标识一个图块。
上图显示了缩放级别 3 的单幅图块坐标范围。(图片来源:微软— 必应地图磁贴系统)
像传单和它的 Python 包装器叶子这样的交互式地图软件在发布服务器请求时使用这种类型的图块寻址。服务器将这些坐标转换成图块的地理空间形状,即纬度和经度空间中的正方形,并使用这些来查询底层数据。通过这些数据,服务器渲染切片的图形内容,并将其发送回地图软件客户端。
四键重访
我们也可以通过其对应的四键来寻址每个图块。四键将图块唯一编码为单个字符串或数字,便于缓存或字典键。根据整数编码方案,我们可以包含缩放级别或将其作为上下文信息。四键非常方便,因为我们可以很容易地从瓷砖坐标计算它们。我们将在这里使用它们来编码本地切片缓存的文件名,作为数据库关键字,并作为代数的基础,这将减轻大量的计算。
上图说明了如何根据位置和细节层次导出四键代码。请注意第 3 级图块坐标如何映射到其对应的四键。(图片来源:微软— 必应地图磁贴系统)
我们可以使用字符串或 64 位整数对四键进行编码。字符串编码在每个缩放级别使用一个字符,可以是零个、一个、两个或三个字符,并且具有将缩放级别保持为字符串长度的优点。它最大的缺点是存储每个四键所需的存储空间。
幸运的是,很容易将字符串编码压缩成更易于管理的东西,一个 64 位整数。一旦我们认识到四键的字符串表示只不过是一个基数为 4 的数,转换就相对容易了。最直接的转换是将字符串编码成整数,但会丢失缩放级别信息。我们必须以某种方式,隐式或显式地将缩放级别信息存储在其他地方。或者,我们可以使用整个 64 位来编码键和缩放级别信息,但是这个解决方案只将我们限制在 23 个级别。在本文中,我使用前一种编码,因为缩放级别总是可以从上下文中获得,并且它支持对所需的 26 个缩放级别进行编码。
上述函数在字符串和整数表示之间转换四键编码。注意,到字符串的转换需要期望的缩放级别,并且整数表示不包括缩放级别。(图片来源:作者)
以上图为例,右下角图块的字符串编码“333”对整数 63 进行编码。但是四键编码并不像看上去那么简单。使用字符串或整数编码,我们可以立即导出封闭缩放级别的平铺键。对于字符串编码,我们删除最右边的字符,而对于整数编码,我们执行整数除以 4。当处理代表图块的 256x256 位图时,该属性具有令人兴奋的含义——每个图块像素是另一个向下缩放八个级别的图块,这将为我们创造奇迹。
地图覆盖
交互式地图通过叠加地理信息来实现其用途。交互式地图软件通常允许叠加两种不同类型的数据:矢量和位图。在这里,我们关注一种特殊的位图数据,平铺覆盖。切片叠加的工作方式与底图切片相同,为每个底图切片提供一个地理内容切片。
每个拼贴是一个正方形位图,与地图拼贴具有相同的尺寸,并使用 alpha 合成来显示底层地图信息。这样,叠加图创建者可以只绘制要在正确的地理位置显示的数据,而不关心底层地图是如何显示的,这就是我们在本文中遵循的方法。
为了说明这种技术,让我们从 OpenStreetMap 服务器中挑选一个与我们的研究区域(美国密歇根州的安阿伯市)相匹配的图块。
上图显示了坐标为 4381,6067,14 的 OpenStreetMap 图块。这就是前面提到的底图。(图片来源:OpenStreetMap)
使用基础切片的坐标,我们现在可以提取相应的地理数据并生成覆盖切片。在这种情况下,我们计算每个采样位置的二元正态分布,并将其添加到地图中。密度较高的区域将以绿色显示。
上面的图块覆盖对应于之前的底图图块。白色区域是透明的。每个点代表一次 GPS 观测,绿色区域对应更高的密度。(图片来源:作者)
通过使用 alpha 合成合成上面的两幅图像,我们得到了最终的位图(见下图)。请注意,地图软件会自动处理这一过程。
通过组合前面的两个图块,我们得到了上面的图像。(图片来源:作者)
您在覆盖图块中看到的每个点实际上是一个使用二元正态分布生成的圆。分布的中点是位置,我们考虑一个单像素标准差的对角协方差矩阵。因此,位置扩展到以下强度矩阵。最终图像是所有位置相加的总和。
上面的矩阵显示了如何使用二元正态分布将单个位置转换为“密度圆”。将所有位置加在一起后,呈现代码将根据缩放级别范围为每个单元格分配一种颜色。(图片来源:作者)
为了避免二元正态分布固有的无限大小,当每个单元格的值低于 0.00001 时,我决定切断表示。空单元格反映了这种情况。
瓦片生成
切片生成是一个分三步的过程。第一步包括数据收集和转换成适合快速查询和检索的格式。服务器使用准备好的数据绘制图块位图,并将其存储在文件缓存中,以便在第二步中重用。本文展示了第二步的一个惰性版本,其中服务器软件按需生成并缓存切片。如果切片存在于缓存中,服务器会立即将其传送给客户端。在此过程中,服务器代码可以将缓存的切片生成日期与当前日期进行比较,并确定是否需要刷新。该过程将保持图块数据是最新的。流程的最后一步是将瓷砖交付给客户。在这里,我用一个简单的基于 Flask 的 API 来说明这个过程。
在我们开始探索瓷砖一代的三步流程之前,我们必须了解最终产品。正如您在上面看到的,我们的目标是显示一组道路的交通密度信息,因此我将每个位置转换为正态二元分布,并将它们相加以生成一个颜色编码的密度图。
图块数据收集
我们的图块的源数据是一长串地理空间位置,编码为纬度和经度对。对于本文,我使用的是我已经在探索一段时间的车辆能源数据集数据。
正如我之前所说的,交互式地图软件一次请求一个图块,然后将它们粘贴在一起以创建最终的地图或覆盖图。服务软件在检索图块数据时需要速度很快,以改善用户体验。根据缩放级别,可能需要收集大量信息,因为缩放级别越低,切片包含的信息越多。我解决这一挑战的方法是预先计算所有图块,并存储每个支持的缩放级别的数据。四键在这里是救命稻草。
正如我们之前看到的,每个瓷砖都可以通过一个四键代码进行唯一寻址。每个拼贴由一个 256x256 像素的位图组成,这意味着我们可以将每个像素作为另一个四键代码进行寻址,更深入八个缩放级别(256 = 2⁸).这种洞察力允许对切片数据进行高效编码,以便在生成阶段进行检索。
我对这个问题的解决方案是使用一个 SQLite 数据库,通过每个缩放级别使用一个表,将所有图块数据按像素聚合。每个表的结构都很简单,只有三列。下面我展示了最低缩放级别的 SQL 表创建脚本。请注意,通过在缩放级别 26 聚合地理数据,我们只能绘制最高级别 18 的切片。
上面的 SQL 脚本创建了包含最详细缩放级别数据的表。所有其他缩放级别都是通过简单的聚合从这个缩放级别派生出来的。(图片来源:作者)
第一列包含缩放级别为 26 的单个像素的 64 位编码四键代码。因此,我们可以非常快速地将该值转换为图块中的像素坐标,并根据计算的亮度值(表的第三列)绘制像素。
第二列对封闭图块进行编码,也是 64 位四键代码。我们为该列创建了一个非唯一索引,以使图块数据检索非常快,并将其计算为像素四键代码除以 256,即 2⁸.
当整个第 26 级计算完成后,我们可以通过简单的聚合立即得到第 25 级,如下面的 SQL 脚本所示。
上面的脚本通过将像素和平铺四键代码除以 4 并对强度值求和,从 26 级数据生成 25 级数据。请注意,它假设 25 级表与 26 级表具有相同的结构。(图片来源:作者)
为了计算所有的缩放级别,我们需要对最高级别重复这个过程,在我们的例子中是 8 级。
上面的代码基于级别 26 生成从 25 到 8 的所有级别。(图片来源:作者)
我们现在可以执行数据准备的最后一步,即计算每个缩放级别的亮度范围。此信息对于绘制平铺位图时的着色过程至关重要,因为值范围映射到预定义的颜色渐变。
上面的函数遍历缩放级别来计算每个级别的范围。它将结果存储在一个表中,以便将来渲染图块。缩放级别范围对于根据单个像素强度分配像素颜色非常有用。(图片来源:作者)
现在,数据已经完全准备好了,我们可以继续进行切片生成和服务流程描述。
瓷砖生成和供应
对于本文,我设计了一个简单的基于 Flask 的 API 来服务 tile 文件。API 端点接收图块坐标作为参数,并返回相应的图块 PNG 文件。这里我使用一个通用函数来完成所有繁重的工作。
上面的清单显示了渲染密度切片的 Flask API 入口点。(图片来源:作者)
作为参数,该函数接受切片坐标、包含切片数据的 SQLite 数据库的路径以及文件缓存文件夹的路径。该过程首先将缩放级别限制在可接受的范围内,在 1 到 18 之间。超过这些限制,它只会返回默认的空(完全透明)图块。
上面的清单显示了通用的图块生成函数。如上所述,它使用结构化数据生成切片。请注意,切片的数据内容可能会根据您的需求而有所不同。您需要提供适当创建的数据库和用于缓存的文件夹。(图片来源:作者)
对于适当的缩放级别,该函数使用图块四键代码计算目标图块文件名,如果该文件已经存在于缓存中,则立即提供该文件。对于不存在的文件,该函数必须在提供文件之前渲染并保存文件。
生成新图块的过程从建立到包含缩放级别数据的数据库的连接开始。然后,它在数据库中查询所有图块的像素强度。如果图块为空,该函数将提供默认的透明图块。
该代码使用单个像素坐标的元组列表作为四键代码,以及它们各自的强度来用数据表示图块。然后,这些列表必须转换为基于图块的像素坐标,这意味着每个图块的左上角具有(0,0)坐标。然后,该功能收集手边缩放级别的亮度范围信息。有了这些信息,我们现在可以绘制瓷砖了。
用 NumPy 和 PyPNG 绘制瓷砖
在研究从 Python 代码创建 PNG 文件时,我遇到了一个优雅的包: PyPNG 。这个包可以将 NumPy 数组转换成 PNG 文件,这似乎是一个好主意。下面是如何创建一个表示 256x256 RGBA 图像的 NumPy 数组,可编码为 PNG:
上面的函数创建了一个 256x256x4 的 NumPy 数组,用给定的红色、绿色、蓝色和 alpha 通道初始化。完全透明的拼贴将所有这些值都设置为零。(图片来源:作者)
绘制图块只是简单地将各个像素值设置为适当的颜色。下一个函数使用像素列表、颜色渐变和合适的缩放范围来绘制图块。
上面的函数生成了一个 NumPy 数组,其中包含了图块内容的位图表示。(图片来源:作者)
渐变列表中的每个颜色值都是一个四维 NumPy 向量,每个通道组件一个,因此设置像素是一个简单的任务。生成渐变列表的函数也将 alpha 通道值设置为 50%。
上面的函数创建了一个颜色渐变,并将其作为 NumPy 数组返回。(图片来源:作者)
使用 PyPNG 包将切片保存为 PNG 格式的文件非常简单。下面的代码演示了这个过程。请注意保存前所需的数组整形。
上面的函数将 NumPy 数组保存为 PNG 位图。(图片来源:作者)
最后,API 可以通过创建一个响应对象来服务图块文件。
API 入口点通过在图块文件周围创建一个响应对象来结束,无论是数据图块还是默认的空图块。(图片来源:作者)
使用代码
要使用这段代码,首先将 GitHub 库克隆到您的本地机器上。第一步是执行前两个编号的 Jupyter 笔记本来创建支持数据库。这将从分布数据集中读取数据,并将其导入本地 SQLite 数据库。
接下来,您必须创建并填充支持 SQLite tile 数据库。您可以通过运行以下脚本来实现这一点:
python generate_densities.py
请注意,在 VED 数据上运行该脚本可能需要很长时间。预计总运行时间超过一小时。完成后,您可以使用以下脚本启动图块 API:
python tileapi.py
该命令启动一个监听端口 2310 的 Flask 服务器。要查看 tile 服务器的运行情况,请运行 Jupyter 十号笔记本。你会看到一张以密歇根州安港为中心的地图。如果一切顺利,您应该开始看到地图上的瓦片被渲染。平移和缩放时,切片服务器将生成、缓存和提供适当的切片。
结论
在本文中,我们探讨了交互式地图切片的概念以及如何生成它们来动态传达自定义地理信息。交互式绘图软件使用正方形位图平铺来构建整个地图。为了使这些地图有用,我们可以叠加矢量或光栅图像来传达地理参考信息。叠加地图切片显示此类信息方便快捷,但通常需要一些冗长的预处理。本文向您介绍了交付一个定制解决方案的步骤,您可以进一步调整该解决方案。
资源
joo Paulo Figueira 在葡萄牙里斯本的TB . LX by Daimler Trucks and bus担任数据科学家