TowardsDataScience-博客中文翻译-2022-十五-

TowardsDataScience 博客中文翻译 2022(十五)

原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

计数流中的不同事件

原文:https://towardsdatascience.com/counting-distinct-events-in-streams-c80ea7d48a46

分布式环境中的大数据统计

阿克顿·克劳福德在 Unsplash 上的照片

想象一个无限的输入符号流。我们想知道到目前为止在任何时间点收到的不同值的数量。

这个问题有许多用途。其中之一是跟踪某个时间段内,比如说上个月,某个访问量很大的网站的不同访问者的数量。

解决这个问题最简单的方法是维护到目前为止看到的所有(不同的)值的集合。当有许多不同的值时,这会消耗大量内存。

如果我们的记忆能力有限,我们能做什么?或者,如果我们处于分布式设置中,需要以分布式方式合并数据结构的许多实例,这涉及到四处传送数据结构?

寻求精确的计数变得不可行。然而,我们仍然可以在不牺牲速度的情况下获得一个近似的计数,并获得很大的压缩。

幸运的是,在许多用例中,一个(足够好的)近似计数就足够了。例如,一个网站在一个月内收到的不同访问者的数量的近似计数可能足够好,只要该近似足够好。

从这一点开始,我们将这个问题称为基数估计

首先,我们想给读者一个如何思考和处理这类问题的具体感受。为了这个目的,这个问题的简化版本将会更有效。接下来,我们将回到最初的问题,因为它有更广泛的用途。

计数事件

我们只想计算到目前为止到达流中的符号的数量。不明显的。如果实际计数为 n ,我们可以在 log n 位数量级的数据结构中捕获它。嗯,这看起来已经很节省内存了,不是吗?假设我们想要做得更好。如果没有别的原因,这将迫使我们思考一个更困难问题的可行解决方案——带有独特的约束。

首先,我们将放弃得到确切答案的目标。也就是说,我们会对一个大概的答案感到满意。只要近似“足够好”

首先,让我们从这里开始。对于预先设定的固定概率 p ,当事件到来时,以概率 p,让我们的计数器加 1。(概率为 1- p ,我们忽略这个事件。)在任何时候,我们对计数的估计都将是计数器的值乘以 1/ p

与当 p 等于 1 时相比,通过选择 p 足够小,我们可以在计数器中使用更少的内存。例如,假设实际数量约为 43 亿。这将需要 32 位来精确存储。通过选择p为 1/2 ⁶,我们期望以 16 位通过。当然,最终的计数只是一个近似值。

更重要的是,如果我们在精确计数小得多的情况下使用相同的 p ,我们的近似值可能会相差很远。我们估计的计数总是以 1/ p 为单位,即 0,1/ p ,2/ p ,…。对于 p = 1/2 ⁶,这将是 0,2⁶,2*2 ⁶,…

假设实际数是 1000。最接近 1000 的估计数 0 和 2 ⁶都相差甚远。

如果我们可以用大的 p 来表示低的实际计数,用小的 p 来表示高的计数,那就太好了。莫里斯柜台体现了这种思想。

假设近似计数器的当前值是 X 。假设看到了下一个事件。准确的计数器会在 X 上加 1。莫里斯计数器以 1/2^ X 的概率给 X 加 1。

这样做的效果是,流中较早发生的事件比流中较晚发生的事件更有可能被计数。这很直观。

这么想吧。想象一下 50 个事件的切片。假设这些是流中的前 50 个事件。我们希望使用一个非常大的 p 值,这样,如果流在那里结束,我们的估计计数就足够好了。例如,如果 p 为 0,那么我们的估计计数将为 0,而实际计数为 50。

另一方面,如果这 50 个事件发生在已经看过一百万个事件之后,我们可以使用更低的 p ,实际上甚至是 0。一百万是“一百万加 50”的一个很好的近似值。

计数不同的事件

现在让我们回到激发这篇文章的问题:估计在一个流中遇到的不同项的数量。

下面我们来说明一下这个问题。假设我们生成 n 个数字 1/ n ,2/ n ,…,3/ n 。请注意,它们平均分布在 0 和 1 之间。接下来,我们将它们复制任意多次。最后,我们任意排列这个集合。我们能从最终数据推断出 n 吗?

这里有一个 n = 4 的例子。

0.25 0.5 0.75 1               // **4 numbers uniformly spread in (0,1]**
0.25 0.25 0.25 0.5 0.5 0.75 1 1 // **Some replicates added**
0.5 0.25 0.25 0.25 1 0.75 0.5 1 // **Permuted**

我们的第一个想法是,我们找到这些数字的最小值,并输出一除以这个最小值作为我们的估计。在我们的例子中,最小值是 0.25,1/0.25 是 4,这确实是不同项目的数量。

这个想法的吸引力在于流中的最小值可以以流的方式更新。速度非常快,而且内存容量不变。

有意思。在我们的例子中,数字平均分布在 0 和 1 之间。我们如何解除这一限制?这将我们引向…

使用最小散列的概率计数

我们先适当散列。不管使用什么情况,一个有效的散列函数都将数据均匀地映射到固定数量 m 个桶上。这正是我们想要的。除了 0,1,…, m -1 到(0,1)的重新缩放只涉及除以 m 和加 1。

当然,你可以说我们已经把问题从原来的问题转移到寻找一个均匀分布数据的散列函数。幸运的是,在过去的几十年里,已经有了很多关于哈希函数对哪种类型的数据做这种事情的研究和经验。

使用这种方法的基数估计如下。我们选择一个散列函数,我们认为它将在 0 和 1 之间平均分布这些值。在做这个选择时,我们可能需要了解到达数据流的数据的性质。

(还要注意,我们不允许对数据进行多次传递,例如第一次学习哈希函数,下一次使用它。也就是说,我们想要一个纯粹的流式解决方案。)

现在,当数据以流的形式到达时,我们保持我们看到的散列值的最小值。一除以这个最小值就是我们对原始集合的基数的估计。

Min+Invert 放大哈希函数中的噪声

合理的预期是,我们选择的哈希函数对于我们正在建模的数据来说并不完美。特别是,数据集中的最小哈希值可能与它应有的值相差甚远。例如,如果它是应有数量的一半,估计的数量将是应有数量的两倍。

取最小值会放大哈希函数中的噪声。直觉类似于为什么 iid 随机变量的最小值具有比它们的和更高的方差的直觉。非正式地说,当取总和时,变量的可变性倾向于抵消,而当取最小值时则不会。

反转噪声最小值会进一步放大噪声。

所以让我们寻求一种噪音更小的方法。

最大前导位哈希概率计数

和前面的方法一样,首先,我们散列。这一次,我们将哈希值视为一个二进制数(固定位数)。接下来,我们计算 k ,这个哈希值中 0 值位的最长前缀的长度。因此, k 对于 0 110 将是 1,对于 00 10 将是 2。最后,我们跟踪 k _max,这是流中计算的哈希值在所有这些 k 中的最大值。我们返回 2^( k _max)作为流中不同事件的估计数量。

直觉是什么?想象一下,我们的散列函数将任何固定事件 x 映射到从散列函数的范围中随机均匀选择的一个数。(注意,相同的 x 应该总是映射到相同的哈希值。)

对于任何固定的 x ,其哈希值以 k 连续 0 开始的概率为 1/(2^ k 。这是因为在 k 位上有 2^ k 个二进制数,其中正好有一个是“全 0”。

因此,如果我们在流中遇到 2^k不同的事件,我们会期望它们的哈希值之一以k连续的 0 开始。将这个推理反过来,如果 k _max 是找到的任何散列值开始的 0 值比特的最大数量,则 2^( k _max)是从其导出这样一组散列值的流中的不同事件的数量的合理估计。

这种方法非常节省内存。假设散列函数的范围是一个 m 位数。也就是说,散列函数在其范围内有 n =2^ m 个不同的值。在处理流的时候,我们只需要跟踪和更新 k _max。 k _max 的值可以存储在 log m 位的二进制数中。用 n 表示——我们可以区分的不同事件的最大值——这只是 log(log n ))位。

与前面的方法一样,这种方法也放大了哈希函数的噪声,尽管可能没有那么大。说实际产生的 k _max 才是真正的一加二。从前者得到的估计计数将是从真实的 k _max 得到的 4 倍。

通过使用多个哈希函数来改进估计

我们可以使用多个独立的散列函数,并取它们产生的估计值的平均值。得到的估计值通常具有较低的方差,因此会更准确。这适用于我们到目前为止看到的两种方法。

我们使用的散列函数越多,我们的估计就越准确。也就是说,我们将需要为我们看到的任何一个事件 x 计算许多哈希值。当我们使用的散列函数的数量达到 1000 个时,这可能会变得非常耗时。

一个叫做随机平均的巧妙程序使用一个散列函数来模拟多个散列函数的效果。假设我们的哈希函数将一个 x 映射到一个 m 位的随机二进制数。我们将把由第一个 q 位形成的二进制数解释为散列函数标识符,将由剩余的 m 个 - q 位形成的二进制数解释为所识别的散列函数的值。

请注意,对于事件的任何一次发生,该方法在 m - q 位上只给出一个散列函数的值。尽管如此,我们得到了方差减少的好处,因为现在就好像我们已经从 2^ q 选择中为这个实例随机选择了散列函数。

我们确实需要跟踪和更新 2^的值,因为我们现在已经有了那么多的散列函数。在估算的时候,我们将需要使用所有这些 2^值。

最大前导位哈希的估计计数

这是:

常数p2^(mean(r1、…、 Rp )、 p = 2^ q

这里 R 1、…、 Rpp 哈希函数传递的最大前导 0 位数,mean( R 1、…、 Rp )是它们的算术平均值。前两个因素,常数和 p ,修正了单独使用 2^(mean( R 1、…、RP)会引入的偏差。这些因素我们就不多说了。如果仅使用一个散列函数,则 R 1,…, Rp 的平均值将是 R1。使用多个哈希函数时,单个值会被平均值替换。正是这一点减少了方差。

用调和平均值代替算术平均值

根据[2],在[4]中观察到,R1 到 Rp 的最大值不成比例地对平均值中的噪声产生影响( R 1,…, Rp )。这可能是如下的直觉。

假设流中有 N 个事件。每个哈希函数 i 看到其中的 N / p 。因此,对于比 1 大得多的 p ,与当 p 为 1 时相比,从更小的样本 N / p 中估计 R i。这导致了估计的误差。因为在估算 R 时我们使用了最大值运算,所以更是如此。最后,取 2^(mean( R 1,…, Rp ))进一步放大噪声。

当然,使用一个远大于 1 的 p 仍然比使用 p = 1 要好。问题是,我们是否可以通过用其他东西替换 R 1、…、 Rp 的算术平均值来进一步提高估计值?

事实证明,是的。从取算术平均值切换到谐波平均值。众所周知,算术平均值受到异常值的过度影响(在我们的例子中,Ri 过大)。相比之下,调和平均值更倾向于较小的 Ri,因此对右尾异常值更稳健。

这个增强序列的最终数据结构被称为超对数对数 (HLL)。

可制造性

现在假设事件以两个流的形式到达,由两个不同的服务器独立处理,这两个服务器可能相距很远。你在想象一个分布式的环境。(实际上可能有数千个服务器,而不仅仅是两个。)

我们希望每个服务器都能处理它的数据流。然而,我们还希望跨两个流的事件计数不同。为了做到这一点,我们需要我们的数据结构是可合并的。

向数据结构中添加可合并性支持可以加速其在分布式设置中的使用。我们可以高效简洁地独立处理大量的流,然后在需要连接流中的点时合并数据结构。

好了,数据结构应该支持合并操作,从两个实例构建数据结构的新实例。这个新实例是两者的“联合”。如果您将两个流合并,并直接从它构建一个实例,那么您将会得到它。这可以描述如下。

构建(s1) +构建(s2) =构建(s1+s2)

其中左手边的+表示数据结构合并操作,右手边的+表示合并操作。

只要两个实例使用相同的“优步”散列函数、相同的 q 和相同的 n ,HLL 就是可合并的。对于每个 i ,合并实例的 Ri 就是输入实例的 R i 的最大值。

只要实例使用相同的散列函数,最小散列数据结构也是可合并的。

总结

这篇文章研究了计算到达一个潜在无限流中的不同事件的数量的问题。这个问题有很多用途,例如,计算网站的不同访问者的数量。

将问题简化为获得计数的估计值,而不一定是其精确值,这为概率数据结构打开了大门,当数据到达时,概率数据结构可以有效而简洁地捕获有关数据的某些信息。估计需求数量所需的信息,即何时需要。这种数据结构还支持可合并性,这允许它们在任意复杂的分布式设置中使用。也就是说,可能在地理上分散的大量流中的每一个可以在本地被处理,并且数据结构根据需要被合并以估计计数,就好像它来自单个合并的流一样。

延伸阅读

  1. 计数问题——维基百科
  2. HyperLogLog in Presto:更快的基数估计 Meta 工程
  3. 概率计数和莫里斯计数器
  4. 数据库应用的概率计数算法
  5. 超级日志—维基百科

将 GPT-3 与语音识别和合成结合起来,实现一个可以在网络浏览器中运行的完全会说话的聊天机器人

原文:https://towardsdatascience.com/coupling-gpt-3-with-speech-recognition-and-synthesis-to-achieve-a-fully-talking-chatbot-that-runs-abfcb7bf580

我是如何创建这个网络应用程序的,通过它你可以和 GPT 3 号自然地谈论任何你想谈论的话题,所有这些都是基于你的笔记本电脑、智能手机或平板电脑的。

我刚刚创建了一个基于网页的聊天机器人,它可以理解你说的话,然后非常智能地回复。我有几篇文章展示了它的作用,这里你有一个 49 秒的预览(查看我最近的文章获得更多的例子)。

您可以在以下位置尝试使用该聊天机器人(即自然交谈):

https://lucianoabriata . alter vista . org/tests/GPT-3/tdsgpt 3-speech rec-speech synth . html

但本文的重点是理解这个应用程序是如何工作的——这是一个很好的例子,说明不同的库、浏览器 API 和通过 API 调用可访问的外部服务如何协同工作来实现很酷的应用程序。

这个“自然对话聊天机器人”的组成部分

我在以前的文章中肤浅地说过,这个网络聊天机器人建立在三个适用于网络浏览器的伟大工具之上。让我们在这里更详细地看看这些组件。

1。语音识别的 ann yang

(或者你可以直接使用 Web Speech API 的 SpeechRecognition 服务)

https://www.talater.com/annyang/

Annyang 是一个小型的语音识别 JavaScript 库,它允许程序员轻松地将语音命令和语音听写集成到他们的 web 应用程序中。它没有依赖性,非常轻,在 MIT 许可下可以自由使用和修改。

为了实现语音识别,Annyang 使用了 Web Speech APISpeechRecognition 服务——它还包括下面第 3 点中描述的 SpeechSynthesis 服务,以允许聊天机器人说话。

Annyang library 在 Chrome 中在电脑、笔记本电脑、平板电脑和智能手机上运行得非常好,只需要 web 应用程序托管在 https 下,并且用户接受麦克风的使用(默认情况下,只有在给定域下第一次加载库时才会请求)。在我的测试中,Annyang 在装有 Windows 操作系统的电脑上也能很好地运行 Firefox。

Annyang 非常容易使用,作为一名程序员,您只需输入以下几行代码就可以插入口头命令:

<script src="//cdnjs.cloudflare.com/ajax/libs/annyang/2.6.1/annyang.min.js"></script><script>
if (annyang) {
  const commands = {
    'hello': () => { alert('Hello world!'); }
  };

  // Add our commands to annyang
  annyang.addCommands(commands);

  // Start listening.
  annyang.start();
}
</script>

2。GPT-3 作为大脑“理解”你说的话,并产生一个可能的答复

GPT-3 是一个最先进的语言处理模型,这是一个深度神经网络,可以读入一些文本,并从生成统计模型中产生新的文本。输出可以是对输入中提出的问题的回答,或者是输入文本的摘要,或者是它的扩展;也可以是表格的文本形式,或者是输入中几个指示后写的一段代码等。

对于真正的应用程序,你可能不会完全按照原样使用 GPT-3,而是根据你的需要进行调整。特别是,您可以使用特定的输入/输出对来微调模型,还可以以提示的形式提供少量的学习,该提示包含您期望的文本输入和输出类型的基本布局。

我已经写了几篇关于 GPT-3 的文章来说明上述特征。以下是最重要的几个:

与本文最相关的是,注意我使用了对 OpenAI 的 API 的基于 PHP 的调用。我在上面列出的三篇文章的第一篇中解释了如何实现这一点。

此外,我在调用 GPT-3 时使用了一些少量的学习,让它“理解”它必须处理模拟两个人对话的信息交换(我稍后会向您展示更多)。

3。语音合成 API 大声朗读 GPT-3 制作的文本

Web Speech API 的 SpeechSynthesis service (为了使用更简单,它还包括由 Annyang 包装的 SpeechRecognition service )允许您相当容易地从文本提示合成音频。

我没有使用包装器(比如语音识别的 Annyang ),而是马上使用了语音合成服务,“香草模式”。

例如,我可以制作一个网页,上面写着“你好,世界,在这里说话!”用这段简单的代码:

var to_speak = new SpeechSynthesisUtterance("Hello world, speaking up here!");to_speak.lang = ‘en-US’;window.speechSynthesis.speak(to_speak);

完成组件后,

现在是时候看看这一切是如何真正一起工作来创建一个自然的语音聊天机器人了。

聊天机器人实际上是如何工作的

将上述元素放在一起会产生一个聊天机器人,就像你在我的视频中看到的那样,这听起来可能很琐碎。然而,当你简单地把三个部分放在一起时,你就开始有问题了。

避免聊天机器人最终“自言自语”

在我的第一次尝试中,最重要的问题是 Annyang 在 speech API 说话时一直在听,因此创建了一个无限循环,机器人只是在与自己说话。并不是以一种有趣的方式,而是一遍又一遍地重复“是的,我说过了”这样的短语。起初很有趣,但最终解决了一个痛苦。

幸运的是,事实证明你可以打开和关闭 Annyang。虽然命令应该是暂停恢复,但它们并没有真正起作用——而且不是我,因为我确实在网上找到了一些关于这个问题的笔记

所以,我不得不使用另一种方法:不是暂停,而是实际上在一个短语被发送到 GPT-3 进行分析时立即中止;然后当 GPT-3 产生的输出准备好被读取时开始而不是恢复

但即使这样也不是完美的:有时 Annyang 在 speech API 的讲话结束之前开始再次收听,所以它抓住了最后的话,并再次试图回复这些最后的话。我可以非常简单地解决这个问题,只需加入一个超时功能,在通话结束后等待 1 秒钟,然后激活 Annyang:

var to_speak = new SpeechSynthesisUtterance(textupdate.replace(“<p>”,””)); to_speak.addEventListener(‘end’, (event) => { setTimeout(annyang.start(),1000); console.log(“End event done”) }) to_speak.lang = ‘en-US’; window.speechSynthesis.speak(to_speak);

“解释”GPT-3,它必须跟随对话,提供问题的答案或合理延续人类所说的话,并记住上下文

关键在于 GPT-3 的少量学习能力。这实质上是在塑造发送给 GPT 3 号的提示,指示它跟随对话。

当我的程序开始时,提示只是下面的问题,其中包含一些聊天机器人应该知道但不知道的背景信息,如其本身的性质,其本身如何工作,我作为开发人员的姓名等。开始提示也遵循输入和回答应该如何相互跟随的模型,这里用字母 Q 和 A 标记,因为它们类似于问题和回答(尽管它们不一定必须是问题和回答,例如当你问“你好,早上好”时)机器人会回复类似“你好”的话

以下是我的应用程序使用的确切提示:

var prompt = “I am a highly intelligent bot with whom you can talk. I try to reply saying the truth, but sometimes I might make up some stuff. I work by integrating GPT-3 as a brain with web APIs for speech recognition and speech synthesis.;Q: What is human life expectancy in the United States?;A: Human life expectancy in the United States is 78 years.;Q: Who was president of the United States in 1955?;A: Dwight D. Eisenhower was president of the United States in 1955.;Q: Which party did he belong to?;A: He belonged to the Republican Party.;Q: How does a telescope work?;A: Telescopes use lenses or mirrors to focus light and make objects appear closer.;Q: Where were the 1992 Olympics held?;A: The 1992 Olympics were held in Barcelona, Spain.;Q: Who developed you?;A: I was developed by Luciano Abriata.”

现在,当 Annyang 第一次理解对它说的一些文本时,应用程序会将这些文本附加到提示中,并将整个新的提示发送到 GPT 3 号。举个例子,如果安扬听到的第一句话是“你好,你好吗?”那么发送给 GPT-3 的提示将类似于:

I am a highly intelligent bot with whom you can talk. I try to reply saying the truth, but sometimes I might make up some stuff. I work by integrating GPT-3 as a brain with web APIs for speech recognition and speech synthesis.;Q: What is human life expectancy in the United States?;A: Human life expectancy in the United States is 78 years.;Q: Who was president of the United States in 1955?;A: Dwight D. Eisenhower was president of the United States in 1955.;Q: Which party did he belong to?;A: He belonged to the Republican Party.;Q: How does a telescope work?;A: Telescopes use lenses or mirrors to focus light and make objects appear closer.;Q: Where were the 1992 Olympics held?;A: The 1992 Olympics were held in Barcelona, Spain.;Q: Who developed you?;A: I was developed by Luciano Abriata.**;Q: Hello, how are you doing?"**

(我用粗体显示了添加到启动提示中的内容)

GPT-3 的输出将由这个相同的提示加上它的附加内容组成。例如:

I am a highly intelligent bot with whom you can talk. I try to reply saying the truth, but sometimes I might make up some stuff. I work by integrating GPT-3 as a brain with web APIs for speech recognition and speech synthesis.;Q: What is human life expectancy in the United States?;A: Human life expectancy in the United States is 78 years.;Q: Who was president of the United States in 1955?;A: Dwight D. Eisenhower was president of the United States in 1955.;Q: Which party did he belong to?;A: He belonged to the Republican Party.;Q: How does a telescope work?;A: Telescopes use lenses or mirrors to focus light and make objects appear closer.;Q: Where were the 1992 Olympics held?;A: The 1992 Olympics were held in Barcelona, Spain.;Q: Who developed you?;A: I was developed by Luciano Abriata.**;Q: Hello, how are you doing?; A: I am doing fine, thanks."**

当听到下一个语音文本时,它被附加到最后一个提示,并再次发送到 GPT-3。然后,提示符会返回并添加新的响应。诸如此类。

这不仅有助于提供聊天机器人不知道的信息(在提示的第一部分),而且有助于聊天的连续性,因为之前的交流仍然存在。这就是为什么 GPT-3 的少击学习能力如此强大。不幸的是,它有一个限制,最多 2048 个令牌,所以在某些时候,API 会因为提示太长而抛出一个错误。可能有一些方法可以改善这一点,也许你可以尝试一下。

清理短语中的字符以输入 GPT-3 并从中获取

正如您在上面展示语音合成如何工作的代码片段中看到的,需要对字符串进行一些清理。例如,我们不希望机器人说出整个提示,所以我们必须删除输入。我们还想删除那些 q 和以及任何 HTML 标签,奇怪的问号等。为此,我在字符串中使用了许多 replace()函数(当我使用 replace(//g)时,这意味着 JavaScript 中的“替换所有实例”。

$.ajax({ url: “TDSgpt3withyourapikey.php?prompt=” + theprompt + “&apikey=” + apikey }).done(function(data) { var textupdate = data.replace(theprompt,””).replace(“https://api.openai.com/v1/engines/text-davinci-002/completions","").trim() if (textupdate.substring(0,1) == “?”) { textupdate = textupdate.substring(1); } textupdate = textupdate.replace(/;Q:/g,””).replace(/;A:/g,””).replace(/Q:/g,””).replace(/A:/g,””) textupdate = textupdate.trim() console.log(textupdate)

对整个代码的全局观察

下面是所有代码,注释简要描述了正在做的事情(上面给出的所有细节的总结)

作者截图,他自己的代码。

作者创作的人物。

如果你觉得这很有趣或者喜欢为网络编程很酷的东西

https://medium.com/technology-hits/a-web-tool-to-view-and-analyze-cryptocurrency-data-in-full-detail-free-access-in-this-article-73e4c1dea911 https://medium.com/age-of-awareness/metaverse-not-sure-but-webxr-hell-yes-12af5b302e08

www.lucianoabriata.com我写作并拍摄我广泛兴趣范围内的一切事物:自然、科学、技术、编程等等。 成为媒介会员 访问其所有故事(我免费获得小额收入的平台的附属链接)和 订阅获取我的新故事 通过电子邮件 。到 咨询关于小职位 查看我的 服务页面这里 。你可以 这里联系我

使用 Streamlit、Pycaret 和 Shap 进行新冠肺炎死亡率分类

原文:https://towardsdatascience.com/covid-19-mortality-triage-with-streamlit-pycaret-and-shap-a8f0dca64c7d

用强大的 Python 工具构建临床决策支持系统

图片来自Patrick Assale@Unsplash

背景

我最近接受了一个项目的任务,目标是设想如何创建一个工具,帮助临床医生识别进入重症监护病房的新冠肺炎患者的死亡风险。这种工具也称为“临床决策支持系统”,需要为临床医生提供数据驱动的健康信息,如以前的新冠肺炎患者结果或当前的患者死亡风险,以做出明智的护理决策。

就像任何真正的修补匠会做的那样,我认为设想某事的最好方式是自己创造。而且,作为一个 python 爱好者,我认为会有很多工具可以实现这一点。因此,本教程概述了我如何用强大的 python 库创建了一个新冠肺炎临床决策支持系统,并为您提供了自己重新创建或重新构想该项目的资源。

在最终产品上偷瞄:https://covidtriage.streamlitapp.com/

概述

我从机器学习问题的角度来看待这个项目。因此,该项目概述如下:

  1. 获取数据(重症监护室中患有新冠肺炎的患者)
  2. 训练和测试机器学习模型(死亡与幸存)
  3. 将机器学习模型部署到用户界面

安装库

这个项目的核心库包括 Pandas、Pycaret、Shap 和 Streamlit。所有这些都用于解决特定的需求。

  • 熊猫:数据处理和分析
  • Pycaret:构建低代码机器学习管道
  • Shap:分析和可视化特征对模型预测的影响
  • Streamlit: python 驱动的应用开发
  • Plotly:高质量的交互式图表

要安装这些库,请打开命令行提示符或终端,并输入以下内容。

pip install pandas
pip install pycaret
pip install shap
pip install streamlit
pip install plotly

本教程中的其他库是 Streamlit 的组件,用于改善用户界面/体验。

pip install streamlit_option_menu
pip install streamlit_shap
pip install streamlit_echarts

获取数据

这个项目的主要障碍之一是为模型训练获取数据。由于没有来自重症监护的新冠肺炎患者的开源数据库,我认为最好的方法是创建自己的数据库。为此,我找到了一篇解决类似问题的论文。下面的论文概述了影响新冠肺炎患者死亡率预测的几个关键特征。然后,我对这些特征进行了松散的逆向工程,以生成一个代理数据集。

https://www.frontiersin.org/articles/10.3389/fdgth.2021.681608/full

import random as rand
import pandas as pd
import numpy as np
samples = 1000age_m = 58.0
age_std = 9.0bun_m = 16.0
bun_std = 6.5creatine_m = 1.1
creatine = 0.3inr_m = 1.2
inr_std = 0.1survived = pd.DataFrame()
survived['Age'] = np.random.normal(loc=age_m,scale=age_std,size=samples)
survived['Gender'] = np.random.binomial(1, 0.367, size=samples)
survived['Blood Urea Nitrogen'] = np.random.normal(loc=bun_m,scale=bun_std,size=samples)
survived['Cardiovascular History'] = np.random.binomial(1, 0.291, size=samples)
survived['Neurological History'] = np.random.binomial(1, 0.16, size=samples)
survived['Int Norm Ratio'] = np.random.normal(loc=inr_m,scale=inr_std,size=samples)

age_m = 72.5
age_std = 8.25bun_m = 30.0
bun_std = 9.0creatine_m = 1.5
creatine = 0.4inr_m = 1.3
inr_std = 0.1deceased = pd.DataFrame()
deceased['Age'] = np.random.normal(loc=age_m,scale=age_std,size=samples)
deceased['Gender'] = np.random.binomial(1, 0.646, size=samples)
deceased['Blood Urea Nitrogen'] = np.random.normal(loc=bun_m,scale=bun_std,size=samples)
deceased['Cardiovascular History'] = np.random.binomial(1, 0.709, size=samples)
deceased['Neurological History'] = np.random.binomial(1, 0.840, size=samples)
deceased['Int Norm Ratio'] = np.random.normal(loc=inr_m,scale=inr_std,size=samples)

然后,组合、重组并保存数据帧来表示我的模型训练数据集。

train = pd.concat([survived, deceased])
train = train.sample(frac=1).reset_index(drop=True)
train.to_csv('COVID_TRAIN.csv')

创建模型

接下来,我使用 Pycaret 的监督分类功能来设置、训练和评估一个识别死者和幸存者的模型。

创建用户界面

一旦模型适合我的需求,我就开始创建用户界面。我认为临床医生必须具备的三个主要要素是:

  • 易用性
  • 可解释的模型预测
  • 当前和过去患者的特征探索

Streamlit 提供了严格使用 python 语法来构建漂亮的 web 应用程序的能力。因此,用户界面仅由 Streamlit 和相关组件构建。

设置

安装库并设置 Streamlit 页面配置。

import streamlit as st
import pandas as pd
from streamlit_option_menu import option_menu
from pycaret.classification import *
import plotly.express as px
import shap
from streamlit_shap import st_shap
from streamlit_echarts import st_echarts#set settings for streamlit page
st.set_page_config(layout="wide",
    page_title="COVID Triage",
    page_icon="chart_with_upwards_trend")#hide streamlit menu bar
hide_streamlit_style = """
        <style>
        #MainMenu {visibility: hidden;}
        footer {visibility: hidden;}
        </style>
        """
st.markdown(hide_streamlit_style, unsafe_allow_html=True)

模型

初始化训练数据和模型。

#use pandas to read covid data for model training and creation
train = pd.read_csv('COVID_TRAIN.csv')
#use pycaret to preprocess and train a decision tree supervised ML model
exp = setup(train, target = 'class', silent=True, verbose=False)
dt = create_model('dt')

菜单栏

创建一个菜单栏,在登录和 COVID triage 之间切换。

#option menu from streamlit component streamlit_option_menu
with st.sidebar:
    selected = option_menu(None, ["Log In", "COVID Triage"], 
    icons=['house',  "list-task"], 
    menu_icon="cast", default_index=0, orientation="vertical")

登录页面

为简单的不起作用的登录页面创建逻辑。

if selected == 'Log In':
    st.title('Covid 19 Mortality Risk Clinical Decision Support System')
    if 'user_name' not in st.session_state:
        st.session_state.user_name = 'User Name'
    user_name = st.text_input("User Name", value = st.session_state.user_name)
    st.session_state.user_name = user_name
    password = st.text_input("Password", type="password")
    st.session_state.password = password
    if st.session_state.password:
        sc = "Welcome " + st.session_state.user_name + " please proceed to COVID Triage"
        st.success(sc)

新冠肺炎分流页面-用户输入

创建小部件以获取与训练数据中的特征相对应的用户输入。

#covid triage page
#feature inputs correspond to training data
if selected == 'COVID Triage':
    col1, col2, col3= st.columns(3)
    with col1:
        name = st.text_input("Patient ID")
    with col2:
        gender = st.selectbox("Gender", (0, 1), help = "0 = Female, 1 = Male")
    with col3:
        age = st.number_input("Age", step = 1)ccol1, ccol2, ccol3, ccol4= st.columns(4)
    with ccol1:
        bun = st.slider("Blood Urea Nitrogen", min_value=0, max_value=60)
    with ccol2:
        inr = st.slider("International Normalized Ratio", min_value=0.88, max_value=1.66)
    with ccol3:
        honeuro = st.selectbox("History of Neurological Disorder", (0, 1), help = "0 = No history of neurological disorders, 1 = History of neurological disorders")
    with ccol4:
        hocard = st.selectbox("History of Cardiovascular Disorder", (0, 1), help = "0 = No history of cardiovascular disorders, 1 = History of cardiovascular disorders")

    test = pd.DataFrame({"Age": [age], "Gender":[int(gender)],"Blood Urea Nitrogen":[bun], "Cardiovascular History":[int(hocard)], "Neurological History":[int(honeuro)], "Int Norm Ratio":[inr]})

新冠肺炎分流页面-模型预测和信心

设置预测值和置信值的显示和逻辑。

preds = predict_model(dt, test)
    st.sidebar.text('Risk Prediction and Confidence')
    preds['Mortality Risk'] = preds['Label'].replace([0,1], ['Low Mortality Risk', 'High Mortality Risk'])
    if preds['Label'].iloc[0] == 0:
        #display if label = 0 (low mortality risk)
        st.sidebar.info(preds['Mortality Risk'].iloc[0])
        liquidfill_option = {
            "series": [{"type": "liquidFill", "data": [preds['Score'].iloc[0]]}]
        }
    if preds['Label'].iloc[0] == 1:
        #display if label = 1 (high mortality risk)
        st.sidebar.error(preds['Mortality Risk'].iloc[0])
        liquidfill_option = {
            "series": [{"type": "liquidFill", "data": [preds['Score'].iloc[0]], 'color': ['#ff0000']}]
        }
    with st.sidebar:
        #liquid fill chart with confidence value and color corresponding to mortality risk (high = red, low = blue)
        st_echarts(liquidfill_option)
    #shapley additive explanation of feature weights on model prediction
    explainer = shap.KernelExplainer(model = dt.predict_proba, data = get_config('X_train'), link = "identity")
    shap_value_single = explainer.shap_values(X = test)
    st_shap(shap.force_plot(base_value = explainer.expected_value[0],
                    shap_values = shap_value_single[0], features=test
                    ))

新冠肺炎分流页面-患者图表

设置逻辑以显示当前患者与以前患者的关系和特征。

st.text("Previous COVID-19 ICU Patients")
    df = train.copy()
    for cols in df.drop(['Unnamed: 0', 'class'], axis=1).columns:
        df['Mortality Outcome'] = train['class'].replace([1,0], ['Deceased', 'Survived'])
        fig = px.histogram(df, x = cols, color = 'Mortality Outcome',
                           color_discrete_map = {'Deceased':'red','Survived':'blue'})
        fig.add_vline(x=test[cols].iloc[0], line_dash="dot",
                      annotation_text="Current Patient", 
                      annotation_position="top left",
                      annotation_font_size=10,
                      annotation_font_color="gray"
                     )
        st.plotly_chart(fig, config= dict(
    displayModeBar = False))

把所有的放在一起

用户界面已准备好进行部署。最后的步骤包括保存需求,将所有文件添加到 GitHub,并部署到 Streamlit 共享服务。要访问最终产品,请访问下面的链接或从我的 covidtriageStreamlit 库在您的本地机器上自行构建。

https://covidtriage.streamlitapp.com/

https://github.com/chags1313/covidtriageStreamlit

结论

本教程深入介绍了如何使用 python 强大的工具生态系统来制作有用的产品。在这种情况下,我们使用 Pandas、Pycaret、Shap、Plotly 和 Streamlit 建立了一个临床决策支持系统,以确定进入重症监护的新冠肺炎患者的死亡风险。

感谢您的阅读!查看我的 GitHub 以获得更多代码和资源,或者在 LinkedIn 上联系我,提出任何问题或评论。

https://github.com/chags1313

https://www.linkedin.com/in/cole-hagen/

作者的相关文章

废水的新冠肺炎结果预测

原文:https://towardsdatascience.com/covid-19-outcome-predictions-from-wastewater-fcc19400e144

用 CDC 的废水数据预测病例、住院和死亡。使用 pandas 窗口和相关性。

Marcin JozwiakUnsplash 上拍摄的照片

摘要

全美 50,000 多项废水测试与来自同一地点和同一时间段的新冠肺炎结果(病例、住院、死亡)相匹配。分析所得数据集的新型冠状病毒 RNA 水平和结果之间的相关性,显示与所有三项指标呈正相关。

背景

在之前的数据工程项目中,我增强了CDC国家废水监测系统的废水测试。该项目的目标是将新冠肺炎疫苗接种和疾病结果信息添加到每个废水测试中,以便特定测量的新型冠状病毒 RNA 水平可以与该时间范围内该位置的疾病结果相关联。

例如,考虑 2021 年 1 月 19 日在密苏里州斯科特县的处理厂进行的水测试,发现每毫升 390.0 份来自新型冠状病毒的 N2 基因。(这基本上是完整的 NWSS 数据集包含的信息,每天和每个提交数据的水处理厂都会重复多次。)

我增强了 NWSS 数据集,在该行中添加了 1 月 9 日该县的疫苗接种率(因为疫苗至少需要 10 天才能发挥作用)、1 月 26 日的已知病例率(水试验后 7 天)、2 月 9 日新冠肺炎患者的医院和 ICU 利用率(水样本后 14 天)以及 2 月 16 日的新增新冠肺炎死亡人数(21 天后)。

该数据工程项目是成功的,下一步是使用增强的 NWSS 数据进行分析。例如,废水新型冠状病毒核糖核酸的水平能预测 14 天后新冠肺炎医院的入院人数吗?如果是,这种相关性有多强?我目前的项目,在这里报告,开始了这种分析。

注意:这项分析,以及我之前的项目,即增强了NWSS 数据集,是建立在详细的废水测试结果之上的,这些结果可以通过签署数据使用协议从 CDC 获得。要获取该受限数据集,请联系列在公共数据页面上的数据所有者。

缺失的医院价值观

这项工作的第一步是解决医院信息的一个问题。我从 CovidActNow.org 的获得医院数据,但是同样的问题在许多医院数据来源中是常见的。医院通常每周只报告一次入院人数、床位数和 ICU 使用率。其他六天,这些值为空。当然,在缺少数据的日子里,仍然有人在医院,所以 null/NaN 不能解释为零。解决方案是使用可用数据来估计每周报告之间的住院数据。

熊猫窗口功能实现了这个功能:

# Sort by county then date.CovidDF = CovidDF.sort_values(["fips", "covid_facts_date"], ascending=[True, True])# Create new columns for ICU, beds and admits with the rolling average over 10 days.CovidDF["metrics.icuCapacityRatioRolling10"] = CovidDF["metrics.icuCapacityRatio"].rolling(10, min_periods=1, center=True, closed='both').mean()CovidDF["metrics.bedsWithCovidPatientsRatioRolling10"] = CovidDF["metrics.bedsWithCovidPatientsRatio"].rolling(10, min_periods=1, center=True, closed='both').mean()CovidDF["metrics.weeklyCovidAdmissionsPer100kRolling10"] = CovidDF["metrics.weeklyCovidAdmissionsPer100k"].rolling(10, min_periods=1, center=True, closed='both').mean()

早期疫苗接种值

CovidActNow 数据还有另一项需要清理。(对于所有的数据源来说,需要各种清理和修复是很常见的。)许多早期日期的疫苗接种字段是空的。但在这种情况下,与住院治疗不同,缺失值是已知的——它们为零,因为疫苗在那些日期不存在。

熊猫。DataFrame.loc()通过明确地将疫苗出现在美国之前的所有日子的所有疫苗计数设置为零来解决这个问题。

# First vaccines on 14 December 2020.CovidDF.loc[CovidDF["covid_facts_date"] <= "2020-12-13", "actuals.vaccinationsInitiated"] = 0.0
CovidDF.loc[CovidDF["covid_facts_date"] <= "2020-12-13", "actuals.vaccinationsCompleted"] = 0.0
CovidDF.loc[CovidDF["covid_facts_date"] <= "2020-12-13", "metrics.vaccinationsInitiatedRatio"] = 0.0
CovidDF.loc[CovidDF["covid_facts_date"] <= "2020-12-13", "metrics.vaccinationsCompletedRatio"] = 0.0# First boosters on 16 August 2021.CovidDF.loc[CovidDF["covid_facts_date"] <= "2021-08-15", "actuals.vaccinationsAdditionalDose"] = 0.0
CovidDF.loc[CovidDF["covid_facts_date"] <= "2021-08-15", "metrics.vaccinationsAdditionalDoseRatio"] = 0.0

一致性测试类型

NWSS 数据集包含各种废水处理厂提交的水质检测报告。大多数,但不是全部,在原始废水中寻找新型冠状病毒的 N1 或 N2 基因。我排除了不支持这种测试的行。

RawDF = RawDF.query("pcr_target == 'sars-cov-2' ")
RawDF = RawDF.query("pcr_gene_target == 'n1' or pcr_gene_target == 'n2' or pcr_gene_target == 'n1 and n2 combined'  ")
RawDF = RawDF.query("sample_matrix == 'raw wastewater' ")

一致的 RNA 检测单位

一个稍微棘手的问题是,检测到的基因拷贝数以三种不同的方式报告:每升拷贝数、每毫升拷贝数和每升拷贝数的对数基数 10。我创建了两个新列:一列表示标准化的一致单位(每毫升的拷贝数),另一列根据需要转换标准化的基因计数。

RawDF["pcr_target_units_norm"] = "copies/ml wastewater"RawDF.loc[RawDF["pcr_target_units"] == "copies/l wastewater", "pcr_target_avg_conc_norm"] = (RawDF["pcr_target_avg_conc"] / 1000)RawDF.loc[RawDF["pcr_target_units"] == "log10 copies/l wastewater", "pcr_target_avg_conc_norm"] = ((10 ** RawDF["pcr_target_avg_conc"]) / 1000)

完成的数据集

初始 NWSS 数据集中有 61,526 行(将 FIPS 县扩展为每行一行后)。仅选择原废水中的 N1 或 N2 基因测试后有 53,521 行,仅选择具有有效基因计数(非 NaN,非负)的行后有 53,503 行。与初始数据集行相比,具有可用数据的行的比例相当高。

废水样本中的 RNA 浓度因多种原因而异——使用下水道系统的人数、当时雨水对废水的稀释、使用的采样方法以及该地区有多少人患有新冠肺炎。一项测试显示 X 个 RNA 基因拷贝,而另一项测试显示 2X 个拷贝,这一事实并不意味着第二项测试一定会发现更多的疾病。然而,NWSS 数据集包含了在多个地点、多个月内进行的多次水质测试,因此这些变化往往会随着时间和地理位置的变化而变得平缓。

结果

我使用简单的 Spearman 等级相关进行分析。如果两个变量作为有序对精确地相互跟踪,则它们的相关性为 1.0。在这种情况下,1.0 的相关性意味着所有水样中 RNA 水平的有序列表完美地预测了其他变量的有序列表,例如住院治疗。

以下是斯皮尔曼相关性:

  • 新型冠状病毒基因拷贝数和新冠肺炎试验阳性率(7 天后)= 0.491。
  • 人均基因拷贝数和新冠肺炎病例密度(7 天后)= 0.541。
  • 基因拷贝和人均新冠肺炎医院入院人数(14 天后)n= 0.377。
  • 新冠肺炎患者的基因拷贝数与医院床位的比率(14 天后)= 0.490。
  • 基因拷贝数和人均新冠肺炎死亡数(21 天后)= 0.282。

在废水中检测到的新型冠状病毒和该地理区域中新冠肺炎病流行的多种测量之间存在一致的正相关。

对于所有的相关数,95%的置信值非常接近,因为样本量很大。例如,入院相关系数为 0.377,95%置信区间为 0.3695 至 0.3845。

未来的工作

最新的完整 NWSS 数据集来自 2022 年 2 月初。此时(5 月初),数据集中缺少三个月的数据,因此分析中也缺少这些数据。一旦 NWSS 发布了新的数据集,这里显示的软件应该重新运行,结果得到验证——也许会加强或削弱相关性。

我计算了上述变量之间的标准等级相关性。增强的数据集还可以接受更高级的统计分析,可能是近似贝叶斯计算或考虑大规模(50,000+)样本集和多种结果测量的测试。

我的熊猫源代码允许轻松调整“前瞻”值。例如,可以对 21 天后的住院和 28 天后的死亡进行相同的分析。有趣的是用不同的前瞻间隔进行实验,看看哪个在废水 RNA 和新冠肺炎结果之间产生最强的相关性。

了解更多信息

https://covid . CDC . gov/covid-data-tracker/#废水监控 (NWSS 仪表板)

https://data.cdc.gov/browse(所有 CDC 数据集的主页)

https://en.wikipedia.org/wiki/Correlation(斯皮尔曼和皮尔逊相关)

破解 SQL 面试问题:Date_Part 函数

原文:https://towardsdatascience.com/crack-sql-interview-question-date-part-function-aff0b15478d9

用有用的程序解决 SQL 问题

艾米·桑布伦在 Unsplash上的照片

在本文中,我们将回顾亚马逊数据科学访谈中的一个 SQL 问题。希望本文中解释的过程能够帮助您更有效地编写 SQL 查询。

SQL 问题:

**Best Selling Item**Find the best-selling item for each month (no need to separate months by year) where the biggest total invoice was paid. The best-selling item is calculated using the formula (unitprice * quantity). Output the description of the item along with the amount paid.

表格:在线零售

作者图片

第一步:先看看原始数据。

  • invoiceno:该字段包括可以唯一标识一笔交易的发票 ID。
  • stockcode:该字段包括可以唯一标识产品的产品 ID。
  • 描述:此字段包括产品描述,它也可以唯一地标识产品
  • invoicedate:该字段包括交易的日期。我们需要这个字段来创建月份变量。
  • quantity、unitprice:这些字段包括给定交易中销售产品的数量和单价。
  • customerid,country:这些字段是客户 id 和国家信息,与这个问题无关。

第二步:大家集思广益,如何解决问题。

最终我们需要返回每个月最畅销的产品。首先,我们需要创建两个新变量,

  • 金额:按照指示,交易的美元金额可以计算为(单价*数量)
  • 月份:我们需要从交易日期中提取月份信息

接下来,我们可以计算每个产品在给定月份的总交易金额,然后根据每个月的交易金额对它们进行排名。最后,我们返回每个月最畅销的产品。

第三步:让我们准备好数据,准备好分析。在这个练习中,我们需要通过几个小步骤来准备数据。

我们可以使用下面的代码创建我们在步骤 2 中确定的两个新变量。

SELECT *,quantity * unitprice AS amount,DATE_PART('month', invoicedate) AS monthFROM online_retailORDER BY DATE_PART('month', invoicedate), stockcode

让我们详细了解一下代码:

**DATE_PART** :此功能允许我们从日期或时间戳值中提取时间信息,如年、月、日、时、分、秒。查看更多示例,

SELECTDATE_PART('year', '2014-04-01 06:16:36'::TIMESTAMP), -- returns 2014DATE_PART('month', '2014-04-01 06:16:36'::TIMESTAMP), -- returns 4DATE_PART('day', '2014-04-01 06:16:36'::TIMESTAMP), -- returns 1DATE_PART('hour', '2014-04-01 06:16:36'::TIMESTAMP), -- returns 6DATE_PART('minute', '2014-04-01 06:16:36'::TIMESTAMP), -- returns 16DATE_PART('second', '2014-04-01 06:16:36'::TIMESTAMP), -- returns 36DATE_PART('year', '2014-04-01'::DATE), -- returns 2014DATE_PART('month', '2014-04-01'::DATE), -- returns 4DATE_PART('day', '2014-04-01'::DATE), -- returns 1DATE_PART('hour', '2014-04-01'::DATE), -- returns 0 based on 00:00:00DATE_PART('minute', '2014-04-01'::DATE), -- returns 0DATE_PART('second', '2014-04-01'::DATE) -- returns 0

当我们运行上面的代码时,我们可以生成如下的表格。

作者图片

接下来,我们可以使用聚合函数**SUM()** 通过**GROUP BY month, description** 计算每个产品在给定月份的总交易金额。由于我们要分几个步骤准备数据,建议使用**WITH** 语句创建临时表。

WITH cte AS(SELECT *,quantity * unitprice AS amount,DATE_PART('month', invoicedate) AS monthFROM online_retail)SELECT month, description, SUM(amount) AS tot_dollarFROM cteGROUP BY month, descriptionORDER BY month, tot_dollar DESC

当我们运行上面的代码时,我们可以生成如下的表格。

作者图片

然后,我们根据每个月的总交易金额对产品进行排名。我们可以用窗口中的 **RANK()** 功能将每个分区(即月份)用**OVER(PARTITION BY month ORDER BY tot_dollar DESC)**隔开。

  • **OVER** :表示我们这里使用的函数是窗口函数,而不是聚合函数
  • **PARTITION BY** :对数据表中的行进行分区,这样我们就可以定义窗口函数将应用于哪些行。在本练习中,我们根据月份变量定义一个分区。
  • **ORDER BY** 用于对每个分区内的观测值进行排序。在本练习中,我们将对每个分区中的总交易金额进行排序。**DESC** 将确保我们从最高值到最低值对行进行排序。
WITH cte AS(SELECT *,quantity * unitprice AS amount,DATE_PART('month', invoicedate) AS monthFROM online_retail),monthly_total_by_product AS(SELECT month, description, SUM(amount) AS tot_dollarFROM cteGROUP BY month, description)SELECT month, description, tot_dollar,RANK() OVER(PARTITION BY month ORDER BY tot_dollar DESC) AS rankFROM monthly_total_by_product

当我们运行上面的代码时,我们可以生成如下的表格。

作者图片

第四步:第三步准备好数据后,使用**WHERE RANK = 1** 保存每个月最畅销的产品就变得简单了。

最终解决方案:

WITH cte AS(SELECT *,quantity * unitprice AS amount,DATE_PART('month', invoicedate) AS monthFROM online_retail),monthly_total_by_product AS(SELECT month, description, SUM(amount) AS tot_dollarFROM cteGROUP BY month, description),monthly_ranking AS (SELECT month, description, tot_dollar,RANK() OVER(PARTITION BY month ORDER BY tot_dollar DESC) AS rankFROM monthly_total_by_product)SELECT month, description, tot_dollarFROM monthly_rankingWHERE rank = 1ORDER BY MONTH

回答:

作者图片

如果你想探索更多的 SQL 面试问题,请查看我的文章:

感谢您的阅读!!!

如果你喜欢这篇文章,并且想请我喝杯咖啡,请点击这里

您可以注册一个 会员 来解锁我的文章的全部访问权限,并且可以无限制地访问介质上的所有内容。如果你想在我发表新文章时收到电子邮件通知,请 订阅

破解 SQL 面试问题:Join vs Case-When 语句

原文:https://towardsdatascience.com/crack-sql-interview-question-join-vs-case-when-statement-116d40a361f0

用有用的程序解决 SQL 问题

照片由 LinkedIn 销售解决方案Unsplash 上拍摄

在本文中,我们将回顾脸书数据科学采访中的一个 SQL 问题。希望本文中解释的过程能够帮助您更有效地编写 SQL 查询。

SQL 问题:

**Users By Average Session Time**Calculate each user's average session time. A session is defined as the time difference between a page_load and page_exit. For simplicity, assume a user has only 1 session per day and if there are multiple of the same events on that day, consider only the latest page_load and earliest page_exit. Output the user_id and their average session time.Source: [stratascratch.com](https://platform.stratascratch.com/coding/10352-users-by-avg-session-time?code_type=1)

表格:facebook_web_log

作者图片

第一步:我们先看看原始数据。该表包括一系列动作,例如对于给定的用户 id,带有时间戳的“页面加载”、“向下滚动”、“向上滚动”、“页面退出”。我们被指派计算每个用户的平均会话时间。让我们澄清几件事。举个例子,

  • 会话是如何定义的?根据这个问题,会话被定义为“页面加载”和“页面退出”之间的时间差。如果在给定的一天中有多个相同的事件,例如“page_load”和“page_exit”,那么考虑只使用最新的 page_load最早的 page_exit
  • 如果用户在给定的一天中只有两个事件中的一个,即“页面加载”和“页面退出”。比如 2019 年 4 月 25 日,user_id 2 只有“page_load”事件,没有找到“page_exit”。我们应该如何处理这种情况?
  • 同样在给定的一天中,对于用户来说,最新的 page_load 可能晚于最早的 page_exit。如果是这样,应该怎么处理?
  • 这些是我们需要从面试官那里得到澄清的问题。在本练习中,如果两个事件中只有一个事件或者最晚的 page_load 比最早的 page_exit 晚,我们将排除这些记录。

步骤 2 :我们需要确定解决问题所需的相关信息。举个例子,

  • 我们只需要在“action”字段中使用值为“page_load”和“page_exit”的行,因为这就是会话时间的定义方式。
  • 我们需要从时间戳变量中提取日期变量,因为这是会话时间的来源。

步骤 3 :我们将准备数据并创建我们在步骤 2 中确定的变量。在这一步,我将使用两种不同的方法。

方法 1:连接两个临时表,加载并退出

  • 要创建只包含“page_load”动作的加载表,我们可以使用WHERE action = ‘page_load’。如果一个用户在给定的日期有多个“page_load”操作,我们将只保留最新的一个。因此,我们使用timestamp::DATE来创建日期变量,并使用聚合函数MAX(timestamp)GROUP BY user_id, timestamp::DATE来保存给定日期内最新的“page_load”操作。
  • 为了创建只包含“page_exit”动作的退出表,我们使用了WHERE action = ‘page_exit’。然后我们使用聚合函数MIN(timestamp)GROUP BY user_id, timestamp::DATE来保持给定日期内最早的“page_exit”动作。
  • 一旦使用WITH语句创建了加载和退出表,我们就可以基于“user_id”和“date”将这两个表连接在一起。这里我们将使用INNER JOIN,因为我们需要在给定的一天为用户提供“page_load”和“page_event”。
WITH load AS(SELECT user_id,timestamp::DATE AS date,MAX(timestamp) AS lastest_loadFROM facebook_web_log**WHERE** action = 'page_load'**GROUP BY** user_id, timestamp::DATE),-- Create Exit tableexit AS(SELECT user_id,timestamp::DATE AS date,MIN(timestamp) AS earliest_exitFROM facebook_web_log**WHERE** action = 'page_exit'**GROUP BY** user_id, timestamp::DATE)SELECT a.user_id,a.date,a.lastest_load,b.earliest_exitFROM LOAD a**INNER JOIN** exit bON a.user_id = b.user_id ANDa.date = b.date

当我们运行上面的代码时,我们可以生成如下所示的表格:

作者图片

方法 2:使用 CASE-WHEN 语句创建两个新列 Load 和 Exit

  • 我们可以使用CASE-WHEN语句,而不是使用 WHERE 语句来保持“page_load”和“page_exit”动作。代码CASE WHEN action = ‘page_load’ THEN timestamp ELSE NULL END可以创建一个只包含“page_load”动作时间戳的新列,并为其他动作将其设置为 NULL。我们将为“page_exit”操作编写类似的代码。
  • 一旦我们有了这两个新列,我们就可以使用聚合函数MAX()MIN()为具有GROUP BY user_id, timestamp::DATE的用户计算给定日期的“page_load”动作的最新时间戳和“page_exit”动作的最早时间戳。
SELECT user_id,timestamp::DATE AS date,**MAX**(**CASE WHEN** action = 'page_load' **THEN** timestamp **ELSE** NULL END) AS latest_page_load,MIN(CASE WHEN action = 'page_exit' THEN timestamp ELSE NULL END) AS earliest_page_exitFROM facebook_web_log**GROUP BY** user_id, timestamp::DATE

当我们运行上面的代码时,我们可以生成如下的表格。您将注意到此输出的不同之处——我们在 2019 年 4 月 25 日为 user_id 2 多了一条记录,因为我们使用了INNER JOIN来仅保留方法 1 中的匹配记录,而我们在此仅保留“page_load”和“page_exit”的所有可用记录。

作者图片

步骤 4 :在步骤 3 中准备好数据后,计算每个 user_id 的平均会话时间应该很简单。我们只需要使用聚合函数AVG()GROUP BY user_id

使用方法 1 的最终解决方案:连接两个临时表,加载并退出

我将代码WHERE b.earliest_exit ≥ a.lastest_load放在最后,因为我们希望避免最新的 page_load 比最早的 page_exit 晚的情况。

WITH load AS(SELECT user_id,timestamp::DATE AS date,MAX(timestamp) AS lastest_loadFROM facebook_web_logWHERE action = 'page_load'GROUP BY user_id, timestamp::DATE),-- Create Exit tableexit AS(SELECT user_id,timestamp::DATE AS date,MIN(timestamp) AS earliest_exitFROM facebook_web_logWHERE action = 'page_exit'GROUP BY user_id, timestamp::DATE)SELECT a.user_id,AVG(b.earliest_exit - a.lastest_load) AS avg_session_timeFROM LOAD aINNER JOIN exit bON a.user_id = b.user_id ANDa.date = b.dateWHERE b.earliest_exit >= a.lastest_loadGROUP BY a.user_id

使用方法 2 的最终解决方案:使用 CASE-WHEN 语句创建两个新列 Load 和 Exit】

对于这个方法,我在代码中包含了,WHERE (earliest_page_exit-latest_page_load) IS NOT NULL,因为我们希望在 2019 年 4 月 25 日删除 user_id 2 的额外记录。

WITH CTE AS(SELECT user_id,timestamp::DATE AS date,MAX(CASE WHEN action = 'page_load' THEN timestamp ELSE NULL END) AS latest_page_load,MIN(CASE WHEN action = 'page_exit' THEN timestamp ELSE NULL END) AS earliest_page_exitFROM facebook_web_logGROUP BY user_id, timestamp::DATE)SELECT user_id,AVG(earliest_page_exit-latest_page_load) AS avg_session_timeFROM CTEWHERE (earliest_page_exit-latest_page_load) IS NOT NULL ANDearliest_page_exit >= latest_page_loadGROUP BY user_id

回答:

如果你想探索更多的 SQL 面试问题,请查看我的文章:

感谢您的阅读!!!

如果你喜欢这篇文章,并且想请我喝杯咖啡,点击这里

您可以注册一个 会员 来解锁我的文章的全部访问权限,并且可以无限制地访问介质上的所有内容。如果你想在我发表新文章时收到电子邮件通知,请订阅。

破解 SQL 面试问题:带分区的窗口函数

原文:https://towardsdatascience.com/crack-sql-interview-question-window-functions-with-partition-by-599d792c07c3

用有用的程序解决 SQL 问题

马文·迈耶Unsplash 上拍摄的照片

在本文中,我们将回顾亚马逊数据科学访谈中的一个 SQL 问题。希望本文中解释的过程能够帮助您更有效地编写 SQL 查询。

SQL 问题:

**Marketing Campaign Success**You have a table of in-app purchases by user. Users that make their first in-app purchase are placed in a marketing campaign where they see call-to-actions for more in-app purchases. Find the number of users that made additional in-app purchases due to the success of the marketing campaign.The marketing campaign doesn't start until one day after the initial in-app purchase so users that only made one or multiple purchases on the first day do not count, nor do we count users that over time purchase only the products they purchased on the first day.Source: [stratascratch.com](https://platform.stratascratch.com/coding/514-marketing-campaign-success-advanced?code_type=1)

表格:营销活动

作者图片

第一步:让我们先检查一下原始数据。

  • user_id:该字段标识一个唯一的用户。我们确实需要这个字段来计算不同用户的数量。
  • created_at:该字段指示进行交易的日期。这可用于识别给定交易是在第一天(首次应用内购买时)还是在营销活动期间(首次应用内购买后一天开始)进行的
  • product_id:该字段标识唯一的产品。与 created_at 结合使用,它们可以用来识别给定的产品是在第一天购买的还是在营销活动期间购买的,或者两者都有。
  • 数量和价格:这两个字段表示与给定交易相关的数量和价格。他们与这个问题无关。

第二步:大家集思广益,如何解决问题。

这个练习最重要的任务是找出如何识别

  • 在营销活动期间购买新产品的用户(即成功)
  • 在营销活动的第一天购买了相同产品的用户(即失败)
  • 在营销活动中没有购买任何产品的用户(即失败)

作者图片

一旦我们将用户分类到正确的类别中,我们就可以很容易地统计出由于营销活动的成功而购买新产品的不同用户的数量。

第三步:让我们准备好数据,准备好分析。

方法一:利用窗口功能, **MIN()** 配合 **OVER-PARTITION-BY**

我们需要构造两个新的变量

  • first_purchase_date:这个字段将给出给定用户进行应用内购买的第一天。
  • first_product_purchase_date:该字段将给出用户购买给定产品的第一天

可以运行以下脚本来产生这两个变量。

SELECT *,MIN(created_at) OVER (PARTITION BY user_id) AS first_purchase_date,MIN(created_at) OVER (PARTITION BY user_id, product_id) AS first_product_purchase_dateFROM marketing_campaign

让我们详细了解一下代码:

  • **MIN()** :这是我们用来计算每个分区中最早购买日期的窗口函数。还有其他的窗口函数,比如 max,sum,行号,rank 等。
  • **OVER** :表示我们这里使用的函数是窗口函数,不是聚合函数
  • **PARTITION BY** :对数据表中的行进行分区,这样我们就可以定义窗口函数将应用于哪些行。在本练习中,“first_purchase_date”是在 user_id 分区上计算的,而“first_product_purchase_date”是在 user_id 和 product_id 分区上计算的。

当我们运行上面的代码时,我们可以生成如下的表格。让我们检查一下新变量是否有意义。例如,user_id 10 在 2019 年 1 月 1 日进行了第一次应用内购买。该用户购买了三种不同的产品,101、111 和 119,最早购买日期分别为 2019 年 1 月 1 日、2019 年 3 月 31 日和 2019 年 1 月 2 日。有了这两个变量,我们很容易得出结论,user_id 10 在营销活动中购买了两个新产品。

作者图片

创建这两个新变量后,我们可以使用WHERE first_purchase_date < first_product_purchase_date标记营销活动期间(从首次应用内购买日期后一天开始)购买的新产品用户。

方法二:利用窗口功能, **DENSE_RANK()** 配合 **OVER-PARTITION-BY**

类似地,我们需要构造两个新变量。

  • user_date_rank:该字段将为我们提供给定用户的购买日期顺序。例如,user_date_rank = 1代表给定用户进行应用内购买的第一天。user_date_rank = 2代表第二早的日期,以此类推。因此,user_date_rank > 1 代表营销活动期间的购买记录。
  • product_date_rank:该字段将给出给定用户和产品的购买日期顺序。例如,product_date_rank = 1 表示用户购买给定产品的第一天。

可以运行以下脚本来产生这两个变量。

SELECT  *,DENSE_RANK() OVER (PARTITION BY user_id ORDER BY created_at) AS user_date_rank,DENSE_RANK() OVER (PARTITION BY user_id, product_id ORDER BY created_at) AS product_date_rankFROM marketing_campaign

让我们详细了解一下代码:

  • **DENSE_RANK**:这是一个常用的窗口功能。此函数给出每个分区中每行的排名,即使出现平局,也有连续的排名值。比如 1,2,2,3,…**RANK** 是一个交替排序函数。不同的是,如果出现平局,后者会在排名值上产生差距。例如,1、2、2、4……对于这个练习,**DENSE_RANK**用起来更合适。
  • **ORDER BY** 用于对每个分区内的观测值进行排序。在本练习中,我们将对每个分区中的购买日期进行排序。

当我们运行上面的代码时,我们可以生成如下的表格。让我们检查这些新变量。例如,user_id 10 在 2019 年 1 月 1 日、2019 年 1 月 2 日和 2019 年 3 月 31 日这三个不同的日期进行了购买,这些日期从最早到最晚分别以订单号(即,“user _ date _ rank”)1、2、3 排序。该用户购买了三种不同的产品,101、119 和 111。“product_date_rank”中的订单编号 1、1 和 1 表示该用户只购买了这三种产品一次。

作者图片

在我们创建这两个新变量之后,我们可以使用WHERE user_date_rank > 1来检查在营销活动期间是否进行了购买。我们还可以使用WHERE product_date_rank = 1检查给定产品是否是第一次购买。结合这两个条件,我们可以标记给定用户在营销活动中购买的新产品的记录。

步骤 4 :一旦在步骤 3 中准备好数据,我们就可以计算在营销活动中购买新产品的不同用户的数量。我们只需要使用聚合函数COUNT(DISTINCT)WHERE 语句来保存符合标准的记录。

最终解决使用方法 1:使用窗口功能, **MIN()** **OVER-PARTITION-BY**

WITH cte AS (SELECTuser_id,MIN(created_at) OVER (PARTITION BY user_id) AS first_purchase_date,MIN(created_at) OVER (PARTITION BY user_id, product_id) AS first_product_purchase_dateFROM marketing_campaign)SELECT COUNT(DISTINCT(user_id))FROM cteWHERE first_purchase_date < first_product_purchase_date;

最终解决使用方法二:使用窗口功能, **DENSE_RANK()** **OVER-PARTITION-BY**

WITH cte AS (SELECTuser_id,DENSE_RANK() OVER (PARTITION BY user_id ORDER BY created_at) AS user_date_rank,DENSE_RANK() OVER (PARTITION BY user_id, product_id ORDER BY created_at) AS product_date_rankFROM marketing_campaign)SELECT COUNT(DISTINCT user_id)FROM cteWHERE user_date_rank > 1 AND product_date_rank = 1;

答案:23

如果你想探索更多的 SQL 面试问题,请查看我的文章:

感谢您的阅读!!!

如果你喜欢这篇文章,并且想请我喝杯咖啡,请点击这里

您可以注册一个 会员 来解锁我的文章的全部访问权限,并且可以无限制地访问介质上的所有内容。如果你想在我发表新文章时收到电子邮件通知,请 订阅

破解数据科学家的统计采访

原文:https://towardsdatascience.com/cracking-statistics-interviews-for-data-scientists-711a63bcb375

数据科学面试

分解统计访谈的必要知识和问题格式

Saad AhmadUnsplash 上拍摄的照片

在这篇博客 posrt 中,我们将报道另一种有抱负的数据科学家必须学会应对的面试:统计面试。统计面试是一种技术面试,评估你作为数据科学家的实际工作能力。如果你没有通过这次面试,你将很难让公司相信你是一名有能力的数据科学家。

赢得任何面试的第一步是知道期待什么。统计学是一个广泛的主题,所以这篇文章将分解数据科学家在采访中应该了解的统计学领域,以及会被问到的问题类型。我们还将从头到尾讨论提示和一般建议。

如果你想通过这种方式获得信息,我还有一个关于这个主题的视频。

让我们从统计面试会涵盖哪些统计知识领域开始。

目录

知识领域

在统计面试中,你自然要展示很多技术知识,但这并不意味着你需要准备回答任何和所有与统计相关的问题。对于数据科学家来说,统计面试通常会关注三个知识领域:

  • 概率
  • 假设检验
  • 回归

可能性

Unsplash 上的 Edge2Edge 媒体拍摄

对于概率问题,你应该预料到会被问到概率基础、条件概率和概率分布。概率基础包括期望、方差、排列、组合等。

对于条件概率,你将需要知道贝叶斯法则,对于概率 分布,你需要熟悉一些常用的离散和连续分布,比如二项式、正态、长尾分布。

这听起来可能很多,但概率通常是你在面试中需要最深入了解的知识领域。因此,面试官会问你很多问题,此外,准备过度总比准备不足好。

假设检验

继续假设检验,对于统计访谈,您需要了解术语,如功效、p 值和置信区间,以及不同种类的检验方法,参数检验,如 z 检验、t 检验,以及非参数检验,如卡方检验,等等。

回归

统计面试需要的最后一个知识领域是回归。对于回归,你需要熟悉线性和多元回归。在这三个领域中,回归可能是面试中出现最少的,但是你仍然需要确保你对此感到满意。

所以概率、假设检验和回归是你在面试中会被问到的领域。现在我们知道了问题将涵盖哪些主题,让我们讨论一下你可能会遇到的问题类型。

问题类型

在统计访谈中,问题有三种不同的形式:

  • 概念性问题
  • 涉及计算的问题
  • 编码问题

了解你将会被问到什么类型的问题对于理解如何准备和如何回答是至关重要的。让我们仔细看看每种类型。

概念问题

顾名思义,概念题更感兴趣的是你解释概念的能力,而不是你用这些概念做数学的能力。我们可以将这一类别进一步细分为两种风格:向技术观众解释和向非技术观众解释。

在我们深入研究向技术和非技术受众解释概念的不同方法之前,概念问题到底是什么?让我们来看一些在统计面试中你可能会遇到的每个知识领域的概念性问题的例子。

  • 概率:每个用户平均花费时间的分布是怎样的?
  • 假设检验:向非技术观众解释 p 值和置信区间。
  • 回归:线性回归的假设有哪些?

正如你从例子中看到的,概念性问题要求你定义术语,并理解这些东西在现实世界中的含义。你可能会被要求解释一个术语或概念,有时是对非技术观众。解释一个概念是一个有点模糊的要求,那么你应该在回答这些概念问题时包括什么呢?

向技术观众解释

让我们先来看看向技术观众解释概念的一些步骤。作为一名数据科学家,很容易忽视这一点的重要性。毕竟,如果观众有技术背景,他们应该理解你的解释没有问题吧?

虽然技术观众应该更容易理解你的解释中的术语和大意,但是如果你的回答没有条理或者涉及到一个更模糊的概念,仍然很难理解。你仍然需要采取措施来确保你能清楚地解释这些概念。以下是我推荐的步骤:

  1. 从一些上下文开始。该术语何时何地使用?
  2. 定义概念。即使在向技术人员解释时,您也希望让定义易于理解。尽量不要听起来像一本高水平的教科书。你用简单的术语解释事物的能力显示了更高的理解水平。
  3. 对于可以用数字表示的概念,你可能想用解释值的变化意味着什么。当这个概念的值更大或更小时,意味着什么?
  4. 这一步是可选的。你可以通过谈论这个概念在实践中是如何应用的来结束。思考一些问题,比如为什么这个概念被广泛使用,或者为什么它对数据科学很重要。

为了看到这些步骤的实施,我推荐看一下我的视频中关于如何在面试中解释 5 大统计学概念的视频。

向非技术观众解释

当你被要求向技术观众解释或定义一个概念时,这些步骤有望给你一些清晰的谈话要点和结构,但是非技术观众有什么不同呢?正如你在前面的例子中看到的,你可能被要求用门外汉的术语解释一个概念,或者向非技术观众解释,这需要你更直观地解释。

使用例子和类比是向非技术观众解释术语的好方法。试着把外行更熟悉的事物联系起来,来解释不熟悉的事物。

来说,在向非技术观众解释事情时避免使用技术术语也很重要。例如,如果你在解释测试的功效时使用假设检验、零假设或替代假设等术语,你只会让你的听众感到困惑。

对于所有概念性的问题,目标应该是让你的解释清晰而有条理。记住,即使是技术型听众,你也希望你的解释尽可能简单易懂,因为这表明你对概念有更深的理解。

涉及计算的问题

对于数据科学家来说,理解概念和术语是美妙且必要的,但是你还必须能够进行与这些概念相关的数学运算。统计面试将包括涉及实际计算的问题。

Joshua HoehneUnsplash 上拍摄的照片

涉及计算的问题可能需要你简单地知道用什么来解决问题。例如,条件概率问题可能要求您使用贝叶斯规则来计算它,但问题本身可能没有提到贝叶斯规则。这是评估你是否能正确识别你需要什么方法和步骤来解决一个特定的问题,基本上是你是否知道如何做这个问题。

不过,一个涉及计算的问题可能会更进一步。它会要求你写出方程式并给出准确的答案。这不仅是评估你是否知道如何做这道题,也是评估你是否能正确地做这道题。

例如,问题可能会说“我们总共有 100 枚硬币,其中包括 99 枚公平硬币和 1 枚有 100%正面概率的偏向硬币。如果你随机选择一枚硬币,抛 10 次,10 次都是正面,那么这枚硬币是偏向硬币的概率是多少?”这个问题不仅要求你知道你需要使用贝叶斯法则,而且你还必须找到这里的数值答案。

更多涉及计算的问题示例如下:

  • "在 10 次投掷一枚硬币的过程中,有多大可能得到两个正面?"
  • 给定两组用户,比较点击率,得出两个点击率是否相同的结论。
  • "你能列出测试步骤并得出结论吗?"

第一个问题是一个处理概率分布的问题的例子,第二个是假设检验。最后一个例子是在得到另一个问题的结果后,你可能会被问到的问题。我推荐这段视频,以便更好地了解假设检验问题。

总而言之,涉及计算的问题是面试官评估你是否能运用你的知识的一种方式。这些问题是一个展示你不仅有知识而且有 T2 技能的机会。最后一类问题,编码问题,给你更多的机会来展示你的技能。

编码问题

制作者 UX 设计工作室Unsplash 拍摄的照片

如果概念问题都是关于你对概念的理解,那么编码问题都是关于你的实现技能。这些问题不仅要求你了解理论,还要求你实施这些理论。

例如,一个处理概率的编码问题可能会要求您设计并运行一个硬币模拟问题。查看这个视频可以更深入地了解这类问题。另一个例子是假设测试编码问题,这可能需要您用 R 或 Python 编写代码来计算结果。

这些例子以及所有的编码问题,让你展示出获得结果的能力。明白该做什么是一回事,但是展示你能做到这一点对于赢得面试至关重要。当然,准备编码题最好的方法就是练习编码。我推荐 HackerRank 的《10 天统计》,以便开始解决一些统计问题。

结论

一般来说,统计学面试是一种非常注重技能的面试类型。你需要做好准备,在这些面试中表现出全面的知识和能力。

总而言之,作为一名数据科学家,你需要在面试中准备三个方面的统计知识:

  • 概率
  • 假设检验
  • 回归

你可能会注意到,我们没有谈论因果推理,尽管它是一个与统计学密切相关的领域。如果你想了解数据科学访谈中的因果推理,请务必订阅这个频道以获取未来内容的更新。

除了这三个知识领域,在统计面试中还有三种类型的问题:

  • 概念性问题
  • 涉及计算的问题
  • 编码问题

这三种类型的问题共同评估你作为数据科学家所面临的统计问题的理解和技能。

有了这个大纲,你现在知道你需要知道什么,并准备作为一名数据科学家在统计面试中胜出。祝你好运!

感谢阅读!

如果你喜欢这个帖子,想支持我…

</7-a-b-testing-questions-and-answers-in-data-science-interviews-eee6428a8b63> https://pub.towardsai.net/4-types-of-machine-learning-interview-questions-for-data-scientists-and-machine-learning-engineers-b8135805ce1b

为机器学习步骤设计一个管道

原文:https://towardsdatascience.com/crafting-one-pipeline-for-machine-learning-steps-373f03e44e1b

使用 scikit-learn 从输入转换到网格搜索

“一条管道来统治他们,一条管道来寻找他们,一条管道来把他们都带来并在亮度上适合他们。

罗迪翁·库察耶夫Unsplash 上拍摄的照片

当我们查看市面上一本机器学习书籍的“目录”(即Geron,2019 )时,我们看到,在获取数据并将其可视化以获得洞察力之后,大致上有数据清洗、转换和处理数据属性、缩放特征、训练然后微调模型等步骤。数据科学家钟爱的模块 scikit-learn ,有一个惊人的功能(类)以一种简化的方式处理这些步骤: Pipeline

在探索在线管道的最佳用途时,我遇到了一些很棒的实现。Luvsandorj 很好地解释了它们是什么(2020),并展示了如何定制一个更简单的(2022)。Geron(2019,p . 71–72)举了一个例子,写了我们“自己的自定义转换器,用于自定义清理操作或组合特定属性等任务”。迈尔斯 (2021)展示了如何用一个分类器的流水线来运行网格搜索。另一方面, Batista (2018)提出了如何在没有管道的情况下在网格搜索中包括大量分类器。

介绍

在这篇文章中,我将把这些来源结合起来,提出一个最终的 ML 管道,它可以处理大多数 ML 任务,如(I)特征清理,(ii)处理缺失值,(iii)缩放和编码特征,(iv)降维,以及(v)运行许多具有不同参数组合的分类器(网格搜索),如下图所示。

作者图片

目录

I-库和数据集

II-设置类

III-设置网格搜索参数

<#ea63>四、建筑及安装管道

V——计算分数

结论

信息库和数据集

为了简单起见,让我们使用 Titanic 数据集,它可以很容易地从 seaborn 库加载。

作者图片

II-设置类别

我们需要为此流程创建以下类:

  • “FeatureTransformer”操作熊猫数据框,列。例如,虽然它对模型没有影响,但我添加了一个“strlowercase”参数,该参数可以应用于一列(的列表)来转换数据。(受 启发 g 埃隆)2019 年第 71–72 页
  • 处理缺失值的“估算器”(类似于 sklearn 的简单估算器类)
  • 处理缺失值的“缩放器”(类似于 sklearn 的标准缩放器类)
  • 【编码器】对(分类的或顺序的)特性进行编码(受Luvsandorj,2022
  • “分类器开关”用于在网格搜索步骤中的分类器之间切换。(灵感来自<#0195>,2021)****

III-设置网格搜索参数

我们创建两个字典(灵感来自 巴蒂斯塔 ,2018) :**

1- models_for_gridsearch =分类器名称作为关键字,分类器对象作为值

2- params_for_models =分类器名称作为关键字,分类器超参数作为:

  • 空字典(如 LogisticRegression 行含义分类器将与默认参数一起使用)或
  • 带列表的词典(KNeighboursClassifier 或 RandomForestClassifier 行)
  • 字典列表(如 SVC 行)

注意:为了简单起见,我注释掉了其他的分类器对象。

我们的目标是创建一个包含分类器和参数选择的字典列表,然后在网格搜索中使用它们。

作者图片

四-建筑和安装管道

我们已经为我们的管道创建了我们需要的类。是时候按顺序使用它们并使其适合了。

我们有一条管道

  • 为我们的列分配数据类型,
  • 对一些列进行基本的数据转换
  • 估算数字列的缺失值,并对其进行缩放
  • 编码分类列,
  • 减少尺寸,
  • 进料后通过分类器。

在将数据分成训练集和测试集之后,我们将这个管道与网格搜索管道参数一起输入到 GridSearchCV 中,以找到最佳评分模型,并使其适合我们的训练集。

最佳参数:{'clf': LogisticRegression()}

最高分:0.8573

按作者排列图像的网格搜索模型

V-计算分数

我们可以使用拟合的管道(pipeline_gridsearch)来计算分数或找到属于我们的目标状态的每个实例的概率。

训练 ROC-AUC: 0.8649

测试 ROC-AUC: 0.8281

作者图片

可以看出,由于我们的 FeatureTransformer 步骤,embark town 值是小写的。毕竟,这是为了证明我们可以在管道内转换我们的特性。

结论

定制 sklearn 类的方式可以(I)转换和预处理我们的特性,(ii)使多个 ML 模型适合各种超参数,有助于增加代码的可读性,并更好地控制 ML 步骤。尽管这种方法需要顺序计算(请参见下图),而多管道方法可以提供并行化,尽管边际时间损失,单独构建一个管道在我们需要循环通过许多条件、参数、预处理步骤来查看它们对模型的影响的环境中会更有益。**

作者图片

参考

热潮还是潮流?解读分析工程师的角色

原文:https://towardsdatascience.com/craze-or-trend-decoding-the-role-of-an-analytics-engineer-73ad0ed6555c

数据作业

这个新角色在数据团队中处于什么位置?你为什么要关心它?

努力融入——照片由 SHVETS 制作

几年来,“分析工程师”一词一直被用于数据科学领域来描述一个新的数据角色。例如,在【2021 年 1 月发表的一篇文章中,Prukalpa Sankar 提到了分析工程师等新角色的出现。是不是说数据相关的工作范围还没定?绝对的。我们是否应该认为分析工程师是任何数据团队未来都需要的下一个数据角色?如果是,如何过渡到这个角色?这就是我想在这篇文章中讨论的。

自从我转行到数据世界,我一直是一名数据分析师。当我与数据工程师一起工作时,我的一个挫折——我猜我不是唯一有这种感觉的人——是依赖其他人来访问原始数据库表中可用的一些字段,但这些字段在我们的数据架构的转换层中还不可用。

在我进入分析工程师角色的细节之前,让我详细说明一下这个具体的用例。一个来自营销业务用户的数据请求刚刚到达我的办公桌:他们想在他们的仪表板中添加一个特定的过滤器。这将是我作为数据分析师的工作,为他们在仪表板中添加这个新的过滤器。那么问题是什么呢?他们请求的字段确实在原始数据源中(例如,数据工程师从 Google Analytics 中提取的数据),但不在构建仪表板的清理过的表中。在这种情况下,我会遵循以下流程:

玛丽·勒费夫尔

这是一个漫长的过程,不是吗?对于数据工程师来说,这通常不是他们最喜欢的请求类型,因为没有战略思维:将这个字段从原始数据源添加到清理后的表中只是一个简单的操作。这正是分析工程师能够满足这种需求的地方。

什么是分析工程师?

正如 Claire Carroll 在这篇分析工程介绍文章中所说,分析工程师的工作可以总结如下:

“分析工程师向终端用户提供干净的数据集,以一种让终端用户能够回答他们自己的问题的方式建模数据。”

在这方面,分析工程师位于数据生命周期中的数据工程师和数据分析师之间。数据工程师构建数据集成并维护数据平台,而分析工程师将版本控制、测试和持续集成应用于分析代码。数据分析师进行深度分析并为业务用户构建仪表盘,而分析工程师则提供可供分析的转换数据。

因此,如果我们看看现代数据堆栈的不同数据角色,分析工程师涵盖了数据工程师的部分工作和数据分析师的部分工作。这是我对现代数据堆栈框架内分析工程师角色的描述:

玛丽·勒费夫尔

为什么会出现这种情况,这种趋势会持续下去吗?

2021 年 7 月,这篇名为“我们紫色的人”的文章发表在 dbt 的博客上。对我来说,它为理解为什么分析工程师的工作会出现以及为什么它会一直存在打下了基础。最近(2022 年 8 月),Reddit 上发布了一张图表,显示了 dbt 社区中数据职位发布的演变。确定的趋势之一是自 2019 年以来,分析工程师不可否认的出现。因此,毫无疑问,分析工程师的兴趣和工作岗位一直在增长。

为什么会这样呢?我发现的第一个原因是数据管道越来越复杂。随着您公司的数据在所有部门中结构化和丰富化,很难对每件事都保持关注。即使当数据团队设法协调他们自己并正确地共享信息时,当从数据分析师到数据工程师(可能反之亦然)的请求数量不断增加时,也会出现一些摩擦。

另一个原因是一些数据分析师愿意更多地参与数据管道的上游(换句话说:在转换步骤期间)以及一些数据工程师希望更接近业务需求。随着工作的发展,通常有两条路可以走:要么你专攻某一领域,成为专家;要么你保持全才,成为经理或团队领导。如果数据角色有第三种方式会怎样?这就是分析工程师的工作:在获得新技能的同时保持通才。

你应该成为一名分析工程师吗?

我认为今天的分析工程师来自数据堆栈的转换层的两个方面。由于数据分析师的就业市场比数据工程师的就业市场更广泛,竞争也更激烈,我可以想象更多的数据分析师将成为分析工程师。至于未来的分析工程师(换句话说:就业市场的新人),我已经在网上看到了分析工程训练营和其他培训。

如果你是一名数据分析师,对数据在分析之前是如何转换的感到好奇,那么成为一名分析工程师会教你如何根据业务用户的需求对数据建模。如果你是一名数据工程师,却错过了工作中的一些业务方面,那么成为一名分析工程师会让你更接近业务用户。

如果你已经有一些技术背景,或者你已经是一名数据分析师或数据工程师,过渡到分析工程师职位的最佳方式可能是完善你自己的技能。通过查看在线资源,你可以建立一个对你最有益的“定制”培训。例如,我看到了 Madison Mae 写的一本“入门”指南,她详细描述了任何分析工程师都应该具备的基础。

结论

回到我的介绍性例子,让我们想象一下,我现在可以依靠分析工程师,而我仍然是营销团队的数据分析师。在这种情况下,在从源到仪表板的数据管道中添加新字段将遵循以下过程:

玛丽·勒费夫尔

会不会更有效率?我认为确实如此,因为它减少了中间环节的数量,而分析工程师专门负责对数据建模,并确保数据生命周期中的数据质量。

你喜欢阅读这篇文章吗? 成为会员 加入一个不断成长的充满好奇心的社区吧!

https://marie-lefevre.medium.com/membership

使用 Streamlit 和 Raceplotly 创建一个条形图比赛动画应用程序

原文:https://towardsdatascience.com/create-a-bar-chart-race-animation-app-using-streamlit-and-raceplotly-e44495249f11

了解如何使用 Streamlit 构建一个无需编码即可在几秒钟内生成条形图比赛动画的应用程序

图片由皮克斯拜(作者修改)

介绍

条形图竞赛是一个有趣的数据可视化,显示随着时间的推移,随着条形图的演变而变化的数据。它被广泛有效地用作一种可视化类型,以显示和评估一组实体在定义的时间范围内的排名。

在过去,用 Python(例如Plotly)创建条形图竞赛并不是一件容易的事情,需要大量的编码。幸运的是,我找到了这个名为raceplotly的包,它是专门为用几行简单的 python 代码创建这样一个图形而编写的。当我需要创建一个条形图比赛动画时,它确实节省了我很多时间和头痛。

然后,我进一步思考:我们创建一个应用程序,让用户快速轻松地创建条形图比赛动画,而无需编写任何代码或了解 python,如何?这可能是一个很酷的动手项目,以提高我的 Streamlit 技能!你感兴趣吗?

所以,没有进一步的拖延,让我们开始建立这个应用程序。我们将要创建的应用程序如下所示(2 分钟的 YouTube 视频演示):

作者的 YouTube 视频

作者图片

先决条件

安装 Streamlit:

可以参考下面的文章,按照说明安装 Streamlit,学习基础知识。

安装以下软件包:

pip install raceplotlypip install streamlit-aggridpip install streamlit-option-menu

导入库

应用程序设计概述

先简单说一下 app 的整体布局和设计。在我之前的文章中,我们谈到了如何使用streamlit-option-menu来创建一个漂亮的、专业外观的导航菜单。

https://medium.com/codex/create-a-multi-page-app-with-the-new-streamlit-option-menu-component-3e3edaf7e7ad

我们可以对这个应用程序做同样的事情。我们将创建一个多页应用程序,包括:

  • “关于”页面:介绍应用程序或应用程序创建者
  • “演示”页面:显示了该应用程序的简短视频演示
  • 应用程序页面:允许用户上传一个 csv 文件,并立即生成一个条形图比赛动画
  • “联系人”页面:显示供用户填写的联系人表单

在本帖中,我们将主要关注“应用”页面,因为我们已经在前述文章中讨论了如何使用streamlit-option-menu创建多页面应用。对于“应用”页面,我将详细解释代码,而对于其他页面,代码非常简单易懂。

作者图片

在边栏中创建菜单

首先,让我们使用streamlit-option-menu创建一个菜单,并使用下面的代码将其添加到侧边栏。

创建“关于”页面

接下来,让我们使用下面的代码创建“关于”页面:

创建“演示”页面

我们可以创建一个演示页面,显示一个简短的视频剪辑来演示应用程序。为此,您可以录制一个关于该应用程序的短视频,并将其保存为. mp4 媒体文件,路径与 python 文件相同。

在下面的代码中,第 6–7 行将视频文件读入 Streamlit,第 8 行使用st.video()显示视频播放器。从 Streamlit 的文档来看,看起来你也可以通过直接指向 YouTube URLs 来显示 YouTube 视频,这非常好!

作者图片

创建“应用程序”页面:

步骤 1:添加文件上传程序

这是酒吧比赛生成器的应用程序页面,也是应用程序中最重要的页面。我们将在这里花大量的时间解释这个页面是如何创建的。

我们首先创建一个文件上传程序,允许用户上传一个 csv 文件,然后使用st.aggrid()将数据显示为一个格式良好的表格。你需要记住的一件重要的事情是,在我们使用st.file_uploader()创建一个文件上传程序(第 8 行)之后,我们需要将剩余的代码(第 10–21 行)放在if uploaded_file is not None:语句中。使用此语句可以确保当 csv 文件尚未上传时,应用程序不会因为找不到任何要导入和显示的文件而抛出任何错误消息。

作者图片

第二步:使用Raceplotly创建条形图比赛动画

在用户上传数据后,我们希望将该数据传递给幕后的 dataframe,并使用raceplotly生成 bar race 图。如果没有raceplotly,可以通过运行下面的命令行来安装:

pip install raceplotly

要使用raceplotly创建一个酒吧比赛图,您只需要两行带有几个参数的代码。下面是一个使用我们之前上传的样本数据创建条形图比赛动画的例子。

raceplot = barplot(df,  item_column='Country Name', value_column='GDP', time_column='Year')

raceplot.plot(item_label = 'Top 10 Countries', value_label = 'GDP ($)', frame_duration = 800)

这里最重要和必需的参数是:

  • item_column:表示要排名的项目/栏的列名(如国家、团队、公司等)。)
  • value_column:表示用于排名的数值/指标的列名(如国内生产总值、人口、分数等)。)
  • time_column:表示时间变量的列名(如年、月等)。).

其余的论点(如 item_labelvalue_label等)。)只是自定义和微调剧情的方法。如果你想了解更多关于这些参数的信息,你可以在这里阅读官方raceplotly文档

步骤 3:创建输入部件来接收用户输入

上面两行代码的问题是,当用户上传他们自己的数据时,数据很可能具有与上面的例子不同的列名。因此,除非用户明确告诉我们,否则我们不知道应该为item_columnvalue_columntime_column使用哪个列名。

因此,我们需要在应用程序中创建一些用户输入小部件,以接受用户对这些参数的输入。我们还可以添加一些小部件,允许用户对情节进行各种定制,以增强应用程序的用户体验。

作者图片

如上图所示,我们将创建 12 个输入参数,排列成 4 行 3 列。前三个参数是必需的字段,而其余的是可选的。让我们用下面的代码来看看如何做到这一点。确保在if uploaded_file is not None:语句中插入的代码具有正确的缩进。

让我们更详细地看看上面的代码:

第 3–5 行:创建一个包含用户上传数据中所有列名的列表。我还在列表的开头加了一个“-”。这是我用来在窗口小部件中允许默认选择为'-'的一个小技巧,这样对用户来说更明确,他们必须从列表中选择一些东西。

第 7–15 行:创建前 3 个参数,并使用st.columns()并排显示它们。注意,我们还可以通过在st.selectbox()小部件中包含help=来在小部件上显示一个小的帮助图标。

第 17–50 行:创建其余的参数。这些参数是可选的,允许用户定制或微调图,例如改变条的方向、调整动画速度、改变图的大小等。

第 6 行和第 52 行:我们使用st.form()st.form_submit_button()来批量输入所有的小部件,这样用户就可以随心所欲地与小部件进行交互,而不会在每次更改小部件时触发应用程序的重新运行。

步骤 4:生成条形图比赛动画

在用户提交他们的输入部件选择后,我们将把他们的输入传递给raceplotly并生成条形图比赛动画。但在此之前,我们需要首先关注两件重要的事情:

  1. 我们希望确保用户为前三个参数提供输入,因为它们是生成绘图所需的字段。如果用户没有完成必填字段,我们可以显示一条警告消息。
  2. value_column必须是数字,time_column必须是日期类型。用户上传的数据可能并不总是符合要求,因此我们需要确保在将 dataframe 传递给raceplotly之前,这两列被转换为正确的数据类型。

在下面的代码中,第 3–8 行处理了这两点。第 10–19 行接受用户输入并将参数传递给barplot()函数,生成一个动画条形图比赛图,并在应用程序中显示。请确保您在if uploaded_file is not None:语句中插入的代码块具有正确的缩进。

作者 Gif

你也可以给这个应用程序添加更多的功能,比如让用户能够将条形图比赛动画导出到 HTML,并与他人分享。我已经在另一篇关于如何在 Streamlit 中创建一个“导出”按钮的文章中介绍了这种技术,该按钮允许用户将plotly图表导出到 HTML。如果你有兴趣,你可以在这里阅读文章。

https://medium.com/codex/create-a-simple-project-planning-app-using-streamlit-and-gantt-chart-6c6adf8f46dd

创建“联系人”页面

最后,让我们使用下面的代码创建一个联系页面:

万岁!我们已经使用 Streamlit 和raceplotly创建了一个快速简单的条形图比赛生成器!我总是喜欢动手项目,因为对我个人来说,这是学习新东西和提高编程技能的最有效方式。

感谢阅读。我希望你喜欢这篇文章,并准备将你所学到的应用到你的下一个新的令人兴奋的项目中!

作者图片

数据源:

应用程序演示中使用的数据集是从世界银行网站https://data.worldbank.org/indicator/NY.下载的开放数据集(无需许可)GDP.MKTP.CD?name_desc=true

你可以通过这个推荐链接注册 Medium 会员(每月 5 美元)来获得我的作品和 Medium 的其他内容。通过这个链接注册,我将收到你的会员费的一部分,不需要你额外付费。谢谢大家!

《我的世界》的强化学习:创造一个寻找钻石的机器人

原文:https://towardsdatascience.com/create-a-bot-to-find-diamonds-in-minecraft-d836606a993a

用 MineRL 实现 Python 中的强化学习和行为克隆

图片作者( Mojang license )

《我的世界》是人工智能的下一个前沿。

这是一个巨大的游戏,有许多机制和复杂的动作序列。仅仅是教人类如何玩《我的世界》,就需要一个超过 8000 页的整个维基。那么人工智能能有多好呢?

这是我们将在本文中回答的问题。我们将设计一个机器人,并尝试完成《我的世界》最困难的挑战之一:从零开始寻找钻石。更糟糕的是,我们将在随机生成的世界中接受这一挑战,因此我们无法学习特定的种子。

寻找钻石的行动顺序,图片作者( Mojang 许可证)

我们要谈的不仅限于《我的世界》。可以应用于类似的复杂环境。更具体地说,我们将实现两种不同的技术,它们将成为我们智能代理的主干。

但是在我们训练代理人之前,我们需要了解如何与环境互动。让我们从一个脚本化的机器人开始熟悉语法。我们将使用 MineRL ,一个奇妙的库来在《我的世界》构建人工智能应用程序。

本文中使用的代码可以在的 Google Colab 上找到。它是由 MineRL 2021 竞赛(麻省理工学院许可)的组织者制作的优秀笔记本的简化和微调版本。

📜一.脚本机器人

MineRL 允许我们用 Python 启动《我的世界》,并与游戏互动。这是通过流行的gym库完成的。

作者图片

我们在一棵树前。如你所见,分辨率相当低。低分辨率意味着更少的像素,这加快了速度。对我们来说幸运的是,神经网络不需要 4K 的分辨率来理解屏幕上发生的事情。

现在,我们想让在游戏中互动。我们的代理能做什么?以下是可能采取的行动:

动作列表(图片由作者提供)

找到钻石的第一步是获取木材来制作一张工艺桌和一把木制鹤嘴锄。

让我们试着靠近那棵树。这意味着我们需要按住“前进”按钮不到一秒钟。使用 MineRL,每秒处理 20 个动作:我们不需要一整秒,所以让我们处理 5 次,然后再等待 40 个滴答。

作者图片

作者图片

太好了,让我们现在砍树吧。我们总共需要四项行动:

  • 前进走在树的前面;
  • 攻击砍树;
  • 摄像头向上或向下看;
  • 拿到最后一块木头。

作者图片

操作相机可能会很麻烦。为了简化语法,我们将使用来自 GitHub 库str_to_act函数 (MIT 许可证)。这是新脚本的样子:

代理人高效的砍下了整棵树。这是一个好的开始,但我们希望以更自动化的方式来完成它…

🧠二世。深度学习

我们的 bot 在固定环境下运行良好,但如果我们改变种子或其起点会发生什么?

一切都是按照的剧本进行的,所以代理人可能会试着砍一棵不存在的树。

这种方法对于我们的需求来说太静态了:我们需要能够适应新环境的东西。我们想要一个知道如何砍树的人工智能,而不是编写命令。自然,强化学习是训练这个代理的一个相关框架。更具体地说,deep RL 似乎是解决方案,因为我们正在处理图像以选择最佳行动。

有两种实现方式:

  • 纯深度 RL :通过与环境的交互,从零开始训练智能体。它每砍一棵树都会得到奖励。
  • 模仿学习:代理学习如何从数据集中砍树。在这种情况下,它是一个人砍树的一系列动作。

这两种方法具有相同的结果,但它们并不等同。据 MineRL 2021 竞赛的作者介绍,纯 RL 解决方案需要 8 小时,模仿学习代理需要 15 分钟才能达到相同的性能水平。

我们没有那么多时间可以花,所以我们选择模仿学习的解决方案。这种技术也叫行为克隆,是模仿的最简单形式。

注意模仿学习并不总是比 RL 更有效率。如果你想了解更多,Kumar 等人写了一篇关于这个话题的很棒的博文

作者图片

该问题被简化为多类分类任务。我们的数据集由 mp4 视频组成,因此我们将使用一个卷积神经网络 (CNN)将这些图像转化为相关的动作。我们的目标也是限制可以采取的行动(类别)的数量,这样 CNN 就有更少的选择,这意味着它将得到更有效的训练。

本例中,我们手动定义 7 个相关动作:攻击、前进、跳跃、移动摄像机(左、右、上、下)。另一种流行的方法是应用 K-means 来自动检索人类采取的最相关的动作。在任何情况下,我们的目标都是抛弃最没用的行为来完成我们的目标,比如我们例子中的手工制作。

让我们在MineRLTreechop-v0数据集上训练我们的 CNN。其他数据集可以在这个地址找到。我们选择 0.0001 的学习率和 6 个时期,批量大小为 32。

Step  4000 | Training loss = 0.878
Step  8000 | Training loss = 0.826
Step 12000 | Training loss = 0.805
Step 16000 | Training loss = 0.773
Step 20000 | Training loss = 0.789
Step 24000 | Training loss = 0.816
Step 28000 | Training loss = 0.769
Step 32000 | Training loss = 0.777
Step 36000 | Training loss = 0.738
Step 40000 | Training loss = 0.751
Step 44000 | Training loss = 0.764
Step 48000 | Training loss = 0.732
Step 52000 | Training loss = 0.748
Step 56000 | Training loss = 0.765
Step 60000 | Training loss = 0.735
Step 64000 | Training loss = 0.716
Step 68000 | Training loss = 0.710
Step 72000 | Training loss = 0.693
Step 76000 | Training loss = 0.695

我们的模型是经过训练的。我们现在可以实例化一个环境,看看它的行为。如果训练成功,它应该疯狂地砍掉视线内的所有树木

这一次,我们将使用ActionShaping包装器将通过dataset_action_batch_to_actions创建的数字数组映射到 MineRL 中的离散动作。

我们的模型需要正确格式的视点观察并输出逻辑。这些逻辑可以通过softmax函数转化为一组 7 个动作的概率分布。然后我们根据概率随机选择一个行动。多亏了env.step(action),选定的动作在 MineRL 中被执行。

这个过程可以重复很多次。让我们做 1000 次,看看结果。

我们的代理人相当混乱,但它设法在这个新的、看不见的环境中砍树。现在,如何找到钻石?

⛏️三世。脚本+模仿学习

一个简单而强大的方法是将的脚本动作和人工智能结合起来。学习枯燥的东西,将知识编写成脚本。

在这个范例中,我们将使用 CNN 来获取适量的木材(3000 步)。然后,我们可以编写一个序列来制作木板、棍子、手工桌、木镐,并开始开采石头(它应该在我们的脚下)。这种石头可以用来制作一个可以开采铁矿石的石镐。

CNN +脚本方式,图片由作者( Mojang license )

这就是事情变得复杂的时候:铁矿石非常稀有,所以我们需要运行一段时间来寻找矿藏。然后,我们将不得不制造一个熔炉,熔化它来得到铁镐。最后,我们必须进入更深的地方,更加幸运地在不掉进岩浆的情况下获得钻石。

如你所见,这是可行的,但结果是随机的。我们可以训练另一个代理人去寻找钻石,甚至训练第三个代理人去制造铁镐。如果你对更复杂的方法感兴趣,你可以阅读 Kanervisto 等人的minell Diamond 2021 竞赛的结果。它描述了几种使用不同智能技术的解决方案,包括端到端深度学习架构。然而,这是一个复杂的问题,没有一个团队能够持续不断地找到钻石,如果有的话。

这就是为什么在下面的例子中我们将自己限制在获取一个石镐,但是您可以修改代码来做得更好。

我们可以看到我们的代理在最初的 3000 步中像疯子一样劈柴,然后我们的脚本接管并完成任务。这可能不明显,但是命令print(obs.inventory)显示了一个石镐。请注意,这是一个精选的例子:大多数运行都没有那么好的结局。

代理失败的原因有几个:它可以在恶劣的环境中繁殖(水,熔岩等)。),在没有木头的区域,甚至会摔死。尝试不同的种子会让你很好地理解这个问题的复杂性,并且有希望获得构建更好的事件代理的想法。

结论

我希望你喜欢这个《我的世界》强化学习的小指南。除了其明显的受欢迎程度,《我的世界》还是一个尝试和测试 RL 代理的有趣环境。像 NetHack 一样,它需要对其机制有透彻的了解才能在程序生成的世界中计划精确的行动顺序。在这篇文章中,

  • 我们学会了如何使用密涅尔
  • 我们看到了两种方法(脚本和行为克隆)以及如何组合它们;
  • 我们用短视频把代理的动作可视化

该环境的主要缺点是它的处理时间慢。《我的世界》不是像 NetHack 或 Pong 那样的轻量级游戏,这就是为什么代理需要很长时间来训练。如果这对你来说是个问题,我会推荐像健身房复古这样的轻松环境。

感谢您的关注!如果你对应用于电子游戏的人工智能感兴趣,请在 Twitter 上关注我。

使用 Plotly 轻松创建蝴蝶图

原文:https://towardsdatascience.com/create-a-butterfly-chart-easily-using-plotly-aa3d43ba410d

如何用两个简单的步骤创建一个漂亮的蝴蝶图

图片由 Pixabay 提供

蝴蝶图(也称为龙卷风图或双向图)是一种数据可视化类型,它通过并排显示两个水平条形图来比较两个数据系列。左右横条共享中心的同一 y 轴,并沿 x 轴指向相反的方向,类似蝴蝶翅膀。

蝴蝶图示例(图片由作者提供)

蝴蝶图是在一组维度上可视化两组之间差异的好方法。例如,您可以使用它来可视化两组调查参与者(例如,调查 1 与调查 2)对一组调查问题的响应差异。你可以用它来形象化不同行业的性别薪酬差距。你可以用它来比较两种产品在不同地区的销售情况,等等。

在这篇文章中,我将向你展示如何用两个简单的步骤用Plotly创建一个蝴蝶图。Plotly没有内置的蝶形图“跟踪”。然而,由于蝴蝶图只是水平条形图的一种特殊类型,我们可以使用一些变通方法来轻松解决这个问题。下面是我们将在本帖中创建的可视化。

作者图片

用于演示的样本数据可以从美国商务部网站上的 QWI 浏览器下载。该数据集按不同行业和性别显示了 Q1 2021 的就业人数。让我们导入所有必要的库并将数据读入一个pandas数据框架。

作者图片

要使用Plotly创建蝴蝶图,我们只需执行以下两个步骤:

步骤 1:创建一个并排有两个水平支线剧情的图形

步骤 2:反转左侧水平条形图的方向

就是这样!让我们看看它是如何工作的。

第一步:用两个并排的水平条形图创建一个图形

让我们首先使用plotly.subplots.make_subplots()创建两个水平条形图。这允许我们创建一个 1 行 2 列的绘图网格,我们可以用一个共享的 y 轴并排放置两个水平条形图(“跟踪”)。

第 2–3 行:因为我们想并排放置两个水平条形图,我们需要创建一个 fig(绘图网格),它有 1 行 2 列,共享 y 轴。

第 5–13 行:我们将第一个水平条形图添加到绘图中。这个条形图显示了各行业男性员工的就业情况。第 7–8 行使我们能够用千位分隔符显示计数,并在条形上显示数字。第 13 行指定了图表的位置—放置在绘图网格左侧的第 1 行第 1 列。

第 15–23 行:我们将第二个水平条形图添加到绘图中。该柱状图显示了按行业分列的女性员工的就业人数。第 23 行指定第二个条形图的位置——放置在绘图网格右侧的第 1 行第 2 列。

作者图片

步骤 2:反转左侧水平条形图的方向

上面的图表看起来不错!现在让我们反转第一个水平条形图的方向,并使图双向。这是通过使用fig.update_xaxes()并将range参数指定为[16000000,0]来实现的,如下面的代码(第 1 行)所示。

如果两个条形图的 x 轴范围相同,我们也可以使用autorange='reversed'。在我们的示例中,左侧条形图的默认 x 轴范围(自动范围)是 0–8M,这比右侧条形图的范围(0–15M)小得多,会使可视化产生误导。因此,我们为左侧图表显式指定范围,并使用[16000000,0]来确保左侧图表的 x 轴反转(从右到左)。

我们还可以添加一个图表标题,并使用fig.update_layout()调整图片大小和其他属性。注意,在第 11 行和第 12 行,我们可以指定 x 轴标题的位置在顶部。

作者图片

这就对了。只需两个简单的步骤和几行 python 代码,您就创建了一个漂亮的蝴蝶图,直观地显示了不同行业中男性和女性员工人数的差异。从可视化上,我们可以立刻分辨出哪些行业板块是男性集中 vs .女性集中,结果相当直观!

感谢阅读,我希望你喜欢这个关于Plotly的教程。如果你对Plotly完全陌生,并且想阅读一篇关于如何使用Plotly创建数据可视化的介绍性文章,你可以在这里阅读我的另一篇文章

数据来源:

Q1 2021 年按行业和性别统计的就业人数:美国人口普查局,LEHD 经济研究中心。这是一个免费的开放数据集,可以使用 QWI 浏览器下载。不需要许可证。

你可以通过这个推荐链接注册 Medium 会员(每月 5 美元)来获得我的作品和 Medium 的其他内容。通过这个链接注册,我将收到你的一部分会员费,不需要你额外付费。谢谢大家!

用谷歌的模型卡工具包创建一个定制的模型卡

原文:https://towardsdatascience.com/create-a-custom-model-card-with-googles-model-card-toolkit-a1e89a7887b5

如何将您自己的字段添加到 Tensorflow 模型卡工具包模式中,以便自动生成自定义机器学习模型文档

这份文档并不老,而是“老古董”(图片由作者提供。)

随着算法决策的快速采用,模型透明性变得越来越重要。帮助解决这个透明性问题的一个提议的解决方案是模型卡,它提供了带有模型性能度量和重要考虑事项的简明的便笺或备忘单风格的文档。准确和最新的文档很难获得,但像谷歌的开源 Tensorflow 模型卡工具包 (MCT)这样的新工具可以通过在执行建模脚本时以编程方式自动生成文档并直接插入模型性能指标和图形来提供帮助。

虽然 MCT 为模型卡内容提供了一个很好的模板,但我在使用它时犹豫了,因为我真的需要能够添加额外的信息来适当地记录我的一些用例的工作。对我来说,所提供的模式(在 protobuf 中定义)是一个真正的限制因素。此外,Python 数据类目前是在模型卡工具包的安装步骤 期间从所提供的模式中生成的

那么,如果模式是在安装中硬编码的,我如何扩展模型卡模板模式来添加关于我的模型的更详细的信息呢?我四处查看了一下,找到了所有需要修改的地方,以便添加模型卡内容,并构建了我自己的安装。因为我下周会忘记如何做这件事,除非我今天把它完整地记录下来,所以我想我会把细节以博客的形式发布出来。(嗯,感觉这里面有什么教训……)

TL;速度三角形定位法(dead reckoning)

所有材料都可以在我的 GitHub 模型-卡片-演示 repo 中获得。接下来,将 repo 克隆到您的本地环境中。你可以只在你的系统上运行 Docker 的例子,而不需要在本地安装任何东西,它将会与你当前拥有的任何版本的 MCT 隔离开来。在下面列出的文件中,我已经标记了在默认 MCT 模式中添加自定义字段所做的更改。只需搜索“自定义字段”

回购有几个不同的组成部分:

生成模型卡的 Python 脚本借用了 Google Cloud 的例子如何在云中创建和部署 scikit-learn 模型卡。Google 的例子是基于 colab 或 Jupyter 的笔记本,并在笔记本中渲染模型卡。我已经去掉了重要的部分,这样定制的模型卡就作为。您可以单独查看的 html 文件。

方向

先克隆 repo,换到根目录。

git clone https://github.com/mtpatter/model-card-demo.git
cd model-card-demo

接下来,构建一个安装了修改后的 MCT 的 Docker 映像。此图像基于 TensorFlow 提供的 Docker 图像。因为 MCT 工具包在安装时编译 Python 数据类(不是我的错!),无论何时对原型模式字段进行更改,都需要重新生成映像来重新安装模型卡工具包。

docker build -t "cards" .

训练模型,生成模型卡。请注意:下面的命令将运行一个 Docker 容器,该容器在本地挂载,对本地目录具有写访问权限,以便写入结果。

docker run \
  -u $(id -u):$(id -g) \
  --rm \
  -v $PWD/:/user/cards cards python make_card.py

结果被本地写入。model_cards目录下的 html 文件。

虽然最初的模型卡看起来像是这个,但是新生成的定制模型卡在第二行有一个额外的框,我在那里添加了更多的信息。在本例中,它是“需要知道的事情”,我在其中插入了另一个标题“干预”,以及一个包含字符串“评估”的列表。

结果代码生成的模型卡。(图片由作者提供,修改自 Tensorflow MCT 示例输出。)

粗糙的细节

我不得不做一些改变,下面描述一下。

旁白:这应该有助于更好地理解 MCT 如何用 protobuf 格式构造和保存模型卡,proto buf 格式是一种序列化为文件的结构化数据格式(有点类似于数据库)。使用 protobuf 有很多好处,包括能够保留数据类型(int、str 等)的特性。)当数据被保存并且能够传递与语言无关的数据时。如果你关注我的任何工作,你会知道我更喜欢 Apache Avro,原因我会在另一篇博文中写。

  1. 模板。proto 文件需要额外的字段——这里我添加了 ThingstoknowIntervention 。安装时,会生成一个包含 Python 数据类的新文件,该文件是在工具包的其余部分中导入的,所以不要直接接触它。

(图片由作者提供。)

2.工具包本身需要在 update_model_card()函数中进行编辑。

(图片由作者提供。)

3.模型卡模块本身需要更多的数据类和更新。

(图片由作者提供。)

4.jinja 模板需要修改,以便在 html 中插入一个新的 div。

(图片由作者提供。)

5.现在模型卡有了一个定制属性,model _ card . thingstoknow . intervention,它可以在您的模型卡生成脚本中定义。

(图片由作者提供。)

这可能看起来有点粗糙(因为确实如此),但它确实有效。四处探索也有助于更好地揭示工具包的工作方式,这对于它的 protobuf 序列化组件来说尤其有趣。这些信息在任何地方都没有真正描述过,但它似乎是模型卡的整个基础…无论如何,请继续关注未来的协议缓冲区博客帖子!

创建一个带有限制登录页面的 Django 应用程序

原文:https://towardsdatascience.com/create-a-django-app-with-login-restricted-pages-31229cc48791

现在,只有授权用户才能看到您的应用程序页面

照片由丹·尼尔森Unsplash 拍摄

简介

有时你可能想创建一个 Django 应用程序,它的页面可以被互联网上的任何人访问。其他时候,您需要保存敏感信息,因此只有经过身份验证的用户才能打开和查看部分或全部页面。Django 再一次用它的内置资源拯救了我们,这使得构建部分或全部受限于授权用户的应用程序变得更加容易。

在本教程中,我将通过使用我在之前的教程中创建的 Films CRUD 应用程序向您展示如何做到这一点:

因此,我邀请你克隆这个 GitHub 库并遵循它的README.md指令,这样你就可以在你的机器上安装并运行 Films CRUD 应用。在本地运行项目后,您的文件夹结构将如下所示(__pycache__文件夹不在此显示):

作者图片

我们将修改其中一些文件,使所有的应用程序页面在第一时间限制为授权用户;然后,我们将只对修改数据库的页面保留这种限制(通过创建、更新或删除电影)。

第 1 部分:限制所有页面

1.为你的电影应用创建一个超级用户,如果你还没有这么做的话。

python manage.py createsuperuser

如果您使用这个新的超级用户登录到管理区(http://localhost:8000/admin),您可以访问用户模型(从django.contrib.auth默认应用程序)并通过在管理界面中选择正确的选项来创建其他用户。

您还可以通过编写代码来创建新用户,这些代码收集用户在注册页面上提供的信息,并执行函数视图或类视图来将这些数据保存到新的django.contrib.auth.models.User实例中。然而,这些步骤超出了本教程的范围。如果你对这个话题感兴趣,我在这里提到它们只是为了给你未来的研究提供一些方向。

2.在project/urls.py中,插入一条新的accounts/路线,如下图所示:

from django.contrib import admin
from django.urls import path, includeurlpatterns = [
    path('', include('films.urls')),
    path('admin/', admin.site.urls), # add this:
    path('accounts/', include('django.contrib.auth.urls')),
]

注意这条新路线是如何连接到已经在django.contrib.auth.urls可用的一些内置 Django 资源的。实际上,如果你打开你的虚拟环境文件夹,沿着路径Lib/django/contrib/auth,打开里面的urls.py文件,你会看到路径login/logout/,调用它们各自定制的基于类的视图。这两条路线将在我们的项目中实现 Django 的认证魔法。

3.创建一个templates/registration文件夹:

mkdir templates/registration

4.创建以下文件,并用以下链接中的内容填充它们:

在模板上,请注意next查询参数的存在。事实上,当访问路线accounts/loginaccounts/logout时,这个参数是必需的,因为它告诉 Django 在登录或退出后去哪里。

5.通过导入LoginRequiredMixin类并使FilmsBaseView继承它来修改films/views.py。这将使所有其他电影视图类也通过扩展继承LoginRequiredMixin。保持这里没有显示的其他代码行不变。

(...)# add this import:
from django.contrib.auth.mixins import LoginRequiredMixin# add LoginRequiredMixin to FilmBaseView
class FilmBaseView(LoginRequiredMixin, View):
    model = Film
    fields = '__all__'
    success_url = reverse_lazy('films:all')(...)

现在停止服务器,再次运行它,并尝试访问地址http://localhost:8000/films/create,这是创建新电影的路线。如果上面的所有代码都构建正确,您现在将无法访问该页面,但会被重定向到登录页面。还要注意查询参数next是如何在 URL 中设置的,它的值是/films/create/,表明 Django 将在成功登录后转到这个页面。

现在你只需要输入正确的登录信息。登录后,您将能够不受限制地再次浏览应用程序页面。

现在,如果您想注销,您应该在浏览器上访问http://localhost:8000/accounts/logout。然而,这不是一个非常实用的程序,如果在我们的应用程序中有一个链接可以更自然地完成这个操作就好了。因此,我们将在templates/base.html中的导航栏上添加一个新链接,它将使用user.is_authenticated方法运行一个条件语句,并检查用户是否登录。如果是这种情况,那么应用程序将显示一个注销链接。

6.将templates/base.html的内容改为这里提供的 HTML 代码 。注意导航条代码上新的{%if user.is_authenticated %}结构。必要时使用此新链接注销。

第二部分: 只让部分页面受限

现在,我们将只对您可以更新、创建或删除电影的页面进行限制访问。实际上,这将是一个超级简单的修改:我们只需要从我们的FilmBaseView中移除LoginRequiredMixin,并将其放置在我们想要限制的视图中(最后三个)。

7.对films/views.py进行如下修改。下面是修改后的完整代码:

重要:让LoginRequiredMixin成为FilmCreateViewFilmUpdateViewFilmDeleteView继承的第一个类。

结束语

就是这样!正如我们可以注意到的,在我们准备好所有的登录/注销结构(在registration文件夹中有具体的路径和各自的模板)之后,我们只需要将LoginRequiredMixin类传递给我们希望限制为授权用户的类视图,Django 为我们做所有剩下的工作。

此外,在第二个场景中,只有一些页面有访问限制,我们可以让像更新删除链接或创建新的按钮这样的资源只在当前用户登录时显示。如果您想做这些更改,只需将上面的链接和按钮放入带有{%if user.is_authenticated %}模板标签结构的条件语句中。现在只有经过认证的用户才能看到它们。

亲爱的读者,非常感谢你花时间和精力阅读我的文章。

快乐编码!

用 Python 从头开始创建正则化梯度下降算法

原文:https://towardsdatascience.com/create-a-gradient-descent-algorithm-with-regularization-from-scratch-in-python-571cb1b46642

通过自己实现来巩固你的梯度下降知识

安德烈·伯恩哈特在 Unsplash 上拍摄的照片

介绍

梯度下降是用于机器学习和优化问题的基本算法。因此,充分理解它的功能和局限性对于任何学习机器学习或数据科学的人来说都是至关重要的。本教程将实现一个从头开始的梯度下降算法,在一个简单的模型优化问题上测试它,最后进行调整以演示参数正则化。

背景

梯度下降试图通过调整模型参数找到成本函数的局部最小值。成本函数(或损失函数)将变量映射到一个代表“成本”或要最小化的值的实数上。

对于我们的模型优化,我们将执行最小二乘优化,其中我们寻求最小化我们的预测值和数据值之间的差异总和。等式 1 表示我们将使用的二次成本函数。

图片作者。

等式 1: 最小二乘优化代价函数。

这里,yhat 是独立变量的模型预测。对于此分析,我们将使用一个通用多项式模型,如等式 2 所示。

图片作者。

方程 2: 本次分析使用的一般多项式模型。

为简单起见,我们将保持这些方程为矩阵形式。这样做在等式 3 中呈现了我们的新模型,在等式 4 中呈现了 X 矩阵结构。请注意,yhat 的大小为(n,),beta 的大小为(m,),X 的大小为(n,m)。

图片作者。

方程 3: 我们模型的矩阵形式。

图片作者。

方程 4: 多项式矩阵,X.

现在,有了成本函数和我们将要部署的模型的背景,我们终于可以开始研究梯度下降算法了。

算法

梯度下降的工作原理是计算成本的梯度,调整参数使像斜坡一样下降梯度。

链式法则(回想一下多变量微积分)为我们提供了一种方法来估算给定参数变化的成本变化。这种关系在等式 5 中给出。

图片作者。

方程式 5: 应用链式法则来确定因参数变化而引起的成本变化。

知道了这一点,我们可以定义一个与成本梯度成比例的参数变化,如等式 6 所示。学习率(eta)被选择为一个小的正数。

图片作者。

方程式 6: 参数更新规则。

当这个更新参数的规则被插入等式 5 时,我们得到我们的证明,即所选择的参数更新规则将总是降低成本。

图片作者。

等式 7: 证明参数更新规则会降低成本。

如果我们回忆一下线性代数,我们可以记住成本梯度向量的平方永远是正的。因此,假设学习率足够小,这种更新方法将降低成本函数的梯度

现在,为了最终实现这个算法,我们需要一个数值计算梯度的方法。对于这个例子,我们可以拿着笔和纸做导数,但是我们希望我们的算法适用于任何模型和成本函数。等式 8 给出了我们这样做的方法,我们将通过一个小值调整每个参数,并观察成本的变化。

方程式 8: 计算成本梯度的数值方法。

数据

我们将为这个项目生成自己的数据集。我们将简单地生成一个独立值的线性间隔向量,并根据这些向量计算因变量,同时引入一些噪声。我设置了一个随机种子,让你看看你是否得到同样的结果。

import numpy as np
import matplotlib.pyplot as plt
np.random.seed(1234)def polynomial_model(beta, x):
    '''
    A polynomial model.
    beta: numpy array of parameters of size (m,)
    x: numpy array of size (n,) return yhat: prediction of the model of size (n,)
    '''
    # Turn x (n,) to X (n, m) where m is the order of the polynomial
    # The second axis is the value of x**m
    X = x[:, np.newaxis] ** np.arange(0, len(beta)) # Perform model prediction
    yhat = np.sum(beta * X, axis=1) return yhat# Construct a dataset
x = np.arange(-2, 3)
beta_actual = [1, 2, 1]
y = polynomial_model(beta_actual, x) + np.random.normal(size=x.size, scale=1)

我选择实际的模型参数为[1,2,3],噪声为标准偏差为 1 的正态分布。让我们看看下面的数据。

# Plot results
fig, ax = plt.subplots()
ax.plot(x, y, '.')
xplt = np.linspace(min(x), max(x), 100)
yplt = polynomial_model(beta_actual, xplt)
plt.plot(xplt, yplt, '-')ax.legend(['Data', 'Actual Relationship'])plt.show()

图片作者。

图 1: 我们的数据和实际模型。

模型创建

功能

我们的成本函数定义如下。请注意,我们只将 beta 设为位置参数,其余的我们将通过关键字参数传递。这是为了提高最终梯度下降算法的可读性,我们将在后面看到。

def cost(beta, **kwargs):
    """
    Calculates the quadratic cost, with an optional regularization
    :param beta: Model Parameters
    :param kwargs:
    :return:
    """
    x = kwargs['x']
    y = kwargs['y']
    model = kwargs['model'] # Calculate predicted y given parameters
    yhat = model(beta, x) # Calculate the cost
    C = sum((y-yhat)**2) / len(y)
    return C

算法

我们的梯度下降类需要我们的模型、成本函数、初始参数猜测和我们的数据。我们还可以调整参数,如学习率或步进参数,以计算梯度,但对于这个分析,我将它们设置为足够小的数字,并没有优化它们的值。

class GradDescent: def __init__(self, model, C, beta0, x, y, dbeta=1E-8, eta=0.0001, ftol=1E-8):
        self.model = model
        self.C = C
        self.beta = beta0
        self.x = x
        self.y = y
        self.dbeta = dbeta
        self.eta = eta
        self.ftol = ftol

现在我们终于可以实现梯度下降算法了。我们将首先创建一个成本函数的输入字典,它不会因迭代而改变。

def descend(self):
        # This dict of cost parameters does not change between calls
        cost_inputs = {'x': self.x,
                       'y': self.y,
                       'model': self.model
                       }

接下来,我们将初始化一个成本列表,并开始迭代。

# Initialize a list of costs, with the indices being the iteration
        costs = [self.C(self.beta, **cost_inputs)] run_condition = True

对于每次迭代,我们必须:

  1. 计算梯度
  2. 更新参数
  3. 计算新成本
  4. 评估我们的运行状况

这一过程如下所示

while run_condition:
            # Get the gradient of the cost
            delC = [] for n, beta_n in enumerate(self.beta):
                # Create a temporary parameters vector, to change the nth parameter
                temp_beta = self.beta
                temp_beta[n] = beta_n + self.dbeta  # Adjusts the nth parameter by dbeta
                C_n = self.C(temp_beta, **cost_inputs)
                dC = C_n - costs[-1]
                delC.append(dC / self.dbeta) # Update the parameters
            self.beta = self.beta - self.eta * np.array(delC) # Re calc C
            costs.append(self.C(self.beta, **cost_inputs)) # Evaluate running condition
            run_condition = abs(costs[-1] - costs[-2]) > self.ftol

现在我们准备实现我们的模型。让我们初始化初始参数,创建一个梯度下降对象,优化我们的模型,并绘制结果。

# Initialize parameters, use a polynomial of order 5
beta0 = np.random.normal(size=(5,), scale=1)# Initialize a GradDescent object, perform descent and get parameters
gd = GradDescent(polynomial_model, cost, beta0, x, y)
gd.descend()beta = gd.beta# Make model prediction with parameters
yhat = polynomial_model(beta, x)# Plot results
fig, ax = plt.subplots()
ax.plot(x, y, '.')
ax.plot(x, yhat, 'x')
xplt = np.linspace(min(x), max(x), 100)
yplt = polynomial_model(beta_actual, xplt)
plt.plot(xplt, yplt, '-')
yplt = polynomial_model(beta, xplt)
plt.plot(xplt, yplt, '--')ax.legend(['Data', 'Predicted Values', 'Actual Relationship', 'Predicted Model'])plt.show()

图片作者。

图 2: 我们数据的 4 阶多项式拟合。

请注意,我们的模型是故意过度拟合的。我们有一个适合 5 个数据点的 4 阶多项式,回想一下,一个 n 阶多项式总是可以完美地预测 n+1 个数据点,而无需考虑任何基础模型。

让我们稍微修改我们的成本函数,以惩罚参数的大小。这个过程被称为正则化,正则化被定义为添加信息以解决不适定问题以防止过度拟合的过程。我们将执行两种类型的正则化, L1套索回归(最小绝对收缩和选择算子)和 L2岭回归。这些技术的修改成本函数在下面的等式 9 & 10 中给出。

图片作者。

方程 9:L1 正则化的代价函数。

图片作者。

方程 10:L2 正则化的代价函数。

我们可以很容易地修改代码来处理这些正则化技术。唯一的变化将发生在成本函数和 GradDescent 对象中,如下所示。

class GradDescent: def __init__(self, model, C, beta0, x, y, reg=None, lmda=0, dbeta=1E-8, eta=0.0001, ftol=1E-8):
        self.model = model
        self.C = C
        self.beta = beta0
        self.x = x
        self.y = y
        self.reg = reg
        self.lmda = lmda
        self.dbeta = dbeta
        self.eta = eta
        self.ftol = ftol def descend(self):
        # This dict of cost parameters does not change between calls
        cost_inputs = {'x': self.x,
                       'y': self.y,
                       'reg': self.reg,
                       'lmda': self.lmda,
                       'model': self.model
                       }
        # Initialize a list of costs, with the indices being the iteration
        costs = [self.C(self.beta, **cost_inputs)] run_condition = True
        while run_condition: # Get the gradient of the cost
            delC = [] for n, beta_n in enumerate(self.beta):
                # Create a temporary parameters vector, to change the nth parameter
                temp_beta = self.beta
                temp_beta[n] = beta_n + self.dbeta  # Adjusts the nth parameter by dbeta
                C_n = self.C(temp_beta, **cost_inputs)
                dC = C_n - costs[-1]
                delC.append(dC / self.dbeta) # Update the parameters
            self.beta = self.beta - self.eta * np.array(delC) # Re calc C
            costs.append(self.C(self.beta, **cost_inputs)) # Evaluate running condition
            run_condition = abs(costs[-1] - costs[-2]) > self.ftoldef cost(beta, **kwargs):
    """
    Calculates the quadratic cost, with an optional regularization
    :param beta: Model Parameters
    :param kwargs:
    :return:
    """
    x = kwargs['x']
    y = kwargs['y']
    reg = kwargs['reg']
    lmda = kwargs['lmda']
    model = kwargs['model'] # Calculate predicted y given parameters
    yhat = model(beta, x) # Calculate the cost
    C = sum((y-yhat)**2) / len(y)
    if reg is not None:
        if reg == 'L1':  # For Lasso Regression (L1), add the magnitudes
            C += lmda * sum(abs(beta))
        elif reg == 'L2':  # For Ridge Regression (L2), add the squared magnitude
            C += lmda * sum(beta**2)
    return C

让我们做一点调整,看看这如何影响我们预测模型的偏差和方差。该代码如下所示,比较图见图 3

fig, axs = plt.subplots(1, 3, figsize=(15, 5)) for i, (reg, lmda) in enumerate(zip([None, 'L1', 'L2'], [0, 1, 1])):
    # Initialize a GradDescent object, perform descent and get parameters
    gd = GradDescent(polynomial_model, cost, beta0, x, y, reg=reg, lmda=lmda)
    gd.descend() beta = gd.beta # Make model prediction with parameters
    yhat = polynomial_model(beta, x) axs[i].plot(x, y, '.')
    axs[i].plot(x, yhat, 'x')
    xplt = np.linspace(min(x), max(x), 100)
    yplt = polynomial_model(beta_actual, xplt)
    axs[i].plot(xplt, yplt, '--')
    yplt = polynomial_model(beta, xplt)
    axs[i].plot(xplt, yplt, '--') # Set title
    if reg is not None:
        axs[i].set_title(reg)
    else:
        axs[i].set_title("No Regularization") # Clean up the plots - remove x,y ticks and labels
    axs[i].axes.xaxis.set_ticklabels([])
    axs[i].axes.yaxis.set_ticklabels([])
    axs[i].axes.xaxis.set_visible(False)
    axs[i].axes.yaxis.set_visible(False) fig.legend(['Data', 'Predicted Values', 'Actual Relationship', 'Predicted Model'])plt.show()

图片作者。

图 3: 正则化方法对比。

我们可以定性地看到,调整我们的参数提高了我们的拟合度(即,使其更接近实际的基础模型。)尝试迭代参数权重,看看它对最终模型的影响。

结论

我们学习了梯度下降的基本原理,并用 Python 实现了一个简单的算法。这样做后,我们做了最小的改变,增加正则化方法到我们的算法,并了解了 L1 和 L2 正则化。我希望你喜欢。

在 GitHub 上看到这个项目
在 LinkedIn 上与我联系
阅读我的一些其他数据科学文章

创建位置生成器 GAN

原文:https://towardsdatascience.com/create-a-location-generator-gan-33680d81289f

根据几个城市的公共位置数据训练一个快速 GAN,以预测电动自行车在世界各地的实际位置

在本帖中,我们将探索在美国各城市的地图数据和公共电动自行车反馈上训练一个捷径生成对抗网络(GAN)模型。然后,我们可以通过为包括东京在内的世界各地的城市创建合成数据集,来测试模型的学习和归纳能力。如果您愿意跟随,请从 GitHub 克隆示例 repo 👇并为您自己的城市创建合成位置数据集!

信用:sylv1rob1 via ShutterStock

git clone [https://github.com/gretelai/GAN-location-generator.git](https://github.com/gretelai/GAN-location-generator.git)

之前的一篇博客中,我们训练了一个基于 LSTM 的语言模型关于来自电动自行车反馈的精确位置数据,并使用该模型为相同地区(例如加利福尼亚州圣莫尼卡)生成合成的和隐私增强的数据集。通过以不同的方式构建问题并结合地图数据,我们可以创建一个模型,生成人类可能在世界各地访问的精确位置

使用地图数据向地理数据集添加上下文。来源:Gretel.ai

入门指南

我们可以通过将电动自行车的位置数据作为像素编码到图像中来建模,然后训练为类似于 CycleGAN、Pix2pix 和 StyleGAN 的图像翻译任务。在这篇文章中,我们将使用由 pix2pix 和 CycleGAN 的作者创建的较新的对比不成对翻译 (FastCUT)模型,因为它的内存效率高,训练速度快(对高分辨率位置有用),并且通过最少的参数调整就可以很好地推广。跟随 GitHubhttps://github.com/gretelai/GAN-location-generator.git上完整的端到端示例,或者为您自己的城市创建合成位置数据

步伐

模型训练步骤

  1. 从地图上精确的电动自行车位置库创建域名
  2. 从相同的地图创建域名,但没有位置
  3. 翻译域名的培训捷径 B →域名

合成数据生成步骤

  1. 对于目标地理位置,请下载新地图(DomainC)
  2. 对捷径模型运行推理以预测指示器位置(translate DomainC->DomainA)
  3. 使用 CV 处理图像以查找踏板车位置并转换为纬度/LON

创建训练数据集

安装完依赖项后,运行python -m locations.create_training_data创建一对 512x512px 的地图图像,包括和不包括来自 e-bike 数据集的位置数据。

来源:Gretel.ai

接下来,在数据集上训练我们的模型——本质上是训练捷径模型来预测电动自行车的位置将在哪里得到地图。

快捷模型将数据记录到 Visdom ,这让我们可以监控模型训练。在下图中,我们可以看到模型损失在训练过程中减少,也可以看到图像转换任务的预览。第一个图像是真实的 DomainA 地图数据,第二个是带有预测的 scooter 位置(假)的 DomainA 图像的翻转版本,第三个是真实的 DomainB 位置。我们可以看到,即使在 25 个时代之后,该模型也在学习预测什么看起来像合理的踏板车位置——例如,街角和道路沿线。

来源:Gretel.ai

当运行推荐的 200 个时代时,模型似乎过拟合,预测的踏板车位置从图像中消失。对于这个例子,我在更早的纪元(纪元 30)中看到了最好的性能。这是一些模型预测的来自旧金山的真实世界 v .合成图像。

来源:Gretel.ai

创建测试数据集

运行以下命令创建东京市区地图位置 15x15 格网的训练数据集,或修改纬度和经度参数以创建任何地理区域的合成位置。注意,根据快捷 python 代码的工作方式,我们需要将地图网格图像复制到testAtestB目录中。

python -m location_utils.create_test_dataset --lat 35.652832 --lon 139.839478 --name Tokyo

我们现在可以使用我们的模型来处理为上面的网格创建的每个图像,以预测电动自行车在东京的位置。

查看单个图像的结果:

东京市中心综合生成的电动自行车位置。来源:Gretel.ai

将合成图像转换回坐标

现在,我们的任务是从东京的图像中获取合成的电动自行车,并将它们转换为现实世界的坐标,以建立我们的合成位置数据集。为了提取电动自行车的位置,我们使用 OpenCV 应用了一个图像蒙版,在图像中搜索任何洋红色像素组。一旦创建了蒙版,我们就可以计算蒙版中任何像素到图像文件名中编码的中心点纬度和经度的距离。

请注意,根据城市在世界上的位置,每个纬度或经度之间的物理距离可能会有很大差异,当将像素映射到位置时,我们需要使用基于椭球的模型来计算精确的偏移。幸运的是,geopy Python 库使这变得容易。

下图使用了cv2.imshow()函数来预览蒙版图像,然后我们将蒙版图像转换回经纬度坐标。

使用 OpenCV 检测合成图像中的踏板车位置。来源:Gretel.ai

把所有的放在一起

现在,我们可以处理所有图像,并将合成位置拼接成一个新的东京数据集。

在查看整个东京的数据时,尤其是针对水路生成位置时,肯定会出现一些误报。也许进一步的模型调整,或在训练数据中提供更多的水道的负面例子(domainA 或 domainB)会减少假阳性。

为东京市中心组装合成踏板车。来源:Gretel.ai

然而,结果是令人鼓舞的(给定很少的模型或数据集调整)-模型似乎能够模拟电动自行车数据集的分布和位置,该数据集是使用世界不同地区的地图进行训练的。

东京市中心的电动自行车位置预测。来源:Gretel.ai

结论

在本帖中,我们尝试应用视觉领域的上下文(例如地图数据)和表格数据,来创建世界上任何地方的真实位置数据。

如果你喜欢这个例子或者有任何问题,请在 GitHub 上留下 ⭐️,或者在我们的 Slack 上告诉我们!

信用

下面的作者为我们在这个例子中应用的快速算法提供了优秀的论文和代码。由朴、埃夫罗斯、张、朱(2020)

Gretel.ai ,我们构建 API,让合成数据对开发者和数据科学家来说变得简单和可扩展。

用提督和 DVC 创建一个可维护的数据管道

原文:https://towardsdatascience.com/create-a-maintainable-data-pipeline-with-prefect-and-dvc-1d691ea5bcea

使您的管道更易于支持和维护

动机

在工程中,可维护性是指产品维护的难易程度:

  • 满足新要求
  • 应对不断变化的环境
  • 提高产品的性能

在数据科学项目中,构建可维护的管道至关重要,因为:

  • 数据的特征可能会经常改变
  • 数据科学家需要尝试处理数据的新方法,引入新功能和模型来提高产品质量
  • 项目的最终目标可能会改变
  • 该项目需要扩大规模以满足新的需求
  • 管道在其生命周期中可能由不同的数据科学家构建和修改

那么如何构建一个可维护的管道呢?让我们从识别可维护管道的一些特征开始。

可维护的管道应该:

  • 容易对理解
  • 很容易看出对管道的改动
  • 可重用减少需要验证和维护的代码总量
  • 可观察的便于调试或确保管道正常工作
  • 快速试验有效改进现有模型

作者图片

在这篇文章中,你将学习如何使用提督和 DVC 的组合来创建一个可维护的管道。

什么是 DVC?

DVC 是数据版本控制系统。它本质上类似于 Git 的数据。

什么是提督?

perfect是一个开源库,允许你编排和观察用 Python 定义的数据管道。

https://medium.com/the-prefect-blog/orchestrate-your-data-science-project-with-prefect-2-0-4118418fd7ce

让我们来看看如何利用提督和 DVC 一起创建一个可维护的数据管道。

容易理解

当管道的组件被很好地定义和适当地命名时,评审者可以关注更高层次的构建块以及它们如何组合在一起,而忽略每个块的细节。

为了使管道更容易理解,您可以:

  • 将代码分成更小的组件,每个组件只做一件事
  • 为每个组件指定一个描述性名称

例如,在下面的代码中,可能很难理解compare_two_keywords函数的每一行代码。

让我们将流程分成更小的函数,每个函数只做一件事。

作者图片

现在,由于内部函数的描述性名称,评审者可以理解compare_two_keywords函数是做什么的。

可观察量

能够观察您的管道使得理解逻辑和调试代码更加容易。在本节中,您将学习如何通过以下方式使您的代码可被观察到:

  • 记录正在执行的组件
  • 可视化管道中的组件
  • 可视化管道的输入和输出

记录正在执行的组件

目前,我们不知道执行文件时运行的是哪个组件,这使得调试变得很困难。

为了了解哪个组件正在运行,我们可以将@flow添加到外部函数,将@task添加到内部函数。

一个流程是所有完美工作流的基础。一个任务代表一个流程中执行的不同的工作。

简单地说,任务是做一件事的功能,而流是包含几个任务的功能。

现在,当执行 Python 脚本时,您应该看到正在执行哪个任务及其状态。

可视化流程中的组件

Prefect UI 中的雷达图允许您可视化流程中任务及其状态之间的依赖关系。

例如,在下面的雷达图中,红色节点是失败的任务,而绿色节点是成功的任务。如果两个任务由一条线连接起来,它们就相互依赖(例如,一个任务使用另一个任务的输出)。

作者图片

通过查看雷达图,审阅者能够更快地理解任务之间的关系。

用 DVC 可视化输入和输出

阶段代表单独的数据过程。有时,您可能希望可视化管道不同阶段的输入和输出。

DVC 允许你通过文件dvc.yaml指定管道中的阶段及其依赖关系

比如在这个项目中,我用dvc.yaml : process_datatrain指定了两个阶段。在每个阶段,

  • cmd指定执行阶段的命令
  • deps指定阶段的依赖关系
  • outs指定阶段的输出
  • plots指定该阶段的情节
  • params指定代码中使用的影响结果的值

现在,评审者可以通过运行dvc dag --outs来可视化每个阶段的输出文件:

 +----------+                    
                          | data/raw |                    
                          +----------+                    
                                *                         
                                *                         
                                *                         
                      +-------------------+               
                      | data/intermediate |               
                      +-------------------+               
                  ****          *          *****          
              ****              *              ****      
           ***                  *                  ***   
+------------+         +-------------------+         +-------+
| data/final |         | model/cluster.pkl |         | image |
+------------+         +-------------------+         +-------+

可重复使用的

很多时候,您可能想要重用管道的一些组件,比如配置和凭证。这减少了需要验证和维护的代码总量。

Prefect 允许你用做同样的事情。块提供了一种安全的方式来存储配置和功能,以便与外部系统(如 AWS、GitHub、Slack 或 Microsoft Azure)进行交互。

作者图片

让我们尝试通过提督 UI 使用 secret 块存储一个秘密。

作者图片

要在机密块中存储值,请单击添加,然后插入名称和值。

作者图片

现在,您和任何有权访问您的工作空间的人都可以在不同的 Python 脚本中访问这个秘密。

很容易看出变化

当您对代码和参数进行更改时,了解这些更改是有用的。

DVC 允许您使用命令dvc status查看管道中的变化:

$ dvc status
process_data:                                                                                                       
        changed deps:
                modified:           src/process_data.py

您也可以使用命令dvc params diff跟踪参数的变化:

$ dvc params diff
Path                           Param                             HEAD    workspace
config/process/process_1.yaml  remove_outliers_threshold.Income  60000   61000

快速实验

建立管道也很重要,这样数据从业者就可以快速地试验不同的代码、参数和模型。

有了 Prefect,您可以通过下面解释的概念快速进行实验。

时间表

您可以安排流程定期运行,例如每天或一周中的某一天。

作者图片

重试次数

假设您正试图连接到一个数据库。有时行得通,有时行不通。通常,如果无法连接到数据库,整个管道都会失败,您需要重新运行整个流程。

作者图片

在这种情况下,重新运行失败特定次数的任务比重新运行整个管道更有效。

作者图片

提督允许你自动重试失败。要启用重试,请向您的任务添加retriesretry_delay_seconds参数:

在上面的代码中,read_data将最多重试 3 次,并在每次重试之间等待 5 秒钟。

通知

你的血流可能需要几个小时。您可以在流程完成或失败时向 Slack 通道或电子邮件发送通知,而不是经常检查代码的状态。

作者图片

https://medium.com/the-prefect-blog/sending-slack-notifications-in-python-with-prefect-840a895f81c

贮藏

想象你运行一个流程两次。流程中的最后一个任务在您第一次运行时失败,因此您修复了您的任务,流程成功运行。

但是任务fill_missing_description运行时间比较长,需要等待第二次运行完成。

作者图片

不需要重新运行fill_missing_description,您可以在第一次运行时使用缓存来保存任务的结果,然后在第二次运行时重用它的结果。

作者图片

要在提督中使用缓存,请指定cache_key_fn。缓存关键字指示一次运行是否与另一次运行相同。如果 Prefect 找到一个带有匹配缓存键的运行,它将使用缓存的运行。

要根据输入缓存任务,使用task_input_hash。如果任务输入没有改变,提督将使用缓存的结果。

仅当管道更改时才触发运行

有时,当有变化时,您可能希望只运行管道。这可以防止你在不必要的跑步上浪费时间和资源。

要在有变化时运行管道,使用命令dvc repro

例如,当我的代码没有变化时,这是我运行命令时得到的输出:

$ dvc repro
'data/raw.dvc' didn't change, skipping                                                                              
Stage 'process_data' didn't change, skipping                                                                        
Stage 'train' didn't change, skipping                                                                               
Data and pipelines are up to date.

如果train阶段的代码发生变化,则只执行train阶段的代码。

结论

恭喜你!您刚刚学习了如何使用 DVC 和提督创建可维护的管道。通过几个步骤来建立您的管道,您的团队在进行更改时会更有信心。

查看这个资源库,了解一个全面的项目,该项目包括“提督”和“DVC ”:

https://github.com/khuyentran1401/prefect-dvc/

我喜欢写一些基本的数据科学概念,并尝试不同的数据科学工具。你可以在 LinkedIn 和 Twitter 上与我联系。

如果你想查看我写的所有文章的代码,请点击这里。在 Medium 上关注我,了解我的最新数据科学文章,例如:

https://pub.towardsai.net/github-actions-in-mlops-automatically-check-and-deploy-your-ml-model-9a281d7f3c84

参考

维基媒体基金会。(2022 年 10 月 9 日)。可维护性。维基百科。检索于 2022 年 10 月 11 日,发自 https://en.wikipedia.org/wiki/Maintainability

可维护的 ETL:让您的管道更容易支持和扩展的技巧。多线程。(未注明)。2022 年 10 月 11 日检索,来自https://multithreaded . stitchfix . com/blog/2019/05/21/maintainable-etls/

使用 Streamlit 创建一个照片编辑应用程序——出奇的简单和有趣

原文:https://towardsdatascience.com/create-a-photo-converter-app-using-streamlit-surprisingly-easy-and-fun-db291b5010c6

让我们享受 Streamlit 和计算机视觉的乐趣:初学者实用指南

图片来源: Pixabay

介绍

你是否曾经使用过这样的照片编辑应用程序,它允许你在线上传照片,并立即将其转换为黑白图像、酷酷的铅笔素描或具有漂亮模糊效果的图片?作为一名好奇的数据科学家,我总是有兴趣找出这些应用程序是如何在幕后创建的。但是,很长一段时间以来,我自己并没有真正投入过什么时间去学习和尝试。

最近,我在网上看到了几篇文章,讲述了如何使用 OpenCV(Python 中的一个实时优化的计算机视觉库)将一张照片变成草图,或灰度图像等。我真的很惊讶,使用 OpenCV 来开始计算机视觉世界中的一些简单任务是多么简单和直接。

与此同时,我也一直在探索和学习 Streamlit。这是一个免费的开源全 python 框架,使数据科学家能够快速构建数据和机器学习 web 应用程序,无需前端 web 开发经验。所以突然有了灵感——这可能是一个完美的动手 Streamlit 项目,让我用 OpenCV 构建自己的照片编辑应用程序!

在不到两个小时的时间里,只用了大约 60 行代码,我就能够使用 Streamlit 构建一个照片编辑应用程序,它对我来说看起来相当不错!我想与你分享我的项目工作,这样你也可以有一些学习建立自己的照片编辑应用程序的乐趣!我们将要构建的应用程序看起来是这样的(或者观看这个简短的 YouTube 视频演示):

图片由作者提供(婴儿照片由 Pixabay 提供)

先决条件

如果您还没有这样做,您需要为这个项目在您的计算机上安装 Streamlit 和 OpenCV。

#1:细流安装:

可以参考下面的文章,按照说明安装 Streamlit,学习基础知识。

#安装 OpenCV:

您可以使用下面的命令来安装 OpenCV,或者参考它的文档页面来获得更多的细节:https://pypi.org/project/opencv-python/

pip install opencv-python

作者图片

启动流线

让我们打开 VS 代码编辑器(或者您选择的任何文本编辑器),创建一个新的空 python 文件,并在您的项目文件夹中将其保存为 photo_converter_app.py。然后我们可以从 Anaconda 终端窗口启动 Streamlit。一个空白的 Streamlit 应用程序应该会出现在您的本地 web 浏览器中。

streamlit run photo_converter_app.py

作者图片

导入库

让我们首先导入所有必需的库,开始我们的应用程序构建之旅:

向应用程序添加标题、品牌标志和侧栏

在应用程序的主界面中,我们希望使用 st.markdown()添加一个标题,以及一个可选的品牌徽标。我们选择 st.markdown()而不是 st.title()的原因是因为我们可以使用 CSS 对其进行样式化,使其更具吸引力。

让我们使用下面的代码在侧边栏中添加一个标题和扩展器,以提供关于该应用程序的更多信息:

作者图片

添加文件上传程序,允许用户上传照片

在应用程序的主界面中,我们希望添加一个文件上传程序,以便用户可以通过拖放或浏览文件来上传他们的照片。我们可以使用 st.file_uploader()小部件来做到这一点,并指定应用程序中接受的图像类型(例如,JPG、PNG、JPEG 等。)

作者图片

添加空格符以显示之前和之后的图像

用户上传照片后,我们希望在应用程序中并排显示原始图像(之前)和转换后的图像(之后)。因此,让我们在文件上传程序下创建两个宽度相同的列,一个用于“之前”,另一个用于“之后”。如果 uploaded_file 不是 None: '语句,请确保将所有内容都放在“语句中。使用这个语句可以确保当图像还没有上传时,应用程序不会因为找不到要转换的图像而抛出任何错误消息。

作者图片

使用 OpenCV 转换照片

现在有趣的部分来了!在第二列 col 2(“after”图像的占位符)中,我们希望根据用户的输入显示转换后的图像。例如,如果用户想要将图像转换为黑白,我们将使用 OpenCV 将原始图像转换为黑白。如果用户想把它转换成铅笔素描,我们就用 OpenCV 把它转换成铅笔素描。我们如何实现这一目标?

#1:创建一个接受用户输入的过滤器

我们首先需要创建一个过滤器或单选框,允许用户指定他们想要做什么。为了保持应用程序主界面的整洁,我们可以将这个过滤器添加到侧边栏中。在下面的代码中,第 12 行在侧栏中添加了过滤器。滤镜显示五个选项:原始、灰色图像、黑白、铅笔素描和模糊效果。我们使用 st.sidebar.radio()一次只允许单一选择。

请注意,如果 uploaded_file 不是 None: '代码块,我们将过滤器变量放在了“内。通过这样做,过滤器将只在用户上传图像后出现。您也可以选择将过滤器变量放在代码块之外(即第 2 行之上)。这样,过滤器将始终显示在侧边栏中。真的只是个人对 app 设计的喜好。

#2:使用条件语句接受用户输入

您将看到单选按钮过滤器立即出现在侧栏中;然而,当你点击一个选项时,它不会触发应用程序中的任何内容来显示转换后的图像。这是因为我们没有告诉 Streamlit 将过滤器的值传递到哪里,以及用户的输入触发了什么操作。为此,我们需要在 col2 下添加一些条件语句。

在下面的代码中,从第 5 行到第 29 行,我们使用 if-else 条件语句将图像转换成不同的用户选择的格式。对于不同的用户选择,我们使用不同的 OpenCV 函数(例如 cv2.cvtColor、cv2.cv2.GaussianBlur、cv2.divide 等。)将图像转换为所需的格式,并使用 st.image()显示转换后的图像。

# 3:OpenCV 如何转换图像

让我们更详细地检查上面的代码,并理解 OpenCV 如何将图像转换成不同的格式:

第 5–8 行:

使用 OpenCV 将图像转换成灰度图像非常简单。我们可以简单地使用 cvtColor()函数,并将颜色空间转换代码指定为 cv2。COLOR _ bgr 2 灰色。

作者图片

第 9–14 行:

将图像转换为黑白图像只需要在首先将其转换为灰色图像之后再执行一个额外步骤。我们需要定义一个阈值,然后对于灰度图像的每个像素,如果其值低于阈值,则我们将值 0(黑色)分配给该像素,如果其值高于阈值,则分配值 255(白色)。

请注意,在第 12 行,我们创建了一个 slider 小部件,允许用户选择一个阈值,第 13 行获取阈值并将灰色图像转换为黑白图像。

作者图片

第 15–22 行:

要将图像转换为铅笔素描,我们首先将其转换为灰色图像,然后基本上将其反转——使黑色调变成白色,反之亦然。

接下来,我们应用模糊过滤器使用高斯模糊函数(cv2。GaussianBlur)。在第 19 行,我们创建了一个 slider 小部件,允许用户选择高斯核的大小来调整模糊的强度。我们选择(125,125)作为内核大小参数([height width])的默认值。高度和宽度应该是奇数,可以有不同的值。

最后,我们使用 cv2.divide 函数将灰度图像中的像素与 255-blur_image 中的像素相除。这将返回一个看起来像铅笔画的图像。

作者图片

第 23–28 行:

要将图像转换为模糊图像,我们首先将图像转换为灰度图像,然后使用高斯模糊函数(cv2)添加模糊滤镜。GaussianBlur)。请注意,在第 25 行中,我们还添加了一个条件化的 Streamlit 小部件 st.siderbar.slider(),它仅在用户选择“模糊效果”选项时出现,并允许用户相应地调整模糊的强度。

作者图片

在侧边栏中添加用户反馈部分

最后,我们将在侧边栏中添加一个用户反馈部分,以收集评论评级和评论。我们使用 st.text_input()小部件允许用户提交评论,使用 st.slider()小部件允许用户选择 1-5 范围内的评级。然后,我们可以使用 st.form_submit_button()和 st.form()一起批量输入小部件,并单击一个按钮提交小部件值,这只会触发整个应用程序的一次重新运行!

以黑暗模式显示的应用程序(图片由作者提供)

这就是你第一个使用 Streamlit 的照片编辑应用程序!你还可以通过 Streamlit Cloud 与他人分享你的应用,这是 Streamlit 最近推出的一项非常酷的功能。通过亲自动手做这样的项目,我能够提高我的 Streamlit 技能,并迈出进入计算机视觉世界的第一步。我真的很喜欢这个项目,希望你也玩得开心!快乐学习!

这里有一小段 YouTube 视频来演示这个应用:

作者的 YouTube 视频

参考资料和图像来源:

  1. OpenCV 文档:OpenCV 中的图像处理
  2. Rafael Messias Grecco 制作一个简单的照片编辑器
  3. 将您的照片转换成代码为的艺术草图,由贝希克·居文设计
  4. 图片来源:app 中使用的宝贝照片可以从 Pixabay 下载。这是免费的商业用途(不需要署名)。

你可以通过这个推荐链接注册 Medium 会员(每月 5 美元)来获得我的作品和 Medium 的其他内容。通过这个链接注册,我将收到你的会员费的一部分,不需要你额外付费。谢谢大家!

使用 Jupyter 笔记本快速创建一个简单的应用程序

原文:https://towardsdatascience.com/create-a-simple-app-quickly-using-jupyter-notebook-312bdbb9d224

数据科学家和其他希望将 Python 函数转化为交互式应用程序的人的指南。

从代码到应用——作者图片

我将演示一个健壮但易于使用的解决方案,除了 Python、Jupyter notebook 和 Ipywidgets 等众所周知的工具之外,它只需要很少的知识。

我们将构建一个交互式应用程序来解决一个二次方程。这是一个非常简单的例子,但足以演示该方法。我们不会直接跳到最终的解决方案,而是通过几个简单的步骤逐步实现。我将从最简单的基于笔记本的实现开始,以完全交互式的解决方案结束。

问题表述:

给定三个数字 a、b 和 c,找出满足以下条件的 x:

a x +b x +c = 0

众所周知,该方程的解由下式给出:

(-b + D)/2a 和(-b-D)/2a,其中 D = sqrt(b -4ac)

1.直接法

我们第一次尝试为这个问题创建一个笔记本,看起来像这样:

现在我们已经实现了功能。是时候重构我们的笔记本来使用功能了,而不是到处扔代码。

2.使用函数

通过创建这些功能,第一个直接的好处是可重用性。我们不需要每次想要使用该功能时都复制并粘贴代码。

但最重要的是它给出的结构:函数清楚地显示了它们的输入和输出。这也有助于在我们的大脑中构建问题,并分离解决方案的主要构件。

3.小工具

现在我们已经实现了主要函数,我们可以进行计算了。
然而,对于必须在代码单元格中输入值的用户来说,这不是很方便。通过为系数引入输入部件,我们可以做得更好。注意,最后几个没有变化的单元格被省略了。

现在它开始看起来像一个应用程序,用户不再需要修改代码,但仍然需要在每个单元格后按 Ctrl-Enter。此外,每次用户更改值时,都需要重新执行单元格#5。(注意,我们必须使用“.”来引用小部件的值。值”。)

4.瞧

如果你使用 Jupyter 笔记本,你很可能听说过“瞧”。它的作用是获取一个普通的 Jupyter 笔记本,保留输出单元格,隐藏代码单元格。这使得外观更加整洁。
瞧,安装起来超级容易。如果您的环境中尚未安装,您可以通过以下方式进行安装:

pip install voila

如果你现在重启 jupyter-notebook,你会在按钮栏看到一个“瞧”按钮。

然而,当你对你的笔记本进行可视化时,你将不得不得出结论,它不能工作,至少不能按照预期的方式工作:改变输入框中的值没有任何效果。
为什么?因为在用户有机会输入/修改值之前,voila 执行了所有的代码单元。所以不管初始值是什么,都将被使用。

为了让 voila 工作,您必须设置某种交互性:代码执行需要通过与小部件的交互来触发。您不再有手动执行代码单元的选项。

5.让我们增加互动性

有几种方法可以让你在部件状态改变时触发一些动作。您可以使用interact函数或observe方法。但是请注意,这些仅在单个小部件级别上运行。您可以设置一个在小部件改变时执行的动作,但是您无法控制它将触发的事件链:您可以很容易地触发D的重新计算,但是您真正需要的是x1x2的重新计算。您可以手动跟踪整个依赖图,并在c改变时记得更新x1x2。但是这不方便而且容易出错。假设您有一个稍微大一点的项目,有 20 或 30 个内部变量,它们以某种非平凡的方式相互依赖。手动跟踪要更新的变量将很快导致错误。

相反,我提出的解决方案是使用自动计算包。这个包允许你在内部变量和用户公开变量之间建立依赖图。一旦建立了这个图(DAG ),您就不需要手动重新计算变量了。就像 Excel 的自动更新功能。

还有其他针对同一问题的包,autocalc 的优势在于它的简单性:你只需要学习一个名为Var的类来设置 DAG。所以让我们深入研究一下:

首先,您需要安装软件包

pip install autocalc

如果你现在打开你的笔记本,它会像预期的那样工作

注意事项:

  • 查看自动计算的文档,了解高级特性,如“惰性”计算。
  • 一些输出值(如数据帧)无法通过微件显示。在这种情况下,仍然可以自动更新输出。诀窍是设置一个虚拟变量,它取决于输出。分配给该变量的函数需要在值改变时执行必要的操作。这种技术在autocalc.tools.PreviewAcc helper 类中使用,这是一种现成的解决方案。

我希望这个小描述对你有用。如果你喜欢我的帖子,请鼓掌,关注,分享,评论。这鼓励我生产更多。

放弃

我是自动计算包的作者。

使用 Streamlit 创建简单的语言学习应用程序

原文:https://towardsdatascience.com/create-a-simple-language-learning-app-using-streamlit-21d6ce1b68c

如何使用 Streamlit 和 Googletrans 构建自己的语言翻译和学习应用程序

图片由 Pixabay

Streamlit 是一个免费、开源的全 python 框架,使数据科学家能够快速构建交互式数据和机器学习应用,无需前端 web 开发经验。如果你懂 python,那么你就可以在几小时内,而不是几周内,使用 Streamlit 创建和共享你的 web 应用。

我将 Streamlit 用于各种有趣的用例及应用,既出于娱乐,也出于学习和分享的目的。你可以在我的 Medium 博客中找到各类 app 实例的完整列表。在这篇文章中,我将向您展示如何使用 Streamlit 和 Googletrans (一个实现了 Google Translate API 的免费且无限制的 python 库)创建一个简单的语言学习应用程序。我在构建这个小应用程序的过程中获得了很多乐趣,希望你也会喜欢它!

请注意,如果你是 Streamlit 的新手,我强烈建议你在阅读这篇文章的细节之前,先阅读下面这篇关于 Streamlit 的介绍性文章。

如果您已经对 Streamlit 有了一些基本的知识和经验,就可以开始了!因此,不再拖延,让我们开始构建应用程序。这是一个简短的 YouTube 视频,演示了我们将要建立的应用程序。

作者的 YouTube 视频

首先,让我们导入所有必要的库,并添加一个侧栏,其中包含该应用程序的一些基本信息。我们还将创建一个临时文件夹,用于保存和存储应用程序中生成的所有音频文件。我们使用googletrans来做翻译,你可以从 PyPI 安装这个包。如果没有的话,您可能还需要安装jiebaxpinyin,它们将用于切分中文文本并将汉字翻译成拼音(mainland China 标准中文的官方罗马化系统)。

pip install googletrans
pip install jieba
pip install xpinyin

接下来,我们来搭建 app 的主界面。我们将首先添加一个水平菜单栏,允许用户选择他们想要学习的语言。在这个应用程序示例中,我只包含了五种语言;但是,如果您愿意,您可以包含更多内容。你可以在这里找到谷歌翻译 API 支持的语言的完整列表。

我们还将添加一个 streamlit 小部件,它接受用户输入的任何文本,并允许用户键入英语单词、句子或段落。我们将提供一个文本示例作为文本框中的默认条目。请注意,在第 14–17 行中,我们使用了st.markdown()和 CSS 样式来使说明文本在视觉上更具吸引力。

作者图片

让我们定义一个 python 函数,它将输入文本从英语翻译成用户选择的任何目标语言。该函数有三个参数:输入语言、输出语言和用户输入的文本。

我们还将为文本输入生成一个音频文件,并在应用程序的主界面上显示音频。音频文件将保存在我们在上一步中创建的 temp_folder 中。

现在让我们编写代码,如果用户在选项菜单中选择“中文”,就把英文翻译成中文。我们还将在应用程序中显示汉字的拼音(mainland China 标准汉语的官方罗马化系统)以及音频播放器。

作者图片

你可以对其他语言做同样的事情。下面是一个把英语翻译成法语的例子。该代码比英汉代码稍微简单一些,因为我们不需要显示拼音。

作者图片

这就对了。我们使用 Streamlit 和Googletrans创建了一个简单的语言学习应用程序。感谢阅读,我希望你喜欢这篇文章。如果您有兴趣了解更多有关 Streamlit 的信息,并享受创建应用程序的乐趣,下面是几个更多的 Streamlit 应用程序示例!

https://medium.com/codex/create-a-multi-page-app-with-the-new-streamlit-option-menu-component-3e3edaf7e7ad https://medium.com/codex/create-a-simple-project-planning-app-using-streamlit-and-gantt-chart-6c6adf8f46dd https://medium.com/codex/create-a-data-profiling-app-using-pandas-profiling-and-streamlit-59300bc50af7

你可以通过这个推荐链接注册 Medium 会员(每月 5 美元)来获得我的作品和 Medium 的其他内容。通过这个链接注册,我将收到你的会员费的一部分,不需要你额外付费。谢谢大家!

使用 Mercury 从您的 Jupyter 笔记本中创建一个 Web 应用程序

原文:https://towardsdatascience.com/create-a-web-app-from-your-jupyter-notebook-with-mercury-21239b7abb37

您可以将笔记本发布为交互式 web 应用程序,可以使用也可以不使用代码

我们都使用 Jupyter 笔记本来分析和创建数据的可视化,不是吗?这是一个很好的工具。

当然,您也可以通过简单地向您的同事发送一份副本来分享它们。但是对于那些不关心你的代码但对你的结果感兴趣的人来说,交互式应用可能是更好的工具。

这就是来自 mljar 的水星所承诺的。只需在笔记本的开头添加一个新的单元格,你就可以让 Mercury 将代码转换成可以部署到 AWS、Heroku 或任何地方的 web 应用程序。

它还允许你在你的笔记本中加入互动元素,比如滑块,下拉菜单。

下面是我在几分钟内创建的一个简单的 web 应用程序的截图。正如你所见,它包括一个互动滑块。这个应用程序非常简单:它加载一组天气数据,并绘制一个特定年份的月最高气温图表——滑块允许你选择年份。

作者截图

更复杂的应用可以在 Mercury 网站上找到。以下是一些例子:

图片来自 Mercury Github repo —经 mljar 许可使用,AGPLv3 许可证

但是我将介绍如何创建我自己创建的简单 web 应用程序。它主要是一个非常简单的 Jupyter 笔记本,由六个电池组成。

恋恋笔记本

第一个是包含一些 YAML 代码的原始单元格,这些代码告诉 Mercury 要做什么以及要包含哪些交互。前三行定义了title、a description并将show-code设置为False。设置为 false 意味着单元格中的代码在最终的 web 应用程序中不可见。

---
title: Exploring London Weather Data
description: How to use mercury to create a web app
show-code: False
params:
   year:
      input: slider
      label: Select year
      value: 1952
      min: 1952
      max: 2018
---

最后几行定义了参数。year是一个将出现在笔记本中的变量,后面的几行定义了一个滑块,用于设置year的值。我认为它们是不言自明的。请注意,单元格以线---.开始和结束

第二个单元格定义了变量year并赋予它一个值。第一个单元格中引用的任何变量都必须像这样在一个单元格中一起定义。

year = 1962

下一个单元格是标记标题。

**# Exploring London Weather Data**

然后我们导入几个库。

import numpy as np
import pandas as pd

和一些数据。

weather=pd.read_csv('heathrowDataFiltered.csv')

然后我们绘制一个图表,根据year的值过滤数据。我们还打印了介绍图表的消息。

weather[weather['Year']==year].plot(y='Tmax', x='Month',    
   legend=False)print(f"Monthly maximum temperatures for {year}")

作者图片

运行笔记本将得到 1962 年的月最高温度图,这是我们在开始时设置的值。

但是我们将创建的 web 应用程序将允许我们选择我们感兴趣的年份。

那么,我们该怎么做呢?

首先,您需要安装 mercury:

pip install mljar-mercury

然后,您需要“添加”您的笔记本:在命令行中键入:

mercury add <path_to_notebook>

最后,您运行它:

mercury runserver --runworker

这将启动一个服务器,您的应用程序将在 IP 地址 127.0.0.1:8000 上运行。在网络浏览器中打开它,您会看到类似这样的内容:

作者图片

如果您添加了多个笔记本,它们都会显示在此处。单击要运行的应用程序的打开按钮,您将看到基于笔记本的应用程序。

作者截图

当应用程序运行时,它会显示图表;然后,您可以调整滑块并再次运行它,以查看所选年份的图表。

这是一个非常快速的水星运行,只向你展示了它的一些能力。在 Mercury 网站和 Github repo 上有更多信息。例如,你可以包括一大堆输入控件,而不仅仅是滑块,Github README 文档详细介绍了你可以使用的所有 YAML 命令,以及如何将你的应用程序部署到云上。这远远超出了我在这里可以涵盖的范围,所以你真的需要去看看。

你可以在这里下载我的文件:资料笔记本

一如既往地感谢阅读。如果你对围绕数据科学和可视化的编程思想感兴趣,我的网页上有更多的文章,或者你可以获得我偶尔的免费时事通讯,Substack 上的 Technofile

使用 PyQt5 创建交互式气泡图

原文:https://towardsdatascience.com/create-an-interactive-bubble-plot-with-pyqt5-48a6e0c1d0f7

使用 GUI 使 Matplotlib 图更吸引人

用 PyQt5 和 Matplotlib 创建的最终输出

pyqt 5 简介

Qt GUI 框架用于创建跨平台的用户界面。该框架是用 C++编写的,但是 PyQt5 库允许它被导入并直接在 Python 中使用。它的易用性使它成为用 Python 创建 GUI 的最流行的应用程序之一。

PyQt5 在 Python 的数据可视化中有许多用途,其中一个是在 matplotlib 中制作的交互式绘图。交互式绘图允许以有效的方式交流更复杂的数据。在这篇文章中,我将演示如何创建一个交互式气泡图,以便用户可以深入数据。

系统需求

我使用了 JupyterNotebook,但是也可以使用任何 IDE。如果版本是允许交互式绘图的 Matplotlib notebook,则 matplotlib 只能与 PyQt5 集成。

在 Jupyter Notebook 中,需要运行行%matplotlib notebook 来更改预设,因为%matplotlib inline 不允许交互式绘图。如果正在使用 IDE,matplotlib notebook 是自动设置。

第一步:制作泡泡图

我将使用的数据集是中情局每年出版的《2020 年世界概况》。该数据集包含世界上每个国家的人民、经济和政府的一般信息。

可以从这里下载:

https://www . CIA . gov/the-world-factbook/about/archives/download/factbook-2020 . zip

该数据集中使用的变量有:

  • x:人均国内生产总值
  • y:预期寿命
  • 色调:出生率
  • 规模:人口

导入库

from matplotlib import pyplot as plt
import pandas as pd
import seaborn as sns

数据清理

在图表中使用每个变量之前,需要对其进行一些数据清理和数据扩充。

x 变量:人均 GDP 目前是一个包含逗号和美元符号的字符串,需要删除这两个符号才能将字符串转换为整数值。

df = pd.read_csv(“factbook.csv”)df[“GDP per Capita”] = df[“GDP per capita”].str.replace(‘,’,’’).str.replace(‘$’,’’).astype(float).astype(int)

y 变量:出生率目前是一个连续变量,但由于它用于色调,因此需要通过创建箱来将其变为离散变量。

bi = []
for i in range(0,60,10):
bi.append(i)
df[‘Birth Rate’] =(pd.cut(df[‘ Birth rate’], bins = bi)

Size 变量:Population 当前是一个由逗号组成的字符串,为了转换成整数,需要删除逗号。

df[‘Population (M)’]=(df[‘Population’].str.replace(‘,’,’’)).astype(int)

Seaborn 是一个基于 matplotlib 构建的数据可视化库,将用于制作这个气泡图。也可以使用传统的 matplotlib 库。

bubble = sns.scatterplot(data=df, x=”GDP per Capita”, y=”Life expectancy at birth”, size=”Population (M)”, hue=”Birth Rate”, legend= True, sizes=(10, 300))

添加尺寸和颜色的图例,并显示绘图。

bubble.legend()
plt.show()

锡伯恩地块

这个初始图表清楚地显示了四个变量之间的关系。试图添加额外的变量会使可视化变得混乱,但是,在这个数据集中仍然有五个其他的变量。通过使可视化具有交互性,用户可以通过查看不同变量之间如何相互作用来更深入地研究数据。

第二步:设置 PyQt5

使用下面的 import 语句下载 PyQt5 的所有库和依赖项。

from PyQt5.QtWidgets import QDialog, QApplication, QPushButton, QVBoxLayout, QLabel, QComboBox, QSlider
from PyQt5.QtCore import Qt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import math
import sys
import re

首先创建一个类和构造函数。然后设置弹出窗口的几何图形(指定几何图形是什么)。我选择 do (400,400,900,900 ),因为我认为这对于用户来说足够大,可以从图中捕捉细节。setGeometry 的参数是 x、y、宽度和高度。

class Window(QDialog):
# constructor
def __init__(self, parent=None):
    self.setGeometry(400, 400, 900, 900)

第三步:在构造函数中添加小部件

Widget 1: FigureCanvas

图形小部件用于在可视化中显示图形。

self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)

Wiget 2: QComboBox

在每个 ComboBox 的构造函数中添加代码。下面的代码是我命名为 xComboBox 的第一个代码,用于捕获 x 轴变量的用户输入。首先,初始化并命名 ComboBox。

self.xComboBox = QComboBox(self)

向 ComboBox 中添加项。addItems()用于包含选项的列表。在这种情况下,所有列名都作为选项添加到组合框中。

self.xComboBox.addItems([“Area”,”Death rate”, “ Birth rate”,”GDP per capita”,”Population”,”Electricity consumption”, “Highways”, “Total fertility rate”, “Life expectancy at birth”])

Wiget 3: QLabel

需要创建一个标签,以便让用户知道其他小部件的用途。在这种情况下,它将允许用户知道组合框中的值将用于什么。

self.xLabel = QLabel(“&X:”)

创建的标签需要链接到 ComboBox,以使其成为一个组合对象。

self.xLabel.setBuddy(self.xComboBox)

Wiget 4: QSlider

滑块用于允许用户调整可视化中的值。PyQt5 中的小部件是 QSlider,它有一个必需的方向参数,可以是 Qt。在这种情况下,水平方向在视觉上是最吸引人的。在这个可视化中,滑块将改变大小变量,以便用户可以增加或减少气泡,以确定可视性的最佳大小。(再看这个)

self.mySlider = QSlider(Qt.Horizontal, self)

滑块的几何形状需要调整,以最适合 GUI 窗口。setGeometry 的参数同上,x,y,width 和 height。

self.mySlider.setGeometry(30, 40, 200, 30)

需要将函数链接到滑块,以便在可视化中利用其值。首先,。需要使用 valueChanged[int]来获得基于滑块位置的当前值。用要使用的函数名连接()。下面的按钮小部件中也会用到所使用的函数,这将在一节中讨论。

self.mySlider.valueChanged[int].connect(self.changeValue)

小工具 5:按钮

使用 QPushButton 创建按钮小部件。这个函数中的参数是以字符串形式传入的按钮名称。按钮名为“绘制当前属性”,任何时候用户更改滑块值或组合框值时,都需要按下该按钮来更新图形。还需要有一个功能连接到按钮,以编程的行动时,按下执行。我创建了一个名为 changeValue 的函数,用于按钮和滑块。

button = QPushButton(“Plot Current Attributes”, self)
button.pressed.connect(self.changeValue)

changeValue:滑块和按钮功能

按钮和滑块小部件需要连接到一个函数,以便在可视化中使用它们的值。我创建的 changeValue 函数可用于按钮和滑块。这可以通过*args 参数实现,它允许传递任意数量的参数。对于按钮,调用函数时不会传递任何参数,但对于滑块,将传递位置值。这个函数在构造函数之外。

def changeValue(self, *args):

检索用于散点图的 comboBox 小工具的所有当前值。

# finding the content of current item in combo box
x = self.xComboBox.currentText()
y = self.yComboBox.currentText()
s = self.sComboBox.currentText()
c = self.cComboBox.currentText()

清除当前剧情并创建新的支线剧情。

#clear current figure
self.figure.clear()#create a subplot
ax = self.figure.add_subplot(111)

调整大小和颜色变量。尺寸变量需要标准化,这样气泡的大小才合适。

#normalize the size data
if len(args) == 0:
   df[“s_new”] = df[s]/df[s].abs().max()
   df[“s_new”] = df[“s_new”] * 4
else:
   df[“s_new”] = df[s] / df[s].abs().max()
   df[“s_new”] = df[“s_new”] * args * 4

颜色变量需要离散化。

df[‘new_c’] = (pd.cut(df[c], bins=5))

一旦用户从组合框中选择新的值,用滑块设置新的大小,并调整新的数据,就可以创建散点图。

#create scatter plot with new data
b = ax.scatter(x=df[x], y=df[y], s = df[“s_new”], c = df[“new_c”].cat.codes)#create labels and title
t = y + “ vs “ + x
ax.set(xlabel=x, ylabel =y, title=t )

为颜色和大小图例创建自定义标签。Matplotlib 自动添加标签,但是对于颜色变量,需要显示范围。自动标签将只标记从 1 到 n 的颜色,n 是颜色的数量。对于大小,我们对数据进行了标准化,以便自动标注会显示这些数据,并且我们希望图例中有真实的数据。这是一个可选的步骤,如果数据没有改变,没有必要这样做,因为自动标签将是正确的。

#create labels and title
t = y + “ vs “ + x
ax.set(xlabel=x, ylabel =y, title=t )#extract handles and labels for legend
handles, labels = b.legend_elements(prop=”sizes”, alpha=0.6)#create custom labels for size legend
num_labels = len(handles)
labels_new = list(np.arange((min(df[s])), (max(df[s])), ((max(df[s]) — min(df[s]))/(num_labels-1))))
labels_new = list(np.around(np.array(labels_new), 1))# create custom labels that show ranges for color legend
df[‘new_c’] = (pd.cut(df[c], bins=5))
num_labels_c = len(b.legend_elements()[0])
col_bins = pd.cut(df[c], bins=num_labels_c,precision=1)

添加带有自定义标签的图例并格式化图表。需要调整图表的大小,以使图例适合图表的外部。这是通过将高度和宽度减少 10%并将 y0 位置向上移动一点来实现的,这样颜色图例就可以在图形的底部,尺寸图例在右侧。

# get and adjust the position of the graph to fit the legends
box = ax.get_position()
ax.set_position([box.x0, box.y0 + box.height * 0.15, box.width * 0.9, box.height * 0.9])#color legend with custom labels
legend1 =ax.legend(b.legend_elements()[0],col_bins , title = c, loc=’upper center’, bbox_to_anchor=(0.5, -0.15), ncol = 5)
ax.add_artist(legend1)#size legend with custom labels
legend2 = ax.legend(handles, labels_new, loc = “center left”, title=s, bbox_to_anchor=(1, 0.5))
ax.set(xlabel=x, ylabel =y, title=t )

使用 figure 小部件绘制新图形。

#draw new graph
self.canvas.draw()

第四步:格式化窗口小部件

一旦创建了所有的小部件,就需要对它们进行格式化。PyQt5 有许多不同的布局,我选择使用 QVBoxLayout()。这将小部件排列在一个垂直框中。还有 QHBoxLayout()将小部件排列在一个水平框中,QGridLayout()以网格格式排列小部件,而 QFormLayout()将小部件排列在两列中。

每个小部件都可以一个接一个地添加到布局中,它们将堆叠在一起。最后,一旦所有的小部件都在布局中,它需要用 self.setLayout(LayoutName)设置。可以使用任何名称,但是这将初始化布局对象,并且需要调用它以便向特定布局添加小部件。

grid = QVBoxLayout()
grid.addWidget(self.xLabel)
grid.addWidget(self.xComboBox)
grid.addWidget(self.yLabel)
grid.addWidget(self.yComboBox)
grid.addWidget(self.sLabel)
grid.addWidget(self.sComboBox)
grid.addWidget(self.cLabel)
grid.addWidget(self.cComboBox)
grid.addWidget(self.canvas)
grid.addWidget(self.mySlider)
grid.addWidget(button)
self.setLayout(grid)

第五步:主方法

main 方法创建该类的一个实例,并无限循环以获取对可视化所做的任何更改。

if __name__ == ‘__main__’:
   # creating apyqt5 application
   app = QApplication(sys.argv) # creating a window object
   main = Window() # showing the window
   main.show() # loop
   sys.exit(app.exec_())

最终输出

最终图形用户界面

总结

结合以上所有步骤,获得您的交互式气泡图!当该函数运行时,GUI 应该在一个单独的窗口中弹出。完整的代码链接如下。虽然这是一个简单的例子,但是 PyQt5 可以集成到任何 matplotlib 可视化中。它允许创建添加深度和信息层的可视化,这是一般报告或静态可视化无法实现的。毕竟一图胜千言。

完整代码可以在这里找到:https://github . com/kruthik 109/Data-Visualization/blob/main/Interactive-Bubble-Plot/widgets . py

来源

中央情报局。(2020 年 4 月 6 日)。2020 年世界概况。中央情报局。检索于 2022 年 2 月 22 日,来自https://www.cia.gov/the-world-factbook/about/archives/

使用 Streamlit 创建交互式数据科学电子课程应用程序

原文:https://towardsdatascience.com/create-an-interactive-data-science-e-course-app-using-streamlit-b9f093aa64a1

探索 streamlit_book -一个新的 streamlit 伙伴库-用于创建交互式电子书/电子教程/电子课程

图片由 Pixabay 提供

介绍

如果你关注我的 Medium 博客已经有一段时间了,你应该知道我是 Streamlit 的忠实粉丝。我写过几篇关于 Streamlit 的文章,从完整的初学者教程到动手的应用程序构建项目。我永远不会对它感到厌倦,并且每天都在寻找新的特性、很酷的技巧或令人兴奋的能力来探索和学习。

作为一名数据科学家,Streamlit 让我探索 web 应用程序开发的世界变得可能和超级容易,我发现这非常迷人和令人大开眼界。我最近偶然发现了一个新的 streamlit 伙伴库 streamlit_book,它是由 Sebastian Flores Benner 创建的,旨在供教师和内容创建者使用 Streamlit 框架创建交互式电子课程、教程或演示文稿。

我超级感兴趣,马上开始探索,玩起来。使用 Streamlit 和 streamlit_book,我创建了一个样本数据科学电子教程应用程序,它有多个页面,用户可以与之交互的代码示例,以及测试知识的测验。尽管在这个过程中我遇到了一些小问题(我不得不使用一些变通方法),但总的来说,我发现这个新库非常有用和方便。

在本文中,我将与您分享创建数据科学电子教程应用程序的想法、步骤和代码。这里的主要想法是试验 Streamlit 框架,看看我们是否可以在数据科学教程中引入某种程度的交互性,例如在多个页面中安排内容,允许用户运行并检查代码示例的输出,以及为用户提供测验以增强他们的学习体验。

下面是一个 YouTube 视频短片,演示了这款应用程序:

作者提供的视频

示例教程页面(图片由作者提供)

先决条件

如果您还没有这样做,您需要在您的计算机上安装 Streamlit 和 streamlit_book 来构建此应用程序。

#1:细流安装:

可以参考下面的文章,按照说明安装 Streamlit,学习基础知识。

#2:安装 streamlit_book

按照 streamlit_book 文档页面上的说明安装 streamlit_book 库:

pip install streamlit_book

创建项目文件夹和文件

在我们启动 Streamlit 并开始构建应用程序之前,让我们创建一个项目文件夹和一些空的 python(。py)文件,遵循如下所示的步骤和结构。

首先,让我们创建一个名为“ecourse_app”的项目文件夹,该文件夹将存放该项目所需的所有文件。在项目文件夹中,创建一个名为“ecourse_streamlit”的子文件夹和一个空 python(。py)名为“ecourse_main.py”的文件。这个 python 文件将是设置应用程序结构和属性的主要文件。

在“ecourse_streamlit”子文件夹中,创建几个空的 python 文件,这些文件将存放所有课程内容。每个 python 文件都充当应用程序中的一个页面。由于 streamlit_book 按字母顺序对内容文件进行排序和读取,因此在文件名前加上数字有助于获得所需的排序。在该文件夹中,您还需要包含一个示例数据 CSV 文件和一个将用作封面图像的图像。

作者图片

现在我们可以从 Anaconda 终端窗口启动 Streamlit。一个空白的 Streamlit 应用程序应该会出现在您的本地 web 浏览器中。

作者图片

创建主文件(ecourse_main.py)

打开 ecourse_main.py 文件,插入下面的代码。第 6 行设置了 streamlit_book 属性,并允许您配置应用程序的行为。您需要使用 path 参数指定 streamlit_book 应该在其中查找所有内容文件的文件夹名称。您还可以通过选取自己的图标或文本来自定义“上一页”和“下一页”按钮的外观。

我们还可以向应用程序添加一个侧栏,并提供一些关于应用程序的附加信息。为了节省空间,我们可以使用 st.sidebar.expander()小部件。我们还将创建一个反馈部分,要求用户对我们的应用程序进行评级并提供任何反馈。

作者图片

创建内容文件(00 封面. py 等。)

创建封面(00 封面. py)

让我们首先为我们的电子教程创建一个封面。打开“00 Cover Page.py”文件并插入以下代码。

第 5–9 行:我们给应用程序的封面添加一个标题。我们选择 st.markdown()而不是 st.title()的原因是,我们可以使用 CSS 来设置文本的样式,使其在视觉上更具吸引力。

第 11–19 行:我们给应用程序添加了一个封面图片。我们在这里使用了一个小技巧来使图像居中。使用 st.columns()我们可以创建 3 列,其中第二列(放置图像的列)的宽度是第一列和第三列的 6 倍,因此将图像放置在中间。

我们的封面完成了!看起来是这样的:

作者图片(熊猫图片由 Pixabay 提供)

创建介绍页面(01 什么是熊猫. py)

在封面之后,让我们创建一个介绍页面,给读者一个关于熊猫图书馆和这个电子教程的介绍。打开“01 什么是 Pandas.py”并插入下面的代码:

因为简介页面是一个文本丰富的页面,所以您不必总是使用 python(.py)文件。您可以使用降价文件(.md 文件),如果您想对文本应用更多的自定义样式和格式。

作者图片

创建“导入数据”页面(02 导入数据. py)

现在让我们进入第三页,即“导入数据”页。在这一页,我们想向读者展示如何导入一个 CSV 文件到熊猫。我们希望通过允许用户运行代码示例并检查输出来使页面具有交互性。我们还希望在页面的末尾包含一些小测验,以便用户可以自测他们的知识。这是一个有趣的页面!

打开“02 Import Data.py”文件并插入以下代码:

让我们详细检查一下上面的代码:

第 5–6 行:我们使用 st.subheader()向页面添加标题,并使用 st.markdown()添加一些文本来解释如何将数据导入 Python。

第 8–10 行:我们将第一段代码放在一个字符串中,然后使用 st.code()显示它。

第 12–18 行:我们创建了一个 Streamlit 按钮,这样当用户点击“检查结果”按钮时,我们使用 st.write()显示第一段代码的输出。

第 20–30 行:我们对第二段代码做同样的事情。

第 32–46 行:我们将第三段代码放入一个字符串中,并使用 st.code()显示它,就像我们对前两段代码所做的那样。但是,由于这段代码的输出不是数据帧,所以我们不能只使用 st.write()来显示输出。相反,我们需要将输出转换成一个字符串,并使用 st.text()来显示结果,如第 39–44 行所示。

第 48–58 行:我们创建了一个测验部分,并使用两个内置的 streamlit_book 小部件添加了两个问题:stb.single_choice()和 stb.true_or_false()。

作者图片

这就是教程的第三页。它有几段解释概念和代码,然后显示代码示例,并允许用户运行和检查输出!它还有一个很好的测验部分,为应用程序带来额外的交互性。

现在,您只需遵循相同的代码模板,并在此应用程序中创建其余的页面!一定要看看 streamlit_books 的文档页面,因为它还有一些其他很酷的功能可以尝试,比如多选题小部件、待办事项小部件等。要有创意,要有乐趣!感谢阅读,我希望你喜欢这篇文章。

作者提供的视频

参考:

  1. 如何用 5 个步骤用 Streamlit 创作互动书籍 Sebastian Flores Benner
  2. Streamlit_book 文档:https://streamlit-book.readthedocs.io/en/latest/

你可以通过这个推荐链接注册 Medium 会员(每月 5 美元)来获得我的作品和 Medium 的其他内容。通过这个链接注册,我将收到你的会员费的一部分,不需要你额外付费。谢谢大家!

用 PyScript 和熊猫创建一个交互式 Web 应用程序

原文:https://towardsdatascience.com/create-an-interactive-web-app-with-pyscript-and-pandas-3918ad2dada1

PyScript 允许我们用 HTML 和 Python 作为脚本语言创建一个无服务器的 web 应用程序

作者图片

PyScript 是 web 应用的未来吗?也许是,但可能还不是——我敢肯定,它仍然只是一个 alpha 版本,还有很多工作要做。

但是现在它有多大用处呢?

我们将创建一个交互式 web 应用程序,其中的逻辑完全是用 Python 编写的。

你可能听说过 PyScript,它是由 Anaconda 的首席执行官王蒙杰在 2022 年的 PyCon 大会上宣布的。用他们自己的话说这是“一项闪亮的新技术……允许用户在浏览器中编写 Python,事实上还有许多语言”。

PyScript 的最大优势在于,您可以用 Python 编写 web 应用程序,而不需要服务器。这是通过使用用 WebAssembly 编写的 Pyodide Python 解释器来实现的,web assembly 是现代浏览器支持的较低级的 web 语言。

到目前为止,Python web 应用程序都是基于服务器的,并且使用 Django 或 Flask 等框架,其中前端是用 HTML 和 Javascript 创建的,而后端是在远程服务器上运行的 Python。

最近,Dash 和 Streamlit 试图通过提供纯 Python 框架来简化此类应用的构建,从而避免学习 HTML 和 Javascript。但是这些仍然是基于服务器的应用程序。

PyScript 是一种不同的方法。在 PyScript 中,用户界面仍然是用 HTML 构造的,但是 Javascript 被 Python 代替了(尽管如果您愿意,您仍然可以使用 Javascript,并且用两种脚本语言编写的函数可以相互通信)。

网络应用

我将运行一个用 PyScript 和 HTML 编写的简单应用程序,它从远程数据源读取数据,并显示一个简单的仪表板,用户可以在其中选择要在 Pandas 图表中显示的数据。

该应用程序的基本形式如下:

<html>
    <head>
        <link rel="stylesheet" 
            href="https://pyscript.net/alpha/pyscript.css" />
        <script defer 
            src="https://pyscript.net/alpha/pyscript.js">
        </script>
        <py-env>
            - libraries-that-will-be-used
        </py-env>
    </head>
    <body> <!-- HTML layout code goes here --> <py-script>
            # PyScript code goes here
        </py-script>
    </body>
</html>

正如你所看到的,它看起来像一个标准的 HTML 文件,事实上它就是一个。但是对于 PyScript app,我们需要在<head>部分包含到 PyScript 样式表和 Javascript 库的链接,以及包含我们稍后将看到的<py-env>块。

接下来在<body>中,我们有 HTML 布局内容,后面是包含 Python 代码的<py-script>...</py-script>标签。

我们现在将充实这些部分。

首先,我将使用 Bootstrap 框架来使整个应用程序看起来很漂亮。这意味着我们需要包含到引导 css 文件的链接。我们将使用 Matplotlib 和 Pandas 库,这些库需要在<py-env>...</pt-env>部分声明。<head>...</head>现在看起来像这样:

<head>
    <link rel="stylesheet" 
        href="https://pyscript.net/alpha/pyscript.css" />
    <script defer 
        src="https://pyscript.net/alpha/pyscript.js">
    </script>
    <link rel="stylesheet" 
        href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
    <py-env>
        - matplotlib
        - pandas
    </py-env>
</head>

接下来要看的是网页的 HTML 代码。第一部分基本上是页面的标题。我们利用 Bootstrap 的大屏幕容器。这给了我们一个令人愉快的灰色背景的盒子和一些经典引导风格的介绍性文本。

<div class="jumbotron">
    <h1>Weather Data</h1>
    <p class="lead">
        Some graphs about the weather in London in 2020
    </p>
</div>

接下来是两行:第一行有一个按钮和一个下拉菜单,第二行包含要显示的图表。

这是第一行:

<div class="row">
    <div class="col-sm-2 p-2 ml-4 mb-1">
        <button type="button" class="btn btn-secondary">Select chart from list:</button>
    </div> <div class="col-sm-4 p-2 mr-4 mb-1">
        <select class="form-control" id="select">
            <option value="Tmax">Maximum Temperature</option>
            <option value="Tmin">Minimum Temperature</option>
            <option value="Sun">Sun</option>
            <option value="Rain">Rain</option>        
        </select>
    </div>
</div>

它由两列组成,一列用于按钮,另一列用于菜单(我在这里使用按钮只是为了美观,它实际上并没有按钮的功能)。下拉列表中的选项将用于显示四个不同图表中的一个。将使用的数据是 2020 年伦敦的天气状况表。表格中有 12 行代表每个月,表格中的列代表该月的最高温度、最低温度、日照时数和降雨量毫米**

因此,菜单项代表这些选项,并将取值为“Tmax”、“Tmin”、“Sun”或“Rain”。

到目前为止,我们已经编写了网页代码,现在我们需要定义对用户输入做出反应的逻辑,并绘制图表。这在<py-script>一节中定义。我们接下来要处理的代码在这一节中。

先导入一些库。

**# Import libraries
import pandas as pd
import matplotlib.pyplot as plt**

当然是熊猫和 Matplotlib,但我们还需要以下内容:

**from pyodide.http import open_url**

这是 PyScript 提供的一个库,它允许我们从网上的资源中读取内容,我们是这样使用它的:

**url = 'https://raw.githubusercontent.com/alanjones2/uk-historical-weather/main/data/Heathrow.csv'
url_content = open_url(url)df = pd.read_csv(url_content)**

PyScript 实现 Pandas 函数read_csv不能直接打开 url,所以我们必须使用上面的技术。

正在下载的文件包含几十年的数据,但为了保持简单,我们将过滤它,只保存 2020 年的数据。

**# filter the data for the year 2020
df = df[df['Year']==2020]**

现在是我们之前看到的在 HTML <div>中绘制图表的函数。

**# Function to plot the chart
def plot(chart):
    fig, ax = plt.subplots()
    df.plot(y=chart, x='Month', figsize=(8,4),ax=ax)
    pyscript.write("chart1",fig)**

这是和熊猫一起策划的非常标准的东西。与“普通”Python 程序的主要区别在于它的呈现方式。pyscript.write函数获取 HTML 元素的 id,并将第二个参数的内容写入其中。

这依赖于 PyScript 实现中包含的呈现特定对象的方式——在本例中是 matplotlib 图表——这不一定适用于任何类型的对象。例如,如果我们想要显示一个 Plotly 图表,我们将不得不使用不同的技术,因为在 PyScript 中没有直接实现 Plotly 图形(到目前为止)。

接下来要做的是定义当用户从下拉菜单中选择一个新图表时调用plot函数的方法。

首先是更多的库——这些是作为 PyScript 的一部分提供的。

**from js import document
from pyodide import create_proxy**

js库允许 PyScript 访问 Javascript 函数,在这个例子中,我们引入了访问 DOM 的能力。Pyodide 的 create_proxy 则相反,允许 Javascript 直接调用 PyScript 函数。

**def selectChange(event):
    choice = document.getElementById("select").value
    plot(choice)**

当一个改变事件发生时,这个函数被调用。它读取选择的值,然后用该值调用先前定义的plot函数。

接下来,我们定义一个代理,它将允许 change 事件调用 PyScript 函数selectChange

**# set the proxy
def setup():
    # Create a JsProxy for the callback function
    change_proxy = create_proxy(selectChange) e = document.getElementById("select")
    e.addEventListener("change", change_proxy)**

最后,当页面第一次加载时,我们需要调用setup函数并使用默认值(‘Tmax’)运行plot函数,以便在启动时显示图表。

这是实现一个简单的仪表板应用程序的全部代码,就像本文开头截图中的那个一样。这个代码和一个演示应用程序的链接将在最后的链接。

PyScript 与现有的基于服务器的应用程序相比如何?

就难度而言,只要你熟悉 Python,构建 web 应用程序就相对简单,可能比构建 Django 或 Flask 应用程序更容易——我认为更像 Dash 的水平。

然而细流是一个不同的故事。Streamlit 在视觉设计方面更受限制,但它的优势是你不必学习 HTML。Streamlit 应用程序非常容易创建——我认为比 PyScript 更容易。

然而,PyScript 相对于所有竞争对手的优势在于它不依赖于服务器。这使得部署变得非常容易——我只需将您在这里看到的应用程序上传到 Github Pages 网站,它就可以正常工作了。

目前 PyScript 有点慢。因为所有的工作都是在浏览器中完成的,所以性能由运行浏览器的机器决定——如果你的硬件很弱,那么加载你的应用可能需要很长时间,尽管一旦它启动并运行,性能似乎就没问题了。

但是,随着硬件变得更加强大,PyScript 变得更加成熟,并且(希望)变得更加高效,性能只会变得更好。有一天,谁知道呢,也许我们会看到 PyScript 像今天的 Javascript 一样嵌入到浏览器中。

更新:你可以在这里 找到这个 app 用 Plotly 代替熊猫绘图的版本

更新 2:PyScript 新增功能 查看截至 2023 年初 PyScript 所做的一些重大更改。

一如既往,感谢阅读。你可以在我的网站上找到完整代码和演示网页的链接,在那里你还可以找到其他文章的链接。

**https://alanjones2.github.io

如果你想跟上我写的东西,请考虑订阅我偶尔的时事通讯或者点击下面的链接,当我在这里发表时,你会收到一封电子邮件。

https://technofile.substack.com

笔记

  1. 天气数据来自我的报告英国历史天气,并来自英国气象局历史气象站数据。它是根据英国开放政府许可证发布的,可以在相同的条件下使用。

海生的

在回答“Seaborn 是否适用”这个问题时:

<html><head>
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script><link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"><py-env>
   - matplotlib
   - pandas
   - seaborn
</py-env></head>
<body><div class="jumbotron"> <h1>Weather Data</h1>
   <p class="lead">
      Some graphs about the weather in London in 2020
   </p></div><div class="row">
   <div class="col-sm-2 p-2 ml-4 mb-1">
   <button type="button" class="btn btn-secondary">Select chart from list:</button>
   </div>
   <div class="col-sm-4 p-2 mr-4 mb-1">
      <select class="form-control" id="select">
         <option value="Tmax">Maximum Temperature</option>
         <option value="Tmin">Minimum Temperature</option>
         <option value="Sun">Sun</option> 
         <option value="Rain">Rain</option>
      </select>
   </div>
</div><div class="row">
   <div class="col-sm-6 p-2 shadow ml-4 mr-4 mb-4 bg-white rounded">
      <div id="chart1"></div>
   </div>
</div><py-script>
# Import libraries
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns# Get the datafrom pyodide.http import open_urlurl = 'https://raw.githubusercontent.com/alanjones2/uk-historical-weather/main/data/Heathrow.csv'url_content = open_url(url)df = pd.read_csv(url_content)# filter the data for the year 2020df = df[df['Year']==2020]# Function to plot the chartdef plot(chart):
   fig, ax = plt.subplots()
   sns.lineplot(y=chart, x="Month", data=df, ax=ax)
   pyscript.write("chart1",fig)# Set up a proxy to be called when a 'change'# event occurs in the select controlfrom js import document
from pyodide import create_proxy# Read the value of the select control# and call 'plot'def selectChange(event):
   choice = document.getElementById("select").value
   plot(choice)# set the proxydef setup():
   # Create a JsProxy for the callback function
   change_proxy = create_proxy(selectChange)
   e = document.getElementById("select")
   e.addEventListener("change", change_proxy)setup()# Intitially call plot with 'Tmax'plot('Tmax')</py-script></body></html>

创建 Cad 并将其部署到。stl 转换器:Streamlit 和 Python

原文:https://towardsdatascience.com/create-and-deploy-a-cad-to-stl-converter-streamlit-and-python-28a01240726a

快速高效地创建并投入生产

这篇文章提出了两个主题:

  • 渴望尝试使用工具来部署【python】****项目作为 web 应用,快速与朋友、客户和同事分享自己的工作,并拥有一个响应迅速、令人愉悦的界面
  • 实现一个定制系统,用于将 CAD 几何图形 (step 和 iges)转换为 STL 格式,用于工程和人工智能应用。

我发现这个工作流程(和最终结果)真的满意满意,考虑到最终结果的质量和实现所需的时间之间非常高的比率。根据这篇文章,你可以尝试这个应用程序,复制它,从它那里获得灵感,创造一个新的应用程序!

您可以在以下位置尝试此应用程序:

https://share . streamlit . io/git Marco 27/deepcadconverter/main/main . py

什么是简化

https://streamlit.io/

Streamlit 是一个开源的 Python 库,可以轻松创建和共享漂亮的、定制的机器学习和数据科学 web 应用。只需几分钟,您就可以构建和部署强大的数据应用程序。

当我第一次读到这个项目时,我立刻被这个想法吸引住了。

自由地用主机部署应用程序的能力令人难以置信。除此之外,还能拥有具有吸引力和响应性的图形,而无需使用 HtmlCSS 。只需创建一个格式良好的 Github 库,就大功告成了:我们已经准备好使用和共享我们的 web 应用程序了。这开启了一个潜在应用的海洋:例如,我们可以以快速、实用、有效的方式实现复杂的人工智能算法,进行训练,并将我们的模型投入最终用户的生产。

CAD 到 STL 转换器 streamlit 应用程序—图片由作者提供

关于什么是 streamlit,它是如何工作的,我就不多说了。你可以在https://marcosanguineti.medium.com/subscribe上找到很多关于它的非常有趣的文章(我提醒你,成为会员你就可以接触到无限的文章,支持我们的工作)。

您可以在下面找到该应用程序的完整代码:

**https://github.com/GitMarco27/DeepCadConverter

可以随意复制,叉或者改进!

为什么是 Cad 到 Stl 的转换器

机械工业工程领域工作,以及在人工智能和软件开发领域工作,恰好要处理各种格式的实体( CAD ),用各种软件制作。

各种格式之间的转换 绝非小事,尤其是在不借助专业软件的情况下,从许可的角度来看通常非常昂贵(尤其是如果仅用于几何图形的转换)。

有很多在线工具可以将 IGES 和 STEP 转换成 STL,但是这些工具通常有:

  • 使用限制
  • 操作问题
  • 缺少设置
  • 成吨的横幅广告。

这导致了用 T21 GMSH T22 Python 和 Streamlit 开发我们自己的转换工具的想法。

Gmsh 是一个开源的 3D 有限元网格生成器,内置 CAD 引擎和后处理器。它的设计目标是提供一个快速、轻便和用户友好的网格工具,具有参数输入和高级可视化功能。Gmsh 围绕四个模块构建:几何、网格、求解器和后处理。对这些模块的任何输入的说明或者使用图形用户界面以交互方式完成,在 ASCII 文本文件中使用 Gmsh 自己的脚本语言(.geo文件),或者使用 C++、C、Python 或 Julia 应用编程接口(API)。

此工具允许您使用大量参数对几何体进行三角剖分,以最大化您的需求(几何体特征、网格密度……)。

Gmsh —作者图片

工作原理

为了使用 streamlit 实现应用程序,首先需要在您的虚拟环境中安装同名库( pip install streamlit )。

我们的应用程序将从 python 脚本启动:在本例中,我的应用程序指向脚本 main.py

要在本地启动我们的应用程序,只需在我们的操作系统中打开一个终端,然后启动(在您的虚拟环境中):

>> (venv) streamlit run main.py

在我们的应用程序中添加组件非常简单和直观,因为它们已经在大量的小部件中可用。

在我们的应用程序中编写文本支持我们习惯使用的 markdown 格式。我们有一个简单的工具来添加标题,一个用于文本,同样,添加图像也非常简单:只需指向正确的路径。我们还可以使用典型的 markdown 语法添加外部链接。

同时,创建多选菜单的语法非常简单:用“ with ”语句打开一个上下文,并在其中添加小部件。在这种情况下,此类对象(扩展器)用于设置 gmsh 参数,不会使主屏幕过于混乱,并且它也将在转换固有的日志之后使用。

file_uploader 小部件允许用一个对话框窗口加载一个(或多个)文件,或者在屏幕上拖动它们,将加载的数据存储在变量“data”中。

应用程序等待加载文件,直到使用适当的按钮激活转换触发器。

首先,执行任何先前运行的清除。首先,对所有以前的运行进行清理。如果加载的几何图形数量大于或等于 1,代码执行开始:在调用脚本执行真正的 cad 转换之前,几何图形保存在一个临时文件夹中(这样做是为了在主线程中执行 gmsh,避免信号问题)。

一旦所有几何图形的转换完成,文件被压缩并可供用户下载。

还会写入一个关于所执行操作的日志,并显示转换成功(或失败)的几何计数。

因此,让我们看看用于执行实际转换的脚本。

正如预期的那样,这个脚本通过系统 参数调用 gmsh,加载一个几何体(阶梯或 IGES)并根据指定的设置创建一个新的表面网格,导出一个新的 STL 到我们的临时文件夹。

要部署我们的应用程序,必须添加一个需求文件和一个文件,其中包含关于 python 和系统库的详细信息,它们必须安装在 docker 中,docker 将在远程主机上管理我们的应用程序。

从 streamlit 站点部署非常简单:一旦您将您的 Github 帐户与 streamlit 帐户连接,您就可以连接您的存储库(在我的例子中是 GitMarco27/DeepCadConverter)、主脚本(在我的例子中是 main.py)并选择 python 的版本(在我的应用程序中是 3.8)以在最终 docker 上使用。

结论

飞机。stl 渲染—作者提供的图像

我们已经看到了如何使用 Streamlit 快速高效地构建一个 python 应用程序,使用简单而强大的小部件实现 UI,并尝试解决一个与曲面网格相关的重要工程问题。我们看到了如何使用这些工具与朋友、同事或客户共享应用程序,以及在本地测试和部署(甚至免费)web 应用程序的可能性。

📚在我的个人资料上阅读更多文章

📨成为电子邮件订阅者,了解最新动态

💻加入媒体没有阅读限制

✏️对疑点或新内容的评论

下次见,马可**

使用 R 在几分钟内创建并托管您的个人网站

原文:https://towardsdatascience.com/create-and-host-your-personal-website-in-a-few-minutes-using-r-9c94e87e2942

在几分钟内建立和主持一个简单,干净,专业的个人网页,如果你喜欢,添加一些铃铛和口哨

我的简单网站的例子创建使用 R 和明信片在keithmcnulty.org

越来越多的数据科学家和其他技术专业人员因为他们的在线存在和开源活动而被认可,就像他们的职业历史一样。因此,我认为每个数据科学家都应该有自己的个人网页,向世界介绍一些他们自己和他们的工作。

这不必很复杂。我们都有 LinkedIn 个人资料,我们中的一些人还有其他在线存在,如媒体博客、YouTube 视频或 Twitter 账户。所以你的个人网页只需要提供一个链接的中心点,并提供一些关于你是谁和你做什么的基本信息。你想告诉人们多少关于你自己的事,这取决于你自己。

好消息是,你可以在不到一个小时的时间内免费完成这项工作。这是配料和要遵循的食谱。我还添加了一些可选的额外说明,以防你使用你自己的个人网站域名来托管你的网站(比如像我在keithmcnulty.org的网站),如果你想添加社交媒体元数据,以便社交媒体上你的网站链接具有专业的外观。

在我开始之前,你可以在 github repo 这里看到我网站上的所有代码,所以你可能希望以此为起点。

佐料

  1. r 和 RStudio
  2. 安装的RMarkdownpostcardsmetathis R 包
  3. Github 账户
  4. 一个 AWS 帐户,如果你想购买和使用个人网站域名

真的是这样。这是一个相当简单的过程,如果你按照这个食谱做,你应该能很快完成。

创建网站的方法

  • 为您的网站创建一个新的公共 github repo。
  • 从这个 github repo 在 RStudio 中启动一个新项目(新建项目>版本控制)
  • 将您想在网站上使用的照片添加到项目中(jpg 或 png 格式即可)。
  • 确保您已经安装了RMarkdownpostcards包。如果还没有,现在安装它们并重启 r。
  • 从模板开始一个新的 R Markdown 文档(新建文件> R Markdown >从模板)。选择弹出选项的postcards模板之一。任何一个应该都可以。如果你想的话,你可以随时改变。您将在弹出的模板文件中看到以下内容:

RMarkdown 基本明信片模板

  • 现在,您只需对您的姓名、照片文件以及您希望从个人资料中提供的任何链接进行必要的编辑。你基本上可以把尽可能多的链接向前。label字段将是显示在链接框中的内容,而url字段将是继续点击的地址。
  • 在底部的文本中,你可以写下任何你想告诉人们的关于你的事情。你可以在这里使用标准的 RMarkdown 格式,包括使用######的标题,或者你可以包括项目符号或其他资源或照片的链接。在本节中,如果您愿意,您甚至可以用原始 HTML 编码。如果你有很多后续链接,那么我会建议你保持一个像我这样的简单的简历。
  • 完成后,将文件保存为index.Rmd(确保它在项目的根目录下)并继续编织它。这将创建网页并保存为index.html。如果您想尝试另一种设计,只需将output字段中的名称更改为另一种postcards格式,如postcards::trestles或其他格式。
  • 完成后,提交新文件并将其推送到 github repo。

主办网站的食谱

我们将在 Github 上托管网站。这意味着每当你改变它,并推动这些变化,网站将自动更新。为您省去管理不同托管平台的麻烦。

  • 将您的网站文件推送到 Github 后,返回 Github repo,选择设置,然后选择页面。
  • 选择主分支和根目录,然后选择保存。
  • 网站上线可能需要几分钟时间,但你最终会看到一个绿框,上面有你的新网址。它通常是https://username.github.io/reponame的形式。
  • 恭喜,你有网站了。
  • 现在,无论你何时对你的网站进行更改,提交和推送,几分钟后这些更改将在你的新网站地址生效。容易得很。

可选方法:添加社交媒体元数据

通常我们会想在社交媒体上分享我们网站的链接。如果我们将社交媒体元数据添加到index.html中,我们可以定制共享时我们网站的链接显示方式。例如,当我在这里分享时,看看我的网站是如何显示的。我已经定制了链接中共享的图像和文本。

https://keithmcnulty.org

您可以使用metathis R 包轻松地将社交媒体元数据添加到您的index.Rmd文件中。就像这样添加一个 R 代码块:

添加社交媒体元数据

meta_social()功能包含了你想要你的链接如何出现在社交媒体上的所有必要定制。如果你想为你的社交媒体链接使用不同的图片,请确保将该图片上传到你网站的根目录下。请注意,不同的社交媒体平台对图像尺寸和大小有不同的规定,所以保持图像相对简单。300 像素乘 300 像素的正方形效果很好,但你也可以做一些简单的风景格式的照片。你可以随时尝试分享你的网站,直到你对它的外观满意为止。

可选方法:使用个人域名

购买和使用个人域名是个好主意。它有一个更专业的外观,更容易被人们记住,在许多情况下,它每年只需花费 12 美元,一旦你拥有它,你可以使用任何你想要的子域用于不同的目的(如 blog.domain.com 或 myproject.domain.com)。

你可以使用亚马逊 Route 53 使用这些指令注册并购买一个域名。大多数域名是可用的,本质上并不罕见,每年只需 12 美元。如果你已经通过另一个提供商购买了一个域名,那么如果你愿意,你也可以找到把它转移到 AWS 的说明。这样做通常更便宜,而且你通常有更大的灵活性。

一旦你购买了一个域名并拥有了它,你需要设置好让yourdomain.comwww.yourdomain.com链接到你创建的 Github 网站。你需要确保无论访问者在网址中使用http还是https都会发生这种情况。以下是方法。

  • 首先,您需要在 AWS Route 53 上为您的域名创建一个托管区域。进入托管区域。选择创建托管区域,并输入您购买的基本域名(不带www.)。选择它作为公共托管区域,然后单击创建。现在,您将在您的托管区域列表中看到这一点,并创建了几条记录。
  • 我们现在必须创建一个别名记录,将这个托管区域指向 Github。进入您的新托管区域,然后单击创建记录。然后简单路由。在下一页,单击“定义简单记录”。保持记录名称不变,并选择一个 under 记录类型。在值/将流量路由到下,选择 IP 地址,然后输入这些值,然后选择定义简单记录,然后选择创建记录。
185.199.108.153
185.199.109.153
185.199.110.153
185.199.111.153
  • 现在对域的www版本做同样的事情,创建另一个简单的记录,这次使用记录名[www.yourdomain.com](http://www.yourdomain.com)。这一次,在记录类型下,选择 CNAME。在 Value/Route traffic to 下,输入您之前创建的 Github 网站的基本网址— yourusername.github.io。不要输入回购名称。定义并创建新记录。
  • 最后,回到你的 github repo,回到设置>页面。在显示自定义域的地方,输入您的根域(不带www.)并点击保存。如果您正确地遵循了前面的步骤,您最终应该会看到一个绿色的勾号,并看到一个过程开始提供您的 TLS 证书。
  • 这可能需要一点时间,但完成后,您可以选择“强制 HTTPS ”,这样就完成了。你可能需要一点耐心来完成这一切,但是一旦完成,访问你在https://yourdomain.comhttps://www.yourdomain.com的新网站,希望你都准备好了。

仅此而已。只需要几分钟就可以设置好网站的基本版本。建议将其附加到自定义域,如果您遵循这些说明,应该不会太难。希望这些对你有帮助。

用 SMOTE 创建人工数据

原文:https://towardsdatascience.com/create-artificial-data-with-smote-2a31ee855904

如何利用简单的算法来弥补数据的不足

布雷特·乔丹在 Unsplash 上的照片

数据不平衡在机器学习中普遍存在。真实数据很少平等地代表每个类。在疾病诊断、欺诈检测和垃圾邮件分类等应用程序中,一些类别将总是被低估。

这是许多机器学习相关工作的主要障碍。毕竟,如果您缺少特定结果的数据,您的模型将无法充分预测该结果。

值得庆幸的是,有办法绕过这个问题。如果你不能为少数民族阶层获取足够的数据,为什么不自己做一些呢?

这里,我们介绍合成少数过采样技术(SMOTE),这是一种用于生成人工数据的流行算法。

为什么击打?

在介绍 SMOTE 的来龙去脉之前,让我们首先讨论一下对人工数据的需求。

当我们可以利用欠采样和过采样等采样技术时,为什么我们需要经历创建我们自己的数据的麻烦?

简而言之,这是因为这些方法存在重大缺陷。

欠采样需要通过从多数类中移除记录来均衡每个类的样本。这在理论上是明智的,但付诸实践就完全荒谬了。

数据是宝贵的。

为了进行后续的分析和建立依赖于这些数据的模型,企业需要付出相当大的努力来收集和存储数据。

试着告诉你的老板,为了“平衡”,你想丢弃你的数据工程师辛辛苦苦获得的 80%的数据。不会有好结果的。

过采样需要通过复制少数类样本来增加少数类大小,从而使每个类的样本均匀。这不是一个更好的选择,因为用这样的数据训练的模型容易过度拟合。

如果丢弃或复制样本无效,剩下的唯一选择就是创建自己的数据。

SMOTE 是用来实现这一点的最流行的算法之一。

SMOTE 如何工作

SMOTE 的内部工作看似抽象,但相当简单。

为了理解 SMOTE 如何生成人工数据,最好认识到每个样本在特征空间中都有自己的表示。

我们用一个例子来演示一下 SMOTE。下面是一组由 2D 特征空间中的向量表示的虚构数据。红点代表少数阶级。

注意:大多数数据集通常有更高的维度。为了方便起见,我们使用了一个过于简化的 2D 特征空间。

2D 特征空间(由作者创建)

首先,SMOTE 识别来自少数类的数据点的 k 个最近邻(在这种情况下,k=3)。

2D 特征空间(由作者创建)

接下来,它在所有邻居之间的随机位置创建一个新点。这些新点表示属于少数类的人工数据。

2D 特征空间(由作者创建)

它将继续生成新数据,直到数据不平衡得到解决。

2D 特征空间(由作者创建)

缺点

SMOTE 补救不平衡的数据集,而不会带来欠采样或过采样带来的风险。然而,这种方法有其自身的局限性。

首先,当搜索少数类的邻居时,SMOTE 冒着包括其他类的风险。这将导致产生不能充分代表少数群体的数据。

其次,SMOTE 的算法用在高维数据上并不可靠。

自然,在大量的研究中已经解决了许多这些弱点,开发了 SMOTE 的不同变体。然而,这超出了本文的范围。

警告

照片由吴礼仁Unsplash 上拍摄

SMOTE 应仅用于增加训练数据。您的测试数据集应该保持不变。对整个数据集应用 SMOTE 将导致数据泄漏。

这也意味着用于评估模型的测试数据将由人工数据组成。这听起来可能是显而易见的,但是您希望评估您的模型的数据是真实的。否则,任何报告的评估指标都没有任何意义。

个案研究

这一次,让我们看看在真实数据上使用 SMOTE 是如何工作的。在这个演示中,我们使用一个提供客户信息的数据集(无版权保护)来构建一个模型,以确定他们是否对车辆保险感兴趣。在这里可以访问的原始数据。

下面,我们可以看到数据集中的变量。

代码输出(由作者创建)

目标标签是“响应”。

在任何模型建立之前,我们需要预处理原始数据。

快速计数显示了每个类中的记录数。

代码输出(由作者创建)

多数群体与少数群体的人数比例约为 5:1,这是数据不平衡的一个明显迹象。

首先,让我们建立一个随机森林模型,在没有 SMOTE 的情况下预测客户对车辆保险的兴趣。

我们将使用 f-1 评分标准评估该模型。

代码输出(由作者创建)

该模型的 f-1 分数为 0.44,有改进的余地。

接下来,我们将重复相同的过程,但是在添加人工数据之后。我们可以用 Python 中的 imblearn 模块的 SMOTE 来实现。

该模块允许我们在创建合成数据时具有一定的灵活性。你可以控制这样的事情:

  • 多数类样本数与少数类样本数的比率
  • SMOTE 针对的类别
  • 构建样本时要考虑的最近邻的数量

我们将坚持默认参数。这意味着该算法考虑 5 个最近的邻居,并将生成数据,直到少数样本的数量和多数样本的数量相同。

请注意 SMOTE 如何仅应用于训练组。如前所述,不要为测试集生成人工数据是非常重要的。

代码输出(由作者创建)

现在已经解决了训练数据中的数据不平衡问题。因此,让我们用这些数据构建一个随机森林分类器,看看它在测试集上的表现如何。

代码输出(由作者创建)

该模型的 f-1 得分为 0.52,证明与未经人工数据训练的模型相比,该模型能更好地预测客户对车辆保险的兴趣。

结论

照片由 Prateek KatyalUnsplash 上拍摄

总而言之,SMOTE 的这个简要概述应该可以帮助您熟悉人工数据生成背后的逻辑及其在机器学习应用程序中的使用。

SMOTE 虽然非常适用,但也有其局限性和弊端。即使是现在,大量的研究和投资正被投入到探索新的变种中。

如果你感兴趣,你可以看看 smote-variants 包,它允许你实现 85 种不同的算法变体!

我祝你在数据科学的努力中好运!

参考

  1. 莫比乌斯。(2022).从不平衡的保险数据中学习,第 4 版。2022 年 1 月 22 日从 https://www.kaggle.com/arashnic/imbalanced-data-practice.取回

使用这些 R 包轻松创建美丽的情节

原文:https://towardsdatascience.com/create-beautiful-plots-easily-with-these-r-packages-bf2f278d479c

不费吹灰之力就能制作出优雅而专业的图表

图片作者。

目录

  1. 简介

  2. 2.1gg map
    2.2gg pubr
    2.3拼凑
    2.4 ggforce
  3. 结论
  4. 参考文献

1.介绍

是一个强大的数据可视化 R 包。

遵循图形的语法,它将绘图定义为数据和:

  • 审美:颜色或大小等属性。
  • 几何图形:线条或条形物体。

该软件包以模块化的方式实现这些独立的组件,允许以优雅和灵活的方式创建几乎任何情节。

在本帖中,我们探索了四个 R 包,它们简化了基于ggplot2的高级绘图的生成。特别是:

  • ggmap为空间图形添加地理背景。
  • ggpubr生成用于发布的图表。
  • patchwork将独立的情节组合成相同的图形。
  • ggforce提供定制ggplot2图表和生成更专业的可视化的方法。

我们使用加州房价数据集,可以在 Kaggle 上免费获得(CC0⁴)。它包含了从 1990 年人口普查中得出的加州各区房价中值。

我们从导入所需的包开始:

library(tidyverse)
library(ggmap)
library(ggforce)
library(ggpubr)
library(patchwork)library(concaveman) # needed for hull geometry

它们可以通过在 R 控制台中键入install.packages("package_name")来安装。

tidyverse包含不同的包。其中,我们将使用:

  • dplyrtibble用于数据操作。
  • readr加载 csv 文件。
  • ggplot2用于可视化。

让我们导入数据集,并对价格和收入变量应用简单的宁滨:

df <- 
  read_delim(file      = "C:/data/housing.csv",
    col_types = "dddddddddf",
    delim     = ",")# Create bins for income and house price
df <- df %>% 
  mutate(
    price_bin  = as.factor(ntile(median_house_value, 4)),
    income_bin = ntile(median_income, 4))

2.包装

2.1 ggmap

作者: 大卫·卡勒哈雷·威克姆

当数据集包含空间信息时,观察地理环境中的变量会很有趣。

我们首先用ggplot2来表示latitudelongitude,并一瞥加州的形状:

df %>%
  ggplot(aes(x = longitude, y = latitude)) +
  geom_point()

图片作者。

ggmap ⁵使用了与ggplot2相同的分层语法来给情节添加地理背景。它允许轻松地包含静态地图,例如来自谷歌地图OpenStreetMapStamen 地图的静态地图。

我们只需要将LatitudeLongitude信息传递给qmplot ⁶函数,如下所示

df %>%
  qmplot(x    = longitude, 
         y    = latitude, 
         data = .)

图片作者。

我们可以根据房价应用颜色渐变:

df %>% 
  qmplot(x      = longitude, 
         y      = latitude, 
         data   = ., 
         colour = median_house_value, 
         legend = "topright") +
  scale_color_gradient("House Prices", 
                       low  = "blue",
                       high = "red")

图片作者。

我们可以过滤我们的坐标来探索特定的区域。比如我们可以观察萨克拉门托周边的房价(纬度:38.575764,经度:-121.478851):

df %>% 
  filter(between(longitude, -121.6, -121.3), 
         between(latitude, 38.4, 38.7)) %>%
  qmplot(x      = longitude, 
         y      = latitude, 
         data   = ., 
         colour = median_house_value, 
         size   = I(7), 
         alpha  = I(0.8), 
         legend = "topright") +
  scale_color_gradient("House Prices", 
                       low  = "blue", 
                       high = "red")

图片作者。

更多信息和例子可以在 here⁷.找到

2.2 ggpubr

作者: 阿尔布卡德尔

基本 ggplots 的定制和格式化需要更深入的语法知识和更高级的 R 技能。

⁸套餐有助于创造美丽的情节。它提供了易于使用的功能,为研究人员和 R 从业人员生成可供发布的图。简而言之,它是一个围绕着ggplot2包装器,处理大部分复杂的情节定制。

例如,我们可以用一行代码生成一个格式良好的箱线图:

df %>%
 ggboxplot(x       = "income_bin", 
           y       = "median_house_value",
           fill    = "income_bin",
           palette = c("#2e00fa", "#a000bc", "#ca0086", "#e40058"))

图片作者。

该软件包允许向图表添加 p 值和显著性水平。在这个例子中,我们使用ggviolin ⁹创建了一个小提琴图,并添加了与stat_compare_means ⁰:的平均值比较

# Comparison between the Income groups
bin_comparisons <- list( c("1", "2"), 
                         c("2", "3"), 
                         c("3", "4"),
                         c("1", "3"),
                         c("2", "4"),
                         c("1", "4"))df %>%
 ggviolin(x          = "income_bin", 
          y          = "median_house_value", 
          title      = "Violin Plot",
          xlab       = "Income Levels",
          ylab       = "House Prices",
          fill       = "income_bin",
          alpha      = 0.8,
          palette    = c("#2e00fa","#a000bc","#ca0086","#e40058"),
          add        = "boxplot", 
          add.params = list(fill = "white")) +
  stat_compare_means(comparisons = bin_comparisons, 
                     label       = "p.signif") +
  stat_compare_means(label.y = 9000)

Violin 图类似于 box 图,只是它们也显示数据的核心概率密度。图片作者。

ggpubr提供几个函数,可轻松生成格式良好的图。密度图和直方图如下所示:

df %>%
  ggdensity(x       = "median_income",
            add     = "mean", 
            rug     = TRUE,
            color   = "price_bin", 
            fill    = "price_bin",
            title   = "Density plot",
            xlab    = "Income",
            ylab    = "Density",
            palette = c("#2e00fa", "#a000bc", "#ca0086", "#e40058")
   )

图片作者。

df %>%
  gghistogram(x       = "median_income",
              add     = "mean", 
              rug     = TRUE,
              color   = "price_bin", 
              fill    = "price_bin",
              title   = "Histogram plot",
              xlab    = "Income",
              ylab    = "Count",
              palette = c("#2e00fa","#a000bc","#ca0086","#e40058")
   )

图片作者。

我们甚至可以在一个页面上混合多个图,甚至可以通过ggarrange功能创建一个独特的图例:

p1 <- df %>%
  ggdensity(x       = "median_income", 
            add     = "mean", 
            rug     = TRUE,
            color   = "price_bin", 
            fill    = "price_bin",
            title   = "Density plot",
            xlab    = "Income",
            ylab    = "Density",
            palette =  c("#2e00fa","#a000bc","#ca0086","#e40058")
   )p2 <- df %>%
  gghistogram(x       = "median_income",
              add     = "mean", 
              rug     = TRUE,
              color   = "price_bin", 
              fill    = "price_bin",
              title   = "Histogram plot",
              xlab    = "Income",
              ylab    = "Count",
              palette = c("#2e00fa","#a000bc","#ca0086","#e40058")
   )ggarrange(p1, 
          p2, 
          labels = c("A", "B"),
          ncol   = 2
   )

图片作者。

官方文档包含更多信息和示例。

2.3 拼接

作者: 托马斯·林

patchwork允许轻松地将单独的ggplot2可视化组合到同一图形中。它还提供了数学运算符,可以更直观地操纵图形。

与前面的ggarrange示例类似,我们可以在同一个图形上混合两个图,如下所示:

p1 + p2

图片作者。

该软件包的最大优势在于直观和简单的 API 的完美结合,以及创建任意复杂的绘图组合的能力。以下是一些例子:

图片作者。

更多信息和例子可以在这里找到。

2.4 ggforce

作者: 托马斯·林

ggforce延伸ggplot2为构成专门地块提供设施。

在分享一些示例之前,我们创建了一个新的列,大约标识了一些著名的地点及其周边地区:

  • 洛杉矶(纬度:34.052235,经度:118.243683)
  • 圣何塞(纬度:37.335480,经度:121.893028)
  • 旧金山(纬度:37.773972,经度:122.431297)
  • 萨克拉门托(纬度:38.575764,经度:121.478851)
  • 圣地亚哥(纬度:32.715736,-117.161087)
  • 弗雷斯诺(纬度:36.746841,经度:119.772591)
  • 斯托克顿(纬度:37.961632,经度:121.275604)
df <- df %>% 
  mutate(area = case_when(
    between(longitude, -118.54, -117.94) 
     & between(latitude, 33.75, 34.35) ~ 'Los Angeles',
    between(longitude, -122.19, -121.59) 
     & between(latitude, 37.03, 37.63) ~ 'San Jose',
    between(longitude, -122.73, -122.13) 
     & between(latitude, 37.47, 38.07) ~ 'San Francisco',
    between(longitude, -121.77, -121.17) 
     & between(latitude, 38.27, 38.87) ~ 'Sacramento',
    between(longitude, -117.46, -116.86) 
     & between(latitude, 32.41, 33.01) ~ 'San Diego',
    between(longitude, -120.07, -119.47) 
     & between(latitude, 36.44, 37.04) ~ 'Fresno',
    between(longitude, -121.57, -120.97) 
     & between(latitude, 37.66, 38.26) ~ 'Stockton',
    TRUE ~ 'Other areas'
    )
  ) %>%
  mutate_if(is.character,as.factor)

我们观察结果:

df %>%
  filter(area != "Other areas") %>%
  ggplot(aes( x = longitude, y = latitude)) +
  geom_point()

图片作者。

ggforce提供不同的功能来高亮显示数据集。人们可以围绕具有不同形状的数据组画出轮廓:

  • 圆圈:geom_mark_circle()
  • 省略号:geom_mark_ellipse()
  • 矩形:geom_mark_rect()
  • 外壳(凸闭包):geom_mark_hull()

我们可以试着在地理区域周围画一个矩形:

df %>%
  filter(area != "Other areas") %>%
  ggplot(aes(x = longitude, y = latitude, color = area)) +
  geom_mark_rect(aes(fill = area), concavity=10) +
  geom_point()

图片作者。

我们可以尝试用geom_mark_hull()画更复杂的多边形。可以通过concavity参数调整最终船体的凹度:

df %>%
  filter(area != "Other areas") %>%
  ggplot(aes(x = longitude, y = latitude, color = area)) +
  geom_mark_hull(aes(fill = area), concavity=10) +
  geom_point()

图片作者。

可以向图上的组添加标签:

df %>%
  filter(area != "Other areas") %>%
  ggplot(aes(x = longitude, y = latitude, color = area)) +
  geom_mark_hull(aes(fill = area, label = area), concavity = 10) +
  geom_point()

图片作者。

我们也可以将图与来自ggmap的地理信息结合起来,如下所示:

df %>%
  filter(area != "Other areas") %>%
  qmplot(x = longitude, y = latitude, data = .) +
  geom_mark_hull(aes(fill  = area, label = area), concavity = 10) +
  geom_point()

图片作者。

更多信息和进一步的例子可以在这里找到⁵.

3.结论

r 是一个强大的数据分析和可视化工具。

在这篇文章中,我们的目标是而不是分享完整的数据分析工作或机器学习问题的解决方案。相反,我们想探索一些能够简化创建漂亮可视化的任务的包。

参考文档提供了更多示例和信息。

关于图形的语法和ggplot2背后的概念的更多见解,我们推荐 Hadley Wickham 的“gg plot 2——数据分析的优雅图形”。

4.参考

[1] Hadley Wickham,“ ggplot2 —用于数据分析的优雅图形”,Springer,2009 年(公共链接)。

[2]利兰·威尔金森,“图形的语法”,斯普林格,2005。

[3]https://www . ka ggle . com/datasets/camnugent/California-housing-prices

https://creativecommons.org/publicdomain/zero/1.0/

https://cran.r-project.org/package=ggmap

[6]https://www . rdocumentation . org/packages/gg map/versions/3 . 0 . 0/topics/QM plot

[7] David Kahle 和 Hadley Wickham, ggmap:用 ggplot2 实现空间可视化,《R Journal》第 5/1 卷,2013 年 6 月,链接

https://cran.r-project.org/package=ggpubr

[9]https://www . rdocumentation . org/packages/gg pubr/versions/0 . 4 . 0/topics/gg violin

[10]https://www . rdocumentation . org/packages/gg pubr/versions/0 . 4 . 0/topics/stat _ compare _ means

https://rpkgs.datanovia.com/ggpubr/

https://cran.r-project.org/package=patchwork

[13]https://patchwork . data-imaginist . com/articles/guides/assembly . html

https://cran.r-project.org/package=ggforce

[15]https://ggforce.data-imaginist.com/

使用 BigQuery 统计聚集函数和 Looker 创建控制图

原文:https://towardsdatascience.com/create-control-charts-using-bigquery-statistical-aggregate-functions-and-looker-2808c14fc102

另一种监控 KPI 目标的方法

亨利&公司在 Unsplash 上拍摄的照片

检查图

统计

“一种图表,在其上绘制一个变量的观察值,通常对照该变量的预期值及其允许偏差,以便在质量、数量等方面的过度变化。变量的,可以被 检测到1

介绍

在我的本科学习期间,一门颇有见地的大学课程是“质量保证简介”。从本质上讲,这是另一个以不同名称包装的统计学课程,但旨在传授跟踪生产过程质量的技术和仪器知识。

其中, 控制图 就是这些工具之一,因为它们显示了以下内容:

  • 过程度量如何发展,
  • 如果工艺措施在预期控制范围内,
  • 应该改进哪些措施来提高生产过程质量。

换句话说,它们是过程图片,通过为观察测量设置以下元素来评估过程质量:

  • 中线 —测量的平均历史值,
  • 控制上限(UCL) —在中心线上方设置三个标准偏差 (+3σ),以及
  • 控制下限(LCL) —在中心线以下设置三个标准偏差(-3σ)。

并且这三个要素易于计算,可用于跟踪各业务领域而不仅仅是生产领域的持续 KPI 的目标。

也就是说,这篇文章旨在展示控制图的实现方法,使用 BigQuery 统计聚合函数来计算标准偏差并找到 UCL 和 LCL 值。

此处介绍的工作是之前使用 BigQuery ML 和硬编码 Looker 方法设置 KPI 目标的后续工作:

与前一篇文章类似,数据将通过 Looker 实例进行建模和可视化。

我们开始吧。😃

用例解释

过去的工作中,我们重点介绍了跟踪marketing costs[cost revenue ratio](https://www.indeed.com/career-advice/career-development/cost-revenue-ratio#:~:text=What%20is%20a%20cost%20revenue,including%20marketing%20and%20shipping%20costs.)[return on ad spend](https://www.klipfolio.com/metrics/marketing/return-on-ad-spend-roas)KPI 的方法。

现在,我们将关注[Cost per Order (CPO)](https://www.klipfolio.com/resources/kpi-examples/ecommerce/cost-per-order) KPI,并展示一种创建统计控制图以跟踪CPO值的方法。

在我们开始解释新开发之前,应该提到以下实施说明:

  • 从业务角度来看,Cost per order KPI 的未来值不应该偏离其历史值太多,这就是为什么用统计控制极限来监控其目标是可行的。
  • 多系列数据集将再次用于开发。
  • 开发的重点放在方法而不是数据集上,并且底层数据源不会被共享

开发方法学

下图展示了 Looker 中控制图的数据建模和可视化的三步方法。

开发方法的分解[图片由作者提供]

第一步,我们将扩展现有的 LookML 视图,对CPO度量进行新的计算。在此步骤之后,我们将计算CPO控制上限和下限。最后,我们将可视化的见解,并提出了一个在 Looker 创建控制图的教程。

前两个步骤是为 Looker 开发者设计的,而最后一个步骤是为商业用户设计的。

步骤 1/3: LookML 建模:使用 CPO 计算扩展现有视图,并开发一个用于计算统计控制限值的新视图

第一步的任务很简单:

  • CPO计算扩展视图T2。
  • 创建一个新视图[cpo_targets](https://gist.github.com/CassandraOfTroy/6d375f59431d0a6bd579e00d33c26266),用于计算统计控制上限和下限。

计算CPO的公式是实际绩效营销成本与观察时间段内创建的订单总数的比率:

measure: cost_per_order { 
  label: “CPO Actual” 
  description: “Cost per Order = PM Costs / Total Order Number”                          
  type: number 
  value_format_name: eur
  sql: ${pm_costs_actual}/NULLIF(${total_number_of_orders},0);; 
}

在我们创建了一个新的度量之后,接下来是从现有数据模型[marketing_control_charts](https://gist.github.com/CassandraOfTroy/494188438a739c098b3f8adefc72a5c6#file-marketing_control_charts-model)开发一个派生表视图。

我们将从模型中选择所需的维度度量,并使用 BigQuery 统计函数创建一个派生列来计算CPO标准偏差。

新创建的视图的功能可以总结如下:

  • 我们使用了 STDDEV_SAMP 统计聚合函数,它返回值的样本(无偏)标准偏差。
  • 除了计算标准偏差之外,我们还计算了CPO平均值,从这两个测量值中,我们计算了 UCL 和 LCL 值。
  • 测量值cpo_lcl_2sigmacpo_ucl_2sigma被计算以设置可能的CPO异常值的早期警报。

让我们进入下一步,展示如何用新的开发来扩展现有的数据模型。

步骤 2/3: LookML 建模:用新视图扩展现有的数据模型

在这一步,我们创建了一个新的数据模型版本,即[marketing_control_charts_v2](https://gist.github.com/CassandraOfTroy/d52fd4c87b662afe469d3c37cd3bc20b),以显示旧模型[marketing_control_charts](https://gist.github.com/CassandraOfTroy/494188438a739c098b3f8adefc72a5c6#file-marketing_control_charts-model)之间的代码差异。

在新模型中,我们在两个视图上增加了一个join:

  • “旧”视图kpi_actual_vs_target——保存CPO实际值,以及
  • “新”视图cpo_target —包含CPO’s统计控制限值。

视图之间的连接是在序列标识符(shop_brandmarketing_shop_types)上创建的,而不是在date标识符上创建的,因为我们希望在不同的时间范围中灵活地观察统计控制限值和实际CPO值的变化。

有了这个解释,我们可以移动到最后一步。😃

第 3/3 步:观察者:创建控制图

见解展示环节终于开始了。利用之前开发的数据模型,业务用户可以探索 it 并创建控制图。

教程:

  • 选择过滤器:CPO Target DateOrder Date DateShop Brand
  • 选择维度和度量:Order Date DateShop BrandCPO ActualCPO AverageCPO LCLCPO UCLCPO 2 sigma LCLCPO 2 sigma UCL
  • 选择Table (Report)可视化类型来呈现结果。

Looker:监控 CPO 值的控制图[图片由作者提供]

功能性:

  • 通过上述设置,我们可以在数据集中为的每个商店(系列)每天跟踪CPO
  • 过滤器部分的两个日期,即CPO Target DateOrder Date Date,使我们能够跟踪CPO 目标日期的实际 CPO不可知(统计控制限)。例如,我们可以将当月的实际CPO值与上个月或去年同月的目标CPO值进行比较。
  • 2 西格玛 UCL 和 LCL 经过计算,可用于及早提醒业务用户可能的CPO异常值。

结论

这个职位的目标可以总结如下:

  • 开发目标是展示使用 BigQuery 统计函数和 Looker 实例创建控制图的方法。
  • 业务目标是为跟踪 KPI 记分卡提供一个简单的框架

总的来说,“大”目标是通过让最终用户尽早识别KPI 异常值来创造商业价值此外,提议的方法旨在减少采取行动的反应时间,以将 KPI 保持在控制范围内。

带着这些笔记,我们结束我们的帖子。一如既往…编码快乐!😃

参考文献:

[1]柯林斯字典,访问时间:2022 年 6 月 11 日,https://www . Collins Dictionary . com/Dictionary/English/control-chart

在 R 图中创建自定义布局

原文:https://towardsdatascience.com/create-custom-layouts-in-your-r-plots-eb7488e6f19f

哈尔·盖特伍德在 Unsplash 上拍摄的照片

当绘制数据时,我们通常试图创建一个单独的图。但是,如果您想创建一组相关情节的单一呈现,该怎么做呢?在 r 中有许多方法可以做到这一点。在本文中,我将向您展示其中的三种,使用我在一些#TidyTuesday 提交中创建的示例。完整的代码可以在我的 github 页面上找到。

基本多情节

作者图片

创建多图的最基本方法之一是使用 par()函数和" mfrow = "属性。在这个项目中,我们有六种不同类型水果的尺寸数据。我决定为它们分别创建直方图,但是我想把它们都放在一个图表中。

我的第一次尝试如上图。这完全是用 base R 图形做的。这些图是简单的 hist()函数,注释是用 lines()和 text()函数创建的。

制作多图的关键是在创建任何图之前运行以下代码块:

par(mfrow = c(2,3))

这将屏幕分为两行和三列,每个图将按顺序逐行填充屏幕。如果您想回到单图模式,只需再次运行该功能,在结束时进行重置即可:

par(mfrow = c(1,1))

这使我们回到一行一列。

这个情节的完整代码在 https://github.com/dikbrown/TTGiantPumpkins/blob/main/EDA.R

网格库

作者图片

对于这个#TidyTuesday 提交,我们只是被要求做一些映射。由于 WalletHub 最近刚刚发表了一篇关于美国多元化的文章,我决定看看我能利用这些数据做些什么。

该数据集包括六个主要类别的多样性排名,以及总体多样性排名。我决定为这些排名创建一个地图,其中“整体”多样性得分在顶部占据优先位置。

除了 ggplot2 和 usmap 库,我发现 grid 和 gridExtra 库在这里也很有帮助。这些库使您能够通过创建矩阵将屏幕分割成单独的窗口。

layout <- rbind(c(1, 2, 2, 2, 2, 3),
                c(1, 2, 2, 2, 2, 3),
                c(4, 4, 5, 5, 6, 6),
                c(7, 7, 8, 8, 9, 9)) 

由于刚刚创建的矩阵有四行六列,屏幕以同样的方式划分:

作者图片

但是,我们使用的数字允许我们定义哪些扇区将成为屏幕每个部分的一部分。例如,所有的“1”部分将组合成屏幕的一个部分,我们可以在那里放置一些东西。这将创建以下布局:

作者图片

现在我们有了布局,我们可以用 grid.arrange()函数把东西放进去。然而,我们只能放入某些类型的对象,例如图形对象(称为 grobs)或 ggplot 对象。如果我们只想在其中一个部分中放入文本,我们可以使用 textGrob()函数。上图中的线条是用 rectGrob()函数创建的。

textobj1 <- textGrob("This is some text.", gp = gpar(fontsize = 24), hjust = 1)
...
create some plots
...
grid.arrange(textobj1, plot1, textobj2, 
             plot2, plot3, plot4, plot5, plot6, plot7,
             layout_matrix = layout)

我们添加的不同对象将按照 grid.arrange()函数中指示的顺序进入上面的部分。因此,textobj1 进入第 1 部分,plot1 进入第 2 部分,以此类推。

我在本节开始创建地图的所有代码,以及布局图像,都可以在我的 GitHub 页面上找到,地址是https://github.com/dikbrown/TTMappingDiversity21

您可以在 https://bookdown.org/rdpeng/RProgDA/the-grid-package.html获得网格包的概述,并在https://stat . ethz . ch/R-manual/R-devel/library/grid/html/00 index . html找到文档

split.screen()

作者图片

split.screen()函数是 r 中基本图形包的一部分,用于将屏幕分割成不同的区域。此外,每个区域可以进一步划分成更小的区域(尽管这超出了本文的范围)。这种能力比前面提到的其他方法更加复杂。它也比其他任何一种方法更容易微调。我用这种方法重做了之前展示的巨型南瓜图片。

首先,我们创建一个布局,其方式类似于网格图形中的布局。然而,我们不是简单地标记不同的区域,而是使用布局矩阵来定义每个区域的边界。该矩阵将由四列组成。这些列表示 x1(左边缘)、x2(右边缘)、y1(下边缘)和 y2(上边缘)。对于要创建的每个区段,基准表中都有一行。以下是一些例子:

最简单的布局,只有一个部分:

layout <- matrix(c(0, 1, 0, 1),          # x1=0, x2=1, y1=0, y2=1
                  ncol = 4, byrow = TRUE)

作者图片

请注意,编号从左下方的(0,0)开始,正如您在笛卡尔坐标系中所期望的那样,右上角的坐标为(1,1)。

现在,假设您想将屏幕分成并排的两半。您可以使用:

layout <- matrix(c(0, 0.5, 0, 1,          # x1=0, x2=0.5, y1=0, y2=1
                   0.5, 1, 0, 1),         # x1=0.5, x2=1, y1=0, y2=1
                  ncol = 4, byrow = TRUE)

作者图片

相反,如果您想将屏幕分成上下两半,您可以使用以下布局:

layout <- matrix(c(0, 1, 0.5, 1,         # x1=0, x2=1, y1=0.5, y2=1
                   0, 1, 0, 0.5),        # x1=0, x2=1, y1=0, y2=0.5 
                  ncol = 4, byrow = TRUE)

作者图片

您应该意识到行的顺序不是固定的,但是跟踪它们的顺序是很重要的。例如,以下布局在质量上等同于第一个布局(即上半部分和下半部分):

layout <- matrix(c(0, 1, 0, 0.5,
                   0, 1, 0.5, 1),
                  ncol = 4, byrow = TRUE)

这里唯一的区别是,在第一个版本中,上半部分是第 1 部分,下半部分是第 2 部分,而在第二个版本中,情况正好相反。当你想把一些东西放在一个特定部分时,这就变得很重要了。出于这个原因,我强烈建议用注释标记这些行:

layout <- matrix(c(0, 1, 0.5, 1,          # top half
                   0, 1, 0, 0.5),         # bottom half
                  ncol = 4, byrow = TRUE)

或者

layout <- matrix(c(0, 1, 0, 0.5,        # bottom half
                   0, 1, 0.5, 1),       # top half
                  ncol = 4, byrow = TRUE)

让我们开始变得更好一点。让我们把顶部(2/3)做得比底部(1/3)大,而不是一模一样的两半

layout <- matrix(c(0, 1, 0.33, 1,        # top section
                   0, 1, 0, 0.33),       # bottom section
                  ncol = 4, byrow = TRUE)

我们还可以将屏幕垂直和水平分成两半:

layout <- matrix(c(0,   0.5, 0.5, 1,        # top-left section
                   0.5, 1,   0.5, 1,        # top-right section
                   0,   0.5, 0,   0.5,      # bottom-left section
                   0.5, 1,   0.5, 1),       # bottom-right section
                  ncol = 4, byrow = TRUE)

作者图片

为了创建本节开头所示的图,我决定要八个部分:六个部分用于六个图,顶部和底部的条用于额外的文本。标题和副标题在顶部,而引用信息(数据源和作者信息)在底部。通过这种方式,我可以非常简单地完全控制文本部分的格式。以下是我使用的布局:

layout <- matrix(c(0,    1,    0.9,  1,      #top strip for title
                   0,    0.33, 0.47, 0.9,    # row 1, plot 1
                   0.33, 0.67, 0.47, 0.9,    # row 1, plot 2
                   0.67, 1,    0.47, 0.9,    # row 1, plot 3
                   0,    0.33, 0.06, 0.47,   # row 2, plot 1
                   0.33, 0.67, 0.06, 0.47,   # row 2, plot 2
                   0.67, 1,    0.06, 0.47,   # row 2, plot 3
                   0,    1,    0,    0.04),  # bottom strip for citation
                 ncol = 4, byrow = TRUE)

总而言之,顶部的条代表屏幕的顶部 10%,底部的条代表屏幕的底部 4%。剩下的 86%的身高被一分为二(43%),这就是我得出 0.47 的数值(0.04 + 0.43)的原因。然后我给每个图 1/3 的水平空间(因此 0.33 和 0.67 作为分界点)。

现在,要填充这些部分,您必须添加一些代码来告诉系统如何使用布局矩阵,并指示您的输出(绘图或文本)将去往何处。

# tell the system to use the layout matrix to split the screen
split.screen(layout)      

# screen(#) tells the system to put the upcoming output in section #
screen(1)                 
text("Overall title of plot", formatting parameters)

screen(2)  # use section 2: row 1, plot 1 from layout
hist(data, parameters)  # add a histogram to section 2 

...

screen(8)
text("Other text to be added", parameters)

# The next step is very important, or you will have 
# issues trying to re-run the code

close.screen(all = TRUE)

创建上述情节的完整代码位于https://github . com/dik brown/TTGiantPumpkins/blob/main/Distribution。R

值得注意的是,这些不同的技术并不总是相互兼容。你一定要阅读每个包装上的警告。例如,您会发现以下一些问题:

  1. split.screen()与其他排列地块的方法不兼容,如 par(mfrow)和 layout()。
  2. 网格图形和常规图形不一定能很好地配合。
  3. par(mfrow)函数根本不适用于 ggplot2 图形。但是,ggplot2 中的 geom_facet()函数会做一些类似的事情。

如果你觉得这篇文章有用,请关注我,看看我的其他文章,包括:

用 Python 创建数字全息图

原文:https://towardsdatascience.com/create-digital-holograms-in-python-with-holopy-eacacad989ae

用 HoloPy 探索数字全息图的世界——用于计算光散射和数字全息术的 python 包

为了看到全息图,前后移动你的头,聚焦在中心点上至少五次。它在桌面显示器或笔记本电脑上显示得更好。图片由作者提供,使用 Holopy。

全息图是用激光制作的一种特殊类型的图像,其中平面物体看起来是固体,就好像它们是真实的一样。随着计算机科学的最新进展,我们可以通过编程从一张图片中重建全息图,而无需使用物理技术(数字传感器、照相机、激光等)。)

似乎数字全息术意味着人类通信许多领域的未来。Meta 的首席执行官马克·扎克伯格解释了流入增强现实的巨额资金,并预测了全息图是其不可或缺的一部分的未来:

“在未来,你将能够坐在我的沙发上,或者我将能够坐在你的沙发上,而不只是通过电话来做这件事,这将实际上感觉我们在同一个地方,即使我们在不同的州或相距数百英里。我认为那真的很强大。”( BBC 新闻,2021 )。

此外,谷歌最近推出了项目 Starline ,一个类似全息图的视频聊天工具。想象一下,透过一个全息窗口,透过那个窗口,你看到的是另一个三维的人。你可以说话、做手势、眼神交流,就好像那个人就在你面前一样。Starline 项目目前只在谷歌的几个办公室可用,它依赖于定制的硬件,但我们可以预计它很快会扩展到标准的谷歌产品。

这篇文章属于这一类。它将为数字全息术提供非常实用的介绍,并介绍 HoloPy,一种基于 python 的处理数字全息图的工具。我们可以在舒适的办公室里编写全息图,而不是建造复杂的机器。

全息照相术:Python 环境中的数字全息术

HoloPy 是由哈佛大学马诺哈兰实验室打造的计算光散射和数字全息软件包。它提供了管理元数据、从 2D 图像重建全息图、将散射模型拟合到数字全息图以及许多其他应用的工具。

Barkley 等人(2020)在中详细描述了全息照相背后的理论。

全息图示例

我们用 HoloPy 准备一些简单的全息图。经过一些编辑,它们是从 HoloPy 文档中改编而来的。下面这段文字包括(a) python 实现和(b)创建高分辨率全息图。

你是怎么看到全息图的?

  1. 用鼠标缩小,直到图片变小
  2. 慢慢放大,直到隐藏的图案开始出现
  3. 当图像有正常尺寸时,来回移动头部几次,以查看更多新的隐藏动机。

全息图至少可以在 14ʺ大小的桌面显示器或笔记本电脑上清晰可见。下面是实现过程:

一个简单的代码产生一个灰度全息图:

全息图在台式显示器或笔记本电脑上显示得更好。图片由作者提供,使用 Holopy。

下一个例子是一个缺乏一些背景动机的单中心彩色全息图。这是 python 代码,后面是全息图像:

图片由作者提供,使用 Holopy。

Holopy 提供了不同的球体、颜色、参数和理论来重建全息图,进行散射计算,并根据数据拟合模型,这确实值得进行实验。

结论

这篇文章用 python 提供了一个数字全息术的实用介绍。全息图在其他领域也有许多应用。例如,在市场营销中, Tohana (2020)Ardolf (2021) 强调了全息营销蓬勃发展的三个原因:

  • X-Factor–全息图具有突破噪音的独特方式
  • 可展示的体验的——它们提供了真实的生活体验
  • 难忘的难忘的经历可以与朋友和家人分享。

数字全息是一门广泛的科学,其概述超出了本文的范围。该领域发展非常迅速,研究和创新在学术界和私营部门受到优先重视。一个激动人心的例子是苏塞克斯大学的科学家 T21 正在开发一种 3D 图像,它可以移动,重现声音,甚至模仿触觉。

所有这些进步看起来都很有希望。自然地,任何 3D 图像都会被拿来与星球大战全息图相比较。但是,目前的技术创造的图像仍然很小,远远没有达到照片级的真实感。然而,Gibney (2019)在《自然》杂志的一篇文章中持乐观态度,并引用了北京航空航天大学的琼的话:

“以任何方式创造《星球大战》中的那种 3D 效果可能需要十年,甚至更长时间”( 吉布尼,2019 )。

这些预测非常激动人心,值得期待。

PS:你可以订阅我的 邮件列表 在我每次写新文章的时候得到通知。如果你还不是中等会员,你可以在这里加入https://medium.com/@petrkorab/membership。****

参考资料:

[1]阿尔多夫,M. (2021)。全息营销-数字营销的未来。2021 年 8 月 16 日。检索自:https://youdontneedwp . com/mathewardolf/hologram-marketing-the-future-of-digital-marketing

[2] Barkley,s .,Dimiduk,t .,g .,Fung,j .,Kaz,d .,m .,Manoharan,v .,n .,McGorty,r .,Perry,r .,w .,Wang,A. (2020 年)。用 Python 和 HoloPy 实现全息显微术。科学中的计算&工程,第 22 卷,第 5 期。**

[3] BBC 新闻(2021)。扎克伯格希望脸书成为网上的“元宇宙”。2021 年 7 月 23 日,英国广播公司新闻处。从https://www.bbc.com/news/technology-57942909取回。

[4]吉布尼,E. (2019)。由一粒泡沫创造的星球大战风格的 3D 图像。自然, 575,第 272–273 页。检索自:https://www.nature.com/articles/d41586-019-03454-y**

[5]托哈纳,L. (2020)。全息图的优势及其对未来营销的意义。营销学院的未来,2020 年 9 月 23 日。检索自:https://futureofmarketinginstitute . com/advantages-of-holograms-and-it-means-for-the-future-of-marketing/**

使用 Julia 的 Unicode 绘图创建快速、通用的可视化效果

原文:https://towardsdatascience.com/create-fast-versatile-visualizations-with-julias-unicode-plots-8750bb7d7450

Julia 的 UnicodePlots.jl 包中令人敬畏的特性概述

(图片由作者提供)

介绍

在很多方面,那些从事更多计算领域和数据科学的人将自己与其他程序员区分开来。首先,我们通常不会一次性编译我们的工作。我们更希望有某种程度的互动,因为你需要从你的数据中获得持续的反馈,以便真正理解它。这当然也是笔记本这个创意的来源。然而,虽然这些笔记本电脑从表面上看是一个很好的解决方案,但它们也带来了一些挑战。

Jupyter 笔记本经常提到的一个重要问题是状态。许多人试图解决这一问题,像 Pluto.jl 这样的项目试图通过使环境对实际查看的代码更具反应性来缓解这些问题。另一个重要的问题是,Jupyter 内核的局限性很大,当达到极限时,有时会令人沮丧。一遍又一遍重启死内核很烦。

然而,这些笔记本电脑的一个非常大的好处是,数据科学家经常想要在网络浏览器中查看他们的输出。在某些情况下,这有点像试图在没有浏览器的情况下测试新的 JavaScript 网站,而试图在浏览器之外进行数据科学研究。至少,对于许多分析任务来说是这样的——更具体地说,是可视化。说到数据可视化,Julia 确实有一些相当健壮和有用的图形库,在大多数情况下,我已经在我的博客上查看了几乎所有的图形库。如果你想看看我过去写的故事的模块的比较,这里有一个文章的链接,我在这里做了一个概述,并对 Plots.jl 和 Vegalite.jl 这样的模块做了比较:

鉴于笔记本电脑存在的所有问题,大多数数据科学家可能已经考虑彻底抛弃它们。REPLs 虽然不是地球上最具可复制性的东西,但实际上工作得相当好——而且有一些 IDE 提供了数据科学家经常渴望的那种反应式编程,所以为什么不抛弃笔记本,尝试一些新的东西呢?今天,我们将在 Julia 中测试 REPL 的潜力,尝试使用一个名为 UnicodePlots.jl 的强大软件包将我们的数据可视化移回 REPL。除了强大之外,这些可视化对于那些可能会暂时或永久绑定到终端的人来说也非常有用。然而,以我的读者能够非常容易地复制我的代码(并在他们的 web 浏览器中查看输入/输出)的名义,我将在下面的例子中在笔记本中使用这个库。如果您想查看此笔记本,请点击以下链接:

https://github.com/emmettgb/Emmetts-DS-NoteBooks/blob/master/Julia/Unicode Plotting.ipynb

基础知识

UnicodePlots 的基础可能是您对大多数不以 Unicode 编码的图形库的期望。这很好,因为它增加了一种熟悉程度。此外,将方法和参数与 Julia 中的许多其他解决方案进行比较,我觉得 UnicodePlots 有许多更容易进行和阅读的调用。当然,这都是主观的——但我的观点是,作为一个有经验的可视化库用户,我非常喜欢 UnicodePlots 的界面设计方式。让我们为我们的绘图示例制作一些快速数组:

x = randn(500)
y = randn(500)

关于许多其他的 Julia 可视化库,我不喜欢的一点是,它们需要大量的工作来获得第一个图,此外,对于它们中的许多来说,“第一个图的时间”真的很长。当然,由于预编译的原因,这是大部分时间,Julia 通常需要编译几个后端,无论是 SDL、Cairo 还是其他,这肯定会增加为数据绘图所需的时间。然而,这些图是 unicode 的,所以这里唯一的可视化库是显示管理器或内核向我们显示的文本。结果,绘图真的很快。该库还关注简单的位置参数,但功能丰富的关键字参数—这在大多数软件情况下不是您想要的,但在绘图的情况下却是最佳的。

plot = lineplot(x, y,
               title = "Unicode Plot", xlabel = "x", ylabel = "y", border = :dotted)

作者图片

基本的线条画看起来确实很棒。这些图的可读性真的很好,因为有时你可能不希望它们像这样超级容易评估。更酷的是,如果你在电脑上,我可以把它粘贴到 Medium 上的一个文本框中,看起来太酷了!!!

⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀**Unicode Plot**⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀          ⡤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⢤        3 ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸          ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠈⠢⡀⠀⡀⠀⡜⣿⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸          ⡇⠀⠀⠐⢤⣄⡀⠀⢀⠀⢢⢌⡢⣏⣲⣁⣧⢴⣿⣑⣄⣀⡠⢤⣴⢢⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸          ⡇⠀⠀⠀⠀⠈⠪⡑⠺⣷⣖⣿⣿⣿⣷⣿⣿⡾⣿⣉⣹⣡⣜⣿⣾⣳⠬⢵⣦⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸          ⡇⠀⠀⠀⠀⠀⠀⢈⣵⣯⣾⡿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣋⣯⣟⣏⣉⣉⡩⠭⠶⠶⠒⠒⠀⠀⠀⠀⢸          ⡇⠀⠀⠀⠀⠀⣀⣼⣻⣹⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣭⢤⣖⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸          ⡇⠀⠀⠀⠀⢀⣁⡠⣷⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⣿⣿⣿⡯⡿⣛⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸     y    ⡇⠤⠤⠤⠭⠷⠶⢼⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣽⣿⣿⣷⣤⠤⠤⠵⠤⡤⠤⠤⠤⠤⠤⠤⠤⢸          ⡇⠀⠀⠀⢀⠤⠤⡞⡾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣽⣍⣙⣍⣉⣉⣉⣀⣀⡀⠀⠀⠀⠀⢸          ⡇⠀⠀⠀⠀⠑⢤⡿⠶⣿⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⣿⣗⠊⠑⠤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸          ⡇⠀⠀⠀⠀⠀⡸⣣⣠⠴⢟⣻⣿⣿⡿⣿⣿⣿⣿⣿⣿⣿⢿⣿⣿⣿⡷⠌⠙⠉⠒⠚⠳⠄⠀⠀⠀⠀⠀⠀⠀⢸          ⡇⠀⠀⠀⠀⣀⠝⠋⠉⠫⡡⣯⣿⣏⣯⣿⡿⣿⡿⠒⠻⣿⡾⠿⢏⠉⠞⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸          ⡇⠀⠀⠀⠘⠊⠉⠉⢩⠿⠛⢫⢋⡷⡟⠀⢀⢎⡧⠒⠉⠹⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸          ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⠕⠁⠀⠀⠀⠚⠁⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸       -3 ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸          ⠓⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠚          ⠀-3⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀4⠀          ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀x⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀

你可能会想,与 unicode 相比,这样的东西在 ASCII 中是多么的可能。不管怎样,这看起来很棒。然而,正如您所料,这并不是 UnicodePlots 能够产生的唯一东西。

可变地块

这就是 Unicode 比许多其他绘图库更聪明的地方。库中的图实际上是可变的,这意味着我们可以在刚刚创建的这个新图中添加或删除元素。比如我们可以用线图!()方法来添加另一行:

lineplot!(plot, randn(500), randn(500), color = :blue)

作者图片

探索更多可视化

这可能令人惊讶,但是这个库实际上能够实现很多非常酷的可视化,例子包括

  • 散点图
  • 楼梯图
  • 条形图
  • 柱状图
  • 箱线图
  • 稀疏模式
  • 密度图
  • 热图图
  • 等值线图表
  • 表面图
  • 等值面图

没错,甚至还有 3D 图可用。虽然三维图是相当新的,它是相当令人印象深刻的,结果看起来相当可怕。下面是一个使用曲面图制作的示例:

sombrero(x, y) = 15sinc(√(x^2 + y^2) / π)
surfaceplot(-8:.5:8, -8:.5:8, sombrero, colormap=:jet, border=:dotted)

作者图片

还有非常酷的等值面图:

torus(x, y, z, r=0.2, R=0.5) = (√(x^2 + y^2) - R)^2 + z^2 - r^2
isosurface(-1:.1:1, -1:.1:1, -1:.1:1, torus, cull=true, zoom=2, elevation=50, border=:dotted)

作者图片

结论

jl 是一个非常棒的图形库,我怎么推荐都不为过。对于探索性分析,与调用真正的图形 API 绑定相比,使用类似 unicode 的东西进行绘图具有很多优势。性能和预编译可能是最大的。此外,我真的很喜欢这种方法,这种可变性,以及这个包的总体设计。

与 Julia 中的许多其他选项相比,UnicodePlots.jl 是一个显而易见的选项。我强烈建议把它捡起来,即使只是为了新奇。该库能够产生一些非常激进的可视化效果,在大多数情况下,我认为这些可视化效果会覆盖你!非常感谢您的阅读,我希望您考虑将此模块添加到您的朱利安科学计算团队中!

在 20 分钟内创建地理图像数据集

原文:https://towardsdatascience.com/create-geo-image-dataset-in-20-minutes-4c893c60b9e6

构建 LAION-5B 的地理特定子集

丹尼斯·库默Unsplash 拍摄的照片

LAION-5B 简介

大规模人工智能开放网络(LAION)是一个非营利性组织,旨在向公众提供机器学习资源。最近,LAION 发布了一个从互联网收集的 58.5 亿个图文对的数据集。LAION-5B 数据集包含 URL、文本以及 KNN 索引。

KNN 索引为一个名为剪辑检索的搜索引擎提供动力,使用户能够交互式地探索 LAION-5B 数据集。剪辑检索提供了一个 anUI 和一个 API 来查询带有文本、图像或嵌入向量的 LAION。剪辑检索使得创建原始 LAION-5B 图像数据集的特定任务子集变得容易。

使用剪辑检索 UI 构建的 LAION 搜索网络界面

[ClipClient](https://github.com/rom1504/clip-retrieval/blob/main/notebook/clip-client-query-api.ipynb) API Example

先前关于介子子集的工作

LAION 数据集以及剪辑检索为生成特定领域的 LAION 子集提供了巨大的机会。这些领域特定的子集然后可以用于训练任务特定的模型。其他人已经探索了这种方法来创建以下子集-

LAION-FaceLAION-400M 的人脸子集,它由 5000 万个图文对组成。进行人脸检测是为了找到带有人脸的图像。LAION-Face 随后被用作 FaRL 的训练集,为人脸分析任务提供强大的预训练变压器骨干。

LAION Aesthetic 是 LAION-5B 的一个子集,已经通过在被判断为美学的图像的剪辑嵌入顶部训练的模型进行了估计。其预期目标是能够创建图像生成模型。

LAION 高分辨率是 LAION-5B 的一个> = 1024x1024 的子集,它有 170M 个样本。该数据集的预期用途是训练超分辨率模型。

里昂地理子集

鉴于 LAION 5B 中存在的规模和多样性,我们希望了解创建 geo 子集的可行性。地理子集可用于训练地理标记、地标检测等的模型。创建子集的流水线大致包括三个阶段,

  • 生成一组位置关键词
  • 使用剪辑检索 API 检索图像
  • 执行探索性分析

位置关键词数据集:为了获得高质量的位置关键词,我们利用 Instagram 的探索位置页面。为了构建概念验证,我们将位置关键字限制在美国>(纽约市)中城东部。然后使用 JavaScript 代码片段下载位置关键字。这给了我们 1096 个位置关键字的列表。

Instagram 位置探索页面

使用 ClipClient 检索图像:使用 clip-retrieval,我们可以为每个关键字检索图像。剪辑客户端有以下参数:

  • backend_url:后台的 url
  • indice_name:指定要使用的索引的名称
  • aesthetic_score:由审美检测器评定的审美分数
  • use_mclip:是否使用多语言版本的剪辑,默认为 False
  • aesthetic_weight:审美得分的权重
  • modality:在索引中搜索图像或文本
  • num_images:从 API 返回的图像数量
  • deduplicate:是否通过图像嵌入对结果进行去重,默认为真
  • use_safety_model:是否删除不安全图像,默认为真
  • use_violence_detector:是否删除暴力图片,默认为真

为了获得最相关的结果,我们对 clip 客户端使用以下配置:

剪辑客户端配置

例如,我们通过向关键字添加上下文来创建搜索字符串。从关键字' grand central terminal '中,我们创建搜索字符串'grand central terminal in midtown east,united states '。我们遍历搜索字符串列表,为每个关键字检索多达 1000 张图片。

检索关键字的图像

执行探索性分析:从 clip 客户端检索所有位置关键字的图像后,我们目测检查一些结果,以执行基本的健全性检查。

中央车站的结果

我们在目视检查一些结果后进行探索性分析。对于 1096 个位置关键字,我们总共有 484,714 个图像,平均每个位置关键字有 513.47 个图像和 540.5 个图像的&中值。图片数量最多的前 20 个地点大多是餐馆。

数据集中的属性域分布高度倾斜。总共有44992 个域,但是数据集中所有图像的 37.88% 仅来自前 20 个域。

前 20 个域

结论和未来工作

我们构建了 LAION-5B 的美国中城东(纽约市)子集,并对数据集进行了探索性分析。该数据集可用于训练地理标记、地标检测模型。您可以通过运行 colab 笔记本中的代码来重现结果。

未来,我们计划扩展到美国的所有地理位置,通过比较现有地理模型的性能来验证结果,并发布带有验证分数的数据集。

如果你觉得我们的工作有帮助,请考虑引用

@article{LAION-geo,
  title={Create geo image dataset in 20 minutes},
  author={Bhat, Aaditya and Jain, Shrey},
  journal={Towards Data Science},
  year={2022},
  url={[https://towardsdatascience.com/create-geo-image-dataset-in-20-minutes-4c893c60b9e6](/create-geo-image-dataset-in-20-minutes-4c893c60b9e6)}
}

使用 TensorFlow 在 10 分钟内创建图像分类模型

原文:https://towardsdatascience.com/create-image-classification-models-with-tensorflow-in-10-minutes-d0caef7ca011

端到端教程—从数据准备到模型训练和评估

Anton Maksimov 5642.su 在 Unsplash 上的照片

机器学习生命周期是一个复杂的过程,涉及许多不同的部分,包括:

  • 预处理和数据清洗。
  • 寻找和建立正确的模型。
  • 测试和适当的评估。

本文将上述所有技术打包成一个简单的教程:

如果我对深度学习Tensorflow 一无所知,这就是我希望有人给我看的!

先决条件:你需要 Tensorflow 2.0+和几个库——Numpy熊猫Sklearn、Matplotlib 。我们将使用 Tensorflow 中包含的时尚 MNIST[1] 数据集。

加载数据

数据集包含训练集中的 60,000 幅灰度图像和测试集中的 10,000 幅图像。每张图片代表属于 10 个类别之一的时尚单品。图 1 中给出了一个例子:

fashion_mnist = tf.keras.datasets.fashion_mnist(train_images, train_labels), (test_images, test_labels) = fashion_mnist.load_data()class_names={ 0: 'T-shirt/top',
              1: 'Trouser',
              2: 'Pullover',
              3: 'Dress',
              4: 'Coat',
              5: 'Sandal',
              6: 'Shirt',
              7: 'Sneaker',
              8: 'Bag',
              9: 'Ankle boot' }plt.figure(figsize=(10,10))
for i in range(25):
   plt.subplot(5,5,i+1)
   plt.xticks([])
   plt.yticks([])
   plt.imshow(train_images[i], cmap=plt.cm.binary)
   plt.xlabel(class_names[train_labels[i]])
plt.show()

图 1: 来自数据集的图像样本

我们的目标是建立一个模型,正确预测每个图像的标签/类别。于是,我们有了一个多类,分类问题。

培训/验证/测试分割

我们已经有了训练测试数据集。我们保留 5%的训练数据集,我们称之为验证数据集。这用于超参数优化。

train_x, val_x, train_y, val_y = train_test_split(train_images, train_labels, stratify=train_labels, random_state=48, test_size=0.05)(test_x, test_y)=(test_images, test_labels)

像素重新缩放

由于图像是灰度,所有值都在0–255的范围内。我们除以 255 ,使像素值位于 01 之间。这是常态化的一种形式,以后会加快我们的训练进程。

# normalize to range 0-1
train_x = train_x / 255.0
val_x = val_x / 255.0
test_x = test_x / 255.0

目标值的一键编码

每个标签属于我们上面看到的 10 个类别中的一个。因此,目标值(y)取 0 到 9 之间的值。比如根据字典class_names ,【0】,就是,【t 恤/上衣】,的类。

让我们看看训练集中前 5 件衣服的目标值:

train_y[:5]
-> array([[2],                  (Pullover)
          [8],                  (Bag)
          [6],                  (Shirt)
          [1],                  (Trouser)
          [.3]], dtype=uint8).  (Dress)

然后,我们对它们进行一次性编码——将每个目标值分配给一个向量。对所有 y 数据集(训练、验证、测试)进行该过程。我们使用to _ categorial()函数:

train_y = to_categorical(train_y)
val_y = to_categorical(val_y)
test_y = to_categorical(test_y)

因此,前 5 件衣服的目标值变为:

array([[0., 0., 1., 0., 0., 0., 0., 0., 0., 0.],        
       [0., 0., 0., 0., 0., 0., 0., 0., 1., 0.],        
       [0., 0., 0., 0., 0., 0., 1., 0., 0., 0.],        
       [0., 1., 0., 0., 0., 0., 0., 0., 0., 0.],        
       [0., 0., 0., 1., 0., 0., 0., 0., 0., 0.]], dtype=float32)

概述

每个数据集存储为一个 Numpy 数组。让我们检查一下它们的尺寸:

print(train_x.shape)  #(57000, 28, 28)
print(train_y.shape)  #(57000, 10)
print(val_x.shape)    #(3000, 28, 28)
print(val_y.shape)    #(3000, 10)
print(test_x.shape)   #(10000, 28, 28)
print(test_y.shape)   #(10000, 10)

训练分类模型

现在,构建我们的模型的一切都准备好了:我们将使用两种类型的神经网络:经典的多层感知器 ( MLP )和卷积神经网络 ( CNN )。

多层感知器(MLP)

标准神经网络架构如图 1 中所示。具有至少一个隐藏层和非线性激活的 MLP 的行为类似于通用连续函数逼近器。像所有的神经网络一样,它们基于斯通-维尔斯特拉斯定理:****

E 定义在闭区间上的非常连续函数[ ab ]可以用一个多项式函数按所希望的那样一致逼近。

当然,神经网络的每一层都将输入的多项式表示投射到不同的空间

图 1 :具有一个隐藏层的多层感知器[2]

接下来,让我们使用来自 TensorflowKeras API 来定义我们的模型。

model_mlp = Sequential()
model_mlp.add(Flatten(input_shape=(28, 28)))
model_mlp.add(Dense(350, activation='relu'))
model_mlp.add(Dense(10, activation='softmax'))print(model_mlp.summary())
model_mlp.compile(optimizer="adam",loss='categorical_crossentropy', metrics=['accuracy'])

你应该知道的是:

  • 网络结构:每幅图像是28x28个像素。第一层将输入展平成一个28*28=784大小的向量。然后,我们添加一个有 350 个神经元的隐藏层。最后一层有 10 个神经元,一个用于我们数据集中的类。
  • 激活功能:****隐藏层使用标准 RELU 激活。最后一层使用 softmax 激活,因为我们有一个多类问题。
  • ****损失函数:我们的模型试图最小化的目标。由于我们有一个多类问题,我们使用categorical_crossentropy损失。
  • ****度量:在训练期间,我们监控准确性:即,我们正确分类的实例的百分比。
  • ****时期:模型在训练期间遍历整个数据集的次数。

这是我们网络的结构:

_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 flatten (Flatten)           (None, 784)               0         

 dense (Dense)               (None, 350)               274750    

 dense_1 (Dense)             (None, 10)                3510      
                                                               =================================================================
Total params: 278,260
Trainable params: 278,260
Non-trainable params: 0

尽管这是一个初学者友好的教程,但是有一个重要的特性你应该知道:

神经网络容易过度拟合:有可能从训练数据中学习得如此之好,以至于它们可能无法对新的(测试)数据进行归纳。

如果我们让网络无限地训练,过拟合最终会发生。由于我们无法确切知道神经网络需要多长时间才能开始过度拟合,我们使用一种称为 提前停止的机制。

提前停止 监控训练期间的确认损失。如果验证损失在指定的时期(称为耐心)内停止下降,训练立即停止。让我们在实现中使用它:

early_stop=EarlyStopping(monitor='val_loss', restore_best_weights= True, patience=5, verbose=1)callback = [early_stop]

最后,我们训练我们的模型:

history_mlp = model_mlp.fit(train_x, train_y, epochs=100, batch_size=32, validation_data=(val_x, val_y), callbacks=callback)

****图 2:MLP 模型的训练历史

对于这个简单的数据集,我们使用了大量的时段(100)来证明提前停止在时段 10 被激活,并恢复了最佳权重(具体来说,在时段 5)。

现在,这里最重要的指标是 损失准确性 :让我们把它们形象化在一个图中。我们定义 plot_history 函数:

# define the function:
def plot_history(hs, epochs, metric):
    plt.rcParams['font.size'] = 16
    plt.figure(figsize=(10, 8))
    for label in hs:
        plt.plot(hs[label].history[metric], label='{0:s} train {1:s}'.format(label, metric), linewidth=2)
        plt.plot(hs[label].history['val_{0:s}'.format(metric)], label='{0:s} validation {1:s}'.format(label, metric), linewidth=2)
    plt.ylim((0, 1))
    plt.xlabel('Epochs')
    plt.ylabel('Loss' if metric=='loss' else 'Accuracy')
    plt.legend()
    plt.grid()
    plt.show()plot_history(hs={'MLP': history_mlp}, epochs=15, metric='loss')
plot_history( hs={'MLP': history_mlp}, epochs=15, metric='accuracy')

****图 3:MLP 的培训和验证损失

****图 4:MLP 的训练和验证精度

两个图都显示了度量标准的改进:损失减少了,而准确性提高了。

如果模型被训练了更多的时期,训练损失将继续减少,而验证损失将保持不变(甚至更糟,增加)。这将导致模型过度拟合。

最后,让我们检查培训、验证和测试集的准确性:

mlp_train_loss, mlp_train_acc = model_mlp.evaluate(train_x,  train_y, verbose=0)
print('\nTrain accuracy:', np.round(mlp_train_acc,3))mlp_val_loss, mlp_val_acc = model_mlp.evaluate(val_x,  val_y, verbose=0)
print('\nValidation accuracy:', np.round(mlp_val_acc,3))mlp_test_loss, mlp_test_acc = model_mlp.evaluate(test_x,  test_y, verbose=0)
print('\nTest accuracy:', np.round(mlp_test_acc,3)) #Output:#Train accuracy: 0.916
#Validation accuracy: 0.889
#Test accuracy: 0.866

测试精度约为 90%。此外,训练和验证/测试精度之间存在 2%的差异。

卷积神经网络

另一类神经网络是卷积神经网络(或 CNN )。CNN 更适合图像分类。他们使用 滤镜 (也称为 内核特征图 )帮助模型捕捉和学习图像的各种特征。 CNN 的通用架构如图 5所示。****

这些过滤器不是静态的:它们是可训练的,这意味着模型在拟合期间以优化训练目标的方式学习它们。这与传统的计算机视觉相反,传统的计算机视觉使用静态过滤器进行特征提取。

此外,CNN 的深度至关重要。这是因为图像可以被视为一个层次结构,所以几层处理对这个领域有直观的意义。CNN 的第一层专注于提取底层特征(如边缘、角落)。随着深度的增加,特征地图学习更复杂的特征,例如形状和脸。

此外,在每个步骤中,信息在被传递到下一个过滤层之前经历“子采样“”。最后一个组件是一个全连接层,它看起来像一个 MLP,但是没有隐藏层。

****图 5:CNN 的顶层架构[3]

让我们深入研究一下实现。

首先,了解每个网络接受什么类型的输入至关重要。对于 MLPs,每个图像被展平成一个单独的28x28向量。这里,每个图像被表示为一个 3d 立方体,其尺寸28x28x1表示格式(宽度、高度、颜色通道)。如果我们的图像不是灰度的,尺寸将会是28x28x3

我们仍然会像以前一样使用相同的激活和损失函数。此外,我们只执行一组过滤/特征映射和子采样。在 Keras API 中,这些分别被称为 Conv2DMaxPooling2D 层:

model_cnn = Sequential()
model_cnn.add(Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))
model_cnn.add(MaxPooling2D((2, 2)))
model_cnn.add(Flatten())
model_cnn.add(Dense(100, activation='relu'))
model_cnn.add(Dense(10, activation='softmax'))model_cnn.compile(optimizer="adam", loss='categorical_crossentropy', metrics=['accuracy'])
print(model_cnn.summary())history_cnn= model_cnn.fit(train_x, train_y, epochs=100, batch_size=32, validation_data=(val_x, val_y), callbacks=callback)

输出是:

_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 conv2d_3 (Conv2D)           (None, 26, 26, 32)        320       

 max_pooling2d_3 (MaxPooling  (None, 13, 13, 32)       0         
 2D)                                                             

 flatten_3 (Flatten)         (None, 5408)              0         

 dense_6 (Dense)             (None, 100)               540900    

 dense_7 (Dense)             (None, 10)                1010      

=================================================================
Total params: 542,230
Trainable params: 542,230
Non-trainable params: 0
_________________________________________________________________
Epoch 1/100
1782/1782 [==============================] - 19s 5ms/step - loss: 0.4063 - accuracy: 0.8581 - val_loss: 0.3240 - val_accuracy: 0.8913
Epoch 2/100
1782/1782 [==============================] - 9s 5ms/step - loss: 0.2781 - accuracy: 0.9001 - val_loss: 0.3096 - val_accuracy: 0.8883
Epoch 3/100
1782/1782 [==============================] - 9s 5ms/step - loss: 0.2343 - accuracy: 0.9138 - val_loss: 0.2621 - val_accuracy: 0.9057
Epoch 4/100
1782/1782 [==============================] - 9s 5ms/step - loss: 0.2025 - accuracy: 0.9259 - val_loss: 0.2497 - val_accuracy: 0.9080
Epoch 5/100
1782/1782 [==============================] - 9s 5ms/step - loss: 0.1763 - accuracy: 0.9349 - val_loss: 0.2252 - val_accuracy: 0.9200
Epoch 6/100
1782/1782 [==============================] - 9s 5ms/step - loss: 0.1533 - accuracy: 0.9437 - val_loss: 0.2303 - val_accuracy: 0.9250
Epoch 7/100
1782/1782 [==============================] - 9s 5ms/step - loss: 0.1308 - accuracy: 0.9516 - val_loss: 0.2447 - val_accuracy: 0.9140
Epoch 8/100
1782/1782 [==============================] - 9s 5ms/step - loss: 0.1152 - accuracy: 0.9573 - val_loss: 0.2504 - val_accuracy: 0.9213
Epoch 9/100
1782/1782 [==============================] - 9s 5ms/step - loss: 0.0968 - accuracy: 0.9644 - val_loss: 0.2930 - val_accuracy: 0.9133
Epoch 10/100
1779/1782 [============================>.] - ETA: 0s - loss: 0.0849 - accuracy: 0.9686Restoring model weights from the end of the best epoch: 5.
1782/1782 [==============================] - 9s 5ms/step - loss: 0.0849 - accuracy: 0.9686 - val_loss: 0.2866 - val_accuracy: 0.9187
Epoch 10: early stopping

同样,我们用 100 个历元初始化我们的模型。然而,10 个纪元足够训练了——直到提前停止开始。

让我们画出我们的训练和验证曲线。我们使用与之前相同的 plot_history 函数方法:

plot_history(hs={'CNN': history_cnn},epochs=10,metric='loss')
plot_history(hs={'CNN': history_cnn},epochs=10,metric='accuracy')

****图 6:CNN 的训练和验证损失

****图 7:CNN 的训练和验证精度

验证曲线遵循与 MLP 车型相同的模式。

最后,我们计算训练、验证和测试精度:

cnn_train_loss, cnn_train_acc = model_cnn.evaluate(train_x,  train_y, verbose=2)
print('\nTrain accuracy:', cnn_train_acc)cnn_val_loss, cnn_val_acc = model_cnn.evaluate(val_x,  val_y, verbose=2)
print('\nValidation accuracy:', cnn_val_acc)cnn_test_loss, cnn_test_acc = model_cnn.evaluate(test_x,  test_y, verbose=2)
print('\nTest accuracy:', cnn_test_acc)#Output:#Train accuracy: 0.938
#Validation accuracy: 0.91
#Test accuracy: 0.908

CNN 的模式胜过了 MLP 的模式。这是意料之中的,因为 CNN更适合图像分类。

结束语

  • 神经网络用于训练的优化函数是“随机”。这意味着,除了别的以外,每次你训练一个模型,你会得到稍微不同的结果。
  • 数据集很简单。首先,图像是灰度的,这意味着它们只有一个通道。彩色图像有 3 个通道(RGB)。
  • 即使我们使用了验证集,我们也没有执行任何超参数调优。在本教程的下一部分,我们将展示如何进一步优化我们的模型。

完整的例子可以在这里找到。

感谢您的阅读!

参考

  1. 时尚 MNIST 数据集由 Zalando,https://www . ka ggle . com/datasets/Zalando-research/fashion mnist,麻省理工学院许可(MIT)版权【2017】
  2. By Glosser.ca —自己的作品,衍生文件:人工神经网络. svg,CC BY-SA 3.0,https://commons.wikimedia.org/w/index.php?curid=24913461
  3. By Aphex34 —自己的作品,CC BY-SA 4.0,https://commons.wikimedia.org/w/index.php?curid=45679374

使用 DCGAN 和 PyTorch 创建新动物

原文:https://towardsdatascience.com/create-new-animals-using-dcgan-with-pytorch-2ce47810ebd4

从野生动物身上学习特征来创造新的生物

图一。动物脸的 DCGAN。作者在 Unsplash 的帮助下创建的图像。

生成网络已经开辟了大量有趣的项目,人们可以用深度学习来做这些项目——其中一项是基于一组逼真的照片生成图像。我们在之前的文章中可以看到,一个非常简单的 GAN 网络在生成数字上可以有很好的效果;但是如果我们有一个更复杂的数据集,比如一群野生动物,会怎么样呢?阿甘看到狐狸、老虎、狮子在一起会产生什么?在本教程中,让我们使用 PyTorch 构建这个实验并找出答案。

边注:本文假设生成性敌对网络的先验知识。请参考之前的 文章 了解更多关于 GANs 的信息。

PyTorch 的 DCGAN

为了测试这一点,我们将需要创建一个更复杂的 GAN,最好是一个 DCGAN,其中我们为这一代涉及了卷积。卷积使它们非常适合学习图像表示。该架构比我们之前构建的 GAN 的 FC 层复杂得多,因为我们必须在生成阶段加入转置卷积。幸运的是,PyTorch 提供了一个非常深入的 教程 自己在创建一个可行的生成器和鉴别器,所以我们将直接使用它的架构如下:

动物面孔数据集

Choi 等人的动物面孔数据集(afhq) 首次在他们的论文 StarGAN 中介绍,可以在 Kaggle 上找到,野生动物文件夹将直接满足我们在数据集中拥有多个动物的需求。然而,这些图像具有相当高的分辨率(512x512),对于使用 CPU 或不太好的 GPU 进行训练来说可能不可行。因此,我们对 64x64 的维度执行额外的自适应平均池。这也符合用于生成的默认 DCGAN 的原始尺寸。以下是数据集创建的代码:

实施环境

虽然 CPU 的训练时间可能要长得多,但我已经在免费版本的 Google Colab 上测试了整个管道,GPU 应该足以执行我们的实验。但是,这需要您将整个数据集放到 Google Drive 上,并将其安装到 Colab 笔记本上。

培养

DCGAN 的训练与普通 GAN 的训练相同。本质上,我们试图玩一个极大极小游戏,试图鼓励鉴别者确定一个图像是真实的还是生成的,同时鼓励生成者愚弄鉴别者。以下是 DCGAN 的代码:

可视化

我们在 30 和 100 个时期后绘制一些随机生成的结果。

30 个时期后:

100 个时期后:

从粗糙的补丁开始,网络似乎通过越来越多的时代逐渐学会了动物的精细表示,并开始生成“动物”,这些“动物”类似于确切有助于成为生物的东西,尽管不知道我们输入的动物的标签或类型!

结论

所以你有它!希望这篇文章提供了如何自己构建 DCGAN 的概述。你甚至可以应用不同的数据集,生成完全不同的东西,比如汽车、飞机,甚至梵高的画!(记住,如果你没有太多的 GPU 资源,一定要平均使用它!).

完整的实现可以在下面的 Github 资源库中找到:

https://github.com/ttchengab/MnistGAN

感谢您坚持到现在🙏 我会在计算机视觉/深度学习的不同领域发布更多内容,所以 加入并订阅 如果你有兴趣了解更多!

使用 Hex 创建可观察和可复制的笔记本

原文:https://towardsdatascience.com/create-observable-and-reproducible-notebooks-with-hex-460e75818a09

如何将笔记本电脑集成到您的数据管道中

动机

Jupyter Notebook 是数据科学家探索和处理数据的流行工具,因为它易于检查代码输出。

然而,Jupyter 笔记本有几个缺点,包括:

  • 可解释性问题:随着代码变大,单元之间的关系变得越来越复杂。因此,当一个单元格发生变化时,很难知道接下来要执行哪个单元格。
  • 再现性问题:因为笔记本中的单元格可以以任何顺序执行,所以以不同的顺序执行同一个笔记本可能会导致不同的输出。
  • 版本问题:很难用 Git 比较一个笔记本两个不同版本的变化。

幸运的是,所有这些问题都可以用 Hex 笔记本解决。Hex 允许您通过依赖关系链接单元,并只执行依赖关系发生变化的单元。

作者图片

在本文中,您将了解 Hex 的一些有用特性,以及如何使用 Prefect 将它集成到您的数据管道中。

作者图片

通过使用 Hex 笔记本来查询和可视化您的管道的最终输出,您团队中的数据分析师不再需要通过管道来分析数据。

免责声明:我与 Hex 没有任何关系。我在工作中发现了 Hex,并写下了它,因为我发现它很有用。

什么是 Hex?

Hex 是一个现代化的数据工作空间,可以轻松连接数据并创建可复制的笔记本。

Hex 提供了很多很酷的特性,但是我喜欢 Hex,因为它使我能够:

  • 轻松连接到数据源
  • 使用 SQL 和 Python 来分析数据
  • 理解细胞之间的关系
  • 仅当单元的依赖关系发生变化时才重新执行单元
  • 给笔记本版本
  • 创建动态报告

让我们在接下来的几节中探索这些特性。

轻松连接到数据源

Hex 允许您通过几个简单的步骤连接到各种数据源。

作者图片

添加数据连接后,您只需从下拉列表中选择即可在笔记本的任何单元格中访问它。

作者图片

使用 SQL 和 Python 来分析数据

Hex 还允许您在一个笔记本中同时使用 SQL 和 Python。

在下面的截图中,有一个 SQL 单元和一个 Python 单元。SQL 单元(segmented)创建的输出随后被用于 Python 单元。

作者图片

这对于那些想在同一个笔记本中同时使用 SQL 和 Python 的人来说很方便。

理解细胞之间的关系

如前所述,在 Jupyter 笔记本中很难找到单元格之间的关系。

Hex 使得用图形视图观察单元之间的关系变得容易。

下面的 GIF 显示了一个十六进制笔记本的图形视图。每个节点代表一个单元。每条边显示单元格之间的依赖关系。

作者图片

仅当单元的依赖关系发生变化时才重新执行单元

当输入改变时,Hex 将自动只重新运行使用该输入的单元格。

因此,您不再需要花费时间寻找执行的单元,或者花费资源运行不相关的单元。

笔记本也是可复制的,因为一个变化将通过图表触发一个可预测的、一致的重新计算集。

作者图片

版本笔记本

给 Jupyter 笔记本版本总是一件痛苦的事。使用 Hex,您可以比较同一工作区中笔记本版本之间的差异。

作者图片

创建动态报告

使用 Hex,您可以创建一个带有内置小部件的动态报告,比如下拉菜单、复选框、按钮、日期、多选、数字和滑块。

作者图片

在下面的 GIF 中,我使用 dropdown 小部件根据选择的值更新绘图。

作者图片

Hex 还允许你通过一个漂亮的 GUI 创建一个简单的图表。

作者图片

如果你想向 CEO 或利益相关者展示你的见解,你可能想隐藏代码。要仅查看笔记本的输出,请从笔记本顶部的逻辑模式切换到应用模式。

作者图片

现在您将看到一个没有代码的漂亮报告。

作者图片

如何将 Hex 集成到您的数据管道中

现在我们知道了 Hex 有多有用,让我们将它集成到我们的数据管道中。我们将使用 Hex 笔记本从数据的最终输出中获得洞察力。

作者图片

您可以在此处找到此数据管道的代码:

https://github.com/khuyentran1401/customer_segmentation/tree/prefect2

概观

我们将使用perfect来观察和编排我们的数据管道。Prefect 是一个开源库,允许您编排和观察 Python 中定义的数据管道。

https://medium.com/the-prefect-blog/orchestrate-your-data-science-project-with-prefect-2-0-4118418fd7ce

管道中有四种流量:process_datasegmentrun_notebook。这些流程的详细信息:

  • process_data :从 Postgres SQL 数据库中提取原始数据,进行处理,并将处理后的数据保存到数据库中
  • :从数据库中提取处理后的数据,训练一个 ML 模型,并将最终数据保存到数据库中
  • run_notebook :运行 Hex notebook,它将从数据库中提取数据,并运行依赖关系发生变化的单元格
  • main :运行上面列出的所有三种流量

在下一节中,我们将更深入地研究run_notebook流程。

用提督运行十六进制笔记本

我们将使用perfect-hex库在一个 perfect 流中运行一个 Hex 笔记本。要安装此库,请键入:

pip install prefect-hex

要访问特定的 Hex 笔记本,您需要项目 ID 和令牌。获得凭证后,您可以通过从prefect_hex.project调用get_project_runs函数在 Python 脚本中运行笔记本。

from prefect import flow
from prefect_hex import HexCredentials

from prefect_hex.project import get_project_runs
from prefect_hex import HexCredentials

@flow
def run_notebook():
    hex_credentials = HexCredentials(token="your-token")
    project_id = 'your-project-id'
    return get_project_runs(
        project_id=project_id,
        hex_credentials=hex_credentials,
    )

if __name__ == "__main__":
    run_notebook()

就是这样!现在,当输入数据改变并且管道重新运行时,报告将被更新。

作者图片

使用完美的 UI,您还可以观察到流程的所有组件。

作者图片

结论

我希望这篇文章能给你建立一个可观察和可复制的管道所需的知识。

总而言之,提督和十六进制的结合:

  • 允许数据工程师协调和观察管道
  • 允许数据分析师专注于分析笔记本电脑中的数据

多酷啊。

我喜欢写一些基本的数据科学概念,并尝试不同的数据科学工具。你可以在 LinkedIn 和 T21 Twitter 上与我联系。

如果你想查看我写的所有文章的代码,请点击这里。在 Medium 上关注我,了解我的最新数据科学文章,例如:

用美国人口普查局的数据创建任何国家的人口金字塔

原文:https://towardsdatascience.com/create-population-pyramids-for-any-country-with-the-us-census-bureau-data-7f48281a5a4a

数据分析、数据工程、Python 编程和人口统计学

使用人口普查数据 API、Python 和 Tableau Public 按国家、年龄、性别和年份创建 2100 年的动态人口金字塔

一群人。2022 年 11 月 15 日,联合国报告世界人口已达 80 亿..圣佛明潘普洛纳摄:https://www . pexels . com/photo/bird-s-eye-view-of-group-of-people-1299086/

虽然人口数据有许多来源,但美国人口普查局在其国际数据库中保存了到 2100 年世界上人口在 5000 或以上的 227 个国家和地区的人口估计和预测数据。本文将向您展示如何使用 Python 通过 Census Data API 从国际数据库中检索数据,并将其从非标准的 JSON 格式转换为 CSV 文件。然后将该文件加载到 Tableau 中,将其可视化为人口金字塔。

什么是人口金字塔?

人口金字塔是按性别和年龄组显示一个国家或地区人口分布的图表。下面显示的 2022 年年中日本的人口金字塔示例表明,该国没有替换其老龄化人口。

2022 年日本的人口金字塔。图表由 Randy Runtsch 创建,Tableau Public。

人口普查数据 API

人口普查数据应用编程接口(API) 让公众能够直接访问美国人口普查局作为其各种计划的一部分收集的原始统计数据。该局在的表格中描述了它的每个数据集,称之为 API 发现工具。

统计局声称,其人口普查数据 API 提供了方便的数据访问。像大多数数据 API 一样,它可以直接从 web 浏览器调用,也可以从各种编程语言中调用,包括 Python 和 r。

虽然许多美国政府数据 API 以标准的 JavaScript 对象表示法(JSON)格式返回数据,有时以逗号分隔值(CSV)格式返回数据,但人口普查数据 API 以非标准的 JSON 格式返回数据。这增加了一个难题,因为程序员和数据工程师必须重新格式化检索到的数据,以使其在 Excel 或 Tableau 等工具中可读和可用。例如,在下面的示例中,请注意,在数据集和记录级别,返回的数据被包装在方括号中。

尝试将以下 URL 粘贴到 web 浏览器中,然后按 enter。它将返回 2015 年法国年中人口的估计值,按年龄(以一年为增量)和性别(男性和女性的综合人口值)进行统计。混合性别的性别代码为 0,男性为 1,女性为 2。GENC 是感兴趣的国家的双字符代码。在这种情况下,法国的 GENC 值是“FR”。

https://api.census.gov/data/timeseries/idb/1year?get=NAME,AGE,POP&GENC = FR&YR = 2015&SEX = 0

上面显示的 URL 查询返回的数据。Randy Runtsch 截图。

本文后面显示的 Python 程序对四个国家的人口普查数据 API 进行了类似的查询。然后,它从返回的数据集中去掉前后的方括号(“[”和“]”)。最后,它将每条记录(包括提供列标题的第一条记录)写入一个 CSV 文件。然后,该文件被加载到 Tableau Public 中,用于根据数据创建人口金字塔。

人口普查局国际数据库

您可以使用人口普查数据 API APIs 从国际数据库中检索世界上人口在 5,000 或以上的所有国家和地区的年中人口估计和预测。您可以检索这些数据集:

  • 时间序列>IDB>1 年:该数据集包括某一年按年龄和性别、国家或地区、年龄和性别划分的人口。
  • 时间序列>美洲开发银行>5 年:该数据集包括按年龄划分的 5 年组人口,以及某一年的性别、国家或年龄以及性别。它还包括生育率、死亡率和移民指标。

本文将展示如何使用时间序列>IDB>1 年数据集来检索数据并构建四个国家的人口金字塔。

普查数据 API 文档

人口普查数据 API 用户指南提供了关于如何使用 API 检索数据的信息。它与该局在 API 发现工具中为每个 API 提供的信息一起,提供了检索任何可用数据集的数据所需的大部分指令。

除了上面链接的文件,该局的国际数据库(IDB)演示页面使用来自时间序列>IDB>1 年或 5 年数据集的数据展示了一个动态人口金字塔。演示页面将有助于与您创建的人口金字塔进行比较,以确认其准确性。

美国人口普查局国际数据库演示页面上显示的日本人口金字塔。Randy Runtsch 截图。

性别代码和国家代码

国际数据库可以按性别和国家或地区查询。查询还会在每个记录中返回这些数据点。

以下是性代码:

0 =双方
1 =男性
2 =女性

ISO 3166 国家代码标准中,双字符国家代码被定义为“Alpha-2 代码”。例如,美国的 Alpha-2 代码是“US”,法国的代码是“FR”国家名称和代码的完整列表记录在 ISO 在线浏览平台的页面上。但是你可能会发现这个ISO 3166-1 alpha-2 维基百科页面使用起来更方便。

IDB 查询和结果示例

人口普查数据 API 国际数据库(IDB)的基本 URL 是http://api.census.gov/data/timeseries/idb/1year。在 web 浏览器中调用这个 URL 会返回一个描述查询的 JSON 结构。

以下 URL 将按女性(性别代码 2)的年龄返回 2022 年日本(GENC 代码“JP”)的数据:

https://api.census.gov/data/timeseries/idb/1year?get=NAME,AGE,POP&GENC = JP&YR = 2022&SEX = 2

除了显示姓名(国家或地区)、年龄和 POP(人口)列之外,返回的数据集还将显示 GENC、YR(年份)和 SEX 列的查询值。示例返回值如下所示。

上面显示的 URL 查询返回的数据。Randy Runtsch 截图。

人口普查数据 API 键

根据 Census Data API 用户指南,用户可以在不注册密钥的情况下,每天为每个 IP 地址提交多达 500 个 API 查询。每天需要进行 500 次以上查询的用户需要获得一个 API 密钥,并将该密钥附加到他们的每个查询中。要申请密钥,点击开发者页面上的“申请密钥”。

请求人口普查数据 API。图片来自人口普查数据网页。

注册密钥后,您将通过电子邮件收到密钥。然后,您可以将该键追加到查询中,如下例所示:

https://api.census.gov/data/timeseries/idb/1year? get=NAME,AGE,POP&GENC = CN&YR = 2022&SEX = 1&KEY = YOUR _ KEY _ GOES _ HERE

用于此项目的工具

对于下面几节中介绍的项目,我使用 Microsoft Visual Studio Community 2022 进行 Python 编程,使用 Tableau 公共桌面和 Tableau 公共网站进行数据可视化。对于 Python 编程,可以随意使用您喜欢的任何编辑器或集成开发环境(IDE)。

Visual Studio Community 和 Tableau Public 是免费工具,您可以从以下位置安装:

请注意,虽然 Tableau 的商业版本允许您将数据可视化工作簿保存到本地驱动器或服务器,但在 Tableau Public 中,所有可视化工作簿只能保存到 Tableau Public 服务器。此外,公众也可以看到可视化效果。

Python 程序从国际数据库中检索数据

现在,您已经对人口普查数据 API 及其国际数据库有了基本的了解,让我们回顾一下检索 2022 年四个国家(按年龄和性别)人口估计数据的 Python 代码。该程序包括两个模块,类 c_country_pop.y 和调用该类的模块 get_population_estimates.py。

乡村流行音乐

总之,c_country_pop 类使用人口普查数据 API 查询国际数据库,将其非标准的 JSON 记录重新格式化为 CSV 格式,并将每个记录写入输出文件。以下伪代码描述了它的功能:

使用以下参数实例化(function _ _ init _ _())c _ country _ pop:

  • out_file_name :人口数据将以 CSV 格式写入的文件。
  • country_code :获取数据的国家的双字符代码。
  • 年份:要检索数据的四位数年份。
  • sex_code :检索数据的性别,其中 0 =双方,1 =男性,2 =女性。
  • write_type : 'w '将返回的记录写入新文件,而' a '将记录追加到现有的输出文件。write_type 的目的将在下面解释。
  • api_key :您的个人普查数据 API key。

调用 get_data()函数来执行这些任务:

  • 构建人口普查数据 API 查询 URL。
  • 用 URL 调用 API。
  • 将返回的数据从二进制格式转换为字符串。

调用 write_data_to_csv()函数来执行这些任务:

  • 用指定的 write_type 值打开输出文件。
  • 遍历从 get_data()函数返回的记录。
  • 仅当 write_type 为“w”时,才写入包含列标题的第一条记录。如果 write_type 为“a”,则不要写入第一条列标题记录,因为这是文件将包含的第二个或以后的数据集。
  • 从记录字符串中去掉任何前导和尾随的方括号和逗号。
  • 将记录字符串写入输出文件。

get_population_estimate.py

我称之为驱动程序的模块 get_population_estimate.py 只是为每个感兴趣的国家调用 c_country_pop 类的一个实例。对于它调用的第一个国家,它包含一个 write_type 值“w”,该值指示 c_country_pop 创建一个新的输出文件来写入 CSV 记录,并写入一个列标题作为它的第一条记录。以下是该模块的伪代码:

  1. 获取 2022 年中国(国家代码“CN”)的男性(性别代码 1)记录。指示 c_country_pop 创建一个新的输出文件(文件名为“c:/population _ data/pop _ 2022 . CSV”,文件类型为“w”),并写入一个列标题作为其第一行。
  2. 获取 2022 年中国女性(性别代码 2)记录。使用相同的输出文件名,写入类型为“a”。这将指示 c_country_pop 打开在步骤 1 中创建的文件,并将其记录(不包括标题列记录)追加到文件中。
  3. 对日本(国家代码“JP”)、挪威(国家代码“NO”)和美国(国家代码“US”)重复上述步骤。在所有情况下,请使用上述步骤 1 中指定的相同文件名。但是,使用“a”写入类型将记录追加到步骤 1 中创建的文件中。

CSV 输出文件

成功运行程序后,检查输出文件。当在 Excel 中打开时,它应该类似于下面的示例。

中国的样本人口数据显示在 Excel 中。Randy Runtsch 截图。

代码

以下是 get_population_estimates.py 和 c_country_pop.py Python 模块的代码。

本文中描述的代码。代码是由 Randy Runtsch 编写的。

Tableau Public 中的人口金字塔示例

本节不会提供在 Tableau 中创建人口金字塔的详细说明。要在 Tableau 中创建人口金字塔,详见 Tableau 帮助中的这些说明

我在 Tableau Public 中构建的人口金字塔版本使用了由上述 Python 程序创建的 CSV 文件中的数据。它允许用户通过点击一个国家单选按钮来切换国家。你可以在这里看到可视化的现场版本。如果您愿意,也可以下载 Tableau 工作簿并根据您的需要进行修改。

摘要

这篇文章提供了关于人口普查数据 API 及其国际数据库(IDB)的信息。它还展示了一个 Python 程序,用于从 IDB 检索数据并将其写入 CSV 文件。最后,它显示了使用数据的人口金字塔。

随着人口增长和气候变化等主题的出现,通过人口普查数据 API 获得的数据肯定会引起全球数据分析师和数据科学家的兴趣。我希望这篇文章能为您使用人口数据的项目提供有用的信息。

使用逻辑回归为分类问题创建强大的模型解释

原文:https://towardsdatascience.com/create-powerful-model-explanations-for-classification-problems-with-logistic-regression-9bdd5f1c3648

从业者指南,使用 IBM 电信客户流失数据集进行演示

巴勃罗·加西亚·萨尔达尼亚在 Unsplash 上拍摄的照片

介绍

逻辑回归通常用于建模分类问题。这是一种参数算法,其输出提供了强大的模型解释(被许多人称为可解释的 ML)。特别是,除了克服线性回归对分类问题建模的已知限制之外,与非参数的基于树的算法相比,它还能够轻松地告知用户特定特性的步长变化如何影响目标变量,我将在本文的后面部分使用 IBM 共享的数据集来演示这一点。

分类问题的逻辑回归与线性回归

在第一个例子中,我将通过一个例子来展示在分类问题中使用逻辑回归比线性回归的优势。

在保险领域,失效是指投保人行使选择权终止与保险人的保险合同的事件。从商业角度来说,了解投保人在下一次保单续期时是否可能失效符合保险公司的利益,因为这通常有助于保险公司优先考虑其保留工作。这就变成了一个分类问题,因为给定特定投保人的属性,响应变量采用 0(非失效)或 1(失效)的二进制形式。

在下图的合成数据集中,我们记录了 30 名投保人的过失行为,1 表示过失,0 表示过失。

图表 1:观察到的 30 名投保人的过失。按作者分类的图表

在这种情况下,使用线性回归对失误进行建模是违反直觉的,如下图中的灰线所示。显然,绿线所示的逻辑回归提供了更好的拟合。

图表 2:线性回归与逻辑回归。按作者分类的图表

逻辑回归属于广义线性模型(“GLM”)家族。假设我们想要为投保人的失效概率建模,用 p 表示,逻辑回归具有如下形式的方差函数,当 p 取值为 0 或 1 时,方差函数最小化。

这是为投保人的失效概率建模的一个很好的属性,因为观察到的失效事件只能取值 0 或 1。一个相应的结果是,逻辑回归对 0 和 1 的观察值给出了更大的可信度,并且可以很容易地扩展到其他分类问题。

逻辑回归与基于树的分类算法

在这一节中,我将使用一个 IBM 数据集演示使用逻辑回归比基于树的算法的优势。我将从描述逻辑回归的模型输出开始。

对数优势比

预测模型旨在表达独立特征(“ X ”)和目标变量(“ Y ”)之间的关系。在线性回归中,该关系可以表示为:

在这种情况下,【y】可能表示房产价格,而【x₁】【x₂】可能分别表示房产大小和房产中卧室的数量,在这种情况下,我们会期望自变量和因变量之间以正系数的形式存在正关系,用于β₂.

另一方面,逻辑回归旨在对事件的概率(【p”)进行建模(例如,投保人的失效)。这首先通过用下面等式(3)中所示的对数比替换等式(2)中的 Y 来表示:

数学上,重新排列等式(3)得到 p ,如下面的等式(4)和(5)所示:

****

等式(3)、(4)和(5)使得逻辑回归模型输出的解释更全面,原因如下:

  • 等式(3)和(4)使用保持线性的结构来表示比值比。
  • 等式(5)将特征映射到范围从 0 到 1 的概率。这使得模型的用户能够为每个输入数据点分配输出概率 p (例如,每个投保人的失效概率,根据该概率可以对保留努力进行优先级排序)。

β系数

通过使用截距项 β ₀,可以很容易地建立基准测试的基线场景。例如,在逻辑回归下,如果 β₂ 是分类变量的估计系数,该分类变量已被编码为取值 1 或 0(例如,对于高或低收入水平),则下面的等式(6)可用于显示输出概率t5】pT7 因该特定特征而有多大差异(即,在高和低收入水平的客户之间)。****

等式(6)暗示收入水平对失效概率*的影响是基于系数 β₂ 的常数。当 β₂ 系数足够小时,概率变化pt19】可以直接用系数 β₂来近似(当 β 小时βe)。*****

此外,从等式(4)可以推断出, β 系数的符号表示相应特征影响输出概率 p 的方向。

然而,在实践中,并不是所有拟合的特征都是重要的。根据经验,如果特征的 β 系数的p-值小于 0.05,则特征通常被认为具有统计显著性,这表明这些 β 系数具有相对较小的方差。

β 方差较大的系数表明应减少对相应特征的依赖,因为估计的系数可能在很大范围内变化。

总之,利用逻辑回归估计的 β 系数,用户可以输出目标事件的概率,并显示每个特征如何在数据点级别影响输出概率。这非常有助于模型解释,我将在后面演示。

IBM 数据集

我将使用的数据集是众所周知的 IBM Telco 流失数据集,用于演示逻辑回归下的模型解释。它包含 20 个独立特征和 1 个目标变量“流失”,该变量指示客户是否停止使用电信公司的服务。最初的目的是训练一个预测目标变量的分类模型。

这个数据集来自官方的 IBM GitHub 存储库。下表提供了该数据集的数据字典。除了任期月费(即每月保费)和总费用之外,所有功能都是绝对的。

表 3:数据字典。按作者分类的表格

模型拟合

出于演示的目的,我用表 3 中列出的特性子集拟合了 R 中的逻辑回归。下面提供了模型拟合的 R 代码,请注意,基本客户资料是使用 relevel 方法为每个分类特征设置的。这使我们能够根据预定义的基本客户资料,量化特定功能的预测流失概率的相对变化。

*## 1\. Load Telco Churn data
data_raw <- read.csv('Directory/Telco-Customer-Churn.csv', header = TRUE)

## 2\. 70/30 Train-Test Split
y = data_raw$Churn_Flag

set.seed(268)
sample_size <- floor(0.7 * nrow(data_raw))
sample_indi <- sample(seq_len(nrow(data_raw)), size = sample_size)
d_train <- data_raw[sample_indi,]
d_test <- data_raw[-sample_indi,]
y_train <- y[sample_indi]
y_test <- y[-sample_indi]

## 3\. Logistic Regression Model Fitting
glm_1 <- glm(Churn_Flag ~

  tenure
+ MonthlyCharges
+ relevel(factor(gender), ref = "Female")
+ relevel(factor(SeniorCitizen_Flag), ref = "Yes")
+ relevel(factor(PhoneService), ref = "No")
+ relevel(factor(InternetService), ref = "DSL")
+ relevel(factor(Contract), ref = "Month-to-month")
+ relevel(factor(PaperlessBilling), ref = "No")
+ relevel(factor(PaymentMethod), ref = "Bank transfer (automatic)")
#+ Partner
#+ Dependents
#+ MultipleLines
#+ OnlineSecurity 
#+ OnlineBackup 
#+ DeviceProtection
#+ TechSupport
#+ StreamingTV
#+ StreamingMovies
, data = d_train
, family = binomial("logit")
)

summary(glm_1)*

下面的屏幕截图显示了上面拟合的 glm_1 模型的输出。特别是:

  • 估计值栏存储每个拟合特征的估计 β 系数。
  • Pr( > |z|) 列存储了每个特征的 p 值(可以宽松地看做接受特征不显著假设的概率),大部分是< 0.05。

表 4:逻辑回归的 R 输出。按作者分类的表格

另外,模型 glm_1 实现了 0.84 的 AUC(这一点都不差,尽管模型性能并不十分重要,因为这个演示是为了解释模型)。

利用上面估计的 β 系数,我们可以立即校准基本客户档案的流失概率。具体使用等式(5),在代码中被设置为参考水平的基本客户简档的流失概率是 32%(即,几乎三分之一!).

这是通过取下表所示的估计值特征值列的和积(得到-0.7557),取该值的指数,然后按照等式(5)除以 1 加上该值的指数来计算的。

表 5:估计基本概率。按作者分类的表格

此外,通过用感兴趣的客户的简档填充表 5 中的特征值列,可以计算数据中任何客户的流失概率。这可能有助于应用优先与流失可能性最高的客户接触。

此外, β 系数的符号告知特定特征影响流失的方向。例如,如果客户是男性,或者每月费用较高(这是直觉),那么他更有可能离开电信公司,如果客户已经锁定了两年的合同,那么他就不太可能离开。

将模型解释进一步,再次使用等式(5),我们可以显示相对于“平均”客户的 32%概率,特定特征的阶跃变化如何影响流失。下表显示了相对于基本客户资料的流失概率的(附加)变化。

表 6:按功能划分的流失概率变化。按作者分类的表格

总之,逻辑回归有效地告知用户以下信息:

  • 给定 p 值机制,表 4 中确定的流失的重要驱动因素自然会过滤掉不重要的驱动因素;
  • 数据中任何客户流失的概率;
  • 相对于预定义的基本客户资料,特定特征引起的客户流失概率的变化。

表 6 也可以在下面的瀑布图中可视化,其中绿色条表示相对于基本客户档案,特定特征的流失概率降低,黑色条表示流失概率增加。

图表 7:按功能划分的流失概率变化。按作者分类的表格

根据模型的输出,如果我是电信公司的决策者,我会开始主动管理客户流失,将注意力集中在以下客户身上:

  • 拥有光纤互联网服务(可能需要调查这是原因还是关联)
  • 用电子支票支付
  • 每月支付高额保费
  • 有纸质账单

限制

在实践中应用逻辑回归(或更一般的 GLM)有一些已知的限制,包括:

  • 由于模型的参数化性质,它需要大量的特征工程工作,因为特征可能需要手动拟合。这包括交互项的拟合,其中一个特征的效果可能取决于另一个特征的水平。保险业中这种相互作用的一个例子是,保险费的增加可能因年龄而异。可能的相互作用项的数量随着特征的数量呈指数增长。此外,虽然逻辑回归允许调查相互作用的条款,他们可能会证明很难解释。
  • 自变量( X )是假设独立的,这可能不成立。对于预测流失的用例,流失事件可能需要按时间段进行度量和细分,这可能会引入不同时间段的重叠客户和相关性。

总结想法

逻辑回归是分类问题的一个很好的模型,因为它的输出允许全面的模型解释,特别是对于非技术观众。

在实践中,我认为将逻辑回归模型的性能与其他已知的用于解决分类问题的模型(如基于树的模型)进行比较,以获得最佳推断,这符合从业者的最佳利益。这两种模型协作的一个用例是使用基于树的模型来指导数字特征的拟合,如年龄和任期,其中这些特征的片段预计会对目标变量产生不同的影响。

数据源

[1] IBM GitHub 存储库,电信客户流失 GitHub 页面,2023 年 1 月 7 日访问,在https://GitHub . com/IBM/Telco-Customer-Churn-on-ICP 4d/blob/master/LICENSE获得许可

使用 Prefect、Docker 和 GitHub 创建强大的数据管道

原文:https://towardsdatascience.com/create-robust-data-pipelines-with-prefect-docker-and-github-12b231ca6ed2

将您的工作流存储在 GitHub 中,并在 Docker 容器中执行它

动机

您是否曾经想要在两个位置存储和执行您的工作流,但发现很难做到这一点?

作者图片

如果您可以轻松地在不同的位置存储和执行您的代码,那不是很好吗?Prefect 允许您在上面列出的所有环境中轻松存储和执行您的工作流,同时在一个漂亮的仪表板中跟踪所有运行。

作者图片

作者图片

在本文中,您将学习如何在 GitHub 中存储工作流代码,并在 Docker 容器中运行工作流。

作者图片

什么是提督?

perfect是一个开源库,可以让你在 Python 中协调数据工作流。

要安装提督,请键入:

pip install -U prefect

本文中使用的提督版本是 2.3.2:

pip install prefect==2.3.2

项目目标

在本文中,我们将创建一个工作流:

  • 从 Google Trends 获取统计数据并创建报告
  • 存储在 GitHub 上
  • 在 Docker 容器中执行
  • keywordstart_datenum_countries为自变量
  • 计划每周运行

作者图片

每当执行工作流时,都会创建以下报告。然后,您可以与您的队友或经理分享该报告。

链接到报告

创建一个流程

流程是所有完美工作流程的基础。要将 Python 函数转换成完美的流,只需向该函数添加装饰器@flow:

完整代码创建一个流。

创建简单的部署

一个部署存储关于流的代码存储在哪里以及流应该如何运行的元数据。通过部署流程,我们可以:

  • 指定流程运行的执行环境基础结构
  • 指定提督代理如何存储和检索您的流代码
  • 使用 UI 中的自定义参数创建流运行
  • 创建运行流程的计划

还有更多。

下图显示了具有不同计划、基础架构和存储的两个部署中的相同流程。

作者图片

创建部署有两个步骤:

  1. 构建部署定义文件,并有选择地将您的流上传到指定的远程存储位置
  2. 通过应用部署定义来创建部署

作者图片

构建部署

要构建部署,请键入:

例如,要从文件src/main.py中为流create_pytrends_report创建部署,在您的终端上键入以下内容:

其中:

  • -n google-trends-gh-docker指定部署的名称为google-trends-gh-docker
  • -q test指定工作队列为test。一个工作队列将部署组织到队列中以供执行。

运行该命令将在当前目录下创建一个create_pytrends_report-deployment.yaml文件和一个.prefectignore

.
├── .prefectignore
├── create_pytrends_report-deployment.yaml

这些文件的功能:

  • .prefectignore防止某些文件或目录上传到配置的存储位置。
  • create_pytrends_report-deployment.yaml指定流程代码的存储位置以及流程应该如何运行。

作者图片

通过 API 创建部署

在创建了create_pytrends_report-deployment.yaml文件之后,我们可以通过键入以下命令在 API 上创建部署:

如果您想将prefect deployment buildprefect deployment apply步骤合并为一个步骤,将--apply选项添加到prefect deployment build:

现在,当进入猎户座府尹 UI 上的“部署”选项卡时(通过键入prefect orion start府尹云,您应该会看到以下内容:

作者图片

创建块

模块使您能够:

  • 存储配置:安全地存储凭证,以便使用 AWS、GitHub、GCS、Slack 等服务进行身份验证
  • 与外部系统交互:创建定制的基础架构块和存储块

“块”选项卡下提供了所有块。

作者图片

实例化 Docker 容器块

要实例化 Docker 容器块,首先单击 Docker 容器块上的Add按钮。

作者图片

在本文中,我们将只填写以下字段:

  • 块名:块名
  • Env :要在已配置的基础设施中设置的环境变量。我们可以使用EXTRA_PIP_PACKAGES在运行时安装依赖项
  • 类型:基础设施的类型。在这种情况下,它是docker-container
  • 流输出:如果设置,输出将从容器流到本地标准输出

下图显示了我用于我的docker-container/google-trends模块的配置。

作者图片

这应该是你点击Save按钮后看到的。

作者图片

您还可以通过添加 Docker 图像的标签来使用自定义 Docker 图像。

作者图片

实例化一个 GitHub 存储块

要实例化 GitHub 存储块,首先单击 GitHub 块上的Add按钮。

作者图片

下图显示了我的 GitHub 块的配置。

作者图片

点击Save按钮后,您应该会看到以下内容。

作者图片

实例化的块将显示在“块”选项卡下。

作者图片

使用 Docker 基础架构+ GitHub 存储创建部署

要使用我们刚刚创建的两个块创建部署,请在您的终端上键入以下命令:

其中:

  • -n google-trends-gh-docker指定部署的名称为google-trends-gh-docker
  • -q test指定工作队列为test
  • -sb github/pytrends指定存储为github/pytrends
  • -ib docker-container/google-trends指定基础设施为docker-container/google-trends
  • -o prefect-docker-deployment指定 YAML 文件的名称为prefect-docker-deployment.yaml

运行该命令后,您应该会看到以下输出:

新部署将出现在“部署”选项卡下。

作者图片

要查看有关部署的更多详细信息,请单击该部署。

作者图片

运行部署

为了从这个部署执行流运行,启动一个代理从test工作队列中提取工作:

prefect agent start -q 'test'

输出:

接下来,单击google-trends-gh-docker部署中的Run按钮,并选择Now with defaults以使用默认参数运行部署。

作者图片

您应该在代理启动的终端上看到以下输出。

单击输出末尾的 Datapane 链接后,您将看到以下报告:

报告链接

使用自定义参数运行部署

你可能会对 Google Trends 上关键字TikTok的统计数据感兴趣,而不是 COVID。通过点击Run按钮下的Custom,Prefect 可以很容易地使用自定义参数运行部署。

作者图片

以下是我的自定义配置:

作者图片

流程运行完成后,将会为您创建一个新的报告!

链接到报告

向部署添加时间表

您还可以通过单击部署中的Edit按钮,然后单击 Scheduling 部分下的Add按钮,向部署添加一个时间表。

作者图片

作者图片

提督支持三种类型的时间表: CronIntervalRRule 。我们将使用 Cron 来安排在周日上午 12:00 运行部署。

作者图片

下一步

恭喜你!您刚刚学习了如何使用 Prefect 将工作流代码存储在 GitHub 中,并在 Docker 容器中运行管道。我希望这篇文章能给你自动化你自己的工作流所需要的知识。

随意发挥,并在这里叉这篇文章的源代码:

https://github.com/khuyentran1401/prefect-docker

我喜欢写一些基本的数据科学概念,并尝试不同的数据科学工具。你可以在 LinkedIn 和 Twitter 上与我联系。

如果你想查看我写的所有文章的代码,请点击这里。在 Medium 上关注我,了解我的最新数据科学文章,例如:

https://medium.com/the-prefect-blog/orchestrate-your-data-science-project-with-prefect-2-0-4118418fd7ce </4-pre-commit-plugins-to-automate-code-reviewing-and-formatting-in-python-c80c6d2e9f5>

创建 GraphQL API,通过 Apollo 服务器访问 mongoDB 数据库

原文:https://towardsdatascience.com/create-your-graphql-api-and-access-your-mongodb-database-via-apollo-server-deployed-on-heroku-9bf8ca410dc8

如果你从头到尾遵循这个教程,你将学会如何组织和创建你自己的数据科学项目的后端。GraphQL API 将通过部署在 Heroku 上的 Apollo 服务器实例公开您的数据库。

道格拉斯·洛佩斯在 Unsplash 拍摄的照片

你能期待什么

本教程面向那些知道一些数据科学技能,如可视化、分析数据,甚至应用机器学习模型,但没有创建连接到持久数据库的 API 的经验的人。如果你想拥有后端技能,通过 API 让你的数据科学技能更加公开,那么这里是我们将在本教程中创建的进一步展望。

在本教程中,我们将更多地关注基础架构方面,这将包括以下步骤。我们将创建一个 GraphQL 模式,定义一个查询和三个不同的突变。此外,我们将编写一些解析器函数。此外,为了使数据持久,我们连接到云中的 mongoDB 集群。我们将一起创建该集群,如果您以前没有这样做过,请不要担心。为了让我们的小教程在现实世界中适用,我们将使用 heroku 在云中部署一个 apollo-server 实例。此外,将服务器实例部署到云中也将逐步介绍。

通过本教程解锁的技能🔓

如果你从头到尾遵循这个教程,你将学会如何为你自己的数据科学项目组织一个后台。例如,您可以创建一个仪表板,显示由您的 graphQL API 提供的一些数据,这些数据是由 apollo-server(您已经创建)提供的,它在后台访问一个数据库(由您控制)。带有 GraphQL 的 Apollo 服务器非常适合组合来自各种资源的信息。你可以维护一个给你一些数据的数据库,apollo 让你访问它,就像你在本教程中看到的那样。此外,你可以从你喜欢的数据源访问不同的 API。您的仪表板只需要连接到您的 apollo 服务器,并从不同的 API 以及您的数据库中获取数据。你甚至可以有多个数据库。我希望你能看到这有多强大。所以让我们解开这个野兽!

注意,我们不打算介绍 graphQL 语法的含义。本教程重点介绍使用 GraphQL、apollo-server、nodejs、heroku 和 mongoDB 为数据驱动的应用程序创建后端结构的总体原则。

基本设置(文件夹和包. json)

首先,创建一些文件夹。进入您的文件夹,其中有您的编程项目和教程,并创建一个名为 graphqlAPI 的文件夹。这是存放教程中所有代码的文件夹。在这个名为 graphqlAPI 的文件夹中创建一个名为 server 的文件夹。进入这个文件夹,通过npm init-y初始化一个 npm 包。

总结:

  1. mkdir graphqlAPI
  2. cd graphqlAPI
  3. mkdir server
  4. cd server
  5. npm init -y

打开 IDE,在服务器文件夹的级别上。打开由 npm init 创建的 package.json 文件。我们将对该文件进行一些更改。将文件更改如下:

  • "test": "echo \\"Error: no test specified\\" && exit 1”将被替换为"start": "nodemon src/index.js"

nodemon 将是一个有助于开发的包,因为我们的更改将直接反映在我们的服务器上,无需完全重启。但是要使用 nodemon,我们需要安装它。在 ide 中打开您的终端,并确保它位于服务器文件夹中。简单的写输入npm i nodemon

如果你想比较你的 package.json 和我的:

创建文件夹 src

然后我们在一个名为src的新文件夹中创建一个名为index.js的文件。

此外,我们创建一个名为types的文件夹,它位于src文件夹中。这是我们将要存储 graphQL 模式的地方。

我们的文件夹结构应该是这样的。

因为我们通过 apollo-server 访问 graphQL API,所以在后面的步骤中我们需要 apollo-server 连接,因此我们需要另一个包。添加第二个依赖项,在您的终端中编写并输入以下内容。

npm i apollo-server

创建我们的 graphQL 模式

我们在types文件夹内的一个名为coin.js的文件中编写我们的模式,并对我们的模式使用以下代码行,以便我们可以创建、读取、更新和删除(完整的 CRUD 应用程序)我们的数据。

此外,我们在 types 文件夹中创建了一个index.js文件,以便于导出我们的模式。如果您愿意,以后添加和删除其他类型会更容易。

存储数据

现在,我们只有一个模式,但是我们缺少数据库、解析器功能和服务器。让我们继续数据库。为了存储我们的数据,我们为我们的项目添加了一个名为 mongoose 的附加依赖项。这是一个与 mongoDB 交互的框架。

编写npm i mongoose以将其添加到我们的依赖项中。

我们创建一个名为models的文件夹,并在里面创建一个名为coin.js的文件

coin.js的内容看起来是这样的:

接下来,我们将名为index.js的文件添加到我们的模型文件夹中

我们需要解决方案

解析器函数是为 GraphQL 查询生成响应的函数的集合。因为我们从 mongoDB 数据库中提供数据,所以我们将使用之前在 models 文件夹中创建的 models 对象,该对象为我们提供了到数据库的连接。但是我们不必把它包含在这个文件中。稍后,当我们创建 apollo 服务器时,模型、模式和解析器功能是同一个上下文的一部分。然后,解析器函数可以访问模型。所以我们现在的任务是写解析器函数。

让我们创建三个文件夹。首先在src里面创建一个名为resolvers的文件夹。在 resolvers 文件夹中创建两个文件夹,一个名为mutations,另一个名为queries。如果您完成了这一步,您可以为名为coins.jsqueries文件夹创建第一个文件

好的,我们要在文件中添加什么?这个:

让我们快速浏览一遍。

这是一个异步函数,检索我们存储在数据库中的所有 ERC 20 硬币。调用解析器函数包括三到四个参数。

  1. 第一个是父解析器函数,如果有的话,但是我们没有,因此我们使用_来表示
  2. 第二个参数是 graphQL 查询的参数。但是请记住,我们在模式中只定义了一个查询(参见src/types/coin.js),如下所示:type Query{ coins: [ERC20Coin]}我们看到它没有任何参数,因此我们使用一个空对象作为第二个参数
  3. 第三个参数是 models 对象,它连接到我前面提到的 mongoDb 数据库。
  4. 第四个参数叫做info,但是你不应该为这个而烦恼,因为只有高级用例才需要它。

因为我们已经完成了单个查询的解析器函数,所以我们可以继续处理变异的解析器函数。

突变

在我们的模式中,我们定义了三种不同但简单的突变:createupdatedelete

我们从最简单的开始(尽管所有的都很容易)。

创建

一般结构与非变异查询相同。但是这次第二个参数不是空的,而是我们在模式中定义的输入对象。(如果你愿意,你可以再查一遍src/types/coin.js)

更新

参数与之前略有不同,因为我们在模式中定义,为了更新 ERC20Coin,我们需要一个id和一个input(例如名称)。通过使用 mongoose 框架,我们可以访问数据库中的 ERC20Coin,它具有我们在函数findOne中提供的 id

下一步是遍历输入对象(一个普通的 javascript 对象),它可能包含数据库条目的新的更新字段,在我们的例子中,这可能是一个不同的名称。如果需要,可以用更多的字段更新模式。最后的步骤包括再次将更新的模型保存到我们的数据库中,我们用这个函数返回新的更新的模型。

删除

如果你检查我们的模式,你会知道我们只使用了id,没有输入对象。就像我们使用猫鼬框架之前一样。在我们完成了三个旋变函数之后,这是非常简单的。

为我们的解析器文件夹创建 index.js 文件

解析器、突变和查询这三个文件夹都缺少一个index.js文件。让我们改进这一点。

在我们创建一些index.js文件之前,我想指出我们在这里用来导出的名字必须与我们给不同的突变和查询的名字相匹配。例如,我们的模式src/types/coin.js中的查询和突变如下所示。

我们需要为我们的出口选择的名称是coinscreateCoinupdateCoindeleteCoin

src/resolvers/queries中创建一个index.js文件,内容如下:

使用以下内容在src/resolvers/mutations中创建一个index.js文件,名称createCoinupdateCoindeleteCoin再次与模式中的名称相匹配。

此外,我们在src/resolvers中创建一个index.js文件

我们可以使用扩展操作符...,这样一旦我们添加了新的突变或查询,就不必更改这个文件。

现在我们已经有了模式以及允许我们从 mongoDB 数据库中检索数据的功能代码。我们将继续在云中创建一个数据库。

在 cloud.mongodb 上创建一个数据库

我们将在一个免费的云服务器上使用一个数据库来进行演示和教学。去https://www.mongodb.com/atlas/database免费试用。登录后:

  • 寻找一个写着创建的绿色按钮
  • 选择共享云
  • 选择离您的物理位置最近的空闲层位置
  • 跳过其他设置,直到进入安全快速入门

在这里,您应该选择用户名和密码来验证您的连接。选择一个你能记住的用户名和密码并写下来。

作者截图

然后会询问您想从哪里连接。

作者截图

本地环境中选择连接,并添加 IP 地址 0.0.0.0/0,这将允许所有 IP 地址连接到您的数据库(这是一个安全问题,不建议用于生产,但由于这是一个教程,我们坚持使用它,因为一旦我们使用 heroku,事情会变得更容易。但是请记住,如果您想要创建一个真实世界的项目,请在这里认真对待这一点,并在您自己对这一主题的研究中投入一些时间)。

当您继续时,您应该会看到类似这样的内容。点击 Cluster0 旁边的按钮Connect

作者截图

就拿这张图片中的“连接您的应用程序”选项来说,它是中间的选项。单击“连接应用程序”后,下一页将让您选择一个驱动程序。使用最新版本的 Node.js。

重要信息:复制连接字符串!就在下面。

作者截图

通过 node.js 连接到数据库的设置

复制连接字符串后。回到您的编辑器,在服务器目录中创建一个.env文件。我在这里列出了文件夹结构应该是什么样子。

在这个文件中,将您从 cloud.mongodb 仪表板上复制的字符串分配给一个名为DATABASE_URL的变量

DATABASE_URL = mongodb+srv://...

为了使用这个.env文件及其变量,你需要安装一个名为dotenv的包

npm i dotenv

此外,我们应该在src中创建一个名为config的新文件夹,其中包含一个名为db.js的文件

在这个文件db.js中,我们将编写一些连接到数据库的基本代码。完成后,我们就可以创建我们的服务器了。

首先,我们要求包读取我们之前创建的.env文件。

  • require('dotenv').config();

这是我们访问变量的方式:

  • const DATABASE_URL = process.env.DATABASE_URL;

我们需要一个让我们与 mongoDB 数据库连接并封装其 API 的包

  • const mongoose = require('mongoose');

下一步包括编写一个函数,让我们连接到数据库,我们称之为connectDB。我们导出这个函数,因为我们以后需要它与我们的 apollo 服务器结合使用。

此外,我们创建一个对象来帮助我们记录连接失败。

到目前为止,我们已经介绍了:我们编写了一个 graphQL 模式,编写了解析器函数来填充对我们的 API 所做的查询,并且我们设置了连接到我们的数据库的所有东西。因此,让我们创建公开 API 的 apollo 服务器。之后,我们将把这台阿波罗服务器部署到 heroku。

创建一个阿波罗服务器

转到 src 目录中的index.js,这个目录是我们之前创建的,但是仍然是空的。

我们首先列出我们之前编写的代码以及 apollo-server 包中的所有需求。

在连接到我们的 apollo 服务器之前,我们确保连接到我们的数据库:

connectDB();添加到该文件的下一行

然后我们创建一个服务器对象并开始运行它

来测试一下吧!转到您的终端的server文件夹中,运行npm run start

我附上了我的终端响应和我的浏览器的截图供你参考。

我的终端(第一个)和我在本地主机的浏览器(第二个)

作者截图

作者截图

在你的浏览器中按下显示Query your server的按钮

测试您的 API

我们现在将探索我们的 GraphQL API 的功能。我们将使用我们在模式中定义的所有突变和单个查询,并将对象从 mongoDB 写入我们的数据库。如果一切正常,我们甚至可以使用 Heroku 将我们的 apollo 服务器实例部署到云中,使其公开可用。

在我们的服务器上编写我们的第一个查询

我们定义了一个名为coins的查询,它返回一个包含 ERC20Coin 类型对象的数组。

结果看起来像这样

我们得到了一个空数组。这并不令人惊讶。因为它从我们的 mongoDB 数据库返回数据,但是我们还没有向这个数据库写入任何东西。所以让我们改变这一点。

书写突变

所以让我们在数据库中添加一些硬币。我选了以太坊,索拉纳,通量,氦。

让我们再次从数据库中查询硬币。

多写一些突变

我们只研究了一种变异,即在我们的数据库中创建条目。让我们尝试删除硬币,然后更新硬币的数据。

永远记住,当我们编写模式时,我们需要坚持这一点。变异要求我们提供硬币的身份。id 来自 mongoDB 数据库,并引用数据库中的一个实体。这样,我们的解析器就能够向我们的数据库发送正确的指令。

我决定从数据库中删除以太坊。我从之前的查询中知道以太坊数据库条目的 id 是什么。

让我们查询数据库中的条目,看看数据库是否反映了我们的更改。

如我们所见,更改已成功写入数据库。

更新

到目前为止,我们还没有使用的最后一种 graphQL API 查询是 updateCoin 变体。我们的模式告诉我们,我们需要提供数据库中文档的 id,此外,我们还需要输入。输入是用于更新数据库条目的数据。我决定更新氦入口的名称,并用符号(HNT)扩展字符串。如果成功,变异将返回数据库条目的 id 和名称。我在这里也包含了这段代码

如前所述,变异会返回数据。看起来很有希望。

如果我们从头开始重复代码,我们可以看到我们有三个条目,我们从以太坊副本和索拉纳的删除反映在我们的数据库中。此外,我们对氦的更新发生了,我们可以从回应中看到

一切都如我们所料。我们的 graphQL API 对我们来说是可访问的,没有任何错误。如果我们想与他人分享我们的数据,我们需要一台其他人也能访问的服务器。我建议你也做这一部分,以了解为数据科学项目创建后端的整体情况。

在 Heroku 上部署服务器

Heroku 是一个云平台即服务。我们可以将应用程序部署到 heroku,然后我们的应用程序就可以通过云使用了。要上传我们的阿波罗服务器,我们需要一个帐户。请注册或登录 heroku。然后选择创建新的应用程序。

作者截图

然后选择一个名称(必须对每个人都是唯一的)和一个服务器位置。在撰写本文时,免费层仅在欧洲和美国两个地方提供。

由于我们通过命令行界面(CLI)部署我们的应用程序,如果您以前没有使用过,您需要自己安装。但是这很简单,看下面这个链接https://devcenter.heroku.com/articles/heroku-cli或者对于 macOS,你可以在假设你已经安装了 brew 的情况下,用最少的努力来安装 CLI。在您的终端中编写并执行:

brew tap heroku/brew && brew install heroku

在你做完那件事之后。我们继续用 heroku 初始化和部署我们的阿波罗服务器。

接下来使用heroku login(它使用 CLI 登录 heroku)

然后确保你终端中的当前文件夹是我们项目中的server。我按正确的顺序列出了你需要采取的以下步骤。

  1. git init
  2. heroku git:remote -a <yourprojectName>
  3. git add -A(暂存您的文件)
  4. git commit -m "first commit"

但是先不要推送到 heroku,因为我们需要将来自.env的 DATABASE_URL 存储在 heroku 应用仪表板的Config Vars

在左侧,您添加了DATABASE_URL,并为该值取您的 mongoDB 数据库的连接字符串。

作者截图

在你添加了这一对之后,你可以将你的项目推送到 heroku。

git push heroku master

当你成功地将它推送到 heroku 的远程存储库后,你可以在右上角打开你的应用程序。

作者截图

这将是一些通用的阿波罗网页,说你现在可以提出一些职位的要求。哇你完成了。

结束语

如果我是你,我会为我所做的感到骄傲。现在,您对让每个人都能访问您的数据驱动应用程序所需的技能有了更全面的了解。您希望与他人共享您创建的哪个仪表板?让互联网上的其他人看到你的仪表板是一个具有挑战性的进一步练习,但在完成这里的一切后,你可以做到。

一个更简单的练习:

你可以试着通过 postman 访问你的 API。请求的内容和以前在操场上一样,唯一的不同是现在你通过万维网发送请求。

我很乐意在我写的其他博客上欢迎你,或者在 linkedin 或 twitter 上收到你的消息。

使用 Docker 为 Google Colab 创建自己的 GPU 加速 Jupyter 笔记本服务器

原文:https://towardsdatascience.com/create-your-own-gpu-accelerated-yupyter-notebook-server-with-google-colab-using-docker-2fa14900bab5

循序渐进的指南

SpaceX 在 Unsplash 上拍摄的

Y 你想在你自己的电脑或服务器上训练你的深度学习模型,你不想处理免费环境如 Google Colab 或 Kaggle 的资源限制,你不想花费数小时安装运行训练所需的所有驱动程序和软件包?那么我有好消息要告诉你,因为今天我们将探讨如何在你自己的系统上使用 Docker 创建你自己的 Jupyter 笔记本服务器,如何启用 GPU 加速,以及如何从 Google Colab 连接到你的服务器。

首先,我想简单地谈谈 Docker,以及为什么它是 ML 从业者的一个很好的工具,他们希望专注于 ML,而不是处理复杂的环境问题,如驱动程序、不同版本和依赖关系等。之后,我们看看安装(剧透警告,这将是简短和容易的)。最后,我将向您展示,如何使用 Google Colab 连接到您的服务器。瞧,现在你可以用自己的电脑运行笔记本了。

概述

  1. 简单地说就是 Docker
  2. 安装指南
  3. 运行您的第一台笔记本电脑
  4. 结论

1.简单地说,码头工人

您可能已经听到人们谈论 Docker 和容器,以及它与虚拟化的关系。我记得我很困惑,我想,如果我可以使用虚拟机,为什么我要在容器中使用 Linux 发行版,比如 Ubuntu?有许多很好的理由,我希望你在读完这一节后同意我的观点。

Docker 能够在隔离的容器中运行应用程序。容器是一个轻量级的构造,包含了应用程序运行所需的一切。这已经是主要优势之一了。因为您需要的所有东西都打包在容器中,所以您可以在不同的主机上执行容器,而不管该主机上当前安装了什么。因此,您可以与同事共享您的容器,并确保您的应用程序以相同的方式运行。它们都与硬件和平台无关。现在让我们更深入一层,检查 Docker 的架构:

作者的 Docker 架构灵感来自 Docker

Docker 遵循客户端-服务器原则。客户端与 Docker 守护进程通信,除了其他任务之外,Docker 守护进程还管理图像、处理依赖关系并执行容器。Docker 守护程序可以在本地主机上,也可以在远程主机上。图像可以被理解为带有如何构建容器的指令的蓝图。容器是一个图像的可运行实例。单个图像的多个容器可以并行运行,这意味着它可以轻松缩放。默认情况下,容器与其他容器和主机隔离。可选地,可以配置接口,例如打开端口。

映像安装在主机上。如果您想创建一个容器,而映像没有安装在主机上,Docker 会自动从标准注册表中下载映像。

现在让我们联系到我们的主要目标:运行 GPU 加速的笔记本来训练我们的机器学习模型。前面我说过容器是硬件不可知的。大多数情况下都是这样。为了在可接受的时间内训练大规模模型,GPU 是必不可少的,由于其硬件架构,可以并行化大规模计算。英伟达是专门用于机器学习应用的 GPU 的大型供应商,包括一个强大的软件框架。由于我们将在本教程中关注 Nvidia GPUs,我们限制我们的硬件不可知的方法。

Docker 本身不支持 GPU 加速。我们需要使用 Nvidia 工具包。让我们看看下面的图片,以了解这在高层次上是如何工作的:

作者的 Nvidia 容器工具包灵感来自于 Nvidia

我们从堆栈的底部开始。我们有一个带有一个或多个 GPU 的服务器(或任何类型的计算机)。这台计算机的操作系统安装了 docker 引擎。支持我们的 GPU 所需的第一个组件是安装它的专用驱动程序。对于 Nvidia,这些被称为 CUDA 驱动程序。Docker 引擎可以创建隔离的容器,为某个应用程序分配操作系统资源。CUDA 工具包支持容器内的 GPU 驱动程序。

2.安装指南

开始之前,我们要做的是:

  1. 我们确保激活了 wsl 2(Linux 的 Windows 子系统)并安装了一个 Linux 发行版,即 Ubuntu
  2. 我们安装 Docker 桌面
  3. 我们启用 GPU 支持
  4. 我们拉官方 TensorFlow Jupyter 笔记本服务器带 GPU 支持

作为参考,此程序已在 Win11、英特尔酷睿 i7 第 10 代和英伟达 GeForce RTX 2060 上进行了测试。你也可以在其他操作系统上安装 Docker,比如 Ubuntu。你甚至可以在 Ubuntu 虚拟机上安装 Docker。但是你应该记住,从虚拟机内部你不能访问你的物理 GPU,这意味着你不能利用 GPU 加速。

注意:以下命令必须在 Windows PowerShell 和 Linux shell 中执行。带有 PS >的代码嵌入是 PowerShell 命令,带有$的代码嵌入是 Linux 命令。

步骤 1:确保 WSL2 被激活并安装 Ubuntu

要在 Linux 的 Windows 子系统中安装 ubuntu,只需打开 PowerShell 并执行:

PS> wsl --install -d Ubuntu

安装完成后,使用以下命令检查您正在运行的 WSL 版本:

PS> wsl -l -v

如果您的版本不是 2,您可以使用以下命令进行设置:

PS> wsl --set-default-version 2

现在,通过浏览开始菜单或在 PowerShell 中键入wsl来启动 Ubuntu 子系统。现在应该要求您创建一个新用户并分配一个密码。该用户将自动成为具有管理员权限的默认用户,并在启动时自动登录。

注意:如果不要求您创建新用户,而是以 root 用户身份登录,您必须手动创建一个用户,使其成为默认用户并添加管理员权限。

  1. 添加新用户(在 WSL 中以 root 用户身份登录):
$ sudo adduser <USER_NAME>

2.将您的用户设置为默认用户(在 PowerShell 中):

PS> ubuntu config — default-user <USER_NAME>

3.将您的用户添加到 sudoers(在 WSL 中以 root 用户身份登录):

$ usermod -aG sudo <USER_NAME>

步骤 2:安装 Docker 桌面

DockerHub 下载 Docker Desktop,并按照安装说明进行操作。确保在安装过程中选择 WSL2。

步骤 3:启用 GPU 加速支持

为了用我们的 Nvidia GPU 加速我们的机器学习代码,我们必须安装 CUDA 框架。因此,下载并安装支持 WLS 的 Nvidia CUDA 驱动程序。安装 CUDA 驱动程序后,打开 Ubuntu 子系统,按照 Nvidia安装指南安装 Nvidia 容器工具包。为了更好的可读性,我复制了文档中的三个步骤。

  1. 设置稳定的存储库和 GPG 键。支持 WSL 2 的运行时更改可在稳定的存储库中获得:
$ distribution=$(. /etc/os-release;echo $ID$VERSION_ID)$ curl -s -L [https://nvidia.github.io/nvidia-docker/gpgkey](https://nvidia.github.io/nvidia-docker/gpgkey) | sudo apt-key add -$ curl -s -L [https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list](https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list) | sudo tee /etc/apt/sources.list.d/nvidia-docker.list

2.更新软件包列表后,安装 NVIDIA 运行时软件包(及其依赖项):

$ sudo apt-get update$ sudo apt-get install -y nvidia-docker2

3.打开一个单独的 WSL 2 窗口,并使用以下命令重新启动 Docker 守护程序以完成安装:

$ sudo service docker stop$ sudo service docker start

就是这样!现在,您可以充分利用 Docker 容器中的 GPU。要测试安装是否成功,您可以运行一个 GPU 基准测试容器。打开 Docker 桌面,然后打开 PowerShell 来执行

PS> docker run --gpus all nvcr.io/nvidia/k8s/cuda-sample:nbody nbody -gpu -benchmark

您现在应该会看到与我的结果类似的内容,如下所示:

GPU 基准测试容器的结果

第 4 步:拉张量流 Docker 图像

TensorFlow 提供现成的用户 Docker 映像,用于配置包含运行 TensorFlow 所需的所有包的容器。可以在这里找到可能配置的完整概述。在这里,我们正在测试最新的稳定 TensorFlow 构建与 GPU 支持,包括一个 Jupyter 笔记本电脑服务器。确保 docker 正在运行,并在 PowerShell 中执行以下命令来提取所述 Docker 映像:

PS> docker pull tensorflow/tensorflow:latest-gpu-jupyter

通过这最后一步,我们现在可以运行我们的笔记本了!细节在下一节。

3.运行您的第一台笔记本电脑

在本部分教程中,我们将:

  1. 使用所需的配置启动我们支持 GPU 的 TensorFlow 容器,
  2. 启动 Jupyter 笔记本服务器,
  3. 将 Google Colab 连接到我们的本地运行时。

注:如果你手头没有笔记本,看看这个笔记本,我在里面实现了一个简单的生成对抗网络。

步骤 1:启动支持 GPU 的 TensorFlow 容器

首先,我们确保 docker 正在运行,并在 PowerShell 中执行下面的命令来创建一个新容器。

PS> docker run --gpus all -p 8888:8888 -it --rm tensorflow/tensorflow:latest-gpu-jupyter bash

配置:

  • 运行:运行一个新的容器
  • —所有 GPU:使用所有可用的 GPU
  • -p 8888:8888 :向主机发布容器的端口 8888:8888
  • -it :保持 STDIN 打开,分配一个伪 tty。
  • —RM tensor flow/tensor flow:latest-GPU-jupyter:如果容器存在,自动移除容器
  • bash :打开容器的 bash,从容器内部执行命令

随着上面命令的执行,从 TensorFlow 图像创建容器,bash 命令行打开:

GPU 支持的 TensorFlow 容器和 Jupyter 笔记本服务器

步骤 2:启动 Jupyter 笔记本服务器

我们现在在容器内部,可以访问 bash。现在我们必须手动启动 Jupyter 笔记本服务器,主要是因为我们必须允许 Google Colab 作为 origin。

$ jupyter notebook --notebook-dir=/tf --ip 0.0.0.0 --no-browser --allow-root --NotebookApp.allow_origin='https://colab.research.google.com' --port=8888 --NotebookApp.port_retries=0

该命令启动本地主机上的服务器,并在会话期间生成一个秘密访问令牌。下图突出显示了这个令牌。它需要从 Google Colab 内部连接到本地运行时

生成访问令牌以连接到本地 Jupyter 笔记本运行时

步骤 3:将 Google Colab 连接到我们的本地运行时

首先在 Google Colab 中打开一个笔记本。

注意:如果你手头没有笔记本,看看这个笔记本,我在里面实现了一个简单的生成对抗网络。

在笔记本中,导航到右上角,然后按“connect(连接)”按钮旁边的小箭头。选择“连接到本地运行时”,如下图所示

连接到本地运行时的导航

将弹出一个新窗口,您可以在其中粘贴上一步中突出显示的令牌。确保将 127.0.0.1 改为 localhost,因为 Google Colab 要求这种格式。现在只需按下连接,你应该完成了。

本地连接设置

现在,您可以从笔记本电脑中查看所有可用设备的设备列表。

注:在我的深度学习包Github 上的 DeepSaki中,你可以在许多有用的模块中找到与硬件检测和性能提升相关的助手,用于你的 GPU 训练。

本地机器上所有可用设备的列表

如上面的截图所示,检测到一个 GPU,你可以看到它是我的 Nvidia GeForce RTX 2060。

4.结论

Docker 可以在隔离的容器中运行应用程序,并提供执行应用程序所需的一切。如果你想从容器内访问你的 GPU,就需要 Nvidia 的 CUDA 工具包。这允许提取包含 Jupyter 笔记本服务器的 GPU 支持的 TensorFlow 图像。我们可以配置 Google Colab 连接到这个本地运行时,并充分利用我们的 GPU。

如果你想用你的 GPU 进一步加速你的训练,我推荐你阅读我的关于如何在 GPU/TPU 上用混合精度加速你的 TensorFlow 训练的文章

用 Python 从头开始创建 K-Means 聚类算法

原文:https://towardsdatascience.com/create-your-own-k-means-clustering-algorithm-in-python-d7d4c9077670

通过自己实现 k-means 聚类巩固你的知识

介绍

k-means 聚类是一种无监督的机器学习算法,旨在根据数据点的相似性将数据集分割成组。无监督模型有自变量,没有因变量

假设您有一个二维标量属性的数据集:

图片作者。

如果该数据集中的点属于不同的组,且这些组之间的属性差异很大,但组内的属性差异不大,则绘制时这些点应形成聚类。

图片作者。

图 1: 具有不同属性组的点的数据集。

该数据集清楚地显示了 3 类不同的数据。如果我们试图将一个新的数据点分配给这三组中的一组,可以通过找到每组的中点(质心)并选择最近的质心作为未分配数据点的组来实现。

图片作者。

图 2: 数据点被分割成用不同颜色表示的组。

算法

对于给定的数据集,k 被指定为点所属的不同组的数量。这 k 个质心首先被随机初始化,然后执行迭代以优化这 k 个质心的位置,如下所示:

  1. 计算从每个点到每个质心的距离。
  2. 点被分配给它们最近的质心。
  3. 质心被移动为属于它的点的平均值。如果质心没有移动,算法结束,否则重复。

数据

为了评估我们的算法,我们将首先在二维空间中生成一个组数据集。sklearn.datasets 函数 make_blobs 创建二维正态分布的分组,并分配与所述点所属的组相对应的标签。

import seaborn as sns
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScalercenters = 5
X_train, true_labels = make_blobs(n_samples=100, centers=centers, random_state=42)
X_train = StandardScaler().fit_transform(X_train)sns.scatterplot(x=[X[0] for X in X_train],
                y=[X[1] for X in X_train],
                hue=true_labels,
                palette="deep",
                legend=None
                )plt.xlabel("x")
plt.ylabel("y")
plt.show()

图片作者。

图 3: 我们将用来评估我们的 k 均值聚类模型的数据集。

该数据集提供了 k-means 算法的独特演示。观察橙色点一反常态地远离其中心,并直接位于紫色数据点群中。这个点不能被准确地分类为属于正确的组,因此即使我们的算法工作得很好,它也应该被错误地表征为紫色组的成员。

模型创建

助手功能

在这个算法中,我们需要多次计算一个点和一组点之间的距离。为此,让我们定义一个计算欧几里德距离的函数。

def euclidean(point, data):
    """
    Euclidean distance between point & data.
    Point has dimensions (m,), data has dimensions (n,m), and output will be of size (n,).
    """
    return np.sqrt(np.sum((point - data)**2, axis=1))

履行

首先,用 k 值和寻找最佳质心位置的最大迭代次数初始化 k 均值聚类算法。如果在优化质心位置时没有考虑最大迭代次数,则存在运行无限循环的风险。

class KMeans: def __init__(self, n_clusters=8, max_iter=300):
        self.n_clusters = n_clusters
        self.max_iter = max_iter

现在,大部分算法是在将模型拟合到训练数据集时执行的。

首先,我们将在测试数据集的域中随机初始化质心,使其均匀分布。

# Randomly select centroid start points, uniformly distributed across the domain of the dataset
min_, max_ = np.min(X_train, axis=0), np.max(X_train, axis=0)
self.centroids = [uniform(min_, max_) for _ in range(self.n_clusters)]

接下来,我们执行优化质心位置的迭代过程。

优化过程是将质心位置重新调整为属于它的点的平均值。重复这个过程,直到质心停止移动,或者经过了最大迭代次数。我们将使用 while 循环来说明这个过程没有固定的迭代次数。此外,您还可以使用 for 循环,该循环重复 max_iter 次,并在质心停止变化时中断。

在开始 while 循环之前,我们将初始化退出条件中使用的变量。

iteration = 0
prev_centroids = None

现在,我们开始循环。我们将遍历训练集中的数据点,将它们分配给一个初始化的空列表。sorted_points 列表为每个质心包含一个空列表,数据点一旦被赋值就被追加到这个列表中。

while np.not_equal(self.centroids, prev_centroids).any() and iteration < self.max_iter:
    # Sort each data point, assigning to nearest centroid
    sorted_points = [[] for _ in range(self.n_clusters)]
    for x in X_train:
        dists = euclidean(x, self.centroids)
        centroid_idx = np.argmin(dists)
        sorted_points[centroid_idx].append(x)

既然我们已经将整个训练数据集分配给它们最近的质心,我们可以更新质心的位置并完成迭代。

# Push current centroids to previous, reassign centroids as mean of the points belonging to them
    prev_centroids = self.centroids
    self.centroids = [np.mean(cluster, axis=0) for cluster in sorted_points]
    for i, centroid in enumerate(self.centroids):
        if np.isnan(centroid).any():  # Catch any np.nans, resulting from a centroid having no points
            self.centroids[i] = prev_centroids[i]
    iteration += 1

迭代完成后,再次检查 while 条件,算法将继续,直到质心被优化或最大迭代次数被通过。下面介绍了完全适合的方法。

class KMeans: def __init__(self, n_clusters=8, max_iter=300):
        self.n_clusters = n_clusters
        self.max_iter = max_iter def fit(self, X_train): # Randomly select centroid start points, uniformly distributed across the domain of the dataset
        min_, max_ = np.min(X_train, axis=0), np.max(X_train, axis=0)
        self.centroids = [uniform(min_, max_) for _ in range(self.n_clusters)] # Iterate, adjusting centroids until converged or until passed max_iter
        iteration = 0
        prev_centroids = None
        while np.not_equal(self.centroids, prev_centroids).any() and iteration < self.max_iter:
            # Sort each datapoint, assigning to nearest centroid
            sorted_points = [[] for _ in range(self.n_clusters)]
            for x in X_train:
                dists = euclidean(x, self.centroids)
                centroid_idx = np.argmin(dists)
                sorted_points[centroid_idx].append(x) # Push current centroids to previous, reassign centroids as mean of the points belonging to them
            prev_centroids = self.centroids
            self.centroids = [np.mean(cluster, axis=0) for cluster in sorted_points]
            for i, centroid in enumerate(self.centroids):
                if np.isnan(centroid).any():  # Catch any np.nans, resulting from a centroid having no points
                    self.centroids[i] = prev_centroids[i]
            iteration += 1

最后,让我们创建一个方法来评估一组点的质心,我们已经优化了我们的训练集。该方法返回每个点的质心和所述质心的索引。

def evaluate(self, X):
        centroids = []
        centroid_idxs = []
        for x in X:
            dists = euclidean(x, self.centroids)
            centroid_idx = np.argmin(dists)
            centroids.append(self.centroids[centroid_idx])
            centroid_idxs.append(centroid_idx) return centroids, centroid_idx

首次模型评估

现在我们终于可以部署我们的模型了。让我们在原始数据集上训练和测试它,看看结果。我们将保留绘制数据的原始方法,通过颜色分离真实标注,但现在我们将额外通过标记样式分离预测标注,以查看模型的执行情况。

kmeans = KMeans(n_clusters=centers)
kmeans.fit(X_train)# View results
class_centers, classification = kmeans.evaluate(X_train)
sns.scatterplot(x=[X[0] for X in X_train],
                y=[X[1] for X in X_train],
                hue=true_labels,
                style=classification,
                palette="deep",
                legend=None
                )
plt.plot([x for x, _ in kmeans.centroids],
         [y for _, y in kmeans.centroids],
         '+',
         markersize=10,
         )plt.show()

图片作者。

图 4: 一个失败的例子,一个质心没有点,一个包含两个集群。

图片作者。

图 5: 一个失败的例子,一个质心没有点,两个包含两个簇,两个分裂一个簇。

图片作者。

图 6: 两个质心包含一个半簇,两个质心分裂一个簇的失败例子。

重新评估质心初始化

看来我们的模型表现不太好。我们可以从这三个失败的例子中推断出两个主要问题。

  1. 如果质心被初始化为远离任何组,它不太可能移动。(例:图 4 中的右下质心。)
  2. 如果质心被初始化得太近,它们不太可能彼此分开。(例如:图 6 中绿色组的两个质心。)

我们将开始用初始化质心位置的新过程来解决这些问题。这种新方法被称为 k-means++算法。

  1. 将第一质心初始化为数据点之一的随机选择。
  2. 计算每个数据点和所有质心之间的距离总和。
  3. 随机选择下一个质心,其概率与到质心的总距离成比例。
  4. 返回步骤 2。重复上述步骤,直到所有质心都已初始化。

该代码包含在下面。

# Initialize the centroids, using the "k-means++" method, where a random datapoint is selected as the first,
# then the rest are initialized w/ probabilities proportional to their distances to the first
# Pick a random point from train data for first centroid
self.centroids = [random.choice(X_train)]for _ in range(self.n_clusters-1):
    # Calculate distances from points to the centroids
    dists = np.sum([euclidean(centroid, X_train) for centroid in self.centroids], axis=0)
    # Normalize the distances
    dists /= np.sum(dists)
    # Choose remaining points based on their distances
    new_centroid_idx, = np.random.choice(range(len(X_train)), size=1, p=dists)
    self.centroids += [X_train[new_centroid_idx]]

如果我们运行这个新模型几次,我们会看到它表现得更好,但仍然不总是完美的。

图片作者。

图 7: 实现 k-means++初始化方法后的理想收敛。

结论

就这样,我们结束了。我们学习了一个简单而优雅的无监督机器学习模型的实现。完整的项目代码包含在下面。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler
from numpy.random import uniform
from sklearn.datasets import make_blobs
import seaborn as sns
import random def euclidean(point, data):
    """
    Euclidean distance between point & data.
    Point has dimensions (m,), data has dimensions (n,m), and output will be of size (n,).
    """
    return np.sqrt(np.sum((point - data)**2, axis=1)) class KMeans: def __init__(self, n_clusters=8, max_iter=300):
        self.n_clusters = n_clusters
        self.max_iter = max_iter def fit(self, X_train): # Initialize the centroids, using the "k-means++" method, where a random datapoint is selected as the first,
        # then the rest are initialized w/ probabilities proportional to their distances to the first
        # Pick a random point from train data for first centroid
        self.centroids = [random.choice(X_train)] for _ in range(self.n_clusters-1):
            # Calculate distances from points to the centroids
            dists = np.sum([euclidean(centroid, X_train) for centroid in self.centroids], axis=0)
            # Normalize the distances
            dists /= np.sum(dists)
            # Choose remaining points based on their distances
            new_centroid_idx, = np.random.choice(range(len(X_train)), size=1, p=dists)
            self.centroids += [X_train[new_centroid_idx]] # This initial method of randomly selecting centroid starts is less effective
        # min_, max_ = np.min(X_train, axis=0), np.max(X_train, axis=0)
        # self.centroids = [uniform(min_, max_) for _ in range(self.n_clusters)] # Iterate, adjusting centroids until converged or until passed max_iter
        iteration = 0
        prev_centroids = None
        while np.not_equal(self.centroids, prev_centroids).any() and iteration < self.max_iter:
            # Sort each datapoint, assigning to nearest centroid
            sorted_points = [[] for _ in range(self.n_clusters)]
            for x in X_train:
                dists = euclidean(x, self.centroids)
                centroid_idx = np.argmin(dists)
                sorted_points[centroid_idx].append(x) # Push current centroids to previous, reassign centroids as mean of the points belonging to them
            prev_centroids = self.centroids
            self.centroids = [np.mean(cluster, axis=0) for cluster in sorted_points]
            for i, centroid in enumerate(self.centroids):
                if np.isnan(centroid).any():  # Catch any np.nans, resulting from a centroid having no points
                    self.centroids[i] = prev_centroids[i]
            iteration += 1 def evaluate(self, X):
        centroids = []
        centroid_idxs = []
        for x in X:
            dists = euclidean(x, self.centroids)
            centroid_idx = np.argmin(dists)
            centroids.append(self.centroids[centroid_idx])
            centroid_idxs.append(centroid_idx) return centroids, centroid_idxs # Create a dataset of 2D distributions
centers = 5
X_train, true_labels = make_blobs(n_samples=100, centers=centers, random_state=42)
X_train = StandardScaler().fit_transform(X_train)# Fit centroids to dataset
kmeans = KMeans(n_clusters=centers)
kmeans.fit(X_train)# View results
class_centers, classification = kmeans.evaluate(X_train)
sns.scatterplot(x=[X[0] for X in X_train],
                y=[X[1] for X in X_train],
                hue=true_labels,
                style=classification,
                palette="deep",
                legend=None
                )
plt.plot([x for x, _ in kmeans.centroids],
         [y for _, y in kmeans.centroids],
         'k+',
         markersize=10,
         )plt.show()

感谢阅读!
在 LinkedIn 上和我联系
在 GitHub 上看到这个项目

用 Python 从头开始创建 K-最近邻算法

原文:https://towardsdatascience.com/create-your-own-k-nearest-neighbors-algorithm-in-python-eb7093fc6339

通过亲身实践来巩固你对 KNN 的了解

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

介绍

有一句名言说:“你是和你相处时间最长的五个人的平均值。”尽管我们不会为你的友谊质量建模(任何人的作品集)。),本教程将教授一种简单直观的算法方法来根据数据的邻居对数据进行分类。k-最近邻(knn)算法是一种监督学习算法,具有优雅的执行和令人惊讶的简单实现。正因为如此,knn 为机器学习初学者提供了一个很好的学习机会,用几行 Python 代码创建一个强大的分类或回归算法。

算法

Knn 是一个监督的机器学习算法。监督模型既有目标变量又有独立变量的目标变量或因变量,表示为 y,取决于自变量,是您寻求预测的值。标为 X(单值)或 X(多值)的独立变量提前已知,用于预测 y。

Knn 既可以用于分类也可以用于回归分类模型预测一个分类目标变量,而回归模型预测一个数字目标。

假设您有一个标量属性和对应于这些属性的类的数据集。

图片作者。

图片作者。

这里,n 是数据点的总数,m 是类的总数。y 不是一个类,它也可以是一个标量值,knn 可以用作回归,但对于本教程,我们将重点放在分类上。

在这个二维例子中,我们可以很容易地在二维空间中看到这些点。假设类将倾向于与该空间中相同类的点聚集在一起,我们可以通过在其附近最频繁出现的类来对新点进行分类。因此,在给定点 k 被指定为所述点附近要考虑的邻居的数量,并且从这些邻居中,最频繁出现的类被预测为即将到来的点的类。

图片作者。

图 1: 当 k=1 时,此点归为第 1 组。

图片作者。

图 2: 当 k=3 时,该点被归为 0 组。

数据

我们将用 UCI 机器学习库虹膜数据集来评估我们的算法。但是,任何由标量输入组成的分类数据集都可以。我们将对数据集进行解包,并对属性进行标准化,使其均值和单位方差为零。这样做是因为我们不想判断哪些特征对于预测类是最重要的(对于这个分析!).最后,我们将把数据集分成训练集和测试集,测试集由 20%的原始数据集组成。

from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler# Unpack the iris dataset, from UCI Machine Learning Repository
iris = datasets.load_iris()
X = iris['data']
y = iris['target']# Preprocess data
X = StandardScaler().fit_transform(X)# Split data into train & test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

模型创建

助手功能

在这个算法中,我们需要找出给定组中多次出现的最常见的元素。下面的函数给出了一种直观的 Pythonic 式方法来查找列表中最常见的元素。

def most_common(lst):
    '''Returns the most common element in a list'''
    return max(set(lst), key=lst.count)

接下来,我们需要计算数据集中一个点和每个点之间的距离。最常见和直观的方法是欧几里德距离,但是也可以使用其他距离方法。

def euclidean(point, data):
    '''Euclidean distance between a point  & data'''
    return np.sqrt(np.sum((point - data)**2, axis=1))

实施

现在,让我们开始构造一个 knn 类。对于给定的 knn 分类器,我们将指定 k 和一个距离度量。为了保持该算法的实现类似于广泛使用的 scikit-learn 套件,我们将初始化 self。X_train 和 self.y_train,但是这可以在初始化时完成。

class KNeighborsClassifier():
    def __init__(self, k=5, dist_metric=euclidean):
        self.k = k
        self.dist_metric = dist_metric def fit(self, X_train, y_train):
        self.X_train = X_train
        self.y_train = y_train

接下来,knn 算法的大部分由预测方法执行。逐点迭代属性数据集(X_test)。对于每个数据点,执行以下步骤:

  1. 计算到训练数据集中每个点的距离
  2. 训练数据集类按到数据点的距离排序
  3. 前 k 个类保存并存储在邻居列表中。现在,我们只需将最近邻居列表映射到我们的 most_common 函数,返回 X_test 中传递的每个点的预测列表。
class KNeighborsClassifier():
    def __init__(self, k=5, dist_metric=euclidean):
        self.k = k
        self.dist_metric = dist_metric def fit(self, X_train, y_train):
        self.X_train = X_train
        self.y_train = y_train def predict(self, X_test):
        neighbors = []
        for x in X_test:
            distances = self.dist_metric(x, self.X_train)
            y_sorted = [y for _, y in sorted(zip(distances, self.y_train))]
            neighbors.append(y_sorted[:self.k]) return list(map(most_common, neighbors))

最后,定义了一种评估方法来方便地评估模型的性能。属性及其类的数据集作为 X_test 和 y_test 进行传递,并且将属性的模型预测与实际类进行比较。

class KNeighborsClassifier():
    def __init__(self, k=5, dist_metric=euclidean):
        self.k = k
        self.dist_metric = dist_metric def fit(self, X_train, y_train):
        self.X_train = X_train
        self.y_train = y_train def predict(self, X_test):
        neighbors = []
        for x in X_test:
            distances = self.dist_metric(x, self.X_train)
            y_sorted = [y for _, y in sorted(zip(distances, self.y_train))]
            neighbors.append(y_sorted[:self.k]) return list(map(most_common, neighbors)) def evaluate(self, X_test, y_test):
        y_pred = self.predict(X_test)
        accuracy = sum(y_pred == y_test) / len(y_test)
        return accuracy

信不信由你,我们已经完成了——我们可以轻松地部署这个算法来建模分类问题。但是,为了完整性,我们应该为 iris 数据集优化 k。我们可以通过迭代 k 的范围并绘制模型的性能来做到这一点。

accuracies = []
ks = range(1, 30)
for k in ks:
    knn = KNeighborsClassifier(k=k)
    knn.fit(X_train, y_train)
    accuracy = knn.evaluate(X_test, y_test)
    accuracies.append(accuracy)fig, ax = plt.subplots()
ax.plot(ks, accuracies)
ax.set(xlabel="k",
       ylabel="Accuracy",
       title="Performance of knn")
plt.show()

图片作者。

图 3: knn 精度对比 k

看来我们的 knn 模型在低 k 下表现最好。

结论

就这样,我们结束了。我们用不到 100 行 python 代码实现了一个简单直观的 k 近邻算法(不包括绘图和数据解包,不到 50 行)。下面包含了整个项目代码。

import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler def most_common(lst):
    return max(set(lst), key=lst.count) def euclidean(point, data):
    # Euclidean distance between points a & data
    return np.sqrt(np.sum((point - data)**2, axis=1)) class KNeighborsClassifier:
    def __init__(self, k=5, dist_metric=euclidean):
        self.k = k
        self.dist_metric = dist_metric def fit(self, X_train, y_train):
        self.X_train = X_train
        self.y_train = y_train def predict(self, X_test):
        neighbors = []
        for x in X_test:
            distances = self.dist_metric(x, self.X_train)
            y_sorted = [y for _, y in sorted(zip(distances, self.y_train))]
            neighbors.append(y_sorted[:self.k]) return list(map(most_common, neighbors)) def evaluate(self, X_test, y_test):
        y_pred = self.predict(X_test)
        accuracy = sum(y_pred == y_test) / len(y_test)
        return accuracy # Unpack the iris dataset, from UCI Machine Learning Repository
iris = datasets.load_iris()
X = iris['data']
y = iris['target']# Split data into train & test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)# Preprocess data
ss = StandardScaler().fit(X_train)
X_train, X_test = ss.transform(X_train), ss.transform(X_test)# Test knn model across varying ks
accuracies = []
ks = range(1, 30)
for k in ks:
    knn = KNeighborsClassifier(k=k)
    knn.fit(X_train, y_train)
    accuracy = knn.evaluate(X_test, y_test)
    accuracies.append(accuracy)# Visualize accuracy vs. k
fig, ax = plt.subplots()
ax.plot(ks, accuracies)
ax.set(xlabel="k",
       ylabel="Accuracy",
       title="Performance of knn")
plt.show()

回到我们的名言“你是和你相处时间最长的五个人的平均值”,knn 分类应该改为“你是和你相处时间最长的 k 个人中最频繁的。”对于一个独立的挑战,通过创建一个 knn 回归模型来修改这个代码以更好地适应原始代码,其中一个点被解释为它的 k 个最近邻居的平均标量目标值。

感谢阅读!
在 LinkedIn 上跟我联系
在 GitHub 中看到这个项目
看到一个 knn 回归实现

使用这种技术创建 requirements.txt

原文:https://towardsdatascience.com/create-your-requirements-txt-using-this-technic-4f5e9376a02e

停止使用没有附加过滤器的“pip 冻结”

照片由Joel&Jasmin fr estbirdUnsplash 拍摄

简介

requirements.txt文件是 Python 数据科学或机器学习项目中非常重要的文档,因为它不仅列出了运行代码所必需的包,还注册了它们各自的版本。这些数据增加了项目的可重复性,例如,允许其他人在他们的机器上创建一个新的虚拟环境,激活它,并运行pip install -r requirments.txt。因此,用户将在本地安装相同的软件包,具有相同的版本,所有这一切都在几秒钟内完成。

传统 **requirements.txt**的问题文件

创建一个requirements.txt文件最常用的技术是当所有的包都已经安装好的时候运行pip freeze > requirements.txt。然而,这种方法的问题是,它不仅将您通过pip install <package_name>实际安装的 Python 包保存到requirements.txt中,还保存了它们自己的依赖包。这就是我的意思。

让我们考虑下面的场景:在一个新的虚拟环境中,我将只安装 Pandas 和 Django 作为我的项目的 Python 额外包。所以,我只是跑:

pip install pandas django

然而,这两个重要的 Python 模块依赖于其他包才能正常工作。例如,Pandas 是建立在 Numpy 之上的,所以前者将在运行pip install pandas时自动安装,以便我们可以使用后者。

Django 也是如此:当执行pip install django时,其他包会同时自动安装,因为 Django 依赖它们来运行。如果您现在执行pip freeze > requirements.txt,您将不会有一个只有两行的新文件,而是有 9 行(一个用于 Pandas,一个用于 Django,七个不必要的用于它们的依赖项)。

这是纯pip freeze方法最让我恼火的地方:它用不必要的信息污染了你的requirements.txt(所有额外的依赖包)。拥有一个实际上只列出你使用pip安装的软件包的requirements.txt不是更好吗?如果你的答案是肯定的,请继续阅读,我将向你展示如何用grep Linux 命令做到这一点。

我现在创建一个 **requirements.txt** 文件

因为我只安装了 Django 和 Pandas,所以我想在我的requirements.txt中只列出这两个。以下命令正是这样做的:

pip freeze | grep -i pandas >> requirements.txtpip freeze | grep -i django >> requirements.txt

请注意,这两个命令之间唯一的区别是包名。

因此,新的命令结构有一个管道(符号|)。它允许pip freeze的输出被用作grep命令的输入,这将只保留出现单词pandasdjango的行。添加-i标志使grep不区分大小写是必要的,因为有些封装在pip freeze中以首字母列出。然后我们使用>>符号将这个新的过滤列表附加到requirements.txt文件中。

创建一个 BASH 函数来自动化这个过程

进一步说,我认为有一个 bash 函数是很有趣的,当使用任意数量的 Python 包名作为参数调用时,可以用pip安装它们,并自动将它们的信息附加到一个requirements.txt文件中。因此,在网上做了一些研究后,我创建了 bash 函数,如下所示:

pip_requirements() {if test "$#" -eq 0
then 
  echo $'\nProvide at least one Python package name\n' 
else 
  for package in "$@"
  do
    pip install $package
    pip freeze | grep -i $package >> requirements.txt
  done
fi}

在您的终端会话中创建了这个函数之后,您将能够调用它来代替纯粹的pip install命令。这里有一个例子:

pip_requirements django pandas seaborn streamlit

因此,只需上面的命令,您就可以安装这四个 Python 包,并创建一个只有它们的名称和版本号的干净的requirements.txt

结束语

现在我的requirement.txt文件摆脱了不必要的信息,我想我可以睡得更好,甚至生活得更幸福!

玩笑归玩笑,尽管这些过程对某些人来说似乎太麻烦了,但我确实认为一个更干净的requirements.txt文件结合了 Python 从我们的项目中删除不必要代码的理念。当我们想要快速检查项目所有者实际上安装了什么样的包来构建他们的代码时,这也会有所帮助。

亲爱的读者,非常感谢你花时间和精力阅读我的文章。

https://medium.com/analytics-vidhya/python-and-openpyxl-gather-thousands-of-excel-workbooks-into-a-single-file-eff4e8c9b514

我有很多关于 Python 和 Django 的文章。如果你喜欢这篇文章,可以考虑在媒体上关注我,并在我发表新文章后订阅接收媒体通知。

编码快乐!

使用 Python 和 GPT-3 创建求职信生成器

原文:https://towardsdatascience.com/creating-a-cover-letter-generator-using-python-and-gpt-3-297a6b2a3886

我们可以使用 NLP 来帮助求职者找到更适合的工作吗?

来源:https://unsplash.com/photos/HXOllTSwrpM

随着 2022 年大辞职的临近,越来越多的工人估计也在找工作。自从新冠肺炎疫情开始以来,已经有创纪录数量的人辞职,尽管这一趋势似乎不会很快放缓,但很明显,这场工人革命也需要适当的工具来帮助这些新失业的工人找到更满意的工作。这就是为什么我开始建立一个求职信生成器作为一个快速的周末项目。

求职信生成器应用程序的屏幕截图

申请工作最乏味的部分之一是写求职信。尽管求职者非常想分享他们的技能和经验如何适合某个角色,但为求职者申请的每份工作和每个雇主写多封求职信通常是一种非常紧张和疲惫的经历。有了这个求职信生成器,我使用 OpenAI 提供的工具和模型创建了一个 web 应用程序,减轻了工作负担,同时仍然允许求职者创建定制的求职信来展示他们的激情和兴奋。

TLDR:

  1. 使用 GPT-3 模型进行文本生成,包括 Curie、Ada、Babbage 和 DaVinci,创建一个 OpenAI 引擎,从给定的提示生成响应。
  2. 收集用户生成的输入,包括公司名称、申请的角色、联系人、申请人信息、个人经历、工作描述和热情,以创建定制求职信回复的提示。
  3. 使用 S treamlit 将模型部署为 web 应用程序,并允许用户基于计算和时间约束选择模型,以及根据他们申请的角色输入他们的响应。

什么是 GPT-3?

在我们开始创建应用程序之前,让我们更深入地了解一下 GPT-3。2021 年的某个时候,OpenAI 的最新型号 GPT-3 在科技界掀起了波澜。然而,这种宣传并非没有风险——来自于对包括数据伦理、模型管理和节制在内的问题的讨论。那么 GPT 3 号到底是什么,是什么让它如此受欢迎?

生成式预训练变压器 3 (GPT-3)是一种深度学习自然语言处理模型,可用于生成“类似人类”的文本,以及其他任务。它还可以用来编写代码、诗歌、故事和其他基于提示的输出。根据他们的论文,OpenAI 的研究人员称 GPT-3 是“一个具有 1750 亿个参数的自回归语言模型”,这个规模是任何其他语言模型的十倍。使用这个使用更大的计算能力和更大的数据集训练的大规模模型,研究人员能够在多个 NLP 任务上实现强大的性能,包括机器翻译、问答、单词解读、在句子中使用新单词,甚至执行 3 位数字运算。不过,最值得注意的是,GPT-3 可以用来生成新闻文章样本,人类评估人员一直难以从真正的人写新闻文章中区分和识别这些样本。

更具体地说,GPT-3 是一系列语言预测模型——这是一种自回归语言模型,采用深度学习来产生类似人类的文本。简而言之,语言模型使用统计和概率技术来预测特定单词在句子中以给定顺序出现的概率。语言模型的下一个单词预测功能可以应用于包括词性(PoS)标注、机器翻译、文本分类、语音识别、信息检索、新闻文章生成和问题回答等领域。GPT-3 家族中的每个语言模型都包含许多可训练参数,尽管所有这些模型都基于相同的 transformer 架构。围绕 GPT-3 的许多兴奋部分是由于它被训练的巨大语料库——common crawlWebText 、维基百科数据以及许多书籍。

求职信应用程序概述

我构建求职信生成器的主要考虑因素包括创建一个直观易用的 web 应用程序,它不需要大量信息或繁琐的用户交互。

可以在 https://bit.ly/coverletter_app网站上观看该应用的现场版

在当前状态下,web 应用程序允许您输入特定于工作的信息,包括公司、角色、招聘经理和工作描述。它还允许你在简历中加入个人信息,比如你的经历、激情以及对这份工作的兴趣。最后,该应用程序允许你选择使用什么样的 GPT-3 模型,这通常不会对结果产生太大影响,以及使用多少令牌(从用户的角度来看,这可能会影响他们希望自己的求职信有多长)。

我使用 Streamlit 构建了这个应用,并将其部署在 Streamlit Cloud 上。我创建了文本框,用户可以在其中输入关于工作、公司和个人经历的具体信息。然后,我在 Streamlit 界面上添加了一个submit_button开关,允许用户根据需要调整 GPT-3 模型。

通过 OpenAI 使用 GPT-3 模型

为了开始探索 GPT 3 号,我在 OpenAI 上注册了一个账户。当你开始的时候,OpenAI 会给你一些免费的积分,我发现这些 18 美元的积分足够我的个人周末黑客马拉松应用了。然而,请注意,这些免费积分将在三个月后到期——因此,如果你正在建立一个作品集项目,请确保记住,测试该应用程序的用户只能在你的积分到期时免费使用。

OpenAI 有一个强大的“入门”指南,介绍了 GPT-3 可以完成的关键概念和潜在任务。也就是说,OpenAI API 允许用户使用 GPT 3 完成几乎任何涉及理解或创建自然语言(和代码,目前处于测试阶段)的任务。

用户可以访问四种主要的 OpenAI GPT-3 模型:

  1. Ada - " 能够完成非常简单的任务,通常是 GPT-3 系列中最快的型号,成本最低。
  2. 巴贝奇- " 能够直接完成任务,速度非常快,成本较低。
  3. 达芬奇- " 最有能力的 GPT-3 型号。可以完成其他模型可以完成的任何任务,通常需要更少的环境。除了响应提示之外,还支持在文本中插入完成。
  4. 居里- " 很有能力,但比达芬奇更快,成本更低。

我用来制作求职信的提示如下:

Streamlit + GPT 用户输入代码。回购可以在这里查看

因此,一个示例提示如下所示:

Cover Letter PromptWrite a cover letter to Technical Hiring Manager from Amber Teng for a Machine Learning Engineer job at Google. I have experience in natural language processing, fraud detection, statistical modeling, and machine learning algorithms. I am excited about the job because this role will allow me to work on technically challenging problems and create impactful solutions while working with an innovative team. I am passionate about solving problems at the intersection of technology and social good.

自动生成的随函回复看起来会像这样:

Auto-Generated Cover LetterDear Technical Hiring Manager,I am writing to apply for the Machine Learning Engineer job at Google. I have experience in natural language processing, fraud detection, statistical modeling, and machine learning algorithms. I am excited about the job because this role will allow me to work on technically challenging problems and create impactful solutions while working with an innovative team. I am passionate about solving problems at the intersection of technology and social good.I believe that my skills and experience make me a perfect fit for this job. I am confident that I can be an asset to the team and contribute to the success of Google.Thank you for your time and consideration.Sincerely,Amber Teng

虽然求职信的文本还不完美,而且在这一点上还需要一些输入,但我很兴奋地看到,根据我的几个读过这封信的朋友所说,它将能够作为一封由人写的“合法的”求职信。

后续步骤

我想在未来探索一些扩展这个应用程序的方法,包括:

  • 为用户创建一个选项,输入他们简历的 PDF 文件,以便他们可以自动上传他们的工作经验和当前技能
  • 添加一个 html 阅读器或网站上传按钮,允许用户自动包含职位描述和公司信息——该功能最好使用 Selenium 来收集职位描述、职位或角色以及公司名称
  • 微调“好”简历上的各种 GPT-3 模型,这些简历由申请相同工作的人类或数据科学家创建,并由人类“专家”或注释者审查

简化应用程序代码

测试 live app 此处 请务必通过 邮箱 报告任何 bug 或建议。

通过 LinkedInTwitter 与我联系。

跟着我上中型

参考资料:

注意:如果 streamlit 应用程序停止工作,这很不幸,因为我用完了 OpenAI 信用,因为该应用程序直接连接到我的 API 密钥。

为 Jupyter 笔记本电脑创建定制的健身房环境

原文:https://towardsdatascience.com/creating-a-custom-gym-environment-for-jupyter-notebooks-e17024474617

婴儿机器人强化学习指南

第 1 部分:创建框架

[所有图片由作者提供]

【2023 年 1 月 3 日更新:

用于强化学习的开放 AI 健身房库的开发已经停止,它是本文最初描述的基础框架。现在已经被 体育馆 所取代,这是一个由法拉马基金会管理的新项目包。

在大多数情况下,这个新框架与原来的框架保持一致,但是 API 有一些细微的变化。因此,本文及其附带的代码示例已经更新,以考虑这些变化并利用这个最新的框架。

因此,尽管框架仍然被称为“体育馆”,但这实际上意味着新的“体育馆”版本的库。

概述

本文(分两部分)描述了为强化学习 ( RL )问题创建一个自定义open ai Gym环境。

已经有相当多的教程展示了如何创建一个定制的 Gym 环境(参见 References 部分获得一些好的链接)。在所有这些例子中,实际上在最常见的 Gym 环境中,这些产生基于文本的输出(例如 Frozenlake )或者出现在单独的图形窗口中的基于图像的输出(例如 月球着陆器 )。

相反,我们将创建一个自定义环境,在 Jupyter 笔记本中生成其输出。环境的图形表示将直接写入笔记本电脑单元,并实时更新。此外,它可以在任何测试框架中使用,并且具有任何 RL 算法,这也实现了 Gym 接口。

到本文结束时,我们将创建一个定制的健身房环境,可以定制该环境以生成一系列不同的网格世界供婴儿机器人探索,并呈现类似于上面显示的封面图像的输出。

密码

相关 Jupyter 笔记本 本文可在Github上找到。这包含设置和运行下面描述的婴儿机器人定制健身房环境所需的所有代码。

介绍

到目前为止,在我们关于强化学习的 系列 ( RL )中,我们已经使用定制的环境来表示机器人宝宝找到自己的位置。从一个简单的网格世界开始,我们添加了一些组件,比如墙和水坑,以增加机器人宝宝面临的挑战的复杂性。

既然我们知道了 RL 的基础知识,在我们进入更复杂的问题和算法之前,似乎是时候正式确定婴儿机器人的环境了。如果我们给这个环境一个固定的、已定义的接口,那么我们可以在我们所有的问题中重用同一个环境,并使用多个 RL 算法。这将使事情变得简单得多,因为我们将向前看不同的 RL 方法。

通过采用一个公共接口,我们可以将这个环境放入任何实现相同接口的现有系统中。我们需要做的就是决定我们应该使用什么接口。幸运的是,我们已经做到了这一点,它被称为 OpenAI Gym 接口。

OpenAI 健身房简介

OpenAI Gym 是一套强化学习(RL) 环境,问题范围从简单的网格世界到复杂的物理引擎。

这些环境中的每一个都实现了相同的接口,使得使用一系列不同的 RL 算法测试单个环境变得容易。类似地,它使得在一系列不同的问题上评估单个 RL 算法变得简单明了。

因此, OpenAI Gym 已经成为事实上的学习和基准测试 RL 算法的标准。

开放式健身界面

所有 OpenAI Gym 环境的界面可以分为 3 个部分:

1.初始化:创建并初始化环境。

2.执行:在环境中采取重复动作。在每一步,环境提供信息来描述它的新状态和作为采取特定行动的结果而收到的奖励。这种情况一直持续到环境发出情节结束的信号。

3.终止:清理破坏环境。

举例:翻车环境

健身房中一个比较简单的问题是 翻跟斗 的环境。在这个问题中,我们的目标是向左或向右移动手推车,这样手推车上的平衡杆就会保持直立。

图 CartPole 环境的输出——目标是通过向左或向右移动小车来平衡杆子。

设置和运行这个健身房环境的代码如下所示。这里我们只是随机选择向左或向右的动作,所以杆子不会停留很长时间!

清单 1:运行健身房环境的 3 个阶段。

在上面的清单 1 中,我们标记了健身房环境的 3 个阶段。更详细地说,其中每个都执行以下操作:

1。初始化

env = gym.make(‘CartPole-v1’, render_mode='human')
  • 创建所需的环境,在本例中是版本' 0 '的 CartPole 。然后可以使用返回的环境对象' env '来调用公共 Gym 环境接口中的函数。
  • “render_mode”参数定义了调用“render”函数时环境应该如何显示。在这种情况下,“人”被用来连续地将环境渲染到显示窗口中。
obs = env.reset()
  • 在每集开始时调用,这将环境置于其开始状态,并返回环境的初始观察结果。

2。执行

这里我们一直运行,直到环境' done '标志被置位,表示剧集完成。如果代理已经达到终止状态或者已经执行了固定数量的步骤,就会发生这种情况。

env.render()
  • 画出环境的当前状态。在 CartPole 的情况下,这将导致一个新窗口打开,显示购物车及其杆的图形视图。在更简单的环境中,例如 FrozenLake 简单网格世界,显示文本表示。
action = env.action_space.sample()
  • 从环境的一组可能操作中选择一个随机操作。
observation, reward, terminated, truncated, info = env.step(action)

采取行动,并从环境中获取有关该行动结果的信息。

这包括 4 条信息:

  • “观察”:定义环境的新状态。在磁极的情况下,这是关于磁极的位置和速度的信息。在网格世界的环境中,它将是关于下一个状态的信息,也就是我们采取行动后的最终状态。
  • 奖励’:因采取行动而获得的奖励金额(如果有)。
  • 终止’:一个标志,表示我们是否已经到达该集的结尾
  • truncated’:一个标志,表示剧集在完成前是否已经停止。
  • 信息’:任何附加信息。一般来说,这是不成立的。

注意 :
在早期版本的健身房环境中,“终止”和“截断”标志由一个“完成”标志表示。现在这已经被一分为二,使得这一集结束的原因更加清晰(更多信息参见'步骤'函数的 API 文档)。

3。终止

env.close()
  • 终止环境。这也将关闭任何由渲染功能创建的图形窗口。

创造一个定制的健身房环境

如前所述,使用 OpenAI Gym 的主要优势是每个环境都使用完全相同的界面。我们可以将上面的' gym.make '行中的环境名字符串' CartPole-v1 '替换为任何其他环境的名称,其余的代码可以保持不变。

对于任何实现 Gym 接口的定制环境也是如此。所需要的是一个从体育馆环境继承的类,它增加了上面描述的一组功能。

下面显示了我们将要创建的自定义' BabyRobotEnv '的初始框架(类名后面的' _v0 '表示这是我们环境的零版本)。随着功能的增加,我们将对此进行更新):

在这个自定义环境的基本框架中,我们从 base ' gym 继承了我们的类。Env '类,它为我们提供了创建环境所需的所有主要功能。在此基础上,我们添加了将该类转变为我们自己的定制环境所需的 4 个函数:

  • init ':类初始化,在这里我们可以设置类需要的任何东西
  • 步骤:实现当婴儿机器人在环境中迈出一步时发生的事情,并返回描述迈出这一步的结果的信息。
  • 重置':在每集开始时调用,将环境恢复到初始状态。
  • render’:提供基于图形或文本的环境表示,让用户看到事情进展如何。

我们还没有实现一个' close '函数,因为目前没有要关闭的东西,所以我们可以依靠基类来完成任何需要的清理。此外,我们还没有添加任何功能。我们的类满足了 Gym 接口的需求,并且可以在 Gym 测试工具中使用,但是它目前不会做太多!

行动和观察空间

上面的代码定义了一个定制环境的框架,但是它还不能运行,因为它目前没有' action_space '来对随机动作进行采样。‘action _ space’定义了代理在环境中可以采取的一组动作。这些可以是离散的、连续的或两者的组合。

  • 离散动作 表示一组互斥的可能动作,例如横翻环境中的左右动作。在任何时间步长,您可以选择向左或向右,但不能同时选择两者。
  • 连续动作 是有关联值的动作,表示该动作要进行的量。例如,当转动方向盘时,可以指定一个角度来表示方向盘应该转动多少。

我们正在创建的婴儿机器人环境被称为网格世界。换句话说,这是一个正方形网格,机器人宝宝可以在其中移动,从一个正方形到另一个正方形,探索和导航环境。这个环境中的默认级别是一个 3 x 3 的网格,起点在左上角,出口在右下角,如图 2 所示:

图 2:婴儿机器人环境中的默认级别。

因此,对于我们正在创建的自定义 BabyRobotEnv ,只有 4 种可能的移动动作:北、南、东西。此外,我们将添加一个'停留动作,婴儿机器人保持在当前位置。因此,我们总共有 5 个互斥的动作,因此我们设置动作空间来定义 5 个离散值:

self.action_space **=** gym.spaces.Discrete(5)

除了一个 action_space,所有环境都需要指定一个'observation _ space '。这定义了当代理接收到关于环境的观察时提供给它的信息。

婴儿机器人在环境中迈出一步时,我们想要返回他的新位置。因此,我们将定义一个观察空间,将网格位置指定为' x 和' y '坐标。

健身房界面定义了几个不同的' 空格 '可以用来指定我们的坐标。例如,如果我们的坐标在连续的地方,浮点值我们可以用 框空格 这也将让我们对可用于“ x ”和“ y ”坐标的值的可能范围进行限制。此外,我们可以使用 Gym 的字典空间 将这些组合起来,形成环境观察空间的单一表达。

然而,由于我们只允许从一个方块整体移动到下一个方块(而不是在两个方块之间移动一半),我们将用整数指定网格坐标。因此,与动作空间一样,我们将使用一组离散的值。但是现在,不是只有一个离散值,而是有两个:分别对应“ x 和“ y ”坐标。幸运的是,Gym 界面正好有这个东西,即https://gymnasium.farama.org/api/spaces/fundamental/#multidiscrete

在水平方向上,最大' x '位置由网格的宽度限定,在垂直' y '方向上由网格的高度限定。因此,观察空间可以定义如下:

*self.observation_space = MultiDiscrete([ self.width, self.height ])*

离散空间是从零开始的,所以我们的坐标值将从零到比定义的最大值小 1。

有了这些改变后, BabyRobotEnv 类的新版本如下所示:

关于新版本的 BabyRobotEnv 类,有几点需要注意:

  • 我们向 init 函数提供了一个 kwargs 参数,让我们用一个参数字典创建我们的实例。在这里,我们将提供我们想要制作的网格的宽度和高度,但是接下来我们可以使用它来传递其他参数,并且通过使用 kwargs 我们可以避免更改类的接口。
  • 当我们从 kwargs、 中获取宽度和高度时,如果没有提供参数,那么在这两种情况下我们都将默认值设为 3。因此,如果在创建环境的过程中没有提供参数,我们将得到一个大小为 3x3 的网格。
  • 我们现在已经使用' self.x 和' self.y '定义了婴儿机器人在网格中的位置,我们现在从' reset' 和' step' 函数返回该位置。在这两种情况下,我们都将这些值转换成了 numpy 数组,虽然不需要匹配 Gym 接口,但是 Gym 环境检查器需要它,这将在下一节中介绍。

测试自定义环境

在我们开始添加任何真正的功能之前,有必要确认我们的新环境符合 Gym 接口。为了测试这一点,我们可以使用 体育馆环境检查器 来验证我们的类。

该测试不仅测试我们已经实现了 Gym 接口所需的功能,还检查了动作和观察空间是否设置正确,以及功能响应是否与相关的观察空间匹配。

要运行检查,只需创建一个环境实例,并将其提供给' check_env '函数。如果有任何问题,将会显示警告信息。如果没有输出,那么一切都很好。

我们还可以看看环境的动作观察空间,以确保它们返回预期值:

*print(f”Action Space: {env.action_space}”)
print(f”Action Space Sample: {env.action_space.sample()}”)*

应该会产生类似于以下内容的输出:

动作空间:离散(5)
动作空间样本:3

  • 正如所料,动作空间是一个具有 5 个可能值的离散空间。
  • 从动作空间采样的值将是 0 和 4 之间的随机值。

对于观察空间:

*print(f"Observation Space: {env.observation_space}")
print(f"Observation Space Sample: {env.observation_space.sample()}")*

应该会产生类似于以下内容的输出:

观察空间:多离散([3 ^ 3])
观察空间样本:[0 ^ 2]

  • 观察空间有一个多离散类型,它的两个组成部分各有 3 个可能的值(因为我们创建了一个默认的 3×3 网格)。
  • 从该网格的观察空间采样时,' x '和' y '都可以取值 0、1 或 2。

创造环境

您可能已经注意到,在上面的测试中,我们没有像对 CartPole 那样使用‘gym . make创建环境,而是通过执行以下操作简单地创建了它的一个实例:

*env = BabyRobotEnv_v1()*

当我们自己处理环境时,这绝对没问题,但是如果我们想要将我们的自定义环境注册为一个合适的 Gym 环境,可以使用' gym.make '来创建,那么我们需要采取一些进一步的步骤。

首先,从 Gym 文档 中,我们需要用如下所示的结构设置我们的文件和目录:

图 3:定制健身房环境的目录结构。

因此,我们需要 3 个目录:

  1. 主目录(在本例中为' BabyRobotGym ')保存我们的' setup.py '文件。这个文件定义了项目目录的名称,并引用了所需的资源,在本例中就是‘gymnasium库。该文件的内容如下所示:

2.项目目录,与设置文件的' name '参数同名。因此,在这种情况下,目录名为' babyrobot '。这包含一个文件' init.py ',它定义了环境的可用版本:

3.主功能所在的' envs '目录。在我们的例子中,它包含了我们上面定义的两个版本的婴儿机器人环境(“ baby_robot_env_v0.py ”和“ baby_robot_env_v1.py ”)。这些定义了' babyrobot/init 中引用的两个类。py' 文件。

此外,该目录包含自己的' init。引用目录中包含的两个文件的 py 文件:

我们现在已经定义了一个 Python 包,它可以上传到一个资源库,比如 PyPi ,以便于分享你的新作品。此外,有了这个结构,我们现在可以导入我们的新环境,并使用' gym.make' 方法创建它,就像我们之前对 CartPole 所做的那样:

*import babyrobot# create an instance of our custom environment
env = gym.make(‘BabyRobotEnv-v1’)*

请注意,用于指定环境的名称是用于注册环境的名称,而不是类名。所以,在这种情况下,虽然类名为' BabyRobotEnv_v1 ',但注册名实际上是 'BabyRobotEnv-v1'

克隆 Github 库

为了更容易检查上面描述的目录结构,可以通过克隆BabyRobotGym Github存储库来重新创建它。完成此操作的步骤如下:**

1。获取代码并移动到新创建的目录:

**git clone [https://github.com/WhatIThinkAbout/BabyRobotGym.git](https://github.com/WhatIThinkAbout/BabyRobotGym.git)
cd BabyRobotGym**
  • 这个目录包含我们上面定义的文件和文件夹结构(加上一些我们将在第 2 部分中看到的额外的文件和文件夹)。

2。创建一个 Conda 环境并安装所需的包:

为了能够运行我们的环境,我们需要安装一些其他的包,最明显的是' Gym 本身。为了方便设置环境,Github repo 包含了几个'。列出所需软件包的 yml 文件。

要使用这些创建一个 Conda 环境并安装软件包,请执行以下操作(选择适合您的操作系统的一个):

在 Unix 上:

**conda env create -f environment_unix.yml**

在 Windows 上:

**conda env create -f environment_windows.yml**

3。激活环境:

我们已经创建了包含所有必需包的环境,所以现在只需要激活它,如下所示:

**conda activate BabyRobotGym**

(当你玩完这个环境后,运行" conda deactivate "退出)

4。运行笔记本

现在,运行自定义健身房环境的一切都应该就绪了。为了测试这一点,我们可以运行资源库中包含的示例 Jupyter 笔记本'baby _ robot _ gym _ test . ipynb'。这将加载' BabyRobotEnv-v1 '环境,并使用 Gymnasium 环境检查器对其进行测试。

要在浏览器中启动,只需键入:

**jupyter notebook baby_robot_gym_test.ipynb**

或者在 VS 代码中打开这个文件,确保选择' BabyRobotGym '作为内核。这将创建' BabyRobotEnv-v1 '环境,在稳定基线中测试它,然后运行环境直到它完成,这恰好发生在一个单独的步骤中,因为我们还没有编写'步骤'函数!

添加操作

尽管定制环境的当前版本满足了 Gym 界面的要求,并且具有通过环境检查器测试所需的功能,但是它还没有做任何事情。我们希望机器人宝宝能够在他的环境中活动,为此我们需要他能够采取一些行动。

由于机器人宝宝将在一个简单的网格世界环境中工作(见上图 2),他可以采取的行动将被限制在向北、向南、向东或向西移动。此外,我们希望他能够留在同一个地方,如果这将是最佳的行动。因此,我们总共有 5 种可能的行动(正如我们在行动空间中已经看到的)。

这可以用 Python 整数枚举来描述:

为了简化代码,我们可以从之前的' BabyRobotEnv_v1 '类继承。这为我们提供了所有以前的功能和行为,然后我们可以扩展它们来添加与动作相关的新部分。如下所示:

添加到类中的新功能具有以下功能:

  • 在'_ _ init _ _【T3]'功能中,可以提供关键字参数,用于指定环境中的开始和结束位置以及婴儿机器人的开始位置(默认设置为网格的开始位置)。
  • take_action ”功能通过应用所提供的动作简单地更新机器人宝宝的当前位置,然后检查新位置是否有效(以防止他离开网格)。
  • '步骤'函数应用当前动作,然后获得新的观察和奖励,然后返回给调用者。默认情况下,每次移动返回-1 的奖励,除非机器人宝宝已经到达终点位置,在这种情况下,奖励设置为零,并且“终止标志设置为真。
  • 函数' render '打印出当前位置和奖励。

所以,最后,我们现在可以采取行动,从一个细胞移动到下一个细胞。然后我们可以使用上面清单 1 的 的修改版本(从使用 CartPole 改为使用我们最新的 BabyRobot_v2 环境)来选择随机动作并在网格中移动,直到婴儿机器人到达被指定为网格出口的单元格(默认情况下是单元格(2,2))。

我们新环境的测试框架如下所示:

当我们运行这个程序时,我们会得到一个类似如下所示的输出。

图 4:从起始单元格(0,0)到出口单元格(2,2)的网格路径示例。

在这种情况下,通过网格的路径非常短,从起点方块(0,0)移动到出口方块(2,2)只需几步。由于随机选择动作,路径通常会更长。还要注意,在到达出口之前,每一步都会得到-1 的奖励。所以机器人宝宝到达出口的时间越长,返回值越负。

创建渲染函数

技术上我们已经创建了渲染功能,只是它不是很令人兴奋!如图 4* 所示,我们得到的都是描述行动、位置和奖励的简单短信。我们真正想要的是环境的图形化表示,显示机器人宝宝在网格世界中移动。*

如上所述,体育馆库中的环境集合通过生成基于文本的表示或者通过创建包含图像的数组来执行它们的渲染,以显示环境的当前状态。

基于文本的表示提供了一种在基于终端的应用程序中渲染环境的快速方法。当您只需要当前状态的简单概述时,它们是理想的。

另一方面,图像给出了当前状态的非常详细的画面,非常适合创建一集的视频,以便在该集结束后显示。

虽然这两种表现都很有用,但在 Jupyter 笔记本中工作时,它们都不太适合创建实时、详细的环境状态视图。当机器人宝宝在一个网格水平上移动时,我们希望实际看到他在移动,而不仅仅是收到一条描述他位置的短信,或者看一幅简单的文字画,上面有一个“ X ”在一个点网格上移动。

此外,我们希望随着剧集的展开看到这种情况的发生,而不仅仅是能够在之后观看,或者在闪烁的显示屏上实时看到。简而言之,我们希望使用不同的方法来呈现文本字符或图像数组。我们可以通过使用优秀的 ipycanvas 库在 HTML5 画布上绘图来实现这一点,我们将在第 2 部分中对此进行全面介绍。

摘要

OpenAI Gym 环境是测试强化学习算法的标准方法。基本系列带来了一大堆不同的、具有挑战性的问题。但是,在许多情况下,您可能想要定义自己的自定义环境。通过实现 Gym 环境的结构和接口,很容易创建这样一个环境,它将无缝地嵌入到任何使用 Gym 接口的应用程序中。

总之,创建自定义健身房环境的主要步骤如下:

  • 创建一个从 env 继承的类。健身房基地班。
  • 实现' reset '、' step' 和' render' 函数(如果需要整理资源,可能还有' close' 函数)。
  • 定义 动作空间 ,指定环境允许的动作数量和类型。
  • 定义 观察空间 来描述在每一步上提供给代理的信息,并设置环境内的移动边界。
  • 组织目录结构,添加' init。py 和' setup.py '文件,以匹配 Gym 规范,并使环境与 Gym 框架兼容。

遵循这些步骤将为您提供一个基本框架,您可以从这个框架开始添加您自己的定制特性,以针对您自己的特定问题定制环境。

在我们的例子中,我们想要创建一个婴儿机器人可以探索的网格世界环境。此外,我们希望能够图形化地查看这个环境,并观察机器人宝宝在周围移动。在第二部分 中,我们将看到这是如何实现的。

* *

参考资料:

  1. 体育馆图书馆:

*https://gymnasium.farama.org/

而完整系列的婴儿机器人强化学习指南可以在 这里

*

为 Jupyter 笔记本电脑创建定制的健身房环境

原文:https://towardsdatascience.com/creating-a-custom-gym-environment-for-jupyter-notebooks-e8718f36547b

婴儿机器人强化学习指南

第 2 部分:呈现 Jupyter 笔记本单元格

所有图片由作者提供。

【2023 年 1 月 7 日更新:

用于强化学习的开放 AI 健身房库的开发已经停止,它是本文最初描述的基础框架。现在已经被 体育馆 所取代,这是一个由法拉马基金会管理的新项目包。

在大多数情况下,这个新框架与原来的框架保持一致,但是 API 有一些细微的变化。因此,本文及其附带的代码示例已经更新,以考虑这些变化并利用这个最新的框架。

因此,尽管框架仍然被称为“体育馆”,但这实际上意味着新的“体育馆”版本的库。

介绍

第一部分 中,我们看到了如何为强化学习 ( RL )问题创建一个定制的 Gym 环境,只需扩展 Gym 基类并实现一些功能。然而,我们最终得到的定制环境有点简单,只有简单的文本输出。

因此,在这一部分,我们将通过添加图形渲染来扩展这个简单的环境。此外,该渲染输出将明确针对 Jupyter 笔记本,直接在笔记本单元中生成环境的图形表示。

这篇文章的所有相关代码都可以在 Github 上找到。此外,我们创建的自定义婴儿机器人健身房环境可以通过运行 pip install babyrobot 来安装,您可以在附带的 API 笔记本中玩这个。

同样,这篇文章的交互式版本可以在 笔记本 中找到,在那里你可以实际运行下面描述的所有代码片段。

ipycanvas 库简介

Jupyter 笔记本中运行强化学习问题时,很容易在笔记本单元格中写入文本来显示事情的进展情况。然而,考虑到随着时间的推移会产生大量的信息,通过创建环境的图形视图可以获得更清晰的表示。

通常,这种图形视图是通过在每个时间步拍摄环境的快照图像,然后在一集结束时将这些图像结合在一起,以创建一部短片。然后可以在笔记本上回放,看看事情进展如何。

这种方法的缺点是您需要等待电影被创建。理想情况下,我们希望实时看到我们环境中发生的变化。我们需要一些可以添加到笔记本单元格中的东西,然后随着动作的发生被绘制和更新。

这个确切的功能可以使用 HTML 画布元素 来实现,该元素可以使用优秀的ipycanvas库在 Jupyter 笔记本中访问。

示例:

我们首先需要创建我们的 婴儿机器人网格世界 ,也就是实际的“世界”,所有的动作都发生在那里。最基本的,这只是一个彩色的矩形。这可以在 ipycanvas 中非常容易地创建,只需定义一个画布,然后指定要绘制的矩形的大小和颜色:

在上面的代码中,我们已经导入了 ipycanvas 库,然后定义了我们将要创建的网格世界的维度。这将是一个 3x3 的网格,其中每个单元是一个 64 像素的正方形。使用这些尺寸,我们可以创建我们的画布。

最初画布是空白的,所以要真正看到画布,我们需要画一些东西。在上面显示的' draw_base '函数中,填充颜色设置为橙色,然后用它来绘制一个覆盖整个画布区域的矩形。

调用这个函数后,最后一行, canvas ,只是将完成的画布绘制到笔记本单元格中,如下图图 1 所示。这个正方形将作为我们网格世界的基础。相当刺激!

图 1:基本的画布世界。

添加网格

任何自尊的网格世界都需要一个真正的网格。同样,这可以通过在 ipycanvas 中画几条虚线来轻松实现:

这里我们定义了一个函数,它设置画布属性来绘制一条 1 像素宽的灰色虚线。然后,我们简单地为网格中的每个单元格绘制一个矩形,这给出了如图 2 所示的输出:

图 2:基本的网格世界。

添加边框

我们可以通过在外部添加边框来改善网格世界的外观。这只是一个黑色矩形,线条比网格略粗,在' draw_border '函数中定义。这会产生如下所示的输出:

图 3:添加了边框的网格世界。

添加动画图像

我们的婴儿机器人网格世界最后需要的是一个婴儿机器人,最好是会动的!因为我们希望我们的机器人在网格层的顶部移动,而不损坏我们已经绘制的任何东西,所以我们将为我们的机器人动画使用单独的画布。

使用 MultiCanvas 元素很容易做到这一点。有了它,我们可以堆叠任意多的画布,并分别绘制每一张,从而构建出完整的环境。如下图所示,我们定义了 MultiCanvas 有两层,然后使用上面的函数在第一层(层索引 0)上重建网格世界。

最后,我们可以加载我们的婴儿机器人图像,并创建一个非常简单的动画,将我们的动画绘制到上层画布上(index = 1)。

为了让机器人宝宝在屏幕上移动,我们使用了一个简单的循环,在绘制下一幅图像之前清除上一幅图像。由于图像上有一些填充,我们可以简单地清除想要绘制新图像的区域。这两个操作都使用' hold_canvas 绑定在一起,这使得事情稍微顺畅一些(对于更高级的动画,请查看 ipycanvas 文档 )。

最终的婴儿机器人网格世界如下图图 4 所示:

图 4。网格世界中的婴儿机器人。

创建图形网格级别

使用 ipycanvas 库和上面描述的基本绘图例程,我们可以创建封装了为我们的定制健身房环境绘制图形网格级别所需的所有功能的类。

作为其中的一部分,我们有两个主要的类:

  • 网格级 :管理网格级的绘制和查询。
  • RobotDraw :将机器人宝宝绘制到网格上的特定位置,并在他在单元格之间移动时制作动画。

这两个类的完整代码可以在 Github 上找到。

在下面的代码中,我们导入这两个类,然后用它们绘制一个默认的 3x3 网格级别,我们在上面添加婴儿机器人,定位在单元格[1,1]。

我们在 Jupyter 笔记本单元中得到的输出显示在下面的图 5 中。这给了我们一个默认的婴儿机器人网格世界级别,我们可以用它来为我们的健身房环境创建图形渲染功能。

图 5:一个默认的婴儿机器人网格世界水平。

创建一个图形化体育馆渲染函数

在创建自定义健身房环境的第一部分结束时,我们用一个渲染函数完成了这个过程:

图 BabyRobotEnv 的“render”函数版本 2 的输出。

虽然提供了关于环境当前状态的所有重要信息,但这并不令人兴奋。此外,很难想象这一集是如何发展的。通过查看每个时间点的坐标,你可以想象出机器人宝宝是如何在网格中移动的,但是如果我们真的能看到这种情况发生,事情会更清楚。

正如我们所见,可以使用 ipycanvasJupyter Notebook 单元中创建实时图形,因此我们可以用一个显示环境图形视图的函数来替换当前基于文本的渲染函数,并在发生变化时更新它。如下所示:

正如我们之前所做的,新类继承了环境的前一版本(在本例中是从 BabyRobotEnv_v2 继承的),这为我们提供了 Gym 基类的所有功能,以及我们在之前的迭代中添加的额外内容。然后,我们只需要提供我们想要替换的函数的新版本,在本例中如下所示:

  • init :包含我们分别绘制网格和婴儿机器人所需的“ GridLevel ”和“ RobotDraw ”类的实例。
  • 复位 :将婴儿机器人和环境都放回初始位置。
  • 渲染 :将婴儿机器人移动到新的位置(该位置已经在健身房界面的’步骤函数中计算过,在 BabyRobotEnv_v2) 中定义)并绘制关卡。当婴儿机器人从一个单元移动到下一个单元时,这将为移动设置动画。

现在,当我们创建这个环境的实例并调用它的渲染函数时,我们会看到:

图 BabyRobotEnv _ v3 的“render”函数的初始输出。

更好的是,当我们运行我们的标准强化学习循环时,如上所示,我们现在可以看到机器人宝宝在环境中移动。在寻找出口的过程中,机器人宝宝目前正在采取随机抽样的行动,因此每一集都将遵循不同的路径。图 7 中显示了一个这样的情节:

图 BabyRobotEnv _ v3 制作的典型剧集的渲染输出。

陈述具体行动空间

如果你再看一下 BabyRobotEnv_v3 ' 渲染函数,你会看到我们仍然在打印每个时间步的动作、位置和奖励。因此,除了新的图形输出之外,我们还从环境的版本 2 中获得了文本输出。此外,如果您检查这个文本输出,您会看到条目,例如图 5 中的第一行:

“北:(0,0)奖励=-1”

换句话说,婴儿机器人在最初的开始方块(0,0)然后选择了向北移动,这会把他直接撞墙!

虽然他只是一个婴儿,但他不笨,所以应该只选择有效的行动。我们可以通过引入特定于状态的动作空间来实现这一点,在该空间中,不是简单地从所有动作中进行选择,而是返回的动作取决于当前状态。

在上面的代码中,我们创建了一个自定义的 健身房空间 。我们将使用它来存储当前状态下可用的动作,然后,当调用' sample '时,我们将随机选择这些动作中的一个。

使用这个类,我们可以增强我们以前的环境,这样,当进入一个新的状态时,它会为该状态设置可能的操作。如下所示:

和以前一样,我们继承了以前的环境(在本例中是 BabyRobotEnv_v3 ),这样我们就可以在它的功能上进行构建。然后,我们添加一个'动态'类的实例,每次调用' take_action '函数时,我们都用当前状态可用的操作填充它。

因此,当对特定状态的动作进行采样时,它将从有效动作集中抽取,不会导致机器人宝宝撞到墙上。

例如,对于开始状态,调用 BabyRobotEnv_v4 的' show_available_actions 函数返回动作 South 和 East。类似地,对于图 8 所示的网格位置(2,1),可用的动作是北、南或西。

图 8:网格位置(2,1),其中可用的动作是北、南或西。

注册和检查本地环境类

为了检查我们的新环境是否符合 Gym API 标准,我们可以使用 Gym 'check _ env'函数。如果这没有返回警告,那么我们都很好。

然而,为了给这个函数提供我们的环境,我们首先需要调用' gym.make '来创建环境,但是在我们这样做之前,我们需要为 gym 注册环境以了解它。

在本文的第一部分中,我们看到了当自定义环境包含在自己的 python 文件中时,如何做到这一点。在这种情况下,提供给' register '函数的' entry_point 定义了文件和类名。

注册本地类略有不同。在这种情况下,' entry_point '只是类名而不是字符串。因此,在这种情况下,我们可以注册并检查BabyRobotEnv _ v4类,如下所示:

增强图形环境

虽然能够看到文本输出是有用的,给出了每个动作的细节,但它生成了不断增加的文本列表,最终淹没了笔记本单元格,这不是很好。

与其在' render' 函数中使用 print 语句,我们可以直接将文本写到画布上。为此,我们首先需要扩展画布来创建一个可以显示文本的区域。通过使用' init '函数的' kwargs '参数,我们可以提供一个定义该文本区域的对象:

在上面的例子中,我们指定了一个灰色的侧面板,其宽度大约等于网格层的宽度。这将产生以下输出:

图 9:为文本输出添加侧面板。

我们现在需要的是一种方法来写入这个面板,并在每次调用' render '时显示所需的信息。我们环境的下一次迭代包含了' show_info '函数来完成这一任务:

新的' show_info '方法调用基础' GridLevel '类中的函数。这需要一个信息对象,给出要显示的文本和它应该去哪里的细节。

之前,在' render 函数中,我们提供了动作和奖励,然后使用 print 命令显示它们:

print(f”{Actions(action): <5}: ({self.x},{self.y}) reward = {reward}”)

在新的图形版本中,我们在主循环中创建一个信息对象,并将其交给 render 函数:

标准健身房环境强化学习循环。

现在,当我们运行主 RL 循环时,我们得到以下输出:

图 10:直接写入画布的图形文本输出。

增加挑战

虽然我们新的自定义健身房环境的图形输出看起来不错,但这并不是一个非常困难的强化学习挑战。为了让事情变得更困难,我们需要给机器人宝宝增加一些障碍来谈判。

添加墙壁:

在创建环境时,我们可以提供一组墙定义。该数组中的每一项都定义了网格坐标和应放置墙的单元格的边:

图 11:给环境添加墙壁。

添加水坑:

目前,当在网格中移动时,婴儿机器人的所有动作都是确定的。例如,在上面的图 11 中,机器人宝宝目前从开始状态只有一个可能的动作,那就是向南走。当他采取这一行动时,他肯定会在下面的单元格中结束,并会因为采取这一行动而获得-1 的奖励。

相反,许多 RL 问题考虑的是概率环境,在这种环境中,当采取一项行动时,不能保证你最终会达到目标状态,也不能保证你会得到预期的回报(有关这方面的更多信息,请参见“ 马尔可夫决策过程和贝尔曼方程 ”一文)。我们可以通过添加水坑将这种随机性引入网格世界。当机器人宝宝遇到这种情况时,他有可能打滑,在这种情况下,他将会在不同于他试图到达的牢房中结束。此外,机器人宝宝需要更长的时间才能通过水坑,因此进入水坑的奖励更为消极(即更大的惩罚)。

在我们添加水坑之前,我们将对环境做最后的改变。在' take_action '函数中,我们将检查该操作是否导致了预期目标的实现。然后,在'步骤函数中,我们将利用 Gym 接口的' info '对象来返回这些信息。这将使我们能够监控机器人宝宝进入水坑的效果:

然后我们可以创建这个新环境的一个实例来设置一个包含水坑的关卡。此外,我们将移动起点和出口,并在起点广场周围放置一些墙壁,这样机器人宝宝就没有其他选择,只能直接进入水坑。

图 12:给环境添加一个水坑。

与墙一样,水坑是通过给出其网格位置的坐标来指定的。然而,水坑存在于单元的中间,所以不需要指定边。相反,定义了水坑的大小,有两个可能的选项,默认情况下,具有以下属性:

  • 1 =小水坑。奖励= -2,打滑概率= 0.4
  • 2 =大水坑。奖励= -4,打滑概率= 0.6

如果我们现在运行简单的测试代码,如下所示,机器人宝宝将尝试向东走两步。第一个会成功,因为他是从干燥的起点出发的。然而,他正在进入一个大水坑,所以将自动获得奖励-4。在下一步行动中,他想要到达出口,所以再次尝试向东移动。然而,他现在正从一个大水坑里出来,所以有 0.6 的概率他会打滑,并最终处于其他可能的状态之一。

当打滑发生时,将显示以下类型的输出:

图 13:打滑后的机器人宝宝。

机器人宝宝并没有停在出口处,也没有得到零奖励,而是下滑到了(1,0)处,得到了-1 的奖励。

添加迷宫:

许多网格世界问题定义了需要被导航的迷宫,以寻找出口。虽然我们可以通过指定大量的墙来实现这一点,但这很快就会变得令人讨厌。因此,我们可以指定我们想要添加一个迷宫,并提供一个随机的种子,它将决定创建的墙壁。

默认情况下,迷宫只有一条路径可以到达出口。对于许多 RL 问题,当几个可能的选项可用时,产生了更好的挑战,并且学习算法将需要找到这些选项中的最佳选项。通过移除迷宫中的一些墙壁,我们可以创建几条通往出口的路线。RL 算法需要找出哪一个能让机器人宝宝获得最大的回报。

在这里,在我们的最后一关,我们已经添加了几乎所有的东西!我们指定了一个更大的 8x5 大小的迷宫。然后我们移除了几堵墙,创造了几条通往出口的路线。然后,我们增加了一些水坑,只是为了创造更多的挑战。最后,为了让事情看起来更好,我们已经指定我们想要使用' black_orange '主题(所有的颜色都是完全可定制的)。

此配置产生以下级别:

图 14:一个完整的婴儿机器人定制健身房环境。

机器人宝宝现在有一个具有挑战性的问题,他必须在迷宫中寻找出口。当标准健身房环境强化学习循环运行时,机器人宝宝将开始随机探索迷宫,收集他可以用来学习如何逃脱的信息。下面的图 15 显示了其中一集的部分内容。

图 15:婴儿机器人探索迷宫寻找出口。

显然,考虑到正在采取的随机行动,加上可能导致打滑的水坑的复杂性,机器人宝宝可能需要一些时间来定位出口。要了解如何使用强化学习算法来找到穿过迷宫的最佳路线,请查看 培训笔记本

摘要

在这两篇文章中,我们已经看到了如何创建一个定制的体育馆环境,将实时图形输出直接呈现到 Jupyter 笔记本单元格中。

ipycanvas 库提供了对 HTML 画布的直接访问,在这里可以组合简单的图形组件来生成强化学习环境的信息视图。

此外,通过将这个环境建立在 Gym API 的基础上,我们可以创建强化学习问题,这些问题与许多不同的开箱即用学习算法兼容。希望这些文章已经为您提供了开始构建自己的定制 RL 环境所需的所有信息。

如果你只是想在婴儿机器人环境中玩一玩,看看这个 笔记本 展示了婴儿机器人网格世界可以创建的不同方式以及可以添加的组件。

现在,我们可以为机器人宝宝创造一系列具有挑战性的世界来探索,剩下要做的就是学习如何解决这些问题。关于如何做到这一点的系列文章的第一部分可以在这里找到:

使用 Dash/Heroku 创建仪表板应用程序:简化的流程

原文:https://towardsdatascience.com/creating-a-dashboard-app-with-dash-heroku-a-streamlined-process-26bce4fbf2c8

Unsplash 上由 Carlos Muza 拍摄的照片

一直想创建一个仪表板,但担心你必须在哪里托管它,学习 CSS 和 HTML 似乎令人生畏?那这篇文章就送给你了!

Dash/Plotly 协同工作,因此您可以在单独使用 Python [1]时轻松创建令人惊叹的视觉效果。Dash 是一个 python 库,作为 HTML 代码的包装库。事实上,这种符号看起来有点类似于 HTML,并与一般的框架和格式相呼应,只是略有不同。

本文致力于使用 Dash 构建和使用 Heroku [2]托管的仪表板的部署,假设您对使用. py 文件构建 Dash 应用程序有足够的了解。如果你想看更多关于 Dash 的信息/文章,请在下面留言。: )

在 Heroku 上创建帐户/安装必要的依赖项:

  1. 在 Heroku 上注册一个账户:【https://id.heroku.com/login
  2. 创建您的应用程序名称(这最终将成为 URL 的一部分,因此请明智选择!)
  3. 下载并安装Heroku CLI:
    ——这允许您使用 git 从命令行更新和更改您的仪表板

在选择的 IDE 中:

4.在文件夹中创建新项目,并使用您喜欢的编辑器(PyCharm,VSCode 等)。)

  • 在 PyCharm 中选择“创建新项目”,这将提示您为文件的位置指定一个带有文件夹的路径名。名称/文件夹应该是特定于项目的
  • 在单击项目的“创建”之前,选择“新环境使用”,并使用下拉菜单选择 Virtualenv
  • venv 应该使用您的基本 Python 解释器环境在项目文件夹中构建自己的 venv
  • 最后,选择“创建”

5.在项目 venv 中,在终端中安装依赖项:

pip install numpy
pip install pandas
pip install dash
pip install plotly
pip install gunicorn

Gunicorn 允许您通过在单个 dyno(支持所有 Heroku 应用程序的构建块)中运行多个 Python 进程来同时运行一个 Python 应用程序。

6.在项目文件夹中创建一个。gitignore 文件(告诉我们的 git 要忽略哪些文件)并在其中复制/粘贴:

venv
.DS_Store
.env
*.pyc

7.在项目文件夹中创建一个 Procfile 并粘贴这个 :

web gunicorn projectname:server

其中 projectname 是您的。您在项目创建开始时指定的 py 文件。你不会使用。以文件名结尾的 py 文件(所以去掉文件名的这一部分)。

8.创建 requirements.txt 。该命令应该从您的项目文件夹和 venv:

(venv) /projectfolder$ pip freeze > requirements.txt

9.您的 project.py 文件是您编写应用程序代码的地方。我将在另一篇文章中介绍 Dash 和 Plotly 的一些基础知识。

10.在你的应用程序的主 project.py 文件中,你需要添加下面两行。这标志着我们正在建立一个应用程序。当我们单击右上角的“运行”时,它将在本地运行,并提供一个 URL 链接,您可以根据需要调整我们的控制面板。

app = dash.Dash(__name__)
server = app.server

您的文件结构应该如下图所示。在我的例子中,我的项目(python 文件)是 multi-ml-health.py 。我们还需要确保我们的。csv(数据被提取)也在我们的项目文件夹中。

11.希望您已经位于您的项目文件夹中,并且您的 venv 是活动的。如果没有,这是下一步所需要的,因为我们将我们在 Heroku 网站上实例化的项目连接到我们使用 Python 中的 Dash 创建的仪表板。登录 Heroku:

(venv) /projectfolder$ heroku login

它将提示您选择任意键来打开浏览器,并使用您的凭据登录。

12.在项目文件夹中创建一个新的 git repo:

(venv) /projectfolder$ git init
(venv) /projectfolder$ heroku git:remote -a projectnameonHeroku

projectnameonHeroku 是你在 Heroku 上创建的名字(不是你上传的文件夹)。并准备将更改转移到 git:

(venv) /projectfolder$ git add .

提交更改并为初始应用程序启动添加消息!这将使我们对 Heroku 上的项目进行修改。: )

(venv) /projectfolder$ git commit -am "launch app"

将变更推送到 Heroku 上项目的主(主要)分支:

(venv) /projectfolder$ git push heroku master

最后三个 git 命令将用于在将来根据需要对您的 dashboard 应用程序进行调整。

更新仪表板:克隆回购

再次登录 Heroku。使用 Git 将 multimodal-ml-health 的源代码克隆到您的本地机器上。从初始部署开始,这应该理想地嵌套在您的本地文件夹系统中。

(venv) /projectfolder$ heroku login
(venv) /projectfolder$ heroku git:clone -a multimodal-ml-health 
(venv) /projectfolder$ cd multimodal-ml-health

部署您的更改

对刚刚克隆的代码进行一些修改,并使用 Git 将它们部署到 Heroku。

(venv) /projectfolder$ git add .
(venv) /projectfolder$ git commit -am "updated figure 2"
(venv) /projectfolder$ git push heroku master

仪表板示例

这是我的仪表板的链接,它是作为一个例子创建的,与提交给Nature Machine Intelligence的一篇评论文章一起开发的:

https://multimodal-ml-health.herokuapp.com/

此外,如果你喜欢看到这样的文章,并希望无限制地访问我的文章和所有由 Medium 提供的文章,请考虑使用下面的我的推荐链接注册。会员费为 5 美元/月;我赚一小笔佣金,这反过来有助于推动更多的内容和文章!

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

参考资料:

[1] Dash Enterprise,plotly.com,访问时间:2022 年 6 月 5 日
【2】Heroku, Heroku:云应用平台,访问时间:2022 年 6 月 6 日

用 Deepnote 创建数据库前端

原文:https://towardsdatascience.com/creating-a-database-front-end-with-deepnote-3271b441a47d

数据工程

下面是我如何在云中用 Python 笔记本自动完成报告的

照片由塞巴斯蒂安·科曼摄影Unsplash 拍摄

作为数据专业人员处理报告就像是在厨房里做厨师。你有大量的原材料需要采购、运输和转化为顾客的食物。

除了在这种情况下,原材料是数据,输出往往是业务涉众的报告。可以想象,在 Excel 和第三方平台上一遍又一遍地做这些是重复的,一点也不好玩。

幸运的是,大量关于重复和低价值报告的问题可以通过混合使用 SQL 和 Python 来解决(例如pandas)。我们还看到 Power BI 和 Tableau 等产品大力推动自助报告,各公司开发了自己的数据基础设施来支持这一点。

然而,许多这些公司的主要问题是,他们在运营数据的关键步骤上失败了。

当然,他们可能将数据存放在某个地方的仓库中,但是最终用户仍然需要一种灵活的方法来访问他们想要的数据。如果您的用户没有很强的 SQL 技能,并且仪表板不够用,那么数据库前端可能是您所需要的。

在本文中,我将介绍 Deepnote 以及我如何使用它为我的公司创建数据库前端。

进入 Deepnote

如果你以前没有听说过它,Deepnote 是一个免费的协作 Python 笔记本,可以在你的浏览器中运行——它与 Jupyter Notebook 非常相似,但可以在云中运行,无需安装 Python。

Deepnote ( 来源)

您可以通过使用 Python 客户端库来超越 Deepnote 的基本 SQL 查询功能,该客户端库直接连接到 Google BigQuery 等数据仓库,代码片段如下:

import json
import os
from google.oauth2 import service_account
from google.cloud import bigquerybq_credentials = service_account.Credentials.from_service_account_info(
 json.loads(os.environ[‘INTEGRATION_NAME_SERVICE_ACCOUNT’]))
client = bigquery.Client(credentials=bq_credentials, 
 project=bq_credentials.project_id)

Deepnote 还集成了 GitHub,这意味着您可以轻松创建和存储可以在多台笔记本电脑中重用的功能。这使得为您的用户自动化报告过程变得更加容易。

所有这些让我能够创建一个 Python 库(或者我喜欢称之为“Ramsey ”),通过从我们的各种营销平台中提取数据来自动生成报告。

没有复杂的 SQL 查询——只需填写参数,添加一些可选的 lambda 表达式,然后点击执行并下载。

借助 Ramsey 实现自动化数据提取和转换

对于我的特定用例(基本上是上面的 GIF ),数据从各种平台被提取到一个数据仓库中,但实际上并没有用于帮助报告。我采取了额外的步骤,为我的团队创建了一个前端,以便轻松地提取、转换和下载数据。

报告流程图

实现前端

上面的这个前端看起来似乎很简单,但实际上有很多事情在进行。

前端基于一个名为Pipeline的父类,它保存一个 SQL 查询模板,其中的参数可以根据正在使用的表进行交换。

带有 SQL 模板的父类

Pipeline类还包含生成最终 SQL 查询的函数,看起来像这样

用 Python 生成 SQL 代码

如果你曾经尝试过跨平台比较数据,比如脸书和 Twitter,你会发现几乎所有的指标和维度都有不同的名称。

这需要对数据进行大量的清理和分组。相应地,每个营销数据提供者(或 BigQuery 中的表)由一个子类表示,该子类包含各种字典,用于帮助重命名或分组维度和指标。

具有特定维度和度量的子类

简而言之,FacebookAdsPipeline子类从Pipeline父类继承了查询模板和generate_query()函数。然后,在创建和运行管道之前,用户创建一个存储必要参数的字典。

import pandas as pd
from pipeline import Pipeline
from platforms import FacebookAdsPipeline# Creating a dictionary to pass into the pipeline
params = {
"start_date": "2021-10-01",
"end_date": "2021-12-24",
"campaign_identifier": "123123123",
"campaign_name": "2022_Q1_Campaign_Name",
"output_file_type": "csv" # Options are .csv / .xlsx
}# Passing in parameters & extracting data via BigQuery SQL
fb = FacebookAdsPipeline(**params)
df = fb.extract()
df.to_csv("output.csv")

为了避免手动实例化每个子类,我创建了一个祖父类——Sandwich——它可以自动检测并从营销活动使用的每个平台中提取数据。

这消除了跟踪营销活动在哪个平台上运行的需要。总的来说,这为我的团队(老实说,是我)节省了无数时间,无论是复制粘贴 SQL 代码还是直接从每个营销平台提取数据。

用祖父类提取数据

通过存储在Sandwich类中的extract_all()函数提取数据。该函数查询跨所有营销平台的顶级活动数据的聚合表,并返回发现活动已经运行的平台列表。

然后实例化每个提供者的管道,并在每个管道上单独运行extract()方法。然后,数据按营销渠道分组,并作为字典保存在实例化的类中。

最后,用transform_all()函数转换数据,并以 CSV 或 XLSX 文件的形式保存到输出目录。

这一切都很复杂,但这是我根据用户偏好设计前端的结果。我想给用户提供一个选择,根据他们是想使用 Excel 还是 Python 进行数据清理和转换,将它作为一个低代码工具还是高代码工具。

前端选项:低代码和高代码

为未来做准备

老实说,很多工作都可以用 SQL 来完成,但这又回到了 SQL 代码复杂性的问题,以及必须在单个查询中连接大量表的问题。这还没有考虑完全聚集的表的大小(可能有几 TB ),查询起来可能会很慢/很昂贵。

在我看来,与编写数千行 SQL 代码相比,为不同的报告和项目托管单独的笔记本和小型函数更可行。

我还想要实时协作的选项,让用户可以随时随地创建新功能。最后,拥有 Python 笔记本格式为使用像scikit-learn这样的包进行更高级的分析,或者使用plotly / seaborn进行更高级的可视化铺平了道路。

归根结底,自动化报告没有单一的“正确”方法。仪表板、Python 笔记本和 Excel/Google Sheet 报告都是很好的选择。有了更多的选择,最终用户将受益匪浅。

本文到此为止!我希望它对你有深刻的见解/有帮助。如果您有任何反馈或想法,请通过 LinkedIn 联系我们。

用 Python 创建双轴组合图

原文:https://towardsdatascience.com/creating-a-dual-axis-combo-chart-in-python-52624b187834

使用 Matplotlib、Seaborn 和 Pandas plot()创建双轴组合图的实践教程

Firmbee.comUnsplash 上拍照

可视化数据对于分析数据至关重要。如果你看不到你的数据——从多方面看,你将很难分析这些数据。

来源于 Python 数据[1]

python 中有许多不同的图形来可视化数据,幸好有 Matplotlib、Seaborn、Pandas、Plotly 等。在数据分析过程中,你可以做一些非常强大的可视化。其中组合图是在同一个图形上组合两种图表类型(如条形图和折线图)。这是 Excel 最受欢迎的内置图表之一,广泛用于显示不同类型的信息。

在同一个图表上绘制多个图表非常容易,但是使用双轴一开始可能会有点混乱。在本文中,我们将探索如何用 Matplotlib、Seaborn 和 Pandas plot()创建双轴组合图。这篇文章的结构如下:

  1. 为什么使用双轴:使用同一轴的问题
  2. Matplotlib
  3. 海生的
  4. 熊猫情节()

源代码请查看笔记本。更多教程可从 Github Repo 获取。

为了演示,我们将使用来自维基百科的伦敦气候数据:

**x_label** = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
**average_temp** = [5.2, 5.3,7.6,9.9,13.3,16.5,18.7, 18.5, 15.7, 12.0, 8.0, 5.5]
**average_percipitation_mm** = [55.2, 40.9, 41.6, 43.7, 49.4, 45.1, 44.5, 49.5, 49.1, 68.5, 59.0, 55.2]london_climate = pd.DataFrame(
  {
    'average_temp': average_temp,
    'average_percipitation_mm': average_percipitation_mm
  }, 
  index=x_label
)

1.为什么使用双轴:使用同一轴的问题

在同一个图表上绘制多个图表非常容易。例如,使用 Matplotlib,我们可以简单地一个接一个地调用图表函数。

**plt.plot**(x, y1, "-b", label="average temp")
**plt.bar**(x, y2, width=0.5, alpha=0.5, color='orange', label="average percipitation mm", )
plt.legend(loc="upper left")
plt.show()

单轴组合图(图片由作者提供)

这当然会创建一个组合图,但是折线图和条形图使用相同的 y 轴。查看和解释可能非常困难,尤其是当数据在不同的范围内时。

我们可以使用双轴有效地解决这个问题。但是一开始使用双轴可能会有点混乱。让我们继续探索如何用 Matplotlib、Seaborn 和 Pandas plot()创建双轴组合图。

2.Matplotlib

制作双轴组合图的诀窍是使用共享同一 x 轴的两个不同的轴。这可以通过 Matplotlib 中的twinx()方法实现。这会生成共享同一 x 轴的两个独立的 y 轴:

# Create figure and axis #1
fig, ax1 = plt.subplots()# plot line chart on axis #1
ax1.plot(x, average_temp) 
ax1.set_ylabel('average temp')
ax1.set_ylim(0, 25)
ax1.legend(['average_temp'], loc="upper left")# set up the 2nd axis
**ax2 = ax1.twinx()**# plot bar chart on axis #2
ax2.bar(x, average_percipitation_mm, width=0.5, alpha=0.5, color='orange')
**ax2.grid(False)** # turn off grid #2
ax2.set_ylabel('average percipitation mm')
ax2.set_ylim(0, 90)
ax2.legend(['average_percipitation_mm'], loc="upper right")plt.show()

我们通常会在ax1上绘制折线图。诀窍是调用ax2 = ax1.twinx()来设置第二个轴,并在其上绘制条形图。注意,ax2.grid(False)被调用来隐藏第二轴上的网格,以避免网格显示问题。因为两个图表使用相同的 x 值,所以它们共享相同的 x 轴。通过执行代码,您应该得到以下输出:

使用 Matplotlib 的双轴组合图(图片由作者提供)

3.海生的

Seaborn 是一个构建在 Matplotlib 之上的高级数据可视化库。在幕后,它使用 Matplotlib 来绘制它的情节。对于底层配置和设置,我们总是可以调用 Matplotlib 的 API,这就是在 Seaborn 中制作双轴组合图的窍门:

# plot line graph on axis #1
ax1 = sns.lineplot(
    x=london_climate.index, 
    y='average_temp', 
    data=london_climate_df, 
    sort=False, 
    color='blue'
)
ax1.set_ylabel('average temp')
ax1.set_ylim(0, 25)
ax1.legend(['average_temp'], loc="upper left")# set up the 2nd axis
**ax2 = ax1.twinx()**# plot bar graph on axis #2
sns.barplot(
    x=london_climate.index, 
    y='average_percipitation_mm', 
    data=london_climate_df, 
    color='orange', 
    alpha=0.5, 
    **ax = ax2**       # Pre-existing axes for the plot
)
ax2.grid(b=False) # turn off grid #2
ax2.set_ylabel('average percipitation mm')
ax2.set_ylim(0, 90)
ax2.legend(['average_percipitation_mm'], loc="upper right")plt.show()

sns.lineplot()返回一个 Matplotlib 轴对象ax1。然后,类似于 Matplotlib 解决方案,我们也调用ax2 = ax1.twinx()来设置第二个轴。之后,使用参数ax = ax2调用sns.barplot()在现有轴ax2上绘图。通过执行代码,您应该得到以下输出:

使用 Seaborn 的双轴组合图(图片由作者提供)

您可能会注意到图例显示不正确,条形图为黑色。我们可以做的一个修复是使用mpatches.Patch()创建一个自定义图例:

import matplotlib.patches as mpatches# plot line graph on axis #1
ax1 = sns.lineplot(
    x=london_climate.index, 
    y='average_temp', 
    data=london_climate_df, 
    sort=False, 
    color='blue'
)
ax1.set_ylabel('average temp')
ax1.set_ylim(0, 25)
**ax1_patch = mpatches.Patch(color='blue', label='average temp')
ax1.legend(handles=[ax1_patch], loc="upper left")**# set up the 2nd axis
ax2 = ax1.twinx()# plot bar chart on axis #2
sns.barplot(
    x=london_climate.index, 
    y='average_percipitation_mm', 
    data=london_climate_df, 
    color='orange', 
    alpha=0.5, 
    ax = ax2       # Pre-existing axes for the plot
)
ax2.grid(False) # turn off grid #2
ax2.set_ylabel('average percipitation mm')
ax2.set_ylim(0, 90)
**ax2_patch = mpatches.Patch(color='orange', label='average percipitation mm')
ax2.legend(handles=[ax2_patch], loc="upper right")**plt.show()

mpatches.Patch()创建一个 Matplotlib 2D 艺术家对象,我们将它传递给轴的图例。通过执行代码,您将获得以下输出:

使用 Seaborn 的双轴组合图(图片由作者提供)

4.熊猫情节

熊猫用plot()的方法制作图表。在引擎盖下,它默认调用 Matplotlib 的 API 来创建图表(注意:后端默认为 Matplotlib)。诀窍是将参数secondary_y设置为True以允许在辅助 y 轴上绘制第二个图表。

# Create the figure and axes object
fig, ax = plt.subplots()# Plot the first x and y axes:
london_climate.plot(
    use_index=True, 
    kind='bar',
    y='average_percipitation_mm', 
    ax=ax, 
    color='orange'
)# Plot the second x and y axes. 
# By secondary_y = True a second y-axis is requested
london_climate.plot(
    use_index=True, 
    y='average_temp', 
    ax=ax, 
    **secondary_y=True,** 
    color='blue'
)plt.show()

注意,我们还设置了use_index=True来使用索引作为 x 轴的刻度。通过执行代码,您应该得到以下输出:

使用熊猫图()的双轴组合图(图片由作者提供)

结论

在本文中,我们学习了如何用【Matplotlib】SeabornPandasplot()创建双轴组合图。组合图是数据可视化中最流行的图表之一,知道如何创建双轴组合图对分析数据非常有帮助。

感谢阅读。请查看笔记本获取源代码,如果您对机器学习的实用方面感兴趣,请继续关注。更多教程可从 Github Repo 获得。

参考

用 PowerBI 创建甘特图

原文:https://towardsdatascience.com/creating-a-gantt-chart-with-powerbi-a6039dad7fe4

在多次使用现有的可视化工具创建甘特图失败后,我决定使用 PowerBI 中的矩阵可视化工具自己创建一个甘特图

实际上,我已经用一个 PowerBI 可视化工具来创建甘特图了。然而,当我试图分享它时,我意识到没有付费订阅是不可能的。你不仅需要为自己订阅,还需要为所有需要看到你的甘特图的人订阅。

照片由 Bich Tran 拍摄:https://www . pexels . com/photo/photo-of-planner-and-writing-materials-760710/

我认为构建它最简单的方法是在 PowerBI 中使用矩阵视觉来创建一个类似于在 Excel 中创建它们的方法,通过改变单元格颜色使单元格看起来像进度栏。然后,我的需求极大地增加了,因为我想为今天的日期画一条线,我必须区分过程和里程碑,等等。

我在网上找到了很多视频,它们非常有用。这是其中之一。

第一件事是将数据加载到 PowerBI。我在做春华的日程表。当 Primavera 计划导出到 Excel 文件时,它会使用各种缩进级别自动划分流程级别。缩进在 PowerBI 上不能正常工作,因此,如果您可以用一些进程 ID 来标记每个任务,它会非常有用。这是我的日程数据的快照。

在这里,子 ID 很大程度上定义了过程的级别,无论它是公司任务、里程碑还是概念设计。通过这种方式,仪表板可以建立各种任务之间的关系,并可以分别理解和可视化流程。

下图显示了 PowerBI 上的上传屏幕。一旦上传,如果还没有这样做,就使用第一行作为标题。请确保开始日期和完成日期的格式正确。我还使里程碑的开始和完成日期相同,这样当这两个日期相同时,它就不会给单元格着色。相反,它将显示一个里程碑图标。

如果你需要,这里是高级编辑器中的代码。

如您所见,我在 DAX 代码中为每一列定义了列类型。

一旦数据上传到您的仪表板,创建一个空的矩阵视觉。开始将公司名称、子 ID、活动 ID、活动名称、开始和结束添加到可视的行部分。

确保通过单击下拉菜单并选择开始和完成来删除日期层次结构。

一旦你添加了所有这些列,你会发现现在在公司名称旁边有一个“+”按钮。如果您展开所有级别,它将如下图所示。

要改变这一点,你需要做的第一件事就是关闭阶梯式布局选项。如果你点击矩阵视觉,然后格式视觉,你需要找到“ 行标题 ”下拉菜单。在那里,你会看到 选项底部的 按钮。

您也可以在此视图中关闭行和列分类汇总

同一个选项页面也可以让你关闭 +/- 图标。现在,你的矩阵视觉应该看起来像下面的图像。

我们可以在这个页面上使用一个过滤器来不显示任何带有“ NA ”的行。在筛选器窗格上,选择子 ID,然后选择全部。取消选中 NA,这将从您的视图中删除子 id 为 NA 的行。

要创建日期括号,我们需要一段 DAX 代码。回到左侧菜单中的模型屏幕,让我们为我们的计划生成一个日历。

点击顶部菜单“计算”下的“新表格”。这将在您的模型中创建一个新的空表。

一旦你点击“新表格”,PowerBI 将让你写一个以“表格=”开头的 DAX 代码。下面是我们将用来创建日历的代码。我们的约会将从 2022 年 1 月 1 日开始,一直持续到 2026 年。如果您的日程表中不需要季度、周和/或日,您不必添加它们。

Calendar =VAR _start = DATE(2022,1,1)VAR _end = DATE(2026,1,1)RETURN ADDCOLUMNS( CALENDAR(_start,_end) ,"Year", YEAR([Date]) ,"Year Month", YEAR([Date]) & " " & FORMAT([Date], "mmmm") ,"Quarter", "Q" & QUARTER([Date]) ,"Month Number", MONTH([Date]) ,"Month", FORMAT([Date], "mmmm") ,"Week", WEEKNUM([Date], 1) ,"Day", DAY([Date]) ,"Date Slicer" , IF([Date] = TODAY(), "Today" , IF( [Date] = TODAY() - 1, "Yesterday" , FORMAT([Date], "dd mmmm yyyy") & "" ) )
)

如果创建了日历,您需要将日期变量添加到矩阵视图的列中。我只打算添加年份和季度。

当您用日期填充列时,您的可视化将失败,并且您将看到一条消息说“不能显示可视化”,因为没有与我们创建的这些日期相关联的值。

下一件事是向我们创建的这些日期列添加一些值。我们需要编写一个函数来查看流程日期,并根据日期范围添加 0 或 1。如果日期范围(项目的开始和结束)与我们创建的日历日期一致,则单元格应该为 1,否则为 0。为此,您需要在您的计划数据中创建一个新的度量。

BetweenDates = VAR beginning = min ( Sheet1[Start] ) VAR finish = max ( Sheet1[Finish] ) VAR duration = max ( Sheet1[Original Duration] ) VAR colorVar = 0RETURNSWITCH (TRUE(),AND ( MAX ( 'Calendar'[Date] ) >= beginning ,  MIN ( 'Calendar'[Date] ) <= finish ) && duration >0,1
)

我们还想显示今天的日期线,并使用图标来说明里程碑。因此,代码将首先检查流程的开始和结束日期是否相同。如果是,它会用 a⭐.填充单元格否则,它将在单元格中使用“|”来模仿一行,在每一行中重复该行。为此,我们应该创建另一个称为“TodayLine”的衡量标准。

TodayLine = VAR TodayLine = "|" VAR beginning = min ( Sheet1[Start] ) VAR finish = max ( Sheet1[Finish] ) VAR duration = max ( Sheet1[Original Duration] )RETURNSWITCH ( TRUE (), duration == 0 && MAX ( 'Calendar'[Date] )  >= beginning && MIN       ( 'Calendar'[Date] ) <= finish ,  "⭐", ISINSCOPE( 'Sheet1'[Sub ID]), IF ( TODAY () IN DATESBETWEEN ( 'Calendar'[Date], MIN ( 'Calendar'[Date] ), MAX ( 'Calendar'[Date] ) ), TodayLine, ""))

一旦创建了这两个度量值,请将 TodayLine 拖动到矩阵视觉值。

TodayLine 函数将突出显示今天的日期。如果流程开始和结束日期相同,它还会添加 a⭐。

我们需要做的最后一件事是根据我们创建的 BetweenDates 度量来更改矩阵视图中的单元格颜色。

要做到这一点,点击你的矩阵视觉,去它的视觉设置,并打开背景颜色。之后,点击 fx 图标来设置正确的配色方案。

一旦点击 fx,输入屏幕将需要一个格式样式。选择规则,然后查找作为规则基础的中间日期字段。如果单元格等于 1,它应该有颜色。

现在,我们的时间表已经准备好了,它应该看起来像下面的图像!

如果您使用钻取(突出显示)选择“列”,然后单击“在层次结构中向下全部展开一级”,计划将显示更多日期功能,如季度。

有一种方法可以根据流程类型给每个流程着色。我们需要创建另一种方法来用颜色区分不同的任务。如果不需要,可以跳过下一部分。

如果它不是一个里程碑,并且我们想用它的过程名来给它着色,那么 DAX 代码的结构应该如下:

Color by Process = IF ( [BetweenDates] <> 0 , SWITCH ( MAX ( Sheet1[Sub ID] ), "Conceptual Design", "#14375A","Company Tasks", "#CC7B29"))

这将为各种过程赋予不同的颜色。但是,它将删除里程碑等流程的颜色。我不需要给一般的过程上色。所以我用了这个功能。

如果你使用它,你的时间表会是这样的:

每个过程使用一个 HTML 颜色代码。如果你想找一种颜色,你可以使用在线资源,比如 https://htmlcolorcodes.com/的

总之,我们都知道甘特图是一种众所周知的、成功的项目管理和进度安排方式。对于计划、监视和控制项目过程的一种有效、高效和实用的方法是创建嵌入甘特图的仪表板,其中许多活动都要在特定的时间范围内完成。

我希望创建一个带有甘特图的仪表板可以确保所有的事情都按时完成,让你很容易看到关键的日期和里程碑,并为你的项目设置依赖关系。

如有疑问,留言评论。

仪表板 Github 回购:【https://github.com/ak4728/PowerBI-schedule

感谢阅读!

创建电影分级模型第 2 部分:五个特征工程策略!

原文:https://towardsdatascience.com/creating-a-movie-rating-model-part-2-five-feature-engineering-tactics-dc9d363bebcd

电影分级模型

研究我在原始数据集上执行特征工程时应用的策略

你好,朋友们!我们将回到我们的电影分级模型项目,对我们收集的数据进行一些功能工程。如果你错过了第一个帖子,你可以通过这个链接赶上。如果你在 YouTube 上关注我,你会知道我实际上在这个项目上以直播的形式取得了很多进展,我现在以博客的形式来分享我们迄今为止所做的事情!

简单回顾一下上一篇文章,我们刚刚从许多不同的来源收集了所有的数据,并将原始数据集保存为 CSV 格式。如果您想查看我们收集的数据,请查看“支持数据”部分,这是我的 GitHub 库的 [README](https://github.com/dkhundley/movie-ratings-model) 的一部分,点击这里。到目前为止,数据没有任何改变。

在我们可以开始用数据测试许多不同的机器学习算法之前,我们需要在我们称为特征工程的实践中适当地清理和设计我们的数据。现在,这篇博文的目标不是要讨论我在原始数据上执行的所有特征工程操作的本质细节。欢迎您在这个 Jupyter 笔记本中看到所有这些细节,但是这篇文章的目的是为您提供一些关于如何为您自己的项目执行特性工程的考虑,而不管主题是什么。因此,虽然我们显然是在管理我们的数据集来预测电影评级,但下面的策略适用于任何项目。

事不宜迟,让我们跳进我用过的各种战术!

策略 0:知道何时忽略无用的数据

好吧,我知道这个帖子说的是五种战术,但如果你包括这一种,我想你可以说有六种战术。我称之为“策略#0 ”,因为如果你有一个包含所有有用特征的原始数据集,那么你就不需要这么做。此外,我认为你可以争辩说,这种策略不完全是一种特性工程策略,因为我们没有做任何工程。它只是从最终数据集中删除要素。

当我们收集原始数据时,我们从电影数据库(TMDb)中获得了一个名为“TMDb Popularity”的字段。我当时不确定这个特性有什么作用,但不管怎样,它是和原始数据集一起收集的。经过进一步分析,我发现这是一个高度可变的特性,用于计算某一天某部电影的受欢迎程度。例如,1999 年的原创电影《黑客帝国》在 2019 年并不那么受欢迎,但在 2021 年末随着该系列的最新作品《黑客帝国复活》在影院首映,变得非常受欢迎。因为“TMDb 流行度”是非常主观可变的,所以我认为它对我们的最终数据集来说不是一个好特性,并完全放弃了这个特性。

策略 1:简单的数字编码

数据集中的要素包含基于二进制字符串的值是很常见的。在我的特定数据集中,我的用于确定二进制肯定/否定支持率的预测要素在值中编码为“是”或“否”。因为机器学习算法需要处理数值数据,所以我们需要将这些基于字符串的值转换为数值。

因为值是二进制的(是/否),我们可以执行一个简单的数字编码,将“是”值转换为数字 1,将“否”值转换为数字 0。下面是为我们的项目实现这一目的的代码:

*# Performing the encoding of the "biehn_yes_or_no" feature*
**for** index, row **in** df**.**iterrows():
    movie_name **=** row['movie_name']
    **if** row['biehn_yes_or_no'] **==** 'Yes':
        df**.**loc[index, 'biehn_yes_or_no'] **=** 1
    **elif** row['biehn_yes_or_no'] **==** 'No':
        df**.**loc[index, 'biehn_yes_or_no'] **=** 0*# Changing the datatype of the 'biehn_yes_or_no' to int*
df['biehn_yes_or_no'] **=** df['biehn_yes_or_no']**.**astype(int)

策略 2:一个热门编码

虽然数字编码对我们上面的二进制特征很好,但我们有更多的特征,比二进制特征有更多的类别,如“是/否”。我们不能像上面那样进行简单的数字编码,因为这将固有地产生偏差,因为我们基本上是将特征转换为序数特征。在我的特定数据集中,这些分类特征之一包括主要和次要电影类型。自然,电影类型并没有一个固有的顺序。比如“喜剧类”电影,客观上并不比“剧情类”电影好。进行数字编码会产生顺序偏差,这是我们不想做的。

代替数字编码,我们想要执行一个热编码。这将把原始分类特征扩展成许多新特征,每个新特征代表在分类特征中找到的单个类别。就我们的电影类型而言,这意味着每部电影都将有一个新的个人特色。当然,对于这些电影中的一些,我们会遇到空值的问题,所以当一个热编码恰当地捕获这些空值时,我们还需要创建一个“全部捕获”特性。在我的具体例子中,我没有太多电影类型的空值,所以我很喜欢这种“全部包含”的空特性方法。如果您有大量的空值,您可能需要重新考虑您的方法。

下面是我执行的代码,用于对电影类型执行 one hot 编码。

*# Defining the OneHotEncoders for the genre columns*
primary_genre_encoder **=** OneHotEncoder(use_cat_names **=** **True**, handle_unknown **=** 'ignore')
secondary_genre_encoder **=** OneHotEncoder(use_cat_names **=** **True**, handle_unknown **=** 'ignore')*# Getting the one-hot encoded dummies for each of the genre columns*
primary_genre_dummies **=** primary_genre_encoder**.**fit_transform(df['primary_genre'])
secondary_genre_dummies **=** secondary_genre_encoder**.**fit_transform(df['secondary_genre'])*# Concatenating the genre dummies to the original dataframe*
df **=** pd**.**concat([df, primary_genre_dummies, secondary_genre_dummies], axis **=** 1)*# Dropping the original genre columns*
df**.**drop(columns **=** ['primary_genre', 'secondary_genre'], inplace **=** **True**)

策略 3:从无用的绝对值中产生相对值

乍一看,这种策略可能听起来令人困惑,但从我们的电影数据集中的一个例子来看,它是有意义的。我一路上收集到的一个特征是电影最初的上映年份。这个特性是一个绝对的整数值,因此,例如,电影《黑客帝国》在这里的值是 1999。正如前面的策略中提到的,按原样使用该数据会产生序数偏差,因为它会根据其固有的无意义的年份值对数据进行不公平的加权。

好消息是,如果我们把它转换成更相关的东西,这仍然是一个有价值的特性。具体来说,我认为这将是一个很有帮助的功能,可以知道电影评论家卡兰·比恩(Caelan Biehn)更喜欢老电影还是新电影。也就是说,我们可以将这个原始的year特性转换成一个更相关的movie_age特性,它指示从原始版本到现在的 2022 年已经过去了多少年。(如果我想在未来保持这种模式的更新,我必须在每个日历年重新进行培训。)

下面是我运行来执行这个相关工程的具体代码。

*# Extracting current year*
currentYear **=** datetime**.**now()**.**year*# Engineering the "year" column to be a relative "movie_age" column based on number of years since original release*
**for** index, row **in** df**.**iterrows():
    movie_name **=** row['movie_name']
    year_released **=** row['year']
    movie_age **=** currentYear **-** year_released
    df**.**loc[index, 'movie_age'] **=** movie_age

策略 4:删除不必要的字符

根据原始数据的存储方式,保持原样可能是好的,或者可能需要在传递到机器学习算法之前进行非常轻微的修改。在我的特定数据集中,烂番茄评论家分数存储为基于字符串的原始值,因为它的末尾有一个百分号(%)。我们想要利用与这个百分比相关的数值,但是我们不需要那个百分比符号。也就是说,我们可以执行下面的代码来去掉这个百分号,并将这个特性从基于字符串的特性转换为基于整数的特性。

*# Removing percentage sign from RT critic score*
**for** index, row **in** df**.**iterrows():
    **if** pd**.**notnull(row['rt_critic_score']):
        df**.**loc[index, 'rt_critic_score'] **=** int(row['rt_critic_score'][:2])

策略 5:处理所有的空值

在将数据传递给机器学习算法之前,我们需要对原始数据集中存在的所有空值做一些处理。这将是一个包含许多不同策略的大杂烩,在我自己的原始数据集中,我做了许多不同的事情。如何处理特定数据集的空值取决于您,但这里有几个我如何处理数据集的空值的例子:

  • 烂番茄评论家得分:看着这篇新闻文章,看起来平均评论家得分徘徊在 59%左右,所以我在这里用 59 分估算所有的空值。
  • Metacritic 的 Metascore :这个很棘手。虽然我能够找到上面的来源指出烂番茄评论家分数为 59%,但我找不到 Metascore 的对等物。不幸的是,我们将不得不以 50 英里的速度行驶在路中间。
  • 烂番茄观众评分:这也是一个很难处理的问题,因为我找不到一个可以给出明确答案的来源。从我分析数据的时间来看,我发现虽然评论家和观众可以在他们的论点上有所不同,但他们似乎都有一个电影评分在 59%左右的钟形曲线。因此,为了匹配烂番茄评论家的评分,我们也要用值 59 来填充这些空值。

下面是我运行的代码来支持上面的空插补。

*# Filling rt_critic_score nulls with critic average of 59%*
df['rt_critic_score']**.**fillna(59, inplace **=** **True**)*# Transforming RT critic score into an integer datatype*
df['rt_critic_score'] **=** df['rt_critic_score']**.**astype(int)*# Filling metascore nulls with 50.0*
df['metascore']**.**fillna(50.0, inplace **=** **True**)*# Filling rt_audience_score with audience average of 59%*
df['rt_audience_score']**.**fillna(59.0, inplace **=** **True**)

这篇文章到此结束!在执行了所有的特征工程之后,我们现在准备尝试将这些清理后的数据拟合到许多不同的机器学习算法中。在下一篇博文中,我们将测试许多不同的二进制分类和回归算法,以支持对每个评论分数的推断。我们将看看这些算法在验证指标方面的表现,然后从中选择一个作为我们的最终候选。令人兴奋的东西!感谢你阅读这篇文章,我们下期再见!

创建多层感知器(MLP)分类器模型以识别手写数字

原文:https://towardsdatascience.com/creating-a-multilayer-perceptron-mlp-classifier-model-to-identify-handwritten-digits-9bac1b16fe10

神经网络和深度学习课程:第 16 部分

阿丽娜·格鲁布尼亚Unsplash 上的原始照片,由作者编辑

现在,我们已经熟悉了神经网络的大部分基础知识,因为我们已经在前面的部分中讨论过了。是时候利用我们的知识为现实世界的应用建立一个神经网络模型了。

与卷积神经网络(CNN)、递归神经网络(RNN)、自动编码器(AE)和生成对抗网络(GAN)等其他主要类型相比,多层感知器(MLP)是最基本的神经网络架构类型。

***Table of contents*** -----------------
**1\. Problem understanding****2\. Introduction to MLPs****3\. Building the model** - Set workplace
 - Acquire and prepare the MNIST dataset
 - Define neural network architecture
 - Count the number of parameters
 - Explain activation functions
 - Optimization (Compilation)
 - Train (fit) the model
 - Epochs, batch size and steps
 - Evaluate model performance
 - Make a prediction**4\. The idea of "Base Model"****5\. Parameters vs hyperparameters in our MLP model
 -** Hyperparameter examples**6\. Summary
** - The idea of "parameter efficient"***Prerequisites - My own articles*** -------------------------------
1\. [Previous parts of my neural networks and deep learning course](https://rukshanpramoditha.medium.com/list/neural-networks-and-deep-learning-course-a2779b9c3f75)

问题理解

今天,我们将构建一个多层感知器(MLP) 分类器模型来识别手写数字。我们有 70,000 张手写数字的灰度图像,分为 10 个类别(0 到 9)。我们将用它们来训练和评估我们的模型。

识别手写数字是一个多类分类问题,因为手写数字的图像属于 10 个类别(0 到 9)。如果我们将手写数字 2 的图像输入到我们的 MLP 分类器模型,它将正确地预测数字是 2。

MLP 多阶层预测演示(图片由作者提供,用 draw.io 制作)

多层感知器简介(MLP)

在我们继续之前,有必要介绍一下多层感知器(MLP)。我已经在第二部分中定义了什么是 MLP。

关于 MLP,强调以下几点:

  • 在 MLP 中,感知器(神经元)堆叠在多层中。
  • 每层上的每个节点都与下一层上的所有其他节点相连。
  • 单个层内的节点之间没有连接。
  • MLP 是一个完全(密集)连接的神经网络(FCNN) 。所以,我们使用 Keras 中的Dense()类来添加层。
  • 在 MLP 中,数据沿一个方向(正向)通过图层从输入移动到输出。
  • MLP 在一些文献中也被称为前馈神经网络深度前馈网络
  • MLP 是一种顺序模型。所以,我们使用 Keras 中的Sequential()类来构建 MLP。

构建模型

我们将按照以下步骤构建模型。

设置深度学习工作场所来运行代码

我已经在 Part 12 中详细解释了整个过程。

如果你想在谷歌实验室运行代码,请阅读第 13 部分

获取、理解和准备 MNIST 数据集

我们使用 MNIST ( M 修正的 N 国家IN 代替 S 标准和 T 技术)数据集来训练和评估我们的模型。它包含 10 个类别(0 到 9)下的 70,000 幅手写数字的灰度图像。

下面的代码块显示了如何在构建模型之前获取和准备数据。我不打算解释这段代码,因为我已经在 Part 15 中详细解释过了。因此,我强烈建议您在进入下一步之前阅读它。

为我们的 MLP 车型获取并准备 MNIST 数据(代码由作者编写)

定义神经网络架构

  • 网络类型:多层感知器(MLP)
  • 隐藏层数: 2
  • 总层数: 4(两个隐藏层+输入层+输出层)
  • 输入形状: (784,)—输入层的 784 个节点
  • 隐藏层 1: 256 个节点, ReLU 激活
  • 隐藏层 2: 256 个节点, ReLU 激活
  • 输出层: 10 节点, Softmax 激活

这是网络架构的代码。输入层是明确定义的。也可以隐式定义。阅读第 10 部分中的完整指南。

定义 MLP 网络架构,获取参数个数(作者代码)

MLP 车型总结(图片由作者提供)

计算参数的数量

可训练参数的数量是 269,322!这是什么?通过训练我们的神经网络,我们将找到这些参数的最佳值。

这些参数包括网络中的权重和偏差项。要了解这方面的更多信息,请阅读本节。

在深度学习中,这些参数用权重矩阵( W1W2W3 )和偏置向量( b1b2b3 )来表示。要了解更多相关信息,请阅读本节。

可训练参数的总数等于权重矩阵和偏差向量中的元素总数。

  • 从输入层到第一个隐藏层:784 x 256 + 256 = 200,960
  • 从第一个隐藏层到第二个隐藏层:256 x 256 + 256 = 65,792
  • 从第二个隐藏层到输出层:10 x 256 + 10 = 2570
  • 可转换参数总数:200,960 + 65,792 + 2570 = 269,322

解释激活功能

输入层不需要激活功能。

我们需要在隐藏层中使用非线性激活函数。这是因为手写数字分类是一项非线性任务。在隐藏层中没有非线性激活函数的情况下,我们的 MLP 模型将不会学习数据中的任何非线性关系。因此,我们在两个隐藏层中都使用了 ReLU 激活函数。ReLU 是非线性激活函数。

ReLU 激活函数(图片由作者提供,用 latex 编辑器和 matplotlib 制作)

在输出层,我们使用 Softmax 激活函数。这是多类分类问题的唯一选择。输出层有 10 个节点,对应于 10 个标签(类)。

Softmax 激活功能(图片由作者提供,用 latex 编辑器制作)

Softmax 函数计算一个事件(类)在 K 个不同事件(类)上的概率值。这些类是互斥的;如果我们将每一类的概率值相加,我们得到 1.0。

最佳化

这也叫编译。这里我们配置学习参数。阅读本节了解更多相关信息。

配置学习参数(作者代码)

训练(拟合)模型

这里,我们向fit()方法提供训练数据(X 和标签)。

训练模型(作者代码)

训练时期和步骤(作者代码)

时期、批量和步骤

一个历元是整个训练数据集的完整传递。这里, Adam 优化器遍历整个训练数据集 20 次,因为我们在fit()方法中配置了epochs=20

我们把训练集分成批次(样本数)。 batch_size 为样本量(每批包含的训练实例数)。批次数量通过以下方式获得:

**No. of batches = (Size of the train dataset / batch size) + 1**

根据上面的等式,这里我们得到 469 (60,000 / 128 + 1)个批次。我们加 1 来补偿任何小数部分。

在一个时期内,fit()方法处理 469 个 步骤 。在每个优化时期,模型参数将被更新 469 次。

在每个时期,该算法采用前 128 个训练实例,并更新模型参数。然后,它采用接下来的 128 个训练实例并更新模型参数。该算法将进行这一过程,直到每个时期完成 469 个步骤。

评估模型性能

在这里,我们使用测试数据(X 和标签)对evaluate()方法评估我们的模型。我们从不使用训练数据来评估模型。

评估模型(作者代码)

损失和准确度得分(图片由作者提供)

根据算法的随机性,你会得到稍微不同的结果。您可以通过如下设置随机种子来获得静态结果。

import numpy as np
import tensorflow as tfnp.random.seed(42)
tf.random.set_seed(42)

做一个预测

现在,我们使用predict()方法对看不见的数据进行预测。我们使用测试图像组的第五幅图像。这个图像代表数字 4。如果我们的模型是准确的,它应该为数字 4 预测更高的概率值。让我们看看。

做个预测(作者代码)

我们的 MLP 模型做出的预测(图片由作者提供)

因为我们在输出图层中使用了 Softmax 激活函数,所以它会返回一个包含 10 个元素的 1D 张量,这些元素对应于每个类的概率值。预测数字位于具有最高概率值的索引处。请注意,索引从零开始。

为了获得具有最高概率值的索引,我们可以使用np.argmax()函数。

np.argmax(MLP.predict(digit, verbose=0))

这会返回 4!所以,我们的 MLP 模型对新数据做出了正确的预测!

由于所有类别都是互斥的,所以上述 1D 张量中所有概率值的总和等于 1.0。

基本模型

我们刚刚在 MNIST 数据上建立的 MLP 分类器模型被认为是我们神经网络和深度学习课程中的基础模型。我们将在 MNIST 数据上构建几个不同的 MLP 分类器模型,并将这些模型与这个基础模型进行比较。

MLP 模型中的参数与超参数

注:要了解参数和超参数的区别,看我写的这篇文章

之前我们计算了 MLP 模型中的参数数量(权重和偏差项)。

即使对于简单的 MLP,我们也需要为以下超参数指定最佳值,这些超参数控制参数值,然后控制模型的输出。

超参数

  • 网络中的隐藏层数
  • 每个隐藏层中的节点数
  • 损失函数的类型
  • 优化器的类型
  • 评估指标的类型
  • 每个隐藏层中激活函数的类型
  • 批量
  • 时代数
  • 学习率

通过改变这些超参数的值,我们可以建立许多不同的模型。例如,我们可以在网络中添加 3 个隐藏层,建立一个新的模型。我们可以在每个隐藏层中使用 512 个节点,并建立一个新的模型。我们可以改变 Adam 优化器的学习率,建立新的模型。我们可以在隐藏层中使用泄漏的 ReLU 激活函数来代替 ReLU 激活函数,并建立新的模型。每次,我们都会得到不同的结果。

请注意,一些超参数的值只有一个选项。例如,损失函数的类型总是分类交叉熵,并且输出层中的激活函数的类型总是 Softmax ,因为我们的 MLP 模型是多类分类模型。

摘要

我们的基本 MLP 模型获得了更高的准确度分数。然而,我们的 MLP 模型不是参数有效的。即使对于这个小的分类任务,它也需要 269,322 个可训练参数,仅用于两个隐藏层,每个隐藏层 256 个单元。

在下一篇文章的中,我将向您介绍一个特殊的技巧,在不改变 MLP 模型的架构和不降低模型精度的情况下,显著减少可训练参数的数量!

今天的帖子到此结束。

如果您有任何问题或反馈,请告诉我。

我希望你喜欢阅读这篇文章。如果你愿意支持我成为一名作家,请考虑 注册会员 以获得无限制的媒体访问权限。它只需要每月 5 美元,我会收到你的会员费的一部分。

*https://rukshanpramoditha.medium.com/membership

非常感谢你一直以来的支持!下一篇文章再见。祝大家学习愉快!

MNIST 数据集信息

  • 引用:邓,l,2012。用于机器学习研究的手写数字图像 mnist 数据库。 IEEE 信号处理杂志,29(6),第 141–142 页。
  • 来源:http://yann.lecun.com/exdb/mnist/
  • 许可:Yann le Cun(NYU 库朗研究所)和 Corinna Cortes (纽约谷歌实验室)持有 MNIST 数据集的版权,该数据集可根据知识共享署名-共享 4.0 国际许可(CC BY-SA)获得。您可以在此了解有关不同数据集许可类型的更多信息。

鲁克山普拉莫迪塔
2022–06–09*

为绝对初学者创建 Python PostgreSQL 连接

原文:https://towardsdatascience.com/creating-a-python-postgresql-connection-for-absolute-beginners-501b97f73de1

Python 脚本如何与 Postgres 数据库通信

Postgres 来了(图片由四月宠物杂交Unsplash 拍摄)

让 Python 连接到您的数据库并交换信息是一项非常基本的技能,但是创建实际的连接一开始可能会有点奇怪。在这篇文章中,我们将了解如何以一种高效安全的方式创建和使用连接。我们将使用一个名为psycopg2的包,并通过一些实例演示如何与数据库交互。我们来编码吧!

在开始之前..

想要连接到非 PostgreSQL 的数据库?查看下面的文章。

https://mikehuls.medium.com/how-to-make-a-database-connection-in-python-for-absolute-beginners-e2bfd8e4e52

生成 SQL 查询

本文重点介绍如何使用 pyodbc 创建与数据库的连接。然后可以使用这个连接来执行 SQL。有些数据库使用不同的语法:

  • MS SQL Server
    SELECT TOP 1 colname FROM tablename LIMIT 10
  • PostgreSQLT2

这意味着您必须为特定的数据库编写 SQL 语句。

有一种与数据库无关的方法,可以用更 Pythonic 化的方式定义查询,然后针对某个数据库进行编译。这将为那个数据库生成特定的 SQL,这样您就不会受限于您当前使用的数据库的特定内容。这意味着,如果你将来决定从 MySQL 转换到 Postgres,你不必修改你的代码。

我正在写这篇文章,所以请务必关注我

关于心理战 2

Psycopg2 是一个为 Python 编写的 PostgreSQL 数据库适配器。它是为高度多线程的应用程序而设计的,是用 C 语言编写的,侧重于有效和安全的通信。

如果你像我一样,认为“心理战 2..这个名字很怪;这是因为作者犯了一个小错误(来源):

我想称之为 psychopg(指他们的精神病司机),但我打错了名字。

尽管这个有趣的背景故事,psycopg2 是一个非常优化,安全和有效的软件包。在接下来的几个部分中,我们将使用它来建立到 Postgres 数据库的连接并执行一些 SQL。

让我们进入我们的数据库(图片由克林特·帕特森Unsplash 上提供)

创建数据库连接

下面我们将介绍如何建立和使用连接。首先,我们将通过安装依赖项来做准备:

准备:依赖性

首先,让我们创建一个虚拟环境,并安装我们唯一的依赖项。看看下面的文章,了解为什么使用 venv 非常重要,以及如何创建它们。

pip install pscopg2

第一步。创建连接

第一个目标是创建一个连接(通常每个应用程序有一个连接)。

首先,我们将通过从环境中加载凭据来获取凭据,这样我们的应用程序就可以访问凭据,而无需将它们硬编码到脚本中。

通过学习如何应用下面文章中的env files来防止您的密码泄露:

第二步。创建游标并执行

现在我们有了连接,我们可以创建一个光标。这是一个用于迭代结果集(由查询产生)的对象。

上面的代码在上下文管理器(with)语句中创建了一个游标,它有两个主要优点:

  1. 上下文管理器中的所有查询都作为一个事务来处理。任何失败的cursor.execute都将导致先前的回滚
  2. 当我们退出程序块时,光标将自动关闭

https://medium.com/geekculture/understanding-python-context-managers-for-absolute-beginners-4873b6249f16

第三步。示例查询

并不是说我们的数据库连接已经建立,我们可以开始查询我们的数据库。在下面的文章中,我们配置了一个到 MS SQL Server(以及其他数据库)的连接。本文还包含一些非常有用的示例查询,您也可以将其应用于我们的 psycopg2 连接。请务必查看它们,以便开始跑步。

https://mikehuls.medium.com/how-to-make-a-database-connection-in-python-for-absolute-beginners-e2bfd8e4e52

重要的

为了防止 SQL 注入攻击,一定要清理放入 SQL 语句中的变量,尤其是在允许用户输入语句的情况下。这一点被 这部 XKCD 著名漫画 诠释得很漂亮。

后续步骤

现在您的脚本可以连接到数据库,您可以开始编写 SQL,如下文所示。查看 此链接 了解许多便捷查询的概述。另请查看本文 中的 部分,该部分详细介绍了如何实现数据库迁移;这样你就可以使用 Python 来控制数据库结构的版本!非常有用!

结论

我希望已经阐明了如何用 psycopg2 连接到 Postgres 数据库,并使用该连接来处理数据库。我希望一切都像我希望的那样清楚,但如果不是这样,请让我知道我能做些什么来进一步澄清。与此同时,请查看我的关于各种编程相关主题的其他文章:

编码快乐!

—迈克

又及:喜欢我正在做的事吗? 跟我来!

https://mikehuls.medium.com/membership

使用 Jupyter 和 autocalc 为您的数据分析项目创建一个用户界面

原文:https://towardsdatascience.com/creating-a-ui-with-ipywidgets-and-autocalc-2ef8ea4cc6c2

autocalc 软件包如何让您自动重新计算变量

App 预览。作者图片

这篇文章的目标读者是使用 Python 的数据科学家,他们希望用最少的努力为他们的项目创建简单的用户界面,如上图所示。

在我之前的文章中,我介绍了 autocalc 包,它可以让你组织内部变量之间的依赖关系。在这篇文章中,我们将更进一步,实现一个更复杂的例子,涉及机器学习,模型拟合和预测。

特别是,我们将:

  • 加载分类数据集,并将其分为训练样本和测试样本
  • 为用户提供微调机器学习模型的超参数(来自 scikit-learn 的支持向量分类器)
  • 报告测试数据集的混淆矩阵
  • 提供一个界面,根据手动输入的参数运行模型。

注意,这篇文章的目的是展示如何使用 Jupyter 笔记本和自动计算包来实现这一点。我们在这里的目的不是深入机器学习算法的细节,也不是提供任何意义上的“最佳”模型。有兴趣的读者可以参考分类器的文档

在文章的最后你会找到整个笔记本的源代码的链接,它包含了下面所有的代码单元。

注意:如果没有安装 autocalc,可以用pip install autocalc安装。

所以让我们开始吧!

进口

像往常一样,我们从导入相关的包开始

创建主布局

让我们从设计我们的小应用程序的外观开始。这个想法是在超参数的左边有一列,在右边有两个选项卡:一个用于混淆矩阵,另一个用于预测。注意,我们使用标准的 IPywidgets 作为我们的接口。

创建和显示超参数的输入

读取 defaulf 超参数值

幸运的是,scikit-learn 提供了一种以编程方式读取估计器默认参数设置的方法。这是通过用默认参数(即空参数列表)然后使用get_params方法。 SVC 类有几个超参数,如Ckerneldegree等。它们中的一些(例如Ckernel)总是相关的,而其他的,只有当其他参数取某个值时才相关。例如degree仅在kernel=='poly'时相关。

指定输入

让我们从简单的开始,它们总是相关的。注意,autocalc.autocalc.Var的第一个位置参数是一个字符串,在显示Var时使用,或者作为一个小部件集,或者在使用例如str(Var('xyz')时使用。本例中使用的其他参数或者不言自明,或者将在相关阶段详细说明。

如上所述,degree参数仅在kernel=='poly'时使用。所以我们决定显示,但是当情况不是这样时,禁用相应的小部件。这里的想法是像往常一样创建 degree-Var,并给kernel变量分配一个回调,这将在需要时禁用/启用 degree 小部件。

请注意,我们如何使用Var来创建回调。这里需要注意的是autocalc的方法与ipywidget.observe方法的不同之处:

  • 当使用autocalc时,动作被附加到被作用的实体,而不是触发动作的实体。
  • ipywidgets中,动作由微件触发。在autocalc的情况下,动作由Var触发,其可能具有关联的窗口小部件,但这不是必需的。

前一个单元格中的代码功能齐全,并给出了如何处理这种情况的想法。然而,对于其他变量,我们将多次使用上述模式。所以让我们避免代码重复,为此创建一个Var的子类。我们将使用与上面相同的方法。

定义了类CVar之后,我们可以从#+-+-+-+-+开始退出代码片段,使用更直接的定义。(你可以安全地删除那部分代码,不会影响功能。它只是为了更容易理解CVar的定义。)

根据文档,gamma的值可以是'auto''scale'或浮点数。我们将使用下面的两个小部件来实现它。注意,自定义gamma值的输入仅在选择'custom'时有效。

现在,我们已经创建了小部件,是时候将它们显示在模型参数框中了。请注意,显示带有关联小部件的Var会导致显示其名称,后跟其小部件。如果你想把这个部件集放在一个HBox中,你必须使用Var对象的.widget_set字段。如果您只想显示小部件,您可以使用.widget字段访问它。当在下面最后一行显示gamma参数的输入时,这两者都会显示出来:

加载数据

在本例中,我们将使用鸢尾数据集,该数据集将用于根据鸢尾植物的某些特征来识别其类型。

创建输出

我们已经指定了我们的输入部件,我们已经加载了数据。是时候处理了。记住,我们希望有两种不同类型的输出:混淆矩阵和预测器。它们都将使用 fitted SVC对象,因此将其定义为一个单独的变量是有意义的,这样就可以在它们之间共享。

混淆矩阵

下面的model_fun函数用于从超参数创建拟合的SVC对象,而confusion_matrix_fun函数使用SVC对象创建混淆矩阵的可绘图表示,也称为“模型”。

在这两个函数中,我们都使用虹膜数据集。这不会出现在输入参数中,对于我们的目的来说,它被认为是一个常数。

模型和混淆矩阵图都将被包装在Var中,这样我们就可以自动化它们的计算。注意,model变量是用lazy=True参数定义的。这个参数是用来做什么的?到目前为止,我们声明的Var要么是输入,要么是根据其他Var(例如gamma)计算出来的,计算速度非常快。所以当他们的一个输入改变时,我们可以立即重新计算。

机器学习模型(如SVC)有许多输入参数,一般来说,计算起来很慢。因此,我们不希望在更改其中一个参数后重新计算模型,而是希望更改我们需要的所有参数,然后让重新计算发生。(注意:当前数据集非常小,因此重新计算只需几秒钟,但通常输入数据集要大几个数量级。)

这是通过lazy标志实现的。如果任何输入改变,Var将进入“未定义”状态(Var.is_undefined评估为True),而不是重新计算。它进入这种状态的原因是为了避免我们的变量不一致:一个Var有一个不同的状态,要么是由它的输入产生的,要么是被显式覆盖的。

为了强制重新计算一个懒惰的Var,需要显式调用它的.get.recalc方法。

model变量相反,cm并不懒惰,但是它依赖于一个懒惰变量(model)。因此,当model的其中一个输入发生变化时,cm也将处于未定义状态。

所以现在model很懒,也就是说我们需要一些手动触发来让它评估。我们将使用一个按钮小部件来实现,它将显示在超参数小部件的下面。

model变量是内部变量,不会向用户透露。因此,如果没有定义,用户就看不到它。但是对于公共变量(即。用户看到的那些)需要对未定义的状态做些什么。如果一个公共变量有一个小部件,它可以有一个未定义的状态(例如一个可以是空的文本框),那么它由autocalc处理。但是当一个更复杂的变量,比如混乱矩阵图,变得不确定时,我们必须指定该做什么。

下面的cm_display函数将作用于cm Var的值。如果定义正确,它将绘制在输出框中。否则,该框中的内容将被清除,并替换为一条消息。

关于cm_display功能,我们需要注意几点:

  1. 我们明确地检查了它的输入是否未定义。在前面的函数中,我们不必做这样的事情。这是因为,默认情况下,这些函数不必处理未定义输入。如果他们的任何一个输入未定义,该函数甚至不会被评估,并且有问题的Var也将被设置为未定义状态。要覆盖这种行为,需要将undefined_input_handling参数设置为'function',如下所示。
  2. 注意,我们如何检查cm是否被定义:cm is undefined,尽管如上所述,检查Var是否被定义需要使用.is_undefined方法。值得注意的是,这些在Varfun参数中给出的函数总是作用于Var内部的值,而不是Var对象本身。所以cm_display函数范围内的cm变量与外部范围内的cm变量不同,而是它的“内部”值:cm._get_raw()。虽然这可能会造成一些混乱,但基本原理是在autocalc框架中,我们希望尽可能重用原始函数,这些函数作用于“普通”变量,而不是Var变量。只有在这些特殊情况下,我们才需要为变量undefined做准备。
  3. 我们重用了之前创建的calibrate_button小部件。这个按钮现在可能出现在两个不同的地方。但是对朱庇特来说是可以的。

预言者

预测器的想法是,当用户遇到一株鸢尾时,她可以要求模型根据variable_list中列出的属性来识别它,如“萼片长度”、“萼片宽度”、“花瓣长度”和“花瓣宽度”。

所以我们为这些参数创建输入部件。我们使用 Python list-comprehension 从variable_list创建Var,并用来自训练集第一条记录的相应值初始化这些值。

我们需要一个函数来将校准的模型和测量的属性转换为预测,我们需要显示:

我们最终的应用程序将如下所示:

结束语

在本文中,我们围绕机器学习任务创建了一个简单的应用程序。我们使用 Jupyter 笔记本作为平台,Ipywidgets 用于大多数输入和输出,autocalc 包用于连接我们的组件。

我希望这篇文章能帮助你在 Jupyter notebook 中轻松创建类似的应用程序。如果你喜欢我的帖子,请鼓掌,关注,分享,评论。这鼓励我生产更多。

  • 赠送的自动计算包是我写的。我欢迎对 github 的评论/建议/请求。
  • 这个帖子最初是作为 Jupyter 笔记本创建的。作为单个笔记本的代码可以作为 github gist 获得。
  • 使用 jupyter_to_medium 工具将其转换为介质。非常感谢 Ted Petrou 的这个包。

https://medium.com/dunder-data/jupyter-to-medium-initial-post-ecd140d339f0

用 Python 创建一个从音频中提取主题的 Web 应用程序

原文:https://towardsdatascience.com/creating-a-web-application-to-extract-topics-from-audio-with-python-21c3f541f3ca

为 Spotify 播客的主题建模构建和部署 web 应用程序的分步教程

伊斯雷尔·帕拉西奥Unsplash 拍摄的照片

这篇文章是故事的续篇,讲述如何用 Python 构建一个 Web 应用程序来转录和总结音频。在上一篇文章中,我展示了如何构建一个应用程序来转录和总结您最喜欢的 Spotify 播客的内容。课文的摘要有助于听众在听之前决定这一集是否有趣。

但是还有其他可能的特征可以从音频中提取出来。话题。主题建模是许多自然语言处理中的一种,它能够从不同类型的源中自动提取主题,如酒店评论、工作机会和社交媒体帖子。

在这篇文章中,我们将构建一个应用程序,用 Python 收集播客中的主题,并通过漂亮的数据可视化分析每个主题的重要性。最后,我们将免费把 web 应用程序部署到 Heroku。

目录:

  • 第 1 部分:创建提取主题的 Web 应用程序
  • 第 2 部分:将 Web 应用程序部署到 Heroku

要求

  • 创建一个 GitHub 存储库,这是将 web 应用程序部署到 Heroku 的生产环境中所需要的!
  • 使用git clone <name-repository>.git在本地 PC 上克隆存储库。在我的例子中,我将使用 VS code,这是一个处理 python 脚本非常有效的 IDE,包括 Git 支持并集成了终端。在终端上复制以下命令:
git init
git commit -m "first commit"
git branch -M master
git remote add origin https://github.com/<username>/<name-repository>.git
git push -u origin master
  • 用 Python 创建一个虚拟环境。

第 1 部分:创建提取主题的 Web 应用程序

本教程分为两个主要部分。在第一部分中,我们创建了一个简单的 web 应用程序来从播客中提取主题。剩下的部分重点是应用的部署,这是随时与世界分享你的应用的重要一步。我们开始吧!

1。从收听笔记中提取剧集的 URL

我们将从一集名为《想要一份加密的工作?交易所正在招人。110.你可以在这里找到《T2》的链接。正如你从电视和报纸上的新闻中所知道的,区块链的工业正在飞速发展,这一领域的职位空缺需要不断更新。当然,他们将需要数据工程师和数据科学家来管理数据,并从这些海量数据中提取价值。

Listen Notes 是一个播客搜索引擎和在线数据库,允许我们通过其 API 访问播客音频。我们需要定义从网页中提取剧集 URL 的函数。首先,您需要创建一个帐户来检索数据,并订阅免费计划来使用 Listen Notes API。

然后,点击你感兴趣的剧集,在页面右侧选择“使用 API 获取该剧集”选项。一旦您按下它,您就可以将默认的编码语言更改为 python,并单击 requests 选项来使用该 Python 包。之后,您复制代码并将其改编成一个函数。

它从一个单独的文件 secrets.yaml 中获取凭证,该文件由一组类似字典的键值对组成:

api_key:<your-api-key-assemblyai>
api_key_listennotes:<your-api-key-listennotes>

2。从音频中检索转录和主题

为了提取主题,我们首先需要向 AssemblyAI 的脚本端点发送一个 post 请求,输入上一步中检索到的音频 URL。之后,我们可以通过向 AssemblyAI 发送 GET 请求来获取脚本和播客的主题。

结果将保存到两个不同的文件中:

下面我展示一个转录的例子:

Hi everyone. Welcome to Unconfirmed, the podcast that reveals how the marketing names and crypto are reacting to the week's top headlines and gets the insights you on what they see on the horizon. I'm your host, Laura Shin. Crypto, aka Kelman Law, is a New York law firm run by some of the first lawyers to enter crypto in 2013 with expertise in litigation, dispute resolution and anti money laundering. Email them at info at kelman law. ....

现在,我展示从播客的剧集中提取的主题的输出:

我们获得了一个 JSON 文件,包含 AssemblyAI 检测到的所有主题。本质上,我们将播客转录成文本,文本被分解成不同的句子及其相应的相关性。对于每个句子,我们都有一个主题列表。在这本大字典的最后,有一个从所有句子中提取的主题总结。

值得注意的是,职业和求职是最相关的话题。在前五个标签中,我们还发现商业和金融、创业公司、经济、商业和银行、风险投资和其他类似的主题。

3。使用 Streamlit 构建 Web 应用程序

已部署应用的链接是这里是

现在,我们将前面步骤中定义的所有函数放入主程序块中,在主程序块中,我们使用 Streamlit 构建我们的 web 应用程序,Streamlit 是一个免费的开源框架,允许使用 Python 用几行代码构建应用程序:

  • 使用st.markdown显示应用程序的主标题。
  • 使用st.sidebar创建一个左侧面板工具条。我们需要它来插入播客的剧集 id。
  • 按下“提交”按钮后,将出现一个条形图,显示提取的最相关的 5 个主题。
  • 如果您想下载转录、主题和数据可视化,可以点击下载按钮

要运行 web 应用程序,您需要在终端上编写以下命令行:

streamlit run topic_app.py

太神奇了!现在应该会出现两个 URL,单击其中一个,web 应用程序就可以使用了!

第 2 部分:将 Web 应用程序部署到 Heroku

一旦您完成了 web 应用程序的代码,并检查了它是否运行良好,下一步就是在互联网上将它部署到 Heroku。

你可能想知道什么是 Heroku。它是一个云平台,允许使用不同的编码语言开发和部署 web 应用程序。

  • 创建 requirements.txt、Procfile 和 setup.sh
  • 连接到 Heroku
  1. 创建 requirements.txt、Procfile 和 setup.sh

之后,我们创建一个文件 requirements.txt ,其中包含了您的脚本所请求的所有 python 包。我们可以通过使用这个神奇的 python 库 pipreqs 使用下面的命令行自动创建它。

pipreqs

它会神奇地生成一个 requirements.txt 文件:

避免使用命令行pip freeze > requirements,就像本文建议的那样。问题是它返回了更多的 python 包,而这些包可能不是特定项目所需要的。

除了 requirements.txt 之外,我们还需要 Procfile,它指定了运行 web 应用程序所需的命令。

最后一个要求是要有一个包含以下代码的 setup.sh 文件:

mkdir -p ~/.streamlit/echo "\
[server]\n\
port = $PORT\n\
enableCORS = false\n\
headless = true\n\
\n\
" > ~/.streamlit/config.toml

2。连接到 Heroku

如果你还没有在 Heroku 的网站上注册,你需要创建一个免费账户来使用它的服务。在你的本地电脑上安装 Heroku 也是必要的。一旦你完成了这两个要求,我们就可以开始有趣的部分了!在终端上复制以下命令行:

heroku login

按下命令后,一个 Heroku 的窗口会出现在你的浏览器上,你需要输入你的邮箱和密码。如果成功,您应该会得到以下结果:

因此,您可以返回 VS 代码,编写命令在终端上创建您的 web 应用程序:

heroku create topic-web-app-heroku

输出:

要将应用程序部署到 Heroku,我们需要以下命令行:

git push heroku master

它用于将代码从本地存储库的主分支推送到 heroku remote。使用其他命令将更改推送到存储库之后:

git add -A
git commit -m "App over!"
git push

我们终于完成了!现在你应该看到你的应用程序终于部署了!

最终想法:

我希望你欣赏这个迷你项目!创建和部署应用程序真的很有趣。第一次可能会有点吓人,但一旦完成,你就不会有任何遗憾了!我还想强调的是,当您在处理内存需求较低的小项目时,最好将您的 web 应用程序部署到 Heroku。其他替代方案可以是更大的云平台框架,如 AWS Lambda 和谷歌云。GitHub 的代码在这里是。感谢阅读。祝您愉快!

你喜欢我的文章吗? 成为会员 每天无限获取数据科学新帖!这是一种间接的支持我的方式,不会给你带来任何额外的费用。如果您已经是会员, 订阅 每当我发布新的数据科学和 python 指南时,您都会收到电子邮件!

为时间序列预测创建 ARIMA 模型

原文:https://towardsdatascience.com/creating-an-arima-model-for-time-series-forecasting-ff3b619b848d

在 AirPassengers 数据集中介绍和实现 ARIMA 模型

照片由卢卡斯像素上拍摄

时间序列预测包括做出预测,以便在广泛的应用中推动未来的战略决策。基于在这篇上一篇文章中介绍的一些术语,如趋势和季节性,这篇文章主要关注基于自回归、集成和移动平均的时间序列预测模型的实现。具体来说,结构展开如下:

  1. AR、I、MA 术语介绍
  2. 寻找模型的顺序
  3. ARIMA 模式的实施
  4. 带回家的信息

对于本文,我使用了开放数据库“ Air Passengers ”,它提供了从 1949 年到 1960 年美国航空公司乘客的月度总数。该数据集包括但不限于对作品进行再许可和将其用于商业用途的权利。代码可以在下面的 GitHub 资源库中找到。

1.AR、I、MA 术语介绍

1.1.自回归(AR)

术语 AR 代表自回归,表示模型使用当前数据与其过去值之间的依赖关系。用于预测下一个值的先前输入的数量被称为,通常被称为 p

换句话说,序列的当前值可以解释为过去的 p 值的线性组合:

其中,𝜖𝑡是白噪声过程(均值为 0,方差恒定,误差不相关),𝑎𝑖是估计值。

根据 p 的值,有以下几种情况:

  1. AR(0):如果 p 参数设为零,则没有自回归项,所以这个时间序列只是白噪声。
  2. AR(1):在 p 参数设置为 1 的情况下,我们考虑的是之前经过乘数调整的时间戳,然后加上白噪声。
  3. AR(p):增加 p 参数意味着增加更多由各自乘数调整的时间戳。

1.2.综合(一)

I 术语代表 integrated,把你的非平稳时间序列变成平稳时间序列。

问:为了使用 ARIMA 模型,我的时间序列应该是平稳的吗?[1]

答:如果你想直接使用 ARMA(p,q ),那么你的时间序列最好是平稳的。在实践中,对于“平稳性”总是有一定程度的不确定性,因为你只是观察现实,并不知道真正的随机过程随机变量。这种不确定性意味着你只是近似地看到它是平稳的,并试图应用 ARMA 模型,或强行使用 d 数,尽管这将使你表现不佳。

1.3.移动平均线

移动平均模型使用观测值和应用于滞后观测值的移动平均的残差之间的相关性。换句话说,MA 将预测值建模为过去误差项的线性组合:

其中𝜖𝑡是白噪声过程(均值为 0,方差恒定,误差不相关), b 𝑖是估计值, rt 定义为

其中,𝑦̂𝑡是预测值,𝑦𝑡是真实值。

2.寻找模型的顺序

2.1.导入数据

首先,让我们导入库。注意 statsmodels 的版本是 0.13.2 ( print(statsmodels。version))。

对于本文的其余部分,让我们考虑一下航空乘客数据集,它提供了从 1949 年到 1960 年美国航空乘客的月度总数。

2.2.评估时间序列的平稳性

平稳时间序列被定义为其性质不依赖于观察时间的时间序列。从更数学的意义上来说,当协方差与时间无关时,时间序列是稳定的,并且它在一段时间内具有恒定的均值和方差。但是,为什么平稳性很重要?这很重要,因为许多模型假设认为时间序列是平稳的。

可视化趋势和季节性的第一种方法是表示滚动平均值。

图 1。滚动平均值和标准值。参考:图片由作者提供。

如图所示,随着时间的推移,有增加的趋势。此外,还有一个年度季节性因素。

此外,扩展的 Dickey-Fuller 检验用于确定时间序列数据是否是平稳的。类似于 t-检验,我们设置了一个显著性水平,并根据得到的 p 值得出结论。

  • 零假设:数据不是静态的。
  • 替代假设:数据是稳定的。

对于静止的数据(即拒绝零假设),ADF 测试的 p 值必须低于 0.05。

Results of Dickey-Fuller Test:
Test Statistic                   0.815369
p-value                          0.991880
Lags Used                       13.000000
Number of Observations Used    130.000000
Critical Value (1%)             -3.481682
Critical Value (5%)             -2.884042
Critical Value (10%)            -2.578770
dtype: float64

结果表明 p 值为 0.992,这意味着数据很可能不是稳定的。

以下函数将时间序列分解为趋势分量、季节分量和残差分量,因此也可用于研究时间序列的平稳性。

图二。时间序列分解。参考号:图片由作者提供。

在图中,我们还可以观察到每年的季节性和随时间推移而增加的趋势。

但是,如果时间序列是平稳的,会发生什么?在这种情况下,有必要将数据转换成平稳的时间序列。

一般来说,在进行时间序列预测时,遵循以下步骤是一种很好的做法:

  • 第一步——检查平稳性:如果一个时间序列有趋势或季节性成分,它必须是平稳的。
  • 第二步——确定 d 值:如果时间序列不是平稳的,需要通过差分来平稳化。
  • 步骤 3 —选择 AR 和 MA 术语:使用 ACF 和 PACF 来决定是否包括 AR 术语、MA 术语、(或)ARMA。
  • 步骤 4——构建模型

第 3 步和第 4 步包含在第 2.4-2.7 节中,而第 5 步包含在第 3 节中。

2.3.选择训练集和测试集

在继续之前,让我们创建一个训练和测试集,以避免在预测中出现偏差。

2.4.求 d(积分)参数的值

如果数据不是平稳的,就需要找到使时间序列成为平稳的综合参数。

因为没有一种方法可以告诉我们最佳的 d 值,所以让我们画出一阶和二阶差分:

图 3。时序差分。参考:图片由作者提供。

在对时间序列进行差分后,趋势性和季节性都有所减弱。

也可以使用自相关图来评估最佳的 d 值。当数据中存在趋势时,小滞后的自相关性往往较大且为正,随着滞后的增加而缓慢下降。当时间序列中有季节性成分时,季节性滞后(季节频率的倍数)的自相关性将大于其他滞后。

图 3。自相关为不同阶差分。参考号:图片作者。

在这里,我们可以看到,在二阶差分中,直接滞后已经变为负值,这表示在二阶差分中,级数已经超过了差分。因此,我们将选择一阶差分。

2.5.求 p(自回归)参数的值

在上一节中,我们已经确定了 d 的最佳值。现在,在这一节中,让我们通过检查 PACF 图来找到自回归项的最佳数量。

部分自相关函数图可用于绘制时间序列及其滞后之间的相关性。平稳时间序列中的显著相关性可以通过添加自回归项来表示。利用 PACF 图,我们可以把显著的滞后作为 AR 项。

图 4。偏自相关。参考:图片由作者提供。

由于有许多显著的滞后,我们可以选择大量的自回归项。

2.6.寻找 q(移动平均)参数的值

为了找出 q 的值,我们可以使用 ACF 图,表示为:

自相关公式

其中 T 是时间序列的长度,k 是应用于时间序列的滞后。

图 5。自相关。 Ref :图片作者。

这里我们看到第二个滞后已经超出了显著性极限。

为了更深入地理解 PACF 和 ACF 的定义,我强烈推荐阅读下面的文章

2.7.使用 auto_arima 查找参数

最后,刚才提到有一个函数,它寻求确定一个**ARIMA**模型的最佳参数,确定在一个单一拟合的 ARIMA 模型上。

然而,这个输出与 SARIMAX 模型相关,这在本文中没有涉及。因此,我们将忽略所获得的结果,而专注于上面实现的分析。

3.ARIMA 模式的实施

最合适的模型将取决于数据的具体特征,如趋势和季节性。在本文中,我们主要关注 ARIMA 模型的实现。

使用从前面的分析中得出的参数(p=12,d=1,q=1 ),让我们检查输出:

图 6。 ARIMA 预测产量。参考:图片由作者提供。

这是输出,其中橙色线表示预测,灰色阴影表示预测区间,用于提供一个范围,在该范围内,预测可能具有特定的置信度[3]。

4.带回家的信息

在本文中,我们讨论了在 ARIMA 模型中寻找参数值的过程。在结束本文之前,请记住以下主要提示:

  • 自回归模型(AR)使用过去的预测来预测未来值。
  • I 术语代表 integrated,把你的非平稳时间序列变成平稳时间序列。
  • 移动平均(MA)模型不使用过去的预测来预测未来值,而是使用过去预测的误差。
  • 使用 PACF 来确定 AR 模型中使用的术语,使用 ACF 来确定 MA 模型中使用的术语
  • 我们可以追溯到我们想要选择的 AR(p)项,但是当我们追溯到更远的时候,我们更有可能应该使用额外的参数,例如移动平均线(MA(q))。

如果你喜欢这个帖子,请考虑 订阅 。你将获得我所有的内容+所有其他来自牛逼创作者的文章!

参考

[1] StackExchange,我的时间序列应该是平稳的才能使用 ARIMA 模型吗?

[2] Otexts,平稳性和差分性

[3]中,时间序列预测预测区间

重要参考

其他:

用 Scikit-Learn 创建集成投票分类器

原文:https://towardsdatascience.com/creating-an-ensemble-voting-classifier-with-scikit-learn-ab13159662d

您可以使用 Python 创建自己的具有不同算法的分类器

照片由 Element5 数码Unsplash 上拍摄

介绍

分类集成模型是由适合相同数据的许多模型组成的模型,其中分类的结果可以是多数人的投票、结果的平均值或最佳执行模型结果。

图 1:带有投票结果的集合模型。图片由作者提供。

在图 1 中,有一个我们将在这个快速教程中构建的投票分类器的例子。观察到有三个模型符合数据。其中两个把数据归类为 1,一个归类为 0。所以,通过大多数人的投票,1 班获胜,这就是结果。

在 Scikit-Learn 中,集成模型的一个常用示例是随机森林分类器。顺便说一句,这是一个非常强大的模型,它使用许多决策树的组合来给我们一个观察的最佳结果。另一个选项是梯度增强模型,这也是一个集合类型的模型,但它有一个不同的配置来获得结果。

如果你感兴趣,这里有一篇非常完整的 TDS 文章是关于打包和提升集合模型的。

然而,这些都是为了方便我们作为数据科学家的生活而创建的预包装模型。它们表现得非常好,并将提供良好的结果,但它们只使用一种算法来训练模型。

如果我们想用不同的算法创建自己的投票分类器会怎么样?

这就是我们将要学习的内容。

投票分类器

投票分类器使用选择的算法训练不同的模型,返回多数人的投票作为分类结果。

在 Scikit-Learn 中,有一个名为VotingClassifier()的类可以帮助我们以一种简单的方式创建具有不同算法的投票分类器。

首先,导入所需的模块。

# Dataset
from sklearn.datasets import make_classification# sklearn
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import GradientBoostingClassifier, VotingClassifier
from sklearn.metrics import f1_score, accuracy_score

让我们为我们的练习创建一个数据集。

seed=56456462
# Dataset
df = make_classification(n_samples=300, n_features=5, n_informative=4, n_redundant=1, random_state=seed)# Split
X,y = df[0], df[1]# Train Test
X_train, X_test, y_train, y_test = train_test_split(X,y, test_size=0.3, random_state=seed)

好了,一切就绪。接下来我们需要决定我们想要使用的算法。我们将使用逻辑回归决策树和集合模型梯度推进的组合。因此,我们可以注意到,投票分类器可以由其他集成模型组成,这很好。想象一下用梯度推进聚集随机森林的力量?

# Creating instances of the algorithms
logit_model = LogisticRegression()
dt_model = DecisionTreeClassifier()
gb_model = GradientBoostingClassifier()

现在,我们有了组成投票分类器的所有东西。

# Voting Classifier
voting = VotingClassifier(estimators=[
          ('lr', logit_model),
          ('dt', dt_model),
          ('gb', gb_model) ],
           voting='hard')

voting='hard'是默认值,它意味着用多数规则投票来预测类标签。接下来,让我们创建这些模型的列表,这样我们可以循环它们来分别比较结果。

# list of classifiers
list_of_classifiers = [logit_model, dt_model, gb_model, voting]# Loop scores
for classifier in list_of_classifiers:
    classifier.fit(X_train,y_train)
    pred = classifier.predict(X_test)
    print("F1 Score:")
    print(classifier.__class__.__name__, f1_score(y_test, pred))
    print("Accuracy:")
    print(classifier.__class__.__name__, accuracy_score(y_test, pred))
    print("----------")

结果是:

F1 Score: LogisticRegression 0.8260869565217391
Accuracy: LogisticRegression 0.8222222222222222
---------- 
F1 Score: DecisionTreeClassifier 0.8172043010752689 
Accuracy: DecisionTreeClassifier 0.8111111111111111 
---------- 
F1 Score: GradientBoostingClassifier 0.8421052631578948 
Accuracy: GradientBoostingClassifier 0.8333333333333334 
---------- 
F1 Score: VotingClassifier 0.851063829787234 
Accuracy: VotingClassifier 0.8444444444444444 
----------

图 2:优于独立模型的投票分类器。图片由作者提供。

在这个例子中,投票分类器优于其他选项。F1 分数(正类准确性和真阳性率的混合)和准确性分数都略高于单独的梯度增强,并且比单独的决策树好得多。

值得注意的是,如果您更改种子值,输入数据集也会更改,因此您可能会得到不同的结果。例如,尝试使用seed=8,您将得到这个结果,其中投票分类器被逻辑回归和梯度提升所超越。

图 3:投票分类器的性能比 Logit 和 Grad 差。助推模型。图片由作者提供。

我告诉你这些是因为表明数据科学不是一门精确的科学是很重要的。它依赖于精确的科学,但不仅仅是成功的秘诀。大多数情况下,为了得到最终的结果,你必须对你的模型进行更多的调整。但是拥有像本文中介绍的工具可以给你很大的帮助。

在你走之前

集合模型是很好的选择,它们经常能提供很好的结果。

  • 他们不太可能过度拟合数据,因为他们用不同的数据分割来训练许多模型
  • 它们可以提供更好的准确性,因为有更多的模型确认分类是在正确的方向上。
  • VotingClassifier()可以帮助你用不同的算法创建一个集成模型。
  • 语法:使用带有VotingClassifier("name of the model", Instance() )的元组

如果你喜欢这个内容,请关注我的博客。也可以在 LinkedIn 上找到我。

http://gustavorsantos.medium.com/

参考

奥雷连恩·盖伦, 2019。用 Scikit-Learn,Keras&tensor flow动手机器学习。第二版,奥赖利。

https://www.amazon.com/Hands-Machine-Learning-Scikit-Learn-TensorFlow/dp/1492032646/ref=asc_df_1492032646/?tag=hyprod-20&linkCode=df0&hvadid=385599638286&hvpos=&hvnetw=g&hvrand=10774777250311570064&hvpone=&hvptwo=&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=9009674&hvtargid=pla-523968811896&psc=1&tag=&ref=&adgrpid=79288120515&hvpone=&hvptwo=&hvadid=385599638286&hvpos=&hvnetw=g&hvrand=10774777250311570064&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=9009674&hvtargid=pla-523968811896 https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.VotingClassifier.html#sklearn.ensemble.VotingClassifier

用 Python 创建多井综合测井和地层顶部数据框架

原文:https://towardsdatascience.com/creating-an-multi-well-integrated-well-log-and-formation-tops-dataframe-in-python-469670550aaf

在 Python 中结合多口井的地层数据和测井测量

照片由 Quintin Gellar 拍摄:https://www . pexels . com/photo/rock-formation-wallpaper-612893/

当处理测井测量和地下数据时,我们经常要处理不同的文件格式和采样率。例如,测井记录测量值通常在内存储和传输。las 文件或 dlis 文件 并且每 0.1 米或 0.5 英尺采样一次。另一方面,地质构造顶部是单个离散深度点。这需要对地层数据进行插值,以匹配测井测量的采样率。

在我之前的教程 中,我们看到了如何合并单井的测井数据和地层数据。在本教程中,我们将了解如何对多口井进行此操作。

导入库

该过程的第一步是导入我们将使用的库。

对于本教程,我们将使用 lasio 来加载。las 文件, os 从目录中读取文件, 熊猫 使我们能够处理数据帧, csv 加载 csv 文件中存储的地层数据。

使用 LASIO 导入测井 LAS 文件

接下来,我们将开始导入数据。

本教程中使用的数据是从 NLOG.nl 下载的,该网站包含北海整个荷兰区块的测井数据。这些数据可以免费下载和使用。数据许可证的全部细节可在 此处 找到,但此处提供了知识产权部分的使用摘要:

NLOG。NL 对本网站上或通过本网站提供的信息不主张任何权利(域名、商标权、专利和其他知识产权除外)。未经 NLOG 事先书面许可,用户可以复制、下载和以任何方式披露、分发或简化本网站提供的信息。NL 或有权方的合法同意。如果提供 NLOG,用户也可以复制、复制、处理或编辑信息和/或布局。引用 NL 作为来源。

nlog . nl网站,我们将使用三口井的数据:L07–01、L07–05 和 L07–04。

当加载单个文件时,我们可以很容易地将文件位置传递给lasio.read()函数。然而,由于我们正在与多个。las 文件,我们需要分别读取它们,并将它们一起添加到一个列表中。

然后使用pd.concat()将存储在该列表中的数据帧连接在一起。

下面的代码将读取所有以。阿联在一个名为Data/Notebook 36的目录内。

一旦获得了完整的文件名(第 8 行),它将与存储它的目录路径结合起来。然后 las 文件被读取(第 11 行)并被转换成一个 熊猫数据帧 (第 12 行)。

为了区分数据来自哪口井,我们可以在 dataframe 中添加一个名为WELL的新列。其值将被设置为 las 文件的井标题部分中包含的井名称(第 15–16 行)。

LASIO 加载文件并将其转换为数据帧时,数据帧的索引将被设置为深度曲线。我们可以改变这一点,使我们有一个简单的整数索引和深度作为数据帧中的实际列。这是通过使用 pandas 中的.reset_index()函数实现的(第 19 行)。

接下来,我们需要对数据帧进行排序,使其从最浅的深度测量到最深的深度测量(第 20 行)。这样做会打乱索引的顺序,所以我们需要再次重置索引,但是这一次,我们不希望索引转换成列(第 20 行),所以我们需要将drop参数设置为 True。

一旦数据帧被排序,我们就可以把它添加到我们的数据帧列表中:df_list(第 21 行)。

重复这个过程,直到所有可用。las 文件已在指定目录中读取。

最后,使用pd.concat()(第 24 行)将存储在列表中的数据帧连接在一起。

当我们调用well_df dataframe 时,我们得到了前 5 行和后 5 行的视图。

加载多个后的数据帧内容。las 文件,并将它们连接成一个数据帧。图片由作者提供。

从 CSV 加载地层顶部数据

地层顶部数据通常以表格形式存储,最常见的是。csv 文件。这些文件将包含地质构造的名称,以及相关的顶部和底部深度。

csv 文件中存储的地层数据示例。图片由作者提供。

本例的 csv 文件已经有一个名为 Well 的列,其中包含了井的名称。在将它们加载到 Python 之前做这些是有帮助的,但不是必需的。如果不这样做,您可能需要从文件名中提取井名,这可能会更加耗时。

在下面的代码示例中,我们再次创建一个空白列表(第 2 行)来存储数据帧。

接下来,我们遍历所有以。csv 文件并使用pd.read_csv()读取它们,然后将它们添加到名为df_formation_list的列表中

一旦所有文件都被读取,我们就可以调用pd.concat()将数据帧连接在一起。

当我们调用formations_df数据帧时,我们得到以下视图:

存储在多口井的 pandas 数据框架中的地层数据。图片由作者提供。

创建地层数据字典

既然我们在一个简单的 pandas 数据帧中有了这些信息,我们现在需要将这个数据帧转换成一个嵌套的字典。

这使得合并两个数据集的过程更加容易,并允许我们创建一个连续的列,其中包含每个深度级别的地层名称。

我们可以通过使用字典理解来做到这一点。

一旦我们运行了上面的代码,我们就可以调用formations_dict,并返回下面的结果。

地层名称和深度的嵌套词典。图片由作者提供。

从中我们可以看到,主键是井名,在每个井内我们都有一个子字典,深度作为键,地层名作为值。

你可能想知道为什么我们使用深度作为关键字,而不是地层名称。这样做将允许我们检查我们当前所处的深度(在我们将在下一节讨论的循环中)是否在两个键之间。如果是,那么我们可以简单地得到编队名称。

如果我们想查看特定井的顶部,我们可以在字典调用中调用特定的井,如下所示:

formations_dict['L07-01']

这将返回该特定井的地层数据:

特定井的地层深度和名称。图片由作者提供。

将地层数据与测井数据合并

现在,处理和设置已经完成,我们可以继续将地层顶部字典与测井数据框架集成。

为此,我们将使用以下函数。

该函数首先获取传入的井(well_name)地层的深度(键来自formations_dict)。

然后,我们需要捕捉一些边缘情况。

首先,我们需要看看我们是否处于队形字典中的最后一个队形。如果是,那么我们将设置一个标志at_last_formation为真,否则,我们将创建一个名为below的新变量,它将是当前深度(depth)以下最近的地层深度。

接下来,我们需要看看我们是否在字典中的第一个结构(第 12-17 行)。在这种情况下,我们正在检查在列出的第一个地层之上是否有任何深度值。如果我们只有特定深度的地层而不是表面的地层,这种情况就会发生。如果当前深度高于(浅于)第一个地层深度,那么我们将把地层名称设置为一个空字符串(第 19–20 行)。否则,我们将从当前深度以上获得地层的深度值。

最后,我们需要检查当前深度值在地层深度中的位置。如果我们不这样做,那么就不会设置正确的编队名称。如果当前深度等于地层字典中包含的深度,那么我们将把它设置为所列深度的地层名称。

一旦编写了函数,我们可以在 pandas 中使用apply方法调用它。这允许我们迭代数据帧中的每一行。

当我们调用well_df时,我们得到数据帧的如下视图:

包含测井数据和地层数据的组合数据框架结果。图片由作者提供。

检查最终结果

当做这样的事情时,近距离检查结果是很重要的。例如,我们可以检查其中一口井的结果,在我们预期地层变化的深度之间。

我们可以这样做。

pandas 数据帧中的地层数据和 csv 文件中的原始数据特写。图片由作者提供。

在上面的原始地层 tops csv 文件中,我们可以看到布鲁塞尔泥灰岩段和 Ieper 段之间的过渡发生在 930 英尺处。这发生在组合数据帧内的同一点。

这有助于我们获得信心,相信这个过程是有效的。

可以肯定的是,以这种方式检查多口井和井段,或者通过生成测井曲线,总是明智的。

摘要

在 Python 中,整合多口井的测井数据和地层数据是一项挑战。在这篇短文中,我们看到了如何加载多个 las 文件和地层数据文件,并将它们组合成一个数据帧。

这将允许我们将地层数据和测井数据集成到机器学习模型或测井显示中。

如果您想处理单井,请务必查看以下文章:

** **

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

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

创建和部署您的第一个 Go 包

原文:https://towardsdatascience.com/creating-and-deploying-your-first-go-package-eae220905745

创建 TigerGraph 包装器 Go 库的分步指南

图片来自 Pixabay

概观

介绍

最近,我决定学习一门新的语言:Go,也叫 Golang,是 Google 创造的。这种语言因其高级和易于理解的语法而广受欢迎。为了开始学习这门语言,我决定为 TigerGraph 创建一个 Go 库。在这篇博客中,我将一步一步地向您介绍如何创建和部署一个 Go 库来连接 GSQL。

目标

读完这篇博客,你会学到:

  • 如何创建 Go 库
  • 如何在 Go 中与 TigerGraph 交互

工具

制作 Go 库所需的工具:

  • Go (可以通过自制安装在 Linux/Mac 上)
  • GitHub :你将需要一个地方来托管你的项目,本教程将使用 GitHub。

该示例项目所需的工具(TigerGraph Go 库):

概述

  • 第一部分:初始化 Go 项目
  • 第二部分:创建包—从 Go 查询 TigerGraph
  • 第三部分:部署包
  • 第四部分:在项目中使用包
  • 第五部分:结论、资源和后续步骤

第一部分:初始化 Go 项目

步骤 1:创建 GitHub 存储库

当你初始化 Go 项目时,它会提示你把它放在某个地方。在 GitHub 上,创建一个新的空存储库。

创建新的存储库

按照您喜欢的方式配置设置。(请注意,如果项目是私有的,请确保终端可以通过特殊的身份验证或 SSH 密钥来访问代码。)在创建 Go 项目时,您需要从存储库中获得 URL(开头没有 https://)。URL 的格式如下所示:

github.com/USERNAME/REPO_NAME

例如,我的示例项目 URL 是:

github.com/GenericP3rson/GoSamplePackage

一旦完成,您就可以在您的机器上打开终端来创建您的项目。

第二步:创建并初始化项目

首先,您需要用 Go 代码创建一个目录。

mkdir GoSamplePackage && cd GoSamplePackage

接下来,使用第一步中的 GitHub URL 初始化 Go 项目。

注意:如果你没有安装 Go,你可以用brew install go来安装,或者用brew upgrade go来更新。

go mod init github.com/USERNAME/REPO_NAME

同样,对于我的示例,这将是:

go mod init github.com/GenericP3rson/GoSamplePackage

这将创建一个go.mod文件,其中将包含您刚才写的 URL 和您的 Go 版本。

最后,创建一个用于编写 Go 包的文件。

touch GoSamplePackage.go

在新 Go 文件的顶部,指出你的库的包名。

package GoSamplePackage

这样,您就可以编辑 Go 文件来创建您的包。

第二部分:创建包—从 Go 查询 TigerGraph

第一步:建立解决方案

首先,在 TG Cloud 上创建一个 TigerGraph 解决方案。在那里,导航到“我的解决方案”选项卡,然后按蓝色的“创建解决方案”按钮。

选择“我的解决方案”,然后按“创建解决方案”

在第一页上,选择任何初学者工具包。我决定从新冠肺炎解决方案开始。

选择任何初学者工具包

第二页保持原样;这将建立一个免费的 TigerGraph 实例。在第三页上,适当地配置解决方案。

适当配置设置

注意:请记住您的子域和初始密码!

最后,在最后一页,验证所有信息都是正确的,然后按“提交”!您需要给解决方案几分钟时间来启动。

步骤二:在 Go 中创建一个 GET 请求函数

使用解决方案设置,我创建了一个到/echo 端点的查询。TigerGraph 内置端点的完整文档可以在这里找到。

https://docs.tigergraph.com/tigergraph-server/current/api/built-in-endpoints

我创建的 Go 函数接受两个参数,子域(主机)和令牌。

注意:所有函数名都必须以大写字母开头,否则将不起作用。

func Echo(Host string, Token string) string {}

接下来,我创建了对端点的 GET 请求。

req, err := http.NewRequest("GET", "https://"+Host+".i.tgcloud.io:9000/echo", nil)if err != nil { return err.Error() } req.Header.Set("Authorization", "Bearer "+Token)response, err := client.Do(req)if err != nil { return err.Error() }

最后,我读取正文并解析结果,返回“message”字段。

body, err := ioutil.ReadAll(response.Body)if err != nil { return err.Error() } sb := string(body)response.Body.Close() map[string]interface{}
json.Unmarshal([]byte(sb), &jsonMap) mess := jsonMap["message"] return fmt.Sprintf("%v", mess) 

要导入所有必需的模块,请运行:

go mod tidy

在这里找到完整的代码。

https://github.com/GenericP3rson/GoSamplePackage/blob/master/GoSamplePackage.go

这是您可以创建的函数的一个例子,但是天空是创建包的极限。一旦您满意了,您就可以继续部署代码了。

第三部分:部署包

第一步:连接 GitHub 并提交代码

最后,你已经准备好向全世界展示你的努力成果了!首先,添加并提交刚刚创建的代码。

git add .git commit -m"First commit of my new Go package"

接下来,连接到 GitHub 存储库。

git remote add origin https://github.com/USERNAME/REPO_NAME.git

在上面的示例中,这将是:

git remote add origin https://github.com/GenericP3rson/GoSamplePackage.git

最后,将变更推送到您的存储库中。

git push origin master

第二步:创建新版本

接下来,使用 git 标记版本,以便用户使用特定的版本。

git tag v0.0.1

关于如何最好地决定版本,请查看语义版本指南。

分配一个版本后,将其推送到存储库。

git push origin v0.0.1

完美!您的包现在可以供全世界使用了!

第四部分:在项目中使用包

第一步:创建另一个 Go 项目

现在让我们看看如何将这个包集成到一个 go 项目中。首先,创建一个新的 Go 项目,类似于从头开始的说明。

mkdir GoTest && cd GoTest

这一次,在初始化代码时,您可以将宿主位置设置为几乎任何位置,因为您可能不会立即宿主项目。

go mod init test/GoTest

再次为主要的 Go 代码创建一个文件。

touch index.go

第二步:导入库并运行函数

在“Go”页面中,创建基本 Go 程序的大纲。

package main

import "fmt"

func main() {
    fmt.Println("Testing my awesome library!")
}

更改导入以包含您的库的 URL,即您在开始时创建的 URL。

import ( "fmt" "github.com/USERNAME/REPO_NAME")

在本例中是:

import ( "fmt" "github.com/GenericP3rson/GoSamplePackage")

接下来,在 main 函数中,调用包中的任何一个函数。

func main() {
    fmt.Println(PACKAGE_NAME.FUNCTION_NAME(PARAMS))
}

在我的情况下,我会从 GoSamplePackage 调用 Echo。此外,我将根据图形传递两个参数。

func main() {
    fmt.Println(GoSamplePackage.Echo("golang", "TOKEN"))
}

第三步:导入包并运行程序

最后,为了导入模块,再次运行:

go mod tidy

这将下载软件包的最新版本,并将其添加到go.mod。最后,使用以下命令运行程序:

go run .

一旦执行,结果应该是Hello GSQL。太棒了。包装器开始工作了!

第五部分:结论、资源和后续步骤

厉害!读完这篇文章后,你已经正式创建了你的第一个 Go 包!您可以继续扩展,添加其他功能和端点,并发布新版本。

Go 和 DigitalOcean 为围棋初学者提供了几个方便的资源,对学习这门语言非常有帮助。

https://go.dev/learn/ https://go.dev/doc/tutorial/getting-started https://www.digitalocean.com/community/tutorials/how-to-write-packages-in-go

最后,如果您对该图感兴趣,请随时加入 TigerGraph 社区,参与令人惊叹的项目并与其他开发人员聊天。

https://discord.gg/DMHabbX3BA

如果你想了解这个项目的完整代码,请点击这里查看。

https://github.com/GenericP3rson/GoSamplePackage

注:除特别注明外,所有图片均由作者创作。

创建自动完成搜索表单

原文:https://towardsdatascience.com/creating-autocomplete-search-forms-e8bc3f2c1669

Tkinter vs Excel VBA

自动完成功能让搜索变得更容易

在今天的数据洪流中,用户友好的搜索掩码几乎是不可或缺的。在这篇文章中,我们将看到如何使用 Tkinter 开发一个用于翻译目的的响应搜索,并与 VBA 进行比较。

响应搜索表单(图片由作者提供)

背景:

我们的表由两列组成。第一列包含英语术语,第二列包含相应的德语翻译。

德-英金融术语摘录(图片由作者提供)

我们希望为用户提供一个用户友好的输入掩码来搜索翻译。两种语言都要搜索。为了好玩,我们将在 Excel VBA 中创建一次,然后将其与 Python 的 Tkinter 匹配。这样我们将再次认识到为什么 Python 是如此伟大的编程语言。玩笑归玩笑,这主要是针对那些来自 VBA,现在想学习 Python 的人。希望这能帮助你更容易理解基本概念。

解决方案:

搜索到的单词会立即与相应的翻译一起列在命中列表中。搜索栏中每输入一个字母,命中列表的范围就会相应缩小。

(图片由作者提供)

要在 Excel VBA 中实现这一点,需要一系列不同的步骤。我只说明最重要的几个方面,这些方面我们也和 Tkinter 比较。但是如果您想详细研究代码,可以在我的 Github 资源库中找到完整的文件和代码。

Excel 表格被分成称为“SearchText”的用户输入:

(图片由作者提供)

结果显示在“ListBoxTerms”中时:

(图片由作者提供)

如果搜索成功,您可以双击每个匹配项,将翻译转移到搜索选择选项卡:

Private Sub ListBoxTerms_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
If ListBoxTerms.ListIndex = -1 Then
    MsgBox "No dataset selected"
    Exit Sub
End If
Dim wks As Worksheet
Set wks = Worksheets("SearchSelect")
With Me.ListBoxTerms
wks.Range("F11:F12").ClearContents
wks.Range("F12").Value = .List(.ListIndex, 0)
wks.Range("F11").Value = .List(.ListIndex, 1)
End With
Unload Me
End Sub

如果没有输入搜索,默认情况下,列表中会填充范围 A1 至 B1000(数据来自“标准”选项卡):

Sub ListFill()
Dim SD As Worksheet
Set SD = Sheets("Standards")
Dim LastRow%
LastRow = Sheets("Standards").Range("A1000").End(xlUp).Row + 1
ListBoxTerms.RowSource = "Standards!A1:B" & LastRow
End Sub

根据最近的字符串搜索显示的过滤列表可能值得更仔细地观察一下。我们使用大写字母(UCase)来确保搜索不区分大小写。然后,我们比较 Standards 选项卡中第一列(包含英文单词)中的每一行,以查看它是否包含在搜索文本(SearchText)的字符串(InStr)中。如果大于 0,则“标准”选项卡中的单词将显示在 ListBoxTerms(显示与用户当前搜索相对应的匹配项的列表框)中。这个英语单词使用 VBA 的 AddItem 显示在 ListBoxTerms 中。“标准”选项卡的第二列也是如此,其中包含德语翻译(SD。细胞(I,2))。借助 VBA 的 ListCount 函数,这个德语单词被插入到 ListBoxTerms 中。

Sub ListFillFilter()
If SearchText = "" Then
    Call ListFill
    Exit Sub
End If
Dim SD As Worksheet
Set SD = Sheets("Standards")
Dim LastRow%
Dim i%
LastRow = Sheets("Standards").Range("A1000").End(xlUp).Row + 1
ListBoxTerms.RowSource = ""
i = 1
Do
    If InStr(UCase(SD.Cells(i, 1)), UCase(SearchText)) > 0 Or InStr(UCase(SD.Cells(i, 2)), UCase(SearchText)) > 0 Then
        ListBoxTerms.AddItem SD.Cells(i, 1)
        ListBoxTerms.List(ListBoxTerms.ListCount - 1, 1) = SD.Cells(i, 2)
    End If
i = i + 1
Loop Until i > LastRow
End Sub

使用 Excel VBA 的初始化功能,每当打开文件时都会填充列表表单:

Private Sub SearchText_Change()
Call ListFillFilter
End Sub

其次,使用 VBA 的 change 函数,我们在每次向搜索表单中输入内容时调用 ListFillFilter:

Private Sub UserForm_Initialize()
Call ListFill
End Sub

这是在 Excel 中设置搜索表单的方法。
现在让我们看看在 Python 中使用 Tkinter 解决这个问题的完整方案。

用 Tkinter 解决 Python 中的响应搜索(图片由作者提供)

本文的目的不是争论哪个解决方案是更好的选择。但是对您来说,比较 VBA 和 Python 可能会很有趣,特别是对于本例中三个最重要的函数。Python 代码对你来说看起来也更苗条了吗(或者只是我不同的截图技术扭曲了这种印象)?

ListFill,VBA 左对 Python 右(图片由作者提供)

至少从上面的 ListFillFilter 我们可以清楚的看到,Python 让对比变得更加方便。Python 中的循环不太费力,因为我们不必一直数到翻译字典的末尾。

ListFillFilter,VBA 左 vs Python 右(图片由作者提供)

ListBoxTermsDoubleClick,VBA 左 vs Python 右(图片由作者提供)

概要:

恭喜您,您已经编写了一个动态搜索程序,使用户更容易输入数据。在这个例子中,我们只搜索了字符串的匹配,这是一个非常简单的搜索功能。但是对于我们的翻译需求,这已经足够了。在的另一篇文章中,我们将把这种“字符串匹配方法”与高度可扩展的认知搜索进行比较(这里有一个小玩笑:Haystack 和 Elasticsearch 是神奇的词)。

在此之前,非常感谢您的阅读!希望这篇文章对你有帮助。请随时在 LinkedInTwitter工作室与我联系。

https://jesko-rehberg.medium.com/membership

您可以在我的 Github 资源库中找到完整的代码和文件。请随时在 LinkedInTwitter工作室与我联系。

用 Python 创建漂亮的森林地图

原文:https://towardsdatascience.com/creating-beautiful-forest-maps-with-python-59a60fba3e27

通过 Matplotlib 使用光栅文件生成引人注目的可视化效果

世界森林。作者图片

介绍

森林和植被一直是人类在地球上生存的核心。它们提供我们呼吸的空气,我们吃的食物和建造我们家园的材料。多亏了大量有趣的研究项目,现在有几个公开来源的数据集来展示地球上森林和植被的分布。借助 Python 的魔力,我们可以获取这些数据集,分析它们,操纵它们,并使用它们来生成如上所示的有见地且引人注目的可视化效果。

虽然可以说数据可视化是游戏的目标,但这是一个以数据操作和重新投影为核心的练习。我们将使用rasteriorioxarray生成上面的可视化以及一些其他有趣的情节。

数据探索

有许多数据集,在本教程中,我们将使用来自日本地理空间信息局、千叶大学和合作组织的百分比树覆盖率(PTC)数据集(链接到数据集和许可信息)。

数据很旧,显示的是 2003 年的森林,这显然是很久以前的事了,但是这里使用的方法将适用于其他森林数据集和/或存储在该存储库中的一些更新的(但更大的)数据。事实上,该存储库中存在更高分辨率的数据集,但是数据被分解为 12 个独立的 tif 文件,代表世界上 90x60 度的大块。组合不同的 tif 文件是可能的,但是超出了本教程的范围,因此将在以后的文章中讨论。

数据存储在 tif 文件中,分辨率为 30 弧秒(约 1 平方公里)。tif 文件的每个网格点都有一个介于 0-100 之间的值,对应于 1 平方公里区域内的树木覆盖率。水体给数值 254,无数据给 255。

下面,使用rasterio打开并读取数据。请注意,文件的加载和打开分两个不同的步骤。使用numpy打印最小值和最大值,显示数值范围从 0-254,意味着没有丢失数据。

对于任何数据可视化问题,要做的第一件事是绘制数据,以了解我们正在处理的问题。下面是使用Greens颜色图的标准imshow数据图(因为你为什么要用其他东西来描绘森林?).

显示世界森林的原始数据。作者图片

显而易见的问题是,海洋被染成了绿色,那里显然没有森林。对于树木覆盖率百分比,数据在 0-100 之间缩放,但对于水体还有一个额外的数据点-254。绘制数据时,matplotlib创建一个颜色图,颜色在数据集中的最小值(0)和最大值(254)之间线性缩放。我们真正关心的数据被映射到前 101 种颜色(约占颜色图的 40%),有 153 种颜色完全没有使用(101–253),一种颜色用于海洋(254)。所以色彩图中大约 60%的颜色没有被使用。下面直观地说明了这一点。

图解说明颜色映射问题的示意图。作者图片

出于本练习的目的,我们将假设海洋的树木覆盖率为 0%,这可能是一个非常安全的假设,但至少值得一提,因此,在下面的代码中,所有大于 100 的值都被更改为 0。因此,在随后的图中,将有 101 个值(0–100 ),颜色图将完美地映射到最低值(0 森林覆盖和水体的区域)到最高值(100%森林覆盖)。

我想为森林覆盖率为 0 的 gridpoints 设置一个自定义背景色,所以我们使用下面的代码生成一个有 101 种颜色的 colourmap,用我们的自定义值替换第一种颜色,然后从颜色列表中创建一个新的 colourmap。

为了从一个颜色列表中创建一个颜色图,我们可以使用ListedColormap方法,该方法获取一个颜色列表并从这些颜色中生成一个颜色图。由于数据是值在 0-100 之间的线性范围,我们不需要担心边界,我们可以使用颜色图而不归一化颜色图(关于颜色图和归一化颜色图的更多信息,我已经写了大量关于它们的内容这里)。

然后,可以将颜色图应用于之前生成的绘图代码。虽然这里有一些补充,但是轴已经被关闭以使情节看起来更艺术,并且情节的边缘已经被修剪以减少空白空间的数量。这些都是风格上的变化,任何试图学习本教程的人都可以并且确实被鼓励忽略。数据可视化应该更多地是关于表达而不是科学,因此每个人都应该发展自己的风格。

世界森林。作者图片

数据看起来像预期的那样,重要的是看着一个可视化,问问你自己它是否有意义。显而易见,颜色图处理水体的方式存在问题,这将在下面讨论,但总体来说,事情都在意料之中。有与亚马逊、刚果和东南亚雨林相对应的深绿色区域,北半球显示出与北美和欧亚大陆的落叶林相对应的连续的浅绿色区域,甚至在伊朗北部海岸线上有一片细长的绿色雨林区域。

我们可以就此结束,但是我更喜欢罗宾逊投影的地图,所以需要一个额外的步骤来重新投影数据。罗宾逊投影是绘制全球数据集的一种更现实的方法,因为地球不是平的。

重新投影地理空间形状(如点和面)很简单,但不幸的是,对栅格数据进行同样的操作更具挑战性。为了做到这一点,我们可以。rioxarray使用rio访问器扩展了xarray,并允许裁剪、合并和重新投影 tiff 和 geotiff 文件中的栅格。下面,用rioxarray打开原始的 tif 文件(它利用了一些底层的rasterio功能,因此有了open_rasterio方法),Robinson 投影被定义为一个rasterio.CRS对象,然后光栅被重新投影到这个CRS上。需要注意的一点是,rasterio.CRS对象是从一个pyproj字符串中创建的。pyproj是一个处理地图投影和坐标转换的极好的库,我将在以后的文章中介绍它。

然后,可以使用与之前相同的彩色地图绘制数据,从而在罗宾逊投影中给出我们的森林地图。

使用罗宾逊投影的世界森林。作者图片

另一方面,当使用线性单色颜色图时,通常可能会丢失颜色图较亮一端的数据,所以为了好玩,你可以使用一个奇怪而奇妙的颜色图,这样每个数据点都清晰地显示出来,0-100。下图和之前一样,但是这次使用了gnuplot颜色图。

一些东西现在会跳出来,美国的西部荒野,南美洲的大部分地区,撒哈拉沙漠的部分地区,中亚,澳大利亚和南非,这些地方的森林覆盖率很低。尼罗河和三角洲也出现了。本文中使用的绿色色图完全适合手头的任务,但是探索其他色图仍然是有用的,只是为了充分理解数据以及创建有趣的图。

使用罗宾逊投影和 gnuplot 彩色地图的世界森林。作者图片

结论

我们有它,一个美丽的地图显示如何产生引人注目的数据可视化显示世界的森林。这是计划展示如何使用 Python 使地理空间数据看起来令人惊叹的许多文章中的第一篇,请订阅以便您不会错过它们。我也喜欢反馈,所以请让我知道你会如何做不同或建议改变,使它看起来更棒。我每周都会在我的推特账户上发布数据可视化,看看地理空间数据可视化是不是你的菜https://twitter.com/PythonMaps

medium 提供的额外收入确实有助于这个项目继续进行,所以如果你是一个粉丝,想取消你的帐户,然后用我的推荐重新注册,那就太好了。https://pythonmaps.medium.com/membership

参考

本教程所需的数据源和库的链接。

数据—【https://globalmaps.github.io/ptc.html

matplotlib—https://matplotlib.org/

numpy—https://numpy.org/

拉斯特里奥—https://rasterio.readthedocs.io/en/latest/

里约—https://corteva.github.io/rioxarray/stable/

pyproj—https://pyproj4.github.io/pyproj/stable/

使用 Python 创建漂亮的人口密度地图

原文:https://towardsdatascience.com/creating-beautiful-population-density-maps-with-python-fcdd84035e06

使用 Python 绘制全球 78 亿人口的居住地地图

作者图片

这篇文章很大程度上是受 subreddit r/people live incities的启发。人类已经在地球上广泛传播,触及了每一个大陆,并在世界的广大地区产生了影响。然而,当你真正观察人口密度图时,你会发现大多数人都聚集在一起,尽管世界上有广阔而空旷的空间。如果世界人口平均分布,那么每平方公里大约有 50 人。事实上,有些地区完全无人居住,有些国家的人口密度为每平方公里数千人。这是一个真正令人着迷的概念,有了正确的人口密度地图,你就可以分辨出单个的城市、城镇甚至村庄,还可以识别出世界上真正偏远的地区。这就是你在封面图像中看到的,黑暗的热点表示人口密集的城市和城镇,混杂在人口稀少的明亮区域中。因此,在本文中,我将概述如何使用开源人口密度数据,通过 Python 来构建自己的人口密度地图。

数据准备

有许多人口密度数据集,但是出于本练习的目的,我们将使用 GHSL —全球人类居住层。数据免费提供,服务条款可在此处找到。

首先要做的是将数据下载到你想进行数据可视化的任何地方。数据在此处可用,所需的具体文件是 GHS 人口,2018 年的 30 弧秒数据集。需要注意的是,本教程需要 WGS84 投影。数据以 tif 文件的形式出现,可以用rasterio.读取

这个数据集很大,在标准笔记本电脑上工作需要很长的执行时间,因此做一些基本的探索来了解您正在处理的东西是很重要的。

数据的单位是 30 弧秒,每个区块包含该区块记录的人数。所以单位是每平方公里的人数(30 弧秒= 1 公里)。

最大值为 459434.69 人/平方公里。值得注意的是,虽然该值大于谷歌搜索人口密度最大的城市或国家后引用的值,但这对应于单公里,城市和国家的值为平均值。例如,纽约中央公园的人口密度与一些住宅区的人口密度非常不同。

最小值是-200,对应于空值。在此地图中,没有数据和人会被视为同一事物,因此任何低于 0 的值都将被设置为零。我想我会尝试的第一件事是一个对数图。如果可能的话,我尽量避免使用对数图,因为由此产生的图可能很难解释,而且数据通常没有意义。但是,当有一小部分数值数量级大于大多数(城市)时,以及在试图显示乘法因子的图中(喜马拉雅山脉与上海),对数图是很好的。我们的数据符合这两个类别,因此在这种情况下是合适的。

测绘

我们将使用一张hot彩色地图。我希望色彩映射表的底部是一个中性的背景色,所以下面我将热色彩映射表对象作为一个列表,从中提取 460 种颜色,添加一个背景色到列表的开始,然后从列表中构建一个新的色彩映射表。因为背景色已被添加为色彩映射表中的第一种颜色,所以最小值(本例中为 0)将被映射到该颜色。

然后可以使用对数标度和刚刚创建的颜色图将数据绘制在下面。这些数据看起来很像预期的那样,但是非常混乱,也不是特别吸引人。这是因为人类倾向于生活在一起,所以数据中有大量高密度值的人口,也有大量低密度值的人口。因此,高密度值将被映射到颜色图的一端,而低密度值被映射到另一端。结果是,我们可能期望显示为“已填充”的所有区域都具有相似的颜色。

使用对数标度绘制的每平方公里的人口密度。作者图片

人口密度地图倾向于使用自定义比例。例如,google images 的第一批结果之一使用了 0、1、5、10、20、50、100、200、1000 人/平方公里的比例,因此在这种情况下,他们只是将所有人口众多的地区一视同仁。这些地图是定性的,而不是定量的,因为在一天结束时,我们希望产生一个发人深省和引人注目的图像。这不是一张用来制定疫情疏散计划的照片。所以经过一点试验,我决定用一组稍微不同的值来映射我们的人口值。默认情况下,在matplotlib中使用色图的对象将色图中的颜色从数据集中的最小值线性映射到数据集中的最大值,间隔由色图中的颜色数量决定。BoundaryNorm类允许你将颜色图中的颜色映射到一组自定义值。在下面的代码中,我们创建了一个有 10 种颜色的颜色图和一个BoundaryNorm对象,它将根据预定义的级别把数据中的值映射到颜色图。

使用自定义比例绘制的每平方公里人口密度。作者图片

这张图片看起来越来越像了,放大后显示的正是我认为我们期望找到的东西。然而,这很难通过这种特殊的通信媒介来实现,您可能决定要生成特定国家的人口地图。所以下面我们将制作世界特定地区的人口地图。要做到这一点,我们需要分离出特定于一个国家的部分人口数据。幸运的是,rasterio 提供了一种基于地理配准形状(如多边形)裁剪栅格的有用方法。在这种情况下,我使用了属于 NaturalEarth 数据集的多边形,通过rasterio.mask.mask 函数来裁剪栅格。

我想从西欧开始,所以我需要建立一个与这些国家相对应的GeoDataFrame多边形。我加载了 NaturalEarth shapefile,并使用geopandas提取了英国、法国、奥地利、德国、捷克、意大利、丹麦、卢森堡、比利时、瑞士、西班牙、葡萄牙、荷兰和爱尔兰的多边形。不幸的是,法国、西班牙、葡萄牙和荷兰都有海外领地,这是它们自然地球几何的一部分。因此,下面的代码提取了这些几何图形的欧洲大陆部分,并丢弃了其余部分。

然后可以使用mask函数将栅格数据匹配到西欧多边形。它获取开始时创建的tif_file对象和一个映射的几何图形列表,并生成一个新的栅格,排除几何图形之外的值。

西欧每平方公里的人口密度。作者图片

为了好玩,下面是为美国、中国、印度和埃及制作的一些地图。人口密度相当有趣且独特的国家。

美国每平方公里的人口密度。作者图片

中国每平方公里的人口密度。作者图片

印度每平方公里的人口密度。作者图片

埃及每平方公里的人口密度。作者图片

结论

我们有了它,一个美丽的地图显示了如何生成引人注目的数据可视化,显示了地球上人们生活的地方。这是计划展示如何使用 Python 使地理空间数据看起来令人惊叹的许多文章中的第一篇,请订阅以便您不会错过它们。我也喜欢反馈,所以请让我知道你会如何做不同或建议改变,使它看起来更棒。我每周都会在我的推特账户上发布数据可视化,看看地理空间数据可视化是不是你的菜【https://twitter.com/PythonMaps

medium 提供的额外收入确实有助于这个项目继续进行,所以如果你是一个粉丝,想取消你的帐户,然后用我的推荐重新注册,那就太好了。【https://pythonmaps.medium.com/membership

参考

全球统一制度数据集——马尔切洛,斯齐亚维纳;塞尔吉奥·弗莱雷;麦克马努斯、凯特(2019 年):全球统一制度人口网格多时段(1975-1990-2000-2015 年),R2019A。欧洲委员会,联合研究中心

概念与方法:—弗雷尔、塞尔吉奥;麦克马努斯,基特;马蒂诺·佩萨雷西;埃林·多克西-怀特菲尔德;Mills,Jane (2016 年):开发新的 250 m 分辨率的开放和免费的多时相全球人口网格。变化中的地理空间数据;欧洲地理信息实验室协会。敏捷 2016

使用 Python 创建漂亮的独立交互式 D3 图表

原文:https://towardsdatascience.com/creating-beautiful-stand-alone-interactive-d3-charts-with-python-804117cb95a7

应用于 D3 力有向网络图

截图来自:【https://d3js.org/

可视化数据可能是项目成功的关键,因为它可以揭示数据中隐藏的见解,并增进理解。说服人们的最好方法是让他们看到自己的数据并与之互动。尽管 Python 中提供了许多可视化包,但制作漂亮的独立交互式图表并不总是那么简单,这些图表也可以在您自己的机器之外工作。D3 的主要优势在于它符合 web 标准,因此除了浏览器之外,你不需要任何其他技术来使用 D3。重要的是,互动图表不仅有助于告诉读者一些东西,还能让读者看到、参与和提问。在这篇博客中,我将概述如何使用 Python 构建你自己的独立的、交互式的力导向 D3 网络。 注意步骤与任何其他 D3 图表相似。如果你需要工作版本, d3graph就是给你的!

如果你觉得这篇文章很有帮助,可以使用我的 推荐链接 继续无限制学习,并注册成为中级会员。另外, 关注我 关注我的最新内容!

使用 D3 的动机

D3 是数据驱动文档的缩写,是一个 JavaScript 库,用于在 web 浏览器中生成动态、交互式数据可视化。它利用了可缩放矢量图形(SVG)、HTML5 和级联样式表(CSS)标准。 D3 又称 D3.jsd3js。 我会互换使用这些名字。在交互式 Python 解决方案上面使用 D3 的优势有很多,我来列举几个[1];

  • D3 是一个 Javascript 库。因此,它可以用于您选择的任何 JS 框架,如 Angular.js、React.js 或 Ember.js。
  • D3 专注于数据,所以如果你在数据科学领域,数万个数据点保持交互而不需要付出任何额外的努力,这是一种解脱。
  • D3 是轻量级的,直接与 web 标准一起工作,因此速度非常快,并且可以很好地处理大型数据集。
  • D3 是开源的。因此您可以使用源代码并添加自己的特性。
  • D3 支持网络标准,所以除了浏览器,你不需要任何其他技术或插件来使用 D3。
  • D3 支持 HTML、CSS 和 SVG 等 web 标准,在 D3 上工作不需要新的学习或调试工具。
  • D3 没有提供任何特定的特性,所以它可以让你完全控制你的可视化,以你想要的方式定制它。

D3 图表。

D3 是被设计成一起工作的模块的集合;您可以单独使用这些模块,也可以将它们作为默认构建的一部分一起使用。D3 网站提供了 168 个工作图表 ,允许交互过程中的性能增量更新,支持拖动缩放等流行交互。图表可用于多种目的,例如定量分析、可视化层次结构、创建网络图、以及条形图、线图、散点图、辐射图、地理投影、和各种其他交互式可视化,用于探索性解释。一些精选的图表如图 1 所示。这些 d3js 图表的各种各样在 D3Blocks 库 中很容易被 python 化。阅读 D3Blocks 中型文章了解更多详情。

图 1:D3 图表的各种例子。截图来自https://d3js.org/

入门!

让我们开始我们的 D3-Python 项目吧!在我们迈向 python 化 D3 图表之前,我们需要理解 D3 是如何工作的。我将通过在 D3 中创建一个非常小的力导向网络图(没有 Python)来演示它,之后,我将演示 Python 中的集成。解释 D3 图表发展的最好方法是将发展分成四个不同的部分;

  1. 层叠样式表(CSS)。
  2. D3 模块包含所有需要的库。
  3. Javascript 构建图表。
  4. 数据为 JSON 文件。

图表中的每个部分都有自己的角色,为了得到一个工作版本,我们需要连接所有部分,这可以在一个 HTML 文件中完成,如图 2 所示。在接下来的部分中,我将描述每个部分的作用和实现。

图 2:在一个最终的 HTML 文件中,四个部分被连接起来以构建 D3 图。图片来自作者。

1.级联样式表(CSS)

CSS 文件是一种简单的机制,用于以结构化的方式向 HTML 文件添加样式。例如,CSS 文件可以定义 HTML 元素的大小、颜色、字体、行距、缩进、边框和位置。我们将创建强制定向网络图,为此我们将定义整体字体大小、字体系列、样式、颜色以及特定于节点和边的属性。您可以相应地更改 CSS,并设置您认为最好的属性。我创建的文件可以在这里下载,看起来如下:

级联样式表(CSS)

2.D3 模块

D3 模块是最简单的部分,因为您只需要导入它或将整个内容嵌入到最终的 HTML 文件中。D3 模块包含了创建任何图表的所有函数。不需要对该文件进行编辑。最新版本是 v7 ,可以从本地资源或网站导入。

<script src="https://d3js.org/d3.v7.min.js"></script>

我将使用稍微老一点的版本( v3 ),因为我已经用这个版本创建了很多脚本。可以在这里下载,它包含在我下面几个步骤的示例代码中。如果您构建自己的图形,我强烈建议使用最新版本。

3.构建图表的 Javascript。

构建图表不需要您从头开始编码。我们可以在 D3 网站上找到感兴趣的图表,并使用开源代码。让我们转到强制导向图页面并阅读“ 我如何使用这段代码? 。描述如下:

图 3:截图来自https://observablehq.com/@d3/force-directed-graph

它告诉我们复制粘贴整个" ForceGraph "函数,如下图所示。我创建了另一个截图(图 4),你可以看到“函数 Forcegraph({ )的一部分,这就是 ForceGraph 函数。您需要复制整个函数并将其粘贴到一个新文件中。我将我的文件命名为D3 graph script . js,可以在这里下载。请注意,我的函数与这里显示的最新函数略有不同。该网站还告诉我们导入 D3 模块,我们在前面的部分中已经介绍过了。

图 4:部分 ForceGraph 代码。截图摘自:https://observablehq.com/@d3/force-directed-graph

4.数据

图表的燃料是我们需要得到正确形状的数据。尽管 D3 有导入本地文件的功能。使用d3.json()的 json-file,它可能无法工作,因为导入带有 D3 的本地 csvjson 文件被认为不安全。一种解决方案是将数据直接嵌入到最终的 HTML 文件中。但是,根据数据量的不同,它可能会产生大量的 HTML 文件。然而,将数据直接嵌入到 HTML 中会将所有脚本和数据集成到一个文件中,这非常实用。下面的方框显示了一个 json 数据文件的例子,其中描述了一些节点和边。我们可以在 HTML 中嵌入这样的数据块。完整的 json 数据文件可以从这里下载。

graph = {"links":[{"node_weight":5,"edge_weight":5,"edge_width":20,"source_label":"node_A","target_label":"node_F","source":0,"target":2},{"node_weight":3,"edge_weight":3,"edge_width":12,"source_label":"node_A","target_label":"node_M","source":0,"target":4}"]}

5.连接零件

我们现在有了构建图表所需的四个部分( CSS、D3、javascript 图形和数据)。我创建了一个 HTML 样板文件,其中的四个部分将被连接起来;下载了这里。这些零件包括如下:

  1. 层叠样式表Style . CSS
  2. D3 模块D3 . v3 . js
  3. javascript 构建为D3 graphscript . js
  4. 数据JSON _ Data

要创建最终的 HTML,您可以替换每个文件中包含它的行上的内容。或者换句话说,用style . CSS中的内容替换 {% include "style.css" %} 以此类推。你会得到一张看起来像这样的工作图。检查页面源代码或下面的脚本,查看四个部分的组合。就是这样!你在 D3 中创建了一个有向力网络图!因为我们创建了四个构建模块,所以在 Python 中集成这个图是一小步。

制作力定向图的最终 HTML。在这里下载,粘贴到一个纯文本文件中,去掉标签<!- TEXT - >,并重命名(如 forcedirected.html)。双击它。

把 D3 变成 Python

Pythonize 化 D3 脚本的步骤是将静态值改为变量。我们可以为最终 HTML 文件和强制定向 javascript 文件中的属性执行此操作。想法如下:

  1. 使用 Python 导入/读取最终的 HTML 和/或 javascript 文件。
  2. 使用 Python 将变量替换为任何所需的值。
  3. 将最终调整后的 HTML 文件写入磁盘。

但是首先,我们需要在最终的 HTML 中将静态值手动更改为变量名。让我们把的宽度、高度、电荷、距离、方向和碰撞转换成变量。创建唯一的变量名以避免意外的错误替换是很重要的。我的解决方案是创建变量名,如 {{ width }} ,现在可以很容易地在 Python 中找到并替换为真实值。除了最终 HTML 文件javascript 图形文件之外, json 数据文件也包含可以相应更改的变量。注意 D3 模块将保持不变。在开始实现所有这些之前,请阅读下一节!

值被转换成变量的 HTML 文件。

D3 图形库

d3graph的设计方式与以上章节所述类似。这是一个 Python 库,构建于 D3 之上,创建了一个独立的、交互式的力导向网络图。输入数据是邻接矩阵,其中列和索引是节点,值为 1 或更大的元素被视为边。输出是一个包含交互式力定向图的 HTML 文件。这里的是泰坦尼克号使用案例中的网络演示。我的 D3 版本有一些扩展,其中一个滑块可以根据边值断开网络的边,双击一个节点会高亮显示该节点及其连接的边。要制作自己的网络图, pip 安装 d3graph 包,其中包含:

pip install d3graph

现在,您可以使用各种参数创建交互式网络:

图 5:使用 d3graph 创建的各种图表。图片由作者提供。

最后的话

恭喜你!你刚刚学习了如何创建漂亮的 D3 图表,特别是使用 D3 的力定向网络,以及如何将它集成到 Python 中。我希望这个博客能给你创建任何你想要的 D3 图表所需要的知识,不管有没有 Python。D3 图形库 将帮助你使用 Python 创建自己的 D3 力定向图。输出是一个单独的 HTML 文件,可以独立工作,可以共享或发布在网站上,除了浏览器,你不需要任何其他技术。您还可以将 D3 网络图作为可视化集成到您现有的库中。阅读这篇关于我如何为 HNet 库 学习联想的博客。如果您想创建其他 d3js 图表,如散点图、小提琴图、移动气泡图、桑基图、热图、和弦、时间序列、Imageslider 或粒子图,我们创建了 D3Blocks 库 ,其中包含 10 个可以用 Python 创建的漂亮图表。阅读 D3Blocks 中帖【2】了解更多详情。随意摆弄库,叉源代码。

注意安全。保持冷静。

干杯,E.

如果你觉得这篇文章很有帮助,可以使用我的 推荐链接 继续无限制学习,并注册成为中级会员。另外, 关注我 关注我的最新内容!

软件

我们连线吧!

参考

  1. https://www.tutorialsteacher.com/d3js
  2. E.Taskesen, D3Blocks:创建交互式和独立 D3js 图表的 Python 库 Medium,迈向数据科学,2022 年 9 月

用 Python 制作漂亮的地形图

原文:https://towardsdatascience.com/creating-beautiful-topography-maps-with-python-efced5507aa3

实践教程

用 Python 制作漂亮的地形图

当您可以用 Python 构建引人注目的 3D 地形图时,谁还需要 GIS 呢?

制图 twitter 目前充斥着一些引人注目的单个国家或地区的地形图。这主要是由于可用数据源的扩展和许多无聊的地理书呆子在疫情期间让自己忙碌。有很多工具和方法可以用来生成上面分享的美丽的意大利地图,但是在这篇文章中,我将使用 Python 向您展示一种非常规的方法,希望您会像我一样确信,如果某件事情值得做,就值得用 Python 来做。

有许多可用的数据源,都有不同程度的分辨率。在纯粹的美学层面上,图像的分辨率越高,最终的图像看起来就越好。从精度的角度来看,较低分辨率的数据集也可能会夸大要素,使相对平坦的区域看起来相当多山。

使用高分辨率(7.5 弧秒)和低分辨率(1 弧分)数据源绘制的意大利地形图。作者图片

虽然它们看起来没有什么不同,但左图使用的数据(1)比右图(2)的分辨率高得多,因此相对平坦的区域,例如意大利北部的波河流域,看起来比右图中的实际丘陵要多。因此,今后我们将使用来自美国地质调查局(1)的高分辨率数据。数据可以免费使用,关于数据使用的更多信息可以在下面的链接中找到。数据被分割成 30 x 20 度的 tif 文件,覆盖了全球的不同地区,幸运的是意大利位于其中一个文件中,所以我们不需要担心合并不同的 tif 文件,尽管我将在以后的文章中探讨这个问题。下载数据非常简单,只需点击您想要的图块,然后按照下载说明进行操作。然后把数据复制到你想编程的任何地方。

美国地质勘探局运营的数据交付平台截图。作者图片

首先要做的是使用rasterio打开并读取数据,这是一个相对简单的过程,但我已经包含了代码。然后,重要的是绘制数据,以了解实际情况和您正在处理的问题。有各种各样的函数可以汇总数据,但是我认为它们很少能很好地代替图表。需要注意的是,rasterio.open创建了一个rasterio 数据集对象,它包含高程值的 2D 数组(纬度 x 经度)以及关于所使用的投影和图像范围的信息,这在后面会很重要。read 方法将 rasterio 数据集对象中的数据读入高程值的 2D numpy 数组(在本例中)。2D 数组用于绘图,而rasterio 数据集对象将在以后使用。

原始数据,使用光谱色彩图绘制。作者图片

有一个迫在眉睫的问题需要解决,数据集包含南欧大部分地区和北非部分地区的信息,而实际上,我们想要的只是意大利。幸运的是,rasterio 提供了一种基于地理配准形状(如多边形)裁剪栅格的有用方法。在本例中,我使用了 NaturalEarth (3)数据集的一部分意大利多边形,通过rasterio.mask.mask 函数来裁剪栅格。我加载了 NaturalEarth shapefile 并使用geopandas 提取了意大利的几何图形,然后使用了蒙版功能。mask 函数采用rasterio 数据集对象,并返回所提供的多边形内的栅格部分。

作者图片

现在我们有了仅与意大利相对应的海拔值,但是仍然有一个问题。默认情况下,rasterio.mask.mask将用 0 填充所有不在意大利多边形内的值。虽然这是合理的,但这些 0 将使绘图变得棘手,因为它们在色彩图的底部充当锚,如果 0 和意大利数据中的最小高程值之间有很大的差距,那么您会看到上面看到的图,其中一半色彩图中有真实数据,另一半色彩图中没有真实数据,因为 0 和真实数据的最小值之间有差距。例如,如果 0 和最小值之间的差距等于最小和最大高度之间的差距,那么实际上只有一半的颜色图将用于真实数据。

为了解决这个问题,mask 函数允许您使用关键字nodata明确设置什么值将被应用到不在意大利多边形内的值。我在下面加入了一个函数来解决这个问题。在下面的函数中,我传递了意大利GeoDataFramerasterio 数据集对象。您会注意到掩膜函数被调用了两次,第一次是如上所述调用,不在意大利面内的值被返回为 0。第二次,使用了nodata 参数,不属于意大利面的值被设置为比意大利地形数据集中的最大值大 1(使用第一个掩膜计算)。结果是,我们现在有了一个在值中没有自然间隙的数据集,并且图表开始成形。这个函数返回的还有value_range 变量。这对应于数组中最小和最大值之间的差距,并且在以后构造颜色图时需要。

作者图片

现在我们需要构建一个合适的颜色图。绝对明确地说,本文的目的是向您展示如何生成有趣但至关重要的美丽地形图。如果你计划创造一些军事单位将在战斗中使用的东西,那么我建议你使用更定量的颜色图。在这个例子中,我们将基于意大利国旗构建一个颜色图。下面我用意大利国旗的颜色做了一个。仅仅使用这三种颜色会产生一个中间有太多白色的颜色图,所以我在这个颜色图的 2 号和 4 号位置添加了额外的绿色和红色,以最小化白色的优势。

作者图片

我们仍然需要处理不属于之前创建的意大利高程数据集的值。我们将这些值设置为比高程数据中的最大值大 1。解决方案是用足够的颜色构建一个色彩图,这样意大利高程数据中的每个唯一值都有自己的颜色,然后用我们的背景色替换色彩图中的最后一种颜色(红边)。例如,考虑一个场景,其中最小高程值为 10,最大高程值为 100,我们将非意大利值设置为 101。如果我们用 91 种颜色创建一个色彩映射表,用我们的背景色替换第 91 种颜色,那些非意大利值将被映射到第 91 种颜色,这是我们的背景色。

现在,有了新的颜色图和剪辑数据,我们就可以绘制数据了。

作者图片

虽然我认为这仍然看起来很好,它仍然是 2D 和地形是三维的。所以最后要做的是添加山体阴影来模拟光照在地形上。山体阴影是表面的 3D 表示,通常以灰度渲染。较暗和较亮的颜色表示您在地形模型中看到的阴影和高光。山体阴影通常用作地图中的参考底图,以使数据看起来更加三维,从而在视觉上更加有趣。我们将使用earthpy 山体阴影函数来生成山体阴影数据。有两个参数可以调整,根据数据集的不同,它们会给出明显不同的结果。第一个是azimuth 值,范围为 0-360 度,与光源的发光位置有关。0 度对应于指向正北的光源。第二个是光源所在的altitude ,这些值的范围是 1-90。下面是几个例子,强调了改变这两个值会产生非常不同的结果。

不同的方位角值及其对山体阴影渲染方式的影响。所有的高度都设置为 1。作者图片

不同的高度值及其对山体阴影渲染方式的影响。所有都设置了 180°的方位角。作者图片

最后我决定方位角值为 180,这样我在阿尔卑斯山的南边得到了一个漂亮的阴影,高度值为 1。不过,我会鼓励你自己尝试这些价值观。最后是绘制成品的时候了。首先绘制主要的意大利地形数据,在顶部绘制带有小 alpha 值的山体阴影。

瞧啊。作者图片

放大可以让你真正看到图像的细节!

作者图片

这就是我们的意大利地形图,一幅非常赏心悦目的意大利地形图,随时可以裱起来挂在你的墙上。这种方法可以应用于任何国家或地区,但是如果一个国家的数据跨越多个 tif 文件,则需要额外的步骤。我将在以后的文章中讨论这个问题,所以请订阅以确保你能看到它。

(1)-高分辨率数据源- Danielson,J.J .和 Gesch,D.B .,2011,全球多分辨率地形高程数据 2010 (GMTED2010):美国地质调查局公开文件报告 2011–1073,26 页 doi: 10.5066/F7J38R2N 数据免费使用,关于使用的进一步信息可在此处找到- https://topotools.cr.usgs

(2)—低分辨率数据源- doi:10.7289/V5C8276M

(3)—natural earth—https://www.naturalearthdata.com/

使用 Seaborn Python 库创建箱线图

原文:https://towardsdatascience.com/creating-boxplots-with-the-seaborn-python-library-f0c20f09bd57

Seaborn 箱线图快速入门指南

图片来自 Pixabay

箱线图是可视化数据的伟大统计工具,通常用于数据科学项目的探索性数据分析(EDA)阶段。它们为我们提供了数据的快速统计摘要,帮助我们了解数据是如何分布的,并帮助我们识别异常数据点(异常值)。

在这个简短的教程中,我们将看到如何使用流行的 Seaborn Python 库生成箱线图。

什么是箱线图?

一个箱线图是一种基于五个关键数字显示数据分布的图形化和标准化方法:

  • “最低”
  • 第一个四分位数(第 25 个百分位数)
  • 中位数(第二个四分位数/第五十个百分位数)
  • 第三个四分位数(第 75 个百分位数)
  • "最大值"

最小值和最大值分别定义为 Q1-1.5 * IQR 和 Q3 + 1.5 * IQR。任何超出这些限制的点都被称为异常值。

箱线图的图形描述,突出显示关键组成部分,包括中位数、四分位数、异常值和四分位数间距。作者创建的图像。

箱线图可用于:

  • 识别异常值或异常数据点
  • 来确定我们的数据是否有偏差
  • 了解数据的分布/范围

为了构建箱线图,我们首先从中间值(第 50 百分位)开始。这代表了我们数据中的中间值。

然后在第 25 和第 75 个百分点之间形成一个方框(分别为 Q1 和 Q3)。这个方框表示的范围称为四分位间距(IQR)。

从这个盒子延伸出两条线,也就是众所周知的胡须。这些延伸到 Q1-1.5 * IQR 和 Q3 + 1.5 * IQR,或者延伸到小于该值的最后一个数据点。

任何超出晶须极限的点都称为异常值。

资料组

我们在本教程中使用的数据集是作为 Xeek 和 FORCE 2020 (Bormann et al .,2020) 举办的机器学习竞赛的一部分使用的训练数据集的子集。

完整的数据集可以通过以下链接获得:https://doi.org/10.5281/zenodo.4351155

竞赛的目的是利用测井测量从现有的标记数据预测岩性。完整的数据集包括来自挪威海的 118 口井。

此外,您可以从 GitHub 资源库下载本教程中使用的数据子集:

https://github.com/andymcdgeo/Petrophysics-Python-Series

锡伯恩图书馆

Seaborn 是一个建立在 matplotlib 之上的高级数据可视化库。它为创建更高级的绘图提供了更容易使用的语法。与 matplotib 相比,默认数字也更具视觉吸引力

使用 Seaborn 构建箱式地块

导入库和数据

首先,我们首先需要导入将要使用的库: pandas 用于加载和存储我们的数据,以及 Seaborn 用于可视化我们的数据。

import seaborn as sns
import pandas as pd

导入库后,我们可以从 CSV 文件导入数据并查看文件头。

df = pd.read_csv('Data/Xeek_train_subset_clean.csv')
df.head()

在数据集内,我们有关于油井、地质分组和地层的详细信息,以及我们的测井测量。如果您不熟悉这些数据,请不要担心,因为下面的技术可以应用于任何数据集。

创建简单的箱线图

我们可以生成第一个箱线图,如下所示。在括号内,我们传入想要从数据帧中访问的列。

sns.boxplot(x=df['GR']);

由 Seaborn 生成的简单箱线图。图片由作者提供。

我们也可以旋转我们的绘图,使方块是垂直的。为了做到这一点,我们为y而不是x提供一个值。

sns.boxplot(y=df['GR']);

Seaborn 生成的垂直箱线图。图片由作者提供。

我们可以结合xy参数来创建多个盒状图。在本例中,我们将 y 轴设置为 GR(伽马射线),它将被 LITH(岩性)列分割成单独的箱线图。

sns.boxplot( x=df['LITH'], y=df['GR']);

根据岩性划分的伽马射线数据的海底生成的垂直箱线图。图片由作者提供。

从表面上看,我们现在有一个按岩性划分的多个箱线图。不过,有点乱。我们可以整理一下,用几行额外的代码使它变得更好。

整理默认的 Seaborn 箱线图

更改图形大小和旋转 x 轴标签

由于 Seaborn 构建在 matplotlib 之上,我们可以使用 matplotlib 的功能来提高我们的绘图质量。

使用 matplotlibs .subplots函数,我们可以使用figsize定义图形的大小,还可以调用图形的元素,如 xticks。在下面的例子中,我们将图形大小设置为 10 乘 10,并将xtick标签的旋转角度设置为 90 度。

import matplotlib.pyplot as pltfig, ax = plt.subplots(1, figsize=(10, 10))sns.boxplot(x=df['LITH'], y=df['GR']);
plt.xticks(rotation = 90)
plt.show()

当我们运行这段代码时,我们得到了一个更容易阅读的图。

在定义图形大小和旋转 x 轴标签后,由按岩性划分的伽马射线数据的海底生成的垂直箱线图。图片由作者提供。

更改 Seaborn 箱线图的图形大小方法 2

改变 Seaborn 地块大小的另一种方法是调用sns.set(rc={“figure.figsize”:(10, 10)})。使用这个命令,我们可以很容易地改变绘图的大小。

然而,当我们使用这条线时,它会将所有后续的图设置为这个大小,这可能并不理想。

sns.set(rc={"figure.figsize":(10, 10)})
sns.boxplot( x=df['LITH'], y=df['GR']);

Seaborn 在使用 sns.set()更改图形大小后生成的垂直箱线图。图片由作者提供。

设计 Seaborn 箱线图

Seaborn 提供了五种预设样式(深色网格、白色网格、深色、白色和刻度),可以快速轻松地改变整个地块的外观。

要使用其中一种样式,我们调用sns.set_style(),并传入其中一种样式作为参数。在本例中,我们将使用白色网格。

sns.set_style('whitegrid')
sns.boxplot( y=df['LITH'], x=df['GR']);

当我们运行代码时,我们得到了下面的图。请注意,我还交换了 x 轴和 y 轴,这样方框就可以水平绘制了。

Seaborn 箱线图显示应用 Seaborn 主题后不同岩性的伽马射线值。图片由作者提供。

如果我们想改变方框图的颜色,我们只需使用color参数,并传入我们选择的颜色。

sns.set_style('whitegrid')
sns.boxplot( y=df['LITH'], x=df['GR'], color='red');

这将返回以下带有红框的图。

Seaborn 盒状图显示了设置盒的颜色后不同岩性的伽马射线值。图片由作者提供。

除了固定的颜色,我们还可以对箱线图应用调色板。这将使每个盒子有不同的颜色。在这个例子中,我们将调用蓝调调色板。你可以在这里了解更多关于 Seaborn 调色板的细节。

sns.set_style('whitegrid')
sns.boxplot( y=df['LITH'], x=df['GR'], palette='Blues');

Seaborn 盒状图显示了应用调色板给盒子着色后不同岩性的伽马射线值。图片由作者提供。

设定 Seaborn 图的 X 轴和 Y 轴标签的样式

默认情况下,Seaborn 将使用轴标签的列名。

首先,我们必须将我们的箱线图赋给一个变量,然后访问所需的函数:set_xlabelset_y_labelset_title。当我们调用这些方法时,我们还可以设置字体大小和字体粗细。

p = sns.boxplot(y=df['LITH'], x=df['GR'])
p.set_xlabel('Gamma Ray', fontsize= 14, fontweight='bold')
p.set_ylabel('Lithology', fontsize= 14, fontweight='bold')
p.set_title('Gamma Ray Distribution by Lithology', fontsize= 16, fontweight='bold');

当我们运行这段代码时,我们得到了一个更好看的图,带有易于阅读的标签。

对标题、x 轴和 y 轴标签应用格式后的 Seaborn Boxplot。图片由作者提供。

设计 Seaborn 箱线图的异常值

除了能够设计盒子的样式,我们还可以设计离群值的样式。为了做到这一点,我们需要创建一个变量字典。在下面的例子中,我们将改变标记的形状(marker)、标记的大小(markersize)、异常值的边缘颜色(markeredgecolor)和填充颜色(markerfacecolor)以及异常值透明度(alpha)。

flierprops = dict(marker='o', markersize=5, markeredgecolor='black', markerfacecolor='green', alpha=0.5)p = sns.boxplot(y=df['LITH'], x=df['GR'], flierprops=flierprops)
p.set_xlabel('Gamma Ray', fontsize= 14, fontweight='bold')
p.set_ylabel('Lithology', fontsize= 14, fontweight='bold')
p.set_title('Gamma Ray Distribution by Lithology', fontsize= 16, fontweight='bold');

更改默认异常值(flier)属性后的 Seaborn 箱线图。图片由作者提供。

摘要

在这个简短的教程中,我们看到了如何使用 Python Seaborn 库来生成测井数据的基本箱线图,并按岩性对其进行拆分。与 matplotlib 相比,Seaborn 直接提供了更好的绘图。

我们可以使用箱线图来可视化我们的数据,并了解数据的范围和分布。然而,它们是识别数据异常值的优秀工具。

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

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

参考

博尔曼,彼得,奥桑德,彼得,迪里布,法哈德,曼拉尔,投降,&迪辛顿,彼得。(2020).机器学习竞赛 FORCE 2020 井测井和岩相数据集[数据集]。芝诺多。http://doi.org/10.5281/zenodo.4351156

使用 Python 的叶库创建 Choropleth 地图

原文:https://towardsdatascience.com/creating-choropleth-maps-with-pythons-folium-library-cfacfb40f56a

如何用 Python 制作不同数据结构的 choropleths

【2019 年 4 月纽约市可出租公寓一览表(GitHub)

C horopleth 地图用于显示地理区域的数据变化( 人口教育 )。我使用 choropleths 显示了纽约市不同邮政编码的可用出租公寓数量,并显示了给定时期每个邮政编码的抵押贷款交易数量。Python 的叶子库使用户能够构建多种定制地图,包括 choropleths,你可以将它们作为.html文件与不知道如何编码的外部用户共享。

加载和查看地理数据

美国政府网站通常有创建地图所需的地理数据文件。纽约市的 OpenData 网站和美国人口普查局的网站有多种数据类型的地理边界文件。Python 允许你加载多种文件类型,包括 geo JSON(*.geojson*)文件和 shape file(*.shp*)。这些文件包含给定位置的空间边界。

关于folium.Choropleth()方法的 folio 文档说明,geo_data参数接受 GeoJSON 几何作为字符串来创建映射,“URL、文件路径或数据 (json、dict、geopandas 等)到您的 GeoJSON
几何“()folio 文档 ) 。无论我们如何加载文件,我们都必须用这种方法转换几何数据才能正常工作。该方法的key_on参数将每个特定位置 (GeoJSON 数据)的数据与该位置(即人口)的数据绑定。

杰奥森

GeoJSON 文件存储几何形状,在这种情况下是位置的边界及其相关属性。例如,加载带有纽约市邮政编码边界的 GeoJSON 文件的代码如下:

# Code to open a .geojson file and store its contents in a variable**with** open ('nyczipcodetabulationareas.geojson', 'r') **as** jsonFile:
    nycmapdata **=** json**.**load(jsonFile)

变量nycmapdata包含一个至少有两个键的字典,其中一个键叫做features,这个键包含一个字典列表,每个字典代表一个位置。第一个位置的主要 GeoJSON 结构摘录如下:

{'type': 'FeatureCollection',
 'features': [{'type': 'Feature',
   'properties': {'OBJECTID': 1,
    'postalCode': '11372',
    'PO_NAME': 'Jackson Heights',
    'STATE': 'NY',
    'borough': 'Queens',
    'ST_FIPS': '36',
    'CTY_FIPS': '081',
    'BLDGpostal': 0,
    '@id': 'http://nyc.pediacities.com/Resource/PostalCode/11372',
    'longitude': -73.883573184,
    'latitude': 40.751662187},
   'geometry': {'type': 'Polygon',
    'coordinates': [[[-73.86942457284177, 40.74915687096788],
      [-73.89143129977276, 40.74684466041932],
      [-73.89507143240859, 40.746465470812154],
      [-73.8961873786782, 40.74850942518088],
      [-73.8958395418514, 40.74854687570604],
      [-73.89525242774397, 40.748306609450246],
      [-73.89654041085562, 40.75054199814359],
      [-73.89579868613829, 40.75061972133262],
      [-73.89652230661434, 40.75438879610903],
      [-73.88164812188481, 40.75595161704187],
      [-73.87221855882478, 40.75694324806748],
      [-73.87167992356792, 40.75398717439604],
      [-73.8720704651389, 40.753862007052064],
      [-73.86942457284177, 40.74915687096788]]]}}, ... ]}

folium.Choropleth()方法的key_on参数要求用户以字符串形式引用 GeoJSON 文件中位置字典的唯一索引键:

key_on ( 字符串,默认无)—geo _ data geo JSON 文件中的变量,数据绑定到该变量。必须以“feature”开头,并且采用 JavaScript 异议表示法。例如:“feature.id”或“feature.properties.statename”。

在上面的例子中,索引键是邮政编码,与每个位置相关的数据也必须有一个邮政编码索引键或列。上例中的key_on参数是以下字符串:

‘feature.properties.postalCode’

注意:字符串的第一部分必须始终是单数单词 *feature* ,它不像父字典那样包含每个单独位置字典的列表。

key_on参数是访问每个特定位置的properties键。properties键本身包含一个有 11 个键的字典,在这种情况下,postalCode键是索引值,它将几何形状链接到我们想要绘制的任何值。

地质公园

另一种加载地理数据的方法是使用 Python 的 GeoPandas 库 ( 链接 ) 。这个库在加载 Shapefile 时很有用,shape file 在美国人口普查网站 ( 制图边界文件— Shapefile ) 上提供。GeoPandas 的工作方式类似于 Pandas,只是它可以存储和执行几何数据的功能。例如,用美国所有州的边界加载 shapefile 的代码如下:

# Using GeoPandasimport geopandas as gpd
usmap_gdf = gpd.read_file('cb_2018_us_state_500k/cb_2018_us_state_500k.shp')

us map _ GDF 数据帧的头部

如果您调用 Jupyter 笔记本中第一行的 (Mississippi) 几何列,您将看到以下内容:

usmap_gdf[“geometry”].iloc[0]

当调用特定的几何值时,您会看到一个几何图像,而不是表示形状边界的字符串,上面是第一行(密西西比)的几何值

与 GeoJSON 字典的内容不同,没有用于访问内部字典的features键,也没有properties列。folium.Choropleth()方法的key_on参数仍然要求字符串的第一部分为feature,但是该方法将引用 GeoPandas 数据帧中的列,而不是引用 GeoJSON 的位置字典。在这种情况下,key_on参数将等于“feature.properties.GEOID”,其中GEOID是包含将我们的数据绑定到地理边界的唯一州代码的列。GEOID列有前导零,加州GEOID06。您也可以使用STATEFP列作为索引,确保使用的列、格式和数据类型都是一致的。

查看 Choropleth 的群体数据

地理数据和要绘制的相关数据可以作为两个单独的变量存储,也可以一起存储。重要的是跟踪列的数据类型,并确保索引(*key_on*)列对于地理数据和位置的关联数据是相同的。

我访问了美国人口普查 API 的美国社区调查 ( 链接 ) 和人口估计与预测 ( 链接 ) 表格,得到 2019 年到 2021 年的人口和人口统计数据。数据帧的标题如下:

美国人口普查数据框架的头

我将数据保存为一个.csv文件,在某些情况下,这将改变列的数据类型;例如,字符串可以变成数值。调用.info() 时的数据类型如下:

将数据框保存和加载为 CSV 文件之前和之后的人口普查数据的数据类型

另一个需要注意的重要事情是,在加载数据帧后,state列中的所有前导零都不会出现。这必须得到纠正;id 必须匹配并且是相同的数据类型(即它不能在一个数据框中是整数而在另一个数据框中是字符串)

基本的 Choropleth 映射有五种不同的方式

如上所述,follow 允许您使用地理数据类型创建地图,包括 GeoJSON 和 GeoPandas。这些数据类型需要被格式化,以便与 least 库一起使用,为什么会出现某些错误并不总是直观的(对我来说是,至少是)。以下示例描述了如何准备地理数据(在本例中为美国州界)和相关的绘图数据(各州的人口)以用于folium.Choropleth()方法。

方法 1:使用 Pandas 和 GeoJSON,不指定 ID 列

这种方法非常类似于文档中的 choropleth 地图的例子。该方法使用包含州边界数据的 GeoJSON 文件和 Pandas 数据帧来创建地图。

当我开始使用 GeoPandas 文件时,我需要使用 GeoPandas 的to_json() 方法将其转换为 GeoJSON 文件。提醒一下 usmap_gdf GeoPandas 数据帧看起来像:

us map _ GDF 数据帧的头

然后我应用.to_json()方法,并指定我们从数据帧中删除id,如果它存在的话:

usmap_json_no_id = usmap_gdf.to_json(drop_id=True)

注意: *usmap_json_no_id* 是本场景中存放 json 字符串的变量

这个方法返回一个字符串,我对它进行了格式化,以便于阅读,并显示在下面的第一组坐标上:

'{"type": "FeatureCollection",
  "features": [{"type": "Feature",
  "properties": {"AFFGEOID": "0400000US28", 
                 "ALAND": 121533519481,
                 "AWATER": 3926919758,
                 "GEOID": 28,
                 "LSAD": "00",
                 "NAME": "Mississippi",
                 "STATEFP": "28",
                 "STATENS": "01779790",
                 "STUSPS": "MS"},
  "geometry": {"type": "MultiPolygon",
               "coordinates": [[[[-88.502966, 30.215235]'

注意:“属性”字典没有名为“id”的键

现在,我们准备将新创建的 JSON 变量与上一节中获得的美国人口普查数据框架连接起来,其标题如下:

美国人口普查数据框架的头部,下面称为 all_states_census_df

使用 folium 的Choropleth()方法,我们创建地图对象:

创建一个Choropleth with a GeoJSON variable which does not specify an id的代码

geo_data参数被设置为新创建的*usmap_json_no_id*变量,而data参数被设置为 all_states_census_df 数据帧。由于在创建 GeoJSON 变量时未指定 id,key_on参数必须引用地理数据中的特定关键字,并且其工作方式类似于字典( GEOID 是“属性”关键字的值)。在这种情况下,GEOID键保存州代码,该代码将州几何边界数据连接到 all_states_census_df 数据帧中相应的美国人口普查数据。下面是 choropleth:

由上述方法得到的 choropleth

方法 2:使用 Pandas 和 GeoJSON,并指定 ID 列

除了在调用.to_json()方法之前使用一个索引之外,这个过程与上面的几乎完全相同。

在上面的例子中,usmap_gdf数据帧没有索引,为了纠正这个问题,我将把索引设置为GEOID列,然后立即调用.to_json()方法:

usmap_json_with_id = usmap_gdf.set_index(keys = “GEOID”).to_json()

直到第一个州的数据的第一对坐标,结果字符串如下:

'{"type": "FeatureCollection",
  "features": [{"id": "28",
                "type": "Feature",
                "properties": {"AFFGEOID": "0400000US28",
                               "ALAND": 121533519481, 
                               "AWATER": 3926919758,
                               "LSAD": "00",
                               "NAME": "Mississippi",
                               "STATEFP": "28",
                               "STATENS": "01779790",
                               "STUSPS": "MS"},
                "geometry": {"type": "MultiPolygon",
                             "coordinates": [[[[-88.502966, 30.215235],'

“properties”字典不再有GEOID键,因为它现在作为一个名为id的新键存储在外层字典中。您还应该注意到,id值现在是一个字符串,而不是一个整数。如前所述,您必须确保连接数据的数据类型是一致的。如果涉及前导零和尾随零,这会变得很乏味。为了解决这个问题,我从all_states_census_df中的state列创建了一个名为state_str的新列:

all_states_census_df[“state_str”]=all_states_census_df[“state”].astype(“str”)

现在我们可以创建 choropleth:

创建 c 的代码horopleth with a GeoJSON variable which specifies an id

该代码与之前使用的代码的区别在于key_on参数引用的是id而不是properties.GEOID。生成的地图与方法 1 完全相同:

由上述方法得到的 Choropleth

方法 3:使用熊猫和 GeoPandas 的 Python 要素集合

该方法从具有__geo_interface__ 属性的原始 GeoPandas 数据帧创建 GeoJSON like 对象( python 要素集合)。

我将usmap_gdfdata frame(US geographic data)的索引设置为STATEFP列,该列以字符串形式存储州 id,并以零开头:

usmap_gdf.set_index(“STATEFP”, inplace = True)

然后,我通过添加一个前导零,在all_states_census_df数据框(美国人口普查数据)中创建了一个匹配列:

all_states_census_df[“state_str”] = all_states_census_df[“state”].astype(“str”).apply(lambda x: x.zfill(2))

最后,我使用us_data_gdf GeoPandas dataframe 的__geo_interface__ 属性来获取几何状态边界的 python 特性集合,存储为一个字典,类似于前两种方法:

us_geo_json = gpd.GeoSeries(data = usmap_gdf[“geometry”]).__geo_interface__

下面是us_geo_json变量的摘录:

{'type': 'FeatureCollection',
 'features': [{'id': '28',
   'type': 'Feature',
   'properties': {},
   'geometry': {'type': 'MultiPolygon',
    'coordinates': [(((-88.502966, 30.215235), ...))]

最后,我们创建了 choropleth:

创建一个 c horopleth with a GeoPanda's **__geo_interface__**属性的代码

这张地图看起来和上面的一样,所以我把它排除了。

方法 4:使用 Geopandas 的几何类型列

在这里,我们坚持 GeoPandas。我创建了一个名为us_data_gdf的 GeoPandas 数据框架,它将几何数据和普查数据组合在一个变量中:

us_data_gdf = pd.merge(left = usmap_gdf,
                       right = all_states_census_df,
                       how = "left", 
                       left_on = ["GEOID", "NAME"],
                       right_on = ["state", "NAME"]
                       )

注:all _ States _ Census _ df是美国人口普查数据的熊猫数据帧,US map _ GDF是存储州几何边界数据的 GeoPandas 数据帧。

使用 GeoPandas 数据帧创建 choropleth 的代码如下:

使用 GeoPandas 数据帧创建 choropleth 的代码

在上面的例子中,geo_data参数和data参数都引用相同的 GeoPandas 数据帧,因为信息存储在一个地方。因为我没有设置索引,所以key_on参数等于“feature.properties.GEOID”。即使使用 GeoPandas,folium 也需要使用key_on参数,就像引用一个类似字典的对象一样。

和以前一样,这张地图看起来和上面的一样,所以我把它排除了。

方法 5:使用 Geopandas 几何类型和 Branca

在这里,我们使用 Branca 和 leav 的示例创建了一个更加时尚的地图。除了安装之外,Branca 的第一步是创建一个ColorMap对象:

colormap = branca.colormap.LinearColormap(
    vmin=us_data_gdf["Total_Pop_2021"].quantile(0.0),
    vmax=us_data_gdf["Total_Pop_2021"].quantile(1),
    colors=["red", "orange", "lightblue", "green", "darkgreen"],
    caption="Total Population By State",
)

在上面的代码中,我们访问了branca.colormap.LinearColormap 。在这里,我们可以设置我们使用的颜色以及色阶的值。对于这个 choropleth,我希望颜色与美国人口普查数据中最低和最高的人口值成比例。为了设置这些值,我使用了上述的vminvmax参数。如果我忽略了这一点,那么色标中将考虑没有值的区域,没有这些设置参数的结果如下:

没有设置**vmin*****vmax***参数的 Branca choropleth

一旦创建了ColorMap对象,我们就可以创建一个 choropleth ( 完整代码在下面):

使用 GeoPandas 数据框架和 Branca 库创建 choropleth

我修改了 leav 网站上的例子,使用了us_data_gdf GeoPandas 数据框架。该示例允许我们排除地理数据中不具有相关人口普查数据的部分(显示为透明的)(如果一个州的人口为空,那么除非被排除,否则 choropleth 上的颜色将为黑色。生成的 choropleth 如下:

用麦麸和地瓜做成的鸡蛋饼

布兰卡是可定制的,但如何使用它的解释是少之又少。其存储库的自述文件指出:

没有文档,但是你可以浏览范例库。

你必须练习使用它来制作你想要的那种地图。

摘要

对那些有或没有编码知识的人来说,叶子可以用来制作信息丰富的地图,如 choropleths。政府网站通常拥有为您的数据创建位置边界所需的地理数据,这些数据也可以从政府网站获得。理解你的数据类型和文件类型是很重要的,因为这会导致不必要的麻烦。这些地图是高度可定制的,例如你可以添加工具提示来注释你的地图。充分利用这个图书馆的潜力需要实践。

我的这篇文章可以在 这里找到 。快乐编码。

使用优步的 H3 六边形创建一致的空间计算

原文:https://towardsdatascience.com/creating-consistent-spatial-calculations-with-ubers-h3-hexagons-1af032802a77

将人口普查数据添加到 H3 六边形以进行空间分析

唐尼·姜在 Unsplash 上的照片

所有的分析技术、观点和代码都是独立开发和我自己的工作。

获取一个城市或邮政编码的人口统计数据非常简单——有几个 API 和数据下载工具可以做到这一点。但是,由于许多原因,这些地理位置有局限性(举几个例子,邮政编码是 USPS 递送的功能,城市边界是在几个世纪前建立的——并不完全是为数据分析设计的)。

我测试了几种不同的方法来克服这些边界的任意性——从创建统一的盒子开发驾驶(或行走)时间多边形,这取决于用例。

我最近开始意识到 H3 的好处,这是优步在 2018 年推出的一套全球六边形。主要思想是六边形是最有利于空间分析的形状,可用于创建一致的计算。

示例 H3 六边形;作者图片

每个六边形都有一系列更小的六边形,这些六边形(大部分)位于另一个六边形的内部,这创建了一个可用于一致参考和分析的层次结构,一直到边长为 2 英尺。

作者图片

这种方法适用于点数据——骑手是在哪里上车的?离开了?任何有经纬度的地址或兴趣点都可以映射成六边形。

如果我们想利用六边形分析的优势,但使用人口统计数据,会发生什么?我发现了很多关于使用六边形的信息,但是没有引入人口统计数据,所以我开始将两者结合起来。

处理人口普查数据时,稳健数据报告提供的最详细视图是人口普查区块组(在此阅读更多关于区块组的信息)。块组是通常有 600 到 3,000 人的边界,这使得它们可以同质学习和使用(这么小的区域往往有相似的人居住在其中)。

然而,因为它们受人口限制,所以大小缺乏一致性。例如,看看下面北卡罗来纳州罗利市中心的人口普查区块组,根据人口分布,有些较小,有些较大。

相比之下,六边形(为了有效分析,我随机选择了一个尺寸,但我们可以根据需要选择更小或更大的尺寸),大小一致,边界一致。

北卡罗来纳州罗利市中心的人口普查区块组(左)和 H3 六边形(右)

因此,问题变成了,我们如何将人口普查区块组中可用的人口统计信息映射到六边形的统一结构?我们如何从人口普查区块组的粒度和六边形的一致性中获益?

总体情况——我们如何解决这个问题?

假设我们想要找到每个六边形内的人口,更具体地说,让我们看一个六边形。我们可以用相应的块组边界覆盖六边形边界,得到类似下面这样的结果…

覆盖在交叉六边形上的块群总体;作者图片

完全位于一个六边形内的块组是容易的,我们考虑将全部人口分配到六边形(人口 708、969 和 1476 在六边形的中间)。然而,由于形状不匹配,许多有部分重叠,这只会更常见于较小的六边形。

对于与六边形相交的任何块组,我们可以计算两个形状之间的面积重叠,并根据此计算按比例分配人口。如果一半的块组重叠,我们认为一半的人口是在六边形。

这种假设并不完美——人口并不均匀地分布在一个街区组中——但出于实用目的,它为我们提供了一种“足够好”的方法来组合这两种地理形状。

实施

我将用 R 编程语言的代码来演示,但这种方法在 Python 或其他语言中在概念上是相同的。

我们需要两条主要信息来进行分析——带有相应人口统计数据的街区组形状和六边形形状。

在 R 中,我们可以利用tidysensus 包从美国人口普查 API 中获取数据(您将需要一个免费的开发者密钥)。[get_acs](https://www.rdocumentation.org/packages/tidycensus/versions/1.2.1/topics/get_acs) https://www.rdocumentation.org/packages/tidycensus/versions/1.2.1/topics/get_acs功能是获取块组数据的最简单方法,其典型语法如下:

get_acs(geography = 'block group', year = 2020, variables = c(total_population = 'B01001_001'), state=37, county=183, output='wide')

可选参数定义了我们需要输出的块组、2020 年 5 年 ACS 的数据、总人口变量标识符、北卡罗来纳州(FIPS 37)、威克县(代码 183)和宽输出格式。

get_acs 函数的输出;作者图片

从那里,我们需要为每个块组匹配一个形状(边界)。我们可以使用来自 tigris 包的block_groups函数,并提供州和县标识符,这将返回一个包含几何多边形的形状文件。

获取北卡罗来纳州威克县每个街区组的形状文件的示例语法;作者图片

通过大地水准面的简单连接可以将 shapefile 与人口统计信息放在一起,以创建单个块组文件。然后使用 sf 包将输出转换回 shapefile(稍后将详细介绍),并转换到坐标参考 4326。

acs_block_data %>%
  left_join(wake_block_groups, by='GEOID') %>%
  st_as_sf() %>%
  st_transform(4326)

为了获得威克郡的所有六边形,我获取了威克郡的 shapefile(也使用了 tigris 包,但是使用了counties函数),然后利用 h3jsr 包来访问 H3 的六边形。

polyfill函数查找给定边界内的所有六边形(这里是威克郡,罗利的家乡),而h3_to_polygon函数给出每个六边形的形状。res = 7参数定义了六边形的粒度级别(15 是最精确的,0 是最大的)。

wake_hex <- polyfill(wake_boundary, res=7, simple=F)
wake_hex_all <- h3_to_polygon(unlist(wake_hex$h3_polyfillers), simple=F)

输出是一系列六边形参考 id 和多边形形状。

作者图片

现在,对于每个六边形,我们想要:

  • 查找与六边形的边界相交(重叠)的任何块组
  • 找出块组的重叠百分比
  • 根据重叠百分比分配块组中的人口

R 中的 sf 包是空间分析的首选包。st_intersection函数查找两个形状的重叠区域,下面的 mutate 函数计算重叠百分比。此代码的输出将是所有重叠的块组,以及它们的重叠区域。

st_intersection(wake_hex_all[1,1], wake_blocks) %>%
mutate(intersect_area = st_area(.),
       pct_intersect = intersect_area / (ALAND + AWATER) )

带有附加数据操作的 st_intersection 函数的输出示例;作者图片

最后一步是将总体转换为数字,将总体乘以百分比交集以获得调整后的总体,然后将调整后的总体相加得到六边形中的总数。

st_intersection(wake_hex_all[1,1], wake_blocks) %>%
mutate(intersect_area = st_area(.),
       pct_intersect = intersect_area / (ALAND + AWATER),
       pop_total = as.numeric(total_populationE),
       pop_adj = pop_total * pct_intersect ) %>%
summarize(total_population = sum(pop_adj, na.rm = T)

例如,在上面的图像中,具有约 11%重叠的 2,911 人口成为约 317 的调整人口。

回到我们之前的例子,这种方法给出了估计的六边形人口为 9,980。再次观察每个区块组中的人口,视觉比较表明这可能不会太远。

底层块组群体的群体计算示例

当六边形/地块组与受保护的土地(如公园或水体)有较大重叠时,这种方法有局限性,这对于更偏远地区的小六边形最有影响。考虑到大多数城市和近郊的人口密度,我认为这种方法在大多数情况下会有方向性的帮助。

扩展分析

人口只是用作示例,但是可以使用在块组级别报告的任何内容。六边形的家庭收入中位数是多少?有多少户人家?有孩子的家庭比例是多少?添加到在get_acs函数中收集的变量中,可以启用如何分析和汇总数据的其他选项。

这也可以与可以聚合到六边形中的任何兴趣点数据相结合,例如,通过查找给定兴趣点的高密度区域,然后描述该六边形的人口构成。

虽然并不完美,但这提供了一种为空间分析和趋势创建一致计算的方法。总的来说,我们希望数据在方向上是有用的——如果我们计算出一个六边形的人口比另一个六边形多 20%,我们可以合理地确定第一个六边形的人口更多,即使不是正好 20%。

有疑问或者对这些话题感兴趣? 在 LinkedIn 上和我联系,或者在 jordan@jordanbean.com 给我发邮件。

在 Django 中创建动态长度表单

原文:https://towardsdatascience.com/creating-dynamic-length-forms-in-django-53709c23464e

费萨尔在 Unsplash 上拍摄的照片

讨论如何创建一个 Django 表单,它可以有动态数量的输入字段

最近,我正在解决一个问题,创建一个应用程序,可以使用搜索词收集维基百科数据,并收集文章简介。该应用程序进一步拆分所有句子,并允许用户将文章中每个句子的翻译保存在数据库中。这个应用程序将帮助任何人通过使用搜索词来注释或翻译维基百科的内容。

该应用程序主要是使用 Django 和 HTML 模板构建的。我使用了维基百科 API 来收集数据。最终输出可以在这里找到和源代码在我的 GitHub 上。

https://github.com/prakharrathi25/wikipedia-translator

下面,我还展示了应用程序的工作原理。输入搜索词后,API 抓取结果并显示在页面上。

输入搜索词后的应用程序输出(图片由作者提供)

搜索之后,应用程序的第二部分对文本进行标记(将文本分解成句子)并为翻译做准备。第二列中的每一行都是一个表单输入,用户可以在其中输入他们的翻译并保存。

准备翻译的标记化句子(图片由作者提供)

我面临的问题——动态表单长度

在构建项目时,我遇到了一个有趣的新问题— 创建动态表单。这是因为在对介绍段落进行标记后,我们需要创建一个表单,它可以根据句子的数量拥有不同数量的输入字段。然后,我们需要在后端处理所有的输入字段,并保存翻译。一个人也可以只输入一些句子的翻译,而把其余的留空。

我能够使用我最喜欢的 web 开发框架之一来解决这个问题——Django

解决方案

现在,我不会深入研究 Django 的基础知识,因为这篇文章的目标读者是那些有编写 Django 代码经验的人。如果你想入门,这里有个好教程

我将从定义我在这个应用程序中使用的模型开始。我使用的主要模式有两种——项目句子。每个新的文章标题都保存在项目中,标题的介绍被标记成句子。然后,每个句子都保存在句子模型中,并带有返回到项目的外键。

显示句子和项目模型及其字段的模型文件

接下来,我将展示我用来在同一个表单中动态显示多个输入字段的 HTML 文件。这意味着输入字段的数量将根据文章引言段落中的句子数量而变化。

显示动态条目的表格和表单(代码片段链接

这是 Django 的模板格式,你可以在这里阅读更多关于它的内容。这个表格循环了我通过视图上下文传递的句子的数量,稍后我会再看一遍。因此,根据句子的数量,我可以在表单中添加更多的字段,并从中获取输入。我做的另一件事是使输入字段 name 属性动态化,这样我就能够从单个输入中收集数据。

用户在文本区输入的数据处理如下。

还有一个 GET 请求处理程序,我已经省略了。完整的功能可以在这里找到。POST 请求处理器处理每个输入字段,并使用文本区域的 name 属性收集翻译。这是一个非常简单而优雅的解决方案,我花了一些时间才想出来。除了这个线程之外,这个没有很多堆栈溢出的答案。

结论

这不应该是一篇精心设计的文章。目的是分享一种解决 Django 论坛上经常提出的问题的新方法。希望这在将来能派上用场。

[## 使用 Streamlit 创建多页面应用程序(高效!)

towardsdatascience.com](/creating-multipage-applications-using-streamlit-efficiently-b58a58134030) https://levelup.gitconnected.com/5-new-features-in-python-3-11-that-makes-it-the-coolest-new-release-in-2022-c9df658ef813

用强化学习和虚幻引擎创建涌现行为

原文:https://towardsdatascience.com/creating-emergent-behaviors-with-reinforcement-learning-and-unreal-engine-4cd89c923b7f

使用虚幻引擎和免费的 MindMaker 机器学习插件生成人工智能角色的紧急行为

在下面的文章中,我将讨论如何使用虚幻引擎、强化学习和免费的机器学习插件 MindMaker 在人工智能角色中生成紧急行为。目的是让感兴趣的读者可以将此作为在他们自己的游戏项目或具体的人工智能角色中创建紧急行为的指南。

什么是突现行为,为什么要使用它?

首先是关于突现行为的初级读本。突现行为是指没有预先编程,而是响应某些环境刺激而有机发展的行为。突现行为是许多生命形式的共性,是进化本身的一种功能。这也是最近具体化人工代理的一个特征。当一个人采用涌现行为方法时,他不会为人工智能严格地编程特定的动作,而是允许它们通过一些适应性算法“进化”,如遗传编程、强化学习或蒙特卡罗方法。在这样的设置中——行为在开始时没有被预言,而是被允许基于级联的一系列事件“出现”,在某种程度上依赖于机会。

为什么人们会选择使用突现行为?一个主要原因是,涌现行为方法可以创造出行为更类似于碳基生命形式的人工智能代理,更不可预测,并显示出更大的战略行为多样性。

为娱乐或人类互动而设计的各种各样的具体化人工智能代理可以从紧急行为中受益,以便对它们的用户来说看起来不那么静态,更有吸引力。《自然》杂志上最近的一篇期刊文章证明了机器人在反应时间和运动模式方面表现出更多的可变性,被认为更像人类。

紧急行为也可以克服用传统编程方法创造的其他形式的人工智能所不能克服的障碍。通过允许人工智能发现开发人员没有预见到的解决方案,代理可以探索比传统编程方法更广阔的解决方案空间。甚至有可能出现的行为方法可能导致人工一般智能的产生,这是一种可以与人类拥有的技能多样性相媲美的人工智能。

使用紧急行为方法有一个警告,但是,如果你想要创建一个非常特定的行为,例如精确地重复模仿特定动物的动作,紧急行为技术可能不是最好的选择。突现行为不太适合不需要变化的重复性高保真任务。这是为什么人类和其他有机生命形式可能在高度重复的任务中挣扎的原因之一,我们天生是可变的。此外,当试图复制特定的行为时,可能很难重现导致该行为模式在现实世界中出现的相同偶然事件。例如,某种生物适应或行为可能会进化出各种各样的解决方案来实现,其结果受偶然事件的影响。因此,并不是所有的新方法都一定会得到相同的解决方案。一个例子是趋同进化在几个场合下重建眼睛的方式,但不一定是完全相同的形式。

涌现行为方法有利于创造多种多样的行为,其中一些行为可能与真实动物的行为相似,但不太可能完全相同。另一方面,人们可以期待用这些方法产生大量有趣和独特的行为。例如,人们可以看到由强化学习代理发现的穿越风景的各种各样的移动方法

创造突发行为的第一步是决定你想让哪种行为成为突发行为,以及该行为的目标是什么。从某种意义上说,这是容易的部分。下一部分稍微复杂一点,需要概述一些涌现理论。

不同类型的紧急行为

当我们讨论突现的话题时,我们需要区分开放式突现和静态或“固定点”突现。在定点涌现中,一开始可能会出现各种各样的行为,但它们会稳定地收敛到一个单一的解决方案或策略,涌现的数量会随着时间的推移而减少。本质上,这发生在当问题有一个静态的全局解决方案时,而这个行为是用来解决这个问题的。考虑井字游戏:虽然采用强化学习或遗传算法等新兴技术的人工智能最初可能会在井字游戏中显示各种各样的策略,但它会相当快地收敛到游戏的单一主导解决方案。在这一点上,不会出现进一步的涌现或策略。

当我谈到突现行为时,我相信当人们接近这个话题时,他们通常想到的是“开放式突现”。在这种形式的涌现中,没有固定的解决方案,一个主体会不断产生新的行为。创造这种开放式涌现比创造固定点涌现更复杂,你必须考虑创造它所必需的结构。令人欣慰的是,最近在理解和编纂开放式涌现的要求方面取得了一些进展,特别是 Joel Liebo 和其他人在自动课程方面所做的工作。在一篇开创性的论文“自主课程和来自社会互动的创新的出现”中,作者列出了一些可以预期开放式涌现发生的条件。下面的表格将有助于理解什么时候会出现无止境的涌现。

开放式涌现的秘诀

1.其中,行为或目标的解决方案非常大,因此,对于所有的意图和目的,涌现的数量在检查的时间周期内看不到顶端。这不是真正的开放式涌现,因为可能存在全局解决方案,如果给予足够的时间,代理将发现并停止适应。然而在许多情况下,这可能超出了人类生命的时间跨度,所以从观察者的角度来看,这种出现将是无限的。一个例子可能是国际象棋游戏,其中一个全局解决方案被认为存在于井字游戏中,但由于游戏的复杂性,这还没有被发现,而且在我们的有生之年也不太可能。

2.创造开放式涌现的另一个方法是采用依赖于不断变化的环境的行为或目标状态。想想地球上的碳基生命形式——由于行星气候条件,环境在不断变化,这确保了动物必须始终适应才能生存,并且在出现水平上没有上限。

3.开放式涌现的第三种方法是多智能体场景,其中涉及合作或竞争,智能体采用一些适应性学习策略。在这种情况下,一个主体必须适应其同伴或竞争对手的策略,这反过来确保其他主体也必须适应,从而形成一个不断适应的反馈循环,这是一种进化军备竞赛。虽然这可能导致涌现,但也可能导致重复行为的循环,无限循环,直到环境中的某些因素将它们推出循环。这不是一个真正的均衡,因为行为或策略不是固定的,但循环本身成为一种均衡。确保环境足够复杂和动态,这是避免这些周期性行为或策略的一种方式。

展示开放式涌现

在下面用虚幻引擎创建的例子中,我选择使用上面列出的第三个配方,一个涉及两个虚拟物种的多代理场景。我们称其中一只为犀牛,另一只为老虎。两组人都在探索他们的环境,寻找“浆果”,在虚幻引擎游戏环境中,这些浆果以大型圆形物体的形式出现。让事情变得更复杂的是,这些浆果丛中随机分布着野生鸟类,它们会尖叫,吓跑老虎和犀牛。然而,如果一只老虎或犀牛与另一只老虎或犀牛一起接近浆果,它们会吓得鸟儿们不敢吱声。他们现在可以吃浆果了,尽管他们必须在彼此之间把浆果分开。

除了这种合作行为,它们还有一种竞争行为,在这种行为中,它们可以发出吼声(老虎)或开始跺脚(犀牛),吓跑对手物种的代理人,但也消耗自己宝贵的能量。在许多方面,这种情况复制了自然界中动物和人类企业的一些合作和竞争权衡。

竞争或合作是最古老的问题之一,通过在虚拟绅士中模拟这一点,我们可以获得对导致各种竞争或合作结果的条件的宝贵见解。使用这样的虚拟代理,我们也可以创造出在动物王国甚至人类身上可以观察到的行为复杂性。OpenAI group 最近发表的一篇论文能够证明工具使用的出现是玩捉迷藏游戏的 AI 角色之间多智能体竞争的函数。

类似的事情也可以在任何一群适应性虚拟代理人身上发生,这些代理人会在彼此之间玩一个游戏,在这个游戏中,收益取决于其他玩家的策略,而环境会受到他们行为的影响。目标可以是任何东西,只是必须有一些方法让代理接收关于他们的环境的反馈,包括竞争者采取的行动和作为响应改变他们自己的行动的能力。这个目标是由玩家设定的还是由程序员设定的,从复杂程度上来说没有太大的区别。选择由玩家来设置它可以带来关于游戏叙事的有趣选项。需要注意的重要一点是——谁选择行为的目标并不重要,重要的是这符合上述竞争或合作的要求。

但是涌现实际上是如何“涌现”的呢?一种方法是使用强化学习或遗传算法。在我们的例子中,我们有一群虚拟代理人,他们随机改变行为来观察目标状态是否受到影响。代理优先考虑那些带来好结果的行为。在遗传算法中,这是通过一种在群体中发生的交叉函数来实现的,在这种情况下,随机发生的良好适应被保留,而相对不那么有用的适应被丢弃。这是由适应度函数决定的。这不需要看起来像有性生殖或任何类似的东西,因为现有的代理可以无缝地被它们适应的对应物取代,而用户看不到任何正在发生的交叉。在我们的例子中,我们将使用强化学习,这是一种算法技术,我已经在另一系列文章中广泛介绍过。

与遗传算法类似,强化学习依赖于大数定律,即给定足够多的随机行为,好的行动将会出现,并可以在未来进行优先排序。最初,代理人选择一系列随机的行为,但是如果这些行为导致代理人获得奖励,那么奖励的价值就归因于代理人采取的行为,因此在未来更有可能重复这些行为。当这个随机行为的过程被充分重复时,那些与接受奖励的代理人有因果关系的行为将与那些仅仅是偶然事件的行为区分开来。这就是为什么强化学习是核心,一种因果算法并可用于检测因果关系。

人们可以这样想象这个过程。

作者图片

为了这个项目,我使用了免费的思维制造插件和稳定基线强化学习算法套件。通过 MindMaker,我们可以使用 OpenAI Gym 格式在虚幻引擎游戏环境中轻松部署各种 RL 算法。这为在各种模拟环境中部署强化学习算法提供了一个通用的结构。

总结和未来发展

上面描述的简单设置导致一种情况,其中差异数字物种的行为不可能达到稳定的平衡。鸟类在浆果斑块中的随机分布确保了环境总是在变化,而这反过来又不断改变着代理商合作或竞争的潜在利益。代理人本身的活动也会影响有鸟类和没有鸟类的浆果斑块的比例——随着更多的代理人相互合作,合作的动力就会减少,因为没有鸟类的浆果斑块的比例会增加。

这个想法是,这些相互作用的力量创造了一个总是波动和不稳定的环境,推动着战略适应。鉴于我们的代理人与他们的环境互动的方式很少——他们不能利用环境中的物体诱捕竞争对手或隐藏浆果,我们的范式不太可能导致像紧急工具使用这样有趣的事情。然而,它确实避免了固定的均衡,并创造了不断变化的合作和竞争策略。这可以提供比开放世界视频游戏中通常出现的更有趣的一组行为。因此,我相信,像这里概述的这种新兴行为技术很可能会主导下一代视频游戏人工智能,创造出更有趣、更吸引人的角色和行为剧目。

Aaron Krumins 是“智胜——强化学习的承诺和危险”的作者,拥有 web 应用和机器学习的背景。他目前是一名自由职业的机器学习顾问。

使用 Python 创建 Excel 报表

原文:https://towardsdatascience.com/creating-excel-reports-with-python-e8c94072f09d

如何在不打开 Excel 的情况下创建 Excel 文件

照片由米卡·鲍梅斯特Unsplash 上拍摄

Excel 非常受欢迎。

Investopedia 来看,“在商业中,从字面上看,任何行业的任何职能都可以受益于那些拥有强大 Excel 知识的人。Excel 是一个强大的工具,已经成为全球业务流程中不可或缺的一部分”

业务用户喜欢 Excel,因此大量的报告需要在 Excel 中。

但这并不意味着我们必须使用 Excel 来处理数据。

在这里,我们将学习使用 Python 和 Pandas 创建 Excel 报表,而无需打开 Excel。

背景

作为一名数据分析师,我的第一个任务是在 Excel 中运行生产力报告。运行这个报告需要我手动下载两个大型数据集,将数据导入 Excel,合并数据,并执行计算。

因为数据集很大——至少对 Excel 来说——我经历了持续的滞后和可怕的死亡之轮。

它花了整整 8 个小时的工作日才完成。

然后,在我可以(当然是手动的)分发之前,它必须被发送给一个主管进行双重检查。

该报告必须每两周用新数据重新创建一次。在一年的时间里,仅复制一份报告就要花费超过 5 周的时间!

我知道一定有更好的方法。

任务

在这里,我们将使用 Python 为我们想象的小部件工厂创建一个 Excel 报表。

widget factory 委托我们创建一份报告,详细说明工人和团队创建 widget 的平均时间。他们希望报告是一个 Excel 文档,每个团队都有一张表格。

资料组

我们将继续使用我们在本文的中创建的小部件工厂数据集,在那里我们通过 Python 生成假数据。

创建报告

我们所有的数据操作都是使用 Python 和 Pandas 完成的。

首先,我们使用 Pandas 读入数据集,并检查每个数据集的形状。

接下来,我们使用.agg()通过worker_id查找汇总统计数据。对数据进行分组后,我们就有了一个需要扁平化的具有多索引的分组数据集。

我们还想处理一些列标题的格式。当我们写到 Excel 时,我们希望标题是漂亮的,所以我们现在可以处理它了。

最后,我们将分组的数据集合并回工人数据集,以获得工人姓名和团队。

按员工分类的汇总统计数据—按作者分类的图片

我们将数据集形状打印为双重检查,以确保我们对数据集的操作和合并是正确的。我们可以看到我们有 5000 行,并且确信这是正确的,因为 worker 数据集也有 5000 行。

下一步是为每个团队将数据分割成单独的数据帧。

首先,我们需要一个独特的团队名称列表。我们可以使用unique()来寻找唯一的名字,而不是硬编码这些名字。这将有助于防止将来数据发生变化——我们的代码不会崩溃!

接下来,我们创建一个空字典,用于存储团队数据帧。

最后,我们遍历各个团队,为每个团队创建一个新的数据帧,并将其存储在字典中,关键字是团队名称。

我们通过打印每个团队数据帧的形状来仔细检查这是否如预期的那样工作。正如所料,这些行加起来有 5000 行!

现在,我们准备将最终报告写入 Excel。

写入 Excel

我们将使用 Pandas 和 XlsxWriter 创建我们的 Excel 报表。

XlsxWriter 是熊猫用来编写 Excel 的 XLSX 格式文件的默认模块。

可以使用以下命令安装它:

pip install xlsxwriter

首先,我们创建一个变量来存储文件名和路径。

接下来,我们使用 Pandas 的ExcelWriter来编写我们的 Excel 文件。

熊猫的文档指示:“作者应该被用作上下文管理器。”非常简单,这告诉我们应该将代码包装在一个with块中,以确保我们的外部资源(这里是我们正在创建的 Excel 文件)在我们完成后关闭。

最后,遍历我们的数据帧字典,将每个数据帧写到它自己的表中。

我们现在可以在 Excel 中打开完成的报告了!

已完成的 Excel 报告—作者图片

虽然我们已经成功地将报告写到 Excel 中,并为每个团队提供了单独的表格,但它看起来并不漂亮。

让我们添加一些格式来改善该报告的外观:

我们为每列添加了下拉箭头,冻结了顶行,并为每列的宽度添加了适当的间距。

带格式的 Excel 报表-按作者排序的图像

这样看起来好多了!我们的报告现在可以分发了!

结论

在这里,我们学习了使用 Python、Pandas 和 XlsxWriter 创建 Excel 报表。

虽然我们只触及了 XlsxWriter 功能的表面,但我们能够使用 Python 创建 Excel 报表,而无需打开 Excel!

为训练机器学习模型创建基础设施

原文:https://towardsdatascience.com/creating-infrastructure-for-training-machine-learning-models-aec52cf37929

引入用于训练、跟踪和比较机器学习实验的自动管道

Firmbee.comUnsplash 上拍照

让我们想象一下下面的场景:你有一个新项目要做。对于这个项目,你需要开发一个机器学习模型,这将需要运行和训练几个实验。每个实验可能需要几个小时甚至几天,并且需要跟踪。

在开发阶段,你有自己的笔记本电脑,但是用自己的笔记本电脑进行培训和运行所有的实验是不现实的。首先,您的计算机可能没有所需的硬件,例如 GPU。第二,浪费时间。如果你可以并行运行和训练所有的实验,为什么要在一台计算机上顺序训练?

如果你平行训练,每次实验在不同的机器上进行,你如何容易地跟踪和比较他们?你如何访问训练数据?你能实时意识到任何故障以便你能够修复并再次运行它吗?

所有这些问题都将得到解答。在这篇博文中,我想描述一个解决上述所有问题的可能方案。

该解决方案结合了不同的服务,并将它们联合起来,以创建一个用于训练机器学习模型的完整管道解决方案。

在下图中,您可以找到该管道的方案。

作者图片

流水线由三个步骤组成——将代码容器化,以便我们以后能够运行它,在几台独立的机器上执行代码,以及跟踪所有的实验。

(1)集装箱化

大规模运行训练实验的最佳方式是构建一个 docker 映像。创建这种图像的一种方法是使用 詹金斯 。开源自动化服务器,支持构建、测试和部署软件。另一种方法是使用 GitHub 动作 持续集成和持续交付(CI/CD)平台,允许您自动化您的构建、测试和部署管道。这可以直接从你的 GitHub 账户上完成。

让我描述一个可能的场景——每个对我们的 git 分支的推送都会触发一个 Jenkins 管道。这个管道构建 docker 映像(除了它能做的其他事情之外),标记它,并将其存储在 docker 注册表中。

一旦我们有了这个图像,我们就可以执行它,并使用虚拟云机器大规模地训练我们的模型。

(二)执行

我们已经准备好代码,我们想开始训练。假设我们有 10 个实验要运行,我们希望并行运行它们。一种选择是使用 10 个虚拟机,使用 ssh 连接每个虚拟机,并在每个虚拟机上手动执行我们的代码。我想你明白这不是最优的。

由于我们已经在上一步中容器化了我们的应用程序,我们可以利用最流行的容器编排平台来运行我们的培训— Kubernetes 。Kubernetes 是一个开源系统,用于自动化容器化应用程序的部署、扩展和管理。这意味着,通过使用它,我们可以获得 docker 映像并大规模部署它。我们可以使用一个简单的 bash 脚本在 10 台独立的机器上运行 10 个实验。

你可以在这里阅读更多相关信息

(3)跟踪

训练数据采集

为了执行培训,我们首先需要访问我们的培训数据。最好的方法是从训练机器直接访问存储器中所需的训练文件( S3GCS 等)以及数据库中所需的表格信息。为了确定这一点,我们可能需要帮助来访问相关的凭证和秘密管理器(一个用于 API 密钥、密码、证书和其他敏感数据的安全而方便的存储系统)。一旦配置完成,我们就可以开始了。

NFS 磁盘—共享磁盘

用于培训的所有机器都连接到一个 NFS 磁盘(在我们的例子中,我们使用 GCP 的托管解决方案,称为文件存储),这是一个额外的共享磁盘,所有机器都可以对其进行读写访问。我们添加这个共享磁盘有三个主要原因—

  1. 当在不同的机器上训练时,我们不想为每台机器反复下载我们的训练数据。相反,我们可以将所有机器连接到一个共享文件存储,这是一个所有机器都可以访问的附加磁盘。这样我们可以只下载一次数据
  2. 培训模型可能需要时间,因此成本可能很高。让训练更便宜的一个选择是使用 现货实例 ,价格低得多。但是,如果需要回收计算容量以分配给其他虚拟机,您的云提供商可能会停止(抢占)这些实例。使用 Kubernetes,一旦实例停止,它会被自动替换并再次执行相同的实验。在这种情况下,我们希望从停止加载最后一个保存的检查点的地方继续训练。只有使用共享磁盘才能做到这一点,没有共享磁盘,我们将无法访问先前机器中保存的检查点。其他解决方案,如将所有检查点上传到云存储并在其上应用逻辑,也是可行的,但开销(和成本)很高。
  3. TensorBoard —使用 TensorBoard 比较不同机器的不同实验,所有信息必须写入同一父目录。我们将把所有信息写入同一个父日志目录,TensorBoard UI 将从中读取。

实验跟踪

训练模型中最重要的一点是能够跟踪训练进度并比较不同的实验。对于每个实验,我们希望保存我们使用的参数、我们训练的数据、损失值、结果等等。我们将使用这些数据进行跟踪、追踪和比较。

有许多跟踪工具。当我想要高级可视化时,我使用 MLflowTensorBoard 。它们都是以这样一种方式部署的,即所有团队都可以访问同一个实例,并看到团队的所有实验。

MLflow 是一个管理端到端机器学习生命周期的开源平台。它提供了一个简单的 API 来集成,并提供了一个很好的 UI 来跟踪和比较结果。

TensorBoard 提供了机器学习实验所需的可视化和工具。它提供了一个 API,将我们的信息记录到机器磁盘上的文件中。TensorBoard UI 从传递的输入目录中读取这些文件。

错误警报

由于培训可能需要时间、几个小时甚至几天,因此如果出现错误,我们希望得到实时通知。一种方法是将我们的代码与 Sentry 集成,将 Sentry 与 Slack 集成。这样,代码中出现的每个错误都将作为事件发送给 Sentry,并触发 Slack 通知。

现在,我可以实时收到错误通知。这不仅减少了我监视运行的需要(不管它们是崩溃了还是还在运行),而且还减少了错误发生和触发下一次运行之间的时间,代码是固定的。

摘要

当我第一次训练模型时,是在我的笔记本电脑上完成的。很快,它就不可伸缩了,所以我使用 ssh 转移到虚拟机上。这种选择也有缺陷。对于每台机器,我不得不登录、提取代码、下载数据、手动运行代码,并希望我没有混淆我传递的参数。数据部分是最容易解决的,我们为我的所有虚拟机添加了一个共享磁盘。但我仍然对参数感到困惑,并在几台机器上传递了相同的参数。正如我在这篇文章中所描述的,是时候使用可扩展的、更加自动化的基础设施了。与 DevOps 团队一起,我们定义并构建了我们今天拥有的强大渠道。现在,我有了一个可伸缩、易于执行的管道,甚至在每次出现错误时都发送一个 Slack 通知。这种流水线不仅由于实验的并行性而节省了时间,而且最大限度地减少了参数错误和由于错误而导致的空闲时间,除非主动检查每台机器,否则我们不会知道这些错误。

https://www.docker.com/ https://www.jenkins.io/ https://kubernetes.io/ https://mlflow.org/ https://www.tensorflow.org/tensorboard https://cloud.google.com/ https://sentry.io/welcome/ https://docs.github.com/en/actions

使用 Python 创建交互式地理空间可视化

原文:https://towardsdatascience.com/creating-interactive-geospatial-visualisation-75a2dfaf7e59

从提取几何数据到创建交互式图表—逐步指南

德尔菲·德拉鲁阿在 Unsplash 上拍摄的照片

背景

最近,在澳大利亚的新南威尔士州,许多地理位置被政府宣布为新冠肺炎传播的关注区域,与其他区域相比,记录了相对更多的病例。

当时(大约在 2021 年 7 月),澳大利亚针对新冠肺炎采取了一种传播最小化策略。因此,对这些受关注地区的居民实施了更严格的公共卫生命令和限制(“封锁措施”),这意味着更少的人被允许离开家园进行非必要的活动(如外出就餐),提供非必要服务的当地企业(如咖啡馆和餐馆)被迫暂时关闭。这影响了消费,并对人们的收入产生了流动效应。请注意,这也可以推广到其他有严格封锁措施的地区,如 2021 年 9 月的整个维多利亚州,或 2021 年的一些欧洲国家。

从人寿保险公司(本文作者是该公司的精算师)的角度来看,锁定措施可能会间接导致人寿保险保单的投保人因家庭可支配收入减少而停止向人寿保险公司续保(“终止”)。

为此,我的公司委派我了解封锁措施是否对澳大利亚某些地理位置的中断率有影响。要做到这一点,没有比在地图上可视化地理区域的中断率更好的方法了,这是我意识到对一个人来说,实施提取几何数据的工作流程,用正确的软件包在 Pyhon 中应用可视化,并最终创建一个综合的同时显示所需信息的交互式图表是多么困难。

鉴于上述动机,本文提供了一步一步的指南,以创建一个交互式图表,显示在悉尼按地理区域的中断率。虽然这个用例是用澳大利亚的几何数据呈现的,但它可以很容易地扩展到世界上的其他地理区域。

中止及其重要性

在澳大利亚,零售市场上的寿险保单主要是由我们称之为金融顾问的中介机构销售的。金融顾问会因成功销售而获得初始佣金。

大多数保单都是以高额初始佣金售出的。在 2018 年之前,这可能会远远超过首年保费的 110%。这种佣金结构通常被称为“预付”结构,这意味着在保单有效期内支付的大部分佣金在保单生效时支付。

如果投保人在出售后不久(比如一两年内)终止保单,人寿保险公司将遭受损失,因为它无法收回支付给财务顾问的初始佣金。事实上,人寿保险公司通常需要 5-7 年的时间才能从保单中赚钱。这就是为什么终止保险很重要,而且大多数公司都投入大量资金来留住现有的投保人。

提取几何数据

澳大利亚地图可以通过各种数字边界划分成地理区域。一个例子是将澳大利亚地图按州界分成九个州。同样,澳大利亚的地图可以用其他类型的边界划分成更多的自然区域。下图分别显示了以州界和地方政府区域(“ LGA ”)为界划分的澳洲地图。

图 1:澳大利亚的州界。图片作者。

图 2:LGA 边界旁的澳洲。图片作者。

前面提到的宣布为关注区的地理位置是在 LGA 一级宣布的。对于悉尼的居民来说,你可能熟悉下面显示的一些地方政府。

表 3: LGA 的例子。按作者分类的表格

几何数据通常以 json 或 shapefile 格式提供。这些都是由澳大利亚政府机构发布的。出于本文的目的,我将提取新南威尔士的 LGA 的几何数据,这些数据可通过 LGA 数字边界在这里获得。

可以使用 Python geopandas 包读取几何数据,如下所示:

## path_LGA_geo refers to the path to the json file downloaded 
## from the link abovedf_LGA_geo = geopandas.read_file(path_LGA_geo) ## In practice, we'll only need two fields from the dataframe,  
## namely the locality name, denoted by 'nsw_lga__3', and the 
## geometry arrays, denoted by 'geometry'df_LGA_geo_final = df_LGA_geo[['nsw_lga__3','geometry']].drop_duplicates(subset ="nsw_lga__3",keep = False, inplace = False)

如果你没有太多的计算资源,你可以按照这个链接中的说明选择缩小 json 或 shapefile。

按地理区域提取中断率

在高层次上,中止率可以被校准为中止的保单数量与有效保单总数之比。由于我们跟踪已中止和有效的投保人,中止率可以在邮政区域级别(即通过邮政编码)进行校准。

然而,与 LGA 相比,邮政区域代表了不同类型的数字边界。幸运的是,澳大利亚统计局在这本出版物中提供了邮政区域和 LGA 之间的地图。通过下载 ASGS 通信(2016),可以在“CA _ POSTCODE _ 2018 _ LGA _ 2018 . xlsx”文件中找到该映射。

我顺便提一下,同一出版物中还提供了数字边界的其他映射,如统计区域到 LGA、统计区域到地区(即郊区)等。这为用户提供了在不同粒度级别切割几何数据的选项。

一旦邮政编码被映射到新南威尔士州的 LGA,就可以通过地方政府机构汇总中止的保单数量和有效的保单数量,并通过地方政府机构校准中止率。然后,这些可以以表格格式存储,如下所示。请注意,出于本文的目的,显示的中断率是合成的(尽管受到实际范围的限制)。

表 4:示例数据帧。按作者分类的表格

然后,可以使用下面的 Python 代码将数据帧与几何数据帧连接起来:

## Joining geometry data with discontinuance rate data
## df_LGA_lapse is a dataframe containing discontinuance rate by 
## LGAsdf_geo_disc_merge = df_LGA_geo_final.merge(df_LGA_lapse,left_on = 'nsw_lga__3', right_on = 'LGA_Name')

创建可视化

我将使用 Python 包来创建可视化。关于这个包的文档使用,请参考这个 Github 页面

还有其他可视化工作所需的包。如果你正在使用 Google Colab,你可以安装和导入如下的包。

图 5:必需的包。作者图片

然后可以通过以下四(4)个步骤创建交互式可视化。

第一步:获取中心坐标

这是为了确保观想“打开”在被绘制的集体地理区域的中心,在这种情况下,是新南威尔士州的中心,也就是澳大利亚地图右下角附近的州。

# Obtain center cordinates of the mapx_center = df_geo_disc_merge.centroid.x.mean()y_center = df_geo_disc_merge.centroid.y.mean()

第二步:创建地图的基础图层

## Create the base layernsw_disc_map = folium.Map(location = [y_center, x_center], zoom_start = 6, tiles = None)folium.TileLayer('OpenStreetMap', name = "Actl Map", control = True).add_to(nsw_disc_map) nsw_disc_map

上面的 Python 代码创建了地图的基础图层,该图层在中心坐标处打开。这个基础图层有一个“OpenStreetMap”的图块样式,如下图所示。您可以根据 Folium 文档在这里使用样式选项定制图块集(并搜索图块图层类)。

图 6:树叶地图的基础层。作者图片

第三步:将 LGA 的中止率添加到基础层

可以使用下面的 Python 代码将 LGA 的中断率图层添加到地图的基础图层上。

set_quantile = (df_geo_disc_merge['Discontinuance_Rate'].quantile((0.0, 0.2, 0.4, 0.6, 0.8, 1.0))).tolist()nsw_disc_map.choropleth(geo_data = df_LGA_geo_final,name = 'Choropleth',data = df_geo_disc_merge,columns=['LGA_Name','Discontinuance_Rate'],key_on = 'feature.properties.nsw_lga__3',fill_color = 'YlGn',fill_opacity = 0.6,line_opacity = 0.3,threshold_scale = set_quantile,legend_name = 'Discontinuance Rate by LGA',smooth_factor = 0,highlight = True) nsw_disc_map

如下图所示,在添加的图层中引入了一些不错的功能。即:

  • LGA 的数字边界已经在新南威尔士州绘制出来,每个 LGA 都由一块以绿色突出显示的土地表示。
  • 中断率以 5 个分位数的色标显示(代码中有 set_quantile 自定义),由地图右上角显示的不同绿色色标表示。

图 7:地图中 LGA 图层的中断率。作者图片

步骤 4:创建工具提示交互

让我们通过添加工具提示层来使地图具有交互性。想法是,当您将鼠标光标悬停在地图上的特定 LGA 上时,将会出现一个工具提示,并显示有关 LGA 的某些信息,在本例中为中断率。

这可以通过以下 Python 代码来实现。

## Add a tooltip layer add_tooltip = folium.features.GeoJson(df_geo_disc_merge, tooltip = folium.features.GeoJsonTooltip(fields=['LGA_Name', 'Discontinuance_Rate'],aliases = ['LGA_Name: ' ,'Disc_Rate: '],))nsw_disc_map.add_child(add_tooltip)nsw_disc_map.keep_in_front(add_tooltip)nsw_disc_map

下面的短片展示了本练习的最终结果。

视频 8:交互式地图演示。作者提供的视频

您可以通过自定义样式和高亮功能来进一步设置地图的样式,这些功能可以嵌入到工具提示层中。互动 Choropleth 地图教程是一个学习的好地方。

工具提示上有更多!

实际上,您可以在工具提示上显示更多信息,只要您可以在与地图相同的位置级别上收集信息(在本例中为 LGA)。例如,我在工具提示中添加了收入数据,如 LGA 的收入者的计数、中值年龄、平均值和平均收入,如下图所示。收入数据可以从这里获得。

此外,我设置的不透明度让我可以看到地图顶层下面的郊区,这是另一个很好的功能。

图 9:带有输入数据工具提示。作者图片

作为一名精算师,可视化之后可以进行一些按地理区域划分的收入与中止率的分析,这可能证明是有见地的。

我能想到的其他使用案例包括:

  • 将此与自然风险数据(如干旱、森林火灾、地震或与洪水相关的地区)叠加,以确定公司的投资组合受影响的程度,并告知公司是否要向受影响的投保人提供保费减免。
  • 通过地理区域(可能还有财务顾问)可视化销售数据来发现机会。

结论

我将几何数据视为半非结构化数据的一种形式,与文本和图像等其他非结构化数据一样,对其进行导航并最终将其可视化在地图上可能很困难。

在本文中,我提供了一个逐步指南,介绍如何使用 Python follow 包创建交互式地图。请注意,虽然本文中讨论的用例是在澳大利亚的环境中,但是您可以很容易地将其扩展到其他管辖区域,就像替换澳大利亚的几何数据一样简单。

对于对保险行业的其他数据科学应用感兴趣的读者,请关注我,并愉快地阅读我在媒体上的文章。

参考

[1]澳大利亚统计局(2021) 新南威尔士州地方政府区域—地理景观行政边界 ABS 网站,2022 年 1 月 14 日访问,(在https://creativecommons.org/licenses/by/4.0/获得许可)

[2]澳大利亚统计局(2021)【ASGS 地理通讯(2016) ABS 网站,2022 年 1 月 14 日访问,(在 https://creativecommons.org/licenses/by/4.0/获得许可)

[3]澳大利亚统计局(2021) 澳大利亚个人收入 ABS 网站,2022 年 1 月 15 日访问,(https://creativecommons.org/licenses/by/4.0/许可)

创建电影分级模型第 3 部分:测试候选模型算法

原文:https://towardsdatascience.com/creating-movie-rating-model-part-3-testing-out-model-algorithm-candidates-612c7cf480ce

尝试五种二元分类和回归算法来支持我们的电影分级模型项目

朋友们好!是的,我们已经连续第二天在我们的电影分级模型系列中有了新的条目。正如我在昨天的帖子中所说的,我仍然在以博客的形式玩着我迄今为止作为 YouTube 直播系列所做的事情。在那篇文章中,我们介绍了从原始系列文章中收集的原始数据集,并执行了一个特征工程来筛选一些干净的特征。如果你想进一步了解,你可以在这个 GitHub 库中找到所有这些工作和支持数据。GitHub 项目的自述文件解释了这个项目的所有内容,所以我建议你去看看,而不是在这里重复。(我想我刚刚创下了一个段落中超链接数量的个人记录!)

既然我们的数据已经进行了适当的特征工程,我们准备开始测试一些不同的机器学习模型算法候选,以查看哪一个将在本系列的下一篇文章中最适合创建正式训练的模型。请记住,我们实际上将为这个项目创建两个模型。这是因为电影评论家卡兰·比恩(Caelan Biehn)为他的电影提供了两个评分。第一个分数更像是一个“是或否”的二元评级,第二个分数是一个介于 0 和 10 之间的数字分数。后者被称为“比恩等级”,可以用一个小数位来表示,例如,电影可以得到 4.2 或 6.9 的评级。因为有两个不同的分数,我们将为二进制“是或否”分数训练一个二进制分类 算法,为 Biehn 标度分数训练一个回归算法

因为我们希望创建尽可能精确的模型,所以我们将为二元分类和回归模型尝试五种不同的算法。在我们开始测试各种算法之前,让我们为如何继续这篇文章制定建模策略。

建模策略

虽然我们已经注意到,我们将测试每种算法中的五种,但是在执行建模时,我们还需要做一些特定的活动。这些事情包括以下内容:

  • 超参数调整:为了确保每个算法都以最佳状态运行,我们将执行超参数调整,为每个模型寻找理想的超参数。具体来说,我们将使用 Scikit-Learn 的 GridSearchCV 工具来帮助我们。
  • K 倍验证:因为我们将要训练的数据集相对较小(大约 125 条记录),所以我们不能像对待通常较大的数据集那样进行典型的训练测试分割。因为我们想最有效地利用我们的数据集,所以我们将使用 Scikit-Learn 的 k 倍验证。这个过程会将数据集分成几个小的训练和验证批次,这将发生多次。该过程的输出将允许我们最大程度地评估数据集。(如果你想了解更多关于 k 倍验证的知识,我最近发表了一篇不同的文章正是关于这个话题!)
  • 指标验证:对于经过训练的模型,我们希望通过将它们与适当的验证指标进行比较,来确保它们能够有效地执行。我们将在下一节详细讨论验证指标。
  • 特征缩放(可选):根据我们使用的算法,我们可能需要也可能不需要对数据集执行特征缩放。为了执行这种缩放,我们将使用 Scikit-Learn 的 StandardScaler 对数据执行基本的标准化。

现在,我们已经在一个高层次上制定了我们的策略,让我们更多地讨论验证指标,然后继续创建一些帮助函数,使测试五个算法中的每一个都变得非常容易。

模型验证指标

确保任何模型按预期执行的基础是,我们必须为每种不同类型的算法定义一些模型验证度量。对于二元分类和回归模型,我们将使用三种不同的度量来验证每个模型的性能。所有这些验证指标都将在 Scikit-Learn 内置函数的帮助下进行计算。

对于我们的二元分类算法,下面是我们将测试的模型验证指标:

  • 准确性:在这个列表的所有指标中,这个指标显然是最基本的。归根结底,这是我最关心的指标,但是这个验证指标本身并不能说明全部情况。为了全面了解模型是否正常运行,我们需要其他验证指标的帮助。
  • ROC AUC :这个指标的全称其实是“受试者工作特征曲线/曲线下面积”自然,它经常被缩写为 ROC AUC,因为全称很拗口。这个指标的作用是计算算法在一条曲线上多个不同阈值下的性能,我们可以从技术上把这条曲线画在图上。一旦计算出这条曲线,我们就可以用曲线下面积(AUC)来为这个模型的表现给出一个分数。
  • F1 :如上所述,准确性本身并没有多大帮助。我可以做的是计算模型的精确度和召回率,或者我可以通过计算 F1 分数让生活变得简单一点。F1 评分发现了精确性和回忆性评分之间的和谐,从而产生单一评分。

对于回归算法,我们将计算以下三个验证指标:

  • 平均绝对误差(MAE) :对于每个数据点,此验证度量计算观察点离模型生成的回归线有多远。回归线和观察点之间的距离通常被称为误差,因为一些观察点可能低于回归线,这意味着我们可能会有负误差。为了抑制这种情况,我们取误差的绝对值,然后取所有这些绝对值的平均值来产生我们的单个 MAE 分数。
  • 均方根误差(RMSE) :这个验证指标与上面的非常相似,除了我们不是取误差的绝对值,而是取误差的平方。这些误差的平均值也将被平方,如果我们愿意,我们可以停下来计算均方误差(MSE)。因为我不是特别喜欢 MSE 不代表实际误差,所以我要取 MSE 的平方根,这自然会产生 RMSE。
  • R 平方:也称为“决定系数”,这个验证度量计算模型输出相对于模型输入的可解释性。值为 1 的 R 平方得分意味着模型输入完美地解释了模型输出,而接近 0 的数字意味着模型输入不能很好地解释输出。是的,R 平方度量可以是负的,这意味着当试图解释模型输出时,模型输入是一种倒退。请记住这一点,因为这个特定的指标将在这篇文章的结尾非常有说服力。

助手功能

因为我们将测试许多不同的算法,所以我将创建两个可重用的辅助函数:一个用于二进制分类算法,另一个用于回归算法。这些函数将做一些事情,如特征缩放(根据需要),执行超参数调整,用理想的超参数训练模型,用 k-fold 验证来验证训练的模型,并打印出来自每个 k-fold 训练的平均验证度量。

(因为这些是大块的代码,这里有一个友好的提醒,你可以在这个 Jupyter 笔记本中找到这些相同的功能。)

以下是二进制分类算法的辅助函数:

作者创建的图像和代码

这是回归算法的辅助函数:

作者创建的图像和代码

唷!这些是一些很大的函数,但是好消息是,你将会看到这两个函数将会使测试每一个算法变得多么容易。说到这里,让我们继续讨论我们将要测试的不同算法。

候选模型算法

如上所述,我们将为每种不同类型的模型测试五种不同的算法。让我们简单地谈谈每个候选人将要参加的选拔。

(M1 Mac 用户请注意:对于这两种类型的模型,我想尝试一下 Catboost 库中的一些算法,因为我的同事已经发现了这些算法的许多成功之处。不幸的是,Catboost 库还不能在基于 M1 的 Mac 电脑上运行。因为我在 M1 Mac mini 和微软 Surface Pro 8 之间工作,所以我必须在我的 Surface 上测试 Catboost 算法。对不起,M1 用户!)

二元分类候选

  • Scikit-Learn 的逻辑回归算法:虽然名字中的“回归”可能具有欺骗性,但逻辑回归是一种非常简单但功能强大的二元分类算法。因为我们想要测试各种算法类型,所以我们选择 Scikit-Learn 的逻辑回归算法作为更简单的变体。
  • Scikit-Learn 的高斯朴素贝叶斯(GaussianNB)算法:朴素贝叶斯算法最流行的实现,我们将测试 Scikit-Learn 的 GaussianNB 实现,看看它在我们的数据集上表现如何。
  • Scikit-Learn 的支持向量机(SVM)算法:虽然不像逻辑回归算法那么简单,但 SVM 是一种更简单的算法。这种算法往往在更高维度(即具有更多特征的数据集)中表现更好,尽管我们的数据集维数更少,但我仍然认为它值得一试。
  • Scikit-Learn 的随机森林分类器算法:这是 ML 行业中最流行的二进制分类算法之一。这是因为它经常产生非常准确的结果,并且具有更容易的算法解释能力。随机森林分类器也是被称为集合模型的经典例子。
  • CatBoost 的 CatBoostClassifier 算法:你可能以前没有听说过这个算法,但它在我的财富 50 强公司的同事中非常流行。这是因为它经常被证明可以提供最佳的性能结果。因此,很自然地,我想看看它与我的数据相比如何!

回归算法候选

  • Scikit-Learn 的线性回归:就像我们用二元分类器分析的逻辑回归算法一样,这可能是我们可以测试的回归算法的最简单实现。鉴于它的简单性,我显然不抱太大期望,但无论如何它总是值得一试!
  • Scikit-Learn 的 Lasso 回归:这个算法和上面的算法是一个家族,Lasso 实际上是一个缩写,代表最小绝对选择收缩算子。坦白地说,我并不精通这个算法背后的数学,所以我甚至不打算解释它。😂
  • Scikit-Learn 的支持向量回归机:类似于我们如何尝试二进制分类模型的支持向量分类器,我们将在这里看到支持向量回归机的表现。因为支持向量机通常计算特征之间的距离,所以这里的特征将需要被适当地缩放。
  • Scikit-Learn 的随机森林回归:就像我们对二元分类模型使用随机森林分类器一样,我想在这里尝试一下它的回归变体,特别是因为这被认为是一个集合模型。
  • CatBoost 的 CatBoostRegressor :最后,就像我们对前面提到的一些变体所做的那样,我想尝试这个最终算法作为第二个集合选项。

当我们使用上面的特殊助手函数时,我们可以非常简单地用几行简短的代码就能完成每一个候选函数。例如,下面是我需要为逻辑回归算法运行的所有附加代码:

*# Setting the hyperparameter grid for the Logistic Regression algorithm*
logistic_reg_params **=** {
    'penalty': ['l1', 'l2'],
    'C': np**.**logspace(**-**4, 4, 20),
    'solver': ['lbfgs', 'liblinear']
}*# Instantiating the Logistic Regression algorithm object*
logistic_reg_algorithm **=** LogisticRegression()*# Feeding the algorithm into the reusable binary classification function*
generate_binary_classification_model(X **=** X,
                                     y **=** y,
                                     model_algorithm **=** logistic_reg_algorithm,
                                     hyperparameters **=** logistic_reg_params)

看看助手函数是如何让测试所有这些算法变得如此容易的?我停在了五种不同的算法上,但是因为助手函数使它变得如此简单,如果我愿意,我可以非常容易地添加额外的候选算法。但是正如您将在我们的下一节中看到的,这并不是真正必需的。

模型验证结果

因为这个帖子已经有点过时了,所以我打算鼓励你去看看这个 Jupyter 笔记本看看具体的结果。此外,我不打算在这里进入每个算法的性能的本质细节,因为每个算法都或多或少地表现相同。当然,有些人比其他人过得好,但差别几乎可以忽略不计。让我们更多地讨论每种模型类型的验证结果。

二元分类验证结果

就准确性而言,每一个算法都有 78%或 79%的准确率。同样,每一个算法的 F1 分数也相当不错,要么是 87%,要么是 88%。ROC AUC 是事情变得有趣的地方,原因有二。首先,ROC AUC 得分在这里变化最大,与准确性和 F1 相反。算法之间的差异不是 1 点,而是 8 点。但是第二点和坏消息 ROC AUC 分数对每一个算法来说都很糟糕。它们在 50%和 58%之间。我并不特别惊讶,我会在即将到来的结论中解释我的不惊讶。

回归验证结果

哦,天啊…我们回归候选人的情况很糟糕。虽然与二元分类算法相比,我们有更多的结果分布,但结果并不太好。对于 MAE,我们看到的平均误差约为 1.6,对于 RMSE,我们看到的平均误差约为 2.0。请记住,我们讨论的是 0 到 10 分之间的分数范围,所以这个误差相当大。甚至谈论 R 平方分数都让我感到痛苦…记住,零或负的验证分数真的很糟糕,而且…嗯,我们看到几个候选人的分数是负的。在最好的情况下,一些算法几乎没有超过零。呀。

最终候选人选择和结论

好吧,伙计们…这并不像我希望的那样好!诚然,这只是一个帮助我们更好地了解数据科学概念的有趣项目,老实说,结果并没有让我感到惊讶。影评人卡兰·比恩(Caelan Biehn)并不是一个严肃的影评人,所以他给电影打出一些非常怪异的分数并不罕见。例如,他可能会给一部电影二进制“是的,去看这部电影”,但随后会给它一个像 1.3 这样极低的比恩评分。请记住,卡兰给出的评论是喜剧播客的一部分,所以荒谬有助于喜剧!此外,我正在处理的这个数据集非常小,目前只有大约 125 条记录(评论)。如果我有一个更大的数据集,也许我们会看到更强的信号。

那么,未来我们将采用什么样的候选算法呢?对于二进制分类模型,我们将使用 Scikit-Learn 的随机森林分类器算法。这是在这个和逻辑回归算法之间的一次掷硬币,但是在一天结束时,每次我重新运行网格搜索和模型训练/验证时,随机森林分类器仍然表现得更好一些。我们也可以使用 Catboost 分类器,但正如你在帖子前面看到的,显然 CatBoost 还不能在 M1 的 Mac 电脑上使用。鉴于我计划继续在我的 M1 Mac mini 上进行正式的模型训练和推理的直播,我不打算使用 Catboost,因为它根本无法在那台计算机上工作。

对于回归模型,我们将使用 Scikit-Learn 的 Lasso 回归算法。同样,我们看到所有回归模型的结果非常相似,但我们看到验证指标之间最大的差异是 R 平方得分。当然,所有的回归算法在 R 平方得分方面都表现得非常糟糕,但 Lasso 回归算法在我每次重新运行模型训练和验证时都表现得更好一些。(如果我完全诚实的话,我有点高兴 Lasso Regression 挤掉了他们,因为我有点希望那个算法“赢”,因为奇妙的电视节目 Ted Lasso 。😂)

这篇文章到此结束!即使我们的模型在以后的推理中不会很棒,我仍然认为这个项目为了学习的目的是值得继续的。在本系列的第 4 部分中,我们将创建一个正式的模型训练管道,它还捆绑了所有适当的特性工程,以将所有这些转换序列化为两个 pickle 文件。即使这里的结果很糟糕,我仍然希望你学到了一些东西,并且一路上玩得开心。下期帖子再见!

创建电影分级模型第 4 部分:创建完整的模型培训渠道

原文:https://towardsdatascience.com/creating-movie-rating-model-part-4-creating-a-full-model-training-pipeline-d52f54fd03c4

将我们的特征工程和模型训练代码正式化为单一管道

作者创作的标题卡

你好,朋友们!我们回到创建电影分级模型系列的第 4 部分。如果你想看这个系列之前的帖子,请查看这个链接,你也可以在我的个人 GitHub 库找到整个项目的代码。简单回顾一下我们停下来的地方,我们之前的帖子测试了许多不同的算法候选项,用作我们的最终模型。提醒一下,我们在技术上构建了两个模型:一个预测电影评论者 Caelan Biehn 给出的二元“是/否”电影评级,另一个预测评论者以基于回归的模型形式给出的 0-10 分。

在测试了许多不同的算法后,我们决定使用 Scikit-Learn 的 RandomForestClassifier 用于二元分类模型,使用 Scikit-Learn 的 Lasso 回归用于回归模型。现在,我们已经准备好创建一个正式的模型培训管道。现在,如果到目前为止您已经了解了这个系列,那么我们主要是在 Jupyter 笔记本上完成我们的工作。因为理想情况下,我们希望将这样的训练脚本集成到类似 MLOps 管道的东西中,所以最好将它转换为 Python 文件。

让我们假设,当我们在将来收集用于推理的数据时,在将数据输入模型之前,在运用特征工程来清理数据之前,它将看起来是相同的原始格式。如果是这种情况,我们可以利用 Scikit-Learn 库提供的一个特殊的 管道 对象。这个 Scikit-Learn 管道本质上允许您做的是将一组事物链接在一起,如数据工程和模型推理,在一个单一的无缝管道中。只需插入您的原始数据,并在另一边得到一个推论,而无需在中间忙乱数据工程!

我知道我们这样做已经有一段时间了,但是你还记得我们为执行特征工程而创建的所有自定义函数吗?我们将再次使用所有这些文件,为了方便起见,我将它们都放在了 this helpers.py 文件中。这是我们写的其中一个函数的例子:

作者截图

因为我们需要对几乎所有的原始特征执行特征工程,所以我们需要在实际使用 Scikit-Learn Pipeline 对象之前做一些事情。也就是说,我们需要建立一个column transformer,它也是由 Scikit-Learn 库提供的。让我继续向您展示代码,我们将在下面解释它在做什么,因为它非常忙。

作者截图

好吧,尽管这里有很多东西,但实际上很容易理解。基本上,我们正在定义如何正确地转换每一列。在以ohe_engineering开始的行中,我们使用一个 OneHotEncoder 对象对右边的特征primary_genresecondary_genre进行一次热编码。每个转换器有 3 个部分:转换的名称(您可以随意称呼它),需要进行什么转换,以及在哪些列上执行转换。

在我们上面提到的ohe_engineering例子中,我们使用了一个“默认的”OneHotEncoder 对象来执行简单的一键编码。但是如果我们想使用我们自己的自定义函数呢?正如你在其他每个转换器中看到的,我们正是在 Scikit-Learn 的 FunctionTransformer 的帮助下做到这一点的。看看以movie_age_engineering开头的那一行。请注意,我们在中间有这段代码:

FunctionTransformer(generate_movie_age, validate = False)

这正是使用我们在上面分享的第一个截图中定义的函数。同样,我们在每个其他转换器中使用我们各自的定制函数,并针对适当的原始列运行它们。非常酷!在继续之前,还要注意最后一个 transformer 是一个默认的columns_to_drop功能,正如您所猜测的,它删除了不需要的特性。此外,确保将remainder参数设置为passthrough,以确保任何其他已经可以从原始状态进入另一端的列。如果这个remainder参数设置不正确,默认行为是删除任何没有明确提到的特性。对我们来说不理想!

好了,我们的 ColumnTransformer 一切就绪,我们现在准备实例化并利用 Scikit-Learn 管道对象。到目前为止,我们已经完成了繁重的工作,所以从这里开始,一切都是下坡路了!请记住,我们在这里有两个单独的模型要训练,所以您在这里基本上会看到双倍的代码。对于这些模型中的每一个,以下是实例化管道对象并执行训练的代码:

作者截图

正如您所看到的管道实例化,实际上集成模型算法作为最终的管道步骤与我们在不使用管道的情况下实例化算法的方式完全相同。此外,请注意,当我们根据训练数据训练管道时,我们会调用与模型算法对象完全相同的fit()函数,而不使用管道。同样,当我们稍后使用这些模型来执行推理时,我们将对经过训练的管道使用相同的predict(),就像我们对任何独立的经过训练的算法一样。不错!

说到推理,我们将在稍后使用这些经过训练的管道,所以让我们用下面的代码将它们导出到序列化的 pickle 文件中:

作者截图

如果你想知道我为什么使用cloudpickle来执行序列化,这是因为我的新 Mac mini 采用了新的苹果硅芯片,与标准的pickle库和 Scikit-Learn 的joblib都不完全匹配。在做了一些研究之后,我发现cloudpickle库工作得很好,它在我的 Windows 笔记本电脑上也工作得很好。

(如果你想看看完整的、有凝聚力的培训脚本是什么样子,请查看这个 train.py 文件。)

这个帖子到此为止!我们还没有完成这个系列。在接下来的几篇文章中,我们将把这些经过训练的模型转换成一个简单的网页,并带有后端 API,允许人们插入电影标题来生成预测的电影评论分数。敬请关注这些帖子,并感谢您的阅读!🎬

创建回归模型以预测数据响应

原文:https://towardsdatascience.com/creating-regression-models-to-predict-data-responses-120b0e3f6e90

学习从头开始创建和编写回归模型,以预测结果

凯利·西克玛在 Unsplash 上的照片

回归分析可以描述为一种统计技术,用于在给定一个或多个自变量(预测因子或特征)值的情况下,预测/预报因变量(响应)的值。回归被认为是监督机器学习的一种形式;这是一种建立数学模型以确定输入和期望输出之间关系的算法。可以生成的回归模型数不胜数,所以让我们先介绍一下回归数学,然后跳到三个常见的模型。

回归数学

在我们看任何代码之前,我们应该了解一点回归模型背后的数学。如前所述,回归模型可以有多个输入变量或特征,但是对于本文,为了简单起见,我们将使用单个特征。回归分析包括猜测哪种类型的函数最适合您的数据集,是直线函数、n 次多项式函数还是对数函数等。回归模型假设数据集遵循以下形式:

这里, xy 是我们在观察点 i 的特征和响应, e 是误差项。回归模型的目标是估计函数 f ,使其最接近数据集(忽略误差项)。函数 f 是我们对哪种类型的函数最适合数据集的猜测。当我们看到例子时,这将变得更加清楚。为了估计函数,我们需要估计 β 系数。最常见的方法是普通最小二乘法,它可以最小化数据集和函数之间的误差平方和。

最小化平方误差将导致我们的 β 系数的估计,标记为 β -hat。可以使用由普通最小二乘法得出的正规方程来获得估计值:

反过来,我们可以使用这些估计的 β 系数来创建我们的响应变量 y 的估计值。

这里的 y -hat 值是一个观察值 i 的预测值。通过使用我们估计的 β 系数和特征值 x 来生成预测。

为了确定我们的回归模型表现如何,我们可以计算 R 平方系数。此系数评估回归模型预测周围数据集的分散程度。换句话说,这是回归模型与原始数据集的拟合程度。通常,该值越接近 1.0,拟合度越好。

为了正确评估您的回归模型,您应该比较多个模型(函数, f )的 R 平方值,然后决定使用哪个模型来预测未来值。让我们开始编码,以便更好地理解回归模型的概念。

导入库

对于这些示例,我们将需要以下库和包:

  • NumPy 用于创建数值数组(为方便调用,定义为 np
  • NumPy 中的 random 用于在函数中创建噪声,以模拟真实世界的数据集
  • 来自 matplotlibpyplot 用于显示我们的数据和趋势线
# Importing Libraries and Packages
import numpy as np
from numpy import random
import matplotlib.pyplot as plt

创建和显示测试数据集

为了创建数据集,我们可以创建一个作为我们的特征的x数组和一个作为我们的响应的y数组。y数组是一个任意指数函数。使用随机包,我们将在我们的数据中引入噪声来模拟某种真实世界的数据。

# Creating Data
x = np.linspace(0, 4, 500)  # n = 500 observations
random.seed(10)  # For consistent results run-to-run
noise = np.random.normal(0, 1, x.shape)
y = 0.25 * np.exp(x) + noise

# Displaying Data
fig = plt.figure()
plt.scatter(x, y, s=3)
plt.title("Test Data")
plt.xlabel("x")
plt.ylabel("y")
plt.show()

以下是我们应用回归分析的数据集:

测试数据[由作者创建]

让我们花点时间来看看这个情节。我们知道使用了一个指数方程来创建该数据,但是如果我们不知道该信息(如果您正在执行回归分析,您就不会知道),我们可能会查看该数据并认为二次多项式最适合数据集。然而,作为最佳实践,您应该评估几个不同的模型,看看哪一个性能最好。然后,您将使用最佳模型来创建您的预测。现在让我们分别来看几个回归模型,看看它们的结果如何比较。

线性回归

正如在数学部分提到的,对于回归分析,你猜测一个模型,或函数。对于线性模型,我们的函数采用以下形式, f :

下一步是为估计我们的 β 系数的正规方程(在数学部分)创建矩阵。它们应该如下所示:

使用我们的数据集,我们估计的 β 系数以及线性回归模型将为:

# Linear Regression
X = np.array([np.ones(x.shape), x]).T
X = np.reshape(X, [500, 2])

# Normal Equation: Beta coefficient estimate
b = np.linalg.inv(X.T @ X) @ X.T @ np.array(y)
print(b)

# Predicted y values and R-squared
y_pred = b[0] + b[1] * x
r2 = 1 - sum((y - y_pred) ** 2)/sum((y - np.mean(y)) ** 2)

# Displaying Data
fig = plt.figure()
plt.scatter(x, y, s=3)
plt.plot(x, y_pred, c='red')
plt.title("Linear Model (R$^2$ = " + str(r2) + ")")
plt.xlabel("x")
plt.ylabel("y")
plt.legend(["Data", "Predicted"])
plt.show()

生成的回归模型如下所示:

测试数据[由作者创建]

正如图的标题所示,线性回归模型的 R 平方值约为 0.75。这意味着线性模型符合数据,但如果我们收集更多的数据,我们可能会看到 R 平方的值大幅下降。

多项式回归

多项式回归的一般形式如下:

本文将函数 f 的形式显示为二阶多项式。您可以以类似的方式添加更多的多项式项来满足您的需求;但是,要警惕过度拟合。

创建正规方程的矩阵给出如下:

使用这些矩阵,估计的 β 系数和多项式回归模型将为:

# Polynomial Regression
X = np.array([np.ones(x.shape), x, x ** 2]).T
X = np.reshape(X, [500, 3])

# Normal Equation: Beta coefficient estimate
b = np.linalg.inv(X.T @ X) @ X.T @ np.array(y)
print(b)

# Predicted y values and R-squared
y_pred = b[0] + b[1] * x + b[2] * x ** 2
r2 = 1 - sum((y - y_pred) ** 2)/sum((y - np.mean(y)) ** 2)

# Displaying Data
fig = plt.figure()
plt.scatter(x, y, s=3)
plt.plot(x, y_pred, c='red')
plt.title("2$^{nd}$ Degree Poly. Model (R$^2$ = " + str(r2) + ")")
plt.xlabel("x")
plt.ylabel("y")
plt.legend(["Data", "Predicted"])
plt.show()

由此产生的情节:

测试数据[由作者创建]

请注意,二次多项式在匹配数据集方面做得更好,其 R 平方值约为 0.91。然而,如果我们看数据集的末尾,我们会注意到模型低估了这些数据点。这应该鼓励我们研究其他模型。

指数回归

如果我们决定不采用多项式模型,我们的下一个选择可能是以下形式的指数模型:

同样,创建法线方程的矩阵给出如下:

现在,有了估计的 β 系数,回归模型将是:

请注意,指数项的领先系数与模拟数据领先系数非常匹配。

# Exponential Regression
X = np.array([np.ones(x.shape), np.exp(x)]).T
X = np.reshape(X, [500, 2])

# Normal Equation: Beta coefficient estimate
b = np.linalg.inv(X.T @ X) @ X.T @ np.array(y)
print(b)

# Predicted y values and R-squared
y_pred = b[0] + b[1]*np.exp(x)
r2 = 1 - sum((y - y_pred) ** 2)/sum((y - np.mean(y)) ** 2)

# Displaying Data
fig = plt.figure()
plt.scatter(x, y, s=3)
plt.plot(x, y_pred, c='red')
plt.title("Exponential Model (R$^2$ = " + str(r2) + ")")
plt.xlabel("x")
plt.ylabel("y")
plt.legend(["Data", "Predicted"])
plt.show()

最终的数字:

测试数据[由作者创建]

请注意,与二次多项式模型相比,R 平方值有所提高。更有希望的是,数据集的末端比二次多项式更适合此模型。给定这些信息,我们应该使用指数模型来预测未来的 y 值,以获得尽可能精确的结果。

文章到此结束。希望这能让您了解如何在 Python 中执行回归分析,以及如何在现实场景中正确使用它。如果你学到了什么,给我一个关注,看看我关于空间、数学和 Python 的其他文章!谢谢大家!

为人工智能平台创建路线图

原文:https://towardsdatascience.com/creating-roadmap-for-internal-ai-platform-product-in-an-enterprise-ce60b77df25f

AI 平台团队的产品路线图应该考虑哪些?

灰色滚柏油路下多云的天空。照片由皮查拜派克斯拍摄。

组织的技术战略目标主要通过其技术平台来实现。这些产品的路线图对于获得组织内关键利益相关者的认可至关重要。因此,路线图对于将技术战略引向正确的方向至关重要。

路线图在高层次上描述了团队实现其产品愿景的方式。它可以用许多不同的方式来表现,比如看板、幻灯片、excel 文件等。,显示时间线上的详细信息。

人工智能平台产品路线图

一个组织的人工智能愿景需要具有不同能力的平台产品。本组织需要以下平台

  • 应用研究
  • 代码版本控制、持续集成和持续部署
  • 数据摄取、功能管理
  • 模型版本控制,模型管理
  • 大规模在线/批量培训
  • 大规模在线/批量服务

通常情况下,组织无法为每一项功能提供单独的产品团队。相反,一个团队选择在一个人工智能平台产品下解决其中的几个功能,该产品结合了供应商管理/提供的以及自行开发的解决方案。这种产品的路线图定义了如何在可预见的时间内实现产品的最终愿景,并指示团队如何组合漏斗以实现愿景。

打算使用这样一个平台的人工智能团队是客户。参与批准预算和承担 AI 平台开发和维护成本的组织内的团体是其利益相关者。其他团队,如数据平台团队,其路线图受其路线图影响,也是其利益相关方

路线图的质量

为人工智能平台产品提出一个好的路线图是很棘手的,特别是当多个团队使用的产品可能在功能上没有明确的界限,并且将购买和开发的解决方案结合在一起时。我们举几个例子来说明下面的复杂性。

问题 1:产品路线图与组织 AI 愿景不一致

通常,提供人工智能平台解决方案的技术供应商,例如可扩展的笔记本电脑环境,正在向团队追加销售/推广他们的功能,以确保他们的未来。同时,团队成员也对新的平台范例和展示/提高他们的技能感到兴奋。当平台通过这些漏斗的目标与组织的人工智能愿景相匹配时,这就是天作之合。然而,团队的目标与组织的目标不一致,很难理解为什么事情会在团队的路线图中。这样的团队获得了太多不必要的想法,面临着对特性和里程碑的大量怀疑,在最坏的情况下,不得不将产品束之高阁。

问题 2:产品输出未能交付客户成果

对于一个人工智能平台产品团队来说,当它有太多的产出,很少或根本没有为客户的人工智能雄心提供什么时,这是一个不好的迹象。这种情况经常发生,因为平台团队不清楚他们的交付如何影响他们客户的底线。在这些情况下,团队观察到实现里程碑对他们的人工智能驱动的业务和客户满意度的 KPI 没有什么影响。

问题 3:团队几乎没有时间去学习什么是有效的

通常情况下,客户和利益相关者会强迫或推动平台产品团队完成一个紧凑的特性交付时间表。然而,找出高影响力特性的道路通常并不简单。相反,团队需要尝试一些东西,并从中学习,直到他们发现什么是真正有效的。致力于详细的特性交付,迫使团队专注于单个解决方案轨道。这太冒险了,因为这样的策略对团队来说是一个障碍,而尝试可能有助于团队找出适当的细节。

问题 4:客户和利益相关者对路线图不感兴趣

有时,人工智能团队会推迟使用平台的新功能。此外,尽管源于组织人工智能愿景的人工智能团队稳步增长,但平台中客户的采用率很低。人工智能团队也避免挑战路线图或在适当的时候向平台团队提供反馈,即使这样做,他们也太迟了。另一方面,涉众可能会羞于提升平台团队的能力,即使他们这么做了,他们也是针对旧的特性。这些症状表明,客户和利益相关者都对平台产品的路线图不感兴趣。

确保良好的路线图

为了提供良好的路线图,平台产品团队应该考虑以下几点:

  1. 团队应该将平台的路线图与组织的关键人工智能愿景联系起来。这要求团队中的每个人都应该意识到组织的宏观愿景,并尽可能经常地讨论它。
  2. 平台团队应该专注于为人工智能团队取得成果,而不仅仅是交付功能。一旦一个平台团队掌握了它的全局,它就不应该进入解决方案的细节,即使这对团队来说是显而易见的。相反,团队应该关注人工智能团队的高层次价值交付。
  3. 平台团队不应该为了说服涉众和客户而致力于不切实际的、雄心勃勃的和不必要的特性。它应该强调结果的重要性并坚持下去。利益相关者和客户不应该规定实现结果的方法。
  4. 平台团队不应该选择太容易的议程,并且在组织的人工智能愿景中产生很小的影响。同样,它应该避免采取没有长期持续时间的速赢措施。

人工智能平台路线图框架

像大多数软件产品一样,人工智能平台产品路线图的良好框架应该包含以下组件:

  • 愿景:一旦平台产品完全实现,人工智能团队将如何从中受益?
  • 时间框架:按时间顺序排列的交付里程碑。每个时间段的持续时间可能不同,时间越早,持续时间越短。
  • 结果:如果组织中的人工智能团队在给定的时间框架内使用平台产品中已交付的功能,他们会有什么可测量的不同?
  • 主题:里程碑期间要实现的成果所涉及的共同主题。
  • 风险:哪些问题会导致无法在规定时间内交付成果?
  • 免责声明:可能改变路线图的警告。

在下图中,我们展示了一个 AI 平台产品的路线图示例。框架的内容并不那么重要。更重要的是把内容作为理解作品设计的素材。

人工智能平台产品路线图示例。图片由作者提供。

评论

路线图有多种形状和形式。你的产品路线图遵循什么样的设计?你同意或同意以上陈述吗?让我知道你的想法。

用微软行星计算机创建哨兵 2 号(真正的)无云镶嵌图

原文:https://towardsdatascience.com/creating-sentinel-2-truly-cloudless-mosaics-with-microsoft-planetary-computer-7392a2c0d96c

使用 GEE 的 S2 无云图层,在微软行星计算机上有效地遮蔽云(和云的阴影)

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

介绍

在地理空间领域,微软行星计算公司是久负盛名的谷歌地球引擎的有力竞争者。无需下载每张图像即可访问数 Pb 卫星信息的可能性以及由 Dask clusters 提供的计算能力是开发区域/全球应用和研究的一大变革。行星计算机仍处于预览阶段(需要访问请求),但使用其计算中心与开源工具如 XARRAYSTAC 的可能性是优于谷歌专有 API 的一个优势(IMHO)。

另一个优点是,我们不必像在 GEE 中那样受大小约束的困扰,大小约束会阻止我们轻松地访问 Numpy 数组格式的值。好吧,我知道 GEE 的概念是不同的,这意味着迫使人们在服务器端运行计算(使用惰性数组的概念),但有时直接访问数据更容易。在 Planetary Computer 中,甚至可以使用“自制”下载器直接从微软的目录中下载全部资产(更多内容将在以后的文章中介绍)。

不利的一面是,数据集目录不像在谷歌的竞争对手那里找到的那样广泛。在处理光学(哨兵 2 号)影像时,有一件事真的很重要:云层遮罩。

在微软的官方无云镶嵌教程(这里是)中,他们陈述了以下内容:“在云是短暂的假设下,合成图像不应该包含(许多)云,因为它们不应该是许多图像在该点的中间像素值。”。那是…呃…不完全是我们所期待的。在 L2A S2 影像中总会有场景分类图层——SCL 图层,但之前用过 Sen2Cor 的人都知道这不是精度最好的。 Baetens et al. (2019) 在这个课题上带来了全面的比较。

这又把我们带回了我们的故事:如果我们运行在微软的行星计算机上,我们如何才能有效地从哨兵 2 的图像中去除云。正如在行星计算机范例 GitHub 知识库中提到的(这里是这里是)这是他们正在做的事情,但是在那之前…

S2 无云算法

S2 无云包是 Sinergise 开发的机器学习云检测算法,在 GitHub 上有售(此处)。欧空局正在使用这种算法,它最近作为一张云概率图被列入了 GEE 的目录。因此,我们可以直接从 GEE 访问概率图,而不是为每个场景运行训练好的算法。这些问题是:

  • 我们想使用行星计算机,但我们不在地球环境中!而且,
  • 云影呢?

为了解决前面提到的两点,我结合使用了 geeS2Downloader 包(关于这个故事的更多信息这里)和 GEE 的页面上提供的教程 Sentinel-2 Cloud Masking with S2 Cloud less(这里)。该教程显示了如何根据太阳方位角投影云阴影,以找到实际的地面阴影,geeS2Downloader 包使从 GEE 下载资源更容易,克服了其大小限制。如果看起来太复杂,让我们来看看完整的解决方案…

解决方案

让我们首先从微软行星中心的一个空笔记本开始,安装依赖项。在这种情况下,我们将需要两个未预装在 PC(行星计算机)中的软件包和一个用于更快可视化的可选软件包:

  • earthengine-api:从谷歌地球引擎访问资产。
  • geeS2Downloader:从 GEE 下载资产。
  • Geemap:来自吴秋生教授的伟大软件包,它的最新版本也可以在微软的个人电脑上运行。

初始化后,我们将通过 TILE_ID日期搜索特定的图块。为此,我们将使用一个名为search_tiles的函数。

<Item id=S2B_MSIL2A_20181214T133219_R081_T22KFV_20201008T100849>

然后,我们需要在 GEE 中得到相应的云概率图。另外,为了投射阴影,我们也需要完整的 S2 图像。为此,我们将创建一个名为get_gee_img 的新函数,它将负责在给定 STAC 商品的情况下定位 GEE 目录中的图像。

现在是时候看看我们用 Geemap 下载的图片了:

代码输出。图片作者。

正如我们所看到的,云被正确识别,但我们仍然需要摆脱阴影。为此,我们将创建一个受 GEE 教程启发的函数create_cloud_mask。它将扩大最终蒙版与 50 米的缓冲区,并重新调整到 20 米的分辨率。

{'type': 'Image',
 'bands': [{'id': 'cloudmask',
   'data_type': {'type': 'PixelType', 'precision': 'int', 'min': 0, 'max': 1},
   'dimensions': [5490, 5490],
   'crs': 'EPSG:32722',
   'crs_transform': [20, 0, 600000, 0, -20, 7500040]}]}

正如我们可以从代码的输出中看到的,掩码似乎是用 20m 正确创建的,正如预期的那样。要在 geemap 上显示它,我们只需使用以下命令添加该层:

Map.addLayer(mask.selfMask(), {'min': 0, 'max': 1, 'palette': ['orange']}, 'Final Mask', True, 0.5)

这是最终的蒙版输出。请注意,我们不仅有云彩,还有阴影(橙色)。

代码输出。图片作者。

将掩模下载到 PC

现在我们已经在 GEE 中处理了遮罩,是时候下载它了,看看它是否正确地符合我们的原始图像。为此,我们将使用 geeS2Downloader 包。这个软件包将根据 GEE 的限制自动分割蒙版并重建最终的矩阵。

代码输出。图片作者。

显示结果

现在,让我们显示最终结果,以与 PC 中的图像进行比较。代替 geemap ,我们将使用普通的 Matplotlib 来完成这个任务。

代码输出。图片作者。

放大输出。图片作者。

结论

正如我们在这个故事中看到的,将谷歌地球引擎和微软行星计算机的资产结合起来,从两个平台中提取精华是可能的。虽然微软的个人电脑不包括可靠的云端层,但这是我为了继续我的项目而创建的一个变通办法。在未来,我预计这种变通办法将不再是必要的。直到那里,希望它能帮助我们中的一些人!

谢谢,下一个故事再见!

保持联系

如果你喜欢这篇文章,并想继续无限制地阅读/学习这些和其他故事,考虑成为 中等会员 。你也可以在 https://cordmaur.carrd.co/查看我的作品集。

https://cordmaur.medium.com/membership

参考

Baetens,l .,Desjardins,c .,Hagolle,o .,2019。使用有监督的主动学习程序生成的参考云掩膜对从 MAJA、Sen2Cor 和 FMask 处理器获得的哥白尼 Sentinel-2 云掩膜的验证。遥感 11433。https://doi.org/10.3390/rs11040433

创建合成数据

原文:https://towardsdatascience.com/creating-synthetic-data-3774391c851d

使用模型来提供数据中心性

我以前写过关于合成数据的博客。实际上是两次。我第一次使用荷兰癌症登记处的合成数据进行生存分析。第二次我使用相同的数据集来应用规格曲线分析

合成数据将在未来几年发挥关键作用,因为模型变得越来越复杂,但受到可用数据的限制。一段时间以来,以模型为中心的运动正慢慢被更以数据为中心的观点所取代,随着时间的推移,模型将再次占据中心舞台。

合成数据实际上是两个世界的结合,还处于起步阶段。它的数据,而是我们习惯于看到和看待数据的方式。对于大多数研究人员来说,数据来自实地观察或传感器。合成数据是模型数据。本质上,它的数据伪装成其他数据,其中敏感组件被删除,但关键组件保留。如果成功,建模者甚至不应该知道数据是合成的。或许一个很好的类比是描绘你站在蒙娜丽莎面前的卢浮宫,但它不是真正的蒙娜丽莎。真正的蒙娜丽莎藏在地下室,你看到的是一个戒备森严的复制品。

合成数据是关于尊重原始的本质,而不是原始的。从这一点出发,合成数据甚至可以更多。可以成为现实生活中很少会出现的模型的训练素材,但是可以作为模拟素材分享给大家。因为数据失去了敏感性,但没有失去价值。

在这个模块中,我要做的是获取 R 中的数据集,即钻石数据集,并从中创建几个模型来建立合成数据。当然,钻石数据集已经开放,因此它没有敏感信息,但是这个练习的本质是在不重新创建数据集的情况下重新创建数据集的本质。

我使用 diamonds 数据集的原因是它有足够的行和列,但没有直接和明确的关系。

让我们开始吧。

rm(list = ls())
options("scipen"=100, "digits"=10)
#### LIBRARIES ####
library(DataExplorer)
library(ggplot2)
library(dplyr)
library(tidyr)
library(ggExtra)
library(skimr)
library(car)
library(GGally)

library(doParallel)
cl <- makePSOCKcluster(5)
registerDoParallel(cl)

加载完库之后,我想立即查看数据本身。如果我要对数据建模,以识别其本质(通过交叉相关),并从本质上复制它,那么我必须牢牢掌握数据集。为此,我将首先查看协方差和相关矩阵。

skimr::skim(diamonds)
str(diamonds)
diamonds$price<-as.numeric(diamonds$price)
diamonds%>%
  dplyr::select(where(is.numeric))%>%
  cor()
diamonds%>%
  dplyr::select(where(is.numeric))%>%
  corrgram::corrgram()
diamonds%>%
  dplyr::select(where(is.numeric))%>%
  cor()%>%
  ggcorrplot::ggcorrplot(., hc.order = TRUE, type = "lower",
             lab = TRUE)

diamonds 数据集有许多行,但只有几列。这应该会使建立模型变得更容易,但是关系并不明确。图片作者。

我将首先从数值开始,因为这是最容易的开始。这并不意味着我们不能为序数或名词性数据重建合成数据。然而,非连续数据的相关矩阵需要一些额外的变换,我们现在不考虑这些变换。

所有数值变量的相关矩阵。图片作者。

和三种类型的相关矩阵。在左侧,您可以看到一个附有树状图的热图。树状图是一种涉及相关性的聚类技术。因此,将它们结合起来是很简单的。正如你所看到的,有一些严重的相关性和一些部分几乎是分离的。图片作者。

我们甚至可以创建更奇特的图,但是 R 在绘制所有 53k 行时会遇到一点问题。因此,我将首先选择一个 1000 行的随机样本,然后绘制每个数值的分布及其潜在的相关性。

diamonds%>%
  dplyr::select(where(is.numeric))%>%
  sample_n(., 1000)%>%
  scatterplotMatrix()

这种分布应该是多峰值的,这很有趣。此外,变量之间的关系有时是极端的,有时是不存在的(你也可以称之为极端)。图片作者。

而且,有一个很好的方法可以使用 GGally 库来绘制每个特定类别(这里是 cut )的相关性和分布。

diamonds%>%
  dplyr::select(x, y, z, carat, depth, table, price, cut)%>%
  sample_n(., 1000)%>%
  ggpairs(.,
  mapping = ggplot2::aes(color = cut),
  upper = list(continuous = wrap("density", alpha = 0.5), 
               combo = "box_no_facet"),
  lower = list(continuous = wrap("points", alpha = 0.3), 
               combo = wrap("dot_no_facet", alpha = 0.4)),
  title = "Diamonds")

图片作者。

如果我们要基于只包含数值的原始数据重新创建数据集,我们需要立即考虑需要注意的两个主要问题:

  1. 每个变量的汇总统计和分布需要尽可能接近原始数据,但不需要完全相同。这意味着需要维护每个变量的结构。如果不是这样,数据集的描述部分就不能使用。造型部分仍可保留。
  2. 数据点之间的协方差/相关性需要相同。这意味着需要维护变量之间的潜在关系。这对造型部分至关重要。

从数据中提取协方差/相关矩阵并不困难,但正如我们所说,标准公式只适用于数值。尽管如此,我们可以用数字值来开始我们的创作,让这个想法继续下去。

这里你可以看到协方差矩阵。

diamond_cov<-diamonds%>%dplyr::select_if(., is.numeric)%>%cov()

                 carat           depth           table             price
carat    0.22468665982   0.01916652822    0.1923645201     1742.76536427
depth    0.01916652822   2.05240384318   -0.9468399376      -60.85371214
table    0.19236452006  -0.94683993764    4.9929480753     1133.31806407
price 1742.76536426512 -60.85371213642 1133.3180640679 15915629.42430145
x        0.51848413024  -0.04064129579    0.4896429037     3958.02149078
y        0.51524781641  -0.04800856925    0.4689722778     3943.27081043
z        0.31891683911   0.09596797038    0.2379960448     2424.71261297
                     x                y                z
carat    0.51848413024    0.51524781641    0.31891683911
depth   -0.04064129579   -0.04800856925    0.09596797038
table    0.48964290366    0.46897227781    0.23799604481
price 3958.02149078326 3943.27081043196 2424.71261297033
x        1.25834717304    1.24878933406    0.76848748285
y        1.24878933406    1.30447161384    0.76731957995
z        0.76848748285    0.76731957995    0.49801086259

从这个过程中,我们获得了协方差,但我们不能创建数据。为此,我们还需要可以很容易获得的汇总统计数据。

diamonds%>%dplyr::select_if(., is.numeric)%>%summary()

     carat               depth             table              price         
 Min.   :0.2000000   Min.   :43.0000   Min.   :43.00000   Min.   :  326.00  
 1st Qu.:0.4000000   1st Qu.:61.0000   1st Qu.:56.00000   1st Qu.:  950.00  
 Median :0.7000000   Median :61.8000   Median :57.00000   Median : 2401.00  
 Mean   :0.7979397   Mean   :61.7494   Mean   :57.45718   Mean   : 3932.80  
 3rd Qu.:1.0400000   3rd Qu.:62.5000   3rd Qu.:59.00000   3rd Qu.: 5324.25  
 Max.   :5.0100000   Max.   :79.0000   Max.   :95.00000   Max.   :18823.00  
       x                   y                   z            
 Min.   : 0.000000   Min.   : 0.000000   Min.   : 0.000000  
 1st Qu.: 4.710000   1st Qu.: 4.720000   1st Qu.: 2.910000  
 Median : 5.700000   Median : 5.710000   Median : 3.530000  
 Mean   : 5.731157   Mean   : 5.734526   Mean   : 3.538734  
 3rd Qu.: 6.540000   3rd Qu.: 6.540000   3rd Qu.: 4.040000  
 Max.   :10.740000   Max.   :58.900000   Max.   :31.800000 

从这些组合中,我们应该能够使用各种程序重建数据。也许最直接的方法是使用多元正态分布,它存在于质量包中。我只需要每个变量的平均值和相关矩阵。多元正态分布将完成剩下的工作。为了获得相等的比较,我将创建与原始数据集一样多的观察值。

sigma<-diamonds%>%dplyr::select_if(., is.numeric)%>%cor()%>%as.matrix()
mean<-diamonds%>%dplyr::select_if(., is.numeric)%>%as.matrix()%>%colMeans()
df<-as.data.frame(MASS::mvrnorm(n=dim(diamonds)[1], mu=mean, Sigma=sigma))
> dim(df)
[1] 53940     7
> head(df)
           carat       depth       table       price           x           y
1 -1.34032717822 61.49447797 57.19091527 3931.162855 3.545669821 3.931061364
2 -0.47751648630 61.16241371 57.86509627 3931.306295 4.906696688 5.057863929
3  2.24358594522 63.09062530 56.73718104 3932.957682 7.386140807 7.405936831
4 -0.03108967881 60.99439588 57.58369767 3931.677322 5.194041948 5.431802322
5  1.16179859890 62.39235813 57.96524508 3933.322044 6.213609464 6.592872841
6 -0.16757252197 60.84783867 56.68337288 3932.501268 4.987489939 5.118558015
            z
1 1.138732182
2 2.596622246
3 5.674154202
4 3.089565271
5 4.387662667
6 2.577509666

创建完成后,接下来的任务是应用两种方法检查数据的有效性和可用性:

  1. 检查单变量特征。
  2. 检查多元特征。

有了 ggpairs 函数,我可以两者兼而有之,并初步了解该过程及其生成的数据的适用性。

diamonds%>%dplyr::select_if(., is.numeric)%>%ggpairs()
ggpairs(df)

左边是原始数据集,右边是从原始数据中提取平均值和相关矩阵的模拟数据。当然,合成数据的分布遵循正态分布。数据之间的相关性得到了维护,但是汇总统计数据肯定没有得到维护(除了平均值)。图片作者。

现在,我们已经说过,我们的目的是构建合成数据,这意味着构建本质上相同的数据,但在前景上不一定相同。我们已经实现了这个目标。我将使用 caret 包在两个数据集上构建一个快速模型,看看合成数据是否会给我与原始数据集完全相同的结果。

diamonds_num<-diamonds%>%dplyr::select_if(., is.numeric)
trainIndex <- caret::createDataPartition(diamonds_num$carat, 
                                         p = .6, 
                                         list = FALSE, 
                                         times = 1)
> wideTrain <- diamonds_num[ trainIndex,];dim(wideTrain)
[1] 32366     7
> wideTest  <- diamonds_num[-trainIndex,];dim(wideTest)
[1] 21574     7

fitControl <- caret::trainControl(
  method = "repeatedcv",
  number = 20,
  repeats = 20)

lmFit1 <- caret::train(carat ~ ., 
                        data = wideTrain, 
                        method = "lm",
                        trControl = fitControl,
                        verbose = FALSE)
> summary(lmFit1)

Call:
lm(formula = .outcome ~ ., data = dat, verbose = FALSE)

Residuals:
        Min          1Q      Median          3Q         Max 
-0.54124488 -0.03836279 -0.00665401  0.03530248  2.71983984 

Coefficients:
                    Estimate       Std. Error   t value               Pr(>|t|)    
(Intercept) -2.5273956761070  0.0294158001479 -85.91966 < 0.000000000000000222 ***
depth        0.0188998891767  0.0003766002370  50.18555 < 0.000000000000000222 ***
table        0.0046678986326  0.0002266381898  20.59626 < 0.000000000000000222 ***
price        0.0000330063643  0.0000002519416 131.00798 < 0.000000000000000222 ***
x            0.2956915579921  0.0032315175272  91.50238 < 0.000000000000000222 ***
y            0.0130670340617  0.0028968529147   4.51077            0.000006482 ***
z           -0.0026197077889  0.0025495576103  -1.02751                0.30419    
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.0844222 on 32359 degrees of freedom
Multiple R-squared:  0.9684143, Adjusted R-squared:  0.9684085 
F-statistic:   165354 on 6 and 32359 DF,  p-value: < 0.00000000000000022204

以上是基于原始数据的简单线性回归的总结。下面是基于合成数据的简单线性回归的总结。是的,程序并不完全相同,根据训练和测试数据的选择,以及反复的交叉验证,可以预期会有一些变化,但是程序的本质是看数据的本质是否被保留。

trainIndex <- caret::createDataPartition(df$carat, 
                                         p = .6, 
                                         list = FALSE, 
                                         times = 1)
> wideTrain <- df[ trainIndex,];dim(wideTrain)
[1] 32364     7
> wideTest  <- df[-trainIndex,];dim(wideTest)
[1] 21576     7

fitControl <- caret::trainControl(
  method = "repeatedcv",
  number = 20,
  repeats = 20)
lmFit2 <- caret::train(carat ~ ., 
                        data = wideTrain, 
                        method = "lm",
                        trControl = fitControl,
                        verbose = FALSE)
summary(lmFit2)

Call:
lm(formula = .outcome ~ ., data = dat, verbose = FALSE)

Residuals:
        Min          1Q      Median          3Q         Max 
-0.68929576 -0.11680934  0.00078743  0.11741046  0.74064948 

Coefficients:
                   Estimate      Std. Error    t value               Pr(>|t|)    
(Intercept) -1079.584562077     8.210331042 -131.49099 < 0.000000000000000222 ***
depth           0.052707918     0.001155147   45.62874 < 0.000000000000000222 ***
table           0.019422785     0.001035408   18.75859 < 0.000000000000000222 ***
price           0.272537301     0.002088731  130.47987 < 0.000000000000000222 ***
x               0.708827904     0.006118409  115.85167 < 0.000000000000000222 ***
y               0.015186425     0.004344156    3.49583             0.00047322 ***
z               0.007592425     0.004694642    1.61725             0.10583335    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.1736463 on 32357 degrees of freedom
Multiple R-squared:  0.9697885, Adjusted R-squared:  0.9697829 
F-statistic: 173109.8 on 6 and 32357 DF,  p-value: < 0.00000000000000022204

一目了然的是截距的大小发生了变化,这是因为合成数据具有相同的均值,但分布不同。了解数据的另一种方法是查看试图预测钻石克拉的模型中每个预测值的方差重要性。

> lm1Imp;lm2Imp
lm variable importance

         Overall
price 131.007979
x      91.502384
depth  50.185548
table  20.596258
y       4.510769
z       1.027515

         Overall
price 130.479871
x     115.851671
depth  45.628736
table  18.758588
y       3.495829
z       1.617253

似乎这些模型确实具有相同的可变重要性,并且它们之间的距离似乎被保留了下来。当然,它并不完美,但也不必如此。

了解合成数据可用性的第二种方法是查看累积分布图。累积密度图是显示变量分布形式的另一种方式,但在这种方式中,如果两个数据集显示相同的本质,就会立即变得清楚。

重要的是要认识到,没有单一的方法来确定合成数据是否保持了与原始数据相同的本质。

df$Model<-"Synthetic"
diamonds_num$Model<-"Original"
combined<-rbind(df, diamonds_num)
ggplot(combined, aes(carat, colour = Model)) +
  stat_ecdf()+
  theme_bw()

在这里,您可以看到我尝试使用简单的线性回归建模的结果。正如你所看到的,合成数据非常清晰,相比之下,原始数据是凹凸不平的。因此,包含相关矩阵的简单多元正态分布是不够的。图片作者。

下一步是为数据集中的每个数值变量绘制图表。

combined_long<-combined%>%tidyr::pivot_longer(!Model, 
                                              names_to = "Variable",
                                              values_to = "Value",
                                              values_drop_na = TRUE)
ggplot(combined_long, aes(Value, colour = Model)) +
  stat_ecdf()+
  theme_bw()+
  facet_wrap(~Variable, scales="free")

如你所见,累积分布函数(CDF)在原始数据和合成数据之间没有太大差异。合成数据更加原始,对于克拉,我们已经看到了某些偏差,这可能会导致原始数据和合成数据之间的分析差异。然而,这与绝对错误的价格相比根本不算什么。图片作者。

让我们深入挖掘价格并比较使用密度。我将首先绘制严重偏离的价格,然后绘制几乎相同密度结构的深度

g1<-ggplot()+
  geom_density(data=diamonds_num, aes(x=price, fill="Original"), alpha=0.5)+
  theme_bw()+
  labs(fill="")
g2<-ggplot()+
  geom_density(data=df, aes(x=price, fill="Synthetic"), alpha=0.5)+
  theme_bw()+
  labs(fill="")
gridExtra::grid.arrange(g1,g2,nrow=2)

合成数据集中价格的 cdf 看起来如此奇怪的原因是因为它必须以与原始数据相同的比例绘制。而且原版的最大值超过 15000,不像合成版停在 3950 左右。图片作者。

这种差异是构成多元正态的函数的直接结果,这意味着每个变量都有一个平均值,并且与其他函数相关。在这个特殊的例子中,平均值并不能说明全部情况。

ggplot()+
  geom_density(data=diamonds_num, aes(x=depth, fill="Original"), alpha=0.5)+
  geom_density(data=df, aes(x=depth, fill="Synthetic"), alpha=0.5)+
  theme_bw()+
  labs(fill="")

原始数据和合成数据并不相同,但确实表现出相似的特征。然而,如果从原始数据中提取汇总统计数据,并将其与合成数据进行比较,这种方法充其量也是有限的。这是因为描述性统计需要完全相同的值,这意味着分布应该完全相同。这就是综合数据集的描述部分和建模部分之间的区别。图片作者。

如果我采用质量包的多元正态分布的经验形式会怎样——这意味着样本大小会发挥更大的作用。

df_emp<-as.data.frame(MASS::mvrnorm(n=dim(diamonds)[1], 
                                    mu=mean, Sigma=sigma, empirical = TRUE))

g1<-ggplot()+
  geom_density(data=diamonds_num, aes(x=price, fill="Original"), alpha=0.5)+
  theme_bw()+
  labs(fill="")
g2<-ggplot()+
  geom_density(data=df, aes(x=price, fill="Synthetic"), alpha=0.5)+
  theme_bw()+
  labs(fill="")
g3<-ggplot()+
  geom_density(data=df_emp, aes(x=price, fill="Synthetic Emp"), alpha=0.5)+
  theme_bw()+
  labs(fill="")
gridExtra::grid.arrange(g1,g2,g3,nrow=3)

对于 53k 行数据,两个多变量正态程序之间没有实际差异,这是可以预期的。样本大小在这里不是问题。图片作者。

好的,多元正态分布确实为创建合成数据集提供了一个良好的开端,但只是从建模的角度来看。不是从描述的角度,人们需要自己决定是否有必要。数据合成的部分本质是确保本质得到保持,对于建模者来说,这意味着建模时能够得到相同的结果。

现在,创建合成数据的另一种方法(这意味着模拟相关变量)是深入到连接函数的世界中,我们现在将在某种程度上使用高斯函数。连接函数是理解和建立多元分布的连接概率的一个很好的方法。copula 这个词的意思是“链接”,这正是他们所做的。

根据维基百科,一个 copula 是:一个多元 累积分布函数 其中 边际概率 每个变量的分布是https://en.wikipedia.org/wiki/Uniform_distribution_(continuous)**均匀分布在区间[0,1]上。如果我们将这些步骤分解开来,看起来会是这样的(这是我从博客上引用的):

  1. 多元正态分布的样本相关标准化(N[0,1])分布。
  2. 用正态 CDF 将它们转换成相关的均匀(0,1)分布(概率积分转换)。
  3. 用概率分布的逆 CDF 将它们转换成你想要的任何相关概率分布(逆变换采样)。

下面是一个函数,显示了一个构建的多元正态分布函数,它等于 MASS 包的 mvnorm 函数。因此,我将从多元正态分布中获得(就像以前一样)数据,但这次是标准化的。

 *mvrnorm <- function(n = 1, mu = 0, Sigma) {
  nvars <- nrow(Sigma)
  # nvars x n matrix of Normal(0, 1)
  nmls <- matrix(rnorm(n * nvars), nrow = nvars)
  # scale and correlate Normal(0, 1), "nmls", to Normal(0, Sigma) by matrix mult
  # with lower triangular of cholesky decomp of covariance matrix
  scaled_correlated_nmls <- t(chol(Sigma)) %*% nmls
  # shift to center around mus to get goal: Normal(mu, Sigma)
  samples <- mu + scaled_correlated_nmls
  # transpose so each variable is a column, not
  # a row, to match what MASS::mvrnorm() returns
  t(samples)
}
df_new <- mvrnorm(dim(diamonds)[1], Sigma = sigma)
mean2<-rep(0,dim(sigma)[2])
names(mean2)<-colnames(sigma)
df_new2 <- MASS::mvrnorm(dim(diamonds)[1], 
                         mu=mean2, 
                         Sigma = sigma)
> cor(df_new)
              carat          depth         table          price              x              y
carat 1.00000000000  0.02548515939  0.1756632819  0.92081606682  0.97483969924  0.95123398113
depth 0.02548515939  1.00000000000 -0.2966914653 -0.01267406029 -0.02968316709 -0.03193223836
table 0.17566328191 -0.29669146532  1.0000000000  0.12037212430  0.18934326066  0.17732479479
price 0.92081606682 -0.01267406029  0.1203721243  1.00000000000  0.88323677536  0.86373468972
x     0.97483969924 -0.02968316709  0.1893432607  0.88323677536  1.00000000000  0.97460644946
y     0.95123398113 -0.03193223836  0.1773247948  0.86373468972  0.97460644946  1.00000000000
z     0.95342001221  0.09193958958  0.1450393656  0.86003163701  0.97049061902  0.95189367284
                  z
carat 0.95342001221
depth 0.09193958958
table 0.14503936558
price 0.86003163701
x     0.97049061902
y     0.95189367284
z     1.00000000000
> cor(df_new2)
              carat          depth         table          price              x              y
carat 1.00000000000  0.02401766053  0.1879023338  0.92211539384  0.97544578111  0.95144071623
depth 0.02401766053  1.00000000000 -0.3013205641 -0.01515267326 -0.02925527573 -0.03155516412
table 0.18790233377 -0.30132056412  1.0000000000  0.13488569935  0.20173295788  0.18904146957
price 0.92211539384 -0.01515267326  0.1348856993  1.00000000000  0.88524849733  0.86534901465
x     0.97544578111 -0.02925527573  0.2017329579  0.88524849733  1.00000000000  0.97451646301
y     0.95144071623 -0.03155516412  0.1890414696  0.86534901465  0.97451646301  1.00000000000
z     0.95428998681  0.09122255285  0.1570967255  0.86180784719  0.97117225153  0.95206791685
                  z
carat 0.95428998681
depth 0.09122255285
table 0.15709672554
price 0.86180784719
x     0.97117225153
y     0.95206791685
z     1.00000000000*

因此,我们首先获得的值是来自多元正态分布的标准化值。这意味着变量都在相同的尺度上,并携带原始的互相关矩阵。我们可以很容易地检查两者(标准化规模和相关性)。

*pairs(df_new)
hist(df_new[,1])*

**

左图:相关矩阵。右图:配送。都来自标准化的多元正态分布。图片作者。

下一步是转换到均匀分布,同时保持基本的互相关矩阵。

*U <- pnorm(df_new, mean = 0, sd = 1)
hist(U[,1])
cor(U)
              carat          depth         table         price              x              y             z
carat 1.00000000000  0.02617867093  0.1682925760  0.9140135553  0.97247036750  0.94681926838 0.94931607318
depth 0.02617867093  1.00000000000 -0.2837151691 -0.0102210807 -0.02657673258 -0.02879604409 0.08933946454
table 0.16829257601 -0.28371516908  1.0000000000  0.1160484259  0.18057212310  0.16815896730 0.13787984049
price 0.91401355528 -0.01022108070  0.1160484259  1.0000000000  0.87409215149  0.85353166518 0.84964860456
x     0.97247036750 -0.02657673258  0.1805721231  0.8740921515  1.00000000000  0.97228684415 0.96785128673
y     0.94681926838 -0.02879604409  0.1681589673  0.8535316652  0.97228684415  1.00000000000 0.94769217826
z     0.94931607318  0.08933946454  0.1378798405  0.8496486046  0.96785128673  0.94769217826 1.00000000000*

均匀分布矩阵中的第一个变量是克拉,现在也是均匀分布的。保持与所有其他数据的互相关。图片作者。

我们可以对不同的变量做同样的处理,比如价格。下面,你会看到我构建了新的价格克拉的变量,但这次我是从泊松分布中对它们进行采样。这是三步中的最后一步,我可以通过逆变换采样,从包含均匀分布数据的矩阵 U 中创建任何我想要的分布。这是一个相当酷的技术!

*price <- qpois(U[, 4], 5)
par(mfrow = c(2, 1))
hist(price)
hist(diamonds_num$price)

carat <- qpois(U[, 1], 30)
par(mfrow = c(2, 1))
hist(carat)
hist(diamonds_num$carat)*

**

原始分布和我用 copula 做的分布。图片作者。

*> cor(diamonds_num$carat, diamonds_num$price)
[1] 0.9215913012
> cor(carat, price)
[1] 0.9097580747*

如您所见,这种相关性在一定程度上得以保持。出现偏差的原因是,离散数据的相关性(泊松分布)与连续数据的相关性(高斯分布)不同。

我们现在可以看看建模部分。我现在不使用 carat,而是选择一种更直接的方法来确保训练和测试集的采样以及交叉验证不会碍事。下面是两个简单的线性回归。

*fit1<-lm(carat~price, data=diamonds_num)
fit2<-lm(carat~price, data=data.frame(cbind(carat,price)))
fit1;fit2

Call:
lm(formula = carat ~ price, data = diamonds_num)

Coefficients:
 (Intercept)         price  
0.3672972042  0.0001095002  

Call:
lm(formula = carat ~ price, data = data.frame(cbind(carat, price)))

Coefficients:
(Intercept)        price  
  18.887067     2.224347* 

很明显,系数不同,但这是意料之中的,因为我建立了不同的描述符。

*par(mfrow = c(2, 4))
plot(fit1);plot(fit2)*

模型拟合当然也有差异——上述四个图的矩阵来自原始数据,这些数据并不原始。后四个图来自我创建泊松分布的连接函数。假设正常数据是错误的,使用线性回归分析离散数据。图片作者。

一个更好的测试,仍然要记住泊松数据的线性回归是错误的,是对交互作用建模。还有什么比使用花键更好的方法呢!

*depth <- qpois(U[, 2], 30)
fit1<-lm(price~ns(carat,3)*ns(depth,3), data=diamonds_num)
fit2<-lm(price~ns(carat,3)*ns(depth,3), data=data.frame(cbind(carat,price, depth)))

> fit1

Call:
lm(formula = price ~ ns(carat, 3) * ns(depth, 3), data = diamonds_num)

Coefficients:
                (Intercept)                ns(carat, 3)1                ns(carat, 3)2  
                  2218.8390                   17936.5330                 -126840.8539  
              ns(carat, 3)3                ns(depth, 3)1                ns(depth, 3)2  
               -231168.4360                   -1091.1322                    1362.9843  
              ns(depth, 3)3  ns(carat, 3)1:ns(depth, 3)1  ns(carat, 3)2:ns(depth, 3)1  
                  7416.9316                    -329.7165                   68951.7911  
ns(carat, 3)3:ns(depth, 3)1  ns(carat, 3)1:ns(depth, 3)2  ns(carat, 3)2:ns(depth, 3)2  
                118173.3027                  -19264.2274                  263279.9813  
ns(carat, 3)3:ns(depth, 3)2  ns(carat, 3)1:ns(depth, 3)3  ns(carat, 3)2:ns(depth, 3)3  
                480346.8166                  -34626.0634                   14745.8991  
ns(carat, 3)3:ns(depth, 3)3  
                 73406.4320  

> fit2

Call:
lm(formula = price ~ ns(carat, 3) * ns(depth, 3), data = data.frame(cbind(carat, 
    price, depth)))

Coefficients:
                (Intercept)                ns(carat, 3)1                ns(carat, 3)2  
               -1.148528255                  8.348102184                 17.258451783  
              ns(carat, 3)3                ns(depth, 3)1                ns(depth, 3)2  
               15.909197993                 -0.242271773                 -1.327453563  
              ns(depth, 3)3  ns(carat, 3)1:ns(depth, 3)1  ns(carat, 3)2:ns(depth, 3)1  
               -0.862625027                 -0.393382870                 -0.090925281  
ns(carat, 3)3:ns(depth, 3)1  ns(carat, 3)1:ns(depth, 3)2  ns(carat, 3)2:ns(depth, 3)2  
               -0.029317928                 -0.006505344                  0.323633739  
ns(carat, 3)3:ns(depth, 3)2  ns(carat, 3)1:ns(depth, 3)3  ns(carat, 3)2:ns(depth, 3)3  
               -1.189341372                  0.188006534                 -0.109592519  
ns(carat, 3)3:ns(depth, 3)3  
               -0.984283018* 

当然,系数是不一样的,因为数据没有标准化,但让我们看看相互作用图。人们会假设,如果数据的本质得到维护,变量之间的关系也会得到维护。

*sjPlot::plot_model(fit1, type="pred")
sjPlot::plot_model(fit2, type="pred")*

**

上面你看到的是来自原始数据的变量之间的关系,下面你看到的是来自合成数据的关系。克拉价格之间的关系似乎在某种程度上得以维持,但深度价格肯定不是。我之所以选择样条曲线,是因为它们经常被使用,并且非常依赖于数据。因此,从原始到合成的转换中的错误转折很可能会被样条曲线拾取。图片作者。

检查转换和模型有效性的一个好方法是绘制原始数据并与合成数据进行比较,因为深度和价格根本不相关。两个模型都显示了联系。

我将使用 ggplot 并在原始数据的图形中拟合一条样条线。

*ggplot(diamonds_num, 
       aes(x=depth, y=price))+
  geom_point()+
  geom_smooth()+
  theme_bw()

ggplot(diamonds_num, 
       aes(x=carat, y=price))+
  geom_point()+
  geom_smooth()+
  theme_bw()*

**

清楚地显示了数据样条拟合的问题。如你所见,价格克拉在这个二维层面上没有相关性,但样条曲线确实倾向于在中间跳动一点。在右边,我们看到克拉价格被显示出来,在它们的顶部,一条样条线首先画出了一个清晰的关系。然后,它需要一个沉重的曲线来寻找它能找到的任何点。样条的伟大和危险显示在两个图中。图片作者。

以上图为原始数据。让我们也观察一下,如果我在合成数据上绘图会发生什么,现在合成数据具有与原始数据完全不同的分布属性(离散的,而不是原始的连续标度)。

*g1<-ggplot(diamonds_num, 
       aes(x=carat, y=price))+
  geom_point()+
  geom_smooth()+
  theme_bw()
g2<-ggplot(data.frame(cbind(carat,price, depth)), 
       aes(x=carat, y=price))+
  geom_point()+
  geom_smooth()+
  theme_bw()
gridExtra::grid.arrange(g1,g2,nrow=1)

g1<-ggplot(diamonds_num, 
           aes(x=carat, y=depth))+
  geom_point()+
  geom_smooth()+
  theme_bw()
g2<-ggplot(data.frame(cbind(carat,price, depth)), 
           aes(x=carat, y=depth))+
  geom_point()+
  geom_smooth()+
  theme_bw()
gridExtra::grid.arrange(g1,g2,nrow=1)* 

**

这里我们看到了两次观察同一关系的图。我们有克拉价格,我们有克拉深度。两者似乎都坚持原来的关系,即使来自不同的分布,但它并不完美。图片作者。

看上面的图,我们可以看到大部分的原始关系(或缺失)被保持。合成数据不会完美也就不足为奇了。此外,原始数据和合成数据的模型显示不同的系数也就不足为奇了。我已经制作了数据,所以它会有不同的描述特征,即使来自不同类型的分布,但仍然能够保持它的本质。

这篇博文只是一个简短的介绍,介绍了一种构建合成数据的方法,而且只是关于数值。使用 copulas,我们可以构建许多不同类型的合成数据。此外,我们还没有冒险进入深度学习,如 GANs,它主要用于建立合成数据。这个例子表明,我们不必走那么远。

如果有什么不对劲,请告诉我!

创建真正的多页面简化应用——新方式(2022 年)

原文:https://towardsdatascience.com/creating-true-multi-page-streamlit-apps-the-new-way-2022-b859b3ea2a15

制作多页简化应用程序的最新方法

尼克·莫里森在 Unsplash 上拍摄的照片

Streamlit 是一个基于 python 的开源框架,用于开发和部署交互式数据科学仪表盘和机器学习模型。

在我之前的 Streamlit 文章中,我讨论了如何开始构建一个简单的地震数据浏览器。这是一个简单的单页 web 应用程序,允许您查看数据并生成一些图表。

构建单个页面的 Streamlit 应用程序相对简单,然而,直到最近,构建具有多个页面的 Streamlit 应用程序还需要许多变通方法。随着真正的多页简化应用程序的发布,这种情况有所改变

在本教程中,我们将介绍如何使用新技术创建一个多页面的 Streamlit 应用程序。我还发布了一个 YouTube 视频,你可以在下面看到。

教程的数据源

本教程使用的数据可以在 CC0: Public Domain 下使用,并且来自 Kaggle。这是一个数据集,包含 1965 年至 2016 年期间世界上震级为 5.5 或更高的最重大地震的数据。您可以通过以下链接查看原始数据。

https://www.kaggle.com/datasets/usgs/earthquake-database?select=database.csv

为什么要在一个 Streamlit 应用中使用多个页面?

当使用 Streamlit 构建基于 web 的应用程序时,很容易连续不断地向我们的应用程序添加部分,一个接一个。然而,这最终会生成一个页面,这个页面可能会很快变得很长。

解决这个问题的一个方法是将信息分成多页。这改善了应用程序的外观和感觉,也改善了用户体验。

创建多页面简化应用程序的老方法

直到最近 Streamlit 还没有一个官方的方法来给一个应用添加多个页面。作为变通办法,我们不得不使用单选按钮或下拉列表在我们的 Streamlit 应用程序的不同页面或部分之间切换。虽然这是一个很好的方法,但看起来确实有点“粗糙”。

以下示例说明了使用单选按钮更改应用程序中主要内容的过程。本质上,当一个按钮被选中时,旧的内容被隐藏,新的内容被添加。

使用单选按钮的多页 Streamlit 应用程序示例。图片由作者提供。

上面的例子是使用下面的代码生成的。所有代码都包含在一个 Python 文件中,然后可以通过调用来运行该文件。

streamlit run app.py

创建多页简化应用程序的新方法

【2022 年 6 月 2 日,Streamlit 宣布真正的多页面应用功能的到来。他们没有使用选择框,而是对库进行了改造,使其能够本地处理多个页面。

这是一个很大的进步,尤其是如果你是一个 Streamlit 的重度用户。

Streamlit 宣布真正的多页面功能的到来。

创建多页 Streamlit 应用程序—新方式

以下方法适用于新应用程序和现有应用程序。

要使用新功能,您需要使用以下方式更新您的 Streamlit 版本:

pip install --upgrade streamlit

在您的主 Streamlit 应用程序目录中,创建一个名为 pages 的新子目录。

在该文件夹中,开始为您想要添加到应用程序的每个页面添加新的.py文件。Streamlit 将自动检测这些文件,并开始将它们作为页面添加到应用程序中。

页面会自动按字母顺序排序。如果你想控制页面的顺序,你可以在文件的开头添加一个数字。

如果您的主要 Streamlit 应用程序 python 文件中有现有的函数/代码,您可以将它们转移到新文件中,并根据需要进行拆分。此外,如果你有代码创建单选按钮,这可以从你的主应用程序中删除。

现在你的页面已经创建好了,在你的应用文件夹中打开你的终端/命令提示符,输入如下。

streamlit run streamlit_app.py

请注意,您需要将streamlit_app.py部分更改为您的主应用程序文件的名称。

一旦您重新启动并运行了 Streamlit,您将会在应用程序的左侧看到新菜单。

默认情况下,您的主应用程序页面将位于菜单顶部。

下面是新的streamlit_app.py文件中的代码:

这是其中一页的代码示例。请注意,您确实需要为每个子页导入 streamlit。

添加页面图标

当您向 Streamlit 应用程序添加新页面时,页面的图标将全部相同。然而,你可以使用表情图标在菜单和页面标题中添加一个简单的图标,就像这样。

使用文件名中的表情符号简化多页应用程序,以生成菜单图标。图片由作者提供。

当你回到你的应用程序时,每个菜单项现在都有相同的图标。

使用页面图标简化多页面应用程序。图片由作者提供。

我不确定在 python 文件中给文件名添加表情符号有多稳定,但是时间会证明一切。

通过会话状态在 Streamlit 应用程序页面之间传递数据

需要注意的最后一点是,上面创建的每个页面都是独立运行的。换句话说,如果你在一个页面上上传了一个文件,它在另一个页面上是不可用的。

处理这个问题的一种方法是使用会话状态。

例如,如果您想使用 Pandas 数据帧,那么在文件上传器所在的.py中添加如下代码:

st.session_state['df'] = df

然后在其他.py文件中,调用该会话状态来检索数据帧,如下所示:

df = st.session_state['df']

然后,您应该能够在任何页面中处理这些数据,而不必重新加载这些数据。

摘要

Streamlit 添加一个真正的多页面功能是期待已久的改进,它确实改善了用户体验。但是,当处理多个页面时,您需要将希望在它们之间使用的数据保存到会话状态中。

感谢阅读。在你走之前,你一定要订阅我的内容,把我的文章放到你的收件箱里。 你可以在这里做!

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

用 Plotly 创建各种剧情类型和支线剧情

原文:https://towardsdatascience.com/creating-various-plot-types-and-subplots-with-plotly-bd727f808262

Python 科学绘图

使用 plotly 创建条形图、直方图、散点图、气泡图和箱线图,并用子图排列它们

卢克·切瑟在 Unsplash 上拍摄的照片

现在你已经有了一些使用plotly包的背景知识(如果你想要一个好的介绍,你可以阅读我以前写的一篇文章),你可能想要扩展一些你有的绘图选项。在这篇文章中,我将展示plotly中其他一些基本的剧情选项,以及如何利用支线剧情来设置面板人物。我们将查看条形图、直方图、散点图/气泡图和箱线图。正如我们在以前的文章中所做的那样,我们将使用plotly.graph_objects从零开始构建我们的数字。

条形图

让我们从创建一些虚拟数据来绘制条形图开始。我们可以使用numpy如下所示:

# import packages
import numpy as np# create dummy data
vals = np.ceil(100 * np.random.rand(5)).astype(int)
keys = ["A", "B", "C", "D", "E"]

现在我们可以在柱状图上绘制这些键值对。我们需要做的第一件事是使用plotly.graph_objects创建一个图形:

# import packages
import plotly.graph_objects as go# create figure
fig = go.Figure()

现在,我们将条形图添加到我们的图中,并用以下代码行显示结果。为了便于查看,我们还可以更新布局中图形的高度和宽度。

# plot data
fig.add_trace(
 go.Bar(x=keys, y=vals)
)fig.update_layout(height=600, width=600)fig.show()

条形图—作者创建的图形

让我们对绘图布局做一些修饰性的更新,以修复某些东西,如轴标签和悬停在条形上方时显示的信息。

# create figure
fig = go.Figure()# plot data
fig.add_trace(
    go.Bar(x=keys, y=vals, hovertemplate="<b>Key:</b> %{x}<br><b>Value:</b> %{y}<extra></extra>")
)# update layout
fig.update_layout(
    font_family="Averta",
    hoverlabel_font_family="Averta",
    title_text="Bar Chart of Key-Value Pairs",
    xaxis_title_text="Keys",
    xaxis_title_font_size=18,
    xaxis_tickfont_size=16,
    yaxis_title_text="Values",
    yaxis_title_font_size=18,
    yaxis_tickfont_size=16,
    hoverlabel_font_size=16,
    height=600, 
    width=600
)fig.show()

修改后的条形图—由作者创建的图表

分组和堆叠条形图

如果我们想要比较具有相同关键字的多个组的值,我们可以制作一个分组或堆叠条形图。为此,我们可以先在我们的图形中添加多个条形图,然后使用fig.update_layout()来改变条形图的模式。让我们首先制作几组虚拟数据,这样我们就可以创建分组和堆叠条形图。

# create more dummy data
vals_2 = np.ceil(100 * np.random.rand(5)).astype(int)
vals_3 = np.ceil(100 * np.random.rand(5)).astype(int)vals_array = [vals, vals_2, vals_3]

对于新的哑数据数组,我们现在遍历数组中的每个值列表,并向图中添加一个条形图。然后我们用barmode="group"更新布局,将它变成一个分组条形图。

# create figure
fig = go.Figure()# plot data
for i, vals in enumerate(vals_array):
    fig.add_trace(
        go.Bar(x=keys, y=vals, name=f"Group {i+1}", hovertemplate=f"<b>Group {i+1}</b><br><b>Key:</b> %{{x}}<br><b>Value:</b> %{{y}}<extra></extra>")
    )# update layout
fig.update_layout(
    barmode="group",
    font_family="Averta",
    hoverlabel_font_family="Averta",
    title_text="Bar Chart of Key-Value Pairs",
    xaxis_title_text="Keys",
    xaxis_title_font_size=18,
    xaxis_tickfont_size=16,
    yaxis_title_text="Values",
    yaxis_title_font_size=18,
    yaxis_tickfont_size=16,
    hoverlabel_font_size=16,
    legend_font_size=16,
    height=600, 
    width=600
)fig.show()

分组条形图—由作者创建的图表

为了把它变成一个堆积条形图,我们简单地把fig.update_layout(barmode="group")修改为fig.update_layout(barmode="stack")

堆积条形图—由作者创建的图表

直方图

绘制直方图对于理解基础数据非常有用,通过观察数据的分布,您可以了解更多关于定量趋势和异常值的信息。这些在plotly中也很简单,只需要几行代码。同样,我们从创建虚拟数据开始——我们将使用numpy.random.normal()从正态分布中取样,并将该数据绘制成直方图。

# create data
data = np.random.normal(size=1000)

现在,为了绘制一个简单的直方图,我们使用下面几行代码:

# create figure
fig = go.Figure()# plot data
fig.add_trace(
    go.Histogram(x=data, hovertemplate="<b>Bin Edges:</b> %{x}<br><b>Count:</b> %{y}<extra></extra>")
)fig.update_layout(
    height=600,
    width=600
)fig.show()

简单直方图—由作者创建的图表

正如预期的那样,直方图类似于正态分布,这是有意义的,因为这是我们从中采样的。请注意,本例中的 y 轴是每个仓中的样本数,我们也可以将其归一化为一个概率值,这与 y 轴上的计数值除以样本总数基本相同。

# create figure
fig = go.Figure()# plot data
fig.add_trace(
    go.Histogram(x=data, histnorm="probability", hovertemplate="<b>Bin Edges:</b> %{x}<br><b>Count:</b> %{y}<extra></extra>")
)fig.update_layout(
    font_family="Averta",
    hoverlabel_font_family="Averta",
    title_text="Histogram of Samples from Normal Distribution",
    xaxis_title_text="Values",
    xaxis_title_font_size=18,
    xaxis_tickfont_size=16,
    yaxis_title_text="Probability",
    yaxis_title_font_size=18,
    yaxis_tickfont_size=16,
    hoverlabel_font_size=16,
    height=600, 
    width=600
)fig.show()

概率直方图—由作者创建的图表

最后,如果我们想把我们的直方图看作一个累积分布函数而不是一个概率分布函数,我们可以如下传递参数cumulative_enabled=True:

累积直方图—由作者创建的图表

散点图和气泡图

让我们从创建一个简单的散点图开始。我们从创建一些虚拟数据开始:

# create dummy data
x = [i for i in range(1, 6)]
y = np.ceil(100 * np.random.rand(5)).astype(int)

现在,我们可以将这些数据绘制成散点图,如下所示:

# create figure
fig = go.Figure()# plot data
fig.add_trace(
    go.Scatter(x=x, y=y, mode="markers", hovertemplate="<b>x:</b> %{x}<br><b>y:</b> %{y}<extra></extra>")
)fig.update_layout(
    font_family="Averta",
    hoverlabel_font_family="Averta",
    title_text="Basic Scatter Plot",
    xaxis_title_text="x",
    xaxis_title_font_size=18,
    xaxis_tickfont_size=16,
    yaxis_title_text="y",
    yaxis_title_font_size=18,
    yaxis_tickfont_size=16,
    hoverlabel_font_size=16,
    height=600, 
    width=600
)fig.show()

基本散点图—由作者创建的图形

现在我们可以把它变成一个气泡图——气泡图只是一个散点图,通过标记的大小传达第三维信息。首先,让我们为我们的标记创建一些不同的大小。

# dummy data for marker size
s = np.ceil(30 * np.random.rand(5)).astype(int)

现在,要将上面的散点图转换成气泡图,我们需要做的就是将一个大小值数组传递给marker_size参数。如果我们想在悬停工具提示中包含大小,我们还需要创建一个text参数,并将其设置为等于我们的数组大小。然后,我们可以在创建hovertemplate时引用该文本—为了创建气泡图,我们使用了以下代码行:

# create figure
fig = go.Figure()# plot data
fig.add_trace(
    go.Scatter(x=x, y=y, mode="markers", marker_size=s, text=s, hovertemplate="<b>x:</b> %{x}<br><b>y:</b> %{y}<br><b>Size:</b> %{text}<extra></extra>")
)fig.update_layout(
    font_family="Averta",
    hoverlabel_font_family="Averta",
    title_text="Basic Bubble Chart",
    xaxis_title_text="x",
    xaxis_title_font_size=18,
    xaxis_tickfont_size=16,
    yaxis_title_text="y",
    yaxis_title_font_size=18,
    yaxis_tickfont_size=16,
    hoverlabel_font_size=16,
    height=600, 
    width=600
)fig.show()

基本气泡图—由作者创建的图形

箱线图

箱线图是收集有关数据分布的汇总统计信息的最有用的图之一。方框图显示数据范围和四分位值,以及数据中存在的异常值。在plotly中,这些都是非常简单的生产。我们将从创建一些虚拟数据开始:

# create dummy data for boxplots
y1 = np.random.normal(size=1000)
y2 = np.random.normal(size=1000)

我们可以将这两个数据集添加为同一轴上的独立盒状图,并将它们标记为Dataset 1Dataset 2

# create figure
fig = go.Figure()# plot data
fig.add_trace(
    go.Box(y=y1, name="Dataset 1"),
)fig.add_trace(
    go.Box(y=y2, name="Dataset 2"),
)fig.update_layout(
    font_family="Averta",
    hoverlabel_font_family="Averta",
    title_text="Basic Box Plots",
    xaxis_title_text="Datasets",
    xaxis_title_font_size=18,
    xaxis_tickfont_size=16,
    yaxis_title_text="Values",
    yaxis_title_font_size=18,
    yaxis_tickfont_size=16,
    hoverlabel_font_size=16,
    height=600, 
    width=600
)fig.show()

基本箱线图—作者创建的图形

这已经包含了我们需要的大部分信息,你会注意到悬停模板已经很好了,并且传达了这些信息。如果我们想让这个看起来更“有趣”,我们可以在方框图旁边添加实际的数据分布,以便读者查看。当我们这样做时,我们需要设置参数boxpoints="all"并添加一个jitter,它是一个值,用于将具有相似值的点展开,以便读者更容易看到分布。

带数据分布的箱线图—由作者创建的图表

支线剧情

现在把它们放在一起,让我们用条形图、柱状图、气泡图和箱线图创建一个包含 4 个支线剧情的面板图。为了创建支线剧情,我们首先需要从plotly.subplots导入make_subplots:

# import packages
from plotly.subplots import make_subplots

现在,与之前唯一的不同是,当我们初始化我们的图形时,我们使用make_subplots来创建我们想要的图形布局。

# create figure
fig = make_subplots(rows=2, cols=2)

让我们首先创建一组主虚拟数据,我们将需要这四个图,我们将添加到这个数字:

# create dummy data
x = [i for i in range(1, 6)]
y = np.ceil(100 * np.random.rand(5)).astype(int)
s = np.ceil(30 * np.random.rand(5)).astype(int)
y1 = np.random.normal(size=1000)
y2 = np.random.normal(size=1000)

我们现在所要做的就是,当我们向我们的图形添加一个轨迹时,我们需要提供一个行和列值,以便plotly知道在哪里放置这个图。需要注意的一点是,这里的索引从 1 开始!

# plot data
fig.add_trace(
    go.Bar(x=x, y=y, hovertemplate="<b>x:</b> %{x}<br><b>y:</b> %{y}<extra></extra>"),
    row=1, col=1
)fig.add_trace(
    go.Histogram(x=y1, hovertemplate="<b>Bin Edges:</b> %{x}<br><b>Count:</b> %{y}<extra></extra>"),
    row=1, col=2
)fig.add_trace(
    go.Scatter(x=x, y=y, mode="markers", marker_size=s, text=s, hovertemplate="<b>x:</b> %{x}<br><b>y:</b> %{y}<br><b>Size:</b> %{text}<extra></extra>"),
    row=2, col=1
)fig.add_trace(
    go.Box(y=y1, name="Dataset 1"),
    row=2, col=2
)fig.add_trace(
    go.Box(y=y2, name="Dataset 2"),
    row=2, col=2
)fig.update_xaxes(title_font_size=18, tickfont_size=16)
fig.update_yaxes(title_font_size=18, tickfont_size=16)fig.update_layout(
    font_family="Averta",
    hoverlabel_font_family="Averta",
    title_text="Subplots with Bar, Histogram, Bubble, and Box Plots",
    xaxis1_title_text="x",
    yaxis1_title_text="y",
    xaxis2_title_text="Values",
    yaxis2_title_text="Count",
    xaxis3_title_text="x",
    yaxis3_title_text="y",
    xaxis4_title_text="Dataset",
    yaxis4_title_text="Values",
    hoverlabel_font_size=16,
    showlegend=False,
    height=800, 
    width=1000
)fig.show()

带有条形图、直方图、气泡图和方框图的支线图—由作者创建的图形

就在那里!

结论

这是对各种类型的人物和使用plotly创建支线剧情的基本介绍。您可以进入plotly 布局文档查看使用fig.update_layout()时的所有可能性。本文中的plotly-charts.ipynb笔记本将在这个 Github 资源库中提供。

感谢您的阅读!我感谢任何反馈,你可以在 Twitter 上找到我,并在 LinkedIn 上与我联系,以获取更多更新和文章。

用 Python 制作遥感影像水质图

原文:https://towardsdatascience.com/creating-water-quality-maps-from-remote-sensed-images-with-python-ca5274041f4c

作者图片

介绍

水安全是联合国制定的 2030 年可持续发展目标之一。拥有适当数量和质量的水对地球上的生命来说是必不可少的。据估计,到 2050 年,世界人口将达到 100 亿。此外,气候变化也增加了水供应的压力,增加了与水有关的灾害,如干旱。

改善水治理至关重要,利益相关者需要获得数据以做出明智的决策。获取水质参数的成本很高,因为需要在野外获取,而且通常是点状的,难以在大区域或沿河进行空间概化。许多水体缺乏水质测量,特别是在发展中国家。

在这种情况下,使用卫星传感器正在成为从空间测量某些水质参数的一种可行的解决办法。例如,欧洲航天局的哥白尼任务每 4-5 天提供高达 10m 的中等分辨率图像。随着新卫星的发射,光谱分辨率也在提高。

在我之前的文章 中,我展示了如何使用waterdetect Python 包从卫星图像中提取水掩膜。在当前的故事中,我将介绍如何使用另一个包(waterquality 包),它使得从遥感图像创建水质地图更加容易。

装置

可以从下面的 github 库获得waterquality 包:https://github.com/cordmaur/WaterQuality

在安装它之前,确保您有一个可用的 GDAL 活动环境。

使用pip安装waterquality:

pip install git+https://github.com/cordmaur/WaterQuality.git@main

或者,您可以克隆项目并在编辑模式下安装它

git clone https://github.com/cordmaur/WaterQuality.git
cd WaterQuality
pip install -e .

安装后,您可以通过键入waterdetect -h来测试安装,它将从控制台显示基本用法。这里我们将调用 jupyter 笔记本中的包。

来自朱庇特

运行这个包我们需要做的第一件事是为waterdetectwaterquality准备配置文件。在这些文件中,可以配置所有的处理。我准备了两份样本,可以放在项目的test文件夹中。因此,如果您在waterdetect 根文件夹中,下面的命令应该通过。

然后,我们必须创建一个水质类的实例,并指向正确的文件夹。对于本例,必须下载并解压缩至少一幅图像(Sentinel-2 或 Landsat 8)。我将使用哨兵-2 L2-从欧空局获得的图像。这些图像是用 Sen2Cor 算法进行大气校正处理的,所以我用的是product = ‘S2_S2COR’。此外,我指定了一个较小的区域来关注我的水库。这可以通过将带有一个多边形的 shapefile 作为参数传递给创建函数来实现。另外,由于我只处理一幅图像,我选择了single_mode=True。创建实例的命令如下所示:

Initializing waterquality package (version=1.1.0) 
Waterdetect version: 1.5.12 
Loading configuration file ..\tests\aux_data\WaterQuality.ini 
File ..\tests\aux_data\WaterQuality.ini verified. 
Loading configuration file ..\tests\aux_data\WaterDetect.ini 
File ..\tests\aux_data\WaterDetect.ini verified. 
Folder d:/images/22KEV/S2A_MSIL2A_20210706T133231_N0301_R081_T22KEV_20210706T172741.SAFE verified. 
File d:/images/shp/22KEV.shp verified. Folder d:/images/output verified.

您应该在开始时看到一条消息,表明包已经初始化,并且找到了 waterdetect。它还会检查通知文件夹。

选择反转功能

在开始处理之前,有必要创建负责将光谱特征转化为实际水质参数的函数(称为转化函数)。

这些函数可以在文献中找到,并且它们将取决于各种因素,例如水的类型、卫星传感器、大气条件等等。

一旦选择了正确的功能,有必要告知waterquality如何使用该功能进行反转。例如,您可以为不同的参数通知多个功能,或者在同一区域测试不同的功能。

例如,我将实现 Nechad 等人(2010) [2]提出的总悬浮物质(TSM)函数。这是建议的功能:

应在包含{ '名称 f1 ':功能 1,'名称 f2 ':功能 2,…}的字典中告知这些功能

下面是一个使用 Nechad 等人(2010)提出的 SPM 算法的例子。

注意:在使用反演函数之前,检查函数在待分析区域的有效性是很重要的,最好是通过将结果与现场测量进行比较。

文章中的表 1 和表 4 列出了要使用的参数 A、B 和 C,具体取决于所用的传感器波长。为了选择波长,我们可以看到最佳的决定系数 R,在这种情况下是 82.9%,大约 710nm。然后,我们可以检查卫星的波段,看是否有接近这个波长的波段。

图 1:空间分辨率为 20 米的哨兵-2 波段。来源:欧空局

从图 1 中,我们可以看到波段 5,即 705 纳米,最接近 710 纳米,因此我们将根据该波长获得参数。在这种情况下,我们将得到:A = 493.65,B = 1.16,C = 0.188。

实现反转功能

现在我们有了一个函数及其参数,让我们用 Python 实现它并将其传递给我们的 waterquality 对象。要实现的功能可以使用卫星图像中可用的任何波段,但是,由于软件对于不同的卫星和大气处理程序是通用的,因此该名称必须与 waterdetect 包内部使用的名称相匹配。因此,在编写反演函数之前,检查卫星可用的频带,这些频带是在waterdetectInputOutput.py模块中通知的。

{'bands_names': {'Blue': 'B02_10m', 'Green': 'B03_10m', 'Red': 'B04_10m', 'Mir': 'B11_20m', 'Mir2': 'B12_20m', 'RedEdg1': 'B05_20m', 'RedEdg2': 'B06_20m', 'RedEdg3': 'B07_20m', 'Nir': 'B08_10m', 'Nir2': 'B8A_20m'}, 'suffix': '.jp2', 'string': '', 'metadata': '*MTD_TL.xml', 'subdir': 'GRANULE/*/IMG_DATA', 'recursive': True}

从结果中,我们可以看到 Band5 在 waterdetct 内部被称为 RedEdg1。所以,要利用这个波段,在函数参数中使用这个名字就足够了。为了简化质量参数的计算,所有波段将被重新调整为相同的形状,如配置文件WaterDetect.ini中的reference_band所定义。我们的函数将如下所示:

请注意,我们不必为没有数据像素或水域与陆地像素而烦恼,因为软件包会为我们处理这些问题。现在,我们必须告诉包使用这个函数作为反转函数来运行处理。这可以通过将字典传递给run_water_quality方法来实现。

Starting WaterDetection version: 1.5.11 
Opening image in folder d:\images\22KEV\S2A_MSIL2A_20210706T133231_N0301_R081_T22KEV_20210706T172741.SAFE 
Retrieving bands for product S2_S2COR 35 were found: 
Loading band: Blue 
Loading band: Green 
Loading band: Red 
Loading band: Mir 
Loading band: Mir2 
Loading band: RedEdg1...

显示结果

一旦处理完成,结果将可用,我们可以在 jupyter 笔记本上绘制结果。如果在 WaterDetect.ini 中将 PDF Reports 设置为 True,将在输出文件夹中写入一个报告,其中包含有关处理的所有信息(图 2)。该报告将显示原始图像(RGB)、水掩膜、阳光照射概率和带有色标的水质参数。

图 2:水质报告示例。该图(左下角)表示用于识别场景中水像素的聚类。图片作者。

从笔记本中,可以通过quality_parameters成员访问结果。功能plot_param也可用于创建结果的快速视图:

wq.plot_param('SPM', cmap='jet', vmin=5, vmax=20)

图 3:代码输出。图片作者。

结论

近年来,文献中关于利用遥感图像进行水质评价的研究越来越多。然而,我们可以注意到,它仍然缺乏操作工具,以使其能够为更广泛的受众所用。这些软件通常是在特定的研究范围内开发的,并停留在实验阶段,被锁在大学或研究中心里。我希望从这个项目和其他类似waterdetect的项目中,越来越多的实用遥感工具可供人们使用、研究和分享。

谢谢,下一个故事再见。

保持联系

如果你喜欢这篇文章,想支持我成为一名作家,可以考虑通过我的推荐链接成为 媒介会员 。每月只需 5 美元,我会从你的会员费中收取一小笔佣金,不需要你额外付费。或者你可以随时给我买杯咖啡。

http://cordmaur.medium.com/membership

参考

[1] 联合国《2021 年世界水资源开发报告:珍惜水资源》。 2021。S.l .:联合国教育。

[2]内恰德、b、K. G .鲁迪克和 y .帕克。2010."混浊水中总悬浮物质绘图的通用多传感器算法的校准和验证."环境遥感 114(4):854–66。https://doi.org/10.1016/j.rse.2009.11.022。

用 Jax 创建单词嵌入

原文:https://towardsdatascience.com/creating-word-embeddings-with-jax-c9f144901472

在 Jax 中实现经典的自然语言处理算法 Word2Vec

克里斯托夫·高尔在 Unsplash 上拍摄的照片

深度学习中的一些新想法比其他想法更有影响力,这是众所周知的。Word2Vec 是这些极具影响力的想法之一,因为它给 NLP word 带来了变化,之后又给复杂网络世界带来了变化,成为了 Node2Vec 和 Deep Walk 等其他算法的基础。

Word2Vec [1]于 2013 年发布,是一种用于从文本语料库(或向量的节点)中生成嵌入的算法。它变得有影响力,因为它允许 NLP 任务考虑单词的关系,而不仅仅是语料库的单个标记,从而生成更健壮的模型。

在这篇文章中,我们将对 Word2Vec 进行一些探索,并使用 Jax 库实现它。如果你有兴趣了解更多关于 Jax 库的知识,请参考我的 Jax 介绍这里

包含这篇文章代码的笔记本可以在 Github 和 Kaggle 上找到。

介绍 Node2Vec

Node2Vec 是一种深度学习算法,旨在从单词(或节点)中生成嵌入。但是你可能会问,什么是嵌入,嵌入是这个世界在 d 维空间的向量表示。

这个想法是,我们希望经常一起出现并且与相同意思相关的单词在这个空间上应该更近(它们的向量之间的距离应该小),而与它们没有任何关系的单词应该更远。

拥有这种嵌入表示是有用的,因为它允许我们用这些词进行一些计算,还允许我们在其他机器学习算法上使用这些嵌入来进行预测。

本文提出了 Word2Vec 的两种架构,如下图所示:

Node2Vec 架构。来自[1]

在 CBOW 架构中,我们将每个单词的上下文(即短语中该单词的相邻单词)插入到神经网络中,并尝试预测目标单词。

因此,如果短语是“女王非常强大”,而我们试图预测的单词是“是”,那么单词“the”、“queen”、“very”、“powerful”将是网络的输入。

请注意,这一过程是在我们的数据集的短语上的滑动窗口中发生的,被视为上下文的单词的数量是一个可配置的参数。

对于跳格结构,我们做相反的事情。我们在网络中插入目标单词,并试图预测该单词的上下文。所以在这个例子中,在我们插入“is”之前,试着预测其他单词的。

如何训练 Node2Vec

这可能会引起混淆,因为我们在这里试图预测单词。毕竟,我们对生成嵌入不感兴趣?确实如此。这里不使用预测任务,因为这不是我们的目标。然而,我们使用梯度下降法进行这些预测来训练我们的网络。

梯度下降法将改善每个时期的嵌入(希望如此)。

然而,请注意,预测的良好准确性并不是生成嵌入的必要条件。

因此,在每个时期,预测词与预期词进行比较,生成一个损失函数,我们用它来更新网络的权重。

我们正在寻找的嵌入将是我们网络的投影层的权重。

履行

我们现在将实现 Word2Vec。为此,我们将使用 Jax 框架,并将重点放在 CBOW 架构上。

这些是您将需要的库和方法:

import numpy as npimport jax.numpy as jnpfrom jax import grad, jit, vmapfrom jax import randomfrom jax.nn import softmax, one_hot

首先,让我们获取我们将用于该任务的数据。我们将使用 wikitext-2-v1 [2]数据集,该数据集可以免费下载到这里

下载完数据集后,我们会做一些非常基础的处理。请注意,由于这不是本任务的重点,因此不包括停用词的处理和删除以及一些常见的 NLP 任务。

with open('wiki.train.tokens') as fp: corpus = fp.read()corpus = corpus.replace('=', '').replace('@', '').replace('.', '').replace(',', '').replace('(', '').replace(')', '').replace('"', '')corpus = corpus.split('\n')corpus = [i for i in corpus if len(i.split(' ')) > 5]

在这里,我们读取数据,用空格替换一些标记,然后通过用分隔线分割数据集来生成语料库。最后,我们删除少于 5 个单词的每个短语。

现在让我们创建一些函数来帮助我们处理这些数据。首先,让我们标记我们的语料库:

def tokenize_corpus(corpus): tokens = [x.split() for x in corpus] return tokens

我们还需要一个函数来创建我们的标记化语料库的词汇:

def create_vocabulary(tokenized_corpus):
    vocab = {}
    vocab['<PAD>'] = 0
    i = 1
    for sentence in tokenized_corpus:
        for word in sentence:
            if word in vocab:
                continue
            vocab[word] = i
            i += 1

    return vocab

在这个函数中,我们为语料库中的每个单词设置一个整数。我们还创建了单词“”,当窗口大小没有包含我们需要的所有单词时,我们将使用它来填充训练数据。

现在,让我们使用现有的语料库和刚刚创建的词汇来生成我们的训练数据:

def generate_train_data(tokenized_corpus, vocab, window_size):
    X = []
    y = []
    for phrase in tokenized_corpus:
        for i in range(len(phrase)):
            target = i
            context = []for j in range(-window_size, window_size + 1):
                if j == 0:
                    continue
                try:
                    if i + j < 0:
                        context.append('<PAD>')
                        continue
                    context.append(phrase[i + j])
                except Exception:
                    context.append('<PAD>')
            X.append([vocab[k] for k in context])
            y.append(vocab[phrase[target]])

    return jnp.array(X), jnp.array(y)

这个函数的结果将是我们的神经网络的输入。生成的 X 的一些例子是:

DeviceArray([[0, 0, 2, 3],
             [0, 1, 3, 0],
             [1, 2, 0, 0],
             [0, 0, 5, 1],
             [0, 4, 1, 6],
             [4, 5, 6, 7],
             [5, 1, 7, 8],
             [1, 6, 8, 2],
             [6, 7, 2, 9],
             [7, 8, 9, 7]], dtype=int32)

最后,让我们应用刚刚创建的函数:

tokenized_corpus = tokenize_corpus(corpus)vocab = create_vocabulary(tokenized)X, y = generate_train_data(tokenized_corpus, vocab, window_size)

现在,让我们将窗口大小定义为 2,例如:

window_size = 2

神经网络的类别如下:

class Word2VecCBOW():
    def __init__(self, window_size, embed_dim, vocab_size, random_state):
        # Defines the key to be used for the random creation of the weights
        self.key = random.PRNGKey(random_state)
        self.vocab_size = vocab_sizeself.linear = self._create_random_matrix(vocab_size, embed_dim)
        self.soft = self._create_random_matrix(embed_dim, vocab_size)

        # Vectorizes the predict method
        self.predict = vmap(self._predict, in_axes=(None, 0))

    def train(self, X, y, num_epochs, batch_size):
        y = one_hot(y, self.vocab_size)
        X = one_hot(X, self.vocab_size)params = [self.linear, self.soft]
        for epoch in range(num_epochs):
            print(f'Epoch: {epoch}')
              for X_batch, y_batch in self.generate_batches(X, y, batch_size):
                    params = self.update(params, X_batch, y_batch)
            print(f'Loss: {float(self.l.primal)}')
        self.linear = params[0]
        self.soft = params[1]def _predict(self, params, X):
        activations = []
        for x in X:
            activations.append(jnp.dot(x, params[0]))# Averages the activations
        activation = jnp.mean(jnp.array(activations), axis=0)logits = jnp.dot(activation, params[1])
        result = softmax(logits)

        return result

    def _create_random_matrix(self, window_size, embed_dim):
        w_key = random.split(self.key, num=1)

        return 0.2 * random.normal(self.key, (window_size, embed_dim))def loss(self, params, X, y):
        preds = self.predict(params, X)
        l = -jnp.mean(preds * y)
        self.l = l
        return ldef update(self, params, X, y, step_size=0.02):
        grads = grad(self.loss)(params, X, y)return [params[0] - step_size * grads[0],
                params[1] - step_size * grads[1]]

    def get_embedding(self):
        return self.lineardef generate_batches(self, X, y, batch_size):
        for index, offset in enumerate(range(0, len(y), batch_size)):
            yield X[offset: offset + batch_size], y[offset: offset + batch_size]

在这里,我们创建一个辅助函数来在训练期间生成批次。除此之外,就神经网络应该做什么而言,实现非常简单。

现在,我们可以训练我们的网络并检索嵌入内容:

w2v = Word2VecCBOW(2, 32, len(vocab), 42)w2v.train(X, y, 10, 32)w2v.get_embedding()

结论

后续论文进一步改进了该论文的结果。此外,skip-gram 体系结构变得更加流行,并产生了更好的结果。还提出了针对复杂网络的新方法,例如 de Deep Walk 和 Node2Vec。

这篇文章旨在介绍这种方法,并举例说明如何从头开始用 Jax 开发神经网络。

希望它能对你的数据科学家之旅有所帮助。

[1]米科洛夫,托马斯&陈,凯&科拉多,G.s &迪恩,杰弗里。(2013).向量空间中单词表示的有效估计。ICLR 研讨会会议录。2013.

[2]梅里蒂,斯蒂芬&熊,&布拉德伯里,詹姆斯&索彻,理查德。(2017).指针哨兵混合模型。

在 R 中从头开始创建自己的逻辑回归模型

原文:https://towardsdatascience.com/creating-your-own-logistic-regression-model-from-scratch-in-r-ce719a49e10b

在没有外部包的情况下用 R 构建二进制分类模型的初学者指南

米利安·耶西耶在 Unsplash 上拍摄的照片

这篇文章着重于从头开始开发一个逻辑回归模型。我们将使用虚拟数据来研究众所周知的判别模型(即逻辑回归)的性能,并随着数据量的增加反映典型判别模型的学习曲线的行为。数据集可以在这里找到。请注意,该数据是使用随机数生成器创建的,用于在概念上训练模型。

介绍

逻辑回归直接将目标变量 y 对输入 x 的预测建模为定义为 p(y|x) 的条件概率。与线性回归模型相比,在逻辑回归中,目标值通常被限制为 0 到 1 之间的值;我们需要使用一个激活函数(sigmoid)将我们的预测转换成一个有界值。

假设函数 sigmoid 应用于数据的线性函数时,将其转换为:

方程式 1。说明了应用于线性函数的 sigmoid 变换。作者使用 Markdown & Latex 制作的图片。

我们现在可以将类别概率建模为:

方程式 2。使用逻辑函数说明了类别概率 C。图片由作者使用 Markdown &乳胶制作。

我们现在可以将类别概率 C=1 或 C=0 建模为:

方程式 3。使用逻辑函数,说明类别概率 C=1|X 和 C=0|X。图片由作者使用 Markdown & Latex 制作。

逻辑回归有一个线性决策边界;因此,使用最大似然函数,我们可以确定模型参数,即权重。注 P(C|x) = y(x),为简单起见,记为 y’。

方程式 4。说明了损失函数。作者使用 Markdown & Latex 制作的图片。

最大似然函数可以计算如下:

方程式 5。作者使用 Markdown & Latex 制作的图片。

现在我们将使用虚拟数据来玩逻辑回归模型。

1.在 R 中加载相关的库

#---------------------------------Loading Libraries---------------------------------
library(mvtnorm)
library(reshape2)
library(ggplot2)
library(corrplot)
library(gridExtra)

这些库将用于创建可视化和检查数据不平衡。

2.读取相关数据

#---------------------------------Set Working Directory---------------------------------setwd("C:/Users/91905/LR/")#---------------------------------Loading Training & Test Data---------------------------------train_data = read.csv("Train_Logistic_Model.csv", header=T)
test_data = read.csv("Test_Logistic_Model.csv", header=T)#---------------------------------Set random seed (to produce reproducible results)---------------------------------
set.seed(1234)#---------------------------------Create  training and testing labels and data---------------------------------
train.len = dim(train_data)[1]
train.data <- train_data[1:2]
train.label <- train_data[,3]test.len = dim(test_data)[1]
test.data <- test_data[1:2]
test.label <- test_data[ ,3]#---------------------------------Defining Class labels---------------------------------
c0 <- '1'; c1 <- '-1'

3.创建图:训练和测试数据的独立变量散点图

#------------------------------Function to define figure size---------------------------------
fig <- function(width, heigth){
     options(repr.plot.width = width, repr.plot.height = heigth)
}

观察数据的分布。

# — — — — — — — — — — — — — — — Creating a Copy of Training Data — — — — — — — — — — — — — — — — -
data=train_data
data[‘labels’]=lapply(train_data[‘y’], as.character)fig(18,8)
plt1=ggplot(data=data, aes(x=x1, y=x2, color=labels)) + 
geom_point()+
 ggtitle (‘Scatter Plot of X1 and X2: Training Data’) +
 theme(plot.title = element_text(size = 10, hjust=0.5), legend.position=’top’)data=test_data
data[‘labels’]=lapply(test_data[‘y’], as.character)fig(18,8)
plt2=ggplot(data=data, aes(x=x1, y=x2, color=labels)) + 
geom_point()+
 ggtitle (‘Scatter Plot of X1 and X2: Test Data’) +
 theme(plot.title = element_text(size = 10, hjust=0.5), legend.position=’top’)grid.arrange(plt1, plt2, ncol=2)

图一。说明了训练和测试数据的分布。我们可以在上面的图中观察到数据是线性可分的。这是伪数据。真实世界的数据可能不像类似的分布,自变量的数量也不会限制为两个。Image credit —由作者使用 r。

4.检查阶级不平衡

#------------------------------Function to define figure size---------------------------------
fig <- function(width, heigth){
     options(repr.plot.width = width, repr.plot.height = heigth)
}

看数据不平衡。我们检查来自训练和测试数据的前 100 行。

library(‘dplyr’)data_incr=100
fig(8,4)# — — — — — — — — — — — — — — — Creating a Copy of Training Data — — — — — — — — — — — — — — — — -
data=train_data
data[‘labels’]=lapply(train_data[‘y’], as.character)# — — — — — — — — — — — — — — — — — — — — — Looping 100 iterations (500/5) — — — — — — — — — — — — — — — — — — — 
# — — — — — — — — — — — — — — — — — — — — — Since increment is 5 — — — — — — — — — — — — — — — — — — — 
for (i in 1:2)

 {interim=data[1:data_incr,]

 # — — — — — — — — — — — — — — — — — — — — — Count of Records by class balance — — — — — — — — — — — — — — — — — — — 
 result<-interim%>%
 group_by(labels) %>%
 summarise(Records = n())

 # — — — — — — — — — — — — — — — — — — — — — Plot — — — — — — — — — — — — — — — — — — — 
 if (i==1)
 {
 plot1=ggplot(data=result, aes(x=labels, y=Records)) +
 geom_bar(stat=”identity”, fill=”steelblue”)+
 geom_text(aes(label=Records), vjust=-0.3, size=3.5)+
 ggtitle(“Distribution of Class (#Training Data=5) “)+
 theme(plot.title = element_text(size = 10, hjust=0.5), legend.position=’top’)
 }

 else
 {
 plot2=ggplot(data=result, aes(x=labels, y=Records)) +
 geom_bar(stat=”identity”, fill=”steelblue”)+
 geom_text(aes(label=Records), vjust=-0.3, size=3.5)+
 ggtitle(“Distribution of Class (#Training Data=10) “)+
 theme(plot.title = element_text(size = 10, hjust=0.5), legend.position=’top’)
 }

 data_incr=data_incr+5

 }
grid.arrange(plot1, plot2, ncol=2)

图二。说明了二进制类的分布。正如我们可以看到的,我们的积极类在数据中占多数;因此,我们可以看到,数据在很大程度上是不平衡的。Credit —由作者使用 r。

5.逻辑回归

概率判别模型使用广义线性模型来获得类别的后验概率,并旨在使用最大似然来学习参数。逻辑回归是一种概率判别模型,可用于基于分类的任务。

图三。展示了设计逻辑回归模型的逐步方法。Credit —由作者使用 markdown 和 latex 开发。

5.1 定义辅助功能

预测功能

使用概率分数返回-1 或+1。这里使用的阈值是 0.5,即如果一个类别的预测概率> 0.5,那么该类别被标记为-1,否则为+1。

#-------------------------------Auxiliary function that predicts class labels-------------------------------predict <- function(w, X, c0, c1)
{
    sig <- sigmoid(w, X)

    return(ifelse(sig>0.5, c1, c0))

}

成本函数

计算成本的辅助功能。

#-------------------------------Auxiliary function to calculate cost function-------------------------------cost <- function (w, X, T, c0)
{
    sig <- sigmoid(w, X)
    return(sum(ifelse(T==c0, 1-sig, sig)))

}

乙状结肠功能

#-------------------------------Auxiliary function to implement sigmoid function-------------------------------sigmoid <- function(w, x)
{
    return(1.0/(1.0+exp(-w%*%t(cbind(1,x)))))    
}

5.1.4 训练逻辑回归模型

该算法的工作原理如下。最初,设置参数。然后,在处理每个数据点 Xn,Tn 之后,参数向量被更新为:

𝑤(𝜏+1):=𝑤𝜏−𝜂𝜏(𝑦𝑛−𝑡𝑛)(𝑥𝑛)其中,(𝑦𝑛−𝑡𝑛)(𝑥𝑛)是误差函数的梯度,𝜏是迭代次数,𝜂𝜏是特定于迭代的学习速率。

Logistic_Regression <- function(train.data, train.label, test.data, test.label)
{

    #-------------------------------------Initializations-----------------------------------------
    train.len = nrow(train.data)

    #-------------------------------------Iterations-----------------------------------------
    tau.max <- train.len * 2

    #-------------------------------------Learning Rate-----------------------------------------
    eta <- 0.01

    #-------------------------------------Threshold On Cost Function to Terminate Iteration-----------------------------------
    epsilon <- 0.01

    #-------------------------------------Counter for Iteration-----------------------------------
    tau <- 1

    #-------------------------------------Boolean to check Terimination-----------------------------------
    terminate <- FALSE#-------------------------------------Type Conversion-----------------------------------

    #-------------------------------------Convert Training Data to Matrix-----------------------------------
    X <- as.matrix(train.data)

    #-------------------------------------Train Labels-----------------------------------
    T <- ifelse(train.label==c0,0,1)

    #-------------------------------------Declaring Weight Matrix-----------------------------------
    #-------------------------------------Used to Store Estimated Coefficients-----------------------------------
    #-------------------------------------Dimension of the Matrix = Iteration x Total Columns + 1-----------------------------

    W <- matrix(,nrow=tau.max, ncol=(ncol(X)+1))

    #-------------------------------------Initializing Weights-----------------------------------
    W[1,] <- runif(ncol(W))#-------------------------------------Project Data Using Sigmoid function-----------------------------------
    #-------------------------------------Y includes the probability values-----------------------------------
    Y <- sigmoid(W[1,],X)

    #-------------------------------------Creating a data frame for storing Cost-----------------------------------
    costs <- data.frame('tau'=1:tau.max)

    #-------------------------------------Threshold On Cost Function to Terminate Iteration-----------------------------------
    costs[1, 'cost'] <- cost(W[1,],X,T, c0)

  #-------------------------------------Checking Termination of Iteration-----------------------------------
  while(!terminate){

      #-------------------------------------Terminating Criterion----------------------------------
      #-------------------------------------1\. Tau > or = Tau Max (Iteration 1 is done before)----------------------------------
      #-------------------------------------Cost <=minimum value called epsilon-----------------------------------

      terminate <- tau >= tau.max | cost(W[tau,],X,T, c0)<=epsilon#-------------------------------------Shuffling Data-----------------------------------
      train.index <- sample(1:train.len, train.len, replace = FALSE)

      #-------------------------------------Obtaing Indexes of Dependent and Independent Variable------------------------------
      X <- X[train.index,]
      T <- T[train.index]#-------------------------------------Iterating for each data point-----------------------------------
      for (i in 1:train.len){

        #------------------------------------Cross check termination criteria-----------------------------------
        if (tau >= tau.max | cost(W[tau,],X,T, c0) <=epsilon) {terminate<-TRUE;break}

        #-------------------------------------Predictions using Current Weights-----------------------------------
        Y <- sigmoid(W[tau,],X)#-------------------------------------Updating Weights-----------------------------------
        #-------------------------------------Refer to the Formula above-----------------------------------

        W[(tau+1),] <- W[tau,] - eta * (Y[i]-T[i]) * cbind(1, t(X[i,]))#-------------------------------------Calculate Cost-----------------------------------
        costs[(tau+1), 'cost'] <- cost(W[tau,],X,T, c0)# #-------------------------------------Updating Iteration-----------------------------------
        tau <- tau + 1# #-------------------------------------Decrease Learning Rate-----------------------------------
        eta = eta * 0.999
      }
      }

    #-------------------------------------Remove NAN from Cost vector if it stops early-----------------------------------
      costs <- costs[1:tau, ]#-------------------------------------Final Weights-----------------------------------
    # #-------------------------------------We use the last updated weight since it is most optimized---------------------
      weights <- W[tau,]#-------------------------------------Calculating misclassification-----------------------------------

    train.predict<-predict(weights,train.data,c0,c1)
    test.predict<-predict(weights,test.data,c0,c1)

      errors = matrix(,nrow=1, ncol=2)

      errors[,1] = (1-sum(train.label==train.predict)/nrow(train.data))
      errors[,2] = (1-sum(test.label==test.predict)/nrow(test.data))

  return(errors)
}

逻辑回归,使用最大似然学习参数。这意味着在学习模型参数(权重)时,必须开发并最大化似然函数。然而,由于非线性方程系统没有解析解,所以使用迭代过程来寻找最优解。

随机梯度下降被应用于逻辑回归的训练目标,以学习参数和误差函数,从而最小化负对数似然。

5.2 使用不同数据子集的训练模型

我们将在不同的数据子集上训练模型。这样做是为了在研究数据量对模型错误分类率的影响时考虑方差和偏差。

#------------------------------------------Creating a dataframe to track Errors--------------------------------------acc_train <- data.frame('Points'=seq(5, train.len, 5), 'LR'=rep(0,(train.len/5)))
acc_test <- data.frame('Points'=seq(5, test.len, 5), 'LR'=rep(0,(test.len/5)))data_incr=5#------------------------------------------Looping 100 iterations (500/5)--------------------------------------
#------------------------------------------Since increment is 5--------------------------------------
for (i in 1:(train.len/5))

    {
        #---------------------------------Training on a subset and test on whole data-----------------------------
        error_Logistic = Logistic_Regression(train.data[1:data_incr, ], train.label[1:data_incr], test.data, test.label)

        #------------------------------------------Creating accuarcy metrics--------------------------------------

        acc_train[i,'LR'] <- round(error_Logistic[ ,1],2)acc_test[i,'LR'] <- round(error_Logistic[ ,2],2)

        #------------------------------------------Increment by 5--------------------------------------
        data_incr = data_incr + 5
}

模型的准确性可通过以下方式进行检验:

head(acc_train)
head(acc_test)

6.结论

在处理每个数据点之后更新参数向量;因此,在逻辑回归中,迭代的次数取决于数据的大小。当处理较小的数据集时(即数据点的数量较少),模型需要更多的训练数据来更新权重和决策边界。因此,当训练数据量很小时,它的准确性很差。

关于作者:高级分析专家和管理顾问,帮助公司通过对组织数据的商业、技术和数学的组合找到各种问题的解决方案。一个数据科学爱好者,在这里分享、学习、贡献;你可以和我在 上联系 上推特

通过数据科学建立更好机构的创造性方法

原文:https://towardsdatascience.com/creative-ways-to-build-a-better-agency-through-data-science-3ee62d0cca3

使用主题建模和网络图来可视化公司数据

克林特·王茂林在 Unsplash 上拍摄的照片

在我的机构 20nine,我们不断利用数据来帮助我们的客户找到问题的解决方案,并跟踪这些解决方案在实现目标方面的有效性。但直到最近,我们还没有考虑到数据科学在向内审视和改善我们自己的运营、员工体验以及实现我们机构目标的有效性方面有多大用处。

所以我们改变了这一切——在这个过程中,我们彻底改变了我们对员工绩效评估和目标设定的看法。以下是我们如何做到这一点的幕后情况。

站台

首先,我们为自己建立了一个名为 Forge 的内部平台,以帮助员工从个人、专业和业务角度创建、管理和跟踪与他们的成长相关的个人优先事项。锻造厂也是一个地方,员工可以通过积分兑换金钱奖励来给予同事认可,也就是荣誉。通过构建一个平台来支持这些操作并捕获相关数据,我们有两个目标:

1.从 30,000 英尺的高度了解员工的热情所在,看看我们如何才能最好地帮助他们实现目标

2.了解我们的组织是如何一起工作的,以及我们如何能够改变事情,以便每个人都有机会更多地合作

员工优先级

我们的第一个计划侧重于了解员工在他们的角色和职业生涯中努力实现的目标。我们的希望是,通过识别重叠,我们可以构建我们的福利和辅导课程,以支持我们团队的主要共同优先事项。

从数据科学的角度来看,该计划旨在汇总迄今为止所有声明的优先级,然后将它们分组到主题中。使用这种方法,我们希望在员工确定的 600 个目标中发现趋势。

主题建模

主题建模是组织、理解和总结内容中包含的大量数据的一种极好的方式。在这种情况下,我们可以加载 600 个数据点,并在一个简单的输出中轻松总结它们的内容。

在 Forge 中,输入的优先级有一些我们可以从中提取的数据点。对于这个计划,我们决定获取标题和描述内容,并将它们合并在一起。(经过一些初步审查后,我们认为仅仅标题并不总是包含足够的信息来确定一个给定条目是关于什么的。)

为了执行我们的主题建模,我们使用潜在的狄利克雷分配(LDA) Mallet 模型。基本思想是,每个文档(或本例中的优先级)由各种单词组成,每个主题也有与之相关的单词。我们需要设置我们想要提取多少主题,然后模型确定每个主题中的单词,然后将文档与这些主题进行匹配。

这个模型不能以编程的方式确定主题的最佳数量,所以我们需要自己找到最佳数量。为此,我们可以使用不同的主题编号在数据集上运行模型多次,并测量主题一致性分数,主题一致性分数指示主题中高分单词之间的语义相似度。本质上,这个话题所有的热门词汇放在一起有意义吗?

然后我们可以画出每个主题的分数,直观地看到下降的位置。

作者图片

在这种情况下,围绕 14 个主题似乎是最佳选择。

从这里,我们运行 LDA Mallet 模型,将这个数字设置为我们的主题计数,以获得 14 个带有相应关键字的主题。在这种情况下,我们得到:

  1. 结束、构建、增长

2.业务、开发、计划、pm

3.设计,专业,个人,任务

4.团队,工作,继续,会议

5.项目、创意、协作、跟踪

6.品牌、战略、时间、风险

7.提高、技能、管理、设置

8.处理,增加,运行,未来

9.日、增长、优先级、帐户

10.客户、知识、沟通、关系

11.制作、领导、角色、审核

12.学习,年份,时间,机构

13.创作,内容,视频,社交

14.工作,专注,伟大,文化

把所有的放在一起

拥有这些带有关键词的主题非常好,因为我们可以开始了解员工真正感兴趣的领域。例如,第 7 个主题似乎围绕着提高与管理相关的技能。帮助希望发展管理才能的员工的一个好方法可能是为管理项目或午餐提供财务帮助,并向我们的管理团队学习。

为了能够更好地可视化这些数据,我们希望将主题缩减到一个二维空间中。为此,我们使用了 t-分布式随机邻近嵌入(或 t-SNE)并将这些值绘制到散点图上。

现在,我们可以轻松地可视化集群,以了解主题的大小,但我们也可以单击单个节点,并获得其特定的数据点(在本例中,是优先级的标题和描述)。

作者图片

员工协作

从数据科学的角度来看,我们的下一个挑战没有那么激烈,但仍然非常有趣。查看锻造厂的员工认可(又名 Kudos)数据,我们的假设是,员工将最多的荣誉给予与他们一起工作最多的人。因此,利用这些数据,我们可以创建一个包含 20 名一线员工的网络图,他们之间的联系代表了他们从彼此那里获得的荣誉。他们从一个人那里得到的越多,他们之间的联系就越紧密。我们还可以使用权重以编程方式对这些信息进行聚类,以帮助我们直观地看到组织内不同的“派系”。

作者图片

以上是我们公司基于三个月 Kudos 数据的输出网络图(去掉了名字)。它很好地展现了我们当时的项目,以及这些项目在公司中是如何划分的。

将来,我们可以将这些数据按季度制作成动画,以确保组织内部有稳固的协作,同时还能看到它如何随着时间的推移而发展。展望未来,这种数据驱动的理解员工协作的方法可以帮助我们确保没有员工或“节点”离开网络太远,从而确保每个人都感觉自己是公司的一部分,尤其是在我们都远程工作的时候。

未来

我们计划继续使用定量和定性数据来优化我们作为一家公司的运营方式,并做出有助于员工感到融入其中并实现其职业目标的决策。作为一家代理机构,这是实践我们每天向客户宣传的一个重要部分:通过释放您数据的力量,您可以释放您团队的全部潜力,并帮助他们实现您组织的目标。

GPT 创意写作-3:从表情符号到 Flash 小说

原文:https://towardsdatascience.com/creative-writing-with-gpt-3-from-emoji-to-flash-fiction-219f2ca3f5a6

用人工智能增强创造性写作过程

一个负责写故事的机器人!作者使用 DALL-E 2 生成的图像。

使用人工智能开发完整的故事

作为一名创意小说作家,我想知道我是否可以让人工智能帮我写故事。人工智能已经被用来创作小说,比如一本儿童读物,一本诗集,以及一篇关于其自身的学术文章。人工智能写作工具,如谷歌的 Wordcraft ,也增强了计划、写作和编辑故事的过程。

在这篇文章中,我使用 GPT-3,一个由 OpenAI 创建的生成式人工智能语言模型,在一个迭代的、人在回路中的过程中写一篇短篇小说。通过这个过程中的每一步,我以不同的方式约束 GPT-3,使用少量学习、详细说明和参数化来诱导不同水平的特异性和稀疏性。使用 GPT-3,我

  • 使用表情符号产生故事创意,
  • 充实场景和人物背景
  • 综合背景知识,创建一个压缩的 100 字的小故事

我对于创造性写作的哲学,就像大多数其他的创造性追求一样,是人对于创作过程是至关重要的。GPT-3 没有自动化的角色,也没有取代创意作家的工作。相反,它是增强创造者工作的工具。我希望展示这种创造性写作的过程,在人工智能的帮助下,确保人类仍然处于创作过程的中心。

形式:Flash 小说

Flash 小说是一个非常短的故事,长度从 1000 字到 6 字不等。Flash 小说迫使作家用尽可能少的话来抓住故事的本质。每一个字都必须经过深思熟虑,对故事至关重要。参见NYT 小爱故事100 字故事中的例子。

该方法

在这一部分,我概述了我用迭代和互动的方式创作一篇短篇小说的方法。

1)使用表情符号选择标题

六个单词的故事是一个更短,更受限制的 flash 小说版本,在其中你只用六个单词讲述一个完整的故事。其中最著名的是海明威写的:“待售:婴儿鞋。没穿过。”

我生成了一个六个字的故事,并用它作为 flash 小说的标题。我使用一种叫做少数镜头学习的方法将表情符号映射到现有的六个单词故事。通过少量的镜头学习,GPT-3 首先获得一些将表情符号映射到六个单词故事的例子,它从中学习,为看不见的表情符号生成一个新的六个单词故事。由于 GPT-3 不太擅长计算单词,我强迫它故意计算每个单词。我随机选择了两个表情符号,看着 GPT 3 为我的新故事起了个名字。

用简单的学习用两个表情符号用六个单词讲述一个故事。作者截图。

标题:夜晚,我又变回人类

2)根据标题制作一个场景

接下来,我为故事生成一个场景。我使用标题(在第一步中生成)作为上下文。我还使用参数来约束格式,并提示 GPT-3 给我三个不同的例子供我选择。我强迫 GPT-3 想出具体的名词和形容词来描述一个场景,以及为什么这可能符合标题的原因。

这是我的提示:

Title: At night, I become human againBrainstorm ideas for the story idea.  Generate three scenes that might align well with the story title. Make sure each scene is distinct from the other ones.Use this format:
${Scene number}: Nouns: ${Describe the scene using at least 5 nouns}. Adjectives: ${Describe the scene using at least 5 adjectives}
Reason: ${Step-by-step reason why this scene might work well with the title}Repeat 3 times.

使用人工智能生成的标题作为上下文和参数化格式,为小说生成三种可能的设置。作者截图

我喜欢第三代的暗示性和神秘感,我就选这个。这种方法的好处是您可以利用不同的约束。例如,你可能想用副词或短语更具体地描述一个场景。

3)创造一个主要角色

接下来,我为故事创造了一个主角。我鼓励《GPT 3》去想象这个角色的详细背景故事。这些细节中的大部分可能不会在最终的故事中使用,但它构建了角色的丰富性。

我以两个要点开始(“有赌瘾的高中微积分老师”和“小镇,大梦想”),并提示 GPT-3 填写其余部分。GPT-3 增加了第三个要点,然后生成了两段描述主要人物的文字。由此产生的一代包括关于主角的背景和上下文:他的动机、欲望和个性。

我的提示:

Write a back story for the following person. Do not repeat any of the traits verbatim. Work them into sentences with additional background context. Do not repeat the same sentence twice.In the first paragraph, include the character's name. Describe their childhood and their relationship with their family, if they have any. Include the major event that led to their main personality traits.In the second paragraph, expand upon each of the points to show how they contributed to them as a person. Talk about the character's main fears and weaknesses and where they originated from. End the paragraph with a major conflict the character faces and the consequences of it.

使用 GPT-3 生成主要人物的详细背景。作者截图。

4)组合这些片段以生成一个故事

现在,我有了一个标题,一个场景和一个主角。为了生成最终的故事,我在提示中使用所有这些片段作为上下文。然后,我向 GPT-3 提供以下指令,解释最终输出的格式。输出被限制为 100 个单词。

Task: Write a 100-word flash fiction short story.
- You only have 100 words, so think very carefully about each word that you use
- Employ brevity of word while still including the important elements of the story
- Use the character's background as context and refer to it as needed. However, do NOT repeat any sentences from the Character background. Only refer to it cryptically and do not bring up the obvious parts
- The story must include elements of conflict, character building, and a dramatic arc
- The story must be in first person.
- Do NOT include the character's name
- The story takes place in a few minutes
- Tell a story that needs to be told.Begin.

我运行了几次 context + prompt 来获得几个不同的故事供选择!你最喜欢哪一个?

版本#1 :一个令人沮丧且相当模糊的故事,讲述了一个男人白天感觉自己像机器人/计算器,晚上可以通过感受自己的情绪“变成人类”。

版本#1 在生成一个标题为“在晚上,我又变成了人类”的 flash 小说作者截图。

第二版:一个稍微不那么模糊,但仍然令人沮丧的故事,关于一个焦虑的男人担心他的赌债,他可以在晚上“变成人”,在晚上有几分钟独处的时间。

第二个版本是生成一篇名为“在夜晚,我又变成了人类”的 flash 小说作者截图。

版本#3: 这个故事翻转了第一个故事,主角白天是数学老师,晚上通过变身成为赌徒的秘密激情而“变成人类”。这个版本有点重复(“我是……”),但有一个很好的节奏风格。

版本#3 在生成一个标题为“在晚上,我又变成了人类”的 flash 小说作者截图。

5)额外收获:以不同的风格重写

GPT-3 可以模仿著名作家的风格。有史以来我最喜欢的作家之一是加布里埃尔·加西亚·马尔克斯,所以我促使 GPT-3 用他的风格重写了这个故事。有一些从原来的故事复制粘贴,但也有新的风格的新点缀。

用 GPT-3 改写一个马尔克斯风格的故事。作者截图。

结束语

在本文中,我展示了一个如何使用 GPT-3 生成一篇短篇小说的例子。这些方法只是限制 GPT-3 在创造性写作过程中发挥作用的众多方法之一。在每个步骤中,人在循环中的部分都很明显:在选择表情符号时,在选择最佳生成设置时,在选择描述主要角色的元素时。此外,我多次生成我的输出,直到生成令我满意的东西。

我希望像 GPT-3 这样的工具能在作家遇到瓶颈的时候帮助有创造力的作家!为了获得更多由 GPT-3 产生的创造性写作和诗歌的很酷的例子,我推荐看看 Gwern.net。

我希望你喜欢这篇文章!

每个数据科学家都应该知道的关键事实—第 1 部分

原文:https://towardsdatascience.com/critical-facts-that-every-data-scientist-should-know-part-1-31f9c25e5e00

许多数据科学家都是出色的程序员,但是忽视某些统计事实会导致非常严重的后果。

康纳·塞缪尔在 Unsplash 拍摄的照片

介绍

在这篇文章中,我想向你解释一些简单但同时重要的概念,以避免像上面的机器一样结束。根据我的经验,即使是经验丰富的数据科学家有时也不知道或不完全理解至少我要告诉你的一些主题。

虽然不知道最新流行的算法通常是无害的,但对本文中描述的一些概念有部分了解是数据科学中代价最大的失败的根源。

对本文中描述的一些概念的部分理解是数据科学中代价最大的失败的根源

我将在下面描述的每一个主题很容易在一篇专门的文章中被涵盖。我的目标是用最少的努力给你最大的回报,并尽可能简单地解释这些经常模糊的概念。

由于涉及的论据太多,我决定将本文分成两部分,第二部分比第一部分更高级:

第一部分

  1. 用例及混淆矩阵介绍
  2. 你真的想最大化准确性吗?
  3. 精确度、召回率和 F1 分数
  4. 当感兴趣的类变得不那么频繁时,精度会发生什么变化
  5. 不平衡分类的度量

第二部分

  1. 数据泄漏,当你的结果好得令人难以置信
  2. 可解释性与辛普森悖论
  3. 采样偏差如何影响您的模型
  4. 非平稳性和协变量移位
  5. 观察者效应和概念漂移

本文中显示的图表和结果可以使用从 Google Colab 轻松运行的 相关 Jupyter 笔记本 来复制和更好地理解,我强烈推荐您尝试一下!

用例及混淆矩阵介绍

本文的第一部分将关注数据科学领域中最常见的问题之一:二进制分类。来自二进制分类的维基百科页面:

二进制分类是根据分类规则将集合中的元素分成两组的任务

虽然在我们的例子中我们只有两个类,但是实际上所有给出的参数都同样适用于多类分类的情况。

为了帮助发挥想象力,在本文的第一部分,我将使用一家公司的例子,该公司希望使用机器学习来了解其一些客户是否正在实施欺诈,这个问题通常被称为欺诈检测

然后我们将【T11 级/定义为诈骗者,将0 级/定义为普通客户

由于我们将使用的许多指标很容易从混淆矩阵中计算出来,在我开始写真正的文章之前,我将简要回顾一下它是如何定义的。如果你以前遇到过这个概念,请跳过这一部分。让我们先来看看这个矩阵的结构:

作者图片

如我们所见,混淆矩阵报告了实际类别和预测类别之间的四种可能组合:

  • 真正的否定:一个我们如此预测的否定。在我们的案例中:一个没有犯欺诈行为的客户,我们的模型(正确地)没有报告他。
  • 假阳性 ( FP ):我们预测为阳性的阴性。在我们的案例中:一个没有犯欺诈行为的客户,我们的模型(错误地)报告了他。
  • 假阴性:我们预测为阴性的阳性。在我们的案例中:一个客户犯了欺诈罪,而我们的模型(错误地)没有报告他。
  • 真阳性 ( TP ):我们预测的阳性。在我们的案例中:一个犯了欺诈罪的客户,我们的模型(正确地)报告了他。

这就是我们在继续这篇文章之前所需要的,如果你还有任何疑问,我建议你看看混乱矩阵上的优秀的维基百科页面。

注意:有时行和列的顺序与上面的相反,由于本文附有一个笔记本来进行实验,所以我使用了著名的 scikit-learn 库所使用的相同约定(参见附带的代码)。

你真的想最大化准确性吗?

在分类问题中,用于测量性能的最流行但也是最容易误导的度量之一是准确性。

  • 准确性 ( ACC ):我们的模型预测正确类别的次数百分比。数学上:ACC = (TP + TN) / (TP + TN + FP + FN)。

假设我们训练了两个模型来将我们的客户分为欺诈者和非欺诈者,看看下面的准确度值,哪个模型似乎表现得更好?

DummyClassifier accuracy: 90.91%
LogisticRegression accuracy: 77.82%

第一个模型的名字可能会让你怀疑一些事情,但是只看数字,似乎很明显 DummyClassifier 比 LogisticRegression 表现得更好。

真的是这样吗?让我们看看混淆矩阵:

作者图片

实际上,DummyClassifier 给出正确预测的次数比 LogisticRegression 多(否则其准确性不会更高),但它从不预测欺诈类。另一方面,LogisticRegression 会犯更多的错误,但至少它帮助我们识别了我们的客户正在犯的 100 起欺诈中的 74 起。

这种现象是由于什么?让我们首先考虑可能出现的类型的错误:

  • 我的客户是一个欺诈者,而我的模型没有意识到这一点(假阴性)。
  • 我的客户不是欺诈者,但我的模型认为他是(误报)。

准确性对这两种类型的错误给予同等的重视,最重要的是,不关心我们的模型是否犯了“太多”这两种类型的错误。

准确性[…]并不关心我们的模型是否犯了“太多”这两种类型的错误

显然,一个从不报告欺诈的识别欺诈的模型是完全无用的,就像一个将所有客户报告为欺诈者的模型一样,因此我们希望指标能够帮助我们知道模型何时以某种方式夸大了事实。

一个可能出现的问题是:那么在什么情况下我必须在使用准确性作为主要度量标准之前好好思考?

当类别非常不平衡时,不管模型有多好,精度往往都很高,因为在这种情况下,预测最频繁的类别就足以获得非常高的精度。

当类别非常不均衡时,不管模型有多好,准确性往往都很高,因为在这种情况下,预测最频繁的类别就足以获得非常高的准确性

事实上,我们的 DummyClassifier 只是使用“最频繁”策略,因此它的预测总是“不欺诈”,即训练数据集中最频繁的类。

精确度、召回率和 F1 分数

让我们来看一些解决准确性问题的分类指标。

  • 精度(或阳性预测值,PPV ):模型预测第 1 类时正确的次数百分比。例如,如果模型说“欺诈”100 次,精度是 75%,平均来说它在 75 种情况下是正确的,其他 25 种是错误警报。数学上:PPV = TP / (TP + FP)。
  • 回忆(或真阳性率TPR ):模型归类为类 1 的观察值的百分比。例如,如果我的客户中有 100 名欺诈者,召回率为 75%,我将平均识别其中的 75 名,遗漏另外 25 名。数学上:TPR = TP / P = TP / (TP + FN)。
  • F1-score ( F1 ):精度和召回率的调和平均值,F1-score 的一个“好”值(什么是“好”取决于问题)通常保证精度和召回率都是“好”的。数学上:F1 = 2 * PPV * TPR / (PPV + TPR)。

为什么要使用调和平均值,而不是更简单、更受欢迎的算术平均值?

好吧,首先,如果我们使用算术平均值,F1 分数将会有与准确性相同的缺陷,这是一个无用的模型,它总是预测两个类别中的一个会有明显不同于零的分数。例如,如果精度是 1%,召回率是 100%,两者的算术平均值接近 50%,而 F1 值(调和平均值)大约是 2%。

使用这种类型的平均值还有更深层的原因,要了解更多,我推荐这篇优秀的文章:

让我们看看我们的新指标在前面的场景中给出了什么指示:

DummyClassifier precision: 0.00%
DummyClassifier recall: 0.00%
DummyClassifier F1-score: 0.00%

LogisticRegression precision: 25.34%
LogisticRegression recall: 74.00%
LogisticRegression F1-score: 37.76%

厉害!F1 分数概括了精确度和召回率,它正确地给我们的 LogisticRegression 一个较高的分数,给我们的 DummyClassifier 一个零分。

在这种情况下,所有三个指标都是一致的,但是需要注意的是,一般来说,情况并不总是这样。如前所示,精确度和召回率之间的一个接近于零就足够了,F1 分数也接近于零,不同于两者的算术平均值。

如果您还不相信,我建议模拟一个总是预测“欺诈”的模型,并使用它在包含 1%欺诈的测试集上进行预测,在这种情况下,您应该会看到如下结果:

DummyClassifier precision: 1.00%
DummyClassifier recall: 100.00%
DummyClassifier F1-score: 1.98%

现在,设想一个模型,该模型只预测几个非常可疑的客户的欺诈行为:一个伪现实案例可能是一个只报告在最近所有检查中被识别为欺诈的欺诈客户的模型。如果这些是模型报告的唯一欺诈,因为我们可以想象它们在总数中所占的百分比非常小,召回率将约为 0%,而精确度将接近 100%,因为我们几乎可以肯定这些客户是欺诈者。

总而言之,在这种情况下,我们期望的值类似于:

DummyClassifier precision: 100.00%
DummyClassifier recall: 0.00%
DummyClassifier F1-score: 0.00%

这应该让你意识到,只看精度或只看回忆可能很有欺骗性。

只看精确度或只看召回率可能很有欺骗性

当感兴趣的类变得不那么频繁时,精度会发生什么变化

精确度、召回率和 F1 分数当然是监控我们模型的很好的指标,但是它们在每种情况下都是最合适的吗?答案显然是否定的。首先,因为没有完美的度量标准,用来评估模型性能的度量标准必须根据我们想要实现的目标来选择。

没有完美的度量标准,评估模型性能的度量标准必须根据我们想要实现的目标来选择

记住我们给不同类型的错误的“价格”。

但是有一个特殊的例子,使用精确度,因此 F1 分数至少是误导的。

让我们想象一下,在包含 50%欺诈客户和 50%非欺诈客户的测试集上计算我们的指标。这种情况并不罕见,因为有时数据科学家会平衡他们的数据集,或者正如我们将在本文的第 2 部分中看到的,我们用来训练和验证模型的数据分布与我们在生产中使用它们的分布不同。

鉴于此,我们得到以下值:

Precision: 75.92% ± 3.52% (mean ± std. dev. of 100 runs)
Recall: 75.97% ± 4.79% (mean ± std. dev. of 100 runs)
F1-Score: 75.87% ± 3.42% (mean ± std. dev. of 100 runs

我们对模型的性能感到满意,决定在生产中使用它来识别欺诈者。在一系列有针对性的检查之后,我们重新计算了我们的指标,从而得出:

Precision: 3.04% ± 0.17% (mean ± std. dev. of 100 runs)
Recall: 75.88% ± 4.42% (mean ± std. dev. of 100 runs)
F1-Score: 5.84% ± 0.33% (mean ± std. dev. of 100 runs)

发生了什么事?第一个想法可能是我们将在本文的第二部分中讨论的一些特殊现象,如协变量转移概念漂移,但事实真的如此吗,或者这里有更简单的解释吗?

事实是,当第一类变得更少时,回忆保持不变,而精确度,因此 F1 分数下降。

当类别 1 变得更少时,回忆保持不变,而精确度,因此 F1 分数下降

为什么?让我们从回忆开始,为什么我们期望它保持不变?召回只取决于模型对欺诈客户的行为。在分子中,它有真阳性(模型识别的欺诈者),在分母中,它有真阳性加上假阴性(模型错误地分类为非欺诈者的欺诈者)。换句话说,我们正在分类的数据集中非欺诈者的数量,以及他们相对于欺诈者的比例,不会以任何方式决定召回。

我们正在分类的数据集中非欺诈者的数量,以及他们相对于欺诈者的比例,不会以任何方式决定召回

让我们现在做这个心理练习:如果在生产中我们有 100 个欺诈者和 10000 个非欺诈者,让我们首先计算我们期望在由 100 个欺诈者加上从 10000 个非欺诈者中随机选取的 100 个非欺诈者组成的数据集上获得的精确度。我们期望获得什么样的精度值?嗯,这很容易,因为这个数据集与我们的测试集具有相同的欺诈者比例,在不考虑其他可能因素的情况下(参见第 2 部分),我们可以预期精度等于已经获得的精度,即大约 75%。

现在我们必须问自己:在最终性能的计算中,哪一个结果可以具有剩余 9900 种情况下尚未考虑的模型的预测?因为他们都是非欺诈客户,所以他们每个人的结果只能是真阴性或假阳性。换句话说,在任何情况下,它都不能增加精度的分子(真阳性),而对于每一个额外的非欺诈性客户,精度的分母有非零的概率将增加,特别是,分母中的假阳性将增加,因此精度(以及 F1 分数)降低。

不平衡分类的度量

那么有没有对阶级不平衡不那么敏感的度量标准呢?还好,是的!

不平衡分类是一个非常重要的问题,在现实生活中我们很少对每一类都有相同百分比的观察值,

在现实生活中,我们很少对每一类都有相同的观察百分比

有大量关于它的文献。在这里,我只想向您展示一些我认为非常有用的指标,这些指标与精确度、召回率和 F1 值非常相似。

第一个指标是敏感度,敏感度是……召回率!是的,不幸的是,根据上下文的不同,相同的指标有许多不同的名称,理解我们正在谈论的内容的唯一方法是查看数学定义。

第二个指标是新的:

  • 特异性(或真阴性率TNR ):模型正确分类的 0 类观测值的百分比。在我们的例子中,有多少次我们的模型认为一个非欺诈者是一个非欺诈者,而不是报告一个假警报。数学上:TNR = TN / N = TN / (TN + FP)。

最后,与 F1 分数类似,我们这次使用几何平均值(再次参见之前的链接文章)来总结两个指标:

  • G 均值:敏感性和特异性的几何均值。数学上:G 均值= sqrt(TPR * TNR)。

让我们看看在平衡测试集(1:1)的情况下,这些指标假设了什么值:

Sensitivity: 76.20% ± 4.84% (mean ± std. dev. of 100 runs)
Specificity: 76.05% ± 4.38% (mean ± std. dev. of 100 runs)
G-Mean: 76.05% ± 3.22% (mean ± std. dev. of 100 runs)

现在,与上一个案例一样,让我们在一个类似的数据集上重新计算它们,但不平衡度为 1💯

Sensitivity: 75.69% ± 4.12% (mean ± std. dev. of 100 runs)
Specificity: 75.68% ± 0.41% (mean ± std. dev. of 100 runs)
G-Mean: 75.65% ± 2.08% (mean ± std. dev. of 100 runs)

可以看出,如果数据集也高度不平衡,这些指标不会改变(除了统计上不显著的变化)。

为什么?

我们已经说过为什么敏感性(又名回忆)不变,同样的推理也适用于特异性。此外,特异性不依赖于模型在两个类上的表现,因此依赖于它们的比例,而只来自两个类中的一个。敏感性衡量模型对欺诈客户的表现,而特异性告诉我们模型对非欺诈客户的表现。

结论

这就结束了文章的第一部分,关于你绝对应该知道的事情,我希望你喜欢它,至少我描述的一些内容是新的,因此对你有用。在 第二部分 中,我们将涵盖一些更为的高级主题如:

  1. 数据泄漏,当你的结果好得令人难以置信
  2. 可解释性与辛普森悖论
  3. 采样偏差如何影响您的模型
  4. 非平稳性和协变量移位
  5. 观察者效应和概念漂移

如果你喜欢这篇文章,并希望我写更多类似的文章,你可以做几件事来支持我:开始在 Medium 上关注我,在社交媒体上分享这篇文章,并使用下面的鼓掌按钮,这样我就知道你对这种类型的内容感兴趣。

最后,如果您还不是中等会员,您可以使用我的推荐链接成为中等会员:

https://mnslarcher.medium.com/membership

想保持联系吗?在 LinkedInTwitter 上关注我!

每个数据科学家都应该知道的关键事实—第 2 部分

原文:https://towardsdatascience.com/critical-facts-that-every-data-scientist-should-know-part-2-c9c06cde6e21

在本文的第二部分,我们将总结成为超级数据科学家需要知道的一些关键事实

米歇尔·卡萨尔在 Unsplash 上的照片

介绍

我们还需要数据科学家吗?

21 世纪最性感的工作现在似乎已经过时了。今天,训练一个随机森林或一个神经网络只需要几行代码,甚至几个点击。每个软件供应商都承诺凭借他们出色的工具将你变成“公民数据科学家”,因此试图理解我们数据背后的统计和数学事实似乎是浪费时间,但真的是这样吗?

在文章的第二部分,请看下面的第一部分

我们将更深入地分析我们在分析真实数据时遇到的一些最重要的现象。当你读完这篇文章时,我希望你会同意我的观点,不是我们使用的工具定义了我们作为数据科学家,而是我们对数据的理解,

定义我们为数据科学家的不是我们使用的工具,而是我们对数据的理解

它是如何产生的,以及它是如何随时间演变的。

特别是,在第二部分中,我们将讨论:

  1. 数据泄漏,当你的结果好得令人难以置信
  2. 可解释性与辛普森悖论
  3. 采样偏差如何影响您的模型
  4. 非平稳性和协变量移位
  5. 观察者效应和概念漂移

理解以下内容所需的所有知识都可以在本文的第一部分找到。

您可以在下面的 GitHub 资源库中找到用于生成结果和图表的代码,我们将一起分析这些结果和图表。在那里你会发现两个笔记本可以很容易地在 Google Colab 上运行,而不需要在你的 PC 上安装任何东西。

https://github.com/mnslarcher/critical-facts-that-every-data-scientist-should-know

我强烈建议您尝试自己运行代码,尝试新的参数并预测它们对结果的影响。毕竟,除了把手弄脏之外,没有其他方法可以真正学到东西。

除了把手弄脏之外,没有其他方法可以真正学到东西

数据泄漏,当你的结果好得令人难以置信

你有过“好得可疑”的结果吗?这很奇怪,但我们的数据科学家的第六感告诉我们,如果一个结果好得令人难以置信,它很可能不是真的。

如果一个结果好得令人难以置信,那么它很可能不是真的

在这种情况下,第一嫌疑人几乎总是数据泄露

维基百科页面上漏(机器学习):

[……]泄漏(也称为数据泄漏目标泄漏)是在模型训练过程中使用信息,该信息预计在预测时间不可用,导致预测分数(指标)高估了模型在生产环境中运行时的效用。

使用第 1 部分中已经介绍的示例,假设我们想要训练一个模型来识别我们的客户中谁在实施欺诈。

我们几乎没有时间,但对我们新购买的软件充满信心,我们点击几次来读取我们去年的客户数据,包括我们将用作目标的欺诈/非欺诈列,瞧,再点击几次来分割数据并训练最新的闪亮模型,我们就完成了。

我们在测试集上测量性能,一切似乎都很顺利,结果非常好,超出了我们的预期,来吧,让我们将模型投入生产!

Test precision: 89.95%
Test recall: 99.40%
Test F1-score: 94.44%

几个月后,大家都很高兴,我们重新计算了我们的模型在一些客户身上的表现,我们以样本为基础检查了这些客户,得到了这些结果:

Production precision: 47.37%
Production recall: 9.00%
Production F1-score: 15.13%

发生了什么事?

让我们来看看用来生成预测的变量,其中我们找到了一个名为 updated_last_year 的二进制变量,它表示客户信息在去年是否更新过。乍一看,这似乎是一个无辜的变量,我们并不真正理解为什么模型认为它如此重要。不理解为什么我们的模型认为一个变量很重要是一个大的危险信号,应该让我们怀疑。

不理解为什么我们的模型认为一个变量很重要是一个大的危险信号

但是让我们想一想,在我们的训练和测试数据集中,这个变量对欺诈客户有什么价值?因为在这种情况下,我们会更新他们的信息,说他们犯了欺诈罪。其他顾客呢?1 或者 0,这么说吧,只是为了举例,10% 1,90% 0。

所以变量 updated_last_year 在检查的结果之后并根据它而改变。但是,当我们在生产中使用我们的模型来决定客户是否正在实施欺诈时,我们是否知道检查的结果以及该变量将取的值?没有。

换句话说,在训练期间,我们无意识地使用仅在我们用来训练模型的检查之后可用的信息。我们用未来来预测过去,这是一个常见的错误。

我们用未来来预测过去,这是一个常见的错误

为什么“利用未来”在培训期间是可能的,而在我们使用模型来决定检查谁的生产中是不可能的?因为在培训期间,我们可以访问过去(检查前的客户信息)和未来(检查的结果及其后续行动),而在生产中,我们只知道到目前为止发生了什么,我们只能在此基础上决定是否检查客户。

这应该给我们一个教训:永远不要盲目使用数据,

永远不要盲目使用数据

当我们使用模型生成预测时,了解存在哪些变量是很重要的,并且要确保在训练期间,我们的解释变量的所有值都是根据我们想要预测的信息计算出来的。

可解释性与辛普森悖论

通常,解释为什么做出预测与预测本身一样重要,甚至更重要。对一个统计学家来说,“解释”可能会让人想到像 p 值这样的概念,

看看我们的线性回归的哪些参数有统计学意义。这听起来可能非常科学,因此是正确的,但是记住有三种谎言:谎言、该死的谎言和统计数据。

有三种谎言:谎言、该死的谎言和统计数字

让我们设想估计一个线性回归来理解 18 岁到 38 岁人群中健康和工资之间的关系。

图片作者,灵感来自 Pace~svwiki

我们惊讶地发现,随着薪水的增加,人们的健康状况却在下降,穷人真幸福!但事实真的是这样吗?

然后,让我们重新估计另一个线性模型,然而这一次通过添加人的可变年龄,我们然后获得:

图片作者,灵感来自 Pace~svwiki

如果加上年龄这个变量,工资和健康的关系就完全颠倒了!这种现象被称为辛普森悖论

一句话:不要因为两个变量相关就得出因果结论,我们没有考虑的潜在变量可能会完全推翻我们对数据的解释。

当心仅仅因为两个变量相关就得出因果结论

采样偏差如何影响您的模型

有时,我们并不太注意我们用来训练模型的数据是如何收集的。毕竟,如果他们是正确的,那又有什么区别呢?不幸的是,数据收集的方式有所不同,很大的不同。

数据是如何收集的很重要,非常重要

回到欺诈检测示例,让我们假设我们的数据如下图所示:

作者图片

我们注意到的第一件事是,在总人口(过去和非过去检查的客户)和我们过去检查的人口之间,私营和国有企业中欺诈的百分比发生了变化。为什么?

嗯,有几个原因:

  1. 如果我们在过去没有完全随机地检查我们的客户,我们可以预期在我们被检查的客户数据集中欺诈的百分比比在总人口中的百分比更高。
  2. 我们还认为,国有企业很少受到检查,几乎只是因为有人目睹了一起欺诈事件而提出投诉。

换句话说,在我们接受检查的客户数据集中,我们有两个偏差:更多的欺诈,以及在国有企业中,有人举报的企业过多。

当偏差是由于数据收集的方式引起时,它被称为采样偏差

让我们暂时忽略这些事实,根据唯一可用的数据——被检查客户的数据库——训练一个模型。我说“仅可用”是因为不可能使用我们没有检查过的客户来训练一个监督的模型,我们不知道目标变量的值,而这个值必须在训练期间知道。

回测之后,我们决定我们很满意,我们使用生产中的模型选择 1000 个客户进行检查。

让我们先来看一些根据客户样本计算的统计数据:

Percentage of state-owned enterprises: 50.00%
Percentage of private enterprises: 50.00%
Fraud rate in state-owned enterprises: 5.00%
Fraud rate in private enterprises: 20.00%

你最想检查谁?

让我们看看我们的模型给出了什么建议:

Percentage of state-owned enterprises in the top 1000 to be inspected: 100.00%

全是国企!

我们过去检查客户的方式引入了偏见,即只有在我们基本确定欺诈行为时才检查国有企业,这产生了一个训练数据集,模型根据这个数据集学习识别国有企业更加可疑(即使实际上它们的欺诈行为较少)。

请注意,如果我们包含一个“报告的”二进制变量,说明客户在检查之前是否被某人报告过,这个问题就会得到缓解。同样的变量意味着一旦模型投入生产,被某人报告的客户看到(正确地)他们被模型估计的欺诈的可能性上升。然后,这个模型就会明白,不是国有企业让欺诈更有可能发生,而是有人看到我们作弊的事实。还是那句话,有没有变量会造成世界上所有的不同。

有或没有一个变量可以让世界变得完全不同

通常,有些变量是无法访问的,所以最好总是问自己用于训练模型的数据和生产中的数据之间有什么不同,在下一节中,我们将看到这是真实的其他原因。

总是问自己,用于训练模型的数据和生产中的数据之间有什么不同

非平稳性和协变量移位

现实在不断发展,但我们用来训练模型的数据往往停留在过去,从长远来看,这可能会产生严重的问题。

现实在不断发展,但我们用来训练模型的数据往往停留在过去

假设我们是研究温度和植物生长速度关系的数据科学家。我们已经训练了一个线性模型,现在正在监控它如何处理每天发送给我们的新数据。

在前 100 天,传入的数据类似于我们训练模型时使用的数据,我们计算性能并得到:

First period mean absolute error: 0.01

很好,误差相当低,我们的模型很好地表现了温度和植物生长速度之间的关系。

然而,在某一点上,有一个明显的变化,植物现在承受的温度更高了。我们问那些进行实验的人为什么,他们告诉我们,他们正在测试一种新的机制,以了解植物对高温的反应。

作者图片

一个随时间改变其分布的系列,就像在我们的例子中植物暴露的温度,被称为非平稳

对我们模型的性能会有什么影响?

我们根据过去 100 天的数据计算性能,我们看到我们的平均绝对误差(MAE)是前一时期的近 20 倍:

Second period mean absolute error: 0.18

让我们绘制测量的增长率和我们的模型预测:

图片由作者提供,灵感来自 Criddle 等人 2005 年

我们的模型从来没有机会了解这一秒的非常高的值的生长和温度之间的关系,只能使用从可用数据中了解到的内容进行推断。不幸的是,在这种情况下,从模型中得知的中低温下有效的线性关系与高温下的真实关系正好相反。

我们的模型使用的解释变量改变其分布的现象被称为协变量移位

这一事实对公平性也有深远的影响:例如,一个被训练用来预测药物对大多数白人男性患者的效果的模型可能对黑人女性患者效果不佳。

[协变量转移]对公平也有深远的影响

观察者效应和概念漂移

我以一个发生在 1924 年至 1927 年间的真实实验来结束这篇文章,我将转移并简化它一点,但实质并没有改变。

假设我们是研究环境照明对工人生产力影响的科学家,我们的假设是照明的增加会提高工人的生产力。

首先,我们在不改变照明的情况下,在两个不同的时间测量工人的生产率,正如预期的那样,我们没有记录任何统计上的显著变化:

Productivity increase - normal illumination: 0.1% ± 5.0% (mean ± std. dev.)

在这一点上,我们警告工人,我们将进行一项实验,在一定时期内,我们将增加照明,以了解这对他们的生产率有什么影响。几天后,我们记录了生产率的以下增长:

Productivity increase - increased illumination: 50.1% ± 4.9% (mean ± std. dev.)

太好了!我们发现了生产力和环境照明之间的关系,这将使实业家们眼前一亮。

但这是真的吗?好吧,好吧,在这一点上,我已经变得无聊了,但重要的是要强调,数据科学家必须始终保持批判性思维。

数据科学家必须始终保持批判性思维

我们重复了这个实验,但这一次没有对此时不知道自己被观察的工人说任何话,我们在一段时间后重新计算生产率的增加,这一次我们得到:

Productivity increase - increased illumination and not aware: -0.2% ± 5.0% (mean ± std. dev.)

啊!我们之前所有的结论都是错误的,生产率的提高并不是因为照明的增加,而仅仅是因为工人们知道他们被监视了。在进行实验时,总是要小心考虑你是否在无意识地影响结果,即使只是因为你的实验对象知道他们正在被观察。

当进行实验时,总是小心考虑你是否在无意识地影响结果

这一事实被称为观察者效应霍桑效应 来源于首次进行实验的植物的名字(请阅读原始实验,不要把我对实际发生的事情简单化)。

在不知道工人们是否知道他们被监视的情况下,我们唯一能得出的结论是,在相同的输入(照明水平)下,输出(生产率)发生了变化。输入与输出关系的变化称为概念漂移。与此形成对比的是协变量转移,在协变量转移中,投入和产出之间的关系不变,但投入的分布发生了变化。

在这个实验中,工人响应照明变化的行为变化不是“真正的”变化,但这是因为我们没有考虑一个重要的变量吗?你注意到前面几节的模式了吗?

结论

最后一部分总结了我在数据科学家职业生涯中学到的一些最重要的概念,我希望你会觉得有用。

如果你喜欢这篇文章,并希望我写得更好,你可以做几件事来支持我:开始在 Medium 上关注我,在社交媒体上分享这篇文章,并使用下面的鼓掌按钮,这样我就知道你对这种内容感兴趣。

最后,如果您还不是中等会员,您可以使用我的推荐链接成为中等会员:

https://mnslarcher.medium.com/membership

想保持联系吗?在 LinkedInTwitter 上关注我!

深度网络中的关键学习阶段

原文:https://towardsdatascience.com/critical-learning-periods-in-deep-networks-35b2f17c4bbe

为什么第一个纪元最重要…

人工系统和生物系统中临界学习期存在的经验证明(来自[1])

什么是关键的学习时期?

为了理解深度学习中的关键学习阶段,首先看一下生物系统的相关类比是有帮助的。在人类和动物中,关键期被定义为出生后早期(即出生后)发育时期,在此期间,学习障碍(如感觉缺陷)可能会导致个人技能的永久性损伤[5]。例如,年轻时的视力障碍——一个人视力发展的关键时期——通常会导致成年人出现弱视等问题。

虽然我远非生物学专家(事实上,我从高中起就没有上过生物学课),但关键学习期的概念仍然与深度学习有着奇怪的关联,因为在神经网络的学习过程中也表现出了同样的行为。如果神经网络在学习的早期阶段受到一些损害(例如,仅显示模糊的图像或没有适当地调整),则所得到的网络(在训练完全完成之后)相对于从未受到这种早期学习损害的网络将概括得更差,即使给定了无限的训练预算。从这种早期学习障碍中恢复是不可能的。

在分析这种奇怪的行为时,研究人员发现神经网络训练似乎分为两个阶段。在第一阶段——对学习缺陷敏感的关键时期——网络记忆数据并通过优化领域的瓶颈,最终找到一个表现更好的区域,在该区域内可以实现收敛。从这里开始,网络经历一个遗忘过程,学习可概括的特征,而不是记忆数据。在这一阶段,网络存在于损耗图的一个区域内,在该区域内存在许多同等性能的局部最优解,并最终收敛于这些解之一。

关键的学习阶段对于我们整体理解深度学习是至关重要的。在这个概述中,我将通过首先概述神经网络学习过程的基本组成部分来拥抱这个主题的基本性质。鉴于这一背景,我希望由此产生的对关键学习阶段的概述将提供一个更微妙的视角,揭示训练深度网络的真正复杂性。

背景资料

在这一部分,我将概述深层网络训练中的基本概念。这种基本思想对于理解神经网络的一般学习过程和学习期间的关键时期都是至关重要的。本节中的概述非常广泛,可能需要时间才能真正掌握,所以我为那些需要更多深度的人提供了进一步的链接。

神经网络训练

神经网络训练是深度学习的一个基本方面。涵盖该主题的全部深度超出了本概述的范围。然而,为了理解关键的学习阶段,人们必须至少对神经网络的训练过程有一个基本的了解。

神经网络训练的目标是-从具有随机初始化的权重的神经网络开始-学习一组参数,这些参数允许神经网络在给定一些输入的情况下准确地产生期望的输出。这种输入和输出可以采取多种形式——图像上预测的关键点、文本分类、视频中的对象检测等等。此外,神经网络体系结构经常根据输入数据的类型和正在解决的问题而变化。然而,尽管神经网络定义和应用存在差异,但模型训练的基本概念仍然(或多或少)相同。

神经网络在不同问题域中学习的输入输出图的描述(作者创建)

为了学习输入和期望输出之间的映射,我们需要一个(最好是大的)输入输出对的训练数据集。为什么?这样我们就可以:

  1. 根据数据做出预测
  2. 查看模型的预测与期望的输出相比如何
  3. 更新模型参数以提高预测效果

通过训练数据更新模型参数以更好地匹配已知标签的过程是学习过程的关键。对于深度网络,该学习过程针对几个时期执行,定义为通过训练数据集的完整遍数。

为了确定模型预测的质量,我们定义了一个损失函数。训练的目标是最小化这个损失函数,从而最大化对训练数据的模型预测的质量。因为损失函数通常选择为可微分的,所以我们可以对模型中每个参数的损失进行微分,并使用随机梯度下降(SGD) 生成模型参数的更新。总的来说,SGD 只是:

  • 计算损失函数的梯度
  • 使用微积分链规则计算模型中每个参数的损失梯度
  • 从每个参数中减去由学习率缩放的梯度

虽然详细理解起来有点复杂,但直观层面上的 SGD 非常简单— 每次迭代只是确定模型参数应该更新以减少损失的方向,并在该方向上迈出一小步。我们对网络参数进行优化,以最小化训练损失。请参见下面对该过程的示意图,其中学习速率设置控制每个 SGD 步长的大小。

不同学习率下的训练损失最小化(作者创建)

总之,神经网络训练过程持续几个时期,每个时期对训练数据执行 SGD 的多次迭代——通常一次使用几个数据示例的小批量。在训练过程中,神经网络在训练数据集上的损失越来越小,从而产生一个模型,该模型很好地拟合了训练数据,并(有希望地)推广到看不见的测试数据,这意味着它也表现良好。

神经网络训练迭代步骤的基本说明(由作者创建)

有关神经网络训练过程的高级描述,请参见上图。关于神经网络训练还有更多细节,但是本概述的目的是理解关键的学习阶段,而不是深入研究神经网络训练。因此,我在下面提供了一些有用文章的链接,可以帮助感兴趣的读者更详细地理解关键的神经网络训练概念。

正规化

神经网络训练执行更新,使训练数据集的损失最小化。然而,我们训练这个神经网络的目标不仅仅是在训练集上获得良好的性能。我们还希望网络在部署到现实世界中时,能够在看不见的测试数据上表现良好。在这种看不见的数据上表现良好的模型被认为能够很好地概括 T1。

最小化训练数据的损失并不保证模型将一般化。例如,模型可能只是“记住”每个训练示例,从而阻止它学习可应用于看不见的数据的可归纳模式。为了确保良好的泛化,深度学习实践者通常利用正则化技术。存在许多这样的技术,但与本文目的最相关的是重量衰减数据增加

权重衰减是一种常用于训练机器学习模型(甚至超越神经网络)的技术。这个想法很简单。在训练期间,调整您的损失函数来惩罚具有大幅度的学习参数的模型。然后,优化损失函数变成了 (i) 最小化训练集上的损失和 (ii) 使网络参数在量值上较低的联合目标。训练期间体重衰减的强度可以调整,以在这两个目标之间找到不同的折衷方案——这是学习过程中可以调整/修改的另一个超参数(类似于学习速率)。想了解更多,建议阅读这篇文章

根据应用的领域和环境,数据扩充有许多不同的形式。但是,数据扩充背后的基本思想保持不变-每次您的模型在训练过程中遇到一些数据时,应该以仍然保留数据输出标签的方式随机更改数据一点点。因此,您的模型不会两次看到相同的数据示例。相反,数据总是稍有扰动,阻止模型简单地记忆训练集中的例子。尽管数据扩充可以采取多种不同的形式,但是有大量的调查报告和解释可以用来更好地理解这些技术。

培训、预培训和微调

除了本节介绍的基本神经网络训练框架,人们还会经常遇到针对深度网络的预训练微调的想法。所有这些方法都遵循上面概述的相同学习过程——预训练和微调只是针对相同训练过程的特定、稍微修改的设置的术语。

预训练通常是指在非常大的数据集上从头开始(即随机初始化)训练模型。尽管在大的预训练数据集上的这种训练在计算上是昂贵的,但是从预训练中学习的模型权重可以是非常有用的,因为它们包含从训练大量数据中学习的模式,这些模式可以在别处推广(例如,学习如何检测边缘、理解形状/纹理等)。).

说明神经网络的预训练、微调和正常训练之间的差异(由作者创建)

预先训练的模型参数通常被用作在其他数据集上执行训练的“热启动”,其他数据集通常被称为下游或目标数据集。不是在执行下游训练时随机初始化模型参数,而是我们可以将模型参数设置为等于预先训练的权重,并在下游数据集上微调或进一步训练这些权重;见上图。如果预训练数据集足够大,这种方法会产生改进的性能,因为模型在更大的数据集上进行预训练时会学习概念,而这些概念不能单独使用目标数据集来学习。

出版物

在以下概述中,我将讨论几篇证明深度神经网络中存在关键学习期的论文。第一篇论文研究了数据模糊对学习过程的影响,而随后的论文研究了在训练期间关于模型正则化和数据分布的学习行为。尽管采用了不同的方法,但这些作品都遵循类似的方法:

  • 对学习过程的一部分施加一些损害
  • 分析这种不足如何影响训练后的模型性能

深度网络中的关键学习期[1]

(来自[1])

主旨。这项由深度学习和神经科学专家混合进行的研究,探索了生物和人工神经网络中关键学习时期之间的联系。也就是说,作者发现将缺陷(例如,图像模糊)引入深度神经网络的训练,即使只是很短的一段时间,也会导致性能下降。更进一步说,对表现的损害程度取决于损害出现的时间和时间——这一发现反映了生物系统的行为。

例如,如果在训练开始时应用损害,则存在足够数量的受损学习时期,超过这些时期,深度网络的性能将永远不会恢复。生物神经网络在学习的早期损伤方面表现出相似的特性。也就是说,在发展的早期阶段经历太长时间的学习障碍可能会产生永久性的后果(例如弱视)。上图展示了人工和生物系统中关键学习阶段的影响。

概括地说,本文中的发现可以简单地表述如下:

如果一个人在训练的早期以持续的方式损害了深度网络的训练过程,网络的性能就不能从这种损害中恢复

为了更好地理解这一现象,作者定量研究了网络权重矩阵的连通性,发现学习是由两步过程组成的“记忆”,然后是“遗忘”。更具体地说,网络在早期学习阶段记忆数据,然后当它开始学习更有效、可概括的模式时,重新组织/忘记这些数据。在早期记忆期间,网络在损失景观中航行一个瓶颈——当它穿过这个狭窄的景观时,网络对学习障碍相当敏感。然而,最终,该网络摆脱了这一瓶颈,发现了一个包含许多高性能解决方案的更广阔的山谷——该网络对该区域内的学习障碍更具鲁棒性。

方法论。在这项工作中,作者在 CIFAR-10 数据集上训练了一个卷积神经网络架构。为了模拟学习障碍,数据集内的图像在学习过程中的不同时间点被模糊化不同数量的时期。然后,在完整的训练过程完成后,通过模型的测试精度来测量这种损伤的影响。值得注意的是,学习障碍通常仅适用于整个学习过程的一小部分。通过研究这种损伤对网络性能的影响,作者发现:

  • 如果在训练期间没有足够早地消除损害,那么网络性能将会永久受损。
  • 对这种学习障碍的敏感性在学习的早期(即前 20%的时期)达到峰值。

为了进一步探索深度网络中关键学习期的属性,作者测量了模型参数中的 Fisher 信息,该信息定量描述了网络层之间的连接性,或网络权重中包含的“有用信息”的数量。

费希尔信息被发现在早期训练阶段迅速增加,然后在训练的剩余阶段衰减。这种趋势表明,该模型首先在早期学习阶段记忆信息,然后通过消除冗余和建立对数据中非相关可变性的鲁棒性,慢慢地重新组织或减少这些信息,即使分类性能有所提高。当应用损伤时,Fisher 信息增长并保持比正常情况高得多,即使在赤字被消除之后,揭示网络在这种情况下学习可概括数据表示的能力较低。这一趋势的图示见下图。

(来自[1])

发现。

  • 在训练的早期阶段,网络性能对损伤最敏感。如果图像模糊没有在深度网络的前 25–40%的训练时段内消除(即,确切的比例取决于网络架构和训练超参数),那么网络性能将会永久受损。
  • 数据的高级更改(例如..图像的垂直翻转、输出标签的排列)对网络性能没有任何影响。此外,用白噪声进行受损训练不会损害网络性能——完全感觉剥夺(即,这类似于生物系统中的黑暗饲养)对学习没有问题。
  • 如果执行得不好(例如,使用太模糊的图像),预训练可能对网络性能有害。
  • Fisher 信息通常位于中间网络层的最高层,在这里可以最有效地处理中低层图像特征。对学习过程的损害导致 Fisher 信息集中在最终的网络层,该网络层不包含低级或中级特征,除非在训练中足够早地消除该缺陷。

深度网络正规化的时间问题:权重衰减和数据增加影响早期学习动态,在收敛附近关系不大[2]

(来自[2])

主旨。 正则化的典型观点(例如,通过权重衰减或数据扩充)假定正则化简单地改变网络的损失情况,以使学习过程偏向具有低曲率的最终解决方案。学习的最后一个关键点是损失图中的平滑/平坦,这(可以说)表明了良好的泛化性能。这样的直觉是否正确是一个激烈争论的话题——你可以在网上阅读几篇关于局部曲率和泛化之间联系的有趣文章

本文提出了正则化的另一种观点,超越了这些基本的直觉。作者发现,在训练的早期阶段之后去除正则化(即,权重衰减和数据增加)不会改变网络性能。另一方面,如果正则化仅在训练的后期阶段应用,它不会有益于网络性能——网络的性能就像从未应用正则化一样差。这些结果共同证明了深层网络正规化的关键时期的存在,这是最终性能的指示;见上图。

这样的结果揭示了正则化并不简单地将网络优化偏向于具有良好泛化能力的最终解决方案。如果这种直觉是正确的,那么在训练后期——当网络开始收敛到它的最终解时——取消正则化将会有问题。相反,发现正则化对早期学习瞬态有影响,使网络优化过程偏向包含许多具有良好泛化能力的解决方案的损失景观区域,这些解决方案将在以后的训练中探索。

方法论。 与之前的工作类似,在 CIFAR-10 数据集上使用卷积神经网络架构研究了正则化对网络性能的影响。在每个实验中,作者将正则化(即,权重衰减和数据增加)应用于第一个 t 时期的学习过程,然后在没有正则化的情况下继续训练。当比较在训练开始时在不同持续时间应用正则化的网络的泛化性能时,作者发现仅在训练的早期阶段执行正则化可以实现良好的泛化。

除了这些初始实验之外,作者还进行了一些实验,在这些实验中,正则化只在训练中的不同点应用不同的持续时间。这些实验证明了正则化的临界周期的存在。也就是说,如果正则化仅在训练中的某个较晚时期之后应用,那么它在最终的泛化性能方面没有产生任何益处。这样的结果反映了[1]中的发现,因为所施加的正则化的缺乏可以被视为损害网络性能的学习缺陷的一种形式。

发现。

  • 在最初的“关键”训练时期,正规化对最终表现的影响是最大的。
  • 重量衰减的临界周期行为比数据增加的更明显。在整个训练过程中,数据扩充同样会影响网络性能,而权重衰减在训练的早期应用时最为有效。
  • 在训练的整个持续时间内执行正则化产生了与那些仅在早期学习过渡期间(即,前 50%的训练时期)接收正则化的网络相比,实现了相当的泛化性能的网络。
  • 在以后的训练期间使用正则化或不使用正则化会导致不同的收敛点(即,最终的解是不相同的),但是最终的泛化性能是相同的。这样的结果揭示了正则化在早期将训练“引导”到具有多个不同解决方案的区域,这些解决方案表现得同样好。

关于热启动神经网络的训练[3]

(摘自[3])

主旨。在现实世界的机器学习系统中,新数据以增量方式到达是很常见的。一般来说,我们将从一些聚合数据集开始,然后随着时间的推移,随着新数据的出现,该数据集将不断增长和发展。在这种情况下,深度学习模型的序列在数据集的每个版本上进行训练,其中每个模型利用了迄今为止可用的所有数据。然而,给定这样的设置,人们可能开始想知道“热启动”是否可以被公式化,使得该序列中的每个模型以先前模型的参数开始训练,模仿允许模型训练更高效和高性能的预训练形式。

在[3]中,作者发现简单地用先前训练的模型的参数初始化模型参数不足以实现良好的泛化性能。虽然最终的训练损失是相似的,但是与使用完整数据集随机初始化和训练的模型相比,首先在较小的数据子集上进行预训练,然后在完整数据集上进行微调的模型实现了降低的测试准确性。这一发现模拟了[1,2]中概述的关键学习期的行为-早期阶段的训练完全集中在较小的数据子集(即新数据到达之前的数据集版本),一旦模型暴露于完整的数据集,就会导致性能下降。然而,作者提出了一种简单的热启动技术,可以用来避免这种测试精度的恶化。

方法论。考虑新数据每天到达系统一次的设置。在这样的系统中,当新数据每天到达时,人们会理想地重新训练他们的模型。然后,为了最小化训练时间,可以通过在训练/微调之前用前几天的模型的参数初始化新模型的参数来实现简单的热启动方法。然而,有趣的是,发现这种热启动方法产生的模型泛化能力很差,这表明在关键时期应用时,在不完整的数据子集上进行预训练是一种学习障碍。

为了克服这种损伤的影响,作者提出了一种叫做收缩、扰动、重复的简单技术:

  1. 将模型权重向零收缩。
  2. 向模型权重添加少量噪波。

如果将这样的过程应用于在不完整的数据子集上训练的先前模型的权重,则该模型的参数可以用于在整个数据集上热启动训练,而不会导致泛化性能的任何恶化。虽然噪声的收缩量和规模给训练过程引入了新的超参数,但这种简单的技巧产生了显著的计算节省,这是因为它能够热启动,从而加速模型训练,而不会恶化网络性能。

为了阐明这种方法的有效性,作者解释说,一种简单的热启动方法在新旧数据的梯度之间经历了显著的不平衡。众所周知,这种不平衡会对学习过程产生负面影响[4]。然而,在训练 (i)(ii) 之前收缩和噪声化模型参数保持了网络预测,并且平衡了新旧数据的梯度贡献,从而在利用先前学习的信息和适应新到达的数据之间取得了平衡。

发现。

  • 尽管在深度网络中证明了与不完整数据集相关的关键学习周期,但更简单的模型(例如,逻辑回归)不会经历这种影响(即,可能因为训练是)。
  • 由于简单的热启动导致的测试精度下降不能通过调整批量大小或学习率等超参数来缓解。
  • 只需要在不完整的数据子集上进行少量的训练(即,几个时期)就可以破坏在完整数据集上训练的模型的测试准确性,这进一步揭示了在不完整数据上的训练是一种与关键学习阶段有关的学习障碍。
  • 利用收缩、扰动、重复方法完全消除了随机初始化和热启动模型之间的泛化差距,从而显著节省了计算量。

深度学习理论是否失之于众?

关键学习周期的存在为深度神经网络的学习过程提供了一个有趣的视角。也就是说,这种网络不能从早期训练阶段的损伤中恢复的事实揭示了学习分两个不同的阶段进行,每个阶段都有有趣的特性和行为。

  1. 关键学习期:识记期。网络必须穿越损失景观的狭窄/瓶颈区域。
  2. 收敛到最终解:遗忘期。在穿越了损失景观的瓶颈区域之后,网络进入了一个由许多同等性能的解决方案组成的宽广的山谷,在那里它可以收敛。

早期学习过渡期间的关键学习阶段在决定最终网络性能方面起着关键作用。以后对学习过程的改变不能减少早期的错误。

有趣的是,深度学习领域的大多数理论工作本质上都是渐进的。简而言之,这意味着这种分析方法关注于经过多次迭代训练后的最终收敛解决方案的属性。没有关键学习时期或不同学习阶段的概念出现。令人信服的实证结果概述了深度网络中关键学习期的存在,表明深度学习比当前的渐近分析所揭示的要多。真正抓住深度网络中学习的复杂性的理论分析还没有到来。

外卖食品

概述中的要点可以简单地表述为:

  • 神经网络训练似乎分两个主要阶段进行——记忆(T0)和遗忘(T2)。
  • 在最初的阶段削弱学习过程是不好的。

更具体地说,第一阶段的学习障碍不仅仅是坏的…它们看起来是灾难性的。在第二阶段,人们无法从这些损伤中恢复,在大多数情况下,由此产生的网络注定性能不佳。这里概述的工作已经在许多领域中证明了这种性质,表明在学习的第一阶段应用的下列损伤会降低网络的泛化能力:

  • 足够模糊的图像
  • 缺乏正则化(即数据增加或权重衰减)
  • 缺乏足够的数据

关键学习阶段提供了神经网络训练的独特视角,即使是经验丰富的研究人员也会质疑他们的直觉。这种神经网络训练的两阶段观点挑战了普遍持有的信念,并且没有反映在深度网络的大部分理论分析中,这表明如果我们要共同达成对深度学习的更细致入微的理解,还有更多工作要做。考虑到这一点,人们可能会开始怀疑我们对深层网络的理解是否会有最根本的突破。

进一步阅读

  1. 线性模式连接和 LTH
  2. 友好训练:神经网络可以调整数据,使学习更容易
  3. 灾难性费希尔爆炸:早期费希尔矩阵影响泛化

结论

非常感谢你阅读这篇文章。我希望你喜欢它并且学到一些新的东西。我是 Cameron R. Wolfe ,是 Alegion 的研究科学家,也是莱斯大学的博士生,研究深度学习的经验和理论基础。如果你喜欢这篇文章,请关注我的深度(学习)焦点时事通讯,在那里我挑选了一个双周一次的深度学习研究主题,提供了对相关背景信息的理解,然后概述了一些关于该主题的热门论文。也可以看看我的其他著述

文献学

[1]阿奇利,亚历山德罗,马特奥·罗韦尔和斯特凡诺索阿托。"深度网络中的关键学习期."国际学习代表会议。2018.

[2] Golatkar、Aditya Sharad、Alessandro Achille 和 Stefano Soatto。"时间对于规范深层网络很重要:权重衰减和数据增加会影响早期学习动态,但对于接近收敛则没什么影响."神经信息处理系统进展 32 (2019)。

[3]阿什、乔丹和瑞安·亚当斯。"热启动神经网络训练."神经信息处理系统进展33(2020):3884–3894。

[4]于,天河,等.“多任务学习的梯度手术”神经信息处理系统进展33(2020):5824–5836。

[5]埃里克·R·坎德尔、詹姆斯·H·施瓦茨、托马斯·M·杰塞尔、史蒂文·A·西格尔鲍姆和詹姆斯·哈德斯佩斯。神经科学原理。纽约州纽约市麦格劳-希尔公司,第 5 版,2013 年。

交叉熵,负对数似然,等等

原文:https://towardsdatascience.com/cross-entropy-negative-log-likelihood-and-all-that-jazz-47a95bd2e81

数据科学中广泛使用的两个密切相关的数学公式,以及它们在 PyTorch 中的实现

克劳迪奥·施瓦兹在 Unsplash 上的照片

TL;速度三角形定位法(dead reckoning)

  • 负对数似然最小化是最大似然估计问题的代理问题。
  • 交叉熵和负对数似然是密切相关的数学公式。
  • 计算负对数似然的基本部分是“对正确的对数概率求和”
  • CrossEntropyLoss 和 NLLLoss 的 PyTorch 实现在预期的输入值上略有不同。简而言之,CrossEntropyLoss 期望原始预测值,而 NLLLoss 期望对数概率。

交叉熵==负对数似然?

W 当我刚开始学习数据科学时,我已经建立了一种印象,交叉熵和负对数似然只是同一事物的不同名称。这就是为什么后来当我开始使用 PyTorch 来构建我的模型时,我发现很难理解 CrossEntropyLossNLLLoss 是两个不同的损失,它们不会产生相同的值。在更多的阅读和实验之后,我对 PyTorch 中实现的这两者之间的关系有了更深的理解。

在这篇博文中,我将首先介绍负对数似然背后的一些数学知识,并向您展示这个想法在计算上非常简单!你只需要总结出正确的对数概率编码条目。然后,我将展示一个最小的数值实验,它帮助我更好地理解 PyTorch 中 CrossEntropyLoss 和 NLLLoss 之间的差异。

如果你只想知道两个损失之间的区别,可以直接跳到数值实验的最后一节。要不,我们先弄个…

深入研究数学!

最大似然估计

让我们首先考虑二元分类的情况。给定由θ参数化的模型 f,主要目标是找到θ,使得最大化观察数据的可能性

其中 y_hat 是正类的预测概率,\sigma 是将值从(-inf,inf)映射到[0,1]的某个非线性激活函数。非线性激活的一个流行选择是 sigmoid:

形式上,可能性定义为[1]:

对数似然

请注意,y_i 和(1-y_i)的幂只不过是告诉您“我们只想计算与真实标签相关联的预测值”的一种巧妙方式

换句话说,为了感觉我们的预测有多好,我们可以看看分配给正确标签的预测概率。这在取对数时可以得到更好的说明,对数将乘积转化为总和,从而得到更常用的对数似然:

因为我们处于二进制分类设置中,y 取 0 或 1。因此,对于每个索引 I,我们要么添加 y_hat_i 的对数,要么添加(1-y_hat_i)的对数。

  • y_hat_i:第 I 个数据点为正的预测概率
  • (1-y_hat_i):第 I 个数据点为负的预测概率

总结正确条目(二进制大小写)

下面的动画进一步说明了选择正确条目进行求和的想法。它由以下步骤组成:

  1. 从正类的预测概率(y_hat)开始。如果给我们原始预测值,应用 sigmoid 使其成为概率。
  2. 计算负类(1-y_hat)的概率。
  3. 计算对数概率。
  4. 合计与真实标签相关的对数概率。

二元负对数似然的计算,图像由作者制作(用 Manim 制作)

在这个例子中,对数似然结果是-6.58。

注意,挑选矩阵中正确条目的最终操作有时也称为屏蔽。遮罩是基于真实标签构建的。

最小化负对数似然

最后,因为对数函数是单调的,所以最大化似然性与最大化似然性的对数(即对数似然性)是相同的。让事情变得复杂一点,因为“最小化损失”更有意义,我们可以取对数似然的负值并将其最小化,从而得到众所周知的负对数似然损失:

总的来说,我们最初的目标是在给定一些参数设置的情况下,最大化观察数据的可能性。最小化负对数似然目标与我们的原始目标是“相同”的,在某种意义上,两者应该具有相同的最优解(在凸优化设置中是迂腐的)。

交叉熵

在离散设置中,给定两个概率分布 pq ,它们的交叉熵定义为

注意,上述负对数似然的定义与 y(真实标签)和 y_hat(真实标签的预测概率)之间的交叉熵相同。

这两个损失之间的相似性导致了我最初的困惑。如果 PyTorch 有两个独立的函数(CrossEntropyLoss 和 NLLLoss ),为什么它们是相同的?正如我们将在稍后使用 PyTorch 的一个小型数值实验中看到的,这两者确实非常相似。一个微小的区别是,CrossEntrypyLoss 的实现隐式地应用了一个 softmax 激活,然后是一个 log 转换,而 NLLLoss 没有。

归纳为多类

在进行数值实验以了解 PyTorch 中实现的一些损失函数是如何相关的之前,让我们看看负对数似然如何推广到多类分类设置。

牢记“掩蔽原理”,我们可以将对数似然重写为

其中上面的指数(而不是幂)意味着我们只查看与真实标签相关的对数概率。在二进制设置中,

回想一下,y_hat_i 是 sigmoid 激活的 f_\theta_i,为简单起见,我们将 f_\theta_i 称为 z_i。

给定上面重写的对数似然,很容易将其直接应用于多类(C 类)设置,其中 y 现在取值从 0 到 C-1。除了我们需要确定 y_hat_i 定义了一个概率分布,即 1)它在 0 和 1 之间有界,以及 2)分布总和为 1。在二进制设置中,这两个条件由 sigmoid 激活和“非正意味着负”的隐含假设来处理。

Softmax 激活

事实证明,softmax 功能是我们所追求的

在这种情况下,z_i 是一个维数为 c 的向量。我们可以检查它是否定义了一个概率分布,因为它介于 0 和 1 之间,并且是归一化的。

此外,不难看出,当 C=2,并设置 z_i_0(“负类”的预测分数)为 0 时,我们可以完全恢复 sigmoid 函数(试试看!).

汇总正确的条目(多类情况)

现在,我们准备将屏蔽策略应用于多类分类设置,以计算相应的负对数似然。与之前类似,步骤包括

  1. 从预测值(还不是概率)z 开始。
  2. 使用 softmax 将值转换为类概率(y_hat ),然后取对数概率(log y_hat)。
  3. 合计与真实标签相关的对数概率。

多类负对数似然的计算,作者图像(由 Manim 制作)

在这个例子中,对数似然结果是-6.91。

数值实验

了解 CrossEntropyLoss 和 NLLLoss(以及 BCELoss 等)的区别。),我设计了一个如下的小数值实验。

在二进制设置中,我首先从正态分布生成一个大小为 5 的随机向量(z ),并手动创建一个具有相同形状的标签向量(y ),其条目可以是 0 或 1。然后我使用 softmax(第 8 行)计算基于 z 的预测概率(y_hat)。在第 13 行,我应用前面部分中推导出的负对数似然公式来计算这种情况下的负对数似然值。使用 BCELoss 和 y_hat 作为输入,BCEWithLogitLoss 和 z 作为输入,我观察到上面计算的相同结果。

在多类设置中,我生成 z2,y2,并使用 softmax 函数计算 yhat2。这一次,以对数概率(yhat2 的对数)作为输入的 NLLLoss 和以原始预测值(z)作为输入的 CrossEntropyLoss 产生了使用先前导出的公式计算的相同结果。

代码片段的结果截图,图片由作者提供。

为了简洁起见,我在这里只包括了一个最小的比较集。查看上面 Github gist 的完整版,进行更全面的比较。在那里,我还包括了 NLLLoss 和 BCELoss 之间的比较。本质上,要在二进制设置中使用 NLLLoss,需要扩展预测值,如第一个动画所示。

什么时候用哪个损耗?

如 PyTorch 中所实现的,损失函数通常采用 Loss(h,y) 的形式,其中 h 是预测值或其某种转换版本,而 y 是标签。仅考虑 h 最多只能二维的简单情况,上面的小实验得出以下建议。

在下列情况下使用 BCELoss 和 BCEWithLogitsLoss

hy 都是一维的, y 取 0 或 1。

  • 如果 h 是数据点为正的概率,则使用 BCELoss。
  • 如果 h 是逻辑值,则使用 BCEWithLogits,即,您想要使用 sigmoid 函数将原始预测值激活为概率。

在下列情况下使用 NLLLoss 和 CrossEntropyLoss

h 是二维的, y 是一维的,取值从 0 到 C-1,有 C 个类。

  • 如果 h 对对数似然进行编码,则使用 NLLLoss(它主要执行屏蔽步骤,然后是均值缩减)。
  • 如果 h 对需要使用 softmax 功能激活的原始预测值进行编码,则使用 CrossEntropyLoss。

多标签分类呢?

在多标签分类设置中,一个数据点可以与多个(或根本没有)类相关联,而不是在多类情况下,每个带标签的数据点仅与一个类标签相关联。在这种情况下,常见的策略是将问题视为多个二元分类问题,每个类一个。事实证明,只要 hy 的形状一致,BCELoss 和 BCEWithLogitsLoss 在这种情况下就能很好地工作。

结论

总之,我们看到负对数似然最小化是一个寻找最大似然估计解的代理问题。结果表明,两个概率分布之间的交叉熵公式与负对数似然性一致。然而,在 PyTorch 中实现时,CrossEntropyLoss 期望原始预测值,而 NLLLoss 期望对数概率。

[1] P. Mehta,M. Bukov,C. Wang,A.G.R. Day,C. Richardson,C.K. Fisher,D.J. Schwab,物理学家机器学习的高偏差、低方差介绍 (2019) 物理报告

[2]PyTorch 损失功能文档页https://pytorch.org/docs/stable/nn.html#loss-functions

[3]用于生成动画的代码(使用 Manim 库)https://github . com/remy lau/visualizations/blob/main/nll loss/nll loss . py

[4]数值实验代码 gists
https://gist . github . com/remy lau/aa 2e 17 c 0612 df 83 a0b 8 dcfa 77 e 525238
https://gist . github . com/remy lau/c 0892869 BD 769 c 421364 a 230d 33 a129 a

[5]其他一些关于负对数似然的博客帖子,我觉得很有帮助
https://notesbylex.com/negative-log-likelihood.html】http://deep learning . Stanford . edu/tutorial/supervised/SoftmaxRegression/

[6]概述 PyTorch 中更多损失函数的博文
https://neptune.ai/blog/pytorch-loss-functions
https://medium . com/uda city-py torch-challengers/a-brief-overview-of-loss-functions-in-py torch-c0ddb 78068 f 7

在交叉验证中处理组

原文:https://towardsdatascience.com/cross-validate-on-data-containing-groups-correctly-ffa7173a37e6

尼古拉斯·孔德在 Unsplash 上的照片

您的数据包含组吗?让我们看看如何在这种情况下正确使用交叉验证

构建模型时,您是否遇到过数据集内似乎存在某种“分组”的情况?您希望确保与同一组相关的所有数据都被考虑用于定型模型或评估模型,而不是两者都考虑。这确保了没有数据泄漏,也就是说,在训练期间,模型不会看到保留测试集中的数据。

这类数据的例子可以是客户 交易数据,其中您希望将同一个客户的交易保存在一起。另一个例子可能是包含不同的数据,并且您希望将与一年相关的所有数据保存在测试集或训练集中。

数据分割和交叉验证

您可能会想——为什么我们需要拆分数据来训练模型?为什么我们不能把所有的数据都用于训练和测试呢?让我们想一想…

你可能已经明白了,但原因是这样的-如果你使用所有的数据来训练模型,然后根据相同的数据评估这个模型,那么你的模型将几乎完美地执行,因为它在训练时已经看到了所有的数据。这种模型不能很好地概括现实世界中的情况,在现实世界中,数据可能与模型所看到的有些不同。为了避免这种情况,并确保我们是在看不见的数据上评估我们的模型,以及看看它们在现实世界中的表现,我们使用了数据分割。在分割过程中,我们使用一定百分比的数据作为保留集,而剩余部分用于训练。

训练/测试分割(图片由作者提供)

现在问题来了,我们如何知道分割数据的最佳方式是什么,这将给我们一个真正好的模型?为了解决这个问题,我们使用 交叉验证

交叉验证中,我们将数据分成块,并使用每个块来测试模型,同时对剩余的块进行训练。在检查完所有的块之后,我们取所有评估的平均值来返回最终的模型性能。这解决了两件事-

  • 确保我们充分利用我们的数据
  • 如果我们比较不同的建模技术,我们可以比较它们的交叉验证性能,以选择最好的。

交叉验证有助于我们评估模型的性能,以及在数据有限的情况下它的泛化能力

k 倍

K 倍 是做交叉验证最简单的方法。这里的“K”代表我们在创建拆分时将数据分成的块(折叠)的数量。下图显示了一个简单的三折叠示例,以及如何在对其他折叠进行训练的同时,使用每个折叠来评估模型的性能。

三重交叉验证(图片由作者提供)

为了先看看这在代码中是什么样子,让我们随机创建一个小数据集来使用。

import numpy as np# create a dataset containing 6 samples
# each sample has 5 features
X = [np.random.uniform(0.0, 10.0, 5) for _ in range(6)]

现在让我们看看 KFold 如何处理这个数据集。我们将使用 KFold 的 scikit-learn 实现。

from sklearn.model_selection import KFold
kf_cv = KFold(n_splits=3, shuffle=True, random_state=11)for split, (ix_train, ix_test) in enumerate(kf_cv.split(X)):
    print(f“SPLIT {split+1}”)
    print(f“TRAIN INDEXES: {ix_train}, TEST INDEXES: {ix_test}\n”) ## OUTPUT ##
SPLIT 1
TRAIN INDEXES: [0 1 3 5], TEST INDEXES: [2 4]SPLIT 2
TRAIN INDEXES: [0 1 2 4], TEST INDEXES: [3 5]SPLIT 3
TRAIN INDEXES: [2 3 4 5], TEST INDEXES: [0 1]

正如你在上面的代码中看到的,我们运行 KFold 进行了 3 次折叠。因为我们的样本数据集由 6 个样本组成,所以它将它们分成每个包含 2 个样本的块,并使用这些块来创建训练/测试分割。

K-折叠组

现在我们已经看到了 K-Fold 是如何工作的,如果我们的数据集中有组,我们该怎么办?使用常规 K 折叠时,无法指定组。幸运的是,我们有 组 K-Fold ,它只是 K-Fold 的增强,有助于考虑数据中的组。

由 3 个小组组成的小组文件夹(图片由作者提供)

K-Fold 组的属性(来自 scikit 的-learn):

-具有非重叠组的 K 重迭代器变量。
-同一组不会出现在两个不同的折叠中(不同组的数量必须至少等于折叠的数量)。
-褶皱大致平衡,即每个褶皱中不同组的数量大致相同。

让我们看看 k-fold 组如何在同一个数据集上工作。 grps 只是一个代表每个样本属于哪个组的列表。我们将这个组列表作为参数与数据集一起传递给 split()函数

# assign groups to samples
grps = [1,2,1,1,2,3]from sklearn.model_selection import GroupKFold
gkf_cv = GroupKFold(n_splits=3)for split, (ix_train, ix_test) in enumerate(gkf_cv.split(X, groups=grps)):
    print(f“SPLIT {split+1}”)
    print(f“TRAIN INDEXES: {ix_train}, TEST INDEXES: {ix_test}\n”) ## OUTPUT ##
SPLIT 1
TRAIN INDEXES: [1 4 5], TEST INDEXES: [0 2 3]SPLIT 2
TRAIN INDEXES: [0 2 3 5], TEST INDEXES: [1 4]SPLIT 3
TRAIN INDEXES: [0 1 2 3 4], TEST INDEXES: [5]

K-Fold 组确保属于同一组的所有样本都在一起。

结论

在某些机器学习问题的情况下,跟踪数据集中的分组是非常重要的,在这种情况下,Group K-Fold 可以提供很大的帮助。

既然明白了什么是团 K 折,那么这个 团洗牌拆分 是什么?这些拆分与 K-fold 组有何不同?让我们在本系列的下一篇文章中尝试找到这些和其他问题的答案。敬请关注…

参考

交叉验证

原文:https://towardsdatascience.com/cross-validation-705644663568

它是什么,为什么使用它?

照片由耶鲁安穴獭Unsplash

回归和分类机器学习模型旨在从数据中包含的变量预测值或类。每个模型都有自己的算法来尝试识别数据中包含的模式,从而做出准确的预测。

模型除了要准确,还必须是通才,能够解释以前从未见过的数据,并得出适当的结果。评估模型泛化能力的一种方法是应用交叉验证。

但是什么是交叉验证呢?

市政府Unsplash 拍摄的照片

交叉验证是一种用于获得模型整体性能评估的技术。有几种交叉验证技术,但它们基本上都是将数据分成训练和测试子集。

顾名思义,训练子集将在训练过程中用于计算模型的超参数。为了计算模型的泛化能力,在训练阶段之后,使用测试模型。

使用来自测试数据集的真实标签和由经过训练的模型对测试数据做出的预测来计算模型的性能度量,例如准确度(分类)和均方根绝对误差(回归)。

交叉验证技术有很多种,在这篇文章中我将讨论其中的三种:保持K-Fold留一法。

维持交叉验证

咖啡极客Unsplash 上的照片

可能最著名的交叉验证技术是拒绝验证。该技术包括将整个数据集分成两组,没有重叠:训练集和测试集。根据项目的不同,这种分离可以是混排数据,也可以是保持数据的排序。

在项目和研究中通常会看到 70/30 的分割,其中 70%的数据用于训练模型,其余 30%用于测试和评估模型。但是,这个比率不是一个规则,它可能会根据项目的特殊性而变化。

应用于数据集的维持交叉验证示例-按作者分类的图像

在 Python 中,使用 scikit-learn 库中的train_test_split 函数可以轻松完成维持交叉验证。

使用乳腺癌数据集和 70/30 分割,我们得到:

k 倍交叉验证

照片由安迪·霍尔Unsplash 拍摄

在将数据分成训练集和测试集之前,K-Fold 交叉验证将整个数据分成 K 个大小近似的独立子集。只有这样,每个子集才会被分成训练集和测试集。

每个子集用于训练和测试模型。在实践中,这种技术产生 K 个不同的模型和 K 个不同的结果。K 倍交叉验证的最终结果是每个子集的单个指标的平均值。

应用于数据集的三重交叉验证示例-按作者分类的图像

值得注意的是,由于 K 倍将原始数据划分为更小的子集,因此必须考虑数据集的大小和 K 个子集。如果数据集很小或者 K 的数量太大,那么产生的子集可能会变得非常小。

这可能导致只有少量数据用于训练模型,从而导致性能不佳,因为算法由于缺乏信息而无法理解和学习数据中的模式。

Python 也有一种简单的方法来执行 K-Fold 分割,即使用来自 scikit-learn 库Kfold

使用与之前相同的数据集,K = 3,我们得到:

基本上,维持交叉验证与一折交叉验证相同。

留一交叉验证

威尔·梅尔斯在 Unsplash 拍摄的照片

留一交叉验证包括创建多个训练和测试集,其中测试集仅包含原始数据的一个样本,而训练集包含原始数据的所有其他样本。对原始数据集中的所有样本重复此过程。

这种类型的验证通常非常耗时,因为如果使用的数据包含 n 个样本,算法将不得不训练(使用 n-1 个样本)并评估模型 n 次。

从积极的一面来看,这种技术,在本文所见的所有技术中,是模型中用于训练的样本量最大的一种,这可能会产生更好的模型。此外,不需要打乱数据,因为所有可能的训练/测试集的组合都将被生成。

应用于数据集的留一交叉验证示例-按作者分类的图像

使用LeaveOneOutscikit-learn 库也可以进行留一交叉验证

使用乳腺癌数据集,我们有:

与维持类似,留一交叉验证也是一种特殊类型的 K-Fold,其中 K 的值等于数据集的样本数。

性能比较

Sabri TuzcuUnsplash 上拍摄的照片

为了显示每种交叉验证的性能差异,这三种技术将与简单的决策树分类器一起使用,以预测乳腺癌数据集中的患者是良性(1 类)还是恶性(0 类)肿瘤。为了进行比较,将使用 70/30 分割、3 折和留一的维持。

使用的代码可以在我的 github 页面找到:https://github . com/alerlemos/medium/blob/main/cross _ validation/cross _ validation _ examples . ipynb

获得的结果如下表所示:

每种交叉验证技术获得的结果

正如预期的那样,与其他两种技术相比,留一法的运行时间要长得多,尽管它使用了更多的数据来训练模型,但总体而言它并不是最佳性能。

解决这个特定问题的最佳技术是维持交叉验证,其中 70%的数据用于培训,30%用于测试模型。

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

感谢您的阅读,希望对您有所帮助。

欢迎任何意见和建议。

请随时通过我的 Linkedin 联系我,并查看我的 GitHub。

领英

Github

交叉验证和网格搜索

原文:https://towardsdatascience.com/cross-validation-and-grid-search-efa64b127c1b

在随机森林模型上使用 sklearn 的 GridSearchCV

图片由 Annie Spratt 通过 Unsplash 提供

为机器学习问题找到最佳的调整参数通常是非常困难的。我们可能会遇到过度拟合,,这意味着我们的机器学习模型在我们的训练数据集上训练得过于具体,当应用于我们的测试/维持数据集时,会导致更高水平的错误。或者,我们可能会遇到欠拟合,,这意味着我们的模型没有针对我们的训练数据集进行足够具体的训练。当应用于测试/维持数据集时,这也会导致更高水平的误差。

在为模型定型和测试执行常规定型/验证/测试拆分时,模型会对随机选择的特定数据部分进行定型,对单独的数据集进行验证,最后对维持数据集进行测试。在实践中,这可能会导致一些问题,尤其是当数据集的大小相对较小时,因为您可能会删除一部分对训练最佳模型至关重要的观察值。将一定比例的数据排除在训练阶段之外,即使其 15–25%仍然包含大量信息,否则这些信息将有助于我们的模型更有效地训练。

我们的问题有了一个解决方案——交叉验证。交叉验证的工作方式是将我们的数据集分成随机的组,选出一组作为测试,然后在其余的组上训练模型。对作为测试组的每个组重复这一过程,然后将模型的平均值用于结果模型。

最常见的交叉验证类型之一是 k-fold 交叉验证,其中“k”是数据集中的折叠数。使用 k =5 是常见的第一步,下面的例子很容易说明这一原理:

作者图片

这里我们看到模型的五次迭代,每次迭代都将不同的褶皱作为测试集,并在其他四个褶皱上进行训练。所有五次迭代完成后,将结果迭代平均在一起,创建最终的交叉验证模型。

虽然交叉验证可以极大地有益于模型开发,但是在进行交叉验证时,也应该考虑一个重要的缺点。因为模型的每次迭代(最多 k 次)都需要运行完整的模型,所以随着数据集变大以及“k”值的增加,计算成本会变得很高。例如,在具有 100 万个观察值的数据集上运行 k = 10 的交叉验证模型需要运行 10 个单独的模型,每个模型都使用所有 100 万个观察值。对于小型数据集来说,这并不是一个问题,因为计算时间可能只有几分钟,但是当处理大型数据集,其规模可能达到数 Gb 或 Tb 时,所需的时间将会显著增加。

在本文的剩余部分,我们将在我之前的文章中创建的随机森林模型上实现交叉验证。此外,我们将实现所谓的网格搜索,它允许我们在超参数网格上运行模型,以确定最佳结果。

数据:该数据集提供了乘客的信息,如年龄、机票等级、性别,以及乘客是否幸存的二元变量。这些数据也可以用于 Kaggle Titanic ML 比赛,所以本着保持比赛公平的精神,我不会展示我进行 EDA &数据争论的所有步骤,或者直接发布代码。我将构建我在上面提到的文章中开发的先前的模型。

提醒一下,使用的基本随机森林训练模型如下所示:

# Train/Test split
x_train, x_test, y_train, y_test = train_test_split(X, y, test_size = .25, random_state = 18)# Model training
clf = RandomForestClassifier(n_estimators = 500, max_depth = 4, max_features = 3, bootstrap = True, random_state = 18).fit(x_train, y_train)

我们取得的成果是:

作者图片

对于本文,我们将保留这个训练/测试分割部分,以保持模型之间的维持测试数据一致,但是我们将使用交叉验证和网格搜索对训练数据进行参数调整,以查看我们的结果输出与使用上面的基本模型得到的输出有何不同。

GridSearchCV:
我们将在本文中使用的模块是 sklearn 的 GridSearchCV,它将允许我们传递我们的特定估计量、我们的参数网格和我们选择的交叉验证折叠数。此方法的文档可以在这里找到。下面重点介绍了一些主要参数:

  • 估计器 —此参数允许您选择要运行的特定模型,在我们的示例中为随机森林分类。
  • param_grid —该参数允许您传递正在搜索的参数网格。这个网格必须格式化为一个字典,其中的键对应于特定估计器的参数名,值对应于要为特定参数传递的值列表。
  • cv — 该参数允许您更改交叉验证的折叠次数。

模型训练: 我们将首先为随机森林分类模型创建一个参数值网格。网格中的第一个参数是 n_estimators,它选择随机森林模型中使用的树木数量,这里我们选择 200、300、400 或 500 的值。接下来,我们选择 max_feature 参数的值,它限制了每棵树考虑的特征数量。我们将该参数设置为“sqrt”或“log2”,它将采用数据集中估计量的平方根或以 2 为底的对数的形式。第三个参数是 max_depth,它将随机森林模型中每棵树的最大深度设置为 4、5、6、7 或 8。最后,标准参数将通过“基尼”或“熵”进行搜索,以找到理想的标准。该网格如下所示:

grid = { 
    'n_estimators': [200,300,400,500],
    'max_features': ['sqrt', 'log2'],
    'max_depth' : [4,5,6,7,8],
    'criterion' :['gini', 'entropy'],
    'random_state' : [18]
}

创建网格后,我们可以运行 GridSearchCV 模型,将 RandomForestClassifier()传递给我们的估计器参数,将我们的网格传递给 param_grid 参数,并将交叉验证折叠值设为 5。

rf_cv = GridSearchCV(estimator=RandomForestClassifier(), param_grid=grid, cv= 5)
rf_cv.fit(x_train, y_train)

我们现在可以使用了”。best_params_ "方法来为我们的模型输出最佳参数。

rf_cv.best_params_

作者图片

现在我们有了最佳的参数列表,我们可以使用这些参数运行基本的 RandomForestClassifier 模型,并与使用没有网格搜索的原始训练/测试分割获得的结果进行比较来测试我们的结果。

rf2 = RandomForestClassifier(n_estimators = 200, max_depth = 7, max_features = 'sqrt',random_state = 18, criterion = 'gini').fit(x_train, y_train)

作者图片

我们的更优模型的结果优于我们的初始模型,准确性得分为 0.883,而先前为 0.861,F1 得分为 0.835,而先前为 0.803。

合并 GridSearchCV 的一个缺点是运行时。如前所述,交叉验证和网格调整会导致较长的训练时间,因为模型必须经过多次迭代。整个 GridSearchCV 模型运行大约需要 4 分钟,这看起来不多,但是考虑到我们在这个数据集中只有大约 1k 个观察值。你认为进行 10 万次观察或者数百万次观察需要多长时间?

结论: 通过使用交叉验证和网格搜索,当与我们的原始训练/测试分割相比时,我们能够以最小的调整获得更有意义的结果。交叉验证是一种非常重要的方法,用于通过对训练数据集的所有部分进行训练和测试来创建更好的拟合模型。

感谢您花时间阅读这篇文章!我希望你喜欢阅读,并了解了更多关于如何将交叉验证和网格搜索应用到你的机器学习模型中。如果你喜欢你所读的,请关注我的个人资料,成为第一批看到未来文章的人!

交叉表或数据透视表(在 Pandas 中)决定何时使用哪个

原文:https://towardsdatascience.com/crosstab-or-pivot-table-in-pandas-deciding-when-to-use-which-a8ee3a9adfd0

交叉制表和数据透视表之间的选择可能会令人困惑

概观

熊猫的许多特征可以产生类似的结果。对于初学者和有经验的用户来说,这种明显的模糊性可能会令人困惑和沮丧。本文对pd.crosstabpd.pivot_table进行了比较,以帮助您了解两者的不同表现。

在这个演示中,我将引用一个个人数据源:我在 LinkedIn 上的发帖活动。这篇相关文章展示了如何自己查找和访问这些数据。

数据

如果您是来寻求热图建议的:请浏览文章末尾的代码摘录,它可以生成如下所示的最佳热图。

以下是我的shares.csv文件中的前五条记录,你可以从 LinkedIn 下载。

图片鸣谢:作者资料节选。显示了我在 LinkedIn 上的前五篇文章,在文章中有进一步的描述。

我在 2010 年(或者更早)开始了在 LinkedIn 上的生活。我的第一篇文章是“工作时吹口哨”。让它发生!”我不知道我在想什么。另外,显然我对 Audible.com 有着长久的兴趣。

根据这些信息,我将准备一个新的数据框,让我按年、月、日、周和小时来统计帖子。

# Prepare An Empty Year Of Data To Avoid Index Errors
next_year = int(str(date.today())[:4]) + 1
next_year = pd.date_range(start=f'1/1/{next_year}', 
                          end=f'12/31/{next_year}')
next_year = pd.DataFrame({'Count':[0] * len(next_year)}, 
                         index=next_year)# Extract The Columns Of Interest From Shares DataFrame
shares_viz = pd.DataFrame(shares.index)
shares_viz['Count'] = 1
shares_viz = shares_viz.set_index('Date')# Concatenate Shares With The Empty Year
shares_viz = pd.concat([shares_viz, next_year])shares_viz['Year'] = pd.DatetimeIndex(shares_viz.index).year
shares_viz['Month'] = pd.DatetimeIndex(shares_viz.index).month
shares_viz['DOfMonth'] = pd.DatetimeIndex(shares_viz.index).day
shares_viz['DOfWeek'] = pd.DatetimeIndex(shares_viz.index).dayofweek
svshares_viz['HourOfDay'] = pd.DatetimeIndex(shares_viz.index).hour

完成后,新数据将类似于:

图片鸣谢:作者资料节选。显示我的前五条记录,在文章中有进一步的描述。

熊猫杂交列表

根据文档pd.crosstab()将“计算两个(或更多)因素的简单交叉列表”以上述数据为例,我们可以使用pd.crosstab(shares_viz['Year'], shares_viz['Month'])快速总结历年来每月的发布模式:

图片鸣谢:作者资料节选。显示文章中描述的交叉列表。

要解释上面的(摘录)输出,请查看标记为 2020 的行。2020 年 1 月,我在 LinkedIn 上发了 39 次帖子。然而在 2020 年 6 月的晚些时候,我根本没有发帖。

熊猫旋转餐桌

数据透视表产生类似的结果,但不完全相同。pd.pivot_table()的文档解释说它将“创建一个电子表格风格的数据透视表作为数据框架”使用pd.pivot_table(shares_viz, values='Count', index='Year', columns='Month' aggfunc='sum'),我们看到以下版本:

图片鸣谢:作者资料节选。显示文章中描述的数据透视表。

结果相似。例如,在 2020 年,我们再次看到 2020 年 1 月有 39 个帖子,2020 年 6 月没有。

您还可以在两个表格中看到,2022 年 5 月或 5 月之后没有帖子,本文的数据贯穿于 2022 年 4 月。

差异

注意pd.crosstab()如何返回没有计数的0,而pd.pivot_table()如何返回没有计数的NaN

一些调整可以使输出更加匹配。例如,通过添加fillna(0)pd.pivot_table()的简单修改将会用0替换那些NaN值。

为什么这很重要

这在很多情况下都很重要。例如,考虑数据可视化。pd.crosstab()pd.pivot_table()的一个常见用例是将输出传递给sns.heatmap()

一个常见的热图用例是将月份放在行中,将月份中的日期放在列中。这样的热图概括了一年中的日常活动。在下图中,每个单元格(或方块)计算该行当月相应日期(列)的帖子数。

交叉列表热图

作者的形象化。显示文章中描述的热图。

在上面的许多问题中,这里是其中的一个:注意 2 月 29 日、30 日和 31 日有零,4 月、6 月、9 月和 11 月 31 日也有零。那些日子是不存在的。难道我们不应该把它们渲染成某种形式的空的吗?

数据透视表热图

作者的形象化。显示文章中描述的热图。

首先注意不存在的日子与此输出有何不同。在这里,数据透视表输出似乎以一种更有助于数据可视化的方式执行。

数据透视表优于交叉制表还有另一个重要原因(在这种特定情况下)。为了避免数据可视化和分析过程中的索引错误,上面的代码添加了一个“空”年份。上图交叉列表中,空的一年错误地将“计数”增加了一。

结论

在数据分析中,通常有许多方法来完成相同的任务。Pandas 是众多工具中的一个,它提供了多种方法和技术,可以产生相似但不完全相同的结果。

本文展示了pd.crosstab()pd.pivot_table()如何执行相似的功能——有时会产生相似的输出。输出结果可能非常相似,如果不仔细检查,分析师可能会无意中误报结果。

在上面给出的用例中,pd.pivot_table()是产生期望结果的最好和最快的选项。pd.crosstab()也是一种选择吗?是的,pd.crosstab()也是一个选择——但这需要额外的工作来确保输出完全符合预期。

这个故事的寓意是:一遍又一遍地检查你的代码和结果。

感谢阅读

你准备好了解更多关于数据科学职业的信息了吗?我进行一对一的职业辅导,并有一份每周电子邮件列表,帮助专业求职者获取数据。联系我了解更多信息。

感谢阅读。把你的想法和主意发给我。你可以写信只是为了说声嗨。如果你真的需要告诉我是怎么错的,我期待着尽快和你聊天。推特:@ adamrossnelsonLinkedIn:亚当罗斯尼尔森

热图代码

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from datetime import dateshares = pd.read_csv('shares.csv').set_index(['Date']).sort_index()# Prepare An Empty Year Of Data To Avoid Index Errors
next_year = int(str(date.today())[:4]) + 1
next_year = pd.date_range(start=f'1/1/{next_year}', 
                          end=f'12/31/{next_year}')
next_year = pd.DataFrame({'Count':[0] * len(next_year)}, 
                         index=next_year)# Extract The Columns Of Interest From Shares DataFrame
shares_viz = pd.DataFrame(shares.index)
shares_viz['Count'] = 1
shares_viz = shares_viz.set_index('Date')# Concatenate Shares With The Empty Year
shares_viz = pd.concat([shares_viz, next_year])shares_viz['Year'] = pd.DatetimeIndex(shares_viz.index).year
shares_viz['Month'] = pd.DatetimeIndex(shares_viz.index).month
shares_viz['DOfMonth'] = pd.DatetimeIndex(shares_viz.index).day
shares_viz['DOfWeek'] = pd.DatetimeIndex(shares_viz.index).dayofweek
shares_viz['HourOfDay'] = pd.DatetimeIndex(shares_viz.index).hour# Month & Day Posting Patterns
plt.figure(figsize = (12,4))
sns.heatmap(pd.pivot_table(shares_viz, 
                           values='Count', 
                           index='Month', 
                           columns='DayOfMonth',
                           aggfunc='sum'),
            cmap=my_blues,
            annot=True)
plt.suptitle(f'Daily Monthly Activity Since {shares_viz["Year"].min()}', 
             horizontalalignment='right')
plt.title(f'{total_posts} Posts Over {shares_viz["Year"].max() - shares_viz["Year"].min()} Years',
          horizontalalignment='right')

SQL 中的 cte 是什么

原文:https://towardsdatascience.com/cte-sql-945e4b461de3

了解 SQL 中的公用表表达式(CTE)

Sunder MuthukumaranUnsplash 拍摄的照片

编写清晰、易读和高效的 SQL 查询是团队中任何工程或分析过程的一个重要方面。这种查询可以有效地维护,并且在适当的时候可以很好地扩展。

为了实现这一点,开发人员和分析人员都可以轻松采用的一个 SQL 结构是通用表表达式(CTE)。

常用表表达式

公用表表达式(CTE)是一种构造,用于临时存储指定查询的结果集,以便后续查询可以引用它。CTE 的结果不是持久存储在磁盘上,而是其生命周期持续到引用它的查询的执行。

用户可以利用 cte,将复杂的查询分解成更容易维护和阅读的子查询。此外,公共表表达式可以在一个查询中被多次引用,这意味着您不必重复。假定 cte 是命名的,这也意味着用户可以让读者清楚地知道一个特定的表达式应该返回什么结果。

构造公共表表达式

每个 CTE 都可以使用WITH <cte-name> AS子句来构造

WITH sessions_per_user_per_month AS (
    SELECT
      user_id,
      COUNT(*) AS no_of_sessions,
      EXTRACT (MONTH FROM session_datetime) AS session_month,
      EXTRACT (YEAR FROM session_datetime) AS session_year
    FROM user_sessions
    GROUP BY user_id
)

在一个查询中可以指定多个 cte,每个 cte 之间用逗号分隔。cte 也可以引用其他 cte:

WITH sessions_per_user_per_month AS (
    SELECT
      user_id,
      COUNT(*) AS no_of_sessions,
      EXTRACT (MONTH FROM session_datetime) AS session_month,
      EXTRACT (YEAR FROM session_datetime) AS session_year
    FROM user_sessions
    GROUP BY user_id
),
running_sessions_per_user_per_month AS (
    SELECT
      user_id, 
      SUM(no_of_sessions) OVER (
        PARTITION BY 
          user_id, 
          session_month, 
          session_year
      ) AS running_sessions
    FROM sessions_per_user_per_month
)

然后,后续查询可以像任何表或视图一样引用 cte:

WITH sessions_per_user_per_month AS (
    SELECT
      user_id,
      COUNT(*) AS no_of_sessions,
      EXTRACT (MONTH FROM session_datetime) AS session_month,
      EXTRACT (YEAR FROM session_datetime) AS session_year
    FROM user_sessions
    GROUP BY user_id
),
running_sessions_per_user_per_month AS (
    SELECT
      user_id, 
      SUM(no_of_sessions) OVER (
        PARTITION BY 
          user_id, 
          session_month, 
          session_year
      ) AS running_sessions
    FROM sessions_per_user_per_month
)

SELECT 
  u.username,
  u.email
  u.country,
  s.running_sessions
FROM users u
LEFT JOIN sessions_per_user_per_month s
  ON u.user_id = s.user_id
WHERE country = 'US';

cte 与子查询

通常,使用子查询可以获得相同的结果。顾名思义,子查询是在另一个查询中定义的查询(也称为嵌套查询)。

有一种误解,认为 cte 往往比子查询执行得更好,但这不是真的。实际上, CTE 是一个语法糖,这意味着在后台,子查询仍然会被执行,但是在决定是否要编写一个公共表表达式或子查询时,您需要记住一些事情。

cte 比嵌套查询更具可读性。您需要做的不是在一个查询中包含两个或多个查询,而是定义一个 CTE 并在后续查询中引用它的名称。

这意味着cte 还可以被后续查询多次重用和引用。对于子查询,您必须一遍又一遍地重写相同的查询。

此外, CTE 可以是递归的,这意味着它可以引用自身。递归 cte 的语法与用于指定非递归 cte 的语法有些不同。您将需要使用WITH RECURSIVE来指定它,并使用UNION ALL来组合递归调用和基础用例(也称为锚)的结果:

-- Syntax used for recursive CTEs
WITH RECURSIVE <cte-name> AS (
  <anchor case>
  UNION ALL
  <recursive case>
)

我现在不打算谈论更多关于递归 cte 的细节,但是我计划在接下来的几天里专门为此写一篇文章,所以一定要订阅下面的内容,并且在它发布的时候得到通知!

最后的想法

公共表表达式提供了一种简单而强大的方式来编写干净、可读和可维护的 SQL 查询。用户可以利用这种结构来增强跨查询的可重用性,在某些情况下甚至可以提高性能,因为 CTE(临时结果集)可以被多次引用。

只要有可能,cte 应该优先于嵌套连接,因为后者会使您的代码混乱,如果需要多次,会使您的代码可读性更差。此外,cte 还可以是递归的,如果需要的话,这是一大优势。

尽管存在子查询可以提供比 cte 更大灵活性的用例,但是本文并不打算让您相信子查询是完全无用的!例如,因为 cte 必须在SELECT子句之前指定,这意味着它们不能在WHERE子句中使用。

成为会员 阅读介质上的每一个故事。你的会员费直接支持我和你看的其他作家。你也可以在媒体上看到所有的故事。

https://gmyrianthous.medium.com/membership

相关文章你可能也喜欢

三次样条:终极回归模型

原文:https://towardsdatascience.com/cubic-splines-the-ultimate-regression-model-bd51a9cf396d

为什么三次样条是最好的回归模型。

三次样条简介—作者

介绍

在本文中,我将介绍三次样条,并展示它们如何比高度线性回归模型更稳健。首先,我将遍历三次样条背后的数学,然后我将用 Python 展示这个模型,最后,我将解释龙格现象。

本文中使用的 python 库叫做 Regressio 。这是作者为单变量回归、插值和平滑创建的开源 python 库。

首先,三次样条是一种分段插值模型,它将三次多项式拟合到分段函数中的每一段。在两个多项式相交的每一点,一阶和二阶导数相等。这有助于形成平滑的拟合线。

分段函数示例—作者

例如,在上图中,我们可以看到三次多项式是如何被分割的。在这个图像中,有 3 块,中间的两个蓝色点是多项式相交的地方。我们可以看到函数在这两点周围是光滑的,并且整个函数是连续的。让我们看看拟合三次样条背后的数学原理。

数学

注:所有数学函数均由本文作者创建。

假设我们有三个数据点(2,3)、(3,2)和(4,4)。计算三次样条时,我们必须使用至少 2 个最多 n-1 个分段函数。这些分段函数中的每一个都是三次回归模型。

由于我们有 3 个数据点,我们将需要 2 个分段函数。我们将这些表示为 f1(x)和 f2(x)。

在上面的每个方程中,我们有 4 个未知变量(a,b,c,d)。我们需要建立一个方程组来计算每个方程中的未知数,所以我们总共有 8 个未知数。首先,我们知道对于第一点和第二点,这些点必须落在第一个函数上。

接下来,我们知道第二点和第三点一定落在第二个函数上。

我们还知道,两个函数相交时,它们的一阶导数必须相等。

然后,我们知道相交样条的二阶导数必须相等。

最后,我们希望每个端点的二阶导数为 0。这就形成了自然的三次样条曲线。

得到的 8 个等式如下。

然后我们可以插入三个数据点(2,3),(3,2),(4,4)。

上述方程可以用矩阵形式表示,并用线性代数求解。这些方程用大小为 4 x (n - 1)的矩阵表示。在这个例子中,矩阵将是 8×8。

然后我们可以把这些值代入我们的两个方程,我们就有了分段函数!

我们理解了算法背后的数学原理,这很好,但是我们不应该每次都必须手动计算权重。在下面的单元格中,我展示了如何在 Numpy 中求解方程。

Numpy 输出—按作者

回归图书馆

我们可以做一个比手工输入更好的。我们可以使用一个名为 Regressio 的轻量级 python 库。该库具有用于回归、插值和平滑的单变量模型。在下面的单元格中,我们正在安装库并生成 200 个数据点的随机样本。

随机数据样本—按作者

然后,我们可以简单地导入三次样条模型,并对数据进行拟合。

回归三次样条模型—作者

这是一个非常惊人的模型,因为我们可以使用低次多项式的组合来拟合高度可变的关系。使用 Regressio 可以很容易地试验不同的数据集和分段大小,我鼓励你使用这个库进行试验。现在让我们看看为什么这些模型比线性回归更好。

龙格现象

拟合样条模型正是 Carl David Tolmé Runge 在 1901 年所做的事情,他发现三次样条等多项式插值方法的效果优于高次线性回归模型。这是由于线性回归模型中区间边缘的大振荡。这在下面的这张图片中很好地显现出来。

作者:龙格现象

在此图像中,橙色线表示三次样条插值法,蓝色线表示线性回归模型。我们可以看到这两个模型的振荡非常不同。

对于线性回归模型,如果给我们一个位于训练数据边界之外的数据点,那么我们将得到一个异常预测。这是因为训练范围之外的模型导数的幅度非常大。这不是样条模型的情况,也是它更健壮的原因。

最后的想法

希望本文向您展示了三次样条如何比高度线性回归模型更稳健。在回归入门课程中,经常会跳过三次样条,但这是不应该的。在我看来,它们是线性回归模型的优秀解决方案,因为它们减轻了龙格现象。

我鼓励你尝试更多三次样条的例子,并看看回归库中的其他模型。作为这个包的作者,我使代码库对于那些试图理解每个模型如何工作的人来说非常可读。该库仍在生产中,并经常发生变化,请随意给它一颗星以跟踪它的变化,或者自己做出贡献!

如果你想要这篇文章的笔记本代码,你可以在这里找到。

参考

CUDA by Numba 示例

原文:https://towardsdatascience.com/cuda-by-numba-examples-1-4-e0d06651612f

阅读本系列文章,从头开始学习使用 Python 进行 CUDA 编程

第 1 部分,共 4 部分:开始并行之旅

介绍

顾名思义,GPU(图形处理单元)最初是为计算机图形而开发的。从那以后,它们在几乎每个需要高计算吞吐量的领域都变得无处不在。这一进步是由 GPGPU(通用 GPU)接口的开发实现的,它允许我们为通用计算编程 GPU。这些接口中最常见的是 CUDA ,其次是 OpenCL 以及最近的 HIP

图 1.0。运行稳定扩散parallel lines futuristic space。学分:在 CreativeML Open RAIL-M 许可下拥有作品。

Python 中的 CUDA

CUDA 最初是为了与 C 兼容而设计的,后来的版本将其扩展到了 C++和 Fortran。在 Python 生态系统中,使用 CUDA 的方法之一是通过 Numba ,这是一个针对 Python 的实时(JIT)编译器,可以针对 GPU(它也针对 CPU,但这不在我们的范围之内)。使用 Numba,可以直接用 Python(的子集)编写内核,Numba 将动态编译代码并运行它。虽然它没有实现完整的 CUDA API,但与 CPU 相比,它支持的功能通常足以获得令人印象深刻的加速(有关所有缺失的功能,请参见Numba 文档)。

然而,Numba 并不是唯一的选择。CuPy 提供了依赖 CUDA 的高级功能、集成 C 语言内核的低级 CUDA 支持和可 JIT 的 Python 功能(类似于 Numba)。 PyCUDA 提供了更细粒度的 CUDA API 控制。最近,Nvidia 发布了官方的 CUDA Python ,这必将丰富生态系统。所有这些项目都可以互相传递设备阵列,您不会被限制只能使用一个。

在这个系列中

本系列的目标是通过用 Numba CUDA 编写的例子提供一个通用 CUDA 模式的学习平台。本系列不是 CUDA 或 Numba 的综合指南。读者可以参考他们各自的文档。本教程的结构灵感来自 Jason Sanders 和 Edward Kandrot 所著的《CUDA by Example:a Introduction to General-Purpose GPU Programming》一书。如果您最终不再使用 Python,而是想用 C 语言编写代码,这是一个极好的资源。该系列还有三个部分:第二部分第三部分第四部分

在本教程中

我们将学习如何运行我们的第一个 Numba CUDA 内核。我们还将学习如何有效地使用 CUDA 来处理令人尴尬的并行任务,即彼此完全独立的任务。最后,我们将学习如何从 CPU 对内核运行时进行计时。

点击此处获取 Google Colab 中的代码

GPU 并行编程简介

与 CPU 相比,GPU 的最大优势在于它们能够并行执行相同的指令。单个 CPU 内核将一个接一个地串行运行指令。在一个 CPU 上实现并行化需要同时使用其多个内核(物理内核或虚拟内核)。一台标准的现代计算机有 4-8 个内核。另一方面,现代 GPU 拥有数百个甚至数千个计算核心。这两者之间的比较见图 1。GPU 核心通常较慢,只能执行简单的指令,但它们的数量通常会成倍地弥补这些缺点。需要注意的是,为了让 GPU 拥有 CPU 的优势,它们运行的算法必须是可并行的。

我认为 钻研 GPU 编程主要有四个方面。第一个我已经提到了:理解如何思考和设计本质上并行的算法。这可能很难做到,因为有些算法是串行设计的,还因为同一算法可能有多种并行方式。

第二个方面是学习如何将位于主机上的结构(如向量和图像)映射到 GPU 构造(如线程和块)上。循环模式和辅助函数可以帮助我们做到这一点,但最终,实验对于充分利用 GPU 是非常重要的。

第三是理解驱动 GPU 编程的异步执行模型。不仅 GPU 和 CPU 彼此独立地执行指令,GPU 还有,允许多个处理流在同一个 GPU 中运行。在设计最佳处理流程时,这种异步性非常重要。

第四个也是最后一个方面是抽象概念和具体代码之间的关系:这是通过学习 API 及其细微差别来实现的。

当你阅读第一章时,试着在下面的例子中识别这些概念!

图 1.1。简化的 CPU 架构(左)和 GPU 架构(右)。算术发生在 ALU(算术逻辑单元)、DRAM 数据、高速缓存中,高速缓存甚至保存可以更快访问的数据,但通常容量较小。控制单元执行指令。信用:维基百科

入门指南

我们将从设置我们的环境开始:高于 0.55 的 Numba 版本和支持的 GPU。

Numba CUDA 的主要工具是cuda.jit装饰器。它用于定义将在 GPU 中运行的函数。

我们首先定义一个简单的函数,它接受两个数字,并将它们存储在第三个参数的第一个元素上。我们的第一个教训是内核(启动线程的 GPU 函数)不能返回值。我们通过传递输入和输出来解决这个问题。这是 C 中常见的模式,但在 Python 中并不常见。

您可能已经注意到,在我们调用内核之前,我们需要在设备上分配一个数组。此外,如果我们想显示返回值,我们需要将它复制回 CPU。您可能会问自己,为什么我们选择分配一个float32(单精度浮点型)。这是因为,虽然大多数现代 GPU 都支持双精度算法,但双精度算法比单精度算法耗时 4 倍或更长。所以最好习惯用np.float32``np.complex64而不是float/np.float64``complex/np.complex128

尽管内核定义看起来类似于 CPU 函数,但内核调用略有不同。特别是,它在参数前有方括号:

add_scalars[1, 1](2.0, 7.0, dev_c)

这些方括号分别表示网格中的数量,以及块中线程的数量。随着我们学习使用 CUDA 进行并行化,让我们更深入地讨论一下这些意味着什么。

使用 CUDA 实现并行化

CUDA 网格的剖析

当一个内核启动时,它有一个网格与之相关联。一个网格由组成;一个滑块由螺纹组成。图 2 显示了一个一维 CUDA 网格。图中的网格有 4 个块。网格中的块数保存在一个特殊的变量中,这个变量可以在内核中被访问,称为gridDim.x.x是指网格的第一维度(本例中唯一的一个)。二维网格也有.y和三维网格,.z变量。截至 2022 年,没有 4 维或更高的网格。同样在内核内部,您可以通过使用blockIdx.x找出哪个块正在被执行,在本例中它将从 0 运行到 3。

每个程序块都有一定数量的线程,保存在变量blockDim.x中。线程索引保存在变量threadIdx.x中,在本例中从 0 到 7 运行。

重要的是,不同块中的线程被调度为不同的运行方式,可以访问不同的内存区域,并且在其他方面也有所不同(参见CUDA Refresher:The CUDA Programming Model进行简要讨论)。现在,我们将跳过这些细节。

图 1.2。一维 CUDA 网格。图片作者。

当我们在第一个例子中用参数[1, 1]启动内核时,我们告诉 CUDA 用一个线程运行一个块。用几个线程传递几个块,会多次运行内核。操纵threadIdx.xblockIdx.x将允许我们唯一地识别每个线程。

让我们尝试对两个数组求和,而不是对两个数字求和。假设每个数组有 20 个元素。如上图所示,我们可以启动一个每个块有 8 个线程的内核。如果我们希望每个线程只处理一个数组元素,那么我们至少需要 4 个块。启动 4 个块,每个块 8 个线程,然后我们的网格将启动 32 个线程。

现在我们需要弄清楚如何将线程索引映射到数组索引。threadIdx.x从 0 运行到 7,所以他们自己不能索引我们的数组。此外,不同的区块有相同的threadIdx.x。另一方面,他们有不同的blockIdx.x。为了获得每个线程的唯一索引,我们可以组合这些变量:

i = threadIdx.x + blockDim.x * blockIdx.x

对于第一个块,blockIdx.x = 0i将从 0 运行到 7。对于第二块,blockIdx.x = 1。从blockDim.x = 8开始,i将从 8 运行到 15。同样,对于blockIdx.x = 2i将从 16 运行到 23。在第四个也是最后一个程序块中,i将从 24 运行到 31。见下表 1。

我们解决了一个问题:如何将每个线程映射到数组中的每个元素…但现在我们有一个问题,一些线程会溢出数组,因为数组有 20 个元素,而i上升到 32-1。解决方案很简单:对于那些线程,不要做任何事情!

让我们看看代码。

在 Numba 的新版本中,我们得到一个警告,指出我们用主机数组调用了内核。理想情况下,我们希望避免在主机和设备之间移动数据,因为这非常慢。我们应该在所有参数中使用设备数组来调用内核。我们可以通过预先将阵列从主机移动到设备来做到这一点:

dev_a = cuda.to_device(a)dev_b = cuda.to_device(b)

此外,每个线程的唯一索引的计算会很快过时。令人欣慰的是,Numba 提供了非常简单的包装器cuda.grid,它是用网格维度作为唯一参数来调用的。新内核将如下所示:

当我们改变数组的大小时会发生什么?一种简单的方法是简单地改变网格参数(块的数量和每个块的线程数量),以便启动至少与数组中的元素一样多的线程。

设定这些参数需要一些科学和艺术。对于“科学”,我们会说(a)它们应该是 2 的倍数,通常在 32 到 1024 之间,以及(b)它们应该被选择为最大化占用率(有多少线程同时处于活动状态)。Nvidia 提供了一个电子表格可以帮助计算这些。对于“艺术”来说,没有什么可以预测内核的行为,所以如果你真的想优化这些参数,你需要用典型的输入来分析你的代码。实际上,现代 GPU 的“合理”线程数是 256。

在讨论矢量求和之前,我们需要讨论一下硬件限制。GPU 不能运行任意数量的线程和块。通常每个块不能有超过 1024 个线程,一个网格不能有超过 2 个⁶1 = 65535 块。这并不是说您可以启动 1024 × 65535 个线程…除了其他考虑因素之外,根据寄存器占用的内存大小,可以启动的线程数量是有限制的。此外,必须警惕试图同时处理不适合 GPU RAM 的大型数组。在这些情况下,可以使用单个 GPU 或多个 GPU 来分段处理数组。

INFO: 在 Python 中,硬件限制可以通过 Nvidia 的 *cuda-python* 库通过 函数 [*cuDeviceGetAttribute*](https://nvidia.github.io/cuda-python/module/cuda.html#cuda.cuda.cuDeviceGetAttribute) 在他们的文档 中获得。有关示例,请参见本节末尾的附录。

网格步长循环

如果每个网格的块数超过了硬件限制,但数组适合内存,我们可以使用一个线程来处理几个元素,而不是每个数组元素使用一个线程。我们将通过使用一种叫做网格步长循环的技术来实现。除了克服硬件限制之外,grid-stride 循环内核还受益于线程重用,这是通过最小化线程创建/销毁开销实现的。马克·哈里斯的博客文章 CUDA Pro 提示:用网格步长循环编写灵活的内核 详细介绍了网格步长循环的一些好处。

这项技术背后的思想是在 CUDA 内核中添加一个循环来处理多个输入元素。顾名思义,这个循环的步距等于一个网格中的线程数。这样,如果网格中的线程总数(threads_per_grid = blockDim.x * gridDim.x)小于数组元素的数量,那么一旦内核处理完索引cuda.grid(1),它将处理索引cuda.grid(1) + threads_per_grid等等,直到所有的数组元素都被处理完。事不宜迟,我们来看看代码。

这段代码与上面的非常相似,不同的是我们在cuda.grid(1)开始,但是执行更多的样本,每threads_per_grid一个,直到我们到达数组的末尾。

现在,哪一个内核更快?

定时 CUDA 内核

GPU 编程都是关于速度的。因此,准确测量代码执行是非常重要的。

CUDA 内核是由主机(CPU)启动的设备功能,当然它们是在 GPU 上执行的。GPU 和 CPU 不通信,除非我们告诉他们。因此,当 GPU 内核启动时,CPU 将简单地继续运行指令,无论它们是启动更多的内核还是执行其他 CPU 功能。如果我们在内核启动前后发出一个time.time()调用,我们将只计算内核启动所花的时间,而不是运行

我们可以用来确保 GPU 已经“跟上”的一个函数是cuda.synchronize()。调用此函数将停止主机执行任何其他代码,直到 GPU 完成执行其中已启动的每个内核。

为了给内核执行计时,我们可以简单地计算内核运行和同步的时间。对此有两点需要注意。首先,我们需要使用time.perf_counter()time.perf_counter_ns()而不是time.time()time.time()不计算主机休眠等待 GPU 完成执行的时间。第二个警告是,来自主机的定时代码并不理想,因为存在与此相关的开销。稍后,我们将解释如何使用 CUDA 事件来为设备中的内核计时。马克·哈里斯有另一篇关于这个主题的优秀博文,题为 如何在 CUDA C/C++ 中实现性能指标。

在使用 Numba 的时候,有一个细节是我们必须注意的。Numba 是一个实时编译器,这意味着函数只有在被调用时才会被编译。因此,对函数的第一次调用计时也将对编译步骤计时,编译步骤通常要慢得多。我们必须记住,总是首先通过启动内核来编译代码,然后同步它,以确保没有任何东西留在 GPU 中运行。这确保了下一个内核无需编译就能立即运行。还要注意数组的dtype应该是相同的,因为 Numba 为参数dtypes的每个组合编译一个唯一的函数。

对于简单的内核,我们还可以测量算法的吞吐量,即每秒钟浮点运算的次数。它通常以 GFLOP/s(每秒千兆次浮点运算)来度量。我们的加法运算只包含一个翻牌:加法。因此,吞吐量由下式给出:

2D 的例子

为了结束本教程,让我们制作一个 2D 内核来对图像应用对数校正

给定值在 0 和 1 之间的图像 I(x,y ),对数校正图像由下式给出

Iᵪ(x,y) = γ log₂ (1 + I(x,y))

首先让我们获取一些数据!

图 1.3。原始“月球”数据集。图片作者。

如你所见,数据在低端已经饱和。0.6 以上的数值几乎没有。

让我们来写内核。

让我们记下这两个for循环。请注意,第一个for循环从iy开始,第二个最里面的循环从ix开始。我们可以很容易地选择i0ix开始,而i1iy开始,这样会感觉更自然。那么我们为什么选择这个顺序呢?事实证明,第一种选择的内存访问模式效率更高。由于第一个网格索引是最快的一个,所以我们想让它匹配我们最快的维度:最后一个。

如果你不想相信我的话(你不应该相信!)您现在已经了解了如何对内核执行进行计时,您可以尝试这两个版本。对于像这里使用的这种小数组,这种差异可以忽略不计,但是对于更大的数组(比如 10,000 乘 10,000),我测得的加速大约是 10%。不是很令人印象深刻,但是如果我可以通过一次变量交换给你 10%的提高,谁会不接受呢?

就是这样!我们现在可以在校正后的图像中看到更多细节。

作为一个练习,尝试用不同的网格来计时不同的启动,以找到适合您的机器的最佳网格大小。

图 1.4。原始(左)和对数校正(右)“月球”数据集。图片作者。

结论

在本教程中,你学习了 Numba CUDA 的基础知识。您学习了如何创建简单的 CUDA 内核,并将内存转移到 GPU 来使用它们。您还学习了如何使用一种叫做网格步长循环的技术迭代 1D 和 2D 数组。

附录:使用 Nvidia 的 cuda-python 探测设备属性

为了对 GPU 的确切属性进行精细控制,您可以依赖 Nvidia 提供的底层官方 CUDA Python 包。

CUDA by Numba 示例

原文:https://towardsdatascience.com/cuda-by-numba-examples-215c0d285088

阅读本系列文章,从头开始学习使用 Python 进行 CUDA 编程

第 2 部分,共 4 部分:穿针引线

介绍

在本系列的第一期中,我们讨论了如何使用 GPU 运行令人尴尬的并行算法。令人尴尬的并行任务是那些任务彼此完全独立的任务,例如对两个数组求和或应用任何元素级函数。

图 2.0。用“穿针赛博朋克”运行稳定扩散。学分:在 CreativeML Open RAIL-M 许可下拥有作品。

在本教程中

许多任务虽然不是令人尴尬的并行,但仍然可以从并行化中受益。在本期的 CUDA by Numba Examples 中,我们将介绍一些允许线程在计算中协作的常用技术。

点击这里在 Google colab 中抓取代码。

本教程后面还有两个部分:第三部分第四部分

入门指南

导入和加载库,确保你有一个 GPU。

线程协作

简单并行归约算法

我们将从一个非常简单的问题开始这一部分:对一个数组的所有元素求和。串行地,这个算法非常简单。不借助 NumPy,我们可以这样实现它:

我知道,这看起来不太像蟒蛇。但是它强调了s正在跟踪数组中的所有元素。如果s依赖于数组的每一个元素,我们如何将这个算法并行化?首先,我们需要重写算法以允许一些并行化。如果有我们不能并行化的部分,我们应该允许线程相互通信。

然而,到目前为止,我们还没有学会如何让线程相互通信…事实上,我们以前说过,不同块中的线程是不通信的。我们可以考虑只启动一个块,但是请记住,在大多数 GPU 中,块只能有 1024 个线程!

我们如何克服这一点?那么,如果我们把数组分成 1024 个(或者适当数量的threads_per_block)的块,然后分别对每个块求和,会怎么样呢?最后,我们可以把每个数据块的结果相加。图 2.1 显示了一个非常简单的两块分割的例子。

图 2.1。对数组元素求和的“分治”方法。图片作者。

我们如何在 GPU 上做到这一点?首先,我们需要将数组分成几个块。每个块都对应于一个块,有固定数量的线程。在每个块中,每个线程可以对多个数组元素求和(grid-stride 循环)。然后,我们必须在整个块中计算每个线程的值。该位需要线程进行通信。我们将在下一个例子中讨论如何做到这一点。

由于我们是在块上并行化,内核的输出应该作为一个块来确定大小。为了完成缩减,我们将它复制到 CPU 并在那里完成工作。

警告 :共享数组必须

  • 要“小”。确切的大小取决于 GPU 的计算能力,通常在 48 KB 和 163 KB 之间。参见本表 项“每个线程块的最大共享内存量”。
  • 在编译时有一个已知的大小(这就是为什么我们调整共享数组 *threads_per_block* 而不是 *blockDim.x* )。诚然,我们总是可以定义一个 工厂函数 来共享任意大小的数组...但是要注意这些内核的编译时间。
  • *dtype* 由 Numba 类型指定,而不是 Numpy 类型(不要问我为什么!).

我在 Google Colab 上运行了这个,我们得到了 10 倍的加速。相当不错!

一种更好的并行归约算法

你现在可能想知道为什么我们把一切都命名为“幼稚”。这意味着有一些非幼稚的方式来做同样的功能。事实上,有很多技巧可以加速这类代码(参见 CUDA 演示中的 优化并行化缩减作为基准)。

在我们展示更好的方法之前,让我们回忆一下内核的最后一点:

我们几乎并行化了所有的事情,但是在内核的最后,我们让一个线程负责对共享数组s_block的所有threads_per_block元素求和。为什么我们不把这个求和也并行化呢?

听起来不错,怎么样?图 2.2 显示了如何在threads_per_block尺寸为 16 的情况下实现这一点。我们从 8 个线程开始工作,第一个线程将对s_block[0]s_block[8]中的值求和。第二个是s_block[1]s_block[9]中的值,直到最后一个线程将值s_block[7]s_block[15]相加。

在下一步中,只有前 4 个线程需要工作。第一个线程将对s_block[0]s_block[4]求和;第二,s_block[1]s_block[5];第三,s_block[2]s_block[6];第四个也是最后一个,s_block[3]s_block[7]

第三步,我们现在只需要 2 个线程来处理s_block的前 4 个元素。第四步也是最后一步将使用一个线程对 2 个元素求和。

由于工作在线程之间进行了划分,因此它被并行化了。当然,这不是每个线程的平均划分,但这是一种改进。在计算上,这个算法是 O(log2( threads_per_block)),而第一个算法是 O( threads_per_block)。在我们的例子中,简单算法有 1024 次运算,而改进算法只有 10 次!

还有最后一个细节。在每一步,我们都需要确保所有线程都已经写入共享数组。所以我们要调用cuda.syncthreads()

图 2.2。通过顺序寻址进行归约。鸣谢:马克·哈里斯, 优化 CUDA 中的并行还原

在我的机器上,这比简单的方法快 25%。

警告 :您可能想将 *syncthreads* 移动到 *if* 块内,因为在每一步之后,超出当前线程数一半的内核将不会被使用。然而,这样做将使名为 *syncthreads* 的 CUDA 线程停止并等待所有其他线程,而所有其他线程将继续运行。因此,停止的线程将永远等待永不停止的线程进行同步。要点是:如果同步线程,确保在所有线程 中调用 ***cuda.syncthreads()***

i = cuda.blockDim.x // 2
while (i > 0):
    if (tid < i):
        s_block[tid] += s_block[tid + i]
        cuda.syncthreads() # don't put it here
    cuda.syncthreads()  # instead of here
    i //= 2

数字减少

因为上面的归约算法很重要,Numba 提供了一个方便的cuda.reduce装饰器,将二进制函数转换成归约。上述长而复杂的算法可以替换为:

就个人而言,我发现手写的归约通常要快得多(至少快 2 倍),但是 Numba 递归非常容易使用。也就是说,我鼓励阅读 Numba 源代码中的 reduction 代码。

还需要注意的是,默认情况下,缩减拷贝到主机,这将强制同步。为了避免这种情况,您可以使用设备数组作为输出来调用缩减:

2D 还原示例

并行归约技术很棒,但如何将其扩展到更高维度并不明显。虽然我们总是可以用一个未展开的数组(array2d.ravel())来调用 Numba 归约,但是理解我们如何手动归约多维数组是很重要的。

在这个例子中,我们将结合我们对 2D 核的了解和对 1D 约简的了解来计算 2D 约简。

设备功能

到目前为止,我们只讨论了内核,这是启动线程的特殊 GPU 函数。内核通常依赖于较小的函数,这些函数在 GPU 中定义,并且只能访问 GPU 数组。这些被称为设备功能。与内核不同,它们可以返回值。

在本部分教程的最后,我们将展示一个跨不同内核使用设备函数的例子。该示例还将强调在使用共享数组时同步线程的重要性。

INFO:CUDA 新版本中,内核可以启动其他内核。这叫做动态并行,Numba CUDA 还不支持。

2D 共享阵列示例

在本例中,我们将在一个固定大小的数组中创建一个波纹图案。我们首先需要声明我们将使用的线程数量,因为这是共享数组所要求的。

图 2.3。左:来自同步(正确)内核的结果。右图:来自不同步(不正确)内核的结果。学分:自己的工作。受 Sanders 和 Kandrot 的 CUDA 示例中图 5.5 和 5.6 的启发。

结论

在本教程中,你学习了如何开发需要一个归约模式来处理 1D 和 2D 数组的内核。在此过程中,我们学习了如何利用共享阵列和设备功能。

CUDA by Numba 示例

原文:https://towardsdatascience.com/cuda-by-numba-examples-7652412af1ee

阅读本系列的第 3 部分,了解 Python 的 CUDA 编程中的流和事件

第 3 部分,共 4 部分:流和事件

介绍

在本系列的前两部分中(第一部分在这里,以及第二部分在这里,我们学习了如何用 GPU 编程来执行简单的任务,比如令人尴尬的并行任务、使用共享内存的缩减以及设备功能。我们还了解了如何对主机的函数计时——以及为什么这可能不是对代码计时的最佳方式。

图 3.0。运行稳定扩散与“湾流多彩空间平静”。学分:在 CreativeML Open RAIL-M 许可下拥有作品。

在本教程中

为了提高我们的计时能力,我们将介绍 CUDA 事件以及如何使用它们。但是在我们深入研究之前,我们将讨论 CUDA 流以及为什么它们很重要。

点击这里在 Google colab 中抓取代码。

本教程后面还有一个部分:第四部分

入门指南

导入和加载库,确保你有一个 GPU。

当我们从主机启动内核时,它的执行会在 GPU 中排队,只要 GPU 完成了之前启动的所有任务,就会执行这个任务。

用户在设备中启动的许多任务可能依赖于之前的任务,“将它们放在同一个队列中”是有意义的。例如,如果您正在将数据异步复制到 GPU,以便用某个内核处理它,则该副本必须在内核运行之前完成。

但是,如果有两个相互独立的内核,将它们放在同一个队列中有意义吗?大概不会!对于这些情况,CUDA 有个流。您可以将流视为独立的队列,它们彼此独立运行。它们也可以并发运行,即同时运行。当运行许多独立的任务时,这可以大大加快总运行时间。

图 3.1。使用不同的流可以允许并发执行,从而提高运行时间。演职员表:张等 2021 (CC BY 4.0)。

Numba CUDA 中的流语义

我们将把到目前为止学到的两个任务进行排队,以创建一个规范化管道。给定一个(主机)数组a,我们将用它的规范化版本覆盖它:

a ← a / ∑a[i]

为此,我们将使用三个内核。第一个内核partial_reduce将是我们对第 2 部分的部分缩减。它将返回一个threads_per_block大小的数组,我们将把它传递给另一个内核single_thread_sum,后者将进一步把它简化为一个单独的数组(大小为 1)。这个内核将在一个单独的块上用一个单独的线程运行。最后,我们将使用divide_by就地除出原始数组,但我们之前计算的总和。所有这些操作都将在 GPU 中进行,并且应该一个接一个地运行。

当内核调用和其他操作没有流时,它们在默认流中运行。默认流是一个特殊的流,其行为取决于运行的是传统流还是每线程流。对我们来说,如果您想要实现并发,您应该在非默认流中运行任务,这样说就足够了。让我们来看看如何为内核启动、阵列拷贝和阵列创建拷贝等操作实现这一点。

在我们真正谈论流之前,我们需要谈论房间里的大象:cuda.pinned。这个上下文管理器创建了一种特殊类型的内存,称为页面锁定固定内存,CUDA 在将内存从主机转移到设备时会从中受益。

驻留在主机 RAM 中的内存可以随时被 分页 ,即操作系统可以偷偷将对象从 RAM 移动到硬盘。这样做是为了将不常用的对象移到较慢的内存位置,从而将快速 RAM 内存留给更急需的对象。对我们来说重要的是,CUDA 不允许从可分页对象到 GPU 的异步传输。这样做是为了防止持续不断的非常慢的传输:磁盘(分页)→ RAM → GPU。

为了异步传输数据,我们必须确保数据总是在 RAM 中,通过某种方式防止操作系统偷偷把它藏在磁盘的某个地方。这就是内存锁定发挥作用的地方,它创建了一个上下文,在这个上下文中,参数将被“页面锁定”,也就是说,被强制放在 RAM 中。参见图 3.2。

图 3.2。可分页内存与固定(页面锁定)内存。演职员表:里兹维等人 2017 (CC BY 4.0)。

从那时起,代码就非常简单了。创建了一个流,之后它将被传递给我们想要在该流上操作的每个 CUDA 函数。重要的是,Numba CUDA 内核配置(方括号)要求流位于第三个参数中,在块维度大小之后。

警告: 一般来说,将流传递给 Numba CUDA API 函数并不会改变它的行为,只会改变它运行所在的流。一个例外是从设备到主机的拷贝。当调用 *device_array.copy_to_host()* (无参数)时,复制同步发生。调用 *device_array.copy_to_host(stream=stream)* (带流)时,如果 *device_array* 没有被钉住,复制会同步发生。仅当 ***device_array*** 被钉住并且流被传递 时,复制才会 异步发生。

INFO:Numba 提供了一个有用的上下文管理器,可以将所有操作放入其上下文中;退出上下文时,操作将被同步,包括内存传输。例 3.1 也可以写成:

with cuda.pinned(a):
    stream = cuda.stream()
    with stream.auto_synchronize():
        dev_a = cuda.to_device(a, stream=stream)
        dev_a_reduce = cuda.device_array((blocks_per_grid,), dtype=dev_a.dtype, stream=stream)
        dev_a_sum = cuda.device_array((1,), dtype=dev_a.dtype, stream=stream)
        partial_reduce[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_reduce)
        single_thread_sum[1, 1, stream](dev_a_reduce, dev_a_sum)
        divide_by[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_sum)
        dev_a.copy_to_host(a, stream=stream)

将独立内核与流解耦

假设我们想要规格化不是一个数组,而是多个数组。对单独数组进行规范化的操作是完全相互独立的。因此,GPU 等到一个规范化结束后再开始下一个规范化是没有意义的。因此,我们应该将这些任务分成不同的流程。

让我们看一个规范化 10 个数组的例子——每个数组都使用自己的流。

现在让我们来比较一下单流。

但是哪个更快呢?当运行这些例子时,当使用多个流时,我没有获得一致的总时间改进。这可能有很多原因。例如,对于并发运行的流,本地内存中必须有足够的空间。另外,我们是从 CPU 计时的。虽然很难知道本地内存中是否有足够的空间,但从 GPU 进行计时相对容易。让我们学习如何!

INFO:Nvidia 提供了几个调试 CUDA 的工具,包括调试 CUDA 流。查看 Nsight 系统 了解更多信息。

事件

来自 CPU 的计时代码的一个问题是,它将包括比 GPU 更多的操作。

谢天谢地,有可能用 CUDA 直接从 GPU 计时事件。事件只是一个时间寄存器,记录了 GPU 中发生的事情。在某种程度上,它类似于time.timetime.perf_counter,与它们不同的是,我们需要处理这样一个事实,即当我们从 CPU 编程时,我们想要从 GPU 计时事件。

因此,除了创建时间戳(“记录”事件),我们还需要确保事件在访问其值之前与 CPU 同步。让我们来看一个简单的例子。

为内核执行计时的事件

计时 GPU 操作的一个有用方法是使用上下文管理器:

计时流的事件

在本系列文章的最后,我们将使用 streams 来更好、更准确地了解我们的示例是否受益于 streams。

结论

CUDA 关注的是性能。在本教程中,您学习了如何使用事件来精确测量内核的执行时间,这种方法可以用来分析您的代码。您还了解了以及如何使用它们让您的 GPU 保持忙碌,以及固定映射数组,以及它们如何改善内存访问。

Numba 的 CUDA 示例:原子和互斥

原文:https://towardsdatascience.com/cuda-by-numba-examples-c583474124b0

本系列的第 4 部分总结了使用 Python 从头开始学习 CUDA 编程的旅程

介绍

在本系列的前三期中(第 1 部分这里第 2 部分这里和第 3 部分),我们已经了解了 CUDA 开发的大部分基础知识,例如启动内核来执行令人尴尬的并行任务、利用共享内存来执行快速缩减、将可重用逻辑封装为设备功能,以及如何使用事件和流来组织和控制内核执行。

图 3.0。运行稳定扩散以“大历风格的原子心妈妈专辑封面”。学分:在 CreativeML Open RAIL-M 许可下拥有作品。

在本教程中

在本系列的最后一部分中,我们将讨论原子指令,它将允许我们从多个线程安全地操作同一个内存。我们还将学习如何利用这些操作来创建一个 互斥体 ,这是一种允许我们“锁定”某个资源的编码模式,因此它一次只能被一个线程使用。

点击此处获取 Google colab 中的代码。

入门指南

导入和加载库,确保你有一个 GPU。

原子学

GPU 编程完全基于尽可能并行化相同的指令。对于许多“令人尴尬的并行”任务,线程不需要协作或使用其他线程使用的资源。其他模式,比如 reductions,通过算法的设计来确保相同的资源只被线程的子集使用。在这些情况下,我们通过使用syncthreads来确保所有其他线程保持最新。

在某些情况下,许多线程必须读写同一个数组。当试图同时执行读取或写入时,这可能会导致问题。假设我们有一个将单个值递增 1 的内核。

当我们用一个线程块启动这个内核时,我们将在输入数组中获得一个值 1。

现在,当我们启动 10 个 16 线程的块时会发生什么?我们将 10 × 16 × 1 的总数加到同一个存储元素上,因此我们应该希望得到存储在dev_val中的值 160。对吗?

事实上,我们不太可能在dev_val达到 160。为什么?因为线程是同时读写同一个内存变量的!

下面是当四个线程试图从同一个全局内存中读取和写入时可能发生的情况的示意图。线程 1–3 在不同的时间(分别为 t=0、2、2)从全局寄存器读取相同的值 0。它们都递增 1,并在 t=4、7 和 8 时写回全局内存。在 t=5 时,线程 4 的启动比其他线程稍晚一些。此时,线程 1 已经写入全局内存,因此线程 4 读取 1 的值。它最终会在 t=12 时将全局变量改写为 2。

图 4.1。几个线程试图从同一个全局内存中读写可能会导致竞争情况。学分:自己的工作。

图 4.2。当线程对内容进行操作时,资源被锁定读/写,我们确保每个线程在读取时获得更新的值,并且它的写入被其他线程看到。原子操作通常较慢。学分:自己的作品。

如果我们想得到我们最初期望的结果(如图 4.2 所示),我们应该用非原子加法运算代替原子运算。原子操作将确保一次由一个单独的线程完成对任何内存的读/写。让我们多谈谈他们。

原子加法:计算直方图

为了更好地理解在哪里以及如何使用原子,我们将使用直方图计算。假设一个人想计算字母表中的每个字母在某个文本中有多少个。实现这一点的简单算法是创建 26 个“桶”,每个桶对应于英语字母表中的一个字母。然后,我们将遍历文本中的字母,每当我们遇到“a”时,我们将第一个桶递增 1,每当我们遇到“b”时,我们将第二个桶递增 1,以此类推。

在标准 Python 中,这些“桶”可以是字典,每个都将一个字母链接到一个数字。由于我们喜欢在数组上操作 GPU 编程,我们就用数组来代替。我们将存储所有 128 个 ASCII 字符,而不是存储 26 个字母。

在此之前,我们需要将字符串转换为“数字”数组。在这种情况下,将 UTF-8 字符串转换为uint8数据类型是有意义的。

请注意,小写和大写字母有不同的代码。因此,我们将使用几个实用函数来只选择小写字母或大写字母。

此外,Numpy 已经提供了一个直方图函数,我们将使用它来验证我们的结果并比较运行时间。

“Numba 示例”的 CUDA 直方图。学分:自己的工作。

让我们编写自己的 CPU 版本的函数来理解其中的机制。

由于每个 ASCII 字符都映射到 128 元素数组中的一个 bin,所以我们需要做的就是找到它的 bin 并递增 1,只要该 bin 在 0 和 127(包括)之间。

我们已经准备好了我们的第一个 GPU 版本。

酷!所以至少我们的功能在起作用。内核非常简单,与串行版本具有相同的结构。它从标准的 1D 网格步长循环结构开始,与串行版本不同,它使用原子加法。Numba 中的原子 add 有三个参数:将被递增的数组(histo)、将看到 incremenet 的数组位置(arr[iarr],它相当于串行版本中的char),最后是histo[arr[iarr]]将被递增的值(即本例中的 1)。

现在,让我们加大赌注,将其应用于更大的数据集。

我们将处理大约 570 万个字符。让我们运行并记录到目前为止的三个版本。

学分:自己的工作。

以我们的 GPU 版本为基准,我们看到 NumPy 版本至少慢 40 倍,而我们的 CPU 版本慢数千倍。我们可以在几毫秒内处理这个 570 万字符的数据集,而我们简单的 CPU 解决方案需要 10 秒以上。这意味着我们有可能在几秒钟内处理 200 亿个字符的数据集(如果我们有一个超过 20 Gb RAM 的 GPU),而在我们最慢的版本中,这将需要一个多小时。所以我们已经做得很好了!

我们能改进它吗?好了,让我们再来看看这个内核的内存访问模式。

...
for iarr in range(i, arr.size, threads_per_grid):
    if arr[iarr] < 128:
        cuda.atomic.add(histo, arr[iarr], 1)

histo是一个位于 GPU 全局内存中的 128 元素数组。在任一点启动的每个线程都试图访问该数组的某个元素(即元素arr[iarr])。因此,在任何时候,我们都有大约threads_per_block * blocks_per_grid = 128 × 32 × 80 = 327,680 个线程试图访问 128 个元素。因此,我们平均有大约 32 × 80 = 2,560 个线程竞争同一个全局内存地址。

为了减轻这种情况,我们在共享存储器阵列中计算局部直方图。这是因为

  1. 共享阵列位于芯片上,因此读/写速度更快
  2. 共享数组对于每个线程块来说都是本地的,因此较少的线程可以访问并因此竞争它的资源。

INFO: 我们的计算假设字符是均匀分布的。要小心,像自然数据集这样的假设可能不符合它们。例如,自然语言文本中的大多数字符都是小写字母,而不是平均 2560 个线程竞争,我们将有 128×32×80÷26≈12603 个线程竞争,这就有更多的问题了!

之前我们有 2,560 个线程争用同一个内存,现在我们有 2,560 ÷ 128 = 20 个线程。在核的末尾,我们需要对所有的局部结果求和。由于有 32 × 80 = 2,560 个块,这意味着有 2,560 个块在争用全局内存。然而,我们确保每个线程只做一次,而以前我们必须这样做,直到我们用尽了输入数组的所有元素。

让我们看看这个新版本与以前的版本相比如何!

学分:自己的工作。

因此,这比原始版本提高了约 3 倍!

我们将块的数量设置为 32×SMs 数量的倍数,就像上一个教程中建议的那样。但是哪个倍数呢?让我们计时吧!

学分:自己的工作。

两件事:首先,我们需要两个轴来显示数据,因为原始版本(蓝色)要慢得多。第二,竖线显示对于某个功能有多少条短信是最佳的。最后,尽管简单版本不会因为添加了更多的块而变得更差,但共享版本却不是这样。要理解这是为什么,请记住共享数组版本有两个部分

  • 第一部分很少有线程竞争相同的(快速)内存(共享数组部分)。
  • 第二部分是许多线程竞争同一个(慢)内存(最后的原子加法)。

随着更多块的添加,在简单版本中,它很快就遇到了瓶颈,而在共享阵列版本中,竞争在第一部分保持不变,但在第二部分增加了。另一方面,太少的块不会产生足够的并行化(对于任一版本)。上图找到了这两个极端之间的“最佳点”。

用互斥锁锁定资源

在前面的例子中,我们使用了带有整数值的原子加法操作来锁定某些资源,并确保一次只有一个线程控制它们。加法不是唯一的原子操作,它不需要应用于整数值。Numba CUDA 支持对整数和浮点数的各种原子操作。但是曾几何时(CUDA compute 1.x),浮点原子是不存在的。因此,如果我们想用原子为浮点写一个归约,我们就需要另一个结构。

虽然现在原子确实支持浮点,但允许我们应用任意原子操作的“互斥”代码模式在某些情况下仍然有用。

互斥,也就是互斥,是一种向试图访问它的其他线程发出某种资源可用或不可用的信号的方式。互斥体可以用一个变量来创建,这个变量有两个值:

  • 0 : 🟢绿灯,继续使用某个内存/资源
  • 1 :🔴红灯,停止,不要试图使用/访问某个内存/资源

要锁定内存,应该向互斥体写入 1,要解锁,应该写入 0。但是需要注意的是,如果有人(自动地)写互斥体,其他线程可能正在访问该资源,至少会产生错误的值,甚至更糟,会产生死锁。另一个问题是互斥体只有在之前没有被锁定的情况下才能被锁定。因此,在写入 1(锁定)之前,我们需要读取互斥体并确保它是 0(未锁定)。CUDA 提供了一种特殊的操作来以原子方式完成这两件事:atomicCAS。在 Numba CUDA 中,它的名称更加明确:

cuda.atomic.compare_and_swap(array, old, val)

如果array[0]处的当前值等于old(这是“比较”部分),该函数只会自动将val分配给array[0](这是“交换”部分);否则它现在将交换。此外,它自动返回array[0]的当前值。为了锁定一个互斥体,我们可以用

cuda.atomic.compare_and_swap(mutex, 0, 1)

由此我们将仅在锁被解锁(0)时分配锁(1)。上面这行代码的一个问题是,如果线程到达它并读取 1 (locked ),它就会继续执行,这可能不是我们想要的。理想情况下,我们希望线程停止前进,直到我们可以锁定互斥体。因此,我们采取以下措施:

while cuda.atomic.compare_and_swap(mutex, 0, 1) != 0:
    pass

在这种情况下,线程将一直存在,直到它可以正确地锁定线程。假设线程到达了一个先前锁定的互斥体。它的当前值是 1。所以我们首先注意到从curr = 1 != old = 0开始compare_and_swap而不是能够锁定它。它也不会退出while,因为当前值 1 不同于 0(while条件)。它将保持在这个循环中,直到最终能够读取当前值为 0 的未锁定互斥体。在这种情况下,从curr = 0 == old = 0开始,它也能够将 1 分配给互斥体。

要解锁,我们只需要自动给互斥体赋值一个 0。我们将使用

cuda.atomic.exch(array, idx, val)

它简单地自动赋值array[idx] = val,返回旧值array[idx](自动加载)。因为我们不会使用这个函数的返回值,在这种情况下,你可以把它看作一个原子赋值(也就是说,鉴于atomic_add(array, idx, val)array[idx] += val的赋值就像exch(array, idx, val)array[idx] = val的赋值)。

现在我们有了锁定和解锁机制,让我们重试原子的“添加一个”,但是使用互斥体代替。

上面的代码相当简单,我们有一个内核,它锁定线程的执行,直到它们自己可以得到一个解锁的互斥体。此时,他们将更新x[0]的值并解锁互斥体。在任何时候x[0]都不会被一个以上的线程读取或写入,这实现了原子性!

上面的代码中只有一个细节我们没有涉及,那就是cuda.threadfence()的使用。这个例子并不要求这样,但是要确保锁定和解锁机制的正确性。我们很快就会知道为什么了!

互斥点积

在本系列的第 2 部分中,我们学习了如何在 GPU 中应用缩减。我们用它们来计算一个数组的和。我们代码的一个不优雅的方面是我们把一些求和留给了 CPU。我们当时缺乏的是应用原子操作的能力。

我们将这个例子重新解释为点积,但是这一次将求和进行到底。这意味着我们不会返回“部分”点积,而是通过使用互斥体在 GPU 中使用原子求和。让我们首先将 reduce 重新解释为一个点积:

一切都检查过了!

在我们结束之前,我答应我们会再来一次。来自 CUDA“圣经”( B.5 .内存栅栏函数 ): __threadfence() 确保在调用 __threadfence() 之后,调用线程对所有内存的写操作不会被设备中的任何线程观察到,因为在调用 __threadfence() 之前,调用线程对所有内存的任何写操作都会发生。

如果我们在解锁互斥锁之前忽略线程防护,即使使用原子操作,我们也可能会读取陈旧的信息,因为内存可能还没有被其他线程写入。同样,在解锁之前,我们必须确保更新内存引用。所有这些都不是显而易见的,而且是在许多年前在阿尔格拉夫等人 2015 首次提出的。最终,这个补丁在 CUDA 的勘误表中发布了,这个例子启发了这一系列的教程。

结论

在本系列的最后一个教程中,您学习了如何使用原子操作,这是协调线程的一个基本要素。您还学习了互斥模式,它利用原子来创建自定义区域,一次只有一个线程可以访问这些区域。

收场白

在本系列的四个部分中,我们已经介绍了足够多的内容,可以让您在各种常见的情况下使用 Numba CUDA。这些教程并不详尽,它们旨在介绍并激发读者对 CUDA 编程的兴趣。

我们还没有涉及的一些主题有:动态并行性(让内核启动内核)、复杂同步(例如 warp-level、协作组)、复杂内存防护(我们在上面提到过)、多 GPU、纹理和许多其他主题。其中一些目前还不被 Numba CUDA 支持(从 0.56 版本开始),其中一些对于入门教程来说被认为是太高级的技术。

为了进一步提高你的 CUDA 技能,强烈推荐 CUDA C++编程指南,以及 Nvidia 博客文章

在 Python 生态系统中,重要的是要强调 Numba 之外的许多解决方案可以提升 GPU。而且它们大多是互操作的,所以不需要只选择一个。 PyCUDACUDA PythonRAPIDSPyOptixCuPyPyTorch 是正在积极开发中的库的例子。

用不同的水冲浓缩咖啡

原文:https://towardsdatascience.com/cupping-different-water-for-espresso-ec552f7640fa

咖啡数据科学

第三波水品鉴

第三波水给我发了几包水测试,我就先从咖啡品鉴开始。我想知道不同的浓度是否会改变味道。我通常需要在食物中加入更多的盐,所以我想也许更浓的水溶液会更有吸引力。然而,我没有注意到和拔火罐有什么不同。

我从一种我很熟悉的咖啡开始,我设置了四种浓度。我用了 2 倍、1.5 倍、1 倍和 0.75 倍包装的浓缩水。我主要是按照 SCA 拔火罐配 8.25g 咖啡和 150ml 热水。

从碗左到右:2x,1.5x,1x,0.75x .所有图片由作者提供。

然后,我用微波炉将水烧开,这样有助于减少不同咖啡浸泡时间的差异。

然后我倒了水。

当咖啡冷却时,我没有看到任何视觉差异。

当我剥掉外壳后,我没有闻到或看到任何不同。

当咖啡变凉时,我尝了很多次,我找不到质地、甜味、酸味或苦味的区别。我没有接受过拔火罐的训练,有经验的品尝者可能会发现不同之处。

我希望这次品尝会在更大的浓缩咖啡实验之前给我一个路标,但这个实验仍然给出了品尝水浓度差异的难度的有趣信息。

如果你愿意,可以在推特YouTubeInstagram 上关注我,我会在那里发布不同机器上的浓缩咖啡照片和浓缩咖啡相关的视频。你也可以在 LinkedIn 上找到我。也可以在关注我,在订阅

我的进一步阅读:

我未来的书

我的链接

浓缩咖啡系列文章

工作和学校故事集

如何在 Python 中获取当前时间

原文:https://towardsdatascience.com/current-time-python-4417c0f3bc4f

使用 Python 以编程方式计算当前日期和时间

照片由 Djim LoicUnsplash 上拍摄

如果你已经编程了一段时间,我敢肯定你遇到过必须处理日期(时间)和时间戳的用例。

当使用这样的结构时,确保使用时区敏感的对象是非常重要的。在生产环境中,为了完成某项任务,全球范围内许多不同的服务相互连接是很常见的。这意味着我们——作为程序员——需要确保避免时区无关的构造,这些构造可能会引入不必要的错误。

在接下来的几节中,我们将介绍几种不同的方法,您可以遵循这些方法以编程方式在 Python 中计算当前时间。

更具体地说,我们将演示如何使用datetimetime模块推断当前日期和/或时间以及时间戳。此外,我们将讨论如何将日期时间作为字符串处理,以及如何考虑时区。

如何用 Python 计算当前时间

现在为了以编程方式推断 Python 中的当前日期时间,我们可以使用如下概述的datetime模块:

>>> from datetime import datetime
>>>
>>> now = datetime.now()
>>> now
datetime.datetime(2022, 9, 30, 16, 34, 24, 88687)

上面的表达式将返回一个类型为datetime.datetime的对象。如果您想以更直观、更易读的格式打印日期时间,您需要做的就是将它转换为str(例如str(now))或者调用strftime()来指定您希望新字符串对象具有的确切字符串格式。

例如,假设我们希望只保留日期时间中的日期部分,而放弃时间信息。以下内容应该可以解决问题:

>>> now_dt = now.strftime('%d-%m-%Y')
>>> now_dt
'30-09-2022'

类似地,如果您想只保留 datetime 对象的时间部分,您可以使用time()方法:

>>> from datetime import datetime
>>> now_time = datetime.now().time()
>>> now_time
datetime.time(16, 43, 12, 192517)

同样,我们可以将上面的 datetime 对象格式化为一个字符串。如果我们想要丢弃毫秒记录部分,只保留小时、分钟和秒,那么下面的表达式就可以满足我们的要求:

>>> now_time.strftime('%H:%M:%S')
'16:43:12'

另一个可以帮助我们处理时间的有用模块是内置的time。根据操作系统和主机的日期时间配置,ctime方法将返回当前时间的字符串。

>>> import time
>>> time.ctime()
'Fri Sep 30 16:48:22 2022'

引入支持时区的日期时间对象

现在,我们在上一节中展示的问题是,我们创建的 datetime 对象是时区无关的。例如,我住在伦敦—这意味着如果我和另外一个住在美国或印度的人在同一时间点运行我们之前演示的相同命令,我们最终都会得到不同的结果,因为上面的所有表达式都将根据主机的时区来计算当前时间(这显然会因地点而异)。

通用协调时间(UTC)是一个全球标准,也被程序员群体所采用。UTC(几乎)等同于 GMT,它不会因为夏令时等而改变。用 UTC 交流与日期时间相关的需求是很常见的,因为它是通用时区,采用 UTC 可以帮助人们更容易地交流日期时间和日程安排。其他常见的编程结构(如时间戳/unix 纪元时间)也使用 UTC 进行计算。

回到前面的例子,让我们试着推断构造的 datetime 对象的时区。请再次注意,我的工作地点在伦敦,在撰写本文时,我们正处于英国夏令时(BST):

>>> from datetime import datetime
>>> now = datetime.now()
>>> tz = now.astimezone().tzinfo
>>> tz
datetime.timezone(datetime.timedelta(seconds=3600), 'BST')

现在,如果你在不同的时区运行上面的命令,你会得到不同的nowtz的值——这正是问题所在。

相反,我们可以用 UTC 时区计算当前的日期时间,这样我们所有人都会得到相同的计算结果。假设我住在伦敦(目前是英国夏令时),我们预计当前的 UTC 日期时间将比我当地的 BST 时区晚一个小时:

>>> from datetime import datetime
>>> now = datetime.utcnow()
>>> now
datetime.datetime(2022, 9, 30, 16, 22, 22, 386588)

注意,也可以通过调用replace()方法并提供datetime.timezone模块中可用的时区选项之一来更改日期时间对象的时区信息。

例如,让我们创建一个当前日期时间的日期时间对象(BST 格式):

>>> from datetime import datetime, timezone
>>> now = datetime.now()
>>> now
datetime.datetime(2022, 9, 30, 17, 26, 15, 891393)
>>> now_utc =  now.replace(tzinfo=timezone.utc)
datetime.datetime(2022, 9, 30, 17, 26, 15, 891393, tzinfo=datetime.timezone.utc)

最后的想法

在今天的文章中,我们展示了用 Python 计算日期时间和时间戳的几种不同方法。这可以通过使用两个内置模块中的一个来实现,即datetimetime

此外,我们讨论了使用时区感知构造的重要性。在生产环境中运行的现代系统通常涉及全球托管的许多不同的服务。

这意味着托管在不同国家的服务将位于不同的时区,因此我们需要以一致和准确的方式处理这种不规则性。

这就是我们通常想要使用时区感知对象的原因。此外,在编程环境中,坚持 UTC 时区和 unix 时间戳是很常见的。

在我即将发表的一篇文章中,我们将讨论更多关于 UTC 和 Unix 纪元时间的内容,所以请保持关注:)

成为会员 阅读介质上的每一个故事。你的会员费直接支持我和你看的其他作家。你也可以在媒体上看到所有的故事。

https://gmyrianthous.medium.com/membership

相关文章你可能也喜欢

用非常简单的代码为你的网站构建定制的基于 GPT 3 的聊天机器人

原文:https://towardsdatascience.com/custom-informed-gpt-3-models-for-your-website-with-very-simple-code-47134b25620b

当你建立一个基于 GPT 3 的在线聊天机器人时,学习 GPT 3、PHP 和 JavaScript,它专门针对你教授的给定主题

内容

  1. 简介
  2. GPT 3 号搭载的惊艳聊天机器人
  3. 用简单的 PHP 和 JavaScript 将 GPT-3 整合到你的网站中
  4. 以 OpenAI 提供的形式教授你的模型它不知道的事实数据
  5. 一个完整的网络应用程序,用户可以使用自己的 API 密钥与你定制的 GPT 3 聊天机器人进行交互

1.介绍

由 OpenAI 开发的 GPT-3 是一个专门从事语言处理的机器学习模型。给定一个输入文本,GPT-3 输出新的文本。根据输入和使用的 GPT-3 的确切风格,输出文本将符合一项任务,例如回答输入中提出的问题,或用附加数据完成输入,或将输入从一种语言翻译成另一种语言,或总结输入,或推断情感,甚至更疯狂的事情,例如根据输入中给出的指示编写一段计算机代码。在许多其他应用中!

经过数十亿参数和大量文本语料库的训练,GPT-3 是最大的可用模型之一。但是在所有的语言处理模型中,我发现 GPT-3 特别有吸引力,因为它有以下特点:

  • 它在线运行,所以我不需要下载任何东西到我想使用它的电脑或服务器上。我可以通过在源代码中包含对 GPT-3 API 的调用来使用它。
  • 以上包括编写调用 GPT-3 API 的 web 代码的可能性,因此您可以在您的 web 页面中包含该工具的强大功能。我们将在这里看到如何用简单的 PHP 和 JavaScript 实现这一点。
  • 尽管没有掌握可执行程序(因为它都在线运行),GPT-3 是非常可定制的。这允许你编写聊天机器人,它们的行为方式你可以调整,甚至更有趣,它们“知道”你教它们的特定主题。事实上,我们将在这里看到两种可能的方法之一来训练您的 GPT-3 模型来实现您的目标。
  • GPT-3 非常容易使用,例如在 Python 中或者在 JavaScript 和 PHP 中。

我最近测试了 GPT 3 号在协助科学教育和研究方面的能力和潜力,它充当了一个随时可用的机器人,可以回答学生或研究人员的问题。这些测试的一部分包括教 GPT-3 一些数据,然后人们可以就这些数据提问。

结果令人印象深刻,尽管有许多限制,主要是因为该模型当然并不真正理解它所读和写的内容…它只是一个统计模型,综合了语法上正确但事实上可能准确也可能不准确的文本。您可以在最近的文章中了解更多关于我所做的这些测试的信息:

重要的是,正如我在一些例子中展示的,特别是在这个例子中,通过正确设置 GPT-3 的参数,并用特别的内容训练它,它被证明非常“聪明”,特别是对于使用关于事实主题的自然语言的信息检索。我们将在这里利用这一点来制作一个网页,它知道你教 GPT-3 的一个特定主题。

通过正确设置 GPT-3 的参数,并用特别的内容对其进行训练,它被证明非常“聪明”,特别是对于使用自然语言对事实主题进行信息检索。我们将在这里利用这一点来制作一个网页,它知道你教 GPT-3 的一个特定主题。

2.GPT-3 允许的惊人聊天机器人

在本文中,我们将看到如何构建一个简单的聊天机器人,它知道你作为提示提供的特定主题。用户可以通过网页自然地与机器人聊天,问任何他们想要的。当问题涉及您告知聊天机器人的主题时,聊天机器人将根据该内容进行回复。最棒的是,你不需要以非常结构化的方式提问,因为你需要一个普通的问答匹配聊天机器人来理解你。相反,您可以用自然的方式检索信息。此外,这种反应取决于语境;例如,你可以谈论一个人的名字,然后用相应的冠词指代他或她。

举个例子,让我们比较一下普通的 GPT-3 聊天机器人在被问及我时的输出和 GPT-3 聊天机器人在被告知关于我和我的一些项目的简短简历时的输出。前者会编造一些东西,或者在最好的情况下不提供任何答案,而后者的答案会更准确,至少当你问它关于我提供的信息时。

让我先给你看这段与定制训练的 GPT-3 聊天机器人的简短对话。我用一种被 OpenAI 人称为“少量学习”的方法实现了这一点;它本质上是在提示问题(要发送给 GPT-3 API)之前加上一个包含相关信息的文本块。这不是很有效,因为它限制了您可以传递的信息量,还因为每次发送提示都会消耗很多令牌。但是极其简单实用。

你看到我的问题用粗体字;剩下的是聊天机器人的回答,我写了一些评论:

参观者:谁是卢西亚诺·阿布利亚塔?
助理:Luciano Abriata 是生物技术专家、化学博士、艺术家和内容创作者。

这个答案是从提供的培训段落中摘录的。

参观者:他创造了什么?
助理:卢西亚诺创建了一个名为 moleculARweb 的网站,这是一个通过增强现实(AR)内容进行化学和结构生物学教育和外联的网站。

该信息是正确的,尽管在培训段落中提供了它,但它与文本中的任何部分都不匹配。GPT-3 可能对它进行了再加工,或者可能它已经知道了,因为我们的网站自 2020 年以来一直在线。

参观者:那看起来很酷!molecularweb 上有出版物吗?
助理:对!该网站被刊登在 2021 年的《化学教育杂志》上:https://pubs.acs.org/doi/10.1021/acs.jchemed.1c00179

完美,链接是正确的。这条信息是在培训段落中提供的,不完全一样,但它得到了它。

我的特设训练 GPT-3 模型,热情地分享我们关于使用增强现实的化学教育网站的出版物。作者图。

参观者:我需要什么特殊的硬件来使用分子网吗?
助理:不需要,不需要什么特殊的硬件。你只需要一个网络浏览器和一个带摄像头的设备,比如智能手机、平板电脑或电脑。

同样,聊天机器人提供了正确的信息,同样,培训中并没有以这种方式给出,但信息是存在的。

参观者:你的意思是我可以在手机上运行它?
助理:是的!你可以在手机、平板电脑或电脑上运行它。参观者:我如何使用它?
助理:你可以登录网站,按照说明使用。

这里的答案是正确的,但奇怪的是看到聊天机器人增加了第二个问题!我很惊讶,这个问题在真实的对话中是很自然的…到这个时候,用户已经对网站有了相当多的了解,所以他想使用它是很自然的。

访问者:网址是什么?
助手:网址为https://molecular web . epfl . ch

很好,培训中提供了这些数据,但不完全是这样的。甚至没有提到网址,但 GPT-3“明白”用户在找什么。

参观者:太好了,谢谢。我会尝试一下。

(没有回答)

现在让我们看看,如果我们向一个未经训练的 GPT-3 聊天机器人问同样的问题会发生什么。所有的参数和上面的完全一样,除了我在这里没有应用任何的少射学习程序;也就是说,我没有向提示添加任何额外的数据。

参观者:谁是卢西亚诺·阿布利亚塔?

(没有回答)

但如果我问它一个更有名的卢西亚诺,它确实知道!

参观者:参观者:谁是卢西亚诺·帕瓦罗蒂?助理:鲁契亚诺·帕瓦罗蒂是意大利歌剧男高音,也是一名成功的跨界歌手。他是有史以来商业上最成功的男高音之一。

让我们继续问:

参观者:你知道 moleculARweb 吗?助手:我不知道分子网。

参观者:有没有利用增强现实进行化学教育的网站?助手:我不知道任何使用增强现实技术的化学教育网站。

参观者:你知道谷歌吗?助理:是的,我知道谷歌。

参观者:谷歌是什么?助理:谷歌是一家专注于互联网相关服务和产品的跨国科技公司。这些包括在线广告技术、搜索、云计算、软件和硬件。

我认为这足以看出我们的聊天机器人清楚地知道许多事情,事实上是标准 as-3 所知道的一切,但它只是不知道任何它从未见过的事情。

继续阅读,了解如何在你自己的网页中插入你自己的助手,并对其进行微调,以了解任何给定的主题——例如关于你和你的工作,这样它就可以像秘书一样与你网站的访问者聊天。

3.用简单的 PHP 和 JavaScript 将 GPT-3 整合到你的网站中

您首先需要一些东西:一个允许 PHP 和 JavaScript 的服务器、一个 API 密匙和一些您将耦合到 JavaScript 代码的 PHP 库。

I)可以运行 PHP 并允许 JavaScript 代码的服务器

最简单的解决方案是使用宿主服务,该服务本身提供 PHP 运行时并允许 JavaScript。我用的是 Altervista,它的基本免费包已经两者都允许了!你甚至不需要安装 PHP!

但你确实需要激活它(截至 2022 年 3 月,这是一项免费功能)。我使用的是 PHP 版本 8,无限制地启用所有连接很重要(否则它不会连接到 OpenAI API)。

作者截图。

ii)来自 OpenAI 的 API 密钥

大多数国家的人们都可以免费获得一个预先充值的 API 来试用这个系统。查看位于https://beta.openai.com/signup的官方 OpenAI 网站

关键:不要泄露你的 API 密匙(无论是个人还是因为你把它暴露在你的 JavaScript 代码中!)因为它的使用会烧你的学分!

我在这里给出的例子要求用户输入他/她自己的密钥。web 应用程序将密钥传递给调用 GPT-3 API 的 PHP 包装器(注意,密钥不会被存储,所以可以放心尝试我的代码,没有任何风险!).

关于这一点的更多信息,请参阅本文的其余部分。

iii)一个连接到 OpenAI 的 GPT-3 的 PHP 库,以及一种从你的网页的 HTML+JavaScript 代码使用这个库的方法

OpenAI 本身并不支持 PHP,但是有一个专门的开发人员社区,他们编写库来通过 PHP 调用 GPT-3 API(也可以从其他语言的代码中调用):

我尝试了几个可用的 PHP 库,决定选择这个:

https://github.com/karamusluk/OpenAI-GPT-3-API-Wrapper-for-PHP-8/blob/master/OpenAI.php

但是我必须对名为 OpenAI.php 的主文件做一些修改。您可以在这里获得我使用的最终文件:

http://lucianoabriata . alter vista . org/tests/GPT-3/open ai-PHP-library-as-used . txt

我做了一些小的修改,需要使它工作。其中一个小的修改是允许以编程方式传递用户的 API 键,而不是固定的。通过这种方式,你的应用程序的用户不会从你的帐户中花费代币!不好的一面是,他们需要自己得到一把钥匙。一个中间的解决方案是让你自己的密钥像最初的 OpenAI.php 文件一样硬编码在 PHP 文件中,然后在用户使用你的应用时向他们收费。

您还需要另一个文件,它将您的 HTML/JavaScript 文件连接到调用 GPT-3 API 的核心 PHP 文件。这是一个简短的 PHP 文件,内容如下:

<?php
//Based on tutorials and scripts at:
// [https://github.com/karamusluk/OpenAI-GPT-3-API-Wrapper-for-PHP-8/blob/master/OpenAI.php](https://github.com/karamusluk/OpenAI-GPT-3-API-Wrapper-for-PHP-8/blob/master/OpenAI.php)
// [https://githubhelp.com/karamusluk/OpenAI-GPT-3-API-Wrapper-for-PHP-8](https://githubhelp.com/karamusluk/OpenAI-GPT-3-API-Wrapper-for-PHP-8)//Thanks to this for hints about connecting PHP and JavaScript:
// [https://stackoverflow.com/questions/15757750/how-can-i-call-php-functions-by-javascript](https://stackoverflow.com/questions/15757750/how-can-i-call-php-functions-by-javascript)require_once “./OpenAI-PHP-library-as-used.php”;$apikey = $_GET[“apikey”];$instance = new OpenAIownapikey($apikey);$prompt = $_GET[“prompt”];$instance->setDefaultEngine(“text-davinci-002”); // by default it is davinci$res = $instance->complete(
 $prompt,
 100,
 [
 “stop” => [“\n”],
 “temperature” => 0,
 “frequency_penalty” => 0,
 “presence_penalty” => 0,
 “max_tokens” => 100,
 “top_p” => 1
 ]
);echo $res;
?>

连接 PHP 和 JavaScript 在 OpenAI 上运行 GPT-3

您的 JavaScript 代码只需异步调用上面 PHP 文件的 complete 函数,该函数在一个 实例 对象上定义(在上面的 PHP 文件中),该对象携带您想要传递的文本提示和参数。

这是在 JavaScript 中进行异步调用的方法(使用 JQuery):

var chatbotprocessinput = function(){
 var apikey = “Bearer <API KEY>“
 var theprompt = “(define prompt)“
 $.ajax({
 url: “phpdescribedfileabove.php?prompt=” + theprompt + “&apikey=” + apikey
 }).done(function(data) {
 console.log(data) //data has the prompt plus GPT-3’s output
 });
}

如果您检查我在本文后面向您展示的示例的源代码,您会发现它有点复杂。那是因为我的 web app 清理了输出(在 数据 )去掉了输入等东西;它还重新格式化文本,以粗体显示用户和聊天机器人的名称,并将整套输入和输出(随着用户与机器人聊天而增长)保存在一个内部变量中,以便 GPT-3 每次执行时都能获得有关对话的上下文。

4.以 OpenAI 提供的形式教授你的模型它不知道的事实数据

换句话说:为了更好地实现你的目标,教 GPT 3 号它需要知道什么

正如我上面所预料的,有两种主要的方法用特别的数据来“训练”你的 GPT-3 模型。一种,在这里使用,也在上面介绍过,叫做“少投学习”。少投学习非常简单:只要用几段相关信息来扩展你的提示(即 GPT-3 问题的输入)。

在我们上面看到的例子中(你可以玩这个,见下面的第 3 部分),用户会问聊天机器人关于我的问题,因为它应该为我回答,我给了它两个段落:

第一段:你好,欢迎来到卢西亚诺·阿布利亚塔的网站。我在这里代表卢西亚诺在线。我很了解他——我是一个开放的 GPT-3 模型,用卢西亚诺写的文字扩展。随意问任何问题。Luciano A. Abriata 博士是生物技术学家、化学博士、艺术家和内容创作者。在科学学科方面,Luciano 在结构生物学、生物物理学、蛋白质生物技术、通过增强和虚拟现实的分子可视化以及使用现代技术的科学教育的实验和计算方面拥有丰富的经验。卢西亚诺·阿布利亚塔 1981 年出生于阿根廷罗萨里奥。他在阿根廷学习生物技术和化学,然后移居瑞士,目前在瑞士洛桑联邦理工学院(EPFL)的两个实验室工作。他在 EPFL 的生物分子建模实验室和 EPFL 的蛋白质生产和结构核心实验室工作。他目前致力于基于网络的方法,以实现商品增强现实和虚拟现实工具,用于身临其境地查看和操纵分子结构。他还与多个团队合作研究分子建模、模拟和应用于生物系统的核磁共振(NMR)光谱。

第二段:文章标题:MoleculARweb:一个通过在商品设备中开箱即用的交互式增强现实进行化学和结构生物学教育的网站(化学教育杂志,2021:【https://pubs.acs.org/doi/10.1021/acs.jchemed.1c00179】T2)。文字:molecular web(https://molecular web . epfl . ch)最初是一个通过增强现实(AR)内容进行化学和结构生物学教育和宣传的网站,这些内容在智能手机、平板电脑和电脑等常规设备的网络浏览器中运行。在这里,我们展示了 moleculARweb 的虚拟建模工具包(VMK)的两个版本,用户可以通过定制打印的立方体标记(VMK 2.0)或通过鼠标或触摸手势在模拟场景中移动(VMK 3.0),在 3D AR 中构建和查看分子,并探索它们的机制。在模拟过程中,分子会经历视觉上逼真的扭曲、碰撞和氢键相互作用,用户可以手动打开和关闭它们,以探索它们的效果。此外,通过手动调整虚拟温度,用户可以加速构象转变或“冻结”特定的构象,以便在 3D 中仔细检查。甚至可以模拟一些相变和分离。我们在这里展示新的 VMKs 的这些和其他特征,将它们与普通化学、有机化学、生物化学和物理化学的概念的教学和自学的可能的具体应用联系起来;以及在研究中协助分子建模的小任务。最后,在一个简短的讨论部分,我们概述了未来化学教育和工作的“梦想工具”需要什么样的发展。"

每当用户向我的聊天机器人提问时,我的网页不只是发送问题,而是实际上将两段信息,加上之前的问题和答案,连接到新问题。是的,GPT-3 的输出是基于这些数据的,如果它在其中找到一些相关的内容(你仍然可以问它任何其他的事情,它可能仍然会回答)。

正如我前面提到的,通过“少量学习”来“训练”你的基于 GPT 3 的聊天机器人不是很有效,因为它限制了你可以传递的信息量,也因为它在你每次发送提示时都要消耗很多令牌。但是它非常简单和实用,正如您在上面的例子中看到的以及将在下面的第 3 节中看到的。

快速简单的少量学习的替代方法是执行 OpenAI 的人所说的“微调”。这是一个更稳定的过程,在此过程中,您只需训练您的 GPT-3 模型一次,然后将此训练存储在一个文件中以供以后使用。我还没有尝试过微调,但是你可以在这里查阅 OpenAI 的网站:

5.一个完整的网络应用程序,用户可以使用他们自己的 API 密钥与你定制的 GPT 3 聊天机器人进行交互

(如果你喜欢,也可以是你的 API 密匙,只要把它包含在你的 PHP 文件中,这样它就不会暴露出来——即使这样,注意你的用户会消耗你的信用。)

你可以很容易地编写一个 web 应用程序来实现一个基于 GPT 3 的聊天机器人。一旦从 OpenAI 获得 API 密钥,您也可以立即尝试我的示例。

作者图。

少投学习的例子:http://lucianoabriata . alter vista . org/tests/GPT-3/TDSarticle-Example-with-prompt . html

没有少拍学习的例子:http://lucianabriata . alter vista . org/tests/GPT-3/TDSarticle-Example-without-prompt . html

你可以

快速浏览一下代码:

作者截图。

额外收获:倡导网络编程

你注意到所有这些都是为网络准备的。事实上,本文是我提出的依赖于 web 编程的解决方案和未来工具的系列文章的一部分——从无缝的跨设备和跨操作系统兼容性开始。

以下是一些亮点,你可以查看我的个人资料:

https://pub.towardsai.net/read-public-messages-from-the-ethereum-network-with-simple-web-programming-70d8650e54e2

www.lucianoabriata.com我写作并拍摄我广泛兴趣范围内的一切事物:自然、科学、技术、编程等等。 成为媒介会员 访问其所有故事(我免费获得小额收入的平台的附属链接)和 订阅获取我的新故事 通过电子邮件 。到 咨询关于小职位 查看我的 服务页面这里 。你可以 这里联系我

用于危险区域绘图的自定义 Matplotlib 色彩映射表

原文:https://towardsdatascience.com/custom-matplotlib-colormaps-for-danger-zone-plots-62310983eb67

为更有意义的绘图构建精确的背景颜色渐变

莎伦·皮特韦在 Unsplash 上的照片

背景——这就是一切。在我们对数据进行排序、组织和解释之后,我们制作描述性的可视化来讲述数据的故事。当我们自己使用图表来理解数据时,一切都很好——我们理解了图表的背景。然而,如果我们的目标是通知其他人并在更广泛的受众中推动决策,我们希望图表不仅共享数据,而且明确地推动结论。你的观众不会像你一样了解数据,所以你要引导他们的思维过程。这通常需要像趋势线或颜色编码这样的情节元素,但即使这样,你也从你的观众那里假设了很多——上升趋势是好事还是坏事?他们怎么会知道?

在这里,我们将描述如何创建自定义 matplotlib cmap 对象(彩色地图)来创建“危险区域”图。这些创建的图表可以让技术和非技术受众立即理解,以显示某些数据何时会以对组织“不利”的方式发展。比较下面的两个图——在不知道这个数据是什么的情况下,你会从右边的图中知道第 2 个月和第 3 个月有些“不好”,只是增加了一些自定义的背景颜色渐变。

传统条形图与“危险区域”条形图—作者图片

我们也不总是能控制我们的数据是如何被共享的。一开始可能是向内部和外部客户进行演示(在演示中,您可以解释数据趋势和见解),但可能会变成通过电子邮件发送的数字,而没有更深入的背景。如果剧情可以自我解释表明一个应该得出的结论,那么对于第一次看它的人来说,它会减轻精神负担。这也有助于确保其他人得出和你一样的结论。

什么构成了色彩映射表(cmap)?

在我们开始定制 cmaps 之前,让我们深入了解一下它们是如何工作的。内置的 matplotlib cmaps 是四种颜色渐变组:顺序、发散、循环和定性。在 matplotlib 的文档中有一个坚实的可视化存储库供您选择,这里,但是这里有一个快速的体验:

不同预加载 cmap 类型的示例—图片由作者提供

当我们获取一个 cmap 对象时,python 会生成一个 256 元素的颜色列表,用于定义色彩映射表。每种颜色在内部都表示为红-绿-蓝-阿尔法(RGBA)的 4 元素数组,其中阿尔法最好被认为是颜色的“透明度”。因此,例如,如果我们拉出'Reds' cmap 对象,并研究第 100 种颜色(256 种颜色中的一种),我们会看到:

调查“红色”cmap

当然,没有一个预先制作的 cmaps 会完全符合我们想要制作适当的“危险图”的特定颜色和过渡点。但是有了这种对 cmap 核心元素的直接访问,重写行以得到我们想要的就变成了一个有趣的命题。这当然是一种方法(我在我的 github 笔记中提到了这一点),但这比需要的要难,尤其是当试图手动混合颜色时。

作为一个更加自动化的路径,我们将使用 matplotlib 中的LineasrSegmentedColormap()。但在此之前,我们需要一个轻量级的函数来查看我们创建的色彩映射表,然后再将它们应用到我们的绘图中:

红色渐变图—图片由作者提供

构建自定义 CMAPs

LinearSegmentedColormap()函数允许我们给出一个颜色列表,以及一个“节点”列表。“节点”指的是颜色图上列出的颜色所在的点(从 0 到 1 ),除此之外,它与它的邻居混合。因此,在下面的示例中,256 元素 cmap 的元素 1(节点 0.0)为纯绿色(rgba= 0,1,0,1),元素 128 (0.5)为纯黄色(1,1,0,1),元素 256(1.0)为纯红色(1,0,0,1)。

3 元素 cmap —作者图片

最终,我们将设计一个功能,在这个功能中,我们可以定制颜色过渡的地方。通过添加更多节点,我们可以有效地做到这一点。如果两个相邻的节点是相同的颜色,那么它们之间的空间将自动是相同的颜色。看看下面的例子,我们添加了一些重复的颜色:

5 元素 cmap —作者图片

啊!更紧密的过渡!在这种情况下,我们在 40%和 50%之间从绿色过渡到黄色,在 60%-70%之间从黄色过渡到红色。太好了!这本身可以用来手动创建自定义阴影危险图所需的 cmaps。然而,当我们处理数据时,我们更熟悉我们希望 cmap 改变颜色的 ,而不是总轴长度的百分比。让我们将这个函数化到我们的图中。

双色和三色定制 CMAPs

使用同样的技术,我们将建立 2 色混合和 3 色混合。这两个函数都接受我们想要使用的颜色——请记住,LinearSegmentedColormap()非常灵活,可以接受颜色作为名称(' Red ')、rgba ([1,0,0,1]),甚至十六进制(#ff0000)。事实上,这里有一个简单的颜色选择器工具可以帮助你得到你想要的颜色。这些函数还接受颜色之间过渡的值、过渡的宽度以及绘图轴限制(最小值和最大值),以便可以适当地缩放过渡值。

目标过渡 cmap —作者提供的图片

在函数调用中,我们假设有一个最小值为 0、最大值为 10 的图,转换正好发生在 5 处,宽度为 2。果然,转换发生在 256 元素生成的 cmap 的中途。

将这个概念扩展到一个三色函数,我们做了很多相同的事情,只是稍微复杂一点。

三色目标过渡 cmap —作者图片

应用于地块

首先,让我们生成一些虚拟数据用于我们的绘图。这是一个数据图表,显示了医院每月接待的患者数量:

要将任何梯度 cmap 添加到 matplotlib 图的背景中,我们使用imshow(),它将数据显示为图像。最难通过的变量是第一个变量(“X”)。这将图中的一个位置映射到 cmap 中的一个元素。因为我们想线性地遍历我们的自定义 cmap,我们将使用np.linspace()来创建一个 0 到 1 之间的 256 个元素的线性数组。我们还想传入extent参数,让imshow()知道我们正在扩展 cmap 的范围。最后,注意ylim()给出了一个(y min,y max)的输出列表,所以在调用two_color_cmap()函数时,我们使用括号符号来访问每一个作为限制。

应用于我们的数据并设置一个任意的转换点,我们得到这个:

双色危险区域图-作者提供的图像

或者,如果想在进入红色“危险”区域之前创建一个“警告”区域,我们可以使用三色函数添加一些黄色:

三色危险区域图—图片由作者提供

厉害!现在,我们的观众的目光被吸引到了第二个月,那里的患者数量高得令人无法接受。

水平条形图

这些自定义 cmaps 也支持水平绘图。我们只需要对我们的绘图代码做一些调整。最重要的是我们如何改变imshow()中的“X”变量。在这种情况下,我们删除了转置.T,它将grad从一组 256 个 1 元素的数组变成了一个 256 元素的数组。用print(grad)研究grad变量,以理解我们如何工作的细微差别。最后,注意np.linspace()现在从 0- > 1。如果我们想要翻转 cmap 的显示方式,我们可以像之前一样将它变回 1- > 0。

水平三色危险区域图—图片由作者提供

应用于线形图

在许多情况下,我们希望突出显示数据的可接受“范围”。在这种情况下,高值和低值都表明有问题,但在中间有一个令人满意的中间值。对于这些情况,线形图更能说明问题。使用我们制作的相同数据和相同的three_color_cmap()函数,我们可以用自定义 cmap 制作一个线图。

具有修改的 cmap 过渡宽度的线图-作者提供的图像

左边是我们的标准“混合”彩色地图,但我们的自定义 cmap 函数非常适合零转换 cmap。如果故障值非常明确,并且没有“回旋余地”,则右侧的零转换 cmap 会更有用。这对于存在已知控制上限和下限的统计过程控制(SPC) 图特别有用。

结论

除了视觉上吸引人之外,危险的情节给我们的情节增加了背景。它们使我们的数据能够自己说话,并可以立即授权任何人识别代表何时出现问题的数据点。有了这些 cmap 生成函数的框架,天空就是极限——制作你自己的,想加入多少颜色就加入多少颜色!正如你所看到的,一旦我们的设置功能到位,它们很容易实现,所以你没有燃烧卡路里产生完美的情节。

像往常一样,整个代码遍历笔记本可以从我的 github 中找到。 如果觉得有用请跟我来! 欢呼声此起彼伏,快乐的编码声此起彼伏。

用 BERT 进行自定义命名实体识别

原文:https://towardsdatascience.com/custom-named-entity-recognition-with-bert-cf1fd4510804

亚历山德拉Unsplash 上的照片

如何使用 PyTorch 和拥抱脸对文本中的命名实体进行分类

命名实体识别(NER)

信息提取的一个子任务,它试图定位非结构化文本中提到的命名实体并将其分类成预定义的类别,如人名、组织、位置、医疗代码、时间表达式、数量、货币值、百分比等。

作者图片

背景:

在本文中,我们将使用一些我在之前的 文章中介绍过的概念。

BERT 是一种基于 Transformer 编码器的语言模型。如果你对《变形金刚》不熟悉,我推荐你阅读这篇惊人的文章

https://jalammar.github.io/illustrated-transformer/

伯特一言以蔽之:

  • 它将一个或多个句子的嵌入标记作为输入。
  • 第一个令牌总是一个叫做【CLS】的特殊令牌。
  • 句子之间由另一个叫做【SEP】的特殊记号分隔。
  • 对于每个令牌,BERT 输出一个称为隐藏状态的嵌入。
  • 伯特接受了掩蔽语言模型下一句预测任务的训练。

屏蔽语言模型(MLM) 中,一个输入单词(或标记)被屏蔽,伯特必须试图找出被屏蔽的单词是什么。对于下一个句子预测(NSP) 任务,伯特的输入中给出了两个句子,他必须弄清楚第二个句子是否在语义上跟随第一个句子。

你想想看,解决命名实体识别任务意味着用标签(人,位置,..).因此,完成这项任务最直观的方式是获取每个令牌的相应隐藏状态,并通过一个分类层将其输入。最终的分类器共享权重,因此实际上我们只有一个分类器,但对于演示目的,我认为更容易将它想象成有更多的分类器。

作者图片

从上面的图片中你可以看到我们将使用一个叫做蒸馏伯特的伯特的轻量级版本。这个经过提炼的模型比原来的小了 40%,但是在各种 NLP 任务上仍然保持了大约 97%的性能。
你可以注意到的另一件事是,BERT 的输入不是原始单词而是令牌。BERT 已经关联了一个预处理文本的标记器,以便它对模型有吸引力。分词器通常将单词拆分成子词,此外还会添加特殊的记号:【CLS】表示句子的开始,【SEP】分隔多个句子,以及【PAD】使每个句子具有相同数量的记号。

此外,每个标记嵌入与一个嵌入的句子相加,嵌入的句子是一个向量,该向量以某种方式添加了信息,即标记是指作为输入给 BERT 的第一个还是第二个句子
由于与递归神经网络不同,变压器模型中的计算是并行的,因此我们失去了时间维度,即辨别句子的第一个单词和第二个单词的能力等。
因此,每个标记还被加和到一个位置嵌入中,该嵌入考虑了标记在序列中的位置

如果你想了解更多关于 BERT 或他的单词标记器的信息,请查看以下资源:

https://huggingface.co/blog/bert-101 https://huggingface.co/docs/transformers/tokenizer_summary

我们来编码吧!

资料组

我们要用的数据集叫做CoNLL-2003,你可以在 Kaggle 上找到(带开放许可证)。

直接从 Colab 下载 Kaggle 数据集(记得上传你的个人 Kaggle 密钥)。

进口

进口

分割数据集

让我们加载数据帧的前 N 行,并更改列名。然后将数据帧分成训练集、开发集和测试集。

分割数据

自定义类别

我现在要定义这个项目需要的三个类。第一个类定义了我们的蒸馏模型。不需要在预训练的语言模型之上手动构建分类器,因为 HuggingFace 已经为我们提供了一个内置的模型,其中包含作为最后一层的分类器。这个型号叫做DistilBertForTokenClassification

蒸馏器类

init 方法将输入分类的维度,即我们可以预测的令牌数,并实例化预训练的模型。

向前计算简单地采用 输入 _ id(令牌)和 注意 _ 屏蔽 (告诉我们令牌是否是填充符的 0/1 数组)和在输出字典中返回:{loss,logits}

模型类

NerDataset 类

第二类是模块 nn 的扩展。数据集,它使我们能够创建自定义数据集。

给定输入数据帧,该方法将对文本进行标记化,并将额外生成的标记与正确的标记进行匹配(如我们在上一篇文章中所述)。现在您可以索引数据集,它将返回一批文本和标签。

自定义数据集

度量跟踪类

有时计算火车循环中的所有指标很烦人,这就是为什么这个类帮助我们做到这一点。您只需要实例化一个新对象,并在每次对批处理进行预测时调用 update 方法。

自定义方法

  • tags_2_labels :获取标签列表和将标签映射到标签的字典的方法,并返回与原始标签关联的标签列表。

标签对标签

  • tags_mapping :取输入一个数据帧的 tags 列,返回: (1) 一个将标签映射到索引(标签)的字典 (2) 将索引映射到标签的字典 (3) 标签对应的标签O(4)一组在训练数据中遇到的唯一标签,这些标签将定义分类器的维数。**

标签映射

  • match_tokens_labels :从标记化的文本和原始标记(与单词而非标记相关联)中,它为每个单独的标记输出一个标记数组。它将一个标记与其原始单词的标签相关联。

  • freeze_model :冻结模型的最后几层,防止灾难性遗忘。

  • ****列车 _ 环线:通常的列车环线。

主要的

现在让我们使用主作用域中的所有内容。

创建映射标记-标签

标记文本

火车模型

现在,您应该会得到与这些类似的结果

结果

恭喜您,您基于预先训练的语言模型构建了您的命名实体识别模型!

最后的想法

在这篇实践文章中,我们看到了如何利用预训练模型的功能来创建一个简单的模型,从而在很短的时间内解决命名实体识别难题。请记住,当您根据新数据训练整个模型时,您可能会过多地更改原始 DistilBert 权重,从而降低模型的性能。为此,您可以决定冻结除最后一层(分类层)之外的所有层,并防止出现名为 精神错乱遗忘 的问题。在模型选择阶段,你可以通过冻结最后的 k 层来尝试不同的模型。

结束了

马赛洛·波利蒂

LinkedinTwitterCV

基于概率模型的客户终身价值评估

原文:https://towardsdatascience.com/customer-lifetime-value-estimation-via-probabilistic-modeling-d5111cb52dd

深入了解 BG-NBD,这是一个有影响力的分层模型,有助于了解客户的购买行为

耐嚼Unsplash 上拍照

客户终身价值的重要性

客户终身价值(CLV)是客户在其关系存续期间对公司的总价值。实际上,这个“价值”可以定义为收入、利润或分析师选择的其他指标。

CLV 是一个重要的跟踪指标,原因有二。首先,一家公司对其整个客户群的 CLV 总和给出了其市场价值的大致概念。因此,一家总 CLV 高的公司对投资者来说是有吸引力的。其次,CLV 分析可以指导客户获取和保留策略的制定。例如,可以特别关注高价值客户,以确保他们对公司保持忠诚。

许多 CLV 模型已经发展到不同的复杂程度和精确度,从粗略的启发式到复杂概率框架的使用。在本系列文章中,我们深入探讨其中之一:贝塔几何负二项分布(BG-NBD)模型。这个模型由 Fader、Hardie 和 Lee 于 2005 年开发,由于其可解释性和准确性,已经成为该领域最有影响力的模型之一。

本系列由三篇文章组成,每一篇都建立在另一篇的基础上。我们的游戏计划如下:

  1. 在第 1 部分(这一部分),我们将实现对 BG-NBD 模型及其假设的 ELI-5 理解。
  2. 第 2 部分中,我们将研究 Python 库 生存期 ,它允许我们以类似于 scikit-learn 的方式方便地将 BG-NBD 模型拟合到数据集,并且几乎可以立即获得模型参数的最大似然估计。我们还将探索寿命支持的各种下游分析。
  3. 第三部分中,我们将从贝叶斯的角度来看实现 BG-NBD 模型的另一种方法。我们将看到贝叶斯分级 BG-NBD 模型如何允许我们将我们对客户行为的先验直觉注入到模型中。为此,我们将使用 Python 库 PyMC3

BG-NBD 的范围

在深入挖掘 BG-NBD 的数学之前,我们需要了解它能做什么和不能做什么。需要记住两个主要限制:

该模型仅适用于非合同性的连续采购。

该模型只处理 CLV 计算的一个组成部分,即购买数量的预测。

让我们更详细地了解一下这些限制。

BG-NBD 适用于非合同性的连续采购

根据卖方和买方之间的关系,企业可以是契约性企业,也可以是非契约性企业。

  • 契约性商业,顾名思义,是一种买卖关系受合同约束的商业。当任何一方不再想继续这种关系时,合同终止。由于有了合同,在某一点上某人是否是企业的客户就不会有歧义。
  • 另一方面,在非合同业务中,采购是在没有任何合同的情况下根据需要进行的。

我们可以进一步区分连续离散设置:

  • 连续设置中,购买可以在任何给定的时刻发生。大多数采购情况(如食品杂货采购)都属于这一类别。
  • 在一个离散的环境中,购买通常以某种程度的规律性周期性地发生。这方面的一个例子是每周杂志购买。

BG-NBD 模型处理非合同的、持续的情况,这是四种分析中最常见也是最具挑战性的。在这种情况下,客户流失不是显而易见的,任何时候都可能发生。这使得区分已经无限期流失的顾客和将来还会回来的顾客变得更加困难。正如我们将在后面看到的,BG-NBD 模型能够为这两个选项中的每一个分配概率。

BG-NBD 专注于预测交易数量

给定期间客户的 CLV 可以通过两个数字相乘来计算:

  1. 客户在此期间的预测交易数量。
  2. 每次购买的预测价值。

通常这两个组件是分开处理和建模的。BG-NBD 模型解决了第一个问题——预测交易数量,这在许多方面是两者中更困难的。

第二个组成部分,购买的预期价值,可以通过使用简单的试探法,如取所有过去购买的平均值,或通过更复杂的概率模型,如伽马-伽马模型(也是由 BG-NBD 的作者创建的)。

直觉

在进入模型的数学之前,让我们试着理解模型在概念层面上是如何工作的。

让我们想象以下场景。日期是 2021 年 12 月 31 日,你是一家蛋糕店的经理。您已经仔细记录了今年发生的所有交易,并希望预测您的客户在 2022 年可以完成多少笔交易。

来源: pixabay

您碰巧也是一名熟练的数据科学家,您计划通过将模型与您的数据相拟合来实现这一预测。这个模型应该能够以一种可解释的方式描述客户的购买行为。

在开发模型时,您可以考虑一些假设。

每个顾客有不同的购买率

你已经注意到有些人每天都买蛋糕,有些人每个周末都买。其他人只在平均每六个月一次的特殊场合购买。您的模型需要一种方法来为每个客户分配不同的购买率。

每个客户都可以随时停止成为你的客户

在竞争激烈的蛋糕行业,忠诚度是无法保证的。在任何时候,你的客户都可能会离开你的公司去另一家公司。让我们将这种离开称为先前活跃客户的“停用”。

为了方便地建立停用模型,我们可以假设它只能在成功购买之后发生。也就是说,每次在你的商店购物后,顾客会决定是继续购买还是放弃购买。当客户决定选择后者时,就会发生停用。

我们假设停用既是永久的又是潜在的永久,因为客户一旦决定流失,就再也不会回来了。潜伏,因为他不会明确的让你知道他不再是你的客户。

说明

有了这些假设,让我们考虑下面的场景,我们有两个客户,A 和 b,他们每个人都在 2021 年进行了一些交易,每笔交易都用一个红点表示。

我们能否知道哪些客户已经停用,哪些客户仍会经常光顾您的商店并为您的未来收入做出贡献?

答案是肯定的——在一定程度上。例如,看上面 A 的模式,我们看到他过去经常购物,但我们已经有一段时间没见到他了。因为他的事务间时间比他上一次事务后的时间短得多,很可能 A 已经停用了。

另一方面,B 是一个不经常购物的人,与她平均的购物间隔期相比,她最近一次购物的时间并不长。她很可能会回来。

现在让我们将这些假设和直觉发展成一个更复杂的模型!

数学模型

概率建模:导论

传统上,CLV 是使用过去数据的简单函数来计算的。例如,我们可以通过取过去交易价值的固定分数来估计未来交易的价值。毫不奇怪,这样的计算过于简单,不可靠,也无法解释。

资料来源:英国地质调查局-NBD 文件

另一方面,BG-NBD 模型是一个概率模型。在概率模型中,我们假设我们的观察结果(即交易)是由我们可以使用概率分布建模的物理过程产生的。我们的任务是估计能最好地解释我们现有观察结果的参数。一个常用的选择是找到这些参数的最大似然估计量。然后,我们可以使用这些估计的参数来执行未来的预测。与第一个相比,这个概率框架通常更健壮、更准确、更易解释。

有了这些介绍,现在让我们将上面定性描述的假设转换成一个可靠的概率框架。

泊松过程用于模拟交易,指数分布用于模拟购买间隔时间

首先,让我们关注活跃客户的重复购买行为。我们可以假设,只要客户仍然活跃,他们的交易就遵循具有恒定购买率𝜆.的泊松过程有了这个假设,我们可以将下次购买时间δt建模为由𝜆.参数化的指数分布该发行版的 PDF 如下:

每个活跃客户都有自己的指数分布,我们可以用它来预测下次购买的概率。

上图显示了与两个客户相关的两个指数分布的 PDF。第一个顾客(蓝色曲线)通常每天购买一个蛋糕(他的购买率𝜆是 1 个蛋糕/天)。他的下一次购买发生在他当前购买的一天内的概率可以通过在 0 和 1 之间取蓝色曲线下的面积并计算为 0.63 来得到

第二个顾客每周只买一个蛋糕(他的𝜆是 1/7 蛋糕/天)。在进行同样的整合后,我们看到他的下一次购买发生在明天之前的可能性要小得多(P = 0.13)。

伽马分布,用于描述人群中购买行为的变化

有必要考虑一下,所有这些𝜆's 不同的顾客都有助于整个商店的𝜆配送。我们现在的任务是模拟这个𝜆分布。为此,我们需要遵守以下要求:

  • 这种分布最好是经过充分研究的。
  • 因为𝜆只能取正实数的值,所以选择的分布必须只有正值。
  • 分布需要足够灵活,以模拟具有不同购买行为的不同客户群。

伽马分布勾选了所有方框,是 BG-NBD 中用于模拟𝜆.的分布由形状参数 r 和比例参数α 参数化;这两个参数的不同组合导致伽马分布呈现不同的形状。这是发行版的 PDF:

值得注意的是,这种伽马分布不仅仅是一些理论上的胡言乱语。事实上,特定的伽马分布定量描述了特定客户群的集体购买行为,并具有重要的商业含义。

例如,下图中的蓝线显示了向下倾斜、左倾的伽玛分布,这是将 r 和α都设置为 1 的结果。如果这种分布符合我的客户群,我不会太高兴——严重的左偏意味着我的大部分客户的购买率𝜆接近于零。也就是说,他们几乎不买蛋糕!

另一个伽玛分布显示为橙色。这是一种更健康的分布,其中𝜆在 2 左右达到峰值,这意味着该客户群中相当大的一部分每天购买两块蛋糕。不算太寒酸!

现在,有一个小小的书呆子注意——泊松/伽马分布的组合,我们一直用来模拟我们客户的购买行为,也被称为负二项分布(NBD )。是的,这就是我们模型名字的由来。

客户的停用被建模为几何过程

现在让我们来处理停用过程。如前所述,每次购买后,客户将决定是否停用。我们可以为这种去激活分配一个概率 p 。因此,根据移动几何分布分配客户停用后的交易。这种离散分布的 PMF 如下所示:

这个 PMF 是非常直观的——它来自于注意到(1)如果一个客户在 xᵗʰ交易后停用,他一定在之前的 x-1 次交易中幸存下来,以及(2)每次幸存都带有概率(1- p )。请注意,根据定义,客户必须在停用之前至少执行过一次交易(否则他一开始就不会成为我们的客户!).

下图比较了两个客户的 p = 0.01 和 p = 0.1。

我们可以看到, p 越高,失活发生得越早。与 p = 0.1(橙色)的客户相比, p = 0.01(蓝色)的客户提前停用的概率要低得多。

描述失活概率变化的贝塔分布

类似于𝜆,考虑顾客群与分布相关联是有用的。然而这一次,我们不能使用伽玛分布,它没有上限。我们需要另一个同样灵活的分布,但是它的取值范围是从 0 到 1(因为 p 只能在 0 到 1 之间)。

这一次,贝塔分布符合我们的需求。这是 Beta 版的 PDF:

我们可以看到,该分布由两个正的形状参数来参数化, ab 。下面是一些贝塔分布的例子:

与 Gamma 分布相似,Beta 分布也有商业含义。你会希望看到一个左偏β分布,它的大部分权重接近 0,这表明你的大部分客户都有较低的 p 值,不太可能提前停用。上图中的橙色线就是一个例子。

另一个快速注意事项——正是这种贝塔/几何分布的组合导致了 BG-NBD 模型中的“BG”。现在你知道了!

将一切联系在一起:个体水平上可能性的数学模型

我们已经研究了定量描述客户行为的所有分布。那么我们如何获得这些分布的最佳参数呢?

一种方法是获得最大似然估计量(MLE),这是一种参数估计量,可以最大化模型产生实际观察到的数据的可能性。

让我们把它说得更具体些。假设我们目前在时间 T ,我们正在回顾一个特定客户的历史交易,该客户的购买率为𝜆.他在tt5 做了第一笔交易,在t做了最后一笔交易。这些画在时间线上的点看起来像这样:**

我们可以通过以下步骤推导出此人的个人水平可能性函数:

  • 第一笔交易发生在 t ₁ 的可能性用我们之前阐述的指数分布来描述:

  • t ₂发生第二笔交易的可能性是客户在 t ₁后保持活跃的概率— (1- p ) —乘以标准指数可能性分量:

  • 这样的可能性模式对每个后续交易重复,即 xᵗʰ交易在 t ₓ发生的可能性为:

  • 现在,让我们分析在ₓ.的最后一笔交易之后发生了什么我们没有观察到 t ₓ与 T 之间的任何交易;这种缺席可能是由于以下两种情况之一:
  1. 客户在 t 完成最后一笔交易后停用。我们知道,这种情况发生的概率是 p
  2. 在这段时间里,他仍然很活跃,但没有进行任何交易。这种情况发生的概率是:

  • 观察我们所观察到的交易模式的可能性就是早期交易的所有可能性乘以两种情况的可能性之和:

  • 以上定义的可能性公式适用于在观察期内进行了一些购买的客户。由于我们假设所有客户一开始都是活跃的,客户在时间[0,T]之间不进行任何购买的可能性是标准的指数函数:

  • 最后,结合上述两个可能性公式,我们获得了一个适用于所有客户的通用公式,而不管他们进行了多少次交易(或没有交易):

然后,我们可以有计划地尝试不同的值 p 和𝜆,并选择一个( p, 𝜆)组合来最大化这种可能性。这些参数值被称为最大似然估计量(MLE ),代表描述个人购买行为和去激活概率的“最佳”参数。

请注意,这个个体水平的似然函数只涉及三个需要由数据提供的未知变量:

  • x : t 重复交易的次数。这也叫【重复】频率
  • t ₓ: 客户最后一次交易时的年龄,也就是他第一次和最后一次交易之间的时间。这也叫做近因
  • T : 客户在分析点的年龄,即从他的第一笔交易到分析时间所经过的时间。

有趣的是,我们可以看到早期交易的时间不是公式的一部分。

其行对应于不同的客户 id 并且其列指示每个客户的 x、t ₓ和 T 的数据集被称为“RFM 格式”。这里的“R”和“F”分别代表最近和(重复)频率。同时,“M”代表货币价值;这是一个我们不会在分析中使用的列,因为我们不关心交易值。RFM 格式是 CLV 分析中常用的标准格式。

缩小:总体水平上可能性的数学模型

作为一家拥有(希望如此)众多客户的公司,我们常常对关注单个客户不太感兴趣。相反,我们想把我们的客户群作为一个整体来分析。具体来说,我们感兴趣的是获得描述我们整个业务绩效的最佳伽玛和贝塔分布。

就像我们如何使用 MLE 获得个体的最佳 p 和𝜆一样,我们也可以使用 MLE 获得总体的最佳 r、 α a、b。在本文中,我不会推导总体水平的似然方程;它已经够长了。然而,如果你已经理解了上面的数学,你应该可以很好地投入到 BG-NBD 论文中清晰解释的推导中。

结尾部分

BG-NBD 的其他应用

到目前为止,我们已经围绕 CLV 计算进行了讨论,这也是 BG-NBD 最初的目的。然而,BG-NBD 比这更通用。事实上,它可以用于模拟任何涉及不同“用户”进行重复“交易”的现象,并预测(1)如果这些“用户”仍然“活跃”,他们将进行多少次未来“交易”,以及(2)在分析期间他们仍然“活跃”的概率。例如:

  • 通过探索用户的使用历史来预测移动应用的未来使用频率。
  • 通过分析你的远房亲戚的通话模式,计算她还活着的概率。
  • 通过查看你的火绒约会对象发短信的频率来检查他们是否对你不感兴趣。

前进

好吧,我知道我们已经经历了很多数学,这可能是具有挑战性的。您可能会想:有没有一种方法可以跳过所有这些等式,使用这个模型的现成实现来开始从中获得商业价值?

我听到了!在系列的第 2 部分中,我们将查看 Python 库生存期,它允许我们使用几行代码从给定的过去事务记录中获得 r、αT6、a、和 b 的 MLE。该库还包含其他有用的分析和绘图功能,使我们能够从 BG-NBD 模型和其他相关模型中获得业务洞察力。

之后,在第 3 部分中,我们将检查 BG-NBD 的替代实现,它从贝叶斯角度接近参数估计。这个贝叶斯框架将允许我们“注入”我们的领域知识和/或信念到建模过程中。

我希望在那里见到你!

参考

[1]“计算你的顾客”简单的方法:帕累托/NBD 模型的替代方案(布鲁斯·哈迪 et。阿尔,2005)

[2]货币价值的伽玛-伽玛模型(Bruce Hardie et。阿尔,2013)

:所有图像、图表、表格、方程式,除特别注明外,均归本人所有。

如果你对这篇文章有任何意见或者想联系我,请随时通过 LinkedIn 给我发一个联系方式。另外,如果你能支持我,通过我的推荐链接成为一名中级会员,我将非常感激。作为一名会员,你可以阅读我所有关于数据科学和个人发展的文章,并可以完全访问所有媒体上的故事。

用 Python 进行客户细分(实现 STP 框架——第 1/5 部分)

原文:https://towardsdatascience.com/customer-segmentation-with-python-implementing-stp-framework-part-1-5c2d93066f82

使用 Python 实现层次聚类的分步指南

艾萨克·史密斯在 Unsplash 上拍摄的照片

我正在开始一个新的博客文章系列,在那里我将向你展示如何用 Python 一步一步地应用流行的 STP 营销框架。STP(细分、目标、定位)是现代营销中一种众所周知的战略方法,可以帮助您了解您的客户群(细分),并更有效地将您的产品投放到目标受众中。这是一种从以产品为中心过渡到以客户为中心的方法。STP 框架允许品牌制定更有效的营销策略,高度关注目标受众的需求。

在这个博客系列中,我将描述如何通过细分更好地了解你的客户,我们将看到不同的方法,然后我们将看到如何定位自己,以便更好地为客户服务。

这第一篇文章将着重于理解和预处理数据集,并以一种简单的方式对我们的客户进行细分。
在接下来的帖子中,我们将应用一种更复杂的分割方式。

整个笔记本和参考数据都可以在 Deepnote 笔记本中找到。

**Table of contents** ∘ [STP Framework](#ebab)
  ∘ [Environment Setup](#43f8)
  ∘ [Data Exploration](#c243)
  ∘ [Data Preprocessing](#c124)
  ∘ [Hierarchical Clustering](#7b10)
  ∘ [Conclusion](#a70d)

STP(细分、目标、定位)框架

细分 帮助您根据潜在或现有客户的人口统计、地理、心理和行为特征,将他们划分为不同的群体。每个群体的顾客都有相似的购买行为,并可能在不同的营销活动中采取相似的行动。像“一刀切”的一般营销活动并不是最好的营销策略。

:一旦你了解了你的客户群,下一步就是选择一部分要关注的客户群。很难制造出让每个人都满意的产品。这就是为什么最好专注于最重要的客户群,让他们开心。以后你总是可以扩大你的关注范围,瞄准更多的顾客。

选择目标市场时,最重要的考虑因素是:

  • 分段的大小
  • 增长潜力
  • 竞争对手的目标市场和产品

目标活动利用定性检查,属于广告领域。这就是为什么我们不会在这个博客系列中关注目标阶段。

定位 :在这一步,你把前两步考虑到的不同属性绘制出来,然后定位你的产品。这有助于您确定您想要提供的产品和服务最能满足客户的需求。定位还指导您如何以及通过什么渠道向客户展示这些产品。在这个过程中还使用了另一个框架,叫做营销组合。当我们到达那个模块时,我将简要描述营销组合。

环境设置

在这整个系列教程中,我将使用 Deepnote 作为唯一的开发工具。Deepnote 是一个令人敬畏的基于 web 的类似 Jupiter 的笔记本环境,它支持多用户开发生态系统。默认情况下,Deepnote 安装了最新的 Python 和著名的数据科学库。如果需要,通过“requirements.txt”或常规的 bash 命令安装其他库也非常容易。

以后我会单独写一篇关于如何更有效地使用 Deepnote 的帖子。

数据探索:了解数据集

我们将使用一个预处理过的快速消费品(FMCG)数据集,它包含 2000 个人的客户详细信息以及他们的购买活动。

用 Excel 查看数据集是一个很好的做法,因为在 Excel 中滚动和浏览数据集更容易(只要数据集不是超级大)。谢天谢地,Deepnote 为 CSV 数据集提供了更好的浏览体验。它每列显示一个分布图,让你对每个变量一目了然。

让我们使用 Pandas 库将客户数据集加载到笔记本中。

我们可以看到,对于每个客户,我们有一个 ID 列和七个其他人口统计和地理变量。
以下是每个变量的简短描述:

将数据集加载到 pandas 数据框后,我们可以应用“描述”方法来获得数据集的摘要。这显示了我们数据集的一些描述性统计数据。

对于数值变量,输出包括计数、平均值、标准差、最小值、最大值以及 25、50 和 75 个百分点。对于分类变量,输出将包括计数、唯一、顶部和频率。

让我们继续我们的数据探索,看看变量的成对相关性。相关性描述变量之间的线性相关性,其范围从-1 到+1。其中+1 表示非常强的正相关,而-1 表示强的负相关。相关性为 0 意味着这两个变量不是线性相关的。

相关矩阵中的对角线值总是 1,因为它表示变量与其自身的相关性。不幸的是,仅仅看数字很难对变量之间的关系有一个大致的了解。让我们用 Seaborn 的热图来绘制这些数字。

从这张热图中,我们可以看到年龄与受教育程度或职业与收入之间有很强的正相关关系。这些变量间的相关性在分割过程的特征选择中将是重要的。

为了理解变量之间的成对关系,我们可以使用散点图。在 Deepnote 笔记本里,你会找到散点图的例子。

数据预处理

即使我们正在处理的客户数据是干净的,我们也没有对数据进行任何统计处理。

在计算单个记录之间的距离时,大多数机器学习算法使用两个数据点之间的欧几里德距离。
当我们的数据集中的要素比例不同时,此计算的执行效果不佳。例如,尽管 5 千克和 5000 克的含义相同,但在距离计算中,算法会对高量值特征(5000 克)施加很大的权重。
这就是为什么在将数据输入任何机器学习或统计模型之前,我们必须首先将数据标准化,并将所有特征置于相同的数量级。

*https://medium.com/greyatom/why-how-and-when-to-scale-your-features-4b30ab09db5e

Scikit-learn 在预处理模块下提供了这个方便的类“StandardScaler ”,这使得标准化我们的数据框架变得非常容易。

分层聚类

我们将使用 Scipy 的层次模块中的树状图和链接对数据集进行聚类。
树状图是数据点的树状层次表示。它最常用于表示层次聚类。另一方面,链接是帮助我们实现聚类的功能。这里我们需要指定计算两个集群之间距离的方法,完成后,链接函数返回链接矩阵中的分层集群。

在 x 轴的底部,我们有观察结果。这些是 2000 个人客户数据点。在 y 轴上,我们看到由垂直线表示的点或簇之间的距离。点之间的距离越小,它们在树中的分组位置就越靠下。

手动确定集群的数量

链接对象可以为我们计算出最佳的聚类数,并且树状图可以用不同的颜色表示这些聚类,这很棒。但是,如果我们必须从树状图中识别这些集群呢?该过程是穿过没有被任何延伸的水平线截取的最大的垂直线。切片后,切片线下的聚类数将是最佳聚类数。在下图中,我们看到两条候选人垂直线,在它们之间,“候选人 2”较高。因此,我们将穿过这条线,这将在下面产生 4 个集群。

结论

分层聚类实现起来非常简单,它可以返回数据中的最佳聚类数,但不幸的是,由于它的速度很慢,所以在现实生活中并不使用。相反,我们经常使用 K-means 聚类。在我们的下一篇文章中,我们将看到如何实现 K-means 聚类,我们将尝试用 PCA 来优化它。*

感谢阅读!如果你喜欢这篇文章一定要给 鼓掌(最多 50!) 让我们 连接上LinkedIn 在 Medium 上关注我 保持更新我的新文章**

通过此 推荐链接 加入 Medium,免费支持我。

**https://analyticsoul.medium.com/membership **

用 Python 进行客户细分(实现 STP 框架——第 2/5 部分)

原文:https://towardsdatascience.com/customer-segmentation-with-python-implementing-stp-framework-part-2-689b81a7e86d

使用 Python 实现 k-means 聚类的分步指南

艾萨克·史密斯在 Unsplash 上拍摄的照片

在我的上一篇文章中,我们开始了实施流行的 STP 营销框架的旅程,在这篇文章中,我们将通过使用最流行的无监督机器学习算法之一“k-means 聚类”来克服分层分割的局限性,从而继续我们的旅程。

整个笔记本和数据集都可以在 Deepnote 笔记本中获得。

**Table of contents** ∘ [Introduction](#2740)
  ∘ [Implementing K-Means Clustering](#99d8)
  ∘ [Observations](#9494)
  ∘ [Visualize our customer segments](#2617)
  ∘ [Conclusion](#b70d)

介绍

分层聚类对于小型数据集非常有用,但是随着数据集大小的增长,计算需求也会快速增长。因此,层次聚类是不实际的,在行业中,平面聚类比层次聚类更受欢迎。在这篇文章中,我们将使用 k-means 聚类算法来划分我们的数据集。

有许多无监督聚类算法,但 k-means 是最容易理解的。K-means 聚类可以将未标记的数据集分割成预定数量的组。输入参数“k”代表我们希望在给定数据集中形成的聚类或组的数量。然而,我们必须小心选择“k”的值。如果“k”太小,那么质心就不会位于簇内。另一方面,如果我们选择‘k’太大,那么一些集群可能会过度分裂。

我推荐下面这篇文章来学习更多关于 k-means 聚类的知识:

实现 K 均值聚类

k 均值算法的工作方式是:

  1. 我们选择我们想要的集群数量“k”
  2. 随机指定簇质心
  3. 直到聚类停止变化,重复以下步骤:
    i .将每个观察值分配给质心最近的聚类
    ii。通过取每个聚类中点的平均向量来计算新的质心

https://commons . wikimedia . org/wiki/File:K-means _ convergence . gif

k-means 算法的局限性在于,您必须预先决定您期望创建多少个聚类。这需要一些领域知识。如果您没有领域知识,您可以应用“肘法”来确定“k”(聚类数)的值。
这种方法本质上是一种蛮力方法,在这种方法中,对于某个“k”值(例如 2-10),计算每个聚类成员与其质心之间的平方距离之和,并根据平方距离绘制“k”。随着簇数量的增加,平均失真将会减少,这意味着簇质心将会向每个数据点移动。在图中,这将产生一个肘状结构,因此命名为“肘法”。我们将选择距离突然减小的“k”值。

正如我们在上面的图表中看到的,这条线僵硬地下降,直到我们到达第 4 类的数字,然后下降更加平稳。这意味着我们的肘在 4,这是我们的最佳集群数量。这也符合我们在上一篇文章中所做的层次聚类的输出。

现在,让我们对 4 个集群执行 k-means 聚类,并在我们的数据帧中包含段号。

观察

太好了,我们已经把客户分成了 4 组。现在让我们试着去了解每一组的特点。首先,让我们通过聚类来查看每个特征的平均值:

以下是我的一些观察:

作者图片

根据以上观察,我感觉第 1 和第 4 部分比第 2 和第 3 部分富裕,收入更高,职业更好。
最好给每个组分配合适的名称。

1: 'well-off'
2: 'fewer-opportunities'
3: 'standard'
4: 'career-focused'

让我们来看看每个细分市场的客户数量:

可视化我们的客户群

年龄与收入:

教育与收入:

结论

我们在“年龄与收入”散点图中观察到,高收入(富裕)的高年龄客户是分开的,但其他三个细分市场没有那么明显。

在第二个观察‘学历 vs 收入’小提琴情节中,我们看到没有学历的客户收入较低,毕业的客户收入较高。但是其他部分不是那么可分的。

根据这些观察,我们可以得出结论,k-means 在将数据分成簇方面做得不错。然而,结果并不令人满意。在下一篇文章中,我们将把 k-means 和主成分分析结合起来,看看我们如何能取得更好的结果。

和以前一样,全部代码和所有支持数据集都可以在 Deepnote 笔记本中获得。

感谢阅读!如果你喜欢这篇文章一定要 鼓掌(最多 50!) 和咱们 连上LinkedIn和* 关注我上媒 保持更新我的新文章*

通过此 推荐链接 加入 Medium,免费支持我。

*https://analyticsoul.medium.com/membership *

使用 Python 进行客户细分(实现 STP 框架——第 3/5 部分)

原文:https://towardsdatascience.com/customer-segmentation-with-python-implementing-stp-framework-part-3-e81a79181d07

使用 PCA 实现 k-means 聚类的分步指南

Unsplash 上由 Carlos Muza 拍摄的照片

到目前为止,在我们实施 STP 营销框架(细分、目标和定位)的过程中,我们已经完成了以下工作:

的第一篇文章中,我们已经
定义了我们的目标并学习了基础知识
设置了 Deepnote 开发环境
探索了我们一直在使用的客户数据集的不同属性/特征
处理并标准化了我们的数据集
使用分层聚类对客户进行了细分

然后,在第篇文章中,我们
学习了 k-means 聚类算法(平面聚类)和 elbow 方法,以确定数据集中的最佳聚类数
使用 k-means 聚类对客户数据集进行细分,并命名我们的客户群
分析了细分结果

正如我们在第二篇文章中看到的,我们的聚类方法无法清楚地区分不同的客户群,在这篇文章中,我们将尝试使用降维来改善这一点。

主成分分析

当数据集中的多个要素高度相关时,会因为冗余信息而扭曲模型的结果。这就是我们的 k 均值模型所发生的情况。这就是所谓的多重共线性问题。我们可以通过降维来解决这个问题。

了解有关多重共线性的详细信息。

第一篇文章中的相关矩阵表明年龄学历相关,并且收入职业也相关。我们将使用主成分分析(PCA)这种降维方法来解决这个问题。

降维是减少数据集的属性数量,同时保留原始数据中有意义的属性的过程。正如莎士比亚所说,“有时少即是多”。降维不完全是这样,但很接近:P

了解更多关于降维的知识

PCA 将一组相关变量(p)转换成较少数量的不相关变量 k (k < p) called 主分量,同时尽可能保持原始数据集中的变化。这是在标准化数据集的数据预处理步骤中执行的。

了解更多关于五氯苯甲醚的信息。

识别主要成分

首先,让我们从 sklearn 导入 PCA 库,并用标准化的客户数据集创建我们的 pca 对象。

pca 对象的属性“explained_variance_ratio_”包含七个组件,它们解释了我们数据集的 100%可变性。第一个成分解释了约 36%的可变性,第二和第三个成分分别解释了 26%和 19%的可变性。

经验法则是选择保留 70–80%可变性的组件数量。如果我们选择三个顶级组件,它们将拥有超过 80%的可变性,如果我们选择四个组件,它们将保留几乎 90%的可变性。让我们挑选三个组件并适合我们的 pca 模型。
然后,我们使用原始数据集中的列创建一个包含三个主要成分的数据框架。请注意,数据帧中的所有值都在负 1 和 1 之间,因为它们本质上是相关的。

现在,让我们看看新的相关矩阵。

成分一与年龄、收入、职业、定居规模正相关。这些特征与一个人的职业有关。

另一方面,性别、婚姻状况、教育是第二个组成部分最显著的决定因素。我们还可以看到,在这个组件中,所有与职业相关的功能都是不相关的。因此,这个组成部分不是指个人的职业,而是指教育和生活方式。

对于第三个组成部分,我们观察到年龄、婚姻状况、职业是最显著的决定因素。婚姻状况职业有负面影响,但仍然很重要。

用 PCA 实现 K-Means 聚类

好了,现在我们对新的变量或组件代表什么有了一个概念。让我们实现 k-means 聚类,将我们的三个组件视为特征。我们将跳过肘方法,因为我们已经在第二篇文章中学习过了。因此,我们将直接开始用四个集群实现 k-means 算法。笔记本包含详细的实现方法供您参考。

分析分割结果

之前我们已经确定成分一代表事业,成分二代表教育&生活方式,成分三代表生活或工作经历。

现在让我们分析分割结果,并尝试像以前一样标记它们。

细分 0: 低职业和经验价值,高教育和生活方式价值。
标签:标准
细分 1: 高职业但低学历、低生活方式、低经历
标签:以职业为中心
细分 2: 低职业、低学历、低生活方式、高经历
标签:机会少
细分 3: 高职业、高学历、高经历

让我们来看看每个细分市场的客户数量:

现在,让我们来看一下与前两个组件相关的部分。

为了比较,这里是没有 PCA 的原始 k-means 实现的散点图(本系列的第 2 篇)。

作者图片

正如您所看到的,现在这四个部分可以清楚地识别。虽然标准和更少机会有一些重叠,但总体结果仍然远远好于先前的结果。

结论

到目前为止,我们已经将我们的客户分成了四个不同的、可明确识别的群体。至此,我们已经完成了 STP 框架的“细分”部分。由于“定位”主要涉及关注哪个客户群的商业决策,我们将在下一篇文章中跳到“定位”。

与往常一样,完整的代码和所有支持数据集都可以在 Deepnote 笔记本中获得。

感谢阅读!如果你喜欢这篇文章,一定要给 鼓掌(最多 50!) 连线LinkedIn和* 在 Medium 上关注我 保持更新我的新文章。*

通过此 推荐链接 加入 Medium,免费支持我。

*https://analyticsoul.medium.com/membership *

使用熊猫交叉表自定义您的数据框

原文:https://towardsdatascience.com/customize-your-data-frame-with-pandas-crosstab-843b4ca8fb5b

只需用交叉表透视您的表数据

罗伯特·基恩在 Unsplash 上的照片

介绍

交叉表函数是帮助您在 Pandas 中重塑数据的众多方法之一。乍一看,它的用途似乎与 pivot 类似,您可以使用 Pandas Crosstab 执行许多与 Pandas Pivot Table 相同的操作。然而,有一些特殊的区别:

  • 交叉表支持您规范化生成的数据框并返回百分比值。
  • 函数的输入不一定是数据帧。对于它的行和列,它也可以接受类似数组的对象。

本文将为您提供一个获得大多数特性的简明摘要。

资料组

在本文中,我将使用一个名为“Taxis”的公开可用的seaborn数据集,可以通过以下方式轻松获得:

import seaborn as sns df = sns.load_dataset('taxis')
df.head()

以下是数据集的一瞥:

图 1:出租车数据集——作者提供的图片

图 2:数据信息—作者图片

现在,让我们开始看看我们有什么。

句法

我们可以把一些基本参数的细节解释如下。更多详细信息见 本文档 :

  • index: V 行中作为分组依据的值。
  • 列:列中作为分组依据的值。
  • 值:要聚合的数据
  • aggfunc: 要使用的聚合函数
  • 边距:获取行或列的小计
  • 规格化:将所有值除以值的总和

下面的例子将帮助你更好地理解这个概念。

分组

可以看出,该数据集包含关于取货地点和付款方式的信息。我想看看每种支付方式在每个地方使用了多少次。使用 Crosstab 可以简单地构建一个频率矩阵表,以不同地方每种支付方式的总计数作为值。

我将 取货地点(区) 传递给index参数,并将 付款方式 设置为columns.

结果显示为多索引交叉表:

图 3:不同地点付款方式的频率表—按作者分类的图片

使用 aggfunc

如果我想计算取货地点在皇后区和布朗克斯区的现金用户的平均距离,那么对备选聚合函数应用aggfunc。在这里,aggfunc = "mean”将被使用。

图 4:平均距离

因此,很容易观察到,顾客使用现金从皇后区旅行的平均距离约为 5.3 英里,这是从布朗克斯区(2.1 英里)开始的距离的两倍多。

小计

交叉表的一个便利特性是为行和列添加小计。例如,我很好奇不同取货地点的人用现金和信用卡支付的总费用。marginsmargins_name在这种情况下应用如下代码。

图 5:不同行政区每个类别的总和——按作者分类的图片

规范化交叉表

以图 5 中的结果为例。对于每一行和每一列,我想找出每个值相对于小计的百分比。要查找行百分比值,normalize = "index"。同时,normalize = "columns"将帮助您找到基于列总数的百分比值。设置normalize = "all"意味着值以整个数据框的百分比计算。

下图 6 描述了不同地点每种支付方式的百分比。

图 6:按列总数的标准化—按作者的图像

为什么交叉表

其实有些任务也可以用 unstackpivot_ table 来解决。然而,有时候,我发现交叉表是最容易使用的方法,因为它的语法容易记忆,转换也很快,而其他两种方法可能需要一些步骤才能得到最终答案。

也就是说,根据不同的情况,使用让你最舒服的工作方法。

参考

数据集

Waskom,m .等人,2017 年。mwaskom/seaborn:v 0 . 8 . 1(2017 年 9 月),芝诺多。可在:https://doi.org/10.5281/zenodo.883859.以 BSD-3 许可证发布。

其他人

https://datagy.io/pandas-crosstab/

定制您的 ggplot2 条形图——5 种快速改善 R 数据可视化的方法

原文:https://towardsdatascience.com/customize-your-ggplot2-bar-graph-5-ways-to-instantly-improve-your-r-data-visualizations-f9c11dfe0163

如何抓住读者的注意力并制作出专业的图表

作者图片

条形图是数据可视化的基础。它们对于比较分类数据的值很有用。

  • 在 tidyverse 中,您可以只用两行代码构建下面的条形图。

作者图片

尽管它看起来很简单,但是这个图表告诉了你需要知道的关于数据的一切。如果你只是做一个探索性的数据分析,它适度的外观可能就足够了。然而,如果你计划通过报告或演示文稿来传达你的数据,这可能不足以吸引读者的注意力。自定义图表的美感可以提高其可读性,突出有趣的趋势,并帮助您的可视化看起来更专业。

这里有 5 个快速简单的方法来改善你的图表。

在本教程中,我们将使用与生成上图相同的数据。它包含了在一个假设的商店中销售的每一个计算机配件的利润。您可以使用以下代码生成数据集。

  1. 重新排序因素

作者图片

  • 通过按降序重新排列条形来引起对突出数据点的注意。

  • 对于其他数据集,按地理位置对数据进行分组或按字母顺序进行排序可能也很有用。通过阅读它的文档掌握 fct_reorder,你也可以这样做。

2。添加标签

作者图片

  • 编辑你的标题,x 轴和 y 轴标签,给你的读者上下文,并确保变量易于理解。

3。重新定位刻度标签

作者图片

  • 随着刻度标签数量和长度的增加,它们变得更难阅读。重叠和对齐问题会使它们难以理解。要解决这个问题,你可以稍微旋转它们。

作者图片

  • 或者翻转你的坐标轴。

4。将不太重要的类别重新归类为“其他”

作者图片

  • 通过重新组合最低的数据点来强调最高数据点的极端情况。您可以通过用mutate()创建一个新列来实现这一点。这里,我们使用ifelse将利润低于 3000 美元的所有项目重新分类到others

5。定制您的调色板

作者图片

  • r 有多种内置调色板可供选择。考虑色盲友好的调色板。包viridis 是一个很好的起点。

作者图片

  • 在为工作制作可视化效果时,您可能希望使用公司的调色板。由于profit是连续数据,所以我用了scale_fill_gradient。你可以用highlow来指定渐变的结束颜色。
  • r 还有定制的主题,可以用来即时改变图形的布局和背景颜色。我在这里使用了theme_minimal()来获得一个漂亮干净的外观。

接下来的步骤

不久前,我们推出了theme_minimal,这是 R 的众多theme预设之一,您可以应用它来立即美化您的图形。尝试其他默认主题在这里并考虑探索自定义主题来应用你自己的品牌。

改善你的视觉效果是一项值得你投入时间的任务。根据我的经验,分享像下面这样的高级动画可视化帮助我在科技 Twitter 上建立了一个追随者。深入学习我们之前学过的库和函数,让你的图表更上一层楼。

定制 Scikit-Learn 管道:编写自己的转换器

原文:https://towardsdatascience.com/customizing-scikit-learn-pipelines-write-your-own-transformer-fdaaefc5e5d7

如何使用管道并将定制的变压器添加到处理流程中

SEO yeon Lee 在 Unsplash 上拍摄的照片

您是否正在寻找一种方法来组织您的 ML 流程,同时保持处理流程的灵活性?想要使用管道,同时将独特的阶段合并到数据处理中吗?本文是一个简单的分步指南,介绍如何使用 Scikit-Learn 管道以及如何向管道添加定制的变压器。

为什么是管道?

如果你作为一名数据科学家工作了足够长的时间,你可能听说过 Skicit-Learn 管道。您可能在从事一个混乱的研究项目,并以一个充满处理步骤和各种转换的巨大笔记本结束时遇到过它们,但不确定在最后的成功尝试中应用了哪些步骤和参数,产生了良好的结果。否则,如果您曾经有机会在生产中部署模型,您一定会遇到它们。

简而言之,管道是为数据科学家制造的对象,他们希望他们的数据处理和建模流程组织良好,并易于应用于新数据。即使是最专业的数据科学家也是人,记忆力有限,组织能力也不完善。幸运的是,我们有管道来帮助我们维持秩序、可复制性和…我们的理智。

这篇文章的第一部分是关于什么是管道以及如何使用管道的简短介绍。如果您已经熟悉管道,请深入研究第二部分,在那里我将讨论管道定制。

管道的简短介绍

管道是顺序转换的列表,后跟 Scikit-Learn 估计器对象(即 ML 模型)。管道为我们提供了一个结构化的框架,用于将转换应用于数据并最终运行我们的模型。它清楚地概述了我们选择应用的处理步骤、它们的顺序以及我们应用的确切参数。它迫使我们对所有现有的数据样本进行完全相同的处理,提供了一个清晰的、可复制的工作流程。重要的是,它使我们以后能够对新样本运行完全相同的处理步骤。每当我们想要将我们的模型应用于新数据时,这最后一点是至关重要的——无论是在使用训练集训练模型之后在测试集上训练我们的模型,还是处理新的数据点并在生产中运行模型。

如何应用管道?

对于这篇文章,我们将遵循一个简单的分类管道的例子:我们希望根据个人和健康相关的信息,确定明年患某种疾病的高风险个人。

我们将使用一个玩具数据集,包括一些相关的功能(这是一个人工数据集,我创建的目的只是为了演示)。

让我们加载数据,看看第一批患者:

import pandas as pdtrain_df = pd.read_csv('toy_data.csv', index_col = 0)
test_df = pd.read_csv('toy_data_test.csv', index_col = 0)train_df.head()

我们的预处理将包括缺失值的插补和标准缩放。接下来,我们将运行一个 RandomForestClassifier 估计器。

下面的代码描述了管道的基本用法。首先,我们导入必要的包。接下来,我们定义管道的步骤:我们通过向管道对象提供一个元组列表来做到这一点,其中每个元组由步骤名称和要应用的转换器/估计器对象组成。

# import relevant packeges
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.impute import SimpleImputer
from sklearn.ensemble import RandomForestClassifier# define our pipeline
pipe = Pipeline([('imputer', SimpleImputer()),('scaler', StandardScaler()), ('RF', RandomForestClassifier())])

然后,我们将管道拟合到训练数据,并预测测试数据的结果。在拟合阶段,保存每个步骤的必要参数,创建一个转换器列表,该列表准确地“记住”要应用的转换和要使用的值,然后是经过训练的模型。

最后,我们使用 predict()方法将整个管道应用于新数据。这将对数据进行转换,并使用估计器预测结果。

X_train = train_df.drop(columns = ['High_risk'])
y_train = train_df['High_risk']# fit and predict
pipe.fit (X_train, y_train)
pipe.predict (X_test)

如果我们希望拟合模型并在一个步骤中获得训练集的预测值,我们也可以使用组合方法:

pipe.fit_predict(X_train, y_train)

通过编写您自己的转换器来定制您的管道

正如我们已经看到的,流水线只是一系列转换器,后跟一个估计器,这意味着我们可以使用内置的 Scikit-Learn 转换器(如 SimpleImputer、StandardScaler 等)混合和匹配各种处理阶段。

但是,如果我们想要添加一个特定的处理步骤,而这个步骤并不是数据处理的常见步骤之一,该怎么办呢?

在这个例子中,我们试图根据个人和健康相关特征来识别在即将到来的一年中患某种疾病的高风险患者。在上一节中,我们创建了一个管道来估算缺失值,缩放数据,最后应用随机森林分类器。

然而,在查看完整数据集后,我们意识到其中一个特征(年龄)具有一些负值或可疑的高值:

经过一些调查,我们发现年龄字段是手动添加的,有时会包含错误。不幸的是,年龄是我们模型中的一个重要特征,所以我们不想忽略它。我们决定(仅在本例中…)用平均年龄值代替不太可能的值。幸运的是,我们可以通过编写一个转换器并将其设置在管道中适当的位置来做到这一点。

这里我们将编写并添加一个定制的 transformer:age imputr。我们的新流水线现在将在估算器和缩放器之前增加一个新步骤:

pipe = Pipeline([('age_imputer', AgeImputer()),('imputer', SimpleImputer()),('scaler', StandardScaler()), ('RF', RandomForestClassifier())])

怎么写变形金刚?

让我们从研究转换器的结构及其方法开始。

transformer 是一个 python 类。对于任何与 Scikit-Learn 兼容的转换器,它都应该包含某些方法:fit()、transform()、fit_transform()、get_params()和 set_params()。方法 fit() 拟合管道; transform() 应用变换;并且组合的 fit_transform() 方法拟合,然后将变换应用于相同的数据集。

Python 类可以方便地从其他类继承功能。更具体地说,我们的转换器可以从其他类继承其中的一些方法,这意味着我们不必自己编写它们。

get_params()和 set_params()方法继承自 BaseEstimator 类。fit_transform()方法继承自 TransformerMixin 类。这使我们的生活变得更容易,因为这意味着我们只需在代码中实现 fit()和 transform()方法,而其余的神奇功能将自行实现。

下面的代码演示了上述新估算转换器的 fit()和 transform()方法的实现。记住,我们希望我们的转换器“记住”年龄平均值,然后用这个值替换不可能的值。init()方法(也称为构造函数)将启动 transformer 的一个实例,使用最大允许年龄作为输入。fit()方法将计算并保存平均年龄值(四舍五入以匹配数据中年龄的整数格式),而 transform()方法将使用保存的平均年龄值对数据进行转换。

# import packages
from sklearn.base import BaseEstimator, TransformerMixin# define the transformer
class AgeImputer(BaseEstimator, TransformerMixin):

    def __init__(self, max_age):
        print('Initialising transformer...')
        self.max_age = max_age

    def fit(self, X, y = None):
        self.mean_age = round(X['Age'].mean())
        return self

    def transform(self, X):
        print ('replacing impossible age values')
        X.loc[(X[‘age’] > self.max_age) 
              |  (X[‘age’] < 0), “age”] = self.mean_age
        return X

如果我们希望看到转换的结果,我们可以应用管道中的这一特定步骤,并查看转换后的数据:

age_scaled = pipe[0].fit_transform(X_train)
age_imputed

不出所料,不可能的值被基于训练集的平均年龄所取代。

一旦我们编写了转换器并将其添加到管道中,我们就可以正常地将整个管道应用到我们的数据中。

pipe.fit(X_train, y_train)
pipe.predict(X_test)

用更复杂的变压器来增加趣味

上面的例子描述了现实的一个简化版本,其中我们只想对现有的管道做一个小的改变。在现实生活中,我们可能想要给我们的管道添加几个阶段,或者有时甚至用定制的预处理转换器替换管道的整个预处理流程。在这种情况下,除了 fit()和 transform()方法之外,我们的新 transformer 类可能还具有用于各种处理阶段的附加方法,这些方法将应用于我们的数据。这些方法将在 fit()和 transform()方法中用于各种计算和数据处理。

但是我们如何决定哪些功能属于 fit()方法,哪些属于 transform()方法呢?

一般来说,fit 方法计算并保存我们进一步计算可能需要的任何信息,而 transform 方法使用这些计算的结果来更改数据。我喜欢一个接一个地检查转换阶段,想象我正在将它们应用到一个新的样本中。我将每个处理阶段添加到转换方法中,然后问自己以下问题:

  1. 这个阶段需要原始数据中的任何信息吗?此类信息的示例包括平均值、标准偏差、列名等。如果答案是肯定的,那么必要的底层计算属于 fit()方法,而处理阶段本身属于 transform()方法。简单的 ImputeAge()转换器就是这种情况,我们在 fit()方法中计算平均值,并使用它来更改 transform()方法中的数据。
  2. 这个处理阶段本身是否是提取后续处理阶段需要的信息所必需的?例如,理论上我可能有一个额外的下游阶段,它需要每个变量的标准差。假设我想计算估算数据的标准偏差,我必须计算并保存转换后的数据帧的标准值。在这种情况下,我将在 transform()方法和 fit()方法中包含处理阶段,但是与 transform()方法不同,fit()方法不会返回转换后的数据。换句话说,如果出于内部目的需要,fit()方法可以对数据应用转换,只要它不返回改变的数据集。

最后,fit()方法将依次执行所有必要的计算并保存结果,transform()方法将依次对数据应用所有处理阶段并返回转换后的数据。

就是这样!

总结一下…

我们从使用现成的变压器应用管道开始。然后,我们讲述了 transformers 的结构,并学习了如何编写一个定制的 transformer 并将其添加到我们的管道中。最后,我们回顾了决定转换器的“fit”和“transform”方法背后的逻辑的基本规则。

如果您还没有开始使用管道,我希望我已经让您相信管道是您的朋友,它们有助于保持您的 ML 项目有组织,不容易出错,可复制,并且易于应用到新数据。

如果你觉得这篇文章有帮助,或者如果你有任何反馈,我很乐意在评论中阅读它!

网络罪犯 vs 机器人

原文:https://towardsdatascience.com/cyber-criminals-vs-robots-f60b8a86114a

构建网络入侵检测和防御的集成模型

除非另有说明,所有图片均为作者所有

链接

介绍

网络罪犯面对机器人会怎样?当他们使用机器人时会发生什么?随着人工智能的不断发展,网络安全的攻防策略将如何演变?人工智能和网络安全都年复一年地登上增长最快行业的排行榜。这两个领域在许多领域有重叠,而且毫无疑问在未来几年还会继续重叠。对于本文,我将范围缩小到一个特定的用例,入侵检测。入侵检测系统(IDS)是一种监控公司网络恶意活动的软件。我深入研究了人工智能在入侵检测系统中的作用,使用机器学习编写了我自己的 IDS 代码,并进一步演示了如何使用它来帮助威胁猎人。

问题定义

最近,不到一半的公司在其入侵检测系统中利用机器学习。最常见的 IDS 仅仅依赖于一种叫做“签名匹配”的技术。基于签名的入侵检测发现“可能匹配特定已知攻击者 IP 地址、文件哈希或恶意域的序列和模式”;然而,它在检测未知攻击方面有很大的局限性。许多网络罪犯了解签名匹配是如何工作的,并将改变他们的行为以避免被发现。虽然签名匹配已被证明是有用的,但在不断变化的网络威胁环境中,它本身并不是一个解决方案。相反,签名匹配必须辅之以适应性更强的解决方案:机器学习。使用来自网络流量的特征,机器学习不仅检测以前发生过的攻击,而且它足够动态以检测全新的攻击。

对于我的入侵检测算法,我想关注的具体问题是优化精度和召回率。在入侵检测环境中,错误会给组织带来巨大的成本。误报可能会导致未被发现的系统漏洞,从而带来潜在的灾难性后果。过多的恶意样本误报警报会降低对系统的信心,并将关键安全人员引向死胡同,导致他们降低优先级,无法缓解实际攻击。量化和优化这种权衡是另一个独立的研究项目,可能因组织而异。在本文中,我假设假阴性比假阳性更糟糕,但尽量减少两者。在评估结果时,我试图根据组织的加权成本来量化这种关系,假设假阴性的成本是假阳性的两倍。

数据

对于这个项目,我使用了⁴的 NSL-KDD 数据集,这是 1999 年 KDD Cup 数据集的改进版本。该数据由模拟美国空军局域网环境的局域网(LAN)中 9 周的原始 tcpdump 流量组成。该数据集中的特征是根据原始 tcpdump 设计的。大多数特性描述可以在这里这里找到。每行代表给定时间点的网络流量样本,每个网络样本可以标记为“恶意”或“良性”,并分为攻击类别 DoS(拒绝服务)、R2L(远程到本地)、U2R(用户到根)和探测。

NSL-KDD 数据预览

相关著作

这个项目的灵感来自大卫·freeman⁴.的《机器学习安全》一书在网络流量分析一章中,Chio 和 Freeman 探索了一种用于入侵检测的集成方法。最终的结果是一个在召回方面表现很好的系统,但是在 precision⁵.方面却没有达到标准我的系统考虑并实现了他们的许多策略,对它们进行优化以提高精确度和召回率。他们的集合模型的结果,如下图所示,将作为基线。

准确度 : 85%

分类报告:Chio/Freeman 模型

分类报告:Chio/Freeman 模型

方法学

特征选择

  1. 对连续、二元和标称列进行分类
  2. 删除只有 1 个值的所有要素。num_outbound_cmds 只有一个唯一值,所以我将其从数据集中删除。
  3. 根据属性类型验证数据(su_attempted 应该是一个二进制属性,但有 3 个值)
  4. 一次性编码分类变量

重采样

培训:攻击类别分布

为了最大限度地提高召回率,并创建一个适用于不常见和看不见的攻击的模型,我需要确保这些不常见的攻击在训练数据中得到更好的体现。如上所述,尽管恶意和良性标签分布相当均匀,但在我们的训练数据中,攻击类别的分布非常不均匀。为了增加一些少数类的表示并减少一些多数类的表示,我采用了一种结合过采样和 undersampling⁵.的重采样方法

训练:重新采样的攻击类别分布

使用不平衡学习中的 SMOTE ⁰和 RandomUnderSampler 类,我现在有了一个平衡的训练集。

预测攻击类别—多类建模

我专注于入侵检测;但是,识别正确的攻击类别将有助于威胁猎人进行入侵防御。我在训练数据上交叉验证了 4 个分类模型,比较了准确性。

多类别三重交叉验证

树模型的表现比线性模型(逻辑回归)好得多,其中 XGBoost 的结果最好。

在整个训练集上拟合 XGBoost 模型并预测测试结果,我得到以下结果:

准确率 : 79%

XGBoost 多分类器:多类结果

XGBoost 在识别探测和拒绝服务攻击方面做得很好,但在 R2L 和 U2R 方面缺乏召回,这在初始训练集中较少出现。重采样有助于改善这种情况;然而,这些类的训练属性不足以代表测试数据。

将攻击类别分为恶意攻击和良性攻击,以下是二进制结果:

T5 精度 : 81%

分类报告:XGBoost 多分类器

混淆矩阵:XGBoost 多分类器

攻击类别的多类分类在识别攻击类别方面做得不错;然而,它识别恶意样本的召回率太低。我决定将重点更多地放在入侵检测而不是攻击分类上,但在选择算法时仍然重视上面的结果。我将把它作为前进的基线,并把它合并到仪表板中。

入侵检测—二元分类

特征减少

我决定通过 SelectPercentile⁶减少特征的数量,执行 ANOVA 测试并返回特征的 f 值,以确定它们与目标向量的独立相关性。经过一些试验,我决定根据 F 值保留前 33%的特性。

模型选择的指标

因为我计划在入侵检测的上下文中优化精确度和召回率,所以在所有阈值下最大化模型的性能是很重要的。因此,受试者工作特征(ROC)曲线下面积(AUC)是在选择模型时最大化的合适指标。ROC 曲线描绘了真阳性率(TRR)对假阳性率(FPR)。这条曲线下的区域将告诉我们随机选择的恶意示例比随机选择的良性示例得分更高的概率。

在使用 5 重交叉验证比较不同模型的 AUC 后,森林模型优于其他模型,尤其是 XGBoost。

下面我建立了一个管道来标准化这些特性,用 SelectPercentile 减少它们,并使数据适合 XGBoost 分类器。

阈值选择

我将数据分为训练和验证,将模型拟合到训练数据,并绘制验证结果的 ROC 曲线。

受试者工作特征曲线

ROC 曲线(放大)

请记住,我们的目标是最大限度地提高精确度和召回率,重点是召回率。这将最大限度地减少通过检测系统的攻击数量,而不会因为标记太多的误报而牺牲算法的完整性。如上所述,我可以调整阈值来提高真阳性率,而不会显著增加假阳性率。设置真阳性率为 1 的阈值意味着没有假阴性;因此,该模型会标记每一个恶意样本。为此,我选择了最佳阈值 0.0050058886,创建了 100%的验证召回率和 98%的精确度。当预测恶意样本时,这意味着根据模型的预测概率,任何具有至少 0.5%的恶意概率的样本都会被标记为恶意样本。

让我们来看看这个模型的测试结果。

准确率 : 90%

分类报告:带阈值的 XGBoost

混淆矩阵:带阈值的 XGBoost

到目前为止,该模型的准确率比 Chio/Freeman 模型提高了 5%,准确率提高了 16%,召回率降低了 6%。

我想进一步探索在保持高精度的同时提高召回率的方法。想想网络攻击模式,许多攻击者会创建新的恶意软件,这些恶意软件要么与以前的版本略有不同,非常不同,要么是全新的零日漏洞利用。那么,机器学习算法如何标记这些没有在训练数据中表示的实例呢?我转向异常检测。在试验了许多不同的算法之后,我认为集群是最好的方法。

使聚集

我的目标是用聚类模型来补充分类模型。理想情况下,聚类模型应该能够挑选出一些被分类模型错误标记的恶意样本。在这个模型中,更重要的是平衡精确度和召回率,因为只有当聚类的预测足够精确以覆盖分类器的预测时,聚类才能很好地补充分类。为了在这种情况下最大限度地提高精确度和召回率,我求助于 F1 分数。

首先,我在 2D 向量空间中画出了训练集的类。

实际的

乍一看,这些聚类看起来没有很好地定义,但是我可以使用聚类来检测一些明显的异常值。

接下来,我拟合了一个简单的 2-聚类 K-均值模型,使用主成分分析(PCA)来降低维度。

预测:2 类 K 均值模型

双聚类模型在表示实际类方面做得很差。我的下一个方法是试验更多的集群,并优化一个策略,将它们映射回恶意或良性。

在对集群数量和策略参数进行多次迭代后,我拟合了一个 27 集群模型。

预测:27 簇模型

这里的集群看起来比双集群模型更加集中和清晰。那么,我的策略是什么,我如何将这些集群映射到良性和恶意的类别标签?这两个问题只有一个答案:

受 Chio 和 Freeman⁴的启发,这种聚类映射技术包括异常检测和恶意样本的多数类标记。

策略:

  • 如果群集中恶意流量的百分比大于 95%,则群集中的所有实例都会被标记为恶意。
  • 如果相对于总人口的群集大小小于 0.1%,则群集中的所有实例都被标记为恶意实例。

这与 Chio 和 Freeman 的模型不同,因为它细化了群集数量、恶意流量百分比和相对群集大小的参数,以最大化 F1 分数。此外,我的方法只关注于标记恶意实例,而没有在逐簇 basis⁴.上使用机器学习模型

前 6 个集群的集群映射表

来自该策略的每个恶意标签可以追溯到多数恶意聚类或离群点,这将在稍后的模型可解释性中有用。根据该策略映射聚类后,这里是 2D 向量空间中预测值与实际值的并排比较。

k-均值映射预测与实际标签

映射后,预测和标签看起来几乎相同。

创建集成模型:结合分类和聚类

我将 XGBoost 模型与 K-Means 模型相结合的方法是覆盖任何 XGBoost 将样本标记为良性,而 K-Means 方法将样本标记为恶意的情况。

估价

最终模型的结果如下:

准确率 : 90%

分类报告:集合模型

混淆矩阵:集合模型

在 XGBoost 模型中增加聚类功能可以在保持精确度的同时提高恶意流量的召回率。算法以仅误标 5 次的小代价发现了 109 次新的恶意攻击;从而提高分类器性能,而不降低其完整性

在我将这个模型与我讨论过的其他基线模型进行比较之前,定义一个成本函数是很重要的。网络攻击的成本或影响本身是一个完整的研究领域,有许多变量。为了最终在未来扩展这项研究,我在本文中定义的成本函数是一个临时的启发。

基于未被发现的网络攻击比假警报更糟糕的逻辑,我假设假阴性的成本是假阳性的两倍。我假设一个组织的平均假警报成本是 1000 美元;因此,未被发现的网络攻击的平均成本是 2000 美元的两倍。

价值函数

结果

从左到右,很明显,映射到二进制标签的 XGBoost 多类预测器具有 97%的最高精度。不幸的是,这种模式在所有其他领域都表现不佳。将集合模型与 Chio Freeman 模型的主基线进行比较,精度提高了 16%,表明入侵检测系统标记样本的置信度提高了。Chio Freeman 模型仍然以 97%的召回率优于其他模型,我的 ensemble 模型以 92%的召回率紧随其后。我认为这方面仍有改进的余地。整体模型的 F1 分数优于其他模型,阈值化的 XGBoost 模型以 0.91 紧随其后。集合模型比 Chio Freeman 基线高出 0.05。F1 的高分实现了我优化精确度和召回率的目标。整体模型和阈值 XGBoost 模型在准确性上非常相似,二者的舍入准确性都为 90%。最后,ensemble 模型在加权成本方面优于其他模型,比第二好的模型便宜 220,000 美元,比 Chio Freeman 模型便宜 540,000 美元。

讨论

虽然我的模型通过实现最高的 F1 分数优化了精确度和召回率,但是我没有在入侵检测的上下文中优化这些指标的具体测量。请记住,我的成本函数是一个临时的启发。不同的攻击类别、网络架构、系统和组织有不同的成本。这些都应该是建模新成本函数的考虑因素。例如,探测攻击通常不会给组织造成很大损失,因为它们通常用于监视网络而不是破坏网络。此外,一次成功的拒绝服务攻击可能会让脸书损失 300 万美元,而当地一家咖啡连锁店只需 2000 美元。由于并非所有这些数据都是现成的,我的下一步将是研究每种攻击类别的平均成本,并将其包括在计算中。之后,我会调整误报的成本,考虑平均人工成本、错过实际攻击的机会成本,甚至可能是贬值的模型信心的成本。

可解释性

我的系统现在可以很好地识别攻击。下一步是以自动化的方式解释这些预测,帮助网络防御保护他们的基础设施。可解释的入侵检测系统可以为威胁猎人指出正确的方向。

使用 streamlit python 库,我创建了一个概念验证仪表板,展示了入侵检测系统如何通过标记攻击、按风险对攻击进行优先级排序以及向威胁猎人推荐具体的补救路径来充当入侵防御系统。

IDS 仪表板

口译模特

为了解释集成模型的恶意预测,我必须自动解释 XGBoost 分类器和 K-Means 聚类算法。

XGBoost :对于 XGBoost,一种选择是提取特征重要性,并潜在地用线性模型拟合数据,以帮助确定方向性。在像 XGBoost 这样复杂的森林模型中,这不是底层模型的最佳表示。XGBoost 模型的完美解释是绘制决策树森林;然而,绘制一个完整的复杂的森林图是很难的,并且对于需要快速工作的安全团队来说也是不切实际的。我求助于软件包 LIME(本地可解释的模型不可知解释),它近似了特定 instance⁸.附近的模型行为我选择 LIME 是因为它将复杂的模型分解成易于理解的局部近似和估计。

对于我的 IDS,LIME 可以对标记为恶意的样本 XGBoost 给出本地解释。下表显示了标记特定样品时最重要的 10 个特征、特征权重和方向性。

为了预测类别,我使用前面讨论的多类 XGBoost 模型。

K-Means :为了解释 K-Means 模型,我首先识别预测的聚类。接下来,我绘制了每个聚类中心的热图,显示了所有特征的相对值,并突出显示了该样本的聚类。就绝对值而言,聚类中心向量中某个特征的高值被视为重要特征,并包含在特征值表中。

为了预测类别,我采用每个集群中最常见的攻击类别,除非它是一个离群的集群。

风险

我将风险计算为 XGboost 模型预测样本是恶意的概率,标准化为 1 到 10。我根据国家漏洞数据库的 cvss v3.0 版影响严重性 mappings⁹.将这些风险分值映射到严重性

将风险分值映射到测试人群的严重性会产生以下分布。

测试集的严重性分布

这里有许多严重程度标志,低是第二高的。仪表板为威胁猎人提供了按严重性过滤的选项,允许他们优先考虑更高严重性的标志。

补救

将入侵检测系统转变为入侵防御系统的最后一步是为威胁猎人指明正确的方向,并提供补救途径。通过查看条形图或热图中突出显示的重要特征,威胁猎人可能会对攻击是如何被检测到的以及问题的根源有一个大致的了解。除了这些图表之外,仪表板还提供了前 3 个最重要特征的值以及样本可能属于的攻击类别。对于每个攻击类别,我都提供了一个补救途径,这是威胁猎人或安全分析师在验证和补救每个攻击时应该采取的措施的建议。

从开始到结束,仪表板允许任何威胁猎人快速检测攻击,确定它们的优先级,识别要调查的关键特征,预测所述攻击的类别,并为特定攻击提供补救策略。

在这里,我将通过几个例子来演示如何使用仪表板。

例#1

在本例中,威胁猎人希望专注于关键攻击。他将严重性过滤为严重,并选择网络流量的标记样本。该样本的风险分值最高,为 10 分。这很可能是拒绝服务攻击,这对于威胁猎人来说是有意义的,因为他可以看到有 255 个连接到同一个目的 IP 地址(“dst_host_count”)。根据仪表板的建议,威胁猎人调查网络流量日志中的这些连接,确认它们来自已知的僵尸网络,并验证这是一种拒绝服务攻击。他立即通知事件响应和网络安全部门阻止这些连接并转移流量。

例二

在处理完当天的高优先级问题后,安全分析师的任务是调查一些已经在队列中等待了一段时间的低优先级项目。他注意到这个样本是一个异常值,并希望进一步调查。根据仪表板的建议,他调查了哪些功能可能会导致该流量被标记。他认为失败的登录尝试增加了系统的怀疑。他监视该用户的活动,并注意到更多失败的登录尝试,这些尝试通常是在工作时间之外通过远程连接进行的。分析师与系统管理员合作,通过要求通过跳转服务器进行远程连接并添加双因素身份认证来加强服务器的安全性。失败的登录尝试次数在接下来的一周内逐渐减少。

讨论

在指出仪表板和可解释性模型中需要改进的地方时,我想参考风险分值计算、多类别模型、补救路径协调以及构建生产就绪仪表板。

我使用了一种临时的启发式方法来衡量风险。这应根据行业标准进行调整,并根据攻击类别和主机系统进行不同的加权。此外,应该有更好的方法来量化 K 均值预测的风险。

另一个下一步是改进预测攻击类别的多类 XGBoost 模型。我决定更多地关注入侵检测而不是攻击分类,但我最终还是在仪表板中加入了多类模型来帮助威胁猎人。改进模型的一个方法是从训练集中去掉良性样本,只标记恶意样本,因为我已经有了一个 IDS。改进此模型可以以最少的工作获得最大的收益,并增加对仪表板预测类别和补救途径的信心。

说到这里,补救路径应该由主题专家来定义。虽然我尽我所知编写了补救措施,但我希望听取有经验的威胁分析师的意见,以完善建议并提供新的建议。

如果是为组织构建,最后一步是在生产环境中设置仪表板。这将意味着建立一个 ETL 来从系统的网络流量中提取相关的特性,将其流式传输到仪表板,实时更新仪表板,并结合一个反馈回路来重新调整模型。

结论

通过集成模型,我实现了优化精度和召回率的目标。使用结合了 XGBoost 和 K-Means 的高级集成,我能够在许多领域超越他人,包括我定义的加权成本指标。由于一些模型仍然比我的模型有优势,而且加权成本指标是一种主观的启发,我不能客观地声称我的模型整体上优于其他模型。尽管如此,这个模型确实实现了我的既定目标。

我相信这个项目的主要收获是,人工智能是一种有价值的工具,可以在入侵检测方面帮助网络安全团队。机器学习是针对 IDS 规避技术和这一领域中不断发展的威胁因素的强大解决方案。此外,这些模型可以与安全团队相互帮助。它们有能力检测大量人类无法自己模式化的威胁,为威胁猎人指明正确的方向,并融入用户反馈以适应新的场景。

那么当网络罪犯面对机器人时会发生什么?这个问题应该重新定义。人工智能在检测大量威胁的能力方面非常出色,但它也有许多弱点,如模型退化、容易中毒和被黑客攻击,以及无法自动修复复杂或看不见的攻击。人类缺乏实时处理如此大量数据的能力,但拥有追踪和阻止一些最复杂的网络犯罪所需的创造力和商业头脑。机器学习模型可以填补网络响应团队的许多空白,反之亦然。入侵检测系统和威胁猎人相互补充,以创建一个更强大的系统。因此,问题应该是,“当网络罪犯面对电子人时会发生什么?”当人类和人工智能一起抵御网络犯罪时,威胁行为者可能不会喜欢这个答案。

链接到代码

链接到仪表板

我的网站

参考文献

  1. 费尔德曼,莎拉和菲利克斯·里克特。“检测安全入侵是 2018 年最热门的人工智能应用。” Statista 信息图,Statista,2019 年 4 月 5 日,https://www . Statista . com/chart/17630/artificial-intelligence-use-in-business/。
  2. 雷塞克迈克尔。"基于特征和基于行为的入侵检测系统有什么区别?" Accedian ,Accedian,2022 年 6 月 29 日,https://Accedian . com/blog/what-is-the-difference-based-and-behavior-based-ids/。
  3. Mahbod Tavallaee 等人,“KDD 杯 99 数据集的详细分析”,第二届 IEEE 安全和国防应用中的计算智能研讨会会议录(2009):53–58。
  4. 齐奥、克拉伦斯和大卫·弗里曼。机器学习和安全:用数据和算法保护系统。奥莱利媒体公司,2018 年。
  5. 你好,克拉伦斯。"图书资源/NSL-KDD-分类. ipynb at Master Oreilly-ml sec/图书资源." GitHub ,Oreilly,https://GitHub . com/Oreilly-ml sec/book-resources/blob/master/chapter 5/nsl-KDD-classification . ipynb .
  6. " sk Learn . feature _ selection . select percentile . "sci kit,Scikit-Learn,https://sci kit-Learn . org/stable/modules/generated/sk Learn . feature _ selection . select percentile . html .
  7. https://pix abay . com/插图/科技-科幻-未来派-7111801/
  8. "局部可解释的模型不可知解释(LIME). "本地可解释的与模型无关的解释(Lime) — Lime 0.1 文档,GitHub,https://lime-ml.readthedocs.io/en/latest/.
  9. “漏洞指标。”https://nvd.nist.gov/vuln-metrics/cvss.NVDNVD
  10. “击杀。” SMOTE —版本 0.9.1 ,不平衡学习,https://不平衡学习. org/stable/references/generated/imb Learn . over _ sampling . SMOTE . html
  11. "随机抽样器" RandomUnderSampler —版本 0.9.1 ,不平衡学习,https://unbalanced-Learn . org/stable/references/generated/imb Learn . under _ sampling . RandomUnderSampler . html .
  12. 增长最快的工作部门:人工智能、数据科学和网络安全。招聘能力,招聘能力,2020 年 3 月 23 日,https://www . therecruitability . com/ai-data-science-and-cyber security-Americas-fast-growth-jobs-sections/。
  13. “简化文档”细流文档,细流,https://docs.streamlit.io/.
  14. " NSL-KDD 数据集."新不伦瑞克大学 Est.1785https://www.unb.ca/cic/datasets/nsl.html. UNB

犬儒主义:数据科学家的必备

原文:https://towardsdatascience.com/cynicism-a-must-have-for-data-scientists-729638b2f517

之所以问“为什么?”不会总是产生影响

我在工作中经历过的最令人泄气的事情之一是西西弗螺旋:做复杂、累人的工作,却发现你的待办事项清单不断增加。虽然这种情况可能是所有成年人生活品尝菜单的一部分,但数据科学家似乎吃得太多了。

在希腊神话中,宙斯惩罚西西弗斯两次欺骗死神,强迫他把一块巨大的圆石滚上山,但每次快到山顶时,圆石都会滚下来,如此反复,直到永远。提香在普拉多博物馆的画作,来源:维基百科

我想邀请你绕一点弯子,在 Twitter 上回答一个投票。结果让你吃惊吗?

推特投票链接。就问题的目的而言,您可以假设:副总裁不是数据科学背景,而经理有技术背景,但不做数据工作。副总裁和经理都擅长他们的工作。

掌控你的命运

在之前的文章中,我们根据数据科学家关心的主要问题讨论了他们职业发展的四个成熟阶段:

阶段 1 — “怎么做?” —关注执行细节
阶段 2— “为什么?” —提问提高自己工作的影响力
阶段 3— “谁?” —成为政治;影响归结到人
阶段 4 — “什么?”

在每一个阶段,你都能更好地控制自己的能力,去做有影响力的项目,跳过那些让你一事无成的令人心碎的东西。每一个阶段都是为了达到逃离西西弗螺旋的速度。许多数据科学家认为,当他们学会问 为什么 时,他们就已经成功了,所以他们永远也过不了第二阶段。

当你从 “为什么?”开始跳跃时,一个最大的增长爆发就发生了 阶段到了 “谁?” 阶段;这也是为热爱真理和数据的人努力塑造更痛苦的性格之一,因为它带来了对 为什么 的答案往往取决于 你问谁

这让我想到了犬儒主义的话题…

为什么数据科学家需要更多的冷嘲热讽?

好吧,也许文章标题有点夸张。(是点击量最高的那些人,还是我自己表现出的愤世嫉俗?)我鼓励数据科学家至少要有更多的 怀疑态度 再加上更敏锐的阅读动机。领导级别越高,他们的责任范围就越广,除了个人利益,他们可能有更多的理由!—告诉您需要听到的任何信息,以便采取有利于整个组织的行动。

换句话说,获得关于 为什么 的明确信号并不像看起来那么简单。如果你不仔细分析 ,你就会误读为什么,然后又滑回推着无用的巨石上坡。

***【why ***上获得明确的信号并不像看起来那么简单。

甚至你的顶头上司也可能有充足的理由让你去做徒劳无功的事情。比如我给大家分享一个很常见的菜谱。

就拿一个在技术工作上才华横溢、但缺乏成熟的人来说吧,他在社交礼仪方面缺乏理性。增加几个可靠的、有能力的、重视工作和生活平衡的团队成员。撒上一点竞争力。彻底搅拌,让它慢慢沸腾。结果呢?非常有可能的是,超额完成者会挫伤团队的积极性,并创造一个整体生产力下降 ,尽管个人带来了超过一个人的价值的胜利。这是导致灾难的原因。**

如果你认为成功所需要的只是才华,请三思!

为了团队的利益,也许是为了防止新的竞争对手获得一个高效的独狼员工,许多经理会倾向于将超额完成任务者转向追求较低的影响。如果你想让他们留在队里,那就是。我的一位导师认为,扼杀生产力的优秀人才不值得努力。他更喜欢移除它们,而不是重新定向/修复它们。哎哟。他并不是唯一一个对高成就者过敏的人——如果你是那种从未学会如何与他人合作的人,至少你可以认为自己受到了警告。(我曾经是一个狂躁的书呆子,直到我长大成人,部分原因是因为这位导师(幸运的是,他不是我的经理)。我记得听到我的倾向被描述为不稳定时的感觉。它刺痛了。)

如果你的老板选择宣布优秀者并把他们留在身边,你可以放心,任何关于 为什么 不会得到真善美所要求的答案的问题。

一个领导人手头的锅越多,隐瞒透明度的动机就越多。

另一个典型情况出现在组织数据科学成熟的早期阶段。许多组织在为数据科学家做好准备之前就雇佣了他们,这意味着他们的工作不太可能立即产生影响,但他们仍然需要在收集数据集、雇佣数据工程师、解决 IT 问题以及最终完成法律审批的过程中保持愉快的忙碌。有时候,声称自己拥有尖端的填入你最喜欢的流行语的能力比那些专家为组织实际生产的任何东西都更有价值,但是祝你好运,让领导直截了当地告诉你,你的职能主要是装饰性的。

既然我们已经看到有充分的理由向你的报告链发出的信号中注入一些噪音,那么是时候进行下一次认识了: 差异来源 。一个领导者越资深(就他们必须激励多少人和多少项目而言,而不是就任期而言),就越有可能信以为真。一个领导者的火气越大,隐瞒透明度的动机就越强烈,尤其是当他们所负责的人之间存在冲突和/或竞争时。

照片由 KS KYUNGUnsplash 上拍摄

让我们再次提起那个调查,稍微改变一下问题的措辞。与其说你应该 相信谁 ,不如让我们来问问哪个信号更干净:经理的反馈还是副总裁的反馈。

增加一个额外的方差源会使信号质量变差。

在其他条件相同的情况下经理是更干净的反馈来源(假设两人都擅长自己的工作,而且都不是讨厌的鼠辈或病态的骗子)。原因如下。

你的直接经理关心你、他们自己和你的团队。您的副总裁关心以上所有内容…以及他们的整个组织。如果你是一个数据爱好者,你会意识到增加一个额外的方差源会使信号质量更差,而不是更好。(更多关于这个话题的在这里。)

你越早停止相信他们的话,越好。

所以,当高级利益相关者告诉你一些事情的时候,检查它,检查他们,仔细检查情况。你越早停止相信他们的话,越好。在你问 为什么之前,问 …然后从你新的角度再试试为什么。

** https://kozyrkov.medium.com/is-your-boss-telling-you-the-truth-f03ae554ce4d

例外

例外?也有一些例外。我们在讨论中假设没有关于所涉及的个性的额外信息。但是如果你有一些额外的上下文呢?在本附录的中,我整理了一些修饰语,它们会让你倾向于接受副总裁的反馈,而不是经理的反馈。

感谢阅读!人工智能课程怎么样?

如果你在这里玩得开心,并且你正在寻找一个为初学者和专家设计的有趣的应用人工智能课程,这里有一个我为你制作的娱乐课程:

在这里欣赏被分成 120 个单独的一口大小的课程视频的课程播放列表:【bit.ly/machinefriend】T2

https://kozyrkov.medium.com/membership

又及:你有没有试过在 Medium 上不止一次点击拍手按钮,看看会发生什么? ❤️

喜欢作者?与凯西·科兹尔科夫联系

让我们做朋友吧!你可以在 TwitterYouTubeSubstackLinkedIn 上找到我。有兴趣让我在你的活动上发言吗?使用表格取得联系。**

面向绝对初学者的 Cython:通过简单的两步将代码速度提高 30 倍

原文:https://towardsdatascience.com/cython-for-absolute-beginners-30x-faster-code-in-two-simple-steps-bbb6c10d06ad

为速度惊人的应用程序轻松编译 Python 代码

让我们加速我们的代码(图片由 Abed IsmailUnsplash 上提供)

Python 非常容易使用;清晰的语法、解释器和鸭式输入让你可以快速开发。但也有一些缺点:如果你不必遵守严格的语法,那么 Python 必须做一些额外的工作来让你的代码运行,导致一些函数执行非常慢,因为它必须一次又一次地做所有这些检查。

将 Python 开发的便捷性和速度与 C 语言的速度结合起来:两全其美

在本文中,我们将从一个普通的 Python 项目中提取一个“慢”函数,并使其速度提高 30 倍。我们通过使用一个名为 Cython 的包来做到这一点,该包会将我们的 Python 代码转换为一段经过编译的超高速 C 代码,我们可以再次直接将其导入到我们的项目中。

一个名为 CythonBuilder 的包会在仅仅两步中为我们自动生成 Python 代码。使用 CythonBuilder,我们将 Cythonize 下面定义的示例项目中的一个函数。我们来编码吧!

但是首先..

对于那些不熟悉 Cython 和 CythonBuilder 的人,我们将回答一些探索性的问题。然后我们将定义我们的示例项目。我们将使用命令行,如果您不熟悉,请仔细阅读:

什么是 Cython /为什么使用 Cython?

Cython 将 Python 代码转换成包含 CPU 指令的文件。Python 解释器不再需要对这个文件进行任何检查;它可以运行它。这导致了性能的显著提高。查看下面的文章,了解关于 Python 如何工作以及它与 C 语言相比如何的更多详细信息:

当你把一段代码变成 Cythonize 时,你就给你的代码添加了额外的信息;定义类型,例如,然后编译代码,这样 Python 就不必执行额外的检查。同样,查看上面的文章可以获得更深入的分析。

Cython 是如何工作的

就像你在一个.py文件中写 Python 代码一样,你在一个.pyx 文件中写 Cython 代码。然后,Cython 会将这些文件转换为. so 文件或. pyd 文件(取决于您的操作系统)。这些文件可以再次直接导入到 python 项目中:

Cythonizing 一个 pyx 文件(作者图片)

所有代码都可以通过编译来优化吗?

并不是所有的代码都是编译好的。例如,在 C 包中等待 API 的响应并不会更快。简而言之:我们专注于需要大量计算的 CPU 密集型任务。在下面的文章中阅读更多,以获得更清晰的区别。

CythonBuilder —自动化 Cythonizing

你是如何将你的.pyx文件有效化的?这个过程相当复杂;您必须创建一个setup.py,定义所有的包,然后运行一些命令(参见下面的文章)。相反,我们将使用CythonBuilder:一个为我们自动化一切的包:在一个命令中构建你的.pyx文件!

你的代码经过了两步(图片由 Sammy Wong 上的 Unsplash

示例项目

我们的项目包含一个函数,出于某种原因,计算一些素数。这个函数需要大量的计算,我们可以优化。
首先安装带有pip install cythonbuilder的 cythonbuilder,然后定义常规的质数计算函数

准备-普通 Python 质数计算函数

这个函数非常简单:我们将传递一个数给这个函数,它返回 0 和目标数之间的质数:

这个函数是纯 Python 的。它可以再优化一点,但目标是有一个执行大量计算的函数。让我们来看看这个函数需要多长时间来找到 0 到 100.000 之间的素数:

PurePython: 29.4883812 seconds

第一步。—糖化

在这一部分,我们将介绍 Cython。我们将复制我们函数的代码,并将其保存到一个名为cy_count_primes.pyx的文件中(注意.pyx)。

接下来我们cd projectfolder并调用cythonbuilder build。这将在 projectfolder 中找到所有的 pyx 文件并编译它们。结果是 Windows 上的一个.pyd文件或 Linux 上的一个.so文件。这个文件是我们的 Python 函数的编译版本,我们可以将它直接导入到我们的项目中:

from someplace.cy_count_primes import count_primes
print(count_primes(100_000))

让我们看看它的表现如何:

PurePython: 29.4883812 seconds
CyPython  : 14.0540504 seconds (2.0982 faster than PurePython

已经快了两倍多!请注意,我们实际上没有对 Python 代码做任何更改。让我们优化代码。

接口:
您会注意到,即使是您的 IDE 也可以检查导入的文件。即使文件被编译,它也知道哪些函数存在,哪些参数是必需的。这是可能的,因为 CythonBuilder 也构建。pyi 文件;这些接口文件为 ide 提供了关于 pyd 文件的信息。

步骤 2 —添加类型

在这一部分中,我们将类型添加到cy_count_primes.pyx文件中,然后再次构建它:

正如你所看到的,我们用cpdef(c 和 p(ython)都可以访问)定义了我们的函数,告诉它返回一个int(在count_primes之前),并且它期望一个limit参数是一个int

接下来,在第 2、3 和 4 行,我们为循环中使用的一些变量定义了类型;没什么特别的。

现在我们可以再次cythonbuilder build并再次计时我们的函数:

PurePython: 29.4883812 seconds
CyPython  : 14.0540504 seconds (2.0982 faster than PurePython
Cy+Types  :  1.1600970 seconds (25.419 faster than PurePython

这是一个非常令人印象深刻的加速!
速度如此之快的原因不在本文的讨论范围之内,但这与 Python 在内存中存储变量的方式有关。与 C 相比,它的效率相当低,所以我们的 C 编译代码可以运行得更快。查看本文 深入探究 Python 和 C 之间的不同之处(以及为什么 C 要快得多)。

附加—编译选项

我们已经将代码执行速度提高了 25 倍,但我认为我们还可以再提高一点。我们将用编译器指令来做这件事。这些需要一点解释:

因为 Python 是一种解释语言,所以它必须在运行时执行大量检查,例如,如果你的程序被零除。在编译语言 C 中,这些检查发生在编译时;编译时会发现这些错误。好处是你的程序可以更有效地运行,因为它不需要在运行时执行这些检查。

使用编译器指令,我们可以禁用所有这些检查,但前提是我们知道我们不需要它们。在下面的例子中,我们用 4 个装饰器升级了我们之前的代码:

  • 阻止对 ZeroDivisionError 的检查
  • 阻止对 IndexErrors 的检查(当列表只包含 3 个项目时调用 myList[5])
  • 阻止对 isNone 的检查
  • 防止绕回;防止调用相对于末尾的列表(如 mylist[-5])所需的额外检查

让我们再次重新构建我们的代码(cythonbuilder build),看看跳过所有这些检查能节省多少时间

PurePython: 29.4883812 seconds
CyPython  : 14.0540504 seconds (2.0982 faster than PurePython
Cy+Types  :  1.1600970 seconds (25.419 faster than PurePython
Cy+Options:  0.9562186 seconds (30.838 faster than PurePython

我们又缩短了 0.2 秒!

最终结果(越低越好)(图片由作者提供)

甚至更快?

通过利用我们的多核处理器,我们有可能进一步加速我们的代码。查看下面的文章,了解如何在 Python 程序中应用多处理和线程。另请查看这篇文章向您展示了如何对 Cython 代码进行多进程处理,并解释了 Cython 的注释文件:图形化概述了您的代码的哪些部分可以进一步优化。非常方便!

结论

CythonBuilder 使得使用 Cython 加速我们的 Python 代码变得很容易。
正如我们所看到的,复制我们的 Python 代码并构建会使执行速度翻倍!最大的速度提升是通过增加类型;相对于普通 Python,速度提高了 25 倍。

我希望一切都像我希望的那样清楚,但如果不是这样,请让我知道我能做些什么来进一步澄清。同时,请查看我的关于各种编程相关主题的其他文章:

编码快乐!

—迈克

又及:喜欢我正在做的事吗? 跟我来!

https://mikehuls.medium.com/membership

posted @ 2024-10-18 09:33  绝不原创的飞龙  阅读(245)  评论(0)    收藏  举报