ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

遗传算法Python实战:从OneMax到云部署的进化计算指南

遗传算法Python实战:从OneMax到云部署的进化计算指南

简介:《Python遗传算法实战指南(第二版)》是一本面向Python开发者与AI从业者的实战型技术书,重点解决传统优化方法难以处理的复杂搜索空间问题。遗传算法模拟自然选择与遗传机制,特别适合函数优化、特征选择、神经网络超参数调优等场景。作者Eyal Wirsansky拥有超过25年软件开发经验,现任数据平台团队核心成员,书中结合其研究与工程实践,系统讲解染色体编码、选择算子、交叉与变异、适应度函数设计等核心环节,并展示如何将遗传算法与神经网络、强化学习相结合,形成混合优化方案。书中包含大量可运行的Python代码示例,并结合案例逐步讲解遗传算法的完整实现流程。资源包为PDF格式,共1个文件,体积5.99MB,内含完整目录、代码清单与运行结果说明,便于读者按章节边读边练,适合有Python基础、希望掌握元启发式算法的中高级开发者自学或作为课程参考。目前已有54人学习下载,内容深度与实用性兼备,可作为算法工程师的常备案头资料。

1. 遗传算法Python实战:从OneMax到云部署,一本能直接跑的进化算法手册

接手过不少搜索和优化类的需求,最让我头疼的往往不是算法本身,而是目标函数写不出来、梯度算不出来,只能靠手工试参。后来靠遗传算法跑了一晚上,解决了我折腾一周都没搞定的特征筛选问题。这本“遗传算法Python实战”第二版正是讲这件事:用Python和DEAP框架,从OneMax、背包、TSP这些经典例子,一路做到特征选择、超参数调优、神经网络结构搜索、强化学习,甚至无服务器云部署,全书代码都是可运行的。

适读人群很明确:已经写过Python、能跑通sklearn或PyTorch的开发者,想给项目引入进化算法但缺一本“按图索骥”的参考书;也包括刚学AI方向、想理解“不靠梯度也能优化”这个思路的学生。书里每个实验都给了完整代码和配套数据,遇到问题时照着改,比从零看论文轻松得多。

2. 核心原理与DEAP框架:从种群进化到第一行可运行的代码

2.1 遗传算法的主循环:选择、交叉、变异如何配合作战

遗传算法处理的是“搜索空间巨大又缺乏梯度或解析式”的优化问题。它不尝试推导最优解,而是模拟自然选择:维护一个候选解组成的种群,每一代先评估适应度,再按适应度挑选父代,通过交叉生成子代,以一定概率变异,最后形成新一代。书中先用了大量篇幅讲这个进化循环和背后的schema定理——低阶、短定义距的模式会在选择压力下指数增长,理解这个定理有助于判断种群大小、选择压力这些参数该往哪个方向调。

作者强调,与传统优化方法相比,遗传算法有三个特点值得注意。第一是群体搜索而非单点迭代,让算法不容易卡死在一个局部最优;第二是不依赖目标函数可导或连续,很多工程里只有“能跑出得分”的黑盒评估;第三是随机性显著,同一组参数重复跑两次结果不会完全一样,所以实验要留随机种子。

我一般在工程里用遗传算法时还会额外盯两个点。终止条件别只写“最大代数”,加上“连续N代最优适应度没有提升就停止”能省掉大量无效计算;种群规模也尽量别照抄书里的参数,它跟问题维度和评估耗时强相关,需要单独实验确认。

2.2 用DEAP搭建遗传算法:creator与Toolbox的分工

这本书后面的案例几乎全部跑在DEAP框架上,算是借了个现成的轮子。DEAP的用法固定,先创建适应度类和个体类:

from deap import base, creator, tools creator.create("FitnessMax", base.Fitness, weights=(1.0,)) creator.create("Individual", list, fitness=creator.FitnessMax)

第一行weights=(1.0,)表示单目标最大化;如果是(1.0, -1.0)就是多目标,第一个目标越大越好、第二个目标越小越好。第二行创建个体类,类型绑定Python的list,fitness属性指向刚创建的最大化适应度类。DEAP这种“类工厂”的设计一开始可能觉得绕,但好处是后面创建的任何个体都自动带上适应度槽位。

之后通过Toolbox注册“如何生成基因、如何组装个体、如何生成种群”:

import random toolbox = base.Toolbox() toolbox.register("attr_bool", random.randint, 0, 1) toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_bool, 100) toolbox.register("population", tools.initRepeat, list, toolbox.individual)

