├── .github └── workflows │ └── pythonapp.yml ├── Chromosome.py ├── GeneticAlgorithm.py ├── README.md └── result.png /.github/workflows/pythonapp.yml: -------------------------------------------------------------------------------- 1 | name: Python application 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Set up Python 3.7 13 | uses: actions/setup-python@v1 14 | with: 15 | python-version: 3.7 16 | - name: Install dependencies 17 | run: | 18 | python -m pip install --upgrade pip 19 | pip install -r requirements.txt 20 | - name: Lint with flake8 21 | run: | 22 | pip install flake8 23 | # stop the build if there are Python syntax errors or undefined names 24 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 25 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 26 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 27 | - name: Test with pytest 28 | run: | 29 | pip install pytest 30 | pytest 31 | -------------------------------------------------------------------------------- /Chromosome.py: -------------------------------------------------------------------------------- 1 | import math 2 | import random 3 | 4 | 5 | class Chromosome: 6 | def __init__(self, bounds, precision): 7 | self.x1 = 1 8 | self.x2 = 1 9 | 10 | self.y = 0 11 | 12 | self.code_x1 = '' 13 | self.code_x2 = '' 14 | 15 | self.bounds = bounds 16 | 17 | temp1 = (bounds[0][1] - bounds[0][0]) * precision 18 | self.code_x1_length = math.ceil(math.log(temp1, 2)) 19 | 20 | temp2 = (bounds[1][1] - bounds[1][0]) * precision 21 | self.code_x2_length = math.ceil(math.log(temp2, 2)) 22 | 23 | self.rand_init() 24 | self.func() 25 | 26 | 27 | def rand_init(self): 28 | for i in range(self.code_x1_length): 29 | self.code_x1 += str(random.randint(0, 1)) 30 | 31 | for i in range(self.code_x2_length): 32 | self.code_x2 += str(random.randint(0, 1)) 33 | 34 | def decoding(self, code_x1, code_x2): 35 | self.x1 = self.bounds[0][0] + int(code_x1, 2) * (self.bounds[0][1] - self.bounds[0][0]) / ( 36 | 2 ** self.code_x1_length - 1) 37 | self.x2 = self.bounds[1][0] + int(code_x2, 2) * (self.bounds[1][1] - self.bounds[1][0]) / ( 38 | 2 ** self.code_x2_length - 1) 39 | 40 | def func(self): 41 | self.decoding(self.code_x1, self.code_x2) 42 | self.y = 21.5 + self.x1 * math.sin(4 * math.pi * self.x1) + self.x2 * math.sin(20 * math.pi * self.x2) 43 | 44 | 45 | if __name__ == '__main__': 46 | a = [[-3, 1], [4, 5]] 47 | chromosome = Chromosome(a, 10) 48 | print(chromosome.code_x1_length) 49 | print(chromosome.code_x2_length) 50 | chromosome.decoding('110111', '1111') 51 | print(chromosome.x1, " ", chromosome.x2) 52 | print(random.randint(0, 1)) 53 | -------------------------------------------------------------------------------- /GeneticAlgorithm.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import random 3 | import matplotlib.pyplot as plt 4 | 5 | 6 | from Chromosome import Chromosome 7 | 8 | 9 | class GeneticAlgorithm: 10 | 11 | def __init__(self, bounds, precision, pm, pc, pop_size, max_gen): 12 | """ 13 | 算法初始化 14 | :param bounds: 变量范围 15 | :param precision: 精度 16 | :param pm: 变异概率 17 | :param pc: 交叉概率 18 | :param pop_size: 种群大小 19 | :param max_gen: 最大迭代次数 20 | :return: 21 | """ 22 | self.bounds = bounds 23 | self.precision = precision 24 | self.pm = pm 25 | self.pc = pc 26 | self.pop_size = pop_size 27 | self.max_gen = max_gen 28 | 29 | self.pop = [] 30 | self.bests = [0] * max_gen 31 | self.g_best = 0 32 | 33 | def ga(self): 34 | """ 35 | 算法主函数 36 | :return: 37 | """ 38 | self.init_pop() 39 | best = self.find_best() 40 | self.g_best = copy.deepcopy(best) 41 | y = [0] * self.pop_size 42 | for i in range(self.max_gen): 43 | self.cross() 44 | self.mutation() 45 | self.select() 46 | best = self.find_best() 47 | self.bests[i] = best 48 | if self.g_best.y < best.y: 49 | self.g_best = copy.deepcopy(best) 50 | y[i] = self.g_best.y 51 | print(self.g_best.y) 52 | 53 | # plt 54 | plt.figure(1) 55 | x = range(self.pop_size) 56 | plt.plot(x, y) 57 | plt.ylabel('generations') 58 | plt.xlabel('function value') 59 | plt.show() 60 | 61 | 62 | def init_pop(self): 63 | """ 64 | 初始化种群 65 | :return: 66 | """ 67 | for i in range(self.pop_size): 68 | chromosome = Chromosome(self.bounds, self.precision) 69 | self.pop.append(chromosome) 70 | 71 | def cross(self): 72 | """ 73 | 交叉 74 | :return: 75 | """ 76 | for i in range(int(self.pop_size / 2)): 77 | if self.pc > random.random(): 78 | # randon select 2 chromosomes in pops 79 | i = 0 80 | j = 0 81 | while i == j: 82 | i = random.randint(0, self.pop_size-1) 83 | j = random.randint(0, self.pop_size-1) 84 | pop_i = self.pop[i] 85 | pop_j = self.pop[j] 86 | 87 | # select cross index 88 | pop_1 = random.randint(0, pop_i.code_x1_length - 1) 89 | pop_2 = random.randint(0, pop_i.code_x2_length - 1) 90 | 91 | # get new code 92 | new_pop_i_code1 = pop_i.code_x1[0: pop_1] + pop_j.code_x1[pop_1: pop_i.code_x1_length] 93 | new_pop_i_code2 = pop_i.code_x2[0: pop_2] + pop_j.code_x2[pop_2: pop_i.code_x2_length] 94 | 95 | new_pop_j_code1 = pop_j.code_x1[0: pop_1] + pop_i.code_x1[pop_1: pop_i.code_x1_length] 96 | new_pop_j_code2 = pop_j.code_x2[0: pop_2] + pop_i.code_x2[pop_2: pop_i.code_x2_length] 97 | 98 | pop_i.code_x1 = new_pop_i_code1 99 | pop_i.code_x2 = new_pop_i_code2 100 | 101 | pop_j.code_x1 = new_pop_j_code1 102 | pop_j.code_x2 = new_pop_j_code2 103 | 104 | def mutation(self): 105 | """ 106 | 变异 107 | :return: 108 | """ 109 | for i in range(self.pop_size): 110 | if self.pm > random.random(): 111 | pop = self.pop[i] 112 | # select mutation index 113 | index1 = random.randint(0, pop.code_x1_length-1) 114 | index2 = random.randint(0, pop.code_x2_length-1) 115 | 116 | i = pop.code_x1[index1] 117 | i = self.__inverse(i) 118 | pop.code_x1 = pop.code_x1[:index1] + i + pop.code_x1[index1+1:] 119 | 120 | i = pop.code_x2[index2] 121 | i = self.__inverse(i) 122 | pop.code_x2 = pop.code_x2[:index2] + i + pop.code_x2[index2+1:] 123 | 124 | 125 | def select(self): 126 | """ 127 | 轮盘赌选择 128 | :return: 129 | """ 130 | # calculate fitness function 131 | sum_f = 0 132 | for i in range(self.pop_size): 133 | self.pop[i].func() 134 | 135 | # guarantee fitness > 0 136 | min = self.pop[0].y 137 | for i in range(self.pop_size): 138 | if self.pop[i].y < min: 139 | min = self.pop[i].y 140 | if min < 0: 141 | for i in range(self.pop_size): 142 | self.pop[i].y = self.pop[i].y + (-1) * min 143 | 144 | # roulette 145 | for i in range(self.pop_size): 146 | sum_f += self.pop[i].y 147 | p = [0] * self.pop_size 148 | for i in range(self.pop_size): 149 | p[i] = self.pop[i].y / sum_f 150 | q = [0] * self.pop_size 151 | q[0] = 0 152 | for i in range(self.pop_size): 153 | s = 0 154 | for j in range(0, i+1): 155 | s += p[j] 156 | q[i] = s 157 | # start roulette 158 | v = [] 159 | for i in range(self.pop_size): 160 | r = random.random() 161 | if r < q[0]: 162 | v.append(self.pop[0]) 163 | for j in range(1, self.pop_size): 164 | if q[j - 1] < r <= q[j]: 165 | v.append(self.pop[j]) 166 | self.pop = v 167 | 168 | def find_best(self): 169 | """ 170 | 找到当前种群中最好的个体 171 | :return: 172 | """ 173 | best = copy.deepcopy(self.pop[0]) 174 | for i in range(self.pop_size): 175 | if best.y < self.pop[i].y: 176 | best = copy.deepcopy(self.pop[i]) 177 | return best 178 | 179 | def __inverse(self, i): 180 | """ 181 | 变异时候用的,将 1 变为 0 ,0 变为 1 182 | :param i: 变异位置 183 | :return: 184 | """ 185 | r = '1' 186 | if i == '1': 187 | r = '0' 188 | return r 189 | 190 | if __name__ == '__main__': 191 | bounds = [[-3, 12.1], [4.1, 5.8]] 192 | precision = 100000 193 | algorithm = GeneticAlgorithm(bounds, precision, 0.01, 0.8, 100, 100) 194 | algorithm.ga() 195 | pass 196 | 197 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## python版遗传算法 2 | 更好阅读体验,请访问(http://tianle.me/2017/04/19/GA/ ) 3 | 4 | 遗传算法(genetic algorithm, GA)是一种进化算法,其基本原理是仿效生物界中的“物竞天择,适者生存”的演化法则。遗传算法是把问题参数编码为染色体,再利用迭代的方式进行选择、交叉以及变异等运算来交换种群中染色体的信息,最终生成符合优化目标的染色体。 5 | 6 | ### 名词解释 7 | 在遗传算法中,染色体对应的是数据或数组,通常是由一维的串结构数据来表示,串上各个位置对应基因的取值。基因组成的串就是染色体(chromosome),或者称为基因型个体(individual)。一定数量的个体组成了群体(population)。群体中的个体数目称为群体大小(population size),也成为群体规模。而各个个体对环境的适应程度叫适应度(fitness)。 8 | 9 | ### 基本步骤 10 | #### 编码 11 | GA在进行搜索之前先将解空间的解数据表示成遗传空间的基因型串结构数据,这些串结构数据的不同组合便构成了不同的点。 12 | #### 初始群体的生成 13 | 随机产生N个初始串结构数据,每个串结构数据称为一个个体,N个个体构成了一个群体。GA以这N个串结构数据作为初始点开始进化。 14 | #### 适应度评估 15 | 适应度表明个体或解的优劣性。不同的问题,适应度函数的定义方式也不同。 16 | #### 选择 17 | 选择的目的是为了从当前群体中选出优良的个体,使它们有机会作为父代为下一代繁殖子孙。遗传算法通过选择过程体现这一思想,进行选择的原则是适应度强的个体为下一代贡献一个或多个后代的概率大。选择体现了达尔文的适者生存原则。 18 | #### 交叉 19 | 交叉操作是遗传算法中最主要的遗传操作。通过交叉操作可以得到新一代个体,新个体组合了其父辈个体的特性。交叉体现了信息交换的思想。 20 | #### 变异 21 | 变异首先在群体中随机选择一个个体,对于选中的个体以一定的概率随机地改变串结构数据中某个串的的值。同生物界一样,GA中变异发生的概率很低,通常取值很小。 22 | 23 | ### 实例详解 24 | 之前已经使用matlab实现了一次,由于现在又布置了作业,正好现在对python不是特别熟悉,那就写个代码练练手吧。 25 | #### 目标函数 26 | max f (x1, x2) = 21.5 + x1·sin(4p x1) + x2·sin(20p x2) 27 | 28 | s. t. -3.0 <= x1 <= 12.1 29 | 4.1 <= x2 <= 5.8 30 | 31 | ![function](http://img.tianle.me/image/20170419/maxfunction.jpg) 32 | 33 | ```python 34 | def func(self): 35 | self.decoding(self.code_x1, self.code_x2) 36 | self.y = 21.5 + self.x1 * math.sin(4 * math.pi * self.x1) + self.x2 * math.sin(20 * math.pi * self.x2) 37 | ``` 38 | 39 | #### 二进制编码 40 | 在刚刚提到的遗传算法中,我们首先要将数据进行编码,这里我们采用二进制的方式进行编码。第一步,我们根据题目的介绍可以得知该函数含有两个变量,以及各自的定义域。在二进制编码中,我们首先要先计算它的编码长度。计算公式如下: 41 | $${2^{{m_j} - 1}} < ({b_j} - {a_j})*precision \le {2^{{m_j}}} - 1$$ 42 | 其中precision为精度,如小数点后5位,则precision=10^5,mj为编码长度,${x_j} \in [{a_j},{b_j}]$ 43 | #### 二进制解码 44 | 解码即编码的逆过程: 45 | $${x_j} = {a_j} + {\rm{decimal}}(substrin{g_j}) \times \frac{{{b_j} - {a_j}}}{{{2^{{m_j}}} - 1}}$$ 46 | ![function](http://img.tianle.me/image/20170419/coding.jpg) 47 | 48 | ```python 49 | def decoding(self, code_x1, code_x2): 50 | self.x1 = self.bounds[0][0] + int(code_x1, 2) * (self.bounds[0][1] - self.bounds[0][0]) / ( 51 | 2 ** self.code_x1_length - 1) 52 | self.x2 = self.bounds[1][0] + int(code_x2, 2) * (self.bounds[1][1] - self.bounds[1][0]) / ( 53 | 2 ** self.code_x2_length - 1) 54 | ``` 55 | 56 | #### 种群初始化 57 | 编码完成那我们就开始对种群初始化吧,为了简便我采用了随机地方式进行初始化。 58 | ```python 59 | def __init__(self, bounds, precision): 60 | self.x1 = 1 61 | self.x2 = 1 62 | 63 | self.y = 0 64 | 65 | self.code_x1 = '' 66 | self.code_x2 = '' 67 | 68 | self.bounds = bounds 69 | 70 | temp1 = (bounds[0][1] - bounds[0][0]) * precision 71 | self.code_x1_length = math.ceil(math.log(temp1, 2)) 72 | 73 | temp2 = (bounds[1][1] - bounds[1][0]) * precision 74 | self.code_x2_length = math.ceil(math.log(temp2, 2)) 75 | 76 | self.rand_init() 77 | self.func() 78 | 79 | def rand_init(self): 80 | for i in range(self.code_x1_length): 81 | self.code_x1 += str(random.randint(0, 1)) 82 | 83 | for i in range(self.code_x2_length): 84 | self.code_x2 += str(random.randint(0, 1)) 85 | ``` 86 | #### 选择 87 | 选择我们采用轮盘赌方式进行选择,主要思想是适应度高的,被选择到的概率大。 88 | ![function](http://img.tianle.me/image/20170419/selection.jpg) 89 | 没怎么优化,用了一堆for循环。。。。 90 | ```python 91 | def select(self): 92 | """ 93 | 轮盘赌选择 94 | :return: 95 | """ 96 | # calculate fitness function 97 | sum_f = 0 98 | for i in range(self.pop_size): 99 | self.pop[i].func() 100 | 101 | # guarantee fitness > 0 102 | min = self.pop[0].y 103 | for i in range(self.pop_size): 104 | if self.pop[i].y < min: 105 | min = self.pop[i].y 106 | if min < 0: 107 | for i in range(self.pop_size): 108 | self.pop[i].y = self.pop[i].y + (-1) * min 109 | 110 | # roulette 111 | for i in range(self.pop_size): 112 | sum_f += self.pop[i].y 113 | p = [0] * self.pop_size 114 | for i in range(self.pop_size): 115 | p[i] = self.pop[i].y / sum_f 116 | q = [0] * self.pop_size 117 | q[0] = 0 118 | for i in range(self.pop_size): 119 | s = 0 120 | for j in range(0, i+1): 121 | s += p[j] 122 | q[i] = s 123 | # start roulette 124 | v = [] 125 | for i in range(self.pop_size): 126 | r = random.random() 127 | if r < q[0]: 128 | v.append(self.pop[0]) 129 | for j in range(1, self.pop_size): 130 | if q[j - 1] < r <= q[j]: 131 | v.append(self.pop[j]) 132 | self.pop = v 133 | ``` 134 | #### 变异 135 | 这里的变异,我们先以变异概率,从种群中选一个,然后对选中的个体,随机选一个变异位点进行变异。 136 | ![function](http://img.tianle.me/image/20170419/mutation.jpg) 137 | ```python 138 | def mutation(self): 139 | """ 140 | 变异 141 | :return: 142 | """ 143 | for i in range(self.pop_size): 144 | if self.pm > random.random(): 145 | pop = self.pop[i] 146 | # select mutation index 147 | index1 = random.randint(0, pop.code_x1_length-1) 148 | index2 = random.randint(0, pop.code_x2_length-1) 149 | 150 | i = pop.code_x1[index1] 151 | i = self.__inverse(i) 152 | pop.code_x1 = pop.code_x1[:index1] + i + pop.code_x1[index1+1:] 153 | 154 | i = pop.code_x2[index2] 155 | i = self.__inverse(i) 156 | pop.code_x2 = pop.code_x2[:index2] + i + pop.code_x2[index2+1:] 157 | ``` 158 | #### 交叉 159 | 这里采用单点交叉法。随机从种群中选两个个体,然后再随机选一个交叉点,交换位置。看图 = . = 160 | ![function](http://img.tianle.me/image/20170419/crossover.jpg) 161 | ![function](http://img.tianle.me/image/20170419/crossoverCode.jpg) 162 | ```python 163 | def cross(self): 164 | """ 165 | 交叉 166 | :return: 167 | """ 168 | for i in range(int(self.pop_size / 2)): 169 | if self.pc > random.random(): 170 | # randon select 2 chromosomes in pops 171 | i = 0 172 | j = 0 173 | while i == j: 174 | i = random.randint(0, self.pop_size-1) 175 | j = random.randint(0, self.pop_size-1) 176 | pop_i = self.pop[i] 177 | pop_j = self.pop[j] 178 | 179 | # select cross index 180 | pop_1 = random.randint(0, pop_i.code_x1_length - 1) 181 | pop_2 = random.randint(0, pop_i.code_x2_length - 1) 182 | 183 | # get new code 184 | new_pop_i_code1 = pop_i.code_x1[0: pop_1] + pop_j.code_x1[pop_1: pop_i.code_x1_length] 185 | new_pop_i_code2 = pop_i.code_x2[0: pop_2] + pop_j.code_x2[pop_2: pop_i.code_x2_length] 186 | 187 | new_pop_j_code1 = pop_j.code_x1[0: pop_1] + pop_i.code_x1[pop_1: pop_i.code_x1_length] 188 | new_pop_j_code2 = pop_j.code_x2[0: pop_2] + pop_i.code_x2[pop_2: pop_i.code_x2_length] 189 | 190 | pop_i.code_x1 = new_pop_i_code1 191 | pop_i.code_x2 = new_pop_i_code2 192 | 193 | pop_j.code_x1 = new_pop_j_code1 194 | pop_j.code_x2 = new_pop_j_code2 195 | ``` 196 | 197 | #### 算法主流程 198 | 至此,遗传的主要框架已经完毕,下面展示主流程,及画图部分代码。 199 | ```python 200 | def ga(self): 201 | """ 202 | 算法主函数 203 | :return: 204 | """ 205 | self.init_pop() 206 | best = self.find_best() 207 | self.g_best = copy.deepcopy(best) 208 | y = [0] * self.pop_size 209 | for i in range(self.max_gen): 210 | self.cross() 211 | self.mutation() 212 | self.select() 213 | best = self.find_best() 214 | self.bests[i] = best 215 | if self.g_best.y < best.y: 216 | self.g_best = copy.deepcopy(best) 217 | y[i] = self.g_best.y 218 | print(self.g_best.y) 219 | 220 | # plt 221 | plt.figure(1) 222 | x = range(self.pop_size) 223 | plt.plot(x, y) 224 | plt.ylabel('generations') 225 | plt.xlabel('function value') 226 | plt.show() 227 | ``` 228 | #### 实验结果图 229 | ![function](http://img.tianle.me/image/20170419/result.png) 230 | 231 | ### 总结 232 | 在编码的时候,我偷懒了一下,把两个变量拆开写,x1和x2,导致之后的操作变得异常复杂,并且不利于代码重构。 233 | 程序中过多的使用了for循环,并没有对此进行优化。 234 | 针对上述两个问题,在此记录一下。 235 | 236 | ### 程序完整代码 237 | [Genetic-Algorithms](https://github.com/zhangtianle/Genetic-Algorithms) 238 | 239 | ### 参考资料 240 | 《MATLAB智能算法-30个案例分析》 241 | -------------------------------------------------------------------------------- /result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangtianle/Genetic-Algorithms/216d1d1f747a13b830b8cd0806a0db1c938c48a9/result.png --------------------------------------------------------------------------------