自适应大领域搜索算法(ALNS) 详解及python示例 您所在的位置:网站首页 智慧医疗领域算法实现代码 自适应大领域搜索算法(ALNS) 详解及python示例

自适应大领域搜索算法(ALNS) 详解及python示例

2024-07-10 08:41| 来源: 网络整理| 查看: 265

1 前言

有关TSP问题的求解方法层出不穷,遗传算法、模拟退火、粒子群算法之类的介绍已经比较多了,但是发现关于自适应大领域搜索算法(Adaptive Large Neighborhood Search, ALNS)的介绍比较少,而且示例代码注释不太详细,刚接触的时候学起来有点费劲,就把学习资料和自己理解的过程简单写一下,造福和我一样的菜鸟吧。

2 什么是自适应大领域搜索

概念理解:干货 | 自适应大邻域搜索(Adaptive Large Neighborhood Search)入门到精通超详细解析-概念篇

优点、步骤和python示例代码:自适应大邻域搜索算法

参考文献及算法应用: [1]王新. 车辆和无人机联合配送路径问题研究[D].大连海事大学,2020. [2]李婷玉. 多商户多车程同城物流配送车辆调度问题研究[D].大连理工大学,2018. [3]张梦颖. 不确定因素下路径规划问题研究[D].中国科学技术大学,2016.

简而言之,自适应大领域搜索的核心思想是:破坏解、修复解、动态调整权重并选择(自适应)。通过设计多组破坏算子和修复算子,扩大解空间搜索范围,对当前解进行改进,表现好的破坏和修复方法相应地得到高分,权重也越高。在每次迭代中,根据以往表现对各个破坏和修复算子进行选择和权重调整,使用高效的组合方法,提高算法寻优能力,从而找到最优解。

2.1 破坏算子(destroy)

破坏解的方法主要有随机移除、最差移除、相似移除等。其中,随机移除删除当前解决方案中的任意节点;最差移除删除当前解决方案中距离较长的路段。

先破坏再修复,这一步得到破坏后的解和移除的节点列表。如,当前解(TSP城市访问顺序)为【1, 4, 2, 3】,随机删除节点4后,得到破坏后的解【1, 2, 3】

2.2 修复算子(repair)

修复解的方法主要有随机插入、贪婪插入等。其中,随机插入将移除的节点逐个插入到破坏后解的任意位置;贪婪插入将移除的节点插到距离成本最小,即插入后总路径最短的位置中。

修复操作的对象是破坏后的解和移除的节点列表,将移除的节点重新插入到破坏后的解中,得到完整的、新的一组解。如,将2.1中的移除节点4,随机插入到破坏后的解【1, 2, 3】中,可得【1, 2, 3, 4】

需要选择至少两组破坏和修复算子加入算法,更多的算子介绍详见上面的参考文献,也可以根据问题特性自行设计其他的破坏或修复算子。

2.3 动态调整权重并选择(自适应过程)

算法在迭代时根据算子权重和轮盘赌的方式选择、调整破坏算子和修复算子。

(1)权重更新

算子的权重按这个公式进行更新:在这里插入图片描述 式中,Wd为算子权重,Sd为算子分数,Ud为算子的使用次数,ρ为权重更新系数(控制权重变化的速度)。其实就是:算子新权重 = 算子旧权重 × (1 - 系数) + 系数 × (累加分数 ÷ 累加次数),将算子权重与以往表现挂钩。

在开始时,所有算子均具有相同的权重和分值。而分数,则是在每个迭代过程中,根据算子的不同表现情况阶梯式给分,得分越高表明算子表现越好。可设定以下4种加分情况:

(1)破坏/修复后得到新的全局最优解,+1.5分 (2)破坏/修复后没有得到全局最优解:   ①尚未接受过的但比当前解好,+1.2分   ②尚未接受过的且比当前解差:    a)在一定标准下接受劣解,+0.8分    b)不满足接受准则的劣解,+0.6分

阶梯分数自己定,1.5、1.2为示例。搜索过程中,若只接受优解容易陷入局部最优,为了避免这种情况应采用一定准则接受劣解。自适应大领域搜索中通常采用模拟退火算法Metropolis准则,在一定概率下接受劣解。

模拟退火算法的介绍:深度学习 — 模拟退火算法详解(Simulated Annealing, SA)

(2)轮盘赌选择算子

得到新的权重后,算法基于轮盘赌的思想对算子进行选择,使算子被选中的概率与其权重表现成正比。

轮盘赌介绍:Evolutionary Computing: 遗传算法_轮盘赌选择(转载)

3 python代码示例及详解

了解了上面的内容之后,下面这个示例代码就很容易理解了。

优点、步骤和python示例代码:自适应大邻域搜索算法

原来的程序把两个轮盘赌函数放在了前面。为了方便理解,我把函数定义的顺序换了一下,由简到易,先分块注释:

(1)导入库,输入城市两两距离矩阵

import numpy as np import random as rd import copy distmat = np.array([[0,350,290,670,600,500,660,440,720,410,480,970], [350,0,340,360,280,375,555,490,785,760,700,1100], [290,340,0,580,410,630,795,680,1030,695,780,1300], [670,360,580,0,260,380,610,805,870,1100,1000,1100], [600,280,410,260,0,610,780,735,1030,1000,960,1300], [500,375,630,380,610,0,160,645,500,950,815,950], [660,555,795,610,780,160,0,495,345,820,680,830], [440,490,680,805,735,645,495,0,350,435,300,625], [720,785,1030,870,1030,500,345,350,0,475,320,485], [410,760,695,1100,1000,950,820,435,475,0,265,745], [480,700,780,1000,960,815,680,300,320,265,0,585], [970,1100,1300,1100,1300,950,830,625,485,745,585,0]])

(2)定义目标函数计算距离 —— disCal( )

已知访问顺序path,求经过的路径长度distance。

def disCal(path): distance = 0 #若路径为path = 【1,2, 3】,len(path)=3 for i in range(len(path) - 1): #先循环两遍,求1-2和2-3的距离之和 distance += distmat[path[i]][path[i + 1]] distance += distmat[path[-1]][path[0]] #从起点出发,回到原来出发的城市,求首尾相连的距离,即3-1 return distance

(3)定义第一个摧毁算子 —— 随机移除randomDestroy( )

随机移除3个城市。输入当前解sol,输出需要移除的城市列表removed。

def randomDestroy(sol): solNew = copy.deepcopy(sol) removed = [] removedIndex = rd.sample(range(0, distmat.shape[0]), 3) #sample随机选取3个城市序号并存入列表,distmat.shape[0]得到距离矩阵维度,即城市总个数 for i in removedIndex: removed.append(solNew[i]) #将需要移除的城市添加到removed列表 sol.remove(solNew[i]) #移除后剩下的城市列表 return removed

(4)定义第二个摧毁算子 —— 最大3段距离移除max3Destroy( )

移除最长的3段距离。输入当前解sol,输出需要移除的城市列表removed。内部过程比随机移除复杂。

def max3Destroy(sol): solNew = copy.deepcopy(sol) removed = [] dis = [] for i in range(len(sol) - 1): dis.append(distmat[sol[i]][sol[i + 1]]) #在距离矩阵中选取路段的长度,如【1,2,3】,循环两次求1-2,2-3的距离, dis.append(distmat[sol[-1]][sol[0]]) #选取首尾的距离3-1,放入dis列表 disSort = copy.deepcopy(dis) disSort.sort() #sort对disSort进行排序,默认升序 for i in range(3): #判断最长的3个路段并移除 if dis.index(disSort[len(disSort) - i -1]) == len(dis) - 1: #如果是距离列表的最后一个,就是城市首尾相连的距离,则去掉列表第一个城市 removed.append(solNew[0]) sol.remove(solNew[0]) else: removed.append(solNew[dis.index(disSort[len(disSort) - i - 1]) + 1]) ''' len(disSort)-i-1得到排在最后面即距离最长的路段。len-1,len-2,... disSort[]得到最大值,dis.index得到最大值在距离列表中的索引 solNew得到最大值路段所在起点的索引,+1删除路段终点 如solNew = 【1,3,2】,dis = 【d13,d32,d21】=【9,7,8】 disSort = 【7,8,9】,先移除最后面的9,再移除8,... 9对应的dis索引为0,solnew对应索引0上的城市为1,移除1-3段,去掉3 ''' sol.remove(solNew[dis.index(disSort[len(disSort) - i - 1]) + 1]) #更新移除后的城市列表 return removed

