You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助:如何实现可收敛至极值的遗传算法?现有代码无法收敛

解决遗传算法收敛至最大值的问题

我明白你遇到的困扰——本来想靠遗传算法找到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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 07:49:42