1. 程式人生 > >集體智慧程式設計chapter5:優化問題

集體智慧程式設計chapter5:優化問題

優化問題1:組團旅遊

五個人要乘坐航班去同一個地方,如何安排航班可以使總成本最低
思路:目標函式是一個10維list,[1,4,3,2,7,3,6,3,2,4,5,3],表示每個人乘坐第幾趟航班往返

優化問題2:學生宿舍優化

10個學生選擇5間宿舍10個床位,每人2個志願,如何安排可以符合多數人的志願
思路:將每個床位看作一個槽,十個學生選擇十個槽

關鍵點

  1. 設定初始值
  2. 設定成本函式cost,即損失函式

可用演算法

隨機選擇:完全隨機,保留最優解

def randomptimize(domain, costf):
    best = 999999999
    bestr = None
    for
i in range(10000): # 我們打算隨機產生1000次結果,從這1000次結果中選擇一個最好的 # 很顯然randint是產生在一定範圍內的隨機數,顯然由於下一句右邊等號裡的for,將會產生一個迴圈 r = [random.randint(domain[j][0], domain[j][1]) for j in range(len(domain))] cost = costf(r) # 每次得到成本我們都判斷一次,如果更低,我們就置換 if cost < best: best = cost bestr =
r return bestr

爬山法:每次更新一個值,保留最優解,容易陷入區域性最優

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)): # 對於每個元素+1或者-1 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.98, step=1):
  # 和爬山法一樣,先產生一個隨機解,然後一切的改變都從這個隨機解開始
  vec = [random.randint(domain[i][0], domain[i][1]) for i in range(len(domain))]

  while T > 0.5:
      # 產生一個隨機數,決定這次改變是改變數列中的哪一個隨機數
      i = random.randint(0, len(domain) - 1)

      # 選擇一個改變的方向,也就是說是增加還是減少
      dir = random.randint(-step, step)

      # 複製隨機解,然後對隨機解進行改變,然後判斷到底新的解好,還是後來產生的解好
      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]

      # 計算新產生的兩次解的成本,然後對成本進行比較
      ea = costf(vec)
      eb = costf(vecb)

      # or後面:表示接受更差的結果。仔細想想,原來概率的表示是如此完成的,注意前一個random()產生的數是在0到1之間。
      if (eb < ea or random.random() < pow(math.e, -(eb - ea) / T)):
          vec = vecb

          # 沒經過一次迴圈,改變溫度,溫度一改變,就會改變迴圈的次數和接受更差解的概率
      # 按一定比例降溫
      T = T * cool

  return vec

遺傳演算法:交叉變異,每次保留一定的最優序列

def geneticoptimize(domain, costf, popsize=50, step=1, mutprob=0.2, elite=0.2, maxiter=100):
  # 方法中還在定義方法
  # 變異操作
  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:]
      else:
          return vec
      # 交叉操作:貌似用python程式設計是好快的說,我感覺比較複雜的句子只要兩句麼,還是我c/c++沒學好

  def crossover(r1, r2):
      # 為什麼減2,其實想把這個一個數字列表劃分為兩段,再各取一半
      i = random.randint(1, len(domain) - 2)
      return r1[0:i] + r2[i:]

  # 構造初始種群
  pop = []
  for i in range(popsize):
      vec = [random.randint(domain[i][0], domain[i][1]) for i in range(len(domain))]
      pop.append(vec)

      # 每一代有多少優勢物種,我們需要保留
  topelite = int(elite * popsize)
  # 主迴圈
  for i in range(maxiter):
      # print pop #但是如果不加這句會使下一句出現一個bug,就是傳過去的v是None,但是我講pop全部打印出來的話,又沒有問題
      scores = [(costf(v), v) for v in pop]  # 列表裡面,每一個元素都是一個元組,每一個元組是由一個數字和一個列表構成
      scores.sort()
      ranked = [v for (s, v) in scores]

      # 從中選擇我們覺得優勢的物種,然後保留
      pop = ranked[0:topelite]

      # 如果種群數量不夠,那麼我們使用變異或者配對,產生新的後代個體
      while len(pop) < popsize:
          # 變異的概率,這是由我們設定的,雖然這裡是變異和配對只能選擇其一,但是我認為是可以共同進行的
          if random.random() < mutprob:  # 如果這樣做,就是變異的少,交叉的多吧
              # 變異
              c = random.randint(0, topelite)  # 注意是從優秀的子代中選出一個進行變異
              pop.append(mutate(ranked[c]))
          else:
              c1 = random.randint(0, topelite)  # 從優秀的子代中選擇
              c2 = random.randint(0, topelite)  # 從優秀的子代中選擇
              pop.append(crossover(ranked[c1], ranked[c2]))

      print(scores[0][0])  # 注意列印的是成本

  return scores[0][1]  # 這裡返回的是航班序列