遗传算法(Genetic Algorithm)详解与实现

embedded/2025/1/8 4:51:55/

遗传算法(Genetic Algorithm)详解与实现

1. 基本原理

遗传算法(Genetic Algorithm, GA)是一种模拟生物进化过程的优化算法。它的核心思想来源于达尔文进化论:

“物竞天择,适者生存”

1.1 核心概念

遗传算法包含以下关键要素:

  1. 染色体(Chromosome):问题解的编码表示
  2. 种群(Population):多个候选解的集合
  3. 适应度(Fitness):评价解的好坏程度
  4. 选择(Selection):模拟自然选择过程
  5. 交叉(Crossover):产生新的后代
  6. 变异(Mutation):维持种群多样性

1.2 数学基础

从优化的角度看,遗传算法在解空间 S S S中搜索最优解 x ∗ x^* x

f ( x ∗ ) = m a x { f ( x ) ∣ x ∈ S } f(x^*) = max\{f(x) | x \in S\} f(x)=max{f(x)xS}
f ( x ∗ ) = m i n { f ( x ) ∣ x ∈ S } f(x^*) = min\{f(x) | x \in S\} f(x)=min{f(x)xS}

算法复杂度:

  • 空间复杂度: O ( N L ) O(NL) O(NL)
  • 时间复杂度: O ( G N L ) O(GNL) O(GNL)

其中:

  • N N N为种群大小
  • L L L为染色体长度
  • G G G为迭代代数

2. 编码机制

2.1 二进制编码

将实数 x ∈ [ a , b ] x \in [a,b] x[a,b]编码为 n n n位二进制串:

x = a + ( b − a ) ⋅ ∑ i = 0 n − 1 b i ⋅ 2 i 2 n − 1 x = a + (b-a) \cdot \frac{\sum_{i=0}^{n-1}b_i \cdot 2^i}{2^n - 1} x=a+(ba)2n1i=0n1bi2i

其中 b i b_i bi表示第 i i i位的二进制值(0或1)。

3. 遗传操作

3.1 选择操作

选择概率计算:
P ( i ) = f i ∑ j = 1 N f j P(i) = \frac{f_i}{\sum_{j=1}^N f_j} P(i)=j=1Nfjfi

3.2 交叉操作

交叉概率 p c p_c pc通常取0.6-0.9,表示进行交叉操作的概率。

3.3 变异操作

变异概率 p m p_m pm通常取0.001-0.1,表示基因发生变异的概率。

4. 完整实现

以下是一个完整的遗传算法Python实现,用于求解函数优化问题:

python">import numpy as np
import matplotlib.pyplot as plt
from typing import List, Tuple, Callableclass GeneticAlgorithm:def __init__(self,pop_size: int = 100,chromosome_length: int = 16,max_gen: int = 200,crossover_rate: float = 0.8,mutation_rate: float = 0.01,x_bounds: Tuple[float, float] = (-1, 2)):"""遗传算法类参数:pop_size: 种群大小chromosome_length: 染色体长度(二进制编码位数)max_gen: 最大迭代代数crossover_rate: 交叉概率mutation_rate: 变异概率x_bounds: 自变量范围"""self.pop_size = pop_sizeself.chromosome_length = chromosome_lengthself.max_gen = max_genself.crossover_rate = crossover_rateself.mutation_rate = mutation_rateself.x_bounds = x_bounds# 记录每代的最佳适应度self.best_fitness_history = []self.avg_fitness_history = []def initialize_population(self) -> np.ndarray:"""初始化种群"""return np.random.randint(2, size=(self.pop_size, self.chromosome_length))def decode_chromosome(self, chromosome: np.ndarray) -> float:"""将二进制染色体解码为实数"""x_min, x_max = self.x_boundsdecimal = int(''.join(map(str, chromosome)), 2)x = x_min + decimal * (x_max - x_min) / (2**self.chromosome_length - 1)return xdef fitness_function(self, x: float) -> float:"""适应度函数: f(x) = x·sin(10πx) + 2.0"""return x * np.sin(10 * np.pi * x) + 2.0def calculate_fitness(self, population: np.ndarray) -> np.ndarray:"""计算种群中所有个体的适应度"""fitness = np.zeros(self.pop_size)for i, chromosome in enumerate(population):x = self.decode_chromosome(chromosome)fitness[i] = self.fitness_function(x)return fitnessdef roulette_selection(self, population: np.ndarray, fitness: np.ndarray) -> np.ndarray:"""轮盘赌选择"""fitness = fitness - np.min(fitness) + 1e-6probs = fitness / fitness.sum()selected_idx = np.random.choice(self.pop_size,size=self.pop_size,p=probs)return population[selected_idx]def crossover(self, parent1: np.ndarray, parent2: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:"""单点交叉"""if np.random.random() < self.crossover_rate:point = np.random.randint(1, self.chromosome_length)child1 = np.concatenate([parent1[:point], parent2[point:]])child2 = np.concatenate([parent2[:point], parent1[point:]])return child1, child2return parent1.copy(), parent2.copy()def mutation(self, chromosome: np.ndarray) -> np.ndarray:"""位变异"""for i in range(self.chromosome_length):if np.random.random() < self.mutation_rate:chromosome[i] = 1 - chromosome[i]return chromosomedef evolve(self) -> Tuple[float, float]:"""执行遗传算法"""population = self.initialize_population()for generation in range(self.max_gen):fitness = self.calculate_fitness(population)self.best_fitness_history.append(np.max(fitness))self.avg_fitness_history.append(np.mean(fitness))selected = self.roulette_selection(population, fitness)new_population = []for i in range(0, self.pop_size, 2):parent1, parent2 = selected[i], selected[i+1]child1, child2 = self.crossover(parent1, parent2)child1 = self.mutation(child1)child2 = self.mutation(child2)new_population.extend([child1, child2])population = np.array(new_population)final_fitness = self.calculate_fitness(population)best_idx = np.argmax(final_fitness)best_x = self.decode_chromosome(population[best_idx])best_y = final_fitness[best_idx]return best_x, best_ydef plot_fitness_history(self):"""绘制适应度变化曲线"""plt.figure(figsize=(10, 6))plt.plot(self.best_fitness_history, label='Best Fitness')plt.plot(self.avg_fitness_history, label='Average Fitness')plt.xlabel('Generation')plt.ylabel('Fitness')plt.title('Fitness History')plt.legend()plt.grid(True)plt.show()

