Evolutionary Computing: Notebook assignment - Traveling Salesman Problem 练习随笔

news/2024/9/16 23:57:55/文章来源:https://www.cnblogs.com/umikaze/p/18403627

本次练习基于EC算法经典的旅行商TSP问题

练习目标:

在所给的架构下编写并运行一个正确的进化算法,并解决经典的旅行商TSP问题。在该练习条件下,当每一次旅行商的旅行距离均低于13500公里时,则代表所编写之算法有效。

有两点额外要求:(1) 算法中人口规模Population必须 ≤100。(2) 迭代次数 ≤1000

实验中可以自由更改超参数,如突变/交叉概率(后续代码中提及)。

Traveling Salesman Problem

在本例中,我们学习如何使用EA来解决旅行推销员的问题。在这个问题中,一个推销员需要完成以最短的方式访问所有城市并返回出发点的任务。此外,我们假设旅行推销员有一个固定的起点和终点(阿姆斯特丹)。因此,有两个约束:

-每个城市都需要被访问到。
-旅行者以家作为起点和终点(本例中以阿姆斯特丹为例)。

在该练习中,我们将使用以下城市,并使用以下编码:

-0:阿姆斯特丹
-1:雅典
-2:柏林
-3:布鲁塞尔
-4:哥本哈根
-5:爱丁堡
-6:里斯本
-7:伦敦
-8:马德里
-9:巴黎

在本例中,我们使用邻接表来表示城市之间的距离,其中Aij表示从城市i到城市j的距离,通过查询我们可以得到上述城市间的距离邻接表,通过该链接可以查到https://www.engineeringtoolbox.com/driving-distances-d_1029.html

adjacency_mat = np.asarray(#Remember that we use the encoding above, i.e. 0 refers to Amsterdam and 10 to Paris!
    [[0, 3082, 649, 209, 904, 1180, 2300, 494, 1782, 515], # Distance Amsterdam to the other cities[3082, 0, 2552, 3021, 3414, 3768, 4578, 3099, 3940, 3140], # Distance Athens to the other cities[649, 2552, 0, 782, 743, 1727, 3165, 1059, 2527, 1094], # Distance Berlin to the other cities[209, 3021, 782, 0, 1035, 996, 2080, 328, 1562, 294], # Distance Brussels to the other cities[904, 3414, 743, 1035, 0, 1864, 3115, 1196, 2597, 1329], # Distance Copenhagen to the other cities[1180, 3768, 1727, 996, 1864, 0, 2879, 656, 2372, 1082], # Distance Edinburgh to the other cities[2300, 4578, 3165, 2080, 3115, 2879, 0, 2210, 638, 1786], # Distance Lisbon to the other cities[494, 3099, 1059, 328, 1196, 656, 2210, 0, 1704, 414], # Distance London to the other cities[1782, 3940, 2527, 1562, 2597, 2372, 638, 1704, 0, 1268], # Distance Madrid to the other cities[515, 3140, 1094, 294, 1329, 1082, 1786, 414, 1268, 0] # Distance Paris to the other cities
    ])

对于进化算法,首先我们需要完成Fitness Function(适应度函数)的编写。在本例中,适应度函数将旅行者行驶的总公里数作为适应度的衡量标准是直观的。给定路线覆盖的总公里数越低越好,因此我们编写如下fitness_function

 1 def compute_distance(route: list, adjacency_mat: np.ndarray) -> int:
 2     '''
 3     Calculates the total number of kilometers for a given route.
 4     '''
 5     total_distance = 0 # Initialize the value of total distance 初始化路径的值为0
 6     number_city = len(route) # Number of cities in the route 路径中的城市数量
 7     '''
 8     ToDo:
 9 
10     Please complete the function that calculates the total distance for a given route!
11     '''
12 
13     # 我们首先遍历路径中的每对连续城市
14     for i in range(number_city - 1): # 因为终点城市使用i+1,且Python的range()所取值是一个左闭右开区间,所以这样编写函数能保证最后一个城市被涵盖的同时不超出数组边界
15         start_city = route[i]
16         des_city = route[i + 1]
17         total_distance += adjacency_mat[start_city, des_city]
18     # 别忘了从最后一个城市返回到起点    
19     total_distance += adjacency_mat[route[-1], route[0]] # Calculate the distance from the last city back to the starting point
20     return total_distance

