关闭

优化算法--以Python实现(1)

标签: 算法python优化domain测试旅游
7846人阅读 评论(9) 收藏 举报
分类:

此文中讨论优化算法,诸如随机搜索,退火算法,爬山法,遗传算法之类。参考了《集体智慧编程》。由于对Python不太熟,因此也讨论了下Python。。。对算法的讨论最好以实例/或问题的方式入手,故我们引入了组团旅游问题。

 

但你会看到,组团旅游什么的都是浮云,真正有意义的是从第四点开始的优化算法:随机搜索,退火算法,爬山法,遗传算法

 

组团旅游:

 

该问题的Java版本在神人vivizhyy的博文中:http://vivizhyy.javaeye.com/blog/643048

问题描述可参见该博文。

 

以下一至三均不是优化算法的一部分,但却是正确有效使用优化算法的前提和关键所在。如果你仅对优化算法感兴趣,请下拉至四。

 

一。先定义数据集和辅助函数:

 

 

 

1.关于文件打开:

   在Python3中取消了file()函数,取代之以open函数。上述with的open文件代码块是with的一个妙用。参                见:http://woodpecker.org.cn/diveintopython3/files.html

2.line.strip()

   line.strip()是Python中取出字符串首尾空格(或字符)的函数。与VB中的Trim,Ltrim,Rtrim类似,但strip不仅能去掉空格,还可以去        掉其他字符。参见:http://blog.csdn.net/suofiya2008/archive/2010/05/19/5608309.aspx

3.flights实际上是一个以元组为键以元组为值的字典。

4.关于getminutes

5.打印字典

若要打印字典flights,应该用(想找一个直接print的函数妙用找到,貌似只能用for来辅助完成)

 

在Python3以前可以用

 

这在Python3中是不可行的。可参见:http://www.sciencenet.cn/blog/user_content.aspx?id=372718以及:http://topic.csdn.net/u/20090721/13/91bf1e47-3d94-4b40-a18b-9406ac6985f3.html

 

二、将flights中的字典数据结构转换为表格形式。

 

代码中的for idx in range( int(len(r)/2) ):一句中,如果不使用int转换len/2的返回值则会出错,因在Python3中除法返回float,因此需要进行int转换。也因此不会再出现:http://apps.hi.baidu.com/share/detail/23147749中的问题。

另,参见:http://hi.baidu.com/gofight/blog/item/a73e3e1fcf9e74fe1ad576e8.html

另,关于print的格式化,参见:http://blogold.chinaunix.net/u2/84280/showart_2068008.html,print很像C中的printf

 

三、成本计算:

此题中的成本计算函数如下:

 

 

算法可改进为以下形式:

 

但该算法并不是很直观。

 

成本函数的确定是优化算法有效使用的一个相当重要的方面。

 

实际上,当优化算法写好以后,定义合理的成本函数以及domain即成为了关键,对优化函数的使用并不是拿来用即可,在使用之前得却确保有正确的辅助函数。详情见本博《优化算法--Python实现(2)》。

 

 

开始的测试我们使用的是人为的随机指定一个往返时刻列表,以此来算花费;

该优化算法的主要用途即是:通过搜索,找到一个合理的序列,该序列能够使得花费最低。

以下开始便是各种有效的搜索算法:

(好吧,优化算法来了。。。请注意以下优化函数中domain的入内参数domain,在不同的使用优化函数的场合是不同,domain和成本函数是优化函数有效的关键。)

 

四:随机搜索算法

 

for i in range(1000):的用法是Python中for的一个很好的用法,甚至比VB中的for each更为好用,因在VB6中,for each仅对variant变量或对象集合有效。

另,domain = [(0,9)]*(len(people)*2)实际上是定义了一个长度为len(people)*2的元组列表,每个元组可以是重复的。列表元素可以是重复的。

 

