转自: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依赖:
-
<!-- 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。
算法框架
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) ;
-
}
-
...
-
}
在构造函数里,你需要提供算法的其他组件(不用担心,这些组件,包里都有现成的):
- Problem:要解决的问题类
- CrossoverOperator:交叉算子
- MutationOperator:变异算子
- SelectionOperator:matepool的选择算子
- Evaluator:优化函数评估算子
- populationSIze:种群大小
- 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>的方式来定义种群。
...............................................................................................................................................................................................................................................................
以上就是整个框架的大致组成了,总结一下:
- 实现AbstractGenericSolution,在具体类里面定义编码方案,定义决策变量,定义优化目标函数储存方法;
- 实现AbstractDoubleProblem,在具体类中定义需要解决的问题,overide evaluate()来定义优化函数;
- 实现各个Operator:选择、交叉、变异;
- 实现AbstractEvolutionaryAlgorithm,Operator提供给具体的Algorithm类,并设定参数;
- 调用run()跑算法,调用getResult()得到结果集。
上述步骤的具体实现在包里几乎都有,所以应用起来会非常简单。
下一节给出NSGA-II的一个具体例子,免得还是云里雾里的。