按照本文梳理的算法各个模块实现,NSGA-II完整代码见GitHub - bujibujibiuwang/NSGA-II-in-python: 《A fast and elitist multi-objective genetic algorithm: NSGA-II》
目录
2.2 多样性保护(Diversity Preservation)
2.2.1 NSGA的共享函数方法(sharing function)
2.2.2 NSGA-II的拥挤距离方法(crowded-comparison)
Srinivas, N., & Deb, K. (1994). Muiltiobjective Optimization Using Nondominated Sorting in Genetic Algorithms. Evolutionary Computation, 2(3), 221-248. doi:10.1162/evco.1994.2.3.221
NSGA-II提出原文 A fast and elitist multiobjective genetic algorithm: NSGA-II
Deb, K., Pratap, A., Agarwal, S., & Meyarivan, T. A. M. T. (2002). A fast and elitist multiobjective genetic algorithm: NSGA-II. IEEE transactions on evolutionary computation, 6(2), 182-197.
NSGA-II作者实验室:Kalyanmoy Deb, Koenig Endowed Chair Professor
pymoo库:实验室创建的python第三方库,实现了各种多目标优化算法pymoo: Multi-objective Optimization in Python
geatpy2库:Geatpy是一个高性能实用型进化算法工具箱,提供许多已实现的进化算法中各项重要操作的库函数,利用“定义问题类 + 调用算法模板”的模式来进行进化优化,可用于求解单目标优化、多目标优化、复杂约束优化、组合优化、混合编码进化优化等 Geatpy
针对多目标优化问题,可以用一些多目标进化算法(multiobjective evolutionary algorithms (MOEAs))找到多个帕累托最优解(Pareto-optimal),比如非支配排序基因算法(nondominated sorting genetic algorithm (NSGA))。但是NSGA有以下问题
基于上述背景,文章提出NSGA改进版本——NSGA-II,NSGA-II有三个重点的改进:快速非支配排序(fast nondominated sorting),精英保留策略(elitist-preserving),无参数的小生境算子(parameterless niching operator)。文章内容如下:
先明白支配解,非支配解,帕累托前沿(Pareto front)的概念,假设任何二解S1 及S2 对所有目标而言,S1均优于S2,则我们称S1 支配(dominate)S2,若S1 的解没有被其他解所支配,则S1 称为非支配解(不受支配解nondominated),也称Pareto解。这些非支配解的集合即所谓的帕累托前沿(Pareto front)。非支配排序就是将种群分成多个不同水平的帕累托前沿。
在传统方法中,为了在大小为N的种群中获得第一个非支配前沿,对于每一个解,都需要将其和种群中的其它解比较所有目标值,确定该解是否被支配,因此对每个解时间复杂度为,那么遍历完所有解找到种群中的第一层非支配集合,时间复杂度为
。在找到第一个非支配前沿后,这些解就会移除,在剩下的解中继续重复上述操作,找到接下来所有层的非支配前沿。这种排序方法最坏的情况是,有N个前沿,每个前沿只有一个解,意味着上述操作重复了N词,总时间复杂度为
,空间复杂度为
。
NSGA-II中提出的的快速非支配排序将总时间复杂度降到了,空间复杂度为
。
首先,遍历种群中的所有解,对于每个解,计算两个变量值
如果,那么解p在第一层非支配前沿,通过上述操作,找到每个解的支配数和被支配集合以及第一层非支配前沿,时间复杂度为
。
接下来,对于每个支配数为0的的解p,遍历其p的支配集合中的每个成员q,q的支配数减少1,如果出现任何q支配数变成了0,则将其放入单独的列表Q中,Q中的成员便是第二层非支配前沿,对于Q中的每个成员重复上述操作找到第三层。重复以上过程直到所有的解已经排序结束。这样排序实际上是两层循环,外循环遍历最新的非支配前沿,内循环遍历非支配前沿中每个成员的支配集合。由于每个解只能在一层非支配前沿里,因此外循环最多N次,而支配集合最多有N-1个成员,内循环最多N-1次,因此时间复杂度为。也可以换一种思路理解,支配数最大为N-1,即对于每个解,到其支配数减少0最多遍历到N-1次,所有解的支配数减到0需要
。下面为快速非支配排序的算法流程。

