集体智慧编程学习之优化系统

来源:互联网 发布:新加坡装甲车事件知乎 编辑:程序博客网 时间:2024/04/28 06:04

欢迎关注我的个人博客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 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迭代的最大次数。


代码还是很简单的,等下次多搞点数据,我来试试效果。。。