欢迎关注我的个人博客blog.timene.com
优化系统的想法真不好简单地说的明白,这样吧,我爸在陕西西安,我妈在安徽的合肥,我弟弟在深圳,打算坐飞机到北京我这里玩。家人都比较节省,打算到了机场后互相等对方,然后一起坐车租车到我住的地方。
我查了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 vecT是初始温度,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迭代的最大次数。
代码还是很简单的,等下次多搞点数据,我来试试效果。。。