人工智能3-模擬退火求解TSP實驗報告

clipboard.png

前言

本實驗報告爲一csdn博客的詳細註釋版,保證網上Java實現SA TSP最詳細的一篇。
改進之處包括且不限於以下幾點:
一、數據集的詳細獲取辦法;
二、代碼每處都有註釋,不懂的地方能夠留言;
三、實驗結果不一樣參數的比較分析。html

1、實驗目的

瞭解SA的思想;
瞭解模擬退火算法求解TSP問題是的算法結構;
體會SA的參數控制。java

2、實驗內容

(一)、TSP問題

TSP問題(Travelling Salesman Problem)即旅行商問題,又譯爲旅行推銷員問題、貨郎擔問題,是數學領域中著名問題之一。假設有一個旅行商人要拜訪n個城市,他必須選擇所要走的路徑,路徑的限制是每一個城市只能拜訪一次,並且最後要回到原來出發的城市。路徑的選擇目標是要求得的路徑路程爲全部路徑之中的最小值。node

TSP問題是一個組合優化問題。該問題能夠被證實具備NPC計算複雜性。TSP問題能夠分爲兩類,一類是對稱TSP問題(Symmetric TSP),另外一類是非對稱問題(Asymmetric TSP)。全部的TSP問題均可以用一個圖(Graph)來描述:算法

V={c1, c2, …, ci, …, cn},i = 1,2, …, n,是全部城市的集合.ci表示第i個城市,n爲城市的數目;
E={(r, s): r,s∈ V}是全部城市之間鏈接的集合;
C = {Crs: r,s∈ V}是全部城市之間鏈接的成本度量(通常爲城市之間的距離);dom

若是Crs = Csr, 那麼該TSP問題爲對稱的,不然爲非對稱的。ide

一個TSP問題能夠表達爲:函數

求解遍歷圖G = (V, E, C),全部的節點一次而且回到起始節點,使得鏈接這些節點的路徑成本最低。oop

Symmetric traveling salesman problem (TSP)

Given a set of n nodes and distances for each pair of nodes, find a roundtrip of minimal total length visiting each node exactly once. The distance from node i to node j is the same as from node j to node i.性能

NonSymmetric traveling salesman problem (TSP)

優化

(二)、模擬退火算法

模擬退火法是由 Metropolis 於1953 年提出的,是一種基於熱力學原理的隨機搜索算法,是局部搜索算法的拓展。它與局部搜索算法的不一樣之處在於:它以必定的機率選擇鄰域中目標函數值差的狀態

退火是一種物理過程,一種金屬物體在加熱至必定的溫度後,它的全部分子在其狀態空間中自由運動。隨着溫度的降低,這些分子逐漸停留在不一樣的狀態。在溫度最低時,分子從新以必定的結構排列。統計力學的研究代表,在同一個溫度,分子停留在能量最小的狀態的機率比停留在能量大的狀態的機率要大。當溫度至關高時,每一個狀態的機率基本相同,都接近平均值。當溫度趨向0時,分子停留在最低能量狀態的機率趨向於1。

模擬退火算法是一種基於上述退火原理創建的隨機搜索算法。組合優化問題與金屬物體的退火過程可進行以下類比:組合優化問題的解相似於金屬物體的狀態,組合優化問題的最優解相似於金屬物體的能量最低的狀態,組合優化問題的費用函數相似於金屬物體的能量。

爲了克服局部搜索算法極易陷入局部最優解的缺點,模擬退火算法使用基於機率的雙方向隨機搜索技術:當基於鄰域的一次操做使當前解的質量提升時,模擬退火算法接受這個被改進的解做爲新的當前解;在相反的狀況下,算法以必定的機率exp(-ΔC/T)接受相對於當前解來講質量較差的解做爲新的當前解,其中Δc爲鄰域操做先後解的評價值的差,T爲退火過程的控制參數(即溫度)。模擬退火算法已在理論上被證實是一種以機率1收斂於全局最優解的全局優化算法。

模擬退火算法的實施步:

