飞道的博客

10分钟彻底理解自适应大邻域搜索算法

529人阅读  评论(0)

阅读本文大概需要 10 分钟。

算法介绍

自适应大邻域搜索算法(Adaptive Large Neighborhood Search),简称(ALNS),是由Ropke与Pisinger在2006年提出的一种启发式方法,其在邻域搜索的基础上增加了对算子的作用效果的衡量,使算法能够自动选择好的算子对解进行破坏与修复,从而有一定几率得到更好的解。

应用场景

1.外卖场景:搜索订单分配骑手的最优方案

2.派单场景:搜索订单分配司机的最优方案

3.车辆路径问题

同类算法

在邻域搜索算法中,有的算法可以只使用一种邻域,如「模拟退火算法」,因此它仅仅搜索了解空间的一小部分,找到全局最优的概率较小,它的优势之一是可以避免陷入局部最优;

而有的算法可以使用多种算子,如「变邻域搜索算法」(VNS),它通过在当前解的多个邻域中寻找更满意的解,能够大大提高算法在解空间的搜索范围,但是它在使用算子时盲目地将每种算子形成的邻域结构都搜索一遍,缺少了一些启发式信息的指导;

「自适应大邻域搜索算法」就弥补了这种不足,这种算法根据算子的历史表现与使用次数选择下一次迭代使用的算子,通过算子间的相互竞争来生成当前解的邻域结构,而在这种结构中有很大概率能够找到更好的解;

算法流程


   
  1. 1. 生成初始解,当前解X0 = 初始解,最优解X1 = X0
  2. 2. 重复以下步骤进行迭代直到停止准则
  3.     2.1 根据算子权重选择破坏与修复算子,并更新算子使用次数
  4.     2.2 破坏算子和修复算子依次对当前解操作得到新解X2
  5.     2.3 更新当前解 
  6.    - 如f(X2) < f(X0),则X0 = X2
  7.    - 如f(X2) > f(X0),则以一定的概率接受该解作为当前解
  8.     2.4 更新最优解 
  9.    - 如f(X2) < f(X1),则X1 = X2
  10.     2.5 更新算子的分数
  11.     2.6 更新算子的权重
  12.     2.7 重置当前解
  13. 3. 返回最优解X1

每次迭代就是从初始解中删除N个点,然后依次将删除的点重新插入,得到一个新的解,即当前解的邻域解;重复上述迭代过程,得到一个成本(cost)最低的一个解,即最优解。

「ALNS会为每个destroy和repair算子分配一个权重,通过该权重从而控制每个destroy和repair算子在搜索期间使用的频率。」 在搜索的过程中,「ALNS」会对各个destroy和repair方法的权重进行「动态调整」,以便获得更好的邻域和解。

「ALNS通过使用多种destroy和repair算子,然后再根据这些destroy和repair算子生成的解的质量,选择那些表现好的destroy和repair算子,再次生成邻域进行搜索」

算法实现

算法本身由算法参数、当前解、状态管理器、算子管理器、最优解管理器组成

算法参数

算法执行过程中一些初始化参数


   
  1. type Parameters  struct {
  2.     MaxExecTime           int          `json:"max_exec_time"`  // 最长执行时间(单位s)
  3.     TimeSegmentsIt        int          `json:"time_segments_iteration"`  // 重新计算权重迭代次数
  4.     ReloadFrequency       int          `json:"reload_frequency"`  // 重置当前解的迭代次数
  5.     Sigma1                int          `json:"sigma1"`  // 算子提升最优解 增加分数
  6.     Sigma2                int          `json:"sigma2"`  // 算子提升当前解 增加分数
  7.     Sigma3                int          `json:"sigma3"`  // 算子更新当前解 增加分数
  8.     Rho                   float64      `json:"rho"`     // 重新计算算子权重的系数
  9.     MinimumWeight         float64      `json:"min_weight"`  // 最小权重值
  10.     MaximumWeight         float64      `json:"max_weight"`  // 最大权重值
  11.     MaxTemperature        float64      `json:"max_temperature"`  // 最大温度
  12.     MinTemperature        float64      `json:"min_temperature"`  // 最小温度
  13.     TemperatureAlpha      float64      `json:"temperature_alpha"`  // 降温系数
  14.     MaxNoImproveRatio     float64      `json:"max_no_improve_ratio"`  // 最大没有提升解的迭代次数占比(超过停止)
  15. }

最大温度 * math.pow(降温系数, n) < 最小温度,max(n)即为「最大迭代次数」,超过最大迭代次数停止

最大迭代次数 * MaxNoImproveRatio = 最大无改善最优解的迭代次数,超过最大无改善最优解的迭代次数停止

超过最长执行时间停止

状态管理器

管理计数的状态变量


   
  1. type Status  struct {
  2.   // 迭代次数:Id of the iteration corresponding to this status.
  3.  IterationId  int
  4.   // 迭代次数中可行解的迭代次数:Number of iteration solution avaliable
  5.  NIterationAvaliable  int
  6.   // 距离上一次改善最优解的迭代次数:Number of iteration since the last improvement of the BKS
  7.  NIterationWithoutImprovement  int
  8.   // 距离上一次重置当前解后改善最优解的迭代次数:Number of iteration since the last improvement of the BKS
  9.   // or the last reload of the best known solution.
  10.  NIterationWithoutImprovementSinceLastReload  int
  11.   // 没有改善当前解的迭代次数:Number of iterations since the last improvement of the current
  12.   // solution.
  13.  NIterationWithoutImprovementCurrent  int
  14.   // 没有更新当前解的迭代次数:Number of iterations without transition.
  15.  NIterationWithoutTransition  int
  16.   // 重置当前解的迭代次数:Number of iterations with reload current solution
  17.  NIterationReloadCurrent  int
  18.   // 更新最优解的迭代次数:Number of iterations with update best solution
  19.  NIterationUpdateBest  int
  20.   // 更新算子权重的迭代次数:Number of iterations with recopute weights
  21.  NIterationRecomputeWeights  int
  22.   // 当前解是否是最优解:Indicate if a new best solution has been obtained.
  23.  NewBestSolution  int
  24.   // 是否更新了当前解:Indicate if the new solution has been accepted as the
  25.   // current solution.
  26.  AcceptedAsCurrentSolution  int
  27.   // 是否提升了当前解:Indicate if the new solution improve the current solution.
  28.  ImproveCurrentSolution  int
  29. }

最优解管理器

管理最优解

更新最优解

获取最优解

算子管理器

算子管理类,提供如下接口

添加摧毁算子

添加修复算子

选择摧毁算子

选择修复算子

更新算子分数

更新算子权重

更新算子调用次数

选择算子

算子管理器根据算子权重选择破坏算子与修复算子


   
  1. func (m *OperatorManager) selectOperator(vecOp []IOperator, sumW float64) IOperator {
  2.  randomVal := rand.Float64()
  3.  randomWeightPos := randomVal * sumW
  4.  cumulSum :=  0.0
  5.   for i :=  0; i <  len(vecOp); i++ {
  6.   cumulSum += vecOp[i].GetWeight()
  7.    if cumulSum >= randomWeightPos {
  8.     if m.noise {
  9.     vecOp[i].SetNoise()
  10.    }  else {
  11.     vecOp[i].UnsetNoise()
  12.    }
  13.    vecOp[i].IncreaseNumberOfCalls()  // 更新算子使用次数
  14.     return vecOp[i]
  15.   }
  16.  }
  17.   return vecOp[ len(vecOp) -1]
  18. }

获取邻域解

先使用破坏算子进行删除,然后使用修复算子将删除的进行添加


   
  1. func (s *AlnsProcess) generateNeighborSolution(repair IRepairOperator, destroy IDestroyOperator) *alg.Solution {
  2.   // 从局部最优中生成新解
  3.  neighbor := s.currentSolution.CopySolution()
  4.   // destroy solution
  5.  removeJobs := destroy.DestroySolution(neighbor)
  6.   // repair solution
  7.  repair.RepairSolution(neighbor, removeJobs)
  8.   return neighbor
  9. }

更新当前解

新解 < 当前解,一定接受

新解 > 当前解,根据温度与成本变化值随机接受


   
  1. func (a *Acceptance) TransitionAccepted(currentSolution, newSolution *alg.Solution) bool {
  2.   // 降温
  3.  a.temperature *= a.parameters.TemperatureAlpha
  4.   if newSolution.SolutionCost < currentSolution.SolutionCost {
  5.    return  true
  6.  }  else {
  7.   difference := newSolution.SolutionCost - currentSolution.SolutionCost
  8.   random := rand.Float64()
  9.    return math.Exp( -1.0*difference/a.temperature) >= random
  10.  }
  11. }

更新最优解

新解 < 最优解,一定接受


   
  1. func (s *AlnsProcess) updateBestSoution(newSol *alg.Solution) bool {
  2.  bestSolution := s.bestSolManager.GetBestSolution()
  3.  accept :=  false
  4.   if newSol.SolutionCost < bestSolution.SolutionCost {
  5.   accept =  true
  6.  }
  7.   if accept {
  8.   s.bestSolManager.AddNewBestSolution(newSol)
  9.   s.status.NewBestSolution = TRUE
  10.   s.status.NIterationWithoutImprovement = s.nIterationsWithoutImprovement
  11.   s.status.NIterationWithoutImprovementSinceLastReload =  0
  12.   s.status.NIterationUpdateBest +=  1
  13.    return  true
  14.  }  else {
  15.   s.status.NewBestSolution = FALSE
  16.   s.status.NIterationWithoutImprovement = s.nIterationsWithoutImprovement
  17.   s.status.NIterationWithoutImprovementSinceLastReload++
  18.    return  false
  19.  }
  20. }

更新算子的分数

根据新解的质量,给当前使用算子加上不同的分数


   
  1. func (m *OperatorManager) UpdateScores(des IDestroyOperator, rep IRepairOperator, status *Status) {
  2.   // 新解是最优解
  3.   if status.NewBestSolution == TRUE {
  4.   rep.SetScore(rep.GetScore() +  float64(m.parameters.Sigma1))
  5.   des.SetScore(des.GetScore() +  float64(m.parameters.Sigma1))
  6.   m.perfRepairOperatorsWithNoise +=  1
  7.   m.perfRepairOperatorsWithoutNoise +=  1
  8.  }
  9.   // 新解改善了当前解
  10.   if status.ImproveCurrentSolution == TRUE {
  11.   rep.SetScore(rep.GetScore() +  float64(m.parameters.Sigma2))
  12.   des.SetScore(des.GetScore() +  float64(m.parameters.Sigma2))
  13.   m.perfRepairOperatorsWithNoise +=  1
  14.   m.perfRepairOperatorsWithoutNoise +=  1
  15.  }
  16.   // 新解更新了当前解
  17.   if status.ImproveCurrentSolution == FALSE &&
  18.   status.AcceptedAsCurrentSolution == TRUE &&
  19.   status.AlreadyKnownSolution == FALSE {
  20.   rep.SetScore(rep.GetScore() +  float64(m.parameters.Sigma3))
  21.   des.SetScore(des.GetScore() +  float64(m.parameters.Sigma3))
  22.   m.perfRepairOperatorsWithNoise +=  1
  23.   m.perfRepairOperatorsWithoutNoise +=  1
  24.  }
  25.   if m.parameters.Noise {
  26.   performanceRepairOperatorsGlobal :=  0.0
  27.   performanceRepairOperatorsGlobal += m.perfRepairOperatorsWithNoise
  28.   performanceRepairOperatorsGlobal += m.perfRepairOperatorsWithoutNoise
  29.   randomVal := rand.Float64()
  30.   randomWeightPos := randomVal * performanceRepairOperatorsGlobal
  31.   m.noise = (randomWeightPos < performanceRepairOperatorsGlobal)
  32.  }
  33. }

更新算子的权重

每迭代TimeSegmentsIt次,更新所有算子的权重,新的权重和算子分数、算子调用次数等有关


   
  1. func (m *OperatorManager) recomputeWeight(op IOperator, sumW *float64) {
  2.  prevWeight := op.GetWeight()
  3.  *sumW -= prevWeight
  4.  currentScore := op.GetScore()
  5.  nbCalls := op.GetNumberOfCallsSinceLastEvaluation()
  6.  newWeight := ( 1-m.parameters.Rho)*prevWeight + m.parameters.Rho*( float64(nbCalls)/ float64(m.parameters.TimeSegmentsIt))*currentScore
  7.   // We ensure that the weight is within the bounds.
  8.   if newWeight > m.parameters.MaximumWeight {
  9.   newWeight = m.parameters.MaximumWeight
  10.  }
  11.   if newWeight < m.parameters.MinimumWeight {
  12.   newWeight = m.parameters.MinimumWeight
  13.  }
  14.  *sumW += newWeight
  15.  op.SetWeight(newWeight)
  16.  op.ResetScore()
  17.  op.ResetNumberOfCalls()
  18. }

重置当前解

每迭代 ReloadFrequency 次并且没有改善最优解,就重置当前解


   
  1. // 重置当前解(防止陷入局部最优解)
  2. func (s *AlnsProcess) reloadCurrentSolution() *alg.Solution {
  3.   if s.status.NIterationWithoutImprovementSinceLastReload >  0 &&
  4.   s.status.NIterationWithoutImprovementSinceLastReload%s.params.ReloadFrequency ==  0 {
  5.   s.status.NIterationWithoutImprovementSinceLastReload =  0
  6.   s.status.NIterationReloadCurrent +=  1
  7.    return s.bestSolManager.GetBestSolution().CopySolution()
  8.  }
  9.   return s.currentSolution
  10. }

公众号后台回复 alns ,获取一份自适应大邻域搜索算法论文pdf

  公众号后台回复「加群」拉你进技术交流群

公众号后台回复「编程」送你编程+面试电子书


转载:https://blog.csdn.net/caspar_notes/article/details/114275233
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场