尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

多目标优化算法NSGA-II原理详解与Python实现

多目标优化算法NSGA-II原理详解与Python实现 1. 项目概述当优化不止一个目标时做项目、搞设计、写代码我们经常会遇到一个头疼的问题怎么才算“好”很多时候“好”不是一个单一的标准。比如设计一辆车我们希望它跑得快性能好又希望它油耗低经济性好还希望它制造成本低成本低。这几个目标往往是相互矛盾的——想让马儿跑又想马儿不吃草这事儿本身就难办。传统的优化方法比如梯度下降或者单目标遗传算法通常只能针对一个目标进行优化或者把多个目标通过加权求和的方式强行捏合成一个“综合指标”。但加权求和有个致命问题权重怎么定你说性能占60%还是成本占60这往往带有很强的主观性而且一旦权重定了最终只能得到一个“妥协”的解你无法知道在同样性能下是不是还有成本更低的方案。这就是多目标优化Multi-objective Optimization要解决的问题。而NSGA-IINon-dominated Sorting Genetic Algorithm II就是解决这类问题的“明星算法”由Kalyanmoy Deb等人在2002年提出至今仍在科研和工程领域被广泛应用。它不给你一个“最好”的解而是给你一组“最优”的解这组解被称为帕累托最优解集Pareto Optimal Set它们在目标空间中的投影就是帕累托前沿Pareto Front。简单来说在这组解里你找不到一个解能在所有目标上都比另一个解更好想要在一个目标上改进就必然要在其他至少一个目标上做出牺牲。这就像给你展示了一个“性能-成本-油耗”的菜单你可以根据自己当下的预算和偏好从中挑选最合适的那一款而不是被迫接受一个事先定好的“套餐”。NSGA-II的核心魅力在于其高效性。它通过“快速非支配排序”和“拥挤度比较”两个关键机制在维持种群多样性的同时快速逼近真实的帕累托前沿。对于需要权衡多个设计指标、调度资源、分配任务或者进行参数调优的场景掌握NSGA-II就等于拥有了一把强大的决策工具。无论你是做算法研究的同学还是面临实际多目标决策问题的工程师理解并实现NSGA-II都极具价值。2. NSGA-II核心思想与流程拆解要理解NSGA-II为什么高效我们需要先拆解它的两个核心操作快速非支配排序和拥挤度计算与比较。整个算法的流程可以看作是对传统遗传算法选择、交叉、变异的一次“智能化”升级使其能够处理多目标并保持解集的分布性。2.1 快速非支配排序给解划分“等级”在单目标优化中谁的目标函数值小或大谁就优秀排序很简单。但在多目标中一个解可能A目标好但B目标差另一个解可能B目标好但A目标差它们无法直接比较优劣。NSGA-II引入了“支配Domination”的概念来建立解之间的偏序关系。支配关系定义假设我们最小化所有目标。对于两个解A和B如果满足以下两个条件则称A支配B对于所有目标解A都不比解B差即 A的目标值 ≤ B的目标值。至少存在一个目标解A严格比解B好即 A的目标值 B的目标值。如果A和B互不支配比如A的第一个目标好B的第二个目标好那么它们就是非支配关系。快速非支配排序的过程就是给种群中的所有解划分等级Rank第一前沿Rank 1首先找出种群中所有不被任何其他解支配的解。这些解构成了帕累托最优前沿的“第一梯队”它们是最好的等级为1。后续前沿将这些第一前沿的解从种群中暂时移除。然后在剩下的解中再次找出所有不被剩余解支配的解它们构成第二前沿Rank 2。以此类推直到所有解都被赋予一个等级。这样我们就得到了一个分层结构Rank 1的解优于Rank 2Rank 2优于Rank 3以此类推。在选择操作时算法会优先选择等级高数字小的解。为什么叫“快速”早期的NSGA算法在计算支配关系时效率较低。NSGA-II的改进在于它通过一次遍历同时计算每个解的两个属性支配计数domination count和被支配解集dominated set。支配计数记录支配当前解的其他解的数量。如果为0说明当前解属于当前前沿。被支配解集记录被当前解支配的所有解的列表。算法通过这两个属性只需对种群进行有限次遍历就能一次性完成所有前沿的划分大大提高了效率尤其是在种群规模较大时。2.2 拥挤度比较在同一等级内“择优”如果仅仅按照非支配排序的等级来选择我们会倾向于选择所有Rank 1的解。但Rank 1的解可能有很多个而我们需要进入下一代的种群数量是固定的比如100个。当需要从同一等级例如Rank 1中选择一部分解时该如何取舍如果随机选可能导致选出的解在目标空间中都挤在一起失去了多样性最终得到的帕累托前沿可能只覆盖了很小一段区域无法反映问题的全貌。NSGA-II引入了拥挤度Crowding Distance来解决这个问题。拥挤度衡量的是一个解在其所在前沿中与其他解的“拥挤”程度。一个解离它的邻居越远它的拥挤度就越大。拥挤度计算步骤对于某个前沿上的所有解对每个目标函数将该前沿上的解按照该目标值的大小进行排序。对于排序后位于边界的两个解该目标值最大和最小的解将其拥挤度设置为无穷大或一个很大的数以确保边界解它们代表了该目标的极端情况能被保留下来。对于排序中间的第i个解其拥挤度等于其相邻两个解第i1个和第i-1个在该目标函数上的归一化差值之和。公式为distance[i] (obj[i1] - obj[i-1]) / (obj_max - obj_min)其中obj_max和obj_min是该前沿上该目标的最大值和最小值用于归一化。计算完所有目标后每个解都会得到一个拥挤度值。拥挤度越大说明该解周围越“空旷”对维持种群多样性越有价值。拥挤度比较算子当需要在两个解之间做出选择时NSGA-II遵循以下规则优先选择非支配等级更小更优的解。如果两个解等级相同则优先选择拥挤度更大的解。这个规则完美体现了NSGA-II的精髓首先朝着帕累托前沿的方向收敛靠非支配等级然后在同一前沿上尽可能均匀地散布开靠拥挤度。2.3 算法主流程全景结合以上两个核心机制NSGA-II的主循环可以概括为以下步骤初始化随机生成一个大小为N的父代种群P_t。评价计算种群中每个个体的所有目标函数值。主循环对于每一代t a.生成子代通过选择、交叉、变异算子从父代种群P_t生成一个同样大小为N的子代种群Q_t。 b.合并种群将父代P_t和子代Q_t合并形成一个大小为2N的混合种群R_t。 c.非支配排序对R_t进行快速非支配排序得到所有个体分属的前沿F1, F2, F3...。 d.构建新父代初始化空的新父代种群P_{t1}。按前沿等级从高到低F1, F2...依次将整个前沿加入P_{t1}直到加入某个前沿F_l时种群大小会超过N。 e.拥挤度排序与筛选对于这个临界前沿F_l计算其中所有个体的拥挤度。然后按拥挤度从大到小排序依次将个体加入P_{t1}直到P_{t1}的大小恰好为N。这样F_l中只有最不拥挤最具多样性的部分个体被保留。 f.进入下一代将P_{t1}作为新的父代回到步骤a直到满足终止条件如达到最大迭代次数。注意这里的关键在于步骤d和e。它既保留了精英父代和子代中的优秀个体通过合并得以保留又通过拥挤度机制保证了种群的多样性。这种(μλ)选择合并父代和子代进行选择是NSGA-II保持收敛性的重要原因。3. 关键模块的Python实现与细节剖析理论清晰了我们来看看如何用Python一步步实现它。这里我们不依赖现成的库如DEAP、pymoo而是自己动手实现核心部分这能让你对算法有更深刻的理解。我们将聚焦于最核心的三个函数fast_non_dominated_sort,crowding_distance_assignment, 和主循环中的选择机制。3.1 快速非支配排序的实现import numpy as np def fast_non_dominated_sort(population): 对种群进行快速非支配排序。 :param population: 种群列表每个个体是一个字典包含‘fitness’键其值为目标函数值列表最小化问题。 :return: 返回一个列表 fronts其中 fronts[i] 是第i层前沿Rank i1的个体索引列表。 num_individuals len(population) # 初始化数据结构 S [[] for _ in range(num_individuals)] # 被个体p支配的解集 n [0] * num_individuals # 支配个体p的解的数量 rank [0] * num_individuals # 个体的等级 fronts [[]] # 存储各层前沿fronts[0]为第一前沿 # 第一遍遍历计算支配关系 for i in range(num_individuals): S[i] [] n[i] 0 for j in range(num_individuals): if i j: continue if dominates(population[i], population[j]): S[i].append(j) # i支配j elif dominates(population[j], population[i]): n[i] 1 # j支配i if n[i] 0: # 没有个体支配ii属于第一前沿 rank[i] 0 fronts[0].append(i) # 分层构建后续前沿 current_front 0 while fronts[current_front]: # 当前前沿不为空 Q [] # 存储下一前沿的个体索引 for i in fronts[current_front]: for j in S[i]: # 对于被i支配的每个个体j n[j] - 1 if n[j] 0: # j不被任何剩余个体支配了 rank[j] current_front 1 Q.append(j) current_front 1 fronts.append(Q) fronts.pop() # 最后一个是空列表去掉 return fronts def dominates(a, b): 判断个体a是否支配个体b最小化问题。 :param a, b: 个体字典包含‘fitness’列表。 :return: True if a dominates b. a_fit a[fitness] b_fit b[fitness] # 条件1: a在所有目标上不差于b not_worse all(ai bi for ai, bi in zip(a_fit, b_fit)) # 条件2: a在至少一个目标上严格好于b strictly_better any(ai bi for ai, bi in zip(a_fit, b_fit)) return not_worse and strictly_better实现要点与避坑数据结构选择使用列表S存储支配关系比直接维护一个支配矩阵更节省内存尤其是在种群规模大时。效率支配判断函数dominates被频繁调用应确保其简洁高效。这里使用了Python的all和any函数配合生成器表达式在大多数情况下已经足够快。如果目标维度极高几十上百维可能需要考虑更高效的向量化比较。边界情况dominates函数严格遵循定义。注意如果两个解的所有目标值都相等它们互不支配。这在处理具有大量重复解或平坦区域的问题时很重要。3.2 拥挤度分配的实现def crowding_distance_assignment(front, population): 计算某个前沿中所有个体的拥挤度。 :param front: 一个前沿包含个体在population中的索引列表。 :param population: 种群。 :return: 返回一个与front等长的列表包含对应个体的拥挤度。 num_objs len(population[0][fitness]) distances [0.0] * len(front) if len(front) 0: return distances # 对每个目标函数分别处理 for obj_idx in range(num_objs): # 根据当前目标函数值对前沿个体排序 sorted_front sorted(front, keylambda i: population[i][fitness][obj_idx]) # 边界个体的距离设为无穷大 distances[front.index(sorted_front[0])] float(inf) distances[front.index(sorted_front[-1])] float(inf) # 获取该目标函数在前沿上的最大值和最小值用于归一化 obj_values [population[i][fitness][obj_idx] for i in sorted_front] obj_range obj_values[-1] - obj_values[0] if obj_range 0: # 防止除零如果所有值相等则忽略该目标对拥挤度的贡献 continue # 计算中间个体的拥挤度 for i in range(1, len(sorted_front) - 1): idx_current front.index(sorted_front[i]) idx_next front.index(sorted_front[i 1]) idx_prev front.index(sorted_front[i - 1]) distances[idx_current] (population[idx_next][fitness][obj_idx] - population[idx_prev][fitness][obj_idx]) / obj_range return distances实现要点与避坑归一化除以(obj_max - obj_min)是关键一步。如果不做归一化不同量纲或数量级的目标函数会对拥挤度计算产生支配性影响导致算法偏向于某个目标。例如目标一是成本单位万元范围0-100目标二是时间单位小时范围0-10如果不归一化时间差带来的拥挤度贡献微乎其微。边界解将边界解的拥挤度设为无穷大float(inf)确保了它们在拥挤度比较中永远胜出从而保证帕累托前沿的端点能被保留这对于描绘前沿的形状至关重要。处理相等值当某个目标在前沿上所有值都相等时obj_range 0该目标对拥挤度无贡献直接跳过避免除零错误。这在某些退化问题中可能出现。索引映射代码中front.index(...)的查找操作在front较大时是O(n)复杂度。一种优化方法是先构建一个从个体索引到其在前沿中位置的字典但鉴于前沿大小通常远小于总种群这里的开销通常可接受。3.3 选择、交叉、变异与主循环集成有了排序和拥挤度计算我们需要将它们嵌入到遗传算法的主框架中。这里给出关键的选择操作和主循环结构。def select_parents(population, fronts, crowding_distances, pop_size): 根据非支配排序和拥挤度比较从合并种群中选择新父代。 :param population: 合并后的种群 (大小 2N)。 :param fronts: 非支配排序得到的前沿列表。 :param crowding_distances: 每个个体的拥挤度列表。 :param pop_size: 需要的父代种群大小 N。 :return: 被选中的个体索引列表。 selected_indices [] current_front_idx 0 # 按前沿等级依次加入直到超过预定大小 while len(selected_indices) len(fronts[current_front_idx]) pop_size: selected_indices.extend(fronts[current_front_idx]) current_front_idx 1 # 处理最后一个前沿需要根据拥挤度筛选 last_front fronts[current_front_idx] if len(selected_indices) pop_size: # 计算该前沿的拥挤度如果之前没算过 # 这里假设crowding_distances已经包含了所有个体的距离 # 我们需要根据last_front中的个体取出其拥挤度并排序 front_with_distance [(i, crowding_distances[i]) for i in last_front] front_with_distance.sort(keylambda x: x[1], reverseTrue) # 按拥挤度降序排 # 选取拥挤度最大的前K个凑满pop_size k pop_size - len(selected_indices) selected_indices.extend([idx for idx, _ in front_with_distance[:k]]) return selected_indices # 主算法框架 def nsga_ii(problem, pop_size100, max_gens200, cx_prob0.8, mut_prob0.1): NSGA-II主函数。 :param problem: 问题定义需包含评价函数、变量边界、交叉变异操作等。 :param pop_size: 种群大小。 :param max_gens: 最大进化代数。 :param cx_prob: 交叉概率。 :param mut_prob: 变异概率。 :return: 最终种群以及各代的日志信息。 # 1. 初始化种群 population initialize_population(pop_size, problem) evaluate_population(population, problem) for gen in range(max_gens): # 2. 生成子代 offspring [] while len(offspring) pop_size: # 使用二元锦标赛选择父本 parent1 binary_tournament_selection(population, fronts, crowding_distances) parent2 binary_tournament_selection(population, fronts, crowding_distances) # 交叉 if np.random.rand() cx_prob: child1, child2 crossover(parent1, parent2, problem) else: child1, child1.copy() child2, child2.copy() # 变异 if np.random.rand() mut_prob: child1 mutate(child1, problem) if np.random.rand() mut_prob: child2 mutate(child2, problem) offspring.extend([child1, child2]) offspring offspring[:pop_size] # 确保子代数量正确 evaluate_population(offspring, problem) # 3. 合并种群 R P Q combined_pop population offspring # 4. 对合并种群进行非支配排序 fronts fast_non_dominated_sort(combined_pop) # 5. 计算拥挤度 (为每个前沿计算) crowding_distances [0] * len(combined_pop) for front in fronts: if not front: continue front_distances crowding_distance_assignment(front, combined_pop) for idx, dist in zip(front, front_distances): crowding_distances[idx] dist # 6. 选择新父代 selected_indices select_parents(combined_pop, fronts, crowding_distances, pop_size) population [combined_pop[i] for i in selected_indices] # (可选) 记录日志如前沿大小、平均拥挤度等 # log(gen, population, fronts[0]) # fronts[0]是第一前沿即近似帕累托前沿 return population二元锦标赛选择实现def binary_tournament_selection(population, fronts, crowding_distances): 二元锦标赛选择随机选两个个体按NSGA-II比较规则选优者。 注意这里需要全局的fronts和crowding_distances信息来确定个体的等级和拥挤度。 在实际实现中通常每个个体会存储其rank和crowding_distance属性这里为清晰起见通过参数传入。 idx1, idx2 np.random.choice(len(population), 2, replaceFalse) ind1, ind2 population[idx1], population[idx2] # 获取个体的rank和crowding distance (假设已存储在个体中或通过索引查询) # 这里假设个体是字典有‘rank’和‘crowding_distance’键 rank1, rank2 ind1[rank], ind2[rank] dist1, dist2 ind1[crowding_distance], ind2[crowding_distance] # NSGA-II比较规则 if rank1 rank2: # 优先等级高的 return ind1 elif rank1 rank2: return ind2 else: # 等级相同优先拥挤度大的 if dist1 dist2: return ind1 else: return ind24. 实战测试ZDT测试函数与结果分析理论实现完毕不跑个例子看看效果心里不踏实。我们选用多目标优化领域经典的ZDT1测试函数。它有两个目标需要最小化其帕累托前沿是凸的且已知非常适合验证算法。ZDT1问题定义变量数通常为30维。变量范围x_i ∈ [0, 1]。目标1f1(x) x1目标2f2(x) g(x) * h(f1, g)g(x) 1 9 * (Σ_{i2}^{n} x_i) / (n-1)h(f1, g) 1 - sqrt(f1 / g)帕累托最优解当 g(x) 1 时取得即 x_i 0 for i2,...,n。此时帕累托前沿由 f2 1 - sqrt(f1) 描述其中 f1 ∈ [0, 1]。编码与算子选择编码采用实数编码每个个体是一个长度为30的列表。交叉采用模拟二进制交叉SBX这是实数编码GA中常用的算子能产生靠近父代的子代。变异采用多项式变异。参数种群大小100进化代数250SBX分布指数η_c20多项式变异分布指数η_m20。核心算子实现示例def sbx_crossover(parent1, parent2, eta_c20): 模拟二进制交叉 (SBX) child1, child2 parent1.copy(), parent2.copy() for i in range(len(parent1)): if np.random.rand() 0.5: if abs(parent1[i] - parent2[i]) 1e-14: x1 min(parent1[i], parent2[i]) x2 max(parent1[i], parent2[i]) rand np.random.rand() beta 1.0 (2.0 * (x1 - 0.0) / (x2 - x1)) if rand 0.5 else 1.0 (2.0 * (1.0 - x2) / (x2 - x1)) alpha 2.0 - beta ** -(eta_c 1.0) rand np.random.rand() if rand 1.0 / alpha: beta_q (rand * alpha) ** (1.0 / (eta_c 1.0)) else: beta_q (1.0 / (2.0 - rand * alpha)) ** (1.0 / (eta_c 1.0)) c1 0.5 * ((x1 x2) - beta_q * (x2 - x1)) c2 0.5 * ((x1 x2) beta_q * (x2 - x1)) c1 min(max(c1, 0.0), 1.0) # 边界修复 c2 min(max(c2, 0.0), 1.0) if np.random.rand() 0.5: child1[i], child2[i] c1, c2 else: child1[i], child2[i] c2, c1 return child1, child2 def polynomial_mutation(individual, eta_m20): 多项式变异 mutated individual.copy() for i in range(len(individual)): if np.random.rand() 1.0/len(individual): # 变异概率通常设为1/n u np.random.rand() if u 0.5: delta (2*u) ** (1.0/(eta_m1)) - 1 else: delta 1 - (2*(1-u)) ** (1.0/(eta_m1)) mutated[i] delta mutated[i] min(max(mutated[i], 0.0), 1.0) # 边界修复 return mutated运行与可视化 运行NSGA-II算法后我们取出最后一代种群中的第一前沿非支配解并将其与真实的帕累托前沿进行比较。import matplotlib.pyplot as plt # 假设 final_population 是运行nsga_ii返回的最终种群 final_front get_first_front(final_population) # 需要实现一个函数从排序后的种群中提取第一前沿 f1_values [ind[fitness][0] for ind in final_front] f2_values [ind[fitness][1] for ind in final_front] # 生成真实的帕累托前沿 true_f1 np.linspace(0, 1, 100) true_f2 1 - np.sqrt(true_f1) plt.figure(figsize(10, 6)) plt.scatter(f1_values, f2_values, cred, s30, labelNSGA-II Approximated Front, alpha0.7, edgecolorsk) plt.plot(true_f1, true_f2, b--, linewidth2, labelTrue Pareto Front (ZDT1)) plt.xlabel(Objective 1 (f1), fontsize12) plt.ylabel(Objective 2 (f2), fontsize12) plt.title(NSGA-II on ZDT1 Test Problem, fontsize14) plt.legend() plt.grid(True, alpha0.3) plt.show()结果分析 一个成功的运行结果应该显示红色的散点算法得到的近似前沿紧密地分布在蓝色的虚线真实前沿周围并且从f10到f11的范围内都有分布。你可以观察收敛性点是否足够靠近真实前沿可以通过计算世代距离Generational Distance, GD等指标量化。分布性点是否均匀分布是否有大的空隙或聚集可以通过计算间距Spacing或反转世代距离Inverted Generational Distance, IGD来评估。广泛性点是否覆盖了真实前沿的整个范围从f10到f11实操心得初次运行可能效果不理想比如点聚集在前沿的某一段。这通常与算法参数有关。种群大小pop_size是影响分布性的关键参数太小的种群难以维持多样性。交叉和变异的分布指数η_c, η_m影响子代与父代的相似程度η值越大子代越靠近父代搜索更精细但探索能力下降η值小则扰动大探索能力强但可能破坏好模式。对于ZDT1η20是个不错的起点。如果发现收敛慢可以尝试略微降低η_c如15以增加探索如果发现分布性差可以尝试增大变异概率或使用自适应算子。5. 参数调优、常见问题与进阶技巧NSGA-II虽然强大但也不是“开箱即用”就能在所有问题上都表现完美。实际应用中你需要根据具体问题调整参数和处理一些典型问题。5.1 关键参数影响与调优指南参数典型范围影响调优建议种群大小 (N)50 - 500收敛性与多样性N越大探索能力越强找到的帕累托前沿越完整、分布越均匀但每代计算开销也越大。从100开始。如果问题复杂变量多、前沿不规则可增至200-300。这是最值得投资的参数。进化代数 (G)100 - 1000收敛深度代数越多算法有更多时间逼近最优前沿。结合终止条件如前沿变化小于阈值使用。对于测试250-500代通常可观察收敛趋势。交叉概率 (P_c)0.6 - 0.9搜索效率控制利用现有基因产生新个体的频率。太高可能导致模式破坏太低则搜索缓慢。默认0.8或0.9对于多数问题表现良好。对于复杂问题可尝试0.7-0.85。变异概率 (P_m)0.01 - 0.1探索能力与多样性引入新基因避免早熟收敛。通常设置较小。常用 1/n (n为变量数) 或 0.01~0.05。若发现早熟收敛可适当提高。SBX分布指数 (η_c)5 - 30子代分布η_c越大子代越靠近父代搜索更精细越小子代越远离父代探索更强。凸前沿问题如ZDT1可用较大值20-30。对于复杂、多模态前沿可用较小值5-15。多项式变异指数 (η_m)10 - 50变异步长η_m越大变异扰动越小越小扰动越大。通常设20-50以获得较小但有效的扰动。调优流程建议固定其他调N先设置一个中等代数如200调整种群大小N观察前沿的分布性是否改善。固定N调G在N足够大的基础上增加代数G观察前沿是否向真实前沿进一步收敛。微调算子参数如果收敛速度慢尝试减小η_c如果多样性不足尝试增大P_m或减小η_m。使用自适应参数高级用法中可以让η_c、η_m或P_m随着进化代数动态变化如逐渐减小变异概率以加强后期收敛。5.2 常见问题与排查技巧问题1算法收敛过早种群多样性迅速丧失所有个体聚集到一点。可能原因种群大小N太小。变异概率P_m太低或变异算子强度太弱。选择压力过大虽然NSGA-II的锦标赛选择压力适中但如果交叉算子总是产生相似子代也会导致此问题。排查与解决首要检查增大N这是最直接有效的方法。检查变异算子确保变异确实能产生有意义的扰动。对于实数编码可以打印出变异前后的基因值看看变化是否足够。尝试增加P_m或者使用更强的变异算子如将多项式变异的η_m调小。监控种群中个体之间的平均距离或拥挤度的变化如果在早期就急剧下降就是早熟迹象。问题2得到的帕累托前沿分布不均匀在某些区域过于密集在某些区域稀疏甚至没有解。可能原因拥挤度计算在目标空间分布不均时效果打折扣。问题的帕累托前沿本身是非均匀的如ZDT3有离散区域。交叉和变异算子未能有效探索稀疏区域。排查与解决验证拥挤度可视化最终前沿观察拥挤度大的点是否确实在稀疏区域。可以尝试输出前沿个体的拥挤度值进行验证。使用其他多样性保持机制如果问题前沿已知不均匀可以考虑参考NSGA-III中的参考点方法或者使用ε-支配、聚类等方法辅助选择。调整SBX的η_c减小η_c可以使子代分布更广有助于探索稀疏区域。问题3算法运行速度慢特别是当目标函数计算耗时很长时。可能原因目标函数本身是计算密集型如调用仿真软件。种群规模N或进化代数G设置过大。非支配排序和拥挤度计算在种群很大时成为瓶颈尽管是“快速”的但O(MN²)复杂度对于大规模种群依然可观M为目标数。排查与解决减少评估次数这是最关键的。在保证性能的前提下尽量减小N和G。可以使用自适应终止条件。并行化评估个体间的适应度评估通常是独立的可以并行计算。这是最容易实现的加速手段。算法层面优化对于极大种群如1000可以研究更高效的非支配排序算法如基于登台排序ENS或使用快速排序思想改进的版本。但在大多数应用中NSGA-II的原始实现已足够高效。问题4处理三个及以上目标高维目标空间时效果变差。根本原因随着目标数增加非支配解的比例急剧上升。在三维目标空间中可能超过80%的解都是非支配的导致选择压力几乎完全依赖于拥挤度而拥挤度在高维空间很难有效衡量“分布性”。解决方案转向专门算法这是NSGA-II的固有局限。对于3个以上目标的问题应优先考虑NSGA-III。NSGA-III使用一组预设的参考点来引导种群分布能更好地处理高维目标空间。降维如果可能通过分析目标间的相关性尝试合并或剔除一些次要目标。调整拥挤度度量尝试使用基于网格的拥挤度或角度拥挤度等变体但效果通常不如直接换用NSGA-III。5.3 进阶技巧与扩展约束处理现实问题大多带约束。NSGA-II处理约束的常用方法是约束支配。修改支配关系判断首先比较约束违反程度违反约束少的解占优如果违反程度相同或都为0再按原来的目标函数支配关系比较。同时在拥挤度计算中也可以将约束违反度作为一个额外的“目标”来考虑以推动种群向可行域移动。动态环境与多任务如果优化问题本身随时间变化可以考虑将NSGA-II与预测或记忆机制结合。对于多任务优化即同时优化多个相关任务可以参考MFEA多因子进化算法的思想让种群在不同任务间共享知识。混合模型将NSGA-II作为全局搜索器与局部搜索方法如梯度下降、模拟退火结合。例如在每一代结束后对第一前沿中的部分解进行局部精细化搜索以加速收敛并得到更精确的前沿。性能指标学会使用标准指标定量评估算法结果而不仅仅是看图。世代距离 (GD)衡量近似前沿到真实前沿的平均距离值越小收敛越好。反转世代距离 (IGD)衡量真实前沿到近似前沿的平均距离能同时评价收敛性和分布性值越小综合性能越好。间距 (Spacing)衡量近似前沿中解分布的均匀程度。超体积 (HV)衡量近似前沿所覆盖的目标空间体积是综合考虑收敛性和分布性的综合指标值越大越好。这是目前最推荐的指标。实现NSGA-II只是第一步真正发挥其威力在于你如何将它与你特定的问题相结合并针对问题的特性进行细致的调优和改造。从ZDT1这样的标准测试函数起步理解每个模块的作用再逐步应用到你的实际模型中这个过程本身就是一个极佳的学习和深化理解的过程。当你看到算法为你生成的那一组权衡解时那种“鱼与熊掌可以兼得至少知道如何权衡”的掌控感正是多目标优化带来的核心价值。
返回列表