clipboard.png

(三)、SA解TSP思路

具體看代碼講解

3、實驗環境(數據來源)

在下面的JAVA實現中咱們選擇使用tsplib (http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/)上的數據att48。

clipboard.png

clipboard.png

clipboard.png
刪除掉數據集頭部,另存爲data.txt。

4、程序源碼與計算結果

這是一個對稱TSP問題,城市規模爲48,其最優值爲10628.

代碼介紹

an example of Simulated Annealing Agorithm to solve TSP
 * 算法主要思路:
 *  一、鄰域變換函數:隨機交換當前的兩個元素。
 *  二、評估函數:以當前方案的路程總和做爲評估值。
 * 數據集來源:tsplib (http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/)上的數據att48去除頭部。
 * 理想計算結果:10628 ,未取得。
 * 最佳計算結果10653時的參數選擇:
 *     外層迭代:1000
 *     內層迭代次數:4000
 *     降溫係數:0.991
 *     初始溫度:1000
 * 名詞解釋
 *      僞歐式距離:....

具體代碼

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Random;

/**
* an example of Simulated Annealing Agorithm to solve TSP
 * 算法主要思路:
 *  一、鄰域變換函數:隨機交換當前的兩個元素。
 *  二、評估函數:以當前方案的路程總和做爲評估值。
 * 數據集來源:tsplib (http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/)上的數據att48去除頭部。
 * 理想計算結果:10628 ,未取得。
 * 最佳計算結果10653時的參數選擇:
 *     外層迭代:1000
 *     內層迭代次數:4000
 *     降溫係數:0.991
 *     初始溫度:1000
 * 名詞解釋
 *      僞歐式距離:....
 */
public class SimulatedAnnealingTSP {

    private int cityNum; // 城市數量,編碼長度
    private int N;// 每一個溫度迭代步長
    private int T;// 降溫次數
    private float a;// 降溫係數
    private float t0;// 初始溫度, 儘量大,最後結果與初始溫度無關

    private int[][] distance; // 距離矩陣
    private int bestT;// 最佳出現代數

    private int[] Ghh;// 初始路徑編碼,當前編碼(相對於temGhh 是上次編碼)
    private int GhhEvaluation;//評估值
    private int[] bestGh;// 最好的路徑編碼
    private int bestEvaluation;//最好的路徑編碼的評估值
    private int[] tempGhh;// 存放臨時編碼
    private int tempEvaluation;//評估值

    private Random random;

    /**
     * constructor
     *
     * @param cn
     *            城市數量
     * @param t
     *            降溫次數
     * @param n
     *            每一個溫度迭代步長
     * @param tt
     *            初始溫度
     * @param aa
     *            降溫係數
     *
     **/
    public SimulatedAnnealingTSP(int cn, int n, int t, float tt, float aa) {
        cityNum = cn;
        N = n;
        T = t;
        t0 = tt;
        a = aa;
    }


    @SuppressWarnings("resource")// 給編譯器一條指令,告訴它對被批註的代碼元素內部的某些警告保持靜默
    /**
     * 讀取數據集,初始化cityNum, distance[][],
     * @param filename 數據文件名,該文件存儲全部城市節點座標數據
     * @throws IOException
     */
    private void init(String filename) throws IOException {
        // 讀取數據
        int[] x;
        int[] y;
        String strbuff;
        BufferedReader data = new BufferedReader(new InputStreamReader(
                new FileInputStream(filename)));
        distance = new int[cityNum][cityNum];
        x = new int[cityNum];
        y = new int[cityNum];
        for (int i = 0; i < cityNum; i++) {
            // 讀取一行數據,數據格式1 6734 1453 (編號 x座標 y座標)
            strbuff = data.readLine();
            // 字符分割
            String[] strcol = strbuff.split(" ");
            x[i] = Integer.valueOf(strcol[1]);// x座標
            y[i] = Integer.valueOf(strcol[2]);// y座標
        }
        // 計算距離矩陣
        // 針對具體問題,距離計算方法也不同,此處用的是att48做爲案例,它有48個城市,距離計算方法爲僞歐氏距離,最優值爲10628
        for (int i = 0; i < cityNum - 1; i++) {
            distance[i][i] = 0; // 對角線爲0
            for (int j = i + 1; j < cityNum; j++) {
                double rij = Math
                        .sqrt(((x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j])
                                * (y[i] - y[j])) / 10.0);
                //小數部分進位, 1.1-> 2  1.7->2
                int tij = (int) Math.round(rij);// 四捨五入
                if (tij < rij) {
                    distance[i][j] = tij + 1;
                    distance[j][i] = distance[i][j];
                } else {
                    distance[i][j] = tij;
                    distance[j][i] = distance[i][j];
                }
            }
        }
        distance[cityNum - 1][cityNum - 1] = 0;

        Ghh = new int[cityNum];
        bestGh = new int[cityNum];
        bestEvaluation = Integer.MAX_VALUE;//設置初始值位最大
        tempGhh = new int[cityNum];
        tempEvaluation = Integer.MAX_VALUE;//設置初始值位最大
        bestT = 0;//迭代歩數
        random = new Random(System.currentTimeMillis());

        System.out.println("城市數:"+ cityNum+","+
                    "每一個溫度迭代步長(內層迭代上限):"+N+","+
                "降溫次數(外層迭代上限):"+T+","+
                "降溫係數:"+a+","+
                "初始溫度:"+t0);

    }

    /**
     * 初始化編碼Ghh
     * 隨機產生一組城市號 0到cityNum的序列
     * 能夠用任意方式指定,此處算法中的二重循環我沒有看懂
     */
    void initGroup() {
        int i, j;
        Ghh[0] = random.nextInt(65535) % cityNum;
        for (i = 1; i < cityNum;)// 編碼長度
        {
            Ghh[i] = random.nextInt(65535) % cityNum;
            for (j = 0; j < i; j++) {
                if (Ghh[i] == Ghh[j]) {
                    break;
                }
            }
            if (j == i) {
                i++;
            }
        }
    }

    // 複製編碼體,複製編碼Gha到Ghb
    public void copyGh(int[] Gha, int[] Ghb) {
        for (int i = 0; i < cityNum; i++) {
            Ghb[i] = Gha[i];
        }
    }

    /**
     * 估計函數
     * @param chr 路徑編碼,起始城市,城市1,城市2...城市n
     * @return 路徑總長度
     */
    public int evaluate(int[] chr) {
        // 0123
        int len = 0;
        // 計算路徑總長度
        for (int i = 1; i < cityNum; i++) {
            len += distance[chr[i - 1]][chr[i]];
        }
        // 加上從最後一個城市回到出發城市的路程
        len += distance[chr[cityNum - 1]][chr[0]];
        return len;
    }

    /**
     * 鄰域交換,獲得當前編碼Ghh的鄰域編碼tempGhh
     * 隨機交換Ghh的兩個編碼位
     */
    public void Linju(int[] Gh, int[] tempGh) {
        int i, temp;
        int ran1, ran2;
        //copy
        for (i = 0; i < cityNum; i++) {
            tempGh[i] = Gh[i];
        }
        //隨機生成兩個不一樣的編碼位下標
        ran1 = random.nextInt(65535) % cityNum;
        ran2 = random.nextInt(65535) % cityNum;
        while (ran1 == ran2) {
            ran2 = random.nextInt(65535) % cityNum;
        }
        //交換兩個編碼位
        temp = tempGh[ran1];
        tempGh[ran1] = tempGh[ran2];
        tempGh[ran2] = temp;
    }

    public void solve() {
        // 初始化編碼Ghh
        initGroup();
        copyGh(Ghh, bestGh);// 複製當前編碼Ghh到最好編碼bestGh
        bestEvaluation = evaluate(Ghh);
        GhhEvaluation = bestEvaluation;
        int k = 0;// 降溫次數
        int n = 0;// 迭代步數
        float t = t0;
        float r = 0.0f;

        while (k < T) {//T位降溫次數上限
            n = 0;
            while (n < N) {
                Linju(Ghh, tempGhh);// 獲得當前編碼Ghh的鄰域編碼tempGhh
                tempEvaluation = evaluate(tempGhh);//評估當前路勁,結果爲總的路程
                if (tempEvaluation < bestEvaluation)//if the temp one is better
                {
                    copyGh(tempGhh, bestGh);//copy tempGhh to bestGh
                    bestT = k;
                    bestEvaluation = tempEvaluation;
                }
                //根據Metropolis準則判斷是否接受當前解
                r = random.nextFloat();// 返回值屬於[0,1]
                if (tempEvaluation < GhhEvaluation
                        || Math.exp((GhhEvaluation - tempEvaluation) / t) > r) {// t = current temperature
                    copyGh(tempGhh, Ghh);
                    GhhEvaluation = tempEvaluation;
                }
                n++;
            }
            t = a * t;
            k++;
        }

        System.out.println("這次運行的最佳長度出現代數:");
        System.out.println(bestT);
        System.out.println("這次運行的最佳長度(理想值爲10628,歷史最佳結果10653)");
        System.out.println(bestEvaluation);
        System.out.println("這次運行的最佳路徑:");
        for (int i = 0; i < cityNum; i++) {
            System.out.print(bestGh[i] + ",");
            if (i % 10 == 0 && i != 0) {
                System.out.println();
            }
        }
    }

    /**
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        System.out.println("Start....");
        SimulatedAnnealingTSP sa = new SimulatedAnnealingTSP(48, 4000, 1000, 10000.0f, 0.992f);
        sa.init("f://data.txt");
        sa.solve();
    }
}

運行結果截圖分析:

clipboard.png

clipboard.png

clipboard.png

比較三次運行結果,發現結果不穩定,且與最優值差距很大。問題的最優值爲10628。

clipboard.png
提升內層迭代次數,結果更加接近最優解。

clipboard.png
提升降溫係數到0.99,結果很是接近最優解。這也是數十次運行中最佳的一次運算結果。

clipboard.png
調整降溫係數到0.999,結果反而出現異常,降溫係數不是越接近1越好

最佳的一次計算結果以下:
clipboard.png

5、總結

模擬算法其特色是在開始搜索階段解的質量提升比較緩慢,可是到了迭代後期,它的解的質量提升明顯,因此若是在求解過程當中,對迭代步數限制比較嚴格的話,模擬退火算法在有限的迭代步數內很可貴到高質量的解。整體而言模擬退火算法比較適合用於有充足計算資源的問題求解。

模擬退火算法的參數控制問題

  模擬退火算法的應用很普遍,能夠求解NP徹底問題,但其參數難以控制,其主要問題有如下三點:
  (1) 溫度T的初始值設置問題。
  溫度T的初始值設置是影響模擬退火算法全局搜索性能的重要因素之一。初始溫度越高,則搜索到全局最優解的可能性大,但所以要花費大量的計算時間;反之,則可節約計算時間,但全局搜索性能可能受到影響。實際應用過程當中,初始溫度通常須要依據實驗結果進行若干次調整。
  (2) 退火速度問題。
  模擬退火算法的全局搜索性能也與退火速度密切相關。通常來講,同一溫度下的「充分」搜索(退火)是至關必要的,但這須要計算時間。實際應用中,要針對具體問題的性質和特徵設置合理的退火平衡條件。
這個是與內外層迭代次數有關嗎?
  (3) 溫度管理問題。
  溫度管理問題也是模擬退火算法難以處理的問題之一。實際應用中,因爲必須考慮計算複雜度的切實可行性等問題,常採用以下所示的降溫方式:T(t+1)=k×T(t)
式中k爲正的略小於1.00的常數,t爲降溫的次數。

參考

附錄.ACM之家Java面向對象解法

http://www.acmerblog.com/simulated-annealing-algorithm-tsp-5841.html

附錄.模擬退火算法通用類,改編自TSP

出處:http://www.codeforge.cn/read/126652/SACompute.java__html

/**
 * 模擬退火算法
 * @author acer
 * @param <T>
 */
