차량 경로 최적화 문제 VRP를 해결하기 위한 유전 알고리즘(Python 코드 구현)

앞의 두 가지 유전 알고리즘을 배웠지만 모두 추상적인 수학적 문제를 해결하는 데 목적이 있으므로 유전 알고리즘을 현실의 실제 문제에 어떻게 적용할 것인가, 그러다가 처음 접하게 된 문제는 차량 경로 최적화 문제입니다. 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()

Guess you like

Origin blog.csdn.net/weixin_43697614/article/details/127561111