‘壹’ 关于不完全动态信息博弈的正规表述,举例说明博弈树方法
这种的话一般可能就是自己怎么去博弈,有一个正规的方法,你根据自己的逻辑世卫去参考吧。
一、启发式搜索策略即为结点排序技术。α-β搜索/剪枝算法的剪枝效率对同一结点下的孩子结点的排列顺序非常敏感,这些结点的排列越理想,则剪枝越早发生,需要展开和估值的结点数越少,算法效率越高,反之搜索效率越低。
二、虽然无法事先预知哪个结点是最佳结点,但却可以采取一些措施,让能产生剪枝的几率越高的结点越排在前面,以优先搜索,使博弈树尽可能接近最小树。
三、在博弈过程中,任何一方都希望自己取得胜利。因此,在某一方当前有多个行动方案可供选择时,他总是挑选对自己最为有利而对者迟缓对方最为不利的那个行动方案。首模此时,如果我们站在A方的立场上,则可供A方选择的若干行动方案之间是“或”关系,因为主动权操在A方手里,他或者选择这个行动方案,或者选择另一个行动方案,完全由A方决定。
四、若B方也有若干个可供选择的行动方案,则对A方来说这些行动方案之间是“与”关系,因为这时主动权操在B方手里,这些可供选择的行动方案中地任何一个都可能被B方选中,A方必须考虑到对自己最不利的情况发生。
五、若把上述博弈过程用图表示出来,得到的是一棵“与/或”树。这里要特别指出,该“与/或”树是始终站在某一方(例如A方)的立场上得出的,决不可一会儿站在这一方的立场上,一会儿又站在另一方的立场上。
六、在博弈问题中,每一个格局可供选择的行动方案都有很多,因此会生成十分庞大的博弈树。据统计西洋跳棋完整的博弈树约有1040个节点。试图利用完整的博弈树来进行极大极小分析是困难的。可行的办法是只生成一定深度的博弈树,然后进行极大极小分析,找出当前最好的行动方案。以中国象棋为例,在开局状态下,最初搜索时,置换表启发、历史启发、杀手启发这些动态启发起的作用很小甚至来不及起作用,此时吃子启发起的作用较大。因此,在着法生成时,考虑首先生成车、马、炮的着法,最后生成帅的着法,往往旦友是很有效的。
‘贰’ 剪枝算法怎么弄 大数取模
α-β剪枝技术
首先分析极小极大分析法效率,上述的极小极大分析法,实际是先生成一棵博弈树,然后再计算其倒推值,至使极小极大分析法效率较低。于是在极小极大分析法的基础上提出了α-β剪枝技术。
α-β剪枝技术的基本思想或算法是,边生成博弈树边计算评估各节点的倒推值,并且根据评估出的倒推值范围,及时停止扩展那些已无必要再扩展的子节点,即相当于剪去了博弈树上的一些分枝,从而节约了机器开销,提高了搜索效率。具体的剪枝方法如下:
(1) 对于一个与节点MIN,若能估计出其倒推值的上确界β,并且这个β值不大于 MIN的父节点(一定是或节点)的估计倒推值的下确界α,即α≥β,则就不必再扩展该 MIN节点的其余子节点了(因为这些节点的估值对MIN父节点的倒推值已无任何影响 了)。这一过程称为α剪枝。
(2) 对于一个或节点MAX,若能估计出其倒推值的下确界α,并且这个α值不小于 MAX的父节点(一定是与节点)的估计倒推值的上确界β,即α≥β,则就不必再扩展该MAX节点的其余子节点了(因为这些节点的估值对MAX父节点的倒推值已无任何影响 了)。这一过程称为β剪枝。
‘叁’ 极大极小算法有些不明白
先来说极小极大算法主要应用于什么样的游戏:
1. 零和游戏(Zero-sum Game):意思就是你死我活,一方的胜利代表另一方的失败,比如,象棋,五子棋等。
2. 完全信息(Perfect Information):玩家知道之前所有的步骤。象棋就是完全信息,因为玩家是交替着落子,且之前的步骤都能在棋盘上体现,但是石头剪子布就不是。
这样的游戏通常可以把他们看作一个树状图,把每一种可能性列出来。比如下面这个井字棋游戏,Max代表你自己,Min代表你的对手。
这个时候我们需要给每一种结果一个分数,就是这里的Utility。这个分数是站在我自己(也就是Max)的角度评估的,比如上图中我赢了就是+1,输了是-1,平局时0。所以,我希望最大化这个分数,而我的对手希望最小化这个分数。(在游戏中,这个分数被称为static value。)这里要说一下,井字棋是个比较简单的游戏,所以可以列出所有可能的结果。但是,大部分游戏是不太可能把所有结果都列出来的。根据计算机运算量,我们可能只能往前推7,8步,所以这个时候分数就不只-1,1,0这么简单了,会有专门的算法来根据当前结果给不同的分数。
假设我们有如下图的游戏,我是先手,我应该如何利用Minmax算法来选出第一步怎么走呢?
当然对于一个复杂的游戏来说,比如象棋,肯定是需要非常多步才能完成的。这就导致结果的数量是成几何增长的,也就是说,如果这个游戏每一步都有n个选择,那么在x步以后,将会有n^x个选择。这个时候,我们就需要采取剪枝算法(Alpha-Beta)来减少运算量。从剪枝算法这个名字我们就能看出,这个算法能让我们剪掉树状图中的一些分支,从而减少运算量。在这里也说一下剪枝算法,因为这并不是个不同于极小极大的算法,而是极小极大算法的升级版。
我们将游戏简化成如下图,使用Minimax算法,我们可以得出这样的结果
‘肆’ 10、填空在AlphaBeta剪枝算法中,我们把一个结点可能取值的上界记作____值
这个问题问的不是很清楚,个人理解,在AlphaBeta剪枝算法中,可以把一个节点可能取值的上界记作 Beta 值。
AlphaBeta剪枝算法是对极大极小算法的优化,效率更高。极大极小是一种暴力搜索策略,需要遍历所有可能的情况,随着节点数特别是深度的增加,算法性能会大幅下降。AlphaBeta剪枝算法采用递归的方式进行倒推估算,可以在搜索过程中剪除无用的分支,从而减少不必要的搜索(这些搜索中不会有满足要求的答案),提升算法的效率。
可以这样简单地理解吧,每一层的节点都有Alpha(下界)、Beta(上界),而且是动态调整的,如果在推导过程中发现 Alpha>=Beta,那么就可以终止当前节点往下各层级的搜索,达到提高效率的目的。
‘伍’ 如何等分上宽下窄桶的容积
要算水桶的容积,只需量出水桶口和水桶底的直径,和水桶的高度,然后算出水桶中间的直径即(桶口直径+桶底直径)/2。最后按3.14✘(水桶中间直径/2)的平方✘高就可以计算出水桶的容积有一个容积为8升的水桶里装满了水,另外还有一个容积为3升的空桶和一个容积为5升的空桶,如何利用这两个空桶等分8升水?附加条件是三个水桶都没有体积刻度,也不能使用其它辅助容器。但是计算机并不能理解这个“1”的重要性,很难按照人类的思维方式按部就班地推导答案,因此用计算机解决这个问题,通常会选择使用“穷举法”。为什么使用穷举法?因为这不是一个典型意义上的求解最优解的问题,虽然可以暗含一个求解倒水次数最少的方法的要求,但就本质而言,常用的求解最优解问题的高效的方法都不适用于此问题。如果能够穷举解空间的全部合法解,然后通过比较找到最优解也是一种求解最优解的方法。不过就本题题意而言,并不关心什么方法最快,能求出全部等分水的方法可能更符合题意。
如果我们把某一时刻三个水桶中存水的容积称为一个状态,则问题的初始状态是8升的水桶装满水,求解的解出状态(最终状态)是8升水桶中4升水,5升水桶中4升水。穷举法的实质就是把从初始状态开始,根据某种状态变化的规则搜索全部可能的状态,每当找到一个从初始状态到最终状态的变化路径,就可以理解为找到了一种答案。这样的状态变化搜索的结果通常是得到一棵状态搜索树,根节点是初始状态,叶子节点可能是最终状态,也可能是某个无法转换到最终状态的中间状态,状态树有多少个最终状态的叶子节点,就有多少种答案。根据以上分析结果,解决本问题的算法关键有三点:首先,建立算法的状态模型;其次,确定状态树的搜索算法(暗含状态转换的规则);最后,需要一些提高算法效率的手段,比如应用“剪枝”条件避免重复的状态搜索,还要避免状态的循环生成导致搜索算法在若干个状态之间无限循环。建立状态模型是整个算法的关键,这个状态模型不仅要能够描述静止状态,还要能够描述并记录状态转换动作,尤其是对状态转换的描述,因为这会影响到状态树搜索算法的设计。所谓的静止状态,就是某一时刻三个水桶中存水的容积,我们采用长度为3的一维向量描述这个状态。这组向量的三个值分别是容积为8升的桶中的水量、容积为5升的桶中的水量和容积为3升的桶中的水量。因此算法的初始状态就可以描述为[8 ,0, 0],则终止状态为[4, 4, 0]。
对状态转换的描述就是在两个状态之间建立关联,在本算法中这个关联就是一个合法的倒水动作。某一此卖薯时刻三个水桶中的存水状态,经过某个倒水动作后演变到一个新的存水状态,这是对状态转换的文字描述,对算法来讲,倒水状态描述就是“静止状态”+“倒水动作”。我们用一个三元组来描述倒水动作:{from, to, water},from是指从哪个桶中倒水,to是指将水倒向哪个桶,water是此次倒水动作所倒的水量。本模型的特例就是第一个状态如何得到,也就是[8, 0, 0]这个状态对应的倒水动作如何描述?我们用-1表示未知的水桶编号(上帝水桶),因此第一个状态对应的倒水动作就是{-1, 1, 8}。应用本模型对前面提到的第一种解决方法进行状态转换描述确定了状态模型后,就需要解决算法面临的第二个问题:状态树的搜索算法。一个静止状态结合不同的倒水动作会迁移到不同的状态,所有状态转换所展示的就是一棵以状态[8, 0, 0]为根的状态搜索树,图(2)画出了这个状态搜索树的一部分,其中森者一个用不同颜色标识出来的状态转换过程(状态树的一个分支)就是本问题的一个解状态树的搜索就是对整个状态树进行遍历,这中间其实暗含了状态的生成,因为状态树一开始并不完整,只有一个初始状态的根节点,当搜索(也就是遍历)操作完成时,状态树才完整。树的遍历可以采用广度配核优先遍历算法,也可以采用深度优先遍历算法,就本题而言,要求解所有可能的等分水的方法,暗含了要记录从初始状态到最终状态,所以更适合使用深度优先遍历算法。状态树的遍历暗含了一个状态生成的过程,就是促使状态树上的一个状态向下一个状态转换的驱动过程,这是一个很重要的部分,如果不能正确地驱动状态变化,就不能实现状态树的遍历(搜索)。
建立状态模型一节中提到的动作模型,就是驱动状态变化的关键因子。对一个状态来说,它能转换到哪些新状态,取决于它能应用哪些倒水动作,一个倒水动作能够在原状态的基础上“生成”一个新状态,不同的倒水动作可以“生成”不同的新状态。由此可知,状态树遍历的关键是找到三个水桶之间所有合法的倒水动作,用这些倒水动作分别“生成”各自相应的新状态。遍历三个水桶的所有可能动作,就是对三个水桶任取两个进行全排列(常用的排列组合算法可以参考《排列组合算法》一文),共有6种水桶的排列组合,也就是说有6种可能的倒水动作。将这6种倒水动作依次应用到当前状态,就可以“生成”6种新状态,从而驱动状态发生变化(有些排列并不能组合出合法的倒水动作,关于这一点后面“算法优化”部分会介绍)。
状态搜索的核心是对三个水桶进行两两排列组合得到6种倒水动作,但是并不是每种倒水动作都是合法的,比如,需要倒出水的桶中没有水的情况和需要倒进水的桶中已经满的情况下,都组合不出合法的倒水动作。除此之外,因为水桶是没有刻度的,因此倒水动作也是受限制的,也就是说合法的倒水动作只能有两种结果:需要倒出水的桶被倒空和需要倒进水的桶被倒满。加上这些限制之后,每次组合其实只有少数倒水动作是合法的,可以驱动当前的状态到下一个状态。利用这一点,就可以对状态树进行“剪枝”,避免对无效(非法)的状态分支进行搜索。
除了通过“剪枝”提高算法效率,对于深度优先的状态搜索还需要防止因状态的循环生成造成深度优先搜索无法终止的问题。状态的循环生成有两种表现形式:一种是在两个桶之间互相倒水;另一种就是图(2)中展示的一个例子,[3, 5, 0] -> [3, 2, 3] -> [6, 2, 0] -> [3, 5, 0]形成一个状态环。要避免出现状态环,就需要记录一次深度遍历过程中所有已经搜索过的状态,形成一个当前搜索已经处理过的状态表,每当生成一个新状态,就先检查是否是状态表中已经存在的状态,如果是则放弃这个状态,回溯到上一步继续搜索。如果新状态是状态表中没有的状态,则将新状态加入到状态表,然后从新状态开始继续深度优先遍历。在这个过程中因重复出现被放弃的状态,可以理解为另一种形式的“剪枝”,可以使一次深度优先遍历很快收敛到初始状态。桶的容积:V=πr²h。圆面积是指圆形所占的平面空间大小,常用S表示。圆是一种规则的平面几何图形,其计算方法有很多种,比较常见的是开普勒的求解方法,卡瓦利里的求解方法等。圆的面积就是:圆的半径(r)的平方乘以π。
圆柱体的体积=圆桶的容积=πr²h=0.25πD²h(π=3.14,r是桶的半径,D是桶的直径,h是桶的高)
圆柱体积公式是用于计算圆柱体体积的公式。
圆柱体积=πr²h=S底面积×高(h)
先求底面积,然后乘高。
π是圆周率,一般取3.14
r是圆柱底面半径
h为圆柱的高
还可以是
v=1/2ch×r
侧面积的一半×半径
木桶底面是圆,底面积公式=πr²=0.25πD²(π=3.14,r是桶的半径,D是桶的直径)
容积和体积是不同的
1、含义不同。如一只铁桶的体积是指它外部所占空间部分的大小,而这只铁桶的容积却是指它内部容纳物体的多少。一种物体有体积,可不一定有容积。
2、测量方法不同。在计算物体的体积或容积前一般要先测量长、宽、高,求物体的体积是从该物体的外部来测量,而求容积却是从物体的内部来测量。一种既有体积又有容积的封闭物体,它的体积一定大于它的容积。
3、单位名称不完全相同。体积单位一般用:立方米、立方分米、立方厘米;固体的容积单位与体积单位相同,而液体和气体的体积与容积单位一般都用升、毫升。
4.公式:V长方体=abh(长× 宽× 高); V正方体=aaa(棱长× 棱长× 棱长);V圆柱=Sh;V圆锥=1/3sh。
5.计量液体的体积,如水、油等,常用容积单位升和毫升,也可以写成L和mL。
6.计算不规则的立体图形体积可以把这个物体放入水中,用现在容积-未放入物体的容积就是体积或用放入物体后高-未放入物体*长*宽(1升=1立方分米;1毫升=1立方厘米)
7.硬盘的容量是以MB(兆)和GB(千兆)为单位的计算机上的单个文件的大小就是容积了!(如:500kb的一个图片:是图片的容积为500kb)
‘陆’ 如何实现C#五子棋难度等级设置
要实现 C# 五子棋的难度等级设置,你需要对计算机 AI(人工智能)的思考深度进行调整。思考深度越深,计算机就会做出更优锋梁秀的决策,但也会增加计算机的计算负担和时间成本。
以下是实现 C# 五子棋难度等级设置的一般步骤银败运:
定义 AI 策略:定义计算机 AI 的下棋策略,例如优先攻击、优先防守、随机下棋等。这些策略会影响计算机决定每一步棋时所考虑的因素。
调整思考深度:根据难度等级的设定,调整计算机 AI 的思考深度。思枯樱考深度可以使用递归函数来实现,每次递归调用都模拟一次对手的落子,然后评估当前局面的得分,并选择最佳的下棋位置。
实现剪枝算法:在计算机 AI 的思考过程中,可以使用剪枝算法来减少搜索空间并提高计算效率。常见的剪枝算法包括alpha-beta剪枝、zobrist哈希等。
测试和优化:完成代码实现后,需要进行测试和优化,以确保计算机 AI 的行为合理且符合预期。可以通过与人类玩家或其他 AI 对战,或使用自动化测试工具进行测试和评估。
需要注意的是,五子棋是一个复杂的游戏,实现一个强大的计算机 AI 需要综合考虑多个因素,并进行反复的测试和优化。上述步骤仅供参考,具体实现方式可能因应用场景和需求不同而有所差异。
‘柒’ 衡量算法效率的方法与准则
算法效率与分析
数据结构作为程序设计的基础,其对算法效率的影响必然是不可忽视的。本文就如何合理选择数据结构来优化算法这一问题,对选择数据结构的原则和方法进行了一些探讨。首先对数据逻辑结构的重要性进行了分析,提出了选择逻辑结构的两个基本原则;接着又比较了顺序和链式两种存储结构的优点和缺点,并讨论了选择数据存储结构的方法;最后本文从选择数据结构的的另一角度出发,进一步探讨了如何将多种数据结构进行结合的方法。在讨论方法的同时,本文还结合实际,选用了一些较具有代表性的信息学竞赛试题举例进行了分析
【正文】一、引论
“数据结构+算法=程序”,这就说明程序设计的实质就是对确定的问题选择一种合适的数据结构,加上设计一种好的算法。由此可见,数据结构在程序设计中有着十分重要的地位。
数据结构是相互之间存在一种或多种特定关系的数据元素的集合。因为这其中的“关系”,指的是数据元素之间的逻辑关系,因此数据结构又称为数据的逻辑结构。而相对于逻辑结构这个比较抽象的概念,我们将数据结构在计算机中的表示又称为数据的存储结构。
建立问题的数学模型,进而设计问题的算法,直至编出程序并进行调试通过,这就是我们解决信息学问题的一般步骤。我们要建立问题的数学模型,必须首先找出问题中各对象之间的关系,也就是确定所使用的逻辑结构;同时,设计算法和程序实现的过程,必须确定如何实现对各个对象的操作,而操作的方法是决定于数据所采用的存储结构的。因此,数据逻辑结构和存储结构的好坏,将直接影响到程序的效率。
二、选择合理的逻辑结构
在程序设计中,逻辑结构的选用就是要分析题目中的数据元素之间的关系,并根据这些特定关系来选用合适的逻辑结构以实现对问题的数学描述,进一步解决问题。逻辑结构实际上是用数学的方法来描述问题中所涉及的操作对象及对象之间的关系,将操作对象抽象为数学元素,将对象之间的复杂关系用数学语言描述出来。
根据数据元素之间关系的不同特性,通常有以下四种基本逻辑结构:集合、线性结构、树形结构、图状(网状)结构。这四种结构中,除了集合中的数据元素之间只有“同属于一个集合”的关系外,其它三种结构数据元素之间分别为“一对一”、“一对多”、“多对多”的关系。
因此,在选择逻辑结构之前,我们应首先把题目中的操作对象和对象之间的关系分析清楚,然后再根据这些关系的特点来合理的选用逻辑结构。尤其是在某些复杂的问题中,数据之间的关系相当复杂,且选用不同逻辑结构都可以解决这一问题,但选用不同逻辑结构实现的算法效率大不一样。
对于这一类问题,我们应采用怎样的标准对逻辑结构进行选择呢?
下文将探讨选择合理逻辑结构应充分考虑的两个因素。
一、 充分利用“可直接使用”的信息。
首先,我们这里所讲的“信息”,指的是元素与元素之间的关系。
对于待处理的信息,大致可分为“可直接使用”和“不可直接使用”两类。对于“可直接使用”的信息,我们使用时十分方便,只需直接拿来就可以了。而对于“不可直接使用”的这一类,我们也可以通过某些间接的方式,使之成为可以使用的信息,但其中转化的过程显然是比较浪费时间的。
由此可见,我们所需要的是尽量多的“可直接使用”的信息。这样的信息越多,算法的效率就会越高。
对于不同的逻辑结构,其包含的信息是不同的,算法对信息的利用也会出现不同的复杂程度。因此,要使算法能够充分利用“可直接使用”的信息,而避免算法在信息由“不可直接使用”向“可直接使用”的转化过程中浪费过多的时间,我们必然需要采用一种合理的逻辑结构,使其包含更多“可直接使用”的信息。
〖问题一〗 IOI99的《隐藏的码字》。
〖问题描述〗
问题中给出了一些码字和一个文本,要求编程找出文本中包含这些码字的所有项目,并将找出的项目组成一个最优的“答案”,使得答案中各项目所包含的码字长度总和最大。每一个项目包括一个码字,以及该码字在文本中的一个覆盖序列(如’abcadc’就是码字’abac’的一个覆盖序列),并且覆盖序列的长度不超过1000。同时,“答案”要求其中每个项目的覆盖序列互相没有重叠。
〖问题分析〗
对于此题,一种较容易得出的基本算法是:对覆盖序列在文本中的终止位置进行循环,再判断包含了哪些码字,找出所有项目,并最后使用动态规划的方法将项目组成最优的“答案”。
算法的其它方面我们暂且不做考虑,而先对问题所采用的逻辑结构进行选择。
如果我们采用线性的逻辑结构(如循环队列),那么我们在判断是否包含某个码字t时,所用的方法为:初始时用指针p指向终止位置,接着通过p的不断前移,依次找出码字t从尾到头的各个字母。例如码字为“ABDCAB”,而文本图1-1,终止位置为最右边的箭头符号,每个箭头代表依次找到的码字的各个字母。
指针p的移动方向
A B D C A B
C D A C B D C A D C D B A D C C B A D
图1-1
由于题目规定码字的覆盖序列长度不超过1000,所以进行这样的一次是否包含的判断,其复杂度为O(1000)。
由于码字t中相邻两字母在文本中的位置,并非只有相邻(如图1-1中的’D’和’C’)这一种关系,中间还可能间隔了许多的字母(如图1-1中’C’和’A’就间隔了2个字母),而线性结构中拥有的信息,仅仅只存在于相邻的两元素之间。通过这样简单的信息来寻找码字的某一个字母,其效率显然不高。
如果我们建立一个有向图,其中顶点i(即文本的第i位)用52条弧分别连接’a’..’z’,’A’..’Z’这52个字母在i位以前最后出现的位置(如图1-2的连接方式),我们要寻找码字中某个字母的前一个字母,就可以直接利用已连接的边,而不需用枚举的方法。我们也可以把问题看为:从有向图的一个顶点出发,寻找一条长度为length(t)-1的路径,并且路径中经过的顶点,按照码字t中的字母有序。
C D A C B D C A D C D B A D C C B A D
图1-2
通过计算,用图进行记录在空间上完全可以承受(记录1000个点×52条弧×4字节的长整型=200k左右)。在时间上,由于可以充分利用第i位和第i+1位弧的连接方式变化不大这一点(如图1-2所示,第i位和第i+1位只有一条弧的指向发生了变化,即第i+1位将其中一条弧指向了第i位),所以要对图中的弧进行记录,只需对弧的指向进行整体赋值,并改变其中的某一条弧即可。
因此,我们通过采用图的逻辑结构,使得寻找字母的效率大大提高,其判断的复杂度为O(length(t)),最坏为O(100),比原来方法的判断效率提高了10倍。
(附程序codes.pas)
对于这个例子,虽然用线性的数据结构也可以解决,但由于判断的特殊性,每次需要的信息并不能从相邻的元素中找到,而线性结构中只有相邻元素之间存在关系的这一点,就成为了一个很明显的缺点。因此,问题一线性结构中的信息,就属于“不可直接使用”的信息。相对而言,图的结构就正好满足了我们的需要,将所有可能产生关系的点都用弧连接起来,使我们可以利用弧的关系,高效地进行判断寻找的过程。虽然图的结构更加复杂,但却将“不可直接使用”的信息,转化成为了“可直接使用”的信息,算法效率的提高,自然在情理之中。。
二、 不记录“无用”信息。
从问题一中我们看到,由于图结构的信息量大,所以其中的信息基本上都是“可用”的。但是,这并不表示我们就一定要使用图的结构。在某些情况下,图结构中的“可用”信息,是有些多余的。
信息都“可用”自然是好事,但倘若其中“无用”(不需要)的信息太多,就只会增加我们思考分析和处理问题时的复杂程度,反而不利于我们解决问题了。
〖问题二〗 湖南省1997年组队赛的《乘船问题》
〖问题描述〗
有N个人需要乘船,而每船最多只能载两人,且必须同名或同姓。求最少需要多少条船。
〖问题分析〗
看到这道题,很多人都会想到图的数据结构:将N个人看作无向图的N个点,凡同名或同姓的人之间都连上边。
要满足用船最少的条件,就是需要尽量多的两人共乘一条船,表现在图中就是要用最少的边完成对所有顶点的覆盖。这就正好对应了图论的典型问题:求最小边的覆盖。所用的算法为“求任意图最大匹配”的算法。
使用“求任意图最大匹配”的算法比较复杂(要用到扩展交错树,对花的收缩等等),效率也不是很高。因此,我们必须寻找一个更简单高效的方法。
首先,由于图中任两个连通分量都是相对独立的,也就是说任一条匹配边的两顶点,都只属于同一个连通分量。因此,我们可以对每个连通分量分别进行处理,而不会影响最终的结果。
同时,我们还可以对需要船只s的下限进行估计:
对于一个包含Pi个顶点的连通分量,其最小覆盖边数显然为[Pi/2]。若图中共有L个连通分量,则s=∑[Pi/2](1<=i<=L)。
然后,我们通过多次尝试,可得出一个猜想:
实际需要的覆盖边数完全等于我们求出的下限∑[Pi/2](1<=i<=L)。
要用图的结构对上述猜想进行证明,可参照以下两步进行:
1. 连通分量中若不存在度为1的点,就必然存在回路。
2. 从图中删去度为1的点及其相邻的点,或删去回路中的任何一边,连通分量依然连通,即连通分量必然存在非桥边。
由于图的方法不是这里的重点,所以具体证明不做详述。而由采用图的数据结构得出的算法为:每次输出一条非桥的边,并从图中将边的两顶点删去。此算法的时间复杂度为O(n3)。(寻找一条非桥边的复杂度为O(n2),寻找覆盖边操作的复杂度为O(n))
由于受到图结构的限制,时间复杂度已经无法降低,所以如果我们要继续对算法进行优化,只有考虑使用另一种逻辑结构。这里,我想到了使用二叉树的结构,具体说就是将图中的连通分量都转化为二叉树,用二叉树来解决问题。
首先,我们以连通分量中任一个顶点作为树根,然后我们来确定建树的方法。
1. 找出与根结点i同姓的点j(j不在二叉树中)作为i的左儿子,再以j为树根建立子树。
2. 找出与根结点i同名的点k(k不在二叉树中)作为i的右儿子,再以k为树根建立子树。
如图2-1-1中的连通分量,我们通过上面的建树方法,可以使其成为图2-1-2中的二叉树的结构(以结点1为根)。(两点间用实线表示同姓,虚线表示同名)
图2-1-2
图2-1-1
接着,我就来证明这棵树一定包含了连通分量中的所有顶点。
【引理2.1】
若二叉树T中包含了某个结点p,那么连通分量中所有与p同姓的点一定都在T中。
证明:
为了论证的方便,我们约定:s表示与p同姓的顶点集合;lc[p,0]表示结点p,lc[p,i](i>0)表示lc[p,i-1]的左儿子,显然lc[p,i]与p是同姓的。
假设存在某个点q,满足qs且qT。由于s是有限集合,因而必然存在某个lc[p,k]无左儿子。则我们可以令lc[p,k+1]=q,所以qT,与假设qT相矛盾。
所以假设不成立,原命题得证。
由引理2.1的证明方法,我们同理可证引理2.2。
【引理2.2】
若二叉树T中包含了某个结点p,那么连通分量中所有与p同名的点一定都在T中。
有了上面的两个引理,我们就不难得出下面的定理了。
【定理一】
以连通分量中的任一点p作为根结点的二叉树,必然能够包含连通分量中的所有顶点。
证明:
由引理2.1和引理2.2,所有与p同姓或同名的点都一定在二叉树中,即连通分量中所有与p有边相连的点都在二叉树中。由连通分量中任两点间都存在路径的特性,该连通分量中的所有点都在二叉树中。
在证明二叉树中包含了连通分量的所有顶点后,我们接着就需要证明我们的猜想,也就是下面的定理:
【定理二】包含m个结点的二叉树Tm,只需要船的数量为boat[m]=[m/2](mN)。
证明:
(i) 当m=1,m=2,m=3时命题显然成立。
图2-2-1
图2-2-2
图2-2-3
(ii) 假设当m<k(k>3)时命题成立,那么当m=k时,我们首先从树中找到一个层次最深的结点,并假设这个结点的父亲为p。那么,此时有且只有以下三种情况(结点中带有阴影的是p结点):
(1) 如图2-2-1,p只有一个儿子。此时删去p和p唯一的儿子,Tk就成为了Tk-2,则boat[k]=boat[k-2]+1=[(k-2)/2]+1=[k/2]。
(2) 如图2-2-2,p有两个儿子,并且p是其父亲的左儿子。此时可删去p和p的右儿子,并可将p的左儿子放到p的位置上。同样地,Tk成为了Tk-2,boat[k]=boat[k-2]+1=[k/2]。
(3) 如图2-2-3,p有两个儿子,并且p是其父亲的右儿子。此时可删去p和p的左儿子,并可将p的右儿子放到p的位置上。情况与(2)十分相似,易得此时得boat[k]=boat[k-2]+1=[k/2]。
综合(1)、(2)、(3),当m=k时,boat[k]=[k/2]。
最后,综合(i)、(ii),对于一切mN,boat[m]=[m/2]。
由上述证明,我们将问题中数据的图结构转化为树结构后,可以得出求一棵二叉树的乘船方案的算法:
proc try(father:integer;var root:integer;var rest:byte);
{输出root为树根的子树的乘船方案,father=0表示root是其父亲的左儿子,
father=1表示root是其父亲的右儿子,rest表示输出子树的乘船方案后,
是否还剩下一个根结点未乘船}
begin
visit[root]:=true; {标记root已访问}
找到一个与root同姓且未访问的结点j;
if j<>n+1 then try(0,j,lrest);
找到一个与root同姓且未访问的结点k;
if k<>n+1 then try(1,k,rrest);
if (lrest=1) xor (rrest=1) then begin {判断root是否只有一个儿子,情况一}
if lrest=1 then print(lrest,root) else print(rrest,root);
rest:=0;
end
else if (lrest=1) and (rrest=1) then begin {判断root是否有两个儿子}
if father=0 then begin
print(rrest,root);root:=j; {情况二}
end
else begin
print(lrest,root);root:=k; {情况三}
end;
rest:=1;
end
else rest:=1;
end;
这只是输出一棵二叉树的乘船方案的算法,要输出所有人的乘船方案,我们还需再加一层循环,用于寻找各棵二叉树的根结点,但由于每个点都只会访问一次,寻找其左右儿子各需进行一次循环,所以算法的时间复杂度为O(n2)。(附程序boat.pas)
最后,我们对两种结构得出不同时间复杂度算法的原因进行分析。其中最关键的一点就是因为二叉树虽然结构相对较简单,但已经包含了几乎全部都“有用”的信息。由我们寻找乘船方案的算法可知,二叉树中的所有边不仅都发挥了作用,而且没有重复的使用,可见信息的利用率也是相当之高的。
既然采用树结构已经足够,图结构中的一些信息就显然就成为了“无用”的信息。这些多余的“无用”信息,使我们在分析问题时难于发现规律,也很难找到高效的算法进行解决。这正如迷宫中的墙一样,越多越难走。“无用”的信息,只会干扰问题的规律性,使我们更难找出解决问题的方法。
小结
我们对数据的逻辑结构进行选择,是构造数学模型一大关键,而算法又是用来解决数学模型的。要使算法效率高,首先必须选好数据的逻辑结构。上面已经提出了选择逻辑结构的两个条件(思考方向),总之目的是提高信息的利用效果。利用“可直接使用”的信息,由于中间不需其它操作,利用的效率自然很高;不不记录“无用”的信息,就会使我们更加专心地研究分析“有用”的信息,对信息的使用也必然会更加优化。
总之,在解决问题的过程中,选择合理的逻辑结构是相当重要的环
三、 选择合理的存储结构
数据的存储结构,分为顺序存储结构和链式存储结构。顺序存储结构的特点是借助元素在存储器中的相对位置来表示数据元素之间的逻辑关系;链式存储结构则是借助指示元素存储地址的指针表示数据元素之间的逻辑关系。
因为两种存储结构的不同,导致这两种存储结构在具体使用时也分别存在着优点和缺点。
这里有一个较简单的例子:我们需要记录一个n×n的矩阵,矩阵中包含的非0元素为m个。
此时,我们若采用顺序存储结构,就会使用一个n×n的二维数组,将所有数据元素全部记录下来;若采用链式存储结构,则需要使用一个包含m个结点的链表,记录所有非0的m个数据元素。由这样两种不同的记录方式,我们可以通过对数据的不同操作来分析它们的优点和缺点。
1. 随机访问矩阵中任意元素。由于顺序结构在物理位置上是相邻的,所以可以很容易地获得任意元素的存储地址,其复杂度为O(1);对于链式结构,由于不具备物理位置相邻的特点,所以首先必须对整个链表进行一次遍历,寻找需进行访问的元素的存储地址,其复杂度为O(m)。此时使用顺序结构显然效率更高。
2. 对所有数据进行遍历。两种存储结构对于这种操作的复杂度是显而易见的,顺序结构的复杂度为O(n2),链式结构为O(m)。由于在一般情况下m要远小于n2,所以此时链式结构的效率要高上许多。
除上述两种操作外,对于其它的操作,这两种结构都不存在很明显的优点和缺点,如对链表进行删除或插入操作,在顺序结构中可表示为改变相应位置的数据元素。
既然两种存储结构对于不同的操作,其效率存在较大的差异,那么我们在确定存储结构时,必须仔细分析算法中操作的需要,合理地选择一种能够“扬长避短”的存储结构。
一、合理采用顺序存储结构。
我们在平常做题时,大多都是使用顺序存储结构对数据进行存储。究其原因,一方面是出于顺序结构操作方便的考虑,另一方面是在程序实现的过程中,使用顺序结构相对于链式结构更便于对程序进行调试和查找错误。因此,大多数人习惯上认为,能够使用顺序结构进行存储的问题,最“好”采用顺序存储结构。
其实,这个所谓的“好”只是一个相对的标准,是建立在以下两个前提条件之下的:
1. 链式结构存储的结点与顺序结构存储的结点数目相差不大。这种情况下,由于存储的结点数目比较接近,使用链式结构完全不能体现出记录结点少的优点,并且可能会由于指针操作较慢而降低算法的效率。更有甚者,由于指针自身占用的空间较大,且结点数目较多,因而算法对空间的要求可能根本无法得到满足。
2. 并非算法效率的瓶颈所在。由于不是算法最费时间的地方,这里是否进行改进,显然是不会对整个算法构成太大影响的,若使用链式结构反而会显得操作过于繁琐。
二、必要时采用链式存储结构。
上面我对使用顺序存储结构的条件进行了分析,最后就只剩下何时应该采用链式存储结构的问题了。
由于链式结构中指针操作确实较繁琐,并且速度也较慢,调试也不方便,因而大家一般都不太愿意用链式的存储结构。但是,这只是一般的观点,当链式结构确实对算法有很大改进时,我们还是不得不进行考虑的。
〖问题三〗 IOI99的《地下城市》。
〖问题描述〗
已知一个城市的地图,但未给出你的初始位置。你需要通过一系列的移动和探索,以确定初始时所在的位置。题目的限制是:
1. 不能移动到有墙的方格。
2. 只能探索当前所在位置四个方向上的相邻方格。
在这两个限制条件下,要求我们的探索次数(不包括移动)尽可能的少。
〖问题分析〗
由于存储结构要由算法的需要确定,因此我们首先来确定问题的算法。
经过对问题的分析,我们得出解题的基本思想:先假设所有无墙的方格都可能是初始位置,再通过探索一步步地缩小初始位置的范围,最终得到真正的初始位置。同时,为提高算法效率,我们还用到了分治的思想,使我们每一次探索都尽量多的缩小初始位置的范围(使程序尽量减少对运气的依赖)。
接着,我们来确定此题的存储结构。
由于这道题的地图是一个二维的矩阵,所以一般来讲,采用顺序存储结构理所当然。但是,顺序存储结构在这道题中暴露了很大的缺点。我们所进行的最多的操作,一是对初始位置的范围进行筛选,二是判断要选择哪个位置进行探索。而这两种操作,所需要用到的数据,只是庞大地图中很少的一部分。如果采用顺序存储结构(如图3-1中阴影部分表示已标记),无论你需要用到多少数据,始终都要完全的遍历整个地图。
4
3
2
1
1 2 3 4
图3-1
head
图3-2
然而,如果我们采用的是链式存储结构(如图3-2的链表),那么我们需要多少数据,就只会遍历多少数据,这样不仅充分发挥了链式存储结构的优点,而且由于不需单独对某一个数据进行提取,每次都是对所有数据进行判断,从而避免了链式结构的最大缺点。
我们使用链式存储结构,虽然没有降低问题的时间复杂度(链式存储结构在最坏情况下的存储量与顺序存储结构的存储量几乎相同),但由于体现了前文所述选择存储结构时扬长避短的原则,因而算法的效率也大为提高。(程序对不同数据的运行时间见表3-3)
测试数据编号 使用顺序存储结构的程序 使用链式存储结构的程序
1 0.06s 0.02s
2 1.73s 0.07s
3 1.14s 0.06s
4 3.86s 0.14s
5 32.84s 0.21s
6 141.16s 0.23s
7 0.91s 0.12s
8 6.92s 0.29s
9 6.10s 0.23s
10 17.41s 0.20s
表3-3
(附使用链式存储结构的程序under.pas)
我们选择链式的存储结构,虽然操作上可能稍复杂一些,但由于改进了算法的瓶颈,算法的效率自然也今非昔比。由此可见,必要时选择链式结构这一方法,其效果是不容忽视的。
小结
合理选择逻辑结构,由于牵涉建立数学模型的问题,可能大家都会比较注意。但是对存储结构的选择,由于不会对算法复杂度构成影响,所以比较容易忽视。那么,这种不能降低算法复杂度的方法是否需要重视呢?
大家都知道,剪枝作为一种常用的优化算法的方法,被广泛地使用,但剪枝同样是无法改变算法的复杂度的。因此,作用与剪枝相似的存储结构的合理选择,也是同样很值得重视的。
总之,我们在设计算法的过程中,必须充分考虑存储结构所带来的不同影响,选择最合理的存储结构。
四、 多种数据结构相结合
上文所探讨的,都是如何对数据结构进行选择,其中包含了逻辑结构的选择和存储结构的选择,是一种具有较大普遍性的算法优化方法。对于多数的问题,我们都可以通过选择一种合理的逻辑结构和存储结构以达到优化算法的目的。
但是,有些问题却往往不如人愿,要对这类问题的数据结构进行选择,常常会顾此失彼,有时甚至根本就不存在某一种合适的数据结构。此时,我们是无法选择出某一种合适的数据结构的,以上的方法就有些不太适用了。
为解决数据结构难以选择的问题,我们可以采用将多种数据结构进行结合的方法。通过多种数据结构相结合,达到取长补短的作用,使不同的数据结构在算法中发挥出各自的优势。
这只是我们将多种数据结构进行结合的总思想,具体如何进行结合,我们可以先看下面的例子。
我们可以采用映射的方法,将线性结构中的元素与堆中间的结点一一对应起来,若线性的数组中的元素发生变化,堆中相应的结点也接着变化,堆中的结点发生变化,数组中相应的元素也跟着变化。
将两种结构进行结合后,无论是第一步还是第二步,我们都不需对所有元素进行遍历,只需进行常数次复杂度为O(log2n)的堆化操作。这样,整个时间复杂度就成为了O(nlog2n),算法效率无疑得到了很大提高。
五、 总结
我们平常使用数据结构,往往只将其作为建立模型和算法实现的工具,而没有考虑这种工具对程序效率所产生的影响。信息学问题随着难度的不断增大,对算法时空效率的要求也越来越高,而算法的时空效率,在很大程度上都受到了数据结构的制约。
‘捌’ 模型压缩:剪枝算法
过参数化主要是指在训练阶段,在数学上需要进行大量的微分求解,去捕抓数据中的微小变化信息,一旦完成迭代式的训练之后,网络模型推理的时候就不需要这么多参数。而剪枝算法正是基于过参数化的理论基础而提出的。
剪枝算法核心思想就是减少网络模型中参数量和计算量,同时尽量保证模型的性能不受影响。
那在AI框架中,实际上剪枝主要作用在右下角的端侧模型推理应用场景中,为的就是让端侧模型更小,无论是平板、手机、手表、耳机等小型IOT设备都可以轻松使用AI模型。而实际在训练过程更多体现在剪枝算法和框架提供的剪枝API上面。
实际上大部分刚接触剪枝算法的时候,都会从从宏观层面去划分剪枝技术,主要是分为Drop Out和Drop Connect两种经典的剪枝算法,如下图所示。
1)Drop Out:随机的将一些神经元的输出置零,称之为神经元剪枝。
2)Drop Connect:随机将部分神经元间的连接Connect置零,使得权重连接矩阵变得稀疏。
下面会把剪枝的更多种方式呈现出来,可能会稍微复杂哈。从剪枝的粒度来划分,可以分为结构化剪枝和非结构化剪枝,2个剪枝结构方法。下面来看看具体的剪枝方法有4种:
细粒度剪枝、向量剪枝、核剪枝在参数量与模型性能之间取得了一定的平衡,但是网络模型单层的神经元之间的组合结构发生了变化,需要专门的算法或者硬件结构来支持稀疏的运算,这种叫做 结构化剪枝(Unstructured Pruning) 。
其中,非结构化剪枝能够实现更高的压缩率,同时保持较高的模型性能,然而会带来网络模型稀疏化,其稀疏结构对于硬件加速计算并不友好,除非底层硬件和计算加速库对稀疏计算有比较好的支持,否则剪枝后很难获得实质的性能提升。
滤波器剪枝(Filter-level)主要改变网络中的滤波器组和特征通道数目,所获得的模型不需要专门的算法和硬件就能够运行,被称为 结构化剪枝(Structured Pruning) 。结构化剪枝又可进一步细分:可以是channel-wise,也可以是filter-wise,还可以是在shape-wise。
结构化剪枝与非结构化剪枝恰恰相反,可以方便改变网络模型的结构特征,从而达到压缩模型的效果,例如知识蒸馏中的student网络模型、NAS搜索或者如VGG19和VGG16这种裁剪模型,也可以看做变相的结构化剪枝行为。
虽然剪枝算法的分类看上去很多,但是核心思想还是对神经网络模型进行剪枝,目前剪枝算法的总体流程大同小异,可以归结为三种:标准剪枝、基于子模型采样的剪枝、以及基于搜索的剪枝,如下图所示。
标准剪枝是目前最流行的剪枝流程,在Tensorflow、Pytroch都有标准的接口。主要包含三个部分:训练、剪枝、以及微调。
1) 训练 :首先是对网络模型进行训练。在剪枝流程中,训练部分主要指预训练,训练的目的是为剪枝算法获得在特定基础SOTA任务上训练好的原始模型。
3) 微调 :微调是恢复被剪枝操作影响的模型表达能力的必要步骤。结构化模型剪枝会对原始模型结构进行调整,因此剪枝后的模型参数虽然保留了原始的模型参数,但是由于模型结构的改变,剪枝后模型的表达能力会受到一定程度的影响。实现上,微调网络模型,参数在计算的时候先乘以该Mask,Mask为1的参数值将继续训练通过BP调整梯度,而Mask为0的部分因为输出始终为0则不对后续部分产生影响。
4) 再剪枝 :再剪枝过程将微调之后的网络模型再送到剪枝模块中,再次进行模型结构评估和执行剪枝算法。目的是使得每次剪枝都在性能更优的模型上面进行,不断迭代式地进行优化剪枝模型,直到模型能够满足剪枝目标需求。
最后输出模型参数储存的时候,因为有大量的稀疏,所以可以重新定义储存的数据结构, 仅储存非零值以及其矩阵位置。重新读取模型参数的时候,就可以还原矩阵。
除标准剪枝之外,基于子模型采样的剪枝《EagleEye: Fast sub-net evaluation for efficient neural network pruning》最近也表现出比较好的剪枝效果。得到训练好的模型之后,进行子模型采样过程。一次子模型采样过程为:
1)对训练好的原模型中可修剪的网络结构,按照剪枝目标进行采样,采样过程可以是随机的,也可以按照网络结构的重要性或者通过KL散度计算进行概率采样。
2)对采样后的网络结构进行剪枝,得到采样子模型。子模型采样过程通常进行 次,得到 个子模型( ≥1), 之后对每一个子模型进行性能评估。子模型评估结束之后,选取最优的子模型进行微调以得倒最后的剪枝模型。
基于搜索的剪枝主要依靠强化学习等一系列无监督学习或者半监督学习算法,也可以是神经网络结构搜索相关理论。
给定剪枝目标之后,基于搜索的剪枝在网络结构中搜索较优的子结构,这个搜索过程往往伴随着网络参数的学习过程,因此一些基于搜索的剪枝算法在剪枝结束后不需要再进行微调。
这几年神经网络剪枝pruning作为模型压缩技术的四小龙之一,正在受到越来越多的关注。当然,各种更好的pruning参数选取方法一定还会层出不穷。另外,从趋势来看,以下几个方向值得关注:
打破固定假设 :挑战已有的固有的假设,例如ICLR2019会议的best paper彩票假说《The Lottery Ticket Hypothesis: Finding Sparse, Trainable Neural Networks 》的出现。还有一开始提到的对于over-parameterization,与重用已有参数是否有有益的反思非常有意思。这样的工作会给剪枝算法非常大的启发,从而根本改变解决问题的思路。
自动化剪枝 :随着AutoML的大潮,越来越多的算法开始走向自动化。模型压缩能拉下吗?当然不能。经过前面的介绍我们知道,像ADC,RNP,N2N Learning这些工作都是试图将剪枝中部分工作自动化。如量化中的《HAQ: Hardware-Aware Automated Quantization》考虑网络中不同层信息的冗余程度不一样,所以自动化使用混合量化比特进行压缩。
与NAS融合 :如前面模型剪枝流程中提到,剪枝算法与神经网络搜索NAS的界限已经模糊了。NAS有针对结构化剪枝进行搜索方法,如One-Shot Architecture Search是先有一个大网络,然后做减法。NAS与模型压缩两个一开始看似关系不是那么大的分支,在近几年的发展过程中因为下游任务和部署场景的需求,最后似乎会走到一块去。这两个分支今天有了更多的交集,也必将擦出更多的火花。
与GAN融合 :这几年机器学习最火热的分支之一GAN,正在不断渗透到已有领域,在pruning中也开始有它的身影。如2019年《Towards Optimal Structured CNN Pruning via Generative Adversarial Learning》让generator生成裁剪后网络,discrimintor来判别是否属于原网络还是裁剪后网络,从而进行更有效的网络结构化裁剪。
硬件稀疏性支持 :剪枝会给神经网络模型带来稀疏性特征,参数稀疏性在计算中会有大量的索引,所以并不能加速。现在虽然有像cuSPARSE这样的计算库,但底层硬件AI芯片本身设计并不是专门为稀疏数据处理打造的。如果能将稀疏计算和处理能力做进芯片那必将极大提高计算效率。仅2021年中国就推出了10+款基于ASIC的AI加速芯片,相信针对稀疏性场景的支持在未来会有所突破。
模型压缩算法中针对已有的模型,有:张量分解,模型剪枝,模型量化。针对新构建的网络,有:知识蒸馏,紧凑网络设计等方法。
剪枝只是模型压缩方法中的一种,它与其它模型压缩方法并不冲突,因此会与量化、蒸馏、NAS、强化学习等方法慢慢融合,这些都是很值得研究的方向。另外在上面的发展来看,打破固有的假设定义,与NAS、GAN、AutoML、RL等技术进行相互的融合,可能到最后会模糊purning方式,出现新的范式或者压缩模式也是很吸引的。
‘玖’ 如何对决策树进行剪枝
决策树洞橡衡的剪枝通常有两种方法,预剪枝(Pre-Pruning)和后剪枝(Post- Pruning)。那么这两种方法是如何进行的呢如渣?它们又各有什么优缺点?
■ 预剪枝
预剪枝的核心思想是在树中结点进行扩展之前,先计算当前的划分是否能带 来模型泛化能力的提升,如果不能,则不再继续生长子树。此时可能存在不同类 别的样本同时存于结点中,按照多数投票的原则判断该结点所属类别。预剪枝对 于何时纳做停止决策树的生长有以下几种方法。
(1)当树到达一定深度的时候,停止树的生长。
(2)当到达当前结点的样本数量小于某个阈值的时候,停止树的生长。
(3)计算每次分裂对测试集的准确度提升,当小于某个阈值的时候,不再继 续扩展。
预剪枝具有思想直接、算法简单、效率高等特点,适合解决大规模问题。但 如何准确地估计何时停止树的生长(即上述方法中的深度或阈值),针对不同问 题会有很大差别,需要一定经验判断。且预剪枝存在一定局限性,有欠拟合的风 险,虽然当前的划分会导致测试集准确率降低,但在之后的划分中,准确率可能 会有显着上升。
■ 后剪枝
后剪枝的核心思想是让算法生成一棵完全生长的决策树,然后从最底层向上
计算是否剪枝。剪枝过程将子树删除,用一个叶子结点替代,该结点的类别同样 按照多数投票的原则进行判断。同样地,后剪枝也可以通过在测试集上的准确率 进行判断,如果剪枝过后准确率有所提升,则进行剪枝。相比于预剪枝,后剪枝 方法通常可以得到泛化能力更强的决策树,但时间开销会更大。
常见的后剪枝方法包括错误率降低剪枝(Reced Error Pruning,REP)、悲 观剪枝(Pessimistic Error Pruning,PEP)、代价复杂度剪枝(Cost Complexity Pruning,CCP)、最小误差剪枝(Minimum Error Pruning,MEP)、CVP(Critical Value Pruning)、OPP(Optimal Pruning)等方法,这些剪枝方法各有利弊,关注 不同的优化角度,本文选取着名的CART剪枝方法CCP进行介绍。
代价复杂剪枝主要包含以下两个步骤。