解释:

  1. 循环遍历路线中的每对连续城市

    • 通过 for i in range(num_cities - 1) 迭代路线中的每对相邻城市。
    • route 中获取起点和终点城市的索引,然后使用 adjacency_mat[from_city, to_city] 查找它们之间的距离并将其添加到 total_distance
  2. 处理回到起点的情况

    • 在旅行商问题中,路径通常是循环的,所以在计算完所有相邻城市之间的距离后,还需要计算从最后一个城市回到起点的距离,并将其添加到总距离中。
  3. 返回总距离

    • 最后返回计算的 total_distance

教材内容补充:

Evaluation/ fitness function 

Role:

  • Fitness fuction represents the task to solve, the requirements to adapt to (can be seen as 'the environment') 适应度函数代表了要解决的任务、要适应的要求(可以看作是“环境”) 
  • Enable Selection 使得后续的算法中可以对样本进行选择
  • If some phenotypic traits are advantegous, desirable, then these traits are rewared by more offspring that will expectedly carry the same trait 如果一些表型特征是有利的、可取的,那么这些特征会被更多的后代所认识(表现为适应度高),这些后代有望携带相同的特征

  Assgin a single real-valued fitness to each phenotype which forms the basis for selection 为每个表型提供一个单一的实值适应度,作为选择的基础 (所以不同的值越多越好 the more discrimination[different values] the better)

  Typically we talk about fitness being maximised 通常我们讨论如何让适应值最大,但某些问题则是适应度越小越好(比如本例)

 

完成Fitness function的编写后,下一步我要做的是从一代个体中找出表现最佳的个体(于本例而言,距离最短的)

def fittest_solution_TSP(compute_distance, generation: list, adjancency_mat: np.ndarray) -> tuple:'''This function calculates the fitness values of all individuals of a generation.It then returns the best fitness value (integer) and the corresponding individual (list).该函数计算一代中所有个体的适应度值,并返回最佳适应度值(整数)及其对应的个体(列表)。''''''ToDo:Please complete the function!'''#WRITE YOUR CODE HERE!best_fitness_value = float("inf") # 初始化为无限大,表示我们寻找最小值best_individual = None # 初始化最佳路线# Calculate the fitness value of the current individualfor indiv in generation:# 计算当前个体的适应度值(即总距离)用到了上一步编写的适应度函数(计算路径距离)current_fitness_value = compute_distance(indiv,adjacency_mat)# If the current individual's fitness value is better than the best fitness value found so far, update it# 如果当前个体的适应度值比目前找到的最佳适应度值更好,则更新if current_fitness_value <= best_fitness_value:best_fitness_value = current_fitness_valuebest_individual = indivreturn best_fitness_value,best_individual

解释:

  1. 初始化最佳值

    • best_fitness_value 被初始化为 float('inf'),这是一个极大的值。我们要寻找最小的距离,因此需要从一个极大的初始值开始。
    • best_individual 被初始化为 None,以便在找到最佳路径时更新。
  2. 遍历种群

    • 对于 generation 中的每一个个体(路径),计算其适应度值(即通过调用 compute_distance(indiv, adjacency_mat) 计算总距离)。
  3. 更新最佳路径

    • 如果当前个体的总距离小于当前的 best_fitness_value,则更新 best_fitness_valuebest_individual,记录这个更优的个体。
  4. 返回值

    • 返回找到的最小总距离 best_fitness_value 和对应的路径 best_individual

这个函数的核心是遍历整个种群,找出在这代中最优的解决方案。输出的最佳距离及其路径可用于进一步的算法优化或作为最终结果。

教材内容补充:

Selection

Role:

  • Identifies individuals 识别辨别个体 (成为亲本、生存、淘汰等等)
  • Pushes population towards higher fitness 推动人口世代向更高的适应度水平拟合
  • Usally probabilistic/stochastic 通常为概率/随机 
    • high quality solution more likely be selected than low quality solutions 高质量解比低质量解更容易被选中
    • but no guaranteed 并不一定是高质量的被选中
    • even the worst in current population usually has non-zero pobability of being selected 最差的解被选中的概率也不是0
  • This stochastic nature can help escape from local optima 随机性帮助摆脱局部最优解

