面试过程是一个很好的查漏补缺的机会,要好好梳理,复盘面试过程。
比如最近面试就发现,我已经把一年前课堂上学的遗传算法和优化方法,全数还给老师了,渣都不剩O_O。。。
这对得起辛辛苦苦上的课和呕心沥血写的大作业吗?
于是抓紧复习,真的多亏当年呕心沥血写的大作业,很快勾起了我的回忆~
遗传算法思想很简单,关键在于在一定概率下选择比较好的样本留下,而结果改进的关键在于交叉和变异。
当年做的单目标优化问题主要用于找到高维函数的极值,代码中用的是20维。简单来说就是求一组自变量(20个),找到类似于
这样的函数的极值(最小值或者最大值)。
当然实际中函数的样子更加复杂(代码中有当时的6道题目,可以感受一下)
遗传算法解题步骤归结下来分为几步:
- 初始化种群样本(比如100个个体构成种群,每个个体有20个自变量的初始值);
- 计算适应度(把自变量代入要求解的函数中,有些函数要求求最小值,那么适应度要计算为函数值的倒数,保证越接近正确的答案适应度越大);
- 轮盘赌选择样本;
- 交叉:以一定概率将两个个体交换一部分自变量;
- 变异:以一定概率将个体中某些值变异成其他值;
- 重复迭代2~5步,直到得到最优个体和最优解。
严肃一点就是
其中,选择算子轮盘赌选择样本的理论如下:
首先来一个饼图,不对是轮盘
三个颜色代表三个个体,饼的大小代表每个个体的适应度大小。
理论上来说,饼越大,被飞刀戳中的概率越大,这就是通过适应度选择较好个体的过程。
具体写成代码怎么做呢?这里用一个累积概率的方法,每个个体排排站,将它们的概率也拼在一起。
想象一条直线,两端代表0和1,中间每个个体凭实力(概率)占据一段,实力越强占据的线段越长。这样我们随机生成一个0~1之间的数,落在长线段区间的机会就比较大。把直线圈成一个圆形,就是轮盘赌的原理。
具体代码如下,做一个参考,建议先从主函数开始看:
六个题目分别为:
咦,怎么才5个?不知道为啥代码里面有6个,代码中对应的是第5个题目。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 18-11-1 上午10:06
# @Author : Xier
# @File : high_d.py
# 高维单目标优化问题
import numpy as np
import copy
import math
import random
import matplotlib.pyplot as plt
from pylab import mpl
mpl.rcParams['font.sans-serif'] = 'NSimSun, Times New Roman'
mpl.rcParams['axes.unicode_minus']=False
def init(range_ab,val_num,popusize):
init_gen = np.random.uniform(range_ab[0], range_ab[1], (popusize,val_num))
return init_gen
def cal_fitness(dec_gen):
f = np.zeros((dec_gen.shape[0],1))
f1 = np.zeros((dec_gen.shape[0], 1))
f2 = np.zeros((dec_gen.shape[0], 1))
# 题目一
# for i in range(dec_gen.shape[0]):
# for j in range(dec_gen.shape[1]):
# if j == 0:
# f[i] = dec_gen[i][j]*dec_gen[i][j]
# else:
# f[i] += dec_gen[i][j]*dec_gen[i][j]
# 题目二
# for i in range(dec_gen.shape[0]):
# for j in range(dec_gen.shape[1]):
# if j == 0:
# f[i] = math.ceil(dec_gen[i][j]+0.5)**2
# else:
# f[i] += math.ceil(dec_gen[i][j]+0.5)**2
# # 题目三
# for i in range(dec_gen.shape[0]):
# for j in range(dec_gen.shape[1]):
# for p in range(j+1):
# if p == 0:
# x = dec_gen[i][p]
# else:
# x += dec_gen[i][p]
# if j == 0:
# f[i] = x**2
# else:
# f[i] += x**2
# 题目四
# for i in range(dec_gen.shape[0]):
# for j in range(dec_gen.shape[1]):
# if j == 0:
# f1[i] = abs(dec_gen[i][j])
# else:
# f1[i] += abs(dec_gen[i][j])
# for j in range(dec_gen.shape[1]):
# if j == 0:
# f2[i] = abs(dec_gen[i][j])
# else:
# f2[i] *= abs(dec_gen[i][j])
#
# f[i]=f1[i]+f2[i]
# 题目五
# for i in range(dec_gen.shape[0]):
# for j in range(dec_gen.shape[1]-1):
# if j == 0:
# f[i] = 100*(dec_gen[i][j+1]-dec_gen[i][j]**2)**2+(1-dec_gen[i][j])**2
# else:
# f[i] += 100*(dec_gen[i][j+1]-dec_gen[i][j]**2)**2+(1-dec_gen[i][j])**2
# 题目六
A = 1
for i in range(dec_gen.shape[0]):
for j in range(dec_gen.shape[1]):
if j == 0:
f[i] = dec_gen[i][j]**2-A*math.cos(2*math.pi*dec_gen[i][j])
else:
f[i] += dec_gen[i][j]**2-A*math.cos(2*math.pi*dec_gen[i][j])
f[i] = dec_gen.shape[1]*A + f[i]
return f
def mate_selection(y, gen, val_num):
fitness = 1/(y+0.1) # 找使函数值最小的个体,函数值越小适应度越大
P = fitness.copy()
Q = fitness.copy()
sum_fitness = sum(fitness)
for i in range(len(P)):
P[i] = fitness[i]/sum_fitness # 为每个个体赋一个选中的概率
Q[i] = sum(P[:i+1]) # 记录至今为止的概率
mate_np = np.zeros((1, gen.shape[1]))
ran_select = np.random.random(len(fitness))#-1 # 随机生成0~1的数
# 轮盘赌,理论上来说蛋糕面积越大,随机戳到的几率越大
for r in range(len(ran_select)):
for q in range(len(Q)):
if ran_select[r] <= Q[q]:
mate_np = np.vstack((mate_np, gen[q, :]))
break
return mate_np[1:,:]
def cross_able(gen,pc):
# cross_np = gen[0].copy()
# no_cross_np = gen[0].copy()
ran_select = np.random.random(gen.shape[0])
cross_np = gen[np.where(ran_select<pc),:]
no_cross_np = gen[np.where(ran_select>=pc),:]
return cross_np[0,:,:], no_cross_np[0,:,:]
def cross(gen):
for i in range(gen.shape[0]//2):
idex = random.randint(0, gen.shape[1]-1)
gen[[i*2,i*2+1],idex:] = gen[[i*2+1,i*2],idex:]
return gen
def mutation(gen,pm):
for i in range(gen.shape[0]):
ran_select = np.random.random(gen.shape[1])
gen[i,np.where(ran_select<pm)] = np.random.uniform(range_ab[0], range_ab[1],(1, len(np.where(ran_select<pm))))
return gen
if __name__ == '__main__':
range_ab = [-5.12, 5.12]
popusize = 100 # 种群个数
val_num = 20 # 自变量个数
epochs = 10000 # 迭代优化次数
# 初始化浮点数种群
gen = init(range_ab, val_num, popusize)
Y = [] # 记录当代最优结果
min_y = []
record = []
for i in range(epochs):
# 解码后的样本
# dec_gen = decoding(range_ab, bin_gen, bit_n,val_num)
# 计算适应度
y = cal_fitness(gen)
y_min = np.min(y)
Y.append(y_min)
if i == 0:
record_min = y_min
record = [i + 1, gen[np.argmin(y)],record_min] # 记录第几个epoch,最优个体,对应的最优结果
if y_min < record_min:
record_min = y_min
# a = i+1
record = [i+1, gen[np.argmin(y)],record_min] # 更新最优结果
min_y.append(record_min)
# 选择样本,轮盘赌
mate_np = mate_selection(y, gen, val_num)
# 判断能否交配
cross_np, no_cross_np = cross_able(mate_np, 0.6)
# 交配
crossed_gen = cross(cross_np)
# 变异
cross_gen = np.vstack((crossed_gen, no_cross_np))
gen = mutation(cross_gen, 0.01)
print('[迭代次数,最优个体,最优值] = ', record)
plt.title('Rastrigin函数')
# plt.ylim(-100, 3000)
# yticks= np.linspace(0, 3000, 5)
# plt.yticks(yticks)
plt.plot(range(epochs), Y, label='当前代最优解')
plt.plot(range(epochs), min_y, label='历史最优解')
plt.legend()
# 添加最优解在图中的位置
plt.scatter([record[0]], [record[2]], color='red')
# 添加坐标点信息
plt.text(record[0]-1000, record[2], (record[0], record[2]))
plt.show()
运行20次的结果如下:
从表可以看出,对于某些复杂高维问题,遗传算法还是过于简单,效能有效。
遗传算法是比较初级的算法,后续广大学者从很多方面改进了遗传算法,如给适应度函数加一些约束,使最优样本的选择更加合理,函数更容易收敛。或者改变变异和交叉机制,使算法优化过程能从局部极小值点跳出来等等。
附上以上5个函数的收敛曲线如下: