码迷,mamicode.com
首页 > 编程语言 > 详细

基于Python的模拟退火算法SA 以函数极值+TSP问题为例(gif动态展示)

时间:2020-07-12 01:05:49      阅读:95      评论:0      收藏:0      [点我收藏+]

标签:ima   wap   迭代   tst   save   idea   pop   random   super   

算法流程:

技术图片

 

 

技术图片

 

 

技术图片

 

 实现:

base.py

from abc import ABCMeta, abstractmethod
import types


class SkoBase(metaclass=ABCMeta):
    def register(self, operator_name, operator, *args, **kwargs):
        ‘‘‘
        regeister udf to the class
        :param operator_name: string
        :param operator: a function, operator itself
        :param args: arg of operator
        :param kwargs: kwargs of operator
        :return:
        ‘‘‘

        def operator_wapper(*wrapper_args):
            return operator(*(wrapper_args + args), **kwargs)

        setattr(self, operator_name, types.MethodType(operator_wapper, self))
        return self

# 空函数
class Problem(object):
    pass

sko目录下的operaters目录 mutation.py 用于TSP问题的状态空间转移函数的设计

import numpy as np


def mutation(self):
    ‘‘‘
    mutation of 0/1 type chromosome
    faster than `self.Chrom = (mask + self.Chrom) % 2`
    :param self:
    :return:
    ‘‘‘
    #
    mask = (np.random.rand(self.size_pop, self.len_chrom) < self.prob_mut)
    self.Chrom ^= mask
    return self.Chrom


def mutation_TSP_1(self):
    ‘‘‘
    every gene in every chromosome mutate
    :param self:
    :return:
    ‘‘‘
    for i in range(self.size_pop):
        for j in range(self.n_dim):
            if np.random.rand() < self.prob_mut:
                n = np.random.randint(0, self.len_chrom, 1)
                self.Chrom[i, j], self.Chrom[i, n] = self.Chrom[i, n], self.Chrom[i, j]
    return self.Chrom


# 交换两个点
def swap(individual):
    n1, n2 = np.random.randint(0, individual.shape[0] - 1, 2)
    if n1 >= n2:
        n1, n2 = n2, n1 + 1
    individual[n1], individual[n2] = individual[n2], individual[n1]
    return individual

# 逆转序列中指定的一段
def reverse(individual):
    ‘‘‘
    Reverse n1 to n2
    Also called `2-Opt`: removes two random edges, reconnecting them so they cross
    Karan Bhatia, "Genetic Algorithms and the Traveling Salesman Problem", 1994
    https://pdfs.semanticscholar.org/c5dd/3d8e97202f07f2e337a791c3bf81cd0bbb13.pdf
    ‘‘‘
    n1, n2 = np.random.randint(0, individual.shape[0] - 1, 2)
    if n1 >= n2:
        n1, n2 = n2, n1 + 1
    individual[n1:n2] = individual[n1:n2][::-1]
    return individual

# 分段交叉
def transpose(individual):
    # randomly generate n1 < n2 < n3. Notice: not equal
    n1, n2, n3 = sorted(np.random.randint(0, individual.shape[0] - 2, 3))
    n2 += 1
    n3 += 2
    slice1, slice2, slice3, slice4 = individual[0:n1], individual[n1:n2], individual[n2:n3 + 1], individual[n3 + 1:]
    individual = np.concatenate([slice1, slice3, slice2, slice4])
    return individual


def mutation_reverse(self):
    ‘‘‘
    Reverse
    :param self:
    :return:
    ‘‘‘
    for i in range(self.size_pop):
        if np.random.rand() < self.prob_mut:
            self.Chrom[i] = reverse(self.Chrom[i])
    return self.Chrom


def mutation_swap(self):
    for i in range(self.size_pop):
        if np.random.rand() < self.prob_mut:
            self.Chrom[i] = swap(self.Chrom[i])
    return self.Chrom

SA.py 定义了众多的模拟退火方案

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Time    : 2019/8/17
# @Author  : github.com/guofei9987

import numpy as np
from base import SkoBase
from sko.operators import mutation


class SimulatedAnnealingBase(SkoBase):
    """
    DO SA(Simulated Annealing)

    Parameters
    ----------------
    func : function
        The func you want to do optimal
    n_dim : int
        number of variables of func
    x0 : array, shape is n_dim
        initial solution
    T_max :float
        initial temperature
    T_min : float
        end temperature
    L : int
        num of iteration under every temperature(Long of Chain)

    Attributes
    ----------------------


    Examples
    -------------
    See https://github.com/guofei9987/scikit-opt/blob/master/examples/demo_sa.py
    """