Assgin a single real-valued fitness to each phenotype which forms the basis for selection 为每个表型提供一个单一的实值适应度,作为选择的基础 (所以不同的值越多越好 the more discrimination[different values] the better)

  Typically we talk about fitness being maximised 通常我们讨论如何让适应值最大,但某些问题则是适应度越小越好(比如本例)

 

定义了适应度函数后,我们需要一个函数来初始化(生成)我们的解决方案。

def initialize_population(n_population: int) -> list:'''This returns a randomly initialized list of individual solutions of size n_population.'''population = []# Based on a literature review, the suggested range for population size# is between one and two times the path length. i.e len(route) <= n <= 2*len(route)'''ToDo:Please complete the function!'''for _ in range(n_population):# 创建一个包含城市 1 到 9 的路径fixed_prefix,fixed_suffix = [0],[0]default_list  = list(range(1, 10))  # 从城市 1 到 9(排除城市 0)random.shuffle(default_list)  # 随机打乱这些城市的顺序individual = fixed_prefix + default_list + fixed_suffix # 为城市列表添加固定的前缀后后缀(即起终点都为阿姆斯特丹)population.append(individual)for individual in population: # 对这些新添加的个体进行检查#Implement some assertion tests for checking if the individual meets criteriaassert (individual[0] == individual[-1]==0), 'Make sure you start and end in Amsterdam' #起终点assert len(set(individual)) == 10, "Individual must contain all unique values from 0 to 9, as is must visit all cities" # 每个城市都访问了#set(individual):#将 individual 列表转换为一个集合 set。集合是一个无序的数据结构,自动去除重复的元素。#因此,如果 individual 列表中有重复的城市,集合会自动去掉这些重复项。assert (len(individual) == 11), "Individual must be length 11" # 长度必须为11 return population

initialize_population 函数中,我们的目标是生成一个具有 n_population 个体的初始种群。每个个体代表一个城市的访问顺序,路径的长度需要包括返回起点的步骤(即从城市 0 出发,经过所有其他城市,最后返回到城市 0)。你需要确保每个个体满足以下条件:

  1. 路径以城市 0(阿姆斯特丹)开始并结束
  2. 路径包含所有城市,且每个城市只出现一次
  3. 路径的长度为 11(包括从起点城市 0 开始,经过所有其他城市,最后返回到城市 0)。

解释:

  1. 生成城市路径

    • deafult_list = list(range(1, 10)) 创建了一个包含城市 19 的列表(排除起点城市 0)。
    • random.shuffle(deafult_list) 随机打乱这些城市的顺序,以生成不同的访问路径。
  2. 创建个体路径

    • individual = fixed_prefix + default_list + fixed_suffix
       在打乱的城市列表前后添加城市 0,表示从起点出发并最终返回到起点城市。
  3. 添加到种群

    • 将生成的个体路径添加到 population 列表中。
  4. 断言检查

    • 确保每个个体以城市 0 开始并结束。
    • 确保每个个体包含所有城市,并且每个城市只出现一次。
    • 确保个体的路径长度为 11。

这个实现会生成一个满足指定条件的初始种群列表,并对其进行验证。

 

 

现在我们已经定义了适应度函数和初始化人口的函数,我们需要定义变分算子,即交叉 Cross-Over和突变 Mutation。对于排列问题,一般将问题分为如下两类

顺序先决的问题(生产问题)或元素相邻的问题(邻接)。在我们的TSP问题中,重要的是哪个元素彼此相邻,即邻接。

在这个 mutation 函数中,我们需要实现一个突变操作,以对给定的路径 child 进行修改。突变是遗传算法中的一个关键步骤,可以帮助搜索到新的解空间。这里我们将讨论几种突变操作(如题所述),并在函数中实现其中一个或多个。我们还要确保突变后的路径满足特定条件,比如路径仍然以阿姆斯特丹(城市0)开始和结束,且必须覆盖所有城市。

突变操作选项

  1. Swap Operator(交换操作):

    • 随机选择路径中的两个城市,并交换它们的位置。
  2. Insert Operator(插入操作):

    • 随机选择路径中的一个城市,并将其插入到路径中的另一个位置。
  3. Scramble Operator(打乱操作):

    • 随机选择路径中的一个子序列,并将其打乱。
  4. Inversion Operator(逆序操作):

    • 随机选择路径中的一个子序列,并将其反转。
def mutation(child:list, p_mutation:float) -> list:'''This applies a mutation operator to a list and returns the mutated list.'''if np.random.uniform() > p_mutation: #np.random.uniform(default low=0.0, high = 1.0) -> generate a float in the interval [0,1)#no mutationreturn childelse:child_mutated = child[:] # Create a copy of the child to mutate# mutation_operator = np.random.choice(['swap', 'insert', 'scramble', 'inversion']) # Using mutation operator randomlymutation_operator = 'swap' # Using 'swap' as example'''ToDo:Please complete the function!'''if mutation_operator == 'swap':i,j = np.random.choice(range(1,len(child_mutated) - 1),size = 2,replace = False)child_mutated[i],child_mutated[j] = child_mutated[j],child_mutated[i] # Swap two random citieselif mutation_operator == 'insert':i = np.random.choice(range(1,len(child_mutated) - 1))insert_city = child_mutated.pop(i) # Pop a city from list randomlyj = np.random.choice(range(1,len(child_mutated) - 1)) # Choose a new position from list to insertchild_mutated.append(j, insert_city)elif mutation_operator == 'scramble':i,j = sorted(np.random.choice(range(1,len(child_mutated) - 1),size = 2,replace=False)) # Choose two numbers(Ascending sorted) as the start and end of the subsequencenp.random.shuffle(child_mutated[i:j+1]) # Shuffle a random subsequence of the path# 为什么是[i:j+1] 因为Python中切片是不包含右侧最大值的即如果我们想要获取的子序列是列表中的第i到第j个值,那么我们切片时# child[i:j+1]:返回从索引 i 到索引 j 的元素,即 包含起点 i 和终点 j。elif mutation_operator == 'inversion':i,j = sorted(np.random.choice(range(1,len(child_mutated) - 1),size = 2,replace=False))# Choose two numbers(Ascending sorted) as the start and end of the subsequencechild_mutated[i:j+1] = child_mutated[i:j+1][::-1] # Reverse the subsequence of the path# child[i:j+1][::-1] 这一表达式中的 [::-1] 是 Python 中的切片语法,用于反转序列(如列表、字符串等)。#Implement some assertion tests for checking if the mutation goes as expectedassert (child_mutated[0] == child_mutated[-1] and child_mutated[0] == 0 ), 'Make sure you start and end in Amsterdam'assert len(set(child_mutated)) == 10, "Individual must contain all unique values from 0 to 9, as is must visit all cities"assert (len(child_mutated) == 11), "Individual must be length 11"return child_mutated

解释

  • 突变的概率控制:我们首先使用 np.random.uniform() 来决定是否对个体进行突变。如果生成的随机数大于 p_mutation,则不进行突变,直接返回原始的 child

  • 突变操作选择:我们随机选择一种突变操作,分别为交换、插入、打乱和逆序。

  • 操作具体实现

    • 交换:随机选择两个位置并交换它们。
    • 插入:将一个城市移除,然后插入到另一个随机位置。
    • 打乱:选择一个子序列并随机打乱它。
    • 逆序:选择一个子序列并将其顺序反转。
  • 断言检查:我们使用断言确保突变后的路径依然满足所有的条件,特别是路径以城市0开始和结束,并且所有城市都被访问一次。

1. Scramble Operator(打乱操作)

目的:打乱路径中的某个子序列,但不改变子序列中各个城市的出现频率。

步骤

  1. 随机选择路径中的两个位置(ij),定义一个子序列。
  2. 将这个子序列的城市顺序随机打乱。
  3. 替换原路径中的该子序列。

2. Inversion Operator(逆序操作)

目的:将路径中的某个子序列反转,使其顺序颠倒。

