ROC 与 TOC
可视化分析二元分类器的两种方法
开始了。(来源:作者)
最近,在撰写一篇关于如何优化配置二元分类器的文章时,我遇到了一个很有前途的替代方法,称为 TOC 分析。在这篇文章中,我将向您介绍这两种可视化工具背后的概念,并讨论它们的相似之处和不同之处。
动机
当谈到评估二元预测器的性能时,接收器操作特性 (ROC)曲线几十年来一直是一种主要形式,实际上早在机器学习时代之前就已经存在了(关于 20 世纪 50 年代的早期示例,请参见 Peterson 等人[1])。它允许我们评估和比较分类器的性能,因此是模型选择的有用工具。
一个分类器完全可以用一条在二维空间中画出的曲线来描述,也就是说,在你的监视器或一张纸上画出的曲线,这是基于这样一个事实:对于任何给定的测试集,2×2 混淆矩阵只有两个自由度。当且仅当你知道你的测试集的组成时,ROC 曲线上的每个点决定了相应的混淆矩阵,也就是说,你知道它包含多少阳性和阴性样本。然而,ROC 图本身不包含作为视觉信息的成分。
鉴于 ROC 分析的悠久历史,这一缺点最近通过 2014 年 Pontius 和 Si 引入的(TOC)得以解决[2]。TOC 图包含完整的 ROC 信息,并允许您读取曲线上每个点的全部信息,即测试集的组成和混淆矩阵的所有四个条目。
在我们了解其工作原理之前,让我们快速回顾一下二进制分类的基本概念和符号。
二元分类基础
在二进制分类中,模型性能通常通过将其对来自测试集的数据点的预测与已知的正确结果进行比较来评估,即与测试集的标签进行比较。每个数据点的预测分为四类:真阳性(TP)、真阴性(TN)、假阳性(TP)或假阴性(FN ),每类中的样本数在 混淆矩阵 中表示:
尽管混淆矩阵有 4 个分量,但实际上只有两个分量是独立的。这是因为每个实际测试集都有一定数量的 P 个实际为正的样本和一定数量的 N 个实际为负的样本。因此,不管特定分类器的性能如何,最终,我们总是得到 TP + FN = P 和 TN+FP = n。
这就产生了两个含有四个未知数的方程,并留给我们两个自由度。参数化这些自由度的一种常见方式(但不是唯一的方式)是借助于真阳性率* (TPR)和假阳性率 (FPR)。数学上,它们被定义为*
从这些定义出发,你可以很容易地用很少的代数说服自己,TPR 和 FPR 足以确定所有四个 TP、FP、FN 和 TN,注意
**
在许多情况下,分类器基于概率分类。这意味着他们根据数据点 x 的特征来计算每个类别的概率。在二元分类的情况下,这是两个概率 p(1|x)和 p(0|x)。由于样本必须属于这两类中的任何一类,即 p(1|x)+p(0|x)=1,所以只需查看两个概率中的一个,比如 p(1|x)。为了获得二元预测,使用判别阈值将连续概率 p(1|x)离散化为两类中的任一类:
因此,TPR 和 FPR 以及混淆矩阵中的所有四个数字都依赖于阈值。这种依赖性就是 ROC 和 TOC 图的设计目的。
ROC 和 TOC 图
ROC 图和 TOC 图都是在单个图中可视化所有可能阈值选择的分类器性能的工具。但是,它们基于两个不同的坐标系。
ROC 曲线被绘制到 FPR-TPR 坐标系中,也就是说,您为 0%和 100%之间的所有阈值绘制(FPR(阈值),TPR(阈值))。另一方面,TOC 图绘制在(TP+FP)-TP 坐标系中,也就是说,您绘制每个阈值的点(TP(阈值)+FP(阈值),TP(阈值))。
图 1: 两个分类器的 ROC 曲线(蓝色和绿色线条)和代表不知情(随机)分类器的曲线(橙色圆点)。
TOC 图的主要吸引力在于,您可以读出 TOC 空间中每个点的完整混淆矩阵。这不仅可以通过绘制曲线来实现,还可以通过用角(0,0)、(N,0)、(N+P,P)、(P,P)来绘制周围的平行四边形框来实现。
我们将在下面更详细地讨论这个平行四边形的性质,但是现在,我们注意到从 TOC 曲线上的任何点到盒子左边界的距离对应于 FP,到右边界的距离对应于 TN,到顶部的距离对应于 FN,到底部的距离对应于 TP。
图 2: 对于 TOC 曲线上的每个点,您都可以读出混淆矩阵的所有四个分量,TP、FP、FN 和 TN,同时还可以获得 ROC 图中的所有信息。(来源:作者)
与 ROC 图不同,ROC 图不允许在不知道 P 和 N 的情况下重建混淆矩阵,P 和 N 不包含在图本身中,TOC 图包含每个给定阈值选择的混淆矩阵。此外,您可以轻松地读取测试集的大小和组成,例如,揭示数据中的偏斜度,这些偏斜度在 ROC 图中是“隐藏”的。**
ROC 空间与 TOC 空间
ROC 曲线被绑定到一个被称为 ROC 空间的正方形区域,其点对应于 TPR 和 FPR 在 0 和 1 之间的所有可能值【3】。这个正方形被从 TPR=FPR=0 到 TPR=FPR=1 的对角线切成两半。
这条对角线上的点代表所谓的未知分类器。
这种分类器只是对数据点进行随机分类,完全不顾其实际特征值。例如,在 TPR=FPR=0.7 时,不知情的分类器会将 10 个数据点中的 7 个数据点分类为阳性,将 10 个数据点中的 3 个数据点分类为阴性。
这条对角线的两端是两个非常特殊的决定者的家。
在左下方,当 TPR=FPR=0 时,我们有一个分类器,它简单地将每一个数据点归类为负面。ROC 空间右上角的分类器将每个数据点分类为阳性。
ROC 空间的另外两个角落同样有趣。在左上角,我们有完美的分类器。它将正确的类别分配给测试集的每个单个数据点,对应于一个对角混淆矩阵,其中 TP=P,TN=N,FP=FN=0,也就是说,
通过翻转它的每个响应,你可以使完美的分类器变得完全不完美。这种最差的分类器给每个数据点分配了错误的类别,导致混淆矩阵 TP=TN=0,FP=N,FN=P,即
在 ROC 空间中,这对应于右下角,TPR=0,FPR=1。
***图 3:*ROC 曲线(蓝色)与无信息(随机)分类器性能的比较(橙色对角线)。ROC 空间的四个极端角是:(1)完美分类器,(2)全正分类器,(3)最差可能分类器,和(4)全负分类器。(来源:作者)
TOC 空间在两个基本方面不同于 ROC 空间。首先,它不是正方形而是平行四边形,其次,它的形状取决于测试集的组成。这是选择轴的直接结果,因为与速率 TPR 和 FPR 不同,绝对值 TP 和 FP 不包含在从 0 到 1 的范围内。为了理解平行四边形,选择 TP 的任意固定值。然后,您的选择自动成为 TP+FP 的下限,TP+FP 是 TOC 图的横坐标(也称为 x 轴),因为 FP 是一个计数,因此永远不会为负。TP+FP 的上限也由您的选择决定。就是 TP+N,因为 FP 不能大于 N,TP+FP 的这些上下界形成了平行四边形的倾斜的左右边界。TP 本身也是一个计数,并且限制在从 0 到 P 的范围内,分别确定平行四边形的上下边界。因此,整个 TOC 空间可以嵌入一个 N+P 乘 P 的矩形中。
尽管有这些差异,TOC 空间只是 ROC 空间的一个(非均匀)缩放和剪切版本。ROC 空间中的任何点都可以使用线性变换映射到 TOC 空间
这种转换是缩放的组合
和剪切
因此,我们可以通过使用
由于这种变换的简单性质,ROC 空间的基本几何结构被带到 TOC 空间。因此,就像 ROC 空间一样,TOC 空间也被表示无信息分类器的对角线减半。TOC 空间的角也保留了它们在 ROC 空间中的含义:左上角是完美的分类器,右下角是最差的分类器,右上角是全肯定的分类器,左下角是全否定的分类器(见图 4)。
***图 4:*TOC 空间的四个极端角分别是(1)完美分类器,(2)全正分类器,(3)最差可能分类器,(4)全负分类器。在第(5)点,患病率(阳性数)被正确估计。(5)(灰色区域)左边 TP+FP < P,这样患病率被低估,右边(5) TP+FP > FP,这样患病率被高估。(来源:作者)
其他派生属性在转换过程中也保持不变。例如,让我们考虑 ROC 曲线下的面积(AUROCC ),这是一个使用单个数字描述 ROC 曲线整体的典型指标。TOC 曲线下的面积(AUTOCC)与 ROC 曲线下的面积(AUROCC)成正比,可以用同样的方式解释。我们有
因此 ROC 曲线下的 ROC 空间部分(1 乘 1 的正方形)与 TOC 曲线下的 TOC 空间部分(N 乘 P 的平行四边形)相同。
在 Pontius 和 Si [2]的原始出版物中有这种关系的冗长证明,但我发现更直接的考虑是只有
ϕₛcₐₗₑ影响该区域(ϕₛₕₑₐᵣ是面积保持的)并且它的雅可比矩阵是 N⋅P,因此很明显,在变换下,AUROCC 被相应地放大。
***图 5:*TOC 曲线下的平行四边形(蓝色区域)相对于整个平行四边形面积(蓝色+橙色部分)的分数等于 ROC 曲线下的面积。比较图 3 所示 ROC 图中的彩色区域。(来源:作者)
TOC 空间有一个值得注意的点是 ROC 空间无法识别的。它是点(P,TP),如图 4 中的点(5)所示。它是曲线上平行四边形左边界上端正下方的点。在这一点上操作的分类器产生与测试集中的阳性和阴性样本完全相同的阳性和阴性预测比率。这样的分类器可以说是正确地代表了实际的流行度。该点左侧的所有分类器都低估了阳性率,而其右侧的所有分类器都高估了阳性率。
TOC 空间的形状
虽然 ROC 空间在测试集组成变化的情况下保持几何静态,但是 TOC 空间变化强烈。Pontius 和 Si 建议通过重新调整图形的比例来“提高视觉清晰度”,这样在纸上(或你的显示器上),TP 轴和 TP+FP 轴具有相同的长度[2]。然而,用相同的比例绘制 TP 和 TP+FP 轴,并观察 TOC 空间如何随着测试集组成的变化而展开和折叠,有助于理解 TOC 空间的概念。
对于任何非空测试集,有五种可能的情况:
1.0=N
2。0 < N < P
3。0 < P=N
4。0 < P < N
5。0=P < N
为了说明,让我们考虑一个在“成人”数据集[4]上训练的玩具分类器,并用同样多的阳性和阴性样本编译一个主测试集。然后,我们可以通过控制这个集合的子集来创建所有五种可能的情况。具体来说,我们从 N=0,P=1552(情况 1)开始,增加 N(情况 2),直到达到 N=P=1552(情况 3)。从那里,我们减少 P(情况 4),直到我们最终达到 P=0,N=1552(情况 5)。图 6 说明了 TOC 空间如何由于测试集组成的这些变化而改变其形状。
图 6: 显示各种测试集构成的 TOC 空间形状的动画。请注意,在 N=0 和 P=0 这两种极端情况下,从二维坍缩到一维。(来源:作者)
图 7: 对于 0=N < P(情况 1),TOC 空间是一维的。它对应于从(0,0)到(P,P)的对角线。(来源:作者)
让我们更详细地讨论情况 1 到 5。
对于 N=0 < P, TOC space is one-dimensional! Its left and right boundaries collapse into a single diagonal line from (0, 0) to (P, P) (see Figure 7).
Although this might seem strange at first glance, it is sensible, since in absence of negatives, the performance of a classifier is in fact one-dimensional, completely determined by a single parameter, its TPR.
Going away from this extreme situation, we increase the number of negatives in the test set.
图 8 :对于 0 < N < P(情况 2),TOC 空间是一个相当窄的对角线带。(来源:作者)
对角线分成左右边界,打开了一个有限的区域,即“正常的”二维 TOC 空间。
然而,TOC 空间保留了一个相当窄的对角线形状,因为它的右边界从 TP+FP=N 开始,在 TP+FP=P 的左边很远的地方,它的左边界在这里结束(见图 8)。
图 9: 对于 0 < N=P 左边界在右边界起点的正上方结束。(来源:作者)
进一步增加否定的比例,然后我们接近 P=N 的情况,也就是说,在测试集中否定和肯定是完全平衡的。
现在,右边的 TOC 空间边界直接从左边界的端点下面开始(参见图 9)。
图 10: 对于 0 < P < N,TOC 空间相当宽广。(来源:作者)
当我们增加负数的数量超过这一点时,我们会遇到这样一种情况,左边界和右边界的对角线不再在彼此之上,在(P,0)、(N,0)、(N,P)和(P,P)之间有一个矩形区域。
因此,TOC 空间现在显得相当大(参见图 10)。
最终,我们接近了另一个极端:整个测试集都充满了否定。同样,TOC 空间的两个边界将两个折叠成一条线。
这一次,是上限和下限,因此 TOC 空间变成了从 0 到 N 的水平线段(参见图 11)。
图 11: A 0=P < N,TOC 空间是从 0 到 N 的一维线段(来源:作者)
同样,这种崩溃是合理的,因为分类器的结果现在完全由 TN 和 FP 组成,其性能完全由一个参数定义,即其 FPR。
我们通过注意到对于 N=P=0 的完全空的测试集的情况(实际上不相关,但是病理上有趣), TOC 空间将进一步折叠,成为单个点,来结束我们的 TOC 空间的往返行程。
讨论
TOC 图是统计师工具箱的有用补充。习惯于 ROC 分析的读者可以很快学会解释 TOC 图,因为这两种表示共享许多属性。
TOC 曲线比 ROC 曲线包含更多的信息。然而,ROC 图的信息稀疏性也可以被看作是有利的。正如 Fawcett 在他的ROC 分析简介*【3】:ROC 图的一个优点是,它们能够可视化和组织分类器性能,而不用考虑类别分布或错误成本。当研究具有偏斜分布的学习或成本敏感学习时,这种能力变得非常重要。研究人员可以绘制一组分类器的性能图,并且该图相对于操作条件(类偏斜和错误成本)保持不变。随着这些条件的改变,感兴趣的区域可能会改变,但是图形本身不会改变。*
因此,ROC 曲线在 TOC 不是合适替代品的某些情况下仍然适用。
当涉及到库支持时,ROC 曲线具有明显的优势。尽管在任何主要的机器学习或统计库中都有现成的用于 ROC 分析的库函数,但由于 TOC 出现的时间相对较短,对它的支持仍然相当有限。有一个由最初 TOC 出版物的作者策划的 TOC R 包。然而,如果你愿意多做一点,一个根据概率绘制 TOC 图的函数可以很快用你最喜欢的编程语言实现。
最后,我们必须考虑到,图表最终是一种交流的手段。在统计学和机器学习社区,ROC 分析是一个众所周知的标准,ROC 图是直接理解的。相比之下,TOC 分析仍然相对年轻,可能仍然会困扰你的一部分听众。如果你在一个重要的会议上有 10 分钟的时间,你不会想花 5 分钟来解释一个不寻常的图表类型(TOC),特别是当有一个可供选择的(ROC ),你的听众会马上得到。
尽管 TOC 分析还远未被广泛采用,但我希望这篇文章能让你相信,在分析分类器的性能时,这是一个值得尝试的工具。
参考
[1] W. Peterson、T. Birdsall 和 W. Fox,信号可检测性理论(1954),信息理论 IRE 专业组汇刊, 4 (4),第 171–212 页。
[2] R. G. Pontius Jr 和 K. Si,测量多阈值诊断能力的总操作特性 (2014),国际地理信息科学杂志 28 (3),第 570-583 页
[3] T. Fawcett,ROC 分析介绍 (2006),模式识别字母 27 ,861–874 页
[4] 成人数据集 (1996),通过 Dua,d .和 Graff,C. (2019), UCI 机器学习知识库提供
建造一个人工智能机器人来玩石头、剪子、布
了解如何做同样的事情
AI Bot 名称:Janken
使用的技术 : TensorFlow、Keras、Numpy、SqueezeNet &打开 CV
都是通过 python 脚本执行的。通过键盘敲击进行游戏设置。
TL;博士
如果你想开始运行,你可以直接到我的 GitHub 和克隆。
https://github.com/DarrenBro/rock-paper-scissors-ai
自述文件上有详细的说明你可以遵循。
在 Github 项目中,我包含了模型示例、项目的依赖项、SqueezeNet 模型(稍后会提到)和测试图像。
即使你以前从未使用过 ML 或 python,我也尽量使入门变得简单。
本文的其余部分将集中在成就 Janken 的 4 个步骤上;
本地网络摄像头——Janken 并不总是做对
- 收集数据
(什么看起来合适) - 挤压网络和训练神经网络
- 测试模型
- 玩游戏!
收集数据——什么是合适的
我们希望收集 4 种类型的图像(石头、布、剪刀和背景/噪声),并将它们映射为我们的输入标签。我们只需要将它们编入索引。
在机器学习中,当具有正确答案的数据集可用时,它可以被视为一种超级监督学习的形式。这种形式的训练是“图像分类”,我们知道正确的标签,Janken 属于监督图像分类。
因此,我们希望每个类别都有数百张图片,这需要我们做一些工作,但我们通过一个简单的开放 CV 脚本在几秒钟内收集所有这些图片,减少了繁重的工作。
这在 Github 自述文件中有解释
需要注意的事项。
保持所有输入图形一致 如果数据不一致,你很容易陷入如下错误。形状。
ValueError:检查时出错:预期 squeezenet_input 具有 shape (None,300,300,3),但得到的数组具有 shape (1,227,227,3)。
所以有了我上面的图,捕捉然后存储你的数据,你可以在几秒钟内自动收集 100 张图像。
本地网络摄像头——尝试获得尽可能多的不同角度
移除偏置
本地网络摄像头——我戴着手套的手扩大了数据范围
ML 中的一个巨大领域是拥有一个具有良好通用性的模型。
数据的多样性足以用来训练吗?100 多张我自己的手的图像可能很难识别除了我以外的任何东西。
我的临时解决方案是用一只戴着手套的手来鼓励 CNN 关注手势的特征。
然后我扩展了这一点,伸出手从量子单元收集图像,并训练进一步降低我的偏见。
一个改进的想法 —保持所有训练图像为黑白,并将游戏图像也转换为 BW 彩色。
最后需要注意的一点是:当我在抓图时用手调整角度,一些照片在运动中变得模糊了。重要的是通过移除来清除它们,因为它们只会混淆模型来识别静态图像,就像在游戏中一样。
噪音污染 **/灯光
‘**噪音’的意思是背景或者除了手势以外的任何东西。关于这一点的一点是,我有麻烦让 Janken 认识到还没有播放,它会在预测之间跳跃。所以在训练中保持一个稳定的背景不是最好的选择,我可以在数据中混合更多的对比、阴影、海报、门,只是更多的自然房屋和办公室物品。
挤压网络和训练神经网络
这里有很多要说的,我在我的 GitHub 里给代码加了很多注释(上下链接),这里要重点关注的文件叫做“train_model.py”。
Keras
让我们的标签输入映射到索引值,因为这是神经网络识别它们的方式。
INPUT_LABELS = {
"rock": 0,
"paper": 1,
"scissors": 2,
"noise": 3
}
在那之后,我决定走“顺序模式”。
一种允许简单的 1-1 映射的设计,在 ML 术语中,它允许层接受 1 个输入张量并创建 1 个输出张量。例如,顺序允许神经网络(NN)将纸张图像识别为值 1。
model = Sequential([
SqueezeNet(input_shape=(300, 300, 3), include_top=False),
Dropout(0.2),
Convolution2D(LABELS_COUNT, (1, 1), padding='valid'),
Activation('relu'),
GlobalAveragePooling2D(),
Activation('softmax')
以上是我们的模型定义,Janken 的核心,这是我们需要做的所有架构设计,大部分工作是在我们编译和拟合模型之前收集、清理和整形数据。
我很快会谈到“挤压网”。下面的所有东西都被认为是一个层(keras.layers),它们都很重要,所以让我们花一分钟来解释每一个。
退出添加以减少过拟合,0.2 = 20%,因此在整个训练过程中,退出结果的 1/5,以保持模型学习新方法,而不变得陈旧。这种高达 50%的情况并不少见。
Convolution2D 通过第一个参数 LABELS_COUNT 控制图层的大小,总共 4 个,(3 个手势+噪波)标签。它被附加到已经定义的神经网络‘SqueezeNet’上。
激活(ReLU) 整流线性单元将负值变为 0 并输出正值。
为什么是?激活函数负责获取输入并为输出节点分配权重,模型不能很好地与负值协调,它在我们期望模型如何执行的意义上变得不一致。
𝑓(𝑥) = max{0,𝑥} = > relu 单元的输出为非负。返回 x,如果小于零,则 max 返回 0。
ReLU 在正端产生一条直线,负端是一个平坦的零点
ReLU 已经成为许多类型的神经网络的默认激活函数,因为使用 ReLU 的模型更容易训练,并且通常实现更好的性能和良好的模型收敛性。
这并不是所有的结束,它可能会导致一个问题,称为“死亡 ReLU”。如果你返回太多的负数,你不会希望它们都变成 0,而是以负的线性方式返回。如果您有兴趣了解更多信息,请搜索“ Leaky ReLU ”。
globaveragepool2d进行分类,计算上一层各特征地图的平均输出。
即数据简化层,为激活(“softmax”)准备模型。
激活(softmax) 给出每个手势的概率。
我们有一个 4 图像类(问题),‘soft max’处理多类,任何超过 2 的。
挤压网
这是一个用于图像分类的预建神经网络,这意味着我们可以专注于它的扩展,以实现我们构建 Janken 的目的,这本身就足够了,而不是从头开始创建神经网络。光是训练时间就可能是几天。
你用 SqueezeNet 得到的奖金;
- 较小的卷积神经网络(CNN)在分布式训练中需要较少的跨服务器通信。
- 导出新型号所需的带宽更少。
- 更小的 CNN 更适合部署并且使用更少的内存。
重新访问培训脚本中的代码行。
SqueezeNet(input_shape=(300, 300, 3), include_top=False)
input_shape 是一张尺寸为 300 x 300 像素的图片。3 代表 RGB 颜色范围。
include_top 让您选择是否需要最终的密集层。
密集层能够解释发现的模式,以便对图像进行分类。这张图片包含了岩石。
#设置为假因为我们已经标记了岩石数据的样子。
卷积层作为特征提取器工作。他们识别图像中的一系列图案,每一层都可以通过看到图案的图案来识别更精细的图案。
重量说明:
- 卷积层中的权重是固定大小的。卷积层不关心输入图像的大小。它只是进行训练,并根据输入图像的相同大小呈现结果图像。
- 密集图层中的权重完全取决于输入大小。它是每个输入元素的一个权重。因此,这要求你的输入总是相同的大小,否则你不会有适当的学习权重。
因此,将最终密集图层设置为 false 可让您定义输入大小(300 x 300)。(并且输出大小会相应增加/减少)。
测试模型
在脚本“ test_model ”中,您可以在您已经处理过的图像或模型从未见过的新图像上看到模型预测。
有时这是正确的
有时候你很幸运
其余 90%的时间你只是叹息
该脚本处理您想要提供的任何新图像,因为它将使用 open CV 重新整形为 300x300。
玩游戏!
对 Janken 游戏结果的预测
我想象 Janken 将做出的预测会“闪烁”很多,因为移动的摄像机图像将总是提供不同的输入来分析和运行模型。
照明将发挥很大的作用,所以我试图分割我的数据集,并在一天的不同时间收集图像。
静态背景或控件冻结图像将有助于做出更稳定的手势预测。
结果如何?
Janken 并没有考虑到锁定,所以一个“优雅”的解决方案是通过 mac 的网络摄像头将其他人播放到显示器上与另一个玩家共享的屏幕上。
我知道你印象深刻
然而,Janken 只能不断地击败我,它能够通过显示器从其他玩家那里赢得 50%的手势,但我怀疑相机图像和处理的性质使 Janken 很难可能做出所有的手势。
为了改进模型,我应该通过我的网络摄像头收集来自用户的图像,给 Janken 更多的概括。
总的来说,我真的很喜欢我学到的一切,这个项目中没有一项技术是我熟悉的。把学习变成游戏是很棒的。
最后,我添加了一些用户控件,希望能增加三局两胜的游戏体验。
主要说明位于灰色标题上。
安静点。
截图的时候总是忘记微笑:)
资源
全代码 GitHub 链接
人工智能机器人和人类玩石头剪刀布
github.com](https://github.com/DarrenBro/rock-paper-scissors-ai/blob/master/collect_image_data.py)
Keras API
Keras 文档
:Keras API 参考 Keras 文档 keras.io](https://keras.io/api/)
SqueezeNet 信息
AlexNet 级精度,参数减少 50 倍
towardsdatascience.com](/review-squeezenet-image-classification-e7414825581a) [## SqueezeNet: AlexNet 级别的精度,参数减少了 50 倍,并且<0.5MB model size
Recent research on deep neural networks has focused primarily on improving accuracy. For a given accuracy level, it is…
arxiv.org](https://arxiv.org/abs/1602.07360)
O’Reilly (TensorFlow 和 Keras)
[## 使用 TensorFlow 2 和 Keras 进行深度学习-第二版
使用最新发布的 TensorFlow 2 和 Keras 构建机器和深度学习系统,用于实验室、生产和…
www.oreilly.com](https://www.oreilly.com/library/view/deep-learning-with/9781838823412/)
使用 PyTorch 的 Ax 包进行摇摆超参数调谐
使用贝叶斯和土匪优化较短的调谐,同时享受难以置信的简单设置。它非常有效。
雅罗斯瓦夫·米奥
对于许多机器学习任务,超参数调整是必须的。我们通常努力为我们的问题选择正确的算法和架构,然后严格训练以获得一个好的模型。在这两次之后进行超参数调整(HPT)可能看起来没有必要,但事实上,这是至关重要的。HPT 应该定期进行,并可能帮助我们以较小的努力实现性能的巨大改进。
我在这里建议的是用新发布的 python 包实现 HPT 的一种简单方法。它将完美地工作,并且花费不超过半个小时来设置你在你自己的计算机上训练的任何东西。对于其他模型,尤其是那些需要部署培训或在生产中运行的模型,这将通过一些小的调整来实现,我们将在本文的 B 部分进一步讨论。
Ax 是什么?
Ax 是 PyTorch 的一个开源包,它可以帮助您在您定义的参数范围内找到任何函数的最小值。机器学习中最常见的用例是找到训练模型的最佳超参数,这些参数将使您的总体损失最小化。该软件包通过运行多轮训练来实现这一点,每轮训练使用一组不同的参数,并返回损失最小的参数。诀窍在于,它不需要对这些参数进行网格搜索或随机搜索,而是使用更复杂的算法,因此节省了大量训练和运行时间。
Ax 可以找到连续参数(比如学习率)和离散参数(比如隐藏层的大小)的最小值。它对前者使用贝叶斯优化,对后者使用 bandit 优化。即使这个包来自 pytorch,它也可以用于任何函数,只要它返回一个您想要最小化的值。
在我们开始之前,安装说明可以在这里找到。
第一部分——“你好,世界!”
Ax 有几种操作模式,但我们将从最基本的一种开始,用一个小例子来说明。如前所述,我们通常会使用 Ax 来帮助我们找到最佳的超参数,但在其核心,这个包可以帮助我们找到一个函数关于某些参数的最小值。这就是为什么对于这个例子,我们将运行 Ax 来寻找一个复杂的二次函数的最小值。为此,我们将定义一个名为 booth 的函数,该函数在字典 p 中接收其参数 {x1,x2} :
# Sample Quadratic Function
def booth(p):
# p = dictionary of parameters
return (p[“x1”] + 2*p[“x2”] — 7)**2 + (2*p[“x1”] + p[“x2”] — 5)**2print(booth({“x1”:6, “x2”: 7}))
这将返回:365。
为了找到“booth”的最小值,我们将让 ax 运行该函数几次。它为每次运行选择的参数取决于以前的运行——它运行的参数以及对这些参数的函数的结果是什么(在机器学习中,“结果”==“损失”)。运行下一个代码位执行 20 次连续的函数运行,具有 20 组不同的参数 {x1,x2} 。然后打印出*【最佳参数】*。作为比较,如果我们想用网格搜索来运行这个,对于 x1 和 *x2,*的跳跃为 0.5,我们将需要 1600 次运行,而不是 20 次!
from ax import optimize
best_parameters, best_values, _, _ = optimize(
parameters=[
{“name”: “x1”,
“type”: “range”,
“bounds”: [-10.0, 10.0],},
{“name”: “x2”,
“type”: “range”,
“bounds”: [-10.0, 10.0],},],
evaluation_function=booth,
minimize=True,)print(best_parameters)
这会打印出来(真实最小值是(1,3)):
{'x1': 1.0358775112792173, 'x2': 2.8776698220783423}
相当接近!在笔记本电脑上,这可能不到 10 秒钟。您可能会注意到,返回的“最佳参数”并不完全是真正的最小值,但是它们使函数非常接近实际的最小值。您对结果中的边距有多宽容是一个您稍后可以使用的参数。
这里我们需要做的唯一一件事是确保我们有一个以字典作为输入返回单个值的函数。这是运行 ax 时大多数情况下需要的。
B 部分—使用部署运行
A 部分非常简单,可以在你的电脑上运行的任何东西上很好地工作,但是它有几个缺点:
- 它一次只运行一个“训练”,如果一个训练会话超过几分钟就不好了。
- 它只在本地运行,如果您的常规模型运行在云/部署等上,这是很糟糕的。
为此,我们需要一些更复杂的东西:最好是某种神奇的方法,只需要几组参数就可以运行,这样我们就可以部署培训,然后耐心等待,直到他们得到结果。当第一批训练完成时,我们可以让 ax 知道损失是什么,获得下一组参数并开始下一批。用 ax 的行话来说,这种运行 ax 的方式就是所谓的“ax 服务”。短语和运行都很简单,几乎和我们在 a 部分看到的一样简单。
为了适应“ax 范式”,我们仍然需要某种运行训练并返回损失的函数,可能类似于下面这样:
def training_wrapper(parameters_dictionary): 1\. run training with parameters_dictionary (this writes the loss value to somewhere in the cloud)2\. read loss from somewhere in the cloud3\. return loss
在定义了适当的包装器之后,我们可以运行 ax 服务 HPT 代码。因为我希望您能够在您的计算机上运行这个演示代码,所以我将坚持使用我们在 A 部分中介绍的二次函数——“booth”。 {x1,x2} 的参数范围将保持不变。对于运行部署,我可以用我的“training_wrapper”版本替换下一个示例代码中的 booth。示例代码如下所示:
from ax.service.ax_client import AxClientax = AxClient(enforce_sequential_optimization=False)ax.create_experiment(
name=”booth_experiment”,
parameters=[
{“name”: “x1”,
“type”: “range”,
“bounds”: [-10.0, 10.0],},
{“name”: “x2”,
“type”: “range”,
“bounds”: [-10.0, 10.0],},],
objective_name=”booth”,
minimize=True,
)for _ in range(15):
next_parameters, trial_index = ax.get_next_trial()
ax.complete_trial(trial_index=trial_index, raw_data=booth(next_parameters))best_parameters, metrics = ax.get_best_parameters()
这与 a 部分有一点不同,我们不再仅仅使用“优化”功能。相反:
- 我们初始化一个 ax 客户端。
- 我们建立了一个“实验”,并选择了一系列我们想要检查的超参数。
- 我们用 get_next_trial()得到下一组我们想要运行函数的参数。
- 等待函数完成,用 complete_trial() 运行
也就是说,我们把获取下次运行的参数和实际运行分开了。如果您想并发运行,您可以一次获得 N 个 get_next_trial(),并异步运行它们。如果您想这样做,请确保不要忘记将*“enforce _ sequential _ optimization”标志设置为 false。如果您想知道您可以同时运行多少次,您可以使用get _ recommended _ max _ parallelism*(在这里阅读这个函数的输出)。
差不多就是这样。这个包是非常可定制的,并且可以处理您想要为您的特定环境所做的任何改变。文档可读性很强,尤其是教程。它还有各种各样的可视化效果来帮助你弄清楚发生了什么。
摘要
您必须手动设置多次运行或使用网格搜索的日子已经一去不复返了。的确,在一个非常大的超参数集上,ax 可能导致类似随机搜索的结果,但是即使它没有减少您最终运行的训练数量,它仍然为您节省了编码和多次运行的处理。设置非常简单——我强烈建议您亲自尝试一下!
链接:
- 通用文档:https://ax.dev/docs/why-ax.html
- 教程:https://ax.dev/tutorials/
- 可视化:https://ax.dev/tutorials/visualizations.html
权力 BI 中的角色扮演维度
检查这个简单的建模技术,以避免数据模型冗余
在解释了如何通过简单地遵守一些简单的规则来将 Power BI 数据模型的大小减少 90% ,并理解了如何通过 VertiPaq 引擎在后台优化您的数据模型之后,现在是学习维度建模的核心概念之一的最佳时机。
Marko Blazevic 在 Pexels 上拍摄的照片
角色扮演维度并不是 Power BI 独有的概念。这是一种通用的数据建模技术,来自金博尔的方法论。
简而言之,这就是当您使用一个相同的维度为您的事实表创建多个关系时的情况。角色扮演维度概念的典型用法是用表示日期维度,因为在许多情况下,您的事实表将包含多个日期字段。例如,在博彩行业中,有 DateBetPlaced 和 DateBetProcessed 字段,它们不需要相同(在大多数情况下也不需要相同)。因此,假设业务请求是分析 DateBetPlaced 和 DateBetProcessed 上的数据。
一维多重引用的解决方案
第一种解决方案是创建两个完全相同的日期维度的副本,并在第一种情况下将 DateKey 关联到 DateBetPlaced,在第二种情况下关联到 DateBetProcessed。大概是这样的:
如您所见,事实表中的每个日期字段都与其自己的日期维度相关。我们说这些引用中的每一个都在模型中“扮演”它的角色。
当然,这种模型并不是最佳的,因为我们基本上是在没有有效理由的情况下使数据冗余。此外,为了获得有效的结果,我们需要为日期维度的每个引用使用单独的过滤器。
优化模型
我们可以将一个维度多次关联到一个事实表,而不是保留同一个维度的多个引用。这个概念在不同的工具中有不同的表现(例如,在 SSAS 多维数据库中,如果数据源视图有适当的外键,您可以在维度和事实表之间定义多个活动关系。但是,这超出了本文的范围),所以我将重点讨论 Power BI。
因此,我将从我的数据模型中删除冗余的 Date 维度,并将 Date 维度中的 DateKey 连接到事实表中的 DateBetProcessed 字段。
这里发生了什么?Power BI 创建了一个关系,但是正如你注意到的,这个关系是用虚线标记的。这是因为 Power BI 只允许两个表之间有一个活动关系,在我的例子中,是在 DateKey 和 DateBetPlaced 之间。所以,当我把它放在报告画布上时,我会得到这样的结果:
我可以看到每月下注的总数,但是由于我的活动关系是在 DateKey 和 DateBetPlaced 之间,所以我看到的总数是基于下注日期的!
如果我想看,而不是下了多少注,每月有多少注被处理呢?这里涉及到 DAX 功能 用户关系 。此功能使我们能够定义对于特定计算哪个关系应该是活动的。
所以,当我写出下面的度量时:
Bets Processed = CALCULATE(
COUNT('Fact Bets'[BetID]),
USERELATIONSHIP(DimDate[DateKey],'Fact Bets'[DateBetProcessed])
)
我在明确的对 Power BI 说:在这里,我不希望你使用默认的主动关系(DateKey — DateBetPlaced)。相反,我希望您切换到使用另一个关系(DateKey — DateBetProcessed ),并使该关系仅对该计算有效!
结果如下:
您可能会注意到,根据计算中使用的关系,线条是不同的。
结论
使用这种技术,我们使我们的用户能够从不同的角度分割数据,并给予他们基于多种场景分析数据的灵活性,从而保持我们的数据模型整洁而不冗余。
成为会员,阅读媒体上的每一个故事!
订阅这里获取更多有见地的数据文章!
追踪石油泄漏
我从漏油到应用人工智能预测设备故障的旅程
一)动机
在求职过程中,我发现我所在地区的许多公司都在寻找具有石油和天然气知识的数据科学家。我的背景大部分是关于数学的,所以我决定沿着石油管道去冒险。
我注意到的一件事是,石油和天然气是一个庞大的行业,基础设施陈旧。2014 年,Inside Energy 报告称,美国 45%的原油管道已经超过 50 年。有些管道甚至在 20 世纪 20 年代以前就铺设好了,而至今仍在运行。这种古老、老化的基础设施可能会导致灾难,如漏油,从而影响公司收入、环境和周围地区的人们。
那么,所有这些石油泄漏的罪魁祸首是什么呢?让我们潜入油中!
二)2010-2017 年石油管道事故
数据包括从 2010 年到 2017 年向管道和危险材料安全管理局报告的每个石油管道泄漏或溢出的记录。在 7 年多的时间里,发生了 2795 次泄漏。有 46 个特征描述每个事件。其中值得注意的有:事故日期和时间、操作者姓名、事故原因、危险液体类型、损失数量、伤亡人数以及相关成本
数据丰富,描述性强。我选择了一些有趣的特性,并从这里开始
1)事件是如何按年、月和一天中的小时分布的?
每年的病例数。资料来源:Huy Bui
观察 2017 年有未完成的数据。最高峰是 2015 年的 462 例
按日期的小时数列出的案例数
这里没有调整时区,但是我可以有把握地假设大多数情况发生在营业时间。
每月病例数。资料来源:Huy Bui
有趣的是,12 月份只有几起案件(212 起)。但在 1 月份,它急剧增加(275)。我想知道,由于运营从旧的一年过渡到新的一年,这里是否有一种趋势,或者,这可能只是一月份发生的一系列随机泄漏?
2)这些事故发生在哪里?
2010-2017 年所有事故的位置。资料来源:Huy Bui
根据我由plotly
创建的地理图,在我看来泄漏发生在管道沿线。德克萨斯州是所有州中事故最多的州(1004 起)。这并不奇怪,因为德克萨斯州是美国最大的国内石油生产商。我以为德州会损失最多的钱,但事实并非如此…
3)每个状态的净损失是多少?
各州净损失(2010–2017)。资料来源:Huy Bui
让我们来看看赔钱最多的三个州:
第三名 得克萨斯发生 1004 起事故,损失 135580 桶,净亏损 18475 万桶
第二名加州,事故 153 起,损失 3390 桶,净亏损 192 桶。密耳
而冠军是…密歇根州 29 起事故,5355 桶损失,834 百万桶
我的假设是完全错误的,事故数量与净损失并不相关。此外,桶损失的数字没有考虑到钱去了哪里。
拼图中缺失的部分是**其他特征,**我天真地认为它们不重要。我应该考虑的其他特征是:
- 管道关闭
- 财产损失成本
- 商品成本损失
- 应急响应成本
- 环境补救费用
4)那么……密歇根到底发生了什么?
密歇根净亏损时间序列(百万美元)。资料来源:Huy Bui
你看到的 2010 年的高峰占了密歇根州总净亏损的 95%,占了 7 年间美国总净亏损的 36%!稍加研究表明,这一事件就是 7 月 25 日的卡拉马祖河漏油事件。当安桥(6B 线)运营的一条管道爆裂并流入塔尔马奇溪时,长达 18 个小时无人察觉。工作人员误解了异常压力数据,认为这是由管道中的气泡引起的。因此,他们重新启动了输油管两次,使得漏油速度大大加快。
密歇根十大事件列表将让你更好地了解卡拉马祖河漏油事件对该州造成了多大的损害。
密歇根十大事故(按净损失排名)。资料来源:Huy Bui
5)漏油的罪魁祸首是什么?
设备故障是这个问题的简答。当涉及到解释具有许多参数的数据时,人的能力是非常有限的。手动读取数据会导致忽略异常情况,并对业务造成严重损害。事实上,材料/焊接/设备故障占所有原因损失的 53%。
原因类别和不同特征之间的关联。资料来源:Huy Bui
幸运的是,随着机器学习的增长,许多大型石油公司已经开始将人工智能应用到他们的基础设施中,以帮助防止设备故障,并使他们的业务受益。
III)预测性设备故障
回到 2019 年 10 月,我参加了德克萨斯 A&M 大学主办的数据马拉松。挑战之一是使用由康菲石油公司赞助的传感器 数据 预测井下设备故障。我没有做这个挑战,但是我对这个问题很感兴趣,并且仍然记得它。之前的分析给了我足够的动力,让我回头再看一遍这个数据集。
传感器数据的样本。资料来源:Huy Bui
训练集由 60000 个观察值和 170 个传感器值组成。这些传感器分为两种类型:
measure:
传感器的单次测量。histogram bin:
一组 10 个柱,它们是传感器的不同柱,显示它们随时间的分布。
工作是对观察结果是故障还是非故障进行分类。下面的直方图显示了列车组分布:
列车组中“故障”和“未故障”的分布。资料来源:Huy Bui
数据包含许多空值。我必须创建一个函数,接受不同的阈值,逐个处理丢失的值,并超调这个函数以获得最佳结果。我用xgboost
来做预测,用f1-score
作为度量。
我将我的模型应用于测试集上的 16001 个不同的观察值。提交预测后,我在第一次尝试中获得了 0.99383。
四)结论
如果石油工业投资研究人工智能,他们会更快地发现有缺陷的设备,更有效地防止灾难,并节省大量资金。具体来说,Kalamazoo 河漏油事件本来可以用机器学习来避免。
我希望你喜欢阅读我的文章,并跟随我踏上这一旅程。感谢任何反馈。如果你想看看数据,玩玩plotly
交互图,判断一下我的技术知识,这里是链接。
是什么让一首歌伟大?第一部分
什么让一首歌变得伟大
使用 Selenium 和 BeautifulSoup 在 Python 中动态抓取生成的内容
Natalie Cardona 在 Unsplash 上拍摄的照片
【这是 系列三篇文章中的第一篇
Web 抓取、可视化、列表理解、正则表达式、熊猫!这个项目拥有一切——包括泡菜!
【2020 年 9 月 1 日更新:自本文首次发表以来,《滚石》已将源代码改为其页面。因此,我已经更新了代码,允许检索动态生成的内容。】
一旦你学会了一些编码和基本的数据科学技能,标准的建议是去做项目,做很多项目。不幸的是,我们中的许多人很难找到这样的项目。
前几天我在翻滚石的 有史以来最伟大的 500 首歌 的榜单。我开始问自己:‘谁的歌在榜单上最多?或者:“会偏向于过去几十年,因为这些评论家可能还不到 20 多岁?”?。在我开始这个项目的第二天,我就一直在寻找一个能够让我将一些网络搜集技术与一些探索性数据分析( EDA )结合起来的项目。我从中获得了很多乐趣:我希望通过分享它,你也能学到一些东西并从中获得乐趣。
我将在这里展示的是许多库、工具和技能集:这是一个端到端的项目,从数据检索开始,到可视化结束,涉及解析、清理和分析数据。我们将涉及的一些内容:
- 网页抓取(使用
BeautifulSoup
和Selenium
- 正则表达式(使用 Python 的
re
模块) - API(即 Spotify 的)(使用
spotipy
) - 数据分析和可视化(用
pandas
和matplotlib
)。
我会认为这个项目适合高级初学者到中级程序员。它本身并不复杂:但它确实涉及许多不同的领域。注意,HTML 和 CSS 的一些非常基础的知识对于第一部分可能是有用的。
网络搜集:获取数据并清理数据
首先,让我们导入我们需要的库。
# webscraping libraries
import urllib # to retrieve web pages
from bs4 import BeautifulSoup # to parse web pages
from selenium import webdriver # to retrieve dynamically generated content
import time # allows us to wait before scraping or interacting with web pages# data, cleaning, analysis and visualization
import pandas as pd # the goto python library for data cleaning and analysis
import matplotlib.pyplot as plt # the goto python library for data visualization
import seaborn as sns # data visualization (but nicer!)
import re # Python's library for regular expressions (see more below)# to interact with Spotify's API
import spotipy # to query Spotify's API
from spotipy.oauth2 import SpotifyClientCredentials # for API login # display charts in jupyter notebook
%matplotlib inline
第一步:用硒和美丽的汤刮动态生成的内容
打开浏览器,导航至我们的列表。首先,请注意,该列表是以 50 为一组进行“分批”的。这告诉我们,一旦我们开始抓取,我们可能需要迭代不同的地址来获取我们的数据——稍后将详细介绍。
向下滚动我们找到我们的歌曲。我们要检索五类数据:
- 艺术家
- 歌曲名称
- 作者
- 生产者
- 发布日期
如果你ctrl-click
(在 Mac 上)点击页面的一个元素,并从弹出的菜单中选择检查**,你会看到相应的 HTML 高亮显示。在这种情况下,要获得艺术家和歌曲的标题,我们需要查找标签为<h2>
的元素,该元素属于类c-gallery_vertical-album__title
。**
检查 web 元素
通常,我们会使用urllib
检索页面,并使用参数html.parser
将结果传递给beautiful soup:BeatifulSoup 会解析我们检索到的 HTML,并允许我们找到使用find_all
方法识别的元素。
然而,事实证明页面是动态生成的(如果你查看源代码,你不会发现任何这些元素)。因此,首先我们将使用[Selenium](https://selenium-python.readthedocs.io)
来模拟浏览器打开页面,然后才检索内容。
def get_page_source(url):
"""
Input: a str (the target url)
Returns: a Beautiful Soup object (the parsed page source)
-----------------------------------------------------------------------
Retrieves the target page's contents and passes them to Beautiful Soup.
-----------------------------------------------------------------------
"""
options = webdriver.ChromeOptions()
options.add_argument('--ignore-certificate-errors')
options.add_argument('--incognito')
options.add_argument('--headless')
driver = webdriver.Chrome(options=options)
time.sleep(10) # sleep for 10s to allow the page to load
target_page = url
driver.get(target_page)
page_source = driver.page_source #get the contents
soup = BeautifulSoup(page_source)
driver.quit() # close the driver
return souptarget_url ="https://www.rollingstone.com/music/music-lists/500-greatest-songs-of-all-time-151127/smokey-robinson-and-the-miracles-shop-around-71184/"
soup = get_page_source(target_url) # pass the HTML contents to Beautiful Soup for parsing.
现在使用 Beautiful Soup 的find_all
方法,并传递适当的 CSS 标识符,我们就可以检索到我们定位的所有项目。
song_all = soup.find_all('h2', {'class':'c-gallery-vertical-album__title'})
song_all[0]<h2 class="c-gallery-vertical-album__title">Smokey Robinson and the Miracles, 'Shop Around'</h2>
好吧,我们有进展了。让我们进一步挖掘。
第二步:用正则表达式清理
列表项不仅包含我们正在寻找的数据,还包含 HTML 标签。为了只获取数据,我们使用get_text()
如下:
song_all[0].get_text()"Smokey Robinson and the Miracles, 'Shop Around'"
可恶。很多格式,空白,额外的引号。我选择通过re
模块使用正则表达式或 Regex 来清理数据。
RegEx 是一种功能强大的微编程语言,对于搜索字符串中的字符模式非常有价值。它有一个相当陡峭的学习曲线,所以我尽量经常使用它:正如他们所说:练习,练习,练习!(点击下面的正则表达式介绍)。
使用 Python 逐步介绍正则表达式
medium.com](https://medium.com/better-programming/introduction-to-regex-8c18abdd4f70)**
def strip_extra_chars(line):
"""
Strips non-alphanumerical, whitespace and newline characters away from string
"""
line = line.get_text()
line = re.sub("\A\S[^a-zA-z0-9]", "", line) # remove any non-whitespace non-alphanumeric character from the beginning of the line
line = re.sub("[’‘]", "", line).strip() # get rid of extra quotes and remove whitespace with .strip()
line = re.sub("\n", "", line) # get rid of newlines
return line.strip()
函数strip_extra_chars
将从我们的数据中提取一行,去掉所有的垃圾,包括那些讨厌的引号。更多细节见函数中的注释,试试这个是测试和学习 RegEx 的好资源。
步骤 3:遍历列表
我们差不多完成了。还记得一开始我们注意到我们的网页只包含 50 首歌曲,因此我们需要迭代内容吗?如果我们通过顶部导航菜单上的ctrl-clicking
再次检查页面,我们会发现它指向我们需要的 URL。这里我们定义了一个get_links
函数,它将 URL 存储在一个列表中。
然后,我们可以轻松地遍历列表并检索所有数据。
def get_links(url):
options = webdriver.ChromeOptions()
options.add_argument('--ignore-certificate-errors')
options.add_argument('--incognito')
options.add_argument('--headless')
options.add_argument("--start-maximized");
driver = webdriver.Chrome(options=options)
# note that in order to get all the urls we need to set the browser's window to max size
driver.set_window_size(1024, 768)
time.sleep(10) # sleep for 10s to allow the page to load
target_page = url
driver.get(target_page)
header = driver.find_element_by_id('pmc-gallery-list-nav-bar-render')
links = header.find_elements_by_tag_name('a')
urls = [link.get_attribute("href") for link in links]
driver.quit()
return urlsurls = get_links(target_url)
现在我们有了 URL,下一步是将数据存储在列表中。对于每个 URL,我们将启动我们的get_page_source
功能,提取相关数据,并存储它。我们将每个页面的数据存储到两个列表中:song_all
,其中包含艺术家和歌曲的标题,以及other_all
,其中包含关于作者、制作人和发行日期的数据。通过对它们进行迭代,我们提取相关的文本,通过调用我们的strip_extra_chars
函数去掉多余的字符和格式,并将结果附加到我们之前初始化的三个空列表中:一个用于艺术家,一个用于标题,一个用于其他信息(接下来我们将对其进行解析)。
songs = []
artists = []
other_data = []for url in urls:
print(f"retrieving data from {url}")
soup = get_page_source(url)
song_all = soup.find_all('h2', {'class':'c-gallery-vertical-album__title'})
other_all = soup.find_all(attrs={'class': 'c-gallery-vertical-album__description'})
for song in song_all:
song = strip_extra_chars(song)
title = song.split(',')[1]
title_inner = title[2:len(title)-1]
songs.append(title_inner)
artists.append(song.split(',')[0])
for other in other_all:
other = strip_extra_chars(other)
other_data.append(other)
driver.quit()
借助正则表达式和一点字符串切片将清理和分割包含在other
中的数据。我们在一个split_others
函数中这样做,然后我们在一个循环中调用这个函数来产生三个作者、制作人和发布日期的列表。
def split_others(line):
x = "(Writers:|Writer:)"
y = "(Producer:|Producers:)"
z = "Released:"
a = re.split(x, line)
a = re.split(y, a[2])
writers = a[0].strip()
b = re.split(z, a[2])
producers = b[0]
released = b[1].split(',')[0]
released
return writers, producers, releasedwriters = []
producer = []
release_date = []
for item in other_data:
w, p, r = split_others(item)
writers.append(w.strip())
producer.append(p.strip())
release_date.append(r[-2:].strip())
第四步:把所有的东西放在一起!
就是这样!我们已经取回了我们的数据。我们现在可以使用字典并将我们的数据传递给熊猫,以便将其存储在数据帧中。
d = {'Artist':artists, 'Song title': songs, 'Writers': writers, 'Producer': producer, 'Year': release_date}
df = pd.DataFrame(data=d)
(可选:因为抓取相当耗时,我将把我们检索到的数据存储在一个pickle
— 中,这个是一个很好的教程。要将其加载回来,您需要用三重引号取消对该部分的注释。
import pickle
filename = 'ROLLING_STONE_DATA'
outfile = open(filename,'wb')
pickle.dump(d,outfile)
outfile.close()
'''
filename = 'ROLLING_STONE_DATA'
infile = open(filename,'rb')
d = pickle.load(infile)
infile.close()
'''df = pd.DataFrame(data=d)
既然我们都是好奇的动物,那就稍微尝一尝,看看谁是唱歌最多的艺人,用matplotlib
来剧情结果。
top_10 = df['Artist'].value_counts()[:10]plt.barh(top_10.index, top_10)<BarContainer object of 10 artists>
Romulus:理解并发性和弹性范围锁的案例研究
超越一维串行程序,开发多核世界
下面的文章希望通过构建一个调度器(名为 Romulus)的案例研究,将读者带入一个并发性、弹性和现代计算趋势的旅程,该调度器可以自动将任何串行数据结构转换为高性能的并发键值存储。通过将分布式计算和数据库设计的概念引入本地内存,范围锁的分析和可扩展性方面的新范例将形成一系列简单而强大的治理原则,这些原则根据目标争用的功能对数据进行分区和保护。
来源:迈克尔·克里姆根 via Baeldung
介绍
正如 ASP los’17 的最佳论文所描述的:
并发数据结构被用在软件栈中的任何地方,从内核(例如,用于调度的优先级队列),到应用程序库(例如,尝试存储器分配),到应用程序(例如,用于索引的平衡树)。这些数据结构效率低下时,会降低性能。”Calciu et。铝
直到最近,由于单核机器的大规模改进,计算机的能力才得以复合。性能界限每两年翻一番,由于当时的应用程序还不够先进,无法满足现代高性能计算的要求,因此串行程序主宰了应用层。渐渐地,摩尔定律的经典形式消亡了。为了重振它的趋势,程序员开始将他们的工作负载分布在同步的资源上,并分配到专门的计算单元中(你好,GPU)。今天,计算不再由一维的串行程序来定义。相反,软件已经超越了本地内存寄存器的逻辑,进入了多个内核,在这些内核中,它们必须在可变的位置和时间下,在它们的并发事务中遵循某种形式的一致性和治理。
来源:卡尔·鲁普
简单的同步技术,比如粗粒度锁,提供了一种简单但不可扩展的方法来管理共享内存。另一方面,细粒度的异步技术对于普通开发人员来说可能过于复杂。开发这些内核对于构建任何高性能计算环境的基础至关重要。但是为什么不为所有的应用程序开发商用硬件的全部功能呢?希望这个案例研究可以为日常开发人员提供一个新的理解,从基本原则的角度来构建并发系统并分析其中的性能。对于更有经验的开发人员,该研究提出了一种更强大的范围锁方法,该方法利用基本的启发式算法,在任何不平衡的工作负载下主动地追逐和收敛到争用级别。如果你还在阅读,系好安全带准备长途旅行。
形式摘要
以下摘要将构成开发和表征 Romulus 的指南:
Romulus 在每个事务中注入一个调度器,以消除一系列弹性分区之间的争用,通过自适应读取-复制-更新(RCU)来同步这些库,并为所有工作负载提供稳定的执行状态。Romulus 的设计灵感来自 MapReduce:一个简单的框架,它并行地编排各种任务的处理,利用状态空间的全局视图来分布和管理跨资源的通信。Romulus 的主要目的是自动将任何串行 API 翻译成并发数据结构,同时达到手工制作的最先进算法的性能。然而,它最重要的贡献是一组竞争试探法,为系统提供了向目标竞争级别收敛的能力。更详细地说,Romulus 通过采用一种新颖的锁设计,根据实时线程冲突来拆分和合并分区,从而快速适应不平衡的工作负载。因此,这种方法可以扩展到其他应用程序中,在这些应用程序中,当不对称的工作负载超越其系统时,稀缺资源必须在线适应。此外,Romulus 通过提供一种在有序键值存储中进行有效范围查询的算法,将数据库设计中的概念引入本地内存。总的来说,Romulus 提出了一种调度算法,通过即插即用部署模型实现可预测的并行性。
有三个基本目标将推动该调度器架构中的决策:
(1)可扩展到任何有序键值存储的简单方法,也接近最先进的手工解决方案的性能(2)保证跨所有操作(包括范围查询)的可序列化性
(3)通过冲突试探法可预测的争用
(4)对不对称工作负载的强大容忍度
最终设计预览
箱子/球的理论原理
眼前的基本问题是一个调度问题:如何将输入连接到计算资源的公平分布(类似于 MapReduce ),该计算资源以预定义的争用级别为目标,并通过数百个线程进行扩展。摘要暗示 Romulus 将把数据分成一系列分区。因此,在进入 Romulus 的设计之前,对需要多少个分区来达到冲突率的第一原理分析可以(I)明确所追求的平衡/行为(ii)快速揭示性能预期。此外,它将提供一个(非常松散的)正式模型,通过将 Romulus 上的预期冲突映射到单个分区中的测量来证明争用的收敛性。正如后续章节所揭示的,当线程确定一系列离散测量违反了分区的预期阈值时,它们会将分区分成两半,以消除争用并适应不平衡的工作负载。因此,以下部分对我们的第三和第四个目标至关重要。
数学分析是通过对球中的经典箱问题的先前工作的垂直整合形成的:将 M 个球放入 N 个箱中的概率结果。这个模型以前已经在计算机科学中以多种形式应用于负载平衡。
箱柜/球数学模型
在最基本的模型中,M 个球通过一个随机函数——产生一个均匀分布——并被放入 N 个箱子中的一个。以下部分集中于理解三个重要现象:(I)观察不到碰撞的概率(ii)在单个箱中观察到大于 K 的高度的概率(iii)在所有 N 个箱中观察到大于 K 的高度的概率。这将有助于我们理解与线程相比,如何扩展分区的数量。
第一种情况:观察不到碰撞的概率
生日悖论再次上演
如果我们让 X 表示在所有箱中没有观察到碰撞的概率,并假设 M ≤ N,那么通常 M 个球必须放置在 N 个唯一的箱中。通过采用泰勒级数近似,并将其限制在一个可忽略的误差范围内(已知 u = 1/N 很小),我们可以这样估计 Pr[X]:
对于常数 Pr[X] = C,可以看出 C = O(m/n);也就是说,为了保持恒定的无冲突概率,箱的数量必须以 n = 0(m)的比率缩放
第二种情况:一个箱内的碰撞
设 Pr[Xi ≥ K]代表在单个 Xi 中至少 K 次碰撞的概率。这可以表示为:
对于常数 Pr[xi ≥ K] = C,如果 N ≥ M,则保持恒定期望高度所需的面元必须按比例缩放到 N = O(M)。无论输入的绝对值如何,对于 N = M,该预期高度保持相对恒定。
第三种情况:N 个箱的 K 次碰撞
设 Pr[X ≥k]表示在所有 N 个面元上至少发生 K 次碰撞的概率。假设 M= O(N ),并且每个面元彼此独立,则通过并集规则形成上界:
我们估计 M:N 的比值为 1。虽然在 M:N = 0.1 的未来讨论中可能不会出现这种情况,但上限仍将在这种情况下成立,并为进一步的数值分析提供足够的背景。应用这个比值,可以发现当 N(因此 M)一起向更大的值增加时,K 如何变化:
为什么 K 的预期值在单个容器中保持不变,但随着 N 和 M 一起缩放,一系列容器的预期值会增加?从逻辑的角度来看,随着我们增加出现差异的机会,我们增加了 K 在任何单个桶中远远超出其预期高度的机会。
结果
这些推导对罗慕路斯的三个期望很重要:
(I)为了保持不发生冲突的恒定概率,必须以 N = O(M)的比率相对于球的数量指数地缩放容器的数量。Romulus 不会遵循这种方法,因为这对于高线程数是不切实际的,因此,调度器不会试图消除所有冲突
(ii)如果将容器的数量与球的数量成线性比例,则每个容器中球的预期平均高度保持不变。但是,对于较大的 N 和 M 值,由于方差的性质,在热点中任何离散时间点的冲突可能更高。注意 Romulus 将遵循这个模型,因此,它将试图保持一个目标冲突率
(iii)当从单个仓构建整个系统的视图时,假设工作负载一致,可以创建关于系统状态的置信区间。更具体地,通过测量单个箱柜上的 K(即球队列的高度),可以确定其值是否持续违反阈值并超过概率。如果是这样的话,调度器应该预测一个偏斜的工作负载已经超越了系统并违反了均匀分布假设
毫无疑问,这是一个超级粗糙的模型(带有许多未言明的假设),但对于理解未来的总体趋势很有帮助。
数值分析
让我们将这个旧的视图转换成一个线程对库执行更新操作的新视图
下一节在线程在一系列分区上执行更新操作的理论环境中,证实了箱成球问题的理论趋势,并揭示了并发环境中两个明显的锁现象:(1)将锁分区减少到更细粒度的范围(即增加箱的数量)对于消除争用具有递减的回报(2)作为结果,给定无限资源的最佳理论配置是将锁的数量最大化到尽可能最小的分区中。然而,在许多情况下,系统只有少量的资源可以消耗。因此,利用范围锁来实现适当的并行性,而没有 40 字节 pthread_lock_t 的内存开销。然而,最重要的是,由于向更细粒度的范围减少锁分区具有递减的回报,Romulus 理论上可以利用更少数量级的锁,同时在许多场景中仍然接近手工制作的性能。
为了将经典的箱柜转化为球的问题建模,我们形成了值域锁的概念;存在一个 N 个仓的锁表——代表一系列 N 个抽象分区——它独立于底层存储层的实际大小。线程的数量代表了球的数量,它们通过即时插入/移除来跨这些分区操作。为了观察争用超时,我们对一个无限的迭代游戏建模如下:
1)在游戏开始时,线程被随机放入一个分区/箱(即锁)
2)每个分区都有一个等待完成的操作队列。对于游戏中的每一轮,一个线程充当系统中的并行代理。队列前面的所有线程都被清空,将自己回收到另一个分区中。他们通过遵循均匀分布的随机数生成来选择下一个分区。这表示一个线程获得一个分区的所有权,执行一个操作,释放该区域,然后继续下一个操作。所有不在队列前面(即在队列中其他线程后面)的线程不执行任何动作,而是等待,直到它们在未来的循环中移动到队列的前面。
3)游戏永远重复自己,类似于真实的系统
该模型有许多重要的假设,最明显的是:所有操作都是瞬时的,每轮完成一次,工作负载具有均匀分布,并且消除了内存访问、线程间通信和其他特定于体系结构的开销的可变性。当然,真实的系统有资源限制,比如内存和 I/O,它们会改变价值等式。然而,正如未来的测试床所揭示的,总体趋势保持不变,并为分析奠定了基础。实验在 Python 中以 10K、100K 和 1M 回合进行。无论游戏时间长短,结果都趋向于相同的值
左边的两个图描绘了在两种极端设置下游戏每一轮的队列高度,希望提供高争用与低争用环境中冲突变化的定性说明。第一张图片展示了一个有 64 个线程和 48 个分区的游戏。第二个图像表示一个游戏,有 64 个线程和 640 个分区。
在第一个游戏中,我们可以看到当分区数量太少时可能发生的灾难性影响。在第二场比赛中,虽然存在许多争夺点,但绝大多数操作都没有冲突。下面的实验将计划第二种情况,Romulus 试图模仿这种情况以获得更好的并行性/性能。
在该图中,我们将活动队列的平均高度定为 1.05,并绘制了在不同线程速率下达到该高度所需的分区数量。这是通过以恒定的 100K 回合玩一些游戏并发现产生适当平均高度的最接近的分区数目来计算的。本实验的目的是了解如何在线程间保持目标争用级别;从结果和先前的分析可以确认,对于 M / N <1,必须将分区的数量缩放为线程数量的线性函数。
在该图中,我们重申了这一点,并假设分区数量为 10 *个线程,将平均高度表示为线程数量的函数。包含了锁的最佳数量(底层结构的大小,表示为 1M ),以展示两种方法的一致差异
最后,这最后一个实验说明了增加分区在消除争用方面的好处在减少。从逻辑上讲,这是有道理的。有一个箱子和 64 个球,一个箱子可以装 64 个球。随着两个箱子的增加,球平均分裂,平均高度达到 32。有了四个容器,球再次均匀分裂,但体积仅从 32 个容器减少到 16 个容器。因此,随着时间的推移,分离的边际量继续减少。
这些图表揭示了罗穆卢斯争用计划的首要原则中的重要期望。首先也是最重要的,Romulus 在理论上永远无法击败手工制作的经典读/写工作负载,它表现出两个特征:( 1)无等待遍历;( 2)每个元素一个锁;( 3)搜索成本相当于转换层。这是因为 Romulus 并不试图消除其系统中的所有竞争;它在某种程度上限制了并行性,即在一系列元素上获取所有权,将粗粒度的锁分布在更小的分区上。因此,它将遭受更多的争用,导致冲突线程的性能下降一个数量级。然而,实际上,达到合理的并行水平所需的范围数量将比元素数量小一个数量级(假设 10 *线程< <映射大小)
回到设计罗慕路斯:方法论
Romulus 将代表一个调度器,它将一个抽象数据结构分割成一系列弹性分区。这些容器不限于任何大小,而是动态地适应来自应用程序线程的不相等的力。对于有序的键值存储,这些分区代表一系列键。假设程序员总是为有序的键值存储提供 API。
罗慕路斯的抽象方法论
塑造 Romulus 需要三个重要的层:适应性翻译层、数据层和同步层。在翻译层,算法必须为给定的输入键产生适当的分区。但是,它不应该表示类似于哈希映射的静态范围集,因为人们必须反射性地修改分区以展平倾斜的输入。在数据层,程序员为添加、包含和删除提供了一个抽象 API 来存储和访问一个元素。同步层确保操作的严格可串行化,并且通常跨转换层和数据层集成自身;这对于在每个分区中创建隔离和其他可序列化保证非常重要。对于图中描述的方法,我们利用读取-拷贝-更新(RCU)并创建两个数据拷贝。写入者视图(数据的活动部分)是写入者独立执行更新操作的状态空间,并且需要一个互斥锁。读者视图(数据的副本部分)使读者能够继续无等待地遍历系统。因此,Romulus 中的争用只来自更新分区的写线程。这就是为什么我们没有把读者包括在上面的数字分析中。
罗穆卢斯的方法论为其具体实现产生了许多变体;也就是说,它可以被视为一种抽象和模块化的并发方法(但最适合于有序的键值存储)。因此,从程序员的角度来看,此后的算法集中于优化性能,尽管仍然存在许多其他的适应,并提供正确的语义。在定义 Romulus 的具体实现之前,调度算法必须首先应用优先竞争分析的原则来决定如何最初划分范围,并求解阈值,使 Romulus 能够动态适应偏差输入。
应用竞争试探法
在 Romulus 中,偏斜的输入分布可以被描述为导致争用的输入操作的不平等加权。这意味着不对称是通过两种现象形成的:(I)不同权重的操作(ii)非均匀的访问分布。在基于违反竞争试探法来分割范围时,罗穆卢斯方法对歪斜的原因是不可知的,只是作为一种反射力
为了将锁队列的平均高度定为 1.05(理论上),系统分成 10 * t 个分区,其中 t 是应用程序中的线程数(这在实践中证明效果很好,尽管常数会根据机器的具体情况而变化)。在执行过程中,Romulus 遵循两个简单的启发式方法来分割/合并范围:
(1)如果应用程序线程测量到竞争高于阈值 K,则它们在应用它们的更新操作之前将一个范围分成两个新的分区。k 是通过将 95%的置信区间应用于先前的理论分析和新的假设 N = 10 * M 来确定的,其中 N =分区的数量,M =线程的数量:
在队列中等待的线程数量的测量有很大的差异,特别是当系统中线程和分区的数量增加时,正如之前所看到的那样。因此,Romulus 通过强制线程投票赞成拆分来减少误报的数量,并且在投票被批准后,拆分继续进行
(2)在获得一系列范围的访问分布之后,当两个分区的邻域被访问的次数低于每个分区的平均访问次数的 2 倍时,后台线程将合并这两个分区。虽然合并很重要,但它可以被视为节省资源的一种方式,而不是提高任何操作的吞吐量(未来的分析表明它不会影响范围查询性能)
Romulus 实现
这描绘了 Romulus 的标准实现的快照,而一个不对称的工作负载在一段时间内超越了系统。Romulus 的目标不是创建一个每个元素都有一个锁的散列映射,而是节省资源并在面对范围查询时表现良好。
转换层被实现为定制的散列树。线程将散列到的树的级别是具有完整的内部分区集的最底层。
数据层是有序的键值存储;有效支持范围查询的要求。
同步层利用 RCU 的自定义适应,特别是在提交协议中,以便在范围查询和单个元素操作中为读者进行优化。同步层深入到转换层,在转换层中存储了一个原子计数器,以便向写线程通知执行一个范围的分区的范围查询的数量。在 Romulus 的抽象需求中,翻译层中的上层树——从分区集向上构建——是不必要的。然而,为了跨分区同步范围查询,该模型扩展了 Romulus 的要求,并形成了一个称为 CRRQS(跨区域范围查询)的上层。接下来的几节从工作负载的要求和对堆栈的更深层次的影响开始,详细介绍了每一层的重要方面。
批量操作(范围查询)
为了提供可序列化的范围查询,Romulus 通过集成多粒度同步从数据库中引入概念。有两种极端的优化方法:基于锁的查询和无锁范围查询。对于跨许多单独分区的大范围查询,基于锁的实现会导致显著的性能下降,同时在各种类型的操作之间实现了公平性。例如,系统可以在活动分区中实现读写锁,阻止更新发生,直到完成它们的操作。获取一系列这些锁的开销对于实际应用来说太高了。因此,在实验开始时,Romulus 在分区的顶部构建了一个上层树层,其中表示要同步的范围的粒度。当范围查询想要在一个范围内操作时,它从较高的树层的根向下搜索,直到找到符合其界限的最小范围。此后,范围查询自动递增节点中的计数器,并继续执行其操作。这向系统中的所有其他代理发出正在执行范围查询的信号,通过将必要的状态集中到单个位置,以恒定成本在无锁机制中同步这些操作。下面讨论提交操作对写线程的影响。尽管如此,范围查询线程的两个附加方面是,它们必须在操作开始时读取指示时间戳的全局纪元,并且它们必须使用该时间戳来指示是否用分区上的最近操作来更新它们的结果。不过,这一需求的成本是不变的,不会对大型查询产生显著影响。
RCU
在 Romulus 中使用读取-复制-更新(RCU)作为同步机制具有重要的二阶效应。首先,它使读操作能够无锁地访问分区。其次,它需要额外的存储空间和操作开销。Romulus 的翻译层中的一个分区通过以下元数据表示(不包括指向树中左右子节点的指针):
因此,调度程序将数据复制到称为副本和活动的两种结构中。复制指针为读线程提供进入底层存储层的入口,而活动指针为写线程提供进入另一层的入口。在经典的 RCU 算法中,人们会将手头的内存复制到一个线程本地地址,并在提交之前隔离执行操作。在此实现中,已经提供了数据,以避免每次更新时复制大范围内存所带来的存储和计算成本。相反,为了保持操作的可序列化性,编写器线程遵循不同的操作序列。首先,写线程通过自定义互斥体获得分区的所有权。这个互斥体包含一个“无效”状态,它表示先前发生了一个合并/拆分操作,以及争用锁的写入程序的数量。假设没有任何东西被无效,并且不需要发生合并/拆分,写线程在活动数据结构上执行其更新操作。此后,当且仅当提交操作有效时,写线程执行原子比较和交换(CAS)以用新鲜状态替换副本指针。这表示更新操作的线性化点。由于调度器知道活动结构上的更新操作的结果,当且仅当操作返回真(即,成功地更新了元素)时,线程然后将等待所有读取器线程离开旧的副本,然后在执行相同的操作时将其陈旧的结构更新到新的状态。完成后,线程完成两个分区到较新版本的复制,并释放锁。RCU 操作中的一个关键点是确定提交操作是否有效。在经典的 RCU 算法中,写线程不需要等待系统中的其他代理提交。相反,提交操作以非阻塞方式进行。当范围查询被启用时,写线程必须确保当它认为范围查询可能在它的分区上操作时,它没有执行更新操作。为了确定范围查询是否依赖于分区,编写器向上遍历翻译层,并检查每个父级没有范围查询,如下所示:
如果它继续通过这个依赖路径,并且似乎没有范围查询发生,那么写线程将认为提交操作是有效的。当更新线程遍历从叶到根的路径中的元素时,出现了一个问题,并且范围查询此后在写入者的路径视图中以较低级呈现陈旧状态开始:
这个问题有一个简单的解决方案。在提交更新之前,范围查询会将操作记录到分区的 MRO 中,并带有相应的全局时间戳。在范围查询记录了它对分区的读操作之后,它将检查时间戳。如果发现节点的时间戳违反了范围查询的时间戳,那么它将撤销其本地记录中最近的操作。因此,范围查询要么不会遇到更新冲突,要么能够在没有太多开销的情况下解决它。重要的是要注意,在与上述相同的条件下,如果另一个写线程希望更新该分区,它将不会提交另一个操作(这将覆盖 MRO ),直到范围查询完成,因为它肯定会在路径的更新版本中看到范围查询的信号。因此,可以说写线程将一直延迟自己,直到不再需要 MRO,并且它可以替换 MRO,而没有进一步的影响。毫无疑问,上面的实现针对大量读取的实验进行了优化,在这些实验中,范围查询跨一系列分区执行。写操作不仅要花更长的时间通过从叶到根的路径来提交操作,而且还会因为阻塞范围查询而被饿死。
哈希树
散列树代表 Romulus 中的转换层:给定一个输入键,它提供存储该键的分区。对于读/写操作,调度器首先散列关键字,以便为线程提供要遍历的初始起始级别。哈希层的位置不会影响正确性,但会影响性能,并希望通过从较低的节点开始来降低转换层搜索成本。从这里开始,线程必须向下搜索,直到到达对应于保存其键的分区的叶节点。树叶代表数据层,包含指向结构的正确指针;它们还被连接起来,以使范围查询能够跨分区线性继续。读取操作无锁进入副本,而写入操作遵循上面的自定义 RCU 算法。但是,在获取锁之前,写线程会增加一个变量,该变量表示分区中写线程冲突的数量。
步骤 1:读取等待队列的大小
然后,写入方记录分区中写入方冲突数量的测量结果。如果度量值高于预先计算的阈值(如前面的分析所示),那么他们投票决定分割分区。如果获得了足够的投票,一旦任何写线程获得了锁的所有权,它们将执行拆分操作
第 2 步:通过 fork(K) API 执行一个 split 操作,其中 K 表示要在范围内进行拆分的值。
在活动数据结构内完成拆分操作后,它会通过向当前分区添加两个新叶而传播到哈希树中。两个新的活动分区将实际上被放入叶子的副本指针中,因为这代表了 RCU 原语中预先的提交。从这里开始,由于所有新的读取器都被重定向到更新的结构,写入器线程将等待所有旧的读取器完成,然后对陈旧的副本结构执行相同的操作。最后,线程在自定义互斥体上指示它不再有效,所有竞争的线程重新开始它们的操作,慢慢进入更新的状态空间,并希望均匀地分散在两个新的区域。
翻译层的自反递归性质
这个过程可以根据需要重复多次,以消除阈值以下的争用。
合并/拆分
近年来,在设计跨公共数据结构的合并/拆分操作方面已经开展了许多工作。SkipLists、红黑树、链表和许多其他结构可以执行分割操作,其时间复杂度与它们的搜索操作相同。因此,当程序员在这种假设下提供 fork(K)的实现时,Romulus 中的 split 操作在算法上使其中一个线程完成更新操作的时间加倍(排除范围查询的存在)。上面提供的用于合并/拆分的实现利用应用程序线程来自己识别争用,并通过同步方法在它们的关键路径内执行操作。这在算法中产生了错误分类热点的风险,并且需要精确的阈值模型来衡量。有人可能会说,将这项工作卸载到后台线程可能有意义;然而,进一步的分析表明并非如此。通过助手线程对争用做出异步、迟缓的反应可能有助于系统完全准确地达到稳定状态,但是用单个助手线程识别大型数据结构的热点的时间和复杂性将无法立即解决不平衡的工作负载。干草堆里的一根针的故事。结果,由于持续的冲突,这些线程的并行性会显著降低。因此,在大多数情况下,解决问题的时间可能比应用程序线程自己处理拆分花费的时间更多。此外,更重要的一点是,如果不对称继续变化,那么异步线程将很可能获得冲突的误报,并且可能永远无法解决动态不对称。这扩展到异步估计的更一般的问题,因为竞争的近似变得更加难以为动态系统建模。这种对不平衡工作负载的忽视会给需要跨不同场景弹性的系统带来灾难,因此会阻碍 Romulus 的主流应用。
其他注释
从前面的图和未来的分析中可以看出,转换层的形状反映了穿过罗穆卢斯的偏斜分布;也就是说,对于经常发生冲突的热点,Romulus 将深化翻译层,以消除更多范围内的争用。一个要考虑的有趣问题是,给定一个偏斜分布,Romulus 的翻译层是否收敛到一个稳定状态。非常宽松的形式分析和实验结果证明,Romulus 在给定偏斜分布的情况下确实收敛到稳定状态,并且它通过主动适应冲突中的应用程序线程本身的争用来快速收敛。因此,Romulus 将在翻译层实现顺序行为,直到新的偏斜超越系统并强制分区更新。在线系统经常困扰性能的一个方面是内存回收行为:安全地释放内存以供重用。Romulus 已经将内存回收的开销融入了它的分区隔离中;也就是说,不需要向 Romulus 添加任何新的元数据来保护内存的释放。此外,可以同步回收内存,而不是异步回收内存(这是一种常见的方法,它会延迟回收,直到其他线程无法在通往退休元素的路径上继续前进)
结果呢
作为一种调度算法,Romulus 中性能的一般趋势应该事先知道:
(1)没有其他瓶颈,如内存,Romulus 将很好地从单线程基线扩展到更高的线程数
(2)倾斜的输入将很快得到解决,并将争用分散到以前的级别
这些在下面的图表和分析中得到证实。另一方面,与竞争对手的相对绩效和其中的垄断案例很难量化;一个人必须遵循一个发现过程。为了准确了解 Romulus 的性能,应该部署一个串行跳表,并与两个手工制作的、最先进的并发跳表解决方案进行比较:NUMASK 和 Herihly。这分别代表了两种最快的无锁和基于锁的结构(至少目前如此)。以下实验中使用的测试平台由一台配备 4 枚英特尔至强白金 8160 处理器的服务器组成,总共提供 192 个线程。有 4 个插槽通过 4 个 NUMA 区(一对一)和 768 GB 内存托管 4 个处理器。所有数字是十次试验的平均值。在实验中,应用程序线程平均分布在 NUMA 区域。注意:竞争对手不提供可序列化的范围查询解决方案,因此我们根据他们的上限进行基准测试,因为他们不安全地遍历结构,并在其批量操作中显示陈旧的结果。尽管如此,对于 200 万大小的数据结构,10%的 500 个预期元素的范围查询、70%的读取和 20%的更新操作,可以观察到以下性能:
synchrobench 中的吞吐量比较
该结构在手工制作的解决方案的性能上限方面有很好的伸缩性,直到 60 个线程。尽管如此,罗穆卢斯在即插即用加速器方面做得非常好。它比 Node Replication(ASPLOS ’ 17 的最佳论文,从一开始就是报价的来源)更好的一个领域是他们订购的关键价值商店;不过,在堆栈/队列/其他结构中,它会比较差,因为在这些结构中,操作只是从结构的前面和后面弹出来——它们在那里找到了自己的最佳位置。
在跳过列表键值存储中,NR 由于其复制方法中的单个编写器要求而不能很好地扩展
基准链接列表是一个有趣但无效的实验,因为翻译层卓越的算法性能降低了搜索成本,导致了超线性加速。
既然我们已经知道了在 Skip Lists 中手工制作的方法的性能,那么 Romulus 的其余声明必须针对手边的数据结构进行研究;具体来说,(Romulus 在偏斜分布下的表现如何?(ii)不同的存储桶如何影响更新/RQ 性能?
确定合并/拆分算法成功与否的两个最重要的衡量标准是它适应争用的速度(即解决争用的时间)以及与均匀分布相比对性能的总体影响。罗穆卢斯在这两方面都表现出色。下图拍摄了实验中每毫秒吞吐量的快照,以创建吞吐量随时间变化的视图。应用程序线程在~5 秒钟引入一个偏斜分布,以便观察(1) Romulus 如何适应采用分叉/合并操作的工作负载(2)Romulus 未采用分叉/合并的天真版本的性能增益
这证实,即使罗穆卢斯牺牲了几毫秒的短期性能,系统的长期性能也会因为即将到来的偏差而迅速恢复到接近先前的水平。
对于相同的工作负载,系统的自反性质可以通过在实验结束时查看 Romulus 叶节点中的三个访问分布来描述。下图绘制了(1)输入偏斜(即无分叉/合并时的访问分布)(2)具有 48 个线程和 200 万大小的 100%更新操作的实验结果(即启用分叉/合并时的访问分布),以及(3)给定 Romulus 部署的试探法集,该实验的目标理论结果(K = 3)。它通过查看每个分区的访问百分比(单个分区中观察到的访问/跨分区的总访问)来实现这一点
Romulus 低于理论目标,并且在分区数量上的开销比预期的多,很可能是由于悲观的宽松竞争启发式算法(K = 3)。具有强保证的明确定义的阈值对于 Romulus 收敛到分区的目标最终平衡状态是至关重要的。阈值太高,启发式算法无法正确适应不平衡的工作负载。阈值太低,系统消耗的资源超过初始目标,并且对输入分布进行错误分类。
最后,一个有趣的观察结果是,在分别希望合并和分割范围时,范围查询和更新会产生冲突。下图显示了当增加纯范围查询和更新工作负载的存储桶时,吞吐量如何变化
范围查询 v 根据存储桶更新吞吐量增长。(i) 64 个线程(ii)100 万个大小(iii)完整数据结构的范围查询(iv)对数轴(v)标准化意味着将每个数字除以最大获得值,以限制 0 和 1 内的所有点
总的来说,Romulus 可以用手工制作的最先进的解决方案很好地扩展,并且由于合并/分割算法的自反性质和竞争的概率方法,在偏斜分布下表现得相当好。我希望在这项工作中发现的更多的想法将被正式化,并使其成为现代系统中寻找一种简单的并发方法。
//
新手数据科学错误使十几项医学研究无效
克里斯蒂安·鲍文在 Unsplash 上的照片
母鸡吉勒·范德维勒注意到大量研究报告在预测准妈妈是否会早产方面近乎完美的准确性,他惊讶得目瞪口呆。这是巨大的。
婴儿难以忍受的高死亡率从一开始就一直困扰着人类。早产是这些过早死亡的主要原因,它困扰着美国十分之一的新生儿。如果有可能肯定地说一名妇女是否会提前分娩,就可以做好准备以减少并发症的风险。
然而,预测早产被证明是难以捉摸的。对于妇科医生来说,要确定一名女性是否提前分娩,他们必须考虑大量的风险因素,包括空气污染、家庭暴力和压力等难以理解的因素。到目前为止,专家们还没有弄清楚如此众多的潜在煽动者之间的复杂相互作用。
现在,在人工智能的帮助下,研究人员似乎已经成功解决了这个难题。兴奋之余,根特大学机器学习专业的博士候选人范德维尔招募了他的同行,并着手复制令人难以置信的结果。他一点也不知道他们即将踏上一场彻底的科学毁灭之旅,导致十几篇同行评议的文章被宣布无效。
任何机器学习系统的基础都是数据。为了让算法学会做出准确的预测,需要用大量相关的例子来教授它们。这些示例的集合称为数据集。
范德维尔遇到的所有看起来非同寻常的研究都是基于流行术语——早产 EHG 数据库。它包含了几百条记录,每条记录对应一次怀孕。每份记录依次包含临床变量,如母亲在产科医生就诊期间的年龄和体重,距离实际分娩的周数,以及由放置在腹部的电极测量的电信号。
通常情况下,医学数据集的敏感性使得原始研究之外的第三方研究人员无法访问它们。这使得繁殖变得极其复杂,如果不是不可能的话。因此,当 Vandewiele 的团队发现必要的数据集可以公开获得时,他们只能想象他们如释重负的叹息。只要按一下按钮,数据就是他们的了。
下载完数据后,是时候开始将其输入论文中概述的预测模型了。理想情况下,科学家们会开源他们的代码库,这样这一步就相当于仅仅运行一些现有的脚本。不幸的是,在我们生活的世界里,将研究代码库保密的做法在人工智能社区很常见。
这群人不是在困难面前退缩的人,他们卷起袖子开始工作。他们采用了显示最佳结果的文章,并完全复制了它的设置。但是当他们最终进行分析时,奇怪的事情发生了——获得的结果明显比报道的差。这些预测比随机预测好不了多少!
“我们肯定犯了一个错误,”范德维尔想。然而,在花了几天时间反复检查他们的每一行代码后,似乎没有任何问题。最终,他们的好奇心被挫败感所取代,团队放弃了第一个实验,并试图复制下一篇论文。同样的事情。该系统的运行情况比宣传的要糟糕得多。发生了什么事?他们偶然发现了一个阴谋吗?
现在完全疯狂,团队处于野兽模式。文章被重新实现左和右。尽管如此,没有一个复制品能达到承诺的近乎完美的预测精度。这就好像他们被困在一场希腊悲剧中,被一块无情的巨石折磨着,在他们即将把它送上山顶时,这块巨石掉头滚下山坡。
几个月过去了,毫无结果,在重复 11 项研究的巨大努力下,这个团队再也受不了了。但是就在他们准备认输的时候……一个突破。在将数据输入机器学习模型之前,只需对数据的组织方式进行简单的改变,Vandewiele 和他的合作者最终能够获得与原始研究相同的结果。唯一的问题是:这样的数据处理方案存在根本性的缺陷。
为了理解这个难题,我们需要更仔细地看看构建机器学习系统背后的方法论。
泛化的概念是人工智能的核心。为了有任何用处,一个根据输入训练的模型——例如母亲年龄和体重的组合——期望的输出——怀孕前的周数——是已知的,它应该能够将推广到新的、以前看不到的输入组合。这种算法就像一个学生在考试中死记硬背一些相似但不完全相同的问题。
相应地,机器学习中使用的数据集被一分为二。第一部分——训练集——用于教授算法,而第二部分——测试集——用于衡量模型对任务本质的理解程度。显然,正如任何称职的校长都可以证明的那样,这两套系统不能重叠是至关重要的。
除了对数据集进行分区,研究人员还需要确保它包含不同类型输出的可比数量的记录。继续类比,一个学生要在考试中取得好成绩,他需要同样多的练习不同类型的问题。一个只研究积分的初露头角的数学家是不会在求导这一节中胜出的。
然而,众所周知,医学数据集是不平衡的。足月-早产 EHG 数据库也不例外,包含的足月分娩记录几乎是早产记录的 7 倍。为了补偿这种不均衡,科学家将对应于少数类的数据点的副本添加到原始的不平衡数据集中。这个过程称为过采样。
让他们惊讶的是,范德维尔的团队发现,这些好得令人难以置信的研究的作者在将数据集一分为二之前进行了过采样。因为这种分割是随机进行的,所以在训练集和测试集中出现相同的数据点会产生毁灭性的副作用。实际上,在考试之前,模特们就已经看到了要被评估的问题。难怪他们的结果惊人。
虽然很明显,但这个错误在数据科学家中并不罕见。机器学习竞赛平台 Kaggle 的联合创始人本·哈姆纳(Ben Hamner)将信息从训练设备意外“泄露”到测试设备称为该公司的“头号挑战”
“我们很多人都犯过同样的错误,包括我自己。我想一个优秀的机器学习研究者的与众不同之处在于,你应该永远对近乎完美的结果持怀疑态度,”范德维尔说。
在这里,范德维尔也触及了一个更好的点。随着算法开始管理我们生活中越来越重要的方面,我们需要确保管理这些系统的专业人员无愧于他们的职责。虽然参加一些在线课程足以开始优化广告收入,但应该要求处理我们医疗数据的分析师接受更多培训。否则,我们就是在拿无辜的生命冒险。
避免机器学习中的新手错误
或者,如何不搞砸
来源:约翰·T 在 Unsplash
这是我 2019 年 1 月在麦吉尔大学演讲的书面版本。随后,安德烈·卡帕西写了一篇很好的帖子,更具技术性和深度学习的具体内容,但内容重叠——在这里查看 。
你可以通过你所犯错误的质量和数量来跟踪你对某个主题的专业知识的加深。这篇文章列出了我和我周围的人在机器学习中犯的一些更重复的错误,希望能加速你走出不熟练的浅水区,进入犯真正有趣错误的黑暗深水区。这不是一篇介绍性的文章:你需要在 Pytorch 或 Tensorflow 中破坏一些模型才能跟上。
我将尝试从中间分布中提取,但一些黄色和紫色的样本也可能会出现在那里。图片来源。
机器学习中常见的错误
我试图将我们在 ML 中看到的错误分为三大类,严重程度逐渐增加。
1.浪费你时间的错误
这种错误是令人遗憾的,但也是可以忍受的。在深度学习中,可怕的形状错误是最常见的,当你试图将大小不兼容的矩阵相乘时就会出现。
我不打算多谈这些错误,因为它们通常是显而易见的:程序失败了,你必须找出原因。你知道你犯了一个错误。你修复它,发现一个新的错误,然后循环重复。没人会受伤。
2.导致实验不准确的错误
这是一种代价更高的错误,因为你最终会做出糟糕的决定。
想想亚航飞行员的故事,他因为 GPS 不可靠而没有去马来西亚,而是去了墨尔本。如果你的导航有问题,你会很快到达你不想去的地方。
例如,如果您实现了一个添加了一堆参数的新特性,并将其与现有模型的性能进行比较,而没有重新进行超参数搜索,您可能会错误地认为您的新特性使事情变得更糟。事实上,您可能需要更加规范化,以揭示更具表现力的模型的好处。
随着时间的推移,导致实验不准确的错误会越来越多,因此尽早发现这些错误是非常有价值的。
3.让你相信你的结果比实际要好的错误
错误的大老板:犯了一个导致你高估表现的错误。这些很难发现,因为我们对看到它们抱有偏见*:当模型表现得令人惊讶地糟糕时,我们倾向于再看一眼,但当它表现得令人惊讶地好时,我们更有可能祝贺自己高超的直觉(本质上是一种形式的确认偏见)。*
高估的常见原因是过度适应您的测试集,您的数据不代表真实世界,或者只是搞砸了您的度量。(下面将详细介绍这些内容)。
如果你从这篇文章中只学到一样东西,那应该是这个。没有什么比意识到你的模型比你想象的更糟糕更令人尴尬或沮丧的了。
机器学习生命周期
大多数机器学习都被上面的香肠制作图所捕获。你获取一些数据,通过一个模型,用一些指标量化输出。我们来看看每个阶段都存在哪些让自己看起来很傻的机会。
结果是什么:度量
机器学习可以归结为试图降低你的损失函数值。
更在https://lossfunctions.tumblr.com
然而,损失函数从来就不是你想要优化的:它只是一个近似值。在训练分类任务的普通情况下也是如此:你在为训练或验证集交叉熵进行优化,但你实际上关心的是**测试集准确性(或 F1/AUC)。**在实际目标是一些松散的、不可微的东西的情况下(比如“制造一个听起来像人类的聊天机器人”),低保真度近似的问题甚至更加尖锐。
无论你的环境如何,如果你的损失函数不能代表模型的真实表现,你就有麻烦了。这里有一大堆搞乱这里的方法:
1.将一些训练集混合到测试集中
将训练数据混合到您的验证或测试集中很容易做到,并且将产生极好的模型性能(由您的不可靠的测试集评估),以及在现实世界中糟糕的性能。
当然,你的 train/val/test 应该是disjoint——包含不同的例子。有时你需要仔细考虑不相交集合的确切构成。这可以决定通过测试集的性能来量化哪种类型的归纳。例如,如果我们试图从收据中提取总价值,显然测试集应该包含从未见过的收据。但它是否也应该包括从未见过的商家,以向我们保证我们不会过度适应特定的商店?
有时你需要仔细考虑不相交集合的确切构成。这可以决定通过测试集的性能来量化哪种类型的归纳。
一种好的做法是将您的数据一次分成训练集、验证集和测试集,然后将它们放在文件系统中的不同文件夹中。无论在哪里读入数据,都要使命名超显式:例如, TrainDataLoader 和 TestDataLoader,。
2.错误指定损失函数
这很难做到,因为大多数框架会为你处理损失函数规范。
然而,有很多方法可以滥用给你的坚如磐石的实现。我在这里看到的两个最常见的错误是混淆损失函数是否期望接收概率分布或对数(即,是否需要添加 softmax),以及混淆回归和分类。
后者出奇的普遍,甚至在学术界也是如此。例如,亚马逊评论数据集包含评论和星级评定,经常被顶级实验室用作分类任务。这显然不太正确,因为 5 星评价更像 4 星评价,而不是 1 星评价(数据是有序的)。
错误指定您的测试功能
虽然深度学习的损失函数必须是可微的,但我们经常使用一套不同的不可微指标来表达测试时的性能。例如,在机器翻译和摘要中,我们分别使用 BLEU 或 ROUGE ,对于其他任务,我们可能使用准确度、精确度或召回率。
通常,这些比你的损失函数值更容易理解,损失函数值可能很难理解(方便的提示:如果数据集是平衡的,当你开始训练时,交叉熵应该是 -log[n_classes] )。因此,在培训期间尽可能多地记录测试集指标是个好主意,而不是等到测试时才使用它们。这将让你更好地了解训练的进展情况,防止你在结束训练后才发现问题。
选择测试函数时要小心。例如,您不能通过计算匹配字符的数量来使用准确性来描述序列模型的性能,因为序列之间的任何不对齐都将产生零准确性。因此需要使用编辑距离代替。选择错误的指标来评估你的模型是一次痛苦而难忘的经历。
继续使用序列建模示例:确保您理解特殊字符—通常是序列开始(SOS)、序列结束(EOS)和填充。如果你忘记将它们从你的计算中排除,你可能最终得到看起来很好的模型——但只是真正擅长预测充满填充的长序列。
我曾经做过一些语义解析的工作,目的是将自然语言语句转换成数据库查询,这些查询可以被评估以回答诸如“明天从蒙特利尔到亚特兰大有多少趟航班?”。为了表征其准确性,我们将候选数据库查询发送到数据库,并检查返回的内容是否与我们发送的真实数据库查询相匹配。在我犯的一个更严重的错误中,我设置了这样一种情况:如果您向数据库发送无意义的查询,数据库会无声地失败,返回一个“错误”字符串——然后我会发送预测的和真实的数据库查询的损坏版本。这两个函数都返回了字符串“error”——然后被计算为 100%准确。
这就引出了一个指导原则,那就是尝试设置事情,这样你犯的任何错误都只会使性能变得更差,并且总是查看你的模型实际做出的预测,而不仅仅是指标。
使用指标避免错误
- 在步骤 1 中运行所有指标
这就是 random 的样子。如果你的模特在没有任何训练的情况下表现得出奇的好,那你就搞砸了。
2。记录一切
ML 是一门定量学科,但统计学是骗人的。你的眼睛很少这样。是的,你应该记录你能想到的所有数字,但是你也应该以人类可读的方式记录模型的预测。
在 NLP 中,这通常意味着反转你的符号化,这可能会很痛苦——但 100%值得。它还能让你对模型训练的定性方面有宝贵的见解。例如,语言模型通常从学习输出像eeeeeeeeee<PAD><PAD><PAD>,
这样的字符串开始,因为这些是数据中最常见的字符。
如果您处理图像,记录东西可能更麻烦,因为您不能只将图像输出到文本文件或终端。我的同事使用 ASCII art 来克服这一点,使他们能够在训练过程中可视化光学字符识别模型的输入:
在培训过程中,尽一切可能可视化模型输入和输出!来源
3。研究您的验证集
使用您的测试指标来识别集合中表现最好和最差的样本。去了解他们。它们是否符合你的直觉,即模型应该在哪里表现良好,何时应该挣扎?如果你有一些量化信心的方法,比如 softmax,你可以探索模型超级自信的情况——以及错误的情况。
回归中,学习残差是有好处的。请记住,平均值可能会产生误导,正如安斯科姆的四重奏所阐述的那样:
安斯科姆的四重奏:4 个都有相同的均值和方差,用同一条回归线拟合最好。不要过于依赖统计数据:要贴近数据。来源
如果你有一个多维的问题,试着画出误差和单个特征。输入空间中是否有你做得特别差的区域?如果是这样,您可能需要收集更多的数据或在该地区进行扩充。
考虑烧蚀或扰动某些特性,并检查它如何影响性能。像 LIME 和 Eli5 这样的工具让机器学习模型变得简单明了。这篇精彩文章描述了扰动分析如何揭示 CNN 的 x 光分类使用 x 光机本身引入的标签来决定患者是否患有肺炎,因为使用的 x 光机类型与肺炎患病率之间存在相关性。
模型
你参加的大多数课程和面试都将关注机器学习的建模方面。事实上,作为一名机器学习实践者,你的职业生涯将主要花在担心数据和指标上,而不是关注机制的奇异实现。
绝大多数深度学习错误是形状错误,如上所述,这是一件好事:它们会导致相对容易修复的显式故障。
打破你的模型的更微妙的方法包括:
- 包括不可微运算
在深度学习模型中,一切都必须是端到端可区分的,backprop 才能工作。因此,你可能会期望像 Tensorflow 这样的深度学习框架中的不可微操作有明确的路标。你错了。我已经看到了与 Keras Lambda 层的特别混乱,它有一种打破背投的天赋。这种特殊毒药的解药是用model.summary()
来验证你的大多数参数是可训练的——如果你看到具有不可训练参数的层,你可能已经破坏了自动分化。
如果你看到带有不可训练参数的层,你可能已经破坏了自动分化。
2。测试时未能关闭漏失
测试时需要关闭 Dropout,否则,你会得到随机的结果。这可能非常令人困惑,特别是对于部署您的模型并试图为它编写测试的人来说。
在大多数框架中,这是通过将模型模式设置为eval
来处理的。还要注意,在培训期间,退出会产生一个令人惊讶的现象,你的验证损失比你的培训损失更好,因为当评估前者时,你已经退出了。当你第一次看到这一点时,它看起来像是过度拟合的反义词,并可能导致一些头部划痕。
3。维度混乱
不同的框架在安排诸如批量大小、序列长度和通道数量等公共维度方面提供了不同的约定。有些给你选择翻转的机会,而其他的如果你做错了就会默默的失败。
未能正确排序你的维度会产生奇怪和微妙的行为。例如,如果你混淆了批次大小和序列长度,你将会在你的批次中的例子之间丢失信息,并且不能随着时间的推移保存信息
避免模型错误
- 模块化、可测试的代码
建模错误主要可以通过结构良好的代码和单元测试来避免,这两者相辅相成。
通过将您的模型分解成具有明确定义的角色的离散块,您将能够有效地测试它们。您的测试应该集中在验证维度是否与您在不同批次和输入大小的条件下所预期的一样。我真的很喜欢【Chase Roberts 关于单元测试 ML 代码的这篇文章。
2。对维度有自信
我喜欢在我的 ML 代码中加入关于维度的断言。这让读者非常清楚哪些维度应该改变,哪些不应该改变——当然,如果发生意外,它会抛出一个错误。
漂亮的表达张量流代码,由 Keith Ito 提供。请注意模块化和形状注释。
至少,试着养成向代码中添加关于维度的注释的习惯,以限制读者的工作内存负荷。查看 Keith Ito 的这个漂亮的 Tacotron 实现,这是一个超级注释 Tensorflow 代码的例子。
3。用小数据过度拟合简单模型
这是另一个 Karpathy 技巧,我很早就知道了它的价值。您应该确保您的模型只适合数据集的一小部分。
为了加分,通过配置文件使您的模型易于配置,并指定一个参数数量最少的测试配置。然后在 CI/CD 中添加一个步骤,检查这个小模型是否能够适应非常小的数据集,并自动运行它。这将有助于捕捉对代码库的任何更改,这些更改会破坏模型或培训管道。
输入什么:数据
首先,了解你的数据
在你开始建模之前,你应该已经厌倦了看数据。
我们做的大多数人工智能都试图复制人脑的一些模式识别能力。在开始写代码之前,通过练习这些能力,让你的生活变得简单!理解您的数据集将有助于您思考架构和指标,并迅速了解性能问题可能出现在哪里。
通常,它还会标记数据本身的问题:类别不平衡、文件类型问题或偏见。后者可能很难用算法来评估,因为要发现它们,你需要一个与你试图训练的模型一样聪明的模型。例如,您将需要查看您的数据来注意类似“我所有关于猫的照片都是在室内拍摄的,而我所有关于狗的照片都是在室外拍摄的,所以也许我正在训练一个室内/室外分类器,而不是一个猫/狗分类器?”。
Andrej Karpathy 为 ImageNet 构建了一个标签平台,以评估自己的表现,并加深对数据集的理解。来源
正如安德烈·卡帕西在这里讨论的一样,投入大量精力开发软件来帮助你查看、分割和切割数据是值得的。2018 年,当我在伦敦 KDD 听他演讲时,他强调优步的许多 ML 工程师并没有编写代码来优化模型;他们正在编写代码来优化数据标签。
为了理解您的数据,您需要获得关于三种分布的直觉:
- 输入分布,例如平均序列长度、平均像素值、音频持续时间
- 输出的分配——等级不平衡是一个大问题
- 输出|输入的分布(这通常是您正在建模的内容)
选择加载数据的方式
有效地加载和预处理数据是机器学习工程中比较痛苦的部分之一。我总的来说发现在效率和透明度之间有一个折衷。
一方面,像 Tensorflow Records 这样的专用数据结构允许您将数据序列化为大数据包,并防止频繁读写磁盘。然而,你在效率上的收获是在透明度上的损失:这些结构很难询问,如果你决定要添加或删除文件,你必须重新序列化。
另一方面,简单地从磁盘直接读入一个列表并遍历它不会赢得任何速度上的奖励,但它完全清楚发生了什么。
现在我发现 Pytorch Dataset
和DatasetLoader
实用程序是控制和效率之间的一个很好的折衷。有专门用于处理文本( torchtext )和图像( torchvision )数据集的包,它们提供了相对高效的方式来加载、填充和批量处理每个域中的数据。我也听说过 Pytorch Lightning 的好消息,但我还没有用过它。
加快数据加载的方法
这里有一些有趣的方法,是我过去曾经尝试过的。
- 没有装载你认为你正在装载的东西
令人惊讶的是,很容易丢失数据,或者重复加载相同的数据。以下是我处理这件事的一些方法:
- 编写正则表达式从文件夹中加载某些文件,然后在添加新文件时没有更新这些正则表达式,这意味着我没有加载这些文件
- 误算一个历元中的步数,因此跳过了一些数据集
- 在一个文件夹中有递归符号链接(是的,真的),这样相同的数据被多次加载(在 Python 中达到 1000 的递归限制)
- 不完全遍历文件层次结构,因此无法加载子文件夹中的数据
2。数据结构不正确
不要把所有的数据放在一个目录中。
如果你有 1,000,000 个文本文件,并把它们都放在一个文件夹里,你的生活将会很痛苦,因为对那个文件夹的任何操作都要花费很长时间。很多时候,当你只想获取一些文件来查看或计算一些东西时,你会因为等待加载大量的文件夹而大大降低工作流程的速度。如果你的数据远程存储在一个数据中心,并且你已经使用sshfs
安装了目录,这种情况会更加严重。
第二个陷阱是在应用昂贵的预处理步骤时,无法制作数据的副本。将耗时的预处理结果保存到磁盘是一个好主意,这样您就不必在每次运行模型时重做工作,但是重要的是不要覆盖原始数据,并且能够跟踪哪个预处理代码在什么数据上运行。
类似下面的内容通常很有效:
raw [your raw data lives here, in whatever format you received it] processed [any preprocessing results in a new folder here] 2018jun14_b9f0c41 [date & commit stamped for reproducibility]
batch-1 [data split into manageable batches of ~1000]
batch-2
... 2017jun18_c760c52
batch-1
batch-2
...
3。预处理不当
特别是在 NLP 中,很容易在预处理中滥用数据。
对非 ASCII 字符的不正确处理可能是一个很大的问题,而且它们经常很少出现,很难被发现。
标记化带来了很多出错的可能性。如果您正在执行基于单词的标记化,很容易在一个数据集上形成您的词汇,然后在另一个数据集上使用它(或者在一些预处理之前或之后的同一个数据集),从而导致大量的词汇外单词。这是一个致命的无声错误——你的模型不会崩溃,它只是不能很好地工作。
训练集和测试集之间在词汇上的巨大差异在这里会产生问题,因为对于测试集中的单词,而不是训练集中的单词,您将无法了解任何信息。同样,充分了解您的数据并尽早发现这些问题是非常值得的。
避免数据处理中的错误
- 记录一切
确保每次转换数据时都在训练过程中记录示例。你不应该只是记录你的模型输出了什么,你应该记录输入了什么。
2。了解你的数字
您应该非常熟悉以下统计数据:
- 你有多少例子
- 对于给定的批量大小,对应于多少批
- 多少批次构成一个时期(即通过整个数据集)
同样,你可以也应该记录这些事情,或者你可以加入一些assert
语句来确保每件事都有意义。
3。预处理期间保存状态
一些预处理步骤需要或创建工件,您需要努力保存这些工件。例如,如果您通过训练集的mean
和variance
对数字数据进行规范化,那么您需要保存那个mean
和variance
,这样您就可以在测试时应用相同的转换。同样,如果你不保存你的训练词汇,你将无法在测试时以同样的方式进行标记——在测试中形成一个新的词汇并重新标记将产生无意义的结果,因为每个单词将获得完全不同的标记。
4。如果可以的话向下采样
当您拥有由大型文件(如图像和音频)组成的数据集时,很容易通过最少的预处理将它们输入到您的神经网络中,希望网络能够学习到预处理效果最好的方法。
如果你有无限的时间和计算,这可能是一个好办法,但在现实世界中,一些明智的下采样可以走很长的路。你可能不需要全高清图像来训练狗/猫分类器,尽管你可以使用扩张卷积来学习下采样器,但如果你在进行任何学习之前只是以传统方式进行下采样,那么你的时间和梯度下降会更好地利用。
向下采样允许您更快地完成模型拟合、模型评估和模型摆弄的循环,因此是一项很好的时间投资。
结论
总结一下,在你的机器学习冒险中要遵循的 5 条指导原则:
- 从小处着手,这样你的实验会进行得很快。限制你的循环时间能让你更早发现问题,更快检验假设。
- **贴近数据。**如果你不理解数据,你就无法做好数据建模工作。不要被诱惑而把时间花在玩弄花哨的模型上,而不是做看数据的无聊工作。
- **记录比你认为需要的更多的信息。**你对学习过程的了解越多,就越容易发现异常并提出改进措施。
- 比起效率,更喜欢简单和透明。少量的时间节省并不能证明代码或数据结构变得更复杂是合理的。浪费在试图理解不透明代码上的人力时间比浪费在低效算法上的计算时间要糟糕得多。
- 如果事情好得令人难以置信,那就是真的。在机器学习中有很多方法可以愚弄自己:成为一名优秀的科学家意味着毫不留情地发现和消除这些机会。
请在下面的评论中分享你犯下的任何有趣或奇特的错误,如果你有任何你认为我应该记录的好习惯,请告诉我!
要避免的菜鸟饭桶错误
https://towards data science . com/how-to-think-about-data-28 EC 05 a 75 CD 2
使用 Git 时,做一些简单的事情来避免沮丧和浪费时间
数据工程师通常比数据分析师、数据科学家和 ML 工程师更熟悉 Git 这样的开发工具。在过去的几年里,随着越来越多的非工程工作涉及到编写代码,像 Git 这样的源代码控制系统已经被广泛采用。尽管收养率有所上升,但没有足够多的新收养者对此感到满意。
当雇主没有足够重视培训他们的团队,或者当他们没有从他们的工程同行那里得到足够的支持时,非工程团队经常面临使用 Git 的问题。人人自立的模式是不可持续的。培训是团队发展和成长的一个非常重要的部分。
非工程团队在编写代码时有不同的思维方式。我试图在的另一篇文章中总结这些观点。在这里,我们将讨论的不是运行错误的 Git 命令可能会犯的错误,而是犯更基本的错误,比如直接在主分支上工作、进行非常大的提交等等。
当师父是你唯一的分支
如果你是团队的一员,并且你唯一的分支是 master,那么你基本上错过了 Git(或者一般的版本控制系统)背后的整个概念。在这种情况下,每个人都有一个本地主分支,并提交到该分支,然后将更改推送到远程主分支,发现有无数的冲突。避免这种情况,使用分支——这就是乐趣所在。
在 StackOverflow 上有一篇 knittl 写的关于为什么不使用 master branch 进行开发的很好的总结。
主分支应该代表代码的“稳定”历史。使用分支来试验新特性,实现它们,当它们足够成熟时,您可以将它们合并回 master。
这样,master 中的代码几乎总是可以顺利构建,并且可以直接用于发布。
不遵循分支方法
一旦您理解了分支的基本概念,并且能够创建分支并将它们合并到 master 中,您将遇到的下一个问题更多的是从分支管理的角度来看。所有分支方法中最流行和最有价值的是 gitflow。我从一个数据工程师的角度写了这篇文章。同样的原则也适用于任何使用 pandas、numpy、scipy 等工具用 SQL 或类似的 Python 脚本编写查询的人。
使用 GitHub、GitLab、BitBucket 等来存储和组织 SQL 查询,以实现发现和重用
towardsdatascience.com](/git-best-practices-for-sql-5366ab4abb50)
Gitflow 包括三个层次的分支,其中master
、develop
和feature
是三个不同的层次。这三种情况也有例外,但你不必一开始就陷入其中。点击此处了解更多信息—
[## GitFlow 简介
GitFlow 是 Git 的一个分支模型,由 Vincent Driessen 创建。它吸引了很多关注,因为它是…
datasift.github.io](https://datasift.github.io/gitflow/IntroducingGitFlow.html)
在一次提交中提交一千个文件
从技术上讲,这不是一个错误,只是一个非常糟糕的做法。除了 repo 中的初始提交之外,您的提交应该只包含可以在单行提交消息中明确定义的更改。提交所有未提交的更改对于一千个新文件来说不是一个好的提交消息。
小投入,多投入。
按照这条规则去做,你会没事的。这个想法是推动最小的完整工作单元。有一篇很棒的文章深入探讨了这个问题。
易于理解的小提交是伟大软件开发的支柱
medium.com](https://medium.com/better-programming/why-you-should-write-small-git-commits-c9a042737aa6)
这里有几篇我喜欢并找到的其他好文章
结论
就像 SQL、Python、Javascript 等其他广泛采用的技术一样,如果你想写代码的话,Git 可能是你不可或缺的东西——不管是以什么身份。你肯定要处理版本控制系统。因此,如果你花一些时间去理解它是什么以及它是如何工作的,那是最好的。
你可能误解了 git。
medium.com](https://medium.com/@gohberg/the-biggest-misconception-about-git-b2f87d97ed52)
最初,您可能不需要复杂的 Git 命令,因为只需要基本的命令就可以了。有几篇类似于 this 的文章讨论了运行错误的 Git 命令所犯的错误。只要把这几条( 1 、 2 、 3 、 4 、 5 )过一遍,就应该排序了。
使用 Python 解决调度问题
实践教程
通过使用简单的 Python 编程应用混合整数模型来公式化和解决复杂的排班问题
这篇文章说服读者用定量的方法来做决策。它阐明了我是如何从优化的角度识别和思考问题的。最优化不仅仅是一个数学研究课题。如果运用得当,它可以用来解决不同学科的实际问题。
使用正确的技术,可以对问题进行建模,以最大化/最小化某个结果。
动机
管理科学是一种基于科学方法的决策方法。它大量使用定量分析。涉及定量决策方法的知识体系有各种各样的名称;除了管理科学,另外两个广为人知和接受的名称是 运筹学 和 决策科学 。
二战后的两个发展导致了管理科学在非军事领域的发展和应用。
- 首先,持续的研究导致了许多方法上的发展。也许最重要的发展是乔治·丹齐格于 1947 年发现了求解线性规划问题的单纯形法。
- 与此同时,这些方法的发展正在发生;数字计算机引发了计算能力的虚拟爆炸。计算机使从业者能够使用先进的方法来解决各种各样的问题。
解决问题是一个过程,它能识别实际情况和期望情况之间的差异,然后采取行动解决这种差异。决策是指构建问题,然后分析它以选择一个替代方案。当我们经历很多成功时,很难理解使用数据做决策的必要性。我们的直觉(理解为自我)实际上淡化了运气在你做出的产生有利结果的决定中的作用。
数据科学教会了我在决策过程中量化的重要性。添加数字或量化结果的概率有助于减少情况的模糊性,并增加决策的一致性。如果没有适当的数据驱动分析,作为决策者,你很容易受到各种偏见和一厢情愿的想法的影响。
分析领域正以指数级的速度增长;AI(人工智能)、ML(机器学习)和深度学习等术语在科技行业很常用。就个人而言,在所有这些实践领域中,有一个经常不被重视的学科— 优化。我最近在研究一个优化问题,这是我们在计划一个活动或安排一个会议时经常遇到的问题。
排班问题
今年早些时候,我父亲被任命为我们地区一个本地演讲会的会长。国际演讲会是一个非营利性的教育组织,在全球范围内运营俱乐部,以促进交流、公众演讲和领导力。他的任务之一是为俱乐部的每个成员分配未来会议的角色。
在他担任主席期间,他将主持 27 次会议,俱乐部目前有 26 个成员(包括他自己)。在每次会议中,成员可以扮演不同的角色,例如“发言评估员”、“发言人”或“桌面主题主管”。为了成功地召开会议,必须给与会的不同成员分配 17 个角色。
我父亲创建了一个 excel 电子表格,在表格中,他尽自己最大的努力以符合逻辑的方式给每个成员分配角色。在几次令人沮丧的尝试后,他让我看看他正在进行的杰作。从计划的角度来看,这不是一个不可能完成的任务。通过多次反复试验,我们将能够解决这个问题。然而,我的基本问题是,如果有另一个成员在他的任期内加入或有人退出。这个耗时的过程不得不重复。
我告诉他,我将热衷于开发一个模型,以改善他的俱乐部的名册。数学模型是任何定量决策方法的关键部分。
例如,如果构建一个自定义表需要 10 个小时, 10x 就是数学模型,它定义了构建 x 表所花费的总小时数。
模型
对我来说,这个练习的主要目的是开发一个公平的方法来分配每个人的角色,并最大限度地提高他们在赛季中的参与度。数学模型还必须遵循其他几个要求:
- 每个会议在给定的一天只能分配 17 个角色。
- 为了使这成为一个公平的模式,每个成员都必须有机会在赛季中至少扮演一次每个角色。
- 每个成员在会议中只能扮演一个角色。
- 会议中的每个角色都必须被分配。
下面给出的是制定演讲会最高参与度花名册问题的公式化。
陈述模型的决策变量
最优化问题的表述
用 Python 公式化模型
- 初始化决策变量 从上面的符号可以看出,决策变量是二进制的(即只能保存 0 或 1 的值)。每个变量决定了成员、日期(会议)和角色的不同组合的值。
在 Python 中初始化决策变量
例如,如果 x_10_2_3 的值为 1,则意味着第二次会议中的 10 号成员将执行 3 号角色。
2。定义目标函数 如前所述,该模型试图最大化俱乐部中每个成员的参与度
在 Python 中定义目标函数
3。设置约束 与任何优化问题一样,模型的约束通常需要最多的分析推理。我更喜欢将问题分解成一个玩具例子,并测试模型在应用特定约束时的行为。
在 Python 中设置约束
报表生成
定量分析过程的一个重要部分是根据模型的解准备报告。决策者必须容易理解报告的结果。它应该包括建议的决定和其他可能对决策者有帮助的有关结果的相关信息。
在解决了上面的排班模型后,我决定创建一个 excel 报表,打印出每个决策变量的值。在这个模型中有11934 个决策变量,这再次加强了使用编程解决这个问题的论点。
为了编程,我给变量“角色”分配了一个数值,并使用下表对其进行了映射。
表格显示角色 5 对应于会议中的第一个发言人。
俱乐部中各种角色的映射表
打印模型的结果
从上面的 excel 报表快照中可以看到,值为 1 的值激活了决策变量。这意味着成员 10 已经被指派为会议 10 的发言评估员 4 的角色。
为了让决策者更容易理解,我在 Excel 中创建了一个简单的数据透视表。
在这个数据透视表中,
- 行被映射到成员。
- 列映射到**(会议)**
- 单元格值映射到角色。
即将召开的会议的俱乐部名单
从上面的报告中可以看出,每个成员都有机会至少扮演一次每个角色。单元格值为 0 意味着该特定会议上的相应成员没有被分配角色。
结论
当我接近这篇文章的结尾时,下面给出了我在研究过程中获得的一些重要知识,我想总结一下,以供大家参考。
- 从上面的公式可以看出,类似的排班问题可以通过改变几个关键决策变量来建模和轻松解决。我强烈建议你自己去修改/测试代码。
- 我们可以添加额外的约束,比如由于一些个人原因,在特定的会议上给成员分配一个角色。或者,我们也可以将问题建模为没有成员在连续 3 次会议中处于空闲状态。
- 一个数学模型只取决于你的专业水平。编程的真正力量在于分析的可重复性和可扩展性。对我来说,手动创建另一个花名册会非常耗时。我现在可以在未来的任何时候重用这些代码,节省我的时间、精力和精力。
GitHub 链接,谢谢
谢谢你一直读到最后。我希望这篇文章能激励你用更定量的视角分析生活中的不同问题。
对于任何感兴趣的人,可以在我的 GitHub 资源库中找到代码。请随意下载并分析您的用例信息。
使用 Python 中的纸浆包来制定和解决一个简单的排班问题打开 jupyter 笔记本安装…
github.com](https://github.com/wiredtoserve/datascience/tree/master/Rostering)
参考
[1]安德森、斯威尼、威廉姆斯、卡姆、科克伦、弗莱、奥尔曼。管理科学导论:决策的定量方法。2015 年第 14 版。Cengage 学习。第 2-8 页
在 Unsplash 上由Courtney hedge拍摄的照片
Rotoscoping:好莱坞的视频数据分割?
在好莱坞,视频数据分割已经做了几十年。简单的技巧,如绿色屏幕的颜色键控可以大大减少工作。
2018 年末,我们开发了一个视频分割工具箱。视频剪辑中常见的一个问题是拍摄场景时,天空过饱和或太亮。电影中的大部分天空都被 VFX 专家取代了。这个任务叫做“天空替换”。我们认为这是引入自动分割来掩盖天空以进行进一步替换的完美起点。基于收集的经验,我将解释 VFX 和数据注释的相似之处。
您会发现我们构建的解决方案与当时被认为是最佳图像分割模型的 Deeplab v3+进行了比较。我们的方法(左)产生了更好的建筑物周围的细节,并大大减少了帧之间的闪烁。
我们的天空分割模型和 Deeplab v3+的比较
好莱坞的视频分割技术
在这一节中,我们将更仔细地看看彩色键控,例如绿色屏幕和旋转镜。
什么是色彩键控?
我很肯定你听说过彩色键控或绿色屏幕。也许你自己在使用 Adobe After Effects、Nuke、Final Cut 或任何其他软件编辑视频时也使用过这样的技巧。
我小时候自己做过很多视频剪辑。与朋友一起制作有趣的视频,并使用 after-effects 等工具添加酷炫的效果。没日没夜的看 videocopilot.com和 creativecow.com的教程。我记得我和一个朋友在我家后院玩木棍,只是为了几个小时后把它们换成光剑。
如果你不知道绿色屏幕是如何工作的,你可以在下面找到一个视频,它会给你一个比我用文字更好的解释。
解释绿屏如何工作的视频
本质上,绿屏使用的是颜色键控。镜头中的“绿色”被屏蔽了。这个遮罩可以用来混合另一个背景。美妙的是,我们不需要烧你的 GPU 的花哨的图像分割模型,而是一个相当简单的算法,寻找具有所需颜色的相邻像素进行遮罩。
什么是 rotoscoping?
你可以想象在许多好莱坞电影中,特效需要更复杂的场景,而不是简单地使用彩色背景来掩盖元素。想象一个场景,动物可能不喜欢强烈的颜色,或者有很多头发在风中飘动。简单的颜色键控方法是不够的。
但也是针对这个问题,好莱坞在多年前就找到了一种技术: Rotoscoping 。
为了让你更好地了解什么是 rotoscoping,我在下面嵌入了一个视频。该视频是一个关于如何使用 after effects 进行旋转观测的教程。使用一个特殊的工具箱,你可以在整个视频中的物体周围绘制样条线和多边形。工具箱允许帧之间的自动插值,为您节省大量时间。
旋转观测的后效教程
这项技术于 2003 年在 After Effects 中推出,已经问世近 20 年,并被许多 VFX 专家和自由职业者使用。
剪影与 After Effects one 工具完全专注于旋转观测形成对比。在这个视频中,你可以了解他们最新的产品更新。
我为你挑选了一个例子来展示 rotoscoping 的结果有多详细。MPC Academy 的以下视频中的三个元素让我大吃一惊:运动模糊(T7)、头发的精细细节(T9)、帧一致性(T11)。当我们为 VFX 编辑开发一个产品时,我们了解到这个行业的质量要求超出了我们在图像分割方面的要求。在计算机视觉中,既没有数据集,也没有符合好莱坞标准的模型。
MPC 学院的旋转观测演示卷
在 YouTube 上搜索“roto showreel”,你会发现更多的例子。
VFX 的工具和工作流程以及数据标注惊人地相似。
质量检验比较
我们如何训练我们的天空分割模型
此外,我们在简单的背景前使用了公开的和无许可证的树、人和其他移动元素的视频。为了获得地面真实信息,我们简单地使用彩色键控。它非常有效,我们在几个小时内就完成了 5 分钟镜头的像素级精确分割。为了增加样本的多样性,我们使用了视频编辑工具,在移动摄像机的同时,裁剪掉部分视频。4k 原始视频有一个平滑移动的全高清帧。对于一些镜头,我们甚至打破了典型的二进制分类,使用平滑的边缘,在全黑和全白之间插入,作为我们的遮罩。通常,分割总是二进制的,黑色或白色。当场景模糊时,中间有 255 种颜色。
颜色键控允许我们获得复杂场景的地面真实数据,如树叶或头发。下面的棕榈树图片已经使用简单的颜色键控进行了遮罩/标记。
这适用于所有种类的树。甚至帮助我们获得了整个视频的良好效果。我们能够在剪辑过程中简单地调整颜色键控参数。
为了让你对我们的色彩键控实验的时间结果有一个概念,请看下面的 gif。注意有一点苦涩。我们特意添加了这个来“模拟”用你手中的相机进行录制。摄像机本身的移动是整个场景上作物的简单线性插值。所以你在下面看到的只是全景的一部分。
训练模型
时间一致性的对抗性训练
因此,对于您的下一个视频数据分割项目,您可能想看看是否可以使用这些技巧来收集数据并节省大量时间。在我的其他帖子中,你会发现一个数据注释工具列表。如果你不想在手工注释上花费时间,这里还有一个数据注释公司的列表。
我要感谢和我一起做这个项目的莫莫和黑基。另外感谢所有 VFX 艺术家和工作室的反馈和富有成效的讨论。
原载于 2020 年 4 月 23 日 https://data-annotation.com*。*
在 R 中映射地理空间数据的 3 个简单步骤
1)获取 API 2)安装库 3)图表
我总是想知道人们是如何用 r 语言创建漂亮的地理空间可视化的。我会瞪大眼睛看着完美的渐变,这些渐变可以让仪表板或演示文稿流行起来,我只是想知道人们到底是如何用我与 p 值和回归线相关联的编程语言来做到这一点的。
令人惊讶的是,您实际上只用几行代码就可以完成这一点——今天我将向您展示如何做到这一点。
第一步:访问美国社区调查(ACS)API
首先,你需要访问人口普查局的 API。前往此链接请求 API 密钥。大约 10 秒钟后,你应该会收到一封电子邮件,里面有你的密钥和一个链接,可以确认你确实是人类。把你的 API 密匙藏在某个地方——你每次重新打开 r 时都需要调用它。
为了正确读取 API,我们需要安装 3 个库:“acs”、“choroplethr”和“choroplethrMaps”。我们还将安装“RColorBrewer”和“ggplot2”用于稍后的绘图。
choropleth 地图(源自希腊语χῶρος的“区域/地区”和πλῆθος的“大众”)是一种专题地图,其中区域按照统计变量的比例进行阴影化或图案化,该统计变量表示每个区域内地理特征的汇总,如人口密度或人均收入。— 维基百科
下面是您需要输入到 R 中以使 API 工作的代码:
第二步:选择表 ID
一旦你安装了 API 密匙,你就可以在data.census.gov找到可用的数据集进行实验(我在教程中使用的是表格 B19301,各县的人均收入)。当您找到想要使用的数据时,记下表 ID。创建一个漂亮的 choropleth 图只需要表 ID。
确定数据集后,让我们开始绘图吧!
步骤 3:映射地理空间数据
下面我有按县绘制美国人均收入图表的代码。这只是可以切换的一瞥;如果你有一个我没有举例说明的修改,关于 county_choropleth_acs 和 ggplot2 Colors 的 R 文档将带你完成剩下的部分。
- 一些 ACS 数据集包含空值;最丰富多彩的图表将是那些他们的大部分观察呈现!
……以及相关图表:
\ 1:默认值\ 2:自定义标题\ 3:渐变+自定义标题\ 4:颜色变化+自定义标题\
奖金:州&地区仅限
改变我们的图表来关注一个州或地区就像添加一个额外的输入一样简单,即state_zoom()
函数。为了一次绘制多个州(而不是整个国家)的图表,我们将把我们想要一起查看的所有州连接起来。
对于最后两张图,我使用了 ggplot2 提供的一些有趣的调色板。这个库的scale_fill_brewer()
函数包含了一组预定的调色板,你可以在这里找到。
…。这里有四个相关的图表:
结论
现在您已经知道如何在 r 中创建简单的地理空间可视化。)你想调查。您可以按国家、州或部分州来绘制图表。最后,您可以更改调色板、渐变和图形标题。
这只是地理空间的冰山一角,但我希望它可以让你开始。感谢您的阅读,祝您工作愉快!❤️
GIF 来自 GIPHY
行或列—我应该将索引放在哪里?
为 SQL Server 工作负载选择最佳索引策略是最具挑战性的任务之一。了解与传统的 B 树结构相比,在哪些场景中使用列存储索引会使您受益
照片由 Nick Fewings 在 Unsplash 上拍摄
为 SQL Server 工作负载选择最佳索引策略是最具挑战性的任务之一。您可能知道,索引可以极大地提高查询的性能,但同时,当涉及到维护时,它们会导致额外的开销。
老实说,我永远不会称自己为索引专家。然而,我想分享我从最近的项目中获得的经验,因为它为我打开了一个全新的视角,我认为它也可以对其他人有益。
首先,直到几个月前,我还从未使用过列存储索引,因为我公司的工作环境是基于 SQL Server 2008R2 的。我有关于列存储索引的理论知识,也知道它们和传统 B 树索引的区别,但我从未在现实中尝试过。
但是,首先要做的是…
什么是列存储索引?
与 rowstore 类型的数据存储相反,columnstore 在表的列级别上操作,rowstore 类型的数据存储在物理上以行的格式存储数据。它最初是在 SQL Server 2012 中引入的,后来在 SQL Server 的每个新版本中都得到了改进。传统的行存储索引(我将它们称为 B 树索引)存储每一行的键值,以便 SQL Server 引擎可以使用这个键来检索行数据,而列存储索引则分别存储每个表列!
使用 Columnstore 索引的主要原因是它的高压缩率!这在内存占用方面带来了显著的好处,因此,如果使用得当,性能会更好。
网上确实有很多很棒的资源供学习关于列存储索引的架构,并且微软的文档在这个主题上也相当全面,但是我想展示一些使用列存储索引有意义的真实例子。
只是强调一下,我将专门使用聚集列存储索引(非聚集列存储索引不在本文讨论范围之内)。
对于所有的例子,我都使用堆栈溢出数据库。
爬上一棵 B 树
让我们首先在 Posts 表上运行几个简单的查询,这个表有 1700 多万条记录,只是为了对数据有一个感觉。一开始,我在这个表上没有任何索引,除了主键列上的聚集索引。
我的目标是找到 2010 年上半年所有浏览量超过 3000 的帖子:
SELECT *
FROM dbo.Posts P
WHERE CreationDate >= '20100101'
AND CreationDate < '20100701'
AND ViewCount > 3000
这个查询返回了 88.547 行,执行起来花了一分多钟!
由于该表上不存在索引,SQL Server 必须扫描整个表来满足我们的请求,执行大约 420 万次逻辑读取。让我们稍微帮助一下我们可怜的 SQL Server,在 CreationDate 列上创建一个非聚集索引:
CREATE NONCLUSTERED INDEX [ix_creationDate] ON [dbo].[Posts]
(
[CreationDate] ASC
)
现在,当我再次运行完全相同的查询时,我在 9 秒钟内得到了我的结果,但是逻辑读取的数量(560 万)表明这个查询还远远不够好。SQL Server 感谢我们的新索引,因为它用于缩小初始搜索的范围。但是,选择所有列显然不是一个好主意,因为 SQL Server 必须从聚集索引中选取所有其他列,执行大量的随机读取。
现在,我会问自己的第一个问题是:我真正需要什么数据?我需要*正文、关闭日期、最后编辑日期、*等吗??好了,我将重写查询,只包含必要的列:
SELECT P.Id AS PostId
,P.CreationDate
,P.OwnerUserId AS UserId
,P.Score
,P.ViewCount
FROM dbo.Posts P
WHERE P.CreationDate >= '20100101'
AND P.CreationDate < '20100701'
AND P.ViewCount > 3000
我们得到了完全相同的执行计划,逻辑读取次数减少了(400 万次),因为返回的数据量减少了。
SQL Server 建议在我们的谓词列(WHERE 子句中的列)上创建索引,并在索引中包含其余的列。让我们遵从 SQL Server 的意愿,修改我们的索引:
CREATE NONCLUSTERED INDEX [ix_creationDate_viewCount] ON [dbo].[Posts]
(
[CreationDate],
[ViewCount]
)
INCLUDE ([OwnerUserId],[Score])
现在,当我运行我的查询时,它在不到一秒的时间内执行,仅执行 3626 次逻辑读取!哇!因此,我们创建了一个很好的“覆盖”索引,它非常适合这个查询**。我特意将“针对此查询”部分加粗,因为我们无法为针对数据库运行的每个查询创建覆盖索引。在这里,它可以用于演示目的。**
列存储索引开始发挥作用
好了,我们不能再优化之前的查询了。现在让我们看看 columnstore index 将如何执行。
第一步是创建 dbo 的副本。Posts 表,但是我将在这个新表(dbo)上创建一个聚集列存储索引,而不是使用 B 树索引。Posts_CS)。
CREATE CLUSTERED COLUMNSTORE INDEX cix_Posts
ON dbo.Posts_CS
您可能注意到的第一件事是这两个相同的表在内存占用方面的巨大差异:
dbo 的内存占用。帖子表
dbo 的内存占用。帖子 _CS 表
因此,一个包含聚集列存储索引的表比一个包含 B 树索引的表消耗的内存要少 4 倍!如果我们也考虑非聚集索引,这种差异只会变得更大。正如我已经提到的,数据在列级别上压缩要好得多。
现在,让我们在新创建的 columnstore 索引表上运行完全相同的查询。
SELECT P.Id AS PostId
,P.CreationDate
,P.OwnerUserId AS UserId
,P.Score
,P.ViewCount
FROM dbo.Posts_CS P
WHERE P.CreationDate >= '20100101'
AND P.CreationDate < '20100701'
AND P.ViewCount > 3000
列存储索引中的数据存储在段中。因此,根据表中的数据分布,SQL Server 必须读取更多或更少的数据段才能检索到请求的数据。
如上图所示,为了返回 88.547 条记录,SQL Server 遍历了 26 个数据段,跳过了 72 个数据段。这是因为我们的列存储索引中的数据没有按照任何特定的顺序排序。比方说,我们可以按 CreationDate 对其进行排序(假设我们的大多数查询将使用 CreationDate 作为谓词),在这种情况下,性能应该会更好,因为 SQL Server 将确切地知道在哪些数据段中查找数据,哪些数据段可以被跳过。
现在,让我们一起运行这两个查询,并比较查询成本:
传统的 B 树索引查找成本为 3.7,而列存储扫描成本为 10.7。很明显,因为我们有一个完美匹配的非聚集索引,它覆盖了我们需要的所有列。还是那句话,差别没那么大。
添加更多配料…
但是,假设过了一段时间后,我们需要扩展我们的输出列表,并为 LastActivityDate 检索数据。让我们看看会发生什么:
哎呀!!!通过只添加一列,结果完全变得有利于 columnstore 索引。现在,B 树非聚集索引没有所有必要的数据,它需要从聚集索引中提取last activity date——这使得这个查询的成本上升到 236!另一方面,columnstore index 变得稍微贵了一点,现在要 14!
当然,正如您可以在上面的图片中注意到的,SQL Server 要求另一个索引(或扩展现有的索引),但这就是我在上面强调的——您不应该盲目地服从 SQL Server 的所有愿望,否则您将完成“过度索引”的表!
运行分析查询
根据定义,在运行分析查询时,列存储索引应该处于领先地位。因此,让我们在下面的场景中检查一下:我想检索在 2010 年上半年注册、在 2010 年和 2011 年发帖的用户,并且用户的总体声誉大于 3000,各个帖子的浏览量超过 3000……我还需要查看用户的位置和显示名称。听起来很复杂,但实际上并不复杂:)
以下是查询:
SELECT U.Id
,U.Location
,U.DisplayName
,P.CreationDate
,P.Score
,P.ViewCount
FROM dbo.Users U
INNER JOIN dbo.Posts P ON U.Id = P.OwnerUserId
WHERE U.CreationDate >= '20100101'
AND U.CreationDate < '20100701'
AND U.Reputation > 3000
AND P.ViewCount > 3000
AND P.CreationDate >= '20100101'
AND P.CreationDate < '20120101'
该查询返回 37.332 行,我们希望对 SQL Server 有所帮助,在 Users 表的 CreationDate 列上创建一个非聚集索引。
CREATE NONCLUSTERED INDEX [ix_creationDate] ON [dbo].[Users]
(
[CreationDate]
)
当我运行查询时,SQL Server 附带了以下执行计划:
因为我们的索引没有覆盖所有必要的列,所以 SQL Server 认为对 Users 表执行扫描比执行索引搜索和昂贵的键查找更便宜。这个查询花费 58.4。
现在,我将创建一个 Users 表(Users_CS)的副本,并在其上创建一个聚集列存储索引:
CREATE CLUSTERED COLUMNSTORE INDEX cix_Users
ON dbo.Users_CS
现在让我们同时运行我们的查询的 bot,并比较性能:
同样,带有 columnstore 索引的表很容易胜过带有 B 树索引的原始表。第二次查询的费用是 9.2!请记住,我们甚至没有优化 columnstore 索引本身(在插入过程中,我们没有对数据进行排序)!
最后一个例子来自我的真实项目,我们在实际工作负载上比较了 columnstore 和 B 树索引的性能。查询本身非常简单:我想汇总今年 1 月 1 日到 7 月底每个客户的存款总额:
SELECT customerID
,SUM(amount) total
FROM factDeposit
WHERE isSuccessful = 1
AND datetm >='20200101'
AND datetm < '20200801'
GROUP BY customerID
SELECT customerID
,SUM(amount) total
FROM factDeposit_cs
WHERE isSuccessful = 1
AND datetm >='20200101'
AND datetm < '20200801'
GROUP BY customerID
结果如下:
同样,columnstore 索引以 4.6 比 26 的查询开销令人信服地“胜出”!
那么,我为什么要使用 B 树索引呢??!!
在你陷入不再需要传统的 B 树索引的陷阱之前,你应该问自己:陷阱在哪里?很明显,问题就在这里,因为 B 树索引在大多数数据库中仍然被大量使用。
列存储索引的最大缺点是更新/删除操作。被删除的记录并没有真正被删除,它们只是被标记为已删除,但是它们仍然是列存储索引的一部分,直到索引被重建。更新的性能甚至更差,因为它们是作为两个连续的操作执行的:删除,然后插入…插入“本身”不是问题,因为 SQL Server 将它们保存在名为 Deltastore 的结构中(顺便说一下,该结构具有 B 树结构),并对列存储索引执行大容量加载。
因此,如果您经常执行更新和/或删除,请注意您不会从列存储索引中获得最大的好处。
所以,正确的问题应该是:
什么时候应该使用 B 树索引,什么时候应该使用列存储索引?
答案是,正如 SQL Server 内部 99%的争论一样——这要看情况!
寻找合适的工作负载
关键的挑战是确定最适合使用列存储和行存储索引的场景,或者更好地说是工作负载。
以下是针对每种索引类型的最佳实践用法的一些建议:
- 对不经常更新/删除的大型表(至少有几百万条记录)使用列存储索引
- 列存储索引在静态数据上表现最好,例如在 OLAP 工作负载中,有许多查询只是从表中读取数据,或者定期大容量加载新数据
- 列存储索引在扫描和执行大数据范围的聚合(执行求和、AVG、计数等)方面表现出色,因为它们能够一次处理大约 900 行,而传统的 B 树索引是逐个处理的(直到 SQL Server 2019,它为基于行的工作负载添加了批处理模式)
- 当表被频繁修改(更新、删除、插入)时,在高事务性工作负载上使用 B 树索引
- b 树索引通常在具有高选择性的查询中性能更好,例如,当您返回单个值或少量值时,或者如果您查询小范围的值( SEEK ing for a value)
如果说应该在 OLAP 工作负载中使用列存储索引,而在 OLTP 环境中使用 B 树索引,这未免过于简单化了。为了得到这个问题的正确答案,你应该问自己: 什么样的查询最常用于特定的表? 只要你得到这个问题的答案,你就能为自己的工作量定义合适的索引策略。
最后,如果你想知道是否可以从两个世界中取长补短:答案是——可以!从 SQL Server 2016 开始,可以在同一个表上结合 columnstore 和传统的 B 树索引!
然而,这是一个独立而复杂的主题,需要认真的规划和各种考虑,超出了本文的范围。
感谢阅读!
RPA 趋势:这是我们今年可以期待的
在2020 年,方向将朝着数字化、自动化、机器人过程自动化(RPA)和超自动化,即 RPA 与人工智能(AI)和机器学习的结合。这意味着激动人心的变化正等待着我们,可能是由 RPA 和全球经济环境驱动的。
许多公司将会发现,只有在人类劳动和辅助软件机器人之间的智能交互中,业务增长才有可能。此外,与以往任何时候相比,政治家们将不得不解决关于工作日益自动化的经济和社会后果的讨论。
因此,我们有必要更仔细地了解一下自动化的各个趋势。2020 年的总体长期趋势和短期发展是有区别的。
超自动化和机器人仍然是长期的潮流引领者
考虑到软件行业的全球发展,在我看来,目前有一个明显的领导者。根据 Gartner 的数据,2018 年机器人过程自动化(RPA)软件的销售额增长了 63.1%,达到 8.46 亿美元,成为全球企业软件市场增长最快的领域。
Gartner 最近发布了 2020 年前 10 大战略技术趋势,其中三个-超自动化、自主事物和人工智能安全-与自动化领域直接相关。
超自动化
根据 Gartner 的说法,“没有任何单一的工具可以取代人类。今天的超自动化是工具的组合,包括自动化流程(RPA)、智能企业管理软件(iBPMS)和人工智能——目标是人工智能驱动的决策。虽然这不是主要目标,但高度自动化通常会导致创建组织的数字双胞胎(DTO),使组织能够可视化功能、流程和关键绩效指标如何相互作用以增加价值。然后,DTO 成为高度自动化过程中不可或缺的一部分,提供有关组织的连续、实时信息,提供重要的商业机会。
自主的事物
虽然今天的物联网倾向于由静态对象组成,但明天它也将由自主行动和在很大程度上独立移动的机器人组成。“自主事物,包括无人机、机器人、船只和设备,使用人工智能来执行通常由人类执行的任务。这项技术的智能范围从半自动到全自动,可用于各种环境,包括空中、海上和陆地。虽然自主事物目前主要存在于受控环境中,如矿井或仓库,但它们最终将演变为开放的公共空间,”Gartner 继续说道。
人工智能安全
自动化带来的不断扩大的机会也带来了新的风险。根据 Gartner 的说法,“高度自动化和自主化等不断发展的技术为商业世界的转型提供了机会。但是它们也产生了新的潜在安全漏洞。安全团队必须应对这些挑战,并了解 AI 如何影响安全领域。对于人工智能安全来说,有三个重要的观点:
- 人工智能驱动系统的保护:人工智能训练数据、训练管道和 ML 模型的保护。
- 使用人工智能来提高防御能力:通过使用人工智能,可以理解模式,检测攻击,部分网络安全流程可以自动化。
- 预测攻击者对人工智能的恶意使用:检测攻击并防御它们。"
今年我们可以期待什么
除了这些中期趋势之外,还可以预测进一步的短期变化,这些变化很可能在 2020 年发生。
全球经济衰退成为自动化的驱动力
与去年相比,经济信号没有改善。因此,今年的特点也将是低利率和贸易战的威胁,资本投资下降和多重政治不稳定。如果这些信号传到消费者那里,新一轮的全球衰退可能很快就会到来。以前只是简单地裁员,现在公司的反应更加明智。特别是在经济困难时期,他们可以利用自动化,扩大软件机器人的使用。
RPA 正在成为“自动化的 YouTube”
RPA 有可能在今年成为中央自动化平台。正如 YouTube 成为视频内容的焦点一样,RPA 可以成为中央自动化存储库。与此同时,自动化程序的有用性和可重用性不断增加,使得它们更容易转移到新的领域。例如,不同行业和地区的公司将使用相同的代码。这一趋势预计将大大简化和扩展 RPA 的总体使用。
机器人的标准化和组合
公司还会发现,标准化的机器人适合不同部门的不同用途,但也可以用于不同行业和跨公司边界。机器人将因此离开它们以前孤立的操作区域。它们的使用将变得更加可预测和可扩展。只有这样,RPAs 的优势才能得到充分发挥。然而,越是简单的标准任务被自动化,因此越不容易出错,消除剩余问题就变得越复杂。公司应该用站点可靠性工程来抵消这一点,在站点可靠性工程中,IT 操作被认为是用软件工程解决的软件任务。
年轻学者将给自动化带来新的动力
现在进入就业市场的年轻学者对传统流程的转型和现代化有着更广泛的兴趣和理解,而在过去,这些老员工一次又一次地失败了。他们会问一些让人不舒服的问题,比如为什么某些事情多年来都是用这种方式解决,而不是用其他任何方式,意思是更好。他们可能已经在学校学到了某些过程的自动化,但肯定是在学习期间,并且已经将这些融入到他们的私人生活中。因此,他们也将开始实现部分工作流程的自动化。他们这样做的效果和效率越好,旧的担忧就能越快得到克服,公司也能越快相信变革的必要性。这些年轻的、积极的自动化专家可以在公司内部形成一种任务团队,定义公司范围的自动化战略,并将其贯彻到各个部门。同时,它们将是 RPA 提供商的理想界面。
机器智能会让所有人大吃一惊
人工智能和机器学习的潜力远远没有被耗尽。昨天显然只能靠人类智慧解决的问题,明天将由软件解决。正如电子电路的复杂性在给定的时间内增加了一倍(摩尔定律),人工智能也将呈指数增长,这将带来许多惊喜。特别是因为这种增长将补充量子计算机领域的进一步创新。量子比特的数量正在有规律地增长,并开辟了许多新的应用可能性。看似坚定的边界会倒下。
爱国军正在进入世界政治和经济舞台
自动化的后果将在今年影响到社会的大部分。RPA 将成为纽约联合国和达沃斯世界经济论坛的一个议题。它还关系到工作和公平的报酬。各个国家也可能对自动化的社会后果产生更大的兴趣。最晚当越来越多的机器人在经济衰退的情况下接管许多工作时,RPA 将成为政治议程的重中之重。在当前的分析中,Forrester 还预测了劳动力市场的重组。根据这项分析,不到 4%的现有办公室工作将会消失。相反,全球可能会创造 30 万个新工作岗位,这些岗位的所有者应该具备直觉、同情心和相当大的灵活性。机器人无法替代的素质。
RPA 行业将继续整合
2020 年,大型和已有的 RPA 供应商将合并新的、较小的竞争对手。像 UiPath 这样的供应商将不会被收购,因为它们的市场估值很高。这也适用于它们被大公司俘获的情况。这些公司将通过收购规模较小的供应商,将其 RPA 专业知识带入集团内部。已经可以观察到,全球软件服务提供商和技术提供商正在收购自动化行业的公司,这一趋势可能会持续下去。
为 2020 年及以后做出正确的决策
这些趋势的总体情况表明,跟上 RPA 和 AI 领域的发展步伐是多么复杂。因此,为了获得决定性的竞争优势,公司了解这些趋势并相应地调整其战略就变得更加重要。RPA 让各行各业各种规模的公司都有可能制定自己的数字化转型战略。UiPath 使用大量案例研究来证明“自动化第一”理念对于公司生存的重要性。即使今年许多有形的自动化趋势是可预见的,但决定性因素将是每个企业为做出充分反应所能贡献的创新力量。
图片经由 Pixabay
RPubs:添加到简历中的快速项目
如果你以前没有数据科学方面的工作,而你正在试图获得一份工作,那么在招聘人员眼中证明你自己的最好方式就是有一个前端工作的项目。虽然一篇研究论文或另一个 Jupyter 笔记本可能有复杂和/或技术上有趣的工作,但它并没有真正吸引人们的注意力。如果你的工作是交互式的,那么敬业度会在你的简历上大大提升。不管你是 10 岁还是 50 岁的博士,当你遇到一个交互式的图表时,你都会点击它。
如果你对 R 有点熟悉,这篇文章会对你有用,让你的作品具有交互性,并发布到网上,让其他人也能看到。
r 降价
R Markdown 是 Markdown 的一个扩展,它可以帮助您创建格式整洁的 pdf、html 文档、word 文档等等。Markdown 的原理相对简单,通过查看这个链接,你可以在大约 5 分钟内学会如何创建表格、插入图片和链接、设置文本样式以及设置标题。
R Markdown 稍微复杂一点。要在 R Studio IDE 中创建 R Markdown 文档,请转到文件>新建文件> R Markdown。这将把我们带到下面的窗格,在这里您可以命名您的文档,并选择您的文档类型。现在,把它作为一个 HTML 文档,然后点击“OK”按钮。
您现在应该有一个类似下图的脚本。在文件的顶部是一些 YAML,它给出了文件的一些信息。你可以在空白处写下你的减价,你也可以用普通的 HTML 来写,因为我们将呈现一个 HTML 文档。灰色区域用反斜杠括起来,它与大多数键盘上的波浪号共用同一个键。在第一组反勾号之后,你写下你正在使用的编程语言(在我们的例子中,我们使用的是 R,但是也可以选择另一种),然后是代码块的名称。
可以为代码块配置附加选项。常见的有:
include
阻止代码和结果出现echo
阻止结果出现warning
阻止代码生成的警告出现background
改变块的背景颜色fig.width
和fig.height
以英寸为单位控制数字的大小
最后,为了在代码顶部呈现生成的文档,在 Knit > Knit to HTML 上单击下拉箭头。这将为您提供所创建文档的视图。接下来,我们将为这个文档创建交互组件,然后在线发布它。
Plotly
Plotly 将帮助我们用最少的代码渲染具有很多特性的迭代图。要开始使用 plotly,请在您的控制台中运行install.packages("plotly")
,并将library(plotly)
放在您的某个代码块中(我推荐第一个代码块)。要创建一个图,您将使用 plot_ly 函数。下面我运行了plot_ly(data = iris, x = ~Sepal.Length, y = ~ Petal.Length, color = ~Species)
并生成了下面的图表,它能够创建截图,使用工具提示,突出显示数据点,并调整轴。
虹膜数据集的散点图
尝试重新渲染文档,您可以在输出中看到绘图。如果您已经在使用 ggplot,但不想转换为 plotly,安装 ggplotly 包,并将 ggplots 包装在ggplotly()
函数中,以获得现有 plotly 功能。要查看更多您可能想要生成的示例,请查看plotly.com/r/。
高价租船合同
尽管 highcharter 不能免费用于商业目的,但它可以用来创建许多不同风格的交互式图表。Highcharter 有助于开发比 plotly 更干净、不那么繁忙的地块。
使用如下的 hchart()函数,我们可以看到一个条形图形式的 diamonds 数据集。
hchart(diamonds$clarity, colorByPoint = TRUE, name = "Clarity")
钻石数据集的高查特条形图
像 plotly 一样,highcharter 的工具提示使其更有用,因为您可以看到这些条形图的准确高度或散点图上某个点的准确位置。有关 highcharter 的更多示例,我建议您查看 highcharter 展示区。
传单
传单是我个人最喜欢的映射包,开始你需要一个地理空间数据集和对 r 中管道操作符的理解。管道操作符把左边的对象作为左边函数的第一个参数。因此,以下内容是相同的:
data %>% func1() %>% func2()
func2(func1(data))
这实际上是我更喜欢 R 代码的编写方式,因为从左到右阅读要容易得多。您的代码采取了明确的步骤,并且您不必花费大量时间来查看特定参数处的嵌套函数。
活页地图是通过向管道链添加额外的功能来创建的,最终形成非常可定制的地图,这些地图可以具有多层影像、迷你地图、定制标记、路径和定制区域。如果您想快速浏览一下,我的文章用不到 15 行代码用 R 语言制作交互式地图是一个很好的起点,下面我将展示如何生成地图。
数据表
在大多数情况下,您希望以可视的格式呈现数据,因为这样更容易理解。如果您想要显示一个表,那么您应该使用 DT 包以交互方式来完成。下面的代码生成了下面的屏幕截图。
datatable(iris)
DT 包很棒,因为它使您能够在页面中显示表格数据(最小化空间),允许您搜索数据,并允许您根据特定的列对数据进行排序。这些是查看你的表的人可能想要的特性,DT 给了他们这些功能,并且是同一个表的截图的一部分。
RPubs
现在您已经对可以制作的交互式可视化类型有了一些概念,让我们来看看如何将您的文档发布到 RPubs。首先编织您的文档,就像您在过去的步骤中可能一直在做的那样来呈现它。渲染完成后,单击窗格右上角的发布按钮,这将生成下面的弹出窗口。
点击 RPubs,然后注册一个账户。注册后,再次单击发布按钮。值得注意的是,RPubs 上的每一个文件都是公开可见的,所以不要分享任何不应该分享的东西。
建议
虽然这不是你项目的一个非常重要的前端,但它比一个没有交互性的纯文本项目要好。希望这能在很短的时间内给你的投资组合增加一些东西。由于这是一个容易创建的项目,我的建议(如果你正在找工作)是对与你想工作的行业或公司相关的数据集进行分析。例如,如果你对房地产感兴趣,搜索住房数据集,如果你对为 Spotify 工作感兴趣,使用他们的 API 来获取数据。
RStudio addins,或者如何让您的编码生活更简单
发现最好的 RStudio 插件,如何在实践中使用它们,以及它们如何在用 R 或 R Markdown 编写代码时帮助您
由萨法尔·萨法罗夫拍摄的照片
什么是 RStudio addins?
答虽然我已经使用 RStudio 好几年了,但我最近才发现 RStudio 插件。从那以后,我几乎每次使用 RStudio 都在使用这些插件。
什么是 RStudio addins?RStudio 插件是一些扩展,它们为从 RStudio 中执行高级 R 函数提供了一种简单的机制。更简单地说,当执行一个插件(通过单击插件菜单中的一个按钮)时,相应的代码被执行,而您不必编写代码。如果仍然不清楚,请记住,对于在 RStudio 中导入数据集的,您有两种选择:
- 通过编写代码导入它(例如,感谢
read.csv()
函数) - 或者,您可以通过单击环境窗格中的“导入数据集”按钮来导入它,设置导入设置,然后单击“导入”
RStudio 加载项与“导入数据集”按钮非常相似,但具有其他常见功能。因此,您可以编写代码,就像您可以通过编写代码来导入数据集一样,但是由于 RStudio 插件,您可以在不实际编写必要代码的情况下执行代码。通过使用 RStudio 插件,RStudio 将为您运行所需的代码。RStudio 插件可以简单到插入常用代码片段的函数,也可以复杂到接受用户输入以绘制图形的闪亮应用程序。RStudio 插件的优势在于,与您自己编写代码相比,它们允许您更轻松地执行复杂和高级的代码。
我相信 addins 值得所有 R 用户去尝试。初学者将有可能使用他们本来不会使用的功能,因为代码太复杂,而高级用户可能会发现它们在某些情况下有助于加快代码的编写。有关 R 中的其他提示,请参见文章“R studio 和 R Markdown 中的提示和技巧”。
装置
RStudio 加载项作为 R 包分发。所以在能够使用它们之前,你需要安装它们。你可以像安装软件包一样安装一个插件:install.packages("name_of_addin")
。一旦你安装了包含插件的 R 包,它将通过顶部的插件菜单立即在 RStudio 中可用。
RStudio 加载项工具栏
艾丁斯
如果您仍然不相信,请参见下面我认为最有用的插件列表,以及下面几节中的具体示例。
请注意,此列表并不详尽,根据您在 RStudio 上进行的分析类型,您可能会发现其他列表也很有用。欢迎在文章末尾发表评论,让我(和其他读者)知道你认为值得使用的插件。
埃斯奎塞
是一个由法国 dreamRs 公司开发的插件。他们是这样定义的:
该插件允许您通过用 ggplot2 包可视化数据来交互式地浏览数据。它允许您绘制条形图、曲线、散点图、直方图、箱线图和 sf 对象,然后导出图形或检索代码以再现图形。
有了这个插件,你可以很容易地从[{ggplot2}](https://www.statsandr.com/blog/graphics-in-r-with-ggplot2/)
包中创建漂亮的图形,对我来说最好的部分是你可以检索代码来复制图形。与默认的{graphics}
包相比,{ggplot2}
包的图形看起来确实更好,但是代码也更长更复杂。有了这个插件,您可以通过在一个用户友好的交互式窗口中拖放感兴趣的变量来从{ggplot2}
包中绘制图形,然后在您的脚本中使用生成的代码。
为了便于说明,假设我们想要创建数据集iris
的变量Sepal.Length
和Petal.Length
的散点图,并用变量Species
给点着色。为此,请遵循以下步骤:
- 加载数据集并重命名为: 1
dat <- iris
2.安装组件{esquisse}
。这必须只做一次
install.packages("esquisse")
3.从 RStudio Addins 菜单中打开“ggplot2”生成器:
步骤 3:从 RStudio 插件菜单中打开“ggplot2”构建器
4.选择您想要处理的数据集(在本例中为dat
,并在检查观察值和变量的数量是否正确后点击“验证导入的数据”(绿框):
步骤 4:选择数据集并验证导入的数据
5.将感兴趣的变量拖放到相应的区域。在这种情况下,我们将绘制变量Sepal.Length
和Petal.Length
以及基于变量Species
的色点的散点图:
步骤 5:将变量拖放到相应的区域
6.点击窗口右下方的“>导出&编码”。您可以复制代码并将其粘贴到脚本中您想要放置的位置,也可以单击“在脚本中插入代码”将代码放置到脚本中光标所在的位置:
步骤 6:检索代码,以便在脚本中使用
如果选择第二个选项,代码应该出现在光标所在的位置。许多不同的选项和定制是可能的(例如,轴标签、颜色、图例位置、主题、数据过滤等。).为此,使用位于窗口底部的按钮(“标签和标题”、“绘图选项”和“数据”)。您可以在窗口中立即看到更改,当情节符合您的需要时,将代码导出到您的脚本中。我不会详细讨论不同类型的图和定制,但是一定要通过移动变量和定制来尝试其他类型的图,看看有什么可能。
提问者
{questionr}
插件在调查分析和处理因素变量时非常有用。有了这个插件,你可以很容易地重新排序和重新编码因子变量。得益于cut()
函数,该插件还允许轻松地将数字变量转换为因子(即,对连续变量进行分类)。像其他插件一样,安装完{questionr}
包后,你应该会看到它出现在顶部的插件菜单中。从加载项下拉菜单中,选择是否要对因子变量进行重新排序或重新编码,或者对数值变量进行分类。
记录因素
我们可以使用{questionr}
插件,而不是从{dplyr}
包中编写recode()
函数。
对于这个例子,假设我们想要重新编码Species
变量以缩短因子的长度,然后将这个新变量存储为Species_rec
:
步骤 1:选择要重新编码的变量和重新编码设置
步骤 2:指定新因素的名称
步骤 3:根据列联表检查结果,并使用顶部的代码
重新排序因素
类似于重新编码,我们可以通过{questionr}
插件对因子进行重新排序。假设我们想对Species
变量的 3 个因子重新排序,顺序是versicolor
然后是virginica
最后是setosa
。这可以通过以下方式完成:
步骤 1:选择要重新排序的变量和新的变量名
第二步:指定你想要的顺序
步骤 3:使用脚本顶部的代码
对数字变量进行分类
{questionr}
addin 也允许将一个数字变量转换成一个分类变量。这通常是为年龄而做的,例如,当年龄被转换成年龄组时。对于这个例子,假设我们想要创建变量Sepal.Length
的 3 个类别:
步骤 1:选择要转换的变量和新的变量名
步骤 2:将休息次数设置为 3
(自己尝试其他切割方法,直接在窗口中查看结果。)
第三步:根据底部的柱状图检查结果
步骤 4:使用脚本中的代码
治疗
如果你经常用 R Markdown 写作,那么{remedy}
插件将会极大地方便你的工作。该插件允许您添加粗体,创建列表,网址,斜体,标题(H1 到 H6),脚注等。以一种有效的方式。我认为直接使用代码可以更快地完成这些任务,而不是通过 addins 菜单然后选择您想要的转换。然而,就我个人而言,我不可能记住所有转换的代码,通过菜单应用它比在 Google 或 Markdown cheat sheet 上搜索答案更快。
Styler
{styler}
插件允许通过运行styler::tidyverse_style()
将你的代码重新格式化成更可读的格式。它对 R 脚本和 R Markdown 文档都有效。您可以重新格式化选定的代码、活动文件或活动包。我发现在共享或发布我的代码之前,这个插件特别有用,因此它尊重最常见的代码样式准则。
例如,像这样的一段代码:
1+1
#this is a comment
for(i in 1:10){if(!i%%2){next}
print(i)
}
变得更加简洁易读:
1 + 1
# this is a comment
for (i in 1:10) {
if (!i %% 2) {
next
}
print(i)
}
蛇皮箱
{snakecaser}
addins 将一个字符串转换成蛇形样式。Snake case 样式是将由空格分隔的几个单词组成的字符串写成单词用下划线(_
)分隔的字符串的做法。此外,它用小写字母代替大写字母。例如,以下字符串:
This is the Test 1
将被转换为:
this_is_the_test_1
snake case 样式对于变量、函数和文件名等特别有用(甚至被许多 R 用户推荐)。
视图管道步骤
感谢这篇文章的读者,我发现了ViewPipeSteps
插件。此加载项允许在每个步骤后打印或查看管道链的输出。
例如,下面是一个包含数据集diamonds
的链:
library(tidyverse)diamonds %>%
select(carat, cut, color, clarity, price) %>%
group_by(color) %>%
summarise(n = n(), price = mean(price)) %>%
arrange(desc(color))## # A tibble: 7 x 3
## color n price
## <ord> <int> <dbl>
## 1 J 2808 5324.
## 2 I 5422 5092.
## 3 H 8304 4487.
## 4 G 11292 3999.
## 5 F 9542 3725.
## 6 E 9797 3077.
## 7 D 6775 3170.
如果您不确定您的管道链或想要调试它,您可以在每个步骤后查看输出,方法是突出显示您的整个管道链,然后单击 addins 菜单中的“查看管道链步骤”:
从 addins 菜单中,您可以选择将结果打印到控制台,或者在新的窗格中查看结果(就好像您在管道的每一步之后都调用了函数View()
)。点击查看管道链步骤将打开一个新窗口,显示每个步骤的输出:
请注意,您必须使用以下命令来安装ViewPipeSteps
addin:
devtools::install_github("daranzolin/ViewPipeSteps")
library(ViewPipeSteps)
现在,你再也没有借口使用这个管道操作符了!
这是
yml 这是一个容易写 YAML 标题的附加程序
[ymlthis](https://ymlthis.r-lib.org/)
addin 使得为 R Markdown 和相关文档编写 YAML front matter 变得很容易。该插件将为您创建 YAML,并将其放在一个文件中,比如一个.Rmd
文件,或者放在您的剪贴板上。
如果你想写(更复杂的) YAML 标题,这个插件特别有用。
Reprex
如果你经常向 R 社区求助,这个 addin 可能会很有用!
重要的是要记住,当你向某人寻求帮助时,你必须通过准确清晰地概述你的问题来帮助他们。这有助于社区快速了解您的问题,从而减少响应时间。
在大多数情况下,这涉及到提供一个可再现的例子,也就是说一段代码(尽可能小和可读)再现所遇到的问题。
{reprex}
插件允许你转换你的可重复的例子,这样它就可以很容易地在平台上共享,比如 GitHub,Stack Overflow,RStudio community 等等。您的可重复示例的布局将适应平台,您甚至可以包含关于您的 R 会话的信息。 2
以下是如何一步一步地使用它:
- 首先在 R 中创建可重复的例子(记住尽可能保持简短易读,以节省潜在帮助者的时间):
最小可重复示例
2.在插件列表中选择{reprex}
插件:
选择{ reprex }加载项
3.选择您将发布问题的平台(如果您希望在可重现示例的末尾显示您的会议信息,请选中“附加会议信息”):
在{ reprex }加载项中设置选项
4.现在,您可以看到可重现示例的输出(右图),但更重要的是,它已被复制到您的剪贴板中,现在可以粘贴到您选择的平台上了:
您的 reprex 被复制到您的剪贴板中
5.将您的 reprex 粘贴到您选择的平台上(这里,它作为一个问题发布在 GitHub 上):
粘贴您的代表
6.检查可重现示例的最终结果:
决赛成绩
(访问本期 GitHub 刊看最终结果。)
博客城
我把这个插件放在列表的最后,因为只有有限数量的 RStudio 用户会对它感兴趣:维护用 R 编写的博客的人(比如这个博客)。
此加载项中最有用的功能如下:
- 新帖子:用
blogdown::new_post()
创建一个新帖子。它还可以用来创建新页面,而不仅仅是文章 - 插入图像:在博客文章中插入外部图像
- 更新元数据:更新当前博客文章的标题、作者、日期、类别和标签
- 服务站点:运行
blogdown::serve_site()
在本地实时预览你的网站
感谢阅读。我希望你会发现这些插件对你将来的 R 相关项目有用。参见 RStudio 和 R Markdown 中的其他提示和技巧。
和往常一样,如果您有与本文主题相关的问题或建议,请将其添加为评论,以便其他读者可以从讨论中受益。
相关文章: