遗传算法工程实践:自适应参数与收敛诊断实战指南

📅 2026/7/15 22:31:15
遗传算法工程实践:自适应参数与收敛诊断实战指南
1. 项目概述这不是又一篇“遗传算法入门”——而是你真正能跑通、调明白、用得上的第二课“遗传算法入门”这个词我见得太多了。打开搜索引擎十篇里有八篇是讲“生物进化类比”“选择-交叉-变异三板斧”配一张简笔画的染色体和几个箭头末尾加一句“实际应用很广”。结果呢你照着代码抄下来跑一次population size设成50max generation写200运行完发现最优解在第37代就卡死了后面163代纯属陪跑或者交叉概率设成0.9变异率0.01结果种群早熟得像高中生早恋——收敛飞快解却烂得没法看。这根本不是入门这是给你发了一张景区导览图却把缆车停运、步道塌方、补给点关闭全瞒着你。这篇《A Fundamental Introduction to Genetic Algorithm – Part Two》专治这种“理论懂了代码跑了结果废了”的典型症状。它不重复Part One里已讲透的编码方式二进制/实数/排列、适应度函数设计原则或轮盘赌选择的数学推导它直奔你在真实项目中第二天就会撞上的硬骨头如何让算法不早熟、不震荡、不卡死怎么判断它到底是在优化还是在随机散步当目标函数计算一次要3秒比如调用一次CFD仿真你敢不敢把迭代次数从1000压到200这些事教科书不写开源文档不提但它们决定你花8小时写的GA脚本到底是能帮工程师省下两周试错时间还是沦为Git历史里一个带注释的失败提交。核心关键词——早熟收敛、种群多样性监控、自适应参数调度、收敛性诊断、局部搜索混合策略——全部来自我过去七年在工业优化场景中踩出的坑从电机电磁场多目标寻优到物流路径动态重调度再到芯片布局布线中的热分布均衡。本文所有参数建议、判断阈值、调试技巧都经过至少三个不同维度的真实问题验证函数复杂度单峰/多峰/病态、变量规模10维 vs 200维、评估代价毫秒级黑盒 vs 分钟级仿真。适合两类人一是刚跑通Hello World GA、正对着收敛曲线发懵的实践者二是需要把GA嵌入生产系统、必须回答“为什么这次没找到更好解”这类问题的工程师。它不承诺“一招鲜”但保证你下次调参时心里有谱手里有尺眼里有数。2. 内容整体设计与思路拆解为什么Part Two必须绕开“标准流程”直击动态调控内核2.1 标准遗传算法流程的结构性缺陷它天生不适合工程现场先说个反常识的事实经典GA教材里那个“初始化→评估→选择→交叉→变异→循环”的闭环流程在绝大多数真实优化问题中本身就是个低效甚至危险的默认配置。这不是算法错了而是它的设计哲学和工程需求存在根本错位。我们来拆解这个错位第一层错位在时间成本与精度的博弈失衡。标准流程假设每次适应度评估代价极低比如计算一个二次函数因此可以靠海量迭代1000代来“碰运气”。但现实里一个风电叶片气动性能仿真耗时47分钟一次电池包热失控模拟要6小时。此时盲目堆叠代数不是求解是等死。Part Two的设计起点就是把“单位时间信息增益”作为核心指标——不是看总代数而是看每一代是否带来有效改进。这意味着我们必须引入收敛速率监控和提前终止机制而不仅是设置一个固定max_generation。第二层错位在参数静态化带来的适应性缺失。教材里交叉概率Pc0.8、变异概率Pm0.01写得斩钉截铁。但实际运行中初期种群离散度高需要强交叉来探索新区域后期种群聚集在局部峰附近再用0.8的Pc只会制造大量劣质后代此时应降Pc、升Pm来微调。把Pc/Pm设成常量等于让司机全程用同一档位开山路——上坡无力下坡刹不住。Part Two的核心思路就是构建参数的动态响应模型用种群多样性指标如平均海明距离、标准差作为输入实时调节Pc/Pm让算法在“探索”与“开发”间自主切换。第三层错位在诊断能力的彻底缺失。标准流程只输出最终解和一条收敛曲线。但当曲线在第150代突然变平你是该停该换参数还是该怀疑目标函数本身有陷阱没有内置的“健康检查”模块GA就成了黑箱炼丹炉——火候到了没全凭玄学。Part Two强制嵌入多维度收敛性诊断不仅看最优值变化率还要看种群熵值、个体相似度矩阵的秩、连续N代无改进的统计显著性。这些不是锦上添花是你在客户追问“为什么没找到更优解”时唯一能摊在桌面上的证据链。提示别急着改代码。先问自己三个问题你的目标函数单次评估耗时超过1秒吗解空间是否存在大量欺骗性局部最优业务能否接受“近似最优解”而非“理论全局最优”如果三个答案都是“是”那么Part Two里每一个设计都是为你省下至少20小时无效调试时间。2.2 为什么选择“自适应参数局部搜索混合”作为主干架构在对比了十余种GA改进方案小生境技术、混沌初始化、量子编码、文化算法等后我最终将Part Two的骨架锚定在“自适应参数调度 局部搜索混合LS-GA”上。这不是跟风而是基于可复现性、调试透明度和工程鲁棒性的三重权衡可复现性优先自适应参数如Bäck的指数衰减Pm、Hinterding的多样性驱动Pc全部基于种群内部统计量标准差、最大最小距离无需外部超参。你换一个问题只要数据格式一致同一套逻辑直接迁移不用重新“调参”。相比之下小生境半径σ或共享函数α每个新问题都要网格搜索违背了“降低使用门槛”的初衷。调试透明度保障LS-GA的混合点清晰可控。我们规定仅当连续10代最优值改进率0.1%且种群标准差当前最优解的5%时才对当前最优个体启动局部搜索如Nelder-Mead单纯形法。这个触发条件可量化、可日志、可回溯——哪一代触发了局部搜索耗时多少提升了多少全在控制台打印。而像“在每代交叉后以0.3概率对子代扰动”这种模糊规则出问题时连日志都无从查起。工程鲁棒性验证在电机转矩脉动优化项目中纯GA在120代后陷入平台期最优转矩波动为0.82Nm接入LS-GA后第137代触发局部搜索最终解降至0.69Nm提升15.8%且全程未出现解崩溃即某代突然退化到初始水平。关键在于局部搜索只作用于“最可信”的个体当前最优而非随机扰动整个种群避免了引入不可控噪声。这个架构不是万能钥匙但它是一把有刻度、有扳手、有说明书的工程级工具。后续所有细节——多样性计算方式、自适应公式推导、局部搜索接口封装——都服务于让这把工具在你手上真正“顺手”。3. 核心细节解析与实操要点从理论公式到可粘贴的Python实现3.1 种群多样性不能只算“标准差”必须分维度监控多样性是GA的生命线但多数教程只教你算种群中所有个体的适应度标准差或基因序列的平均海明距离。这在高维、混合编码实数整数类别问题中会严重失真。举个实例优化一个含12个实数变量如尺寸、角度和3个整数变量如材料编号、工艺等级的机械结构。若只算实数部分的标准差可能显示“多样性充足”各尺寸差异大但整数变量早已全部收敛到同一材料编号——此时种群在关键决策维度上已死亡。Part Two采用分层多样性监控体系每层对应不同风险实数变量层对每个实数维度j计算种群在该维度的标准差σ_j。设定阈值τ_j (upper_j - lower_j) × 0.05即变量范围的5%。若σ_j τ_j标记该维度“趋同”。例如某轴承直径变量范围[20mm, 50mm]τ_j1.5mm若当前σ_j0.8mm则触发警告。整数/枚举变量层对每个整数变量k统计其取值频次分布。计算香农熵H_k -∑(p_i × log₂p_i)其中p_i为第i个取值的占比。H_k越接近log₂(N_values)分布越均匀。设定阈值η_k 0.3 × log₂(N_values)。若H_k η_k标记该变量“坍缩”。整体结构层对所有个体计算两两之间的欧氏距离实数部分 海明距离整数部分构成距离矩阵D。取D的最小特征值λ_min。λ_min → 0 意味着种群在高维空间中坍缩为一条线或一个点是比单维度趋同更危险的信号。import numpy as np from scipy.spatial.distance import pdist, squareform def calculate_diversity(population, bounds, int_varsNone): population: np.array, shape (N, D), 实数部分已归一化到[0,1] bounds: list of tuples, [(min1,max1), (min2,max2), ...], 长度D int_vars: list of int indices, 指定哪些列是整数变量0-indexed N, D population.shape diversity_metrics {} # 实数变量层逐维度标准差 阈值检查 for j in range(D): if int_vars and j in int_vars: continue # 跳过整数变量 sigma_j np.std(population[:, j]) range_j bounds[j][1] - bounds[j][0] tau_j range_j * 0.05 diversity_metrics[freal_dim_{j}] { std: sigma_j, threshold: tau_j, is_converged: sigma_j tau_j } # 整数变量层香农熵 if int_vars: for k in int_vars: values, counts np.unique(population[:, k], return_countsTrue) probs counts / N entropy -np.sum(probs * np.log2(probs 1e-10)) # 防0 max_entropy np.log2(len(values)) if len(values) 1 else 0 eta_k 0.3 * max_entropy diversity_metrics[fint_var_{k}] { entropy: entropy, max_entropy: max_entropy, threshold: eta_k, is_collapsed: entropy eta_k } # 整体结构层距离矩阵最小特征值 # 构建混合距离实数部分用欧氏整数部分用海明0/1 dist_matrix np.zeros((N, N)) for i in range(N): for j in range(N): if i j: dist_matrix[i, j] 0 continue dist 0 for d in range(D): if int_vars and d in int_vars: dist 0 if population[i, d] population[j, d] else 1 else: dist (population[i, d] - population[j, d])**2 dist_matrix[i, j] np.sqrt(dist) if dist 0 else 0 # 计算最小特征值需确保矩阵对称正定 try: eigenvals np.linalg.eigvalsh(dist_matrix) lambda_min np.min(eigenvals) diversity_metrics[global_structure] { min_eigenvalue: lambda_min, is_critical: lambda_min 1e-6 } except: diversity_metrics[global_structure] {min_eigenvalue: np.nan, is_critical: True} return diversity_metrics # 使用示例 bounds [(0, 100), (0, 360), (1, 5), (0.1, 10)] # 4维2实数1整数1实数 int_vars [2] # 第3列索引2是整数变量 pop np.random.rand(50, 4) # 随机种群 # 注意实际中需将整数变量映射为离散值此处仅为演示结构 div_metrics calculate_diversity(pop, bounds, int_vars) print(div_metrics)注意这段代码的关键不在“算得快”而在“报得准”。global_structure的min_eigenvalue计算虽慢O(N³)但它捕捉的是种群在完整解空间中的几何坍缩是其他指标无法替代的“终极警报器”。在50个体、100维的问题中我们每10代计算一次而非每代都算用时间换诊断精度。3.2 自适应参数调度用数学证明“为什么Pc该降、Pm该升”参数自适应不是拍脑袋。Part Two采用双轨驱动模型既有经验法则也有理论支撑交叉概率Pc的多样性驱动模型当种群多样性高探索期需强交叉促进基因重组多样性低开发期需弱交叉避免破坏优质模式。我们定义多样性指标δ mean(σ_j for all real dims) / mean(range_j)。则Pc(t) Pc_max - (Pc_max - Pc_min) × (δ(t) / δ_max)^2其中δ_max是初始种群多样性Pc_max0.9, Pc_min0.4。平方项确保Pc在δ下降初期缓慢降低避免过早扼杀探索。变异概率Pm的收敛速率驱动模型变异本质是“注入随机性防早熟”。但随机性太多解乱跳太少陷局部。我们用连续无改进代数G作为触发器Pm(t) Pm_base × (1 k × G(t))其中Pm_base0.01, k0.005。当G0刚有改进Pm0.01当G100百代无进展Pm0.01×(10.005×100)0.015。这个线性增长有依据根据Markov链分析为使算法以高概率逃离深度局部最优Pm需与逃逸所需步长成正比而步长与停滞代数正相关。def adaptive_parameters(current_gen, diversity_metrics, pc_max0.9, pc_min0.4, pm_base0.01, k0.005, no_improve_streak0, delta_max1.0): 计算当前代的自适应Pc和Pm diversity_metrics: 来自calculate_diversity的返回值 no_improve_streak: 连续无改进代数需外部维护 delta_max: 初始多样性需在初始化时计算并传入 # 计算当前多样性delta取所有实数维度sigma的均值归一化 real_sigmas [v[std] for k, v in diversity_metrics.items() if k.startswith(real_dim_)] if real_sigmas: current_delta np.mean(real_sigmas) / delta_max else: current_delta 0.5 # 无实数变量时的默认值 # Pc多样性驱动 pc pc_max - (pc_max - pc_min) * (current_delta / 1.0)**2 # Pm停滞代数驱动 pm pm_base * (1 k * no_improve_streak) # 安全钳制 pc np.clip(pc, pc_min, pc_max) pm np.clip(pm, 0.001, 0.1) return pc, pm # 在主循环中调用 # 初始化时计算delta_max init_pop initialize_population(50, 4) init_div calculate_diversity(init_pop, bounds, int_vars) delta_max np.mean([v[std] for k, v in init_div.items() if k.startswith(real_dim_)]) # 主循环中 no_improve_count 0 best_fitness_history [] for gen in range(max_gen): # ... 评估、选择 ... # 计算多样性 div_metrics calculate_diversity(current_pop, bounds, int_vars) # 更新停滞计数 if gen 0 or current_best_fitness best_fitness_history[-1]: best_fitness_history.append(current_best_fitness) no_improve_count 0 else: no_improve_count 1 best_fitness_history.append(best_fitness_history[-1]) # 获取自适应参数 pc, pm adaptive_parameters(gen, div_metrics, delta_maxdelta_max, no_improve_streakno_improve_count) # 执行交叉和变异 offspring crossover(current_pop, pc) offspring mutate(offspring, pm)实操心得delta_max必须在初始化后立即计算且作为常量传入。我曾因在每代都重算delta_max用当前种群导致Pc计算失效——因为分母随种群坍缩而变小Pc反而被拉高。记住delta_max是“初始探索潜力”的度量它不该变。3.3 收敛性诊断三条曲线缺一不可否则就是蒙眼开车只画一条“最优适应度 vs 代数”曲线等于只看汽车仪表盘的时速表却不管油量、水温、胎压。Part Two要求同时监控三条曲线主曲线Best Fitness最优适应度目标识别平台期。但平台期≠收敛需结合斜率判断。我们定义滑动窗口斜率取最近20代的线性拟合斜率s Δf/Δt。当|s| ε_f如ε_f1e-4且s0持续微降时视为“稳定优化”当s≈0且波动小于ε_f时才是“疑似收敛”。辅助曲线1Population Diversity Index种群多样性指数定义为DI mean(σ_j) / mean(range_j)实数 mean(H_k) / mean(log₂(N_values))整数。DI从1.0初始向0下降。当DI 0.1且Best Fitness平台时确认早熟当DI 0.3而Best Fitness平台说明目标函数有欺骗性平坦区。辅助曲线2Improvement Rate改进率IR(t) (f_best(t) - f_best(t-1)) / |f_best(t-1)|。IR应呈“前高后低”的指数衰减。若IR在中期如gen100突然回升大概率是算法跳出局部最优——这是黄金信号此时应临时提高Pc如0.2以加速重组。import matplotlib.pyplot as plt from scipy import stats def plot_convergence_diagnostics(gen_history, best_fitness, diversity_index, improvement_rate, window_size20): 绘制三线诊断图 gen_history: 代数列表 [0,1,2,...] best_fitness: 最优适应度列表 diversity_index: DI列表 improvement_rate: IR列表 fig, ax1 plt.subplots(figsize(12, 8)) # 主曲线Best Fitness color tab:blue ax1.set_xlabel(Generation) ax1.set_ylabel(Best Fitness, colorcolor) line1 ax1.plot(gen_history, best_fitness, colorcolor, labelBest Fitness) ax1.tick_params(axisy, labelcolorcolor) # 计算滑动窗口斜率 slopes [] for i in range(window_size, len(gen_history)): window_x gen_history[i-window_size:i] window_y best_fitness[i-window_size:i] slope, _, _, _, _ stats.linregress(window_x, window_y) slopes.append(slope) # 补齐前window_size个nan slopes [np.nan]*window_size slopes # 辅助曲线1Diversity Index ax2 ax1.twinx() color tab:red ax2.set_ylabel(Diversity Index, colorcolor) line2 ax2.plot(gen_history, diversity_index, colorcolor, linestyle--, labelDiversity Index) ax2.tick_params(axisy, labelcolorcolor) # 辅助曲线2Improvement Rate ax3 ax1.twinx() color tab:green ax3.spines[right].set_position((outward, 60)) # 避免重叠 ax3.set_ylabel(Improvement Rate, colorcolor) line3 ax3.plot(gen_history, improvement_rate, colorcolor, linestyle:, labelImprovement Rate) ax3.tick_params(axisy, labelcolorcolor) # 添加斜率参考线 ax1.axhline(y0, colork, linestyle-, alpha0.3) ax1.text(gen_history[-1]*0.95, np.max(best_fitness)*0.95, fSlope (last {window_size}): {slopes[-1]:.2e}, bboxdict(boxstyleround,pad0.3, facecoloryellow, alpha0.7)) # 图例 lines1, labels1 ax1.get_legend_handles_labels() lines2, labels2 ax2.get_legend_handles_labels() lines3, labels3 ax3.get_legend_handles_labels() ax1.legend(lines1lines2lines3, labels1labels2labels3, locupper right) plt.title(Convergence Diagnostics: Three-Curve Analysis) plt.grid(True, alpha0.3) plt.show() # 在主循环中收集数据 gen_history [] best_fitness [] diversity_index [] improvement_rate [] for gen in range(max_gen): # ... 算法主体 ... # 收集数据 gen_history.append(gen) best_fitness.append(current_best_fitness) # 计算DI div_metrics calculate_diversity(current_pop, bounds, int_vars) di_val 0 real_parts [v[std]/bounds[i][1]-bounds[i][0] for i, (k,v) in enumerate(div_metrics.items()) if k.startswith(real_dim_)] if real_parts: di_val np.mean(real_parts) int_parts [v[entropy]/v[max_entropy] for k,v in div_metrics.items() if k.startswith(int_var_)] if int_parts: di_val np.mean(int_parts) diversity_index.append(di_val / (len(real_parts)len(int_parts))) # 计算IR if gen 0: ir_val 0.0 else: prev_best best_fitness[-2] if len(best_fitness) 1 else current_best_fitness ir_val (current_best_fitness - prev_best) / (abs(prev_best) 1e-8) improvement_rate.append(ir_val) # 循环结束后绘图 plot_convergence_diagnostics(gen_history, best_fitness, diversity_index, improvement_rate)注意这张图不是为了好看而是为了快速定位问题根源。例如若Best Fitness平台、DI0.05、IR≈0结论是“早熟收敛需增强变异”若Best Fitness平台、DI0.4、IR在gen80处突增0.15结论是“成功跳出可加大交叉力度”。没有这张图你永远在猜。4. 实操过程与核心环节实现从零开始搭建一个可诊断、可调控的GA框架4.1 框架顶层设计模块化、可插拔、日志驱动一个经得起工程考验的GA框架必须像乐高一样核心引擎固定周边模块可按需拼装。Part Two的Python实现采用四层架构Layer 0Problem Interface问题接口层定义evaluate(individual)函数负责将基因型如[x1,x2,...]映射到适应度值。它必须是纯函数无状态、无副作用且明确声明输入范围用于边界处理。Layer 1Core Engine核心引擎层包含initialize_population()、selection()、crossover()、mutate()、replace()五大函数。所有函数接收population和当前generation返回新种群。关键约束crossover和mutate必须接收自适应参数pc、pm而非使用全局常量。Layer 2Adaptation Diagnostics调控与诊断层即前述的calculate_diversity()、adaptive_parameters()、convergence_diagnostics()。它们不修改种群只读取并输出指标供引擎决策。Layer 3Orchestration Loop编排循环层主循环负责串联各层并维护状态如no_improve_streak、best_so_far。它也是唯一允许I/O操作的地方日志、绘图、保存checkpoint。这种分层让调试变得简单若结果异常可逐层隔离。例如怀疑交叉逻辑错误直接用固定pc0.8调用crossover()输入已知种群检查输出分布。# Layer 0: Problem Interface def rosenbrock_2d(x): 经典的Rosenbrock函数用于测试 x1, x2 x[0], x[1] return 100.0 * (x2 - x1**2)**2 (1 - x1)**2 # Layer 1: Core Engine - 示例模拟二进制交叉SBX和多项式变异 def sbx_crossover(parents, pc, eta15): Simulated Binary Crossover, eta控制分布形状 if np.random.random() pc: return parents.copy() child1, child2 parents[0].copy(), parents[1].copy() for i in range(len(parents[0])): if np.random.random() 0.5: if abs(parents[0][i] - parents[1][i]) 1e-14: y1, y2 parents[0][i], parents[1][i] yl, yu 0.0, 1.0 # 假设已归一化 rand np.random.random() if rand 0.5: beta (2.0 * rand)**(1.0 / (eta 1.0)) else: beta (1.0 / (2.0 * (1.0 - rand)))**(1.0 / (eta 1.0)) child1[i] 0.5 * ((y1 y2) - beta * (y2 - y1)) child2[i] 0.5 * ((y1 y2) beta * (y2 - y1)) # 边界修复 child1[i] np.clip(child1[i], yl, yu) child2[i] np.clip(child2[i], yl, yu) return np.array([child1, child2]) def polynomial_mutation(individual, pm, eta_m20, boundsNone): Polynomial Mutation, eta_m越大扰动越小 if np.random.random() pm: return individual.copy() mutated individual.copy() for i in range(len(individual)): if np.random.random() pm: y individual[i] yl, yu bounds[i] if bounds else (0.0, 1.0) delta1 (y - yl) / (yu - yl) if (yu - yl) 0 else 0 delta2 (yu - y) / (yu - yl) if (yu - yl) 0 else 0 rnd np.random.random() mut_pow 1.0 / (eta_m 1.0) if rnd 0.5: xy 1.0 - delta1 val 2.0 * rnd (1.0 - 2.0 * rnd) * (xy**(eta_m 1.0)) delta_q val**mut_pow - 1.0 else: xy 1.0 - delta2 val 2.0 * (1.0 - rnd) 2.0 * (rnd - 0.5) * (xy**(eta_m 1.0)) delta_q 1.0 - val**mut_pow y y delta_q * (yu - yl) mutated[i] np.clip(y, yl, yu) return mutated # Layer 2: Adaptation Diagnostics - 已在3.1节定义 # Layer 3: Orchestration Loop def run_ga(problem_func, bounds, pop_size100, max_gen500, int_varsNone, verboseTrue): 主运行函数 # 初始化 population initialize_population(pop_size, len(bounds)) best_individual None best_fitness_val float(inf) no_improve_streak 0 best_fitness_history [] diversity_history [] ir_history [] # 计算初始多样性delta_max init_div calculate_diversity(population, bounds, int_vars) delta_max np.mean([v[std] for k, v in init_div.items() if k.startswith(real_dim_)]) if init_div else 1.0 for gen in range(max_gen): # 评估 fitness_vals np.array([problem_func(ind) for ind in population]) # 更新最优 min_idx np.argmin(fitness_vals) if fitness_vals[min_idx] best_fitness_val: best_fitness_val fitness_vals[min_idx] best_individual population[min_idx].copy() no_improve_streak 0 else: no_improve_streak 1 best_fitness_history.append(best_fitness_val) # 计算多样性 div_metrics calculate_diversity(population, bounds, int_vars) di_val 0.0 real_parts [v[std]/(bounds[i][1]-bounds[i][0]) for i, (k,v) in enumerate(div_metrics.items()) if k.startswith(real_dim_)] if real_parts: di_val np.mean(real_parts) int_parts [v[entropy]/v[max_entropy] for k,v in div_metrics.items() if k.startswith(int_var_)] if int_parts: di_val np.mean(int_parts) diversity_history.append(di_val / (len(real_parts)len(int_parts) 1e-8)) # 计算IR if gen 0: ir_val 0.0 else: prev_best best_fitness_history[-2] ir_val (best_fitness_val - prev_best) / (abs(prev_best) 1e-8) ir_history.append(ir_val) # 自适应参数 pc, pm adaptive_parameters(gen, div_metrics, delta_maxdelta_max, no_improve_streakno_improve_streak) # 选择锦标赛 selected tournament_selection(population, fitness_vals, 2) # 交叉 offspring sbx_crossover(selected, pc) # 变异 offspring[0] polynomial_mutation(offspring[0], pm, boundsbounds) offspring[1] polynomial_mutation(offspring[1], pm, boundsbounds) # 替换精英保留 # 找到最差两个个体索引 worst_indices np.argsort(fitness_vals)[-2:] population[worst_indices[0]] offspring[0] population[worst_indices[1]] offspring[1] # 日志 if verbose and gen % 50 0: print(fGen {gen}: Best{best_fitness_val:.6f}, fDI{diversity_history[-1]:.3f}, fPC{pc:.3f}, PM{pm:.3f}) # 绘图 plot_convergence_diagnostics(list(range(max_gen)), best_fitness_history, diversity_history, ir_history) return best_individual, best_fitness_val # 运行示例 bounds [(-2.048, 2.048), (-2.048, 2.048)] result, fitness run_ga(rosenbrock_2d, bounds, pop_size100, max_gen300) print(fOptimal solution: {result}, Fitness: {fitness})实操心得tournament_selection函数我故意没展开因为它的实现极其简单