模拟退火算法(附python代码)

https://www.cnblogs.com/ranjiewen/p/6084052.html这篇文章把模拟退火的来龙去脉讲得十分清楚,下面直接上代码。值得注意的是对于不同的问题,其中的关键一步随机扰动的选择是不一样的,如果选择不当会导致搜索域变小或者很难达到最优解(较优),并且求得的解不一定就是最优解。

Q1:第一个例子就是求一个一元函数y=x+10sin(5x)+7cos(4x)的最小值:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import math
import random
from mpl_toolkits.mplot3d import Axes3D

#曲线显示
x = np.linspace(0,10,10000)
y = x+10*np.sin(5*x)+7*np.cos(4*x)
plt.plot(x,y)

图像如图:

                         

模拟退火算法的关键两个函数就是判断函数和随机扰动函数的编码,其中判断函数要根据优化目标进行符号调整,而随机扰动函数要考虑问题的维度或者复杂度。

#判断函数
#返回1表示接受当前解,返回0表示拒绝当前
#当系统内能减少时(目标函数值减小,新解更理想),接收该解;
#当系统内能增加时(新解比旧解更差),以一定的概率接受当前解(当T变小时,probability在减小,于是接受更差的解的概率值越小,退火过程趋于稳定)
def Judge(deltaE,T):
    if deltaE < 0:
        return 1
    else:
        probability = math.exp(-deltaE/T)
        if probability > random.random():
            return 1
        else:
            return 0

这个问题的随机扰动函数就是让x在定义域内左右摆动(随机化):

#为当前解添加随机扰动
def Disturbance(low,high,x_old):
    if random.random()>0.5:
        x_new = x_old + (high - x_old) * random.random()
    else:
        x_new = x_old - (x_old - low) * random.random()
    return x_new

当然目标函数不能忘:

#优化目标函数
def ObjFun(x):
    y = x + 10 * math.sin(5 * x) + 7 * math.cos(4 * x)
    return y

迭代循环过程如下:

#参数设置
low = 0
high = 9
tmp = 1e5
tmp_min = 1e-3
alpha = 0.98

#初始化
x_old = (high-low) * random.random() + low
x_new = x_old
value_old = ObjFun(x_old)
value_new = value_old

counter = 0
record_x = []
record_y = []
while(tmp > tmp_min and counter <= 10000):
    x_new = Disturbance(low,high,x_old)
    value_new = ObjFun(x_new)
    deltaE = value_new - value_old
    if Judge(deltaE,tmp)==1:
        value_old=value_new
        record_x.append(x_new)
        record_y.append(value_new)
        x_old=x_new
    if deltaE < 0:
        tmp=tmp*alpha #deltaE < 0说明新解较为理想,继续沿着降温的方向寻找,减少跳出可能性;当温度减小,当前内能变化量的概率值会变小
    else:
        counter+=1

迭代过程自变量x和函数值变化曲线如下,最终都收敛由于稳定值:

                         

具体代码以及迭代动画如下:

#观察x的变化以及目标函数值的变化
length=len(record_x)
index=[i+1 for i in range(length)]
plt.plot(index,record_y)
plt.plot(index,record_x)

#动画绘制
fig, ax = plt.subplots()
l = ax.plot(x, y)
dot, = ax.plot([], [], 'ro')
def init():
    ax.set_xlim(0, 10)
    ax.set_ylim(-16, 25)
    return l
def gen_dot():
    for i in index:
        newdot = [record_x[i-1], record_y[i-1]]
        yield newdot
def update_dot(newd):
    dot.set_data(newd[0], newd[1])
    return dot,
ani = animation.FuncAnimation(fig, update_dot, frames = gen_dot, interval = 10, init_func=init)
plt.show()

Q2:求解二元函数y=ysin(2*pi*x)+xcos(2*pi*y)最大值

#三维曲面显示
X=np.linspace(-2,2,500)
Y=np.linspace(-2,2,500)
XX, YY = np.meshgrid(X, Y)
Z=YY*np.sin(2*math.pi*XX)+XX*np.cos(2*math.pi*YY)
fig = plt.figure()
ax = Axes3D(fig)
ax.plot_surface(XX, YY, Z,rstride=1, cstride=1, cmap='rainbow')
plt.show()

图像如下:

                         

注意现在的扰动函数是在x和y方向上都会随机变化:

#待优化(最大值)目标函数
def ObjFunc(x,y):
    z=y*math.sin(2*math.pi*x)+x*math.cos(2*math.pi*y)
    return z

