python框架优化方法,Python 遗传算法框架 GAFT 优化小记

新媒体管家

点击上方“Python开发”,选择“置顶公众号”

关键时刻,第一时间送达!

0818b9ca8b590ca3270a3433284dd417.png

0818b9ca8b590ca3270a3433284dd417.png

前言

前段时间一直在用自己写的遗传算法框架测试算法在优化力场参数的效果,但是跑起来效率很慢,因为适应度函数需要调用多次力场程序计算能量,但是还是比我预想中的慢我也没有及时对程序进行profiling和优化。直到放假前在github有个使用gaft做SVM参数优化的童鞋开了个issue中说道在gaft优化的过程中会大量调用适应度函数,这才使我在国庆放假期间对gaft进行了profiling找到程序瓶颈并针对性的优化。

本文就记录下自己gaft做profiling并优化的过程以及优化的效果。

gaft项目链接:

GitHub: https://github.com/PytLab/gaft

PyPI: https://pypi.python.org/pypi/gaft

正文

对GAFT进行性能分析(Profiling)

为了能针对gaft中不同的函数进行分析,借助Python内置的cProfile和pstats模块我写了个装饰器方便分析并生成不同的分析统计文件。

defdo_profile(filename,sortby='tottime'):

'''

Constructor for function profiling decorator.

'''

def_do_profile(func):

'''

Function profiling decorator.

'''

@wraps(func)

defprofiled_func(*args,**kwargs):

'''

Decorated function.

'''

# Flag for doing profiling or not.

DO_PROF=os.getenv('PROFILING')

ifDO_PROF:

profile=cProfile.Profile()

profile.enable()

result=func(*args,**kwargs)

profile.disable()

ps=pstats.Stats(profile).sort_stats(sortby)

ps.dump_stats(filename)

else:

result=func(*args,**kwargs)

returnresult

returnprofiled_func

return_do_profile

对上面的带参数的装饰器我在这里稍微解释下,装饰器构造器do_profile的两个参数filename和sortby分别指定分析结果报告的文件名以及统计结果的排序方式。它会对需要进行性能分析的函数进行装饰,然后在函数运行完后在当前目录生成结果报告。例如我需要对gaft中遗传算法迭代主循环进行分析,则需要:

@do_profile(filename='gaft_run.prof')

defrun(self,ng=100):

...

同时为了方便,我还需要一个环境变量PROFILING来启动分析:

export PROFILING=y

分析结果

这里为了方便查看函数的相互调用关系,我是用了pyprof2calltree 然后使用Mac上的QCacheGrind来可视化分析结果:

pyprof2calltree -i gaft_run.prof -k

将Python的profiling文件转换并直接调用QCacheGrind便可以方便的查看分析相关信息。

0818b9ca8b590ca3270a3433284dd417.png

通过调用关系图可以看到,gaft的初始版本的min,max,mean等函数多次调用best_indv和worst_indv会多次调用适应度函数来相互比较,而通常情况下用户自定义的适应度函数都是需要额外去调用外部程序的,一般都比较费时。所以必须要通过优化best_indv和worst_indv对fitness的调用次数才能提升gaft的效率。

优化GAFT

函数返回值缓存

从之前我写的best_indv中可以看到,我将fitness作为key用于获取最大值,Python内置的max函数会内部调用fitness进行相互比较来获取最大值,这个时候便对fitness进行了多余的调用,因为在遗传算法中,每一代的population中的个体是不会发生变化的我们只需要在每一次迭代的一开始调用fitnessn次就好了(n为种群大小),每一代中再次需要用到适应度值的地方直接获取。这样需要我们对种群中的个体进行惰性求值,也就是对所有的fitness的值进行缓存。这种操作我在优化自己的催化动力学程序的时候也使用过,叫做函数返回值缓存.

但是在gaft中这种缓存有稍微麻烦一点,因为缓存并不是缓存一次就可以一直用了,它会随着条件的变化需要重新计算种群中所有个体的适应度然后重新缓存。

重新计算适应度值需要同时满足的条件

种群中的所有个体没有发生任何变化 (如果变化了那肯定要重新计算适应度值了)。

已有缓存的适应度值 (如果是第一次那肯定需要计算一次所有个体的适应度值)。

计算适应度值的适应度函数与之前比较没有发生变化(如果计算适应度函数都改变了,那当然需要重新估计适应度值了)。

函数返回值缓存描述符

为此我写了个装饰器来缓存函数的返回值:

classMemoized(object):

'''

Descriptor for population statistical varibles caching.

'''

def__init__(self,func):

self.func=func

self.result=None

self.fitness=None

def__get__(self,instance,cls):

self.instance=instance

returnself

def__call__(self,fitness):

if((notself.instance._updated)# population not changed

and(self.resultisnotNone)# result already cached

and(fitness==self.fitness)):# fitness not changed

# Return cached result directly.

returnself.result

else:

# Update fitness function.

self.fitness=fitness

# Update and memoize result.

self.result=self.func(self.instance,fitness)

# Recover flag.

self.instance._updated=False

returnself.result

动态监视种群的变化

好了上面我们可以通过描述符来缓存函数返回值,但是一旦种群不满足上述的三个条件就需要重新计算适应度值,那我们如何监控种群的变化呢?

我在GAPopulation中添加了一个标记_updated用于标记种群是否已经发生了变化, 然后我们的任务就是在其他能够影响到种群的地方试图去更新这个flag。

如何能更Pythonic的更新这个标记呢?

