作为数据工程师为企业提供有价值的数据
如何构建有用的数据资产
很容易忽略作为一名数据工程师的复杂性。毕竟,我们只是将数据从一个地方转移到另一个地方,对吧:这能有多难?
然而,在我担任数据工程师期间,我发现某些技能帮助我为我工作过的公司提供了宝贵的数据资产。
通常,我们的任务是接受一个业务需求或问题,并找到一种方法来获取、转换和连接数据,要么自己找到答案,要么让分析师施展他们的魔法。
和大多数事情一样,对于某些问题来说,有一条快捷简单的途径。然而,大多数时候首选的途径是未来的证明。
我们希望获得数据,这样既可以进行历史分析,也可以在未来继续进行分析。
我们还希望确保它是准备好的、干净的、易于使用的,并包含其他相关数据,以帮助进一步的问题。
这篇文章不打算关注数据工程的技术细节,而是更多地谈论我如何获取数据来帮助回答问题并为接下来的问题做好准备。
领域知识
如果你是一家企业的新手,或者被问到的问题超出了你以前探索过的领域,你就需要填补这个知识空白。
我发现,几乎可以肯定的是,企业中有人在该领域有很好的背景。即使他们不知道如何找到你想要的答案,他们也会给你提供至少一个关键资产:事物的名称和关键词。
Find the SME’s : Image Source
能够谈论组成一个领域的不同的事物将有助于你的研究、进一步的问题以及最重要的命名。
只有通过正确标记对象和与之相关的属性,才能实现对新数据的未来保护。
正确在这里也是主观的,因为它应该对你的业务是正确的。如果你的公司称人们为“用户”,那么你也应该这样做;如果他们称他们为“顾客”,那么你也应该这样做。
这使得整个企业更容易找到、分析和理解信息,从而使您创造的数据资产更有价值。
这也让您了解了与领域相关的其他度量和维度。很好地引导到下一部分…
面向未来
获取领域知识是关键的第一步,并很好地引导解决方案的未来验证。
根据经验,我知道提供数据来帮助回答一个问题只会引发更多的问题。
这很好,这是新信息应该做的,激发新的思维方式,让人们兴奋和渴望知道更多。
Data hungry! — Image source
当这些新问题出现时,你要做好准备。最糟糕的事情莫过于引发一场讨论,让想法流动起来,然后不得不等待新的数据被挖掘出来。
这就是一些经验和对问题的预期发挥作用的地方,但是早先获得的领域知识将会有所帮助。
在这一点上,我通常会开始思考为什么人们会问他们正在问的问题,并思考一旦他们有了答案,他们想要探索的其他途径。你可以通过问一个简单的问题做到这一点。
为什么答案比我预期的高/低/好/差?
问 为什么, 迫使你去思考你想要打破领域的维度种类。
如果你被要求查找每件产品的负面评论数量,你可能会想要一些维度,如时间、产品类型、客户档案——你甚至可以通过文本分析来查找负面评论中的常用短语。
这让您了解了进一步增强获得基本答案所需的额外数据和元数据的种类。
大多数情况下,您可以免费获得这些维度,因为通常特定事物的属性通常是与一起收集的,因此也与它一起存储。然而,要得到本质的答案,您通常需要扩展到其他数据源来补充您所拥有的。
Have all the answers : Image Source
重要的是你至少想过接下来会发生什么。
因此,当人们提出进一步的问题时,你将拥有所有的数据,最好的数据(或者至少知道它在哪里)来帮助回答他们。
近似策略优化教程(第 1/2 部分:行动者-批评家方法)
让我们从头开始编码一个强化学习足球代理!
欢迎来到数学和代码系列的第一部分。我将展示如何实现一种被称为近似策略优化(PPO)的强化学习算法,用于教一个人工智能代理如何踢足球。在本教程结束时,你将会了解到如何在行动者-批评家框架中应用策略学习方法,以便学习在任何游戏环境中导航。我们将看到这些术语在 PPO 算法的上下文中的含义,并在 Keras 的帮助下用 Python 实现它们。所以,我们先从我们游戏环境的安装开始。
**注意:**这整个系列的代码可以在下面链接的 GitHub 资源库中找到。
[## 中国机器人足球
在 Ubuntu 18.04 和单个 NVIDIA GPU 上测试。使用以下工具启动并运行谷歌足球研究环境…
github.com](https://github.com/ChintanTrivedi/rl-bot-football)
设置谷歌足球环境
Google Football Environment released for RL research
我在本教程中使用了谷歌足球环境,但是你可以使用任何游戏环境,只要确保它支持 OpenAI 在 python 中的 Gym API。请注意,在撰写本教程时,足球环境目前仅支持 Linux 平台。
首先创建一个名为footballenv
的虚拟环境并激活它。
>> virtualenv footballenv
>> source footballenv/bin/activate
现在安装这个项目所需的系统依赖项和 python 包。确保您选择了适合您系统的正确的 gfootball 版本。
>> sudo apt-get install git cmake build-essential libgl1-mesa-dev
libsdl2-dev libsdl2-image-dev libsdl2-ttf-dev libsdl2-gfx-dev libboost-all-dev libdirectfb-dev libst-dev mesa-utils xvfb x11vnc libsqlite3-dev glee-dev libsdl-sge-dev python3-pip
>> pip3 install gfootball[tf_gpu]==1.0
>> pip3 install keras
运行足球环境
现在我们已经安装了游戏,让我们试着测试它是否能在你的系统上正确运行。
一个典型的强化学习设置通过让一个人工智能代理与我们的环境进行交互来工作。代理观察我们环境的当前state
,并基于一些policy
做出采取特定action
的决定。这个动作然后通过一个step
传递回向前移动的环境。这产生了一个reward
,指示所采取的行动在所玩游戏的上下文中是积极的还是消极的。使用这种奖励作为反馈,代理试图找出如何修改其现有的政策,以便在未来获得更好的奖励。
Typical RL agent
所以现在让我们继续,为一个随机行动的人工智能代理实现与这个足球环境的交互。创建一个名为train.py
的新 python 文件,并使用我们之前创建的虚拟环境执行以下内容。
这为场景academy_empty_goal
创建了一个环境对象env
,在这个场景中,我们的球员在半场休息,必须在右侧的一个空门得分。representation='pixels'
意味着我们的代理将观察到的状态是在屏幕上呈现的帧的 RGB 图像的形式。如果你在屏幕上看到一个玩家在游戏中随机行动,恭喜你,一切都设置正确,我们可以开始实现 PPO 算法了!
如果你更喜欢视频格式,这里有相同的安装步骤。
近似策略优化(PPO)
PPO 算法是由 OpenAI 团队在 2017 年推出的,并迅速成为最受欢迎的 RL 方法之一,取代了 Deep-Q 学习方法。它包括收集一小批与环境互动的经验,并使用这些经验来更新决策政策。一旦用这一批更新了策略,这些经验就被丢弃,并且用新更新的策略收集新的一批。这就是为什么它是一种“政策学习”方法的原因,在这种方法中,收集的经验样本只对更新当前政策有用一次。
PPO 的主要贡献是确保新的政策更新不会对之前的政策造成太大的改变。这导致训练中以一些偏差为代价的更少的变化,但是确保更平滑的训练,并且还确保代理不会走上采取无意义的行动的不可恢复的道路。所以,让我们继续把我们的 AI 代理分解成更多的细节,看看它如何定义和更新它的策略。
演员-评论家方法
我们将对我们的 PPO 代理人采用演员-评论家的方法。它使用两个模型,都是深度神经网络,一个称为演员,另一个称为评论家。
PPO Agent
演员模型
Actor model
执行学习在环境的特定观察状态下采取什么行动的任务。在我们的例子中,它将游戏的 RGB 图像作为输入,并将特定的动作(如射门或传球)作为输出。
The Actor model
先实现这个吧。
这里,我们首先为我们的神经网络定义输入形状state_input
,它是我们的 RGB 图像的形状。n_actions
是我们在这个足球环境中可用的动作总数,将是神经网络的输出节点总数。
我使用预训练的 MobileNet CNN 的前几层来处理我们的输入图像。我也使这些层的参数不可训练,因为我们不想改变它们的权重。只有添加在该特征提取器之上的分类层将被训练来预测正确的动作。让我们将这些层合并为 Keras Model
并使用均方误差损失进行编译(现在,这将在本教程的稍后部分更改为自定义 PPO 损失)。
评论家模式
我们将演员预测的动作发送到足球环境中,观察游戏中发生的事情。如果我们的行动产生了积极的结果,比如进了一个球,那么环境就会以奖励的形式发回积极的回应。如果我们的行动导致了乌龙球,那么我们会得到负面的奖励。这个奖励被Critic model
拿走了。
The Critic model
批评家模型的工作是学会评估行动者采取的行动是否使我们的环境处于更好的状态,并向行动者提供反馈,因此得名。它输出一个实数,表示在先前状态中采取的行动的等级(Q 值)。通过比较从批评家那里获得的评级,参与者可以将其当前政策与新政策进行比较,并决定如何改进自己以采取更好的行动。
让我们实现批评家。
如你所见,评论家神经网络的结构和演员几乎一样。唯一的主要区别是,最后一层 Critic 输出一个实数。因此,使用的激活是tanh
而不是softmax
,因为我们不需要像演员一样的概率分布。
现在,PPO 算法中的一个重要步骤是用两个模型运行整个循环,运行固定数量的步骤,称为 PPO 步骤。所以本质上,我们在一定数量的步骤中与我们的环境互动,并收集状态、动作、奖励等。我们将用它来训练。
把这一切联系在一起
现在我们已经定义了我们的两个模型,我们可以使用它们与足球环境进行固定步数的交互,并收集我们的经验。这些经验将被用来更新我们的模型的政策后,我们有足够大的一批这样的样本。这就是如何实现收集这种样本经验的循环。
正如你在上面的代码中看到的,我们已经定义了几个 python 列表对象,用于存储观察到的状态、动作、奖励等信息。当我们与我们的环境互动时,共有ppo_steps
。这给了我们一批 128 个样本经验,这些经验将在以后用于训练演员和评论家的神经网络。
接下来的两个视频逐行解释了这段代码,并展示了游戏屏幕上的最终结果。
未完待续…
这部分教程到此为止。我们在 Linux 系统上安装了 Google Football 环境,并实现了一个与该环境交互的基本框架。接下来,我们定义了演员和评论家模型,并使用它们与这个游戏进行交互,并从这个游戏中收集示例体验。希望你能坚持到现在,否则,如果你遇到了什么困难,请在下面的评论中告诉我,我会尽力帮助你的。
下一次我们将看到如何使用我们收集的这些经验来训练和改进演员和评论家模型。我们将检查Generalized Advantage Estimation
算法,并使用它来计算用于训练这些网络的custom PPO loss
。所以留下来!
**编辑:**这是本教程系列的第二部分。
感谢您的阅读。如果你喜欢这篇文章,你可以关注我在媒体、 GitHub 上的更多作品,或者订阅我的 YouTube 频道。
近似政策优化教程(第二部分:GAE 和 PPO 损失)
让我们从头开始编写一个 RL 足球代理!
第一部分链接: 近端政策优化教程(第一部分:演员-评论家法)
欢迎来到强化学习数学和代码教程系列的第二部分。在本系列的第一部分中,我们看到了如何设置谷歌足球环境,然后实现了一个演员-评论家模型框架来与这个游戏环境进行交互并从中收集示例体验。
今天,我们将通过使用该批示例体验来训练我们的模型在游戏中得分,从而完成教程的剩余部分。关于我们上次实施的代码,回想一下到目前为止我们已经收集了以下信息:
利用这些信息,我们现在可以继续计算优势。
广义优势估计(GAE)
优势可以被定义为当我们处于特定的状态时,通过采取特定的行动来衡量我们可以变得多好的一种方式。我们希望使用我们在每个时间步骤收集的奖励,并计算通过采取我们所采取的行动我们能够获得多少优势。因此,如果我们采取了一个好的行动,比如朝着一个目标射击,我们想要计算我们通过采取那个行动,不仅在短期内,而且在更长的一段时间内,有多好。这样,即使我们没有在射门后的下一个时间步立即进球,我们仍然会在那个动作后的几个时间步看更长的未来,看看我们是否进球了。
为了计算这一点,我们将使用一种称为广义优势估计或 GAE 的算法。所以让我们用我们收集的那批经验来看看这个算法是如何工作的。
Generalized Advantage Estimation Algorithm
- 这里,使用掩码值
*m*
,因为如果游戏结束,那么我们批次中的下一个状态将来自新重启的游戏,所以我们不想考虑它,因此掩码值取为 0。 - Gamma
*γ*
只不过是一个常数,称为折扣因子,目的是减少未来状态的值,因为我们希望更多地强调当前状态而不是未来状态。考虑到这一点,现在进球比将来进球更有价值,因此我们不考虑将来的目标,这样我们可以给现在的目标更多的价值。 - λ
λ
是平滑参数,用于减少训练中的方差,使其更加稳定。该论文中建议的该平滑参数的值是 0.95。因此,这给了我们在短期和长期采取行动的优势。 - 在最后一步中,我们简单地反转返回列表,因为我们从最后一个时间步骤循环到第一个时间步骤,所以我们获得了原始订单。
这基本上是 GAE 算法,可以在我们的代码中实现,如下所示。
下面的视频是对这个算法的逐行解释。
我们现在有了训练演员和评论家模特所需的一切。因此,我们将了解如何使用这些信息来计算自定义 PPO 损失,并使用该损失来训练参与者模型。
自定义 PPO 损失
这是近似策略优化算法中最重要的部分。那么我们先来了解一下这个损失函数。
回想一下*π*
表示由我们的 Actor 神经网络模型定义的策略。通过训练这个模型,我们希望改进这个策略,以便随着时间的推移,它给我们提供越来越好的行动。现在,一些强化学习方法的一个主要问题是,一旦我们的模型采用了一个坏的策略,它只会在游戏中采取坏的行动,因此我们无法从那里产生任何好的行动,从而导致我们在训练中走上不可恢复的道路。PPO 试图通过在更新步骤中只对模型进行小的更新来解决这个问题,从而稳定训练过程。PPO 损失可计算如下。
Custom PPO loss calculation
- PPO 在更新步骤中使用新更新的策略和旧策略之间的比率。从计算上来说,用对数形式表示更容易。
- 利用这个比率,我们可以决定我们愿意容忍多大程度的政策变化。因此,我们使用限幅参数ε
*ε*
来确保我们一次只对我们的策略进行最大限度的*ε%*
改变。文中建议ε值保持在0.2
。 - 批评家损失只不过是收益的平均平方误差损失。
- 如果我们想用一个折扣系数使演员和评论家的损失达到相同的数量级,我们可以把他们结合起来。添加熵项是可选的,但它鼓励我们的参与者模型探索不同的政策,并且我们想要实验的程度可以由熵β参数来控制。
这个自定义损失函数可以用 Keras 使用下面的代码来定义。
下面是这个自定义损失函数在下面嵌入的视频中的逐行解释和实现。
模型训练和评估
现在终于可以开始模特训练了。为此,我们如下使用 Keras 的fit
函数。
现在,您应该能够在屏幕上看到模型采取不同的行动,并从环境中收集奖励。在训练过程的开始,当随机初始化的模型探索游戏环境时,动作可能看起来相当随机。
好了,现在让我们实现一些模型评估代码。这将在模型训练期间告诉我们,就成功得分而言,模型的最新更新版本有多好。因此,为了评估这一点,我们将计算平均奖励,定义为从零开始多次玩游戏所获得的所有奖励的平均值。如果我们在 5 场比赛中的 4 场比赛中进球,我们的平均回报将是 80%。这可以如下实现。
一旦模型开始学习哪一组行动产生最好的长期回报,测试阶段将如下所示。在我们的例子中,向右击球被观察到产生了最好的回报,因此我们的演员模型将产生正确的方向和射击动作,作为它的首选输出动作。
用于将所有内容联系在一起的其余代码可以在 GitHub 资源库的train.py
脚本中找到。
*[## 中国机器人足球
此代码实现了近似策略优化(PPO)算法的基本版本,目的是…
github.com](https://github.com/ChintanTrivedi/rl-bot-football)*
如果你想通过逐行解释来学习这个实现,你可以看下面的视频。
结论
我希望这篇教程能让你对基本的 PPO 算法有一个很好的了解。现在,您可以通过并行执行多个环境来继续构建,以便收集更多的训练样本,并解决更复杂的游戏场景,如完整的 11 对 11 模式或角球得分。一些有用的参考资料可以对此有所帮助,也是我在本教程中使用的,可以在这里和这里找到。祝你好运!
感谢您的阅读。如果你喜欢这篇文章,你可以关注我在媒体、 GitHub 上的更多作品,或者订阅我的 YouTube 频道。
修剪深度神经网络
TL;修剪的不同方法,DR:通过修剪,基于 VGG-16 的分类器变得快 3 倍,小 4 倍
如今,深度学习模型需要大量的计算、内存和能力,这在我们需要实时推理或在计算资源有限的边缘设备和浏览器上运行模型的情况下成为瓶颈。能效是当前深度学习模型的主要关注点。解决这种效率的方法之一是启用推理效率。
更大的型号= >更多的内存引用= >更多的能量
Prunning 是一种推理方法,可以有效地生成尺寸更小、更节省内存、更省电、推理速度更快的模型,同时精确度损失最小,其他类似的技术还有权重共享和量化。深度学习从神经科学领域获得了灵感。深度学习中的修剪也是一个生物启发的概念,我们将在本文稍后讨论。
随着深度学习的进展,最先进的模型越来越准确,但这种进展是有代价的。我将在这个博客中讨论其中的一些。
第一个挑战——模特越来越大
难以通过空中更新分发大型模型
Dally, NIPS’2016 workshop on Efficient Methods for Deep Neural Networks
第二个挑战:速度
Training time benchmarked with fb.resnet.torch using four M40 GPUs
如此长的训练时间限制了 ML 研究员的生产力。
第三个挑战:能源效率
AlphaGo: 1920 个 CPU 和 280 个 GPU,每局 3000 美元电费
- 在移动设备上:耗尽电池
- 在数据中心:增加总拥有成本
解答—高效的推理算法
- 修剪
- 重量共享
- 量化
- 低秩近似
- 二元/三元网络
- 威诺格拉变换
修剪的生物灵感
人工神经网络中的修剪是从人脑中的 突触修剪 中得到的想法,其中轴突和树突完全衰退和死亡,导致许多哺乳动物在幼儿期和青春期开始之间发生突触消除。修剪从出生时开始,一直持续到 25 岁左右。
Christopher A Walsh. Peter Huttenlocher (1931–2013). Nature, 502(7470):172–172, 2013.
修剪深度神经网络
[Lecun et al. NIPS’89] [Han et al. NIPS’15]
网络通常看起来像左边的:下面一层中的每个神经元都与上面一层有联系,但这意味着我们必须将许多浮点相乘。理想情况下,我们只需将每个神经元连接到少数几个其他神经元上,省下一些乘法运算;这被称为“稀疏”网络。
稀疏模型更容易压缩,我们可以在推断过程中跳过零,以改善延迟。
如果你可以根据神经元的贡献大小对网络中的神经元进行排序,那么你就可以从网络中移除排序较低的神经元,从而形成一个更小、更快的网络。
获得更快/更小的网络对于在移动设备上运行这些深度学习网络很重要。
例如,可以根据神经元权重的 L1/L2 范数来进行排序。剪枝之后,准确率会下降(如果排序巧妙的话希望不会下降太多),通常会对网络进行训练-剪枝-训练-剪枝迭代恢复。如果我们一次修剪太多,网络可能会被严重破坏,无法恢复。所以在实践中,这是一个迭代的过程——通常称为“迭代修剪”:修剪/训练/重复。
参见 Tensorflow 团队编写的代码,了解迭代修剪。
权重修剪
- 将权重矩阵中的单个权重设置为零。这相当于删除连接,如上图所示。
- 这里,为了实现 k%的稀疏性,我们根据权重矩阵 W 中各个权重的大小对它们进行排序,然后将最小的 k%设置为零。
f = h5py.File("model_weights.h5",'r+')
for k in [.25, .50, .60, .70, .80, .90, .95, .97, .99]:
ranks = {}
for l in list(f[‘model_weights’])[:-1]:
data = f[‘model_weights’][l][l][‘kernel:0’]
w = np.array(data)
ranks[l]=(rankdata(np.abs(w),method=’dense’) — 1).astype(int).reshape(w.shape)
lower_bound_rank = np.ceil(np.max(ranks[l])*k).astype(int)
ranks[l][ranks[l]<=lower_bound_rank] = 0
ranks[l][ranks[l]>lower_bound_rank] = 1
w = w*ranks[l]
data[…] = w
单元/神经元修剪
- 将权重矩阵中的所有列设置为零,实际上删除了相应的输出神经元。
- 这里,为了实现 k%的稀疏性,我们根据它们的 L2 范数对权重矩阵的列进行排序,并删除最小的 k%。
f = h5py.File("model_weights.h5",'r+')
for k in [.25, .50, .60, .70, .80, .90, .95, .97, .99]:
ranks = {}
for l in list(f[‘model_weights’])[:-1]:
data = f[‘model_weights’][l][l][‘kernel:0’]
w = np.array(data)
norm = LA.norm(w,axis=0)
norm = np.tile(norm,(w.shape[0],1))
ranks[l] = (rankdata(norm,method=’dense’) — 1).astype(int).reshape(norm.shape)
lower_bound_rank = np.ceil(np.max(ranks[l])*k).astype(int)
ranks[l][ranks[l]<=lower_bound_rank] = 0
ranks[l][ranks[l]>lower_bound_rank] = 1
w = w*ranks[l]
data[…] = w
自然地,当你增加稀疏度和删除更多的网络时,任务性能会逐渐降低。你认为稀疏性对性能的退化曲线会是怎样的?
使用简单的神经网络架构在 MNIST 数据集上修剪图像分类模型的结果,如下所示
The architecture that I used in the code given in reference
Use the code to regenerate the graph
关键要点
许多研究人员认为修剪是一种被忽视的方法,在实践中会得到更多的关注和使用。我们展示了如何使用一个非常简单的神经网络架构在玩具数据集上获得好的结果。我认为深度学习在实践中用来解决的许多问题都类似于这个问题,在有限的数据集上使用迁移学习,所以他们也可以从修剪中受益。
参考
- 本帖代号
- 修剪,还是不修剪:探索模型压缩中修剪的功效 ,Michael H. Zhu,Suyog Gupta,2017
- 学习卷积神经网络中的剪枝滤波器 ,黄千贵等。艾尔,2018
- 修剪深层神经网络使其快速而小巧
- 用 Tensorflow 模型优化工具包优化机器学习模型
清晰地编码或者被憎恨
代码被读取的次数比它被写入的次数多
Source: Imgur
T 这是面向所有数据科学家的公共服务公告。请清晰地编码。如果不是为了你的合作者的理智,那也是为了你自己的理智 x 月/年后。
个人趣闻
作为全国最佳贝叶斯统计项目的一名硕士生,我被勤奋、磨砺的头脑和富有挑战性的问题所包围。但可以说,我项目中最累人的部分是在我在那里的两年时间里给 1000 多名本科生的 R 代码评分。
我在寒冷、黑暗的房间里度过了许多漫长的周五夜晚,试图编译和执行他们的代码。当它们不可避免地失败,又一个新的错误信息出现时,我会低声吟唱来安慰自己:
“我们能行,我们能行,我们能行。”
这是一次创伤性的经历,经常把我的助教们逼到精神崩溃的边缘……甚至更远。
当然,给我们的代码打分的上层博士们要糟糕得多。我们的 12-15 名硕士生有编码作业,有时每个作业会产生 50 多页的输出…每周一次。我还记得我们的 TA 和他的眼泪。我听说他还在走廊里找人帮他给我们的作业打分。
要点
没有什么比看到两位数的 HiveQL/SQL 子查询嵌套和不考虑行字符最大值更糟糕的了。在阅读这些代码时,我几乎淹没在从我布满血丝的眼睛流出的红宝石般的血流中。
毕竟,你能想象在任何语言中阅读一段由数百行组成的段落是多么痛苦吗?每一行都有不同的字符长度,没有清晰的模式,每行的最大字数是冗长的,缺少标点符号,没有证据
关于白色 s p a c e
压痕的清晰图案?
The best metric for code assessment: WTFs per Minute (WTFM), averaged (weighted or unweighted) across multiple reviewers. (Image source: Thom Holwerda, https://www.osnews.com/story/19266/wtfsm/)
好的编码保证了产品的连续性。文档就更好了。虽然文档并不令人兴奋,也不总是有回报,但它使其他人能够继续你的工作,并享受这样做。结果是更好的软件更容易维护。
好的编码是细致入微的:有些东西可能太多或太少,比如空白。这里有几个例子突出了优秀编码的复杂细微差别:
示例:空白太少
我认为这些图片已经足够说明问题了。
A comically illustrative example of poor white space utilization.
I wrote this as an example of poorly formatted HiveQL code. It is too dense and there is no logical separation of ideas/clauses in the code that allows for a quick and seamless reading.
The original query. Image source: https://image.slidesharecdn.com/usinghive-151001173243-lva1-app6892/95/using-apache-hive-with-high-performance-44-638.jpg?cb=1443721144
示例:空白太多
当你不能一次看到所有相关的部分时,ode 就更难理解了。通过在大多数行之间插入一个空行,您可以看到的代码量减少了一半,并且可能没有充分利用宝贵的屏幕空间。
例子:没有普遍的规则
有时,没有一个单一的规则被多种语言所接受。例如,像每行最大字符数这样简单的事情在不同的语言中有不同的标准:
Max line length/character limits by language.\
结论
风格的一致性是编码项目的最高目标。只要一致,项目遵循哪些特定的惯例并不太重要。如果不处理不同代码段的不同缩进模式、命名约定和习惯用法,就很难理解大型系统。《Python》的作者 Guido Van Rossum 说得好:
风格指南是关于一致性的。与本风格指南保持一致非常重要。项目内部的一致性更重要。一个模块或功能内的一致性是最重要的。
然而,要知道什么时候不一致——有时候风格指南的建议并不适用。当有疑问时,使用你最好的判断。看看其他例子,决定什么是最好看的。不要犹豫地问!
总之,遵循风格规则,直到你有足够的经验和合理的理由去打破它们!
以下是数据科学家常用语言的一些风格指南:
Python
R
通用编码样式
- https://www.reddit.com/r/badcode/(针对用户提交的糟糕代码的例子)
参考文献
[1]https://www.codereadability.com/maximum-line-length/
【2】https://www . python . org/dev/peps/pep-0008/# a-foody-consistency-is-the-hobby-the-goblin-of-little-minds
处理小数据集的伪标记——什么、为什么和如何?
使用模型输出提高模型输出的指南!
TSNE Plot of MNIST Pseudo-Labeling
几天前,我看到了 Yoshua Bengio 对 Quora 问题的回复——“为什么无监督学习很重要?” *。*以下是他的回复摘录:
通过监督学习攀登人工智能阶梯可能需要通过显示大量这些概念出现的例子来“教”计算机所有对我们重要的概念。这不是人类学习的方式:是的,由于语言,我们得到了一些例子来说明给我们的新的命名概念,但我们观察到的大部分东西并没有被标记,至少最初是这样。
从神经科学的角度和实践的角度来看,他的回答都很有意义。标注数据在时间和金钱方面都很昂贵**。这个问题的显而易见的解决方案是找出一种方法:**
(a)使 ML 算法在没有标记数据的情况下工作(即无监督学习)
(b)自动标记数据或使用大量未标记数据和少量标记数据(即半监督学习)
正如 Yann LeCun 在这篇文章中提到的,无监督学习是一个很难解决的问题:
“我们知道最终的答案是无监督学习,但我们还没有答案。”
然而,最近人们对半监督学习有了新的兴趣,这在学术和工业研究中都有所反映。这里有一张图表,显示了谷歌学术每年与半监督学习相关的研究论文数量。
Semi-Supervised Learning Research Papers by Year
在这篇博客中,我们将深入研究伪标签——一种简单的半监督学习(SSL)算法。尽管伪标记是一种幼稚的方法,但它给我们提供了一个极好的机会来理解 SSL 面临的挑战,并提供了一个基础来学习一些现代的改进,如 MixMatch、虚拟对抗训练等。
大纲:
- 什么是伪标签?
- 理解伪标记方法
- 实现伪标签
- 伪标签为什么会起作用?
- 伪标签什么时候效果不好?
- 用传统的 ML 算法进行伪标记
- 半监督学习的挑战
1.什么是伪标签?
伪标记方法由 Lee 于 2013 年【1】首次提出,使用一小组已标记数据和大量未标记数据来提高模型的性能。这项技术本身非常简单,只需遵循 4 个基本步骤:
- 基于一批标记数据的训练模型
- 使用训练好的模型来预测一批未标记数据上的标签
- 使用预测的标签来计算未标记数据的损失
- 将标记的损失与未标记的损失和反向传播结合起来
…然后重复。
这种技术可能看起来很奇怪——几乎类似于 youtube 上数百个“免费能源设备”视频。然而,伪标记已经成功地用于几个问题。事实上,在一场 Kaggle 竞赛中,一个团队使用伪标签来提高他们模型的性能,以确保第一名并赢得 25,000 美元。
我们一会儿就来看看为什么会这样,现在,让我们来看看一些细节。
2.理解伪标记方法
伪标记在每批中同时用标记和未标记的数据训练网络。这意味着对于每批标记和未标记的数据,训练循环会:
- 对贴有标签的批次进行一次正向传递以计算损耗→这是贴有标签的损耗
- 对未标记的批次进行一次正向传递,以预测未标记的批次的“伪标签”
- 使用这个“伪标签”来计算未标记的损失。
现在,Lee 建议使用权重,而不是简单地将未标记的损失与标记的损失相加。总损失函数如下所示:
Equation [15] Lee (2013) [1]
或者用更简单的话说:
在等式中,权重(α)用于控制未标记数据对总损失的贡献。此外,重量是时间(历元)的函数,并且在训练期间缓慢增加。当分类器的性能可能很差时,这允许模型最初更多地关注标记的数据。随着模型的性能随着时间(时期)的增加,权重增加,未标记的损失更加强调整体损失。
Lee 建议对 alpha (t)使用以下等式:
Equation [16] Lee (2013) [1]
其中α_ f = 3,T1 = 100,T2 = 600。所有这些都是基于模型和数据集而变化的超参数。
让我们看看 Alpha 如何随着时代的变化而变化:
Variation of Alpha with Epochs
在第一个 T1 时段(本例中为 100)中,权重为 0-有效地强制模型仅根据标记的数据进行训练。在 T1 时段之后,权重线性增加到 alpha_f(在这种情况下为 3 ),直到 T2 时段(在这种情况下为 600 ),这允许模型缓慢地合并未标记的数据。T2 和 alpha_f 分别控制权重增加的速率和饱和后的值。
如果你熟悉最优化理论,你可能会从 模拟退火 中认出这个方程。
这就是从实现的角度理解伪标签的全部内容。该白皮书使用 MNIST 来报告性能,因此我们将坚持使用相同的数据集,这将有助于我们检查我们的实现是否正常工作。
3.实现伪标签
我们将使用 PyTorch 1.3 和 CUDA 来实现,尽管使用 Tensorflow/Keras 应该也没有问题。
模型架构:
虽然本文使用了一个简单的 3 层全连接网络,但在测试过程中,我发现 Conv 网络的性能要好得多。我们将使用一个简单的 2 Conv 层+ 2 全连接层网络,并带有 dropout(如本报告中所述)
Model Architecture for MNIST
基准性能:
我们将使用 1000 个标记图像(类别平衡)和 59,000 个未标记图像用于训练集,10,000 个图像用于测试集。
首先,让我们在不使用任何未标记图像(即简单的监督训练)的情况下检查 1000 个标记图像的性能
Epoch: 290 : Train Loss : 0.00004 | Test Acc : 95.57000 | Test Loss : 0.233
对于 1000 幅标记图像,最佳测试准确率为 95.57%。现在我们有了基线,让我们继续伪标签实现。
伪标签实现:
对于实现,我们将做两个小的更改,使代码更简单,性能更好:
- 在前 100 个历元中,我们将像往常一样在标记数据上训练模型(没有未标记的数据)。正如我们之前看到的,这与伪标记没有区别,因为在此期间 alpha = 0。
- 在接下来的 100+个时期中,我们将对未标记的数据进行训练(使用 alpha 权重)。这里,对于每 50 个未标记的批次,我们将在已标记的数据上训练一个时期—这充当校正因子。
如果这听起来令人困惑,不要担心,这在代码中要容易得多。这次修改是基于这个 Github 回购,它在两个方面有帮助:
- 它减少了对标记的训练数据的过度拟合
- 提高了速度,因为我们只需要每批向前传递 1 次(在未标记的数据上),而不是论文中提到的 2 次(未标记和已标记)。
Flowchart
Pseudo-Labeling Loop for MNIST
注意:我这里不包括监督训练的代码(前 100 个时期),因为它非常简单。你可以在我的 回购这里 找到所有代码
以下是对标记数据进行 100 个时期的训练,然后进行 170 个时期的半监督训练后的结果:
# Best Accuracy is at 168 epochsEpoch: 168 : Alpha Weight : 3.00000 | Test Acc : 98.46000 | Test Loss : 0.075
在使用未标记数据后,我们达到了 98.46%的准确率,比监督训练高出了 3%。事实上,我们的结果比论文的结果更好——1000 个标记样本的准确率为 95.7%。
让我们做一些可视化来理解伪标签是如何工作的。
阿尔法权重与准确度
Alpha vs Epochs
很明显,随着 alpha 的增加,测试精度也慢慢增加,然后达到饱和。
TSNE 可视化
现在让我们看看伪标签是如何在每个时期被分配的。在下面的图中,有 3 点需要注意:
- 每一簇背景中淡淡的颜色才是真正的标签。这是使用所有 60k 训练图像(使用标签)的 TSNE 创建的
- 每个聚类中的小圆圈来自监督训练阶段使用的 1000 幅训练图像。
- 不断移动的小星星是模型为每个历元的未标记图像分配的伪标记。(对于每个时期,我使用了大约 750 张随机采样的未标记图像来创建绘图)
TSNE Animation
以下是一些需要注意的事项:
- 伪标签大部分都是对的。(恒星在具有相同颜色的星团中)这可以归因于较高的初始测试准确度。
- 随着训练的继续,正确伪标签的百分比增加。这反映在模型的整体测试精度的提高上。
这里有一个图显示了在第 0 时段(左)和第 140 时段(右)的相同的 750 点。我已经用红圈标出了改进的点。
(Note: This TSNE plot only show 750 unlabeled samples out of 59000 pts)
现在我们来看看为什么伪标签实际上是有效的。
4.伪标签为什么会起作用?
任何半监督学习算法的目标都是使用未标记和标记样本来学习数据的底层结构。伪标签能够通过做出两个重要的假设来做到这一点:
- 连续性假设(平滑度) : 彼此靠近的点更有可能共享一个标签。( 维基 ) 换句话说,输入的小变化不会引起输出的大变化。这种假设允许伪标记推断图像中的小变化如旋转、剪切等不会改变标记。
- 聚类假设 : 数据往往会形成离散的聚类,而分在 中的同一个聚类 更有可能与 共用一个标签 。这是连续性假设 ( 维基百科)的一个特例,另一种看待这个问题的方式是——类之间的决策边界位于低密度区域*(这样做有助于泛化——类似于 SVM 这样的最大间隔分类器)。*
这就是初始标记数据非常重要的原因,它有助于模型了解底层的聚类结构。当我们在代码中分配一个伪标签时,我们使用模型已经学习的聚类结构来推断未标记数据的标签。随着训练的进行,使用未标记的数据来改进所学习的聚类结构。
如果初始标记的数据太小或包含离群值,伪标记可能会将不正确的标记分配给未标记的点。相反的情况也成立,即伪标记可以受益于仅用标记数据就已经表现良好的分类器。
在下一节中,当我们查看伪标签失败的场景时,这应该更有意义。
5.伪标签什么时候效果不好?
初始标记数据不足以确定聚类
为了更好地理解这种情况,让我们进行一个小实验:不使用 1000 个初始点,让我们采取极端情况,只使用 10 个标记点,看看伪标记如何执行:
10 Initial Labeled Points
不出所料,伪标几乎没有区别。模型本身和随机模型一样好,精度 10%。由于每个类实际上只有 1 个点,该模型无法学习任何类的底层结构。
让我们将标记点的数量增加到 20(每节课 2 点) :
20 Initial Labeled Points
现在,该模型的性能略有提高,因为它学习了一些类的结构。这里有一些有趣的东西-请注意,伪标注为这些点(下图中用红色标记)分配了正确的标注,这很可能是因为附近有两个标注点。
Small Cluster near labeled points
最后,让我们试试 50 分:
50 Labeled Points
性能好多了!再一次,注意图像正中央的一小组棕色标记的点。同一个棕色聚类中距离标记点较远的点总是被错误地预测为水绿色(“4”)或橙色(“7”)。
Pseudo Labels near labeled points
需要注意一些事情:
- 对于上述所有实验(10 分、20 分和 50 分),标记点的选择方式产生了巨大的差异。任何异常值都会完全改变模型的性能和对伪标签的预测。这是小数据集的常见问题。(你可以阅读 我之前的博客 我已经详细讨论过这个问题了)
- 虽然 TSNE 是一个很好的可视化工具,但我们需要记住,它是概率性的,仅仅给了我们一个关于星团如何在高维空间中分布的想法。
总之,当涉及伪标记时,初始标记点的数量和质量都会产生影响。此外,模型可能需要不同类的不同数量的数据来理解特定类的结构。
初始标记数据不包括某些类别
让我们看看如果标记的数据集不包含一个类会发生什么(例如:“7”不包含在标记的集中,但未标记的数据仍然保留所有类)
在标记数据上训练 100 个时期后:
Test Acc : 85.63000 | Test Loss : 1.555
在半监督训练后:
Epoch: 99 : Alpha Weight : 2.50000 | Test Acc : 87.98000 | Test Loss : 2.987
总体准确度确实从 85.6%增加到 87.98%,但是之后没有显示出任何改进。这显然是因为模型无法学习类标签“7”的聚类结构。
下面的动画应该清楚地说明了这些:
Animation for pseudo-labeling with a missing class
毫不奇怪,伪标签在这里很困难,因为我们的模型没有能力学习它以前从未见过的类。然而,在过去几年中,人们对零触发学习技术表现出了极大的兴趣,这种技术使模型能够识别标签,即使它们不存在于训练数据中。
数据增加没有任何好处
在某些情况下,模型可能不够复杂,无法利用额外的数据。这通常发生在将伪标记与传统的 ML 算法(如逻辑回归或 SVM)一起使用时。当谈到深度学习模型时,正如吴恩达在他的 Coursera 课程中提到的那样——大型 DL 模型几乎总是受益于更多的数据。
Andrew Ng — Coursera Deep Learning Specialization
6.用传统的 ML 算法进行伪标记
在这一节中,我们将把伪标签概念应用于逻辑回归。我们将使用相同的 MNIST 数据集,包括 1000 张标记图像、59000 张未标记图像和 10000 张测试图像。
特征工程
我们首先将所有的图像归一化,然后进行 PCA 分解,从 784 维分解到 50 维。接下来,我们将使用 sklearn 的 degree = 2 的PolynomialFeatures()
来添加交互和二次特性。这使得我们每个数据点有 1326 个特征。
数据集规模增加的影响
在我们开始伪标记之前,让我们检查一下当训练数据集大小缓慢增加时,逻辑回归是如何执行的。这将有助于我们了解模型是否可以从伪标签中受益。
随着训练数据集中的样本数量从 100 增加到 1000,我们看到准确性慢慢增加。此外,看起来准确性并没有停滞不前,而是呈上升趋势。由此,我们可以得出这样的结论:伪标签应该可以提高我们的性能。
基准性能:
让我们检查当逻辑回归只使用 1000 个标记图像时的测试准确性。我们将进行 10 次训练,以考虑测试分数的任何变化。
from sklearn.linear_model import SGDClassifiertest_acc = []
for _ in range(10):
log_reg = SGDClassifier(loss = 'log', n_jobs = -1, alpha = 1e-5)
log_reg.fit(x_train_poly, y_train)
y_test_pred = log_reg.predict(x_test_poly)
test_acc.append(accuracy_score(y_test_pred, y_test))
print('Test Accuracy: {:.2f}%'.format(np.array(test_acc).mean()*100))**Output:** Test Accuracy: 90.86%
我们使用逻辑回归的基线测试准确率为 90.86%
伪标签实现:
当使用逻辑回归和其他传统的 ML 算法时,我们需要以稍微不同的方式使用伪标记,尽管概念是相同的。
以下是步骤:
- 我们首先在我们的标记训练集上训练一个分类器。
- 接下来,我们使用这个分类器从无标签数据集中预测随机采样集上的标签。
- 我们组合原始训练集和预测集,并在这个新数据集上重新训练分类器。
- 重复步骤 2 和 3,直到使用完所有未标记的数据。
Flowchart for Pseudo-Labeling with conventional ML Algos
这种技术与这篇博客中提到的略有相似。然而,这里我们递归地生成伪标签,直到所有未标记的数据都被使用。阈值也可以用于确保只为模型非常确信的点生成伪标签(尽管这不是必需的)。
以下是围绕 sklearn 估算器的包装实现:
Pseudo-Labeling Wrapper for Logistic Regression
(完整代码可在 回购 )
现在我们可以把它和逻辑回归一起使用:
from sklearn.linear_model import SGDClassifierlog_reg = SGDClassifier(loss = 'log', n_jobs = -1, alpha = 1e-5)pseudo_labeller = pseudo_labeling(
log_reg,
x_unlabeled_poly,
sample_rate = 0.04,
verbose = True
)pseudo_labeller.fit(x_train_poly, y_train)y_test_pred = pseudo_labeller.predict(x_test_poly)
print('Test Accuracy: {:.2f}%'.format(accuracy_score(y_test_pred, y_test)*100))**Output:**
Test Accuracy: 92.42%
伪标记将准确率从 90.86%提高到 92.42%。(具有更高复杂性的非线性模型,如 XGBoost,可能执行得更好)
这里,sample_rate
类似于深度学习模型例子中的alpha(t)
。以前,alpha 用于控制使用的未标记丢失的数量,而在这种情况下,sample_rate
控制在每次迭代中使用多少未标记的点。
sample_rate
值本身是一个超参数,需要根据数据集和模型*进行调整(类似于 T1、T2 和 alpha_f)。*值 0.04 最适合 MNIST +逻辑回归示例。
一个有趣的修改是安排sample_rate
随着训练的进行而加速,就像alpha(t).
一样
在我们结束之前,让我们看看半监督学习中的一些挑战。
7.半监督学习的挑战
将未标记数据与标记数据相结合
半监督学习的主要目标是使用未标记数据和标记数据来理解数据集的底层结构。这里显而易见的问题是——如何利用未标记的数据来达到这个目的?
在伪标记技术中,我们看到使用预定的权重函数(alpha
)来缓慢地将未标记的数据与标记的数据相结合。然而,alpha(t)
函数假设模型置信度随时间增加,因此线性增加未标记的损失。不一定是这种情况,因为模型预测有时可能不正确。事实上,如果模型做出了几个错误的未标记预测,伪标记可能会像一个糟糕的反馈循环一样,进一步恶化性能。(参考:第 3.1 节 阿拉佐等人 2019【2】)
上述问题的一个解决方案是使用概率阈值——类似于我们对逻辑回归所做的。
其他半监督学习算法使用不同的方式来组合数据,例如,MixMatch 使用两步过程来猜测标签(对于未标记的数据),然后使用 MixUp 数据扩充来组合未标记的数据和标记的数据。(贝特洛等(2019)[3] )
数据效率
半监督学习的另一个挑战是设计可以处理非常少量的标记数据的算法。正如我们在伪标记中看到的,该模型对 1000 个初始标记样本的效果最好。然而,当标注数据集进一步减少时(例如:50 个点),伪标注的性能开始下降。
Oliver et al .(2018)【4】对几种半监督学习算法进行了比较,发现伪标记在“two-moons”数据集上失败,而 VAT 和 pi-model 等其他模型工作得更好。
Source: Oliver et al (2018) [4]
如图所示,VAT 和 Pi-Model 学习了一个决策边界,仅用 6 个带标签的数据点(显示在白色和黑色的大圆圈中)就取得了惊人的效果。另一方面,伪标记完全失败,取而代之的是学习线性决策边界。
我使用 Oliver 等人使用的相同模型重复了实验,发现伪标记需要 30-50 个标记点(取决于标记点的位置)来学习底层数据结构。
Pseudo-Labeling on Two Moons Dataset. Triangles are labeled points.
为了使半监督学习更加实用,我们需要数据效率高的算法,即可以在非常少量的标记数据上工作的算法。
8.结论
Oliver 等人[4]提到:“伪标记是一种简单的启发式方法,在实践中被广泛使用,可能是因为它的简单性和通用性”,正如我们所见,它提供了一种学习半监督学习的好方法。
在过去的 2-3 年中,用于图像分类的半监督学习已经取得了一些令人难以置信的进步。无监督数据增强(谢等(2019) [5] )在 CIFAR- 10 上仅用 4000 个标签就取得了 97.3%的效果。为了客观地看待这一点,DenseNet ( 黄等人(2016 )[6])在 2016 年在完整的 CIFAR-10 数据集上实现了 96.54%。
看到机器学习和数据科学社区如何转向使用更少标记数据(如半监督学习、零/少量学习)或更小数据集(如迁移学习)的算法,真的很有趣。就个人而言,我认为如果我们真的想为所有人普及人工智能,这些发展是至关重要的。
如果你有任何问题,请随时与我联系。我希望你喜欢!
Github Repo:https://Github . com/anirudhshenoy/pseudo _ labeling _ small _ datasets
数据集:https://www.kaggle.com/oddrationale/mnist-in-csv
参考资料:
- 李东炫。“伪标签:深度神经网络的简单有效的半监督学习方法”ICML 2013 研讨会:表征学习的挑战(WREPL),美国佐治亚州亚特兰大,2013 年(http://Deep Learning . net/WP-content/uploads/2013/03/Pseudo _ Label _ final . pdf)
- 埃里克阿拉索,迭戈奥特戈,保罗阿尔伯特,诺埃尔奥康纳,凯文麦克吉尼斯。“深度半监督学习中的伪标记和确认偏差”(https://arxiv.org/abs/1908.02983)
- 大卫·贝特洛,尼古拉斯·卡里尼,伊恩·古德菲勒,尼古拉斯·帕伯诺,阿维塔尔·奥利弗,科林·拉斐尔。“混合匹配:半监督学习的整体方法”(https://arxiv.org/abs/1905.02249)
- "阿维塔尔·奥利弗,奥古斯都·奥登纳,科林·拉斐尔,艾金·d·库布克,伊恩·j·古德菲勒. "深度半监督学习算法的现实评估”(https://arxiv.org/abs/1804.09170)
- 谢启哲,戴子航,Eduard Hovy,Minh-Thang Luong,郭诉乐。“用于一致性训练的无监督数据扩充”(https://arxiv.org/abs/1904.12848)
- 黄高,刘庄,劳伦斯·范·德·马滕,基利安·q·温伯格。“密集连接的卷积网络”(【https://arxiv.org/abs/1608.06993】T2)
- 【https://github.com/peimengsui/semi_supervised_mnist
- https://www . analyticsvidhya . com/blog/2017/09/pseudo-labeling-semi-supervised-learning-technique/
- https://www . quora . com/Why-is-unsupervised-learning-important
- https://www.wired.com/2014/08/deep-learning-yann-lecun/
普学习
处理隐藏在未标记数据中的负类
PU Learning — finding a needle in a haystack
在工作中不断出现的一个挑战是,在需要训练二进制分类器的情况下,没有标记的负类。通常,这个问题伴随着严重不平衡的数据集,在时间紧迫的情况下,我经常采取简单的方法,对未知数据集进行子采样,并将其视为未知。显然,这并不理想,因为未知集合被污染了,因此分类器不能很好地训练。然而,在野外,在现实生活的最后期限内,这种方法是省时的,而且结果常常出人意料地有用。
最近,我很幸运有几天时间稍微围绕这个话题阅读。我发现了一些有趣的方法,并认为值得做一些笔记,它们变成了这个帖子。
有几种不同的 PU 方法。所有的方法都包括从未知数据集中分离出一组所谓的可靠否定(RNs)。据我所知,最广泛引用的初始方法是刘等人在 2002 年和 2003 年提出的,其中一组 RNs 是从未知类中迭代生长出来的。
Fusilier 等人在 2015 年描述了另一种吸引我的方法。在他们的论文中,作者描述了一种方法,该方法迭代地从未知类中减少 rn 的集合,有效地收紧围绕那些与正类最不相似的情况的网。这种方法吸引了我,因为它含蓄地处理了阶级不平衡。
我遇到的第三种方法(Mordelet & Vert 2013)也隐含地说明了类别不平衡,它涉及装袋,或从未知类别中随机抽样,并将样本视为阴性。这与我上面提到的天真方法的不同之处在于,该过程被重复多次,并且训练了一系列模型。该模型针对具有不同污染程度的未知数据集来表征正类。得到的模型分数被集合,并且结果应该更好地将可靠的否定从未知类别中分离出来。
下面,我将详细介绍这三种方法。
方法
“原创”方法(刘等,2002 和 2003)
给定一个只包含阳性§和未知(U)类的训练集,遵循以下步骤:
- 将所有 U 视为否定(N)训练分类器 P 对 U
- 使用分类器,对未知类别进行评分,并分离出“可靠的”否定集合(RN)。
- 在 P 对 RN 上训练一个新的分类器,用它对剩余的 U 进行评分,分离出额外的 RN,放大 RN。
- 重复步骤 3,迭代地扩大 RN 的集合,直到满足停止条件。
当没有新的阴性病例被分类时,满足停止条件。
其中Q
被定义为分类为负数的未知集合,而i
是迭代器,停止条件被定义为:
|Qi| > 0
改良方法(Fusilier 等人,2014 年)
给定一个只包含阳性§和未知(U)类的训练集,遵循以下步骤:
- 将所有 U 视为否定(N)训练分类器 P 对 U
- 使用分类器,对未知类别进行评分,并分离出“可靠的”否定集合(RN)。
- 在 P 对 RN 上训练新的分类器。对 RN 评分并从 RN 中排除预测阳性
- 重复步骤 3,迭代地改进 RN 集合,直到满足停止条件。
其中Q
被定义为被归类为否定的未知集合,i
是迭代器,停止条件被定义为:
|Qi| <= |Q(i-1)| & |P| < |Qi|
停止条件确保 Q 的大小减小(避免 RN 大小突然大幅度减小),同时 RN 集永远不会变得小于 P 集。更明确地说:
虽然在这次迭代中被分类为否定的未知集合的大小小于或等于在先前迭代中被分类为否定的未知集合的大小,并且肯定类别集合的大小小于从这次迭代得到的精炼 rn 集合
装袋方法(归纳)(Mordelet & Vert,2013 年)
给定仅包含阳性§和未知(U)的训练集,其中 K =引导样本的大小,T =样本的数量,遵循以下步骤:1 .从 U ^ 2 中抽取大小为 K 的 bootstrap 样本 Ut。训练一个分类器 P 对 Ut 3。重复步骤 1 和 2,共 4 次。使用袋装模型通过集成方法对测试数据进行评分。
这里的停止标准是由 T 的值决定的,作者认为将 T 设置为> 100 通常不会获得太多的附加值。然而,从它们的曲线图来看,在|P|
和K
都很大的地方,T = 5 以上几乎没有变化。我怀疑,如果可能的话,在训练期间尝试跟踪这一点是值得的,或者在您的函数中设置早期停止类型标准,因为根据您的时间限制,训练 100 个模型可能是不可行的。
需要跟进的事项:
-大部分文章使用 SVM,但也往往是 NLP 问题。分类器家族很重要吗?
-原始文件如何确定确定“可靠性”的截止点
-改进的方法:WRT 停止准则,为什么 Q 会随着迭代而变大?
-打包方法:考虑如何最好地惩罚假阴性。
-截止选择?
结论
这三种方法为 PU 学习问题提供了合理的方法,但是只有改进的和 bagging 方法提供了处理不平衡数据的固有方法。我的计划是尝试并实现这两种方法,并比较它们的结果。虽然我不能公开分享数据,但我会试着在博客和 GitHub 上分享代码和一般结果。我们主要在 Python/PySpark 或 Scala/Spark 中工作。一些不错的链接:
- https://github.com/ispras/pu4spark用 Scala/Spark 编写的 PU 学习库
- https://astrakhantsev.com/pu-learning/pu4s park 的作者写了一篇不错的帖子
- https://Roy Wright . me/2017/11/16/positive-unlabeled-learning/PU 学习方法的精彩概述
参考
Fusilier DH,Montes-y-Gómez M,Rosso P,Guzmán Cabrera R (2015)使用 PU-learning 检测正面和负面欺骗性观点。Inf 流程管理 51:433–443。doi:10.1016/j . IPM 2014 . 11 . 001
刘波,戴 Y,李 X,等(2003)利用正例和未标记样本构造文本分类器。第三届 IEEE 数据挖掘国际会议。第 179-186 页
刘 B,李伟生,俞平山,李 X (2002)文本文档的部分监督分类。在:过程中。第 19 国际机场。糖膏剂关于机器学习。第 387 至 394 页
Mordelet F,Vert J-P (2014) A bagging SVM 从正面和无标签的例子中学习。模式识别列特 37:201–209。2013 年 6 月 10 日
PUBG —使用 AWS 和 Plotly 进行分析
Athena 和 cufflinks 在 PUBG 数据分析中的应用
对于本文,我将开始分析用本文中解释的管道提取的数据。这篇文章的目标也是:
- 获取 AWS athena 简介
- 使用 plotly 深入了解数据
- 更好地理解视频游戏 PUBG 的消费
PUBG 是一个被定义为皇家战役的游戏,其中的原则是有 X 个人(或小队)被投放到一个岛上,目标是通过使用随机部署在岛上的物品和武器成为岛上的最后一个幸存者。为了增加游戏中的紧张感(并给它一个结尾),地图中可用的部分会有规律地减少,以促使玩家为自己的生命而战。
就游戏性而言,有多个岛屿可用(每个岛屿都有自己的环境,如沙漠,雪地),你可以在不同的模式下玩(单人,双人小队),有时相机可以预定义(fpp 仅适用于第一人称玩家或第一/第三人称相机)
雅典娜概述
Athena 是亚马逊开发的一项服务,旨在让用户能够在不使用服务器或数据仓库的情况下,轻松地从 S3 桶中查询数据。这是一个在 web 浏览器上使用 Athena 的界面示例。
开发的系统在数据格式方面非常开放,可以使用 CSV、JSON、ORC、Avro 和 Parquet。
该系统的核心是建立在被定义为开源分布式 SQL 查询引擎的 Presto 之上。这个项目的主要用户之一是围绕交互式分析、批量 ETL、A/B 测试和开发者广告分析的各种主题的脸书。
有一篇关于 Presto 的很好的文章详细介绍了引擎的所有机械。
这个工具的一些其他大用户是网飞和 Airbnb,他们正在围绕这种系统建立服务。
如果我们回到 Athena 服务,无服务器系统很有趣,因为计费只基于扫描的数据。因此,对于想要处理大量“大”数据而不处理所有基础设施(这是一项全职工作)的人来说,S3 +雅典娜组合确实是一个很好的组合。
为了将 AWS Athena 连接到 python 脚本,有一个可以安装的包pyathenaidbc,它将安装一个可以在 pandas 数据框架中使用的连接器。这是一个连接数据的脚本示例。
代码非常简单,看起来像是对经典 postgreSQL 数据库的调用。为了让成本更加透明,我们用图表展示了实验的成本。
真正的数量是来自 S3 读数和雅典娜激活,每天分析不到 2 美元(这个实验只有 4 美元)。
让我们开始分析与 PUBG 相关的数据。
收集数据的状态
管道在 2019 年 1 月 26 日至 2019 年 4 月 5 日之间运行了一个多月,这段时间代表了大约 69000 场比赛,因此要处理的数据量相当有趣(比赛期间收集的事件量)。
就区域和平台而言,管道的重点是从北美的 PC 平台提取数据。
在本次分析中,我将重点关注 3 个事件:
- gamestat 周期,大约是 11 00 万行
- 玩家杀死,那大约是 7 00 万行
- 使用的项目大约有 19 00 万行
普罗特利公司
我是 plotly 库的忠实粉丝,去年我写了一篇文章,是关于一个闪亮的 python 包,名为 Dash ,由 plotly 提供支持,它真的是一个很酷的包,使得用 Python 构建 dashboard 更加容易。
该软件包是由总部设在蒙特利尔的公司 Plotly 开发的,所以我们实际上是邻居(真的就像 4 分钟的步行时间)。
原始包是一个非常酷的库,可以基于 D3.js 免费制作交互式图表,他们提供了在他们的图表工作室部署所制作的图表的可能性(免费版本提供了在他们的平台上托管 25 个图表的可能性,但是使用高级帐户你可以托管更多图表/数据)
就用法而言,最初的语法有点“沉重”,因此有人开发了包装器,以方便在一个以上的线性样式上构建图形:
对于这篇文章,我只使用袖扣,但我正计划使其他文章将使用 plotly express。
提取的数据
老实说,有很多网站做了类似的分析,比如 PUBGmap.io ,但是做不同的分析和比较仍然很有趣。
有一个基于地图和模式收集的匹配的表示。
最受欢迎的模式是小队模式,地图野蛮。对于下面的分析,我将把重点放在模式小队。
比赛持续时间
就持续时间而言,我为每张地图抽取了一个匹配样本(1000 个匹配),并且有一些箱线图。
地图野蛮人看起来在持续时间上有不同的表现,这可以通过比其他人小得多的大小来解释(中间值有 5 分钟的差异)。
比赛的演变
对于玩家的寿命,在下图中显示了玩家存活的百分比与地图中完成比赛的百分比。
在比赛开始时,大部分玩家都活着,这与地图上所有玩家的着陆时刻有关。就活着的玩家的进化而言,这张地图似乎没有一直进化下去,这张地图比另一张“更平滑”,艾朗格尔和迪霍洛托克非常相似,而萨维奇似乎是最暴力的一个(这可以通过地图的格式来解释)。
武器使用
就用于杀人的武器而言,这一时期每种武器的杀人数量分布情况。
游戏中有多种类型的武器,从手枪,步枪,猎枪或弩,但最受欢迎的是 AK47,在武器的顶部有很多步枪。
杀戮事件中另一个真正有趣的数据是最后一枪在身体上的位置,下图是最后一枪的位置在每个武器上的重新分配。
爆头的部分在武器的功能上是不同的,手枪看起来没有步枪精确(这是有道理的)
另一个有趣的见解是,一些武器似乎是地图特定的,在下图中有每个地图的武器分布。
支持项目用途
作为总结,我决定对游戏中的治疗和助推器的使用有一个总体的了解,在下面的图中,事件的演变使用了一个治疗对象和一个助推器对象来完成比赛。
对于助推器,在 40%的比赛中肯定有一个峰值,但是对于治疗对象,在 15%中有第一个凸起,这是指第一波被淘汰的球员,峰值在第二阶段淘汰的比赛中间。
这篇文章介绍了我将对从 PUBG 收集的数据进行的更多工作,我将重点关注 PUBG 上关于位置数据的未来文章,该数据集代表大约 500,00 0,00 0 行,因此会更有趣。
最初发表于【http://the-odd-dataguy.com】。
使用 Jupyter、Github 和 Kyso 将数据科学文章发布到网络上
结合这 3 个工具来增强你的 DS 工作流程
数据科学正在飞速发展,越来越多的组织使用数据来推动一切。但是有时候发布基于数据科学的报告还是有点困难。这可能是因为图表是交互式的,或者是因为您想要一个可复制的文档,或者是因为从数据科学工具转移到可读性更好的格式可能很困难。
在这篇文章中,我将介绍如何在 Jupyter 笔记本、Github 和 Kyso 中使用 python,我们可以在几分钟内从获得一些原始数据到在网络上发布一个非常棒的图表。
让我们开始分析一些数据
我首先假设你已经安装了 python3 否则去 https://www.python.org/下载它。然后,让我们安装 Jupyterlab 和 plotly 扩展,以获得出色的交互式图表。
pip3 install jupyterlab
jupyter labextension install @jupyterlab/plotly-extension
那么让我们从 Jupyterlab 开始
jupyter lab
让我们下载一些数据来玩玩,我用最近的欧洲晴雨表数据来看看有多少人在欧盟认同欧盟国旗,这里有。
所以现在你应该已经打开了 Jupyter 这是一个很好的交互地方,你可以在这里输入结果并看到实时输出。
然后,我们可以在第一个笔记本单元格中开始编写代码!
import plotly
import pandas as pd
import plotly.graph_objs as go
import country_converter as coco
plotly.offline.init_notebook_mode(connected=True)
这将导入我们需要的所有库,并设置 Plotly 来渲染 Jupyter 笔记本中的图表。
cols2skip = [0]
cols = [i for i in range(100) if i not in cols2skip]
df_raw = pd.read_excel('./eb_90_volume_A.xls', sheet_name="QD11.3", skiprows=8, usecols=cols)
df_raw.rename(columns={'UE28\nEU28':'EU28', 'UE28-UK\nEU28-UK':'EU28-UK' }, inplace=True)
df = df_raw.transpose()
df = df.rename(columns=df.iloc[0])
df = df.iloc[1:]
names = df.index.tolist()
names = ['GR' if (x == 'EL') else x for x in names]
names = ['GB' if (x == 'UK') else x for x in names]
iso3 = coco.convert(names=names, to='ISO3', not_found=None)
然后,我们可以与数据争论,并设置它进行绘图,这里没有太多的解释,我在几分钟的游戏和检查数据形状后得出了这个代码。
最后,让我们绘制数据:
data = [go.Choropleth(
locations = iso3,
z = df['Tend to agree'] * 100,
text = iso3,
locationmode="ISO-3",
reversescale=True,
colorscale="Blues",
marker = go.choropleth.Marker(
line = go.choropleth.marker.Line(
color = 'rgb(0,0,0)',
width = 0.5
)),
colorbar = go.choropleth.ColorBar(
ticksuffix = '%',
title = '% identify',
len=0.5,
),
)]
layout = {
"height": 700,
"width": 700,
"margin"
: {"t": 0, "b": 0, "l": 0, "r": 0},
"geo": {
"lataxis": {"range": [36.0, 65.0]},
"lonaxis": {"range": [-12.0, 36.0]},
"projection": {"type": "transverse mercator"},
"resolution": 50,
"showcoastlines": True,
"showframe": True,
"showcountries": True,
}
}
fig = go.Figure(data = data, layout = layout)
plotly.offline.iplot(fig)
现在你的笔记本里应该有一个漂亮的交互式图表了。
Plotly Chart in Jupyterlab
在 Jupyter 中,您还可以通过将单元格更改为 markdown 类型来为您的分析编写一系列注释,然后编写您喜欢的任何内容。
让我们把这个笔记本推到网上
我们现在有几个选择可以让我们在网上分享这个笔记本。我已经在 http://kyso.io/eoin/do-i-identify-with-eu-flag发布到网上,你可以看到预览。
我们的第一选择是,我们可以把它上传到 Kyso。去 https://kyso.io/create/study,顺便去看看。ipynb 文件,给它一个标题,你就会有一个分享的链接。
或者,我们可以将 Github 用于一个长期项目,在这个项目中,我们可能会做出很多改变。开始去 Github 做一个新项目,然后做正常的生意
git add .
git commit -m "initial commit"
git push origin master
然后你就可以在 Github 上查看你的项目和笔记本了。然而,这个阴谋是不可见的。所以让我们把这个项目导入到 Kyso。
前往https://kyso.io/github并登录 Github,搜索你的项目,点击该项目的“连接到 Kyso”,选择主文件,你就完成了。您的笔记本将在 Kyso 上可见,任何时候您提交到该存储库,它都会在 Kyso 上得到反映,这意味着您可以不断更新您的分析。例如,查看 Kyso 上的这个帖子,它链接到这个 Github 库。
使用 TensorFlow 服务发布 Keras 模型 API
TensorFlow Serving 是一个面向机器学习模型的高性能服务系统。我们提供了一个端到端示例来帮助您入门。
Source: Pixabay
构建 ML 模型是一项至关重要的任务。在生产中运行 ML 模型是一项复杂而重要的任务。我以前有一个关于通过 Flask REST API 服务 ML 模型的帖子— 用 Python Flask 发布机器学习 API。虽然这种方法可行,但它肯定缺少一些要点:
- 模型版本控制
- 请求批处理
- 多线程操作
TensorFlow 附带了一套工具来帮助您在生产中运行 ML 模型。其中一个工具——tensor flow 服务。有一个很好的教程描述了如何配置和运行它——tensor flow Serving with Docker。我将在我的例子中遵循相同的步骤。
TensorFlow 服务提供模型版本控制功能。默认情况下,客户端可以访问特定的模型版本或获取最新版本。当模型被保存时,我们可以使用当前的时间戳生成一个模型版本:
import calendar;
import time;
ts = calendar.timegm(time.gmtime())tf.saved_model.save(model, "./model_report_exec_time/" + str(ts))
这是生成的文件夹结构,模型的版本带有时间戳:
我更喜欢在自己的 Docker 容器中运行 TensorFlow 服务。根据说明,可以选择将 ML 模型复制到 TensorFlow 服务容器中。首先,从 tensorflow/serving image 创建一个基本容器:
docker run -d --name serving_base tensorflow/serving
将 ML 模型从本地文件夹复制到基本容器中(在我的例子中, model_folder 和 model_name 都被设置为 model_report_exec_time ):
docker cp <model_folder> serving_base:/models/<model_name>
现在您可以仔细检查模型是否复制成功。进入容器:
docker exec -it serving_base bash
导航到 models 文件夹,您应该在那里看到您的 ML 模型:
现在创建一个新的 Docker 容器,并将您的 ML 模型名称设置为环境变量,这样模型将在下一次容器启动时提供服务:
docker commit --change "ENV MODEL_NAME <model_name>" serving_base katanaml/core-serving:v19.8
你不再需要基本容器,移除它:
docker kill serving_base
docker rm serving_base
从您新创建的映像启动容器(REST 端点运行在端口 8501 上):
docker run -d -p 8500:8500 -p 8501:8501 --name katana-ml-serving katanaml/core-serving:v19.8
检查容器日志,以确保 TensorFlow 服务启动时没有错误:
docker logs -f katana-ml-serving
我建议通过 RESTful API 指南了解 TensorFlow 服务。您应该检查模型端点是否可用,执行 GET:http://localhost:8501/v1/models/。这将返回模型版本状态:
如果获得响应成功,我们可以进一步执行 ML 模型预测功能。使用 TensorFlow 服务,可以通过 POST 请求直接调用 predict 函数,参数可以通过一个名为 instances 的变量传递。ML 模型接受规范化数据,这意味着数据应该在调用预测端点之前规范化。在这个例子中,数据在 Python 中被规范化(检查本文中描述的 ML 模型— 用 Keras 和 TensorFlow 报告时间执行预测):
使用 CURL 执行预测请求:
curl -d '{"instances": [[ 3.19179609, 2.05277296, -0.51536518, -0.4880486, -0.50239337, -0.50629114, -0.74968743, -0.68702182, 1.45992522]]}' \
-X POST [http://localhost:8501/v1/models/model_report_exec_time:predict](http://localhost:8501/v1/models/model_report_exec_time:predict)
响应返回预测值:424.9289
资源:
- 带有源代码的 GitHub 回购
- 用 Keras 和 TensorFlow 报告时间执行预测
- TensorFlow 与 Docker 一起发球
用 Python Flask 发布机器学习 API
描述如何通过 Python Flask REST API 向外界公开机器学习模型的一组指令
Source: Pixabay
正如 Flask 网站上所说,Flask 很有趣,也很容易设置。这是事实。这个 Python 的微框架提供了一种用 REST endpoint 注释 Python 函数的强大方法。我使用 Flask 来发布 ML 模型 API,以供第三方业务应用程序访问。
这个例子基于 XGBoost。
为了更好地维护代码,我建议使用一个单独的 Jupyter 笔记本,ML 模型 API 将在那里发布。连同烧瓶 CORS 一起导入烧瓶模块:
from flask import Flask, jsonify, request
from flask_cors import CORS, cross_originimport pickle
import pandas as pd
在皮马印第安人糖尿病数据库上训练模型。CSV 数据可以从这里下载。要构建 Pandas 数据框变量作为模型预测函数的输入,我们需要定义一个数据集列数组:
# Get headers for payload
headers = ['times_pregnant', 'glucose', 'blood_pressure', 'skin_fold_thick', 'serum_insuling', 'mass_index', 'diabetes_pedigree', 'age']
使用 Pickle 加载先前训练和保存的模型:
# Use pickle to load in the pre-trained model
with open(f'diabetes-model.pkl', 'rb') as f:
model = pickle.load(f)
做一个测试运行并检查模型是否运行良好总是一个好的实践。使用列名数组和数据数组(使用新数据,即训练或测试数据集中不存在的数据)构建数据框架。调用两个函数— model.predict 和 model.predict_proba 。通常我更喜欢使用 model.predict_proba ,它返回描述可能性为 0/1 的概率,这有助于解释基于特定范围(例如 0.25 到 0.75)的结果。Pandas 数据帧由样本有效载荷构成,然后执行模型预测:
# Test model with data frame
input_variables = pd.DataFrame([[1, 106, 70, 28, 135, 34.2, 0.142, 22]],
columns=headers,
dtype=float,
index=['input'])# Get the model's prediction
prediction = model.predict(input_variables)
print("Prediction: ", prediction)
prediction_proba = model.predict_proba(input_variables)
print("Probabilities: ", prediction_proba)
烧瓶 API。确保您启用了 CORS,否则 API 调用将无法在另一台主机上工作。在要通过 REST API 公开的函数之前写注释。提供一个端点名和支持的 REST 方法(在这个例子中是 POST)。从请求中检索有效载荷数据,构建 Pandas 数据帧并执行 model predict_proba 函数:
app = Flask(__name__)
CORS(app)[@app](http://twitter.com/app).route("/katana-ml/api/v1.0/diabetes", methods=['POST'])
def predict():
payload = request.json['data']
values = [float(i) for i in payload.split(',')]
input_variables = pd.DataFrame([values],
columns=headers,
dtype=float,
index=['input']) # Get the model's prediction
prediction_proba = model.predict_proba(input_variables)
prediction = (prediction_proba[0])[1]
ret = '{"prediction":' + str(float(prediction)) + '}'
return ret# running REST interface, port=5000 for direct test
if __name__ == "__main__":
app.run(debug=False, host='0.0.0.0', port=5000)
响应 JSON 字符串被构造并作为函数结果返回。我在 Docker 容器中运行 Flask,这就是为什么使用 0.0.0.0 作为它运行的主机。端口 5000 被映射为外部端口,这允许来自外部的呼叫。
虽然在 Jupyter notebook 中直接启动 Flask interface 是可行的,但我建议将其转换为 Python 脚本,并作为服务从命令行运行。使用 Jupyter nbconvert 命令转换为 Python 脚本:
jupyter nb convert—to python diabetes _ redsamurai _ endpoint _ db . ipynb
带有 Flask endpoint 的 Python 脚本可以通过 PM2 进程管理器作为后台进程启动。这允许将端点作为服务运行,并在不同的端口上启动其他进程。PM2 开始命令:
PM2 start diabetes _ redsamurai _ endpoint _ db . py
pm2 监视器帮助显示正在运行的进程的信息:
从邮递员通过由 Flask 服务的端点的 ML 模型分类 REST API 调用:
更多信息:
发布您自己的 Python 包
打包 Python 代码的实用指南
假设你有一段很好的 Python 代码;几个相关的小函数,或者一个只有几百行代码的中型模块。并说你最终会一次又一次地使用这段代码;也许您一直将它复制到不同的项目或存储库中,或者您一直从您在特定路径中设置的某个专用实用程序代码文件夹中导入它。
这很自然——我们都在编码时不断积累这些小的个人工具,Python 可能比一般的编程语言更支持和鼓励这一点——拥有这些代码片段感觉很好。
但是,如果您可以轻松地导入您开发的一个小工具,而不用到处复制它,并维护它的一个更新版本,这不是很好吗?让它在不同的环境、机器和上下文中可用,而不依赖于特定的文件或路径?甚至能够对它进行版本化,并让使用它的代码清楚地反映这种依赖性?如果其他人也能使用它不是更好吗?
是的。是的,会的。🐃
当然,这个概念并不新鲜。这就是为什么我们通常在编程语言中有模块、包和库,以及为什么我们在 Python 中有这些。这是驱动 Python 非凡的能力、可用性和受欢迎程度的一部分;我们都可以通过简单的pip install
和import
获得 beautifulSoup 的 html 解析或 pandas 的数据帧处理的惊人能力。
最酷的部分是,Python 让每个人都可以轻松地在官方 Python 包索引 PyPI 上编写和发布自己的代码包,并让它们像sklearn
、requests
或delorean
(所有这些都是非常有用和流行的 Python 包)一样容易获得。你绝对应该这么做,因为:
- 让别人使用你的代码是很有趣的,即使只是少数人;在工作、社区活动或求职面试中分享和展示是一件很酷的事情。
- 它通过迫使您组织和记录代码,并将其公开给同行评审,使您的代码变得更好。
- 最后,它通过贡献一些缺失的功能而使社区受益。您会惊讶地发现,有多少人会发现您高效地将 HTTP 头序列化为 JSON 或您为轻松验证输入 MongoDB 查询文档而创建的 decorator 非常有用。
现在,我已经满怀希望地说服了您,让您把放在那里的那个很酷的旧 Python 模块上的灰尘吹掉,并把它做成一个小 Python 包,让我们开始吧。
Figure 1: Python dust 🐍
在接下来的文章中,我将尝试向您介绍我决定打包 Python 代码的最小过程,基于我发布几个小软件包(例如 pdpipe 、 skift 和 cachier )到全球 PyPI 服务器和几十个我曾经发布过的初创企业的私有 PyPI 服务器我知道这并不完美,但这是我所知道的,所以只能这样了。
第一步:都在名字里
让我们从为您的包选择一个名称开始。在我看来,一个好的名字足够短,很容易在一个pip install
或import
声明后随意键入(虽然我们现在有自动完成功能),但足够信息丰富,人们可以理解,或者至少在安装包后记得它是关于什么的。
处理 HTTP 请求的requests
,处理日期和时间的delorean
,以及提供机器学习框架的sklearn
,都是很好的例子。在为我的 pandas pipelines 包( pdpipe )选择名称时,我试图遵循这个例子,它建立在这样一个事实上,即pandas
通常以更短的别名pd
和我的缓存包( cachier )导入。
不过,老实说,这是例外,而不是规律。流行的 Python 包有类似于pandas
、keras
、django
、boto
、jinja
、flask
和pytorch
的名字,然而我们都记得它们的名字,所以你也可以使用任何短的和可发音的名字(例如,尽管 skift 是“SciKIt-learn wrappers for fast text”的一个很好的缩写,但我主要关心的是可发音性)。这也是一个好主意,以确保它工作良好,所有小写。
为了这个帖子,假设你和chocobo
一起去的。🐤
步骤 2:基本的包结构
现在,让我们通过几个简短的步骤,为您留下一个通用的包结构:
- 用你的包的确切的名称创建一个 Github 库。不要风格化或骆驼案件。然后,在本地克隆它。
- 在存储库中创建一个文件夹,使用您包的确切名称;这是保存软件包代码的文件夹。这个是规范,你只需要记住外层的
chocobo
文件夹(在我们的例子中)是存储库的文件夹,而内层的chocobo
文件夹是包的文件夹。 - 把你的模块,以及它使用的任何其他模块,放在内部的
chocobo
文件夹中。如果缺少一个文件,则添加一个__init__.py
文件。 - 将软件包用户将直接调用的重要对象(通常是函数)从各自的模块导入到
__init__.py
文件中。这些是包名称空间下可用的函数、类和变量。软件包的 API,如果你愿意的话。 - 不是特定于包的,但是您确实应该在存储库的根目录中包含一个
.gitignore
文件。这是一个优秀的。
所以我们现在有了一个结构,我们可以在其中添加不同的文件来组成一个包;内部文件夹将保存包的代码,外部文件夹将保存辅助打包文件和其他与存储库相关的文件。
因此,假设您的原始模块chocobo.py
看起来像这样:
那么,您的新存储库文件夹应该如下所示:
chocobo/
chocobo/
__init__.py
chocobo.py
.gitignore
其中__init__.py
看起来像这样:
这意味着在我们完成之后,我们最先进的chocobo
软件包的用户将能够这样使用它:
你知道要点了。
哈哈!因为我刚刚附上了一堆 GitHub gists好了,继续前进!
第三步:许可证
用一些公共许可证发布你的代码是一个好主意;虽然我们在这里讨论的是将您的代码作为开源发布,但是您可能希望在保留版权的同时允许重用您的代码,或者您可能希望有人扩展您的代码以保持衍生代码的开放,而许可证可以帮助您非常容易地做到这一点。
对于一个你并不真正关心其他人会怎么做的宠物项目来说, MIT 许可证可能是一个很好的主意,但是你可以前往choosealicense.com获取 GitHub 和开源社区关于这个主题的一些很好的建议。
无论你选择哪种许可证,都比根本不选择许可证要好。没有许可证就发布代码在很多情况下等同于根本不发布;如果你不明确地表明你对代码的权利,大多数公司不会冒使用它可能带来的法律后果的风险,因此它的许多潜在用户会考虑其他选择。
选择许可证后,在您的存储库中创建一个LICENSE
文件(不需要文件扩展名),并用所选许可证的精确文本填充它。
步骤 4:安装文件
我们现在将创建 Python 打包工具(在我们的例子中是setuptools
)依赖的基本文件;setup.py
。setup.py
包含构建和分发包时使用的实际指令。
这里有一个模板,您可以从它开始(不要担心,我们会检查所有内容):
所以,我们从导入setuptools
开始。这是一个非常有用的包,旨在促进 Python 包的轻松分发,尽管它没有包含在标准库中(不像它的功能较弱的替代品distutils
),但它是当今 Python 包分发的事实上的标准,也是您应该使用的标准。我们将只使用setuptools
包中的两个函数:setup
和find_pacakges
。
在导入setuptools
之后,调用setup()
函数之前,我们将把README.md
文件的内容读入一个方便的全局变量README
。
现在,剩下的就是用下面的参数调用setuptools.setup()
函数:
author
:提供姓名即可。author_email
:你的邮箱。name
:包名。“乔科博”,在这种情况下。license
:字符串*“MIT”*,或者您选择的任何其他许可证的名称。- 你的包裹的简短描述。比如这里:“chocobo 是美味 choco bo 食谱的 python 包”。
version
:表示软件包当前版本的字符串。我希望在以后的文章中讨论一种稍微优雅一点的处理包版本控制的方法,但是现在你可以从一个你想发布新版本时手动增加的数字开始。通常的做法是在版本号前面加上字母 v,,因此v1
是您第一个版本的合适版本字符串,但是我建议您将v0.0.1
视为一个等价的版本字符串,并使用这种格式。同样,在以后的文章中会更多地讨论这意味着什么。long_description
:自述内容在此处。事实上,这确实是对该软件包的一个很长的描述。这是 PyPI 上的包的页面内容(例)。url
:指向包主页的 URL。假设您没有专门的站点,那么您的存储库的 URL 是一个很好的目标。packages
:啊哈,这里我们第二次用setuptools
!该参数接受所有要构建和分发/安装的包的名称数组(取决于调用的命令)。从技术上来说,你可以把["chocobo"]
放在这里,但是更好的做法是推广并使用这个能够处理更复杂的包和存储库结构的setuptools
函数。它需要两个可选参数,where
和exclude
,这里我们都忽略了。结果是where
被设置为安装文件所在的目录,并且不排除任何子目录,这对于大多数情况来说已经足够好了。python_requires
:如果你的包支持所有 Python 版本,你可以丢弃这个参数。因为很可能不是这样,所以您应该选择一个合适的值。从技术上来说,我不会宣传对一个我没有测试过的版本的支持,但是现在我们可以有把握地假设:
(1)如果你正在使用 Python 2,那么你就是在使用 python 2.7,并且(a)你是一只罕见的奇妙的鸟(b)除了 Python 2.7 之外,你不需要支持任何东西,所以你可以用">=2.7"
字符串提供这个参数。另外,请继续学习 Python 3;时钟确实在滴答作响。
(2)如果你使用的是 Python 3,那么你可以支持任何比你用来开发包的版本更大或相同的 Python 版本。所以如果你正在使用 Python 3.5,那么">=3.5"
应该没问题。install_requires
:所有非标准库包依赖项都在这里。例如,如果我们的chocobo
包同时依赖于requests
包和pytz
包,我们将用下面的字符串数组来提供这个参数:["requests", "pytz"]
。- 你的包裹很快就会和成千上万的其他包裹一起出现在 PyPI 上。为了帮助分类和区分,包的作者可以给 PyPI 提供一个分类器的列表来对每个版本进行分类,描述它为谁服务,它可以在什么系统上运行,以及它有多成熟。然后,社区成员可以使用这些标准化的分类器,根据他们想要的标准来查找项目(尽管我不确定实际上是谁在做这件事🦆).所有可能的分类器的列表可以在这里找到;建议你先从这些入手:
-【开发状态::3—Alpha】
-【License::OSI Approved::MIT License】
-【编程语言::Python】
-【编程语言::Python::3.5】
-【编程语言::Python::3.6】
-【编程语言::Python::3.7】
-【主题::软件开发::库】
-【主题::软件开发::库::Python 模块】
-【目标受众::开发者】
好吧,那么!我们结束了!☃️
Figure 2: Ace Ventura after choosing his trove classifiers
步骤 5:构建分发文件
Python 包被构建到构建发行版文件中,然后这些文件被上传到服务器——通常是全球 PyPI 服务器——每个人都可以从那里下载它们。
我不会详细讨论不同的可能的发行版格式(你可以在这里阅读更多关于它们的内容和这里,但是我们将使用标准方法并构建两个文件:一个源发行版文件——它基本上是包代码的存档——和一个轮构建发行版文件。
首先,确保你安装了最新版本的setuptools
和wheel
:
python3 -m pip install --user --upgrade setuptools wheel
现在,要构建发行版文件,只需在您的setup.py
所在的存储库的根文件夹中运行以下命令:
python setup.py sdist bdist_wheel
这里发生的事情是,我们告诉 Python 解释器运行 setup.py Python 脚本,并向它发送两个参数,命令它构建一个源分发文件( sdist 参数)和一个轮构建分发文件( bdist_wheel 参数)。
运行该命令时,将在调用目录下创建三个文件夹:build
、dist
和chocobo.egg-info
。这三个都应该被你的.gitignore
文件忽略。如果这些目录已经存在——例如,在之前运行该命令时——最好通过运行rm -rf build dist
来删除它们,因为dist
下的任何有效包文件都将被上传。
我们将要上传的两个文件位于dist
文件夹中:chocobo-0.0.3-py-none.any.whl
(构建发行版;一个wheel
文件)和chocobo-0.0.3.tar.gz
(源码分发;一个 gzipped tar
存档)。确保它们已成功创建,我们将继续上传您的包!
步骤 6:上传您的包
现在剩下的就是把你的包上传到全球 PyPI 服务器!然而,要做到这一点,你必须在 PyPI 网站注册。按照注册步骤,记下您选择的用户名和密码。
如果你想在上传软件包到全球 PyPI 服务器之前测试它们的上传,你可以另外在测试 PyPI 网站注册一个用户。
现在,我们将用来上传我们的包的 Python 包将在一个名为.pypirc
的文本文件中寻找 PyPI 用户名和密码——以向 PyPI 服务器进行认证,该文件通常位于您的主文件夹中。创建它,并如下所示填充它(可选的testpypi
部分):
[distutils]
index-servers =
pypi
testpypi[pypi]
username: teapot48
password: myPYPIpassword[testpypi]
repository: [https://test.pypi.org/legacy/](https://test.pypi.org/legacy/)
username: teapot48
password: MYtestPYPIpassword
我们将按照最新推荐的 方式上传文件到 PyPI 服务器,并使用twine
上传 Python 包的实用程序,而不是旧的python setup.py upload
方法。只需运行:
twine upload dist/*
如果您想从使用测试 PyPI 服务器测试上传过程开始,只需运行twine upload — repository testpypi dist/*
。
在任何情况下,当您的.whl
文件被上传时,您应该会看到一个进度条,当.tar.gz
档案被上传时,您会看到第二个进度条,之后上传将会完成。
恭喜你!你现在可以在 PyPI 官方网站上看看你的全新 Python 包页面,让所有人都看到!这里有一个例子:
Figure 3: An example for a package page on the PyPI website
就是这样!你完了!各地的人们现在都可以运行pip install chocobo
并庆祝你的 Python 包的惊人创造!😃 ☃️
最后的话
我希望这篇文章有助于你理解如何构建和发布你的第一个 Python 包。如果你用它来打包一些代码,请在这里评论或者联系我让我知道!
还有一个官方的——简明的——打包 Python 项目的教程,可以在这里找到。我试图通过添加更多的细节和我在构建一些小型开源 Python 项目时获得的一些个人最佳实践来改进它。
最后,我希望在未来的一些帖子中触及一些其他相关主题:编写好的自述文件、Python 包的基本测试(我在这里写了一些)和包版本控制。干杯!
脚注
- 主要原因 twine 是 Python Packaging Authority 推荐的将包上传到 PyPI 的方式(以我的理解)——我将该推荐带到这篇博文的原因——是它“使用一个经过验证的连接通过 HTTPS 安全地验证你到 PyPI 的身份,而不管底层的 Python 版本如何”,这是一个非常好的理由——在我看来——更喜欢它并推荐给初学者。在缠绕文档的相应章节中阅读更多信息。
泵起来:使用数据科学预测水泵状况
现实世界中的数据科学
如何使用 Python 中的数据科学和机器学习来预测坦桑尼亚的水泵运行情况?
Pump image courtesy of Flickr user Christopher Jensen
DrivenData 的比赛以建设一个更美好的世界为背景。 Pump It Up 着眼于确定坦桑尼亚水泵的功能。在数据方面,数据是 38 个特征的逗号分隔值(CSV)文件,目标是预测三类中的一类(功能性、非功能性、功能性需要修复)。
代码并不太花哨,但是提供了一个很好的基线方法,可以对其进行改进,稍后会有更多的介绍。简而言之,你将对这个过程的每一步都有所了解,并在 70 秒内获得一个 f1 分数!
这篇文章将涵盖:
- 解读竞争
- 探索性数据分析
- 型号选择
- 估价
- 结论和提示
解读竞争
数据集的大小是合理的——59,400 个条目,所以我们不应该觉得有义务在我们的模型中使用所有 38 个特征!
F1 得分是评估一项技术表现如何的指标。这在数据不平衡时特别有用,因为类不是均匀分布的。
A table indicating the data imbalance in the training set.
F1 分数是一个调和平均值,考虑了精确度和召回率。数学上,它看起来像这样:
Precision 询问我们的模型在预测正面事件时的正确率,即:
另一方面,回忆询问我们的模型对积极事件的预测有多好:
F1 分数结合了这两个指标,如下所示:
探索性数据分析
我们已经看到了明显的阶级不平衡,但是我们现有的信息类型呢?当我们将它输入到模型中时,我们如何确保它得到适当的处理?
哪些栏目是多余的?现在,让我们忽略
‘date_recorded’, ‘gps_height’, ‘longitude’, ‘latitude’, ‘wpt_name’, ‘num_private’, ‘subvillage’, ‘lga’,’ward’, ‘recorded_by’, ‘extraction_type_group’, ‘extraction_type’, ‘scheme_name’, ‘management’, ‘waterpoint_type_group’, ‘source’, ‘source_class’, ‘quantity_group’, ‘quality_group’, ‘payment_type’
这给我们留下了剩余的 19 个特征:
- amount_tsh —总静压头(水点可用的水量
- 出资人——谁为油井出资
- 安装者——安装油井的组织
- 区域—地理位置
- 区域代码-地理位置(编码)
- 地区代码—地理位置(编码)
- 人口—油井周围的人口
- 公开会议—对/错
- scheme _ management——谁在运营供水点
- 允许—如果水点是允许的
- 建造年份——供水点建造的年份
- construction _ type 水点使用的提取类型
- construction _ type _ group 水点使用的提取类型
- construction _ type _ class 水点使用的提取类型
- 管理组—如何管理水点
- 付款——水的价格
- 水质——水的质量
- 数量——水的数量
- 水点类型—水点的种类
接下来,任何包含类似字符串/单词的信息的列都需要更改为数值,我们必须确保没有隐含的邻近性。为了实现这一点,我们使用了一个叫做一次热编码的过程。这将一列中所有不同的可能值(n)展开为 n 列,用 1 或 0 表示是否满足特定值。例如,quantity 列有五个可能的值:干旱、足够、不足、季节性和未知。一次热编码前后该特性的快照如下所示:
A table showing how the quantity column is transformed through one-hot-encoding.
funder 和 installer 列有大量的唯一值,因此我们可以使用一个函数将这些列扩展到前 n 个值(将 n 指定为函数中的一个参数)。不在这些值范围内的任何值都被标记为其他。这允许我们减少通过任何模型输入的列数,同时仍然表示大量的数据。
最后,状态组的标签需要改为数值,以便模型理解输入。将它们转换成有序类还是一次性编码,这是一个很难决定的问题。在这种情况下,我们使用的模型预期有一列,所以我们用序数转换来凑合,但是在某些情况下,这种隐含的接近性可能不合适。
这些步骤通过下面的函数进行总结。通过创建一个函数,我们可以调用它并使它适应任何版本的数据。状态组标签到数值的转换应该单独进行,因为这些仅针对训练数据。
Code for data pre-processing.
型号选择
当考虑使用哪个模型时,我们首先缩小到适合分类而不是回归问题的模型。在这种情况下,我们尝试六种不同的方法。
- 随机森林是一系列决策树,其中的叶节点表示预测的类别。
- XGBoost 是一种复杂的决策树算法,它使用梯度推进(通过查看变化率来最小化误差)。
- 决策树使用整个数据集为每个特征创建决策过程。
- k-最近邻查找与当前数据点最近的 k 个数据点,并采用多数分类。
- 支持向量机将创建平面作为区分类别的边界,试图最大化类别之间的距离。
- 逻辑回归也创建了决策边界,但却最小化了对数似然函数。
实现模型相对容易,导入相关的类,然后输入数据,接受初始探索的默认参数。
Code for model implementation.
估价
为了评估模型性能,我们留出了训练数据的子集,称为验证集。这使我们能够预测这部分数据的类别,并直接将预测结果与已知类别进行比较。对于每个模型,我们可以打印分类报告,该报告为我们提供分类指标,包括精确度、召回率、F1 分数和准确度。我们在验证集上实现了这一点,使用下面这条线比较真实标签和预测标签
print(classification_report(y_test,clf.predict(X_test)))
如前所述,在这种情况下,由于数据不平衡,准确性不是一个合适的指标,因此我们使用 F1 得分。
Graph showing the respective metrics for each type of model (0–1).
评估模型时的另一个考虑因素是运行时间。这与计算步骤成比例,并导致性能和速度之间的折衷。
Graph showing the respective run times for each type of model in seconds.
考虑到图形的组合结果,在给定训练数据的情况下,使用随机森林或决策树似乎是最合适的。但是,当模型预测测试数据的结果时,结果可能会有所不同。
结论和提示
对于这种应对挑战的方法,随机森林和决策树模型表现最佳,但是,通过超参数调整(查看设置每个模型的参数),所有模型都可以表现得更好。这个项目的所有代码和数据可以在这个 GitHub 文件夹中找到。
- 不要觉得必须使用数据集中给定的所有特征/变量。
- 尝试几种不同的型号。
- 考虑哪种指标最适合评估模型。
- 一旦你完成了一些初步的探索,通过创建函数来优化代码结构。
直到下一次…随时与我分享任何意见/问题!
参考
DrivenData。(未注明)。抽水:地下水位的数据挖掘。检索自https://www . driven data . org/competitions/7/pump-it-up-data-mining-the-water-table/
达尔,奥,埃纳霍罗,p,格林,s .,&斯塔克,A. (2019,10 月)。冲刺二队一队。从 https://github.com/shiaoligreen/practical-data-science取回
向上泵:预测坦桑尼亚的水点功能
我认为我们(或者至少我知道我肯定这样做)认为理所当然的东西是我们在家里饮用和使用的容易获得的水。我在佐治亚州东北部一个类似养鸡场的小地方长大,现在住在巴尔的摩,在这个国家能持续获得干净且随时可用的水确实令人惊讶。
直到最近,我参加了 Lambda School 举办的名为 Pump It Up 的 Kaggle 竞赛,我才真正思考过我的生活方式的含义。我们获得了由 Taarifa 和坦桑尼亚水利部收集的数据,以便预测供水点故障的可能性。该比赛由 DrivenData 主办,这是一个使用数据科学的社会公益平台。如果你有时间,一定要去看看。
比赛的目标是从训练数据中预测这些水点的状态。在我上面的地图里,可以看到水点的整体分布。没有功能的供水点几乎和有功能的供水点一样多,尽管这些供水点中有很小一部分被确定为需要修复。这意味着人们仍然想使用它们/它们还没有被另一个供水点取代,但是人们根本就无法获得这些水。
一点数据探索
我一开始只是想知道这些供水点是什么时候建成的。不幸的是,丢失的数据数量惊人。我收集了这些年来得到一个简单的图表,这就是结果。
如何处理这些缺失的数据?普遍的共识是估算数据,在这种情况下,您使用一些函数/模块根据您选择的参数(如平均值或中值)对数据进行有根据的猜测。然而,我选择了不同的做事方式。
我在数据中列出了所有可用的建造年份。然后,我根据这些年的计数建立了一个概率分布。因此,出现频率更高的年份出现的概率更高。在使用一些 python-fu 完成这一任务后,是时候规范化数据了。我们希望对概率分布的权重进行归一化,以便在下一步过程中将它们用作百分位数:从归一化概率分布的随机抽样中生成新的建筑年份。
在这样做之后,并采取一个新的 bin 的建设年,我的图表和信息的保真度,大大提高。
查看生成的数据,我们可以看到,在过去十年中,供水点建设出现了相当大的热潮。这种方法将我的模型从 53%的预测准确率提高到大约 75%。
我们还可以查看哪些地区的用水量增长/需求最大。
创建新特征
然后我就可以记录下水点最后一次观测的日期,并从新生成的数据中减去建造的年份,我称之为水点的年龄。这使我的准确率达到了 80%。
人口数据:将一个无关紧要的问题转变为模型焦点
人口中也有大量缺失数据。为此,我很乐意收集坦桑尼亚的人口普查数据,并生成如上的另一个概率分布来估算我的缺失值,但不幸的是,我只能替换为中位数(向低端人口倾斜的数据),因为我们只有四天时间来建立模型和进行可视化。输入缺失值的人口中值使我的准确率达到 82%(四舍五入)。人口最终成为我的模型的焦点。我所有的其他特征和直觉都建立在对人群的假设之上。因此,您可能会理解为什么我想要更准确地表示缺失的人口数据。
什么决定了水点故障的可能性。
在我的模型中,地理坐标(纬度/经度)、人口和对水点功能的估计之间存在令人难以置信的关系。
随着人口的增长,需要更多的水。这导致使用更多的水。人口成为水量的决定性值,随着水量的减少,我们开始看到水点失效的可能性更大。水点年龄的工程特征增加了我们的准确性,因为它允许模型了解到泵已经存在的时间越长,它不工作/由于上述原因需要修理的可能性就越大。这就像一个强化变量。
我觉得我的功能可以通过工程和对所有功能的更多研究来继续改进,也许可以更好地处理我的地理数据,但时间对任何人都是静止的。幸运的是,比赛在 Lambda 学校之外还有四个月的时间,所以我肯定会在业余时间继续努力改进我的模型。
同时,感谢您的阅读。
正如我最喜欢的库德的罗格里洞穴里的居民所说,“生活和喝酒!”
下次见。
提升音量:Docker 中的数据
学习足够有用的 Docker 的第 6 部分
这篇文章是关于在 Docker 中使用数据的。其中,我们将重点关注 Docker 卷。如果您还没有,请查看本系列的前几篇文章。我们讨论了 Docker 概念、生态系统、 Dockerfiles 、瘦身图片和流行命令。
Spices
把贯穿这些文章的食物隐喻推到断裂点,我们来对比一下 Docker 里的数据和香料。就像世界上有很多香料一样,用 Docker 保存数据也有很多方法。
快速参考:本指南适用于 Docker 引擎版本 18.09.1 和 API 版本 1.39 。
Docker 中的数据可以是临时的,也可以是持久的。让我们先检查一下临时数据。
临时数据
数据可以通过两种方式临时保存在 Docker 容器中。
默认情况下,由容器内的应用程序创建的文件存储在容器的可写层中。你不需要设置任何东西。这是又快又脏的方法。只要保存一个文件,然后继续你的工作。然而,当你的容器不复存在时,你的数据也将不复存在。
如果您希望 Docker 保存临时数据的性能更好,您还有另一个选择。如果您不需要您的数据在容器的生命周期之后继续存在,那么 tmpfs 挂载是一种使用主机内存的临时挂载。tmpfs 装载具有更快读写操作的优势。
很多时候,您会希望数据在容器消失很久之后仍然存在。您需要持久化您的数据。
不变数据
有两种方法可以将数据保存到容器寿命之外。一种方法是将文件系统绑定挂载到容器。通过绑定挂载,Docker 之外的进程也可以修改数据。
From the Docker Docs
绑定装载很难备份、迁移或与其他容器共享。卷是保存数据的更好方式。
卷
卷是位于任何容器之外的主机上的文件系统。卷由 Docker 创建和管理。体积为:
- 坚持的
- 自由浮动的文件系统,独立于任何一个容器
- 可与其他容器共享
- 有效的输入和输出
- 能够在远程云提供商上托管
- 可加密
- 可指名的ˌ可命名的
- 能够用容器预先填充它们的内容
- 便于测试
这是很多有用的功能!现在让我们来看看你如何制作一个卷。
Volumes
创建卷
可以通过 docker 文件或 API 请求来创建卷。
以下是在运行时创建卷的 Dockerfile 指令:
VOLUME /my_volume
然后,当创建容器时,Docker 将使用指定位置已经存在的任何数据创建卷。注意,如果使用 docker 文件创建卷,您仍然需要在运行时声明卷的挂载点。
还可以使用 JSON 数组格式在 Dockerfile 文件中创建一个卷。关于 Dockerfiles 的更多信息,请参见本系列的这篇早期文章。
还可以在运行时从命令行实例化卷。
卷 CLI 命令
创造
您可以使用docker volume create —-name my_volume
创建独立卷。
检查
用docker volume ls
列出 Docker 卷。
可以用docker volume inspect my_volume
检查卷。
去除
然后你可以用docker volume rm my_volume
删除这个卷。
悬挂卷是容器未使用的卷。您可以使用docker volume prune
移除所有悬挂的卷。Docker 会警告你,并在删除前要求确认。
如果卷与任何容器相关联,则在删除容器之前,您不能删除它。即使这样,码头工人有时也不会意识到集装箱已经不见了。如果发生这种情况,你可以使用[docker system prune](http://docker-compose down --volumes)
来清理你所有的 Docker 资源。那么您应该能够删除该卷。
Where your data might be stored
与 **--mount**
和 **--volume**
一起工作
您将经常使用标志来引用您的卷。例如,要在创建容器的同时创建卷,请使用以下命令:
docker container run --mount source=my_volume, target=/container/path/for/volume my_image
在过去(即 2017 年之前)😏--volume
旗很受欢迎。最初,-v
或--volume
标志用于独立容器,而--mount
标志用于码头工人群。然而,从 Docker 17.06 开始,您可以在所有情况下使用--mount
。
--mount
的语法有点冗长,但由于几个原因,它比--volume
更受欢迎。--mount
是您可以使用服务或指定卷驱动程序选项的唯一方式。用起来也比较简单。
您将在现有代码中看到许多-v
。注意--mount
和--volume
的选项格式不同。你通常不能用一个--mount
替换现有代码中的一个-v
就完事了。
最大的区别是-v
语法将所有选项组合在一个字段中,而--mount
语法将它们分开。让我们看看--mount
的行动吧!
Easy enough to mount
**--mount**
—选项是键值对。每一对的格式如下:key=value
,一对和下一对之间用逗号隔开。常见选项:
type
—安装类型。选项有[bind](https://docs.docker.com/storage/bind-mounts/)
、[volume](https://docs.docker.com/storage/volumes/)
或[tmpfs](https://docs.docker.com/storage/tmpfs/)
。我们都是关于volume
。source
—挂载的来源。对于命名卷,这是卷的名称。对于未命名的卷,将省略此选项。该键可缩短为src
。destination
—文件或目录在容器中挂载的路径。该键可缩短为dst
或target
。readonly
—将卷挂载为只读。可选。没有价值。
这是一个有很多选项的例子:
docker run --mount type=volume,source=volume_name,destination=/path/in/container,readonly my_image
Volumes are like spices — they make most things better. 🥘
包装
关键音量命令概述
docker volume create
docker volume ls
docker volume inspect
docker volume rm
docker volume prune
docker run --mount my_options my_image
中--mount
标志的常用选项:
type=volume
source=volume_name
destination=/path/in/container
readonly
现在,您已经熟悉了 Docker 中的数据存储,让我们看看您的 Docker 之旅可能的后续步骤。
Next steps
更新,我最近发表了一篇关于 Docker 安全的文章。看看它,并了解如何保持你的容器安全。😃
如果您还没有阅读本系列中关于 Docker 概念、 Docker 生态系统、Docker 文件、瘦图像和命令的文章,也可以查看一下。
如果你正在寻找另一篇关于 Docker 概念的文章来帮助巩固你的理解,请点击查看 Preethi Kasireddy 的伟大文章。
如果你想更深入,可以去看看奈杰尔·波尔顿的书Docker Deep Dive*(*一定要拿到最新版本)。
如果你想边学边做大量的建筑工作,可以看看詹姆斯·特恩布尔的《码头工人手册》。
我希望您发现本系列对 Docker 有所帮助。如果你有,请在你最喜欢的论坛或社交媒体渠道上与他人分享,这样你的朋友也可以找到它!😃
我写了一些关于用 Kubernetes 编排容器的文章,你可以在这里阅读。我写关于 Python、数据科学、人工智能和其他技术主题的文章。如果你对这些感兴趣,请关注我。
感谢阅读!👏
小狗和 Python:分析地理空间数据
通过使用 Geopandas、Shapely、Pyplot 和 Folium 组合多个形状文件和数据源来可视化西雅图宠物密度
所以你对有地理空间成分的数据感兴趣?酷!我也是!
当我开始我的第一个植根于地理空间数据的项目时,我并没有意识到我所面临的挑战的程度——并没有超出我的能力范围,但对于我当时的情况来说,这绝对是一个难题。
photo credit: Chris Martin on Flickr
我快速的谷歌搜索让我,或许还有你,直接找到了[geopandas](http://geopandas.org/)
。
This plus another 600+ lines of package conflicts… including NumPy.
我最初与geopandas
安装有很多包冲突,包括numpy
和我项目环境中的几乎所有东西。然后我按照他们的指示在conda-forge
频道的新环境中安装了geopandas
。非常管用!
在您的新环境中,您还需要conda install descartes
和matplotlib
来获得全部功能。
在今天的例子中,我正在为我的下一步行动做一些研究:在西雅图哪里可以找到密度最高的宠物?当我在未来的社区里散步、蹦蹦跳跳、做瑜伽时,我想向所有的宠物抛媚眼。为此,我从西雅图市的公开数据中下载了关于宠物执照的文件和关于邮政编码、市政边界和海岸线的形状数据(这样我们就可以看到我们所了解和喜爱的西雅图的轮廓)。
Photo Credit: Joshua T. Beck [CC BY-SA 4.0] on wikimedia commons
要点 1:shape file 不仅仅是一个文件
当我下载并解压缩我的第一个 shapefile 时,我只将.shp
文件移到我的目录中,心想,“也许这些其他文件可以在不同的程序中工作。”但是没有。
即使你用来读取形状文件的任何包可能只需要一个.shp
文件路径,它们需要整个文件集在同一个目录下才能工作:
.shp
—实际形状文件—这是你的形状的几何图形.shx
—形状索引 x 格式—帮助快速浏览形状.dbf
—这是关于形状的其他信息(同样,这是必需的,即使您不需要它。关于每个形状的参考信息(如名称)通常很有帮助。)
有时也有其他文件。查看这篇文章了解更多信息。现在,我不用太担心我想要或需要哪个,我只需要将它解压到我的目标目录中。然后你就知道你的数据源提供了什么好东西。
还要注意:所有文件必须有相同的文件名,唯一的区别是扩展名/后缀。所以如果你喜欢改文件名去掉空格,大写字母等(只是恢复完美主义的事情?),确保您更改了软件包中所有文件的名称,并注意拼写错误。
当你考虑一个 shapefile 的时候,总是要加上那个尾随的 s——shape files。
在搜索最大 pet 密度时,只需几行代码就可以让我开始使用地理数据框中的形状:
import geopandas as gpd# load zip code shape data
zip_codes_gdf = gpd.read_file('data/shapes/Zip_Codes.shp')# load seattle shape
cities = gpd.read_file('data/shapes/Municipal_Boundaries.shp')
seattle = cities.loc[cities['CITYNAME'] == 'Seattle']
seattle = seattle.reset_index()
seattle_shp = seattle.loc[0, 'geometry']# load seattle waterfront shape
waterfront = gpd.read_file('data/shapes/
City_of_Seattle_Shoreline.shp')
注意这里的seattle_shp
是一个shapely Polygon
,在 Jupyter 笔记本中调用它会显示该形状的图像。或者,waterfront
是一个地理数据框架,因此您可以用waterfront.plot()
来可视化它。跟踪对象类型将为您省去麻烦和时间!或者至少是当事情不像你期望的那样工作时的一个好的开始。
首先,我注意到西雅图开放数据上的邮政编码文件实际上有华盛顿几个县的邮政编码的形状文件。我需要把邮编范围缩小到西雅图。我很快意识到西雅图地区的邮政编码边界与城市边界不一致。
这里是geopandas
对所有其他包的包装,包括shapely
,派上用场的地方(即使它使安装变得很困难)。
拿走#2: Shapely 是你的朋友
为了组合来自不同来源的数据并准确获得您想要的数据,我发现 shapely 函数非常有用。方便的是,它们在GeoDataFrames
上工作得很好,在每行上用一行代码进行计算(只需调用geo_df.**shapely_function**()
)。一些[shapely](https://shapely.readthedocs.io/en/stable/manual.html)
功能在[geopandas](http://geopandas.org/)
文档中有描述,但是geopandas
没有包括所有 shapely 功能的文档。当 shapely docs 中描述的额外功能,而不是geopandas
中描述的功能工作良好时,我感到惊喜,尤其是intersection
!
在:
object.**within**(*other)*
—如果对象的边界和内部仅与另一个对象的内部相交(而不是其边界或外部),则返回True
。这适用于所有类型,与[**contains()**](https://shapely.readthedocs.io/en/stable/manual.html#object.contains)
相反。 *
所以我试了一下。这里,zip_codes_gdf.within(seattle_shp)
返回一系列的True
和False
,每个记录对应于GeoDataFrame
中的一条记录。这个系列与.loc
一起使用,创建一个新的GeoDataFrame
,只包含全部在西雅图的记录(邮政编码及其形状)。
seattle_zips_within = zip_codes_gdf.loc[zip_codes_gdf.within(seattle_shp)]
接下来,我绘制了我的结果,以及西雅图的轮廓和滨水区。
fig, ax = plt.subplots(figsize=(10,10))
seattle.plot(ax=ax, color='white', edgecolor='black', linewidth=2)
waterfront.plot(ax=ax, color='blue')
seattle_zips_within.plot(ax=ax, cmap='Paired', alpha=.8)
使用within()
函数,进入西雅图以外其他城市的邮政编码被排除在这个选择过程之外。如果邮政编码与城市边界一致,这个功能会很棒。不幸的是,事实并非如此,所以我继续寻找更好的邮政编码选择功能。
相交
object.**intersects**(*other*)
—如果对象的边界或内部以任何方式与其他对象相交,则返回True
。换句话说,如果几何对象有共同的边界或内点,则它们相交。 *
seattle_zips_intersects = zip_codes_gdf.loc[zip_codes_gdf.intersects(seattle_shp)]fig, ax = plt.subplots(figsize=(10,10))
waterfront.plot(ax=ax, color='blue')
seattle_zips_intersects.plot(ax=ax, cmap='Paired', alpha=.8)
seattle.plot(ax=ax, color='#00000000', edgecolor='black',
linewidth=2)
现在我们走上了正轨,整个西雅图都被代表了。现在我想把城市范围之外所有区域都去掉。如果我使用西雅图市范围内注册的宠物数量和这些跨境邮政编码的整个(西雅图和非西雅图)土地面积来计算宠物密度,这将低估这些西雅图社区的宠物密度。
Photo Credit: George Hodan [CC0 Public Domain] from PublicDomainPictures
所以我寻找另一个shapely
函数。
路口
object.**intersection**(*other*)
—返回该对象与其他几何对象的交集的表示。 *
换句话说,它返回两个对象重叠区域的形状。
seattle_zips_intersection = zip_codes_gdf.intersection(seattle_shp)fig, ax = plt.subplots(figsize=(10,10))
waterfront.plot(ax=ax, color='black')
seattle_zips_intersection.plot(ax=ax, cmap='Paired', alpha=.8,
edgecolor='gray')
seattle.plot(ax=ax, color='#00000000', edgecolor='black',
linewidth=2)
我还不确定地图上的粉红色是怎么回事,但这看起来越来越好了!随着邮政编码被修剪到西雅图内的区域,就地图绘制而言,我准备去掉标记胡德运河中心的西雅图轮廓,或者西雅图技术上结束的任何地方,取而代之的是水路和邮政编码轮廓。
准备好地图形状后,我就可以使用 pet 数据了!
第三点:Geopandas.plot()使用了 matplotlib.pyplot 中几乎所有你知道和喜欢的参数
首先,我将上一步中确定的新相交几何图形添加到我的GeoDataFrame
中。注意:默认情况下,Geopandas 总是绘制“几何图形”列,以绘制您的形状或完成如上所用的基于形状的计算。您可以使用geo_object.**set_geometry**(*‘column_name')*
切换哪个列geopandas
将用作感兴趣的几何图形。在这种情况下,我决定用一个新的、成对的 down GeoDataFrame
重新开始——只有西雅图的邮政编码及其交集几何和面积。
# add intersection geometry to my dataframe
zip_codes_gdf['intersection'] = seattle_zips_intersection# create dataframe with the seattle in-city geometries
seattle_zip_geom = zip_codes_gdf.loc[:, ['ZIP', 'intersection']]# name the geometry column 'geometry' for easy geopandas plotting
seattle_zip_geom.columns = ['ZIP', 'geometry']# calculate the in-seattle area for each zipcode and add as a column
seattle_zip_geom['area'] = seattle_zip_geom.area# drop zip codes that have no area in seattle
seattle_zip_geom = seattle_zip_geom.loc[seattle_zip_geom['area'] >
0]
为此,我添加了宠物数量和宠物密度,只使用相交面积来计算密度。
# create dataframe with Seattle zips and their respective pet counts
seattle_pets_zip = seattle_zip_geom.merge(pet_counts, on='ZIP')# add pet_density column
seattle_pets_zip['pet_density'] = (seattle_pets_zip['pets'] /
seattle_pets_zip['area'])
要根据列'pet_density'
对地图进行着色,将该列设置为 plot 调用中的列名。这种基于数据值着色的地图被称为 choropleth (vocab,我希望我以前就知道……用更精确的关键字搜索帮助效果更好…)
fig, ax = plt.subplots(figsize=(10,10))
waterfront.plot(ax=ax, color='black')
seattle_pets_zip.plot(**column='pet_density'**, ax=ax, edgecolor='gray')
啊!所有的邮政编码看起来都一样!
然后,我注意到两个邮政编码(98146,98168)的宠物密度要大得多,尽管在我的地图比例下看不到它们。
为了更仔细地研究这些地点,我用folium
将它们绘制在城市街道上:
import folium
import geopandas as gpddef create_geo_map(geo_df, index, location, zoom=17,
tiles='Stamen Terrain'):
"""creates a folium map with the geometry highlighted
Args:
geo_df: a geopandas dataframe (must have a column 'geometry'
index: the index of the row you want mapped
location: [lat, long] list with the center location for the map
zoom: start zoom level. Default: 17
tiles: Folium tiles / design. Default: Stamen Terrain Returns:
folium map with geometry highlighted
"""
# create geodataframe for zip code
zip_code = gpd.GeoDataFrame(geo_df.iloc[index]).T # get the geojson for the zipcode
gjson = zip_code['geometry'].to_json() # create folium map
m = folium.Map(location=location, tiles=tiles, zoom_start=zoom) # add geojson to the map
folium.GeoJson(gjson, name='geojson').add_to(m)
return m
看看这两个邮政编码:
第一个是西雅图市中心的一个街区,紧挨着图书馆,里面有一栋高层公寓。我不知道为什么这个街区有自己的邮政编码,但是好吧。我能相信这里登记了 35 只宠物。
第二个更神秘。更大的邮政编码中的这一小部分是波音机场附近的工业区。有一个超级价值,但是在谷歌地图卫星视图上没有可见的房屋(是的,我试图跟踪这 35 只宠物——你可以自由地亲自查看。目前,我的工作假设是这些是超价值工作者的情感支持动物。)
Example emotional support animal. Photo Credit: Kevin Honold, used with permission
经过进一步调查,邮政编码 98168 在未合并的国王县有一些地区,这些地址可能标有“西雅图”,但从技术上来说不在西雅图市的范围内。我想知道,在这个未合并的地区,一些非西雅图居民的宠物主人是否在该市登记了他们的宠物,而没有意识到他们在技术上不是该市居民。你可能会认为这个城市不会接受非城市地址的注册……这仍然是个谜。
无论如何,我选择放弃这两个邮政编码,以便更大的邮政编码的宠物密度更容易区分。在geopandas
文档的帮助下,我还添加了图例。
# adding a legend using code from [http://geopandas.org/mapping.html](http://geopandas.org/mapping.html)
**from mpl_toolkits.axes_grid1 import make_axes_locatable**fig, ax = plt.subplots(figsize=(10,10))
**divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.1)**
waterfront.plot(ax=ax, color='black')
seattle_pets_zip_dropped.plot(column='pet_density', ax=ax,
**legend=True**, **cax=cax**);
所以现在我有了一个很好的主意,最适合和最不适合看宠物的社区!我确实有一些挥之不去的问题:如果我们把人口密度标准化呢?我们谈论的是什么类型的宠物——这张地图对于猫/狗/山羊/猪来说会有所不同吗(是的,山羊和猪在城市里注册了!)?但是,这些问题将不得不等待另一天…即将来到你附近的数据科学博客。
Are these your neighbors? Photo Credit: Kevin Honold, used with permission
分模提示:在显示图形之前,使用plt.savefig('fig_name.png')
保存图像,以避免保存空白画布。例如,所有功能都包含在一个 jupyter 笔记本电池中:
fig, ax = plt.subplots(figsize=(10,10))
waterfront.plot(ax=ax, color='blue')
plt.savefig('images/seattle_zips_intersects.png');
您仍然可以显示图像并保存。
你可以在我的 GitHub repo 这里看到完整的代码。
*来自身材匀称的医生的描述。
**更新:**追加安装descartes
和matplotlib
。感谢您关注沙希德·纳瓦兹·汗!
使用 Spring Boot 将数据放入亚马逊 Kinesis 消防水管交付流
最初发表于我的个人博客。
如果你处理需要收集、转换和分析的大数据流,你肯定会听说过亚马逊 Kinesis Firehose。它是一种 AWS 服务,用于将数据流加载到数据湖或分析工具,以及压缩、转换或加密数据。
你可以使用 Firehose 将流数据加载到 S3 或红移中。从那里,您可以使用 SQL 查询引擎,如 Amazon Athena 来查询这些数据。您甚至可以将这些数据连接到您的 BI 工具,并获得数据的实时分析。这在需要实时数据分析的应用中非常有用。
在这篇文章中,我们将看到如何在 Kinesis Firehose 中创建一个交付流,并编写一段简单的 Java 代码将记录(生成数据)放入这个交付流。我们将设置 Kinesis Firehose 将传入的数据保存到亚马逊 S3 的一个文件夹中,该文件夹可以添加到一个管道中,您可以使用 Athena 查询它。仔细想想,当事情失去控制时,你真的会使你的管道变得复杂,并在将来遭受损失。建议在项目的架构阶段后退一步,分析所有管道可能导致瓶颈的地方。的确,大多数 AWS 服务都是完全托管和自动扩展的。但是,控制您的架构并以这样一种方式设计它,使管道不会成为您项目中最薄弱的结构,这不会有什么坏处。不管怎样,我们继续吧。
在 Kinesis Firehose 中创建交付流
要开始向 Kinesis Firehose 交付流发送消息,我们首先需要创建一个。为此,让我们登录 AWS 控制台,并前往 Kinesis 服务。您应该会在 Kinesis 主页上看到一个创建新的消防软管交付流的按钮。希望你找到了那个按钮。创建交付流包括四个步骤。让我们一个接一个地看看它们:
第一步:名称和来源
这里,我们指定了流的名称,以及流如何获取数据,这是源部分。为该流选择一个名称。该页面应该类似于下面的截图:
接下来,我们必须指定这个流的数据源。有两种方法可以将数据放入流中:
- 使用 AWS 提供的 Put data APIs,它可以集成到您的应用程序中,直接向流发送数据。
- 从另一个数据流传输数据。
在这个例子中,我们将使用第一个选项,直接看跌期权或其他来源。选择此选项,然后单击页面底部的“下一步”进入第二步。
第二步:流程记录
Kinesis Firehose 的众多功能之一是,它可以在将传入数据发送到目的地之前对其进行转换。在第二步中,我们可以告诉 Kinesis 它与这个流的传入数据有什么关系。现在,我们将保持一切默认。这意味着没有任何形式的转换,也没有数据格式的转换。理想情况下,您会希望将数据转换为 Parquet 格式,这样既可以压缩数据,又可以提高处理效率。保持这两个配置处于禁用状态,然后单击页面底部的 Next 按钮。
第三步:选择目的地
既然我们已经配置了 Firehose 来获取数据,现在我们必须告诉它将数据发送到哪里。有四个选项:
- 亚马逊 S3
- 亚马逊红移
- 亚马逊弹性搜索服务
- Splunk
每个目标都有自己配置。因为我们现在并不真正担心其他任何事情,只是想将数据发送到 Firehose,所以我们将数据保存到 S3,这也是默认选项。
向下滚动,您会发现提供一个 S3 存储桶来存储数据的选项。您可以选择现有的存储桶,也可以创建新的存储桶。
接下来,我们有前缀选项。所以 Firehose 写给 S3 的任何东西都会被默认分区,格式为“YYYY/MM/DD/HH”(UTC)。您当然可以覆盖它,但是您的前缀中必须至少有一个时间戳。如果你没有提供一个带有时间戳的前缀,Firehose 会自动添加一个,因为这是强制的。此外,您不能添加一部分传入数据作为前缀。所以不要打算这样设计。现在,让它为空或者给出一个随机的字符串。
类似地,你也可以给错误一个前缀。我会让它暂时为空,因为这只是一个概念验证。单击页面底部的“下一步”按钮。我们将进入流程的下一步。
第四步:配置设置
在这一步中,我们将为我们数据在 S3 中的存储方式指定一些重要的配置。我将对每一项进行解释,但我建议在此 POC 中保留它们的默认设置。
首先,我们有缓冲条件。这里有两个配置,缓冲区大小和缓冲区间隔。Kinesis Firehose 在将传入数据写入 S3 之前对其进行缓冲。将收到的每一条信息写给 S3 将会非常昂贵。因此数据被缓冲并批量写入 S3。您可以指定缓冲区大小,例如 5MB,以及缓冲区间隔,例如 300 秒。所以无论哪个先发生都会被考虑。例如,如果缓冲区在短短 10 秒内达到 5MB 的大小限制,数据将被写入 S3,缓冲区将被清除。此外,如果数据还不到 5MB,但自上次写入以来已经过了 300 秒,则缓冲区中的任何数据都将被写入 S3,缓冲区将被清除。
接下来,我们可以选择在 S3 压缩和加密数据。我们可以使用压缩算法,如 GZIP 和 SNAPPY 来压缩数据。当我们处理千兆字节的数据时,会考虑到这一点。对于此 POC,请将其禁用。我们现在也不需要任何加密。
如果有必要,我们可以启用错误日志。或许还可以添加一些标签。但是我不认为他们中的任何一个需要这个概念验证。所以让我们直接翻到这一页的底部。
这里,我们需要指定一个 IAM 角色,该角色有权向 S3 写入数据。Kinesis Firehose 将使用这个 IAM 角色将传入数据写入 S3。您可以选择现有的 IAM 角色,也可以创建新的 IAM 角色。选择此项后,单击页面底部的“下一步”按钮。
您现在应该已经创建了交付流。我们已经完成了流的创建。我们可以转到 Java 代码,我们将使用它向这个流发送数据。
Java 代码
在我们进入代码之前,像往常一样,让我们看一下我们需要的依赖项。我为此创建了一个 Maven 项目。所以我有一个 pom.xml 文件用于依赖管理。我已经列出了我在这里使用的所有依赖项:
<dependencies> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk</artifactId>
<version>1.11.627</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>amazon-kinesis-client</artifactId>
<version>1.11.2</version>
</dependency>
<dependency>
<groupId>org.codehaus.jettison</groupId>
<artifactId>jettison</artifactId>
<version>1.2</version>
</dependency></dependencies>
接下来,使用 AWS SDK,我们需要创建一个 AmazonKinesisFirehose 客户端,它将用于将数据或记录放入 Firehose 交付流。为此,我们首先需要创建 AWS 凭证对象。对于这个例子,我使用的是 BasicAWSCredentials 类。这意味着,我们必须提供访问密钥和访问秘密。我们将把这些数据保存在 application.properties 文件中,这样我们就可以将这些数据放入 Java 代码中,而无需硬编码。因为这是一个 Spring Boot 应用程序,所以我们可以为此使用 @Value() 注释:
@Value("${aws.auth.accessKey}")
private String awsAccessKey;@Value("${aws.auth.secretKey}")
private String awsSecretKey;
使用这两个变量,我们将创建凭证对象和 Firehose 客户端:
BasicAWSCredentials awsCredentials = new BasicAWSCredentials(awsAccessKey, awsSecretKey);AmazonKinesisFirehose firehoseClient = AmazonKinesisFirehoseClient.builder()
.withRegion(Regions.US_WEST_2)
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
.build();
请确保您更改了上面代码片段中的区域,以匹配您的 Kinesis 设置。一旦我们创建了这个 Firehose 客户端,我们就可以开始向 Firehose“放记录”了。但是让我们来看看我们将要输入的数据。
JSONObject messageJson = new JSONObject();
messageJson.put("key1", "We are testing Amazon Kinesis Firehose!");
messageJson.put("integerKey", 123);
messageJson.put("booleanKey", true);
messageJson.put("anotherString", "This should work!");logger.info("Message to Firehose: " + messageJson.toString());
如您所见,我创建了一个简单的 JSON 对象,它将被序列化,转换成字节,然后发送到 Firehose。在我们实际发送数据之前,我们必须创建一个 PutRecordRequest 对象,使用它我们将指定向哪个流发送数据。流名称不是硬编码的,但是我们从application . propertiets文件中获取它,类似于 AWS 凭证:
@Value("${aws.kinesis.firehose.deliveryStream.name}")
private String fireHoseDeliveryStreamName;
我们现在将创建 PutRecordRequest:
PutRecordRequest putRecordRequest = new PutRecordRequest();
putRecordRequest.setDeliveryStreamName(fireHoseDeliveryStreamName);
接下来,我们将创建一个记录对象,它将保存将要发送的实际数据。我们将这个记录设置为我们刚刚创建的 putRecordRequest 对象,并调用*。Firehose 客户端对象上的 putRecord()* 方法。
Record record = new Record().withData(ByteBuffer.wrap(messageJson.toString().getBytes()));
putRecordRequest.setRecord(record);PutRecordResult putRecordResult = firehoseClient.putRecord(putRecordRequest);
就是这样。如果您没有从 AWS 客户端得到任何错误,您可以确信您发送的数据将在您的 S3 桶中。去看看。
如果你对这个例子中完整的 Spring Boot 项目感兴趣,可以去我的 Github 库看看,也许可以分支一下?
在 Twitter 上关注我,了解更多数据科学、机器学习,以及一般技术更新。此外,你可以关注我的个人博客,因为我在那里发布了很多我的教程、操作方法帖子和机器学习的优点。
把 AI 放在第一位
Image credit : cio.com
如何将人类和机器智能结合起来,为企业带来实实在在的好处?
上个月在达沃斯,人工智能被第四次工业革命和以人为中心的人工智能的持续喧嚣所掩盖,几乎每个与会的首席执行官都可以读到人工智能。【1】与技术介绍的多样性形成鲜明对比的是,人们似乎有一个共同的想法:人工智能对商业和工业来说都是一个巨大的机会。在这种兴奋的阴影下,两个基本问题的答案似乎在闲聊中消失了:组织将如何实施人工智能,以及如何将人类和机器智能结合起来,为企业带来切实的好处?在这篇文章中,我们将区分人工智能和机器学习,首先讨论人工智能的概念,并提出我们的人工智能路线图,以展示如何将数据科学带到桌面上来,使组织在今天和明天受益。
人工智能与组织在过去十年中利用的机器学习有什么不同?如果这两种愿景都建立在数据科学的进步之上,那么这两种方法在目标、方法和评估指标上有着根本的不同。就像 HTML 和 XML 在编写网页方面的差异一样,人工智能超越了数据中可见内容的前提,探索了人类交互背后的逻辑和伦理。传统的数据科学利用机器学习,通过检查数据、特征和实验来阐明人类知识,而人工智能的目标是揭示人类智能背后的认知推理。机器学习的目标是预测与测试数据集中的模式一致的新数据点,而人工智能的主旨是解决复杂的(非线性甚至混乱的)商业问题。机器学习的价值主张是提高一个组织预测未来的能力,而人工智能的愿景是阐明每个决策环境的最佳答案。
将人工智能作为商业战略放在第一位将要求管理层放弃以前利用 IT 硬件和软件的观念。AI First 不是承诺支持手机或云等技术平台,而是投资一个组织的能力来解决未来的业务挑战。对 IT 的投资不能再被归类为硬件、软件和专有数据,而是对人力资本的基础设施投资,与构建业务本身的投资没有区别。数据不再被视为一种资源,而是一种独特的资产,必须对其进行评估、丰富,并与组织开展业务的方式密切相关。AI 首先意味着放弃提高运营效率的正统观念,转而采用不断实验的思维模式,并接受创新者困境的悖论。人工智能首先作为一种商业战略,并不是一切照旧,而是一种有意识的选择,以改变组织及其生态系统之间的几个边界——这不仅需要改变思维模式,还需要改变交易条款和我们做生意的标准。
虽然我们今天很方便地区分了有限、一般和超级人工智能,但我们很少将我们的人工智能愿景与我们商业战略的具体细节联系起来。姚为我们提供了一个人工智能的连续体,来理解为什么组织投资人工智能的不同观点的含义。【3】在竞技场的一端,人工智能的目标可以简单地是比人类更快、更便宜或更有效地执行简单的人类任务。在该领域的另一端,人工智能可以用来建立认知系统,以应对商业(或社会)生态系统的未来挑战。在这两者之间,可以张贴旗帜来鼓励预测、学习、创造和掌握人类行为的复杂性。经理和他们的数据科学团队可以在一定程度上玩人工智能游戏,因为他们知道他们的利益相关者在哪里调整了目标。
白的人工智能路线图
组织将如何利用人工智能将数据转化为集体行动?无论是经理还是客户都不会根据数据采取行动,而是根据他们对数据如何代表他们面前的挑战和机遇的看法采取行动。在我们的人工智能路线图中,我们展示了客户和经理如何做出运营决策,并指出了机器学习和人工智能可以发挥作用的地方。该路线图着眼于感知的四个基本要点:识别、预测、评估和行动,以及每个要点如何根据手头的数据来制约我们的执行、发明和创新能力。基于特定的业务环境和逻辑,在每个方向上探索匹配人类和机器智能的挑战和机遇。
我们将基于真实世界的例子,在 BAI 在大波士顿地区举办的春季课程“人工智能管理”、在 BAI 在法国巴约纳举办的夏季课程“数据科学实践”以及在印度迈索尔举办的秋季课程“数据科学伦理”中,详细探讨这些论点。商业分析实践是商业分析学院的核心和灵魂。通过关注数字经济、组织决策、机器学习和人工智能的管理挑战,白让分析为您和您的组织工作。
李·施伦克博士
2019 年 2 月 16 日
【1】k . Roose(2019 年 1 月 25 日),“达沃斯精英们隐藏的自动化议程 ,”《纽约时报》
克莱顿·克里斯滕森(2015 年 12 月 15 日)。《创新者的困境:当新技术导致大公司失败的时候》。哈佛商业评论出版社
【姚,m .(2017 年 10 月 16 日)机器智能连续体,中
将 ML 投入生产 I:在 Python 中使用 Apache Kafka。
这是两个帖子中的第一个,我们将展示如何使用一系列工具(主要是卡夫卡和 MLFlow )来帮助制作 ML。为此,我们将设置一个简单的场景,我们希望它类似于一些真实的用例,然后描述一个潜在的解决方案。所有代码的配套回购可以在这里找到。
场景
一家公司使用一系列服务来收集数据,当用户/客户与该公司的网站或应用程序交互时,这些服务会生成事件。当这些交互发生时,算法需要实时运行**,并且需要根据算法的输出(或预测)立即采取一些行动。最重要的是,在 N 次交互(或观察)之后,算法需要重新训练而不停止预测服务 ,因为用户将保持交互。
在这里的练习中,我们使用了成人数据集,目标是根据个人的年龄、祖国等来预测个人收入是否高于/低于 50k。为了使这个数据集适应前面描述的场景,可以假设年龄、国籍等是通过在线问卷/表格收集的,我们需要实时预测用户的收入是高还是低。如果收入高,我们会立即打电话/发电子邮件给他们,提供一些优惠。然后,在新的观察之后,我们重新训练算法,同时继续预测新用户。
解决方案
图 1 是一个潜在解决方案的示意图。为了实现这个解决方案,我们使用了 Kafka-Python (一个不错的教程可以在这里找到),以及 LightGBM 和hyperpt或 HyperparameterHunter 。
Figure 1. Real-time prediction ML pipeline. A full description is provided below
在本练习中,我们将使用的唯一 Python“局外人”是 Apache-Kafka (我们将使用 python API Kafka-Python,但 Kafka 仍然需要安装在您的系统中)。如果你用的是苹果电脑,就用自制软件:
brew install kafka
这也将安装 zookeeper 依赖项。
如前所述,我们使用了成人数据集。这是因为我们的目的是说明一个潜在的 ML 管道,并提供有用的代码,同时保持它相对简单。但是请注意,这里描述的管道原则上是数据不可知的。当然,预处理将根据数据的性质而变化,但是管道组件即使不完全相同,也会保持相似。
初始化实验
这篇文章使用的代码(以及更多)可以在我们的报告中找到。在那里,有一个名为initialize.py
的剧本。该脚本将下载数据集,设置 dir 结构,预处理数据,在训练数据集上训练初始模型,并优化该模型的超参数。在现实世界中,这将对应于通常的实验阶段和离线训练初始算法的过程。
在这篇文章中,我们希望更多地关注管道和相应的组件,而不是 ML。尽管如此,让我们简单地提一下我们在这个练习中使用的 ML 工具。
给定我们正在使用的数据集,数据预处理相当简单。我们编写了一个名为[FeatureTools](https://github.com/jrzaurin/ml_pipelines/blob/master/utils/feature_tools.py)
的定制类,可以在回购的utils
模块中找到。这个类有.fit
和.transform
方法,它们将标准化/缩放数字特征,编码分类特征,并生成我们所说的“交叉列”,这是两个(或更多)分类特征之间的笛卡尔乘积的结果。
处理完数据后,我们使用 LightGBM 拟合模型,并使用 Hyperopt 或 HyperparameterHunter 进行超参数优化。与这个任务相关的代码可以在train
模块中找到,在这里可以找到两个脚本[train_hyperop](https://github.com/jrzaurin/ml_pipelines/blob/master/train/train_hyperopt.py).py
和[train_hyperparameterhunter](https://github.com/jrzaurin/ml_pipelines/blob/master/train/train_hyperparameterhunter.py).py
。
我们可能会写一个单独的帖子来比较 python 中超参数优化包( Skopt ,Hyperopt 和 HyperparameterHunder),但是现在,要知道:如果你想要速度,那么使用 Hyperopt。如果你不关心速度,你想保持你的优化程序的详细跟踪,然后使用超参数猎人。用这个包的创造者亨特麦古西翁的话说:
“长期以来,超参数优化一直是一个非常耗时的过程,只是为您指明了进一步优化的方向,然后您基本上不得不重新开始。”
HyperparameterHunter 就是为了解决这个问题,而且做得非常好。目前,该软件包是建立在 Skopt 之上的,这就是为什么它明显比 Hyperopt 慢的原因。然而,我知道有人在努力将 Hyperopt 作为 HyperparameterHunter 的另一个后端。当这种情况发生时,将不会有争论,超参数猎人应该是你的选择工具。
尽管如此,如果有人感兴趣,我在回购中包括了一个笔记本,比较 Skopt 和 Hyperopt 的性能。
现在让我们转到管道流程本身。
应用程序消息制作者
这是生产管道的一个相对简单的例子。因此,我们直接使用成人数据集来生成消息(JSON 对象)。
在现实世界中,可能会有许多服务生成事件。从那里,一个人有几个选择。这些事件中的信息可能存储在数据库中,然后通过您的常规查询进行汇总。从那里,Kafka 服务将消息发布到管道中。或者,这些事件中的所有信息可以直接发布到不同的主题中,一个“聚合服务”可以将所有信息存储在一个消息中,然后该消息将被发布到管道中(当然,也可以是两者的组合)。
例如,用户可以通过脸书或谷歌注册,收集他们的姓名和电子邮件地址。然后,他们可能会被要求填写一份调查问卷,我们会随着事件的进展不断收集信息。在这个过程中的某个时刻,所有这些事件将被聚合在一条消息中,然后通过 Kafka 制作人发布。本文中的管道将从所有相关信息聚集的地方开始。我们这里的信息是成人数据集中的个人观察。下面是我们信息内容的一个例子:
’{“age”:25,”workclass”:”Private”,”fnlwgt”:226802,”education”:”11th”,”marital_status”:”Never-married”,”occupation”:”Machine-op-inspct”,”relationship”:”Own-child”,”race”:”Black”,”gender”:”Male”,”capital_gain”:0,”capital_loss”:0,”hours_per_week”:40,”native_country”:”United-States”,”income_bracket”:”<=50K.”}’
应用/服务(图 1 中最左边的灰色方框)的核心是下面的代码片段:
请注意,我们使用测试数据集来生成消息。这是因为我们设计了一个尽可能接近真实世界的场景(在一定范围内)。考虑到这一点,我们已经使用训练数据集来构建初始的model
和dataprocessor
对象。然后,我们使用测试数据集来生成消息,目的是模拟随着时间的推移接收新信息的过程。
关于上面的片段,简单地说,生产者将消息发布到管道中(start_producing()
),并将使用最终预测(start_consuming()
)来消费消息。同样,我们在这里描述的管道不包括流程的最开始(事件收集和聚合),我们也跳过最结尾,即如何处理最终预测。尽管如此,我们还是简要讨论了一些用例,在这些用例中,这个管道在本文的末尾会很有用,这将说明最后的阶段。
实际上,除了忽略过程的开始和结束,我们认为这个管道与现实世界中使用的管道相当相似。因此,我们希望我们回购中包含的代码对您的一些项目有用。
预测器和训练器
该实现的主要目标是实时运行该算法,并且每隔 N 次观察重新训练该算法,而不需要停止预测服务。为此,我们实现了两个组件,预测器(回购中的[predictor](https://github.com/jrzaurin/ml_pipelines/blob/master/predictor.py).py
)和训练器**([trainer](https://github.com/jrzaurin/ml_pipelines/blob/master/trainer.py).py
)。******
现在让我们使用代码片段作为指导方针,逐一描述图 1 中显示的数字。注意,下面的过程假设用户已经运行了initialize.py
脚本,因此初始的model.p
和dataprocessor.p
文件存在于相应的目录中。此外,强调下面的代码包括预测器和训练器的核心。完整代码请参考回购。
预测器
预测器的核心代码如下所示
predictor.py
片段中的 (1a) 第 12 行。预测器将接收来自应用程序/服务的消息,它将进行数据处理,并在接收消息时实时运行模型。所有这些都是通过使用predict
函数中现有的dataprocessor
和model
对象来实现的。
(1b)predictor.py
片段中的第 13 行。一旦我们运行了预测,预测器将发布最终将被应用/服务接收的结果(publish_prediction()
)。
(2 ) 代码片段中的第 17–20 行。每收到一条RETRAIN_EVERY
消息,预测器将发布一条“重新训练”消息(send_retrain_message()
)供训练员阅读。
培训师
(3)trainer.py
片段中的第 12 行。训练器将读取该消息,并用新的累积数据集触发再训练过程(train()
)。这是,原始数据集加上RETRAIN_EVERY
的新观测数据。训练功能将独立于 1a 和 1b 中描述的过程,运行“初始化实验” 部分中描述的整个过程。换句话说,当消息到达时,训练器将重新训练模型,而预测器继续提供预测。**
在这个阶段,值得一提的是,我们在这里发现了我们的实现与现实世界中使用的实现之间的进一步差异。在我们的实现中,一旦已经处理了RETRAIN_EVERY
个观察值,就可以重新训练算法。这是因为我们使用成人测试数据集来生成消息,其中包括目标列(" income_braket ")。在现实世界中,基于算法的输出所采取的行动的真实结果通常不会在算法运行之后立即可获得,而是在一段时间之后。在这种情况下,另一个过程应该是收集真实结果,一旦收集的真实结果的数量等于RETRAIN_EVERY
,算法将被重新训练。
比如说这个管道实现了一个电商的实时推荐系统。我们已经离线训练了一个推荐算法,目标列是我们的用户喜欢我们的推荐的程度的分类表示:0、1、2 和 3 分别表示不喜欢该项目或与该项目交互、喜欢该项目(例如,点击喜欢按钮)、将该项目添加到他们的购物篮和购买该项目的用户。当系统提供建议时,我们仍然不知道用户最终会做什么。
因此,除了在用户浏览网站(或应用程序)时收集和存储用户信息的过程之外,第二个过程应该收集我们推荐的最终结果。只有当两个过程都收集了RETRAIN_EVERY
消息和结果时,算法才会被重新训练。
(4)trainer.py
片段中的第 13 行。一旦重新训练完成,将发布一条包含相应信息的消息(published_training_completed()
)。
(5)predictor.py
片段中的第 5–8 行。预测者的消费者订阅了两个话题:[‘app_messages’, ‘retrain_topic’]
。一旦它通过“retrain_topic”接收到再训练已完成的信息,它将加载新的模型并照常继续该过程,而不会在该过程中的任何时间停止。
如何运行管道
在配套回购中,我们包含了如何运行管道(本地)的说明。其实很简单。
- 开始 zookeper 和卡夫卡:
**$ brew services start zookeeper
==> Successfully started `zookeeper` (label: homebrew.mxcl.zookeeper)
$ brew services start kafka
==> Successfully started `kafka` (label: homebrew.mxcl.kafka)**
2.运行 initialize.py:
**python initialize.py**
3.在 1 号终端运行预测器(或训练器):
**python predictor.py**
4.在 2 号终端运行训练器(或预测器):
**python trainer.py**
5.在 3 号终端运行示例应用程序
**python samplea_app.py**
然后,一旦处理了 N 条消息,您应该会看到类似这样的内容:
右上角终端:我们已经重新训练了模型,Hyperopt 已经运行了 10 次评估(在真实的练习中应该有几百次)。左上终端:一旦模型被重新训练和优化,我们将看到预测器是如何加载新模型的(在新的 LightGBM 版本发出恼人的警告消息之后)。底部终端:服务照常进行。
一些潜在的使用案例
这里有几个潜在的用例,以及其他一些。
- 实时调整在线旅程
我们来考虑一个卖一些物品的电商。当用户浏览网站时,我们收集他们的活动信息。我们之前已经训练了一个算法,我们知道,比如说,在 10 次互动之后,我们就可以很好地知道客户最终是否会购买我们的产品。此外,我们还知道他们可能购买的产品可能会很贵。因此,我们希望定制他们的“移动”旅程,以促进他们的购物体验。这里的定制可以是任何东西,从缩短旅程到改变页面布局。
2.给你的客户发邮件/打电话
与之前的用例类似,现在让我们假设客户决定停止旅程(厌倦、缺少时间、可能太复杂,等等)。如果算法预测这个客户有很大的潜力,我们可以使用像这篇文章中描述的管道立即或在控制延迟的情况下发送电子邮件或打电话给他们。
接下来的步骤
- 日志记录和监控:在即将发布的帖子中,我们将通过 MLFlow 在管道中插入日志记录和监控功能。与 HyperparameterHunter 一起,该解决方案将自动全面跟踪模型性能和超参数优化,同时提供可视化监控。
- 流管理:这里描述的解决方案以及相应的代码,已经被设计成可以在本地的笔记本电脑上轻松运行。然而,人们可能会认为,在现实生活中,这必须大规模地运行云。这一具体部分将不会在两个帖子中涉及。然而,我可以向你保证,将这里描述的结构移植到 AWS(举例来说)并在你方便的时候自动运行它(使用 EC2s、S3 和任何适合你的特定情况的前端)并不特别复杂。
任何问题/建议,请发邮件至:jrzaurin@gmail.com