【问题标题】:How to maximize non-analytic function over space of variables如何在变量空间上最大化非解析函数
【发布时间】:2015-07-24 01:46:20
【问题描述】:

我有一个非常简单的问题,我认为网上可能有解决方案,但我还没有找到。

我有一个(非数学,即非分析)函数,它根据来自一组文件/数据库/在线抓取的一组变量 a、b、c、d 计算值 F,我想找到使 F 最大化的变量 a、b、c、d 的集合。搜索 a、b、c、d 的整个空间是不可行的,并且由于 F 不是分析性的,因此无法使用微分/导数。我真的很感激一个指向我可以使用哪些包/算法的指针,或者只是如何开始。我在网上看到的关于 python 优化的大部分内容似乎都是关于分析/数学函数 (f(x) = x^2 + ...),而不是更多的非分析问题。

例如:

def F(a,b,c,d):
   ... a lot of computations from databases, etc using 
   a,b,c,d that are different float values ...
   returns output # output is a float

现在,对于 a,b,c,d 的所有值,其中每个值都有可能的值,假设为 [0, 0.1, 0.2, ... 1.0]。这些值是离散的,我的优化不需要极高的精度。

现在,我想找到给我最高 F 的值 a、b、c、d 的集合。

哦,我对 F、a、b、c、d 都没有最大化约束。

