简介
多目标优化是优化问题中经常遇到的一类问题,解决这类问题的方法通常有直接求和法、加权求和法、帕累托最优集合法等。前两种方法将多个目标值相加的方法过于简单粗暴,而且不同量纲的量相加之后的实际意义也有待商榷。而帕累托集合就比较好地解决了这一缺点。
多目标优化问题生活中很常见,比如汽车车身零部件设计中,要求设计的零件刚度要很大,同时质量很轻,这就是一个两目标问题,同时他还有一些条件约束,比如模态约束,尺寸约束等。在生产制造过程中,我们希望生产线的产能尽可能高,同时又想要生产的成本尽可能低,最好碳排放还很低。再者金融领域中,我们希望投入的资金少,风险小,并且获得的利益最大,这就是一个三目标问题,但是掰着脚趾头都知道同时达到这三个目标是不可能的,多目标优化就是给出他的一些列可能的选择,成为帕累托最优解集,然后用户自己去评判想选谁,哈哈,是不是很鸡肋。
总而言之,在现实生活中有很多的问题都是由互相冲突和影响的多个目标组成,这些目标不可能同时达到最优的状态,我们通常会尽量让这些目标在一定的区域内达到最佳的状态,这就是多目标优化。
帕累托前沿等概念
定义1:帕累托占优(Pareto Dominate)
也可称为a支配b,如果对于一个决策变量,不存在其他决策变量能够支配他,那么就称该决策变量为非支配解。
定义2:帕累托最优解
如果在整个参数空间内不存在任何一个决策向量帕累托占优某一个决策向量,就称该决策向量是帕累托最优解。所有帕累托最优解组成的集合称为帕累托最优解集合。
定义3:绝对最优解、非劣解、帕累托前沿
在决策变量的集合S中,有一个变量X*,对于任意的X属于S,存在目标函数F(X*)<=F(X),则称X*为目标函数的 绝对最优解
在决策变量的集合S中,有一个变量X-,对于任意的X属于S,存在目标函数F(X)<=F(X-) ,则称X-为目标函数的 最优解(非劣解)
多目标优化问题的非劣解一般不止一个,由所有非劣解构成的集合称为 非劣解集
所有非劣解对应的目标函数构成了非劣最优目标域也就是帕累托前沿。
NSGA-II算法
该算法是引入了帕累托最优集合思想的遗传算法。
快速非支配排序步骤:快速非支配排序就是将解集分解为不同次序的Pareto前沿的过程。
它可以描述为:
- 1.为每个解p分配两个关键量:支配p的解个数n_p以及被p支配的解集S_p;
- 2.设置i=1,将n_p=0的个体归入F_i;
- 3.对于F_i中的个体,遍历每个解p的S_p,将其中每个解的n_p减1;
- 4.i+=1,将n_p=0的解归入F_i;
- 5.重复3、4,直到解集中所有个体都被归入某一个F_i。
python实现快速非支配排序:
# 进行快速非支配排序
def fast_non_dominate_sorting(popsize, chrom):
s, n = {}, {}
front, rank = {}, {}
front[0] = []
iter_range = popsize # 如果不是亲子代混合,就是100+x个需要比较
for p in range(iter_range):
s[p] = []
n[p] = 0
for q in range(iter_range):
if dominate(p, q, chrom): # if p dominates q for three objectives
if q not in s[p]:
s[p].append(q) # s[p] is the set of solutions dominated by p
elif dominate(q, p, chrom): # if q dominates p for three objectives
n[p] = n[p] + 1 # n[p] is the set of solutions dominating p, 3 obj
if n[p] == 0:
rank[p] = 0 # p belongs to front 0
if p not in front[0]:
front[0].append(p)
i = 0
while front[i]:
Q = [] # Used to store the members of the next front
for p in front[i]:
for q in s[p]:
n[q] = n[q] - 1
if n[q] == 0:
rank[q] = i + 1
if q not in Q:
Q.append(q)
i = i + 1
front[i] = Q
del front[len(front) - 1]
return front, rank
计算拥挤距离,避免选出的解扎堆聚集在一起
def crowding_distance_calculation(front, chrom):
if np.array(front).ndim != 1:
distance = {m: 0 for m in front} # {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0}
for o in range(3): # dual objective, we have three obj, so this should be adapted
obj = {m: chrom[m][o] for m in front}
sorted_keys = sorted(obj, key=obj.get)
distance[sorted_keys[0]] = distance[sorted_keys[len(front) - 1]] = 999999999999
for i in range(1, len(front) - 1):
if len(set(obj.values())) == 1:
distance[sorted_keys[i]] = distance[sorted_keys[i]]
else:
distance[sorted_keys[i]] = distance[sorted_keys[i]] + (
obj[sorted_keys[i + 1]] - obj[sorted_keys[i - 1]]) / (
obj[sorted_keys[len(front) - 1]] - obj[sorted_keys[0]])
# The overall crowding distance is the sum of distance corresponding to each objective
return distance
elif np.array(front).ndim == 1:
distance = np.zeros(len(front))
objs = np.array([chrom[front[i]] for i in range(len(front))])
for o in range(3): # dual objective, we have three obj, so this should be adapted
obj = {m: objs[m][o] for m in range(len(front))}
sorted_keys = sorted(obj, key=obj.get)
distance[sorted_keys[0]] = distance[sorted_keys[-1]] = 999999999999
if len(distance) == 1:
break
else:
for i in range(1, len(front) - 1):
distance[sorted_keys[i]] = distance[sorted_keys[i]] + (
obj[sorted_keys[i + 1]] - obj[sorted_keys[i - 1]]) / (
obj[sorted_keys[len(front) - 1]] - obj[sorted_keys[0]])
return objs[np.argmin(distance)], front[np.argmin(distance)]
迭代100代后实现的效果如下图:
