NSGA-III是在NSGA-II的基础上,为了解决在目标数超越3个乃至更多的情况下,拥挤度指标不再适应的问题,通过引入参考点的概念来解决这个问题。与NSGA-II相比,NSGAIII仍然适配了他的交叉变异算子,快速非支配排序算子,但从快速非支配排序之后,NSGA-III开始与NSGA-II产生了分歧,在NSGA-II中这里应该进行拥挤度划分,但是NSGAIII是开始了相关的参考点计算,下面进行详细解释
for (int i=0;i<s.size();i++){
//首先把帕累托等级给分开
if (front.keySet(). contains ( s.array.get(i).rank)){
front.get(s.array.get(i).rank).add(s.array.get(i));
} else{
front_l=new ArrayList<>();
front_l.add(s.array.get(i));
front.put(s.array.get(i).rank,front_l);
}
}
这里可以使用一个HashMap来实现,根据每一个解的rank值作为key,rank值相同的作为同一个帕累托层,这样就生成了根据帕累托层分辨的种群front
int rankingIndex=1;//表示第几层,因为是从帕累托等级为1开始的,所以,你懂的
int candidateSolutions=0;
while(candidateSolutions<maxsize){
candidateSolutions+=front.get(rankingIndex).size();
if ((newS.size()+front.get(rankingIndex).size()<=maxsize)){
//如果没有溢出就往里面添加
front_l = front.get(rankingIndex);
for (int i = 0 ; i < front_l.size(); i++) {
newS.add(front_l.get(i));
}
}
根据划分好帕累托层的种群向新的种群news中添加个体,直到添加到第l-1层时,newS大小刚好不大于种群数。
ideal-point理想点的含义是所有目标维度上目标值最小的点
List<Double> ideal_point;
int numberOfObjectives=pop.array.get(1).fitness.length;
ideal_point = new ArrayList<>(numberOfObjectives);
for (int f=0; f<numberOfObjectives; f+=1){
double minf = Double.MAX_VALUE;
for (int i=0; i<front.get(1).size(); i+=1) // min values must appear in the first front
{
minf = Math.min(minf, front.get(1).get(i).fitness[f]);
}
ideal_point.add(minf);
}
return ideal_point;
}
ASF函数就是对所有个体在固定一个维度的情况下其他维度上的数同时除以一个极小数,然后取这些个体上的最大值作为ASF函数值
List<NSGAIIIDoubleSolution> extremePoints = new ArrayList<>();
int numberOfObjectives=population.array.get(1).fitness.length;
NSGAIIIDoubleSolution min_indv = null;
for (int f=0; f < numberOfObjectives; f+=1)
{
double min_ASF = Double.MAX_VALUE;
for (NSGAIIIDoubleSolution s : front.get(0)) {
// only consider the individuals in the first front
double asf = ASF(s, f);
if ( asf < min_ASF ) {
min_ASF = asf;
min_indv = s;
}
}
extremePoints.add(min_indv);
}
而额外点就是ASF函数值最小值的个体集合,这个集合也就代表着在种群中最靠近坐标轴的存在
当我们得到了额外点的时候我们便可以计算出解集所在的帕累托平面与坐标轴之间的截距
public List<Double> constructHyperplane(NSGAIIIDoubleSolutionSet population, List<NSGAIIIDoubleSolution> extreme_points) {
// Check whether there are duplicate extreme points.
// This might happen but the original paper does not mention how to deal with it.
int numberOfObjectives=population.array.get(1).fitness.length;
boolean duplicate = false;
for (int i=0; !duplicate && i< extreme_points.size(); i+=1)
{
for (int j=i+1; !duplicate && j<extreme_points.size(); j+=1)
{
duplicate = extreme_points.get(i).equals(extreme_points.get(j));
}
}
List<Double> intercepts = new ArrayList<>();
if (duplicate) // cannot construct the unique hyperplane (this is a casual method to deal with the condition)
{
for (int f=0; f<numberOfObjectives; f+=1)
{
// extreme_points[f] stands for the individual with the largest value of objective f
intercepts.add(extreme_points.get(f).fitness[f]);
}
}
else
{
// Find the equation of the hyperplane
List<Double> b = new ArrayList<>(); //(pop[0].objs().size(), 1.0);
for (int i =0; i < numberOfObjectives;i++)
b.add(1.0);///????这里为什么是1.0不应该是0.0????
List<List<Double>> A=new ArrayList<>();
for (NSGAIIIDoubleSolution s : extreme_points)
{
List<Double> aux = new ArrayList<>();
for (int i = 0; i < numberOfObjectives; i++)
aux.add(s.fitness[i]);
A.add(aux);//A是极端点的极端值的集合的集合
}
List<Double> x = guassianElimination(A, b);
// Find intercepts
for (int f=0; f<numberOfObjectives; f+=1)
{
intercepts.add(1.0/x.get(f));
}
}
return intercepts;
}
public List<Double> guassianElimination(List<List<Double>> A, List<Double> b) {
List<Double> x = new ArrayList<>();
//这里实际上就是高斯消元
int N = A.size();
for (int i=0; i<N; i+=1)
{
A.get(i).add(b.get(i));
}
for (int base=0; base<N-1; base+=1)
{
for (int target=base+1; target<N; target+=1)
{
double ratio = A.get(target).get(base)/A.get(base).get(base);
for (int term=0; term<A.get(base).size(); term+=1)
{
A.get(target).set(term, A.get(target).get(term) - A.get(base).get(term)*ratio);
}
}
}
for (int i = 0; i < N; i++)
x.add(0.0);
for (int i=N-1; i>=0; i-=1)
{
for (int known=i+1; known<N; known+=1)
{
A.get(i).set(N, A.get(i).get(N) - A.get(i).get(known)*x.get(known));
}
x.set(i, A.get(i).get(N)/A.get(i).get(i));
}
return x;
}
当我们得到了理想点,额外点,截距之后,我们便可以进行函数表量化操作,这一步的操作实际上是在为参考点的部署做铺垫,
ai表示截距,fi表示原来的函数值,zmin表示理想点
众所周知,在NSGA-II中引入拥挤距离的概念是为了使解集的分布更加均匀,与之类似,参考点的实现也是为了这种操作,参考点实际上将整个帕累托理想平面划分成了多个等分
H是参考点数,M是目标维数,p是划分为多少份,大括号就是C(多项式?好像是叫这个来着)
这一部分可以使用递归实现
private void generateRecursive(
List<ReferencePoint<S>> referencePoints,
ReferencePoint<S> refPoint,
int numberOfObjectives,
int left,
int total,
int element) {
if (element == (numberOfObjectives - 1)) {
refPoint.position.set(element, (double) left / total);
referencePoints.add(new ReferencePoint<>(refPoint));
} else {
for (int i = 0; i <= left; i += 1) {
refPoint.position.set(element, (double) i / total);
generateRecursive(referencePoints, refPoint, numberOfObjectives, left - i, total, element + 1);
}
}
//return referencePoints;
}
得到相应划分的参考点之后,接下来要做的即为构建参考点向量(参考点到原点的连线,即为一条参考向量)。
做完这一步以后,需要针对每一个种群个体遍历所有向量,找到距离每个种群个体最近的参考点,同时记录下参考点的信息和对应的最短距离。其中,种群个体到参考点向量的距离将用垂直距离来描述
经过非支配排序后,假设从第一个非支配层级到第FL层级的种群成员数目总和第一次超过种群规模N,那么定义St+1为包含了FL中全部个体的集合,由于St+1的规模超过了预先设定的种群成员数目,因此需要进行相应的筛选。筛选的第一步是对每个参考点进行遍历,查看它被不包含FL的St+1引用的次数,并且寻找到被引用次数最少的参考点,也即被数量最少的种群个体所关联的参考点,将其被引用次数记录为pj。
然后就是黑暗前的黎明,成功前的最后一步——分情况讨论:
(1)假如这个参考点关联的种群个体数量为零,也即pj等于零,但在FL中有个体被关联到这个参考点向量,则从中寻找距离最小的点,并将其从FL中抽取,加入到被选择的下一代种群中,设置pj=pj+1
(2)如果在FL中没有个体被引用到该参考点,则删除该参考点向量,倘若pj>0,则从中选择距离最近的参考点直到种群规模为N为止,就大功告成了!
最后一步来源:(我感觉讲的就很明白)
链接:https://www.zhihu.com/question/41365143/answer/105346224
associate(s);
//生成参考向量,计算距离和p
while (s.size() < maxsize)
{
int min_rp = FindNicheReferencePoint();
NSGAIIIDoubleSolution chosen = SelectClusterMember(this.referencePoints.get(min_rp));
if (chosen == null) // no potential member in Fl, disregard this reference point
{
this.referencePoints.remove(min_rp);
}
else
{
this.referencePoints.get(min_rp).AddMember();
this.referencePoints.get(min_rp).RemovePotentialMember(chosen);
s.add(chosen);
}
}
//添加,程序结束
return s;
}
NSGAIIIDoubleSolution SelectClusterMember(ReferencePoint<NSGAIIIDoubleSolution> rp)
{
NSGAIIIDoubleSolution chosen = null;
if (rp.HasPotentialMember())
{
if (rp.MemberSize() == 0) // currently has no member
{
chosen = rp.FindClosestMember();
}
else
{
chosen = rp.RandomMember();
}
}
return chosen;
}
int FindNicheReferencePoint()
{
// find the minimal cluster size
int min_size = Integer.MAX_VALUE;
for (ReferencePoint<NSGAIIIDoubleSolution> referencePoint : this.referencePoints)
min_size = Math.min(min_size,referencePoint.MemberSize());
// find the reference points with the minimal cluster size Jmin
List<Integer> min_rps=new ArrayList<>();
for (int r=0; r<this.referencePoints.size(); r+=1)
{
if (this.referencePoints.get(r).MemberSize() == min_size)
{
min_rps.add(r);
}
}
// return a random reference point (j-bar)
Random random=new Random();
return min_rps.get(min_rps.size() > 1 ? random.nextInt(min_rps.size()-1) :0);
}
public void associate(NSGAIIIDoubleSolutionSet population) {
for (int t = 1; t < front.size(); t++) {
for (NSGAIIIDoubleSolution s : front.get(t)) {
int min_rp = -1;
double min_dist = Double.MAX_VALUE;
for (int r = 0; r < this.referencePoints.size(); r++) {
double d = perpendicularDistance(this.referencePoints.get(r).position, s);
if (d < min_dist) {
min_dist=d;
min_rp = r;
}
}
if (t != front.size()) {
this.referencePoints.get(min_rp).AddMember();
} else {
this.referencePoints.get(min_rp).AddPotentialMember(s, min_dist);
}
}
}
}
public double perpendicularDistance(List<Double> direction, NSGAIIIDoubleSolution point) {
double numerator = 0, denominator = 0;
for (int i=0; i<direction.size(); i+=1)
{
numerator += direction.get(i)*point.fitness[i];
denominator += Math.pow(direction.get(i),2.0);
}
double k = numerator/denominator;
double d = 0;
for (int i=0; i<direction.size(); i+=1)
{
d += Math.pow(k*direction.get(i) - point.fitness[i],2.0);
}
return Math.sqrt(d);
}
ps:代码只是我认为比较重要的部分,如果想仔细了解可参考JMetal,或者是访问菜鸡的GitHub https://github.com/FHhui/bigpro