TowardsDataScience-博客中文翻译-2022-三十九-

TowardsDataScience 博客中文翻译 2022(三十九)

原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

OpenAI Whisper 掌握着 GPT 4 号的钥匙

原文:https://towardsdatascience.com/openai-whisper-holds-the-key-to-gpt-4-a7f922a7dad9

以及使其成为最佳 ASR 模型的 8 个关键特征(嘿,Siri,这是给你的)

低语的嘴唇。信用:作者通过中途+稳定扩散

以下节选自The algorithm Bridge,这是一份教育通讯,其目的是在算法和人之间架起一座桥梁。它将帮助你理解人工智能对你生活的影响,并开发工具来更好地导航未来。

https://thealgorithmicbridge.substack.com/subscribe?

今天,我将覆盖一个我从未想过会写的人工智能子领域——主要是因为它比我通常写的领域(大型语言模型、人工智能艺术)要成熟得多,而且看不到突破。但是我错了。我指的是自动语音识别(ASR),你肯定已经从标题中推断出来了…请耐心听我说,因为有很好的理由阅读这条新闻。

OpenAI 几天前发布了 Whisper,人们已经开始疯狂了。不是因为这是一个新概念,也不是因为算法设计的改进。不,原因更简单:Whisper 比任何其他商业 ASR 系统都更好。Alexa,Siri,Google Assistant(这些是你可能很熟悉的),在你尝试了 Whisper 之后,它们中的任何一个都会感觉像是上世纪的技术。你可以的。OpenAI 是一家名不副实的公司,它决定开源这个模型。对许多人来说,数字体验将会彻底改变。

让我们看看这项技术是否能引起轰动。

耳语的简要概述:它是什么和做什么

OpenAI 将 Whisper 描述为一个通用的端到端弱监督的基于变压器的 ASR 系列模型。让我们用更简单的话来看看这些行话是什么意思。

  • “通用”是指除了语音识别的核心任务之外,Whisper 还可以完成所有的外围任务,像语音检测、语言识别、转录和机器翻译。
  • “端到端”意味着模型在没有人工干预或手工制作模块的情况下进行无缝训练——这与单独训练各部分(用于外围任务)然后合并它们形成对比。
  • “弱”意味着接受音频数据的阈值比通常低(较低质量的数据可以提高模型的泛化能力)。
  • “监督的”意味着它是在音频-文本对上训练的,这与以前的自我监督的模型相反(改变的原因在数据集中。稍后将详细介绍)。
  • “基于 Transformer”你已经知道:它通过关注前面的元素来预测序列中的下一个元素。作为补充说明,OpenAI 的研究人员选择了原始的 transformer 架构,而不是更花哨的现代版本,因为他们不想通过模型改进来让 Whisper 变得很棒。他们想证明,如果有足够的数据可用,高质量的监督 ASR 是可能的。

“由于我们工作的重点是研究语音识别的大规模监督预训练的能力,我们使用现成的架构来避免我们的发现与模型改进混淆。”

  • 而且是“一家人”因为 Whisper 有五个版本。按照参数从最小到最大:微型(39M)、基本型(74B)、小型(244B)、中型(769M)、大型(1.55B)。你可能会惊讶地发现,与大型语言模型(LLM)如 GPT-3 (175B)或 LaMDA (137B)相比,它们很小。与其他人工智能应用相比,制作一个足够好的生成式语言模型是一个更高层次的追求。从现在开始,当我提到 Whisper 时,它将是 Whisper large(模型大小关系到质量),除非另有说明。

在这里阅读更多:博客(可用演示)论文GitHub 回购 ( 模型卡Colab 示例,以及代码)。

好吧,这是文章中最专业的部分。你不需要记住任何细节就能理解接下来的部分。我现在想关注的是,是什么让 Whisper 与你和整个 AI 社区相关。我已经阅读了我上面链接的所有资源,并编制了一个我认为是 Whisper 的八个关键特性的列表(包括研究和生产层面)。

用 8 个键轻声细语:什么让它与众不同

1.耳语准确度超高

即使在 ASR 基准测试中,Whisper 并不总是最先进的(SOTA ),但它比其他替代品更符合现实世界中的人类表现。来自报纸:

“这些结果表明,Whisper 的英语 ASR 性能并不完美,但非常接近人类水平的准确度。”

这意味着你会发现 Whisper 比任何其他 ASR 系统都更好地理解你(正如我所说,如果条件不是最佳的,Alexa 或 Siri 会明显更差)。这推断到真实世界的场景(例如,有背景噪声、快速语音或重口音。稍后将详细介绍)。试用过 OpenAI 系统的非 open ai 成员表示,他们对该系统的质量印象深刻。例如, Andrej Karpathy (特斯拉前人工智能总监)今天早些时候在推特上写道:

安德烈·卡帕西的推特

2.Whisper 是开源的

麻省理工学院许可下的 OpenAI 开源 Whisper。人工智能公司中有一个明显的趋势,即提供模型和代码:OPT,BLOOM,Stable Diffusion……这种做法提高了人工智能工具的民主化,同样重要的是,允许更好地评估它们如何工作,它们在哪里失败,以及如何改进它们。

我之前曾说过开源是人工智能的前进之路,当考虑到安全时,它会比严格控制和隐私下的人工智能产生更大的影响。像谷歌这样在这些类型的实践中落后的公司(开放二级技术与开放最好的技术是非常不同的事情),如果他们保持封闭,将很难利用各种可能性(DALL E mini 对 DALL E 的成功是每个认为高质量>开放的人工智能公司的一个教训)。

3.耳语是容易使用的

这与它是开源的密切相关。拥抱脸已经为制作了一个可以在电脑或智能手机上使用的演示。数据科学家 Yuvi Sharma 使用 Whisper(语音到文本)、BLOOM(文本生成)和 CoquiTTS(文本到语音)构建了一个语音-文本-语音系统。你说话,Whisper 识别并转录你的语音,BLOOM 根据你的要求生成文本,Coqui 从中制作音频文件。一切都在不到 2 分钟内完成。

这让我想起了这个月稳定扩散所发生的事情——开发者不停地发布工具和资源。幸运的是,大多数都是无代码的。这是开源政策的结果。为所有人提供更多无障碍环境。

对于程序员来说:你可以随时下载 Whisper 到你的电脑上,然后玩代码。最大的型号需要 10GB 的 VRAM,所以不是每个人都适合。最小的需要 1GB VRAM,速度快 32 倍,但质量明显较差。中间有三个版本。

4.耳语是多语言的

还有一种趋势是让人工智能更具包容性。英语世界很大,但 94%的人口不以英语为第一语言,75%的人根本不说英语。直到现在,数以亿计的人们的母语已经被遗忘了。BLOOM 是一个 LLM,支持多达 46 种语言,并率先将多语言作为人工智能模型的核心特征。Meta AI 在 6 月发布了项目没有语言落后(NLLB) 。它旨在开源可以在 200 种语言之间进行机器翻译的人工智能模型。OpenAI 用 97 种语言训练了 Whisper。其他 96 种语言占数据集的 17%。

这仍然是不够的——说那些在数据中没有得到很好体现的语言的人将会体验到质量的降低。在互联网上广泛使用的语言之间存在几个数量级的差距,如中文(在 Whisper 的数据集上为 23.5K 小时)或西班牙语(11.1K 小时),以及其他不像印地语(12 小时)或孟加拉语(1.3 小时)的语言,尽管有数百万人在使用。不太常用的语言,尽管在数据中出现的次数相对较少,但仍是世界上许多人的第一语言——这些人将从这类技术进步中受益最多。

5.Whisper 可以增强许多跨行业的应用程序

关于现实世界的使用,想象一下你有一个涡轮增压的 Siri 或 Alexa,不是在智力方面,而是在完美地转录你的演讲方面。你可以用声音做学习笔记,而不必经常检查。你可以在 YouTube 视频或电影中享受即时准确的字幕。而且公司肯定会把 Whisper 嵌入到现有的服务中。无论噪音、口音或语音节奏如何(如果亚马逊聪明的话),Alexa 都不会错过你说的话。

更重要的是,耳语可能意味着有语言或听力障碍的人生活质量的提高。例如,你可以立即为一个聋人抄写你的话。这可能是消费者服务包容性的提高。所有这些都没有提到专业工作的用途:法律、医疗保健、银行、市场营销、教育……几乎每个行业都可以从中受益,使用案例数不胜数。

6.耳语是多任务的

正如开始所解释的,Whisper 不仅做 ASR,还做围绕核心问题的所有任务。例如,Whisper 也在 X→英语翻译(数据集的 18%)上被训练,并且在语言识别上被评估(类似于 SOTA 模型的性能)。总的来说,这使得 Whisper 更容易与现有产品或服务集成,也更容易部署。

7.Whisper 是健壮的,并且可以很好地推广

这和高精度点有关,但我觉得值得强调一下。现有 ASR 系统的主要限制之一是底层模型是在具有实验室类型质量的特定数据集(或它们的组合)上训练的。这意味着,当在目标基准上评估时,模型做得很好,但是即使稍微偏离也会导致质量的巨大下降。

Whisper 是以弱监督的方式对大量数据进行训练的,这使得它更加健壮,能够推广到非分布情况(简而言之,非分布意味着训练和测试数据具有不同的基本特征)。例如,Whisper 更擅长理解口音或快速语音,识别技术术语和标点符号,并且它比替代品更擅长检测静音或消除背景噪音。

8.Whisper 的训练数据集庞大、多样,而且是私有的

到目前为止,一切看起来都太完美了。如果你在期待一个“但是”的时刻,这就是了。耳语有黑暗的一面。对于那些从事技术工作的人来说,这个特性会立即脱颖而出。让我们从客观部分开始:Whisper 是在 68 万小时的音频-文本数据上训练的(那是 77 年!)从网上刮来的。这比此类数据的现有最大监督数据集高出一个数量级。

不知何故,这使得 Whisper 在音频-文本数据采集方面的突破超过了在算法设计或比例定律方面的突破。OpenAI 的研究人员证明了数据缩放对 ASR 系统的质量非常有影响。这使得数据集成为 Whisper 发布的一个关键方面。如果没有 OpenAI 收集的所有数据,Whisper 就不会存在。

现在,阴暗的部分:数据集是私有的。OpenAI 决定通过开放推理模型和代码来认可他们的名字,但决定保持数据集的封闭。为什么?我不知道。没有人知道这些数据是从哪里来的。他们在论文中解释说,机器生成的数据通常缺乏足够的质量,因此他们想出了一些启发式方法来消除它。但是,他们把它都移除了吗?如果不是来自人工智能来源,他们从哪里找到 10 倍人类标记的数据?OpenAI 似乎不愿意解决的许多公开问题。

这意味着开发人员和外行人仍然可以从 Whisper 中受益,但大学研究人员无法分析数据来源或内部的潜在偏见(这里有一个关于这个的好线索),其他科技公司也无法建立在 OpenAI 的研究成果上——或许可以构建和开源一个改进版本。

更有趣的问题是,他们为什么要关闭它。他们显然不打算从 Whisper(直接)获利。我会让你考虑这个问题,因为我认为有更多的事情要做。我没有答案,只有猜测。

所以,我们来推测一下。这就是乐趣的开始。

超越耳语:关于 OpenAI 未来的疯狂假设

OpenAI 为什么要把 Whisper 的训练集保密?我还没能找到任何知道答案的可靠来源。有很多可能的(无聊的)假设。例如,OpenAI 不希望数据集对竞争对手开放,因为他们计划以某种方式利用它获利。另一个选择是存在法律问题(网络抓取可能很棘手),他们更喜欢保持沉默。另一个选择是,这是他们的工作方式,他们认为现在没有理由改变它——尽管他们明显转向开放。

但是,有一种更令人兴奋的可能性——如果你问我的话,这种可能性相当合理。这个假设由米兰-魁北克人工智能研究所的 ML 博士生伊桑·卡瓦耶罗在推特上分享:

伊桑·卡瓦列罗的推特

好吧,让我解释一下。

首先,GPT-4。这是 GPT 家族的下一个版本。《GPT》于 2018 年出版。2019 年的 GPT 2 号。和 2020 年的 GPT 3 号。预计 GPT-4 将于 2021 年发布,但 OpenAI 专注于人工智能艺术(CLIP 和 DALL E)。现在经过两年的等待,发布日期感觉很近了。今年早些时候,在 4 月份,我写了一篇关于 GPT 4 号的文章,题为“GPT 4 号即将到来”。下面是我们对它的了解(如果你去谷歌搜索,输入“gpt-4”仍然是排名第一的结果,这让我很自豪!)无论如何,我当时预测 GPT 4 将在 7 月或 8 月发布。对人工智能艺术的突然兴趣可能让它慢了下来。我不能确定,但我愿意打赌,我们将在未来几个月内拥有 GPT-4——原因如下。

在同一篇文章中,我回顾了自 2020 年以来人工智能社区在 LLM 领域取得的最重大进展,并将它们与我们关于 GPT-4 的少量信息进行了匹配(OpenAI 的首席执行官萨姆·奥特曼在 2021 年底进行了一次私人问答)。我强调的一点是,GPT 4 号不会比 GPT 3 号大,但在计算资源方面会优化得多,性能能力也有显著的飞跃。为此,OpenAI 需要更多的数据。

这就是 DeepMind 的龙猫发挥作用的地方。DeepMind 发现,普遍接受的缩放定律(如何通过放大某些特征使模型更具性能;计算、数据或大小)不完整。让模型更大只是让它们更有性能的一半。

另一半呢?数据。DeepMind 的研究人员发现,如果他们使用更多的训练数据,同时保持参数数量固定,他们可以建立一个 LLM“计算优化”。他们用 Chinchilla 证明了他们的假设是正确的,这是一个 70B 参数模型,立即达到了 SOTA 状态,以显著的优势超过了 LaMDA (137B),GPT-3 (175B),J1-Jumbo (178B),Gopher (280B)和 MT-NLG (530B)。他们发现所有这些模特都训练不足。鉴于其体型,龙猫已经从训练数据中获得了最多的信息。

现在我们回到卡瓦列罗的假设:为了训练一个 GPT 3 大小的模型作为计算最优模型,OpenAI 需要的数据比 DeepMind 训练 70B 参数的龙猫所需的数据多得多。卡瓦列罗的猜测?OpenAI 将使用 Whisper 从互联网上转录语音数据,并生成他们缺乏的所有文本数据,以训练 GPT-4 成为计算机最佳。

为了构建一个计算优化的 GPT-4,OpenAI 需要比 GPT-3 多 10 倍的数据(从 300B 到大约 3.7T 令牌)。信用:龙猫纸业

早在 4 月份,我就预测 OpenAI 会摒弃旧的比例定律,将 DeepMind 的发现应用到 GPT 4 号上。如果卡瓦列罗的假设是真的,我的预测将被证明是正确的。

但是,可能吗?Caballero 帖子上的一位评论者估计,仅 YouTube 的音频数据就相当于大约 12 万亿代币。这足以训练一个计算优化的 GPT 3 大小的 GPT 4(约 3.7 吨令牌),甚至是 MT-NLG 或 PaLM 大小的模型(约 11 吨令牌)。当然,他们也需要更多的计算能力,但鉴于微软支持 OpenAI,他们不会有任何问题。

如果 OpenAI 成功了,我们能期待什么?正如热门科技博客 Robert Scoble 上个月在一条推特上说的,我们可以期待 GPT 4 号“就像 GPT 3 号一样是一次激动人心的飞跃”。精神错乱。”如果那最终是真的,我甚至无法想象这种可能性。而且,他含蓄地说GPT-4 已经训练过了。我对发布日期的预测是错误的,但可能不会差太远。

罗伯特·斯考伯的推特

订阅 算法桥 。弥合算法和人之间的鸿沟。关于与你生活相关的人工智能的时事通讯。

您也可以直接支持我在 Medium 上的工作,并通过使用我的推荐链接 这里 成为会员来获得无限制的访问权限! 😃

OpenAI 的 Amazing ChatGPT:对小众话题有前景吗?

原文:https://towardsdatascience.com/openais-amazing-chatgpt-is-it-promising-for-niche-topics-fcee2328dbd8

询问 ChatGPT 关于岩石物理学的问题

图片由作者 DALL-E 2 生成。

OpenAI 最近发布了他们最新的人工智能聊天机器人原型,由来自 GPT-3.5 系列的模型驱动。它提供了一种服务,你可以问问题,然后它会以对话的方式给出详细的答案。就像你在和一个人说话一样!

ChatGPT 是基于一个训练有素的模型,使用来自人类反馈的强化学习,允许它模拟对话,回答后续问题,甚至承认错误。

即使 OpenAI 的 ChatGPT 最近在互联网上掀起了风暴,但在处理小众话题时,它真的像看起来那么好吗?

在这篇文章中,我向 chatbot 5 提出了一些特定于我的岩石物理学科的问题,并根据我的领域知识评估了人工智能给出的回答。

提示 1 :用简单的术语解释岩石物理学

想法:

岩石物理学是石油和天然气行业中一个非常专业的领域。从本质上来说,这是一个我们试图了解岩石的物理性质、其流体含量以及这些流体如何在岩石中移动的角色。该职位主要负责从测井测量中识别石油和天然气。

有时这是一个很难向非地质学家解释的话题。

响应:

岩石物理学是地质学的一个分支,研究岩石的物理性质,尤其是岩石的物理性质与其传输流体(如石油、天然气和水)的能力之间的关系。简单地说,岩石物理学关注的是理解岩石的物理特性,以及这些特性如何影响流体在岩石中的流动。这对于依赖于从地下储层中提取石油和天然气的能力的各种行业(例如石油和天然气行业)来说是重要的。岩石物理学家使用各种技术,如钻探岩心、测井和地球物理测量,来测量和分析岩石的物理性质及其传输流体的能力。

评论和想法:

总的来说,描述是准确的。岩石物理学家的主要行业确实是石油和天然气,但也有其他领域,如学术界和研究相关的角色。

至于用简单的术语解释这个主题,对于非科学学科来说,它可能仍然太复杂,术语太多,尤其是像“测井”和“传输流体”这样的词。后者可以简化为“允许流体流动”。

回答中也有一点重复,最后一句的最后一部分重复了第一句的最后一部分。

我很乐意——稍加修改——用这个描述向其他人解释什么是岩石物理学。

提示 2 :解释伽马射线工具如何测量地层放射性?

想法:

理解测井工具如何工作、如何测量以及如何解释它们是岩石物理学家的关键角色之一。

这个提示是为了测试 ChatGPT 将如何回应一个技术问题,以及它是否会提供伽马射线测井工具如何工作的足够好的概述。

回应:

伽马射线工具是测井中使用的一种工具,用于测量地下地层的天然放射性。这些工具通过检测伽马辐射来工作,伽马辐射是由某些元素(如钾、钍和铀)发出的一种高能辐射。当伽马辐射从这些元件发出时,它可以被伽马射线工具检测到,并用于测量地层的放射性。

伽马射线工具通常由一个辐射探测器组成,如闪烁晶体或充气探测器,它连接到一根细长的电缆上。电缆被放入井中,当它穿过地层时,由岩石中的元素发出的伽马辐射被工具检测到。然后,该工具将这些信息发送到地面,用于创建伽马射线测井,这是地层放射性的图形表示。

地质学家和岩石物理学家使用伽马射线测井来了解地下的地质情况,并且可以提供关于地层的组成和性质的有价值的信息。这些信息对于各种行业都很重要,例如石油和天然气行业,这些行业依赖于从地下储层中提取石油和天然气的能力。通过使用伽马射线工具,岩石物理学家可以确定最有前景的钻井和开采区域,并开发出更高效和有效的石油和天然气开采方法。

评论和想法:

总的来说,这很好地解释了伽马射线工具的工作原理。它们确实被用来测量三种关键元素——钾、钍和铀——的天然放射性,这三种元素通常存在于岩石所含的矿物中。

当用电缆工具进行测井时,工具被连接在一起,并通过电缆下降到井底。这是缠绕在钻机和测量的地层。伽马射线工具确实包含闪烁晶体或气场管,但它被封装在金属工具主体内。“连接在一根细长电缆上的闪烁晶体或充气探测器”的响应在这个语境下可能会略有误解。

至于回应中的最后一句话,部分正确。伽马射线工具可用于识别潜在的储油层。较低的伽马射线读数表明潜在的清洁储层段。然而,单独使用伽马射线工具不能告诉我们地层中是否有碳氢化合物。为了充分描述地层及其流体的特征,我们需要额外的测井测量。

提示三:如何用阿尔奇公式计算含水饱和度?

想法:

岩石物理学领域中的一个基础方程是阿尔奇方程。该方程允许我们从测井测量和关键参数中推导含水饱和度(Sw)。简而言之,它用于确定多少孔隙空间(0-100%)被水填充,进而可用于确定存在多少碳氢化合物(1-Sw)。

阿尔奇方程需要许多关键参数,如地层内流体的电阻率(Rw)、孔隙的几何形状(弯曲度-a)和可用孔隙空间(φ-φ)。这些可以通过其他测量方法获得,也可以从岩心栓中获得。

回应:

阿尔奇方程是岩石物理学中广泛使用的方程,用于计算含水饱和度,即储集岩中存在的水量。该等式通常写为:

S_w = (a * R_w) / (φ * m)

其中:

S_w =水饱和度

a =胶结指数(取决于岩石类型和岩石润湿性的数值)

R_w =岩石中水的电阻率

φ =岩石的孔隙度(岩石体积中被孔隙占据的部分)

m =胶结系数(该值取决于岩石类型和孔隙中存在的流体类型)

要使用阿尔奇公式计算含水饱和度,您需要知道所有这些参数的值。一旦你有了这些值,你就可以把它们代入方程并求解 S_w,这将给出你的水饱和度。例如,如果 a = 2,R_w = 100 欧姆米,φ = 0.2,m = 4,则水饱和度计算如下:

S_w = (2 * 100) / (0.2 * 4) = 10

这意味着岩石的含水饱和度为 10%。

值得注意的是,阿尔奇方程是一个简化的模型,它假设了岩石和孔隙中存在的流体的某些条件。在实践中,实际的水饱和度可能不同于使用该方程计算的值,并且可能需要其他更复杂的模型来准确预测水饱和度。

评论和想法

这一次的回应在一定程度上是正确的。

它提供了岩石物理学家将在该方程中使用的几个关键参数的信息,然而,方程的形式是不正确的,并且缺少几个参数。

上面给出的表格:

S_w = (a * R_w) / (φ * m)

应该是:

阿奇含水饱和度方程。图片由作者提供。

两个缺失的参数 Rt 是地层的电阻率,n 是饱和指数。这两者在计算水饱和度时都是必不可少的。

ChatGPT 提供的等式顶行是正确的,但底部是错误的。孔隙度(φ)应提高到 m 的幂,而不是乘以 m。

此外,对于未经培训的人来说,示例中使用的值乍一看似乎是合理的。

Rw 值为 100 非常高,m 值为 4 超出了正常范围。通常情况下,m 约为 2,但根据岩石的胶结程度,可以在 1.7 和 3.0 之间变化。

此外,代表孔隙弯曲度的 a 参数被错误地标记为胶结指数,并在示例中被赋值为 2。该参数通常假定为 1,但是,它可以根据存在的岩石类型而变化。

总的来说,如果没有领域知识的人在现实世界中使用提供的等式,我会很担心。

提示 4:用 Python 代码展示阿奇方程

想法:

我听说 ChatGPT 也能够产生代码。所以作为一个 Python 爱好者,我必须尝试一下。继续上一个提示,我想看看如何用 Python 的形式写出给定的方程。

回应:

以下是如何用 Python 实现阿奇方程的示例:

# define the parameters
a = 2
rw = 100
phi = 0.2
m = 4

# calculate water saturation using the Archie equation
sw = (a * rw) / (phi * m)
# print the result
print(sw)

在本例中,参数值(a、rw、phi 和 m)被硬编码到程序中。实际上,您可能希望使用用户输入或从文件或数据库中读取这些值。该程序使用阿尔奇公式计算含水饱和度,然后将结果打印到屏幕上。在这种情况下,输出将是 10,这意味着岩石的含水饱和度为 10%。

评论和想法:

忽略等式不正确的事实,它提供了可以立即运行的可用 Python 代码。此外,令人惊讶的是,它已经产生了相关的评论,以帮助解释正在发生的事情。

如果能让这个等式发挥作用,允许用户传递参数,那就太好了。如果需要的话,也可以向 ChatGPT 机器人询问这一点。

最后,它还提供了如何通过使用用户输入或从文件中读取值来改进代码的建议,这是常见的做法。

和前面的提示一样,我担心使用这种形式的等式,但是,它确实生成了可运行的代码。

提示 5:如何计算密度孔隙度?

想法:

根据错误的水饱和度方程,我必须看看这是不是一次性的。因此,我尝试了另一个简单但常用的方程——密度孔隙度方程。

该方程用于根据大量关键参数和体积密度测井测量值,提供岩石中可用孔隙空间的指示。

回应:

为了计算密度孔隙度,你需要知道岩石的密度和填充岩石孔隙的流体的密度。然后,您可以使用以下等式:

φ = (ρ_m — ρ_f) / ρ_m

其中:

φ =密度孔隙度

ρ_m =岩石的密度

ρ_f =孔隙中流体的密度

这个方程简单地计算了岩石密度和流体密度之间的差值,并将这个差值除以岩石密度。结果是密度孔隙度,它是岩石中孔隙体积相对于岩石总体积的度量。

例如,如果岩石的密度为 2.7 克/厘米,孔隙中流体的密度为 1.0 克/厘米,则密度孔隙度的计算公式为:

φ = (2.7–1.0) / 2.7 = 0.63

这意味着岩石的密度孔隙度为 63%。

值得注意的是,这个方程是一个简化的模型,假设岩石和孔隙中的流体具有恒定的密度。在实践中,实际的密度孔隙度可能与使用该方程计算的值不同,可能需要其他更复杂的模型来准确预测密度孔隙度。

评论和想法:

同样,ChatGPT 提供了一个具有可信参数的部分正确的方程。但是,正确的形式如下所示:

密度孔隙度方程。图片由作者提供。

其中,ρm 为基质密度,ρf 为流体密度,ρb 为体积密度,使用密度测井工具测量。

它为基质和流体提供的值在预期值范围内,但是计算出的 63%的孔隙度在自然界是不可能的。超过 47.6%,岩石就会崩塌。

有趣的是,它为不同的密度测量选择了正确的数学符号和单位。

和以前一样,我担心任何没有领域知识的人使用这个等式来进行现实世界的计算和工作。

摘要

总的来说,我对 ChatGPT 及其用途印象深刻并充满热情,但是,如果它被非专家用来获得关于小生境方程和方法的信息,我会非常担心。正如这里看到的,聊天机器人努力提供方程的正确形式。

我可以看到它目前的形式被用来产生想法和围绕主题进行对话,但它应该永远在你的脑海中,它提供的可能是不正确的。

事实上,就在最近,StackOverflow 已经暂时禁止 ChatGPT 使用其平台上的内容来生成答案,原因是其准确性。在接下来的几周里,看看这将如何发展将会很有趣。

我认为它将来会取代谷歌和维基百科吗?可能,在进一步训练后的几年里,但是现在,我不会把太多的重量放在回答上。在可能的情况下,我会试着用其他资源检查它能提供什么。

还有一个风险是,如果这些工具被没有经验的人使用,它们会在以后导致严重的问题。对于许多现有的机器学习算法和程序来说也是如此。

感谢阅读。在你走之前,你一定要订阅我的内容,把我的文章放到你的收件箱里。 你可以在这里做!*或者,您可以* 注册我的简讯 免费获取更多内容直接发送到您的收件箱。

其次,通过注册会员,你可以获得完整的媒介体验,并支持我和其他成千上万的作家。每月只需花费你 5 美元,你就可以接触到所有精彩的媒体文章,也有机会通过写作赚钱。如果你用 我的链接报名,你直接用你的一部分费用支持我,不会多花你多少钱。如果你这样做了,非常感谢你的支持!

OpenAI 的 ChatGPT 是世界上最好的聊天机器人

原文:https://towardsdatascience.com/openais-chatgpt-is-the-world-s-best-chatbot-a25fa9f54442

意见

一个新模型令人印象深刻的能力——以及如果人工智能模型变得非常善于隐藏自己的不完美会发生什么

鸣谢:作者 via midway

OpenAI 发布了 ChatGPT ,这是一个新的对话语言模型(LM),基于 GPT-3.5 家族系列(在文本和代码上进行训练),类似于 InstructGPT (通过人类反馈与强化学习对齐)。该公司建立了一个在线演示,人们对它失去了理智。

简而言之,ChatGPT 是一个聊天机器人,可以“回答后续问题,承认错误,挑战不正确的前提,并拒绝不适当的请求。”

这很好地概括了 ChatGPT 如此特殊的原因:“承认”、“挑战”和“拒绝”是描述 LM 行为的不常见的动词。然而,在 ChatGPT 的例子中这并不夸张(我将很快分享的无数例子证明了这一点)。

ChatGPT 是迄今为止世界上最好的聊天机器人。它可以写散文诗歌。它可以为 AI 艺术模型找到很棒的提示。它可以角色扮演。它可以写代码,发现一个 bug,解释它,解决它,解释解决方案。它能以最怪异的方式结合各种想法:

推特

这个模型的卓越能力和比基线 GPT-3 更好的排列,使它感觉更像人类。反过来,这使它更可信——尽管这不一定意味着它更可靠,因此更可信。

像所有其他的 LMs(例如 GPTs,Galactica,LaMDA)一样,它编造事情,可以产生有害的完成,并产生错误的信息。这些缺陷都没有显著改变(ChatGPT 更好,但它是基于相同的原则构建的)。

推文

但这不是我今天的重点。我不会用另一个关于我们为什么不应该相信这些模型的警示故事或者一篇关于批判性思维的指导文章来烦你。

在本文中,我将与您分享人们从 ChatGPT 中挖掘出的最有趣的发现和含义的汇编(当然,还有我添加的评论)。

为了把樱桃放在最上面,我将带你踏上一段旅程。我想探讨一个假设:如果 AI 模型变得如此善于隐藏不完美,以至于我们再也找不到它们的任何缺点或不足,会发生什么?

本文选自The Algorithmic Bridge,这是一份旨在弥合算法与人之间鸿沟的教育通讯。它将帮助你理解人工智能对你生活的影响,并开发工具来更好地导航未来。

https://thealgorithmicbridge.substack.com/subscribe

ChatGPT 的广泛含义

如果你最近没有查看 Twitter,人们已经花了两天时间不停地和 ChatGPT 聊天。我将回顾他们的发现和结论。一旦你看到我将要展示给你的东西,你就会明白为什么上述假设并不那么疯狂。

散文已经死了

我 100%同意,散文,作为一种评价形式,很快就会消亡。我以前写过关于这一点的文章——关于教师和教育系统对此都没有准备好。对于 ChatGPT,这是现在普遍接受的说法:

推文

在学生开始在 GPT-3 的作业中作弊之后,事情变得很明显,老师们意识到他们必须准备。现在这是一个切实的现实。我写了一篇 1500 字的关于 2023 年五大人工智能预测的文章,所有这些看起来都非常可信。

然而,我不得不说,有见地、引人入胜、创新或发人深省不是描述 ChatGPT 创作的最佳形容词。它的大部分输出都是乏味的(这是不可避免的,除非你真的试图让成为一首令人难忘的曲子或两首重复的不准确的——当不是的时候,胡说八道

除了教育系统需要的改革之外,让我担心的是,我们是否还能再次识别人工书写的作品。LMs 可能会变得很好,以至于完全模糊他们和我们之间的差距。以至于即使是人工智能鉴别器(GAN 式)也无法发现哪个是哪个,因为可能没有区别。

然而,还有另一种可能性:人类的文字有一些特征,使用正确的工具,可以揭示作者身份。随着 LMs 成为散文大师,他们可能会发展出某种写作特质(作为一种特性而非缺陷)。

也许我们可以发现人工智能的风格(就像隐藏在语言中的指纹)不仅仅是为了区分 ChatGPT 和人类,而是为了区分它的风格。

谷歌死了吗?

ChatGPT 的另一个重大含义是,它将“杀死”谷歌——互联网搜索的霸主已经完成。没有人暗示这已经发生或即将发生,但鉴于人们已经在使用这种模式令人满意地取代谷歌,这显然不是一件难事:

推特

但是这里有一些警告。

在研究能力、人才和预算方面,谷歌都在 OpenAI 之上——如果有人能在 OpenAI 之前建立这项技术,那就是他们。然而,互联网广告的威力实在是太大了,无法做出充分的反应和应对。谷歌的人工智能研究部门可以说是世界上最好的,但他们几乎不再提供任何产品/服务。

谷歌正面临一个“创新者困境”的案例:公司不能仅仅因为其他公司可能最终取代它,就用有风险的创新来制约它的主要商业模式。

推文

LMs 实际上可能是谷歌 20 年来面临的第一个真正的威胁。

然而,如果我们分析搜索引擎和 LMs 之间的差异,我们会发现它们并不完全重叠。

一方面,搜索引擎是刚性的。他们只是进入互联网寻找网站,并向你展示一个链接列表,大约会给你你想要的东西——这基本上是最简单的互联网搜索形式。但是,另一方面,他们是可靠的。你知道他们不会捏造事实的。(谷歌搜索和所有其他搜索一样,是有偏见的,可能会向你展示假新闻,但你可以检查来源,这在这里很关键。)

ChatGPT 要灵活得多,但是,因为它的目标不是事实或真实,它可以像给你一个惊人的、高度复杂的、精确的答案一样容易地编造信息。你永远不知道哪一个会是先验的,之后可能会很难检查(ChatGPT 不会给你来源,如果你问的话,它无论如何都会编造这些来源)。

推文

简而言之,搜索引擎的局限性要大得多,但更适合这项任务。

也就是说,我不认为搜索引擎会在 LMs 中存活下来。时间与他们赛跑——当搜索引擎技术一点也不进步时,LMs 却以光速发展。

一旦更健壮的 transformer 架构出现,或者公司实现“可靠性模块”(无论这意味着什么),LMs 将自动成为超级生成搜索引擎

没有人会再使用谷歌。

ChatGPT 令人担忧地令人印象深刻

现在,我将尝试解释为什么我在介绍中提出的假设如此重要——并且在不久的将来会更加重要。

你已经看到了 ChatGPT 拥有的许多令人印象深刻的能力,所以现在你明白我为什么认真对待这个问题了:ChatGPT 让那些反对炒作的人更难找到不足之处——这并不意味着他们不存在。

推特

推文

很明显,ChatGPT 缺乏推理能力,也没有很好的记忆窗口(Gary Marcus 写了一篇很棒的文章,解释了为什么“这一分钟看起来很聪明,下一分钟又笨得惊人”。

像卡拉狄加一样,让废话听起来像是真的。人们可以“轻易地”通过它的过滤器,它容易受到及时注射的影响。显然,它并不完美。

然而,ChatGPT 是一个进步——朝着我们无法通过测试和采样来绊倒它的方向前进:

推文

这是一件大事。

不久前,我写了一篇关于 AGI 的文章,题目是“ AGI 将给所有人带来惊喜”ChatGPT 不在那个水平或接近它的任何地方(它实际上只是类固醇上的 GPT-3),但它值得提出我对那篇文章的论点:

“凡事都有极限。宇宙是有极限的——物理定律之外的事情是不可能发生的,无论我们怎么努力——甚至是无限的——自然数的集合是无限的,但它不包含实数的集合。

GPT-3 有局限性,而我们,那些试图发现它们的人,也有局限性。格温在这里证明了[https://www.gwern.net/GPT-3#prompts-as-programming**】的是,在寻找 GPT 3 号的极限时,我们找到了自己的极限。不是 GPT 3 号没有完成某些任务,而是我们找不到合适的提示。我们的极限是阻止 GPT 3 号执行任务。我们阻止了 GPT 3 号发挥其真正的潜力。

这就提出了一个直接的问题:如果 GPT-3 的局限性经常被误认为是我们的局限性,我们如何精确地定义该系统能做什么或不能做什么的界限?

最终,我们是一个试图评价另一个有限系统的有限系统。谁能保证我们的极限在各方面都超越他们?我们有一个很好的例子说明情况可能并非如此:我们非常不善于评估自己的局限性。我们不断为自己能做的事情感到惊讶,无论是个人还是集体。我们不断打破身体和认知的极限。因此,我们的测量工具很可能达不到足够强大的人工智能的行动能力。"

在他的文章中,Gwern(一个受欢迎的技术博客)指出“抽样可以证明知识的存在,但不能证明知识的缺失。”他用这个想法来捍卫他的论点,即 GPT 3 号失败的原因可能是不良的提示,而不是模型本身缺乏“知识”。

我想在这里强调的是,抽样作为一种测试方法的局限性并不适用于我们发现我们的局限性(Gwern 论点)或 AI 的不足(反炒作论点)的情况,而且如果我们* 没有发现任何。*

当人们发现 ChatGPT 的回答有缺陷时,一个常见的反驳是“你不知道如何最大限度地利用人工智能。”这很公平——但还不够——因为一旦发现系统缺陷,我们就可以断定这个系统不可靠。

但是,如果尽管无数次尝试让一个人工智能模型打破角色,击败它的过滤器,并让它放弃推理的外表,但人们没有这样做,会发生什么?

这可能看起来像一个没有现实基础的哲学思想实验,但我认为很有可能——将这种推理应用于未来的超级人工智能模型——我们可以在发现人工智能的缺陷之前找到手头方法的上限。

(我并不是说这个模型实际上能够完美地推理,而是说一套精心设计的护栏、过滤器和内在的保守性,加上我们作为人类的局限性,会使它看起来如此。)

我们就没有办法证明这个模型不能推理。没有人会相信现在用抽样来证明这些局限性的人,每个人最终都会开始信任这个系统。如果我们不尽快认识到这个问题,并找到解决方案,那就太晚了。

如果说我们应该从 ChatGPT 的卓越能力中学到什么的话,那就是我们正在不可避免地接近这一现实。

订阅 算法桥 。弥合算法和人之间的鸿沟。关于与你生活相关的人工智能的时事通讯。

您也可以直接支持我在 Medium 上的工作,并通过使用我的推荐链接 这里 成为会员来获得无限制的访问权限! 😃

OpenAI 的首席科学家声称 AI 可能是有意识的——并引发了一场激烈的辩论

原文:https://towardsdatascience.com/openais-chief-scientist-claimed-ai-may-be-conscious-and-kicked-off-a-furious-debate-7338b95194e

意见

我们甚至能定义意识吗?

由 Shutterstock 上的 Agsandrew 拍摄的

一个月前,伊利亚·苏茨基弗(Ilya Sutskever)在推特上写道,大型神经网络可能“略有意识”。他是 OpenAI 的联合创始人之一和首席科学家,也是引发深度学习革命的里程碑式论文的合著者。拥有这样的头衔,他当然知道他的大胆主张——既没有证据也没有解释——会吸引人工智能社区、认知科学家和哲学爱好者的注意。几天之内,这条推文得到了 400 多条回复和两倍的转发量。

伊利亚·苏茨基弗的推特

人工智能先锋圈子里的人喜欢思考人工智能的未来:我们什么时候才能实现人工通用智能(AGI)?像 GPT-3 这样的基于大型变压器的系统和 AlphaZero 这样的超人强化学习模型的能力和局限性是什么?人工智能何时——如果可能的话——会发展出意识?

对这些话题的看法各不相同。Gary Marcus 等坚定的怀疑论者因批评 AGI 的纯深度学习方法而闻名。他为结合基于数据和基于知识的模型——混合系统——的必要性辩护。在光谱的另一端,我们发现像雷·库兹韦尔这样充满希望的乐观主义者,他声称奇点(机器将不可避免地超越人类智能成为霸权物种的拐点)离我们只有几十年之遥。

在关于人工智能的对话中,意识有时会被提及。虽然在人类的情况下与智力密不可分,但还不清楚机器是否也是如此。那些不喜欢人工智能拟人化的人经常攻击“机器智能”的概念。意识更抽象,通常会更糟。这是正确的,因为意识——和智力一样——是一个模糊的概念,存在于哲学和认知科学的模糊交叉地带。

现代概念的起源可以追溯到约翰·洛克的作品。他将其描述为“对一个人内心想法的感知”然而,事实证明这是一个难以捉摸的概念。关于意识有多种模型和假设,这些年来或多或少引起了人们的兴趣,但科学界尚未达成一致的定义。例如,泛灵论——阅读苏茨基弗的思想时想到的——是一个独特的想法,最近得到了一些关注。它认为“心灵是现实中一个基本的、无处不在的特征。”简而言之,在泛心理学家看来,一切都是潜在的意识。

然而,这只是一个吸引人的假设。意识仍然停留在模糊的前科学概念领域。大多数人同意它的基本基础:有意识不可避免地通过理解“我”的概念和对我们周围环境的感性认识。但是当我们试图给它下一个精确的定义时,它就变得难以捉摸了。用认知神经科学家安尼尔·塞斯的话说,“意识的主观本质甚至让它难以定义。”

鉴于意识在科学上是未定义的,客观上是不可测量的,我想知道为什么 Sutskever 会做出这样的肯定。

我会走得更远。如果我们不能测量意识,他是对的对我们来说有什么关系吗?问一个人工智能是否有意识让我想起了迫使艾伦·图灵在他 1950 年的开创性论文“计算机械和智能”中设计模仿游戏的原因——现在普遍称为图灵测试他知道问机器是否会思考是没有意义的,因为这个问题太模糊了,没有意义。(现在人们普遍认为,图灵测试也不足以评估人工智能的智能。)

但是不管 Sutskever 的主张的效用以及就意识的定义达成一致的可能性,这条推文吸引了人工智能和神经科学领域的知名人士。Meta 的首席人工智能科学家 Yann LeCun 给出了肯定的回答“不”

Yann LeCun 的推文

他相当不具体地认为,神经网络需要一种特殊的架构——可能我们现在还无法构建——来实现某种程度的意识。著名认知心理学家斯塔尼斯拉斯·德阿纳同意勒村的观点。他引用了一篇他与人合著的科学论文,题为“什么是意识,机器会有意识吗?“结论对他来说很明显,”我们认为答案是否定的:当前深度学习网络实现的计算大多对应于人脑中的无意识操作。”

关注人工智能伦理方面的专家也提到了 Sutskever 的推文。Santa Fe Institute 的 Davis 教授 Melanie Mitchell 、Washington 大学的语言学教授 Emily M. Bender 和其他人选择了一种嘲讽的语气来提供证据,证明声称 AI 可能有轻微意识而没有进一步的证据支持是多么荒谬。

加州大学伯克利分校的 CS 博士生 Deb Raji 强调了将人工智能视为有意识的存在所带来的问题:

黛比·拉吉的推文

其他人,如特斯拉人工智能总监 Andrej Karpathy 和 OpenAI 首席执行官 Sam Altman,似乎支持 Sutskever 的想法。奥特曼利用这种情况,在 LeCun 发起反击,这更像是一场精心策划的营销噱头,旨在雇用从 Meta 的行列中掉队的人工智能研究人员,而不是一场关于有意识人工智能的诚实辩论。

山姆·奥特曼的推文

然后,他对自己的观点进行了限定,说“GPT-3 或-4 将非常非常可能不会像我们使用这个词的方式那样有意识……在这个问题上,我唯一可以肯定的是,我有意识。”

处于人工智能研究前沿的人,他们的声音听起来比大多数人都要响亮,如果我们不想让这些空洞——但危险地令人兴奋——的声明渗透到新闻渠道,填满那些不知道更多的人的渴望的头脑,他们应该保持理智的谦卑。OpenAI 的高管应该尽最大努力使他们的流行模型如 GPT-3 更安全,而不仅仅是更强大。他们的最新型号 instruct GPT——Altman 称之为比 GPT-3 更安全的型号——实际上更有毒,更有害,如果用户愿意的话。

作为关于探索人工智能意识的难度的最后一个论点,我认为人工智能最终可能会成为有意识的——如果有的话,最有可能是在遥远的未来——但即使我们就定义达成一致,我们如何证明这是真的还是假的?只要我们能够测量,现实对科学就很重要。超出我们认知能力的真相不是要解决的问题,而是谜团。就目前而言,而且似乎不会很快改变,“我感觉像我”这句话的主观本质使得意识无法用我们所拥有的工具来理解。

我的观点是,我们应该把对人类意识的神经认知和哲学研究与对人工智能的研究分开。我们如何科学地理解作为一个人工智能的感觉?我们应该按照图灵的步骤,不要再问 AI 有没有意识。我们应该定义与意识的模糊概念相关的更具体、可测量的属性——类似于图灵测试与思维机器的概念的关系——并设计工具、测试和技术来测量它们。然后,我们可以检查人工智能在这些方面与人类相比如何,并得出它们在多大程度上展示了这些特征的结论。

我想以积极的方式结束。除了这些天围绕 Sutskever 的不幸观点进行的对话,以及人工智能社区当前面临的问题——从工作场所的歧视到有毒的语言模型——以外,令人兴奋的是,人工智能研究人员与哲学家和神经科学家携手辩论一个需要这三个领域努力才能向前迈进的话题。看到越来越多的人在原本不应该相距如此之远的领域的交汇处找到智力动力,这将是一件大有希望的事情。

如果你已经读到这里,可以考虑订阅我的免费双周刊 【明天的思想】 !每两周一次关于人工智能和技术的新闻、研究和见解!

您也可以直接支持我的工作,使用我的推荐链接 这里 成为中级会员,获得无限权限!😃

运筹学——组织运营效率的关键

原文:https://towardsdatascience.com/operations-research-key-to-organizational-operational-efficiency-fc5ae9b54387

有效的流程管理是任何组织成功的关键,它来自于数据科学、运营管理和运筹学的结合。

摘要

随着公司转向人工智能,他们必须考虑业务、公司和功能战略,以及人工智能如何适应这些战略。在我进入正题之前,我想给你简单介绍一下。商业战略包括商业模式,以及外部和内部分析。作为一名人工智能领导者,应该理解商业战略,因为数据科学用例会受到公司内部和外部环境的影响。

其次,我想谈谈职能或运营战略,它涉及有效和高效地管理资源以满足客户的期望,同时还帮助组织盈利。该组织专注于其运营战略内部的重要关键绩效指标,这对人工智能领导者的理解至关重要。如果数据科学家想要真正对企业产生影响,他们必须首先了解每个流程中的运营战略和 KPI。

以保险业为例,索赔周转时间可能是一个重要的 KPI。在电信行业,客户生命周期管理流程中的流失率是一个关键的 KPI。因此,数据科学家必须了解每个流程内部的 KPI,以便充分利用数据科学。

在这篇文章中,我想把重点放在运筹学上,因为它与运营战略中的流程管理有关。运筹学是一个非常重要的主题,也是数据科学领域中代表性不足的领域之一。无论您选择哪个组织,它都有多个流程,我们有资源来处理这些流程。有时,某些流程没有得到有效优化,导致管理不善和资源效率低下。

例如,在医院中,低效的流程可能会导致更长的等待时间,从而带来负面体验。同样,低效的库存管理和需求满足会导致持有成本增加。此外,当涉及到项目管理时,不适当的资源分配可能会导致项目失败。这就是运筹学派上用场的时候了。通俗地说,运筹学有助于管理者在正确的时间做出正确的决策,从而降低运营效率,帮助组织提高关键绩效指标。更准确地说,运筹学需要最优化,这有助于在给定各种约束的情况下,资源的分配能够满足总体目标。

介绍

在这篇文章中,我将主要关注两个例子,它们将激励你继续努力。首先,我们将解决背包问题,然后我们将使用图解法来研究一些理论。使用图表理解理论有助于建立直觉。我使用这些琐碎的例子只是为了演示整个技术是如何工作的。

背包问题

图 1:露营物品清单(作者提供的图片)

假设你正在整理一份露营装备清单,如上表所示。如您所见,所有物品的总重量为 11.29 千克,但您在包中最多只能携带 5 千克。然后你将如何从列表中选择项目?

表中的第三列还包括物品的重要性/价值,这可以帮助您选择最佳的物品数量。你可以采取的一种方法是尝试所有可能的项目组合,这将花费大量的时间。这就是所谓的贪婪方法,编码这个问题会导致很高的时间复杂度。这是一个小问题,但如果你有大量的项目,有无数的组合呢?这种贪婪的方法是行不通的。

现在让我们用数学规划来解决这个问题。数学问题的概念与你在学校已经学过的内容有关,包括线性系统和方程,解联立方程等。在数学或线性规划中,重要的部分是制定你的问题陈述。

制定问题陈述

  1. 决策变量

决策变量是我们想要确定的。在这种情况下,我们需要选择最大化总价值的物品组合,同时考虑 5 公斤的限制。

按作者:如果选择了项目,则为 1;如果未选择项目,则为 0。如果我们排除一个项目,它将被标记为 0。

2。目标函数

在这种情况下,我们希望最大化总价值。让我们先计算一下每件商品的重量价值。

图 2:露营物品清单(作者提供的图片)

作者:目标函数

所以,理想情况下,我们希望最大化价值,如果我们包括所有的项目,我们可以这样做,但我们没有这个选项。我们必须选择最佳的项目组合来满足我们的约束条件。

3。约束条件

总重量不得超过 5 公斤

作者:限制→重量不得超过 5 公斤

给定这 3 个部分,我们可以使用求解器来求解完整的方程。我们可以利用纸浆来解决这个方程,并找到正确的项目组合。

纸浆程序

from pulp import *v = {'Sleeping bag':4.17,
'Pillow':5.13,
'Torch':10,
'First Aid Kit':8,
'Hand sanitiser':2,
'Coolbox':1.33,
'Rubbish bags':2,
'lantern':0.37,
'Eating/cooking utensils':1.2,
'Compass':8}
w = {'Sleeping bag':1.2,
'Pillow':0.39,
'Torch':0.5,
'First Aid Kit':0.5,
'Hand sanitiser':0.5,
'Coolbox':1.5,
'Rubbish bags':1,
'lantern':2.7,
'Eating/cooking utensils':2.5,
'Compass':0.5}
limit = 5
items = list(sorted(v.keys()))# Create model
m = LpProblem("Knapsack Problem", LpMaximize)# Variables
x = LpVariable.dicts('x', items, lowBound=0, upBound=1, cat=LpInteger)# Objective
m += sum(v[i]*x[i] for i in items)# Constraint
m += sum(w[i]*x[i] for i in items) <= limit# Optimize
m.solve()# Print the status of the solved LP
print("Status = %s" % LpStatus[m.status])# Print the value of the variables at the optimum
for i in items:
    print("%s = %f" % (x[i].name, x[i].varValue))# Print the value of the objective
print("Objective = %f" % value(m.objective))

结果

Status = Optimal
x_Compass = 1.000000
x_Coolbox = 0.000000
x_Eating_cooking_utensils = 0.000000
x_First_Aid_Kit = 1.000000
x_Hand_sanitiser = 1.000000
x_Pillow = 1.000000
x_Rubbish_bags = 1.000000
x_Sleeping_bag = 1.000000
x_Torch = 1.000000
x_lantern = 0.000000
Objective = 39.300000

正如我们在这里看到的,我们应该为我们的露营选择指南针、急救箱、洗手液、枕头、垃圾袋、睡袋和手电筒。所有这些物品的总重量为 4.59 千克,这满足了我们的约束条件,并使我们的目标函数最大化。

虽然这是一个非常简单的例子,但是相同的概念可以应用于许多实际的用例中,例如生产计划,其中您需要选择正确的资源来最大化您的利润、订单选择等。

我们刚刚解决的问题在技术上被称为“整数线性规划”,因为变量被限制为整数。这只是一个简单的二进制 0-1 整数线性规划。我不会转向其他类型,因为我现在只想让你理解这个概念。

理论理解(图解法)

现在让我们更详细地理解数学概念,这样你就能发展直觉。

制造商的目标是通过销售包括桌椅在内的家具来实现收入最大化。制作桌椅需要木材和劳动力等资源。假设所有单位都是在生产时售出的。做一张桌子,我们需要 10 单位的木材和 4 小时的工时。为了制作椅子,我们需要 8 单位的木材和 6 小时的劳动时间。但是,我们只有 200 单位的木材和总共 100 个工时可用。

桌子的单位利润:5 美元
椅子的单位利润:6 美元

为了实现收入最大化(假设本例中收入=利润),我们可以继续生产桌子和椅子,但由于木材可用性和工时方面的限制,我们不能这样做。让我们用表格的形式总结一下这个问题。

图 3:问题公式化表(作者提供的图)

通过表格阐明问题确实有助于澄清一切。

我们需要确定最大化利润的最佳桌椅数量。这将有助于制造商的决策过程。

公式化问题

第一步:决策变量(在给定约束条件下,我们需要生产多少张桌子和椅子来实现利润最大化)

作者:这是我们需要确定的

第二步:目标函数

作者:我们想最大化这一点

第三步:约束

按作者:要满足的约束

因为我们有两个变量,X1 和 X2,我们可以用图解法解决这个问题。

这个问题我们已经公式化了。下一步是绘制约束条件。

作者:木约束→得到两点画直线

如本例所示,我们有两个点来在图上绘制约束 1。这两点将用来在图上画一条直线。

我们将对约束 2 做同样的事情。

作者:劳动力约束→得到两点画直线

现在我们有了约束 1 和约束 2 的点,我们可以把它们画在图上。

图 4:图表上绘制的约束。阴影部分显示可行区域(作者提供的图片)

在这个图上,我们可以看到一个可行的区域,其中这两个约束都得到满足。我们不能超越它,因为它不符合我们的限制。可行区域是有效约束线两侧的区域。

现在我们必须确定最佳解决方案的目标函数值。换句话说,我们需要在这张图上找到利润最大化的最佳点。

作者:目标功能概述

为此,使用这个目标函数并输入 z 的任意值(理想情况下,从 0 开始)。然后,对于任意 x1,求解 x2。对于另一点,使用不同的 x1 来求解 x2。这将得到一条基于指定 z 值的目标函数线。

同样,尝试不同的 Z 值,得到不同的方程直线。你会注意到这条线将平行于前一条线。

为了找到最吸引人的角,画一条与目标函数线平行的线,该线接触可行域中的最后一点。

在这种情况下,红点是 Z 值最大化的最有吸引力的角落。

因此,目标函数的最大值出现在 Z 值为 114.29 的极值点(14.29,7.14)。这是我们能从 14 张桌子和 7 把椅子中获得的最大利润。

结论

通过解决两个重要的问题,我们在这篇文章中看到了运筹学的重要性。这方面有很多应用。当我们将预测分析与运筹学相结合时,我们可以有效地将数据科学转化为决策科学,从而让管理层做出可行的决策。

同样重要的是要注意,当我们添加许多约束时,解决方案可能变得不可行。即使我们有一个可行的区域,当这些区域是无界的时,也是很困难的。此外,同一问题可能有多个最优解。因此,仔细阐述问题是至关重要的。

参考

[1]https://py Omo-simple model . readthedocs . io/en/latest/backpack . html #:~:text = The % 20 napsack % 20 problem % 20 considers % 20 The % 20 items % 20 that % 20 can % 20 be % 20 carried

[2]https://thinkinsights . net/strategy/functional-strategy/#:~:text = A % 20 functional % 20 strategy % 20 is % 20 the,for % 20a % 20 specific % 20 business % 20 function

基于 KNN 分类器的光学字符识别

原文:https://towardsdatascience.com/optical-character-recognition-with-knn-classifier-10fd220ed797

通过流行的 K 近邻算法编码并解释光学字符识别的工作原理

来自 MNIST 数据集的手写数字

光学字符识别( OCR )比我们想象的更经常地出现在我们的日常生活中。当我们使用谷歌翻译从图片翻译文本时,我们使用的是光学字符识别。当我们发送一封信并且它到达目的地时,OCR 正在为我们工作。当一个有视觉障碍的人扫描一份文件,一台机器为他们阅读时,OCR 又一次获得了荣誉。这些只是我们日常工作中 OCR 出现的几个例子,但是,到今天为止,还有更多的例子存在,并且在不久的将来,它们的数量还会进一步增加。

本文旨在说明什么是光学字符识别,并提供一个循序渐进的应用程序来熟悉它。我将使用 MNIST 数据集来训练一个机器学习模型,它将能够从图片中识别手写数字。在微调算法后,我将评估它的准确性。

什么是光学字符识别?

光学字符识别( OCR ),也称为文本识别,是一种能够将印刷或手写字符转换为机器可读文本的技术。OCR 利用硬件和软件工具。硬件通常由光学扫描仪或照相机组成,而软件(本文的重点)由机器学习算法组成,如 KNN 分类器、SVM 分类器或神经网络。

当前 OCR 的应用是无止境的。仅举其中一些例子:

  • 旧书籍或文件的电子版本(软版本)的生成(参见 Google Books
  • 扫描路标自动驾驶
  • 创造工具使盲人和视障人士能够阅读(见 AFB )
  • 手写文本的实时转换
  • 从护照或保险文件中自动提取信息

型号选择

光学字符识别是一个分类问题,这意味着它是一个机器学习问题,其中输出是分类的(即,它属于有限的一组值)。当然,输出类是不同的字符。在这个应用中,我们负责手写数字识别。输出类的数量为 10,由 0 到 9 的整数组成:

有许多现成的分类算法,所以我们需要选择一个非常适合这个目的的算法。算法选择的主要约束是它必须适合于多类分类。事实上,由于类别多于 2,这不能被认为是一个二元分类问题。

多类分类

一些算法天生能够处理多类分类问题(例如随机梯度下降、随机森林或朴素贝叶斯分类器)。其他算法(如支持向量机或逻辑回归分类器)本身就是二元分类器。即使存在使用二元分类器( 一对一一对一 策略)执行多类分类的技术,对于本文的范围,我将坚持使用原生多类分类器。

k 近邻分类器

由于没有公认的理论来确定每种问题类型的最佳学习算法,我的选择落在了k-最近邻分类器 ( KNN ) 上,主要是因为它简单:它需要调整的超参数很少(要考虑的邻居数量 k ,距离函数,通常对应于欧几里德距离或曼哈顿距离),而且它是非参数的(这意味着它不需要关于数据分布的假设)。一个缺点是,由于其固有的性质,KNN 对大型数据集的伸缩性很差:每当需要对新的例子进行分类时,它都必须扫描整个数据集。出于这个原因,探索额外的学习算法是有意义的,比如决策树或随机森林。

KNN 背后的思想是,一个实例 i 有很高的概率属于某个特定的类,姑且称之为 m ,如果 m 也是特征空间中 k 最近实例的最流行类。更正式地说,KNN 学习算法遵循以下步骤:

  1. 给定一个未知示例,测量它与数据集中所有已标记示例的距离
  2. 以最接近未标记的示例的标记示例为例
  3. 基于 k 个最近邻的类别,预测未标记示例的类别
  4. 对所有未标记的示例重复步骤 1-3。

在这个操作之后,特征空间可以被可视化为分成与类一样多的区域。根据一个新例子的位置,它将被预测为属于一个特定的类。

特征空间的分类(图像来源:Wikipedia.org

为了将 KNN 应用于分类问题,我们需要说明:

  • 一个距离度量 p 。最常用的是欧几里德距离,它是闵可夫斯基距离的一个特例,这里设置 p=2。给定相同尺寸的 2 个点 XY ,闵可夫斯基距离 D(X,Y) 计算如下:

  • 要考虑的邻居数量 k。这可能是 KNN 最关键的超参数,因为:
    -对于 k 来说太小的值可能导致过度拟合
    -对于 k 来说太大的值可能导致拟合不足
    强烈建议使用交叉验证集来优化 k
  • 可选加权函数。它经常被忽略,但是在某些应用中,通过设置一个奖励最近邻类的加权函数可以获得更好的结果。
  • 聚合方法。通常选择简单多数票。

在这里我提供了 KNN 算法的一个简明的快照,有关其功能的更详细的信息请查看本文。

MNIST 数据集

为了训练我们的模型,我将使用 MNIST 数据集,这是一个手写数字的大型数据库。该数据集包含 70,000 个小图像(28 x 28 像素),每个图像都被标记。

首先,我们必须导入数据集。

为了了解数据集,我定义了一个函数来打印 70,000 幅图像中的一幅:

输出将是:

数据集由 70,000 个示例组成,每个示例都有 784 个要素(28 x 28 像素)。每个特征代表灰度色图上给定像素的强度,特征值范围从 0 到 255。
0 值对应白色像素,而 255 值对应黑色像素。

模特培训

按照惯例,我们将数据集分为训练集和测试集。我选择将 25%的例子分配给测试集。测试集将被放在一边,直到最后都不会被触及。其唯一目的是提供模型的一般评估。将使用交叉验证集执行超参数调整。

让我们首先尝试用默认参数来拟合分类器。

这应该输出大约 96.8%的准确率,考虑到构建模型的工作量很小,这还不算太差。

超参数调谐

为了获得更好的结果,我将尝试使用网格搜索方法来调整模型超参数。网格搜索是一种穷举搜索方法。这就是我在交叉验证中降低折叠数的原因,也就是说,为了计算的原因。我通过考虑以下组合来探索超参数空间:

换句话说,网格搜索将探索最近邻 k 的 3 个不同值的组合和聚合邻居类的 2 种方式(统一权重或距离加权)。有了 6 个可能的超参数组合,我将交叉验证折叠数设为 3,总共 18 个训练步骤。

另一个值得研究的可能的超参数是距离度量。

在网格搜索和交叉验证过程之后,我们可以通过在拟合的网格搜索对象上调用best_estimator_方法来访问最佳估计量。

在我的例子中,最好的估计器是具有 k=4 并使用距离加权聚合的那个。

我现在将在测试集上评估优化后的模型。这里的步骤如下:

  1. 使用训练好的模型来预测测试集中每个示例的标签
  2. 将预测标签与实际标签进行比较
  3. 计算准确度分数

Scikit-learn 允许用一行代码执行所有这些操作:

通过简单地应用网格搜索来寻找更好的超参数,模型的准确度增加到 97.3%。

后续步骤

为了进一步改进 OCR 模型,我们可以尝试不同的方法,如运行其他学习算法,扩展超参数搜索空间,以及通过综合修改原始数据(添加噪声,移动或旋转图像)来增加训练集的大小。接下来,将会有一篇文章用合成生成的样本训练一个新模型。

感谢您的阅读,希望您喜欢!

如果你的公共汽车不来,等待或步行的最佳决策

原文:https://towardsdatascience.com/optimal-decision-on-wait-or-walk-if-your-bus-is-not-coming-daaefe61bcd6

没有统计数据,困境如何解决?

罗伯特·多肖和迈克尔·施瓦茨

你想去拜访一个朋友,并尽快到达那里。有直达的公共汽车,但是公共汽车通常晚点,有时根本不来。你准时到达车站,但公共汽车却不在那里。你要怎么办?

要么你等公共汽车,要么你开始步行去找你的朋友。如果你离开车站,你将不能再乘公共汽车。一方面,你愿意等着搭车,因为这样更快。另一方面,公共汽车也有可能不来。真是进退两难!

照片由马里乌斯·马图奇克Unsplash 上拍摄

问题类型

最初,这似乎是一个优化问题,目标是最小化到你朋友的旅行时间。仔细观察就会发现,只有有限的信息可用,而且无法做出有用的统计假设。旅行时间没有一定的控制。真正的问题是如何最大限度地减少个人对错误决策的后悔。当你意识到公交车不准时时,你会在两个选择之间犹豫不决:等待还是步行?整个问题是一个顺序决策问题,因为波动时间可分为离散的子问题。每当你意识到“公交车还没到”的时候,你就不得不做出新的等待或步行的决定。这种信息以串行方式一点一点到达的顺序问题被称为在线问题。相比之下,所有信息都是预先知道的相应问题,如真实的未来,被称为离线问题。请注意,如果未来是已知的,就不需要统计假设。

一个在线问题的最优解

一个在线问题通常可以用不同的策略来解决。在线算法是策略的公式化,作为解决具体场景的适用步骤序列。这种算法的质量可以通过竞争分析进行评估,并允许与替代算法进行比较。对于这个在线算法,需要导出竞争比 c 。定义为任何场景下在线解( ALG )与对应离线解( OPT )的最大比值。换句话说,这一比率是通过确定一个最坏的情况得出的,在这种情况下,信息有限的算法解决方案与知道未来的人的解决方案差异最大。

在线最小化算法的竞争比

如果在数学上可以证明这个 c 是一个常数(其中 c ≥ 1),则一个在线算法被表示为竞争,这意味着它独立于任何场景。这个概念有一个巨大的优势。它为该算法提供了性能保证,即使问题的实际实例的结果是未知的。无论任何场景,在线问题的解永远小于或等于一个知道未来的理性决策者的解的 c 倍。

最小化问题的保证公式

具有数学上可证明的最低 c 的在线算法具有最严格的性能保证,因此是最优的。(注意,对于最大化问题,上述保证公式不同。有关更多信息,请查看在线优化。)

离线问题的最佳解决方案

假设 d 到你朋友的距离是 3 公里,你的步行速度 v_w 是 4 公里/小时,公共汽车的行驶速度 v_b 是 20 公里/小时,因此,步行需要 45 分钟(3/4 * 60 = 45),但乘坐公共汽车只需要 9 分钟(3/20 * 60 = 9)。

但是,你个人的决定取决于你是否知道你要等多少分钟,直到公交车到站 t_b 。所有可能的情况只是到达时间不同。如果等待加上开车的时间比纯步行的时间长,那么你总是会立刻开始步行。如果没有,你就等着。

离线算法:未来已知时的最优解

如果你知道未来,你将永远不会需要超过 45 分钟到达你朋友的地方。在离线问题的所有场景中,只有当公交车在 36 分钟内到达时等待才是有益的。这也可以让你在 45 分钟内到达(36 + 9 = 45)。不幸的是,你不知道未来…

各种在线算法的分析

现在,看一看你看不到未来的真正的实际问题。从抽象的意义上说,只有几种方法可以解决这个问题:

算法 1——运动员(算法 1 ) :一个策略是,如果公共汽车不在,你总是立即开始步行。该算法最糟糕的情况看起来如下:你将总是步行,并且一旦你开始步行,公共汽车将立即到达(假设在ε时间之后,其中T9ε非常小)。****

在线算法 1-运动员的竞争比

你总是需要 45 分钟,但有很多情况下,你会后悔你的决定,更准确地说,在所有情况下,公交车在接下来的 36 分钟内到达。在最坏的情况下,公交车在你开始步行后的 ε 时间到达,其中 ε 时间小得可以忽略不计。那么你需要的时间是实际可能时间的 5 倍(9 分钟对 45 分钟,因为 ε 接近于 0)。这个策略看起来很极端,但至少你在每个场景中都到达了你朋友的地方。算法是有竞争力的…但它也是最优的吗?

算法二——懒人( ALG2 ) :另一种极端的策略是,你总是在那里等公交车,从不决定走路。很容易预见,在最坏的情况下,公共汽车永远不会来。你从来没有到达你朋友的地方。这种策略没有常数 c ,因此没有竞争力。

在线算法 2 的竞争比——懒人

算法 3 —优化器( ALG3 ) :人们可能总是决定等待一段固定的时间 x ,然后变得不耐烦并决定走。引人注目的是,采用不同 x 的竞争策略在竞争比率上可以有显著差异。

在线算法 3 的竞争比—优化器

例如,假设你总是在开始行走前等待 10 分钟(即×= 1/6 小时)。在最坏的情况下,你离开车站后,公共汽车马上就来了。你在 10+45 = 55 分钟后到达你朋友的住处,而离线解决方案需要 10+T5εT7+9 = 19 分钟。因此,你比你知道公共汽车确切到达时间的解差 2.89 倍(55 分钟对 19 分钟)。相比于运动员c = 5(即 x = 0 小时),= 2.89 已经是很大的进步了。但是你能做得更好吗?

*如何导出最佳等待时间 x *?关于最优在线解 *x *** ,有两个错误你会后悔的。你要么开始走得太早,要么走得太晚。在过早的情况下,您会后悔没有等待更长时间,因为相应的离线解决方案总是要等待公共汽车。在来不及的情况下,你后悔没有早点开始走,因为线下解决方案会在你到站的时候立刻开始走。最佳在线策略会平衡这两种情况,使你的遗憾最小化。

为最佳在线策略平衡过早和过晚的错误

重新排列到 x 提供了最优等待算法。通过将 x 插入上述误差竞争比之一,可导出相应的最优竞争比 c *。这是最低的竞争比率。如果公交车速度始终大于步行速度, c 永远不会大于 2。

最佳在线策略和最佳竞争比率

在具体示例中,最佳等待时间是×0= 0.6 小时(3/4–3/20 = 0.6),即 36 分钟(0.6 * 60 = 36)。对应的竞争比是c**= 1.8(2–4/20 = 1.8),其中离线解 45 分钟后到达,在线解需要 81 分钟(36 + 45 = 81)。基于给定的公交车和步行速度,没有更好的策略。这意味着任何其他等待时间总是导致更高的 c

*为了更好地理解,考虑以下所有在线策略在 0 到 120 分钟等待时间范围内的竞争比率的可视化。最低点代表 c *,对应 *x *** 。

各种在线策略的竞争比率

结论

那么下次你在公交车站,公交车没来的时候,你打算怎么办?在你开始走路之前,想想这篇文章。有一种策略永远不会比知道确切未来的策略差两倍。最佳等待时间是步行旅行时间减去公共汽车旅行时间。这是一个最坏的策略,你对未来毫无所知。认识到这一点:如果在实践中你做出了不同的决定,你要么假设其他信息,要么你没有理性地行动。

缩小!你还学到了什么?首先,您解决了一个与未来相关的优化问题,而没有做出任何统计或分布假设。使用的方法叫做竞争分析。第二,您的解决方案有一个性能保证,它基于一个知道确切未来的解决方案。

想想商业和私人生活中的其他应用:制造还是购买?租还是买?…做还是不做?

参考

使用机器学习和 Python 实现最佳欠采样

原文:https://towardsdatascience.com/optimal-undersampling-using-machine-learning-with-python-d40779583d53

以下是如何使用几行代码智能地对信号进行欠采样

照片由 Unsplash 上的 Prateek Katyal 拍摄

在大数据时代欠采样是数据处理的关键部分。
即使我们能够以非常严格的方式定义欠采样,但我们的想法是,我们希望获得一个长、大、耗时且消耗内存的信号,并用一个更小且耗时更少的信号来代替它。

在这篇文章中,你将学习如何使用机器学习和几行代码以一种“智能”的方式对你的信号进行欠采样。我们将从描述欠采样背后的优化任务开始,然后我们将解释使这种优化尽可能高效的策略。

让我们开始吧:)

1.介绍

当然,每个欠采样任务最终都是一个优化任务。
我来解释一下原因。

考虑这个函数:

作者图片

我用了 100 个点来制作这个函数,正如你所看到的,这足以让它看起来非常平滑。那么欠采样是怎么回事呢?

以一种非常天真的方式和 2D 信号,它是这样工作的:

  1. 考虑沿 x 轴的随机点(或采样均匀分布的点)。当然,你应该选择少于你的信号的原始点。在我们的例子中,你可以考虑 10 个数据点,或者 20,或者 30,但是少于 100。
  2. 使用插值技术连接这些点。最简单的方法是线性插值。基本上你只是用直线连接这些点。

很简单。让我们看看如果我们从 100 个点中随机选择 10 个点会是什么样子。

作者图片

好吧…你们对这种欠采样满意吗?我打赌你不是。

让我们考虑更多的数据点,看看它是什么样子的。具体来说,让我们把数据点翻倍。

作者图片

当然更好。

让我们试着看看 70 会发生什么:

作者图片

那样的话,我们基本上就完美了。尽管如此,我们考虑了 70%的数据点,所以即使我们实际上是欠采样,我们仍然考虑了很多点。

现在应该更清楚为什么我们每次尝试欠采样时都要考虑一个优化问题了:

  • 如果使用太少的数据点进行欠采样:您的重建会有损失(高效率,低重建质量)
  • 如果您使用太多的数据点进行欠采样:您的重建是有损失的,但是该方法不是很有效或者根本没有效率。(低效率,高重建质量)

作者图片

2.提议的算法

从这个分析中我们可以得到的信息是随机均匀采样 x 轴不是最优策略

我们提出的算法在传统算法的第一步和第三步之间增加了另一步:

  1. 考虑沿 x 轴的随机点(或采样均匀分布的点)。当然,你应该选择少于你的信号的原始点。在我们的例子中,你可以考虑 10 个数据点,或者 20,或者 30,但是少于 100。
  2. 对低质量区域进行过采样
  3. 使用插值技术连接这些点。最简单的方法是线性插值。基本上你只是用直线连接这些点。

让我们更仔细地分析步骤 2。
如果我们考虑 10/20 数据点图像,我们可以看到有些区域的误差比其他区域大。例如,在尾部误差最小,因为在那里考虑直线而不是高斯曲线基本上是无害的。然而,如果我们想在高峰期使用线路,我们会遇到麻烦,而且质量会更低。

在我们的算法中,我们考虑了目标信号和插值采样信号之间的绝对差异。然后我们如果距离太大就加点。我们来说清楚一点。

假设原始信号有 N 个起始点,我们从 N 个采样点开始。我们将执行以下操作:

1\. **Interpolate** the signal and compute, from your n starting point, an interpolated signal with values in the original N data points 2\. **Compute** the absolute normalized difference between the interpolated signal in the N data points and the original N data points values 3\. **Add** the point if the difference is larger than a certain threshold (let's say 5 percent of the maximum)

让我们编码:)

3.动手实施

导入库:

创建信号:

定义欠采样和插值函数:

定义算法:

4.分析结果

此过程的结果可以显示在

作者图片

正如我们所看到的,我们从欠采样的通用信号开始,然后我们找到一个真正的优化版本的信号,在需要它的区域中有更多的数据。

5.n 维信号

在原始的文件中,您可以找到 N 维信号的相同实现。

6.结论

如果你喜欢这篇文章,你想知道更多关于机器学习的知识,或者你只是想问我一些你可以问的问题:

A.在 Linkedin 上关注我,在那里我发布我所有的故事
B .订阅我的 简讯 。这会让你了解新的故事,并给你机会发短信给我,让我收到你所有的更正或疑问。
C .成为 推荐会员 ,这样你就不会有任何“本月最大数量的故事”,你可以阅读我(以及成千上万其他机器学习和数据科学顶级作家)写的任何关于最新可用技术的文章。

🟩🟩🟩🟩🟩最优世界

原文:https://towardsdatascience.com/optimal-wordle-d8c2f2805704

你读到的关于沃尔的一切都是错的。或者,至少,我读到的关于选择你的第一个单词猜测的所有东西都做了一个糟糕的假设:你应该尽可能多地猜测最常见的字母。现在,事实证明,最佳的第一个猜测(我将谈到)是由非常常见的字母组成的,但它在平均字母频率的单词列表中排名第 24 位。为什么猜最常见的字母不是最佳策略?因为这些字母不是很有辨别力:它们出现在很多单词中,所以如果你猜的时候看到黄色或者绿色,你还有很多单词要筛选。

让我们看一个简单的例子。假设你在玩一个两个字母的单词游戏,游戏中有一个简短的(非英语的)单词列表 AB,AE,AS,AT,BE,se,TE。还假设列表中的所有单词同样有可能被选为隐藏单词。显然,这个列表中最常见的字母是 A 和 E,你瞧,你甚至可以把它们放在一起,猜 AE 开始,这保证你会看到绿色!

如果你从猜测 AE 开始,你得到🟩🟩的概率是 1/7,⬛🟩的概率是 3/7,🟩⬛的概率是 3/7。你有 1/7 的机会赢,否则你知道元音。如果是 A,你列表中剩下的单词是 AB,AS,AT。如果是 E,就剩下 BE,s E,TE 了。所以你有 6/7 的机会剩下 3 个单词可以猜。

现在,让我们先看看猜测 BE。同样,你有 1/7 的机会获得🟩🟩奖(这总是正确的,不管你的第一次猜测是什么)。你有 7 分之 3 的机会获得⬛🟩。你还有 2/7 的几率得⬛⬛ (AS,AT),1/7 的几率得🟨⬛ (AB)。

如果你猜错了,你保证还有三个可能的单词。如果你猜对了,你会有 50%的时间猜对 3 个单词,33%的时间猜对 2 个单词,另外 17%的时间只猜对 1 个单词。所以你有 50%的机会比从 AE 开始时过得更好。

在这个基本示例中,很容易完成所有案例。这如何根据 12,972 个单词的列表(这是 Collins Scrabble Words 中的五个字母单词的数量)推广到五个字母的 Wordle?答案是熵。在信息论中,熵是对事物随机程度的一种度量。在这种情况下,我们想猜一个结果最随机的单词;我们最不可能预测绿色、黄色和灰色瓷砖图案的猜测。在一个极端,如果我们已经知道一个猜测的结果,比如说两次猜测同一个单词,那么这是一个浪费的猜测——即使它确实包含很多绿色。更多的熵意味着有更多可能的结果,并且这些结果的概率更加平衡。

警告:数学来了。我会长话短说。信息熵被定义为

我们如何理解这个价值?首先,概率乘以某物的和是一个期望值,所以熵是结果的负对数概率的期望值:

结果的负对数概率是多少?在这种情况下,结果的概率是符合该结果的单词数除以列表中的单词总数。这一事实,以及对数的一些规则,使我们得出这样的结论:

这是对我们在解决问题上迈出了多大一步的衡量。我们从最初的单词列表开始,最后得到一个更短的符合我们观察结果的单词列表,所以这个差异就是我们解决了多少问题。对数是因为这个过程是一个分支搜索:每次猜测,我们将剩余的单词分成几个桶,结果告诉我们将保留哪个桶。如果你相对平均地拆分,那么拆分到一个单词所需的次数是你开始时单词数的对数。

综上所述,熵是猜测一个特定单词所产生的解决方案的预期进展量。我写了一个 Python 脚本来计算所有可能猜测的熵,给定一个单词列表(并假设答案是从列表中均匀随机选取的)。运行科林斯拼字游戏单词,前 11 个第一猜测是:
1。TARES
2。拉雷斯
3。罗音
4。费率
5。鼻孔
6。传说 7。托雷什 8。雷亚尔
9。敢不敢
10。阿尔
11。传说

就像我之前说的,这些单词确实包含一些非常常见的字母。然而,第一次猜猜会得到最多颜色的瓷砖,ROSE/AEROS/SOARE,分别排在第 298、25 和 20 位。此外,前 11 个单词中只有一个单词有三个元音。第一个有四个元音的单词是 AUREI,排名 1044。知道单词中的元音实际上没有多大帮助。

你怎么想呢?我在什么地方犯了错误吗?这会影响你玩 Wordle 的方式吗?请在下面的评论中告诉我。

使用 Geopandas 和 OpenStreetMap 优化送餐员的位置

原文:https://towardsdatascience.com/optimising-the-location-of-food-delivery-couriers-using-geopandas-and-openstreetmap-e771433e5e9f

快递员应该把自己定位在哪里,才能最好地接触到最多数量的餐厅?

罗文·弗里曼在 Unsplash 上的照片

在“零工经济”中,食品快递员希望处理尽可能多的食品提货和交付。本文将探讨如何使用地理空间工具(如 OpenStreetMap 和 Geopandas)来优化送餐员的位置,使其位于餐厅密度较高的区域,从而增加送餐上门的机会。

如果我们站在送餐员的立场,我们可以问自己以下问题:

"我应该在哪里接收更多的餐馆送餐?"

该问题与寻找潜在机会密度最高的区域(即食品交付提货)有关。为了简单起见,我们不考虑餐馆的受欢迎程度,因为这当然会影响送餐的数量,因此我们坚持在空间背景下考虑问题,例如餐馆的位置。

导入和安装相关库

我们将使用 osmnx 库来查询 OpenStreetMap 并检索我们需要的数据。其他示例将帮助我们处理地理空间数据,主要是 geopandas 。我们可以从安装和导入我们需要的库开始:

#Installing and importing libraries
!pip install osmnx
!pip install contextily folium==0.12.1.post1 geopandas==0.10.2 matplotlib mapclassify==2.4.3 pygeos==0.12.0 pyproj==3.2.1import geopandas as gpd
from shapely.geometry import Point, Polygon
import contextily as cx
import osmnx as ox
import shapely
import pandas as pd

提取餐馆数据

使用 osmnx,我们将从查找一个城市中的所有餐馆开始;我们将使用挪威奥斯陆作为我们的测试案例,因为与大城市相比,餐馆的数量较少(也更容易管理)。这非常简单,因为幸运的是奥斯陆很容易被认出是一个地方:

#choosing area of interest
place = 'oslo'#Selecting amenities and extracting information
tags = {"amenity":"restaurant"}
food = ox.geometries_from_place(place, tags=tags)
print(food.shape)food = food[['name', 'geometry']]

这将生成一个形状为 (527,114) 的地理数据框架。关于这些餐馆,我们可以删除许多冗余的元数据列,因为我们只关心位置,它在“几何”列中存储为点或多边形的形式。

数据处理

由于多边形也作为此地理数据框的一部分返回,因此我们可以通过获取多边形的质心来快速将它们转换为点。我们所有的餐馆数据行将对应于纬度和经度的一个点集:

#Convert polygons to points
food['geometry'] = food['geometry'].apply(lambda x : x.centroid if type(x) == Polygon else x)

为了绘图,我们将更改点集坐标的 crs,这可以使在底图上绘图更容易(例如,从上下文中),并清理我们的列。最后,我们可以使用 Geopanda 的 explore() 生成一个交互式地图,用于可视化餐厅数据:

#Simplifying the df and converting crs in case of plotting on other map tiles
food = food.to_crs(epsg=3857)
food.explore()

绘制了奥斯陆 OSM 的餐馆数据。图片作者。

所以现在我们可以看到餐馆分布在哪里。不出所料,有些地区的餐馆密度更高。我们假设的送货快递员会对这些感兴趣,因为他们应该能够更好地接收更多餐厅的订单。让我们放大来看看市中心的餐厅是如何分布的:

绘制了奥斯陆 OSM 的餐馆数据。图片作者。

我们已经进入了问题陈述的细节,即在我们的奥斯陆地图上选择一个地方,在那里快递员可以接收更多餐馆的订单。 你认为上面地图上的那个位置会在哪里? 猜一猜,最后发现…

在这种情况下,我们会说,如果送货员在 5 分钟的步行距离内,即大约 400 米,他们就可以从餐馆接收订单。如果我们在我们所有的餐馆周围添加一个 400 米的缓冲区,我们可以更好地了解这些缓冲区重叠的地方(即在多个餐馆范围内的位置会显得更不透明):

food['geometry'] = food.buffer(400)
food.explore(style_kwds={'stroke':False, 'fillOpacity':0.05})

奥斯陆 OSM 餐厅的缓冲区。图片作者。

数餐馆的十字路口

我们现在对在哪里定位有了更好的想法,但是我们如何改进呢?最好是量化有更多重叠餐馆缓冲区的区域。这个解决方案已经存在于 ArcGIS Pro 中,它创建多边形并计算相交要素的数量,但是我发现了一个很好的解决方案在这里。这现在给出了新创建的相交多边形 的地理数据框 ,其中包括一个相交计数。我们可以根据相交点的数量对这些多边形进行排序,并将多边形转换为点,以获得与最多餐馆缓冲区相交的前十个位置:

#Count the overlapping features of restaurant buffers
food_intersections = count_overlapping_features(food)food_intersections = food_intersections.sort_values('count_intersections', ascending=False)
food_intersections['geometry'] = food_intersections.centroid#Plot results
food_intersections[:10].explore(marker_type = 'circle', marker_kwds = {'radius':20, 'fill':True})

餐厅取菜的最佳位置是…?

这为我们提供了以下位置,根据奥斯陆餐厅的缓冲半径和位置,这些位置可以为最多数量的餐厅安排快递员:

餐厅缓冲区交叉口数量最多的位置地图。图片作者。

有趣的是,我们看到我们有一个单一的位置点集群(即许多相交多边形靠近在一起)以及两个小得多的区域,分别位于西边和东边。基于交叉点计数选择前 10 个质心意味着这些质心中的每一个都与大约 25 个餐馆相交。那么,这和你的猜测相比如何? 我们现在可以向我们假设的食品配送快递员推荐,如果他们位于我们发现的区域,他们有更高的机会收到食品提货订单。

摘要

在这篇文章中,我们思考了如何让送餐员更好地接收更多来自餐厅的外卖订单和减少闲置时间。通过提取餐厅数据,创建假设的“可用性”缓冲区,并找到大多数缓冲区重叠的区域,我们可以精确定位等待提货订单的最佳位置。OpenStreetMap 是一个很好的数据集,那么为什么现在在另一个城市尝试一下,看看最佳景点的分布是什么样的。

感谢阅读!

使用表格编辑器优化 Power BI 数据模型

原文:https://towardsdatascience.com/optimize-power-bi-data-model-with-tabular-editor-95449aae5af2

表格编辑器是不是一种魔杖,只需点击几下,就能帮你把数据模型“怪物”变成“美女”?差不多!了解如何利用表格编辑器中的最佳实践分析器扩展来快速获得最佳形状的模型

作者图片

在上一篇文章中,我们已经解释了如何利用 DAX Studio 免费外部工具来对执行不佳的 DAX 查询进行故障排除。现在是时候将我们的焦点转移到另一个非常流行的外部工具——表格编辑器!我已经写了关于表格编辑器和它的一些特性,这些特性可以极大地促进您的 Power BI 开发

表格编辑器是一个全面的工具,可能对各种开发场景都有帮助。然而,由于 DP-500 考试的重点是 使用表格编辑器 优化数据模型的主题,让我们解释一下该工具如何帮助您实现这一特定目标。

免责声明:由于 DP-500 考试明确地为这个主题指定了表格编辑器 2(免费版本),我们将在本文的剩余部分使用这个版本。当然,您可以在表格编辑器 3 中应用所有相同的步骤,这是该产品的商业版本

但是,现在您可能会问自己:优化数据模型是一个如此广泛的主题,如何用一个工具来帮助我应用跨越多个书籍、视频、白皮书和博客帖子的所有建议呢?表格编辑器是不是一种魔杖,只需点击几下,就能帮你把数据模型“怪物”变成“美女”?!

当然不是!了解数据模型优化的一般最佳实践,例如避免双向关系,减少列基数,尽可能避免 DirectQuery ,或者移除自动日期/时间隐藏表,仍然是关键需求!但是,Tabular Editor 可以帮助您快速、轻松地识别这些实践的潜在违规行为——基于所获得的洞察力,您可以决定是要应用推荐的实践还是保留原始的数据建模逻辑。

在我向您展示如何使用表格编辑器实现这一特定目标之前,我必须承认,我没有对您完全坦诚…单靠表格编辑器无法将您的数据模型从灾难中拯救出来。您必须使用表格编辑器扩展,称为最佳实践分析器。

最佳实践分析器简介

当处理有问题的数据模型时,Best Practice Analyzer 将很快成为您最好的朋友。这个工具,或者更好的说法是 extension,是由微软的 Michael Kovalsky 开发的。您可以在本文的中找到关于工具本身的更多细节。

简而言之,最佳实践分析器(BPA)扫描您的表格数据模型,并警告您模型中的所有“问题”。有些“问题”就是——带双引号的“问题”。这意味着您可以忍受这些“问题”,而不会牺牲模型性能。

BPA 依赖于规则—每个规则都属于某个类别,比如 DAX 表达式、性能、格式等等。显然,并不是所有的规则对模型性能都有同样的重要性。也就是说,从模型性能的角度来看,如果您将数据模型对象的第一个字母大写,或者隐藏外键,都没有关系。但是,如果您尚未对大型表进行分区,如果您的模型包含许多双向或 M:M 关系,或者如果您的模型中有许多计算列和计算表,这种影响可能会很大。

在表格编辑器中使用 BPA

安装表格编辑器时,最佳实践分析器不是现成的。您需要通过在表格编辑器的高级编辑器窗口中运行以下脚本来手动添加它:

System.Net.WebClient w = new System.Net.WebClient(); 

string path = System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData);
string url = "https://raw.githubusercontent.com/microsoft/Analysis-Services/master/BestPracticeRules/BPARules.json";
string downloadLoc = path+@"\TabularEditor\BPARules.json";
w.DownloadFile(url, downloadLoc);

你也可以从 GitHub 下载 BPARules.json 文件,如这里所述。

作者图片

这是我的数据模型,您可能注意到了,我有意对在线销售事实表和客户维度表之间的关系启用了双向过滤。现在,我将切换到表格编辑器,并从表格编辑器工具选项卡中启动最佳实践分析工具:

作者图片

当我运行 Best Practice Analyzer 来扫描我的数据模型时,会弹出一个新窗口,显示所有的发现和警告:

作者图片

我可以看到 222 个对象违反了 19 条 BPA 规则!看来有人没做作业。然而,这个模型是故意这样构建的,事先没有任何优化。此外,您可能会注意到,125 个对象违反了没有描述的规则,这从性能优化的角度来看是完全不相关的。

但是,这里有一个与数据模型性能非常相关的问题,它对我之前在 Power BI Desktop 中创建的双向关系提出了警告:

作者图片

如果我右键单击特定对象,会有一系列可能的选项可供选择:

作者图片

其中一个选项是对该对象应用即时修复,一旦这样做,警告就会消失。您也可以决定忽略整个规则,就像我对那些“无描述”对象所做的那样:

作者图片

但是,还有更多。在我看来,最佳实践分析器最强大的功能是表格编辑器中 Tools 选项卡下的 Manage BPA Rules 选项。一旦您打开它,您将不仅能够更改规则逻辑和定义(假设您熟悉 C#脚本),而且在描述字段中,您还会找到一篇文章的链接,该文章解释了为什么某个规则是——RULE!

作者图片

这是一个惊人的特性,因为它为您提供了学习和理解表格数据模型优化背后的推荐实践的可能性,而不是盲目地应用所有建议的修改。

表格编辑器还使您能够扩展预建的规则集,并定义自己的规则!关于如何做到这一点的更多信息可以在这里找到。

结论

我猜你已经知道表格编辑器有多神奇了!尽管它的功能不仅限于优化表格数据模型,但在本文中,我们解释了表格编辑器如何与最佳实践分析器扩展协同工作,帮助您对数据模型执行快速健康检查。不仅如此,基于这个检查的结果,您可以快速地应用修复和推荐的实践来提高整个模型的效率。

感谢阅读!

成为会员,阅读媒体上的每一个故事!

通过更好的数据存储优化 Python 性能

原文:https://towardsdatascience.com/optimize-python-performance-with-better-data-storage-d119b43dd25a

使用更快更轻便的数据格式

弗兰克·麦肯纳在 Unsplash 上拍摄的照片

由于其简单性, CSV 文件格式已经在许多 Python 项目中被普遍用作数据存储。不过,是!因此,如果您的项目变得更加广泛,那么不再使用 CSV 可能不是一个好主意。

作为文本文件, CSV 占用更大的磁盘空间更长的 加载时间 = > 性能更低。在本文中,两种更好的数据格式( ParquetFeather) 已经被证明在各个方面(读取时间、写入时间、磁盘存储)都优于 CSV,如下图所示。

CSV |拼花|羽毛性能概述—数据:500 万行& 5 列(作者)

这是[CSV]和[拼花地板或羽毛]之间的巨大差异。我们稍后将回到这次表演的细节。现在,让我们开始介绍拼花地板/羽毛文件格式。

拼花格式

Parquet 是一种面向列的数据文件格式,提供了高效的数据压缩和编码方案,增强了批量处理复杂数据的性能。它最初是在 Apache Hadoop 生态系统中开发和使用的。后来,它被 Apache Spark 采用,并被 Amazon、Google 和 Microsoft 等云供应商广泛用于数据仓库。

在 Python 中,Pandas 模块本来就支持 Parquet,所以您可以直接在项目中集成 Parquet 的使用,如下例所示。

例如:

# Import module **import** **pandas** **as** **pd**# Read a Parquet file as df
**df = pd.read_parquet('<filename>.parquet')**# do something with df ...# Write df as a Parquet file
**df.to_parquet(‘name.parquet’)**

羽化格式

它是使用 Apache Arrow 开发的,用于快速、可互操作的帧存储。从 Pandas 1 . 1 . 0 版本开始,它原生支持羽毛格式。您可以像 CSV/Parquet 一样读取/写入羽毛文件格式。

例如:

# Import module **import pandas as pd**# Read a Feather file as df
**df = pd.read_feather('<filename>.feather')**# do something with df ...# Write df as a Feature file
**df = pd.to_feather('<filename>.feather')**

特性试验

一个由五列五百万行随机数组成的数据帧被用来做性能测试(大约 500 Mb 大小的 CSV)。它是用 Numpy 模块生成的,如下面的脚本所示。测试是在谷歌实验室进行的。

为性能测试生成一个随机 df。(作者)

书写时间比较

现在,让我们检查一下将随机生成的 500 万行数据帧写入 CSV |Parquet | Feather 所用的时间。

将 500 万行熊猫的 df 写入 CSV | Parquet | Feather 文件。(作者)

嗯,我们可以看到,编写一个拼花和羽毛文件只需要大约 2 秒钟,而编写 CSV 文件需要 50 多秒钟!!
( ~快 25 倍)

阅读时间对比

接下来,我们来对比一下阅读速度。

读取时间对比— CSV |拼花|羽毛文件。(作者)

很明显,拼花和羽毛的 性能比 CSV(快~12 倍)。

存储比较

****储物对比— CSV |拼花|羽毛(作者)

****拼花和羽毛在磁盘空间上更轻 (~2.6 倍轻)

拼花地板与羽毛

用哪个?

  • ****羽毛根据我在 Google Colab 的测试环境,读写性能略好。
  • Parquet 从网络读取时读取性能更好。
  • ****羽搭配固态硬盘(SSD)性能更好。
  • Parquet 与 Hadoop 系统有更好的互操作性。

所以,如果你的项目主要是在 Python 或者 R 上(没有集成 Hadoop),并且有 SSD 作为数据存储,那就用 Feather 吧。否则,使用拼花地板。

结论

在 Python 中使用 Parquet 或 Feather 格式可以显著提高数据写入、读取和存储性能。

我希望你喜欢它,并发现它对你的日常工作或项目有用。如果你有任何问题,请随时联系我。

关于我和我所有的博客内容:链接

平安健康!
感谢阅读。👋😄

优化 PyTorch 性能以提高速度和内存效率(2022)

原文:https://towardsdatascience.com/optimize-pytorch-performance-for-speed-and-memory-efficiency-2022-84f453916ea6

你应该知道的 18 个 PyTorch 技巧:它们是如何工作的以及为什么工作

调整深度学习管道就像找到正确的齿轮组合(图片由 Tim MossholderUnsplash 上提供)

你为什么要读这篇文章?

深度学习模型的训练/推理过程涉及许多步骤。在时间和资源有限的情况下,每次实验迭代越快,我们就越能优化整个模型的预测性能。我收集并整理了一些 PyTorch 技巧和提示,以最大化内存使用效率并最小化运行时间。为了更好地利用这些技巧,我们还需要了解它们是如何工作的以及为什么工作。

我首先提供了一个完整的列表和一个组合代码片段,以防您想直接优化您的脚本。随后,我将逐一详细介绍它们。对于每个技巧,我还提供了代码片段,并注释了它是特定于设备类型(CPU/GPU)还是模型类型。

清单:

  • 数据加载
    1。将活动数据移动到 SSD
    2。Dataloader(dataset, **num_workers**=4*num_GPU)
    3。Dataloader(dataset, **pin_memory=True**)
  • 数据操作 4。直接创建矢量/矩阵/张量作为torch.Tensor,并在设备上运行操作
    5。避免 CPU 与 GPU
    6 之间不必要的数据传输。使用torch.**from_numpy**(numpy_array)torch.**as_tensor**(others) 7。当适用于重叠数据传输时使用tensor.to(**non_blocking=True**)8。通过 PyTorch JIT 将点态(元素态)操作融合到一个内核中
  • 模型架构
    9。将所有不同架构设计的尺寸设置为 8 的倍数(对于混合精度的 FP16)
  • 训练
    10。将批处理大小设置为 8 的倍数,并最大化 GPU 内存使用率
    11。使用混合精度进行正向传递(而不是反向传递)
    12。在优化器更新权重
    13 之前,将梯度设置为**None**(例如model.zero_grad(**set_to_none=True**))。梯度累积:每隔一批 x 更新重量,以模拟更大的批量
  • 推论/验证
    14。关闭渐变计算
  • CNN(卷积神经网络)具体
    15。torch.backends.cudnn.benchmark = True
    16。使用 4D NCHW 张量
    17 的 channels_last 存储器格式。关闭批量规范化之前的卷积图层的偏差
  • 分布式优化
    18。用DistributedDataParallel代替DataParallel

结合第 7、11、12、13 条提示的代码片段:

*# Combining the tips No.7, 11, 12, 13: nonblocking, AMP, setting 
# gradients as None, and larger effective batch size*model.train()
*# Reset the gradients to None* optimizer.zero_grad(**set_to_none=True**)
scaler = **GradScaler**()
for i, (features, target) in enumerate(dataloader):
 *# these two calls are nonblocking and overlapping*    features = features.to('cuda:0', **non_blocking=True)**
    target = target.to('cuda:0', **non_blocking=True**) *# Forward pass with mixed precision*    with torch.cuda.amp.**autocast**(): *# autocast as a context manager*
        output = model(features)
        loss = criterion(output, target) *# Backward pass without mixed precision
    # It's not recommended to use mixed precision for backward pass
    # Because we need more precise loss*
    scaler.scale(loss).backward() *# Only update weights every other 2 iterations
    # Effective batch size is doubled*    **if (i+1) % 2 == 0 or (i+1) == len(dataloader):
** *# scaler.step() first unscales the gradients .
        # If these gradients contain infs or NaNs, 
        # optimizer.step() is skipped.*        scaler.step(optimizer) *# If optimizer.step() was skipped,
        # scaling factor is reduced by the backoff_factor 
        # in GradScaler()*    scaler.update() *# Reset the gradients to None*        optimizer.zero_grad(**set_to_none=True**)

高级概念

总体而言,您可以通过 3 个关键点来优化时间和内存使用。首先,尽可能减少 i/o(输入/输出),以便模型管道被绑定到计算(数学受限或数学受限),而不是绑定到 i/o(带宽受限或内存受限)。通过这种方式,我们可以利用 GPU 及其专业化来加速这些计算。第二,尽量重叠流程,节省时间。第三,最大化内存使用效率,节省内存。那么节省内存可以实现更大的批量,从而节省更多的时间。有更多的时间有助于更快的模型开发周期,并带来更好的模型性能。

1.将活动数据移动到 SSD

有些机器有不同的硬盘驱动器,如 HHD 和 SSD。建议将活动项目中使用的数据移动到 SSD(或具有更好 i/o 的硬盘)以获得更快的速度。

CPU # GPU #保存时间

2.异步数据加载和扩充

num_workers=0只有在训练或之前的过程完成后,才会执行数据加载。设置**num_workers** > 0 有望加速该过程,尤其是对于 i/o 和大型数据的扩充。具体到 GPU,本次实验发现num_workers = 4*num_GPU性能最好。话虽如此,你也可以为你的机器测试最好的num_workers。需要注意的是,高num_workers会产生较大的内存消耗开销( ref ),这也是意料之中的,因为内存中同时会处理更多的数据副本。

Dataloader(dataset, **num_workers**=4*num_GPU)

CPU # GPU #保存时间

3.使用固定内存减少数据传输

设置 pin_memory=True 跳过从可分页内存到固定内存的传输(图片由作者提供,灵感来自该图片

GPU 不能直接从 CPU 的可分页内存中访问数据。设置pin_memory=True可以直接为 CPU 主机上的数据分配暂存内存,节省从可分页内存向暂存内存(即固定内存,即页锁内存)传输数据的时间。该设置可与num_workers = 4*num_GPU结合使用。

Dataloader(dataset, **pin_memory=True**)

GPU #保存时间

4.直接创建矢量/矩阵/张量作为火炬。张量和它们将运行操作的设备

每当您需要 PyTorch 的torch.Tensor数据时,首先尝试在您将使用它们的设备上创建它们。不要用原生 Python 或者 NumPy 创建数据然后转换成torch.Tensor。在大多数情况下,如果你打算在 GPU 中使用它们,直接在 GPU 中创建它们。

*# Random numbers between 0 and 1
# Same as np.random.rand([10,5])*
tensor = torch.rand([10, 5], **device=torch.device('cuda:0')**)*# Random numbers from normal distribution with mean 0 and variance 1
# Same as np.random.randn([10,5])*
tensor = torch.randn([10, 5], **device=torch.device('cuda:0')**)

唯一的语法区别是 NumPy 中的随机数生成需要额外的 random ,比如np.**random**.rand() vs torch.rand()。许多其他函数在 NumPy 中都有相应的函数:

torch.empty()torch.zeros()torch.full()torch.ones()torch.eye()torch.randint()torch.rand()torch.randn()

GPU #保存时间

5.避免 CPU 和 GPU 之间不必要的数据传输

正如我在高级概念中提到的,我们希望尽可能减少 i/o。请注意以下这些命令:

*# BAD! AVOID THEM IF UNNECESSARY!* print(cuda_tensor)
cuda_tensor.cpu()
cuda_tensor.to_device('cpu')
cpu_tensor.cuda()
cpu_tensor.to_device('cuda')
cuda_tensor.item()
cuda_tensor.numpy()
cuda_tensor.nonzero()
cuda_tensor.tolist()*# Python control flow which depends on operation results of CUDA tensors*
if (cuda_tensor != 0).all():
    run_func()

GPU #保存时间

6.使用torch.from_numpy(numpy_array) and torch.as_tensor(others) instead of torch.tensor

torch.tensor()总是复制数据。

如果源设备和目标设备都是 CPU,torch.from_numpytorch.as_tensor可能不会创建数据副本。如果源数据是一个 NumPy 数组,使用torch.from_numpy(numpy_array)会更快。如果源数据是具有相同数据类型和设备类型的张量,那么torch.as_tensor(others)可以避免复制数据(如果适用)。others可以是 Python listtuple或者torch.tensor。如果源设备和目标设备不同,那么我们可以使用下一个技巧。

torch.from_numpy(numpy_array)
torch.as_tensor(others)

CPU #保存时间

7.当适用于重叠数据传输和内核执行时,使用tensor.to(non_blocking=True)

重叠数据传输以减少运行时间。(图片由作者提供)

本质上,non_blocking=True允许异步数据传输以减少执行时间。

for features, target in loader:
 *# these two calls are nonblocking and overlapping*    features = features.to('cuda:0', non_blocking=True)
    target = target.to('cuda:0', non_blocking=True) *# This is a synchronization point
    # It will wait for previous two lines*
    output = model(features)

GPU #保存时间

8.通过 PyTorch JIT 将点态(元素态)操作融合到一个内核中

逐点运算(见示例列表)包括常见的数学运算,通常是内存受限的。PyTorch JIT 会自动将相邻的逐点操作融合到一个内核中,以节省多次内存读/写。(挺神奇的吧?)例如,gelu函数可以通过将 5 个内核融合成 1 个来对一百万个矢量加速 4 倍( ref )。更多 PyTorch JIT 优化的例子可以在这里这里找到。

**@torch.jit.script** *# JIT decorator*
def fused_gelu(x):
    return x * 0.5 * (1.0 + torch.erf(x / 1.41421))

CPU # GPU #保存时间

9 & 10.将所有不同建筑设计的尺寸和批量设置为 8 的倍数

为了最大化 GPU 的计算效率,最好确保不同的架构设计(包括神经网络的输入和输出大小/维度/通道数和批量大小)是 8 的倍数甚至更大的 2 的幂(例如 64、128 和高达 256)。这是因为当矩阵维数对齐 2 的倍数时,Nvidia GPUs 的张量核心可以实现最佳的矩阵乘法性能。矩阵乘法是最常用的运算,也可能是瓶颈,所以我们最好能确保张量/矩阵/向量的维数能被 2 的幂整除(例如,8、64、128 和 256)。

这些实验表明,将输出维度和批量大小设置为 8 的倍数(即 33712、4088、4096)可以将计算速度提高 1.3 到 4 倍,而输出维度为 33708,批量大小为 4084 和 4095,它们不能被 8 整除。加速幅度取决于过程类型(例如正向传递或梯度计算)和 cuBLAS 版本。特别是,如果你在 NLP 上工作,记得检查你的输出维度,通常是词汇量。

使用大于 256 的倍数不会增加更多的好处,但也没有坏处。这些设置取决于 cuBLAS 和 cuDNN 版本以及 GPU 架构。你可以在这里找到矩阵维数的具体张量核心要求。由于目前 PyTorch 放大器主要使用 FP16,而 FP16 需要 8 的倍数,因此通常建议使用 8 的倍数。如果你有 A100 这样更高级的 GPU,那么你可能会选择 64 的倍数。如果你使用的是 AMD GPU,你可能需要查看 AMD 的文档。

除了将批处理大小设置为 8 的倍数,我们还最大化批处理大小,直到它达到 GPU 的内存限制。这样,我们可以花更少的时间来完成一个纪元。

GPU #保存时间

11。对正向传递使用混合精度,但不对反向传递使用混合精度

有些操作不需要 float64 或 float32 的精度。因此,将操作设置为较低的精度可以节省内存和执行时间。对于各种应用,Nvidia 报告称,将混合精度与带张量内核的 GPU 结合使用可以将速度提高 3.5 倍至 25 倍( ref )。

值得注意的是,通常矩阵越大,加速度混合精度越高( ref1ref2 )。在较大的神经网络(例如 BERT)中,一项实验表明,混合精度可以将训练加速 2.75 倍,并减少 37%的内存使用(参考)。采用 Volta、Turing、Ampere 或 Hopper 架构的新型 GPU 设备(例如,T4、V100、RTX 2060、2070、2080、2080 Ti、A100、RTX 3090、RTX 3080 和 RTX 3070)可以从混合精度中受益更多,因为它们拥有张量内核架构,该架构经过特殊优化,性能优于 CUDA 内核( ref )。

张量核心的 NVIDIA 架构支持不同精度(图片由作者提供;数据来源

需要注意的是,采用 Hopper 架构的 H100 预计将于 2022 年第三季度发布,支持 FP8 (float8)。PyTorch AMP 可能也会支持 FP8(当前版本 1.11.0 还不支持 FP8)。

在实践中,您需要在模型精度性能和速度性能之间找到一个最佳平衡点。我以前确实发现混合精度可能会降低模型性能,这取决于算法、数据和问题。

利用 PyTorch 中的自动混合精度(AMP)包很容易实现混合精度。PyTorch 中默认的浮点类型是 float32 ( ref )。AMP 通过对一组操作使用 float16 来节省内存和时间(如matmullinearconv2d等,见完整列表)。对于某些操作,AMP 会autocast到 float32(例如mse_losssoftmax等,参见完整列表)。一些操作(如add,见完整列表)将在最宽的输入类型上操作。例如,如果一个变量是 float32,另一个变量是 float16,相加的结果将是 float32。

autocast自动将精度应用于不同的操作。因为损耗和梯度是以 float16 精度计算的,所以梯度太小时可能会“下溢”并变成零。GradScaler通过将损失乘以一个比例因子,根据缩放后的损失计算梯度,然后在优化器更新权重之前取消梯度的缩放,来防止下溢。如果缩放因子太大或太小,并导致infNaN s,那么缩放器将为下一次迭代更新缩放因子。

scaler = **GradScaler**()
for features, target in data:
 *# Forward pass with mixed precision*    with torch.cuda.amp.autocast(): *# autocast as a context manager*
        output = model(features)
        loss = criterion(output, target) *# Backward pass without mixed precision
    # It's not recommended to use mixed precision for backward pass
    # Because we need more precise loss*
    scaler.scale(loss).backward() *# scaler.step() first unscales the gradients .
    # If these gradients contain infs or NaNs, 
    # optimizer.step() is skipped.*    scaler.step(optimizer) *# If optimizer.step() was skipped,
    # scaling factor is reduced by the backoff_factor in GradScaler()*    scaler.update()

您也可以使用autocast作为正向传递函数的装饰器。

class AutocastModel(nn.Module):
    ...
    @autocast() *# autocast as a decorator*
    def forward(self, input):
        x = self.model(input)
        return x

CPU # GPU #保存时间#保存内存

12.在优化器更新权重之前,将梯度设置为None

通过model.zero_grad()optimizer.zero_grad()将梯度设置为零将对所有参数执行memset,并通过读写操作更新梯度。然而,将梯度设置为None将不会执行memset,而只会用操作来更新梯度。因此,将渐变设置为None会更快。

*# Reset gradients before each step of optimizer* for param in model.parameters():
    param.grad = **None***# or (PyTorch >= 1.7)*
model.zero_grad(**set_to_none=True**)*# or (PyTorch >= 1.7)* optimizer.zero_grad(**set_to_none=True**)

CPU # GPU #保存时间

13.梯度累积:每隔一批 x 更新重量,以模拟更大的批量

这个技巧是关于从更多的数据样本中积累梯度,以便梯度的估计更准确,并且权重被更新得更接近局部/全局最小值。这在批量较小的情况下更有帮助(由于 GPU 内存限制较小或每个样本的数据量较大)。

for i, (features, target) in enumerate(dataloader):
 *# Forward pass*    output = model(features)
    loss = criterion(output, target) *# Backward pass*    loss.backward() *# Only update weights every other 2 iterations
    # Effective batch size is doubled*    **if (i+1) % 2 == 0 or (i+1) == len(dataloader):
**        # Update weights
        optimizer.step() *# Reset the gradients to None*        optimizer.zero_grad(set_to_none=True)

CPU # GPU #保存时间

14.关闭推断/验证的梯度计算

本质上,如果您只计算模型的输出,那么推断和验证步骤就不需要梯度计算。PyTorch 使用一个中间内存缓冲区来处理requires_grad=True变量中的操作。因此,如果我们知道我们不需要任何涉及梯度的操作,我们可以通过禁用推断/验证的梯度计算来避免使用额外的资源。

# torch.no_grad() as a context manager:
    with torch.no_grad():
    output = model(input)# torch.no_grad() as a function decorator:
@torch.no_grad()
def validation(model, input):
    output = model(input)
return output

CPU # GPU #保存时间#保存内存

15.torch.backends.cudnn.benchmark = True

在训练循环之前设置torch.backends.cudnn.benchmark = True可以加速计算。因为 cuDNN 算法计算不同内核大小的卷积的性能各不相同,所以自动调优器可以运行一个基准来找到最佳算法(当前的算法是这些这些这些)。当您的输入大小不经常改变时,建议使用“打开”设置。如果输入大小经常变化,自动调谐器需要过于频繁地进行基准测试,这可能会损害性能。向前和向后传播可以加速 1.27 倍到 1.70 倍(参考)。

torch.backends.cudnn.benchmark = True

GPU #CNN #SaveTime

16.对 4D NCHW 张量使用通道 _ 最后存储格式

4D NCHW 被重组为 NHWC 格式(图片由作者从参考获得灵感)

使用**channels_last**内存格式以逐个像素的方式保存图像,作为内存中最密集的格式。原始 4D NCHW 张量在内存中按每个通道(红/绿/蓝)进行聚类。转换后,x = x.to(**memory_format=torch.channels_last**),数据在内存中被重组为 NHWC ( channels_last格式)。你可以看到 RGB 层的每个像素都更近了。据报道,这种 NHWC 格式通过 FP16 的放大器获得了 8%到 35%的加速(参考)。

目前仍在测试阶段,仅支持 4D NCHW 张量和一组模型(如alexnetmnasnet族、mobilenet_v2resnet族、shufflenet_v2squeezenet1vgg族,见全列表)。但我可以肯定地看到这将成为一个标准的优化。

N, C, H, W = 10, 3, 32, 32x = torch.rand(N, C, H, W)# Stride is the gap between one element to the next one 
# in a dimension.
print(x.stride()) *# (3072, 1024, 32, 1)****# Convert the tensor to NHWC in memory* x2 = x.to(memory_format=torch.channels_last)**print(x2.shape)  *# (10, 3, 32, 32) as dimensions order preserved*print(x2.stride())  *# (3072, 1, 96, 3), which are smaller*print((x==x2).all()) *# True because the values were not changed*

GPU #CNN #SaveTime

17.关闭批量规范化之前的卷积图层的偏差

这是可行的,因为从数学上讲,偏差效应可以通过批量标准化的均值减法来抵消。我们可以保存模型参数、运行时间和内存。

nn.Conv2d(..., **bias=False**)

CPU # GPU # CNN #保存时间#保存内存

18.用DistributedDataParallel代替DataParallel

对于多 GPU 来说,即使只有一个节点,也总是首选[DistributedDataParallel](https://pytorch.org/docs/stable/generated/torch.nn.parallel.DistributedDataParallel.html#torch.nn.parallel.DistributedDataParallel)而不是[DataParallel](https://pytorch.org/docs/stable/generated/torch.nn.DataParallel.html#torch.nn.DataParallel),因为[DistributedDataParallel](https://pytorch.org/docs/stable/generated/torch.nn.parallel.DistributedDataParallel.html#torch.nn.parallel.DistributedDataParallel)应用多处理,并为每个 GPU 创建一个进程来绕过 Python 全局解释器锁(GIL)并加速。

GPU #分布式优化#保存时间

摘要

在这篇文章中,我制作了一个清单,并提供了 18 个 PyTorch 技巧的代码片段。然后,我解释了它们如何以及为什么在各个方面逐一工作,包括数据加载、数据操作、模型架构训练、推理、CNN 特定的优化以及分布式计算。一旦你深刻理解了它们是如何工作的,你也许就能在任何深度学习框架中找到适用于深度学习建模的通用原则。

希望您喜欢更高效的 PyTorch 并学习新的东西!

如果你有任何意见和建议,请在下面留下。谢谢你。

我是 Jack Lin, C3.ai 的高级数据科学家,我对深度学习和机器学习充满热情。你可以看看我在 Medium 上的其他文章!

希望我们的深度学习管道将是“火箭般快速”的:D(图片由比尔·杰伦Unsplash 上拍摄)

使用稀疏矩阵优化 Python 中 SK-Learn 模型的训练和预测

原文:https://towardsdatascience.com/optimize-training-predicting-sk-learn-models-in-python-with-sparse-matrices-1612072a025d

使用稀疏矩阵训练 SK-Learn 模型的速度提高了 40 倍

凯西·霍纳从 Unsplash 拍摄的图像

在处理大型数据集和使用 Python 时,一个常见的问题是时间和空间复杂度的效率问题。解决这些问题的常用方法是要么增加计算机的计算能力(这可能很昂贵),要么在 Spark / PySpark 这样的分布式框架中继续你的管道(这可能很麻烦&建立起来很昂贵)。这类问题在行业环境中经常出现,尤其是在处理需要聚集大量用户数据的问题时,这些问题与聚类/推荐系统有关。将所有这些数据输入到效率低下的模型中可能很难处理,因为计算机很可能会耗尽内存,或者需要很长时间来执行。

我们可以通过优化我们训练和预测 sklearn 模型的方式来解决这个问题。我们传入的不是用于训练和预测的向量,而是稀疏向量。正如您将在下面看到的,这将极大地降低用于模型训练和预测的速度和内存。

目录

  • 什么是稀疏矩阵?
  • 实现
    -需求
    -生成数据
    -训练模型
    -预测模型
  • 结束语
  • 资源

什么是稀疏矩阵?

矩阵有两种形式,稀疏矩阵和密集矩阵。如果矩阵中的大多数元素具有零值,则矩阵是稀疏的,而如果矩阵中的大多数元素具有非零值,则矩阵是密集的。对于稀疏或密集的矩阵,没有严格确定的零值与非零值的比例。

稀疏矩阵更有效,因为 0 值被移除,而密集矩阵保持 0。图片由作者提供

在使用推荐引擎、编码(例如 1-hot 编码)、矢量化等时,您最常看到的是稀疏矩阵。在应用 ML 中使用稀疏矩阵是有益的,因为它在时间和空间复杂度上都更有效。它将减少存储对象所需的内存,同时提高计算效率。稍后您将看到,sci-kit learning 非常擅长使用稀疏矩阵,并将减少在大型模型上进行训练和预测所需内存和速度。

当应用于大型稀疏矩阵时,使用标准密集矩阵结构和算法的运算速度慢且效率低,因为处理和内存都浪费在零点上[1]。稀疏数据很容易压缩,并且需要较少的存储空间。一些非常大的稀疏矩阵使用标准的密集矩阵算法是不可行的[1]。
引自维基百科

此图显示了稀疏矩阵数据类型是如何存储在 Scipy 中的,它保存了元素的坐标及其相应的非零值。图片由作者提供

矩阵通常存储为二维数组。数组中的每个条目代表一个元素,该元素可以映射到矩阵中的行和列索引,以对应于该元素中的值。一般来说,对于典型的m x n矩阵,存储该矩阵的内存量将与矩阵的维数成比例,但是对于稀疏矩阵,由于我们只存储非零值,因此导致内存减少。矩阵中非零条目的频率也会对节省内存和时间产生影响。

履行

要求

Python=3.8.8
pandas=1.2.4
scipy=1.6.2
matplotlib=3.3.4
sklearn=0.24.1
uuid=1.30

生成数据

我们将首先使用 random 和 pandas 库综合生成一个包含大量零值的数据集。通过使用 pandas 中可用的pivot_table函数,我们可以用零替换数据透视表中的所有 nan 值。出于本教程的目的,我们将创建一个具有 50,000 行和 1,001 列的数据帧。最后一列将与一个类别相关联,剩余的 1000 列将是训练分类器来预测该类别所必需的特征。

通过使用 scipy 库,我们可以轻松地将与我们要传递到模型中的特性相关联的值转换为稀疏矩阵。现在我们有了一个矩阵和一个稀疏矩阵,我们可以在这两个矩阵上训练一个模型,看看哪个性能更好。我将在 sci-kit learn 的 SVC 模型上对此进行研究,因为已知支持向量机基于其架构具有高训练和预测延迟。sci-kit learn 的文档在他们的实现中证实了这一点这里【2】。

火车模型

从上面的结果可以看出,当使用稀疏向量代替常规向量时,SVC 的训练速度显著提高。根据时间性能,我们看到性能提高了大约 40 倍,这是相当可观的。这不仅减少了训练模型的运行时间,而且在基于稀疏矩阵的性质和结构的记忆上取得了实质性的改进。

预测模型

根据您在上面看到的性能,您可以看到,当在稀疏矩阵中传递时,该模型的预测速度几乎快了 4 倍。这是一个实质性的改进,尤其是考虑到与生产中的模型相关的运行时间和计算成本。这将使实时和批量预测在生产中更容易和有效地实施。

在观察上面的结果时,有几件事情要记住,首先,即使模型没有在稀疏数据上训练,也可以将稀疏向量发送到预测中,反之亦然。其次,速度和性能在我的电脑上,可能比你的慢,也可能比你的快。总的来说,当用稀疏矩阵而不是密集矩阵训练模型时,性能是显著的。如果你想在你自己的机器上重新运行整个管道,你可以下载我写的 Jupyter 笔记本来生成这些结果这里。我注意到的另一件事是,当你试图将稀疏矩阵传入 sklearn 的朴素贝叶斯模型时,会出现错误,这个模型需要密集矩阵作为输入。

结束语

正如您可能已经注意到的,如果您不处理大型稀疏数据集,这个解决方案很可能不会对您的应用程序产生大的影响。当数据集中没有很多零时,将其转换为稀疏矩阵并运行计算不会产生相同的影响(如果有的话)。在这种情况下,我的建议是尝试将您的代码并行化,以便在多个池/线程上运行。这将有助于减少与生成预测和训练相关的时间。

你可以在我的 GitHub 这里找到与本文相关的笔记本。

资源

如果你喜欢这篇文章,你可能也会喜欢我下面写的其他文章。

[## 贝叶斯 A/B 测试解释

towardsdatascience.com](/bayesian-a-b-testing-explained-344a6df88c1a) https://pub.towardsai.net/dynamic-time-warping-explained-fbb24c1e079b

用这个窗口函数优化你的 SQL 代码

原文:https://towardsdatascience.com/optimize-your-sql-code-with-this-window-function-409d3341cb20

通过使用 FIRST_VALUE()来替换您痛苦的 cte

照片由 Say Cheeze 工作室Unsplash 上拍摄

我们都遇到过这种 SQL 代码块…它很容易阅读,但是它的某些部分就是,嗯,烦人的。通常,可以用一个查询编写的代码被视为 CTE。

在清理一些旧的数据模型时,我遇到过很多这样的代码。其他代码很容易修复,因为读起来很痛苦,而且如何改进也很简单。然而,这并不是板上钉钉的事。

我说的是什么 SQL 代码?

这个。

问题是

WITHorders AS (
   SELECT 
      name, 
      model,
      year, 
      date_at_lot
      row_number() over(partition by model, year order by date_at_lot asc) AS order
   FROM cars 
)SELECT
   name AS oldest_car_name,
   model, 
   year
FROM orders
where order = 1 

这是一个你非常想把它写得更好,但又不确定如何去做的 cte。这并不是说它超级低效,但是如果你不断地写这样的查询,读起来就变得多余了。

还有,可以的话为什么不优化一下呢?

解决方案

如果你熟悉窗口功能,那么你可能已经知道我在这里暗示的功能。然而,这个窗口功能并没有得到应有的重视。

当我第一次学习窗口函数的时候,我从来没有接触过这个函数!你经常听说[LEAD()](/how-to-use-sql-rank-and-dense-rank-functions-7c3ebf84b4e8?source=your_stories_page----------------------------------------)[RANK()](/how-to-use-sql-rank-and-dense-rank-functions-7c3ebf84b4e8)ROW_NUMBER(),但从来没听说过FIRST_VALUE()

FIRST_VALUE()返回有序、分区数据输出中的第一个值。

它基本上取代了在一个查询中使用ROW_NUMBER()然后在下一个查询中使用column = 1进行过滤的需要。

它的工作原理与任何其他窗口函数相同,您可以在其中指定希望对数据进行分区的列以及希望对它们进行排序的顺序。

分割

如果您不熟悉的话,PARTITION BY帮助对数据进行分组,以便对每个分区重新开始计数。

例如,使用FIRST_VALUE()时按型号和年份划分会将数据分成不同的组,这些组具有相同的汽车型号和年份。然后,在这些组中,它将根据组的排序方式找到第一个值。

所有 2013 款福特 Escapes 将被分组在一起,然后所有 2013 款福特 Escapes 的 first_value 将作为输出,这取决于ORDER BY子句中指定的内容。

如果不包含PARTITION BY子句,该函数会将整个数据集视为一个单独的分区。

排序

有了 first_value 函数,ORDER BY就显得尤为重要。如果你用错了方法,你可能得到的是最后一个值而不是第一个值。在对数据分区进行排序时,确保使用ASCDESC来正确指定排序。

例如,如果您想要某个型号的最新汽车,您想要ORDER BY date_at_lot DESC 。但是,如果您想要某个型号的批次中最老的汽车,您需要编码ORDER BY date_at_lot ASC

您也可以选择使用LAST_VALUE()函数,其工作方式相同,但选择数据子集中的最后一个值,而不是第一个值。这完全取决于您如何对数据进行排序。

那么,使用ROW_NUMBER()的例子和使用FIRST_VALUE()的例子会是什么样子呢?让我展示给你看。

SELECT 
   FIRST_VALUE(name) OVER(PARTITION BY model, year ORDER BY date_at_lot ASC) AS oldest_car_name
   model,
   year
FROM cars

注意我是如何在函数的FIRST_VALUE()部分包含name的。这样做将导致该函数输出第一辆汽车的名称。您可以在这里指定任何列,但是这个名称最有意义,因为它在不同的型号和年份中都是唯一的。

结论

现在您已经准备好进一步优化您的 SQL 代码了!停止使用ROW_NUMBER()切换到FIRST_VALUE()对第一个输出进行滤波。这个功能的创建是有原因的,现在是我们都开始使用它的时候了!

不断改进你的代码是成为更好的程序员、分析师、工程师等的关键。当我们停止进步,我们就停止成长。我强烈建议对您的旧代码进行这样的小改动,不断优化它,并不断学习。

如果你想获得更多关于编写 SQL、成为一名分析工程师或学习现代数据堆栈的技巧,请订阅我即将发布的时事通讯

在新的增长市场中优化选址决策

原文:https://towardsdatascience.com/optimizing-location-decisions-in-new-growth-markets-bf7b78f4c79

企业在进入新市场时,超越传统地理位置进行选址决策的方法

佩德罗·拉斯特拉在 Unsplash 上的照片

所表达的观点是我基于公开信息和工具的个人观点。

对于处于增长模式的企业来说,缩小选择范围并找出他们下一步应该去的市场可能会非常困难…他们应该进入哪个 MSA、县或城市,是什么让他们对这一选择深信不疑?或者,在当前的市场中,他们应该在哪里加倍投资新的地点或市场?

在你选择了你的市场并需要采取的下一步,你应该在那个市场的什么地方定位之后会发生什么?

一种方法是提取市场内各地区的相关人口统计数据并进行比较——每个邮政编码或城市的人口和家庭收入是多少?千禧一代占人口的比例是多少?每个地区的商业活动是什么?

这种方法受益于数据访问和可识别性。这些地理位置的数据很容易获得,数据的可解释性也很明显,因为我们在日常生活中经常引用城市和邮政编码。

但是,对于一些企业来说,这种方法有局限性,因为:

  1. 根据很久以前开发的城市或美国邮政编码分配,边界是任意的。
  2. 这些边界内的信息可能变化很大——城镇的一边可能呈现出与另一边截然不同的特征,这可能被更广泛的地理数据所掩盖。

那么,当企业处于需要不同于预定义地理位置的信息的阶段时,他们会怎么做呢?特别是,对于希望开设物理位置的企业来说,存在的另一个限制是,他们不能只选择完美的位置;他们对可用的商业房地产充满兴趣。

这意味着我们需要在有限的范围内进行概率性思考——根据可用的人口统计数据和竞争数据,我们认为企业最有可能成功的领域在哪里,然后可以将这些数据作为搜索区域传递给商业房地产经纪人?

新方法

我们希望找到一种方法来解决这个问题,同时保持普通地理的可识别性和直观性。为了防止任意边界的偏差,我们创建了具有可定制间距选择的一致大小的框,然后将数据来源和属性分配给每个框。之后,我们在一个评分函数中分层,对一个区域内的地理位置进行排名和优先排序。

结果是一个区域网格,该网格在给定所提供的评分标准的情况下识别区域内的热点,此外还可以在每个框被单击时访问提供给该评分算法的数据。下面的区块可以间隔 1.25 英里、2.5 英里、5 英里或 10 英里(或者,实际上,任何距离,这些只是为了举例)。

作者图片

通过在得分下方使用半透明地图,我们仍然可以获得传统地理的可解释性。我们可以识别每个盒子的邻居、街道边界和/或公共地理。

在每张地图中,根据行业,我们可以将竞争数据分层,以确定竞争对手选择的位置,这可以帮助企业决定瞄准或避开该区块内的特定区域。

上面点击的方框中的每个点都是已知的牙医。气泡的大小表示多个牙医在同一地址。

用例:开设牙医诊所

考虑一下你打算开一家新的牙医诊所的情况。牙科和一般的医疗保健为位置智能工具提供了引人注目的机会,因为它们通常具有清晰可辨的目标市场和竞争。

面向家庭的儿科牙医将受益于 18 岁以下人口比例高、周围人口规模大、人口不断增长以及中等或低竞争的地区。这些都是我们可以从数据中提取出来的特征,然后调整评分函数以适当地反映这一点。

下面是一个示例,说明除了调整块间距之外,我们如何调整评分标准并让数据工具自动更新。

基于块的框架的一个例子。 自己尝试工具

然后,如前面的图片所示,对于每个区块,我们可以在被点击的框中及其周围映射竞争。在这种情况下,它将是在被点击的框附近操作的任何牙医(或者,在可能的范围内,具有相似目标市场/专业的牙医)。

我们还可以看到,我们的吸引力分数可能会根据所选区域的大小而有所不同。在下面的图片中,从左到右,盒子的间距分别为 1.25 英里、2.5 英里和 5 英里。每个人对吸引人的地方都有略微不同的解释。

例如,在 5 英里处,西北与卡里重叠的区域更有吸引力,但是在 2.5 英里处,我们可以看到吸引力是由重叠城市名称的方框驱动的,而不是它上面的两个方框。

自己尝试一下这个工具,看看如果由你来做决定,你会定位在哪里。

作者提供的图片

限制

像任何分析一样,使用这种方法时,我们需要知道一些限制。最突出的两个是:

  • 小面积受小样本量的影响
  • 在这个粒度级别上可以收集的数据是有限的

当考虑像 1.25 英里选项这样的小区域时,数据可能会有剧烈的波动或异常,需要进一步研究。例如,也许某个地区的大发展使这个小地区的人口增加了两倍,不相称地扭曲了一些指标和得分。因此,虽然我们可以显示这个级别的数据,但由于样本量小,它变得稍微不可靠和有偏差。

在数据限制方面,我们可以收集关于人口及其特征的粒度信息,如年龄或收入。我们还可以绘制兴趣点数据,如客户地址、当前位置或竞争对手位置。

然而,任何只发布到邮政编码级别的数据,如通过劳工统计局发布的一般商业活动和机构,都不能映射到邮政编码级别。大多数可以在邮政编码级别收集的已发布数据都无法翻译到这一详细级别。

尽管存在上述限制,但这种方法在各种业务中都有令人兴奋的应用。如上所述,医疗保健是一个拥有巨大潜力的领域。这也适用于商业房地产经纪人和健身房等消费服务。

许多中小型企业很难访问位置数据,特别是当他们没有分析员工来收集和整理所有信息时。然而,现代数据工具和方法可以使这些信息更容易被企业获取和操作,从而做出下一步发展的关键增长决策。

有兴趣讨论位置分析吗? 在 LinkedIn 上联系我 或在 jordan@jordanbean.com 给我发邮件。

优化 Python 应用程序中的内存使用

原文:https://towardsdatascience.com/optimizing-memory-usage-in-python-applications-f591fc914df5

通过这些简单的技巧和高效的数据结构,找出您的 Python 应用程序使用过多内存的原因,并减少它们的 RAM 使用

Unsplash 无聊的冒险家贝可拍摄的照片

说到性能优化,人们通常只关注速度和 CPU 使用率。很少有人关心内存消耗,直到他们耗尽内存。尝试限制内存使用有很多原因,不仅仅是为了避免应用程序因为内存不足错误而崩溃。

在这篇文章中,我们将探索发现 Python 应用程序中哪些部分消耗了过多内存的技术,分析其原因,并最终使用简单的技巧和内存高效的数据结构来减少内存消耗和内存占用。

为什么要这么麻烦呢?

但是首先,你为什么要费心保存内存呢?除了避免前面提到的内存不足错误/崩溃之外,还有其他节省内存的理由吗?

一个简单的原因就是钱。资源 CPU 和 RAM——都是要花钱的,如果有减少内存占用的方法,为什么还要通过运行低效的应用程序来浪费内存呢?

另一个原因是“数据有质量”的概念,如果有大量数据,那么它会慢慢移动。如果数据必须存储在磁盘上,而不是 RAM 或快速缓存中,那么加载和处理将需要一段时间,从而影响整体性能。因此,优化内存使用可能会有一个很好的副作用,那就是加快应用程序的运行时间。

最后,在某些情况下,可以通过添加更多的内存来提高性能(如果应用程序的性能受内存限制),但是如果机器上没有任何内存,就不能这样做。

找到瓶颈

很明显,我们有充分的理由减少 Python 应用程序的内存使用,但是在我们这样做之前,我们首先需要找到占用所有内存的瓶颈或代码部分。

我们将介绍的第一个工具是memory_profiler。该工具逐行测量特定函数的内存使用情况:

为了开始使用它,我们将它与pippsutil包一起安装,这显著提高了 profiler 的性能。除此之外,我们还需要用@profile装饰器标记我们想要进行基准测试的函数。最后,我们使用python -m memory_profiler对我们的代码运行分析器。这逐行显示了修饰函数的内存使用/分配情况——在本例中是memory_intensive——它有意创建和删除大型列表。

既然我们知道了如何缩小我们的关注范围,并找到增加内存消耗的特定行,我们可能想要更深入地挖掘一下,看看每个变量使用了多少。你可能以前见过用来测量这个的sys.getsizeof。然而,对于某些类型的数据结构,这个函数会给你一些有问题的信息。对于整数或 bytearrays,您将获得以字节为单位的实际大小,但是对于 list 这样的容器,您将仅获得容器本身的大小,而不是其内容的大小:

我们可以看到,对于普通整数,每当我们越过一个阈值时,大小就会增加 4 个字节。类似地,对于普通字符串,每次我们添加另一个字符,就会增加一个额外的字节。然而对于列表来说,这并不成立——在本例中,sys.getsizeof遍历数据结构,只返回父对象的大小。

更好的方法是使用专门设计的工具来分析记忆行为。一个这样的工具是,它可以帮助您获得关于 Python 对象大小的更现实的想法:

Pympler 为asizeof模块提供了相同名称的函数,它可以正确地报告列表的大小以及它包含的所有值。此外,这个模块还具有asized功能,可以给我们进一步的尺寸细分对象的单个组件。

Pympler 有更多的特性,包括跟踪类实例识别内存泄漏。如果您的应用程序可能需要这些东西,那么我建议查看文档中的教程。

节省一些内存

既然我们知道如何寻找各种潜在的内存问题,我们需要找到一种方法来修复它们。潜在的、最快和最容易的解决方案可能是切换到更节省内存的数据结构。

在存储值数组时,Python lists是更需要内存的选项之一:

上面的简单函数(allocate)使用指定的size创建一个数字的 Python list。为了测量它占用了多少内存,我们可以使用前面显示的memory_profiler,它给出了函数执行过程中 0.2 秒间隔内使用的内存量。我们可以看到,生成 1000 万个数字的list需要 350MiB 以上的内存。对一堆数字来说,这似乎太多了。我们能做得更好吗?

在这个例子中,我们使用 Python 的array模块,它可以存储原语,比如整数或字符。我们可以看到,在这种情况下,内存使用的峰值刚刚超过 100MiB。与list相比,这是一个巨大的差异。通过选择适当的精度,可以进一步减少内存使用:

使用array作为数据容器的一个主要缺点是它不支持那么多类型。

如果您计划对数据执行大量的数学运算,那么您最好使用 NumPy 数组:

我们可以看到,NumPy 数组在内存使用方面表现也很好,峰值数组大小约为 123MiB。这比array多一点,但是使用 NumPy,您可以利用快速数学函数以及array不支持的类型,比如复数。

上述优化有助于值数组的整体大小,但我们也可以对 Python 类定义的单个对象的大小进行一些改进。这可以通过使用__slots__ class 属性来完成,该属性用于显式声明类属性。在一个类上声明__slots__还有一个很好的副作用,就是拒绝创建__dict____weakref__属性:

这里我们可以看到Smaller类实例实际上要小得多。没有__dict__会从每个实例中删除整整 104 个字节,这在实例化数百万个值时可以节省大量内存。

上面的提示和技巧应该有助于处理数值和class对象。但是,字符串呢?你应该如何储存它们通常取决于你打算用它们做什么。如果你要搜索大量的字符串值,那么——正如我们已经看到的——使用list是非常糟糕的主意。如果执行速度很重要的话,set可能会更合适一些,但是可能会消耗更多的内存。最好的选择可能是使用优化的数据结构,如 trie ,尤其是用于查询等静态数据集。Python 中常见的是,已经有一个这样的库,以及许多其他类似树的数据结构,其中一些你可以在 https://github.com/pytries 找到。

根本不使用内存

节省 RAM 的最简单方法是首先不要使用它。显然,您无法完全避免使用 RAM,但是您可以避免一次加载全部数据集,而是尽可能地增量处理数据。实现这一点最简单的方法是使用返回一个惰性迭代器的生成器,它按需计算元素,而不是一次全部计算。

您可以利用的更强大的工具是内存映射文件,它允许我们只从一个文件中加载部分数据。Python 的标准库为此提供了mmap模块,可用于创建内存映射文件,其行为类似于文件和字节数组。您可以将它们用于文件操作,如readseekwrite以及字符串操作:

加载/读取内存映射文件非常简单。我们首先像往常一样打开文件进行阅读。然后我们使用文件的文件描述符(file.fileno())来创建内存映射文件。从那里,我们可以通过文件操作(如read或字符串操作(如切片)来访问它的数据。

大多数情况下,您可能更有兴趣阅读如上所示的文件,但也有可能写入内存映射文件:

您将注意到的代码中的第一个不同之处是访问模式变成了r+,它表示读和写。为了展示我们确实可以执行读写操作,我们首先从文件中读取,然后使用 RegEx 搜索所有以大写字母开头的单词。之后,我们演示从文件中删除数据。这不像阅读和搜索那样简单,因为当我们删除一些内容时,我们需要调整文件的大小。为此,我们使用mmap模块的move(dest, src, count)方法,该方法将数据的size - end字节从索引end复制到索引start,在这种情况下,这意味着删除前 10 个字节。

如果您在 NumPy 中进行计算,那么您可能更喜欢它的memmap特性 (docs) ,它适合存储在二进制文件中的 NumPy 数组。

结束语

优化应用程序通常是个难题。它还严重依赖于手头的任务以及数据本身的类型。在本文中,我们研究了查找内存使用问题的常用方法以及修复这些问题的一些选项。然而,还有许多其他方法可以减少应用程序的内存占用。这包括通过使用概率数据结构,如 bloom filtersHyperLogLog 来换取存储空间的准确性。另一种选择是使用树状数据结构,如 DAWGMarissa trie ,它们在存储字符串数据方面非常有效。

**本文原帖martinheinz . dev

* *

优化患者日程安排

原文:https://towardsdatascience.com/optimizing-patient-scheduling-dfad022b1614

优化移动设备上视频的风格转移模型

原文:https://towardsdatascience.com/optimizing-style-transfer-models-for-videos-on-mobile-devices-a924e6f633d6

尝试 PyTorch 照明和闪光灯的灵活性,以创建一个面部卡通模型

iOS ToonClip App 采用本帖描述的人脸转卡通模型——模型、App、图片均由作者提供。

拥抱面部空间演示在:https://huggingface.co/spaces/Jacopo/ToonClip

免费的 iOS 移动应用位于:【https://apps.apple.com/us/app/toonclip/id1536285338

源代码可在(WIP)获得:【https://github.com/JacopoMangiavacchi/ToonClip-ComicsHero

介绍

gan 是构建图像风格传输模型的超级流行的架构,但有时需要相当长的时间来训练它们,更重要的是优化生成器模型,以便能够在移动设备上实时运行。

在这次实验中,我主要关注这三个特定领域:

  1. 利用 PyTorch 照明和闪光灯的灵活性,轻松试验不同的模型架构和主干
  2. 使用定制 Pytorch 感知特征损失在没有 GAN 鉴别器的情况下快速训练
  3. 不断转换到 ONNX / CoreML,并在设备上对模型进行基准测试,以找到更好的移动设备优化

信用

在开始之前,我真的想感谢多伦阿德勒提供了一个伟大的项目,真正激发了我的实验和这篇文章,U2 网络英雄风格转移:

该 U2Net 模型特别实现了 muti _ bce _ loss _ fusion 函数的核心思想,以建立感知特征损失来比较 U2Net 编码器-解码器的所有层输出。

在我的实验中,我采用了一种类似但同时更通用且有望更灵活的技术,该技术使用感知特征损失,在分离的图像模型上进行计算,并且能够独立于用于编码器-解码器的特定模型架构捕获不同的层特征。

具体来说,我试图利用 PyTorch 之上的现代高级框架的所有优势来:

  • 使用 PyTorch Lighting Flash 提供的预训练权重,支持编码器-解码器架构的任何已知图像模型和编码器上的任何图像特征化器后端
  • 使用支持 GPU/TPU 等的 PyTorch 照明实现自定义丢失和快速训练。

资料组

在我的实验中,我重复使用了上面提到的 Doron Adler 在他的 U2Net Heros 风格转换项目中提供的相同数据。这是从 FFHQ 数据集中选择的 100 个高分辨率(1024x1024)人脸图像的图像数据集,以及相关的卡通图像。数据集在上面提到的相同 GitHub repo 上提供,并在 Apache License 2.0 下获得许可

在下面的代码中,您可以看到 PyTorch 数据集类加载并转换这些数据:

正如你可能注意到的,在这个小数据集之上,我使用了 Albumentations 库来添加一些图像增强转换,并且能够在从移动相机捕获的真实图像和视频上推断模型时更好地进行概括。

特别是,由于数据包含作为输入(面部)和输出(风格化面部)的图像,我重用了 Albumentation api 的 ReplayCompose 功能,以确保对每一对面部和风格化面部图像应用相同的随机变换。

在下面的代码片段中,您将看到一个示例,说明如何使用不同的增强过滤器将此图像配置到图像数据集:

你会在这篇文章的后面看到,我使用迁移学习技术从之前在 ImageNet 数据集上训练的模型中导入权重,所以我还在上面的白蛋白管道中添加了一个用 ImageNet stats 配置的标准化步骤。

最后,为了能够在同一个分布上多次重复执行训练循环,在随机分割训练和评估数据中的数据集之前,我指定了一个特定的随机手动种子,然后将该种子重置为初始 PyTorch 随机种子。通过这种方式,我能够每次都使用不同的增强管道随机应用程序进行训练,但仍然在相同的训练和验证分布上工作。

感知特征损失

在 GAN 中,并且通常在风格转移领域中,基于特征激活和风格损失的损失函数的思想是一种众所周知的技术,例如在诸如超分辨率的场景中经常采用。

在这个实验中,我特别遵循众所周知的技术,即使用外部的、预先训练的 VGG 网络从不同的卷积块获得特征,并使用这些特征来弥补不同特征的损失,而不是简单地计算生成的图像和原始标签/样式图像之间的 MSE 或 L1 距离。

来自论文“实时风格传输和超分辨率的感知损失”(https://arxiv.org/abs/1603.08155)的下图说明了这一思想,并且存在许多文章来全面描述这一过程并提供代码的参考实现(包括 Fast。AI MOOC 培训由杰瑞米·霍华德)。

模型结构提供论文

在我的例子中,我重用了 Alper ahmetoluon 在 GitHub Gist 上提供的代码片段(https://Gist . GitHub . com/Alper 111/8233 CDB 0414 B4 CB 5853 F2 f 730 ab 95 a 49),但是我已经更新了它,添加了对每个块特性进行加权的选项,以便遵循 Fast 的论文和杰瑞米·霍华德建议。人工智能课程在这些层的输出上加电格拉米矩阵产品。

我简单地注释了将输入图像规范化为 ImageNet stats 的调用,因为我已经在数据集转换管道中规范化了所有输入的面部和样式图像。

图像到图像建模和分割

图像到图像的风格转换问题最终非常类似于更一般的语义分割问题,并且任何用于该一般图像域的模型架构,如完全卷积编码器-解码器,在风格转换场景中也确实工作良好。

在我的探索中,我希望尽可能灵活地试验不同的特定编码器-解码器模型架构,并能够在以下架构上测试真实的性能结果:' deeplabv3 '、 deeplabv3plus '、 fpn '、 linknet '、 manet '、 pan '、 pspnet '、

我发现 PyTorch Lighting Flash 库在这个特定的上下文中特别有用,因为它专门提供了一个高级的semantic segmentation类,支持上面所有的架构。

此外,它为图像特征骨干的不同组合提供权重,以用于这些全卷积网络(FCN)的编码器部分。例如,在 UNet++上,它专门支持主干,例如:

*'densenet121', 'densenet161', 'densenet169', 'densenet201', 'dpn107', 'dpn131', 'dpn68', 'dpn68b', 'dpn92', 'dpn98', 'efficientnet-b0', 'efficientnet-b0', 'efficientnet-b1', 'efficientnet-b1', 'efficientnet-b2', 'efficientnet-b2', 'efficientnet-b3', 'efficientnet-b3', 'efficientnet-b4', 'efficientnet-b4', 'efficientnet-b5', 'efficientnet-b5', 'efficientnet-b6', 'efficientnet-b6', 'efficientnet-b7', 'efficientnet-b7', 'efficientnet-b8', 'efficientnet-l2', 'gernet_l', 'gernet_m', 'gernet_s', 'inceptionresnetv2', 'inceptionv4', 'mobilenet_v2', 'mobilenetv3_large_075', 'mobilenetv3_large_100', 'mobilenetv3_large_minimal_100', 'mobilenetv3_small_075', 'mobilenetv3_small_100', 'mobilenetv3_small_minimal_100', 'regnetx_002', 'regnetx_004', 'regnetx_006', 'regnetx_008', 'regnetx_016', 'regnetx_032', 'regnetx_040', 'regnetx_064', 'regnetx_080', 'regnetx_120', 'regnetx_160', 'regnetx_320', 'regnety_002', 'regnety_004', 'regnety_006', 'regnety_008', 'regnety_016', 'regnety_032', 'regnety_040', 'regnety_064', 'regnety_080', 'regnety_120', 'regnety_160', 'regnety_320', 'res2net101_26w_4s', 'res2net50_14w_8s', 'res2net50_26w_4s', 'res2net50_26w_6s', 'res2net50_26w_8s', 'res2net50_48w_2s', 'res2next50', 'resnest101e', 'resnest14d', 'resnest200e', 'resnest269e', 'resnest26d', 'resnest50d', 'resnest50d_1s4x24d', 'resnest50d_4s2x40d', 'resnet101', 'resnet152', 'resnet18', 'resnet34', 'resnet50', 'resnext101_32x16d', 'resnext101_32x32d', 'resnext101_32x48d', 'resnext101_32x4d', 'resnext101_32x8d', 'resnext50_32x4d', 'se_resnet101', 'se_resnet152', 'se_resnet50', 'se_resnext101_32x4d', 'se_resnext50_32x4d', 'senet154', 'skresnet18', 'skresnet34', 'skresnext50_32x4d', 'tf_efficientnet_lite0', 'tf_efficientnet_lite1', 'tf_efficientnet_lite2', 'tf_efficientnet_lite3', 'tf_efficientnet_lite4', 'vgg11', 'vgg11_bn', 'vgg13', 'vgg13_bn', 'vgg16', 'vgg16_bn', 'vgg19', 'vgg19_bn', 'xception'*

语义分割实际上是在像素级执行分类的任务,这意味着每个像素将与给定的类别相关联。通常,当这些像素分类器 FCN 模型被用于语义分割时,有必要为最终卷积层指定数量等于预期类别数量的通道,并在最后调用 ArgMax 激活来为每个像素选择得分最高的类别。

在风格转换(图像到图像)场景中,采用 FCN 甚至更容易,因为只需使用具有 3 个通道的卷积层作为最终输出,就可以让模型预测彩色图像。

下面的代码片段显示了如何轻松灵活地获得您想要使用的特定 FCN 模型,提供预期模型架构(unetplusplus)、头部编码器的主干(mobilenetv3_large_100)和迁移学习(imagenet)中重用的特定权重作为参数。

正如您在这个具体测试中所看到的,我使用了 mobilenetv3_large_100 主干来实现移动设备上实时视频推理的特定性能,但当然也可以使用其他主干来获得更高质量的图像以进行最终预测。

培养

不幸的是 PyTorch Lighting Flash 还没有灵活到可以直接用 Flash Trainer 训练上面图像到图像场景的语义分割模型。

无论如何,非常幸运的是,我仍然能够使用 PyTorch Lighting 并编写我自己的 ImageToImage Lighting 模块类,最终训练并验证具有先前描述的 VGG 感知特征损失的模型。

下面的代码片段显示了 ImageToImageModel 类,以及如何实例化该类并将其传递给 PyTorch Lighting Trainer,以便对您拥有的特定 ML 加速启动一个训练和评估循环。

ONNX 导出、结果和 HuggingFace 空间演示

从 PyTorch Lighting 甚至 PyTorch 导出模型到 ONNX 格式现在是一个非常简单的过程。

具体到 PyTorch 照明,我必须调用以下 api 将模型权重首先移回 CPU(显然 ONNX exporter 需要这样做):

还值得一提的是,对于我在该模型实例中使用的 MobileNetV3 主干,我必须指定一个最新的 opset 版本,以便在 ONNX 运行时支持该模型使用的所有新 ops,例如 hardwish 之类的激活函数(有关更多详细信息,请参见下面的 CoreML 段落)。

为了快速测试 ONNX 模型,我发现拥抱面部空间环境非常有用,特别是支持易于使用的 Gradio api。

下面是使用 Gradio 在用户图像上快速测试 ONNX 模型并从 Hugging Face Hub 缓存模型图像的代码:

这款车型的现场 Gradio 演示可以在拥抱脸空间上找到,网址:【https://huggingface.co/spaces/Jacopo/ToonClip

为了方便重复使用,我还在 HuggingFace 模型中心——https://huggingface.co/Jacopo/ToonClip上直接导出了 ONNX 模型

无论如何,以下是由该模型从未经训练的 FFHQ 验证集中生成的一些示例图像:

FFHQ 数据集提供的样本图像以及作者提供的模型生成的相应预测

请在拥抱脸上直接尝试这个演示,并最终使用演示上提供的 UI 功能(https://huggingface.co/spaces/Jacopo/ToonClip)标记任何问题或异常

CoreML 导出和图像处理

苹果提供了一个非常有用的 Python 包,名为CoreMLTools(【https://coremltools.readme.io/docs】T2),可以快速地将 ML 模型从 PyTorch 和 TensforFlow 等不同框架导出到 CoreML 格式,允许模型在苹果生态系统上可用的特定硬件(GPU、神经引擎、CPU 加速器)上得到加速。

不幸的是,导出图像模型需要在导出过程中进行更多的编码,特别是如果您想要在视频上实现实时图像处理的最高性能。

开发人员特别需要对推理平台中获得的图像进行预处理和后处理,通常使用 Swift 编程语言和本地图像捕获和处理库,并对该图像进行适当的归一化和整形。

有不同的技术可以在模型本身上实现这一点,用 python 编码,或者在客户端使用 Swift、iOS/macOS 库和 CoreML 运行时 API。

我的建议通常是将模型封装在包装 Python 模块中,并将该包装直接提供给 CoreML exporter api,最后更新 CoreML 生成的规范(实际上是 proto-buffer 格式),以转换图像中的输出张量,从而轻松地将模型集成到客户端应用程序流中。

在下面的代码片段中,您可以找到前面讨论的模型的 PyTorch 包装类,以及使用输入和输出参数的图像类型导出和格式化模型所需的代码。

最后,本示例中描述的特定 UNet++主干模型使用了 CoreMLTools 转换库尚不支持的 PyTorch 运算符。幸运的是,CoreMLTools 允许 ML 开发人员使用他们的 MIL 框架 api 来定义定制的 Op,以便为缺失的 PyTorch Op 实现快速重新定义和注册正确的转换。

具体来说,以下代码重新实现了 UNet++模型使用的 hardwish 和 hardsigmoid PyTorch 激活器:

源代码

这个实验的大部分代码已经在本文上面的代码片段中提供了。

完整的源代码和完整的端到端笔记本将很快在 https://github.com/JacopoMangiavacchi/ToonClip-ComicsHero提供

感谢您阅读本文,测试 iPhone 应用程序或拥抱脸演示,并再次感谢本文中提到的人给了我们巨大的灵感。在玩卡通的时候学习新的东西很有趣!

优化随机种子

原文:https://towardsdatascience.com/optimizing-the-random-seed-99a90bd272e

做那件事有意义吗?

乍一看,答案似乎显而易见。但是在深入挖掘之前,我们先不要急着下结论。

马库斯·斯皮斯克在 Unsplash 上的照片

我先讲一个小故事来创造背景。

实践中的随机种子优化

大约 7 年前,我开始学习数据科学,大约在同一时间,我也开始在 Kaggle 平台上参加机器学习比赛。我只知道一点点理论,但没有多少建立真实模型的经验。作为一名机器学习的初学者,当时我做了与 Kaggle 上许多初学者完全相同的事情——我拿了一些高分的公共示例脚本,并试图修改该脚本中的模型参数,希望改善结果。

这是一个 XGBoost 模型,有大约 10 个参数。那时候。我对每个参数的含义没有太多的了解,所以我只是开始在两个方向上改变它们——增加和减少原始参数值。每次更改后,我都会重新训练模型并提交新的结果,将分数与之前的进行比较。根据结果,我将参数值进一步更改为最佳方向,每次迭代都会略微提高分数。

该模型的参数之一是随机种子。很明显,通过改变随机种子,CV 值就改变了。它可以变得稍微低一点,或者稍微高一点——取决于运气。或者更准确地说,是随机的。这给了我们优化随机种子的可能性。我们可以找到种子值,对于这个值,CV 值是最好的。这正是我在那次比赛中所做的,发现对我来说最好的随机种子是 51。

但是现在,整篇文章的主要问题来了。

有意义吗?

优化随机种子有意义吗?或者,换句话说,一个种子具有更好的 CV 值是否意味着该模型总体上比用另一个随机种子训练的具有更低 CV 值的相同模型更好?

让我们试着找到这个问题的答案。

正如我已经提到的,乍一看,答案似乎是显而易见的。通过改变随机种子,我们简单地得到 CV 分数的随机变化。因此,我们可以假设这些 CV 变化并不意味着什么,纯粹基于不同随机种子的 CV 分数更好的模型并不比用原始种子训练的相同模型更好。

如果还没有被说服,有更多的论据证明随机种子优化不会有帮助。取任意一个模型,找到 CV 值最好的随机种子值。然后对模型做一些细微的改变,比如添加一个新的特性,改变一些其他的超参数,甚至只是简单地调整一下训练行。然后再次检查先前找到的最佳随机种子值是否仍然给出最佳 CV 分数。几乎可以肯定,不会。这意味着没有单一的最佳随机种子。

那么,我们完事了吗?优化随机种子没有任何意义?

纳丁·沙巴纳在 Unsplash 上拍摄的照片

不完全是。让我们再深入一点。

深入挖掘

首先,让我们了解一下,随机种子到底是干什么的。它被机器学习模型用于各种任务——在所有需要随机性的地方。对于基于树的模型,随机性是需要的,以确保所有的树不是相同的,有不同的分裂。基于树的模型中的树越多样化,结果应该越好。

我们可以使用各种技术使树木更加多样化。例如,我们可以通过删除一些行或一些特征(列)来使用子采样。在每次迭代中,删除一些行或列的子集。随机性确保了每次这个子集都是不同的。这反过来确保了模型将在每次迭代中创建不同的树。

因此,随机种子(以随机的方式)影响将被创建的树。现在的问题是——一棵树能比另一棵树更好吗?

是的,可以。例如,假设我们有两个特征。其中一个包含真实信号,但另一个主要包含噪声。可能发生的情况是,在一棵树中,真实特征被子采样移除,使得在考虑最佳可能分割时该特征对于模型不可用。因此,模型将首先根据噪声特征创建分割。但是在其他一些树中,噪声特征将被移除——这意味着第一次分割将由强特征完成。在这种情况下,基于真正强特征的第二棵树通常会更好。

现在,如果一颗随机的种子幸运地比其他种子长出更强壮的树会怎么样呢?在这种情况下,用第一个种子训练的模型将确实比用另一个种子训练的模型更好,具有更多在噪声特征上分裂的较弱的树。

把结论放在一起

所以,再一次,优化随机种子有意义吗?看情况。在大多数情况下,它不会。但是在某些情况下,在某些特殊的情况下,这可能会有所不同。例如,如果满足以下两个条件:

1)很少有特征具有显著不同的预测能力。

2)子采样或类似技术用于在每次迭代中随机丢弃特征的子集。

在满足这两个条件的数据集上(https://www . ka ggle . com/datasets/alijs 1/artificial-data-leaks),我设法得到了仅在随机种子上有所不同,但在性能上表现出明显差异的模型。一个种子被选为针对验证集优化的最佳种子,而另一个种子被选为最差种子。并且所选的两个种子的最终性能是在不同的数据上测量的,而不是在优化种子的相同验证集上测量的。这个过程用不同的验证集重复了 100 次,以获得一些统计上显著的结果。

一个额外的观察是特征重要性分数。根据特定随机种子的幸运程度,具有真实信号的要素在要素重要性列表中的位置会略微靠前或靠后。

作为结论——存在随机种子优化可以产生更好的模型的情况(即使这个特殊的例子是人为创建的)。

一些最后的话

在现实世界的问题中,我从来没有优化过随机种子。就好像你有两个用不同种子训练的模型,最好的方法不是拿走其中一个,而是把两个融合在一起。在大多数情况下,混合(平均)结果对每个单独的结果都更好。

但是——如果出于某种原因,我只能从两个模型中选择一个,只是在使用的随机种子上有所不同,我会选择验证分数更高的那个。以防万一。

感谢阅读!希望你能有所启发。

以后如果有兴趣看更多数据科学和机器学习相关的文章,别忘了关注我。

使用 PPDAC 组织您的数据科学项目—案例研究

原文:https://towardsdatascience.com/organize-your-data-science-projects-with-ppdac-a-case-study-26badfb28fbf

定义你的问题,制定计划,找到数据,分析数据,然后传达你的结论——这就是 PPDAC

费利佩·费塔多在 Unsplash 上的照片

方法论到底怎么了?当我刚开始从事软件开发时,它们风靡一时,但似乎已经失去了吸引力。

也许开发人员只是不喜欢被告知该做什么。

我怀疑当方法论是规定性的时候,它们就不受欢迎了。但他们不一定要这样;它们可以成为最佳实践的指南。这就是 PPDAC。这是一种帮助确保你以正确的顺序做事,并且不要忘记可能危及你的项目的事情的方法。

PPDAC 代表项目开发的五个阶段:问题、计划、数据、分析和结论/沟通。记住这些阶段将有助于你的项目成功。

PPDAC 循环—作者图片

让我们更详细地看一下每一个。

  • 问题——大多数项目以一个问题开始,例如:吸烟和肺癌之间有关系吗?如果你不清楚你的问题是什么,那就很难回答。
  • 计划——一旦你明确了你想解决什么问题,你就需要考虑衡量什么以及如何衡量。你需要记录或收集什么数据,你将采取什么方法来解决你的问题?
  • 数据——当然,你需要获取数据,但随后你需要清理、过滤数据,并在必要时合并数据。
  • 分析——一旦数据有了合适的形式,你需要构建表格/图表,寻找模式,并形成假设。
  • 结论/交流——最后,你应该解释你的结果,得出结论,形成新的想法并交流发现。有时这将提出进一步的问题,然后你可以反馈到周期的开始。

案例研究

我们将使用 PPDAC 方法逐步完成一个案例研究。

我们将介绍 PPDAC 的每个阶段,像往常一样,在本文发表后不久,所有代码和数据都可以从我的 Github 网页下载。

问题

我们将通过一个简单的案例研究来说明 PPDAC 循环。我们的问题首先由统计学先驱弗朗西斯·高尔顿(1882-1911)在 19 世纪晚期提出。

利用父母的身高,我们能预测成年子女的身高吗?

很明显,父母和他们后代的身高之间应该有某种关系。毕竟,其他身体特征也会遗传,比如头发和眼睛的颜色。

那么个子高的父母一般都有个子高的孩子吗?似乎有可能。他们有更高的孩子和更矮的父母吗?可能不会,因为否则,我们将会看到人类身高的范围变得更大,而我认为这不会发生。成年后代最终会和他们的父母一样高吗?

有什么关系?

计划

收集数据

为了他的实验,高尔顿收集了数百对父母和他们孩子的数据。这些数据属于公共领域,可以从不同的来源以不同的形式获得。因此,我们计划的第一部分必须是确定合适的数据来源。简单的互联网搜索就足够了。

在我搜索数据时,列表中的第一项是网站https://www.randomservices.org/random/index.html,用他们自己的话说就是“……一个致力于概率、数理统计和随机过程的网站,面向这些学科的教师和学生。它由位于亨茨维尔的阿拉巴马大学数学科学系的数学家凯尔·西格里斯特经营。西格里斯特根据知识共享许可协议提供他的所有作品,只要给出适当的署名,你就可以出于任何目的使用这些作品。**

另外,在浏览网站时,我注意到数据集中有另一组来自英国数学家和生物统计学家卡尔·皮尔逊(1857–1936)的身高数据。

您可以在此下载数据:

我们的方法

我们到底要尝试预测什么?根据经验观察,很明显,男性往往比女性高,因此父子之间的身高关系很可能不同于父女之间的身高关系。同样,母亲和女儿的身高关系也不一定和父亲和女儿一样。

高尔顿的方法是使用一组母亲、父亲、儿子和女儿的综合数据,为了考虑到性别之间的身高差异,他将女性身高调整为男性身高的 1.08 倍。

这是正确的两性身高差异吗?将女性视为和男性一样,只是稍微矮一点,这样做合理吗?从政治上来说,这可能会引起一些人的惊讶,但从数学上来说,这合理吗?

也许我们应该把性别分开,做两个预测;一个是父亲和儿子,另一个是母亲和女儿。

或者,也许我们应该分别分析儿子和女儿,但用父母双方来预测。

在我看来,后者可能是一个更好的方法,因为父母双方似乎都对他们孩子的最终身高有所贡献,但这种关系对儿子和女儿来说是不同的。然而,为了简单起见,我们将采取第一种方法,我们将看看父亲和他们的儿子之间的关系。我们将把另一种方法留给 PPDAC 周期的第二次迭代。

目前,我们将局限于高尔顿数据,我们将在 Python 和 Jupyter 笔记本中进行分析。

数据

将数据下载到一个名为【T2 数据】的文件夹后,我用下面的 Python 代码将它加载到一个熊猫数据框中。

*import pandas as pd
df = pd.read_csv("data/Galtons Height Data.csv")*

这是高尔顿数据的视图。

高尔顿数据—作者图片

数据已经是干净的,没有丢失的值,但是我们应该过滤它,删除我们不感兴趣的列和行。

我们只打算处理父子数据(目前),所以我们只希望在“性别”列中有“M”的行。

*sons = df[df['Gender'] == 'M']*

这给了我们一个新的数据帧,叫做sons.

此外,我们将删除不使用的列(不是绝对必要的,但是更简洁)。

*sons = sons.drop(columns=['Family','Mother','Kids','Gender'])*

sons数据帧现在看起来像这样。

“儿子”数据框架——作者图片

分析

我们的数据准备好了,接下来呢?一个显而易见的事情是绘制数据的散点图,看看我们是否能看到任何模式。

*sons.plot.scatter(x='Father', y='Height', figsize = (12,6))*

这给了我们下面的图表。

父亲/儿子身高数据散点图-作者图片

这并没有告诉我们太多:你可以看到,在左边,矮的父亲有矮的儿子,在右边,高的父亲有高的儿子。在中间,它是相当不清楚的。

一个问题是,你不能清楚地看到所有的数据点,许多数据点是一个接一个绘制的。在图中添加一些抖动可能会提供更好的视图,例如:

带抖动的父亲/儿子身高数据散点图-作者提供的图片

虽然您可以看到有更多的数据点,但我不确定这对分析是否有很大帮助,因此在本练习的剩余部分,我将继续使用原始数据。

(我已经在 Jupyter 笔记本中包含了抖动代码,您可以下载,但它目前被注释掉了)。

我们可能看到某种模式或趋势的一种方法是在散点图的顶部绘制一条回归线。一个简单的方法是使用 Plotly Express。

*import plotly.express as px
px.scatter(sons, x='Father', y='Height', 
           trendline='ols', width = 800, height = 400)*

Plotly Express 散点图可以采用一个参数,该参数将使用普通最小二乘法创建一条趋势线。

用 ols 趋势线绘制的父亲/儿子身高数据散点图-图片由作者提供

**普通最小二乘趋势线穿过散点图的中间,代表父亲和儿子身高的平均值。这清楚地表明,高个子的父亲有高个子的儿子。

如果您将鼠标悬停在趋势线上,Plotly 将为您提供回归模型的参数以及与散点相关的数据:

父亲/儿子身高数据的散点图,显示趋势线数据-作者提供的图片

您也可以自己动手,使用 SciPy 计算回归线,并将其绘制在散点图的顶部。完成这项工作的代码在下载中,我将很快在这里复制它,但我不打算详细介绍。如果您想了解更多关于使用 SciPy 创建回归图的信息,请参阅下面的文章。

*

from scipy import stats

x = sons['Father']
y = sons['Height']

# create model
m = stats.linregress(x, y)

# create predicted values
t = [m.slope * i + m.intercept for i in x]

# add predicted values as a trend line
sons.insert(len(sons.columns),'Trend',t)

# plot the original data as a scatter diagram
# and add the trend as a line plot 
ax = sons.plot.scatter(x='Father',y='Height')
sons.plot.line(x='Father',y='Trend', color='red', ax=ax, figsize = (12,6))

在上面的代码中,我又回到了用熊猫来绘图,因为我发现用这种方式组合绘图更容易。下面是由上面的代码生成的图:

熊猫的父亲/儿子身高数据散点图,带 ols 趋势线-图片由作者提供

我们能了解更多吗?

使用 SciPy calculate 回归模型,我们可以看到两组身高之间关系的细节,但给出了每个父亲身高值的儿子身高值的范围。我不确定它能提供很多信息。

不过,我们可以做一件简单的事情。如果父亲和儿子的身高大致相同,我们可以画一条可以预测儿子身高的等值线。

sons['Equality']=sons['Father']
ax = sons.plot.line(x='Father',y='Equality', color = "black")
ax = sons.plot.scatter(x='Father',y='Height',ax= ax)
sons.plot.line(x='Father',y='Trend', color='red', ax=ax, figsize = (12,6))

这很容易通过创建一个新列来完成,该列是父亲身高列的副本。我称之为“相等”,并在图表中添加了一个新的点。

带有回归图和一条等号线的身高数据-图片由作者提供

如果图表是正方形的,那么等位线应该与基线成 45 度角。如果儿子倾向于和他们的父亲一样高,那么回归线就会和平等线一样。但事实并非如此。在高的一端,儿子比父亲矮,在矮的一端,儿子往往比父亲高。

结论

很明显,正如所料,个子高的父亲有个子高的儿子。但是我们可以从有平等线的图表中看到,高个子父亲的儿子比他们矮,矮个子父亲的儿子比他们高。

我们能预测一下吗?也许我们可以。我们可以使用 SciPy 中的回归模型来预测一般身高,但不能预测特定儿童的身高。然而,这个模型可能并不准确。在最初的实验中,数据的大小可能被认为是很大的,但是现在几百个数据点并不是一个大的数据集。此外,如果你对皮尔森数据做同样的练习,你会得到不同的结果——就像你对母女关系做同样的练习一样。我哪里知道?因为我就是这么做的,你可以在可下载的笔记本中看到代码和结果。

母亲/女儿:

母亲/女儿身高图——作者图片

皮尔森数据公司的父子:

皮尔森数据中的父子身高图——作者图片*

感谢阅读,我希望你觉得有用。该案例研究的灵感来自于大卫·斯皮格哈尔特的优秀著作《统计的艺术:如何从数据中学习》中的一个类似练习。

正如我上面提到的,在这篇文章发表后不久,代码和数据将会在我的 Github 网页上提供。你也可以在那里找到我的其他作品。

参考

[1] 统计学的艺术:如何从数据中学习,大卫·斯皮格尔哈特,2021 ( 附属链接)

*https://medium.com/membership/@alan-jones *

使用时空资产目录组织地理空间数据-使用 python 进行 STAC

原文:https://towardsdatascience.com/organizing-geospatial-data-with-spatio-temporal-assets-catalogs-stac-using-python-45f1a64ca082

如何使用 pystac 包在 Python 中创建地理空间数据的 STAC 目录

格雷格·罗森克Unsplash 上拍摄的照片

介绍

那些处理地理空间数据的人可能习惯于处理大量的文件。通常,为了达到最终目标,需要处理大量带有中间文件的步骤。除此之外,随着空间分辨率更高的免费卫星图像的不断增加和计算能力的提高,更大的感兴趣区域正在成为研究对象,甚至需要更多的文件来涵盖一切。

地理空间云,如谷歌地球引擎和微软行星计算机,是避免处理单个文件的所有麻烦的绝佳选择(关于它们的一些故事这里这里),但有时不可避免地要在本地处理。

在最近的一个项目中,为了评估巴西东南部从 2018 年到 2021 年的水面范围,我必须使用全空间分辨率(10m)的 Sentinel-2 图像覆盖大约 320,000km 的区域。2000 万个频段已相应升级。每张图像的表面水遮罩都是使用修改版的waterdetect包创建的(在故事中了解更多信息:使用 waterdetect python 包在高分辨率卫星图像中进行水检测)。

为了覆盖研究的整个感兴趣区域,总共选择了 36 块瓷砖。考虑到时间限制,大约有 12 000 份档案被单独处理。

图 1: (a)国家背景下的巴拉那水文区。(b)研究的感兴趣区域。红线代表 Sentinel-2 瓷砖的覆盖区。图片作者。

为了组织所有的工作,我在本地使用了 Python 中的 STAC 目录,我将演示如何创建一个 STAC 目录,然后如何使用客户端包(如stackstac)快速访问它的一部分(和堆叠资产)。

为什么是 STAC 目录

为 12,000 份文件中的每一份文件都制作了中间产品,以及用于质量评估的单独报告和其他散点图。为了处理所有这些文件,人们通常依靠文件名(或文件夹名)来链接中间作品,但这很容易出错。

此外,简单的任务,例如查询覆盖特定区域的图像,仅仅使用文件名是不可行的。除此之外,每个图像可能具有不同的属性,如形状和空间投影,因此在没有更多潜在信息的情况下,不容易对它们进行比较。这正是 STAC 目录适合的地方。根据他们的官方页面【https://stacspec.org/ :

时空资产目录(STAC)规范提供了一种通用语言来描述一系列地理空间信息,因此可以更容易地对其进行索引和发现。“时空资产”是表示在特定空间和时间捕获的关于地球的信息的任何文件。

简而言之,它是描述地理空间图像(或任何其他资产,例如 PNG 缩略图)的标准 XML 规范。这些规范被组织在一个目录、子目录和集合的结构中(图 2)。每个 STAC 条目都有一个指向资产位置的引用(链接),它们是在文件系统中还是托管在云中。这样,一旦创建了目录,文件的确切位置就无关紧要了,您可以按地区或日期执行搜索,只需查看目录项目。

图 2: STAC 结构示例。来源:https://github . com/stac-utils/py stac/blob/d 70 DEA 5c 70 a 243450 AC 64120 E0 EBA 84d 786574 b 4/docs/tutorials/py stac-introduction . ipynb

许多卫星影像提供商都按照 STAC 规范发布数据集(图 2),但对于本地目录来说,这种情况并不常见,例如,我发现关于这方面以及如何用 python 创建 STAC 目录的信息非常少。

图 2:根据 stacspec.org 的说法,支持使用 STAC 目录的组织。来源:https://stacspec.org/

创建 STAC 目录

现在我将向您展示如何使用pystac 包在 Python 中创建 STAC 目录。第一件事是有一些图像进行分类。我将选择我的 Sentinel-2 防水面具的一个子集,但代码应该对任何被xarray接受的地理空间图像类似地工作。我用瓦片将文件分开,每个日期有 3 个不同的文件,如图 3 所示:

1-水面膜本身(。TIF);

2-快速查看(thumbnail . png);和

3-输出散点图(graph.png)。

图 3:选择要编目的文件。图片作者。

需要的软件包有:

rasterio
shapely
pystac
stackstac

创建基本目录的代码片段相当简单。请注意 stac_extensions 参数。在接下来的步骤中,需要将投影属性添加到资产中(关于创建资产部分的更多解释)。

创建 STAC 项目

为了创建项目,我们将遍历我们的文件。每个项目将有 3 个相关资产(防水面具,拇指和图表)。

使用地理空间目录的一个优点是,如简介中所述,它允许按日期或位置搜索数据,而无需实际打开每个图像或解析名称字符串。为此,我们的项目将必须有一些属性,如几何,边界框,日期时间等。为了从我们的遮罩中提取地理空间信息(其他两个资产不是“地理空间”),我们将创建以下函数。它可以处理用rasterioxarray 打开的数据集(只要还安装了rioxarray )

现在我们将创建项目。请注意,在将属性写入项目之前,我们会将边界框和封装外形投影到 WGS84 (epsg:4326)。如果你不习惯坐标参考系统(CRS ),我在我的 Python for Geosciences 系列中写了一篇关于它的教程: Python for Geosciences: Raster 合并、裁剪和使用 Rasterio 重新投影

我们还需要给这个项目一个 ID,一个日期时间,可选地,我们将写瓷砖代码。我们将从 Path 对象中获取它们。最后的 describe 函数给出了目录的摘要:

2331
* <Catalog id=Paranapanema>   
  * <Item id=S2A_MSIL2A_20180103T133221_R081_T22KDV>   
  * <Item id=S2A_MSIL2A_20180116T134201_R124_T22KDV>   
  * <Item id=S2A_MSIL2A_20180123T133221_R081_T22KDV>   
  * <Item id=S2A_MSIL2A_20180215T134211_R124_T22KDV>   
  * <Item id=S2A_MSIL2A_20180222T133221_R081_T22KDV>   
  * <Item id=S2A_MSIL2A_20180225T134211_R124_T22KDV>

就这样。我们在目录中添加了 2331 件商品。但是它仍然缺少每个资产的路径。我们将在下一步添加它们。

创建资产

请注意,我们现在有一个包含项目的目录,但是我们不再有实际文件的路径。出于教育目的,我将它分为两步(创建项目和创建资产),但在“现实生活”中,您应该考虑在同一个循环中编写所有内容。现在,我们将不得不再次遍历这些项目,并从 ID 中获取正确的项目,以便向其中添加资产。

除此之外,我们将再次需要边界框和轮廓,但是我们将把它们留在原来的 CRS 中。这将使将来使用stackstac打开这些资产变得更加容易。

为了将投影信息添加到资产中,我们需要使用一个名为投影扩展的 Stac 扩展。项目扩展提供了一种描述其资源位于地理空间投影中的项目的方法。它将向资产添加一个属性列表(查看其手册以获得完整列表),但是我们将使用以下内容:

  • 项目:epsg,
  • 投影:几何图形,
  • 项目:bbox,
  • 投影:形状,
  • 项目:转换。

要在目录中使用 Stac 扩展,必须在创建目录期间定义它。这就是我们将扩展添加到目录和项目中的原因。因此,下面是将 water mask 资产添加到我们的项目中的代码:

创建媒体资产

创建媒体资产要简单得多。同样,我们将遍历我们的文件,但是不需要获取地理空间属性。

如果我们想检查资产是否被正确创建,我们可以访问项目属性assets,如下所示:

item.assets{'watermask': <Asset href=paranapanema/22KGV/S2B_MSIL2A_20210817T132229_R038_T22KGV_watermask.tif>,  'thumbnails': <Asset href=paranapanema/22KGV/S2B_MSIL2A_20210817T132229_R038_T22KGV_thumbs.png>,  'graphs': <Asset href=paranapanema/22KGV/S2B_MSIL2A_20210817T132229_R038_T22KGV_Graphs.png>}

节约

在保存之前,了解每个项目都将有一个.json文件写入磁盘是很重要的。放在哪个文件夹将取决于我们尚未设定的“策略”。默认情况下,每个条目 id 都有一个子文件夹,位于根目录文件夹下。我们将保留默认值。

要创建 hrefs,我们需要使用.normalize_hrefs并传递根文件夹。

测试

为了测试我们的目录,让我们在一个新变量中打开它,并将一个条目显示为 dictionary 来查看结果。

最后,让我们看看 stackstac 的实际操作。首先,我们将创建一个函数,通过 tile 和 datetime 选择项目,并在一个特定的月份中获取项目。

在我的例子中,它返回了 19 个条目。现在让我们用stackstac 将它们组合起来,创建一个漂亮的马赛克来展示。请注意,我们不必为 CRS 和预测费心,因为这些信息已经保存在资产中。

代码输出。图片作者。

正如你所看到的,它用我们的 19 张图片创建了一个“懒惰”的数据立方体,我们现在将执行中值计算来创建一个月层。

代码输出。Paranapanema 盆地的水面镶嵌图。图片作者。

结论

我将给你们留下这幅美丽的巴西帕拉纳帕内马盆地的水面镶嵌画。

今天,我们学习了一些关于 STAC 目录以及如何使用它们来组织地理空间数据的知识。正如您在测试部分看到的,一旦我们很好地“组织”了目录,未来的操作和分析就容易多了。考虑到地理空间分析所需的文件数量和处理步骤,这一点非常重要。

我希望你喜欢这个教程。下一个故事再见!

保持联系

如果你喜欢这篇文章,想支持我当作家,可以考虑成为 中等会员 。每月只需 5 美元,我会从你的会员费中收取一小笔佣金,不需要你额外付费。或者你可以随时给我买杯咖啡。

https://cordmaur.medium.com/membership

我们即将到来的(自己造成的)人工智能恐怖热潮

原文:https://towardsdatascience.com/our-impending-self-inflicted-ai-horror-boom-e764a20af456

世界越来越不信任人工智能,这是人工智能开发者的错

这是我们关于 ML 公平性系列的第 2 部分( 链接 )。

之前,我们讨论了由于各种(微弱的)原因,ML 团队很少尝试解决模型中的社会偏见。

在本文中,我们将探讨这种忽视的后果。

论文:由于高调的人工智能失败和对人工智能安全的日益担忧,公众对坏人工智能的恐惧和对公平人工智能系统的渴望将决定 21 世纪 20 年代的电影和消费者行为

世界越来越不信任人工智能,这是人工智能开发者的错。由阿瑟尼·托古列夫Unsplash 上拍摄的照片

在本文中,我将涉及 3 点:

#1.恐怖电影可以用来衡量共同的社会恐惧

#2.一系列高调的头条新闻和纪录片唤醒了社会对人工智能的恐惧

#3.人们越来越不信任人工智能系统,这反映在消费者行为和关于“邪恶”人工智能的新电影中

我们开始吧🤖 🚀

#1.恐怖电影与社会

我保证这和机器学习有关!

在疫情之前,我参观了西雅图的流行文化博物馆,那里有一个关于恐怖电影的展览。正是在这里,我第一次了解到社会学理论:

恐怖电影经常反映一个时期的恐惧,对抗者象征着共同的社会恐怖

恐怖电影不是关于怪物的。它们是关于我们的。照片由亚当·弗洛克曼Unsplash 上拍摄

这个想法已经被比我聪明得多、见多识广的人所涵盖,但这里是 spark notes 版本 w/ citations:

电影和社会恐惧的恐怖时间表

请注意,这些恐惧通常是以美国为中心的,因为好莱坞的电影业通常是以美国为中心的

第一次世界大战后,进入大萧条时期,出现了一系列怪物电影——弗兰肯斯坦、德古拉和木乃伊——这些电影捕捉了美国人的无能为力和沮丧感,即当权者让这些可怕的世界事件发生[ 来源

第二次世界大战后,随着原子弹的毁灭性进入,出现了恐怖电影,其(通常是科幻) 对手造成了绝对的毁灭——哥斯拉,Blob,来自世界大战的火星人。所有人都谴责科学是不道德的,会导致无法控制的毁灭

随着麦卡锡主义带来的冷战和偏执,出现了恐怖电影,如《来自另一个世界的东西》《入侵异形基地》。在这些电影中,像美国的红色恐慌,“几乎不可能分清你的朋友和你的敌人。” [ 来源

911 事件导致了僵尸电影的激增,这既是美国明显的种族紧张的象征,也是其他日益增长的恐惧,如气候变化

数据来自维基百科。作者制作的图表

尤其是僵尸已经被用来象征各种各样的社会恐惧,大多是存在的和不可避免的威胁,并且在 2010 年继续上升。 [ 来源 ]

以及 2010 年代的恐怖题材:家族性破裂 ( 世袭滚出来《巴巴多克》, 《水到渠成》,)。所有这些电影都反映了现代社会中家庭成员之间,尤其是几代人之间日益扩大的沟通鸿沟。[ 来源

2016 年,恐怖电影聚焦于入室抢劫,反映了在极具争议的总统选举期间,美国日益高涨的排外情绪

有了这个理论,让我们探索可能反映社会恐惧的新趋势…

艾:快速移动,打破(许多)东西

机器学习在 2010 年代的后半期席卷了整个世界,并且从未回头。

来源: CB Insights

虽然我可以很容易地用自那时以来人工智能/人工智能的重大成功的标题来填充这篇文章,但我将选择同样引起公众注意的负面人工智能标题:

这仅仅触及了 AI 在 2010 年和 2020 年造成的公关噩梦的表面。

还有几部广受欢迎的纪录片讲述了人工智能的阴暗面:

  • 你信任这台电脑吗?(2018) 一部关于人工智能在关键任务领域的影响的纪录片……称人工智能为“生死攸关的问题”和“一种我们无法控制也无法阻止的力量
  • The Great Hack(2019)——脸书的故事& Cambridge Analytica,其视频描述以开头,“首先他们拿走了你的数据。然后他们控制了局面”。 ( 作者注 :在这部纪录片之后,我交谈过的大多数人也第一次知道了什么是数据科学家…而且不是以一种好的方式…)
  • 编码偏见(2020)——一部关于面部识别系统中发现的种族偏见的纪录片
  • 《社交困境》(2020) ,以“社交媒体的阴暗面”为标题,讨论了一小群工程师如何跟踪你在网上做的一切,并使用人工智能控制“我们思考、行动和生活的方式

《你信任这台电脑吗》的预告片

你能猜到所有这些关于人工智能的负面报道是如何影响我们的集体心理的吗?

🤖 👎

数字弗兰肯斯坦

通过允许算法控制我们在网上看到的和做的大量事情,我们已经允许技术成为一种'数字弗兰肯斯坦,'操纵数十亿人的态度、信仰和行为'——特里斯坦·哈里斯

从 2010 年后半期到 2020 年,我们对人工智能的公众认知开始发生变化。这种变化表现在电影院和消费者如何决策上

1.改变电影院

由于人工智能首先是一种技术(其次是公众的恐惧),所以大多数涉及人工智能的电影都是科幻小说,而不是恐怖片。

但我希望说明的是,近年来 AI 电影的绝对呈爆发式增长,伴随着 AI 电影的爆发,出现了越来越多的 AI 惊悚/恐怖动作。

人们的假设是,随着这十年的展开,这些趋势将继续下去。

2010 年代:成为坏人工智能的好十年

正如开明数码的莎拉所写的,2010 年是“真正的人工智能相关电影的黄金时代。无论是次要角色还是节目中的明星,随着这项技术在现实世界中的普及,人工智能已经在娱乐中变得无处不在。”

电影中的人工智能无处不在。

在 2010 年代,关于人工智能的电影数量增加了两倍,预计这一趋势将在 2020 年代继续上升。

数据来自维基百科,图表由作者提供

2010 年代值得注意的人工智能角色:

  • 漫威的电影世界在这十年的 21 部电影中几乎每一部都突出了人工智能的角色,最著名的是在《奥创时代》(2015) 中扮演的对手
  • 《Ex 玛奇纳》(2014) 是一部广受好评的电影,围绕着一个神秘的人工智能及其创造者,获得最佳原创剧本提名&赢得最佳视觉效果
  • Maniac (2018)和 Upgrade (2018)都采用了试图接管人类宿主思想的人工智能系统

显然,人工智能已经进入了电影制作人的脑海,进而也进入了普通大众的脑海

及时重启

基于对现代科技的恐惧,老的恐怖/惊悚电影被重新制作了很多,这很有趣:

儿童剧在 2019 年重新开机,但这一次杀手娃娃不是附身,而是一个邪恶的 AI。由埃里克·麦克莱恩Unsplash 上拍摄的照片

2015 年重拍的《恶作剧之神》是一个明确的警告,告诉我们允许科技进入我们的家的危险。

儿童游戏(2019) 是一部恐怖电影翻拍版,用一个着魔的娃娃替换了一个出现故障的人工智能

《西部世界》于 2016 年重新开播,是一部关于流氓人工智能机器人的电视剧,这些机器人与人类难以区分,以残酷的方式实施他们的复仇。

《银翼杀手 2049》(2017)是第一部科幻惊悚片 30 年后的续集,主要讲述了与人类融合的杀人机器人的主要对手(当然,这部电影的信息混淆了好与坏、人与机器之间的界限)。

《普罗米修斯》(2012)《异形之约》(2019) ,一部在 30 多年后重新开机的《异形》原著中,有一个杰出的 AI 机器人为了追求自己的目标不顾一切地伤害/杀死自己的人类同伴。

随着我们进入 21 世纪 20 年代,这些人工智能惊悚/恐怖重启可能会在数量和受欢迎程度上增长。我们已经看到了两部大片——《太空堵塞》 & 《黑客帝国》——在上一部《AI 恶棍》几十年后重新开机。这种趋势也说明了以艾为主角的恐怖/惊悚片越来越受欢迎。

机器来了

虽然人工智能恐怖片的数量没有像人工智能电影的整体数量那样激增,但它一直在稳步增长:

维基百科搜集的数据,作者图表

TODO:拉科幻惊悚招式用邪恶 AI:

甚至在 2021 年,我们已经看到了一系列探索人工智能坚韧不拔的黑暗面的电影:

  • 《人的怪物》( 2021)是一部关于战争的恐怖的电影……被机器残酷地折磨着
  • 已公布的电影 BigBug (2022) 是关于流氓 AI 与他们的人类主人反目成仇的(有着恐怖的恐怖谷面孔……)
  • 已公布的谋杀惊悚片《KIMI》(2022)将探索智能设备在我们身边的意义

还有… 机器人僵尸

  • 活死人军团(2021) 有了机器人僵尸
  • 米切尔与机器大战(2021) 讲述了一群僵尸般的机器人接管世界的故事
  • 母亲/机器人(2021) 讲述的是机器人变成凶残的僵尸般的杀手,消灭了大部分人类

所有这些都是为了说——最近电影院已经看到了越来越多的邪恶人工智能,而且这很可能会持续到 2020 年,因为公众对人工智能的关注越来越多。

2.改变消费者行为

对于人工智能专业人士来说,不断变化的电影景观可能看起来是一个古怪的结果。但这表明了一种更阴险的趋势:产品中对人工智能系统的不信任日益增长。

正如在痴迷中所讨论的:

“如今,如果一个品牌带着良好的意愿处理其业务的一个方面,而忽视另一个领域的道德规范,它就会陷入困境。

消费者……比以往任何时候都渴望购买他们感觉良好的品牌,这包括相信不可告人的秘密不会被揭露——艾米丽·海沃德

而今天,消费者对拥有人工智能解决方案的公司感觉不是很好。

公众越来越不信任

TechCrunch 指出消费者对人工智能越来越有所保留:

然而,机器的智能潜力经常引起人们的恐惧。研究显示,34%的人害怕人工智能,而 24%的人认为人工智能对社会有害。GWI 发现 63%的人担心他们的个人数据会被公司利用。

或者关于大肆宣传的 IBM Watson 在大量健康应用中被废弃的文章,因为 医生不相信 Watson 的预测/诊断 有些人称之为“ 偏见和未经证实的推荐系统

或者一篇关于只有 25%的消费者信任人工智能而不是人类来为他们做出财务决定的文章。

越来越大的民间压力

各国政府都注意到了这一点——仅在 2020 年就有 31 个国家出台了新的人工智能法规。

即使增加了这一规定,68%的消费者希望公司在负责任的人工智能方面超越法律要求。

越来越多的商业影响

企业也开始感受到这种日益增长的不信任。

66%的商业专业人士认为人工智能道德将在两年内成为组织战略的核心要素, 80%的商业高管 担心他们的人工智能系统的道德含义。

普华永道指出“随着人工智能变得越来越复杂,信任将成为复杂商业决策的一个基本因素”

  • 67%的商业领袖认为他们对未来 5 年内即将到来的人工智能模型的信任将会下降
  • 很快,知道谁对人工智能系统的决策负责将变得至关重要

显然,相当一部分人对在日常生活中采用人工智能存在担忧,这与人工智能应用的兴起不谋而合,最重要的是,高调人工智能失败和纪录片的兴起。

ML 社区对日常生活中不良人工智能的社会影响的忽视是罪魁祸首。

结论

流氓人工智能并不是电影的新话题——有史以来最伟大的两个人工智能反派分别出现在 1968 年(HAL 3000)和 1991 年(终结者)。(不是引用这个,很明显是事实)

但就像 2000 年代的僵尸电影一样,随着公众越来越意识到并害怕人工智能系统的缺点和普及,很明显,基于人工智能的恐怖爆炸可能就在眼前。

就像反映公众恐惧的僵尸电影一样,人工智能恐怖事件的增加将表明公众对人工智能的普遍恐惧和不信任;一旦失去,就几乎不可能再赢回的信任。

这将导致“消费者人工智能冬天”

现在注意到这些趋势是 ML 在煤矿中的金丝雀——它可以唤醒像我这样的 ML 开发者改变我们的方式。

与这篇文章的基调相反,我绝对不是一个 AI 卢德派。我深信曼梯·里是一股向善的力量。正如吴恩达的名言:

艾是新电

就像电一样,它的能量需要被正确利用,否则我们就有可能对周围的人造成严重伤害。

除了公平之外,ML 还有很多可靠性问题,但目前,公平是公众最关心的问题。

作为快速增长的人工智能社区的成员,我们需要看到墙上的文字,并意识到如果我们打算将人工智能系统深深嵌入我们的社会,我们需要确保这些系统激发那些建造和使用它们的人的信任和信心。****

如果做不到这一点,曼梯·里就会变成一个适合搬上银幕的怪物🎥 🤖 🔪

我们的下一个启蒙将是人工智能驱动的

原文:https://towardsdatascience.com/our-next-enlightenment-will-be-ai-driven-970b88ebd818

个人观点

人类和人工智能之间现实和共生的未来

纽约城市之光。作者图片

登上顶峰的启蒙运动

启蒙时代发生在 17 世纪和 18 世纪,被视为一个全球现象,我们集体“打开”我们头脑中的灯泡。这场运动是一股思想和哲学力量,见证了一些最杰出的思想家的崛起,如康德、伏尔泰和亚当·斯密。

启蒙运动在很大程度上是由笛卡尔和牛顿这样的哲学家和数学家推动的。这是因为在此之前的世界将知识和力量归于更高的力量——神、星星等等。这些宗教和精神存在被视为主要的权威,因此创造了人类生活的社会。

启蒙运动解决了个人自由、幸福、知识等复杂问题。在此之前,社会结构、法律和观点在很大程度上是由天主教会的教义所驱动的,而这个时代将焦点转移到了另一种更高的力量——理性。

科学革命结合了这种对理性的关注,这种关注紧接在启蒙运动之前。这两者的结合导致我们用对天主教会的忠诚来换取对经验主义和理性的忠诚。这个不可思议的重要时刻催生了哲学家、数学家和科学家之间的联姻,通过符号逻辑、摩尔定律以及现在处于顶峰的机器学习等思想刺激了计算时代。

第一次启蒙运动将我们的目光重新聚焦于科学和实验,以获得真知灼见。这导致我们几代人以数据的形式记录我们的经历,这样我们就可以以增长的名义进行科学和工程研究。这种增长最终让我们创造出了创新,彻底改变了从个人生活到全球社会的一切。互联网、iPhones、社交媒体等等在短短 50 年内改变了世界,现在我相信我们正处于第一次启蒙运动带给我们的旅程的顶点。

我们将何去何从?

机器的崛起

在几代人依靠理性做决定之后,我们根据我们在所做决定中看到的结果和经历的模式发展出了直觉。这就产生了一个循环,每次我们把技能传授给下一代时,这个循环都多了一点直觉,少了一点理性。本质上,我们以效率的名义削弱了我们的推理能力——对你肯定会无休止重复的事情做出“只是因为它一直在被做”的决定要比每次都采用批判性思维快得多,因为这一直在被做。

这对于包含的、局部的问题来说很好,这些问题变化很小,需要扩展。在我们今天生活的这个时代,当你试图在高度互联的世界中运用理性时,问题就出现了。不仅我们的推理能力有限,而且当我们处理大量的外部因素时,我们的直觉也会动摇。社会对此的解决方案是计算机编程。使用计算机,我们可以运行模拟,进行统计测试,自动化整个工作流程,以及以前任何一个人都无法管理的规模。学习编码可以让一个人拥有五个人的影响力。

我们都已经看到,随着人工智能和机器学习席卷全球,这种自然进化在最近的历史中变得多么受欢迎。我们记录和存储的所有这些经验已经成为世界各地机构的面包和黄油,因为我们可以训练统计模型来学习我们直觉应用于大规模全球范围的模式。这是一项如此具有变革性的技术,以至于我们看到技术专家、未来学家、哲学家和更多的人出来警告我们要创造一个类似终结者的未来。

山姆·哈里斯著名的 TED 演讲尼克·博斯特罗姆讨论超级智能出现的书马克斯·泰格马克概述人工智能未来的书都向我们表明,人工智能融入人类社会不再是一个流行文化、科幻概念。在无数个小时阅读专栏、文章、书籍等之后。人们一致认为要极其谨慎地建造。几乎就像有人可以[在一夜之间]建造能够与人类抗衡并有可能超越人类的 AGI(人工通用智能)。时不时地,会有一位未来学家或哲学家认为,我们很可能在未来几代都不会看到 AGI 建成,尤其是考虑到今天的实现是多么天真和相对“简单”。

即使有所有的理论、猜测和担忧,我也很少看到有人认为人工智能会让我们成为更好的人。这是一个乐观的看法,我可能直到最近才写了一个故事。

让我们开始吧。

提高做人的标准

人工智能炒作周期已经持续了一段时间,但当谷歌基于 DeepMind 构建的人工智能击败世界上最复杂的游戏之一的围棋世界冠军时,它才真正开始掀起波澜。最终,我们看到人工智能系统可以根据氨基酸序列确定蛋白质结构,建立一个只有文本输入的网站,用细微差别回答哲学问题,创造像梵高一样的艺术,等等。事实上,即使作为一名数据科学家,观察人工智能的进步也是令人惊讶的。

当看到这些创新几乎每年都在发生时,所有对未来的担忧和警示故事都完全有道理。然后,Choi 等人在 2021 年 7 月发表了一篇论文。艾尔。标题为“人工智能如何提高人类的决策能力?来自人工智能围棋程序"的证据让我大吃一惊。

研究人员评估了人工智能围棋程序(Leela)建立之前和之后人类棋手的移动质量,以衡量差异。他们控制了可以表明玩家自己变得更好或更差的趋势,并通过诸如人为错误、最严重错误的大小以及在大量不确定性下(在游戏开始时)决策的质量等措施进行评估。

作者发现,在 Leela 被释放后的 3 年时间里,移动质量显著提高,人为错误减少,最严重错误的数量减少,在大量不确定性中决策质量提高。这张图表显示了人类表现和决策技能的巨大变化。

Choi,Sukwoong 和 Kim,Namil 和 Kim,Junsik 和 Kang,Hyo,人工智能如何改善人类的决策?来自人工智能围棋程序的证据(2021 年 7 月 26 日)。由 iORB 赞助的南加州大学马歇尔商学院研究论文,第号即将发表,可在 https://ssrn.com/abstract=3893835或 http://dx.doi.org/10.2139/ssrn.3893835SSRN获得

这可能看起来相对无关紧要,因为它是基于一个游戏,但我保证它绝对不是。这项研究表明,当我们集体思考人工智能及其在社会中的作用时,我们认为人类学习、成长和提高的能力是理所当然的。我们忘记了人类社会在如此短的时间内所取得的巨大进步,而启蒙运动的顶峰让我们感觉到一次巨大的下滑即将到来。

我不同意。

我认为我们会看到由人工智能系统驱动的第二次启蒙。在第一种情况下,我们把天主教会的权威换成了理性的权威。几代人以来,这导致了对我们的直觉和批判性思考能力的过度依赖,如上所述,这有很多局限性。我想在第二次启蒙中我们会用理性的权威来换取人工智能的权威。

换句话说,我们将放弃人类智慧的自我,以更好地处理我们无法理解的复杂问题。这将如何使我们变得更好?这是我们想要的现实吗?就像每一个运动一样,它不会完全好或坏,但我确实认为它会迫使我们提高做人的标准。无知,或故意懒惰,将被视为你能做的最糟糕的事情之一,而不是“违约”。当你拥有全球知识、大规模模式识别,并且有机会变得更好时,选择留在自己的盒子里,不让自己超越偏见,这是“错误的”。

人工智能的一个大问题,也是我在工作中经常遇到的问题,是我们如何轻易地将人类的偏见融入人工智能模型中。这是真的,需要持续监控、控制和修复。这正是这能让我们变得更好的关键。理性曾被认为是不受我们的偏见和人为错误影响的绝对权威。理性是我们认知功能的产物,因此容易产生与我们自主系统相同的偏见。除了那些偏见是看不见的,需要几代人才能真正改变,如果有的话。随着人工智能模型中的偏见,它就在我们面前,由一个[理想的]多元化团队监控。我们也有办法修正这些偏见,因为它们都是我们自己写的代码和数学。人工智能中发现的偏差从识别到辩论再到修正到结果的周期比理性中的偏差快一百万倍。

在遥远的未来,我们可能会有一天不得不调和人类和人工智能之间的权力动态,但我相信我们会看到人工智能引导人类进化的漫长时代,同时使我们更加平等和高效。

这将如何开始?

当你从这些方面考虑我们的进化时,我相信我们仍然处于相当早期的阶段。到目前为止,人工智能的广泛采用实际上只出现在监管最少、技术得到大量资助的地方。像广告、营销或其他消费技术行业这样的领域已经在将人工智能部署到野外方面取得了巨大的成功(和问题)。

我认为,当我们看到人工智能在监管更严格的行业或技术资金不足的行业激增时,我们将看到人工智能的前景和使用方法发生巨大变化。气候变化或公共卫生的解决方案将保证政府和私营企业在监管监督下在全球范围内合作创建人工智能系统。这将开启一个人工智能共生的时代,大多数人将更容易欢迎这一时代。

随着我们看到人工智能主宰社会的各个方面,开源社区正在产生并成为默认,这是一件好事。如果我们要相信自主系统将进一步嵌入我们的生活,有效的透明度和用户控制的系统将是至关重要的特征。

最终,当领域专家开始将他们的直觉转化为代码,并允许他们超越自己的推理能力时,我们将开始看到人类进化的下一个阶段的形成。

来自《走向数据科学》编辑的提示:虽然我们允许独立作者根据我们的规则和指导方针发表文章,但我们并不认可每个作者的贡献。你不应该在没有寻求专业建议的情况下依赖一个作者的作品。详见我们的读者术语

非分布概括

原文:https://towardsdatascience.com/out-of-distribution-generalization-66b6f8980ef3

播客

Irina Rish 谈论人工智能中最棘手的问题之一

苹果 | 谷歌 | SPOTIFY | 其他

编者按:TDS 播客由杰雷米·哈里斯主持,他是人工智能安全初创公司墨丘利的联合创始人。每周,Jeremie 都会与该领域前沿的研究人员和商业领袖聊天,以解开围绕数据科学、机器学习和人工智能的最紧迫问题。

在训练过程中,人工智能往往会学习根据容易学习但具有欺骗性的特征进行预测。

例如,想象一下,一个经过训练的人工智能可以在图像中识别奶牛。理想情况下,我们希望它能够根据奶牛的形状和颜色来识别它们。但是如果我们放在训练数据集中的奶牛图片总是显示奶牛站在草地上呢?

在这种情况下,我们在草和牛之间有一个虚假关联,如果我们不小心,我们的人工智能可能会学习成为草检测器而不是牛检测器。更糟糕的是,只有当我们在现实世界中部署它,并且它第一次撞上一头没有站在草地上的牛时,我们才能意识到这种情况的发生。

那么,你如何构建能够学习健壮的、通用的概念的人工智能系统,这些概念在其训练数据的上下文之外仍然有效?

这就是非分布概括的问题,也是 Mila 魁北克人工智能研究所的核心成员、加拿大自主人工智能卓越研究主席 Irina Rish 研究议程的核心部分。Irina 的研究探索了许多不同的策略,旨在克服分布不足的问题,从经验性的人工智能扩展工作到更多的理论工作,她和我一起在本期播客中讨论了这一点。

以下是我在对话中最喜欢的一些观点:

  • 对伊琳娜来说,GPT-3 是人工智能比对和人工智能安全研究的“AlexNet 时刻”。我们第一次在没有真正理解人工智能的行为,或者不知道如何驾驭它的情况下,构建了高能力的人工智能。因此,Irina 认为这是进入人工智能比对研究的绝佳时机。
  • Irina 认为,分布外泛化是人工智能能力研究开始与人工智能对齐和人工智能安全研究融合的一个领域。让系统学习健壮的概念不仅有助于确保它们对世界有丰富的表示(这有助于能力),还有助于通过解决虚假相关性问题来确保事故不会发生。
  • 综合区域信息网研究了几项旨在解决分发范围以外的抽样问题的战略。其中一个涉及到使用不变性原则:无论我们的数据来自什么环境,我们希望我们的人工智能模型学习的特征都将是一致的(不变的)。以我之前提到的奶牛检测为例:我们希望我们的人工智能锁定的特征(例如,奶牛的形状和颜色)在不同的环境中保持一致。不管是在牧场、室内还是在沙漠中,牛还是牛。Irina 正在探索一些技术,让人工智能能够区分不变的和令人满意的特征(如奶牛的形状和颜色)和可变的和不可靠的预测特征(如地面上是否有草)。
  • Irina 认为另一个有前途的方法是扩大规模。我们之前讨论过播客的规模——但简而言之,这是一种想法,即当前的深度学习技术或多或少可以让我们按原样进入 AGI,只要它们用于训练足够大的神经网络,拥有足够大的数据集和同样巨大的计算能力。原则上,扩展某些类型的神经网络可以让人工智能了解太多关于它们的训练数据,以至于它们的性能只受到数据本身不可减少的噪声的限制。
  • 这种可能性提出了另一个问题:对于只接受语言训练的人工智能来说,语言数据(用于训练 GPT-3 和第一代大规模基础模型)是否有太多噪音,以达到人类水平的全面能力?Irina 认为这是可能的——这就是为什么她对多模态学习的趋势感到兴奋:同时对多种数据类型(例如,图像、文本和音频数据)训练人工智能的实践。希望通过组合这些输入数据类型,人工智能可以学习超越可能单独存在于任何一种数据类型中的噪声限制。

你可以在 Twitter 上关注伊琳娜,或者我在这里

章节:

  • 0:00 介绍
  • 2:00 研究、安全和推广
  • 8:20 不变风险最小化
  • 15:00 缩放的重要性
  • 语言的作用
  • AGI 和缩放
  • 32:30 GPT 对 ResNet 50
  • 37:00 建筑领域的潜在革命
  • 42:30 电感偏置方面
  • 46:00 新风险
  • 49:30 总结

离群点检测(第二部分)

原文:https://towardsdatascience.com/outlier-detection-part-2-6839f6199768

偏斜分布的校正箱线图

图片来自 Unsplash

简介

之前的文章中,我已经讨论了大多数正态分布的异常值检测程序。这些程序包括

  1. IQR(四分位数间距)
  2. 标准偏差
  3. z 分数
  4. 修改的 Z 分数

在实施了上述程序后,我们仔细查看了箱线图,并显示了每种情况下异常值的数量。在真实数据集中,分布并不总是正态的。通常,它们是有偏差的,并且包含不想要的极端值。在本文中,我将详细介绍偏斜分布的异常值检测过程,并相应地调整箱线图。

偏态分布

IQR 方法以及标准差、Z 得分和修正 Z 得分非常适合正态或接近正态的类型分布。然而,大多数真实的工作数据是不正常的,并且经常是有偏差的。这意味着数据可以在分布的低端或高端有尾部。这些类型的偏态分布如下所示。对于偏斜数据,相应的箱线图也会变得不对称。

免费可重复使用的图片来自维基媒体

半四分位数范围

Aucremanne L .等人[1]提出使用半四分位间距(SIQR)来调整这些偏斜分布的上限和下限,但后来发现它不支持对长尾分布的箱线图进行适当调整。例如,我们想要比较同一个 AirBnB 数据的一个倾斜箱线图,并提取联排别墅的价格。使用标准 IQR 方法会产生一个异常值,SIQR 也会产生一个异常值。因此,不幸的是 SIQR 方法在我们的情况下对调整箱线图没有多大帮助[2]。

用于 SIQR 实施的公式为

标准 IQR 和 SIQR 程序产生相同的箱线图。左图来自标准 IQR 方法,右图来自 SIQR 方法。[图片由作者提供]

医疗所

Brys 等人引入了对数偶的概念来度量具有连续单峰分布的单变量样本的偏斜度。这意味着感兴趣的参数应该具有一个清晰的单峰,而没有与另一个可能的峰的任何重叠。它定义了下面的内核函数

其中 xi < Q2 < xj。核函数 h 也由下式定义

这个定义非常类似于 Bowley [4]提出的四分位数偏斜度的测量

[2]中设定了一组新的异常值限值,其中包含了下图所示的热电偶。

下限:Q1-h_low (MC) * IQR

上限:Q3+ h_high (MC) * IQR

显然,该核函数可以被线性或二次或指数模型利用。然而,我们不会详细讨论这些细节,但是我们将用 python 实现 medcouple 计算,如果读者有兴趣了解更多,可以浏览[2]。简而言之,很明显,与固定的 IQR 乘数 1.5 相比,这里我们基本上部署到不同的核函数(h_low 和 h_high),因此,我们将获得非对称边界,而不像标准的 IQR 过程。

python 中的医疗所

使用 stasmodel 库在 python 中实现 medcouple 的计算非常简单。

对于这个数组,medcouple 值是 0.145,这是上面讨论的所有核函数值的中值。据报道,与二次模型相比,指数模型更加有效和方便,因为它只有一个参数要处理,而二次模型可以根据模型的复杂性确定几个未知变量。

与[2]不同的核函数(指数模型)将导致不对称极限

在[2]中,指数项的乘数取为 1.5,a 和 b 的值可以通过用不同的值拟合数据来计算。我们可以在以后的文章中讨论利用线性、二次和指数模型。但是现在,通过阅读到目前为止,你已经很好地理解了为什么传统的 IQR 方法和箱线图不能用于偏斜分布。

结论

在本文中,我们已经了解了偏态分布的基本概念,并展示了为什么确定异常值的传统方法可能会产生一些后果。常规 IQR 极限之外的数据点不一定是真正的异常值。利用 medcouple 程序,我们可以获得不对称的边界,并更有效地提取异常值,这些异常值有更多的机会表现出与大多数群体不同的行为。

感谢阅读。

有用的链接

Github 页面

我的网站:从数据中学习

Youtube

https://mdsohel-mahmood.medium.com/membership https://mdsohel-mahmood.medium.com/subscribe

参考文献

  1. Aucremanne,Luc & Brys,Guy & Hubert,Mia & Rousseeuw,Peter & Struyf,Anja。(2004).对比利时通货膨胀、相对价格和名义刚性的研究,使用新的稳健的偏度和尾部权重测量。10.1007/978–3–0348–7958–3_2.
  2. 米(meter 的缩写))休伯特和 e .范德维伦,“偏斜
    分布的调整箱线图”,计算统计&数据分析第 52 卷,第 12 期,2008 年 8 月 15 日,第 5186–5201 页
  3. Brys,g ,, Hubert,m ,, Struyf,A,“一个稳健的偏度度量”,计算和图形统计杂志,2004 年,第 13 卷,996 -1017
  4. 鲍里,洛杉矶,1920 年。统计学原理。查尔斯·斯克里布纳的儿子,纽约

离群点检测(第 1 部分)

原文:https://towardsdatascience.com/outlier-detection-part1-821d714524c

IQR、标准差、Z 值和修改后的 Z 值

作者图片

简介

在数据驱动模型中包含异常值是有风险的。一个误导值的存在有可能改变模型所暗示的结论。因此,检测并决定是否将其从数据集中移除非常重要。有时数据点可能非常高或非常低,但这并不意味着它是我们想要摆脱的异常值。可能只是一个极端的数据点。这取决于用户决定它是否应该包含在模型中。然而,有时数据点可能只是数据收集或处理过程中的一个打字错误或一些人为因素。

离群点检测方法

在本文中,我将介绍几种统计异常值检测方法,包括四分位间距(IQR)、标准差、Z 得分和修正的 Z 得分。我将使用 python 来实现这些方法,并最终共享数据和笔记本。我将使用波士顿 AirBnB 数据和 CC0:公共领域许可。

四分位间距

四分位数间距(IQR)是最广泛使用的离群点检测和删除程序之一。根据这个程序,我们需要遵循以下步骤:

  • 找到第一个四分位数,Q1。
  • 求第三个四分位数,Q3。
  • 计算 IQR。IQR = Q3-Q1。
  • 定义正常数据范围,下限为 Q1-1.5 * IQR,上限为 Q3+1.5*IQR。
  • 任何超出此范围的数据点都被认为是异常值,应该删除以便进一步分析。

在箱线图中,实施这种 IQR 方法来检测任何极值数据点,其中最大点(高晶须的末端)是 Q3+1.5*IQR,最小点(低晶须的起点)是 Q1–1.5 * IQR。

用 python 实现

AirBnB 数据有一个价格列,我们将考虑实施。确定 Q1、Q3 和 IQR 后,异常点将从数据框中移除,如下所示。

应用 IQR 方法后检测到一个异常值[图片由作者提供]

异常值与其他箱线图细节一起显示[图片由作者提供]

异常值为 417,高于箱线图所示的最大限值(whishi = 402)。我们没有低于 whislo (10)的 ant 异常值。

正态分布说明

如果我们有一个正常的数据集,四分位数可以根据平均值和中位数来确定。

[图片由作者提供]

[图片由作者提供]

因此,对于正态分布数据,实施乘数为 1.5 的 IQR 方法与使用乘数为 2.7 的标准差方法相同,如上所示。然而,我们经常会将规格限制在 3 个标准差。为此,IQR 乘数应该是~1.7,而不是 1.5(为此,我们只将限制设置为标准差的 2.7 倍)。

标准偏差

标准差法类似于上面讨论的 IQR 程序。根据 2 倍标准偏差或 3 倍标准偏差的设定限制,我们可以从数据集中检测并移除异常值。

上限=平均值+ 3 *标准偏差

下限=平均值-3 *标准偏差

当平均值+/- 3 倍标准差被设定为极限值时,会发现更多的异常值[图片由作者提供]

这里异常值的总数是 57。当使用 2 倍 stdev 时,传单数量下降到 20。这是因为 2 倍 stdev 意味着更严格的极限集,并且大多数可能的极值点已经被该过程移除。移除后只剩下少数几个,当用更新的数据生成新的箱线图时,这些仍然被认为是异常值。

Z 值

z 分数只是另一种形式的标准差程序。z 得分用于将数据转换为另一个均值为 0 的数据集。

[图片由作者提供]

这里,X-bar 是平均值,s 是标准偏差。转换数据后,中心变为 0,对应于每个数据点的 z 得分以标准差的形式表示与中心的距离。例如,z 值为 2.5 表示数据点距离平均值有 2.5 个标准偏差。通常 z 分数=3 被认为是设置限制的临界值。因此,任何大于+3 或小于-3 的 z 分数都被视为异常值,这与标准差方法非常相似。

我们发现在实施该方法之前异常值的数量是 21,并且在去除这 21 个异常值之后获得了 20。

实施截止值设置为 3 的 Z 分数方法后的异常值[图片由作者提供]

事实上,这 20 个异常值是我们从 3 倍 stdev 方法中获得的相同数据点。因此,用户可以选择任何一种。

修改后的 Z 值

Z-score 易受极端数据点的影响。如果有一个极值,对应于该点的 z 值也将是极值。它有可能使平均值明显偏离其实际值。修改后的 z 得分比标准 z 得分更稳健,因为它是根据中位数绝对偏差(MAD)计算的。修正 Z 值的公式为[1]

[图片由作者提供]

因为它依赖于中间值,所以不容易受到任何异常值的影响。我们可以使用修正的 Z 值的临界值 3.5 [1]。一旦实现了这个过程,我们就获得了与 Z-score 相同的结果,因为数据集可能没有任何有影响的异常值。

实施截断值设置为 3.5 的修正 Z 分数方法后的异常值[图片由作者提供]

结论

在本文中,我们展示了检测数据集中异常值的四个统计过程。对于正常数据集,IQR 乘数 1.7 类似于标准偏差乘数 3,这也类似于将 Z 值的临界值设置为 3 作为检测限。统计学家使用修改的 Z-score 来最小化异常值对 Z-score 的影响。这个修改后的 Z 值表示异常值的相对强度,以及它与它应该具有的 Z 值的偏差程度。所有这些程序都是统计确定异常值的标准程序。

代码为的 Github 页面

我的网站:从数据中学习

本文的 Youtube】

代码演练

https://mdsohel-mahmood.medium.com/membership

参考

  1. 析因实验设计的稳健数据分析:改进的方法和软件,萨马德,马吉德,http://etheses.dur.ac.uk/2432/1/2432_443.pdf

Python 中的离群点检测技术

原文:https://towardsdatascience.com/outlier-detection-techniques-in-python-1fabf8e68bec

Python 中带有示例的异常值检测方法指南

图片由塞巴斯蒂安·沃特曼像素上拍摄

离群点检测是识别数据中极值的过程,在金融、保险、网络安全和医疗保健等各行各业都有许多应用。例如,在金融领域,它可以检测像信用卡欺诈这样的恶意事件。在保险业,它可以识别伪造或编造的文件。在网络安全中,它用于识别密码盗窃和网络钓鱼等恶意行为。最后,异常值检测已经被用于医疗环境中的罕见疾病检测。

鉴于异常值检测在各行各业都有许多重要的应用,在这些领域工作的数据科学家需要对进行此类分析的最新方法和工具有一个扎实的了解。

离群点检测有许多方法,每种方法都有自己的优点。广泛用于离群点检测的两种方法是描述性统计和聚类。描述性统计是一种使用汇总统计对数据集中的特征进行定量描述的方法。这包括平均值、方差、最大值和最小值等计算。这还包括箱线图、直方图和散点图等图形表示。相反,聚类技术是一组无监督的机器学习算法,将数据集中的对象分组在一起,以便相似的对象在同一组中。

使用描述性统计检测异常值的一种常用方法是使用四分位数范围(iqr)。该方法通过分析落在四分位数指定范围内的点来工作,其中四分位数是数据的四个等分部分。虽然 IQR 适用于包含单一形状或图案的数据,但它无法区分数据集中不同类型的形状或数据点组。

幸运的是,聚类技术通过有效地将样本分成不同的形状,解决了 IQR 的局限性。用于异常值检测的常用聚类方法是 DBSCAN,它是一种无监督聚类方法,解决了 IQR 的许多限制。它通过识别组来工作,使得每个组的成员密集地聚集在一起,然后将离群值识别为落在任何密集聚集的聚类之外的数据点。与 IQR 不同,DBSCAN 能够捕捉形状和大小不同的星团。

Pandas 中的分位数方法便于计算 IQR。对于聚类方法,Python 中的 Scikit-learn 库有一个易于使用的 DBSCAN 算法实现,可以很容易地从 Scikit-learn 中的 clusters 模块导入。这种易用性对于初学者来说尤其理想,因为 Scikit-learn 软件包允许用户使用默认算法,只需要数据科学家提供最少的规范。此外,每种算法的界面都允许用户轻松修改参数,以进行快速原型制作和测试。

我们将使用信用卡欺诈数据集。我们将应用 IQR 和 DBSCAN 来检测这些数据中的异常值,并比较结果。这些数据拥有开放数据库许可证,可以自由共享、修改和使用。

读入数据

让我们从导入 Pandas 库开始,并将我们的数据读入 Pandas 数据框:

df = pd.read_csv("creditcard.csv")

接下来,让我们使用 Pandas 方法 set_option()放宽列和行的显示限制:

pd.set_option('display.max_columns', None)
pd.set_option('display.max_rows', None)

出于演示目的,我们将使用数据的缩减采样版本:

df = df.sample(30000, random_state=42)df.to_csv("creditcard_downsampled5000.csv", index=False)

现在,让我们使用 head()方法显示前五行数据:

print(df.head())

作者图片

正如我们所看到的,数据集具有从 V1 到 V28 的列,这反映了使用对应于交易信息的特征生成的 28 个主要成分。由于客户保密,有关原始功能的信息不公开。

数据还包含交易金额、类别(对应于欺诈结果:欺诈为 1,否则为 0)和时间,时间是数据集中每笔交易和第一笔交易之间的秒数。

四分位距(IQR)

iqr 是一种分析数据中的点如何分布在数据的各个部分的有用方法。一般来说,这是测量数据分布的好方法。iqr 是根据四分位数定义的,即四个等分的数据组。

  • 第一个四分位数(Q1)对应于 25%的数据低于该点的值。
  • 第二个四分位数(Q2)是数据列的中值。该列中 50%的数据低于该值。
  • 第三个四分位数(Q3)是列中 75%的数据低于该值的点。
  • IQR 是第三个四分位数(Q3)和第一个四分位数(Q1)之间的差值。

计算完第一个和第三个四分位数后,计算 IQR 就简单了。我们只取第三和第一个四分位数之差(第三季度减去 Q1)。一旦有了 IQR,我们就可以用它来检测数据列中的异常值。

使用 IQR 检测异常值被称为 1.5 倍 IQR 规则。使用这个规则,我们计算上限和下限,我们可以用它来检测异常值。上限被定义为第三个四分位数加上 1.5 倍的 IQR。下限定义为第一个四分位数减去 1.5 倍 IQR。

它的工作方式如下:

  1. 计算上限:Q3 + 1.5 x IQR
  2. 计算下限:Q1-1.5 x IQR
  3. 通过移除任何小于下限或大于上限的值来计算异常值。

让我们对数据中的 V13 列执行这个操作。首先,让我们创建一个 V13 列的箱线图。我选择 V13 是因为我们的箱线图中该数据列的 IQR 很容易看到。箱线图是直观显示数据列中 IQR 的有用方法。我们可以使用三行简单的代码来生成 V13 的箱线图:

sns.set()sns.boxplot(y = df['V14'])
plt.show()

作者图片

这里我们可以看到,我们将大量的信息浓缩在一个图中。很可能,我们首先会注意到的是蓝框,它对应于 IQR。在蓝框内,垂直黑线对应于中间值。蓝框的左边缘和右边缘分别对应于 Q3 和 Q1。最左边和最右边的垂直黑线分别对应于上限和下限。最后,最左边和最右边的黑点对应异常值。我们可以使用上限和下限的值来移除异常值,然后通过生成另一个箱线图来确认它们已经被移除。

首先让我们计算这个列的 IQR,这意味着我们首先需要计算 Q1 和 Q3。幸运的是,熊猫有一个简单的方法,叫做分位数,可以让我们这样做。为了计算 Q1,我们调用 quantile()方法,参数输入为 0.25(代表第 25 个百分点):

Q1=df['V13'].quantile(0.25)
print("Q1:", Q1)

作者图片

我们看到第一个四分位数(Q1)是-0.64。这意味着 V13 列中只有 25%的数据低于-0.64。

为了计算 Q3,我们调用 quantile()方法,参数输入为 0.75(代表第 75 个百分点):

Q3=df['V13'].quantile(0.75)
print("Q3:", Q3)

作者图片

我们看到第三个四分位数(Q3)是 0.66。这意味着 V13 列中 75%的数据低于 0.66。

IQR 就是 Q3 和 Q1 的差值:

IQR=Q3-Q1

从这里,我们可以定义一个新的 Pandas 系列,它包含没有异常值的 V13 值:

IQR=Q3-Q1
print("IQR: ", IQR)

作者图片

我们看到 IQR 是 1.3。这是 Q3 和 Q1 之间的距离。

从这里,我们可以计算上下界。下限是 Q1-1.5 倍 IQR:

lower_bound = Q1 - 1.5*IQR
print("Lower Bound:", lower_bound)

作者图片

我们看到下界是-2.61。因此,任何低于-2.61 的值都是异常值。

上限是 Q3 + 1.5 倍 IQR:

upper_bound = Q3 + 1.5*IQR
print("Upper Bound:", upper_bound)

作者图片

我们看到上限是 2.62。因此,任何高于 2.62 的值都是异常值。

因此,我们移除该列异常值的方法是移除高于 2.62 和低于-2.61 的任何值:

df_clean = df[(df['V13']>lower_bound)&(df['V13']<upper_bound)]

我们现在可以绘制清理后的数据:

sns.boxplot(y = df_clean['V13'])
plt.show()

我们看到上下边界之外的点已经被移除

作者图片

尽管这种方法对于删除单个列中的异常值很有用,但它有一些明显的局限性。最大的限制是无法捕捉数据中的不同形状。例如,列中的某个点在一维箱线图中可能不是异常值,但在二维散点图中可能是异常值。

为了理解这一点,考虑一下美国的中值收入:在撰写本文时,它是 44,225 美元。尽管这一数值属于美国所有收入的 IQR,但如果我们考虑其他因素,它可能会被视为异常值。例如,44,225 美元对于已经执业 10 年的美国医生来说可能是一个异常收入。为了在我们的数据中捕捉不同的形状来描述这种情况,聚类是一种更好的方法。

数据库扫描

DBSCAN 代表基于密度的带噪声应用空间聚类,是一种无监督聚类算法。。该算法通过寻找聚类来工作,使得每个聚类中的点密集/紧密地包装在一起。密度基于聚类中每个数据点附近有多少邻近点。附近的邻居越多,集群密度越高。这种方法将松散聚集的点或单独位于密集聚集的聚类之外的点识别为离群点。

让我们使用 DBSCAN 来识别我们一直在处理的数据中的异常值。首先,让我们可视化一些集群,以了解算法是如何工作的。让我们绘制一个 V13 与 V14 的散点图:

sns.scatterplot(df['V13'], df['V14'])

作者图片

我们看到,我们有一个非常密集的集群,有许多远离集群的离群点。有趣的是,这个二维空间中的一些异常点会落入 V13 的 IQR,并错误地留在数据中。观察图中 V13 接近于 0 的点和 V14 接近于-20 的点。V13 的值很好,而 V14 的值是异常值。这使得这些点成为异常值。

虽然我们可以从 V13 和 V14 中删除离群值来补救这一点,但是对每一列都这样做是很费力的,尤其是如果您正在处理几十个特性的话。

让我们看看如何使用聚类来做得比 IQR 方法更好。让我们从 Scikit-learn 导入 DBSCAN 算法:

from sklearn.cluster import DBSCAN

接下来,让我们定义我们的训练数据。让我们考虑第 13 列和第 14 列:

X_train = df[['V13', 'V14']]

现在,让我们将我们的聚类方法用于训练数据。为了简单起见,让我们通过将输入参数留空来保持默认值:

model = DBSCAN()model.fit(X_train)

让我们生成一个散点图,在这里我们将标记异常值:

cluster_labels = model.labels_
plt.scatter(df["V13"], df["V14"], c = cluster_labels)
plt.show()

作者图片

我们看到,该算法在标记异常值方面做得很好,甚至是 IQR 方法可能会遗漏的异常值。散点图中的黑点对应于 V13/V14 2D 异常值,而红点是良好的数据点。

现在,我们可以根据这些聚类标签轻松地移除这些异常值。让我们将分类标签存储在数据框的新列中:

df['labels'] = cluster_labels

接下来让我们移除离群值。Scikit-learn 的 DBSCAN 实现将聚类标签值-1 分配给有噪声的样本(异常值)。我们可以轻松地删除这些值,并将清除的数据存储在一个新变量中:

df_cluster_clean = df[df['labels'] != -1]

现在,让我们绘制清理后的数据:

作者图片

我们看到原始数据中不再有异常点。最重要的是,IQR 方法在我们只研究 V13 时遗漏的异常值也被剔除了。

值得注意的是,我们只考虑了二维的异常值。三维以上的可视化变得越来越困难。尽管如此,像 DBSCAN 这样的方法能够检测出数据中的异常值,这些数据包含的维度比我们能够可视化或解释的维度多得多,这是一个好消息。当涉及到高维空间中的离群点检测时,聚类确实是一种优越的方法。

这篇文章中的代码可以在 GitHub 上找到。

结论

离群点检测和剔除是数据科学和机器学习的重要组成部分。数据中的异常值可能会对数据中的统计数据的解释产生负面影响,如果公司根据这些错误的计算做出决策,可能会损失数百万美元。此外,离群值会对机器学习模型性能产生负面影响,这会导致样本外性能不佳。糟糕的机器学习模型性能是许多公司的一大担忧,因为这些预测被用来推动公司决策。因此,任何数据科学团队都应该熟悉异常值检测和剔除的可用方法。

在考虑选择哪种方法时,数据科学家应该对手头的数据有很好的理解。如果公司数据很简单,包含的列很少,IQR 应该工作得很好。如果数据包含许多列,很可能数据中存在 IQR 无法捕获的形状和图案。正因如此,在考虑高维空间的离群点剔除任务时,像 DBSCAN 这样的聚类方法是很好的选择。

如果你有兴趣学习 python 编程的基础知识、Pandas 的数据操作和 python 中的机器学习,请查看Python for Data Science and Machine Learning:Python 编程、Pandas 和 sci kit-初学者学习教程 。我希望你觉得这篇文章有用/有趣。

本帖原载于 内置博客 。原片可以在这里找到https://builtin.com/data-science/how-find-outliers-examples

使用自动编码器的异常检测

原文:https://towardsdatascience.com/outlier-detection-with-autoencoders-6c7ac3e2aa90

自动编码器

如果你不能压缩它,它可能有问题

兰迪·法特在 Unsplash 上的照片

你在数据科学和机器学习中遇到的一个经典障碍是离群值。离群值的概念对人类来说是直观明了的,然而除了涉及标准偏差或四分位间距的简单统计数据 101 之外,没有一般有意义的数学定义。

我认为原因可能是想出一个异常值是相当棘手的,因为一个异常值对不同的人可能意味着不同的事情。以下面的数据集为例:

图片由作者提供。

我认为大多数人——包括我——会认为右上角的点是异常值。中间的点更有意思:一方面,它不在你脑子里画的这个假想的圆上,所以它应该是一个离群值。另一方面,与其他点相比,它的 xy 坐标都不是真正疯狂的——中心点实际上是尽可能平均的。

我仍然认为这个点是一个异常值,因为实际数据点形成一个圆感觉是对的。然而,这不是你可以变成一个实际的算法来寻找离群值。

如果这个小小的二维例子已经引发了一些讨论,想象一下当我们处理更高维的数据时会发生什么,你不能再可视化来看它。

在本文中,您将学习如何使用 Tensorflow 中的自动编码器用几行代码构建一个离群点检测器。

我们为什么关心?

根据定义,异常值不像其他数据点。除其他原因外,异常值可能是:

  • 测量错误或输入错误:有人输入了 777 公斤,而不是 77 公斤
  • 虚拟值:有些东西无法测量,数据管道将 999 估算为固定值
  • 稀有数据点/自然离群值:如果你有一个由二手车组成的数据集,一辆法拉利与其他汽车不同

我们关心异常值,因为它会严重扭曲分析和机器学习模型训练的结果。

图片由作者提供。

向模型提供源自错误、输入错误和伪值的离群值基本上意味着向模型提供垃圾,这反过来又使模型学习并输出垃圾。

用自然离群值填充模型可能会导致对这些稀有数据点的过度拟合,这是需要记住的一件重要事情。

注: 处理离群值的方法有很多,这里就不赘述了。最常用的方法是删除异常值。因此,您会丢失一些数据,但是您的分析或模型的整体质量可能会提高。

寻找异常值

有多种方法可以发现异常值。来自牛逼的 scikit-learn 网站:

https://sci kit-learn . org/stable/auto _ examples/miscellaneous/plot _ anomaly _ comparison . html

然而,我们将关注另一种你不知道的方法(还不知道?)在 scikit-learn 中查找。

自动编码器

它基于我在另一篇文章中介绍的自动编码器。

提醒:一个好的自动编码器应该能够将数据压缩(编码)成更少的维度,然后再次解压缩(解码),而不会引入很多错误。

我们可以利用这种直觉得出以下逻辑,用自动编码器找出异常值:

如果自动编码器为某些数据点引入了较大的误差,则该数据点可能是异常值。

更直观地说:自动编码器试图为给定的数据集学习好的编码。由于数据集中的大多数数据点都不是异常值,自动编码器将受正常数据点的影响最大,并且应该在这些数据点上表现良好。

离群值是 autoencoder 在训练过程中不常看到的,因此它可能很难为它们找到一个好的编码。很简单,对吧?让我们试试这个主意吧!

Tensorflow 自动编码器的简单示例

下面,我们将看一个简单的例子,它将帮助您理解和实现上述逻辑。

给圆圈编码

让我们将带有圆圈的介绍性示例编写成代码。首先,让我们创建一个数据集。

import tensorflow as tf

tf.random.set_seed(1234)

t = tf.expand_dims(tf.linspace(0., 2*3.14, 1000), -1)
noise = tf.random.normal((1000, 2), stddev=0.05)
points = tf.concat([tf.cos(t), tf.sin(t)], axis=1) + noise

绘制该图应该如下所示:

图片由作者提供。

现在让我们添加一些异常值。

points_with_outliers = tf.concat(
    [
        points,
        tf.constant([[0., 0.], [2., 2.]]) # the outliers
    ],
    axis=0)

结果是:

图片由作者提供。

现在,我们准备定义和训练一个简单的自动编码器,将二维数据压缩成一维数据。

shuffled_points = tf.random.shuffle(points)

encoder = tf.keras.Sequential([
    tf.keras.layers.Dense(16, activation="relu"),
    tf.keras.layers.Dense(16, activation="relu"),
    tf.keras.layers.Dense(16, activation="relu"),
    tf.keras.layers.Dense(1) # one-dimensional output
])

decoder = tf.keras.Sequential([
    tf.keras.layers.Dense(16, activation="relu"),
    tf.keras.layers.Dense(16, activation="relu"),
    tf.keras.layers.Dense(16, activation="relu"),
    tf.keras.layers.Dense(2) # decode to two dimensions again
])

autoencoder = tf.keras.Sequential([
    encoder,
    decoder
])

autoencoder.compile(loss="mse")

autoencoder.fit(
    x=shuffled_points, # goal is that output is 
    y=shuffled_points, # close to the same input
    validation_split=0.2, # to check if the model is generalizing
    epochs=500
)

# Output:
# [...]
# Epoch 500/500
# 25/25 [==============================] - 0s 2ms/step - loss:       # 0.0037 - val_loss: 0.0096

我们现在可以通过以下方式将我们的点放入自动编码器

reconstructed_points = autoencoder(points_with_outliers)

看看重建是什么样的:

图片由作者提供。

我们可以看到,我们的自动编码器在识别潜在模式方面做得很好。我认为,这不是一个完美的圆,但也足够好了。

顺便提一下,在这里我们可以看到,我们的自动编码器基本上是学会了 将圆展开成一维线,然后再卷回来:

图片由作者提供。

此外,通过观察颜色,我们可以看到重建的点与它们的原始点非常接近。

获取异常值

但是离群值会发生什么呢?现在,让我们将所有的点都放到模型中,并突出显示我们添加的两个异常值。

图片由作者提供。

自动编码器也将两个异常值放在其学习的圆近似上,这意味着它们都移动了相当多,因为它们之前都远离该圆。让我们打印一些数字:

import pandas as pd

reconstruction_errors = tf.reduce_sum(
    (model(points_with_outliers) - points_with_outliers)**2,
    axis=1
) # MSE

pd.DataFrame({
    "x": points_with_outliers[:, 0],
    "y": points_with_outliers[:, 1],
    "reconstruction_error": reconstruction_errors
})

图片由作者提供。

即使没有太多的分析,我们已经可以看到正常的数据点有大约 0.002 的微小重建误差,而我们人工添加的异常值有大约 1 和 4 的误差。不错!

但是,等等,我们只是再次查看一个表来找出异常值。我们不能自动化吗?

好问题!是的,我们可以。从这里有几种方法可以处理。例如,我们可以说重建误差>+3的所有点都是异常值,其中是所有重建误差的平均值, σ 是标准偏差。**

我们可以做的另一件事是说重建误差最高 x%的数据点是异常值。在这种情况下,我们假设数据集的 x%由异常值组成,也称为污染因子,您可以在隔离森林等其他几种异常值检测算法中找到。

与 Scikit-Learn 异常检测器的比较

我创建了一个快速和肮脏的 scikit-learn 兼容异常检测器,以便能够将这种方法与其他 scikit-learn 异常检测器进行比较,如这里的所示

**from sklearn.base import BaseEstimator, OutlierMixin
import numpy as np

class AutoencoderOutlierDetector(BaseEstimator, OutlierMixin):
    def __init__(self, keras_autoencoder, contamination):
        self.keras_autoencoder = keras_autoencoder
        self.contamination = contamination

    def fit(self, X, y=None):
        self.cloned_model_ = tf.keras.models.clone_model(self.keras_autoencoder)
        self.cloned_model_.compile(loss="mse")

        self.cloned_model_.fit(
            x=X,
            y=X,
            epochs=1000,
            verbose=0,
            validation_split=0.2,
            callbacks=[
                tf.keras.callbacks.EarlyStopping(patience=10)
            ]
        )

        reconstruction_errors = tf.reduce_sum(
            (self.cloned_model_(X) - X) ** 2,
            axis=1
        ).numpy()

        self.threshold_ = np.quantile(reconstruction_errors, 1 - self.contamination)

        return self

    def predict(self, X):
        reconstruction_errors = tf.reduce_sum(
            (self.cloned_model_(X) - X) ** 2,
            axis=1
        ).numpy()

        return 2 * (reconstruction_errors < self.threshold_) - 1**

如果我们使用与圆形相同的自动编码器架构,我们会得到以下结果:

图片由作者提供。

这里我们看到了整个方法的一个缺点:如果我们使用错误的神经网络架构,我们可能会以奇怪的形状结束。如果我们使用一个更简单的自动编码器,每个隐藏层有 2 个单元,而不是之前的 16 个,我们得到:

图片由作者提供。

我认为这稍微好一点,但还远远不够好。

给你的作业:找一个在这些小玩具数据集上表现不错的架构!

结论

离群值有可能破坏分析和模型。因此,我们应该能够发现并处理它们。一种方法是使用自动编码器:一种机器学习模型——通常是神经网络——对数据进行编码,然后再次解码。如果编码/解码步骤对于一个数据点效果不好,这个点可能是一个异常值。

这个基本的想法很简洁,但是你仍然需要考虑如何设计自动编码器。虽然这可能非常困难,但这是其他异常值检测模型无法做到的。隔离林和公司有一个固定的架构,只允许你调整一些超参数。有了自动编码器,你就自由了——不管有什么优点和缺点。

我希望你今天学到了新的、有趣的、有用的东西。感谢阅读!

作为最后一点,如果你

  1. 想支持我多写点机器学习和
  2. 无论如何都要计划获得一个中等订阅,

为什么不做 通过这个环节 ?这将对我帮助很大!😊

说白了,给你的价格不变,但大约一半的订阅费直接归我。

非常感谢,如果你考虑支持我的话!

如有问题,在LinkedIn上写我!**

离群值以及在哪里找到它们

原文:https://towardsdatascience.com/outliers-and-where-to-find-them-99de703be9ff

通过示例概述不同的异常值类型

来自兰迪·法斯特的《不拉什》

你有没有想过为什么一个基本的分析,比如月平均销售额的比较,会产生一个与通过非常深入的研究得到的结果完全不同的结果?你有没有想过军事监控是如何如此迅速地发现异常行为的,或者如果你意外购物,你的信用卡是如何向你发送通知的?

在当今的环境中,数据非常重要。它们被到处记录和分析,希望能提取重要的数据。每天都有新的和改进的分析方法和仪器被开发出来。分析的缺点之一是数据并不总是准确的。他们可能有威胁整个过程的差异。一种差异是数据中存在异常值。

简单来说,离群值是与现有数据非常不同的数据点。它们可能是一个干扰分析的坏数据点,也可能是一个导致有趣发现的独特数据点,从而产生可操作的见解。

因此,让我们通过一个例子来看看异常值的各种形式:

例子

假设我们生活在一个有十户人家的小社区里。这些家庭大多由退休人员组成,他们早上 8 点开始一天的工作,晚上 8 点结束

保罗·卡皮斯卡

案例一 :如果你观察到有人晚上 11 点在这个街区的街上走,那就是个异数,可能是个小偷。

比方说,一种新的习俗出现了,整个城镇在星期六聚集在一起,举行聚会直到深夜。

案例二: 如果你在这个小区的某个周六晚上 11 点看到有人走在街上,那很正常;然而,在另一天,这是不寻常的。这是一个异常现象,在特定的背景下,它可能是一个小偷。

这么多老年人住在一起,不时看到有人生病是很自然的。

案例三: 让我们想象一下,我们目睹四个家庭同时患病。这很奇怪,可能是有原因的,比如病毒的传播。

让我们看看如何使用这三种场景来发现不同类型的异常值。

不同的异常值

在一般问题空间的大多数文献中,离群点可以分为三类:全局离群点、上下文离群点和集体离群点。

第 1 类:全球异常值:

图 1:全球异常值(作者图片)

这是最基本的异常值,可以被识别为与整个数据集非常不同的值。这也被称为点异常。

我们示例中的案例 1 就是一个例子。所有人都以一种特定的方式行动,他们不会在晚上 8 点后四处走动,但有一个人或一个点会偏离常规,在某天晚上 11 点四处游荡。

这是异常值的最简单形式,相对于其他异常值来说很容易识别。

类型 2:上下文(条件)异常值:

图 2:上下文异常值(作者图片)

我们根据上下文或条件来定义这种异常值。在单个上下文或条件下,一个数据点显著偏离相同上下文中的其他数据点将被视为异常值。这意味着相同的数据点在不同的情况下不会是异常值。

上述案例 2 是一个上下文异常值。星期六,一个人在晚上 11 点徘徊是常见的,但在其他日子里不会;因此,在周六,它不是一个异常值,但在其他任何一天,它都是。离群值取决于上下文,这里是一周中的某一天。

如果没有背景信息,背景异常值基本上很难发现。在问题公式化本身,必须定义与目标领域相关的上下文

第三类:集体异常值:

图 3:收集异常值(作者图片)

集体离群值是与数据集的其余部分完全不同的数据点的集合。单个数据对象可能不是离群值,但从整体上看,它们可能表现得像离群值。

在前面的场景中,案例 3 是集体异常值的一个例子。当一个地区大量的人同时生病时,这是不寻常的;然而,不时有一两个人生病并不罕见。

我们可能需要关于表现出异常行为的数据对象之间的联系的背景知识,以便发现这些类型的异常。

结论

本文的目的是在分析不同类型的异常值之前对它们进行概述,因为理解它们是如何存在的是至关重要的。如果我们只专注于一种类型,我们可能会忽略其他类型。例如,如果我们只是简单地寻找全局异常值,就很容易错过上下文异常值。因此,了解异常值如何以不同的形式存在是很重要的。

离群值:保留还是放弃?

原文:https://towardsdatascience.com/outliers-keep-or-drop-892b599b8ab6

处理极端值的指南

克里斯平·琼斯在 Unsplash 上的照片

虽然异常值在数据集中很常见,但没有多少人知道如何处理这种观察值的存在。

移除它们通常是可行的,因为它们会扭曲数据分析的结果并影响模型性能。也就是说,不加选择地从数据集中删除离群值也会对项目产生不利影响。

是否应该移除数据集中的极值取决于具体情况。

在这里,我们讨论在去除异常值之前检查异常值的重要性,并探索用统计技术识别它们的方法。

离群值不好吗?

离群值通常被描述为不准确和不适当的信息,需要被移除以获得干净的数据。

然而,在现实中,保留这些数据点可能有其优点。

异常值可以是合法的异常,对于获取感兴趣主题的信息至关重要。虽然它们的包含可能会显著影响结果,但它们可以通过某些统计或数据建模技术来解释。

异常值也可能源于人为错误。数据收集和数据输入等领域的错误会导致获取的数据中包含不适当的值(例如,将负值分配给只应包含正值的特征)。需要移除这些类型的数据点,以便构建稳健的模型或进行精确的分析。

最终,离群值是数据,不管它们来自哪里。因此,删除数据的决定应该始终有足够的证据支持。

为了证明剔除异常值的合理性,首先需要识别这些数据点。一旦识别出它们,用户就可以根据他们的标准进行判断并删除任何值。

识别异常值

有很多方法可以识别数据集中的异常值。让我们介绍一些比较流行的方法,并用 Python 来执行它们。

为了演示这些方法,我们将使用 Scikit-learn 模块中的一个玩具数据集。它不包含任何极值,因此将手动添加一个。

  1. Z 值

数据点可以基于它们的 z 分数被识别为异常值。z 得分是一个数据点偏离平均值多少标准偏差的度量。

z 分数(由作者创建)

z 分数超过某个阈值(通常为 3)的记录被视为异常值。

我们可以计算所有数据点的 z 分数,并识别数值大于 3 的数据点。

代码输出(由作者创建)

2。四分位数间距(IQR)

另一个解决方案是使用 IQR 方法来发现异常值。这种方法需要使用第一个四分位数、第三个四分位数和 IQR 来定义数据点的下限和上限。

下限和上限(由作者创建)

位于这些界限之外的观察值可以被归类为异常值。

在这里,我们使用下限和上限作为参考来找到数据集中的异常值。

代码输出(由作者创建)

除了手动导出数据点的下限和上限,用户还可以使用箱线图找到异常值。该可视化工具定义了具有相同下限和上限的异常值。

代码输出(由作者创建)

方框图中的点代表异常值。虽然箱线图提供了一种更简单的方法来检测异常值,但它不允许用户单独检查这些数据点。

基于异常值检测的发现,用户可以决定是否有足够的证据来支持排除这些数据点,并相应地保留或删除它们。

要考虑的其他因素

在决定保留或删除异常值时,还必须考虑其他因素。

1。领域知识

z 值和 IQR 等统计工具可用于定义阈值,将异常值从其他数据点中分离出来。然而,当定义离群值的标准时,用户也可以利用他们的领域知识。

2。建模技术

在异常值是合法异常的情况下,可能没有足够的理由放弃它们。除了删除这些数据点,人们还可以利用更好地解释它们的技术。

例如,像线性回归和逻辑回归这样的线性模型容易受到异常值的影响。对于这种情况,可以利用对极值更有抵抗力的非线性模型(例如,基于树的模型)。

3。数据量

不幸的是,在现实世界中,数据可能是稀缺的。即使是最精心策划的项目也会因缺乏数据而受阻。在这种情况下删除数据是不利的,并可能导致任何后续分析无效。

移除较小数据集中的数据点应始终合理。

关键要点

照片由 Unsplash 上的 Prateek Katyal 拍摄

一般来说,在做出任何关于预处理或建模的重要决定之前,熟悉数据总是一个好主意。

虽然移除极值可能有助于项目的成功,但是移除任何数据的决定需要有足够的证据支持。

忽略离群值或不加选择地丢弃它们同样是会妨碍项目结果的坏习惯。

为了说明这些类型的数据点,最好是彻底地首先识别异常值,然后在决定如何处理它们之前确定它们的性质。

我祝你在数据科学的努力中好运!

异常值、杠杆、残差和有影响的观察值

原文:https://towardsdatascience.com/outliers-leverage-residuals-and-influential-observations-df3065a0388e

因果数据科学

是什么让观察变得“不寻常”?

封面图片,由作者使用nightcafe生成

在数据科学中,一个常见的任务是异常检测,即理解一个观察值是否“异常”。首先,不寻常是什么意思?在本文中,我们将考察观察值异常的三种不同方式:它可能具有异常特征,它可能不太适合模型,或者它可能对模型的训练特别有影响。我们将会看到,在线性回归中,后一个特征是前两个特征的副产品。

重要的是,与众不同不一定是坏事。具有不同特征的观察结果通常包含更多的信息。我们还预计一些观察结果不会很好地拟合模型,否则,模型很可能是有偏差的(我们过度拟合)。然而,“不寻常”的观察结果也更有可能是由不同的数据生成过程产生的。极端情况包括测量错误或欺诈,但其他情况可能更微妙,如具有罕见特征或行为的正版用户。领域知识永远是王道,仅仅因为统计原因而放弃观察是不明智的。

也就是说,让我们看看观察结果“不寻常”的一些不同方式。

简单的例子

假设我们是一个点对点在线平台,我们有兴趣了解我们的业务是否有任何可疑之处。我们掌握了用户在平台上花费时间的信息,以及他们交易的总价值。是不是有些用户可疑

首先,我们来看一下数据。我从[src.dgp](https://github.com/matteocourthoud/Blog-Posts/blob/main/notebooks/src/dgp.py)导入数据生成过程dgp_p2p(),从[src.utils](https://github.com/matteocourthoud/Blog-Posts/blob/main/notebooks/src/utils.py)导入一些绘图函数和库。为了不仅包括代码,还包括像数据和表格这样的输出,我使用了 Deepnote ,一个类似 Jupyter 的基于网络的协作笔记本环境。

我们有 50 个用户的信息,我们观察他们在平台上花费的hours和总transactions金额。因为我们只有两个变量,我们可以很容易地用散点图来检查它们。

数据散点图,图片由作者提供

hourstransactions之间的关系似乎遵循明显的线性关系。如果我们拟合一个线性模型,我们会观察到一个特别紧密的拟合。

是否有任何数据点看起来可疑地不同于其他数据点?怎么会?

杠杆

我们用来评估“不寻常”观察的第一个指标是杠杆。杠杆作用的目标是捕捉单个点相对于其他数据点的不同程度。这些数据点通常被称为异常值,有几乎无限数量的算法和经验法则来标记它们。然而,想法是相同的:标记在特征方面不寻常的观察。

观察值 i 的杠杆作用定义为

线性回归中的杠杆作用,图片由作者提供

对杠杆的一种解释是作为距离的度量,其中将单个观察值与所有观察值的平均值进行比较。

杠杆的另一种解释是观察结果 iyᵢ 对相应拟合值 ŷᵢ 的影响。

利用替代公式,作者图片

从代数上讲,观察的杠杆 i设计矩阵 X'(X'X)⁻ X 的 iₜₕ 元素。在杠杆的许多属性中,有一个事实是它们是非负的,并且它们的值总和为 X 的维数(在我们的例子中为 1)。

让我们计算一下数据集中观察值的杠杆作用。我们还标记了具有不寻常杠杆的观察结果(我们任意定义为偏离平均杠杆超过两个标准差)。

让我们在数据中绘制杠杆值的分布图。

高杠杆点,作者图片

正如我们所看到的,分布是偏斜的,两个观察值具有异常高的杠杆作用。事实上,在散点图中,这两个观察值与分布的其余部分略有不同。

这是坏消息吗?看情况。离群值本身并不是问题。实际上,如果它们是真实的观察,它们可能比其他观察携带更多的信息。另一方面,它们也更有可能而非是真实的观察结果(例如欺诈、测量误差……),或者与其他观察结果有本质上的不同(例如专业用户与业余用户)。在任何情况下,我们可能希望进一步调查,并尽可能多地使用特定于上下文的信息。

人们永远不应该仅仅因为统计原因而放弃观察

重要的是,观察具有高杠杆的事实告诉我们关于模型特征的信息,但是没有关于模型本身的信息。这些用户只是不同还是他们的行为也不同?

残差

到目前为止,我们只讨论了不寻常的特性,但是不寻常的行为呢?这就是回归残差所测量的。

回归残差是预测结果值和观察结果值之间的差值。从某种意义上来说,它们捕捉到了模型无法解释的东西:一个观察值的残差越高,就越不寻常,因为模型无法解释它。

在线性回归的情况下,残差可以写成

线性回归中的残差,作者提供的图像

在我们的例子中,由于 X 是一维的(hours,我们可以很容易地把它们想象成观测值和预测线之间的距离。

数据、预测值和残差,按作者分类的图像

某些观测值是否有异常高的残差?让我们画出它们的分布图。

高残留点,作者图片

两个观测值具有特别高的残差。这意味着对于这些观察结果,模型不擅长预测观察到的结果。

这是坏消息吗?还是那句话,不一定。太符合观测值的模型很可能 有偏 。但是,理解为什么一些用户在花费的时间和总事务之间有不同的关系可能仍然很重要。像往常一样,领域知识是关键。

到目前为止,我们已经从模型的角度观察了具有“不寻常”特征和“不寻常”行为的观察结果。但是哪里来的模型?我们的模型有多少是由少量的观察数据驱动的?哪些?

影响

影响和影响函数的概念正是为了回答这个问题而开发的:什么是有影响的观察?这个问题在 80 年代非常流行,并且在很长一段时间内失去了吸引力,直到最近,因为解释复杂的机器学习和人工智能模型的需求越来越大。

总的想法是,如果移除它会显著改变估计的模型,则将观察定义为有影响的。在线性回归中,我们将观察值 i 的影响定义为:

线性回归中的影响,作者提供的图像

其中 β̂-i 是省略观测值 i 估计的 OLS 系数。

如你所见,杠杆 hᵢᵢ 和残差 eᵢ 之间有着紧密的联系:这两者的影响力都在增加。实际上,在线性回归中,具有高杠杆的观察值是既有异常值又有高残差的观察值。这两个条件中没有一个足以使观察对模型产生影响。

我们可以从数据中最好地看到这一点。

高影响点,作者图片

在我们的数据集中,只有一个具有高影响的观测值,其值不成比例地大于所有其他观测值的影响。单从散点图上你会猜到吗?

我们现在可以在同一个图中标出所有“不寻常”的点。我还在单独的图中报告了每个点的残差和杠杆。

不寻常的观察,作者的图像

正如我们所看到的,我们有一个高剩余和低杠杆的点,一个高杠杆和低剩余的点,只有一个高杠杆和高剩余的点:唯一有影响的点。

从图中还可以清楚地看出,为什么这两个条件中没有一个足以使一个观察结果产生影响并扭曲模型。橙色点具有较高的残差,但它正好位于分布的中间,因此不能倾斜最佳拟合线。相反,绿点具有很高的杠杆作用,并且远离分布的中心,但它与拟合线完全对齐。移除它不会改变任何事情。相反,红点在特性和行为方面不同于其他红点,因此使拟合线向自身倾斜。

结论

在这篇文章中,我们已经看到了观察“不寻常”的几种不同方式:它们可能有不寻常的特征不寻常的行为。在线性回归中,当一个观察同时具有两者时,它也是有影响的:它使模型向自身倾斜。

在本文的例子中,我们集中在一元线性回归上。然而,由于需要让黑盒机器学习算法可以理解,影响函数的研究最近成为热门话题。对于拥有数百万个参数、数十亿个观察值和大量非线性的模型,很难确定单个观察值是否有影响以及如何影响。

参考

[1] D .库克,线性回归中有影响的观测值的检测 (1980),

[2] D. Cook,S. Weisberg,(1980),技术计量学

[2]许佩文,梁,通过影响函数理解黑箱预测 (2017),《ICML 论文集》

密码

你可以在这里找到 Jupyter 的原始笔记本:

**

感谢您的阅读!

真的很感谢!🤗如果你喜欢这个帖子并想看更多,可以考虑 关注我 。我每周发布一次与因果推断和数据分析相关的主题。我尽量让我的帖子简单而精确,总是提供代码、例子和模拟。

还有,一个小小的 免责声明 :我写作是为了学习所以错误是家常便饭,尽管我尽力了。当你发现他们的时候,请告诉我。也很欣赏新话题的建议!

克服机器学习中最大的障碍:过拟合

原文:https://towardsdatascience.com/overcome-the-biggest-obstacle-in-machine-learning-overfitting-cca026873970

过度拟合是数据科学中的一个概念,当预测模型学会对训练数据进行很好的概括,而不是对看不见的数据进行概括时,就会出现这种情况

图片作者。

解释什么是过度拟合的最好方法是通过一个例子。

想象一下这个场景:我们刚刚被一家开发照片处理软件的公司聘为数据科学家。该公司最近决定在他们的流程中实施机器学习,目的是创建可以区分原始照片和编辑照片的软件。

我们的任务是创建一个模型,可以检测以人为主题的照片编辑。

我们对这个机会感到兴奋,作为我们的第一份工作经历,我们非常努力地工作以留下一个好印象。

我们适当地训练了一个模型,它在训练数据上表现得非常好。我们对此非常高兴,并向利益相关者传达了我们的结果。下一步是在生产中为一小组用户提供模型服务。在模型上线后不久,我们就和技术团队一起设置好了一切,并将结果输出给测试用户。

第二天早上,我们打开收件箱,看到一系列令人沮丧的消息。用户反馈非常负面!我们的模型似乎不能正确分类图像。在培训阶段,我们的模型表现良好,而在生产阶段,我们却观察到如此糟糕的结果,这怎么可能呢?

简单。我们是 过度拟合 的受害者。

我们失业了。好打击!

介绍

上面的例子代表了一种有些夸张的情况。一个新手分析师至少听说过术语过度拟合。这可能是你在行业内工作、关注或听在线教程时最先学会的词汇之一。

尽管如此,过度拟合是一种在训练预测模型时几乎总能观察到的现象。这导致分析师不断面对由多种原因引起的相同问题。

在这篇文章中,我将讨论什么是过度拟合,为什么它是分析师在进行机器学习时面临的最大障碍,以及如何通过一些技术来防止这种情况发生。

虽然这是机器学习中的一个基本概念,但清楚地解释过度拟合的含义并不容易。这是因为你得从训练一个模型意味着什么开始,评估它的性能。在这篇文章中,我写了什么是机器学习,以及它对训练一个模型意味着什么。

从提到的文章来看,

向模型展示数据并让它从中学习的行为称为训练。[…].在训练期间,模型会根据某些假设尝试学习数据中的模式。例如,概率算法的运算基础是在某些数据存在的情况下推断事件发生的概率。

训练模型时,我们使用评估指标来确定模型的预测值与实际观察值的差距。例如,对于一个分类问题(就像我们示例中的问题),我们可以使用 F1 分数来了解模型在训练数据上的表现。

初级分析师在介绍性示例中所犯的错误与培训阶段对评估指标的错误解释以及缺乏验证结果的框架有关。

事实上,分析师在训练期间关注了模型的性能,忘记了查看和分析测试数据上的性能。

什么是过度拟合?

当我们的模型能够很好地归纳训练数据而不是测试数据时,就会发生过度拟合。当这种情况发生时,我们的算法无法很好地处理它从未见过的数据。这完全破坏了它的目的,使它成为一个毫无用处的模型。

这就是为什么过度拟合是分析师最大的敌人:它完全违背了我们工作的目的。

当模型被训练时,它使用训练集来学习模式并将特征集映射到目标变量。然而,正如我们已经看到的,模型可能会开始学习嘈杂甚至无用的信息 —更糟糕的是,这些信息只存在于训练集中。

我们的模型学习它不需要(或实际上不存在)的信息,以在新的、看不见的数据上完成工作,例如在实时生产环境中的用户数据。

Python 中的一个例子

让我们用 Kaggle 著名的红酒数据集来形象化一个过度拟合的案例。这个数据集有 11 个维度来定义红酒的质量。基于这些,我们必须建立一个能够预测红酒质量的模型,它的值在 1 到 10 之间。

我们将使用一个基于决策树的分类器(sk learn . tree . decision tree classifier)来展示一个模型是如何被过度拟合的。

如果我们打印前 5 行,这就是数据集的样子

红酒数据集的前 5 行。图片作者。

我们用这个代码来训练一个决策树。

训练精度:0.623

测试精度:0.591

我们用超参数 max_depth = 3 来初始化我们的决策树。现在让我们尝试使用不同的值—例如 7。

clf =树。decision tree classifier(max _ depth = 7)#其余代码保持不变

让我们来看看准确度的新值

列车精度:0.754

‍Test 精度:0.591

训练集的精度在提高,但测试集的精度没有提高。我们把一切都放在一个循环中,我们将动态地修改 max_depth ,并在每次迭代中训练一个模型。

我们实验的结果。图片作者。

看看高 max_depth 如何对应于训练中的非常高的准确度(接近 100%的值),但在测试集中这如何在 55–60%左右。

我们观察到的是过度拟合!

事实上,测试集中的最高精度值可以在 max_depth = 9 处看到。超过该值,精度不会提高。因此,将参数值增加到 9 以上是没有意义的。

max_depth = 9 的值代表“最佳点”,也就是说,没有过度拟合的模型,但仍然能够很好地概括数据的理想值。

事实上,一个模特也可能非常“肤浅”,会经历合身不足,与合身过度正好相反。“甜蜜点”在这两点之间保持平衡。分析师的任务是尽可能接近这一点。

“甜蜜点”。图片作者。

过度拟合的最常见原因

导致模型过量灌装的最常见原因如下:

  • 我们的数据包含噪音和其他不相关的信息
  • 训练集和测试集太小
  • 模型太复杂了

数据包含噪声

当我们的训练数据包含噪声时,我们的模型学习这些模式,然后尝试将这些知识应用于测试集,显然没有成功。

数据很少,不具有代表性

如果我们只有很少的数据,这些数据可能不足以代表将由使用该模型的用户提供的现实。

模型太复杂了

过于复杂的模型会关注与目标变量映射根本不相关的信息。在前面的例子中,max_depth = 9 的决策树既不太简单也不太复杂。增加该值会导致训练中的性能度量增加,但不会导致测试设置中的性能度量增加。

如何避免过度拟合

有几种方法可以避免过度拟合。在这里,我们看到了实际上经常使用的最常见和最有效的方法

  • 交叉验证
  • 向我们的数据集中添加更多数据
  • 移除功能
  • 使用提前停止机制
  • 使模型正规化

这些技术中的每一种都允许分析人员很好地理解模型的性能,并更快地达到上面提到的“最佳点”。

交叉验证

交叉验证是一种非常常见和非常强大的技术,它允许您在几个验证“迷你集”上测试模型的性能,而不是像我们以前所做的那样使用单个集。这使我们能够理解模型如何在整个数据集的不同部分上进行归纳,从而对模型的行为有一个更清晰的概念。

向我们的数据集中添加更多数据

我们的模型可以简单地通过整合更多的信息来接近甜蜜点。我们应该尽可能地增加数据,以便为我们的模型提供越来越有代表性的“现实”部分。我推荐读者阅读这篇文章,其中我解释了如何从头构建数据集。

移除功能

特征选择技术(如 Boruta )可以帮助我们理解哪些特征对于预测目标变量是无用的。移除这些变量有助于减少观察模型时的背景噪音。

使用提前停止机制

提前停止是一种主要用于深度学习的技术,包括在一系列训练周期内性能没有增加时停止模型。这允许您保存模型在最佳时间的状态,并且只使用这个性能最佳的版本。

使模型正规化

通过对超参数的调整,我们可以经常并且心甘情愿地控制模型的行为,以降低或增加其复杂性。我们可以在交叉验证期间直接修改这些超参数,以了解模型在不同数据分割上的表现。

结论

很高兴你来了。希望您会发现这篇文章很有用,并在您的代码库中实现它的片段。

如果你想支持我的内容创作活动,请随时关注我下面的推荐链接,并加入 Medium 的会员计划。我将收到你投资的一部分,你将能够以无缝的方式访问 Medium 的大量数据科学文章。

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

祝你愉快。留得安好👋

克服机器学习中的偏差和差异

原文:https://towardsdatascience.com/overcoming-bias-variance-in-machine-learning-31169dc649ed

关于模型选择和超参数调整的初学者友好指南

当开始一个机器学习项目时,知道选择什么模型并不总是容易的,尤其是如果你是这个领域的新手。在本帖中,我们将解决你在开始一个项目时应该考虑的一些常见问题。

选择具有正确预测类型的模型:

选择具有正确输出类型的机器学习模型听起来可能是显而易见的,但这是该过程中必不可少的第一步。分类和回归是两种最流行的监督机器学习预测类型。

分类

分类模型预测一段数据应该被分配到什么类别。一些例子可能是弄清楚一张照片是否包含特定的动物,或者一封电子邮件是否属于垃圾邮件。

回归

回归模型预测一个连续的数字,如数量或价格。例如,预测房价、物体重量或产品销量。

图片作者作者

偏差和方差为您的数据集选择和调整模型:

偏差和方差是指机器学习模型产生预测误差的原因。一般来说,我们希望有尽可能低的偏差和方差,但在大多数情况下,你不能减少一个而不增加另一个;这被称为偏差-方差权衡。

图片由作者

什么是高偏差?

高偏差,也称为欠拟合,意味着机器学习模型没有从数据集中学习足够的知识。当模型不够复杂时,就会出现欠拟合。

什么是高方差?

高方差,也称为过度拟合,意味着模型过于关注训练数据集中的特定模式,而不能很好地概括看不见的数据。当模型过于复杂时,可能会发生过度拟合。

偏差和方差的调整模型

模型超参数调整模型从数据集学习的方式。一旦您为您的项目选择了正确的模型类型,您将希望调整超参数以找到数据集的最佳拟合;这是偏差和方差的一个重要因素。

例如,选择深度低的决策树模型可能会不足(高偏差)。但是,我们可以通过增加树深度超参数来增加模型的复杂度,减少偏差误差。

图片由作者

如果模型在调整后仍然有较高的偏差,您可能需要尝试更复杂的模型类型。例如从线性回归切换到多项式回归。

动手决策树代码示例

决策树,也称为分类和回归树(CART),是分类和回归问题最流行的机器学习模型之一。

让我们快速看一下如何通过使用 scikit-learn 中的实现来手动调整分类树的复杂性。

注意:在调优你的模型之前,你还应该决定是要针对精度进行优化还是召回(这个话题不在本文讨论范围之内)。

试着在谷歌实验室自己运行代码

如果您运行上面的代码,我们将在测试数据集上获得大约 31%的准确率。现在尝试增加“最大深度”超参数,使模型预测更加准确。你认为停在什么深度比较好?

开源工具如 scikit-learnDetermined AI (用于深度学习)具有自动估计最佳超参数值的功能,以节省时间并建立更好的模型。

线性与非线性模型

线性模型倾向于具有高偏差,而非线性模型具有高方差。如果相对简单的线条可以表示数据模式,您可以从线性模型开始;否则,您将需要一个更复杂的模型来获得更低的偏差。

几种常用的线性模型:

  • 逻辑回归(用于二元分类)
  • 线性回归(用于简单回归)
  • 多项式回归(用于回归)

几种常用的非线性模型:

  • 决策树(用于分类和回归)
  • k-最近邻(用于分类和回归)
  • 神经网络(用于分类和回归)

我们将在以后的文章中实现这些功能!

尝试从简单开始,建立一个基线,并获得快速胜利!

当有疑问时,从你认为可能有效的最简单的模型开始。即使它可能达不到最终项目所需的精度,但在选择和调整新模型时,您将有一个比较基准。另外,得到一个已经在你的数据集上训练过的模型感觉很棒!

关键要点:

随着经验的增长,选择模型变得更容易,但如果你是机器学习的新手,在开始项目时请记住这些步骤。

  • 知道你想从你的模型中得到什么结果。
  • 如果模型拟合不足,请调整超参数以增加复杂性,或者尝试更复杂的模型以更好地拟合数据集。如果模型过度拟合,尝试相反的方法来降低复杂度。
  • 与任何模型建立基线,并对此感到满意!

用 gRPC 克服 ML 数据预处理瓶颈

原文:https://towardsdatascience.com/overcoming-ml-data-preprocessing-bottlenecks-with-grpc-ca30fdc01bee

提高资源利用率和加速培训的简单解决方案

安德斯·诺贝克·博恩霍尔姆在 Unsplash 拍摄的照片

深度学习项目的健康程度的一个衡量标准是它利用分配给它的训练资源的程度。无论您是在云中还是在您自己的私有基础架构上进行培训,培训资源都是要花钱的,任何一段时间的闲置都代表着增加培训吞吐量和整体生产力的潜在机会。对于培训加速器来说尤其如此——通常是最昂贵的培训资源——无论是 GPU、谷歌 TPU 还是哈瓦那高迪。

这篇博客是上一篇主题为克服数据预处理瓶颈的文章的续篇,在这篇文章中,我们解决了一个不希望出现的场景,在这个场景中,您的训练加速器(此后假设为 GPU)发现自己处于空闲状态,同时等待来自任务过重的 CPU 的数据输入。这篇文章介绍了解决这种瓶颈的几种不同方法,并通过一个玩具示例展示了它们,同时强调最佳选择在很大程度上取决于手头的模型和项目的具体情况。讨论的一些解决方案有:

  1. 选择 CPU 与 GPU 计算比率更适合您的工作负载的训练实例,
  2. 通过将一些 CPU 操作转移到 GPU 来改善 CPU 和 GPU 之间的工作负载平衡,以及
  3. 将一些 CPU 计算卸载到辅助 CPU 工作设备。

第三个选项是使用 TensorFlow 数据服务 API演示的,只需一行代码,您就可以对部分预处理管道进行编程,使其在一个或多个预配置的远程设备上运行。我们展示了如何通过将 tf.data 服务应用于我们的玩具示例,我们能够完全消除 CPU 瓶颈并最大化 GPU 利用率。听起来很棒,对吧?遗憾的是,这一引人注目的解决方案有两个明显的局限性:

  1. 它仅限于张量流框架,并且
  2. 即使在 TensorFlow 框架内,tf.data 服务也仅限于仅使用本机 TensorFlow 操作编程的管道。这可能是一个限制性的约束,因为许多预处理管线需要没有内置张量流等价物的操作。

本文的目标是展示一种更通用的方法,使用 gRPC 将预处理卸载到辅助设备,gRPC 是 TensorFlow 数据服务的基础协议。

尽管本文中的例子将基于 PyTorch 1.10 和 Amazon EC2,但是我们将描述的技术也可以应用于其他培训框架和基础设施。请记住,当你读到这篇文章时,我们使用的一些库可能已经发生了变化,可能需要对我们共享的代码样本进行一些调整。

运行中的 CPU 瓶颈

作为一个玩具示例,我们将使用下面的 PyTorch 模型,它大致基于这个 MNIST 官方示例

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
import timeclass Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 32, 3, 1)
        self.conv2 = nn.Conv2d(32, 64, 3, 1)
        self.dropout1 = nn.Dropout(0.25)
        self.dropout2 = nn.Dropout(0.5)
        self.fc1 = nn.Linear(9216, 128)
        self.fc2 = nn.Linear(128, 10) def forward(self, x):
        x = self.conv1(x)
        x = F.relu(x)
        x = self.conv2(x)
        x = F.relu(x)
        x = F.max_pool2d(x, 2)
        x = self.dropout1(x)
        x = torch.flatten(x, 1)
        x = self.fc1(x)
        x = F.relu(x)
        x = self.fc2(x)
        output = F.log_softmax(x, dim=1)
        return output**class MyMNIST(datasets.MNIST):
    '''
    A personalized extension of the MNIST class in which we
    modify the __len__ operation to return the maximum value
    of int32 so that we do not run out of data. 
    '''
    def __len__(self) -> int:
        import numpy as np
        return np.iinfo(np.int32).max** **def __getitem__(self, index: int):
        return super(MyMNIST,self).__getitem__(index%len(self.data))**def main():
    from torch.profiler import profile, schedule, \
        ProfilerActivity, tensorboard_trace_handler
    profiler = profile(activities=[ProfilerActivity.CPU,  
                                   ProfilerActivity.CUDA],
                       schedule=schedule(wait=120, warmup=5,
                                         active=20, repeat=0),
                       on_trace_ready=tensorboard_trace_handler(
                                 dir_name='profile'),
                       profile_memory=True) use_cuda = torch.cuda.is_available()
    device = torch.device("cuda" if use_cuda else "cpu")
    train_kwargs = {'batch_size': 8192,
                    'num_workers': 8,
                    'pin_memory': True
                   }
    transform=transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize((0.1307,), (0.3081,))
        ])
    dataset = **MyMNIST**('/tmp/data', train=True, download=True,
                   transform=transform)
    train_loader = torch.utils.data.DataLoader(dataset,
                                               **train_kwargs)
    model = Net().to(device)
    optimizer = optim.Adadelta(model.parameters())
    model.train()
    t = time.perf_counter() for idx, (data, target) in enumerate(train_loader, start=1):
        data, target = data.to(device), target.to(device)
        optimizer.zero_grad()
        output = model(data)
        loss = F.nll_loss(output, target)
        loss.backward()
        optimizer.step()
        profiler.step()
        if idx % 100 == 0:
            print(
             f'{idx}: avg step time: {(time.perf_counter()-t)/idx}')if __name__ == '__main__':
    main()

我们在以下部分的目标是比较不同场景下的培训吞吐量。我们将而不是关心模型是否收敛。作为对训练吞吐量的度量,我们将使用每秒输入模型的样本的平均数量。因为我们的目标是最大化吞吐量,所以我们增加批量大小,直到步进时间/批量大小的值达到最小值或者耗尽内存。我们进一步覆盖内置的 PyTorch MNIST 数据集,这样我们就不会用完数据样本。

当在一个 Amazon EC2 p3.2xlarge 实例上运行上述脚本时(使用深度学习 AMI (Ubuntu 18.04)版本 60.1 和内置 pytorch_p38 conda 环境),我们记录的平均步进时间为 0.33 秒,或平均每秒约 2.6 万个样本的吞吐量。

作为常见深度学习训练作业的典型,模型训练(包括正向传递和梯度计算)在 GPU 上计算,而训练数据在传递到 GPU 之前在 CPU 核心上加载和预处理。在我们希望研究的用例中,预处理流水线无法跟上 GPU 的速度。因此,在等待来自 CPU 的数据输入时,GPU 将间歇地空闲。我们通过向预处理流水线添加相对较重的模糊操作来演示这一现象。

transform=transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.1307,), (0.3081,)),
    **transforms.GaussianBlur(11)**
    ])

当我们使用上面的更改重新运行脚本时,步长时间跳到 1.01 秒,吞吐量下降到大约每秒 8000 个样本。我们强调,在 GPU 上运行的训练图没有任何变化;训练速度的显著降低是 CPU 上运行的计算繁重的预处理流水线的结果。

在上面的训练循环中,我们启用了内置的 PyTorch 分析器。在我们的输入瓶颈的情况下,可以在 TensorBoard 中查看收集的概要。

TensorBoard Profiler 报告(按作者)

分析报告显示,GPU 利用率低于 10%,近 90%的训练步骤时间集中在加载和处理训练数据上。

应该注意的是,进一步的分析表明,即使在基线运行中,如果没有大量的模糊操作,也会出现数据输入瓶颈,尽管不太严重。稍后,当我们能够演示低至 0.17 秒的步进时间时,我们将看到这方面的证据。

克服数据输入流水线中瓶颈的方法之一是将一部分数据预处理工作卸载给一个或多个辅助 CPU 设备。在下一节中,我们将探索如何使用 gRPC 协议来实现这一点。

使用 gRPC 卸载数据处理

远程过程调用 (RPC)系统中,客户端-服务器接口由服务器定义,服务器实现客户端可以远程调用的指定方法。在下文中,我们将定义一个专用的 gRPC 服务,用于使用协议缓冲区提取已处理的训练批次。服务器和客户端将在本入门教程之后用 Python 实现。

GRPC 接口

我们创建一个名为 data_feed.proto 的 protobuf 文件,它用一个 get_samples 方法定义了一个 DataFeed 服务:

syntax = "proto3";service DataFeed {
    rpc get_samples(Config) returns (stream Sample) {}
}message Config {
}message Sample {
   bytes image = 1;
   bytes label = 2;
}

该方法被定义为返回类型为的样本的流,其中每个样本代表一个由 batch_size 图像及其关联标签组成的训练批次。

教程中所述,下面的命令将生成相应的 gRPC Python 文件, data_feed_pb2.pydata_feed_pb2_grpc.py ,我们将使用它们来实现服务器和客户端。

python -m grpc_tools.protoc -I<protos folder> --python_out=<out_path> --grpc_python_out=<out_path> <path to proto file>

GRPC 服务器

下一步是实现我们的数据馈送 gRPC 服务器。

import multiprocessing as mp
from concurrent import futures
import grpc
**import data_feed_pb2
import data_feed_pb2_grpc** import torch
from torchvision import datasets, transforms
import numpy as np**# The following class implements the data feeding servie
class DataFeedService(data_feed_pb2_grpc.DataFeedServicer):
    def __init__(self, q):
        '''
        param q: A shared queue containing data batches
        '''
        self.q = q
    def get_samples(self, request, context):
        while True:
            sample = self.q.get()
            yield data_feed_pb2.Sample(image=sample[0], 
                                       label=sample[1])****# The data loading and preprocessing logic.
# We chose to keep the existing logic unchanged, just instead
# of feeding the model, the dataloader feeds a shared queue**class MyMNIST(datasets.MNIST):
    def __len__(self) -> int:
        return np.iinfo(np.int32).max def __getitem__(self, index: int):
        return super(MyMNIST,self).__getitem__(index%len(self.data))**def fill_queue(q,kill):
**    **train_kwargs = {'batch_size': 8192, 'num_workers': 16}**
    transform=transforms.Compose([
            transforms.ToTensor(),
            transforms.Normalize((0.1307,), (0.3081,)),
            transforms.GaussianBlur(11)
            ])
    dataset = MyMNIST('/tmp/data', train=True,
                           transform=transform, download=True)
    loader = torch.utils.data.DataLoader(dataset, **train_kwargs)
    for batch_idx, (data, target) in enumerate(loader):
        added = False
        while not added and not kill.is_set():
            try:
 **# convert the data to bytestrings and add to queue**               
 **q.put((data.numpy().tobytes(),
                       target.type(torch.int8).numpy().tobytes()),
                       timeout=1)**                added = True
            except:
                continuedef serve():
 **'''
    Initialize the data batch queue and start up the service.
    '''** q = mp.Queue(**maxsize=32**)
    kill = mp.Event() # an mp.Event for graceful shutdown
    p = mp.Process(target=fill_queue, args=(q,kill))
    p.start()
 **server = grpc.server(futures.ThreadPoolExecutor(max_workers=8))
    data_feed_pb2_grpc.add_DataFeedServicer_to_server(
        DataFeedService(q), server)
    server.add_insecure_port('[::]:50051')
    server.start()
    server.wait_for_termination()**    kill.set()
    p.join()if __name__ == '__main__':
    serve()

正如代码文档中所描述的,我们选择了一个保持数据加载逻辑完整的实现,并且数据批次被输入到一个共享队列中。 DataFeedServiceget_samples 函数从同一队列中取出批次,并以连续数据流的形式发送给客户端。

该脚本包括一些常量值,应该根据基于 gRPC 的解决方案的部署方式进行调整(或自动配置):

  • gRPC 服务器的 ThreadPoolExecutormax_workers 设置应该根据您想要创建的 gRPC 客户端的数量进行配置。因为我们计划为培训设备上的每个 CPU 内核创建一个客户端,并且我们的 ec2p 3.2 x 大型实例有 8 个 vcpu,所以我们将其设置为 8。
  • 输入到数据加载器的 train_kwargs 结构的 num_workers 设置应该配置为我们的服务器实例上的 vCPUs 数量。我们打算使用的实例是由 16 个 vCPUs 组成的 EC2 c5.4xlarge ,我们已经相应地设置了 num_workers
  • 对于共享队列的 maxsize 值,我们简单地选择了两倍于 num_workers 的值。

我们应该强调,实现数据馈送服务器有许多不同的方式。我们在这里展示的实现对于像我们的玩具问题这样的用例来说不一定是最佳的。以下是更多相关信息。

GRPC 客户

最后,我们修改我们的训练脚本,以便使用来自 gRPC 服务器的经过处理的训练数据。下面的块包括修改:

**class RemoteDataset(torch.utils.data.IterableDataset):
    '''
    An iterable PyTorch dataset that opens a connection to the
    gRPC server and reads from a stream of data batches 
    '''
    def __iter__(self):
        import grpc
        import data_feed_pb2_grpc
        import data_feed_pb2
        import numpy as np
        host = '<ip of host>'
        channel = grpc.insecure_channel(f'{host}:50051',
                    # overwrite the default max message length
                    options=[('grpc.max_receive_message_length',
                               200 * 1024 * 1024)])
        stub = data_feed_pb2_grpc.DataFeedStub(channel)
        samples = stub.get_samples(data_feed_pb2.Config())
        for s in samples:
            image = torch.tensor(np.frombuffer(s.image, 
                              dtype=np.float32)).reshape(
                                       [8192, 1, 28, 28])
            label = torch.tensor(np.frombuffer(s.label, 
                              dtype=np.int8)).reshape(
                                       [8192]).type(torch.int64)
            yield image, label**def main():
    device = torch.device("cuda")
    train_kwargs = {**'batch_size': None, #the data is already batched**
                    **'num_workers': 8,**
                    'pin_memory': True
                   }
    **dataset = RemoteDataset()** train_loader = torch.utils.data.DataLoader(dataset,
                                               **train_kwargs)
    model = Net().to(device)
    optimizer = optim.Adadelta(model.parameters())
    model.train()
    t = time.perf_counter() for idx, (data, target) in enumerate(train_loader, start=1):
        data, target = data.to(device), target.to(device)
        optimizer.zero_grad()
        output = model(data)
        loss = F.nll_loss(output, target)
        loss.backward()
        optimizer.step()
        profiler.step()
        if idx % 100 == 0:
            print(
             f'{idx}: avg step time: {(time.perf_counter()-t)/idx}')
if __name__ == '__main__':
    main()

我们已经定义了一个可迭代的 PyTorch 数据集, RemoteDataset ,它打开一个到 gRPC 服务器的连接,并从一个数据批次流中读取数据。 DataLoader workers 的数量设置为 8,即训练实例上 vCPUs 的数量。因此,总共将创建 8 个 gRPC 客户端。

我们再次强调,选择 gRPC 客户端实现主要是因为它的简单性,并且很可能存在更优的实现。

结果

是时候衡量我们的 gRPC 解决方案在多大程度上成功缓解了我们的 CPU 瓶颈了。下表总结了我们在 EC2 c5.4xlarge 和 EC2.9xlarge 实例上运行数据服务器时获得的吞吐量结果。对于 EC2.9xlarge 实验,我们更新了常量参数以反映 36 个 vCPUs 的存在。

实验结果(作者)

使用 16 位 vCPU 工作人员使我们的培训速度提高了约 60%,使用 36 位 vCPU 工作人员提高了 80%以上。考虑到 EC2 实例的小时成本(在撰写本文时,p3.2xlarge 的成本为 3.06 美元,c5.4xlarge 的成本为 0.68 美元,c5.9xlarge 的成本为 1.53 美元),我们发现将数据处理卸载到 16 个 vCPU 工作线程可以节省超过 50%的成本,36 个 vCPU 工作线程可以节省大约 75%的成本。我们应该注意,我们的成本计算没有考虑到加速开发时间的潜在收益(例如,为其他任务释放人力和计算资源)。

我们应该注意到,c5.9xlarge 实验产生了比基线实验更低的步进时间。这表明,即使是基线实验也没有最大限度地利用 GPU,并且它也遇到了数据输入管道上的瓶颈。

下面的 TensorBoard 截图总结了 c5.9xlarge 案例的培训表现:

TensorBoard Profiler 报告(按作者)

虽然瓶颈的严重程度已经显著降低,但很明显仍有进一步优化的空间,因为 GPU 仍有大约 30%的空闲时间。

实施说明

我们用一些关于基于 gRPC 的解决方案和我们已经演示的具体实现的一般说明来结束这一部分。

探索所有选项:虽然以我们所描述的方式根除 CPU 瓶颈的想法可能非常有说服力,但我们建议您在采用基于 gRPC 的解决方案之前考虑其他替代方案。您可能会发现,在没有太大困难的情况下,您可以优化您的数据处理以更有效地利用 CPU,或者不同类型的训练实例包含更适合您的工作负载的 CPU 计算与 GPU 计算的比率。

优化负载平衡:在我们展示的解决方案中,我们选择将整个预处理管道卸载到 gRPC 服务器。但是,这可能会导致训练实例上的 CPU 核心利用率严重不足。更有效的解决方案是使用保持 GPU 完全活动所需的最少数量的总 CPU,并平衡训练实例上的 CPU 和辅助 CPU 之间的负载,使它们都得到充分利用。这可以通过在 gRPC 服务器上运行预处理流水线的开始部分并将部分处理的数据发送到训练实例以完成流水线来实现。

小心网络 IO 瓶颈:需要注意的一件事是从服务器传递到客户端的数据量。否则,我们最终可能会用网络瓶颈取代 CPU 瓶颈。网络 IO 带宽限制由服务器和客户端实例的属性决定。为了减少带宽,可以考虑利用 gRPC 协议中内置的压缩支持

扩展到多 GPU :虽然我们提出的解决方案可以扩展到支持多 GPU——数据分布式设置,没有太大的困难,但如何做到这一点的细节可能会有所不同。根据您的项目,您可以选择单个 gRPC 服务器实例和为所有 GPU 核心提供服务的单个服务、单个实例上的多个服务、多个服务器实例或其他组合。

Python GRPC 的多重处理问题:要知道 GRPC 的 Python 实现在处理分叉时有局限性。这可能会干扰 PyTorch 数据加载器,py torch 数据加载器依赖多处理来支持多工作。gRPC 服务器和客户机的任何实现都需要协商这种潜在的冲突。参见此处了解更多关于此限制的详细信息。

tf.data service的对比:与我们提出的解决方案类似,TF . data service 也依赖于 gRPC 协议。然而,与我们的解决方案相反,它是基于 C++ gRPC 的,具有更少的限制,可能更优。此外,tf.data 服务对用户隐藏了 gRPC 实现的许多细节,在某种程度上,将操作卸载到数据服务被简化为仅向输入管道添加一行代码。这使得找到管道中执行卸载的最佳点变得特别容易。相比之下,我们的解决方案需要更多的努力。如果您的工作负载与 tf.data 服务兼容,很可能会带来更好的性能,并且可能是更好的选择。然而,正如我们已经提到的,在撰写本文时,tf.data 服务仅限于只包含本机 TensorFlow 操作的管道。我们描述的基于 gRPC 的解决方案要灵活得多——它不仅在构建输入管道时提供了更大的自由度,而且还允许对如何在 CPU 工作者之间分配任务进行更大的控制。

摘要

过去几年人工智能的进步,部分归功于强大的训练加速器的可用性。我们认为,作为开发者,我们有责任关注我们如何使用这种专门的机器,以便最大限度地提高效率,增加成本节约,并减少碳排放。本文重点介绍了我们可以用来提高资源利用率的一个选项——将数据预处理卸载到一个或多个辅助 CPU 实例。

像往常一样,请随时提出意见、问题和纠正。

深度学习中的过度拟合

原文:https://towardsdatascience.com/overfitting-in-deep-learning-what-is-it-and-how-to-combat-it-9760d25ad05b

关于过度拟合问题和预防方法的简单指南。

国家癌症研究所Unsplash 上拍摄的照片

介绍

深度学习最近的成功是基于拥有数百万参数和大数据的巨大网络。这个简单的配方在许多领域,如图像分类或自然语言处理,给行业带来了革命性的变化。事实证明,这是一把双刃剑。太多的参数可能会导致过度拟合和对看不见的数据泛化能力差。因此,为什么在这篇文章中,我们将仔细看看这个问题,以及我们如何才能防止它。

什么是过度拟合?

图一。图片作者。

当网络具有太多参数并且夸大了数据中的潜在模式时,会发生过度拟合。尽管该模型完全符合数据点,但它不能很好地概括看不见的数据。

另一方面,线性函数产生了过于简化的假设,导致对数据集的拟合不足。

二次方程最适合我们的数据点。它捕捉点的一般形状,从而在测试集上产生良好的泛化。

描述模型性能的常用方法是使用偏差和方差项。偏差代表输出和目标之间的距离,方差定义结果的分布。

图二。图片作者。

使用上面的例子,很明显欠拟合和过拟合取决于网络的容量。在这些统计玩具示例中,模型复杂性和性能之间的最佳平衡点相对容易建立,而深度学习则不是这样。

在下一部分,我们将戴上深度学习的帽子,看看如何在大型网络中发现这些问题。

深度学习方法

到目前为止,我所描述的是一种老式的机器学习方法,目标是找到模型复杂性和性能之间的最佳平衡点。

当深度学习出现时,这种范式发生了转变。事实证明,当模型处于过度拟合状态时,性能会更好。在这种情况下,我们需要应用强大的规则并在训练期间监控模型的行为。

为了检查模型的性能,我们需要首先将数据分成 3 个子集:

  • 训练集-模型训练所依据的数据(65–98)%
  • 验证集-帮助评估模型在训练期间的性能(1–10)%
  • 测试集—帮助评估模型在训练后的性能(1–25)%

分割比例取决于数据集的大小。例如,ImageNet 包含 1000 个类和 120 万个图像。当我们以 98:1:1 的方式分割它们时,我们仍然有 24 万个未见过的测试示例。这应该足以正确评估性能。

就较小的数据集而言,最好保留较大块的不可见数据,以确保模型性能良好。此外,记住每个集合中的类的数量要平衡,这样评估就涵盖了所有的例子。

性能图

图 3。图片作者。

泛化误差是训练误差和验证误差之间的差异。我们模型的最终目标是同时最小化训练和泛化错误。当泛化差距增大时,会发生过度拟合。

监控两条曲线有助于发现任何问题,然后采取措施防止问题发生。在下一节中,我们将介绍用于对抗过度拟合的最流行的正则化技术。

正规化

正则化是我们对学习算法进行的任何修改,旨在减少其泛化误差,而不是训练误差。https://www.deeplearningbook.org/contents/regularization.html

重量衰减

明确地修改网络的复杂性不是一个容易的过程。幸运的是,我们可以通过惩罚大的权重来控制参数的大小。

图 4。图片作者。

这个简单的过程是基于在损失函数中加入惩罚项。现在,新模型的目标是最小化训练误差,并使权重更小。

L2 项是参数的平方和(点积),它严重影响了异常值。因此,权重分布更加均匀(图 5)。

lambda参数定义了模型对重量的敏感度。该值越大,权重变化受到的惩罚就越小(图 5)。在学术论文中,初始值通常被设置为0.0005

图 5。图片作者。多亏了 Piotr 的 Skalski 可视化技术。

辍学

此技术在图层上应用随机采样零值的遮罩。该方案在模型中创建了多个子网组合(图 6)。

图 6。图片作者。

如你所见,单个节点不能再依赖来自其他神经元的信息。它迫使每个节点学习如何自己提取特征。当每个神经元变得更加自治时,整个网络就可以更好地泛化。

更多数据

扩大数据集是使网络更加稳定的最简单方法。模型接触到更多的例子,可以更好的泛化。尽管如此,在大多数情况下,现实生活中的样本数量是有限的。数据扩充基于添加已有数据的略微修改的副本。

图 7。图片作者。

这个方案是许多计算机视觉和 NLP 任务的核心部分。我已经在我的上一篇文章中深入讨论了这个话题,所以我强烈推荐你去看看。

随机深度

图 8。图片来自用于图像识别的深度残差学习。

这种方法仅适用于计算机视觉架构。2015 年,ResNet 论文介绍了剩余连接,它允许我们堆叠越来越多的层🍰相互叠加无信息丢失 ( 图 8 ) 。将多个卷积组合成一个块成为了计算机视觉任务中的一个新范例(图 9)。我们获得了构建任意深度网络的能力,但过度拟合的主要问题仍然是一个障碍。随机深度通过随机丢弃块来解决这个问题。

图 9。图片作者。

SD 使网络更短而不是更薄。丢弃随机输出会给每个模块带来更多的自主权。除了正则化能力,与原始配置相比,它减少了 25%的训练时间。

SD 仅在训练期间使用。在干涉期间,模型使用所有块。

标签平滑

在分类任务中,我们的模型正在优化权重以映射期望的独热编码概率分布[0, 0, 1]。实际上,网络不能精确地预测值 0 或 1,所以它开始无休止的劳动产生越来越大的权重以得到想要的结果。这个过程被称为过度自信。这是过度合身的一个很好的指标。**

K —例数。图 10。图片作者。

给标签添加噪声会阻止网络搜索理想的分布。参数alpha控制噪音量。通常,0.1值是一个很好的起点。

批量标准化

BN 的主要目的是加速收敛并减少网络中的不稳定性。这个附加层位于卷积层之后,以优化输出分布(图 11)。除了计算上的提升,BN 也可以作为一种正则化技术。

图 11。图片作者。

数据集统计数据的近似值会给网络增加一些噪声。请记住,这些估计仅适用于较大的批次,对于较小的批次,性能会急剧下降。

提前停止

我们之前讨论过,监控损失函数有助于发现网络中的问题。手动照看模型是一项单调乏味的任务,可以自动完成。每当泛化差距增大时,提前停止算法就终止训练。

图 12。图片作者。

在这种情况下,离开模型进行通宵训练,早上回来,并加载重量是安全的。有许多方法可以选择保存检查点,但最安全的选择是在每次错误比前一个时期更好时进行选择。

学习率计划

在模型开始过度拟合之前,验证损失达到平稳阶段(图 13)。与其停模型,不如降低学习率,让它训练更久。

图 13。图片来自用于图像识别的深度残差学习。

在这种情况下,我们可以使用更高的学习率,大大加快训练速度。要选择学习率下降的触发因素,最好先观察模型的行为。

结论

祝贺你成功到达终点!在这篇文章中,我解释了过度拟合现象及其从网络的不想要的属性到深度学习的核心组件的进展。我还会给你大量的正规化工具,帮助你成功地训练你的模型。欢迎在评论中提问。

查看我的 Github简介如果你想看我的其他项目。

ML 中的过度拟合:理解和避免陷阱

原文:https://towardsdatascience.com/overfitting-in-ml-avoiding-the-pitfalls-d5225b7118d

探索机器学习模型中过度拟合的原因和解决方案

照片由法比奥Unsplash 上拍摄

机器学习中的过度拟合是一个常见问题,当模型在训练数据集上训练过多时,它会学习关于训练数据的特定细节,这些细节不能很好地概括,并导致对新的、看不见的数据的性能不佳。过度拟合可能由于各种原因而发生,但最终它会导致模型无法很好地进行概括,也无法对以前没有见过的数据进行准确预测。

在这篇博文中,我们将探讨过度拟合的原因,防止过度拟合的方法,以及在过度拟合发生时处理过度拟合的一些策略。

过度拟合的原因

我们将在本文中讨论过度拟合的两个主要原因:模型过于复杂,训练运行时间过长。事实上,这两种情况的结合是过度拟合最普遍的时候!

模型有太多参数

过度拟合的最常见原因之一是模型中的参数相对于可用的训练数据量过多。当一个模型有很多参数时,它可以很容易地学习训练数据中的特定模式,这可以在该数据上产生令人难以置信的性能。然而,当训练表现好得令人难以置信时,它往往是真的!

如果模型已经学习了训练数据中的特定细节,当它遇到新数据时,它可能无法很好地进行归纳并做出准确的预测。这是因为模型基本上记住了训练数据,而不是学习与做出预测相关的潜在模式和关系。

举例

假设您有一个包含 100 所房子的数据集,包括它们的大小、卧室数量、位置和价格。您决定在此数据集上训练一个具有许多参数的复杂模型,如深度神经网络,以预测每栋房屋的价格。

照片由 Breno AssisUnsplash 上拍摄

在对模型进行训练后,您评估了它在训练数据上的性能,发现它可以以非常高的精度预测训练集中的房屋价格。例如,假设模型在训练数据上的平均误差只有 10,000 美元。这可能会让你相信这个模型非常好,可以用来对新房子做出准确的预测。

但是,当你试图用这个模型对新房子进行预测时,你会发现它的表现很差。例如,它对新房子的平均误差可能是 10 万美元。这是一个过度拟合的例子,因为模型有太多的参数,它可以学习训练数据中的特定模式,而这些模式不能推广到新数据。

在本例中,避免过度拟合的一种方法是使用参数较少的简单模型。或者,您可以尝试收集更多的训练数据,以便模型有更多的示例可以学习。这两种潜在的解决方案都可以帮助模型学习更多可以应用于新数据的通用模式。

过度训练

过度拟合的另一个原因是模型训练时间过长。如果模型训练时间过长,它可能会开始过度专门化,并学习训练数据中的特定模式,而不是与做出准确预测相关的一般模式。这可能会导致新的、看不见的数据性能不佳。

Unsplash 上的 Karsten Winegeart 拍摄

过度训练的例子

继续我们上面的例子,我们正在训练一个模型来预测房子的价格。您在 100 个房屋的数据集上训练模型,并在每个训练时期后评估其性能。最初,该模型在训练数据上的错误率很高,但是随着对其进行更多时期的训练,错误率会降低,并且该模型开始在训练数据上表现良好。

但是,如果您继续为模型训练太多的时期,它最终会开始过度适应训练数据。这意味着它将学习训练数据中的模式,这些模式不能推广到新数据,因此在新房子上表现不佳。

在此示例中,避免过度拟合的一种方法是在训练期间使用验证数据集来评估模型。该验证数据集应该与训练数据集分开,并且应该用于评估模型对新数据的性能,并决定超参数,例如训练时期的数量。如果模型在验证数据集上的错误率开始增加,而在训练数据集上的错误率继续减少,这是过度拟合的迹象。然后,您可以在此时停止训练模型,以避免过度拟合。

防止过度拟合

凯·皮尔格Unsplash 上的照片

防止过度拟合的一种方法是使用正则化。正则化是一种为模型添加过多参数或参数值较大的惩罚的技术。这种惩罚鼓励模型只学习数据中最重要的模式,这有助于防止过度拟合。

[## 正规化——查看这篇文章了解更多细节!

towardsdatascience.com](/regularization-avoiding-overfitting-in-machine-learning-bb65d993e9cc)

另一种防止过度拟合的方法是使用交叉验证。在交叉验证中,训练数据被分成几个子集,模型在每个子集上进行训练,并在剩余的数据上进行评估。这允许模型被多次训练和评估,这有助于识别和防止过度拟合。但是,交叉验证在计算上可能非常昂贵,尤其是对于大型数据集,因为它涉及多次训练模型。

通过减少参数数量或使用一般不太复杂的模型来简化模型也有助于防止过度拟合。通常,参数越少的模型越不容易过度拟合。然而,这里必须找到一个平衡点,因为模型需要足够复杂以捕捉数据中感兴趣的模式。

另一种方法是使用集成学习,包括训练多个模型并组合它们的预测。这有助于减少单个模型中可能出现的过度拟合,并可带来更好的整体性能。

最后,收集更多的训练数据会很有帮助。在许多情况下,这可能是困难的、耗时的或昂贵的,但是如果可能的话,为训练收集更多的数据总是一个好主意!更多的数据允许模型学习更多的一般模式和关系,这可以提高它对看不见的数据做出准确预测的能力。

结论

总之,过度拟合是机器学习中的一个常见问题,当一个复杂的模型在训练数据集上训练太长时间时,就会发生这种问题。过度拟合可以通过使用正则化和交叉验证来防止,并且可以通过简化模型、使用集成学习或收集更多训练数据来解决。通过了解和解决过度拟合,可以提高机器学习模型的性能,并对新数据进行更准确的预测。

过度使用“统计显著”这个词会让你看起来很无知

原文:https://towardsdatascience.com/overusing-the-term-statistically-significant-makes-you-look-clueless-f96e1ad1a78e

解读他人假设检验的初级读本

如果你想学一种新的绕口令,试试这句经典的意译:

“统计显著和统计不显著的区别不一定是显著的。”

照片由你好我是尼克Unsplash

作为一名恢复中的统计学家,我有幸认识了许多数据专家,也不快地遇到了许多装腔作势者。尽管我遇到的人很难算是随机样本,但我注意到在使用术语“有统计学意义”和不知道它的意思之间有一种关联。数据专家很少在非正式谈话中使用它。这是为什么呢?

真正的数据专家很少在随意的谈话中使用“统计显著”这个词。

简而言之,因为从其他人的角度来看,术语统计显著并不特别显著。重要的是 设置了计算…如果不是你,结果可能也不适合你。专家明白这一点,所以他们不会把统计意义强加给无辜的旁观者。

这个术语的真正含义和骗子们如何用它来欺骗你是有差距的。我在之前的一篇文章中详细解决了这个问题,我也在本页底部添加了一个快速总结。*在这篇文章中,我将通过一个例子来说明统计决策的过程,并通过给你一个关于解释其他人的假设检验结果的入门知识来帮助你抵御骗子。

罗迪·洛佩兹在 Unsplash 上的照片

无关紧要的例子

想象你有一个制作蓝丝带的朋友。你的朋友碰巧也是一位熟练的决策者,他知道如何使用经典假设检验框架,我已经在这里为需要复习的人解释了这个框架。我假设您知道什么是默认操作以及该流程如何工作。如果你不知道,我建议你在继续之前绕一个小弯:

现在,让我们应用这个框架,让您体验一下推理的过程。

默认操作

通过默认,你的朋友将会转向更便宜的蓝色染料供应商,只要他们已经制作的蓝带没有人类可察觉的视觉差异(操作化以包括对人类感官易错性的容忍)。

设置选择

之前—之前,不是之后(!!)—做一个假设测试,你的朋友必须通过调整一些设置,包括显著性水平、功效/样本大小和假设,来选择做出决定的质量。我希望更多的人了解统计有多少旋钮和刻度盘。

假设检验

然后你的朋友收集数据神奇的事情发生了 **,他们以一个 p 值结束,总结他们是应该便宜点(默认动作还是坚持使用昂贵的蓝色染料(替代动作)。

结论

在统计奥德赛的最后,你的朋友拒绝了零假设,并坚持使用更贵的蓝色染料。这在统计学上是显著的!这是你朋友的结论…但是你应该得出什么结论呢?

解释——从他们的结论到你的结论

差别大吗?

你应该断定染料之间有的巨大差异吗?

不一定。如果不知道你的朋友是如何操作 “人类可察觉的视觉差异”的,你就不会知道他们现在相信的差异是不是你所说的大差异。

在愤怒的统计学家咆哮的这一点上,我们经常会对“效果大小”产生错位的大惊小怪(我将在另一篇文章中涉及)。现在,我要说的是,这些讨论经常没有抓住要点,因为每当我们处理适当的统计决策时,效应大小应该已经被纳入假设。

这是一个重要的发现吗?

这是一个重要的发现吗?不一定。只有那些关心蓝色染料的人,你的朋友选择做什么才是重要的(剧中人的列表,当然包括你的朋友,他采取了统计方法,因为蓝色染料问题对他们个人或专业都很重要)。**

为什么这对其他人来说很重要?为什么真的。可能不是。

统计摘要本质上可以归结为一个关于惊奇的陈述。比如"这让我很惊讶"

不过,有时候,特定的个人惊讶到足以改变他们对某些事情的想法,这一事实对我们其他人来说可能是重要的消息。例如,你可能会认为发表在医学杂志上的一项结果很重要,因为撰写该结果的科学家改变了他们对药物如何起作用的看法。当然,你不知道他们在他们的实验室里到底做了什么(即使他们在论文中为你做了总结,你仍然缺乏一些背景知识...如果你不是专家,你不会探究他们做出的假设的细微差别,但是如果选择信任他们,你可能会对他们认为重要和令人惊讶的事情感兴趣。然而,发现 真的是 吗?不幸的是,如果你只有一个总结,你唯一能做的结论就是:“我信任的人被说服了,所以我被说服了。”如果你不打算自己做研究,你所能做的就是相信你选择的专家,鹦鹉学舌他们的结论,或者在生活中随波逐流,尽力避免对任何事情形成看法。但是我跑题了——如果你想了解更多这方面的推理,你可以在这里找到:**

*https://kozyrkov.medium.com/why-do-we-trust-scientists-98c24e3b9f0e

让我们回到蓝丝带上,好吗?

有意义吗?

除了你的朋友,每个人都可能认为得到完美的蓝色缎带染料是微不足道的。那么,你朋友的发现有意义吗?

除非你知道(并接受)整套假设风险设定决策框架,否则这里唯一的意义就是你知道你的朋友下周会用哪种染料。

这是否意味着来自廉价供应商的另一种染料质量较低?当然不是。

你朋友的决策设置与染色质量问题无关,所以他们的结论不涉及这个话题。即使质量是问题的一部分,检查操作化也很重要。你对染色质量的看法可能与你朋友的不同。

不幸的是,从你朋友的问题()“我是否应该放弃向供应商 B 的更便宜的蓝色染料转移的计划?”)对一位八卦博主的热捧(“根据科学,供应商 B 制造劣质染料!!!")正是那种弱智的数据盲,让我们统计学家想要敲打一些东西。

正是这种弱智的数据盲让我们这些统计学家想要敲打一些东西。

你应该避免更便宜的染料吗?

如果你也从事蓝带行业,你会相信便宜的染料不好吗?不一定。

你可能不会相信你的朋友对染料总量所做的假设(也许他们假设所有瓶染料的方差为零,并且只测试了一瓶,这可能是一个坏批次)和/或你可能对统计风险有不同的容忍度和/或你不会以同样的方式框定你自己对染料供应商的决定,所以他们的发现可能与你无关。

好问题来自于让你对概率好奇的可能性。

然而,欢迎你从分析的角度来消费你朋友的发现……只要你不把它们看得太重。你可以从其他人的工作中获得大量的灵感,帮助你提出更好的问题,构建你自己的决策方法。毕竟,好问题不会凭空出现。它们来自于让你对概率感到好奇的可能性。你需要对什么可能 可能 有一些接触,以便开始问那些好问题。如果你朋友的发现让你好奇,并且蓝色染料在你的生活中是一件大事,你可能会受到启发去做你自己的测试。如果不是,那么,这不就是降噪耳机的作用吗?

Fiona Murray 在 Unsplash 上拍摄的照片

感谢阅读!YouTube 课程怎么样?

如果你在这里很开心,并且你正在寻找一个为初学者和专家设计的有趣的完整的应用人工智能课程,这里有一个我为你制作的娱乐课程:

寻找动手 ML/AI 教程?

以下是我最喜欢的 10 分钟演练:

*解读提醒:“有人被某事惊到了”

虽然“统计显著”听起来像是“重要”或“有意义”……但事实并非如此。不幸的是,这个术语经常被这样滥用。这是个陷阱。

  • 统计上显著的 =某人对某事感到惊讶。
  • 重大的;重要的;值得注意的;值得注意。

请不要把一段枯燥的统计术语和一个意思完全不同的词的诗意混淆起来。

欢迎来到统计学,答案是 p = 0.042,但是你不知道问题是什么。

更多信息请点击此处:

*

AI 应用的 Azure 资源概述

原文:https://towardsdatascience.com/overview-of-azure-resources-for-ai-applications-f9f8d68b6426

了解主要的 Azure 服务,以构建语言、视觉和搜索人工智能应用程序

照片由西格蒙德Unsplash 上拍摄

⚠️在我的博客⚠️中读到了的原帖

导航云提供商找到如何在云中构建人工智能应用程序可能是一项艰巨的任务。这篇文章介绍了不同人工智能应用的主要 Azure 人工智能资源。

视力

1.计算机视觉

计算机视觉是图像应用的主要 Azure 资源。在其最基本的形式中,它支持图像分类和多类对象检测。

它的第一个有趣的功能是图像分析,它可以从图像中提取几个特征:它可以检测品牌,名人和地点。它可以检测图像中人的性别和年龄。它可以向图像添加描述文本,检测配色方案,使用智能裁剪生成缩略图等等。

此外,它可以通过 OCR 技术读取图像中的文本。这可以通过 NLP 工具、内容审核等用于进一步的文本分析。

另一个非常有用的功能是空间分析,计算机视觉可以实时分析人们如何在一个空间中移动,以进行占用计数、社交距离和面具检测。

它还集成了 Face API,如下所述。

计算机视觉有许多集成,可以部署在容器中。

2.定制视觉

Custom Vision 是 Azure 最简单的图像分类和对象检测工具。您可以导入您的图像,给它们贴上标签(Custom vision 至少需要 50 张),并根据您的数据快速训练一个最先进的模型。

您可以探索模型的精确度和召回结果,并将模型发布到可以快速访问它并获得预测的端点。

3. Face API

这是 Azure 的高级面部识别工具。它支持多种面部功能:

  • 人脸检测:检测图像中的人脸
  • 人脸识别:搜索和识别人脸
  • 人脸验证:检查两张人脸是否属于同一个人
  • 面孔相似性:给定一张面孔/一个人,找出相似的面孔/人
  • 人脸分组:根据相似性将未识别的人脸分组

分析图像时,Face API 可以提取以下特征:

  • 面部位置:显示面部位置的边界框
  • 面部标志:面部细节点的集合,包括眼睛位置
  • 人脸属性:年龄、性别、头发颜色、面具检测、配饰、情感、面部毛发、眼镜、头部姿势、化妆、微笑

4.视频分析仪

视频分析器是 Azure 的主要视频分析工具。它可以从视频中提取可操作的见解。

集成的人工智能模型提取准确和有意义的数据。他们利用空间分析来实时了解人们在物理空间中的运动。有了元数据,您可以创建基于时间轴的可视化、热图和异常检测。

视频分析仪主要用于工作场所安全、数字资产管理和流程优化。

该工具可以从视频中提取以下图像特征:

  • 深度搜索:允许搜索整个视频库。例如,对说出的单词和面部进行索引可以实现在视频中查找某人说出某些单词或两个人被看到在一起的时刻的搜索体验
  • 人脸检测
  • 名人识别
  • 视觉文本识别
  • 视觉内容审核
  • 场景分割
  • 滚动积分
  • 等等。

关于音频洞察,视频分析器可以生成以下洞察:

  • 音频转录
  • 自动语言检测
  • 多语言语音识别和转录
  • 隐藏字幕
  • 噪声降低
  • 说话者统计
  • 情感检测
  • 等等。

语言

Azure 有很多 NLP 应用的资源。基本的包括:

  • 语言检测
  • 关键短语提取
  • 情感分析
  • 命名实体识别
  • 实体链接
  • 文本翻译
  • 问题回答
  • 内容审核

路易斯

这是 Azure 的旗舰 NLP 应用程序,用于语言理解。它实现了用户和对话式人工智能工具(如聊天机器人)之间的语言交互。LUIS 可以解释用户目标,并从会话短语中提取关键信息

借助 LUIS,您可以构建企业级对话机器人、商务聊天机器人或使用语音助手控制物联网设备。

在你的 LUIS 应用程序中,你可以创建触发该动作的意图(BookFlight)和话语。您可以添加实体以获得更好的洞察力,例如检测星期几、目的地、机场名称等。

定义意图和示例话语后,训练应用程序。您可以添加语音和拼写检查的集成。你可以用不同的话语来测试这个应用程序,看看它的表现如何。借助 LUIS 的预测洞察力,您可以实现主动学习并提高应用程序的性能。

当模型足够好的时候,你可以发布它,并将其与 Azure 的 Bot 框架或 QnA Maker 集成。

2. Bot 框架

这是创建机器人的服务,例如在后台使用 LUIS。

您可以使用开源 SDK 和工具轻松地将您的 bot 连接到流行的频道和设备。

3. QnA 制造者

基于现有的 FAQ URLs、结构化文档和产品手册发布一个简单的问答机器人。

无需任何经验,您可以使用 QnA Maker 的自动提取工具从半结构化内容中提取问答对,包括常见问题解答页面、支持网站、Excel 文件、SharePoint 文档等。

QnA Maker 允许您通过 QnA Maker 门户或使用 REST APIs 轻松设计复杂的多回合对话。它支持主动学习,支持 50 多种语言

演讲

1.语音转文字

使用 Azure 语音到文本,您可以通过最先进的语音识别获得准确的音频到文本转录。您可以将特定的单词添加到您的基本词汇中,或者构建您自己的语音到文本模型。语音转文本可以在云中运行,也可以在容器的边缘运行。

2.文本转语音

文本到语音允许你用 119 种语言和变体合成超过 270 种神经声音的文本。您可以调整语调、声音类型和许多其他功能。

结论

这只是对 Azure AI 资源的一个大概的概述,范围从视觉、语言、语音。

感谢您的阅读,并在 Twitter 上关注我🚀

机器学习算法概述:回归

原文:https://towardsdatascience.com/overview-of-machine-learning-algorithms-regression-e0f5510e84c

最基本的机器学习算法之一概述:回归算法

作者在 Canva 上创建的图片

回归算法是最基本的机器学习算法之一。不管我们是否注意到,我们在生活的某个阶段都会遇到回归问题。你想坐出租车去机场吗?你给自己找了个回归问题。你想买新房子吗?你又有回归问题了。

回归是一种监督学习,我们在训练过程中为算法提供每个数据的真实值。之后,我们可以使用训练好的模型来预测一个数值,无论是你买新房子应该支付的价格,人们的体重和身高,出生率等。

有几种常见的回归模型,我们将在本文中逐一介绍。具体来说,下面是您将在本文中学到的内容的概要:

  • 线性回归
  • 回归度量和成本函数
  • 正规方程
  • 线性回归的梯度下降
  • 多项式回归
  • 偏差-方差权衡
  • 正则化线性模型(岭回归、套索回归、弹性网)
  • 支持向量回归
  • 决策树回归

所以事不宜迟,让我们从最简单的模型开始,线性回归。

用于回归问题的常见机器学习算法综述

1.线性回归

顾名思义,线性回归试图捕捉预测值(一组输入变量)和我们想要预测的变量之间的线性关系。为了理解这个概念,让我们来看看回归问题的常见例子:

  • 你想乘出租车去机场。你家离机场越远,出租车费就越贵。换句话说,出租车费用与距离成线性相关。
  • 你想买一栋新房子。你想买的房子越大,你需要支付的房款就越多。换句话说,房价和房子大小线性相关。

下面是最简单形式的线性回归方程:

其中:

:预测值
:截距
θ₁ :第一个预测值的权重
x₁ :第一个预测值

为了使上面的等式更直观,让我们使用上面的出租车例子。假设我们想要预测我们需要花费多少钱来乘坐出租车到达机场。如果我们把这个问题代入如上的线性回归方程,那么我们有:

  • 出租车费为预测值【ŷ】****
  • 你家到机场的距离作为预测值【x₁】****
  • 你可以把截距想象成你一上出租车就需要支付的初始价格。如果你注意到,无论你什么时候上出租车,出租车费都不会从 0 开始,而是某个价格,比如说 5 美元。这 5 美元就是截取的【θ₀】
  • 你可以把预测器的权重想象成出租车行驶的距离每增加 1 公里你需要支付的钱数【θ₁】

现在,假设我们已经收集了与距离相关的出租车费用数据,您的数据如下所示。

作者图片

为了预测你需要花在出租车上的钱,你需要在下面画一条红线:

作者图片

上面的红线叫做回归线。多亏了这条回归线,现在你可以根据你的房子和机场之间的距离来估计你需要花的钱。

随之而来的自然问题是:我们如何得到如上的回归线?
这就是我们需要了解回归问题的不同度量和成本函数的地方。

2.回归度量和成本函数

考虑到我们所拥有的数据点,您从上面的可视化中看到的回归线应该是给出最佳拟合的线。

为了得到最佳拟合的线,我们应该有一些东西来衡量。在回归问题中,我们使用指标和成本函数来衡量回归线的好坏,以捕捉数据点的模式。

尽管它们可能是相似的,但是度量和成本函数之间是有区别的。

  • 指标是我们用来评估回归线性能的一个值(即模型生成的拟合线在捕捉数据模式方面有多好)。
  • 成本函数是我们的回归模型在训练过程中试图最小化的一个值(即使用梯度下降算法)

就回归问题而言,我们通常对度量和成本函数使用相同的值,而在分类问题中,度量和成本函数可以不同。

那么,我们通常用于回归问题的这些度量和成本函数是什么呢?让我们从均方差开始。

均方误差

均方差(MSE)是回归分析中最常用的指标。它通过测量我们的回归线预测误差的平方和来测量我们的回归线的拟合优度,如下式所示。

其中:

N :数据点总数
:每个数据点的实际值
:回归线预测的每个数据点的值

从上面的等式中,我们可以看到,实际值和预测值之间的差异越大,MSE 就越大。小 MSE 意味着我们的回归线在预测数据点的价值方面做得很好,反之亦然。

我们需要根据上面的等式计算每个数据点的实际值和预测值之差的平方有两个原因:

  1. 为了确保我们有一个正的误差值
  2. 当误差较大时“惩罚”回归模型

均方根误差(RMSE)

顾名思义,均方根误差(RMSE)是 MSE 的扩展。基本上就是 MSE 的平方根,从下面的等式可以看出。

你可能想知道为什么我们要计算 MSE 的平方根。原因很简单:只是为了方便。它确保误差的单位与我们想要预测的值的单位相同。

使用上面的例子,假设我们想要预测出租车费用,我们的单位是“美元”。平方根运算确保我们的 RMSE 也是“美元”,而不是“美元的平方”。

平均绝对误差

平均绝对误差(MAE)也是回归问题最常见的度量之一,因为它与我们想要预测的值具有相同的单位,就像 RMSE 一样。

MAE 与上面另外两个度量标准的区别在于它如何累积误差。与 MSE 和 RMSE 不同,MAE 不会通过平方每个数据点的实际值和预测值之间的差异来放大预测误差。

从上面的等式可以看出,MAE 的等式和其他两个指标没有太大的区别。它使用 abs 操作符来确保我们有一个正的误差值。

对于回归问题,您可以使用上面提到的三个指标中的任何一个。然而,在本文中,我们将使用 MSE 作为我们的回归指标和成本函数。

现在,一个自然的问题应该出现了:我们知道如何衡量我们的回归线的好,但我们如何改善它?换句话说,我们的模型如何创建最小化 MSE 值的回归线?

为了最小化 MSE 值,我们需要在可用数据点上训练我们的回归模型。有两种最小化 MSE 值和训练回归模型的方法:使用正态方程和使用梯度下降算法。

我们将涵盖这两种方法,让我们从法线方程开始。

3.正态方程

正规方程是封闭形式的解决方案,仅在回归问题中可用。有了这个方程,你可以在一瞬间得到一条最佳拟合的回归线。

计算正规方程需要线性代数知识,因为它广泛地处理矩阵乘法和矩阵求逆。法线方程的一般方程如下:

其中:

:截距和最小化代价函数的每个预测器的权值
X :预测器矩阵
y :每个数据点真值的向量

随着预测器数量的增加,计算将更加复杂。然而,我们可以用 Python 很容易地计算出来,如下图所示。

******import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

# Generate data
np.random.seed(42)
x = 2 * np.random.rand(100,1)
y = 4 + 3 * x + np.random.rand(100,1)

x_b = np.c_[np.ones((100,1)), x] #for intercept

# Compute normal equation
theta_hat = np.linalg.inv(x_b.T.dot(x_b)).dot(x_b.T).dot(y)
print(theta_hat)

# Output: array([[4.51359766],
       [2.98323418]])******

正如您在上面的代码片段中看到的,我们得到了一个有两个值的输出:一个是截距,另一个是预测器的权重。这些值是两个参数的最佳值,这样我们就可以得到最佳拟合的回归线。

最后,我们可以用下面的代码来可视化回归线:

******# Create regression line
x_new = np.array([[0], [2]])
x_new_b = np.c_[np.ones((2,1)), x_new]
y_new = x_new_b.dot(theta_hat)

# Plot regression line
plt.figure(figsize=(12, 8))
plt.plot(x_new, y_new, 'r-')
plt.plot(x,y,'bd')
plt.xlabel('Distance (km)')
plt.ylabel('Price ($)')******

作者图片

有了上面看到的回归线,现在我们可以根据距离预测出租车费用。假设我们知道我们的房子距离机场 1.5 公里,我们可以使用此回归模型预测出租车费用如下:

******x_new = np.array([[1.5]]) # Our distance
x_new_b = np.c_[np.ones((1,1)), x_new]
y_new = x_new_b.dot(theta_hat)

print(y_new)

# Output: array([[8.98844892]])******

如您所见,回归模型预测,如果我们想乘出租车去机场,我们必须支付大约 9 美元。

使用正态方程来寻找最佳拟合的回归线有两个优点:

  • 实现起来非常简单
  • 如果我们有很多数据点(训练数据),这是非常可扩展的

然而,正规方程的一个缺点是,我们的预测因子越多,计算就越慢。这是因为随着预测器数量的增加,矩阵求逆操作将变得越来越复杂。

如果我们使用第二种方法来找到我们的回归线的最佳拟合,即使用梯度下降,这个缺点就不会出现。

4.线性回归的梯度下降

梯度下降是一种优化的机器学习算法,其目标是最小化成本函数,在我们的情况下是 MSE 值。

如果你想了解梯度下降是如何工作的,就想象你在山顶上,你想下山。快速做到这一点的最佳方法是选择最陡斜坡的方向。

这就是梯度下降的作用。它试图通过在每次迭代中选择下降梯度的方向来最小化成本函数,直到它收敛。如果它收敛,这意味着我们已经达到了成本函数的最低可能值。

作者图片

为了实现梯度下降,我们需要在每个迭代或时期中实现两个步骤:

  1. 计算成本函数相对于每个预测值权重的梯度。

或者,我们可以将上面针对所有预测值的等式归纳为以下等式:

其中:

j :预测值总数
k :数据点总数

当我们稍后尝试用 Python 实现梯度下降时,上面等式右边的最后一个等式会派上用场。

2.通过用梯度减去预测值来更新每个预测值的权重。

梯度下降的学习率

从上面的图像中,我们可以看到梯度下降算法试图通过一次一步地向梯度下降的方向移动来最小化我们的成本函数。现在我们可能会问的问题是:我们可以调整梯度下降算法在每次迭代中所采取的步长吗?

是的,我们可以。在这个算法中有一个超参数叫做学习率,或者上式中的 η ,我们可以在每次迭代中调整梯度下降的步骤。

然而,我们需要理解这种学习率调整的好处和缺点:

  • 如果学习率太小,那么回归模型将收敛得非常慢,并且您需要更多的训练迭代来使梯度下降达到全局最小值。

作者图片

  • 如果学习率太高,那么算法将从山谷的一边无序地跳到另一边,这是我们的结果出现分歧的主要原因。成本函数可能会变得比我们的初始值更大,我们将永远得不到最优解。

作者图片

现在我们知道了梯度下降是如何工作的,让我们看看我们可以使用的不同类型的梯度下降实现。

批量梯度下降

批量梯度下降是梯度下降机器学习算法的默认实现。
它在每次迭代中都考虑了我们所有的训练数据,以最小化我们的成本函数。

为了进行批量梯度下降,我们需要计算成本函数相对于每个迭代或时期中每个预测器的权重的梯度。换句话说,我们想知道如果我们稍微调整一下每个预测器的权重,成本函数会发生多大的变化。

下面是批量梯度下降的实现

******# Set hyperparameters
lr = 0.01
epochs = 3000
train_data = len(x)

# For intercept
X_b = np.c_[np.ones((100,1)), x] 

# Initialize weight
theta = np.random.randn(2,1)

# Gradient descent over whole data
for i in range(epochs):

      # Compute the gradient of each predictor
	grad = 2 / train_data * X_b.T.dot(X_b.dot(theta)-y)

      # Update the weight of each predictor 
	theta = theta - lr*grad

print(theta)
# Output: array([[4.51354059], [2.98328456]])

# Visualize result

plt.figure(figsize=(12, 8))
plt.plot(x, theta[0][0] + x * theta[1][0], 'r-')
plt.plot(x, y,'bd')
plt.xlabel('Distance (km)')
plt.ylabel('Price ($)')****** 

作者图片

最后我们得到了完美拟合的回归线!

然而,您可能已经猜到,实现批量梯度下降的一个缺点是速度和效率。如果您有大量的训练数据集,实现批量梯度下降可能不是最好的方法,因为它会变得更慢。因此,让我们看看其他选项。

随机梯度下降

顾名思义,随机梯度下降通过在每次迭代中在我们的训练数据上随机选取数据点来执行成本函数的最小化。由于我们没有使用全部的训练数据,如果我们有大量的训练数据,那么训练过程会快得多。

下面是随机梯度下降的实现:

******epochs = 3000
lr = 0.01

theta = np.random.randn(2,1)

for i in range(epochs):

  for j in range(train_data):

    	# Choose random training data
    	idx = np.random.randint(train_data)
    	x_i = X_b[idx:idx+1]
    	y_i = y[idx:idx+1]

    	# Compute gradients based on randomly selected training data
    	grad = 2 * x_i.T.dot(x_i.dot(theta) - y_i)
    	theta = theta - lr * grad

print(theta)
# Output: array([[4.4920683 ],[2.98692425]])If you want to have a simpler implementation than the code snippet above, then you can also utilize SGDRegressor method from scikit-learn library.from sklearn.linear_model import SGDRegressor sgd = SGDRegressor(max_iter=3000, eta0=0.01) sgd.fit(x, y.ravel()) print(sgd.intercept_, sgd.coef_) # Output: [4.1762039] [3.28159362] # Visualize result plt.figure(figsize=(12, 8)) plt.plot(x, sgd.predict(x), 'r-') plt.plot(x, y,'bd') plt.xlabel('Distance (km)') plt.ylabel('Price ($)')******

如果您想要比上面的代码片段更简单的实现,那么您也可以利用 scikit-learn 库中的 SGDRegressor 方法。

******from sklearn.linear_model import SGDRegressor

sgd = SGDRegressor(max_iter=3000, eta0=0.01)
sgd.fit(x, y.ravel())

print(sgd.intercept_, sgd.coef_)
# Output: [4.1762039] [3.28159362]

# Visualize result
plt.figure(figsize=(12, 8))
plt.plot(x, sgd.predict(x), 'r-')
plt.plot(x, y,'bd')
plt.xlabel('Distance (km)')
plt.ylabel('Price ($)')******

作者图片

由于这种机器学习算法在每次迭代中都会选取我们训练数据的随机实例,因此您会注意到成本函数会在每次迭代中上下跳动,而不是像您在批量梯度下降中看到的那样持续下降。

这有时会导致最终成本函数值不是 100%最优,正如您在上面的回归线可视化中看到的那样。

小批量梯度下降

与批量梯度下降和随机梯度下降相比,小批量梯度下降的实现略有不同,但是一旦我们知道前面的两个实现,它的概念就很容易理解了。

迷你批量梯度下降不是像批量梯度下降那样在每次迭代中获取整个训练数据,也不是像随机梯度下降那样在每次迭代中选取我们训练数据的随机实例,而是在两种实现之间采取中间立场。它将整个训练数据分成几个小的批次,每个批次由从我们的训练数据中随机抽样的实例组成。

如果我们用 GPU 训练我们的数据,这个想法允许我们获得合理的性能增加。此外,我们从这个实现中得到的结果比随机梯度下降更少“混乱”,但不如批量梯度下降平滑。

然而,重要的是要注意,与批量梯度下降相比,用小批量梯度下降训练我们的数据要快得多。

如果您想交互式地了解如何根据我们的数据点生成回归线,请查看这个资源

5.多项式回归

到目前为止,我们已经看到了线性回归模型如何通过最小化成本函数来产生符合我们数据的直线回归线。但是模型能产生比直线更多的东西吗?

虽然该模型被称为线性回归,但它实际上可以产生不仅仅是一条直线。它还可以生成一条曲线,以防您希望模型拟合非线性数据,例如:

******np.random.seed(42)
x = 6 * np.random.rand(100, 1)-3
y = 2 * x**2 + np.random.randn(100,1)

plt.figure(figsize=(12, 8))
plt.plot(x, y,'bd')
plt.xlabel('x1')
plt.ylabel('y')******

作者图片

如果你有像上面这样的数据点,当然常规的线性回归不会削减它,因为它总是产生一条直线。当我们的拟合线是一条直线,但我们有如上的非线性数据点时,我们最终得到如下结果:

作者图片

正如你所看到的,我们的拟合线表现不佳,代表我们的数据点。如果我们提供一个看不见的数据点,这条拟合线的表现也会很差。这种现象被称为欠拟合,这意味着我们的模型过于简单,无法捕捉我们的数据点的模式。

那么,我们应该做些什么来解决欠拟合呢?我们需要对我们的线性回归方程进行一些变换,在回归方程中加入更高次的预测因子,如下所示。

其中 n 是我们可以预先设定的多项式次数。经过这个变换,我们基本上有一个多项式回归。

要将我们的正常线性回归函数转换成上述的多项式回归函数,我们可以使用 scikit-learn 中的 多项式特性 类。这个类将创建每个预测因子的功效,作为我们回归函数中的附加预测因子。

******from sklearn.preprocessing import PolynomialFeatures
import operator

poly_features = PolynomialFeatures(degree=2, include_bias=False)
x_poly = poly_features.fit_transform(x)******

现在如果你想增加每个预测器的次数,那么你需要做的就是在初始化上面的 多项式特征 实例时调整次数参数。

在此特征变换之后,我们可以在此变换的特征上拟合线性回归模型,如下所示:

******import operator

lin = LinearRegression()
lin.fit(x_poly, y)

# Sort value before plotting
sort_axis = operator.itemgetter(0)
sorted_zip = sorted(zip(x,lin.predict(x_poly)), key=sort_axis)
x_pred, y_pred = zip(*sorted_zip)

# Visualize the result
plt.figure(figsize=(12, 8))
plt.plot(x_pred, y_pred, 'r-')
plt.plot(x, y,'bd')
plt.xlabel('x1')
plt.ylabel('y')******

作者图片

现在我们有了一条回归线,可以很好地捕捉我们的数据模式!

如果你想了解更多关于多项式回归的知识,请查看这个资源

6.偏差-方差权衡

现在我们知道,我们实际上可以用多项式回归来拟合非线性数据。我们可能想到的下一个问题是:如果我们进一步增加每个预测器的次数,会发生什么?

如果我们不断增加每个预测因子的次数,那么我们将得到一条严格遵循每个数据点的拟合线。下面是这种现象的一个例子。

作者图片

这不是我们想要从拟合线中得到的,原因很明显:它不能很好地概括。如果我们向模型提供一个看不见的数据点,我们会预期一个糟糕的性能。这就是我们所说的过度拟合。

到目前为止,我们已经听说了描述回归模型性能的两个重要术语:欠拟合和过拟合。

  • 拟合不足意味着模型无法捕获训练数据点的模式。
  • 过度拟合意味着模型试图拟合每个训练数据点,这样它就不会对看不见的数据点进行很好的概括。

如果你理解了这两个术语,那么你也会理解像偏差和方差这样的术语。

  • 偏差衡量我们的机器学习模型的平均预测与我们试图预测的数据点的实际值之间的差异。如果我们的模型产生高偏差,这意味着我们的模型过于简单,无法捕捉我们数据的模式。这意味着我们有一个欠拟合的情况。****
  • 方差测量给定数据点的模型预测范围。如果我们的模型在一个看不见的数据点上产生了很高的方差,这意味着我们的模型对训练数据的微小变化非常敏感,因为它太努力地遵循我们的训练数据的模式。这意味着我们有一个过度装配的案例。****

为了更清楚地理解偏差和方差,下面是当我们的模型具有高/低偏差和方差时,这意味着什么的可视化。

作者图片

当然,通过看上面的图片,你可能已经知道一个理想的模型应该是低偏差和低方差的。

控制偏差和方差通常可以通过调整模型的复杂性来完成(即是否使用线性回归、二阶多项式回归、三阶多项式回归等等)。

增加模型的复杂性也会增加其方差,减少其偏差。另一方面,降低模型的复杂性也会降低其方差,但会增加其偏差。

这就是偏差-方差权衡的意义所在。我们应该做的是找到最佳点,即偏差和方差误差相交的地方,如下图所示:

作者图片

从上面的可视化,现在我们知道我们应该做什么来建立我们的回归模型,这将在训练数据和看不见的数据上表现良好。

  • 如果我们的模型产生高偏差,增加其复杂性(即,我们使用多项式回归代替线性回归)
  • 如果我们的模型产生了很高的方差,降低其复杂性,添加更多的训练数据,或者应用正则化技术,这将在下一节中介绍。

7.正则化线性模型

如上所述,正则化是一种我们可以用来对抗过度拟合的技术。这种技术的作用是给每个预测器的权重增加一个额外的约束。

实践中常用的正则化线性模型有三种类型:岭回归、套索回归和弹性网。先说岭回归。

里脊回归

与常规线性回归相比,岭回归在其成本函数中增加了一项,如下式所示:

其中:

  • α :一个超参数,控制你想要正则化你的线性回归模型的程度。
  • θi :每个预测值的权重

这个附加项的主要目标是在训练过程中保持模型的权重较小。

如果你设置 α 为 0,那么这个岭回归就变成了正规的线性回归。同时,如果我们将 α 设置为一个大的数字,那么所有的权重变得非常接近于零。这将通过数据点的平均值将拟合线变成一条平坦的线。

作者图片

用 scikit-learn 库实现岭回归非常简单,如下所示:

******from sklearn.linear_model import Ridge

ridge = Ridge(alpha=1)
ridge.fit(x,y)******

套索回归

Lasso 实际上是代表最小绝对和选择运算符的缩写。好的一面是,Lasso 回归的实现与岭回归非常相似,从下面的成本函数中可以看出:

我们在套索回归中添加的附加项类似于岭回归。不同之处在于,我们没有平方每个预测器的权重(l2 范数),而是使用了 abs 运算符(I 范数)。

直觉还是和岭回归一样。如果你将 α 设置为零,那么我们最终会得到常规的线性回归。同时,如果我们将 α 设置为一个较大的数字,那么我们最终会得到一条穿过数据平均值的平线。

作者图片

要使用 scikit-learn 实现 Lasso 回归,我们可以执行以下操作:

******from sklearn.linear_model import Lasso

lasso = Lasso(alpha=0.1)
lasso.fit(x,y)******

如果您想了解更多关于岭回归和套索回归的知识,请查看此资源

弹性网

弹性网可以被视为岭回归和套索回归的组合,正如您在下面的成本函数方程中看到的那样。

从上面的等式中,我们有一个额外的术语叫做 I 比率。如果你设置 I 比率为 1,那么我们有一个纯套索回归。同时,如果你将 I 比率设置为 0,那么我们就有一个纯岭回归。

用 scikit learn 实现 Elastic Net 也和其他两个正则化线性模型一样简单,如下所示。

******from sklearn.linear_model import ElasticNet

elastic_net = ElasticNet(alpha=1, l1_ratio=0.5)
elastic_net.fit(x, y)****** 

8.支持向量机回归(SVM 回归)

支持向量机(SVM)是一种更常用于分类任务的机器学习算法。SVM 算法的基本原理是创建一个超平面来分隔具有最大余量的数据点。例如,让我们考虑以下数据点:

作者图片

我们有许多不同的选项来绘制一个区分两个数据点的超平面,最佳选项如下所示:

作者图片

原来我们可以用上面的概念来解决一个回归问题。不同的是:

  • 在分类问题中,我们要求 SVM 算法创建一个具有最大可能余量的超平面,以分离不同类别的数据点。
  • 在回归问题中,我们要求 SVM 算法创建一个超平面,该超平面具有适合尽可能多的数据点的边缘。

SVM 回归本身的概念也不同于典型的基于梯度下降的回归算法,如上面的岭回归、套索回归和弹性网。

对于 SVM 回归,我们的主要目标不是最小化成本函数,而是创建一条误差在某个可接受范围内的回归线。为此,首先我们设置最大误差, ε ,然后 SVM 回归试图找到一个超平面(或回归线)来拟合我们的数据点,同时满足我们预先指定的最大误差。

作者图片

要使用 SVM 回归实现线性回归,可以使用 scikit-learn 库,如下所示:

******from sklearn.svm import SVR

np.random.seed(42)
x = 2 * np.random.rand(100,1)
y = 4 + 3 * x + np.random.rand(100,1)

svr = SVR(kernel = 'linear', epsilon = 0.1)
svr.fit(x,y.ravel())

# Plot result
plt.figure(figsize=(12, 8))
plt.plot(x, svr.predict(x), 'r-')
plt.plot(x,y,'bd')
plt.xlabel('x1')
plt.ylabel('y')******

作者图片

不仅对于线性数据点,SVM 回归也可以用于非线性数据点。您需要做的只是更改 SVM 的核,并选择所需的多项式次数,如下所示:

******svr = SVR(kernel = 'poly', degree= 2)
svr.fit(x,y.ravel())******

如果你想了解更多关于支持向量回归背后的理论,请查看这个资源

9.决策树回归

决策树也是常用于分类的机器学习算法之一,但它也可以用于回归任务。

一棵决策树由三部分组成: 根节点内部节点叶节点 ,如下图所示。

作者图片

对于我们的每个数据点,它通过回答内部节点中的一系列真或假条件,从根节点一直运行到叶节点。最后,叶节点将给出我们每个数据点的结果。

用于分类和回归的决策树之间的概念是相似的。唯一的区别是:

  • 在分类任务中,决策树预测每个节点中的类。
  • 在回归任务中,决策树预测每个节点中的连续值。

用 scikit-learn 实现决策树回归非常简单,如下图所示。

******from sklearn.tree import DecisionTreeRegressor

np.random.seed(42)
x = 2 * np.random.rand(100,1)
y = 4 + 3 * x + np.random.rand(100,1)

tree = DecisionTreeRegressor()
tree.fit(x, y)

# Visualize regression line
x_grid = np.arange(min(x), max(x), 0.01)
x_grid = x_grid.reshape((len(x_grid), 1))
plt.figure(figsize=(12, 8))
plt.plot(x_grid, tree.predict(x_grid), 'r-')
plt.plot(x,y,'bd')
plt.xlabel('x1')
plt.ylabel('y')******

作者图片

然而,上面的结果不是我们想要的,因为它过度拟合了数据。这是决策树回归的常见问题,容易过度拟合。

当我们将所有的超参数设置为默认值时(即我们没有指定树的最大深度),通常会发生这种过度拟合。为了避免这种情况,当我们用 scikit-learn 初始化模型时,我们可以使用 max_depth 参数。

******from sklearn.tree import DecisionTreeRegressor

tree = DecisionTreeRegressor(max_depth=3)******

作者图片

而且看起来比我们以前的型号要好!解决决策树回归中过度拟合问题的一个主要思路是在构建模型时创建一个额外的约束,即通过限制树的深度或限制叶节点的数量。

如果您想了解决策树回归算法背后的可视化,请查看这个资源

结论

在本文中,我们讨论了用于回归问题的常见机器学习算法的概述:如线性回归、岭回归、套索回归、弹性网、SVM 回归和决策树回归。

这些机器学习算法的主要目标是创建一条回归直线,使我们的成本函数或指标(如 MSE、RMSE 或 MAE)最小化。

你准备好创建你自己的回归项目了吗?以下资源可能对您有所帮助:

  • 如果你想开始你自己的回归项目,你可以查看这个资源来启发你从哪里得到你的数据集。
  • 如果你想学习如何使用 Python 进行线性回归,可以查看这个资源或者这个资源

原载于https://www.stratascratch.com**

监督机器学习算法综述

原文:https://towardsdatascience.com/overview-of-supervised-machine-learning-algorithms-a5107d036296

大图如何通过连接点给我们洞察力和对 ML 的更好理解

有这么多的机器学习算法,我们可以找到不同种类的概述和备忘单。为什么是另一个概述?

我试图从以下三个主要方面来构建这个不同的概述:

  • 所有算法的完整层次关系
  • 明显不同的算法之间的关系——比如 LDA vs Logistic 回归梯度推进机 vs 线性回归SVM vs 线性回归等。
  • 更好地回答常见的 ML 问题的结构—例如,数值缩放的效果、特征重要性、线性模型与非线性模型。

当试图连接众多机器学习算法的点时,我发现通常有几种方法来构建/理解算法。例如:

  • 标准线性回归可被视为 OLS (普通最小二乘)回归,并等同于 MLE (最大似然估计)找到的残差正态分布的解决方案。
  • 逻辑回归可定义为奇数比率,但也可视为线性回归的平滑版本。
  • SVM 几乎总是被定义为(软)边际最大化器,这给我们的印象是该方法与其他算法非常不同。而它具有与具有另一损失函数的线性回归相同的方程。

没有更好的方法,每种方法都让我们了解一个方面。一种方法可能比另一种容易,这取决于你已经知道了什么。我在这篇文章中的目的是找到一种有意义的方法来连接它们。这也给了我许多有趣的见解,帮助我更好地理解它们。这种概述也成为一种工具,一种深入研究每种算法的框架。

与其他数据科学家的每次讨论都让我发现了更多。我会继续改进,非常欢迎大家的评论。

1.大局

这第一个概述的想法是映射最常见的算法。在全球范围内,它们被组织在一个等级结构中,有三个主要类别。不同类别的算法之间也有联系,我们将在后面讨论。

  • 第一类包含最近邻算法。其思想是通过为一个新的观测值定义一个距离来进行预测,从而找到最相似的观测值。寻找邻居主要有两种方法:我们固定邻居的数量(这是最常见的方法,它被称为 K-Nearest-Neighbors 算法),或者我们可以定义一个固定的半径(半径邻居算法使用较少,但可以认为它更直观,我们可以在互联网上找到说明 KNN 半径邻居的图片)。
  • 第二类包含基于决策树的算法。简单的决策树通常性能不够,集成方法(理论上,它们也可以应用于其他类别的算法,但实际上,它经常用于决策树)可以用于聚合更多的树。装袋包括用自举样本构建树,并汇总它们(通过计算所有树的平均值)。 RandomForest 引入了另一个随机性:当选择变量来创建分裂时,随机选择的预测值更少。助推技术包括添加弱学习器,常见的例子有 AdaBoost梯度助推机
  • 第三类包含由数学函数做出的算法。而线性回归可以认为是基础算法。从那里我们可以修改损失函数得到套索弹性网SVIM、等等。线性回归的一个缺点是它是…线性的。因此,为了处理非线性数据,必须对其进行增强。我们将讨论特征空间映射、GAM、神经网络。

监督机器学习概述(图片由作者提供)

为了更好地解释这一大背景,我们将一步一步地讨论以下主题:

  • 算法的三个主要类别背后的直觉:为什么是这三个类别,为什么是这个顺序
  • 模型如何被训练并用于回归分类(二元和多类)
  • 这些算法如何处理分类变量数值变量缩放比例
  • 模型之间的关系及其增强:基本算法不是可执行的,可以使用不同的方法来增强它们以克服缺点。而且我还发现,增强一类算法的方式可以来自另一类。

2.算法背后的直觉

如何定义有监督的机器学习?为了具体地回答这个问题,人们通常会提出一种机器学习算法。但是你会选择哪一个呢?对于初学者来说,哪个最直观易懂?

当我试图决定三个主要类别的顺序时,我想反映出有一种强烈的人类直觉。对此,我们来提一些经典的机器学习问题:房价预测和泰坦尼克号幸存者预测。当问一些没有机器学习知识的人时(因为这个想法是将机器智能与人类智能联系起来),邻居的想法最容易用于房屋定价预测,决策树最直观用于泰坦尼克号幸存者预测。

碰巧应用数学函数是最不直观的方法。我们还会看到简单的数学函数不是可执行的,复杂的函数也不是那么直观…

2.1.最近的邻居

对于房价预测,答案往往是:我们看看小区卖的类似的房源吧!

距离的概念非常直观,因为为了预测一个新观测值,我们可以搜索相似的观测值。而且数学上,相似度和邻域会被认为是一回事。我们直观感知的距离是几何距离(在这种情况下,是两栋房子之间的距离)。这个距离也可以推广到两个向量。

这个算法直观的另一个原因是不需要训练。

尽管基于最近邻的算法由于几个原因在现实中不经常使用,但这个想法很有趣,我们将能够涉及到其他算法。

2.2.规则组合

当涉及到泰坦尼克号生存预测时,由规则组合而成的决策树(if-else)是解释机器学习的一种非常直观的方式。而且我们常说,对于某些商业问题,在创建复杂算法之前,可以先应用商业规则。

我们经常问人们,在预测泰坦尼克号乘客的存活率时,应该首先应用哪条规则。许多人认为性是最重要的变量。如果我们试图建立一个决策树,这是真的。我们可以看到,当人们拥有领域专业知识时(女士优先!),他们的直觉对于算法所预测的通常是足够的。

2.3.数学函数

实际上,最不直观的方法是应用数学函数。除非你是线性回归的狂热爱好者。

这一类别中的所有不同算法都是不同的数学函数(因此输入必须是数字,我们稍后将回到这一点),然后必须定义一个损失函数。

在这种情况下,区分模型的作用和损失函数就显得尤为重要。例如,公式 y=ax+b 对于以下算法是相同的:线性回归、岭、套索、弹性网和 SVM。区别在于用于优化的损失函数。

监督机器学习算法背后的直觉(图片由作者提供)

3.模型培训和使用

我们先来定义一些关键词:

  • 模型:每个算法产生一个用于预测的模型(带有新的观察值)
  • 训练算法:对于一些固定的超参数,如何获得模型
  • 超参数调整:寻找最佳超参数以避免过拟合或欠拟合的过程。

3.1.参数和超参数

  • 最近邻:没有训练过程,所以不计算参数。也叫实例学习。虽然没有涉及到训练算法,但仍然有趣的是,可以有不同的方法来找到邻居(球树或 kd 树)。在实践中,我们可以说所有的数据集就是模型。因此,要使用该模型,我们必须存储所有可能非常庞大的数据集。有一些超参数需要优化:KNN 的数量 k 和半径邻居的半径。
  • 决策树:训练过程包括寻找分裂来构建树。所以参数是创建分割的规则。针对一棵树的超参数调整是标准(Gini 或熵)和修剪(树的深度、叶子中的观察值的数量、创建分裂的可用观察值的数量等)。).对于聚集树,树的数量是一个超参数。根据系综方法,有特定的超参数。例如梯度增强树的学习率。
  • 数学函数:模型由两个主要元素组成,数学函数(在深度学习模型的情况下通常称为架构)和每个预测器的系数。训练算法包括寻找系数。在实践中,通过最小化成本函数来使用梯度下降可用的超参数通常用于调整系数并指定特征空间映射的特征(我们将在后面讨论)。我们可以为内核 SVM 选择不同的内核)。超参数的确切定义可能是模糊的。套索回归、岭回归或弹性网回归与线性回归是不同的模型吗?或者它们只是带有用于参数正则化的超参数的线性回归?答案可能不是很重要。有趣的是,通过 sci-kit learn,逻辑回归直接具有这些参数(惩罚和 C)。模型SGD 分类器SGD 回归器非常值得考虑,因为所有这些参数都是集中的(损失函数、惩罚和 alpha、l1 比率)。我们可以更进一步:具有特定核的 SVM 模型可以被认为是不同的模型,或者它可以被认为是具有所选核作为超参数的一般 SVM)?在深度学习的情况下,我们可以再提到一些:层数、辍学率、激活函数等。就模型的大小而言,它通常非常小,因为只存储了几个系数。除了深度学习,最近的模型可以包含数万亿个系数。

监督机器学习算法的参数与超参数(图片由作者提供)

为了彻底理解模型训练过程,我创建了一系列文章(工作进展)来演示使用 Excel 的所有步骤。你可以在这里查看这些文章并支持我:https://ko-fi.com/s/ea8faebbb5

在 ko-fi 上支持我 —作者图片

3.2.模型实现

在创建模型时,我们关注的是培训过程,但了解模型将如何实现也很重要。为了更好地理解,我们可以想象您必须在 Excel 中实现它们。

  • 最近邻居:我们必须存储所有的数据集。对于一个观测值,我们可以计算所有观测值的距离,并通过按距离排序来寻找最近的邻居。
  • 决策树:我们要存储所有的规则,在 Excel 中通常很难手工做到,哪怕是一棵树,更别说是很多棵树的时候了。
  • 数学函数:除了大型深度学习模型,模型可以很容易地存储在 Excel 中:有系数要存储在单元格中,我们也可以编写函数,它会将新的观察值作为输入来计算预测。

3.3.回归和分类

当谈到监督学习时,在许多概述中,我们经常会看到两个子类:回归和分类。提醒一下,回归问题是当目标变量是连续的,而分类任务是当目标变量是分类的。

我没有选择将它们作为监督学习算法的两个子类别,因为一种算法可以为一种或另一种算法工作。让我们也提一下在训练过程中如何使用目标变量。

对于最近邻居算法,目标变量不用于训练过程,因为只有预测器用于寻找邻居。所以目标值的性质对训练过程没有影响。找到邻居时:如果目标变量是数值型的,则使用邻居的平均值进行预测;如果目标变量是分类变量(有两个或更多类别),则使用类别的比例进行预测。而比例可以认为是概率。对于二元分类或多类分类,没有区别。

对于基于决策树的模型,目标变量用于创建分裂或规则,使其在叶子中同质。模型的结构是一样的。为了进行预测,我们首先确定新的观察值在哪个叶子上。然后:对于回归,我们用叶子中观察值的平均值预测;为了分类,我们计算所有类别比例。对于二元分类或者多类分类,我们可以看到没有区别。

对于数学函数模型,目标变量用于成本函数中,该成本函数应最小化以找到每个预测器的系数。对于分类变量,使用一种热编码。由于数学函数只考虑输入数值(我们将在后面看到),也只输出数值,我们应该先验地说它们只对回归任务起作用。所以对于一个回归问题,没有问题。分类任务会发生什么?有三种主要的解决方案,并且它们可以从二元分类到多类分类进行不同的概括。

  • 通过在中间切割预测目标值的超平面:对于二元分类,目标值是 0 或 1,我们仍然可以用线性回归来拟合。然后,如果输出高于 0.5,则预测为 1,如果输出低于 0.5,则预测为 0。这对 SVM(支持向量机)也是一样的。有时,目标值被选择为-1 和 1,但它不会改变结果。然后最后一个方程叫做超平面,它把空间分成两部分。对于多类分类(让 m 是类的数量),这种方法不能很好地推广,因为我们试图将问题带回二进制分类。我们可以使用 one-vs-rest 方法,通过将一个类视为 1,而将所有其他类视为 0,来创建(m-1)个模型。我们还有一对一的方法,这将创建 m*(m-1)/2 个模型,因为我们试图为每对 m 个类创建一个模型。
  • Logistic/softmax 回归:解决二元分类任务的另一种方法是使用 GLM(广义线性模型)方法,通过使用链接函数将函数的输出值限制在 0 和 1 之间,具有单调平滑的过渡。一般来说,可以有不同的函数具有这种特性,我们可以提到逻辑函数( logit 的反函数)和 probit 的反函数。实际上,最常见的函数是逻辑函数,它在求导方面也有很好的数学特性。而且值得一提的是得到类的最终预测,我们也是中间切的。所以 Logistic 回归也是一个线性分类器。不同的是,输出的是一个可以直接使用的概率。对于用线性回归或 SVM 构建的超平面,我们不可能天生就有概率。一种方法是使用逻辑函数来获得概率。(一个有趣的讨论这里这里)。对于多类分类,使用 softmax 函数可以很好地完成概化。对于神经网络,最后一层通常是用于多类分类的 softmax 回归/分类。
  • GDA(高斯判别分析):在这个算法家族中,我们可以找到 LDA(线性判别分析)、QDA(二次判别分析)、朴素贝叶斯分类等。它们也被称为生成模型。vs 判别式模型(即使这些生成式模型中有关键字判别式!这里的是维基百科的文章)。并且它们仅用于先验的回归任务。

回归和分类(图片由作者提供)

4.特征变量处理

在我们考虑了如何在这些算法中使用目标变量之后,我们现在可以考虑特征变量。

4.1.数字变量和分类变量

  • 最近邻:在计算两次观测之间的距离时,只能考虑数值。不处理分类变量。人们可以想到 one-hot 编码,但它们意义不大。标签编码将是一种错误的方式。
  • 决策树:由于树是按照将观察结果连续分成两组的规则构建的,变量总是以某种方式分类。当然,数字变量是可以处理的,但是它们被转换成带有拆分的类别。值得一提的是,在 sci-kit learn 中只处理数字变量,因此它们通常是一位热编码的。
  • 数学函数:只能考虑数值。所以分类变量通常是一次性编码的。

4.2.数值变量缩放的效果

  • 最近邻:所有数值变量都以同样的方式考虑(除非为不同的变量分配权重),数值的改变(无论是缩放还是其他变换)都会改变计算的距离,因此结果会有所不同。
  • 决策树:数值变量将被(相继)切割成两部分,因此缩放将而不是改变模型的结果。
  • 数学函数:对于线性回归,缩放没有影响。你可以用数学方法证明。但对于其他型号,缩放会有影响(如有反例,请分享)。线性回归的正则化版本(Lasso、Ridge、Elastic net)考虑了系数的二次误差和(L1、L2 或两者)范数,因此系数将受到数值变量缩放的影响。

特征变量处理(图片由作者提供)

4.3.缺失值处理

  • 最近邻:由于只考虑数值变量,无法处理缺失值,可以使用各种插补方法。
  • 决策树:缺失值可以作为一个类别处理,不需要一些特殊的插补。
  • 数学函数:对于数值变量和最近邻变量,必须进行插补,并以有意义的方式对缺失值进行估计。例如,将它们设置为 0 并不是一个合适的解决方案,因为这会造成不连续和异常。例如,通常使用平均值估算法。对于分类变量,可以使用“最频繁类别”方法,但有时这可能不是最合适的方法。例如,如果我们能发现由于某种特殊原因而丢失的值,就会创建一个特殊的类别。因为它们是一次性编码的,所以不存在不连续的问题。

4.4.特征重要性

特征重要性可以是模型不可知的,这里我们只考虑算法如何能够直接给出一些关于特征如何被使用以及它们在预测过程中的重要性的见解。

  • 最近邻居:由于所有变量都以相同的方式用于距离计算,因此特征重要性的概念在这里并不相关。然而,值得注意的是,如果我们知道某些特性应该更重要(根据业务考虑),我们可以使用权重作为预测值。
  • 决策树:可以有不同的方法来估计特性的重要性。可以使用的指标有:一个变量在分割中使用的频率,分割的影响程度(根据基尼系数或熵值变化)。连续变量和分类变量之间是有区别的。由于连续变量被分成不同的类别来创建分割,因此它们可以被多次分割,从而有更多的机会被使用。对于一个分类变量,我们可以考虑它对每个分类或所有分类的重要性。
  • 数学函数:对于每个预测器,通常有一个系数(对于神经网络有更多)。它们可用于考虑特征的重要性。这并不意味着系数越高,特征重要性越高。我们还必须考虑特性的规模(本文解释了其解释的一些缺陷)。在传统的线性回归建模中,我们通常将 p 值(对系数进行零假设检验)作为系数的显著性。(我还没查:特征标准化时考虑 p 值和系数是否等价?)

缺失值和特征重要性(图片由作者提供)

5.模型增强以及与其他算法的关系

经过前面的分析,我们现在对三类算法中的三种基本算法的优缺点有了一些了解。由于缺点不同,改进模型的方式也不同。这就是我们可以把三个不同类别的不同算法联系起来的地方。

5.1.最近邻模型

我们可以通过以下方式改善最近邻:

  • 一个主要的缺点是它不是一个模型,因为它使用所有的数据集。一个极端的简化是计算每个类的质心。然后使用质心而不是所有的数据点来计算距离。考虑这种方法很有趣,因为它可以与 k-means(是的,它是一种无监督的学习算法)和 GDA 相关。
  • 另一种增强最近邻的方法是改进距离的概念。通常使用欧氏距离,我们可以使用它的广义版本( Minkowski distance)不同的幂参数。但是我们也可以使用其他类型的数学距离:例如, Mahalanobis 距离将引导它到达 LDA。想法是考虑不同距离的不同数据点可以被不同地考虑。对于最近邻,数据点要么是“邻居”,要么是“不是邻居”,而对于 GDA 或逻辑回归,距离是以数据点的参数方式建模的。

还有什么?最近邻和决策树有关系吗?我们可以说决策树的叶子包含邻居。没有用距离,而是用另一种方式找邻居,有规则。因此,您不必存储真实的邻居来进行预测,您只需存储帮助您找到邻居的规则。一旦发现它们,你就不需要它们了,因为只需要预测,而且它可以被存储。

最近邻模型增强以及与其他算法的关系(图片由作者提供)

5.2.基于决策树的模型

对于决策树,改进模型的方法主要是聚集树。聚合/加法是一个数学函数。所以我们可以把 Boosting 和 GAM(广义可加模型)联系起来。这是一个非常强大的想法,因为为了优化梯度增强机器中的树聚集,使用了梯度下降。(这里是一个有趣的讨论)针对不同的任务,各种优化也考虑了损失函数。

值得注意的是,Boosting 可以用于各种基础算法,但是对于线性回归,添加线性回归仍然会是线性的(这里这里我们可以找到一些有趣的讨论)。然而决策树的一个有趣的特征是它在默认情况下是非线性的(这里的)。

另一个想法是为树叶中的观察值引入一些函数。例如,默认情况下,平均值是为回归任务计算的。现在的想法是引入更复杂的关系,例如,线性关系。我们可以在这里找到一些参考资料。

该方法可以以相反的方式用于线性回归,而不是为所有预测值范围创建单一的线性关系,可以考虑将特征变量分成不同的区域并创建线性回归。

基于决策树的模型增强(图片来自作者)

5.3.线性模型增强

下面举几个线性回归失败的案例,以及如何改进:

  • 当使用线性回归做分类时,它对异常值非常敏感,这就是为什么逻辑回归或 SVM 更适合。逻辑回归使异常值(远离两类边界的异常值)造成的误差最小化。SVM 完全忽略了异常值。
  • 当目标变量具有某些特定属性:只有正值、计数数据或二进制值时,如果我们能通过考虑具有 GLM 的链接函数或损失函数来反映这些属性,那就更好了。
  • 线性回归的一个主要缺点是它是线性的,而现实世界充满了非线性

要创建非线性模型,有不同的方法:

  • 特征工程:有人会说特征工程不是算法,它应该在预处理阶段完成。我在这里提到它有两个原因:1)特征工程和预测建模之间的界限可能是模糊的。尤其是深度学习模型。关于线性回归是否可以过度拟合,还有一个有趣的考虑。我们通常以多项式回归为例来说明线性回归的过拟合。有些人可能会认为多项式回归不是简单的线性回归,而是另一种更复杂的回归算法,其超参数是多项式的次数。但是我也可以认为多项式特征可以存在于现实世界中。然后应用线性回归(这里是一个有趣的讨论)。2)特征工程技术可以与其他算法相关联。例如,宁滨正在使用决策树(它们是基于专家的)将连续变量分类。特征交互类似于特征空间映射。日志转换就像应用一个链接函数。
  • 局部回归:我们已经讨论过它与最近邻模型的关系。
  • 特征空间映射:想法是将非线性数据投影到一个特征空间,在那里数据变成线性。一个简单的例子是多项式回归,即使它通常不以这种方式呈现。这里有一个的课堂笔记对统一方法很有帮助。有了正则化,就变成了内核脊。由于其计算性能,它主要用于 SVM。值得注意的是特征空间映射和内核技巧之间的区别。(这里是一个有趣的讨论)。
  • 函数组合 : GAM 使用加法方法组合函数,而神经网络在全局线性函数(或 softmax 回归)中使用多层激活函数。中间层包括将非线性数据转换到最终空间,在最终空间中,数据可以以线性方式建模。你可以在下面找到我写的一篇文章,用一个神经网络来可视化数据的转换。

线性模型增强(图片由作者提供)

特别是对于分类算法,我们可以提到更多的特异性。

带有数学函数的分类算法(图片由作者提供)

对于线性回归LDA 的关系,这里有个有趣的讨论。

对于 LDAlogistic 回归,我写了一篇文章来说明它们其实非常相似。它们是同一个模型(数学函数相同,只是变量前的系数不同)。

要了解更多的细节,你可以写我写的这篇关于线性分类器到非线性分类器的转换的文章,附带一些可视化。

6.结论

当问所有机器学习算法一个一般性问题时,我认为重要的是要记住,答案可能因算法而异。希望这个概述能对彻底解答有用。如果你有任何问题,请让我知道,每个讨论都有助于我更好地理解这些算法。

有些人可能会注意到,在第一个概述中提到了一些我在文章中没有提到的算法:ARIMA、RNN、CNN、注意力机制等。我正在写关于无监督学习算法计算机视觉技术NLP 技术深度学习算法的类似文章。

应用、映射、应用映射、iterrows 和 itertuples 概述

原文:https://towardsdatascience.com/overview-on-apply-map-applymap-iterrows-itertuples-df752a11f451

附有示例的实用指南

数据清理和特征工程在处理数据时是必不可少的。我最近回顾了一些过去的个人项目,并对它们进行了重构或继续构建。其中之一是 iaito 订单细节的可视化。合气道是模仿武士刀来学习合气道的。iaito 的制造属于高混合小批量制造类别,因为在我们的情况下,每个 iaito 都是为所有者定制的(即按订单生产)。

[## 使用 Matplotlib 和 Seaborn 实现数据可视化

towardsdatascience.com](/data-visualization-using-matplotlib-seaborn-97f788f18084)

我在 2012 年开始构建数据集,同时帮助 iaidoka 成员翻译 iaito 订单细节。每个记录包含每个所有者的 iaito 零件规格。出于隐私考虑,诸如所有者的全名和地址等敏感信息永远不会在数据集中捕获。

在本文中,使用数据集的更新版本,我们将浏览几个常见的数据操作用例以及可能不太明显的注意事项。涵盖的操作概述如下:

  • 应用
  • 地图
  • 应用地图
  • for 循环
  • iterrows
  • 迭代元组
  • …向量化…
  • 词典

我们将进行通常的库导入,检查数据框并进行一些基本的数据清理。

# import libraries
import pandas as pd
import numpy as np
import re
pd.set_option('display.max_columns', None)# load the dataset
df = pd.read_excel('project_nanato2022_Part1.xlsx')
print(f'df rows: {df.shape[0]}, df columns: {df.shape[1]}')
df.head(1)

导入的数据框|按作者分类的影像

# Check for null or missing values
df.isnull().sum()[df.isnull().sum()>0]

有 44 个空值的列|作者图片

包含空值的列…?没什么大不了的。检查数据总是好的做法。看来unnamed column号是多余的,可以安全丢弃。尽早丢弃或输入空值(如果可以的话)有助于简化数据清理过程&减少内存使用。

在删除未命名的列|按作者排序的图像之前

删除未命名的列|作者的图像后

然后,列标题被重命名,详细信息可在笔记本中找到。

用例#1

我们将从一个简单的用例开始——计算所有者姓名的字符长度。实际上,这可以推导出广告文本的长度等。本质上,这是从现有的数据列派生出一个新的数据列。我们将通过这个用例的applymapapplymapfor循环。我们还将对每个实现进行计时,以便进行比较。使用timeit,循环次数保持在 100 次。

我们可以从文档中更好地了解操作。在apply的情况下,它描述了一个函数沿着数据帧的一个轴的应用,其中一系列对象被传递给该函数。map用另一个值替换系列值。mapapply对 DataFrame 的元素应用一个函数-输入需要的数据帧;在我们浏览这些例子的时候,需要记住一些事情。

# apply
%timeit -n100 df['name_L1'] = df['owner'].apply(lambda x: len(x))

按作者应用计算时间|图像

# map
%timeit -n100 df['name_L2'] = df['owner'].map(lambda x: len(x))

地图计算时间|作者图片

# applymap - applies to elementwise, so pass in a dataframe
%timeit -n100 df['name_L3'] = df[['owner']].applymap(lambda x: len(x))

应用地图计算时间|作者图片

%%timeit -n100
# for loop
t_name = []
for ele in df['owner']:
    t_name.append(len(ele))
df['name_L4'] = t_name

for 循环计算时间|作者图片

根据使用情况,里程和最佳操作会有所不同。对于上面的用例,无论是for循环还是map都足够了,尽管前者更加冗长。我们也许可以摆脱apply,但是在处理更大的数据集(例如,数百万条记录)时,它无法伸缩。

用例 2

对于下一个用例,weight 列由 iaito 的预期重量范围组成。我们使用for循环和apply将值提取到两个新的单独的列中。

%%timeit -n100
# for loops and splitting of strings
w_lower1, w_upper1 = [], []
for weight in df['weight_(g)']:
    w_lower1.append(weight.split('-')[0])
    w_upper1.append(weight.split('-')[1])
df['w_lower1(g)'] = w_lower1
df['w_upper1(g)'] = w_upper1

for 循环计算时间|作者图片

# helper function to split weight column
def splitter(row):
    return [row.str.split('-')[0][0],  row.str.split('-')[0][1],]%%timeit -n100
# .apply result_type='expand' creates a dataframe
frame = df[['weight_(g)']].apply(splitter, axis=1, result_type='expand')
frame.rename(columns={0:'w_lower2(g)',
                      1:'w_upper2(g)',
                     },inplace=True)
df[frame.columns] = frame

按作者应用计算时间|图像

关于apply实施,实施轴必须为 1,才能使用result_type。在上面的例子中,用expand返回一个新的数据帧。

用例#3

对于第三个也是最后一个例子,我们将创建一个新的 name 列,标题后面跟着所有者的姓名。我们可以从性别一栏推断出标题。我们将经历这些操作:

  • for循环
  • iterrows
  • itertuples
  • 列表理解+ apply
  • …向量化…
  • 词典
%%timeit -n100
# for loop
res = []
for i in range(len(df['gender'])):    
    if df['gender'][i] == 'M':
        res.append('Mr. ' + df['owner'][i])
    else:
        res.append('Ms. ' + df['owner'][i])
df['name1'] = res

for 循环计算时间|作者图片

iterrows顾名思义,遍历数据框行。它是作为一个索引序列对来实现的。让我们休息一下,然后再仔细检查一下。

布莱恩·麦高恩在 Unsplash 拍摄的照片

# cap at the 1st data record
for ele in df[['owner', 'gender']].iloc[:1].iterrows():
    print(ele,'\n')
    print(f"index\n{ele[0]}",'\n')
    print(f"series\n{ele[1]}",'\n')

iterrows 对象|作者图片

列标题构成了系列的索引。对 iterrows 如何工作有了更好的理解,我们可以这样继续:

%%timeit -n100
# iterrows implementation
res = []
for ele in df[['owner', 'gender']].iterrows():
    if ele[1][1] == 'M':
        res.append('Mr. ' + ele[1][0])
    else:
        res.append('Ms. ' + ele[1][0])
df['name2'] = res

iterrows 按作者计算时间|图像

itertuples以命名元组的形式遍历数据帧。关闭默认的index会将第一列的值移入索引。它比iterrows还要快。让我们仔细看看。

# cap at 1st record, default index on
for ele in df[['owner', 'gender']].iloc[:1].itertuples():
    print(f"index\n{ele[0]}")
    print(f"tuples\n{ele[1], ele[2]}")

itertuples #1 |作者图片

# cap at first record, index off
for ele in df[['owner', 'gender']].iloc[:1].itertuples(index=False):
    print(f"index\n{ele[0]}")
    print(f"tuples\n{ele[1]}")

itertuples #2 |作者图片

综上所述,itertuples 实现类似,但速度更快,如下图所示。

%%timeit -n100
res = []
# itertuples implementation
for ele in df[['owner', 'gender']].itertuples(index=False):
    if ele[1] == 'M':
        res.append('Mr. ' + ele[0])
    else:
        res.append('Ms. ' + ele[0])
df['name3'] = res

itertuples 按作者计算时间|图像

列表理解和应用的结合也是一种备选解决方案,只是没有那么有效。

%%timeit -n100
# combination of list comprehension and apply
title_ = ['Mr. ' if x == 'M' else 'Ms. ' for x in df['gender'] ]
df['t'] = title_
def name_(row):
    return row['t'] + row['owner']
df['name4'] = df.apply(name_,axis=1)

按作者列出理解和应用计算时间|图片

接下来,向量指令。在本节中,我们将探索通过代码矢量化进行优化。所以比较的是标量(非矢量)指令和矢量指令。

%%timeit -n100
# vectorization
arr1 = df['owner'].array
arr2 = df['gender'].array
arr3 = []
for i in range(len(arr1)):
    if arr2[i] == 'M':
        arr3.append('Mr. ' + arr1[i])
    else:
        arr3.append('Ms. ' + arr1[i])
df['name5'] = arr3

向量指令计算时间|作者图片

最后,可以将数据框转换成字典。我们还可以将数据框转换成字典,然后对其进行处理。但是,对于字典来说,一定要注意人们可能希望如何转换键-值对。如果忽略这一点,意外的结果可能会令人惊讶。我们将通过两个例子:1。使用list作为orient参数来完成工作。默认dict输入。

%%timeit -n100
# create dictionary from subset of data
df_dict = df.to_dict('list')name_list = []
for idx in range(len(df_dict['gender'])):
    if  df_dict['gender'][idx] == 'M':
        name_list.append( 'Mr. ' + str(df_dict['owner'][idx]) )
    else:
        name_list.append( 'Ms. ' + str(df_dict['owner'][idx]) )
df_dict['name6'] = name_list# convert dictionary back to dataframe
df_new = pd.DataFrame.from_dict(df_dict)

计算字典列表操作的时间

%%timeit -n100
# create dictionary from data
df_dict2 = df.to_dict()
df_dict2['name7'] = {}
for idx in range(len(df_dict2['gender'])):
    if  df_dict2['gender'][idx] == 'M':
        df_dict2['name7'][idx] =  'Mr. ' + str(df_dict['owner'][idx]) 
    else:
        df_dict2['name7'][idx] =  'Ms. ' + str(df_dict['owner'][idx])# convert dictionary back to dataframe
# separated to deconflict with timeit 
df_new2 = pd.DataFrame.from_dict(df_dict2)

计算字典操作的时间

结论

恭喜你!你坚持到了文章的结尾。我们已经讨论了applymapapplymapfor循环、iterrowsitertuples、向量化指令、字典。最佳操作和里程取决于用例,但总的来说,我们可以看到一些操作比其他操作更有效,这肯定是生产级代码的考虑因素之一。

数据集和笔记本可以分别在这里这里访问,以供参考和个人使用。如果您想将数据集用于您自己的项目,请指明适当的认证。

暂时就这样了。感谢阅读。

2022 年 2 月 27 日更新:

  1. 添加了关于数据集所有权和权限的说明。
  2. 添加字典操作

假设检验的 p 值法

原文:https://towardsdatascience.com/p-value-method-for-hypothesis-testing-c967c0f78d74

P 值法是一种常用于假设检验以验证假设的方法。在本文中,我将用一个解决的例子来解释这种方法。

克里斯·利维拉尼在 Unsplash 上的照片

为什么要进行假设检验

通常,我们获取样本数据集进行工作,并执行数据分析和可视化,找到见解。通过这种分析,我们可以对整个人口做出推断。群体是整个数据集,而样本数据集是来自群体数据集的组块。与总体相似,这些样本数据集也有一些相关的均值和标准差。

这些样本的平均值通常与总体平均值相差不同的范围。这些范围可以从非常接近到非常远离总体平均值。为了做出正确的推断,我们的样本数据集需要类似于总体数据集的属性。因此,这些样本和样本均值通过各种假设检验方法进行验证,如 P 值法、临界值法、T 检验、ANOVA 检验等。

例如,考虑一家销售电子设备的商店。不久前,他们推出了一款新的笔记本电脑,并举行了一场营销活动来推广该产品。这场运动持续了 100 天。他们声称,在活动期间,所有分支机构平均每天售出 500 台笔记本电脑。该索赔值(500)是总体平均值,表示在 100 天的活动中每天售出的笔记本电脑数量。现在,我们取出多个样本,每个样本有 20 天的数据,并检查每个样本的平均笔记本电脑销量。我们知道每个样本的平均值可能与其他样本的平均值不同。这些样本平均值可以不同于总体平均值(500)。如果样本均值接近 500,比如我们得到的笔记本电脑销售的样本均值为 490 或 498 或 505,我们更有可能相信商店的说法。但如果样本均值是极值,比如 200 或 150 或 710,我们就不太可能相信这种说法。在这种情况下,p 值方法有助于在统计上验证假设。

假设检验中的 P 值法是什么? P 值法用于假设检验,检查给定零假设的显著性。然后,基于指定的显著性水平或阈值来决定拒绝或支持它。

用这种方法计算 P 值,这是一种检验统计。这个统计可以给我们找到一个值(样本均值)的概率,这个值和总体均值一样远。

P 值中的 P 代表概率

基于该概率和显著性水平,我们拒绝或未能拒绝零假设。

一般来说,p 值越低,拒绝零假设的机会就越大,反之亦然。

此外,我们利用 Z 表来执行这个过程。

如何通过 p 值法进行假设检验?

为了执行这种方法,我们首先制定零假设和替代假设。

然后,我们有以下给定的输入:

Population Mean (X)
Population Standard Deviation (SD)
Alpha or the Significance Level
Sample Size (n)
Sample Mean (x)

α或显著性水平是当零假设为真时,在假设检验中出错的概率。它还指定了分布中零假设的临界区域或拒绝区域。如果样本均值位于临界区域,我们拒绝零假设,接受替代假设。

通常使用的 alpha 是 0.05 (5 %),但是它可以根据业务需求而变化。如果检验是双尾检验,我们将α除以 2,以平均分配下限和上限的显著性水平。在单尾检验的情况下,我们保持α不变。

你可以在这里阅读更多关于这两种考试的内容。

然后,我们计算样本的标准差,即总体标准差除以样本大小(n)的平方根。

计算标准误差的公式

找到标准误差后,我们取一个样本和该样本的平均值,然后找到与该平均值相关的 Z 得分。

Z = (Sample Mean - Population Mean)/ (Standard Error)

计算 Z 分数的公式

现在,我们找到与我们计算的 Z 得分相关联的 p 值。
然后我们参考 Z 表,找出计算出的 Z 得分的概率。然后,为了找到 p 值,我们从 1 中减去这个概率。

P-Value = 1 - Probability(Z-score)

最后,我们检查计算的 p 值是否大于显著性水平。

如果 P 值>显著性水平,那么我们无法拒绝零假设
否则,如果 P 值<显著性水平,我们拒绝零假设

让我们通过一个例子来更好地理解这个方法

让我们以前面的电子商店为例。不久前,商店推出了一款新的笔记本电脑。他们已经进行了 100 天的营销活动来推广该产品。该商店声称,在 100 天的营销活动中,平均每天售出 500 台笔记本电脑。这意味着,在活动期间,所有商店平均每天售出 500 台笔记本电脑。所以,人口平均数是 500。

因此,无效假设和替代假设是:

**Null Hypothesis (H0)** : The Average laptop units sold during the campaign were 500 (**Average Laptop units sold = 500**)**Alternate Hypothesis (H1)** : The Average laptop units sold during the campaign were not 500 (**Average Laptop units sold != 500**)

所以,看看零假设和备择假设,我们注意到这是一个双尾检验。

现在,我们从整个活动数据集中抽取了大约 5 个样本数据集。每个样本都有 15 天的数据(而且,这些样本中的数据可能会重叠)。

我们观察了每个样本中售出的笔记本电脑数量的平均值。我们获得 5 个样本的平均值如下:

Sample_1 : 535.5
Sample_2 : 495.2
Sample_3 : 510.5
Sample_4 : 497.7
Sample_5 : 504.3

现在,让我们使用 p 值方法验证样本 1。我们将采用 0.05 (5%)的阈值或显著性水平来验证这一假设。由于这是一个双尾检验,我们将显著性水平除以 2,以使其平均分布在上下临界点上。

我们假设总体的标准差是 45。但是总体的标准差通常是已知的。

我们知道,如果任何样本的计算 p 值高于显著性水平,我们无法拒绝零假设。否则,我们拒绝零假设。

在这里,我们正在验证第一个样品(样品 _1 )。

因此,我们的给定输入如下:

Population Mean               : 500
Population Standard Deviation : 45
Sample Mean (**Sample_1**)        : 535.5
Sample Size (**Sample_1**)        : 15
Alpha (Significance Level)    : 0.05 (2-tailed Test) 

我们现在计算标准误差

计算标准误差

第二步是计算 Z 值

计算 Z 分数

我们得到的 Z 值为 3.05
现在,我们将参考 Z 表来查找所获得的 Z 得分的概率。

Z 工作台

我们得到的 Z 值是 3.05。现在,在 Z 表中,我们需要垂直查找第一列中的值(+3)。之后,我们需要横向寻找下一个小数值(0.05)。因此,我们通过垂直方向上的+3 和水平方向上的 0.05 找到 Z 得分的概率。
Z 得分的概率为 0.99886

现在,为了计算 p 值:

P-value = 1 - Prob(Z-score)
P-value = 1 - 0.99886
P-value = 0.001

由于这是一个双尾检验,我们将 p 值乘以 2

P-value = 0.001 * 2
P-value = 0.002

我们得到了 P 值= 0.002

最后,我们现在将它与显著性水平进行比较

P-value = 0.002
Alpha (Significance Level) = 0.05

我们注意到,
P 值<显著性水平

这意味着,在这个问题陈述和假设中,找到一个样本均值 535.5 有 0.002 或 0.2%的概率,这个概率极小于显著性水平(0.05 或 5%),被认为离总体均值(500)太远。

因此,我们拒绝零假设。

这也意味着我们的样本平均值 535.5 位于接受区域之外,我们无法预计商店索赔的价值会有这样的差异(平均每天售出 500 台笔记本电脑)。

因此,样本平均值 535.5 不是该假设的可接受平均值,因此需要验证和改变现状(零假设)。

类似地,对所有样本及其样本均值重复相同的过程,计算它们的 p 值并与显著性水平进行比较。

通过这种方式,使用 P 值方法验证假设。

结论

在本文中,我通过解决一个实际的例子,解释了如何使用一种最常见的方法 P 值法来进行假设检验。
我将非常乐意在评论中听到你对这篇文章的评论和评论。另外,请随时在 LinkedIn 上给我发消息。

p-价值观:“科学种族主义”的遗产

原文:https://towardsdatascience.com/p-values-a-legacy-of-scientific-racism-d906f6349fc7

统计学和人工智能的遗产

深入了解 p-values 的不为人知的历史及其遗产

免责声明:我会给某些词加上引号,比如种族、种族测量等等。因为这些术语不仅带有历史歧视,而且“种族”也是不科学的。一个“种族”的人是一个 非科学的 名词。此外,读者应该对 p 值、假设检验和贝叶斯定理有一点熟悉。

照片由 Edge2Edge MediaUnsplash 上拍摄

很容易认为科学是客观的。毕竟,科学是研究和记录自然界发生的事情,对吗?自然世界“超越”了人类的领域,超越了我们的想象和思维,就像科学课会教的那样。

但是我们不应该忘记是谁在进行科学研究:当然,显微镜会帮助你更近距离地观察更小的物体和有机体,是的,使用 X 射线望远镜可以显示星系的某些特征,这些特征在可见光下可能不明显——但是最终,我们人类在进行观察。并且我们正在做数据的解释

在我们深入研究 p 值的历史,以及该值可能的替代解决方案(以及它们的缺点)之前,让我们简要回顾一下它的定义。

统计学中的定义是:假设检验的 p 值是你的检验统计量取观察值或更极端值的概率,假设你的零假设为真。在数学符号中,这将是

垂直线表示“给定 H0”,这意味着“假设零假设为真”。值 x 可能是,比如说“z 得分”,或者“t 统计”,或者“卡方统计”——如果这些是你统计学课上熟悉的词的话。

一个最常见的错误是假设 p 值告诉你给定数据证据的零假设的概率。这是 错了 。这种情况的数学符号是:

这两个公式是不同的 但是很容易混淆。通常,在做了实验后,科学家们想要第二个公式,但经常被第一个公式卡住。稍后,我们将看到如何从一个到另一个,以及如何连接到测量实验统计结果的替代方法。

维基百科对这种常见错误有一个很好的解释:

图片来源:https://en . Wikipedia . org/wiki/P-value #/media/File:P-value _ in _ statistical _ significance _ testing . SVG

统计学家对 p 值有一种又爱又恨的关系,最近他们一直批评 p 值是统计测试中“重要性”的衡量标准,并警告科学家注意常见的陷阱。不相信我?阅读美国国立卫生研究院【2015 年的这篇文章。我们稍后会重温这种又恨又爱的关系,所以把它放在你的脑海深处。

什么是科学种族主义?

在我讨论 p 值背后的历史之前,我需要简要提及“科学种族主义”一词的背景,因为它不仅包含了政府支持的多年种族政策,而且是与 p 值和统计学中的显著性测试交织在一起的历史之一。“科学的种族主义”,或者更准确地说伪科学的种族主义(因为种族主义不是科学的),是欧洲殖民政府——以及他们雇佣来进行政府调查和数据收集的统计学家通过使用统计测量来证明其种族主义政策的一种方式,这种方式通常是以一种极其偏见和不正确的方式进行的。例如,如果你听说过殖民时代欧洲伪科学家使用的臭名昭著的“头骨测量”来试图证明头骨大小和智力之间的(虚假)相关性,他们这样做是为了给诸如“非洲人、美洲原住民和亚洲人因为体型较小而不如欧洲人聪明”的说法提供科学支持当然,这些说法是完全不科学和毫无根据的——但是使用科学术语和测量作为支持是一种安抚他们负罪感的方法。

现在,我已经简要介绍了什么是科学种族主义,我们将在后面与显著性测试和 p 值相关的文章中看到更具体的例子。但是首先,我们来了解一下 p 值的历史和背景。

我将引用数学家奥布里·克莱顿的精彩文章,《优生学如何塑造统计学》鹦鹉螺,关于统计学的历史。

P 值的历史

p 值不是突然出现的。它们是对 18 世纪和 19 世纪数学家的回应,他们试图理解概率和随机性的理论概念,以及如何将其与计算物理实验的统计结果联系起来,如掷骰子、掷硬币或更复杂的物理实验。

如果你上过统计学课(或者是统计学家,或者大学主修),我肯定你听说过高尔顿、皮尔逊和费希尔这样的名字。我们会提醒自己,他们是人(有时在课堂上听了这么多次[插入姓名]的公式后,我很难记住这一点!).尽管这些人支持统计学是从数据中得出结论的可靠、客观的方法,甚至在他们自己的工作中,这些统计学家“揭示了客观性的神话一直是如何思考的”,正如 Clayton 所写。

高尔顿来自维多利亚时代的一个上流社会家庭,他经常发表关于如何通过人类选择性繁殖来“精炼”人类智力的观点——通过繁殖最富有的,“有学问的”男人,而不是英国社会的其他阶层。很正常…我知道。他还生活在 19 世纪 50 年代,当时欧洲在亚洲和非洲的殖民主义不断扩张,而美国仍深陷于种族主义奴隶制和种族隔离之中。因此,高尔顿将他的“繁殖”思想应用于不同“种族”(种族主义科学家使用这个术语)的人也就不足为奇了。

但是这和种族主义有什么关系呢?嗯——当殖民时期的欧洲“科学家”开始测量世界上“种族”的身高、体重和外貌时,他们依靠像高尔顿这样的欧洲统计学家根据数据得出结论。他和他同时代的人相信每个“种族”的人的测量值会遵循一个钟形曲线——正态分布。

图片来源:https://en . Wikipedia . org/wiki/Normal _ Distribution #/media/File:Normal _ Distribution _ pdf . SVG

如果这些“种族测量”遵循正态分布——嗯,因为每个正态曲线都有特定的均值和标准差——它“遵循”每个“种族”的人都有“平均长相”。如果这种逻辑听起来已经让你毛骨悚然——那是因为它确实如此——坦率地说,这些统计学家如何说服自己他们的研究完全是“科学的”,这令人震惊

Raveena,那么 p 值和这个有什么关系呢?!

后来,到了 19 世纪,科学家们研究了各种各样的(非人类)生物,注意到有相当多的“不寻常的测量”,正如 Clayton 所指出的,19 世纪 70 年代的一位统计学家——卡尔·皮尔逊——是的,是心爱的皮尔逊相关性的统计学家——想知道如何确定数据点是否呈正态分布。理论上,如果你能搞清楚这一点,这将告诉你一船的信息,特别是在检测科学家们所渴望的异常值(“不寻常”)数据方面。

正如克莱顿所指出的,在皮尔森之前,统计学家只是简单地画出结果的直方图,然后基本上玩点连接(或连接条??)来看看它是否像一条钟形曲线。但是皮尔森在他的方法中加入了更多的数学知识,并创造了一个 3 步流程:

  1. 假设数据符合您最初设想的某种类型的分布。这就是我在文章开头提到的“零假设”。也写下你的“另类”假设。
  2. 将数据与您从零假设假设的数据分布中预期的数据进行比较,并计算所谓的皮尔逊“卡方检验统计量”
  3. 计算该检验统计量的 p 值,并确定结果是否“显著”。并评估这是否提供了支持替代假说的证据。

克莱顿指出,皮尔森实际上并不认为统计学意义一定意味着“值得科学发现”——他只是用这个词来表示,“哦,数据表明了一个不同的假设。"

所以在这里,我们可以看到“异常值”,或“不寻常的观察值”是不太可能被检验统计量标记出来的。而 p 值就是那个区域的总面积(概率)。

用统计数据为种族主义辩护的大罪

但是皮尔森和他同时代的统计学家犯的主要错误不是像克莱顿指出的那样将这些测试应用于螃蟹——而是应用于人类。人类在我们的生活经历和文化中差异太大,但这并没有阻止皮尔森试图将他的统计方法应用于对不同“种族”的人类进行的“生物学测量”,正如他所认为的那样。20 世纪 10 年代初,皮尔森对英国的 4000 对兄弟姐妹进行了一项统计研究,并“确定”眼睛颜色和其他身体部位的统计测量与他们的性格有“强烈的相关性”——根据 p 值显著性测试——如“自信和内省”(克莱顿)。根据今天的科学标准,我们会立即驳回这种说法,但仅仅十年后,当犹太难民逃离东欧逃到英国时,皮尔逊将相同的有缺陷的 p 值相关性分析应用于犹太儿童——,他从他的显著性测试中得出结论,他们是“劣等血统”,他们的“智力几乎与环境因素和歧视无关”(Clayton)如果你认为你会因为这个判断而嘲笑他——你并不孤单——但是不要忘记,Pearson 相关性仍然存在于 Python 和 R 库中。很难将这些统计学家用数学支持优生学判断的事实,与他们对该领域非常有效的令人印象深刻的贡献分开。

像皮尔逊、高尔顿这样的统计学家,以及后来像达文波特这样的教授,都相信优生学、种族主义,并且会用统计学上的论据来支持关于“种族间混合的危险”、“种族内繁殖以保存智力”以及各种其他论据的可怕理论(克莱顿)。皮尔逊甚至参与了美国大学内外的“优生学系”和社团——这些组织坚决反对美国白人和有色人种(亚洲人、非洲人、土著人等)的种族混合

现在是什么情况?

快进 100 年后——2019 年,近千名统计学家和科学家呼吁对显著性测试进行深刻的批判,他们要求改变的呼吁得到了美国统计协会的回应。事实上,统计学家 Ron Wasserstein 在同年的美国统计学教学会议上做了一个令人惊讶的演讲,解释了为什么 p 值,尤其是 p 值为 0.05 的低显著性标准,应该重新评估。

他们有效的论点指出,基于两个假设(看起来像是一个任意的数字)做出显著性决定不是一项有意义的统计任务,如果你的数据中有足够多的变量,那么“伪相关性”——正如他们所称的那样——可能经常发生,并且仅仅由于概率的乘法和互补性质,按照“p < 0.01” criteria will happen — even if that measurement is 的标准,至少一个“显著性测量”的机会对观察者来说是没有意义的。

因此,如果 p 值和所谓的“经典假设检验”需要批判和回顾,有哪些替代方案,它们是“最佳”选择吗?

可能的替代方案

因此,如果 p 值和显著性测试有缺陷,需要深入的批判性分析,有什么替代方法呢?它们是好的替代品吗?他们是否访问了与 p 值意义类似的模糊且有风险的领域。嗯,答案是,一如既往:这很复杂,但有一些途径。

统计数据的先验知识

在文章的开头,我提到人们很容易认为统计和科学数据测量是客观的,但事实并非如此——关键是数据本身不会说话——它必须在更大的背景下解读才能得出实际的结论。

例如:假设我在人行道上捡到一枚硬币,并做了一个实验:我将它抛 10 次,结果是 9 个头,1 条尾巴。现在,假设 A,Jason,以前从未见过硬币——也许他们是来自 2100 年的时间旅行者,那里没有硬币存在,他们的社会没有硬币的历史记忆,他们只使用电子支付——跟我来!杰森会如何解读这些数据?好吧,数据表明,既然 10 次翻转中有 9 次是正面,那么正面的概率“应该”是 9/10。至少,证据是与 p = 9/10 的假设一致。因为杰森从未见过硬币,所以他相信证据是有道理的。

人 B,Verity——来自 2022 年,他肯定见过硬币并持有过硬币,对硬币有一定的了解。数据再次表明,因为 10 次翻转中有 9 次是正面,所以证据与 p = 9/10 的假设一致。但是,对维蒂来说,这里有一个陷阱:从维蒂的经验(和社会)来看,在人行道上随机发现的硬币通常是对半公平的,所以她有关于硬币如何工作的先验背景。她会在他们的头脑中非常重视“公平硬币”(p = 1/2)假设,并得出结论,在他们的假设背景下,9 个头的数据是一个统计上的侥幸。

(顺便说一句,对不起,杰森——我知道你来自一个比我和 Verity 科技发达得多的时代,但是……这一次,我掷了一枚公平的硬币。缺乏关于硬币的上下文歪曲了你的结论!)

如果我将硬币抛 100 次,得到 90 个正面,那么这可能足以让 Verity 改变她对硬币公平的假设,换句话说,她认为硬币有偏见的几率会增加。麻省理工学院的认知科学家 Joshua Tenenbaum 在 Youtube 上有一个很棒的视频详细介绍了这个实验,大约在 37:20 的时候。

解读数据得出结论

注意之前我提到 Verity 的时候——她在她假设的不同背景下解释了同样的数据。“语境”这个词很重要。事实证明,“贝叶斯统计”领域非常关注假设的背景——它称之为“先验背景”,或“先验假设”——杰森和维里蒂做出的先验假设极大地扭曲了他们对相同数据做出的结论。事实证明,贝叶斯主义者正试图为通常的显著性检验提供替代方案。

其中一个选择叫做“贝叶斯因子”。在某种意义上,贝叶斯因子有助于比较在零假设下的数据是否比另一个更有可能。它基本上告诉我们有多少数据(证据)支持零的选择,这是非常有用的。数学上是这样写的:

图片鸣谢:作者。等式 1 是贝叶斯因子,写为“K”。情商。2 是贝叶斯推断,写成赔率。

如果你曾经见过下注者把他们的赌博赔率写为“2 比 1”,或者“这样赢的几率是千分之一”,那就是我们在这里使用的相同类型的赔率。“二比一”和“千分之一”的赔率将分别写成 2:1 和 1:1000。

斯坦福大学医学教授 Steven Goodman,在这里有一篇很棒的文章比较了贝叶斯因子和 P 值,并指出 200 左右的贝叶斯因子——这意味着替代假设为真的可能性比空值高 200 倍——通常具有 0.001 左右的“相应”P 值,这表明“经典显著性”可能需要更强的 P 值条件。德克萨斯大学奥斯汀分校的统计学家 Brendan Kline 在他的研究论文中指出,在大数据的背景下,0.1 左右的贝叶斯因子表示反对零假设的“强有力”证据,对于这些类型的数据集的假设检验,对应的“显著性”p 值应小于 0.0025(Kline)。回到 2018 年,Springer Nature Psychiatry的这篇文章提到,在假设检验方面,贝叶斯因素的优势是补充 p 值和效应大小。然而,贝叶斯因子的强度确实取决于“先验分布的宽度,这是由研究人员设定的——所以,如果你在寻找一些“客观”的衡量标准,你就不走运了。最后,贝叶斯因子本身并不能决定你,研究者,应该选择哪一个假设——在看到任何数据之前,你必须将它与完全由研究者决定的先前优势比结合起来。

最后的想法

我希望通过阅读这篇文章,你已经了解了 p 值的历史,p 值和经典假设检验的有问题的应用,这揭示了它的局限性,以及研究人员正在采取的补救这些挑战的尝试。不希望读者看完这篇文章后觉得 p 值没用。p 值在 R 等统计软件或 Python 的scikit-learn&scipy . stats库中更为常见——贝叶斯技术对 Python 的数据库来说是非常新的。但是重要的是理解为什么对经典测试的批评存在,统计学的一个潜在主题是主观性无处不在,在这个领域中贯穿始终。数据几乎不能说明问题。它必须在周围的环境中被解释。

p 值——正确解释的介绍

原文:https://towardsdatascience.com/p-values-an-introduction-to-correct-interpretation-14417771dd0f

在 5 分钟内理解一个非常广泛使用(和滥用)的统计概念

Edge2Edge 媒体Unsplash 上拍摄

如果你和我一样,很早就接触了数据科学,那么你很有可能会听到“p 值”。他们无处不在;假设检验、线性/逻辑回归、Anova / Ancova 表等。— 没有逃逸的‘p 值’。与此同时,有许多分析师/数据科学家不理解它的含义,并盲目地使用它来得出他们分析中的结论。下面的帖子旨在理解 P 值背后的直觉,它意味着什么,更重要的是它不意味着什么。下面的帖子还涉及到显著性水平、阿尔法值和零假设,这是正确理解 P 值所必须的。

这是什么意思?

p 值是对观察到的数据随机出现的可能性的度量。

它传达了,在零假设的前提下,得到观测数据值的可能性有多大。

→如果这个可能性低,那么零假设可能不成立;然而,如果这种可能性很高,那么就没有理由质疑零假设。

我们稍后会谈到零假设。让我们从一个基本的例子开始,然后在此基础上建立理解的层次。

考虑一个场景,你收集英国的房价数据,并绘制它们。请看图-1(下图),其中左图表示伦敦房价分布。平均房价 40 万。显然,房价以平均值为中心(由峰值表示),这意味着如果我们随机选择一所房子,其价格接近 400,000 的可能性很高,随着我们从平均值向极端值移动,找到价格高于(或低于)平均值的房子的可能性迅速降低。

让我们在这个分布中选择一个随机值,比如 600K。你找到 60 万以上房价的机会(或概率)**(即至少 60 万)可以用这个点右边的阴影区域来表示。这就是' P 值所代表的。

图片-1 |英国房价分布(虚拟数据)(图片由作者提供)

P 值是从总体分布中随机选取值时,获得至少与观察值一样“极端”的值的总概率。

上面的定义是有用的,但是没有给出它如何决定你的分析结果的味道。为了理解 P 值的用途,让我们深入研究重要性和 Alpha 级别。

显著性水平和 Alpha

统计测试的全部思想是了解我们观察到的是否有意义,或者换句话说,我们观察到的数据是否与标准(或总体)有显著差异。

这意味着我们总是在比较两件事情,并试图确定这种差异是否具有统计学意义。显著性水平是一个预先确定的临界值,基于这个临界值,我们可以判断我们的观察或发现(或假设)是真是假。作为一名数据科学家,这是一个预先确定的置信度。

Alpha 级别直接来自您决定的显著性级别,就像作为数据科学家您乐于在分析中拥有的阈值错误率。 alpha 值是一个阈值 p 值,超过该值,您会高兴地认为观察到的样本值与总体显著不同,甚至属于某个新的样本分布。

Alpha 值= 1 —显著性

请注意,显著性水平和 Alpha 水平由您决定。最常见的显著性选择值是 95%。对于显著性=95%,alpha 变为 5%。

在下面的图片-2 中,橙色点表示这个阈值 alpha 截止值为 5%,相应的房价为 750K。也就是说,基于该分布,该点右侧的面积(橙色阴影区域)为 5%。

图片-2 |观察值与人口分布的比较(虚拟数据)(图片由作者提供)

假设绿点和红点代表我们对房价的一些额外样本观察(可能来自也可能不来自英国)。绿色代表房价 50 万(图-2 图-2.1),红色代表房价 80 万(图-2 图-2.2)。我们将检查它们是否来自英国房价的原始人口分布。

  • 让我们从对应于 500K 的绿点开始,与之相关的 p 值大于阈值α(即绿点右侧的区域大于橙色的‘p 值’阴影区域)。也就是观察到房价至少极端到 500K 的概率大于 5%(我们的阈值α)。这意味着在人口分布中很有可能观察到这个值(与我们的阈值 5%相比)。因为这些很有可能从这个群体中观察到,所以没有理由认为它们来自不同的分布。
    →这意味着,绿点与该群体的分布没有显著差异

(请注意 如果作为一名数据科学家,你需要更放松地对待你想要拥有的置信度 ,即将置信度降低到 95%以下,导致 alpha 水平高于 5%, 这个绿点可以变成显著的 。)

  • 现在我们来看 800K 对应的红点;显然,红点对应的 p 值小于我们设置的 阈值α(即红点右侧的区域小于橙色的‘p 值’阴影区域)。基于我们预先决定的显著性水平(以及预先决定的 alpha 水平),我们可以说不太可能从这个人口分布中观察到 80 万的房价。
    因此,红点与人口****明显不同,很可能来自不同的分布(即该房屋可能不在英国)。

(请注意 如果作为一名数据科学家,你要对置信度 更加严格,即把置信度提高到 95%以上,反过来把 alpha 从 5%降低, 这个红点观察可以变成不重要的 。)

迷茫!!..让我们详细检查一下我们在上面做了什么:

我们有一个分布(即在这种情况下英国房价的人口分布),我们有两个额外的样本观察值,由一个绿色和一个红色的点表示(分别对应于 50 万和 800 英镑的房价)。对于绿点和红点,观察值(500K 和 800)不同于总体平均值(400K)。

任务:证明或反证观察到的房价是否属于人口分布。
—这反过来意味着检查绿点和红点的观察值是否与该人群有显著差异。

注意只能是两种场景中的一种:

  1. 房子属于英国,我们随机选择了一个以观察值(500 和 800k)定价的房子,不同于英国平均房价 400K(即人口平均值)。
  2. 房子不属于英国,因此我们观察到的价格(500 和 800k)不同于英国平均房价 400K(即人口平均值)

我们从假设基线开始,中性假设,也称为零假设。我们从一开始就假设零假设是正确的,并寻找证据来证明它是错误的。在这种情况下,

零假设: 绿色和红色分属于总体分布。

请注意,我们从假设中性零假设为真开始,并试图收集证据来拒绝它

我们在上述框架中隐含遵循的步骤:

  • 我们预先选择了我们的置信度/显著性水平,这意味着我们预先决定了我们的 alpha 水平(即 5%是最普遍接受的水平)。
    注意,我们希望根据我们预先确定的置信度检查观察值(500K 和 800K)是否属于总体分布。
  • 基于观察值(500K 和 800K),我们找出了相应的 p 值,即在总体分布中得到该观察值的可能性。
  • 我们p 值与我们预先确定的 Alpha 水平进行比较。

只有两种可能…

1。观察 P 值≤α:

这意味着,(与我们预先确定的α水平相比)从总体分布中获得观察值的可能性非常小

→观察值与总体分布显著不同。
→它可能来自一个完全不同的分布

基于这些证据,我们可以拒绝零假设

2。观察到的 P 值>α:

这意味着(与我们预先决定的阿尔法水平相比)很有可能从人口分布中获得观察值。

→观察值与人口分布没有显著差异。
→一定是来自相同的人口分布

基于这些证据,我们不能拒绝零假设

对于α= 5%,

p 值≤ 0.05:拒绝零假设

p 值> 0.05:不能拒绝零假设

p 值可以被认为是反对零假设的证据 b 为真

你知道吗?

p 值仅说明观察到的数据是否与我们的零假设中陈述的数据有显著差异。通过得到的 p 值并不意味着观察到的数据和所述的零假设之间的差异的大小。

摘要

p 值是对应于获得数据值(‘检验统计’)的可能性的概率值,在假设零假设正确的情况下,该值至少与实际观察到的数据值(观察到的‘检验统计’)一样“极端”。

p 值对应于您的数据在零假设下出现的可能性。

P 值提供了一个答案: 假设无差异(即零假设),在数据中看到我们正在观察的差异的可能性有多大。

记住:零假设——中性、基线、不变假设

  • p 值≤α值→拒绝零假设
  • p 值≤α值→拒绝零假设

P 值玩得开心!!

保持联系..

如果你喜欢这篇文章并且对类似的文章感兴趣的话 在 Medium 上关注我加入我的邮件列表(..如果你已经不是了..)跳上成为 中型家族的一员获取数以千计的有用文章。(如果你使用以上链接,我将获得你 50%的会员费)**

..不断学习,不断成长!

p 值:在被证明有罪之前是无辜的

原文:https://towardsdatascience.com/p-values-innocent-until-proven-guilty-65cb4e93e52c

统计假设、线性回归和多元宇宙之旅

科林·劳埃德在 Unsplash 上的照片

被告坐在证人席上一言不发。陪审团认为他有罪。他能感觉到。最后一条证据是毁灭性的。一张他衬衫上有血、一手拿着屠刀的照片足以证明他有罪。他怎么解释呢?他怎么能让陪审团相信这只是巧合呢?他不是杀人犯,他只是碰巧在错误的时间出现在错误的地方,出现在错误的地方。辩方传唤最后一名证人出庭。他是一名数学家。他的第一句话是:“我计算了这种情况下的 P 值,它远低于 5%”。陪审团似乎很困惑。数学家解释了他的计算。陪审团仔细检查了证据。在审判结束时,他们同意:判决无罪。

什么是 P 值,它们意味着什么?

这是怎么回事?什么是 P 值,它们与谋杀审判有什么关系?更何况这一切和线性回归有什么联系?首先,我们需要了解 P 值实际上是什么。一个好的起点是统计假设。假设是一种假设,一种我们对某事提出解释的想法。什么都有可能。假设是解释为什么事情会发生的理论。收集证据可以帮助我们确认假说是真是假。我最奇怪的假设之一是,猫不打开冰箱就知道冰箱里有什么!我没有很多证据支持这一点,但这仍然是一个假设。如果我想接受或拒绝这个假设,我应该找到检验它的方法,然后找到足够的证据来自信地说这个假设是真的还是假的。考虑到这一点,什么是统计假设呢?它和另一个假设一样,不同的是,这一次我们将有一个关于变量分布的假设。我可以说世界上超过 75%的金毛猎犬是黄色的。这个假设告诉了我一些关于金毛寻回犬在世界上的分布情况。P 值是检验这些统计假设之一的一种方法,一种知道一个人是否可以忽略或考虑该假设的方法。

P 值的概念与零假设的概念紧密相关。一般来说,如果你正在使用 P 值或考虑使用 P 值,你是在试图确定一件事是否对另一件事有真正的影响。在这种情况下,假设大概是这样的:猫移动的速度和它的颜色有关。也许这只是你的看法,因为每次你看到一只黑猫,它就跳来跳去,而当你看到一只暹罗猫,它就躺在那里等待下一碗虾头!无效假设是你试图反驳的。在这种情况下,无效假设是“猫的颜色不影响猫移动的速度”。想象如何构建零假设的一个好方法是加上“证明我错了!”最后。在这种情况下,你可以这样说:

  • 猫比狗睡得多。证明我错了!
  • 哈士奇的平均脱毛量比其他任何犬种都要高。证明我错了!
  • 猫比狗醒得早。证明我错了!
  • 一般来说,人们喝茶比喝咖啡快。证明我错了!
  • 每个被拍到穿着染血衬衫拿着刀的人都是杀人犯。证明我错了!

证明某人错误的最好方法是向他或她提供足够的证据,证明假设不成立。如果我回到前面的例子,说“猫的颜色不影响猫移动的速度(证明我错了!)”,你可以找到 10 只比我家黑猫跑得快的非黑猫。这足以无视我的假设吗?假设你测 1000 只非黑猫的最高速度,平均速度高于 1000 只黑猫的平均速度。怎么样?这看起来像确凿的证据!但是,你本来可以挑世界上最慢的 1000 只黑猫,最快的 1000 只非黑猫。我怎么会知道?也许你很幸运…P 值可以帮助我们确定这一点。P 值是假设零假设为真,获得我们正在观察的结果的概率。最后一句话的第二部分才是这里的关键!P 值不是找到比黑猫快的非黑猫的概率。P 值是在一个黑猫总是更快的世界里找到一只比黑猫更快的非黑猫的概率。所以,一个小的 P 值告诉我们,如果零假设是真的,那么我们的观察就不太可能发生。这意味着我们可以拒绝零假设。在这种情况下,我们可以说“猫的颜色不影响猫移动的速度是不对的”。如果你的非黑猫小组和黑猫小组一样快或者更快,那么这纯粹是运气。

回到审判,高 P 值表明在一个被告有罪的世界里,找到他带着染血衬衫和屠刀的几率很小。在这种情况下,即使他看起来有罪,也有统计证据表明他可能不是。我们用一个更好的例子来分析这个。

教室、年级和多元宇宙

Matthew Coolguy 教授是教授中世纪早期欧洲历史的大学教授。他教同样的课程已经超过 8 个学期了,他很担心他的学生表现不佳。他认为早上 7 点上课对大多数学生来说可能太早了,影响了他们的表现。他还认为课程材料可能与此有关。他用非常旧的幻灯片,里面有很多文字和黑白图片。也许如果他更新幻灯片,减少文字量并加入彩色图片,学生们会对他的课程更感兴趣。最后,Coolguy 教授的一位同事告诉他,他也有类似的问题,他通过更换教室解决了这个问题。酷盖教授可以使用另一栋楼里更新更明亮的教室。他可以把他的讲座搬到那栋楼去,看看情况如何。考虑到这一点,Coolguy 教授决定进行一项实验:他将从这些选项中选择一个,并在接下来的几个学期中开始实施。然后,他会将新学期的成绩与旧学期的成绩进行比较,并决定他的假设是否正确。

图一。酷盖教授想要分析的三种情况。(作者编造的情节)。

做这个实验的好方法是什么?我们可以简单地在下一个学期实施一个改变,如果平均分数高于之前的平均分数,我们可以说这个改变对学生的表现产生了积极的影响。有这么简单吗?假设 8 个学期后,平均成绩是 5.9 分(满分 10 分)。下学期,Coolguy 教授将他的讲座开始时间从早上 7:00 调整到 10:00。对于上课的 25 名学生来说,这是唯一改变的事情,教室、材料和幻灯片都是一样的。我们现在假设在这个学期结束时,这些学生的平均成绩是 6.9 分(满分 10 分)。这是否足以得出结论,开始时间的改变对学生是有益的?也许这 25 个学生真的是好学生,马修只是碰巧发现了 25 个优秀的学生,无论讲座什么时候开始,他们都会得到更高的分数。另一方面,想象下一学期的 25 名学生是真正的坏学生,对中世纪早期欧洲的历史毫无兴趣。不管什么时候开始,他们都得了很差的分数。分析结果的更好方法是什么?

100 多年前,一位在爱尔兰吉尼斯啤酒厂工作的化学家发明了一种解决类似问题的方法。笔名为“学生”威廉·希利·戈塞的作者发明了 t 检验法,这种方法对处理小样本进行质量控制等非常有用。关于学生 t 分布的数学知识在其他来源中有更好的介绍。然而,我们更感兴趣的是这种方法在我们问题中的应用以及这种方法和 P 值之间的关系。通过 t 检验,我们可以考虑样本均值、标准差和样本中的点数来确定我们所做的更改是否显著。它是这样工作的:

第一步。计算 t-test 或 t-student 值

图二。t 检验方程

请注意该值如何随着样本中点数的增加而增加,以及如何随着标准偏差的增加而减少。此外,t 检验取决于总体和样本均值的差异程度。我们将在接下来的章节中详细阐述这一点。

第二步。使用学生的 t 分布计算得到与你得到的结果一样极端的结果的概率

图 3。t-学生分布。(作者编造的情节)。

对于这一步,我们需要使用学生的 t 分布曲线。该曲线在商业软件或表格中广泛可用。t 分布曲线看起来像正态分布曲线,但尾部更重。除此之外,t 分布曲线的形状将取决于自由度,通常计算为 n-1,其中 n 是样本数。

如果我们想知道如何找到一组平均值高于总体平均值的值,那么我们可以查看曲线下包含等于或高于 t 检验值的区域。相反,如果我们有兴趣找到一组平均值低于总体平均值的值,我们必须查看曲线下覆盖等于或低于 t 检验的值的区域。还有第三种选择,我们计算两条尾巴在曲线下的面积。当我们分析与线性回归相关的计算时,我们将回到这一点。

曲线下的区域代表 P 值。所以每次我们看到 P 值小于 0.05,我们实际上说的是曲线下的面积小于 0.05。在这种情况下,曲线下的面积代表获得一个和我们正在获得的值一样极端的值的概率。记住“作为极端”意味着“等于或大于”或“等于或小于”,这取决于具体情况。

回到我们的例子,假设 Coolguy 教授决定将他的课移到上午 10 点。请记住,他已经在上午 7 点授课 8 个学期了,现在,他的课程将首次在上午 10 点开始,希望学生们提高他们的整体分数。在改革后的第一个学期,他有 30 名学生,他们的平均成绩为 6.9/10,高于之前的成绩(5.9/10)。图 4 显示了分数的原始分布以及一个黑点,该黑点表示新的平均值。本例中使用的数据集是仅为本练习随机生成的,它不代表一组学生的真实成绩,也不是从任何数据库中复制的。

图 4。原始等级的正态分布。黑点代表实施某些更改后的新平均成绩。

对于新的学生组,标准差是 2.75,这意味着计算 t-student 值将得到 2。这是我们必须在 t 分布曲线或表格中使用的数字。在我们的例子中,对于 30–1 = 29 自由度的 t 分布,获得等于或大于 6.9 的平均值的累积概率是 0.027。图 5 显示了一个有 29 个自由度的 t 分布曲线,以及一个代表 t-student 值 2 的黑点。黄色区域代表 0.05 的面积。这意味着任何落在该区域内的 t-student 值将表示 P 值为 0.05 或更小。0.05 是一个常用的 P 值阈值,用于决定是否可以拒绝零假设。

图五。原始学生群体的 t-学生分布。黑点代表新变化的 t-student 值,黄色区域代表 0.05 曲线下的区域

在这一点上,重要的是要记住,零假设是我们试图反驳的。在这种情况下,零假设表示“开课时间不影响学生的表现”。因此,P 值表示如果假设零假设为真,获得我们所看到的结果的概率。在这种情况下,假设开始时间的变化对他们的表现没有任何影响,有 2.7%的概率找到平均分数为 6.9 的一组 30 名学生。我们的意思是,如果开学时间没有任何影响,有一群学生达到那个平均分数的可能性很小。这就是为什么小 P 值(通常小于 0.05)被认为是该参数或结果变化的相关性的指示。

让我们分析一下 t-student 值是如何在代表 P 值<0.05. Figure 2 shows the equation that is used to calculate the t-student value. Note how if the difference between the population average and the sample average increases, the t-student value will also increase. This means that this value will be closer to the yellow zone. A variation that doesn’t lead to important changes in the average will have fewer probabilities of ending up inside of the yellow zone. However, the t-student value can also increase according to the sample size. A small change in a few samples will generate a smaller t-student value than a small change in many samples. In the case of our example, an increment in the grades of a few students means less than an increment in the grades of many students. If Prof. Coolguy had made the experiment with 5 students instead of 30, the t-student value would have probably ended outside the yellow zone.

There is still one thing we have to consider which is how dispersed our sample is. This means how different each of the points is from the average. This is measured by the standard deviation (Figure 6). A low standard deviation indicates that the values are close to the mean, while a high standard deviation indicates that the values are spread out. When we calculate the t-student value, a low standard deviation will increase the t-student. In our case, a low standard deviation means that all the students obtained more or less similar grades which means that the changes we did in the experiment affected most students. On the contrary, if the average grade had increased because a few students, who were not influenced by the changes, got better grades; the standard deviation would reflect that since not all grades would be close to the average. This would lower the value of the t-student and will take us to the left of the yellow zone in Figure 5.

的黄色区域内结束的图 6。标准偏差方程

多元宇宙

回到我们的例子,我们可以问自己,如果 Coolguy 教授实施拟议的变化,会发生什么,更重要的是,P 值在确定这些变化的影响方面会有多好。我们将考虑三种不同的场景,在这些场景中,Coolguy 教授实现了三种改变中的每一种:开始时间、课程材料和教室类型。实际上,同时测试这三种情况是不可能的,所以我们将生成三个多重宇宙,在其中我们将测试每一个变化会发生什么。作为多元宇宙的创造者,我们知道每一个变化会有什么反应。这意味着我们预先知道每一个变化将产生的分数分布。我们真正想测试的是 P 值如何决定这些变化是否显著。以下结果包含在 Jupyter 笔记本中,您可以通过 GitHub 链接访问。

图 7 显示了 150 名学生当前年级的分布情况。这种等级分布的平均值为 5.7,标准偏差为 2.6。图 7 还包含了实施每个变更的案例的等级直方图。

图七。原始人员级别以及每个方案中的级别的直方图。(作者利用本 Jupyter 笔记本 制作的剧情)。

  • 场景 1:开始时间的改变。这种变化对大多数情况都有积极的影响。这 150 名学生代表了 Coolguy 教授改变上课时间的世界。
  • 场景 2:教室里的变化。这种变化对大多数情况都有负面影响。这 150 名学生代表了 Coolguy 教授改变课堂的世界。
  • 场景 3:课程材料的变化。这一变化在大多数情况下没有影响。这 150 名学生代表了一个世界,在这个世界里,酷盖教授改变了他在讲座中使用的材料。

在附加的代码中,我们将定义我们想要分析的场景,然后我们将从该场景中随机选择一些学生。对于这个样本,我们将计算 t-student 值,并确定 P 值是否小于 0.05,这通常意味着我们可以拒绝零假设。在这种情况下,这意味着修改对学生的成绩有影响。在此之后,我们可以从场景中抽取多个样本,并查看该组学生是否比原始组学生表现得更好。

场景 1。开始时间的改变。

正如我们在图 7 中看到的,这一变化将对大多数学生的成绩产生积极影响。假设我们在一个有 30 名学生的新学期实施了这一改变。图 8.b 显示了 t-student 分布以及我们用来计算 P 值的 30 名学生原始样本的位置(大黑点)。在这种情况下,P 值位于黄色区域内,这意味着小于 0.05。

图 8.a 显示了一个正态分布,它代表了原始学生的成绩以及来自新场景的 30 名学生相对于该分布的位置(大黑点)。我们可以看到,在新的场景中,平均值高于原始场景。现在的问题是:在这种情况下,这 30 名学生在评估其余学生的表现时有多准确。换句话说,我们能相信这 30 名学生说,任何学生在上午 10 点而不是 7 点上课会表现得更好吗?毕竟,小于 0.05 的 P 值意味着,如果我们假设开始时间的变化对学生的成绩没有影响,那么只有 5%的概率获得这样的结果。

图 8。a)在实施开始时间的变化后,原始成绩和新的平均成绩的正态分布(黑点)。b)原始学生群体的 t-学生分布。黑点表示新变化的 t-student 值,黄色区域表示 0.05 曲线下的区域。黑点右边曲线下的面积是 0.046(作者用这个 Jupyter 笔记本 做出的图)。

因为我们是这个多元宇宙的主人,我们可以看到如果 Coolguy 教授在上午 10 点继续他的讲座,其他学生会发生什么。图 9 类似于图 7,但是这次它包含了一些其他的点,这些点对应于五个额外的迭代,在这些迭代中我们从新的群体中选择了 30 名学生。这类似于回到过去,假设 Coolguy 教授额外教授 5 个学期,每个学期有 30 名学生。每个点的 y 轴位置并不意味着什么,这些点以这种方式定位是为了避免有许多点在一起,从而很难看出哪个是哪个。

图 9。a)原始等级的正态分布、实施开始时间(大黑点)变化后的新平均等级以及来自新人群的五个额外样本。b)原始学生群体的 t-学生分布。大黑点表示新变化的 t-student 值,黄色区域表示 0.05 曲线下的区域。其余的点代表来自新群体的五个额外样本(作者使用这个 Jupyter 笔记本 制作的情节)。

请注意,在图 9.a 中,所有组的平均分数都高于原来的平均分数。这意味着对所有这些组来说,开始时间的改变有积极的影响。还需要注意的是,有一组学生的计算 P 值大于 0.05(图 9.b)。如果我们选择这组学生来确定开始时间的变化是否对学生的表现有任何影响,我们会得出结论,它没有任何影响。即使这组学生的平均成绩高于之前的平均成绩,年级和学生人数的差异也会降低 t-student 值,使其位于 0.05 区域之外。

场景二。教室里的变化。

现在让我们来关注第二种情况,即教室中的变化。请记住,我们知道这一变化将对学生的平均成绩产生负面影响。如果我们从这个场景中随机选择一组 30 名学生,计算他们的平均成绩,我们得到 4.7,低于原来的平均成绩 5.7(图 10.a)。这一次,P 值为 0.0204,表示-2.1 左侧曲线下的面积(图 10.b)。这意味着,在一个教室的变化不会影响成绩的世界里,获得等于或小于 4.7 的平均成绩的概率是 2.04%。

图 10。a)在教室中实施变更后,原始成绩和新平均成绩的正态分布(黑点)。b)原始学生群体的 t-学生分布。黑点表示新变化的 t-student 值,黄色区域表示 0.05 曲线下的区域。黑点左边曲线下的面积为 0.0204(作者用这个 Jupyter 笔记本 做出的图)。

现在,让我们从人群中随机抽取 5 个与这个场景相对应的样本。请注意,其中两个组的计算 P 值大于 0.05(图 11.b)。如果我们一开始就选择了这些组,我们的结论可能是教室的变化并没有影响平均成绩。另一方面,有一组学生的平均成绩似乎没有受到这一变化的影响(图 11.a)。对于这群学生来说,换教室其实是稍微提高了成绩。这意味着,即使在我们采集的样本导致 P 值小于 0.05 的情况下,我们也可能有一组个体没有受到这种变化的影响。这没关系。小 P 值只是一个小概率,并不能保证某件事会发生或不会发生。

图 11。a)原始年级的正态分布、实施课堂时间变化后的新平均年级(大黑点)以及来自新人群的五个额外样本。b)原始学生群体的 t-学生分布。大黑点表示新变化的 t-student 值,黄色区域表示 0.05 曲线下的区域。其余的点代表来自新群体的五个额外样本(作者使用这个 Jupyter 笔记本 制作的情节)。

场景三。课程材料的变化。

第三个也是最后一个场景对应的是 Coolguy 教授改变他在讲座中使用的材料的世界。请记住,这种变化不会对成绩产生重要影响。如果我们随机抽取 30 名学生并计算其 P 值,我们可能会得到一个总是大于 0.05 的 P 值(图 12)。但是,在这种情况下,可能会有一些学生的计算 P 值小于 0.05。在这种情况下,这组学生会指出这种变化对成绩有影响,但我们会看到大多数平均成绩与总体平均水平相似。

图 12。黑点代表的班级资料(作者使用本 Jupyter 笔记本绘制的图)实施变更后的原始成绩和新的平均成绩的正态分布。

正如您在前面的场景中看到的,我们可以识别出 4 种可能的结果(图 13)。

  • 计算出的 P 值表明我们可以拒绝零假设,并且这种变化确实对总体有影响:真的正
  • 计算出的 P 值表明我们可以拒绝零假设,但这种变化对总体没有影响:假阳性
  • 计算出的 P 值表明我们不能拒绝零假设,并且变化对总体没有影响:真阴性
  • 计算出的 P 值表明我们不能拒绝零假设,但是这种变化确实对总体有影响:假阴性

图 13。不同的结果取决于对 Ho 的拒绝或接受以及变化的重要性。这也就是所谓的 混乱矩阵 。(作者自制的剧情)

关于 P 值的警告

在进行线性回归之前,考虑 P 值的有效性和正确解释是非常重要的。当 P 值小于 0.05 时,您可能会认为您得到的结果有小于 5%的概率是随机的,或者发现假阳性的概率是 5%。然而,这并不是 P 值告诉我们的。实际上,在 P 值小于 0.05 的情况下,我们可能会得到高于 5%的假阳性率。请记住,小于 0.05 的 P 值意味着在零假设为真的情况下,找到您找到的结果的可能性小于 5%(或者更极端)。这并不能告诉你最终可能得到的假阳性的数量。

关于 0.05 的阈值,重要的是要记住 0.05 只是一个任意的极限。为什么是 0.05 而不是 0.025?还是 0.005?如果你在实验中发现的 P 值是 0.06,这个实验与你得到 0.05 的实验相比有多差?这里的最佳答案是,无论 P 值是一个小值还是更小的值,你都应该始终保持谨慎。根据《自然》杂志上一篇阅读量最大的文章,P 值不应该被分类对待,这意味着我们永远不应该仅仅根据 P 值就急于断言某事具有或不具有统计学意义[2]。关于 p 值有效性的讨论比以往更加活跃。因此,在计算 P 值后,我们应该小心不要很快得出结论。这也适用于线性回归,这是下一节的主题。

这和线性回归有什么关系?

我们很清楚 P 值是什么。那么,P 值和线性回归之间的关系是什么?还有,为什么 P 值一直出现在线性回归报告上?答案与之前解释的一切都有关系,但它仍然需要一些额外的思考。此时,您可能已经熟悉了类似于来自 statsmodels 的回归报告。像这样的线性回归报告类似于在 MATLABMiniTab 等中得到的报告。它包含每个回归系数、拟合度(R)的一般度量以及 P 值等。回顾回归报告中每个数字的实际含义是一个很好的练习,但这不是本文的主题。如果你想了解更多关于如何阅读和解释回归报告的信息,请参阅参考文献[3]。我们此时的兴趣是了解我们在回归报告中看到的 P 值是如何计算的,以及如何解释它们。

如前所述,为了理解 P 值告诉我们什么,我们首先需要理解 P 值所指的零假设是什么。在线性回归的情况下,零假设说:“自变量 X 对因变量 Y 没有影响”。这意味着 P 值是在计算回归系数取报告上显示的值的概率,假设特定自变量对因变量没有影响。让我们想象一下:你试图找到因变量 Y 和自变量 X1,X2 和 X3 之间的回归。有人告诉你:“X2 不可能和你有关系,证明我错了!”。运行线性回归后,您会发现 X2 的回归系数为 23,其 P 值为 0.001。这意味着如果你不考虑 X2 对 Y 的影响,那么只有 1%的概率得到和你一样好的拟合。在这种情况下,我们可以说我们可以拒绝零假设:X2 对 y 没有影响是不正确的。换句话说,P 值为 0.05 意味着结果出现在随机分布中的概率只有 5%[4]。现在让我们考虑一下:运行线性回归后,您发现 X2 的回归系数是 23,现在它的 P 值是 0.75。我们现在不能拒绝零假设,因为如果 X2 和 Y 无关是真的,那么有 75%的机会得到和我们得到的一样好的拟合。这一次我们可以说 X2 对 y 没有影响

我们再用一个例子来分析一下这个。下图(图 14)显示了美国 30-39 岁女性样本中女性平均体重与身高之间的关系[5]。这个数据集可以在维基百科的简单线性回归页面上找到。使用线性回归,我们可以将该数据调整为以下形式的函数:权重= A0+a1 *距离。图 14 中的蓝线代表这一功能。根据线性回归的结果,较好地调整数据的系数为 a0=-39.062 和 a1=61.2722。

图 14。体重-身高数据集的线性回归。(作者编造的情节。数据集取自[5])。

我们可以使用 statsmodels 计算每个系数的 P 值(图 15)。在这种情况下,我们将得到比 0.05 小得多的值。在线性回归世界中,这意味着两个系数都很重要,我们不能忽略其中任何一个。然而,这真正的意思是,如果我们假设 a0 或 a1 对重量没有任何影响,那么就很少有机会得到像我们这样的拟合。这类似于说我们有统计学上的显著证据证明这两个系数不为零。

图 15。线性回归结果,其中两个参数显示小于 0.05 的 P 值

让我们更深入地研究一下。考虑身高和体重之间的关系,如图 16 所示。在这种情况下,线的斜率几乎为零,这意味着身高和体重之间没有很强的关系,因为不管年龄如何,体重似乎总是约为 65。不做任何其他计算,我们可以推断,在这种情况下,如果我们想将数据点调整为曲线,高度并不真正重要。

图 16。体重-身高数据集修改版本的线性回归。(作者编造的情节)。

如果我们计算这种情况下的 P 值,我们会发现乘以身高的系数的 P 值为 0.7302,高于通常的阈值 0.05(图 17)。这是什么意思?在线性回归世界中,这意味着我们不需要这个系数来调整数据点,这是我们之前通过视觉检查图发现的。利用我们已经知道的 P 值,这个数字意味着得到和不考虑年龄一样好的拟合的概率是 82%。换句话说,如果我们不考虑身高,我们有很好的机会调整数据,而身高是我们在查看数据后已经知道的。

图 17。其中一个参数的 P 值大于 0.05 的线性回归结果

如何计算线性回归中的 P 值?

此时,我们唯一需要知道的是如何计算线性回归中的 P 值。如果您已经读到这里,那么您应该知道 P 值代表 t-student 分布中曲线下的面积。根据不同的情况,我们可以计算左尾、右尾或两者之下的面积。因此,为了获得 P 值,我们首先需要计算 t-student。对于线性回归,计算如下:

图 18。t-线性回归中参数的学生计算

注意,如果零假设为真,则 b0 代表系数 b 的值。所以,我们唯一需要计算 t-student 的是系数的标准差。标准误差衡量每个系数的精度。我们可以把它想成一个系数的标准差。然而,由于每个系数可能有不同的数量级,标准误差本身并不能很好地表明每个系数的精度。这就是我们在线性回归中使用 P 值的原因!我们取每个系数,除以它的标准误差,然后在 t 分布曲线中找到这个值。

为了计算标准误差,我们首先需要计算分布的标准偏差,通常用σ (sigma)表示:

图 19。样本的标准偏差方程

一旦我们有了分布的标准偏差,就可以使用(X'X)^-1 矩阵)计算标准误差(图 20)。如果你以前用过线性回归,你会对它很熟悉。请注意,每个参数的值(X'X)^-1(用红色书写)是不同的。一旦你有了 SE,你就可以通过将系数的值除以 SE 来计算 t-student。

图 20。标准误差方程

由于我们对每个系数的绝对值感兴趣,因此 P 值是在考虑两个尾部的情况下计算的。这意味着,一旦我们有了 t-student 值,我们将计算 t-student 和-t-student 左右两边曲线下的面积。P 值实际上代表得到拟合的概率,例如我们得到的系数大于或等于 bj 或小于-bj 的拟合。

图 21。双尾 P 值

结论

p 值为我们提供了一些关于人口变化影响的信息。在线性回归中,如果我们想用一个变量来调整一些观察到的数据,它们帮助我们了解这个变量有多重要。的确,它们有时会误导人,我们可以找到更好的方法来进行这种分析。然而,如果我们正确地解读它们,我们可以从中提取有价值的信息。记住它们的含义、它们是如何计算的以及为什么通常采用 0.05 的惯例总是很重要的。不像 Coolguy 教授的例子,我们不能创造许多多重宇宙,并分析每一个会发生什么(至少现在不行!).但是,我们可以像本文开头所描述的那位神秘的数学家一样。我们需要接受生活中大多数事情都是不确定的这一事实。包括 P 值在内的统计分析是我们在这条不确定道路上的最佳指南。

参考

  1. 布莱恩·雷斯尼克(2017)。关于 p 值的无聊辩论展示了科学——以及如何修正它。声音
  2. 弗吉尼亚州安海因,南格陵兰,麦克沙恩,B. (2019)。科学家揭竿而起反对统计学意义。自然 567,305–307
  3. 麦克勒蒂姆。(2020).通过 statsmodels 解释线性回归。总结()。中等
  4. 普林斯顿大学图书馆。解释回归输出
  5. 维基百科贡献者。简单线性回归。维基百科,免费的百科全书。世界协调时 2022 年 5 月 3 日 00:13。可在:https://en.wikipedia.org/w/index.php?购买 title = Simple _ linear _ regression&oldid = 1085888208。访问时间是 2022 年 5 月 13 日。

使用原生 XGBoost API 进行成对超参数调优

原文:https://towardsdatascience.com/pair-wise-hyperparameter-tuning-with-the-native-xgboost-api-2f40a2e382fa

在解决偏差-方差权衡时搜索全局最小值

马库斯·斯皮斯克在 Unsplash 上的照片

由于 Boosting Machine 有过拟合的倾向,XGBoost 非常关注解决 偏差-方差权衡 并通过超参数调整方便用户应用各种正则化技术。

这篇文章将带您浏览使用原生 XGBoost API 的超参数调优的代码实现,以解决偏差-方差权衡问题。该项目的全部代码发布在我的 Github 库中。

读完这篇文章后,您可以复制或修改我在下面演示的过程,用本机 XGBoost API 为您自己的回归项目进行超参数调优。

介绍

我们的目标是对原生 XGBoost API 执行超参数调优,以提高其回归性能,同时解决偏差-方差权衡问题,特别是减轻 Boosting 机器的过度拟合倾向。

为了进行超参数调谐,本次分析采用 网格搜索 的方法。换句话说,我们选择超参数的搜索网格,并计算搜索网格上所有超参数数据点的模型性能。然后,我们确定 性能的全局局部最小值— 或产生最佳性能的超参数数据点(目标函数的最小值)—作为调整模型的最佳超参数值。

计算约束

超参数调整在计算上可能非常昂贵,这取决于您如何设置搜索网格。因为它需要在搜索网格中设置的所有数据点上迭代性能计算。数据点越多,计算成本就越高。很简单。

不幸的是,我的笔记本电脑计算能力非常有限。但是,一个好消息是,Google Colab 为其免费帐户的每个用户提供一个 GPU。并且原生的 XGBoost API 具有 GPU 支持特性。总之,我可以通过使用 Google Colab 的 GPU 运行原生 XGBoost API 来加快调优过程。

尽管如此,当选择的搜索网格有大量的数据点要覆盖时,一个 GPU 仍然不够。因此,我仍然需要解决计算资源的限制。

理想情况下,对所有选定的超参数进行单个联合调整将准确捕捉它们之间所有可能的相互作用。然而,联合调谐具有巨大的搜索网格数据点,因此在计算上将是昂贵的。在这种情况下,我将执行多个 成对超参数调整 ,而不是 单个联合同时调整所有超参数 。这不仅会减少用于调整的超参数数据点的数量,还允许我们为每个成对调整呈现性能前景的 3D 可视化。当然,这种方法有一个问题。我们将在以后讨论这个问题。

现在,让我们开始编码。首先,我们需要下载数据进行分析。

分析

A.数据

对于回归分析,我们使用 sklearn API 的内置加州住房数据集。数据集的来源是 StatLib 的数据集存档中的 houses.zip。数据集的许可证“License: BSD 3 clause”可以在这里的代码的第 22 行找到。关于数据集的解释,请阅读此链接

我们可以使用fetch _ California _ Housing函数sk learn . datasets下载加州房屋数据集。

我们可以将数据集存储到变量 housing_data 中。

from sklearn.datasets import fetch_california_housing
housing_data = fetch_california_housing()

现在,我们需要将导入的数据集转换成熊猫数据帧。我们将特征和目标变量分别分离成 df1 和 df2。

descr = housing_data['DESCR']
feature_names = housing_data['feature_names']
data = housing_data['data']
target = housing_data['target']
df1 = pd.DataFrame(data=data)
df1.rename(columns={0: feature_names[0], 1: feature_names[1], 2: feature_names[2], 3: feature_names[3],
 4: feature_names[4], 5: feature_names[5], 6: feature_names[6], 7: feature_names[7]}, inplace=True)
df2 = pd.DataFrame(data=target)
df2.rename(columns={0: 'Target'}, inplace=True)
housing = pd.concat([df1, df2], axis=1)
print(housing.columns)

现在,让我们看看数据帧功能的摘要信息。

df1.info()

以下是输出:

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 20640 entries, 0 to 20639
Data columns (total 8 columns):
 #   Column      Non-Null Count  Dtype  
---  ------      --------------  -----  
 0   MedInc      20640 non-null  float64
 1   HouseAge    20640 non-null  float64
 2   AveRooms    20640 non-null  float64
 3   AveBedrms   20640 non-null  float64
 4   Population  20640 non-null  float64
 5   AveOccup    20640 non-null  float64
 6   Latitude    20640 non-null  float64
 7   Longitude   20640 non-null  float64
dtypes: float64(8)
memory usage: 1.3 MB

B .库导入

接下来,让我们看一下我们将要使用的库。

  • 首先,我们需要原生的 XGBoost API 来运行它和它的 plot_importance 来显示调优后所选模型的特性重要性。
import xgboost as xgb
from xgboost import plot_importance
  • 我们还使用来自sk learn . metricsmean _ squared _ error进行度量。
from sklearn.metrics import mean_squared_error
  • 对于训练测试数据集的拆分,从sk learn . model _ selection中的train _ test _ split可以方便我们拆分数据帧。
from sklearn.model_selection import train_test_split
  • 为了可视化,我们使用 Matplotlib 中的以下项目来呈现 3D 图像。
from matplotlib import pyplot
import matplotlib.pyplot as plt

from mpl_toolkits.mplot3d import Axes3D

C .超参数&成对调谐

1.选择用于调谐的超参数

原生 XGBoost API 内置了这么多超参数。由于给定的有限计算资源可用性,我们必须做一些挑选。

Jason Brownlee概述了正则化有 5 类超参数:迭代控制、收缩、树约束、随机采样、L1 & L2 正则化。根据他的框架,该分析选择了以下 9 个超参数来解决偏差-方差权衡问题。有关每个超参数的详细信息,请访问下面每个超参数中设置的超链接。

a)迭代控制:以下两个超参数操作迭代控制。

其余的超参数需要设置在一个字典中, pram_dict 。当我们在搜索网格上的数据点上迭代 k 倍交叉验证时,我们可以使用这个参数字典来操纵超参数的值。

b)收缩率:

c)采油树助推器约束:

d)随机子采样:

e) L1 和 L2 的正规化

f)不用于调谐的附加条目:

此外,我们需要在参数字典中指定以下 4 个额外的超参数。作为一项预防措施,这些超参数将是固定的,但不会被调整。

  • 目标 :选择回归的目标函数。
  • eval_metric :选择回归的绩效评估指标
  • gpu_id : 0 由 Google Colab 早先指定
  • tree _ method:gpu _ hist选择 GPU 设置

到目前为止,我们已经讨论了 3 件事情:选择用于调整的超参数、参数字典的初始设置以及用于评估初始偏差-方差权衡的初始(预调整)性能曲线。我们发现了实质性的差异,或者说过度拟合。

3.成对超参数调谐

理想地,对所有这些超参数的单个联合调谐将准确地捕捉参数字典中这些所选超参数之间的所有可能的相互作用。然而,联合调谐具有巨大的搜索网格数据点,因此在计算上将是昂贵的。

为了节约分析的计算成本,我从 8 个参数对中选择了 4 个:即

  • (最大深度,eta)
  • (子样本,列样本 _ 字节树)
  • (最小 _ 孩子 _ 体重,伽玛),以及
  • (寄存器α,寄存器λ)

然后,我对这 4 个超参数对中的每一个进行了成对调整。每次调整后,最佳调整结果用于替换超参数对的初始值。

通过这种方式,每一次成对调优都会逐步提高调优模型的性能。这总体上减少了搜索网格数据点的数量,从而以网格搜索的公正性为代价节省了调整过程的计算成本。当然,这里的基本假设是,通过 4 次成对调节获得的结果不会与通过理想的单一联合调节所有 8 个超参数获得的结果有实质性的偏离。

D .预调优回归:训练和测试数据集上的性能曲线

在调优之前,我们首先需要初始化参数字典的值,以便对训练数据集和测试数据集执行第一次回归。它是沿着训练迭代比较那些数据集上的初始性能曲线。这很重要,原因有二。

  • 首先,我们想要比较训练性能和测试性能,以评估初始状态下的偏差-方差权衡,即欠拟合-过拟合权衡的问题。
  • 第二,在调整结束时,我们希望比较调整前的性能曲线(包括训练和测试)和调整后的性能曲线,以评估调整的正则化影响。我们希望评估调优是否有效地对模型执行正则化,以解决偏差-方差权衡问题。

这里是我们的参数字典的初始化。

*param_dict={'objective': 'reg:squarederror', # fixed. pick an objective function for Regression. 
 'max_depth': 6,
 'subsample': 1,
 'eta': 0.3,
 'min_child_weight': 1,
 'colsample_bytree': 1,
 'gamma': 0,
 'reg_alpha': 0.1,
 'reg_lambda': 1,
 'eval_metric': 'mae', # fixed. picked a evaluation metric for Regression.
 'gpu_id': 0, 
 'tree_method': 'gpu_hist' # XGBoost's built-in GPU support to use Google Colab's GPU
}*

此处分配的值仅用于初始化目的。这些值(除了 objective、eval_metric、gpu_id 和 tree_method)将在超参数调整期间发生变化。

除了 pram_dict 中指定的那些超参数,我们还需要在 pram_dict 之外初始化另外两个超参数 num_boost_round 和 early_stopping_rounds。

*num_boost_round = 1000
early_stopping_rounds = 10*

现在,利用初始化后的参数字典 param_dict,我们可以使用内置的原生训练函数 xgb.train(),定义一个自定义函数,称之为bias _ variance _ performance()。其目的是通过比较训练数据集和测试数据集之间的初始模型性能曲线,评估沿着训练迭代的偏差-方差权衡。

*def bias_variance_performance(params):

    evals_result = {}
    model = xgb.train(
        params,
        dtrain=DM_train,
        num_boost_round=num_boost_round,
        evals=[(DM_train, "Train"), (DM_test, "Test")],
        early_stopping_rounds=early_stopping_rounds,
        evals_result=evals_result
    )
    train_error = evals_result["Train"]["mae"]
    test_error = evals_result["Test"]["mae"]

   print("Best MAE before the Tuning: {:.4f} with {} rounds".format(model.best_score, model.best_iteration+1))return model, evals_result*

关于内置训练函数 xgb.train()的详细信息,请参考文档

以下是输出:

*Best MAE before the Tuning: 0.3103 with 121 rounds*

输出显示测试性能的最佳值,而不是训练性能的最佳值。在第 121 轮迭代中检测到了最佳的测试性能。换句话说,初始(预调整)模型(具有初始超参数设置)是在 121 轮迭代时确定的。

让我们比较第 121 次迭代时训练数据集和测试数据集之间的初始(预调优)模型性能。

*[120]	Train-mae:0.17322	Test-mae:0.31031*

在 121 轮迭代中确定的初始(预调整)模型(具有初始超参数设置)的训练性能为 0.17322,测试性能为 0.3103。巨大的差距表明差异,或过度拟合。

下面的图表有两条初始的(预调优的)性能曲线以及迭代循环:一条是蓝色的用于训练性能,另一条是红色的用于测试性能。

制作人:杉尾道夫

随着迭代的进行,初始的(预调优的)训练性能继续提高,而初始的(预调优的)测试性能在某个点上停滞不前。这表明在初始(预调整)模型中有一个显著的 方差 ,或 过拟合

现在,为了解决偏差-方差的权衡,我们通过调整原生 XGBoost API 的超参数来对模型进行正则化。

E .调法:

在调优方法方面,我想强调几个重要的脚注。

  • 我为每个超参数对设置了 搜索网格 ,并使用 网格搜索方法 进行成对参数调整。
  • 我迭代了 k 重交叉验证 来计算预定搜索网格上所有数据点的训练数据集的性能度量。
  • 可视化使用了三角插值技术来填充搜索网格数据点之间的间隙,并渲染出一个平滑的表演场景表面。

a) k 倍交叉验证

为了运行 k 重交叉验证,我们使用了内置的交叉验证函数, xgb.cv() 。作为一项预防措施,k-fold 交叉验证只在训练数据集(验证数据集从该数据集分配)上执行:不要将测试数据集传递到交叉验证函数:xgb.cv()。

我们可以对每个超参数对的预定搜索网格上的所有数据点重复 k 重交叉验证,以搜索回归性能度量的全局最小值,即平均绝对误差。

自定义 k 重交叉验证函数:(可选)

为了方便调优迭代,我们可以通过使用原生 XGBoost API 的内置交叉验证函数 xgb.cv()来定制一个 k 重交叉验证函数。对于我们的例子,我们为我们的分析设置了 5 个折叠。或者,可以探索其他值,例如 10。

下面是我的自定义交叉验证函数 cross_validation()的代码。这种定制是个人偏好的问题,因此是可选的步骤。关于要传递的超参数,请查看文档

*def cross_validation(param_dict, DM_train, metrics, num_boost_round, early_stopping_rounds): 

    cv_results = xgb.cv(
        params=param_dict,
        dtrain=DM_train, 
        num_boost_round=num_boost_round,
        seed=42, # seed for randomization
        nfold=5, # k of k-fold cross-validation
        metrics=metrics,
        early_stopping_rounds=early_stopping_rounds   
    )

    return cv_results*

成对网格搜索:pair_wise_gridsearch_CV

现在,为了定义成对网格搜索,我们可以定义一个函数,在超参数对的搜索网格的所有数据点上迭代 k 重交叉验证自定义函数 cross_validation()。下一个函数,pair _ wise _ grid search _ CV*采用 3 个变量:*

  • param1_name 和 param1_name:要优化的超参数对的名称。
  • gridsearch_params:要调整的超参数对的搜索网格。
*def pair_wise_gridsearch_CV(param1_name, param2_name, gridsearch_params):min_mae = float("Inf")
    best_params = Nonex = []
    y = []
    z = []
    for param1, param2 in gridsearch_params:
        # Update our parameter dictionary for the tuning
        param_dict[param1_name] = param1
        param_dict[param2_name] = param2
        print("CV with {}={}, {}={}".format(param1_name, param1,
                             param2_name,
                             param2))
        # calculate cross_validation
        cv_results = cross_validation(param_dict, DM_train, metrics={'mae'}, num_boost_round=1000, early_stopping_rounds=10)
        mean_mae = cv_results['test-mae-mean'].min()
        boost_rounds = cv_results['test-mae-mean'].argmin()

        x.append(param1)
        y.append(param2)
        z.append(mean_mae)

        print("\tMAE {} for {} rounds".format(mean_mae, boost_rounds))

        if mean_mae < min_mae:
            min_mae = mean_mae
            best_params = (param1, param2)

return x, y, z, min_mae, best_params*

函数 pair_wise_gridsearch_CV 在 gridsearch_params 中定义的超参数对的搜索网格上的所有数据点上迭代自定义交叉验证函数 cross_validation,以搜索性能前景的全局最小值。

gridsearch_params 需要在每次成对超参数调整之前预先确定。这是后面要展示的。

b)超参数对的性能景观的 3D 可视化

现在,我们可以定义一个可视化实用函数 Trisurf() 来呈现超参数的性能景观的 3D 可视化。我们将使用 matplotlib 的plot _ tris URF()**

plot _ tri surf()使用三角插值技术填充搜索网格上的数据点之间的间隙,以渲染性能景观的平滑表面。**

**import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3Ddef Trisurf(xname, yname, x, y, z, min_mae, best_params):
    # (xname, yname): the name of variables to be displayed for visualization
    # (`x`,`y`): all the hyperparamters' datapoints on the search grid 
    # (`z`): the results of the evaluation metric the results of the evaluation metric over the datapoints
    # (`min_mae`, `best_params`): the Native XGBoost API's `k-fold cross-validation` function's outputs the minimum mae result and the corresponding datapoint of the hyperparameters
    fig = plt.figure(figsize=(12, 12))
    ax = fig.add_subplot(111, projection="3d")
    # Creating color map
    my_cmap = plt.get_cmap('RdPu')

    # Creating dot plot of the minimum
    ## min_mae*1.01 to float it for the visualization purpose.
    ax.scatter(best_params[0], best_params[1], min_mae, color='red')
    # Tri-Surface Plot
    ax.plot_trisurf(x,y,z,cmap = my_cmap,
                         linewidth = 0.2,
                         antialiased = True,
                         edgecolor = 'grey')
    ax.set_xlabel(xname)
    ax.set_ylabel(yname)
    ax.set_zlabel('mae')

    # Write the Title
    ax.set_title("Hyperparameters Performance Landscape ("+str(xname) + ' & '+str(yname)+")") 

    # Download the figure as jpeg file.
    figname = str(xname) + '_'+str(yname)
    fig.savefig(str(figname) + '.jpg')**

关于代码的更多细节,请访问我的 Github repo

F.调整实现

到目前为止,我们已经准备好了调优所需的所有定制实用函数。

现在,我们准备实施调优过程。

我们将一次对每一对进行成对的超参数调整。每次成对调整后,我们将使用最佳结果(产生损失函数的最低值)更新每个超参数对的值。我们继续下一对超参数来重复这个过程。通过这种方式,我们可以逐步提高模型的性能。

现在,我们将对 4 对超参数进行这一过程。

a)第一对: max_depth 和 eta

现在,为了调整第一个超参数对 max_depth 和 eta,我们为它们设置搜索网格如下。

**pair_wise_gridsearch_params1 = [
    (max_depth, eta)
    for max_depth in range(5,15)
    for eta in [i/10\. for i in range(1, 10)]
]**

现在,让我们将这个搜索网格传递到定制函数 pair_wise_gridsearch_CV()中,以便在超参数对上调优模型。这是代码。

**x1, y1, z1, min_mae1, best_params1 = pair_wise_gridsearch_CV('max_depth', 'eta', pair_wise_gridsearch_params1)**

这是等式左侧输出变量的描述。

  • x1y1 存储搜索网格中超参数对的所有数据点;
  • z1 存储每个搜索网格数据点的性能值( x1,y1);
  • min_mae1 存储最佳性能值,损失函数的最小值。
  • best_params1 存储产生最佳性能的超参数对的搜索网格数据点。

min_mae1best_params1 是成对调优的结果。

所有这些变量都将被传递到我们刚才定义的自定义可视化实用程序函数中,以呈现性能场景的 3D 可视化。

这是结果。

**Best params: 7, 0.1, MAE: 0.2988302932498683**

输出表明,在 max_depth = 7 和 eta = 0.1 的搜索网格中,在 0.2988 处产生了最佳性能。

现在,出于可视化的目的,我们将把所有调整输出——x1、y1、z1、min_mae1 和 best _ params 1——连同超参数对的名称一起传递到自定义可视化函数中。

**Trisurf("max_depth", 'eta',x1,y1,z1, min_mae1, best_params1)**

这是搜索网格中超参数对的所有数据点的性能前景的可视化。

使用 Matplotlib 的 plot_trisurf 进行 3D 可视化:由 Michio Suginoo 制作

第一对的表演景观 max_depth 和 eta 展示了表演景观形状的合理一致性,而没有多个凹陷和凸起。这似乎表明,在这两个超参数的最佳值附近,模型性能具有合理的可预测性。

现在,让我们用成对调整的最佳结果来更新超参数对的值,即 max_depth = 7,eta = 0.1。由于这些值存储在变量 best_params1 中,我们可以更新关于 max_depth 和 eta 的超参数对的参数字典,如下所示。

**param_dict["max_depth"] = best_params1[0]
param_dict["eta"]= best_params1[1]**

这逐渐提高了模型的性能。

现在,我们可以对其他 3 个超参数对重复相同的过程来调整模型。作为预防措施,自定义函数的输出将具有不同的数字后缀,以区分不同的超参数对。然而,该过程将经历与我们刚刚经历的完全相同的步骤。

对于接下来的 3 个超参数对,我将跳过过程的细节,只显示相关的过程。

b)第二对: subsamplecolsample_bytree

接下来,我们有两个与子采样相关的超参数,subsample 和 colsample_bytree 因为两者都处理子采样,所以它们可能会展示彼此之间的一些交互。出于这个原因,我们将这一对放在一起调谐,以观察相互作用。

首先,让我们按如下方式设置搜索网格。

**pair_wise_gridsearch_params2 = [
    (subsample, colsample_bytree)
    for subsample in [i/10\. for i in range(1, 10)]
    for colsample_bytree in [i/10\. for i in range(1, 10)]
]**

现在,让我们将这个搜索网格传递到自定义函数 pair_wise_gridsearch_CV()中,以便在第二个超参数对上调整模型。

**x2, y2, z2, min_mae2, best_params2= pair_wise_gridsearch_CV('subsample', 'colsample_bytree', pair_wise_gridsearch_params2)**

这是结果。

**Best params: 0.9, 0.8, MAE: 0.2960672089507568**

输出表明,0.296 处的最佳性能是在 subsample =0.9 和 colsample_bytree =0.8 的搜索网格中产生的。正如预期的那样,性能从最后一次调优开始逐步提高,最后一次调优的性能为 0.2988。

这是超参数对的搜索网格的所有数据点上的性能前景的可视化。

使用 Matplotlib 的 plot_trisurf 进行 3D 可视化:由 Michio Suginoo 制作

第二对 subsample 和 colsample_bytree 的性能状况显示了性能状况的合理一致性,没有多次下降和起伏。这似乎表明,在这两个超参数的最佳值附近,模型性能具有合理的可预测性。

现在,让我们用成对优化的最佳结果来更新超参数对的值,即 subsample = 0.9 和 colsample_bytree = 0.8。

**param_dict["subsample"] = best_params2[0]
param_dict["colsample_bytree"]= best_params2[1]**

这再次逐步提高了模型的性能。

c)第三对:min_child_weight & gamma

接下来,第三个超参数对 min_child_weight 和 gamma 都与树的划分相关联。所以,让我们一起调一下,看看它们之间的相互作用。

首先,让我们按如下方式设置搜索网格。

**pair_wise_gridsearch_params3 = [
    (min_child_weight, gamma)
    for min_child_weight in range(0, 10)
    for gamma in range(0, 10)
]**

现在,让我们将这个搜索网格传递到定制函数 pair_wise_gridsearch_CV()中,以便在超参数对的搜索网格上的所有数据点上调整模型。

**x3, y3, z3, min_mae3, best_params3 = pair_wise_gridsearch_CV('min_child_weight', 'gamma', pair_wise_gridsearch_params3)**

这是结果。

**Best params: 3, 0, MAE: 0.29524631108655486**

输出显示,0.2952 处的最佳性能是在 min_child_weight = 3 且 gamma =0 的搜索网格处生成的。正如预期的那样,性能从最后一次调优开始逐步提高,最后一次调优的性能为 0.296。

这是超参数对的不同值的性能情况的可视化。

使用 Matplotlib 的 plot_trisurf 进行 3D 可视化:由 Michio Suginoo 制作

这两个与分区相关的超参数 min_child_weight 和 gamma 的性能状况显示了性能状况的合理一致性,没有多次下降和上升。这似乎表明,在这两个超参数的最佳值附近,模型性能具有合理的可预测性。

现在,让我们用成对调整的最佳结果来更新超参数对的值,即 min_child_weight = 3,gamma =0。

**param_dict["min_child_weight"] = best_params3[0]
param_dict["gamma"]= best_params3[1]**

d)第四对:reg_alphareg_lambda

最后,我们将对以下两个正则化超参数 reg_alpha 和 reg_lambda 进行最后的成对超参数调整。

首先,让我们按如下方式设置搜索网格。

**pair_wise_gridsearch_params4 = [
    (reg_alpha, reg_lambda)
    for reg_alpha in [0, 1e-2, 0.1, 1, 2, 3, 4, 8, 10, 12, 14]
    for reg_lambda in [0, 1e-2, 0.1, 1, 2, 3, 4, 8, 10, 12, 14]
]**

现在,让我们将这个搜索网格传递到定制函数 pair_wise_gridsearch_CV()中,以便在超参数对上调优模型。

**x4, y4, z4, min_mae4, best_params4 = pair_wise_gridsearch_CV('reg_alpha', 'reg_lambda', pair_wise_gridsearch_params4)**

这是结果。

**Best params: 0.1000, 8.0000, MAE: 0.2923583947526392**

输出表明,在 reg_alpha = 0.1 和 reg_lambda = 8.0 的搜索网格中,在 0.292358 处产生了最佳性能。正如预期的那样,性能从最后一次调优开始逐步提高,最后一次调优的性能为 0.2952。

这是超参数对的不同值的性能情况的可视化。

使用 Matplotlib 的 plot_trisurf 进行 3D 可视化:由 Michio Suginoo 制作

与其他 3 个早期的性能环境不同,reg_alpha 和 reg_lambda 的成对性能环境呈现了多个局部最小值,如图所示。它展示了一个崎岖不平的性能景观,有各种各样的下降和颠簸。有证据表明,在调整期间,模型的性能对这两个正则化超参数 reg_alpha 和 reg_lambda 的值的微小变化非常敏感。

换句话说,他们价值观的微小变化会极大地影响他们的表现结果。当我们将新类型的数据集传递到部署域中的优化模型时,这很可能会转化为模型的不稳定性。

这需要仔细研究后才能下定论。

现在,我们用 reg_alpha 和 reg_lambda 的当前最佳值更新参数字典,并在测试数据集上计算调优模型的性能。

**param_dict["reg_alpha"] = best_params4[0]
param_dict["reg_lambda"]= best_params4[1]**

G.第一次成对调整后的测试结果

因为我们已经逐段更新了参数字典 param_dict,所以它的当前值已经在所有 8 个超参数上更新了。让我们打印出来,以确认优化后超参数的更新值。

**print("Parameters after the Pair Wise Tuning:", param_dict)**

这是输出。

**Parameters after the Pair Wise Tuning: {'objective': 'reg:squarederror', 'max_depth': 7, 'subsample': 0.9, 'eta': 0.1, 'min_child_weight': 3, 'colsample_bytree': 0.8, 'gamma': 0, 'reg_alpha': 0.1, 'reg_lambda': 8, 'eval_metric': 'mae', 'gpu_id': 0, 'tree_method': 'gpu_hist'}**

现在,我们可以在测试数据集上拟合带有这些超参数的更新值的优化模型,以评估测试性能的改进。让我们将更新后的参数字典 param_dict 传递到 bias_variance_performance()的自定义函数中。

**pw_model_1, pw_evals_result_1=bias_variance_performance(param_dict)**

这是结果。

**Best MAE after the Pair-Wise Tuning: 0.2903 with 315 rounds**

第 315 轮看结果吧。

**[314]	Train-mae:0.15790	Test-mae:0.29034**

在 315 轮迭代时确定调整后的模型(具有调整后的超参数设置)。它在训练数据集上的性能为 0.1579,而在测试数据集上的性能为 0.29034。

与预调优的模型性能(在训练数据集上为 0.17322,而在测试数据集上为 0.3103)相比,有所提高。

这是训练数据集和测试数据集上的性能曲线的可视化。

制作人:杉尾道夫

结果在 偏差-方差权衡 中产生了轻微的改进。

H. 多个局部最小值和模型不稳定性: 关于reg_alphareg_lambda的高度敏感的模型性能

重复地,这两个正则化参数 reg_alpha 和 reg_lambda 的调整过程产生了具有各种下降和上升以及多个局部最小值的粗糙性能状况。有证据表明,在调整过程中,性能对这两个正则化超参数值的微小变化非常敏感。它们值的微小变化会显著影响它们性能的结果,即目标函数(损失函数)的值。

现在,我们至少可以更仔细地观察当前的性能状况,并评估模型对超参数对值的微小变化的敏感性。

  • 首先,我们可以根据性能指标对前 10 个调优结果进行排名,并确定前 10 个数据点。
  • 第二,根据对前 10 个调优结果的观察,我们可以对性能状况进行切片,以便更仔细地了解其一致性或性能稳定性。

a)检查前 10 个最低性能值,mae

下一个排序的数据帧按照 mae 评估度量值的升序排列前 10 个性能数据点。

现在,当我们看到 reg_alpha 时,前 10 个调整结果数据点沿着 reg_alpha 分布在(0.1,4.0)范围内。

对于 reg_lambda ,它的前 10 个排名值分散在一个很大的范围内(0.00 到 12)。

为了更好地了解 3D 性能,我们可以从以下几个方面进行分析:

  • reg_alpha = 0.1
  • reg_alpha = 3.0
  • reg_lambda = 8.0

所有这些值都出现在列表中的前 2 个性能数据点中。这是他们的分段性能曲线:

制作人:杉尾道夫

制作人:杉尾道夫

制作人:杉尾道夫

例如,在(reg_alpha = 3.0)处的性能状况的第二部分确定了在(reg_lambda =0.01)和(reg_lambda =1.0)之间形成的严重深渊。

与搜索网格的任何其他部分相比,沿(reg_lambda =0.01)、(reg_lambda =0.1)和(reg_lambda = 1)设置的更精细的局部搜索网格捕捉到了深渊形状的强烈凹凸纹。换句话说,如果我们在搜索网格中错过了(reg_lambda =0.1)的单个数据点,我们将无法捕捉到(reg_lambda =0.01)和(reg_lambda =1.0)之间的局部最小值的存在。

搜索网格设置中更精细的粒度可能会揭示更粗糙的性能环境。也就是说,通过更精细的搜索网格设置,我们可能会发现更多的凹陷和凸起。它可能会发现许多其他隐藏的局部最小值的存在,这些最小值在当前的性能环境中是不可见的。甚至它可以在当前搜索网格无法捕捉到的地方发现最佳性能数据点。

b)第二轮成对调谐reg_alphareg_lambda

现在,让我们定义一个新的更细粒度的搜索网格。这是新的搜索网格设置。

**pair_wise_gridsearch_params4 = [
    (reg_alpha, reg_lambda)
    for reg_alpha in [0, 1e-2, 2e-2, 3e-2, 0.1, 0.5, 1, 1.5, 1.8, 1.9, 2, 2.1, 2.2, 2.5, 3, 3.5, 4]
    for reg_lambda in [0, 1e-2, 2e-2, 3e-2, 0.1, 0.5, 1, 1.5, 1.8, 1.9, 2, 2.1, 2.2, 2.5, 3, 4, 5, 6, 
                       7, 7.5, 8, 8.5, 9, 9.5, 10, 10.5, 11, 11.5, 12, 12.5, 13, 13.5, 14, 14.5, 15, 15.5]
]**

我们可以使用上面的新搜索网格设置,在 reg_alpha 和 reg_lambda 对上再次运行成对调整。

下面是第二次调优的结果:

**Best params: 0.5000, 13.5000, MAE: 0.29122008437950175**

输出表明,在 reg_alpha = 0.5 和 reg_lambda =13.5 的搜索网格中,在 0.29122 处产生了最佳性能。正如所推测的,更精细的搜索网格粒度在与第一个结果完全不同的位置识别出了更好的最佳性能数据点,其 reg_alpha = 0.1 和 reg_lambda = 8.0,这产生了 0.292358 的性能。

这是使用新的搜索网格设置的性能前景的可视化。

使用 Matplotlib 的 plot_trisurf 进行 3D 可视化:由 Michio Suginoo 制作

正如推测的那样,第二次调优捕捉到了一个更崎岖的性能景观,有更多的下降和颠簸。正如所推测的,更精细的搜索网格粒度确定了更好的最佳性能数据点,reg_alpha = 0.5 和 reg_lambda =13.5,位于与第一个结果完全不同的位置,第一个结果是 reg_alpha = 0.1 和 reg_lambda =8.0,这产生了 0.292358 的性能。第二次调整也在 0.29122 处产生了更好的最佳性能。

一、型号选择

现在,我们能说第二次调整的模型比第一次调整的模型更好吗?

我们仍然不知道。

请记住,我们是基于训练数据集来比较结果的,训练数据集包括验证数据集。我们需要用测试数据集来测试这些调整后的模型,以评估偏差-方差权衡,即欠拟合-过拟合权衡的问题。

a)偏差-方差权衡,过度拟合的风险

现在,让我们在测试数据集上运行新调整的模型。我们可以将参数字典 param_dict 的最新更新传递给自定义函数 bias_variance_performance()。

**pw_model_2, pw_evals_result_2=bias_variance_performance(param_dict)**

这是结果。

**Best MAE after the Pair-Wise Tuning: 0.2922 with 319 rounds**

上面说最好的测试成绩,0.2922,出现在第 319 轮。

**[318]	Train-mae:0.17208	Test-mae:0.29225**

现在,让我们比较第二次调整后模型与调整前模型和第一次调整后模型的结果。

上面的比较表显示,就测试性能而言,第一次调优在三次调优中产生了最佳性能。这意味着第二次调整的更好的训练性能是在训练数据集(包括验证数据集)上过度拟合模型的结果。这是一个典型的偏差-方差权衡。

由于模型开发期间调优的总体目标是追求部署领域中的模型稳定性,因此我们需要在选择调优模型时解决偏差-方差权衡问题。

因此,我们最好选择第一次调整的结果,而不是第二次调整的结果。

b)绘制特征重要性

现在,我们想知道每个特性在定义优化模型中的贡献有多大。我们可以使用第一次调整的模型来绘制 特征重要性

**from xgboost import plot_importance
plot_importance(pw_model_1)
pyplot.show()**

这是输出。

制作人:杉尾道夫

结论

这篇文章向您介绍了本机 XGBoost API 的超参数调优的代码实现步骤。

原生 XGBoost API 使用户能够调整超参数,以应用各种正则化技术来解决偏差-方差权衡问题。这个分析迭代了内置的交叉验证函数来调整超参数。

现在,您可以为自己的项目复制或修改流程。

我将在下面的帖子中单独分享我对这个特殊案例结果的进一步观察:超参数调优期间过多多个局部最小值的风险含义

另外,如果你对 XGBoost 的快速概述感兴趣,欢迎阅读我的另一篇文章: XGBoost:它的系谱、它的架构特性和它的创新

感谢阅读这篇长文。

确认

我要感谢 TDS 的编辑团队,特别是凯瑟琳·普雷里,感谢他们在编辑阶段提供的宝贵意见。

参考

Brownlee,J. 对机器学习的梯度推进算法的温和介绍 (2016)。检索自机器学习掌握:https://Machine Learning Mastery . com/gentle-introduction-gradient-boosting-algorithm-Machine-Learning/

数据砖。xgboost-Linux 64。 (2017)。从 github 检索:https://github . com/databricks/xgboost-Linux 64/blob/master/doc/model . MD

Hunter,j .,Dale,d .,Firing,e .,Droettboom,m .,Matplotlib 开发团队。mplot3d 工具包 (2019)。从 matplotlib.org 检索:https://matplotlib . org/3 . 1 . 0/tutorials/toolkits/mplot3d . html # mpl _ toolkits . mplot3d . axes3d . plot _ tris URF

jeeteshgavande30、singghakshay 和 anikakapoor。Python 中的三面绘图使用 Matplotlib (2021)。检索自 geeks forgeeks:https://www . geeks forgeeks . org/tri-surface-plot-in-python-using-matplotlib/

莱昂 d . m .[_ California _ housing . py](https://github.com: https://github.com/scikit-learn/scikit-learn/blob/36958fb24/sklearn/datasets/_california_housing.py#L53) (2022) 检索自https://github . com:https://github . com/scikit-learn/scikit-learn/blob/36958 fb24/sk learn/datasets/_ California _ housing . py # L53

佩斯,R. K .和罗纳德·b .(1997 年)。稀疏空间自回归,统计和概率字母,33291–297。StatLib 数据集存档:http://lib.stat.cmu.edu/datasets/

sci kit-学习开发者。sk learn . datasets . fetch _ California _ housing(未注明)检索自https://sci kit-learn . org/stable/modules/generated/sk learn . datasets . fetch _ California _ housing . html

维基百科。 偏差-方差权衡 (2022) 从维基百科检索:https://en . Wikipedia . org/wiki/Bias % E2 % 80% 93 variance _ trade off #:~:text = In % 20 statistics % 20 and % 20 machine % 20 learning,Bias % 20 In % 20 估计% 20 参数。

维基百科。 【交叉验证(统计) (2022) 从维基百科检索:https://en . Wikipedia . org/wiki/Cross-validation _ % 28 statistics % 29 # k-fold _ Cross-validation

维基百科。 曲面三角剖分 (2022) 从维基百科检索:https://en.wikipedia.org/wiki/Surface_triangulation

xgboost 开发人员。 [XGBoost 文档](https://xgboost.readthedocs.io/: https://xgboost.readthedocs.io/en/stable/) (未注明)。检索自https://xgboost.readthedocs.io/:https://xgboost.readthedocs.io/en/stable/

使用 Python 评估机器学习分类器的配对 t 检验

原文:https://towardsdatascience.com/paired-t-test-to-evaluate-machine-learning-classifiers-1f395a6c93fa

作者图片

了解如何使用假设检验比较分类器

介绍

在我的上一篇文章中,我谈到了使用统计工具正确比较不同模型的重要性,以便在选择阶段选择最佳模型。

在这篇文章中,我想特别关注一个统计测试,作为一名数据科学家或机器学习工程师,你需要知道这个测试。您可以使用此测试来确定两个分类器之间是否存在统计上的显著差异,以便您实际上可以只使用最好的一个。

k 倍交叉验证

假设你有两个分类模型 A 和 B(逻辑回归,决策树等…)。
这种成对 t 检验需要使用 N 个不同的测试集来评估每个分类器。幸运的是,我们并不真的需要 N 个测试集,而是可以使用 k 倍交叉验证
这种方法允许您在几个测试集上测试一个模型,以获得更可靠的结果,即使您的数据有限。它是如何工作的?

10 倍简历(图片由作者提供)

把你的数据集分成 k 等份,例如 k = 10。现在,您可以将第一个拆分用作测试集,将另外 9 个拆分用作训练集。接下来,您将使用第二个拆分作为测试集,使用另外 9 个拆分作为训练集,依此类推。这样,您将得到 10 个结果,比如说 10 个精度,您通常可以对这些结果取平均值以得到更可靠的结果。

计算差异

成对 t 检验想要找出两个分类器之间是否存在真正的差异,所以假设我们对准确性感兴趣,我们从计算两个模型之间的准确性差异开始。

测试装置 i 上 A 的精度与测试装置 i 上 B 的精度配对。

Diff(A,B)(图片由作者提供)

  • 假设:精度来自正态分布(方差未知)
  • 零假设:A 和 B 的精度来自同一个分布。因此,精度差的期望值等于 0 (E[diff]=0)。基本上两个型号没什么区别。
  • 替代假设:精度来自两种不同的分布,E[diff] ≠ 0。基本上,模型实际上是不同的,所以一个比另一个好。

配对 t 检验

  • 取一个 N 次观察的样本(从 k 倍 cv 中给出)。假设这些结果来自具有固定均值和方差的正态分布。
  • 计算这些观察值的样本均值和样本方差
  • 计算 t 统计量。
  • 使用具有 N-1 个自由度的 t 分布来估计真实平均值在给定范围内的可能性。
  • 如果 t 统计不在以下区间内,则在显著性水平 p 拒绝零假设:

间隔(图片由作者提供)

你需要的配方

现在只需使用下面的简单公式按照上述步骤。

公式(作者的图像)

让我们完成我们的案子

Diff(A,B)(图片由作者提供)

  • m = ( 2 + 3 + 1 + 2)/4 = 2
  • SD=[(0+1+1+0)/(4–1)]^(1/2)= = 0.471
  • t=(^(1/2 北部)* m)/sd = 0.816

如果我们设置一个 p 值= 0.05 和一个自由度 n-1 = 3 ,我们可以在 t 表上检查分数,你可以在这里找到。

t 表(来源:https://www . sjsu . edu/faculty/gerst man/stat primer/t-table . pdf)

t = 0.816 是否在范围[-3.182,+3.182]内?是的,所以我们不能拒绝零假设,我们的两个分类器之间没有显著差异。

5x2 成本验证

当你在真实数据上运行这个测试时,你应该有至少 20 到 30 个测试集 (k 倍),否则你可以使用一个 5x2 coss 验证。这篇文章很好地解释了这个方法。但是三言两语:

在本测试中,我们进行了 5 次 2 重交叉验证。在每次复制中,可用数据被随机划分为两个大小相等的集合 S1 和 S2。每个学习算法(A 或 B)在每一组上训练,并在另一组上测试。

我们来编码吧!

让我们导入 iris 数据并定义两个分类器,一个逻辑回归(LR)和一个 max_depth = 1 的决策树分类器(DT)。我们想证明 LR 的性能明显优于 DT。让我们从训练这两个模型开始,看看它们的准确性。

训练两个模型

output:
Model A accuracy: 95.56% 
Model B accuracy: 64.44%

我想将显著性水平(alpha)设置为 0.05。我想非常确定这些模型在统计学上是不同的。如果函数返回的 p 值小于α,我们可以成功地拒绝零假设。

运行配对 t 检验

output:t statistic: 7.936 
aplha  0.05 
p value: 0.001 Reject null hypotesis

最后的想法

假设检验为做出关于感兴趣人群的数据决策提供了一个可靠的框架。它有助于研究人员成功地将数据从样本外推至更大的人群。

比较一次在不同模型上得到的结果来选择哪一个是最好的,从来都不是一个好方法。统计测试允许我们客观地陈述一个模型是否表现得更好。每次你运行实验时,统计测试被用来表明你所做的比以前存在的更好,只要去阅读任何机器学习论文。这显然不是存在于下一篇文章中的唯一测试,我将继续更详细地研究 McNemar 测试!

结束了

马赛洛·波利蒂

LinkedinTwitterCV

熊猫和 Python 数据科学和数据分析的提示和技巧

原文:https://towardsdatascience.com/pandas-and-python-tips-and-tricks-for-data-science-and-data-analysis-1b1e05b7d93a

用这些熊猫和蟒蛇的把戏让你的效率更上一层楼!

安德鲁·尼尔Unsplash 上拍照

动机

这个博客在我的 LinkedIn 页面的基础上,重新组合了我分享的所有熊猫和蟒蛇的技巧和窍门。我决定把它们集中在一个博客里,通过轻松找到你想要的东西来帮助你充分利用你的学习过程。

内容分为两个主要部分:

  • 熊猫技巧和提示只与熊猫有关。
  • Python 技巧&与 Python 相关的技巧。

如果你更喜欢视频,你可以开始在我的 YouTube 频道上观看我关于这些技巧的系列,以获得更多的互动性。每个视频一次包含两到三个技巧。

熊猫的技巧和窍门

本节提供了所有技巧的列表

1. 𝗖𝗿𝗲𝗮𝘁𝗲 𝗮 𝗻𝗲𝘄 𝗰𝗼𝗹𝘂𝗺𝗻 𝗳𝗿𝗼𝗺 𝗺𝘂𝗹𝘁𝗶𝗽𝗹𝗲 𝗰𝗼𝗹𝘂𝗺𝗻𝘀 𝗶𝗻 𝘆𝗼𝘂𝗿 𝗱𝗮𝘁𝗮𝗳𝗿𝗮𝗺𝗲.

执行简单的算术任务(比如创建一个新列作为其他两列的和)可能很简单。

🤔但是,如果您想要实现一个更复杂的函数,并将其用作列创建背后的逻辑,该怎么办呢?这就是事情变得有点挑战性的地方。

猜猜看…

✅ 𝙖𝙥𝙥𝙡𝙮和𝙡𝙖𝙢𝙗𝙙𝙖可以帮助您使用以下格式轻松地将任何逻辑应用到您的列中:

𝙙𝙛[𝙣𝙚𝙬_𝙘𝙤𝙡] = 𝙙𝙛.𝙖𝙥𝙥𝙡𝙮(𝙡𝙖𝙢𝙗𝙙𝙖 𝙧𝙤𝙬: 𝙛𝙪𝙣𝙘(𝙧𝙤𝙬), 𝙖𝙭𝙞𝙨=1) 

其中:

➡ 𝙙𝙛是你的数据框架。

➡ 𝙧𝙤𝙬将对应于数据框中的每一行。

➡ 𝙛𝙪𝙣𝙘是要应用于数据框的函数。

➡ 𝙖𝙭𝙞𝙨=1 将函数应用于数据框中的每一行。

💡下面是一个插图。

candidate _ info函数组合每个候选人的信息,以创建关于该候选人的单个描述列。

熊猫申请和 lambda 的结果(图片由作者提供)

2.将分类数据转换成数字数据

这个过程主要发生在特征工程阶段。它的一些好处是:

  • 识别数据中的异常值、无效值和缺失值。
  • 通过创建更健壮的模型来减少过度拟合的机会。

➡用熊猫的这两个功能,看你的需要了。下图提供了示例。

1️⃣ .𝙘𝙪𝙩()来具体定义您的装订边。

𝙎𝙘𝙚𝙣𝙖𝙧𝙞𝙤
根据候选人的专业知识和经验数量对其进行分类,其中:

  • 入门级:0-1 年
  • 中级:2-3 年
  • 高年级:4-5 年

的结果。剪切功能(作者图片)

2️⃣ .𝙦𝙘𝙪𝙩()将您的数据分成大小相等的箱。
它使用数据分布的基本百分位数,而不是条块的边缘。

𝙎𝙘𝙚𝙣𝙖𝙧𝙞𝙤:将候选人的通勤时间分为𝙜𝙤𝙤𝙙、𝙖𝙘𝙘𝙚𝙥𝙩𝙖𝙗𝙡𝙚或𝙩𝙤𝙤 𝙡𝙤𝙣𝙜.

的结果。qcut 函数(图片由作者提供)

𝙆𝙚𝙚𝙥 𝙞𝙣 𝙢𝙞𝙣𝙙 💡

  • 使用时。𝙘𝙪𝙩():箱数量=标签数量+ 1。
  • 使用时。𝙦𝙘𝙪𝙩():箱柜数量=标签数量。
  • 与。设置,否则,最低值将转换为 NaN。

3.基于列值从 Pandas 数据框架中选择行

➡使用。𝙦𝙪𝙚𝙧𝙮()函数通过指定过滤条件。

➡过滤表达式可以包含任何运算符(,==,!=,等等。)

➡使用@̷符号在表达式中使用变量。

根据列值从 Pandas 数据框架中选择行(图片由作者提供)

4.处理 zip 文件

有时候读和写会很有效率。zip 文件,而不从本地磁盘中提取它们。下面是一个插图。

5.选择𝗮𝘀𝘂𝗯𝘀𝗲𝘁𝗼𝗳𝘆𝗼𝘂𝗿𝗣𝗮𝗻𝗱𝗮𝘀𝗱𝗮𝘁𝗮𝗳𝗿𝗮𝗺𝗲𝘄𝗶𝘁𝗵𝘀𝗽𝗲𝗰𝗶𝗳𝗶𝗰𝗰𝗼𝗹𝘂𝗺𝗻𝘁𝘆𝗽𝗲𝘀

你可以使用𝙨𝙚𝙡𝙚𝙘𝙩_𝙙𝙩𝙮𝙥𝙚𝙨函数。它需要两个主要参数:𝚒𝚗𝚌𝚕𝚞𝚍𝚎 𝚊𝚗𝚍 𝚎𝚡𝚌𝚕𝚞𝚍𝚎.

  • 𝚍𝚏.𝚜𝚎𝚕𝚎𝚌𝚝_𝚍𝚝𝚢𝚙𝚎𝚜(𝚒𝚗𝚌𝚕𝚞𝚍𝚎 = ['𝚝𝚢𝚙𝚎_𝟷','𝚝𝚢𝚙𝚎_𝟸',… '𝚝𝚢𝚙𝚎_𝚗'])表示我希望数据框的子集包含𝚝𝚢𝚙𝚎_𝟷、𝚝𝚢𝚙𝚎_𝟸,…、𝚝𝚢𝚙𝚎_𝚗.的列
  • 𝚍𝚏.𝚜𝚎𝚕𝚎𝚌𝚝_𝚍𝚝𝚢𝚙𝚎𝚜(𝚎𝚡𝚌𝚕𝚞𝚍𝚎 = ['𝚝𝚢𝚙𝚎_𝟷','𝚝𝚢𝚙𝚎_𝟸',… '𝚝𝚢𝚙𝚎_𝚗'])表示我希望数据框的子集不包含𝚝𝚢𝚙𝚎_𝟷、𝚝𝚢𝚙𝚎_𝟸,…、𝚝𝚢𝚙𝚎_𝚗.的列

下面的✨就是一个例证

select _ subset _ column _ types . py

列子集选择(作者图片)

6.从熊猫数据框列中删除评论

假设我想通过从申请日期列中删除注释来清除这些数据(candidates.csv)。这可以在加载熊猫数据帧时使用𝙘𝙤𝙢𝙢𝙚𝙣𝙩参数动态完成,如下所示:

➡ 𝚌𝚕𝚎𝚊𝚗_𝚍𝚊𝚝𝚊 = 𝚙𝚍.𝚛𝚎𝚊𝚍_𝚌𝚜𝚟(𝚙𝚊𝚝𝚑_𝚝𝚘_𝚍𝚊𝚝𝚊, 𝙘𝙤𝙢𝙢𝙚𝙣𝙩=’𝚜𝚢𝚖𝚋𝚘𝚕’)

在我的例子中,是𝙘𝙤𝙢𝙢𝙚𝙣𝙩='#',但也可以是任何其他字符(|,/等)。)取决于你的情况。插图是第一种情况。

✋🏽等等,如果我想为这些评论创建一个新列,但仍然将它们从申请日期列中删除,该怎么办?第二种情况是一个例子。

删除熊猫数据框中的评论(图片由作者提供)

7.从控制台以表格形式打印熊猫数据帧

❌否,对熊猫数据框应用𝚙𝚛𝚒𝚗𝚝()函数并不总是呈现易于阅读的输出,尤其是对于具有多列的数据框。

✅如果你想得到一个控制台友好的表格输出,使用。𝚝𝚘_𝚜𝚝𝚛𝚒𝚗𝚐()的功能如下所示。

8.突出显示熊猫的数据点

对熊猫数据框应用颜色是强调某些数据点以便快速分析的好方法。

✅这是𝚙𝚊𝚗𝚍𝚊𝚜.𝚜𝚝𝚢𝚕𝚎模块派上了用场。它有许多功能,但不限于以下功能:

✨ 𝚍𝚏.𝚜𝚝𝚢𝚕𝚎.𝚑𝚒𝚐𝚑𝚕𝚒𝚐𝚑𝚝_𝚖𝚊𝚡()为每列的最大值指定一种颜色。

✨ 𝚍𝚏.𝚜𝚝𝚢𝚕𝚎.𝚑𝚒𝚐𝚑𝚕𝚒𝚐𝚑𝚝_𝚖in()为每列的最小值分配一种颜色。

✨ 𝚍𝚏.𝚜𝚝𝚢𝚕𝚎.𝚊𝚙𝚙𝚕𝚢(𝚖𝚢_𝚌𝚞𝚜𝚝𝚘𝚖_𝚏𝚞𝚗𝚌𝚝𝚒𝚘𝚗)将自定义函数应用于数据框。

突出显示熊猫的数据点(图片由作者提供)

9.减少数据中的小数点

有时,数据集中很长的十进制值并不能提供重要的信息,这可能会很痛苦🤯去看看。

因此,您可能希望将数据转换为大约 2 到 3 个小数点,以便于您的分析。

✅这是你可以用𝚙𝚊𝚗𝚍𝚊𝚜.来完成的事情𝙳𝚊𝚝𝚊𝙵𝚛𝚊𝚖𝚎.𝚛𝚘𝚞𝚗𝚍()的功能如下所示。

减少数据中的小数点(图片由作者提供)

10.替换数据框中的一些值

您可能想要替换数据框中的一些信息,以使其尽可能保持最新。

✅这可以通过熊猫𝚍𝚊𝚝𝚊𝚏𝚛𝚊𝚖𝚎.来实现𝚛𝚎𝚙𝚕𝚊𝚌𝚎()的功能如下所示。

替换数据框中的一些值(图片由作者提供)

11.比较两个数据帧并获得它们的差异

有时候,在比较两个熊猫数据帧时,不仅想知道它们是否等价,还想知道如果不等价,区别在哪里。

✅这是。𝚌𝚘𝚖𝚙𝚊𝚛𝚎()的功能派上了用场。

✨它生成一个数据框,并排显示有差异的列。只有当比较的两个数据相同时,它的形状才不同于(0,0)。

✨如果要显示相等的值,请将𝚔𝚎𝚎𝚙_𝚎𝚚𝚞𝚊𝚕参数设置为𝚃𝚛𝚞𝚎.否则,它们会显示为𝙽𝚊𝙽.

比较两个数据框并获得它们的差异(图片由作者提供)

12.获取大型数据集的子集以进行快速分析

有时,我们只需要一个非常大的数据集的子集来进行快速分析。其中一种方法是在获取样本之前读取内存中的全部数据。

这可能需要大量内存,具体取决于数据的大小。此外,读取数据可能需要很长时间。

✅你可以通过指定你想要的行数来使用𝚛𝚎𝚊𝚍_𝚌𝚜𝚟(熊猫函数中的𝚗𝚛𝚘𝚠𝚜参数。

获取大型数据集的子集以进行快速分析(图片由作者提供)

13.将数据框从宽格式转换为长格式

有时,它可以是有用的𝚝𝚛𝚊𝚗𝚜𝚏𝚘𝚛𝚖𝚢𝚘𝚞𝚛𝚍𝚊𝚝𝚊𝚏𝚛𝚊𝚖𝚎𝚏𝚛𝚘𝚖𝚊𝚠𝚒𝚍𝚎𝚝𝚘𝚊𝚕𝚘𝚗𝚐𝚏𝚘𝚛𝚖𝚊𝚝,更灵活,更好的分析,特别是在处理时间序列数据。

  • 𝙒𝙝𝙖𝙩 𝙙𝙤 𝙮𝙤𝙪 𝙢𝙚𝙖𝙣 𝙗𝙮 𝙬𝙞𝙙𝙚 & 𝙡𝙤𝙣𝙜?

✨宽格式是当你有很多栏的时候。
✨长格式的另一边是当你有很多行的时候。

✅ 𝙿𝚊𝚗𝚍𝚊𝚜.𝚖𝚎𝚕𝚝()是这项任务的完美候选人。

下面是一幅插图

将数据框从宽格式转换为长格式(图片由作者提供)

14.通过忽略索引来减小熊猫数据框的大小

你知道你可以通过在保存熊猫数据框时忽略索引来减小它的大小吗?

保存文件时,✅类似于𝚒𝚗𝚍𝚎𝚡 = 𝙵𝚊𝚕𝚜𝚎。

下面是一个插图。

通过忽略索引来减小熊猫数据框的大小(图片由作者提供)

15.拼花地板代替 CSV

很多时候,我不会手动查看👀熊猫将使用 CSV 或 Excel 文件的内容进行进一步分析。

如果这是你的情况,也许你不应该使用。CSV 了,想一个更好的选择。

尤其是如果你只关心

✨处理速度

储蓄和装载的✨速度

数据帧占用的✨磁盘空间

✅在那种情况下,。如下图所示,𝙥𝙖𝙧𝙦𝙪𝙚𝙩格式是你的最佳选择。

拼花地板代替 CSV(图片由作者提供)

16.将您的数据框转换为降价

以更容易理解的方式打印数据框总是更好。

✅一种方法是使用。𝚝𝚘_𝚖𝚊𝚛𝚔𝚍𝚘𝚠𝚗()函数。

💡下面是一幅插图

17.格式化日期时间列

当加载 Pandas 数据帧时,默认情况下,日期列表示为𝗼𝗯𝗷𝗲𝗰𝘁,这不是❌的正确日期格式。

✅您可以在𝗽𝗮𝗿𝘀𝗲_𝗱𝗮𝘁𝗲𝘀参数中指定目标列,以获得正确的列类型。

日期时间格式

Python 技巧和窍门

1.用 tqdm 和 rich 创建进度条

当您想要查看给定任务的可视状态时,使用进度条非常有用。

#!pip -q install rich
from rich.progress import track
from tqdm import tqdm
import time

实现回调函数

def compute_double(x):
      return 2*x

创建进度条

丰富的进度条实现

tqdm 进度条实现

2.获取日、月、年、星期几、一年中的月份

获取日、月、年、星期几、一年中的月份(图片由作者提供)

3.列的最小和最大值

如果要获取给定列中具有最大值或最小值的行,可以使用以下函数:

✨ 𝚍𝚏.𝚗𝚕𝚊𝚛𝚐𝚎𝚜𝚝(𝙽,“𝙲𝚘𝚕_𝙽𝚊𝚖𝚎”)→基于𝙲𝚘𝚕_𝙽𝚊𝚖𝚎的顶级𝙽行

✨ 𝚍𝚏.𝚗𝚜𝚖𝚊𝚕𝚕𝚎𝚜𝚝(𝙽,“𝙲𝚘𝚕_𝙽𝚊𝚖𝚎”)→𝙽基于𝙲𝚘𝚕_𝙽𝚊𝚖𝚎的最小行

✨ 𝙲𝚘𝚕_𝙽𝚊𝚖𝚎是您感兴趣的列的名称。

最小和最大值插图(图片由作者提供)

4.忽略 pip 安装命令的日志输出

有时,当从 jupyter 笔记本安装一个库时,您可能不想让默认的𝚙𝚒𝚙 𝚒𝚗𝚜𝚝𝚊𝚕𝚕命令生成关于安装过程的所有细节。

✅您可以指定-q 或 quiet 选项来删除这些信息。

下面是一幅插图💡

pip 安装插图(作者制作的动画)

5.在单个笔记本单元中运行多个命令

感叹号“!”是从 Jupyter 笔记本上成功运行 shell 命令的基础。

然而,这种方法可能相当重复🔂当处理多个命令或者一个很长很复杂的命令时。

✅解决这个问题的更好的方法是在你的笔记本单元格的开头使用%%𝐛𝐚𝐬𝐡表达式。

💡下面是一幅插图

%%bash 语句的图示(由 Autor 制作动画)

6.虚拟环境。

一个数据科学项目可能涉及多个依赖项,处理所有这些依赖项可能有点烦人。🤯

✨一个好的做法是组织你的项目,使它可以很容易地与你的团队成员分享,并以最少的努力再现。

✅:一种方法是使用虚拟环境。

⚙️ 𝗖𝗿𝗲𝗮𝘁𝗲 𝘃𝗶𝗿𝘁𝘂𝗮𝗹 𝗲𝗻𝘃𝗶𝗿𝗼𝗻𝗺𝗲𝗻𝘁 𝗮𝗻𝗱 𝗶𝗻𝘀𝘁𝗮𝗹𝗹 𝗹𝗶𝗯𝗿𝗮𝗿𝗶𝗲𝘀.

→安装虚拟环境模块。
𝚙𝚒𝚙 𝚒𝚗𝚜𝚝𝚊𝚕𝚕 𝚟𝚒𝚛𝚝𝚞𝚊𝚕𝚎𝚗𝚟

→给环境起一个有意义的名字。𝚟𝚒𝚛𝚝𝚞𝚊𝚕𝚎𝚗𝚟[𝚢𝚘𝚞𝚛_𝚎𝚗𝚟𝚒𝚛𝚘𝚗𝚖𝚎𝚗𝚝_𝚗𝚊𝚖𝚎]

→激活你的环境。𝚜𝚘𝚞𝚛𝚌𝚎[𝚢𝚘𝚞𝚛_𝚎𝚗𝚟𝚒𝚛𝚘𝚗𝚖𝚎𝚗𝚝_𝚗𝚊𝚖𝚎]/𝚋𝚒𝚗/𝚊𝚌𝚝𝚒𝚟𝚊𝚝𝚎

→开始安装项目的依赖项。
𝚙𝚒𝚙 𝚒𝚗𝚜𝚝𝚊𝚕𝚕 𝚙𝚊𝚗𝚍𝚊𝚜

这一切都很棒👏🏼,但是…您刚刚创建的虚拟环境在您的机器上是本地的😏。

𝙒𝙝𝙖𝙩 𝙩𝙤 𝙙𝙤?🤷🏻‍♂️

💡您需要永久保存这些依赖关系,以便使用以下命令与其他人共享它们:

→ 𝚙𝚒𝚙 𝚏𝚛𝚎𝚎𝚣𝚎 > 𝚛𝚎𝚚𝚞𝚒𝚛𝚎𝚖𝚎𝚗𝚝𝚜.𝚝𝚡𝚝

这将创造𝚛𝚎𝚚𝚞𝚒𝚛𝚎𝚖𝚎𝚗𝚝𝚜.包含项目依赖项的𝚝𝚡𝚝文件。

🔚最后,任何人都可以通过运行这个命令来安装完全相同的依赖项:
→ 𝚙𝚒𝚙 𝚒𝚗𝚜𝚝𝚊𝚕𝚕 -𝚛 𝚛𝚎𝚚𝚞𝚒𝚛𝚎𝚖𝚎𝚗𝚝𝚜.𝚝𝚡𝚝

7.一次运行多个指标

Scikit 学习指标

8.将多个列表链接成一个序列

您可以使用单个 for 循环作为单个序列遍历多个列表🔂。

✅这可以通过使用 Python 𝗶𝘁𝗲𝗿𝘁𝗼𝗼𝗹𝘀模块中的𝚌𝚑𝚊𝚒𝚗() ⛓函数来实现。

列表链接

9.JSON 数据的漂亮打印

❓曾经想要以正确的缩进格式打印 JSON 数据以便更好地可视化吗?

dumps()方法的 indent 参数可用于指定格式化字符串输出的缩进级别。

漂亮地打印您的 JSON 数据

10.单元测试

你测试你的代码吗?🧪

我的意思是你执行单元测试吗?

无论您是数据科学家还是软件开发人员,单元测试都是确保实现的特性符合预期行为的重要步骤。

这无疑在许多层面上都是有益的:

✨质量更好💎代码。

当添加新特性时,✨允许更简单和更敏捷的代码。

✨降低成本💰通过节省开发时间⏳和避免错误发现的后期阶段。

✨更……

✅和𝘂𝗻𝗶𝘁𝘁𝗲𝘀𝘁一起,你可以像专家一样执行单元测试😎

下面是一幅插图💡

单元测试插图

11.迭代多个列表

当试图从这些列表中映射⛓信息时,同时迭代多个列表是有益的。

✅:我的首选方法是 Python 𝘇𝗶𝗽函数。

下面是一幅插图💡

迭代多个列表

12.嵌套 for 循环的替代方法

如果你曾经使用过嵌套循环,请举手🔁 🙋🏾‍♂️

当程序变得复杂时,这在大多数情况下是不可避免的。

然而,使用嵌套循环 get 会使你的程序更难阅读🔬并保持🙅🏽‍♂️.

✅你可以使用 Python 内置的𝗽𝗿𝗼𝗱𝘂𝗰𝘁()函数来代替。

下面是一幅插图💡

嵌套 for 循环的解决方案

13.文本预处理变得简单

文本␂␃预处理从来都不容易。

❓How 您必须编写许多函数或正则表达式来执行基本的文本预处理任务,例如:

✨修复 Unicode

✨删除网址

✨正在摆脱数字、标点符号等吗?

这些任务不仅耗时⏰而且还可能增加复杂性📈取决于文本。

✅使用𝗰𝗹𝗲𝗮𝗻-𝘁𝗲𝘅𝘁 Python 库可以带走所有的负担。

下面是一幅插图💡

文本预处理插图(图片由作者提供)

结论

感谢您的阅读!🎉 🍾

我希望你发现这个蟒蛇和熊猫的把戏列表很有帮助!在这里保持关注,因为内容会在日常的基础上用更多的招数来维护。

此外,如果你喜欢阅读我的故事,并希望支持我的写作,可以考虑成为一个媒体成员。每月支付 5 美元,你就可以无限制地阅读媒体上的故事。

欢迎在媒体推特YouTube 上关注我,或者在 LinkedIn 上问好。讨论人工智能、人工智能、数据科学、自然语言处理和人工智能是一种乐趣!

如何将 Pandas 数据帧转换成 NumPy 数组

原文:https://towardsdatascience.com/pandas-df-to-numpy-array-c1a9e7d8585f

将熊猫数据帧转换为 NumPy 数组

照片由徐世洋Unsplash 上拍摄

介绍

当处理 pandas 数据帧时,如果我们将它们转换成 NumPy 数组,有时会有好处。后者似乎更节省内存,尤其是在对数据执行一些复杂的数学运算时。

当您处理相对少量的数据(比如 50K 行或更少)时尤其如此。在涉及大量数据的情况下(比如说超过 50 万行),Pandas 通常会优于 ndarrays。不过这只是一个经验法则——在大多数情况下,根据您的特定需求和使用案例,测试两个选项并查看哪一个在性能和内存使用方面更好会更好。

在今天的简短教程中,我们将展示如何有效地将熊猫数据帧转换成 NumPy 数组。

首先,让我们创建一个示例 pandas DataFrame,我们将使用它来演示几种可能用于将其转换为 numpy 数组的不同方法。

import pandas as pd df = pd.DataFrame(
    [
        (1, 'A', 10.5, True),
        (2, 'B', 10.0, False),
        (3, 'A', 19.2, False),
        (4, 'C', 21.1, True),
        (5, 'A', 15.5, True),
        (6, 'C', 14.9, False),
        (7, 'C', 13.1, True),
        (8, 'B', 12.5, False),
        (9, 'C', 11.2, False),
        (10, 'A', 31.4, False),
        (11, 'D', 10.4, True),
    ],
    columns=['colA', 'colB', 'colC', 'colD']
)print(df)
 ***colA colB  colC   colD*** *0      1    A  10.5   True
1      2    B  10.0  False
2      3    A  19.2  False
3      4    C  21.1   True
4      5    A  15.5   True
5      6    C  14.9  False
6      7    C  13.1   True
7      8    B  12.5  False
8      9    C  11.2  False
9     10    A  31.4  False
10    11    D  10.4   True*

用熊猫。DataFrame.to_numpy()

当谈到将 pandas 数据帧转换成 NumPy 数组时,我们的第一个选择是[pandas.DataFrame.to_numpy()](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_numpy.html)方法。

**ndarray = df.to_numpy()**print(ndarray)*array([[1, 'A', 10.5, True],
       [2, 'B', 10.0, False],
       [3, 'A', 19.2, False],
       [4, 'C', 21.1, True],
       [5, 'A', 15.5, True],
       [6, 'C', 14.9, False],
       [7, 'C', 13.1, True],
       [8, 'B', 12.5, False],
       [9, 'C', 11.2, False],
       [10, 'A', 31.4, False],
       [11, 'D', 10.4, True]], dtype=object)*

并且返回对象的类型将是numpy.ndarray:

>>> type(ndarray)
<class 'numpy.ndarray'>

用熊猫。DatFrame.to_records()

这里的另一个选项是[pandas.DataFrame.to_records()](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_records.html)方法,它将把 pandas 数据帧转换成一个 NumPy 记录数组:

**recarray = df.to_records()**print(recarray)rec.array([( 0,  1, 'A', 10.5,  True), 
           ( 1,  2, 'B', 10\. , False),
           ( 2,  3, 'A', 19.2, False), 
           ( 3,  4, 'C', 21.1,  True),
           ( 4,  5, 'A', 15.5,  True), 
           ( 5,  6, 'C', 14.9, False),
           ( 6,  7, 'C', 13.1,  True), 
           ( 7,  8, 'B', 12.5, False),
           ( 8,  9, 'C', 11.2, False), 
           ( 9, 10, 'A', 31.4, False),
           (10, 11, 'D', 10.4,  True)],
          dtype=[('index', '<i8'), ('colA', '<i8'), ('colB', 'O'), ('colC', '<f8'), ('colD', '?')])

如上所述,与to_numpy()相反,to_records()方法将返回一个类型为nympy.recarray的对象:

>>> type(recarray)
<class 'numpy.recarray'>

使用 numpy.asarray()

第三个选项是[numpy.asarray()](https://numpy.org/doc/stable/reference/generated/numpy.asarray.html#numpy-asarray)方法,它将输入的 pandas 数据帧转换成一个 NumPy 数组:

import numpy as np**ndarray = np.asarray(df)**print(ndarray)array([[1, 'A', 10.5, True],
      [2, 'B', 10.0, False],
      [3, 'A', 19.2, False],
      [4, 'C', 21.1, True],
      [5, 'A', 15.5, True], 
      [6, 'C', 14.9, False],
      [7, 'C', 13.1, True],
      [8, 'B', 12.5, False],
      [9, 'C', 11.2, False],
      [10, 'A', 31.4, False],
      [11, 'D', 10.4, True]], dtype=object)

返回的对象将再次成为numpy.ndarray的实例:

>>> type(ndarray)
<class 'numpy.ndarray'>

避免使用 df.values

在较早的 pandas 版本中,将 pandas 数据帧转换成 NumPy 数组的另一种方法是通过[pandas.DataFrame.values](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.values.html#pandas.DataFrame.values)属性。但是请注意,即使是官方文档也鼓励您不要再使用它:

我们建议使用[**DataFrame.to_numpy()**](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.to_numpy.html#pandas.DataFrame.to_numpy)来代替。

这是因为该属性的行为不一致。要了解更多细节,可以阅读 0.24.0 版本的发行说明。

从历史上看,这可以用series.values来完成,但是用.values还不清楚返回值是实际的数组、它的某种转换,还是熊猫定制的数组之一(像Categorical)。

最后的想法

在今天的文章中,我们讨论了将 pandas 数据帧转换成 NumPy 数组,以及在什么情况下这样做是有益的。

此外,我们展示了如何使用pandas.DataFrame对象的to_numpy()to_records()方法以及numpy.asarray()方法将 DataFrame 转换为 ndarray。

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

https://gmyrianthous.medium.com/membership

相关文章你可能也喜欢

数据科学家的熊猫练习—第一部分

原文:https://towardsdatascience.com/pandas-exercise-for-data-scientists-part-1-b601a97ee091

一组具有挑战性的熊猫问题

奥拉夫·阿伦斯·罗特内在 Unsplash 上的照片

熊猫图书馆一直吸引着数据科学家用它来做令人惊奇的事情。毫无疑问,它是表格数据处理、操作和加工的首选工具。

因此,为了扩展您的专业知识,挑战您现有的知识,并向您介绍数据科学家中众多受欢迎的 Pandas 功能,我将介绍 Pandas 练习的第一部分。目标是增强您的逻辑能力,并帮助您用最好的 Python 包之一进行数据分析来内化数据操作。

在这里找到包含本次测验所有问题的笔记本: GitHub

目录:

1。基于另一个列表
2 对数据帧进行排序。在 DataFrame
3 中的特定位置插入一列。根据列的数据类型
4 选择列。计算每列的非 NaN 单元的数量
5。将数据帧分割成相等的部分
6。反转数据帧的行或列。重新排列数据帧
8 的列。获取数据帧的交替行
9。在任意位置插入一行
10。将函数应用于数据帧的每个单元格

作为练习,我建议你先自己尝试这些问题,然后看看我提供的解决方案。

请注意,我在这里提供的解决方案可能不是解决问题的唯一方法。你可能会得出不同的结论,但仍然是正确的。然而,如果发生这种情况,请留下评论,我会很有兴趣知道你的方法。

我们开始吧!

1。基于另一个列表对数据帧进行排序

提示:给你一个数据框。此外,您还有一个包含数据帧中特定列的所有唯一值的列表。对数据帧进行排序,使列中的值以与给定列表中相同的顺序出现。

输入和预期输出:

解决方案:

这里的想法是从给定的列表中生成一个序列。每个索引将表示字符,相应的值将指示位置。使用这个,我们可以将原始数据帧映射到生成的序列,并将其传递给 sort_values() 方法进行引用,如下所示:

另外,我们也可以使用 merge 来解决这个问题。如果你能明白这一点,请在评论中告诉我。

2.在数据帧中的特定位置插入列

提示:假设您又有了一个类似上面使用的数据帧。此外,您会得到一个列表,其大小与给定数据帧中的行数相同。任务是将给定的列表作为新列插入到数据帧的给定位置。

输入和预期输出:

解决方案:

这里,我们可以使用 insert() 方法,并将位置、列名和值作为参数传递,如下所示:

3。根据列的数据类型选择列

提示:我们都很熟悉基于行的过滤,不是吗?好吧,让我们试试别的。您的任务是从数据帧中筛选出所有符合给定数据类型的列。

投入和预期产出:

解决方案:

在这里,我们可以使用select _ dtypes()方法,传递我们需要过滤出来的数据类型,如下所示:

4.计算每列中非 NaN 单元格的数量

提示:接下来,给定一个 DataFrame(一列或多列有 NaN 值),需要打印每列非 NaN 单元格的个数。

输入和预期输出:

解决方案:

这里,我们可以使用 count() 方法来获得结果:如下所示:

5。将数据帧分割成相等的部分

提示:给定一个数据帧,你的任务是将该数据帧分割成给定数量的相等部分。

输入和预期输出:

解决方案:

在这里,我们将使用 NumPy 的 split() 方法,并将零件数作为参数传递,如下所示:

6.按行或按列反转数据帧

提示:接下来,考虑您有一个类似于我们上面使用的数据帧。你的任务是按行或列翻转整个数据帧。

输入和预期输出:

解决方案:

我们可以使用 loc (或 iloc )并使用“::-1”指定反向分度方法,如下所示:

7.重新排列数据帧的列

提示:在这个练习中,给你一个数据框。此外,您还有一个指定列在数据帧中出现的顺序的列表。给定列表和数据框架,按照列表中指定的顺序打印各列。

投入和预期产出:

解决方案:

与上面类似,我们可以使用 iloc 选择所有行,并指定列表中给定的列的顺序,如下所示:

8.获取数据帧的交替行

提示:接下来,给定一个数据帧,您需要从数据帧的第一行开始打印每一个交替的行。

输入和预期输出:

解决方案:

这个解决方案也和上面两个类似。这里,在定义切片部分时,我们可以将切片的步骤指定为 2,如下所示:

9.在任意位置插入一行

提示:与前面的任务类似,您会看到相同的数据帧。您的任务是在数据帧的特定索引处插入一个给定的列表,并重新分配索引。

输入和预期输出:

解决方案:

给定一个插入位置,首先将新行分配给给定索引和前一个索引之间的一个索引。这就是赋值语句要做的事情。接下来,我们对索引上的数据帧进行排序。最后,我们重新分配索引以消除基于浮点的索引值。

10.将函数应用于数据帧的每个单元格

提示:最后,您需要给整个数据帧应用一个给定的函数。给定的数据帧仅由整数值组成。任务是通过函数将每个条目加 1。

投入和预期产出:

解决方案:

这里我们不使用 apply()方法,而是使用如下所示的 applymap() 方法:

这个小测验到此结束,我希望你喜欢尝试这个。让我知道你答对了多少。另外,如果你没有注意到,这个小测验可以在 Jupyter 笔记本上找到,你可以从这里下载。

另外,不要走开,因为我打算很快发布更多的练习。感谢阅读。

数据科学家的熊猫练习—第二部分

原文:https://towardsdatascience.com/pandas-exercise-for-data-scientists-part-2-4d532cfc00bf

一组具有挑战性的熊猫问题

艾伦·德·拉·克鲁兹在 Unsplash 上的照片

熊猫图书馆一直吸引着数据科学家用它来做令人惊奇的事情。毫无疑问,它是表格数据处理、操作和加工的首选工具。

因此,为了扩展您的专业知识,挑战您现有的知识,并向您介绍数据科学家中众多受欢迎的 Pandas 功能,我将介绍 Pandas 练习的第二部分。你可以在这里找到熊猫练习的第一部分:

目标是增强您的逻辑能力,并帮助您用一个最好的 Python 包进行数据分析,将数据操作内在化。

在这里找到包含本次测验所有问题的笔记本: GitHub

目录:

1。DataFrame
2 中一列的累计和。为每个组分配唯一的 id
3。检查一列是否有 NaN 值
4。将一个列表作为一行追加到数据帧
5。获取第一行中每一列的唯一值
6。识别熊猫合并
中每一行的来源 7。从数据帧
8 中过滤 n 个最大值和 n 个最小值。将分类数据映射到唯一的整数值
9。给每个列名添加前缀
10。将分类列转换为一个热点值

作为练习,我建议你自己尝试这些问题,然后看看我提供的解决方案。

请注意,我在这里提供的解决方案可能不是解决问题的唯一方法。你可能会得出不同的结论,但仍然是正确的。然而,如果发生这种情况,请留下评论,我会很有兴趣知道你的方法。

我们开始吧!

1.数据帧中一列的累积和

提示:给你一个数据框。您的任务是从 integral 列生成一个新列,该列表示该列的累积和。

投入和预期产出:

解决方案:

在这里,我们可以对给定的序列使用 cumsum() 方法,得到如下所示的累计和:

P.S .能不能也试试累计积,累计最大,累计最小?

2.为每个组分配唯一的 id

提示:接下来,您有一个数据帧,其中一列有重复值。你的任务是生成一个新的序列,以便每个组都有一个唯一的编号。

输入和预期输出:

下面, col_A 中的值“A”在新系列中被赋予值 1 。此外,对于每次出现的“A”,group _ num列中的值始终为 1

解决方案:

这里,在 group_by 之后,可以使用如下所示的grouper . group _ info方法:

3.检查列是否有 NaN 值

提示:作为下一个问题,你的任务是判断一列中是否存在 NaN 值。不需要找出 NaN 值的个数什么的,只需要列中是否有一个或多个 NaN 值。

输入和预期输出:

解决方案:

在这里,我们可以对序列使用 hasnans 方法来获得所需的结果,如下所示:

4.将列表作为一行追加到数据帧中

提示:大家都知道如何将元素推送到 python 列表中(在列表上使用 append 方法)。但是,您曾经向数据帧追加过新行吗?对于下一个任务,您将获得一个数据帧和一个列表,该列表应作为新行追加到数据帧中。

输入和预期输出:

解决方案:

这里,我们可以使用 loc 并将新行分配给数据帧的新索引,如下所示:

5.获取列中每个唯一值的第一行

提示:给定一个数据帧,你的任务是获得列 col_A 中每个唯一元素第一次出现的整行。

输入和预期输出:

解决方案:

这里,我们将对给定的列使用 GroupBy,并获取第一行,如下所示:

6.识别熊猫合并中每一行的来源

提示:接下来,考虑你有两个数据帧。您的任务是将它们连接起来,使输出包含一个表示原始数据帧中行的来源的列。

输入和预期输出:

解决方案:

我们可以使用 合并 的方法,将指标实参作为传递,如下所示:

7.从数据帧中过滤 n 个最大值和 n 个最小值

提示:在这个练习中,给你一个数据框。您的任务是获取整个行,其在 col_B 中的值属于该列的 top- k 条目。

投入和预期产出:

解决方案:

我们可以使用 nlargest 方法,并从指定的列中传递我们需要的顶部值的数量:

与上面的方法类似,可以使用 nsmallest 方法从列中获取前 k 个最小值。

8.将分类数据映射到唯一的整数值

提示:接下来,给定一个 DataFrame,您需要将列的每个惟一条目映射到一个惟一的整数标识符。

输入和预期输出:

解决方案:

使用 pd.factorize 方法,您可以生成表示给定列的基于整数的编码的新序列。

9.为每个列名添加前缀

提示:与前面的任务类似,您会得到相同的数据帧。您的工作是重命名所有的列,并添加“pre _”作为所有列的前缀。

输入和预期输出:

解决方案:

这里,我们可以使用 add_prefix 方法,将我们想要的字符串作为前缀传递给所有列名,如下所示:

10.将分类列转换为一个热点值

提示:最后,在数据帧中给出一个分类列。你需要把它转换成一个热点值。

输入和预期输出:

解决方案:

在这里,我们可以使用 get_dummies 方法,并将序列作为参数传递,如下所示:

这个小测验到此结束,我希望你喜欢尝试这个。让我知道你答对了多少。另外,如果你没注意到,这个小测验可以在 Jupyter 笔记本上找到,你可以从这里下载。

另外,不要走开,因为我打算很快发布更多的练习。感谢阅读。

Pandas for One-Hot 编码数据防止高基数

原文:https://towardsdatascience.com/pandas-for-one-hot-encoding-data-preventing-high-cardinality-16148a0ca399

使用 Pandas 一键编码变量的简单技术

尤金尼奥·马佐尼Unsplash 上的照片

介绍

数据清理是必要的。我相信我们大多数人都同意这一点。在我们进入建模部分之前,一个项目通常会从一些探索和清理开始。

实际上,我会说数据科学家的大部分工作是在清理和转换数据集之间完成的。

这个快速教程要解决的问题是我们处理变量编码。大多数机器学习算法期望数字而不是文本来估计一些东西。毕竟,计算机是依赖数字作为主要语言的逻辑机器。

也就是说,当我们得到一个包含分类变量的数据集时,我们可能需要将其转换为数字,这样我们就可以为算法提供转换后的数据。

一种常用的转换是热编码[OHE],它将类别转换成二进制值。看下一张图。第一行是类别 A,所以在 OHE 之后,它变成三列,其中 A 是正的(1),B/C 是负的。下一个是类别 B 的行。因为 B 现在是正数,所以它得到 1,其他的得到 0。它适用于我们拥有的所有类别

一个热点编码图。图片由作者提供。

在某些情况下,转换非常简单,但在其他一些时候,当我们有太多的类别时,它会使我们的数据集非常宽,在一次热编码后会有太多的变量。这就是我们接下来要讨论的。

高基数

看看这个随机创建的数据集。

本练习的数据集。图片由作者提供。

让我们看看分类变量cityproduct_dsc的唯一值的数量。

# Unique values for city
df.city.nunique()
[OUT]: 24

# unique values for product
df.product_dsc.nunique()
[OUT]: 21

请注意大量不同的城市和产品。如果我们对这个数据集使用 OHE,我们将得到一个非常大的数据集。观察。

# One Hot Encoding
pd.get_dummies(df, drop_first=True).head()

45 个变量太多,可能会影响 ML 模型。图片由作者提供。

即使使用参数drop_first =True来丢弃每个变量的第一个类别,我们仍然得到 45 个变量,这可能太多了,并可能影响模型的性能。

(旁注:删除编码类别的第一列是可以的。考虑一下之前 ABC 的例子。如果我们删除编码列 A,当我们看到一个 B=0,C=0 的观测值时,只能说明这个观测值是 A=1。)

具有高基数(太多变量)的数据集可能会受到 维数灾难 的影响,这表明当我们有一个高维数据集时,它会变得稀疏(太多的零),因此对更多数据的需求会以指数形式增长,达到我们无法再实现理想观察数的程度。

那么,我们如何解决这个问题呢?

这就是我们接下来将看到的,一个简单的解决方案在许多情况下都是有效的。

简单溶体

让我们回到数据集,看看城市和产品的计数。

# Counting by city
df.city.value_counts()

# Counting by product
df.product_dsc.value_counts()

这产生了结果…

不同城市和产品的计数。图片由作者提供。

我们可以看到只有 3 个城市和 3 个产品制造了大约。90%的数据。因此,在这种情况下,这里提出的简单解决方案是使用 Pandas 来减少类别的数量,只保留那些最高值,并将其他所有内容收集在“其他”桶下。

# List top 3
city_top3 = df.groupby('city').city.count().nlargest(3).index
prod_top3 = df.groupby('product_dsc').product_dsc.count().nlargest(3).index

# Keep top 3 as is and the rest as "Other"
df['city2'] = df.city.where(df.city.isin(city_top3), other='Other')
df['product2'] = df.product_dsc.where(df.product_dsc.isin(prod_top3), other='Other')

让我们来看一个到目前为止得到的数据样本。

转换后的结果数据。图片由作者提供。

接下来,我们可以丢弃旧变量cityproduct_dsc,我们准备再次使用 OHE。

# Drop old city and product
df2 = df.drop(['city', 'product_dsc'],axis=1)

# One Hot Encoding
pd.get_dummies(df2)

减少类别数量后的 OHE。图片由作者提供。

请注意,我们将 OHE 之后的变量数量从 45 列减少到只有 10 列!我们仍然可以使用参数drop_first=True,或者,也许使用一个更好的解决方案,即删除“其他”列,因为无论如何这些列都是零。毕竟,我们知道,如果城市不是纽约、罗马或巴黎,它只能是“其他”。同样的逻辑也适用于产品。

在你走之前

下面是我们所学内容的总结:

  • 一种热编码有助于将分类数据转换成数字。
  • 在包含太多变量的数据集中使用 OHE 会创建一个很大的数据集。
  • 太宽的数据可能会遭遇维数灾难,从而危及模型的性能。
  • 一个简单的解决方案是使用熊猫.groupby().nlargest().where()来减少 OHE 之前的类别数量。

这篇文章的代码在 GitHub 中:

https://github.com/gurezende/Studying/blob/master/Python/Pandas/OHE_Dimensionality.ipynb

如果你喜欢这些内容,请关注我的博客或在 LinkedIn 上找到我,在那里我也分享了一些数据科学技巧。如果你正在考虑中等会员,这是我给你的推荐代码

http://gustavorsantos.medium.com/

参考

熊猫归零地——选择与投影

原文:https://towardsdatascience.com/pandas-ground-zero-selection-and-projection-3827f74ae6c5

Python 熊猫数据框架和系列的数据索引、选择和投影的核心概念

Unsplash 上的 Max Delsid 拍摄的照片

朋友们,有抱负的数据分析师、崭露头角的数据科学家和 Python 新秀们,请听我说……

动机

本章的目的是描述熊猫数据框架的一些核心概念,这些概念是数据选择和预测的基础。我们将在此尝试回答的典型问题有:

  • 如何索引数据帧以支持访问?
  • 数据帧的基本组成部分是什么?
  • 如何从数据帧中访问数据:切片和切块?
  • 如何编写更新数据帧中特定行或列的函数?

我们在某个时候会用到这些概念,了解这些基础知识对我们打下坚实的基础确实有所帮助。

首先是踏脚石—索引和轴

让我们从定义开始:

系列 是一个 一维 标签数组,能够存放任何类型的数据(整数、字符串、浮点、python 对象等)。).将 标签 统称为 索引

一个 数据帧 是一个 二维数据结构 ,即数据以表格方式按行和列对齐,因此有两个 — row

毫无疑问,这对读者来说并不新鲜!!但是让我们后退一步,看看上面的一些具体的单词:

  • 轴:系列轴是行指针,数据框轴是行和列指针。
  • 索引:一种特殊类型的列表,存储指向每一行的指针。

让我们探索这些概念,以便理解如何访问这些数据结构。

指数——核心概念:

数据结构 Series 和 DataFrame 都被索引为。Pandas 使用一种称为索引的特殊序列来指向这些数据结构中的一组特定数据。让我们看一些索引创建的例子,以及为什么它是特殊的。

关于指数的重要提示:

  • 根据索引中包含的元素的数据类型,索引可以有多种类型。代码片段显示了三种类型的索引,但是可以有许多其他类型的索引,如在文档中提到的。
  • 这些指数有什么特别之处?我们可以通过为我们想要的元素的位置(索引)指定布尔值来从索引中选择元素。这就是上面代码片段中“选择器”的作用——如果选择器在位置 I 有一个 True 值,那么在索引中位置 I 的元素被选中。
  • 选择器数组必须是与索引大小相同的布尔数组。它可以是硬编码的,或者由如上所示的布尔函数来确定。

轴和索引:

显然,系列有一个引用其行的轴,而数据帧有两个轴——一个用于行,一个用于列。所有这些轴都是不同类型的索引。让我们看看它们是什么,以及它们与索引的关系。

关于轴和索引的重要提示:

  • 序列的(行)轴由 RangeIndex 表示(参见它是一个包含开始、停止和步进值的范围)。
  • DataFrame 有两个轴——行轴由 RangeIndex 表示,就像 Series 一样,还有一个附加索引——Columns——它是一个基本索引类型,具有列名。
  • 可以根据需要修改、设置或重置索引。

访问数据帧中的行和列

很好地理解了索引和轴的概念后,我们现在可以继续探索如何访问数据帧的元素。

使用本机索引支持

下图显示了如何使用df[ <something> ]形式的选择器访问数据帧

如果是一个选择器的布尔列表,其大小与数据帧的索引大小相同,那么 Pandas 理解使用前面部分描述的索引选择技术来请求行选择。

如果只是一个列名,Pandas 只是创建一个序列,该列的索引与 DataFrame 的索引相同。

如果是一个列名列表,Pandas 认为它是数据帧中请求的列的子集,没有对行进行任何过滤。

作者创建的图像

使用带标签的索引定位器:loc 和 iloc

Pandas 在其 DataFrame 对象中使用两个强大的索引定位器——loc 和 iloc——来选择行或项目列。

定位:通过标签定位

语法是:df.loc [<row_labels>,<column_labels>]

row_labels 可以是下列之一:

  • 与数据帧索引长度相同的布尔选择器:这可用于行选择。布尔选择器通常由一些过滤函数生成
  • 要检索的单个索引值或实际索引值列表。这些应该是实际的索引标签。
  • 形式为 start:end 的行索引值范围(slice 对象)

column_labels 是可选的。如果未指定任何内容,它将列出所有列。否则,如果指定了某个内容,它可以是以下内容之一:

  • 长度小于用于列选择的数据帧列索引(列计数)长度的布尔选择器。
  • 要检索的单个列名或列名列表。这些应该是实际的列名。
  • 格式为 start:end 的列索引值范围(slice 对象)

iloc:按位置定位(基于整数的位置)

语法是:df.iloc [<row_locations>,<column_locations>]

通常,使用行的默认 RangeIndex,loc 可以做得很好,如上所示。

但是,如果数据帧的索引不是默认的 RangeIndex,而是数据帧中的某个其他列,如时间戳索引或某个唯一的 ID,则很难记住行标签。因此,有时索引数据帧就像编程语言中的数组一样变得很重要:df[i , j]这有助于基于位置的检索。iloc 前来救援。iloc 的一个很好的用途是基于行子集上的过滤器更新数据帧——我们稍后会检查它。

数据框架“工具包”

基本操作

下面是对数据帧及其数据类型进行的一些关键操作

关于基本操作的重要提示:

  • 数据帧的一列可以被投影为一个系列(具有相同的索引)或一个具有单列的数据帧(具有相同的索引)。
  • 当投影为类似df['RecruitmentSource'],的系列时,可以应用不同的函数来生成新的系列(如 value_counts)或一些标量(nunique)。
  • 可以使用参数更改函数的默认行为,比如包含 NaN 和排序顺序。

字符串操作

对 Object(String)类型的列的操作有时需要一个称为 String 访问器的特殊对象,它允许 Python 对一系列元素进行字符串操作。

管柱作业重要注意事项:

  • 当一个字符串列像df['RecruitmentSource'],一样被投影为一个序列时,常规的 Python 字符串函数不能直接应用于每个元素;相反,需要一个字符串访问器来将序列中的每个元素转换成字符串,并使它们适合字符串操作。

两个强大的函数— any()和 all()

数据帧有两个非常强大的过滤/检查数据的功能:

any() —返回任何元素是否为真,潜在地在一个轴上。返回 False,除非在一个序列中或沿着 DataFrame 轴至少有一个元素为真或等效(例如非零或非空)**

all() —返回所有元素是否为真,潜在地在一个轴上。返回 True,除非序列中或 DataFrame 轴上至少有一个元素为 False 或等效(例如零或空)。**

这里重要的一点是:“潜在轴上”****

any()和 all()都有一个名为 axis 的参数。根据文件:

*****axis***{0 or ‘index’, 1 or ‘columns’, None},* ***default 0*** Indicate which axis or axes should be reduced.**0** / **‘index’** : reduce the index, return a Series whose index is the original column labels.
**1 / ‘columns’** : reduce the columns, return a Series whose index is the original index.
**None** : reduce all axes, return a scalar.***

这一点解释如下。

让我们看一些例子:

关于 any()和 all() 的重要提示:

  • any()和 all()验证某个轴上的真值。要扫描的轴(行或列)可以指定为参数。
  • any()遵循 OR 逻辑,而 all()遵循轴的真值的 AND 逻辑。
  • any()和 all()都返回一个序列。对于轴=0,系列的索引有列名,而对于轴= 1,系列的索引是行索引。
  • 在上面的例子中,df.isnull()用于生成真值,但是它可以扩展到任何行/列子集上的任何函数,只要该函数测试某些条件并生成真值。

对元素应用函数—使用 lambda 应用()

apply()是 Series 和 DataFrame 的另一个强大方法,用于对其元素应用函数。最简单的形式是这样的:

***# Define a function* 
def my_function(x):
    do something with x*#* ***Apply to DataFrame*** *- apply this function to all elements of df*
df.apply(my_function)   
df.apply(my_function , axis = 0)   --this is default
df.apply(my_function , axis = 1)*#* ***Apply to Series*** *- apply function to only one column or one row*
df[<column>].apply(my_function)
df.loc[<row index>].apply(my_function)**

值得注意的是,apply()在与 DataFrame 一起使用时,只有 —可以接受一个“轴”作为参数来控制函数的应用方式——沿着行还是沿着列。这不适用于系列。

****axis***{0 or ‘index’, 1 or ‘columns’}, default 0* 0 or ‘index’: apply function to each column.
1 or ‘columns’: apply function to each row.**

我们现在看到了apply()的一些典型用法

关于 **apply()**的重要观点

  • 它可以使用 Python 的lambda构造处理未命名函数——我们可能不总是需要像上面所示的“printme”这样的命名函数,除非该函数太复杂。这是最常见的用法。

df.apply(lambda x: <do something with x>, axis = <some axis>)

  • apply 还可以使用数据类型的本地转换函数:比如它也可以使用 len、np.log 等函数。
  • 当应用于 DataFrame 时,它将“逐行”/“逐列”作为一个序列读取,并将该序列传递给函数。
  • 它返回一个系列,其索引取决于坐标轴规格。如果我们想要基于现有的列插入或更新行或列,这是很有帮助的。

注意**:“primt me”函数不“返回”任何东西,它只是为了演示数据是如何处理的。这就是为什么我们看到返回序列的结果在值和索引中均为“None”。但在现实世界中,我们会使用 *apply()* 实际返回序列中的一些值,并使用返回的序列向数据帧添加新内容或过滤数据帧。

经常被谷歌搜索的话题

好了,我们准备起飞了!!让我们将这些概念应用于一些最常见的感兴趣的话题。

让我们使用一个非常简单的数据集—人力资源数据—您可以在这里找到。

如果读者试图根据上面说明的基本概念来证明或解释每个结果,那将是最好的。

结束想法…

虽然有大量的书籍、博客和知识库承载着大量关于熊猫的知识及其官方文档,但有时合并关于核心概念的笔记是值得的。我试图传达我自己学习基础知识的方式,这可能对某个地方的人有益,在他们陷入更深更黑的水域之前。

所以快乐学习。万事如意。

如果有任何改进/错误需要添加/纠正,您可以随时通过我的链接个人资料联系我,或者在本页的这里写下您的意见。

感谢您的耐心阅读。

熊猫小组,简单解释

原文:https://towardsdatascience.com/pandas-groupby-explained-simply-b2ea80521cbb

熊猫分组的分步示例和不同用例

Anthony Intraversato 在 Unsplash 上拍摄的照片

简介

在数据科学社区中, Pandas 已经成为一个非常流行的处理和操作数据的框架。它基于 Python,一种我们都熟悉的非常简单和通用的语言。它提供了许多有用的功能来帮助我们将数据转换成我们想要的格式。其中之一是[groupby](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.groupby.html),这个函数可以根据某些列的值将 DataFrame 行划分成组。

当我第一次学习groupby时,我感到有点困惑,因为似乎有不同的方式可以使用groupby。这篇文章的目的是解释groupby如何与具体的例子和不同的用例一起工作。

注意,我会假设你对熊猫有基本的了解。事不宜迟,让我们直接开始吧!

数据

为了举例说明涉及groupby的例子,首先有一些数据是有用的。在这里,我提供了一个定制的数据框架,其中包含几个国家的一些信息。这里,我们有北美的美国和加拿大;欧洲的英国、法国和德国;亚洲的中国、日本和韩国。

import pandas as pddf = pd.DataFrame({
    "country" : ["United States", "Canada", "United Kingdom", "France", "Germany", "China", "Japan", "South Korea"],
    "continent" : ["North America", "North America", "Europe", "Europe", "Europe", "Asia", "Asia", "Asia"],
    "population" : [332722557, 38711108, 67081234, 67853000, 83222442, 1412600000, 125502000, 51745000],
    "area" : [3796742, 3855100, 93628, 247368, 137882, 3705407, 145937, 38690],
    "population percentage of world": [4.18, 0.487, 0.843, 0.853, 1.05, 17.8, 1.58, 0.650]
})
df

以下是生成的数据帧的外观:

数据来自维基百科(人口来源地区来源)

在这个数据框架中,面积单位是平方英里,而世界人口百分比是百分比。

按对象分组

既然有了数据,就开始探索groupby能做什么吧。

假设我们想根据洲来划分我们的数据。我们可以从以下方面着手:

df.groupby("continent")

请注意,在 jupyter 单元格中执行此操作会打印一个存储在某个内存地址的groupby对象。幕后发生的事情是,groupby对象根据我们指定的列continent对数据帧进行分组。

具有聚合函数的 GroupBy】

为了得到更有用的东西,我们需要告诉groupby在我们将数据分组后该做什么。

举个例子吧。假设我们想打印每个洲的条目(行)数,并保留所有列。我们如何实现这一目标?下面是最简单的方法:

df.groupby("continent").count()

这里的[count()](https://pandas.pydata.org/docs/reference/api/pandas.core.groupby.GroupBy.count.html)接收属于每个组(洲)的所有条目,并计算条目的数量,有效地将每个组的条目聚合成一个代表计数的数字。count()是一个聚合函数的例子。其他比较流行的聚合函数还有[sum()](https://pandas.pydata.org/docs/reference/api/pandas.core.groupby.GroupBy.sum.html)[min()](https://pandas.pydata.org/docs/reference/api/pandas.core.groupby.GroupBy.min.html)[max()](https://pandas.pydata.org/docs/reference/api/pandas.core.groupby.GroupBy.max.html),对每组进行相应的运算。这里是groupby对象支持的所有聚合函数的列表

具有聚合函数的 GroupBy 多索引

在一个groupby对象上使用一个聚合函数是很好的,但是如果您希望每一列使用不同的聚合函数呢?例如,假设对于每个大洲,我们需要一个国家和最小、最大和总人口的计数。我们如何实现这一目标?

嗯,[agg](https://pandas.pydata.org/pandas-docs/version/0.23/generated/pandas.core.groupby.DataFrameGroupBy.agg.html)函数就是实现这一点的确切方法。我们可以向agg传递一个字典,其中包含作为键的列名和作为值的聚合函数名,如下所示:

df_agg = df.groupby("continent").agg({"country": "count", "population": ["sum", "min", "max"]})
df_agg

生成的数据帧如下所示:

这就是我们想要的。如您所见,现在我们有了一个国家的计数,以及人口的三个统计数据。

这里值得注意的一点是,数据帧似乎有一个两级列结构。这在 Pandas 中称为多索引,其中需要多个值来访问某一列的值。为了验证这一点,我们可以打印 DataFrame 的列:

print(df_agg.columns)

例如,要访问 max population 的值,我们需要传入人口元组和 max 作为 DataFrame 的索引:

print(df_agg[("population", "max")])

具有聚合函数的 GroupBy 单索引

如果您不想处理多索引,而是恢复到单索引,您可以通过在agg函数中指定新的列名作为参数来实现:

df_agg_single = df.groupby("continent").agg(country_count = ("country", "count"), population_sum = ("population", "sum"), population_min = ("population", "min"), population_max = ("population", "max"))
df_agg_single

这将给出一个包含您指定的新列名的单一索引结果:

我们可以通过返回并打印列以及通过单个索引进行访问来验证这一点。

print(df_agg_single.columns)

print(df_agg_single["population_max"])

具有应用功能的分组依据

到目前为止,我们已经了解了如何对不同的列应用不同的聚合函数。但是,如果您想计算依赖于两列或更多列的值,该怎么办呢?

例如,假设您想要根据数据中某个洲内的国家来计算该洲的人口密度。我们还想计算一个大陆的人口占世界人口的百分比是否大于 3%。就我们目前所拥有的,我们无法通过聚合函数实现这一点。

我们需要别的东西。这个是另外一个[apply](https://pandas.pydata.org/pandas-docs/version/0.22/generated/pandas.core.groupby.GroupBy.apply.html),一个熊猫函数,它允许我们在将数据分组后指定我们自己的处理函数,在我们的例子中,是按洲分组。

如果我们想要总人口和人口密度,解决方案应该是这样的:

def process_continent(continent):
    result = {}
    if continent["population percentage of world"].sum() > 3:
        result["population"] = continent["population"].sum()
        result["population density"] = result["population"] / continent["area"].sum()
    return pd.Series(result, index = ["population", "population density"], dtype = "float64")df_density = df.groupby("continent").apply(process_continent)
df_density

如您所见,我们将定义的函数process_continent传递给apply。在process_continent,我们可以接触到每个大洲对应的每个组。我们检查它占世界人口的百分比是否大于 3;如果是,那么我们计算它的总人口和密度,并将结果作为序列返回。

你可能会注意到有欧洲的NaN条目。这是因为在我们的数据中,欧洲的人口不超过 3%,因此没有返回任何内容。为了将其从结果数据帧中排除,我们可以使用.dropna():

df_density.dropna()

最后,我们的数据框架显示了我们想要的东西:)

结论

如果你已经做到了这一步,恭喜你,我希望你在这篇文章中学到了一些有用的东西。我希望这篇文章能对groupby的工作原理和实际应用给出更清晰的解释。简而言之,groupby与聚合函数配合得很好,如果需要涉及多列的更具体的操作,那么apply可以与自定义函数配合使用。

如果你需要的话,这里有这篇文章中讨论的所有代码的链接。

这一个就这么多了!敬请关注未来的文章,如果你觉得我的文章有帮助,别忘了关注我。

以下是我的一些其他帖子,请随意查看:

https://medium.datadriveninvestor.com/top-20-masters-programs-in-ai-data-science-2021-98aadb7db409

参考

群聚,熊猫

pandas GroupBy:你的 Python 数据分组指南,真正的 Python

Python | Pandas data frame . group by(),GeeksForGeeks

Python 熊猫— GroupBy,TutorialsPoint

按人口统计的国家和属地列表,维基百科

维基百科按地区列出的国家和属地列表

如何对熊猫数据帧进行分组以计算平均值

原文:https://towardsdatascience.com/pandas-groupby-mean-fd34885a3d72

用分组表达式计算熊猫的平均值

Diana Polekhina 在 Unsplash 上拍摄的照片

介绍

当使用 pandas 数据框架时,我们通常需要为特定的组计算某些度量。实现这一点的典型方法是通过传统的分组表达式,然后是我们需要计算的相关计算。

在今天的简短教程中,我们将展示如何对 pandas 数据帧执行分组操作,以计算每组的平均值。

首先,让我们在 pandas 中创建一个示例数据框架,我们将在本文中使用它来演示一些概念,并理解为了获得目标结果我们需要遵循什么步骤。

import pandas as pd df = pd.DataFrame(
    [ 
        (1, 'B', 121, 10.1, True),
        (2, 'C', 145, 5.5, False),
        (3, 'A', 345, 4.5, False),
        (4, 'A', 112, 3.0, True),
        (5, 'C', 105, 2.1, False),
        (6, 'A', 435, 7.8, True),
        (7, 'B', 521, 9.1, True),
        (8, 'B', 322, 8.7, True),
        (9, 'C', 213, 5.8, True),
        (10, 'B', 718, 9.1, False),
    ],
    columns=['colA', 'colB', 'colC', 'colD', 'colE']
)print(df)
 **colA colB  colC  colD   colE** *0     1    B   121  10.1   True
1     2    C   145   5.5  False
2     3    A   345   4.5  False
3     4    A   112   3.0   True
4     5    C   105   2.1  False
5     6    A   435   7.8   True
6     7    B   521   9.1   True
7     8    B   322   8.7   True
8     9    C   213   5.8   True
9    10    B   718   9.1  False*

使用 mean()方法

我们这里的第一个选项是对感兴趣的列执行groupby操作,然后使用我们想要执行数学计算的列分割结果,最后调用mean()方法。

现在让我们假设对于列colB中出现的每个值,我们想要计算列colC的平均值。下面的表达式将为我们解决这个问题。

>>> df.groupby('colB')['colC'].mean()
colB
A    297.333333
B    420.500000
C    154.333333
Name: colC, dtype: float64

结果将是一个熊猫系列,包含列colB中出现的每个值的colC的平均值。

使用 agg()方法

同样,我们可以使用agg()方法来为指定的操作执行聚合——在我们的例子中是平均值计算。

>>> df.groupby('colB')['colC'].agg('mean')
colB
A    297.333333
B    420.500000
C    154.333333
Name: colC, dtype: float64

结果将与我们之前展示的方法完全相同。

计算中位数

同样,您可以使用相同的策略来计算其他指标,如计算组的中位数、计数或总和。

在下面的例子中,我们使用了与本教程第一部分中展示的相同的方法来计算列colB中出现的每个值的colC的中值。

>>> df.groupby('colB')['colC'].median()
colB
A    345.0
B    421.5
C    145.0
Name: colC, dtype: float64

同样,您可以使用其他方法,如count()sum()来计算相应的指标。

这同样适用于涉及agg()方法的第二种方法:

>>> df.groupby('colB')['colC'].agg('median')
colB
A    345.0
B    421.5
C    145.0
Name: colC, dtype: float64

最后的想法

在今天的文章中,我们讨论了 pandas 中最常执行的操作之一,它要求我们对感兴趣的数据帧执行分组操作。

此外,我们展示了如何计算有用的指标,例如感兴趣的组的平均值和中值。当然,这只是您可以计算的度量值的一个示例—实际上,可以使用相同的方法来计算计数、总和等。

https://levelup.gitconnected.com/how-to-group-by-pandas-dataframes-to-compute-sum-82a6bd890cbf

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

https://gmyrianthous.medium.com/membership

相关文章你可能也喜欢

如何使用“在”和“不在”筛选熊猫数据帧

原文:https://towardsdatascience.com/pandas-in-notin-ff2415f1e3e1

理解相当于 SQL“in”和“not in”表达式的熊猫

丹尼尔·利维斯·佩鲁西在 Unsplash 拍摄的照片

介绍

在 pandas 中处理数据帧时,我们通常需要根据某些条件来过滤行,比如某列的行值是否在某组指定值中(或者不在其中)。您可以将这样的表达式想象成对同一列有多个OR操作符。

在今天的简短教程中,我们将展示如何使用相当于 SQL INNOT IN表达式的熊猫。此外,我们还将讨论如何在一个熊猫表达式中组合这些条件。

首先,让我们创建一个示例数据框架,我们将在本教程中使用它来演示一些概念。

import pandas as pddf = pd.DataFrame(
    [
        (1, 'A', 10, True),
        (2, 'B', 12, False),
        (3, 'B', 21, False),
        (4, 'C', 18, False),
        (5, 'A', 13, True),
        (6, 'C', 42, True),
        (7, 'B', 19, True),
        (8, 'A', 21, False),
    ],
    columns=['colA', 'colB', 'colC', 'colD']
)print(df) ***colA colB  colC   colD*** 0     1    A    10   True
1     2    B    12  False
2     3    B    21  False
3     4    C    18  False
4     5    A    13   True
5     6    C    42   True
6     7    B    19   True
7     8    A    21  False

熊猫相当于 SQL IN表达式

[pandas.Series.isin()](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.isin.html)方法,是相当于 SQL 中众所周知的IN表达式的熊猫。该方法返回一个布尔值Series,指示元素是否包含在指定的值中。

例如,让我们假设在我们的示例数据帧中,我们希望只保留在列colB中具有值AC的行。为此,我们可以使用通过isin()方法获得的结果对数据帧进行切片,如下所示:

df = df[df.colB.isin(['A', 'C'])]print(df)
 ***colA colB  colC   colD***
0     1    A    10   True
3     4    C    18  False
4     5    A    13   True
5     6    C    42   True
7     8    A    21  False

熊猫相当于 SQL NOT IN表达式

同样,我们可以简单地否定来自isin()方法的结果,以获得与NOT IN表达式等价的熊猫。~求反运算符可用于实现这一点。

现在让我们假设我们想要过滤掉在列colB中具有值AC的所有行。下面的表达式可以解决这个问题。

df = df[~df.colB.isin(['A', 'C'])]print(df)
 ***colA colB  colC   colD*** 1     2    B    12  False
2     3    B    21  False
6     7    B    19   True

将条件应用于多列

现在让我们假设我们想要在熊猫数据帧上组合多个条件。我们可以使用逻辑 AND 运算符&将所有的连接起来。举个例子,

df = df[df.colB.isin(['A', 'C']) & df.colC.isin([10, 13, 16])]print(df)
 ***colA colB  colC  colD*** 0     1    A    10  True
4     5    A    13  True

isin()方法的替代方法

另一种方法是使用query()方法,让您指定类似 SQL 的表达式。

vals_to_keep = ['A', 'B']
df = df.query('colB in @vals_to_keep')print(df)
 ***colA colB  colC   colD*** 0     1    A    10   True
1     2    B    12  False
2     3    B    21  False
4     5    A    13   True
6     7    B    19   True
7     8    A    21  False

同样,我们也可以使用not in表达式:

vals_to_drop = ['A', 'B']
df = df.query('colB not in @vals_to_keep')print(df)
 ***colA colB  colC   colD*** 1     2    B    12  False
2     3    B    21  False
6     7    B    19   True

注意,SQL 表达式是区分大小写的,所以如果您改为指定INNOT IN(即大写字符),您将以SyntaxError结束。

最后的想法

在今天的文章中,我们讨论了相当于 SQL INNOT IN表达式的熊猫。在过滤掉不符合指定标准的数据帧行时,这样的条件可能非常有用。

特别是isin()表达式非常强大,因为它可以帮助您指定多个条件,为了不从结果中过滤掉,数据帧的行必须至少满足其中一个条件。换句话说,你可以认为它有多个OR表达式。

最后,我们讨论了isin()方法的替代方法,即query(),它允许您在 pandas 数据帧上指定类似 SQL 的表达式。

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

https://gmyrianthous.medium.com/membership

相关文章你可能也喜欢

实践中的熊猫

原文:https://towardsdatascience.com/pandas-in-practice-64be9228062a

这是一个实践教程,演示了如何利用 Pandas 包来清理、操作和分析数据。

图像取自 Unsplash

1。简介

您是否曾经在 Excel 中难以分析大数据集?那么,您应该考虑 Pandas,它是一个强大的编程和数据分析工具箱,允许您在毫秒内处理百万行数据点,具有很高的处理能力和用户生产率。事实上,Pandas 是一个开源的 python 库,由软件开发人员 Wes McKinney 在 2008 年推出,它包括高级数据结构和编程工具,可以对不同的数据类型(无论是数字、文本还是日期等)进行数据分析和操作。本教程为你开始在你的项目中应用熊猫提供了必要的知识。

2.目标

让我们假设你刚加入一家初创公司,担任数据分析师,你被分配到支持团队,推动对客户群的深入了解。您的经理共享了数据集,现在您有机会展示您的 python 技能并使用 Pandas 包来执行分析。

本教程将帮助你发展和使用熊猫知识:

1-导入和读取 CSV 文件

2-生成对给定数据的基本理解

3-根据条件选择和过滤数据

4-应用各种数据操作工具,如创建新变量或更改数据类型

5-使用分组依据和数据透视表方法执行数据聚合

6-将分析见解转化为商业建议。

#load the needed packages and modules 
import pandas as pd
import numpy as np
from datetime import date
import datetime as dt

3.数据分析

3.1 加载数据

第一步是使用 pd_read_csv 加载数据,它读取 csv 文件的数据,加载并返回数据帧中的内容。您可能会问自己,什么是数据框?数据帧是 pandas 包中的主要数据结构,它以不同属性的表格显示数据,如下所示。有关 pd.read_csv 的更多信息,请访问链接

本教程中使用的数据集是为本文创建的虚拟数据集。在此链接中可以找到它,它包含了关于客户的有趣数据,可用于有针对性的营销活动。

#load the data and make sure to change the path for your local
directory 
data = pd.read_csv('C:/Users/Smart PC/Desktop/project_data.csv')

3.2 获得基本了解

加载数据后,我们可以使用不同的方法来查看和理解变量。例如,data.head()使我们能够查看 dataframe 的前 5 行,而 data.tail()返回最后 5 行。如果想获得前 10 行,需要在方法中将其指定为 data.head(10)。

#first 5 rows
data.head()

#last 5 rows 
data.tail()

另一个有趣的方法是 data.info(),它给出了数据集的数据点和变量的数量。它还显示数据类型。我们可以看到,我们的数据集有 499 个数据点和 12 个变量,从客户的个人信息到购买、通话、对讲机和投诉。

#check the basic information of the data
data.info()

我们可以使用 data.shape 检查数据集的形状;它表明我们的数据集有 449 行和 12 列。

#extract the shape of the data
data.shape

假设我们想知道婚姻状况字段的唯一值,那么我们应该选择列并应用唯一方法。如下所示,可变婚姻状况有 5 个独特的类别。但是,我们注意到寡妇和鳏夫是同一类别的两个不同的命名,因此我们可以通过对列值使用 replace 方法来使其保持一致,如下所示。

data['marital_status'].unique()

data[‘marital_status’] = data[‘marital_status’].replace(‘Widow’, ‘Widowed’)

了解唯一值及其各自计数的另一个有趣方法是对列应用 value_count 方法。例如,教育属性有 5 个类别,其中毕业和博士构成了最大的比例。

round(data['educational_level'].value_counts(normalize=True),2)

我们可以使用。复制和。isnull 方法。此外,我们可以在数据集中选择一个感兴趣的变量,以检测其缺失值或重复值,如下所示。

data.isnull()
data.duplicated().sum()
data['educational_level'].isnull().sum() #specifying Education as a variable where we should look for the sum of missing values

3.3 选择和过滤数据:loc 和 iloc

我们可以简单地从数据框中选择数据点的子集。让我们考虑我们想要选择每个客户的生日、教育和收入;这可以通过选择带有双括号的列名来实现,如下所示。

subset_data = data[['year_of_birth ', 'educational_level', 'annual_income']]
subset_data

我们可以通过指定只从数据框中返回“Master”来选择教育的唯一类别。

data[data["educational_level"] == "Master"]

从数据框中选择数据的另外两种流行方法是:loc 和 iloc。这两种方法的主要区别在于:loc 是基于标签的,这意味着我们必须根据标题指定行和列。然而,iloc 是基于整数位置的,因此我们必须按照整数位置值选择行和列,即:0,1,2。要了解更多关于使用这些方法选择数据的信息,请访问此链接

正如我们在下面注意到的,我们使用 loc 方法选择前七个数据点,但是变量“educational_level”和“recency”。我们也可以使用 iloc 实现相同的结果,但是将行和列指定为整数值。

data.loc[:6, ['educational_level', 'recency']] #specify the rows and columns as labels

data.iloc[:6, [2,6]] #speciy rows and columns as integer based values

另一个强大的工具是使用 loc 和 isin()方法过滤数据,我们可以选择感兴趣的变量和我们想要的类别。例如,下面我们选择婚姻状况描述为单身或离婚的所有客户。

data.loc[data[‘marital_status’].isin([‘Single’, ‘Divorced’])]

我们可以将 iloc 与 python 操作符结合使用,以选择满足两个条件的数据,例如选择收入高于 75,000 英镑且拥有硕士学位的客户。

data.iloc[list((data.annual_income > 75000) & (data.educational_level == 'Master')),  :,]

3.4 应用数据操作:索引、新变量、数据类型等等!

我们可以使用 Pandas 对数据集应用不同的操作,例如但不限于:
-使用。set_index()方法
-使用变量之一对数据帧进行排序。sort_values()按升序或降序排列;有关 sort_values()的更多信息,请访问链接
-创建一个新变量,该变量可能是数学运算的结果,例如其他变量的和
-使用 pd.cut()方法构建变量的类别;有关 pd.cut()的更多信息,请访问链接
-将变量的数据类型更改为日期时间或整数类型
-根据出生年份确定年龄
-根据购买日期创建星期日期(日历周和年)
等等;这只是我们用熊猫所能达到的成就的一瞥!

#set the index as customer_id
data.set_index(‘customer_id’)

#sort the data by year_of_birth, ascending is default;
data.sort_values(by = ['year_of_birth '], ascending = True) # if we want it in descending we should set ascending = False

#create a new variable which is the sum of all purchases performed by customers
data['sum_purchases'] = data.online_purchases + data.store_purchases 
data['sum_purchases']

#create an income category (low, meduim, high) based on the income variable
income_categories = ['Low', 'Meduim', 'High'] #set the categories
bins = [0,75000,120000,600000] #set the income boundaries 
cats= pd.cut(data['annual_income'],bins, labels=income_categories) #apply the pd.cut method
data['Income_Category'] = cats #assign the categories based on income
data[['annual_income', 'Income_Category']]

#we can change the datatype of purchase_date to datetime and year_birth to integer
data['purhcase_date'] = pd.to_datetime(data['purhcase_date'])
data['year_of_birth '] = data['year_of_birth '].astype(int)#find out the age of customers based on the current year
today = date.today()
year = int(today.year)
data['Age'] = year - data['year_of_birth ']
data['Age']

#extract the week_date from the purchase_date which shows the year of purchase and the calendar week
#make sure to change the purhcase_date varibale to datetime as shown above before applying the .isocalendar() method
data["week_date"] = [int(f'{i.isocalendar()[0]}{i.isocalendar()[1]}') for i in data.purhcase_date]
data["week_date

3.5 执行数据聚合:groupby 和 pivot_table

在我们创建了新的变量之后,我们可以进一步聚合,从类别中生成有趣的见解。每位数据科学家都使用两种主要方法来分组分析数据:

  1. groupby() :一种方法,包括拆分类别、应用函数和组合结果,从而可用于数学运算,如均值、求和或计数以及按组的聚合视图。有关此操作的更多信息,请访问此链接

  2. pivot_table() :这是一种非常有用的技术,可以创建电子表格风格的数据透视表作为数据框架,它还允许分析师对每组中选定的变量应用数学运算。有关此 pivot_table 的更多信息,请访问此链接

#apply groupby to find the mean of income, recency, number of web and store purchases by educational group
aggregate_view = pd.DataFrame(data.groupby(by='educational_level')[['annual_income', 'recency', 'store_purchases',  'online_purchases']].mean()).reset_index()
aggregate_view

#apply pivot table to find the aggregated sum of purchases and mean of recency per education and marital status group
pivot_table = pd.DataFrame(pd.pivot_table(data, values=['sum_purchases', 'recency'], index=['marital_status'],
                        columns=['educational_level'], aggfunc={'recency': np.mean, 'sum_purchases': np.sum}, fill_value=0)).reset_index()
pivot_table

4。建议

现在,在我们完成了数据清理和对数据集执行操作和聚合的过程之后,我们可以总结出一些关于我们客户群的有趣见解:

  • 博士人群收入最高,线上和店铺购买次数最多;然而,高中毕业生拥有最高的最近购买次数或最近购买天数
  • 普通人在网上和商店购买的数量最少
  • 毕业水平的已婚人士购买总额最高

因此,对任何潜在营销活动的商业建议都应侧重于吸引和留住博士和具有毕业水平的已婚夫妇,并应提供更多产品来满足其他类别的需求和兴趣,如购买量最低的基本人群以及不经常购买的高中生。

此外,应开展进一步的工作来了解客户的行为和兴趣,例如,执行 RFM 分析或回归建模将有利于研究变量对教育或婚姻状况组购买数量的影响。

5.结论

最后,我希望这篇文章对您在即将到来的数据分析练习中使用 pandas 有所启发、实用和有用。下面是总结性的总结:

  • Pandas 是一个建立在 Python 编程语言之上的数据分析库,
  • Pandas 擅长对包含多个变量的大型数据集执行复杂操作,
  • Data frame 是 Pandas 中的主要数据结构,
  • loc 和 iloc 用于选择和过滤数据,
  • group_by 和 pivot_table 是两个最知名的聚合方法。

敬请关注更多精彩的机器学习和数据分析分享知识内容。

熊猫还不够?替代数据争论解决方案的全面指南

原文:https://towardsdatascience.com/pandas-is-not-enough-a-comprehensive-guide-to-alternative-data-wrangling-solutions-a4730ba8d0e4

照片由马修·施瓦茨Unsplash 拍摄

包括达斯克、摩丁、polars、Vaex、Terality 等 6 人

我认为pandas不需要介绍。这是数据科学家使用的一个伟大而通用的工具,并且很可能会继续在日常生活中使用。但是在使用pandas时,我们可能会面临一些潜在的挑战。

最大的问题与数据量有关,这在大数据时代肯定会成为一个问题。虽然有许多任务不涉及如此大量的数据,但我们迟早会遇到这种情况。在这种情况下,我们可以试几招。首先,我们可以尝试优化存储在数据帧中的变量的数据类型,以使数据适合内存。或者,我们可以一次只加载整个数据的一部分。

这些解决方案通常会有所帮助,但有时它们仅仅是不够的,我们可能会耗尽内存或操作变得慢得令人无法忍受。在这种情况下,我们可能想要远离pandas,使用更好的工具来完成工作。

本文的目标不是提供所有可能方法的性能比较。相反,我想介绍一下pandas的可能替代方案,并简要介绍它们的潜在用例,以及它们的优缺点。然后,您可以选择哪个解决方案符合您的需求,并且可以更深入地了解实现的本质细节。

回顾线程与流程

在整篇文章中,我们将提到使用线程或进程运行并行操作。我认为这需要快速复习一下:

  • 进程不共享内存,在单个内核上运行。它们更适合于不需要相互通信的计算密集型任务。
  • 线程共享内存。在 Python 中,由于全局解释器锁(GIL),两个线程不能在同一程序中同时运行。因此,只有一些操作可以使用线程并行运行。

有关线程与进程的更多信息,请参考本文。

熊猫替代品列表

在本节中,我们将介绍最受欢迎的pandas替代品(截至 2022 年初)。列表的顺序不是从最好到最差的排序,也不是事实上的任何排序。我只是尝试提出这些方法,并在这样做的同时,当各种解决方案之间存在逻辑桥梁时,介绍一些结构。开始吧!

Dask — ~10k GitHub stars

Dask 是一个用于分布式计算的开源库。换句话说,它有助于在一台机器或许多独立的计算机(集群)上同时运行许多计算。对于前者,Dask 允许我们使用线程或进程并行运行计算。

来源

Dask 依靠的是被称为懒惰评估的原理。这意味着直到我们明确地要求(使用compute函数)操作才会被执行。通过延迟操作,Dask 创建了一个转换/计算队列,以便它们可以在以后并行执行。

在幕后,Dask 将一个大型数据处理任务分解成许多较小的任务,然后由numpypandas处理。之后,该库将结果重新组合成一个连贯的整体。

关于 Dask 的一些要点:

  • 将数据处理工作负载从单台机器扩展到分布式集群(可能扩展到具有 1000 个内核的集群)的良好选择。我们可以很容易地使用完全相同的代码在本地机器上用整个数据集的样本来测试运行一些任务。然后,我们可以在完整数据上重复使用完全相同的代码,并在群集上运行它。
  • 数据不必放入内存,而是需要放在磁盘上。
  • 它建立在现有的众所周知的对象之上,如numpy数组和pandas数据帧——没有必要放弃当前的方法并从头重写
  • API 与pandas非常相似,除了它有懒惰的行为。
  • Dask 为更多的定制工作负载和与其他项目的集成提供了任务调度接口。此外,它还提供了大量交互式图表和任务分布的可视化,以便进行深入的分析和诊断。
  • Dask 不仅仅是数据处理。Dask-ML(一个独立的库)使用 Dask 以及流行的机器学习库,如scikit-learnxgboostlightgbm等,提供了 Python 中可扩展的机器学习。它有助于缩放数据大小和模型大小。一个例子是,许多scikit-learn算法是使用joblib为并行执行而编写的(它支持众所周知的n_jobs参数)。Dask 通过提供另一个joblib后端将这些算法扩展到一个机器集群。

有用参考:

摩丁——约 7k GitHub 星

Modin 是一个库,旨在通过在系统的所有可用 CPU 内核之间自动分配计算来并行化pandas数据帧。由于这一点,摩丁声称能够获得接近线性的速度提升到你的系统上的 CPU 核心的数量。

那么这是怎么发生的呢?Modin 只是将现有的数据帧分成不同的部分,这样每个部分都可以发送到不同的 CPU 内核。更准确地说,Modin 将数据帧划分为行和列,这使得它的并行处理对于任何大小和形状的数据帧都是高度可扩展的。

该库的作者专注于将数据科学家的时间优先于硬件时间。这就是为什么摩丁:

  • 具有与pandas相同的 API,因此不会增加数据科学家的学习成本。与大多数其他库不同,它的目标是全面覆盖pandas API。在撰写本文时,它提供了pd.DataFrame90%的>功能和pd.Series88%的>功能。如果某个功能/方法没有实现,Modin 默认为pandas,所以最终所有命令都被执行。
  • 运行非常简单,可作为pandas的替代产品。其实我们只需要把一个导入行改成import modin.pandas as pd
  • 提供与 Python 生态系统的流畅集成。
  • 不仅可以在本地机器上运行,还可以在 Ray/Dask 集群上运行。我们之前已经介绍过 Dask,所以提到 Ray 是有意义的。Ray 是一个高性能的分布式执行框架。完全相同的代码可以运行在一台机器上(高效的多处理)和一个专用集群上进行大规模计算。
  • 支持核外模式,在这种模式下,Modin 使用磁盘作为内存的溢出存储。这样,我们可以处理比内存大得多的数据集。

那么摩丁和达斯克有什么不同呢?有一些差异值得一提:

  • 与 Dask 相反,Modin 提供了完全的兼容性。为了可伸缩性,Dask 数据帧提供基于行的存储。这就是为什么他们不能完全支持所有的pandas功能。相比之下,Modin 被设计成一个灵活的列存储。
  • Dask 数据帧需要使用compute方法显式计算(因为它们处于惰性模式)。在 Modin 中,对用户查询的所有优化都是在幕后执行的,不需要用户的任何输入。
  • 必须明确说明 Dask 中的分区数量。
  • Modin 可以在 Dask 上运行,但它最初是为了与 Ray 一起工作而构建的。

有关这些差异的更多信息,请参见下面的链接。

有用的参考资料:

更快—约 2k 颗 GitHub 星

swifter是一个开源库,它试图以最快的方式将任何函数有效地应用到pandas数据帧或系列中。apply是一个非常有用的函数,因为它允许我们轻松地将任何函数应用于pandas对象。然而,这是有代价的——该函数充当 for 循环,导致速度很慢。

除了首先对函数进行矢量化之外,本文中已经提到了相当多的并行替代方法。那么swifter在这一切中扮演什么角色呢?

我们已经提到,它试图以最快的方式应用该功能。首先,如果可能的话,swifter对函数进行矢量化。如果这是不可能的,它估计什么更快:使用 Dask/Modin 或简单的pandas应用并行处理。

swifter的主要特点:

  • 低学习曲线——这是在apply方法链中添加swifter的问题。例如:
df["col_out"] = df["col_in"].swifter.apply(some_function)
  • 截至目前,swifter支持以下加速方式:applyapplymaprolling().apply()resample().apply()
  • 它受益于 Dask/Modin 等库的潜力
  • 我们不应该盲目地把任何功能扔在swifter上,抱最好的希望。这就是为什么当我们写 UDF 时,我们应该考虑到函数的矢量化。一个例子是使用np.where代替 if-else 条件流。

有用参考:

vaex — 7k GitHub stars

Vaex 是另一个开源的数据帧库,专门研究懒惰的核外数据帧。

该库最大的亮点可能是 Vaex 只需要很少的 RAM 来检查和与任意大小的数据集交互。这是可能的,因为结合了惰性评估和内存映射。后者是一种技术,你告诉操作系统你想要一块内存与磁盘上的内容同步。当一段时间内没有修改或使用某块内存时,它将被丢弃,以便可以重用 RAM。

实际上,当我们用 Vaex 打开一个文件时,实际上并没有读取任何数据。相反,Vaex 只读取文件的元数据:数据在磁盘上的位置、数据的结构(行数/列数、列名和类型)、文件描述等。

这就是为什么受益于 Vaex 的要求之一是以内存可映射文件格式存储数据,例如 Apache Arrow、Apache Parquet 或 HDF5。如果我们满足这个要求,Vaex 将立即打开这样一个文件,不管它有多大,也不管我们有多少 RAM。

Vaex 的主要特点:

  • 类似于pandas的 API。
  • 易于处理非常大的数据集—通过结合内存映射和惰性评估,Vaex 只受我们可用硬盘空间的限制。
  • Dask 等库侧重于让我们将代码从本地机器扩展到集群,而 Vaex 侧重于使在单台机器上处理大型数据集变得更容易。
  • Vaex 不会创建内存副本,因为过滤后的数据帧只是原始数据的浅层副本。这意味着过滤只需要很少的内存。假设我们有一个 50GB 的文件。许多工具需要 50GB 来读取文件,过滤后的数据帧也需要大约 50GB。
  • 虚拟列是在我们转换 Vaex 数据帧的现有列时创建的。它们的行为就像普通的一样,主要区别是它们根本不使用内存。这是因为 Vaex 只记住它们的定义,并不实际计算这些值。虚拟列仅在必要时才进行延迟评估。
  • Vaex 非常快,因为虚拟列的计算是完全并行的,并且使用一些流行的列方法(value_countsgroupby等)的 C++实现。).此外,所有这些都在核外工作,这意味着我们可以在使用所有可用内核的同时,处理比 RAM 中所能容纳的更多的数据。
  • 速度还来自于智能优化,它允许我们只通过一次数据就可以为多个选择计算一些统计数据(无需每次创建新的参考数据帧)。更好的是,我们可以将这些与groupby聚合结合起来,同时仍然只传递一次数据。
  • Vaex 可以通过 Numba、Pythran 或 CUDA(需要支持 CUDA 的 NVIDIA 显卡)使用即时编译来进一步加速函数的评估。
  • Vaex 坚持只在必要时检查整个数据集的策略,然后尽可能少地检查数据。例如,当显示 Vaex 数据帧或列时,Vaex 仅从磁盘中读取前 5 行和后 5 行。
  • Vaex 还提供了非常快速且节省内存的字符串操作(几乎支持所有的pandas操作)。
  • 还有一个vaex.ml库,它实现了一些常见的数据转换,例如 PCA、分类编码器和数字定标器。它们具有熟悉的 API、并行化和核外执行的优势。该库还提供了几个流行的机器学习库的接口,如scikit-learnxgboost。通过使用它,我们在处理数据争论部分(清理、特征工程和预处理)时不会浪费任何内存。这使我们能够最大限度地利用可用内存来训练模型。

这是相当多的信息。我们还将简要介绍 Vaex 和前面提到的方法之间的一些差异。

  • 虽然 Dask 与pandas并不完全兼容,但摩丁的目标是兼容,因此,这些库带有一些pandas固有的包袱。通过更多地偏离源(但仍然非常相似),Vaex 在功能方面受到的限制更少(内存映射的查询方式等)。)
  • Dask 和 Modin 扩展到集群,而 Vaex 试图通过内存映射文件和使用本地机器的所有可用核心来帮助用户避免对集群的需求。
  • Vaex 的作者将 Vaex 和 Dask 之间的关系描述为正交。Dask(和 Modin)主要关注数据处理和争论,而 Vaex 也提供了在 N 维网格上快速计算统计数据的能力,并具有一些易于可视化和绘制大型数据集的功能。

关于 Vaex 和 Dask 更深入的对比,请看这篇文章

有用参考:

数据表— 1.5k GitHub stars

datatable是一个用于操作二维表格数据的 Python 库。它是由 H2O.ai 开发的,它的第一个用户是无人驾驶. ai 。在许多方面,它类似于pandas,特别强调速度和单节点机器上的大数据(高达 100GB)支持。

如果您使用过 R,您可能已经熟悉了相关的包data.table,这是 R 用户在快速聚合大数据时的首选包。Python 的实现试图模仿其核心算法和 API。

说到 API,其实就是datatablepandas(以及 R 的data.frame)的“爱它还是恨它”的区别。在datatable中,执行所有操作的主要方式是方括号符号,这是受传统矩阵索引的启发。一个例子是:

DT[i, j, ...]

其中i是行选择器,j是列选择器,...表示可能添加的附加修饰符。虽然这已经很熟悉了,因为它与在 R/ pandas / numpy中索引矩阵或对象时遇到的符号完全相同,但还是有一些不同。

其中之一是i可以是任何可以被解释为行选择器的东西:一个整数、一个切片、一个范围、一个整数列表、一个切片列表、一个表达式、一个布尔值/整数值框架、一个生成器等等。但这仍然是熟悉的,不应该是一个大问题。

当我们想要执行更高级的操作时,棘手的部分就来了,因为datatable的语法与我们大多数人习惯的语法相差甚远。例如:

DT[:, sum(f.quantity), by(f.product_id)]

该代码片段计算产品数量的总和。而不熟悉的f是必须从datatable模块导入的特殊变量。它提供了引用给定框架中任何列的快捷方式。

关于datatable的要点:

  • datatable中的数据帧被称为帧,和pandas中的数据帧一样,它们是柱状数据结构。
  • pandas相反,该库为所有数据类型提供了 native-C 实现,包括字符串。pandas仅对数值类型有效。
  • 它提供了从 CSV 和其他文件格式快速读取数据。
  • 使用datatable时,我们应该将数据以与内存相同的格式存储在磁盘上。得益于此,我们可以使用磁盘上数据的内存映射,并处理内存不足的数据集。这样,我们就避免了为每个特定的操作加载过多的数据到内存中。
  • datatable使用多线程数据处理来实现最高效率。
  • 该库最大限度地减少了数据复制量。
  • 很容易将datatable的帧转换成pandas / numpy的对象。

有用参考:

cuDF —约 4.5k GitHub stars

[cuDF](https://github.com/rapidsai/cudf)是一个 GPU 数据框架库,是 NVIDIA 的 RAPIDS 的一部分,这是一个跨多个开源库并利用 GPU 功能的数据科学生态系统。cuDF 提供了一个类似于 pandas 的 API,允许我们从性能提升中获益,而无需深入 CUDA 编程的细节。

关于cuDF的要点:

  • pandas-像 API 一样——在很多情况下,我们只需要修改一行代码就可以开始受益于 GPU 的强大功能。
  • 使用 Apache Arrow 列内存格式构建。
  • cuDF是单 GPU 库。然而,它可以利用多 GPU 设置结合 Dask 和专用的dask-cudf库。有了它,我们能够在一台机器上的多个 GPU 之间扩展cuDF,或者在一个集群中的许多机器之间扩展多个 GPU。
  • 使用cuDF需要一个兼容的 NVIDIA GPU 和一些额外的设置(更新驱动程序,安装 CUDA 等)。)
  • 我们应该记住,只要数据合理地适合 GPU 内存,就可以获得最佳性能。

有用参考:

pyspark

与之前的库相比,我们实际上首先需要后退一步,描述 Spark 是什么。

Apache Spark 是一个用于大规模数据处理的统一分析引擎,用 Scala 编写。它基本上是为数据科学处理大型数据集(比如 100GB 以上)的库。其受欢迎有多种原因,包括以下原因:

  • 它比 Hadoop 快 100 倍,
  • 它实现了静态、批处理和流式数据的高性能,
  • 它使用最先进的 DAG ( 有向无环图)调度器、查询优化器和物理执行引擎。

Spark 采用主从架构工作,其中主设备实际上被称为“驱动器”,从设备被称为“工人”。运行 Spark 应用程序时,Spark driver 会创建一个上下文,作为应用程序的入口点。然后,所有操作都在工作节点上执行,而资源由集群管理器管理。

Spark 自带数据帧风格。虽然它们具有类似于pandas数据帧的功能,但主要区别在于它们是分布式的,它们具有惰性评估并且是不可变的(不允许覆盖数据)。

对 Spark 的介绍已经足够了,让我们把重点放在与本文最相关的部分,即缩放数据帧。为此,我们可以使用 PySpark,这是一个用于 Spark 的 Python API。

关于 PySpark 需要了解的关键事项:

  • PySpark 是一个通用的、内存中的分布式处理引擎,用于以分布式方式进行高效的数据处理。
  • 速度大幅提升——在 PySpark 上运行计算比使用传统系统快 100 倍。
  • 它有一个不同于pandas的 API,并且它不能很好地与其他库集成(例如用于绘图的matplotlib等)。).一般来说,它的学习曲线比pandas更陡。
  • 当使用宽转换(查看所有节点的全部数据,例如,排序或使用groupby)时,我们应该小心,因为它们比窄转换(查看每个节点中的单个数据)计算量更大。
  • 要使用 PySpark,我们需要克服一些开销——设置一个 Spark(本地或集群),在我们的计算机上有一个 JVM (Java 虚拟机),等等。当我们的组织中还没有运行时,这可能是一个阻碍,并且对于一些较小的实验来说,设置它将是一个大材小用。或者,我们可以使用托管云解决方案,如 Databricks
  • 使用 PySpark,我们可以轻松处理来自 Hadoop HDFS、AWS S3 和许多其他文件系统的数据。这也包括使用流媒体和 Kafka 处理实时数据。
  • 是 PySpark 的包装器,基本上是 Spark 的机器学习库。由MLlib库提供的 API 非常容易使用,并且支持许多分类、回归、聚类、维度减少等算法。
  • Spark 允许我们用 SQL 和Python 查询数据帧。这很方便,因为有时用 SQL 编写一些逻辑比记住确切的 PySpark API 更容易。因为工作可以互换,你可以使用任何你喜欢的。
  • 由于 Spark 运行在几乎无限的计算机集群上,它可以处理的数据集大小实际上没有限制。

有用参考:

考拉——约 3k GitHub 星

我们提到过 PySpark 最大的痛点是语法,它不同于pandas,并且有一个相当陡峭的学习曲线。这正是 Databricks 想用考拉解决的问题。该库的目标是通过在 Spark 上实现pandas API,让数据科学家在与大数据交互时更有效率。

关于考拉要知道的一些事情:

  • 你可以拥有一个既能与pandas(较小的数据集)又能与 Spark(分布式数据集)一起工作的单一代码库。你只需要把导入语句从pandas替换到koalas
  • 考拉支持 Spark ≤ 3.1,在 Spark 3.2 中正式被 PySpark 收录为pyspark.pandas
  • 空值的处理方式可能稍有不同。pandas使用 NaNs(特殊常量)来表示缺失值,而 Spark 在每个值上都有一个特殊的标志来表示是否有值缺失。
  • 惰性求值——由于 Spark 本质上是惰性的,一些操作(例如创建新列)只有在 Spark 需要打印或写入数据帧时才会执行。
  • 很容易将考拉数据帧转换成pandas /PySpark 数据帧。
  • 考拉还支持带有ks.sql()的标准 SQL 语法,允许执行 Spark SQL 查询,并以数据帧的形式返回结果。
  • 考拉和 PySpark 的性能结果非常相似,因为它们都在幕后使用 Spark。但是,与纯 PySpark 相比,性能会略有下降。大多数情况下,它与构建默认索引的开销有关,或者与一些pandas和 PySpark APIs 共享相同的名称,但具有不同的语义(例如,count方法)的事实有关。
  • 树袋熊数据帧与 PySpark 数据帧在外观上略有不同。为了实现需要隐式排序的pandas数据帧结构及其丰富的 API,考拉数据帧具有表示类似pandas的索引的内部元数据和映射到 PySpark 数据帧列的列标签。另一方面,PySpark 对应物往往更符合关系数据库中的关系/表。因此,它们没有唯一的行标识符。
  • 在内部,考拉数据帧建立在 PySpark 数据帧之上。考拉将pandasAPI 翻译成 Spark SQL 的逻辑计划。然后 Spark SQL 引擎优化并执行该计划。

有用的参考资料:

极地星——约 5k GitHub 星

polars是一个强调速度的开源数据框架库。为了实现这一点,它在 Rust 中以 Apache Arrow 作为内存模型来实现。直到最近,作为polars的 Python 包装器的库被称为pypolars,然而,为了简单起见,现在它也被称为polars

polars的一些关键特性:

  • API 类似于pandas,然而,它实际上更接近 R 的dplyr
  • 有两个 API——渴望和懒惰。前者与pandas非常相似,因为结果是在执行完成后立即产生的。另一方面,lazy API 更类似于 Spark,在 Spark 中,计划是在执行查询时形成的。但是当我们调用collect方法时,该计划直到在 CPU 的所有核心上并行执行时才真正看到数据。
  • 绘图很容易生成并与最流行的可视化工具集成。
  • polars是目前最快的(如果不是最快的)数据帧库之一(根据这个基准),并且支持对于pandas来说可能太大的数据帧。
  • polars的速度来自于利用你机器所有可用的内核。它与其他解决方案的区别在于,polars是从底层开始编写的,并考虑到了数据帧查询的并行化,而 Dask 等工具则并行化了现有的单线程库(如numpypandas)。

有用参考:

潘达平行星——约 2k 颗 GitHub 星

pandarallel(承认吧,这个听起来有点像神奇宝贝)是一个开源库,可以在所有可用的 CPU 上并行化pandas操作。

当我们使用pandarallel调用并行化函数时,下面的步骤会在幕后发生:

  • 该库初始化 PyArrow 等离子体共享存储器,
  • 它为每个 CPU 创建一个子进程,然后要求它们处理原始数据帧的一部分,
  • 它合并了父流程中的所有结果。

关于pandarallel的一些要点:

  • 该库允许您并行化下面的pandas方法:applyapplymapgroupbymaprolling
  • 如果您的 CPU 使用超线程(例如,8 个核心和 16 个线程),则只会使用 8 个核心。
  • pandarallel需要两倍于标准pandas操作使用的内存。不言而喻,如果数据最初不适合使用pandas的内存,则不应使用该库。

有用参考:

https://github.com/nalepae/pandarallel

Terality

在谈到pandas替代品时,Terality 是一个新手。它是一个无服务器的数据处理引擎,使pandas像 Apache Spark 一样可扩展和快速(比pandas快 100 倍,能够处理 100 多 GB 的数据),既没有基础设施要求,也不涉及任何代码更改。听起来已经很棒了!有什么条件?

与其他库/方法的最大区别是 Terality 不是开源软件。有不同类型的订阅(包括一个免费的游戏!)但总的来说,你是按处理的数据量收费的。有关定价的更多信息,请参见本页

关于 Terality 需要知道的一些事情:

  • pandas API 的 100%覆盖率。
  • Terality 在现有的pandas功能的基础上提供了两个新方法:to_csv_folderto_parquet_folder。它们允许我们轻松地将原始数据集分割成多个更小的数据集。当将数据分割成块,然后分别分析每个块时,这个特性特别有用。
  • 由于该项目不是开源的,我们不能对其快速性能背后的底层架构说太多。我们所知道的是,Terality 团队开发了一个专有的数据处理引擎,因此它不是 Spark 或 Dask 的分支/风格。
  • 由于是托管的,所以不需要管理基础设施,内存实际上是无限的。
  • 它消除了pandas的可扩展性问题,Spark 的复杂性(设置+不同的语法),以及 Dask/Modin 的局限性。
  • 截至 2022 年 2 月,您可以将 Terality 与 Google Colab 结合使用。
  • Terality 是可自动扩展的——无论数据大小如何,我们的操作都会以极高的速度自动处理。不需要手动调整处理能力来匹配数据集的大小。所有的基础设施都是在 Terality 这边管理的,包括在你完成你的处理后关闭东西。
  • Terality 与针对您的数据的云存储解决方案(亚马逊 S3、Azure Data Lake 等)结合使用时效果最佳。).这是因为另一种选择是加载一个本地文件,这可能需要相当长的时间(取决于你的网速)。
  • 自然,在使用具有潜在敏感数据的第三方解决方案时,会有约束/限制,尤其是在涉及公司数据时。就安全性而言,Terality 提供了安全隔离,我们的数据在传输和计算过程中都得到了充分保护。更多安全信息请参考本网站
  • 不久前,Terality 可以部署在您自己的 AWS 帐户中。由于这种自托管部署,您的数据永远不会离开您的 AWS 帐户。这种功能可以帮助您遵守数据保护要求,并消除对数据安全性的任何疑虑。

有用参考:

结论

不要误解我,pandas是一个很棒的工具,我每天都在使用并将继续使用。然而,对于某些特定的用例来说,这可能是不够的。这就是为什么在这篇文章中,我提供了最流行的pandas选择的概述。

让我先回答一个你可能会想到的问题:哪个解决方案是最好的?你可能已经猜到了,答案是:视情况而定。如果你想简单地在本地机器上加速你的pandas代码,也许 Modin 是一个很好的起点。如果已经有 Spark 集群在运行,可以试试 py Spark/考拉。对于计算一些统计数据或可视化大规模数据集,Vaex 可能是一个很好的起点。或者,如果您想使用最大速度,而不必担心设置任何基础设施,那么 Terality 可能是一个不错的选择。

最后但同样重要的是,如果没有某种基准测试的参考,这样的文章是不完整的。说到纯速度,H2O.ai 为大部分用于数据处理的 Python 库准备了这样的一个基准测试。为了评估这些库,他们对 2 个数据集进行了数据聚合和连接。对于这些任务,他们使用了不同大小的数据集:0.2 GB、5 GB 和 50 GB。

你对文章中提到的图书馆有经验吗?还是我错过了一个你知道的图书馆?我很想听听你的经历!你可以在推特或评论中联系我。

喜欢这篇文章吗?成为一个媒介成员,通过无限制的阅读继续学习。如果你使用这个链接成为会员,你将支持我,而不需要额外的费用。提前感谢,再见!

您可能还会对以下内容感兴趣:

</8-more-useful-pandas-functionalities-for-your-analyses-ef87dcfe5d74> </9-useful-pandas-methods-you-probably-have-not-heard-about-28ff6c0bceee>

熊猫还不够。学习这 25 个熊猫到 SQL 的翻译来升级你的数据分析游戏

原文:https://towardsdatascience.com/pandas-isnt-enough-learn-these-25-pandas-to-sql-translations-to-upgrade-your-data-analysis-game-af8d0c26948d

熊猫中常见的 25 种 SQL 查询及其对应方法。

照片由詹姆斯·亚雷马Unsplash 上拍摄

这是我第 50 篇关于媒介的文章。非常感谢你阅读和欣赏我的作品😊!这绝对是一次收获颇丰的旅程。

如果你喜欢在 Medium 上阅读我的文章,我相信你也会喜欢这个:https://avichawla.substack.com/【数据科学的每日剂量】。

****这是什么?这是一份面向数据科学的出版物,我在 substack 上运营。

****你会从中得到什么?在这里,我介绍了关于数据科学/Python/机器学习等的优雅而方便的技巧和诀窍。,一天一个提示(见出版档案此处)。如果你感兴趣,你可以在你的收件箱里订阅每日剂量。而且完全免费。很想看到另一面!

动机

SQL 和 Pandas 都是数据科学家处理数据的强大工具。

众所周知,SQL 是一种用于管理和操作数据库中数据的语言。另一方面,Pandas 是 Python 中的一个数据操作和分析库。

此外,SQL 通常用于从数据库中提取数据,并为 Python 中的分析做准备,主要使用 Pandas,它提供了广泛的工具和函数来处理表格数据,包括数据操作、分析和可视化。

SQL 和 Pandas 一起可用于清理、转换和分析大型数据集,并创建复杂的数据管道和模型。因此,精通这两种框架对于数据科学家来说非常有价值。

因此,在这篇博客中,我将提供一个快速指南,将最常见的 Pandas 操作翻译成它们的等效 SQL 查询。

我们开始吧🚀!

资料组

出于演示目的,我使用 Faker 创建了一个虚拟数据集:

随机员工数据集(图片由作者提供)

#1 读取 CSV 文件

熊猫

CSV 通常是读取熊猫数据帧最流行的文件格式。这是在熊猫身上用pd.read_csv()方法完成的。

结构化查询语言

要在数据库中创建表,第一步是创建一个空表并定义其模式。

下一步是将 CSV 文件的内容(如果第一行是标题,从第二行开始)转储到上面创建的表中。

输出

创建数据帧/表后,我们得到以下输出:

读取 CSV 后的输出(图片由作者提供)

#2 显示前 5(或 k)行

熊猫

我们可以在熊猫身上使用df.head()方法。

结构化查询语言

在 MySQL 语法中,我们可以在select 后使用limit,指定我们想要显示的记录数。

#3 打印尺寸

熊猫

DataFrame 对象的shape属性打印行数和列数。

结构化查询语言

我们可以使用 count 关键字来打印行数。

#4 打印数据类型

熊猫

您可以使用dtypes参数打印所有列的数据类型:

结构化查询语言

在这里,您可以打印如下数据类型:

#5 修改列的数据类型

熊猫

这里,我们可以使用如下的astype()方法:

结构化查询语言

使用ALTER COLUMN改变列的数据类型。

上述操作将永久修改表中列的数据类型。但是,如果您只是想在过滤时这样做,请使用cast

# 6–11 过滤数据

有各种方法来过滤熊猫的数据帧。

#6: 您可以按如下方式过滤一列:

上述内容可以转换为 SQL,如下所示:

#7: 此外,您还可以过滤多个列:

上述过滤的 SQL 等价物是:

#8: 您还可以使用isin()从值列表中进行筛选:

为了模拟上述情况,我们在 SQL 中使用了in关键字:

#9: 在 Pandas 中,您还可以使用点运算符选择特定的列。

在 SQL 中,我们可以在select之后指定所需的列。

#10: 如果您想在 Pandas 中选择多个列,您可以执行以下操作:

通过在 SQL 中的select之后指定多个列也可以做到这一点。

你也可以根据熊猫的 NaN 值进行过滤。

我们没有 NaN 值,所以我们看不到行。

这同样可以扩展到 SQL,如下所示:

我们还可以执行一些复杂的基于模式的字符串过滤。

在 SQL 中,我们可以使用LIKE子句。

您也可以在字符串中搜索子字符串。例如,假设我们想要查找所有last_name包含子字符串“an”的记录。

在熊猫身上,我们可以做到以下几点:

在 SQL 中,我们可以再次使用LIKE子句。

# 14–16 排序数据

排序是数据科学家用来对数据进行排序的另一种典型操作。

熊猫

使用df.sort_values()方法对数据帧进行排序。

您也可以按多列排序:

最后,我们可以使用ascending参数为不同的列指定不同的标准(升序/降序)。

这里,ascending对应的列表表示last_name降序排列,level升序排列。

结构化查询语言

在 SQL 中,我们可以使用order by子句来实现。

此外,通过在order by子句中指定更多的列,我们可以包含更多的列作为排序标准:

我们可以为不同的列指定不同的排序顺序,如下所示:

#17 填充 NaN 值

对于这一个,我有意删除了 salary 列中的几个值。这是更新后的数据框架:

熊猫

在 Pandas 中,我们可以使用fillna()方法来填充 NaN 值:

结构化查询语言

然而,在 SQL 中,我们可以使用 case 语句来实现。

# 18–19 连接数据

熊猫

如果你想用一个连接键合并两个数据帧,使用pd.merge()方法:

结构化查询语言

连接数据集的另一种方法是将它们连接起来。

熊猫

考虑下面的数据框架:

在 Pandas 中,您可以使用concat()方法并传递 DataFrame 对象以连接成一个列表/元组。

结构化查询语言

使用 SQL 中的UNION(只保留唯一的行)和UNION ALL(保留所有行)也可以达到同样的效果。

#20 分组数据

熊猫

要对数据帧进行分组并执行聚合,请在 Pandas 中使用groupby()方法,如下所示

结构化查询语言

在 SQL 中,可以使用 group by 子句并在 select 子句中指定聚合。

我们确实看到了相同的输出!

# 21–22 寻找独特的价值

熊猫

要打印一列中的不同值,我们可以使用unique()方法。

要打印不同值的数量,使用nunique()方法。

结构化查询语言

在 SQL 中,我们可以如下使用select中的DISTINCT关键字:

为了计算 SQL 中不同值的数量,我们可以将COUNT聚合器包装在distinct周围。

#23 重命名列

熊猫

这里,使用df.rename()方法,如下所示:

结构化查询语言

我们可以使用ALTER TABLE来重命名一个列:

#24 删除列

熊猫

使用df.drop()方法:

结构化查询语言

类似于重命名,我们可以使用ALTER TABLE,将RENAME改为DROP

#25 创建新列

假设我们想要创建一个新列full_name,它是列first_namelast_name的串联,中间有一个空格。

熊猫

我们可以在 Pandas 中使用一个简单的赋值操作符。

结构化查询语言

在 SQL 中,第一步是添加新列:

接下来,我们使用 SQL 中的SET设置该值。

||在 Sqlite 中用作串联运算符。延伸阅读

结论

恭喜你!您现在知道了 Pandas 中最常见方法的 SQL 翻译。

我试着翻译了科学家们经常在熊猫身上使用的大部分数据。然而,我知道我可能错过了一些。

请在回复中让我知道。

一如既往的感谢阅读!

由作者使用稳定扩散生成和编辑的图像。

🚀订阅数据科学每日剂量。在这里,我分享关于数据科学的优雅技巧和诀窍,一天一个技巧。每天在你的收件箱里收到这些提示。

🧑‍💻成为数据科学专家!获取包含 450 多个熊猫、NumPy 和 SQL 问题的免费数据科学掌握工具包。

获取机器学习领域排名前 1%的研究论文、新闻、开源回购、推文的每周汇总。

熊猫多指数解释

原文:https://towardsdatascience.com/pandas-multi-index-explained-6f11d880c0e5

分级索引

在前几章中,我们看到了如何使用索引。我们要么使用DataFrame中提供的默认索引,要么将一列转换成索引。

https://battox.medium.com/a-thorough-intro-to-pandas-for-data-analysis-ii-d09cbbf67cd

现在,我们将看到 Pandas 提供的更多可能性,以通过索引探索一个DataFrame,以及我们如何能够拥有多个索引。

在下面的例子中,我们将使用一个数据集,该数据集包含基于马克斯·普朗克进化人类学研究所的世界语言结构图集的 CSV 文件中的世界人类语言信息。

import pandas as pd

# We import a CSV dataset.
df = pd.read_csv('human_natural_languages.csv')

# And we are going to convert the column "genus" into index.
df.set_index('genus', inplace=True)

**inplace**论证****

我们可以用来修改一个DataFrame的许多方法会产生一个新的DataFrame,并保持原来的完整。用参数inplace=True我们修改原来的DataFrame

df.set_index(column, inplace=True)

现在DataFrame有了新的索引。如果不使用inplace=True,我们也可以做得差不多:

df = df.set_index(column)

这就是我们在上一章中所做的将列转换成索引。

我们可以用“genus”作为索引快速浏览一下DataFrame,我们会注意到设置索引保留了行的原始顺序(在这种情况下是列“Name”的字母顺序)。请记住,当我们将一个列转换为索引时,即使它看起来像一个列,它的行为却并不如此。

图片作者。

例如,我们可以通过使用loc来选择索引的一个类别。如果我们选择的标签出现不止一次,我们将看到几行。

图片作者。

或者几类。

图片作者。

在表格的开始将会有一种爱尔兰语,到最后,将会有日耳曼语。

每当我们需要将索引变回列时,我们将使用DataFrame.reset_index()。索引一直存在:当它不是数据集的一列或我们定义的另一个索引时,默认情况下将生成一个索引(标签与位置匹配)。

图片作者。

**注意“genus”也是一个列(在第一个位置)。

如果我们没有兴趣将索引(属)保存为一列,我们可能会将drop=True参数添加到reset_index()中,它可能会被丢弃。**

DeepAI text2img 模型生成的熊猫套娃。

多指数

Pandas 的优点在于它能够将多列或者多行转换成索引。那叫多指标。多索引将包含许多索引级别,因此,将建立索引级别的层次结构。

尽管能够将多列的内容转换成索引,但我们不能认为现在一行有几个索引,这一点很重要。多指标还是一个。有层次。

图片作者。

在这种情况下,“家庭”是指数化的第 0 级,并且优先于指数化的第 1 级“元音目录”。我们现在可以通过名称或级别编号来引用级别。

图片作者。

FrozenList有点像普通的list,现在不用担心。

索引可以有名称,也可以没有名称。大多数情况下,并不是这样:例如,我们到目前为止使用的自动生成的整数索引是在没有名称的情况下创建的。提升为索引的列将列的名称转换为索引的名称,因此索引有了名称,可以修改或删除该名称。

元组

在全面了解多索引之前,我们需要介绍 Python 的另一个本机对象tuple

**一个tuple和一个list的区别在于列表是可变的,而元组不是;换句话说,我们可以在创建了一个list之后添加或删除元素,但是我们不能对一个tuple做这样的事情。一旦创建,如果我们想要不同的东西,我们需要创建另一个tuple

我们创建它们的方式类似于创建list的方式,但我们将使用圆括号(圆括号)代替方括号。**

name_of_tuple = (element_a, element_b)

**DataFrame的每一行现在用一个tuple索引。在前面的例子中,当我们使用“单一”索引时,行是由字符串标签索引的。将tuple视为多索引行或列的标签。

在多指标中,tuple的元素和指标级别一样多。例如,如果多索引有三个级别,这样的元组将被成形为(level_0, level_1, level_2)。**

图片作者。

如果你需要检索一个级别的值,Pandas 会帮你搞定MultiIndex.get_level_values()

图片作者。

让我们试一试。我们从 0 级“家族”中选择印欧语,从 1 级“元音库存”中选择元音库存为 5 或 6 的语言。

图片作者。

让我们假设这一次我们不想要印欧语系的语言,而是阿拉瓦坎语和乌拉尔语系的语言。我们没有使用单个标签(将其解释为单个tuple),而是传递一个list标签(实际上是一个tuple列表)。

图片作者。

这是我们刚才所做的另一种方式。

图片作者。

现在有一个小挑战:让我们假设我们想要从级别 0 的所有行中的中找到级别 1 的结果(例如,小元音库存)(我们实际上忽略了级别 0)。当然,我们可以将 0 级变回列,但是不行,我们不想修改多索引。所以,也许,我们会尝试检索 0 级的所有标签和 1 级的一个标签。我们可以尝试通过使用数据集的全范围slice ( first_row:last_row)或仅使用冒号:来做到这一点。****

row_labels = (:, '1 Small (2-4)')

**不。这个语法不正确。slice仅在索引器[](方括号)内工作,不与任何其他对象(本例中为tuple)一起工作。

为此,熊猫提供了一种叫做IndexSlice的特殊型号来代替tuple s。**

图片作者。

有些人喜欢将IndexSlice分配给一个较短的标识符,因为他们知道会多次使用它。

图片作者。

横截面

使用DataFrame.xs()方法是绕过层级的一种简便方法。它的缺点是只为选择而不为分配而工作。当我们需要改变帧值时,我们必须照常使用loc / iloc。****

图片作者。

部分选择

**分层索引的一个重要特性是,您可以通过部分标签选择数据,识别数据中的子组。

如果我们只对多索引的更高级别感兴趣,而不是通过pd.IndexSlice['Indo-European', :]来选择行,比如我们将一个级别为 0 的标签和所有级别为 1 的标签作为目标,我们可以简单地使用'Indo-European'

非常重要:部分选择会在结果中删除层次索引的级。**

图片作者。

整理

**这个DataFrame仍然是按照“名称”栏的字母顺序组织的。为了对值进行不同的排序,我们使用了DataFrame.sort_values(),它能够使用一个或多个列和/或索引的内容对行进行排序。

我们将按照“家族”(索引级别 0)对DataFrame进行排序,按字母升序排列。如果“family”是一个简单的列,而不是一个索引级别,那么方法是相同的。**

图片作者。

**现在DataFrame从亚非语系的语言开始,显示的第一种语言是库洛语😃。

如前所述,我们也可以通过非索引列对DataFrame进行排序,例如,通过语法案例的数量。此外,我们将通过添加ascending=False按降序排列。**

图片作者。

注意,为了通过索引对值进行排序,我们需要通过它的名称来引用它,正如我们之前所说的,有时索引没有名称。我们可能还想使用索引的位置/级别,而不是名称。然后就是方法DataFrame.sort_index()派上用场的时候了(自然,它对列不起作用)。

图片作者。

sort_index()的另一个有趣的用例是当我们想要按所有级别排序时(不需要指定名称和层次)。

图片作者。

它首先使用级别 0 对行进行排序,然后使用级别 1。存在重复的 0 级值,这些行很难排序。第二层消除了歧义。

重新命名

**我们可以更改(行)索引标签或列标签的名称。为此,我们将使用DataFrame.rename()方法。

当我们想要改变一些列的名称时,我们传递一个带有旧名称到新名称映射的dict。因为rename()对两个轴都有效,所以指定我们要瞄准哪一个是很重要的,我们用index(对于行)和columns参数(非此即彼)这样做,就像这样**

df.rename( columns={old_name: new_name, ...} )

还有一种指定轴的方法,不仅在rename()中使用,在许多其他方法中也使用。它在一个方向或另一个方向上起作用,值得了解:论点axis。它对行使用值0(默认情况下),对列使用值1。为了通过使用axis获得前一个例子的结果,我们应该这样写:

df.rename( {old_name: new_name, ...}, axis=1 )

图片作者。

要更改(行)索引中的名称,我们使用rename(index={...})。从旧到新的映射适用于所有级别,无论是多少级别或哪个级别。如果有必要将更改限制在某些级别(在我们想要更改的标签出现在几个级别的情况下),我们可以使用参数levels

图片作者。

额外奖金

**我们被告知,一些讲 Kullo 语的人不太喜欢这个名字,他们更喜欢把他们的语言称为 Dawro。我们想重命名数据集中出现的 Kullo,但是方法rename()不行,因为“语言”不是索引而是列。

无论我们想做什么,熊猫总有办法做到:DataFrame.replace()行为超过框架值。**

图片作者。

结论

当使用多索引时,DataFrame的每一行和/或每一列都用代表索引级别的tuple进行索引,而不是像简单索引那样用字符串进行索引。

# Let us suppose that we are dealing with a 3-level multi-index at the rows.
rows = [('big', 'square', 'blue'), ('small', 'square', 'green')]

# Retrieve rows with big blue and small green squares multi-index
# and all columns.
df.loc[rows, :]

当我们需要使用slice来表示所有的等级值(或它们的范围)时,我们使用IndexSlice来代替。

 # Now imagine that we are interested in all the figures
# that are blue or green.
idx = pd.IndexSlice[:, :, ('blue', 'green')]

# Retrieve intended rows and all columns.
df.loc[idx, :]

我喜欢的熊猫文章

  • 看看杰西卡·斯特林汉姆写的这个很酷的多索引备忘单。

方法列表

在本教程中看到的方法的简短列表。

  • set_index()
  • reset_index()
  • Index.get_level_values()
  • xs() —横截面。
  • sort_values()
  • sort_index()
  • rename() —更改索引标签。
  • replace() —改变数值。

和一些方便的看不见的方法。

  • rename_axis() —更改一个IndexMultiIndex的名称。
  • swaplevel() —切换两个MultiIndex级别的顺序。
  • Index.set_names() —与rename_axis()相似,但直接在Index上。

pandas Profiling——Python 中简单的探索性数据分析

原文:https://towardsdatascience.com/pandas-profiling-easy-exploratory-data-analysis-in-python-65d6d0e23650

使用 Pandas Profiling 库实现快速有效的 EDA

照片由 Unsplash 上的agency followeb拍摄

探索性数据分析(EDA) 是数据科学和机器学习工作流程中重要且必不可少的部分。它允许我们从多个角度,通过统计、数据可视化和数据汇总来探索数据,从而熟悉数据。这有助于发现数据中的模式,发现异常值,并获得对我们正在处理的数据的可靠理解。

在这个简短的 Python EDA 教程中,我们将介绍一个名为 Pandas Profiling 的优秀 Python 库的使用。这个库帮助我们用最少的代码行在数据集上实现快速和自动的 EDA。

在本文中,我们将涵盖:

  1. 什么是探索性数据分析(EDA)?
  2. 什么是 Python 熊猫剖析库?
  3. 如何使用 Pandas Profiling 库进行探索性数据分析

在我的 YouTube 频道上也有这个教程的视频版本,可以在下面看到:

本教程中使用的笔记本可以从我的 GitHub 资源库下载,链接如下:

https://github.com/andymcdgeo/ExploratoryDataAnalysis_YT

什么是探索性数据分析(EDA)?

探索性数据分析,简称 EDA,简单来说就是‘先看数据’。它构成了机器学习工作流程的一个关键部分,正是在这个阶段,我们开始理解我们正在处理的数据及其包含的内容。本质上,它允许我们在应用高级分析和机器学习之前理解数据。

在 EDA 过程中,我们可以开始识别数据中的模式,了解特征之间的关系,识别数据集内可能存在的异常值识别我们是否有缺失值。一旦我们了解了数据,我们就可以检查是否需要进一步的处理,或者是否有必要清理数据。

当使用 Python 时,或者如果您正在学习 Python 培训课程,您通常会使用 pandasmatplotlib 对您的数据执行 EDA。 Pandas 具有包括[df.describe()](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.describe.html)[df.info()](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.info.html)在内的多个函数,这些函数有助于汇总数据集的统计数据,而 matplotlib 具有多个图表,例如条形图散点图直方图,以使我们能够可视化我们的数据。

当处理机器学习或数据科学训练数据集时,上述方法可能是令人满意的,因为许多数据已经过清理和设计,使其更容易处理。在现实世界的数据集中,数据通常是脏的,需要清理。使用上述方法进行检查是一项耗时的任务。这就是 auto EDA 可以帮助我们在不影响质量的情况下加快这部分工作流程的地方。

熊猫 Profiling Python 库是什么?

Pandas Profiling 是一个 Python 库,它允许你在我们的 Pandas 数据框架上生成一个非常详细的报告,而不需要用户太多的输入。它

根据 PyPi 的统计,该库每月有超过 1,000,000 次下载,这证明了它在数据科学领域是一个非常受欢迎的库。

熊猫下载-根据 PyPistats 分析 python 库。图片由作者提供。

如何在 Python 中为 EDA 使用 Pandas Profiling

安装熊猫档案

要安装 Pandas Profiling,您可以使用以下命令:

如果使用画中画:

pip install pandas-profiling

如果使用 Anaconda 的 conda 包管理器:

conda env create -n pandas-profiling
conda activate pandas-profiling
conda install -c conda-forge pandas-profiling

资料组

我们在本教程中使用的数据集来自澳大利亚政府的国家海洋石油管理系统(NOPIMS)

澳大利亚测井数据网站。图片由作者提供。

它包含一系列由科学仪器获得的测井测量数据,用于评估和表征地下的地质和岩石物理性质。

不要担心数据的性质,因为下面描述的技术可以应用于任何数据集。

导入库

第一步是导入我们将要使用的库(熊猫和熊猫档案),如下所示:

import pandas as pd
from pandas_profiling import ProfileReport

加载数据集

接下来,我们加载我们要探索的数据。在这种情况下,我们的数据存储在一个 csv 文件中,需要使用 pd.read_csv 读入该文件,如下所示:

df = pd.read_csv('data/NOPIMS_Australia/Ironbank-1.csv', na_values=-999)

由于我们的数据包含由-999 表示的 null / missing 值,我们可以告诉 pandas 将这些值设置为而不是数字(NaN)

运行熊猫档案

为了生成报告,我们首先创建一个名为 report 的变量,并将ProfileReport()赋给它。在括号内,我们传入数据帧,在本例中是df

然后,我们可以调用报告并开始该过程。

report = ProfileReport(df)
report

当我们运行该单元时,报告过程将开始,并分析数据框架中的所有数据。

时间长度将取决于数据的大小,较大的数据集将需要较长的时间来完成。

使用 Python 中的 pandas-profiling 生成报告。图片由作者提供。

了解熊猫概况分析结果

概观

概述部分包含三个选项卡:概述、警告和再现。

Overview 选项卡提供关于数据集的统计信息,包括变量数量(数据帧中的列数)、观察值数量(总行数)、有多少值与百分比一起丢失、有多少重复以及文件大小。

熊猫概况报告的概述部分。图片由作者提供。

警报选项卡用于通知您数据中每一列的任何问题,例如变量之间的相关性、数据偏斜度、数据分布。

pandas-profiling 报告的警报部分。图片由作者提供。

变量

在报告的变量部分,我们可以查看数据帧中包含的每个列的详细统计信息。这包括有多少缺失值、数据的统计(平均值、最小值和最大值)等等。

在每个部分的右侧,我们可以看到数据分布的直方图。这给了我们一个数据偏斜度的指示,以及它的传播。

pandas-profiling 报告的变量部分。图片由作者提供。

相互作用

报告的交互部分允许您绘制一个变量与另一个变量的关系图,以便了解它们之间的关系。

熊猫档案报告的互动部分。图片由作者提供。

相关

相关性部分让我们了解两个变量相互关联的程度。在 pandas_profile 报告中,我们可以查看不同的关联方法:

  • 斯皮尔曼的ρ
  • 皮尔逊氏 r
  • 肯德尔的τ
  • Phik (φk)

如果您不确定每种方法是什么,您可以单击“切换相关性描述”按钮,它将提供每种方法含义的详细信息。

pandas-profiling 报告的相关部分显示了可用的不同方法。图片由作者提供。

缺少值

我们还可以很好地了解我们的数据集有多完整。这类似于提供的功能,缺少 Python 库。

我们可以使用 4 种类型的图来查看数据:

  • 计数图提供了当前总值的计数。
  • 矩阵图显示了数据框中缺失值的位置。
  • 热图向我们展示了变量之间的空值是如何关联的。
  • 树状图是一个类似树的图形,它显示了变量之间有多少空值相关。越靠近的组表示无效性的强相关性。

使用 pandas-profiling 报告通过各种视图识别缺失值。图片由作者提供。

样品

最后,示例部分允许我们查看数据集前 10 行和后 10 行的原始数字。这相当于运行df.head(10)df.tail(10)

数据集的前 10 行。图片由作者提供。

数据集的最后 10 行。图片由作者提供。

摘要

pandas-profiling Python 库是一个非常棒的工具,可以快速分析数据集,而无需花费大量时间记忆和编写 pandas 和 matplotlib 代码。一定要为你的下一个项目去看看。

感谢阅读。在你走之前,你一定要订阅我的内容,把我的文章放到你的收件箱里。 你可以在这里做!

其次,通过注册会员,你可以获得完整的媒介体验,并支持我自己和成千上万的其他作家。它每个月只花你 5 美元,你可以完全接触到所有令人惊叹的媒体文章,也有机会用你的写作赚钱。如果你用 我的链接报名,你会直接用你的一部分费用支持我,不会多花你多少钱。如果你这样做了,非常感谢你的支持!

如何去除熊猫身上不需要的部分

原文:https://towardsdatascience.com/pandas-remove-unwanted-parts-from-strings-92ac881e60a2

在 Pandas 中从列中删除不需要的子字符串

照片由波莱特·伍滕Unsplash 上拍摄

介绍

当使用 pandas 时,我们通常需要执行一些预处理任务,以便将数据转换成所需的形式。作为这一步骤的一部分,通常需要执行的一项常见任务涉及到字符串列的转换,我们会消除一些不需要的部分。

在今天的简短教程中,我们将讨论几种可能的方法,您最终可以应用于 pandas 数据帧,以便从某些列的字符串中删除任何不需要的部分。

首先,让我们创建一个示例 DataFrame,我们将在本文中引用它来演示一些概念,并展示如何在使用字符串列时获得预期的结果。

import pandas as pd df  = pd.DataFrame(
    [
        (1, '+9A', 100),
        (2, '-1A', 121),
        (3, '5B', 312),
        (4, '+1D', 567),
        (5, '+1C', 123),
        (6, '-2E', 101),
        (7, '+3T', 231),
        (8, '5A', 769),
        (9, '+5B', 907),
        (10, '-1A', 15),
    ],
    columns=['colA', 'colB', 'colC']
) print(df)
 ***colA colB  colC***
*0     1  +9A   100
1     2  -1A   121
2     3   5B   312
3     4  +1D   567
4     5  +1C   123
5     6  -2E   101
6     7  +3T   231
7     8   5A   769
8     9  +5B   907
9    10  -1A    15*

让我们假设我们想要转换存储在列colB下的数据,以便移除前缀符号-/+和后缀字母。

用熊猫。Series.str.replace()方法

[pandas.Series.str.replace()](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.str.replace.html)方法可以用来替换Series / Index中每一次出现的模式/正则表达式。

在我们的例子中,我们可以指定一个正则表达式将所有非数字值替换成一个空字符串。因此,下面的表达式可以解决这个问题:

df['colB'] = df['colB'].str.replace(r'\D', '')print(df)
 ***colA colB  colC*** *0     1    9   100
1     2    1   121
2     3    5   312
3     4    1   567
4     5    1   123
5     6    2   101
6     7    3   231
7     8    5   769
8     9    5   907
9    10    1    15*

在使用正则表达式时,这无疑是众多选择之一。我猜你可以变得很有创造性,但是这个正则表达式应该是我们特定用例中最直接的。

另一个适合我们用例的选项是删除任何非整数字符:

df['colB'].str.replace(r'[^0-9]', '')

用熊猫。Series.str.extract()方法

在 pandas 中,从字符串中删除不需要的部分的另一个选择是[pandas.Series.str.extract()](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.str.extract.html)方法,该方法用于提取 regex pat 中的捕获组作为 DataFrame 中的列。

在我们的例子中,我们将简单地提取字符串中我们希望保留的部分:

df['colB'] = df['colB'].str.extract(r'(\d+)', expand=False)print(df)
 ***colA colB  colC*** *0     1    9   100
1     2    1   121
2     3    5   312
3     4    1   567
4     5    1   123
5     6    2   101
6     7    3   231
7     8    5   769
8     9    5   907
9    10    1    15*

用熊猫。Series.replace()方法

[pandas.Series.replace()](https://pandas.pydata.org/docs/reference/api/pandas.Series.replace.html)是另一种选择,与我们在本教程中讨论的第一种方法非常相似。该方法将把to_replace中给定的值替换为 value,并允许用户指定提供给to_replace的值是否应该解释为正则表达式。

df['colB'] = df['colB'].replace(r'\D', r'', regex=True)

使用 map()方法

另一种选择是利用[map()](https://pandas.pydata.org/docs/reference/api/pandas.Series.map.html)方法,该方法可用于根据输入映射或函数来映射熊猫Series的值。

在我们的具体示例中,我们可以使用map()来应用一个 lambda 函数,该函数从字符串的开头移除+/-,从字符串的结尾移除任何 ascii 字符。

from string import ascii_lettersdf['colB'] = \
    df['colB'].map(lambda x: x.lstrip('+-').rstrip(ascii_letters))print(df)
 ***colA colB  colC*** *0     1    9   100
1     2    1   121
2     3    5   312
3     4    1   567
4     5    1   123
5     6    2   101
6     7    3   231
7     8    5   769
8     9    5   907
9    10    1    15*

同样,在处理这种类型的操作时,您可以变得非常有创造性,因此可以随意尝试最适合您的特定用例的方法。

你也可以在我最近的一篇文章中读到更多关于map()apply()applymap() pandas 方法的内容。

使用列表理解

当处理大型熊猫数据帧时,您应该始终考虑矢量化,因为这将极大地提高执行操作的效率。

大多数时候,字符串函数可能很难向量化,因此它们可能以迭代的方式执行(这不是最好的方法)。

一个可能解决这个问题的好方法是列表理解。例如,代替str.replace(),我们可以使用re.sub()方法作为列表理解。

[*re.****sub****(pattern, repl, string, count=0, flags=0)*](https://docs.python.org/3/library/re.html#re.sub)

返回用替换*repl*替换*string*图案最左边不重叠出现的*string*。如果没有找到*pattern*,则不变地返回*string**repl*可以是字符串,也可以是函数;如果它是一个字符串,其中的任何反斜杠转义都会被处理。

import redf['colB'] = [re.sub('[^0-9]', '', x) for x in df['colB']]

或者,str.extract()方法可以用re.search()表示为列表理解:

import redf['colB'] = [re.search('[0-9]', x)[0] for x in df['colB']]

最后的想法

在今天的简短教程中,我们讨论了如何处理 pandas 数据帧,以便从包含 string 对象的列中截断任何不需要的部分。

请注意,在对相当大的数据帧应用这种转换时,应该考虑每种方法的性能。性能通常因数据帧的大小而异,例如,一种方法在应用于小数据帧时可能最有效,但在应用于大数据帧时就不那么有效了。

因此,尝试不同的方法并为您的特定用例应用最有效的方法总是有益的。

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

https://gmyrianthous.medium.com/membership

相关文章你可能也喜欢

Pandas Sidetable 简化了探索性数据分析过程

原文:https://towardsdatascience.com/pandas-sidetable-simplifies-the-exploratory-data-analysis-process-417b42eebed6

熊猫的实用附加产品

照片由维达尔·诺德里-马西森Unsplash 拍摄

Sidetable 是由 Chris Moffitt 创建的开源 pandas 实用程序库。它为 pandas 数据框架创建汇总表,这在探索性数据分析中非常有用。

sidetable 所做的也可以在熊猫身上实现,但是它简化了这个过程。您只需一行代码就可以获得数据的大致情况。

在撰写本文时,sidetable 有 5 个功能,并且正在改进中。因此,我们可以期待更多的即将到来。我们将为每个函数做一些示例,以理解它做什么以及它如何有助于有效的探索性数据分析。

您可以按如下方式安装 sidetable:

# terminal
$  python -m pip install -U sidetable# jupyter notebook
!pip install sidetable

安装后,您可以导入 sidetable 并将其用作 pandas 数据帧上的访问器。

import pandas as pd
import sidetable

我们将使用我用模拟数据创建的销售数据集。你可以从我的 GitHub 页面上的数据集库中下载。我们将在本文中使用的数据集称为商店销售数据。

sales = pd.read_csv(
  "sales_data_with_stores.csv",
   usecols = ["store", "product_group", "product_code", "cost",  
              "price", "last_week_sales"]
)sales.head()

(图片由作者提供)

类固醇的价值统计

假设我们想从产品组的角度获得一个概述,并寻找以下问题的答案:

  • 存在多少个产品组?
  • 就每个产品组包含的产品数量而言,每个产品组的规模有多大?
  • 整个投资组合的累积覆盖率是多少?

我们可以通过使用 freq 函数的一行代码找到所有这些问题的答案。

sales.stb.freq(["product_group"])

(图片由作者提供)

有 6 个产品组,我们可以看到每个组包含多少产品。此外,结果表具有累积值。例如,PG4、PG5 和 PG6 占我们整个产品组合的 85%。

上表基于数据帧中的行数。比方说,我们希望看到销售的分布情况。这可以通过使用 freq 函数的 value 参数轻松完成。

sales.stb.freq(["product_group"], value="last_week_sales")

(图片由作者提供)

就产品数量而言,PG3 是第三大产品组,但其销售额最高。这对我们的分析是一个有价值的见解。

我们可能希望进一步分析,并分别查看每个商店产品组的表现。我们需要做的就是在 Python 列表中编写产品组和存储列。

sales.stb.freq(["product_group", "store"], value="last_week_sales")

一次 6 个问题

  • 有多少观察值(即行数)?
  • 有多少独特的价值?
  • 最常值?
  • 最频繁值有多少次观察?
  • 最不频繁的值?
  • 最不频繁值有多少个观察值?

使用 counts 函数,用一行代码就可以回答所有这些问题。

默认情况下,我们获得所有列的结果。但是,这些问题通常针对分类列。因此,如果我们只需要分类列的数据,我们可以使用 exclude 参数排除数值列。

sales.stb.counts(exclude="number")

(图片由作者提供)

让我们对 store 列做一个快速的结果检查:

sales["store"].value_counts()**# output**
Daisy     470
Violet    330
Rose      200
Name: store, dtype: int64

最常见的值是 daisy,它有 470 个观察值,这就是上面的 sidetable 所示。

缺少值

用熊猫寻找缺失的价值观并不是一件复杂的事情。isna 和 sum 函数可以一起使用来查找每列中缺失值的数量。sidetable 增加的是丢失值的百分比。

sales.stb.missing()

(图片由作者提供)

缺失函数提供了数据帧中缺失值的快速概览。

如果只需要包含缺失值的列,请将 clip_0 参数的值设置为 True。

汇总值的小计

Sidetable 使得向 groupby 函数的结果添加小计变得非常简单。对于某些任务,小计可能会派上用场。让我们做有和没有小计的例子来看看区别。

sales_filtered = sales[sales["product_group"].isin(["PG1", "PG2"])]sales_filtered.groupby(["store", "product_group"]).agg(
    total_sales = ("last_week_sales", "sum")
)

不带小计(图片由作者提供)

sales_filtered.groupby(["store", "product_group"]).agg(
    total_sales = ("last_week_sales", "sum")
).stb.subtotal()

带小计(图片由作者提供)

需要注意的是,如果在 groupby 函数的输出中将分组显示为列(即,将 as_index 参数的值设置为 False),则不会计算小计。

用多级列索引展平数据帧

假设给我们一个数据帧,如下所示:

(图片由作者提供)

让我们检查列名:

df.columns**# output**
MultiIndex([('total_sales', 'PG1'),
            ('total_sales', 'PG2')],
           names=[None, 'product_group'])

它是一个多索引,每个列名是一个元组。我们可以通过使用 sidetable 的展平功能来展平列名。

df.stb.flatten()

(图片由作者提供)

它看起来更好,更容易工作。

Sidetable 是探索性数据分析中非常实用的工具。我们在本文中所做的操作并不复杂,但是简化它们并没有坏处。Sidetable 允许用一行代码执行所有这些操作。

你可以成为 媒介会员 解锁我的全部写作权限,外加其余媒介。如果你已经是了,别忘了订阅https://sonery.medium.com/subscribe如果你想在我发表新文章时收到电子邮件。

感谢您的阅读。如果您有任何反馈,请告诉我。

用于时间序列分析的熊猫把戏

原文:https://towardsdatascience.com/pandas-tricks-for-time-series-analysis-726618532172

用于分析熊猫时间序列的 3 个函数(带代码)

照片由卢卡斯·布拉塞克Unsplash 上拍摄

熊猫几乎不需要介绍。对于那些刚刚开始接触数据科学领域的人来说,Pandas 代表 Panel Data Analysis,是目前 Python 中数据转换的主要库。

除了很多其他的东西,我们还可以使用这个漂亮的包来分析时间序列。可以,有很多方法可以用。在这篇文章中,我们将会看到其中的一些。

时间序列

让我们首先商定什么是时间序列。

在连续时间内获得的一个量的一系列值,它们之间通常有相等的间隔。(牛津语言)

简单地说,时间序列是指我们在一段时间内测量的东西,比如说商店的销售额。因此,我们将从 1 月、2 月、3 月开始取值,以此类推,直到 12 月。这将为我们提供一个包含月份和销售额的数据集。这是一个时间序列。

熊猫创造的时间序列。图片由作者提供。

熊猫对处理时间序列有充分的准备。下一节将展示一些有助于我们分析的代码片段。

使用熊猫

咱们import numpy as npimport pandas as pd吧。

我们应该学习的第一件事是如何在熊猫中创建一组日期。这在生成用于训练目的的时间序列时非常有用。

因此,如果我们使用date_range(),就有可能创建我们想要的任意多的日期,使用任何需要的时间间隔。

# Create a date range of 7 days
pd.date_range("2022-01-01", periods=7, freq='D')**[OUT]:** DatetimeIndex(['2022-01-01', '2022-01-02', '2022-01-03', '2022-01-04','2022-01-05', '2022-01-06', '2022-01-07'],               dtype='datetime64[ns]', freq='D')

注意,输出是一个从我们的初始日期起 7 天的列表。还有许多其他频率可用作freq。最常见的是表示每周的"W”、表示月末日期的"M”、表示月初的"MS”以及其他许多可以在这里找到的

知道了这一点,我们可以继续创建一个数据集,用于本帖中的练习。我们将设置一个种子,这样您就可以重现与我相同的结果,然后我们创建一个包含每日日期和销售额的数据集,并将日期设置为索引。

# Setting a seed for reproductionability
np.random.seed(12)# Create dataset
df = pd.DataFrame({
 'date': pd.date_range("2022-01-01", periods=180, freq='D'),
 'sales': np.random.randint(1000, 10000, size=180)}) # Set index
df = df.set_index('date')

数据集头。图片由作者提供。

现在,我们将继续熊猫的功能代码。请注意,时间序列的一个重要特征是将日期作为索引。当我们在前面的代码中设置索引时,我们已经满足了这个要求。

重新取样

第一个有趣的函数是resample。它所做的是接受一个数据集或一个系列(一列),并根据作为参数提供的rule聚集数据。这是一种按日期按功能分组的方式。

我们的数据集日期频率是每天,对吗?如果要转化为月销售额呢?

我们可以用resample做到这一点,但是由于数据被聚合,我们还必须添加一个函数来处理聚合的数字。我们可以sum()或计算mean()median(),看月份的max()

# Resample by month end date
df.resample(rule= 'M').mean()

按月取平均值,并将指数设置为每月结束日期,结果如下。

按月平均。图片由作者提供。

现在让我们按周销售额来绘制汇总数据。

# Resample plot
df.resample('W').mean().plot(figsize=(15,5), title='Avg Weekly Sales');

平均每周销售额。图片由作者提供。

看起来我们的销售额在三月和四月之间有所下降,在六月中旬达到顶峰。

变化

Pandas 中的shift功能是让你在一列中上下移动数据。假设我们要将当天的销售额与前一天的销售额()以及当天的销售额与下周的销售额()进行比较。下面是如何使用shift完成的。

# Create a copy of the data
df_shift = df.copy()#Shift one day up
df_shift['next_day_sales'] = df_shift.sales.shift(-1)#Shift one week up
df_shift['next_week_sales'] = df_shift.sales.shift(-7)

看看我们如何拉动第二天的销售额和 7 天的销售额,将它们放在一起进行比较。

当天对次日对下周。图片由作者提供。

显然,如果需要,我们可以在这些列之间创建差异。

# Net gain/loss week over week
df_shift['one_week_net'] = df_shift.sales - df_shift.sales.shift(-7)

滚动平均值

下一个函数是从事股票交易的人常用的工具,即rolling平均值。事实上,这个功能是可以与其他功能结合使用的,不仅意味着,而且它恰好是最常见的。

这个函数创建一个窗口来聚集数据。因此,举例来说,如果我们创建 2 天的移动平均值,rolling将从数据集中提取连续 2 天的子集,并计算聚合结果,即最大值、中值或最常用的平均值。

# Calculate 2 days average with Pandas
df.rolling(2).mean()

在下图中,我们可以看到第一个值是一个NaN,因为没有足够的数据(本例中为 2 个数据点)来创建平均值。因此,对于第二个点,它获取原始数据集的前两行,并计算平均点。(6787 + 4325)/2 = 5556.

2 天移动平均线。图片由作者提供。

滚动平均非常常用于绘制数据,是趋势的一个很好的指示。子集周期越长,线越平滑。注意,100 天滚动平均线几乎是一条线,只是显示主要趋势,而 7 天平均线随着数据上下波动。

# Rolling plot
df.sales.plot(figsize=(25,8), legend=True, linestyle='--', color='darkgray')df.rolling(window=7).sales.mean().plot(legend=True, label='7 day average', linewidth=2)df.rolling(30).sales.mean().plot(legend=True, label='30 day average', linewidth=3)df.rolling(100).sales.mean().plot(legend=True, label='100 day average', linewidth=4);

实际数据(灰色)与 7、30 和 100 天平均值的比较。图片由作者提供。

在你走之前

我相信熊猫对我们分析任何数据都是有价值的,当然也包括时间序列。这篇文章中介绍的这三个函数可以帮助你开始时间序列分析。好好利用他们。

  • resample:将数据从日频率转换到其他时间频率。
  • shift:在列中上下移动数据,进行比较或计算。
  • rolling:创建滚动平均线,观察趋势。

如果你喜欢这些数据,请关注我。

http://gustavorsantos.medium.com/

如果你想成为中等会员,这里有一个介绍链接

参考

熊猫日期范围:https://pandas . pydata . org/docs/reference/API/pandas . Date _ Range . html

熊猫再取样:https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.resample.html

熊猫转移:https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.shift.html

熊猫滚滚:https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.rolling.html

Pandas vs Dask vs Datatable:处理 CSV 文件的性能比较

原文:https://towardsdatascience.com/pandas-vs-dask-vs-datatable-a-performance-comparison-for-processing-csv-files-3b0e0e98215e

熊猫可能不再是最好的选择了

马丁·雷施在 Unsplash 上的照片

W 说到处理 CSV 文件,每个人脑海中出现的第一个工具就是熊猫。毫无疑问,pandas 是一个伟大的框架,dataframe 提供了一种极其精简的数据表示形式,帮助我们更好地分析和理解数据。

最近做了一个任务,要求我合并 30K+ CSV 文件。我的神感是用熊猫,但是因为某些文件操作的表现不太顺利。在这篇文章中,我想和你分享另外两种选择,并比较它们和熊猫的行为和表现。最后,您将理解每个库的权衡,并能够做出正确的选择。

我对实验得出的一些结果感到非常惊讶,因为除非你实际尝试,否则无法预测。希望这篇文章可以节省你试错的时间,帮助你在选择库的时候做出更好的决定。

Dask

大多数数据分析 Python 库(如 Numpy、pandas 和 scikit-learn)的一个问题是,它们不能扩展到单台机器之外。Dask 是一个开源库,当您处理大型数据时,它为分析提供了高级并行化。它可以在需要时将这些分析包扩展到多核机器和分布式集群。它提供了一个与 pandas 相似的 API 接口,以确保一致性并最小化摩擦。

数据表

Datatable 是另一个考虑到性能的 Python 库。与 dask 不同,datatable 的目标是在一台单节点机器上以尽可能快的速度执行大型数据处理。同时,它与 pandas 的互操作性提供了轻松转换到另一个数据处理框架的能力。

这两个库都旨在提高 pandas 的性能,并保持与 pandas 相似的界面以方便使用。在接下来的几节中,我在我的 Macbook Pro(2.6 GHz 6 核英特尔酷睿 i7,16GB 内存)上进行了实验,在不同的环境中运行时,您可能会得到不同的结果。

读取单个 CSV 文件

让我们从最简单的操作开始——读取单个 CSV 文件。让我惊讶的是,在最基本的操作上,我们已经可以看到巨大的差异了。数据表比熊猫快 70%,而 dask 快 500%!结果是各种各样的数据帧对象,它们有非常相同的接口。

读取多个 CSV 文件

当我试图用熊猫来完成这个任务时,这就是我被卡住的地方。pandas 没有提供可以在一行中读取多个 CSV 文件的接口。唯一的方法是做一个 for 循环,将每个数据帧追加到一个列表中,最后,使用pd.concat来组合所有这些数据帧。但这是相当低效的。让我们检查以下内容:

这个结果也很有趣,因为事实证明,在读取多个 CSV 文件方面,datatable 的性能比 pandas 差,这与我的预期相反。尽管如此,达斯克仍然赢得了比赛,表现比熊猫好 4 倍。

警告— CSV 文件有不同的格式

组合多个 CSV 文件时,由于版本不同或数据文件损坏,CSV 文件可能会有不同的格式。在这种情况下,我们应该小心不要无意中混淆了不同的 CSV 文件。

新列

在这个例子中,我准备了两个文件,其中一个有一个额外的列。我试图模拟一个真实的场景,在这个场景中,源文件中有一个微小的模式变化。

这是你所期望的吗?

Pandas 将使用新模式作为目标模式,新列将被回填为旧数据中的 **NaN** 。列表files中的顺序无关紧要,因为 pandas 将选择具有更多列的模式,而不管读取顺序如何。

另一方面,Dask 让我很惊讶。它使用列表中第一个文件的模式作为目标模式,并忽略不匹配的文件。当我反转files时,我得到了完全不同的结果,因为先读取了一个不同的文件。这有点冒险,因为它可能会在不通知您的情况下忽略许多行。一种解决方法是读取每个文件的文件头,并在合并之前进行比较。尽管这产生了一点点开销,但总的处理时间仍然比 pandas 快,因为在“合并”阶段获得了巨大的性能增益。

与其他的相比,数据表是相当安全的。如果在模式中发现差异,它将引发异常。你可以在rbind函数中添加force=True,这样它就会有和熊猫一样的行为。

重命名现有列

另一个常见的模式更改是重命名现有的列,这是一个突破性的更改。在这个例子中,data.csv只包含gross_amount,而data_rename_col.csv只包含net_amount。看到前面的例子后,在检查结果之前,让我们猜一猜。

所以,熊猫的结果既包含了gross_amount又包含了net_amount,它用NaN填充了缺失的值。Dask 给出与之前相同的结果,这取决于它首先读取的文件。在这种情况下,Datatable 抛出一个不同的异常,其名称为中断列。这在调试过程中很有帮助。一般来说,它们都继承了上一个例子的相同行为。

计算聚合

一项重要的分析是计算聚合,如最小值、最大值、总和、平均值和中值,其中单个数字可以洞察整个(部分)数据集。那么这些计算的性能如何呢?

根据 dask 文档:一般来说,dask . data frame group-by-aggregations 的性能与 pandas group-by-aggregations 大致相同,只是可伸缩性更强。

计算聚合的性能也是一样的。但与熊猫相比,dask 能够在集群中扩展解决方案。

写入 CSV 文件

最后一部分是卸载。所有这 3 个库都有相同的接口.to_csv()将数据帧保存到 CSV 中。除此之外,dask 还支持 Apache Parquet 格式,这是一种流行的柱状二进制格式,旨在实现高效的数据存储和检索。它提供了高效的数据压缩和编码方案,增强了批量处理复杂数据的性能。

如你所见,获胜者是镶木地板格式的 dask。Pandas 和 datatable 的表现一样。常规 CSV 格式的 Dask 性能最差,这与读取 CSV 文件的性能完全相反。parquet 的高性能是因为数据被分成了几个分区。默认情况下,dask 会将每个 parquet 文件作为数据帧中的一个分区单独加载,这样更便于并行加载。

此外,dask 在默认情况下用 snappy compression 写出 parquet 文件。快速压缩通常是分布式计算中文件的最佳选择。虽然它压缩文件的力度不如 gzip 等其他压缩算法,但在解压文件时速度更快。您也可以用其他压缩算法覆盖它。

结论

我希望这篇文章能够让您对 pandas、dask 和 datatable 的不同方面有一个整体的了解。事实证明,没有一个图书馆是完美的。Dask 擅长读写文件,尤其是使用它的 parquet 格式。它能够将您的解决方案分发到一个集群。Datatable 试图以稍好的性能模仿熊猫的行为。Pandas 是另外两个库的核心,提供了最完整的计算方法。

比较不同的工具并了解它们的优缺点总是很有趣的。我鼓励你在你感兴趣的领域也做这样的练习,并与社区分享。它将使许多人受益。

让我知道你对这三个图书馆的看法!干杯!

参考

https://coiled.io/blog/speed-up-pandas-query-10x-with-dask/#:~:text=Dask runs faster than pandas,cores to run the computation

熊猫大战 SQL——第一部分:美食广场和米其林风格的餐厅

原文:https://towardsdatascience.com/pandas-vs-sql-part-1-the-food-court-and-the-michelin-style-restaurant-6921a0786cab

()杜尚·哈努斯卡在 flickr 上的照片 | ( )普拉依特诺在 openverse 上的照片

tldr: 这是一系列对比 Pandas 和 SQL(数据框架和数据库)的博客文章中的第一篇。事实上,数据框架和数据库都是旧思想,可以追溯到几十年前。数据库选择可伸缩性、健壮性和效率,而 Pandas 选择简洁、灵活和方便。我们能指望两个世界都好吗?是啊!

每隔几个月,就会有一条关于熊猫大战 SQL 的推文,比如 2020 年的这条:

我不得不给出一个结合了 Pandas 和 SQL 的代码库给那些对 Pandas 或 SQL 一无所知的学术界人士,但是(1)他们可能知道一些 R,(2)他们可能有学术界的朋友/同行知道 Pandas 或 SQL。当一个任务可以在 Pandas 或 SQL 中完成时,应该使用哪一个?

-高级 PowerPoint 工程师(@ ryx commar)2020 年 7 月 19 日

或者这个:

SQL 比 python/r 更受当今数据人员的欢迎,是因为它更好,还是因为人们可以使用它而无需设置开发环境,将数据从一个地方移动到另一个地方,或者做任何其他困难的“搜索另一个神秘的终端错误”的事情?

-本斯坦西尔(@本斯坦西尔)2022 年 1 月 25 日

然后这个月早些时候的这个:

跟进问题:你什么时候从 SQL 切换到另一个 PL (Python,JS 等)。)?硬的时候还是不符合人体工程学的时候还是别的?

-莎拉·卡坦扎罗(@ Sarah cat 21)2022 年 6 月 15 日

所有这些推文本质上都问了同一个问题:就数据工作而言,Pandas/Python 能比 SQL/数据库更好吗如果能,什么时候?显然,使用熊猫的数百万数据科学家在他们首选的工具上不可能都是错的——但是很难找到一种资源以一种确定的方式回答这个问题。

所以我们决定写一系列的博客文章来探讨这个问题,比较熊猫和 SQL/数据库。这是第一篇,我们将在未来几周发布后续文章。即使你没有阅读任何帖子,其中的要点是:数据框架和数据库已经以某种形式存在了几十年。两者都在数据分析和数据科学领域占有重要地位!

无论如何,说到这篇博文的主题。嗯...谁不喜欢美食类比呢?让我们开始吃吧。

首先,让我们谈谈关系数据库——一项成熟的、有几十年历史的技术,市值高达数千亿美元,由 Snowflake、Databricks、Oracle、Amazon、Microsoft 和 Google 等公司提供。我们都喜欢关系数据库。

数据库允许你用一个预定义的结构或者模式来表示直观集合中的数据,这些集合被称为关系。数据库让你使用 SQL 或“结构化查询语言”来操作关系,这种语言是 IBM 在 20 世纪 70 年代发明的,通常被称为“星系间数据峰”SQL 和关系数据库经受住了时间的考验,尽管许多人试图取代它们,包括 2000 年代中期的 noSQL 运动。数据库将比我们所有人都长寿。

一家高级餐厅。我还没去过一个用餐巾纸做天鹅的地方。也许有一天。(图片由杰伊·温宁顿在 unsplash 上拍摄)

事实证明,关系数据库非常像米其林星级高级餐厅。这种类型的餐馆非常正式:你需要穿衬衫或连衣裙,而不是短裤或 t 恤——就像你的数据库一样,在你操作数据之前需要一个正式的模式。菜单上没有太多的选项:它通常是一个固定的菜单,有一组精心挑选的小选项,就像您的数据库一样,它只支持 SQL 中的少量关键字(SELECT-FROM-WHERE-GROUP-BY……)。但就其作用而言,高级餐厅——就像数据库一样——是经过微调的、高效的和有效的。

或者,让我们考虑一个非常不同的野兽, Pandas ,数据科学的流行数据框架库。

Pandas 是一个快速、强大、灵活且易于使用的开源数据分析和操作工具。Pandas 的起源源于统计社区,在 2008 年移植到 Python 之前,该社区在 20 世纪 90 年代的 [S](https://en.wikipedia.org/wiki/S_(programming_language) 和 21 世纪的 [R](https://en.wikipedia.org/wiki/R_(programming_language) 中开发了底层数据帧抽象。因此,数据框架已经以某种形式存在了三十多年,这种形式是为了统计数据分析和数据科学的目的而明确设计的!

Pandas 在数据科学家和数据分析师中非常受欢迎,每周被下载 3000 万次。事实上,Python 现在是世界上发展最快的语言,这在很大程度上要归功于熊猫。在各种文章中,它被称为数据科学中最重要或最受欢迎的工具。

那么熊猫的数据框架和数据库相比如何呢?Pandas dataframe 系统类似于数据库,但也有所不同。与数据库关系不同,在数据帧中,Pandas 允许在一列中使用混合类型的数据,并维护顺序概念。除了列标签之外,数据框架还支持行标签,这使得引用数据变得很容易。pandas 支持 600 多种功能,涵盖数据清理、准备、转换和汇总,让您可以对数据做几乎任何可以想象的事情,从关系、矩阵(线性代数)和电子表格中提取数据,涵盖数据清理、转换、准备、特征化和探索。

Dataframe 数据模型和 600 多个函数(图片由作者提供)

因此,如果关系数据库就像米其林星级餐厅,那么熊猫就非常像一个美食广场。美食街不在乎你穿什么——就像熊猫一样,它不需要你在加载数据前整理或清理数据。你可以零零碎碎地订购你喜欢的东西——就像熊猫一样,它让你可以逐步提出你的查询。你甚至可以做一些不虔诚的事情——比如混合寿司和薯条。你甚至可以在薯条上放冰淇淋。你可能会得到一些奇怪的眼神,但没有人能阻止你。从最老的人到最年轻的人,每个人都是快乐的!

典型的美食广场(由保罗·芬威克在 openverse 上拍摄)

让我们将我们的数据库美食体验与熊猫美食广场进行对比。就方便性而言,关系数据库要求您一次订购整个 SELECT-FROM-WHERE 查询;Pandas 允许你一次下一个订单,检查它,然后如果你愿意的话可以订购更多。我们的美食数据库要求在你进门之前有一个正式的模式(如果你穿短裤,你会被踢出去的!);Pandas food court 并不在乎——在一个列中混合类型完全没问题,这在数据清理的早期阶段很方便。Pandas 也非常平等:它对行和列一视同仁,对数据和元数据一视同仁;祝你好运,从你的美食数据库里得到那种待遇!我们的美食数据库菜单上只有几样东西——你的“从哪里选择”查询;熊猫拥有一切可以想象的东西,从寿司到汉堡到冰淇淋,有数据科学家贡献的 600+开源函数。

那么坏处是什么呢?不幸的是,尽管 Pandas API 有许多优点,但 Pandas 实现并没有真正扩展,因为它是在没有任何正式基础的情况下以特别的方式发展的。它只支持单线程执行,所以即使在一个强大的集群上运行它也不会给你带来任何好处。由于它完全在内存中操作,并制作许多副本,因此经常会引发内存不足(OOM)错误。最后,在一个序列中没有跨多个 pandas 操作符的真正的查询优化。所以你经常发生的事情是:你对你的数据进行采样,用熊猫进行探索,然后重写你的工作流程来大规模运作。

不幸的是,这太普通了(图片由作者提供,使用 imgflip.com)

因此,如果我们对比 Pandas 与数据库等大数据框架,这些大数据框架在性能谱上得分很高,跨越了可扩展性健壮性效率。正如我们将在随后的博客文章中探讨的那样,Pandas 在易用性方面得分很高,包括简洁灵活方便

比较熊猫和大数据框架(图片由作者提供)

一个悬而未决的问题是我们是否能两者兼得:我们能保留 Pandas API 但改变实现以获得可伸缩性、健壮性和效率吗?有一些团队正在研究这个问题——例如,通过将类似数据库的技术应用于熊猫——包括一个开源项目 Modin ,它旨在成为熊猫的可扩展替代者。

在我们的 Pandas vs. SQL 系列的下一篇文章(第 2 篇,共 4 篇)中,我们认为 Pandas 是更简洁的语言。更多阅读 此处

如果你是熊猫或者 SQL 爱好者(或者两者都是!)我们希望收到您的来信。欢迎在 Twitter 上关注我们,了解更多类似的内容,并回复我们的帖子

原载于 2022 年 6 月 28 日https://pounder . io

Pandas 与 SQL —第 3 部分:Pandas 更灵活

原文:https://towardsdatascience.com/pandas-vs-sql-part-3-pandas-is-more-flexible-cb43167189f5

Pandas 灵活的数据模型非常适合数据科学用例

美洲虎坦巴克在 Openverse 拍摄的照片(CC BY-ND 2.0)

TL;在这篇文章中,我们在三个坐标轴的第二个坐标轴上比较了 Pandas 和 SQL:灵活性。我们介绍了 Pandas dataframe 数据模型提供额外灵活性的八种方式,这使其非常适合数据科学和机器学习,优于 SQL 的关系模型。

我们继续寻求理解 Pandas 和 SQL 的相对优势——两种用于数据操作和分析的流行且强大的语言。这是比较熊猫和 SQL 的系列帖子中的第三篇;下面是之前的两个帖子:熊猫大战 SQL —第一部分:美食广场和米其林风格的餐厅熊猫大战 SQL —第二部分:熊猫更简洁

正如我们在上一篇关于 Pandas 与 SQL 的文章中所看到的,Pandas 有 600 多个函数,可以让您以各种强大的方式对数据进行操作,这些方式在 SQL 中是不可能或极难做到的,涵盖了一系列关键的机器学习、线性代数、特征化和数据清理操作。在这篇文章中,我们展示了 Pandas dataframe 数据模型(或抽象)如何提供额外的灵活性,使其非常适合数据科学和机器学习,优于 SQL 底层的关系模型。因此,前一篇文章关注的是数据框架代数,而这篇文章将关注数据框架数据模型

以下是 Pandas 数据框架比关系/SQL 数据框架更灵活的多种方式的列表:

  1. 将数据转移到元数据,然后再转移回来
  2. 行和列元数据
  3. 混合型色谱柱
  4. 灵活的模式
  5. 元数据转换
  6. 柱状操作
  7. 分层元数据
  8. 对元数据差异的容忍

我们将从一个简单的数据集开始,类似于从电子表格加载数据后可能得到的数据集。这是一个包含 2020 年夏季奥运会最佳表现国家的奖牌总数的数据集。

1.在 Pandas 中,你可以在元数据之间来回移动数据!在 SQL 中,您不能。

关于上面的数据集,您可能注意到的第一件事是,我们有像A, B, C, ... 这样的非描述性列名,而实际的列名是数据的一部分(第 0 行)。理想情况下,我们希望用这些列名替换A, B, C, ...。我们可以通过以下方式做到这一点:

我们所做的只是将标题行提取到header,修整数据帧以保留最后的n-1行,并通过df.columns设置列名。

类似地,可以将信息从元数据(列标题)反向移动到数据。见下文。

我们在这里所做的是通过to_frame()将列名提取为数据帧,通过单个字母T将数据帧转置为一行,最后通过pd.concat将该行与旧数据帧垂直连接。

在 SQL 中,实际上不可能将数据移动到元数据(列名)中,或者将数据移动回来。

2.Pandas 支持行和列元数据;SQL 只有列元数据。

虽然 Pandas 像数据库一样支持列元数据(即列标签),但 Pandas 也支持行标签形式的逐行元数据。如果我们想以直观的方式组织和引用数据,这是很方便的。这里有一个例子:我们从重新加载数据帧开始。

现在我们需要做的就是使用set_index来设置行标签或索引。这里我们将其设置为数据中的Name列。

就像列标签一样,我们也可以使用reset_index命令将行标签移回到数据中。

数据库没有行标签的概念。您可能声称行标签功能可以用一个*PRIMARY* KEY / *UNIQUE*关键字来复制,但这并不完全正确——行标签实际上不需要唯一。这是熊猫灵活的另一种方式!

3.熊猫允许混合式栏目;SQL 只支持单一类型的列

由于它在数据科学中的使用,在不同的清理阶段对数据进行操作,Pandas 没有对每列强制严格的类型。再看我们的例子:

这里,Ranking列包含字符串和整数。如果我们检查该列的类型,我们将看到以下内容:

我们所做的是在通过apply推断出列中每个值的类型之后,通过value_counts计算每个类型的值。正如您在输出中看到的,Ranking列包含四个整数和一个字符串(—)。如果我们希望将字符串值强制转换为空值,以便该列具有单一类型,我们可以通过以下方式实现:

4.在 Pandas 中,输出模式可以根据数据灵活变化;在 SQL 中,模式是固定的

在 SQL 中,输出的模式(即一组列及其类型)基于输入和查询的模式进行约束。Pandas 没有这种限制,允许输出根据数据而变化,从而提供了额外的灵活性。这里有一个例子,也是在我们熟悉的数据集上:

如您所见,get_dummies函数为Continent列中的每个值创建了一个新的布尔列。这对机器学习非常有帮助,因为大多数机器学习包只接受数字数据,所以所有的字符串列都需要转换成数字列。如果我们从数据集中省略了对应于ROC的最后一行,就不会有对应于Continent_Europe/Asia的列。通过这种方式,输出列可以依赖于数据——非常简单!

5.Pandas 允许您灵活地转换元数据(列/行标签);在 SQL 中,您不能

Pandas 认识到模式总是不断变化的,并赋予我们灵活清理元数据的超能力,而不仅仅是以简单的编程方式清理数据。这里,如果我们发现Continent_前缀很难跨列重复,我们可以使用一个命令在所有列中删除它:

噗,就这样,没了!不幸的是,SQL 不能像 Pandas 那样给你操作列名的能力。您需要手动指定每个列名将如何更改。

6.Pandas 允许您像操作行一样操作列;SQL 没有

不同于 SQL 对行和列的不同处理,Pandas 对它们的处理是一样的。从上面使用Name列作为行索引/标签的df_name_index开始,我们可以简单地转置它,使行成为列,列成为行,如下所示:

最酷的是,Pandas 自动推断新列的类型,而无需用户做任何工作。在这种情况下,所有这些列都是混合型的,因为它们既有整数也有字符串。以这种方式组织数据通常更容易比较。除了转置,我们还可以像对待行一样沿着列应用大多数函数。我们在之前的博客中已经提到了这方面的例子。

7.Pandas 让你拥有层次化的元数据;SQL 没有

分层元数据帮助我们以更易于阅读和直观的方式组织和呈现信息。幸运的是,Pandas 支持分层的行标签和列标签。假设我们想要创建分层的行标签:

正如我们在上面看到的,我们通过set_index操作符创建了一个由一对大陆和国家名称组成的行标签层次结构。通过使用T操作符转置这个结果,我们得到了一个分层列标签的例子:

在上面的结果中,列标签的第一行捕获了大陆,而第二行捕获了国家名称。与 Pandas 不同,SQL 和关系数据库不支持分层元数据。对此最好的代理是使用半结构化数据格式(例如 JSON、XML),关系数据库通常支持这种格式作为列中的特殊数据类型。但是,在这些列上实施分层模式是很困难的,对这些嵌套数据格式的操作也是如此。

8.Pandas 在对具有不同元数据的多个数据帧进行操作时是宽容的;SQL 不是

当在多个数据帧上操作时,Pandas 操作允许模式差异。我们将通过考虑一个常见的二元运算concat来说明这一点。假设我们在当前数据框架旁边有另一个数据框架,包含关于另外三个国家(澳大利亚、法国和荷兰)的信息。我们可以使用concat操作符来执行具有匹配模式的两个数据帧的有序联合,如下所示:

得到的数据帧有八行。这是一个简单的例子,类似于 SQL *UNION*操作符。现在假设两个数据帧的模式不同;具体来说,df2没有金牌榜栏目。这是我们从熊猫身上得到的:

正如我们所看到的,Pandas 采用了一种“尽力而为”的方法,继续执行有序的联合(即连接),为两个数据帧之一中缺少的列填充NaN(即 null)值。在这里,SQL 会给出一个错误,因为模式需要匹配一个联合。

结论

正如我们在上面的例子中看到的,Pandas 中的 dataframe 抽象比 SQL 下的关系模型灵活得多。从最终用户的角度来看,这种额外的灵活性通常使表示和使用数据更加直观。此外,在执行数据清理时,这种增加的灵活性是必不可少的:数据通常是脏的,具有异构类型和不精确、未指定和/或未对齐的模式。最后,Pandas 数据模型放弃了任意的区分:行和列是等价的,数据和元数据是等价的——这使得表达许多重要的数据科学和机器学习操作变得更加方便。

如果你能想到 Pandas 比 SQL 更灵活的其他例子,或者相反,我们很乐意听到!欢迎在推特上关注我们,了解更多类似的内容,并在这里回复我们的推特

在我们的 Pandas 与 SQL 系列的下一篇文章(第 4 篇,共 4 篇)中,我们认为 Pandas 是更方便的语言。在这里阅读更多!

原载于 2022 年 7 月 26 日https://pounder . io

Pandas 与 SQL —第 4 部分:Pandas 更方便

原文:https://towardsdatascience.com/pandas-vs-sql-part-4-pandas-is-more-convenient-8e9744e2cd10

gamene 在 Openverse 的照片(CC BY 2.0)

TL;dr: 在这篇文章中,我们在三个坐标轴中的第三个坐标轴上比较了 Pandas 和 SQL:便利性。我们描述了 Pandas dataframe 数据模型更便于数据科学和机器学习用例的六种方式。

在我们的 Pandas 与 SQL 之间的史诗般的战斗的第四部分中,我们说明了 Pandas 如何在数据科学和机器学习方面比 SQL 更方便。熊猫是由数据科学家为数据科学家设计的,并受益于开源数据科学社区热情反馈的数千项改进——所有这些改进都着眼于更大的实用性和易用性。所以很契合也就不足为奇了!

在我们开始之前,如果你错过了我们以前的 Pandas vs. SQL 产品,你仍然可以在这里赶上:第一部分:美食广场和米其林风格的餐厅第二部分:Pandas 更简洁,和第三部分:Pandas 更灵活。我们之前的帖子关注的是数据框架数据模型和数据框架代数的比较——在这篇帖子中,我们关注的是数据框架的人类工程学:特别是,数据框架是如何使用的。

为了便于查找,下面列出了 Pandas 数据框架比关系/SQL 数据框架更方便的多种方式:

  1. 在 Pandas 中,您可以逐步构建查询;在 SQL 中,您不能。
  2. 在熊猫身上,操作和命名中间结果很容易;在 SQL 中更难。
  3. 在熊猫身上,很容易获得对数据的快速感知;在 SQL 中,这要困难得多。
  4. Pandas 具有对可视化的本地支持;SQL 没有。
  5. 熊猫让机器学习变得简单;SQL 没有。
  6. Pandas 保留顺序以帮助用户验证中间步骤的正确性——并允许用户按顺序操作;SQL 没有。

在这篇文章中,我们将使用来自 Kaggle 的 5 天数据清理挑战的数据集;这是旧金山建筑许可的数据集。数据来自旧金山政府数据门户,他们通过公共领域专用和许可 v1.0 提供数据

1.在 Pandas 中,您可以逐步构建查询;在 SQL 中,您不能。

Pandas 和 SQL 之间的一个重要区别是,Pandas 允许用户在其他操作的基础上逐步构建更复杂的查询。同时,用户可以检查这些查询片段的中间结果——努力验证它们的正确性。和熊猫一起调试简直易如反掌!

因此,在我们的数据集中,假设我们想要关注 Geary Street 对应的许可证。我们可以如下提取数据集子集:

我们可能已经注意到的一件事是 Geary 跨越了许多社区,这里编码为'Neighborhoods - Analysis Boundaries'。假设我们只想检查这一列'Neighborhoods - Analysis Boundaries'(并删除剩余的 42 列),我们可以简单地将子句[['Neighborhoods - Analysis Boundaries']]附加到前面的表达式的末尾。

这是很多行:1966 年。然后,作为我们的最后两步,假设我们想要识别 Geary 上拥有最多许可的街区。一种方法是追加一个'sort_values'后跟一个'value_counts'

有意思,所以最高的街区是田德隆区,其次是外里士满区。请注意,虽然这一系列操作肯定可以用 SQL 来表达,但这会更加痛苦。我们不能简单地在一个 SQL 查询的末尾添加操作符:查询中有特定的位置需要我们进行修改。例如,要更改显示哪些列,我们需要在早期修改查询的*SELECT*部分。相反,Pandas 允许你操作性地(或命令性地)思考——一步一步地构建你的最终结果,同时检查中间结果。

2.在熊猫身上,操作和命名中间结果很容易;在 SQL 中更难。

Pandas 嵌入在真正的编程语言 Python 中,借用了许多熟悉的编程习惯用法来操作数据帧。特别是,我们可以将一个数据帧表达式赋给一个变量;这些变量然后可以被操作和/或分配给其他变量。

我们举一个简单的例子来说明。由于该数据集来自数据清理挑战,假设我们怀疑可能有许多空值。我们可以使用以下方法检查每列有多少个:

空值太多了!假设我想创建一个数据集的清理版本,删除包含太多 null 值的列,将阈值设置为 190000 个非 null 值。(整个数据集大约有 199000 行。)

哇——列的数量从 43 列减少到只有 13 列。正如我们在这里看到的,我们能够轻松地定义一个新的变量'sf_permits_cleaned'(就像我们创建之前的变量'missing_values_count'),使用标准的编程变量赋值,然后对它进行操作。这种方法对程序员来说很自然。在 SQL 中,可以通过视图实现类似的效果,但是定义视图并对其进行操作不太直观,而且更麻烦。

3.在熊猫身上,很容易获得对数据的快速感知;在 SQL 中,这要困难得多。

Pandas 提供了快速理解数据帧的数据和元数据的方法。我们已经看到过这样的例子,当我们简单地使用变量名或者使用函数'head/tail()'来打印数据帧时。为了方便起见,为了适合屏幕,某些行和列用'...'隐藏起来,以帮助用户获得数据的高层次图像。

如果我们想要检查列及其类型的摘要,Pandas 提供的一个方便的函数是'info()',它列出了数据集的列、它们的类型以及空值的数量。我们可以使用这个函数来检查我们刚刚创建的数据帧。

因此,看起来唯一仍然包含空值的列是 description 列;所有其他列都已完全填充。

另一个针对数字列的有用的 Pandas 函数是'describe()',它提供了这些列的方便摘要,包括计数、平均值、标准偏差和分位数。

嗯,看起来街道号是 0。好奇!

不幸的是,SQL 没有提供类似的便利来理解数据集的形状和特征——为此,您必须编写自定义查询。对于前面的例子,这个查询的长度将与数字列的数量成比例。

4.Pandas 具有对可视化的本地支持;SQL 没有。

对数字表格的分析只能帮到你这么多。通常你需要的是在数据框架中理解信息的视觉方式。与需要将数据加载到单独的可视化或 BI(商业智能)工具中的 SQL 不同,Pandas 在库中提供了内置的可视化支持。例如,我可以简单地调用'plot()'来查看各种许可的'Current Status'的条形图。

看起来绝大多数许可证属于已完成、已签发和已归档类别,少数属于其他类别。

这个特性的强大之处是显而易见的:与 SQL 数据库不同,如果您想生成可视化效果,您不需要离开这个库——您可以在这里完成!如果你想“增强”你的可视化体验,有许多与熊猫紧密集成的可视化库,包括 Matplotlibseabornaltair 。如果你像我一样懒惰,根本不希望编写任何代码来生成可视化,那么你可以使用我们的 Pandas-native 可视化推荐库 Lux ,来自动为你生成可视化,并根据你的数据集进行调整。点击阅读更多关于力士的信息。

5.熊猫让机器学习变得简单;SQL 没有。

机器学习是数据科学的关键组成部分,使用户不仅能够理解图像、视频和文本等非结构化数据,还能预测未来。由于 Pandas 与数据科学生态系统紧密集成,因此它与机器学习库(包括常见的库,如 scikit-learnpytorchnumpy )配合良好也就不足为奇了。这里,我们将使用 spaCy 库,一个相对较新的自然语言处理库,来理解数据集中的文本列。SpaCy 提供各种单词预训练模型来执行单词嵌入、命名实体识别、词性标注、分类等。要安装 spaCy,我们运行以下命令:

现在我们已经安装了它,假设我们想要了解活动的类型(例如,拆除、移除、替换等。)涉及我们数据集中的每个许可申请(即 row)。这在前面很难理解,但是隐藏在文本字段'Description'中。让我们使用这个包来提取这个字段中提到的动词列表。作为其中的一部分,我们首先加载 spaCy 的'en_core_web_md'模型,然后使用该模型提取描述的标记化中的每个动词,并将其存储在一个数组中,如下所示。

因此,正如我们在上面看到的,该模型在提取动词方面做得很好,尽管它确实漏掉了一些(例如,install)。随着大型预训练模型(例如,transformer 模型)的不断增加,我希望这些模型能够更好地集成到 pandas 的日常数据处理中。

在 SQL 数据库中集成机器学习非常困难。虽然一些数据库提供了特定于机器学习的构造(例如,BigQuery ML),但是用户在他们能够完成什么方面受到限制,并且没有细粒度的控制。另一种组装方法是使用 UDF 来进行机器学习。通常最终发生的是用户将他们的数据导出到数据库上下文之外来执行机器学习。

6.Pandas 保留顺序以帮助用户验证中间步骤的正确性——并允许用户按顺序操作;SQL 没有。

熊猫维持秩序。这对于调试和验证非常重要,因为我们正在构建更复杂的查询表达式。继续我的例子,在提取动词的空间之后,假设我想使用'explode'函数将前面数据帧中的单个动词展开成多行,每一行一个动词;我可以简单地这样做。

注意,我现在有三行对应于原来的第一行,每一行提取一个动词。这种顺序的保持使得验证该步骤的正确性变得容易。使用 SQL 数据库,这将更加困难,因为顺序没有保证,所以需要查看整个输出以了解给定行的结束位置(或者添加一个*ORDER* *BY*子句来强制执行特定的输出顺序)。

结论

在这篇文章中,我们从最终用户的角度介绍了 Pandas 比 SQL 更方便的各种方式。这包括通过保持顺序、增量合成、命名和操作以及沿途检查来正确地构建 Pandas 查询。这还包括与其他数据科学和数据分析需求的集成,包括可视化和机器学习:Pandas 不仅允许用户完全在 Pandas 中可视化和执行预测建模,还提供了将输出连接到其他流行的可视化和机器学习库和包的挂钩,特别是在 PyData 生态系统中。最终,Pandas 位于成熟的编程语言 Python 中,并继承了它的所有功能。

如果你能想到其他例子,Pandas 比 SQL 更方便,或者相反,我们很乐意听到!请随意回复我们的推文,并且在 TwitterLinkedIn 上关注我们,了解更多熊猫/ Python /数据科学内容!

panda sql——用 Python 运行 SQL 查询的有趣方式

原文:https://towardsdatascience.com/pandasql-interesting-way-to-run-sql-queries-in-python-18a4fc36406a

熊猫

使用 Python Pandas 中的 SQL 查询对数据进行提取、分组、排序和连接数据集!!

Unsplash萨尔茨堡自由行旅行团拍摄的照片

pandas 是一款快速、强大、灵活且易于使用的开源数据分析和操作工具,构建于 Python 编程语言之上。

这就是为什么 Pandas 是 Python 的一个广泛使用的数据分析和操作库。🏆

尽管如此,有时 SQL 查询看起来非常简单易懂。另外,如果你学习 SQL,用 SQL 提取、操作和分析数据似乎非常容易。对于所有这样的用户(包括我), pandasql 绝对是一个惊艳的库。

**pandasql**允许您使用 SQL 语法查询 pandas 数据帧。这也不需要安装或连接任何 SQL server。💡

这可能是使用 Pandas 学习 SQL 的一个小小的动机和简单的开始。

我在上一个项目中使用了 pandasql ,它简化了整个数据提取和分析的方式让我感到惊讶。它提供了一种更熟悉的操作和清理数据的方式。

在本文中,我用几个经典的例子解释了 pandasql 在数据分析中的真正潜力。

要开始,我们需要做的就是用 Python 安装pandasql库,

**pip install pandasql**

一边用笔记本打字一边在线下的一个单元格中点击Shift + Enter

**!pip install pandasql**

pandasql中的主要功能是**sqldf** 。因此,可以使用,

**from pandasql import sqldf**

**sqldf** 有两个参数,其中一个是完全可选的(事实上我从未使用过)。所以,重要且唯一的参数是一个 SQL 查询字符串

sqldf 使用它的语法**sqldf(sql_query)**给出一个 pandas 数据帧作为输出。我们可以使用 SQL 查询任何 pandas 数据帧,就像我们使用 SQL 从任何表中提取数据一样。

***pandasql***使用 SQLite 语法

就是这样!!我们都准备好去探索了!🚀

为了使文章有趣并易于理解,我想使用pandas.read_csv()在 pandas DataFrame 中导入一个虚拟销售数据(自己创建的数据集)。

import pandas as pd
from pandasql import sqldfdf = pd.read_csv("Dummy_Sales_Data_v1.csv", sep=",")
df.head()

虚拟销售数据|作者图片

它是一个 10000 x 12 的数据集,仅包含虚拟数据,可用于所有目的的数据分析。你可以免费下载并用来练习数据分析。

为了便于访问数据,我将重命名这些列。

df.rename(columns={"Shipping_Cost(USD)":"ShippingCost_USD",
                   "UnitPrice(USD)":"UnitPrice_USD",
                   "Delivery_Time(Days)":"Delivery_Time_Days"},
          inplace=True)
df.info()

按作者更改列名|图像

现在,让我们先从简单的事情开始。

数据析取

从简单的例子开始,让我们从上面的数据框架df中提取一些数据,并制作两个小表— df_ordersdf_products

这可以是sqldf最简单的例子。

此任务的 SQL 查询将是,

SELECT OrderID, Quantity, Sales_Manager, Status, Shipping_Address, ShippingCost_USD FROM df

要使它成为一个字符串,必须用“ ”括起来。作为一个好的实践,我将把这个字符串赋给一个变量*query*

query = "SELECT OrderID, Quantity, Sales_Manager, Status, Shipping_Address, ShippingCost_USD FROM df"

现在,是时候使用sqldf功能了!

df_orders = sqldf(query)
df_orders.head()

作者使用 sqldf | Image 提取的 df_orders

类似地,另一个数据帧df_products也可以使用下面的sqldf生成。

query = "SELECT OrderID,\
        Quantity, \
        Product_Code, \
        Product_Category, \
        UnitPrice_USD \
        FROM df"
df_products = sqldf(query)
df_products.head()

df _ 产品使用 sqldf |图片作者

用一行代码编写一个 SQL 查询可能会很繁琐和令人困惑,尤其是当我们想要查询多个列时。

由于我们将 SQL 查询作为一个字符串传递给sqldf,我们可以使用多行字符串,就像我在上面创建df_products表时使用的一样。

它使代码变得干净,易于编写、阅读和修改!

这里是 Python 中关于 的一行程序 的趋势之一。

</5-most-powerful-one-liners-you-should-know-in-python-programming-c9d49a89b7f3>

接下来,sqldf也可以用于有条件地提取或选择数据。

基于条件选择数据

可以根据一列或多列上的条件提取数据。在sqldf中,这可以通过使用 WHERE 语句来实现。

例如,从*df_orders*中选择所有订单,其中发货地址肯尼亚

让我们首先编写如下的 SQL 查询,

SELECT * FROM df_orders WHERE Shipping_Address = 'Kenya'

将其转换为字符串并传递给sqldf函数,我们可以提取所需的数据,如下所示。

query = "SELECT * \
        FROM df_orders \
        WHERE Shipping_Address = 'Kenya'"
df_kenya = sqldf(query)
df_kenya.head()

在 SQL | Image by Author 中使用 WHERE 语句

让事情变得复杂一点,让我们试着把所有发货到的订单,数量少于 40 并且状态为已发货或已交付。

query = "SELECT * \
        FROM df_orders \
        WHERE Shipping_Address = 'Kenya' \
        AND Quantity < 40 \
        AND Status IN ('Shipped', 'Delivered')"

让我们将这个查询字符串传递给sqldf以获得输出。

df_kenya = sqldf(query)
df_kenya.head()

在 WHERE 语句|作者图片中使用 AND 关键字

这表明,我们可以在sqldf 中使用来自 SQL 的所有关键字,例如Status IN (‘Shipped’, ‘Delivered’)和在 WHERE 语句中使用AND关键字。

故事并没有到此结束!我们还可以将数据分成不同的组,并执行计算以供进一步分析。

分组依据

在 Python 中,pandas groupby用于将数据分类,并有效地聚合数据。在 SQL 中,类似的功能由GROUP BY语句提供。

GROUP BY语句或 pandas groupby函数通常与COUNT()MAX()MIN()SUM()AVG()等聚合函数一起使用,按一列或多列对结果集进行分组

pandasql 使我们能够使用 sql 查询在 Pandas 数据帧上应用groupby功能。🏆

例如,让我们看看有多少订单被发送到每个送货地址。从逻辑上讲,这是通过按不同的送货地址对数据进行分组,然后计算订单数量来实现的。

因此,SQL 查询是,

SELECT Shipping_Address, COUNT(OrderID) AS Orders FROM df_orders GROUP BY Shipping_Address

将该查询转换成字符串并传递给sqldf 函数,我们将得到如下预期结果:

query = "SELECT Shipping_Address, \
        COUNT(OrderID) AS Orders \
        FROM df_orders \
        GROUP BY Shipping_Address"df_group = sqldf(query)
df_group.head(10)

熊猫分组使用 sqldf |图片作者

现在,这个输出与熊猫组的输出略有不同。

sqldf给出熊猫数据帧作为输出🏆而熊猫groupby返回数据帧对象作为输出。

如果是 pandas groupby,*Shipping_Address*列将是索引列,pandas groupby的输出也不会是数据帧。因此,为了进一步使用分组和聚合的数据,我们需要将groupby对象转换为 pandas DataFrame 并重置其索引。

下面是获得相同输出的 pandas groupby代码。

df_grouped = df_orders.groupby("Shipping_Address")
df_grouped = df_grouped["OrderID"].count()
df_grouped = pd.DataFrame(df_grouped)
df_grouped.reset_index(inplace=True)
df_grouped.rename(columns={"OrderID":"Orders"}, inplace=True)

显然, pandasql 简化了数据分组及其聚合。

此外,可以使用 SQL **ORDER BY**语句基于一列或多列对数据进行升序或降序排序。

让我们继续使用相同的数据分组示例,并根据订单数量对送货地址进行排序。

query = "SELECT Shipping_Address, \
        COUNT(OrderID) AS Orders \
        FROM df_orders \
        GROUP BY Shipping_Address \
        ORDER BY Orders"df_group = sqldf(query)
df_group

使用 sqldf |图片按作者排序熊猫数据帧

简单地说,只需在现有查询的末尾添加 3 个单词,我们就可以快速地按升序排列结果。

这里有一篇关于熊猫groupby的精彩文章。

</4-awesome-pandas-methods-to-quickly-analyze-any-dataset-65d2252af6e8>

如果你认为这些都是很简单的查询,那么sqldf会给你一个惊喜。

sqldf可以执行高级 SQL 查询,如连接。🏆

SQL 中的 JOINs 或 pandas 中的 merge 是用于根据两个或多个数据集之间的相关列来组合它们的工具。

到目前为止,在我的项目中,我使用了所有类型的使用sqldf的 SQL 连接,看起来超级方便。

让我们看看如何!

合并数据集

如果我们使用 pandas,有两个函数来组合数据集— mergeconcat。并且有时会弄不清何时使用哪一个。

SQL 通过使用JOIN语句简化了事情。并且sqldf也可以处理JOIN语句。

例如,让我们将所有订单、送货地址和产品类别放在一起。为此,我们将使用本文开头创建的df_ordersdf_products

query = "SELECT T1.OrderID, \
        T1.Shipping_Address, \
        T2.Product_Category \
        FROM df_orders T1\
        INNER JOIN df_products T2\
        ON T1.OrderID = T2.OrderID"df_combined = sqldf(query)
df_combined.head()

使用 sqldf | Image by Author 的内部联接

这样, df_ordersdf_products都连接在 OrderID 列上。由于是一个INNER JOINOrderID s,两个数据帧中都存在的将被返回。

同样,sqldf也可以执行JOIN s。

以下是理解如何在 Python pandas 中组合数据集的推荐读物。

总结一下,

我个人经常使用sqldf,因为它保持代码干净、简单、易于理解和修改。因此,我展示了我们可以用它做什么。

这是关于pandasql最全面的指南之一,这里是一个完整的 笔记本 以及所有代码。如果我错过了什么,请在评论中告诉我。

现在你可以通过 在这里报名成为中会员 阅读我&其他作家发表的所有故事。当你这样做,我会得到你的费用的一小部分。欢迎加入我的 电子邮件列表 也可以随时更新我的写作。

🚩不要忘记将您的 SQL 查询放在“ ”中,以便在将其传递给sqldf之前将其转换为字符串。

🚩在一行中编写代码时,你应该记住PEP-8关于一行中最大字符数的准则,即 79 个字符。

感谢您的阅读!!

熊猫的分组功能是一个强大的工具,但如果你不小心,可能会误导你的观众

原文:https://towardsdatascience.com/pandass-groupby-function-is-a-powerful-tool-but-can-mislead-your-audience-if-you-re-not-careful-c4491055a98e

你需要明智地决定如何显示 groupby 的结果,否则你的读者可能会从你的数据中收集到错误的见解。

马库斯·斯皮斯克Unsplash 上拍摄

数据科学家最重要的工作之一是向他们的受众有效地传达关于复杂数据的见解。可以说,这样做包括总结数据和呈现“大画面”。换句话说,向你的观众展示一张巨大的原始数据表是没有用的。

想象一下,去参加一个关于房地产市场的讲座,并看到一个没完没了的表,其中列出了该国的每栋房子,以及附加列中给出的相关统计数据,如价格、建造年份等。这将是一场噩梦。这些数据的组织和提炼应该由数据科学家事先完成,而不是由观众按需完成。

进入 Pandas 中流行的groupby函数 Python 用户中流行的生成汇总数据的方法。关于groupby的一般用法和应用已经写了很多文章,但是今天我想探讨一个更微妙的问题。如果您不注意分组后如何显示数据,您可能会无意中误导您的观众相信一些不真实的东西。

快速回顾 GroupBy

在进入本文的主题之前,我将简要回顾一下groupby是如何工作的。从技术上讲,大多数程序员在使用这个函数时所做的事情最好称为“分组和聚集”groupby的工作方式是,它接受一个列来组成您的组,然后它用一些指定的函数聚集您选择的其他列。

这个句子有点复杂。让我们用一个例子来说明这一点。假设我们有下面这个名为grades数据帧,它包含了大学各年级学生的成绩信息:

一组假设学生的期中和期末成绩。

现在,假设我们对找出每年的最高期中分数感兴趣。下面的代码使用groupby来完成这项任务:

grades.groupby('Year')[['Midterm Score']].max()

上面代码的输出。

以上代码片段中的一些要点:

  • 我们分组的列是'Year'。从编程角度来说,这意味着该列成为 DataFrame 的新索引。从概念上讲,这意味着我们接下来指定的任何统计数据都将在基于该列对值进行分组之后进行计算。
  • 分组之后,我们使用[['Midterm Score']]选择我们想要聚合的列。我们使用双括号的原因是我们的代码返回一个数据帧而不是一个序列。
  • 在这一点上,Pandas 已经定义了两个迷你数据帧,看起来像下面这样(注意你不能很容易地看到它们;它涉及到一些底层groupby对象的欺骗:

熊猫在引擎盖下定义的两个迷你数据框。

  • 最后,.max()从这些值中选出最大值(即,根据聚合函数,将每个唯一组的所有值聚合成一个值),并将它们组合回我们上面看到的分组聚合数据帧。
  • 完成同样任务的更完整的语法是grades.groupby('Year')[['Midterm Score']].agg(max)。如果您想使用用户定义的函数,而不是内置的函数,这就是您需要使用的语法。

现在,让我们进入主题。

为什么如果你不小心的话,GroupBy 会误导人

您会注意到,在上面的例子中,我在通过max函数聚合值之前提取了期中成绩。这样做的明显原因是为了保持简单,为了便于说明,只关注一个专栏。

然而,潜在的原因是,如果不这样做,就会产生误导性的输出。让我们看一个例子。我们使用与上面相同的grades数据帧,但是这次运行下面的代码片段:

grades.groupby('Year').max()

这为我们提供了以下数据帧作为输出:

这一次,我们也保留了最终分数。

乍一看,这似乎很好。它似乎完成了与上面代码完全相同的事情,除了这次我们还获得了每个年级的最高最终分数。有什么问题?

退一步,以一个不了解数据处理的人的身份来看待数据框架——一个在手机上随意滚动、阅读一篇关于大学生成绩模式的文章的人。

对这样一个人来说,上面的数据似乎暗示着有一个大一新生期中得了 97 分,期末考了 99 分。然而,如果我们回头看看原始数据,情况并非如此。两个不同的学生获得了这些分数,而它们恰好是各自考试的最高分。

这是一个重要的观察。当你聚合多个列作为你的分组的一部分时,Pandas 会单独对待它们。因此,新列可能相互匹配,也可能不匹配。例如,事实上有一个二年级学生分别得了 88 分和 100 分。

如果我们添加更多的列,这个问题会变得更加突出。下面,我们有和以前一样的数据框架,除了这个还包括学生姓名:

同样的数据,但是现在有了名字。

让我们在这个名为grades_with_names的新数据帧上运行相同的代码:

*grades_with_names.groupby('Year').max()*

输出。

现在,好像有一个叫赞达亚的大二学生期中考试得了 88 分,期末考试得了 100 分。事实上,有一个叫赞达亚的大二学生,但那些不是她的分数。顺便提一句,如果你想知道为什么max'Names'栏中有效,那是因为它将字母表中后面的字母视为“更大”

上面的例子说明了在大多数情况下,当作为您的groupby的一部分进行聚合时,最好是隔离出一个特定的列。如果你选择不这样做,不管是出于什么原因,你都需要向将来可能查看你的数据的人非常清楚地表明这一点。

最后的想法

作为数据科学家,仔细思考我们向受众传达的信息非常重要。当我们长期使用数据时,往往很容易忽略无意中误导我们的数据。我们的头脑对数据足够熟悉,可以避免欺骗。

然而,对我们的观众来说却不是这样。他们中的许多人将是第一次看到这些数据,我们有责任确保他们所显示的内容是准确的,并且易于解释。

因为groupby是熊猫汇总数据的主要方式之一,所以在广泛使用它之前,了解它的内部运作是合适的。当有人第一次向我解释上述问题时,它被证明对我如何考虑数据表示非常有帮助。希望对你也有帮助。

下次见,伙计们!

想擅长 Python? 获取独家、免费获取我简单易懂的指南点击

请考虑使用我下面的推荐链接注册成为正式的媒体会员。你一个月就能看无限的故事,你的会员费直接支持我和其他作家。

*https://murtaza5152-ali.medium.com/?source=entity_driven_subscription-607fa603b7ce--------------------------------------- *

论文:“EpiLPS:估计时变再生数的快速灵活的贝叶斯工具”

原文:https://towardsdatascience.com/paper-epilps-a-fast-and-flexible-bayesian-tool-for-estimation-of-the-time-varying-reproduction-c9280017ab64

如何平滑流行病曲线并灵活估计时变再生数

作者的情节

介绍

我的一位同事(也是朋友)最近在 PLoS 计算生物学上发表了一篇题为“EpiLPS:一种快速灵活的估计时变繁殖数的贝叶斯工具”的研究论文。

我没有分享我没有参与的研究论文的习惯。尽管如此,我还是想为这一次破例,因为我坚信这篇论文中开发的方法值得被了解,尤其是对任何从事流行病学工作的人。

下面是这篇文章背后的动机,以及对模拟和真实数据(美国住院数据)的说明。更多信息可以在的论文和相关的网站上找到。

动机

epi LPS(Gressani et al . 2022)是时变再生数 Rt 的灵活贝叶斯推断的方法论;在时间 t 时由受感染病原体产生的继发性病例的平均数量。这是一个关键的流行病学参数,可告知传染病的传播潜力,可由公共卫生当局用于评估干预措施的有效性,并为未来的控制策略提出方向。

这一指标在新型冠状病毒疫情期间广受欢迎,媒体广泛报道,因为其含义易于直观理解。简而言之,当 R < 1, the signal is encouraging as the epidemic is under control and will eventually vanish. On the contrary, a value of R > 1 意味着疾病持续传播,感染正在目睹扩张性影响。因此,拥有一个强大而可靠的工具来根据传染病数据计算繁殖数是至关重要的。

来自哈瑟尔特大学(比利时)、莱顿大学(荷兰)和伯尔尼大学(瑞士)的 EpiPose 团队的一组研究人员最近开发了一种新方法,用于根据给定序列间隔分布的发病率时间序列数据(从感染者出现症状到继发病例出现症状之间经过的时间)来估计瞬时再生数。他们将他们的方法称为 EpiLPS,用于“Epidemiology modeling withLapla cian-P-Splines”,因为拉普拉斯近似和 P 样条平滑器是形成所提出的方法的主干的关键成分。

作者图片

EpiLPS 模型假设观察到的报告病例(按报告日期或症状发作日期)受负二项分布支配。因此,与泊松模型相反,它允许考虑过度分散的特征。流行病曲线在第一步中用 P 样条(其中潜在变量的后验估计通过拉普拉斯近似计算)平滑,并且在第二步中使用更新方程模型作为再生数和通过“插入”方法估计的样条系数之间的桥梁。

作者还解释了 EpiLPS 和 EpiEstim 之间的主要区别,epi estim 是一种由柯里等人(2013 )开发的用于实时估计 Rt 的成熟方法,并在不同的流行病情景下对这两种方法进行了广泛的比较。

EpiLPS 的一个有趣特性是,用户可以选择完全“无采样”路径,其中模型超参数固定在其最大后验概率 (LPSMAP)或基于 Metropolis-adjusted Langevin 算法(LPSMALA)的完全随机路径(LPSMALA)。说到效率,拉普拉斯近似和 B 样条求值的例程已经用 C++编写,并通过 Rcpp 包集成到 R 中,因此底层算法可以在可忽略的时间内执行。

下面,我们提供一个简短的例子,说明如何使用 EpiLPS 例程来估计 rt。

入门指南

EpiLPS 包可从 CRAN 获得(参见https://cran.r-project.org/web/packages/EpiLPS/index.html),并可通过输入以下命令从 R 控制台安装:

install.packages("EpiLPS")

然后,可以按如下方式加载该包:

library("EpiLPS")

EpiLPS 包的结构相当简单,因为它包含几个例程:

  • 函数epilps()是拟合再现数的核心程序。
  • 通过plot.epilps(),用户可以绘制估计的流行曲线和 Rt。
  • 最后,开发了两个辅助程序episim()perfcheck()来基本上再现相关论文的模拟结果。

模拟的例子

一组流行病数据可以用episim()程序模拟,方法是指定一个序列区间分布,并在一组可用的模式中选择真实的繁殖数曲线(这里我们选择模式 5,对应于一条相当波动的曲线)。

按照endepi选项中的规定,模拟疫情将持续 40 天。通过设置选项plotsim = TRUE,程序返回一个总结事件时间序列的图形、一个指定序列间隔分布的条形图以及真实的基本再生数曲线。

set.seed(1234)SI <- c(0.344, 0.316, 0.168, 0.104, 0.068)
simepidemic <- episim(
  serial_interval = SI,
  Rpattern = 5,
  plotsim = TRUE,
  verbose = TRUE,
  endepi = 40
)## Chosen scenario: 5 'Wiggly then stable Rt'.
## Incidence of cases generated from a Poisson distribution. 
## Total number of days of epidemic: 40.

作者的情节

如果您想了解生成的事件时间序列的概况,只需输入:

simepidemic$y##  [1]  10   6  15  24  37  43  54  46  47  28  20   8  10  10   3   5   3   4   6
## [20]   6  15  21  44  75 135 217 329 409 453 487 457 443 297 290 255 246 246 339
## [39] 395 573

平滑流行病曲线和估计 Rt

现在让我们使用epilps()例程来平滑流行病曲线并估计再生数。

我们将通过 LPSMAP(一种完全无采样的方法)和 LPSMALA(一种完全随机的方法,依赖于具有朗之万动态的 MCMC 算法)来实现这一点,其中我们指定长度为 10000 的链和大小为 4000 的老化。

LPSMAP_fit <- epilps(
  incidence = simepidemic$y,
  serial_interval = SI,
  tictoc = TRUE
)## Inference method chosen: LPSMAP. 
## CI for LPSMAP computed via lognormal posterior approx. of Rt.Total number of days: 40\. 
## Mean Rt discarding first 7 days: 1.327.
## Mean 95% CI of Rt discarding first 7 days: (1.164,1.527) 
## Elapsed real time (wall clock time): 0.229 seconds.LPSMALA_fit <- epilps(
  incidence = simepidemic$y, serial_interval = SI,
  method = "LPSMALA", chain_length = 10000, burn = 4000
)## Inference method chosen: LPSMALA with chain length 10000 and warmup 4000.
## MCMC acceptance rate: 56.41%. 
## Geweke z-score < 2.33 for:  32 / 33  variables. 
## Total number of days: 40\. 
## Mean Rt discarding first 7 days: 1.326.
## Mean 95% CI of Rt discarding first 7 days: (1.117,1.555). 
## Timing of routine not requested.

执行后,每个例程在控制台中打印用户请求的方法的简要摘要。

对于 LPSMALA,总结了链长、接受率(应该在 57%左右)等基本信息。从打印输出可以看出,模拟流行病的平均再生数约为 1.32。

比方说,我们现在可以使用LPSMALA_fit对象和plot()例程来获得平滑的流行病曲线和估计的再生数(默认情况下,可信区间为 5%的显著性水平,但这可以由用户更改)。

days <- seq(8, 40)#--- Smoothed epidemic curve
gridExtra::grid.arrange(
  plot(LPSMALA_fit,
    plotout = "epicurve", incibars = TRUE, themetype = "light",
    epicol = "darkgreen", cicol = rgb(0.3, 0.73, 0.3, 0.2),
    epititle = "Smoothed epidemic curve", titlesize = 13, barwidth = 0.25
  ), #--- Estimated reproduction number
  plot(LPSMALA_fit,
    plotout = "rt", theme = "light", rtcol = "black",
    titlesize = 13, Rtitle = "Estimated R (LPSMALA)"
  ),
  nrow = 1, ncol = 2
)

作者的情节

该图可以通过多种方式定制:

  • 用户可以在themetype下指定主题。可用选项有gray(默认)、classiclightdark
  • 其他选择,如是否显示发病率柱、可信区间包络的颜色、平滑流行病曲线的颜色和估计再生数也是可用的。

上图是在[ggplot2](https://statsandr.com/blog/graphics-in-r-with-ggplot2/) 中生成的,但是还有另一种直接从LPSMAP_fitLPSMALA_fit对象中提取信息的方法。事实上,可以提取并绘制每天的估计再现数值及其相关可信区间。

下面,我们进行练习并绘制分别用 LPSMAP 和 LPSMALA 获得的估计 Rt,并将其与真实的基本再生数曲线进行比较。合身度挺好的。

par(mfrow = c(1, 2))#--- LPSMAP vs target R
plot(days, sapply(days, simepidemic$Rtrue),
  type = "l", lwd = 2, ylim = c(0, 4),
  ylab = "Estimated R", xlab = "Time"
)
polygon(
  x = c(days, rev(days)), y = c(
    LPSMAP_fit$epifit$R95CI_low[8:40],
    rev(LPSMAP_fit$epifit$R95CI_up[8:40])
  ),
  col = rgb(0.23, 0.54, 1, 0.3), border = NA
)
lines(days, LPSMAP_fit$epifit$R_estim[8:40], type = "l", col = "cornflowerblue", lwd = 2)
lines(days, sapply(days, simepidemic$Rtrue), type = "l", lwd = 2)grid(nx = 10, ny = 10)
legend("topright",
  lty = c(1, 1), lwd = c(2, 2),
  col = c("black", "blue", rgb(0.23, 0.54, 1, 0.3)),
  c("Target R", "LPSMAP", "LPSMAP 95% CI"), bty = "n", cex = 0.9
)#--- LPSMALA vs target R
plot(days, sapply(days, simepidemic$Rtrue),
  type = "l", lwd = 2, ylim = c(0, 4),
  ylab = "Estimated R", xlab = "Time"
)
polygon(
  x = c(days, rev(days)), y = c(
    LPSMALA_fit$epifit$R95CI_low[8:40],
    rev(LPSMALA_fit$epifit$R95CI_up[8:40])
  ),
  col = rgb(1, 0.23, 0.31, 0.3), border = NA
)
lines(days, LPSMALA_fit$epifit$R_estim[8:40], type = "l", col = "red", lwd = 2)
lines(days, sapply(days, simepidemic$Rtrue), type = "l", lwd = 2)grid(nx = 10, ny = 10)
legend("topright",
  lty = c(1, 1), lwd = c(2, 2),
  col = c("black", "red", rgb(1, 0.23, 0.31, 0.3)),
  c("Target R", "LPSMALA", "LPSMALA 95% CI"), bty = "n", cex = 0.9
)

作者的情节

例如,您可以通过键入以下内容来访问上周疫情的结果:

# Estimated R of the last week (with LPSMAP)
round(tail(LPSMAP_fit$epifit[, 1:4], 7), 3)##    Date R_estim R95CI_low R95CI_up
## 34   34   0.708     0.663    0.756
## 35   35   0.724     0.676    0.775
## 36   36   0.809     0.755    0.868
## 37   37   0.971     0.908    1.039
## 38   38   1.205     1.135    1.279
## 39   39   1.461     1.384    1.541
## 40   40   1.671     1.557    1.794# Estimated mean number of cases of the last week (with LPSMAP)
round(tail(LPSMAP_fit$epifit[, 5:7], 7))##    mu_estim mu95CI_low mu95CI_up
## 34      284        225       357
## 35      254        202       319
## 36      248        196       312
## 37      267        211       338
## 38      319        253       404
## 39      411        325       520
## 40      552        394       774

美国住院数据

为了说明真实数据上的 EpiLPS,我们从COVID19包中下载了美国在 2021 年 9 月 1 日至 2022 年 9 月 1 日期间的住院数据,并应用epilps()例程来估计再生数。

install.packages("COVID19")
library("COVID19")# Get data and specify serial interval distribution
USADat <- COVID19::covid19(
  country = "US", level = 1, start = "2021-09-01",
  end = "2022-09-01", verbose = FALSE
)si <- c(0.344, 0.316, 0.168, 0.104, 0.068)inciUSA <- USADat$hosp
dateUSA <- USADat$date

我们使用带有 LPSMAP(默认)方法的epilps()例程,绘制平滑的流行病曲线和具有 95%可信区间的估计再生数。

epifit <- epilps(incidence = inciUSA, serial_interval = si, K = 20)## Inference method chosen: LPSMAP. 
## CI for LPSMAP computed via lognormal posterior approx. of Rt.Total number of days: 366\. 
## Mean Rt discarding first 7 days: 0.994.
## Mean 95% CI of Rt discarding first 7 days: (0.983,1.005) 
## Timing of routine not requested.gridExtra::grid.arrange(
  plot(epifit,
    dates = dateUSA, datelab = "3m",
    plotout = "epicurve", incibars = FALSE, themetype = "light",
    epicol = "darkgreen", cicol = rgb(0.3, 0.73, 0.3, 0.2),
    epititle = "USA smoothed epidemic curve", titlesize = 13
  ),
  plot(epifit,
    dates = dateUSA, datelab = "3m",
    plotout = "rt", theme = "light", rtcol = "black",
    titlesize = 13, Rtitle = "USA Estimated R"
  ),
  nrow = 1, ncol = 2
)

作者的情节

感谢阅读。我希望你会像我一样发现本文中开发的方法很有用。如果您碰巧在自己的研究中使用它,请随时联系我和作者。

和往常一样,如果您有与本文主题相关的问题或建议,请将其添加为评论,以便其他读者可以从讨论中受益。

参考

柯里,安妮,尼尔·M·费格森,克利斯朵夫·弗雷泽和西蒙·柯西梅兹。2013."一个新的框架和软件来估计流行病期间随时间变化的繁殖数."美国流行病学杂志 178(9):1505–12。

Gressani,Oswaldo,Jacco Wallinga,Christian L Althaus,Niel Hens 和 Christel Faes。2022." EpiLPS:一种快速灵活的估计时变再生数的贝叶斯工具." PLoS 计算生物学 18 (10): e1010618。https://doi.org/10.1371/journal.pcbi.1010618

相关文章

原载于 2022 年 10 月 19 日https://statsandr.com

论文说明:探索用于目标检测的平面视觉变压器主干

原文:https://towardsdatascience.com/paper-explained-exploring-plain-vision-transformer-backbones-for-object-detection-a84483ac83b6

vit 作为目标探测骨干的力量

在这个故事中,我们将仔细研究 Meta AI 的研究人员最近发表的一篇论文,其中作者探索了如何将标准 ViT 重新用作对象检测主干。简而言之,他们的检测架构叫做 ViTDet

用于普通主干网的特征金字塔网络(FPN)的可视化,如 ViTDet 中的一个,“经典”分级主干网,通常表示为 CNN。来源:[1]

先决条件:目标探测骨干

以前,目标探测器的主干网在网络的不同阶段受益于不同的分辨率。如上图所示,特征图具有不同的分辨率,执行实际物体检测步骤的检测头从中受益匪浅。这些主干在科学文献中通常被称为等级主干。通常,ResNets 或其他 CNN 被称为分层主干,但某些 vit(如 Swin Transformer)也有分层结构。我们今天要看的论文必须处理一种不同的主干结构:由于 vit 由一定数量的变换器块组成,所有变换器块都以相同的维度输出特征,所以它从不自然地输出不同分辨率的特征图。作者在论文中讨论了这个问题,并探索了构建多分辨率 FPN 的不同策略。

从单一分辨率主干生成多分辨率特征

由于 ViTs 自然只为其特征地图提供一种分辨率,作者探索了如何使用 FPN 将该地图转换为不同的分辨率。为了简化内存约束和增加特征输出的全局上下文,作者没有计算所有 ViT 块的自注意。相反,他们选择将变压器分成 4 个偶数部分,例如,对于具有 24 个块的 ViT-L,每个部分组成 6 个块。在每个部分的末尾,他们计算该部分的整体自我注意力,其输出被用作 FPNs 的特征图。

为 ViTDet 开发的不同 fpn。来源:[1]

对于方法(a) ,他们试图通过使用卷积或去卷积,从每个部分的个体全局注意力输出对 1/16 特征图进行上采样或下采样,来构建类似 FPN 的解决方案。它们还增加了横向连接,用连接蓝色方块的箭头表示。

对于方法(b) ,他们通过仅放大和缩小来自全局自我注意模块的最后特征图来构建 FPN。这意味着 FPN 中的所有要素都是根据单个输出构建的。此外,他们再次添加了横向连接。

对于方法(c) ,他们提出了一个非常简单纯粹的解决方案:对最终的整体注意力输出进行上采样和下采样,并且不增加任何横向连接。这种方法是迄今为止最简单的方法,但正如我们现在将看到的,它非常有效。

不同 FPN 方法的性能比较

让我们开始吧!

不同 fpn 的 COCO 结果。来源:[1]

值得注意的是,在 MS COCO 检测基准上,简单 FPN,方法(c),在两种 ViT 尺寸上,对于包围盒回归和实例分割工作得最好

但是,既然已经有了基于 ViT 的检测网络,为什么还要尝试这样一种简单的解决方案来将普通 ViT 用作检测主干呢?答案现在将变得显而易见。

与最先进的(SOTA) ViT 检测网络进行比较

最近在自我监督预培训领域的研究已经开始释放出 ViTs 令人难以置信的能力。该领域中最有前途的任务之一是挑战网络来重建对象的屏蔽部分,通过屏蔽自动编码器(MAE)论文来实现。我们在我的博客上重温了这篇论文,在这里 随意刷新你的知识

与之前基于 ViT 的检测器相比,ViT-Det 的 COCO 结果(底部三行)。来源:[1]

MAE 预先训练一个标准的 ViT 来学习重建图像的屏蔽部分。事实证明,这是一种成功的预培训策略。为了将这种优势转移到对象检测,作者创建了 ViTDet 架构。这就是本文的全部目的:释放预训练 ViTs 用于物体检测的能力。结果说明了一切。

从结果表中可以看出,使用 MAE 预训练主干,然后在其上使用简单的 FPN,可以得到基于 ViT 的检测主干的 SOTA 结果。由于 Swin 变压器和 MViT 在没有修改的情况下与自监督预训练策略不兼容,因此它们在 ImageNet 上被监督进行预训练。令人惊讶的是,MAE 预训练比标准的监督预训练释放出更多的性能。因此,作者暗示对象检测研究的未来改进将来自于:不是检测架构本身,而是以自我监督的方式对主干进行更强大的预训练。

在我看来,这代表了物体探测研究的一个关键转变。如果你想阅读更多关于自我监督预训练给计算机视觉领域带来的范式转变的信息,请点击这里 阅读我的故事

包装它

我们探索了 ViTDet 架构,这是对传统 fpn 的简单而强大的修改,特别是对 vit 的修改,它释放了自监督视觉变压器的能力,用于对象检测。不仅如此,这项研究还为目标检测研究的一个新方向铺平了道路,其中的重点是从架构转移到预训练技术。

虽然我希望这个故事能让你对 ViTDet 有一个很好的初步了解,但仍有很多东西有待发现。因此,即使你是这个领域的新手,我也会鼓励你自己阅读这些论文。你必须从某个地方开始;)

如果你对论文中介绍的方法有更多的细节感兴趣,请随时在 Twitter 上给我留言,我的账户链接在我的媒体简介上。

我希望你喜欢这篇论文的解释。如果你对这篇文章有任何意见,或者如果你看到任何错误,请随时留下评论。

最后但同样重要的是,如果你想在高级计算机视觉领域更深入地探索,考虑成为我的追随者。我试着到处张贴一个故事,让你和其他人对计算机视觉研究的最新进展保持兴趣!

参考资料:

[1]李,,等.“探索用于物体检测的平面视觉变换骨干”arXiv 预印本 arXiv:2203.16527 (2022)。

论文解释——具有多分辨率散列编码的即时神经图形图元

原文:https://towardsdatascience.com/paper-explained-instant-neural-graphics-primitives-with-a-multiresolution-hash-encoding-8e5a05865378

开创性的 NeRF 论文在计算机视觉界掀起了风暴,但提出的方法需要对每个新场景进行数小时的训练。NVIDIA 的一种新方法将这一时间缩短到了几秒钟

项目网站上的视频生成的 GIF,经许可拍摄。戴珍珠耳环的女孩翻新 Koorosh Orooj ( CC BY-SA 4.0 )。迪士尼云模型华特迪士尼动画工作室( CC BY-SA 3.0 )。

介绍

临近 2020 年底,注意到今年人工智能的主要趋势之一,教授兼研究员 Frank Dellaert 将他的一篇博客文章命名为“NeRF Explosion 2020”。Mildenhall 等人在他们的论文 NeRF:将场景表示为用于视图合成的神经辐射场中提出的惊人结果开辟了一条新的研究路线,该研究仍然是计算机视觉中最热门的主题之一,并可能代表许多应用程序的未来,如3D 形状和图像的合成、人体动画、3D 重建和姿态估计

最初的 NeRF 很棒,但是……非常慢!为单个场景训练模型可能需要几个小时,甚至几天。由 NVIDIA 研究人员托马斯·穆勒、亚历克斯·埃文斯、克里斯托夫·席德和亚历山大·凯勒撰写的论文即时神经图形图元与多分辨率哈希编码 ( 即时 NGP )提出了一种新的方法,即将这一时间从几个小时缩短到几秒钟

这是一个高级话题,你可能从未听说过 NeRF 或者需要复习一下。那样的话,别担心,我会掩护你的!下面你会发现我的文章,这一切都是从这张纸上开始的;我建议您在继续之前阅读它,因为我不打算重复解释已经讨论过的内容。

https://blog.devgenius.io/paper-explained-nerf-representing-scenes-as-neural-radiance-fields-for-view-synthesis-e16567180531

免责声明:大部分图片和内容来源于即时 NGP 论文 / 项目网站,在本文中我尽量报道了对其理解至关重要的部分,必要时补充细节。所有图片都是在作者许可下拍摄的,必要时还列出了额外的许可。

计算机图形图元

在解释即时 NGP 引入的新功能之前,有必要介绍四个任务,或者如论文中所称的计算机图形图元,作者将他们的方法与以前的方法进行了比较。

对于即时 NGP 的部分解释,与 NeRF 一样,首先根据一些逻辑对输入进行编码,然后使用多层感知器(MLP)来学习它们与给定输出之间的映射。在这一节中,我们将看到本文中考虑的所有任务的输入和输出是什么。

千兆像素图像近似

图片由报社许可拍摄。戴珍珠耳环的女孩翻新 Koorosh Orooj(CC BY-SA 4.0)。

这项任务旨在测试模型表现高频细节的能力,目标是了解(非常大的)图像的 2D 坐标与其 RGB 颜色之间的映射。基本上,该模型正在学习有效地压缩图像,并逐个像素地重建图像,也就是说,给定一个像素的坐标,它将返回该位置处原始图像的颜色。

例如,上图的分辨率为 20 000 X 23 466 (469 M RGB 像素),而根据我们稍后将介绍的超参数 t 的选择,模型的可训练参数分别为 117 k (T = 2)、2.7 M (T = 2 ⁷)和 47.5 M (T = 2)。

符号距离函数

报社许可拍摄的图像。

带符号距离函数(SDF)用于将 3D 形状表示为函数的零水平集。想象在感兴趣的表面上的一个点上计算 SDF,在这种情况下,SDF 将等于零,而如果该点在表面之外,则它将具有等于该点与表面的最小距离的正值,如果它在表面之内,则相同,但具有负号。

在这个任务中,MLP 学习从 3D 坐标到离表面的距离的映射。这可用于 3D 形状压缩(类似于之前的任务,但用于 3D 形状)和形状完成等任务,如 DeepSDF:学习形状表示的连续带符号距离函数所示。

神经辐射缓存

图片由报社许可拍摄。

该任务涉及从特征缓冲区预测照片级像素颜色。

在这种情况下,MLP 从蒙特卡洛 路径跟踪器 中学习给定场景的 5D 光场

在这种情况下,训练在线发生,在渲染过程中,“过拟合”在后台运行的路径跟踪器的输出。

神经辐射和密度场(NeRF)

图片由报社许可拍摄。

最后,最后一项任务是以发起该研究系列的论文命名的,即时 NGP 也是该研究系列的一部分。在这种情况下,MLP 从图像观察和相应的透视变换中学习给定场景的 3D 密度和 5D 光场。

通过这种方式,可以从新的有利位置创建场景的新视图,这个问题称为 新颖的视图合成 ,逼真地再现灯光效果和材质属性。更多细节请看我之前的文章

既然我们已经提出了我们想要解决的任务,让我们看看这篇论文的主要贡献是什么。

输入编码

频率编码

NeRF 受早期作品的启发,比如介绍 Transformer 架构的著名论文,将标量位置 x∈R 编码为 L∈N 正弦和余弦函数的多分辨率序列

图片由报社许可拍摄。

如 NeRF 论文中的消融研究所示,如果没有这种编码,高频细节将无法捕捉。

这种编码是固定的,因此不太有表现力,这意味着需要一个“大”MLP来学习前面介绍的复杂任务,因为从输入到输出的任务都委托给它了。

参数编码

最新的先进方法使用参数编码,其思想是在辅助数据结构中安排额外的可训练参数,例如网格。然后根据输入查找这些参数,并(可选地)进行插值。

为什么这种方法应该是有益的?我们用较大的内存占用换取较小的计算成本;我们可以拥有一个小得多的 MLP,而不是一个大的,虽然以这种方式消除的参数可以通过使用额外的数据结构以某种方式重新获得,只有其中的一个小的子集会随着每个梯度反向推而更新,从而大大加快了训练速度。这一点很关键,所以让我们看一个例子来更好地理解它。假设我们用 3D 网格划分我们感兴趣的场景,对于网格内的给定点,对于每个反向传播的样本,我们只需要更新 8 个嵌入(对于包含该点的网格中的 3D 体素的每个顶点一个嵌入)。当我们介绍所提出的方法时,这一点将变得更好理解。

即时 NGP 使用的编码就属于这一类;在描述它之前,让我们先看看以前的方法有什么问题。

一种可能的方法是使用类似于 ACORN:用于神经场景表示的自适应坐标网络中的域的树细分,其中大型辅助坐标编码器神经网络被训练为在 x 周围的叶节点中输出密集的特征网格。这种方法虽然比以前的方法产生更大程度的自适应性,但它以更高的计算成本实现,仅当足够数量的输入在每个叶节点结束时才可摊销。

另一种方法是使用可训练特征的密集网格,但是它们比神经网络权重消耗更多的内存。特别是,这种方法是浪费的,因为空白空间的区域被视为靠近表面的区域,这导致参数的数量增长为 O(N),而感兴趣的可见表面的区域仅增长为 O(N)。

如果感兴趣的表面是先验已知的,诸如八叉树稀疏网格的数据结构可用于剔除密集网格中未使用的特征。然而,在 NeRF 设置中,表面仅在训练期间出现

对于这些先前作品的所有参考,请参考论文。现在让我们来看看即时 NGP 最重要的组成部分之一。

多分辨率散列编码

图片由报社许可拍摄。

程序概要

论文的主要贡献肯定是提出了输入坐标编码

让我们首先简要地总结一下这个过程,然后分别分析细节:

  1. L d 维 网格被定义。具体来说,如果我们要建模的是一幅图像,至于“Gigapixel”任务,d 等于 2;另一方面,如果我们想要对三维体积建模,至于“NeRF”任务,d 等于 3。在上图中,你可以看到两个 2D 网格的例子,一个是分辨率为 1 / N0 的网格,另一个是分辨率为 1 / N1 的网格,我们将在后面看到这些 N l 是如何选择的
  2. 每个 l 与多达 T 个具有维度** F 的特征向量**相关联
  3. 对于一个给定的样本,其输入坐标 x 被映射到每一层中相应的体素 (d 维单元)。例如,在图像中,x 落在级别 0 的右下角单元格中,而在级别 1 的中间单元格中
  4. 给定层的顶点数V = (Nl + 1)^d,例如,如果 N1 = 3 且 d = 2(就像上图中 N1 将边精确地分为 3),对于层 1,我们有(3 + 1) = 16 个顶点。如果 V ≤T,我们在该层的顶点和特征向量之间有 1:1 的映射。在更精细的级别,其中 V > T,我们使用一个散列函数h将每个 d 维顶点映射到相应级别的 T 个特征向量之一。我们确实可以想到一个散列表,尽管没有明确的冲突处理
  5. 我们现在有了每层的 d 个特征向量,每个向量都与各自体素的顶点相关联。在这一点上,我们线性内插d 矢量以获得代表该层的最终矢量
  6. 我们连接每一层的最终矢量,如果有的话,辅助输入(视角方向,材质参数等。),这是将作为输入给一个多层感知器 ( MLP )的向量,从这里开始,程序类似于最初的 NeRF

现在让我们更详细地了解每一步。

选择每个级别的网格分辨率

一个几何级数用于定义在最粗 ( Nmin )到最细 ( Nmax )级别之间的中间分辨率:

图片由报社许可拍摄。

使用简单的对数规则,我们可以证明:

图片由作者提供。

我们看到,如果 l = 0,Nl = Nmin * b⁰ = Nmin,而如果 l = L - 1(级别范围从 0 到 L - 1),Nl = Nmin * Nmax / Nmin = Nmax,则其他值介于两者之间。

由于等级 L 的数量很大,增长因子 b 通常很小。论文的用例有 b ∈[1.26,2]。

空间哈希函数

在一个散列函数中我们想要什么特性?作者列举如下:

  1. 高效计算
  2. 导致连贯的查找
  3. 一致覆盖特征向量阵列,而不管查询点的结构如何

由于这些原因,即时 NGP 使用了一个空间哈希函数 [ Teschner 等人 2003 ]的形式

报社许可拍摄的图像。

其中⊕denotes 的逐位异或运算和ππI 唯一,大素数

优化散列函数也被认为是未来的工作,将该方法变成一种字典学习方法。

原文提供了其他细节,并为感兴趣的读者提供了有用的参考。

性能与质量

可以想象,随着 T 的增加,我们拥有更高的质量、更高的内存使用率和更低的性能(更长的训练/推理时间)。该论文强调的有趣方面是,虽然内存随 T 线性增长,但质量和性能往往呈亚线性增长。

同样,L 和 F 也代表了质量和性能之间的权衡。作者发现 F = 2 和 L = 16 对于他们的大多数应用来说都是很好的值。

隐式哈希冲突解决

我们说冲突不被处理,这意味着什么?

首先,我们注意到在低层没有碰撞,因为映射是 1:1。在可能发生这些碰撞的较高层,梯度被平均化。一个重要的观察是,不是所有碰撞的点都同等重要;可见表面上的点将具有更大的梯度,因此对学习的编码具有更大的影响。

值得注意的是,这并非完全没有后果,正如在一项关于使用 MLP 的重要性的最终消融研究中所示,我们看到哈希碰撞会在重建中产生一些噪声,如果我们用线性层替换 MLP,这种噪声会特别明显。

图片由报社许可拍摄。

d-线性插值

需要注意的是插补不是可选的。没有它,我们将有网格对齐的不连续性(块状外观)。线性插值不是人们能想到的最高阶,对于需要更高阶的应用,作者提出了 平滑步骤 函数

图片由报社许可拍摄。

这是更昂贵的 d-二次或 d-三次插值的低成本替代方案。

然而,这种解决方案在本文中从未使用过,根据经验,它会降低所考虑用例的重建质量。

履行

性能考虑因素

这项工作的一个相关方面是架构是如何实现的,在这里我们可以看到 NVIDIA 研究人员在充分利用 GPU 方面的经验。整个系统实现利用了完全融合的 CUDA 内核(融合运算以加速计算),并专注于最小化浪费的带宽和计算操作。散列表条目以半精度存储,以全精度维护参数的主副本,用于稳定的混合精度参数更新。他们还优化了 GPU 缓存,调度计算,以便在移动到下一个输入之前,在一个批处理中查找所有输入的多分辨率哈希编码的每个级别。关于哈希表的大小,他们能够最大限度地提高性能,直到 T ≤ 2 ⁹,超过这个阈值,性能开始显著下降,因为 NVIDIA RTX 3090 GPU 的二级缓存对于各个级别来说变得太小。

这种高度优化的 C++实现比 Python 中的简单实现快 10 倍。

有了这种级别的优化,将实现带来的改进与文章中提出的创新带来的改进区分开来是很重要的;在所有的实验中,作者都小心翼翼地将这两种效应分开,有时会重新实现以前的架构。

架构、初始化和培训

架构、初始化和训练程序都相当标准。我不会在这里报告我建议查阅文件的所有细节。重要的是要注意,同样在这种情况下,在编码之后,我们使用了一个在结构上与原始 NeRF 非常相似的 MLP,一个密度 MLP 后面跟着一个彩色 MLP,但是由于额外的可训练数据结构而变得更小。

实验和结果

图片由报社许可拍摄。

即时 NGP 超越了以前方法的性能和/或增加了所有考虑的任务的灵活性,具有更简单的额外优势。最令人惊讶的结果之一是,他们的峰值信噪比** ( PSNR )相匹配**

图片由作者提供。这个指标还有其他定义,这是它在即时 NGP 的 GitHub 库中的实现方式。

原来的 NeRF(训练小时数)只需 5–15s,与相比提升了 20–60 倍,这可以归功于提出的哈希编码

在其他任务中也可以看到类似的结果,例如,即时 NGP 在 2.5 分钟而不是 36.9 小时的训练后,在重建东京的千兆像素图像方面实现了与 ACORN 相同的 PSNR。

总的来说,我们可以说瞬时 NGP 令人难以置信的速度将使得有可能将 NeRF 应用于新的任务,例如,所提出的多分辨率散列编码是将 NeRF 加速几个数量级并匹配并行非神经 3D 重建技术的性能的直接替代。

结论

在本文中,我们探讨了 NVIDIA 的即时 NGP 如何让 NeRF 快如闪电。

如果这项新技术引起了你的兴趣,并且你想亲自尝试一下即时 NGP,你可以!看看官方 GitHub 库并按照所有的说明去做,结果会很惊人。

感谢您读到这里,如果您有任何与本文相关的建议或问题,请随时留下评论或与我联系,我的联系方式如下。

想保持联系,不要错过我的新文章?在LinkedInTwitter 上关注我。

论文解释:无监督视觉表征学习的动量对比

原文:https://towardsdatascience.com/paper-explained-momentum-contrast-for-unsupervised-visual-representation-learning-ff2b0e08acfb

查看 MoCo v1 和 v2 文件中的原则

在这个故事中,我们将回顾 MoCo 论文,这是一种使用动量对比(MoCo)对计算机视觉模型进行预训练的无监督方法,已经由作者从版本 1 到版本 3 进行了迭代改进。

于 2020 年由何等人在论文 【动量对比无监督视觉表征学习】 中首次提出,并在 【改进的基线与动量对比学习】 中进一步改进为 v2。在陈等人的“训练自监督视觉变形器的实证研究”中有另一个称为 v3 的迭代,它介绍了训练过程和模型架构的基本适应,我们将在未来的故事中介绍。

由于这种方法一直在不断改进,我们将首先回顾本文背后的最初想法,然后概述新版本引入的改进。我尽量让文章简单,这样即使没有什么先验知识的读者也能理解。事不宜迟,我们开始吧!

对最初的 MoCo 论文背后的关键思想的简单说明。来源:【1】

先决条件:计算机视觉的自我监督和非监督预训练

在我们深入研究 MoCo 的论文之前,有必要快速回顾一下自我监督的职前培训到底是怎么回事。如果你一直在阅读我的其他自我监督学习故事,或者你熟悉自我监督预培训,请随意跳过这一部分。

传统上,计算机视觉模型总是使用监督学习来训练。这意味着人类看着这些图像并为它们创建各种标签,这样模型就可以学习这些标签的模式。例如,人类注释者可以为图像分配一个类标签,或者在图像中的对象周围绘制边界框。但是,任何接触过标注任务的人都知道,创建足够的训练数据集的工作量很大。

相比之下,自我监督学习不需要任何人为创造的标签。顾名思义,模型学会自我监督。在计算机视觉中,对这种自我监督进行建模的最常见方式是获取图像的不同裁剪或对其应用不同的增强,并通过模型传递修改后的输入。尽管图像包含相同的视觉信息,但看起来并不相同,我们让模型知道这些图像仍然包含相同的视觉信息,即相同的对象。这导致模型学习相同对象的相似潜在表示(输出向量)。

我们可以稍后在这个预训练的模型上应用迁移学习。通常,这些模型然后在 10%的带有标签的数据上进行训练,以执行下游任务,如对象检测和语义分割。

使用动量对比进行无监督学习

在 MoCo 的论文中,无监督学习过程被框定为字典查找:每个视图或图像被分配一个关键字,就像在字典中一样。该密钥通过使用卷积神经网络对每个图像进行编码而生成,输出是图像的矢量表示。现在,如果一个查询以另一个图像的形式呈现给这个字典,这个查询图像也被编码成矢量表示,并且将属于字典中的一个关键字,具有最小距离的关键字。

左边是查询图像和编码器,右边是小批量密钥队列和 momentum 编码器。来源:【1】

训练过程设计如下:编码器网络选择并处理查询图像,以计算编码的查询图像 q 。由于该模型的目标是学习区分大量不同的图像,因此该查询图像编码不仅与一个小批量的编码关键图像相比较,而且与它们中的多个相比较。为了实现这一目标,MoCo 形成了一个由动量编码器网络编码的小批量队列。当选择新的小批量时,其编码被排队,并且数据结构中最旧的编码被出列。这将由队列表示的字典大小与批处理大小分离,并允许查询更大的字典。

如果查询图像的编码与字典中的关键字匹配,则这两个视图被认为来自同一图像(例如,多个不同的裁剪)。

训练目标由信息损失函数表示:

信息对比损失函数。来源:【1】

该损失函数允许模型学习来自同一图像的视图的较小距离以及不同图像之间的较大距离。为了实现这一点,损失类似于基于 softmax 的分类器损失,其目的是将 q 分类为 k+ 。对查询 q 的一个正键和 K 个负键计算总和。

最后,如第一幅图所示,通过编码器网络执行反向传播,但不通过动量编码器。

作者试图用动量编码器对每幅图像实现相对一致的编码。由于保持权重冻结不会反映模型的学习进度,他们建议使用动量更新而不是复制编码器的权重来获得一致的动量编码器结果。为了实现这一点,将动量编码器网络设置为由查询编码器的基于动量的移动平均值来更新。每次训练迭代都执行这种更新。

MoCo v2 的进一步改进

在模型的第二个版本中, MoCo v2 ,在他们的论文 “改进的基线与动量对比学习” 中,作者将 SimCLR 论文中的一些贡献转移到他们的模型中。最初,为了在预训练 MoCo 之后训练线性分类器,他们向模型添加了单个线性层。在 MoCo v2 中,该层已被 MLP 投影头取代,从而提高了分类性能。另一个显著的改进是引入了更强的数据扩充,类似于 SimCLR 中的数据扩充。他们通过添加模糊和更强的颜色失真来扩展 v1 增强。

最新版本的 MoCo v3 ,在“训练自我监督视觉变形者的实证研究”论文中介绍,将该模型改编为不同的训练过程。由于这些变化不仅仅是简单的扩展,我们将在以后的文章中讨论这篇文章。

包装它

在本文中,您已经了解了 MoCo,这是一篇最受欢迎的自我监督框架论文,为该领域更有前途的论文铺平了道路。

通常在我的论文故事中,我会在结尾介绍这种方法的结果和表现。这一次,我们将跳过这一部分,因为这两种方法都不是最先进的了,但 MoCo v3 是。尽管如此,我发现了解 MoCo 背后的原理作为最先进的 MoCo v3 论文的基础是非常重要的。

虽然我希望这个故事能让你对 MoCo 有一个很好的初步了解,但仍有很多东西有待发现。因此,即使你是这个领域的新手,我也会鼓励你自己阅读这些论文。你必须从某个地方开始;)

如果你对论文中介绍的方法有更多的细节感兴趣,请随时在 Twitter 上给我留言,我的账户链接在我的媒体简介上。

我希望你喜欢这篇论文的解释。如果你对这篇文章有任何意见,或者如果你看到任何错误,请随时留下评论。

最后但同样重要的是,如果你想在高级计算机视觉领域更深入地探索,考虑成为我的追随者。我试着每周发一篇文章,让你和其他人了解计算机视觉研究的最新进展。

参考资料:

[1]何,,等.“用于无监督视觉表征学习的动量对比”IEEE/CVF 计算机视觉和模式识别会议论文集。2020.https://arxiv.org/pdf/1911.05722.pdf

论文解释:推动自我监督网络的极限:我们能胜过 ImageNet 上没有标签的监督学习吗?

原文:https://towardsdatascience.com/paper-explained-pushing-the-limits-of-self-supervised-resnets-can-we-outperform-supervised-2ba322dd6409

探索 ReLICv2 中的新方法

在这个故事中,我们将看看最近的一篇论文,这篇论文推动了自我监督学习的发展,由 DeepMind 发表,昵称为 ReLICv2。

在他们的出版物“推动自我监督网络的极限:我们能胜过 ImageNet 上没有标签的监督学习吗?”、Tomasev 等人提出了对他们在 ReLIC 后面的名为“通过不变因果机制的表征学习”的论文中提出的技术的改进。他们方法的核心是增加了一个 Kullback-Leibler-Divergence 损失,这是用经典对比学习目标的概率公式计算出来的。不仅如此,他们还使用了一个完善的增强方案,并借鉴了其他相关出版物的成功经验。

我试图保持文章的简单,这样即使没有什么先验知识的读者也能理解。事不宜迟,我们开始吧!

遗迹训练管道的插图。来源:【1】

先决条件:计算机视觉的自我监督和非监督预训练

在我们深入讨论之前,有必要快速回顾一下自我监督预培训是怎么回事。如果你一直在阅读我的其他自我监督学习的故事,或者你熟悉自我监督预培训,请随意跳过这一部分。

传统上,计算机视觉模型总是使用监督学习来训练。这意味着人类看着这些图像,并为它们创建了各种各样的标签,这样模型就可以学习这些标签的模式。例如,人类注释者可以为图像分配一个类标签,或者在图像中的对象周围绘制边界框。但是,任何接触过标注任务的人都知道,创建足够的训练数据集的工作量很大。

相比之下,自我监督学习不需要任何人为创造的标签。顾名思义,模特学会自我监督。在计算机视觉中,对这种自我监督进行建模的最常见方式是获取图像的不同裁剪或对其应用不同的增强,并通过模型传递修改后的输入。尽管图像包含相同的视觉信息,但看起来并不相同,我们让模型知道这些图像仍然包含相同的视觉信息,即相同的对象。这导致模型学习相同对象的相似潜在表示(输出向量)。

我们可以稍后在这个预训练的模型上应用迁移学习。通常,这些模型然后在 10%的带有标签的数据上进行训练,以执行下游任务,如对象检测和语义分割。

新颖的贡献和知识的结合

正如许多其他自我监督的预训练技术一样,ReLICv2 训练过程的第一步是数据扩充。在论文中,作者首先提到了以前成功的增强方案的使用。

第一个是在 SwAV 中使用的增强。与以前的工作相反,SwAV 不仅创建了输入图像的两种不同的裁剪,而且多达 6 种裁剪。这些可以制成不同的尺寸,如 224x244 和 96x96,最成功的数量是两个大作物和 6 个小作物。如果你想了解更多关于 SwAV 的增强计划,请务必阅读我关于它的故事

前面描述的第二组扩充来自 SimCLR。这个方案现在被这个领域几乎所有的报纸所采用。通过应用随机水平翻转、颜色失真、高斯模糊和曝光来处理图像。如果你想了解更多关于 SimCLR 的信息,请务必去看看我的文章

但是 ReLICv2 还使用了另一种增强技术:从图像中的对象中移除背景。为了实现这一点,他们以无人监管的方式在一些 ImageNet 数据上训练了一个显著背景去除模型。作者发现,当以 10%的概率应用时,这种增加是最有效的。

使用无监督 DeepUSPS 的显著背景去除增强。来源:【2】

一旦图像被增强并且进行了多次裁剪,输出就通过编码器网络和目标网络,它们输出相同维度的特征向量。当使用反向传播更新编码器网络时,目标网络通过类似于MoCo 框架的动量计算接收更新。

ReLICv2 的总体目标是学习编码器网络,以便为相同的类产生一致的输出向量。为了实现这一点,作者制定了一个新的损失函数。他们从标准对比负对数似然开始,其核心是一个相似性函数,将锚视图(主输入图像)与正样本(图像的增强版本)和负样本(同一批中的其他图像)进行比较。

ReLICv2 损失函数由负对数似然性以及锚视图和正视图的 Kullback-Leibler 散度组成。来源:【2】

这种损失通过对比目标的概率公式 : 锚图像和正面的可能性之间的 Kullback-Leibler 差异而扩展。这种强制网络不学习积极的靠近和消极的远离,但是在避免可能导致学习崩溃的极端聚类时,在集群之间创建更平衡的景观。因此,这个附加损失项可以被视为类似于调节。这两项都有一个α和β超参数,允许对这两项损失进行单独加权。

所有这些新奇事物的加入证明是成功的。为了找出是在哪些方面,让我们更仔细地看看论文中给出的结果。

结果

relic v2 试图证明的要点,正如其在论文标题中所说的,是自监督预训练方法只有在它们都为编码器网络使用相同的网络架构时才具有可比性。对于他们的工作,他们选择使用经典的 ResNet-50。

在 ImageNet 线性评估方案下使用不同的预训练 ResNet-50 的结果。来源:【2】

当使用相同的 ResNet-50 并在 ImageNet-1K 上训练其线性层,同时冻结所有其他权重时,ReLICv2 的输出远远超过现有方法。引入的改进甚至导致相对于原始遗物纸的性能优势。

与不同数据集上的监督预训练模型相比,准确性有所提高。来源:【2】

当在其他数据集上比较迁移学习性能时,ReLICv2 与其他方法(如 NNCLR 和 BYOL)相比,继续表现出令人印象深刻的性能。这进一步表明 ReLICv2 是一种新型的自我监督预训练方法。对其他数据集的评估在其他论文中不常提及。

ReLICv2 和 BYOL 对习得集群的阐释。点越蓝,学习到的越接近对应的类簇。来源:【2】

另一个生动的图形显示了 ReLICv2 学习的职业比其他框架如 BYOL 学习的职业更接近。这再次表明,与其他方法相比,该技术具有创建更细粒度集群的潜力。

包装它

在本文中,您已经了解了 ReLICv2,这是一种用于自我监督预训练的新方法,已经显示出有希望的实验结果。

通过结合对比学习目标的概率公式,并通过添加经过验证的增强方案,该技术已经能够将视觉中的自我监督预训练空间向前推进。

虽然我希望这个故事让你对 ReLICv2 有了一个很好的初步了解,但仍有很多东西有待发现。因此,即使你是这个领域的新手,我也会鼓励你自己阅读这些论文。你必须从某个地方开始;)

如果你对论文中介绍的方法有更多的细节感兴趣,请随时在 Twitter 上给我留言,我的账户链接在我的媒体简介上。

我希望你喜欢这篇论文的解释。如果你对这篇文章有任何意见,或者如果你看到任何错误,请随时留下评论。

最后但同样重要的是,如果你想在高级计算机视觉领域更深入地探索,考虑成为我的追随者。我试着每周发一篇文章,让你和其他人了解计算机视觉研究的最新进展。

参考资料:

[1]米特洛维奇、约瓦娜等人,“通过不变因果机制的表征学习”arXiv 预印本 arXiv:2010.07922 (2020)。https://arxiv.org/pdf/2010.07922.pdf

[2] Tomasev,Nenad,等人“推动自我监督的结果网的极限:在 ImageNet 上没有标签的情况下,我们能胜过监督学习吗?." arXiv 预印本 arXiv:2201.05119 (2022)。https://arxiv.org/pdf/2201.05119.pdf

论文综述:视觉异常检测的修复重建

原文:https://towardsdatascience.com/paper-review-reconstruction-by-inpainting-for-visual-anomaly-detection-70dcf3063c07

用 5 分钟时间解释如何通过将带有随机图案的图像传送到自动编码器来改进异常检测

Unsplash 上由 Neven Krcmarek 拍摄的照片

在这个故事中,我将回顾由卢布尔雅那大学介绍的修复异常检测 (RIAD)方法【1】。本文中有两个主要概念:

  • RIAD 是一种方法,去除图像中的部分局部区域,并重建由受损图像开始的图像
  • RIAD 基于一个编码器-解码器网络,该网络学习区分无异常图像有缺陷图像

概述

  1. 先决条件
  2. MVTec 数据集
  3. RIAD 算法
  4. 结果

1。先决条件

在深入解释本文之前,有一些概念需要了解。由于论文使用了自动编码器,你需要了解它是什么。查看我的文章来了解它是如何工作的。

  • U-net
  • auto encoder 如何应用于异常检测?
  • 自动编码器在异常检测中的局限性
  • 自动编码器的重建损失
  • 结构相似度
  • 梯度幅度相似度

优信网

U-net 是 Ronneberger 在 2015 年推出的卷积编解码网络[4]。它由两个网络组成:压缩数据模式的编码器和解压缩并重建原始数据的解码器。这个网络的主要特点是它的架构。在编码器架构中,每个卷积层中的特征通道数量加倍,而在解码器架构中,通道数量减半。此外,U-net 使用跳过连接来通过网络的不同层传输功能。这是导致精确重建的一个重要方面。

为了更好的了解细节,有一篇@ Sik-Ho Tsang 写的评论解释了 U-net 的架构。

autoencoder 如何应用于异常检测?

  • 对于银行、保险和制造业等众多领域而言,异常检测是一项至关重要的挑战。在这种情况下,自动编码器可以很好地解决这类问题,因为它们可以处理无人监督的问题。
  • 在训练期间,您只将正常数据传递给自动编码器。这样,模型将从提供的训练数据中学习潜在的表示。我们还将假设在训练期间没有观察到的异常数据不应该被自动编码器很好地重建,因此与正常数据相比应该具有高重建误差
  • 例如,可以应用自动编码器来解决欺诈检测问题。在训练期间,我们只将正常的交易传递给模型。当我们评估测试集中的模型时,大多数欺诈性交易可能具有高于正常交易的均方误差。

自动编码器在异常检测中的局限性

  • 自动编码器倾向于学习“身份功能
  • 然后,它能够重新创建异常数据,即使它从未被训练过。

自动编码器的重构损耗

训练自动编码器的损失函数被命名为重建损失。最流行的重建损失是均方误差 (MSE) 损失函数。它计算原始输入和网络输出之间的平方差的平均值。如果应用于图像,MSE 测量我们正在比较的图像的平均像素差异。MSE 越高,图像越不相似。

MSE 的替代方案是 L1 函数,其测量输入和输出之间的绝对差值的和

虽然这些重建损失是常用的,但它们有一些局限性。它们假设相邻像素之间的独立性,这通常是不被尊重的,并且不足以定量地测量原始样本和网络输出之间的相似性。

结构相似性

为了考虑局部相关性,而不是单个像素值的差异,许多论文中提出了 SSIM (结构相似性)损失【2】。SSIM 测量一对图像补片 p 和 q 之间的距离,其特征在于三个不同的方面:

  • 亮度,通过计算补丁的平均值来考虑。
  • 对比度是补丁方差的函数。
  • 结构通过计算两个面片的协方差来考虑。

其取值范围介于-1 和 1 之间。当分数接近 1 时,这意味着两个被比较的图像是相似的,而分数为-1 表示它们非常不同。

梯度震级相似性

像 SSIM,梯度幅度相似性是一个补丁相似性度量。它考虑了比较图像之间的局部相似性[3]。不是比较图像像素,而是考虑两幅图像 I 和 I_{R}上的局部梯度。

其中 g(I)是图像 I 的梯度幅值图,c 是常数以确保数值稳定性。

沿原始图像的 x(或水平)和 y(或垂直)方向卷积 3x3 Prewitt 滤波器 得到原始图像的梯度幅值图。

2.MVTEc 广告数据集

MVTEc 广告数据集[5]

MVTec AD 是一个新颖而全面的工业数据集
,由 5354 幅高分辨率图像组成,分为 15 类:5 种纹理和 10 种物体【5】。训练集仅由正常的
图像组成,而测试集包含有缺陷和无缺陷的图像。
图像分辨率在 700x700 和 1024x1024 像素之间变化

3.里亚德算法

RIAD 方法概述[1]

在无异常的图像上训练自动编码器,其中随机选择的区域被设置为 0。如前所述,使用的编码器-解码器架构是 U-net。但是这些图像是如何被掩盖的呢?这些是以下步骤:

  • 我们通过输入区域大小参数 k 从每幅图像中随机选择一组像素。
  • 每个图像可以被认为是一个尺寸为高度/k *宽度/k 的网格。
  • 我们的图像/网格被随机分成 n 个不相交的(或不重叠的)集合 Si
  • 我们生成一个二进制掩码,M_{Si},如果局部区域属于集合 Si,它包含 0,否则包含 1。
  • 最终图像是二进制掩模和原始图像的乘积。

从图像中随机移除像素的算法[1]。

在 U-网的训练过程中,大小 K 是从一组 K 值中随机选取的。在本文中,他们使用 K = {2,4,8,18}作为一组区域大小。在将掩模应用于原始图像之后,通过将修补的图像提供给模型来获得重建,然后通过考虑三种类型的损失来计算总损失:

  • MSGMS 损失,即几个尺度下的平均 GMS 距离:

其中 Hl 和 Wl 分别是被比较图像的高度和宽度,Nl 是比例 l 下的像素数

  • SSIM 损失是所有本地 SSIM 值的平均值:

  • 逐像素 L2 损失(或均方误差损失)

U-net 的训练[1]。

然后,我们可以获得在每个 k 值下为每个图像重建 I_{R}产生的异常图 G_A(I,I_{R}) 的平均值。随后,我们计算图像级异常分数,其本质上是异常图平均值的最大值。

对 U-net 的评价[1]。

评估期间获得的异常图示例说明[1]。

比如我们假设 K = {2,4,8,16}。我们计算属于{2,4,8,16}的每个 k 值处的每个图像重建的异常图,然后,我们计算这些异常图的平均值,如最后一列所示。

4.结果

异常检测的图像水平 ROC-AUC 评分[1]。

  • 与其他最先进的模型相比,RIAD 在对象类别上表现更好,如瓶子、金属螺母和牙刷,它们呈现出较高的平均 ROC-AUC,并且在一些纹理类别上也取得了不错的结果,如网格皮革

异常定位的每像素水平 ROC-AUC 得分[1]。

  • 此表显示了 MVTec AD 数据集类别的异常定位结果。
  • 与其他最先进的方法相比,它在异常定位方面获得了更高的总体 ROC-AUC 分数。

参考

[1] 视觉异常检测修复重建,Vitjan Zavrtanik,Matej Kristan,Danijel Skocaj,(2021)

[2] 通过将结构相似性应用于自动编码器来改进无监督缺陷分割,保罗·博格曼,辛迪·洛,迈克尔·福瑟,大卫·萨特勒格,卡斯滕·斯泰格

[3] 图像质量评估技术显示了自动编码器生成对抗网络的改进的训练和评估,迈克尔·o·弗托利,吉姆·戴维斯,卡尔顿大学,(2017)

[4] U-Net:用于生物医学图像分割的卷积网络,Olaf Ronneberger,Philipp Fischer,Thomas Brox,(2015)

[5]MVTec 异常检测数据集:用于无监督异常检测的综合真实数据集,保罗·博格曼、基利安·巴茨纳、迈克尔·福瑟(2021)

Github 知识库

其他相关文章:

你喜欢我的文章吗? 成为会员 每天无限获取数据科学新帖!这是一种间接的支持我的方式,不会给你带来任何额外的费用。如果您已经是会员, 订阅 每当我发布新的数据科学和 python 指南时,您都可以收到电子邮件!

论文简化:反相关噪声注入用于改进推广

原文:https://towardsdatascience.com/papers-simplified-anticorrelated-noise-injection-for-improved-generalization-8dd379af059c

机器学习研究

了解在梯度更新期间添加噪声如何有助于概化,包括在 TensorFlow 中的实现

Unsplash 上的照片

在本文中,我们将查看安东尼奥·奥维多、汉斯·克斯汀、弗兰克·普罗斯克、弗朗西斯·巴赫和奥雷连·卢奇于 2022 年 2 月 6 日发表的一篇论文。它被称为反相关噪声注入,用于改进泛化【1】,而你可以在这里找到

我会这样总结这篇论文:

当使用梯度下降优化复杂函数时(即,当训练神经网络时),在梯度更新期间以智能方式添加噪声。

让我们深入研究一下这张纸,看看与不太聪明的方法相比,聪明的方法是什么。在摘要中,作者写了以下内容:

将人工噪声注入梯度下降(GD)通常被用来提高机器学习模型的性能。通常,在这种扰动梯度下降(PGD) 方法中使用 不相关的噪声。但是,不知道这是否是最佳的,也不知道其他类型的噪声是否可以提供更好的泛化性能。在本文中,我们放大了关联连续 PGD 步骤的扰动的问题。我们考虑了各种目标函数,发现带有反相关扰动的 GD(“反 PGD”)比 GD 和标准(不相关的)PGD** 更具普遍性。为了支持这些实验发现,我们还得出了一个理论分析,表明反 PGD 运动到更宽的最小值,而广东和 PGD 仍然停留在次优区域甚至发散。反相关噪声和泛化之间的这种新联系为利用噪声来训练机器学习模型的新方法开辟了领域。**

这真的让我开始思考。在梯度下降过程中添加噪声不仅有帮助。如果该噪声在梯度下降步骤中相关,则优化结果在某些方面甚至更好。这种方法被作者称为反 PGD

在这篇文章中,我不会向你解释所有的(令人兴奋!)数学细节。相反,我将为您提供一些实现和图片,以便理解本文的主旨。我还尽力创建了论文中提到的优化器的实现,但是要小心使用代码,因为我在这方面也不是专家。但是我很高兴得到任何反馈

先决条件

为了理解反 PGD(反扰动梯度下降)是什么,让我们简单回顾一下 GD 和衍生算法如 SGD 和 PGD 是如何工作的。我在本文的先决条件部分解释了关于 GD 和 SGD 的重要内容

但是让我给你一个简略的版本。

梯度下降

让我们假设我们想要最小化一个函数 f ,其梯度表示为∇ f ( θ )。然后,GD 随机初始化模型的参数 θ ,然后用简单的更新规则t15】θ←θ—αf(θ)进行迭代更新,直到收敛,即 θf(θ)do α 是学习率。**

对于凸函数,如果学习率足够小,GD 保证找到全局——也是唯一的局部——最小值。例如,对 L2 损失进行线性回归就是这种情况。对于非凸函数,GD 可能会陷入(许多)局部极小值之一。

图片由我提供。

随机梯度下降

SGD 也做同样的事情,但是它估计f ( θ )而不是像 GD 那样精确地计算它。这有几个优点:

  • 使用较少的 RAM ,因为我们不需要解析完整的数据集来计算梯度
  • 更快的收敛 (在历元数中)因为在每个历元中关于 dataset_size / batch_size 的梯度更新发生
  • 根据罗伯特·克莱恩伯格、李沅芷和杨远[2]的说法,与 GD 相比,它倾向于找到更好的局部极小值。

第三点肯定不是显而易见的,你应该查看这篇论文来更深入地探讨这个话题。基本上,从 GD 到 SGD 平滑我们想要优化的功能(假设从现在开始最小化):

图片由我提供。

扰动梯度下降

这里,我们稍微改变了更新规则:

图片由我提供。

其中ξₙ(扰动 ) 来自一个居中的正态分布 N (0, σ ),并且是随机独立的。在梯度更新期间添加这种噪声可能有助于优化器摆脱局部最小值。然而,作者无法观察到使用 PGD 训练的模型实现了更好的泛化性能。尽管如此,通过给这个想法一点小小的改变,作者使它变得更加有用。

反扰动梯度下降

更准确地说,作者认为拥有反相关扰动是有益的。他们伪造它们的方式再简单不过了:

图片由我提供。

这里——再一次——ξₙ独立于一个 N (0, σ 分布。这已经足以产生完美的反相关扰动,这意味着

图片来自他们的论文。

这里, I 只是单位矩阵。为了得到为什么这是真的直觉,想象一下ξₙ是一维的。然后我们可以忽略换位,可以计算

图片由我提供。

这同样适用于更高维度。不错!

然后作者推断这些扰动对损失函数 f = L 的最小化有以下影响:

从他们的报纸上。

注: 一个函数的 黑森 只是广义二阶导数。例如,如果有函数 f ( x y ) = xy ,则给出的黑纱为

图片由我提供。

轨迹 Tr 就是主对角线的和——从左上到右下,即 0 + 6 xy

主要要点是:

我们不是试图最小化损失函数,而是试图最小化损失加上其二阶导数的大小,二阶导数充当正则化项。

这也与从 GD 切换到 SGD 如何平滑损失函数有关。

反 PGD 如何对抗广东和 PGD

好的,我们加入了一些粗麻布和一条现在用于正规化的痕迹。但是为什么呢?为什么有这个正则化子是有益的?我们将在本节中找到答案!

为了给出一个直觉,作者在所谓的加宽谷函数上测试他们的反 PGD

图片由我提供。

他们试图将其最小化。虽然最小化这个函数是微不足道的(设置 v=0 或所有 u = 0),但作者(还有我)认为,例如,我们在(0,0,…,0)处的最小值比在(0,100,100,…,100)处的最小值更好一些。要了解原因,请看他们论文的截图:

从他们的报纸上。

在左边,你可以看到一个函数图。原点(0,…,0)的最小值被平地包围,而远离原点的最小值则在越来越陡的山谷中。这是图表中的大图:

从他们的论文中,加上我的注解。

因此,在某种意义上,我们希望我们的优化器在原点找到最小值,因为它感觉更安全。如果我们在原点稍微摆动一下最小值,我们仍然会有一点损失。然而,如果我们达到最小值(0,100,…,100),稍微改变v——例如从 0 到 0.1——会使损失飙升到 100 d ,维度 d 越高,损失越大。

当然,反 PGD 在起源中找到了好的最小值。

GD 似乎找到了退而求其次的东西, **PGD 甚至走向被高山环绕的险峻极小**。😅

反 PGD 发现它是因为黑森的痕迹告诉它:

从他们的报纸上。

在哪里

图片由我提供。

如果我们将此作为正则项添加到 L 中,优化器将被迫寻找最小值(0,…,0),因为优化目标变为

图片由我提供。

而这个函数最小化正好是对于 u = (0,… 0)和 v = 0,没有别的。

作者声称,这不仅适用于加宽谷函数,而且更是一个一般概念。从他们的论文来看:

从他们的报纸上。

履行

你甚至可以在实践中尝试一下,因为据我所知,我实现了 SGD (让你开始了解如何编写优化器)、PGD 和反 PGD。如有错误,请写评论!**

**import tensorflow as tf

class SGD(tf.keras.optimizers.Optimizer):
    def __init__(self, learning_rate=0.01, name="SGD", **kwargs):
        super().__init__(name, **kwargs)
        self._set_hyper("learning_rate", kwargs.get("lr", learning_rate))

    @tf.function
    def _resource_apply_dense(self, grad, var):
        var_dtype = var.dtype.base_dtype
        lr_t = self._decayed_lr(var_dtype)  # learning rate decay
        new_var = var - grad * lr_t
        var.assign(new_var)

    def get_config(self):
        base_config = super().get_config()
        return {
            **base_config,
            "learning_rate": self._serialize_hyperparameter("learning_rate"),
        }

class PGD(tf.keras.optimizers.Optimizer):
    def __init__(self, learning_rate=0.01, std=0.5, name="PGD", **kwargs):
        super().__init__(name, **kwargs)
        self._set_hyper("learning_rate", kwargs.get("lr", learning_rate))
        self._set_hyper("std", std)

    @tf.function
    def _resource_apply_dense(self, grad, var):
        var_dtype = var.dtype.base_dtype
        lr_t = self._decayed_lr(var_dtype)
        perturbation = tf.random.normal(var.shape)
        new_var = var - grad * lr_t + self._get_hyper("std", dtype=tf.float32) * lr_t * perturbation
        var.assign(new_var)

    def get_config(self):
        base_config = super().get_config()
        return {
            **base_config,
            "learning_rate": self._serialize_hyperparameter("learning_rate"),
            "std": self._serialize_hyperparameter("std")
        }

class AntiPGD(tf.keras.optimizers.Optimizer):
    def __init__(self, learning_rate=0.01, std=0.5, name="AntiPGD", **kwargs):
        super().__init__(name, **kwargs)
        self._set_hyper("learning_rate", kwargs.get("lr", learning_rate))
        self._set_hyper("std", std)

    def _create_slots(self, var_list):
        for var in var_list:
            self.add_slot(var, "previous_perturbation", initializer="random_normal")

    @tf.function
    def _resource_apply_dense(self, grad, var):
        var_dtype = var.dtype.base_dtype
        lr_t = self._decayed_lr(var_dtype)
        previous_perturbation = self.get_slot(var, "previous_perturbation")
        current_perturbation = tf.random.normal(var.shape)
        perturbation_diff = current_perturbation - previous_perturbation
        new_var = var - grad * lr_t + self._get_hyper("std", dtype=tf.float32) * lr_t * perturbation_diff
        previous_perturbation.assign(current_perturbation)
        var.assign(new_var)

    def get_config(self):
        base_config = super().get_config()
        return {
            **base_config,
            "learning_rate": self._serialize_hyperparameter("learning_rate"),
            "std": self._serialize_hyperparameter("std")
        }**

结论

在他们的论文中,Antonio Orvieto、Hans Kersting、Frank Proske、Francis Bach 和 Aurelien Lucchi 已经表明,在梯度更新期间添加噪声可能有利于找到更好的最小值。在机器学习的设置中,这些是具有良好泛化能力的最小值。

然而,像 PGD 那样添加独立噪声的天真方法不足以找到这些最小值——它甚至适得其反,而(S)GD 似乎是更好的选择。作者推荐反相关噪声,产生反 PGD** 优化器。**

作者声称可能运行良好是因为:

平坦最小值=小 Hessian =良好的概括

但是还有很多研究要做。他们甚至说 SGD 之所以如此有效是因为同样的效果:

从他们的报纸上。

所以,似乎即使对于像 SGD 这样简单的算法,还是有那么多不清楚的地方。我们甚至还没有达到 RMSprop、Adam 或 Fadam 的水平。

图片由我提供。

我希望你今天学到了新的、有趣的、有用的东西。感谢阅读!

作为最后一点,如果你

  1. 想支持我多写点机器学习和
  2. 无论如何都要计划获得中等订阅量,

为什么不做 通过这个环节 ?这将对我帮助很大!😊

透明地说,给你的价格不变,但大约一半的订阅费直接归我。

非常感谢,如果你考虑支持我的话!

有问题就在LinkedIn上写我!**

参考

[1]奥维多,a .,克尔斯汀,h .,普罗斯克,f .,巴赫,f .和卢奇,a .,2022。反相关噪声注入,以提高泛化能力arXiv 预印本 arXiv:2202.02831

[2] Kleinberg,b .,Li,y .和 Yuan,y . 2018 年 7 月。另一种观点:SGD 何时逃离局部极小值?。在机器学习国际会议(第 2698–2707 页)。PMLR。

论文简化:无反向传播的梯度

原文:https://towardsdatascience.com/papers-simplified-gradients-without-backpropagation-96e8533943fc

机器学习研究

如何评估一个函数并一次计算其梯度的近似值

Photo by 愚木混株 cdd20 on Unsplash

在这篇文章中,我们将看看由 atlm günebaydin,Barak A. Pearlmutter,Don Syme,Frank Wood 和 Philip Torr 于 2022 年 2 月 17 日发表的一篇最近的论文——没有反向传播的梯度你可以在这里找到

这份文件仍在审查中,所以以后可能会有一些变化。尽管如此,非常简洁的核心思想应该保持不变。让我们读一下报纸的摘要:

使用反向传播来计算优化目标函数的梯度一直是机器学习的支柱。反向传播或反向模式微分是自动微分算法家族中的一个特例,它也包括正向模式。我们提出了一种仅基于方向导数计算梯度的方法,人们可以通过正演模式精确有效地计算方向导数。我们称这个公式为正向梯度,这是一个无偏的梯度估计,可以在函数的单次正向运行中进行评估,完全消除了梯度下降中的反向传播需求。我们在一系列问题中演示了前向梯度下降,显示出在计算上的大量节省,并在某些情况下使训练速度提高两倍

听起来不错,对吧?现在,我会给你一些直觉,告诉你这种方法实际上是如何工作的,以及为什么会工作。我还会在途中解释一些重要的数学概念,给那些对此感到有点不安全的人。让我们开始吧。

先决条件

在开始我们的旅程之前,让我们回顾一些重要的事情,以便正确看待整个主题。让我们假设我们想要优化某个目标函数 f只是想象最小化神经网络情况下的损失函数。

f 有很多,可能有几十亿个参数,因此这个任务很难完成。尽管如此,聪明人设法发明了许多算法来解决这个问题,其中有一阶方法,如梯度下降,还有高阶优化算法,如牛顿法

然而,由于作者关注的是前者,我们也将这样做。

对渐变的需求

你可能知道通常做梯度下降的方式。这里我指的是随机梯度下降(SGD)、RMSProp、Adam、Adamax 等算法。这些工具在 Tensorflow、PyTorch 或 scikit-learn 等软件库中都很容易找到。

所有这些不同的优化算法都依赖于一个共同的成分:目标函数ft5】的梯度,表示为f(θ)。上述算法之间的区别在于它们如何处理梯度。例如,最简单的算法 SGD 从一个随机的 θ 开始,然后用简单的更新规则θθ-αf(θ)进行更新,直到收敛,即 θf(θ)这里的 α 是学习率*。***

RMSProp,Adam 等人也采用了梯度,但是做了更精细的计算,这导致了更快的收敛。考虑下面的动画,这是一个老歌,但歌蒂:

亚历克·拉德福德的伟大动画。

反向传播

现在让我们继续研究神经网络。通常计算梯度∇ f ( θ )的方法是使用一种叫做反向传播的技术,这实质上是微积分中的链式法则。别担心,我们不会谈论太久,也不会让你做冗长的计算。只看下面这个简单的例子函数: f ( θ,θ,θ)=(θ₂*)θ₃.*

使用反向传播计算梯度的工作方式如下:

  1. 前进传球:**插 θf,接 f ( θ )
  2. 向后传递:f ( θ )开始,计算向后的坡度。

让我们插入向前传球的数字 θ = (3,-1,2),然后看看在我们的玩具网络中会发生什么:

向前传球。我创作的动画。

向后传球。我创作的动画。右边的蓝色 1 是初始渐变值。

向前传递从左到右计算输出 4,非常容易。然后向后传递从右到左计算所有的梯度。我们只对最后的梯度感兴趣,在我们的例子中,∇ f ( θ ) = (2,2,2)。由于直接计算得到∇f(θ)=(θ₃, θ ₃, θ+ θ ₂).,所以结果是正确的

注意:直接计算梯度是一种奢侈,我们在处理复杂公式中的数十亿个参数时没有这种奢侈。

到目前为止一切顺利,但这意味着我们必须通过网络两次。我们能否在一次正向传递中计算函数输出和梯度?

这篇论文的作者提出了一个简单的方法来做到这一点!但是我们仍然需要一个要素来理解他们的方法。

方向导数

如果你以前从未和他们打过交道,这听起来可能很可怕,但让我用简单的方式解释给你听。在学校,你知道如何对一个变量求导,对吗? θ 为标量 时的定义如下:

我创造的形象。

这个值f’(θ)就是图中点 θ、的切线的斜率,即一个实数。

现在,当处理神经网络时,我们的函数 f 没有单个输入,而是潜在地有数十亿个输入,记得吗?因此,在这种情况下,我们必须为导数找到一个更一般的定义。当然,数学家已经帮我们找到了答案。 f 相对于矢量 v 在点 θ 的方向导数定义为

我创造的形象。

为什么我们需要一个 v ?在更高维度中,我们有很多选择来定义图的切线。以抛物面为例:在这个形状表面的黑点中,我们可以任意定义多条切线。

原图来自维基百科。漂亮的切线是我加的。

如果我们选择一些 v = ( v,v ₂) 切线会指向同一个方向,只是多了一个z-坐标,即( v,v ₂, s )。这个斜坡 s 正好是∇ ᵥf ( θ )顺便说一下。

无论如何,如果你对几何不感兴趣,只要记住∇ ᵥf ( θ )也是一个标量,就像f’(θ)一样。对于每个 v,都是不同的数字,表示 fv 方向的斜率。现在可以证明以下几点:

我创造的形象。点表示两个向量的点积。

这里,∇ f ( θ )是 f 的通常梯度。很好记,对吧?从左到右,小下标 v 刚好跳到旁边。快速检查一下:左边是一个标量,右边也是,因为它是两个向量的点积。

太好了,现在我们有一切可以理解这篇论文了!

向前的梯度

因此,给定一个函数 f ,作者定义了另一个函数 gᵥ ,他们称之为向前梯度

注意:作者在他们的论文中省略了下标 v,但我发现把它放在那里很有用。它提醒我们向前的梯度依赖于一些 v。

我创造的形象。

我们可以看到, gᵥ 取一个矢量 θ 再次输出另一个矢量,某种缩放版的 v ( 记住:ᵥf(θ是标量)。**我们还需要 θv 具有相同的尺寸。****

但这看起来比之前更复杂,它甚至引入了另一个矢量 v !如何选择 vgᵥ 更有用?答案很简单:将 v 的每个分量独立采样为标准高斯 N (0,1)。现在,你可能想知道为什么这是有用的,因为这只是创建了许多随机向量。作者使用一些简单易懂的数学方法展示了以下内容:

直接从报纸上。

这是对以下内容的巧妙描述:

如果对大量 v 进行采样,计算相应的前向梯度,然后对这些前向梯度求平均值,最终会得到一个非常接近真实梯度的值。

让我们用一些简单的代码来验证这个声明。我们定义函数 f ( θ,θ₂)=θ₁+θ₂,我们要计算它在点 θ = (2,1) 的梯度。

**import numpy as np

f = lambda theta: theta[:, 0]**2 + theta[:, 1]**2
theta = np.array([[2, 1]])**

注: 我让函数一次取几个输入,即批量输入。

到目前为止,一切顺利。在我们继续之前,让我们自己计算一下梯度。我们有∇ f ( θ,θ ₂) = (2 θ ₁,2 θ ₂),所以∇ f (2 ,1 ) = (4,2)。

我们也可以通过使用规范基计算方向导数来验证这一点,即所有向量除了一个 1 之外都是零。

**h = 0.00001
v = np.array([[1, 0], [0, 1]])

(f(theta + h*v) - f(theta)) / h # approximate derivative

# Output:
# array([4.00001, 2.00001])**

现在,让我们计算许多随机向量 v 并计算前向梯度 gᵥ:

**np.random.seed(0) # set a seed to keep it reproducible

v = np.random.randn(1_000_000, 2) # a million vectors of size 2
grad_v_f = ((f(theta + h*v) - f(theta)) / h) # ∇ᵥf(θ)
g = grad_v_f.reshape(-1, 1) * v # scale v with grad_f to compute g**

重要提示:**我 calculate∇ᵥf(θ)的方式没那么好。它只是真实方向导数的近似,甚至在数值上是不稳定的。正如作者所说,有一种方法可以在单次正向传递中使用正向模式自动微分来计算∇ ᵥf ( θ ) 和 f ( θ ) ,但遗憾的是,他们没有为这种说法提供来源。**

例如,侯赛因·阿卜杜拉赫曼指出 PyTorch 使用双数。谢谢!

我不会说得太详细,但是双数类似于复数 a + bi ,但是我们处理的不是具有属性 i = -1 的虚数 i ,而是具有属性 ɛ = 0 的数字 a + ɛ 对于这些数字,我们有f(a+)= f(a)+f’(a)函数f它很自然地扩展到多输入的函数,在这种情况下,我们接收(重命名变量后)f(θ+ɛv)= f(θ)f(θ)v【t100

g中,现在存储了很多随机向量,但是根据定理,它们的平均值应该接近∇ f (2 ,1 ) = (4,2)。让我们看看。

**g.mean(axis=0)

# Output:
# array([3.99433963, 2.00797197])**

看起来不错!但是我们对数学的期望并没有减少。😎我们甚至可以想象向前的梯度 gᵥ:

我创造的形象。

我们可以看到,围绕真实值(4,2)有相当大的差异,但正如作者进行的实验所证明的那样,该算法似乎工作得很好。但是在我们谈到它们之前,让我从纸上复制完整的算法,因为它很好,很短。

直接从报纸上。

这基本上是一个普通的梯度下降,只是用另一种方法来计算梯度。而且,正如作者提到的,这仅仅是开始:创建 Adam、RMSProp 和任何其他使用梯度的算法的前向梯度版本是很简单的。所以,请在某个时候继续关注 法达姆 算法。

科学界仍然需要解决的唯一有趣的事情是,前向梯度引入的噪声是否会破坏 Adam 等更高级的算法。对于 SGD 来说,它工作得相当好,所以让我们来看看实验。

实验

****免责声明:我现在从报纸上复制所有图片。

****

直接从报纸上。

这看起来很有希望。特别是对于更复杂的神经网络,前向梯度比反向传播用更少的时间产生相似的性能。只是要小心,因为总是有可能作者没有向我们展示他们的算法不能很好工作的情况。

结论

在这篇文章中,我和你一起回顾了最近的论文 渐变无反向传播 。作者展示了如何仅使用单个正向传递而不是通过网络反向传播来进行梯度下降。这样既节省了时间,又不会增加内存消耗。

作者在 PyTorch 中实现了他们的方法,但是,我仍然在等待他们发表它。总的来说,我认为这是一篇写得很好的论文,有一个漂亮整洁的想法,易于理解甚至实施。

我希望你今天学到了新的、有趣的、有用的东西。感谢阅读!

作为最后一点,如果你

  1. 想支持我多写点机器学习和
  2. 无论如何都要计划获得中等订阅量,

为什么不通过这个环节**https://dr-robert-kuebler.medium.com/membership?这将对我帮助很大!😊******

透明地说,给你的价格不变,但大约一半的订阅费直接归我。

非常感谢,如果你考虑支持我的话!

如有任何问题,请在 LinkedIn 上给我写信!

什么是并行计算?

原文:https://towardsdatascience.com/parallel-computing-92c4f818c

理解并行计算在数据工程环境中的重要性

马丁·桑切斯在 Unsplash 上的照片

介绍

数据工程师通常必须从几个数据源中提取数据,对它们进行清理和汇总。在许多情况下,这些流程需要应用于大量数据。

在今天的文章中,我们将探讨计算和数据工程中最基本的概念之一,称为并行编程,它使现代应用程序能够在相对较短的时间内处理大量数据。

此外,我们将讨论并行编程的好处,以及它的缺点。最后,我们将探索一些能够利用现代多核系统和计算机集群来分布和并行化工作负载的包和框架。

什么是并行编程

并行计算是几乎所有现代数据处理工具的核心。这种框架利用了现代机器提供的处理能力和内存,可以将一个主任务分割成子任务,这些子任务可以在几台计算机上并行执行。

并行执行多个任务(或子任务)——来源:作者

请注意,并行计算甚至可以利用单台机器中的多个内核。

不要混淆并发性和并行性也很重要。这是两个不同的概念,所以如果你想更好地了解它们的主要区别,你可以阅读我最近的一篇文章,讨论多线程和多处理之间的区别。

并行编程的好处

并行编程的第一个优势与多核系统有关,它能够在执行任务时充分利用所有多核系统,从而在更短的时间内完成任务。

在大数据时代,数据集可以增长到巨大的规模,因此不可能将它们加载到一台机器上。在并行计算中,这样的数据集可以利用多台计算机,通过对它们进行分区,以分布式方式加载它们。

平行减速

除了明显的好处,并行计算的概念也有一些缺点。在计算机集群中分配任务会带来开销,这与节点之间的通信方式有关。

因此,相当简单的任务的分配有可能不会加速执行,事实上,它可能会触发并行的减速,这实际上会对执行时间产生负面影响。

换句话说,对于小而简单的任务,在一台机器上执行比分布在一个集群节点上更有效(也许更容易)。

在并行化任务时,我们需要考虑的另一个因素是它们是否可以实际分布在一个节点集群中。

Python 中的并行编程

multiprocessing包附带了 Python 标准库,并提供了一个直观的 API,允许用户为本地和远程并发生成多个进程,这可以有效地避开全局解释器锁。你可以在我下面分享的文章中了解更多关于什么是 Python GIL 以及它的作用。

更具体地说,[Pool](https://docs.python.org/3/library/multiprocessing.html#multiprocessing.pool.Pool)对象可用于跨多个输入值并行执行所提供的函数,其方式是将输入数据分布在多个进程中,这些进程可能产生于不同的 CPU 内核上,甚至是一台机器。

例如,假设我们有一个熊猫数据帧,其中包含学生的个人信息,包括他们的年龄和年级。

import pandas as pd df = pd.DataFrame(
    [
        (1, 2021, 15),
        (2, 2020, 14),
        (3, 2021, 17),
        (4, 2019, 13),
        (5, 2019, 14),
        (6, 2020, 15),
        (7, 2020, 14),
        (8, 2021, 14),
        (9, 2021, 13),
        (10, 2020, 14),
        (11, 2019, 12),
        (12, 2018, 10),
        (13, 2019, 15),
        (14, 2019, 16),
    ],
    columns=['student_id', 'class_year', 'age']
)print(df)
 ***student_id  class_year  age*** *0            1        2021   15
1            2        2020   14
2            3        2021   17
3            4        2019   13
4            5        2019   14
5            6        2020   15
6            7        2020   14
7            8        2021   14
8            9        2021   13
9           10        2020   14
10          11        2019   12
11          12        2018   10
12          13        2019   15
13          14        2019   16*

现在,假设我们想要计算每个学年学生的平均年龄。在下面的例子中,我们利用了multiprocessing库,以便在本地机器上的 4 个不同内核上并行化这个计算。

from multiprocessing import Pooldef compute_mean_age(groupby_year):
    year, group = groupby_year
    return pd.DataFrame({'age': group['age'].mean()}, index=[year])with Pool(4) as p:
    mean_age = p.map(compute_mean_age, df.groupby('class_year'))
    df_mean_age_per_year = pd.concat(mean_age)

结果会是:

print(df_mean_age_per_year) 
 ***age*** *2018  10.00
2019  14.00
2020  14.25
2021  14.75*

请注意,在本例中,我们使用了一个非常小的数据集,否则会导致我们前面讨论过的并行减速效应。只有当我们的数据集非常大时,上面的方法才是有益的。对于小型数据集,下面的方法可以解决问题:

>>> df.groupby('class_year')['age'].mean()class_year
2018        10.00
2019        14.00
2020        14.25
2021        14.75
Name: age, dtype: float64

现代并行计算框架

随着时间的推移,出现了许多技术,这些技术能够通过利用多核系统和计算机集群来分配工作负载并并行执行多个任务。

一个这样的工具是 Dask ,它提供高级并行功能,使其他工具如numpypandasscikit-learn能够实现规模化性能。

在前面的例子中,我们可以使用 Dask 来计算每个班级一年的平均年龄,而不是“低级”multiprocessing包。使用 Dask,我们可以用更干净、更直观的代码达到同样的效果,如下所示。

import dask.dataframe as dd dd_students = dd.from_pandas(df, npartitions=4)df_mean_age_per_year = \    
    dd_sudents.groupby('class_year').age.mean().compute()

如果你做过一些关于大数据的研究,你很有可能会遇到由 Apache Software Foundation 维护的 Hadoop 。Hadoop 是开源项目的集合,包括 MapReduceHadoop 分布式文件系统 (HDFS)。简而言之,MapReduce 是用于访问存储在 HDFS 上的大数据的首批分布式编程范例之一。Hadoop 生态系统的另一个流行工具是 Hive,这是一个运行在 Hadoop 之上的层,可以使用结构化查询语言 Hive SQL 访问数据。例如,您可以使用 Hive 编写一个查询,该查询将被翻译成一个作业,该作业可以在一个计算机集群上以分布式方式执行。

最后,数据工程堆栈中的另一个流行工具是 Apache Spark —用于大规模执行大数据和机器学习的引擎。该框架支持多种语言,包括 Scala 和 Python,可用于大规模执行 SQL 分析、数据科学和机器学习,以及批处理和流数据处理。

你可以在我最近在 Medium 上发表的一篇名为《数据工程师工具》的文章中找到更多与大数据工程相关的工具,在这篇文章中,我将更详细地介绍我今天提到的工具的用途,包括 Apache AirflowApache Kafka

最后的想法

在今天的文章中,我们讨论了数据工程中最重要的支柱之一,即并行计算。我们讨论了它的用途以及它带来的好处,尤其是在大数据时代。此外,我们还探索了如何使用 Python 内置包multiprocessing在 pandas 数据帧上执行并行计算,以及如何使用dask实现相同的行为。

最后,我们讨论了最常用的并行计算框架,每个数据工程师至少应该了解这些框架,以便能够在整个组织中设计和实施高效且可扩展的数据工作流。这样的框架包括 Hadoop 生态系统(包含众多工具,包括 MapReduce、HDFS 和 Hive)和 Apache Spark。

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

https://gmyrianthous.medium.com/membership

相关文章你可能也喜欢

https://betterprogramming.pub/kafka-cli-commands-1a135a4ae1bd

拥抱脸的平行推理🤗CPU 上的变压器

原文:https://towardsdatascience.com/parallel-inference-of-huggingface-transformers-on-cpus-4487c28abe23

大型机器学习和深度学习模型的多重处理预测介绍

图片由斯莱文·久拉科维奇Unsplash 上拍摄

人工智能研究的当前趋势是朝着越来越大的深度学习模型的发展,这些模型在性能方面不断超越彼此。最近自然语言处理(NLP)的例子有 GPT-3XLNet 或经典的伯特变压器模型。虽然不断改进的结果激励着研究人员热情地研究更大的模型,但这种发展也有一个明显的缺点。训练这些大型模型是非常昂贵和耗时的。其中一个原因是深度学习模型需要同时在大量 GPU 上进行训练。由此产生的模型是如此之大,以至于它们不仅需要 GPU 进行训练,还需要在推理时使用 GPU。理论上,对 CPU 的推断是可能的。然而,这些大型模型需要很长时间才能在 CPU 上生成预测,这使得这种方法在实践中不太可行。不幸的是,近年来区块链的大肆宣传导致 GPU 短缺,这大大限制了许多人对 GPU 的访问。

如果我们想从预先训练的深度学习模型的出色性能中受益,而又没有 GPU 访问,我们该怎么办?我们一定要转向其他方法吗?答案是否定的。至少对于较小的项目,我们可能会在 CPU 上并行化模型推理,以提高预测速度。但是,从长远来看,对于更大的项目,仍然强烈建议使用 GPU。

深度学习模型的推理如何并行化?

在本教程中,我们将使用光线对预训练的 HuggingFace 进行并行推理🤗Python 中的变压器模型。Ray 是一个框架,不仅可以在一台机器上,也可以在多台机器上扩展计算。对于本教程,我们将在一台配备 2,4 Ghz 8 核英特尔酷睿 i9 处理器的 MacBook Pro (2019) 上使用 Ray。

为什么要用 Ray 进行并行推理?

在 Python 中处理并行计算时,我们通常利用多重处理模块。但是,该模块有一些限制,使其不适合大型模型的并行推理。因此,我们需要在这方面主要考虑两个因素:

  1. Python 多处理模块使用 pickle 在进程间传递大型对象时序列化它们。这种方法要求每个进程创建自己的数据副本,这增加了大量的内存使用以及昂贵的反序列化开销。相比之下,Ray 使用共享内存来存储对象,所有工作进程都可以访问这些对象,而不必反序列化或复制这些值( Robert Nishihara )。考虑到模型通常试图预测大量数据,当使用 Ray 而不是多处理模块时,仅这个因素就已经可以加快计算速度。
  2. Python 多处理模块执行无状态函数,这意味着在一个 map 调用中产生的任何变量,如果我们想在另一个 map 调用中使用,都需要从第一个调用中返回,并传递给第二个调用。对于小对象,这种方法是可以接受的,但是当需要共享大的中间结果时,传递它们的成本是非常高的。在我们的例子中,这意味着我们需要重新加载我们的大拥抱面🤗Transformer 模型,因为映射的函数被认为是无状态的。最终,加载这些模型花费了我们太多的时间,以至于我们可能会因为只加载一次模型而变得更快,然后让它在单个 CPU 上预测整个数据,而不是并行化推理。相比之下,Ray 提供了一个 actor 抽象,这样类就可以在并行和分布式环境中使用。使用 Ray,我们可以克服这个问题,因为我们可以通过在 actor 的构造函数中只加载一次模型,然后在多个 CPU 上使用它进行并行推理,从而避免模型重新加载的开销。

如果你想更多地了解雷,我推荐你去看看罗伯特·西原的博客。

辅导的

在本教程中,我们将使用 Ray 对来自 CPU 上 20 个新闻组数据集的文本文档类进行并行预测。数据集是大约 20,000 个文本文档的集合,平均分布在 20 个不同的新闻组类别中。在本教程中,我们将只关注由类别“rec.motorcycles”和“rec.sport.baseball”组成的 20 个新闻组数据集的子集。我们将使用一个蒸馏蕴涵模型来执行零镜头文本分类。简单地说,零炮分类的工作原理如下:

我们为零镜头蕴涵模型提供文本形式的候选标签,然后该模型可以使用该文本来预测给定文本是否与给定候选标签相关联。零镜头蕴涵模型不必为特定的标签进行显式训练,而是可以基于看不见的标签和文本的语义来执行分类。

零镜头文本分类示例:

Text to classify: 
*The Avengers, is a 2012 American superhero film based on the Marvel Comics superhero team of the same name.*Candidate labels:
*Movies, Automotive*When providing the zero-shot entailment model with both, the text and candidate labels, the model will then predict a relationship score between the text and labels:
*Movies: 0.9856
Automotive: 0.0144*

有兴趣的可以在这里找到更详细的零拍文本分类介绍。但是现在让我们来看看实际的代码示例。

安装所需的 Python 包:

pip install ray
pip install torch
pip install transformers
pip install scikit-learn
pip install psutil

加载拥抱面🤗变压器型号和数据

首先,我们导入所有重要的包,从 Scikit-learn 获取 20 个新闻组数据集,并从 HuggingFace 初始化我们的零镜头蕴涵模型🤗。

Number of text articles: 777

预测单个文本文档并检查输出

让我们在单个文档上测试零射击模型预测。

Text: Hey, the Lone Biker of the Apocalypse (see Raising Arizona) had flames coming
out of both his exhaust pipes. I love to toggle the kill switch on my Sportster
to produce flaming backfires, especially underneath overpasses at night (it's
loud and lights up the whole underpass!!!
Labels: ['motorcycle', 'baseball']
Scores: [0.9970590472221375, 0.002940954640507698]

我们可以观察到,我们的零镜头蕴涵模型可以正确地预测类,而无需事先在数据集上进行训练。

预测单个 CPU 上的所有文本文件

让我们预测单个 CPU 上所有文本文档的类别,并测量计算时间。

Prediction time: 0:04:12.172323

预测多个 CPU 上的所有文本文档

我们来预测一下所有文本文档在多个 CPU 上的类,测量一下计算时间。

Number of available CPUs: 16
Prediction time: 0:01:58.203863

与仅在一个 CPU 上进行计算相比,我们通过利用多个 CPU 显著减少了预测时间。为了用 Ray 并行化预测,我们只需要把 HuggingFace🤗局部对象存储中的管道(包括 transformer 模型),定义一个预测函数predict(),用@ray.remote修饰。之后,我们必须在远程设置中执行该功能,并用ray.get()收集结果。

摘要

Ray 是一个易于使用的计算框架。我们可以用它在预先训练好的 HuggingFace 上进行并行 CPU 推理🤗Transformer 模型和 Python 中的其他大型机器学习/深度学习模型。如果你想了解更多关于射线和它的可能性,请查看射线文件

来源:

https://www.ray.io https://medium.com/@robertnishihara

使用 Python 和 AWS EC2 进行并行处理

原文:https://towardsdatascience.com/parallel-processing-with-python-and-aws-ec2-3b01fdc8272

更快地运行您的脚本

UnsplashMax Duzij 拍摄的照片

介绍

在进行数据科学项目时,我经常遇到的一个问题是需要加快脚本的速度。这可能是在探索性数据分析期间,或者可能是在自然语言处理时获得文档相似性。无论如何,在这种情况下,我可怜的计算机要么在我运行脚本几个小时时变慢,要么在我需要再次使用计算机之前花费太长时间。然后等了这么久,我甚至可能需要回去编辑,重新运行剧本!

有几种方法可以解决这个问题,但在本文中,我将首先直接用 Python 介绍我的方法,然后引入 AWS EC2。

多重处理

你电脑的 CPU(很可能)有多个内核,每个内核可以执行不同的任务。这意味着当你运行一个 Python 脚本时,你可以继续检查空闲时间和回复邮件,但是我们经常希望尽快得到我们的脚本结果。为了实现这一点,我们可以使用 Python 的多处理库来并行使用我们的所有(或 1 个以上)CPU 内核。

下面的要点给出了一个简单的方法来实现函数process的多重处理,该函数为i_list中的每个参数i运行。

AWS EC2

然而,我们可能并不总是拥有生成结果的本地计算能力,甚至不想让脚本在本地运行。Amazon EC2 也被称为 Amazon Elastic Compute Cloud,它支持创建具有不同计算能力级别的虚拟机,这对于将我们的本地脚本运行扩展到云是完美的。

  1. 创建一个虚拟机,并按照标准步骤连接
1\. Open an SSH client.
2\. Locate your private key file. The key used to launch this instance is temp-cluster.pem
3\. Run this command, if necessary, to ensure your key is not publicly viewable.
	chmod 400 temp-cluster.pem
4\. Connect to your instance using its Public DNS:
	ec2-1234.eu-west-1.compute.amazonaws.com
5\. Example: 
	ssh -i "temp-cluster.pem" ubuntu@ec2-1234.eu-west-1.compute.amazonaws.com

2.用 Python 设置 VM,并安装所需的 Python 包。在我的上一个项目中,我做了一些自然语言处理,需要去除停用词。

sudo apt update
sudo apt install python3-pippip install nltk
python3 -m nltk.downloader stopwords
pip install numpy pandas sklearn scipy

3.要将文件(通常是我们的脚本)移动到 EC2 实例,我们可以使用 secure copy 命令。默认路径是/home/ubuntu。

scp -i .ssh/temp-cluster.pem Documents/nlp_script.py ubuntu@ec2-1234.eu-west-1.compute.amazonaws.com:/home/ubuntu

4.现在,如果需要,我们可以创建一个文件夹来保存脚本输出,然后运行 Python3 脚本。

sudo mkdir script_output_folder
python3 nlp_script.py

# List directory folders
ls -d */
# List folder contents
ls -R

5.为了在会话断开或计算机关闭的情况下保持脚本运行,我们可以使用 tmux。

# Install tmux
sudo apt-get install tmux# Start a terminal session
tmux new -s mywindow# Restart local computer, ssh to reconnect to the EC2 instance, and 
resume the terminal window
tmux a -t mywindow

6.最后,从 EC2 实例下载文件。

# To copy the script back to the local "Documents" folder
scp -i .ssh/temp-cluster.pem ubuntu@ec2-1234.eu-west-1.compute.amazonaws.com:/home/ubuntu/nlp_script.py Documents

# Zip a folder for easy download
zip -r squash.zip /home/ubuntu/script_output_folder/

# Use wildcards to download many files
scp -i .ssh/temp-cluster.pem ubuntu@ec2-1234.eu-west-1.compute.amazonaws.com:/home/ubuntu/script_output_folder/* Documents

一旦我们所有的处理完成并下载回我们的本地驱动器,EC2 实例就可以删除了。我们现在可以正常访问新文件了。

最后的想法

您可能是在试图弄清楚 Python 中的并行处理或者如何使用 Amazon EC2 实例来实现它的时候偶然看到这篇文章的,在这种情况下,我希望这对您有所帮助!

C++中的 CUDA 并行编程(第一部分)

原文:https://towardsdatascience.com/parallel-programming-with-cuda-in-c-part-1-d935e93a9e87

照片由 aaron borisUnsplash 拍摄

了解如何利用现代 GPU 的强大功能加速计算密集型应用

最常见的深度学习框架,如 TensorflowPyThorch 通常依赖于内核调用,以便使用 GPU 进行并行计算,并加速神经网络的计算。允许开发人员使用 GPU 编程的最著名的接口是 CUDA ,由 NVIDIA 创建。

并行计算需要与普通编程完全不同的观点,但是在您开始动手之前,有一些术语和概念需要学习。

背景

在下图中,您可以看到一个经典的设置,其中 CPU 一次处理一条指令来生成一个输出。但是我们如何同时处理多个指令呢?这就是我们将在本文中试图理解的。

作者图片

术语

  • 进程:是正在执行的计算机程序的实例。一个进程在 CPU 上运行,并在 RAM 中创建一个分配。

过程(图片由作者提供)

  • 上下文:一个进程的数据集合(内存地址,程序状态)。它允许处理器挂起或暂停一个进程的执行,并在以后重新启动它。
  • 线程:是流程的一个组件。每个进程至少有一个被称为主线程的线程,它是程序的入口点。一个线程可以执行一些指令。在一个进程中,多个线程可以共存并共享分配给该特定进程的内存。进程之间没有内存共享。
  • 循环调度:如果我们有一个单核处理器,进程将按照循环调度执行,执行优先级最高的第一个进程。当你从一个过程的执行切换到另一个过程时叫做上下文切换。

平行

在现代计算中,不需要上下文切换,我们可以在不同的内核上运行不同的线程(现在可以把一个内核想象成一个小处理器),这就是我们拥有多核设备的原因!

多核(图片由作者提供)

但是请记住,几乎在每个进程中都有一些应该顺序执行的指令和一些可以同时并行计算的指令。

当您谈论并行时,您应该记住有两种类型的并行:

  • 任务级别:相同或不同数据上的不同任务
  • 数据级别:同一任务不同数据

此时,注意不要将并行性与并发性混淆。

  • 并发:我们有一个处理器,它顺序执行进程,我们只是有并行的错觉,因为处理器真的很快。
  • 并行:多个处理器的真正并行。

CPU、GPU 和 GPGPU

图形处理单元(GPU)可以在短时间内执行复杂的操作。复杂性取决于同时执行的操作数量
,但前提是它们保持简单且基本相似。游戏行业一直是 GPU 实现的启动市场,后来 Nvidia 公司通过平台 CUDA 实现了这一市场。GPU 的恶名甚至增加了更多,特别是对于开发人员来说,他们现在能够使用几行代码运行多个计算操作。

CUDA 允许我们在图形处理单元 ( GPGPU )上使用并行计算进行所谓的通用计算,即使用 GPU 进行除 3D 图形之外的更多通用计算

我们来总结一下 CPU 和 GPU 的一些基本区别。

图形处理器:

  • 低时钟速度
  • 成千上万的核心
  • 上下文切换由硬件完成(非常快)
  • 如果一个线程停止,可以在线程之间切换

CPU:

  • 高时钟速度
  • 几个核心
  • 上下文切换由软件完成(慢)
  • 在线程之间切换并不容易

Cuda 编程的基本步骤

在接下来的文章中,我们将编写使用并行编程的代码。然而,我们必须首先知道基于 cuda 的代码的结构是什么,有几个简单的步骤可以遵循。

  • CPU 上数据的初始化
  • 将数据从 CPU 传输到 GPU
  • 内核启动(GPU 上的指令)
  • 将结果从 GPU 传输回 CPU
  • 从 CPU 和 GPU 回收内存

在这样的环境中,我们将把主机代码称为将要在 CPU 上运行的代码,把设备代码称为将要在 og GPU 上运行的代码。

最后的想法

还有更多的东西要看,但我不喜欢把它们都写在一篇文章里,因为我认为这比其他任何东西都更令人困惑。

并行编程是当今一切的基础,它是我们必须加快非常长的计算时间的方式,只是更多的处理器或更多的核心一起工作,团结就是力量!

如果您有兴趣了解 GPU 如何工作,并且喜欢与硬件密切相关的编程,请继续阅读我将发表的这一系列文章。就我个人而言,我发现对 CUDA 的研究非常有趣,每一行代码都像是一个有待解决的难题。

结束了

马赛洛·波利蒂

LinkedinTwitterCV

Python 中的并行化:简单的方法

原文:https://towardsdatascience.com/parallelization-in-python-the-easy-way-aa03ed04c209

并行化不一定很难

Python 中的并行化并不一定很难。阿巴斯·特拉尼在 Unsplash 上拍摄的照片

许多初学者和中级 Python 开发人员害怕并行化。对他们来说,并行代码意味着困难的代码。进程、线程、greenlets、协程……并行化代码的工作不是以高性能代码而告终,而是经常以令人头疼和沮丧而告终。

在这篇文章中,我想告诉大家事实并非如此。在简单的场景中,代码很容易并行化。众所周知,Python 是一种易于理解的编程语言,并行代码也易于阅读和实现。

本文是而不是并行化的介绍。不全面。相反,我想向您展示在简单的情况下并行化代码是多么简单。这将为您提供将并行化应用于更复杂场景的必要背景。或者至少,从简单的开始总比从难的开始好。

简单的例子不一定意味着学术上的例子。作为 Python 开发人员或数据科学家,您会经常遇到需要加快应用程序速度的情况。您应该做的第一件事是分析代码。忽略这一点可能是试图优化它时最常见的错误。概要分析可以向您展示应用程序中的瓶颈,在这些地方您可以获得一些收益。如果您不分析您的代码,您可能会花一天的时间来优化应用程序中实际上最有效的部分。

例如,在进行分析后,您会发现应用程序 99%的时间都在运行执行某些计算的 iterable 上的一个函数。那么,你可能会想,如果你并行运行这个函数,应用程序应该会更快——你可能是对的。这种情况就是我所说的简单情况。

本文旨在展示如何在这种情况下简单地并行化 Python 代码。不过,你可能还会学到一件事:如何设计和原型化代码。我不会写一个实际的应用程序,因为这样的代码会让我们分心。相反,我将编写这样一个应用程序的简单原型。原型可以工作,但不会做任何有价值的事情;它只会做一些类似于这种应用程序在现实生活中所能做的事情。

示例应用程序

假设我们的目标是编写一个处理文本的应用程序。这种处理可以是你想对文本做的任何事情。例如,你可以在书中搜索指纹,正如本·布拉特在他关于纳博科夫的精彩著作中所做的那样(布拉特 2017)。

在我们的应用程序中,对于每个文本,将运行以下管道:

  • 读取文本文件
  • 预处理文件;这意味着清洁和检查
  • 处理文件
  • 返回结果

一种优雅的方法是创建一个生成器管道。我在我的另一篇文章中展示了如何做。我们将使用这种方法。

下面,您将找到创建上述管道的一个函数,以及管道使用的三个函数。管道函数将负责运行所有这些函数,然后返回结果。

我将使用这些函数的简单模拟,因为我不希望您关注与并行化无关的代码。我还将添加一些睡眠时间,以便函数花费的时间与它们在现实中可能花费的时间成比例。请注意,这些休眠旨在表示计算量大(而不是等待 HTTP 响应)的任务,对于这些任务,加速计算的唯一方法是使用并行化。

import time
import pathlib
from typing import Dict, Generator, Iterable

# type aliases
ResultsType = Dict[str, int]

def read_file(path: pathlib.Path) -> str:
    # a file is read to a string and returned
    # here, it's mocked by the file's name as a string
    # this step should be rather quick 
    time.sleep(.05)
    text = path.name
    return text

def preprocess(text: str) -> str:
    # preprocessing is done here
    # we make upper case of text
    # longer than reading but quite shorter than processing
    time.sleep(.25)
    text = text.upper()
    return text

def process(text: str) -> ResultsType:
    # the main process is run here
    # we return the number of "A" and "O" letters in text
    # this is the longest process among all
    time.sleep(1.)
    search = ("A", "B", )
    results = {letter: text.count(letter) for letter in search}
    return results

def pipeline(path: pathlib.Path) -> ResultsType:
    text = read_file(path)
    preprocessed = preprocess(text)
    processed = process(preprocessed)
    return processed

现在,我们需要一个可迭代的路径。真正的read_file()函数会读取文件,但是我们的模拟函数不会;相反,它根据路径名生成文本。我这样做是为了让事情尽可能简单。所以,这将是我们的迭代:

file_paths = (
    pathlib.Path(p)
    for p in (
        "book_about_python.txt",
        "book_about_java.txt",
        "book_about_c.txt",
        "science_fiction_book.txt",
        "lolita.txt",
        "go_there_and_return.txt",
        "statistics_for_dummies.txt",
        "data_science_part_1.txt",
        "data_science_part_2.txt",
        "data_science_part_3.txt",
    )
)

我们已经拥有了我们所需要的一切:一个产生路径的file_paths生成器,就像pathlib.Path一样,指向文件。我们现在可以使用这个生成器运行整个管道,这样我们将使用一个生成器管道,就像我承诺的那样。为此,我们可以迭代file_path生成器,在每次迭代中,每条路径都将通过pipeline()函数进行处理。这样,我们将像承诺的那样使用一个生成器管道。

让我们在 name-main 块中执行生成器的评估:

if __name__ == "__main__":
    start = time.perf_counter()
    results = {path: pipeline(path) for path in file_paths}
    print(f"Elapsed time: {round(time.perf_counter() - start, 2)}")
    print(results)

这个管用。我增加了测量时间。因为我们有 10 个文件要处理,而一个文件的处理时间大约为 1.3 秒,所以我们应该预计整个管道的运行时间大约为 13 秒,外加一些开销。

事实上,在我的机器上,整个流水线需要 13.02 秒来处理。

Python 并行化:操作指南

上面,我们使用字典理解评估了管道。我们可以使用生成器表达式或map()函数:

if __name__ == "__main__":
    start = time.perf_counter()
    pipeline_gen = map(lambda p: (p, pipeline(p)), file_paths)
    # or we can use a generator expression: 
    # pipeline_gen = ((path, pipeline(path)) for path in file_paths)
    results = dict(pipeline_gen)
    print(f"Elapsed time: {round(time.perf_counter() - start, 2)}")
    print(results)

map()的调用看起来比上面的 dict 理解更难。这是因为我们不仅想返回pipeline()的结果,还想返回一个正在处理的路径。当我们需要将结果与输入联系起来时,这是一种常见的情况。在这里,我们通过返回一个包含两个条目的元组来实现这个目的,(path, pipeline(path)),基于这些元组,我们可以创建一个包含path-pipeline(path)键-值对的字典。将lambda s 和map()一起使用是一种常见的方法——但不一定是最清晰的方法。

注意,如果我们不需要路径名,代码会简单得多,因为我们唯一需要的是pipeline(path)的结果:

pipeline_gen = map(pipeline, file_paths)

然而,由于我们需要添加路径名,所以我们使用了lambda函数。产生的代码,带有lambda的那个,看起来不太吸引人,不是吗?幸运的是,有一个很好的简单技巧来简化对使用lambda s 的map()的调用。我们可以使用包装函数来代替:

def evaluate_pipeline(path):
    return path, pipeline(path)

if __name__ == "__main__":
    start = time.perf_counter()
    pipeline_gen = map(evaluate_pipeline, file_paths)
    results = dict(pipeline_gen)
    print(f"Elapsed time: {round(time.perf_counter() - start, 2)}")
    print(results)

上面的map对象产生一个二元元组的生成器(path, pipeline(path))。我们可以使用dict(pipeline_gen)

  • 评估发电机,以及
  • 将结果转换成由path - pipeline(path)键值对组成的字典。

难道你不同意这段代码比带有lambda的版本可读性更强,更容易理解吗?

对了,如果你想知道生成器管道在哪里,就在这里:map(evaluate_pipeline, file_paths)。我们使用dict()函数对其进行评估。

为什么我们谈论 map()这么多?!

你可能想知道为什么我如此关注map()函数。这是怎么回事?我们不应该讨论并行化吗?

我们应该这样做——我们确实这样做了。为了并行化你的代码,你需要首先理解map()是如何工作的。这是因为你经常会使用与map()非常相似的函数;事实上,这个功能经常被称为map。因此,每当您考虑并行化您的代码时,不要使用for循环、生成器表达式、列表理解、字典理解、集合理解等——考虑使用map()函数。

正如我在关于map()(科萨克 2022)的数据科学文章中所写的,这个函数被许多并行化模块和技术使用,它们的map()版本与内置的map()函数非常相似。因此,如果您计划并行化代码,从一开始就使用map()通常是好的。不是必须的;只是一个建议。这样,当并行化代码时,您可以节省一些时间。

所以,也许从一个map()版本开始更好,甚至不用考虑是否将代码并行化?另一方面,map()有时比相应的生成器表达式可读性差;但是你可以使用一个包装函数,基于map()的代码应该变得非常可读。

我不会告诉你哪个设计是最好的。有时,map()将是完美的解决方案——尤其是当您计划并行化代码时。重要的一点是,永远要小心。代码可读性很重要,但并不总是简单明了的。没有单一的配方。用你的技能和经验来决定。

但是,如果您决定用最流行的并行化包之一来并行化代码,您通常会使用类似于map()的函数。所以,不要忽略内置的map()功能。明天,它可能会成为你比你今天想象的更亲密的朋友。

行动的并行化

现在我们已经使用了map(),我们的代码已经准备好被并行化了。为此,让我们使用[multiprocessing](https://docs.python.org/3/library/multiprocessing.html),用于并行化的标准库模块:

import multiprocessing

在大多数情况下,这可能是一个好主意。然而,我们需要记住,其他模块可以提供更好的性能。我希望在其他文章中写他们。

我们的名称-主块现在变成了:

if __name__ == "__main__":
    start = time.perf_counter()
    with mp.Pool(4) as p:
        pipeline_gen = dict(p.map(evaluate_pipeline, file_paths))
    print(f"Elapsed time: {round(time.perf_counter() - start, 2)}")
    print(results)

四核,仅 4.0 秒!(使用 8 名工人,耗时 2.65 秒。)好看;我们看到multiprocessing像预期的那样工作。

请注意,我们不需要对代码做太多修改,只需要做两处修改:

  • with mp.Pool(4) as p::为mp.Pool使用上下文管理器是一个很好的规则,因为您不需要记住关闭池。我选择了 4 个工人,这意味着该流程将在四个流程中运行。如果您不想对工作人员的数量进行硬编码,可以用不同的方式来实现,如下所示。我使用了mp.cpu_count()-1,这样我们的应用程序就不会触及一个进程;mp.cpu_count()返回要使用的内核数量。我有 4 个物理核和 8 个逻辑核,mp.cpu_count()返回 8。
workers = mp.cpu_count() - 1
with mp.Pool(workers) as p:
  • p.map(evaluate_pipeline, file_paths):我们所做的唯一改变是将map()改为p.map()。还记得我告诉过你使用map()会使代码并行化更容易吗?给你,这是这点零钱。然而,不要忘记p.map()并不像map()那样懒洋洋地求值,而是贪婪地(立即)求值。因此,它不返回生成器;相反,它返回一个列表。

一个更简单的例子

import multiprocessing as mp
import random
import time

def some_calculations(x: float) -> float:
    time.sleep(.5)
    # some calculations are done
    return x**2

if __name__ == "__main__":
    x_list = [random.random() for _ in range(20)]
    with mp.Pool(4) as p:
        x_recalculated = p.map(some_calculations, x_list)

就是这样—这个脚本使用并行计算为x_list运行some_calcualations()。这是一段非常简单的 Python 代码,不是吗?在这样一个简单的情况下,当您想要使用并行计算时,您可以随时返回到它。

一件好事是,如果您想运行更多的并行计算,您可以为mp.Pool使用另一个这样的上下文管理器。

结论

我承诺向您展示 Python 中的并行化可以很简单。第一个例子是一个应用程序的原型,它有点复杂,就像现实生活中的应用程序一样。但是请注意,代码的并行部分非常短,相当简单。第二个例子强化了这一点。完整的代码只有十几行——而且是简单的代码。

即使在简单的情况下,也不要害怕使用并行化。当然,只在需要的时候使用。当你不需要让你的代码更快时,使用它是没有意义的。请记住,当要并行调用的函数非常快时,您可能会使您的应用程序变慢,因为并行化不是没有代价的;这一次,成本是创建并行后端的开销。

我展示了 Python 中的并行化可以很简单,但是您可以用它做更多的事情。我将在下一篇文章中讨论这个问题。如果你不想将你的知识局限于这些基础知识,Micha Gorelick 和 Ian Ozsvald 的书(2020)是一个非常好的来源——虽然不是一个简单的来源,因为作者讨论了相当高级的主题。

并不总是最好的方法。有时使用其他工具会更好。有[pathos](https://pathos.readthedocs.io/en/latest/pathos.html),有[ray](https://docs.ray.io/en/latest/index.html),还有其他工具。有时,您的环境会促使您选择并行化后端。例如,在 AWS SageMaker 中,您将需要使用ray。这个计划比multiprocessing提供的要多得多,我也会试着写一篇关于这个的文章。

今天到此为止。感谢阅读。如果您对 Python 中的并行化感到害怕,我希望您不会再有这种感觉了。亲自尝试一下,注意基本并行化是多么容易。当然,还有更多,但是让我们一步一步地学习。今天,你知道了没有理由害怕它,最基本的是…基本而简单。

我没有涉及技术细节,比如并行化代码的其他方式而不是池、命名和其他类似的事情。我只是想告诉你如何去做,以及有多简单。我希望我成功了。

感谢您阅读本文,请在评论中分享您对并行化的想法。我很乐意阅读您的首次尝试故事:您对并行化的首次尝试是什么?你并行了什么?你有什么问题吗?它像你期望的那样工作吗?你对它的工作方式满意吗?

资源

  • 科萨克米(2022)。用 Python 构建生成器管道。
  • 布拉特 B. (2017)。纳博科夫最喜欢的词是紫红色:数字揭示的经典、畅销书和我们自己的作品。西蒙和舒斯特。
  • 科萨克米(2022)。Python 还需要 map()函数吗?
  • [multiprocessing](https://docs.python.org/3/library/multiprocessing.html) 模块文档
  • Gorelick M .,Ozsvald I. (2020 年)。高性能 Python:人类实用的高性能编程。第二版。奥莱利媒体。
  • [pathos](https://pathos.readthedocs.io/en/latest/pathos.html) 模块文档
  • [ray](https://docs.ray.io/en/latest/index.html) 模块文档

Python 中的多处理并行化

原文:https://towardsdatascience.com/parallelization-w-multiprocessing-in-python-bd2fc234f516

并行运行您的数据科学任务以加快计算速度

图片由迪帕克·劳特拉Unsplash 拍摄

本文将直观地介绍多处理是如何工作的,以及使用它来并行运行作业的相关步骤。我将使用 pandas 和 numpy 提供示例函数来并行运行计算量大的任务,以降低时间复杂度。以下是这篇文章的提纲:

目录

  • 什么是多重处理?
  • 并行化背后的直觉
  • 数据
    -需求
    -生成数据
  • 正在检查可用处理器的数量
  • 并行化单参数函数
    -常规实现
    -并行实现
  • 并行化多参数函数
    -常规实现
    -并行实现
  • 结束语
  • 资源

什么是多重处理?

多重处理是指一个程序在两台或多台计算机上同时执行[1]。多重处理是 Python 2.6 以上版本中安装的一个模块。这是一个支持用户在一台机器上创建和销毁多个处理器的包。这使得程序员能够编写利用机器上所有可用处理器的代码[2]。它适用于 Unix 和 Windows [2]。

[multiprocessing](https://docs.python.org/3/library/multiprocessing.html#module-multiprocessing)包中的[Pool](https://docs.python.org/3/library/multiprocessing.html#multiprocessing.pool.Pool)函数允许程序员跨多个输入值并行执行一个函数,将输入数据分布到多个进程中(数据并行)。
【2】https://docs.python.org/3/library/multiprocessing.html

并行化背后的直觉

在数学中,如果两条或多条直线共面且不相交于任何一点,则它们是平行的。类似地,编程中的并行化指的是让一个功能同时在两个或多个进程上运行。这些进程在运行时从不相互影响。下图概述了一个函数的正常执行和一个函数在多个 CPU(处理器)上的并行执行。

正常运行一项功能与并行运行一项功能的过程。图片由作者提供。

数据

下面是我们将要合成的数据集,用于并行执行代码。

要求

运行下面的脚本需要以下依赖项和版本。

pandas>=1.2.4
numpy>=1.20.1

生成数据

上面脚本的结果应该会产生类似下面的输出。这些值会有所不同,因为这些值是随机生成的,但是数据的整体结构将保持不变。

合成数据集。图片由作者提供。

正在检查可用处理器的数量

以下命令允许您确定正在使用的计算机中可用的处理器数量:

from multiprocessing import cpu_count
cpu_count()

本教程中可用的 CPU 数量。图片由作者提供。

请注意,即使有多个处理器可用,计算机上的大多数(如果不是全部)应用程序也只使用一个处理器。如果你要并行执行一个需要大量计算能力的脚本,你的计算机基本上会瘫痪。您可能正在使用的大多数其他应用程序几乎肯定会大幅变慢。建议使用一台远程计算机(你可以通过 AWS / GCP / Azure /任何其他云平台租用),它有足够数量的 CPU 可供使用。

并行化单参数函数

下面的函数将计算每个读者阅读的平均图书价格。

常规实施

并行实现

请注意,仅仅因为您可以并行化一个函数,并不意味着您总是应该这样做。在 Python pandas 中执行的上述相同函数的下面 1 行实现比普通和并行实现快得多。当你发现最有效的时间复杂度实现并不足够时,并行化你的工作。

gb_df = d.groupby('reader_id')['book_price'].mean().reset_index()

并行化多参数函数

下面的函数将生成与每个用户已经阅读的类型相关联的标准化的 1 热编码。

常规实施

并行实现

结束语

本文概述了对多重处理和并行执行程序的直觉和理解。它通过一个教程指导用户当函数有单个和多个参数时如何并行执行它们的函数。请注意,仅仅因为您可以并行运行一个函数并不意味着它总是最佳解决方案。以最有效的时间复杂度来确定脚本的执行,如果脚本仍然花费过多的时间,那么并行运行它。

要了解代码,你可以参考我的 GitHub 上与本教程相关的 Jupyter 笔记本。

如果你想转型进入数据行业,并希望得到经验丰富的导师的指导和指引,那么你可能想看看最敏锐的头脑。Sharpest Minds 是一个导师平台,导师(他们是经验丰富的实践数据科学家、机器学习工程师、研究科学家、首席技术官等。)将有助于你的发展和学习在数据领域找到一份工作。点击查看

资源

如果你喜欢读这篇文章,你可能会发现我写的关于数据科学和机器学习的其他文章也很有趣。下面来看看吧。

[## 贝叶斯 A/B 测试解释

towardsdatascience.com](/bayesian-a-b-testing-explained-344a6df88c1a)

使用 MLlib 和 PySpark 并行处理您的大规模 SHAP 计算

原文:https://towardsdatascience.com/parallelize-your-massive-shap-computations-with-mllib-and-pyspark-b00accc8667c

使用 SHAP 有效解释模型的分步指南。

Unsplash 上的 Pietro Jeng 拍摄

ml lib 简介

Apache Spark 的机器学习库(MLlib)主要是为了可伸缩性和速度而设计的,它利用 Spark 运行时来处理监督学习(如分类和回归)中常见的分布式用例,以及非监督学习(如聚类和协作过滤)和其他情况(如降维)。在这篇文章中,我将介绍我们如何使用 SHAP 来解释梯度增强树(GBT)模型,该模型在一定程度上符合我们的数据。

什么是梯度增强树?

在我们理解什么是梯度增强树之前,我们需要理解增强。Boosting 是一种集成技术,其中顺序地组合多个弱学习者以实现整体强学习者。在梯度提升树的情况下,每个弱学习器是一个决策树,该决策树顺序地最小化由该序列中的前一个决策树生成的误差(在回归的情况下是 MSE,在分类的情况下是 log loss)。要阅读更多关于 GBTs 的细节,请参考这篇博客文章。

了解我们的进口

from pyspark.sql import SparkSession
from pyspark import SparkContext, SparkConf
from pyspark.ml.classification import GBTClassificationModel
import pyspark.sql.functions as F
from pyspark.sql.types import *

前两个导入用于初始化 Spark 会话。它将被用来把我们的熊猫数据帧转换成 spark 帧。第三个导入用于将我们的 GBT 模型加载到内存中,该模型将被传递给我们的 SHAP 解释器来生成解释。倒数第二个也是最后一个导入用于执行 SQL 函数和使用 SQL 类型。这些将在我们的用户定义函数(UDF)中使用,我将在后面描述。

将 MLlib GBT 特征向量转换为熊猫数据帧

SHAP 解释器将一个数据帧作为输入。然而,训练 MLlib GBT 模型需要数据预处理。更具体地说,我们数据中的分类变量需要使用分类索引一键编码转换成数字变量。要了解更多关于如何训练 GBT 模特的信息,请参考这篇文章。产生的“特性”列是一个稀疏向量(要了解更多信息,请查看这个示例中的“预处理数据”部分)。它看起来像下面的东西:

SparseVector 特性列描述— 1。默认索引值,2。向量长度,3。功能列的索引列表,4。位于 3 的相应索引处的数据值列表。[图片由作者提供]

上面显示的“特性”列是针对单个训练实例的。我们需要为所有的训练实例转换这个 SparseVector。一种方法是迭代处理每一行,并添加到我们的熊猫数据帧中,我们将把它提供给我们的 SHAP 解释器(哎哟!).有一种更快的方法,它利用了我们将所有数据都加载到内存中的事实(如果没有,我们可以成批地加载它,并对每个内存中的批处理执行预处理)。用 Shikhar Dua 的话说:

1.创建字典列表,其中每个字典对应一个输入数据行。

2.根据此列表创建数据框。

所以,基于上面的方法,我们得到这样的东西:

rows_list = []
for row in spark_df.rdd.collect(): 
    dict1 = {} 
    dict1.update({k:v for k,v in zip(spark_df.cols,row.features)})
    rows_list.append(dict1) 
pandas_df = pd.DataFrame(rows_list)

如果说 rdd.collect() 看起来很吓人,其实解释起来还是挺简单的。弹性分布式数据集(RDD) 是基本的 Spark 数据结构,它们是和不可变的对象分布。RDD 中的每个数据集被进一步细分为逻辑分区,这些分区可以在 Spark 集群的不同工作节点中进行计算。因此,PySpark RDD collect() 所做的就是从所有工作节点检索数据到驱动节点。正如您可能猜到的,这是一个内存瓶颈,如果我们处理的数据超过了驱动程序节点的内存容量,我们需要增加 RDD 分区的数量,并通过分区索引对它们进行过滤。点击阅读如何操作

不要相信我的执行力。看看统计数据。

向 pandas 数据帧插入行的性能分析。[来源(感谢米哈伊尔 _ 萨姆和彼得·莫滕森):此处

以下是我的一个 Databricks 笔记本计划作业运行的指标:

输入大小:11.9 GiB (~12.78GB),所有任务的总时间:20 分钟,记录数:165.16K

运行上述单元的阶段执行的 125 个已完成任务的摘要度量。[图片由作者提供]

与 SHAP 图书馆合作

我们现在准备将预处理过的数据集传递给 SHAP 树解释器。请记住,SHAP 是一种局部特征归因方法,它将单个预测解释为我们模型的特征的 shapley 值的代数和。

我们使用 TreeExplainer 的原因如下:

  1. 合适: TreeExplainer 是一个为基于树的模型(Random Forest,XGBoost,LightGBM,GBT 等)计算 SHAP 值的类。
  2. 精确:它不是通过随机采样来模拟缺失特征,而是通过简单地忽略依赖于缺失特征的决策路径来利用树结构。因此,TreeExplainer 的输出是确定性的,不会随背景数据集而变化。
  3. 高效:所有组合不是在每个可能的特征组合(或其子集)上迭代,而是同时通过树推送,使用更复杂的算法来跟踪每个组合的结果——将复杂度从所有可能组合的 O( TL2ᵐ )降低到多项式 O( TLD )(其中 m 是特征的数量, T 是树的数量, L 是最大叶子数量,

check_additivity = False 标志运行验证检查,以验证 SHAP 值的总和是否等于模型的输出。然而,该标志要求运行 Spark 不支持的预测,因此需要将其设置为 False,因为它无论如何都会被忽略。一旦我们得到了 SHAP 值,我们就把它从一个 Numpy 数组转换成一个 pandas dataframe,这样它就很容易解释了。

需要注意的一点是,当我们将 Spark 数据帧转换为 pandas 时,数据集的顺序保持不变,但是相反的情况不成立

以上几点将我们引向下面的代码片段:

gbt = GBTClassificationModel.load('your-model-path') 
explainer = shap.TreeExplainer(gbt)
shap_values = explainer(pandas_df, check_additivity = False)
shap_pandas_df = pd.DataFrame(shap_values.values, cols = pandas_df.columns)

Pyspark UDFs 简介以及何时使用它们

PySpark UDFs 如何将单个任务分配给 worker (executor)节点[Source: 此处 ]

用户定义的函数是复杂的自定义函数,对数据集的特定行进行操作。当本地火花函数被认为不足以解决问题时,通常使用这些函数。Spark 函数天生比 UDF 快,因为它本身是一个 JVM 结构,其方法是通过对 Java APIs 的本地调用来实现的。然而,PySpark UDFs 是 Python 实现,需要在 Python 解释器和 JVM 之间移动数据(参考上图中的箭头 4)。这不可避免地引入了一些处理延迟。

如果不能容忍任何处理延迟,最好的办法是创建一个 Python 包装器,从 PySpark 本身调用 Scala UDF。这个博客展示了一个很好的例子。然而,使用 PySpark UDF 对我的用例来说已经足够了,因为它易于理解和编码。

下面的代码解释了在每个 worker/executor 节点上执行的 Python 函数。我们只是选取最高的 SHAP 值(绝对值,因为我们也想找到最有影响力的负面特征)并将其附加到相应的 pos_featuresneg_features 列表,然后将这两个列表附加到返回给调用者的 features 列表。

def shap_udf(row):
    dict = {} 
    pos_features = [] 
    neg_features = [] 
    for feature in row.columns: 
        dict[feature] = row[feature]     dict_importance = {key: value for key, value in
    sorted(dict.items(), key=lambda item: __builtin__.abs(item[1]),   
    reverse = True)}     for k,v in dict_importance.items(): 
        if __builtin__.abs(v) >= <your-threshold-shap-value>: 
             if v > 0: 
                 pos_features.append((k,v)) 
             else: 
                 neg_features.append((k,v)) 
   features = [] 
   features.append(pos_features[:5]) 
   features.append(neg_features[:5])    return features

然后,我们用我们的 Python 函数名注册我们的 PySpark UDF(在我的例子中,它是 shap_udf ),并在 F.udf() 的参数中指定函数的返回类型(在 Python 和 Java 中是强制的)。在外部 ArrayType()中有两个列表,一个用于正面特性,另一个用于负面特性。因为每个单独的列表最多包含 5 个(feature-name,shap-value) StructType()对,所以它表示内部 ArrayType()。下面是代码:

udf_obj = F.udf(shap_udf, ArrayType(ArrayType(StructType([ StructField(‘Feature’, StringType()), 
StructField(‘Shap_Value’, FloatType()),
]))))

现在,我们只是创建一个新的 Spark 数据帧,它有一个名为‘Shap _ Importance’的列,该列为 spark_shapdf 数据帧的每一行调用我们的 UDF。为了区分正面和负面特性,我们在一个名为 final_sparkdf 的新 Spark 数据帧中创建了两列。我们最终的代码片段如下所示:

new_sparkdf = spark_df.withColumn(‘Shap_Importance’, udf_obj(F.struct([spark_shapdf[x] for x in spark_shapdf.columns])))final_sparkdf = new_sparkdf.withColumn(‘Positive_Shap’, final_sparkdf.Shap_Importance[0]).withColumn(‘Negative_Shap’, new_sparkdf.Shap_Importance[1])

最后,我们已经提取了每个测试实例的 GBT 模型的所有重要特性,而没有使用任何显式的 for 循环!合并后的代码可以在下面的 GitHub gist 中找到。

从拟合的 GBT 模型中获取最具影响力的正负 SHAP 值

这是我第一次尝试写一篇文章,如果有任何事实或统计上的不一致,请联系我,我将非常乐意与你一起学习!😃

参考

[1]Soner y ldr RM,梯度推动决策树-向数据科学解释 (2020)

[2] Susan Li,使用 PySpark 和 MLlib 的机器学习—解决一个二元分类问题 (2018),走向数据科学

[3] Stephen Offer,如何用 Spark 训练 XGBoost(2020),数据科学与 ML

[4] 在数据块上使用 Apache Spark ml lib(2021),数据块

[5] Umberto Griffo,不要收集大型 rdd(2020),Apache Spark 最佳实践和调优

[6]尼基莱什·努卡拉,·朱,·布拉恰利,汤姆·戈登伯格(2019),斯帕克·—对性能的深刻见解,昆腾布莱克

人工智能和医学的相似之处

原文:https://towardsdatascience.com/parallels-in-ai-and-medicine-38f446c533b

意见

对人工智能未来的医学主题思考

对于那些已经认识我一段时间的人来说,我喜欢阅读书籍、文章和 Youtube 系列中的医学主题。这与我的职业有些距离,但我认为有这种兴趣是健康的(明白吗?).下面是我最近喜欢的一些书。

作者编辑的书籍封面。

最终,我在我的学科和医学科学之间找到了一些相似之处。就像免疫系统表现得像一个微服务架构,其中没有集中控制,只有一个具有本地输入和输出的应急系统。或者基因通向不同人类特征和条件的路径如此复杂,类似于具有 1750 亿个参数的 GPT3。

我一直在思考的一件事是,医学如何告知人工智能的未来。前者是一个影响深远的实践,其轨迹是停滞、可怕的实践、再创造和持续发展。后者深入人心,成就斐然。但是它没有几个世纪的学习带来的好处。事实上,它的影响不仅越来越大,而且从我们的身体、自然生态系统和地缘政治中在意想不到的地方被发现。

在接下来的几篇文章中,我想把我的写作组织成输出、输入、过程。非常像软件工程,我知道。此外,我将转而使用术语“机器学习”,因为人工智能可能是一个太大的术语。机器学习(ML)作为人工智能的一个子集,是与从大型数据集学习有用程序相关的工作主体。这个子领域享受着人工智能聊天机器人、推荐器、视觉应用等的持续宣传,但它实际上归结为从数据中学习。

免责声明:我是一名机器学习从业者,因此请不要将我的任何陈述视为完全等同于解剖学事实和流行的医学观点。还有,这个帖子不包含医学建议,也不代表医学上准确

输出

显而易见,ML 将变得更加普遍。它的输出将更加广泛,它对我们日常生活的影响甚至会比 Instagram 滤镜更深入。除了自动驾驶汽车和全自动客户服务的宣传之外,人们还必须考虑这些新技术将影响的系统。所以我将这些趋势分为“微观”和“宏观”两个角度。

微观上,房屋、仓库等小系统将被改造。亚马逊一直在做后一种,工业领域的其他公司也会跟进。从宏观上来说,像整个生态系统和整个城市这样的大系统将被建模和管理。在这方面,我为我的菲律宾同胞感到骄傲,他们在利用卫星图像制作流行病和流动性模型的工作获得了美国国家航空航天局的奖励。

个性化医疗中一个潜在有用的数据点——可穿戴设备。卡罗琳娜·格拉博斯卡在Pexels.com拍摄的照片

在医学领域,产出也很有希望。个性化医疗,医疗保健和遗传学(以及商业利益——苹果手表,看着你)的交汇点,在当前的媒体中很突出。但从我的阅读来看,机器学习和统计也可以在检测保险欺诈、医疗保健过度治疗和治疗不足、医生倦怠以及总体医疗保健系统的压力方面发挥重要作用。这会很痛,这一点尤其明显。

输入

大型硬件。大模特。大数据。这是深度学习飞速发展的趋势。与云时代的全球数字化和无服务器应用相一致,从业者可以依靠不断增长的数据量,同时降低成本。但近年来,几位机器学习重量级人物呼吁重新评估方法——最著名的是吴恩达。人们并不总是每天都能拿出大数据。正如他所引用的,

当你拥有相对较小的数据集时,领域知识就变得很重要。(人工智能系统应该)有两个知识来源——来自数据和人类经验。当我们拥有大量数据时,人工智能将更多地依赖数据,而不是人类知识。

吴恩达接受 Venturebeat 采访

在医学中,人类的经验被概括在大型医学本体论中。统一医学语言系统就是这样一个数据库,它汇集了医学中不同的相关领域。这是许多专家系统的基础,自然语言处理的进步也推动了这些专家系统的发展。

我相信,我们应该有积累和处理这种“小数据”的诀窍。象征性的人工智能可以提供一条前进的道路。这是机器学习的又一次重新想象,它有可能成为解释我们可解释性问题的一个框架。

处理

说到可解释性,我相信这是我们 ML 从业者必须面对的最重要的问题。归根结底是信任的问题。在漫长的医学世纪中,增进信任一直是医患关系的焦点。信任也应该是构建 ML 系统的指导原则。ML 社区应该关注一些职业道路来建立信任。我在这里提出了几个反映医疗保健角色的角色。

侧重于 ML 输出的操作

这与 MLOps 的上升趋势有关联但又有点不同。这些分析师或代表了解机器学习,并拥有在系统出现严重故障时提供解决方案的工具。他们就像机器学习的急救医学专家。有时,最快的可能解决方案应该完成。分析师可以发现令人震惊的推荐(例如普通观众网站中的软核色情),由于种族原因的热修复计算机视觉错误(例如移动应用程序中的身份证照片证明),并报告聊天机器人发生的有趣事情(还记得 Tay 吗?)。在更深层的后端,工程师应该注意数据漂移和模型漂移,以及与 ML 系统相关的事件。

当系统出现问题时,这个角色体现了护理人员快速到达需要他们的地方。在我们建立信任的范例中,他们是服务补救型的工作。然而,我们不应该如此被动。他们还应该汇编他们的学习,最终应达到…

机器学习设计师-审计师

他们有点像医疗保健管理员,医院依靠他们来提供高效的护理。在 ML 术语中,他们建立了对产品的信任。他们从用户那里学习,并从运营中收集知识,以形成一个能够提供最佳体验的产品。这并不容易,因为早期的数据输入可能有很大的偏差,只有通过仔细的管理和注释才能解决。如何使用输出也可能被重新评估。例如,设计师可以创建候选过滤器(消除可疑内容),甚至“弹出”过滤器气泡(例如,使新闻主题多样化),而不是直接提供推荐。

这份工作需要超越准确性的衡量标准——公平性、可用性和影响力。不容易量化但非常关键的指标。在我们早期的“紧急事件”中,软核色情可能是由于 clickbait 图像,这是由热衷于利用点击而不是更有意义的交互的模型产生的。我们的身份证明捕获应用程序的输入数据可能缺少有色人种的图像。由于地区方言,聊天机器人无法充分理解它声称理解的外语。

我想起了悉达多·慕克吉的医学第三定律。“对于每一个完美的医学实验,都有一个完美的人类偏见”。在追求最大精确度的过程中,我们工程师可能已经忘记了我们的偏见。

遗憾的是,超准确性指标并不总是底线的一部分。设计师如何向董事会证明这些指标的合理性?风气的改变是必需的,这就是为什么我们需要…

机器学习伦理学家

设计师关注产品,伦理学家关注 T2 人口。一个很好的比喻是公共卫生官员,在那里疾病在时间和空间上受到监测。设计师建立对产品的信任,而伦理学家建立对整个 T4 领域的信任。这是一个非常艰巨的任务,但是必须完成。我们需要更多像 Timnit Gebru 这样的伦理学家,她和她的同事引发了与谷歌关于偏见、环境问题和模型透明度的辩论。

有一些突出的应用程序的例子,如果某些东西失败了,会产生深远的影响——监控数据公司招聘信用评分。如果太成功了呢?《剑桥分析》的报道公开揭示了 ML 的潜力,它不仅影响了社会的微观部分,而且也分化了整个人群。像癌细胞一样,ML 生成的假货是我们自己认知偏见的破坏性镜子,并且继续繁殖和入侵。定向营销、假新闻和心理战的领域正在合二为一。

显然,在这方面,特别是在通信方面,需要做大量的工作。我们应该有一个类似 CDC 的组织吗?是的,我想是的。类似于 NIH 在 80 年代是如何通过志愿者组织的活动从基层建立支持的。这就是为什么伦理学家应该是好的教育者,能够分享他们的发现和建议,这样我们才能逐步前进,建立对人工智能的信任。假以时日,它将创造出新的传销从业者……

每个从业者也需要进化

人工智能专业人员也必须做出贡献,以促进该领域的信任。我可以想到两种方法:为 ML 和公共教育创建一个道德准则。

很难想象程序员是道德的典范,但是底线应该是,是的,每个人都应该有道德准则。奥利娅·丹尼利维奇在Pexels.com拍摄的照片。

道德准则将作为所有道德 ML 活动的指导框架。道德准则是难以捉摸的,因为哲学和实施。我们可以从医学中寻找一些哲学上的灵感。为了纠正第二次世界大战中的大规模错误,日内瓦宣言建立在希波克拉底誓言的基础上,并将其置于现代世界的背景下。继临床研究中的严重不公正之后,贝尔蒙特报告概述了人类受试者的原则。回到曼梯·里,我不确定剑桥分析公司的故事是否已经成为一个分水岭,或者是否会错过一个改变的机会。

至于实现,在医学中,有医生委员会管理医疗事故的问题,但它不是 ML 和 AI 的直接端口,更不用说它的相邻领域,软件工程。请注意,ACM 有一套道德规范,但不幸的是,没有太多的机构遵守这一规范,甚至没有通知他们的员工。在医学中,有四大支柱:仁慈、无害、自主和公正。有时,这些相互冲突,但有一个成熟的申诉法律制度。在 ML 里还不是那么清楚,至少,现在还不是。这就是为什么我们需要更多的人参与对话,他们的声音不仅仅来自股东的立场。多元文化、经济和生态视角至关重要。

公众教育将有助于围绕 ML 话题展开并继续对话。希望我们中小学的计算机素养科目涵盖 ML 不断扩大的影响力。它可以向我们的年轻人介绍互联网微妙的运作方式。对于更成熟的人,我推荐像速成班人工智能这样的努力,以扩大外行人的理解。

我们应该尽可能扩大对话的范围,保持对话,开始行动,继续回顾。就像过去的医学一样,人工智能还有很长的路要走。尽管如此,我还是希望我们能尽早结束“数字黑暗时代”,继续创新和持续发展。

感谢阅读!

原载于 2022 年 5 月 3 日【http://itstherealdyl.com】

参数测试与非参数测试,以及何时使用它们

原文:https://towardsdatascience.com/parametric-vs-non-parametric-tests-and-where-to-use-them-85130b3877dc

作者图片

数据科学的基础包括计算机科学、统计学和数学。人们很容易被最新、最棒、最强大的算法——卷积神经网络、强化学习等——所吸引。

作为一名 ML/health 研究人员和算法开发人员,我经常使用这些技术。然而,在我作为一名电气工程师接受了大约 10 年的培训后,我在数据科学社区看到的一个普遍现象是,如果你只有一把锤子,那么一切看起来都像钉子。可以说,尽管这些令人兴奋的算法有巨大的适用性,但数据科学社区的统计基础经常被忽视。我很幸运,除了在母亲是统计学家的陪伴下成长之外,我的本科和研究生课程都是专门针对统计学的。所以这篇文章可能是分享一些基本的统计测试以及何时何地使用它们的第一篇文章!

参数检验对总体参数进行假设:

1。正态性——每组数据应呈正态分布

2。独立性 —每组数据应随机独立取样

3。无异常值 —数据中无极端异常值

4。等方差 —每组中的数据应具有大致相等的方差

如果可能的话,我们应该使用参数测试。然而,非参数测试(有时也称为无分布测试)并不假设任何关于基础分布的事情(例如,数据来自正态(参数分布)。

我们可以使用 Q-Q(分位数-分位数)图直观地评估正态性。在这些图中,观察到的数据与正态分布的预期分位数相对应。这里可以看到一个 python 演示代码,其中创建了一个随机正态分布。如果数据正常,它会显示为一条直线。

import numpy as np
import statsmodels.api as statmod
import matplotlib.pyplot as plt#create dataset with 100 values that follow a normal distribution
data = np.random.normal(0,1,100)#create Q-Q plot with 45-degree line added to plot
fig = statmod.qqplot(data, line='45')
plt.show()

作者图片

检查正常性的测试

  • 夏皮罗-维尔克
  • 科尔莫戈罗夫-斯米尔诺夫

这两种检验的无效假设是样本是从正态(或高斯)分布中抽取的。因此,如果 p 值显著,则违反了正态性假设,并且数据必定是非正态的替代假设被接受为真。

选择正确的测试

处理参数和非参数测试的区间水平数据时,可以参考此表。

作者图片

优点和缺点

非参数测试有几个优点,包括:

  • 当参数测试的假设被违反时,更多的统计能力。
  • 常态假设不适用
  • 小样本是可以的
  • 它们可用于所有数据类型,包括顺序、标称和间隔(连续)
  • 可用于含有异常值的数据

非参数测试的缺点:

  • 如果假设没有被违反,不如参数测试有效

如果你喜欢这篇文章,请留下你的评论,或者如果有你想看到的附加信息,或者一篇关于这个主题的更深入的后续文章,我很乐意提供!: )

此外,如果你喜欢看到这样的文章,并希望无限制地访问我的文章和所有由 Medium 提供的文章,请考虑使用下面的我的推荐链接注册。会员费为 5 美元/月;我赚一小笔佣金,这反过来有助于推动更多的内容和文章!

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

参考

[1] Kotz,s;等人编辑。(2006),统计科学百科全书,威利。
[2]林德斯特罗姆特区(2010)。绍姆简易统计大纲,第二版(绍姆简易大纲)第二版。麦格劳-希尔教育公司(2003 年)。假人统计,第 18 版

参数与非参数机器学习算法

原文:https://towardsdatascience.com/parametric-vs-nonparametric-machine-learning-algorithms-5bf31393d944

有什么区别,什么时候使用它们

图片由来自 Pixabay 的 Jerzy Górecki 提供

有两种主要类型的机器学习算法:参数和非参数。但是它们之间有什么区别呢?在本文中,我们将讨论参数算法和非参数算法之间的差异,以及每种类型何时最合适。我们还将提供每种算法的示例,以帮助您更好地理解它们。

参数算法

参数算法基于定义输入和输出之间关系的数学模型。这使它们比非参数算法更具限制性,但也使它们更快、更容易训练。参数算法最适用于输入数据明确且可预测的问题。

参数算法的一些例子包括:

线性回归 —线性回归用于根据一组输入变量预测目标变量的值。它通常用于预测建模任务,例如根据历史销售数据预测产品的销售量。

逻辑回归 —逻辑回归用于根据一组输入变量预测目标变量的值。它通常用于预测建模任务,例如预测客户购买产品的可能性。

神经网络 —神经网络是一种机器学习算法,用于对数据中的复杂模式进行建模。神经网络受人脑工作方式的启发,可用于解决各种问题,包括回归和分类任务。

非参数算法

非参数算法不是基于数学模型;相反,他们从数据本身中学习。这使得它们比参数算法更灵活,但计算成本也更高。非参数算法最适用于输入数据定义不明确或过于复杂而无法使用参数算法建模的问题。

非参数算法的一些例子包括:

决策树 —决策树是一种非参数机器学习算法,用于对数据中的复杂模式进行建模。决策树基于层次结构,可以用来解决各种各样的问题,包括回归和分类任务。

支持向量机 —支持向量机是一种用于数据分类任务的机器学习算法。支持向量机特别适合输入数据是线性可分的任务。这意味着可以使用线性边界将数据分成不同的类或组。

何时使用参数算法?

参数算法最适合于输入数据定义明确且可预测的问题。这使它们成为预测建模等任务的理想选择,这些任务的目标是根据一组输入变量预测目标变量的值。

例如:假设你想预测一个产品的销售量。您可以使用参数算法(如线性回归)来建立模型,定义历史销售数据和预测销售量之间的关系。

何时使用非参数算法?

非参数算法最适合于输入数据定义不明确或过于复杂而无法使用参数算法建模的问题。这使它们成为数据分类等任务的理想选择,这些任务的目标是将数据分成不同的类或组。此外,对于复杂问题,非参数算法通常比参数算法更准确。

例如:假设你想建立一个机器学习算法,根据历史数据预测一只股票的价格。在这种情况下,您可以使用参数算法,如线性回归。这是因为输入数据(历史股票价格)是定义明确且可预测的。另一方面,如果你想建立一个算法来对动物图像进行分类,你可以使用一个非参数算法,比如支持向量机。这是因为输入数据(动物的图像)没有很好地定义,并且太复杂而不能使用参数算法建模。

总之,参数算法最适合于输入数据定义明确且可预测的问题,而非参数算法最适合于输入数据定义不明确但我们可以使用更多数据来训练它的问题。

其他一些你可能感兴趣的文章!

</7-essential-ai-youtube-channels-d545ab401c4>

解析,不验证| Python 模式

原文:https://towardsdatascience.com/parse-dont-validate-f559372cca45

验证数据就像看门人一样,将数据解析成有意义的数据类型,为原始数据增加了有价值的信息

照片由威尔·波拉达Unsplash 拍摄

当使用处理外部数据的应用程序时,我们通常会设置几层验证和数据转换来保护我们的业务逻辑不会崩溃……或者更糟,成为攻击的受害者。

第一层保护包括验证输入数据的一致性及其实际有效性。我们不想让我们的系统受到 SQL 注入攻击,甚至是无法正确处理的不完整字段。保护信息系统的一种广泛而经典的方法是使用数据验证:我们执行有效性检查,如果它们没有全部通过,那么我们就丢弃传入的数据。

为了简单起见,在本文中,我们将假设已经执行了安全检查,并且我们对数据模型的数据一致性感兴趣。所以就从它开始吧。

简单的例子

假设一家商店要求我们构建一个组件来联系他们的客户。客户可以将她或他的数据插入到表单中,然后收到回复。这些数据包括姓名、电子邮件和电话号码。此外,还有根据所提供的联系类型联系用户的功能。

用 Python 建模数据最直接的方法是使用字典:

customer1 = {
    "name": "John", 
    "surname": "Smith",
    "email": "john.smith@mymail.com",
}
customer2 = {
    "name": "Jessica",
    "surname": "Allyson",
    "telephone": "0123456789",
}

这种方法遵循了 Rich Hickey 在他著名的演讲“也许不是”中强调的观点。在那次演讲中,Clojure 的发明者捍卫了只使用实际存在的字段的字典,而不是使用可选字段(在他提到的语言中称为 Maybe)的想法。

里奇·希基(Rich Hickey)是一位魅力非凡、技术高超的工程师和社区领袖,人们很容易被他的谈话所说服。然而,这种数据建模有一个主要的缺点:在验证了字典的结构之后,验证之后的代码将没有关于数据内部结构的更多信息。

让我们用一个例子来说明:

def validate_customer_data(customer: Dict[str, str]) -> None:
    if "name" not in customer or "surname" not in customer:
        raise ValueError("Customer data must contain keys name and surname")
    if "email" not in customer and "telephone" not in custormer):
        raise ValueError("At least one field among email and telephone must be present in customer data")def contact_customer(customer: Dict[str, str]) -> None:    
    if "telephone" in customer:
        open_call(customer["telephone"])
    elif "email" in customer:
        open_email_client(customer["email"]) customer = receive_customer_data()
validate_customer_data()
contact_customer(customer)

在上面这段代码中,我们首先以某种方式接收客户数据,然后验证该数据,最后用它联系客户。然而,经过验证,我们只知道客户向我们提供了至少一个来自电子邮件或电话号码,而不是这两个中的哪一个。我们从验证函数中得到的服务是,如果两个字段都不存在,就抛出一个错误。contact_customer 函数需要控制这两个字段中的哪一个出现,并相应地采取行动。

请注意,基于(数据)类的建模方法只会迫使我们处理无对象,在这里没有多大帮助:

*from dataclasses import dataclass@dataclass
class Customer:
    name: str
    surname: str
    email: Optional[str]
    telephone: Optional[str] def contact(self) -> None:
       if self.telephone is not None:
           open_call(self.telephone)
       elif self.email is not None:
           open_email_client(self.email)*

在这里,在做出决策之前控制数据结构的代码看起来微不足道,不值得进行更复杂的设计选择。然而,当数据结构变得比一个玩具示例更大时,控制代码将相应地增加(在某些情况下以超线性的方式)。这可能导致性能成本,并且容易出错。

这一节要带回家的要点是,在验证函数时会学到一些额外的知识,而这些知识在函数返回的那一刻就会丢失。

我想在本文中讨论的解决方案是将数据解析成一种类型,这种类型包含了我们作为验证的一部分而生成的信息。这种方法受益于 Python 类型的静态部分,我在上一篇文章中已经提到过。

*

解析,不验证

解析,不验证是 Alexis King 在她的同名文章中引入的一个概念,该文章关注 Haskell 语言。然而,Haskell 和 Python 之间的巨大差异不应该让你认为相同的思想不能移植到 Python 中。事实上,解析是 Pydantic library 的基本思想,我建议您在阅读完本文后查阅一下。一旦想法得到澄清,即使没有 Haskell 知识,King 的文章也应该非常容易理解。

主要思想是验证防止坏数据进入我们的应用程序。然而,一些信息是在验证过程中创建的,然后立即被丢弃。另一方面,解析允许我们拒绝格式错误的数据,但也允许我们将有效数据映射到保留这些信息的数据类型。应用程序的其余部分将使用这些类型来阻止已经执行的控件。

例如,在上面的例子中,字典可以被映射到新的类型:

@dataclass
class CustomerWithPhone:
  name: str
  surname: str
  phone: str@dataclass
class CustomerWithEmail:
  class CustomerWithPhone:
  name: str
  surname: str
  email: str@dataclass
class CustomerWithPhoneAndEmail:
  class CustomerWithPhone:
  name: str
  surname: str
  phone: str
  email: str

上面的类也可以有自己的方法来联系客户,现在不再需要检查有效字段。此外,Python 为通用函数提供了单一分派:

from functools import singledispatch @singledispatch
def contact_customer(customer):
  raise NotImplementedError("Cannot contact a customer of unknown type")@contact_customer.register(CustomerWithPhone)
def _contact_customer_by_phone(customer):  # this name is irrelevant 
  .
  .
  call_number(customer.phone)
  .
  .@contact_customer.register(CustomerWithEmail)
def _contact_customer_by_email(customer):  # this name is irrelevant 
  .
  .
  send_email(customer.email)
  .
  .

上面的代码声明了一个名为 contact_customer 的通用函数,并为它定义了两个不同的实现,分别用于两个输入类型 CustomerWithPhone 和 CustomerWithEmail。Python 解释器在运行时根据对象的运行时类型决定调用哪个函数。您不需要编写任何代码来决定要调用的正确函数。

为了从我们定义的类型中获得最大利益,使用静态类型检查变得很重要,例如使用 mypy ,这样您就可以自动控制静态和动态的代码正确性。这里重要的是,它们不是手动控制,也不是由您(或您的团队)必须维护的代码组成的。

使用静态类型处理解析的数据是确保代码正确性的一种方式。你永远不会调用错误的函数或读取错误的属性。Mypy 将为您检查并突出显示此类错误。在自动完成和可变建议方面,我们还免费获得 IDE 支持。

结论

静态类型不是 Python 语言最初设计的一部分,但随着时间的推移,它正成为一个越来越强大的功能。大型项目可以从可以静态检查的深思熟虑的数据类型中受益匪浅。

使用数据类型的第一步是将应用程序的输入数据转换成这种类型。解析就是这个过程,它检查数据的正确性,然后将它们分配到应用程序中正确的位置。静态检查和动态多态可以帮助开发人员通过为数据本身的控件编写尽可能少的代码来减少错误的数量。

中等会员

你喜欢我的文章吗?你是否正在考虑申请一个中级会员来无限制地阅读我的文章?

如果您通过此链接订阅,您将通过您的订阅支持我,无需为您支付额外费用【https://medium.com/@mattiadigangi/membership*

为经济学家解析 PII——STATA 方式

原文:https://towardsdatascience.com/parsing-pii-for-economists-the-stata-way-ad9d4cc1ac37

良好的编码实践和伪代码,用于在 STATA(经济学家的常用语言)中从个人层面数据解析 PII

来自像素的免费使用照片

介绍

在过去的两年里,我在一个经济学实验室工作,对社会科学研究领域的各种概念越来越熟悉。特别是,PII(代表个人身份信息(PII))包含在应用程序、调查和管理操作中收集的各种个人级别的数据中。当研究人员想要将不同的数据集链接在一起,并在不同的数据或时间跨度上跟踪个人的运动或活动时,PII 尤其有价值。这允许在研究中进行更动态和更深入的分析。然而,PII 本身是敏感的,因此需要谨慎对待。这就是为什么包含 PII 的数据通常会被严格的安全协议分开处理。无论如何,本文将考察一些的好的编码实践和伪代码,以便使用 STATA——许多经济学的首选软件——解析出 PII。

使用局部变量和全局变量

尽管 STATA 不是编程语言,但良好的编码实践仍然适用。使用局部变量和全局变量可以更方便地管理代码路径。假设您正在对包含您需要使用的文件的目录的文件路径进行硬编码。如果要修改路径,就必须修改使用该路径的每一行代码。这就是本地人和全球人发挥作用的地方。

局部变量可以是任何东西,从标量到变量,再到存储在局部内存中的变量列表和字符串。这里,本地内存意味着在脚本中定义的本地变量只在该脚本中有效。它们将无法从另一个脚本中引用。另一方面,全局变量可以被全局引用,不管它是从哪个脚本调用的。

我们假设有一个你要读入的数据集叫做“pii_data.dta”。它位于“O:/josh/data”中。您可以通过以下两种方式定义数据目录路径:

local data_dir = “O:/josh/data”global data_dir = “O:/josh/data”use "`data_dir'/pii_data.dta" # read in data using local pathuse "$data_dir/pii_data.dta" # read in data using global path

请注意局部变量和全局变量在语法上的不同。STATA 局部使用反勾号(`)和撇号('),而全局使用美元符号($)。

定义模式

在从数据中解析出 PII 之前,应该先定义一个“模式”。这里定义一个模式意味着确定我们将提取什么类型的 PII,以及信息将存储在什么变量中。这取决于你从事的研究或项目试图从 PII 中得到什么。全名是否足够,还是必须分开成单独的列(例如,名、中间名、姓和后缀)?).您只对每个人的年龄感兴趣,还是对出生的具体年月日感兴趣?

在本例中,我们将假设数据包含全名、出生日期、地址和邮政编码信息。我们将模式定义为以下变量:

名 _ 名|名 _ 中间|名 _ 姓|名 _ 后缀| dob_yyyy | dob_mm | dob_dd |地址 _bldnum |地址 _ 字符串|地址 _ 城市|地址 _st |地址 _zip

解析名称

名字可以有许多不同的格式。在某些情况下,它只出现在一列中,每个单元格中有全名。在其他情况下,它出现在单独的列中,每个列代表全名的一部分(例如姓氏)。就我个人而言,我发现后缀很少被提取到单独的列中。不幸的是,为了增强实体解析的性能(即,如果那些观察值利用概率匹配算法共享相似的 PII,则链接不同数据集中的不同数据点并给予它们相同的唯一个体标识符的过程),具有诸如后缀的附加信息有助于将一个数据点与另一个数据点区分为两个不同的个体或实体。

假设数据包含三个与姓名相关的列,即姓氏、名字和中间名。我们希望转换这三列,使它们符合我们的模式,该模式有四个与名称相关的列。这样做的 STATA 代码如下:

gen n_last = strtrim(stritrim(lower(LASTNAME)))gen n_first = strtrim(strtrim(lower(FIRSTNAME)))gen n_mid = strtrim(strtrim(lower(MIDDLENAME))) foreach var of varlist n_* { replace `var’ = subinstr(`var’, “-“, “ “, .) replace `var’ = strtrim(stritrim(subinstr(`var’, “.”, “”, .))) /* check for suffix */ gen `var’_suff = regexs(0) if regexm(`var’, “(1|11|111|[  ]i|ii|iii|[  ]iv|[  ]v|[  ]v[i]+|jr|sr|[0-9](nd|rd|th)*)$” replace `var’ = strtrim(stritrim(subinstr(`var’, `var’_suff, “”, 1)))}gen suffix = “”foreach var of varlist n_*_suff {replace suffix = `var’ if missing(suffix) & !missing(`var’)}drop n_*_suff n_suffixreplace name_raw = raw_namereplace name_first = n_firstreplace name_middle = n_midreplace name_last = n_lastReplace name_suffix = suffix

前三行很简单——它们只是清除每个 name 变量中的空格,并将它们复制到新生成的变量中进行处理。请注意 strtrimstritrim 函数对于清理 STATA 中的字符串变量很有用。

接下来是 for 循环。它在我们刚刚创建的三个变量之间循环。

循环的前两行(对于每个变量…)删除了特殊字符“-”和“.”。通常建议在应用之前删除它们,因为它们会妨碍正则表达式的有效应用。

循环中的下一部分是利用正则表达式,这在其他编程语言中很常见。这里,我们试图获取混合到每个名称列中的任何后缀。如果后缀始终只混合在一列中就好了,但这种情况很少发生。这就是为什么我们必须遍历名字、中间名和姓氏列,以查看它们中是否存在任何后缀。欢迎来到真实世界数据的世界,这里的数据很少有一致性!正则表达式旨在获取典型的后缀,包括第二、第三、第四、jr、sr 等。如果作为第一个参数输入的变量满足第二个参数中的正则表达式模式,STATA 中的 regexm 函数返回 1,否则返回 0。

DOB 解析

在 STATA 中解析出生日期(DOB)非常简单,因为有一些定制的函数。假设 DOB 列是年、月和日的字符串格式(按此顺序),我们可以很容易地解析 DOB,如下所示:

replace dob_yyyy = year(date(DOB, “YMD”))replace dob_mm = month(date(DOB, “YMD”))replace dob_dd = day(date(DOB, “YMD”))

请注意,date 函数首先将日期字符串变量转换为 date 对象,year、month 和 day 函数允许您提取日期的相关部分。如果 DOB 变量是非字符串格式的,那么在继续上面的代码之前,您必须将格式改为字符串。另外,请注意 date 函数的第二个参数是日期的格式(例如 YMD,MDY…),所以请确保您指定了正确的格式。然而,真实世界的数据通常在一列中混合了多种格式,在解析之前需要仔细检查。

解析地址

典型的地址格式如下:

2301 County Street, Ann Arbor, MI 40000 apt 220

它以某个街道地址开始,后面是城市、州、邮政编码和更细粒度的信息,如公寓号。如果数据包含这些不同的组件,那么将它们解析成我们之前定义的地址模式会更容易。但是假设数据在一列中带有完整的地址,那么用于解析地址信息的 STATA 代码如下:

gen raw_addr = ADDRESSreplace raw_addr = strtrim(stritrim(lower(raw_addr)))gen bldnum = strtrim(regexs(0) if regexm(raw_addr, “^([0–9]+) [ ]”)gen street = strtrim(strtrim(subinstr(raw_addr, bldnum, “”, 1)))replace ZIP = “” if ZIP == “0” # zipcodes that are just 0 are probably missing zipcodes that have been filled in with 0s by the agencyreplace addr_raw = raw_addrreplace addr_bldnum = bldnumReplace addr_str = streetReplace addr_str = substr(addr_str, 1,5)Replace addr_city = strtrim(stritrim(lower(CITY)))Replace addr_st = strtrim(stritrim(lower(STATE)))Replace addr_zip = ZIP

与名称解析类似,我们也利用正则表达式来解析地址。STATA 的正则表达式模式和语法几乎类似于任何其他编程语言中使用的正则表达式,因此请参考 Python 文档中的正则表达式以深入了解它。

希望这有所帮助!以后请考虑关注我更多的文章!

关于作者

数据科学家。在密歇根大学刑事司法行政记录系统(CJARS)经济学实验室担任副研究员。Spotify 前数据科学实习生。Inc .(纽约市)。即将入学的信息学博士生。他喜欢运动,健身,烹饪美味的亚洲食物,看 kdramas 和制作/表演音乐,最重要的是崇拜耶稣基督。结账他的 网站

用 TensorFlow 解析 UrbanSound8K 数据集

原文:https://towardsdatascience.com/parsing-the-urbansound8k-dataset-with-tensorflow-43192a5206e7

从原始音频到 TFRecord 再到填充批次

有多种方法可以将数据输入神经网络。最常用的是 Numpy 数组和 CSV/Pandas 文件。此外,TensorFlow 还提供自定义存储格式 TFRecord。它很方便,但对初学者不太友好。

理查德·霍瓦特在 Unsplash 上的照片

尽管许多数据集都是现成的,比如 MNIST 或 CIFAR,但有时您必须自己实现定制的数据集。令人欣慰的是,一旦你做了几次,把其他数据集转换成 TFRecord 格式并不困难。在这里,我们将分析 UrbanSound8K 数据集,以 TFRecord 格式存储它,最后,迭代它的样本。如果你对这种格式完全陌生,你可以在这里得到一个实际操作的回顾,它也涵盖了我们如何解析文本和图像数据。

数据集描述

UrbanSound8K 数据集 [1】(可在免费获得,仅用于非商业用途;CC BY-NC 3.0)包含 8732 个不同时长的音频文件。每个样本最多 4 秒钟,属于 10 个类别中的一个。这些文件被预先安排在十个文件夹中,并以 WAVE 格式存储。每个文件可能有不同的采样率、位深度和通道数。大约 7 GB,是一个中等大小的数据集。因此,如果你正在寻找一个更小,更容易访问的音频数据集,那么看看 ESC50。

下载和提取

转到数据集的网页,点击底部的下载链接下载数据集。如前所述,您可以免费使用这些数据,但只能用于非商业项目——对于这篇展示数据解析和相关概念的解释性博文来说,一切都很好。

根据您的连接,下载可能需要几分钟。下载完成后,解压归档文件。你现在有两个文件夹,音频元数据。我们只需要第一个。

在这个文件夹中,文件被预先安排在十个文件夹中,文件夹 1文件夹 10 。所有文件都根据以下方案命名:

{源 ID }-{标签}-{拍摄}-{切片}。声音资源文件

第一部分用来标识源文件的 ID,标签是类标签,用来区分取自同一个原始文件的多个样本。

将音频数据解析为 TFRecords

事先说句话,如果这是你第一次使用 TFRecords,TensorFlow 高效数据存储的原生格式,或者你需要复习一下,看看这款 Google Colab 笔记本和这里的配套描述。

我们不需要元数据文件,因为一个音频样本的标签就写在它的文件名中。因此,我们可以直接开始将数据写入 TFRecord 文件。为了做到这一点,我们首先导入一些包和几个助手函数:

然后,我们定义一个从磁盘加载音频的函数。为此,我们使用了图书馆图书馆。虽然有其他选择,比如 scipy ,但我发现它最方便。此外,我们可以通过首先在“/”上拆分文件名,然后在“-”上拆分文件名来快速提取标签。最后,我们返回音频数据、其采样率、标签和文件名:

然后,我们定义一个函数,这个函数使得提取的数据准备好被存储。这里,我们将使用前面实现的助手函数。正如我在 TFRecord 格式的介绍性实践指南中所描述的,这些函数用于将整数、浮点、字符串和字节数据准备好写入磁盘。给特性起一个合适的名字有助于我们以后提取它们。最后,我们将它们包装成一个示例对象,就像一个包含一些内容和属性的盒子。下图形象地展示了这一概念:

TFRecord 格式将数据存储为示例对象,可以这么想。图片由作者提供。

我们现在已经定义了两个核心功能:从磁盘加载音频数据,并将其解析为 TFRecord 兼容的格式。因此,我们可以实现两者相结合的方法。为此,我们创建一个 TFRecordWriter 对象,负责将数据写入磁盘,并遍历我们找到的所有音频文件。然后,解析每个音频文件,打包成一个示例对象,并写入一个 TFRecord 文件。

即使数据集相对较大,存储一个完整文件夹的单个 TFRecord 文件也有合适的大小,大约为 550 MB。对于较大的数据集,您希望使用更多的文件。对于这种情况,文档中有一些提示

最后,我们实现下面的 main 函数来迭代所有的折叠:

为了完善我们的小脚本,我们定义了一个参数解析器。它采用开头提到的音频目录和输出目录的路径。如果该目录尚不存在,将创建它:

通过调用 python /path/to/script.py 来运行脚本

在定义了创建 TFRecord 文件的脚本之后,我们可能希望稍后读回这些文件。这就是我们现在要实现的。

从 TFRecords 读取音频数据

一旦做了几次,从 TFRecord 文件中获取数据实际上非常简单。我们只需颠倒存储过程。之前,我们将名为 srleny等特征放入盒子中。因此,我们使用相同的名称来获取数据。

唯一需要注意的是音频数据。因为它是一个数组,所以我们要对它进行整形。这就是我们存储 len 属性的原因。类似地,文件名必须被解析成一个字符串:

我们使用下面的函数来读取一个或多个 TFRecord 文件的内容。它返回一个数据集对象:

我们可以使用 for 循环迭代前几个元素,检查它们是否有错误。每个样本由四部分组成:实际音频数据、标签、采样速率和原始文件名称:

一个警告是不均衡的文件持续时间。常规批处理操作将会失败。所以,我们要垫批。我们可以通过以下方式做到这一点:

注意:我们将样本填充到 178017 个条目,因为最长的音频文件略超过 4 秒,我们不想截断这些数据。所有其他文件的最大输出时间为 4 秒* 44100 Hz = 176400 个样本。

就是这样!现在就看你的了。

摘要

在这篇文章中,我们将 UrbanSound8K 数据集解析为 TFRecord。为此,我们使用了 librosa python 库和几个助手函数。然后,我们迭代预定义的折叠,并分别解析每个折叠。

最后,我们提取了数据并填充了一批数据。这给了我们一个可以迭代的数据集。

文学

[1] J. Salamon、C. Jacoby 和 J. P. Bello,“城市声音研究的数据集和分类法http://www.justinsalamon.com/uploads/4/3/9/4/4394963/salamon_urbansound_acmmm14.pdf”,第 22 届 ACM 多媒体国际会议,美国奥兰多,2014 年 11 月。

谷歌云平台简介

原文:https://towardsdatascience.com/part-1-road-to-google-cloud-professional-data-engineer-introduction-to-the-google-cloud-7abbd3411f3a

数据|工程|技术|云

第 1 部分——谷歌云专业数据工程师之路

拉杰什瓦尔·巴楚在 Unsplash 上的照片

目前正在准备考谷歌云专业数据工程师认证。我将在博客上记录我在这次旅程中的经历,以帮助其他感兴趣的人找到通过考试所需的信息。

这个系列将会有很多部分——我还不知道有多少。

当我浏览我在网上找到的不同课程材料和资源时,我会在这里记下笔记。这些博客帖子仅仅是我在吸收内容时对这些话题的理解,所以请不要轻信任何事情。这些帖子不应该作为你的学习材料,而应该作为帮助你组织准备的指针。

谷歌云平台

作为前三大云服务提供商之一,谷歌云平台(GCP)包含大量大数据和机器学习产品和服务。

GCP 的四个主要层。图片由作者提供。

计算实例

这些资源或服务将提供一些处理能力。这通常是指 CPU、TPU 和 RAM,比如一个虚拟机。

储存;储备

云和桌面计算的主要区别之一是,在云系统中,存储独立于计算。您不希望将连接到计算实例的磁盘视为您可以处理和存储多少数据的限制。

我们的首要任务是获取实例中的数据,并根据需要对其进行转换(即构建适当的管道)。

GCP 存储的一个简单例子是弹性桶存储。弹性储物有 4 种选择。

  1. 标准 —用于经常访问的数据
  2. 近线 —用于每月访问的数据
  3. Coldline —用于每季度访问的数据
  4. 归档 —用于数据备份

网络

谷歌的网络在全球 100 多个接入点(PoP)与公共互联网互联。所有请求都从最近的边缘 PoP 发送,以确保最低的延迟。

安全性

大多数较低层次的安全完全由 GCP 处理。这包括硬件的物理安全性、数据的完整性以及网络的完整性。

作为 GCP 用户,我们有权管理用户访问和数据保护。Cloud IAM 帮助您实施这些安全策略。

GCP 会自动加密静态存储的数据。BigQuery 表中的数据使用数据加密密钥加密。然后使用密钥加密密钥对这些密钥进行加密(这个过程称为信封加密)。

谷歌云平台资源层级

GCP 由四个主要部分组成:

  1. 资源——整个 GCP 生态系统中颗粒最大的物体。这些是我们使用的实际服务和流程,如 BigQuery、数据存储或计算实例。这些资源必须属于特定的项目。
  2. 项目这些是基层组织实体。他们允许我们创建和使用资源或服务,管理他们的账单,并控制他们的权限。他们合乎逻辑地组织所有 GCP 资源。可以创建、销毁项目,也可以从意外删除中恢复项目。
  3. 文件夹项目的集合。要有文件夹,我们必须有一个组织。
  4. 组织整个 GCP 层级的根节点。它允许我们设置适用于在我们企业内创建的所有项目和文件夹的策略。我们还可以通过这个级别的策略来微调访问控制(企业范围的)。

谷歌云平台资源层级概述。图片作者。

进一步阅读清单

你喜欢这篇文章吗?如果是,请考虑订阅我的电子邮件列表,以便在我发布新内容时得到通知。

https://david-farrugia.medium.com/subscribe

另外,考虑成为 Medium 的会员,支持我和你喜欢的其他作家。每月 5 美元,你就可以无限制地阅读 Medium 上的每一篇文章。

https://david-farrugia.medium.com/membership

想给我买杯咖啡吗?

https://paypal.me/itsdavidfarrugia?country.x=MT&locale.x=en_US

想联系吗?

我很想听听你对这个话题的想法,或者其他什么。如果你想和我联系,请发电子邮件到 davidfarrugia53@gmail.com。

Linkedin——Twitter

第 11 部分:用户引导的主题搜索

原文:https://towardsdatascience.com/part-11-user-guided-motif-search-d3d317caf9ea

带有 STUMPY 的注释向量

(图片由凯利·西克玛提供)

整体大于部分之和

(图片由作者提供)

STUMPY 是一个强大且可扩展的 Python 库,用于现代时间序列分析,在其核心,有效地计算出一种叫做矩阵轮廓的东西。这个多部分系列的目标是解释什么是 matrix profile,以及如何开始利用 STUMPY 完成所有现代时间序列数据挖掘任务!

注:这些教程最初出现在 STUMPY 文档 中。

第 1 部分:矩阵轮廓图
第 2 部分: STUMPY 基础知识
第 3 部分:时间序列链
第 4 部分:语义分割
第 5 部分:用 STUMPY 快速近似矩阵轮廓图
第 6 部分:用于流式时间序列数据的矩阵轮廓图
第 7 部分:用 STUMPY 快速模式搜索 10: 发现多维时间序列模体
第 11 部分:用户引导的模体搜索
第 12 部分:机器学习的矩阵轮廓

以用户引导的方式搜索主题

本教程总结了 Matrix Profile V 论文的发现,并复制了一些已展示的案例研究。

STUMPY 基础教程中所述,时间序列主题是一个时间序列中近似重复的子序列。虽然模体发现的概念对许多领域至关重要,但模体发现的过程在现实世界中通常不像我们希望的那样清晰,并且在模体发现的大多数实际应用中,我们发现一些模体比其他模体更令人满意。例如,考虑一下亚马逊客户评论数据集,它包含了来自亚马逊网站的几百万条客户评论。如果我们要结合所有的文字评论来揭示平台上最常用的单词,不出所料,我们会发现单词“Amazon”在列表的顶部,可能紧随其后的是其他一些更常用的单词,如“the”、“and”和“is”。虽然这个结果是显而易见的,但肯定是没有用的。为了产生更有见地的结果,我们需要过滤结果,排除不太理想的或"停用词,为更理想的结果让路。

当涉及到时间序列基序发现时,我们可以使用如上所述的类似方法。虽然我们不能“过滤”时间序列,但我们可以支持或“引导”识别更有趣的主题,惩罚不太有趣的主题。本教程将简要介绍注释向量,并探讨它们在引导的基序搜索中的作用。

什么是注释向量?

注释向量包含 0.0 和 1.0 之间(包括 0.0 和 1.0)的实数值,可用于区分时间序列中的重要/不重要子序列(0 表示不需要的子序列,1 表示需要的子序列)。时间序列的注释向量可以与矩阵分布图结合,形成校正矩阵分布图,我们可以用它来识别所需的基序。可以使用以下公式获得校正的矩阵分布:

CMP[I]= MP[I]+(1 AV[I])×max(MP)

其中 CMP 是校正的矩阵分布,MP 是原始未改变的矩阵分布,AV 是注释向量,max(MP)是原始矩阵分布的最大值。本质上,该公式通过将不期望的距离移向最大可允许的矩阵分布值 max(MP)来转换原始矩阵分布,从而消除/削弱那些作为潜在基序的相应子序列。

入门指南

让我们导入加载、分析和绘制数据所需的包。

**%matplotlib** inline**import** numpy **as** np
**import** pandas **as** pd
**import** matplotlib.pyplot **as** plt
**from** matplotlib.patches **import** Rectangle
**import** stumpyplt**.**style**.**use**(**'https://raw.githubusercontent.com/TDAmeritrade/stumpy/main/docs/stumpy.mplstyle'**)**

血红蛋白浓度数据集

考虑由加州大学洛杉矶分校医学中心制作的 fNIRS 脑成像数据集fNIRS ,或功能近红外光谱,是一种光学成像技术,用于测量整个大脑的血红蛋白浓度。

df **=** pd**.**read_csv**(**'https://zenodo.org/record/5045218/files/hemoglobin.csv?download=1'**)**
df **=** df**[6000:14000]**
df **=** df**.**reset_index**(**drop**=True)**
df**.**head**()** Hemoglobin Concentration   Sensor Acceleration
0                    4578.0                7861.0
1                    4579.0                8008.0
2                    4580.0                7959.0
3                    4581.0                7959.0
4                    4582.0                7959.0

数据集包含两列:血红蛋白浓度和传感器加速度数据,后者我们将在后面讨论。我们可以先将血红蛋白浓度时间序列可视化。

plt**.**plot**(**df**[**'Hemoglobin Concentration'**])**
plt**.**xlabel**(**'Time'**,** fontsize**=**"20"**)**
plt**.**ylabel**(**'Intensity'**,** fontsize**=**"20"**)**
plt**.**title**(**'Hemoglobin Concentration'**,** fontsize**=**"30"**)**
plt**.**show**()**

(图片由作者提供)

我们可以使用stumpy.stump函数计算该时间序列的矩阵轮廓,并确定其最近邻的索引位置:

m **=** **600**
mp **=** stumpy**.**stump**(**df**[**'Hemoglobin Concentration'**],** m**)**
motif_idx **=** np**.**argmin**(**mp**[:,** **0])**
nn_idx **=** mp**[**motif_idx**,** **1]**

现在我们知道了基序/最近邻对的索引位置,我们可以将它们一起可视化:

fig**,** axs **=** plt**.**subplots**(2,** sharex**=True,** gridspec_kw**={**'hspace'**:** **0})**
plt**.**suptitle**(**'Motif (Pattern) Discovery'**,** fontsize**=**'30'**)**axs**[0].**plot**(**df**[**'Hemoglobin Concentration'**])**
axs**[0].**set_ylabel**(**'Intensity'**,** fontsize**=**'20'**)**
rect **=** Rectangle**((**motif_idx**,** **4000),** m**,** **4000,** facecolor**=**'lightgrey'**)**
axs**[0].**add_patch**(**rect**)**
rect **=** Rectangle**((**nn_idx**,** **4000),** m**,** **4000,** facecolor**=**'lightgrey'**)**
axs**[0].**add_patch**(**rect**)**
axs**[1].**set_xlabel**(**'Time'**,** fontsize **=**'20'**)**
axs**[1].**set_ylabel**(**'Matrix Profile'**,** fontsize**=**'20'**)**
axs**[1].**axvline**(**x**=**motif_idx**,** linestyle**=**"dashed"**)**
axs**[1].**axvline**(**x**=**nn_idx**,** linestyle**=**"dashed"**)**
axs**[1].**plot**(**mp**[:,** **0])**
plt**.**show**()**plt**.**show**()**

(图片由作者提供)

没有进一步的上下文,结果看起来就像我们想要的一样。识别的两个子序列(灰色)实际上是最接近的匹配。

然而,事实证明,上面确定的基序/最近邻对属于时间序列中没有医学意义的一段。大脑成像传感器在这段时间内采集到的只是由测试对象的身体运动引起的红外辐射的变化,而不是由大脑的血红蛋白浓度引起的。这可以通过可视化伴随的传感器加速度数据来验证,该数据揭示了 4000 和 7000 时间点之间的快速移动:

fig**,** axs **=** plt**.**subplots**(2,** sharex**=True,** gridspec_kw**={**'hspace'**:** **0})**axs**[0].**plot**(**df**[**'Hemoglobin Concentration'**])**
axs**[0].**set_ylabel**(**'Intensity'**,** fontsize**=**"20"**)**
axs**[0].**set_title**(**'Hemoglobin Concentration'**,** fontsize**=**"30"**)**axs**[1].**plot**(**df**[**'Sensor Acceleration'**])**
axs**[1].**set_ylabel**(**"Sensor\nAcceleration"**,** fontsize**=**"20"**)**
axs**[1].**set_xlabel**(**"Time"**,** fontsize**=**"20"**)**
rect **=** Rectangle**((3500,** **6200),** **3750,** **3000,** facecolor**=**'lightgrey'**)**
axs**[1].**add_patch**(**rect**)**plt**.**show**()**

(图片由作者提供)

那么,给定这些数据,我们如何排除这个不可取的高方差区间(灰色)?换句话说,我们如何从这个传感器加速度数据中产生一个注释向量,以便保留期望的区域并移除不期望的区域?

我们可以通过简单地计算传感器加速度数据中的滑动方差来实现这一点:

variance **=** (
    pd**.**Series**(**df**[**'Sensor Acceleration'**])
      .**rolling**(**m**)
      .**var**()
      .**dropna**()
      .**values
)

然后找到具有异常高的局部方差(即,由于测试对象的物理运动)的间隔,并且在那些区域中将我们的注释向量设置为零。类似地,如果局部方差小于比如说10000,那么这表示可靠的区域,因此我们在注释向量中将其注释为值 1.0:

annotation_vector **=** **(**variance **<** **10000).**astype**(**np**.**float64**)**

请注意,注释向量和矩阵轮廓应该具有完全相同的长度。接下来,让我们结合这些信息,看看它是什么样子的:

fig**,** axs **=** plt**.**subplots**(3,** sharex**=True,** gridspec_kw**={**'hspace'**:** **0})**
plt**.**suptitle**(**"Annotation Vector Generation"**,** fontsize**=**"30"**)**axs**[0].**plot**(**df**[**"Sensor Acceleration"**])**
axs**[0].**set_ylabel**(**"Sensor\nAcceleration"**,** fontsize**=**"20"**)**
axs**[1].**plot**(**variance**)**
axs**[1].**set_ylabel**(**"Variance"**,** fontsize**=**"20"**)**
axs**[2].**plot**(**annotation_vector**)**
axs**[2].**set_ylabel**(**"Annotation\nValue"**,** fontsize**=**"20"**)**
axs**[2].**set_xlabel**(**'Time'**,** fontsize**=**"20"**)**plt**.**show**()**

(图片由作者提供)

太好了!现在我们有了一个注释向量,可以用来排除物理运动的区域,并且可以使用前面介绍的公式来计算校正后的矩阵轮廓:

corrected_mp **=** mp**[:,** **0]** **+** **((1** **-** annotation_vector**)** ***** np**.**max**(**mp**[:,** **0]))**

我们可以绘制校正后的矩阵分布图,以查看注释向量如何改变原始矩阵分布图:

fig**,** axs **=** plt**.**subplots**(2,** sharex**=True,** gridspec_kw**={**'hspace'**:** **0})**
plt**.**suptitle**(**'Matrix Profile Comparison'**,** fontsize**=**"30"**)**axs**[0].**plot**(**mp**[:,** **0],** linestyle**=**'--'**)**
axs**[0].**plot**(**corrected_mp**,** color**=**'C1'**,** linewidth**=3)**
axs**[0].**set_ylabel**(**"(Corrected)\nMatrix Profile"**,** fontsize**=**"20"**)**axs**[1].**plot**(**annotation_vector**)**
axs**[1].**set_xlabel**(**"Time"**,** fontsize**=**"20"**)**
axs**[1].**set_ylabel**(**"Annotation\nValue"**,** fontsize**=**"20"**)**plt**.**show**()**

(图片由作者提供)

正如我们所看到的,校正后的矩阵轮廓(橙色线)在身体运动区域周围升高。由于惩罚区域(蓝色实线),原始未改变的矩阵轮廓(虚线)中的最低点不再处于测试对象物理移动的区间中。现在,我们可以用这个校正后的矩阵分布图代替我们原来的矩阵分布图来定位我们的顶部基元,就像我们以前做的那样:

motif_idx **=** np**.**argmin**(**corrected_mp**)**
nn_idx **=** mp**[**motif_idx**,** **1]**

并绘制结果:

fig**,** axs **=** plt**.**subplots**(2,** sharex**=True,** gridspec_kw**={**'hspace'**:** **0})**
plt**.**suptitle**(**'Motif (Pattern) Discovery'**,** fontsize**=**'30'**)**axs**[0].**plot**(**df**[**'Hemoglobin Concentration'**])**
axs**[0].**set_ylabel**(**'Intensity'**,** fontsize**=**'20'**)**
rect **=** Rectangle**((**motif_idx**,** **4000),** m**,** **4000,** facecolor**=**'lightgrey'**)**
axs**[0].**add_patch**(**rect**)**
rect **=** Rectangle**((**nn_idx**,** **4000),** m**,** **4000,** facecolor**=**'lightgrey'**)**
axs**[0].**add_patch**(**rect**)**
axs**[1].**set_xlabel**(**'Time'**,** fontsize **=**'20'**)**
axs**[1].**set_ylabel**(**'Corrected\nMatrix Profile'**,** fontsize**=**'20'**)**
axs**[1].**axvline**(**x**=**motif_idx**,** linestyle**=**"dashed"**)**
axs**[1].**axvline**(**x**=**nn_idx**,** linestyle**=**"dashed"**)**
axs**[1].**plot**(**corrected_mp**)**
plt**.**show**()**plt**.**show**()**

(图片由作者提供)

正如我们所看到的,基序/最近邻对(灰色)已经向左移动到对应于更理想数据的小峰附近,因此,我们已经成功地从基序搜索中排除了物理移动区域!

在上面的例子中,我们产生了一个注释向量,它完全忽略了我们不需要的区域。然而,有时我们可能不想从基序搜索中完全去除一个区域,而仅仅是“阻止/削弱”它。下一个例子探索了这种情况。

眼电图数据集

考虑包含左眼电势数据的 EOG 数据集,以 50 Hz 采样。 EOG ,或眼电描记术,是一种用于测量人眼前后之间存在的电位的技术。我们可以下载并可视化数据,以更好地了解其趋势:

df **=** pd**.**read_csv**(**'https://zenodo.org/record/5045252/files/eog.csv?download=1'**)**
df**.**head**()** Electric Potential
0              –0.980
1               2.941
2               2.941
3               2.941
4               2.941 plt**.**plot**(**df**[**'Electric Potential'**])**
plt**.**xlabel**(**'Time'**,** fontsize**=**"20"**)**
plt**.**ylabel**(**'Voltage'**,** fontsize**=**"20"**)**plt**.**show**()**

(图片由作者提供)

你应该注意到图中的一些点有平的顶部或底部。从医学上讲,这并不能反映现实。平坦的顶部和底部只是剪切区域,因为测量值超出了浮点数的测量范围。尽管如此,我们可以盲目地计算矩阵轮廓并定位最佳基序:

m **=** **450**
mp **=** stumpy**.**stump**(**df**[**'Electric Potential'**],** m**)**
motif_idx **=** np**.**argmin**(**mp**[:,** **0])**
nn_idx **=** mp**[**motif_idx**,** **1]**

并将结果可视化:

fig**,** axs **=** plt**.**subplots**(2,** sharex**=True,** gridspec_kw**={**'hspace'**:** **0})**
plt**.**suptitle**(**'Motif (Pattern) Discovery'**,** fontsize**=**'30'**)**axs**[0].**plot**(**df**[**'Electric Potential'**])**
axs**[0].**set_ylabel**(**'Voltage'**,** fontsize**=**'20'**)**
rect **=** Rectangle**((**motif_idx**,** **-150),** m**,** **300,** facecolor**=**'lightgrey'**)**
axs**[0].**add_patch**(**rect**)**
rect **=** Rectangle**((**nn_idx**,** **-150),** m**,** **300,** facecolor**=**'lightgrey'**)**
axs**[0].**add_patch**(**rect**)**
axs**[1].**set_xlabel**(**'Time'**,** fontsize **=**'20'**)**
axs**[1].**set_ylabel**(**'Matrix Profile'**,** fontsize**=**'20'**)**
axs**[1].**axvline**(**x**=**motif_idx**,** linestyle**=**"dashed"**)**
axs**[1].**axvline**(**x**=**nn_idx**,** linestyle**=**"dashed"**)**
axs**[1].**plot**(**mp**[:,** **0])**
plt**.**show**()**plt**.**show**()**

(图片由作者提供)

不出所料,基序/最近邻对包含时间序列中最大的平顶。这是有意义的,因为两个平顶之间的距离很小。

当然,因为平坦的顶部或底部是电位的实际值不清楚的区域,我们想使这成为一个不太理想的主题。但之前的血红蛋白示例与当前示例的不同之处在于,我们不想完全忽略这些削波区域。相反,我们只想稍微惩罚他们。我们通过构造一个实值的注释向量来实现这一点,这与前面的例子相反,在前面的例子中,注释向量只限于 0 或 1。因此,对于每个子序列,我们计算等于全局最小值或最大值的值的数量。计数越高,我们为该子序列设置的注释向量值就越低,反之亦然:

global_min **=** df**[**"Electric Potential"**].**min**()**
global_max **=** df**[**"Electric Potential"**].**max**()**
count_minmax **=** **lambda** x**:** np**.**count_nonzero**(**x **==** global_max**)** **+** np**.**count_nonzero**(**x **==** global_min**)**
annotation_vector **=** df**[**"Electric Potential"**].**rolling**(**m**).**apply**(**count_minmax**).**dropna**().**values
annotation_vector **=** **1** **-** **(**annotation_vector **-** np**.**min**(**annotation_vector**))** **/** np**.**max**(**annotation_vector**)**fig**,** axs **=** plt**.**subplots**(2,** sharex**=True,** gridspec_kw**={**'hspace'**:** **0})**
axs**[0].**plot**(**df**[**"Electric Potential"**])**
axs**[0].**set_ylabel**(**"Voltage"**,** fontsize**=**"20"**)**
axs**[1].**plot**(**annotation_vector**)**
axs**[1].**set_ylabel**(**"Annotation\nValue"**,** fontsize**=**"20"**)**
axs**[1].**set_xlabel**(**'Time'**,** fontsize**=**"20"**)**
plt**.**show**()**

(图片由作者提供)

现在我们有了一个注释向量,它在包含平顶或平底的子序列中较低,在没有平顶或平底的子序列中较高。与为血红蛋白浓度示例生成的注释向量相比,该注释向量可以取 0 到 1 之间的任何实数值。我们现在可以像以前一样继续,并使用该注释向量来生成校正的矩阵轮廓:

corrected_mp **=** mp**[:,** **0]** **+** **((1** **-** annotation_vector**)** ***** np**.**max**(**mp**[:,** **0]))**fig**,** axs **=** plt**.**subplots**(2,** sharex**=True,** gridspec_kw**={**'hspace'**:** **0})**
plt**.**suptitle**(**'Matrix Profile Comparison'**,** fontsize**=**"30"**)**axs**[0].**plot**(**mp**[:,** **0],** linestyle**=**'--'**)**
axs**[0].**plot**(**corrected_mp**,** color**=**'C1'**,** linewidth**=3)**
axs**[0].**set_ylabel**(**"(Corrected)\nMatrix Profile"**,** fontsize**=**"20"**)**axs**[1].**plot**(**annotation_vector**)**
axs**[1].**set_xlabel**(**"Time"**,** fontsize**=**"20"**)**
axs**[1].**set_ylabel**(**"Annotation\nValue"**,** fontsize**=**"20"**)**plt**.**show**()**

(图片由作者提供)

当我们比较原始未改变的矩阵分布(虚线)和校正的矩阵分布(橙色线)时,我们清楚地看到具有最低矩阵分布值的子序列已经改变。现在,我们准备使用校正后的矩阵轮廓再次计算最佳基元:

motif_idx **=** np**.**argmin**(**corrected_mp**)**
nn_idx **=** mp**[**motif_idx**,** **1]**fig**,** axs **=** plt**.**subplots**(2,** sharex**=True,** gridspec_kw**={**'hspace'**:** **0})**
plt**.**suptitle**(**'Motif (Pattern) Discovery'**,** fontsize**=**'30'**)**axs**[0].**plot**(**df**[**'Electric Potential'**])**
axs**[0].**set_ylabel**(**'Voltage'**,** fontsize**=**'20'**)**
rect **=** Rectangle**((**motif_idx**,** **-150),** m**,** **300,** facecolor**=**'lightgrey'**)**
axs**[0].**add_patch**(**rect**)**
rect **=** Rectangle**((**nn_idx**,** **-150),** m**,** **300,** facecolor**=**'lightgrey'**)**
axs**[0].**add_patch**(**rect**)**
axs**[1].**set_xlabel**(**'Time'**,** fontsize **=**'20'**)**
axs**[1].**set_ylabel**(**'Matrix Profile'**,** fontsize**=**'20'**)**
axs**[1].**axvline**(**x**=**motif_idx**,** linestyle**=**"dashed"**)**
axs**[1].**axvline**(**x**=**nn_idx**,** linestyle**=**"dashed"**)**
axs**[1].**plot**(**corrected_mp**)**
plt**.**show**()**plt**.**show**()**

(图片由作者提供)

正如我们所见,新的图案(灰色)不再位于平顶或平底区域,我们再次成功引导我们的图案搜索找到更有趣的图案。

摘要

就是这样!现在,您已经学习了使用注释向量进行引导式模体搜索的基础知识,现在您可以利用这种简单的方法并在自己的项目中使用它。编码快乐!

资源

矩阵简介 I
矮胖基础知识
矩阵简介 V

第十部分:发现多维时间序列主题 | 第十二部分机器学习的矩阵轮廓

第 12 部分:机器学习的矩阵轮廓

原文:https://towardsdatascience.com/part-12-matrix-profiles-for-machine-learning-2dfd98d7ff3f

在 STUMPY 发现 Shapelet

(图片由 Jason Leung 提供)

整体大于部分之和

(图片由作者提供)

STUMPY 是一个强大且可扩展的 Python 库,用于现代时间序列分析,在其核心,有效地计算出一种叫做矩阵轮廓的东西。这个多部分系列的目标是解释什么是 matrix profile,以及如何开始利用 STUMPY 完成所有现代时间序列数据挖掘任务!

注:这些教程最初出现在 STUMPY 文档 中。

第 1 部分:矩阵轮廓图
第 2 部分: STUMPY 基础知识
第 3 部分:时间序列链
第 4 部分:语义分割
第 5 部分:用 STUMPY 快速近似矩阵轮廓图
第 6 部分:用于流式时间序列数据的矩阵轮廓图
第 7 部分:用 STUMPY 快速模式搜索 10: 发现多维时间序列模体
第 11 部分:用户引导的模体搜索
第 12 部分:机器学习的矩阵轮廓

Shapelet 发现

本教程探讨了来自研究论文的“Shapelet Discovery”案例研究:时间序列数据挖掘的瑞士军刀:你可以用矩阵概要和十行代码做的十件有用的事情】(见 3.7 节)。此外,您可能想要参考矩阵概要 I时间序列 Shapelets:一种新的数据挖掘原语论文以获得更多信息和其他相关示例。

什么是 Shapelet?

非正式地,时间序列“shapelets”是时间序列子序列,在某种意义上,它最大限度地代表了一个类。例如,想象一下,如果您有一个时间序列来跟踪您家中大型电器五年来每秒钟的耗电量。每次你运行干衣机、洗碗机或空调时,你的电表都会记录消耗的电力,只需查看时间序列,你就可以将电力消耗“特征”(即形状、持续时间、最大能源使用量等)与每台设备关联起来。这些模式可能显而易见,也可能很微妙,正是它们独特的“成形”时序子序列让您能够区分每个电器类别。因此,这些所谓的 shapelet 可用于对包含 shape let 出现的未标记时间序列进行分类。如果这听起来有点行话,不要担心,因为通过下面的例子,一切都会变得更清楚。

最近的研究(见上文)表明,矩阵配置文件可以用来有效地识别特定类的 shape let,因此,在本教程中,我们将建立在我们的矩阵配置文件知识之上,并演示如何使用 STUMPY 轻松地发现有趣的 shape let,只需几行额外的代码。

入门指南

让我们导入加载、分析和绘制数据所需的包,然后构建简单的决策树模型。

**%**matplotlib inline**import** stumpy
**import** pandas **as** pd
**import** numpy **as** np
**import** matplotlib.pyplot **as** plt
**from** sklearn **import** tree
**from** sklearn **import** metricsplt.style.use('https://raw.githubusercontent.com/TDAmeritrade/stumpy/main/docs/stumpy.mplstyle')

加载枪口数据集

该数据集是一个运动捕捉时间序列,用于跟踪演员右手的运动,包含两个类:

1\. Gun

2\. Point

Gun类中,演员从臀部安装的枪套中取出一把实体枪,将枪对准目标大约一秒钟,然后将枪放回枪套,并将手放松到身体两侧。在Point类中,演员将枪放在身体两侧,相反,他们将食指指向目标(即没有枪)大约一秒钟,然后将手放回身体两侧。对于这两个类,演员的右手质心被跟踪,以代表其运动。

下面,我们将检索原始数据,将它们分成gun_dfpoint_df,然后,对于每个相应的类,将所有单独的样本连接成一个长时间序列。此外,我们通过在每个样本后附加一个NaN值,为每个样本建立一个清晰的边界(即样本开始和结束的地方)。这有助于确保所有矩阵分布计算不会返回跨越多个样本的人为子序列:

train_df **=** pd.read_csv("https://zenodo.org/record/4281349/files/gun_point_train_data.csv?download=1")gun_df **=** train_df[train_df['0'] **==** 0].iloc[:, 1:].reset_index(drop**=True**)gun_df **=** (gun_df.assign(NaN**=**np.nan)
                .stack(dropna**=False**)
                .to_frame()
                .reset_index(drop**=True**)
                .rename({0: "Centroid Location"}, axis**=**'columns')
)point_df **=** train_df[train_df['0'] **==** 1].iloc[:, 1:].reset_index(drop**=True**)point_df **=** (point_df.assign(NaN**=**np.nan)
                   .stack(dropna**=False**)
                   .to_frame()
                   .reset_index(drop**=True**)
                   .rename({0: "Centroid Location"}, axis**=**'columns')
)

可视化枪口数据集

接下来,让我们绘制我们的数据,并想象没有枪时和有枪时的运动差异:

fig, axs **=** plt.subplots(2, sharex**=True**, gridspec_kw**=**{'hspace': 0})
plt.suptitle('Point vs. Gun', fontsize**=**'30')
plt.xlabel('Time', fontsize **=**'20')
fig.text(0.09, 0.5, 'Centroid Location', va**=**'center', rotation**=**'vertical', fontsize**=**'20')axs[0].plot(point_df, label**=**"Point")
axs[0].legend()
axs[1].plot(gun_df, color**=**"C1", label**=**"Gun")
axs[1].legend()
plt.show()

(图片由作者提供)

在这个数据集中,您将看到PointGun分别有 26 个和 24 个样本。这两个类都包含窄/宽样本和垂直移动的质心位置,这使得区分它们具有挑战性。您是否能够识别出PointGun之间的任何细微差异(即 shapelets ),从而帮助您区分这两个类别?

事实证明,矩阵轮廓可能有助于我们自动识别潜在的形状!

使用矩阵轮廓查找候选 Shapelets

回想一下我们的寻找跨两个时间序列的保守模式教程,从单个时间序列𝑇𝐴计算的矩阵分布图𝑃𝐴𝐴被称为“自连接”,它允许你识别𝑇𝐴.内的保守子序列然而,通过两个不同的时间序列(𝑇𝐴和𝑇𝐵)计算的矩阵分布图(𝑃𝐴𝐵)通常被称为“AB-join”。本质上,AB-join 将𝑇𝐴的所有子序列与𝑇𝐵的所有子序列进行比较,以确定𝑇𝐴的任何子序列是否也能在𝑇𝐵.找到换句话说,产生的矩阵轮廓,𝑃𝐴𝐵,用它在𝑇𝐵.的最佳匹配子序列来注释在𝑇𝐴的每个子序列相比之下,如果我们交换时间序列并计算𝑃𝐵𝐴(即“BA-join”),那么这会用𝑇𝐴.的最近邻子序列来注释𝑇𝐵的每个子序列

根据 Matrix Profile I 论文的 h 部分,声称我们可以利用 Matrix Profile 来试探性地“建议”候选 shape let,并且主要直觉是,如果在Gun类中存在区别性图案,而在Point类中不存在区别性图案(反之亦然),那么我们将期望在它们相应的𝑃(𝑃𝑜𝑖𝑛𝑡,𝑃𝑜𝑖𝑛t)- 𝑃(𝑃𝑜𝑖𝑛𝑡,𝐺𝑢𝑛(或𝑃(𝐺𝑢𝑛,𝐺𝑢n)- 𝑃(𝐺𝑢𝑛,𝑃𝑜𝑖𝑛t)矩阵 Profile 中看到一个或多个“凸起”,并且高度上的任何显著差异可能是良好候选 shape let 的强有力指示。

因此,首先,让我们计算矩阵轮廓,𝑃(𝑃𝑜𝑖𝑛𝑡,𝑃𝑜𝑖𝑛t(自连接)和𝑃(𝑃𝑜𝑖𝑛𝑡,𝐺𝑢n(a B-连接),并且,为了简单起见,我们使用子序列长度m = 38,这是为该数据集报告的最佳 shapelet 的长度:

m **=** 38P_Point_Point **=** stumpy.stump(point_df["Centroid Location"], m)[:, 0].astype(float)P_Point_Gun **=** stumpy.stump(
    point_df["Centroid Location"], 
    m, 
    gun_df["Centroid Location"], 
    ignore_trivial**=False** )[:, 0].astype(float)

由于我们的时间序列中有一些np.nan值,矩阵配置文件的输出将包含几个np.inf值。因此,我们将通过将其转换为np.nan来手动调整:

P_Point_Point[P_Point_Point **==** np.inf] **=** np.nan
P_Point_Gun[P_Point_Gun **==** np.inf] **=** np.nan

现在我们将它们一个叠一个地绘制出来:

plt.plot(P_Point_Point, label**=**"$P_{Point,Point}$")
plt.plot(P_Point_Gun, color**=**"C1", label**=**"$P_{Point,Gun}$")
plt.xlabel("Time", fontsize**=**"20")
plt.ylabel("Matrix Profile", fontsize**=**"20")
plt.legend()
plt.show()

(图片由作者提供)

接下来,我们可以通过绘制𝑃(𝑃𝑜𝑖𝑛𝑡,𝑃𝑜𝑖𝑛t 和𝑃(𝑃𝑜𝑖𝑛𝑡,𝐺𝑢n).之间的差异来突出两个矩阵图之间的主要偏差直觉上,𝑃(𝑃𝑜𝑖𝑛𝑡,𝑃𝑜𝑖𝑛t)将比𝑃(𝑃𝑜𝑖𝑛𝑡,𝐺𝑢n)小,因为我们期望同一类中的子序列比不同类中的子序列更相似:

P_diff **=** P_Point_Gun **-** P_Point_Point
idx **=** np.argpartition(np.nan_to_num(P_diff), **-**10)[**-**10:]  *# get the top 10 peak locations in P_diff* plt.suptitle("", fontsize**=**"30")
plt.xlabel("Time", fontsize**=**"20")
plt.ylabel("$P_{diff} = P_{Point,Gun} - P_{Point, Point}$", fontsize**=**"20")
plt.plot(idx, P_diff[idx], color**=**"C1", marker**=**"o", linewidth**=**0, markersize**=**10)
plt.plot(P_diff)
plt.show()

(图片由作者提供)

𝑃_𝑑𝑖𝑓𝑓(橙色圆圈)中的峰值是良好 shapelet 候选者的指标,因为它们暗示了在它们自己的类(即,𝑃(𝑃𝑜𝑖𝑛𝑡,𝑃𝑜𝑖𝑛t 自连接)中非常保守的模式,但是也与它们在另一类(即,𝑃(𝑃𝑜𝑖𝑛𝑡,𝐺𝑢n a B-连接)中最接近的匹配非常不同。有了这些知识,让我们提取发现的 shapelets,然后绘制它们:

point_shapelets **=** []
**for** i **in** idx:
    shapelet **=** point_df.iloc[i : i **+** m, 0]
    point_shapelets.append(shapelet)plt.xlabel("Time", fontsize**=**"20")
plt.ylabel('Centroid Location', fontsize**=**'20')
plt.plot(point_df, label**=**"Point")**for** i, shapelet **in** zip(idx, point_shapelets):
    plt.plot(range(i, i **+** m), shapelet, color**=**"C1", linewidth**=**3.0)plt.legend()
plt.show()

(图片由作者提供)

基于这些候选形状(橙色),似乎这两个类别之间的主要区别因素在于演员的手如何将(假想的)枪放回枪套,然后在演员身边放松。根据原作者的说法,Point类“有一个‘下降’,演员将手放在身体两侧,这是惯性使她的手有点太远,她被迫纠正这一点——作者将这一现象称为“过度”。

构建一个简单的分类器

现在我们已经确定了 10 个候选 shape let,让我们基于这些 shape let 构建 10 个独立的决策树模型,看看它们能在多大程度上帮助我们区分Point类和Gun类。幸运的是,这个数据集包括一个训练集(如上)和一个独立的测试集,我们可以用它们来评估我们的模型的准确性:

test_df **=** df **=** pd.read_csv("https://zenodo.org/record/4281349/files/gun_point_test_data.csv?download=1")*# Get the train and test targets* y_train **=** train_df.iloc[:, 0]
y_test **=** test_df.iloc[:, 0]

现在,对于我们的分类任务,需要评估每个 shapelets 的预测能力。因此,我们首先计算 shapelet 和每个时间序列或样本中的每个子序列之间的距离轮廓(成对欧几里德距离)。然后,保持最小值,以了解是否在时间序列中找到了 shapelet 的紧密匹配。stumpy.mass功能非常适合这项任务:

**def** distance_to_shapelet(data, shapelet):
    """
    Compute the minimum distance beteen each data sample
    and a shapelet of interest
    """
    data **=** np.asarray(data)
    X **=** np.empty(len(data))
    **for** i **in** range(len(data)):
        D **=** stumpy.mass(shapelet, data[i])
        X[i] **=** D.min() **return** X.reshape(**-**1, 1)clf **=** tree.DecisionTreeClassifier()
**for** i, shapelet **in** enumerate(point_shapelets):
    X_train **=** distance_to_shapelet(train_df.iloc[:, 1:], shapelet)
    X_test **=** distance_to_shapelet(test_df.iloc[:, 1:], shapelet)
    clf.fit(X_train, y_train)
    y_pred **=** clf.predict(X_test.reshape(**-**1, 1))
    print(f"Accuracy for shapelet {i} = {round(metrics.accuracy_score(y_test, y_pred), 3)}")Accuracy for shapelet 0 = 0.867
Accuracy for shapelet 1 = 0.833
Accuracy for shapelet 2 = 0.807
Accuracy for shapelet 3 = 0.833
Accuracy for shapelet 4 = 0.933
Accuracy for shapelet 5 = 0.873
Accuracy for shapelet 6 = 0.873
Accuracy for shapelet 7 = 0.833
Accuracy for shapelet 8 = 0.86
Accuracy for shapelet 9 = 0.913

正如我们所看到的,所有的 Shapelet 都提供了一些合理的预测能力,有助于区分PointGun类,但 Shapelet 4 返回了 93.3%的最佳准确度,该结果准确地再现了发布的结果。太好了!

附加部分—第二类的 Shapelets

作为额外的一点,我们还将从Gun时间序列中提取 shapelets,看看它们是否能为我们的模型增加任何额外的预测能力。该过程与我们在上面解释过的过程相同,只是我们关注的是从gun_df派生的 shapelets,所以我们在这里不打算进行过多的描述:

m **=** 38P_Gun_Gun **=** stumpy.stump(gun_df["Centroid Location"], m)[:, 0].astype(float)
P_Gun_Point **=** stumpy.stump(gun_df["Centroid Location"], m, point_df["Centroid Location"], ignore_trivial**=False**)[:, 0].astype(float)P_Gun_Gun[P_Gun_Gun **==** np.inf] **=** np.nan
P_Gun_Point[P_Gun_Point **==** np.inf] **=** np.nan
P_diff **=** P_Gun_Point **-** P_Gun_Gun
idx **=** np.argpartition(np.nan_to_num(P_diff), **-**10)[**-**10:]  *# get the top 10 peak locations in P_diff* plt.suptitle("", fontsize**=**"30")
plt.xlabel("Time", fontsize**=**"20")
plt.ylabel("$P_{diff} = P_{Gun, Point} - P_{Gun, Gun}$", fontsize**=**"20")
plt.plot(idx, P_diff[idx], color**=**"C1", marker**=**"o", linewidth**=**0, markersize**=**10)
plt.plot(P_diff)
plt.show()

(图片由作者提供)

gun_shapelets **=** []**for** i **in** idx:
    shapelet **=** gun_df.iloc[i : i **+** m, 0]
    gun_shapelets.append(shapelet)
plt.xlabel("Time", fontsize**=**"20")
plt.ylabel('Centroid Location', fontsize**=**'20')
plt.plot(gun_df, label**=**"Gun")**for** i, shapelet **in** zip(idx, gun_shapelets):
    plt.plot(range(i, i **+** m), shapelet, color**=**"C1", linewidth**=**3.0)plt.legend()
plt.show()

(图片由作者提供)

请注意,当存在物理枪时,在Gun绘制动作开始时发现的 shapelets 不像Point样本那样平滑。同样,给Gun重新装上枪套似乎也需要一点微调。

最后,我们构建我们的模型,但是这一次,我们结合了来自Gunshape let 和Pointshape let 的距离特征:

clf **=** tree.DecisionTreeClassifier()
**for** i, (gun_shapelet, point_shapelet) **in** numerate(zip(gun_shapelets, point_shapelets)):
    X_train_gun **=** distance_to_shapelet(train_df.iloc[:, 1:], gun_shapelet)
    X_train_point **=** distance_to_shapelet(train_df.iloc[:, 1:], point_shapelet)
    X_train **=** np.concatenate((X_train_gun, X_train_point), axis**=**1)
    X_test_gun **=** distance_to_shapelet(test_df.iloc[:, 1:], gun_shapelet)
    X_test_point **=** distance_to_shapelet(test_df.iloc[:, 1:], point_shapelet)
    X_test **=** np.concatenate((X_test_gun, X_test_point), axis**=**1)
    clf.fit(X_train, y_train)
    y_pred **=** clf.predict(X_test)
    print(f"Accuracy for shapelet {i} = {round(metrics.accuracy_score(y_test, y_pred), 3)}")Accuracy for shapelet 0 = 0.913
Accuracy for shapelet 1 = 0.853
Accuracy for shapelet 2 = 0.813
Accuracy for shapelet 3 = 0.953
Accuracy for shapelet 4 = 0.933
Accuracy for shapelet 5 = 0.94
Accuracy for shapelet 6 = 0.873
Accuracy for shapelet 7 = 0.867
Accuracy for shapelet 8 = 0.867
Accuracy for shapelet 9 = 0.913

我们可以看到,如果我们包括来自Gun类 Shapelet 和Point类 shape let 的距离,分类器达到 95.3%的准确度(Shapelet 3)!显然,添加与第二类的距离也为模型提供了额外的有用信息。这是一个很好的结果,因为它将结果提高了大约 2%。同样,令人印象深刻的是,所有这些信息都可以通过利用矩阵配置文件“免费”提取。

摘要

就是这样!您刚刚学习了如何利用矩阵配置文件来查找 shapelets,并使用它们来构建机器学习分类器。

参考

时间序列数据挖掘的瑞士军刀:矩阵概要和十行代码可以做的十件有用的事情(参见第 3.7 节)
时间序列 Shapelets:数据挖掘的新原语
STUMPY 文档
STUMPY 矩阵概要 Github 代码库

第 11 部分:用户导向的主题搜索 |第 13 部分即将推出→

分析玻璃数据集—第 1 部分

原文:https://towardsdatascience.com/part-i-analyzing-the-glass-dataset-c556788a496f

使用 Julia 及其生态系统的机器学习

Julia 很快,可以像解释语言一样使用,具有高度的可组合性,但不是面向对象的。它有一个快速增长的生态系统,可以帮助典型的 ML 工作流程的所有方面。

罗宾·范·德·普洛格在 Unsplash 上拍摄的照片

教程概述

这是教程的第一部分,展示了 Julia 的特定语言特性和它的生态系统中的各种高质量的包是如何在一个典型的 ML 工作流中方便地结合使用的。

  • 第一部分"分析玻璃数据集"集中于如何使用ScientificTypesDataFramesStatsBaseStatsPlots等软件包对数据进行预处理、分析和可视化。
  • 第二部分"使用决策树"关注 ML 工作流的核心:如何选择模型,以及如何使用它进行训练、预测和评估。这部分主要靠包MLJ(=MachineLinJulia)收入。
  • 第三部分“如果事情还没有‘准备好使用’”解释了如果可用的包没有提供您需要的所有功能,那么用几行代码创建您自己的解决方案是多么容易。

介绍

每一个 ML 工作流都从分析和预处理用于训练模型的数据开始。这些最初的步骤包括理解数据来自的应用领域内的数据、检查数据质量和识别其统计属性(使用可视化和统计测量)。

在本教程中,我们将使用“玻璃”数据集。这是一个用于教育目的的数据集。它带有 Weka 工具包,可以从不同的网站下载(例如这里)。该数据集的特征包含关于不同玻璃变体的信息(如用于其生产的材料和光学特征),目标变量是产生的玻璃变体(见下文摘录)。

“玻璃”数据集中的几行

数据以一种叫做 ARFF(属性-关系文件格式)的文件格式出现;Weka 工具包使用的一种格式。因此,我们需要一个 Julia 包,它具有读取这种格式的功能:ARFFFiles.jl。因为我们想将数据转换成一个DataFrame以供进一步使用,所以也需要包DataFrames.jlScientificTypes.jl用特定于 ML 的特性扩充了标准的 Julia 类型,最后但同样重要的是,我们需要Downloads.jl包来实现从网站下载数据的功能。

因此,使用这些包、从上述网站下载数据、从磁盘读取数据并将其转换为DataFrame(并使用ScientificTypes)的代码如下所示:

加载“玻璃”数据集并创建“数据框架”

数据集的第一个概述

为了对数据集有一个初步的了解,我们从中获取一些基本信息:

  • sz = size(glass)给出了尺寸:(214, 10),即它有 214 行 10 列。
  • 通过col_names = names(glass),我们得到了DataFrame的列名:
10-element Vector{String}: 
“RI”, “Na”, “Mg”, “Al”, “Si”, “K”, “Ca”, “Ba”, “Fe”, “Type”
  • schema(glass)(来自ScientificTypes)给出了所有列的数据类型。types是 Julia 数据类型,而scitypes是由ScientificTypes引入的抽象层次。这里我们可以看到所有的特征都是Continuous,目标变量是一个标称值(有七个不同的条目)。
schema(glass) -->
┌───────┬───────────────┬──────────────────────────────────┐
│ names │ scitypes      │ types                            │
├───────┼───────────────┼──────────────────────────────────┤
│ RI    │ Continuous    │ Float64                          │
│ Na    │ Continuous    │ Float64                          │
│ Mg    │ Continuous    │ Float64                          │
│ Al    │ Continuous    │ Float64                          │
│ Si    │ Continuous    │ Float64                          │
│ K     │ Continuous    │ Float64                          │
│ Ca    │ Continuous    │ Float64                          │
│ Ba    │ Continuous    │ Float64                          │
│ Fe    │ Continuous    │ Float64                          │
│ Type  │ Multiclass{7} │ CategoricalValue{String, UInt32} │
└───────┴───────────────┴──────────────────────────────────┘

目标变量

现在让我们仔细看看目标变量的特征。glass_types = unique(glass.Type)给出了来自Type列的唯一值列表:

6-element Vector{String}:
 “build wind float”
 “vehic wind float”
 “tableware”
 “build wind non-float”
 “headlamps”
 “containers”

因此,我们假设数据集中有 6 种不同的玻璃类型。但这只是事实的一部分。使用ScientificTypes中的levels(glass.Type)显示实际上有 7 种不同的玻璃类型:

7-element Vector{String}:
 “build wind float”
 “build wind non-float”
 “vehic wind float”
 “vehic wind non-float”
 “containers”
 “tableware”
 “headlamps”

其中只有 6 个出现在玻璃数据集中(计数> 0)。类型‘车辆风非浮动’不出现。关于它的存在的知识被储存在 ARFF 档案中,并被ScientificTypes建立的类型系统保留下来。在这个例子中,我们可以看到使用这种类型系统的附加价值,而不是仅仅依赖语言的本机类型系统(在这个例子中,这可能会欺骗我们)。

在下一步中,对countmap(来自StatsBase)的调用告诉我们,这些类型在目标变量中出现的频率。因此,我们对它们的分布有了一些了解:用于建筑物窗户的玻璃变体是最常见的类型,其次是用于前照灯的玻璃类型:

type_count = countmap(glass.Type) -->Dict{CategoricalArrays.CategoricalValue{String, UInt32}, Int64} with 6 entries:
 “vehic wind float” => 17
 “tableware” => 9
 “build wind non-float” => 76
 “build wind float” => 70
 “headlamps” => 29
 “containers” => 13

一个图表当然比一系列数字更能提供信息。因此,让我们使用Plots包中的条形图来可视化它:

bar(type_count, group = glass_types, legend = false,
 xlabel = “glass types”, xrotation = 20,
 ylabel = “count”)

不同玻璃类型的出现频率[图片由作者提供]

这里我们也注意到,来自ScientifcTypes 的额外信息已经被使用,因此显示“车辆风非浮动”的计数为零。

详细的功能

现在来看特征变量:第一个特征 RI 是所谓的折射率,这是一种光学特性,它告诉我们光穿过材料的速度。其他特征给出了不同元素的百分比,如钠(=拉丁文 Na trium)、镁( MgAlumi um),这些元素被用于生产玻璃。

价值观是否合理?

我们先从技术角度检查一下这些特性的值是否合理。从DataFramesdescribe的调用给了我们一个关于数据集中值的范围的印象:

通过“描述(玻璃)”获得的统计特征

  • RI 在 1.5 左右略有变化。从上面提到的维基百科页面,我们了解到不同种类玻璃的典型 RI 值在 1.49 … 1.69 之间。因此,“glass”数据集中的 RI 值似乎相当合理。
  • 其他特征是百分比。也就是说,它们必须在 0 … 100 的范围内,这适用于我们的数据。
  • 硅是玻璃中最重要的成分(这里> 70%),其次是钠和钙。

我们可以对百分比再做一次质量检查。每种玻璃成分的总和应为 100%。我们使用来自DataFramestransform函数,并在每一行中添加特性 NaFe 的所有值。结果存储在一个名为sumpct的新列中:

glass_wsum = transform(glass, 
                  Between(:Na, :Fe) => ByRow(+) => :sumpct)

查看这一新列并使用StatsBase中的maximumminimum函数,可以发现大多数行的总和并不完全是 100%,但是它们在该值附近有很小的变化。这可以用测量和舍入误差来解释。所以对我们来说没问题。

maximum(glass_wsum.sumpct), minimum(glass_wsum.sumpct) --->(100.09999999999998, 99.02)

统计和可视化

由于所有特征属性都是Continuous类型,因此可以计算一些统计数据,并绘制它们的分布图,以获得更多的洞察力。

峰度&偏斜度

为了初步了解实值变量的分布,计算两个度量值https://en.wikipedia.org/wiki/Kurtosis偏斜度

  • 偏斜度是对分布的对称性(或者说不对称性)的一种度量。负值表示它的左尾巴更长(相当平),质量集中在右边。我们称之为左偏或左尾分布。另一方面,正值表示右偏分布。
  • 峰度是对分布“尾部”的一种度量。它显示了尾巴向左或向右伸出的距离。正态分布的峰度为 3。因此,通常将其他分布的峰度与该值进行比较,并计算出所谓的过度峰度。它表示所考虑的分布的峰度超过正态分布的峰度多少。这也是我们从StatsBase包的kurtosis函数中得到的值。

以下表达式为“glass”数据集的所有要素创建了这些测量值的列表:

*[(atr, skewness(glass[:, atr]), kurtosis(glass[:, atr])) 
    for atr in col_names[1:end-1]]*

像这样的表达在 Julia 中被称为“理解”。这是一个数组,它的初始值是通过迭代计算的。for atr in col_names[1:end-1]遍历glass的列名(从第一个到倒数第二个)。对于每个列名(atr),它创建一个包含三个元素的元组:列名atr本身以及该列的偏度(skewness(glass[:, atr]))和峰度(kurtosis(glass[:, atr])),从而创建以下输出:

*9-element Vector{Tuple{String, Float64, Float64}}:
 (“RI”, 1.6140150456617635, 4.7893542254570605)
 (“Na”, 0.4509917200116131, 2.953476583500219)
 (“Mg”, -1.1444648495986702, -0.42870155798810883)
 (“Al”, 0.9009178781425262, 1.9848317746238253)
 (“Si”, -0.7253172664513224, 2.8711045971453464)
 (“K”, 6.505635834012889, 53.392326556204665)
 (“Ca”, 2.0326773755262484, 6.498967959876067)
 (“Ba”, 3.392430889440864, 12.222071204852575)
 (“Fe”, 1.7420067617989456, 2.5723175586721645)*

从这些图中我们已经可以看出,例如, KBa 的分布是非对称和长尾的。但是在大多数情况下,如果我们将分布可视化,我们会得到更多的见解。

功能RI**

先说特征 RI 。violin plot 可以使用StatsPlots包中的violin函数创建,它很好地展示了它的分布情况。violin(glass.RI, legend = false)产生以下情节:

特征 RI 的分布[图片由作者提供]

直方图的条形根据目标值着色,还显示了特征值和最终玻璃类型之间的关系。为此,我们可以使用histogram函数(同样来自StatsPlots;参见图表下方的 Julia 代码):

RI 值直方图,按玻璃类型分组[图片由作者提供]

特色……菲****

我们可以对剩下的特征 NaFe 做同样的处理。描绘分布的小提琴图的一个很好的替代方案是箱线图。有时小提琴图给人更多的洞察力,有时箱线图。所以我们最好两者都创造出来。如果我们并排绘制每个特征的所有三种图表类型,我们可以很容易地对它们进行比较,从而得到以下图表网格:

小提琴图、箱线图和直方图的特征 Na…Fe[图片由作者提供]

用于生成该绘图网格的 Julia 代码如下所示:

前三行具有相似的结构:每一行创建一个图表数组(首先是小提琴,然后是箱线图,最后是直方图)。同样,我们使用“理解”来达到这个目的:我们从 2 到 9 进行迭代,因为我们想要一个图来表示glass数据帧的第 2 到 9 列。对于这些列中的每一列,我们用适当的参数创建各自的图表(与我们在上面对特征 RI 所做的方式相同)(每个图表获得例如特征名称作为其xlabel)。

plot功能(绘制整个网格;第 7 行)将一个子情节数组作为其参数,并根据layout参数将它们排列在一个网格中(这里是一个 8 x 3 的网格)。因此,我们必须应用以下步骤(第 5 行中的代码)创建一个适当的数组,该数组以正确的顺序包含子情节(即,对于每个素材,一个小提琴情节、一个箱线图和直方图):

  • hcat将三个数组violinsboxeshistos连接起来,从而创建一个 8 x 3 的绘图矩阵(每种类型的图表对应一列)
  • 由于 Julia 矩阵是按列优先的顺序处理的,我们用permutedims切换维度,创建一个 3×8 矩阵(因此 Na 的三个图表在第一列,而 Mg 的图表在第二列,依此类推)
  • 最后,我们必须使用vec将矩阵“展平”成一个数组(根据plot的需要)。

相关性

在处理数字属性时,知道它们是否相关以及相关程度如何总是很重要的,因为有些模型不太适合强相关的属性。

除此之外,两个属性之间的强相关性表明其中一个可能是多余的,因为它没有携带太多的附加信息。这些知识可用于减少特征的数量,从而使训练和评估模型的计算更加可行。

相关矩阵

函数cor(来自StatsPlots))以矩阵的形式给出我们想要的信息,该矩阵具有每对特征的相关性(所谓的相关性矩阵):

像往常一样,可视化使数字更容易理解。在这种情况下,热图是合适的图表类型(我们使用来自StatsPlotsheatmap 创建)。由于相关矩阵是对称的,可视化它的下半部分就足够了(可以使用LowerTriangularLinearAlgebra包中提取;见图表下方的 Julia 代码)。用于热图的色标:tol_sunset来自ColorSchemes包。

相关矩阵热图[图片由作者提供]

*heatmap(LowerTriangular(cor_mat), yflip = true,
 seriescolor = :tol_sunset,
 xticks = (1:9, col_names[1:9]),
 yticks = (1:9, col_names[1:9]))*

从热图(和关联矩阵)中可以看出,不同玻璃属性之间最强的正关联是:

  • Ca/RI → 0.810403
  • 钡铝比→ 0.479404
  • 钡/钠→ 0.326603
  • 钾/铝→ 0.325958

…当我们看数字时,只有前两个是真正“强”的。

可以观察到明显的负相关性:

  • Si/RI = -0.542052
  • 钡/镁= -0.492262
  • 铝/镁= -0.481798
  • 钙/镁= -0.443750

相关图和角图

下面的图更详细地展示了不同属性对之间的相关性:

  • **关联图在下半部分显示了散点图(每个散点图都有相应的 SD 线),在上半部分显示了热图。散点图以蓝色显示正相关,以红色显示负相关,以黄色显示中性相关。
  • **角图(此处为紧凑形式)显示了与图表下半部分的相关图相同的散点图。此外,它在顶部和右侧空白处显示每个属性的直方图。

“玻璃”特征的相关图[图片由作者提供]

“玻璃”特征的角图[图片由作者提供]

这些图都是用一行 Julia 代码使用StatsPlots中的函数创建的,如下所示:

*[@df](http://twitter.com/df) glass corrplot(cols(1:9))
[@df](http://twitter.com/df) glass cornerplot(cols(1:9), compact = true)*

结论

上面的例子展示了如何仅仅使用几行代码来执行典型 ML 工作流的重要步骤。本教程的第二部分将继续这一旅程,重点关注训练、预测和评估等步骤。

不同 Julia 包的功能如何容易地组合,以及它们如何很好地一起工作,变得很清楚,即使它们是独立开发的。本教程的第三部分将解释这种可组合性的内部工作原理。敬请关注!

当然,这些例子只能触及表面。有关更多信息,请参见下面给出的资源。

进一步的信息

使用决策树—第 2 部分

原文:https://towardsdatascience.com/part-ii-using-a-decision-tree-ddffa4004e47

使用 Julia 及其生态系统的机器学习

MLJ 是 Julia 中机器学习的元包。它的第一个版本在 2019 年才发布。因此,它的创造者可以在该领域现有软件包十多年经验的基础上再接再厉。

照片由简·侯伯Unsplash 上拍摄

教程概述

这是本教程的第二部分,它展示了 Julia 的特定语言特性和来自其生态系统的各种高质量的包是如何在一个典型的 ML 工作流中轻松组合使用的。

  • 第一部分分析玻璃数据集”集中讲述如何使用ScientificTypesDataFramesStatsBaseStatsPlots等软件包对数据进行预处理、分析和可视化。
  • 第二部分"使用决策树"关注 ML 工作流的核心:如何选择模型,以及如何使用它进行训练、预测和评估。这部分主要靠包MLJ(=MachineLinJulia)收入。
  • 第三部分“如果事情还没有‘准备好使用’”解释了如果可用的包没有提供您需要的所有功能,那么用几行代码创建您自己的解决方案是多么容易。

介绍

在本教程的第二部分中,机器学习算法将应用于“玻璃”数据集,我们在第一部分中对其进行了分析。

MLJ——朱莉娅的机器学习

为此,我们使用[MLJ](https://alan-turing-institute.github.io/MLJ.jl/dev/)封装。它是一个所谓的元包,为选择、调整、评估、组合和比较大量的 ML 模型提供了一个通用的接口和通用的实用机制。在撰写本文时,它提供了 186 种型号。

MLJ 是伦敦图灵研究所与奥克兰大学以及其他科学和工业伙伴合作的一个项目。始于 2019 年。

接下来的步骤

使用 ML 算法通常包括以下步骤:

  • 准备数据,以便获得用于培训和测试的适当结构。
  • 选择适合给定数据并产生所需结果(例如分类或聚类)的适当模型。
  • 用训练数据集训练选择的模型。
  • 使用测试数据集评估所训练的模型。

我们将在下一节中应用这个(基本的)ML 工作流。这里我们假设我们的数据集存在于第一部分创建的glass数据框中。

ML 工作流程

准备数据

几乎所有的 ML 模型都期望训练数据集是表格形式的。所以我们在第一部分中使用的DataFrame数据类型是一个很好的起点。

在我们的例子中,我们有一个所谓的分类问题:我们希望模型在给定一组值时预测正确的玻璃类型(即我们的特征 RINaBa;独立属性)。将使用独立属性的数据以及每种情况下产生的相应玻璃类型来训练模型。这就是所谓的监督学习。

对于这种训练,我们需要表格形式的独立属性数据和单独列表中的结果类型(在列Type)。函数unpack完成这项工作。它将glass中的DataFrame拆分为这两个组件(XGlassyGlass)。除此之外,它还使用一个随机数发生器对行进行洗牌,该随机数发生器用“种子”rng = 123进行初始化。

scitype检查结果数据结构的科学类型,我们可以看到,我们确实得到了一个向量和一个:

scitype(yGlass) --> AbstractVector{Multiclass{7}}scitype(XGlass) --> Table{AbstractVector{Continuous}}

选择模型

现在我们必须选择一个合适的模型。models函数给出了 MLJ 提供的所有型号的列表。目前这个列表中有 186 个型号,这可能有点让人不知所措。

all_models = models() -->186-element Vector{ ... }:
 (name = ABODDetector, package_name = OutlierDetectionNeighbors, … )
 (name = ABODDetector, package_name = OutlierDetectionPython, … )
 (name = AEDetector, package_name = OutlierDetectionNetworks, … )
 (name = ARDRegressor, package_name = ScikitLearn, … )
 (name = AdaBoostClassifier, package_name = ScikitLearn, … )
 (name = AdaBoostRegressor, package_name = ScikitLearn, … )
 (name = AdaBoostStumpClassifier, package_name = DecisionTree, … )
 (name = AffinityPropagation, package_name = ScikitLearn, … )
 (name = AgglomerativeClustering, package_name = ScikitLearn, … )
 (name = BM25Transformer, package_name = MLJText, … )
 (name = BaggingClassifier, package_name = ScikitLearn, … )
 (name = BaggingRegressor, package_name = ScikitLearn, … )
 (name = BayesianLDA, package_name = MultivariateStats, … )
 (name = BayesianLDA, package_name = ScikitLearn, … )
 ⋮
 (name = Standardizer, package_name = MLJModels, … )
 (name = SubspaceLDA, package_name = MultivariateStats, … )
 (name = TSVDTransformer, package_name = TSVD, … )
 (name = TfidfTransformer, package_name = MLJText, … )
 (name = TheilSenRegressor, package_name = ScikitLearn, … )
 (name = UnivariateBoxCoxTransformer, package_name = MLJModels, … )
 (name = UnivariateDiscretizer, package_name = MLJModels, … )
 (name = UnivariateFillImputer, package_name = MLJModels, … )
 (name = UnivariateStandardizer, package_name = MLJModels, … )
 (name = UnivariateTimeTypeToContinuous, package_name = MLJModels,…)
 (name = XGBoostClassifier, package_name = XGBoost, … )
 (name = XGBoostCount, package_name = XGBoost, … )
 (name = XGBoostRegressor, package_name = XGBoost, … )

因此,我们应该缩小列表范围:首先,我们希望看到哪些模型基本上与数据集的数据类型“兼容”。我们所有的特征都有连续的数值值,并且产生的类(玻璃类型)是一个名义值。

matching(XGlass, yGlass)models配合使用,可过滤出符合该要求的型号。这将数量大大减少到 47:

compatible_models = models(matching(XGlass, yGlass)) -->47-element Vector{ … }:
 (name = AdaBoostClassifier, package_name = ScikitLearn, … )
 (name = AdaBoostStumpClassifier, package_name = DecisionTree, … )
 (name = BaggingClassifier, package_name = ScikitLearn, … )
 (name = BayesianLDA, package_name = MultivariateStats, … )
 (name = BayesianLDA, package_name = ScikitLearn, … )
 (name = BayesianQDA, package_name = ScikitLearn, … )
 (name = BayesianSubspaceLDA, package_name = MultivariateStats, … )
 (name = ConstantClassifier, package_name = MLJModels, … )
 (name = DecisionTreeClassifier, package_name = BetaML, … )
 (name = DecisionTreeClassifier, package_name = DecisionTree, … )
 (name = DeterministicConstantClassifier, package_name =MLJModels,…)
 (name = DummyClassifier, package_name = ScikitLearn, … )
 (name = EvoTreeClassifier, package_name = EvoTrees, … )
 (name = ExtraTreesClassifier, package_name = ScikitLearn, … )
 ⋮
 (name = ProbabilisticSGDClassifier, package_name = ScikitLearn, … )
 (name = RandomForestClassifier, package_name = BetaML, … )
 (name = RandomForestClassifier, package_name = DecisionTree, … )
 (name = RandomForestClassifier, package_name = ScikitLearn, … )
 (name = RidgeCVClassifier, package_name = ScikitLearn, … )
 (name = RidgeClassifier, package_name = ScikitLearn, … )
 (name = SGDClassifier, package_name = ScikitLearn, … )
 (name = SVC, package_name = LIBSVM, … )
 (name = SVMClassifier, package_name = ScikitLearn, … )
 (name = SVMLinearClassifier, package_name = ScikitLearn, … )
 (name = SVMNuClassifier, package_name = ScikitLearn, … )
 (name = SubspaceLDA, package_name = MultivariateStats, … )
 (name = XGBoostClassifier, package_name = XGBoost, … )

除了这个相当技术性的“兼容性”,我们对我们的模型还有一些其他的期望:对于本教程,我们想要一个具有高度可解释性的模型,也就是说,当对一些数据进行分类时,应该很容易理解它是如何工作的。理想情况下,它应该在纯 Julia 中实现,因为这是一个关于 Julia 和 ML 的教程。

决策树分类器具有高度的可解释性。所以让我们看看,我们是否能在这个列表中找到一些在 Julia 中实现的。

MLJ 的所有模型都提供了全面的元信息(如下例所示)。例如,docstring包含一个简短的文本描述,is_pure_julia告诉我们,如果它是用纯 Julia 实现的(或者只是其他一些用 Julia 包装的编程语言),最后每个模型都有一个name。我们可以使用这个元信息来过滤我们的兼容模型列表(使用 Julia 的filter-函数,它接受一个任意的匿名函数作为过滤表达式):

filter(
    m -> m.is_pure_julia && 
         contains(m.docstring * m.name, “DecisionTree”),
    compatible_models
)4-element Vector{ ... }:
 (name = AdaBoostStumpClassifier, package_name = DecisionTree, … )
 (name = DecisionTreeClassifier, package_name = BetaML, … )
 (name = DecisionTreeClassifier, package_name = DecisionTree, … )
 (name = RandomForestClassifier, package_name = DecisionTree, … )

所以我们可以把名单缩减到四个候选人。第一项和第四项是高级决策树模型。但是我们更喜欢‘经典’的,为了本教程的目的,它很容易理解。这就是我们关注第二和第三位候选人的原因。这些模型的docstring包含一个到 GitHub 库的 URL,在那里我们也可以找到相关的文档:第二个是 BetaML 包的一部分,第三个可以在决策树包中找到。

两者都实现了著名的 CART 算法并且非常相似,但是仔细看看文档,后一个似乎更成熟并且提供了更多的功能。所以我们选择这个。

这里作为一个例子,对于那些感兴趣的人,3 号候选者的所有元数据的完整列表。这种描述适用于 MLJ 的每一款车型:

使用决策树分类器

使用@load宏可以在 MLJ 加载模型。它返回一个类型定义(我们在这里称之为MyDecisionTree)。

MyDecisionTree = [@load](http://twitter.com/load) DecisionTreeClassifier pkg = ”DecisionTree” 

在下一步中,我们创建一个这种类型的实例dc。该实例包含所有参数及其默认值。因此,我们可以看到,例如,默认情况下树的深度不受限制(max_depth = -1),并且在创建后不会被修剪(post_prune = false)。

dc = MyDecisionTree() -->DecisionTreeClassifier(
 max_depth = -1,
 min_samples_leaf = 1,
 min_samples_split = 2,
 min_purity_increase = 0.0,
 n_subfeatures = 0,
 post_prune = false,
 merge_purity_threshold = 1.0,
 pdf_smoothing = 0.0,
 display_depth = 5,
 rng = Random._GLOBAL_RNG())

训练模特

我们现在拥有训练模型的一切。首先,我们使用简单的维持策略将数据分成训练数据集测试数据集(在本例中是 70:30 分割)。也就是说,我们将 30%的数据放在一边用于测试,仅将 70%用于训练。

partition完成这项工作,并在应用拆分之前使用随机数生成器(我们将其种子初始化为rng = 123)来打乱行。由于我们已经将数据垂直拆分为XGlassyGlass,这些组件将分别拆分为Xtrain / Xtestytrain / ytest。我们通过设置multi = true来通知partition这个特殊情况。

(Xtrain, Xtest), (ytrain, ytest) = 
 partition((XGlass, yGlass), 0.7, multi = true, rng = 123)

因此Xtrain, ytrain现在包含 70%的“玻璃”数据集,而Xtest, ytest包含剩余的 30%。下图显示了我们如何将数据集分成上述四个部分:

横向和纵向分割的玻璃数据集[图片由作者提供]

接下来,我们应用以下步骤进行培训和测试:

  • 首先,使用所谓的机器将模型和训练数据连接起来。
  • 然后通过调用机器上的fit!进行培训。
  • 之后,经过训练的机器可用于对新数据进行预测(使用predict)。

为了简单起见,我们预先将生成的决策树的深度限制为 3。

dc.max_depth = 3
dc_mach = machine(dc, Xtrain, ytrain)
fit!(dc_mach)

预测和评估

所以让我们看看这个模型在测试数据集Xtest(使用 MLJs predict -function): yhat = predict(dc_mach, Xtest)上的预测有多好。

yhat现在包含了我们的模型对测试数据集Xtest的预测。yhat的前三个元素如下:

“yhat”的前三个元素[作者图片]

也许yhat中的预测看起来不像你所期望的:yhat不是一个预测玻璃类型的列表,而是一个分布列表陈述了每种可能玻璃类型的预测概率。这源于选择的模型(a DecisionTreeClassifier)是一个概率模型(从它的元信息可以看出:prediction_type = :probabilistic)。MLJ 所有的概率模型都预测分布(使用Distributions.jl)。

但是只需调用predict_mode(而不是predict)就可以很容易地获得预测类的列表(每个分布中概率最高的类):

yhat_classes = predict_mode(dc_mach, Xtest) -->64-element CategoricalArrays.CategoricalArray{String,1,UInt32}:
 “headlamps”
 “build wind float”
 “containers”
 “build wind float”
 “vehic wind float”
 “build wind non-float”
 “build wind non-float”
 “build wind float”
 “headlamps”
 ⋮
 “build wind non-float”
 “build wind float”
 “headlamps”
 “build wind non-float”
 “containers”
 “build wind float”
 “build wind float”
 “build wind float”

在此基础上,我们可以通过比较ytest(来自测试集的正确类)和yhat_classes(来自我们的分类器的预测类)来检查有多少类被正确预测。

注意,我们要比较这两个数组的元素(不是数组本身)。这可以在 Julia 中使用所谓的广播机制来实现,该机制通过在所应用的函数前面使用一个点来表示(在这种情况下,使用==进行相等比较)。所以ytest .== yhat_classes进行元素间的比较,得到一个布尔值列表。最后,count计算列表中true值的数量。

correct_classes = count(ytest .== yhat_classes) --> 44accuracy = correct_classes / length(ytest) --> 0.6875

使用计数的结果,我们可以看到,在测试集中的 64 个实例中,有 44 个被正确预测。这个比率是 68.75%。

打印决策树

现在,如果我们能看到由我们的DecisionTreeClassifier构建的决策树是什么样子,那就太好了。然后,我们可以检查哪些属性已经用于分支,以及在哪个阈值发生了分支。

幸运的是,有一个用于此目的的内置函数。report是一个通用函数,可在经过训练的机器上调用,并传递(取决于使用的型号)关于经过训练的机器的所有相关信息。在决策树的情况下,这个“相关信息”是一个TreePrinter对象,可以使用print_tree()调用。

report(dc_mach).print_tree() -->Feature 3, Threshold 2.745
L-> Feature 2, Threshold 13.77
    L-> Feature 4, Threshold 1.38
        L-> 2 : 6/7
        R-> 5 : 9/12
    R-> Feature 8, Threshold 0.2
        L-> 6 : 8/10
        R-> 7 : 18/19
R-> Feature 4, Threshold 1.42
    L-> Feature 1, Threshold 1.51707
        L-> 3 : 5/11
        R-> 1 : 40/55
    R-> Feature 3, Threshold 3.42
        L-> 2 : 5/10
        R-> 2 : 23/26

因此,我们可以看到,例如,树根处的第一个分支使用 3 号特征(即 Mg =镁)在阈值 2.745 处进行第一次分割。也就是说,Mg-值≤ 2.745 的所有实例将使用左侧子分支中的规则进一步分类,而Mg-值>为 2.745 的实例进入右侧分支。

上面print_tree()的输出给出了决策树的样子...但是说实话,它有点基础:它只是 ASCII 文本,节点只显示属性号,而不是用于分支的属性名。此外,树叶不显示类名,在这一点上得到预测。如果有一个包含更多信息的图形化描述不是很好吗?

由于没有现成的函数可用于此目的,我们不得不考虑如何使用 Julia 生态系统中的其他方法来实现这一目的。这就是我们将在本教程的第三部分中做的事情…所以请跟我走这条路:-)。

结论

上面的例子显示了如何使用 MLJ 中的综合元数据来选择合适的模型,以及如何使用通用界面来应用 ML 工作流的典型步骤。该接口对于所有提供的型号都是相同的。这大大减少了学习曲线以及使用各种模型时出现应用程序错误的可能性。

在本教程中,只能介绍 MLJs 功能的一小部分。除了这个相当简单的摘录之外,该软件包还在以下方面提供了大量的功能:

  • 评估 :有大量的度量标准可用于评估模型。在此过程中,可以应用各种保留和交叉验证策略以及用户定义的采样策略。
  • :提供多种超参数(半)自动调谐策略(可添加用户自定义策略)。
  • 构图模型 : MLJ 提供不同变化的模型构图。
    管道可用于链接(从而自动化)ML 工作流的不同处理步骤。
    同类组合允许相同类型(但内部工作不同)的模型组合,例如减少过度拟合的影响。
    模型堆叠 是从不同类型的模型中创建新模型的方法。使用这些合成机制创建的所有新模型都是“一等公民”。也就是说,它们可以以与基本型号相同的方式使用。

科学类型的优势(它是 MLJ 的一个组成部分)已经在教程的第一部分中展示了。

与 Python 不同,在 Python 中,每个性能关键的代码都必须用 C 语言编写,在这里,我们可以为整个 ML 栈应用一种语言(即 Julia)。除了降低复杂性和维护工作量之外,这种方法还有其他优点:

  • 超参数可以使用梯度下降算法(反过来使用基于一种语言环境的自动区分)进行调整。
  • 可以使用高性能硬件架构(GPU、并行计算),而无需进行重大代码重构。

进一步的信息

  • 艾伦图灵研究所的 MLJ 项目主页
  • MLJ 的文档以及大量的介绍材料可以在这里找到。
  • Anthony Blaom 基于 Jupyter 和 Pluto 笔记本创建了一系列互动教程(最近在我的帮助下),涵盖了 MLJ 的所有重要方面。它们源自他在 2020 年 Julia con举办的工作坊

如果事情还没有“准备好使用”——第 3 部分

原文:https://towardsdatascience.com/part-iii-if-things-are-not-ready-to-use-59d2db378bec

使用 JULIA 及其生态系统的机器学习

朱莉娅有很高的可组合性。只需几行代码,就可以在现有软件包的基础上构建新的功能。

照片由 Unsplash 上以像素表示我们的生活

教程概述

这是本教程的第三部分,它展示了 Julia 的特定语言特性和来自其生态系统的各种高质量的包是如何在一个典型的 ML 工作流中轻松组合使用的。

  • 第一部分分析玻璃数据集”集中讲述如何使用ScientificTypesDataFramesStatsBaseStatsPlots等软件包对数据进行预处理、分析和可视化。
  • 第二部分使用决策树”关注 ML 工作流的核心:如何选择模型,以及如何使用它进行训练、预测和评估。这部分主要靠包MLJ(=MachineLinJulia)收入。
  • 第三部分"如果事情还没有“准备好使用”"解释了如果可用的软件包没有提供您需要的所有功能,那么用几行代码创建您自己的解决方案是多么容易。

介绍

在本教程的第二部分中,我们创建了一个决策树,并使用print_tree()打印了以下表示:

Feature 3, Threshold 2.745
L-> Feature 2, Threshold 13.77
    L-> Feature 4, Threshold 1.38
        L-> 2 : 6/7
        R-> 5 : 9/12
    R-> Feature 8, Threshold 0.2
        L-> 6 : 8/10
        R-> 7 : 18/19
R-> Feature 4, Threshold 1.42
    L-> Feature 1, Threshold 1.51707
        L-> 3 : 5/11
        R-> 1 : 40/55
    R-> Feature 3, Threshold 3.42
        L-> 2 : 5/10
        R-> 2 : 23/26

它描述了树的基本特征,但有点简陋:它只是 ASCII 文本,节点只显示属性号,而不是用于分支的属性名。此外,树叶不显示类名,在这一点上得到预测。

因此,最好有一个包含所有附加信息的图形表示。由于没有现成的函数可用于此目的,我们必须为自己创建一个。这就是本教程第三部分的内容。

积木

我们自己实现这个需要什么?好吧,下面的成分肯定是主要的组成部分:

  1. 决策树本身(数据结构)
  2. 有关该数据结构外观的信息(内部)
  3. 一个能够绘制树木的图形包

…当然,还有一个想法,关于如何将这些东西粘在一起:-)

1.决策树

MLJ 函数fitted_params()为每台经过训练的机器提供训练产生的所有参数。当然,这很大程度上取决于所使用的模型。在决策树分类器的情况下,这些参数之一仅仅是树本身:

fp = fitted_params(dc_mach)
typeof(fp.tree)     → DecisionTree.Node{Float64, UInt32}

2.关于数据结构的信息

为了得到一些关于决策树内部结构的信息,我们不得不看看 GitHub 上的DecisionTree.jl 的源代码。

在这里我们可以找到LeafNode的如下定义:

从“DecisionTree.jl”中摘录源代码

这告诉我们,树结构由Node组成,它存储用于在该节点分支的特征号(featid)和发生分支的阈值(featval)。此外,每个节点都有一个left和一个right子树,子树又是一个NodeLeaf(如果到达了树的底部)。

Leaf s 知道到达该叶子时将被预测的多数类(majority)以及已经被分类到该叶子中的所有目标值(values)的列表。

3.图形包

Julia 图形包[Plots.jl](https://github.com/JuliaPlots/Plots.jl)本身并不能绘制树木。但是有一个叫做[GraphRecipes.jl](https://github.com/JuliaPlots/GraphRecipes.jl)的‘助手’包,里面包含了几个所谓的绘图配方。这些规范描述了如何使用Plots(或另一个理解这些配方的图形包)绘制不同的图形结构。在其他食谱中,有一种是针对(叫做TreePlot)的。

GraphRecipes也作为要绘制的结构和执行绘制的图形包之间的抽象层。

将这一切结合在一起

基本概念

现在我们已经确定了主要的构件,剩下的问题是我们如何将这些东西组合在一起,以获得想要的结果。主要问题是:绘图配方TreePlot怎么知道,我们的决策树详细是什么样子的,以便TreePlot绘图?两个包(DecisionTree.jlGraphRecipes.jl)都是独立开发的。他们对彼此一无所知。

TreePlot的文档告诉我们,它需要一个AbstractTree类型的结构(来自包[AbstractTree.jl](https://github.com/JuliaCollections/AbstractTrees.jl))。但是来自DecisionTree.jl包的决策树不符合这种类型。我们现在被困住了吗?

如果我们使用一种“普通的”面向对象语言,我们会陷入困境。在这种情况下,我们也许可以尝试定义一个继承自NodeAbstractTree的新类。但是,只有在语言支持多重继承的情况下,这种方法才有效。如果没有,我们可以尝试定义一个继承自AbstractTree的新类,并在其中包装NodeLeaf结构。所有这些可能需要相当多的工作,并且似乎不是一个优雅的解决方案。

幸运的是,我们使用朱莉娅。这里我们只需要(重新)定义决策树上AbstractTree的几个函数,这使得看起来像AbstractTreeTreePlot配方只期望从我们的决策树得到一个特定的行为(它真的不关心这个数据结构的类型):它希望树有

  • 当应用于树t(或树的一个节点)时,返回其子树列表的函数children(t)
  • 以及将节点node的表示打印到输出流io的函数printnode(io::IO, node)

下图显示了所有这些组件以及它们之间的关系:

基本结构[图片由作者提供]

我们只需要创建绿色部分(蓝色部分已经存在)。这就是我们所需要的。那么决策树符合一个AbstractTree(就TreePlot而言)。所以让我们开始吧:

第一个简单的实现

这可以通过下面几行代码来完成:

当我们使用DecisionTreeAbstractTree包中的一些类型和函数定义时,我们必须声明它们的用法(用importusing)。

children的实现是一个简单的单行函数,它只返回左边和右边的子树。

为了打印,我们必须区分节点和叶子,因为节点的输出看起来不同于叶子的输出。在大多数编程语言中,我们需要一个实现来检查要打印的节点/叶子的类型,并在这种情况下使用一个if ... then ... else-语句。朱丽亚却不是这样!在这里,我们可以使用它的多重分派功能,实现两个版本的printnode:一个用于Node s,另一个用于Leaf s。

这里,我们为printnode选择了一个非常简单的实现作为第一步。Node的版本打印的信息与我们之前使用的内置print_tree()相同(特征的 ID 和阈值)。Leaf的变体甚至更简单:它打印预测类的 ID 和叶子中考虑的值的数量。

AbstractTree.jl包还有一个函数print_tree,它使用我们上面实现的两个函数来输出基于文本的树。让我们使用AbstractTree.print_tree来测试我们的第一个实现:

AbstractTrees.print_tree(fp.tree) -->Feature: 3 — Threshold: 2.745
├─ Feature: 2 — Threshold: 13.77
│ ├─ Feature: 4 — Threshold: 1.38
│ │ ├─ maj: 2 — vals: 7
│ │ └─ maj: 5 — vals: 12
│ └─ Feature: 8 — Threshold: 0.2
│   ├─ maj: 6 — vals: 10
│   └─ maj: 7 — vals: 19
└─ Feature: 4 — Threshold: 1.42
  ├─ Feature: 1 — Threshold: 1.51707
  │ ├─ maj: 3 — vals: 11
  │ └─ maj: 1 — vals: 55
  └─ Feature: 3 — Threshold: 3.42
    ├─ maj: 2 — vals: 10
    └─ maj: 2 — vals: 26

看起来很好,所有值都与上面决策树的第一个打印结果相对应。所以实现看起来完美无缺!

现在使用PlotsGraphRecipes的图形表示:因为我们已经实现了必要的功能,我们可以使用TreePlot调用plot(应用 Buchheim 树布局算法)。就是这样!

using GraphRecipesplot(TreePlot(fp.tree), 
        method = :buchheim, nodeshape = :rect,
        root = :left, curves = false)

决策树的图形描述[图片由作者提供]

微调输出

在下一步中,我们想要一个树,在它的节点上显示 featurename,在它的叶子上显示预测类的name(而不是之前使用的 id)。可惜 a DecisionTree根本不知道这个信息。也就是说,我们必须以某种方式添加这些信息,以便它们可以被上一节中应用的机制所使用。

这个想法是在使用children遍历决策树时添加这个信息,这样printnode就可以访问它。那是我们唯一需要这些信息的地方。没有必要以任何方式改变决策树结构。

因此,children不会直接从DecisionTree返回NodeLeaf结构,而是返回带有附加信息的丰富结构。这些富集结构定义如下:

这些新的struct中的属性info可能包含我们想要在打印的树上显示的任何信息。dt_nodedt_leaf只是我们之前已经使用过的决策树元素。

为了在每次调用children时创建这些丰富的结构,我们定义了一个具有两个实现的函数wrap_element():一个创建NamedNode,另一个创建NamedLeaf。这是我们依赖于 Julia 的多重调度机制的另一种情况。根据第二个参数的类型(a Node或 a Leaf),将选择正确的方法。

这使得我们新的children函数的实现非常容易:只需两次调用wrap_element(),在这里我们添加属性名和类名作为参数(作为NamedTuple;朱莉娅标准类型在这里派上用场)。

printnode的实现现在接收一个NamedNode或者一个NamedLeaf,并且可以使用这些元素中的信息进行打印:

变量matches(在printnode的第二个实现中)包含已经被正确分类到这个叶子上的实例(在values中这个叶子上的所有实例中)。因此可以显示正确分类的实例与所有实例的比率(match_countval_count)。

为了绘制决策树,我们最后为决策树的根创建一个NamedNode。然后我们再次使用plotTreePlot配方来获得想要的结果。

nn = NamedNode(
        (atr = names(XGlass), cls = levels(yGlass)), 
        fp.tree)plot(TreePlot(nn), 
        method = :buchheim, nodeshape = :rect,
        root = :left, curves = false)

决策树的丰富变体[图片由作者提供]

结论

上面的例子展示了用几行代码就可以组合几个不相关的 Julia 包,从而创建新的有用的功能。

尤其是第二个例子(“微调 …”),当然在(内存)效率和模块化/重用方面有一些优化的潜力。但是这也可以通过稍微多一点的努力来完成。

进一步的信息

在 GitHub 上(在:Roland-KA/juliaformtutorial)我提供了本教程所有三个部分的 Pluto 笔记本,并附有完整的代码示例。所以每个人都被邀请亲自尝试一下。

自然语言处理中的词性标注

原文:https://towardsdatascience.com/part-of-speech-pos-tagging-in-nlp-e067a1c5e06c

如何为 NLP 选择模型

作者图片

所有语言的基础是单词。单词根据它们在句子中的功能被分为词类。重要的是要认识到,一个单词属于多个词类并不罕见,这取决于该单词的使用方式。理解这种基本的分类需要大量的训练。

在自然语言处理(NLP)中,有一个类似的任务叫做词性标注,目的是将句子中的每个单词标注到正确的词性(词性)。词性标注是一项消歧任务。一个单词可以有多个词性标签;目标是在给定当前上下文的情况下找到正确的标签。例如, left 在用作“他离开房间”时可以是动词,在用作“离开房间”时可以是名词。

词性标注是自然语言处理中的一个基本问题。有许多基于 POS 标签的 NLP 任务。大多数好的词性标注者报告的准确率都在 97%以上。然而,一些学者认为每个令牌的准确性不是估计 POS 引擎准确性的最佳方式。在他们看来,在句子层面,准确率远低于声称的 97%。

使用空间的词性标注

SpaCy 提供了四种英语词性标注模型-

  • '核心网'
  • en_core_web_md '
  • en_core_web_lg '
  • en_core_web_trf '

我的合作伙伴玛姆塔·查克拉沃蒂创造了一个可视化效果来识别马丁·路德·金著名的“我有一个梦想”每一段的位置演讲。

她用的是 SpaCy 的‘en _ core _ web _ LG’语言模型。

语言模型的质量取决于两个构件——模型架构和训练数据。

' en_core_web_lg '的代码不是公开的,所以我们无法确认模型架构。然而,我们知道 SpaCy 已经在来源于 notes 5.0 版、 ClearNLP 成分到依赖转换Wordnet 3.0 的数据上训练了这个语言模型,并报告了这个模型的 POS 准确率为 97%。

SpaCy 的' en_core_web_trf '模型基于变压器架构。Transformer 是最新的 NLP 技术之一,它已经变得非常流行,因为这些模型显示了对上一代语言模型的重大改进。SpaCy 的“en_core_web_trf”报告的准确率为 98%。

“en_core_web_sm”和“en_core_web_md”是轻型型号。他们的准确性不如另外两个。

“en_core_web_lg”和“en_core_web_trf”结果比较

让我们参考 MLK 的演讲。它有 23 个段落和 84 个句子。下面的代码将语音存储为数据帧,并输出“lg”和“trf”模型的 POS 标签之间的差异。

import spacy
texts = speech['Para_txt'].to_list()
nlp_trf = spacy.load('en_core_web_trf')
nlp_lg = spacy.load('en_core_web_lg')
pos_trf = [(tok.text, tok.pos_) for t in texts for tok in nlp_trf(t)]
pos_lg = [(tok.text, tok.pos_) for t in texts for tok in nlp_lg(t)]
assert len(pos_lg) == len(pos_trf)
diff = [(a, b, d) for ((a, b), (c, d)) in zip(pos_lg, pos_trf) if b != d]
print(diff)
-------------------------------------------------------------------[('Negro', 'ADJ', 'PROPN'),
 ('crippled', 'ADJ', 'VERB'),
 ('material', 'NOUN', 'ADJ'),
 ('â\x80\x94', 'ADJ', 'PUNCT'),
 ('Negro', 'PROPN', 'ADJ'),
 ('bank', 'PROPN', 'NOUN'),
 ('justice', 'PROPN', 'NOUN'),
 ('demand', 'VERB', 'NOUN'),
 ('invigorating', 'ADJ', 'VERB'),
 ('Those', 'DET', 'PRON'),
 ('content', 'NOUN', 'ADJ'),
 ('until', 'ADP', 'SCONJ'), ...
]

有 45 个单词在两个模型之间被标记为不同。在上表中,我只展示了前几个例子。在上述所有差异中,突出的差异(“需求”)最为突出。虽然模型混淆名词和形容词是常见的,但是模型混淆动词和名词是不常见的。动词表示动作,因此很容易与名词区分开来。

因此,我们来兑现这张支票,这张支票将在我们要求时给予我们自由的财富和正义的保障。

-马丁·路德·金

在上面的句子中,' demand '一词用作名词。Riches 是动词 give 的宾语。因此基于变压器的“trf”模型是准确的。当我们使用这两种模型检查 SpaCy 中的依赖图时,这变得更加清楚。

结论——这重要吗

这个帖子不是关于变形金刚的;你可以在这篇伟大的文章中阅读变形金刚的介绍。这篇文章的目的是展示我们做出的选择是如何改变我们的 NLP 结果的。

NLP 任务逐步建立在上游任务的基础上。我们将 POS 标签用于其他任务,例如实体检测。POS 标签中的一个小错误会完全改变这些下游任务的结果。在 MLK 演讲的例子中,根据我们将单词 demand 视为名词还是动词,这个句子呈现出完全不同的含义。

模型的质量只是我们决策的一个参数。在为我们的应用选择模型之前,我们还需要考虑成本、延迟和其他工程因素。基于 Transformer 的模型比上一代模型需要更多的计算资源。因此,“trf”模型很可能比“lg”模型贵很多个数量级。直到时间变压器模型变得像旧模型一样便宜,旧模型仍将在许多 NLP 应用中使用。

我也写在 sauravc.online

时间序列分析的偏自相关

原文:https://towardsdatascience.com/partial-autocorrelation-for-time-series-481a9cfa7526

描述什么是偏自相关及其在时间序列分析中的重要性

马库斯·斯皮斯克在 Unsplash 上的照片

介绍

在我之前的文章中,我们讨论了 自相关 的概念:

自相关是随机变量或数据在不同时间点(滞后)的相关性。自相关传递了不同滞后的数据的相似性,使我们能够推断出我们的时间序列的一些有趣的特征,如季节性和趋势。

如果你想了解更多关于自相关的知识,一定要看看我上面链接的帖子!

大多数人都听说过自相关,然而你可能不知道它不太流行的表亲【PACF】。在这篇简短而甜蜜的文章中,我想描述什么是 PACF,为什么它有用,并通过一个简单的例子来说明在 Python 中应用 PACF。

什么是偏自相关?

我们可以从解释 偏相关 开始。这是两个随机变量之间的 相关性 ,同时控制另一个(orm more)随机变量的影响,该随机变量影响我们相关的原始变量。

假设我们有三个随机变量:XYZXY、 的偏相关,排除Z的影响,数学上就是😗*

作者在 LaTeX 中创建的方程。

其中 r 是范围在-1 和 1 之间的 相关系数

偏自相关仅仅是时间序列在两个不同时间状态下的偏相关。更进一步,它是两个不同滞后的时间序列之间的相关性,不考虑任何中间滞后的影响。例如,滞后 2 的偏自相关只是滞后 1 没有解释的相关性。

为什么有用?

与自相关不同,偏相关还没有被我用于时间序列分析。然而,它的主要和非常重要的影响出现在构建预测模型时。

当拟合 自回归ARMAhttps://en.wikipedia.org/wiki/Autoregressive_integrated_moving_average模型时,PACF 用于估计 自回归 分量的数量/阶数,如 Box-Jenkins 程序所定义。这些模型可能是最常用的,并且在训练预测模型时通常会提供最佳结果。****

在以后的博客中,我计划解释自回归模型、ARMA 模型和 ARIMA 模型。在此之前,请参考上面的链接来了解这些算法。

现在让我们看一个应用 PACF 的 Python 例子。

Python 中的示例

我们将使用航空客运量数据集:

数据来自拥有 CC0 许可证的 Kaggle

作者代码要点。

作者用 Python 生成的图。

数据有明显的趋势和明显的年度季节性。

statsmodels Python 模块提供了一个 plot_pacf 函数来绘制不同滞后的 pacf,这也被称为相关图:**

作者代码要点。

作者用 Python 生成的图。

我们看到滞后 1 是高度相关的,在时间上还有其他高度相关的滞后,这可能是由于季节性影响。

然而,从这个图来看,如果我们建立一个预测模型,我们将选择多少个自回归函数是很不清楚的。因此,通常建议使用auto ARIMA等建模软件包对可能的参数进行网格搜索。

蓝色区域是滞后不再具有统计显著性https://en.wikipedia.org/wiki/Statistical_significance的地方。我们通常通过查看蓝色区域上方的自回归数来选择自回归数。

总结和进一步的思考

在这篇文章中,我们已经了解了部分自相关。这是一个时间序列与其自身滞后版本的相关性,不包括任何中间滞后。它的主要用途是估计预测模型(如 ARMA 和 ARIMA)的自回归分量的数量。

这篇文章中使用的完整代码可以在我的 GitHub 中找到:

******https://github.com/egorhowell/Medium-Articles/blob/main/Time Series/Time Series Tools/partial_autocorrelation.py

参考资料和进一步阅读

和我联系!

(所有表情符号都是由 OpenMoji 设计的——开源的表情符号和图标项目。执照: CC BY-SA 4.0******

使用 Scikit-learn 绘制部分相关性图

原文:https://towardsdatascience.com/partial-dependence-plots-with-scikit-learn-966ace4864fc

数据科学基础

走向可解释的人工智能

部分相关图(PDP)是深入了解特征和预测之间关系的有用工具。它有助于我们理解特定特征的不同值如何影响模型的预测。在本帖中,我们将学习 PDP 的基本知识,并熟悉一些使用 Scikit-learn 绘制 PDP 的有用方法。

特里·维里斯迪斯的照片

📦数据

在这篇文章中,我们将使用泰坦尼克号数据集 ( 该数据可通过 Seaborn 获得,并持有 BSD-3 许可证)。让我们导入库并加载数据集。然后,我们将训练一个随机森林模型,并评估其性能。

import numpy as np
import pandas as pd# sklearn version: v1.0.1
from sklearn.model_selection import train_test_split
from sklearn.ensemble import (RandomForestClassifier, 
                              AdaBoostClassifier)
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import roc_auc_score
from sklearn.inspection import (partial_dependence, 
                                PartialDependenceDisplay)import matplotlib.pyplot as plt
import seaborn as sns
sns.set(style='darkgrid', context='talk', palette='Set2')columns = ['survived', 'pclass', 'age', 'sibsp', 'parch', 'fare', 
           'adult_male']
df = sns.load_dataset('titanic')[columns].dropna()
X = df.drop(columns='survived')
y = df['survived']
X_train, X_test, y_train, y_test =  train_test_split(
    X, y, random_state=42, test_size=.25
)rf = RandomForestClassifier(random_state=42)
rf.fit(X_train, y_train)def evaluate(model, X_train, y_train, X_test, y_test):
    name = str(model).split('(')[0]
    print(f"========== {name} ==========")
    y_train_pred = model.predict_proba(X_train)[:,1]
    roc_auc_train = roc_auc_score(y_train, y_train_pred)
    print(f"Train ROC AUC: {roc_auc_train:.4f}")

    y_test_pred = model.predict_proba(X_test)[:,1]
    roc_auc_test = roc_auc_score(y_test, y_test_pred)
    print(f"Test ROC AUC: {roc_auc_test:.4f}")

evaluate(rf, X_train, y_train, X_test, y_test)

现在,让我们来学习 PDP 的基础知识。

📊部分相关图介绍

让我们以 PDP 为例开始讨论。我们将使用[PartialDependenceDisplay](https://scikit-learn.org/stable/modules/generated/sklearn.inspection.PartialDependenceDisplay.html)pclass绘制一个:

var = 'pclass'
PartialDependenceDisplay.from_estimator(rf, X_train, [var]);

随着特征值的变化,PDP 显示了对预测的平均影响。

在上图中,纵轴显示预测概率,横轴显示pclass值。绿线表示平均预测概率随着pclass值的变化而变化。我们看到,随着乘客等级从 1 增加到 3,平均生存概率降低。

为了更好地理解 PDP,让我们简单地看一下如何手动构建先前的情节。我们首先会发现pclass的独特价值。Then️对于每个唯一值,我们将用它替换训练数据中的pclass列中的值,并记录预测如何变化。

values = X_train[var].sort_values().unique()
print(f"Unique values: {values}")
individual = np.empty((len(X_train), len(values)))
for i, value in enumerate(values):
    X_copy = X_train.copy()
    X_copy[var] = value
    individual[:, i] = rf.predict_proba(X_copy)[:, 1]
individual

这里我们可以看到,如果我们更改pclass的值,训练数据集中每条记录的个体预测(也称为个体条件期望,ICE )将如何变化。通过平均这些预测值(部分相关,PD ),我们得到 PDP 的输入值。

individual.mean(axis=0)

通过绘制这些值和pclass的唯一值,我们可以重现 PDP。与使用PartialDependenceDisplay相比,我们自己从原始值中绘图给了我们更多的灵活性和对如何可视化 PDP 的控制。

sns.lineplot(x=values, y=individual.mean(axis=0), style=0, 
             markers=True, legend=False)
plt.ylim(0.2,0.6)
plt.ylabel("Partial dependence")
plt.xlabel(var);

如上所述的手动计算对于学习和理解概念非常有用,但是,在实际用例中继续使用这种方法是不现实的。在实践中,我们将使用 Scikit-learn 更高效的partial_dependence函数来提取原始值。

raw_values = partial_dependence(rf, X_train, var, kind='both')
raw_values

这里,我们指定了kind='both'来查看单个预测以及平均预测。如果我们只是在平均预测之后,我们可以用kind='average':

partial_dependence(rf, X_train, var, kind='average')

值得注意的是,partial_dependence(…, kind='both')partial_dependence(…, kind='average')的平均预测可能不总是与一些机器学习算法完全匹配,其中更有效的recursion方法可用于后者。

让我们检查一下我们手动计算的值是否与 Scikit-learn 的版本相匹配:

print(np.array_equal(raw_values['individual'][0], individual))
print(np.isclose(raw_values['average'][0], 
                 np.mean(individual, axis=0)))

很好,他们很配!

PartialDependenceDisplay允许我们绘制单个预测的子集以及平均值,以更好地理解数据:

n = 50
PartialDependenceDisplay.from_estimator(
    rf, X_train, ['pclass'], kind="both", n_jobs=3, subsample=n
)
plt.legend(bbox_to_anchor=(1,1));

这提供了更多的上下文。我们可以从原始值中自己复制一个类似的图表:

sns.lineplot(x=values, y=individual.mean(axis=0), style=0, 
             markers=True, legend=False)
sns.lineplot(data=pd.DataFrame(individual, columns=values)\
                    .sample(n).reset_index().melt('index'), 
             x='variable', y='value', style='index', dashes=False, 
             legend=False, alpha=0.1, size=1, color='#63C1A4')
plt.ylabel("Partial dependence")
plt.xlabel(var);

对于像pclass这样的离散变量,我们不必局限于线形图,甚至可以使用条形图,因为我们有充分的自由从原始值构建任何图表:

raw_df = pd.DataFrame(raw_values['individual'][0], 
                      columns=raw_values['values'])
sns.barplot(data=raw_df.melt(var_name=var), x=var, y='value')
plt.ylabel("Partial dependence");

我们很可能会考虑 PDP 的多个变量。了解了基础知识之后,让我们来看看绘制多个变量的 PDP 的几种方法。

📈多个变量的 PDP

由于我们的玩具数据集有少量的功能,让我们为每个功能绘制 PDP。我们将首先使用PartialDependenceDisplay:

n_cols = 2
n_rows = int(len(X_train.columns)/n_cols)fig, ax = plt.subplots(n_rows, n_cols, figsize=(10, 12))
PartialDependenceDisplay.from_estimator(rf, X_train, X_train.columns, ax=ax, n_cols=n_cols)
fig.suptitle('Partial Dependence Plots')
fig.tight_layout();

从这些图中,我们可以看到特征和预测之间的关系类型。一些关系看起来是线性的,而另一些则更复杂。

现在,让我们根据用partial_dependence提取的原始值绘制 PDP:

fig, ax = plt.subplots(n_rows, n_cols, figsize=(10,12), sharey=True)
for i, x in enumerate(X_train.columns):
    raw_values = partial_dependence(rf, X_train, i, kind='average')
    loc = i//n_cols, i%n_cols
    sns.lineplot(x=raw_values['values'][0], 
                 y=raw_values['average'][0], ax=ax[loc], style=0, 
                 markers=True, legend=False)
    ax[loc].set_xlabel(x)
    if i%n_cols==0:
        ax[loc].set_ylabel('Partial dependence')
fig.suptitle('Partial Dependence Plots')
fig.tight_layout()

或者,我们也可以绘制单个预测的子集,为我们提供平均值背后的更多背景信息:

plt.figure(figsize=(10,12))
for i, x in enumerate(X_train.columns):
    raw_values = partial_dependence(rf, X_train, i, kind='both')
    ax = plt.subplot(n_rows, n_cols, i+1)
    sns.lineplot(x=raw_values['values'][0], y=raw_values['average'][0], 
                 style=0, markers=True, legend=False, ax=ax)
    sns.lineplot(data=pd.DataFrame(raw_values['individual'][0], 
                                   columns=raw_values['values'][0])\
                        .sample(n).reset_index().melt('index'), 
                 x='variable', y='value', style='index', dashes=False, 
                 legend=False, alpha=0.1, size=1, color='#63C1A4')
    ax.set_xlabel(x)
    ax.set_ylabel('Partial dependence')
plt.suptitle('Partial Dependence Plots')
plt.tight_layout()

这些图帮助我们理解特征之间的关系以及它们对目标预测的影响,并检测模型学习的模式是否合理和可解释。PDP 还可以用来直观地评估和比较模型。在下一节中,我们将了解如何为多个模型绘制 PDP。

📉多个型号的 PDP

让我们再构建两个模型,并提取所有三个模型的原始值:

pclass_df = pd.DataFrame(columns=values)
pclass_df.loc['rf'] = partial_dependence(
    rf, X_train, var, kind='average'
)['average'][0]ada = AdaBoostClassifier(random_state=42)
ada.fit(X_train, y_train)
evaluate(ada, X_train, y_train, X_test, y_test)pclass_df.loc['ada'] = partial_dependence(
    ada, X_train, var, kind='average'
)['average'][0]knn = KNeighborsClassifier()
knn.fit(X_train, y_train)
evaluate(knn, X_train, y_train, X_test, y_test)
pclass_df.loc['knn'] = partial_dependence(
    knn, X_train, var, kind='average'
)['average'][0]
pclass_df

现在,我们可以绘制每个模型的部分相关性:

pclass_df = pclass_df.reset_index().melt('index')
sns.lineplot(data=pclass_df, x='variable', y='value', 
             hue='index');
sns.scatterplot(data=pclass_df, x='variable', y='value', 
                hue='index', legend=False)
plt.legend(bbox_to_anchor=(1, 1))
plt.ylabel("Partial dependence")
plt.xlabel(var);

对于 AdaBoost 和 K-最近邻分类器,预测概率几乎与乘客类别无关。

现在让我们对所有变量进行类似的比较:

summary = {}
fig, ax = plt.subplots(n_rows, n_cols, figsize=(10,12), sharey=True)for i, x in enumerate(X_train.columns):
    summary[x] = pd.DataFrame(columns=values)
    raw_values = partial_dependence(rf, X_train, x, kind='average')
    summary[x] = pd.DataFrame(columns=raw_values['values'][0])
    summary[x].loc['rf'] = raw_values['average'][0]
    summary[x].loc['ada'] = partial_dependence(
        ada, X_train, x, kind='average'
    )['average'][0]
    summary[x].loc['knn'] = partial_dependence(
        knn, X_train, x, kind='average'
    )['average'][0]

    data = summary[x].reset_index().melt('index')
    loc = i//n_cols, i%n_cols
    if i==1:
        sns.lineplot(data=data, x='variable', y='value', 
                     hue='index',ax=ax[loc]);
        ax[loc].legend(bbox_to_anchor=(1, 1));
    else: 
        sns.lineplot(data=data, x='variable', y='value', 
                     hue='index', ax=ax[loc], legend=False);
    sns.scatterplot(data=data, x='variable', y='value', 
                    hue='index', ax=ax[loc], legend=False)
    ax[loc].set_xlabel(x)
    if i%n_cols==0:
        ax[loc].set_ylabel('Partial dependence')fig.suptitle('Partial Dependence Plots')
fig.tight_layout()

通过不同的模型查看 PDP 有助于选择一个更合理、更易于解释的模型。

部分相关图提供了预测如何受特征变化影响的见解。PDP 的一个缺点是它假设特性是相互独立的。虽然我们已经看到了分类用例,但是 PDP 也可以用于回归。在这篇文章中,我们主要关注最简单的 PDP 形式:单向 PDP。对于渴望了解 PDP 更多信息的学习者来说,能够洞察特性之间交互的双向和/或三向 PDP 是值得研究的有趣主题。

照片由卡勒姆·肖Unsplash 上拍摄

您想访问更多这样的内容吗?媒体会员可以无限制地访问媒体上的任何文章。如果你使用 我的推荐链接成为会员,你的一部分会费会直接去支持我。

感谢您阅读这篇文章。如果你感兴趣,这里有我的一些其他帖子的链接:
◼️️ 解释 scikit-learn models with shap
◼️️会见 histgradientsboostingclassifier
◼️️从 ML 模型到 ML 管道
◼️️ 用 Python 绘制多个图形的 4 个简单技巧
◼️ 美化熊猫数据帧
简单的数据可视化

再见🏃 💨

R 中的部分比例奇模型

原文:https://towardsdatascience.com/partial-proportional-odd-model-in-r-dc5face36e40

R 系列中的统计

约书亚·索蒂诺在 Unsplash 上拍摄的照片

简介

我们之前已经执行了一个广义有序逻辑回归模型,其中解释变量的影响被允许在不同水平的响应变量之间变化。当我们发现一些预测变量违反比例奇数假设时,我们可以只允许这些变量的影响在不同水平的结果中变化。这种模型被称为部分比例奇数(PPO)模型。

背景示例

在常规逻辑回归中,假设遵循比例奇数(PO)假设。这种假设表明,每个预测值的系数在自变量的所有类别中都是相同的。部分比例奇数(PPO)假设正好相反。它指出,每个预测值的影响在所有类别的响应变量中不应该是相同的。

如果我们考虑一个广义的模型,所有四个预测变量的系数对于不同水平的健康状况是不同的。另一方面,如果我们考虑一个 PPO 模型,只有那些违反 PPO 假设的预测变量的系数是不同的。这就是广义模型和 PPO 模型的区别。现在让我们看看下面的例子。

我们从 UCI 资料库中提取了成人数据集。我们将健康状况作为反应变量,并将教育、性别、婚姻状况和家庭收入作为预测因素。健康状态有四种不同的顺序响应。

让我们假设在 PO 模型中,教育系数是“x ”,这个值是相同的,与健康状况水平无关。例如,健康状况从 1 变到 2 时,教育系数为“x ”,健康状况从 2 变到 3 时,教育系数仍为“x ”,依此类推。在 PPO 模型中,我们说这是不正确的。不同输出电平的系数可能不同。换句话说,将健康状况差的人转变为健康状况一般的人的教育效果不同于将健康状况一般的人转变为健康状况良好的人的教育效果。

数据集

UCI 机器学习知识库的成人数据集将是本案例研究的数据源。该数据集应该能够根据人口统计数据识别出大约 3 万人,比如他们的种族、教育程度、职业、性别、工资、如果他们有工作的话每周工作多长时间,以及他们的收入。

来自 UCI 机器学习知识库的成人数据集

这将需要我们对原始数据进行一些更改,以便我们可以在 r 中实现它。更改后,预测值和响应变量如下所示。

  • 学历:数值型,连续型。
  • 婚姻状况:二进制(0 表示未婚,1 表示已婚)。
  • 性别:二进制(0 代表女性,1 代表男性)。
  • 家庭收入:二进制(0 代表平均或低于平均,1 代表高于平均)。
  • 健康状况:依次(1 表示差,2 表示一般,3 表示好,4 表示优秀)

修改的数据

违反采购订单假设的变量

要执行部分比例奇数模型,我们首先需要确定哪些变量违反了比例奇数假设。一旦确定了变量或变量集,我们就可以使用这些变量的输入运行相同的模型,并要求模型在不同的结果中改变这些保护器的效果。

首先,我们可以使用下面的命令找出违反 PO 假设的变量。我们需要将健康状态变量声明为一个使用 clm()命令的因子。

data$healthrev 型号< - clm(可接受性 _ 代码~价格 _ 代码+维护 _ 代码+安全 _ 代码,数据=数据)
标称 _ 测试(型号)

婚姻状况和家庭收入违反 PO 假设

一旦执行,我们可以得到对数似然、AIC 统计以及似然比卡方检验数据。这里,很明显,婚姻和家庭收入变量违反了 PO 假设,其中 p <0.05 (also marked with a star). The PO assumption holds true for both education and gender.

定义了 R 中的 PPO 模型

广义模型和 PPO 模型之间的区别是在“平行=假”部分后提到了违反 PO 假设的变量。剩下的和概化模型差不多。

结果解读

PPO 模型总结

结果窗口非常相似。在这里,教育和性别是固定的,因为它们的影响是不允许变化的,我们还观察到婚姻和家庭系数有三个不同的值。为了更好地理解这里的输出,读者可以在下一篇文章中浏览这个通用模型的输出。

https://mdsohel-mahmood.medium.com/generalized-ordinal-regression-model-in-r-930834edae10

我们还可以获得如下的奇数比矩阵。

奇数比率

对于教育程度和性别,所有三个模型的系数估计值和比值比都是相同的。婚姻状况的系数分别为-0.667、-0.447 和-0.121,比值比为 0.513、0.638 和 0.885。

  • 已婚者比未婚者处于第 1 类(健康状况较差)的可能性高 0.513 倍。
  • 已婚者处于 2 类或以下(健康状况较差)的可能性是未婚者的 0.638 倍。
  • 已婚者处于 3 级或以下(健康状况较差)的可能性是未婚者的 0.885 倍。

总的来说,可以说结婚降低了一个人处于或低于特定健康状态的几率,增加了一个人处于或高于特定健康状态的几率。显然,一个人结婚与更好的健康状况联系在一起的事实是非常明显的。

对于家庭收入,解释变得有点棘手。第一个系数是正的,而第二和第三个是负的。奇数比分别为 1.587、0.985 和 0.642。

  • 较高的家庭收入使 1 类健康状况的几率增加 1.587 倍。
  • 较高的家庭收入使 2 级或以下健康状况的几率降低 0.985。
  • 较高的家庭收入使 3 级或以下健康状况的几率降低 0.642。

对于性别而言,它似乎不是健康状况的重要预测因素。

结论

当我们执行广义有序逻辑回归模型和部分比例奇数模型时,我们看到了一些有趣的结果。在本文中,我们解释了部分比例奇数模型的系数和旧比率。这种方法只适用于当我们想改变不同水平的结果之间的一些预测因素的影响。

数据集确认

杜瓦博士和格拉夫博士(2019 年)。UCI 机器学习知识库[http://archive . ics . UCI . edu/ml]。加州欧文:加州大学信息与计算机科学学院(CC BY 4.0)

感谢阅读。

https://mdsohel-mahmood.medium.com/membership https://mdsohel-mahmood.medium.com/subscribe

算法责任的参与式方法

原文:https://towardsdatascience.com/participatory-approaches-to-algorithmic-responsibility-c349d0e96200

人工智能伦理系列

一个让人们参与负责任地使用算法决策系统(ADS)的框架

来源:图片由作者提供

没有公民参与的规定,负责任的算法系统的框架将是不完整的(和不太负责任的)。

前提很简单:在协商民主中,公民应该有权决定如何使用他们的数据,并对影响他们福祉的政策拥有发言权。这种政策影响也应该延伸到由私人实体部署的算法决策系统(ADS)。

高风险广告是算法决策系统,直接或间接影响个人或团体可以获得的利益、惩罚或机会。这里的风险是个人或群体受到决策系统输出的伤害,导致不正确或不公平的结果。高风险的广告实施目前正面临越来越大的监管压力。

在这篇文章中,我提出了一个广泛的框架,让公民能够负责任地设计、开发和部署算法决策系统。该框架旨在挑战公民社会对危险广告一无所知的现状。

公共利益团体如何为更好的算法透明度和问责制开辟道路

我们首先承认公共利益集团在提供需求方影响供应方问责机制中发挥的现有作用。一个参与性框架应该考虑到公共利益团体在推进负责任的广告和实现其使命中的作用。

公益团体由致力于保护和倡导公民社会和边缘群体权利的各种个人和组织组成。他们通常带来领域的专业知识,他们的工作可能有报酬,也可能没有报酬。公共利益团体可以包括倡导者、智囊团、调查记者、学者等。

有几个例子表明,公共利益团体在让有问题的广告实施变得可见方面发挥了关键作用(例如美国的展示了司法累犯算法中的偏见)以及帮助关闭有害系统(例如荷兰的禁止入侵性和不准确的欺诈检测算法)。

这些例子说明了需求方问责制,在缺乏明确或迅速的监管行动的情况下,非国家行为者通常以自下而上的方式承担责任,挑战被认为有过错的利益相关者。

摘自职业记者协会的“记者处理算法指南”(来源)。关于有问题的算法系统的新闻汇编可以在这里找到。

公共利益团体在形成算法决策系统的政策和监管方面也发挥着关键作用。例如,在加拿大,来自学术界和倡议者的审查人工智能在移民系统中的使用的呼吁 有助于制止有问题的做法并导致负责任的使用指南的创建要求对公共部门部署的广告进行影响评估和质量保证措施

尽管 ADS 监管仍处于初级阶段,但公共利益团体可以通过继续向监管者施加压力来帮助发展供应方问责制。

更广泛地说,政府可以从民间社会早期参与反倾销监管的形成过程中获益。来自 OECD 的一份详细的研究表明,实施代表审议流程可以带来更好的政策结果

参与陷阱以及如何避免它们

在深入探讨如何增强参与式方法以实现透明和负责任的算法决策之前,我们应该首先检查一些常见的参与式陷阱:

  1. “自由审计师问题”,即依赖公共利益集团(而非国家行为者)的劳动来提供问责。
  2. 公众参与的符号化和“参与洗”。

第一个陷阱是 主要依靠公共利益团体和更广泛的公民社会的劳动来实施(在缺乏配套监管的情况下相当困难)算法透明和问责机制。人们担心行动的重担将落在为公众利益充当“自由审计员”的公民肩上。虽然一些公共利益团体在维护民间社会和/或特定少数群体的利益方面有具体的任务授权,但问责制的主要负担应由政府行为者承担。他们负责起草和执行监管,并可以部署激励和控制措施,以保证负责任地使用和开发算法决策系统。

话虽如此,公共利益集团应该继续在加强需求方透明度和问责制方面发挥关键作用。通过提供对 ADS 文档的特权访问****渠道(特别是对于高风险系统),有机会授权给公共利益团体并减轻劳动负担。目前,访问 ADS 文档构成了公共利益团体的一个主要痛点,即使在访问应该受到“信息自由”法保障的情况下

**另一个要避免的陷阱是公众参与算法决策系统的设计和开发的符号化由于这些系统与受信任的公共利益团体有关联,它们似乎可以在公众认知中获得合法性。这在以下情况下可能会有问题:广告的所有者公开分享被咨询方的名称,但不披露咨询的结果,或者如果公民的建议没有反映在系统的设计中。简而言之,公众咨询并不能保证更好的广告效果。还应该考虑如何披露民间社会的参与,同时避免“参与清洗”。

建立更好的参与框架

一个关于负责任的算法决策系统的详尽框架应该概述公共利益团体、受影响团体和更广泛社会的角色和参与模式,同时避免上述参与陷阱。

算法决策系统的参与式方法有 7 个指导原则。

算法决策系统参与式方法的指导原则。来源:图片由作者提供。

参与性框架应该:

1。以负责任的算法决策原则为中心

参与式方法应该支持负责任地使用算法决策系统。这意味着确保一个广告产生公平可解释的结果,稳健地执行,维护数据安全隐私,以及避免对受影响用户的伤害。****

为了维护这些价值观,参与性机制应该补充和加强已经到位的透明度问责制机构措施。

节选自重温负责任的算法决策(ADS)框架。来源:图片由作者提供。

一个负责任的 ADS 框架的北极星是对人类代理的保护,包括允许受影响的个人质疑系统结果的追索权的可能性。

2.促进受影响群体的代理和赋权

在负责任的 ADS 框架的基础上,参与式方法应该能够增强相关群体的能力,特别是直接或间接受到相关系统影响的个人。

Arnstein 的“公民参与的阶梯”是一个思考如何提高公民控制和代理水平的基础框架。该框架已在数据管理框架中被采用,以支持负责任地使用、收集和管理数据。

********

左:参与的阶梯。改编自 Arnstein,S.R. 1969 ( 来源)。右图:参与数据管理的框架,阿达·洛芙莱斯研究所,2021 年(来源)

在负责任地使用算法决策系统的背景下,“参与阶梯”框架也是有用的。在最低级别的机构中,受影响的群体被告知广告如何决定他们的结果。接下来,他们可以咨询相关广告,获得反馈和关注。他们可以参与广告的设计、开发和监控,确保反馈得到反映。他们还可以在 ADS 生命周期中与系统所有者合作,支持负责任的租户。最后,在最高级别的机构中,受影响的团体被授权挑战广告结果,要求资源并影响系统的设计。

这些级别的参与可以被认为是构建模块。我们首先需要告知公民不同的算法决策系统如何影响他们的结果——这是一个最低的参与门槛,遗憾的是,今天在大多数国家都没有这一要求。一旦公民被告知正在发生的事情,他们就可以在决策过程中被咨询、参与和授权。

3.贯穿广告的整个生命周期

有效的参与式参与模式,尤其是对于高风险决策系统,应该从试运行阶段开始,此时使用算法系统解决手头问题的意图实现。在此阶段,系统所有者应该研究手头的问题,研究不同的解决方案及其利弊,就解决方案提案达成一致,并为 ADS 制定路线图。确保以负责任的方式设计和策划广告的最有效机会发生在这个阶段。

试运行阶段的参与性参与应有助于回答以下问题:

  • 拟定的广告是否充分解决了问题或增加了程序的价值,同时将伤害风险降至最低?
  • 首先,广告是否合适(相对于其他决策系统)?
  • 提议的风险评估和缓解计划是否已经过潜在受影响群体或领域专家的验证?

除了试运行阶段,参与式参与有助于确保 ADS 已准备好启动,它有效地支持代理和求助,并且它实际上支持良好的决策。

高风险算法决策系统的内部和外部利益相关者之间基于生命周期的参与模型(说明性的)。来源:图片由作者提供。

在算法系统的整个生命周期中,注册可以是一个非常有用的工具,可以访问与算法系统相关的过程和结果信息,也可以很好地支持获取参与式方法的输入和结果。

ADS register是一种治理工具,能够实现与一套算法决策系统相关的结果和流程的透明度。它基本上是不同利益相关者共享的关于算法系统在其整个生命周期中被治理的披露的日志。想了解更多关于算法寄存器的信息,请查看我之前的文章

4.适应当地环境

推荐的参与模式应受 ADS 的风险水平和应用该模式的当地环境的影响。

现有的政策提案计划采用一种风险比例的方法来监管广告。这种方法也可以扩展到推荐的参与模式。值得注意的是,所有 ADS 系统,无论风险如何,都应该有一个最低披露要求,以帮助验证风险级别是否正确分类。相关民间团体应可获得该文件,以进行评论或质疑。

研究现有审议程序中目前使用的现有参与模式,并将其作为参与性参与 ADS 的基础,可能会有所帮助。

参与模式可以是:

  • 正式(纳入治理和监管)或非正式(无法律约束力的约定要求);****
  • 集体(所有相关方达成一套决定或建议)或分散(代表独立的意见或建议)。****

不同的参与模式(非详尽)。来源:图片由作者提供。

5.为互动提供有意义的信息

参与参与机制的个人或团体应:

  • 在推广负责任的广告方面有一个清晰明确的任务;
  • 获得相关文档和培训,帮助他们完成任务。

例如,如果一个可能会受到拟议广告影响的个人被要求对其影响评估发表意见,他们首先需要了解广告将如何工作。他们需要访问与其设计、测试和监控相关的信息。这些信息需要以个人能够理解的格式呈现。对于大多数人来说,访问原始代码甚至复杂的系统架构本身可能并不是有意义的信息。这里的个人希望了解影响他们的决策是如何做出的,以及有什么护栏来确保负责任地做出决策。

算法寄存器可以是一个有用的工具,使参与的个人和团体能够访问关于被评估系统的有意义的信息。****

6.披露参与结果

披露参与结果是避免“参与清洗”的一种方式,尤其是在参与的个人或团体不同意与广告相关的某些做法或决策的情况下。

具有集体产出的正式参与模式的情况下,如公民投票或公民陪审团,参与结果通常是公开的。

主持非正式公开活动的实体通常没有义务披露受聘个人或团体的推荐。

理想情况下,ADS 所有者应该对他们利用的参与性方法以及随之而来的建议和决定保持透明。

还应该有第三方机制来清楚地证明 ADS 是否通过了不同参与模式的批准。这可以是证书或审计报告的形式。

7.补偿参与努力

最后,参与性的努力应该得到与投入的努力成比例的补偿。提出这一措施是为了避免大公司和国家行为者受益于并依赖公民社会成员的劳动,从而为有问题的广告带来透明度和问责制。

这一提议可能是最难实施的,因为一方面,这可能会阻碍组织采用参与式方法,另一方面,薪酬可能会扭曲参与式激励机制。

政府应该释放足够的资金,在需要时支持正式的参与模式。补偿参与者或将其作为公民义务的一部分是有法律先例的。

针对公益团体的定向资助可能是补偿非正式参与模式的潜在工具。

在私营部门,付费招募非正式用户研究和公民陪审团的参与者已经是一种惯例(查看来自微软的这个例子)。希望扩大 ADS 覆盖范围的公司也应该考虑与建立负责任的流程相关的参与成本。

把这些放在一起。

下面的数据库为 ADS 生命周期的每个阶段的参与模式提供了详细的建议。正确的参与模式在很大程度上取决于系统的风险水平和当地的实施环境。

负责任的广告生命周期管理数据库——由作者创建。

走向实施

参与机制应该是算法监管的基石。不幸的是,最近的监管提案中缺乏公民参与的条款。

对欧盟人工智能法案的一个批评是,它没有正确考虑面对风险广告时的公民授权。正如阿达·洛芙莱斯研究所指出的那样,拟议中的法规“未能为个人参与创造有意义的空间”。此外,初稿没有关于受影响的团体协商或追索权的规定。由于公共利益集团的压力,已经增加了关于追索权的新条款,但是我们还没有看到关于告知最终用户他们受人工智能系统的约束并保证他们获得解释的权利的条款。

由于在广告生命周期中缺乏“供应方”执法来赋予公民权力,我们可以同时考虑“需求方”机制,如认证和第三方审计。

将价值观集中在负责任地使用广告上的公司将有机会以身作则。在缺乏实施监管的情况下,公共登记册可以成为邀请和鼓励参与模式的一个很好的工具。

关于该主题的其他资源:

基于我的研究生论文“ 超越黑盒*”(MIT 2021)的一系列关于负责任的 AI 的一部分。提出的想法是基于几个在管理、部署和评估人工智能系统方面有直接经验的从业者的反馈和支持而开发的。我正在分享和开源我的发现,以使其他人能够轻松地研究并为这个领域做出贡献。*

使用数据上下文划分十亿行体育数据表

原文:https://towardsdatascience.com/partitioning-a-billion-row-table-of-sports-data-using-data-context-6eb94bb9f18a

我的团队如何帮助一家数据驱动的初创公司处理十亿行的表格

布拉登·科拉姆在 Unsplash拍摄的照片

在本文中,您将学习如何在对数据库进行分区时使用数据背后的语义。这可以极大地提高应用程序的性能。而且,最重要的是,您将发现应该根据您独特的应用程序域来定制您的分区标准。

正如我在这里已经提到的,我已经与一家初创公司合作开发了一个 web 应用程序,供体育专家做出决策和探索数据。该应用程序支持任何运动,但我们位于欧洲,而欧洲人热爱足球。全世界每天玩的数百个游戏中的每一个都有数千行。仅仅几个月,我们应用程序中的Events表就达到了 5 亿行!

通过了解体育专家如何查询我们的数据,我们可以智能地对数据库进行分区。这个新表上的平均时间提高了 20 到 40 倍。所有查询的平均时间提高了 5 到 10 倍。

现在让我们深入研究这个场景,了解为什么在对数据库进行分区时不能忽略数据上下文。

介绍背景

我们的体育应用程序提供原始数据和聚合数据,尽管采用它的专业人士更喜欢后者。底层数据库包含来自多个提供商的数万亿字节的复杂、非结构化、异构数据。因此,最大的挑战是设计一个可靠、快速、易于探索的数据库。

应用领域

在这个行业中,许多提供商向他们的客户提供对最重要的足球比赛的访问。具体来说,它们为您提供与比赛中发生的事情相关的数据,如进球、助攻、黄牌、传球等等。包含这些数据的表格是迄今为止我们处理过的最大的表格。

VPS 规格、技术和架构

我的团队一直在开发后端应用程序,它提供了最重要的数据探索特性。我们采用运行在 JVM ( Java 虚拟机)之上的 Kotlin v1.6 作为编程语言, Spring Boot 2.5.3 作为框架,Hibernate5 . 4 . 32 . final作为 ORM ( 对象关系映射)。我们选择这种技术堆栈的主要原因是速度是最重要的业务需求之一。因此,我们需要一种能够利用大量多线程处理的技术,而 Spring Boot 被证明是一种可靠的解决方案。

我们通过由 Dokku 管理的 Docker 容器在 16GB 8CPU VPS 上部署了我们的后端。它最多可以使用 15GB 的内存。这是因为 1gb 的 RAM 专用于基于 Redis 的缓存系统。我们添加它是为了提高性能,避免后端因重复操作而过载。

数据库和表结构

至于数据库,我们决定选择 MySQL 8 。一个 8GB 和 2 个 CPU 的 VPS 当前托管数据库服务器,它支持多达 200 个并发连接。后端应用程序和数据库位于同一个服务器群中,以避免通信开销。我们设计数据库结构是为了避免重复并考虑到性能。我们决定采用关系数据库,因为我们希望有一个一致的结构来转换从提供者那里收到的数据。通过这种方式,我们将体育数据标准化,使其更易于探索并呈现给最终用户。

在撰写本文时,该数据库包含数百个表格,由于我签署了 NDA ,我无法一一呈现。幸运的是,一张表足以彻底分析我们为什么最终采用了您将要看到的基于数据上下文的分区。当我们开始在 Events 表上执行繁重的查询时,真正的挑战来了。但是在开始之前,让我们看看事件表是什么样子的:

事件表模式

正如您所看到的,它没有涉及很多列,但是请记住,出于保密的原因,我不得不省略其中的一些。但是这里真正重要的是parameterIdgameId列。我们使用这两个外键来选择参数的类型(例如,进球、黄牌、传球、点球)以及发生这种情况的比赛。

性能问题

Events表在短短几个月内就达到了 5 亿行。正如我们在这篇文章中已经深入讨论过的,主要问题是我们需要在查询中使用 slow 来执行聚合操作。这是因为比赛中发生的事情并不重要。相反,体育专家希望分析汇总的数据来发现趋势,并基于这些数据做出决策。

此外,虽然他们通常分析整个赛季或最近 5 或 10 场比赛,但用户通常希望从他们的分析中排除一些特定的比赛。这是因为他们不希望一场比赛打得特别差或特别好,使他们的结果两极分化。我们不能预先生成汇总数据,因为我们必须对所有可能的组合都这样做,这是不可行的。因此,我们必须存储所有数据,并即时汇总这些数据。

了解性能问题

现在,让我们深入到导致我们不得不面对的性能问题的核心方面。

百万行的表很慢

如果您曾经处理过包含数亿行的表,您就会知道它们天生就很慢。你甚至不能想到在如此大的表上运行JOIN s。然而,您可以在合理的时间内执行SELECT查询。当这些查询涉及简单的WHERE条件时尤其如此。另一方面,当使用集合函数或IN子句时,它们变得非常慢。在这些情况下,他们可以轻松地花费 80 秒,这实在是太多了。

索引是不够的

为了提高性能,我们决定定义一些指标。这是我们寻找性能问题解决方案的第一种方法。但是,不幸的是,这导致了另一个问题。索引需要时间和空间。这通常是无关紧要的,但在处理如此大的表时就不是这样了。事实证明,基于最常见的查询定义复杂的索引需要几个小时和几千兆字节的空间。此外,索引是有帮助的,但不是魔术

基于数据上下文的数据库分区解决方案

由于我们无法用自定义索引解决性能问题,我们决定尝试一种新方法。我们与其他专家交谈,在网上寻找解决方案,阅读基于类似场景的文章,最终决定对数据库进行分区是正确的方法。

为什么传统分区可能不是正确的方法

在划分所有最大的表之前,我们研究了 MySQL 官方文档和有趣文章中的主题。尽管我们都同意这是应该走的路,但是我们也意识到应用分区而不考虑我们特定的应用领域将是一个错误。具体来说,我们知道在对数据库进行分区时找到合适的标准是多么重要。一些分区专家告诉我们,传统的方法是根据行数进行分区。但是我们想找到比这更智能更有效的东西。

深入应用程序领域以找到划分标准

通过分析应用领域和采访用户,我们学到了重要的一课。体育专家倾向于分析同一场比赛中各场比赛的汇总数据。例如,足球中的比赛可以是联赛、锦标赛,或者是你可以赢得奖杯的单项比赛。有成千上万种不同的比赛。欧洲最重要的是欧冠、英超、拉力加、意甲、德甲、荷甲、西甲、西甲。

这意味着我们的用户很少考虑来自不同比赛的数据。此外,他们更喜欢逐季探索数据。换句话说,他们很少离开在特定季节进行的体育比赛所代表的背景。我们的数据库结构用一个名为SeasonCompetition的表表达了这个概念,它的目标是将一场比赛与一个特定的赛季相关联。因此,我们意识到一个好的方法是将我们的大表分成与特定的SeasonCompetition实例相关的子表。

具体来说,我们为这些新表定义了以下名称格式:<tableName>_<seasonCompetitionId>

因此,如果我们在SeasonCompetition表中有 100 行,我们就必须将大的Events表分割成较小的Events_1Events_2,...,Events_100表。根据我们的分析,这种方法在一般情况下会导致相当大的性能提升,尽管在极少数情况下会引入一些开销。

将标准与最常见的查询相匹配

在编码和启动脚本来执行这个复杂且可能不返回的操作之前,我们通过查看后端应用程序执行的最常见查询来验证我们的研究。但是这样做的时候,我们发现绝大多数的查询只涉及一个赛季比赛中的比赛。这使我们相信我们是对的。因此,我们使用刚才定义的方法对数据库中的所有大型表进行了分区。

我们的应用程序领域中的一个常见查询示例

SELECT AVG('value') as 'value', SUM('minutes') as 'minutes'
FROM 'Events'
WHERE 'parameterId' = 15 AND 'gameId' IN(223,241,245,212,201,299,187,304,187,205)
GROUP BY 'teamId'

现在,让我们研究一下这个决定的利弊。

赞成的意见

  • 在一个最多包含 50 万行的表上运行查询比在一个包含 5 亿行的表上运行查询要高效得多,尤其是在聚合查询方面。
  • 较小的表更容易管理和更新。添加一个列或索引在时间和空间上甚至比不上以前。另外,每个SeasonCompetition都是不同的,需要不同的分析。因此,它可能需要特殊的列和索引,而前面提到的分区允许我们轻松地处理这个问题。
  • 提供商可能会修改一些数据。这迫使我们执行删除和更新查询,这在如此小的表上要快得多。加上它们总是只和某个特定SeasonCompetition的某些游戏相关,所以我们现在只需要只在单个桌上操作就可以了。

骗局

  • 在对这些子表进行查询之前,我们需要知道与感兴趣的游戏相关的seasonCompetitionId。这是因为表名中使用了seasonCompetitionId值。因此,我们的后端需要在运行查询之前通过查看分析中的游戏来检索这些信息,这是一个很小的开销。
  • 当一个查询涉及一组包含许多SeasonCompetitions的游戏时,后端应用程序必须在每个子表上运行一个查询。因此,在这些情况下,我们不能再在数据库级别聚合数据,我们必须在应用程序级别进行聚合。这在后端逻辑中引入了一些复杂性。同时,我们可以并行执行这些查询。此外,我们可以高效、并行地聚合检索到的数据。
  • 管理一个拥有数千个表的数据库并不容易,而且在一个客户机中进行探索也是一项挑战。类似地,在每个表中添加新列或更新现有列也很麻烦,需要定制脚本。

基于数据上下文的分区对性能的影响

现在让我们看看在新的分区数据库中执行查询时获得的时间改进。

  • 平均情况下的时间改进(仅涉及一个SeasonCompetition的查询):从 20x 到 40x
  • 一般情况下的时间改进(涉及一个或多个SeasonCompetitions的查询):从 5 倍提高到 10 倍

最后的想法

对数据库进行分区无疑是提高性能的一个很好的方法,尤其是在大型数据库上。然而,不考虑您的特定应用程序域就这样做可能是一个错误,或者导致低效的解决方案。相反,花时间通过采访专家和用户以及查看执行最多的查询来研究领域对于构思高效的划分标准是至关重要的。本文向您展示了如何做到这一点,并通过一个真实的案例研究展示了这种方法的结果。

感谢阅读!我希望我的故事对你有所帮助。如果有任何问题、意见或建议,请随时联系我。

利用模式归纳提取用户高亮文本模式

原文:https://towardsdatascience.com/pattern-induction-what-is-a-pattern-part-1-79ee1bd5adc6

一步一步的指导如何快速准确地从几个文档中提取文本,只需少量用户给定的高亮文本

提取包含季度收入的文本模式。图片作者。

你还记得上一次你不得不花费数不清的时间从成百上千的文档中找到你需要的确切信息吗?这是常见使用情形的几个示例:

  • 财务分析师需要从季度报告和市场分析师报告中提取公司的收入,例如“500 亿美元的收入”或“150 亿美元的收入”。
  • 质量保证专业人员提取产品编号以解决客户投诉,例如“MR-9483”或“MR-2930”。
  • 一名记者试图从 FBI 新闻稿的数据集中提取刑事犯罪的增加和减少,例如“犯罪增加了 6%”或“犯罪减少了 5.5%”

从大量文本和文档中提取概念和关键信息的过程既麻烦又耗时。我们在 IBM 的团队已经将多年的研究和工程技术提炼为模式归纳,这是一个人工智能驱动的工具,旨在大大加快这一过程。

模式归纳的高级概述。图像由 Yannis Katsis。

给定几个提取示例和额外的用户反馈,模式归纳学习所提供示例背后的模式,并使用它们从输入文档中提取相似的模式信息。

模式归纳:使用用户突出显示的文本示例从一组文档中提取货币金额。图片作者。

在本文中,我们将讨论以下内容:

  • 什么是图案
  • 如何使用模式归纳,利用用户提供的高亮文本示例提取模式。
  • 关于如何在 IBM Cloud 上设置一个帐户来访问 Watson Discovery,然后访问模式归纳特性的补充部分。

什么是模式?

在我们开始之前,让我们首先从理解模式的含义开始。一个模式在概念上是一系列具有一定规律性的记号或单词。例如,考虑以下财务用例中的示例:

如上所述,这些实例遵循一种模式,可以描述为以单词“revenue”或“income”开头的文本,后跟冒号和货币金额

在我们如何设计模式归纳来读取这样的文本的上下文中,模式是在记号上的正则表达式,其中模式中的记号可以:

  • 来自字典(例如,从文本中的标记生成的字典,例如包含货币比例的字典)。
  • 始终是一个精确的字符串文字。
  • 成为众所周知的命名实体的一部分(如货币、位置等。)使用人工智能技术识别。

字典、文字和命名实体如何捕获文本中的标记。图片作者。

使用字典、文字、命名实体和正则表达式,我们可以使用规则简洁地描述模式,这些规则是令牌上这种正则表达式的序列。以下是描述我们财务用例的基本模式的规则:

其中<货币金额>是一个命名实体。

诸如记者、金融分析师、刑事调查人员或非技术背景的人经常发现手工制作这些规则具有挑战性。创建这些规则通常需要大量的试验和错误,以及一定程度的技术经验,如对语言概念的更深入理解。模式归纳从用户提供的文本示例中的模式自动生成这些规则。通过自动化手动构建规则,我们的模式归纳实现将帮助您专注于提炼提取的文本。

如何使用模式归纳提取模式

在上一节中,我们简要地向您解释了模式的结构。在这一节中,我们将带您了解如何使用模式归纳来提取文本模式。

模式归纳是一个人在回路系统,它结合了领域专家的专业知识和自动学习能力,以快速学习高质量的提取器。在这个系统中,我们使人类能够快速地提供例子和对系统建议的反馈,以实现特定领域的结果和高覆盖率和高质量。

让我们从用户的角度向您介绍一个典型的模式归纳工作流。出于示例的目的,我们继续使用相同的场景,我们的目标是从财务文档中提取收入信息,例如“200 亿美元的收入”。

先决条件:在开始之前,请创建一个模式归纳项目,遵循本博客末尾的“尝试模式归纳”一节中概述的几个简单步骤。

第一步:突出几个例子。一旦您完成了先决条件,首先突出显示几个属于您想要提取的模式的字符串(参见下面图 1 中的例子)。一旦你提供了足够多的例子(我们推荐这个版本至少有两个),系统将学习所提供的例子背后的一般模式。

提示:我们鼓励您从提供两个示例开始,等待系统完成学习,然后再对学习结果提供反馈和/或直接突出显示更多示例。

图 1:用户突出显示了几个例子。图片作者。

第二步:检查模型发现的提取,并回复系统的建议。一旦系统处理了突出显示的示例并学习了您的第一个提取器版本,它会用两种类型的信息更新屏幕(参见下面的图 2):首先,它会用绿色突出显示当前学习的提取器预测的所有文本片段,供您检查。第二,系统探查一系列是/否问题供您验证,以理解您的意图并纠正任何错误的提取。

提示:我们鼓励您回答尽可能多的问题(最好是所有问题),因为这些问题是系统有策略地选择的,以帮助区分您可能想要提取的潜在模式。

图 2:系统返回一些建议供用户验证。图片作者。

第三步:等一会儿……一旦系统学习到一个准确的提取器(由少量模式组成),它会相应地通知你。

图 3:后端算法告知已经学习了精确的算法。图片作者。

第四步:回顾摘录的例子。 为了确保提取的准确性,您可以单击“查看示例”窗格并检查提取的示例列表。如果您发现任何错误或遗漏的摘录,您可以通过重复上述步骤 1-3 来提供更多示例和/或反馈。

图 4:用户评论提取的模式。图片作者。

第五步:保存你的模式。如果一切看起来都正确,你现在可以进入过程的最后阶段,包括保存学习到的模式以备将来使用。只需在左上角为您的图案键入一个名称,然后单击右上角的“保存图案”按钮。保存模式时,选择“文本”等字段进行丰富。

步骤 6:在原始文档的上下文中可视化模式提取。如果您导航到左上角的“改进和定制”选项卡,您会看到一个搜索栏。点击搜索栏中的“Enter”键将会显示一个段落列表(图 5)。

图 5:查看“改进和定制”选项卡中的段落

您可以为任何一个搜索结果选择“查看文档中的段落”,在右下角,选择“打开高级视图”将显示原始 PDF 文档。选择任何一个保存的模式将会直接在文档中突出显示提取内容(图 6)。

图 6:我们刚刚创建的模式中的收入短语在文档的上下文中突出显示。

补充部分:试用模式归纳

按照这些简单的步骤尝试模式归纳:

第一步:创建一个 IBM 帐户,并按如下所述建立一个 Watson Discovery 项目:在 Watson Discovery 上注册一个 IBM 帐户,然后导航到您的云仪表盘:【https://cloud.ibm.com】T2。点击屏幕右上角的“创建资源”按钮。

图 7:你的云账户主页。图片作者。

在你的左边搜索“沃森发现”,点击标题为“沃森发现”的服务。选择适合您的计划,如 premium、plus 等。

图 8:创建 Watson 发现服务。图片作者。

创建服务后,导航到 https://cloud.ibm.com/resources 的。在这里,您可以查看最近创建的服务,如下所示。点击您的“沃森发现”服务,然后点击“启动沃森发现”按钮。这将把您重定向到服务,在那里您可以为您的提取任务创建一个项目。

图 9:资源列表。请注意“服务和软件”部分下的“Watson Discovery”服务。图片作者。

要创建一个项目,提供一个项目名称,选择“文档检索”作为项目类型(参见图 10),然后单击“下一步”。完成上传数据集的步骤。

图 10:选择“文档检索”作为您的项目类型。图片作者。

文档上传后,建议本教程启用智能文档理解功能。单击以管理您的数据集(参见图 11 中的左上角)。选择“识别字段”选项卡,然后选择“预训练模型”。然后通过选择“提交”来确认选择,并通过点击右上角的按钮“应用更改并重新处理”来应用更改。

图 11:启用智能文档理解特性

第二步:现在,为了跟进,你可以尝试在这里下载以下任何一个数据集:

  • 从演示中,您可以尝试从 IBM 新闻稿数据集中提取收入和现金流,“23 亿美元的收入”或“450 亿美元的现金流”。点击这里
  • 用联邦调查局的新闻发布数据集挑战自己,提取不同类型犯罪相关的百分比的增加和减少,“犯罪上升 5%”或“犯罪下降 6%”。点击这里

数据上传完成后,导航到“改进和定制”屏幕,在这里您可以通过点击“教授领域概念”下的“模式”来访问模式归纳(参见图 12)。

图 12:访问模式归纳。图片作者。

点击“Create”来创建一个新的模式,选择要从中创建模式的文档(或者让系统从您的文档集合中随机选择文档),然后点击“Next”(参见图 13)。这将导航到模式归纳,在这里您可以开始创建模式。

图 13:选择用来创建模式的文档。图片作者。

结论

在本文中,我们向您介绍了模式归纳,这是一个帮助用户使用突出显示的文本示例快速准确地提取文本模式的工具。模式归纳只需要很少的努力就可以启动提取文本的过程。它也不需要用户编写一行代码。

补充资源

如果您正在将模式归纳应用到您的文档中,并且您正在寻找关于使用模式归纳的更全面的用户指南和最佳实践,请查看下一篇帖子:

作者:前田哈纳菲博士、亚尼斯·卡西斯博士、李蕴瑶博士、比卡尔帕·纽帕内博士

PostgreSQL 中的模式匹配和正则表达式

原文:https://towardsdatascience.com/pattern-matching-and-regular-expressions-in-postgresql-cd1fa76e5f3f

了解如何在 PostgreSQL 中使用正则表达式

来自像素的图像

在本文中,我将讨论 PostgreSQL 中的模式匹配和正则表达式。PostgreSQL 是一个开源的关系数据库管理系统,在最近几天受到了广泛的欢迎。PostgreSQL 可以快速轻松地安装在内部或流行的云平台上,如 Azure、AWS 等。使用任何数据库时需要掌握的一项重要技能是学习和编写 SQL 查询。PostgreSQL 还支持本地 ANSI SQL 查询,对于初学者来说,开始为 PostgreSQL 数据库编写查询非常容易。

编写查询的一个重要方面是用户应该能够过滤和选择系统需要访问的数据。额外或不必要的数据会增加带宽并降低查询性能。因此,SQL 提供了一个过滤谓词“WHERE ”,用户可以使用它来过滤查询并选择只与过滤谓词匹配的结果。例如,如果我们想要选择在计算机科学部门工作的用户,那么我们将编写如下 SQL 查询。

SELECT * FROM users WHERE department = ‘Computer Science’

请注意,过滤器谓词使用等号(=)和要在用户表的 department 列中匹配的精确短语。然而,在某些情况下,可能需要基于精确短语的一部分进行过滤。换句话说,可能会要求用户过滤所有与短语的一部分匹配但不完全匹配的记录。比如让我们考虑有两个系,计算机科学和计算数学。现在,过滤这两个部门的查询可以编写如下。

SELECT * FROM users WHERE department LIKE ‘Comput%’

注意 LIKE 操作符是如何在查询中使用的,以便只过滤后面跟有“%”符号的精确短语的一部分。“%”运算符是一个通配符运算符,表示可以在匹配的短语后选择任何内容。

PostgreSQL 中的正则表达式

到目前为止,我们已经学习了如何使用 WHERE 子句过滤查询,以及如何使用 LIKE 操作符匹配模式。在正常的 SQL 操作中,LIKE 操作符很好,但是在过滤大型数据库时,LIKE 操作符似乎存在一些性能问题。此外,LIKE 运算符的过滤条件仅限于通过仅包含通配符(%)来查找模式。为了克服这个问题,PostgreSQL 提供了一种使用正则表达式进行模式匹配的高级方法。正则表达式已经在编程语言中大量使用了很长时间,然而,在 SQL 语句中使用这些正则表达式,使得查询高度动态化,并且在大型数据库中执行得更好。PostgreSQL 中的正则表达式是使用波浪号( ~ )运算符实现的,并使用了“”。* "作为通配符。

图 1 —在 PostgreSQL 数据库中使用正则表达式

如上图所示,我们在 PostgreSQL 中使用了正则表达式,使用了代字号( ~ )运算符和通配符'*’。该查询将从 GreaterManchesterCrime 表中选择具有有效 CrimeID 的所有记录。因为模式条件只是通配符,所以它将从表中获取所有记录。另外,在 PostgreSQL 中编写正则表达式时需要注意的另一个要点是,模式匹配语句总是以' ^ 操作符开始,以' \(** '符号结束。这两个操作符标记了正则表达式语句的开始和结束。综上图,当使用通配符过滤所有记录时,表达式可以实现为" **^.*\) ”。

正则表达式—以数字或字符开头的字符串

在上一节中,我们已经学习了如何通过使用通配符来实现正则表达式。现在,我们将向前迈进一步,尝试获取以字符或数字开头的记录,用于列 CrimeID。

在 PostgreSQL 中,字母数字字符可以通过使用模式“【A-Z】”或“【A-Z】”进行匹配,具体取决于我们尝试匹配的大小写。这里需要注意的重要一点是,由于 PostgreSQL 是区分大小写的,因此,必须指定我们试图在模式中匹配的确切大小写。同样,对于匹配的数字,我们可以笑脸使用“【0–9】”或“ \d ”。现在我们已经对使用 Regex 过滤字符和数字有了一些了解,让我们继续在数据库上实现它。

对于匹配字符–

SELECT * FROM GreaterManchesterCrime WHERE CrimeID ~ ‘^[A-Z].*$’

图 2 —在 PostgreSQL 中使用正则表达式—大写模式匹配

如上图所示,CrimeID 字段已经过筛选,仅包含 ID 以大写字母开头的记录。注意通配符“”是如何。* "再次用于表示 SQL 语句中第一个字符之后的任何内容。

要过滤语句中的数字,我们可以简单地编写如下查询。

SELECT * FROM GreaterManchesterCrime WHERE CrimeID ~ ‘^[0–9].*$’

图 3 —在 PostgreSQL 中使用正则表达式—数字模式匹配

从上图中,我们可以看到,只有以数字开头的 CrimeID 记录被过滤,以匹配正则表达式标准。

正则表达式-以重复数字或字符开头的字符串

在本文的上一节中,我们已经了解了如何使用正则表达式语法在 PostgreSQL 中编写 SQL 查询,以及如何在被过滤的字符串的第一个位置过滤字符和数字。现在,让我们给需求增加一些复杂性,并过滤开头超过一个字符或数字的记录。PostgreSQL 通过在花括号内提供计数,使得指定重复次数变得非常容易。或者,您也可以多次重复相同的字符模式匹配,以匹配您的标准。例如,我们希望过滤所有以两个字符开头的记录。这个查询可以写成如下形式。

SELECT * FROM GreaterManchesterCrime WHERE CrimeID ~ ‘^[A-Z] [A-Z].*$’

运筹学

SELECT * FROM GreaterManchesterCrime WHERE CrimeID ~ ‘^[A-Z] {2}.*$’

图 4 —在 PostgreSQL 中使用正则表达式—重复大写模式匹配

正如您在上图中看到的,我们使用了花括号中的数字来表示在列中查找模式的次数。这使得查询非常动态,因为您可以指定想要在查询中搜索的任意数量的字符,并且您将得到结果。同样,您也可以实现相同的逻辑来重复数值。在这种情况下,查询可以写成如下形式。

SELECT * FROM GreaterManchesterCrime WHERE CrimeID ~ ‘^[0–9]{2}.*$’

图 5 —在 PostgreSQL 中使用正则表达式—重复数字模式匹配

上图显示了在过滤重复数值时使用正则表达式实现模式匹配。

正则表达式-以字符和数字开头的字符串

在本文的最后一部分,我们将结合目前为止所看到的内容。我们将构建查询并匹配模式,该模式将过滤以字符开头后跟数字的记录。要形成该规范的查询,只需在正则表达式中组合字母和数字字符的条件。SQL 语句可以写成如下形式。

SELECT * FROM GreaterManchesterCrime WHERE CrimeID ~ ‘^[A-Z][0–9].*$’

图 6 —在 PostgreSQL 中使用正则表达式—字母数字模式匹配

结论

在本文中,我们深入探讨了如何用 PostgreSQL 中的正则表达式编写 SQL 语句。正则表达式帮助我们编写动态 SQL 语句,用于匹配数据库中某一列的模式。当您不需要查询中的精确匹配,但是希望查找所有符合条件的记录时,模式匹配非常有用。PostgreSQL 中的模式匹配也可以使用 SQL LIKE 操作符来实现,但是搜索是有限的。正则表达式提供了更大的灵活性,并允许动态控制匹配模式的范围。要了解更多关于在 PostgreSQL 中使用正则表达式进行模式匹配的知识,我建议您阅读官方教程。

用于复制机器学习特征的模式

原文:https://towardsdatascience.com/patterns-for-reproducing-machine-learning-features-949e95544e35

如何通过可再现的特征来再现机器学习应用中的流程

典型软件应用的可靠性需要确定性的输出。对于生活在随机性和不确定性领域的机器学习(ML)应用来说,很难保证同样的效果。然而,通过使它们可再现来确保应用程序的大部分是可能的,这是可靠性的先决条件。每个人都知道应该在任何地方一致使用的随机种子。然而,除此之外,在应用程序的培训、服务和评估阶段,还有其他一些组件应该修复。

在这篇文章中,我们探索了机器学习中可重复性的设计模式。设计模式的灵感来自《机器学习设计模式》这本书。我强烈推荐这本书给 ML 从业者。

设计模式是:

  • 转换:如何在不同阶段保持特性的一致性,比如简单机器学习应用的训练、服务和评估
  • 可重复分割:如何在训练、验证和测试数据集中分割特征,以确保无泄漏训练,而不管数据量如何
  • 特征存储:如何以可复制的方式获取特征,并在不同的机器学习作业中重用

可重复特征分割

训练模型需要在训练集、测试集和验证集中拆分特征。通常的做法是使用随机方法将特征分成这些集合。它不适合现实情况,因为它假设一个特征集中的行是不相关的,而在许多情况下并非如此。

有必要使用分布良好的列和确定性散列函数。首先,可以从捕获行之间相关性的列开始。如图 1 所示,然后可以使用该列散列的尾部数字来分割数据集。有一系列散列函数可用于此目的,例如 Farm Hash 。然而,在继续进行剩余的过程之前,必须对分割进行测试,例如使用 Kolmogorov-Smirnov 测试。有时一个列是不够的,在这种情况下,应该探索更多的列。在极端情况下,可以对所有列执行拆分。

图 1:使用列创建训练/测试/验证数据集。图片由作者、

在某些情况下,对散列进行正常的随机分割是不合适的。就时间序列数据而言,基于日期/时间序列或间隔的定制分割更为可取。对于具有偏斜分布的列的情况,分层抽样将更适合。尽管对于大量数据,这种分割不是必须的。

一致变换

真实机器学习应用的输入并不按原样使用。相反,它们经历了一系列产生特征的转变。这些特征用于训练机器学习模型。在服务或评估期间,输入是新的,但是需要经过相同的转换集。

严肃的团队不应该将输入数据提取和特征生成视为机器学习工作流的数据管道的一部分。此外,采用/设计一种建模框架将是有益的,该建模框架实际上允许对模型对象内的转换进行编码,并且作为建模图/流水线的一部分,作为训练和预测任务的一部分来执行转换。常见的机器框架,如 scikit-learnTensorFlow 等。这样做将允许模型创建或操作的可靠特征生成,并且只要转换操作简单,就保持 ML 工作流简单。

虽然输入数据处理和特征变换的单独处理在所有情况下都是必要的,但是打包变换函数不适合复杂的机器学习情况。对于处理大量信息的应用程序来说尤其如此。在这种情况下,有必要在分布式数据处理环境中运行特征变换,这对于训练任务或服务任务来说可能不是推荐的环境。试图在同一环境中打包这些操作将需要复杂的设置,这会增加操作成本和/或迫使选择不合适的解决方案堆栈。

图 2:模型训练和服务过程,除了其他典型函数,如 fit、predict 等,还包括 transform 函数。适用于简单的应用程序。图片由作者提供。

功能存储

对于机器学习应用程序来说,特征工程是至关重要的,但也是最耗时的过程之一。特性从组织数据中获取领域和业务洞察力。真实的工业应用表明,使用正确的特征是成功的机器学习配方的最关键的成分。没有一个准确的、一致的、治理良好的过程,就不可能获得正确的特性。一个不成熟的组织应用特定的过程来生成特性,这是实现生成和消费特性的期望过程的障碍。由于重复,特设流程极其浪费,并且难以采用适当的治理流程。

如图 3 所示,特征库设计模式将特征创建过程与使用这些特征的模型的开发和服务相分离。使用一组特征生成平台上的特征生成库来处理表格、事件和文件形式的原始数据。库-平台对根据数据-特征类型对而变化。从集合的角度来考虑特性是有好处的,集合的大小取决于使用它的模型的性质。对于批量模型,数据集将会很大,而对于在线模型,数据集可能只包含一些最近的数据点。有必要在生态系统中包含创建、更新、删除、接收、版本控制、编目、治理和缓存特性的功能。这方面有几款产品可以走中心组件,比如顶点 AI 特征店HopsWorks 特征店Databricks 特征店等。

图 3:机器学习应用的特征存储解耦特征生成和消费。图片由作者提供。

特征存储来自特征服务的特征工程。这允许在模型开发期间,特征开发和创建与特征服务异步发生。一旦特征被添加到特征存储中,它们就可以立即用于训练和服务,并且被存储在来自各种 ML 应用的单一位置。这确保了模型训练和服务之间的一致性。功能库是一个版本控制的功能库。这允许新的 ML 项目从目录的特征选择过程开始,而不是从零开始执行特征工程。随着越来越多的功能添加到商店中,它允许组织实现规模经济效应。

备注

本文关注的是特征工程的设计模式,以重现机器学习流程。一致拆分模式将主数据集拆分为训练、测试和验证数据集,以确保即使数据集不断增长,训练中使用的样本也不会用于评估或测试。转换设计模式通过明确捕获将模型输入转换为模型特征的过程,解决了模型训练管道和模型服务管道之间的数据准备依赖性的再现性。最后,我们引入了特征库设计模式来解决复杂 ML 作业中特征的可再现性和可重用性。

有没有更多你觉得有必要的设计模式?请分享。

PCA 102:该不该用 PCA?使用多少组件?如何解读它们?

原文:https://towardsdatascience.com/pca-102-should-you-use-pca-how-many-components-to-use-how-to-interpret-them-da0c8e3b11f0

对 PCA 分析中一些中间概念的探讨

约翰·施诺布里奇在 Unsplash 上的照片

主成分分析(PCA)是数据科学领域中最常用的降维技术之一。由于它的重要性,提高我们对它的理解是更好地使用这项技术的必要条件。

然而,在介绍性课程中经常可以看到 PCA 是如何制作的以及它代表了什么,然而,有些方面通常不会在这些课程中进行评论。一些改进 PCA 的想法只能在论文中找到,因此许多数据科学家没有接触过它。

这篇文章旨在提高对 PCA 的理解,提供工具来回答,至少在一定程度上,以下问题:

  • 我如何知道是否应该对我的数据使用 PCA?
  • 我应该使用多少个主成分?我怎么知道有多少是相关的?
  • 我能解释主要成分并对它们有某种可解释性吗?

因此,首先,我们将(真正)简要描述一下 PCA 是如何构建的,之后,我们将直接回答这些问题。这款笔记本的代码(甚至更多)可以在 KaggleGithub 上获得。

这篇文章的灵感来自于论文[1]。

主成分分析的简要回顾

PCA 算法相当简单,在互联网上有广泛的解释。因此,这一节将快速概述算法。它的实现可以在上面链接的笔记本中找到。

因此,算法如下:

  • 计算数据集的协方差矩阵
  • 提取矩阵的特征向量和特征值
  • 选择所需维度的数量,过滤特征向量以匹配它,并按照相关的特征值对它们进行排序
  • 将原始空间乘以上一步生成的特征向量。

提醒一下,在应用 PCA 之前对数据进行标准化是一个很好的做法。这就避免了所有方差只在一个分量上的情况,因为方差只在一个或两个更大范围的变量上。

评估我们是否应该对空间应用 PCA

判断我们是否应该在机器学习问题上使用 PCA 的通常程序包括应用有和没有 PCA 的建模过程,并查看哪一个产生最好的结果。由于机器学习是一个非常经验性的领域,这对于几种方法来说是常见的。

然而,有一种更好的方法来评估主成分分析的有效性,而不必多次拟合昂贵的模型。

首先,重要的是要知道什么时候主成分分析产生的主成分是无用的:什么时候你的特征彼此不相关。

因此,验证 PCA 有用性的第一个方法是验证数据集的不相关程度。如果它是非常不相关的,那么你有一个不应用主成分分析的好理由。

有几个指标可以用来评估这一点。这里,我将关注两个有界的度量。无界度量需要一个比较度量,因此更难处理。

分散系数

该度量的几何解释是,数据集在空间上创建的超大体积随着数据集变量之间的相关性增加而减小。因此,我们期望该度量的非常低的值将指示使用 PCA 是一个好的选择。

从代数的角度来看,为了计算这个度量,我们只需要计算数据集的相关矩阵的行列式。此指标的最大值是空间的维数,最小值为零。

下面的代码片段包含使用和不使用标准化来计算该指标的代码:

def scatter_coefficient(X, normalize=True):
    corr = np.corrcoef(X, rowvar=False)
    if normalize:
        return np.linalg.det(corr) / np.trace(corr)
    else:
        return np.linalg.det(corr)

这种度量与另一种称为广义方差的度量非常相似,它使用协方差矩阵而不是相关性。

Psi 指数

该度量寻找从相关矩阵获得的特征值的幅度。如果变量不相关,每个 PC 倾向于解释与单个变量一样多的方差,并且它们的特征值倾向于 1。因此,越靠近 y = 1 行,面积越小,数据集的不相关程度越高。

对于此指标,值越大越好。其最大值为 p(p-1),最小值为零。它由以下等式给出:

我们可以用下面的代码很容易地计算出来:

def psi_index(X, normalize=False):
    corr = np.corrcoef(X, rowvar=False)

    # Eigenvalues and eigenvectors from the correlation matrix
    eig_val, eig_vec = np.linalg.eig(corr)
    idx = eig_val.argsort()[::-1]
    eig_val = eig_val[idx]if normalize:
        p = X.shape[0]
        return np.sum((eig_val - 1)**2) / (p*(p-1))
    else:
        return np.sum((eig_val - 1)**2)

选择主成分

选择要使用的主成分的通常方法是设置解释方差的阈值,例如 80%,然后选择产生尽可能接近该阈值的解释方差的累积和的成分的数量。

这种方法有两个主要问题:

  • 它需要一个阈值的主观选择。在大多数情况下,选择 80%或 90%的门槛没有公平的动机,它们是任意的。
  • 方差某些部分可能是纯噪声而不是信号。你无法事先知道,你选择的这个阈值是否只去除了噪声,如果是,去除了多少,或者你实际上是否去除了信号。

选择组件数量的一种方法是使用排列测试。

置换测试是统计学中一种非常常见的测试,它包括置换您正在评估的实例,以查看您从假设测试中获得的见解是否仍然成立。为了更好地解释排列测试,我强烈推荐这个网站

因此,在处理 PCA 时,策略如下:

  • 对您的数据运行 PCA,并保存每个主成分解释的方差
  • 定义多个测试,例如 1000 个,并且对于每个测试,不替换地抽取彼此独立的数据集列
  • 对这些测试中的每一个再次运行 PCA,并保存每个主成分解释的方差
  • 将原始数据集的解释差异与置换版本进行比较

这里的想法是,通过对数据集的列进行采样,我们将去相关特征,因此,在这个新的采样数据集上,PCA 不应该生成良好的变换。因此,我们期望,如果主成分是相关的,在 PC 上置换解释的方差不应该大于最初解释的方差。

让我们把它编码起来,看看我们能得到什么。

编码排列测试

首先,让我们创建一个函数来消除数据列的相关性:

def de_correlate_df(df):
    X_aux = df.copy()
    for col in df.columns:
        X_aux[col] = df[col].sample(len(df)).values

    return X_aux

现在,让我们保存我们最初解释的方差比:

pca = PCA()
pca.fit(df)
original_variance = pca.explained_variance_ratio_

现在,我们将定义测试的数量,并创建一个矩阵来保存我们的实验结果:

N_permutations = 1000
variance = np.zeros((N_permutations, len(df.columns)))

最后,让我们生成新的数据集并保存结果:

for i in range(N_permutations):
    X_aux = de_correlate_df(df)

    pca.fit(X_aux)
    variance[i, :] = pca.explained_variance_ratio_

有了这些,我们就可以计算 p 值,看看哪些成分是相关的:

p_val = np.sum(variance > original_variance, axis=0) / N_permutationsfig = go.Figure()
fig.add_trace(go.Scatter(x=[f'PC{i}' for i in range(len(df.columns))], y=p_val, name='p-value on significance'))fig.update_layout(title="PCA Permutation Test p-values")

例如,如果将此应用于在 BSD 许可下在 UCI 机器学习库上免费获得的乳腺癌数据集[2],结果将是前 5 个主成分是相关的,如图所示:

在乳腺癌数据集上获得置换检验的 p 值。由作者创作。

链接的笔记本有一些使用这种方法的实验,我强烈建议读者看看这在实践中的工作。事实上,这种方法似乎更适用于具有更好有用性度量的数据集。

解释主要成分

当手头的问题需要解释的时候,我在使用 PCA 时看到了很多阻力。这是一个合理的阻力,因为,例如,如果你要用 SHAP 来解释你的模型,它将输出主成分对模型的影响,而不是直接输出单个特征的影响,在大多数情况下,这是我们感兴趣的。

因此,在我们的 PCA 中增加一点可解释性可以在日常生活中给我们很大帮助。首先,我们将看一个可以用来估计每个主成分与我们的每个特征的相关程度的度量。

装货

首先,我们需要理解载荷的概念。假设每个分量是变量的线性组合,我们有:

这个组合的重量就是我们所说的主分量的载荷。如你所见,它们表明了每个变量在每个主成分上的权重。

寻找“归一化”载荷的一种常用方法是将特征向量乘以特征值的平方根。

负载指数(IL)

这是我们将用来估计每个变量和每个主成分之间的相关性的指标。它将使用载荷的平方值和特征值的平方值,如下所示:

其中 sj 分量是变量 j 的标准差,uij 是第 j 个变量对第 I 个 PC 的加载。

给定协方差矩阵,此指标计算起来很简单,下面的代码片段有一个非优化的实现:

def index_of_loadings_matrix(cov_matrix):
    eig_val, eig_vec = np.linalg.eig(cov_matrix)
    r = np.zeros((len(eig_vec), cov_matrix.shape[0]))
    for i in range(len(eig_vec)):
        for j in range(cov_matrix.shape[0]):
            r[i][j] = (eig_vec[i][j]**2 * eig_val[i])**2 / np.sqrt(cov_matrix[j][j])return r

这将返回一个矩阵,其中估计了变量和 PCs 之间的“相关性”。

结论

这里介绍的技术可以帮助我们在分析和日常建模实践中更好地利用 PCA。

有更多的评估有用性的方法,更多类型的排列测试,以及更多解释结果的方法。链接笔记本提供了一些其他的度量和方法,以及一些数据集的初步分析。

我希望通过这篇文章和笔记本,你可以开始提高你对这个工具的知识,而不仅仅是入门课程中通常教授的知识。

[1] Vieira,Vasco,估计主成分分析显著性的排列检验(2012)。计算生态学和软件。2.103–123.

[2] O. L. Mangasarian 和 W. H. Wolberg:“通过线性规划进行癌症诊断”,《暹罗新闻》,第 23 卷,第 5 期,1990 年 9 月,第 1 和 18 页。

主成分分析-增白 vs ZCA-增白:一个数字二维视觉

原文:https://towardsdatascience.com/pca-whitening-vs-zca-whitening-a-numpy-2d-visual-518b32033edf

标准化数据的矩阵方法

作者图片

记得使用右边的“关注”按钮来关注我→:你会收到新文章的通知,并帮助我达到 100 个关注者的目标:)

O 在数据科学和机器学习领域处理新数据的第一步通常是标准化——或者“扩展”——你的数据。最常见的程序——通常称为 Z 值归一化 —就是从每个列特征中去除平均值,然后除以其标准偏差。剩下的要素具有 0 均值和 1 方差。这使得每个特征在规模上是“可比较”。下一步是检查你的数据,找出潜在的共线性,也就是相关性。如果您的数据完全不相关,则得到的协方差矩阵将是单位矩阵,即每列都有一个方差,并且与其他列完全去相关。但这在现实世界中从未发生过。

白化数据的过程在于变换,使得变换后的数据具有作为协方差矩阵的单位矩阵。这个过程也称为“球形化”数据。它有许多应用,最著名的是主成分分析中的降维。球形化数据使数据在“拓扑上”更简单,这反过来提高了数值分辨率的稳定性和收敛速度。

有很多方法可以“白化”你的数据,大多数常见的方法被称为 PCA 白化ZCA 白化。

请记住:白化你的数据不仅“标准化”,它还去相关的每个功能。

数据矩阵和协方差矩阵

设 X 是你的数据矩阵,m 个样本为行,n 个特征为列。让我们首先将数据沿列居中,使得每个特征列的平均值为 0。由于数据矩阵沿列具有 0 均值,我们可以简单地将样本协方差矩阵写成:

协方差矩阵

PCA-增白

协方差矩阵 C 是实数且对称的,因此可以使用特征分解对角化:这意味着我们可以将协方差矩阵重写为:

协方差矩阵的特征分解

其中 D 是对角线矩阵,特征值在对角线上,P 是正交非正规矩阵,也称为“旋转”矩阵。
然后,我们可以使用 D 和 P 来白化我们的数据矩阵 X,使用线性变换:

PCA-白化变换矩阵

这种变换实际上是用旋转 P^T 去相关数据,然后用 D^{-1/2}缩放新特征,使它们具有单位方差。

以下是变换数据的协方差矩阵是单位矩阵的证明:

W_PCA 白化数据矩阵 X 的证明

ZCA 美白

ZCA 白化非常接近 PCA 白化:不同之处在于额外的旋转——或正交变换。

当 PCA-白化完成应用时,

ZCA 美白是通过应用:

ZCA-白化变换矩阵

在数学上,可以示出 W_{ZCA}是矩阵 a,使得 X'=XA^T 具有作为协方差的单位矩阵(如预期的,因为我们正在白化我们的数据),最小化

白化后的数据尽可能接近原始数据

主成分分析白化和 ZCA 白化的关系

让我们回顾一下我们所做的:首先使用特征分解来分解输入数据矩阵 X 的中心数据协方差矩阵:

然后,通过以下变换完成 PCA 白化:

并且 ZCA 白化是通过应用变换来完成的:

这样就很容易将两种变形联系起来:

****这最后一个等式表明,ZCA 白化数据只是具有附加旋转的 PCA 白化数据(因为特征分解中的 P 是旋转矩阵)。

我应该用哪一个

如你所见,PCA-白化和 ZCA-惠特宁等于旋转 P,但我们可以使用任何其他旋转:事实上,有无限多的变换可以白化我们的数据。

比如除了 PCA-美白和 ZCA-美白,维基百科还提到了 Cholesky 美白

你应该使用的方法,一如既往,取决于你想要什么:
— ZCA 白化是最大化白化和原始向量的每个分量之间的平均互协方差的唯一过程
— PCA 白化是唯一的球形化过程,其基于作为基础测量的互协方差,最大化原始向量 X 的所有分量在球形化向量 X’的每个分量中的积分或压缩。

也就是说:如果你计划减少数据的维度,使用 PCA-白化。如果你想你的白化数据接近你的原始数据,使用 ZCA 白化。

用 numpy 增白

既然我们已经建立了理论,让我们来实践吧!

我们创建一个仲裁数据矩阵 X,并计算它的协方差矩阵:

然后,我们使用特征分解来分解协方差矩阵:

我们可以检查协方差矩阵确实是 C = PDP^T:

现在我们计算 D 的逆矩阵的平方根:因为 D 是对角矩阵,所以它的逆矩阵非常简单:

再次检查我们的计算是否有效:

我们现在可以使用上面的公式计算我们的变换矩阵,一个用于 PCA 白化,一个用于 ZCA 白化:

测绘

现在我们已经有了所有需要的矩阵,是时候做一些绘图了:

你应该得到这样一个数字:

作者图片

我们可以注意到几件事:
—为了绘制原始数据,我们将单位矩阵作为变换矩阵传递,因为 X = I X
—PCA 白化数据和 ZCA 白化数据的协方差矩阵确实是单位矩阵, 因此,我们的白化在两种情况下都有效
—你可以说服自己 PCA 白化数据和 ZCA 白化数据在旋转后相等
—你可以看到 ZCA 白化数据的颜色方向与原始数据明显相同,而 PCA 白化数据可能有任何方向,与原始数据相差很大。

感受转变

让我们通过添加几个向量来表示初始的 x 轴和 y 轴,以及一个彩色的圆来提高我们的可视化。

你可以把这些点想象成原始数据集中的新数据:就像我们的数据矩阵 X 一样,它们有一个 X 和 y 值,所以它们也位于与原始数据相同的初始空间中。

由于白化数据是通过应用线性变换完成的,因此我们可以对这些点(箭头和圆)应用相同的变换,并可视化它们是如何被变换的:

作者图片

再一次,你可以看到 PCA 和 ZCA 只是一个轮换阵容。还要注意圆圈上颜色的大致方向。

为了完整起见,让我们把一切都叠加起来:

作者图片

外卖食品

以下是你应该从这篇文章中记住的;
-白化,也称为球化,包括转换你的数据,使其具有单位矩阵作为其协方差矩阵,即具有单位方差的非相关数据
-有无限种方法来白化你的数据,两种最常见的方法被称为 PCA-白化和 ZCA-白化
-PCA-白化和 ZCA-白化相当于旋转
-如果你计划减少数据的维度,请使用 PCA-白化。如果希望白化后的数据接近原始数据,请使用 ZCA 白化。

如果你喜欢这篇文章,并且觉得它内容丰富, 请订阅,并查看我的其他文章,它们可能会说服你订阅:)

**</300-times-faster-resolution-of-finite-difference-method-using-numpy-de28cdade4e1> https://medium.com/analytics-vidhya/deep-dive-into-seaborn-meet-the-datasets-8d08755a320b

既然你一直往下读,这里有一个函数来白化你的数据,把它全部包装起来:**

带有 Plotly 破折号的 PDF 解析仪表板

原文:https://towardsdatascience.com/pdf-parsing-dashboard-with-plotly-dash-256bf944f536

介绍如何在下一个仪表板中阅读和显示 PDF 文件。

PDF 解析器(图片来自作者)

介绍

我最近对在我的自然语言处理(NLP)项目中使用 PDF 文件产生了兴趣,你可能想知道,为什么? PDF 文档包含大量信息,可以提取这些信息并用于创建各种类型的机器学习模型以及在不同数据中寻找模式。问题? PDF 文件在 Python 中很难处理。此外,当我开始在 Plotly dash 上为一个客户创建仪表板时,我几乎找不到关于如何在 Plotly 仪表板中摄取和解析 PDF 文件的信息。这种信息的缺乏将在今天改变,我将与你分享你如何上传和使用 PDF 文件在一个 Plotly 仪表板!

包裹

import pandas as pd 
from dash import dcc, Dash, html, dash_table
import base64
import datetime
import io
import PyPDF2
from dash.dependencies import Input, Output, State
import re
import dash_bootstrap_components as dbc
import spacy
from spacy.lang.en.stop_words import STOP_WORDS
from string import punctuation
from heapq import nlargest

上面的大多数包是您在部署 Dash 应用程序时通常会找到的。例如, dash 是我们将使用的主要 Plotly API,而 dcc、dash、html 和 dash_table 是我们添加功能所需的一些主要方法。当阅读 Python 中的 PDF 时,我倾向于使用py pdf 2但是还有其他 API 可以探索,你应该总是使用最适合你的项目的 API。

支持功能

支持功能有助于增加 dash 应用程序的交互性。我为这个应用程序创建了两个不同的类,允许 PDF 被分开解析。第一个类是 pdfReader 类。 这个类中的函数都与将 PDF 读入 Python 并将其内容转换成可用形式有关。此外,一些功能能够提取 PDF 中固有的元数据(即创作日期、作者等。)**

*class pdfReader:    
    def __init__(self, file_path: str) -> str:
        self.file_path = file_path

    def PDF_one_pager(self) -> str:
        """A function that returns a one line string of the 
            pdfReader object.

            Parameters:
            file_path(str): The file path to the pdf.

            Returns:
            one_page_pdf (str): A one line string of the pdf.

        """
        content = ""
        p = open(self.file_path, "rb")
        pdf = PyPDF2.PdfFileReader(p)
        num_pages = pdf.numPages
        for i in range(0, num_pages):
            content += pdf.getPage(i).extractText() + "\n"
        content = " ".join(content.replace(u"\xa0", " ").strip().split())
        page_number_removal = r"\d{1,3} of \d{1,3}"
        page_number_removal_pattern = re.compile(page_number_removal, re.IGNORECASE)
        content = re.sub(page_number_removal_pattern, '',content)

        return content

    def pdf_reader(self) -> str:
        """A function that can read .pdf formatted files 
            and returns a python readable pdf.

            Returns:
            read_pdf: A python readable .pdf file.
        """
        opener = open(self.file_path,'rb')
        read_pdf = PyPDF2.PdfFileReader(opener)

        return read_pdf

    def pdf_info(self) -> dict:
        """A function which returns an information dictionary
        of an object.

        Returns:
        dict(pdf_info_dict): A dictionary containing the meta
        data of the object.
        """
        opener = open(self.file_path,'rb')
        read_pdf = PyPDF2.PdfFileReader(opener)
        pdf_info_dict = {}
        for key,value in read_pdf.documentInfo.items():
            pdf_info_dict[re.sub('/',"",key)] = value
        return pdf_info_dict

    def pdf_dictionary(self) -> dict:
        """A function which returns a dictionary of 
            the object where the keys are the pages
            and the text within the pages are the 
            values.

            Returns:
            dict(pdf_dict): A dictionary of the object within the
            pdfReader class.
        """
        opener = open(self.file_path,'rb')
        #try:
        #    file_path = os.path.exists(self.file_path)
        #    file_path = True
        #break
        #except ValueError:
        #   print('Unidentifiable file path')
        read_pdf = PyPDF2.PdfFileReader(opener)
        length = read_pdf.numPages
        pdf_dict = {}
        for i in range(length):
            page = read_pdf.getPage(i)
            text = page.extract_text()
            pdf_dict[i] = text
            return pdf_dict

    def get_publish_date(self) -> str:
          """A function of which accepts an information dictionray of an object
              in the pdfReader class and returns the creation date of the
              object (if applicable).

              Parameters:
              self (obj): An object of the pdfReader class

              Returns:
              pub_date (str): The publication date which is assumed to be the 
              creation date (if applicable).
          """
          info_dict_pdf = self.pdf_info()
          pub_date= 'None'
          try:
              publication_date = info_dict_pdf['CreationDate']
              publication_date = datetime.date.strptime(publication_date.replace("'", ""), "D:%Y%m%d%H%M%S%z")
              pub_date = publication_date.isoformat()[0:10] 
          except:
              pass
          return str(pub_date)*

创建的第二个类是 pdfParser ,这个类执行我们希望在 Python 中的 PDF 上使用的主要解析操作。这些功能包括:

  1. get_emails() -能够在一串文本中找到所有邮件的功能。
  2. 这个函数可以在一串文本中定位日期。对于这个控制面板,我们将找到下载 PDF 的日期。
  3. get_summary() -能够基于单词的重要性和用户希望从原始文本中放入摘要中的文本的百分比来创建文本主体的摘要的功能。
  4. get_urls() -这个函数将在一个文本字符串中找到所有的 URL 和域名。
*class pdfParser:
    def __init__(self):
        return

    @staticmethod 
    def get_emails(text: str) -> set:
        """A function that accepts a string of text and
            returns any email addresses located within the 
            text

            Parameters:
            text (str): A string of text

            Returns:
            set(emails): A set of emails located within
            the string of text.
        """
        email_pattern = re.compile(r'[\w.+-]+@[\w-]+\.[\w.-]+')
        email_set = set()
        email_set.update(email_pattern.findall(text))

        return str(email_set)

    @staticmethod
    def get_dates(text: str, info_dict_pdf : dict) -> set:
        date_label = ['DATE']
        nlp = spacy.load('en_core_web_lg')
        doc = nlp(text)

        dates_pattern = re.compile(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})')
        dates = set((ent.text) for ent in doc.ents if ent.label_ in date_label)
        filtered_dates = set(date for date in dates if not dates_pattern.match(date))

        return str(filtered_dates)

    @staticmethod
    def get_summary(text: str, per: float) -> str:

        """A function that accepts of a string of text as input
           and a percentange value as input and returns a summary
           of the text.

           Parameters:
           text (str): A string of text to be summarized.
           Per (int): Percentage value that effects the number of words 
           in a summary.

           Returns:
           summary (str): A summary of the text."""

        nlp = spacy.load('en_core_web_sm')
        doc= nlp(text)
        word_frequencies={}
        for word in doc:
            if word.text.lower() not in list(STOP_WORDS):
                if word.text.lower() not in punctuation:
                    if word.text not in word_frequencies.keys():
                        word_frequencies[word.text] = 1
                    else:
                        word_frequencies[word.text] += 1
        max_frequency=max(word_frequencies.values())
        for word in word_frequencies.keys():
            word_frequencies[word]=word_frequencies[word]/max_frequency
        sentence_tokens= [sent for sent in doc.sents]
        sentence_scores = {}
        for sent in sentence_tokens:
            for word in sent:
                if word.text.lower() in word_frequencies.keys():
                    if sent not in sentence_scores.keys():                            
                        sentence_scores[sent]=word_frequencies[word.text.lower()]
                    else:
                        sentence_scores[sent]+=word_frequencies[word.text.lower()]
        select_length=int(len(sentence_tokens)*per)
        summary=nlargest(select_length, sentence_scores,key=sentence_scores.get)
        final_summary=[word.text for word in summary]
        summary=''.join(final_summary)
        return summary

  @staticmethod
  def get_urls_domains(text: str) -> set:
      """A function that accepts a string of text and
          returns any urls and domain names located within
          the text.

          Parmeters:
          text (str): A string of text. 

          Returns:
          set(urls): A set of urls located within the text
          set(domain_names): A set of domain names located within the text.
      """
      #f = open('/Users/benmccloskey/Desktop/pdf_dashboard/cfg.json')
      #data = json.load(f)
      #url_endings = [end for end in data['REGEX_URL_ENDINGS']['url_endings'].split(',')]
      url_end = 'com,gov,edu,org,mil,net,au,in,ca,br,it,mx,ai,fr,tw,il,uk,int,arpa,co,us,info,xyz,ly,site,biz,bz'
      url_endings = [end for end in url_end.split(',')]
      url_regex = '(?:(?:https?|ftp):\\/\\/)?[\\w/\\-?=%.]+\\.(?:' + '|'.join(url_endings) + ')[^\s]+'
      url_reg_pattern = re.compile(url_regex, re.IGNORECASE)
      url_reg_list = url_reg_pattern.findall(text)

      url_set = set()
      url_set.update(url_reg_list)

      domain_set = set()
      domain_regex = r'^(?:https?:\/\/)?(?:[^@\/\n]+@)?(?:www\.)?([^:\/\n]+)'
      domain_pattern = re.compile(domain_regex, re.IGNORECASE)
      for url in url_set:
          domain_set.update(domain_pattern.findall(url))

      return str(url_set), str(domain_set)*

仪表板

在今天的例子中,我们将解析由 Alex Krizhevsky Ilya Sutskever 和 Geoffrey e . hint on【1】撰写的论文ImageNet class ification with Deep 卷积神经网络

设置

您需要创建一个目录来保存您希望在仪表板中分析的 PDF 文件。然后,您需要初始化 Dash 应用程序。

*directory = '/Users/benmccloskey/Desktop/pdf_dashboard/files'

app = Dash(__name__, external_stylesheets=[dbc.themes.CYBORG],suppress_callback_exceptions=True)* 

我做了一个名为“文件”的文件夹,我把我想上传的 PDF 文件放在这个文件夹里。

PDF 解析器仪表板(图片来自作者)

如上所示,仪表板在初始化时非常简单,但这是意料之中的!没有向应用程序提供任何信息,此外,我不希望应用程序在开始时太忙。

骷髅

下一步是创建 dash 应用程序的框架。这真的是 dash 应用程序的脚手架,我们可以在上面放置不同的部分,最终创建最终的架构。首先,我们将添加一个标题,并按照我们想要的方式设计它。你可以在这里得到与不同颜色相关的数字!

*app.layout = html.Div(children =[html.Div(children =[html.H1(children='PDF Parser', 
                                          style = {'textAlign': 'center',
                                                   'color' : '#7FDBFF',})])*

第二,我们可以使用 dcc。上传允许我们实际上传数据到我们的 dash 应用程序。

这是我第一次接受挑战的地方。

Plotly Dash 提供的示例没有显示如何读取 PDF 文件。我配置了以下代码块,并将其附加到 Plotly 提供的摄取代码中。

*if 'csv' in filename:
            # Assume that the user uploaded a CSV file
            df = pd.read_csv(
                io.StringIO(decoded.decode('utf-8')))
        elif 'xls' in filename:
            # Assume that the user uploaded an excel file
            df = pd.read_excel(io.BytesIO(decoded))
        elif 'pdf' in filename:
            pdf = pdfReader(directory + '/' + filename)
            text = pdf.PDF_one_pager()
            emails = pdfParser.get_emails(text)
            ddate = pdf.get_publish_date()
            summary = pdfParser.get_summary(text, 0.1)
            df = pd.DataFrame({'Text':[text], 'Summary':[summary],
                               'Download Date' : [ddate],'Emails' : [emails]})*

需要的主要改变是强制该函数查看文件名是否在标题末尾有“pdf”。然后,我能够从我最初启动的目录中提取 pdf。请注意:在代码的当前状态下,任何上传到仪表板的 PDF 文件,如果不在目录文件夹中,将无法被仪表板读取和解析。这是未来的更新!* 如果你有任何与文本相关的功能,你可以在这里添加。*

如前所述,我用来阅读 PDF 文件的包是 PyPDF2 ,还有很多其他的包可以使用。在仪表板的开始启动类大大减少了框架内的混乱。我发现的另一个有用的技巧是,在上传 pdf 文件时解析它,并在代码的后面一行保存数据帧。对于本例,我们将显示与 PDF 文件相关的文本、摘要、下载日期、电子邮件地址

上传功能(图片来自作者)

单击上传按钮后,将会打开文件目录的弹出窗口,您可以选择想要解析的 pdf 文件(不要忘记,对于此设置,PDF 必须位于您的仪表板的工作目录中!).

文件上传后,我们可以通过返回带有日期时间的文件名来完成该功能,并创建一个 Plotly dash 清单,列出我们希望仪表板解析和显示的 PDF 中的不同功能。下面显示的是实例化回调之前仪表板的完整主体。

*app.layout = html.Div(children =[html.Div(children =[html.H1(children='PDF Parser', 
                                          style = {'textAlign': 'center',
                                                   'color' : '#7FDBFF',})]),

html.Div([
    dcc.Upload(
        id='upload-data',
        children=html.Div([
            'Drag and Drop or ',
            html.A('Select Files')
        ]),
        style={
            'width': '100%',
            'height': '60px',
            'lineHeight': '60px',
            'borderWidth': '1px',
            'borderStyle': 'dashed',
            'borderRadius': '5px',
            'textAlign': 'center',
            'margin': '10px'
        },
        # Allow multiple files to be uploaded
        multiple=True
    ), 
    #Returns info, above the datatable,
    html.Div(id='output-datatable'),
    html.Div(id='output-data-upload')#output for the datatable,
]),

  ])

def parse_contents(contents, filename, date):
    content_type, content_string = contents.split(',')

    decoded = base64.b64decode(content_string)
    try:
        if 'csv' in filename:
            # Assume that the user uploaded a CSV file
            df = pd.read_csv(
                io.StringIO(decoded.decode('utf-8')))
        elif 'xls' in filename:
            # Assume that the user uploaded an excel file
            df = pd.read_excel(io.BytesIO(decoded))
        elif 'pdf' in filename:
            pdf = pdfReader(directory + '/' + filename)
            text = pdf.PDF_one_pager()
            emails = pdfParser.get_emails(text)
            ddate = pdf.get_publish_date()
            summary = pdfParser.get_summary(text, 0.1)
            df = pd.DataFrame({'Text':[text], 'Summary':[summary],
                               'Download Date' : [ddate],'Emails' : [emails],
 '                      URLs' : [urls], 'Domain Names' : [domains]})
    except Exception as e:
        print(e)
        return html.Div([
            'There was an error processing this file.'
        ])

    return html.Div([
        html.H5(filename),#return the filename
        html.H6(datetime.datetime.fromtimestamp(date)),#edit date
        dcc.Checklist(id='checklist',options = [
            {"label": "Text", "value": "Text"},
             {"label": "summary", "value": "Summary"},
            {"label": "Download Date", "value": "Download Date"},
            {"label": "Email Addresses", "value": "Email Addresses"}
        ],
            value = []),
        html.Hr(),
        dcc.Store(id='stored-data' ,data = df.to_dict('records')),

        html.Hr(),  # horizontal line

    ])*

仪表板清单

检查表。这是我们开始向仪表板添加一些功能的地方。使用 dcc。Checklist 函数,我们可以创建一个列表,允许用户选择他们想要显示的 PDF 的哪些特性。例如,用户可能只想一次看一个特征,这样他们就不会不知所措,可以检查一个特定的模式。或者,用户可能希望将摘要与整个正文进行比较,以查看摘要中是否缺少任何可能需要添加的信息。使用清单的方便之处在于,您可以查看 pdf 中不同批次的功能,这有助于减少感官超负荷。

清单(图片来自作者)

pdf 上传后,将显示一个清单,其中没有选择任何项目。在下一节中,看看当我们在清单中选择不同的参数时会发生什么!

复试

回调在 Plotly 仪表盘中极其重要。它们向仪表板添加功能,并使其与用户交互。第一个回调用于上传功能,并确保我们的数据可以上传。

第二个回调显示数据表中的不同数据。它通过调用上传 PDF 时最初创建的已保存数据框来实现这一点。根据选择了清单中的哪些框,将决定数据表中当前显示的信息。

*@app.callback(Output('output-datatable', 'children'),
              Input('upload-data', 'contents'),
              State('upload-data', 'filename'),
              State('upload-data', 'last_modified'))
def update_output(list_of_contents, list_of_names, list_of_dates):
    if list_of_contents is not None:
        children = [
            parse_contents(c, n, d) for c, n, d in
            zip(list_of_contents, list_of_names, list_of_dates)]
        return children

@app.callback(Output('output-data-upload', 'children'),
              Input('checklist','value'),
              Input('stored-data', 'data'))

def table_update(options_chosen, df_dict):
    if options_chosen == []:
        return True
    df_copy = pd.DataFrame(df_dict)
    text = df_copy['Text']
    emails = df_copy['Emails']
    ddate = df_copy['Download Date']
    summary = df_copy['Summary']
    value_dct = {}
    for val in options_chosen:
        if val == 'Text':
            value_dct[val] = text
        if val == 'Summary':
            value_dct['Summary'] = summary
        if val == 'Download Date':
            value_dct['Download Date'] = ddate
        if val == 'Email Addresses':
             value_dct['Email'] = emails
    dff =  pd.DataFrame(value_dct)
    return dash_table.DataTable(
            dff.to_dict('records'),
            [{'name': i, 'id': i} for i in dff.columns],
            export_format="csv"
            style_data={
                'whiteSpace': 'normal',
                'height': 'auto',
                'textAlign': 'left',
                  'backgroundColor': 'rgb(50, 50, 50)',
                  'color': 'white'},
            style_header={'textAlign' : 'left',
                    'backgroundColor': 'rgb(30, 30, 30)',
                    'color': 'white'
                    })*

例如,如果我们想查看位于 PDF 中的电子邮件网址域名该怎么办?通过检查清单中的这三个选项,该数据将填充表格并显示在仪表板上。

清单示例(图片来自作者)

最后,虽然我们可以轻松地将条目复制并粘贴到我们选择的另一个电子表格或数据库中,但左上角的“导出”按钮允许我们下载表格并将其保存到 CSV 文件中!

就是这样!这几个代码块就是在 Plotly 仪表板中对 pdf 执行基本解析功能所需的全部内容。

结论

今天,我们探讨了数据科学家将 PDF 上传到 Plotly 仪表板并向用户显示特定内容的一个方向。这对于必须从 pdf 中提取特定信息的人来说非常有用。可能是客户的电子邮件、姓名和电话号码)并需要分析这些信息。虽然这个仪表板是基本的,但它可以针对不同风格的 pdf 进行调整。例如,您可能想要创建一个分析仪表板,显示您正在阅读的研究论文 PDF 中的引文,以便您可以保存或使用这些引文来查找其他论文。今天试着把这段代码写出来,如果你添加了任何酷的功能,请告诉我!

如果你喜欢今天的阅读,请关注我,并告诉我你是否还有其他想让我探讨的话题!如果你没有中等账号,就通过我的链接 这里 报名吧!另外,在LinkedIn上加我,或者随时联系!感谢阅读!

完整代码

*import pandas as pd 
from dash import dcc, Dash, html, dash_table
import base64
import datetime
import io
import PyPDF2
from dash.dependencies import Input, Output, State
import re
import dash_bootstrap_components as dbc
import spacy
from spacy.lang.en.stop_words import STOP_WORDS
from string import punctuation
from heapq import nlargest

directory = '/Users/benmccloskey/Desktop/pdf_dashboard/files'

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = Dash(__name__, external_stylesheets=[dbc.themes.CYBORG],suppress_callback_exceptions=True)

class pdfReader:    
    def __init__(self, file_path: str) -> str:
        self.file_path = file_path

    def PDF_one_pager(self) -> str:
        """A function that returnss a one line string of the 
            pdfReader object.

            Returns:
            one_page_pdf (str): A one line string of the pdf.

        """
        content = ""
        p = open(self.file_path, "rb")
        pdf = PyPDF2.PdfFileReader(p)
        num_pages = pdf.numPages
        for i in range(0, num_pages):
            content += pdf.getPage(i).extractText() + "\n"
        content = " ".join(content.replace(u"\xa0", " ").strip().split())
        page_number_removal = r"\d{1,3} of \d{1,3}"
        page_number_removal_pattern = re.compile(page_number_removal, re.IGNORECASE)
        content = re.sub(page_number_removal_pattern, '',content)

        return content

    def pdf_reader(self) -> str:
        """A function that can read .pdf formatted files 
            and returns a python readable pdf.

            Returns:
            read_pdf: A python readable .pdf file.
        """
        opener = open(self.file_path,'rb')
        read_pdf = PyPDF2.PdfFileReader(opener)

        return read_pdf

    def pdf_info(self) -> dict:
        """A function which returns an information dictionary
        of an object associated with the pdfReader class.

        Returns:
        dict(pdf_info_dict): A dictionary containing the meta
        data of the object.
        """
        opener = open(self.file_path,'rb')
        read_pdf = PyPDF2.PdfFileReader(opener)
        pdf_info_dict = {}
        for key,value in read_pdf.documentInfo.items():
            pdf_info_dict[re.sub('/',"",key)] = value
        return pdf_info_dict

    def pdf_dictionary(self) -> dict:
        """A function which returns a dictionary of 
            the object where the keys are the pages
            and the text within the pages are the 
            values.

            Returns:
            dict(pdf_dict): A dictionary of the object within the
            pdfReader class.
        """
        opener = open(self.file_path,'rb')

        read_pdf = PyPDF2.PdfFileReader(opener)
        length = read_pdf.numPages
        pdf_dict = {}
        for i in range(length):
            page = read_pdf.getPage(i)
            text = page.extract_text()
            pdf_dict[i] = text
            return pdf_dict

    def get_publish_date(self) -> str:
          """A function of which accepts an information dictionray of an object
              in the pdfReader class and returns the creation date of the
              object (if applicable).

              Returns:
              pub_date (str): The publication date which is assumed to be the 
              creation date (if applicable).
          """
          info_dict_pdf = self.pdf_info()
          pub_date= 'None'
          try:
              publication_date = info_dict_pdf['CreationDate']
              publication_date = datetime.datetime.strptime(publication_date.replace("'", ""), "D:%Y%m%d%H%M%S%z")
              pub_date = publication_date.isoformat()[0:10] 
          except:
              pass
          return str(pub_date)

class pdfParser:
    def __init__(self):
        return

    @staticmethod 
    def get_emails(text: str) -> set:
        """A function that accepts a string of text and
            returns any email addresses located within the 
            text

            Parameters:
            text (str): A string of text

            Returns:
            set(emails): A set of emails located within
            the string of text.
        """
        email_pattern = re.compile(r'[\w.+-]+@[\w-]+\.[\w.-]+')
        email_set = set()
        email_set.update(email_pattern.findall(text))

        return str(email_set)

    @staticmethod
    def get_dates(text: str, info_dict_pdf : dict) -> set:
        date_label = ['DATE']
        nlp = spacy.load('en_core_web_lg')
        doc = nlp(text)

        dates_pattern = re.compile(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})')
        dates = set((ent.text) for ent in doc.ents if ent.label_ in date_label)
        filtered_dates = set(date for date in dates if not dates_pattern.match(date))

        return str(filtered_dates)

    @staticmethod
    def get_summary(text, per):
        """A function that accepts of a string of text as input
           and a percentange value as input and returns a summary
           of the text.

           Parameters:
           text (str): A string of text to be summarized.
           Per (int): Percentage value that effects the number of words 
           in a summary.

           Returns:
           summary (str): A summary of the text."""

        nlp = spacy.load('en_core_web_sm')
        doc= nlp(text)
        word_frequencies={}
        for word in doc:
            if word.text.lower() not in list(STOP_WORDS):
                if word.text.lower() not in punctuation:
                    if word.text not in word_frequencies.keys():
                        word_frequencies[word.text] = 1
                    else:
                        word_frequencies[word.text] += 1
        max_frequency=max(word_frequencies.values())
        for word in word_frequencies.keys():
            word_frequencies[word]=word_frequencies[word]/max_frequency
        sentence_tokens= [sent for sent in doc.sents]
        sentence_scores = {}
        for sent in sentence_tokens:
            for word in sent:
                if word.text.lower() in word_frequencies.keys():
                    if sent not in sentence_scores.keys():                            
                        sentence_scores[sent]=word_frequencies[word.text.lower()]
                    else:
                        sentence_scores[sent]+=word_frequencies[word.text.lower()]
        select_length=int(len(sentence_tokens)*per)
        summary=nlargest(select_length, sentence_scores,key=sentence_scores.get)
        final_summary=[word.text for word in summary]
        summary=''.join(final_summary)
        return summary

app.layout = html.Div(children =[html.Div(children =[html.H1(children='PDF Parser', 
                                          style = {'textAlign': 'center',
                                                   'color' : '#7FDBFF',})]),

html.Div([
    dcc.Upload(
        id='upload-data',
        children=html.Div([
            'Drag and Drop or ',
            html.A('Select Files')
        ]),
        style={
            'width': '100%',
            'height': '60px',
            'lineHeight': '60px',
            'borderWidth': '1px',
            'borderStyle': 'dashed',
            'borderRadius': '5px',
            'textAlign': 'center',
            'margin': '10px'
        },
        # Allow multiple files to be uploaded
        multiple=True
    ), 
    #Returns info, above the datatable,
    html.Div(id='output-datatable'),
    html.Div(id='output-data-upload')#output for the datatable,
]),

  ])

def parse_contents(contents, filename, date):
    content_type, content_string = contents.split(',')

    decoded = base64.b64decode(content_string)
    try:
        if 'csv' in filename:
            # Assume that the user uploaded a CSV file
            df = pd.read_csv(
                io.StringIO(decoded.decode('utf-8')))
        elif 'xls' in filename:
            # Assume that the user uploaded an excel file
            df = pd.read_excel(io.BytesIO(decoded))
        elif 'pdf' in filename:
            pdf = pdfReader(directory + '/' + filename)
            text = pdf.PDF_one_pager()
            emails = pdfParser.get_emails(text)
            ddate = pdf.get_publish_date()
            summary = pdfParser.get_summary(text, 0.1)
            df = pd.DataFrame({'Text':[text], 'Summary':[summary],
                               'Download Date' : [ddate],'Emails' : [emails]})
    except Exception as e:
        print(e)
        return html.Div([
            'There was an error processing this file.'
        ])

    return html.Div([
        html.H5(filename),#return the filename
        html.H6(datetime.datetime.fromtimestamp(date)),#edit date
        dcc.Checklist(id='checklist',options = [
            {"label": "Text", "value": "Text"},
             {"label": "summary", "value": "Summary"},
            {"label": "Download Date", "value": "Download Date"},
            {"label": "Email Addresses", "value": "Email Addresses"}
        ],
            value = []),

        html.Hr(),
        dcc.Store(id='stored-data' ,data = df.to_dict('records')),

        html.Hr(),  # horizontal line

    ])

@app.callback(Output('output-datatable', 'children'),
              Input('upload-data', 'contents'),
              State('upload-data', 'filename'),
              State('upload-data', 'last_modified'))
def update_output(list_of_contents, list_of_names, list_of_dates):
    if list_of_contents is not None:
        children = [
            parse_contents(c, n, d) for c, n, d in
            zip(list_of_contents, list_of_names, list_of_dates)]
        return children

@app.callback(Output('output-data-upload', 'children'),
              Input('checklist','value'),
              Input('stored-data', 'data'))

def table_update(options_chosen, df_dict):
    if options_chosen == []:
        return True
    df_copy = pd.DataFrame(df_dict)
    text = df_copy['Text']
    emails = df_copy['Emails']
    ddate = df_copy['Download Date']
    summary = df_copy['Summary']
    value_dct = {}
    for val in options_chosen:
        if val == 'Text':
            value_dct[val] = text
        if val == 'Summary':
            value_dct['Summary'] = summary
        if val == 'Download Date':
            value_dct['Download Date'] = ddate
        if val == 'Email Addresses':
             value_dct['Email'] = emails
    dff =  pd.DataFrame(value_dct)
    return dash_table.DataTable(
            dff.to_dict('records'),
            [{'name': i, 'id': i} for i in dff.columns],
            style_data={
                'whiteSpace': 'normal',
                'height': 'auto',
                'textAlign': 'left',
                  'backgroundColor': 'rgb(50, 50, 50)',
                  'color': 'white'},
            style_header={'textAlign' : 'left',
                    'backgroundColor': 'rgb(30, 30, 30)',
                    'color': 'white'
                    })

if __name__ == '__main__':
    app.run_server(debug=True)*
  1. 克里日夫斯基、亚历克斯、伊利亚·苏茨基弗和杰弗里·e·辛顿。"使用深度卷积神经网络的图像网络分类."ACM 的通信60.6(2017):84–90。

使用 Python 和 Pytesseract 提取 PDF 文本,同时保留空白

原文:https://towardsdatascience.com/pdf-text-extraction-while-preserving-whitespaces-using-python-and-pytesseract-ec142743e805

使用 pdf2image 和 pytesseract 的 OCR PDF 和图像文件

作者图片

在数据科学项目中,PDF 数据可能很难处理。例如,您尝试从自然语言处理(NLP)项目的 PDF 中提取文本,您可能会遇到在单词之间缺少空格或者用随机空格分隔整个单词的情况。 如果单词之间没有正确的空格,你就无法开发任何有意义的自然语言处理模型。在本文中,我将介绍一种在保留空白的同时从 PDF 中提取文本的替代方法: pdf2imagepytesserac。

有许多软件包(如 PyPDF2、pdfPlumber、Textract)可以从 PDF 中提取文本。每一种都有自己的优点和缺点。一个包可能更擅长处理表格,另一个更擅长提取文本。但是没有放之四海而皆准的解决方案。

以下面的 PDF 文件为例,我们想从这一段中提取文本。这看起来很简单,但是如果不能正确指定单词之间的空格,这可能会很麻烦。

examle.pdf(作者图片)

问题 1:缺少空白

在下面的代码中,“PyPDF2”包用于提取 PDF。如您所见,空白没有保留。如果我们的机器学习模型需要理解文本的上下文,输出将是无用的。

import PyPDF2
file = open('examle.pdf', 'rb')
pdfReader = PyPDF2.PdfFileReader(file)
ocr_text = pdfReader.getPage(0).extractText()

作者图片

问题 2:随机和无用的空白

在下面的代码中,使用了“pdfplumber”包。如您所见,没有正确指定空白。并且整个单词的随机分离使得输出对于 NLP 项目毫无用处。

import pdfplumber
file = pdfplumber.open('examle.pdf')
ocr_text = file.pages[0].extract_text()

作者图片

使用 pdf2image 和 Pytesseract 保留有意义的空白

我们可以将 PDF 转换为图像,然后使用 OCR 引擎(例如,https://en.wikipedia.org/wiki/Tesseract_(software))从图像中提取文本,而不是依赖 PDF 结构来提取底层文本。

所需库

  • pdf2image :将 pdf 文件转换为图像
  • ****pytesserac:从图像中提取文本

安装库

pip install **pdf2image**
pip install **pytesseract**

下载并安装附加软件

我们需要额外的软件来使用这些库。

导入库

import pytesseract
from pdf2image import convert_from_path

初始化 pytesseract 和 pdf2image

下载并安装软件后,可以将它们的可执行路径添加到环境变量中。或者,您可以在程序中直接包含它们的路径。

poppler_path = '...\pdf2image_poppler\Release-22.01.0-0\poppler-22.01.0\Library\bin'
pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'

实现很简单。“convert_from_path”用于将 PDF 转换为图像。“pytesseract.image_to_string”用于从图像中提取文本。正如您所看到的,在这个例子中,单词之间的空格被正确地指定。

# convert PDF to image
images = convert_from_path('examle.pdf', poppler_path=poppler_path)
# Extract text from image
ocr_text = pytesseract.image_to_string(images[0])

作者图片

处理 PDF 中的多个页面

如果一个 PDF 中有多个页面,我们可以简单地使用一个循环函数来合并所有页面的文本。

images = convert_from_path('example.pdf', poppler_path=poppler_path)
ocr_text = ''
for i in range(len(images)):        
    page_content = pytesseract.image_to_string(images[i])
    page_content = '***PDF Page {}***\n'.format(i+1) + page_content
    ocr_text = ocr_text + ' ' + page_content

不仅仅是 PDF,Pytesseract 也适用于图像文件

使用pytesserac而不是其他包的另一个优点是它可以直接从图像文件中提取文本。

pytesseract.image_to_string('example.tif')
pytesseract.image_to_string('example.jpg')
pytesseract.image_to_string('example.png')

将图像转换为可搜索的 PDF

如果您想将图像格式(如 tif、png、jpg)的扫描文件转换为可搜索的 PDF。过程很简单。

PDF = pytesseract.**image_to_pdf_or_hocr**('Receipt.PNG', extension='pdf')
# export to searchable.pdf
with open("searchable.pdf", "w+b") as f:
    f.write(bytearray(PDF))

作者图片

将同一文件夹中的多个图像转换为单个可搜索的 PDF

如果您想将同一文件夹中的大量图像转换为单个可搜索的 PDF 文件,您可以使用 os.walk 为同一文件夹中的所有图像文件创建一个路径列表,然后使用上述相同的功能处理图像并导出为单个可搜索的 PDF 文件。****

all_files = []
for (path,dirs,files) in **os.walk**('images_folder'):
    for file in files:
        file = os.path.join(path, file)
        all_files.append(file)pdf_writer = PyPDF2.PdfFileWriter()
for file in all_files:
    page = pytesseract.image_to_pdf_or_hocr(file, extension='pdf')
    pdf = PyPDF2.PdfFileReader(io.BytesIO(page))
    pdf_writer.addPage(pdf.getPage(0))

with open("searchable.pdf", "wb") as f:
    pdf_writer.write(f)

如果你想继续探索 PDF 抓取,请查看我的其他文章:

感谢您的阅读!!!

如果你喜欢这篇文章,并且想请我喝杯咖啡,点击这里

您可以注册一个 会员 来解锁我的文章的全部访问权限,并且可以无限制地访问介质上的所有内容。如果你想在我发表新文章时收到电子邮件通知,请订阅。

pdf 到 Word Cloud 只需 3 个步骤

原文:https://towardsdatascience.com/pdfs-to-word-cloud-in-3-steps-73ccbff6d835

从多个文档生成单词云的快速指南

单词云是一种数据可视化工具,用于在图像中描绘单词。每个单词的大小不同,它代表了单词的重量。尺寸越大,其重要性越高。在大多数情况下,单词出现的频率被用作其权重。

词云在数据科学领域有多种应用。它通常用于 NLP 任务的探索性数据分析阶段。它给出了大型文本语料库中频繁出现的术语的快速概览,并且还给出术语相对于语料库中其他术语的用法的指示。它应用于情感分析任务,其中可以可视化总体情感的快速摘要。它还应用于主题建模任务,其中主题术语可以在词云中快速发现。

这里有一个词云的例子。

单词云示例(图片由作者提供)

词云给出了创建它的文本语料库的快速摘要。查看上面的词云,很容易识别出文本语料库是关于使用强化学习的,特别是股票数据集上的深度 q 网络方法。

被几个 PDF 文件卡住了?? (图片由作者提供)

在这篇文章中,我们将学习如何阅读多个 pdf 文档,将它们拆分成关键词,并用最常用的关键词创建一个词云。

我们开始吧

这个过程包括三个简单直接的步骤。

  • 第一步:读取文件
  • 步骤 2: 确定关键词
  • 第三步:创建单词云

必需的 PYTHON 包

在开始实现之前,这里是需要的包的列表。

pip install PyPDF2
pip install textract
pip install wordcloud
pip install nltk
pip install collection

下面是为什么我们需要这些包:

  • PyPDF2 :解析 PDF 文件(方法 1)
  • textract :解析 PDF 文件(方法 1)
  • 文字云:创建文字云图像
  • nltk :获取停用词并将字符串标记为关键词
  • 集合:获取关键字到其出现次数的映射

第一步:阅读文件

有多种方法可以将 pdf 文件的内容读入字符串。这里我们将研究两种这样的方法。

方法 1:使用 textract 包

除 pdf 文件外,textract 包还支持多种文件类型。参考官方文档了解其他支持的文件类型。

这里我们将使用基本的解析器来处理文件的内容,并将其作为字符串返回。默认情况下,返回类型是一个字节字符串,需要对其进行解码,以将其提取为一个字符串。

方法 2:使用 PyPDF2 包

PyPDF2 是一个开源 python 包,专门用于执行 PDF 特定的功能,如读取、分割、合并、裁剪和转换 PDF 文件的页面。请参考此链接以获取关于 PyPDF2 包的更多详细信息。

下面的代码片段从文件路径中读取 PDF 并以字符串形式返回其内容。

上面提到的任何包都可以用来提取 PDF 文件的内容。就我个人而言,我觉得 textract 包的结果比 PyPDF2 的结果更好,因此将它设置为默认值。

第二步:识别关键词

阅读 PDF 文件的内容后,下一步是将它分割成关键字。为了实现这一点,我们使用 nltk 包中的 word_tokenize 。它接受一个字符串作为输入,并将它分成一系列子字符串。

标点符号将从最终的关键字列表中删除。nltk 包中定义的常见单词(停用词)从关键字列表中删除。此外,如果这个列表中有一些额外的单词需要忽略,那么它可以作为一个列表传递给这个函数。

最后,函数返回一个关键字列表。默认情况下,数字从列表中删除,单词转换为小写。这可以通过参数设置来覆盖。

第三步:创建单词云

提取关键词后的最后一步是根据每个词的出现频率绘制词云。出现频率较高的词在词云中会有较大的字号,突出其意义。

这里我们使用的是 wordcloud 包中提供的 WordCloud。单词云中显示的单词数量及其大小和颜色设置是可配置的。

把它们放在一起

一旦这 3 个函数可用,我们就可以用 3 行代码为 PDF 文件生成单词云了。

单个文件的 WORD CLOUD

pdf 文件中前 200 个单词的 Wordcloud】(图片由作者提供)

文件夹中所有文件的 WORD CLOUD

同样的代码可以扩展为一个文件夹中的多个文档创建一个单词云。这将给出文档中常见单词的概述。

在下面的示例中,输出是从 sample_files 文件夹中的两个示例 pdf 文件中提取的(请参考 git repo 来访问这些文件)。

文件夹中所有 pdf 文件的关键字合并列表被创建,该关键字列表用于生成单词云。

Wordcloud,包含文件夹中所有文档的前 100 个单词(图片由作者提供)

源代码

从下面的链接下载 Jupyter 笔记本,用它来生成你自己的 word cloud:word cloud _ from _ docs

结论

本文解释了从多个 pdf 文件中读取并创建单词云的多种可能方法之一。

可以探索其他方法来执行相同的任务,如使用不同的 PDF 解析器,使用 TFIDF 代替计数器,使用具有计数映射的字典/数据帧代替所有关键字列表,生成漂亮的单词云图像等。探索愉快!!

参考文献:

PDF 文档引用实例创建文字云:

杨,赵,黄和郑,2022。将区块链和人工智能与元宇宙融合:一项调查。arXiv 预印本 arXiv:2201.03201。

Gadekallu,T.R .,Huynh-The,t .,Wang,w .,Yenduri,g .,Ranaweera,p .,Pham,Q.V .,da Costa,D.B .和 Liyanage,m .,2022。元宇宙的区块链:综述。arXiv 预印本 arXiv:2203.09738。

南巴杰派,2021。深度强化学习在印度股票交易自动化中的应用。 arXiv 预印本 arXiv:2106.16088

posted @ 2024-10-18 09:30  绝不原创的飞龙  阅读(248)  评论(0)    收藏  举报