需求為(自己編的,非實際專案):
某配送中心進行攬貨,目標客戶數為50個客戶,配送中心目前的運力資源如下:
- 現有車輛5臺
- 單臺運力最大行駛距離200千米
- 單臺運力最大載重公斤1噸
問:運力怎樣走法才能以最低的成本完成針對這50個客戶的攬貨行為
是個最優化問題(運籌學),我們只考慮簡化後的模型,不考慮路面交通、時間視窗這些複雜計算,用蟻群優化演算法來實現接近最優解的計算。
關於蟻群優化演算法的理論請看這篇文章:https://www.cnblogs.com/asxinyu/p/Path_Optimization_Tsp_Problem_Ant_System_CSharp.html
裡面的基本演算法已經寫明瞭,也有demo,本文是針對如何適應到具體業務的介紹(本文用的蟻群核心程式碼也是上文中改來的)
蟻群主要步驟為:
- 初始化(如資訊素)
- 開始迭代
- 構造各個螞蟻,以及螞蟻走的路徑(核心是針對後續節點的SELECT)
- 計算適應度
- 加入優秀螞蟻到跟蹤列表
- 更新資訊素(根據適應度)
- 結束迭代
- 給出報告
原文章裡用的是TSP做DEMO,比較難看清楚如何應用到實際業務邏輯中
同樣的,最困惑的核心中的核心,類似遺傳演算法,也是適應度值的計算,有的地方是一步一步增加vlaue,比如單純距離的增加,但是複雜點的都沒法這麼操作,而是要看整體路徑的指標(包括懲罰等)
由於蟻群優化演算法和本文程式碼都能下載,所以只介紹適應度value的計算
class FitnessValueCalculator { private static int 擁有運力車輛數 = 5; private static int 單臺運力最大行駛距離 = 200; private static int 單臺運力最大載重公斤 = 1000; private static double 懲罰權重 = 20; public static double Calculator(ShortestDeliverAnt ant) { var paths = new List<string>(); var distances = new List<double>(); var weights = new List<double>(); double 當前行駛距離 = 0; double 當前運力載重 = 0; string 當前行駛路徑 = ""; int 當前所需運力數 = 1; //計算樞紐到第一個客戶配送距離 當前行駛路徑 += "HUB-->" + ant.PathNodes.First(); 當前行駛距離 += ant.DistanceHelper.hub.DistanceTo(ant.DistanceHelper.customers[ant.PathNodes.First()]); 當前運力載重 += ant.DistanceHelper.customers[ant.PathNodes.First()].需求量_公斤; foreach (var path in ant.Edges) { var fromNodeId = path.Key; var toNodeId = path.Value; var fromNode = ant.DistanceHelper.customers[fromNodeId]; var toNode = ant.DistanceHelper.customers[toNodeId]; double newAddedDistance2Customer = 0; double newAddedDistance2Hub = 0; double newAddedWeight = 0; newAddedDistance2Customer = fromNode.DistanceTo(toNode); newAddedDistance2Hub = toNode.DistanceTo(ant.DistanceHelper.hub); newAddedWeight = toNode.需求量_公斤; if (當前行駛距離 + newAddedDistance2Customer + newAddedDistance2Hub <= 單臺運力最大行駛距離 && 當前運力載重 <= 單臺運力最大載重公斤) { 當前行駛距離 += newAddedDistance2Customer; 當前運力載重 += newAddedWeight; 當前行駛路徑 += "-->" + toNodeId; } else { //加當前客戶距離、以及回到HUB的距離 當前行駛距離 += fromNode.DistanceTo(ant.DistanceHelper.hub); distances.Add(當前行駛距離); weights.Add(當前運力載重); 當前行駛路徑 += "-->HUB"; paths.Add(當前行駛路徑); //RESET 當前行駛距離 = 0; 當前行駛距離 += ant.DistanceHelper.hub.DistanceTo(toNode); 當前運力載重 = 0; 當前運力載重 += toNode.需求量_公斤; 當前行駛路徑 = ""; 當前行駛路徑 += "HUB-->" + toNodeId; 當前所需運力數++; } } //回到樞紐 當前行駛距離 += ant.DistanceHelper.customers[ant.PathNodes.Last()].DistanceTo(ant.DistanceHelper.hub); distances.Add(當前行駛距離); 當前行駛路徑 += "-->HUB"; paths.Add(當前行駛路徑); int 懲罰係數 = 0; if (當前所需運力數 > 擁有運力車輛數) 懲罰係數 = 當前所需運力數 - 擁有運力車輛數; ant.運輸距離順序 = distances; ant.運輸路徑 = paths; ant.Total行駛距離 = distances.Sum(); ant.Total運力數 = 當前所需運力數; return ant.Total行駛距離 + 懲罰係數 * 懲罰權重; } }
ant.DistanceHelper.hub: 是配送中心的info,有地址資訊
ant.DistanceHelper.customers: 是50個客戶的info,也有地址資訊
目前為了簡化,是以街道距離來計算距離的
目前程式碼只是單目標優化演算法,非多目標優化,後續研究研究再發文。
上述程式碼其實就是第一輛車從配送中心開出到第一個客戶位置,然後加上客戶需求(攬的貨物重量)
接著判斷能否開到下一個客戶那裡攬貨,如果里程、重量都在限制條件只能,就開過去,不滿足條件就開回樞紐;然後繼續判斷第二輛車,也是這麼個邏輯
最終車輛的數量就是完成這50個客戶攬貨所需的運力數
萬一碰到所需運力超出了限制(程式碼中為5輛車),這時就需要懲罰,由於最終函式返回是double,而且是越小代表越優越,因此碰到了需要懲罰的情況,實際就是大幅度的增加返回值(適應度值)
紅色部分就是懲罰變數部分。
各種優化演算法的核心寫完框架後基本就不怎麼變化了,最易變的其實是適應度函式的計算,如果適應度計算中用到了預測技術,還得在上面那函式裡調機器學習的程式碼,感覺強化學習中動作施加後給出的反饋值也是這麼個值