遗传算法

介绍遗传算法之前,先将与遗传相关的生物学概念和计算机中的数据结构对应起来,方便之后进一步进行建模

  • 基因 (Gene):问题的一个参数或变量。比如一个浮点数 3.14,或者一个布尔值 true
  • 染色体/个体 (Chromosome/Individual):问题的一个可能解。通常由一串基因组成,比如一个数组 [1.2, 3.4, 5.6]。个体数组中元素的类型由其中每个基因的类型决定
  • 种群 (Population):一组候选解的集合。每次迭代时维护的多个个体的集合。比如一个多维数组 [[1.2, 3.4, 5.6], [1.1, 3.3, 5.2], [1.0, 3.0, 5.0]]
  • 适应度 (Fitness):一个评估函数 (Objective Function) 的返回值。用来衡量这个解有多“好”。适应度越高,个体存活并繁衍后代的概率就越大

遗传算法流程

flowchart
  1. 初始化种群:随机生成population_size个个体 (Initialize Population)
  2. 评估适应度:对种群中的每个个体进行适应度评估 (Evaluate Fitness)
  3. 选择:根据适应度从当前种群中挑选出优秀个体作为父母,适应度高个体更大概率被选入成为父母 (Select)
  4. 交叉:被选中的父母的基因进行重新组合生成新的孩子 (Crossover)
  5. 变异:随机改变生成的孩子个体上的某些基因 (Mutate)
  6. 停止:如果满足以下三个条件之一,算法停止并输出当前最好的个体;否则,用新生成的孩子替换老一代种群,回到第 2 步继续循环)(Stop)
    1. 达到了预设的最大迭代次数
    2. 找到了满足预期适应度的解
    3. 种群连续若干代最优解不再提升

Rust设计遗传算法

INFO

完整代码参考基于Rust重写的遗传算法库genetic-algorithm-rust(目前还在完善...)

使用Rust对遗传算法中的使用的数据进行自底向上式的分层建模,最底层是基因类;上面一层是个体类(具有适应度属性);最顶层是种群类。可以使用Rust对上面三种数据类型进行建模

layer
rust
#[derive(Debug, Clone, PartialEq)]
/// 单个基因的真实值表示
pub enum GeneValue {
    Isize(isize),
    I8(i8),
    I16(i16),
    I32(i32),
    I64(i64),
    Usize(usize),
    U8(u8),
    U16(u16),
    U32(u32),
    U64(u64),
    F32(f32),
    F64(f64),
}
rust
#[derive(Debug, Clone, PartialEq)]
pub struct Individual {
    /// 个体的基因序列,代表一组待优化的变量
    pub genes: Vec<GeneValue>,
    /// 个体的适应度评分。`None` 表示该个体尚未被评估或基因已发生更改
    pub fitness: Option<f64>,
}

impl Individual {
    pub fn new(genes: Vec<GeneValue>) -> Self {
        Self {
            genes,
            fitness: None,
        }
    }
}
rust
#[derive(Debug, Clone)]
pub struct Population {
    /// 构成当前种群的个体列表
    pub individuals: Vec<Individual>,
}

impl Population {
    pub fn new(individuals: Vec<Individual>) -> Self {
        Self { individuals }
    }
}

下一步基于 有状态系统(Stateful System) 的设计范式,对遗传算法执行引擎进行架构设计。首先,核心结构体 GeneticAlgorithm 用于表示一个“运行中的遗传算法系统”,因此其设计需要同时包含两类要素: 构造输入(Constructor Input)运行状态(Runtime State)

engine

在构造阶段,应遵循最小构造原则(Minimal Constructor Principle),即构造函数仅接收那些“不可推导、不可默认”的外部输入。对于遗传算法而言,这些必要输入包括:配置参数 config(定义算法行为)和适应度函数 fitness_fn(定义优化目标)

而其他组件(如 populationgenerationstatsrng)属于运行状态,可以根据配置进行初始化,并在算法执行过程中不断演化与更新,因此不应作为构造函数的输入参数。