#给旧点以随机扰动
def Disturb(point_old,LB_POINT,RT_POINT):
    if random.random()<0.5:
        point_old[0]=LB_POINT[0]+random.random()*(point_old[0]-LB_POINT[0])
    else:
        point_old[0]=point_old[0]+random.random()*(RT_POINT[0]-point_old[0])
    if random.random()<0.5:
        point_old[1]=LB_POINT[1]+random.random()*(point_old[1]-LB_POINT[1])
    else:
        point_old[1]=point_old[1]+random.random()*(RT_POINT[1]-point_old[1])
    return point_old[0],point_old[1]

def Judge2Max(deltaE,T):
    if deltaE>0:
        return 1
    else:
        probability=math.exp(deltaE/T)
        if probability>random.random():
            return 1
        else:
            return 0

LB_POINT = np.array([-2,-2])
RT_POINT=np.array([2,2])
x_old = 4*random.random()-2
y_old = 4*random.random()-2
x_new = x_old
y_new = y_old

point_old = np.array([x_old,y_old])
value_old = ObjFunc(x_old,y_old)
point_new = np.array([x_new,y_new])
value_new = value_old

tmp = 1e5
tmp_min = 1e-3
alpha = 0.98
counter = 0

record_coord = []
record_value = []
while(tmp >= tmp_min and counter <= 100000):
    point_new[0],point_new[1] = Disturb(point_old,LB_POINT,RT_POINT)
    value_new = ObjFunc(point_new[0],point_new[1])
    deltaE = value_new - value_old
    if Judge2Max(deltaE,tmp) == 1:
        record_coord.append(point_new.copy())
        record_value.append(value_new)
        point_old = point_new
        value_old = value_new
    if deltaE > 0:
        tmp = tmp * alpha
    else:
        counter += 1
        print(counter)

length=len(record_value)
x=[i+1 for i in range(length)]
plt.plot(x,record_value)
coor_x=[record_coord[i][0] for i in range(length)]
coor_y=[record_coord[i][1] for i in range(length)]
coordinates=[]
for i in range(length):
    coordinates.append([coor_x[0],coor_y[1]])
plt.plot(x,coor_x)
plt.plot(x,coor_y)

横坐标x和纵坐标y以及函数值的变化曲线如下:

                                  

                                       

                                          

Q3.经典的TSP问题

假设有这些城市,坐标如下:

cities_coords = np.array([[0,0],[1,10],[2,8],[9,5],[1.3,5],[7,9],[3,3],[6,4],[9,8],[8.1,6.8],[15,16],[12.5,18.6],[14.8,18.4],[2.3,15.7],[9.1,19]])

图示:

                               

            随机扰动函数要做的就是将随机的两个城市交换位置,其他代码类似:

def Route_Length(cities):
    s = 0
    for i in range(cities.shape[0]-1):
       s += math.sqrt(np.sum(np.power(cities[i+1,:]-cities[i,:],2)))
    return s

#这里的扰动函数将随机将两个点位坐标的位置进行原地交换
def Exchange(cities):
    n = cities.shape[0]
    i = 0
    j = 0
    while True:
        i = math.floor(random.random()*n)
        j = math.floor(random.random()*n)
        if i != j:
            break
    cities[[i,j],:] = cities[[j,i],:]
    print("exchange " + str(i) + " and " + str(j))
    return cities

def Judge(deltaE,T):
    if deltaE < 0:
        return 1
    else:
        probability = math.exp(-deltaE/T)
        if probability > random.random():
            return 1
        else:
            return 0

cities_old = cities_coords.copy()
length_old = Route_Length(cities_old)
cities_new = cities_coords.copy()
length_new = length_old

tmp = 1e5
tmp_min = 1e-5
alpha = 0.99
counter = 0

record_route = []
record_length = []
All_length = []
All_length.append(length_old)
while(tmp >= tmp_min and counter <= 1000000):
    cities_new = Exchange(cities_old)
    length_new = Route_Length(cities_new)
    All_length.append(length_new)
    deltaE = length_new - length_old
    if Judge(deltaE,tmp) == 1:
        record_route.append(cities_new.copy())
        record_length.append(length_new)
        cities_old = cities_new
        length_old = length_new
        print("第%d条线路的长度为:%0.2f" %(counter,record_length[-1]))
    print(str(counter)+"次迭代的路线总长度"+str(length_old))
    if deltaE < 0:
        tmp = tmp * alpha
    else:
        counter += 1

            执行结果如下:

具体路线如下,显然不是最优解只是个较优解:

猜你喜欢

转载自blog.csdn.net/To_be_to_thought/article/details/81914417
今日推荐