在ZDT1和ZDT2问题上对非支配排序粒子群优化器(NSPSO)进行基准测试#
在本教程中,我们将对NSPSO算法进行基准测试,该算法可以在pygmo类nspso中找到,测试问题集为Zitzler、Deb和Thiele(ZDT)的前两个问题(即ZDT1和ZDT2)。
特别是,为了尝试复制原始论文中提出的类似策略(即,由Xiadong Li提出的“用于多目标优化的非支配排序粒子群优化器”),我们检查了三种不同算法(即,带有拥挤距离的NSPSO、带有生态位计数的NSPSO和NSGA-II)在ZDT1和ZDT2上的两个性能指标的值。特别是,虽然在原始论文中使用了Delta和GD指标,但在这种情况下,我们使用拥挤距离的平均值作为多样性指标,使用p距离作为收敛指标。这两个指标已经可以在pagmo中使用。
拥挤距离可以在任何种群集合中计算,只需调用类crowding_distance,并输入相关种群,其中拥挤距离是为非支配前沿计算的。另一方面,p距离值仅适用于ZDT和DTLZ测试套件,可以通过调用zdt类的p_distance方法来获取,并传入种群集合作为输入。
ZDT1和ZDT2问题都是盒约束的连续双目标问题,并且由pygmo在zdt类中作为UDP(用户定义问题)提供。
为了对上述算法进行基准测试,我们将使用原始论文中建议的相同输入参数。因此,对于两种NSPSO算法,我们将使用相同的输入参数集,但使用两种不同的多样性策略(即拥挤距离和生态位计数)。在第一种情况下,我们将有:algo = algorithm(nspso(gen = 100, omega=0.001, c1 = 2.0, c2 = 2.0, chi = 1.0, v_coeff = 0.5, leader_selection_range = 100, diversity_mechanism = ‘crowding distance’, memory = False, seed = 20)),而在第二种情况下:algo = algorithm(nspso(gen = 100, omega=0.001, c1 = 2.0, c2 = 2.0, chi = 1.0, v_coeff = 0.5, leader_selection_range = 100, diversity_mechanism = ‘niche count’, memory = False, seed = 20))。同样,对于NSGA-II,我们也将使用原始论文中推荐的相同输入参数:algo_3 = algorithm(nsga2(gen = 100, cr=0.9, m=1/30, eta_c=20, eta_m=20, seed = 20))。
此外,将选择相同的种群和代数大小:分别为200和100。
需要注意的是,我们的目的并不是完全复制原始论文的结果:实际上,pagmo 版本的 NSPSO 略有不同。首先,惯性权重参数(即 omega)不是动态变化的,但用户可以决定实现一个调度并在每一代的基础上进行变化。实际上,由于 memory 参数的存在,NSPSO 可以在一次调用中完成,也可以在 for 循环中迭代调用,同时保持相同的结果。这使得用户可以逐代访问算法的种群,并可能更改输入参数。其次,在 pagmo 中,我们引入了领导者选择范围参数(即 leader_selection_range),这在原始实现中是不存在的。
每个UDA在每个问题上运行十次,结果最终取平均值并以均值和标准差的形式显示。以下代码片段用于执行基准测试:
>>> from pygmo import *
>>> from pygmo import *
>>> import numpy as np
>>> pop_size=200
>>> problems=[1,2]
>>> #We declare empty arrays for storing the p-distance and mean crowding distance values for all
>>> #the algorithms and problems over 10 runs:
>>> #p-distance
>>> p_dist_nspso_cd_zdt1=[]
>>> p_dist_nspso_cd_zdt2=[]
>>> p_dist_nspso_nc_zdt1=[]
>>> p_dist_nspso_nc_zdt2=[]
>>> p_dist_nsga2_zdt1=[]
>>> p_dist_nsga2_zdt2=[]
>>> #crowding distance
>>> mean_cd_nspso_cd_zdt1=[]
>>> mean_cd_nspso_cd_zdt2=[]
>>> mean_cd_nspso_nc_zdt1=[]
>>> mean_cd_nspso_nc_zdt2=[]
>>> mean_cd_nsga2_zdt1=[]
>>> mean_cd_nsga2_zdt2=[]
>>> # We run the algos ten times each, and we store p-distance and crowding distance
>>> for j in problems:
... # 1. We declare the problem (either ZDT1 or ZDT2):
... if j==1:
... udp=zdt(prob_id=1)
... elif j==2:
... udp=zdt(prob_id=2)
...
... for ii in range(0,10):
... # 2. We declare the three populations to be evolved:
... pop_1 = population(prob = udp, size = pop_size, seed = ii+3)
... pop_2 = population(prob = udp, size = pop_size, seed = ii+3)
... pop_3 = population(prob = udp, size = pop_size, seed = ii+3)
... # 3. We declare the algorithms to be used: NSPSO with crowding distance, NSPSO with niche count and NSGA-II:
... algo = algorithm(nspso(gen = 100, omega=0.001, c1 = 2.0, c2 = 2.0, chi = 1.0, v_coeff = 0.5, leader_selection_range = 100, diversity_mechanism = 'crowding distance', memory = False, seed = 20))
... algo_2 = algorithm(nspso(gen = 100, omega=0.001, c1 = 2.0, c2 = 2.0, chi = 1.0, v_coeff = 0.5, leader_selection_range = 100, diversity_mechanism = 'niche count', memory = False, seed = 20))
... algo_3 = algorithm(nsga2(gen = 100, cr=0.9, m=1/30, eta_c=20, eta_m=20, seed = 20))
... # 4. We evolve the populations for the three algorithms:
... pop_1 = algo.evolve(pop_1)
... pop_2 = algo_2.evolve(pop_2)
... pop_3 = algo_3.evolve(pop_3)
...
... #This returns the first (i.e., best) non-dominated front:
... nds_nspso_cd = non_dominated_front_2d(pop_1.get_f())
... nds_nspso_nc = non_dominated_front_2d(pop_2.get_f())
... nds_nsga2 = non_dominated_front_2d(pop_3.get_f())
...
... #We store all the non-dominated fronts crowding distances, for all the algorithms:
... cd_nspso_cd = crowding_distance(pop_1.get_f()[nds_nspso_cd])
... cd_nspso_nc = crowding_distance(pop_2.get_f()[nds_nspso_nc])
... cd_nsga2 = crowding_distance(pop_3.get_f()[nds_nsga2])
...
... # 5. We compute the p-dist and store it in a vector, for each problem and each algorithm:
... if j==1: #ZDT1
... #We gather the crowding distance means:
... mean_cd_nspso_cd_zdt1.append(np.mean(cd_nspso_cd[np.isfinite(cd_nspso_cd)]))
... mean_cd_nspso_nc_zdt1.append(np.mean(cd_nspso_cd[np.isfinite(cd_nspso_cd)]))
... mean_cd_nsga2_zdt1.append(np.mean(cd_nsga2[np.isfinite(cd_nsga2)]))
... #And the p-distance values:
... p_dist_nspso_cd_zdt1.append(udp.p_distance(pop_1))
... p_dist_nspso_nc_zdt1.append(udp.p_distance(pop_2))
... p_dist_nsga2_zdt1.append(udp.p_distance(pop_3))
... elif j==2: #ZDT2
... #We gather the crowding distance means:
... mean_cd_nspso_cd_zdt2.append(np.mean(cd_nspso_cd[np.isfinite(cd_nspso_cd)]))
... mean_cd_nspso_nc_zdt2.append(np.mean(cd_nspso_cd[np.isfinite(cd_nspso_cd)]))
... mean_cd_nsga2_zdt2.append(np.mean(cd_nsga2[np.isfinite(cd_nsga2)]))
... #And the p-distance values:
... p_dist_nspso_cd_zdt2.append(udp.p_distance(pop_1))
... p_dist_nspso_nc_zdt2.append(udp.p_distance(pop_2))
... p_dist_nsga2_zdt2.append(udp.p_distance(pop_3))
一旦我们在ZDT1和ZDT3问题上运行了这三种算法,通过存储所有的拥挤距离和p距离值,我们可以展示结果:
>>> # 6. We print the results:
>>> print("\n NSPSO with crowding distance:")
>>> print("ZDT1-> p-distance mean and std: %(mean)f +/- %(std)f" %{"mean":np.nanmean(p_dist_nspso_cd_zdt1), "std":np.nanstd(p_dist_nspso_cd_zdt1)})
>>> print("ZDT2-> p-distance mean and std: %(mean)f +/- %(std)f" %{"mean":np.nanmean(p_dist_nspso_cd_zdt2), "std":np.nanstd(p_dist_nspso_cd_zdt2)})
>>> print("ZDT1-> crowding distance mean and std: %(mean)f +/- %(std)f" %{"mean":np.nanmean(mean_cd_nspso_cd_zdt1), "std":np.nanstd(mean_cd_nspso_cd_zdt1)})
>>> print("ZDT2-> crowding distance mean and std: %(mean)f +/- %(std)f" %{"mean":np.nanmean(mean_cd_nspso_cd_zdt2), "std":np.nanstd(mean_cd_nspso_cd_zdt2)})
>>> print("\n NSPSO with niche count:")
>>> print("ZDT1-> p-distance mean and std: %(mean)f +/- %(std)f" %{"mean":np.nanmean(p_dist_nspso_nc_zdt1), "std":np.nanstd(p_dist_nspso_nc_zdt1)})
>>> print("ZDT2-> p-distance mean and std: %(mean)f +/- %(std)f" %{"mean":np.nanmean(p_dist_nspso_nc_zdt2), "std":np.nanstd(p_dist_nspso_nc_zdt2)})
>>> print("ZDT1-> crowding distance mean and std: %(mean)f +/- %(std)f" %{"mean":np.nanmean(mean_cd_nspso_cd_zdt1), "std":np.nanstd(mean_cd_nspso_cd_zdt1)})
>>> print("ZDT2-> crowding distance mean and std: %(mean)f +/- %(std)f" %{"mean":np.nanmean(mean_cd_nspso_cd_zdt2), "std":np.nanstd(mean_cd_nspso_cd_zdt2)})
>>> print("\n NSGA2:")
>>> print("ZDT1-> p-distance mean and std: %(mean)f +/- %(std)f" %{"mean":np.nanmean(p_dist_nsga2_zdt1), "std":np.nanstd(p_dist_nsga2_zdt1)})
>>> print("ZDT2-> p-distance mean and std: %(mean)f +/- %(std)f" %{"mean":np.nanmean(p_dist_nsga2_zdt2), "std":np.nanstd(p_dist_nsga2_zdt2)})
>>> print("ZDT1-> crowding distance mean and std: %(mean)f +/- %(std)f" %{"mean":np.nanmean(mean_cd_nsga2_zdt1), "std":np.nanstd(mean_cd_nsga2_zdt1)})
>>> print("ZDT2-> crowding distance mean and std: %(mean)f +/- %(std)f" %{"mean":np.nanmean(mean_cd_nsga2_zdt2), "std":np.nanstd(mean_cd_nsga2_zdt2)})
NSPSO with crowding distance:
ZDT1-> p-distance mean and std: 0.054309 +/- 0.028563
ZDT2-> p-distance mean and std: 0.020207 +/- 0.016466
ZDT1-> crowding distance mean and std: 0.020099 +/- 0.000038
ZDT2-> crowding distance mean and std: 0.020065 +/- 0.000102
NSPSO with niche count:
ZDT1-> p-distance mean and std: 0.054797 +/- 0.016863
ZDT2-> p-distance mean and std: 0.011945 +/- 0.010522
ZDT1-> crowding distance mean and std: 0.049834 +/- 0.009898
ZDT2-> crowding distance mean and std: 0.044450 +/- 0.010312
NSGA2:
ZDT1-> p-distance mean and std: 0.011525 +/- 0.001534
ZDT2-> p-distance mean and std: 0.009290 +/- 0.001335
ZDT1-> crowding distance mean and std: 0.020099 +/- 0.000038
ZDT2-> crowding distance mean and std: 0.020065 +/- 0.000102