public abstract class SimulatedAnnealingTemplate<T> implements Runnable {

    private T initAnswer;
    private T resultAnswer;

    public SimulatedAnnealingTemplate(T initAnswer) {

        this.initAnswer = initAnswer;

    }

    /**
     * 獲取初始溫度
     *
     * @return
     */
    public abstract double getInitTemplate();

    /**
     * 獲取臨近解
     *
     * @param current
     * @param nowTemperature
     * @param nowExternalIterateNumber
     * @param nowInnerIterateNumber
     * @return
     */
    public abstract T getNearbyAnser(T current, double nowTemperature,
                                     int nowExternalIterateNumber, int nowInnerIterateNumber);

    /**
     * 獲取兩個解的評價值之差
     *
     * @param answer1
     * @param answer2
     * @return
     */
    public abstract double getDeltaValue(T answer1, T answer2);

    /**
     * 是否退出內部循環
     *
     * @param nowInnerIterateNumber
     * @return
     */
    public abstract boolean exitInnerLoop(int nowInnerIterateNumber);

    /**
     * 是否退出外部循環
     *
     * @param nowExternalIterateNumber
     * @return
     */
    public abstract boolean exitExternalLoop(int nowExternalIterateNumber, double nowTemperature);

    /**
     * 外部循環降溫
     *
     * @param nowTemperature
     * @param nowExternalIterateNumber
     * @return
     */
    public abstract double countDownTemperature(double nowTemperature,
                                                int nowExternalIterateNumber);

