4 个稳定版本
使用旧的 Rust 2015
2.2.2 | 2021年2月28日 |
---|---|
2.2.0 |
|
2.1.0 |
|
2.0.2 |
|
1.1.0 |
|
#712 在 算法
145 每月下载量
用于 4 crates
135KB
3K SLoC
oxigen
Oxigen 是一个用 Rust 实现的并行遗传算法框架。这个名字来源于 OXI
dación(Rust 翻译成西班牙语)和 GEN
etic 的合并。
每个版本引入的更改可以在 CHANGELOG.md 中找到。
要在主版本之间迁移,请查看迁移指南(MIGRATE.md)。
Oxigen 提供以下功能
- 快速并行遗传算法实现(它在几秒钟内解决了 N=255 的 N 皇后问题)。有关基准测试,请参阅此文件的基准测试部分。
- 可定制变异和选择率,使用内置的常数、线性、二次函数,可根据代数进行定制(您可以通过
MutationRate
和SelectionRate
特性实现自己的函数)。 - 可定制个体年龄不适应度,内置无不适应度、线性、二次不适应度,并可根据个体的代数设置阈值(您可以通过
Age
特性实现自己的年龄函数)。 - 内置累积
Roulette
、Tournaments
和Cup
选择函数(您可以通过Selection
特性实现自己的选择函数)。 - 内置
SingleCrossPoint
、MultiCrossPoint
和UniformCross
交叉函数(您可以通过Crossover
特性实现自己的交叉函数)。 - 许多内置的生存压力函数。您可以通过
SurvivalPressure
特性实现自己的生存压力函数。 - 内置
Niches
的PopulationRefitness
函数。您可以通过PopulationRefitness
特性实现自己的种群重新适应度函数。 SolutionFound
、Generation
和Progress
以及更多内置的停止条件(您可以通过StopCriterion
特性实现自己的停止条件)。Genotype
特性用于定义遗传算法的基因型。任何结构体都可以在以下限制下实现Genotype
特性:- 它有一个
iter
函数,返回一个对其基因的use std::slice::Iter
迭代器。 - 它有一个
into_iter
函数,消耗个体并返回一个对其基因的use std::vec::IntoIter
迭代器。 - 它有一个
from_iter
函数,从迭代器设置基因。 - 它实现了
Display
、Clone
、Send
和Sync
。 - 它有生成随机个体、变异个体、获取个体的
fitness
以及判断个体是否为问题的is_solution
的函数。
- 它有一个
- 个体的适应度被缓存以避免不必要的重新计算(如果您的适应度函数是随机的并且需要在每一代中重新计算适应度,可以使用
.cache_fitness(false)
禁用此功能)。 - 可以将进度统计信息配置为每经过一定数量的代数打印到文件中。
- 可以将种群个体的适应度配置为每经过一定数量的代数打印到文件中。
- 可以将特定的初始个体插入到遗传算法执行中。
- 可以使用上一代种群作为初始种群继续遗传执行。
- 在适应度函数中执行小的遗传算法重新执行是可能的。
可选功能 global_cache
可选功能 global_cache
添加了一个 HashMap
,保存了整个执行过程中每个个体的评估结果。
当每个个体的评估成本很高时,这个缓存非常有用,并且它补充了之前版本中已经存在的基于个体的缓存(如果一个个体已经被评估,除非 cache_fitness
设置为 false
,否则不会重新评估)。换句话说,这个全局缓存保存了与之前评估过的另一个个体相等的新的个体的评估结果。
请注意,全局缓存并不总是更好的选择,因为如果适应度函数成本较低,获取和插入缓存的成本可能比评估本身还要高。同时也要考虑全局缓存增加的 RAM 使用量。
要启用全局缓存,请将 global_cache
功能添加到项目的 Cargo.toml 中,并将 cache_fitness
(默认值为 true
)和 global_cache
(当启用 global_cache
时默认为 true
)属性设置为 true
)添加到您的 GeneticExecution
。Cargo.toml 示例:
[dependencies]
oxigen = { version="2.1", features=["global_cache"] }
用法
在您的 Cargo.toml
文件中添加 oxigen
依赖。Oxigen 遵循 semver 规范进行版本命名,因此主版本变更永远不会破坏现有的 API,并且应始终使用最新版本。如果需要指定最小版本,请指定该次要版本以包含该版本及其所有更高版本的次要版本。
[dependencies]
oxigen = "2"
要使用 oxigen
,请使用 use oxigen::prelude::*
并在 GeneticExecution
实例上调用 run
方法,覆盖默认的超参数和函数以满足您的需求。
let n_queens: u8 = std::env::args()
.nth(1)
.expect("Enter a number between 4 and 255 as argument")
.parse()
.expect("Enter a number between 4 and 255 as argument");
let progress_log = File::create("progress.csv").expect("Error creating progress log file");
let population_log = File::create("population.txt").expect("Error creating population log file");
let log2 = (f64::from(n_queens) * 4_f64).log2().ceil();
let population_size = 2_i32.pow(log2 as u32) as usize;
let (solutions, generation, progress) = GeneticExecution::<u8, QueensBoard>::new()
.population_size(population_size)
.genotype_size(n_queens as u8)
.mutation_rate(Box::new(MutationRates::Linear(SlopeParams {
start: f64::from(n_queens) / (8_f64 + 2_f64 * log2) / 100_f64,
bound: 0.005,
coefficient: -0.0002,
})))
.selection_rate(Box::new(SelectionRates::Linear(SlopeParams {
start: log2 - 2_f64,
bound: log2 / 1.5,
coefficient: -0.0005,
})))
.select_function(Box::new(SelectionFunctions::Cup))
.age_function(Box::new(AgeFunctions::Quadratic(
AgeThreshold(50),
AgeSlope(1_f64),
)))
.progress_log(20, progress_log)
.population_log(2000, population_log)
.run();
要查看完整示例,请访问 nqueens-oxigen 示例。
要获取更多信息,请访问 文档。
恢复之前的执行
从版本 1.1.0 开始,遗传算法执行返回上一代的人口,并且新的遗传算法执行接受一个初始人口。这允许恢复之前的执行,并使协同进化成为可能,因为可以在适应度函数中启动少量遗传算法的重新执行。
在以下示例中,启动了一个有 10000 代次的执行,并在找到解决方案后以不同的速率恢复它。
let n_queens: u8 = std::env::args()
.nth(1)
.expect("Enter a number between 4 and 255 as argument")
.parse()
.expect("Enter a number between 4 and 255 as argument");
let progress_log = File::create("progress.csv").expect("Error creating progress log file");
let population_log = File::create("population.txt").expect("Error creating population log file");
let log2 = (f64::from(n_queens) * 4_f64).log2().ceil();
let population_size = 2_i32.pow(log2 as u32) as usize;
let (_solutions, _generation, _progress, population) = GeneticExecution::<u8, QueensBoard>::new()
.population_size(population_size)
.genotype_size(n_queens as u8)
.mutation_rate(Box::new(MutationRates::Linear(SlopeParams {
start: f64::from(n_queens) / (8_f64 + 2_f64 * log2) / 100_f64,
bound: 0.005,
coefficient: -0.0002,
})))
.selection_rate(Box::new(SelectionRates::Linear(SlopeParams {
start: log2 - 2_f64,
bound: log2 / 1.5,
coefficient: -0.0005,
})))
.select_function(Box::new(SelectionFunctions::Cup))
.age_function(Box::new(AgeFunctions::Quadratic(
AgeThreshold(50),
AgeSlope(1_f64),
)))
.stop_criterion(Box::new(StopCriteria::Generation(10000)))
.run();
let (solutions, generation, progress, _population) = GeneticExecution::<u8, QueensBoard>::new()
.population_size(population_size)
.genotype_size(n_queens as u8)
.mutation_rate(Box::new(MutationRates::Linear(SlopeParams {
start: f64::from(n_queens) / (8_f64 + 4_f64 * log2) / 100_f64,
bound: 0.005,
coefficient: -0.0002,
})))
.selection_rate(Box::new(SelectionRates::Linear(SlopeParams {
start: log2 - 4_f64,
bound: log2 / 1.5,
coefficient: -0.0005,
})))
.select_function(Box::new(SelectionFunctions::Cup))
.age_function(Box::new(AgeFunctions::Quadratic(
AgeThreshold(50),
AgeSlope(1_f64),
)))
.population(population)
.progress_log(20, progress_log)
.population_log(2000, population_log)
.run();
构建
要构建 oxigen,使用 cargo
如同任何 Rust 项目一样
cargo build
以调试模式构建。cargo build --release
以优化方式构建。
由于 cargo 工作区中存在一个错误,从根目录构建时会出现错误(请参阅 #12,#19),但每个项目内部的构建工作无故障。
要运行基准测试,您需要一个 nightly Rust 编译器。从 lib.rs
中取消注释以下行 // #![feature(test)]
和 // mod benchmarks;
,然后可以使用 cargo bench --jobs 1 --all-features
运行基准测试。
基准测试
以下基准测试已创建,用于衡量遗传算法函数的性能
running 29 tests
test benchmarks::bench_cross_multi_point_255inds ... bench: 895,332 ns/iter (+/- 34,409)
test benchmarks::bench_cross_single_point_255inds ... bench: 227,517 ns/iter (+/- 4,802)
test benchmarks::bench_cross_uniform_255inds ... bench: 304,957 ns/iter (+/- 9,421)
test benchmarks::bench_distance_255 ... bench: 41,669 ns/iter (+/- 45)
test benchmarks::bench_fitness_1024inds ... bench: 14,260 ns/iter (+/- 3,789)
test benchmarks::bench_fitness_age_1024inds ... bench: 32,495 ns/iter (+/- 5,705)
test benchmarks::bench_fitness_age_not_cached_1024inds ... bench: 581,263 ns/iter (+/- 3,988)
test benchmarks::bench_fitness_global_cache_1024inds ... bench: 343,314 ns/iter (+/- 25,763)
test benchmarks::bench_fitness_not_cached_1024inds ... bench: 554,870 ns/iter (+/- 32,916)
test benchmarks::bench_generation_run_tournaments_1024inds ... bench: 4,202,844 ns/iter (+/- 111,604)
test benchmarks::bench_get_fitnesses_1024inds ... bench: 777 ns/iter (+/- 17)
test benchmarks::bench_get_solutions_1024inds ... bench: 2,126 ns/iter (+/- 7)
test benchmarks::bench_mutation_1024inds ... bench: 1,553,265 ns/iter (+/- 23,022)
test benchmarks::bench_refitness_niches_1024inds ... bench: 29,616 ns/iter (+/- 783)
test benchmarks::bench_refitness_none_1024inds ... bench: 29,756 ns/iter (+/- 3,576)
test benchmarks::bench_selection_cup_255inds ... bench: 357,611 ns/iter (+/- 37,254)
test benchmarks::bench_selection_roulette_256inds ... bench: 141,654 ns/iter (+/- 1,338)
test benchmarks::bench_selection_tournaments_256inds ... bench: 616,907 ns/iter (+/- 50,645)
test benchmarks::bench_survival_pressure_children_fight_most_similar_255inds ... bench: 17,748,382 ns/iter (+/- 762,602)
test benchmarks::bench_survival_pressure_children_fight_parents_255inds ... bench: 139,405 ns/iter (+/- 2,267)
test benchmarks::bench_survival_pressure_children_replace_most_similar_255inds ... bench: 17,716,416 ns/iter (+/- 739,662)
test benchmarks::bench_survival_pressure_children_replace_parents_255inds ... bench: 202,788 ns/iter (+/- 18,250)
test benchmarks::bench_survival_pressure_children_replace_parents_and_the_rest_most_similar_255inds ... bench: 1,387,504,266 ns/iter (+/- 45,914,604)
test benchmarks::bench_survival_pressure_children_replace_parents_and_the_rest_random_most_similar_255inds ... bench: 9,389,378 ns/iter (+/- 1,224,136)
test benchmarks::bench_survival_pressure_competitive_overpopulation_255inds ... bench: 12,803,024 ns/iter (+/- 1,946,079)
test benchmarks::bench_survival_pressure_deterministic_overpopulation_255inds ... bench: 220,667 ns/iter (+/- 2,790)
test benchmarks::bench_survival_pressure_overpopulation_255inds ... bench: 12,243,512 ns/iter (+/- 726,154)
test benchmarks::bench_survival_pressure_worst_255inds ... bench: 20,339 ns/iter (+/- 1,113)
test benchmarks::bench_update_progress_1024inds ... bench: 7,595 ns/iter (+/- 378)
这些基准测试在 Intel Core i7 6700K(16 GB DDR4 和 1024 GB 三星 970 Evo Plus NVMe SSD,格式为 ext4)上执行,在 Fedora 33 中使用 Linux 内核 5.9.16。
不同适应度基准测试之间的性能差异有以下解释
bench_fitness
测量了缓存执行的性能,而无需在每次基准迭代后清理适应度。在之前的 README 版本中未执行此清理,因此它更高。bench_mutation
在之前的 README 版本中非常快,因为基准测试中存在一个错误(空人口)。bench_fitness_age
测量了在所有基准迭代中缓存适应度的性能,因此它稍微慢一些。- 未缓存的基准测试测量了未缓存的执行性能,在最后一种情况下,个体为 1 代,因此性能相似,但必须应用年龄不适应度的基准测试会稍微慢一些。
children_fight_most_similar
和children_replace_most_similar
函数需要调用距离函数c * p
次,其中c
是子代数量,p
是种群大小(基准测试中分别为255和1024)。overpopulation
和competitive_overpopulation
函数与children_replace_most_similar
和children_fight_most_similar
类似,但它们只与种群中的m
个个体进行比较(m
大于子代数量,小于种群大小,基准测试中为768)。因此,与children_replace_most_similar
和children_fight_most_similar
相比,这些基准测试中有3/4的比较是在这些函数中完成的。children_replace_parents_and_the_rest_random_most_similar
与children_replace_parents
类似,但在之后,随机选择个体与种群中最相似的个体进行战斗,直到种群大小恢复到原始种群大小。这意味着在每一代中,根据重复的父母,在0到254次之间进行随机选择和距离计算。children_replace_parents_and_the_rest_most_similar
与之前的函数类似,但它搜索种群中最相似个体的配对,这意味着 p2 次距离函数调用(基准测试中为 220)。
贡献
贡献绝对是受欢迎和鼓励的!贡献可以有多种形式。你可以
我们的目标是保持 Oxigen 代码质量处于最高水平。这意味着你贡献的任何代码都必须是
- 注释:公共项目必须有注释。
- 文档:公开的项目必须有 rustdoc 注释,如果有适用示例的话。
- 风格:尽可能使用
rustfmt
格式化你的代码。 - 简单:你的代码应该尽可能简单和地道地完成任务。
- 测试:当可能时,你应该添加(并通过)任何你添加的功能的令人信服的测试。
- 专注:你的代码应该只做它应该做的事情,不做其他。
请注意,除非你明确声明,否则你提交给 Oxigen 的任何有意为之的贡献都将根据 Mozilla Public License 2.0 许可。
参考
Pozo, Martín "Oxigen: 用 Rust 编写的快速、并行、可扩展和适应性强的高性能遗传算法框架"。
Bibtex
@misc{
title={Oxigen: Fast, parallel, extensible and adaptable genetic algorithms framework written in Rust},
author={Pozo, Martín},
howpublised = "\url{https://github.com/Martin1887/oxigen}"
}
许可
Oxigen 根据 Mozilla Public License 2.0 许可。
依赖
~2MB
~36K SLoC