集體智慧程式設計chapter5:優化問題
阿新 • • 發佈:2019-01-26
優化問題1:組團旅遊
五個人要乘坐航班去同一個地方,如何安排航班可以使總成本最低
思路:目標函式是一個10維list,[1,4,3,2,7,3,6,3,2,4,5,3],表示每個人乘坐第幾趟航班往返
優化問題2:學生宿舍優化
10個學生選擇5間宿舍10個床位,每人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] # 這裡返回的是航班序列