爬山算法的缺点 爬山算法是一种简单的贪心搜索算法,它的原理是,只向高出走,如果发现移动后的高度变小了,那么就不移动,如果高度变高了就移动。该算法每次从当前解的临近解空间中选择一个最优解作为当前解,直到达到一个局部最优解。
爬山算法实现很简单,其主要缺点是会陷入局部最优解 ,而不一定能搜索到全局最优解。假设B
点为当前解,爬山算法搜索到A
点这个局部最优解 就可能会停止搜索,因为在A
点无论向那个方向小幅度移动都不能得到更优的解。
模拟退火算法实现 模拟退火算法从某一较高初温出发,伴随温度参数的不断下降,结合 一定的概率 突跳特性 在解空间中随机寻找目标函数的全局最优解,即在局部最优解能概率性地跳出并最终趋于全局最优。
核心思想:服从目标就一定接受目标,如果不服从目标就按概率接受。关键点就是这个概率,模拟每一步走的步长也是关键点。
爬山算法和模拟退火的比喻
爬山算法:兔子朝着比现在高的地方跳去。它找到了不远处的最高山峰。但是这座山不一定是珠穆朗玛峰。这就是爬山算法,它不能保证局部最优值就是全局最优值。
模拟退火:兔子喝醉了(它本来知道自己要去哪里)。它随机地跳了很长时间。这期间,它可能走向高处,也可能踏入平地。但是,它渐渐清醒了并朝最高方向跳去。这就是模拟退火。
模拟退火算法的数学原理 核心算法:
背后的原理和物理上的一模一样。能量越低越有秩序,越靠近最优解。
温度越高接受错误解的概率越高
温度越低接受错误解的概率越低
错误解:目标是求最小值,正确解是解变小的方向,错误解就是变大的方向。在爬山算法中接受错误解的概率就是0。
问题案例 问题 求
Python代码 研究函数 1 2 3 def func (self, x, y ): res = 4 *x**2 - 2.1 *x**4 + x**6 /3 + 4 *y**2 + 4 *y**4 return res
初始化 1 2 3 4 5 6 7 8 9 10 11 12 class SA : def __init__ (self, func, N=100 , T0=100 , Tf=0.01 , alpha=0.99 ): self.func = func self.N = N self.alpha = alpha self.T0 = T0 self.Tf = Tf self.T = T0 self.x = [random()*11 -5 for i in range (N)] self.y = [random()*11 -5 for i in range (N)] self.most_best = [] self.history = {'f' : [], 'T' : []}
生成点,由于 $x$ 的范围已经规定了,所以随机生成的点就是random()*11-5
参数解释:
self不解释
func:待研究函数
iter:迭代次数
T0:初始温度 一般是100左右,如果解的范围比较大,可以设置大一点
Tf:温度终值 一般接近于0,但不等于0(看下一个函数,就知道等于0时就没有意义了,不扰动了)
alpha:降温系数 一般取0.85~0.99
温度变化公式:$T = \alpha T$
画个温度曲线图演示一下,0.99不太明显,用0.9演示:
随机扰动产生新解 1 2 3 4 5 6 7 def generate_new (self, x, y ): while True : x_new = x + self.T*(random() - random()) y_new = y + self.T*(random() - random()) if (-5 <= x_new <= 5 ) & (-5 <= y_new <= 5 ): break return x_new, y_new
random() - random()
理解为就是一个随机数就可以了。
所以 $\Delta x$ 与当前温度和随机值有关。所以在温度比较高的时候 $\Delta x$ 比较大,寻找新解的步长比较大。也就是突跳特性。
前期找到更多新区域
后期找到局部最优解
局部最优解的出全局最优解
Metroplis准则 1 2 3 4 5 6 7 8 9 def Metropolis (self, f, f_new ): if f_new <= f: return 1 else : p = math.exp((f - f_new) / self.T) if random() < p: return 1 else : return 0
这个函数就是判断是否选取新解。
上面提到了如果是正确解就一定选择,如果是错误解就按概率选择。
2-3行体现了正确解一定选择。
第5行是计算当前温度下的选择概率。
第6行生成一个随机数,来模拟是否选择。
代码很简单。重点是概率的计算。这里是:
更规范的描述这一步的公式:
当然,要根据题目分析正确解的情况。
获取最优目标函数值 1 2 3 4 5 6 7 8 def best (self ): f_list = [] for i in range (self.N): f = self.func(self.x[i], self.y[i]) f_list.append(f) f_best = min (f_list) idx = f_list.index(f_best) return f_best, idx
主体函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def run (self ): while self.T > self.Tf: for i in range (self.N): f = self.func(self.x[i], self.y[i]) x_new, y_new = self.generate_new(self.x[i], self.y[i]) f_new = self.func(self.x[i], self.y[i]) if self.Metropolis(f, f_new): self.x[i] = x_new self.y[i] = y_new ft, idx = self.best() self.history['f' ].append(ft) self.history['T' ].append(self.T) self.T = self.T*self.alpha f_best, idx = self.best() print (f"F={f_best} , x={self.x[idx]} , y={self.y[idx]} " )
整体代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 import mathfrom random import randomimport matplotlib.pyplot as pltdef func (x, y ): res = 4 * x ** 2 - 2.1 * x ** 4 + x ** 6 / 3 + 4 * y ** 2 + 4 * y ** 4 return res class SA : def __init__ (self, func, N=100 , T0=100 , Tf=0.01 , alpha=0.99 ): self.func = func self.N = N self.alpha = alpha self.T0 = T0 self.Tf = Tf self.T = T0 self.x = [random() * 11 - 5 for i in range (N)] self.y = [random() * 11 - 5 for i in range (N)] self.history = {'f' : [], 'T' : []} def generate_new (self, x, y ): while True : x_new = x + self.T * (random() - random()) y_new = y + self.T * (random() - random()) if (-5 <= x_new <= 5 ) & (-5 <= y_new <= 5 ): break return x_new, y_new def Metropolis (self, f, f_new ): if f_new <= f: return 1 else : p = math.exp((f - f_new) / self.T) if random() < p: return 1 else : return 0 def best (self ): f_list = [] for i in range (self.N): f = self.func(self.x[i], self.y[i]) f_list.append(f) f_best = min (f_list) idx = f_list.index(f_best) return f_best, idx def run (self ): while self.T > self.Tf: for i in range (self.N): f = self.func(self.x[i], self.y[i]) x_new, y_new = self.generate_new(self.x[i], self.y[i]) f_new = self.func(self.x[i], self.y[i]) if self.Metropolis(f, f_new): self.x[i] = x_new self.y[i] = y_new ft, idx = self.best() self.history['f' ].append(ft) self.history['T' ].append(self.T) self.T = self.T * self.alpha f_best, idx = self.best() print (f"F={f_best} , x={self.x[idx]} , y={self.y[idx]} " ) sa = SA(func) sa.run() plt.plot(sa.history['T' ], sa.history['f' ]) plt.title('SA' ) plt.xlabel('T' ) plt.ylabel('f' ) plt.gca().invert_xaxis() plt.show()
最终结果:
F=1.4410512133214726, x=-0.2964965160195926, y=0.4748706225379135
Matlab 代码
这里的Matlab代码实际上是根据上面的python代码改编的,写得不太严谨。
Matlab中有模拟退火算法的工具箱,所以也不用自己写模拟退火算法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 clc, clear; fun = @(x, y) 4 *x^2 - 2.1 *x^4 + x^6 /3 + 4 *y^2 + 4 *y^4 ; N = 100 ; T0 = 100 ; Tf = 0.01 ; alpha = 0.99 ; x = rand (2 , 100 )*10 -5 ; T = T0; history = []; while Tf < T for i = 1 : N f = fun(x(1 , i ), x(2 , i )); x_new = generate_new(x, i , T); f_new = fun(x_new(1 , i ), x_new(2 , i )); flag = Metropolis(f, f_new, T); if flag x(1 , i ) = x_new(1 , i ); x(2 , i ) = x_new(2 , i ); end end [ft, idx] = best(fun, x, N); history = [history, ft]; T = T*alpha; end hs = size (history); plot (1 :hs(2 ), history);function x_new = generate_new (x, i, T) x_new = x; while true x_new(1 , i ) = x(1 , i ) + T * (rand - rand ); x_new(2 , i ) = x(2 , i ) + T * (rand - rand ); if -5 <= x_new(1 , i ) <= 5 && -5 <= x_new(2 , i ) <= 5 break end end end function flag = Metropolis (f, f_new, T) if f >= f_new flag = 1 ; else p = exp ((f - f_new) / T); if rand () < p flag = 1 ; else flag = 0 ; end end end function [f_best, idx] = best (fun, x, N) f_list = []; for i = 1 : N fb = fun(x(1 , i ), x(2 , i )); f_list = [f_list, fb]; end [f_best, idx] = min (f_list); end
应用场景 优化类问题