解空间bound、目标函数func、初始解s
求解函数 y = s i n ( x 2 − 1 ) + 2 c o s ( 2 x ) , x ∈ [ − 8 , 8 ] y=sin(x^2-1)+2cos(2x),x\in[-8,8] y=sin(x2−1)+2cos(2x),x∈[−8,8]的极大值
# 本段代码用于绘制函数图像
import numpy as np
import matplotlib.pyplot as plt
bound = [-8, 8]
func = lambda x: np.sin(x**2-1)+2*np.cos(2*x)
plt.ylim([-4,4])
inputs=np.arange(bound[0],bound[1],0.1)
plt.title(r'$y=sin(x^2-1)+2cos(2x)$')
plt.xlabel('x')
plt.ylabel('f')
# 绘制两条正交直线
plt.axvline(x=[0.0],ls='--',color='g')
plt.axhline(y=[0.0],ls='--',color='g')
y=[func(x) for x in inputs]
plt.plot(inputs,y,'--')
xmax=inputs[y.index(max(y))]
# print(xmax,max(y))
plt.scatter(xmax,max(y),color='r')
plt.annotate(f'{round(xmax,2),round(max(y),2)}',xy=(xmax,max(y)),xytext=(round(xmax-1.5,2),round(max(y)+0.5,2)))
plt.show()
函数图像如下
import numpy as np
import random
import matplotlib.pyplot as plt
# 设置参数:温度、最低温度、降温系数、解空间
T = 20000
Tmin = 1e-4
delta = 0.95
bound = [-8, 8]
# 定义目标函数,注意我们要求的是极大值,函数值取反即可
func = lambda x: -(np.sin(x ** 2 - 1) + 2 * np.cos(2 * x))
# 初始值定义(也表示最优值)
x = random.uniform(bound[0], bound[1])
f = func(x)
# 开启matplotlib交互模式,准备绘制动图
plt.ion()
# 绘制目标函数图像
inputs = np.arange(bound[0], bound[1], 0.1)
plt.plot(inputs, [func(x) for x in inputs], '--')
plt.ylim([-4, 4])
epoch = 0
while T > Tmin:
is_change = False
# 产生新解及新函数值
x_new = x + (random.random() * 2 - 1) * T
while not -8 < x_new < 8:# 保证生成的解在解空间中,使得每次温度降低是有意义的
x_new = x + (random.random() * 2 - 1) * T
f_new = func(x_new)
# 计算增量
if f_new < f: # 以1的概率接受新解
x, f = x_new, f_new
is_change = True
else:
if pow(np.e, (f - f_new) / T) > random.random(): # 根据Metropolis准则接受新解
x, f = x_new, f_new
is_change = True
plt.title(f'epoch:{epoch},ischange:{is_change}')
plt.plot(x_new, f_new, 'go')
plt.pause(0.5)
T *= delta
epoch += 1
plt.ioff()
plt.show()
print(f'函数的最大值为:{f},此时变量的值为:{x}')
>>> 函数的最值为:-2.9098918846391335,此时变量的值为:-3.0008540944272024
运行结果
加速(设置plt.pause()即可)
最终效果
上图中红色的点表明新解没有被Metropolis准则接受
遗留的问题:在程序运行的过程中笔者观察到下面的规律
- 红色的点在开始的时候不会生成,也就是说温度较高的时候,产生的新解几乎全部被接受了,这应该是模拟退火算法的一个特点,但是笔者可能还不太明白
- 在程序即将结束时,红色的点和绿色的点在极值点附近反复生成(如图中点稠密的位置),这个原因笔者暂时也没有弄明白
首先观察程序的运行结果,与原图中我们得到的极值点几乎是完全一样的,也就是说我们花费了这么久研究算法得到的结果是有意义的!
得到了满意的结果之后,我们再来分析一下过程:实际上,笔者在研究退火算法时发现,它与爬山算法的代码十分相近,但不同的是,退火算法的Metropolis准则十分有效,他可以保证算法不拘泥于局部最优解,而是以一定的概率探索新的解
在上面的程序中,当新解对应的函数值f_new
然后我们再来观察一下求解过程的可视化图像,其中红点表示的是:在此次循环中产生的新解与旧解相比较次,而且没有被Metropolis准则接受,我们再来看下面两个图
# 本段程序用于生成图一
plt.ylim([-0.1,1.1])
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
plt.title(r'$f_{new} \geq f$情况下:$e^{\frac{f-f_{new}}{T}}$与P的比较')
plt.scatter(range(len(list1)),[float(i) for i in list1.keys()],color='g',label=r'$e^{\frac{f-f_{new}}{T}}$')
plt.scatter(range(len(list1)),list(list1.values()),color='b',label='P')
plt.xlabel('次数')
plt.ylabel('概率值大小')
plt.legend(loc='upper right')
plt.show()
运行结果
# 本段程序用于生成图二
plt.subplots_adjust(hspace=1)
plt.subplot(211)
plt.title('温度T的变化')
plt.plot(range(len(t_list)),t_list,color='g')
plt.subplot(212)
plt.title('是否更新')
plt.scatter(change.keys(),change.values(),color='b')
plt.show()
运行结果
我们可以从上面的图中发现在温度较高的时候, e f − f n e w T e^{\frac{f-f_{new}}{T}} eTf−fnew的值会比较大。此时在Metropolis准则的作用下新解被接受,也就是说,即使现在产生的新解并不是很理想,算法还是决定接受,这样做有利于在解空间中寻找另外的优解而不拘泥于局部极值,实际上从上面的动态图中我们也可以看出来这一过程:在开始的时候即使下一个点对应的函数值大于前一个点对应的函数值,但是它仍然是绿色的,而且点的出现不是单区域爆发性的,而是整个区域内都有生成