遺傳算法的原理參考維基百科:https://zh.wikipedia.org/wiki/%E9%81%97%E4%BC%A0%E7%AE%97%E6%B3%95
遺傳算法流程圖:
遺傳算法的思想和流程都是很簡單的,但是運用在具體應用時卻會常常無從下手。如何編碼解碼,如何進行交叉是兩個難點。
本文以用遺傳算法解決旅行商問題(TSP)爲例。
問題描述:現有34個城市,已知其座標;從其中某一城市作爲起點出發,途徑其他的所有城市,然後回到起點,要求走過的距離最短。
34個城市位置圖如下所示:
重慶,106.54,29.59
拉薩,91.11,29.97
烏魯木齊,87.68,43.77
銀川,106.27,38.47
呼和浩特,111.65,40.82
南寧,108.33,22.84
哈爾濱,126.63,45.75
長春,125.35,43.88
瀋陽,123.38,41.8
石家莊,114.48,38.03
太原,112.53,37.87
西寧,101.74,36.56
濟南,117,36.65
鄭州,113.6,34.76
南京,118.78,32.04
合肥,117.27,31.86
杭州,120.19,30.26
福州,119.3,26.08
南昌,115.89,28.68
長沙,113,28.21
武漢,114.31,30.52
廣州,113.23,23.16
臺北,121.5,25.05
海口,110.35,20.02
蘭州,103.73,36.03
西安,108.95,34.27
成都,104.06,30.67
貴陽,106.71,26.57
昆明,102.73,25.04
香港,114.1,22.2
澳門,113.33,22.13
採用遺傳算法解決此問題:
爲了簡化說明,對城市進行編號0,1,2,3……33
由於起點是任意的,不影響結果的,所以取起點爲城市15
參數設計:種羣規模count=3000,染色體長度length=33,進化次數itter_time=300
編碼策略採用十進制:直接用城市的編號進行編碼,染色體{1,2,……33}表示路徑爲15-1-2-……-33。
初始化種羣:爲了加快程序的運行速度,在初始種羣的選取中要選取一些較優秀的個體。我們先利用經典的近似算法—改良圈算法求得一個較好的初始種羣。算法思想是隨機生成一個染色體,比如{1,2,……33},任意交換兩城市順序位置,如果總距離減小,則更新改變染色體,如此重複,直至不能再做修改。
對應代碼:
#改良
def improve(x):
i=0
distance=get_total_distance(x)
while i<improve_count:
# randint [a,b]
u=random.randint(0,len(x)-1)
v = random.randint(0, len(x)-1)
if u!=v:
new_x=x.copy()
t=new_x[u]
new_x[u]=new_x[v]
new_x[v]=t
new_distance=get_total_distance(new_x)
if new_distance<distance:
distance=new_distance
x=new_x.copy()
else:
continue
i+=1
交叉策略:交叉策略是本問題的難點,如果直接採用點交叉那麼得到的子代染色體會出現城市重複和城市遺漏的問題。所以採用次序交叉法。次序雜交算法首先 隨機地在雙親中選擇兩個雜交點,再交換雜交段,其他 位置根據雙親城市的相對位置確定。例如:A1[10]={0, 8,5,4,7,3,1,6,9,2},A2[10]={1,5,4,6,9,0,3,2,8,7}, 隨機雜交點爲 4,7。 首先交換雜交段: B1[10]={&,&,&,&|9,0,3,2|&,&} B2[10]={&,&, &,&|7,3,1,6|&,&}, 從 A1 的第二個雜交點開始 9— 2—0—8—5—4—7—3—1—6,去除雜交段中的元素得 8—5—4—7—1—6, 依次從第二個雜交點開始填入得 B1[10]={4,7,1,6,9,0,3,2,8,5},同理得 B2[10]={4,9, 0,2,7,3,1,6,8,5}。
對應代碼:
#交叉繁殖
def crossover(parents):
#生成子代的個數,以此保證種羣穩定
target_count=count-len(parents)
#孩子列表
children=[]
while len(children)<target_count:
male_index = random.randint(0, len(parents) - 1)
female_index = random.randint(0, len(parents) - 1)
if male_index!=female_index:
male=parents[male_index]
female=parents[female_index]
left=random.randint(0,len(male)-2)
right=random.randint(left+1,len(male)-1)
#交叉片段
gene1=male[left:right]
gene2=female[left:right]
child1_c=male[right:]+male[:right]
child2_c=female[right:]+female[:right]
child1=child1_c.copy()
child2= child2_c.copy()
for o in gene2:
child1_c.remove(o)
for o in gene1:
child2_c.remove(o)
child1[left:right]=gene2
child2[left:right]=gene1
child1[right:]=child1_c[0:len(child1)-right]
child1[:left] = child1_c[len(child1) - right:]
child2[right:] = child2_c[0:len(child1) - right]
child2[:left] = child2_c[len(child1) - right:]
children.append(child1)
children.append(child2)
return children
變異:變異也是實現羣體多樣性的一種手段,同時也是全局尋優的保證。具體設計如下, 按照給定的變異率,對選定變異的個體,隨機地取三個整數,滿足 1<u<v<w<33 ,把 v 、u之間(包括u 和v)的基因段插到w後面。
對應代碼:
#變異
def mutation(children):
for i in range(len(children)):
if random.random() < mutation_rate:
child=children[i]
u=random.randint(1,len(child)-4)
v = random.randint(u+1, len(child)-3)
w= random.randint(v+1, len(child)-2)
child=children[i]
child=child[0:u]+child[v:w]+child[u:v]+child[w:]
全部代碼:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib
import math
import random
matplotlib.rcParams['font.family']= 'STSong'
#載入數據
city_name=[]
city_condition=[]
with open('data.txt','r') as f:
lines=f.readlines()
for line in lines:
line=line.split('\n')[0]
line=line.split(',')
city_name.append(line[0])
city_condition.append([float(line[1]),float(line[2])])
city_condition=np.array(city_condition)
#展示地圖
# plt.scatter(city_condition[:,0],city_condition[:,1])
# plt.show()
#距離矩陣
city_count=len(city_name)
Distance=np.zeros([city_count,city_count])
for i in range(city_count):
for j in range(city_count):
Distance[i][j]=math.sqrt((city_condition[i][0]-city_condition[j][0])**2+(city_condition[i][1]-city_condition[j][1])**2)
#種羣數
count=300
#改良次數
improve_count=10000
#進化次數
itter_time=3000
#設置強者的定義概率,即種羣前30%爲強者
retain_rate=0.3
#設置弱者的存活概率
random_select_rate=0.5
#變異率
mutation_rate=0.1
#設置起點
origin=15
index=[i for i in range(city_count)]
index.remove(15)
# def get_path(x):
# graded = [[x[i], index[i]] for i in range(len(x))]
# graded_index = [t[1] for t in sorted(graded)]
# return graded_index
#總距離
def get_total_distance(x):
distance=0
distance+=Distance[origin][x[0]]
for i in range(len(x)):
if i==len(x)-1:
distance += Distance[origin][x[i]]
else:
distance += Distance[x[i]][x[i+1]]
return distance
#改良
def improve(x):
i=0
distance=get_total_distance(x)
while i<improve_count:
# randint [a,b]
u=random.randint(0,len(x)-1)
v = random.randint(0, len(x)-1)
if u!=v:
new_x=x.copy()
t=new_x[u]
new_x[u]=new_x[v]
new_x[v]=t
new_distance=get_total_distance(new_x)
if new_distance<distance:
distance=new_distance
x=new_x.copy()
else:
continue
i+=1
#自然選擇
def selection(population):
"""
選擇
先對適應度從大到小排序,選出存活的染色體
再進行隨機選擇,選出適應度雖然小,但是倖存下來的個體
"""
# 對總距離從小到大進行排序
graded = [[get_total_distance(x), x] for x in population]
graded = [x[1] for x in sorted(graded)]
# 選出適應性強的染色體
retain_length = int(len(graded) * retain_rate)
parents = graded[:retain_length]
# 選出適應性不強,但是倖存的染色體
for chromosome in graded[retain_length:]:
if random.random() < random_select_rate:
parents.append(chromosome)
return parents
#交叉繁殖
def crossover(parents):
#生成子代的個數,以此保證種羣穩定
target_count=count-len(parents)
#孩子列表
children=[]
while len(children)<target_count:
male_index = random.randint(0, len(parents) - 1)
female_index = random.randint(0, len(parents) - 1)
if male_index!=female_index:
male=parents[male_index]
female=parents[female_index]
left=random.randint(0,len(male)-2)
right=random.randint(left+1,len(male)-1)
#交叉片段
gene1=male[left:right]
gene2=female[left:right]
child1_c=male[right:]+male[:right]
child2_c=female[right:]+female[:right]
child1=child1_c.copy()
child2= child2_c.copy()
for o in gene2:
child1_c.remove(o)
for o in gene1:
child2_c.remove(o)
child1[left:right]=gene2
child2[left:right]=gene1
child1[right:]=child1_c[0:len(child1)-right]
child1[:left] = child1_c[len(child1) - right:]
child2[right:] = child2_c[0:len(child1) - right]
child2[:left] = child2_c[len(child1) - right:]
children.append(child1)
children.append(child2)
return children
#變異
def mutation(children):
for i in range(len(children)):
if random.random() < mutation_rate:
child=children[i]
u=random.randint(1,len(child)-4)
v = random.randint(u+1, len(child)-3)
w= random.randint(v+1, len(child)-2)
child=children[i]
child=child[0:u]+child[v:w]+child[u:v]+child[w:]
#得到最佳純輸出結果
def get_result(population):
graded = [[get_total_distance(x), x] for x in population]
graded = sorted(graded)
return graded[0][0],graded[0][1]
#使用改良圈算法初始化種羣
population=[]
for i in range(count):
#隨機生成個體
x=index.copy()
random.shuffle(x)
improve(x)
population.append(x)
register=[]
i=0
distance, result_path = get_result(population)
while i<itter_time:
#選擇繁殖個體羣
parents=selection(population)
#交叉繁殖
children=crossover(parents)
#變異操作
mutation(children)
#更新種羣
population=parents+children
distance,result_path=get_result(population)
register.append(distance)
i=i+1
print(distance)
print(result_path)
result_path=[origin]+result_path+[origin]
X=[]
Y=[]
for index in result_path:
X.append(city_condition[index,0])
Y.append(city_condition[index, 1])
plt.plot(X,Y,'-o')
plt.show()
plt.plot(list(range(len(register))),register)
plt.show()
最終結果圖如下:
可以增加代數,增加變異率,得到更加好的結果。