摘要
多目标优化(Multi-Objective Optimization, MOO)是优化领域的一个分支,它处理的是同时优化多个相互冲突的目标函数的问题。在实际应用中,很少有决策问题只涉及单一目标,通常需要在多个目标之间找到平衡点。例如,在工程设计中可能需要同时考虑成本、性能和可靠性等因素。本周进一步学习一些多目标优化算法的常见类型。
Abstract
Multi-objective optimization (MOO) is a branch of optimization field, which deals with the problem of simultaneously optimizing multiple conflicting objective functions. In practical application, few decision-making problems only involve a single goal, and it is usually necessary to find a balance between multiple goals. For example, factors such as cost, performance and reliability may need to be considered simultaneously in engineering design. Learn more about some common types of multi-objective optimization algorithms this week.
多目标优化算法
多目标优化的基本概念
- 目标函数:每个目标函数衡量了某个特定方面的性能或质量。
- 解空间:所有可能的解决方案组成的集合。
- 可行解:满足所有约束条件的解。
- Pareto 优势:如果一个解在至少一个目标上优于另一个解,并且在其他目标上不劣于该解,则称这个解对另一个解具有 Pareto 优势。
- Pareto 最优解:没有其他解能够对该解产生 Pareto 优势,这样的解称为 Pareto 最优解。
- Pareto 前沿:所有 Pareto 最优解在目标空间中的投影构成的集合。
实现步骤
- 初始化种群:随机生成初始解集。
- 评估:计算每个个体的目标函数值。
- 选择:根据某种策略选择下一代的父代个体。
- 遗传操作:执行交叉和变异操作以产生新个体。
- 更新:根据 Pareto 优势更新种群。
- 终止条件:达到预定迭代次数或其他终止标准后停止。
多目标优化算法常见类型及其实现
多目标优化算法的目标是找到一组 Pareto 最优解,而不是单一的最佳解。常见的多目标优化算法包括但不限于以下几种:
NSGA-II (Non-dominated Sorting Genetic Algorithm II)
- 使用快速非支配排序来评价种群中的个体。
- 利用拥挤距离保持种群多样性。
- 通过遗传操作(如交叉和变异)生成新的后代。
代码如下:
import random
import numpy as np
from deap import base, creator, tools, algorithms
# 定义问题和解决方案的类型
creator.create("FitnessMin", base.Fitness, weights=(-1.0, -1.0))
creator.create("Individual", list, fitness=creator.FitnessMin)
# 初始化工具箱
toolbox = base.Toolbox()
# 注册属性初始化器
toolbox.register("attr_float", random.random)
toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_float, n=2)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
# 注册评估函数
def evaluateZDT1(individual):
f1 = individual[0]
g = 1 + 9 * sum(individual[1:]) / (len(individual) - 1)
f2 = g * (1 - (f1 / g) ** 0.5)
return f1, f2
toolbox.register("evaluate", evaluateZDT1)
# 注册遗传操作
toolbox.register("mate", tools.cxSimulatedBinaryBounded, eta=20.0, low=0, up=1)
toolbox.register("mutate", tools.mutPolynomialBounded, eta=20.0, low=0, up=1, indpb=1.0/2)
toolbox.register("select", tools.selNSGA2)
# 主程序
def main():
# 初始化种群
pop = toolbox.population(n=90)
hof = tools.ParetoFront()
stats = tools.Statistics(lambda ind: ind.fitness.values)
stats.register("avg", np.mean, axis=0)
stats.register("std", np.std, axis=0)
stats.register("min", np.min, axis=0)
stats.register("max", np.max, axis=0)
# 进化过程
pop, logbook = algorithms.eaMuPlusLambda(pop, toolbox, mu=len(pop), lambda_=len(pop),
cxpb=0.9, mutpb=0.1, ngen=20,
stats=stats, halloffame=hof, verbose=True)
return pop, logbook, hof
if __name__ == "__main__":
pop, log, hof = main()
这段代码的主要组成部分如下:
1.定义问题和解决方案的类型:
creator.create(“FitnessMin”, base.Fitness, weights=(-1.0, -1.0)) 定义了一个最小化多目标问题。
creator.create(“Individual”, list, fitness=creator.FitnessMin) 创建了一个列表类型的个体,其中包含一个FitnessMin实例作为其fitness属性。
2.注册属性初始化器:
toolbox.register(“attr_float”, random.random) 定义了一个随机浮点数生成器。
toolbox.register(“individual”, tools.initRepeat, creator.Individual, toolbox.attr_float, n=2) 注册了个体初始化器,每个个体有两个基因。
toolbox.register(“population”, tools.initRepeat, list, toolbox.individual) 注册了种群初始化器。
3.评估函数:
evaluateZDT1 是一个示例评估函数,用于评估ZDT1测试问题的个体。该函数返回两个目标值。
4.遗传操作:
cxSimulatedBinaryBounded 和 mutPolynomialBounded 分别用于执行模拟二进制交叉和多项式变异。
selNSGA2 用于执行快速非支配排序的选择操作。
5.主程序:
初始化种群和统计信息。
执行进化算法 eaMuPlusLambda。
输出最终的种群、日志和Pareto前沿。
结果如下:
SPEA2 (Strength Pareto Evolutionary Algorithm 2)
- 引入个体适应度的概念,根据支配关系和密度估计进行排序。
- 通过存档机制保持历史最优解。
代码如下:
import random
import numpy as np
from deap import base, creator, tools, algorithms
# 定义问题和解决方案的类型
creator.create("FitnessMin", base.Fitness, weights=(-1.0, -1.0))
creator.create("Individual", list, fitness=creator.FitnessMin)
# 初始化工具箱
toolbox = base.Toolbox()
# 注册属性初始化器
toolbox.register("attr_float", random.random)
toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_float, n=2)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
# 注册评估函数
def evaluateZDT1(individual):
f1 = individual[0]
g = 1 + 9 * sum(individual[1:]) / (len(individual) - 1)
f2 = g * (1 - (f1 / g) ** 0.5)
return f1, f2
toolbox.register("evaluate", evaluateZDT1)
# 注册遗传操作
toolbox.register("mate", tools.cxSimulatedBinaryBounded, eta=20.0, low=0, up=1)
toolbox.register("mutate", tools.mutPolynomialBounded, eta=20.0, low=0, up=1, indpb=1.0/2)
# SPEA2的选择函数
def selSPEA2(individuals, k):
if len(individuals) < k:
return individuals[:]
# 计算支配关系
dominance = tools.SPEADomination()
dominated_counts = [sum(dominance(i, j) for j in individuals) for i in individuals]
# 计算邻近度
k_nearest = tools.knn(20)
distances = [k_nearest(i, individuals) for i in individuals]
# 创建存档
archive = tools.ParetoFront()
archive.update(individuals)
# 选择个体
selected = []
while len(selected) < k:
candidates = [i for i in range(len(individuals)) if i not in selected]
if not candidates:
break
# 选择支配数量最少的个体
min_dominated_count = min(dominated_counts[i] for i in candidates)
candidates = [i for i in candidates if dominated_counts[i] == min_dominated_count]
# 如果有多个个体,则选择距离最大的个体
if len(candidates) > 1:
max_distance = max(distances[i] for i in candidates)
candidates = [i for i in candidates if distances[i] == max_distance]
selected.append(candidates[0])
# 更新支配计数和邻近度
for i in candidates:
dominated_counts[i] = sum(dominance(i, j) for j in individuals if j not in selected)
distances[i] = k_nearest(i, [j for j in individuals if j not in selected])
return [individuals[i] for i in selected], archive
# 主程序
def main():
# 初始化种群
pop = toolbox.population(n=90)
stats = tools.Statistics(lambda ind: ind.fitness.values)
stats.register("avg", np.mean, axis=0)
stats.register("std", np.std, axis=0)
stats.register("min", np.min, axis=0)
stats.register("max", np.max, axis=0)
# 进化过程
hof = tools.ParetoFront()
logbook = tools.Logbook()
record = stats.compile(pop)
logbook.record(gen=0, evals=len(pop), **record)
print(logbook.stream)
for gen in range(1, 21):
offspring = algorithms.varOr(pop, toolbox, lambda_=len(pop), cxpb=0.9, mutpb=0.1)
invalid_ind = [ind for ind in offspring if not ind.fitness.valid]
fitnesses = toolbox.map(toolbox.evaluate, invalid_ind)
for ind, fit in zip(invalid_ind, fitnesses):
ind.fitness.values = fit
# 选择操作
pop, archive = selSPEA2(offspring + pop, len(pop))
record = stats.compile(pop)
logbook.record(gen=gen, evals=len(pop), **record)
print(logbook.stream)
return pop, logbook, hof
if __name__ == "__main__":
pop, log, hof = main()
MOEA/D (Multi-Objective Evolutionary Algorithm based on Decomposition)
- 将多目标问题分解为多个单目标子问题。
- 每个个体负责优化一个子问题。
- 使用局部搜索策略提高优化效率。
代码如下:
import random
import numpy as np
from deap import base, creator, tools, algorithms
# 定义问题和解决方案的类型
creator.create("FitnessMin", base.Fitness, weights=(-1.0, -1.0))
creator.create("Individual", list, fitness=creator.FitnessMin)
# 初始化工具箱
toolbox = base.Toolbox()
# 注册属性初始化器
toolbox.register("attr_float", random.random)
toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_float, n=2)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
# 注册评估函数
def evaluateZDT1(individual):
f1 = individual[0]
g = 1 + 9 * sum(individual[1:]) / (len(individual) - 1)
f2 = g * (1 - (f1 / g) ** 0.5)
return f1, f2
toolbox.register("evaluate", evaluateZDT1)
# 注册遗传操作
toolbox.register("mate", tools.cxSimulatedBinaryBounded, eta=20.0, low=0, up=1)
toolbox.register("mutate", tools.mutPolynomialBounded, eta=20.0, low=0, up=1, indpb=1.0/2)
# 分解多目标问题
def decompose_objectives(objective_values, weight_vector):
weighted_sum = sum(w * obj for w, obj in zip(weight_vector, objective_values))
return weighted_sum
# MOEA/D的选择函数
def selMOEAD(individuals, k, weights):
if len(individuals) < k:
return individuals[:]
# 计算每个个体对应的子问题权重
for ind in individuals:
ind.f_weights = weights[ind.id]
# 创建存档
archive = tools.ParetoFront()
archive.update(individuals)
# 选择个体
selected = []
while len(selected) < k:
candidates = [i for i in range(len(individuals)) if i not in selected]
if not candidates:
break
# 选择支配数量最少的个体
min_dominated_count = min(ind.fitness.values[0] for ind in individuals if ind.id not in selected)
candidates = [ind for ind in individuals if ind.fitness.values[0] == min_dominated_count and ind.id not in selected]
# 如果有多个个体,则随机选择一个
if len(candidates) > 1:
selected.append(random.choice(candidates))
else:
selected.append(candidates[0].id)
# 更新支配计数
for i in candidates:
i.fitness.values = decompose_objectives(i.fitness.values, i.f_weights)
return [individuals[i] for i in selected], archive
# 主程序
def main():
# 初始化种群
pop = toolbox.population(n=90)
weights = tools.uniform_reference_points(nobj=2, p=len(pop))
for i, ind in enumerate(pop):
ind.id = i
ind.fitness.values = decompose_objectives(toolbox.evaluate(ind), weights[i])
# 进化过程
hof = tools.ParetoFront()
logbook = tools.Logbook()
record = tools.Statistics(lambda ind: ind.fitness.values)
record.register("avg", np.mean, axis=0)
record.register("std", np.std, axis=0)
record.register("min", np.min, axis=0)
record.register("max", np.max, axis=0)
record = record.compile(pop)
logbook.record(gen=0, evals=len(pop), **record)
print(logbook.stream)
for gen in range(1, 21):
offspring = algorithms.varOr(pop, toolbox, lambda_=len(pop), cxpb=0.9, mutpb=0.1)
invalid_ind = [ind for ind in offspring if not ind.fitness.valid]
fitnesses = toolbox.map(toolbox.evaluate, invalid_ind)
for ind, fit in zip(invalid_ind, fitnesses):
ind.fitness.values = decompose_objectives(fit, weights[ind.id])
# 选择操作
pop, archive = selMOEAD(offspring + pop, len(pop), weights)
record = record.compile(pop)
logbook.record(gen=gen, evals=len(pop), **record)
print(logbook.stream)
return pop, logbook, hof
if __name__ == "__main__":
pop, log, hof = main()
ε-MOEA (Epsilon-Dominance Based Multi-Objective Evolutionary Algorithm)
- 引入 ε-支配关系,允许一定程度的目标函数值差距。
- 可以更好地控制 Pareto 前沿的分布。
代码如下:
import random
import numpy as np
from deap import base, creator, tools, algorithms
# 定义问题和解决方案的类型
creator.create("FitnessMin", base.Fitness, weights=(-1.0, -1.0))
creator.create("Individual", list, fitness=creator.FitnessMin)
# 初始化工具箱
toolbox = base.Toolbox()
# 注册属性初始化器
toolbox.register("attr_float", random.random)
toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_float, n=2)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
# 注册评估函数
def evaluateZDT1(individual):
f1 = individual[0]
g = 1 + 9 * sum(individual[1:]) / (len(individual) - 1)
f2 = g * (1 - (f1 / g) ** 0.5)
return f1, f2
toolbox.register("evaluate", evaluateZDT1)
# 注册遗传操作
toolbox.register("mate", tools.cxSimulatedBinaryBounded, eta=20.0, low=0, up=1)
toolbox.register("mutate", tools.mutPolynomialBounded, eta=20.0, low=0, up=1, indpb=1.0/2)
# ε-支配关系
def epsilon_dominates(a, b, epsilons):
dominates = True
for ai, bi, eps in zip(a, b, epsilons):
if ai > bi + eps:
return False
elif ai < bi - eps:
dominates = False
return dominates
# ε-MOEA的选择函数
def selEPSMOEA(individuals, k, epsilons):
if len(individuals) < k:
return individuals[:]
# 创建存档
archive = tools.ParetoFront()
archive.update(individuals)
# 选择个体
selected = []
while len(selected) < k:
candidates = [i for i in range(len(individuals)) if i not in selected]
if not candidates:
break
# 选择支配数量最少的个体
min_dominated_count = min(sum(epsilon_dominates(ind.fitness.values, other.fitness.values, epsilons)
for other in individuals if other.id != ind.id)
for ind in individuals if ind.id not in selected)
candidates = [ind for ind in individuals if ind.fitness.values[0] == min_dominated_count and ind.id not in selected]
# 如果有多个个体,则随机选择一个
if len(candidates) > 1:
selected.append(random.choice(candidates))
else:
selected.append(candidates[0].id)
return [individuals[i] for i in selected], archive
# 主程序
def main():
# 初始化种群
pop = toolbox.population(n=90)
epsilons = [0.05, 0.05] # 目标函数的ε值
# 进化过程
hof = tools.ParetoFront()
logbook = tools.Logbook()
record = tools.Statistics(lambda ind: ind.fitness.values)
record.register("avg", np.mean, axis=0)
record.register("std", np.std, axis=0)
record.register("min", np.min, axis=0)
record.register("max", np.max, axis=0)
record = record.compile(pop)
logbook.record(gen=0, evals=len(pop), **record)
print(logbook.stream)
for gen in range(1, 21):
offspring = algorithms.varOr(pop, toolbox, lambda_=len(pop), cxpb=0.9, mutpb=0.1)
invalid_ind = [ind for ind in offspring if not ind.fitness.valid]
fitnesses = toolbox.map(toolbox.evaluate, invalid_ind)
for ind, fit in zip(invalid_ind, fitnesses):
ind.fitness.values = fit
# 选择操作
pop, archive = selEPSMOEA(offspring + pop, len(pop), epsilons)
record = record.compile(pop)
logbook.record(gen=gen, evals=len(pop), **record)
print(logbook.stream)
return pop, logbook, hof
if __name__ == "__main__":
pop, log, hof = main()
PESA-II (Performance-Scalarizing Evolutionary Algorithm)
- 使用标量化方法将多目标问题转换为一系列单目标问题。
- 通过动态调整权重向量来探索 Pareto 前沿的不同区域。
代码如下:
import random
import numpy as np
from deap import base, creator, tools, algorithms
# 定义问题和解决方案的类型
creator.create("FitnessMin", base.Fitness, weights=(-1.0,))
creator.create("Individual", list, fitness=creator.FitnessMin)
# 初始化工具箱
toolbox = base.Toolbox()
# 注册属性初始化器
toolbox.register("attr_float", random.random)
toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_float, n=2)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
# 注册评估函数
def evaluateZDT1(individual, weights):
f1 = individual[0]
g = 1 + 9 * sum(individual[1:]) / (len(individual) - 1)
f2 = g * (1 - (f1 / g) ** 0.5)
scalarized_value = weights[0]*f1 + weights[1]*f2
return scalarized_value,
toolbox.register("evaluate", evaluateZDT1)
# 注册遗传操作
toolbox.register("mate", tools.cxSimulatedBinaryBounded, eta=20.0, low=0, up=1)
toolbox.register("mutate", tools.mutPolynomialBounded, eta=20.0, low=0, up=1, indpb=1.0/2)
# PESA-II的选择函数
def selPESA2(individuals, k, weights):
if len(individuals) < k:
return individuals[:]
# 计算所有个体的适应度
fitnesses = [toolbox.evaluate(ind, weights) for ind in individuals]
# 按照适应度排序
sorted_individuals = [ind for _, ind in sorted(zip(fitnesses, individuals))]
# 选择前k个个体
return sorted_individuals[:k]
# 主程序
def main():
# 初始化种群
pop = toolbox.population(n=90)
num_weights = 2 # 两个目标
num_weights_per_generation = 5 # 每代使用的权重数量
# 进化过程
hof = tools.ParetoFront()
logbook = tools.Logbook()
record = tools.Statistics(lambda ind: ind.fitness.values)
record.register("avg", np.mean, axis=0)
record.register("std", np.std, axis=0)
record.register("min", np.min, axis=0)
record.register("max", np.max, axis=0)
record = record.compile(pop)
logbook.record(gen=0, evals=len(pop), **record)
print(logbook.stream)
for gen in range(1, 21):
# 生成权重向量
weights = generate_weights(num_weights, num_weights_per_generation)
offspring = algorithms.varOr(pop, toolbox, lambda_=len(pop), cxpb=0.9, mutpb=0.1)
invalid_ind = [ind for ind in offspring if not ind.fitness.valid]
fitnesses = toolbox.map(lambda ind: toolbox.evaluate(ind, weights), invalid_ind)
for ind, fit in zip(invalid_ind, fitnesses):
ind.fitness.values = fit
# 选择操作
pop = selPESA2(offspring + pop, len(pop), weights)
record = record.compile(pop)
logbook.record(gen=gen, evals=len(pop), **record)
print(logbook.stream)
return pop, logbook, hof
# 生成权重向量
def generate_weights(num_objectives, num_weights):
weights = []
for _ in range(num_weights):
w = [random.random() for _ in range(num_objectives)]
w_sum = sum(w)
w = [wi / w_sum for wi in w] # 归一化
weights.append(w)
return weights
if __name__ == "__main__":
pop, log, hof = main()
这些算法通常用于解决复杂问题,比如设计优化、资源分配等问题。每种算法都有其特点和适用场景,选择哪种算法取决于具体问题的需求。
一些常见的dos命令
“DOS命令”通常指的是在MS-DOS(Microsoft Disk Operating System)或者兼容的命令行界面(如Windows命令提示符cmd.exe)中使用的命令。
更换盘符: d:
查看当前目录下的文件及文件夹:dir
进入文件夹: cd 文件夹的名字
返回上一级目录:cd …
创建文件夹:mkdir/md 文件夹名
删除文件夹:rd 文件夹的名字
清屏:cls
退出: exit
总结
本周进一步学习了一些多目标优化算法的常见类型,下周将继续学习项目申报书中提到的动态优化GA模型等内容。