使用示例

python">if __name__ == "__main__":# 创建遗传算法实例ga = GeneticAlgorithm(pop_size=100,chromosome_length=16,max_gen=200,crossover_rate=0.8,mutation_rate=0.01,x_bounds=(-1, 2))# 执行算法best_x, best_y = ga.evolve()print(f"最优解: x = {best_x:.6f}")print(f"最优值: f(x) = {best_y:.6f}")# 绘制适应度变化曲线ga.plot_fitness_history()# 绘制目标函数x = np.linspace(-1, 2, 1000)y = x * np.sin(10 * np.pi * x) + 2.0plt.figure(figsize=(10, 6))plt.plot(x, y, label='f(x) = x·sin(10πx) + 2.0')plt.scatter([best_x], [best_y], color='red', marker='*', s=200, label=f'Best Solution: ({best_x:.3f}, {best_y:.3f})')plt.xlabel('x')plt.ylabel('f(x)')plt.title('Optimization Result')plt.legend()plt.grid(True)plt.show()

5. 代码设计解析

让我们深入理解这个遗传算法的实现,看看每个部分是如何工作的,以及为什么要这样设计。

5.1 整体框架设计

首先,我们把整个算法封装成一个类GeneticAlgorithm。这样做有几个好处:

  1. 参数管理更清晰

    • 所有重要参数都在初始化时设置
    • 可以随时访问和修改这些参数
    • 不同的优化问题可以创建不同的实例
  2. 代码结构更合理

    • 相关的功能被组织在一起
    • 方法之间可以方便地共享数据
    • 便于扩展和修改功能

5.2 参数设计

我们在初始化时设置了几个关键参数:

python">def __init__(self,pop_size: int = 100,        # 种群大小chromosome_length: int = 16, # 染色体长度max_gen: int = 200,         # 最大代数crossover_rate: float = 0.8, # 交叉概率mutation_rate: float = 0.01, # 变异概率x_bounds: Tuple[float, float] = (-1, 2) # 取值范围
):

为什么要这样设置这些参数?

  1. 种群大小(pop_size)

    • 设置为100是一个比较好的平衡点
    • 太小:容易陷入局部最优
    • 太大:计算量会显著增加
    • 类比:就像找最好的餐馆,同时派100个人去不同的餐馆吃饭,然后互相分享体验
  2. 染色体长度(chromosome_length)

    • 使用16位二进制编码
    • 提供了足够的精度(可以表示2^16个不同的值)
    • 计算量还不会太大
    • 类比:就像用16位数字来表示一个价格,精确到分
  3. 最大代数(max_gen)

    • 设置为200代
    • 给算法足够的进化时间
    • 防止运行时间过长
    • 类比:就像限制找餐馆的时间,不能无限期地找下去