步骤

  1. 随机选择路径中的两个位置(ij),定义一个子序列。
  2. 将这个子序列的顺序逆转。
  3. 替换原路径中的该子序列。
sorted(np.random.choice(range(1,len(child_mutated) - 1),size = 2,replace=False))

关于上述代码的解释,使用np.random.choice()函数,从child序列中选出子序列的开头和末尾,因为是获得list的索引,所以使用range(1,len(child_muted))能刚好覆盖除了出发点、结尾点(阿姆斯特丹)以外的其他城市使用sorted()保证取出的第一个数比第二个小,后续不需要做顺序调整,简化代码 

逆序补充

Python 的切片语法是 list[start:end:step],它允许我们从列表或其他序列类型中提取特定部分。各部分的含义如下:

  • start:切片开始的索引(包含)。
  • end:切片结束的索引(不包含)。
  • step:切片的步长,即每次从序列中取元素时的间隔。

[::-1] 的含义

  • startend 都为空时,表示从头到尾遍历整个序列。
  • step-1 表示以倒序的方式遍历序列,因此 [::-1] 表示从序列的最后一个元素到第一个元素,按相反的顺序返回。

swap交换补充

child_mutated[i],child_mutated[j] = child_mutated[j],child_mutated[i] # Swap two random cities

 该行代码之所以在Python中不会出错,是因为Python 中的多重赋值(multiple assignment)允许我们在同一行代码中同时更新多个变量的值。在这个过程中,右侧的表达式首先被完全计算,然后再一次性地将计算结果赋值给左侧的变量。左侧的两个值是在同一时刻交换的。右侧的值是在赋值之前就已经计算完成,因此互换的过程中,两个索引对应的值都已经被临时存储下来,不会因为某个赋值操作而改变另一个操作的结果。

*注意,该操作在JAVA中不可行,必须使用临时变量存储中间值

完成了变异算子后,我们可以继续定义交叉算子。同样,我们有多种选择可供选择:

  • 单点/多点交叉
  • 均匀交叉
  • 洗牌交叉
  • 部分映射交叉(PMX)
  • 边缘重组交叉

由于我们的基因型表示的是具有特定边界的排列,因此我们需要确保保留父代的特定属性(节点之间的连接),并确保边界条件仍然满足(两端为零,且每个数字1-9只出现一次)。

边缘重组交叉非常适合本例的问题,因为它能保留元素之间的相邻关系,并确保每个元素的数量相同。

1. 剥离零点

旅行商问题的路径通常是环形的(起点和终点为同一个城市),在本问题中,城市 0 代表阿姆斯特丹。为了处理中间的城市,我们需要剥离城市 0,即从两个父代中移除起始和终止的 0。同样在此处,我们运用到了Python的切片[1:-1]表示取列表索引1到最后一位之前的值(因为切片右侧不计入)完成对起点终点 0的剥离

# Strip the zeros (remove the starting and ending city which is 0)
parent_1_stripped = parent_1[1:-1]
parent_2_stripped = parent_2[1:-1]

2. 创建边缘表

边缘表是一个记录每个城市与之相邻城市的表,它将帮助我们决定如何继承父代中的路径结构。对于每个父代中的每个城市,我们记录其左邻和右邻的城市。

#create an edge table
edge_table = {key: set() for key in parent_1_stripped} #parent_1_stripped 是一个可以迭代的对象(例如列表、字符串等),它的每一个元素将成为 edge_table 字典的键。
#{key: set() for key in parent_1_stripped} 是一个字典推导式(dictionary comprehension),它的作用是:
#遍历 parent_1_stripped 中的每个 key。
#为每个 key 生成一个对应的空集合 set()。# Fill edge table from parent 1
for i in range(len(parent_1_stripped)): # 遍历整个列表city = parent_1_stripped[i] left_neighbor = parent_1_stripped[i-1]right_neighbor = parent_1_stripped[(i+1) % len(parent_1_stripped)] # 此处取余是防止右邻居的索引值超出边界,若超出则环形edge_table[city].update([left_neighbor, right_neighbor])# Fill edge table from parent 2
for i in range(len(parent_2_stripped)):city = parent_2_stripped[i]left_neighbor = parent_2_stripped[i-1]right_neighbor = parent_2_stripped[(i+1) % len(parent_2_stripped)]edge_table[city].update([left_neighbor, right_neighbor])

