jMetal使用教程(一)

转自:https://my.oschina.net/u/3534184/blog/918928

最近发现jMetal框架更新到了5.2,我也把以前写代码顺着框架重新改动一遍,正好整理出来供大家参考。

jMetal是Java实现的一套多目标优化框架,只需要很少量的改动就能定制自己的算法。关键是开源包里包括了几乎所有常用的算法,大大方便了懒人群体。

关于项目的其他信息,请移步:https://jmetal.github.io/jMetal/,github:https://github.com/jMetal/jMetal

下载使用

jMetal的5.0版本以后终于maven化了

maven依赖:


   
   
  1. <!-- https://mvnrepository.com/artifact/org.uma.jmetal/jmetal-core -->
  2. <dependency>
  3. <groupId>org.uma.jmetal </groupId>
  4. <artifactId>jmetal-core </artifactId>
  5. <version>5.2 </version>
  6. </dependency>
  7. <!-- https://mvnrepository.com/artifact/org.uma.jmetal/jmetal-algorithm -->
  8. <dependency>
  9. <groupId>org.uma.jmetal </groupId>
  10. <artifactId>jmetal-algorithm </artifactId>
  11. <version>5.2 </version>
  12. </dependency>
  13. <!-- https://mvnrepository.com/artifact/org.uma.jmetal/jmetal-problem -->
  14. <dependency>
  15. <groupId>org.uma.jmetal </groupId>
  16. <artifactId>jmetal-problem </artifactId>
  17. <version>5.2 </version>
  18. </dependency>
  19. <!-- https://mvnrepository.com/artifact/org.uma.jmetal/jmetal-exec -->
  20. <dependency>
  21. <groupId>org.uma.jmetal </groupId>
  22. <artifactId>jmetal-exec </artifactId>
  23. <version>5.2 </version>
  24. </dependency>

如果不使用maven,可以去官网下载对应的jar包添加到工程里面。

jmetal-core是核心包,jmetal-exec是算法配置包。jmetal-algorithm是各类算法的具体实现,而jmetal-problem是各类优化问题包。其中,jmetal-core是必须的,其余三个包视情况而定,建议都添加。

下面先对jMetal的基本框架和主要的类做一个介绍,下一节再贴出一个完整的Demo。

算法框架

UML class diagram of jMetal 5.0 core classes

jmetal-core框架

The Algorithm :

Algorithm是所有优化算法的模板,只定义了两个接口方法run()和getResult()。run()是算法的运行入口,getResult()用于返回结果集,一般就是算法得到的Pareto集。代码如下:


   
   

   
   
  1. package org.uma.jmetal.algorithm;
  2. /**
  3. * Interface representing an algorithm
  4. * @author Antonio J. Nebro
  5. * @version 0.1
  6. * @param <Result> Result
  7. */
  8. public interface Algorithm<Result> extends Runnable {
  9. void run() ;
  10. Result getResult() ;
  11. }

你的自己的算法需要继承的是 AbstractGeneticAlgorithm这个类。作者的意图是写一个多目标优化的通用框架,遗传算法只是其中之一,而AbstractGeneticAlgorithm就是遗传算法的算法模板。

