基於C#的機器學習--旅行推銷員問題

 

咱們有一個必須在n個城市之間旅行的推銷員。他不在意什麼順序。他最早或最後訪問的城市除外。他惟一關心的是他會去拜訪每個人,每一個城市只有一次,最後一站是他得家。算法

每一個城市都是一個節點,每一個節點經過一條邊與其餘封閉節點相連(能夠將其想象成公路、飛機、火車、汽車等)編程

每一個鏈接都有一個或多個權值與之相關,咱們稱之爲成本。數組

成本描述了沿着該鏈接旅行的困難程度,如機票成本、汽車所需的汽油量等。網絡

他的首要任務是儘量下降成本和旅行距離。框架

對於那些學過或熟悉圖論的人,但願大家還記得無向加權圖。dom

城市是頂點,路徑是邊,路徑距離是邊的權值。本質上,咱們有一個最小化的問題,即在訪問了其餘每一個頂點一次以後,從一個特定的頂點開始和結束。實際上,當咱們完成的時候,可能會獲得一個完整的圖,其中每一對頂點都由一條邊鏈接起來。函數

接下來,咱們必須討論不對稱和對稱的問題,由於這個問題最終多是其中之一。究竟是什麼意思?咱們有一個非對稱旅行推銷員問題或者一個對稱旅行推銷員問題。這徹底取決於兩座城市之間的距離。若是每一個方向上的距離相等,咱們有一個對稱的旅行推銷員問題,對稱性幫助咱們獲得可能的解。若是兩個方向上的路徑不存在,或者距離不一樣,咱們就有一個有向圖。下圖顯示了前面的描述: 工具

旅行推銷員問題能夠是對稱的,也能夠是非對稱的。讓咱們從對將要發生的事情從最簡描述開始。學習

在生物界,當咱們想要建立一個新的基因型時,咱們會從父a那裏取一點,從父b那裏取一點。這叫作交叉突變。在這以後,這些基因型就會受到輕微的干擾或改變。這被稱爲突變。這就是遺傳物質產生的過程。測試

接下來,咱們刪除原始代,代之以新的代,並測試每一個基因型。更新的基因型,做爲其先前組成部分的更好部分,如今將向更高的適應度傾斜;平均而言,這一代人的得分應該高於上一代人。

這一過程將持續許多代,隨着時間的推移,人口的平均適應度將不斷進化和提升。在現實生活中,這並不老是有效的,但通常來講,它是有效的。

在後面會有一個遺傳算法編程的講解,以便讓咱們深刻研究咱們的應用程序。

下面是咱們的示例應用程序。它是基於Accord.NET框架的。在定義了須要訪問的房屋數量以後,只需單擊生成按鈕:

在咱們的測試應用程序中,咱們能夠很是容易地更改咱們想要訪問的房屋的數量,如高亮顯示的區域所示。

咱們能夠獲得一個很是簡單的空間問題或者更復雜的空間問題。這是一個很是簡單的空間問題的例子:

這是一個更復雜的空間問題的例子:

最後,設置咱們但願算法使用的迭代總數。點擊計算路線按鈕,假設一切順利,咱們的地圖看起來應該像這樣:

讓咱們看看當咱們選擇咱們想要的城市數量,而後點擊生成按鈕,會發生什麼:

   /// <summary>
        /// 從新生成地圖
        /// </summary>
        private void GenerateMap()
        {
            Random rand = new Random((int)DateTime.Now.Ticks);

            // 建立座標數組
            map = new double[citiesCount, 2];

            for (int i = 0; i < citiesCount; i++)
            {
                map[i, 0] = rand.Next(1001);
                map[i, 1] = rand.Next(1001);
            }

            //設置地圖
            chart.UpdateDataSeries("cities", map);
            //刪除路徑
            chart.UpdateDataSeries("path", null);
        }