在NSGA中,与普通的GA相比,交叉和变异操作相同,区别在于选择操作。首先按照传统非支配排序的方法获得种群的第一非支配前沿(first nondominated front),给第一非支配前沿中的每个个体分配一个相同的大的虚拟适应值(dummy fitness value),也就是最初第一非支配前沿的每个个体选择的可能性相同。接下来为了保留每层前沿解的多样性,使用共享函数方法(sharing function)得到每个解的共享适应值(shared fitness value)。共享函数如下,其中是当前前沿中任何两个个体的表型距离(phenotypic distance),
为需要设置的共享参数。定义一个变量niche count,niche count为当前前沿所有个体的共享函数值和,最后对于每个解,其共享适应值=虚拟适应值 / niche count。个人通俗理解,同一层的解之间会有相似度,对于每个解,如果与它相似的解很多,那么就降低这个解的适应值,减小它被选择的概率,而同一个生态圈的解数越多,其选择概率越小,但是保留这个生态圈里的解的整体期望还是不变,也维持了解的多样性

共享函数方法有两个问题
NSGA-II用拥挤度比较的方法代替共享函数来保持种群的多样性,不仅无参数,还减小了时间复杂度,首先定义一个密度估计度量(density-estimation metric),然后描述拥挤度比较算法
(1)密度估计(density-estimation)
为了估计种群中某个解被其他解包围的密度,引入拥挤距离(crowding distance)这个概念。计算拥挤距离,先对每一个等级的非支配解集合,按照每个目标函数值的大小升序排列。因此对每个目标函数,都能找到边界解(目标值最大最小的解),设置边界解的距离值为无穷大。每个解的拥挤距离为每个目标函数下的距离值之和,求和前先对距离值进行归一化处理,算法流程如下:
代表非支配前沿
中第i个解的第m个目标函数值,
和
为第m个目标函数的最大值和最小值。分析时间复杂度:首先排序算法需要logn,外层循环M次,内层循环最多N次,因此总的时间复杂度为

从几何角度理解拥挤距离,以两个目标函数为例,下图中黑点和白点分别为两个非支配前沿,对于解i,从与i在同一非支配前沿中选择与解i最相近的两个点i-1和i+1为顶点组成一个长方形(cuboid),拥挤距离即为长方形周长。

(2)拥挤度比较算子(Crowded-Comparison Operator)
在计算完每个解的拥挤距离后,从一定程度上来说,某个解的拥挤距离越小,这个解被其他解拥挤的程度越高。接下来通过拥挤度比较算子来选择解实现更广的帕累托最优解分布。种群中的每个个体都有两个属性:
定义一个比较顺序,满足一下判断条件。对于不同非支配等级的两个解,倾向于选择rank值更低的解,如果两个解的等级相同,倾向于选择拥挤距离更大或者说拥挤区域更小的解。
综上,有三个创新点,一个快速非支配排序方法,一个快速估计拥挤距离的方法,一个简单的拥挤度比较算子,接下来描述NSGA-II的主循环。
先明确以下概念
随机生成初始种群P(0),基于非支配排序P(0),每个解都赋予一个适应值与其前沿等级相同(1是最好的,2其次....),假设问题是要最小化。起初使用联赛选择,重组和变异获得一个子代Q(0),NSGA-II使用了精英选择策略,因此后面迭代过程会不同。假设现在是第t代种群,下面一步步描述结合了精英选择,快速非支配排序,拥挤距离等方法的NSGA-II算法过程。
首先生成一个基于P(t)和Q(t)得到的组合种群R(t),R(t)大小为2N,N为种群大小。对R(t)执行快速非支配排序,属于R(t)的第一非支配前沿F1中的解是当前最好的解,因此要重点选择。如果F1长度小于N,那么选择F1添加到新一代种群P(t+1)中,同样操作在F2,F3....,直到P(t+1)剩余解数量不足以再合并一个完整的非支配前沿。假设F(i)是最后一个无法容纳的非支配前沿,按照拥挤距离给F(i)中的元素降序排列,选择排序后靠前的元素添加到P(t+1)中直到P(t+1)大小为N。对P(t+1)执行选择,交叉,变异生成新种群Q(t+1),此处的选择虽然也是联赛选择方法,但是选择标准是基于拥挤度比较算子。这个算子需要每个解的等级和拥挤距离,因此在组成种群P(t+1)的时候就可以计算这两个属性。P(t+1)是从大小为2N的R(t)中按照前沿等级和拥挤距离生成的大小为N的种群,而R(t)包含了所有之前和现在的种群成员,确保了精英选择策略。算法流程如下图