initRepeat的作用是重复调用attr_bool直到生成100个0/1基因,然后用列表包成个体。这里把“造一个基因”和“造一个个体”解耦,后续如果改成实数编码,只需换掉attr_bool的注册内容,个体组装逻辑完全不用动。这也是我后来复用这本书代码时最舒服的一点,换问题等于换评估函数和换基因生成器,主循环永远不变。

2.3 第一个完整例子:OneMax问题与三行核心超参数

OneMax的目标是让二进制串里的1尽可能多,简单到可以把所有注意力放在框架本身上。我第一次跑通这个例子后,后面的案例基本全是换“评估函数”和“基因表示”。

from deap import base, creator, tools, algorithms import random random.seed(42) creator.create("FitnessMax", base.Fitness, weights=(1.0,)) creator.create("Individual", list, fitness=creator.FitnessMax) toolbox = base.Toolbox() toolbox.register("attr_bool", random.randint, 0, 1) toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_bool, 100) toolbox.register("population", tools.initRepeat, list, toolbox.individual) def eval_one_max(individual): return sum(individual), toolbox.register("evaluate", eval_one_max) toolbox.register("mate", tools.cxTwoPoint) toolbox.register("mutate", tools.mutFlipBit, indpb=0.05) toolbox.register("select", tools.selTournament, tournsize=3) population = toolbox.population(n=300) result, logbook = algorithms.eaSimple( population, toolbox, cxpb=0.5, mutpb=0.2, ngen=40, verbose=True )

几个关键参数值得单独说明。cxpb=0.5是每对个体执行交叉的概率,等效于平均每两代有一半组合发生两点交叉;mutpb=0.2是“每个个体发生变异”的概率,不是每个基因位翻牌的概率,真正控制每个基因位翻转的是indpb=0.05,即一个100位基因的个体平均有5个位点翻转;tournsize=3表示锦标赛选择每次从3个个体里挑适应度最高的,值越大选择压力越大,太大会让种群很快就同质化。

eval_one_max返回元组而非单个数值,是DEAP对多目标优化的兼容设计。这里只能写return sum(individual),,不能写return sum(individual),否则运行时会直接报类型错误。这个细节第一次跑DEAP的人基本都会踩,书里虽然没有反复强调,但从第一个例子开始就保持了正确写法,照着抄就不容易出问题。

跑通的程序还需要保留进化过程中的最优解。加一个Hall of Fame记录器:

hall_of_fame = tools.HallOfFame(1) # 每一代结束后执行 hall_of_fame.update(population)

HallOfFame(1)只会保留整个进化过程中出现过的最优个体,而不是最后一代的最优。别小看这个区别,很多进化算法最后一代因为变异把好解破坏了,直接取最后一代反而拿不到最优结果。

2.4 进化参数怎么调:常见参考区间

用DEAP框架起步时,初始参数可以照这个参考区间来,跑完一个基准再逐步调整。

参数参考区间影响
种群大小n50-500太小易早熟,太大评估耗时上升
交叉概率cxpb0.5-0.9控制搜索与开发的平衡
变异概率mutpb0.1-0.4过小易停滞,过大会变成随机搜索
位点变异概率indpb1/染色体长度通常设为长度的倒数
锦标赛规模tournsize2-5越大选择压力越大
最大代数ngen问题相关结合停止判据使用

跑OneMax时可以试着把mutpb从0.2改成0.05再改成0.5,对比logbook里best的收敛曲线,很快就能感受到两件事同时存在:变异概率太小,种群丧失多样性;太大,已经找到的好解容易被破坏。书中在连续函数优化章节专门做过这类实验,提高突变率之后,Eggholder函数反而更稳定地找到全局最优,原因就是多样性的补充大于对好解的破坏。

3. 经典问题求解:背包、TSP、N皇后与连续函数优化

3.1 背包问题:二进制编码与带惩罚的适应度函数

背包0-1问题适合作为“第一个有真实约束”的练手。染色体是一串0/1,依次表示每个物品选还是不选,长度等于物品数量。难点在约束处理:不能超过背包容量,但遗传算法不会自动遵守约束。

书中用的做法是“惩罚式适应度”,把超重方案的收益打折,而不是直接判零分:

def eval_knapsack(individual, items, capacity): total_value = 0 total_weight = 0 for selected, item in zip(individual, items): if selected: total_value += item.value total_weight += item.weight if total_weight > capacity: return total_value * 0.5, # 超重惩罚 return total_value,

这里有两个设计点值得展开。惩罚系数取0.5是让超重方案依然有部分竞争力,避免直接把超重个体判死、损失边缘搜索的可能;但系数又不足以让“塞一堆超重物品”的方案排到合法方案前面。实际项目中,更好的是根据超重程度做线性惩罚,比如乘上capacity / total_weight,比固定打折更平滑,也不会因为系数拍脑袋而出现适应度倒挂。

另一种常见做法是在评估后做“修复”:随机从超重方案里移除物品直到合法。两种策略各有适用场景,惩罚适合约束宽松的问题,修复适合约束严格、必须保证每个方案都合法的场景。这两个思路在书后面的约束满足章节还会反复出现。

3.2 TSP与VRP:有序列表不能乱交叉

TSP的染色体是城市访问顺序的排列,城市在序列中恰好出现一次。此时使用单点或两点交叉会带来重复城市,产生非法解。解决思路是使用保持排列性质的有序交叉算子,DEAP已经实现了这个算子:

toolbox.register("attr_int", random.randint, 0, 19) toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_int, 20) toolbox.register("mate", tools.cxOrdered) toolbox.register("mutate", tools.mutShuffleIndexes, indpb=0.05) toolbox.register("evaluate", eval_tsp)

有序交叉的核心思路是:从第一个父代中选取一段连续的路径片段,保留这段顺序,再从第二个父代中按顺序补全剩余城市。这样既能继承父代的良好路径片段,又不会产生重复城市。变异算子也要配套改成mutShuffleIndexes,它对序列做局部乱序而不是逐位翻转,因为翻转一个城市编号会立刻产生重复。

书中还提到用TSPLIB标准测试文件验证算法。文件格式固定:第一行注明城市数量,之后是若干行“编号 横坐标 纵坐标”,解析出来就能直接与已知最优解对比。这种“拿真实基准测试套件验证算法”的习惯值得沿用到自己的项目里,比单纯看收敛曲线更有说服力。

VRP是TSP的超集:多辆车、每辆车有容量上限。书中把单条路径表示扩展成“车辆1路径+车辆2路径+……”拼接成一个长列表,路径之间用固定长度块分隔。评估时按块解析成多条路径,再计算总行驶距离和容量约束惩罚。

3.3 N皇后与图着色:硬约束与软约束的组合

N皇后棋盘问题比较经典。书中用了一个很聪明的编码技巧:染色体只表达“每一行的皇后所在列号”,长度等于N。比如[1,3,0,2]表示第0行皇后在第1列、第1行在第3列……这样“行冲突”和“列冲突”直接在编码层面消解,遗传算法只需要处理对角线冲突。

这样设计的好处是搜索空间大幅压缩,剩下的约束用惩罚函数处理:

def eval_nqueens(individual): size = len(individual) diagonal_conflicts = 0 for i in range(size): for j in range(i + 1, size): if abs(individual[i] - individual[j]) == abs(i - j): diagonal_conflicts += 1 return diagonal_conflicts, # 最小化场景,权重设为负

注意这里没有用individual[i] == individual[j]判断同行同列,因为列号已经被编码保证互不相等,检查对角线差值的绝对值是否等于行号差即可。DEAP最小化目标通过creator.create("FitnessMin", base.Fitness, weights=(-1.0,))实现,前面章节先教最大化,这一章会专门讲把目标反转过来的写法。

图着色问题则把约束拆成两层:相邻节点颜色必须不同是硬约束,各颜色使用数量尽量均匀是软约束。书中建议把硬约束放进表示或修复算子,软约束放进适应度函数,这样进化过程中的非法解比例会大幅下降,收敛速度明显提升。

3.4 连续函数优化:实数编码、SBX与多峰搜索

从离散问题转向连续函数时,染色体不再是二进制串而是实数向量。DEAP提供了连续优化的现成算子:模拟二进制交叉和多项式变异,它们比简单的高斯变异保留更好的局部收敛特性。

toolbox.register("attr_float", random.uniform, -512, 512) toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_float, 2) toolbox.register("mate", tools.cxSimulatedBinaryBounded, eta=20.0, low=-512, up=512) toolbox.register("mutate", tools.mutPolynomialBounded, eta=20.0, low=-512, up=512, indpb=0.1)