咱們要作的第一件事就是初始化隨機數生成器並對其進行種子化。接下來,咱們獲得用戶指定的城市總數,而後從中建立一個新數組。最後,咱們繪製每一個點並更新地圖。這張地圖是來自Accord.NET的圖表控件,它將爲咱們提供大量可視化繪圖。完成這些以後,咱們就能夠計算路徑並解決問題了。

接下來,讓咱們看看咱們的主要搜索解決方案是什麼樣的:

// 建立網絡
            DistanceNetwork network = new DistanceNetwork(2, neurons);

            // 設置隨機發生器範圍
            foreach (var neuron in network.Layers.SelectMany(layer => layer?.Neurons).Where(neuron => neuron != null))
            {
                neuron.RandGenerator = new UniformContinuousDistribution(new Range(0, 1000));
            }


            // 建立學習算法
            ElasticNetworkLearning trainer = new ElasticNetworkLearning(network);

            double fixedLearningRate = learningRate / 20;
            double driftingLearningRate = fixedLearningRate * 19;

            double[,] path = new double[neurons + 1, 2];
            double[] input = new double[2];
            int i = 0;

            while (!needToStop)
            {
                // 更新學習速度和半徑
                trainer.LearningRate = driftingLearningRate * (iterations - i) / iterations + fixedLearningRate;
                trainer.LearningRadius = learningRadius * (iterations - i) / iterations;

                // 設置網絡輸入
                int currentCity = rand.Next(citiesCount);
                input[0] = map[currentCity, 0];
                input[1] = map[currentCity, 1];

                // 運行一個訓練迭代
                trainer.Run(input);

                // 顯示當前路徑
                for (int j = 0; j < neurons; j++)
                {
                    path[j, 0] = network.Layers[0].Neurons[j].Weights[0];
                    path[j, 1] = network.Layers[0].Neurons[j].Weights[1];
                }
                path[neurons, 0] = network.Layers[0].Neurons[0].Weights[0];
                path[neurons, 1] = network.Layers[0].Neurons[0].Weights[1];

                chart.UpdateDataSeries("path", path);

                i++;

                SetText(currentIterationBox, i.ToString());

                if (i >= iterations)
                    break;
            }

如今咱們已經解決了問題,讓咱們看看是否能夠應用咱們在前面關於自組織映射(SOM)一章中學到的知識,從不一樣的角度來處理這個問題。

咱們將使用一種叫作彈性網絡訓練的技術來解決咱們遇到的問題,這是一種很好的無監督的方法。

首先簡單介紹一下什麼是彈性映射。

彈性映射爲建立非線性降維提供了一種工具。它們是數據空間中的彈性彈簧系統,近似於低維流形。利用這種能力,咱們能夠從徹底無結構聚類(無彈性)到更接近線性主成分分析流形(高彎曲/低拉伸)的彈簧。

在使用咱們的示例應用程序時,您將看到這些線並不必定像在之前的解決方案中那樣僵硬。在許多狀況下,它們甚至不可能進入咱們所訪問的城市的中心(這條線從中心生成),而是隻接近城市邊界的邊緣,如前面的示例所示。

接下來,介紹下神經元。此次咱們將有更多的控制,經過指定咱們的學習速率和半徑。與前面的示例同樣,咱們將可以指定銷售人員今天必須訪問的城市總數。

首先,咱們將訪問50個城市,使用0.3的學習率和0.75的半徑。最後,咱們將運行50,000次迭代(不用擔憂,這很快的)。咱們的輸出是這樣的:

如今,若是咱們改變半徑爲不一樣的值,好比0.25,會發生什麼?注意咱們在一些城市之間的角度變得更加明顯:

接下來,咱們將學習率從0.3改成0.75:

儘管獲得路線最終看起來很是類似,但有一個重要的區別。在前面的示例中,直到全部迭代完成,才繪製銷售人員的路由路徑。