#L是单次迭代中马尔科夫链的长度
    def __init__(self, func, x0, T_max=100, T_min=1e-7, L=300, max_stay_counter=150, **kwargs):
        assert T_max > T_min > 0, T_max > T_min > 0

        self.func = func
        self.T_max = T_max  # initial temperature
        self.T_min = T_min  # end temperature
        self.L = int(L)  # num of iteration under every temperature(also called Long of Chain)
        self.max_stay_counter = max_stay_counter  # stop if best_y stay unchanged over max_stay_counter times

        self.n_dims = len(x0)

        self.best_x = np.array(x0)  # initial solution
        self.best_y = self.func(self.best_x)
        self.T = self.T_max
        self.iter_cycle = 0
        self.best_y_history = [self.best_y]
        self.best_x_history = [self.best_x]

    def get_new_x(self, x):
        u = np.random.uniform(-1, 1, size=self.n_dims)#产生[-1,1]之间的随机数,维数指定
        x_new = x + 20 * np.sign(u) * self.T * ((1 + 1.0 / self.T) ** np.abs(u) - 1.0)
        return x_new

    def cool_down(self):
        self.T = self.T * 0.7

    def isclose(self, a, b, rel_tol=1e-09, abs_tol=1e-30):
        return abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)

    def run(self):
        x_current, y_current = self.best_x, self.best_y
        stay_counter = 0
        while True:
            for i in range(self.L):
                x_new = self.get_new_x(x_current)
                y_new = self.func(x_new)

                # Metropolis
                df = y_new - y_current
                if df < 0 or np.exp(-df / self.T) > np.random.rand():
                    x_current, y_current = x_new, y_new
                    if y_new < self.best_y:
                        self.best_x, self.best_y = x_new, y_new

            self.iter_cycle += 1
            self.cool_down()
            self.best_y_history.append(self.best_y)
            self.best_x_history.append(self.best_x)

            # if best_y stay for max_stay_counter times, stop iteration
            # 最后一次的best_y值和倒数第二次的best_y值一样,表示停留在平坦的状态
            if self.isclose(self.best_y_history[-1], self.best_y_history[-2]):
                stay_counter += 1
            else:
                stay_counter = 0

            if self.T < self.T_min:
                stop_code = Cooled to final temperature
                break
            if stay_counter > self.max_stay_counter:
                stop_code = Stay unchanged in the last {stay_counter} iterations.format(stay_counter=stay_counter)
                break

        return self.best_x, self.best_y

    fit = run


class SAFast(SimulatedAnnealingBase):
    ‘‘‘
    u ~ Uniform(0, 1, size = d)
    y = sgn(u - 0.5) * T * ((1 + 1/T)**abs(2*u - 1) - 1.0)

    xc = y * (upper - lower)
    x_new = x_old + xc

    c = n * exp(-n * quench)
    T_new = T0 * exp(-c * k**quench)
    ‘‘‘

    def __init__(self, func, x0, T_max=100, T_min=1e-7, L=300, max_stay_counter=150, **kwargs):
        super().__init__(func, x0, T_max, T_min, L, max_stay_counter, **kwargs)
        self.m, self.n, self.quench = kwargs.get(m, 1), kwargs.get(n, 1), kwargs.get(quench, 1)
        self.lower, self.upper = kwargs.get(lower, -10), kwargs.get(upper, 10)
        self.c = self.m * np.exp(-self.n * self.quench)

    def get_new_x(self, x):
        r = np.random.uniform(-1, 1, size=self.n_dims)
        xc = np.sign(r) * self.T * ((1 + 1.0 / self.T) ** np.abs(r) - 1.0)
        x_new = x + xc * (self.upper - self.lower)
        return x_new

    def cool_down(self):
        self.T = self.T_max * np.exp(-self.c * self.iter_cycle ** self.quench)


class SABoltzmann(SimulatedAnnealingBase):
    ‘‘‘
    std = minimum(sqrt(T) * ones(d), (upper - lower) / (3*learn_rate))
    y ~ Normal(0, std, size = d)
    x_new = x_old + learn_rate * y

    T_new = T0 / log(1 + k)
    ‘‘‘

    def __init__(self, func, x0, T_max=100, T_min=1e-7, L=300, max_stay_counter=150, **kwargs):
        super().__init__(func, x0, T_max, T_min, L, max_stay_counter, **kwargs)
        self.lower, self.upper = kwargs.get(lower, -10), kwargs.get(upper, 10)
        self.learn_rate = kwargs.get(learn_rate, 0.5)

    def get_new_x(self, x):
        std = min(np.sqrt(self.T), (self.upper - self.lower) / 3.0 / self.learn_rate) * np.ones(self.n_dims)
        xc = np.random.normal(0, 1.0, size=self.n_dims)
        x_new = x + xc * std * self.learn_rate
        return x_new

    def cool_down(self):
        self.T = self.T_max / np.log(self.iter_cycle + 1.0)


