本篇並非介紹如何從0開始開發遺傳算法框架,反而推薦各位使用已有的GA庫jenetics來做遺傳算法。
GA算法的邏輯還是貼下:

好了,下面介紹的是基於jenetics開發的更貼近業務側的框架,以及使用方法。
pom依賴,畢竟java的嘛,就不要用matlab、R、python這些了
<!-- https://mvnrepository.com/artifact/io.jenetics/jenetics --> <dependency> <groupId>io.jenetics</groupId> <artifactId>jenetics</artifactId> <version>5.1.0</version> </dependency> <dependency> <groupId>io.jenetics</groupId> <artifactId>jenetics.ext</artifactId> <version>5.1.0</version> </dependency>
先看看我們提供的這個框架,能怎樣簡化業務側代碼吧:
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5); //需要指定一個線程池,因為GA算法需要很多運算
Demo1SolutionConverter demo1SolutionConverter = new Demo1SolutionConverter(); //這個class中定義了GA算法所需的底層基因序列的定義、以及業務解決方案class與基因的互相轉換邏輯
Demo1FitnessBuilder fitnessBuilder = new Demo1FitnessBuilder(); //這個class定義了適應度函數的指向(遺傳算法非常依賴適應度函數,非常非常依賴,重中之重)
EngineInvokeOptions options = new EngineInvokeOptions(); //引擎調用默認參數,如需改寫默認參數單獨修改即可
options.setFitnessBuilder(fitnessBuilder);
options.setEnableMaxLimits(true); //設置為停止條件為最大種群次數限制
options.setMaxLimits(20); //設置為最大次數限制為20次,既:20次種群迭代
for(int idx=0;idx<20;idx++) { //連續執行20次看效果
MinMaxScaler minMaxScaler = new MinMaxScaler(100, false);
GAEngine gaEngine = new GAEngine(50, executorService);
Demo1SolutionConverter.Demo1Solution solution = (Demo1SolutionConverter.Demo1Solution) gaEngine.generate(demo1SolutionConverter, minMaxScaler, options);
System.out.println(solution.toString());
}
executorService.shutdown();
}
我們先看看核心class:Demo1SolutionConverter,這個是每個業務都不同的地方,需要單獨業務單獨編碼
public class Demo1SolutionConverter extends DefaultSolutionConverter { //DefaultSolutionConverter是框架里的默認實現,已經實現了很多默認方法 @Override public AbstractSolution convert2Solution(EvolvingSolutionInfo solutionInfo) { Demo1Solution solution=new Demo1Solution(); solution.setFitnessValues(solutionInfo.getFitnessValues()); Genotype<IntegerGene> geneMap=solutionInfo.getMap(); //這個是獲取基因架 Chromosome<IntegerGene> chromosome=geneMap.getChromosome(); //獲取默認的也就是第一個基因條 for(int idx=0;idx<chromosome.length();idx++) //開始業務解碼 { IntegerGene integerGene=chromosome.getGene(idx); if(integerGene.intValue()==1) { solution.getSelectedIds().add(idx); //這里實現的解碼邏輯:共10個數字,有2種可能0和1,0代表不選中,1代表選中,選中了就會加入selectedIds這個List里 } } return solution; } @Override public Genotype<IntegerGene> loadGenotype() { //這個是唯一一個DefaultSolutionConverter種的抽象方法,必須實現,用來定義基因序列的組成 Genotype<IntegerGene> genotype=Genotype.of( IntegerChromosome.of(0, 1, IntRange.of(10)) //10個基因,每個基因只有2種可能,0或者1 ); return genotype; } public double test(AbstractSolution solution) //這個是適應度函數定義,要注意入參和出參,這2個是固定的 { //test是method名,待會會在后面介紹,會在其他class中指定過來 Demo1Solution s=(Demo1Solution)solution; long 偶數個數=s.getSelectedIds().stream().filter(f->f%2==0).count(); long 奇數個數=s.getSelectedIds().stream().filter(f->f%2!=0).count(); double score=(-偶數個數)+(+奇數個數); //score是適應度函數的分值,越小越優,由於我們希望不要奇數、只要偶數,所以偶數部分為負號、奇數部分為正號 return score; } @Data public class Demo1Solution extends DefaultSolution { //這個是業務解碼后存放的class,很好理解 private List<Integer> selectedIds=new ArrayList<>(); @Override public String toString() { StringBuilder stringBuilder=new StringBuilder(); for(int id:selectedIds) { stringBuilder.append(id); stringBuilder.append(", "); } return stringBuilder.toString(); } } }
然后再看看Demo1FitnessBuilder類,既:適應度函數指向類:
public class Demo1FitnessBuilder implements FitnessBuilder {
@Override
public Tuple2<Map<String, Object>, List<Tuple3<String, String, Double>>> build() {
Map<String, Object> objConfigs= buildObjectAndConfigs();
List<Tuple3<String, String, Double>> fitnessConfigs=buildFitnessConfigs();
return Tuples.of(objConfigs, fitnessConfigs);
}
private Map<String, Object> buildObjectAndConfigs() {
Map<String, Object> configs=new HashMap<>();
Demo1SolutionConverter obj=new Demo1SolutionConverter(); //這里定義的是n個適應度函數所在的對象(此處只定義了1個,也可以多個)
configs.put("obj", obj);
return configs;
}
private List<Tuple3<String, String, Double>> buildFitnessConfigs() {
List<Tuple3<String, String, Double>> fitnessConfigs = new ArrayList<>();
fitnessConfigs.add(Tuples.of("obj", "test", 1D)); //obj和上面的對應,test指明了適應度函數是在obj這個對象中方法名為test的函數
//此處也可以多個
//1D代表默認權重,也可以定期較低的權重,比如:0.5D這種
return fitnessConfigs;
}
}
看看程序的輸出:
0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 3, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8,
如同大家所見,GA算法的問題在於不太穩定,只能代表一種優化趨勢,導致這樣的原因有很多,最重要的原因還是在於智能算法一般使用的場景都是由於傳統算法無能為力的場景,或者無法窮舉的場景。
下面看看加大種群迭代次數是否能解決:
options.setMaxLimits(100);
0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8, 0, 2, 4, 6, 8,
算是表面解決了,但其實還有很多因素能導致不穩定,比如,我們從當前的10個數字,enlarge到50個數字,會怎樣:
@Override
public Genotype<IntegerGene> loadGenotype() {
Genotype<IntegerGene> genotype=Genotype.of(
IntegerChromosome.of(0, 1, IntRange.of(50))
);
return genotype;
}
0, 2, 4, 5, 6, 8, 10, 12, 14, 15, 16, 17, 18, 20, 22, 24, 26, 28, 30, 32, 34, 35, 36, 38, 40, 41, 42, 44, 46, 0, 2, 4, 6, 8, 10, 11, 12, 13, 14, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 2, 4, 5, 6, 8, 10, 12, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 6, 8, 10, 12, 14, 16, 18, 22, 24, 26, 28, 30, 32, 34, 35, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 8, 14, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 41, 42, 44, 46, 48, 0, 2, 4, 5, 6, 8, 10, 12, 14, 15, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 47, 48, 49, 0, 2, 4, 6, 8, 10, 12, 13, 14, 16, 18, 20, 22, 24, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 0, 2, 4, 6, 8, 10, 12, 13, 16, 18, 19, 22, 24, 26, 28, 30, 31, 32, 36, 38, 40, 42, 43, 44, 45, 46, 48, 0, 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 21, 22, 24, 25, 26, 28, 30, 32, 34, 38, 40, 42, 44, 45, 46, 48, 0, 1, 2, 4, 5, 6, 8, 10, 11, 12, 14, 16, 18, 20, 21, 22, 23, 24, 26, 28, 30, 32, 36, 38, 40, 42, 44, 46, 48, 2, 4, 6, 8, 10, 12, 14, 16, 17, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 44, 46, 48, 0, 2, 6, 8, 10, 12, 14, 18, 20, 22, 24, 26, 28, 32, 34, 36, 38, 40, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 38, 40, 42, 43, 44, 46, 48, 49, 0, 2, 4, 6, 8, 9, 10, 12, 14, 18, 22, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 45, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 22, 24, 26, 32, 34, 36, 38, 40, 42, 44, 45, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 24, 26, 28, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 13, 14, 16, 18, 20, 22, 24, 26, 28, 31, 32, 34, 38, 40, 42, 45, 46, 48, 0, 2, 4, 6, 8, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 27, 29, 30, 32, 34, 36, 38, 40, 46, 48, 0, 4, 6, 8, 10, 12, 14, 16, 20, 22, 24, 25, 26, 28, 30, 32, 34, 36, 38, 40, 44, 46, 48, 0, 2, 4, 8, 10, 12, 14, 16, 18, 20, 21, 22, 24, 26, 28, 32, 34, 36, 38, 40, 42, 44, 46, 48,
哇塞,簡直了,這結果。。。
再來:
options.setMaxLimits(2000);
0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48,
體會到了不確定的痛苦了嗎?
再來:
// options.setEnableMaxLimits(true);
// options.setMaxLimits(2000);
options.setEnableSteadyFitness(true); //設置為穩定適應度函數值默認
options.setSteadyFitnessValue(1000); //當連續1000次的種群迭代的最優適應度函數值都穩定的時候,穩定就是分值沒有超越當前的最好分值,然后才停止算法
0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48,
遺傳算法是非常耗費算力的,能其他算法就盡量其他算法來做,真的。雖然GA也有很多優點,比如:
- 當無法窮舉時,這個算法就是好的
- 當數據量較少時,這個算法也挺穩定(要調好參數)
- 當計算什么是好,什么是壞時的邏輯是非線性的時候,GA算法也是個很好的選項
完整代碼下載,帶demo+自研框架+底層是jenetics