分析整个过程的时间复杂度如下, NSGA-II的整体时间复杂度为 。实际上在对R(t)做快速非支配排序时,并不需要多整个种群排序,只需找到N个解即可。使用拥挤距离选择下一代也避免了人工参数的设置。
pymoo库了提供最先进的单目标和多目标优化算法,以及与多目标优化相关的更多功能,例如可视化和决策制定。比如用NSGA-II解决一个二元决策问题,示例代码如下
from pymoo.algorithms.moo.nsga2 import NSGA2
from pymoo.problems import get_problem
from pymoo.operators.crossover.pntx import TwoPointCrossover
from pymoo.operators.mutation.bitflip import BitflipMutation
from pymoo.operators.sampling.rnd import BinaryRandomSampling
from pymoo.optimize import minimize
from pymoo.visualization.scatter import Scatter
problem = get_problem("zdt5")
algorithm = NSGA2(pop_size=100,
sampling=BinaryRandomSampling(),
crossover=TwoPointCrossover(),
mutation=BitflipMutation(),
eliminate_duplicates=True)
res = minimize(problem,
algorithm,
('n_gen', 500),
seed=1,
verbose=False)
Scatter().add(res.F).show()
和pymmo相比,geatpy2支持自定义问题,提供模块化的算法框架,示例代码如下
import geatpy as ea
import numpy as np
# 构建问题
r = 1 # 目标函数需要用到的额外数据
@ea.Problem.single
def evalVars(Vars): # 定义目标函数(含约束)
f = np.sum((Vars - r) ** 2) # 计算目标函数值
x1 = Vars[0]
x2 = Vars[1]
CV = np.array([(x1 - 0.5)**2 - 0.25,
(x2 - 1)**2 - 1]) # 计算违反约束程度
return f, CV
problem = ea.Problem(name='soea quick start demo',
M=1, # 目标维数
maxormins=[1], # 目标最小最大化标记列表,1:最小化该目标;-1:最大化该目标
Dim=5, # 决策变量维数
varTypes=[0, 0, 1, 1, 1], # 决策变量的类型列表,0:实数;1:整数
lb=[-1, 1, 2, 1, 0], # 决策变量下界
ub=[1, 4, 5, 2, 1], # 决策变量上界
evalVars=evalVars)
# 构建算法
algorithm = ea.soea_SEGA_templet(problem,
ea.Population(Encoding='RI', NIND=20),
MAXGEN=50, # 最大进化代数。
logTras=1, # 表示每隔多少代记录一次日志信息,0表示不记录。
trappedValue=1e-6, # 单目标优化陷入停滞的判断阈值。
maxTrappedCount=10) # 进化停滞计数器最大上限值。
# 求解
res = ea.optimize(algorithm, seed=1, verbose=True, drawing=1, outputMsg=True, drawLog=False, saveFlag=Tr
关于第三方库的内容可自行查阅官方网站
按照前面的算法流程分别实现快速非支配排序模块和拥挤距离计算模块
(1)快速非支配排序模块
"""
两个目标函数为例
输入:种群每个解的两个目标函数值
输入:所有非支配前沿
"""
def fast_non_dominated_sort(values1, values2):
size = len(values1) # 种群大小
s = [[] for _ in range(size)] # 每个解的被支配集合
n = [0 for _ in range(size)] # 每个解的支配数
rank = [0 for _ in range(size)] # 每个解的等级
fronts = [[]] # 所有非支配前沿
for p in range(size): # 遍历所有解
s[p] = [] # 初始化非支配集合和支配数
n[p] = 0
for q in range(size): # 判断p和q支配情况
# 如果p支配q,增加q到p的被支配集合
if values1[p] >= values1[q] and values2[p] >= values2[q] \
and ((values1[q] == values1[p]) + (values2[p] == values2[q])) != 2:
s[p].append(q)
# 如果q支配p,p的支配数+1
elif values1[q] >= values1[p] and values2[q] >= values2[p] \
and ((values1[q] == values1[p]) + (values2[p] == values2[q])) != 2:
n[p] += 1
# n[p]=0的解等级设为0,增加到第一前沿
if n[p] == 0:
rank[p] = 0
fronts[0].append(p)
# 依次确定其它层非支配前沿
i = 0
while fronts[i]:
Q = []
for p in fronts[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
fronts.append(Q)
del fronts[-1]
return fronts
(2)拥挤距离计算模块
"""
输入:所有解所有目标的函数值,要计算的前沿,目标数
输出:输入前沿每个解的拥挤距离
"""
def crowed_distance_assignment(values1, values2, front):
length = len(front)
sorted_front1 = sorted(front, key=lambda x: values1[x])
sorted_front2 = sorted(front, key=lambda x: values2[x])
dis_table = {sorted_front1[0]: np.inf, sorted_front1[-1]: np.inf, sorted_front2[0]: np.inf, sorted_front2[-1]: np.inf}
for i in range(1, length - 1):
k = sorted_front1[i]
dis_table[k] = dis_table.get(k, 0)+(values1[sorted_front1[i+1]]-values1[sorted_front1[i-1]])/(max(values1)-min(values1))
for i in range(1, length - 1):
k = sorted_front1[i]
dis_table[k] = dis_table[k]+(values2[sorted_front2[i+1]]-values2[sorted_front2[i-1]])/(max(values2)-min(values2))
distance = [dis_table[a] for a in front]
return distance
如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby
在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has
我的主要目标是能够完全理解我正在使用的库/gem。我尝试在Github上从头到尾阅读源代码,但这真的很难。我认为更有趣、更温和的踏脚石就是在使用时阅读每个库/gem方法的源代码。例如,我想知道RubyonRails中的redirect_to方法是如何工作的:如何查找redirect_to方法的源代码?我知道在pry中我可以执行类似show-methodmethod的操作,但我如何才能对Rails框架中的方法执行此操作?您对我如何更好地理解Gem及其API有什么建议吗?仅仅阅读源代码似乎真的很难,尤其是对于框架。谢谢! 最佳答案 Ru
我的假设是moduleAmoduleBendend和moduleA::Bend是一样的。我能够从thisblog找到解决方案,thisSOthread和andthisSOthread.为什么以及什么时候应该更喜欢紧凑语法A::B而不是另一个,因为它显然有一个缺点?我有一种直觉,它可能与性能有关,因为在更多命名空间中查找常量需要更多计算。但是我无法通过对普通类进行基准测试来验证这一点。 最佳答案 这两种写作方法经常被混淆。首先要说的是,据我所知,没有可衡量的性能差异。(在下面的书面示例中不断查找)最明显的区别,可能也是最著名的,是你的
几个月前,我读了一篇关于rubygem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:
我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur
前言作为一名程序员,自己的本质工作就是做程序开发,那么程序开发的时候最直接的体现就是代码,检验一个程序员技术水平的一个核心环节就是开发时候的代码能力。众所周知,程序开发的水平提升是一个循序渐进的过程,每一位程序员都是从“菜鸟”变成“大神”的,所以程序员在程序开发过程中的代码能力也是根据平时开发中的业务实践来积累和提升的。提高代码能力核心要素程序员要想提高自身代码能力,尤其是新晋程序员的代码能力有很大的提升空间的时候,需要针对性的去提高自己的代码能力。提高代码能力其实有几个比较关键的点,只要把握住这些方面,就能很好的、快速的提高自己的一部分代码能力。1、多去阅读开源项目,如有机会可以亲自参与开源
目录一.加解密算法数字签名对称加密DES(DataEncryptionStandard)3DES(TripleDES)AES(AdvancedEncryptionStandard)RSA加密法DSA(DigitalSignatureAlgorithm)ECC(EllipticCurvesCryptography)非对称加密签名与加密过程非对称加密的应用对称加密与非对称加密的结合二.数字证书图解一.加解密算法加密简单而言就是通过一种算法将明文信息转换成密文信息,信息的的接收方能够通过密钥对密文信息进行解密获得明文信息的过程。根据加解密的密钥是否相同,算法可以分为对称加密、非对称加密、对称加密和非
嗨~大家好,这里是可莉!今天给大家带来的是7个C语言的经典基础代码~那一起往下看下去把【程序一】打印100到200之间的素数#includeintmain(){ inti; for(i=100;i 【程序二】输出乘法口诀表#includeintmain(){inti;for(i=1;i 【程序三】判断1000年---2000年之间的闰年#includeintmain(){intyear;for(year=1000;year 【程序四】给定两个整形变量的值,将两个值的内容进行交换。这里提供两种方法来进行交换,第一种为创建临时变量来进行交换,第二种是不创建临时变量而直接进行交换。1.创建临时变量来
文章目录git常用命令(简介,详细参数往下看)Git提交代码步骤gitpullgitstatusgitaddgitcommitgitpushgit代码冲突合并问题方法一:放弃本地代码方法二:合并代码常用命令以及详细参数gitadd将文件添加到仓库:gitdiff比较文件异同gitlog查看历史记录gitreset代码回滚版本库相关操作远程仓库相关操作分支相关操作创建分支查看分支:gitbranch合并分支:gitmerge删除分支:gitbranch-ddev查看分支合并图:gitlog–graph–pretty=oneline–abbrev-commit撤消某次提交git用户名密码相关配置g