这个AbstractEvolutionaryAlgorithm的代码如下:


   
   

   
   
  1. public abstract class AbstractGeneticAlgorithm<S, Result> extends AbstractEvolutionaryAlgorithm<S, Result> {
  2. protected int maxPopulationSize ;
  3. protected SelectionOperator<List<S>, S> selectionOperator ;
  4. protected CrossoverOperator<S> crossoverOperator ;
  5. protected MutationOperator<S> mutationOperator ;
  6. ...
  7. /**
  8. * Constructor
  9. * @param problem The problem to solve
  10. */
  11. public AbstractGeneticAlgorithm(Problem<S> problem) {
  12. setProblem(problem);
  13. }
  14. /**
  15. * This method implements a default scheme create the initial population of genetic algorithm
  16. * @return
  17. */
  18. protected List<S> createInitialPopulation() {
  19. List<S> population = new ArrayList<>(getMaxPopulationSize());
  20. for ( int i = 0; i < getMaxPopulationSize(); i++) {
  21. S newIndividual = getProblem().createSolution();
  22. population.add(newIndividual);
  23. }
  24. return population;
  25. }
  26. /**
  27. * This method iteratively applies a {@link SelectionOperator} to the population to fill the mating pool population.
  28. *
  29. * @param population
  30. * @return The mating pool population
  31. */
  32. @Override
  33. protected List<S> selection(List<S> population) {
  34. List<S> matingPopulation = new ArrayList<>(population.size());
  35. for ( int i = 0; i < getMaxPopulationSize(); i++) {
  36. S solution = selectionOperator.execute(population);
  37. matingPopulation.add(solution);
  38. }
  39. return matingPopulation;
  40. }
  41. /**
  42. * This methods iteratively applies a {@link CrossoverOperator} a {@link MutationOperator} to the population to
  43. * create the offspring population. The population size must be divisible by the number of parents required
  44. * by the {@link CrossoverOperator}; this way, the needed parents are taken sequentially from the population.
  45. *
  46. * No limits are imposed to the number of solutions returned by the {@link CrossoverOperator}.
  47. *
  48. * @param population
  49. * @return The new created offspring population
  50. */
  51. @Override
  52. protected List<S> reproduction(List<S> population) {
  53. int numberOfParents = crossoverOperator.getNumberOfParents() ;
  54. checkNumberOfParents(population, numberOfParents);
  55. List<S> offspringPopulation = new ArrayList<>(getMaxPopulationSize());
  56. for ( int i = 0; i < getMaxPopulationSize(); i += numberOfParents) {
  57. List<S> parents = new ArrayList<>(numberOfParents);
  58. for ( int j = 0; j < numberOfParents; j++) {
  59. parents.add(population.get(i+j));
  60. }
  61. List<S> offspring = crossoverOperator.execute(parents);
  62. for(S s: offspring){
  63. mutationOperator.execute(s);
  64. offspringPopulation.add(s);
  65. }
  66. }
  67. return offspringPopulation;
  68. }
  69. ...
  70. }

run()函数内部就是整个遗传算法的流程了,一目了然。各个具体的遗传算法只需要继承这个类,对很少的函数进行override就得到了一个完整的算法。

比如官方NSGA-II:


   
   

   
   
  1. public class NSGAII<S extends Solution<?>> extends AbstractGeneticAlgorithm<S, List<S>> {
  2. ...
  3. /**
  4. * Constructor
  5. */
  6. public NSGAII(Problem<S> problem, int maxEvaluations, int populationSize,
  7. CrossoverOperator<S> crossoverOperator, MutationOperator<S> mutationOperator,
  8. SelectionOperator<List<S>, S> selectionOperator, SolutionListEvaluator<S> evaluator) {
  9. super(problem);
  10. this.maxEvaluations = maxEvaluations;
  11. setMaxPopulationSize(populationSize); ;
  12. this.crossoverOperator = crossoverOperator;
  13. this.mutationOperator = mutationOperator;
  14. this.selectionOperator = selectionOperator;
  15. this.evaluator = evaluator;
  16. }
  17. ...
  18. @Override protected List<S> replacement(List<S> population, List<S> offspringPopulation) {
  19. List<S> jointPopulation = new ArrayList<>();
  20. jointPopulation.addAll(population);
  21. jointPopulation.addAll(offspringPopulation);
  22. RankingAndCrowdingSelection<S> rankingAndCrowdingSelection ;
  23. rankingAndCrowdingSelection = new RankingAndCrowdingSelection<S>(getMaxPopulationSize()) ;
  24. return rankingAndCrowdingSelection.execute(jointPopulation) ;
  25. }
  26. ...
  27. }

在构造函数里,你需要提供算法的其他组件(不用担心,这些组件,包里都有现成的):

  1. Problem:要解决的问题类
  2. CrossoverOperator:交叉算子
  3. MutationOperator:变异算子
  4. SelectionOperator:matepool的选择算子
  5. Evaluator:优化函数评估算子
  6. populationSIze:种群大小
  7. maxEvations:最大评估次数(作为停止条件)

AbstractGeneticAlgorithm会在对应的函数中调用这些组件,比如reproduction()会调用crossoverOperator、CrossoverOperator和MutationOperator三个组件,如果你需要自己定义选择、交叉、变异这些操作的话,可以实现对应的接口,然后将这些类作为组件提供给具体的Algorithm。

The Solution:

注意构造函数中的泛型参数S extends Soluiton。Solution是个体定义的接口,每个Solution就是种群中一个个体,Solution需要做的是定义染色体编码方案,记录优化函数值,约束目标值,其他信息等,默认提供了BinarySoluiton、DoubleSolution、IntegerSoluiton等。同样的,如果需要定义新的Solution,你需要继承的是AbstractGenericSolution这个类。


   
   

   
   
  1. public abstract class AbstractGenericSolution<T, P extends Problem<?>> implements Solution<T> {
  2. private double[] objectives;
  3. private List<T> variables;
  4. protected P problem ;
  5. protected double overallConstraintViolationDegree ;
  6. protected int numberOfViolatedConstraints ;
  7. protected Map<Object, Object> attributes ;
  8. protected final JMetalRandom randomGenerator ;
  9. /**
  10. * Constructor
  11. */
  12. protected AbstractGenericSolution(P problem) {
  13. this.problem = problem ;
  14. attributes = new HashMap<>() ;
  15. randomGenerator = JMetalRandom.getInstance() ;
  16. objectives = new double[problem.getNumberOfObjectives()] ;
  17. variables = new ArrayList<>(problem.getNumberOfVariables()) ;
  18. for ( int i = 0; i < problem.getNumberOfVariables(); i++) {
  19. variables.add(i, null) ;
  20. }
  21. }
  22. ...
  23. }

The Problem:

构造函数中稍微难理解一点的就是这个Problem<S>,problem定义了需要解决的问题,比如有几个决策变量,几个优化函数。其中每个个体的优化函数的计算过程就是由Problem.evluate()来实现的,这也是Problem类最重要的部分。举个例子:ZDT1,ZDT是一个经典的多目标优化测试函数集,定义如下:

(两个优化函数,30个决策变量)

 jMetal中ZDT1的代码如下:


   
   

   
   
  1. public class ZDT1 extends AbstractDoubleProblem {
  2. /** Constructor. Creates default instance of problem ZDT1 (30 decision variables) */
  3. public ZDT1() {
  4. this( 30);
  5. }
  6. /**
  7. * Creates a new instance of problem ZDT1.
  8. *
  9. * @param numberOfVariables Number of variables.
  10. */
  11. public ZDT1(Integer numberOfVariables) {
  12. setNumberOfVariables(numberOfVariables);
  13. setNumberOfObjectives( 2);
  14. setName( "ZDT1");
  15. List<Double> lowerLimit = new ArrayList<>(getNumberOfVariables()) ;
  16. List<Double> upperLimit = new ArrayList<>(getNumberOfVariables()) ;
  17. for ( int i = 0; i < getNumberOfVariables(); i++) {
  18. lowerLimit.add( 0.0);
  19. upperLimit.add( 1.0);
  20. }
  21. setLowerLimit(lowerLimit);
  22. setUpperLimit(upperLimit);
  23. }
  24. /** Evaluate() method */
  25. public void evaluate(DoubleSolution solution) {
  26. double[] f = new double[getNumberOfObjectives()];
  27. f[ 0] = solution.getVariableValue( 0);
  28. double g = this.evalG(solution);
  29. double h = this.evalH(f[ 0], g);
  30. f[ 1] = h * g;
  31. solution.setObjective( 0, f[ 0]);
  32. solution.setObjective( 1, f[ 1]);
  33. }
  34. /**
  35. * Returns the value of the ZDT1 function G.
  36. *
  37. * @param solution Solution
  38. */
  39. private double evalG(DoubleSolution solution) {
  40. double g = 0.0;
  41. for ( int i = 1; i < solution.getNumberOfVariables(); i++) {
  42. g += solution.getVariableValue(i);
  43. }
  44. double constant = 9.0 / (solution.getNumberOfVariables() - 1);
  45. g = constant * g;
  46. g = g + 1.0;
  47. return g;
  48. }
  49. /**
  50. * Returns the value of the ZDT1 function H.
  51. *
  52. * @param f First argument of the function H.
  53. * @param g Second argument of the function H.
  54. */
  55. public double evalH(double f, double g) {
  56. double h ;
  57. h = 1.0 - Math.sqrt(f / g);
  58. return h;
  59. }
  60. }

