jMetal使用教程(一)
博客专区 > mseeds 的博客 > 博客详情
jMetal使用教程(一)
mseeds 发表于10个月前
jMetal使用教程(一)
  • 发表于 10个月前
  • 阅读 150
  • 收藏 0
  • 点赞 0
  • 评论 0

【腾讯云】买域名送云解析+SSL证书+建站!>>>   

摘要: 一个不错的Java开源多目标优化框架

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

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

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

下载使用

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

maven依赖:

<!-- https://mvnrepository.com/artifact/org.uma.jmetal/jmetal-core -->
<dependency>
    <groupId>org.uma.jmetal</groupId>
    <artifactId>jmetal-core</artifactId>
    <version>5.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.uma.jmetal/jmetal-algorithm -->
<dependency>
    <groupId>org.uma.jmetal</groupId>
    <artifactId>jmetal-algorithm</artifactId>
    <version>5.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.uma.jmetal/jmetal-problem -->
<dependency>
    <groupId>org.uma.jmetal</groupId>
    <artifactId>jmetal-problem</artifactId>
    <version>5.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.uma.jmetal/jmetal-exec -->
<dependency>
    <groupId>org.uma.jmetal</groupId>
    <artifactId>jmetal-exec</artifactId>
    <version>5.2</version>
</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集。代码如下:

package org.uma.jmetal.algorithm;

/**
 * Interface representing an algorithm
 * @author Antonio J. Nebro
 * @version 0.1
 * @param <Result> Result
 */
public interface Algorithm<Result> extends Runnable {
  void run() ;
  Result getResult() ;
}

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

这个AbstractEvolutionaryAlgorithm的代码如下:

public abstract class AbstractGeneticAlgorithm<S, Result> extends AbstractEvolutionaryAlgorithm<S, Result> {
  protected int maxPopulationSize ;
  protected SelectionOperator<List<S>, S> selectionOperator ;
  protected CrossoverOperator<S> crossoverOperator ;
  protected MutationOperator<S> mutationOperator ;
...
  /**
   * Constructor
   * @param problem The problem to solve
   */
  public AbstractGeneticAlgorithm(Problem<S> problem) {
    setProblem(problem);
  }

  /**
   * This method implements a default scheme create the initial population of genetic algorithm
   * @return
   */
  protected List<S> createInitialPopulation() {
    List<S> population = new ArrayList<>(getMaxPopulationSize());
    for (int i = 0; i < getMaxPopulationSize(); i++) {
      S newIndividual = getProblem().createSolution();
      population.add(newIndividual);
    }
    return population;
  }

  /**
   * This method iteratively applies a {@link SelectionOperator} to the population to fill the mating pool population.
   *
   * @param population
   * @return The mating pool population
   */
  @Override
  protected List<S> selection(List<S> population) {
    List<S> matingPopulation = new ArrayList<>(population.size());
    for (int i = 0; i < getMaxPopulationSize(); i++) {
      S solution = selectionOperator.execute(population);
      matingPopulation.add(solution);
    }

    return matingPopulation;
  }
   /**
   * This methods iteratively applies a {@link CrossoverOperator} a  {@link MutationOperator} to the population to
   * create the offspring population. The population size must be divisible by the number of parents required
   * by the {@link CrossoverOperator}; this way, the needed parents are taken sequentially from the population.
   *
   * No limits are imposed to the number of solutions returned by the {@link CrossoverOperator}.
   *
   * @param population
   * @return The new created offspring population
   */
  @Override
  protected List<S> reproduction(List<S> population) {
    int numberOfParents = crossoverOperator.getNumberOfParents() ;

    checkNumberOfParents(population, numberOfParents);

    List<S> offspringPopulation = new ArrayList<>(getMaxPopulationSize());
    for (int i = 0; i < getMaxPopulationSize(); i += numberOfParents) {
      List<S> parents = new ArrayList<>(numberOfParents);
      for (int j = 0; j < numberOfParents; j++) {
        parents.add(population.get(i+j));
      }

      List<S> offspring = crossoverOperator.execute(parents);

      for(S s: offspring){
        mutationOperator.execute(s);
        offspringPopulation.add(s);
      }
    }
    return offspringPopulation;
  }
...
}

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

比如官方NSGA-II:

public class NSGAII<S extends Solution<?>> extends AbstractGeneticAlgorithm<S, List<S>> {
  ...
/**
   * Constructor
   */
  public NSGAII(Problem<S> problem, int maxEvaluations, int populationSize,
      CrossoverOperator<S> crossoverOperator, MutationOperator<S> mutationOperator,
      SelectionOperator<List<S>, S> selectionOperator, SolutionListEvaluator<S> evaluator) {
    super(problem);
    this.maxEvaluations = maxEvaluations;
    setMaxPopulationSize(populationSize); ;

    this.crossoverOperator = crossoverOperator;
    this.mutationOperator = mutationOperator;
    this.selectionOperator = selectionOperator;

    this.evaluator = evaluator;
  }
  ...
 @Override protected List<S> replacement(List<S> population, List<S> offspringPopulation) {
    List<S> jointPopulation = new ArrayList<>();
    jointPopulation.addAll(population);
    jointPopulation.addAll(offspringPopulation);

    RankingAndCrowdingSelection<S> rankingAndCrowdingSelection ;
    rankingAndCrowdingSelection = new RankingAndCrowdingSelection<S>(getMaxPopulationSize()) ;

    return rankingAndCrowdingSelection.execute(jointPopulation) ;
  }
  ...
}

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

  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这个类。

