当前位置:   article > 正文

NSGA-II 遗传多目标算法(python示例)_nsga2算法实例

nsga2算法实例

一、前言

        最近在准备毕业论文,研究了一下主流的多目标算法,对于NSGA-II,网上大部分代码是全部是面向过程来实现的,本人更喜欢采用面向对象的方式,故采用python面向对象实现了一个示例,实现了对于二元多目标问题的求解。

二、算法基本流程

三、核心思想

1、非支配排序

这个简单的例子说明了帕累托最优的概念。上面我们有4个成员A, B, C和D,有两个特征:身高和工资。现在,如果我们同时比较他们的身高和薪水,我们会发现这不是很直观,因为他们有多个目标。

既然这两个目标越大越好,我们可以简单地对它们进行比较。首先,我们观察到A和B都比C和D多,所以我们说A和B在身高和薪水上“支配”C和D。同理,C支配D,D可被A,B,C支配。

A和B呢?A比B高,但是工资低。相反,B面临着同样的情况。我们称这种情况为“非支配”。 如果我们能找到一组解它们不互相支配,也不受其他解支配,我们称之为"帕累托最优"解。在上面的例子中,A和B都在帕累托最优前沿

几个概念:

非支配解:假设任何二解S1 及S2 对所有目标而言,S1均优于S2,则我们称S1 支配S2,若S1 的解没有被其他解所支配,则S1 称为非支配解(不受支配解),也称Pareto解(帕雷托解)

支配解:若解S2的所有目标均劣于S1,则称S1优于S2,也称S1支配S2,S2为受支配解。

Pareto前沿面:找到所有Pareto解之后,这些解组成的平面叫做Pareto前沿面(Non-dominated front)。在目标函数较多时,前沿面通常为超曲面。

2、拥挤度

通俗的来讲,当需要舍弃某一rank平面的部分节点时,由于同一平面中的所有节点rank相同,不能通过rank来舍弃,而是要通过拥挤度来舍弃,以上就是拥挤度的作用。

算法更倾向于稀疏的点,也就是让节点更可能的分散,可以有效地方式早熟和过拟合现象

3、精英选择策略

每次都将父类与子类想结合,依次采用非支配排序、计算拥挤度来选择父代。

四、python实现

github:   源代码地址

  1. """
  2. author: pym
  3. time: 2023.10.29
  4. ide: pycharm2
  5. """
  6. from collections import defaultdict
  7. import numpy as np
  8. import random
  9. import matplotlib.pyplot as plt
  10. import math
  11. class Individual(object):
  12. def __init__(self):
  13. self.solution = None # 实际为nparray类型,方便四则运算
  14. self.objective = defaultdict()
  15. self.n = 0 # 解p被几个解支配
  16. self.rank = 0 # 解p所在层数
  17. self.S = [] # 解p支配解的集合
  18. self.distance = 0 # 拥挤度距离
  19. def bound_process(self, bound_min, bound_max):
  20. """
  21. 对解向量 solution 中的每个分量进行定义域判断;超过最大值赋为最大值
  22. :param bound_min: 定义域下限
  23. :param bound_max: 定义域上限
  24. :return:
  25. """
  26. for i, item in enumerate(self.solution):
  27. if item > bound_max:
  28. self.solution[i] = bound_max
  29. elif item < bound_min:
  30. self.solution[i] = bound_min
  31. def calculate_objective(self, objective_fun):
  32. """
  33. 计算目标值
  34. :param objective_fun: 目标函数
  35. :return:
  36. """
  37. self.objective = objective_fun(self.solution)
  38. def __lt__(self, other):
  39. """
  40. 重载小于号,只有当solution中全部小于对方,才判断小于
  41. :param other: 比较的个体
  42. :return: 1:小于 0:大于
  43. """
  44. v1 = list(self.objective.values())
  45. v2 = list(other.objective.values())
  46. for i in range(len(v1)):
  47. if v1[i] > v2[i]:
  48. return 0
  49. return 1
  50. def fast_non_dominated_sort(P):
  51. """
  52. 非支配排序
  53. :param P: 种群P
  54. :return: F:分层结果,返回值类型为dict,键为层号,值为list(该层中的个体)
  55. """
  56. F = defaultdict(list)
  57. for p in P:
  58. p.S = []
  59. p.n = 0
  60. for q in P:
  61. if p < q: # p支配q
  62. p.S.append(q)
  63. elif q < p: # q支配p
  64. p.n += 1
  65. if p.n == 0:
  66. p.rank = 1
  67. F[1].append(p)
  68. i = 1
  69. while F[i]:
  70. Q = []
  71. for p in F[i]:
  72. for q in p.S:
  73. q.n -= 1
  74. if q.n == 0:
  75. q.rank = i + 1
  76. Q.append(q)
  77. i += 1
  78. F[i] = Q
  79. return F
  80. def crowding_distance_assignment(L):
  81. """
  82. 计算拥挤度
  83. :param L: F[i],是个list,为第i层的节点集合
  84. :return:
  85. """
  86. l = len(L)
  87. # 初始化距离
  88. for i in range(l):
  89. L[i].distance = 0
  90. # 遍历每个目标方向(有几个优化目标,就有几个目标方向)
  91. for m in L[0].objective.keys():
  92. L.sort(key=lambda x: x.objective[m]) # 使用objective值排序
  93. L[0].distance = float('inf')
  94. L[l - 1].distance = float('inf')
  95. f_max = L[l - 1].objective[m]
  96. f_min = L[0].objective[m]
  97. # 当某一个目标方向上的最大值和最小值相同时,会出现除0错误
  98. try:
  99. for i in range(1, l - 1):
  100. L[i].distance = L[i].distance + (L[i + 1].objective[m] - L[i - 1].objective[m]) / (f_max - f_min)
  101. except Exception:
  102. print(str(m) + "目标方向上,最大值为:" + str(f_max) + " 最小值为:" + str(f_min))
  103. def binary_tornament(ind1, ind2):
  104. """
  105. 二元锦标赛:先选非支配排序靠前的,再选拥挤度低(即距离远);如果都不行,则随机
  106. :param ind1: 个体1
  107. :param ind2: 个体1
  108. :return: 返回较优的个体
  109. """
  110. if ind1.rank != ind2.rank:
  111. return ind1 if ind1.rank < ind2.rank else ind2
  112. elif ind1.distance != ind2.distance:
  113. return ind1 if ind1.distance > ind2.distance else ind2
  114. else:
  115. return ind1
  116. def crossover_mutation(parent1, parent2, eta, bound_min, bound_max, objective_fun):
  117. """
  118. 交叉:二进制交叉算子(SBX),变异:多项式变异(PM)
  119. :param parent1: 父代1
  120. :param parent2: 父代2
  121. :param eta: 变异参数,越大则后代个体越逼近父代
  122. :return:
  123. """
  124. poplength = len(parent1.solution) # 解向量维数
  125. # 初始化两个后代个体
  126. offspring1 = Individual()
  127. offspring2 = Individual()
  128. offspring1.solution = np.empty(poplength)
  129. offspring2.solution = np.empty(poplength)
  130. # 二进制交叉
  131. for i in range(poplength):
  132. rand = random.random()
  133. if rand < 0.5:
  134. beta = (rand * 2) ** (1 / (eta + 1))
  135. else:
  136. beta = (1 / (2 * (1 - rand)))**(1 / (eta + 1))
  137. offspring1.solution[i] = 0.5 * ((1 + beta) * parent1.solution[i] + (1 - beta) * parent2.solution[i])
  138. offspring2.solution[i] = 0.5 * ((1 - beta) * parent1.solution[i] + (1 + beta) * parent2.solution[i])
  139. # 多项式变异
  140. for i in range(poplength):
  141. mu = random.random()
  142. if mu < 0.5:
  143. delta = 2 * mu ** (1 / (eta + 1))
  144. else:
  145. delta = (1 - (2 * (1 - mu)) ** (1 / (eta + 1)))
  146. # 只变异一个
  147. offspring1.solution[i] = offspring1.solution[i] + delta
  148. offspring1.bound_process(bound_min, bound_max)
  149. offspring2.bound_process(bound_min, bound_max)
  150. offspring1.calculate_objective(objective_fun)
  151. offspring2.calculate_objective(objective_fun)
  152. return [offspring1, offspring2]
  153. def make_new_pop(P, eta, bound_min, bound_max, objective_fun):
  154. """
  155. 选择交叉变异获得新后代
  156. :param P: 父代种群
  157. :param eta: 变异参数,越大则后代个体越逼近父代
  158. :param bound_min: 定义域下限
  159. :param bound_max: 定义域上限
  160. :param objective_fun: 目标函数
  161. :return: 子代种群
  162. """
  163. popnum = len(P) # 种群个数
  164. Q = []
  165. # 二元锦标赛选择
  166. for i in range(int(popnum / 2)):
  167. # 从种群中随机选择两个个体,进行二元锦标赛,选择一个parent
  168. i = random.randint(0, popnum - 1)
  169. j = random.randint(0, popnum - 1)
  170. parent1 = binary_tornament(P[i], P[j])
  171. parent2 = parent1
  172. while (parent1.solution == parent2.solution).all(): # 小细节all
  173. i = random.randint(0, popnum - 1)
  174. j = random.randint(0, popnum - 1)
  175. parent2 = binary_tornament(P[i], P[j])
  176. Two_offspring = crossover_mutation(parent1, parent2, eta, bound_min, bound_max, objective_fun)
  177. Q.append(Two_offspring[0])
  178. Q.append(Two_offspring[1])
  179. return Q
  180. def KUR(x):
  181. """
  182. 计算各个目标方向上的目标值
  183. :param x: 解向量
  184. :return: 字典:各个方向上的目标值(key:目标方向;value:目标值)
  185. """
  186. f = defaultdict(float)
  187. poplength = len(x)
  188. f[1] = 0
  189. f[2] = 0
  190. for i in range(poplength - 1):
  191. f[1] = f[1] + (-10) * math.exp((-0.2) * (x[i] ** 2 + x[i + 1] ** 2) ** 0.5)
  192. for i in range(poplength):
  193. f[2] = f[2] + abs(x[i]) ** 0.8 + 5 * math.sin(x[i] ** 3)
  194. return f
  195. def plot_P(P):
  196. """
  197. 给种群绘图
  198. :param P: 种群集合
  199. :return:
  200. """
  201. X = []
  202. Y = []
  203. for ind in P:
  204. X.append(ind.objective[1])
  205. Y.append(ind.objective[2])
  206. plt.xlabel('F1')
  207. plt.ylabel('F2')
  208. plt.scatter(X, Y)
  209. def main():
  210. # 初始化参数
  211. generations = 250 # 迭代次数
  212. popnum = 100 # 种群大小
  213. eta = 1 # 变异分布参数
  214. poplength = 3 # 单个个体解向量的维数
  215. bound_min = -5
  216. bound_max = 5
  217. objective_fun = KUR
  218. # 生成第一代种群
  219. P = []
  220. for i in range(popnum):
  221. P.append(Individual())
  222. P[i].solution = np.random.rand(poplength) * (bound_max - bound_min) + bound_min
  223. P[i].bound_process(bound_min, bound_max) # 越界处理
  224. P[i].calculate_objective(objective_fun) # 计算目标值
  225. # 快速非支配排序
  226. fast_non_dominated_sort(P)
  227. Q = make_new_pop(P, eta, bound_min, bound_max, objective_fun)
  228. P_t = P # 当前这一代的父代种群
  229. Q_t = Q # 当前这一代的子代种群
  230. for gen_cur in range(generations):
  231. R_t = P_t + Q_t
  232. F = fast_non_dominated_sort(R_t)
  233. P_n = [] # 即为P_t+1,表示下一代的父代
  234. i = 1
  235. # 依次将最高级别的支配平面中的节点放入到P_n中,之后更新非支配,直到达到要求的规模
  236. while len(P_n) + len(F[i]) < popnum:
  237. crowding_distance_assignment(F[i])
  238. P_n += F[i]
  239. i += 1
  240. # 按照支配排序选完之后,再按照拥挤度来选择
  241. F[i].sort(key=lambda x: x.distance)
  242. P_n = P_n + F[i][:popnum - len(P_n)]
  243. Q_n = make_new_pop(P_n, eta, bound_min, bound_max, objective_fun)
  244. # 将下一届的父代和子代成为当前的父代和子代
  245. P_t = P_n
  246. Q_t = Q_n
  247. # 可视化
  248. plt.clf()
  249. plt.title("current generation: " + str(gen_cur + 1))
  250. plot_P(P_t)
  251. plt.pause(0.1)
  252. plt.show()
  253. return 0
  254. if __name__ == "__main__":
  255. main()

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

闽ICP备14008679号