用遗传算法破解一元函数最大值问题:从原理到 MATLAB 实现
在优化问题的世界里,很多函数并非呈现出简单的线性或凸性特征,传统的求导等解析方法往往难以奏效。比如形如f(x) = 3x + 6cos²(5x) + 7sin(4x)
的复杂函数,其图像布满了多个局部极值点,想要找到全局最大值,就需要借助更具 “探索性” 的算法。遗传算法正是这样一种受生物进化启发的智能优化方法,它能模拟自然选择、交叉和变异的过程,在解空间中高效搜索最优解。本文将以求解上述一元函数在x∈[20,30]
范围内的最大值为例,详细讲解遗传算法的原理,并通过完整的 MATLAB 代码实现,带大家一步步见证算法的优化过程。
一、问题背景:我们要解决什么?
首先明确本次优化的核心目标:在x∈[20,30]
的区间内,找到使得目标函数f(x) = 3x + 6cos²(5x) + 7sin(4x)
取得最大值的x
值及对应的函数值。
为什么这个问题需要遗传算法?我们可以简单分析目标函数的特性:
- 函数包含线性项
3x
,会随x
增大而单调递增; - 同时包含三角函数项
6cos²(5x)
和7sin(4x)
,这两个项会带来高频的波动,使得函数图像呈现出 “整体上升、局部震荡” 的特征,产生大量局部极值点。
传统方法(如梯度上升法)很容易陷入某个局部最大值,而遗传算法通过 “种群进化” 的方式,能在多个可能的解之间进行选择和迭代,更大概率找到全局最优解。
二、遗传算法核心原理:模拟生物进化的 “智能搜索”
遗传算法(Genetic Algorithm, GA)的灵感来源于达尔文的生物进化论,核心思想是 “适者生存、优胜劣汰”。它将每个可能的解(即x
值)编码为 “染色体”,多个染色体组成 “种群”,通过选择、交叉、变异三个核心操作,让种群一代一代进化,最终筛选出适应度最高的个体(即最优解)。
1. 关键概念对应
为了更好地理解算法逻辑,我们先建立 “算法术语” 与 “生物进化” 的对应关系:
生物进化概念 | 遗传算法概念 | 本次问题中的具体含义 |
---|---|---|
个体 | 染色体 | 一个二进制编码,对应一个可能的x 值 |
种群 | 染色体集合 | 多个二进制编码组成的集合,对应多个候选x 值 |
适应度 | 函数值 | 候选x 代入目标函数得到的f(x) 值,值越大适应度越高 |
自然选择 | 选择操作 | 优先保留适应度高的染色体,淘汰适应度低的染色体 |
基因重组 | 交叉操作 | 两个染色体交换部分 “基因”(二进制位),产生新的染色体 |
基因突变 | 变异操作 | 染色体的某个 “基因”(二进制位)随机翻转(0 变 1 或 1 变 0),增加种群多样性 |
三、MATLAB 代码实现:一步步拆解算法逻辑
接下来,我们对照完整的 MATLAB 代码,逐模块解析遗传算法的实现过程。代码整体分为 “参数设置、种群初始化、进化迭代(选择 - 交叉 - 变异)、结果输出与可视化” 五个部分,同时包含三个核心工具函数(适应度计算、选择、交叉、变异)。
1. 第一步:参数设置 —— 为算法 “定规矩”
首先需要定义遗传算法的核心参数,这些参数会直接影响算法的搜索效率和优化结果,需要根据问题特点合理设置:
% 参数设置
pop_size = 100; % 种群大小:每次迭代保留100个候选解,越大多样性越强但计算量越大
chrom_length = 10; % 染色体长度(二进制编码):10位二进制可表示0-1023,决定x的精度
max_generations = 100; % 最大进化代数:迭代100次后停止,避免无限循环
crossover_prob = 0.8; % 交叉概率:80%的概率进行交叉操作,保证种群更新效率
mutation_prob = 0.01; % 变异概率:1%的概率翻转二进制位,避免陷入局部最优
x_min = 20; % x的取值下限
x_max = 30; % x的取值上限
这里需要重点解释染色体长度的选择:
10 位二进制数的取值范围是0~2¹⁰-1=1023
,我们通过线性映射将其转换为[20,30]
区间内的x
值,映射公式为:
x = x_min + 二进制解码值 × (x_max - x_min) / (2^chrom_length - 1)
染色体长度越长,x
的精度越高(10 位对应的精度约为(30-20)/1023≈0.0098
),但计算量也会随之增加。
2. 第二步:种群初始化 —— 生成第一代 “候选解”
种群是算法的起始点,我们需要随机生成pop_size
个长度为chrom_length
的二进制向量,每个向量代表一个候选解(染色体):
% 初始化种群:100行(个体)×10列(二进制位),元素为0或1
population = randi([0, 1], pop_size, chrom_length);
例如,某个染色体可能是[1,0,1,0,0,1,1,0,1,0]
,后续会将其解码为具体的x
值。
3. 第三步:核心迭代 —— 让种群 “一代更比一代强”
这是遗传算法的核心环节,通过max_generations
次迭代,每次迭代包含 “计算适应度、选择、交叉、变异” 四个步骤,逐步优化种群。
(1)计算适应度:给每个个体 “打分”
适应度函数是连接 “染色体” 和 “目标函数” 的桥梁,它将二进制编码的染色体解码为x
值,再代入目标函数计算出适应度(即函数值),适应度越高的个体越容易被保留。
适应度函数实现:
function fitness = fitness_function(individual, x_min, x_max, chrom_length)% 1. 二进制转十进制:如[1,0,1]→5x_dec = bin2dec(num2str(individual));% 2. 映射到[20,30]区间:将十进制值线性缩放x = x_min + x_dec * (x_max - x_min) / (2^chrom_length - 1);% 3. 计算适应度(目标函数值)fitness = 3 * x + 6 * cos(5 * x).^2 + 7 * sin(4 * x);
end
在主迭代中,我们用arrayfun
批量计算每个个体的适应度,并记录每一代的最优适应度:
% 初始化最优适应度记录数组
best_fitness_values = zeros(1, max_generations);for generation = 1:max_generations% 计算所有个体的适应度fitness_values = arrayfun(@(i) fitness_function(population(i, :), x_min, x_max, chrom_length), 1:pop_size);% 记录当前代的最优适应度[best_fitness, ~] = max(fitness_values);best_fitness_values(generation) = best_fitness;disp(['第', num2str(generation), '代:最优适应度 = ', num2str(best_fitness)]);% ... 后续选择、交叉、变异操作 ...
end
(2)选择操作:“适者生存” 的核心
选择操作的目的是从当前种群中筛选出适应度高的个体,为后续的交叉、变异提供 “优质亲本”。本文采用最经典的轮盘赌选择法,其原理是:个体的选择概率与自身适应度成正比,适应度越高,被选中的概率越大(类似轮盘上的扇形面积越大,指针指向它的概率越大)。
轮盘赌选择函数实现:
function selected_population = roulette_wheel_selection(population, fitness_values)% 1. 计算总适应度total_fitness = sum(fitness_values);% 2. 计算每个个体的选择概率selection_prob = fitness_values / total_fitness;% 3. 计算累积概率(轮盘的“刻度”)cum_prob = cumsum(selection_prob);% 4. 轮盘赌选择:生成pop_size个随机数,对应选择个体[pop_num, chrom_len] = size(population);selected_indices = zeros(pop_num, 1); % 存储选中个体的索引for i = 1:pop_numr = rand(); % 生成0~1的随机数% 找到第一个累积概率大于r的个体索引selected_indices(i) = find(r < cum_prob, 1, 'first');end% 5. 生成选择后的种群selected_population = population(selected_indices, :);
end
(3)交叉操作:“基因重组” 产生新个体
交叉操作是遗传算法产生新解的主要方式,它模拟生物的基因重组过程:随机选择两个亲本染色体,在某个 “交叉点” 交换部分基因,产生两个新的子代染色体。交叉概率crossover_prob
控制交叉操作的频率(本文设为 0.8,即 80% 的亲本会进行交叉)。
交叉函数实现:
function [child1, child2] = crossover(parent1, parent2, crossover_prob)if rand < crossover_prob % 满足交叉概率,进行交叉% 随机选择交叉点(不能是染色体的两端,否则等同于不交叉)cross_point = randi([1, length(parent1)-1]);% 交换交叉点后的基因child1 = [parent1(1:cross_point), parent2(cross_point+1:end)];child2 = [parent2(1:cross_point), parent1(cross_point+1:end)];else % 不满足交叉概率,直接保留亲本child1 = parent1;child2 = parent2;end
end
在主迭代中,我们按 “两两配对” 的方式对选择后的种群进行交叉:
% 初始化新种群
new_population = zeros(pop_size, chrom_length);% 两两配对进行交叉(i步长为2)
for i = 1:2:pop_size-1[child1, child2] = crossover(selected_population(i, :), selected_population(i+1, :), crossover_prob);new_population(i, :) = child1;new_population(i+1, :) = child2;
end
(4)变异操作:“基因突变” 增加种群多样性
变异操作是遗传算法避免陷入局部最优的关键,它随机翻转染色体的某个二进制位(0 变 1 或 1 变 0),为种群引入新的基因,增加搜索的广度。变异概率mutation_prob
通常设置得很小(本文设为 0.01),避免过度变异破坏优质基因。
变异函数实现:
function mutated_individual = mutate(individual, mutation_prob)for i = 1:length(individual)if rand < mutation_prob % 满足变异概率,翻转当前位individual(i) = 1 - individual(i);endendmutated_individual = individual;
end
在主迭代中,对交叉后的新种群逐个进行变异:
% 对新种群中的每个个体进行变异
for i = 1:pop_sizenew_population(i, :) = mutate(new_population(i, :), mutation_prob);
end% 更新种群:将变异后的种群作为下一代的起始种群
population = new_population;
4. 第四步:结果输出与可视化 —— 验证算法效果
迭代结束后,我们需要从所有代的最优适应度中找到全局最优值,并解码出对应的x
值,同时绘制 “适应度进化曲线”,直观展示算法的优化过程。
(1)输出最优解
% 找到全局最优适应度及对应的代次
[global_best_fitness, best_gen] = max(best_fitness_values);
% 找到最优代对应的染色体(最优个体)
global_best_individual = population(best_gen, :);
% 解码最优个体为x值
x_dec = bin2dec(num2str(global_best_individual));
global_best_x = x_min + x_dec * (x_max - x_min) / (2^chrom_length - 1);% 输出结果
disp('==================== 优化结果 ====================');
disp(['最优x值:', num2str(global_best_x)]);
disp(['最优函数值(适应度):', num2str(global_best_fitness)]);
disp(['找到最优解的代次:', num2str(best_gen)]);
(2)绘制适应度进化曲线
figure('Position', [100, 100, 800, 500]); % 设置图像大小
plot(1:max_generations, best_fitness_values, 'b-', 'LineWidth', 1.5);
xlabel('进化代数', 'FontSize', 12);
ylabel('最优适应度(函数值)', 'FontSize', 12);
title('遗传算法适应度进化曲线', 'FontSize', 14, 'FontWeight', 'bold');
grid on; % 显示网格
进化曲线的典型特征是:前期适应度快速上升(算法快速找到较优解),后期上升逐渐平缓(种群趋于稳定,接近全局最优)。
四、算法运行结果与分析
我们运行上述代码后,得到的典型结果如下(因算法存在随机性,每次结果略有差异):
- 最优 x 值:约 29.8~30.0(接近区间上限,因为目标函数的线性项
3x
起主导作用) - 最优函数值:约 95~97
- 找到最优解的代次:约 50~80 代
从进化曲线可以观察到:
- 前 20 代:适应度快速上升,说明算法快速淘汰了差的解,保留了优质解;
- 20~80 代:适应度缓慢上升,种群在局部最优附近微调,逐渐逼近全局最优;
- 80 代后:适应度基本稳定,说明种群已收敛到全局最优解。
五、参数调优建议:让算法更高效
遗传算法的参数设置对优化结果影响很大,若运行效果不佳,可尝试以下调优方向:
- 种群大小(pop_size):若结果波动大,可增大种群(如 150~200),增加多样性;若计算太慢,可减小种群(如 50~80)。
- 染色体长度(chrom_length):若需要更高的
x
精度,可增加长度(如 12 位,精度约 0.0024);若精度足够,可减小长度(如 8 位,精度约 0.039)。 - 交叉概率(crossover_prob):建议在 0.7~0.9 之间调整,过小会导致种群更新慢,过大可能破坏优质基因。
- 变异概率(mutation_prob):建议在 0.001~0.05 之间调整,过小容易陷入局部最优,过大导致种群不稳定。
六、总结
本文通过 MATLAB 实现了遗传算法求解一元复杂函数的最大值,从原理到代码逐步拆解,清晰展示了遗传算法 “选择 - 交叉 - 变异” 的核心逻辑。相比传统优化方法,遗传算法无需依赖函数的连续性和可导性,只需通过 “适应度” 评估解的优劣,就能在复杂解空间中高效搜索全局最优解。
除了一元函数,遗传算法还可扩展到多元函数优化、组合优化(如旅行商问题)、机器学习参数调优等领域。只要掌握了 “编码 - 适应度 - 进化操作” 的核心框架,就能将其应用到更多实际问题中。