通过阅读本文,你将了解遗传算法(Sciopt-kit GA)的具体实现。

一、外部库学习任务表

按需要学习,在学习完成后,在下方打勾:

  • [x] import numpy as np
  • [ ] from .base impot SkoBase
  • [x] from sko.tools import func_transformer
  • [ ] from abc import ABCMeta, abstractmethod
  • [x] from .operators import crossover, mutation, ranking, selection

1.1 def func_transformer(func)

使用指定模式 mode, 对 func 进行优化,返回一个优化后的函数。

预备知识备注:

  1. getattr(object, name[, default]) 返回一个对象的属性值,default 为默认值
  2. @lru_cache(@cache 3.9)装饰器,缓存某个函数的运行结果,以避免重复执行
  3. multiprocessing.Pool 对象,通过使用子进程而非子线程执行并发操作
  4. multiprocessing.dummy.Pool 对象,通过使用子线程执行并发操作
  1. 确定 mode 值,mode 可能值为 :

('common', 'multithreading', 'multiprocessing', 'vectorization', 'cached', 'others')

  1. 根据 mode 值对函数进行优化:

二、类库的主体函数

2.1 GeneticAlgorithmBase 类解读

2.1.1 GABase 类包含的属性(字段)

为了在大纲里显示更多文字,将 GeneticAlgorithmBase 简写成 GABase
字段名注释
func经过优化后的目标函数
size_pop种群的数量,assert 其为偶数,默认 50
max_iter最大迭代次数,默认 200
prob_mut编译的概率,默认 0.001
n_dim变量的个数(问题的维度)
early_stop提取终止的标志,整数,最优值出现了 early_stop 次
has_constraint是否有约束条件
constraint_eq等式约束,默认空
constraint_ueq不等式约束,默认空
Chrom种群,GA 中实现为由基因的个体
X运算过程变量,由 chrom2x 得到
Y_raw运算过程变量,由 func(X) 计算得到,迭代个体的真实目标函数值
Y运算过程变量,由 x2y 得到
FitV运算过程变量,变量(种群)的适应度
generation_best_X过程记录变量,记录着每一代种群适应度最大个体的 X
generation_best_Y过程记录变量,记录着每一代种群适应度最大个体的 Y
all_history_Y过程记录变量,记录着每一代种群所有个体的 Y 值
all_history_FitV过程记录变量,记录着每一代种群所有个体的适应度
best_x迭代结束的结果,为 generation_best_X 的最小值
best_y迭代结束的结果,为 generation_best_X 的最小值

2.1.2 GABase 类包含的方法

@ 指的是 abstractmethod,即抽象方法

方法名注释
chrom2x(Chrom)@a基因转换成实数
x2y由 X 计算得到 Y,应用了罚函数,将约束转换成无约束
ranking@a排序
selection@a选择
crossover@a交叉
mutation@a变异
run(max_iter)执行迭代过程

2.1.3 GABase 类 run 方法解读

  1. 赋值最大迭代数 self.max_iter,赋值用于判断提前结束标志的临时变量 best
  2. 进入种群的迭代循环:

    1. 计算 X、Y(初始化种群?)
    2. 排序、选择、交叉、变异
    3. 记录 generation_best_X、generation_best_Y、all_history_Y、all_history_FitV
    4. 如果指定可以提前结束迭代,当最优值出现 early_stop 次,则跳出迭代循环
  3. 返回 generation_best_Y 中记录着的最小个体

2.1.4 GABase 类 x2y 方法解读

存在约束条件,则对每个个体应用罚函数(看下表),罚因子是 1e5:

约束条件类型个体罚函数的计算方法
等式约束$\sum{\vert constranint \vert}$
(constranint 为等式约束求得的结果,为 0 说明满足等式约束)
不等式约束$\sum{\max(0, constraint)}$
(constranint 为不等式约束求得的结果,大于0,说明不满足不等式约束)

援引作者对罚函数缺点的说明:

  • 非常依赖初始值的选择,如果初始值“不小心”全随机到了可行域外,那么算法会在可行域外迭代
  • 约束不能太多,否则目标函数非常“不平滑”,最后的优化结果什么也不是
  • 最拖累效果的是等式约束,超过两个基本就完蛋了。

2.2 GA 类解读

GA 类继承自 GeneticAlgorithmBase 基类,该类实现了 chrom2x、排序、选择、交叉、变异等过程,接下来将这些过程进行解读。

2.2.1 GA 类额外包含的属性(字段)

预备知识备注:

  1. np.where(_condition, x, y_) 矢量化 + 广播,相当于 x if condition else y