所谓的种群发生变化,也是就种群中的个体列表发生了变化,种群中的个体我都放在了一个列表中,我需要监控这个列表是否发生变化以便更新flag,具体又是那些变化呢?

列表整体是否发生了变化(赋值操作)

列表中的元素是否发生变化(对列表中的元素赋值操作,列表的append, extend操作等)

好了我们要具体怎么实现呢?

1)对于第一种,由于Python中无法进行赋值运算符重载,但是我们可以通过描述符的__set__来处理:

classGAIndividuals(object):

'''

Descriptor for all individuals in population.

'''

def__init__(self,name):

self.name='_{}'.format(name)

def__get__(self,instance,owner):

returninstance.__dict__[self.name]

def__set__(self,instance,value):

instance.__dict__[self.name]=value

# Update flag.

instance._updated=True

2)对于第二种情况,我们需要对Python的List类型的相应方法进行override

但是嘞,即使重写了list的接口,又如何更新population中的变量呢?这个时候就需要用闭包了。在GAPopulation的构造函数__init__中定义list的派生类,并立即实例化,这时候派生类的便可以获取population对象了,于是GAPopulation的构造函数可以这些写:

def__init__(self,indv_template,size=100):

# ...

# Flag for monitoring changes of population.

self._updated=False

# Container for all individuals.

classIndvList(list):

'''

A proxy class inherited from built-in list to contain all

individuals which can update the population._updated flag

automatically when its content is changed.

'''

# NOTE: Use 'this' here to avoid name conflict.

def__init__(this,*args):

super(this.__class__,this).__init__(*args)

def__setitem__(this,key,value):

'''

Override __setitem__ in built-in list type.

'''

old_value=this[key]

ifold_value==value:

return

super(this.__class__,self).__setitem__(key,value)

# Update population flag.

self._updated=True

defappend(this,item):

'''

Override append method of built-in list type.

'''

super(this.__class__,this).append(item)

# Update population flag.

self._updated=True

defextend(this,iterable_item):

ifnotiterable_item:

return

super(this.__class__,this).extend(iterable_item)

# Update population flag.

self._updated=True

self._individuals=IndvList()

优化效果

通过上面对代码的优化,我们看看我们优化的效果如何,使用分析描述符来分析GAEngine.run跑一代种群的情况,其中种群大小为10。如下图为cProfile生成的分析报告对比:

0818b9ca8b590ca3270a3433284dd417.png

可以看到优化后的跑一代种群的时间缩短为将近原来的1/7! 优化效果还是很明显的。

然后看一看调用关系图:

0818b9ca8b590ca3270a3433284dd417.png

energy_fitness的调用次数从3807降到了621次!

总结

本文记录了遗传算法框架GAFT的一次profiling和优化过程,通过缓存值的方式极大的减少了适值函数的调用次数,在时间上,跑一代种群的效率提升了7倍左右。

0818b9ca8b590ca3270a3433284dd417.png

来源:伯乐在线 - iPytLab

Python开发整理发布,转载请联系作者获得授权

0818b9ca8b590ca3270a3433284dd417.png

【点击成为安卓大神】

表情包
插入表情
评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符
相关推荐
<p> <b><span style="font-size:14px;"></span><span style="font-size:14px;background-color:#FFE500;">【Java面试宝典】</span></b><br /> <span style="font-size:14px;">1、68讲视频课,500道大厂Java常见面试题+100个Java面试技巧与答题公式+10万字核心知识解析+授课老师1对1面试指导+无限次回放</span><br /> <span style="font-size:14px;">2、这门课程基于胡书敏老师8年Java面试经验,调研近百家互联网公司及面试官的问题打造而成,从筛选简历和面试官角度,给出能帮助候选人能面试成功的面试技巧。</span><br /> <span style="font-size:14px;">3、通过学习这门课程,你能系统掌握Java核心、数据库、Java框架、分布式组件、Java简历准备、面试实战技巧等面试必考知识点。</span><br /> <span style="font-size:14px;">4、知识点+项目经验案例,每一个都能做为面试的作品展现。</span><br /> <span style="font-size:14px;">5、本课程已经在线下的培训课程中经过实际检验,老师每次培训结束后,都能帮助同学们运用面试技巧,成功找到更好的工作。</span><br /> <br /> <span style="font-size:14px;background-color:#FFE500;"><b>【超人气讲师】</b></span><br /> <span style="font-size:14px;">胡书敏 | 10年大厂工作经验,8年Java面试官经验,5年线下Java职业培训经验,5年架构师经验</span><br /> <br /> <span style="font-size:14px;background-color:#FFE500;"><b>【报名须知】</b></span><br /> <span style="font-size:14px;">上课模式是什么?</span><br /> <span style="font-size:14px;">课程采取录播模式,课程永久有效,可无限次观看</span><br /> <span style="font-size:14px;">课件、课程案例代码完全开放给你,你可以根据所学知识,自行修改、优化</span><br /> <br /> <br /> <span style="font-size:14px;background-color:#FFE500;"><strong>如何开始学习?</strong></span><br /> <span style="font-size:14px;">PC端:报名成功后可以直接进入课程学习</span><br /> <span style="font-size:14px;">移动端:<span style="font-family:Helvetica;font-size:14px;background-color:#FFFFFF;">CSDN 学院APP(注意不是CSDN APP哦)</span></span> </p>
©️2020 CSDN 皮肤主题: 1024 设计师:白松林 返回首页