智能计算hw1
1.遗传算法入门
简介
遗传算法是一种优化算法,受自然选择过程的启发。它们通常用于通过模仿自然选择和进化的过程来寻找优化和搜索问题的解决方案。基本思想是从候选解的种群开始,然后使用选择、交叉和变异的过程来生成新的候选解。选择过程偏向于适合问题的解决方案,而交叉和变异过程引入了新的遗传物质到种群中。随着时间的推移,候选解的种群不断演化和改进,收敛于问题的最优解集。遗传算法已被用于解决各种优化和搜索问题,从工程设计到金融预测。
一些在线资源,例如Melanie Mitchell的书《遗传算法简介》或网站“The Nature of Code”上的遗传算法教程系列。
自己的理解
为了求得一个函数 f ( x ) f(x) f(x)的最小值,在一些简单的情况下,我们可以得到解析解。比如 f ( x ) = a x 2 + b x + c f(x) = ax^2 + bx + c f(x)=ax2+bx+c,可以通过求导数为0的解来求得。对于一元二次函数 f ( x ) = a x 2 + b x + c f(x) = ax^2 + bx + c f(x)=ax2+bx+c,它的导数为 f ′ ( x ) = 2 a x + b f'(x) = 2ax + b f′(x)=2ax+b。当导数为0时,有 2 a x + b = 0 2ax + b = 0 2ax+b=0,解得 x = − b 2 a x = -\frac{b}{2a} x=−2ab。将 x x x 带入 f ( x ) f(x) f(x) 中,即可得到最小值 f ( − b 2 a ) = a ( b 2 a ) 2 − b 2 2 a + c f(-\frac{b}{2a}) = a(\frac{b}{2a})^2 - \frac{b^2}{2a} + c f(−2ab)=a(2ab)2−2ab2+c。
然而,在高维情况下,有可能我们要得到的解析解形式上比较复杂,甚至根本无法求得解析解而只能得到数值解,这个时候就能用上进化算法来求解这类问题。(例如三体中魏成就是试图用进化算法去解决三体问题)比如随便给一个
m
i
n
sin
(
x
1
2
+
x
2
2
)
/
x
1
2
+
x
2
2
+
e
c
o
s
(
2
∗
π
∗
x
1
)
+
c
o
s
(
2
∗
π
∗
x
2
)
)
/
2
−
2.71289
min \sin{(\sqrt{x_{1}^2 +x_{2}^2})/\sqrt{x_{1}^2 + x_{2}^2}+ e^{cos(2 * \pi * x_{1}) + cos(2 * \pi*x_{2}))/2}}-2.71289
minsin(x12+x22)/x12+x22+ecos(2∗π∗x1)+cos(2∗π∗x2))/2−2.71289
这个函数实在是复杂,怎么求解析解?
这时候就可以使用遗传算法,来获得接近全局最小值的一个近似最优解
下面介绍几个概念
简单来说,就是一组解x我们可以看成一个染色体(这个染色体包含了这组解的基因,可以代表这组解),我们用一串编码来表示它,这串编码我们用了二进制编码。二进制编码之后可以转换回这组解,但是在遗传过程我们操作的都是二进制编码。
而一旦这组解算出来的 f ( x ) f(x) f(x)更小,说明它更适应环境,在自然选择中就不容易被淘汰。
我们假设自然选择是一个法则 m a x f ( x ) max f(x) maxf(x),只要个体的 f ( x ) f(x) f(x)更大,那就更适应环境。这个 f ( x ) f(x) f(x)称为适应度函数。
而在遗传过程中会为了是优秀的基因被继承,则需要让两个优秀个体之间的基因进行交叉,这样就有可能得到更好的个体。
为了避免陷入局部最优,模拟自然的情况,就要让个体随机变异,变异不一定会产生优秀个体,但是好的变异使种群总体的质量更好,也就是更接近全局最优。
下面给出一个简单例子来帮助理解遗传算法的具体过程。
2.遗传算法的应用
解决目标函数
max − 1 ≤ x ≤ 1 1 − x 2 \max _{-1 \leq x \leq 1} 1-x^2 −1≤x≤1max1−x2
这个目标函数是要求得给定范围 [ − 1 , 1 ] [-1,1] [−1,1]之间 1 − x 2 1-x^2 1−x2的最大值。
遗传算法的步骤
-
确定适应度函数 由于后续的交叉和变异操作是基于概率进行的,如果适应度函数为负值可能会出现问题。
f i t n e s s ( x ) = x ⃗ − min x ⃗ + 1 0 − 3 fitness(x) = \vec{x} - \min{\vec{x}} + 10^{-3} fitness(x)=x−minx+10−3 -
初始化种群,随机生成100个个体,每个个体包含10个基因的种群(基因是由0,1组成的二进制序列)
% 初始化种群 pop = randi([0, 1], [POP_SIZE, DNA_SIZE]);%生成一个每一行就是指一个个体,(也指这个个体包含的dna
-
选择操作,使用的是轮盘赌算法
轮盘赌算法(Roulette Wheel Selection)是遗传算法中常用的一种选择算法,也称为比例选择算法(Fitness Proportionate Selection)。轮盘赌算法的基本思想是将每个个体的适应度值转化为选择概率,然后按照这些概率进行选择。具体来说,轮盘赌算法的选择过程如下:
计算每个个体的适应度值 f i t n e s s i fitness_i fitnessi。
计算每个个体的选择概率 p i p_i pi,其中 p i = f i t n e s s i ∑ j = 1 n f i t n e s s j p_i = \frac{fitness_i}{\sum_{j=1}^n fitness_j} pi=∑j=1nfitnessjfitnessi。
构造一个轮盘,将每个个体的选择概率对应到轮盘上的一个扇形区域。
生成一个随机数 r r r,落在哪个扇形区域,就选择哪个个体。
select = @(pop, fitness) pop(randsample(POP_SIZE, POP_SIZE, true, fitness/sum(fitness)),:);%
交叉
这里使用的是多点交叉,随机选择一个个体10个基因中的几个,将对应位置的基因替换掉父代个体对应位置的基因,得到新的子代。
% 交叉操作
%多点交叉:多点交叉或称广义交叉,是指在个体染色体中随机设置多个交叉点,然后进行基因交换。
% 其操作过程与单点交叉和两点交叉相类似。
% 如果多点交叉只选择了一个交叉点,那么多点交叉就变成了单点交叉。
function child = crossOver(parent, pop, CROSS_RATE, DNA_SIZE,POP_SIZE)
if rand() < CROSS_RATE
i_ = randi(POP_SIZE);
cross_points = randi([0, 1], [1, DNA_SIZE]);%cross_points 交叉点:指的是一个个体dna位置上为1的位置被选中
parent(logical(cross_points)) = pop(i_, logical(cross_points));%为什么要加logical,本来不就是01了嘛
end
child = parent;
end
变异
对一个个体的每个位置取0.003的变异概率(即0变成1,1变成0)
% 变异操作
function child = mutate(child, MUTATION_RATE, DNA_SIZE)
for point = 1:DNA_SIZE
if rand() < MUTATION_RATE
child(point) = 1 - child(point);%取反
end
end
end
解码
一个染色体含有多个基因(二进制)可是目标函数需要的是十进制,这就需要进行解码
% 将二进制DNA转换为十进制
function dec = decode(pop, DNA_SIZE, X_BOUND)
dec = (pop * 2.^(DNA_SIZE-1:-1:0)') / (2^DNA_SIZE-1) * (X_BOUND(2)-X_BOUND(1)) + X_BOUND(1);
end
迭代
对于每一代种群解码后计算适应度,根据适应度自然选择淘汰掉适应度低的个体,对于剩余个体进行交叉变异,得到新的子代
% 迭代
for i = 1:N_GENERATIONS%循环n代每一代是对整个群体进行操作
F_values = F(decode(pop, DNA_SIZE, X_BOUND));%得到当前的函数值
fitness = get_fitness(F_values);
pop = select(pop, fitness);%自然选择,淘汰掉适应度低的个体
pop_copy = pop;
for j = 1:POP_SIZE%对每一个个体交叉变异,替换掉之前的
parent = pop(j,:);
child = crossOver(parent, pop_copy, CROSS_RATE, DNA_SIZE,POP_SIZE);
child = mutate(child, MUTATION_RATE, DNA_SIZE);
pop(j,:) = child;
end
% F_values = F(decode(pop, DNA_SIZE, X_BOUND));
end
% 输出结果找到种群中适应度最高的个体
[~, idx] = max(F_values);
disp(['最优解: ', num2str(decode(pop(idx,:), DNA_SIZE, X_BOUND))]);
下面是主函数完整代码
clc
clear
% %解法一,使用ga工具箱
%
% % Define the fitness function定义适应度函数
% fitnessFunction = @(x) (1 - x^2);
%
% % Set the number of variables
% nvars = 1;
%
% % Set the options for the genetic algorithm
% options = optimoptions('ga','PlotFcn',@gaplotbestf);
%
% %constrain
%
% % Run the genetic algorithm
% [x,fval] = ga(fitnessFunction,nvars,[],[],[],[],-1,1,[],options);
%解法2,自己实现:
% 遗传算法参数
POP_SIZE = 100; % 种群大小
CROSS_RATE = 0.8; % 交叉概率
MUTATION_RATE = 0.003; % 变异概率
N_GENERATIONS = 400; % 迭代次数
DNA_SIZE = 10; % DNA序列长度
X_BOUND = [-1, 1]; % 解空间范围
% 目标函数
F = @(x) 1 - x.^2;
% 适应度函数
%
get_fitness = @(pred) pred + 1e-3 - min(pred);%类似于"标准化"元素都变为正值,加上0.001是为了不出现0,似乎是因为有负数的话后面繁殖的时候会出现问题
% 选择操作
% 轮盘赌算法
%使用randsample实现随机采样,第一个POP_SIZE指的是当前待抽样的总体
%第二个POP_SIZE指的是要抽多少个出来
%true指的是有放回的操作
%fitness/sum(fitness)是指每一项被抽到的概率
select = @(pop, fitness) pop(randsample(POP_SIZE, POP_SIZE, true, fitness/sum(fitness)),:);%
% 初始化种群
pop = randi([0, 1], [POP_SIZE, DNA_SIZE]);%生成一个每一行就是指一个个体,(也指这个个体包含的dna
% 迭代
for i = 1:N_GENERATIONS%循环n代每一代是对整个群体进行操作
F_values = F(decode(pop, DNA_SIZE, X_BOUND));%得到当前的函数值
fitness = get_fitness(F_values);
pop = select(pop, fitness);%自然选择,淘汰掉适应度低的个体
pop_copy = pop;
for j = 1:POP_SIZE%对每一个个体交叉变异,替换掉之前的
parent = pop(j,:);
child = crossOver(parent, pop_copy, CROSS_RATE, DNA_SIZE,POP_SIZE);
child = mutate(child, MUTATION_RATE, DNA_SIZE);
pop(j,:) = child;
end
% F_values = F(decode(pop, DNA_SIZE, X_BOUND));
end
% 输出结果找到种群中适应度最高的个体
[~, idx] = max(F_values);
disp(['最优解: ', num2str(decode(pop(idx,:), DNA_SIZE, X_BOUND))]);