咱們所作的第一件事就是建立一個DistanceNetwork對象。這個對象只包含一個DistanceLayer,它是一個距離神經元的單層。距離神經元將其輸出計算爲其權值與輸入值之間的距離,即權值與輸入值之間的絕對差值之和。全部這些組成了SOM,更重要的是,咱們的彈性網絡。

接下來,咱們必須用一些隨機權值來初始化咱們的網絡。咱們將爲每一個神經元建立一個均勻連續的分佈。均勻連續分佈,或稱矩形分佈,是一種對稱的機率分佈,對於族中的每個成員,在分佈的支撐點上相同長度的全部區間具備相同的機率。你一般會看到這寫成U(a, b)參數a和b分別是最小值和最大值。

// 設置隨機發生器範圍
            foreach (var neuron in network.Layers.SelectMany(layer => layer?.Neurons).Where(neuron => neuron != null))
            {
                neuron.RandGenerator = new UniformContinuousDistribution(new Range(0, 1000));
            }

接下來,咱們建立彈性學習對象,它容許咱們訓練咱們的距離網絡:

// 建立學習算法
            ElasticNetworkLearning trainer = new ElasticNetworkLearning(network);

下面是ElasticNetworkLearning構造函數內部的樣子:

如今咱們計算學習速率和半徑:

double fixedLearningRate = learningRate / 20;
            double driftingLearningRate = fixedLearningRate * 19;

最後,進入咱們的主循環:

while (!needToStop)
            {
                // 更新學習速度和半徑
                trainer.LearningRate = driftingLearningRate * (iterations - i) / iterations + fixedLearningRate;
                trainer.LearningRadius = learningRadius * (iterations - i) / iterations;

                // 設置網絡輸入
                int currentCity = rand.Next(citiesCount);
                input[0] = map[currentCity, 0];
                input[1] = map[currentCity, 1];

                // 運行一個訓練迭代
                trainer.Run(input);

                // 顯示當前路徑
                for (int j = 0; j < neurons; j++)
                {
                    path[j, 0] = network.Layers[0].Neurons[j].Weights[0];
                    path[j, 1] = network.Layers[0].Neurons[j].Weights[1];
                }
                path[neurons, 0] = network.Layers[0].Neurons[0].Weights[0];
                path[neurons, 1] = network.Layers[0].Neurons[0].Weights[1];

                chart.UpdateDataSeries("path", path);

                i++;

                SetText(currentIterationBox, i.ToString());

                if (i >= iterations)
                    break;
            }

在前面的循環中,訓練器每次循環增量運行一個epoch(迭代)。這是trainer.Run函數的樣子,咱們能夠看到發生了什麼。基本上,該方法找到獲勝的神經元(權重值最接近指定輸入向量的神經元)。而後更新它的權重以及相鄰神經元的權重:

這個方法的兩個主要功能是計算網絡和得到獲勝者(突出顯示的項目)。

如今,簡要介紹一下咱們能夠在屏幕上輸入的參數。

學習速率

學習速率是決定學習速度的一個參數。更正式地說,它決定了咱們根據損失梯度調整網絡權重的程度。若是過低,咱們沿着斜坡向下的速度就會變慢。即便咱們但願有一個較低的學習率,這可能意味着咱們將須要很長時間來達到趨同。學習速率也會影響咱們的模型收斂到最小值的速度。

在處理神經元時,它決定了有權重用於訓練的神經元的獲取時間(對新體驗作出反應所需的時間)。

學習半徑

學習半徑決定了獲勝神經元周圍要更新的神經元數量。在學習過程當中,半徑圓內的任何神經元都會被更新。神經元越靠近,發生的更新就越多。距離越遠,數量越少。

總結

       在這一章中,咱們學習了神經元,還學習了著名的旅行推銷員問題,它是什麼,以及咱們如何用電腦解決它。這個小例子在現實世界中有着普遍的應用。

       在下一章中,咱們將回答咱們全部開發人員都面臨的問題:我應該接受這份工做嗎?

相關文章
相關標籤/搜索