平面测试的 Hopcroft-Tarjan 图的平面性和路径加法
给定一个无向图,平面性测试问题是确定是否存在围绕每个顶点的顺时针边序,使得该图可以在没有任何交叉边的平面上绘制。
平面图形在计算几何的许多问题中扮演着核心角色。如果化学结构是平面的,确定化学结构的同构就简单了。另一个例子是当工程师希望在芯片上嵌入组件网络时。元件由导线表示,任何两根导线交叉都会造成短路。这个问题可以通过把网络看作一个图并找到它的平面嵌入来解决。
下面是一些需要的基本概念。一个图 G=(V,E) 是 平面 如果可以把它画在一个平面上,使得除了端点之外没有边相交。这样的图叫做平面嵌入。平面图的一个例子是 K4 ,4 个顶点的完整图形(图 1)。
Figure 1: K4 (left) and its planar embedding (right).
并非所有的图形都是平面的。图 2 给出了两个非平面图的例子。它们被称为 K5 ,五个顶点上的完全图,以及 K_{3,3} ,两个大小为 3 的集合上的完全二部图。无论选择哪种复杂的曲线来表示边,当最后一条边无法在不与其他边交叉的情况下插入时,嵌入它们的尝试总是会失败,如图所示。
Figure 2: K5, the complete graph of 5 vertices, and K_{3, 3}, the complete bipartite graph on two sets of size 3
连接 u 和 v 的连续边序列被称为从 u 到 v 的 G 的路径。如果一条路所有顶点都是不同的,那么这条路是最简单的。从顶点到自身的路径是一条封闭路径*。从 v 到 v 带有一条或多条边的闭合路径是一个循环,如果它的所有边都是不同的并且唯一出现两次的顶点是 v 。我们用p:u** v来表示 p 是 G 中从 u 到 v 的一条路径。
(有向、有根)树 T 是具有一个根 r 的有向图,使得 T 中的每个顶点都可以从 r 到达,没有边进入 r ,并且恰好有一条边进入 T 中的每个其他顶点。关系“ (v,w)是 T 中的一条边”用 v → w 表示。用 v → ** w 表示关系“T中有一条从 v 到 w 的路径”。如果 v → w , v 是 w 的父*, w 是 v 的子。如果 v → ** w* , v 是 w 的祖先, w 是 v 的后代。每个顶点都是自己的祖先和后代。如果 G 是有向图,一棵树 T 是 G 的生成树如果 T 是 G 的子图包含 G 的所有顶点。
一个图的边 (x,y) 的细分是通过创建一个新的节点 z ,并用新的边 (x,z)**(z,y) 替换 (x,y) 得到的。一个图的细分是通过一系列细分操作可以从中获得的任何图。
由于 K5 和 K_{3,3} 是非平面的,显然这些图的细分也是非平面的。因此,有一个子图是 K5 或 K_{3,3} 的子图的图一定是非平面的。这样一个子图被称为对一个 K_{3,3} 或一个 K5 的同胚。图论中的一个著名结果是 Kuratowski 定理,该定理指出,没有一个 K5 或一个 K_{3,3} 的细分也足以证明一个图是平面的。
一般来说,图算法需要一种探索图的系统方法。平面性测试算法使用的最重要和最常用的技术是深度优先搜索。
深度优先搜索 (DFS) 是以特定的方式访问一个图的所有顶点 G 的方法。它从任意选择的顶点 G 作为根节点开始,并继续从当前顶点移动到未探索的相邻顶点。当当前顶点没有未探索的相邻顶点时,遍历回溯到具有未探索的相邻顶点的第一个顶点。请看下图中的 DFS 示例。
Figure 3: An example of graph G and its palm tree T
DFS 到达 G 顶点所使用的边形成了 G 的一棵生成树 T ,称为棕榈树,或 DFS 树。所以这个搜索以某种方式将 G 转换成一个有向图。 T 的根是访问开始的顶点。 T 的边称为树边,而 G 的其余边称为背边,在棕榈树中用虚线表示。深度优先搜索很重要,因为棕榈树中的路径结构非常简单。
如果对于每对顶点 u 和 v , G 包含一条从 u 到 v 的路径,则无向图 G 是连通的。图的连通分支是最大连通子图。
图中的关节顶点是删除后断开图的顶点。如果一个图没有连接顶点,那么它就是双连通的。图的双连通分支是最大双连通子图。
库拉托夫斯基子图是一个子图,它有 K5 或 K_{3,3}的细分。
库拉托夫斯基定理 (库拉托夫斯基【Ku30】)有限图是平面的当且仅当它不包含库拉托夫斯基子图。
狄拉克和舒斯特(1954)对这个定理的证明可以在[BM76]中找到。证明相当冗长,其中两个主要引理和定理的主要证明都使用了矛盾。对顶点数使用归纳法的另一个证明是由于汤姆森(1980) [GT87]。虽然这个证明不是最短或最容易的,但它很有启发性,因为它还产生了关于平面嵌入的其他结果。一个结果是单纯图的平面嵌入的边可以选择为直线的 Fary 定理(其他的是 Tutte 定理和 Whitney 定理)。此外,汤姆森的证明很容易转换成一个平面算法,这是多项式的顶点数。
平面性测试的朴素算法
有人可能会想,上面的 Kuratowski 的优雅定理是否可以用一种简单的方式作为测试图形平面性的标准。答案是肯定的,基于这个定理的朴素算法有指数级的运行时间,如下图。
顶点的度或价是该顶点的关联边数。设 u 为图 G=(V,E) 中度数为 2 的顶点,设 e1 和 e2 为 u 上的边,设 e1=(u,V),e2=(u,w) 。通过“在 u 处平滑”得到的图形,其顶点集合为 V / {u} ,其边集合为 E / {e1,e2} ,加上一条在 v 和 w 之间的新边。细分这个新边将反转 u 处的平滑操作。然后我们有以下结果,这是库拉托夫斯基定理的一个结果:
设 G 为图。对于 E 的每个子集,其移除仅留下一个非平凡分量 H ,通过连续平滑 H 中的价 2 的每个顶点来构建“平滑图” H^{smooth} ,如果 H 是一个循环,则可能除了最后一个顶点之外。如果某个 H^{smooth} 同构于 K5 或 K_{3,3} 那么这个图是非平面的,否则, G 是平面的。
因为有 2^{|E|} 边子集要考虑,这个朴素算法有指数运行时间。
hop croft 和 Tarjan 的路径添加算法
解决平面性问题的最好方法似乎是试图构造给定图的平面嵌入的表示。如果这样的表示能够完成,那么该图是平面的;如果不是,那么这个图是非平面的。一个这样的算法是 Hopcroft 和 Tarjan [HT74]的路径添加方法。他们首先表明平面度测试可以在线性时间内完成。
在给出算法的草图之前,我们可以做一些注释来限制所考虑的图 G 的类别。很明显,一个图是平面的当且仅当它的所有连通分量都是平面的。因此,我们可以假设 G 是连通的。
一个图是平面的当且仅当它的所有双连通分支都是平面的([Be64])。此外,铰接顶点可以在线性时间内找到([HT73],[Ta72]),因此单独嵌入每个双连接组件,然后通过它们的邻接铰接顶点连接它们就足够了。因此,我们可以假设 G 是双连通的。
下面的结果是著名的欧拉定理:
(欧拉 1750)设 G 是一个连通的平面图,设 n,m,f 分别表示 G 的顶点数、边数和面数。然后 n-m+f=2 。
这个定理可以通过对边数 m 的归纳来证明。我们需要的是以下推论:
如果 G 是一个有 n (≥ 3) 个顶点和 m 条边的平面图,那么 m ≤ 3n - 6 。
现在我们准备给出路径加法的概要。
算法示意图
- 统计边数,如果 |E| > 3 |V|- 6 那么它是非平面的(利用上面的推论去掉边太多的图)。
- 应用 DFS,将图形转换成棕榈树 T,并对顶点进行编号。
- 在树中找到一个循环并删除它,留下一组不相连的片段(使用 Auslander,Parter,Goldstein 的算法,[AP61],[Go63])。
- 检查每个零件加上原始循环的平面度(通过递归应用算法)。
- 确定各部分的嵌入是否可以组合,以给出整个图的嵌入。
对算法的每次递归调用都需要找到一个循环,并一次添加一条简单的路径。每一条这样的新路径通过新的边和顶点连接两个旧的顶点(有时整块被翻转过来,围绕着某条线;为了做到这一点,我们需要利用众所周知的 乔丹曲线定理 的一个推论:一条简单的闭合曲线将平面恰好分成两个连通的区域)。这解释了“路径添加方法”的名称。这也解释了使用 DFS 将图形分成简单路径的重要性,这些路径可以组合成平面性测试所需的循环。我将以上面图 3 中的图 G 和图 DFS 树 T 为例进行说明。
Figure 4: First cycle c and disconnected pieces s1 and s2
现在考虑第一个周期 c 。它将由一系列树边组成,后面是 T 中的一个后沿。顶点的编号是这样的,顶点沿着循环按编号顺序排列。所以在这个例子中周期 c 是1→2→3→4→5→8→1。当 c 被拆下后, G 会分成几个相连的块。不属于循环的每个片段将由单个后边缘 (v,w) (本例中的片段 s1 )或树边缘 (v,w) 加上带有根 w 的子树,加上从子树引出的所有后边缘(本例中的片段 s2 )组成。根据乔丹曲线定理,每一个棋子可以走“内”或“外” c 。当我们添加一个部分时,某些其他部分必须从内部移动或“翻转”到外部,或者如果必要的话反之亦然(以保持平面性),直到一个部分不能被添加或者整个图形被嵌入到平面中。该示例没有显示“翻转”情况,但是可以参考文章中的图 4 和图 5 进行说明。对 DFS 的修改将有助于生成路径的顺序,使得一个片段中的所有路径都在任何其他片段中的路径之前生成(在这种情况下 s1 有一个路径,而 s2 有三个路径),并且按照 v 的递减顺序浏览这些片段,其中 v 是路径的起点,如上所述。
Figure 5: Piece s2 after transformation
根据 Jordan 曲线定理,一块必须完全嵌入在 c 的一边。为了嵌入一个片段(比如说 s2 ,我们在其中找到一条路径 p 。我们选择一面,比如左边,在上面嵌入 p 。我们将 p 与之前嵌入的后沿进行比较,以确定 p 是否可以嵌入。如果没有,我们从左向右移动后边缘阻挡 p 的棋子。如果移动棋子后 p 可以嵌入,我们就嵌入。然而,如果我们将棋子从左边移到右边,我们可能必须将其他棋子从右边移到左边。因此可能无法嵌入 p 。如果是这样,我们声明这个图是非平面的。如果 p 可以嵌入,我们尝试通过递归使用算法来嵌入剩余的片段( s2 )。然后我们试着嵌入下一块。在本例中,需要将棋子 s2 转换为棋子S2’,如图 5 所示。
尽管算法的内部工作是基于对输入图的平面表示的搜索,但是没有描述如何实际产生它的平面嵌入。二十年后,Mehlhorn 和 Mutzel 填补了这一空白,描述了如何在测试阶段收集构建平面嵌入所需的信息,这些信息被隐含地用于显示平面性[MM96]。
结论
平面图形和平面性测试在计算几何中的各种问题中起着重要作用,包括地理信息系统、点定位。例如,集成电路的设计需要知道电路何时可以嵌入到平面中。
在 1974 中,Hopcroft 和 Tarjan【H提出了第一个线性时间平面性测试算法。该算法也称为路径添加算法,从一个循环开始,一次添加一条路径。然而,该算法是如此复杂和难以实现,以至于其他几个贡献跟随他们的突破。例如,在[HT74]之后大约二十年,Mehlhorn 和 Mutzel [MM96]发表了一篇论文,阐明了如何构造通过原始 Hopcroft 和 Tarjan 算法发现是平面的图的嵌入。
参考
[AP61] Auslander,l .和 Parter,S. V .,关于平面中的嵌入图,数学杂志。还有械甲怪。10, 517–523, 1961.
图的理论及其应用。伦敦梅图恩,1964 年。
J. A. Bondy,U. S. R. Murty,图论及其应用,北荷兰,1982 年。
[Go63] Goldstein,A. J .,一个测试一个图是否可以嵌入一个平面的有效的和构造性的算法,图论和组合学会议。,合同编号 1858-(21),1963 年。
[GT87]乔纳森·l·格罗斯,托马斯·w·塔克,拓扑图论,威利-Interscience,1987。
【Hhop croft,j .和 Tarjan,r .图操纵的有效算法,Comm. ACM 16,372–378,1973。
[HT74]约翰·霍普克罗夫特;Tarjan,Robert E .,有效的平面性测试,《计算机械协会杂志》21(4):549–568,1974 年。
[Ku30] Kuratowski,c .,关于地形学中的 corbes gauches 问题,数学基础 15,271–283,1930 年。
[MM96]库尔特·梅尔霍恩和佩特拉·穆策尔。,关于 Hopcroft 和 Tarjan 平面性测试算法的嵌入阶段。《算法》,16:233–242,1996 年。
[Ta72] Tarjan,r .,深度优先搜索和线性图算法,SIAM J. Comput。1, 2 146–159, 1972.
图形表示学习
Graph captured on the Floating Piers study conducted in our data science lab.
在使用复杂信息的任何科学和工业领域,图模型普遍用于描述信息。
在图中需要解决的经典问题有:节点分类、链接预测、社区检测等等。
当网络庞大时,分析它们会变得很有挑战性。一般来说,机器学习技术在网络上效果不佳。
因此,考虑到实际的网络结构,要点变成**学习网络中节点的特征。**这些被称为特征表示或嵌入。因此,任何节点都被描述为一个向量。
目标是将每个节点映射到一个低维空间,同时保留大部分网络信息。
基于这种低维表示,我可以进行任何下游分析。
主要的挑战是,如果与图像、音频或文本相比,网络的结构非常不规则。图像可以被看作是刚性的图形网格。运行机器学习更容易。
相反,图是非欧几里得的:节点的数量是任意的,它们的连接也是任意的。例如,如何将它输入神经网络?这就是为什么我们需要图形嵌入。
node2vec:无监督特征学习
主要思想是找到保持相似性的维度为 d 的节点的嵌入。目标也是嵌入使得附近的节点靠得很近。我需要计算一个节点的邻域,然后运行一个最大化问题,以一种 f(u) 预测邻域 N(u) 的方式计算节点 u 的特征学习。这可以通过 softmax 函数来实现,假设节点的因式分解(独立性)。
邻域的定义可以基于 BFS(宽度优先)或 DFS(深度优先)策略。BFS 提供网络的局部微观视图,而 DFS 提供网络的宏观视图。一个聪明的解决方案是 BFS 和 DFS 的插值。这是一个二阶随机游走,因为我们记得最后访问的节点,并且在每一步我们都要决定是返回最后访问的节点还是继续前往新的节点。本质上,在每一步,我都有三个选择:
- 节点回到更靠近原节点的位置 u
- 与当前节点距离相同的节点相对于 u
- 距离节点更远的节点 u
我可以设置概率 p (返回)和 q (离开)来决定每一步是离 u 远还是近。然后我可以运行随机梯度下降(线性时间复杂度)算法来寻找最佳路径。
基于我如何设置 p 和 q 的值,我可以得到对网络完全不同的解释:随着更大的 q ,我倾向于检测节点的“角色”,随着更小的 q ,我倾向于检测节点的“接近度”。
这是一种独立于任务的特征学习方法,在某种意义上,它不依赖于你对结果的使用。复杂度是线性的。网络的规模。
GraphSAGE:监督特征学习
相反,这种方法是受卷积神经网络的启发,通过将它们从简单网格(图像网格)的处理推广到一般图形。但是如何推广卷积呢?
您可以将图像视为一个图表,其中每个像素都连接(并以某种方式影响)附近的像素。
一种选择是使用图的邻接矩阵(加上节点的可能特征),并将其输入神经网络。问题:输入的数量与网络的规模成线性关系(巨大)。
一个改进是使用子图(例如,定义节点邻域的一片邻近节点)。这个邻域定义了一个计算图,我用它来传播和转换所考虑节点的所有邻居的信息(即属性)。本质上,对于每个节点,计算图是一个不同的神经网络,我可以运行它来学习参数。我有许多神经网络,它们使用相同的一组参数,所以这就像在进行监督学习。
如下所述,整个框架可以方便地以 MapReduce 方式执行,因为这自动避免了计算任务的重复。
本质上,我们定义了图的卷积网络概念。
在 Pinterest 中的实验已经成功地在 3 个 BLN 节点和 17 个 BLN 边上运行。
Graph captured on the Floating Piers study conducted in our data science lab.
这篇报道是关于 2017 年 12 月在波士顿举行的第四届 IEEE BigData 会议上由 Jure Leskovec (与斯坦福大学和 Pinterest)发表的主题演讲。
演讲的完整幻灯片可以在 Jure 的主页上找到:
http://I . Stanford . edu/~ jure/pub/talks 2/graph sage-IEEE _ big data-dec 17a . pdf
图论——基本性质
第三部分——从简单的图表开始
让我们后退一步,以便在图论的基础知识中更进一步。本系列的前一篇文章主要围绕着解释&标注一个简单图形。我们现在将返回来强调一个简单图形的属性,以便为本文的其余部分提供一个熟悉的起点。
simple graph — part I & II example
在之前的文章中,我们将我们的图定义为简单由于四个关键性质: 边是无向的&未加权的;该图不包括多条边&有向回路 *。*这绝不是所有图形属性的详尽列表,但是,这足以让我们继续我们的旅程。本文将通过介绍关键的图属性,把我们从简单的图带到更复杂的(但相当常见的)图。
常见图表属性
图形,就像它们所代表的物体的动态系统一样,呈现出难以估量的形状和大小;因此,创建一组属性有助于指定唯一的图形属性。让我们检查一下简单图形示例的定义属性:
- 无向边
- 未加权的边
- 排除多条边和循环
无向图与有向图
上例中的边除了连接两个顶点外,没有其他特征。他们明显缺乏方向。
最清晰和最大的图分类形式始于图中的类型的边 。存在两种主要类型的边缘:有方向的和没有方向的。无向图,如示例简单图,是由无向边组成的图。在一个有向图中,或者一个有向图*,每个顶点至少有一个输入边&和一个输出边——表示每个边相对于它的两个连接顶点的严格方向。*
未加权与加权图形
类似地,加权边仅仅是具有相关数字或值的边,或者称为 权重 (通常以非负整数的形式)。权重值允许对更复杂的问题进行建模,从而通过图形更准确地表示现实生活中的系统。在许多实际应用中,边的权重通常也称为边的成本;图中边权重的实际例子包括测量路径的长度、电缆的容量或穿过特定路径所需的能量。下图为我们的示例图提供了一个快速的视觉向导,如果它包含加权边,该图看起来会是什么样子:
多重边缘和循环
我们的示例图中突出显示的第三个简单属性引入了两个独立的图关系,它们都基于相同的属性:基于顶点关系的图的简单性。
在我们的示例图中,每个顶点有条边连接到另一个顶点——没有顶点通过多条边连接到另一个顶点。此外,没有顶点循环回自身。一个图,如果包含其中一个或两个,多条边&自循环,称为多重图。**
下图通过右图突出了这两个区别:
循环-非循环与循环图
我们之前没有列出这个属性,因为非循环图和循环图都可以算作简单图,但是,图的循环属性是一种重要的分类形式,值得一提。在图论中,一个 圈 是一条由边&个顶点组成的路径,其中一个顶点可以从自身到达;换句话说,如果一个人可以从一个顶点回到自身,而不沿着路径重复(折回)一个边或顶点,那么这个循环就存在。
包含至少一个循环的图称为循环图。没有单圈的图称为无圈图。在下面的示例中,我们将在简单的图表中突出显示多个循环中的一个,同时在右侧展示一个非循环图表:
来源
现在已经对与图相关的关键属性有了基本的了解,是时候跳到一个非常激动人心的图论主题了:网络!在下一篇文章 &中,我们将开始在更深层次上构建对网络的理解——最终将这些原则应用于网络分析。
图论帮助英国变得不那么臭了
这是为那些可能在数据科学环境中使用图论的人解释图论原理的系列文章中的第二篇。第一篇文章,主要讲图论的起源和图的基本性质,这里可以找到。
19 世纪中期的一个 7 月的早晨,伦敦人醒来时闻到一股令人作呕的恶臭。他们不能离开家而不生病。更富裕的人在手帕上涂上香水,走来走去,永远遮住他们的脸。许多穷人离开城镇去农村找工作,因为他们实在无法忍受。毫无疑问,这是英国历史上最卑鄙的事件。
这就是后来被称为 1858 年大恶臭的开始。泰晤士河,被中世纪木制下水道系统直接倾倒的人类排泄物填满了几个世纪,终于得到了它的报复。霍乱肆虐的淤泥被冲上河岸,在异常炎热的夏季温度下晒成了一股恶臭,方圆数英里都无法逃脱。
伦敦金融城公司(City of London Corporation)当时并不以在公共卫生方面特别积极而闻名,该公司意识到适可而止,并邀请提交该市新污水排放计划的设计方案。约瑟夫·巴扎格特的计划被接受了,他现在被认为是伦敦历史上主要的市民英雄之一。作为一名才华横溢的土木工程师,他监督了一个巨大的公共工程项目,该项目改变了伦敦的卫生水平和生活质量。Bazalgette 的下水道网络被广泛认为是创建现代城市的第一步,也是伦敦霍乱终结的开始。
The Great Stink of 1858 was solved with the help of graph theory.
Bazalgette 的下水道网络仍然运行良好,将数百万人的废物向东运送到泰晤士河口的处理设施。作为一个工程项目,这是人类努力的一个令人震惊的例子:22,000 公里的下水道,3.18 亿块砖,270 万立方米的挖掘土。
Bazalgette 以他自己的努力工作而闻名。他想尽一切办法让这项巨大的努力经得起未来的考验。确保水流的重力和坡度,隧道的直径,所有这些都是他非常关心的细节。但是有两个问题从一开始就必须回答,以使项目可管理和可持续:第一,我们如何将网络中任何两点之间的污水路线最小化,第二,哪一个是最重要的连接点?
Bazalgette 的工程壮举是图论新兴领域的一些最初用途的一个例子,并说明了我们今天一直在关注的与网络有关的两个概念的重要性:顶点之间的距离和顶点的重要性*。*
测量图形中的距离
距离在图论中是一个相当简单的概念,但在实践中非常有用。回想一下本系列的上一篇文章,图由一组顶点和一组连接成对顶点的边组成。给定任意两个顶点,它们之间的距离定义为它们之间最短路径的边数。这有时也被称为“测地线距离”,如果顶点之间不存在路径,则按照惯例被描述为“无穷大”。例如,在上面的简单图形中,顶点 2 和顶点 6 之间的距离是 3(有两条这样长度的路径可以到达那里)。
距离是一个非常有用的概念,因为我们经常想要优化它。在复杂的工程网络中,最小化距离是一个非常常见的要求。在对人的研究中,最小距离也是一个常见的利益问题。六度分离问题认为世界上的任何两个人可以通过最多六个中间顶点或七条边相互连接,这是一个图网络中的最小距离问题。最近对脸书的研究显示,该网络中个人之间的平均最小距离为 4.57。
但是最大距离也可能是有趣的,因为它意味着不熟悉和不同。例如,可以使用某些公司数据来开发一个表示员工之间过去协作的图表。然后,在公司活动中,你将人们组织成讨论组,如果你想最大限度地形成新的联系和观点的多样性,你可以问这样的问题:我们如何将这 100 人分成 10 组,每组 10 人,这样这些组有最大的平均距离,因此最不可能以前彼此合作过?以这种方式使用,图论可以对组织内的人们的体验产生有意义的影响。
测量图中顶点的重要性
在任何图中,有些节点更重要。例如,在 Bazalgette 的下水道网络中,将会有一些需要更多监控的连接点,因为任何故障或泄漏都会对整个网络产生更大的影响。同样,在一个人的网络中,某些个人由于其相对于网络中其他人的定位和连接性而具有更大的影响力。
衡量重要性的一个简单方法是顶点的价数 T2。这是连接到顶点的不同边的数量。以脸书为例,你的价就是你拥有的联系数量。但这并没有完全掌握影响力或重要性的概念,不是吗?并不是每个拥有大量人脉的人都在网络中扮演着真正重要的角色。
根据我的经验,衡量网络重要性的最佳标准是中间中心度*。简单地说,给定顶点的介数中心性是该顶点在网络中两个其他顶点之间的最短路径上出现的次数。具有高度介数中心性的顶点在更大程度上影响信息的传播,并且它们从网络中的损失往往对其整体连通性具有更显著的影响。在上图中,红色顶点的中间中心度最小,而蓝色顶点的中间中心度最大。*
理解中间中心性在人际网络中非常重要。它可以帮助确定投资于哪些个人,以确保某个信息尽可能广泛地传播。通过介绍给合适的人,它可以帮助你结交新朋友。它有助于确定你应该对从网络中失去一个人及其对其他人的潜在影响有多担心。
中间中心性很难测量,因为你需要计算网络中所有顶点对之间的路径。对于大型网络,这可能是高度计算密集型的。然而,有一些优秀的数据科学软件包可以用来计算网络特征,包括介数中心性。在我工作的 R 生态系统中,igraph
包特别方便。
下一次,我们将看看信息是如何在网络中流动的,这对谣言和趋势的研究有着非常有趣的应用。 这里读一下 。
最初我是一名纯粹的数学家,后来我成为了一名心理计量学家和数据科学家。我热衷于将所有这些学科的严谨性应用到复杂的人的问题上。我也是一个编码极客和日本 RPG 的超级粉丝。在LinkedIn或者在Twitter上找我。
图论——历史和概述
第一部分——什么是图论&为什么它与今天相关?
Originally Published On https://www.setzeus.com/
图表,尤其是网络图,引起我的注意。
也许这是一种直觉,用图来分析系统会增加我对分散式和集中式网络的理解。我这个数学家看到了明确的网络分析是如何极大地有利于激励驱动系统的研究的。或者可能是我这个设计师很大程度上被上图这种与生俱来的艺术魅力所吸引——视觉上很吸引人,但最终还是信息丰富的*。*
不管出于什么原因,在软件开发中遇到了作为树的图,在区块链研究中遇到了作为网络的图,或者在 r/databasebeable click-bait 中遇到了作为诱饵的图之后,我决定深入图论的世界&它是网络理论的一个分支。
Article Originally Published On https://www.setzeus.com/
数学领域是 大 。它的知识之树分支到越来越多的子领域。对描述一组分支的&进行细分的最高级别方法之一是通过给定问题中号的类型。问题中的数字既可以是 离散的 ,如固定的、可终止的数值如自然数 1、2、3、4。或者它们可以是 连续的 ,这些数字更准确地将我们的现实映射为动态的、不断变化的数值比如物体的速度。
离散数字对于老式数字钟就像连续数字对于现代模拟时钟(一种滑动而非滴答的时钟)一样。在前者中,上午 11:32 和下一分钟 11:33 之间不存在任何数字。在后一种情况下,秒针持续移动,这意味着当秒针从一秒滑动到下一秒时,模拟时钟在技术上每一个瞬间都显示不同的值。包含离散数字的数学分支的最好例子是组合学,研究对象的有限集合。基于连续数的数学分支的最好例子是微积分,它研究事物如何变化。**
图论是离散数学的一个分支,是对事物之间联系的最高层次的研究。这些事物*,更正式地被称为顶点,顶点或者节点节点,而连接本身被称为*边。**
图论的历史
图的基本概念是由瑞士数学家莱昂哈德·欧拉在 18 世纪首先提出的。他对著名的柯尼斯堡桥问题的尝试和最终解决方案(如下所示)通常被引用为图论的起源:
Article Originally Published On https://www.setzeus.com/
德国城市柯尼斯堡(今俄罗斯加里宁格勒)坐落在普雷戈利亚河畔。地理布局由四个陆地主体共七座桥梁连接而成。向欧拉提出的问题很简单:有没有可能步行穿过城镇,穿过每座桥一次,而且只穿过一次(被称为欧拉步行)?
欧拉认识到相关的约束条件是四块陆地&七座桥,他画出了第一张现代图形的可视表示。如右下方的图像 C 所示,现代图形由一组称为 v 顶点或节点的点表示,这些点由一组称为 e dges 的连接线连接。
Article Originally Published On https://www.setzeus.com/
通过首先尝试在上图中绘制路径,然后用顶点和边数交替变化的多个理论图进行实验,他最终推断出一条一般规则:
为了能够走欧拉路径(又名不重复边),一个图可以没有或者有两个奇数个节点?
从那以后,被称为图论的数学分支潜伏了几十年。然而,在现代,它的应用终于爆炸了。
图论的应用
图论*归根结底是对关系**的研究。给定一组节点&连接,它们可以抽象出从城市规划到计算机数据的任何东西,图论提供了一个有用的工具来量化&简化动态系统的许多移动部分。通过一个框架来研究图形为许多排列、网络、优化、匹配和操作问题提供了答案。*
随着我们继续学习图形集和矩阵符号的基础知识 (2) ,通过介绍一些应用来激发我们自学的动机不会有什么坏处——一窥图论的应用:
在软件工程中,它们被认为是一种相当常见的数据结构,被恰当地命名为决策树。在电气工程领域,整个学科都围绕着多部分电路的创建、计算和维护,这些电路通常按照图论原理绘制。与此同时,在分子生物学领域,科学家们推断预测模型来跟踪疾病的传播或繁殖模式。最后,在充满争议的社交媒体网络分析领域,我们见证了图论被用来创建现在的标准功能,如 LinkedIn 的分离度&脸书的朋友推荐功能。让我们进入下一篇文章,熟悉常见的图形符号。**
原载于
来源
图论——网络理论
第四部分——网络理论的基础
Originally Published On https://www.setzeus.com/
最后,我们在这一系列图论文章中的路径把我们带到了图论的一个新兴分支的核心:网络理论。
网络理论是图论原理在复杂、动态互动系统研究中的应用
当提供额外的相关信息时,它为进一步分析相互作用的代理的结构提供技术。网络理论的应用,正如这篇文章之前的文章中所说的 (3) ,是深远的&行业不可知论。从计算机科学,到电子工程,到博弈论&社会媒体分析,网络理论的基础提供了一个强大的心智模型来增加我们对现代系统的理解。
在不破坏太多未来文章的情况下,提供一个网络理论通常用来解决的类问题的快速概述可能是有意义的。其中,有几个经典网络理论问题的例子:
- 最短路径问题——图中任意两个节点之间的最短(成本方面)路径是什么?
- 网络流量—有向路径是否有足够的容量来承载沿途每个节点接收到的“流量”?
- 匹配问题——一个图是否包含一对或多对匹配的独立边集?
- 关键路径分析——在一个相互依赖的活动系统中,哪一条路径最长?
一目了然——社交媒体网络
通过图论原理研究主流社交媒体的异同,我们可以加深对这些日常实体的理解和欣赏。让我们通过类似迷你图的表示来分解《媒介与脸书》的结构。
中等
代表 Medium(这个站点)的图表看起来像什么?从划分用户之间的*关系开始。在介质上,用户可以跟随另一个用户,而第二个用户不一定要跟随第一个用户回来——这意味着介质图上的每条边都有方向。因此,Medium 的网络采用了有向图的形式。*例如,如下图所示,我的一个朋友托尼·斯塔克(Tony Stark)既可以追随朱莉·卓(Julie Zhuo)(我追随 IRL 的一位才华横溢的脸书产品设计师&多产媒体作家);然而,由于她没有兴趣看我们写的东西,她没有跟着我们回去——从朱莉到托尼我自己没有回去的路。这在下面的例子中直观地表达出来:
Article Originally Published On https://www.setzeus.com/
脸谱网
现在让我们比较一下上面的媒介代表和蓝色的有争议的有罪快感,脸书。除了相对较新的“关注”功能,脸书用户已经成为朋友,以便共享对彼此内容的访问。一个脸书用户请求第二个脸书用户成为*的朋友。*然而,一旦请求被接受,谁向谁发送请求就不再重要了。方向性不再存在:*双方用户将在各自的时间轴上看到对方的内容。*让我们继续看看影响者 Julie 的例子,我自己也有两个普通用户& Tony Stark。在脸书,托尼很可能是我的朋友;然而,我们俩都不是朱莉的朋友:
Article Originally Published On https://www.setzeus.com/
我们已经对主流社交媒体进行了两次思维实验,以直观地理解它们各自的图形结构。让我们来看看加密货币这个激动人心的世界,以便浏览另外两个例子。
加密货币一览
我们现在将分析另一个类似图表的现代奇迹:加密货币。下面,我们将分析比特币的闪电网络和 IOTA 的纠结共识机制的支持网络的文字截图,而不是通过例子来思考。
闪电网络
Article Originally Published On https://www.setzeus.com/
上图是 Lightning Network 的截图,这是一个用于比特币的 p2p、链外结算层,是最有希望实现即时、近乎免费的比特币交易的扩展解决方案之一。闪电网是由个节点&个支付通道(又名边缘)组成的网络。支付通道严格由两个终端节点组成,通过发送初始金额来打开支付通道,这使得两个连接节点之间的比特币交易速度极快。为了加入 Lightning 网络,一个节点需要与另一个节点至少有一个支付通道。
节点& 支付渠道,如上所述,组合成一个图形结构的典型例子。一旦支付通道打开,两个用户都可以无休止地将交易发送回&——方向是双向的,这意味着闪电网络代表了一个无向图。
一丝纠结
Article Originally Published On https://www.setzeus.com/
另一方面,IOTA 项目基于 DAG — 有向无环图,为他们的区块链版本推出了一种特殊的共识机制。
在上面显示的纠结中,每个交易都被表示为图中的一个顶点。当一个新的事务加入这个纠结时,它选择两个先前未确认的事务进行确认,向图中添加两个新的边&节点。一旦确认,已确认的交易不能再被进来的未确认交易确认;但是最近加入未确认交易的&很可能会被下一个确认的传入,未确认交易。因此,根据交易是已确认还是未确认,导致图形中的方向性。通过上面的观察,我们可以通过注意到图形底部附近的大多数奇异顶点有两条边&顶点悬挂在它们上面来验证这一点(等待确认的未确认事务)。
最后
从图论历史的最基础开始,从哥尼斯堡的七座桥开始,我们现在一直前进到网络理论的中心。然而,这只是实际图形和网络理论应用的启动平台,下一步是使用许多制作精良的图形和网络可视化和分析工具,如下面链接的工具:
社会网络分析:社会网络可视化(SocNetV)是一个用户友好的免费软件工具,为社会…
socnetv.org](https://socnetv.org/) [## ge phi——开放图形平台
Gephi 是各种图形和网络的领先可视化和探索软件。Gephi 是开源的…
gephi.org](https://gephi.org/) [## 免费在线网络可视化工具。在浏览器中创建交互式网络。不需要编码。
面向非开发人员的在线交互式可视化工具。
rhumbl.com](https://rhumbl.com/)
最初发表于
来源
图论——集合和矩阵符号
第二部分——图论符号的基础
Article Originally Published On https://www.setzeus.com/
一个图 G 由两组项组成:顶点( V ) &边( E )。换句话说,一个图 G = < V,E >。
简单图形—集合符号
Article Originally Published On https://www.setzeus.com/
如上图所示,可视化图形表示包含大量有用的信息;然而,为了以一种可操作的方式揭示这些数据,我们需要一种不同的方式来描述图表。首先,为了更好地理解开头的 G = < V,E > 公式,让我们继续记下上面的例图。为了开始标注,因为我们的顶点缺少标签,我们继续&分配任何随机节点标签“A”——然后遍历图形,用比前一个大的字母数字字符标记每个节点。还没有什么新奇的东西,我们所做的只是给上图中的顶点分配一个代表性的角色:
Article Originally Published On https://www.setzeus.com/
现在,让我们继续&通过创建两个数字集合来构造图形的数字符号:顶点&边。示例图的第一组顶点相当简单:它是一组字符 A-F(包括 A-F)。但是我们的优势呢?简单。回想一下,边是连接两个节点的*。因此,一条边可以用它所连接的两个节点来表示;这意味着边集合包含一系列类似坐标的顶点。例如,顶点 A &和顶点 B 之间的第一条边最好表示为 AB 。让我们继续&写出图表其余部分的集合表示:*
updated thanks to Simon Dedman
如上图所示,我们的图 G 的标准符号可以用两个独立的集合来完美地描述,一个表示顶点*,&一个表示边*。**
然而,这是进行数学和分析运算的最好方法吗?把它喂给电脑处理怎么样?事实证明,矩阵提供了一个强有力的工具来为一个更加计算机友好的数据集转录图形。让我们回顾一下这两种主要的方法&相应地标注我们的示例图。
简单图形—矩阵符号
计算机更擅长处理数字,而不是识别图片。这也是为什么以矩阵形式向计算机传达图形规格更为常见的原因之一。两种主要类型的矩阵设置是行业惯例:邻接矩阵&关联矩阵。
邻接矩阵
相连的顶点被称为邻居*,或者彼此相邻的*。一个邻接矩阵因此描述两个顶点是邻接(1) 还是非(0) 。邻接矩阵中的每一项都是简单的描述连通性的布尔值。**
在邻接矩阵中,具有顶点集合 V 和边集合 E 的图 G 转化为大小为 V 的矩阵。在矩阵内部,我们发现 0 或 1——1 表示在行&中标记的顶点与列中标记的顶点是相连的,或者用更恰当的术语来说,它们是相邻的。因此,邻接矩阵是表示为矩阵的图,其中相邻的顶点是唯一的焦点。让我们继续&将我们的示例图转录为下面的邻接矩阵:
Article Originally Published On https://www.setzeus.com/
关联矩阵
*将图形转换成矩阵的第二种常见语法是通过*关联矩阵。在关联矩阵中,具有顶点集合 V &和边集合 E 的图 G 转化为大小为 V 乘以 E 的矩阵。行&列分别在顶点&边后标注。在矩阵内部,我们再次发现所有的项目都被标记为 0 或 1——更多的布尔值。然而这次,1 表示行&中标注的 顶点 与列中标注的 边 相连。
关联矩阵的一个有趣的性质是:将一个列中的每一项相加总会得到两个。这在直觉上是有意义的,因为简单图中的任何&每条边都只有两个顶点与之相连。让我们继续&将我们的示例图写成关联矩阵:
Article Originally Published On https://www.setzeus.com/
比较这两种矩阵符号,我们可以推出一些区别。首先,由于边总是比顶点多,因此邻接矩阵的列数比关联矩阵少。相应地,关联矩阵更稀疏(0 到 1),因此每个矩阵项的信息量可能更少。最后,邻接矩阵总是遵循正方形矩阵模式,而关联矩阵更可能代表矩形。
关键区别在于,前者的行&列代表顶点,而后者的行&列分别代表顶点&边。
过去简单图形
现在基本符号已经过时了,是时候继续研究基本的图形属性了,这些属性通常用于描述不同类型的图形。回想一下,我们的示例图早先被定义为简单的图;我在下一篇文章中,我们将在跳到图论中最激动人心的话题:网络理论之前探索一些图。
原载于
来源
Jupyter 中的 GraphFrames:实用指南
最初用于计算生物学的图分析已经成为社交网络分析(社区挖掘和建模作者类型)和推荐系统中越来越重要的数据分析技术。一个简单而直观的例子是曾经非常著名的脸书朋友聚类图,它可视化了你的脸书网络的隐藏结构。
有一段时间,权力图分析一直是学术界、脸书或 Twitter 内部人士的一门学科。但是自从 GraphX 和 Graphframes 之类的软件包发布以来,每个拥有一点数据和一个 Jupyter 笔记本的人都可以很容易地在迷人的图形世界中玩耍。
lostcircles.com
在这篇文章中,我提供了一个关于 Jupyter 笔记本中 Graphframes API 的大范围的玩笑。首先,我们通过设置 Jupyter 笔记本环境来使用 GraphFrames 包(在 Windows 机器上)。然后,我们将了解基本功能以及如何将数据导入框架,最后解释一些常用的高级功能。请注意,本文假设您对 Spark 和 Spark 数据帧有基本的了解。
GraphX 对于 rdd 就像 GraphFrames 对于 DataFrames 一样。
GraphFrames 和 GraphX 的功能基本相同,唯一的区别是 GraphFrames 基于 Spark 数据帧,而不是 rdd。如果你习惯使用 GraphX,那么浏览一下 API 文档,Graphframes 应该很容易学会。
设置环境
在继续之前,首先要检查的是确保 Spark 和 pyspark 正确安装在您的机器上。为了简单起见,在本教程中,我们将在本地模式下运行 spark。检查这一点最简单的方法是在 Python 发行版的外壳中输入 pyspark 。如果两者都安装正确,您应该会看到如下内容:
Welcome to
____ __
/ __/__ ___ _____/ /__
_\ \/ _ \/ _ `/ __/ '_/
/__ / .__/\_,_/_/ /_/\_\ version 2.xx.xx
/_/
Using Python version 3.xx.xx
关于设置 Apache Spark 和 pyspark 的更多信息,我推荐本教程和官方文档。
接下来,您希望在 Jupyter 笔记本中设置环境变量来运行 pyspark(而不是 shell)。这可以通过添加两个环境变量来轻松实现:
set PYSPARK_DRIVER_PYTHON=jupyter
set PYSPARK_DRIVER_PYTHON_OPTS=notebook
然后导航到您想要存储新笔记本的位置,并在您的 shell 中再次运行 pyspark ,但是添加一个 packages 标志并指示您想要使用 GraphFrames 包。这里使用的是最新版本,但是任何旧版本都可以通过更改参数的最后一部分来使用:
pyspark --packages graphframes:graphframes:0.5.0-spark2.1-s_2.11
创建新笔记本,并确保您可以成功运行:
from graphframes import *
入门指南
令人惊讶的是,这个包的核心类是 GraphFrame。GraphFrame 总是从顶点数据帧(例如用户)和边数据帧(例如用户之间的关系)创建的。两个数据帧的模式都有一些强制列。顶点数据帧必须包含一个名为 id 的列,用于存储唯一的顶点 id。edges DataFrame 必须包含一个名为 src 的列,用于存储 edge 的源,以及一个名为 dst 的列,用于存储 edge 的目的地。所有其他列都是可选的,可以根据需要添加。
一个简单的例子是:
from pyspark import *
from pyspark.sql import *spark = SparkSession.builder.appName('fun').getOrCreate()vertices = spark.createDataFrame([('1', 'Carter', 'Derrick', 50),
('2', 'May', 'Derrick', 26),
('3', 'Mills', 'Jeff', 80),
('4', 'Hood', 'Robert', 65),
('5', 'Banks', 'Mike', 93),
('98', 'Berg', 'Tim', 28),
('99', 'Page', 'Allan', 16)],
['id', 'name', 'firstname', 'age'])
edges = spark.createDataFrame([('1', '2', 'friend'),
('2', '1', 'friend'),
('3', '1', 'friend'),
('1', '3', 'friend'),
('2', '3', 'follows'),
('3', '4', 'friend'),
('4', '3', 'friend'),
('5', '3', 'friend'),
('3', '5', 'friend'),
('4', '5', 'follows'),
('98', '99', 'friend'),
('99', '98', 'friend')],
['src', 'dst', 'type'])g = GraphFrame(vertices, edges)## Take a look at the DataFrames
g.vertices.show()
g.edges.show()## Check the number of edges of each vertex
g.degrees.show()
你当然希望使用真实的数据。您可以将从简单的 csv 文件到拼花文件的任何内容导入到数据帧中。然后适当地命名您的列,过滤(如果需要)并从那里继续前进。关于将数据导入 Spark 数据帧的更多信息可在文档中找到。
我们刚刚创建的 GraphFrame 是一个有向图,可以如下图所示:
有向边与无向边
无向图的边没有方向。这些边表示双向关系,因为每个边都可以在两个方向上遍历。如果数据帧仅由双向有向边组成,您可能会对分析无向边感兴趣。如果 src ≥ dst (或者相反),您可以通过在 edges 数据帧上映射一个函数来转换您的图形,该函数会删除该行。在 GraphX 中,您可以使用 to_undirected() 来创建一个深度的、无方向的图形副本,不幸的是 GraphFrames 不支持这个功能。
在下面的代码片段中可以找到一个简单的例子来解决这个缺失的功能。请注意,“跟随”边在无向图中没有实际意义,因为它不代表双向关系。
copy = edgesfrom pyspark.sql.functions import udf[@udf](http://twitter.com/udf)("string")def to_undir(src, dst):
if src >= dst:
return 'Delete'
else :
return 'Keep'copy.withColumn('undir', to_undir(copy.src, copy.dst))\
.filter('undir == "Keep"').drop('undir').show()## for efficiency, it's better to avoid udf functions where possible ## and use built-in pyspark.sql.functions instead.
过滤和连接组件
GraphFrame 本身不能被过滤,但是从 Graph 中推导出的 DataFrames 可以。因此,filter-函数(或任何其他函数)可以像使用数据帧一样使用。唯一的陷阱可能是引号的正确使用:整个条件应该被引用。下面的例子应该可以澄清这一点。
g.vertices.filter("age > 30").show()g.inDegrees.filter("inDegree >= 2").sort("inDegree", ascending=False).show()g.edges.filter('type == "friend"')
图的连通分支是这样的子图,其中任意两个顶点通过一条或多条边相互连接,并且在超图中不连接任何额外的顶点。在下面的(无向)示例中,有三个相连的组件。连通分量检测对于聚类很有意义,而且可以使计算更有效。
Source: Wikipedia
实际上,GraphFrames 要求您设置一个目录来保存检查点。在您的工作目录中创建这样一个文件夹,并在 Jupyter 中放置下面一行(其中 graphframes_cps 是您的新文件夹)来设置检查点目录。
sc.setCheckpointDir('graphframes_cps')
然后,可以用 connectedComponents 函数很容易地计算出连通分量。
g.connectedComponents().show()
我们的迷你图有两个相连的组件,在组件列中对每个顶点进行了描述。
+---+------+---------+---+------------+
| id| name|firstname|age| component|
+---+------+---------+---+------------+
| 1|Carter| Derrick| 50|154618822656|
| 2| May| Derrick| 26|154618822656|
| 3| Mills| Jeff| 80|154618822656|
| 4| Hood| Robert| 65|154618822656|
| 5| Banks| Mike| 93|154618822656|
| 98| Berg| Tim| 28|317827579904|
| 99| Page| Allan| 16|317827579904|
+---+------+---------+---+------------+
主题发现
寻找主题有助于执行查询以发现图形中的结构模式。网络主题是图形中重复出现的模式,代表顶点之间的关系。GraphFrames motif finding 使用声明性领域特定语言(DSL)来表达结构化查询。
可以通过使用find-函数调用查询,其中 motif(在引号中)被表示为函数的第一个参数。
下面的例子将搜索由边e连接的顶点对 a 、 b 和由边 e2 连接的顶点对 b 、 c 。它将返回图形中所有此类结构的数据帧,其中包含主题中每个命名元素(顶点或边)的列。
g.find("(a)-[e]->(b); (b)-[e2]->(a)").show()
如果边和/或顶点是匿名的,它们将不会显示在结果数据帧中。主题可以用分号连接,也可以用感叹号否定。关于领域特定语言的更多细节可以在文档中找到。
例如,我们可以尝试为任何一对用户 a 和 c 找到共同的朋友。为了成为共同的朋友 b , b 必须是既有 a 又有 c 的朋友(而不仅仅是后面跟着 c 之类的)。
mutualFriends =
g.find("(a)-[]->(b); (b)-[]->(c); (c)-[]->(b); (b)-[]->(a)")\
.dropDuplicates()
要查询 2 和 3 之间的所有共同好友,我们可以过滤数据帧。
mutualFriends.filter('a.id == 2 and c.id == 3').show()
b-列将显示所有共同的朋友(在本例中只有一个)。
+--------------------+--------------------+--------------------+
| a| b| c|
+--------------------+--------------------+--------------------+
|[2, May, Derrick,...|[1, Carter, Derri...|[3, Mills, Jeff, 80]|
+--------------------+--------------------+--------------------+
三角形计数和 PageRank
最后,我们将发现两个额外的内置算法。TriangleCount 计算通过此图中每个顶点的三角形数量。三角形可以定义为一组相互关联的三个顶点,即 a 有一条边到 b , b 有一条边到 c ,以及 c 有一条边到 a 。下面的例子显示了一个有两个三角形的图形。
geeksforgeeks.org
在 GraphFrames 包中,你可以通过调用triangle count-函数来计算通过每个顶点的三角形数量。请注意,我们的简单示例总共只有两个三角形。三角形用于现实网络中的各种任务,包括社区发现、链接预测和垃圾邮件过滤。
g.triangleCount().show()
我们讨论的最后一个函数是 PageRank。PageRank 的工作原理是通过计算一个页面的链接数量和质量来粗略估计该网站的重要性。潜在的假设是,更重要的网站可能会从其他网站收到更多的链接。
PageRank 算法认为,一个假想的随机点击链接的冲浪者最终会停止点击。在任何一步,这个人继续下去的概率是一个阻尼因子。阻尼系数可以通过改变重置概率参数来设置。其他重要的参数是公差( tol )和最大迭代次数( maxIter )。
pr = g.pageRank(resetProbability=0.15, tol=0.01)## look at the pagerank score for every vertex
pr.vertices.show()## look at the weight of every edge
pr.edges.show()
我找到“真命天子”的几率
你有没有想过,“我约会的对象对吗?他/她是那个人吗?我是不是太早安定下来了?当我们不以一种方式做事时,我们会以另一种方式做事。如果我不安定下来,他/她会成为“逃脱的那个”吗?"
我决定用一点数据就能解决这个问题。
在你说“你不能对爱下定义”之前,我会说我完全同意。如果你说,“你的女朋友一定很生你的气,因为你把她物化了”,让我说她很好奇,但也很不安。最后,让我说这部漫画小说有一个快乐的结局。
该方法
世界上有固定数量的人,我有一些基本要求。是的,爱情是盲目的,我知道,但让我们找点乐子,假设一些品质是真正的交易破坏者。你可能对年龄、教育程度、智力、吸引力、兴趣、性别、身高、语言等有所要求。我将详细说明我的要求,您可以通过复制这张谷歌表单来遵循您自己的偏好。你会发现找到你生命中的那个人的可能性有多大。
打开电子表格并跟随:-)
年龄和性别
世界上男性略多于女性。我感兴趣的是年龄在 25 岁到 34 岁之间的女性,大约占总人口的 7%,或者所有女性的 14%。
世界上 70 亿* 7% = 4.9 亿女性年龄大致合适
在印度,老年人很少,随着医疗保健的改善,他们的人口将快速增长。在中国,人口结构是相当不稳定的,因为有很多贫困时期和独生子女政策。
语言
我只能和说英语(或者可能说西班牙语)的人交往,所以那大约是 11 亿/70 亿=15%。这是为第一或第二语言使用者准备的。
70 亿*7%*15%
宗教
我对我伴侣的信仰有点挑剔。无神论者,不可知论者或精神工作对我来说。克里斯蒂安不行。只有 16%的女性符合我的标准。这对非宗教人士来说实在是太多了。
70 亿*7%*15%*16%
吸引力
好吧,这个因素有点不确定。显然,你想被和你在一起的人吸引,但是给某人加一个号码会触发很多人。我发现了一项对几十个快速约会事件进行的研究,其中男性和女性在事件后按照 1 到 10 的等级对他们的约会对象进行评级(也就是说,他们不仅根据外表进行评级,还根据化学成分进行评级)。[注意:在图表中,你可以看到女性比男性更挑剔,男性的评分也比男性低!]男人声称 5%的女人是 10 岁。
70 亿* 7% * 15% * 16% * 5%…对于那些正在记录分数的人来说。
https://www0.gsb.columbia.edu/mygsb/faculty/research/pubfiles/867/datingFULL-EK1.pdf
有趣的是,快速约会研究中的每一个人都至少获得了一个高于平均水平的评级和一个低于平均水平的评级,这表明,事实上,吸引力是主观的,“每个人都有自己的人”与此同时,收视率分布并不均匀。有些人的平均得分比其他人高得多,所以对“吸引力”也有一些共识。
根据我的计算,我只包括 10 个。你可能会说“7 还不够好吗?为什么需要 10 分?”首先,我的 10 分和你的 10 分不一样,所以我们不是在竞争同一个人。第二,我不想处于这样一种境地:我必须向我的伴侣撒谎,说他们是我见过的最有魅力的人——我以前也有过这种经历。
为了进一步支持人们会被非常不同的人吸引的论点,正如你在 OkCupid 的下图中看到的,平均水平高并不意味着低。虽然右边的女性平均水平较高(3.4 比 3.3),但她收到的消息是左边女性的三分之一(是网站平均水平的 0.8 倍,是网站平均水平的 2.3 倍)。这怎么可能?事实证明,平均水平会说谎;更有趣的是分布。左边的女人比右边的女人获得了更多的 1 分。但这并不重要。重要的是有多少人给你打 5 分。所以老话“做你自己”真的适用于现实生活。虽然照片中的两个女人可能都很特别,但左边的那个看起来更独特,因此也更两极分化。
From “Mathematics of Beauty” OkCupid blog post in 2011 (blog taken down, now available the book by the CEO of OkCupid called Dataclysm — Amazon link).
智力
我忘记了来源,但我读到过,如果夫妻之间的智商在 10 分以内,他们成功的可能性会大得多(我找到后会添加这项研究,或者如果你记得请贴上链接)。我想找一个智商 125+的女人。是的,我知道智商并不是衡量智力的完美标准,如果你有更好的衡量标准,请告诉我!
70 亿*7%*15%*16%*5%*5%
https://www.us.mensa.org/join/testing/scoreevaluation/
高度
我比男性平均身高(6 英尺 3 英寸)高出两个标准差,我发现很难和身高低于 5 英尺 3 英寸的女性跳舞,这是女性的平均身高,50%的女性高于平均身高。
70 亿*7%*15%*16%*5%*5%*50%
https://tall.life/height-percentile-calculator-age-country/
其他特征
此外,我的“另一半”需要目前单身( 47%的 25-34 岁女性单身)、想要孩子(显然只有 2/3 的千禧一代想要孩子)、不吸烟( 85% )、多面手( 4.5% )并且愿意骑摩托车穿越拉丁美洲 3000 英里(我估计只有 5%的女性愿意,尽管我不能
70 亿* 7% * 15% * 16% * 5% * 5% 50% * 47% * 66% * 85% * 4.5% * 5%…
Percent of women who are single by age. This photo highlights that 29 percent of 34-year-old women are single. I averaged the single rate for women 25–34. You can see the full data at: https://www.thrillist.com/news/nation/overflow-data-shows-percentage-of-people-your-age-are-married
免费奖金图表:D
如果以下任何一个恋物癖对你很重要,这里有一个关于这个话题的研究。或者也许你和我一样,性恋物癖不是一个必要条件,但发现这个图表很迷人…
计算一下
我找到真命天子的几率有多大?世界上有 70 多亿人居住在 200 多个国家的数十万个城镇中。肯定有很多女性符合我的标准…
在世界上所有的人当中,只有 9 个女人符合上述要求。这对我来说是一个令人震惊的结果。我要见 9 亿人才能找到一个这样的女人。当然,我没见过那么多人。我一生中最多见过一万个人。
得出这个惊人的结论后,我兴奋地打电话给我的女朋友,并解释了我们相遇的低得荒谬的概率。我认为电话那头她的沉默意味着她被深深打动了。就像中了彩票一样。我甚至受到启发,给她写了一两封情书;-)然后她告诉我她不觉得好笑,而且世界上肯定没有其他 8 个女人像她一样。
那么你如何筛选 70 亿粒沙子呢?
两年前,在我搬到旧金山之前,我回顾了自己的约会史。我在寻找一种数据驱动的方法。认识女人最有效的方法是什么?哪种认识他们的方式会带来最好的关系?我应该在 Tinder 上搜索还是去参加速配活动?
结果也不是。
Analyzing my own dates and relationships.
通过朋友认识女性是认识可能成为你的另一半的女性的最有效的方式。朋友是一个伟大的过滤器。做了这项研究后,我开始意识到为什么人际关系网如此重要。我无法筛选世界上数百万可能的生活或商业伙伴。但是如果我利用我的朋友和我的关系网,他们可以为我筛选大部分的机会。
这与其他研究非常吻合。大多数异性恋夫妇通过朋友认识,然而网上约会正成为一种流行的认识伴侣的方式。
https://web.stanford.edu/~mrosenfe/Rosenfeld_How_Couples_Meet_Working_Paper.pdf mrosenfe@stanford.edu Searching for a Mate: The Rise of the Internet as a Social Intermediary Michael J. Rosenfeld, Stanford University* and Reuben J. Thomas, The City College of New York Published in the American Sociological Review 77(4): 523–547 ©2012
机会对我们不利,但事实证明很少有女人适合我。我相信有一些女人/男人适合你。现在你知道了:
- 如何确定找到梦中情人的几率
- 如何在世界上数十亿人中搜寻找到他/她。
你喜欢吗?请在这里创建您自己的谷歌表单,确定您找到那一个的机会,并在下面评论您的结果。
如果你喜欢,可以考虑注册我的时事通讯,玩玩创意。
感谢我的女朋友编辑了这个。
GraphQL 模式用例及功能
使用 graphql-tools 复制、混合和定制现有的 API
在本文中,我介绍了 GraphQL API 的一些用例。对于新 API 的创建,有用的功能有“远程模式”、“模式拼接”、“模式转换”。它们能够复制、混合和定制现有的 API 模式。
用例
One API service called “Gian” is created from existing APIs called “Nobita” and “Suneo”
有时我们使用现有的 API 来创建新的 API。有人会从别人的 API 中复制 API 模式。其他人会将一个 API 模式与另一个 API 模式混合在一起。
“Gian” web service frontend utilizing API “manga” originated in “Nobita” API
如果您在 REST API 世界中
这是一个 web 服务“Gian”的用例。这个服务有一些 API(REST API)。大多数 API 源自其他服务“Nobita”和“Suneo”。
- “漫画”API 是从一个服务大雄复制而来,“游戏”API 是从一个服务 Suneo】复制而来
- 服务巨人融合了服务大雄和服务太阳
- orenomanga 的 API 是原创的,但它的内容来自“漫画”API。服务 Gian 修改 API 的内容。(例如。添加新字段或重命名现有字段。)
您可以使用 API 网关工具,但是管理许多 API 是一件困难的事情。
有用的功能:在 GraphQL 世界中复制、混合和定制模式
GraphQL 和一些工具为复制、混合和定制 API 准备了有用的函数。
- [初步]模式自省
- API 复制的远程模式
- 用于 API 混合的模式拼接
- 用于 API 复制和定制的模式委托
- 用于 API 定制(修改)的模式转换
初步知识:GraphQL 中的模式检查
在 GraphQL 中,你只用 GraphQL Schema 语言定义数据类型,然后 GraphQL 服务器自动提供 API 和文档并发布。例如,服务 Nobita 定义了以下数据类型。
GraphQL 模式语言包括数据名称、类型和描述。这些信息被重组为 API 规范文档的格式,称为自省。您可以通过特殊查询__schema.
向 GraphQL 服务器请求这些信息
GraphQL schema creates API and document (__schema) automatically.
自省使 GraphQL 服务器和客户机能够共享模式并验证查询。
功能 1(复制一个 API)“远程模式”
远程模式从远程 GraphQL 服务器获取 API 模式🤔。
Remote schema enables Gian to copy Nobita’s API schema and reproduce it.
当大雄 web 服务正在发布其 API 文档时,graphql-tools makeRemoteExecutableSchema
(→ref) 可以获取 API 文档并复制 API。此函数接收远程 GraphQL 服务器 URL 作为参数。introspectSchema
在通常情况下连用。
Web service Gian. (gian.ts)
graphql-tools
中的makeRemoteExecutableSchema
获取大雄的 API 文档(__schema)并在该服务器中生成可执行的 API 和自己的文档。😲
这个 web 服务器从下面几行开始😜
Starting web service Gian
功能 2(混合 API)“模式拼接”
融合大雄的 API 和 Suneo 的 API(模式拼接)🤔
graphql-tools
的mergeSchemas
(→ref)作为模式拼接工具非常好用。
过程
- 使用远程模式获取 Nobita 和 Suneo 的模式(上一节)
- 将模式数组传递给
mergeSchemas
参数schemas
现在我们有了一个新的服务,包括现有的多个 API😀
功能 3(复制和定制 API)“模式委托”
当您想要复制 API 模式并进行一些定制时(例如重命名查询字段),可以使用Schema Delegation
的(→ref)graphql-tools.
的
你可以在 query resolver
中使用delegateToSchema
,然后你让你的服务器上的任何请求转移到指定的服务器并进行查询。
以下示例显示了将原始的manga
查询重命名为另一个名称orenomanga
😳
功能 4(修改 API)“模式转换”
当你想定制一个现有的 API(例如删除查询字段),你可以使用Schema Transforms
(→ref)graphql-tools.
的
以下示例暗示原始查询manga
被 Gian 移除😱
transformSchema
的第二个参数是转换类型。在本例中,它被设置为FilterRootFields
,这意味着“场移除”。
其他转换
- 过滤器类型 /移除类型/有时使用
- 有时重命名类型/使用
- TransformRootFields,RootField / 修改查询、变异、订阅/很少使用之一
- FilterRootFields / 删除字段/使用大部分
- 重命名根字段 /重命名字段/常用
- ExtractField / 换路径/不太懂……
- WrapQuery /也许全能/不太懂…
摘要
GraphQL 和graphql-tools
是优秀的工具🍺
- 复制/
makeRemoteExecutableSchema,
introspectSchema
/ 这么有用 - 迸/
mergeSchemas
/ 有用的 - 定制/
transformSchema
/也许有用
我没有提到模式指令。您可以通过身份验证来限制访问。
遗留问题
如果重用 API,可能会遇到新的问题。
- N+1 problem⌛️
有时候,会出现性能问题。模式拼接可能会从一个请求带来 N+1 个请求的结果。用于批处理查询的数据加载器对于这类问题是有效的。数据加载器由来自脸书的 GraphQL 联合开发者 Lee Byron 开发。 - 版权问题👁
当您使用任何其他人的 API 时,您必须遵守规则。例如,当重用的 API 需要 creative commons cc-by 时,必须说明作者的姓名。您可以向 GraphQL 响应添加元数据,并使用中间件进行定制。
摘要
GraphQL 工具(远程模式和模式拼接)支持复制、混合和定制 API。很简单。
图形和 ML:线性回归
为了启动机器学习的一系列 Neo4j 扩展,我实现了一组 用户定义的过程 ,在图形数据库中创建线性回归模型。在这篇文章中,我演示了在 Neo4j 浏览器中使用线性回归来建议德克萨斯州奥斯汀的短期租赁价格。让我们来看看用例:
Photo from Yelp user Benigno A. (https://www.yelp.com/user_details?userid=GS2R_auCwoSzfQccLJIY6Q)
德克萨斯州奥斯汀最受欢迎的地区是通过邮政编码的最后两位数字“04”来识别的。这里有最时尚的俱乐部、餐馆、商店和公园,“04”是游客经常去的地方。假设你是一个将要去度假的奥斯汀当地人。你想趁你不在的时候,通过出租你的房子来利用你的邻居的声望。然而,你在每晚收费多少上有点卡住了。
此处 William Lyon 使用 Neo4j 对德克萨斯州奥斯汀的现有短期房屋租赁进行建模。除了每晚的价格,每个Listing
节点还包含诸如卧室数量、浴室数量、容纳的客人数量和可用性等属性。你家有四间卧室,两间浴室,可以住五个客人。我们如何使用存储在 Will 图表中的住房数据来为您的列表预测合适的每夜房价?
Will’s data model demonstrates that the data set is well suited for Neo4j
我们必须以某种方式量化未知变量(价格)和已知变量(卧室、浴室和可容纳的客人数量)之间的关系。住房市场中的许多价格预测者使用房间总数来预测住房价格。他们假设价格和房间数量之间的关系是线性的。我们将把这一理念扩展到短期租赁市场。线性回归是一种用于分析变量之间关系的工具,在这里,它可以用于量化每晚租金与列表中总房间数之间的关系。
一些背景:
回归是建模两个变量之间关系的线性方法。因变量、 y ,是我们希望预测的数量(在本例中是租赁价格)。我们使用独立变量的已知值,“ x ”来预测因变量。简单线性回归的目标是创建一个以自变量为输入并输出因变量预测值的函数。记得代数课吗?由于模型是一条线,所以以*y* = **a** + **b****x*
的形式写出来,可以让它用两个参数唯一表示:
斜率( b )和截距( a )。
https://en.wikipedia.org/wiki/Linear_regression
目测有几个( x , y )数据点的图。简单的线性回归找出通过点的直线,这些点与数据最“符合”。为了确定最佳拟合线,我们使用最小二乘法的方法找到系数 a 和 b ,这些系数根据线性模型*y* = **a** + **b****x*
最小化每个观察值的实际 y 值与其预测 y 值之间的平方差之和。
有几种测量方法试图量化模型的成功,或它与数据的“拟合”程度。例如,决定系数(R ) 是从自变量中可预测的因变量方差的比例。系数 R = 1 表示因变量的方差完全可以从自变量中预测出来(因此模型是完美的)。
线性回归是统计学中最流行的工具之一,它经常被用作机器学习的预测器。
等等,这是图的问题吗?
最小二乘法可以追溯到 19 世纪早期。它不依赖于数据可能存储在图中的事实。如果不是图形问题,为什么要在 Neo4j 中实现线性回归?因为在我们的例子中,我们感兴趣的数据已经存在于一个图表中了!
图表是租赁数据模型的合理选择,因为短期租赁数据固有的关联特性,建模为
(:User)-[:WRITES]->(:Review)-[:REVIEWS]->(:Listing)
以及列表、用户和评论的持续更新。线性回归可能不是图的问题,但数据集总体上肯定是。价格预测只是我们想要做的整体分析的一部分。在 Neo4j 中实现回归程序可以让我们避免将数据导出到另一个软件中的麻烦。通过这种方式,我们可以利用图形数据库的独特功能,同时也不会忘记更传统的统计方法,如线性回归。我们还创建了另一个构建模块,用于在我们的图形数据上运行更复杂的分析管道。
关于这些用户自定义过程的实现细节,请查看我的下一篇文章。在这里,我们将在创建过程之后进行分析。
我们开始吧
安装程序
在 Neo4j 桌面中创建项目和数据库。从最新的线性回归版本下载 jar 文件。将 JAR 文件放到数据库的 plugins 文件夹中。将regression
包的内容添加到管理- >设置底部的已识别程序中。如果你已经安装了 APOC 和图形算法,它看起来会像这样:
dbms.security.procedures.whitelist=algo.*,apoc.*,regression.*
How to add to a Neo4j Desktop Database
重新启动数据库。奔跑
CALL dbms.procedures() YIELD name WHERE name CONTAINS 'regr' RETURN *
检查regression.linear.*
程序是否可以使用。
导入数据
打开 Neo4j 浏览器。从 Neo4j 浏览器运行:play http://guides.neo4j.com/listings
,按照导入查询创建威尔的短期租赁列表。
Neo4j Browser Guide (Slides) for the Rental Listings
添加我们的列表
创建一个新的Listing
节点,代表我们希望添加到“04”短期租赁市场的房屋。在图中,每个Neighborhood
都可以通过存储在属性neighborhood_id
中的邮政编码来识别。
MATCH (hood:Neighborhood {neighborhood_id:'78704'})
CREATE (list:Listing
{listing_id:'12345', bedrooms:4, bathrooms:2, accommodates:5})
MERGE (list)-[:IN_NEIGHBORHOOD]->(hood)
分割训练和测试数据
我们应该使用哪个房屋数据子集来创建模型?房价因位置而异,所以让我们只使用位于“04”附近的列表来创建一个模型。列表节点包含bedrooms
和bathrooms
属性,所以我们将这两个值的和作为房间总数的近似值。因此,我们的数据集是“04”邻域中具有有效的price
、bedrooms
和bathrooms
属性的所有列表。
我们可以使用整个数据集来训练模型,但这样我们就无法在看不见的数据上测试模型的性能。因此,我们将数据集分成 75:25(训练:测试)的样本。从所有合格的列表节点中收集节点 id,并使用regression.linear.split
函数随机选择 75%的数据集。用Train
标记这个子集。
MATCH (list:Listing)-[:IN_NEIGHBORHOOD]->(:Neighborhood {neighborhood_id:'78704'})
WHERE exists(list.bedrooms) AND exists(list.bathrooms)
AND exists(list.price)
WITH regression.linear.split(collect(id(list)), 0.75) AS trainingIDs
MATCH (list:Listing) WHERE id(list) in trainingIDs
SET list:Train
将:Test
标签添加到数据集中剩余的列表节点。
MATCH (list:Listing)-[n:IN_NEIGHBORHOOD]->(hood:Neighborhood {neighborhood_id:'78704'})
WHERE exists(list.bedrooms) AND exists(list.bathrooms)
AND exists(list.price) AND NOT list:Train SET list:Test
初始化模型
查看我们的create
程序,它有这样的签名:
create('model-name', 'model-type' = 'Simple',
constantTerm? = true, noOfIndependentVars = 1)
为了给将来使用相同的create
程序实施多元线性回归创造灵活性,您必须输入模型类型'Simple'
。create 过程还将模型是否应该包含常数项和独立变量的数量作为参数。这些默认设置分别等于true
和1
。如果您知道您的数据在其整个域上遵循一个线性关系,并且必须经过原点,则仅使用false
排除常数项。****
CALL regression.linear.create('rental prices', 'Simple', true, 1)
添加培训数据
找到所有标有Train
的Listing
节点,添加每个对应的(bedrooms
、price
)数据点。如果我们不小心多次添加来自同一个列表的数据,我们的模型将会不太准确。作为额外的预防措施,让我们用标签Seen
来标记Listing
节点,以表明训练数据已经被添加到模型中:
MATCH (list:Listing:Train) WHERE NOT list:Seen
CALL regression.linear.add('rental prices',
[list.bedrooms+list.bathrooms], list.price)
SET list:Seen
注意,
regression.linear.add
程序采用List<Double>
类型的独立变量,即使这是简单的线性回归。这保留了将来创建多元线性回归(具有多个独立变量)并继续使用相同程序的灵活性。
检查模型
注意在的任何一点,你都可以调用info
程序。它将总是返回关于模型的信息流,并且如果模型的状态表明这些字段是有效的,它将使用关于训练和测试数据的统计来填充trainInfo
和testInfo
映射。
CALL regression.linear.info('rental prices')
添加测试数据
既然我们已经训练了模型,我们需要在看不见的数据上测试它的性能。添加测试数据:
MATCH (list:Listing:Test) WHERE NOT list:Seen
CALL regression.linear.add('rental prices',
[list.bedrooms + list.bathrooms], list.price, **'test'**)
SET list:Seen
注意,为了添加测试数据,我们使用相同的
add
过程,但是将数据类型(采用默认值'train'
)设置为'test'
。
执行测试计算
一旦我们最终确定了测试数据,调用test
程序来分析模型。
CALL regression.linear.test('rental prices')
在这种情况下,训练 R = 0.363,测试 R = 0.456。该模型预测测试数据中出现的方差比训练中出现的方差比例更大!虽然这两个值都比我们希望的要低一点(理想情况下,R 为 0.6 或更大),但这些数字表明模型在看不见的数据上表现相似。为了直观的理解,您可以看到测试数据比训练数据更接近趋势线。这是因为采样的测试数据点较少:
做出并存储预测
这些线性回归过程是用来自 Commons Math 库的SimpleRegression
构建的。虽然过程regression.linear.train
确实存在,并且将用于具有多个独立变量的未来线性模型,但是对于简单回归,不需要调用train
过程(如果调用,它将只返回关于模型的信息)。只要模型中有两个或更多数据点,模型就能够做出预测。作为用户,您可以决定是否在进行预测之前对模型进行测试。**
既然我们已经测试了模型,我们可以做出明智的价格预测。我们可以使用用户定义的函数** regression.linear.predict
来预测您在“04”中列出的 4 卧室、2 浴室(总共大约 6 个房间)的价格:**
RETURN regression.linear.predict('rental prices', [6])
或者在图表中为每个价格未知的“04”列表进行预测并存储预测:
MATCH (list:Listing)-[:IN_NEIGHBORHOOD]->
(:Neighborhood {neighborhood_id:'78704'})
WHERE exists(list.bedrooms)and exists(list.bathrooms)
AND **NOT exists(list.price)**WITH list, list.bedrooms + list.bathrooms as rooms
SET list.predicted_price =
regression.linear.predict('rental prices', [rooms])
RETURN DISTINCT rooms, list.predicted_price AS predictedPrice
ORDER BY rooms
现在,所有“04”列表都存储了推荐价格,可以提供给主机,以帮助他们做出明智的价格选择。
编辑测试/培训数据
我们对数据集一无所知,只知道所有列表都位于“04”中,并且包含有效的bedrooms
、bathrooms
和price
数据。让我们通过排除不受欢迎的列表来改进我们的模型。我们将只列出属于(:Review)-[:REVIEWS]->(:Listing)
关系的数据点,这表明该列表至少有一个审查。希望这将消除来自新上市房源的数据(潜在的不可靠定价)。
首先,通过从“租赁价格”模型中复制培训数据来创建一个新模型。
CALL regression.linear.create('popular rental prices', 'Simple')
CALL regression.linear.copy('rental prices',
'popular rental prices')
然后通过从没有评论的列表中删除数据来编辑模型。
MATCH (list:Listing:Train)
WHERE **NOT (:Review)-[:REVIEWS]->(list)**
CALL regression.linear.**remove**('popular rental prices',
[list.bedrooms+list.bathrooms], list.price)
REMOVE list:Train, list:Seen
现在,我们必须从没有审查的列表中删除测试数据。随着测试数据被添加到模型中,使用在训练期间创建的模型参数来执行计算。因此,如果任何训练数据被更新,则模型改变,并且所有测试计算无效。更改训练数据会自动使测试数据无效。因此,我们必须从没有评论的列表中删除Test
标签,然后重新添加所有剩余的测试数据。
请注意,如果在模型测试期间,您希望更新测试数据,您可以调用
regression.linear.add
或regression.linear.remove
而无需重新添加所有测试数据。只要训练数据保持不变,这就会起作用。
MATCH (list:Listing:Test) WHERE NOT (:Review)-[:REVIEWS]->(list) REMOVE list:Test
MATCH (list:Listing:Test)
CALL regression.linear.add('popular rental prices',
[list.bedrooms + list.bathrooms], list.price, 'test')
RETURN count(list)
CALL regression.linear.test('popular rental prices')
哇,消除“不受欢迎”的列表数据提高了我们模型的拟合度!现在,训练 R = 0.500,测试 R = 0.561。两者都增加了,而且测试 R 再次高于训练。这表明我们的模型更好地解释了价格数据中的差异,并在看不见的数据上表现良好。让我们更新图表中的预测价格:
MATCH (list:Listing)-[:IN_NEIGHBORHOOD]->
(:Neighborhood {neighborhood_id:’78704'})
WHERE exists(list.bedrooms)and exists(list.bathrooms)
AND NOT exists(list.price)WITH list, list.bedrooms + list.bathrooms AS rooms
SET list.predicted_price =
regression.linear.predict('popular rental prices', [rooms])RETURN DISTINCT rooms, list.predicted_price AS predictedPrice
ORDER BY rooms
存储、删除和重新加载模型
我们完成了今天的分析!让我们保存刚刚创建的模型。SimpleRegression
的每个命名实例都作为静态变量存储在用户定义的过程中。只要数据库关闭,这些变量就会被清除。确保在关闭数据库之前,您存储了您想要在图中或外部保留的任何模型的序列化版本:
MERGE (m:ModelNode {model: 'popular rental prices'})
SET m.data = regression.linear.data('popular rental prices')
删除模型:
CALL regression.linear.delete('popular rental prices')
当您重新启动数据库时,将模型从图中加载回过程中:
MATCH (m:ModelNode {model: 'popular rental prices'})
CALL regression.linear.load(m.model, m.data, 'Simple')
YIELD model, framework, hasConstant, numVars, state, nTrain, nTest, trainInfo, testInfo
RETURN *
现在,模型已经准备好进行额外的数据更改、测试和预测了!注意,新加载的简单回归不包含任何测试数据。
附加功能
请注意,以下程序存在,但不在本文讨论范围内:
regression.linear.clear
-默认情况下清除模型中的所有数据,或者只清除带有附加参数的测试数据'test'
regression.linear.addM
-从输入/输出数据列表中添加相应的数据点。如果从外部来源(如 CSV 文件)添加数据,会很有帮助regression.linear.removeM
-从输入/输出数据列表中删除相应的数据点
有问题吗?评论?你是更新测试数据回归平方和(SSR)计算公式的专家吗(我需要一些帮助)?请联系 LinkedIn 或 @ML_auren
特别感谢格蕾丝·特诺里奥对这个项目的帮助。查看她的博客或在推特上与她交谈
图形和 ML:多元线性回归
上一次 ,我用 简单的 线性回归从 Neo4j 浏览器创建了一个德克萨斯州奥斯汀的短期租房的模型。在这篇文章中,我演示了如何通过一些小的调整,同一套 用户定义的过程 可以创建一个带有多个 独立变量的线性回归模型。这被称为多元线性回归。
Nightlife in Austin, TX (https://www.nestvr.com/)
我们之前使用短期租赁列表中的房间总数来预测每晚的价格。然而,显然还有其他因素可能会影响价格。例如,一个列表接近热门旅游区可能会极大地影响其价值。让我们再来看看 Will 的数据模型,看看还有哪些额外的信息可以用来预测租赁价格。
Will Lyon’s rental listing data model with node properties
因为我们没有地址,所以很难分析一个列表相对于奥斯汀最受欢迎的目的地的位置。相反,考虑一下(:Review)-[:REVIEWS]->(:Listing)
关系。在我之前的文章中,我限制我的数据集只包括至少有一个评论的列表。我这样做是为了剔除定价可能不可靠的新上市商品,这大大提高了我的模型的拟合度。现在,让我们更深入一点,除了房间数量之外,使用一个列表的评论数量来预测每晚的价格。
请注意,我只是玩玩,并没有真正的统计理由来假设评论数量影响上市价格。我这样做主要是为了演示如何创建一个有多个自变量的模型。接下来分析评论并以某种方式量化文本的正面/负面反馈会很有趣。
一些背景
在我们回到奥斯汀租赁市场之前,我将回顾一下多元线性回归的重要细节。请记住,在简单线性回归中,我们希望使用单个独立变量“ x ”的值来预测因变量、 y 的值。不同的是,在多元线性回归中,我们用多个自变量( x1,x2,…,xp )来预测 y 而不是只有一个。
对多元线性回归的直观理解有点复杂,取决于自变量的数量( p) 。如果 p = 1,这只是简单线性回归的一个实例,并且( x1,y )数据点位于标准的二维坐标系中(具有 x 和 y 轴)。线性回归通过最“适合”数据的点找到线*。*
Visual for p = 1 (https://en.wikipedia.org/wiki/Linear_regression)
如果 p = 2,这些( x1,x2,y) 数据点位于三维坐标系中(具有 x,y,和 z 轴),多元线性回归找到最适合数据点的平面。
Visual for p = 2 (https://www.mathworks.com/help/stats/regress.html)
对于更多数量的独立变量,视觉理解更抽象。对于 p 自变量,数据点( x1,x2,x3 …,xp,y )存在于一个 p + 1 维空间中。真正重要的是,线性模型(是 p 维)可以用 p + 1 系数β0 , β1 ,…, βp 来表示,这样 y 就可以用公式*y =* **β0** + **β1****x1* + **β2****x2* + … + **βp****xp*
来近似。
小心!
多元线性回归有两种:普通最小二乘法(OLS) 和广义最小二乘法(GLS) 。两者之间的主要区别在于,OLS 假设任何两个独立变量之间都有很强的相关性。GLS 通过转换数据来处理相关的独立变量,然后使用 OLS 用转换后的数据建立模型。
这些程序使用 OLS 的方法。因此,要建立一个成功的模型,你应该首先考虑你的变量之间的关系。将bedrooms
和accommodates
作为两个独立变量可能不是一个好主意,因为客房数量和入住的客人数量可能有很强的正相关性。另一方面,评论数量和房间数量之间没有明确的逻辑关系。为了进行更定量的分析,挑选独立变量,使每一对变量的皮尔逊相关系数接近于零(见下文)。
多个 LR 在工作
好了,让我们来看一组在 Neo4j 中构建多元线性回归模型的查询。
建立
从最新的线性回归版本下载并安装 jar 文件。从 Neo4j 浏览器运行:play http://guides.neo4j.com/listings
,按照导入查询创建威尔的短期租赁列表。请参阅我之前的文章以获得安装 g 线性回归程序和导入奥斯汀租赁数据集的更全面的指南。
分割训练和测试数据
导入数据集并安装程序后,我们将数据集分成 75:25(训练:测试)的样本。让我们从上次的最终模型开始,再次只考虑至少有一个评论的列表。用:Train
标注 75%。
MATCH (list:Listing)-[:IN_NEIGHBORHOOD]->(:Neighborhood {neighborhood_id:'78704'})
WHERE exists(list.bedrooms)
AND exists(list.bathrooms)
AND exists(list.price)
**AND (:Review)-[:REVIEWS]->(list)** WITH regression.linear.split(collect(id(list)), 0.75) AS trainingIDs
MATCH (list:Listing) WHERE id(list) in trainingIDs
SET list:Train
并给剩下的 25%加上:Test
的标签。
MATCH (list:Listing)-[n:IN_NEIGHBORHOOD]->(hood:Neighborhood {neighborhood_id:'78704'})
WHERE exists(list.bedrooms)
AND exists(list.bathrooms)
AND exists(list.price)
**AND (:Review)-[:REVIEWS]->(list)**
AND NOT list:Train
SET list:Test
量化相关性
如果您想知道自变量之间的皮尔逊相关性,请使用函数regression.linear.correlation(List<Double> first, List<Double> second)
。此函数的一个List<Double>
输入包含来自许多列表的聚合“评论数”数据,并且每个“评论数”是通过聚合来自每个列表的关系来计算的。我们不能在同一个查询中执行两个级别的聚合(collect(count(r))
)。因此,我们必须执行两个查询。
首先在数据集列表上存储一个num_reviews
属性。
MATCH (:Review)-[r:REVIEWS]->(list)
WHERE list:Train OR list:Test
WITH list, count(r) AS num_reviews
SET list.num_reviews = num_reviews
然后从所有列表中收集(num_reviews,rooms)数据并计算相关性。
MATCH (list)
WHERE list:Test OR list:Train
WITH collect(list.num_reviews) as reviews,
collect(list.bedrooms + list.bathrooms) as rooms
RETURN regression.linear.correlation(reviews, rooms)
皮尔逊相关系数在[-1,1]范围内,0 表示不相关,1 表示完全正相关,而-1 表示完全负相关。评论数量和房间数量具有相关性-0.125,这表明非常弱的负相关性。对于这种小相关性,OLS 是一种可接受的方法,因此我们可以继续使用该模型。阅读更多关于解释相关系数的信息。
初始化模型
我们调用相同的create
过程,但是现在我们的模型框架是'Multiple'
而不是'Simple'
,独立变量的数量是2
而不是1
。
CALL regression.linear.create(
'mlr rental prices', **'Multiple'**, true, **2**)
添加培训数据
以与简单线性回归相同的格式添加已知数据。只需在自变量列表中添加另一个条目(评论数量)即可。
MATCH (list:Train)
WHERE NOT list:Seen
CALL regression.linear.add(
'mlr rental prices',
[list.bedrooms + list.bathrooms**, list.num_reviews**],
list.price)
SET list:Seen RETURN count(list)
训练模型
我们必须训练模型,使线参数(βi)、决定系数®等。是在接受测试数据或做出预测之前计算出来的。如果您忘记了这一步,然后尝试添加测试数据或进行预测,您的模型将会自动首先接受训练。
CALL regression.linear.train('mlr rental prices')
注意,在调用
train
过程后,您仍然可以添加更多的训练数据。你只需要在测试新型号之前再打一个电话给train
!
添加测试数据
根据看不见的数据分析模型的性能。请记住,当您添加测试数据时,需要一个额外的参数'test'
作为过程regression.linear.add
的输入。
MATCH (list:Test)
WHERE NOT list:Seen
CALL regression.linear.add(
'mlr rental prices',
[list.bedrooms + list.bathrooms, list.num_reviews],
list.price,
**'test'**)
SET list:Seen
RETURN count(list)
测试模型
现在所有的测试数据都已添加,调用test
过程来计算测量值(在下面的testInfo
中),以便我们分析模型的性能。
CALL regression.linear.test('mlr rental prices')
您也可以通过调用info
过程随时返回关于模型的信息。
CALL regression.linear.info('mlr rental prices')
再来看调整后的 R 。这个值类似于我们在上一篇文章中考虑的 R,但它被调整了,这样,随着独立变量被添加到模型中,调整后的 R 不会增加,除非模型被改进得比它偶然得到的更多。因此,当将我们的多元线性回归模型(有两个独立变量)与之前的模型(只有一个独立变量)进行比较时,调整后的 R 是更好的成功衡量标准。
我们通常寻找大于 0.6 的 R 值,但是更接近 1 的值表示更好的线性拟合。这里,训练调整 R = 0.517,测试调整
R = 0.556。上次我们训练和测试的 R 值分别是 0.500 和 0.559。使用多元线性回归创建的模型在训练数据上比上次的简单回归模型表现得好一点,在测试数据上表现得差不多。能不能找到更好的自变量,改进我的工作?
后退一步
在 Neo4j,我和我的同事们目前面临着这样一个问题:机器学习和人工智能在图数据库中处于什么位置?这个问题有几个答案,但没有一个是“正确”的。在我以前的帖子中,我演示了如何根据图中的数据建立一个简单的机器学习模型,以避免导出到另一个软件。在这篇文章中,另一个有趣的方面出现了:用存储在图的结构中的数据构建模型的能力。我们能够通过计算每个列表上的(:Review)-[:REVIEWS]->(:Listing)
关系的数量来轻松检索“评论数量”数据。在这方面,图形数据库增加了我们可以轻松访问的数据类型,以便建立机器学习模型。
展望未来,我很想探索机器学习模型如何更有效地利用数据集的图结构来学习和做出预测。
摘要
下面是使用多元线性回归的regression.linear.*
过程时必须做出的调整的快速列表:
- 在
regression.linear.create
期间指定型号“Multiple”
- 在
regression.linear.create
期间指定独立变量的数量 - 没有或
regression.linear.remove
方法用于测试或训练数据 - 你不能用
regression.linear.copy
将训练数据从一个模型复制到另一个模型 - 在测试、分析或预测之前,您应该通过调用
regression.linear.train
来训练模型 - 查看
adjusted R²
以更准确地比较具有不同数量独立变量的模型 regression.linear.data
返回模型的参数,不将其字节【序列化】。所以不能用regression.linear.load
。(我想解决这个问题)
这个项目是实验性的,所以我真的很感谢任何反馈。检查程序存储库并联系 LinkedIn 或 @ML_auren 。
对我来说,是时候离开这个线性建模的世界,转而使用逻辑回归构建分类器了。敬请关注新的一组程序和新的数据集!
模型调整的网格搜索
模型 超参数 是模型外部的模型特征,其值无法从数据中估计。必须在学习过程开始之前设置超参数的值。比如支持向量机中的 c ,k-最近邻中的 k ,神经网络中的隐层数。
相反, 参数 是模型的内部特性,其值可以从数据中估计。例如,线性/逻辑回归的β系数或支持向量机中的支持向量。
网格搜索用于寻找模型的最佳超参数,从而产生最“准确”的预测。
让我们通过在乳腺癌数据集上建立分类模型来看看网格搜索。
1.导入数据集并查看前 10 行。
输出:
数据集中的每一行都有两个可能的类别:良性(用 2 表示)和恶性(用 4 表示)。此外,该数据集中有 10 个属性(如上所示)将用于预测,除了作为 id 号的样本代码号。
2.清理数据,并将类值重命名为 0/1,用于建模(其中 1 表示恶性病例)。还有,我们来观察一下班级的分布。
输出:
良性病例 444 例,恶性病例 239 例。
3.在构建分类模型之前,让我们构建一个虚拟分类器来确定“基线”性能。这就回答了这个问题——“如果只是猜测,这个模型的成功率会是多少?”我们使用的虚拟分类器将简单地预测多数类。
输出:
从输出中,我们可以观察到测试数据集中有 68 个恶性和 103 个良性案例。然而,我们的分类器预测所有病例都是良性的(因为它是多数类)。
4.计算此模型的评估指标。
输出:
模型的准确性为 60.2%,但这是准确性可能不是评估模型的最佳度量的情况。那么,让我们来看看其他评估指标。
上图是混淆矩阵,添加了标签和颜色以便更直观(生成该矩阵的代码可以在这里找到)。总结一下混淆矩阵:真阳性(TP)= 0,真阴性(TN)= 103,假阳性(FP)= 0,假阴性(FN)= 68。评估指标的公式如下:
由于该模型没有对任何恶性病例进行正确分类,因此召回率和精确度指标为 0。
5.现在我们已经有了基线精度,让我们用默认参数建立一个逻辑回归模型并评估这个模型。
输出:
通过用默认参数拟合逻辑回归模型,我们得到了一个“更好”的模型。准确率为 94.7%,同时精度更是惊人的 98.3%。现在,让我们再次看一下该模型结果的混淆矩阵:
查看错误分类的实例,我们可以观察到 8 个恶性病例被错误地分类为良性(假阴性) 。此外,只有一个良性病例被归类为恶性(假阳性)。
假阴性更严重,因为疾病被忽略了,这可能导致患者死亡。同时,假阳性会导致不必要的治疗——产生额外的费用。
让我们通过使用网格搜索来寻找最佳参数,从而尽量减少假阴性。网格搜索可用于改进任何特定的评估指标。
我们需要关注的减少假阴性的指标是召回。
6.网格搜索最大化回忆
输出:
我们调整的超参数是:
此外,在网格搜索功能中,我们有一个评分参数,可以指定评估模型的指标(我们选择了 recall 作为指标)。从下面的混淆矩阵中,我们可以看到假阴性的数量减少了,然而,这是以假阳性增加为代价的。网格搜索后的召回率从 88.2%上升到 91.1%,而准确率从 98.3%下降到 87.3%。
通过使用“f1”分数作为评估指标,您可以进一步调整模型,以在精确度和召回率之间取得平衡。查看这篇文章,更好地理解评估指标。
网格搜索为指定的每个超参数组合建立一个模型,并评估每个模型。一种更有效的超参数调整技术是随机搜索,其中超参数的随机组合用于寻找最佳解决方案。
连接 LinkedIn和 Github查看完整的笔记本。
[## rohanjoseph 93/用于数据科学的 Python
用 Python 学习数据科学。通过创建帐户,为 rohanjoseph 93/Python-for-data-science 开发做出贡献…
github.com](https://github.com/rohanjoseph93/Python-for-data-science/blob/master/Grid%20Search%20-%20Breast%20Cancer.ipynb)
基本事实与偏见
Photo by David Kovalenko on Unsplash
机器学习的主要目标是在给定一组输入的情况下预测结果。在人工智能社区取得的惊人进展中,这个目标一直保持不变。几十年前的这些进步引导我们研究今天有趣的社会含义。具体来说,在我们的算法技术中嵌入的基本事实和偏见之间有一种独特而敏感的相互作用。
相互影响
基础事实是我们的模型用来训练和测试自己的标记结果。通过有监督的机器学习方法,我们的模型试图将输入与这一事实联系起来。也就是说,当我们命令脚本进行拟合和预测时,我们会做出一些假设。我们说,我们相信有一种关系存在,这些变量有一些预测能力,这些结束标签是最终的真相。
最后一点可能会有点模糊。
我们怎么知道末端标签是真实的呢?好吧,如果我们谈论的是试图预测温度,我们可以相当肯定的是,这些数字中有很多是真实的。也就是说,现在的温度传感技术既精确又准确。然而,如果我们预测某个人是否会得到贷款,或者某个人是否是 x 公司的合适人选呢?
这就是有趣的地方。在这些情况下,我们引入了偏见——人为的偏见,隐含的和未知的偏见。这些混杂的变量可以成就或破坏一个实验,这就是为什么人们大声疾呼在我们的算法中包含多样性。
我同意这个观点。然而,我也相信我们的算法是工具,不管数据集如何多样化,垃圾进垃圾出的概念仍然盛行。
也就是说,这是良好的第一步。我相信,提高算法可推广性的下一步是始终回到最初的、潜在的假设,并作为数据科学家批判性地评估它们。
我们真正要求算法做的是什么?有没有可以减轻偏差的来源
这些问题至关重要,尤其是当我们使用这些模型来筛选一组从事特定任务的人时。也许我们会在此期间发现一些关于我们自己的有趣的事情。
感谢阅读。
分组卷积—并行卷积
通常,卷积滤波器被逐层应用于图像以获得最终的输出特征图。我们增加每层内核的数量来学习更多的中间特征,从而增加下一层的通道数量。但是为了了解更多的特征,我们也可以创建两个或更多的模型,并行地训练和反向传播。在同一幅图像上使用不同组卷积滤波器组的过程称为分组卷积。简而言之,创建一个具有一定数量层的深层网络,然后复制它,以便在一个图像上有不止一个卷积路径。这种分组卷积思想的第一次使用可以在 Alexnet 中看到,如下所示。
Grouped convolutions in ALexnet
Alexnet 中使用了分组卷积,以便深度神经网络可以在当时可用内存较小的功能较弱的 GPU 上进行训练。为了理解分组卷积的重要性,让我们想象一个没有分组卷积的世界,以及我们可能面临的问题:
- 我们构建每层多个内核的深度网络,从而在每层产生多个通道输出。基于我们的网络学习各种各样的低级和高级特征的希望,这将导致更宽的网络。
- 每个核滤波器对在前一层获得的所有特征图进行卷积,导致大量卷积,其中一些可能是冗余的。
- 训练更深层次的模型将是一场噩梦,因为在单个 GPU 上拟合这些模型并不容易。即使这是可能的,我们可能不得不使用较小的批量,这将使整体收敛困难。
我们可以开始看到分组卷积对于训练深度网络是多么重要。下图显示了如何理解 ResNext 体系结构中的分组卷积:
Grouped convolutions in ResNext
通过使用分组卷积,让我们逐一解决上述问题:
- 通过分组卷积,我们可以构建任意宽的网络。取一个过滤器组模块并复制它们。
- 现在,每个滤波器只对从其滤波器组中的核滤波器获得的一些特征图进行卷积,我们大大减少了获得输出特征图的计算量。当然,你可以说我们复制了过滤器组,导致了更多的计算。但是为了客观地看待问题,如果我们将所有的内核过滤器都放在分组卷积中,并且不使用分组卷积的概念,那么计算的数量将呈指数增长。
- 我们可以通过两种方式并行培训:
- —数据并行性:我们将数据集分成块,然后在每个块上进行训练。直观上,每个块可以被理解为在小批量梯度下降中使用的小批量。块越小,我们可以从中挤出更多的数据并行性。然而,更小的组块也意味着我们在训练中更像随机而不是批量梯度下降。这将导致收敛速度变慢,有时收敛效果更差。
- —模型并行性:在这里,我们尝试将模型并行化,以便我们可以接收尽可能多的数据。分组卷积实现了高效的模型并行,以至于 Alexnet 在只有 3GB RAM 的 GPU 上进行了训练。
Grouped convolutions’ performance nos
除了解决上述问题之外,分组卷积似乎可以更好地表示数据。本质上,不同过滤器组的内核过滤器之间的相关性通常很小,这意味着每个过滤器组正在学习数据的唯一表示。这在传统的神经网络中是不容易实现的,因为核通常是相互关联的。事实上,上图显示了增加组数对模型整体准确性的影响。最棒的是,所有的精度都来自降低的计算成本。
这篇文章的灵感来自雅尼·若安努的另一篇精彩文章,标题为滤波器组(分组卷积)教程,提供了更详细的分析。
使用内容分组对内容进行分组
本周,我有幸在纽约州布法罗市的 WPCampus 举办了一场关于谷歌分析的研讨会。我在会议期间演示的一个功能是使用内容分组,以便能够更好地理解作为比较单位的网站的各个部分。在与其他人交谈后,我认为有必要对这个特性和一些例子进行更深入的讨论。所以,给你!
你可能会问,什么是内容分组?这个设置非常自我描述——它是 Google Analytics 的视图管理中的一个功能,可以让你将网站某个部分的内容定义为属于某种类别。谷歌的文档使用了一个商店示例,展示了如何根据产品使用的方面命名法将产品集合分组在一起。
Initial Content Groupings Panel
我将演示的例子使用了来自我的演示站点的虚拟课程目录的课程部分级别。正如您在上面看到的,我设置了三个内容分组(其中两个我已经弃用)。类似于目标,一旦你进行了分组,你实际上就无法摆脱它。你只需要关掉它或者改变它的用途。同样值得注意的是,您最多只能有 5 个分组类别,所以如果您认为您可能会大量使用该功能,请花一些时间提前计划,以便以提供最大实用价值的方式创建顶级分组。
Making a new Content Grouping via tracking code
在 g 中创建一个内容组就像点击新内容分组按钮并给它命名一样简单。这是第一步。这是您在分析报告中使用内容分组功能时将看到的名称。在那里,您将看到三种方法来确定将与该选择一起出现的组。第一种方法是启用跟踪代码。这意味着你告诉 Google,你将在你的站点上使用 JavaScript 代码动态地提供名称(或者你将在标签管理器中使用标签,见最后一节)。
如果您使用跟踪代码方法,请记下适合您的分析实现的代码版本(此时最像通用分析)。您需要将它复制到页面上与您告诉 Google 的所属组相匹配的某个 JavaScript 中。所以在我的例子中,我的内容分组是名称课程,我将要使用的组名称将是 100 级课程和 200 级课程。在我的例子中,我不打算使用这种方法,但是如果我这样做了,大一学生的课程页面上的代码将看起来像这样:
ga('set', 'contentGroup1', '100 Level Courses');
创建组名的第二个选项是使用提取规则。这允许您使用 regex 来定义页面路径、页面标题或应用程序屏幕名称的一部分,以按照约定提供组名。根据我的例子给出的页面结构,看起来应该是这样的:
Using page path to determine the content group name.
这很好,也很有效,但是这意味着我的内容组名称会有点难看。在我的例子中,根据我的 URL,这些名字可能是 1xx 或 2xx 。可用,但不美观。页面标题提取可以提供更好的可读性,但是在我的例子中,页面标题不包括文本数据,这很好地满足了这个目的。值得一提的是,在这个和下一个选项中,如果与您的群组匹配的内容出现在网站的不同部分,您可以设置多个提取。这些规则将按照您列出它们的顺序应用,就像过滤器在视图上的应用一样。一旦有匹配的提取,它就停止检查。
最后一个选项是我最喜欢的,它提供了很大的灵活性,而不需要太多额外的努力。它基本上是提取选项的一个稍微高级的版本。这是组使用的规则定义。
My catalog rule definitions.
在这里,您可以看到我已经创建了两个组名,每个组名都基于我创建的目录的各个部分。显然,一个真正的内容分组可以有更多,但这只是一个演示案例。所以你有两个。规则定义基本上类似于自定义命名的提取,使用您提供的名称,而不是由提取产生的匹配。因此,如果我们看一个,我们会看到一个类似的匹配设置,如上例。
Rule to match all 100 level courses.
有了这些规则,Google Analytics 将开始跟踪与我的正则表达式匹配的页面,这样我就可以在我的内容报告中看到它们。这对于比较整体区域或不同类型的内容非常有用。因此,如果我想查看我的目录中不同类别的流量比较,我可以查看我的行为>网站内容>所有页面报告,并查看您的主维度的选项,您现在可以选择将内容分组更改为您选择的组,在我的情况下是课程。
Viewing a content report with content groupings enabled.
现在我可以看到,在我想象的目录中查看 200 门水平课程的用户的退出率大大高于 100 门水平课程(注意:我使用数据表正上方右侧的按钮将该报告切换到了对比视图),并且远远高于其他所有课程。每当你在你的内容分组维度中看到*(未设置)值时,这表示你的站点上的内容在内容分组*下没有被分配任何组名。它基本上是给你一个基线,让你可以和你的团队进行比较。
考虑到这一点,你可以开始考虑将它应用到大学网站或其他地方的所有方法,将所有大区域的内容聚集在一起,在那里你有许多不同的部分,你想与他人进行比较。一些想法可能是(格式遵循内容组>组名结构):
- 学术部门>部门名称
- 学院>学院名称
- 新闻>新闻类别
- 运动>运动名称
- 学生生活>服务类型
- 事件>事件类别
- 位置>位置类型
- 专业>专业名称
这样做,如果你的物理系有 87%的退出率,你可以将它与通信系的 32%的退出率进行比较,并意识到可能是你网站的物理系出现了问题,导致人们离开。它消除了使用 Data Studio 或其他工具进行更复杂的计算或定制报告来确定这些块如何比较的需要。
我要提到的另一个技术要点是,您也可以使用 Google Tag Manager 来确定类别组名称。正如我在开始时提到的,您可以使用跟踪代码作为名称的传输机制。Simo Ahava 有一个关于如何使用标签管理器的教程。它有点旧(你现在会使用谷歌分析设置变量,但它仍然会给你一些基本的东西。
Content Groups in Google Tag Manager
他的例子使用多个标签来实现这个目的。在上面的截图中,我使用了单个 pageview 标签中的变量,它可以使用一个 JavaScript 变量或 dataLayer 变量来检查页面的路径,并确定它应该指向的内容组索引,以及它应该如何基于此获得名称。这减轻了您创建决定这一切的代码的责任,但这也使它成为最灵活的方法,并且比在站点模板使用的原始 JavaScript 文件中做同样的事情更容易维护。
这就是在谷歌分析中使用内容分组的优缺点。想一想你可能如何应用它们,或者在下面的评论中分享你当前的实现,让其他人看到。
原载于 2017 年 7 月 15 日fienen.com。
GSoC 2017:神功文档的 Eclipse 插件
WSO2 复杂事件处理器(WSO2 CEP)是一个实时复杂事件处理引擎,在识别传入的事件流/大型数据馈送中的模式时具有快速的响应时间,并且存储空间最小。WSO2 CEP 的核心是 Siddhi,这是一种类似 SQL 的查询语言,用于模式和序列检测等复杂查询,几乎解决了复杂事件处理中抑制应用程序性能指标的所有问题。
除了组织给出的项目目标之外,还完成了从 Antlr 到 Xtext 的语法文件转换,并修复了神功语法中的歧义。
迄今为止所做的工作如下。
- 神功语法文件的 Antlr 到 Xtext 转换
- 修正神功语法中的歧义
- 语法和语义突出显示
- 内容辅助
- 错误报告
Antlr 到 Xtext 神功语法文件转换
原始神功语法文件是用 Antlr 写的。因为 Xtext 被确定在这个项目中使用,所以将 Antlr 语法文件转换成 Xtext 是必要的。Xtext 框架是在 Antlr LL(*)解析器生成器之上开发的。尽管 Antlr 和 Xtext 的语法大致相似,但在 Siddhi 语法中处理左递归是我在转换语法文件时面临的一个挑战,因为 Xtext 处理它的方式不同。因为 LL 解析器是一个自顶向下的解析器,并且输入是从左到右传递的,所以需要小心处理左递归。EveryPatternSourceChain、PatternSourceChain、LeftAbsentPatternSource、RightAbsentPatternSource、LeftAbsentSequenceSource、RightAbsentSequenceSource、SequenceSourceChain 和 MathOperation 是为 Xtext 解析器生成器设置了左递归调用图的规则。下面描述一个例子。
神功最初的 EveryPatternSourceChain 和 PatternSourceChain 规则如下。
every_pattern_source_chain :
'('every_pattern_source_chain')' within_time?
| EVERY '('pattern_source_chain ')' within_time?
| every_pattern_source_chain '->' every_pattern_source_chain
| pattern_source_chain
| EVERY pattern_source within_time? ; pattern_source_chain :
'('pattern_source_chain')' within_time?
| pattern_source_chain '->' pattern_source_chain
| pattern_source within_time? ;
规则 every_pattern_source_chain 在“every _ pattern_source_chain”->“every _ pattern _ source _ chain”替代元素中留下了递归,并且规则 pattern _ source _ chain 在“pattern _ source _ chain”->“pattern _ source _ chain”替代元素中留下了递归,因为最左边的符号是规则本身。这些被转换成 Xtext 语法,如下所示。
EveryPatternSourceChain:
EveryPatternSourceChain1 =>({EveryPatternSourceChain.left=current} op= ‘->’ right=EveryPatternSourceChain1)*;EveryPatternSourceChain1 returns EveryPatternSourceChain:
=>(OPEN_PAR eps=EveryPatternSourceChain CLOSE_PAR wt=WithinTime?)
|psc=PatternSourceChain
|every=EVERY psc=PatternSourceChain1;PatternSourceChain:
PatternSourceChain1 -> ({PatternSourceChain.left=current} op=’->’ right=PatternSourceChain1)*;PatternSourceChain1 returns PatternSourceChain:
=>(OPEN_PAR psc_2=PatternSourceChain CLOSE_PAR wt=WithinTime?)
|ps=PatternSource wt=WithinTime?;
移除 MathOperation 规则中的左递归是一项不同的任务,因为它需要考虑语法的运算符优先级。运算符优先级越高,规则调用得越晚。例如,下面解释规则的一部分。
原始 MathOperation 规则的一部分如下。
math_operation :
'('math_operation')'
|null_check
|NOT math_operation
|math_operation (multiply='*'|devide='/'|mod='%') math_operation
|math_operation (add='+'|substract='-') math_operation
|function_operation
|constant_value
|attribute_reference;
这里“math _ operation(multiply = ’ * ’ | devide = ‘/’ | mod = ’ % ')math _ operation”和“math _ operation(add = ‘+’ | subtract = ‘-’)math _ operation”是左递归规则。由于乘法的运算符优先级高于加法,所以应该在 Xtext 中首先调用与加法相关的规则,如下所示。
MathOperation:
MathAddsubOperation;MathAddsubOperation returns MathOperation:
MathDivmulOperation =>({MathAddsubOperation.left=current} (add='+'|substract='-') right=MathDivmulOperation)*;MathDivmulOperation returns MathOperation:
MathOtherOperations =>({MathDivmulOperation.left=current} (multiply='*'|devide='/'|mod='%') right=MathOtherOperations)*;MathOtherOperations returns MathOperation:
=>({NotOperation} not=NOT op=MathOperation)
|OPEN_PAR op=MathOperation CLOSE_PAR
|=>NullCheck
|mathOtherOperations1=MathOtherOperations1;MathOtherOperations1:
const_val=ConstantValue
|fo=FunctionOperation
|attrRef=AttributeReference;
其他 Antlr 至 Xtext 转换可在此处找到。
修正语法中的歧义
语法中的模糊性会导致特定输入有几个解析树,而解析器无法确定它应该遍历的确切路径。特别是在 Xtext 中,歧义可以通过使用句法谓词、左因子分解和在工作流中启用回溯来解决。启用回溯并不是一个好的选择,因为它隐藏了由模糊语法引起的大问题。
我使用了 Antlrworks 调试工具来识别语法中的歧义,以及输入如何通过解析器进行解析。也就是说,它显示了在解析树表示中为特定用户输入解析了哪些规则。Antlrworks 以如下所示的典型方式显示语法规则中的歧义。两个彩色箭头显示了导致特定模糊性的两个可选路径。
Xtext 中有两种类型的语法谓词运算符,即“->”和“= >”。“–>”是第一个标记谓词,表示如果您处于决策点,无法做出决策,请查看第一个标记并做出决策。" = > "表示如果你正处于一个决定点,难以做出决定,检查语法的特定部分并做出决定。
例如,LogicalStatefulSource 规则中的歧义是使用如下的句法谓词解决的。
LogicalStatefulSource:
=>(stdSource+=StandardStatefulSource and=AND)stdSource+=StandardStatefulSource
|=>(stdSource+=StandardStatefulSource or=OR) stdSource+=StandardStatefulSource;
这意味着,如果解析器处于 LogicalStatefulSource,需要做出决定,请考虑“(STD source+= StandardStatefulSource AND = AND)”或“= >(STD source+= StandardStatefulSource OR = OR)”来决定选择哪个备选项。
另一个例子是 ExecutionPlan 的歧义修复,如下所示。
最初的规则是,
siddhi_app :
(app_annotation|error)* ( (definition_stream|definition_table|definition_trigger|definition_function|definition_window|definition_aggregation|error) (';' (definition_stream|definition_table|definition_trigger|definition_function|definition_window|definition_aggregation|error))* ';'? || (execution_element|error) (';' (execution_element|error))* ';'? || (definition_stream|definition_table|definition_trigger|definition_function|definition_window|definition_aggregation|error) (';' (definition_stream|definition_table|definition_trigger|definition_function|definition_window|definition_aggregation|error))* (';' (execution_element|error))* ';'? ) ;
这里第一个和第三个替代元素是不明确的。Xtext 中的模糊固定规则是,
ExecutionPlan:
(appAnn+=AppAnnotation)*( (=>defStream+=DefinitionStream|=>defTable+=DefinitionTable| =>def_window+=DefinitionWindow| =>defTrigger+=DefinitionTrigger|=>defFunction+=DefinitionFunction|=>defAgrregation+=DefinitionAggregation)=>(';' (=>defStream+=DefinitionStream|=>defTable+=DefinitionTable| =>def_window+=DefinitionWindow| =>defTrigger+=DefinitionTrigger|=>defFunction+=DefinitionFunction|=>defAgrregation+=DefinitionAggregation))* =>(';'(exElement+=ExecutionElement))* ';'?);
更多歧义固定可以在这里找到。
语法突出显示
句法突显由词汇突显和语义突显两部分组成。对关键词、数字和标点符号进行词汇突出显示。语义突出显示在变量名上,如流名、表名、触发器名、窗口名、函数名和紫色事件名。如下图所示,实现了词汇和语义突出显示。
Lexical and Semantic highlighting for Siddhi
代码自动完成
内容辅助在任何编辑器中都是一个非常重要的特性。在这个插件中,根据如下所示的语法为关键字和标点符号提供了内容帮助。
代码自动完成是针对执行计划中不同位置使用的流名、表名、窗口名、属性名和事件名完成的。这主要是通过使用 Xtext 中的局部作用域和交叉引用来实现的,因为它只需要提出属于一个执行计划的变量。交叉引用的一个例子将在下面解释。
Source:
strId=[Source1|IdNew];Source1:
inner='#'? name=IdNew;DefinitionStream:
{DefinitionStream}(ann += Annotation)* DEFINE (STREAM|TABLE) src=Source1 OPEN_PAR (feature += Features )(',' feature += Features )* CLOSE_PAR;StandardStream:
MainSource =>((postWindowHandlers = BasicSourceStreamHandlers)?);MainSource:
src=Source basicSSh=BasicSourceStreamHandlers1;
此处,源规则具有对 IdNew 类型(标识符)的 Source1 的交叉引用。因此,在流定义中定义的流名称可以在查询输入规则的 StandardStream 元素中访问。在语法文件中还使用了其他几个交叉引用,如 SourceOrEventReference、FeaturesOrOutAttrReference 和 AttributeNameReference。
查询输入中实现的代码完成如下所示。当所需变量的字母作为建议给出时,建议变量名。
为查询部分实现的代码完成如下。
定义了查询部分功能操作的一般方案,方便了用户的使用。当键入“,”或“:”时触发。
该建议可以定制如下。
错误报告
语法错误由 Xtext 框架本身捕获,并显示在编辑器中。逻辑错误,比如把错误的变量放在错误的地方,是在对流名、表名等进行了局部作用域之后捕获的。
语法错误显示在编辑器中,如下所示。
逻辑错误显示给用户,如下所示。
github 中共享代码的链接:https://github.com/udeshika-sewwandi/siddhi_editor
已实现特性的 Eclipse 编辑器插件链接:https://drive . Google . com/drive/folders/0 B3 tawrcyox 59 C1 pxa 2 vdammzuu?usp =共享
如何安装插件?
要在 Eclipse 中安装插件,需要遵循以下步骤。
- 下载并安装 Eclipse IDE(例如 Mars 2 可以在这里下载)
按照下面的过程在 Eclipse 中安装 Xtext。
- 点击帮助菜单->安装新软件
- 将http://download . eclipse . org/modeling/tmf/xtext/updates/composite/releases/放入与字段一起安装 Xtext
- 获取后,从列表中的 Xtext 类别中选择 Xtext complete SDK 并安装 Xtext
- 一段时间后,Xtext 将被安装并完成安装
- 重启 Eclipse
然后安装神功插件
- 在这里下载中的插件文件夹。
- 点击帮助->安装新软件
- 点击“添加”按钮
- 点击“本地”按钮,选择主插件文件夹,然后点击“确定”
- 为“名称”字段命名,然后单击“确定”
- 从列表中选中该功能,取消选中“按类别分组项目”,然后单击“下一步”
- 经过一些下载过程后,插件将安装到 Eclipse IDE 中
- 重启 Eclipse
安装两个插件后,按照下面的步骤创建一个. siddhi 文件。
- 点击文件->新建->项目
- 选择常规->项目
- 右键单击创建的项目并选择新建->文件
- 将文件名命名为 file_name.siddhi
- 点击完成
- Eclipse 将询问“您想将项目转换成 Xtext 项目吗?”->单击是
- 然后创建了。神功文件可以如下图所示使用
未来的工作
实现 Siddhi Eclipse 编辑器的运行和调试功能需要在将来完成,以便使用该插件。运行功能可以通过使用 Xtext 框架提供的代码生成器存根为特定于领域的语言定制来提供。
确认
这是我在大学生活中第一次参加 2017 年谷歌代码之夏,为 WSO2 的一个项目做贡献,WSO2 是一家开源的知名公司。Sriskandarajah Suhothayan 和 Nirmal Fernando 是我在 WSO2 的导师。虽然这是我第一次为 WSO2 做贡献,但是导师们真的很有帮助,在整个夏天都在这个项目上指导我。非常感谢导师和 WSO2 给我机会,指导我确定项目目标。我真的很高兴在 GSoC 2017 中为 WSO2 做出贡献,我也愿意在这款产品以及其他产品上为 WSO2 做出贡献。
GSoC 是一个鼓励大学生为开发自由和开源软件做出贡献的项目。我很高兴成为 2017 年 GSoC 的一员,我非常感谢谷歌每年组织这个项目,这为学生提供了一个在虚拟工作环境中工作来提高许多技能的大好机会。
如果需要任何澄清,请通过sewwandikaus . 13 @ CSE . mrt . AC . lk联系我。
关于 Xtext 的进一步阅读
[1]https://eclipse . org/Xtext/documentation/102 _ domainmodelwalk . html
[2]https://eclipse . org/Xtext/documentation/301 _ grammar language . html
[3]https://eclipse . org/Xtext/documentation/307 _ special _ languages . html
https://github.com/MartijnDwars/Xtext/wiki/Expressions
GSoC-2017:在 HSF 欧洲核子研究中心从事异常检测工作
Error function a 200,000 points long Time-Series
今年是我第一次作为学生参加谷歌的代码之夏项目。我被 CERN-HSF 组织接受,在 ATLAS 子组织下从事一个异常检测项目。这篇文章的目的是总结我在这个巨大的夏天所做的努力,包括成就和失败。
我要感谢我的导师 Mario、Alessandro 和 Tomas,他们来自 HSF 欧洲粒子物理研究所,在这段旅程中为我提供了指导。!
深度异常— 使命宣言
ATLAS Open Analytics 是一个平台,用于收集多个参与异构计算中心之间各种计算操作生成的数据。
我与欧洲粒子物理研究所-HSF 的项目都是关于使用机器学习来识别这个时间序列数据中的异常。
其想法是设计、构建和部署一个框架,该框架能够对这种实时 ATLAS 分布式计算数据中的异常行为进行监控、分类和聚类,然后根据这些信息自主采取行动。
目标
建立一个自动管道来触发异常,然后分析它们以预测其根本原因以及一些潜在的解决方案。
这个想法是让系统从历史和实时数据中学习,并对这种异常事件做出准确的预测。
数据探索
Recurring Trends for “transfer-done” events within a single day’s index
大多数 ADC 数据存储在两个不同的 ElasticSearch 实例中。对于像我这样不熟悉这个非常棒的工具的人来说,ElasticSearch 是一个分布式的 RESTful 搜索和分析引擎。ElasticSearch 最棒的地方在于它非常快,几乎是实时的。它允许你以一种非常方便的方式存储、索引和分发你的数据。结合一些真正灵活和直观的可视化工具,如 Kibana,它变得非常有吸引力,特别是如果你有大量的数据。
我个人使用 python 的两个 elasticsearch 库来处理 elasticsearch 实例: elasticsearch-py 和 elasticsearch-dsl 。
在任何给定的时间点,CERN ES 主实例包含 31 个" atlas-rucio-events-* "索引,过去 31 天中的每一天都有一个索引。由于可获得的数据确实非常庞大且种类繁多,我的导师 Mario 建议我只从这些“ atlas-rucio-events ”指数中的“ transfer-done ”事件开始。
我首先查询“transfer-done”类型的事件,并将它们保存到本地数据帧中。通常,每个索引包含 1,000,000–2,000,000 个此类事件,每个此类事件有 26 个子字段,包含有关特定传输的各种信息,如其来源、目的地、正在传输的文件大小等。
这是我对数据的第一次观察,下面是我在探索和提取数据时绘制的一些重要图表
File sizes for transfers in bytes
Actual durations for the transfers to finish(not including the time spent in queue)
Time that a file spent in queue before it actually got transferred
有些文件排了整整一周的队是很常见的!!(1 周~=600000 秒)
除了这些连续变量,还有很多分类变量需要我去处理。下面是一些直方图,描述了它们在单个索引中的分布频率:
在不冒任何风险的情况下,我选择了所有可以有意义地解析为任何模型的输入的变量,这些变量可能对决定转移的持续时间有影响。我总共得出了 10 个变量,它们将作为我未来模型的训练数据。
用于异常检测的机器学习
在理解了数据中不同变量的实际含义后,我开始实际识别这些转移中的异常。直到现在,我都不知道“大海捞针”到底意味着什么。
作为我的社区结合期的一部分,我对特定于时间序列数据的 ML 算法做了相当多的研究。科学家在各种研究论文和文章中使用的大多数模型都采用了某种“监督学习方法”来解决这样的问题。但这里的情况略有不同。在这些巨大的转会事件数据框架中,没有标签将异常转会与正常转会分开。无人监管的方法可能是前进的方向,我在我最初的提案中也提到过这一点。
ML 型号的选择:
神经网络
对我来说,我们正在做的一件事是,有数百万个数据点可供我训练机器学习模型!!我还被可靠地告知,发生异常的百分比非常低,大约为 1 / 1000 。有了这些知识,我开始在 Keras 中实现各种前馈架构,试图预测特定传输的传输持续时间,并取得不同的成功。
我尝试过的几乎所有前馈架构,更深、更广、不同/高级激活……都表现出了相似的性能。该模型学习了一段时间,然后稳定在一个不太好的水平上。我尝试了不同的缩放选项,看看是否有什么不同,但没有任何明显的改善。
递归神经网络-长短期记忆网络
在前馈网络上做了一些实验后,我终于到达了 RNNs。鉴于我的数据的时间序列性质,它们是我最理想的选择,我对它们持乐观态度。
我从简单的 LSTM 网络和一个 LSTM 开始,并开始训练他们在 10 个事件上的时间转移步骤。它们比以前的架构稍微好一点,但也好不了多少。经过与多个堆叠层、每层更多的 lstm/GRU 单元以及用数据训练网络(其中每个数据点都追溯到其历史)的争论,我最终得出了以下架构:
该模型以 100 个事件的时间步长为输入,即,为了预测每个单次转移的持续时间,它考虑了该转移的详细信息+在该转移之前的 99 次转移的详细信息。这些传输中的每一个都有 9 个输入—文件大小、队列中的时间、源、目的地、活动、协议、传输端点、src 类型、dst 类型。
对于 dim (100,9)的输入,网络输出单个值(输入中第 100 次传输的传输持续时间)。我们在数百万个这样的事件的数据集上训练这个网络,并且每个事件的迭代不超过 2 次。这样做是为了确保模型不会学习映射数据集中的任何异常事件。以下是对 200,000 次前所未见的传输进行评估后的网络性能图:
error distribution
这是迄今为止我得到的性能最好的型号。
使用的阈值:
- 为了区分异常和正常事件,我使用了 600 秒的阈值。实际传输持续时间比预测时间长 600 秒以上的传输事件被标记为异常。
分析检测到的异常
在构建了检测异常的网络之后,下一步是验证它们并确定它们的根本原因。这被证明是相当具有挑战性的,因为检测到的异常数量仍然太大。正是在这个时候,我开始与托马斯合作。由于没有潜在的资源来实际验证检测到的异常,调查其原因更加困难。
correlation plot b/w predicted and actual durations
经过一段时间的探索、研究我的结果以及与托马斯的多次讨论,我们得出了以下结论:
- 我需要一个更好的标签标准来标记异常事件;比单个错误阈值更强大的东西。
- 发现的异常数量需要降低到一个更易于管理的数量。
使用“性能声纳”数据
这个项目的最终目的是修复导致系统中检测到的异常的问题。这只能通过修复发生传输异常的特定源-目的地链路来完成。
欧洲核子研究中心有一个系统,它测量关于这种源-目的地对链路性能的一些核心统计数据。这些统计数据包括“吞吐量”、“延迟”和“数据包丢失率”等信息。我相信这些数据可以作为一个更好的异常触发系统的基础,用于检测基于 LSTM 模型预测的异常。
比如说-
- 在具有异常高的分组丢失率的 src-dst 链路上传输的、与预测传输持续时间具有高偏差值的传输事件更有可能是异常。
- 类似地,吞吐量度量可用于对通过同一 src-dst 链路传输的异常进行分组。
这就是我的努力所达到的状态。写这篇文章的时候,我还在纠结性能声纳数据。道路是艰难的,但我相信我的工作已经成功地证明了检测和修复异常的系统肯定是可行的。
失败和缺点:
这个项目是我第一次体验专业编码。我从未参与过如此规模、如此复杂的项目。尽管我为自己取得的进步感到自豪,但直到现在,我发现自己还存在一些不足:
- 实现一个实时触发器——这样做的目的是同时提取实时索引的数据,对其进行预处理,在模型中运行,并检测其中的异常。由于我缺乏使用多任务脚本的经验,我无法成功实现它。
- 当前模型有时不能捕捉具有较大传输大小的一些非异常传输的高尖峰。
虽然我的正式工作期间,我想继续这个项目的工作,并看到它通过它的完成和部署!!
Git 仓库— 深度异常
用 R 从婴儿的名字中猜出你出生的年份
一个简单的问题展示了数据科学的痛苦
最近,推特上流传着一篇关于“希瑟”这个名字兴衰的文章在 20 世纪 70 年代和 80 年代,希瑟是一个非常受欢迎的婴儿名字,但这个名字很快就变得模糊不清了。 JD Long ,一位在保险行业工作的著名数据科学家,发了一条关于此事的推文:
读了这条推文,以及数据科学家珍妮·汤普森的回应,让我想知道当时我认为是一个简单的问题:
哪一组四个婴儿名字最能代表你出生的年份?
在 JD 的例子中,他认为他出生年份的四个婴儿名字是 Chad、Clay、Jennifer 和 Lori。对于生于 80 年代中期的我来说,我猜我出生年份的四个名字是马修、迈克尔、劳拉和伊丽莎白。但是什么才是最好的名字呢?我们如何用数据来展示它呢?
这类问题正是数据科学家一直在处理的。当你读它的时候,它听起来像是一个你能很快回答的非常精确的问题。一旦你开始思考这个问题,它就变得很难理解。很快你就会意识到,你处理问题的方式会极大地改变答案,而且没有唯一正确的方法。最后,我尝试了三种不同的方法来解决这个问题,得到了一些有趣而独特的结果。
尝试 1:使用统计上最有可能来自该年的名字。
对我来说,“最能表明”这个词的意思是,如果你告诉某人这四个名字,他们就会知道你出生的确切年份。这个定义启发了我把它变成一个统计问题的第一种方法:如果从一年中随机抽取四个名字,那么这四个名字最有可能是从这一年中抽取的?
换句话说,假设我有一堆粗麻布袋子,每个袋子上都写着一年。在每个袋子里,我都有一些大理石,上面写着那一年出生的每个人的名字。如果我在袋子里选了四颗弹珠,然后递给你,我应该选哪四颗,让你猜猜是哪一年?
This is a super weird metaphor but like, aren’t most statistical and combinatorial framings?
从贝叶斯定理开始的一点点数学,原来我们想找到最大化的四个名字: P(name|year)/P(name) 。或者取我们关心的年份拉名字的概率,除以跨所有年份拉名字的概率(我假设年份的概率相等)。
让我们在 R 里做这个!谢天谢地,哈德利·威克姆利用政府数据制作了一个婴儿名字分析包。不幸的是,这只是使用了美国的数据,没有办法将结果推广到其他国家。首先,让我们加载我们需要的库:
library(babynames) # to get the baby name data
library(dplyr)
library(tidyr)
library(ggplot2)
babynames 包包含一个数据集babynames
,其中有一行是每年、婴儿姓名和出生时分配的性别(错误地标记为“性别”)。它记录了这个名字出现的次数,以及那一年出生的婴儿中使用这个名字和性别的百分比。只包括一年中至少出现五次的姓名和性别对。
> babynames
# A tibble: 1,858,689 x 5
year sex name n prop
<dbl> <chr> <chr> <int> <dbl>
1 1880 F Mary 7065 0.0724
2 1880 F Anna 2604 0.0267
3 1880 F Emma 2003 0.0205
4 1880 F Elizabeth 1939 0.0199
首先,让我们计算总概率,作为每个名字分数的分子。以下代码创建了一个数据框,其中为每个分配的性别和姓名对各占一行:
total_props <-
babynames %>%
complete(year,nesting(sex,name),fill=list(n=0,prop=0)) %>%
group_by(sex,name) %>%
summarize(total_prop = mean(prop)) %>%
ungroup()
代码获取表babynames
,用 0 填充缺失的行,然后获取每个名字和指定性别对的平均概率。
然后,我们计算一年中每个名字和指定性别的概率,并将其加入到前面的表中。我们计算这个比率,看看每年的前四名:
most_informative_ratio <-
babynames %>%
inner_join(total_props,by=c("sex","name")) %>%
mutate(value = prop/total_prop) %>%
group_by(year) %>%
filter(row_number(desc(value)) <= 4) %>%
ungroup() %>%
arrange(year,desc(value))
我们来看结果!以下是 1980 年最具参考价值的名字:
> most_informative_ratio %>% filter(year == 1980)
# A tibble: 4 x 7
year sex name n prop total_prop value
<dbl> <chr> <chr> <int> <dbl> <dbl> <dbl>
1 1980 F Nykeba 26 0.0000146 0.000000107 136
2 1980 F Sumeka 14 0.00000786 0.0000000578 136
3 1980 F Renorda 11 0.00000618 0.0000000454 136
4 1980 F Shanndolyn 11 0.00000618 0.0000000454 136
这些是什么鬼名字!?雷诺达?Shanndolyn?原来这四个名字只在 1980 年的数据中出现过。所以这些名字非常晦涩,很少使用。因此,如果我说表示 1980 年生日的四个名字是“Nykeba、Sumeka、Renorda 和 Shanndolyn”,那么是的,这些名字只在 1980 年使用,因此我表示年份。然而,这个问题隐含着这样一个假设:与我交谈的人以前听过这些名字*。*我对这个问题的统计公式没有考虑到一些从未说过但确实有必要的东西。因此,如果我们想考虑到与你交谈的人必须听说过这些名字,我们需要一种新的方法
尝试 2:用高于平均水平的名字来表示年份。
第一次尝试的问题是它没有考虑名字的受欢迎程度,只考虑了名字的信息力量。使用名字的平均受欢迎程度和一年内的受欢迎程度的想法似乎仍然有希望,那么如果我们把它改为两个数字之间的差异会怎么样呢?
这给出了公式二:在给定的一年中,哪一组四个名字的使用从它们的平均值增加最多?因此,如果多年平均下来,男性迈克尔斯的可能性是 1%,如果在某一年,男性迈克尔斯是儿童的 5%,那么我们会给出 4%的值?哪些名字的价值最高?
所以再次使用之前的总概率表,让我们计算一个新的最具信息量的表。现在的值是当年的概率和总概率之差。
most_informative_diff <-
babynames %>%
inner_join(total_props,by=c("sex","name")) %>%
mutate(value = prop - total_prop) %>%
group_by(year) %>%
filter(row_number(desc(value)) <= 4) %>%
ungroup() %>%
arrange(year,desc(value))
让我们看看 1980 年的表格:
> most_informative_diff %>% filter(year == 1980)
# A tibble: 4 x 7
year sex name n prop total_prop value
<dbl> <chr> <chr> <int> <dbl> <dbl> <dbl>
1 1980 F Jennifer 58381 0.0328 0.00609 0.0267
2 1980 M Jason 48173 0.0260 0.00413 0.0218
3 1980 M Michael 68673 0.0370 0.0178 0.0192
4 1980 M Christopher 49088 0.0265 0.00785 0.0186
太好了!这些看起来像 1980 年的流行名字!如果你看看不同的年份,这些名字似乎与当时流行的名字一致。不幸的是,现在这些年往往会混在一起。让我们看看 1978-1982 这四个名字。在这里,我添加了姓名在年份中的排名,并创建了一个name_gender
变量来制作表格。我使用 tidyr spread 函数,这样我们可以一次看到所有这些数据
most_informative_diff %>%
group_by(year) %>%
mutate(rank = paste0("rank_",row_number())) %>%
ungroup() %>%
mutate(name_gender = paste0(name,"-",sex)) %>%
select(year,rank,name_gender) %>%
spread(rank,name_gender) %>%
filter(year >= 1978, year <= 1982)
结果是:
# A tibble: 5 x 5
year rank_1 rank_2 rank_3 rank_4
<dbl> <chr> <chr> <chr> <chr>
1 1978 Jennifer-F Jason-M Michael-M Christopher-M
2 1979 Jennifer-F Jason-M Christopher-M Michael-M
3 1980 Jennifer-F Jason-M Michael-M Christopher-M
4 1981 Jennifer-F Jessica-F Michael-M Christopher-M
5 1982 Jennifer-F Christopher-M Jessica-F Michael-M
如果你看一下,五年间名字的唯一区别是杰森在 1981 年被杰西卡取代。*这对我们用名字猜测年份的目标来说并不太好。*这是 1980 年以来这四个名字出现的概率图。您可以看到,虽然它们都在那段时间达到峰值,但也有相当数量的其他年份处于高位:
most_informative_diff %>%
filter(year == 1980) %>%
semi_join(babynames, ., by = c("name","sex")) %>%
mutate(name_gender = paste0(name,"-",sex)) %>%
ggplot(aes(x=year,y=prop,group=name_gender, color = name_gender))+
geom_line(size=3) +
theme_minimal(20)+
labs(x="Birth year",y="Probability",color = "Name and assigned gender")
尝试 3:去他妈的,让我们做一个很酷的交互可视化来让人们惊叹。
虽然我可以多花十个小时来精确地找出这个问题的最佳统计框架,但我想尝试一些不同的东西。如果目标是从数据中传达一些意义,通常最好是制作一些有趣的东西,让人们对此进行更深入的思考。有时你只是有喜欢玩东西的业务主管。无论你的情况如何,交互式可视化都是很棒的。
我决定用 R 包 shiny 创建一个随机样本教室。这是一个很好的方法来看看如果你出生在某一年,你周围的人的名字会是什么样的。它传达了流行的名字,加上不流行的是什么样的。 自己试试吧! 如果你想玩很短的代码,在 GitHub 上有。
The my-classroom tool showing a class from the year 2000. You can tell the year by the three Jacobs and two Alex…es(?). Click the picture to try it for yourself!
这个小项目极大地提醒了我,简单的问题可能不容易用数据科学来解决。即使你在一个表格中拥有了你可能需要的所有数据,想出一个将问题转化为分析的方法也可能是一个阻碍。虽然这是一个有趣的小项目,但同样的事情也可能发生在诸如“哪些客户拥有最高的保留率?”或者“本季度我们生产了多少产品?”在这种情况下,你能为自己和同事做的最好的事情就是,在花费大量时间进行交互式可视化之前,立即解决问题的框架,并获得认同。
机器学习工作流的 GUI 化:快速发现可行的流水线
前言
软件开发的一个原则是尽可能自动化。这就是 DevOps 的思维模式,在这种模式下,致力于自动化有助于团队不断地推出特性,同时确保最佳实践得到尊重。当团队优先考虑自动化时,他们的开发人员关注的是为产品带来新特性所需的最少代码。这对现实世界的软件项目有 3 大好处:
- 代码库中少了人类错误;
- 发展更快;
- 更多变异。
虽然前两点是显而易见的,但最后一点却不太受重视。变化随着自动化而增加,因为当需要编写的代码更少时,尝试更容易。在产品发现的早期阶段,变化是一个关键因素。通过尝试更多的选项,我们减少了对前期假设的依赖,转而探索可能结果的空间。
传统软件(没有机器学习)选项很多。无论是添加按钮,创建登录屏幕,还是连接警报,传统软件都有大量选项可供选择。如果可以用计算机代码来表达,这是可以做到的。
Figure 1 With traditional software, product discovery operates under the assumption all features are options.
但是机器学习产品没有这样的保证。有了机器学习,产品特征不再是选项,而是可能性。只有当我们确认正确的数据存在时,我们才能称之为一种可能性。
Figure 2 Machine learning software cannot enter product discovery until it has been determined which product features are actual options.
这对产品发现有影响。产品发现假设选项是给定的;挑战在于发现这些选项中的哪一个会带来最好的产品。但是在机器学习中,我们不能开始这个过程,除非我们已经暴露了哪些选项是可用的,给定了数据。
有了机器学习,产品功能只有在数据允许的情况下才是可能的。
传统软件在“产品发现”期间开始发现过程*,而机器学习应用依赖于该过程之前的发现。这意味着自动化的变化方面,促进发现,必须在开发开始之前发生。换句话说,自动化必须发生在机器学习工作流程中。*
工作流与管道
数据科学家通过机器学习工作流程探索数据并验证模型,如下所示:
Figure 3 The Machine Learning Workflow
这是一个反复的过程,包括收集可用数据、清理/准备这些数据、构建模型、验证预测和部署结果。这导致了需要做出关于什么进入数据管道的战略决策的那种发现。数据管道是上述“扁平”布局的工作流程,负责将上述工作流程迭代中发现的最佳部分投入生产:
Figure 4 The Data Pipeline
尽管数据管道受益于传统软件中的自动化类型(测试、持续集成、构建、持续部署),但机器学习工作流需要不同类型的自动化。
我们如何自动化机器学习工作流程?
这个问题看似显而易见的答案是,某个库尝试了许多不同的模型,并自动验证哪一个模型导致最高的准确性。这些库是存在的,比如谷歌的 AutoML , auto-sklearn , tpot , MLBox ,还有很多其他。虽然这些努力有助于减少找到像样的模型所花的时间,但他们认为有产品价值的模型可以通过最少的人工干预来获得。这大大简化了将原始数据转化为真正可行的机器学习产品的过程。
机器学习实践者转动多个旋钮(超参数)来帮助推动学习算法走向更好的结果。我们最好的数据科学家称这一过程为艺术的原因是,知道转动什么旋钮,以及转动多少,是无法编纂的。它包括经验、与领域专家的交谈,以及最重要的试错。
可能结果的空间来自数据属性、模型和参数的复杂交互。真正自动化的机器学习将不得不做比旋转旋钮更多的事情,直到达到一定的准确度。它必须改变数据量,尝试各种数据集,尝试多种数据准备,设计新功能,运行验证指标的 gammit,并在不同的环境中部署结果;更不用说将特定领域的知识融入到这些步骤中了。
这代表了一种“组合爆炸”数据,科学家在寻找建立可行的机器学习产品时必须控制这种数据。
Figure 5 The right mix of tasks to effectively convert raw data to product-worthy outputs exists in a space of near-infinite possibilities.
有效地将原始数据转换为有价值的产品的正确任务组合存在于一个几乎无限可能的空间中。
经验丰富的数据科学家的标志是他们能够通过缩小可能的组合来控制这种复杂性。通过探索和推理,数据科学家将统计技术与经验结合起来,开辟出一条从无数可能性到少数可行选项的道路。
要真正实现机器学习工作流程的自动化,我们需要的不仅仅是自动化的旋钮转动。我们需要能够执行各种常见的机器学习任务,同时仍然允许人类干预,以探索和推理数据告诉我们的东西。
我认为这种自动化只有通过机器学习工作流程的 GUI 化才有可能。GUI 化就是通过图形用户界面来展示功能。当从业者按下按钮执行普通任务时,这使他们能够快速尝试许多选项,导致发现可行管道所需的大量变化。
这为数据科学提供了一个更加完整的devo PS 理念。虽然我们的管道需要传统的测试和构建实践,但我们也需要工作流程阶段的自动化,因此产品发现可以从一组可行的机器学习选项开始。
将机器学习工作流程图形化似乎太具挑战性了。毕竟,每当我们在机器学习任务中指向一个新的数据集时,事情似乎都会发生变化。但是理解事情出错时的场景正是我们走向自动化的方式。我认为自动化是经验丰富的从业者的责任。作为最终的抽象,自动化确保我们的工作只关注挑战中的新事物。当我们能够快速探索可能性的空间时,我们就能提高数据密集型项目的投资回报率。
自动化是经验丰富的从业者的责任。
随着时间的推移,GUI 化会在机器学习工作流程的每个步骤中产生一组健壮的机器学习能力。最终,这种能力可以作为服务公开,其目的是使团队能够在构思的早期阶段快速评估组织/客户的数据状态。
随着团队继续从事项目,他们会增加这些服务,公开更多的机器学习功能,并在寻求构建机器学习产品时更快地发现存在的选项。
Figure 6 Using a GUI-Service for machine learning. This enables rapid assessment of an organization’s data, laying the foundation for better product discovery.
值得注意的是,GUI 化并不意味着取代手工编码。当我们必须改进从数据中提取价值的过程时,没有什么可以替代深入研究代码。但是编码永远不应该是我们对数据集的第一次尝试。这样做是抽象的失败,自动化的失败,并不代表构建机器学习产品的精益方法。我们应该只在没有遇到问题的时候接触代码。
我们应该只在没有遇到问题的时候接触代码。
为机器学习构建 GUI 服务
机器学习工作流程的 GUI 化有两个部分。在后端上,我们需要将机器学习功能公开为 REST 服务,因此可以使用前端调用常见任务。在前端,我们需要轻松地可视化和管理我们选择运行的任务之间的数据流。
我将列出重要的要求,然后讨论每一点:
技术要求
- 公开R 和 Python 的功能;
- 允许在不重新启动服务的情况下更改代码;
- 允许适当的代码组织和版本控制;
- 间歇数据应可由 R 和 Python 访问;
- 利用本地 R 和 Python 可视化;
- r 和 Python 应该是分开的服务;
- 自动跟踪和管理数据依赖关系;
- 请等到后端完成处理后,再在前端显示结果。
讨论
公开 R 和 Python 的功能:我认为同时支持R 和 Python 是很重要的。这是因为正如本文开头所讨论的,变化是关键。当我们从语言中抽象出来时,唯一重要的是能力*,允许机器学习的全部广度和深度用于应对我们的挑战。*
允许在不重启服务的情况下更改代码:数据科学依赖于 REPL 式的环境,当我们更改代码时,这些环境会显示结果。如果我们的服务包含在 Docker 运行时中,我们将不得不重新构建/运行映像来显示新的变化。另一个选择是 Jupyter 笔记本,它支持 REPL 风格的编码,和可以使用 Jupyter 内核网关公开为 REST 服务。但是同样,如果不重启网关,代码更改是不会暴露的。
Node.js 中的 子进程 允许我们通过 Node.js 调用R 和 Python 函数。当与 web 框架 Express 结合时,就有可能创建一个 REST API,向前端公开 R 和 Python 功能。另外,这种类型的服务不需要重启就可以暴露子进程中的变化。
允许适当的代码组织和版本控制:笔记本在组织代码* 和进行适当的版本控制方面也有缺点。笔记本不是有组织的代码,笔记本之间的版本控制只是对我们工作的 JSON 表示进行版本控制。这对于现实世界的软件开发来说是不可估量的。我们的服务应该允许 R 和 Python 脚本作为适当的文件存在,可以像任何其他应用程序一样进行组织和版本控制。*
R 和 Python 都应该能够访问间歇数据:服务还应该允许 R 和 Python 在工作流的任何阶段访问间歇数据。例如,用户应该能够用 R 准备数据,用 Python 构建模型,在各种语言之间无缝切换。JSON 是一个很好的选择。**
JSON 很容易被 R 和 Python 读写。JSON 也很容易存储嵌套的数据。这对于机器学习非常有用,因为我们经常在一次调用中保存多个数据集,例如将数据分成训练集和测试集:
*[{
"**X_train**": [{
"feature_a": 100,
"feature_b": 150,
"feature_c": 40
}],
"**X_test**": [{
"feature_a": 200,
"feature_b": 250,
"feature_c": 140
}],
"**y_train**": [{
"feature_d": 170
}],
"**y_test**": [{
"feature_d": 270
}]
}]*
利用原生 R 和 Python 可视化:大多数前端应用程序选择交互式的、基于 JS 的可视化。虽然交互式图表有很多好处,但是它们缺少本地 R 和 Python 图表的多样性。例如,我们可以使用代码的 1 行在 R 中创建以下影响图:
复制这个需要多少定制的 JavaScript?原生视觉是数据科学家探索和验证其工作的方式;我们不应该妨碍那件事。
R 和 Python 应该是独立的服务:GUI 服务应该使用微服务架构,以便 R 和 Python 保持独立。仅仅用调用 R 和 Python 的脚本来包装服务并不是一个好的设计选择。当然,这必须与立即暴露代码变更的需求相平衡,正如以上关于 Docker 运行时的观点。
自动跟踪和管理数据依赖关系:执行的每个任务都将依赖于一些先前的数据集,无论是原始数据集还是转换后的数据集。GUI 需要自动跟踪和管理数据集之间的依赖关系,以保留工作流。这可以通过让前端管理跨节点的读写路径来实现,如下图所示:
例如,在节点上运行选择的函数将启动以下步骤:
等待后端完成处理,然后在前端显示结果:由于工作流将涉及运行计算密集型任务,前端必须等待后端处理完成,然后再尝试显示结果。只要我们调用 REST 端点,这就相当简单。比如 jQuery 的 $中的 Promise 接口。ajax 方法确保我们可以推迟执行额外的 JavaScript,直到请求完成。
介绍机器流程
我创建了机器流程来展示本文中讨论的哲学。Machine Flow 包含上述技术要求,并允许数据科学家和数据团队将他们的工作流程 GUI 化。
机器流支持机器学习工作流的可视化执行和跟踪。用户动态创建依赖图,每个节点负责执行一个任务并显示结果。
机器流程提供开发模式和服务模式。开发模式用于主动向我们的服务添加新的 R 和/或 Python 代码。对 R 或 Python 的任何添加/更改都会被它们各自的 REST 端点立即公开,从而允许数据科学家快速构建他们的 GUI 服务。
**服务模式在数据科学家的 GUI 服务中已经添加了大量机器学习功能时使用。在这种情况下,不太需要在后端和前端之间来回移动。服务模式背后的想法是提供一个容器化的运行时,团队成员可以用来构建机器学习工作流,而不必编写代码。
开发模式
我们将从开发模式开始,因为这使我们启动并运行,并且是大多数数据科学家使用机器流的方式。
克隆机器流程
您可以通过在 GitHub 上探索项目来查看自述文件。下载机器流程最简单的方法是打开终端并运行下面的命令:
*git clone https://github.com/WorldofDataScience/machine_flow.git*
这将把机器流带入你的目录。键入 ls 查看内容:
***machine_flow**
├── README.md
├── **app**
├── **data**
├── docker-compose.yml
├── **python_ml**
├── **r_ml***
4 个子目录是最重要的:
- app 包含前端应用*;*
- 数据保存数据集和日志文件*;*
- python_ml 保存我们的 python 脚本;
- r_ml 保存我们的 R 脚本。
打开机器流程 GUI
首先,让我们运行机器流的前端。我们可以启动一个简单的 Python web 服务器,在终端中运行以下命令:
*python3 -m http.server*
我们现在在端口 8000 上提供 HTTP 服务。在浏览器中打开以下链接:
http://localhost:8000/app/machine _ flow . html
您应该看到带有空白工作流程的机器流程应用程序。
启动 R 和 Python 服务
我们的前端通过 REST 端点向后端发送函数来运行 R 和 Python 代码。因此,我们需要在后端启动我们的 R 和 Python 服务。
在“终端”中打开 2 个新标签。在第一个新选项卡中,切换到 r_ml 目录,运行以下命令启动 R 服务:
*node connect.js*
在另一个新选项卡中,切换到 python_ml 目录下和运行相同的命令来启动 Python 服务:
*node connect.js*
每个选项卡都应显示其各自准备好的服务:
> R 机器学习端口:9191
> Python 机器学习端口:8181
添加数据
我们使用的任何数据集都必须添加到数据文件夹中。你可以随意添加;唯一真正的要求是所有数据都是 JSON 格式。
让我们添加来自 GitHub 的波士顿住房数据集:
https://raw . githubusercontent . com/selva 86/datasets/master/Boston housing . CSV
在“终端”中打开一个新标签页(确定您在 machine_flow 目录中),并通过键入 R + enter 启动一个 R 会话。我们通过运行以下命令将数据以 JSON 格式添加到我们的数据文件夹中:
*library(jsonlite)
df <- read.csv('https://url_to_boston_data.csv')
df_json <- toJSON(df)
write(df_json, file='data/boston.json')*
我们可以在 Python 中使用相同的方法,通过启动 Python 会话并运行以下命令:
*import pandas as pd
import jsondf = pd.read_csv('https://url_to_boston_data.csv')with open('data/boston.json', 'w') as f:
f.write(df.to_json(orient='records'))*
在任一种情况下,JSON 输出都有下面的格式,其中每个观察都包含在自己的对象中:
*[{
"feature_a" : "100",
"feature_b" : "150",
"feature_c" : "40"
}, {
"feature_a" : "720",
"feature_b" : "14",
"feature_c" : "431"
},
....
}]*
添加后端功能
为了让我们的前端对我们的数据做任何有用的事情,我们必须向后端添加 R 和/或 Python 函数。在编写我们的第一个后端函数之前,我们需要回顾一些需求,以使后端函数与机器流程一起工作。
每个后端函数必须有前两个参数作为读路径和写路径。函数使用这些来读取正确的数据集并将结果写入正确的路径。这些路径是由机器流自动管理的,所以我们只需要确保在我们的后端函数中使用它们。**
Back-end function for Machine Flow.
正如我们在上图中看到的,我们函数需要的任何额外参数都在 read_path 和 write_path 之后。**
*我们还确保每个函数都以一个**return(‘done’)*语句结束。这告诉节点层 R/Python 已经执行完了它的函数。
添加我们的第一个后端函数
**必须提供的第一个后端函数是 list_data 函数。机器流程要求我们将这个函数命名为 list_data。所有其他功能都可以随意命名。
我们当然可以使用 R 或 Python 来编写 list_data 函数。我将如下使用 R:
*list_data <- **function**(read_path, write_path) {
res <- toJSON(list.files(read_path, pattern = **".json"**))
write(res, file=write_path)
return(**'**done**'**)
}*
这使用 R 的本机 list.files 函数来列出所提供路径中的所有文件。注意,我添加了 read_path 和 write_path 作为参数,并根据需要在我的函数中使用它们。
我们把这个函数放在哪里?
我们将后端函数添加到 Machine Flow 的 r_ml 和 python_ml 目录中的适当脚本中。
***machine_flow**
**r_ml**
├── R_Scripts
└── data_gathering.R
└── data_preparation.R
└── model_building.R
└── model_validation.R*
此时打开一个 IDE 比只打开终端更有意义,因为我们将管理多个脚本。我用 PyCharm 你可以用最适合你的。
因为我们用 R 编写了 list_data 函数,所以我们把它添加到了 R_Scripts 中。导航到 r_ml 目录内的 R_Scripts 并打开其内容。打开 utility_functions.R。您将看到已经添加了 list_data 函数的 Machine Flow,以及一些其他函数。因为它已经在那里,我们不需要添加它。如果您使用 Python 列出文件,您可以将 list_data 函数添加到 python_ml 目录下的 utility_functions.py 中。
添加端点
由于我们已经有了后端功能,我们可以打开机器流程 GUI,并在端点下添加此功能:
点击端点打开功能覆盖图。这是我们告诉机器流 GUI 我们的机器学习服务提供了哪些后端功能的地方。
我们需要添加我们的 list_data 函数。默认端点设置为端口 9191,这是我们的 R 服务的端口,所以我们不会更改它。我们将添加 list_data 作为函数名,选择 data 作为预期输出,然后单击 add:
Adding endpoint and function in Machine Flow
我们需要指定我们想要向机器流 GUI 公开的任何后端功能的预期输出。这确保了机器流知道在运行函数时要显示什么样的结果。
关闭功能覆盖图,并单击工作流程的第一个节点。按回车键添加一个新节点。第一层中的任何节点都是数据节点。这些节点在我们的工作流上开始了一个新的分支。
Choosing raw dataset in Machine Flow
单击“添加数据”会弹出打开模式,并显示一个下拉列表,列出我们添加到数据文件夹的任何数据集。我们可以看到我们添加的波士顿数据集,以及机器流附带的虹膜数据集。
从列表中选择 boston.json 以显示数据集。这就是我们如何将数据加载到 GUI 中来开始一个新的分支。如果我们想添加另一个数据集,我们可以单击根节点并按 enter 键,选择不同的数据集进行操作。
添加任务
在第一层之后添加的任何节点都是任务节点。这就是我们如何通过添加和运行从起始数据节点分支的任务来构建我们的工作流。
点击 boston 节点上的并点击 enter,然后点击 ADD TASK。我们添加到端点的任何功能都将出现在此下拉列表中。因为我们还没有添加任何函数(这里没有列出 list_data ),所以现在我们可以这样做了。
Machine Flow 附带了一些 R 和 Python 中的函数来运行本教程。我们可以想象这些是由我们编写或由我们的数据科学团队公开的端点。
机器流程附带的功能如下:
- 【T4 列表 _ 数据(9191)(数据)
- show_outliers (9191)(图片)( 特征 )
- show_distribution (9191)(图片)(feature)
- show_missing (8181)(图片)
- 规格化 _ 数据(9191)(数据)
- remove_features (9191)(数据)(features)
- split_data (8181)(数据)( 目标 _ 特征,split _ percentage)**
- 运行 _ 线性 _ 回归(8181)(数据)
- 实际 _ vs _ 预测(8181)(图片)
- show_mse (8181)(图片)
- show_r_squared (8181)(图片)
第一个括号中显示了端口。预期输出显示在第二个括号中。如果需要,参数显示在第三个括号中。
让我们用这些来实验波士顿数据集。打开端点并添加每个函数及其预期输出。根据功能检查以确保端点上的端口号正确:
Adding endpoints in Machine Flow
其中一些功能也需要参数*。我们可以通过单击相应功能的参数图标来添加这些参数:*
Adding parameters in Machine Flow
当我们稍后保存我们的工作流时,这些端点、函数和参数被保存,因此我们不需要重新添加它们。
构建工作流
一切就绪,可以开始使用我们的波士顿数据集构建工作流了。点击添加任务并检查下拉列表。您现在应该可以看到我们添加的所有功能。让我们开始探索数据。从下拉菜单中选择 show_outliers :
Choosing a function in Machine Flow
当我们选择一个函数时,我们会看到任何预期的特性的输入字段,以及一个函数调用,它是被调用的后端函数。前两个参数是传递给我们后端函数的 read_path 和 write_path 。这些是由机器流程自动设置的,所以我们可以不去管它们。当我们输入参数时,它会被动态添加到函数调用中。
这个特定的函数接受一个 ALL 参数作为特性参数传递,这在想要发现异常值时非常有用。在输入框中输入 ALL 并点击 RUN :
Looking for outliers in Machine Flow
我们可以看到,一旦后端代码完成,就会返回结果。正如在上面的需求中所讨论的,所产生的视觉效果是原生的 R 视觉效果。我还在模态顶部添加了一个名字并关闭它。任何时候我点击这个节点,任务的结果就会显示出来。
机器流程使用了许多检查来确保不违反数据相关性*😗
- 除非父节点已经运行了任务,否则无法运行任务;
- 一旦创建了第一个层节点的子节点,就不能在其上更改数据集;
- 如果前一个节点是图像,机器流将搜索路径,直到找到最近的数据集。
注:一个分支包含所有共享同一个第一层节点的节点。一条路径*包含所有直接相互连接的节点。*
让我们继续构建工作流。由于异常值对于特征 crim 、 zn 和 b 最为极端,我将从数据集中移除这些特征。让我们选择移除 _ 特征功能。我们可以在后端看到,该函数被编写为接受以逗号分隔的要素名称字符串,用于从数据集中移除要素:
*remove_features <- **function**(read_path, write_path, features) {
res <- read_json(read_path)
rem_vec <- unlist(strsplit(features, **', '**))
res <- res[,!(names(res) %in% rem_vec)]
res_json <- toJSON(res)
write(res_json, file=write_path)
return(**'done'**)
}*
因此,我们知道如何为这个函数调用添加参数:
Removing features in Machine Flow.
我们可以理解为什么我们在添加端点时必须提供预期的输出。第一个示例生成了一个图像,而本示例显示了结果数据集。
我现在将分割数据,训练一个线性回归模型,并显示预测值与实际值的对比图:
Stepping through splitting, building and validating in Machine Flow.
我可以从这个分支中分离出多种类型的验证,因为它指向由模型训练的数据。由于这是回归,我将检查 r 平方值和均方误差:
Spinning off multiple validations in Machine Flow
机器流程的一个主要好处是能够快速尝试许多选项。例如,我开始了一个新分支,其中我将数据归一化,仅删除的b 特征,并选择 0.4 的分割百分比。然后,我像上次一样训练并验证了一个回归模型。
我还创建了一个新的节点*,它位于前一个分支**的中途,在数据被标准化并删除了 b 特征之后,我选择了一个不同的分割百分比 0.5。*
最后,我在原始数据集之外创建了另一个节点,并探索了它的一些分布而不是离群值。
这是我的工作流程的样子:
Building out workflow in Machine Flow
Bring 能够在任何分支的任何点添加新节点,这更符合数据科学工作的进展。
突出显示路径
机器流程的另一个重要特征是能够突出显示通向最终结果的路径*。当工作流变得庞大而复杂时,这很有用。例如,我可能想快速查看哪些步骤导致我的最佳 R 平方值为 0.688。我可以弹出包含该结果的节点,并单击显示路径图标:*
Highlighting a path in Machine Flow
保存工作流
机器流在浏览器中自动保存所有工作。这意味着您可以刷新甚至删除缓存,并且您的工作流仍然可用。当然,我们可能希望创建多个工作流,所以单击 SAVE 按钮将下载一个 JSON 文件,其中包含所有节点、端点、函数和参数,以及附加到每个节点的读写路径。
加载已保存的工作流就像点击 LOAD 并上传 JSON 文件一样简单。
注意:您可以与团队中的其他成员共享工作流文件*,只要他们的 GUI 可以访问相同的数据文件夹。*
预检方式
如上所述,服务模式是我们努力的目标;一个容器化的运行时,为一群数据科学家提供通用的机器学习任务。这是一种“内部产品化”,我们创建一个应用程序在我们的组织内部使用。
我们可以想象数据团队积极地向服务添加新任务。构建定制 GUI 服务的好处是服务对于组织来说是独一无二的。没有任何供应商工具能够捕捉到适合您组织的产品需求的细致入微的建模和验证。建立一个内部服务,能够快速评估新的数据集和可行的管道,为我们提供富有成效的产品发现。
Service Mode in Machine Flow. Service mode is a containerized runtime of commonly-encountered machine learning tasks, allowing teams to rapidly assess new datasets and discover viable pipelines.
访问容器外部的数据
机器流程使用 Docker 卷来保存数据。正如 Docker 文档,中所述,卷是保存 Docker 容器生成和使用的数据的首选机制。卷的一个伟大之处在于,它们允许数据在主机和 Docker 容器之间进行实时通信。这很重要,因为我们应该能够在不重启服务的情况下添加新数据集。
Machine Flow 使用 Docker Compose 在一次调用中启动 R 和 Python 服务。Docker Compose 是一个定义和运行多容器 Docker 应用程序的工具。
如果我们在机器流程中查看 docker-compose.yml ,我们可以看到卷安装在哪里。我们需要将 volumes 下的路径设置为我们机器的根目录:
***version: '3'
services:
r_ml:** *# builds R_ML Dockerfile* **build:** ./r_ml
**ports:** - **"9191:9191"
volumes:** - /path_to_directory/data:/app/data
**python_ml:** *# builds Python_ML Dockerfile* **build:** ./python_ml
**ports:** - **"8181:8181"
volumes:** - /path_to_directory/data:/app/data*
挂载卷所需的完整路径是我们在控制台中键入 pwd 时看到的路径(在 machine_flow 目录中)。
例如,在我的本地机器上,我在卷下的 :/app/data 前添加了以下行:
***volumes:** -/Users/seanmcclure/PycharmProjects/machine_flow/data:/app/data*
确保对 yml 文件的 r_ml 和 python_ml 部分都执行此操作。
添加库
尽管开发模式可以根据需要通过简单地导入库来使用它们,但是容器化的服务必须将这些库放入 Dockerfile 中。
为 R 服务添加库:
*FROM r-base
# install R packages
RUN R -e "**install.packages(c('dplyr', 'tidyr')**, repos = 'http://cran.us.r-project.org', dependencies=TRUE)"
# install node
RUN apt-get update
RUN apt-get install -y nodejs
RUN apt-get install -y npm
ADD . /app
# install node dependencies
RUN cd /app; npm install
WORKDIR /app
EXPOSE 9191
CMD node connect.js*
在这种情况下,我们只需在 RUN 命令中列出它们。以上,我正在安装 dyplr 和 tidyr。
为 Python 服务添加库:
*FROM nikolaik/python-nodejs
# install Python modules
**RUN pip3 install -U numpy
RUN pip3 install -U scipy
RUN pip3 install -U scikit-learn**
ADD . /app
WORKDIR /app
EXPOSE 8181
CMD node connect.js*
对于我们的 Python 服务,我们使用 pip3 install 安装库。注意用于绕过提示的-U 标志。
众所周知,库安装并不总是按计划进行。记住,机器流有 R 和 Python 的日志文件。要查看容器中的日志文件,只需“执行”容器。例如,进入 r_ml 容器我们可以运行:
*sudo docker exec -i -t machine_flow_r_ml_1 /bin/bash*
…并且查看 R 的 r_log.log 文件,Python 的 py_log.log ,数据目录内的:
*vi data/r_log.log*
更改读取、写入和结果路径
在服务模式下,我们需要更改机器流程使用的路径。首先,点击顶部的齿轮图标:
并将路径更改为:
同样,打开中的 connect.js 两个* r_ml 和 python_ml 目录,更改:*
*fs.appendFileSync(**"../data/r_log.log"**, data)*
到
*fs.appendFileSync(**"data/r_log.log"**, data)*
构建图像和运行服务
我们通过运行以下命令,使用 docker-compose 构建r _ ml 和 python_ml 映像:
*docker-compose build*
然后运行我们的容器,通过运行:
*docker-compose up*
您应该看到以下内容,以确认服务已经启动并正在运行:
*Attaching to machine_flow_r_ml_1, machine_flow_python_ml_1r_ml_1 | R Machine Learning on port: 9191python_ml_1 | Python Machine Learning on port: 8181*
注意:如果你和开发模式在同一台机器上,在运行 docker-compose up 之前,一定要停止在端口 9191 和 8181 上运行的节点服务。
摘要
无论你是想从机器流程开始,还是想建立自己的 GUI 服务,我认为数据科学朝着这个方向发展是很重要的。通过自动化产品发现之前的必要探索,将机器学习工作流程 GUI 化形成了一个完整的开发职责。机器学习没有选项,它有可能性。通过快速评估我们数据的就绪性,并揭示从原始数据到验证模型的可行路径,我们为自己在机器学习项目上取得更多成功做好了准备。
和往常一样,如果你遇到问题,请在评论区提问。
如果你喜欢这篇文章,你可能也会喜欢:
* [## 学习建立机器学习服务,原型真实的应用程序,并部署您的工作…
在这篇文章中,我将向读者展示如何将他们的机器学习模型公开为 RESTful web 服务,原型真实…
towardsdatascience.com](/learn-to-build-machine-learning-services-prototype-real-applications-and-deploy-your-work-to-aa97b2b09e0c) [## 用 D3.js 从玩具视觉过渡到真实应用
我们经常孤立地学习技术和方法,与数据科学的真正目标脱节;至…
towardsdatascience.com](/combining-d3-with-kedion-graduating-from-toy-visuals-to-real-applications-92bf7c3cc713) [## 创建 R 和 Python 库的分步指南(在 JupyterLab 中)
r 和 Python 是当今机器学习语言的支柱。r 提供了强大的统计数据和快速…
towardsdatascience.com](/step-by-step-guide-to-creating-r-and-python-libraries-e81bbea87911)*