代码很简单。其中AbstractDoubleProblem 是通用的DoubleProblem模板,如果你的问题是Double类型的实现这类就可以了。evaluate()由AbstractEvolutionaryAlgorithm.evaluatePopulation()调用,在你的算法中,如果需要自己定义优化函数,就可以在evaluate()中进行定义。

The Operator:

交叉、变异和选择算子都是Operator接口的实现,这里用SimpleRandomMutation这个类来说明,SimpleRandomMutation实现变量在定义域内变异。


   
   

   
   
  1. public class SimpleRandomMutation implements MutationOperator<DoubleSolution> {
  2. private double mutationProbability ;
  3. private RandomGenerator<Double> randomGenerator ;
  4. ...
  5. /** Execute() method */
  6. @Override
  7. public DoubleSolution execute(DoubleSolution solution) throws JMetalException {
  8. if ( null == solution) {
  9. throw new JMetalException( "Null parameter") ;
  10. }
  11. doMutation(mutationProbability, solution) ;
  12. return solution;
  13. }
  14. /** Implements the mutation operation */
  15. private void doMutation(double probability, DoubleSolution solution) {
  16. for ( int i = 0; i < solution.getNumberOfVariables(); i++) {
  17. if (randomGenerator.getRandomValue() <= probability) {
  18. Double value = solution.getLowerBound(i) +
  19. ((solution.getUpperBound(i) - solution.getLowerBound(i)) * randomGenerator.getRandomValue()) ;
  20. solution.setVariableValue(i, value) ;
  21. }
  22. }
  23. }
  24. }

逻辑非常简单,不过注意在DoubleSolution里面设定好决策变量的定义域大小,如果每个决策变量的定义域不一样的话可以用ArrayDoubleSolution(extends DoubleSolution)这个类。

交叉选择算子和变异算子很类似,可以自己看包里的源码,这里就不做说明了。

结果集

算法运行完后,是通过调用Algorithem.getResult()来得到结果集的,对大部分算法来说也就是Pareto集。NSGA-II中的实现代码如下:


   
   

   
   
  1. @Override public List<S> getResult() {
  2. return getNonDominatedSolutions(getPopulation());
  3. }
  4. protected List<S> getNonDominatedSolutions(List<S> solutionList) {
  5. return SolutionListUtils.getNondominatedSolutions(solutionList);
  6. }

SolutionListUtils类提供了对种群的操作,如对种群按支配关系排序,得到最好最差解等。和4.0版本采用SolutionSet不同的是5.0版本以后采用的是List<Solution>的方式来定义种群。

...............................................................................................................................................................................................................................................................

以上就是整个框架的大致组成了,总结一下:

  1. 实现AbstractGenericSolution,在具体类里面定义编码方案,定义决策变量,定义优化目标函数储存方法;
  2. 实现AbstractDoubleProblem,在具体类中定义需要解决的问题,overide evaluate()来定义优化函数;
  3. 实现各个Operator:选择、交叉、变异;
  4. 实现AbstractEvolutionaryAlgorithm,Operator提供给具体的Algorithm类,并设定参数;
  5. 调用run()跑算法,调用getResult()得到结果集。

上述步骤的具体实现在包里几乎都有,所以应用起来会非常简单。

下一节给出NSGA-II的一个具体例子,免得还是云里雾里的。


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值