参数说明:cxSimulatedBinaryBounded的eta控制子代与父代的相似程度,eta越大子代越接近父代,有点像正态分布的标准差;low和up是变量边界,SBX和多项式变异都做了边界限制,防止下一代跑出定义域。评估函数直接代入Eggholder或Himmelblau的公式即可,书里用这些多峰函数反复说明一个问题:全局最优就在那,但局部陷阱很多,搜索策略稍弱就会卡住。

连续优化中最值得借鉴的是niching和sharing技术。做法是计算共享适应度时,个体距离太近会互相“分摊”适应度,迫使种群在多个峰上并行搜索:

def sharing_adjusted_fitness(individual, population, sigma=0.5): raw = eval_eggholder(individual) niche_count = sum( 1 for other in population if euclidean_distance(individual, other) < sigma ) return raw / niche_count,

sigma是共享半径,代表“多近算同一个峰”。这个参数需要根据搜索空间的尺度去调,空间范围越大,sigma应该越大。加了共享机制后,种群不会全部挤在同一个局部最优附近,能同时探索多个峰值,这是解决多峰函数优化的关键技巧。

4. AI场景落地:特征选择、超参数调优、网络结构搜索与部署加速

4.1 特征选择:用遗传算法替代穷举组合

特征选择的本质是从原始特征集中选出最有分类贡献的子集。特征有几十个时,穷举2的N次方种组合不可行。遗传算法里,一个个体就是一个掩码,第i位为1表示选择第i个特征。

def eval_feature_selection(individual, X_train, y_train, X_test, y_test): selected = [i for i, bit in enumerate(individual) if bit == 1] if len(selected) == 0: return 0.0, model = RandomForestClassifier(n_estimators=50, random_state=0) model.fit(X_train[:, selected], y_train) accuracy = model.score(X_test[:, selected], y_test) return accuracy,

评估流程是:掩码切片数据、训练分类器、测试集算准确率。这个评估每次调用都要训练一次模型,进化过程中会执行几百上千次,所以实际项目里必须加缓存:同一掩码不要重复评估。书中在Zoo数据集上做了实验,分类准确率和特征数量呈现明显的权衡关系。

如果特征数量较多,还可以在适应度函数里加一个正则项:accuracy - alpha * num_features,防止搜索偏向“全选”这种耗特征却收益有限的方案。alpha的取值需要实验,通常从0.001开始调,太小没约束力,太大会把好特征也牺牲掉。

4.2 超参数调优:遗传算法对比传统网格搜索

Wine数据集上训练AdaBoost分类器,常规做法是网格搜索:每个参数给一组候选值,然后遍历所有组合。参数一多,组合数爆炸。书中先用传统网格搜索跑了一遍,再用遗传算法搜索同样的参数空间,直观对比效果。

基因表示用整数索引,解码为超参数实际值:

# 个体:[决策树深度索引, 学习率索引, 估计器数量索引] depth_choices = [1, 2, 3, 5, 8, 10] lr_choices = [0.01, 0.05, 0.1, 0.3, 0.5] n_estimators_choices = [10, 25, 50, 80, 100] def decode_hyperparams(individual): return { "max_depth": depth_choices[individual[0]], "learning_rate": lr_choices[individual[1]], "n_estimators": n_estimators_choices[individual[2]], }

整数编码与符号编码的差别在于,整数索引天然带有相对顺序信息,交叉产生的新组合大概率仍然落在合理区域;直接用字符串,比如'gini'和'entropy'这种符号型参数,交叉运算没有意义,只能走变异。一个实用技巧是把映射表放在全局,评估函数里做一次解码,训练好分类器之后再返回精度。

遗传算法跑超参数调优的优势在参数空间变大时体现得特别明显。网格搜索要遍历所有组合,遗传算法只需要评估几百上千个组合就能逼近同样水平,这也是书里反复强调“搜索策略优于穷举”的原因。

4.3 深度网络结构搜索与强化学习中的遗传算法

书中用Iris数据集优化多层感知机网络,把隐藏层层数、每层神经元个数、激活函数都作为基因,个体看成一整份“网络结构描述”。评估时按描述搭建模型、训练并返回准确率。

这个思路稍微扩展就能与超参数调优合在一起:把训练轮数、批量大小、dropout率也编码进染色体,变成“结构+超参数联合搜索”。做这类实验时要特别小心评估成本,一个500个个体的种群跑50代,等于要训练25000次模型,没有缓存和早停机制根本等不到结果。书中给的缓解办法是缩小种群规模、减少每轮训练轮数、增加早停。

