上一次我们大体地总结抽象了一下典型的人类做设计的流程,并且尝试着论证了其中不同步骤能被现有数字化工具代替的可行性。
这次该说到实操了,笔者准备了一个工作中真实遇到的例子。本来是想理论部分尽可能的精简,更多注重于实际上手操作。然而后来意识到,过于精简的理论,会让这次实践变成是无脑的“看说明书跟着做”。于是我决定任性滴扩充本篇的篇幅,理论实践一把抓。(信息量绝对有保障的一篇……)
遗传算法是一种被进化论启发的,可以从海量可能性中快速搜索到近似最优解的算法。旨在通过模拟生物进化繁衍的过程,不断优胜劣汰迫使种群进化,以便最终获得近似最优个体。
举例来说。你想要一个比世界上所有香蕉都甜的香蕉品种。但你完全搞不懂香蕉的基因里面哪几对碱基是控制甜度的,能做的只有买来市面上所有香蕉的种子,种了下去。
第一年收获了之后逐个品尝,然后把不甜的扔了,甜的品种杂交一下,再种。
时不时地你还会想点招让香蕉们随机变异一下增加基因多样性碰碰运气。
你的棚子里面的香蕉越来越甜越来越甜。直到你觉得好了没时间继续种了,就这样吧。然后收了当时在你棚子里面最甜的那个香蕉树。
然后我们来拆分一下这个例子,这个香蕉进化的过程里头,人要做的事情有这么几个:
培育香蕉直到结出果实。
尝香蕉果实甜不甜。
杂交,并进行下一次播种。
决定什么时候停下。
还记得我们上篇文章中提到的,设计的大体步骤分解吗?
解读任务书和解读场地
出第一个方案,元设计
评价方案
改方案
定方案
上文关于香蕉的1、2和4步的核心动作分别对应着“生成/修改方案”和“评价方案”。那么差的只是3(杂交,并进行下一次播种 )
我们得稍微解释一下遗传算法的内部运行方式。如开篇提到,它是一种模仿生物优胜劣汰进化的算法,所以首先我们需要提炼一下生物进化的过程是怎样。
足够的个体数量形成种群保证基因多样性。
种群内部可以繁殖,代代传递基因库。
基因,众所周知,我们发育的过程基本上就是“RNA把DNA转录(翻译)成蛋白质”的过程。DNA一开始是一堆编码,没翻译之前谁也看不懂是什么内容(种子看不出果实甜不甜)。然后RNA出现了,把它翻译成了有用的蛋白质,蛋白质又次序井井有条地发挥着作用,发育成了一个完整的生物,最后开花结果(香蕉)。至此我们才知道哇塞,原来这条基因(基因型)的表现型是这样的。
(人话:基因是个密码,只要它确定了,最后的生物大体形态就确定了。工蜂和蜂后这种,会后天根据外在环境不同发育的,我们就先不理它了。但!如果不翻译成生物的话,基因谁也看不懂,更无从得知这条基因发育成的生物成体是否满足我们的需求。)
进化是一个宏观上渐进的过程(先别说突变够乱的了已经),这种改变是在种群尺度上发生的,让过程中整个种群的某一个特征变得越来越明显与普遍。它的动力是外在环境(自然选择),让那些不太适应环境的个体找不到对象或者早早夭折,没什么机会繁衍后代传递自己的基因;而那些很适应的,就可以很容易找到对象而且很长寿,生一大堆孩子把自己的基因传递下去。
这里有个很有趣的例子,不一定是真,但用来解释进化的现象很恰当:
有个理论说,母爱其实是进化出来的。一开始母亲都是要怀胎远长于10个月才分娩,如此婴儿发育的更健壮,夭折的概率就更小。可当人类开始直立行走,产道变窄,体积太大的婴儿已经无法出生了,就导致大家都在早产。可怀胎十月的孩子幼年脆弱之极,只有那些真正悉心呵护自己孩子的母亲们,得以把孩子养大,她们的基因才得以传下来。而她们的基因里便有着“母爱”这一片段。
基因中没有母爱行为的母亲的孩子都夭折了,而有母爱的母亲们得以把孩子养大,母爱的基因也得以继续在孩子们身上流传下去。如此万年,如今不只所有的母亲都有母爱了,甚至所有的人类面对婴儿都有照顾保护的强烈冲动。
所以进化的本质其实说白了就是:代表适应环境的特性的基因会传递下去,同时环境不喜欢的那些特性的基因呢,就没什么机会把自己传递下去了。而且环境不需要知道到底是哪条基因决定的甜不甜(比如香蕉),它只需要把不甜的香蕉搞死,或者让它孤独终老即可,结果就会导致:只有带有比较甜的基因的香蕉有机会繁殖,然后整个香蕉种群越来越甜。
生物的特性是由基因决定的
他们每次繁殖都会传递个体的基因
那么这种方法就可以略过极度复杂的转录翻译过程,透过表现型直接对基因型进行筛选,这是一种相当聪明的机制。并且因为每一代都是基于上代的迭代,所有前人的进化都不会浪费。(如果知识也可以像基因一样传递多好。)
基因型。
把基因型翻译成表现型的转录器。
可以对表现型进行筛选的“自然环境”。
在这里停顿,看下去之前请再次在头脑中回顾一下“元设计”的概念。
元设计指的是“用来生成设计的设计”。大多数情况下是一段程序,但也包含一些物理装置,例如高迪的悬垂找型铁链。
一段GH定义,或者一段可以用参数控制最终形态的代码,都可以叫做“元设计”。因为一个“元设计”里面随着参数的修改包含着成千上万个具体的方案可能性。比如高迪做好了垂链模型,他就可以很方便快捷地调节每根铁链的长度,从而快速生成无穷多个不同,但又都符合他的重力找型需求的方案。
好,如果元设计就是一个你设计出来的物种,它里面会涵盖着有成千上万种不同的个体(因为它的参数有着成千上万种不同的组合方式),但所有这些不同的个体,都有共同的几个输入参数(基因型),只不过每个参数的具体取值不一样。而且你还有一个转录器,就是那段从参数生成最后方案的程序。然后你还有一个挑剔的自然环境(量化评价自己方案好坏的方法),可以是上篇中我们提到的评价的数字化工具,也可以是你自己规定的一种评价方法,只要评价的结果是量化的。那么,你的元设计物种就已经准备好可以开始进化了。
于是,从方法论上说,只需要把以下几个部分逐一做好:
输入参数(基因型)
转录器,就是那段从参数生成最后方案的程序。
挑剔的自然环境(量化评价自己方案好坏的方法)
然后再把这几块东西用遗传算法联系到一起,我们就得到了一个可以在电脑里自行进化的“物种”。
如果把你的方案看成一种生物,它的参数就是基因型,最终生成的几何体就是表现型,加上量化评价表现型好坏的方法,我们便可以实现让这种生物在电脑里高速地“进化”。
来打开Rhino和GH。 (本文后半部分就完全开始当做教学写了哈。)
有时候我们会拿到这种无法进一步编辑的曲线(因为控制点实在太密了):
这种曲线多数时候,是因为在不同软件之间互导,或者是投影过导致的。但是如果偏偏我们就是需要很优雅地,用寥寥数个控制点,来进一步编辑它的形态,那怎么办呢……是的有人选择徒手描线……或者牺牲很大一部分原来的曲线细节,直接rebuild……又或者可控性很低的Fitcrv……
接下来,我们就来介绍一个与以上方法相比,看起来要更有逼格也更可控的方法:用遗传算法描线。
那么好的~你是不是已经拿到了一条3d扭来扭去的,而且有好几百控制点的曲线呢?
我们拿到线了之后第一件事情自然是把它拾取进入Grasshopper:
然后关键的部分来了:构建元设计。如上文,我们的程序需要分成三块:
一个基因型(参数池);
一块生成表现型的程序;
和一个量化评价表现型个体的评价算法。
在这个例子里面我们用EvaluateCrv里面的t来做基因型,这些t会通过Evaluate Crv运算器给我们取到曲面上面不同位置的点,0代表曲线的头,0.5代表曲线的中点,1代表曲线的尾端,以此类推。每个个体有若干个t,就会取到若干个在曲线上的点。所以我们只要把基因池的取值范围,设置成从0到1,那么每个单独基因的取值范围,就包含了几乎所有在这根曲线上的点的可能性。在串联所有的点成为新的曲线之前呢,我们需要把基因池给出的基因排序一下,以便最后点的顺序是从头到尾,不会随意乱画:
这里有一个隐形的坑,等到你第一次执行算法之后我们再说,先往下看。
请试试看拉动一下基因池里面的参数基因(这里需要你调动一下自己的nurbs感觉,大概估计一下,诠释这个曲线大概需要几个控制点。然后相应地给基因池放几个基因参数),看到新生成的曲线是如何随着变化的,那么表现型我们已经得到了,就是这根曲线,并且这个基因池应该已经包含了所有的可能性,其中自然也就有我们想要的那个基因取值组合。
那么怎么告诉电脑我们想要什么呢?(构造量化评价方式)
在这个例子里,我们细分新生成的曲线,均匀在上面取100个点,然后测量这些点距离原来曲线的距离,这100个点就有100个距离,我们把这100个距离全部累加起来,最后得到了一个数字(总误差):
这个数字,就可以用来量化衡量新曲线和目标曲线的相似度,明显是误差越小越好。
接下来轮到主角出场:Galapagos。这是个运算器的名字,GH默认自带的。它只有两个接口,一个是评价值,一个是基因型。
于是最后一步便是连接上Galapagos的评价值(fitness)到这个评价系数上,然后把基因型链接到我们的基因池上(注意Galapagos的连线需要从它身上往外拖动) 。
双击Galapagos,在参数这边改到“minimum”,因为我们希望fitness越小越小~然后点击start:
等候一段时间,你会看到代表种群适应度排序的图表不断地收敛,并且最佳适应度屡屡突破新高。你可以点击辐射按钮给种群加入随机基因,也可以点击窗口右上角那几个半球,切换是否显示即时生成的表现型,然后在犀牛窗口里预览看到飞快变换的个体们。
等到整个种群的基因型已经基本没有多样性了,而且结果也算是可以接受的满意时候,就可以停下了,然后关掉galapagos,你会发现基因池和之前不一样了,已经自动设置成了目前算法找到的最优解:
如果你没有遇到那个隐形的坑呢,应该会得到一条满意的结果:用寥寥数个点就画得与原曲线非常非常贴近的曲线了。但如果遇到了坑,会发现最佳结果的误差值是0,而且内差点曲线Int Crv报错,然后没有新曲线生成。这种情况多半是因为在基因池里面一不小心出现了两个一样的数字,结果就是两个重叠点。重叠点输入进IntCrv的结果自然就是报错,所以没有新曲线被画出来,结果适应度就变成了0。因为我们命令算法搜索的是最小值,于是它就会觉得0这个结果是目前最好的,就把它的基因型保留了下来。这正是我们前面说的,筛选只看表现型,完全不会理会生成过程是如何。
解决这一问题的方法有不只一个,我推荐的方法是去重。在基因池刚刚出来数字的时候,就把所有重复的数字去掉,然后再排序,再取点。如此排除此bug:
以上,恭喜已经完成了第一次遗传算法优化操作。记得把这个小程序存下来,如果以后去了喜欢搞曲线的事务所工作是真的用得到的。(其实这是笔者在参加工作后为公司做的第一个“实用”的内部参数化小工具。)
请思考一下,如果我们想要同时使用两个评价方法,比如“越贴近原曲线越好”的同时“控制点越少越好”。那该如何整合这两个评价方法一同放进Galapagos呢?
去找一下一个叫做Octopus的插件,相比GH自带的Galapagos而言,它可以做多目标的优化(也就是同时向好几个方向进化)并且还会提供比较炫酷的出图支持。
没错,2就是1的插件版答案,但1这个问题其实也是值得思考并尝试的。
最后给接触GH时间不长的朋友留个关于GH的思考题:
如果我们要描的曲线不是闭合的,这个小程序要做哪些改动呢?
其实遗传算法还有蛮多东西可以说的,但再继续拆开就真没完了。但其实了解的更透彻一点的话,非常有助于你有方向性地根据需求调节执行算法时的具体参数,比如初代倍增,淘汰率,变异率,精英保留等等,好的设置会对结果和搜索效率产生蛮大的影响,要是有兴趣的同学,可以有空自行寻找相关信息深入研究噢~
注:此篇主体成文于多年以前,故如有朋友识得请放心,别处应也是笔者本人。P.S. 未来更新计划大概如下
概念慎读——当我们在谈“非线性”的时候,我们……真的知道自己在谈什么吗?
反思与沉淀——参数化的“三个原罪”(不是说不好,但是彻底想清楚了我们才能有的放矢)
从业前线指北——在真正的设计前线使用参数化工具的一些小建议
评论区
共 22 条评论热门最新