    /**
     * 開始運行
     */
    public void run() {

        double nowTemperature = getInitTemplate();
        int nowExternalIterNumber = 0;
        int nowInnerIterNumber = 0;
        resultAnswer = initAnswer;

        while (true) {

            double deltatotaldis = 0.0;
            while (true) {

                T nearbyAnswer = getNearbyAnser(resultAnswer,
                        nowTemperature, nowExternalIterNumber,
                        nowInnerIterNumber);

// 從某路徑的鄰域中隨機選擇一個新的路徑,鄰域映射爲2-opt
                deltatotaldis = getDeltaValue(nearbyAnswer, resultAnswer);
// 計算新路徑與當前路徑的路程長度差值
                if (deltatotaldis <= 0.0)
                    resultAnswer = nearbyAnswer; // 若是新路徑的路程短,則用它替換當前路徑
                else {

                    double chgprobability = Math
                            .exp(-(deltatotaldis / nowTemperature));
                    double random = Math.random();
                    if (chgprobability > random)
                        resultAnswer = nearbyAnswer;
// 若是新路徑長於當前路徑,但exp(-Δf/t) > random(0,1),則仍然替換當前路徑

                }
                if (exitInnerLoop(nowInnerIterNumber))
                    break; // 判斷內循環是否結束,結束則跳出當前溫度的內循環
                else
                    nowInnerIterNumber++; // 判斷內循環是否結束,不結束則繼續內循環

            }
            if (exitExternalLoop(nowExternalIterNumber, nowTemperature))
                break; // 判斷外循環是否結束,結束則結束模擬退火計算
            else {

                nowTemperature = countDownTemperature(nowTemperature,
                        nowExternalIterNumber);
                nowExternalIterNumber++;
                nowInnerIterNumber = 0;
// 判斷外循環是否結束,不結束則計算出降低後的溫度,並繼續循環

            }

        }

    }

    public static void main(String[] args) {

        System.out.println(Math.exp(-5));

    }

}

附錄.模擬退火算法的參數自動調整算法

有待研究

附錄.人工智能算法求解TSP問題

http://blog.csdn.net/column/details/mio-algorithms.html
基於蟻羣算法求解求解TSP問題(JAVA)
基於GA求解求解TSP問題(JAVA)
基於粒子算法求解求解TSP問題(JAVA)

相關文章
相關標籤/搜索