求助:如何实现可收敛至极值的遗传算法?现有代码无法收敛
解决遗传算法收敛至最大值的问题
我明白你遇到的困扰——本来想靠遗传算法找到ASCII码总和最大的句子,结果算法却像“悠悠球”一样来回波动,根本收敛不到最优解。这种情况大多和遗传算法的核心环节设计有关,咱们一步步拆解问题,把你的代码改稳:
1. 先把目标落地:预计算词的ASCII得分
首先可以先把每个词的ASCII总和提前算好,不用每次迭代都重复计算,既省时间也能避免出错:
# 预计算每个词的ASCII总和,存成索引对应得分的字典 word_score_map = [] for group in words: score_dict = {} for idx, word in enumerate(group): score_dict[idx] = sum(ord(c) for c in word) word_score_map.append(score_dict)
2. 波动的核心原因:这些环节大概率没做好
你的代码出现来回波动,基本逃不开这几个设计问题:
- 选择太随机:如果不优先保留高分个体,好不容易找到的优秀基因很容易被随机淘汰
- 变异/交叉概率太高:过度的变异会把好不容易凑出来的高分个体直接打乱,导致种群来回“折腾”
- 没做精英保留:每一代都完全替换种群,之前找到的最优解可能直接消失,自然会波动
3. 针对性改进方案
3.1 必须加的精英保留策略
这是解决波动最关键的一步——每次迭代都把当前种群里的前几名高分个体直接保留到下一代,至少保证不会丢失已经找到的最优解:
def elitism_selection(population, scores, elite_size=2): # 把个体和得分绑定,按得分从高到低排序,取前elite_size个精英 sorted_pairs = sorted(zip(population, scores), key=lambda x: x[1], reverse=True) return [ind for ind, score in sorted_pairs[:elite_size]]
3.2 换更稳定的选择方式:锦标赛选择
别用随机选个体了,换成锦标赛选择——随机抽几个个体,选其中得分最高的,既能保证选到优秀个体,又不会完全卡死在局部最优:
def tournament_selection(population, scores, tournament_size=3): # 随机抽3个个体,选得分最高的当亲本 candidates = random.sample(list(zip(population, scores)), tournament_size) return sorted(candidates, key=lambda x: x[1], reverse=True)[0][0]
3.3 降低变异概率,别乱打乱优秀个体
变异是用来探索新解的,但概率太高就是瞎折腾了,建议把变异率设到0.05-0.1之间:
def mutate(individual, mutation_rate=0.08): new_ind = list(individual) for i in range(len(new_ind)): # 只有小概率会替换当前位置的词 if random.random() < mutation_rate: new_ind[i] = random.choice(range(len(words[i]))) return tuple(new_ind)
3.4 种群更新:精英+后代的组合
下一代种群由“保留的精英”加上“选择交叉变异出来的后代”组成,既守住最优解,又能探索新可能:
def evolve_population(population, scores, elite_size=2, mutation_rate=0.08): elites = elitism_selection(population, scores, elite_size) # 计算需要补充的后代数量 remaining = len(population) - elite_size offspring = [] while len(offspring) < remaining: parent1 = tournament_selection(population, scores) parent2 = tournament_selection(population, scores) # 单点交叉:随机选个位置,把两个亲本的基因拼接 cross_point = random.randint(1, len(parent1)-1) child = parent1[:cross_point] + parent2[cross_point:] # 给后代加一点变异 child = mutate(child, mutation_rate) offspring.append(child) # 精英+后代组成新种群 return elites + offspring
4. 整合后的完整代码
把上面的改进整合起来,再加上迭代和可视化逻辑,你跑起来应该就能看到收敛的趋势了:
import random import statistics import matplotlib.pyplot as plt EVOLUTION = [] words = [ ["Un", "Des", "Une", "On", "Elle"], ["a", "eu", "avait", "est", "était", "fut"], ["soif", "rouge"] ] # 预计算每个词的ASCII得分 word_score_map = [] for group in words: score_dict = {} for idx, word in enumerate(group): score_dict[idx] = sum(ord(c) for c in word) word_score_map.append(score_dict) def create_individual(): # 生成个体:每个元素对应words中每组词的索引 return tuple(random.choice(range(len(group))) for group in words) def calculate_fitness(individual): # 计算个体的ASCII总和 total = 0 for i, idx in enumerate(individual): total += word_score_map[i][idx] return total def elitism_selection(population, scores, elite_size=2): sorted_pairs = sorted(zip(population, scores), key=lambda x: x[1], reverse=True) return [ind for ind, score in sorted_pairs[:elite_size]] def tournament_selection(population, scores, tournament_size=3): candidates = random.sample(list(zip(population, scores)), tournament_size) return sorted(candidates, key=lambda x: x[1], reverse=True)[0][0] def mutate(individual, mutation_rate=0.08): new_ind = list(individual) for i in range(len(new_ind)): if random.random() < mutation_rate: new_ind[i] = random.choice(range(len(words[i]))) return tuple(new_ind) def evolve_population(population, scores, elite_size=2, mutation_rate=0.08): elites = elitism_selection(population, scores, elite_size) remaining = len(population) - elite_size offspring = [] while len(offspring) < remaining: parent1 = tournament_selection(population, scores) parent2 = tournament_selection(population, scores) cross_point = random.randint(1, len(parent1)-1) child = parent1[:cross_point] + parent2[cross_point:] child = mutate(child, mutation_rate) offspring.append(child) return elites + offspring # 主程序 def main(): population_size = 50 generations = 100 elite_size = 3 mutation_rate = 0.08 # 初始化种群 population = [create_individual() for _ in range(population_size)] for gen in range(generations): scores = [calculate_fitness(ind) for ind in population] # 记录当前世代的平均和最高得分 avg_score = statistics.mean(scores) max_score = max(scores) EVOLUTION.append((avg_score, max_score)) print(f"世代 {gen+1}: 平均得分 {avg_score:.2f}, 最高得分 {max_score}") # 进化种群 population = evolve_population(population, scores, elite_size, mutation_rate) # 可视化结果 avg_scores = [x[0] for x in EVOLUTION] max_scores = [x[1] for x in EVOLUTION] plt.plot(avg_scores, label="平均得分") plt.plot(max_scores, label="最高得分") plt.xlabel("世代") plt.ylabel("ASCII总和") plt.legend() plt.show() # 输出最优解 final_scores = [calculate_fitness(ind) for ind in population] best_idx = final_scores.index(max(final_scores)) best_ind = population[best_idx] best_sentence = " ".join([words[i][best_ind[i]] for i in range(len(best_ind))]) print(f"\n最优句子: {best_sentence}") print(f"ASCII总和: {max(final_scores)}") if __name__ == "__main__": main()
5. 为什么这样改能解决波动?
- 精英保留:直接锁死当前最优解,不会出现“一代不如一代”的倒退情况
- 锦标赛选择:相比随机选择,更大概率把优秀基因传递下去,种群整体得分会稳步上升
- 低变异率:只在小概率下探索新方向,不会破坏已经积累的优秀基因
- 平衡更新:精英+后代的组合,既守住了当前最优,又不会完全停止探索新解
你可以根据实际情况调整参数,比如如果还是有小波动,可以再降低变异率,或者增大精英数量试试。
内容的提问来源于stack exchange,提问作者ilapasle
相关产品推荐
相关产品推荐

