发现最大流-最小割定理:一种全面而正式的方法
探索流网络领域和最大流-最小割定理
·
关注 发表在 Towards Data Science ·22 分钟阅读·2023 年 8 月 24 日
–
图片由 israel palacio 提供,来源于 Unsplash
引言
在网络流优化领域,Maxflow Mincut 定理作为一个显著的数学里程碑脱颖而出。它的优雅之处在于解决关于流体或资源在由节点和边互联的网络中流动的复杂优化问题。它的应用涵盖了从交通网络到通信基础设施的广泛系统,在这些系统中,高效的流动管理至关重要。通过理解这个定理及其数学表达背后的基本概念,你可以解开最大化资源利用和在各种实际场景中达到最佳性能的谜团。
在本文中,我们旨在简化并使定理对所有读者易于接近。我们将引导你了解其历史发展,概述其从早期公式化的根源,这将使我们能够欣赏到为这一定理及其整个数学研究领域铺平道路的杰出思想者的贡献。此外,我们将深入探讨 Maxflow Mincut 定理的实际应用。从设计高效的交通系统到处理图像处理任务,其多样性似乎无穷无尽。通过探索其实际影响,你将见证定理在各种领域和行业中的深远影响。
最终,目标是为你提供一个既简洁又正式的全面解释。不需要高级数学的先验知识,虽然一些图论和离散数学*(逻辑和集合论)*的知识会有很大帮助;你只需要一颗好奇的心和解开这个杰出定理实用性的愿望。
历史
Maxflow Mincut 定理首次由 Ford 和 Fulkerson 在 1956 年的开创性论文“Maximal flow through a network”中提出,并与其他相关数学家,如 Claude Shannon,即信息论的发展者合作。该定理指出,网络中的最大流量等于一个割的最小容量,其中割是将网络节点分成两个不相交的集合,其容量是穿过割的所有边的容量之和。从那时起,这一定理成为流网络理论的基石。
然而,这个定理的引入伴随着其他关键的科学贡献,例如 Edmonds–Karp、 Ford–Fulkerson 或 Dinic’s 算法,这些算法都用于寻找可以通过源和汇之间的网络传递的最大流量。同样,通过最大流最小割定理,这个值与将汇与源分开的最小割相匹配。此外,我们可以利用算法的内部计算来识别构成最小割的边集,正如我们将进一步探讨的那样。
流网络
因此,为了简化后续定理的解释,我们首先将了解图论中流网络的基本原理和不可错过的概念。
示例流网络(作者提供的图像)
如上例所示,流网络是一个加权的、有向的多重图,用于表示一个网络结构的对象或系统,其中一定量的资源,以所谓的“流量”来衡量,需要从一个或多个点 “源”(表示为 S 节点) 传输或移动到一个或多个其他节点,称为 “汇” (T 节点)。尽管这个特定示例没有显示多重图的特性,因为两个节点之间只有一条边。
为了实现这样的模板表示,流网络的每条边都有权重。在这种情况下,加权边建模了多个资源*(流量)*交换点之间的物理/逻辑连接,其中实际的正值权重代表其 容量(最大流量支持)。如上所示,容量标记在每条边标签的右侧,以及通过的当前流量,这里为 0。
除了每条边的容量外,定义资源每单位时间穿越每条边的速率的关键指标是 流量。你可以把它想象成道路上的交通或管道中的水量。因此,由源节点或 超源 节点生成(如果所有网络的源节点连接到主源流量生成器),并传递到汇节点或 超汇(如果汇节点上有类似构造),我们可以将流量定义为一个函数 f:E→R ,它接受属于图边集 E 的边 (u,v) 并输出其当前流量 f(u,v),这是一个实际的正值。
作者提供的图像
因此,如果我们计算流网络中所有对应的源S或汇T的上述表达式,我们可以得到通过图的总流量。
为了保证流量符合网络的约束,它必须满足两个基本属性:
-
容量约束:通过任何边的流量不能超过其容量。正式地,如果边的容量表示为“c(u, v)”,而通过该边的流量为“f(u, v)”,那么它必须满足条件0 ≤ f(u, v) ≤ c(u, v),适用于网络中的所有边**(u, v)**。简单来说,我们不能通过一条边推动超过其容量所设定的流量。
-
流量守恒:在每个节点*(不包括源节点和汇节点)*,进入节点的总流量必须等于流出的总流量。
作者图片
这确保了流量持续流动,不会在网络内积累或消散,尽管你可以在系统需要时允许流量积累。在数学上,对于每个节点“u”及其邻接节点,由超节点“v 和 w”表示和聚类,流量守恒属性表示为:
流量守恒属性(作者图片)
最后,请注意流量可以相互抵消,因为如果流量f1(u,v) 和 f2(v,u) 在两个节点u和v之间共存,那么减少f1(u,v) 相当于增加f2(v,u),因为它们具有相反的方向。
剩余网络和增广路径
在这里,我们将引入两个新的、更复杂的概念,这些概念在使用之前提到的算法找到最大流量时将非常有帮助。
这些中的第一个是边的容量和在给定时间的流量之间的差异,称为剩余容量,并表示为:
作者图片
牢记这个属性,我们可以定义一种特殊类型的流网络,称为剩余网络,它与标准网络的唯一区别在于其边的重新定义容量。剩余网络具有上述定义的函数cf,该函数将边的集合及其相应的容量和流量映射到相应的剩余容量。
流量-剩余网络示例(作者图片)
在这个示例中,你有一个网络,其中上图中所有边都有特定的流量函数。因此,剩余网络是下图,其边标签包含可以根据相应边的方向发送的剩余容量,以及在撤销流量增广操作时可以交付的反向流量的数量(记住流量抵消属性,这在网络具有某些对称性时可能有用)。
在这里,通过网络实现的流量可以用之前看到的源或汇公式计算,在这种情况下,是4+3个单位从S发出,或4+1+2个单位到达T。然而,如果我们考虑边*(v5, v1)的反向方向(或双向),有可能沿着路径S-V1-V5-V4-V3-T*发送 2 个更多的流量单位,这将增加总流量,并成为给定网络中最大的可用流量。随后,在得出残余网络后,可能在一个或多个连接源与汇的路径中,所有边的残余容量都大于 0。换句话说,有路径可以从源传输流量到汇,在有多个源或汇的情况下。
增广路径示意图 (作者提供的图片)
在这个背景下,这些路径是解决流量最大化或成本最小化问题的算法的基础,被称为增广路径。要理解为什么,在上面的网络中,我们可以看到建立的流量导致了一个增广路径的存在,其中 2 个单位的流量可以从S传输到T。因此,网络上的实际流量函数并未提供通过它的最大可运输流量,这也是我们稍后将讨论的最大流最小割定理面临的问题之一。
作者提供的图片
因此,如果我们增加通过显示路径的流量,我们将能够确保最终的流量达到最大值 9 个单位,因为不会有其他路径来增加网络流量。最后,在引入定理之前,重要的是要记住,要找到网络的最大流量,诸如Ford-Fulkerson这样的算法使用一种直观的过程,贪心算法从一个完全没有流量的残余网络开始,并通过这些路径来增加流量,(通过残余边或相反方向的流量的帮助)。因此,一旦没有更多的路径可以发现来增加流量,就可以确保流量达到了最大值,即由于一些边的容量不足或网络中甚至没有边,从S到T没有更快的方法来移动资源。
另一种思考这种过程的方法是考虑每次迭代中增加的流量。对于任意的增广路径,你可以增加的最大流量由最小剩余容量的边决定,因为它构成了从S流出的瓶颈。由于它能够限制沿增广路径的所有潜在流量,这种瓶颈边在需要最大化流量的情况下至关重要。
瓶颈边 (作者图片)
例如,考虑上面简单的流网络*(剩余的),只有一条增广路径可用,我们可以清楚地识别出边**(v2, v3),*** 的瓶颈组件,它将整个路径*(以及在这个情况下的网络)*的最大流量设置为 3。按照路径的建议将流量增加 3 个单位后,没有增广路径可以进一步增加流量,因此最大流量被认为已达到。然而,验证结果流量的另一种方法是关注网络中的瓶颈;如果每条 S 和 T 之间的路径都有一个为零的瓶颈值,即其最大剩余容量为 0,这等同于没有增广路径,则不能再添加更多流量,当前流量将被认为是最大流量。
为了解决瓶颈问题;我们应该强调,最大流量也可以表示为所有增广路径瓶颈的总和,这些瓶颈用于通过类似于 Ford-Fulkerson 的算法找到最大流量,因为每条路径的流量增加量由其对应的瓶颈决定。
流网络切割
我们将讨论的流网络基础的最后一部分是切割,它是最大流最小割定理的一个基本组成部分,并且是理解前面章节的一个关键概念,比如瓶颈与流网络分区之间的关系。
首先,我们从它的定义开始;一个切割是将网络节点分成两个集合,其中源节点 S 在集合A中,汇节点 T 在另一个与之不相交的集合B中。
作者图片
集合A和B都不能是空的,因为它们必须分别包含源节点和汇节点。因此,如果网络是连通的,则会有边在 A 和 B 之间双向连接这些节点。此外,这些边包含在另一个名为切割集的集合中,但只有那些起点在 A 中的节点并且终点在 B 中的边被考虑,即能在正确方向上运输流量的边。
作者图片
例如,上面可以看到我们可以应用于图的最简单的切割, resulting in a partition of the vertex set V as the union of the sets A={S} and B={V1,V2,V3,V4,V5,T}。由于唯一的网络源保持在一个集合中隔离,我们可以更准确地理解切割的概念及其后续属性。通常,切割被表示为一条线,旨在包围两个集合中的一个 A 或 B,不分区别。此外,分界线穿越了多个属于cut-set的边,这些边用于确定切割的流量和容量,它们在建立网络的任意切割和流量之间的关系时是至关重要的。这对于证明本文中提出的定理至关重要。
一方面,通过任何给定切割的流量定义为所有承载 A 到 B 方向的流量的边的总和,减去从 B 到 A 方向的边的流量。
图片来自作者
然而,流量在这种情况下并没有显著的价值,因为它受到切割容量的限制。因此,我们也可以以类似的方式定义切割的容量,区别在于这里只考虑上述公式的第一项,即能从 S 到 T 传递流量的边的容量,而不需要减去其他边的值。
图片来自作者
一旦我们正式介绍了流量和切割容量的概念,就有必要考虑一些示例,以尽可能简化这些概念并理解它们在这一领域的原理。
图片来自作者
首先,让我们检查每个集合的顶点,留下A={S,V5,V4}和B={V1,V2,V3,T}。由于网络已经分配了流量,因此切割流量不会为零,将由连接集合 A 到 B 的边上的流量总和决定。
图片来自作者
此外,它的容量来自相同的边及其各自的容量,构成了可能通过切割的最大流量。
图片来自作者
**(A={S,V2}, B={V1,V5,V3,V4,T})**切割 (图片来自作者)
在这个有趣的最后示例切割中,我们可以观察到切割不一定是一个分裂,其中两个集合的顶点组成了连接的组件,也就是说,只要满足切割的基本约束,每个集合可以包含任何节点。
图片来自作者
此外,这个例子特别有助于理解切割与流量之间的关系,为解决定理提供了坚实的基础。首先,请注意根据切割定义,结果网络*(切割后)*在 s-t 方面是断开的。这意味着这样的切割的容量是从源头汇总到汇点的所有边的总和。在隔离单一流源的最基本情况下,切割的容量将超过或等于网络的最大流量。然而,在之前的示例中,可以看到通过插入更多具有外发边的节点,切割的容量不可避免地增加,因为有更多的边而不是严格所需的以达到最大流量,即源头的边决定了在瓶颈情况下随后的网络流量。
定理陈述
我们在解决流网络优化问题时的主要目标是确定从源头到汇点的最大可达流量。这必须在遵守容量限制、流量守恒的前提下完成,并确保达到的流量实际上是最大值。因此,我们在处理定理时的步骤将是用一个可以大致类似于流量的上界来限制该值,从而确认其正确性。
首先,需要强调的是,这样的上界实际上是一个切割,满足具有最小容量的特性。作为定理的主要引理,它可能并不完全清楚,所以让我们引入并证明两个更简单的概念;
作者图片
第一个步骤涉及证明任何给定的切割处的流量与整个网络流量之间的上述等式,这反过来又与源生成的流量相匹配。为此,我们可以假设初始命题为真,同时对任何切割的集合 A 应用归纳法,其中 A={S}作为基本情况,然后使用前面提到的流量守恒原则,针对不同于 S 或 T 的节点。但由于这会比较复杂,我们将选择一种更简单但非常相似的方法。
请注意,在证明过程中,之前的流量值可以是任何允许的值。
1- 流量定义: 在初始步骤中,我们从网络中任何给定流函数f的总流量值开始,并定义其可能的定义之一。在这里,以源节点 S 为参考,即任何网络切割的最小可能集合,我们将流量值与由 S 生成的流量相匹配,减去流入 S 的流量,因为有时可能会有一定量的流量返回到 S。
作者图片
2- 流量守恒属性: 在考虑网络流量为源点 S 产生的总流量后,我们应用流量守恒原理,即除了 s-t 外,所有节点必须传播它们接收到的所有流量,从而使流量 |f| 通过减去流出流量与流入流量的差值来贡献为零。现在,如果我们考虑任何切割 (A,B),节点 v 在集合 A 中除产生流量的节点 {S} 外,总流量将为零,满足我们之前的等式。
图片由作者提供
3- 流量通过切割: 最后,我们得到一个表达式,其中在第二项中将集合 A 中除 S 外的所有节点的流出流量相加,并将 S 自身的流出流量在第一项中,减去所有之前节点的对应流入部分。这对应于前面提到的切割流量定义,因此我们可以得出结论,网络中的所有现有流量必然与任何给定切割的流量匹配。
图片由作者提供
我们将证明的关于最大流最小割定理的第二个命题包括一个不等式,该不等式为网络中任何流量的值提供了一个上界,限制为任何给定切割的容量值。
图片由作者提供
1- 替代流量定义: 利用关于任何切割的流量的先前结果,我们可以将任意流量 |f| 等同于通过任意切割 (A,B) 的流量。
2/3- 流量边界: 在第二步中,我们建立了一个不包含模拟集合 A 中流入流量的第二项的不等式,只保留从 A 到 B 的边的流出流量。移除这样的项后,结果将始终大于或等于之前的结果,因为如果没有边从 B 返回到 A,则剩余边从 A 到 B 的流量总和不会减少。
然后,我们可以通过设置从 A 流出的边的流量小于或等于这些边的容量来简单地增加不等式的值。这一不等式的有效性由所有网络边上出现的容量约束所给出。
4- 弱对偶性: 在将集合 A 的所有流出边的容量总和与由于其定义的切割容量匹配后,可以得出结论,对于网络中的任何给定流量和切割,流量将始终小于或等于切割容量,这也成为我们即将证明的定理的起点。此外,如果我们试图最大化流量,我们将达到一个点,这可以通过最小化切割容量来满足,建立了一种弱对偶关系,其中没有确定性保证最小容量切割总是等于最大流量。
在此阶段,在达成最大流最小割定理之前的弱对偶性之后,我们可以提供一个更易于理解和验证的声明。
作者提供的图像
如前所述,该定理通过 强对偶性 得以成立,即任何网络中的最大流量与可达的最小容量割匹配。与前述的弱对偶结果不同,该定理确保流量的 最大化 对偶与任何割容量的 最小化 完全相等,消除了两者结果之间存在差异的可能性,并且在引理上提供了强对偶条件。
(A={S,V1}, B={V2,V5,V3,V4,T}) 割 (作者提供的图像)
在进行演示之前,我们应该强调定理的一个使用案例。在这里,最大流的值为 7,等于每个外流割边的容量之和。请注意,这些边承载了最大容量的流量,在像所示的最小容量割中,这些边成为瓶颈,即割集本身作为全球网络流的瓶颈。为了简化对这个想法的解释,您可以在下面找到一个资源以帮助您理解:
我即将阅读最大流最小割定理的证明,该定理有助于解决最大网络流问题。可以…
证明
如果我们想证明最大网络流在所有情况下等于网络中的最小容量割,我们将使用 3 个必须等价的命题,以确保定理的正确性。
存在一个割 (A, B) 满足 |f|= cap(A, B)。
流量值 |f| 是最大值。
流网络中不存在增广路径。
为了显示所有陈述是等价的,我们将演示逻辑推导 1⇒2⇒3⇒1。意思是我们可以从任何陈述推导出其他任何陈述。在 1⇒2 的情况下,可以使用之前展示的弱对偶性轻松验证。然后,考虑到任何流量都小于最小容量割,如果我们假设存在一个等于任意割容量的流(1),弱对偶性告诉我们,这个容量是任何给定流量的上界,因此结果流量与该上界相符,是最大流量(2)。
进行 2⇒3 的验证,最简单的方法是取对立命题 ¬3⇒¬2。然后,举例一个任意流 |f|,如果存在一个可以输送流量的增广路径 s-t ¬(3),则可以通过相应路径增加 |f|,这意味着 |f| 原本不是最大流量 ¬(2)。
最具挑战性的步骤是 3⇒1。首先,我们假设网络中没有增广路径的流 |f|。此外,我们定义一个集合 A,包含在残余网络中从 S 可达的所有顶点。也就是说,A 包含所有在残余网络中存在从 S 到达路径的顶点,同时该路径的所有残余边都不为零。通过这些定义,我们可以确定 S 在 A 中,因为它是自我可达的,而由于没有增广路径,T 在残余网络中从 S 无法到达,因此我们知道至少有一个节点 (T) 不在集合 A 中。然后,如果我们将 T 插入到一个不同的集合 B 中,那么 (A, B) 这一对满足作为网络中有效割集的所有标准。
示例 (A={S,V1,V4}, B={T,V2,V3}) 割 (图片由作者提供)
在这一点上,我们必须意识到关于割 (A, B) 的两件事。一方面,S-T 方向上的割流必须等于其容量。因为根据之前的定义和假设(3),它们不相等的唯一可能性在于 B 的节点的可达性,因此如果它们中的任何一个从 S 在残余网络中可达,导致割边的流量未达到其最大容量,则该节点必须在 A 中而不是 B 中,这就产生了矛盾。另一方面,割的另一个方向的流量因同样的原因为零,即如果它不是零,那么在残余网络中就会有一条 A-B 方向的边 (残余边流量以负号表示) 到达 B 节点,造成矛盾。
图片由作者提供
最后,剩下的唯一任务是将网络流与之前演示的割流匹配,去掉流量中的割流项(因为它为零),并使用割容量定义来得出流 |f| 等于得到的割容量(3⇒1)。
应用
最大流最小割定理在各个领域有着广泛的应用。然而,为了简洁起见,我们将简要提及一些关键方面,并提供更多详细资源,以帮助你正确理解这些应用。
Ford-Fulkerson/Edmonds–Karp 算法
作为第一个后果,定理提供的发现和结果,加上其他定理如整性定理,导出了并支持了一系列旨在计算最大流的算法的正确性证明。
其中最重要的,也是我们已经讨论过的,是Ford Fulkerson’s算法,这是一种贪婪方法,通过寻找 s-t 增广路径来增加流量。然而,算法的最基本版本在某些特定输入下(例如处理实际或无理数及其表示)没有终止或收敛到最大流的保证,这影响了它的时间复杂度,时间复杂度为O(|E| |f|),这意味着在最坏的情况下,算法需要遍历网络中所有边缘,以达到最大流中的每个*(至少一个)*流量单位。
然后,为了改进之前的版本,即最早创建的解决此类问题的版本,改进了计算增广路径的方法。这样,虽然Ford-Fulkerson版本使用深度优先搜索(DFS),计算随机路径到 T,而改进后的Edmonds-Karp变体使用广度优先搜索(BFS)算法来找到增广路径。因此,旨在每次迭代中选择边数最少的增广路径,这个算法相较于之前的版本具有终止保证,并且时间复杂度改变为O(V E²)。
然而,使用这些及类似的算法,不仅可以计算网络中的最大流,还可以计算其容量等于值的最小割。过程非常简单;在计算网络中所有边的最大流之后,根据最大流最小割定理,从 S 出发可以访问的节点在相应的剩余网络中形成我们寻找的割的集合 A,剩余的节点形成 B,从而得到结果最小容量割***(A, B)***。
最后,需要指出的是,最大流算法的研究领域远大于此处所展示的内容。因此,如果你希望继续学习,这里有一个资源详细介绍了这些算法及其实现。
实际应用案例
我们生活中几乎所有的系统都有可能被建模为流网络(至少部分),这使它们成为解决复杂可扩展性问题的重要工具。由于可能性很广,这里仅提及一些与基本概念直接相关的应用。
最初,所有的交通系统,从道路网络和公共交通系统到航空路线和货物分配,都可以表示为流网络。因此,我们可以分析交通模式、优化路线,并提高整体效率。这在城市规划中尤为重要,因为管理人员、车辆和货物的流动对于防止拥堵和确保顺畅运营至关重要。此外,并非所有这些用例都是完全有益的;例如,流网络也可以模拟一个国家的铁路系统,在军事冲突中可能成为攻击的目标,这些攻击应尽可能具备战略最优化。你可以在这个资源中了解更多关于这一特定应用的信息。
尽管在电信、能源分配甚至医疗保健中有其他超越的实现,我们将重点关注一个与计算机科学更密切相关的领域,特别是计算机视觉,该领域已取得了显著突破。在图像处理领域,流网络的主要应用依赖于图像分割算法,该算法负责将图像划分为对应于对象、主题或特定区域的区域,这些区域可能无法通过肉眼区分。在这种情况下,流网络通过将像素之间的关系建模为网络而展示其优势,其中边表示邻近像素之间相似性/差异性的可能值流动。此外,还值得提及在类似范围内的应用,如机器学习模型,其中流的概念用于优化特定的学习、生成或分类任务。
结论
本文涵盖了流网络数学领域的一小部分,并证明和简化了其中一个基本定理。然而,由于这是一个具有广泛应用的主题,特别是在世界消费、交通和人口管理系统中,因此继续扩展理论并深入了解这些应用是有益的。为此,观察定理更高级形式化以及逐步理解本文提到的算法和学习有关流网络某些应用的新概念的最有效资源是以下内容:
www.cs.upc.edu/~mjserna/docencia/grauA/P20/MaxFlow-fib.pdf
ocw.tudelft.nl/wp-content/uploads/Algoritmiek_Introduction_to_Network_Flow.pdf
在强化学习中离散化连续特征
原文:
towardsdatascience.com/discretizing-the-continues-features-in-reinforcement-learning-b69b388215ea
如何使用平铺编码和 Python 将无限变量转换为离散空间
·发表于 Towards Data Science ·阅读时间 6 分钟·2023 年 3 月 13 日
–
图片由 Ehud Neuhaus 提供,来源于 Unsplash
本文是强化学习系列的延续。要回顾,请访问以下文章:
Python 的原始实现,展示如何在强化学习的基本世界之一中找到最佳位置…
towardsdatascience.com ## 使用 Python 的时间差 — 第一个基于样本的强化学习算法
使用 Python 编码并理解 TD(0) 算法
towardsdatascience.com ## 使用 Q-learning 的强化学习中行动的价值
从零开始在 Python 中实现 Q-learning 算法
towardsdatascience.com
关于 Q 学习的最后一篇文章探讨了将数字分配给状态动作对的概念:
Q 值函数
使用的状态是可以列出并写入表格的状态。例如,我们对代理可能处于的迷宫中的所有可用位置进行了索引。即使在一个巨大的迷宫(想象一个百万乘百万的网格)中,我们仍然可以为每个状态分配一个唯一的索引,并在填写 Q 表时直接使用这些状态。
实践中,我们的代理所处的状态通常不能被唯一索引并适配到表格中。例如,假设状态是轮子的角度,该角度可以精确地旋转一次,并且可以取**[-360, 360]度范围内的任何值。轮子可以精确地转到12.155…、152.1568…**等度数。我们不能索引所有独特的度数并创建一个表——可能性是无限的。
然而,我们仍然希望使用强化学习(RL)提供的所有算法。因此,第一步是从具有无限可能性的特征中创建一个离散特征空间。
离散化连续特征空间的流行技术之一是所谓的瓷砖编码算法。
瓷砖编码的定义如下¹:
瓷砖编码是一种通过将状态空间划分为若干重叠区域(称为瓷砖),然后通过状态所落入的瓷砖集合来表示连续状态空间的方法。
我们可以用以下代码和图表示一个简单的 1 特征离散化:
# Creating an example 1D feature that goes from 0 to 1
x = np.linspace(0, 1, 100)
# Defining the number of tilings
n_tilings = 4
# Defining the offset
offset = 0.05
# Defining the number of tiles in a tiling
n_tiles = 10
# Creating a list of tilings
tilings = []
cur_tiling = 0
for i in range(n_tilings):
# Creating a tiling by adding the offset to the feature
tiling = x + cur_tiling * offset
# Appending the tiling to the list
tilings.append(tiling)
# Incrementing the tiling
cur_tiling += 1
# Ploting the x feature and the tilings
# The x feature is plotted a horizontal line
# The tilings are plotted as horizontal lines, each moved up by 0.1
vertical_offset = 0.1
plt.figure(figsize=(10, 5))
plt.plot(x, np.zeros_like(x), color='black')
for i, tiling in enumerate(tilings):
plt.plot(tiling, np.zeros_like(x) + vertical_offset + vertical_offset * i, color='red')
# Adding vertical ticks on the tiling lines
for j in range(n_tiles):
plt.plot(
[j / n_tiles + offset * i, j / n_tiles + offset * i],
[vertical_offset + vertical_offset * i - 0.01, vertical_offset + vertical_offset * i + 0.01],
color='black'
)
plt.xlabel('Feature values')
plt.ylabel('Tilings')
# Drawing a vertical line at x = 0.46
plt.plot([0.46, 0.46], [0, vertical_offset * n_tilings + 0.1], color='blue', linestyle='dashed')
plt.show()
x=0.46 的瓷砖编码实际操作;图由作者提供
为了理解瓷砖编码,我们需要完美地理解上述图中的内容。
最底部的水平线是特征x,它可以在[0, 1]范围内取得任何值。
每条红线是用于离散化特征x的瓷砖。
每个瓷砖被划分为瓷砖,这些瓷砖间隔均匀。
蓝色虚线是从 x 范围中取出的随机值。问题是,我们如何使用 4 个瓷砖和 8 个瓷砖来从 x 特征值创建一个离散状态?
算法如下:
给定来自连续 x 特征的值 s:
对于每个瓷砖:
-
初始化一个大小等于瓷砖数量的向量。将其填充为 0。
-
计算s 值落在哪个瓷砖中。保存该索引i。
-
用值 1 填充向量坐标i。
最后,将所有向量堆叠成一个向量。
让我们计算图中展示的示例。对于第一个瓷砖,在特征空间 x 的正上方,蓝色值落在第 5 个瓷砖空间中。因此,第一个瓷砖的特征向量是:
[0, 0, 0, 0, 1, 0, 0, 0]
对于第二个瓷砖,我们重复相同的过程,并得到如下向量:
[0, 0, 0, 0, 1, 0, 0, 0]
第三和第四个瓷砖向量:
[0, 0, 0, 1, 0, 0, 0, 0]
[0, 0, 0, 1, 0, 0, 0, 0]
表示蓝色虚线“状态”的最终离散向量是
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]
让我们再做一个例子,x 值为 0.44,以完全理解这个过程。
x=0.44;作者图表
每个平铺向量(从底部开始):
[0, 0, 0, 0, 1, 0, 0, 0]
[0, 0, 0, 1, 0, 0, 0, 0]
[0, 0, 0, 1, 0, 0, 0, 0]
[0, 0, 1, 0, 0, 0, 0, 0]
最终状态向量:
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]
最终状态向量的长度为 N 平铺 * N 瓦片。
将向量分配给由 2 个特征表示的状态的过程遵循一个非常类似的算法。现在的平铺不再是水平线,而是矩形。
让我们假设我们的状态由连续的 x 和 y 变量组成,每个变量的范围从 0 到 1。
我们将用 2 个平铺将整个特征空间划分,每个平铺包含 4 个瓦片:
二维连续空间;作者图表
上图中的灰色区域表示原始特征空间。每个红色平铺被划分为 4 个瓦片。我们想为蓝点(0.44,0.44)创建一个表示状态的向量。
算法与 1D 情况相同,但现在我们为点分配索引,按从左到右的顺序,从左上角开始:
索引平铺;作者图表
因此,对于第一个和第二个平铺,蓝点将落入第 3 个平铺,结果状态向量如下:
[0, 0, 1, 0]
[0, 0, 1, 0]
最终向量将是:
[0, 0, 1, 0, 0, 0, 1, 0]
取另一个点:
另一个二维点;作者图表
向量将是:
[1, 0, 0, 0]
[0, 0, 1, 0]
最终向量为:
[0, 0, 1, 0, 0, 0, 1, 0]
创建表示 3D 及以上维度状态的向量的直觉与上述示例相同。
在 Python 中可以找到一个强大的实现:
RLAI 打开网页在这里我们描述了实现平铺编码核心部分的软件,如…
在本文中,我展示了如何从连续特征空间到有限向量以表示状态,使用平铺编码。在本 RL 系列的下一篇文章中,我将使用这种编码为每个状态分配动作值。
快乐学习,敬请关注!
[1]
作者: 理查德·萨顿
揭示真实数据离散度的两个指标超越标准差
数据科学
变异系数和分位数离散度系数计算与解释指南
·发表于 Towards Data Science ·8 分钟阅读·2023 年 7 月 30 日
–
图像由作者使用 StockImg.AI 生成
介绍
我们都听过这样一句话:“变化是生活的调味品”,在数据中,这种变化或多样性通常表现为离散度。
数据的离散度使数据变得引人入胜,因为它突出了我们否则无法发现的模式和见解。通常,我们使用以下作为离散度的度量:方差、标准差、范围和四分位距 (IQR)。然而,在某些情况下,我们可能需要超越这些典型的度量来检查数据集的离散度。
在比较数据集时,变异系数 (CV) 和四分位离散度系数 (QCD) 提供了洞察力。
在本教程中,我们将深入探讨 CV 和 QCD 两个概念,并回答它们各自的以下问题:
-
它们是什么,它们是如何定义的?
-
如何计算它们?
-
如何解释结果?
上述所有问题将通过两个示例进行详细解答。
理解变异性和离散度
无论我们是在测量人的身高还是房价,我们很少发现所有数据点都是相同的。我们不会期望每个人都一样。有些人高,有些人中等,有些人矮。数据通常会有所变化。为了研究这种数据变异性或离散度,我们通常使用范围、方差、标准差等度量来量化它。离散度的度量量化了我们数据点的分布情况。
但是,如果我们希望评估不同数据集之间的变异性呢?例如,我们如果要比较一家珠宝店和一家书店的销售价格怎么办?标准差在这里无效,因为这两个数据集的尺度可能非常不同。
在这种情况下,CV 和 QCD 是有用的离散度指标。
深入探讨:变异系数
变异系数 (CV),也被称为相对标准差,是一个标准化的离散度量。它以百分比形式表示,没有单位。因此,CV 是比较不同尺度数据的出色离散度量。
从数学角度来看,CV 计算为标准差与均值的比率,有时乘以 100 得到百分比。公式如下:
让我们使用 Numpy 的 mean
和 std
函数在 Python 中计算 CV。
def calc_cv(data_array) -> float:
"""Calculate coefficient of variation."""
return np.std(data_array) / np.mean(data_array)
现在,让我们看看如何在一个例子中使用这个统计数据。
示例 1
考虑以下两个数据集,展示了珠宝店和书店的月销售额:
-
珠宝店:月平均销售额为 $10,000,标准差为 $2,000。
-
书店:月平均销售额为 $1,000,标准差为 $200。
让我们使用 Numpy 生成两个例子的样本数据。
Jewelry Shop:
- Mean = $10119.616
- Standard Deviation = $2015.764
- CV = 0.199 (dimensionless)
Bookstore:
- Mean = $1016.403
- Standard Deviation = $206.933
- CV = 0.204 (dimensionless)
通过标准差研究珠宝店和书店的月平均销售额的离散度。生成这个图表的代码可以在笔记本中找到。(图像作者)
珠宝店的平均销售额和标准差显著高于书店(平均 $10,119 和标准差 $2,015 对比书店的平均 $1,016 和标准差 $206),然而它们的 CV 相同(20%)。
这意味着,相对于各自的平均销售额,尽管销售量(及其标准差)有很大差异,珠宝店和书店的相对变异性是相同的。
这展示了 CV 作为相对变异度量的概念,并展示了它如何用于比较不同尺度的数据集。
接下来,让我们考虑另一个无量纲的离散度量,即 QCD。
深入了解:四分位离散系数
四分位离散系数 (QCD) 是另一种相对离散度量,特别适用于处理偏态数据或具有离群值的数据。QCD 关注数据集中间 50% 部分的分布,即四分位距 (IQR)。这就是为什么 QCD 是一个稳健的度量。
QCD 的计算方法如下:
其中 Q1 是第一个四分位数(第 25 百分位数),Q3 是第三个四分位数(第 75 百分位数)。
def calc_qcd(data_array) -> float:
"""Calculates Quartile Coefficient Difference"""
q1, q3 = np.percentile(data_array, [25, 75])
return (q3 - q1) / (q3 + q1)
类似于 CV,QCD 是一个无单位的度量,对比较偏态数据集的离散度可能非常有用。
以下示例将更好地展示 CV 和 QCD 背后的概念。
示例 2
考虑两个公司员工年龄的数据集。
-
公司 A(一个初创公司):年轻员工,有些年长员工。
-
公司 B(一个成熟的公司):年长的员工,一些较年轻的员工。
让我们使用 Numpy 为两个示例生成样本数据。
Company A:
- Q1 = 22.840 years
- Q3 = 26.490 years
- IQR = 3.650 years
- QCD = 0.074 (dimensionless)
Company B:
- Q1 = 42.351 years
- Q3 = 47.566 years
- IQR = 5.215 years
- QCD = 0.058 (dimensionless)
现在,让我们绘制数据分布及其箱线图和 QCD,以可视化上述信息。
基于 IQR 的稳健度量,研究公司 A 和公司 B 员工年龄的离散度。生成此图的代码可在 notebook 中找到。(图片由作者提供)
公司 B 的 IQR(5.215 年对比 3.65 年)表明年龄离散度更广。然而,公司 B 的老年员工影响了这一点(查看箱线图)。
另一方面,公司 A 的 QCD(0.074 对比 0.058)比公司 B 更大,显示出相对于其中位数,年龄分布的变异性更大。IQR 并未揭示这一点。
在接下来的部分,我们将学习如何使用 CV 和 QCD 量化这种差异。
讨论
让我们回答一些你可能会有的问题。
为什么不关注标准差或 IQR 这样的度量?
我们使用标准差和 IQR 来量化数据集的离散度。标准差显示数据点与均值之间的平均距离。IQR 显示我们数据中间 50% 的分布情况。
然而,在比较具有不同单位或规模、偏态分布或存在离群值的两个或多个数据集的离散度时,这些度量可能会产生误导。
虽然标准差和 IQR 是有用的统计工具,但我们偶尔需要 CV 和 QCD 来进行公平比较。
CV 和 QCD 都衡量和比较变异性,尽管它们的方式有所不同。你的数据和期望的变异性决定了使用哪一个。
何时使用 CV
CV 是比较具有不同大小、单位或平均值的数据集的变异量的好方法。由于 CV 是一个相对的分散度量,它显示了事物与均值的不同程度。
平均值和标准差这两个受“离群值”影响较大的度量被用来创建 CV。因此,CV 可能会对那些不符合正态分布或具有离群值的数据集提供扭曲的分散视图。因此,CV 最适合用于均匀分布且没有极端值的数据。
在销售案例中,两个组的价格范围差异很大,因此用于测量它们销售的尺度也非常不同。珠宝店的平均销售额可能要高得多,并且变异性也大得多。如果我们使用标准差来衡量这两个组的变异性,可能会得出错误的结论,认为珠宝店的销售变异性更大。
CV 允许我们比较两个数据集之间的销售变异性,而不管它们的不同规模。如果某一类别的 CV 较高,这意味着该类别的销售相对于平均销售而言变异性更大。
何时使用 QCD
QCD 使用数据集四分位数,这些四分位数对异常值的敏感度较低。QCD 是对偏斜分布或包含异常值的数据集的鲁棒离散度测量。QCD 关注数据中心 50%,这可能更好地捕捉这些数据集的离散度。
在我们的例子中,我们检查了两家公司之间的年龄差异:一家以年轻员工为主的初创公司(A)和一家以年长员工为主的成熟公司(B)。鉴于它们的年龄范围不同,年长公司的中位年龄和变异性会更高。使用四分位数范围(IQR)来比较离散度可能不准确地表示成熟公司的年龄变异,因为 IQR 测量的是绝对变异,并且对于较大值更高。
QCD 更有效,因为它以中位数为基准标准化变异性,使我们能够在不同尺度下比较公司之间的年龄变异性。较高的 QCD 表示相对于中位数的年龄变异性更大。因此,选择 QCD 进行比较是因为它考虑了不同的尺度以及潜在的数据偏斜或异常值。
收获
选择 CV 还是 QCD 取决于数据集的性质和分析目标。以下是关于这两种度量的关键点:
变异系数(CV):
-
CV 是通过标准差与均值的比率计算得出的。
-
CV 是无量纲的。
-
较高的 CV 表示相对于均值的变异性更大。
-
如果均值接近零,CV 可能会产生误导性结果(除以零!)。
四分位数离散系数(QCD):
-
QCD 基于四分位数。
-
QCD 是一个鲁棒的测量指标(对极端值不敏感)。
-
QCD 是无量纲的。
-
较高的 QCD 表示相对于中位数的值变异性更高。
-
如果分布的尾部很重要,QCD 无法完全捕捉散布情况。
结论
总结一下,变异系数(CV)和四分位数离散系数(QCD)是检查数值数据离散度的重要统计指标。CV 在比较标准化数据方面表现出色,而 QCD 在偏斜或异常值数据集的情况下有帮助。我们通过两个案例(使用 Python 程序和分析)来观察这些指标在实践中的作用。通过明智地使用它们,我们可能会获得有用的决策信息。
📓 你可以在 GitHub 上找到本文的笔记本。
感谢阅读!📚
更新(2023 年 8 月 13 日): 本文的早期版本中在示例 2(QCD)中包含了两个相同直方图的错误图像。图像已更新以反映正确信息。
我是一个高级数据科学家 📊 和工程师,撰写关于统计学、机器学习、Python 等方面的内容。
-
在 Medium 上关注我 👋 以获取我的最新文章
有用的链接
变异系数(CV)是衡量数据点围绕均值离散程度的一种指标。
www.investopedia.com [## 四分位数离散系数 - 维基百科
来源于维基百科,自由百科全书。在统计学中,四分位数离散系数是一种描述性统计量…
维基百科 [## 变异系数 - 维基百科
在概率论和统计学中,变异系数(COV),也称为标准化均方根…
最初发布于 https://ealizadeh.com。
解剖 Twitter 顶级声音的覆盖范围和影响力
原文:
towardsdatascience.com/dissecting-the-reach-and-impact-of-twitters-top-voices-52262ef58b40
用数据科学绘制 Twitter 影响力的全景
深入探讨塑造 Twitter 最强大声音的关系和模式
·发表于 Towards Data Science ·阅读时间 16 分钟·2023 年 4 月 6 日
–
图片由作者生成,Midjourney 提示:“一幅描绘 2023 年 Twitter 状况的巴洛克风格杰作”
介绍
Twitter 因为一些大型账户的影响力而成为众多争议的中心。前 100 个 Twitter 账户(按关注人数排序)总关注量估计约为 41 亿。我们已经看到 Twitter 的顶级声音如何左右政治观点、影响金融市场、设定趋势,甚至煽动仇恨。作为一名数据科学家,我自然对通过深入分析他们的推文能揭示出什么样的模式感到好奇。
本文的其余部分将探讨我如何通过检查这些账户之间的关系来理解其影响力的本质。我将使用聚类算法和统计分析来揭示集群内部及跨集群的模式。我希望能更深入地了解这些顶级声音所具有的影响力的性质。
前 100 个 Twitter 账户 关注者数量的来源是 Social Blade(2023 年 3 月) 。
免责声明:此分析纯属探索性质,不应被视为定论。作者与提到的账户所有者没有任何关联,提供的见解也未得到他们的认可。
网络应用
我喜欢在项目中分享应用程序,以使读者的体验更加互动和参与。我将聚类和统计分析功能构建成一个网页,读者可以在其中进行实验。你可以调整聚类算法的超参数,并亲自观察这些调整对分析的影响。我建议在进行这些操作之前先阅读相关内容,以便熟悉方法。
💻账户 聚类应用 — 这个应用可以在手机上使用,但在电脑上查看效果最佳。
注意:由于库依赖问题,应用在 Streamlit 服务器上的编译效果与本地略有不同。应用中的聚类标签与博客文章中的显示有轻微不对齐,但聚类本身仍然相同。
数据
我使用了 Twitter API 从每个账户提取了最新的 100 条公开推文。由于 Twitter 数据比较混乱,我在进行分析之前进行了常规的预处理,去除了 URL。
转发被排除在分析之外,因此重点仅在于每个账户的原创推文。这确实使得一些账户的推文数量少于 100 条。由于 Twitter API 的局限性,这一问题较难解决。
请注意,这符合 Twitter 的服务条款。他们允许通过 其 API分析和聚合公开可用的数据。数据允许用于 非商业和商业用途.&text=Your%20product%20or%20service%20is%20monetized%20if%20you%20earn%20money%20from%20it.)。
目录
-
定义影响力:我如何定义和量化影响力。
-
维度减少与聚类:使用 UMAP 和 HDBSCAN 算法揭示数据中的隐藏结构。
-
统计分析:对聚类进行深入的统计分析。
-
观察结果:对聚类和统计分析结果的评论。
-
局限性与扩展:对这种方法的局限性进行简要讨论,并探讨如何进行扩展。
1. 定义影响力
第一步是明确影响力的定义。我将其概念化为两个主要类别,参与度和影响力。参与度估算了账户与其粉丝之间通过推文的互动情况。影响力估算了推文背后的情感和/或观点。
参与度指标
所有参与度指标都经过了调整,依据的是账户在分享推文时的粉丝数量,然后进行归一化处理。
-
收藏次数:推文被收藏的次数。
-
转发次数:推文被转发的次数。
-
引用次数:推文被引用的次数。
-
回复数:推文被回复的次数。
参与度指标
影响指标
影响指标通过对每条推文运行分类模型被细分为情感和情绪分布。之后,计算每个子类别在账户层面的分布。情感的子类别包括:积极、消极和中立。情绪的子类别包括:愤怒、快乐、乐观和悲伤。
用于在数据框中的推文上运行情感/情绪模型的模块。
请注意,模块 TextCleaner 是我创建的,用于从推文中删除 URL。
对每条推文进行情感和情绪分析,并在账户层面创建分布
情感和情绪检测的输出。每个 user_id 指代一个账户。这在聚类之前会与账户层面的指标表进行合并。
作者提供的图片:账户层面的情感和情绪分布
关于大型语言模型的说明
我使用了 Twitter-roBERTa-base 来进行情绪和情感分类。研究人员对约 6000 万条推文进行了模型预训练,并分别对情绪识别和情感分析进行了微调¹。训练语料库由 Twitter 自动标注的推文(英语)组成。研究人员已发布了模型在所有任务上的性能指标。
图片来自 Barbieri (2020)¹
2. 维度降低与聚类
维度降低和聚类的目的是揭示隐藏的结构,因此,基于定义的影响指标来探讨账户之间的关系。我将详细描述我如何做到这一点。
统计所有影响指标后,我们有 12 个维度需要聚类。在这样的高维空间中进行聚类可能会遮蔽模式(参见 维度诅咒),更不用说它是不可能进行可视化的。我通过使用 UMAP 将 12 个维度减少到仅两个来解决这个问题。
使用 UMAP 进行维度降低
在高层次上,UMAP 使用图布局算法将数据从更高维空间降低到较低维空间,同时尽可能保持结构相似性。
你可以将这视为在较低维度中保留高维空间中的‘信息’。
UMAP 不会完美地保留 12 个维度中的所有信息,但通过选择合适的超参数,它保留了足够的信息,给我们提供了数据结构的一些见解。选择合适的 UMAP 参数更像是一种艺术而非科学,我主要是调整参数,直到形成看似连贯的簇,考虑诸如样本量等因素。我将简要说明每个超参数及其对聚类的影响。
这是一个很酷的 资源 可以帮助你更好地理解 UMAP
-
n_neighbours:在构建高维图时,确定每个数据点所考虑的最近邻居的数量。它调整数据的局部和全局结构。较小的值优先考虑局部结构,结果是更详细的聚类,而较大的值则强调全局结构,导致聚类更连通且不那么明显。
-
min_distance:控制低维嵌入中数据点之间的最小距离(由 UMAP 算法构造)。它决定了点的聚集程度。较小的值生成更紧凑的聚类,保留局部结构。较大的值则将聚类扩展开来,使得可视化全局关系更容易,但可能会丢失一些细节。
-
距离度量:UMAP 算法使用距离度量来计算数据点之间的最小距离。由于 12 个维度是连续且归一化的(值范围在 0 到 1 之间),我选择使用欧氏距离。这一选择是合适的,因为欧氏距离测量两点之间的直线距离,有效地捕捉了归一化数据集中的关系。
使用 HDBSCAN 进行聚类
在降低度量空间的维度后,我能够应用聚类和随后的数据可视化。对于聚类,我利用了 HDBSCAN 算法,这是一种基于密度的聚类算法,可以有效地在二维空间中工作。
高层次上,HDBSCAN 根据密度转换数据点之间的空间,构建距离加权图的最小生成树,形成聚类层次结构,然后提取稳定的聚类。我使用 HDBSCAN 是因为它的稳健性和简洁性。只有一个(重要的)超参数需要调整——最小聚类大小。
HDBSCAN 库的创建者提供了详尽的 文档 供你进一步了解算法的内部工作原理。
我编写的生成聚类的模块:
用于执行 UMAP 降维和聚类的模块
运行聚类分析
在账户数据上运行聚类生成了六个稳定的聚类。我将在下一节中使用一些统计分析来研究这些聚类。
作者提供的图像:由聚类分析生成的账户聚类
你可以通过我建立的网页应用自己实验聚类分析。
3. 统计分析
我对各个类别进行了深入分析,以揭示隐藏的关系。所有统计分析都以 0.05 的显著性水平进行评估。
谁在这些类别中?
此时,我认为列出每个类别中的个体账户是有意义的。这应该为随后的分析提供一些背景。
第 0 类:政治类别—— 主要是政治家和世界领导人。令人惊讶的是,艾玛·沃特森和比尔·盖茨被包含在这个类别中……模型是否预测了他们未来职业志向的某些东西?两者都以政治活跃而闻名,因此这可能在他们的推文中有所体现。
5 Barack Obama
6 Joe Biden
48 Bill Gates
72 Emma Watson
87 PMO India
92 Hillary Clinton
94 Amit Shah
95 President Biden
第 1 类:新闻媒体(主要)—— 这主要是新闻媒体,但其中也包括一些名人。其中一些以其争议性的推文而闻名——这可能是为什么模型将他们与新闻媒体进行聚类的原因?
0 CNN Breaking News
1 BBC News (World)
2 CNN
3 Twitter
4 The New York Times
7 Reuters
9 BBC Breaking News
10 The Economist
19 National Geographic
26 Wiz Khalifa
33 Kourtney Kardashian
34 Donald J. Trump
45 Nicki Minaj
46 Elon Musk
62 Conan O'Brien
91 Cardi B
第 2 类:主要是体育组织—— 有趣的是,相当多的与体育相关的账户被包含在这个类别中:我认为除了两个,其他的都在这里。然而,难以将第 2 类仅仅归为体育,因为这里还有大量其他类型的账户。
注:指的是团队和组织,而不是运动员本身。
8 ESPN
11 YouTube
12 PlayStation
13 NASA
15 Real Madrid C.F.
24 NFL
25 NBA
36 SportsCenter
38 Drizzy
39 Justin Bieber
43 SpaceX
56 FC Barcelona
73 BIGHIT MUSIC
77 Adele
79 Whindersson
80 netflixbrasil
82 Miley Cyrus
90 UEFA Champions League
93 BTS_official
第 3 类:流行歌星和脱口秀主持人—— 第 3 类主要是流行歌星,但也包括一些电视名人,如奥普拉·温弗瑞和艾伦·德詹尼丝等。可以将这个类别大致归为娱乐类。这里只有一个组织,Instagram,因此我们可以非正式地将其归类为“明星”类别。
16 Jimmy Fallon
17 Ellen DeGeneres
20 Taylor Swift
21 PRIYANKA
23 Oprah Winfrey
28 Demi Lovato
29 KATY PERRY
30 LeBron James
32 Selena Gomez
37 Justin Timberlake
42 Khloé
47 Shakira
51 Rihanna
57 Bruno Mars
58 Shah Rukh Khan
61 Hrithik Roshan
63 Lil Wayne WEEZY F
69 Kendall
70 Liam
71 Neymar Jr
74 zayn
75 Instagram
78 One Direction
85 Shawn Mendes
第 4 类:运动员—— 第 4 类主要是运动员,主要是足球运动员和板球运动员。除了谷歌、布兰妮·斯皮尔斯和尼尔·帕特里克·哈里斯,这个类别几乎完全一致。
18 Britney Spears 🌹🚀
22 Narendra Modi
27 Google
49 Kaka
50 Virat Kohli
54 Neil Patrick Harris
55 Andrés Iniesta
66 Sachin Tendulkar
67 Amitabh Bachchan
68 Cristiano Ronaldo
86 Arvind Kejriwal
88 Mesut Özil
第 5 类:主要是流行歌星—— 模式似乎是流行歌星,但这里也有相当多的名人和演员。明显的异常值有曼联和英超联赛。类似于第 3 类,你可以将其大致归为娱乐类。
14 Lady Gaga
31 Kevin Hart
35 Kim Kardashian
40 P!nk
41 Akshay Kumar
44 Alicia Keys
52 Louis Tomlinson
53 jlo
59 Deepika Padukone
60 Niall Horan
64 Chris Brown
65 Salman Khan
76 Harry Styles.
81 Kylie Jenner
83 방탄소년단
84 Premier League
89 Manchester United
情感与情绪的卡方分析
类别与情感/情绪分布之间是否存在关联?为回答这个问题,我进行了卡方测试,以查看是否存在统计显著的关联。卡方检验比较了情感/情绪的预期分布(如果它们只是随机分配到各个类别)与实际数据中观察到的情况。我计算了每个类别和情感/情绪组合的标准化残差,以测量每个观察值与预期值的偏差(通过热图显示)。你可以将较高的标准化残差粗略解释为该类别对特定情感或情绪的‘更大’倾向。
卡方统计量:2291.23,p 值小于 0.05
作者提供的图像:按簇分类的情绪标准残差
卡方统计量:1535.78,p 值小于 0.05
作者提供的图像:按簇分类的情感标准残差
参与度指标的统计分析
我想了解参与度指标在各个簇中的分布情况。我通过进行一系列统计测试来实现这一点。
克鲁斯卡尔-沃利斯检验:我对每个参与度指标进行了初步的克鲁斯卡尔-沃利斯检验,以了解各簇之间每个参与度指标的中位值是否存在统计显著差异。
注:克鲁斯卡尔-沃利斯检验假设所有比较分布的形状相同,并且样本是随机且独立的。快速检查箱线图显示大多数分布是长尾的。克鲁斯卡尔-沃利斯检验应用于对数变换的调整后参与度指标。
作者提供的图像:参与度指标分布的形状
克鲁斯卡尔-沃利斯检验的结果表明,各簇之间的参与度指标中位值存在统计显著差异。然而,它并未告诉我们哪些簇之间存在差异。我进行了些后续的统计测试,以确定成对的簇差异。
注:p 值小于 0.05 表示统计显著性
favorites
Kruskal-Wallis H-test statistic: 895.7382389032183
P-value: 2.225650711196283e-191
retweets
Kruskal-Wallis H-test statistic: 767.6074631334371
P-value: 1.1759288846534128e-163
quotes
Kruskal-Wallis H-test statistic: 440.94518399410543
P-value: 4.4087653321001564e-93
replies
Kruskal-Wallis H-test statistic: 595.2647170442586
P-value: 2.1329465806092327e-126
邓恩检验:邓恩检验是一种非参数后续检验,用于在克鲁斯卡尔-沃利斯检验后进行成对的多重比较。它在调整多重比较的同时比较两组之间排名数据的差异。邓恩检验的主要假设包括:
-
独立观察:每组中的观察应该彼此独立。
-
序数或连续数据:数据应该是序数或连续的。
-
组间方差(离散度)的同质性:虽然与 ANOVA 等参数检验相比,邓恩检验对方差同质性的假设较不敏感,但它仍然假设数据的离散度在各组之间相似。如果这一假设被违反,测试结果可能不够可靠。
-
随机抽样:数据应通过从感兴趣的总体中随机抽样获得。
邓恩检验告诉我们簇的中位值是否显著不同。对于 p 值为“NaN”的地方,中位数没有显著差异。为了简洁起见,我仅展示了对调整后收藏的邓恩检验结果。
favorite_count_pf
Significant differences in Dunn's test (adjusted p-values):
0 1 2 3 4 \
0 NaN 8.478780e-39 NaN 1.738409e-10 6.224105e-06
1 8.478780e-39 NaN 1.313445e-46 1.228485e-155 1.879287e-87
2 NaN 1.313445e-46 NaN 3.520918e-23 6.119853e-12
3 1.738409e-10 1.228485e-155 3.520918e-23 NaN NaN
4 6.224105e-06 1.879287e-87 6.119853e-12 NaN NaN
5 3.376921e-07 8.379384e-119 9.390205e-16 NaN NaN
5
0 3.376921e-07
1 8.379384e-119
2 9.390205e-16
3 NaN
4 NaN
5 NaN
我生成了所有参与度指标的热图,显示统计上显著的中位数差异方向。为了便于解释,差异以每个粉丝的单位数计。差异表示为行减去列。
作者提供的图像:调整后的点赞和转发的中位数差异(差异表示为行(i)减去列(j))
作者提供的图像:调整后的引用和回复的中位数差异(差异表示为行(i)减去列(j))
4. 观察
簇 0 — 政治簇
情感与情绪:最引人注目的是愤怒的强标准残差(27)。在我看来,由于该簇中政治家的突出地位,这一点并不太令人惊讶——注意到乐观度在这里也很低。这是否会根据更广泛的经济和政治气候而变化?在撰写时,我们已经看到硅谷银行的崩溃、科技行业的大规模裁员、生活成本的增加。愤怒和低乐观度可能是这种情况的反映。
参与度指标:调整后的引用和回复的中位数值(几乎)始终高于其他簇,簇 3 除外。如果我要猜测的话,我会认为这一观察主要由政治辩论驱动。从箱形图中我们可以看到,簇 0 的异常值数量较少,这可能部分是由于该簇相对较小。
作者提供的图像:簇 0 的词云
簇 1 — 新闻媒体:
情感与情绪:负面和正面情绪的标准残差分别为 16 和 -17。这是合理的,因为该簇主要是新闻媒体。奇怪的是,喜悦和悲伤的正残差较高。这可能表明情感模型和情绪模型之间存在一些不一致,尽管仅凭此分析很难确定。中立性也强烈为正,这可能在传播金融新闻时是预期的。争议性公众人物的存在也可能贡献于观察到的残差。
参与度指标:簇 1 的中位参与度相对于其他簇始终较低。这可能与新闻媒体的强中立性有关。
作者提供的图像:簇 1 的词云
簇 2 — 主要是体育媒体
情感与情绪:这个集群有一个强烈的中性标准残差。它也有最低的负面残差和第二低的愤怒残差,但由第二高的乐观残差所平衡。对于体育媒体来说,乐观是有意义的,因为我想象这有很多宣传材料。
互动指标:类似于集群 1,有趣的是,它们都具有强烈的中性残差。这些集群在一定程度上被新闻/媒体主导而非个人,这可能驱动了较低的互动。
作者图片:集群 2 的词云
集群 3 — 流行歌手和脱口秀主持人
情感与情绪:集群 3 在娱乐方面较重。残差更倾向于正面情感和乐观。也许这很有意义,因为娱乐通常应该是轻松和有趣的。
互动指标:集群 3 的互动中位数在各方面都是最高的。通过查看箱型图,我们可以看到离群值的数量和幅度都大于其他集群。考虑到集群内的名字,这似乎并不令人惊讶,它似乎是所有集群中 A-list 名人账户的浓度最高的。离群值给了我们一些迹象,表明这个集群的推文相比之下更有可能“病毒式传播”。我会谨慎地认为这可能只是由于集群 3 相对其他集群的样本效应。然而,较大的中位数互动差异在一定程度上表明了病毒式传播。
作者图片:集群 3 的词云
集群 4 — 运动员
情感与情绪:主要是高度的积极性,一些愤怒,一些乐观,但关键是低悲伤。我认为这很好地捕捉了运动员的高能量特性。
互动指标:中位数关注者调整的转发、收藏和回复都高于除了集群 3 和 0 之外的所有集群。这可能与分享的内容类型有关。例如,这个集群可能有更多的视频内容。中位数引用也低于其他集群,这也许更多地证明了视频和图片内容相对于文字内容的存在。
作者图片:集群 4 的词云
集群 5 — 主要是流行歌手
情感与情绪:该集群具有最低的愤怒和悲伤,以及最高的乐观和正面情感残差。它与集群 3 有相似之处。
互动指标:集群 5 是一个混合体,但与其他娱乐相关集群有类似的模式,只是从互动指标的角度来看效果稍弱。与集群 3 类似,这个集群在互动指标上有一些大的异常值。可以说,集群 5 实际上只是集群 3 的一个子集,从互动和情感的角度来看。
作者提供的图像:集群 5 的词云
5. 局限性与扩展
在结束之前,我应该讨论这种方法的局限性,实际上存在几种。
-
由于我们仅查看每个账户的最后 100 条推文,因此这些集群不一定在时间上稳定。账户的发推方式可能会随着时间的推移而因复杂的外部情况而发生变化。可以将分析扩展以捕捉时间上的关系。
-
该方法依赖于预训练的分类模型。尽管它们在最先进的基准测试中表现良好,但并不完美。尽管如此,将更多的影响指标如讽刺、仇恨言论检测纳入其中将会很有趣。
-
聚类的结果可能会根据分析师选择的超参数而变化。将这种无监督机器学习方法与专业领域知识相结合总是有益的,以确保形成的聚类是合理的。与社交媒体专家合作,听取他们对生成的见解的看法,将会很有趣。
感谢阅读。
[## 通过我的推荐链接加入 Medium - 约翰·阿德约]
我分享数据科学项目、经验和专业知识,以帮助你在你的旅程中。你可以通过…
johnadeojo.medium.com [## 首页 | 约翰·阿德约]
关于我 作为一名经验丰富的数据科学家和机器学习(ML)专家,我热衷于利用…
在 LinkedIn 上关注我
引用
[1] Barbieri, Camacho-Collados, Neves, & Espinosa-Anke (2020 年 10 月 26 日). TWEETEVAL: 统一基准与推文分类的比较评估。Snap Inc., Santa Monica, CA 90405, USA & 计算机科学与信息学学院, 卡迪夫大学, 英国。获取自 arxiv.org/pdf/2010.12421.pdf
[2] Ostertagova, E., Ostertag, O., & Kováč, J. (2014). Kruskal-Wallis 检验的方法论与应用。《应用力学与材料》,611,115–120。
[3] Dinno, A. (2015). 使用 Dunn 检验在独立组中进行非参数配对多重比较。Stata Journal, 15, 292–300. doi.org/10.1177/1536867X1501500117
我的简历策略如何让我进入了 0.1%被录取的申请者——这是终极指南
原文:
towardsdatascience.com/dissecting-the-resume-that-got-me-my-data-scientist-job-in-tech-db4b4d943228
技术领域数据科学职位简历成功的秘密
·发表于Towards Data Science ·12 分钟阅读·2023 年 11 月 10 日
–
作者提供的图片(DALL.E)
就在三年前,我在 Spotify 获得了梦寐以求的实习机会。快进到今天,我已经成为他们的全职数据科学家,我感到非常满意!
不过,进入并不容易。 我申请了他们的实习项目多年,连续三年都被拒绝了。在我被录取的那一年,我申请了他们的 6 个实习机会,只获得了一个面试。
当你申请技术职位时,最困难的部分是通过简历筛选。在我的案例中,我只是从六万多名申请者中被挑选出的六十名实习生之一(这不是夸张!)。
这就是你在申请任何大名鼎鼎的技术公司时面临的几率。可悲的是,这些职位的定义就是竞争激烈。
所以问题是——你如何在与所有自称为数据科学家的其他人竞争时实际通过这令人烦恼的筛选阶段?
你需要脱颖而出——为此,你需要一个策略!
成功的一个最关键却被低估的步骤就是精通你的简历——你的 CV。
这是你唯一能控制的事情——是否能获得梦寐以求的工作的面试机会。
但是,为技术职位制作简历并不遵循常规规则,因为你首先需要说服机器你是被挑选的那一个。
你必须跳出框框思维,或者你可以跟随那些成功进入的人的路径——像我呵呵。
让我们一起深入剖析那份为我打开 Spotify 大门的简历。 我们将逐部分揭示使我的申请在成千上万的申请者中脱颖而出的原因,甚至会告诉你一些我加入后学到的秘密!
要记住的一点!
作者提供的图片(DALL.E)
在求职时,你希望别人不仅仅是在你的店前停留。你希望他们对你的店情有独钟,以至于无法抗拒进去。
你的窗口 — 或简历,是你与招聘者之间唯一的障碍。 你的简历是你是谁以及你能提供什么的表象。这就是为什么投资时间打造一个引人注目的窗口是如此重要。
注意 — 请记住,虽然这些技巧对我的个人成功至关重要,我分享它们的原因也是如此,但成功是多方面的。技能、时机甚至运气等因素,往往超出我们的控制范围,也起着重要作用。因此,将这些技巧视为指导方针或优化你自己旅程中可控部分的垫脚石,而不是成功的万无一失的公式。
第一部分—标题
事后看来,我在目标方面本可以做得更好,但嘿,这仍然有效!
简历的标题往往是你窗口中最被忽视和低估的地方。这里是展示你愿景和你能带来的东西(当然,还有你的个人信息,但那是理所当然的)的绝佳场所。
这为简历的其余部分定下了基调。
那么如何打造一个引人注目的标题呢?
秘密在于:确保它针对你申请的公司量身定制。
这很重要,因为它展示了:
-
你不是采取一种一刀切的方法。
你付出额外的努力是因为你在乎,如果你这样做,他们会感受到,并可能也会付出额外的努力来考虑你的申请!
-
你已经研究了公司并了解其文化和价值观。 很多科技公司不仅仅在寻找有能力的人。任何人都能编码。此时,决定你是否脱颖而出的主要因素之一就是你能多好地融入公司文化!
你应该做的两件事:
-
缩小到 5 家你真正想进入的公司。 研究公司的使命和价值观。将你的目标与它们对齐。
-
避免泛泛的陈述。 确保目标明确,并始终根据你申请的具体角色和公司量身定制。
目标不是给自己创造一个新的角色,而是挖掘你本来的自我,看看什么适合这个公司。如果你找不到合适的东西,也许这不是你合适的地方。
不要忘记显而易见的 — 你的联系信息
在制作联系信息时,请记住以下几点:
-
你的标题必须干净简约。 只提供必要的联系信息,不要过于拥挤。
-
你可以只包括城市,而不是完整的地址。 对于大多数科技公司来说,这已经足够了。
-
包括你的电话号码。 很多招聘者会打电话给他们筛选出的申请者。
-
别忘了将你的简历链接到 LinkedIn 个人资料和作品集或 GitHub。 这总是一个聪明的举动,尤其是在科技行业。确保任何链接的个人资料或作品集都是最新的,并且专业策划。
-
避免使用随意的邮箱地址, 应选择听起来更专业的邮箱地址。
第二部分——经历
如果有一件事我从所有那些简历辅导课程中记住,那就是这个:
-
使用动词—— 使用像“领导”、“开发”、“设计”等动词开头的描述能为任何经历带来动态的感觉。确保在描述你自己的经历时也这样做!
-
数字讲述故事—— 与其只是列出角色和责任,更重要的是用可量化的数据来支持你的成就。这是一个重大优点。在我的情况下,我只有实习经历,但我仍然尽力包括一些数字。
总是问自己:“我的角色对项目或公司有什么影响?” 目标是展示以结果为导向的工作。
使用以下策略来优化你的简历:
-
研究你目标公司中目标职位员工的 LinkedIn 个人资料。
-
模仿他们在职位描述中使用的技能和关键词,以便与你申请的公司的价值观对齐。
-
自我审视一下你已经拥有的类似技能,并确保突出这些技能。
为什么这是一个颠覆性的改变?
记住,初筛通常是自动化的,机器扫描与职位相关的关键词。这就是为什么定制你的简历以包括这些关键词是如此重要。这样可以提高通过机器筛选到达人工招聘人员的机会。
一旦你做了这些,你还需要确保你的简历能与非技术 HR 人士产生共鸣,使用类似于职位描述的语言。
第三部分——教育
注意——如果你没有像我那样的经历,那么这部分应该放在你的经历部分之前。
让我们澄清一下。科技公司不在乎你在哪里受过教育。我见过许多在 FAANG 和其他顶尖科技公司工作的人员来自一些鲜为人知的大学。
这一部分并不是关于你的窗口显示的是香奈儿还是迪奥,而是你是否在销售顶级质量的产品——无论品牌名称如何。
你如何证明你的教育质量值得认可?
-
突出相关课程。 这并不是列出你学过的所有数学或编程课程,而是仅引用那些你学习了对申请职位有相关技能的课程!再强调一遍,甄选是关键。这对实习特别有用,因为实际经验可能有限。
-
突出任何荣誉或奖项(如果有的话)。 这展示了学术卓越或仅仅是卓越。没有获得奖项不会受到惩罚,但如果你获得过奖项会更好。
技术公司重视持续学习。突出相关课程和项目可以展示对该领域的热情。
你如何做到这一点?
注册相关的在线课程和认证。然后突出你学习的直接应用,这将引导我们进入下一部分。
随着职业的进步,更多地关注实际经验,减少对学术成就的强调。
第四部分——项目
如果你已经有数据科学方面的经验,这一部分的重要性就不那么大了。目标是将这些项目整合到你的经验部分。
不过,如果像当时的我一样,你几乎没有什么经验,那么这一部分就是你的窗口的关键点。那是你展示你所拥有的最新趋势的地方!
下面是如何突出你的项目
-
量身定制——我不会停止重复这一点。目标是展示所需的技能,这适用于你简历的每一部分。你的窗口需要大声喊出你是那个唯一的选择!所以确保你只突出那些与你所需工作或技能相匹配的项目。
-
详细说明——每个项目都有简要描述、使用的技术和成果。这提供了全面的视角。
-
展示—— 在你的简历中添加直接链接到你的项目是展示你具体做了什么的好方法。这是一个容易抓住的机会,所以不要错过!
最后,避免使这一部分过于拥挤。目标是展示你在实际场景中应用技能的能力。
定期开展副项目,以为这一部分增加更多价值。
第五部分——技能
展示技能时要记住的主要点就是展示它——不要仅仅说出来!
如果你有确凿的事实,你就不需要说任何话。
我见过太多简历,特别是早期职业者的简历,写着*“良好的沟通和领导能力”*。仅仅因为你明确指出了这一点并不意味着它就是真的。如果你没有提供你是一个良好的沟通者和团队合作者的支持证据——它可能只是空话。
这就是为什么在你的简历中隐性地展示这些技能是如此重要。与其说*“良好的领导能力”,不如尝试用“领导了一个由 5 人组成的团队完成项目等…”*来替代。至少现在你有了更多的可信度,人们知道你所说的领导能力是什么意思。
然后是技术技能。以下是你可以做的事情。
-
通过项目和经历展示你的熟练程度。就像我之前做的那样。
-
与其列出长长的清单,不如添加一个技能部分,将你的技术栈进行分类。 列出并分类它们可以使招聘人员更容易将你与合适的职位匹配。
-
列出的技能与技术角色相关。 没有人关心你掌握了微软办公套件用于数据科学角色。关键是列出所有相关的编程技术栈,但要根据职位描述的顺序排列。优先考虑!
第六部分— 兴趣
还记得我们说过科技公司不仅仅寻找有能力的人,还寻找那些适合公司文化的人吗?
这个部分可能看起来不那么重要,但我见过很多人——那些已经在你感兴趣的公司里的人——非常关注这一部分。
这可能会让你感到惊讶,但在两个技能相当的候选人之间,他们的兴趣类型对决策的影响更大。
为什么?因为招聘你的人员将是与你一起工作的同事。他们希望和可以相处的人一起工作!
你的兴趣部分需要展示你不仅仅关心工作/学习,还拥有超越这些的生活。科技公司欣赏那些具有多样视角的全面发展的人。
我弹小提琴,所以我确保它是课外活动列表中的第一项。为什么?因为我在申请一家音乐公司。
然后我提到了定义我的其他核心兴趣话题。这样做为你的窗口增添了色彩,使其更具吸引力!
设计与布局
这是完成的产品。总体来说,你需要为简历增添个性。这意味着选择自己的展示风格、颜色、字体等。
我在 2021 年申请 Spotify 时使用的简历
招聘人员在最佳情况下花不到一分钟 浏览你的简历。目标是让招聘人员尽可能轻松地阅读。
在设计时,你需要牢记这些要点:
-
保持干净与整洁 — 简历不应杂乱。留有足够的空白以提高可读性。干净整洁的设计反映了结构化的思维方式——这是技术角色中必不可少的。
-
一致的格式 — 字体、项目符号、空格和加粗的一致性。我见过很多简历在这些方面杂乱无章。这很重要,因为它显示了你对细节的关注——这是技术角色的另一个必要技能。
-
保持简短 — 对于实习生,简历最多一页。如果申请全职工作可以稍微多一点!
理想情况下,尽量获得一些关于你简历的反馈。其他人可能会发现你忽略的不一致之处。
我最终的秘密
当我加入 Spotify 时,我发现我的一位同事从一堆简历中亲自挑选了我的简历。因此,我抓住了这个机会问他为什么他选择了我担任这个角色。
他的回答?我有双重背景,即商业和数据科学,这对他很重要,因为他认识的最优秀的数据科学家都具备这双重背景。他看到了我身上的潜力。
从中得到的教训是?强调你的商业眼光以及数据科学技能。科技公司重视那些能够利用数据推动业务目标的人。
如果你没有这个 B 背景,你可以做些什么?
-
参加商业基础的在线课程 —— 像 Coursera、edX 和 LinkedIn Learning 这样的平台注册提供商业基础、金融、市场营销和战略等课程。重点选择提供商业原则概述的课程。
-
阅读以商业为重点的书籍和出版物 —— 像埃里克·里斯的《精益创业》或吉姆·柯林斯的《从优秀到卓越》这样的书籍可以提供关于商业战略和思维的见解。
-
参与商业案例竞赛或黑客马拉松 —— 这些活动通常是跨学科的。它们可以为你提供用数据驱动方法解决商业问题的实践经验。
目标是将你在数据科学方面的技术专长与商业知识相辅相成,并在简历上有明确的证据展示。
回顾
在设计你的简历时,请记住以下几点:
-
量身定制与创意方法 —— 为了脱颖而出,你的简历需要针对你申请的特定职位和公司进行定制。
-
经验的相关性 —— 只突出与你申请的职位相关的经历/项目。如果你申请的是 Meta,没有人关心你在 Titanic Kaggle 项目中获得了第一名。
-
软技能与硬技能的平衡 —— 除了你的技术栈外,还应有足够的证据表明团队合作、领导力和其他软技能。
-
商业和技术技能的胜出 —— 你的经历/项目应该体现出你平衡商业目标与技术专长的能力。
-
格式很重要 —— 注意简历的设计,应当井然有序、易于阅读且一致。你的故事需要自上而下自然流畅。
-
展示,而不是讲述!
请记住,决定你是否会被选中面试的因素还包括其他元素 → 比如:运气、时机、招聘官那天是否吃了早餐等。
正是因为这个原因,你必须全力以赴地优化你可以控制的事情。
有时候,你可能需要超越这些。三年前,当我还是学生的时候,我申请了一个非常有竞争力且非常酷的科技数据科学实习(不是 Spotify),我真的很想得到这个实习。
剧透警告:我被拒绝了。两次。但我还是拿到了实习机会。怎么做到的?网络关系救了我,我在下面的文章中会详细告诉你 ⬇
网络让我在科技行业获得了工作,即使我被拒绝了,这是我做到的方式
为什么你在 LinkedIn 上的网络游戏可能会阻碍你的成功(以及如何解决这个问题)
towardsdatascience.com
我有礼物送给你🎁!
订阅我的通讯 K 的 DataLadder,你将自动获得我的终极 SQL 备忘单,其中包含我在大科技公司工作中每天使用的所有查询 + 另一个秘密礼物!
我每周分享作为科技领域数据科学家的经历,以及实用的技巧、技能和故事,所有这些都是为了帮助你提升水平——因为没人真正了解,直到他们亲身经历!
如果你还没有做这件事
很快见!
大型语言模型:DistilBERT——更小、更快、更便宜、更轻便
解锁 BERT 压缩的秘密:一个用于最大效率的学生-教师框架
·
关注 发表在 Towards Data Science ·7 min read·2023 年 10 月 7 日
–
介绍
近年来,大型语言模型的发展迅速。BERT 成为最受欢迎和高效的模型之一,能够以高精度解决各种自然语言处理任务。在 BERT 之后,出现了一系列其他模型,这些模型也展示了卓越的结果。
显而易见的趋势是随着时间的推移,大型语言模型(LLMs)往往变得更加复杂,通过指数级增加参数和数据的数量。深度学习研究表明,这些技术通常会带来更好的结果。不幸的是,机器学习领域已经遇到了一些关于 LLMs 的问题,可扩展性已成为有效训练、存储和使用它们的主要障碍。
针对这个问题,已经制定了专门的技术来压缩 LLMs。压缩算法的目标是减少训练时间、降低内存消耗或加快模型推断。实际中使用的三种最常见的压缩技术如下:
-
知识蒸馏涉及训练一个较小的模型,试图表示较大模型的行为。
-
量化是减少存储表示模型权重的数字所需内存的过程。
-
剪枝指的是丢弃最不重要的模型权重。
在本文中,我们将理解应用于 BERT 的蒸馏机制,这导致了一个新模型叫做DistilBERT。顺便提一下,下面讨论的技术也可以应用于其他 NLP 模型。
蒸馏基础
蒸馏的目标是创建一个较小的模型,可以模仿较大的模型。在实践中,这意味着如果一个大型模型预测某些内容,则期望较小的模型做出类似的预测。
为了实现这一点,需要一个已经预训练的大型模型(在我们的例子中是 BERT)。然后,需要选择一个较小模型的架构。为了增加成功模仿的可能性,通常建议较小模型的架构与大型模型相似,但参数数量减少。最后,较小模型从大型模型在某一数据集上做出的预测中学习。为了达到这一目标,选择一个适当的损失函数对于帮助较小模型更好地学习至关重要。
在蒸馏术语中,大型模型称为教师,较小模型称为学生。
一般来说,蒸馏过程应用于预训练阶段,但也可以在微调阶段应用。
DistilBERT
DistilBERT 从 BERT 学习,并通过使用包含三个组件的损失函数来更新其权重:
-
掩码语言建模(MLM)损失
-
蒸馏损失
-
相似性损失
接下来,我们将讨论这些损失组件,并了解每个组件的必要性。不过,在深入之前,有必要了解一个重要概念,即Softmax 激活函数中的温度。温度概念在 DistilBERT 损失函数中使用。
Softmax 温度
通常可以观察到 softmax 转换作为神经网络的最后一层。Softmax 规范化了所有模型输出,使其总和为 1,可以解释为概率。
存在一个 softmax 公式,其中模型的所有输出都除以一个 温度 参数 T:
Softmax 温度公式。pᵢ 和 zᵢ 分别是模型输出和第 i 个对象的规范化概率。T 是温度参数。
温度 T 控制输出分布的平滑度:
-
如果 T > 1,那么分布变得更平滑。
-
如果 T = 1,则分布与应用正常 softmax 时相同。
-
如果 T < 1,那么分布变得更粗糙。
为了明确起见,我们来看一个例子。考虑一个具有 5 个标签的分类任务,其中神经网络生成了 5 个值,表示输入对象属于相应类别的信心。应用不同 T 值的 softmax 会产生不同的输出分布。
一个神经网络根据温度 T 产生不同概率分布的示例
温度越高,概率分布越平滑。
基于不同温度 T 值的 logit(自然数从 1 到 5)的 softmax 转换。随着温度的升高,softmax 值变得更加趋同。
损失函数
掩码语言建模损失
类似于教师模型(BERT),在预训练期间,学生(DistilBERT)通过对掩码语言建模任务进行预测来学习语言。在对某个标记生成预测后,预测的概率分布与教师模型的一热编码概率分布进行比较。
一热编码分布表示一个概率分布,其中最可能的标记的概率设置为 1,其它所有标记的概率设置为 0。
如同大多数语言模型一样,交叉熵损失是在预测分布和真实分布之间计算的,学生模型的权重通过反向传播进行更新。
掩码语言建模损失计算示例
蒸馏损失
实际上,可以仅使用学生损失来训练学生模型。然而,在许多情况下,这可能不够。仅使用学生损失的常见问题在于其 softmax 转换,其中温度 T 设置为 1。在实际应用中,T = 1 的结果分布会变成一种形式,其中一个可能的标签具有非常接近 1 的高概率,而所有其他标签的概率则很低,接近 0。
这种情况与对特定输入有效的两个或多个分类标签不太一致:当 T = 1 的 softmax 层将很可能排除所有有效标签,除了一种,并将概率分布接近于 one-hot 编码分布。这会导致可能被学生模型学习的有用信息丢失,从而使其多样性降低。
这就是为什么论文的作者引入了蒸馏损失,其中 softmax 概率使用温度 T > 1 进行计算,使得平滑对齐概率成为可能,从而考虑到学生的多个可能答案。
在蒸馏损失中,相同的温度 T 应用于学生和教师。移除了教师分布的 one-hot 编码。
蒸馏损失计算示例
可以使用 KL 散度损失代替交叉熵损失。
相似度损失
研究人员还指出,在隐藏状态嵌入之间添加余弦相似度损失是有益的。
余弦损失公式
这样,学生不仅有可能正确重建被掩盖的标记,还能构建与教师相似的嵌入。这也为在两个模型空间中保持嵌入之间的相同关系打开了大门。
相似度损失计算示例
三重损失
最后,计算所有三个损失函数的线性组合和,这定义了 DistilBERT 中的损失函数。根据损失值,对学生模型进行反向传播以更新其权重。
DistilBERT 损失函数
有趣的是,在三种损失组件中,掩盖语言建模损失对模型性能的影响最小。蒸馏损失和相似度损失的影响要大得多。
推断
DistilBERT 中的推断过程与训练阶段完全相同。唯一的细微之处是 softmax 温度 T 设置为 1。这是为了获得接近 BERT 计算的概率。
架构
总体而言,DistilBERT 使用与 BERT 相同的架构,只是进行了以下更改:
-
DistilBERT 仅有 BERT 层的一半。模型中的每一层都通过从两个 BERT 层中选择一个来初始化。
-
移除了标记类型嵌入。
-
应用于[CLS]标记的隐藏状态的分类任务的密集层被移除。
-
为了更强的性能,作者使用了 RoBERTa 中提出的最佳方法:
-
使用动态掩蔽
-
移除下一个句子预测目标
-
在更大的批次上训练
-
应用梯度累积技术以优化梯度计算
-
DistilBERT 的最后一层隐藏层大小(768)与 BERT 相同。作者报告称,其减少不会在计算效率方面带来显著改进。他们认为,减少总层数有更高的影响。
数据
DistilBERT 在与 BERT 相同的数据语料上训练,这些数据包含 BooksCorpus(8 亿字)和英语维基百科(25 亿字)。
BERT 与 DistilBERT 的比较
BERT 和 DistilBERT 的关键性能参数在几个最流行的基准测试上进行了比较。以下是需要记住的重要事实:
-
在推理过程中,DistilBERT 比 BERT 快 60%。
-
DistilBERT 的参数比 BERT 少 4400 万,总体比 BERT 小 40%。
-
DistilBERT 保留了 BERT 97% 的性能。
BERT 与 DistilBERT 的比较(在 GLUE 数据集上)
结论
DistilBERT 在 BERT 的进化中迈出了巨大的一步,它通过显著压缩模型的体积,同时在各种 NLP 任务上实现了相当的性能。除此之外,DistilBERT 的体积仅为 207 MB,使得在容量有限的设备上集成更加容易。知识蒸馏并不是唯一可应用的技术:DistilBERT 可以通过量化或剪枝算法进一步压缩。
资源
除非另有说明,所有图像均由作者提供
PyTorch 中的分布式数据并行和分布式模型并行
原文:
towardsdatascience.com/distributed-data-and-model-parallel-in-deep-learning-6dbb8d9c3540
了解分布式数据并行和分布式模型并行如何在随机梯度下降中工作,以便让你可以在庞大的数据集上训练巨型模型
·发表于 Towards Data Science ·阅读时长 14 分钟·2023 年 5 月 8 日
–
由 Olga Zhushman 在 Unsplash 拍摄的照片
你一定听说过,最近成功的模型,如 ChatGPT,拥有数万亿个参数,并且使用了数 TB 的数据进行训练。同时,你也可能经历过你的深度学习模型,即使只有几千万个参数,也无法在一个 GPU 上完成训练,并且用几 GB 的数据训练了好几天。
如果你想知道为什么其他人在同样的时间内能取得如此多的成就,并且希望成为他们,请理解这两种技术,它们使得在庞大的数据集上训练大型深度学习模型成为可能:
-
分布式数据并行 将一个小批量数据分割到多个 GPU 上。这使得训练速度更快。
-
分布式模型并行 将模型的参数、梯度和优化器的内部状态分割到多个 GPU 上。这使得你可以在 GPU 上加载更大的模型。
有许多分布式数据并行和分布式模型并行的 API 实现,如DDP、FSDP和DeepSpeed。它们都有一个共同的主题,即将训练数据或模型拆分成多个部分,并将这些部分分配到不同的 GPU 上。本文不是关于如何使用这些 API,因为教程已经很丰富。本文是对这些 API 如何在幕后工作的直观理论探讨。之所以称之为“探讨”,是因为本文并没有完成这两个庞大的主题——它在你获得足够的背景知识和勇气去深入了解它们或面对技术面试时就会停止。
随机梯度下降中的并行性
了解分布式数据和模型并行的工作原理实际上意味着了解它们在执行深度神经网络的参数学习(或等同于模型训练)的随机梯度下降算法中的工作方式。具体来说,我们需要了解这两种技术如何在以下方面工作:
-
前向传播计算模型预测和数据点或样本的损失函数。
-
反向传播阶段,或称为反向传递,计算损失函数相对于每个模型参数的梯度。
让我们从较简单的分布式数据并行开始。
分布式数据并行
它通过并行化随机梯度下降遍历训练数据的方式来解决巨大的训练数据集问题。让我们回顾一下单个 GPU 中随机梯度下降的过程。
随机梯度下降的步骤
-
训练过程将整个模型加载到该 GPU 中。
-
然后该过程会多次遍历整个训练数据集,每次遍历称为一个时期(epoch)。
-
在每个时期,该过程会通过随机采样的小批量遍历训练数据集中的所有样本。一个小批量由若干数据点组成。这种随机采样是不放回的,确保每个数据点在一个时期内仅存在于一个小批量中一次。小批量的随机采样解释了算法名称中的“随机”一词。每个数据点在前向传播和反向传播中都会被使用。
-
在前向传播中,该过程将每个数据点通过神经网络推送以计算模型的输出,即预测,然后使用模型的预测来通过计算预测与实际之间的差异来计算损失函数。
-
在反向传播中,该过程计算损失相对于每个模型参数的梯度,即神经网络中的权重和偏置。
-
然后,该过程使用当前模型参数及其梯度的值,通过权重更新规则分配模型参数的新值。如果一个参数的当前值是w,其梯度是∇w,学习率是α,那么权重更新规则将计算新的参数值w′,即w′ ← w - α·∇w。
-
重复步骤 3 到 6,直到模型训练得足够好,例如,直到损失不再减少一段时间,或者直到你耗尽资金或耐心为止。
分布式数据并行将一个小批量分配到多个 GPU 上
分布式数据并行在上述训练过程的第 4 步和第 5 步中做出了改进。它将一个小批量分成不同的部分,并将这些部分发送到不同的 GPU 上进行前向和反向传播。这样,相同的小批量可以更快地处理。
要理解这究竟意味着什么,我们假设我们的一个小批量仅包含两个数据点。也就是说,我们的批量大小是 2。这个小批量中的两个数据点是*(X₁, Y₁)和(X₂, Y₂)。对于第一个数据点,前向传播使用X₁来计算模型的预测值ŷ₁*,第二个数据点也是如此,计算ŷ₂。
我们有两个 GPU,分别命名为 GPU1 和 GPU2。
分布式模型并行中的前向传播
如果我们采用通常的二次损失函数,那么前向传播最终计算损失L的值:
分布式数据并行中的损失计算
注意在第(3)行中,两个术语L₁和L₂只依赖于单个但不同的数据点。因此,损失项L₁的计算与L₂的计算是独立的。这允许分布式数据并行将第一个数据点*(X₁, Y₁)发送到 GPU1,将第二个数据点(X₂, Y₂)*发送到 GPU2,以进行损失计算。
当然,为了使上述工作,每个 GPU 必须加载完整模型,以便它可以通过整个网络推送单个数据点来计算模型的预测,然后计算该数据点的损失。
分布式模型并行中的反向传播
现在让我们看看反向传播,它计算损失函数对每个模型参数的梯度。我们将关注一个单一的模型参数,比如w₁。损失L对w₁的梯度,记作∇w₁,通过*:*
分布式数据并行中的梯度计算
使用微分的线性性,第(3)行将完整梯度分成两个术语,每个术语对应一个单独的数据点。由于我们有两个 GPU,每个 GPU 加载了完整模型并接收了一个数据点,该 GPU 可以计算该数据点的梯度。换句话说,第(3)行的两个梯度项可以使用两个 GPU 并行计算。也就是说,GPU1 计算并保存∂L₁,GPU2 计算∂L₂。
同步的参数权重更新
最后,随机梯度下降通过使用权重更新规则来执行参数值更新:
梯度下降权重更新规则
该规则通过从当前参数值 w₁ 中减去 α·∇w₁ 来给出新值 w₁′,因此有了“梯度下降”这个术语。α 是学习率;它控制下降的步长。这里我使用符号 w₁ 来表示参数名称及其当前值,以避免引入过多的符号。
所有 GPU 都需要使用相同的 ∇w₁ 来执行 w₁ 的权重更新,以确保每个 GPU 在权重更新步骤后具有相同的模型。
在这里我们应该发现一个问题:权重更新规则需要模型参数w₁的完整梯度 ∇w₁,但没有 GPU 拥有这个量。GPU1 持有量 ∂L₁,因为它在其中计算 ∂L₁;而 GPU2 持有 ∂L₂。为了解决这个问题,一些 GPU 之间的计算会将 ∂L₁ 和 ∂L₂ 相加,然后将和转移到两个 GPU 上。AllReduce GPU 操作符完成了这个工作。
AllReduce 操作符
AllReduce 操作符 对数据执行降维操作,例如求和、最大值,跨所有 GPU 并将结果写入所有 GPU。
下图说明了 AllReduce 如何将两个 GPU 上模型参数 w₁ 的部分梯度 ∂L₁ 和 ∂L₂ 相加,并将结果 —— 完整梯度 ∇w₁ 写入所有 GPU。
由作者绘制的 AllReduce 操作符插图
为什么分布式并行可以减少训练时间?
数据在 GPU 之间的传输需要时间,但只要数据传输的时间少于计算所有数据点的损失和梯度的时间,就能在花费更多钱雇用更多 GPU 的代价下获得时间上的收益。
如果你很富有,你可以雇用 10,000 个 GPU,并将你的 mini-batch 大小设置为 10,000。这样在一个优化步骤中,你可以处理大量的训练数据。我将让你的想象力在这里放飞,思考一下这对你那几 TB 大小的数据集意味着什么。
分布式数据并行中的警告
分布式数据并行有一个问题——它要求每个 GPU 持有完整的模型。你不能在单个 GPU 中加载大型模型,通常 GPU 的内存为 16GB 到 24GB,因此它们大致支持一亿个参数。要训练比这更大的模型,我们需要分布式模型并行。
分布式模型并行
分布式模型并行将模型的参数、它们的梯度和优化器的内部状态分割成不同的部分,并将这些部分分布到 GPU 上。
很容易理解为什么分布式模型并行需要拆分模型的参数及其梯度——随机梯度下降中的权重更新规则需要这两者。但是,优化器的内部状态是什么呢?
优化器的内部状态
你看,为了减轻随机梯度下降算法中引入的问题,像 Adam 这样的优化器会跟踪每个模型参数的两个额外信息:其梯度的移动平均,以减少权重更新中的波动,以及平方梯度的移动平均,以实现每个参数的自适应学习率。有关更多详细信息,请查看:
我们可以在线性回归模型上使用随机梯度下降(SGD)吗?
了解为什么在参数学习中使用 SGD 在线性回归模型上是有效的,但请注意,SGD 可能会…
我们可以在线性回归模型上使用随机梯度下降(SGD)吗?
从数学上讲,Adam 对参数 w₁ 的权重更新规则是:
Adam 优化器的权重更新规则
行(1)计算了 w₁ 参数的梯度移动平均。multiplier × old_value + (1-multiplier) × new_value 公式结构告诉我们这是一个指数移动平均。m₁ 是当前的指数移动平均值,β₁ 控制了新值(这里是新的梯度 ∇w₁)对新移动平均值的贡献量。m₁′ 是梯度移动平均的新值。
同样,行(2)计算了平方梯度的指数移动平均,其中 v₁ 是平方梯度的当前移动平均值,β₂ 控制了在平均过程中平方梯度 (∇w₁)² 的贡献量。v₁′ 是平方梯度移动平均的新值。
行(3)是参数权重更新规则。注意它提到了参数 w₁ 的当前值,梯度移动平均 m₁′ 和平方梯度移动平均 v₁′。再次查看 我们可以在线性回归模型上使用随机梯度下降(SGD)吗? 以获取直观理解。
梯度移动平均和平方梯度移动平均是 Adam 优化器的内部状态,实际上,Adam 还保留了权重的完整副本,但这是技术细节,在本文中你不需要担心。不同的优化器可能会保持不同的内部状态。
如何将模型拆分为多个部分?
为了理解分布式模型并行如何将模型分成多个部分,假设即使小批量大小设置为 1,我们的神经网络也太大,无法适配到 GPU 的内存中,如下图所示的神经网络:
作者提供的神经网络架构示意图
这个神经网络接受两个输入单元。因此,对于单个训练数据点 (X₁, Y₁),其中 X₁ 由两个输入单元 X₁ = [x₁, x₂] 组成,网络接受 x₁ 和 x₂ 作为输入,并使用两个隐藏层,四个神经元 h₁ 到 h₄ 来计算模型的预测 ŷ₁,并使用实际的 Y₁ 和模型预测 ŷ₁ 来计算损失 L。为了简化起见,神经网络中没有激活函数,每个接收多个输入箭头的节点将接收到的数量相加。
我们如何将模型分成多个部分,以便每个部分都可以适配到一个 GPU 上?有许多方法。一种方法是垂直切分模型:
作者提出的模型不够智能的切分方法
其中 w₁~w₄ 在 GPU1 中,w₅~w₁₀ 在 GPU2 中。注意输入 (X₁, Y₁) 始终在所有 GPU 中。
这种切分方法虽然有效,但并不智能,因为它迫使计算必须顺序进行。GPU2 需要等待 GPU1 的结果。具体来说,GPU2 需要等待神经元 h₁ 和 h₂ 的值,然后才能开始计算 h₃ 和 h₄ 的值。
我们认识到,为了实现并行计算,需要水平切分模型。我将使用一个更简单的例子来说明这种水平切分,以简化公式。
作者提供的神经网络架构示意图
分布式模型并行中的前向传播
以下方程描述了该神经网络的前向传播:
神经网络前向传播方程
我们可以看到方程 (1) 和 (2) 彼此独立,因此可以并行计算。方程 (3) 和 (4) 需要 h₁ 和 h₂ 的值,因此需要等待 h₁ 和 h₂ 的计算结果。
等效地,我可以将上述方程 (1) 到 (3) 重新写成以下块矩阵形式:
块矩阵形式的神经网络前向传播方程
块 A₁ 和 A₂ 为
权重矩阵的块矩阵
我们现在意识到可以将 X₁A₁ 放在 GPU1 中,而 X₁A₂ 放在 GPU2 中以并行计算。换句话说,分布式模型并行可以将参数 w₁ 和 w₂ 放在 GPU1 中,将 w₃ 和 w₄ 放在 GPU2 中。
AllReduce 操作符将对它们求和,得到模型预测 ŷ₁ 的值,并使 ŷ₁ 对两个 GPU 可用。得到 ŷ₁ 后,两个 GPU 现在可以计算损失 L。请注意,在前向传递中,训练数据 (X₁, Y₁) 始终在所有 GPU 中加载。或者,AllReduce 操作符可以计算模型预测和损失,然后通过一种叫做操作融合的技术将预测和损失一次性复制到所有 GPU。
分布式模型并行中的反向传递
现在让我们检查反向传递。它使用链式法则计算梯度。在你现在的数据科学工作之前,你已经熟悉链式法则了,对吗?
分布式模型并行中的梯度计算
方程 (1) 和 (2) 在 GPU1 上执行,方程 (3) 和 (4) 在 GPU2 上执行。
我们需要检查 GPU1 中是否有足够的信息来计算模型参数 w₁ 和 w₂ 的梯度,以及 GPU2 中是否有足够的信息来计算模型参数 w₃ 和 w₄。
让我们通过查看方程 (1) 专注于 w₁。它揭示了计算梯度 ∇w₁ 需要:
-
训练数据 x₁, Y₁,始终对所有 GPU 可用。
-
模型预测 ŷ₁,通过 AllReduce 将其提供给所有 GPU。
所以 GPU1 能够计算 w₁ 的梯度。由于 GPU1 拥有模型权重 w₁ 及其梯度 ∇w₁,它将能够计算梯度的指数移动平均和平方梯度的指数移动平均,这些都是优化器的内部状态。
这就是分布式模型并行在高层次上的工作原理。请注意,有许多方法可以将模型分割成多个部分。上面展示了一种方式来说明该技术的工作原理。
ReduceScatter 操作符
我还想在分布式模型并行中提到一件事。在一个更实际的神经网络中,从模型预测到其输入有多个路径。请参见我之前介绍的原始神经网络,再次如下所示:
作者的神经网络架构插图
计算模型参数 w₁ 对 L 的梯度,有两个路径:
-
route1: L → ŷ₁ → h₃ → h₁ → x₁
-
route2: L → ŷ₁ → h₄ → h₁ → x₁
因此,完整的梯度是这两个路径中计算的梯度之和,公式为:
两条路径的梯度
梯度从 route1 和 route2 很可能在两个不同的 GPU 上计算。为了计算完整的梯度 ∇w₁,需要将这些 GPU 的信息同步,并进行求和,类似于 AllReduce。不同之处在于,此时求和结果不需要传播到所有 GPU,只需放入负责更新模型参数 w₁ 的单个 GPU。ReduceScatter 操作符就是为了这个目的。
ReduceScatter
ReduceScatter 操作符执行与 AllReduce 操作符相同的操作,只不过结果在 GPU 之间以相等的块进行分散,每个 GPU 根据其排名索引获取一部分数据。
作者提供的 ReduceScatter 操作符示意图
以我们的例子为例,ReduceScatter 操作符对 w₁ 参数的部分梯度进行求和,即来自 route1 的 ∂route₁ 和来自 route2 的 ∂route₂,这些梯度在不同的 GPU 上计算,然后将总和放入唯一负责 w₁ 参数权重更新的 GPU 中,这里是 GPU1*。请注意,GPU2 并未收到完整的梯度 ∇w₁,因为它不负责对 w₁ 参数进行权重更新。
分布式模型并行并不是为了提高训练速度而设计的
请注意,分布式模型并行的目标是让你将更大的模型加载到多个 GPU 中,而不是加速模型训练。事实上,从上面的例子来看,我们将模型横向切分后,在每个 GPU 上,前向和后向计算的传递并没有缩短,只是变得更薄。这意味着每次传递的步骤数相同,因此它们不一定更快(但由于每次传递的计算量较少,它们可以更快,当然,你还需要考虑数据同步所花费的时间)。要更快地训练大型模型,我们需要结合分布式数据和模型并行。
结合分布式数据和模型并行
在训练过程中同时启用分布式数据并行和分布式模型并行是一种常见做法。上面提到的 API,如 PyTorch 的 FSDP,支持这种组合。从概念上讲:
-
分布式模型并行在内层工作,它将一个大型模型分配给一组 GPU。这组 GPU 至少可以处理来自一个小批数据的单个数据点。它们表现得像一个具有无限内存的超级 GPU。这样,你可以加载更大的模型。
-
分布式模型并行在外层工作,它将来自同一小批数据的不同数据点分配到由分布式模型并行模拟的不同超级 GPU 上。这样,你可以更快地训练大型模型。
结论
本文从理论层面解释了在随机梯度下降算法的背景下,分布式数据并行和分布式模型并行的工作原理。有关 API 使用的详细信息,请参考上述其他文档,如 DDP、FSDP 和 DeepSpeed。
支持我
如果你喜欢我的故事,请考虑成为我的推荐会员。我将从你的订阅费用中获得一小部分,这对我有很大支持。
阅读韦毅(以及在 Medium 上的其他成千上万位作者)的每一个故事。我很享受花费数千小时写作的过程……
medium.com](https://medium.com/@jasonweiyi/membership?source=post_page-----6dbb8d9c3540--------------------------------)
Vertex AI 流水线中的分布式超参数调优
启用 GCP Vertex AI 流水线中的分布式超参数调优路径
·
关注 发表在 Towards Data Science ·10 min read·2023 年 3 月 29 日
–
照片由Marsha Reid提供,发布在Unsplash
介绍
Vertex AI 流水线提供了一种便捷的方法来实现从数据收集到端点监控的端到端机器学习工作流,几乎无需额外努力。对于新用户来说,开发和部署的简便性很大程度上归功于 GCP 提供的Vertex AI 流水线示例。
## professional-services/examples/vertex_pipeline 在 main · GoogleCloudPlatform/professional-services
这个仓库展示了如何使用 Vertex AI 平台和智能分析技术进行端到端的 MLOps 过程…
尽管示例全面展示了基本组件,官方示例仍然揭示了用户可以根据自身需求定制和增强管道的可行性。在所有组件中,最令人兴奋的之一是能够在短时间内探索大量搜索空间以识别最佳超参数的分布式超参数调整(HPT)。目前,GCP 推荐使用cloudml-hypertune和google_cloud_pipeline_components来实现这一目的,并提供了相应的教程:
## Vertex AI: 分布式超参数调整 | Google Codelabs
在这个实验中,你将学习如何使用 Vertex AI 进行超参数调整和分布式训练。虽然这个实验使用了…
codelabs.developers.google.com
## vertex-ai-samples/get_started_with_hpt_pipeline_components.ipynb 在…
你目前无法执行该操作。你在另一个标签页或窗口中登录了。你在另一个标签页或窗口中登出了…
然而,这些教程的局限性在于分布式 HPT 被呈现为一个独立的 HPT 任务/管道,而没有明确展示如何将其集成到现有的 Vertex AI 管道中,如Vertex AI 管道示例所示,这促使我分享我成功弥合这一差距的尝试。我相信这将有利于许多已经建立或将要建立基于 Vertex AI 管道的 ML 工作流的企业。
这个博客的主要贡献是将分布式 HPT 集成到 Vertex AI 管道中。具体来说,它演示了如何:
-
将数据收集和预处理链入 Vertex AI 管道中的分布式 HPT。相比之下,GCP HPT 任务教程和HPT 管道示例通过在训练步骤中加载静态的 tensorflow 数据集简化了数据收集和处理。
-
优化 HPT 结果收集以避免 docker 参数长度限制。在HPT 管道示例中,所有试验的完整 HPT 结果被编码为一个字符串,该字符串作为输入参数传递给 docker 任务以进行进一步处理。然而,风险在于如果搜索空间较大,该字符串可能会违反 docker 输入参数的长度限制。因此,本文探讨了一种将这两个组件结合的简单解决方案。
-
将最佳参数保存在 Firestore 中。在HPT 管道示例中,HPT 运行试验、保存模型并部署最佳模型,但之后如何访问最佳参数尚不清楚。这不适合 HPT 和训练期望解耦的场景。因此,本文探讨了使用 Firestore 选项来保存最佳超参数以供后续训练作业使用。
-
将分布式 HPT 链到训练组件中,并使用最佳参数训练模型。与HPT 管道示例中所示的每个 HPT 试验都保存模型不同,本文探讨了一种替代方案,即重新训练并仅保存最佳模型,尽管这种方法是否提供了更好的存储-计算权衡仍然存在争议,取决于具体场景。
将分布式 HPT 集成到 Vertex AI 管道中
现在,让我们按照上面提到的主要步骤进行。值得注意的是,这里演示的 ML 管道主要基于Vertex AI 管道示例,仅对其进行了最小的更改以实现 HPT。为了演示,只通过网格搜索调整了两个超参数,如下所示。
SEARCH_SPACE={"num_boost_round": [100, 200],
"min_data_in_leaf": [5, 10]}
这个工作的 jupyter notebook 包含了部署分布式 HPT 的端到端过程,托管在以下的仓库中。
[## professional-services/hpt_pipeline_development.ipynb at distributed-hpt-demo ·…
Google Cloud 的专业服务团队开发的常见解决方案和工具…
github.com](https://github.com/simon19891101/professional-services/blob/distributed-hpt-demo/examples/vertex_pipeline/notebook/hpt_pipeline_development.ipynb?source=post_page-----2f3278a1eb64--------------------------------)
1. 将数据预处理链接到 HPT。
我遇到的第一个挑战是 vertex-ai-samples 的教程在调用 google_cloud_pipeline_components.v1.hyperparameter_tuning_job 的 HyperparameterTuningJobRunOp 类时,将数据收集硬编码在了 HPT 容器映像中,而实际上我们可能想要在管道中使用数据收集和处理组件。
data, info = tfds.load(name='horses_or_humans', as_supervised=True, with_info=True)
然而,目前 HyperparameterTuningJobRunOp 不支持将输入数据作为参数传递,这激发了我寻找一种替代方法来传递数据源的动机。幸运的是,HyperparameterTuningJobRunOp 使用包含支持 HPT 容器输入参数的 HPT 容器规范的 worker_pool_specs。
worker_pool_specs = [
{
"machine_spec": {
"machine_type": "n1-standard-4",
},
"replica_count": 1,
"container_spec": {"image_uri": hpt_container_image_uri, "args": CMDARGS},
}
]
直观地说,它意味着将输入数据源作为容器规范的一部分传递是可行的,并且验证证明这是一次成功的尝试。
下面展示了这种操作的代码示例。具体来说,创建了一个名为 worker_pool_specs 的新管道组件,用于接收来自数据处理组件的 input_dataset,并生成传递给 HyperparameterTuningJobRunOp 的 worker_pool_specs。这样,数据预处理和核心 HPT 模块就如下面的截图所示相关联。值得注意的是,training_data_schema、label 和 features 也被传递,因为它们是 Vertex AI 管道示例 的训练脚本所需的。
@component
def worker_pool_specs(project_id: str,
data_region: str,
data_pipeline_root: str,
hpt_container_image_uri: str,
custom_job_service_account: str,
input_dataset: Input[Dataset],
input_data_schema: str) -> list:
"""
Pass the preprocessed data uri to hpt as a worker pool argument. The vanilla hpt API
doesn't support 'input data' so it's done this way.
data_preprocess -> dataset.uri -> CMDARGS -> worker_pool_specs -> hpt
"""
display_name = 'hpt-pipeline-template'
fields = [field.split(':')[0] for field in input_data_schema.split(';')]
label = fields[-1]
features = ','.join(fields[0:-1])
CMDARGS = [
"--training_data_uri="+str(input_dataset.uri),
"--training_data_schema="+input_data_schema,
"--label="+label,
"--features="+features
]
# The spec of the worker pools including machine type and Docker image
worker_pool_specs = [
{
"machine_spec": {
"machine_type": "n1-standard-4",
},
"replica_count": 1,
"container_spec": {"image_uri": hpt_container_image_uri, "args": CMDARGS},
}
]
return worker_pool_specs
通过 worker-pool-specs 组件将预处理链入 HPT
2. 优化 HPT 结果收集
在原始的 HPT 管道示例 中,HPT 模块的输出,即 HPT 作业的资源名称,被传递给 GetTrialsOp 模块以检索所有超参数及其得分,从而让 GetBestTrialOp 模块找到最佳的,如下所示。
tuning_op = HyperparameterTuningJobRunOp(
display_name=display_name,
project=project,
location=region,
worker_pool_specs=worker_pool_specs,
study_spec_metrics=study_spec_metrics,
study_spec_parameters=study_spec_parameters,
max_trial_count=max_trial_count,
parallel_trial_count=parallel_trial_count,
base_output_directory=base_output_directory,
)
trials_op = hyperparameter_tuning_job.GetTrialsOp(
gcp_resources=tuning_op.outputs["gcp_resources"]
)
best_trial_op = hyperparameter_tuning_job.GetBestTrialOp(
trials=trials_op.output, study_spec_metrics=study_spec_metrics
)
当前,GetTrialsOp 模块将所有 HPT 试验的结果编码为一个字符串,如下所示。
GetTrialsOp 的示例输出
当搜索空间很大时,实际观察到的一个风险是,这个长字符串可能会违反 GetBestTrialOp docker 容器的输入参数长度限制。
job_spec.worker_pool_specs[0].container_spec.args; 消息:容器参数应包含少于 100k 字符
为了避免这个限制,尝试了一种有些绕但有效的方法,尽管可能还有更好的选择。基本上,将 GetTrialsOp 的源代码(见 hyperparameter_tuning_job)注入到 GetBestTrialOp 的源代码中,使这两个管道组件合并为一个,以避免将长字符串作为 docker 输入传递。
@component(
packages_to_install=['google-cloud-aiplatform',
'google-cloud-pipeline-components',
'protobuf'], base_image='python:3.7')
def GetBestTrialOp(gcp_resources: str, study_spec_metrics: list) -> str:
from google.cloud import aiplatform
from google_cloud_pipeline_components.proto.gcp_resources_pb2 import GcpResources
from google.protobuf.json_format import Parse
from google.cloud.aiplatform_v1.types import study
api_endpoint_suffix = '-aiplatform.googleapis.com'
gcp_resources_proto = Parse(gcp_resources, GcpResources())
gcp_resources_split = gcp_resources_proto.resources[0].resource_uri.partition(
'projects')
resource_name = gcp_resources_split[1] + gcp_resources_split[2]
prefix_str = gcp_resources_split[0]
prefix_str = prefix_str[:prefix_str.find(api_endpoint_suffix)]
api_endpoint = prefix_str[(prefix_str.rfind('//') + 2):] + api_endpoint_suffix
client_options = {'api_endpoint': api_endpoint}
job_client = aiplatform.gapic.JobServiceClient(client_options=client_options)
response = job_client.get_hyperparameter_tuning_job(name=resource_name)
trials = [study.Trial.to_json(trial) for trial in response.trials]
if len(study_spec_metrics) > 1:
raise RuntimeError('Unable to determine best parameters for multi-objective'
' hyperparameter tuning.')
trials_list = [study.Trial.from_json(trial) for trial in trials]
best_trial = None
goal = study_spec_metrics[0]['goal']
best_fn = None
if goal == study.StudySpec.MetricSpec.GoalType.MAXIMIZE:
best_fn = max
elif goal == study.StudySpec.MetricSpec.GoalType.MINIMIZE:
best_fn = min
best_trial = best_fn(
trials_list, key=lambda trial: trial.final_measurement.metrics[0].value)
return study.Trial.to_json(best_trial)
GetTrialsOp 注入到 GetBestTrialOp 中成为一个组件
3. 将最佳参数保存到 firestore
在 HPT 管道示例 中,每个 HPT 试验保存其训练模型,并且最佳模型会在后续部署。然而,这种将 HPT 和模型训练耦合在一起的方法暴露了一些限制:
-
部署的模型是在一次 HPT 试验期间训练的。然而,实际操作中并非所有训练都需要 HPT。例如,使用矩阵分解构建的推荐系统。该模型需要频繁地使用最新的用户-项目交互数据进行训练,但 HPT 并不总是需要。因此,解耦训练和 HPT 的选项是必要的。
-
直接部署 HPT 模型可能会导致偏倚评估,因为 HPT 是基于验证数据的。
为此,与其保存训练后的模型,更倾向于将最佳 HPT 结果保存到 Firestore 等数据库以备后用。存储最佳超参数后,模型训练和 HPT 被解耦。最佳参数可以在需要新的 HPT 轮次之前重复用于训练模型。此外,通过在训练模型时添加单独的测试集,可以改进模型评估。
以下代码演示了如何将最佳 HPT 结果保存到 Firestore。具体来说,定义了一个名为 best_hpt_to_args 的管道组件,用于处理由之前讨论的 GetBestTrialOp 步骤找到的最佳超参数。Firestore 的存储结构需要根据具体情况决定。在这里,时间戳用于标记 HPT 管道。最后,此函数返回字符串“true”,这是管道条件所偏好的,以启动稍后讨论的条件模型训练。为了可观察性,验证准确率也被记录,但这是完全可选的。
@component(packages_to_install=['google-cloud-firestore==2.3'])
def best_hpt_to_args(hpt_best: str,
project_id: str,
solution_name: str) -> str:
"""
Write the best hpt params to firestore.
We keep the output to chain this component to the conditional training
"""
import json
from datetime import datetime
from google.cloud import firestore
hpt_best = json.loads(hpt_best.replace("'", '"'))
hpt_best_dict = {}
for i in hpt_best['parameters']:
hpt_best_dict.update({i['parameterId']: i['value']})
for i in hpt_best['finalMeasurement']['metrics']:
hpt_best_dict.update({i['metricId']: i['value']})
db = firestore.Client(project=project_id)
task_flag=datetime.now().strftime('%Y-%m-%d %H:%M:%S')
db.collection(solution_name).document(task_flag).set(hpt_best_dict,merge=True)
return "true"
保存 HPT 结果的 Firestore 示例
将最佳 HPT 结果保存到 Firestore
4. 使用最佳超参数训练模型
最后,HPT 完成了。我做的最后一个改进是添加了一个条件训练任务,以便利用最新的 HPT 最佳超参数立即更新生产中的模型。这一步是完全可选的,取决于具体的使用案例。值得注意的是,这个条件接收的是 hpt_op.output,这是一个函数,封装了从 worker_pool_specs 到 best_hpt_to_args 的所有 HPT 组件,因此它的输出等于 best_hpt_to_args 的输出。详情请参见笔记本。
with dsl.Condition(
hpt_op.output=="true",
name="train_model"
):
train_task = train_op(
project_id=project_id,
data_region=data_region,
data_pipeline_root=data_pipeline_root,
input_data_schema=training_data_schema,
training_container_image_uri=training_container_image_uri,
train_additional_args=train_additional_args,
serving_container_image_uri=serving_container_image_uri,
custom_job_service_account=custom_job_service_account,
input_dataset=preprocess_task.outputs['output_dataset'],
machine_type=machine_type,
accelerator_count=accelerator_count,
accelerator_type=accelerator_type,
hptune_region=hptune_region,
hp_config_max_trials=hp_config_max_trials,
hp_config_suggestions_per_request=hp_config_suggestions_per_request,
vpc_network=vpc_network)
条件训练
在训练脚本(images/training/app.py)中,实现了一个名为 get_best_param_values 的函数,用于通过查询 Firestore 收集最新的 HPT 结果。根据标记 HPT 管道的不同方式,可能会有不同的方法来收集感兴趣的 HPT 结果。收集的超参数形式为字典,因此可以轻松用于训练模型。
def get_best_param_values(project_id, solution_name='hpt-pipeline-template'):
db = firestore.Client(project=project_id)
docs = db.collection(solution_name).list_documents()
doc_latest = max([doc.id for doc in docs])
params_latest = db.collection(solution_name).document(doc_latest).get().to_dict()
logging.info(f'Latest doc id {doc_latest}: {params_latest}')
return params_latest
best_param_values = get_best_param_values(project_id=args.hp_config_gcp_project_id)
总结
Vertex AI 管道在 GCP 上提供了一个出色的平台,用于以高性能和灵活性将 ML 解决方案生产化。然而,现有教程对于如何实现分布式 HPT 的覆盖面有限。为填补这一空白,本文展示了将分布式 GCP HPT 模块成功集成到现有 Vertex AI 管道中的尝试。具体而言,现有教程忽视的四个局限性已被解决:
-
数据输入。这将允许用户即时使用预处理的数据进行 HPT。
-
HPT 结果收集。优化的结果收集能够探索更大的搜索空间。
-
HPT 结果存储。将 HPT 结果保存在 Firestore 中意味着训练和 HPT 可以解耦。
-
使用最佳 HPT 结果进行模型训练。现在我们可以使用保存的 HPT 结果来训练新模型。
以上讨论的改进预计将大大有利于 Vertex AI 管道在需要涉及完全自动化分布式 HPT 的工业应用案例中,以优化运行中的 ML 解决方案的预测能力。有关详细的端到端实现,请访问笔记本,并随时通过LinkedIn与我联系。
感谢您的阅读!
除非另有说明,否则所有图片均为作者提供。
在 CPU 上分布式运行 Llama 2
一个使用 Python 在普通硬件上进行批量推理的玩具示例,通过 llama.cpp 和 PySpark。
·
关注 发表于 Towards Data Science ·6 分钟阅读·2023 年 8 月 2 日
–
作者通过 DALL-E 制作的图像
为什么?
本练习旨在使用 Llama 2,一个来自 Meta AI 的 LLM(大型语言模型),一次性总结多个文档。大规模的非结构化、半结构化和结构化文本摘要可以作为 一个独立的特性, 也可以作为数据管道的一部分,为下游机器学习模型提供数据。
具体来说,我们希望证明以下情况的同时可行性:
-
在 CPUs 上运行 Llama 2(即,去除 GPU 能力限制)
-
与 Apache Spark 的平滑集成(大数据生态系统的关键部分)
-
不使用第三方端点(即,由于隔离基础设施或保密要求,模型必须在本地运行)
如何?
很多艰巨的工作已经为我们完成了!
llama.cpp 项目 通过减少其数值权重的分辨率(“量化”)来实现 LLM 在 CPU 上的 简化 运行。这些现成的模型文件易于获取。
接下来,llama-cpp-python 绑定提供了从 Python 内部使用 llama.cpp 的简单方法。
最后,Spark 的 applyInPandas()
(文档) 可以将巨大的数据源分割成 Pandas 大小的块并独立处理。请注意,如果向量化的 Spark 函数能够实现相同的结果,这种方法 可能 是一种反模式,但在我们的案例中,我们基本上是将 Spark 作为一个简单的协调器来扩展我们对 llama.cpp 的使用。可能还有更高效的批处理方式来使用 llama.cpp,但考虑到 Spark 的容错和可扩展性的简易性和自动化好处,这种方式很有吸引力。
计划
创意共享许可证 (CC BY-SA 3.0)
作为一个有趣的测试,我们将使用 Llama 2 来总结列夫·托尔斯泰的《战争与和平》,这是一本超过 1200 页的小说,包含超过 360 章。我们将把每章当作一个文档。注意,Llama 2 已经“知道”这部小说;询问它关于一个关键角色会生成以下输出(使用 llama-2–7b-chat.ggmlv3.q8_0.bin
):
Prompt:
Briefly describe the character Anna Pavlovna from 'War and Peace'
Response:
Anna Pavlovna is a major character in Leo Tolstoy's novel "War and Peace".
She is a wealthy, intelligent, and charming socialite who is known for her beauty and wit.
Despite her beauty and charm, she is also portrayed as being manipulative and cunning, often using her intelligence and wit to get what she wants.
Throughout the novel, Anna Pavlovna is shown to be a complex and multifaceted character, with both admirable and flawed qualities.
She is particularly close to the main character Pierre Bezukhov, and plays an important role in his personal and emotional development.
步骤:
-
安装 7B 量化聊天模型和 llama-cpp-python。
-
下载小说,按章节分割,创建一个 Spark
DataFrame
。 -
按章节分区并生成摘要。
安装
配置 Spark 集群超出了我们的范围;我将假设你已经在本地、通过托管服务(如 Synapse 或 Elastic Map Reduce)或像 Kubernetes 这样的自定义部署中运行了 Spark。
有两个工件需要安装在所有 工作节点 上,无论这些节点是物理机器、虚拟机还是无服务器池中的容器:
-
GGML 格式的 LLama 2 模型(位于
/models
) -
llama-cpp-python 模块(通过
pip
安装)
我们使用的是 Llama 2 的 7B 聊天 “Q8” 版本,可以在 这里 找到。下载链接可能会变化,但单节点的“裸金属”设置类似于下面的内容:
确保您可以通过 python3
使用模型,并查看 这个示例。总结一下,每个 Spark 上下文必须能够从 /models
读取模型并访问 llama-cpp-python 模块。
处理小说文本
以下 Bash 命令下载小说并打印字数。
接下来,我们在 Python 中读取文本文件,去除 Project Gutenberg 的头部和尾部。我们将通过正则表达式 CHAPTER .+
进行分割,创建一个章节字符串的列表,并从中创建一个 Spark DataFrame
(此代码假设有一个名为 spark
的 SparkSession
)。
代码应该产生以下输出:
number of chapters = 365
max words per chapter = 3636
+------------------------------------------------------------+-------+
| text|chapter|
+------------------------------------------------------------+-------+
|\n\n“Well, Prince, so Genoa and Lucca are now just family...| 1|
|\n\nAnna Pávlovna’s drawing room was gradually filling. T...| 2|
|\n\nAnna Pávlovna’s reception was in full swing. The spin...| 3|
|\n\nJust then another visitor entered the drawing room: P...| 4|
|\n\n“And what do you think of this latest comedy, the cor...| 5|
|\n\nHaving thanked Anna Pávlovna for her charming soiree,...| 6|
|\n\nThe rustle of a woman’s dress was heard in the next r...| 7|
|\n\nThe friends were silent. Neither cared to begin talki...| 8|
|\n\nIt was past one o’clock when Pierre left his friend. ...| 9|
|\n\nPrince Vasíli kept the promise he had given to Prince...| 10|
+------------------------------------------------------------+-------+
太好了!现在我们有了一个包含 365 行的 DataFrame
,每行都包含完整的章节文本和编号。最后一步是创建一个包含每章总结的新 DataFrame
。
Spark 处理
以下是生成 单章总结 的 Python 代码(请参见对 limit(1)
的调用以返回单行)。代码片段下方有解释:
llama2_summarize()
函数是 Spark 按 每组 应用的代码。由于我们按 chapter
列分组,该函数会在每一章的行上调用;df
参数仅仅是一个 Pandas DataFrame
,其中包含一行数据。请注意,我们在 每次调用 llama2_summarize()
时都会读取模型;这是为了简便采用的快捷方式,但效率并不是很高。
最后,使用 Spark 我们进行 groupby()
并调用 applyInPandas()
,设置模式以包含章节总结和编号。
输出(为了可读性进行重新格式化)如下所示:
summary
The chapter is about a conversation between Prince Vasíli Kurágin and
Anna Pávlovna Schérer, a well-known socialite and favorite
of Empress Márya Fëdorovna.
They are discussing various political matters, including the possibility
of war with France and Austria's role in the conflict.
Prince Vasíli is hoping to secure a post for his son through
the Dowager Empress, while Anna Pávlovna is enthusiastic
about Russia's potential to save Europe from Napoleon's tyranny.
The conversation also touches on personal matters,
such as Prince Vasíli's dissatisfaction with his younger son
and Anna Pávlovna's suggestion that he marry off
his profligate son Anatole to a wealthy heiress.
chapter
1
(请注意使用 拿破仑,尽管它在章节中并不存在!再说一次,这是一个有趣的练习,而不是使用真正未见过的文档的现实示例。)
这个单章测试的运行时间约为 2 分钟,在一个 64 核心的虚拟机上。我们略过了许多影响运行时间的选择,例如模型大小/量化和模型参数。关键结果是,通过适当扩展我们的 Spark 集群,我们可以在几分钟内总结 所有 章节。因此,使用由廉价虚拟机组成的大型 Spark 集群,每天处理数十万(甚至百万!)的文档是可能的。
总结
我们甚至没有提到调整标准 LLM 参数如 temperature
和 top_p
,这些参数控制结果的“创造力”和随机性,或者 提示工程,这几乎是一门独立的学科。我们也选择了 Llama 2 7B 模型而没有说明理由;可能还有更小且性能更好的模型或模型系列,更适合我们的特定用例。
相反,我们展示了如何使用 Spark 轻松分发(量化)LLM 工作负载,只需付出相当少的努力。接下来的步骤可能包括:
-
更高效的模型加载/缓存
-
针对不同使用场景的参数优化
-
自定义提示