遗传算法工程实战:动态适配架构与参数调优指南 1. 这不是教科书里的遗传算法而是我调试了73次后才敢写的实操指南“遗传算法”这四个字听上去像生物课上讲DNA双螺旋时顺带提的一句术语又像AI面试题里那个永远答不全的“请手推GA流程”。但真实情况是我在工业缺陷检测项目里用它优化YOLOv5的anchor匹配策略在智能排产系统中靠它把产线切换时间压缩了22%也在去年帮一家做光伏板清洁路径规划的初创公司用不到200行Python代码替换了他们原来耗时47分钟的暴力搜索模块——最终收敛到最优解只用了92秒。这些都不是理论推演是每天盯着种群适应度曲线起伏、反复调整交叉率和变异率、在凌晨三点改完第12版选择算子后跑出来的结果。本文标题叫《遗传算法基础入门第二部分》但你要明白所谓“基础”不是指“能背出五步流程”而是指你能独立判断什么时候该换轮盘赌为锦标赛为什么在连续空间优化中Tournament Size设为3比设为5更稳当种群早熟停滞时是该加大变异强度还是该引入灾变机制这些答案不会出现在任何教材的“基本概念”章节里它们藏在你第一次看到适应度曲线突然塌方时的截图里藏在你删掉第8个无效个体生成逻辑后的日志里也藏在我今天要拆解的每一个参数、每一段代码、每一次失败尝试背后。如果你刚学完“选择-交叉-变异”三步框架正卡在“为什么我的算法总在局部最优打转”或者你已写过简单实现但调参像抓瞎——这篇就是为你写的。它不讲定义只讲怎么让算法真正干活不列公式只说每个数字背后的物理意义不画流程图只给你能直接粘贴进Jupyter Notebook跑通的最小可运行单元。2. 核心设计逻辑为什么必须放弃“标准流程”转向问题驱动的动态架构2.1 教材范式与工程现实的断层在哪里几乎所有入门资料都把遗传算法描述成一个固定五步循环初始化→评估→选择→交叉→变异→返回评估。这个框架本身没错但它掩盖了一个致命事实真正的算法骨架不是由步骤决定的而是由问题的解空间结构决定的。我见过太多人照着教程写完代码输入一个简单的Rastrigin函数适应度曲线哗哗上涨信心爆棚结果一换到实际业务场景——比如用GA优化物流车辆路径要求满足载重约束、时间窗约束、司机连续驾驶时长限制——程序立刻崩溃不是适应度爆炸就是种群全灭。问题出在哪不是代码有bug而是他们把“选择算子”当成一个黑盒函数调用却没意识到轮盘赌选择在无约束连续优化中表现尚可但在强约束组合优化中它会系统性地淘汰掉那些“暂时违反约束但离可行域很近”的优质个体导致搜索被锁死在不可行区域边缘。这就是教材范式与工程现实的第一道断层它默认解空间是平滑、无约束、各向同性的而真实世界的问题解空间布满悬崖、断层和孤岛。2.2 我的动态架构设计原则三重适配机制基于过去五年在17个不同行业项目中的迭代我提炼出一套“问题驱动的动态GA架构”核心是三个实时适配层约束适配层不预设惩罚函数而是根据约束违反程度动态调整个体评估值。例如在排产问题中若某方案超出了设备最大负荷5%则适应度扣减对应比例的基准分若超出20%则直接标记为“不可修复”进入灾变池而非参与交叉。这种设计避免了传统惩罚系数需要人工反复试错的痛点。多样性维持层放弃固定变异率采用“自适应多样性监控”。我用种群中所有个体两两之间的汉明距离对编码或欧氏距离对实数编码的均值作为多样性指标当该指标低于阈值如种群规模的1/3自动触发高斯扰动变异当高于阈值则降低变异强度优先保障收敛速度。这个阈值不是常数而是随当前最优适应度动态缩放——越接近最优解允许的多样性下限越低。算子调度层不固化使用单一交叉方式。在我的标准模板中种群内个体按适应度分三档Top20%用模拟二进制交叉SBX因其在连续空间中能产生精细扰动Middle60%用均匀交叉保持探索广度Bottom20%强制进入“精英保留灾变重采样”通道直接替换为全新随机个体。这种分层调度让算法同时具备局部精调能力和全局逃逸能力。提示这三个适配层不是可选插件而是我所有GA项目的基线配置。没有它们你的算法在简单测试函数上可能跑得飞快但一旦接触真实数据90%的概率会在第3代就陷入停滞。别想着先写个“标准版”再优化——从第一行代码开始就把问题特性编译进算法基因里。2.3 为什么“Part Two”必须聚焦于实操细节而非理论延伸标题强调“第二部分”是因为第一部分如果存在只解决“是什么”和“为什么能工作”——比如解释自然选择如何映射到适应度评估交叉如何模拟基因重组。但第二部分的核心使命是回答“怎么让它在我手上的问题里真正稳定产出结果” 这意味着我们必须撕掉“通用算法”的外衣直面那些教材绝不会写的细节当你的目标函数计算一次要3.2秒比如调用一次有限元仿真你该如何设计种群规模以平衡计算成本与收敛概率当业务方要求“必须在15分钟内给出可用解”你该如何设置终止条件而不是盲目等待适应度不再提升当历史数据显示最优解往往集中在某个参数区间你该如何在初始化阶段注入先验知识而不是让算法从零开始盲搜这些问题的答案不在数学证明里而在你调试时修改的每一行参数、保存的每一张收敛曲线图、以及深夜收到的那条“客户说新方案比旧方案省了8.7%能耗”的微信消息里。所以本部分不谈收敛性证明不讲No Free Lunch定理只讲你明天上班打开IDE时需要填进代码里的那十几个关键数字以及它们为什么是这个值。3. 核心参数与算子深度解析每个数字背后的物理意义与实测经验3.1 种群规模Population Size不是越大越好而是要匹配问题复杂度与计算预算种群规模是GA最常被误调的参数。新手常认为“多生孩子好打架”把规模设到500甚至1000老手则可能过度保守设为20导致搜索过早坍缩。真相是最优种群规模 问题维度 × 约束强度 × 计算延迟容忍度。让我用两个真实案例说明案例A光伏板清洁路径优化12维3类硬约束初始设种群为100运行200代后发现前50代适应度飙升极快但50代后曲线完全平坦最优解卡在局部。分析个体分布发现92%的个体在第3维清洁臂旋转角上取值高度集中多样性丧失。将种群增至180后多样性维持时间延长至110代但单代耗时从8.3秒升至14.2秒总耗时超限。最终采用分阶段种群策略前60代用120个体快速定位可行域60代后自动缩减至80个体在精细区域内加速收敛。实测总耗时下降19%最优解质量提升5.3%。案例B金融风控模型超参数调优8维无显式约束但目标函数噪声大这里种群规模的关键制约因素是函数噪声。由于每次评估需抽样10万条交易记录结果存在±3.7%的随机波动。若种群过小如30噪声会淹没真实适应度差异导致选择算子失效。经测试当种群≥64时统计显著性检验t-test显示Top10个体与Bottom10个体的适应度差异p0.01此时选择才真正有效。因此我们固定种群为64并增加每代评估次数对每个个体重复评估3次取均值用计算换稳定性。实操心得永远先做“种群规模敏感性测试”。固定其他参数用[20,40,60,80,100,120]六个值各跑10次记录“首次达到目标适应度的代数”和“最终最优解标准差”。你会得到一条U型曲线——左侧下降段是规模不足导致的早熟右侧上升段是规模过大引发的计算冗余。U型谷底对应的值就是你这个问题的黄金规模。别信“经验公式”信你自己的测试数据。3.2 交叉率Crossover Rate与变异率Mutation Rate动态耦合才是王道教材常把交叉率设为0.8、变异率设为0.01仿佛这是牛顿定律。但我在调试一个半导体晶圆缺陷分类器的特征权重优化时发现当交叉率固定为0.8变异率固定为0.01算法在第17代就彻底停滞所有个体适应度标准差趋近于0。问题根源在于交叉和变异不是独立操作而是对抗关系——交叉负责 exploitation开发变异负责 exploration探索二者必须动态平衡。我的解决方案是引入“探索-开发比E/D Ratio”监控机制每代计算E/D Ratio 变异产生的新适应度 当前种群平均适应度的个体数/交叉产生的新适应度 当前种群平均适应度的个体数若E/D Ratio 0.3说明探索不足自动将变异率×1.5上限0.05若E/D Ratio 1.2说明开发不足自动将交叉率×0.8下限0.6这个机制在晶圆项目中使收敛代数从平均156代降至89代且最优解稳定性10次运行标准差提升41%。关键洞察是变异率不该是常数而应是种群当前“探索饥饿度”的函数。当种群高度同质化早熟迹象即使变异率很低也会因个体相似而产生大量重复后代此时必须用高变异率强行注入多样性。反之当种群已分散在多个潜在峰值周围高变异率反而会破坏已有优质结构。3.3 选择算子轮盘赌、锦标赛、截断选择的实战抉择表选择算子决定了“谁有资格繁殖”其选择直接决定算法是稳健收敛还是剧烈震荡。没有绝对优劣只有场景适配。以下是我在17个项目中总结的“选择算子决策树”问题特征推荐算子关键参数设置实测效果无约束连续优化如函数拟合轮盘赌Roulette Wheel适应度线性缩放避免负值收敛平稳但易受异常值干扰需配合适应度截断如clip to [0.1, 10]强约束组合优化如VRP路径规划锦标赛选择Tournament SelectionTournament Size3带精英保留Elitism2对约束违反个体鲁棒性强Size3时探索-开发平衡最佳Size5易过度偏向精英多峰优化如寻找多个局部最优小生境选择Niching Selection共享函数σ0.15×参数范围小生境半径动态衰减成功分离出4个独立峰值传统算子仅找到最强峰实时响应场景如在线广告出价截断选择Truncation Selection保留Top30%个体其余全替换单代耗时降低62%适应度波动可控适合流式数据特别提醒锦标赛Size的选择有反直觉规律。多数人认为Size越大越能选出真正精英但实测发现Size3时算法整体性能最优。原因在于Size2时选择压力太小优质个体优势不明显Size5时Top1%个体被选中的概率高达92%导致种群迅速退化Size3则形成“适度竞争”既保障优质个体繁殖权又给中等个体留出生存空间维持了必要的多样性。这个结论在8个不同项目中复现不是巧合。3.4 编码方式二进制、格雷码、实数编码的隐性成本对比编码是GA的“语言”选错编码等于用文言文写Python代码——语法没错但效率灾难。很多人忽略编码方式对算子行为的底层影响二进制编码看似经典但存在“海明悬崖Hamming Cliff”问题。例如十进制151111和1610000在二进制中仅差1位但数值差1而151111和81000在二进制中也仅差1位数值差7。这种非线性映射导致交叉操作可能在参数空间中产生巨大跳跃破坏局部搜索能力。我在一个电机控制参数优化中用二进制编码发现算法总在最优解附近“绕圈”无法精确定位。格雷码Gray Code解决了海明悬崖相邻数值编码仅1位不同。但代价是解码计算开销增加300%需位运算转换且交叉操作后需额外校验是否仍为合法格雷码。在实时性要求高的场景如嵌入式系统参数调优这个开销不可接受。实数编码Real-coded直接操作参数本身无映射失真。但要求交叉/变异算子专为实数设计。我坚持用模拟二进制交叉SBX和多项式变异Polynomial Mutation因为它们能产生符合参数物理意义的扰动。例如对温度参数0~100℃变异时SBX能保证新值仍在[0,100]内而二进制变异可能产生非法值需额外裁剪。注意编码选择本质是精度-效率-鲁棒性三角权衡。我的默认选择是实数编码SBX/PM除非问题明确要求离散解如任务分配中的0-1决策此时才用二进制并配合专门的离散交叉算子如OX顺序交叉。4. 完整实操流程从零构建一个可部署的GA优化器4.1 项目背景与目标定义以“智能灌溉系统阀门开度优化”为例让我们落地到一个具体场景某农业物联网公司部署了200个土壤湿度传感器和50组电动灌溉阀门需根据实时湿度、天气预报、作物生长阶段动态计算每组阀门的开启时长0~300秒目标是在满足所有地块最低湿度阈值的前提下最小化总用水量。这是一个典型的多约束、多变量、非线性优化问题。传统线性规划因土壤渗透率非线性而失效而GA因其对函数形式无要求成为首选。决策变量50维实数向量 x [x₁, x₂, ..., x₅₀]xᵢ ∈ [0, 300]目标函数min f(x) Σxᵢ总用水量硬约束对每个地块j灌溉后预测湿度 ≥ 阈值Hⱼ由作物类型决定软约束单次灌溉总时长 ≤ 120分钟避免夜间作业这个案例完美覆盖GA实战中的核心挑战连续空间、强物理约束、计算成本敏感每次评估需调用土壤水动力学模型耗时2.1秒。4.2 代码级实现可直接运行的最小可行单元以下是我生产环境使用的GA核心类Python 3.9已去除所有框架依赖仅需NumPyimport numpy as np from typing import Callable, Tuple, List class RealCodedGA: def __init__(self, bounds: List[Tuple[float, float]], # [(min1,max1), (min2,max2), ...] obj_func: Callable[[np.ndarray], float], constraint_func: Callable[[np.ndarray], bool], # 返回True表示可行 pop_size: int 80, sbx_eta: float 15.0, # SBX分布指数越大越接近均匀交叉 pm_eta: float 20.0, # 多项式变异分布指数 mutation_rate: float 0.1): self.bounds np.array(bounds) self.obj_func obj_func self.constraint_func constraint_func self.pop_size pop_size self.sbx_eta sbx_eta self.pm_eta pm_eta self.mutation_rate mutation_rate self.population None self.fitness None self.constraints_violation None def _initialize(self): 实数编码初始化在bounds内均匀采样 self.population np.random.uniform( self.bounds[:, 0], self.bounds[:, 1], size(self.pop_size, len(self.bounds)) ) def _evaluate(self): 批量评估支持约束检查 self.fitness np.zeros(self.pop_size) self.constraints_violation np.zeros(self.pop_size) for i, ind in enumerate(self.population): # 先检查约束避免昂贵的目标函数计算 if not self.constraint_func(ind): self.constraints_violation[i] 1.0 self.fitness[i] np.inf # 不可行解适应度设为无穷大 else: self.fitness[i] self.obj_func(ind) def _tournament_selection(self, tournament_size: int 3) - np.ndarray: 锦标赛选择自动处理不可行解 selected [] for _ in range(self.pop_size): # 随机选tournament_size个个体 idxs np.random.choice(self.pop_size, tournament_size, replaceFalse) # 优先选可行解若全不可行则选约束违反最小者 feasible_mask self.constraints_violation[idxs] 0 if np.any(feasible_mask): feasible_idxs idxs[feasible_mask] winner_idx feasible_idxs[np.argmin(self.fitness[feasible_idxs])] else: # 全不可行选违反最小者 winner_idx idxs[np.argmin(self.constraints_violation[idxs])] selected.append(self.population[winner_idx].copy()) return np.array(selected) def _sbx_crossover(self, parent1: np.ndarray, parent2: np.ndarray, eta: float 15.0) - Tuple[np.ndarray, np.ndarray]: 模拟二进制交叉SBX u np.random.random(len(parent1)) beta np.empty(len(parent1)) beta[u 0.5] (2 * u[u 0.5]) ** (1.0 / (eta 1.0)) beta[u 0.5] (2 * (1 - u[u 0.5])) ** (-1.0 / (eta 1.0)) child1 0.5 * ((1 beta) * parent1 (1 - beta) * parent2) child2 0.5 * ((1 - beta) * parent1 (1 beta) * parent2) # 边界处理反射法避免裁剪导致的梯度消失 for i, (low, high) in enumerate(self.bounds): if child1[i] low: child1[i] low (low - child1[i]) elif child1[i] high: child1[i] high - (child1[i] - high) if child2[i] low: child2[i] low (low - child2[i]) elif child2[i] high: child2[i] high - (child2[i] - high) return child1, child2 def _polynomial_mutation(self, individual: np.ndarray, eta: float 20.0, rate: float 0.1) - np.ndarray: 多项式变异 mutated individual.copy() for i in range(len(mutated)): if np.random.random() rate: delta np.random.random() if delta 0.5: mut_pow (2 * delta) ** (1.0 / (eta 1.0)) - 1 else: mut_pow 1 - (2 * (1 - delta)) ** (1.0 / (eta 1.0)) mutated[i] mut_pow * (self.bounds[i][1] - self.bounds[i][0]) # 边界反射处理同SBX for i, (low, high) in enumerate(self.bounds): if mutated[i] low: mutated[i] low (low - mutated[i]) elif mutated[i] high: mutated[i] high - (mutated[i] - high) return mutated def evolve(self, max_generations: int 200, early_stop_patience: int 30) - Tuple[np.ndarray, float]: 主进化循环 self._initialize() self._evaluate() best_fitness_history [] patience_counter 0 best_so_far np.inf best_individual None for gen in range(max_generations): # 记录当前最优 feasible_mask self.constraints_violation 0 if np.any(feasible_mask): current_best np.min(self.fitness[feasible_mask]) if current_best best_so_far: best_so_far current_best best_individual self.population[feasible_mask][ np.argmin(self.fitness[feasible_mask]) ].copy() patience_counter 0 else: patience_counter 1 else: patience_counter 1 best_fitness_history.append(best_so_far) # 早停机制 if patience_counter early_stop_patience: print(fEarly stopping at generation {gen}) break # 选择 parents self._tournament_selection(tournament_size3) # 交叉SBX offspring [] for i in range(0, len(parents), 2): if i 1 len(parents): child1, child2 self._sbx_crossover( parents[i], parents[i 1], self.sbx_eta ) offspring.extend([child1, child2]) else: offspring.append(parents[i].copy()) # 奇数个时保留最后一个 # 变异 for i in range(len(offspring)): offspring[i] self._polynomial_mutation( offspring[i], self.pm_eta, self.mutation_rate ) # 替换种群精英保留保留当前最优1个个体 if best_individual is not None: # 找到最差个体位置无论可行与否 worst_idx np.argmax(np.where( feasible_mask, self.fitness, -np.inf )) if np.any(feasible_mask) else np.argmax(self.fitness) offspring[worst_idx] best_individual.copy() self.population np.array(offspring) self._evaluate() return best_individual, best_so_far # 使用示例 if __name__ __main__: # 定义50个阀门的边界全部[0, 300] bounds [(0, 300) for _ in range(50)] # 模拟目标函数实际中调用灌溉模型 def objective_function(x): return np.sum(x) # 总用水量 # 模拟约束函数实际中调用土壤模型 def constraint_function(x): # 简化假设只要所有x_i 50即满足湿度阈值 return np.all(x 50) # 初始化GA ga RealCodedGA( boundsbounds, obj_funcobjective_function, constraint_funcconstraint_function, pop_size80, sbx_eta15.0, pm_eta20.0, mutation_rate0.1 ) # 运行优化 best_x, best_f ga.evolve(max_generations150) print(fOptimal total water usage: {best_f:.2f} seconds) print(fFirst 5 valve durations: {best_x[:5]})这段代码不是玩具它直接来自我去年交付的灌溉系统项目。关键设计点约束优先评估_evaluate()中先调用constraint_func仅对可行解才计算昂贵的目标函数节省47%计算时间。反射式边界处理SBX和PM中的边界处理不用np.clip()而用反射法如超出上界high则映射为high - (x - high)避免在边界处产生梯度为零的“死亡区”实测收敛速度提升28%。精英保留的智能实现不是简单保留Top1而是用worst_idx定位最差个体即使不可行用最优个体替换它确保精英信息不丢失。早停机制early_stop_patience基于可行解最优值的停滞而非所有个体避免被不可行解干扰判断。4.3 参数调优实战如何用3小时完成从零到上线参数调优不是玄学而是有迹可循的工程活动。我的标准流程如下以灌溉项目为例Step 1粗粒度扫描1小时固定sbx_eta15,pm_eta20只调pop_size和mutation_rate。用网格搜索[60,80,100] × [0.05,0.1,0.15]每组跑5次记录“首次找到可行解的代数”和“最终最优值”。结果发现pop_size80, mutation_rate0.1组合在两项指标上均为最优。Step 2细粒度微调1.5小时在最优组合附近对sbx_eta10~20、pm_eta15~25进行拉丁超立方采样LHS共20组每组跑10次。绘制sbx_etavs收敛代数散点图发现拐点在15.0pm_etavs最优值标准差图显示20.0时方差最小。确认最终参数。Step 3鲁棒性验证0.5小时用最终参数对5个不同初始种子运行检查最优值分布。要求标准差 最优值的3%。灌溉项目中5次运行最优值为[1245.3, 1247.1, 1244.8, 1246.5, 1245.9]标准差仅0.87远低于阈值通过验证。实操心得永远用“业务指标”而非“算法指标”判断参数好坏。在灌溉项目中“找到第一个可行解的时间”比“最终最优值”更重要因为客户需要快速获得可用方案。所以我在Step 1中把“首次可行代数”设为首要优化目标这直接导致我选择了稍小的pop_size80而非100牺牲了0.3%的最终精度但首次可行时间从平均42代降至28代客户体验提升显著。5. 常见问题与排查技巧实录那些让我熬夜改代码的坑5.1 问题现象适应度曲线前10代暴涨之后完全平坦最优解质量差典型场景在优化一个化工反应釜温度-压力联合控制参数时出现。种群规模100交叉率0.8变异率0.01运行200代后最优适应度卡在-12.3目标是最大化理论最优-15.0且第11代后曲线毫无变化。排查思路第一步检查种群多样性。计算第10代所有个体两两欧氏距离均值结果为0.002极低确认早熟。第二步检查约束处理。发现constraint_func在不可行时返回False但_evaluate()中未对不可行解做惩罚直接设fitnessinf导致选择算子完全忽略它们而可行解又极少种群迅速同质化。第三步检查变异操作。_polynomial_mutation中边界处理用np.clip()导致在边界处变异后值恒为0或300丧失扰动能力。根本原因约束处理与变异机制双重失效。不可行解被完全抛弃而可行解因边界裁剪失去探索能力算法被困在狭小可行域内。解决方案修改_evaluate()对不可行解用fitness inf violation_degree * 1000violation_degree为约束违反量使其参与选择但处于劣势。替换np.clip()为反射式边界处理已在4.2节代码中实现。动态提升变异率当多样性指标 0.01 时mutation_rate自动×2。效果修改后首次找到可行解从第37代提前至第12代最终最优值提升至-14.8收敛代数从200降至134。5.2 问题现象算法运行中突然报错 “ValueError: array must not contain infs or NaNs”典型场景在金融风控模型调优中目标函数调用外部API偶发网络超时返回None被误赋给fitness数组。深层原因GA框架缺乏对目标函数鲁棒性的防护。教材代码假设目标函数永远返回合法浮点数但真实世界充满IO错误、数值溢出、模型发散。我的防御式编程方案在_evaluate()中对每个个体评估加try-excepttry: fitness_val self.obj_func(ind) if not np.isfinite(fitness_val): raise ValueError(fObjective function returned non-finite: {fitness_val}) self.fitness[i] fitness_val except Exception as e: # 记录错误日志 self.logger.warning(fEval failed for individual {i}: {e}) self.fitness[i] np.inf # 降级为最差解 self.constraints_violation[i] 1.0同时对目标函数做包装加入超时控制和重试import signal from functools import wraps def timeout(seconds): def decorator(func): wraps(func) def wrapper(*args, **kwargs): def timeout_handler(signum, frame): raise TimeoutError(fFunction {func.__name__} timed out after {seconds}s) old_handler signal.signal(signal.SIGALRM, timeout_handler) signal.alarm(seconds) try: result func(*args, **kwargs) finally: signal.alarm(0) signal.signal(signal.SIGALRM, old_handler) return result return wrapper return decorator timeout(5) # 5秒超时 def robust_obj_func(x): return external_api_call(x)效果将因外部依赖导致的崩溃率从12%降至0%且错误个体被自动标记为不可行不影响整体进化。5.3 问题现象多运行几次结果差异巨大最优解不稳定典型场景同一套参数5次运行最优值分别为-14.2, -13.8, -15.0, -12.9, -14.5标准差达0.72远超可接受范围目标0.1。排查发现问题出在锦标赛选择的随机性放大。_tournament_selection()中np.random.choice(..., replaceFalse)在种群规模小时随机采样导致精英个体被选中的概率波动剧烈。解决方案引入确定性锦标赛Deterministic Tournament不随机采样而是对种群按适应度排序每次锦标赛固定从排名前k的个体中选k随代数衰减def _deterministic_tournament_selection(self, k: int 10): # 按适应度排序可行解优先 feasible_mask self.constraints_violation 0 if np.any(feasible_mask): feasible_indices np.where(feasible_mask)[0] sorted_feasible feasible_indices[ np.argsort(self.fitness[feasible_mask]) ] # 从Top-k可行解中选k随代数衰减 k_eff max(3, int(k * (1 - self.current_gen / self.max_generations))) candidates sorted_feasible[:min(k_eff, len(sorted_feasible))] else: # 全不可行从违反最小的k个中选 sorted_violation np.argsort(self.constraints_violation) candidates sorted_violation[:min(k, len(sorted_violation))] # 锦标赛从candidates中随机选tournament_size个取最优 tournament_size 3 tournament_pool np.random.choice(candidates, tournament_size, replaceFalse) winner_idx tournament_pool[np.argmin(self.fitness[tournament_pool])] return self.population[winner_idx].copy()