rust
pub struct GeneticAlgorithm<F>
where
    F: Fn(&[GeneValue]) -> f64 + Sync,
{
    /// 遗传算法的运行配置参数
    pub config: GaConfig,
    /// 适应度评估函数(闭包)。用于计算每个个体的优劣程度
    pub fitness_fn: F,
    /// 当前代的种群状态
    pub population: Population,
    /// 当前已演化的代数(从 `0` 开始计数)
    pub generation: usize,
    /// 算法运行过程中的统计信息(如历代最优适应度、平均适应度等)
    pub stats: RunStats,
    /// 内部使用的标准随机数生成器
    rng: StdRng,
}

impl<F> GeneticAlgorithm<F> 
{
	pub fn new(config: GaConfig, fitness_fn: F) -> Result<Self, GaError> {
		config.validate()?;
		let rng = build_rng(config.random_seed);
	
		Ok(Self {
			config,
			fitness_fn,
			population: Population::empty(),
			generation: 0,
			stats: RunStats::default(),
			rng,
		})
	}	
}
rust
// 在遗传算法执行引擎里实现遗传算法流程
impl<F> GeneticAlgorithm<F>
where
    F: Fn(&[GeneValue]) -> f64 + Sync,
{
	/// 随机初始化初代种群(第 0 代)
	///
    /// 新生成的个体默认没有适应度(`fitness` 为 `None`)
	pub fn initialize_population(&mut self) -> Result<(), GaError>{}
	
	/// 对当前种群中的所有个体执行适应度评估
    ///
    /// 该方法会并行遍历 `population` 中的每一个个体,将其基因序列提取并传递给
    /// 用户定义的 `fitness_fn`,计算结果会被写回个体的 `fitness` 字段中
	pub fn evaluate_population(&mut self){}
	
	/// 根据配置的选择策略,从当前种群中挑选父代
    ///
    /// 返回的父代列表将被完全克隆,准备用于后续的交叉繁育
	pub fn select_parents(&mut self) -> Vec<Individual>{}
	
	/// 对选出的父代执行交叉操作,生成子代
	pub fn crossover(&mut self, parents: &[Individual]) -> Vec<Individual>{}
	
	/// 对交叉生成的子代群体执行变异操作
    ///
    /// 变异是原地(in-place)发生的。如果个体的基因发生了变异,其适应度将被重置为 `None`
    pub fn mutate(&mut self, offspring: &mut [Individual]){}
	
	/// 步进执行一个完整的世代演化(Generation)
    ///
    /// 此方法封装了遗传算法单次迭代的核心流水线:
    /// 1. 选择
    /// 2. 交叉
    /// 3. 变异
    /// 4. 评估种群个体
	pub fn next_generation(&mut self) -> Result<(), GaError> {
        let parents = self.select_parents();
        let mut offspring = self.crossover(&parents);
        self.mutate(&mut offspring);

        let mut next_individuals = Vec::new();
        next_individuals.extend(offspring);

        self.population = Population::new(next_individuals);
        self.evaluate_population();
        self.stats.record(&self.population)?;
        Ok(())
    }	
    
    /// 启动并自动运行遗传算法,直到满足停止条件
	pub fn run(&mut self) -> Result<&RunStats, GaError> {
		self.initialize_population()?;
		self.evaluate_population();
		// 记录每一代的种群状态信息
		self.stats.record(&self.population)?;

		while !stop::should_stop(
			// 内部函数参数帮助判断是否应该停止遗传算法
		) {
			self.next_generation()?;
			self.generation += 1;
		}
		Ok(&self.stats)
	}
}

部分Rust技术栈

  • 不可变变量的借用&和可变变量的借用&mut,在不转移所有权的基础上是否修改借用变量的值:比如对种群中每个个体计算适应度时传入参数应该使用不可变借用,对种群进行交叉变异操作传入参数应该使用可变借用
  • 利用编译器为类型自动实现接口(auto impl)#[derive(trait_1, trait_2, ...)],比如为基因类、个体类和种群类自动实现接口
  • 利用函数接口Fn实现要优化的目标函数的调用...