class SACauchy(SimulatedAnnealingBase):
    ‘‘‘
    u ~ Uniform(-pi/2, pi/2, size=d)
    xc = learn_rate * T * tan(u)
    x_new = x_old + xc

    T_new = T0 / (1 + k)
    ‘‘‘

    def __init__(self, func, x0, T_max=100, T_min=1e-7, L=300, max_stay_counter=150, **kwargs):
        super().__init__(func, x0, T_max, T_min, L, max_stay_counter, **kwargs)
        self.learn_rate = kwargs.get(learn_rate, 0.5)

    def get_new_x(self, x):
        u = np.random.uniform(-np.pi / 2, np.pi / 2, size=self.n_dims)
        xc = self.learn_rate * self.T * np.tan(u)
        x_new = x + xc
        return x_new

    def cool_down(self):
        self.T = self.T_max / (1 + self.iter_cycle)


# SA_fast is the default
SA = SAFast

class SA_TSP(SimulatedAnnealingBase):
    def cool_down(self):
        self.T = self.T_max / (1 + np.log(1 + self.iter_cycle))

    def get_new_x(self, x):
        x_new = x.copy()
        new_x_strategy = np.random.randint(3)
        if new_x_strategy == 0:
            x_new = mutation.swap(x_new)
        elif new_x_strategy == 1:
            x_new = mutation.reverse(x_new)
        elif new_x_strategy == 2:
            x_new = mutation.transpose(x_new)

        return x_new

demo_sa.py模拟退火的函数极值搜索案例,使用了一个著名的震荡函数,只能搜索到局部最优解,因为这个函数的极小值群体过于庞大

import numpy as np
# demo_func = lambda x: x[0] ** 2 + (x[1] - 0.05) ** 2 + x[2] ** 2
# 震荡函数,有无穷多个极小值点
demo_func = lambda x: 0.5+(np.square(np.sin(np.sqrt(x[0]**2+x[1]**2)))-0.5)/np.square(1+0.001*(x[0]**2+x[1]**2))

# %% Do SA
from sko.SA import SA

sa = SA(func=demo_func, x0=[1,1], T_max=1, T_min=1e-9, L=300, max_stay_counter=150)
best_x, best_y = sa.run()
print(best_x:, best_x, best_y, best_y)

# %% Plot the result
import matplotlib.pyplot as plt
import pandas as pd

plt.plot(pd.DataFrame(sa.best_y_history))
plt.show()

# %%
from sko.SA import SAFast

sa_fast = SAFast(func=demo_func, x0=[1, 1], T_max=1, T_min=1e-9, q=0.99, L=300, max_stay_counter=150)
sa_fast.run()
print(Fast Simulated Annealing: best_x is , sa_fast.best_x, best_y is , sa_fast.best_y)

# %%
from sko.SA import SABoltzmann

sa_boltzmann = SABoltzmann(func=demo_func, x0=[1, 1], T_max=1, T_min=1e-9, q=0.99, L=300, max_stay_counter=150)
sa_boltzmann.run()
print(Boltzmann Simulated Annealing: best_x is , sa_boltzmann.best_x, best_y is , sa_fast.best_y)

# %%
from sko.SA import SACauchy

sa_cauchy = SACauchy(func=demo_func, x0=[1, 1], T_max=1, T_min=1e-9, q=0.99, L=300, max_stay_counter=150)
sa_cauchy.run()
print(Cauchy Simulated Annealing: best_x is , sa_cauchy.best_x, best_y is , sa_cauchy.best_y)

得到的函数值下降曲线为

技术图片

 

 TSP问题的优化以及动态迭代过程展示

import numpy as np
from scipy import spatial
import matplotlib.pyplot as plt
import sys

file_name = sys.argv[1] if len(sys.argv) > 1 else data/nctu.csv
points_coordinate = np.loadtxt(file_name, delimiter=,)
num_points = points_coordinate.shape[0]
distance_matrix = spatial.distance.cdist(points_coordinate, points_coordinate, metric=euclidean)
distance_matrix = distance_matrix * 111000  # 1 degree of lat/lon ~ = 111000m


def cal_total_distance(routine):
    ‘‘‘The objective function. input routine, return total distance.
    cal_total_distance(np.arange(num_points))
    ‘‘‘
    num_points, = routine.shape
    return sum([distance_matrix[routine[i % num_points], routine[(i + 1) % num_points]] for i in range(num_points)])


# %%
from sko.SA import SA_TSP