(5)定义第一个修复算子 —— 随机插入randomInsert( )

随机插入已经移除的3个城市。输入移除后的城市访问列表sol和移除列表removed,执行插入操作后访问列表sol变化。

def randomInsert(sol, removeList): insertIndex = rd.sample(range(0, distmat.shape[0]), 3) #随机生成插入的位置索引 for i in range(len(insertIndex)): sol.insert(insertIndex[i], removeList[i]) #将移除列表中的元素逐个插入指定位置

(6)定义第二个修复算子 —— 贪婪插入greedyInsert( )

插入产生的距离成本最小的城市。输入移除后的城市访问列表sol和移除列表removed,执行插入操作后访问列表sol变化。

def greedyInsert(sol, removeList): dis = float('inf') #初始化距离 insertIndex = -1 #初始化插入索引 for i in range(len(removeList)): #移除列表里有几个城市就循环几次 for j in range(len(sol) + 1): #对于可行解中的每一个索引位置 solNew = copy.deepcopy(sol) solNew.insert(j, removeList[i]) #将移除列表中的元素插入新解列表索引j的位置中,生成新的城市访问顺序 if disCal(solNew) = r: if i == 0: repairOperator = i randomInsert(destroyedSolution,removeList) repairUseTimes[i] += 1 break elif i == 1: repairOperator = i greedyInsert(destroyedSolution,removeList) repairUseTimes[i] += 1 break return destroyedSolution,repairOperator

(9)初始化算法运行数据

设定系数、迭代次数等参数。这个例子用了简单的按顺序依次访问作为初始解,也可以通过节约法之类的方法生成初始解。

T = 100 #模拟退火温度 a = 0.97 #降温速度 b = 0.5 #权重更新系数,控制权重变化速度 #用列表分别存储2个摧毁算子和2个修复算子的权重、次数、分数等信息 wDestroy = [1 for i in range(2)] #摧毁算子的初始权重,[1,1] wRepair = [1 for i in range(2)] #修复算子的初始权重 destroyUseTimes = [0 for i in range(2)] #摧毁初始次数,0 repairUseTimes = [0 for i in range(2)] #修复初始次数 destroyScore = [1 for i in range(2)] #摧毁算子初始得分,1 repairScore = [1 for i in range(2)] #修复算子初始得分 solution = [i for i in range(distmat.shape[0])] #初始解[0,1,2,3,..., 11] bestSolution = copy.deepcopy(solution) #最优解 iterx= 0 #初始迭代次数 iterMax = 100 #最大迭代次数100

(10)主程序

设置终止条件等,内嵌2个选择轮盘赌函数及模拟退火算法接受准则。

if __name__ == '__main__': while iterx 10: #终止温度 destroyedSolution, remove, destroyOperatorIndex = selectAndUseDestroyOperator(wDestroy, solution) #摧毁轮盘赌输入初始摧毁权重、初始解,得到摧毁后城市列表、移除列表、选择的摧毁算子序号 newSolution, repairOperatorIndex = selectAndUseRepairOperator(wRepair, destroyedSolution, remove) #修复轮盘赌输入初始修复权重、摧毁后城市列表、移除列表,得到新的城市列表、选择的修复算子序号 if disCal(newSolution)


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有