앞의 두 가지 유전 알고리즘을 배웠지만 모두 추상적인 수학적 문제를 해결하는 데 목적이 있으므로 유전 알고리즘을 현실의 실제 문제에 어떻게 적용할 것인가, 그러다가 처음 접하게 된 문제는 차량 경로 최적화 문제입니다. VRP.물론 좋은 글인 Logistics Management Paper Implementation: Vehicle Routing Optimization with Time Window and Load Constraints Based on Genetic Algorithms도
찾았습니다 . 이 블로거가 더 강력하기 때문에 읽기가 더 어렵습니다. 그는 그 안에 클래스 클래스를 정의하고 일반 정의 기능과 약간 다른 자체가 많을 것입니다. 구체적인 선택, 교배, 변이 연산을 구현하는 방법을 몰라 앞선 두 글의 연구와 나만의 이해를 바탕으로 코드를 다시 작성했다. 또한 단순화했습니다. 가장 짧은 거리만 요구하면 됩니다. 이 유전자 알고리즘이 실제 문제에서 어떻게 보이는지 확인하고 싶기 때문입니다. 물론 간단할수록 더 잘 이해할 수 있다고 생각합니다. , 대부분의 사람들은 괜찮은.
그리고 이 과정에서 기술도 배웠는데, 오류가 발생하여 작업을 계속할 수 없을 때 try를 추가하고 예외를 제외하고 오류를 건너뛰고 계속 실행하면 결과를 얻을 수 있습니다.
먼저 질문에 주어진 조건을 정의하고 매개변수를 초기화합니다.
geneNum = 100 # 种群数量
generationNum = 300 # 迭代次数
CENTER = 0 # 配送中心
# HUGE = 9999999
# PC = 1 #交叉率,没有定义交叉率,也就是说全部都要交叉,也就是1
PM = 0.1 # 变异率 以前是用的vary
n = 25 # 客户点数量
m = 2 # 换电站数量
k = 3 # 车辆数量
Q = 5 # 额定载重量, t
# dis = 160 # 续航里程, km
length = n+m+1
# 坐标 第0个是配送中心 1-25是顾客 26和27是换电站 一共28个位置 行驶距离要通过这个坐标自己来算
X = [56, 66, 56, 88, 88, 24, 40, 32, 16, 88, 48, 32, 80, 48, 23, 48, 16, 8, 32, 24, 72, 72, 72, 88, 104, 104, 83,32]
Y = [56, 78, 27, 72, 32, 48, 48, 80, 69, 96, 96, 104, 56, 40, 16, 8, 32, 48, 64, 96, 104, 32, 16, 8, 56, 32, 45, 40]
# 需求量
t = [0, 0.2, 0.3, 0.3, 0.3, 0.3, 0.5, 0.8, 0.4, 0.5, 0.7, 0.7, 0.6, 0.2, 0.2, 0.4, 0.1, 0.1, 0.2, 0.5, 0.2, 0.7,0.2,0.7, 0.1, 0.5, 0.4, 0.4]
코딩 : 실제 문제에 따라 코딩한 후 실수 코딩을 사용하여 획득해야 할 내용을
2단계에 걸쳐 염색체에 배치하여 조건을 만족하는 초기 개체를 생성한다. 첫 번째와 마지막 위치에 물류센터 0을 삽입한 후 차량의 운송 수요의 합이 차량의 적재량을 초과하지 않는지 여부에 따라 이 무순서 목록에 무작위로 0을 물류센터에서 새로운 시작으로 삽입합니다. , 이는 여러 대의 자동차가 있음을 의미합니다. 참고: 여기서 초기 인구가
생성됩니다. 이전 두 기사의 순수 수학 문제만큼 간단하지 않으며 요구 사항을 충족하는 초기 솔루션을 생성하는 함수로 작성해야 합니다. 즉, 초기 모집단입니다.
def getGene(length):
##先产生一个无序的列表
data = list(range(1,length)) ##先产生一个有序的列表
np.random.shuffle(data) ##有序列表打乱成无序列表
data.insert(0, CENTER) ##在开始插入0
data.append(CENTER) ##在结尾插入0
#再插入车
sum = 0
newData = []
for index, pos in enumerate(data):
sum += t[pos]
if sum > Q:
newData.append(CENTER)
sum = t[pos]
newData.append(pos)
return newData
def getpop(length,geneNum):
pop = []
for i in range(geneNum):
gene = getGene(length)
pop.append(gene)
return pop
체력 값을 계산하고, 개인의 체력 값을 계산한 다음 전체 모집단의 체력 목록을 가져옵니다.
참고: 체력 값은 각 지점의 좌표에 따라 표현해야 하는 거리 함수입니다.
##计算一个个体的适应度值
def getfit(gene):
distCost = 0
dist = [] # from this to next
# 计算距离
i = 1
while i < len(gene):
calculateDist = lambda x1, y1, x2, y2: math.sqrt(((x1 - x2) ** 2) + ((y1 - y2) ** 2))
dist.append(calculateDist(X[gene[i]], Y[gene[i]], X[gene[i - 1]], Y[gene[i - 1]]))
i += 1
# 距离成本
distCost = sum(dist) #总行驶距离
fit = 1/distCost ##fitness越小表示越优秀,被选中的概率越大,
return fit
##得到整个种群的适应度列表
def getfitness(pop):
fitness = []
for gene in pop:
fit = getfit(gene)
fitness.append(fit)
return np.array(fitness)
룰렛을 이용한 선택, 적합도 값이 클수록 다음 세대로 선택될 확률이 높아짐
def select(pop,fitness):
fitness = fitness / fitness.sum() # 归一化
idx = np.array(list(range(geneNum)))
pop_idx = np.random.choice(idx, size=geneNum, p=fitness) # 根据概率选择
for i in range(geneNum):
pop[i] = pop[pop_idx[i]]
return pop
여기의 교차도 더 번거로운데, 이 문제에서 아무렇게나 교차할 수 없기 때문입니다. 왜냐하면 5개의 교차를 사용하여 6으로 교환하지만 실제로 이 개인에는 이미 6이 있고 각 고객은 한 번만 방문할 수 있기 때문입니다. 문제를 충족하지 않는 규정이므로
길을 닦기 위해 일부 작업을 수행해야 합니다.경로 선택의 효과는 다음과 같습니다.
그러면 두 개인의 교차점 효과는 다음과 같습니다.
gene1을 예로 들어 보겠습니다. , gene2에는 있지만 gene1 앞의 경로에는 없는 숫자를 더하는 것입니다.
그리고 적합도가 높은 처음 1/3 모집단만 교차 선택하는데 교차 확률이 없다면 모두 교차여야 하는데 교차 후 얻은 모집단은 개체의 1/3에 불과하므로 1/3 개체를 대체한다. 적합도가 낮은 원래 모집단의 마지막 부분으로 이동하고 최종적으로 완전한 모집단으로 병합합니다.
#选择路径
def moveRandSubPathLeft(gene):
import random
path = random.randrange(k) # 选择路径索引,随机分成k段
print('path:',path)
try:
index = gene.index(CENTER, path+1) #移动到所选索引
# move first CENTER
locToInsert = 0
gene.insert(locToInsert, gene.pop(index))
index += 1
locToInsert += 1
# move data after CENTER
print('index:',index)
try:
print('len(gene):',len(gene))
while gene[index] != CENTER:
gene.insert(locToInsert, gene.pop(index))
index += 1
print('执行完index+1,index=',index)
locToInsert += 1
return gene
# assert(length+k == len(gene))
except:
print('出错啦,index:',index)
return gene
except:
print('0 is not in list',gene)
return gene
# 选择复制,选择适应度最高的前 1/3,进行后面的交叉
def choose(pop):
num = int(geneNum/6) * 2 # 选择偶数个,方便下一步交叉
# sort genes with respect to chooseProb
key = lambda gene: getfit(gene)
pop.sort(reverse=True, key=key) ##那就是说按照适应度函数降序排序,选了适应度值最高的那1/3
# return shuffled top 1/3
return pop[0:num]
교차의 경우 교차의 확률은 고려되지 않습니다. 왜냐하면 모두 차례로 교차하기 때문입니다. 그러나 코드는 먼저 한 쌍의 교차를 작성한 다음 이전에 더 높은 적합도를 선택한 첫 번째 1/3 모집단을 교차합니다.
##交叉一对
def crossPair(i,gene1, gene2, crossedGenes):
gene1 = moveRandSubPathLeft(gene1)
gene2 = moveRandSubPathLeft(gene2)
newGene1 = []
newGene2 = []
# copy first paths
centers = 0
firstPos1 = 1
for pos in gene1:
firstPos1 += 1
centers += (pos == CENTER)
newGene1.append(pos)
if centers >= 2:
break
centers = 0
firstPos2 = 1
for pos in gene2:
firstPos2 += 1
centers += (pos == CENTER)
newGene2.append(pos)
if centers >= 2:
break
# copy data not exits in father gene
for pos in gene2:
if pos not in newGene1:
newGene1.append(pos)
for pos in gene1:
if pos not in newGene2:
newGene2.append(pos)
# add center at end
newGene1.append(CENTER)
newGene2.append(CENTER)
# 计算适应度最高的
key1 = lambda gene1: getfit(gene1)
possible1 = []
try:
while gene1[firstPos1] != CENTER:
newGene = newGene1.copy()
newGene.insert(firstPos1, CENTER)
possible1.append(newGene)
firstPos1 += 1
print('第{}位置:{}'.format(i,len(possible1)))
if len(possible1) == 0:
crossedGenes.append(newGene1)
else:
possible1.sort(reverse=True, key=key1)
crossedGenes.append(possible1[0])
except:
print('交叉出错啦:firstPos1', firstPos1)
key2 = lambda gene2: getfit(gene2)
possible2 = []
try:
while gene2[firstPos2] != CENTER:
newGene = newGene2.copy()
newGene.insert(firstPos2, CENTER)
possible2.append(newGene)
firstPos2 += 1
print('第{}:{}'.format(i,len(possible2)))
if len(possible2) == 0:
crossedGenes.append(newGene2)
else:
possible2.sort(reverse=True, key=key2)
crossedGenes.append(possible2[0])
print('交叉完成第:', i)
except:
print('交叉出错啦:',i)
# 交叉
def cross(genes):
crossedGenes = []
for i in range(0, len(genes), 2):
# print('gene[i]:',genes[i])
# print('gene[i+1]:', genes[i])
crossPair(i,genes[i], genes[i+1], crossedGenes)
print('交叉完成')
return crossedGenes
# 合并
def mergeGenes(genes, crossedGenes):
# sort genes with respect to chooseProb
key = lambda gene: getfit(gene)
genes.sort(reverse=True, key=key) ##先把原来的种群100按照适应度降序排列,然后,将交叉得到的32个个体替换到种群的最后32个
pos = geneNum - 1
for gene in crossedGenes:
genes[pos] = gene
pos -= 1
return genes
Mutation, 먼저 개체가 어떻게 변이하는지를 적고 난 뒤 돌연변이 확률에 따라 전체 교배 모집단을 변이시킨다
참고: 여기서 변이는 매우 간단하며 초기 모집단에서 개체를 생성하는 방식으로 직접 새로운 개체를 생성하지만, 여기에도 몇 명의 개체를 더 생성하고 가장 적합도가 높은 개체를 선택하여 오류를 줄이는 방법을 사용합니다.
# 变异一个
def varyOne(gene):
varyNum = 10
variedGenes = []
for i in range(varyNum): # 先按照这种方法变异10个,选择适应度最高的那个作为变异完的子代
p1, p2 = random.choices(list(range(1,len(gene)-2)), k=2)
newGene = gene.copy()
newGene[p1], newGene[p2] = newGene[p2], newGene[p1] # 交换
variedGenes.append(newGene)
key = lambda gene: getfit(gene)
variedGenes.sort(reverse=True, key=key)
return variedGenes[0]
# 变异
def vary(genes):
for index, gene in enumerate(genes):
# 精英主义,保留前三十,这个意思就是前三十个一定不变异,到后面的个体才按照变异概率来变异
if index < 30:
continue
if np.random.rand() < PM:
genes[index] = varyOne(gene)
return genes
유전 알고리즘 과목
import numpy as np
import random
from tqdm import * # 进度条
import matplotlib.pyplot as plt
from pylab import *
mpl.rcParams['font.sans-serif'] = ['SimHei']
mpl.rcParams['axes.unicode_minus'] = False
best_fitness = []
min_cost = []
J = []
pop = getpop(length, geneNum) # 初始种群
# 迭代
for j in tqdm(range(generationNum)):
print('j=',j)
chosen_pop = choose(pop) # 选择 选择适应度值最高的前三分之一,也就是32个种群,进行下一步的交叉
crossed_pop = cross(chosen_pop) # 交叉
pop = mergeGenes(pop, crossed_pop) # 复制交叉至子代种群
pop = vary(pop) # under construction
key = lambda gene: getfit(gene)
pop.sort(reverse=True, key=key) # 以fit对种群排序
cost = 1/getfit(pop[0])
print(cost)
min_cost.append(cost)
J.append(j)
print(J)
print(min_cost)
# key = lambda gene: getfit(gene)
# pop.sort(reverse=True, key=key) # 以fit对种群排序
print('\r\n')
print('data:', pop[0])
print('fit:', 1/getfit(pop[0]))
plt.plot(J,min_cost, color='r')
plt.show()