事实证明,增加随机搜索的调用次数,或者增加随机搜索的尝试次数(randomoptimize中for的循环次数),并不能显著的得到更好的序列。在我的机子上,最好的结果是31XX,在增加循环次数后,并没有得到比之更低的结果。

 

五:爬山法

该方法充分利用了已发现的解序列,基于最有序列很可能接近已发现的解序列,且,已发现的解序列意味着,在它之前的所有序列中,它是最优的,那么基于该解序列的进化应该能得到更为优异的序列,当不能产生更为优秀的序列,则进化完毕。

该算法从一随机序列开始,在一次循环所得到的序列之上进行不断的进化,直到找到应该更为优秀的,或最为优秀的序列。如下:

 

注意,sol是以一随机序列开始的,此后的循环中,sol是不断进化的。if best == current即为终结条件。

事实证明,爬山法能/容易得到比随机搜索更好的序列,且该算法保证,且所搜索到的序列必是该次算法中所能产生的最好序列。(随机搜索算法不能保证,因,其仅仅搜索了状态空间中的1000个可能序列,实际共有10^9个可能序列)。在我的机子上,该算法产生的最好序列是:

 

 

 

2456

[4, 3, 3, 3, 4, 3, 3, 4, 4, 3, 2, 3]

   Seymour       BOS 12:34-15:02 $109 10:33-12:03 $ 74

    Franny       DAL 10:30-14:57 $290 10:51-14:16 $256

     Zooey       CAK 12:08-14:59 $149 10:32-13:16 $139

      Walt       MIA 11:28-14:40 $248 12:37-15:05 $170

     Buddy       ORD 12:44-14:17 $134 10:33-13:11 $132

       Les       OMA  9:15-12:03 $ 99 11:07-13:24 $171

 

 

算法共检查序列264个。

我觉得这个算是爬山法的顶点了。实际上,在测试爬山法中,我没有发现比它更好的。

因此,算法的好坏并不在于其所检视的序列的多少,而在很大程度上要取决于,第一次所产生的随机序列sol的好坏。比如,我的测试中,有检视701次,得分3766的。

这不由让我想起现实社会中,个人的成功并不在于(或,不全在于)个人的努力程度,在很大程度上,个人的起点相当重要。

 

 

话说回来,爬山法也有其自身的缺陷,即:可能陷入局部范围内的最小值。详细讨论可参见《集体智慧编程》P94.

要避免局部范围内的最小值,我们可以使用模拟退火算法或遗传算法。(实际上,个人觉得,爬山法很像是遗传算法/遗传编程了)。

 

六、模拟退火

模拟退火是为了避免爬山法中的局部最小二提出的,该算法在温度较高时不介意接受较差解,但当温度很低时候,仅接受最优解。

 

但该算法并不能完全解决局部最小问题,且不见得能得到比爬山法更好的结果。事实上,在我的测试中并没有产生比爬山法更好的结果。况且,既然是要解决爬山法的局部最小问题,那么模拟退火算法应该是用于对爬山法结果的修正更为合理。

因此,最好的是,在爬山法的使用中,嵌入模拟退火算法,以期解决局部最小弊端。

为了嵌入,我们首先要对模拟退火法稍作修改,如下:

 

在修改后的模拟退火算法中,接受一由爬山法所得的较优解,并以此为基,进行一轮模拟退火,返回更优解(亦可能返回不优解,因此在爬山法中有对之进行判断的if)。

 

嵌入的方法有两种:

第一种是,在爬山法中,使用模拟退火对每次循环中所得的当前最优解进行计算,若能通过模拟退火得到更好的解,那么则以该解作为下一轮爬山的序列基础。

 

 

另一种是,仅在爬山法的最后,对爬山法所得的序列进行一次模拟退火,以避开陷入局部最小。

但,将上述的方法一和方法二结合起来是没有必要的,因若结合,则在推出main loop后,所得的结果已是经由模拟退火算法修正过的。