public abstract class AbstractGenericSolution<T, P extends Problem<?>> implements Solution<T> {
  private double[] objectives;
  private List<T> variables;
  protected P problem ;
  protected double overallConstraintViolationDegree ;
  protected int numberOfViolatedConstraints ;
  protected Map<Object, Object> attributes ;
  protected final JMetalRandom randomGenerator ;
/**
   * Constructor
   */
  protected AbstractGenericSolution(P problem) {
    this.problem = problem ;
    attributes = new HashMap<>() ;
    randomGenerator = JMetalRandom.getInstance() ;

    objectives = new double[problem.getNumberOfObjectives()] ;
    variables = new ArrayList<>(problem.getNumberOfVariables()) ;
    for (int i = 0; i < problem.getNumberOfVariables(); i++) {
      variables.add(i, null) ;
    }
  }
  ...
}

The Problem:

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

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

 jMetal中ZDT1的代码如下:

public class ZDT1 extends AbstractDoubleProblem {

  /** Constructor. Creates default instance of problem ZDT1 (30 decision variables) */
  public ZDT1() {
    this(30);
  }

  /**
   * Creates a new instance of problem ZDT1.
   *
   * @param numberOfVariables Number of variables.
   */
  public ZDT1(Integer numberOfVariables) {
    setNumberOfVariables(numberOfVariables);
    setNumberOfObjectives(2);
    setName("ZDT1");

    List<Double> lowerLimit = new ArrayList<>(getNumberOfVariables()) ;
    List<Double> upperLimit = new ArrayList<>(getNumberOfVariables()) ;

    for (int i = 0; i < getNumberOfVariables(); i++) {
      lowerLimit.add(0.0);
      upperLimit.add(1.0);
    }

    setLowerLimit(lowerLimit);
    setUpperLimit(upperLimit);
  }

  /** Evaluate() method */
  public void evaluate(DoubleSolution solution) {
    double[] f = new double[getNumberOfObjectives()];

    f[0] = solution.getVariableValue(0);
    double g = this.evalG(solution);
    double h = this.evalH(f[0], g);
    f[1] = h * g;

    solution.setObjective(0, f[0]);
    solution.setObjective(1, f[1]);
  }

  /**
   * Returns the value of the ZDT1 function G.
   *
   * @param solution Solution
   */
  private double evalG(DoubleSolution solution) {
    double g = 0.0;
    for (int i = 1; i < solution.getNumberOfVariables(); i++) {
      g += solution.getVariableValue(i);
    }
    double constant = 9.0 / (solution.getNumberOfVariables() - 1);
    g = constant * g;
    g = g + 1.0;
    return g;
  }

  /**
   * Returns the value of the ZDT1 function H.
   *
   * @param f First argument of the function H.
   * @param g Second argument of the function H.
   */
  public double evalH(double f, double g) {
    double h ;
    h = 1.0 - Math.sqrt(f / g);
    return h;
  }
}

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

The Operator:

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

public class SimpleRandomMutation implements MutationOperator<DoubleSolution> {
  private double mutationProbability ;
  private RandomGenerator<Double> randomGenerator ;
  
  ...
/** Execute() method */
	@Override
  public DoubleSolution execute(DoubleSolution solution) throws JMetalException {
    if (null == solution) {
      throw new JMetalException("Null parameter") ;
    }

    doMutation(mutationProbability, solution) ;
    
    return solution;
  }

  /** Implements the mutation operation */
	private void doMutation(double probability, DoubleSolution solution) {
    for (int i = 0; i < solution.getNumberOfVariables(); i++) {
      if (randomGenerator.getRandomValue() <= probability) {
      	Double value = solution.getLowerBound(i) +
      			((solution.getUpperBound(i) - solution.getLowerBound(i)) * randomGenerator.getRandomValue()) ;
      	
      	solution.setVariableValue(i, value) ;
      }
    }
	}
}

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

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

结果集

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

 @Override public List<S> getResult() {
    return getNonDominatedSolutions(getPopulation());
  }
 protected List<S> getNonDominatedSolutions(List<S> solutionList) {
    return SolutionListUtils.getNondominatedSolutions(solutionList);
  }

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的一个具体例子,免得还是云里雾里的。

  • 打赏
  • 点赞
  • 收藏
  • 分享
共有 人打赏支持
粉丝 2
博文 6
码字总数 5122
×
mseeds
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: