1. 程式人生 > >代碼 | 自適應大鄰域搜索系列之(6) - 判斷接受準則SimulatedAnnealing的代碼解

代碼 | 自適應大鄰域搜索系列之(6) - 判斷接受準則SimulatedAnnealing的代碼解

orm pri cool 變量 好的 支持 rand news var

前言

前面三篇文章對大家來說應該很簡單吧?不過輕松了這麽久,今天再來看點刺激的。關於判斷接受準則的代碼。其實,判斷接受準則有很多種,效果也因代碼而異。今天介紹的是模擬退火的判斷接受準則。那麽,相關的原理之前的推文有講過,不懂的同學回去翻翻這個文章 復習一下哈,小編也回去看看,咳咳~。好了,廢話不多說,開始幹活。

01 總體概述

其實這個ALNS的代碼庫提供了很多的判斷接受準則,有最簡單的直接根據目標值來判斷,也有各種復雜的模擬退火降溫冷卻等過程來判斷。不過,今天挑一個最具代表性的來講吧,就是模擬退火的判斷接受準則。其代碼實現是由兩個類IAcceptanceModule、SimulatedAnnealing來實現的。它們的關系依舊如下:

技術分享圖片

其中IAcceptanceModule依舊是抽象類,只提供接口。下面對這兩貨進行解析。

02 IAcceptanceModule

這個抽象類也很簡單,只提供了一個接口transitionAccepted,以用來判斷是否要接受新的解,為純虛函數,需要在後續的代碼中重寫的。
```C++
class IAcceptanceModule
{
public:
//! Indicate if the new created solution have to be accepted or not
//! \param bestSolutionManager a reference to the best solution manager.

//! \param currentSolution current solution.
//! \param newSolution new solution.
//! \param status the status of the current alns iteration.
//! \return true if the transition is accepted, false otherwise.
virtual bool transitionAccepted(IBestSolutionManager& bestSolutionManager, ISolution& currentSolution, ISolution& newSolution, ALNS_Iteration_Status& status) = 0;

//! Some Acceptance modules needs to initialize some variable
//! only when the solver actualy starts working. In this case
//! you should override this method.
virtual void startSignal(){};

};


# 03 SimulatedAnnealing
SimulatedAnnealing繼承於上面的接口類IAcceptanceModule,它利用模擬退火的判斷接受準則實現了transitionAccepted的功能。值得註意的是,該類成員變量裏面是一個CoolingSchedule,用來獲取當前溫度。該表有另一個抽象類ICoolingSchedule定義,下面會詳細說道。
```C++
class SimulatedAnnealing: public IAcceptanceModule {
private:
    //! The cooling schedule to be use to compute the temperature each time it
    //! is needed.
    ICoolingSchedule* coolingSchedule;
public:
    //! Constructor.
    //! \param cs the cooling schedule to be used by the simulated annealing.
    SimulatedAnnealing(ICoolingSchedule& cs);

    //! Destructor.
    virtual ~SimulatedAnnealing();

    //! Compute if the newly created solution have to be accepted or not
    bool transitionAccepted(IBestSolutionManager& bestSolutionManager, ISolution& currentSolution, ISolution& newSolution, ALNS_Iteration_Status& status);

    virtual void startSignal();

};

其成員函數的實現也非常的簡單,不過多說兩句。先利用CoolingSchedule獲取當前冷卻過程的溫度。如果新解目標值<當前解的,那麽直接接受就行了。如果>,那麽按照一定的概率接受。具體公式解釋嘛,小編截個圖過來吧,因為在以前的文章已經講過了:
技術分享圖片

不過這裏的能量差計算用的是解的目標懲罰值算的,不是目標值。

```C++
bool SimulatedAnnealing::transitionAccepted(IBestSolutionManager& bestSolutionManager, ISolution& currentSolution, ISolution& newSolution, ALNS_Iteration_Status& status)
{
double temperature = coolingSchedule->getCurrentTemperature();
if(newSolution < currentSolution)
{
return true;
}
else
{
double difference = newSolution.getPenalizedObjectiveValue() - currentSolution.getPenalizedObjectiveValue();
double randomVal = static_cast<double>(rand())/static_cast<double>(RAND_MAX);
return (exp(-1*difference/temperature)>randomVal);
}
}

void SimulatedAnnealing::startSignal()
{
coolingSchedule->startSignal();
}


# 04 ICoolingSchedule
## 4.1 ICoolingSchedule
這貨是一個抽象類,CoolingSchedule有很多種類型,根據不同需要由這個類可以派生出下面類型的CoolingSchedule:
![](https://upload-images.jianshu.io/upload_images/10386940-3ed3efb467930f6e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

ICoolingSchedule只提供了兩個接口,其中getCurrentTemperature是純虛函數,用以獲取當前的退火溫度,需要重寫。

```C++
class ICoolingSchedule
{
public:
    //! \return the current temperature.
    virtual double getCurrentTemperature()=0;

    //! This method should be called when the optimization
    //! process start. The cooling schedules that actually need
    //! this should override this method.
    virtual void startSignal(){};
};

4.2 LinearCoolingSchedule

由於CoolingSchedule有很多類型,小編挑一個LinearCoolingSchedule給大家講解吧。LinearCoolingSchedule主要的根據是叠代的次數來工作的。成員函數getCurrentTemperature是核心,用以獲取當前的溫度,便於上面的判斷接受準則計算概率。

```C++
class LinearCoolingSchedule: public ICoolingSchedule {
private:
//! The current temperature.
double currentTemperature;

//! The amount to remove at each temperature recomputation.
double amountRemove;

public:
//! Constructor.
//! \param initSol the initial solution.
//! \param csParam the cooling schedule parameters.
//! \param nbIterations the number of iterations to be performed.
LinearCoolingSchedule(ISolution& initSol, CoolingSchedule_Parameters& csParam, size_t nbIterations);

//! Constructor.
//! \param startingTemperature the initial temperature.
//! \param nbIterations the number of iterations to be performed.
LinearCoolingSchedule(double startingTemperature, size_t nbIterations);

//! Destructor.
virtual ~LinearCoolingSchedule();

//! Compute and return the current temperature.
//! \return the current temperature.
double getCurrentTemperature();

void startSignal(){};

};


然後現在來看看其具體方法是怎麽實現的吧。其實也很簡單,沒有那麽復雜。每次獲取currentTemperature的時候呢,先讓currentTemperature降降溫,再返回。降溫的幅度是利用currentTemperature 減去 amountRemove實現的。那麽amountRemove又是怎麽得出來的呢?LinearCoolingSchedule提供了兩個構造函數,對應不同的計算方法:
1. currentTemperature = (csParam.setupPercentage*initSol.getPenalizedObjectiveValue())/(-log(0.5));
    amountRemove = currentTemperature/static_cast<double>(nbIterations);
其中,setupPercentage為參數,nbIterations為總的叠代次數。
2. amountRemove = startingTemperature/static_cast<double>(nbIterations);
其中,startingTemperature為傳入參數。
```C++
LinearCoolingSchedule::LinearCoolingSchedule(ISolution& initSol, CoolingSchedule_Parameters& csParam, size_t nbIterations) {
    currentTemperature = (csParam.setupPercentage*initSol.getPenalizedObjectiveValue())/(-log(0.5));
    amountRemove = currentTemperature/static_cast<double>(nbIterations);

}

LinearCoolingSchedule::LinearCoolingSchedule(double startingTemperature, size_t nbIterations) {
    assert(nbIterations>0);
    assert(startingTemperature>=0);
    currentTemperature = startingTemperature;
    amountRemove = startingTemperature/static_cast<double>(nbIterations);

}

LinearCoolingSchedule::~LinearCoolingSchedule() {
    // Nothing to be done.
}

double LinearCoolingSchedule::getCurrentTemperature()
{
    currentTemperature-= amountRemove;
    if(currentTemperature < 0)
    {
        currentTemperature = 0;
    }
    assert(currentTemperature>=0);
    return currentTemperature;
}

05 小結

今天講的總體也不是很難,相信之前模擬退火學得好的小夥伴一眼就能看懂了,如果其他小夥伴還不是很理解的話,回去看看之前的文章,看看模擬退火的判斷接受準則再多加理解,相信對大家不是什麽問題。

至此,代碼已經講得差不多了,估摸著還能再做幾篇文章,依然感謝大家一路過來的支持。謝謝!咱們下期再見。

代碼及相關內容可關註公眾號。更多精彩盡在微信公眾號【程序猿聲】
技術分享圖片

代碼 | 自適應大鄰域搜索系列之(6) - 判斷接受準則SimulatedAnnealing的代碼解