• python实现遗传算法,并绘制训练过程以及参数对比


    前言:

            本实验使用遗传算法寻找3维函数的最大/最小值,并对基因位数,种群初始大小,每次死亡个数,适应度计算方式这些参数进行修改,对比结果。

    初始代码如下:

    1. import numpy as np
    2. import matplotlib.pyplot as plt
    3. import random
    4. import time # 暂停用的,方便我录像,你们不需要
    5. # 所用的函数
    6. def Function(x_data, y_data):
    7. """
    8. :param x_data: x数值
    9. :param y_data: y数值
    10. :return: 输出表达式计算出的z
    11. """
    12. # 本来想找个能可视化捏函数,给表达式的方法,在matlab绘图中发现这个表达式长得不错,就直接用了。
    13. return 3 * (1 - x_data) ** 2 * np.exp(-x_data ** 2 - (y_data + 1) ** 2) - 10 * (
    14. x_data / 5 - x_data ** 3 - y_data ** 5) * np.exp(
    15. -x_data ** 2 - y_data ** 2) - np.exp(-(x_data + 1) ** 2 - y_data ** 2)
    16. def Get_Grid(): # 生成坐标网格
    17. """
    18. :return: 返回Function的x,y,z
    19. """
    20. # 生成坐标网格
    21. x = np.linspace(-4, 4, 100) # 坐标轴是-3~3,100个均匀分布,为了个体不跑到图片外,修改至-4~4
    22. y = np.linspace(-4, 4, 100)
    23. x, y = np.meshgrid(x, y) # 按刚刚的坐标轴生成二维矩阵
    24. z = np.array(Function(x, y)) # 调用生成函数,获得y值
    25. return x, y, z
    26. def Get_Random_gene(number, n): # 随机生成基因型
    27. """
    28. :param number: 生成个数
    29. :param n: x的总位数
    30. :return: 生成的族群
    31. """
    32. return np.random.randint(0, 2, size=(number, n + n))
    33. def Plot_Draw_F(fig, x, y, z): # 绘图,重新绘制F的图像,返回引用
    34. """
    35. :param fig: 窗口的引用
    36. :param x:
    37. :param y:
    38. :param z:
    39. :return: axes_3d,画布引用
    40. """
    41. fig.clf()
    42. axes_3d = fig.add_subplot(111, projection='3d')
    43. cmap = plt.cm.viridis # 设定变色的颜色,可选项:viridis, plasma, inferno, magma, cividis 等
    44. norm = plt.Normalize(vmin=-5, vmax=5) # 颜色变化范围,不设置就是按z轴最大最小,
    45. img_f = axes_3d.plot_surface(x, y, z, rstride=1, cstride=1, alpha=0.75, cmap=cmap, norm=norm) # 绘制3D图
    46. # 长得还是有点抽象,一会发一下三视图,就能知道函数大概形状了
    47. # 添加颜色条
    48. cbar = fig.colorbar(img_f, ax=axes_3d)
    49. cbar.set_label('Color')
    50. # 设置坐标轴范围和标签
    51. axes_3d.set_xlim(-4, 4)
    52. axes_3d.set_ylim(-4, 4)
    53. axes_3d.set_zlim(-10, 10)
    54. axes_3d.set_xlabel('X')
    55. axes_3d.set_ylabel('Y')
    56. axes_3d.set_zlabel('Z')
    57. return axes_3d
    58. def Plot_Scatter(ax, plot_gene_data, plot_z, colour): # 根据解码后数据绘制种群的散点图
    59. """
    60. :param ax: 画布引用
    61. :param plot_z: 计算出的z值
    62. :param plot_gene_data: 全部基因型转码后的数据
    63. """
    64. for i in range(len(plot_z)):
    65. ax.scatter(plot_gene_data[i][0], plot_gene_data[i][1], plot_z[i], c=colour, marker='o')
    66. # 刷新图形
    67. plt.draw()
    68. plt.pause(1e-3)
    69. # 解码,将全部二进制基因型数据转换为数值
    70. def Decoding(data, n, point): # 输入的分别是要解码的列表,x,y,的位数,小数位数
    71. """
    72. :param data: 要解码的列表,[[x符号,x整数部分,x整数部分,x整数部分,x小数部分,x小数部分,······y符号,y整数部分,x整数部分,x整数部分,y小数部分,y小数部分,],]
    73. :param n: x的总位数
    74. :param point: 小数位数
    75. :return: 二进制基因型数据转换的数值
    76. """
    77. # 在这个例子中,x,y的取值范围为-3~3,整数刚好整2位,加一位符号位,加上小数部分就-4~4了(为了不跑到图像外,修改一下图像范围),
    78. # 小数部分不用太多,整个8位,就差不多够了,所以前11位x,后11位y,正负只看第一个符号,1正0负
    79. # [x符号,x整数部分,x整数部分,x整数部分,x小数部分,x小数部分,······y符号,y整数部分,x整数部分,x整数部分,y小数部分,y小数部分,]
    80. decode_data = []
    81. for i in data: # 遍历每个个体,转码
    82. x = Decoding_to_decimal(i[0:n], n, point)
    83. y = Decoding_to_decimal(i[n:], n, point)
    84. decode_data.append([x, y])
    85. return decode_data
    86. def Decoding_to_decimal(data, n, point):
    87. # 仅一个x或y的转换
    88. integer_len = n - point
    89. decimal_data = 0
    90. for i in range(1, integer_len): # 整数部分 2^n n=0,1,2···
    91. decimal_data += data[i] * 2 ** (integer_len - i - 1)
    92. for i in range(point): # 小数部分 1/2^n n = 1,2,3···
    93. decimal_data += data[i + integer_len] / 2 ** (i + 1)
    94. return (data[0] * 2 - 1) * decimal_data
    95. def Get_gene_z(data): # 根据解码后数据,计算z值
    96. """
    97. :param data: 全部基因型转码后的数据
    98. :return: z值,z最大值,z最小值
    99. """
    100. data_z = []
    101. max_z = -float("inf")
    102. min_z = float("inf")
    103. for i in range(len(data)):
    104. data_z.append(Function(data[i][0], data[i][1]))
    105. if data_z[i] > max_z:
    106. max_z = data_z[i]
    107. if data_z[i] < min_z:
    108. min_z = data_z[i]
    109. return data_z, max_z, min_z
    110. def Get_Fitness(data, max_data, min_data, maximum): # 计算适应度,这里用z的归一化加次方
    111. """
    112. :param data: 需要计算的z值列表
    113. :param max_data: z最大值
    114. :param min_data: z最小值
    115. :param maximum: 数值较大适应度高?
    116. :return: 适应度列表
    117. """
    118. # 之所以加个1e-3是为了让最小的也有活下去可能(来自i道i的仁慈)
    119. gap = max_data - min_data # 最大最小值的差距
    120. # 下面这里加点次方能更好的提升最大数值与最小数值的差距,保证较优秀的个体不会寄
    121. if (maximum):
    122. fitness = [((i - min_data) / gap)**1 + 1e-3 for i in data] # 归一化
    123. else:
    124. fitness = [((max_data - i) / gap)**1 + 1e-3 for i in data] # 归一化
    125. return fitness
    126. def Inheritance(parents, n): # 生育时,交叉+变异,可以扔进去多个个体
    127. len_parents = len(parents) # 父辈个数
    128. child = [] # 生出的孩子
    129. for i in range(n + n): # 遍历每个基因点
    130. child.append(parents[random.randint(0, len_parents - 1)][i]) # 随机继承某一个父辈的基因
    131. # 假如我想每一轮,每个人都有10%概率突变,那么,当有11位数时,我需要 22个基因点突变概率x,
    132. # (1-x)^22约等于0.9,解得x约为0.0048,整个0.005得了
    133. if random.random() < 0.005:
    134. child[i] = 0 if child[i] == 1 else 1
    135. return child
    136. def Reproduction(number, data, n, strength): # 让新族群生育到原先族群大小
    137. """
    138. :param number: 族群大小
    139. :param data:生育前的族群基因
    140. :param n: x的总位数
    141. :param point: 小数位数
    142. :return: 剩余完成的族群
    143. """
    144. initial_len = len(data) # 初始个数
    145. len_data = initial_len # 当前个数
    146. if initial_len < 2:
    147. print("种族没人")
    148. while len_data < number: # 生够了就停下
    149. parents = random.sample(data[0:initial_len], strength) # 对象包分配,不过是随机分配,不能和子代生育
    150. data.append(Inheritance(parents, n)) # 生出的个体添加进族群
    151. len_data += 1
    152. return data
    153. def Genetic_train(fig, gene_data, number, n, point, loop, x, y, z, maximum=True): # 遗传算法训练,带过程绘制
    154. """
    155. :param fig: 窗口引用
    156. :param gene_data: 基因型
    157. :param number: 族群大小
    158. :param n: x的总位数
    159. :param point: 小数位数
    160. :param maximum: 是否求函数最大值,默认是
    161. :return: 最终的族群
    162. """
    163. new_gene = gene_data # 开始的输入就是新族群
    164. for i in range(loop): # 最大训练loop轮
    165. # 物竞天择,适者生存
    166. gene_data = new_gene
    167. decode_data = Decoding(gene_data, n, point) # 解码
    168. data_z, max_z, min_z = Get_gene_z(decode_data) # 计算z
    169. fitness = Get_Fitness(data_z, max_z, min_z, maximum) # 求适应度
    170. ax = Plot_Draw_F(fig, x, y, z) # 绘画出函数
    171. Plot_Scatter(ax, decode_data, data_z, "blue") # 绘制全部个体
    172. if max_z - min_z < 1e-2: # 认为训练完毕
    173. break
    174. # 开杀,每回死一半
    175. len_gene = len(gene_data)
    176. index_table = list(range(len_gene)) # 未选中的索引标志
    177. new_gene = [] # 新族群
    178. for j in range(len_gene // 2):
    179. selected_elements = random.choices(index_table, weights=fitness) # 根据适应度,选中生存的个体
    180. k = index_table.index(selected_elements[0]) # 根据选中的值,找到索引
    181. new_gene.append(gene_data[k]) # 生存的个体进入新族群
    182. decode_data.pop(k)
    183. data_z.pop(k)
    184. index_table.pop(k) # 活下来的不再选了
    185. fitness.pop(k)
    186. Plot_Scatter(ax, decode_data, data_z, "red") # 绘制死亡的个体
    187. new_gene = Reproduction(number, new_gene, n, 2) # 让新族群生育到原先族群大小,两个人生
    188. return new_gene
    189. if __name__ == "__main__":
    190. # 建立窗口
    191. fig = plt.figure()
    192. # 生成坐标网格
    193. x, y, z = Get_Grid()
    194. plt.pause(1) # 方便录像用,开窗口后等1秒再出图,你们建议删去
    195. # 参数设置
    196. number = 100 # 种群初始大小
    197. point = 8 # 小数位数
    198. n = point+3 # x或y长度
    199. loop = 100 # 最大训练轮数
    200. gene_data = Get_Random_gene(number, n) # 获得初始基因
    201. gene_end = Genetic_train(fig, gene_data, number, n, point, loop, x, y, z) # 族群开始进化
    202. # 显示图形,完成后不消失
    203. plt.show()

            注释写的比较清楚了,就不多解释了,

    gene_end = Genetic_train(fig, gene_data, number, n, point, loop, x, y, z)

            最后还可以再加一个参数maximum,True求最大值False求最小值,默认求最大值

            贴一下所用函数的三视图,三个极大值,两个极小值

    还想不明白,用这个主函数看一眼

    1. if __name__ == "__main__":
    2. # 建立窗口
    3. fig = plt.figure()
    4. # 生成坐标网格
    5. x, y, z = Get_Grid()
    6. Plot_Draw_F(fig,x,y,z)
    7. # plt.pause(1) # 方便录像用,开窗口后等1秒再出图,你们建议删去
    8. # # 参数设置
    9. # number = 100 # 种群初始大小
    10. # point = 8 # 小数位数
    11. # n = point+3 # x或y长度
    12. # loop = 100 # 最大训练轮数
    13. # gene_data = Get_Random_gene(number, n) # 获得初始基因
    14. # gene_end = Genetic_train(fig, gene_data, number, n, point, loop, x, y, z) # 族群开始进化
    15. # 显示图形,完成后不消失
    16. plt.show()

    参数修改:

    每一项修改都完成了上面的修改项,比如增加小数点位数的结果也修改了适应度计算。

    修改适应度计算

            通过训练过程,我们发现,有些跑到最大值附近的点,却没存活下来,这是因为我们使用的是

    random.choices(index_table, weights=fitness)

            通过weights的值,来决定存活下来的概率,值越大存活概率越高,因此,哪怕值比较高,依然有可能去世(先帝创业未半而中道崩殂),基于这点,我修改了适应度计算函数,让最高值和最低值之间的差距加大,使得优秀的个体更容易存活下来

    def Get_Fitness(data, max_data, min_data, maximum):  # 计算适应度,这里用z的归一化加次方
    1. def Get_Fitness(data, max_data, min_data, maximum): # 计算适应度,这里用z的归一化加次方
    2. """
    3. :param data: 需要计算的z值列表
    4. :param max_data: z最大值
    5. :param min_data: z最小值
    6. :param maximum: 数值较大适应度高?
    7. :return: 适应度列表
    8. """
    9. # 之所以加个1e-3是为了让最小的也有活下去可能(来自i道i的仁慈)
    10. gap = max_data - min_data # 最大最小值的差距
    11. # 下面这里加点次方能更好的提升最大数值与最小数值的差距,保证较优秀的个体不会寄
    12. if (maximum):
    13. fitness = [((i - min_data) / gap)**5 + 1e-3 for i in data] # 归一化
    14. else:
    15. fitness = [((max_data - i) / gap)**5 + 1e-3 for i in data] # 归一化
    16. return fitness

            通过实验,增加次方运算能帮助收敛,3次方有一定效果,5次方效果比较明显,更高次方效果显著。这里使用5次方为例,结果如下:

    增加小数点位数

            通过观察最后的结果,我们发现最后并没有到达最值点,这是因为我们使用二进制编码的方式来决定个体的x,y,使用了8位小数,这代表个体的取值其实是离散的,且最小粒度为2^-8 = 0.00390625

    因此,我们可以通过增加小数位数,来使得最终收敛结果更加接近真正的最值点。

    修改小数位数可以直接修改主函数中的point变量,这里我们以修改为21为例,2^-21=0.000000476837158203125

    主函数修改:

    1. if __name__ == "__main__":
    2. # 建立窗口
    3. fig = plt.figure()
    4. # 生成坐标网格
    5. x, y, z = Get_Grid()
    6. plt.pause(1) # 方便录像用,开窗口后等1秒再出图,你们建议删去
    7. # 参数设置
    8. number = 100 # 种群初始大小
    9. point = 21 # 小数位数
    10. n = point+3 # x或y长度
    11. loop = 100 # 最大训练轮数
    12. gene_data = Get_Random_gene(number, n) # 获得初始基因
    13. gene_end = Genetic_train(fig, gene_data, number, n, point, loop, x, y, z) # 族群开始进化
    14. # 显示图形,完成后不消失
    15. plt.show()

    增加小数点位数,确实能减少粒度,更趋近于最值点,但随着位数增多,收益会不断递减

    修改存活个体数

            我们可能下意识认为,减少存活个体数会加快收敛过程,但会更容易找到错误最值点,相对而言,增加存活个体数,虽然会使得收敛变慢,但找到的最值点更准确,也更能避免错误的收敛。

            但实际使用时,我们会发现,增加存活个体数,因为每次死亡的个体少,所以出生的个体也少,导致变异的个体比较少,最后往往会因为没有变异个体早停,最后往往不能寻找到更好的最值点,总是差一些,减少存活个体数会导致留下的基因型少,但是生的多,更容易变异,反倒经常比增加存活个体数结果好。

    修改Genetic_train函数,代码202行左右,这个for语句range(len_gene//2)更改为

    保留1成个体

    1. new_gene = [] # 新族群
    2. for j in range(len_gene *1// 10):
    3. selected_elements = random.choices(index_table, weights=fitness) # 根据适应度,选中生存的个体

    保留1成个体结果:

    保留9成个体 

    1. new_gene = [] # 新族群
    2. for j in range(len_gene *9// 10):
    3. selected_elements = random.choices(index_table, weights=fitness) # 根据适应度,选中生存的个体

    酌情使用

    增加初始个体数目:

    无明显变化(计算时间除外)结果肯定是更好的,毕竟搜索的点更多了,但感觉不如多次取最好值。视频过长就不粘贴了

  • 相关阅读:
    Codeforces Round 731 (Div 3)(A - F)
    政府信息化与电子政务、企业信息化与电子商务、数据库和数据仓库的区别、商业智能系统处理过程、数据仓库结构图、数据挖掘、数据仓库和数据湖的对比
    【愚公系列】2022年07月 Go教学课程 023-Go容器之列表
    SQLite3
    【刷题系列】链表经典OJ题
    STM8应用笔记8.UART应用2
    uboot移植-野火imx6ull
    Element UI 实战:跨页保存表格选中状态与判断状态可选性的高效方案
    wpf中的Border和Background
    Android使用osmdroid加载在线地图,离线地图以及各种填坑姿势
  • 原文地址:https://blog.csdn.net/weixin_58196051/article/details/133383611