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

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


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


你可能感兴趣的:(数据挖掘,机器学习,遗传算法,优化解)