使用集合的 update() 方法时,它会将这个列表视为一个可迭代对象,并将其所有元素添加到集合中。例如我们update了一个[4,5]到 my_set={1,2,3} 中,那么集合将会被更新为 {1, 2, 3, 4, 5} 因为集合 my_set 会自动处理重复元素,所以即使列表中包含重复的元素,它们也不会在集合中重复出现 注意Update()的传入参数必须是可以迭代的对象如list等等,否则会报TypeError

3. 生成孩子

我们从一个随机城市开始,选择有最少剩余边缘的城市,直到生成完整的路径。如果没有相邻城市可选,则从未访问过的城市中随机选择一个。

    #Start with a random city:current = random.choice(parent_1_stripped) # 从列表中随机选择一个城市作为起点child = [current] # 子列表为仅有当前城市的列表#until we build the entire child:while len(child) < len(parent_1_stripped): # 重复操作直到子列表和原列表长度相同#remove the current city from the others' adjacency lists#WRITE YOUR CODE HERE!for cities in edge_table.values():cities.discard(current)#在交叉过程中,每次选择一个城市后,我们需要将这个城市从所有其他城市的邻接表中移除,#确保它不会被重复选择。edge_table 是一个记录每个城市邻接城市的表,#通过 discard() 方法,我们从每个城市的邻接表中删除当前城市 current。#if current city has neighbors, choose the one with the fewest connections left - this helps priroritizing a structure that preserves the parents' structures.#检查当前城市 current 是否还有剩余的邻接城市。如果 edge_table[current] 还有邻居,则继续执行。if  edge_table[current]:next_city = min(edge_table[current], key=lambda city: len(edge_table[city]))#if no neighbors left, choose a random unvisited city#WRITE YOUR CODE HERE!else:#如果当前城市没有剩余的邻接城市(即邻接表为空),则需要随机选择一个未访问的城市。other_city = list(set(parent_1_stripped) - set(child)) #从亲代减去已经访问过的城市列表来得到剩下的城市next_city = random.choice(other_city)#将选择的下一个城市 next_city 添加到孩子路径中child.append(next_city)#更新当前城市为刚刚选择的下一个城市,准备下一次循环。current = next_city

补充:

  1. 为什么使用Discard()而不是remove()?  discard() 是集合(set)的一个方法,用于从集合中移除指定的元素。如果使用 remove(),而被移除的城市不在集合中,会抛出 KeyError 异常。相比之下,discard() 如果发现当前城市不在集合中,它不会报错,这让代码更健壮,可以在不同情况下正常运行。
  2. 关于lambda函数  
    lambda arguments: expression
    lambda:关键字,用于定义一个匿名函数。
    arguments:函数的参数,可以有多个,用逗号分隔。
    expression:函数的返回值,是一个表达式,不能是语句
# 普通函数定义
def square(x):return x ** 2# 等价的 Lambda 函数
square_lambda = lambda x: x ** 2
# 使用 Lambda 函数
result = square_lambda(4)  # 16

 Lambda 函数经常用作其他函数(如 sorted()map()filter())的参数。例如,在 sorted() 函数中用 lambda 作为排序的键

在本例中,lambda 函数用于 min() 函数的 key 参数,以确定 edge_table 中每个城市的邻接城市数量。具体来说:

next_city = min(edge_table[current], key=lambda city: len(edge_table[city]))

传入的cityedge_table[current]中取,然后min() 函数使用这个 lambda 函数来找到邻接城市中拥有最少邻接城市的城市。

*min() 函数中使用 lambda 函数可以方便地按特定的标准(如邻接城市数量)来选择最小值,帮助实现复杂的逻辑操作。

4. 处理结果

最后,别忘了将 0 添加回路径的起点和终点,确保孩子个体符合旅行商问题的要求。

# Add zeros back (starting and ending city is Amsterdam)
child = [0] + child + [0]

5. 调用交叉操作

当你调用 crossover 时,它将生成两个新孩子。