强化学习部分更有意思。第10章用Gymnasium的CartPole和MountainCar环境,不是让遗传算法直接控制小车,而是先定义一个神经网络策略,再用遗传算法进化它的权重参数。每次评估让网络权重驱动一次环境交互,累积回报作为适应度:

def eval_cartpole(individual, env): net = build_network_from_weights(individual) observation, _ = env.reset() total_reward = 0.0 done = False while not done: action = 0 if net(observation) < 0 else 1 observation, reward, terminated, truncated, _ = env.step(action) done = terminated or truncated total_reward += reward return total_reward,

这个模式可以套用到很多控制类场景。评估过程完全不依赖反向传播,只需要能跑一集交互。这种“黑盒优化”的思路在真实系统中很有用,尤其当环境模拟器本身不可导时,遗传算法几乎是唯一能直接优化策略的选择。

4.4 NLP与可解释AI:词向量的神秘词与反事实搜索

第11章的实验设计很有意思:加载预训练词向量,给定一个目标词,用遗传算法在词汇表里搜索“与目标词语义最接近的词”。每个个体是一个词索引,适应度函数计算目标词向量与候选词向量的余弦相似度。这个案例把遗传算法当作高维语义空间的检索工具来用,扩展了“搜索”这个词的边界。

第12章把遗传算法用于可解释AI和反事实分析。德国信用风险数据集上,每个样本表示一个贷款申请人的特征。给定一个被判为“拒绝”的样本,遗传算法搜索一个与该样本尽量接近、但能够让模型判定为“批准”的扰动向量。适应度函数由两个部分组成:一部分是扰动幅度要小,另一部分是分类结果要翻转。

核心实现思路是:把一个训练好的黑匣子模型当作评估函数,遗传算法围绕目标样本搜索最小扰动。真实工程中做反事实解释也是一样的套路,不改模型、不重训练,只搜索“解释路径”。

4.5 并发加速:多进程Pool与SCOOP

真实工程里评估函数往往很贵,遗传算法的进化开销反而占比很小。书中演示了用标准库的multiprocessing.Pool把种群评估并行化:

import multiprocessing def evaluate_one(individual): return sum(individual), def evaluate_population(population): with multiprocessing.Pool(processes=4) as pool: return pool.map(evaluate_one, population)

pool.map会把整个种群列表拆成任务分发给多个进程,再把结果按原顺序收集回来,适应度数组能和原个体正确对应。实际使用中我注意到三点:进程启动有一定开销,小种群直接并行反而更慢;DEAP支持注册自己的map函数来替换内置串行map;不要在子进程里创建DEAP的Toolbox对象,否则会反复执行模块导入,拖慢整个流程。

SCOOP库则进一步支持跨节点分布式评估,它把多个worker进程抽象成可跨机器部署的分布式任务队列。书中用它跑OneMax基准,多核环境下效果提升明显。这块内容放到AI章节后面讲,时机很合适——前面的案例已经足够让人感受到评估耗时的痛苦。

4.6 云部署:客户端-服务器架构下的进化计算

书中把“评估函数在云端”“进化逻辑在本地”拆成客户端-服务器模式。本地演化种群、生成下一代个体,云端负责评估每个个体的适应度并返回结果。实现上定义了标准的REST接口,服务端用Gunicorn或Waitress作为生产级WSGI服务器运行。

最后落到函数计算场景,将评估模块打包部署到某云厂商的函数计算服务上,通过API网关暴露接口,客户端每次只需提交个体、拿到适应度。这样本地机器就不用承担高并发的评估压力,进化计算的整个流程被拆成了标准Web服务,可以复用现有的监控、配额和权限体系。

5. 常见问题与避坑:种群早熟、评估瓶颈与云部署翻车

5.1 早熟收敛:适应度几代就卡住不再动

现象:连续运行十几代,最优适应度几乎不增长,种群内个体高度相似,logbook里的多样性指标一路下滑。

原因:最常见的是选择压力过大,锦标赛规模偏大、交叉率偏高、变异率偏低,导致优秀个体快速占领种群,多样性丧失。其次是种群规模太小,有效积木块不够,进化退化为随机搜索。

解决:优先做三件事。第一,把锦标赛规模从3降到2,减弱选择压力;第二,把mutpb从0.2提到0.3到0.4,必要时把indpb提高到1除以染色体长度;第三,如果种群只有几十个个体,先扩到200以上再谈其它。若依然卡住,建议检查目标函数是否给所有个体返回了相同适应度,那是另一个层面问题,跟算子没关。

5.2 惩罚函数调不动:约束总是不满足或者合法解太少

