【数学建模】用python numpy实现模拟退火算法

前言

模拟退火算法是现代优化算法的一种,是一种启发式算法。即上一次的计算结果会对下一次结果造成影响。模拟退火算法得益于材料的统计力学结果。结构力学表明材料中离子的不同结构对应于粒子的不同能量水平。在高温条件下,粒子的能量较高,可以自由运动和重新排列;而在低温条件下,粒子的能量较低。若从高温开始,非常缓慢地降温(即退火),粒子就可以在某个温度下达到热平衡,当系统完全冷却时,形成能量最低形态的晶体。

模型过程

采用Metropolis算法可以用一个很简单的数学模型描述退火过程。
假设材料在状态 i i i之下的能量为 E ( i ) E(i) E(i),那么材料在温度 T T T时从状态 i i i进入状态 j j j就会遵循以下规律:

(1)若 E ( j ) ≤ E ( i ) E(j) \leq E(i) E(j)E(i),接受该状态被转换;
(2)若 E ( j ) > E ( i ) E(j)>E(i) E(j)>E(i),以一定的概率 p p p接受状态变换。
其中, p = e E ( i ) − E ( j ) K T p=e^{\frac{E(i)-E(j)}{K T}} p=eKTE(i)E(j)

因此在求解优化问题时,即可通过该思想来对模型进行求解。
若一个组合优化问题:优化函数为: f : x → R + f: x \rightarrow R^{+} f:xR+,其中 x ∈ S x\in S xS为优化问题的一个可行解, R + = { y ∣ y ∈ R , y > 0 } R^{+}=\{y \mid y \in R, y>0\} R+={yyR,y>0},且 S S S表示函数定义域。
在进行求解时,首先给定一个初始温度 T 0 T_0 T0和一个初始解 x ( 0 ) x(0) x(0),并由该初始解形成一个在其邻域内的新解 x ′ x' x,是否接受该新截遵循以下公式:

P ( x ( 0 ) → x ′ ) = { 1  若  f ( x ′ ) < f ( x ( 0 ) ) e − f ( x ′ ) − f ( x ( 0 ) ) T 0  其它  P\left(x(0) \rightarrow x^{\prime}\right)=\left\{\begin{array}{ll}1 & \text { 若 } f\left(x^{\prime}\right)P(x(0)x)=1eT0f(x)f(x(0))  f(x)<f(x(0)) 其它 

而对于其他任一温度 T i T_i Ti时,接受新解的概率为:

P ( x ( k ) → x ′ ) = { 1  若  f ( x ′ ) < f ( x ( k ) ) e − f ( x ′ ) − f ( x ( k ) ) T i  其它  P\left(x(k) \rightarrow x^{\prime}\right)=\left\{\begin{array}{ll}1 & \text { 若 } f\left(x^{\prime}\right)P(x(k)x)=1eTif(x)f(x(k))  f(x)<f(x(k)) 其它 

实例

现利用模拟退火算法解决一个简单的二次函数极值问题。
代码如下:

#模拟退火算法求函数极值

import numpy as np

def func(x):
    return 21.5+x[:,0]*np.sin(20*np.pi*x[:,0]) + x[:,1]*np.sin(20*np.pi*x[:,1])

#定义变量
var = 2                        #变量个数
T0 = 1000                      #初始温度
Tt = T0                        #t时刻温度
maxi = 1000                    #迭代次数
times = 200                    #每个温度下的迭代次数
alfa = 0.95                    #衰减系数
xlow = np.array([[-3,4.1]])    #下界
xhigh = np.array([[12.1,5.8]]) #上界

#生成初始解
x0 = np.zeros((1,var))
for i in range(var):
    x0[:,i] = xlow[:,i] + (xhigh[:,i]-xlow[:,i])*np.random.rand()

it_x,it_y = [x0],[func(x0)]

#模拟退火过程
for i in range(maxi):
    for j in range(times):
        y0 = func(x0)
        y = np.random.randn(1,var)
        z = y / np.sqrt(np.sum(y*y))
        xnew = x0 + z*Tt
        
        for k in range(var):
            if xnew[:,k] < xlow[:,k]:
                r = np.random.rand()
                xnew[:,k] = r*xhigh[:,k] + (1-r)*x0[:,k]
            elif xnew[:,k] > xhigh[:,k]:
                r = np.random.rand()
                xnew[:,k] = r*xlow[:,k] + (1-r)*x0[:,k]
        
        x1 = xnew
        y1 = func(x1)
        if y1 > y0:
            print('接受新解:{}'.format(x1))
            x0 = x1
            it_x.append(x1)
            it_y.append(y1)
        else:
            p = np.exp(-(y0-y1)/Tt)
            if np.random.rand() > p:
                print('新解较小,但仍接受新解:{}'.format(x1))
            else:
                print('保留旧解')
                x0 = x1

    Tt = alfa*Tt
    
print(max(it_y))

最终求解结果为极值:39.15003305。

下面是相同的问题的另一种代码,思想一样,但是编写方式稍微有些不同:

import random
import  numpy as np

def ob(x):
     return 21.5 + x[:,0]*np.sin(20*np.pi*x[:,0]) + x[:,1]*np.sin(20*np.pi*x[:,1])


def main(T, x0, J):
    global fB
    n = 1
    xl = np.array([[-3,4.1]])
    xu = np.array([[12.1,5.8]])
    while n <= 501:
        fA = ob(x0)
        print("旧解{}".format(fA))
        a = np.random.randn(1,2)
        zi = a / (sum(a ** 2) ** 0.5)
        xn = x0 + T*zi
        for k in range(2):
            if xn[:,k] < xl[:,k]:
                r = np.random.rand()
                xn[:,k] = r*xu[:,k] + (1-r)*x0[:,k]
            elif xn[:,k] > xu[:,k]:
                r = np.random.rand()
                xn[:,k] = r*xl[:,k] + (1-r)*x0[:,k]
        
        x1 = xn
        fB = ob(x1)
        if fB >= fA:
            print("第{}次操作,接受新解{}".format((i-1) * 500 + n, fB))
            fA, x0 = fB, x1

        elif fB < fA:
            P = np.exp(-(abs(fB - fA) / T))
            r = random.uniform(0, 1)
            if P >= r:
                print("第{}次操作,虽然比较小,但我们接受新解{}".format(i * n, fB))
                fA, x0 = fB, xn
            else:
                print("第{}次操作,仍使用旧解{}".format((i-1) * 500 + n, fA))
        J.append(fB)
        n += 1
    return  xn

T0 = 100
x0 = np.zeros((1,2))
J = []
for i in range(1,201):
    T = 0.95**i*T0
    main(T, x0, J)
    xn = main(T,x0,J)
print("{:*^25}".format("分割线"))
print("最优解是{}".format(max(J)))

第一种方式是我自己写的,第二种是我老婆写的。最后的结果比较相近,求解得极值为:39.10768688。

参考来源

https://www.bilibili.com/video/BV1hK41157JL

你可能感兴趣的:(数学建模)