用Python谈一场进化论恋爱:遗传算法趣味入门指南
当你在约会软件上滑动屏幕时,其实正在无意识地进行一种优化算法——在有限时间内寻找最佳匹配。这种寻找最优解的过程,与自然界中生物通过遗传变异适应环境的神奇机制惊人地相似。今天,我们就用Python编写一个"智能择偶系统",在代码世界里模拟这场进化游戏。
1. 从相亲派对到种群初始化
想象你组织了一场有200位参与者的相亲派对(POP_SIZE = 200),每个人用8位二进制数字描述其特质(DNA_SIZE = 8)。在Python中,我们可以用NumPy快速生成这个初始群体:
import numpy as np # 派对参数设置 DNA_SIZE = 8 # 特质编码长度 POP_SIZE = 200 # 参与者数量 X_BOUND = [-3, 3] # 特质x的范围 Y_BOUND = [-3, 3] # 特质y的范围 # 生成随机初始群体 def init_population(): return np.random.randint(2, size=(POP_SIZE, DNA_SIZE*2)) population = init_population()这个二维数组中,每行代表一个参与者,奇数列存储x特质,偶数列存储y特质。比如[1,0,1,1,0,0,1,0,1,1,0,1,0,0,1,1]表示x特质编码为10110010,y特质为11011001的参与者。
提示:将连续变量离散化为二进制串是遗传算法的关键步骤,类似于生物DNA的碱基对组合
2. 解码基因:二进制到约会魅力值
二进制编码需要转换为实际的魅力值才能评估。以下解码函数将8位二进制映射到[-3,3]区间:
def decode_dna(pop): # 分离x,y特质 x_pop = pop[:, 1::2] # 奇数列 y_pop = pop[:, 0::2] # 偶数列 # 二进制转十进制并归一化 x = x_pop.dot(2**np.arange(DNA_SIZE)[::-1]) / (2**DNA_SIZE-1) y = y_pop.dot(2**np.arange(DNA_SIZE)[::-1]) / (2**DNA_SIZE-1) # 映射到目标区间 x = x * (X_BOUND[1]-X_BOUND[0]) + X_BOUND[0] y = y * (Y_BOUND[1]-Y_BOUND[0]) + Y_BOUND[0] return x, y例如二进制10110010转换为十进制178,归一化为178/255≈0.698,最终映射到-3 + 6×0.698 ≈ 1.188的魅力值。
3. 魅力评估函数设计
我们定义以下函数评估每个参与者的综合魅力值(适应度):
def attractiveness(x, y): return 3*(1-x)**2 * np.exp(-x**2 - (y+1)**2) - \ 10*(x/5 - x**3 - y**5) * np.exp(-x**2 - y**2) - \ (1/3)*np.exp(-(x+1)**2 - y**2)这个函数会在坐标系中形成多个魅力峰值,就像现实中有不同类型的理想伴侣。可视化后可以看到三个明显的"心动区域":
| 区域 | 坐标范围 | 魅力值特征 |
|---|---|---|
| 心动区A | (-0.5, -1)附近 | 温和稳定的高魅力值 |
| 心动区B | (1.5, 1)附近 | 强烈但波动大的魅力值 |
| 心动区C | (-1.5, 0.5)附近 | 中等但持久的魅力值 |
4. 自然选择:轮盘赌配对机制
模拟"适者生存"的选择过程,我们采用轮盘赌算法:
def select(pop, fitness): # 确保最小适应度为0.001避免除零错误 fitness = fitness - np.min(fitness) + 1e-3 idx = np.random.choice( np.arange(POP_SIZE), size=POP_SIZE, replace=True, p=fitness/fitness.sum() ) return pop[idx]这个选择过程就像:
- 计算每个参与者被选中的概率(魅力值占比)
- 转动轮盘POP_SIZE次,记录每次选中的索引
- 返回新群体(可能包含重复的优秀个体)
5. 基因重组:模拟约会与后代产生
在下一代产生过程中,我们模拟两个关键生物现象:
5.1 基因交叉(约会交流)
def crossover(parent, pop, crossover_rate=0.8): if np.random.rand() < crossover_rate: # 随机选择母亲 mother_idx = np.random.randint(POP_SIZE) mother = pop[mother_idx] # 随机选择交叉点 cross_point = np.random.randint(DNA_SIZE*2) # 产生后代:交叉点之前来自父亲,之后来自母亲 parent[cross_point:] = mother[cross_point:] return parent5.2 基因突变(意外惊喜)
def mutate(child, mutation_rate=0.01): if np.random.rand() < mutation_rate: # 随机选择突变位置 mutate_point = np.random.randint(DNA_SIZE*2) # 二进制位翻转 child[mutate_point] = child[mutate_point] ^ 1 return child这两个过程共同构成繁殖函数:
def reproduce(pop): new_pop = [] for father in pop: child = crossover(father.copy(), pop) child = mutate(child) new_pop.append(child) return np.array(new_pop)6. 完整进化模拟与结果分析
将上述模块组合成完整的进化模拟:
def genetic_algorithm(generations=50): pop = init_population() best_fitness = [] for i in range(generations): # 评估当前群体 x, y = decode_dna(pop) fitness = attractiveness(x, y) # 记录最佳个体 best_idx = np.argmax(fitness) best_fitness.append(fitness[best_idx]) # 进化迭代 pop = select(pop, fitness) pop = reproduce(pop) return best_fitness运行50代后,我们可以观察到:
适应度进化曲线:
- 前10代快速上升
- 10-30代波动中提升
- 30代后趋于稳定
参数设置经验值:
参数 推荐范围 作用 群体大小 50-500 避免早熟/计算量大 交叉率 0.6-0.9 平衡探索与开发 变异率 0.001-0.1 维持多样性 常见问题排查:
- 早熟收敛:增大变异率或群体规模
- 波动剧烈:降低交叉率
- 停滞不前:尝试自适应变异率
7. 进阶技巧:让算法更懂"恋爱"
7.1 精英保留策略
每次迭代保留前5%的优秀个体直接进入下一代:
def select_with_elite(pop, fitness, elite_ratio=0.05): elite_size = int(POP_SIZE * elite_ratio) elite_idx = np.argsort(fitness)[-elite_size:] elite = pop[elite_idx] selected = select(pop, fitness) selected[:elite_size] = elite # 替换最差个体 return selected7.2 自适应变异率
根据群体多样性动态调整变异率:
def adaptive_mutation(child, avg_fitness, max_fitness): diversity = 1 - (avg_fitness / max_fitness) mutation_rate = 0.01 + 0.1 * diversity return mutate(child, mutation_rate)7.3 多目标优化
同时考虑多个魅力指标(如颜值、幽默感、责任心):
def multi_objective(x, y): return { '颜值': x**2 + y**2, '幽默感': np.sin(x) + np.cos(y), '责任心': np.exp(-(x-1)**2) + np.exp(-(y-1)**2) }在实际项目中,我发现将变异率设置为动态调整(如初始0.1,每代衰减1%)效果往往比固定值更好。另外,对于复杂问题,采用实数编码(而非二进制)有时能获得更平滑的优化过程。