Моделируемый отжиг сходится к неправильным глобальным минимумам

Я реализовал симулированный отжиг, чтобы найти глобальные минимумы заданной функции, используя

https://perso.crans.org/besson/publis/notebooks/Simulated_annealing_in_Python.html

но хотя температура сначала высокая, а затем медленно снижается (из-за ступенек), но иногда это дает мне неправильный ответ. (локальные минимумы)

Я должен добавить, что я пытался решить проблему, используя случайное начало восхождения на холм, и ниже приведен список локальных минимумов в заданном интервале:

x = 0.55 0.75 0.95 1.15 1.35 1.54 1.74 1.94 2.14 2.34 2.5

y = -0.23 -0.37 -0.47 -0.57 -0.66 -0.68 -0.55 -0.16 0.65 2.10 5.06

и optimize.basinhopping()докажите, что глобальные минимумы равны (1.54, -.68)

вот код:

import math
import numpy as np
import numpy.random as rn
import matplotlib.pyplot as plt  # to plot
from scipy import optimize       # to compare
import seaborn as sns

def annealing(random_start, func, func_interval, random_neighbour, acceptance, temperature, maxsteps=1000, debug=True):
    """ Optimize the black-box function 'func' with the simulated annealing algorithm."""
    x = random_start(func_interval)
    y = func(x)
    x_list, y_list = [x], [y]
    for step in range(maxsteps):
        fraction = step / float(maxsteps)
        T = temperature(fraction)
        new_x = random_neighbour(x, func_interval, fraction)
        new_y = func(new_x)
        if debug: print("Step #{:>2}/{:>2} : T = {:>4.3g}, x = {:>4.3g}, y = {:>4.3g}, new_x = {:>4.3g}, new_y = {:>4.3g} ...".format(step, maxsteps, T, x, y, new_x, new_y))
        if acceptance_probability(y, new_y, T) > rn.random():
            x, y = new_x, new_y
            x_list.append(x)
            y_list.append(y)
            # print("  ==> Accept it!")
        # else:
        #    print("  ==> Reject it...")
    return x, func(x), x_list, y_list

def clip(x, func_interval):
    """ Force x to be in the interval."""
    a, b = func_interval
    return max(min(x, b), a)

def random_start(func_interval):
    """ Random point in the interval."""
    a, b = func_interval
    return a + (b - a) * rn.random_sample()


def random_neighbour(x, func_interval, fraction=1):
    """Move a little bit x, from the left or the right."""
    amplitude = (max(func_interval) - min(func_interval)) * fraction / 10
    delta = (-amplitude/2.) + amplitude * rn.random_sample()
    return clip(x + delta, func_interval)

def acceptance_probability(y, new_y, temperature):
    if new_y < y:
        # print("    - Acceptance probabilty = 1 as new_y = {} < y = {}...".format(new_y, y))
        return 1
    else:
        p = np.exp(- (new_y - y) / temperature)
        # print("    - Acceptance probabilty = {:.3g}...".format(p))
        return p

def temperature(fraction):
    """ Example of temperature dicreasing as the process goes on."""
    return max(0.01, min(1, 1 - fraction))

def see_annealing(x, y, x_list, y_list):
    sns.set(context="talk", style="darkgrid", palette="hls", font="sans-serif", font_scale=1.05)
    xs = np.linspace(func_interval[0], func_interval[1], 1000)  # Get 1000 evenly spaced numbers between .5 and 2.5
    plt.plot(xs, np.vectorize(func)(xs))
    plt.scatter(x_list, y_list, c="b")
    plt.scatter(x, y, c="r")
    plt.title("Simulated annealing")
    plt.show()

if __name__ == '__main__':
    func = lambda x: math.sin(10 * math.pi * x) / 2 * x + (x - 1) ** 4
    func_interval = (.5, 2.5)
    x, y, x_list, y_list = annealing(random_start, func, func_interval, random_neighbour, acceptance_probability, temperature, maxsteps=1000, debug=False)
    see_annealing(x, y, x_list, y_list)
    print(x, y)

    print(optimize.basinhopping(lambda x: math.sin(10*math.pi*x)/2*x + (x-1)**4, [random_start(func_interval)]))

Но что не так?

Редактировать:

@ user3184950 вы правы, алгоритм теперь работает лучше, но вот псевдокод из третьего издания AIMA введите здесь описание изображения

next — это просто случайно выбранный преемник текущего.

Кроме того, я написал заметку из своего курса ИИ о том, что имитация отжига гарантированно сходится к глобальному максимуму, если мы начнем с высокого T и будем уменьшать его достаточно медленно. (Я имею в виду, что мой профессор ничего не сказал о «следующей точке», или я как-то пропустил, а может это просто не важно).

Между прочим, я думал, что проблема связана с вероятностью взятия «следующей точки», если и y, и new_y отрицательны, то вероятность взятия следующей точки высока, даже если T достаточно мала. Например

введите здесь описание изображения

как вы можете видеть на шаге 891, и y, и new_y отрицательны, и мы берем new_y, однако T равно 0,109.

Опять же, проблема в том, что формула вероятности, приведенная в псевдокоде, совпадает с формулой вероятности, которую я использовал в своем коде.




Ответы (1)


Похоже сосед не оптимален.

def random_neighbour(x, func_interval, fraction=1):
"""Move a little bit x, from the left or the right."""
amplitude = (max(func_interval) - min(func_interval)) * 1 / (fraction + 0.1) 
delta = 1 * amplitude * (.5 - rn.random_sample()) 
print(delta)

return clip(x + delta, func_interval)

Вам нужно что-то, что будет двигаться влево/вправо с равной вероятностью, но может двигаться более случайно в начале отжига и меньше к концу.

Вышеприведенное — это просто предложение, которое, кажется, работает лучше.

person Willem Hendriks    schedule 27.05.2020
comment
Вы правы, но я думал, что выбор следующей точки является случайным и не имеет значения, как мы ее выбираем, проверьте часть редактирования вопроса. - person Me.a; 27.05.2020
comment
SA не заботится об абсолютном значении objective(). Чтобы было ясно, переход от -1000 к -999 имеет ту же вероятность, что и от -10 до -9. Так что только разница учитывается при принятии. В строке 891 в вашем примере разница составляет 0,118, то есть выше y. Правильно это принимается только с вероятностью от 0 до 1, что составляет 0,333. Если вы считаете, что на данном этапе отжига вероятность этого слишком высока, вам необходимо изменить схему охлаждения. Попробуйте другой старт/стоп и способ снижения T. - person Willem Hendriks; 27.05.2020