另,注意在爬山法中调用模拟退火时的step的设定。因,step是对于爬山法所得次优解的计算所需的增量产生范围,而爬山法所用的增量相当于是1,故step不宜再设定为1,经测试,设定为非1较1,性能及结果有明显的差别。

但同样,step不宜设置较大,因在模拟退火中,若step过大,则在进行增量时会对其所产生的增量进行修改,会舍得其反。

经测试,step设定为2-4较合理。

 

上述两种算法均能得到良好的结果,一般所产生的结果均在3000以下。

在我的测试中,所产生的最好结果为:2356(而且该结果在测试中不止产生一次,看起来像是个算法的极限值。。。)

 

 

2356

   Seymour       BOS 12:34-15:02 $109 10:33-12:03 $ 74

    Franny       DAL 10:30-14:57 $290 10:51-14:16 $256

     Zooey       CAK 12:08-14:59 $149 10:32-13:16 $139

      Walt       MIA 11:28-14:40 $248 12:37-15:05 $170

     Buddy       ORD 12:44-14:17 $134 10:33-13:11 $132

       Les       OMA 12:18-14:56 $172 11:07-13:24 $171

 

 

当然,其速度的变慢也是显著的。。。

 

六:神奇的遗传算法

这里是神奇的遗传算法。

大体是模拟生物进化的理论,对序列进行诸如优胜劣汰,变异,交配之类的操作,基于生物进化,优优结合下一代应该更为优秀的假设,我们有理由相信,所得结果必为最优。

 

代码注释已详细,不再详说。想赞一下的是,在函数中定义子函数相当有意思啊,从未在其他语言中遇到过……

说下结果。

结果并未如我所想那么好。往往是,进化到一定阶段后,虽然进化仍然继续,但没有能超过在前阶段进化中最优异者,于是,往往是,无论进化多少代,优秀者恒优秀。。。(又让我想起现实社会,富人更富穷人越穷。。。),而这与初始随机产生的序列密切相关。

传说中的遗传算法啊,终于见识了。。。


既然如此,那么是不是越是优异的初始种群,越能产生优异的序列呢?

基于此,笔者试验了使用爬山法产生初始种群,以遗传算法使之进化,更改后的遗传算法代码代码如下:

 

笔者使用如下代码进行了测试:

 

测试用例不多,仅10次。考虑到该算法的运行效率,我认为,这大大的够了。。。(嗯,其实,大约在1分钟之内就跑完了。。。)

 

部分测试结果如下:

 

可以看出,其实爬山法给出的最优解是不稳定的,受爬山法所随机生成的初始序列影响相当大。但经由遗传算法进化后,其最终结果是相当的稳定,在我的测试中,未看到最终结果有超过2700的,且多集中在23XX--24XX之间。根据以上所有算法的结果,可以看出23XX算是该旅游问题的最优解了,亦即是说,经过初始种群修正后的遗传算法,总能给出最优解或很接近于最优解的解,且非常稳定。

 

另,值得一提的是,从上边的测试中我们可以看出,由爬山法所生成的最优序列(即是遗传算法的初始序列)中的最优解(成本最低者)并不一定就是遗传算法最终所给出的最优解,倒霉的解,被进化了。。。

也如同真实的地球进化史,优秀的,并不一定就能被进化保留。。。

 

最后给出代码的完整版本:

 

优化算法:

 

组团旅游:

 

再,

谈下Python。

总体上说,Python是门优秀的语言。大多数语言要素均在表达what,而不是how。且,个人认为,以该语言开发极有效率。

 

至此,这篇超长的优化算法博文算告一段路。

最后要提的是,优化算法并不一定总能奏效。有效无效,是相对于问题而言的。

 

By Kewing.

1
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:72115次
    • 积分:1342
    • 等级:
    • 排名:千里之外
    • 原创:67篇
    • 转载:0篇
    • 译文:0篇
    • 评论:16条
    最新评论