【问题讨论】:

    标签: python search optimization


    【解决方案1】:

    对于非解析函数,您可以使用遗传算法或类似的进化计算来探索参数空间。然后在结果空间中寻找最大值或“山丘”,以找到最大化您的功能的解决方案。我建议使用库而不是自己编写; DEAP 看起来很有希望。

    【讨论】:

    • 感谢您的评论。我一直在研究 DEAP,它看起来很有希望。我会尝试发布我的解决方案!
    • 您的评论最终成为了一个很好的指针,我能够使用 GA 来解决我的问题。见下面的解决方案(现在写)
    • 很高兴听到这个消息!几年前,我使用 GA 解决方案来优化没有“正确”答案的交易系统的参数。我只需评估参数空间即可查看大部分山丘的分组位置。
    【解决方案2】:

    你已经很好地解决了你的问题。如您所知,这就是空间搜索

    除了 Prolog(它实际上是解决方案引擎的语言本身)之外,我不知道用任何语言执行此操作的库,但最常用的空间搜索算法之一是“A 星”搜索,也是称为“启发式优化”。您的问题看起来很适合 A-star 搜索的一个很好的邻居,称为“贪婪的最佳优先搜索”。

    您基本上从一组参数开始,在这些参数上调用F,然后稍微调整每个参数以查看F 的变化。你“上坡”,接受使F 增加最多的调整,并可能留出“其他”路径以供稍后搜索。这会贪婪地将您推向“山顶” - 局部最大值。一旦达到局部最大值,您可以尝试从一些随机的参数组合再次搜索。您甚至可以使用模拟退火之类的方法来减少随着时间的推移调整参数的数量 - 首先搜索非常混乱,然后在您知道模糊的问题地形后安定下来。

    保证最佳结果的唯一方法是进行完整的搜索,例如 BFS,但是有许多好的方法可以使您在概率上很可能获得最佳结果。哪一个能最快地给你最好的结果取决于F:如果输入和输出之间的映射至少接近到一个几乎没有不连续性的连续拓扑,我在这里介绍的爬山是最好的。

    【讨论】:

    • 谢谢,这很有帮助,证实了我的怀疑。我将研究 A-star 并尝试发布我的解决方案代码!你太棒了!
    • 感谢您的回答。我改用 GA 是因为它更适合我的问题,只是发布了我的解决方案。
    【解决方案3】:

    感谢大家的帮助和这里的 cmets,我能够建立一个答案。 DEAP 文档非常有用,但无论如何我还是想分享我的答案以及一些希望对其他人有所帮助的 cmets。

    我使用了此处 https://github.com/DEAP/deap/blob/b46dde2b74a3876142fdcc40fdf7b5caaa5ea1f4/examples/ga/onemax.py 的 OneMax 示例以及此处的演练:https://deap.readthedocs.org/en/latest/examples/ga_onemax.html。我发现这两页也非常宝贵,运算符:https://deap.readthedocs.org/en/latest/tutorials/basic/part2.html#next-step 和创建类型:https://deap.readthedocs.org/en/latest/tutorials/basic/part1.html#creating-types

    所以我的解决方案就在这里(抱歉格式化,这是我第一次发布长代码并添加 cmets)。基本上,您真正需要做的就是使适应度评估函数(此处为eval_Inidividual)成为您要优化的函数 F。您可以通过在初始化(此处为 random_initialization)和突变(此处为 mutate_inputs)时限制它们的可能值来限制 F 的 N 个变量/输入中的每一个的范围(和分布)。

    最后一点:我使用多处理库编写了多核代码(只需两行代码并更改您使用的映射函数!)。在此处阅读更多信息:https://deap.readthedocs.org/en/default/tutorials/distribution.html

    代码(阅读我的 cmets 以获得更多解释):

    import random
    
    from deap import base
    from deap import creator
    from deap import tools
    
    start_clock = time.clock()
    
    
    NGEN =  100  # number of generations to run evolution on
    pop_size = 10000 # number of individuals in the population. this is the number of points in the N-dimensional space you start within
    CXPB  = 0.5 # probability of cross-over (reproduction) to replace individuals in population by their offspring
    MUTPB = 0.2 # probability of mutation
    mutation_inside = 0.05 # prob mutation within individual
    num_cores = 6
    N = 8 # the number of variables you are trying to optimize over. you can limit the range (and distribtuion) of each of them by limiting their possible values at initialization and mutation.
    
    
    def eval_Inidividual(individual):
        # this code runs on your individual and outputs the 'fitness' of the individual
    
    def mutate_inputs(individual, indpb):
        # this is my own written mutation function that takes an individual and changes each element in the tuple with probability indpb 
        # there are many great built in such mutation functions
    
    def random_initialization():
        # this creates each individual with an N-tuple where N is the number of variables you are optimizing over
    
    creator.create("FitnessMax", base.Fitness, weights=(-1.0,)) # negative if trying to minimize mean, positive if trying to maximize sharpe. can be a tuple if you are trying to maximize/minimize over several outputs at the same time e.g. maximize mean, minimize std for fitness function that returns (mean, std) would need you to use (1.0, -1.0)
    creator.create("Individual", list, fitness=creator.FitnessMax)
    
    toolbox = base.Toolbox()
    # Attribute generator
    toolbox.register("attr_floatzzz", random_initialization) # i call it attr_floatzzz to make sure you know you can call it whatever you want.
    # Structure initializers
    toolbox.register("individual", tools.initRepeat, creator.Individual, 
        toolbox.attr_floatzzz, N) # N is the number of variables in your individual e.g [.5,.5,.5,.5,.1,100] that get 
    # fed to your fitness function evalOneMax
    toolbox.register("population", tools.initRepeat, list, toolbox.individual)
    
    import multiprocessing as mp
    
    pool = mp.Pool(processes=num_cores)
    toolbox.register("map", pool.map) # these 2 lines allow you to run the computation multicore. You will need to change the map functions everywhere to toolbox.map to tell the algorithm to use a multicored map
    
    # Operator registering
    toolbox.register("evaluate", eval_Inidividual)
    toolbox.register("mate", tools.cxTwoPoint)
    toolbox.register("mutate", mutate_inputs, indpb = mutation_inside)
    toolbox.register("select", tools.selTournament, tournsize=3)
    
    
    def main():
    #     random.seed(64)
    
        pop = toolbox.population(n=pop_size) # these are the different individuals in this population, 
                                        # each is a random combination of the N variables
    
        print("Start of evolution")
    
        # Evaluate the entire population
        fitnesses = list(toolbox.map(toolbox.evaluate, pop))
        for ind, fit in zip(pop, fitnesses):
            ind.fitness.values = fit   #this runs the fitness (min mean on each of the individuals)
    
    #     print("  Evaluated %i individuals" % len(pop))
    
        # Begin the evolution
        for g in range(NGEN):
            print("-- Generation %i --" % g)
            f.write("-- Generation %i --\n" % g)
    #         f.write("-- Generation %i --\n" % g)
    #         g = open('GA_generation.txt','w')
    #         g.write("-- Generation %i --" % g)
    #         g.close()
    
            # Select the next generation individuals
            offspring = toolbox.select(pop, len(pop)) # this selects the best individuals in the population
            # Clone the selected individuals
            offspring = list(toolbox.map(toolbox.clone, offspring)) #ensures we don’t own a reference to the individuals but an completely independent instance.
    
            # Apply crossover and mutation on the offspring
            for child1, child2 in zip(offspring[::2], offspring[1::2]): #this takes all the odd-indexed and even-indexed pairs child1, child2 and mates them
                if random.random() < CXPB:
                    toolbox.mate(child1, child2)
                    del child1.fitness.values
                    del child2.fitness.values
    
            for mutant in offspring:
                if random.random() < MUTPB:
                    toolbox.mutate(mutant)
                    del mutant.fitness.values
    
            # Evaluate the individuals with an invalid fitness
            invalid_ind = [ind for ind in offspring if not ind.fitness.valid]
            fitnesses = toolbox.map(toolbox.evaluate, invalid_ind)
            for ind, fit in zip(invalid_ind, fitnesses):
                ind.fitness.values = fit
    
    #         print("  Evaluated %i individuals" % len(invalid_ind))
    
            # The population is entirely replaced by the offspring
            pop[:] = offspring
    
            # Gather all the fitnesses in one list and print the stats
            fits = [ind.fitness.values[0] for ind in pop]
    
    #         length = len(pop)
    #         mean = sum(fits) / length
    #         sum2 = sum(x*x for x in fits)
    #         std = abs(sum2 / length - mean**2)**0.5
    
            print("  Min %s" % min(fits))
    #         print("  Max %s" % max(fits))
    #         print("  Avg %s" % mean)
    #         print("  Std %s" % std)
    
        print("-- End of (successful) evolution --")
    
        best_ind = tools.selBest(pop, 1)[0]
        print("Best individual is %s with mean %s" % (best_ind, 
            best_ind.fitness.values[0])
    
        done = time.clock() - start_clock # the clock doens't work on the multicored version. I have no idea how to make it work :)
        print "time taken: ", done, 'seconds'
    
    
    if __name__ == "__main__":
        main()
    

    p.s.:时钟在多核版本上不起作用。我不知道如何使它工作:)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-02-16
      • 1970-01-01
      • 2018-01-06
      • 1970-01-01
      • 2020-10-31
      • 2012-10-22
      • 2013-11-04
      相关资源
      最近更新 更多