最佳化系統的想法真不好簡單地說的明白,這樣吧,我爸在陝西西安,我媽在安徽的合肥,我弟弟在深圳,打算坐飛機到北京我這裡玩。家人都比較節省,打算到了機場後互相等對方,然後一起坐車租車到我住的地方。
我查了qunar,一天從西安,合肥,深圳到北京有很多航班,怎麼樣讓總的票價最少,並且在機場互相等待的時間降到最低。這裡假設一個人等1分鐘相當於1塊錢,這樣我們的目標就是讓成本schedulcost=(父親機票錢+母親機票錢+弟弟機票錢+父親等候分鐘+母親等候分鐘+弟弟等候時間)最小,這裡面有可能父親,或者母親或者弟弟的等候分鐘是0。
先定義家長控制:
people = [('baba','XA'), ('mama','HF'), ('didi','sz')]
爸爸從西安出發,媽媽從合肥出發,弟弟從深圳出發;
再定義航班資訊:
flights = [('XA','BJ') : [('06:30', '08:30', 600), ('07:45', '10:15', 500)....],('HF','BJ') : [('06:45', '09:30', 900), ('07:25', '10:45', 800)....],('SZ','BJ') : [('06:25', '10:30', 1200), ('08:05', '12:10', 1300)....],]
從西安到北京的航班有:6:30出發,8:30到達,機票600元;07:45出發,10:15到達,機票500元。。。
從合肥到北京的航班有:6:45出發,9:30到達,機票900元;07:25出發,10:45到達,機票800元。。。
從深圳到北京的航班有:6:25出發,10:30到達,機票1200元;08:05出發,12:10到達,機票1300元。。。
下面假設航班是:
sol = [2,2,1]
從西安到北京的第二班,從合肥到北京的第二班,從西安到北京的第一班。
下面就可以算計我上面定的航班的花費了:其中
def schedulecost(sol): totalprice=0 latestarrival=0 for d in range(len(sol)): origin=people[d][1] outbound=flights[(origin,'BJ')][int(sol[d])] totalprice+=outbound[2] if latestarrival<getminutes(outbound[1]): latestarrival=getminutes(outbound[1]) totalwait=0 for d in range(len(sol)): origin=people[d][1] outbound=flights[(origin,'BJ')][int(sol[d])] totalwait+=latestarrival-getminutes(outbound[1]) return totalprice+totalwait
很好懂,shedulecost計算了總的航班機票錢和總的等候時間,
計算總的等候時候先找出最晚到達的一班飛機的時間latestarrival。
我們的目標:找出某種組合的sol,讓schedulecost成本最低;
因為一天內的航班有很多,並且我已經把問題簡化到最少,所以在現實情況中結果的組合數會輕鬆上億,
如果shedulecost稍微複雜的話,找出最低成本的方案會非常吃力,下面看看幾種可能的做法:
(一)隨機搜尋法
最簡單的,隨機計算其中的一部分結果,比如1000條可能的解,最終的結果取這部分計算結果中的最小值,這種演算法最簡單,問題也最明顯。
下面的實現,第一個參數是每種可能結果的範圍,比如從西安到北京一天有100個航班,從合肥到北京一天有80個航班,從深圳到北京一天有150個航班,那麼domain的定義就是domain=[(0,100),(0,80),(0,150)];第二個參數就是上面定義的schedulecost函數;
def randomoptimize(domain,costf): best=999999999 bestr=None for i in range(0,1000): # Create a random solution r=[float(random.randint(domain[i][0],domain[i][1])) for i in range(len(domain))] # Get the cost cost=costf(r) # Compare it to the best one so far if cost<best: best=cost bestr=r return r
一千次的嘗試可能是總解總很說得好的一部分,但是在多試幾次之後,也許會得到一個差不多的解;
隨機搜尋法的問題在於,隨機搜尋是跳躍性的,這次的計算和上次的計算之前沒有任何關係,也就是最新一次計算並沒能利用之前已發現的優解,下面看看幾種改進的演算法:
(二)類比爬山法
爬山法從一個隨機解開始,然後在其臨近的解中尋找更好的題解。在本例中,亦即找到所有相對於最初的隨機安排,能夠讓某個人乘坐的航班稍早或者稍晚一些的安排,沃恩對每一個相鄰的時間安排都進行成本計算,具有最低成本的安排將成為新的題解。重複這一過程知道沒有相鄰安排能夠改善成本為止:
def hillclimb(domain,costf): # Create a random solution sol=[random.randint(domain[i][0],domain[i][1]) for i in range(len(domain))] # Main loop while 1: # Create list of neighboring solutions neighbors=[] for j in range(len(domain)): # One away in each direction if sol[j]>domain[j][0]: neighbors.append(sol[0:j]+[sol[j]+1]+sol[j+1:]) if sol[j]<domain[j][1]: neighbors.append(sol[0:j]+[sol[j]-1]+sol[j+1:]) # See what the best solution amongst the neighbors is current=costf(sol) best=current for j in range(len(neighbors)): cost=costf(neighbors[j]) if cost<best: best=cost sol=neighbors[j] # If there's no improvement, then we've reached the top if best==current: break return sol
之所以叫爬山法,就是我們沿著一條下坡路一直走到坡底,這個方法的速度很快,並且找到的結果一般會比隨機搜尋法好一些。但是也有個問題,這種演算法的假設是最優解就在初始下坡位置的波穀處,第一次的隨機解會影響最終的效果。如果初始所處的位置的坡穀不是所有坡穀中最低的,那這種演算法的結果不會得到最優解,所以這種演算法可以求出局部最優解,而不是全域最優解。
(三)類比退火法
退火指合金在加熱後再慢慢冷卻的過程。大量的原子因為受到激發而向周圍跳躍,然後又逐漸穩定到一個低能階的狀態。
有時候在找到最優解前轉向一個更差的解是有必要的。退火法一個隨機解開始,用一個變數表示溫度,溫度在最開始會很高,爾後慢慢變低。每一次迭代周期,演算法會隨機選中題解中的某個數字,然後朝某個方向變化。這個演算法和爬山法的區別是。爬山法每次的迭代都會朝成本最低的發展,而退火法不一定,有一定的比例選選擇出更差的解成為當前迭代題解,這是避免局部最優解的一種嘗試。
溫度在開始會比較高,也就有更高的機率會轉向更差的解,隨著迭代的進行,溫度越來越低,演算法越來越不能接受較差的解。
def annealingoptimize(domain,costf,T=10000.0,cool=0.95,step=1): # Initialize the values randomly vec=[float(random.randint(domain[i][0],domain[i][1])) for i in range(len(domain))] while T>0.1: # Choose one of the indices i=random.randint(0,len(domain)-1) # Choose a direction to change it dir=random.randint(-step,step) # Create a new list with one of the values changed vecb=vec[:] vecb[i]+=dir if vecb[i]<domain[i][0]: vecb[i]=domain[i][0] elif vecb[i]>domain[i][1]: vecb[i]=domain[i][1] # Calculate the current cost and the new cost ea=costf(vec) eb=costf(vecb) p=pow(math.e,(-eb-ea)/T) # Is it better, or does it make the probability # cutoff? if (eb<ea or random.random()<p): vec=vecb # Decrease the temperature T=T*cool return vec
T是初始溫度,cool是冷卻率,step是每次迭代的步伐。
(四)遺傳演算法
這種演算法是先隨機產生一組結果,我們成為種群。在最佳化的每次迭代中,演算法會計算整個種群中成本函數,從而得到一個有關題解的有序列表。在對題解排序後,我們來創造下一代種群:首先,當前種群中位於最頂端題解加入新種群,這是精英選拔,新種群的其他成員是修改最優解的變異而成。
變異有兩種方法,一種是對最優解進行微小的簡單的隨機的改變。在本例中,我們從最優解中隨機播放一個數字,然後對其遞增遞減即可。另一種方法稱為交叉或者配對。這種做法是選取最優解中的兩個解,然後講他們按照某種方式進行結合。在本例中,實現交叉的一種簡單的方式是,從一個解中隨機取出一個數字作為新題解的某個元素,剩餘元素則來自於另一個題解。
一個新種群是通過對最優解進行隨機的變異和配對處理構造出來的,它的大小通常和舊種群相同,爾後,這一過程會一直重複進行。新的種群進過排序,又一個種群被構造出來。達到指定的迭代次數或者連續經過數次迭代後結果都沒有得到改善,整個過程結束。
def geneticoptimize(domain,costf,popsize=50,step=1, mutprob=0.2,elite=0.2,maxiter=100): # Mutation Operation def mutate(vec): i=random.randint(0,len(domain)-1) if random.random()<0.5 and vec[i]>domain[i][0]: return vec[0:i]+[vec[i]-step]+vec[i+1:] elif vec[i]<domain[i][1]: return vec[0:i]+[vec[i]+step]+vec[i+1:] # Crossover Operation def crossover(r1,r2): i=random.randint(1,len(domain)-2) return r1[0:i]+r2[i:] # Build the initial population pop=[] for i in range(popsize): vec=[random.randint(domain[i][0],domain[i][1]) for i in range(len(domain))] pop.append(vec) # How many winners from each generation? topelite=int(elite*popsize) # Main loop for i in range(maxiter): scores=[(costf(v),v) for v in pop] scores.sort() ranked=[v for (s,v) in scores] # Start with the pure winners pop=ranked[0:topelite] # Add mutated and bred forms of the winners while len(pop)<popsize: if random.random()<mutprob: # Mutation c=random.randint(0,topelite) pop.append(mutate(ranked[c])) else: # Crossover c1=random.randint(0,topelite) c2=random.randint(0,topelite) pop.append(crossover(ranked[c1],ranked[c2])) # Print current best score print scores[0][0] return scores[0][1]
popsize指的是種群大小,mutprob指的是新成員是由變異或者交叉而來的機率,elite值得是種群中可以遺傳到下一代的部分,maxiter迭代的最大次數。
代碼還是很簡單的,等下次多搞點資料,我來試試效果。。。