额外字段名注释
lbndarray,变量的下界
ubndarray,变量的上界
precisionndarray,变量的精确度
Lindndarray,变量基因占用的位数,用于确定 X 的位置:
$Lind_i=⌈\log_2{(\frac{ub_i-lb_i}{precision_i}+1)}⌉$
int_mode_ndarray of boolean,变量是否为整数
int_mode布尔值,进入整数模式的标识
un_extend if int_mode进入整数模式,染色体最大的取值:
$un\_extend_i = \begin{cases} lb_i + 2^{Lind_i}-1) \cdot precision_i & \text{if } int\_mode\__i \\ ub_i & \text{otherwise} \end{cases}$
len_chrom基因的总个数

2.2.2 GA 类包含的方法

预备知识备注:

  1. np.random.randint(_low, high: int, size: tuple of ints_) 根据上下界和形状创建随机数组。
  2. 格雷码(Gray Code)是用 0-1 编码数字的一种方法。?
  3. _ndarray_.cumsum() 按照顺序累加元素,得到新列表,例如输入 array([0, 1, 2, 3, 4]) 则输出 array([ 0, 1, 3, 6, 10])。该方法用于确定基因的位置。
  4. _ndarray_.cumsum(_axis=x_) 在某轴上按照顺序累加元素,得到新列表。
  5. 位操作?
方法名注释
crtbp初始化种群,Chrom is ndarray of ndarray of byte
gray2rv(_gray_code_)将(用格雷码表示的)染色体片段转换为实数
chrom2x(_Chrom_)将格雷码数组(染色体数组)转换为变量数组 X
ranking计算种群的适应度,这里 $FitV=-Y$
selection从种群中随机抽取 3 个个体进行比赛,获胜者进入下一代,下一代种群满则停止比赛
crossover分两半按顺序配对,选取一个随机区间,交换区间内父代的基因,得到两个子代
mutation按照概率选择某些基因发生变异

2.2.3 GA 类 chrom2x 方法解读

chrom2x 可以看作是基因表达成性状的过程,这里指由格雷码编码的基因表达为实数种群。方法的大致逻辑如下:

  1. 获取基因的位置信息
  2. 初始化 X,根据位置信息,切片转换成实数并放入 X
  3. 基因是按照精细度划分成段得到,并不包含起始信息,于是进一步转化 $x_i:=lb_i+(ub\_extend_i-lb_i)\cdot x_i$。
  4. 如果进入整数模式,转换出来的整数可能越界,如是则转换为最大值。

    2.2.4 位运算在基因交叉、变异中的运用

    位运算包括:与、或、异或、取反、左移、右移。为了方便复习,总结一下与、或和异或的运算规则:

位运算符号运算规则
&$1\&1=1;1\&0=0;0\&0=0$
|$1 \vert 1=1;1 \vert 0=1;0 \vert 0=0$
异或^$1∧1=0;1∧0=1;0∧0=1$
取反未知1 变成 0;0 变成 1

GA 类将基因表示成 0-1 构成的数组,应用位运算可以有效的完成交叉和编译操作,非常巧妙。下面我将分别进行说明:
一、基于源码解读位操作在基因「交叉」中的运用
列举某个位置的基因交叉前后的状态:

父代 1 交叉前父代 2 交叉前父代 1 交叉后父代 2 交叉后
1001
1111
0000
0110

思路巧妙,我也不过多解释了,看总结的整个过程:

父代 1 交叉前(Chrom1)父代 2 交叉前(Chrom2)mask2 = Chrom1 ^ Chrom2Chrom1 ^ mask2(父代 1 交叉后)Chrom2 ^ mask2(父代 2 交叉后)
10101
11011
00000
01110

二、基于源码解读位操作在基因「变异」中的运用
思路巧妙,不过多解释,看下面这个过程:

原始基因(Chrom)变异(mask)变异后的基因(Chrom ^ mask)
011
110
000
101

三、心得体会

  1. 使用 numpy 进行矢量化运算,确实可以大幅提升程序的运行效率。
  2. 位运算用于 0 1 基因交叉和变异非常巧妙。
  3. 遗传算法的实现并不能,阅读源码的过程很有启发性,是精修编程语言的有效方法。
  4. 遗传算法模拟了生物进化的过程,貌似有一定的科学依据。
  5. 现有一个疑惑:如何才能更加有效的处理约束条件。
  6. 对二进制编码与实数的映射还需要进一步学习。
最后修改:2024 年 03 月 02 日
如果觉得我的文章对你有用,请随意赞赏