现象:背包问题的解绝大部分超重,N皇后的解总是存在对角线冲突,适应度函数里惩罚项已经设得很大,依然无效。

原因:惩罚系数设置不当。系数太高时,合法解与非法解适应度差距过大,种群几乎没有机会通过“超重但有高价值”的中间态过渡;系数太低,非法解占据明显优势,进化方向偏了。

解决:改线性惩罚为与超过程度成比例的惩罚,比如total_value * (capacity / total_weight);或者在评估后直接修复,随机删除超重物品生成合法后代。书中在约束满足章节明确区分了“硬约束编码进表示”和“软约束放进适应度”的原则,能编码进染色体的约束就不要留给适应度去惩罚。

5.3 评估函数太慢:每一代都要训练好几百次模型

现象:特征选择或网络搜索中,评估一个个体要训练一个模型,种群200、代数50,等于跑10000次训练,跑一宿都没出结果。

原因:没有做评估缓存和早停,也没有用并发。评估是纯计算密集场景,遗传算法本身的进化开销只占很小比例,瓶颈全在评估上。

解决:第一,用字典缓存评估结果,同一个体避免重复训练;第二,网络训练时把epochs减小、加早停;第三,将评估扔给多进程池或云函数并行。书中的并发章节就是针对这个问题设计的,建议一开始就把评估函数写成可并行、无副作用的纯函数,后面加速只是改一行调用方式。

5.4 多进程在交互式环境里无限递归

现象:在Jupyter或某些脚本中创建ProcessPoolExecutor或Pool后,报错RuntimeError: An attempt has been made to start a new process before the current process has finished its bootstrapping phase,或者程序莫名卡死。

原因:多进程在Windows/macOS的spawn模式下会重新导入整个主模块,如果主模块里直接创建子进程,就会套娃式启动,彼此等待形成死锁。

解决:把创建Pool和调用评估的逻辑全部放进if __name__ == "__main__":保护块里,或者保证主模块可以被安全重复导入。DEAP官方示例都严格遵循这个写法,真正的教训是别把它当“示例代码的仪式”跳过,该写的保护块一行都不能少。

5.5 无服务器部署后接口调用不稳定

现象:本地测试通过,发布到函数计算后,第一批请求超时严重,或者在并发和冷启动叠加的情况下频繁出错。

原因:无服务器的冷启动需要加载运行时与依赖,评估模块如果引入大模型或大特征集,冷启动耗时明显超出网关默认超时时间。

解决:评估用的模型先序列化好,比如用joblib或pickle保存后随部署包传入;把网关与函数的超时时间调大;测试时先预热一次调用,再评估真实表现。如果评估本身超过数秒,函数计算可能不适合,应改为常驻服务。

6. 二次开发:把这本书的遗传算法代码移植到自己的项目

全书所有案例都共享同一个流程模板:表示、评估、算子、进化循环。落到自己的项目时,我默认按照三步走。

第一步,写清楚三个问题。染色体怎么表示,是二进制、排列、实数,还是“神经网络权重向量”这种自定义结构;评估函数是否昂贵,需不需要缓存与并行;约束属于硬约束还是软约束,硬约束能不能直接编码进染色体,软约束才进适应度。这三个问题想清楚,书中代码的替换点也就清楚了,只需要替换attr_xxx注册和评估函数体。

第二步,坚持用DEAP内置算子起步,不要一上来就自己发明交叉方法。先跑通默认参数,保存logbook和Hall of Fame,观察收敛曲线。然后每次只改一个参数,比较平均适应度和最优适应度的变化。这个实验方法书里用了很多次,拿来调自己的模型同样成立。

第三步,给评估函数加缓存。这是我认为最稳妥的加速技巧:

_cache = {} def evaluate_with_cache(individual): key = tuple(individual) if key not in _cache: _cache[key] = expensive_evaluate(individual) return _cache[key],

字典缓存要留意key的类型。二进制、整数、浮点向量都能转成tuple做key;如果染色体是嵌套结构,就转成带分隔符的字符串。这个技巧在特征选择和网络结构搜索里能省掉大量重复训练,收益非常明显。

把这本书读下来,我自己最大的习惯变化是:遇到“需要手动试参”的需求,不再拍脑袋定参数,而是先花半小时把搜索空间、约束和评估代价写清楚,再像书中那样跑一组小规模参数实验,用logbook判断收敛性。从那以后,我每次接到优化类需求都会先走一遍这套流程,再决定要不要上遗传算法以及怎么上——希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进