5.3 编码和解码机制

这部分可能是最有趣的设计之一:

python">def decode_chromosome(self, chromosome: np.ndarray) -> float:"""将二进制染色体解码为实数"""x_min, x_max = self.x_boundsdecimal = int(''.join(map(str, chromosome)), 2)x = x_min + decimal * (x_max - x_min) / (2**self.chromosome_length - 1)return x

为什么要用二进制编码?

  1. 直观性

    • 二进制是最基本的编码方式
    • 容易理解和实现
    • 类比:就像把一个数字转换成二进制,然后再转回来
  2. 精确性

    • 16位二进制可以表示65536个不同的值
    • 在[-1, 2]区间内,精度约为0.00004
    • 类比:就像用尺子测量长度,刻度越密,测量越精确

5.4 适应度计算

适应度函数决定了个体的优劣:

python">def fitness_function(self, x: float) -> float:"""适应度函数: f(x) = x·sin(10πx) + 2.0"""return x * np.sin(10 * np.pi * x) + 2.0

这个设计考虑了:

  1. 评价标准

    • 直接用目标函数作为适应度
    • 值越大表示解越好
    • 类比:就像给餐馆打分,分数越高越好
  2. 计算效率

    • 计算简单快速
    • 能够清晰区分解的好坏
    • 类比:评分标准要简单明确

5.5 选择机制

选择操作模拟"适者生存":

python">def roulette_selection(self, population: np.ndarray, fitness: np.ndarray):"""轮盘赌选择"""fitness = fitness - np.min(fitness) + 1e-6probs = fitness / fitness.sum()selected_idx = np.random.choice(self.pop_size,size=self.pop_size,p=probs)return population[selected_idx]

为什么用轮盘赌选择?

  1. 公平性

    • 适应度越高,被选中的概率越大
    • 但较差的个体仍有机会被选中
    • 类比:就像转轮盘,好的解占的格子多,但其他解仍有机会
  2. 多样性

    • 保留了一定的随机性
    • 防止过早收敛到局部最优
    • 类比:不能只选最好的餐馆,偶尔也要尝试新餐馆

5.6 交叉操作

交叉操作创造新的解:

python">def crossover(self, parent1: np.ndarray, parent2: np.ndarray):"""单点交叉"""if np.random.random() < self.crossover_rate:point = np.random.randint(1, self.chromosome_length)child1 = np.concatenate([parent1[:point], parent2[point:]])child2 = np.concatenate([parent2[:point], parent1[point:]])return child1, child2return parent1.copy(), parent2.copy()

这个设计的巧妙之处:

  1. 信息交换

    • 两个父代解交换部分信息
    • 产生新的可能更好的解
    • 类比:就像把两家餐馆的优点结合起来
  2. 概率控制

    • 不是每次都交叉
    • 保留一部分原有的好的解
    • 类比:保留一些已经很好的餐馆,不要都改变

5.7 变异操作

变异带来新的可能:

python">def mutation(self, chromosome: np.ndarray):"""位变异"""for i in range(self.chromosome_length):if np.random.random() < self.mutation_rate:chromosome[i] = 1 - chromosome[i]return chromosome

变异的重要性:

  1. 探索新空间

    • 随机改变一些基因
    • 可能发现更好的解
    • 类比:偶尔去一个完全没去过的餐馆
  2. 防止早熟

    • 维持种群多样性
    • 防止陷入局部最优
    • 类比:不能总是去同一家餐馆

5.8 进化过程

整个进化过程是这样工作的:

  1. 初始化

    • 随机生成一组解
    • 类比:随机选择一些餐馆开始尝试
  2. 迭代进化

    • 评价当前解的好坏
    • 选择好的解
    • 通过交叉产生新解
    • 偶尔发生变异
    • 类比:不断尝试新餐馆,记住好餐馆,组合好餐馆的特点
  3. 终止条件

    • 达到最大代数
    • 找到足够好的解
    • 类比:在规定时间内找到满意的餐馆

5.9 结果分析

最后,我们通过可视化来分析结果:

  1. 适应度曲线

    • 显示进化过程
    • 反映算法效果
    • 类比:看看找到的餐馆质量是否在不断提升
  2. 最优解展示

    • 直观显示找到的最好的解
    • 便于理解和评价结果
    • 类比:最终推荐的最佳餐馆

6. 结果展示与分析

6.1 运行结果

算法找到的最优解:

  • 最优解: x = 1.850629
  • 最优值: f(x) = 3.850268

6.2 适应度进化曲线分析

在这里插入图片描述

从适应度历史曲线可以观察到以下特点:

  1. 快速收敛阶段(0-25代)

    • 平均适应度从2.0快速上升到3.3左右
    • 最优适应度在前20代就达到了接近3.85的水平
    • 表明算法在初期具有很强的探索能力
  2. 稳定优化阶段(25-200代)

    • 最优适应度保持在3.85左右稳定
    • 平均适应度在3.5-3.7之间波动
    • 说明种群维持了良好的多样性
  3. 收敛特征

    • 最优解在早期就被找到
    • 后期主要是种群整体的优化
    • 没有出现明显的早熟收敛现象

6.3 优化结果可视化分析

在这里插入图片描述

目标函数f(x) = x·sin(10πx) + 2.0在区间[-1, 2]上:

  1. 函数特征

    • 高度非线性
    • 存在多个局部最优解
    • 在x接近2处有全局最优解
  2. 算法表现

    • 成功找到了靠近全局最优解的位置(x ≈ 1.85)
    • 避开了多个局部最优解
    • 红星标记处确实是函数的最高点之一
  3. 解的质量

    • 理论最优解应该在x = 1.85附近
    • 算法找到的解与理论值非常接近
    • 优化结果令人满意

6.4 算法性能评估

  1. 收敛速度

    • 在前25代内就基本收敛
    • 收敛速度较快
    • 计算效率较高
  2. 解的稳定性

    • 最优解在进化后期保持稳定
    • 平均适应度波动在合理范围内
    • 说明算法具有良好的稳定性
  3. 全局搜索能力

    • 成功跳出局部最优
    • 找到了全局最优解区域
    • 展现了良好的全局搜索能力

http://www.ppmy.cn/embedded/151370.html

相关文章

HTML——26.像素单位

<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>像素</title></head><body><!--像素&#xff1a;1.指设备屏幕上的一个点&#xff0c;单位px&#xff0c;如led屏上的小灯朱2.当屏幕分辨率固定时&…

用Tkinter制作一个用于合并PDF文件的小程序

需要安装PyPDF2库&#xff0c;具体原代码如下&#xff1a; # -*- coding: utf-8 -*- """ Created on Sun Dec 29 14:44:20 2024author: YBK """import PyPDF2 import os import tkinter as tk import windndpdf_files [] def dragged_files(f…

Unity 实现Canvas显示3D物体

新建一个UI相机&#xff0c;选择渲染层为UI 将主相机的渲染层去掉UI层 、 将Canvas的RenderMode设置为Screen Space - Camera,将RenderCamera设置为UI相机 新建3D物体的UI父物体&#xff0c;并将3D物体的层级设置为UI层 适当的放缩3DObjParent&#xff0c;让3D物体能显示出来…

MYSQL---------支持数据类型

数值类型 整数类型 TINYINT&#xff1a;通常用于存储小范围的整数&#xff0c;范围是-128到127或0到255&#xff08;无符号&#xff09;。例如&#xff0c;存储年龄可以使用TINYINT类型。示例&#xff1a;CREATE TABLE users (age TINYINT);SMALLINT&#xff1a;范围比TINYINT…

BP神经网络的反向传播算法

BP神经网络&#xff08;Backpropagation Neural Network&#xff09;是一种常用的多层前馈神经网络&#xff0c;通过反向传播算法进行训练。反向传播算法的核心思想是通过计算损失函数对每个权重的偏导数&#xff0c;从而调整权重&#xff0c;使得网络的预测输出与真实输出之间…

如何免费解锁 IPhone 网络

您是否担心 iPhone 上的网络锁定&#xff1f;如果您的 iPhone 被锁定到特定运营商&#xff0c;解锁它可以连接到不同的运营商。好吧&#xff0c;我们为您准备了一份指南。 iPhone运营商免费解锁将是小菜一碟。在我们的解锁运营商 iphone 免费指南中。我们为您提供了一份简介&am…

Pycharm访问 Redis 数据库

安装redis-py模块 &#xff1a; pip3 install redis -i https://pypi.tuna.tsinghua.edu.cn/simple some-package 1. 创建连接 创建连接对象与线程池对象&#xff0c;最后封装连接池&#xff1a; import redis try:pool redis.ConnectionPool(hostlocalhost,port6379,#passw…

python 给钉钉发消息简易demo

# dingtalk_robot 发送消息 def dingtalk_robot(webhook, secret, message):dogBOSS DingtalkChatbot(webhook, secret)now_time datetime.now().strftime(%Y.%m.%d %H:%M:%S)dogBOSS.send_markdown(titlef刀具检测app提醒,textf### **刀具检测参数**\n{message}\n\n**发送时…