算法_Simulated-Annealing_Python

本文用到的包

import random
import numpy as np
from numpy import linalg as LA
import pylab as plt
import networkx as nx
import sys

Simulated-Annealing (模拟退火) 是一种常见的优化方法。这里我们用它将一个任意维度的网络嵌入二维空间。为了检验算法效果,我们先制造一个二维网络A,从这个网络上我们可以得到任意两点之间的欧式距离。我们重新生成一个同等规模的随机网络C,利用C网络两点之间距离与A网络对应距离的差异,来促使C不断朝A演化,经过一千步得到B。可以看出效果非常好,几乎完全恢复了已有网络的结构。

算法_Simulated-Annealing_Python_第1张图片
图1. 使用SA方法恢复网络结构

SA的思路来源于统计力学中的波尔兹曼分布。该分布假设,一个多粒子系统的态出现的概率与其总能量反比,同时这种反比关系受到温度约束。

![Eq. 1][1]
[1]: http://latex.codecogs.com/svg.latex?P({state})\propto{e^{-\frac{E}{kT}}}

上式中E代表系统能量,k是一个非常小的常数,T是系统的温度。我们在其他系统中,如果将要最小化的某个目标(往往是sum of squares)看做能量,则玻尔兹曼分布给我们建议了一种梯度下降的具体方法,因为从公式1我们可以推得

![Eq. 2][2]
[2]: http://latex.codecogs.com/svg.latex?\frac{P({state2})}{P({state1})}=e^{\frac{E_1-E_2}{kT}}

于是,在实际优化过程中,我们先可以对系统做一小调整,在本例中就是比照目标网络对初始网络中的节点位置进行微调,计算这个调整带来的“能量”差异,然后通过Eq.2来判断这个调整是否应该被接受。

与死板地接受所有带来能量下降的调整的优化方法不同,模拟退火可以保证系统有一定的自由度:以小概率接受短期看不利,但长期可能有利的变化,使得系统不会被锁死在一个变化轨道上。所谓模拟退火,模拟指的就是早期保持较高的稳定,给系统较大的自由度接受短期不利的变化;后期降低温度,使得系统稳定地朝一个方向演化。

将上述思想转化为Python函数如下:

# -----------simulated annealing functions----------------------

# refreshing results
def flushPrint(variable):
    sys.stdout.write('\r')
    sys.stdout.write('%s' % variable)
    sys.stdout.flush()
    
# calculate variance contributed by a node or all nodes 
def varaince(newPos,Dis,node,nodePosition):
    if len(nodePosition) == 2:
        return sum([(LA.norm(newPos[j]-nodePosition)-Dis[(min(node,j),max(node,j))])**2 for j in newPos if j!=node])
    else:
        return 2*sum([(LA.norm(newPos[j]-newPos[i])-Dis[(i,j)])**2 for i,j in Dis])  
    
def moveOneNode(newPos,Dis):
    k = 1.0/len(newPos) #factor to normalize force
    [i] = random.sample(newPos,1)
    newpos_i = np.array(list(newPos[i]))# prevent Python from connecting the value of newPos[i] with newpos_i
    varianceBefore = varaince(newPos,Dis,i,newpos_i)
    for j in newPos:
        newDis = LA.norm(newPos[j]-newpos_i)
        if j!=i and newDis!=0:
            oldDis = Dis[(min(i,j),max(i,j))]
            deltaDis = newDis - oldDis # magnitude and direction of the force j imposing on i
            newpos_i += (newPos[j]-newPos[i])*k*deltaDis/newDis # j pushes/pulls i
    varianceAfter = varaince(newPos,Dis,i,newpos_i)
    deltaE = varianceAfter-varianceBefore
    return i,newpos_i,deltaE

def SA(newPos,Dis,Nsimulate,BoltzmanConstant,Temperature,coolingSpeed):
    v=varaince(newPos,Dis,"null","null")
    V=[v]
    for k in range(Nsimulate):
        flushPrint(k)
        i,newpos_i,deltaE = moveOneNode(newPos,Dis)
        if np.random.rand()

现在制造两个网络,一个用于作为优化目标,另一个作为初始状态

# -----------initialization----------------
# generating a random network in 2D space #
N = 20
Pos = dict((i,np.random.rand(2)) for i in range(N))
Dis = {}
E = nx.Graph()
for i in Pos:
    for j in Pos:
        if i

本例中因为系统规模较小,可以设定参数进行如下演化

newPos,V=SA(newPos,Dis,Nsimulate=1000, BoltzmanConstant=.01, Temperature=1, coolingSpeed = .9)

先定义绘制网络的函数

def plotG(ax,positions,edges):
    #nx,ny = zip(*nodes.values())
    for i in positions:
        xi,yi = positions[i]
        ax.scatter(xi,yi,s=300,facecolor='RoyalBlue',edgecolor='gray')
        ax.text(xi,yi,i,ha='center',va='center',size=10,color='white')
    for i,j in edges:
        x1,y1 = positions[i]
        x2,y2 = positions[j]
        ax.plot([x1,x2],[y1,y2],marker='',linestyle='-',color='gray',alpha=0.3)

最后通过以下代码得到图1

fig = plt.figure(figsize=(10, 10))
ax1 = fig.add_subplot(221)
plotG(ax1,Pos,E.edges())
plt.text(.9,.9,'A',size=14)
ax2 = fig.add_subplot(222)
plt.text(.9,1.1,'B',size=14)
plotG(ax2,newPos,E.edges())
ax3 = fig.add_subplot(223)
plt.text(.9,.9,'C',size=14)
plotG(ax3,newPosCopy,E.edges())
ax3 = fig.add_subplot(224)
plt.text(900,37,'D',size=14)
plt.plot(V,marker='.',linestyle='-',color='RoyalBlue')
plt.xlabel('Simulation steps')
plt.ylabel('Variance')
#
plt.tight_layout()
plt.show()
#plt.savefig('/Users/csid/Desktop/SA.png',transparent=True)

你可能感兴趣的:(算法_Simulated-Annealing_Python)