如何在python中加速非线性优化的多次迭代?
我正在尝试使用 优化类是单独定义的,然后通过单独的函数调用如何在python中加速非线性优化的多次迭代?,python,numpy,optimization,parallel-processing,python-multiprocessing,Python,Numpy,Optimization,Parallel Processing,Python Multiprocessing,我正在尝试使用 优化类是单独定义的,然后通过单独的函数调用dyn\u optimGMO。该优化必须完成并保存,例如,由变量(节点)inits(或init_val) 使用timeit模块,我发现每次迭代大约需要17秒才能完成。这意味着1000次迭代大约需要5小时。这是一个非常长的时间 如果我必须对20个扰动节点重复此操作,那么总迭代次数将转到200000,这将需要上面计算的线性时间 我试图通过使用pythonmultiprocessingmodule来解决这个问题,为20个扰动节点中的每个节点并行
dyn\u optimGMO
。该优化必须完成并保存,例如,由变量(节点)inits(或init_val)
使用timeit
模块,我发现每次迭代大约需要17秒才能完成。这意味着1000次迭代大约需要5小时
。这是一个非常长的时间
如果我必须对20个扰动
节点重复此操作,那么总迭代次数将转到200000
,这将需要上面计算的线性时间
我试图通过使用pythonmultiprocessing
module来解决这个问题,为20个扰动节点中的每个节点并行化每组1000次迭代。但这没用
我还尝试使用Numba jit函数,但它们无法识别pyGMO模块,因此失败
有没有什么方法可以并行化这段代码,并使它在任何迭代次数下都更快
请让我知道我的问题是否足够清楚,如果不清楚,我将根据需要添加详细信息
import numpy as np
import pygmo as pg
matL = np.random.rand(300,300) ; node_len = 300
inits = []; results = []
perturb = {25:0} #setting a random node, say, node 25 to 0
class my_constrained_udp:
def __init__(self):
pass
def fitness(self, x):
matA = np.matrix(x)
obj1 = matA.dot(matL).dot(matA.T)[0,0] #this is int value
ce1 = sum(init_val) - sum(x)
return [obj1, ce1]
def n_objs(self): # no of objectives
return 1
def get_nec(self): #no of equality constraints
return 1
def get_nic(self): #no of in-equality constraints
return 0
def get_bounds(self): #lower and upper bounds: use this to perturb the node
lowerB = np.array([0]*node_len); upperB = np.array([1]*node_len)
if perturb:
for k,v in perturb.items():
lowerB[k] = v; upperB[k] = v
return (lowerB,upperB)
def gradient(self, x):
return pg.estimate_gradient_h(lambda x: self.fitness(x), x)
def dyn_optimGMO(matL, node_len ,init):
if perturb:
for k,v in perturb.items(): init_val[k] = v #setting perturbations in initial values
inner_algo = pg.nlopt("slsqp"); inner_algo.maxeval = 5
algo = pg.algorithm(uda = pg.mbh(inner_algo, stop = 2, perturb = .2))
#algo.set_verbosity(10) # in this case this correspond to logs each 1 call to slsqp
pop = pg.population(prob = my_constrained_udp(), size = 1000 , seed=123)
pop.problem.c_tol = [1E-6] * 1 # get_nec + get_nic = 1, so multiplied by 1
pop = algo.evolve(pop)
res = pop.champion_x
return res
# running above optimization code for 1000 random initializations
for i in range(1000):
init_val = np.array([random.uniform(0, 1) for k in range(node_len)])
if perturb:
for k,v in perturb.items(): init_val[k] = v #setting perturbations in initial values
res = dyn_optimGMO(matL ,node_len ,init_val) # this function is defined here only
inits.append(init_val); results.append(res)
编辑1:
正如@Ananda在下面所建议的,我对目标函数进行了修改,使运行时间减少了近7倍。我已经重写了代码,使用pythonmultiprocessing
模块在1000次迭代中运行此代码。下面是我的新代码,我试图在其中生成并行处理迭代的进程。因为我的系统只有8个线程,所以我将池大小限制为5个,因为PyGMO也使用内部并行,并且它也需要一些线程
import numpy as np
import pygmo as pg
matL = np.random.rand(300,300) ; node_len = 300
perturb = {12:1} # assign your perturb ID here
def optimizationFN(var):
results = []
inits = var[0]; perturb = var[1]
class my_constrained_udp:
def fitness(self, x):
obj1 = x[None,:] @ matL @ x[:,None] # @ is mat multiplication operator
ce1 = np.sum(inits) - np.sum(x)
return [obj1, ce1]
def n_objs(self): # no of objectives
return 1
def get_nec(self): #no of equality constraints
return 1
def get_nic(self): #no of in-equality constraints
return 0
def get_bounds(self): #lower and upper bounds: use this to perturb the node
lowerB = np.array([0]*node_len); upperB = np.array([1]*node_len)
if perturb:
for k,v in perturb.items():
lowerB[k] = v; upperB[k] = v
return (lowerB,upperB)
def gradient(self, x):
return pg.estimate_gradient_h(lambda x: self.fitness(x), x)
def dyn_optimGMO(matL, node_len ,inits):
'''
perturb should be a dict of node index and value as 0 or 1. Type(node_index) = int
'''
if perturb:
for k,v in perturb.items(): inits[k] = v #setting perturbations in initial values
inner_algo = pg.nlopt("slsqp"); inner_algo.maxeval = 5
algo = pg.algorithm(uda = pg.mbh(inner_algo, stop = 2, perturb = .2))
#algo.set_verbosity(10) # in this case this correspond to logs each 1 call to slsqp
pop = pg.population(prob = my_constrained_udp(), size = 100 , seed=123)
pop.problem.c_tol = [1E-6] * 1 # get_nec + get_nic = 1, so multiplied by 1
pop = algo.evolve(pop)
res = pop.champion_x
return res
if perturb:
for k,v in perturb.items(): inits[k] = v #setting perturbations in initial values
res = dyn_optimGMO(matL ,node_len ,inits) # this function is defined here only
results.append(res)
return results
import time
st = time.time()
#1000 random initialisations
initial_vals = []
for i in range(1000): initial_vals.append(np.array([random.uniform(0, 1) for k in range(node_len)]))
initial_vals = np.array(initial_vals)
inp_val = []
for i in range(len(initial_vals)): inp_val.append([initial_vals[i],perturb])
#running evaluations
#eqVal = optimizationFN(initial_vals,perturb=perturb)
from multiprocessing import Pool
myPool = Pool(8)
data = myPool.map(optimizationFN,inp_val)
myPool.close(); myPool.join()
print('Total Time: ',round(time.time()-st,4))
这将在1.13小时内执行整个1000次迭代
不过,是否还有其他可能使其更快?在尝试并行化等之前,请尝试找出性能的瓶颈,并尝试解决这一问题
如果您使用
如您所见,大部分时间都花在dot
和sum
函数中,创建matA
也花费了大量时间
我会像这样重写函数-
def fitness(self, x):
obj1 = x[None, :] @ matL @ x[:, None]
ce1 = np.sum(init_val) - np.sum(x)
return [obj1, ce1]
如果你配置了这个函数,你可以看到
Line # Hits Time Per Hit % Time Line Contents
==============================================================
20 @profile
21 def fitness(self, x):
22
23 77084 3151649.0 40.9 48.9 obj1 = x[None, :] @ matL @ x[:, None]
24 77084 3214012.0 41.7 49.9 ce1 = np.sum(init_val) - np.sum(x)
25
26 77084 79439.0 1.0 1.2 return [obj1, ce1]
完整功能的每次点击总时间从380左右下降到80
np.建议不要再使用矩阵
方法,该方法将被弃用。使用本地pythonsum
而不是np.sum
可以大大降低性能
在我的机器上,它将性能从33秒/次提高到6秒/次。性能提高约5倍
Q:“是否有任何方法可以并行化此代码,并使其在任何迭代次数下都更快?”
对。如果试图numba.jit()
代码“来自外部”(由于numba编译警告的原因失败),您可以尝试分发该批约1k+独立初始化的部分,并让它们并行计算,然后收集结果
这样做的好处是在一个快照中将性能提高1000倍,并且可以进一步扩展
如果您使用的是一个包含约1k+节点的大学集群,那么1k批计算可以在1k长序列的第一次单独运行的时间内产生结果(此处的通信成本可以忽略不计,请参阅).CUDA与您的问题完全无关,我已经删除了标记。根据我的判断,代码有缺陷,请发布一个工作代码。对我来说,这是在扰动中k,v的处抛出错误。items():init_val[k]=v
@Ananda完成了编辑。现在它应该在运行了,你要求的是代码检查——这并不是StackOverflow的真正目的。如果您对如何改进这一点有具体的想法,并且您已经尝试过,但遇到了麻烦,请描述具体的问题并征求建议。您是在要求其他人“找到一种方法使其快速运行或进一步并行化”——即代码审查?您需要解决哪些具体问题?你试了什么?你有什么问题?你使用的分析器是什么?@Naveen我使用的是line_profiler这实际上大大提高了运行时间,在我的机器上几乎快了7倍。LineProfiler是一个很好的工具。但是,你也能建议一种并行化上述执行以使它更快的方法吗?我怀疑,如果你想把它推到比这更大的位置,你可能会从Python移开,使用C++或者别的什么东西。据我所知,在python中,多处理+确保事情正确矢量化可能是最好的方法。单击“更多和更多向下投票”不会创建更强的反参数。在许多独立初始化的进化模型中,大规模并行过程流是最强大的性能助推器。以这种方式处理相同大小的批(数量)PyGMO作业要比以纯[串行]队列顺序处理它们快得多,这有利于加速模拟管道的某些部分。否决票在这方面一点也没有改变,也没有产生任何有意义的反论点——因此它们表现出纯粹的仇恨和愤怒行为,而不是我没有投票的任何好处,但我想知道否决票是否是因为人们告诉你一段时间的原因:这是一篇没有说太多的浮夸文章,并且过度使用了格式,这使得帖子更难阅读。你介意解释一下@halfer你从哪里得出了“不太多”的结论吗?是的。它a)说明了为什么numba相关的期望失败了,并将再次失败+b)将用户引导到唯一可行的方向,以真正提高E2E性能(使用分布式c
Line # Hits Time Per Hit % Time Line Contents
==============================================================
20 @profile
21 def fitness(self, x):
22
23 77084 3151649.0 40.9 48.9 obj1 = x[None, :] @ matL @ x[:, None]
24 77084 3214012.0 41.7 49.9 ce1 = np.sum(init_val) - np.sum(x)
25
26 77084 79439.0 1.0 1.2 return [obj1, ce1]