sa_tsp = SA_TSP(func=cal_total_distance, x0=range(num_points), T_max=100, T_min=1, L=10 * num_points)

best_points, best_distance = sa_tsp.run()
print(best_points, best_distance, cal_total_distance(best_points))
# %% Plot the best routine
from matplotlib.ticker import FormatStrFormatter

fig, ax = plt.subplots(1, 2)

best_points_ = np.concatenate([best_points, [best_points[0]]])
best_points_coordinate = points_coordinate[best_points_, :]
ax[0].plot(sa_tsp.best_y_history)
ax[0].set_xlabel("Iteration")
ax[0].set_ylabel("Distance")
ax[1].plot(best_points_coordinate[:, 0], best_points_coordinate[:, 1],
           marker=o, markerfacecolor=b, color=c, linestyle=-)
ax[1].xaxis.set_major_formatter(FormatStrFormatter(%.3f))
ax[1].yaxis.set_major_formatter(FormatStrFormatter(%.3f))
ax[1].set_xlabel("Longitude")
ax[1].set_ylabel("Latitude")
plt.show()

# %% Plot the animation
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

best_x_history = sa_tsp.best_x_history

fig2, ax2 = plt.subplots(1, 1)
ax2.set_title(title, loc=center)
line = ax2.plot(points_coordinate[:, 0], points_coordinate[:, 1],
                marker=o, markerfacecolor=b, color=c, linestyle=-)
ax2.xaxis.set_major_formatter(FormatStrFormatter(%.3f))
ax2.yaxis.set_major_formatter(FormatStrFormatter(%.3f))
ax2.set_xlabel("Longitude")
ax2.set_ylabel("Latitude")
plt.ion()
p = plt.show()


def update_scatter(frame):
    ax2.set_title(iter =  + str(frame))
    points = best_x_history[frame]
    points = np.concatenate([points, [points[0]]])
    point_coordinate = points_coordinate[points, :]
    plt.setp(line, xdata, point_coordinate[:, 0], ydata, point_coordinate[:, 1])
    return line


ani = FuncAnimation(fig2, update_scatter, blit=True, interval=25, frames=len(best_x_history))
plt.show()

# ani.save(‘sa_tsp.gif‘, writer=‘pillow‘)

data/nctu.csv 文件

24.783003,120.997474
24.785737,120.996905
24.784604,120.998911
24.785261,120.998698
24.785266,120.996754
24.784001,120.998107
24.783266,120.99845
24.785178,120.994979
24.784736,120.995445
24.785626,120.995474
24.784542,120.995755
24.788155,120.99547
24.788339,120.995857
24.78886,120.995259
24.788192,120.995671
24.786551,120.995681
24.787233,120.995651
24.786942,120.995354
24.788947,120.994135
24.789596,120.995856
24.78969,120.995223
24.789652,120.994996
24.788933,120.995836
24.789182,120.997927
24.786731,121.001706
24.787541,120.998777
24.786444,120.997757
24.788442,120.999333
24.787059,120.996254
24.788719,120.997473
24.787729,121.000022
24.786509,120.997
24.787374,121.000994
24.787748,120.997273
24.789707,120.99633
24.785103,121.000918
24.787056,120.997282
24.785563,120.998591
24.79033,120.997038
24.785684,120.999245
24.788844,120.999945
24.789328,120.997568
24.786955,120.999126
24.78872,120.999253
24.78777,120.998151
24.788856,120.999068
24.787943,120.996833
24.788764,120.996599
24.784636,120.999428
24.784805,120.99954
24.784957,120.997073
24.783229,120.997842
24.785159,120.998036
24.787014,120.998277
24.787786,121.000889
24.788249,121.000433
24.78445,120.997293
24.786209,120.999259
24.788648,121.000958
24.787658,120.999493
24.784886,121.000451
24.78455,120.997889
24.786806,121.000725
24.786624,120.999088
24.784222,120.996917
24.787919,120.996822
24.788746,121.001269
24.788667,120.998652
24.783467,120.997571
24.788086,121.001596
24.788868,120.998306
24.786342,121.000484
24.785605,120.997364
24.787122,120.998925
24.785791,120.998019
24.786612,120.996584
24.786708,120.999975
24.78581,121.000076

技术图片

 

 gif迭代过程展示,gif太大无法展示,可以通过上面的Python源码运行后进行保存即可,根据每次迭代,动态更新的,可以看出TSP问题求解的过程

技术图片

 

 

 

 

基于Python的模拟退火算法SA 以函数极值+TSP问题为例(gif动态展示)

标签:ima   wap   迭代   tst   save   idea   pop   random   super   

原文地址:https://www.cnblogs.com/randy-lo/p/13286020.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!