当前位置:   article > 正文

Python 第二代非支配排序遗传算法(NSGA-II)求解多目标高次函数的帕累托前沿_第二代遗传算法

第二代遗传算法

系列文章目录



前言

        在项目进度管理中,NSGA-II是常用的求解多目标项目进度管理优化问题的算法,虽然NSGA-III也出来了些日子,但是目前主流的研究MORCPSP问题的论文大多采用NSGA-II算法。我认为主要有三个原因:

        第一个是,NSGA-III算法需要更大的计算代价,因为它需要进行额外的种群初始化和排序操作来维护解的分布。这增加了算法的时间复杂度。第二就是,NSGA-III算法需要更多的参数设置,如分治桶的数量和邻域半径等。这些参数的不恰当设置可能会影响算法的性能。最后一点我觉得应该是,NSGA-III算法对目标函数的连续性和可微性有更高的要求。如果目标函数不满足这些条件,NSGA-III算法可能无法找到全局最优解。

        所以,在多目标资源受限问题上,NSGA-II算法仍然是一种更为通用和高效的解决方案,因为它具有更快的速度、更少的参数和对目标函数的较低要求。而我们在求解MORCPSP问题的时候,往往最追求的就是效率问题,即在短时间内求得问题的最优解(或近似最优解)。

        为了能吃透NSGA-II算法,为求解MORCPSP问题打基础,我这里先用NSGA-II求解一个常规的多目标高次函数的帕累托前沿。


一、模型的建立

        研究的模型为:min(y1=x^{2},x\in[0,10]), min(y2=(2-x)^{2},x\in[0,10])。 即求解两个目标函数最小值的问题。

二、算法的步骤

        1 设置参数,定义种群大小、进化代数、交叉概率、变异概率等

        2 定义一个自变量的类,这个类的属性值包括自变量x的值、目标函数值、非支配排序列表、拥挤度距离。

        3 通过random.uniform()函数初始化种群。

        4 迭代进化部分:

                4.1 计算目标函数的值

                4.2 进行非支配排序

                4.3 计算拥挤度距离

                4.4 依次变异、交叉、更新种群

        5 输出最优解集合并可视化展示出帕累托前沿离散图像

三、代码的实现

        代码如下:

  1. import copy
  2. import random
  3. import matplotlib.pyplot as plt
  4. # 设置参数
  5. pop_size = 100 # 种群大小
  6. gen_size = 500 # 进化代数
  7. pc = 1 # 交叉概率
  8. pm = 0.3 # 变异概率
  9. num_obj = 2 # 目标函数个数
  10. x_range = (-10, 10) # 自变量取值范围
  11. # 定义自变量的类
  12. class Individual:
  13. def __init__(self, x):
  14. self.x = x
  15. self.objs = [None] * num_obj
  16. self.rank = None
  17. self.distance = 0.0
  18. # 计算目标函数的值
  19. def evaluate(self):
  20. self.objs[0] = self.x * self.x
  21. self.objs[1] = (2 - self.x) ** 2
  22. # 初始化种群
  23. pop = [Individual(random.uniform(*x_range)) for _ in range(pop_size)]
  24. # 进化
  25. for _ in range(gen_size):
  26. print(f"第{_}次迭代")
  27. # 计算目标函数的值
  28. for ind in pop:
  29. ind.evaluate()
  30. # 非支配排序
  31. fronts = [set()]
  32. for ind in pop:
  33. ind.domination_count = 0
  34. ind.dominated_set = set()
  35. for other in pop:
  36. if ind.objs[0] < other.objs[0] and ind.objs[1] < other.objs[1]:
  37. ind.dominated_set.add(other)
  38. elif ind.objs[0] > other.objs[0] and ind.objs[1] > other.objs[1]:
  39. ind.domination_count += 1
  40. if ind.domination_count == 0:
  41. ind.rank = 1
  42. fronts[0].add(ind)
  43. rank = 1
  44. while fronts[-1]:
  45. next_front = set()
  46. for ind in fronts[-1]:
  47. ind.rank = rank
  48. for dominated_ind in ind.dominated_set:
  49. dominated_ind.domination_count -= 1
  50. if dominated_ind.domination_count == 0:
  51. next_front.add(dominated_ind)
  52. fronts.append(next_front)
  53. rank += 1
  54. # 计算拥挤度距离
  55. pop_for_cross=set()
  56. for front in fronts:
  57. if len(front) == 0:
  58. continue
  59. sorted_front = sorted(list(front), key=lambda ind: ind.rank)
  60. for i in range(num_obj):
  61. sorted_front[0].objs[i] = float('inf')
  62. sorted_front[-1].objs[i] = float('inf')
  63. for j in range(1, len(sorted_front) - 1):
  64. delta = sorted_front[j + 1].objs[i] - sorted_front[j - 1].objs[i]
  65. if delta == 0:
  66. continue
  67. sorted_front[j].distance += delta / (x_range[1] - x_range[0])
  68. front_list = list(sorted_front)
  69. front_list.sort(key=lambda ind: (-ind.rank, -ind.distance))
  70. selected_inds =front_list
  71. if len(pop_for_cross) + len(selected_inds)<=pop_size:
  72. pop_for_cross.update(selected_inds)
  73. elif len(pop_for_cross)+len(selected_inds)>=pop_size and len(pop_for_cross)<pop_size:
  74. part_selected_inds=selected_inds[:(pop_size-len(pop_for_cross))]
  75. pop_for_cross.update(part_selected_inds)
  76. break
  77. # 交叉
  78. new_pop=set()
  79. # print(len(pop_for_cross))
  80. while len(new_pop) < len(pop_for_cross):
  81. x1, x2 = random.sample(pop_for_cross, 2)
  82. if random.random() < pc:
  83. new_x = (x1.x + x2.x) / 2
  84. delta_x = abs(x1.x - x2.x)
  85. new_x += delta_x * random.uniform(-1, 1)
  86. new_x = max(x_range[0], min(x_range[1], new_x))
  87. new_pop.add(Individual(new_x))
  88. # 变异
  89. for ind in new_pop:
  90. if random.random() < pm:
  91. delta_x = random.uniform(-1, 1) * (x_range[1] - x_range[0])
  92. ind.x += delta_x
  93. ind.x = max(x_range[0], min(x_range[1], ind.x))
  94. # 更新种群,把原来的精英(pop_for_cross)保留下来。即精英保留策略
  95. pop = list(new_pop)+list(pop_for_cross)
  96. # 输出最优解集合
  97. for ind in pop:
  98. ind.evaluate()
  99. pareto_front = set()
  100. for ind in pop:
  101. dominated = False
  102. for other in pop:
  103. if other.objs[0] < ind.objs[0] and other.objs[1] < ind.objs[1]:
  104. dominated = True
  105. break
  106. if not dominated:
  107. pareto_front.add(ind)
  108. print("Pareto front:")
  109. for ind in pareto_front:
  110. print(f"x={ind.x:.4f}, y1={ind.objs[0]:.4f}, y2={ind.objs[1]:.4f}")
  111. # 可视化
  112. plt.scatter([ind.objs[0] for ind in pop], [ind.objs[1] for ind in pop], c='gray', alpha=0.5)
  113. plt.scatter([ind.objs[0] for ind in pareto_front], [ind.objs[1] for ind in pareto_front], c='r')
  114. plt.xlabel('Objective 1')
  115. plt.ylabel('Objective 2')
  116. plt.show()

四、输出的结果

        分别输出了最优解(近似最优解)及帕累托前沿的离散图像:


总结

        程序每一次运行的结果可能都不一样,这也是像遗传算法这样的启发式算法的特性。此外,还可以考虑画一条种群的平均适应度函数值(最好归一化处理后)随着迭代次数变化的曲线,能够更加直观地展示迭代的梯度上升的过程。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/150518
推荐阅读
相关标签
  

闽ICP备14008679号