def crossover(parent_1: list, parent_2: list, p_crossover:float) -> tuple:"""Performs the Edge Recombination crossover twice on the same pair of parents, returns a pair of children."""if np.random.uniform() > p_crossover:# Do not perform crossover uniform用于生成均匀分布的随机数 默认区间是[0,1) 当小于超参数p_crossover时 则不发生变异return parent_1, parent_2else:# Create two childrenchild_1 = create_offrping_edge_recombination(parent_1, parent_2)child_2 = create_offrping_edge_recombination(parent_1, parent_2)return child_1, child_2

 

在实现了初始化种群函数(initialize_population)、适应度函数(compute_distance)、突变操作(变异mutation())、父代选择函数(fittest_solution_TSP)和生存选择函数后。我们将使用锦标赛选拔,并实施代际生存机制,即所有孩子都取代父母。 

def tournament_selection(generation: list,compute_distance, adjacency_mat: np.ndarray, k: int) -> int:'''Implements the tournament selection algorithm.It draws randomly with replacement k individuals and returns the index of the fittest individual.''''''ToDo:Complete the building blocks below!'''current_winner = None # 初始化当前胜者winner_distance = float('inf')# 初始化胜者的距离为无穷大,表示我们要找最小的# 从代际中取样k个样本, 分为带放回抽样和不带放回抽样#chosen_individuals = np.random.choice(len(generation),size = k,replace = False) # Sampling without replacementchosen_individuals = np.random.choice(len(generation),size = k,replace = True) # Sampling with replacementfor individuals in chosen_individuals:current_distance = compute_distance(generation[individuals],adjacency_mat)if current_distance < winner_distance:winner_distance = current_distancecurrent_winner = individualsreturn current_winner
遗传算法锦标赛选择(Tournament Selection)中,使用带放回抽样的主要原因有以下几点:
以下内容来自chat

1. 增加随机性,防止过早收敛

遗传算法本质上是一种启发式搜索算法,通过引入随机性来探索解空间。如果使用不放回抽样,选择过程中每个个体只能被选中一次,可能会导致过多关注在部分个体上,减少种群的多样性。而带放回抽样允许同一个个体被多次选择,有助于保持种群的多样性,避免算法过早收敛到局部最优解。

2. 允许强个体有更高的被选择机会

在带放回的情况下,适应度较高的个体有可能被多次抽中,这使得它们有更高的概率参与多次锦标赛,并被选为父代个体。这样,适应度高的个体更容易被传递到下一代,从而提高算法的进化效率。

3. 减少个体选择不足的情况

如果种群规模较小,而你又不允许带放回抽样,可能会在锦标赛选择中耗尽不同的个体,导致没有足够的多样性去选择足够多的父代。带放回抽样可以确保每次都能从全体种群中抽取出个体,即使个体已经被选中过。

4. 简化实现和逻辑

带放回抽样可以保证每次选择中不会出现某些个体因为其他个体被选择而被排除在外,使得算法更加简洁且易于实现。通过允许相同个体多次参与锦标赛,可以避免在不放回的情况下考虑额外的约束条件。

举例说明:

假设种群中有一个适应度非常高的个体 A 和多个适应度较低的个体。如果不带放回抽样,A 只能参与一次锦标赛。但是,如果带放回抽样,A 有机会参与多个锦标赛,从而有更高的概率被选为父代。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/794284.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

宝可梦gba改版教程/口袋妖怪gba改版教程

背景 心血来潮玩了一些改版,感觉不太人性化,于是想要去稍微学学。 过程 劝退 可以说是非常劝退,gba改版非常的不人性化,比如汇编语言asm,改版工具不支持中文之类的。没有成体系的教程 这是口袋资源吧的教程汇总,虽然看上去很详细,但是其实还是蛮零散的,而且由于贴吧的一…

414周赛第三题 - 3282. 到达数组末尾的最大得分

题目链接 3282. 到达数组末尾的最大得分思路 转换为“矩阵面积”;贪心解决题解链接 【一图秒懂】贪心(Python/Java/C++/Go)关键点时间复杂度 \(O(n)\)空间复杂度 \(O(1)\)代码实现: class Solution:def findMaximumScore(self, nums: List[int]) -> int:answer = maxv =…

使用 Microsoft.Extensions.ServiceDiscovery 进行服务发现并调用

简介 在现代微服务架构中,服务发现(Service Discovery)是一项关键功能。它允许微服务动态地找到彼此,而无需依赖硬编码的地址。以前如果你搜 .NET Service Discovery,大概率会搜到一大堆 Eureka,Consul 等的文章。现在微软为我们带来了一个官方的包:Microsoft.Extension…

第19篇 Protocol Buffers 编译器生成proto文件

1.下载 Protocol Buffers 编译器(protoc) 前往 Protocol Buffers GitHub Releases 页面。在 "Assets" 下找到适合您系统的压缩文件,通常为 protoc-{version}-win32.zip 或 protoc-{version}-win64.zip,其中 {version} 是版本号。2.解压缩 Protoc 编译器 创建一个…

Zlibrary镜像站官方网址的使用教程

Zlibrary的使用说明: 手机端 步骤一、以苹果浏览器为例,打开Z-library镜像站,搜索自己的图书,注册登录之后选择EPUB格式下载步骤二、手机打开下载的文件,即可正常阅读。电脑端 步骤一,如下图所示, 打开Zlibrary官网搜索电子书,点击自己需要的图书步骤二,如下图所示, 在…

Python实现批量提取视频

前言 最近在玩整nas,但是之前把视频和照片都上传到immich了,因为我可以直接在手机中上传照片,但是因为手机内存不够就把视频全删了的,现在只需要把视频下载下来上传到nas中就OK。但是现在问题是immich这东西不支持批量删选视频,只能全选照片和视频,而且一共有50个G的照片…

边缘计算平台:为客户端提供更快、更可靠的应用响应!

边缘计算平台是一种基于云计算技术的辅助计算系统。它可以在数据源的边缘(例如传感器、IoT设备等)进行数据的处理和分析,从而为客户端提供更快、更可靠的应用响应。与传统的云计算模式相比,边缘计算平台更加灵活和实时,它通过在分布式的边缘设备上进行计算,可以大大减少由…

1-9Java数组

Java 数组 数组对于每一门编程语言来说都是重要的数据结构之一,当然不同语言对数组的实现及处理也不尽相同。 Java 语言中提供的数组是用来存储固定大小的同类型元素。 你可以声明一个数组变量,如 numbers[100] 来代替直接声明 100 个独立变量 number0,number1,....,numbe…

VR虚拟现实解决方案在电商行业中的7大优势

从电子商务的角度来看,虚拟现实可以通过多种方式为在线卖家带来好处。VR 可带来身临其境、富有创意且令人难忘的客户体验,可以极大地提高转化率。VR 可以通过更多方式使卖家受益。从电子商务的角度来看,虚拟现实可以通过多种方式为在线卖家带来好处。VR 可带来身临其境、富有…

Mathematica 入门

前言 Wolfram Mathematica(简称 MMA),是由 Wolfram Research 开发的科学计算软件。本文我们将介绍 Mathematica 的界面、语法和基本应用。类似的软件还有 MATLAB 和 Maple 等。 MMA 官网:https://www.wolfram.com/mathematica/ MMA 的安装及激活:Mathematica安装激活极简教…

【算法笔记】位运算详解

0. 前言 突然想到位运算是个好东西,就来水一波文章了…… 注意:我把能想到的有关位运算的所有内容都放进来了,所以篇幅较长,请谅解!若有写的不清楚或者不够详细的地方欢迎在评论区补充,谢谢支持! 本文中参考代码均使用C++编写。 废话不多说,下面步入正题。1. 基本运算 …

【算法笔记】【专题】RMQ 问题:ST表/树状数组/线段树

0. 前言 好久没更算法笔记专栏了,正好学了新算法来更新…… 这也是本专栏的第一个专题问题,涉及到三种数据结构,如果写得有问题请各位大佬多多指教,谢谢! 1. 关于 RMQ 问题 RMQ 的全称是 Range Minimum/Maximum Query,即区间最大/最小值问题。 本文中,我们的算法以求最大…